Compare commits

...

10 Commits

2 changed files with 86 additions and 67 deletions

View File

@ -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
// make a glide on/off swithc instead of drone, both for MIDI and CV (sample pitch on gate) // add overdrive
// add accent handling // frequency from CV (noteFreq) could be Q16n16 for consistency
#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 <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 kMapCutoff( 0, 1023, 26, 100 );
IntMap kMapEnvMod( 0, 1023, 0, 140 );
IntMap kMapResonance( 0, 1023, 0, 255 ); IntMap kMapResonance( 0, 1023, 0, 255 );
IntMap kMapCutoff( 0, 1023, 20, 127 ); IntMap kMapDrive( 0, 1023, 0, 400 );
IntMap kMapAttack( 0, 1023, 3, 100 ); IntMap kMapAttack( 0, 1023, 1, 100 );
IntMap kMapDecay( 0, 1023, 200, 2000 ); IntMap kMapDecay( 0, 1023, 100, 1500 );
IntMap kMapEnvMod( 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; 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;
kEnvelope.start();
//envelope.noteOn(); 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();
}
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; 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,74 +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 cutoff = kMapCutoff(knob1Val); int accentLevel = kMapAccentLevel(knobSVal);
int resonance = kMapResonance(knob2Val); 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); 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);
//Update Envelope Settings //Update Envelope Settings
int attackTime = kMapAttack(knobAVal); int attackTime = kMapAttack(knobAVal);
int decayReleaseTime = kMapDecay(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(constrain(cutoff + (((long) env * envMod) >> 8), 20, 4096));
lpfr.setCutoffFreqAndResonance(constrain(cutoff + (((long)env * envMod * cutoff)>>16), 0, 255), resonance);
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() * 3) >> 2;// 0.75 gain to ~ match saw level signal = (aSquare.next() * 3) >> 2; //tricky way to set 0.75 gain to ~ match saw level
} else { } else {
signal = aSaw.next(); signal = aSaw.next();
} }
signal = signal >> 2; //lowering the gain before filter to avoid filter overflow bug
signal = lpfr.next(signal); //filter signal = lpfr.next(signal); //filter
signal = (signal * gain); //envelope signal = (signal * gain) << 2; //envelope
//signal = softClip((signal * (127 + driveAmount)) >> 6); //overdrive //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();
} }

View File

@ -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