diff --git a/LICENSE b/LICENSE index a4e9dc9..83b72da 100644 --- a/LICENSE +++ b/LICENSE @@ -1,16 +1,24 @@ -MIT No Attribution - -Copyright - -Permission is hereby granted, free of charge, to any person obtaining a copy of this -software and associated documentation files (the "Software"), to deal in the Software -without restriction, including without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +BSD 2-Clause License + +Copyright (c) 2023, Trevor Hall + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index b0f824d..affbff6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,63 @@ -# CS9236 +# CS9236 Library -A library for operating the Crystal CS9236 Wavetable Synthesizer chip. \ No newline at end of file +The CS9236 library provides an interface for working with the Crystal CS9236 Wavetable Synthesizer Chip. It allows you to control and communicate with the chip using an Arduino or compatible microcontroller. This library provides an easy-to-use API for sending MIDI messages and commands to the Crystal CS9236. + +## Installation + +To use this library in your Arduino project, follow these steps: + +1. In the Arduino IDE, click on "Sketch" -> "Include Library" -> "Add .ZIP Library" and select the downloaded ZIP file of the CS9236 library. + +2. Now, you can include the library in your Arduino sketch by adding the following line at the top of your sketch: + #include + +## Usage + +To use the CS9236 library, you'll need to create an instance of the `CS9236` class and initialize it with the appropriate parameters. I've included an example project to help demonstrate this. + +## API Reference + +### Constructor + +- `CS9236(uint8_t rs, Stream *comm)`: Initializes an instance of the CS9236 class with the specified reset pin (`rs`) and communication stream (`comm`). + +### Basic MIDI Messages + +- `NoteOn(uint8_t channel, uint8_t note_number, uint8_t velocity)`: Sends a Note On message. +- `NoteOff(uint8_t channel, uint8_t note_number)`: Sends a Note Off message. +- `ProgramChange(uint8_t channel, uint8_t program)`: Sends a Program Change message. +- `SetChannelPressure(uint8_t channel, uint8_t pressure)`: Sets the channel pressure. +- `SetPitchBend(uint8_t channel, uint16_t pitchbend)`: Sets the pitch bend. +- `SetModWheel(uint8_t channel, uint8_t depth)`: Sets the modulation wheel. +- `SetVolume(uint8_t channel, uint8_t volume)`: Sets the channel volume. +- `SetPan(uint8_t channel, uint8_t value)`: Sets the pan position. +- `SetExpression(uint8_t channel, uint8_t value)`: Sets the expression. +- `SetPedal(uint8_t channel, bool pedaldown)`: Sets the pedal. +- `SetReverb(uint8_t channel, uint8_t value)`: Sets the reverb level. +- `SetChorus(uint8_t channel, uint8_t value)`: Sets the chorus level. + +### RPN Messages + +- `SelectRPN(uint8_t channel, uint8_t rpn)`: Selects the RPN (Registered Parameter Number). +- `SetPitchBendSensitivity(uint8_t channel, uint16_t value)`: Sets the pitch bend sensitivity. +- `SetFineTuning(uint8_t channel, uint16_t value)`: Sets the fine tuning. +- `SetCoarseTuning(uint8_t channel, uint16_t value)`: Sets the coarse tuning. + +### Channel Mode Messages + +- `AllSoundsOff(uint8_t channel)`: Sends an All Sounds Off message. +- `ResetAll(uint8_t channel)`: Sends a Reset All Controllers message. +- `AllNotesOff(uint8_t channel)`: Sends an All Notes Off message. + +### Special Functions + +- `EnablePressureRecognition()`: Enables pressure recognition. +- `DisablePressureRecognition()`: Disables pressure recognition. +- `EnableTestTone()`: Enables the test tone. +- `DisableTestTone()`: Disables the test tone. + +For detailed information on the usage of each function, please refer to the header file `CS9236.h`. + +## License + +This library is released under the BSD License. Please see the LICENSE file for more details. \ No newline at end of file diff --git a/cs9236.cpp b/cs9236.cpp new file mode 100644 index 0000000..943b18a --- /dev/null +++ b/cs9236.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2023 Trevor Hall + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD license. See the LICENSE file for details. + */ +#include +#include "cs9236.h" + +CS9236::CS9236(uint8_t rs, Stream * comm) { + //constructor + _rs = rs; + _comm = comm; +} + +void CS9236::Init(){ + digitalWrite(_rs, LOW); + delay(1); + digitalWrite(_rs, HIGH); +} +void CS9236::Shutdown(){ + digitalWrite(_rs, LOW); +} +void CS9236::SystemReset(){ + _comm->write(CS_SYSTEM_RESET); +} +void CS9236::Poll(){ //For active sensing, if Active Sensing is on, either a note/message, or this polling message must be sent no later than 372 ms, or else the All sounds off and reset all controllers function will be executed + _comm->write(CS_ACTIVE_SENSE); +} +void CS9236::NoteOn(uint8_t channel, uint8_t note_number, uint8_t velocity){ + if(channel > 15) return; //only channels 0 through 15 are allowed + uint8_t midipacket[3] = {CS_NOTE_ON | channel, note_number, velocity};; + _comm->write(midipacket, 4); +} +void CS9236::NoteOff(uint8_t channel, uint8_t note_number){ + if(channel > 15) return; + uint8_t midipacket[3] = {CS_NOTE_OFF | channel, note_number, 0x00}; + _comm->write(midipacket, 3); +} +void CS9236::ProgramChange(uint8_t channel, uint8_t program){ + if(channel > 15) return; + uint8_t midipacket[2] = {CS_PROGRAM_CHANGE | channel, program}; + _comm->write(midipacket, 2); +} +void CS9236::SetChannelPressure(uint8_t channel, uint8_t pressure){ + if(channel > 15) return; + uint8_t midipacket[2] = {CS_CHANNEL_PRESSURE | channel, pressure}; + _comm->write(midipacket, 2); +} +void CS9236::SetPitchBend(uint8_t channel, uint16_t pitchbend){ + if(channel > 15) return; + uint8_t midipacket[3] = {CS_PITCH_BEND | channel, (pitchbend >> 8) & 0xff, pitchbend & 0xff}; + _comm->write(midipacket, 3); +} +void CS9236::SetModWheel(uint8_t channel, uint8_t depth){ + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_CC_MODWHEEL, depth}; + _comm->write(midipacket, 3); +} +void CS9236::SetVolume(uint8_t channel, uint8_t volume){ + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_CC_VOLUME, volume}; + _comm->write(midipacket, 3); +} +void CS9236::SetPan(uint8_t channel, uint8_t value) {//0 - left, 64 - center, 127 - right + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_CC_PAN, value}; + _comm->write(midipacket, 3); +} +void CS9236::SetExpression(uint8_t channel, uint8_t value){ + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_CC_EXPRESSION, value}; + _comm->write(midipacket, 3); +} +void CS9236::SetPedal(uint8_t channel, bool pedaldown){ // 0 through 63 = off, 64 through 127 =on | Sostenuto + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_CC_PEDAL, pedaldown ? 0x7f : 0x00}; + _comm->write(midipacket, 3); +} +void CS9236::SetReverb(uint8_t channel, uint8_t value){ + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_CC_REVERB, value}; + _comm->write(midipacket, 3); +} +void CS9236::SetChorus(uint8_t channel, uint8_t value){ + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_CC_CHORUS, value}; + _comm->write(midipacket, 3); +} + +//RPN messages +void CS9236::SelectRPN(uint8_t channel, uint8_t rpn){ + if(channel > 15) return; + uint8_t msbpacket[3] = {0xB0 | channel, 0x65, 0x00}; + _comm->write(msbpacket, 3); + uint8_t lsbpacket[3] = {0xB0 | channel, 0x64, rpn}; + _comm->write(lsbpacket, 3); +} +void CS9236::SetPitchBendSensitivity(uint8_t channel, uint16_t value){ + SelectRPN(channel, CS_RPN_PITCHBEND_SENSITIVITY); + uint8_t msbpacket[3] = {0xB0 | channel, CS_CC_RPN_MSB, (value >> 8)}; + _comm->write(msbpacket, 3); + uint8_t lsbpacket[3] = {0xB0 | channel, CS_CC_RPN_LSB, value & 0xff}; + _comm->write(lsbpacket, 3); +} +void CS9236::SetFineTuning(uint8_t channel, uint16_t value){ + SelectRPN(channel, CS_RPN_FINE_TUNING); + uint8_t msbpacket[3] = {0xB0 | channel, CS_CC_RPN_MSB, (value >> 8)}; + _comm->write(msbpacket, 3); + uint8_t lsbpacket[3] = {0xB0 | channel, CS_CC_RPN_LSB, value & 0xff}; + _comm->write(lsbpacket, 3); +} +void CS9236::SetCoarseTuning(uint8_t channel, uint16_t value){ + SelectRPN(channel, CS_RPN_COARSE_TUNING); + uint8_t msbpacket[3] = {0xB0 | channel, CS_CC_RPN_MSB, (value >> 8)}; + _comm->write(msbpacket, 3); + uint8_t lsbpacket[3] = {0xB0 | channel, CS_CC_RPN_LSB, value & 0xff}; + _comm->write(lsbpacket, 3); +} + +//channel mode messages +void CS9236::AllSoundsOff(uint8_t channel){ + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_ALL_SOUNDS_OFF, 0x00}; + _comm->write(midipacket, 3); +} +void CS9236::ResetAll(uint8_t channel){ + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_RESET_ALL, 0x00}; + _comm->write(midipacket, 3); +} +void CS9236::AllNotesOff(uint8_t channel){ + if(channel > 15) return; + uint8_t midipacket[3] = {0xB0 | channel, CS_ALL_NOTES_OFF, 0x00}; + _comm->write(midipacket, 3); +} +void CS9236::EnablePressureRecognition(){ + uint8_t midipacket[8] = {0xF0, 0x00, 0x01, 0x02, 0x01, 0x01, 0x01, 0xF7}; + _comm->write(midipacket, 8); +} +void CS9236::DisablePressureRecognition(){ + uint8_t midipacket[8] = {0xF0, 0x00, 0x01, 0x02, 0x01, 0x01, 0x02, 0xF7}; + _comm->write(midipacket, 8); +} +void CS9236::EnableTestTone(){ + uint8_t midipacket[8] = {0xF0, 0x00, 0x01, 0x02, 0x01, 0x01, 0x03, 0xF7}; + _comm->write(midipacket, 8); +} +void CS9236::DisableTestTone(){ + uint8_t midipacket[8] = {0xF0, 0x00, 0x01, 0x02, 0x01, 0x01, 0x04, 0xF7}; + _comm->write(midipacket, 8); +} \ No newline at end of file diff --git a/cs9236.h b/cs9236.h new file mode 100644 index 0000000..1a4ce1d --- /dev/null +++ b/cs9236.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 Trevor Hall + * All rights reserved. + * + * This software may be modified and distributed under the terms + * of the BSD license. See the LICENSE file for details. + */ +#include "Stream.h" +#ifndef CS9236_H_ +#define CS9236_H_ + +#define CS_ACTIVE_SENSE 0xFE +#define CS_SYSTEM_RESET 0xFF + +#define CS_NOTE_ON 0x90 +#define CS_NOTE_OFF 0x80 + +#define CS_VOLUME 0xB0 +#define CS_PROGRAM_CHANGE 0xC0 +#define CS_CHANNEL_PRESSURE 0xD0 +#define CS_PITCH_BEND 0xE0 + +#define CS_CC_MODWHEEL 0x01 +#define CS_CC_RPN_MSB 0x06 +#define CS_CC_RPN_LSB 0x26 +#define CS_CC_VOLUME 0x07 +#define CS_CC_PAN 0x0A +#define CS_CC_EXPRESSION 0x0B +#define CS_CC_PEDAL 0x40 +#define CS_CC_REVERB 0x5B +#define CS_CC_CHORUS 0x5D + +#define CS_RPN_PITCHBEND_SENSITIVITY 0x00 +#define CS_RPN_FINE_TUNING 0x01 +#define CS_RPN_COARSE_TUNING 0x02 + +#define CS_ALL_SOUNDS_OFF 0x78 +#define CS_RESET_ALL 0x79 +#define CS_ALL_NOTES_OFF 0x7B +#define CS_OMNI_MODE_OFF 0x7C +#define CS_OMNI_MODE_ON 0x7D +#define CS_MONO_MODE_ON 0x7E +#define CS_POLY_MODE_ON 0x7F + + +class CS9236 { + public: + CS9236(uint8_t rs, Stream * comm); + + void Init(); + void Shutdown(); + void SystemReset(); + void Poll(); + void NoteOn(uint8_t channel, uint8_t note_number, uint8_t velocity); + void NoteOff(uint8_t channel, uint8_t note_number); + void ProgramChange(uint8_t channel, uint8_t program); + void SetChannelPressure(uint8_t channel, uint8_t pressure); + void SetPitchBend(uint8_t channel, uint16_t pitchbend); + void SetModWheel(uint8_t channel, uint8_t depth); + void SetVolume(uint8_t channel, uint8_t volume); + void SetPan(uint8_t channel, uint8_t value); //0 - left, 64 - center, 127 - right + void SetExpression(uint8_t channel, uint8_t value); + void SetPedal(uint8_t channel, bool pedaldown); // 0 through 63 = off, 64 through 127 =on | Sostenuto + void SetReverb(uint8_t channel, uint8_t value); + void SetChorus(uint8_t channel, uint8_t value); + + void SelectRPN(uint8_t channel, uint8_t rpn); + void SetPitchBendSensitivity(uint8_t channel, uint16_t value); //RPN messages + void SetFineTuning(uint8_t channel, uint16_t value); + void SetCoarseTuning(uint8_t channel, uint16_t value); + + //channel mode messages + void AllSoundsOff(uint8_t channel); + void ResetAll(uint8_t channel); + void AllNotesOff(uint8_t channel); + + void EnablePressureRecognition();//F0H 00H 01H 02H 01H 01H 01H F7H + void DisablePressureRecognition();//F0H 00H 01H 02H 01H 01H 02H F7H + + void EnableTestTone();//F0H 00H 01H 02H 01H 01H 03H F7H + void DisableTestTone();//F0H 00H 01H 02H 01H 01H 04H F7H + private: + uint8_t _rs; + Stream * _comm; +}; +#endif \ No newline at end of file diff --git a/examples/SimpleDrumMachine/SimpleDrumMachine.ino b/examples/SimpleDrumMachine/SimpleDrumMachine.ino new file mode 100644 index 0000000..ce41c95 --- /dev/null +++ b/examples/SimpleDrumMachine/SimpleDrumMachine.ino @@ -0,0 +1,65 @@ +#include +#include + +//The reset pin for the Wavetable Chip. I use pin 7 in my projects +#define wavetable_rs 7 +#define rhythm_width 16 + +CS9236 wavetableChip(wavetable_rs, &Serial1); + +//For the simple drum machine loop. +const uint32_t rhythm_frames[rhythm_width] = {0x3c000030, 0x00000000, 0x00000010, 0x00000000, 0x00e00030, 0x00000000, 0x00000020, 0x0000002c, 0x00000034, 0x00000000, 0x34000024, 0x00000000, 0x00f00034, 0x00000000, 0x00000024, 0x2c000000}; +byte master_volume = 127; +byte rhythm_ticks = 0; +uint32_t rhythm_waitmicros = 150000; +uint32_t last_sequencer_update = 0; +byte note_values[5] = {42,51,47,38,36}; + +//Updates the sequencer/drum machine, if percussion events are triggered for the current from it will send that information to the Wavetable chip +void updateSequencer(uint32_t microseconds){ + //Only increment the rhythm frame if enough time has passed + if(microseconds - last_sequencer_update > rhythm_waitmicros){ + last_sequencer_update = microseconds; + + //Establish which frame we are on in the drum groove + byte frameindex = rhythm_ticks++ % rhythm_width; + uint32_t current_frame = rhythm_frames[frameindex] & 0x3fffffff; //clear top 2 bits + + // if a frame is 0, that means nothing happens at all, so we can skip it + if(current_frame > 0){ + //I store my frames as 5 values with 6 bits each, this fits into a 32 bit unsigned integer leaving the most significant 2 bits blank, I currently do not use those bits for any purpose + for(int i = 0; i < 5; i++){ + //extract the 6 bit value for this instrument, processing only non-zero values + byte val6 = (current_frame >> (i * 6)) & 0x3f; + if(val6 != 0) { + float velo = ((float)val6) / 63.0; + byte midivelo = constrain(127.0 * (velo), 0, 127); + + //Trigger the percussion instrument with the specified velocity. We do not need a corresponding "NoteOff" call since this is percussion + wavetableChip.NoteOn(0x09, note_values[i], midivelo); + } + } + } + } +} + +void setup() { + //Set the appropriate pinmode for the reset pin + pinMode(wavetable_rs, OUTPUT); + + //Begin communication through Serial, I use Serial1 assuming we want Serial to be free and clear for debugging or other uses + Serial1.begin(31250); + while(!Serial1) delayMicroseconds(10); + + //Set up the wavetable chip + wavetableChip.Init(); + //Reset all values on Midi Channel 10, please note that while this value is "9", since Midi Channel 1 is "0", Midi Channel 10 would be "9" + wavetableChip.ResetAll(0x09); + //Sets the volume of Channel 10 to master_volume, 0 is silent and 127 is maximum loudness + wavetableChip.SetVolume(0x09, constrain(master_volume, 0, 127)); +} + +void loop() { + //pumps the drum-machine loop + updateSequencer(micros()); +}