// DubSiren firmware for Sitka Instruments WS-1.0 // by Oleksiy Hrachov // // Although the code designed to work on Sitka Instruments WS-1.0 synth, it should // be pretty easy to adapt to run on other arduino/mozzi-based setups // // This code is licenced under GPL v3 or later //ToDo: //Transform Flamingo into DubSiren :) //✅ Square oscilator //✅ Square/Sine pitch LFO on switch //✅ Filter //⬜ Delay? #include #include #include #include #include #include //#include #include #include #include #include #include #include //Band limited oscilator tables for aliasing-free Meta Oscilator #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Oscil aSq90(SQUARE_MAX_90_AT_16384_512_DATA); Oscil aSq101(SQUARE_MAX_101_AT_16384_512_DATA); Oscil aSq122(SQUARE_MAX_122_AT_16384_512_DATA); Oscil aSq138(SQUARE_MAX_138_AT_16384_512_DATA); Oscil aSq154(SQUARE_MAX_154_AT_16384_512_DATA); Oscil aSq174(SQUARE_MAX_174_AT_16384_512_DATA); Oscil aSq210(SQUARE_MAX_210_AT_16384_512_DATA); Oscil aSq264(SQUARE_MAX_264_AT_16384_512_DATA); Oscil aSq327(SQUARE_MAX_327_AT_16384_512_DATA); Oscil aSq431(SQUARE_MAX_431_AT_16384_512_DATA); Oscil aSq546(SQUARE_MAX_546_AT_16384_512_DATA); Oscil aSq744(SQUARE_MAX_744_AT_16384_512_DATA); Oscil aSq910(SQUARE_MAX_910_AT_16384_512_DATA); Oscil aSq1170(SQUARE_MAX_1170_AT_16384_512_DATA); Oscil aSq1638(SQUARE_MAX_1638_AT_16384_512_DATA); Oscil aSq2730(SQUARE_MAX_2730_AT_16384_512_DATA); Oscil aSq8192(SQUARE_MAX_8192_AT_16384_512_DATA); MetaOscil aSquare {&aSq90, &aSq101, &aSq122, &aSq138, &aSq154, &aSq174, &aSq210, &aSq264, &aSq327, &aSq431, &aSq546, &aSq744, &aSq1170, &aSq1638, &aSq2730, &aSq8192}; //Settings const int pitchSubSteps = 32; //set how many steps are there between semitones. set to 1 to quantize to semitones const int driveAmount = 450; #define MIDI_CHANNEL MIDI_CHANNEL_OMNI #define MOZZI_CONTROL_RATE 1024 //Hardware Definitions #define Knob1 A6 //LFO Frequency #define Knob2 A4 //LFO Intensity #define Knob3 A2 //LPF Resonance #define Knob4 A0 //LPF Cutoff #define KnobA A5 //Attack #define KnobDR A3 //Decay and Release #define KnobS A1 //Sustain #define CVIn A7 //CV input and Pitch knob #define GateIn 10 #define EnvSwitch 11 #define DroneSwitch 12 #define LED 5 #define MOZZI_ANALOG_READ_RESOLUTION 10 MIDI_CREATE_DEFAULT_INSTANCE(); IntMap kMapNote( 0, 4095, 24 * pitchSubSteps, 84 * pitchSubSteps ); IntMap kMapLFOSpeed( 0, 1023, 500, 20000 ); IntMap kMapFreqMod( 0, 1023, 0, 1023 ); IntMap kMapResonance( 0, 1023, 200, 1 ); IntMap kMapCutoff( 0, 1023, 60, 3600 ); IntMap kMapAttack( 0, 1023, 0, 80 ); IntMap kMapDecayRelease( 0, 1023, 8, 160 ); IntMap kMapSustain( 0, 1023, 0, 255 ); //Oscil aSquare(SQUARE_ANALOGUE512_DATA); Oscil kSquareLFO(SQUARE_NO_ALIAS_2048_DATA); Oscil kSineLFO(SIN2048_DATA); ADSR envelope; StateVariable lpf; OverSample kOverSamplePitch; //Global variables byte gain; bool MIDINotePlaying; bool gateIsHigh = false; float noteFreq; void MIDINoteOn(byte channel, byte note, byte velocity) { noteFreq = mtof((int) note); envelope.noteOn(); MIDINotePlaying = true; digitalWrite(LED, LOW); } void MIDINoteOff(byte channel, byte note, byte velocity) { envelope.noteOff(); digitalWrite(LED, HIGH); } long softClip(long input) { int threshold = 2048; if (input < -threshold) { return -threshold + (input + threshold) / 4; } else if (input > threshold) { return threshold + (input - threshold) / 4; } else { return input; } } void setup(){ pinMode(LED, OUTPUT); pinMode(GateIn, INPUT_PULLUP); pinMode(EnvSwitch, INPUT_PULLUP); pinMode(DroneSwitch, INPUT_PULLUP); MIDI.setHandleNoteOn(MIDINoteOn); MIDI.setHandleNoteOff(MIDINoteOff); MIDI.begin(MIDI_CHANNEL); aSquare.setCutoffFreqs(90, 101, 122, 138, 154, 174, 210, 264, 327, 431, 546, 744, 1170, 1638, 2730, 8192); startMozzi(); envelope.setAttackLevel(255); digitalWrite(LED, HIGH); } void updateControl(){ //Get Control Values int CVInVal = mozziAnalogRead(CVIn); int knob1Val = mozziAnalogRead(Knob1); int knob2Val = mozziAnalogRead(Knob2); int knob3Val = mozziAnalogRead(Knob3); int knob4Val = mozziAnalogRead(Knob4); int knobAVal = mozziAnalogRead(KnobA); int knobDRVal = mozziAnalogRead(KnobDR); int knobSVal = mozziAnalogRead(KnobS); bool droneSwitchVal = digitalRead(DroneSwitch); bool envSwitchVal = digitalRead(EnvSwitch); bool gateInVal = !digitalRead(GateIn); //Remap the values and assign to parameter float LFOSpeed = kMapLFOSpeed(knob1Val) / 100; float expLFOSpeed = (float) LFOSpeed * LFOSpeed / 800000; float freqMod = (float) kMapFreqMod(knob2Val) / 1023; float modSpeed = LFOSpeed; int resonance = kMapResonance(knob3Val); int cutoff = kMapCutoff(knob4Val); //Set pitch and play notes on trigger if (!MIDINotePlaying) { int oversampledCVInVal = kOverSamplePitch.next(CVInVal); noteFreq = mtof((float) kMapNote(oversampledCVInVal) / pitchSubSteps); //noteFreq = mtof((float) kMapNote(CVInVal << 2) / pitchSubSteps); digitalWrite(LED, !gateInVal); if (gateInVal && !gateIsHigh) { gateIsHigh = true; envelope.noteOn(); } else if (!gateInVal && gateIsHigh) { gateIsHigh = false; envelope.noteOff(); } } else if (MIDINotePlaying && !envelope.playing()) { MIDINotePlaying = false; } //Update Filter settings lpf.setResonance(resonance); lpf.setCentreFreq(cutoff); //Update Envelope Settings int attackTime = kMapAttack(knobAVal); int decayReleaseTime = kMapDecayRelease(knobDRVal); int sustainLevel = kMapSustain(knobSVal); envelope.setDecayLevel(sustainLevel); envelope.setTimes(attackTime, decayReleaseTime, 30000, decayReleaseTime); //30000 is so the note will sustain 30 seconds unless a noteOff comes //LFO stuff //Set oscillator frequencies kSquareLFO.setFreq(modSpeed); kSineLFO.setFreq(modSpeed); int LFOValue; if(envSwitchVal) { LFOValue = kSquareLFO.next()-127; //-109 to bring the rangt to ~ 0-192 } else { LFOValue = kSineLFO.next()-127; } //Set Oscilator Frequency float oscFreq = noteFreq - noteFreq * LFOValue * freqMod * 3 / 256; //UFix<16,16> oscFreq = aSquare.setFreq(oscFreq); envelope.update(); int env = envelope.next(); if(!droneSwitchVal) { gain = env; } else { gain = 255; } MIDI.read(); } AudioOutput updateAudio(){ long signal = aSquare.next(); signal = lpf.next(signal); //filter signal = (signal * gain); //envelope //signal = softClip((signal * (127 + driveAmount)) >> 8); //overdrive return MonoOutput::fromNBit(17, signal).clip(); } void loop(){ audioHook(); }