|
|
|
@ -7,8 +7,9 @@
|
|
|
|
// This code is licenced under GPL v3 or later
|
|
|
|
// This code is licenced under GPL v3 or later
|
|
|
|
|
|
|
|
|
|
|
|
// todo:
|
|
|
|
// todo:
|
|
|
|
// debug accent handling that overloads the filter
|
|
|
|
// 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
|
|
|
|
// add overdrive
|
|
|
|
|
|
|
|
// frequency from CV (noteFreq) could be Q16n16 for consistency
|
|
|
|
|
|
|
|
|
|
|
|
#include <Mozzi.h>
|
|
|
|
#include <Mozzi.h>
|
|
|
|
#include <MIDI.h>
|
|
|
|
#include <MIDI.h>
|
|
|
|
@ -98,12 +99,13 @@ 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 kMapResonance( 0, 1023, 0, 255 );
|
|
|
|
|
|
|
|
IntMap kMapCutoff( 0, 1023, 26, 100 );
|
|
|
|
IntMap kMapCutoff( 0, 1023, 26, 100 );
|
|
|
|
IntMap kMapAttack( 0, 1023, 3, 100 );
|
|
|
|
IntMap kMapEnvMod( 0, 1023, 0, 140 );
|
|
|
|
IntMap kMapDecay( 0, 1023, 200, 2000 );
|
|
|
|
IntMap kMapResonance( 0, 1023, 0, 255 );
|
|
|
|
IntMap kMapEnvMod( 0, 1023, 0, 160 );
|
|
|
|
|
|
|
|
IntMap kMapDrive( 0, 1023, 0, 400 );
|
|
|
|
IntMap kMapDrive( 0, 1023, 0, 400 );
|
|
|
|
|
|
|
|
IntMap kMapAttack( 0, 1023, 1, 100 );
|
|
|
|
|
|
|
|
IntMap kMapDecay( 0, 1023, 100, 1500 );
|
|
|
|
|
|
|
|
IntMap kMapAccentLevel( 0, 1023, 0, 128 );
|
|
|
|
|
|
|
|
|
|
|
|
Ead kEnvelope(MOZZI_CONTROL_RATE);
|
|
|
|
Ead kEnvelope(MOZZI_CONTROL_RATE);
|
|
|
|
LowPassFilter lpfr;
|
|
|
|
LowPassFilter lpfr;
|
|
|
|
@ -114,7 +116,7 @@ byte gain;
|
|
|
|
bool MIDINotePlaying;
|
|
|
|
bool MIDINotePlaying;
|
|
|
|
bool MIDINoteFaded = true;
|
|
|
|
bool MIDINoteFaded = true;
|
|
|
|
bool gateIsHigh = false;
|
|
|
|
bool gateIsHigh = false;
|
|
|
|
Q16n16 noteFreq;
|
|
|
|
float noteFreq;
|
|
|
|
bool envSwitchVal;
|
|
|
|
bool envSwitchVal;
|
|
|
|
bool droneSwitchVal;
|
|
|
|
bool droneSwitchVal;
|
|
|
|
byte MIDIChannel;
|
|
|
|
byte MIDIChannel;
|
|
|
|
@ -123,11 +125,10 @@ int driveAmount;
|
|
|
|
byte notePlaying;
|
|
|
|
byte notePlaying;
|
|
|
|
bool accent = false;
|
|
|
|
bool accent = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MIDINoteOn(byte channel, byte note, byte velocity) {
|
|
|
|
void MIDINoteOn(byte channel, byte note, byte velocity) {
|
|
|
|
notePlaying = note;
|
|
|
|
notePlaying = note;
|
|
|
|
noteFreq = Q16n16_mtof((int) note);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
accent = false; //reset if the previous note was accented
|
|
|
|
if (velocity >= 127) {
|
|
|
|
if (velocity >= 127) {
|
|
|
|
accent = true;
|
|
|
|
accent = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -150,7 +151,6 @@ void MIDINoteOff(byte channel, byte note, byte velocity) {
|
|
|
|
if (note == notePlaying || !droneSwitchVal) {
|
|
|
|
if (note == notePlaying || !droneSwitchVal) {
|
|
|
|
MIDINotePlaying = false;
|
|
|
|
MIDINotePlaying = false;
|
|
|
|
digitalWrite(LED, HIGH);
|
|
|
|
digitalWrite(LED, HIGH);
|
|
|
|
accent = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -269,11 +269,14 @@ void updateControl(){
|
|
|
|
int CVInVal = mozziAnalogRead(CVIn);
|
|
|
|
int CVInVal = mozziAnalogRead(CVIn);
|
|
|
|
|
|
|
|
|
|
|
|
//Remap the values and assign to parameters
|
|
|
|
//Remap the values and assign to parameters
|
|
|
|
int cutoff = accent? 100 : kMapCutoff(knob1Val);
|
|
|
|
int accentLevel = kMapAccentLevel(knobSVal);
|
|
|
|
int resonance = accent? 255 : 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);
|
|
|
|
driveAmount = kMapDrive(knob4Val);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Set pitch and play notes on trigger
|
|
|
|
//Set pitch and play notes on trigger
|
|
|
|
if (MIDINoteFaded) {
|
|
|
|
if (MIDINoteFaded) {
|
|
|
|
noteFreq = mtof((float) kMapNote(CVInVal << 2) / pitchSubSteps);
|
|
|
|
noteFreq = mtof((float) kMapNote(CVInVal << 2) / pitchSubSteps);
|
|
|
|
@ -288,29 +291,35 @@ void updateControl(){
|
|
|
|
|
|
|
|
|
|
|
|
//Update Envelope Settings
|
|
|
|
//Update Envelope Settings
|
|
|
|
int attackTime = kMapAttack(knobAVal);
|
|
|
|
int attackTime = kMapAttack(knobAVal);
|
|
|
|
int decayReleaseTime = kMapDecay(knobDRVal);
|
|
|
|
int baseDecay = kMapDecay(knobDRVal);
|
|
|
|
kEnvelope.setDecay(accent? 400 : decayReleaseTime);
|
|
|
|
int decayTime = accent? (100 + (((long)(128 - accentLevel) * (baseDecay - 100)) >> 7)) : baseDecay;
|
|
|
|
kEnvelope.setAttack(accent? 3 : attackTime);
|
|
|
|
kEnvelope.setDecay(decayTime);
|
|
|
|
|
|
|
|
kEnvelope.setAttack(accent? 1 : attackTime);
|
|
|
|
|
|
|
|
|
|
|
|
//Calculate makeup Gain
|
|
|
|
//Calculate makeup Gain
|
|
|
|
//makeupGain = (256L * 127) / (127 + driveAmount); // Q8 fixed-point compensation factor
|
|
|
|
//makeupGain = ((long)256 * 127) / (127 + driveAmount);
|
|
|
|
|
|
|
|
|
|
|
|
//Set Oscilator Frequency
|
|
|
|
//Set Oscilator Frequency
|
|
|
|
Q16n16 oscFreq;
|
|
|
|
Q16n16 oscFreq;
|
|
|
|
oscFreq = kGlide.next();
|
|
|
|
if (!MIDINoteFaded) {
|
|
|
|
aSquare.setFreq_Q16n16(oscFreq);
|
|
|
|
oscFreq = kGlide.next();
|
|
|
|
aSaw.setFreq_Q16n16(oscFreq);
|
|
|
|
aSquare.setFreq_Q16n16(oscFreq);
|
|
|
|
|
|
|
|
aSaw.setFreq_Q16n16(oscFreq);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
aSquare.setFreq(noteFreq);
|
|
|
|
|
|
|
|
aSaw.setFreq(noteFreq);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int env = kEnvelope.next();
|
|
|
|
int env = kEnvelope.next();
|
|
|
|
|
|
|
|
|
|
|
|
if (!MIDINotePlaying && !MIDINoteFaded && env == 0) { //threshhold might need adjustment
|
|
|
|
if (!MIDINotePlaying && !MIDINoteFaded && env == 0) {
|
|
|
|
MIDINoteFaded = true;
|
|
|
|
MIDINoteFaded = true;
|
|
|
|
|
|
|
|
accent = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gain = accent? env * 2 : env;
|
|
|
|
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);
|
|
|
|
lpfr.setCutoffFreqAndResonance(constrain(cutoff + (((long)env * envMod * cutoff)>>16), 0, 255), resonance);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
AudioOutput updateAudio(){
|
|
|
|
AudioOutput updateAudio(){
|
|
|
|
@ -320,8 +329,9 @@ AudioOutput updateAudio(){
|
|
|
|
} 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)) >> 9); //overdrive
|
|
|
|
//signal = softClip((signal * (127 + driveAmount)) >> 9); //overdrive
|
|
|
|
//signal = (signal * makeupGain) >> 7; //makeup gain compensation
|
|
|
|
//signal = (signal * makeupGain) >> 7; //makeup gain compensation
|
|
|
|
|
|
|
|
|
|
|
|
|