Files
WS-1.0-Firmwares/Acid/Acid.ino
2026-09-15 23:39:09 +03:00

338 lines
14 KiB
C++

// Acid synth 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:
// debug accent handling that overloads the filter, also accent can be too short or too long if it's relying on midi note
// add overdrive
// frequency from CV (noteFreq) could be Q16n16 for consistency
#include <Mozzi.h>
#include <MIDI.h>
#include <Oscil.h>
#include <MetaOscil.h>
#include <ResonantFilter.h>
#include <Ead.h>
#include <Portamento.h>
#include <mozzi_midi.h>
#include <IntMap.h>
#include <FixMath.h>
#include <EEPROM.h>
#include "config.h"
//Band limited SQUARE oscilator tables
#include <tables/BandLimited_SQUARE/512/square_max_90_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_101_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_122_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_138_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_154_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_174_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_210_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_264_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_327_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_431_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_546_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_744_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_910_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_1170_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_1638_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_2730_at_16384_512_int8.h>
#include <tables/BandLimited_SQUARE/512/square_max_8192_at_16384_512_int8.h>
Oscil <SQUARE_MAX_90_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq90(SQUARE_MAX_90_AT_16384_512_DATA);
Oscil <SQUARE_MAX_101_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq101(SQUARE_MAX_101_AT_16384_512_DATA);
Oscil <SQUARE_MAX_122_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq122(SQUARE_MAX_122_AT_16384_512_DATA);
Oscil <SQUARE_MAX_138_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq138(SQUARE_MAX_138_AT_16384_512_DATA);
Oscil <SQUARE_MAX_154_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq154(SQUARE_MAX_154_AT_16384_512_DATA);
Oscil <SQUARE_MAX_174_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq174(SQUARE_MAX_174_AT_16384_512_DATA);
Oscil <SQUARE_MAX_210_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq210(SQUARE_MAX_210_AT_16384_512_DATA);
Oscil <SQUARE_MAX_264_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq264(SQUARE_MAX_264_AT_16384_512_DATA);
Oscil <SQUARE_MAX_327_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq327(SQUARE_MAX_327_AT_16384_512_DATA);
Oscil <SQUARE_MAX_431_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq431(SQUARE_MAX_431_AT_16384_512_DATA);
Oscil <SQUARE_MAX_546_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq546(SQUARE_MAX_546_AT_16384_512_DATA);
Oscil <SQUARE_MAX_744_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq744(SQUARE_MAX_744_AT_16384_512_DATA);
Oscil <SQUARE_MAX_910_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq910(SQUARE_MAX_910_AT_16384_512_DATA);
Oscil <SQUARE_MAX_1170_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq1170(SQUARE_MAX_1170_AT_16384_512_DATA);
Oscil <SQUARE_MAX_1638_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq1638(SQUARE_MAX_1638_AT_16384_512_DATA);
Oscil <SQUARE_MAX_2730_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq2730(SQUARE_MAX_2730_AT_16384_512_DATA);
Oscil <SQUARE_MAX_8192_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSq8192(SQUARE_MAX_8192_AT_16384_512_DATA);
MetaOscil<SQUARE_MAX_90_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE, 16> aSquare {&aSq90, &aSq101, &aSq122, &aSq138, &aSq154, &aSq174, &aSq210, &aSq264, &aSq327, &aSq431, &aSq546, &aSq744, &aSq1170, &aSq1638, &aSq2730, &aSq8192};
//Band limited SAW oscilator tables
#include <tables/BandLimited_SAW/512/saw_max_138_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_154_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_182_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_240_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_292_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_341_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_390_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_431_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_512_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_630_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_744_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_910_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_1170_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_1638_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_2730_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_4096_at_16384_512_int8.h>
#include <tables/BandLimited_SAW/512/saw_max_8192_at_16384_512_int8.h>
Oscil <SAW_MAX_138_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw138(SAW_MAX_138_AT_16384_512_DATA);
Oscil <SAW_MAX_154_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw154(SAW_MAX_154_AT_16384_512_DATA);
Oscil <SAW_MAX_182_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw182(SAW_MAX_182_AT_16384_512_DATA);
Oscil <SAW_MAX_240_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw240(SAW_MAX_240_AT_16384_512_DATA);
Oscil <SAW_MAX_292_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw292(SAW_MAX_292_AT_16384_512_DATA);
Oscil <SAW_MAX_341_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw341(SAW_MAX_341_AT_16384_512_DATA);
Oscil <SAW_MAX_390_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw390(SAW_MAX_390_AT_16384_512_DATA);
Oscil <SAW_MAX_431_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw431(SAW_MAX_431_AT_16384_512_DATA);
Oscil <SAW_MAX_512_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw512(SAW_MAX_512_AT_16384_512_DATA);
Oscil <SAW_MAX_630_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw630(SAW_MAX_630_AT_16384_512_DATA);
Oscil <SAW_MAX_744_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw744(SAW_MAX_744_AT_16384_512_DATA);
Oscil <SAW_MAX_910_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw910(SAW_MAX_910_AT_16384_512_DATA);
Oscil <SAW_MAX_1170_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw1170(SAW_MAX_1170_AT_16384_512_DATA);
Oscil <SAW_MAX_1638_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw1638(SAW_MAX_1638_AT_16384_512_DATA);
Oscil <SAW_MAX_2730_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw2730(SAW_MAX_2730_AT_16384_512_DATA);
Oscil <SAW_MAX_4096_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw4096(SAW_MAX_4096_AT_16384_512_DATA);
Oscil <SAW_MAX_8192_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw8192(SAW_MAX_8192_AT_16384_512_DATA);
MetaOscil<SAW_MAX_138_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE, 16> aSaw {&aSaw138, &aSaw154, &aSaw182, &aSaw240, &aSaw292, &aSaw341, &aSaw390, &aSaw431, &aSaw512, &aSaw630, &aSaw744, &aSaw1170, &aSaw1638, &aSaw2730, &aSaw4096, &aSaw8192};
IntMap kMapNote( 0, 4095, 24 * pitchSubSteps, 84 * pitchSubSteps );
IntMap kMapResonance( 0, 1023, 0, 255 );
IntMap kMapCutoff( 0, 1023, 26, 100 );
IntMap kMapAttack( 0, 1023, 3, 100 );
IntMap kMapDecay( 0, 1023, 200, 2000 );
IntMap kMapEnvMod( 0, 1023, 0, 160 );
IntMap kMapDrive( 0, 1023, 0, 400 );
Ead kEnvelope(MOZZI_CONTROL_RATE);
LowPassFilter lpfr;
Portamento <MOZZI_CONTROL_RATE> kGlide;
//Global variables
byte gain;
bool MIDINotePlaying;
bool MIDINoteFaded = true;
bool gateIsHigh = false;
float noteFreq;
bool envSwitchVal;
bool droneSwitchVal;
byte MIDIChannel;
byte memCode = "a"; //a = MIDI Channel stored in 1023
int driveAmount;
byte notePlaying;
bool accent = false;
void MIDINoteOn(byte channel, byte note, byte velocity) {
notePlaying = note;
if (velocity >= 127) {
accent = true;
}
if (!MIDINotePlaying || !droneSwitchVal) {
kGlide.setTime(2); //4 at 256, 2 at 512 control rate to avoid portamento glitches caused by 0
} else {
kGlide.setTime(GLIDE_TIME);
}
kGlide.start(note);
if (!MIDINotePlaying || !droneSwitchVal) {
kEnvelope.start();
}
MIDINotePlaying = true;
MIDINoteFaded = false;
digitalWrite(LED, LOW);
}
void MIDINoteOff(byte channel, byte note, byte velocity) {
if (note == notePlaying || !droneSwitchVal) {
MIDINotePlaying = false;
digitalWrite(LED, HIGH);
accent = false;
}
}
long softClip(long input) {
int threshold = 24000;
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 Channel setup
if(!digitalRead(GateIn)) { //MIDI setup mode, first knob you rotate = MIDI channel
int knob1Orig = analogRead(Knob1);
int knob2Orig = analogRead(Knob2);
int knob3Orig = analogRead(Knob3);
int knob4Orig = analogRead(Knob4);
int knobAOrig = analogRead(KnobA);
int knobDROrig = analogRead(KnobDR);
int knobSOrig = analogRead(KnobS);
int threshhold = 50; //To reduce noise from the input
while(true) {
if(abs(analogRead(Knob1) - knob1Orig) > threshhold) {
MIDIChannel = 1;
break;
} else if (abs(analogRead(Knob2) - knob2Orig) > threshhold) {
MIDIChannel = 2;
break;
} else if (abs(analogRead(Knob3) - knob3Orig) > threshhold) {
MIDIChannel = 3;
break;
} else if (abs(analogRead(Knob4) - knob4Orig) > threshhold) {
MIDIChannel = 4;
break;
} else if (abs(analogRead(KnobA) - knobAOrig) > threshhold) {
MIDIChannel = 5;
break;
} else if (abs(analogRead(KnobDR) - knobDROrig) > threshhold) {
MIDIChannel = 6;
break;
} else if (abs(analogRead(KnobS) - knobSOrig) > threshhold) {
MIDIChannel = 7;
break;
}
digitalWrite(LED, HIGH); //slow blinking to indicate the setup mode
delay(500);
digitalWrite(LED, LOW);
delay(500);
}
EEPROM.write(0, MIDIChannel);
EEPROM.write(1023, memCode);
digitalWrite(LED, LOW);
delay(150);
for(int i = 0; i < MIDIChannel; i ++) { //fast blink to indicate saved MIDI channel
digitalWrite(LED, HIGH);
delay(150);
digitalWrite(LED, LOW);
delay(150);
}
delay(500);
} else if (EEPROM.read(1023) == memCode) {
MIDIChannel = EEPROM.read(0);
} else { //Save default MIDI Channel to EEPROM if it wasn't set
MIDIChannel = 1; //channel 1 by default
EEPROM.write(0, MIDIChannel);
EEPROM.write(1023, memCode);
}
disconnectDigitalIn(Knob1);
disconnectDigitalIn(Knob2);
disconnectDigitalIn(Knob3);
disconnectDigitalIn(Knob4);
disconnectDigitalIn(KnobA);
disconnectDigitalIn(KnobDR);
disconnectDigitalIn(KnobS);
disconnectDigitalIn(CVIn);
MIDI.setHandleNoteOn(MIDINoteOn);
MIDI.setHandleNoteOff(MIDINoteOff);
MIDI.begin(MIDIChannel);
aSquare.setCutoffFreqs(90, 101, 122, 138, 154, 174, 210, 264, 327, 431, 546, 744, 1170, 1638, 2730, 8192);
aSaw.setCutoffFreqs(138, 154, 182, 240, 292, 341, 390, 431, 512, 630, 744, 1170, 1638, 2730, 4096, 8192);
startMozzi();
digitalWrite(LED, HIGH);
}
void updateControl(){
for(int i=0; i<10; i++) { // Hacky way to drain the buffer faster
MIDI.read();
}
//Get Control Values
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);
droneSwitchVal = digitalRead(DroneSwitch);
envSwitchVal = digitalRead(EnvSwitch);
bool gateInVal = !digitalRead(GateIn);
int CVInVal = mozziAnalogRead(CVIn);
//Remap the values and assign to parameters
int cutoff = accent? 100 : kMapCutoff(knob1Val);
int resonance = accent? 255 : kMapResonance(knob2Val);
int envMod = kMapEnvMod(knob3Val);
driveAmount = kMapDrive(knob4Val);
//Set pitch and play notes on trigger
if (MIDINoteFaded) {
noteFreq = mtof((float) kMapNote(CVInVal << 2) / pitchSubSteps);
digitalWrite(LED, !gateInVal);
if (gateInVal && !gateIsHigh) {
gateIsHigh = true;
kEnvelope.start();
} else if (!gateInVal && gateIsHigh) {
gateIsHigh = false;
}
}
//Update Envelope Settings
int attackTime = kMapAttack(knobAVal);
int decayReleaseTime = kMapDecay(knobDRVal);
kEnvelope.setDecay(accent? 400 : decayReleaseTime);
kEnvelope.setAttack(accent? 3 : attackTime);
//Calculate makeup Gain
//makeupGain = (256L * 127) / (127 + driveAmount); // Q8 fixed-point compensation factor
//Set Oscilator Frequency
Q16n16 oscFreq;
if (!MIDINoteFaded) {
oscFreq = kGlide.next();
aSquare.setFreq_Q16n16(oscFreq);
aSaw.setFreq_Q16n16(oscFreq);
} else {
aSquare.setFreq(noteFreq);
aSaw.setFreq(noteFreq);
}
int env = kEnvelope.next();
if (!MIDINotePlaying && !MIDINoteFaded && env == 0) { //threshhold might need adjustment
MIDINoteFaded = true;
}
gain = accent? env * 2 : env;
lpfr.setCutoffFreqAndResonance(constrain(cutoff + (((long)env * envMod * cutoff)>>16), 0, 255), resonance);
}
AudioOutput updateAudio(){
long signal;
if(envSwitchVal) {
signal = (aSquare.next() * 3) >> 2; //tricky way to set 0.75 gain to ~ match saw level
} else {
signal = aSaw.next();
}
signal = lpfr.next(signal); //filter
signal = (signal * gain); //envelope
//signal = softClip((signal * (127 + driveAmount)) >> 9); //overdrive
//signal = (signal * makeupGain) >> 7; //makeup gain compensation
return MonoOutput::fromNBit(17, signal).clip();
}
void loop(){
audioHook();
}