diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index b51bce0..638c436 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -1,940 +1,958 @@ -#include "Arduino.h" - -#include "application.h" - -#include "hw.h" -#include "SPImcpDAC.h" -#include "ihandlers.h" -#include "timer.h" -#include "EEPROM.h" - -const AppMode AppModeValues[] = {MUTE,NORMAL}; -const int16_t CalibrationTolerance = 15; -const int16_t PitchFreqOffset = 700; -const int16_t VolumeFreqOffset = 700; -const int8_t HYST_VAL = 40; - -static int32_t pitchCalibrationBase = 0; -static int32_t pitchCalibrationBaseFreq = 0; -static int32_t pitchCalibrationConstant = 0; -static int32_t pitchSensitivityConstant = 70000; -static int16_t pitchDAC = 0; -static int16_t volumeDAC = 0; -static float qMeasurement = 0; - -static int32_t volCalibrationBase = 0; - -static uint8_t new_midi_note =0; -static uint8_t old_midi_note =0; - -static uint8_t new_midi_loop_cc_val =0; -static uint8_t old_midi_loop_cc_val =0; - -static uint8_t midi_velocity = 0; - -static uint8_t loop_hand_pos = 0; -static uint8_t new_midi_rod_cc_val =0; -static uint8_t old_midi_rod_cc_val =0; - -static uint16_t new_midi_bend =0; -static uint16_t old_midi_bend = 0; -static uint8_t midi_bend_low; -static uint8_t midi_bend_high; - -static double double_log_freq = 0; -static double midi_key_follow = 0.5; - -// Configuration parameters -static uint8_t registerValue = 4; - // wavetable selector is defined and initialized in ihandlers.cpp -static uint8_t midi_channel = 0; -static uint8_t old_midi_channel = 0; -static uint8_t midi_bend_range = 2; -static uint8_t midi_volume_trigger = 0; -static uint8_t flag_legato_on = 1; -static uint8_t flag_pitch_bend_on = 1; -static uint8_t loop_midi_cc = 7; -static uint8_t rod_midi_cc = 255; - -// tweakable paramameters -#define VELOCITY_SENS 9 // How easy it is to reach highest velocity (127). Something betwen 5 and 12. -#define PLAYER_ACCURACY 0.2 // between 0 (very accurate players) and 0.5 (not accurate at all) - -static uint16_t data_pot_value = 0; -static uint16_t old_data_pot_value = 0; - -Application::Application() - : _state(PLAYING), - _mode(NORMAL) { -}; - -void Application::setup() { - - HW_LED1_ON;HW_LED2_OFF; - - pinMode(Application::BUTTON_PIN, INPUT_PULLUP); - pinMode(Application::LED_PIN_1, OUTPUT); - pinMode(Application::LED_PIN_2, OUTPUT); - - digitalWrite(Application::LED_PIN_1, HIGH); // turn the LED off by making the voltage LOW - - SPImcpDACinit(); - -EEPROM.get(0,pitchDAC); -EEPROM.get(2,volumeDAC); - -SPImcpDAC2Asend(pitchDAC); -SPImcpDAC2Bsend(volumeDAC); - - -initialiseTimer(); -initialiseInterrupts(); - - - EEPROM.get(4,pitchCalibrationBase); - EEPROM.get(8,volCalibrationBase); - - init_parameters(); - midi_setup(); - -} - -void Application::initialiseTimer() { - ihInitialiseTimer(); -} - -void Application::initialiseInterrupts() { - ihInitialiseInterrupts(); -} - -void Application::InitialisePitchMeasurement() { - ihInitialisePitchMeasurement(); -} - -void Application::InitialiseVolumeMeasurement() { - ihInitialiseVolumeMeasurement(); -} - -unsigned long Application::GetQMeasurement() -{ - int qn=0; - - TCCR1B = (1<>2); - pitch_l=pitch_v; - - -//HW_LED2_ON; - - - // set wave frequency for each mode - switch (_mode) { - case MUTE : /* NOTHING! */; break; - case NORMAL : setWavetableSampleAdvance(((pitchCalibrationBase-pitch_v)+2048-(pitchPotValue<<2))/registerValue); break; - }; - - // HW_LED2_OFF; - - pitchValueAvailable = false; - } - - if (volumeValueAvailable) { - vol = max(vol, 5000); - - vol_v=vol; // Averaging volume values - vol_v=vol_l+((vol_v-vol_l)>>2); - vol_l=vol_v; - - switch (_mode) { - case MUTE: vol_v = 0; break; - case NORMAL: vol_v = MAX_VOLUME-(volCalibrationBase-vol_v)/2+(volumePotValue<<2)-1024; break; - }; - - // Limit and set volume value - vol_v = min(vol_v, 4095); - // vol_v = vol_v - (1 + MAX_VOLUME - (volumePotValue << 2)); - vol_v = vol_v ; - vol_v = max(vol_v, 0); - loop_hand_pos = vol_v >> 4; - - // Give vScaledVolume a pseudo-exponential characteristic: - vScaledVolume = loop_hand_pos * (loop_hand_pos + 2); - - volumeValueAvailable = false; - } - - if (midi_timer > 100) // run midi app every 100 ticks equivalent to approximatevely 3 ms to avoid synth's overload - { - midi_application (); - midi_timer = 0; - } - - goto mloop; // End of main loop -} - -void Application::calibrate() -{ - resetPitchFlag(); - resetTimer(); - savePitchCounter(); - while (!pitchValueAvailable && timerUnexpiredMillis(10)) - ; // NOP - pitchCalibrationBase = pitch; - pitchCalibrationBaseFreq = FREQ_FACTOR/pitchCalibrationBase; - pitchCalibrationConstant = FREQ_FACTOR/pitchSensitivityConstant/2+200; - - resetVolFlag(); - resetTimer(); - saveVolCounter(); - while (!volumeValueAvailable && timerUnexpiredMillis(10)) - ; // NOP - volCalibrationBase = vol; - - - EEPROM.put(4,pitchCalibrationBase); - EEPROM.put(8,volCalibrationBase); - -} - -void Application::calibrate_pitch() -{ - -static int16_t pitchXn0 = 0; -static int16_t pitchXn1 = 0; -static int16_t pitchXn2 = 0; -static float q0 = 0; -static long pitchfn0 = 0; -static long pitchfn1 = 0; -static long pitchfn = 0; - - - - InitialisePitchMeasurement(); - interrupts(); - SPImcpDACinit(); - - qMeasurement = GetQMeasurement(); // Measure Arudino clock frequency - -q0 = (16000000/qMeasurement*500000); //Calculated set frequency based on Arudino clock frequency - -pitchXn0 = 0; -pitchXn1 = 4095; - -pitchfn = q0-PitchFreqOffset; // Add offset calue to set frequency - - - -SPImcpDAC2Bsend(1600); - -SPImcpDAC2Asend(pitchXn0); -delay(100); -pitchfn0 = GetPitchMeasurement(); - -SPImcpDAC2Asend(pitchXn1); -delay(100); -pitchfn1 = GetPitchMeasurement(); - - -while(abs(pitchfn0-pitchfn1)>CalibrationTolerance){ // max allowed pitch frequency offset - -SPImcpDAC2Asend(pitchXn0); -delay(100); -pitchfn0 = GetPitchMeasurement()-pitchfn; - -SPImcpDAC2Asend(pitchXn1); -delay(100); -pitchfn1 = GetPitchMeasurement()-pitchfn; - -pitchXn2=pitchXn1-((pitchXn1-pitchXn0)*pitchfn1)/(pitchfn1-pitchfn0); // new DAC value - -delay(100); - - -pitchXn0 = pitchXn1; -pitchXn1 = pitchXn2; - -HW_LED2_TOGGLE; - -} -delay(100); - -EEPROM.put(0,pitchXn0); - -} - -void Application::calibrate_volume() -{ - - -static int16_t volumeXn0 = 0; -static int16_t volumeXn1 = 0; -static int16_t volumeXn2 = 0; -static float q0 = 0; -static long volumefn0 = 0; -static long volumefn1 = 0; -static long volumefn = 0; - - - InitialiseVolumeMeasurement(); - interrupts(); - SPImcpDACinit(); - - -volumeXn0 = 0; -volumeXn1 = 4095; - -q0 = (16000000/qMeasurement*460765); -volumefn = q0-VolumeFreqOffset; - - - -SPImcpDAC2Bsend(volumeXn0); -delay_NOP(44316);//44316=100ms - -volumefn0 = GetVolumeMeasurement(); - -SPImcpDAC2Bsend(volumeXn1); - -delay_NOP(44316);//44316=100ms -volumefn1 = GetVolumeMeasurement(); - - - -while(abs(volumefn0-volumefn1)>CalibrationTolerance){ - -SPImcpDAC2Bsend(volumeXn0); -delay_NOP(44316);//44316=100ms -volumefn0 = GetVolumeMeasurement()-volumefn; - -SPImcpDAC2Bsend(volumeXn1); -delay_NOP(44316);//44316=100ms -volumefn1 = GetVolumeMeasurement()-volumefn; - -volumeXn2=volumeXn1-((volumeXn1-volumeXn0)*volumefn1)/(volumefn1-volumefn0); // calculate new DAC value - -delay_NOP(44316);//44316=100ms - -volumeXn0 = volumeXn1; -volumeXn1 = volumeXn2; -HW_LED2_TOGGLE; - -} - -EEPROM.put(2,volumeXn0); - - -} - -void Application::hzToAddVal(float hz) { - setWavetableSampleAdvance((uint16_t)(hz * HZ_ADDVAL_FACTOR)); -} - -void Application::playNote(float hz, uint16_t milliseconds = 500, uint8_t volume = 255) { - vScaledVolume = volume * (volume + 2); - hzToAddVal(hz); - millitimer(milliseconds); - vScaledVolume = 0; -} - -void Application::playStartupSound() { - playNote(MIDDLE_C, 150, 25); - playNote(MIDDLE_C * 2, 150, 25); - playNote(MIDDLE_C * 4, 150, 25); -} - -void Application::playCalibratingCountdownSound() { - playNote(MIDDLE_C * 2, 150, 25); - playNote(MIDDLE_C * 2, 150, 25); -} - -void Application::playModeSettingSound() { - for (int i = 0; i <= _mode; i++) { - playNote(MIDDLE_C * 2, 200, 25); - millitimer(100); - } -} - -void Application::delay_NOP(unsigned long time) { - volatile unsigned long i = 0; - for (i = 0; i < time; i++) { - __asm__ __volatile__ ("nop"); - } -} - - - -void Application::midi_setup() -{ - // Set MIDI baud rate: - Serial.begin(115200); // Baudrate for midi to serial. Use a serial to midi router http://projectgus.github.com/hairless-midiserial/ - //Serial.begin(31250); // Baudrate for real midi. Use din connection https://www.arduino.cc/en/Tutorial/Midi or HIDUINO https://github.com/ddiakopoulos/hiduino - - _midistate = MIDI_SILENT; -} - - -void Application::midi_msg_send(uint8_t channel, uint8_t midi_cmd1, uint8_t midi_cmd2, uint8_t midi_value) -{ - uint8_t mixed_cmd1_channel; - - mixed_cmd1_channel = (midi_cmd1 & 0xF0)| (channel & 0x0F); - - Serial.write(mixed_cmd1_channel); - Serial.write(midi_cmd2); - Serial.write(midi_value); -} - -// midi_application sends note and volume and uses pitch bend to simulate continuous picth. -// Calibrate pitch bend and other parameters accordingly to the receiver synth (see midi_calibrate). -// New notes won't be generated as long as pitch bend will do the job. -// The bigger is synth's pitch bend range the beter is the effect. -// If pitch bend range = 1 no picth bend is generated (portamento will do a better job) -void Application::midi_application () -{ - double delta_loop_cc_val = 0; - double calculated_velocity = 0; - - - // Calculate loop antena cc value for midi - new_midi_loop_cc_val = loop_hand_pos >> 1; - new_midi_loop_cc_val = min (new_midi_loop_cc_val, 127); - delta_loop_cc_val = (double)new_midi_loop_cc_val - (double)old_midi_loop_cc_val; - - // Calculate log freq - if ((vPointerIncrement < 18) || (vPointerIncrement > 65518)) - { - // Lowest note - double_log_freq = 0; - } - else if ((vPointerIncrement > 26315) && (vPointerIncrement < 39221)) - { - // Highest note - double_log_freq = 127; - } - else if (vPointerIncrement < 32768) - { - // Positive frequencies - // Find note in the playing range - double_log_freq = (log (vPointerIncrement/17.152) / 0.057762265); // Precise note played in the logaritmic scale - } - else - { - // Negative frequencies - // Find note in the playing range - double_log_freq = (log ((65535-vPointerIncrement+1)/17.152) / 0.057762265); // Precise note played in the logaritmic scale - } - - // Calculate rod antena cc value for midi - new_midi_rod_cc_val = round (double_log_freq); - - // State machine for MIDI - switch (_midistate) - { - case MIDI_SILENT: - // Always refresh midi loop antena cc. - if (new_midi_loop_cc_val != old_midi_loop_cc_val) - { - midi_msg_send(midi_channel, 0xB0, loop_midi_cc, new_midi_loop_cc_val); - old_midi_loop_cc_val = new_midi_loop_cc_val; - } - else - { - // do nothing - } - - // Always refresh midi rod antena cc if applicable. - if ((rod_midi_cc != 255) && (new_midi_rod_cc_val != old_midi_rod_cc_val)) - { - midi_msg_send(midi_channel, 0xB0, rod_midi_cc, new_midi_rod_cc_val); - old_midi_rod_cc_val = new_midi_rod_cc_val; - } - else - { - // do nothing - } - - // If player's hand moves away from volume antenna - if (new_midi_loop_cc_val > midi_volume_trigger) - { - // Set key follow to the minimum in order to use closest note played as the center note - midi_key_follow = 0.5; - - // Calculate note and associated pitch bend - calculate_note_bend (); - - // Send pitch bend to reach precise played note (send 8192 (no pitch bend) in case of midi_bend_range == 1) - midi_msg_send(midi_channel, 0xE0, midi_bend_low, midi_bend_high); - old_midi_bend = new_midi_bend; - - // Calculate velocity - if (midi_timer != 0) - { - calculated_velocity = (64 * (127 - (double)midi_volume_trigger) / 127) + (VELOCITY_SENS * (double)midi_volume_trigger * delta_loop_cc_val / (double)midi_timer); - midi_velocity = min (round (abs (calculated_velocity)), 127); - } - else - { - // should not happen - midi_velocity = 64; - } - - - // Play the note - midi_msg_send(midi_channel, 0x90, new_midi_note, midi_velocity); - old_midi_note = new_midi_note; - - _midistate = MIDI_PLAYING; - } - else - { - // Do nothing - } - break; - - case MIDI_PLAYING: - // Always refresh midi loop antena cc. - if (new_midi_loop_cc_val != old_midi_loop_cc_val) - { - midi_msg_send(midi_channel, 0xB0, loop_midi_cc, new_midi_loop_cc_val); - old_midi_loop_cc_val = new_midi_loop_cc_val; - } - else - { - // do nothing - } - - // Always refresh midi rod antena cc if applicable. - if ((rod_midi_cc != 255) && (new_midi_rod_cc_val != old_midi_rod_cc_val)) - { - midi_msg_send(midi_channel, 0xB0, rod_midi_cc, new_midi_rod_cc_val); - old_midi_rod_cc_val = new_midi_rod_cc_val; - } - else - { - // do nothing - } - - // If player's hand is far from volume antenna - if (new_midi_loop_cc_val > midi_volume_trigger) - { - if ( flag_legato_on == 1) - { - // Set key follow so as next played note will be at limit of pitch bend range - midi_key_follow = (double)(midi_bend_range) - PLAYER_ACCURACY; - } - else - { - // Set key follow to max so as no key follows - midi_key_follow = 127; - } - - // Calculate note and associated pitch bend - calculate_note_bend (); - - // Refresh midi pitch bend value - if (new_midi_bend != old_midi_bend) - { - midi_msg_send(midi_channel, 0xE0, midi_bend_low, midi_bend_high); - old_midi_bend = new_midi_bend; - } - else - { - // do nothing - } - - // Refresh midi note - if (new_midi_note != old_midi_note) - { - // Play new note before muting old one to play legato on monophonic synth - // (pitch pend management tends to break expected effect here) - midi_msg_send(midi_channel, 0x90, new_midi_note, midi_velocity); - midi_msg_send(midi_channel, 0x90, old_midi_note, 0); - old_midi_note = new_midi_note; - } - else - { - // do nothing - } - } - else // Means that player's hand moves to the volume antenna - { - // Send note off - midi_msg_send(midi_channel, 0x90, old_midi_note, 0); - - _midistate = MIDI_SILENT; - } - break; - - case MIDI_STOP: - // Send all note off - midi_msg_send(midi_channel, 0xB0, 0x7B, 0x00); - - _midistate = MIDI_MUTE; - break; - - case MIDI_MUTE: - //do nothing - break; - - } -} - -void Application::calculate_note_bend () -{ - double double_log_bend; - double double_norm_log_bend; - - double_log_bend = double_log_freq - old_midi_note; // How far from last played midi chromatic note we are - - // If too much far from last midi chromatic note played (midi_key_follow depends on pitch bend range) - if ((abs (double_log_bend) >= midi_key_follow) && (midi_key_follow != 127)) - { - new_midi_note = round (double_log_freq); // Select the new midi chromatic note - double_log_bend = double_log_freq - new_midi_note; // calculate bend to reach precise note played - } - else - { - new_midi_note = old_midi_note; // No change - } - - // If pitch bend activated - if (flag_pitch_bend_on == 1) - { - // use it to reach precise note played - double_norm_log_bend = (double_log_bend / midi_bend_range); - if (double_norm_log_bend > 1) - { - double_norm_log_bend = 1; - } - else if (double_norm_log_bend < -1) - { - double_norm_log_bend = -1; - } - new_midi_bend = 8192 + (8191 * double_norm_log_bend); // Calculate midi pitch bend - } - else - { - // Don't use pitch bend - new_midi_bend = 8192; - } - - - // Prepare the 2 bites of picth bend midi message - midi_bend_low = (int8_t) (new_midi_bend & 0x007F); - midi_bend_high = (int8_t) ((new_midi_bend & 0x3F80)>> 7); -} - - - -void Application::init_parameters () -{ - // init data pot value to avoid 1st position to be taken into account - data_pot_value = analogRead(WAVE_SELECT_POT); - old_data_pot_value = data_pot_value; - -} - -void Application::set_parameters () -{ - uint16_t param_pot_value = 0; - - param_pot_value = analogRead(REGISTER_SELECT_POT); - data_pot_value = analogRead(WAVE_SELECT_POT); - - // If data pot moved - if (abs((int32_t)data_pot_value - (int32_t)old_data_pot_value) >= 8) - { - // Modify selected parameter - switch (param_pot_value >> 7) - { - case 0: - // Transpose - registerValue=4-(data_pot_value>>8); - break; - - case 1: - // Waveform - vWavetableSelector=data_pot_value>>7; - break; - - case 2: - // Channel - midi_channel = (uint8_t)((data_pot_value >> 6) & 0x000F); - if (old_midi_channel != midi_channel) - { - // Send all note off to avoid stuck notes - midi_msg_send(old_midi_channel, 0xB0, 0x7B, 0x00); - old_midi_channel = midi_channel; - } - break; - - case 3: - // Rod antenna mode - switch (data_pot_value >> 7) - { - case 0: - case 1: - flag_legato_on = 0; - flag_pitch_bend_on = 0; - break; - case 2: - case 3: - flag_legato_on = 0; - flag_pitch_bend_on = 1; - break; - case 4: - case 5: - flag_legato_on = 1; - flag_pitch_bend_on = 0; - break; - default: - flag_legato_on = 1; - flag_pitch_bend_on = 1; - break; - } - break; - - case 4: - // Pitch bend range - switch (data_pot_value >> 7) - { - case 0: - midi_bend_range = 1; - break; - case 1: - case 2: - midi_bend_range = 2; - break; - case 3: - case 4: - midi_bend_range = 7; - break; - case 5: - case 6: - midi_bend_range = 12; - break; - default: - midi_bend_range = 24; - break; - } - break; - - case 5: - // Volume trigger - midi_volume_trigger = (uint8_t)((data_pot_value >> 3) & 0x007F); - break; - - case 6: - //Rod antenna cc - switch (data_pot_value >> 7) - { - case 0: - rod_midi_cc = 255; // Nothing - break; - case 1: - case 2: - rod_midi_cc = 8; // Balance - break; - case 3: - case 4: - rod_midi_cc = 10; // Pan - break; - case 5: - case 6: - rod_midi_cc = 16; // Ribbon Controler - break; - default: - rod_midi_cc = 74; // Cutoff (exists of both loop and rod) - break; - } - break; - - - default: - // Loop antenna cc - switch (data_pot_value >> 7) - { - case 0: - loop_midi_cc = 1; // Modulation - break; - case 1: - loop_midi_cc = 7; // Volume - break; - case 2: - loop_midi_cc = 11; // Expression - break; - case 3: - loop_midi_cc = 71; // Resonnance - break; - case 4: - loop_midi_cc = 74; // Cutoff (exists of both loop and rod) - break; - case 5: - loop_midi_cc = 91; // Reverb - break; - case 6: - loop_midi_cc = 93; // Chorus - break; - default: - loop_midi_cc = 95; // Phaser - break; - } - break; - } - - // Memorize data pot value to monitor changes - old_data_pot_value = data_pot_value; - } +#include "Arduino.h" + +#include "application.h" + +#include "hw.h" +#include "SPImcpDAC.h" +#include "ihandlers.h" +#include "timer.h" +#include "EEPROM.h" + +const AppMode AppModeValues[] = {MUTE,NORMAL}; +const int16_t CalibrationTolerance = 15; +const int16_t PitchFreqOffset = 700; +const int16_t VolumeFreqOffset = 700; +const int8_t HYST_VAL = 40; + +static int32_t pitchCalibrationBase = 0; +static int32_t pitchCalibrationBaseFreq = 0; +static int32_t pitchCalibrationConstant = 0; +static int32_t pitchSensitivityConstant = 70000; +static int16_t pitchDAC = 0; +static int16_t volumeDAC = 0; +static float qMeasurement = 0; + +static int32_t volCalibrationBase = 0; + +static uint8_t new_midi_note =0; +static uint8_t old_midi_note =0; + +static uint8_t new_midi_loop_cc_val =0; +static uint8_t old_midi_loop_cc_val =0; + +static uint8_t midi_velocity = 0; + +static uint8_t loop_hand_pos = 0; +static uint8_t new_midi_rod_cc_val =0; +static uint8_t old_midi_rod_cc_val =0; + +static uint16_t new_midi_bend =0; +static uint16_t old_midi_bend = 0; +static uint8_t midi_bend_low; +static uint8_t midi_bend_high; + +static double double_log_freq = 0; +static double midi_key_follow = 0.5; + +// Configuration parameters +static uint8_t registerValue = 4; + // wavetable selector is defined and initialized in ihandlers.cpp +static uint8_t midi_channel = 0; +static uint8_t old_midi_channel = 0; +static uint8_t midi_bend_range = 2; +static uint8_t midi_volume_trigger = 0; +static uint8_t flag_legato_on = 1; +static uint8_t flag_pitch_bend_on = 1; +static uint8_t loop_midi_cc = 7; +static uint8_t rod_midi_cc = 255; + +// tweakable paramameters +#define VELOCITY_SENS 9 // How easy it is to reach highest velocity (127). Something betwen 5 and 12. +#define PLAYER_ACCURACY 0.2 // between 0 (very accurate players) and 0.5 (not accurate at all) + +static uint16_t data_pot_value = 0; +static uint16_t old_data_pot_value = 0; + +Application::Application() + : _state(PLAYING), + _mode(NORMAL) { +}; + +void Application::setup() { + + HW_LED1_ON;HW_LED2_OFF; + + pinMode(Application::BUTTON_PIN, INPUT_PULLUP); + pinMode(Application::LED_PIN_1, OUTPUT); + pinMode(Application::LED_PIN_2, OUTPUT); + + digitalWrite(Application::LED_PIN_1, HIGH); // turn the LED off by making the voltage LOW + + SPImcpDACinit(); + +EEPROM.get(0,pitchDAC); +EEPROM.get(2,volumeDAC); + +SPImcpDAC2Asend(pitchDAC); +SPImcpDAC2Bsend(volumeDAC); + + +initialiseTimer(); +initialiseInterrupts(); + + + EEPROM.get(4,pitchCalibrationBase); + EEPROM.get(8,volCalibrationBase); + + init_parameters(); + midi_setup(); + +} + +void Application::initialiseTimer() { + ihInitialiseTimer(); +} + +void Application::initialiseInterrupts() { + ihInitialiseInterrupts(); +} + +void Application::InitialisePitchMeasurement() { + ihInitialisePitchMeasurement(); +} + +void Application::InitialiseVolumeMeasurement() { + ihInitialiseVolumeMeasurement(); +} + +unsigned long Application::GetQMeasurement() +{ + int qn=0; + + TCCR1B = (1<>2); + pitch_l=pitch_v; + + +//HW_LED2_ON; + + + // set wave frequency for each mode + switch (_mode) { + case MUTE : /* NOTHING! */; break; + case NORMAL : setWavetableSampleAdvance(((pitchCalibrationBase-pitch_v)+2048-(pitchPotValue<<2))>>registerValue); break; + }; + + // HW_LED2_OFF; + + pitchValueAvailable = false; + } + + if (volumeValueAvailable) { + vol = max(vol, 5000); + + vol_v=vol; // Averaging volume values + vol_v=vol_l+((vol_v-vol_l)>>2); + vol_l=vol_v; + + switch (_mode) { + case MUTE: vol_v = 0; break; + case NORMAL: vol_v = MAX_VOLUME-(volCalibrationBase-vol_v)/2+(volumePotValue<<2)-1024; break; + }; + + // Limit and set volume value + vol_v = min(vol_v, 4095); + // vol_v = vol_v - (1 + MAX_VOLUME - (volumePotValue << 2)); + vol_v = vol_v ; + vol_v = max(vol_v, 0); + loop_hand_pos = vol_v >> 4; + + // Give vScaledVolume a pseudo-exponential characteristic: + vScaledVolume = loop_hand_pos * (loop_hand_pos + 2); + + volumeValueAvailable = false; + } + + if (midi_timer > 100) // run midi app every 100 ticks equivalent to approximatevely 3 ms to avoid synth's overload + { + midi_application (); + midi_timer = 0; + } + + goto mloop; // End of main loop +} + +void Application::calibrate() +{ + resetPitchFlag(); + resetTimer(); + savePitchCounter(); + while (!pitchValueAvailable && timerUnexpiredMillis(10)) + ; // NOP + pitchCalibrationBase = pitch; + pitchCalibrationBaseFreq = FREQ_FACTOR/pitchCalibrationBase; + pitchCalibrationConstant = FREQ_FACTOR/pitchSensitivityConstant/2+200; + + resetVolFlag(); + resetTimer(); + saveVolCounter(); + while (!volumeValueAvailable && timerUnexpiredMillis(10)) + ; // NOP + volCalibrationBase = vol; + + + EEPROM.put(4,pitchCalibrationBase); + EEPROM.put(8,volCalibrationBase); + +} + +void Application::calibrate_pitch() +{ + +static int16_t pitchXn0 = 0; +static int16_t pitchXn1 = 0; +static int16_t pitchXn2 = 0; +static float q0 = 0; +static long pitchfn0 = 0; +static long pitchfn1 = 0; +static long pitchfn = 0; + + + + InitialisePitchMeasurement(); + interrupts(); + SPImcpDACinit(); + + qMeasurement = GetQMeasurement(); // Measure Arudino clock frequency + +q0 = (16000000/qMeasurement*500000); //Calculated set frequency based on Arudino clock frequency + +pitchXn0 = 0; +pitchXn1 = 4095; + +pitchfn = q0-PitchFreqOffset; // Add offset calue to set frequency + + + +SPImcpDAC2Bsend(1600); + +SPImcpDAC2Asend(pitchXn0); +delay(100); +pitchfn0 = GetPitchMeasurement(); + +SPImcpDAC2Asend(pitchXn1); +delay(100); +pitchfn1 = GetPitchMeasurement(); + + +while(abs(pitchfn0-pitchfn1)>CalibrationTolerance){ // max allowed pitch frequency offset + +SPImcpDAC2Asend(pitchXn0); +delay(100); +pitchfn0 = GetPitchMeasurement()-pitchfn; + +SPImcpDAC2Asend(pitchXn1); +delay(100); +pitchfn1 = GetPitchMeasurement()-pitchfn; + +pitchXn2=pitchXn1-((pitchXn1-pitchXn0)*pitchfn1)/(pitchfn1-pitchfn0); // new DAC value + +delay(100); + + +pitchXn0 = pitchXn1; +pitchXn1 = pitchXn2; + +HW_LED2_TOGGLE; + +} +delay(100); + +EEPROM.put(0,pitchXn0); + +} + +void Application::calibrate_volume() +{ + + +static int16_t volumeXn0 = 0; +static int16_t volumeXn1 = 0; +static int16_t volumeXn2 = 0; +static float q0 = 0; +static long volumefn0 = 0; +static long volumefn1 = 0; +static long volumefn = 0; + + + InitialiseVolumeMeasurement(); + interrupts(); + SPImcpDACinit(); + + +volumeXn0 = 0; +volumeXn1 = 4095; + +q0 = (16000000/qMeasurement*460765); +volumefn = q0-VolumeFreqOffset; + + + +SPImcpDAC2Bsend(volumeXn0); +delay_NOP(44316);//44316=100ms + +volumefn0 = GetVolumeMeasurement(); + +SPImcpDAC2Bsend(volumeXn1); + +delay_NOP(44316);//44316=100ms +volumefn1 = GetVolumeMeasurement(); + + + +while(abs(volumefn0-volumefn1)>CalibrationTolerance){ + +SPImcpDAC2Bsend(volumeXn0); +delay_NOP(44316);//44316=100ms +volumefn0 = GetVolumeMeasurement()-volumefn; + +SPImcpDAC2Bsend(volumeXn1); +delay_NOP(44316);//44316=100ms +volumefn1 = GetVolumeMeasurement()-volumefn; + +volumeXn2=volumeXn1-((volumeXn1-volumeXn0)*volumefn1)/(volumefn1-volumefn0); // calculate new DAC value + +delay_NOP(44316);//44316=100ms + +volumeXn0 = volumeXn1; +volumeXn1 = volumeXn2; +HW_LED2_TOGGLE; + +} + +EEPROM.put(2,volumeXn0); + + +} + +void Application::hzToAddVal(float hz) { + setWavetableSampleAdvance((uint16_t)(hz * HZ_ADDVAL_FACTOR)); +} + +void Application::playNote(float hz, uint16_t milliseconds = 500, uint8_t volume = 255) { + vScaledVolume = volume * (volume + 2); + hzToAddVal(hz); + millitimer(milliseconds); + vScaledVolume = 0; +} + +void Application::playStartupSound() { + playNote(MIDDLE_C, 150, 25); + playNote(MIDDLE_C * 2, 150, 25); + playNote(MIDDLE_C * 4, 150, 25); +} + +void Application::playCalibratingCountdownSound() { + playNote(MIDDLE_C * 2, 150, 25); + playNote(MIDDLE_C * 2, 150, 25); +} + +void Application::playModeSettingSound() { + for (int i = 0; i <= _mode; i++) { + playNote(MIDDLE_C * 2, 200, 25); + millitimer(100); + } +} + +void Application::delay_NOP(unsigned long time) { + volatile unsigned long i = 0; + for (i = 0; i < time; i++) { + __asm__ __volatile__ ("nop"); + } +} + + + +void Application::midi_setup() +{ + // Set MIDI baud rate: + Serial.begin(115200); // Baudrate for midi to serial. Use a serial to midi router http://projectgus.github.com/hairless-midiserial/ + //Serial.begin(31250); // Baudrate for real midi. Use din connection https://www.arduino.cc/en/Tutorial/Midi or HIDUINO https://github.com/ddiakopoulos/hiduino + + _midistate = MIDI_SILENT; +} + + +void Application::midi_msg_send(uint8_t channel, uint8_t midi_cmd1, uint8_t midi_cmd2, uint8_t midi_value) +{ + uint8_t mixed_cmd1_channel; + + mixed_cmd1_channel = (midi_cmd1 & 0xF0)| (channel & 0x0F); + + Serial.write(mixed_cmd1_channel); + Serial.write(midi_cmd2); + Serial.write(midi_value); +} + +// midi_application sends note and volume and uses pitch bend to simulate continuous picth. +// Calibrate pitch bend and other parameters accordingly to the receiver synth (see midi_calibrate). +// New notes won't be generated as long as pitch bend will do the job. +// The bigger is synth's pitch bend range the beter is the effect. +// If pitch bend range = 1 no picth bend is generated (portamento will do a better job) +void Application::midi_application () +{ + double delta_loop_cc_val = 0; + double calculated_velocity = 0; + + + // Calculate loop antena cc value for midi + new_midi_loop_cc_val = loop_hand_pos >> 1; + new_midi_loop_cc_val = min (new_midi_loop_cc_val, 127); + delta_loop_cc_val = (double)new_midi_loop_cc_val - (double)old_midi_loop_cc_val; + + // Calculate log freq + if ((vPointerIncrement < 18) || (vPointerIncrement > 65518)) + { + // Lowest note + double_log_freq = 0; + } + else if ((vPointerIncrement > 26315) && (vPointerIncrement < 39221)) + { + // Highest note + double_log_freq = 127; + } + else if (vPointerIncrement < 32768) + { + // Positive frequencies + // Find note in the playing range + double_log_freq = (log (vPointerIncrement/17.152) / 0.057762265); // Precise note played in the logaritmic scale + } + else + { + // Negative frequencies + // Find note in the playing range + double_log_freq = (log ((65535-vPointerIncrement+1)/17.152) / 0.057762265); // Precise note played in the logaritmic scale + } + + // Calculate rod antena cc value for midi + new_midi_rod_cc_val = round (double_log_freq); + + // State machine for MIDI + switch (_midistate) + { + case MIDI_SILENT: + // Always refresh midi loop antena cc. + if (new_midi_loop_cc_val != old_midi_loop_cc_val) + { + midi_msg_send(midi_channel, 0xB0, loop_midi_cc, new_midi_loop_cc_val); + old_midi_loop_cc_val = new_midi_loop_cc_val; + } + else + { + // do nothing + } + + // Always refresh midi rod antena cc if applicable. + if ((rod_midi_cc != 255) && (new_midi_rod_cc_val != old_midi_rod_cc_val)) + { + midi_msg_send(midi_channel, 0xB0, rod_midi_cc, new_midi_rod_cc_val); + old_midi_rod_cc_val = new_midi_rod_cc_val; + } + else + { + // do nothing + } + + // If player's hand moves away from volume antenna + if (new_midi_loop_cc_val > midi_volume_trigger) + { + // Set key follow to the minimum in order to use closest note played as the center note + midi_key_follow = 0.5; + + // Calculate note and associated pitch bend + calculate_note_bend (); + + // Send pitch bend to reach precise played note (send 8192 (no pitch bend) in case of midi_bend_range == 1) + midi_msg_send(midi_channel, 0xE0, midi_bend_low, midi_bend_high); + old_midi_bend = new_midi_bend; + + // Calculate velocity + if (midi_timer != 0) + { + calculated_velocity = (64 * (127 - (double)midi_volume_trigger) / 127) + (VELOCITY_SENS * (double)midi_volume_trigger * delta_loop_cc_val / (double)midi_timer); + midi_velocity = min (round (abs (calculated_velocity)), 127); + } + else + { + // should not happen + midi_velocity = 64; + } + + + // Play the note + midi_msg_send(midi_channel, 0x90, new_midi_note, midi_velocity); + old_midi_note = new_midi_note; + + _midistate = MIDI_PLAYING; + } + else + { + // Do nothing + } + break; + + case MIDI_PLAYING: + // Always refresh midi loop antena cc. + if (new_midi_loop_cc_val != old_midi_loop_cc_val) + { + midi_msg_send(midi_channel, 0xB0, loop_midi_cc, new_midi_loop_cc_val); + old_midi_loop_cc_val = new_midi_loop_cc_val; + } + else + { + // do nothing + } + + // Always refresh midi rod antena cc if applicable. + if ((rod_midi_cc != 255) && (new_midi_rod_cc_val != old_midi_rod_cc_val)) + { + midi_msg_send(midi_channel, 0xB0, rod_midi_cc, new_midi_rod_cc_val); + old_midi_rod_cc_val = new_midi_rod_cc_val; + } + else + { + // do nothing + } + + // If player's hand is far from volume antenna + if (new_midi_loop_cc_val > midi_volume_trigger) + { + if ( flag_legato_on == 1) + { + // Set key follow so as next played note will be at limit of pitch bend range + midi_key_follow = (double)(midi_bend_range) - PLAYER_ACCURACY; + } + else + { + // Set key follow to max so as no key follows + midi_key_follow = 127; + } + + // Calculate note and associated pitch bend + calculate_note_bend (); + + // Refresh midi pitch bend value + if (new_midi_bend != old_midi_bend) + { + midi_msg_send(midi_channel, 0xE0, midi_bend_low, midi_bend_high); + old_midi_bend = new_midi_bend; + } + else + { + // do nothing + } + + // Refresh midi note + if (new_midi_note != old_midi_note) + { + // Play new note before muting old one to play legato on monophonic synth + // (pitch pend management tends to break expected effect here) + midi_msg_send(midi_channel, 0x90, new_midi_note, midi_velocity); + midi_msg_send(midi_channel, 0x90, old_midi_note, 0); + old_midi_note = new_midi_note; + } + else + { + // do nothing + } + } + else // Means that player's hand moves to the volume antenna + { + // Send note off + midi_msg_send(midi_channel, 0x90, old_midi_note, 0); + + _midistate = MIDI_SILENT; + } + break; + + case MIDI_STOP: + // Send all note off + midi_msg_send(midi_channel, 0xB0, 0x7B, 0x00); + + _midistate = MIDI_MUTE; + break; + + case MIDI_MUTE: + //do nothing + break; + + } +} + +void Application::calculate_note_bend () +{ + double double_log_bend; + double double_norm_log_bend; + + double_log_bend = double_log_freq - old_midi_note; // How far from last played midi chromatic note we are + + // If too much far from last midi chromatic note played (midi_key_follow depends on pitch bend range) + if ((abs (double_log_bend) >= midi_key_follow) && (midi_key_follow != 127)) + { + new_midi_note = round (double_log_freq); // Select the new midi chromatic note + double_log_bend = double_log_freq - new_midi_note; // calculate bend to reach precise note played + } + else + { + new_midi_note = old_midi_note; // No change + } + + // If pitch bend activated + if (flag_pitch_bend_on == 1) + { + // use it to reach precise note played + double_norm_log_bend = (double_log_bend / midi_bend_range); + if (double_norm_log_bend > 1) + { + double_norm_log_bend = 1; + } + else if (double_norm_log_bend < -1) + { + double_norm_log_bend = -1; + } + new_midi_bend = 8192 + (8191 * double_norm_log_bend); // Calculate midi pitch bend + } + else + { + // Don't use pitch bend + new_midi_bend = 8192; + } + + + // Prepare the 2 bites of picth bend midi message + midi_bend_low = (int8_t) (new_midi_bend & 0x007F); + midi_bend_high = (int8_t) ((new_midi_bend & 0x3F80)>> 7); +} + + + +void Application::init_parameters () +{ + // init data pot value to avoid 1st position to be taken into account + data_pot_value = analogRead(WAVE_SELECT_POT); + old_data_pot_value = data_pot_value; + +} + +void Application::set_parameters () +{ + uint16_t param_pot_value = 0; + + param_pot_value = analogRead(REGISTER_SELECT_POT); + data_pot_value = analogRead(WAVE_SELECT_POT); + + // If data pot moved + if (abs((int32_t)data_pot_value - (int32_t)old_data_pot_value) >= 8) + { + // Modify selected parameter + switch (param_pot_value >> 7) + { + case 0: + // Transpose + switch (data_pot_value >> 8) + { + case 0: + registerValue=3; // -1 Octave + break; + case 1: + case 2: + registerValue=2; // Center + break; + default: + registerValue=1; // +1 Octave + break; + } + break; + + case 1: + // Waveform + vWavetableSelector=data_pot_value>>7; + break; + + case 2: + // Channel + midi_channel = (uint8_t)((data_pot_value >> 6) & 0x000F); + if (old_midi_channel != midi_channel) + { + // Send all note off to avoid stuck notes + midi_msg_send(old_midi_channel, 0xB0, 0x7B, 0x00); + old_midi_channel = midi_channel; + } + break; + + case 3: + // Rod antenna mode + switch (data_pot_value >> 7) + { + case 0: + case 1: + flag_legato_on = 0; + flag_pitch_bend_on = 0; + break; + case 2: + case 3: + flag_legato_on = 0; + flag_pitch_bend_on = 1; + break; + case 4: + case 5: + flag_legato_on = 1; + flag_pitch_bend_on = 0; + break; + default: + flag_legato_on = 1; + flag_pitch_bend_on = 1; + break; + } + break; + + case 4: + // Pitch bend range + switch (data_pot_value >> 7) + { + case 0: + midi_bend_range = 1; + break; + case 1: + midi_bend_range = 2; + break; + case 2: + midi_bend_range = 4; + break; + case 3: + midi_bend_range = 5; + break; + case 4: + midi_bend_range = 7; + break; + case 5: + midi_bend_range = 12; + break; + case 6: + midi_bend_range = 24; + break; + default: + midi_bend_range = 48; + break; + } + break; + + case 5: + // Volume trigger + midi_volume_trigger = (uint8_t)((data_pot_value >> 3) & 0x007F); + break; + + case 6: + //Rod antenna cc + switch (data_pot_value >> 7) + { + case 0: + rod_midi_cc = 255; // Nothing + break; + case 1: + case 2: + rod_midi_cc = 8; // Balance + break; + case 3: + case 4: + rod_midi_cc = 10; // Pan + break; + case 5: + case 6: + rod_midi_cc = 16; // Ribbon Controler + break; + default: + rod_midi_cc = 74; // Cutoff (exists of both loop and rod) + break; + } + break; + + + default: + // Loop antenna cc + switch (data_pot_value >> 7) + { + case 0: + loop_midi_cc = 1; // Modulation + break; + case 1: + loop_midi_cc = 7; // Volume + break; + case 2: + loop_midi_cc = 11; // Expression + break; + case 3: + loop_midi_cc = 71; // Resonnance + break; + case 4: + loop_midi_cc = 74; // Cutoff (exists of both loop and rod) + break; + case 5: + loop_midi_cc = 91; // Reverb + break; + case 6: + loop_midi_cc = 93; // Chorus + break; + default: + loop_midi_cc = 95; // Phaser + break; + } + break; + } + + // Memorize data pot value to monitor changes + old_data_pot_value = data_pot_value; + } }