Compare commits
12 Commits
83921644dc
...
WIP
| Author | SHA1 | Date | |
|---|---|---|---|
| d084cc9912 | |||
| 75f3211629 | |||
| 7e4c0c62b7 | |||
| 41403c9817 | |||
| 29e5c12777 | |||
| b507798c22 | |||
| 0a17cda874 | |||
| 7f57145035 | |||
| fc65622935 | |||
| 245982c575 | |||
| 86535fa998 | |||
| 92faf5c81e |
143
Acid/Acid.ino
143
Acid/Acid.ino
@ -7,18 +7,17 @@
|
|||||||
// This code is licenced under GPL v3 or later
|
// This code is licenced under GPL v3 or later
|
||||||
|
|
||||||
// todo:
|
// todo:
|
||||||
// add glide handling
|
// debug accent handling that overloads the filter, also accent can be too short or too long if it's relying on midi note
|
||||||
// add accent handling
|
// add overdrive
|
||||||
// test StateVariable vs Resonant filters
|
// frequency from CV (noteFreq) could be Q16n16 for consistency
|
||||||
// exponential envelope https://sensorium.github.io/Mozzi/doc/html/07_8_envelopes_2_ead__envelope_2_ead__envelope_8ino-example.html
|
|
||||||
|
|
||||||
#include <Mozzi.h>
|
#include <Mozzi.h>
|
||||||
#include <MIDI.h>
|
#include <MIDI.h>
|
||||||
#include <Oscil.h>
|
#include <Oscil.h>
|
||||||
#include <MetaOscil.h>
|
#include <MetaOscil.h>
|
||||||
#include <StateVariable.h>
|
#include <ResonantFilter.h>
|
||||||
//#include <ADSR.h>
|
|
||||||
#include <Ead.h>
|
#include <Ead.h>
|
||||||
|
#include <Portamento.h>
|
||||||
#include <mozzi_midi.h>
|
#include <mozzi_midi.h>
|
||||||
#include <IntMap.h>
|
#include <IntMap.h>
|
||||||
#include <FixMath.h>
|
#include <FixMath.h>
|
||||||
@ -100,43 +99,63 @@ Oscil <SAW_MAX_8192_AT_16384_512_NUM_CELLS, MOZZI_AUDIO_RATE> aSaw8192(SAW_MAX_8
|
|||||||
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};
|
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 kMapNote( 0, 4095, 24 * pitchSubSteps, 84 * pitchSubSteps );
|
||||||
IntMap kMapLFOSpeed( 0, 1023, 500, 20000 );
|
IntMap kMapCutoff( 0, 1023, 26, 100 );
|
||||||
IntMap kMapFreqMod( 0, 1023, 0, 1023 );
|
IntMap kMapEnvMod( 0, 1023, 0, 140 );
|
||||||
IntMap kMapResonance( 0, 1023, 200, 1 );
|
IntMap kMapResonance( 0, 1023, 0, 255 );
|
||||||
IntMap kMapCutoff( 0, 1023, 1, 20 );
|
IntMap kMapDrive( 0, 1023, 0, 400 );
|
||||||
IntMap kMapAttack( 0, 1023, 3, 100 );
|
IntMap kMapAttack( 0, 1023, 1, 100 );
|
||||||
IntMap kMapDecayRelease( 0, 1023, 200, 2000 );
|
IntMap kMapDecay( 0, 1023, 100, 1500 );
|
||||||
IntMap kMapSustain( 0, 1023, 0, 255 );
|
IntMap kMapAccentLevel( 0, 1023, 0, 128 );
|
||||||
|
|
||||||
//ADSR <MOZZI_CONTROL_RATE, MOZZI_CONTROL_RATE> envelope;
|
|
||||||
Ead kEnvelope(MOZZI_CONTROL_RATE);
|
Ead kEnvelope(MOZZI_CONTROL_RATE);
|
||||||
StateVariable <LOWPASS> lpf;
|
LowPassFilter lpfr;
|
||||||
|
Portamento <MOZZI_CONTROL_RATE> kGlide;
|
||||||
|
|
||||||
//Global variables
|
//Global variables
|
||||||
byte gain;
|
byte gain;
|
||||||
bool MIDINotePlaying;
|
bool MIDINotePlaying;
|
||||||
|
bool MIDINoteFaded = true;
|
||||||
bool gateIsHigh = false;
|
bool gateIsHigh = false;
|
||||||
float noteFreq;
|
float noteFreq;
|
||||||
bool envSwitchVal;
|
bool envSwitchVal;
|
||||||
|
bool droneSwitchVal;
|
||||||
byte MIDIChannel;
|
byte MIDIChannel;
|
||||||
byte memCode = "a"; //a = MIDI Channel stored in 1023
|
byte memCode = "a"; //a = MIDI Channel stored in 1023
|
||||||
|
int driveAmount;
|
||||||
|
byte notePlaying;
|
||||||
|
bool accent = false;
|
||||||
|
|
||||||
void MIDINoteOn(byte channel, byte note, byte velocity) {
|
void MIDINoteOn(byte channel, byte note, byte velocity) {
|
||||||
noteFreq = mtof((int) note);
|
notePlaying = note;
|
||||||
|
|
||||||
|
accent = false; //reset if the previous note was accented
|
||||||
|
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();
|
kEnvelope.start();
|
||||||
//envelope.noteOn();
|
}
|
||||||
MIDINotePlaying = true;
|
MIDINotePlaying = true;
|
||||||
|
MIDINoteFaded = false;
|
||||||
digitalWrite(LED, LOW);
|
digitalWrite(LED, LOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MIDINoteOff(byte channel, byte note, byte velocity) {
|
void MIDINoteOff(byte channel, byte note, byte velocity) {
|
||||||
//envelope.noteOff();
|
if (note == notePlaying || !droneSwitchVal) {
|
||||||
|
MIDINotePlaying = false;
|
||||||
digitalWrite(LED, HIGH);
|
digitalWrite(LED, HIGH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long softClip(long input) {
|
long softClip(long input) {
|
||||||
int threshold = 2048;
|
int threshold = 24000;
|
||||||
if (input < -threshold) {
|
if (input < -threshold) {
|
||||||
return -threshold + (input + threshold) / 4;
|
return -threshold + (input + threshold) / 4;
|
||||||
} else if (input > threshold) {
|
} else if (input > threshold) {
|
||||||
@ -153,7 +172,7 @@ void setup(){
|
|||||||
pinMode(DroneSwitch, INPUT_PULLUP);
|
pinMode(DroneSwitch, INPUT_PULLUP);
|
||||||
|
|
||||||
//MIDI Channel setup
|
//MIDI Channel setup
|
||||||
if(!digitalRead(GateIn)) { //&& !digitalRead(DroneSwitch) && !digitalRead(EnvSwitch)) { //MIDI setup mode, first knob you rotate = MIDI channel
|
if(!digitalRead(GateIn)) { //MIDI setup mode, first knob you rotate = MIDI channel
|
||||||
int knob1Orig = analogRead(Knob1);
|
int knob1Orig = analogRead(Knob1);
|
||||||
int knob2Orig = analogRead(Knob2);
|
int knob2Orig = analogRead(Knob2);
|
||||||
int knob3Orig = analogRead(Knob3);
|
int knob3Orig = analogRead(Knob3);
|
||||||
@ -161,7 +180,7 @@ void setup(){
|
|||||||
int knobAOrig = analogRead(KnobA);
|
int knobAOrig = analogRead(KnobA);
|
||||||
int knobDROrig = analogRead(KnobDR);
|
int knobDROrig = analogRead(KnobDR);
|
||||||
int knobSOrig = analogRead(KnobS);
|
int knobSOrig = analogRead(KnobS);
|
||||||
int threshhold = 50; //To reduce noise from the input, needs testing and fine-tuning
|
int threshhold = 50; //To reduce noise from the input
|
||||||
while(true) {
|
while(true) {
|
||||||
if(abs(analogRead(Knob1) - knob1Orig) > threshhold) {
|
if(abs(analogRead(Knob1) - knob1Orig) > threshhold) {
|
||||||
MIDIChannel = 1;
|
MIDIChannel = 1;
|
||||||
@ -227,9 +246,6 @@ void setup(){
|
|||||||
|
|
||||||
startMozzi();
|
startMozzi();
|
||||||
|
|
||||||
//envelope.setAttackLevel(255);
|
|
||||||
kEnvelope.setAttack(3);
|
|
||||||
|
|
||||||
digitalWrite(LED, HIGH);
|
digitalWrite(LED, HIGH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,73 +263,78 @@ void updateControl(){
|
|||||||
int knobAVal = mozziAnalogRead(KnobA);
|
int knobAVal = mozziAnalogRead(KnobA);
|
||||||
int knobDRVal = mozziAnalogRead(KnobDR);
|
int knobDRVal = mozziAnalogRead(KnobDR);
|
||||||
int knobSVal = mozziAnalogRead(KnobS);
|
int knobSVal = mozziAnalogRead(KnobS);
|
||||||
bool droneSwitchVal = digitalRead(DroneSwitch);
|
droneSwitchVal = digitalRead(DroneSwitch);
|
||||||
envSwitchVal = digitalRead(EnvSwitch);
|
envSwitchVal = digitalRead(EnvSwitch);
|
||||||
bool gateInVal = !digitalRead(GateIn);
|
bool gateInVal = !digitalRead(GateIn);
|
||||||
int CVInVal = mozziAnalogRead(CVIn);
|
int CVInVal = mozziAnalogRead(CVIn);
|
||||||
|
|
||||||
//Remap the values and assign to parameter
|
//Remap the values and assign to parameters
|
||||||
int resonance = kMapResonance(knob3Val);
|
int accentLevel = kMapAccentLevel(knobSVal);
|
||||||
int cutoff = kMapCutoff(knob4Val);
|
int baseCutoff = kMapCutoff(knob1Val);
|
||||||
|
int cutoff = accent? (baseCutoff + (((long)accentLevel * (100 - baseCutoff)) >> 7)) : baseCutoff;
|
||||||
|
int resonance = accent? (255 - (((long)(128 - accentLevel) * (255 - kMapResonance(knob2Val))) >> 7)) : kMapResonance(knob2Val);
|
||||||
|
int envMod = kMapEnvMod(knob3Val);
|
||||||
|
driveAmount = kMapDrive(knob4Val);
|
||||||
|
|
||||||
|
|
||||||
//Set pitch and play notes on trigger
|
//Set pitch and play notes on trigger
|
||||||
if (!MIDINotePlaying) {
|
if (MIDINoteFaded) {
|
||||||
noteFreq = mtof((float) kMapNote(CVInVal << 2) / pitchSubSteps);
|
noteFreq = mtof((float) kMapNote(CVInVal << 2) / pitchSubSteps);
|
||||||
digitalWrite(LED, !gateInVal);
|
digitalWrite(LED, !gateInVal);
|
||||||
if (gateInVal && !gateIsHigh) {
|
if (gateInVal && !gateIsHigh) {
|
||||||
gateIsHigh = true;
|
gateIsHigh = true;
|
||||||
kEnvelope.start();
|
kEnvelope.start();
|
||||||
//envelope.noteOn();
|
|
||||||
} else if (!gateInVal && gateIsHigh) {
|
} else if (!gateInVal && gateIsHigh) {
|
||||||
gateIsHigh = false;
|
gateIsHigh = false;
|
||||||
//envelope.noteOff();
|
|
||||||
}
|
}
|
||||||
} /*else if (MIDINotePlaying && !envelope.playing()) {
|
}
|
||||||
MIDINotePlaying = false;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//Update Filter settings
|
|
||||||
lpf.setResonance(resonance);
|
|
||||||
//lpf.setCentreFreq(cutoff);
|
|
||||||
|
|
||||||
//Update Envelope Settings
|
//Update Envelope Settings
|
||||||
int attackTime = kMapAttack(knobAVal);
|
int attackTime = kMapAttack(knobAVal);
|
||||||
int decayReleaseTime = kMapDecayRelease(knobDRVal);
|
int baseDecay = kMapDecay(knobDRVal);
|
||||||
int sustainLevel = kMapSustain(knobSVal);
|
int decayTime = accent? (100 + (((long)(128 - accentLevel) * (baseDecay - 100)) >> 7)) : baseDecay;
|
||||||
kEnvelope.setDecay(decayReleaseTime);
|
kEnvelope.setDecay(decayTime);
|
||||||
kEnvelope.setAttack(attackTime);
|
kEnvelope.setAttack(accent? 1 : attackTime);
|
||||||
//envelope.setDecayLevel(sustainLevel);
|
|
||||||
//envelope.setTimes(attackTime, decayReleaseTime, 30000, decayReleaseTime); //30000 is so the note will sustain 30 seconds unless a noteOff comes
|
//Calculate makeup Gain
|
||||||
|
//makeupGain = ((long)256 * 127) / (127 + driveAmount);
|
||||||
|
|
||||||
//Set Oscilator Frequency
|
//Set Oscilator Frequency
|
||||||
float oscFreq = noteFreq;
|
Q16n16 oscFreq;
|
||||||
aSquare.setFreq(oscFreq);
|
if (!MIDINoteFaded) {
|
||||||
aSaw.setFreq(oscFreq);
|
oscFreq = kGlide.next();
|
||||||
|
aSquare.setFreq_Q16n16(oscFreq);
|
||||||
|
aSaw.setFreq_Q16n16(oscFreq);
|
||||||
|
} else {
|
||||||
|
aSquare.setFreq(noteFreq);
|
||||||
|
aSaw.setFreq(noteFreq);
|
||||||
|
}
|
||||||
|
|
||||||
//envelope.update();
|
|
||||||
|
|
||||||
//int env = envelope.next();
|
|
||||||
int env = kEnvelope.next();
|
int env = kEnvelope.next();
|
||||||
|
|
||||||
if(!droneSwitchVal) {
|
if (!MIDINotePlaying && !MIDINoteFaded && env == 0) {
|
||||||
gain = env;
|
MIDINoteFaded = true;
|
||||||
} else {
|
accent = false;
|
||||||
gain = 255;
|
|
||||||
}
|
}
|
||||||
lpf.setCentreFreq(env * cutoff);
|
|
||||||
|
|
||||||
|
gain = accent? constrain(env + (((long)env * accentLevel) >> 7), 0, 255) : env; //needs finetuning
|
||||||
|
|
||||||
|
lpfr.setCutoffFreqAndResonance(constrain(cutoff + (((long)env * envMod * cutoff)>>16), 0, 255), resonance);
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioOutput updateAudio(){
|
AudioOutput updateAudio(){
|
||||||
long signal;
|
long signal;
|
||||||
if(envSwitchVal) {
|
if(envSwitchVal) {
|
||||||
signal = aSquare.next() * 0.5;
|
signal = (aSquare.next() * 3) >> 2; //tricky way to set 0.75 gain to ~ match saw level
|
||||||
} else {
|
} else {
|
||||||
signal = aSaw.next() * 1;
|
signal = aSaw.next();
|
||||||
}
|
}
|
||||||
signal = lpf.next(signal); //filter
|
signal = signal >> 2; //lowering the gain before filter to avoid filter overflow bug
|
||||||
signal = (signal * gain); //envelope
|
signal = lpfr.next(signal); //filter
|
||||||
signal = softClip((signal * (127 + driveAmount)) >> 6); //overdrive
|
signal = (signal * gain) << 2; //envelope
|
||||||
|
//signal = softClip((signal * (127 + driveAmount)) >> 9); //overdrive
|
||||||
|
//signal = (signal * makeupGain) >> 7; //makeup gain compensation
|
||||||
|
|
||||||
return MonoOutput::fromNBit(17, signal).clip();
|
return MonoOutput::fromNBit(17, signal).clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,16 @@
|
|||||||
//Settings
|
//Settings
|
||||||
const int pitchSubSteps = 16; //set how many steps are there between semitones. set to 1 to quantize to semitones
|
const int pitchSubSteps = 16; //set how many steps are there between semitones. set to 1 to quantize to semitones
|
||||||
const int driveAmount = 250;
|
#define MOZZI_CONTROL_RATE 512
|
||||||
const bool oversamplingEnabled = false; //makes V/OCT tracking more precise, but adds a little portamento
|
#define GLIDE_TIME 60
|
||||||
#define MOZZI_CONTROL_RATE 256
|
|
||||||
|
|
||||||
//Hardware Definitions
|
//Hardware Definitions
|
||||||
#define Knob1 A6 //Intensity
|
#define Knob1 A6 //Cutoff
|
||||||
#define Knob2 A4 //Modulator frequency ratio/Harmonics
|
#define Knob2 A4 //Resonance
|
||||||
#define Knob3 A2 //LFO Frequency
|
#define Knob3 A2 //Env.Mod
|
||||||
#define Knob4 A0 //LFO Shape
|
#define Knob4 A0 //Overdrive
|
||||||
#define KnobA A5 //Attack
|
#define KnobA A5 //Attack
|
||||||
#define KnobDR A3 //Decay and Release
|
#define KnobDR A3 //Decay
|
||||||
#define KnobS A1 //Sustain
|
#define KnobS A1 //Accent
|
||||||
#define CVIn A7 //CV input and Pitch knob
|
#define CVIn A7 //CV input and Pitch knob
|
||||||
#define GateIn 10
|
#define GateIn 10
|
||||||
#define EnvSwitch 11
|
#define EnvSwitch 11
|
||||||
|
|||||||
Reference in New Issue
Block a user