/*
* llammas.ino
*
* Copyright 2014 Guillaume Riou, Aude Forcione-Lambert & Nicolas Hurtubise.
*
* This file is part of Llammas.
*
* Llammas is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Llammas is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Llammas. If not, see .
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "pitchbendArray.h"
#include "multiFilter.h"
#include "lfoFreqArray.h"
#define CONTROL_RATE 256
//Array of sound generating units. (numerically controlled oscillators)
Oscil < SAW512_NUM_CELLS, AUDIO_RATE > nco[] = {
(SAW512_DATA), (SAW512_DATA), (SAW512_DATA)};
//Array of lwo frequency oscillator (acting as modulators)
Oscil < SIN512_NUM_CELLS, CONTROL_RATE > lfo_one(SIN512_DATA),
lfo_two(SIN512_DATA);
//Amplitude envelope
ADSR < CONTROL_RATE > adsr_envelope;
//Filter Envelope
ADSR < CONTROL_RATE > adsr_filter;
//Buffer of last received midi notes
int note_buffer[4];
//Amount of pitch bend in fractional midi notes.
float pb_amount = 0.0;
//Last midi note played
float last_midi_note = 0.0;
//Detune values of the oscilator 2 and 3 in fractional midi notes.
float osc_det[2];
//Detune amount of osc2 and osc3.
int osc_det_amount[2] = {0,0};
//Last values of the lfos.
int last_lfo_values[2];
//Multimode filter.
Multifilter mf(0);
//Destination of the lfo modulations
bool lfo_effect[2][7]; //0:oscil1, 1:oscil2, 2:oscil3, 3:envelope, 4:cutoff, 5: det osc2, 6: det osc3
//Last filter type in use
int last_filter = 0;
//Last read values of knobs.
int last_reading[4];
//Potentiometer readings.
int readings[4][2];
//Array for precalculated osc1,2,3 lfo1 amounts.
int lfo_amount[] = {0,0,0};
//Midi notes to add to osc1,2,3 pitch
int pitch[] = {0,0,0};
//Levels of the oscillators.
int nco_levels[] = {255,255,255};
//amount of glitch
int glitch = 0;
//global lfo2 amount
int global_lfo = 0;
/*Handler for the pitchbend midi message.
*Reads a float array from ROM at the index indicated by the most significant byte
*Of the midi message and set the value as the current pitch bend amount.
*/
void handle_pitch_bend(byte channel, byte lsb, byte msb) {
pb_amount = pgm_read_float_near(PB_ARRAY + msb);
play_note(last_midi_note);
}
/*
*Handles the midi note on messages.
*
*/
void handle_note_on(byte channel, byte note, byte velocity) {
//If velocity is 0, the noteOn message is equivalent to a noteOff message.
if(velocity == 0) {
handle_note_off(channel, note, velocity);
return;
}
for(int i = 3; i > 0; i--) {
note_buffer[i] = note_buffer[i - 1];
}
note_buffer[0] = note;
play_note((float) note);
adsr_filter.noteOn();
adsr_envelope.noteOn();
}
/*
*Sets the frequency of each oscillator.
*
*/
void play_note(float note) {
// Note joué sur le clavier additionnée du pitch bend.
float totalNote = note + pb_amount + pitch[0];
nco[0].setFreq(Q16n16_to_float(Q16n16_mtof(float_to_Q16n16(totalNote))));
for(int i = 0; i < 2; i++) {
// Note globale plus le désacordage de l'oscillateur.
float noteOscil = osc_det[i] + pitch[i+1] + totalNote;
nco[i+1].setFreq(Q16n16_to_float(Q16n16_mtof(float_to_Q16n16(noteOscil))));
}
last_midi_note = note;
}
/*
*Handles the midi note off message
*/
void handle_note_off(byte channel, byte note, byte velocity) {
int i = 0;
while(i < 3 && note_buffer[i] != note) {
i++;
}
while(i < 3) {
note_buffer[i] = note_buffer[i + 1];
i += 1;
}
if(i < 4) {
note_buffer[3] = -1;
}
if(note_buffer[0] != -1) {
play_note((float) note_buffer[0]);
} else {
adsr_filter.noteOff();
adsr_envelope.noteOff();
}
}
/*
*This is run at startup. It sets most variables to defaults values
* and it initializes arrays.
*/
void setup() {
MIDI.setHandleNoteOn(handle_note_on);
MIDI.setHandleNoteOff(handle_note_off);
MIDI.setHandlePitchBend(handle_pitch_bend);
nco[0].setFreq(440);
nco[1].setFreq(440);
nco[2].setFreq(440);
startMozzi(CONTROL_RATE);
MIDI.begin();
lfo_one.setFreq((float) 0.3);
lfo_two.setFreq(5);
for(int i = 0; i < 3; i++) {
note_buffer[i] = -1;
}
adsr_envelope.setADLevels(255, 210);
adsr_envelope.setTimes(188, 345, 65000, 345);
adsr_envelope.setSustainLevel(255);
mf.setResonance(156);
for (int i = 0; i < 4; i ++){
for (int j = 0; i < 2; i ++){
readings[i][j] = 0;
}
}
last_lfo_values[0] = 0;
last_lfo_values[1] = 0;
for(int i = 0; i < 2; i++) {
for(int j = 0; j < 5; j++) {
lfo_effect[i][j] = false;
}
}
}
// Reads the four potentiometers and compare the result to what was previously
// found. Returns a 2d array in which the first position is which potentiometre was read
// and in the second is the value in the first position and a boolean that is True if the
// value read is different than the last reading.
void read_and_compare(int position, int readings[4][2]) {
int reading = 0;
for(int i = 0; i < 4; i++) {
reading = mozziAnalogRead(i+1);
readings[i][0] = reading;
readings[i][1] = ((reading > last_reading[i]+4) || (reading < last_reading[i]-4));
if(readings[i][1]){
last_reading[i] = readings[i][0];
}
}
}
/**
*Changes the waveform of the oscillator (0 :saw, 1: sin, 3: triangle, 3: square)
*/
void change_waveform(int wave, int osc){
switch(wave){
case 0 :
nco[osc].setTable(SAW512_DATA);
break;
case 1 :
nco[osc].setTable(SIN512_DATA);
break;
case 2 :
nco[osc].setTable(TRIANGLE512_DATA);
break;
case 3 :
nco[osc].setTable(SQUARE_NO_ALIAS512_DATA);
break;
}
}
/**
*Executed 256 times a second. Updates the internal controls of
*the synth and reads analog values.
*/
void updateControl() {
last_lfo_values[0] = lfo_one.next();
last_lfo_values[1] = lfo_two.next();
int entry_mode = mozziAnalogRead(0) >> 7;
int cutoff = 255;
int cutoff_lfo[] = {0,0};
read_and_compare(entry_mode, readings);
switch (entry_mode) {
// Osc1 lfo amount, Osc1 pitch, Osc1 Level, Osc1 Waveform
case 0:
if(readings[0][1]) {
lfo_amount[0] = (last_lfo_values[0]*readings[0][0])>>8;
}
if(readings[1][1]) {
pitch[0] = (readings[1][0]>>5)-16;
}
if(readings[2][1]) {
nco_levels[0] = readings[2][0]>>1;
}
if(readings[3][1]) {
change_waveform(readings[3][0]>>8, 0);
}
break;
// Osc2 lfo, Osc2 pitch, Osc2 detune, Osc2 level
case 1:
if(readings[0][1]) {
lfo_amount[1] = (last_lfo_values[0]*readings[0][0])>>8;
}
if(readings[1][1]) {
pitch[1] = (readings[1][0]>>5)-16;
}
if(readings[2][1]) {
osc_det_amount[0] = readings[2][0]>>2;
}
if(readings[3][1]) {
nco_levels[1] = readings[3][0]>>1;
}
break;
// Osc2 Waveform, Osc3 lfo amount, Osc3 pitch, Osc3 Level
case 2:
if(readings[0][1]) {
change_waveform(readings[0][0]>>8, 1);
}
if(readings[1][1]) {
lfo_amount[2] = (last_lfo_values[0]*readings[1][0])>>8;
}
if(readings[2][1]) {
pitch[2] = (readings[2][0]>>5)-16;
}
if(readings[3][1]) {
nco_levels[2] = readings[3][0]>>1;
}
break;
// Osc3 Waveform, Osc 3 Detune, Amp attack, Amp decay
case 3:
if(readings[0][1]) {
change_waveform(readings[0][0]>>8, 2);
}
if(readings[1][1]) {
osc_det_amount[1] = readings[1][0]>>2;
}
if(readings[2][1]) {
adsr_envelope.setAttackTime(readings[2][0]<<1);
}
if(readings[3][1]) {
adsr_envelope.setDecayTime(readings[3][0]<<1);
}
break;
// Amp sustain Amp Release, Filter attack, Filter Decay
case 4:
if(readings[0][1]) {
adsr_envelope.setSustainLevel(readings[0][0]>>2);
}
if(readings[1][1]) {
adsr_envelope.setReleaseTime(readings[1][0]<<1);
}
if(readings[2][1]) {
adsr_filter.setAttackTime(readings[2][0]<<1);
}
if(readings[3][1]) {
adsr_filter.setDecayTime(readings[3][0]<<1);
}
break;
// Filter Sustain, Filter Release, LFO1 Freq, LFO1 Waveform
case 5:
if(readings[0][1]) {
adsr_filter.setSustainLevel(readings[0][0]>>2);
}
if(readings[1][1]) {
adsr_envelope.setReleaseTime(readings[1][0]<<1);
}
if(readings[2][1]) {
lfo_one.setFreq(pgm_read_float_near(LFO_ARRAY + (readings[2][0]>>2)));
}
if(readings[3][1]) {
switch(readings[3][0]>>8){
case 0 :
lfo_one.setTable(SAW512_DATA);
break;
case 1 :
lfo_one.setTable(SIN512_DATA);
break;
case 2 :
lfo_one.setTable(TRIANGLE512_DATA);
break;
case 3 :
lfo_one.setTable(SQUARE_NO_ALIAS512_DATA);
break;
}
}
break;
// LFO2 Freq, LFO2 Waveform, Filter type, Filter Cutoff
case 6:
if(readings[0][1]) {
lfo_two.setFreq(pgm_read_float_near(LFO_ARRAY + (readings[0][0]>>2)));
}
if(readings[1][1]) {
switch(readings[1][0]>>8){
case 0 :
lfo_two.setTable(SAW512_DATA);
break;
case 1 :
lfo_two.setTable(SIN512_DATA);
break;
case 2 :
lfo_two.setTable(TRIANGLE512_DATA);
break;
case 3 :
lfo_two.setTable(SQUARE_NO_ALIAS512_DATA);
break;
}
}
if(readings[2][1]) {
mf.changeFilter(readings[2][0]>>8);
}
if(readings[3][1]) {
cutoff = readings[3][0];
}
break;
// Filter LFO1, Filter LFO2, Glitch amount, global LFO.
case 7:
if(readings[0][1]) {
cutoff_lfo[0] = ((last_lfo_values[0]+127)*(readings[0][0]>>3))>>7;
}
if(readings[1][1]) {
cutoff_lfo[1] = ((last_lfo_values[1]+127)*(readings[1][0]>>3))>>7;
}
if(readings[2][1]) {
glitch = readings[2][0];
}
if(readings[3][1]) {
global_lfo = (last_lfo_values[1]*(readings[3][0]>>2))>>8;
}
break;
}
//Detune
if(osc_det_amount[0]) {
osc_det[0] = pgm_read_float_near(PB_ARRAY +((((last_lfo_values[0] + 127)*osc_det_amount[0])>>8) >> 1));
}
if(osc_det_amount[1]) {
osc_det[1] = pgm_read_float_near(PB_ARRAY +((((last_lfo_values[0] + 127)*osc_det_amount[1])>>8) >> 1));
}
// TODO : Lire la valeur sur des knobs.
for(int i = 0; i < 2; i++) {
if(cutoff_lfo[i]) {
cutoff = (cutoff * cutoff_lfo[i]) >> 7;
}
}
mf.setCutoffFreq(cutoff * adsr_envelope.next() >> 8);
// Lis et traite les valeurs midi.
MIDI.read();
play_note(last_midi_note);
// Update l'adsr.
adsr_envelope.update();
}
/*
*Fills the audio buffer of the synth. This method does
*all the needed operation to convert the values from the different
*signal generators and modifiers to aunique signal that can be outputed.
*/
int updateAudio() {
//Useless operation that juste makes the thing lag more and more as the glitch parameter increases.
for (int i = 0; i < glitch; i ++){
nco[0].setPhaseFractional(nco[0].getPhaseFractional());
}
//fetch the next osc samples
int osc1 = nco[0].next();
int osc2 = nco[1].next();
int osc3 = nco[2].next();
//applies oscillators lfos.
if(lfo_amount[0]) {
osc1 = (osc1*(lfo_amount[0]))>>7;
}
if(lfo_amount[1]) {
osc2 = (osc2*(lfo_amount[1]))>>7;
}
if(lfo_amount[2]) {
osc3 = (osc3*(lfo_amount[2]))>>7;
}
//adds the ncos together.
int total = ((osc1*nco_levels[0]>>8) + (osc2*nco_levels[0]>>8) + (osc3*nco_levels[0]>>8)) >> 2;
//apply the global lfo.
if(global_lfo) {
total = (total*global_lfo)>>7;
}
//applies the filter and the envelope.
return (int) (adsr_envelope.next() * (mf.next(total))) >> 2;
}
//Continuously executes mozzi's audiHook method wich sends pwmd voltage values out
//in the real world
void loop() {
audioHook();
}