diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index fa9c5c2..c1ef3c1 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -1,801 +1,859 @@ -#include "Arduino.h" - -#include "application.h" - -#include "hw.h" -#include "mcpDac.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 uint16_t old_midi_note =0; -static uint16_t old_midi_volume =0; -static uint16_t old_midi_bend = 0; - -static double midi_key_follow = 0.5; -static uint8_t midi_channel = 0; -static uint8_t old_midi_channel = 0; -static uint8_t midi_bend_range = 0; -static uint8_t midi_volume_trigger = 0; - -uint8_t registerValue = 0; - -uint16_t data_pot_value = 0; -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 - - mcpDacInit(); - -EEPROM.get(0,pitchDAC); -EEPROM.get(2,volumeDAC); - -mcpDac2ASend(pitchDAC); -mcpDac2BSend(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)/registerValue+2048-(pitchPotValue<<2)); 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); - vScaledVolume = vol_v >> 4; - - 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(); - mcpDacInit(); - - 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 - - - -mcpDac2BSend(1600); - -mcpDac2ASend(pitchXn0); -delay(100); -pitchfn0 = GetPitchMeasurement(); - -mcpDac2ASend(pitchXn1); -delay(100); -pitchfn1 = GetPitchMeasurement(); - - -while(abs(pitchfn0-pitchfn1)>CalibrationTolerance){ // max allowed pitch frequency offset - -mcpDac2ASend(pitchXn0); -delay(100); -pitchfn0 = GetPitchMeasurement()-pitchfn; - -mcpDac2ASend(pitchXn1); -delay(100); -pitchfn1 = GetPitchMeasurement()-pitchfn; - -pitchXn2=pitchXn1-((pitchXn1-pitchXn0)*pitchfn1)/(pitchfn1-pitchfn0); // new DAC value - - - -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(); - mcpDacInit(); - - -volumeXn0 = 0; -volumeXn1 = 4095; - -q0 = (16000000/qMeasurement*460765); -volumefn = q0-VolumeFreqOffset; - - - -mcpDac2BSend(volumeXn0); -delay_NOP(44316);//44316=100ms - -volumefn0 = GetVolumeMeasurement(); - -mcpDac2BSend(volumeXn1); - -delay_NOP(44316);//44316=100ms -volumefn1 = GetVolumeMeasurement(); - - - -while(abs(volumefn0-volumefn1)>CalibrationTolerance){ - -mcpDac2BSend(volumeXn0); -delay_NOP(44316);//44316=100ms -volumefn0 = GetVolumeMeasurement()-volumefn; - -mcpDac2BSend(volumeXn1); -delay_NOP(44316);//44316=100ms -volumefn1 = GetVolumeMeasurement()-volumefn; - -volumeXn2=volumeXn1-((volumeXn1-volumeXn0)*volumefn1)/(volumefn1-volumefn0); // calculate new DAC value - - - -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; - 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 () -{ - uint16_t new_midi_note; - uint16_t new_midi_volume; - - uint16_t new_midi_bend; - uint8_t midi_bend_low; - uint8_t midi_bend_high; - - double double_log_freq; - double double_log_bend; - - // Calculate volume for midi - new_midi_volume = vScaledVolume >> 1; - new_midi_volume = min (new_midi_volume, 127); - - // Calculate note and pitch bend for midi - if (vPointerIncrement < 18) - { - // Highest note - new_midi_note = 0; - new_midi_bend = 8192; - } - else if (vPointerIncrement > 26315) - { - // Lowest note - new_midi_note = 127; - new_midi_bend = 8192; - } - else - { - // Find note in the playing range - double_log_freq = (log (vPointerIncrement/17.152) / 0.057762265); // Precise note played in the logaritmic scale - 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) - { - 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 range greater than 1 - if (midi_bend_range > 1) - { - // use it to reach precise note played - new_midi_bend = 8192 + (8191 * double_log_bend / midi_bend_range); // Calculate midi pitch bend - } - else - { - // Don't use pitch bend (portamento would do a beter job) - 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); - - // State machine for MIDI - switch (_midistate) - { - case MIDI_SILENT: - // Synth sound could be in Release phase of ADSR or may have some delay or reverb effect so... - - // ... don't refresh pitch bend: - // Instruction "midi_key_follow = 0.5;" and unrefreshed notes would make pitch bend verry messy. - - // ... but always refresh midi volume value. - if (new_midi_volume != old_midi_volume) - { - midi_msg_send(midi_channel, 0xB0, 0x07, new_midi_volume); - old_midi_volume = new_midi_volume; - } - else - { - // do nothing - } - - // If player's hand moves away from volume antenna - if (new_midi_volume > midi_volume_trigger) - { - // 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; - - // Play the note - midi_msg_send(midi_channel, 0x90, new_midi_note, 0x45); - old_midi_note = new_midi_note; - - // Set key follow so as next played note will be at limit of pitch bend range - midi_key_follow = (double)(midi_bend_range) - 0.2; - - _midistate = MIDI_PLAYING; - } - else - { - // Do nothing - } - break; - - case MIDI_PLAYING: - // Always refresh midi volume value - if (new_midi_volume != old_midi_volume) - { - midi_msg_send(midi_channel, 0xB0, 0x07, new_midi_volume); - old_midi_volume = new_midi_volume; - } - else - { - // do nothing - } - - // If player's hand is far from volume antenna - if (new_midi_volume > midi_volume_trigger) - { - // 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, 0x45); - 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); - - // Set key follow to the minimum in order to use closest note played as the center note for pitch bend next time - midi_key_follow = 0.5; - - _midistate = MIDI_SILENT; - } - break; - - case MIDI_STOP: - // Send all note off - midi_msg_send(midi_channel, 0xB0, 0x7B, 0x00); - - // Mute long release notes - midi_msg_send(midi_channel, 0xB0, 0x07, 0); - old_midi_volume = 0; - - _midistate = MIDI_MUTE; - break; - - case MIDI_MUTE: - //do nothing - break; - - } -} - - -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; - - // Transpose - registerValue=4; - - // Audio, Audio+Midi, MIDI - - // Waveform - vWavetableSelector=0; - - // Channel - midi_channel = 0; - - // Rod antenna mode - - // Pitch bend range - midi_bend_range = 2; - - // Volume trigger - midi_volume_trigger = 0; - - // Loop antenna mode -} - -void Application::set_parameters () -{ - uint16_t param_pot_value = 0; - uint16_t data_pot_value = 0; - - param_pot_value = analogRead(REGISTER_SELECT_POT); - data_pot_value = analogRead(WAVE_SELECT_POT); - - // If data pot moved - if (abs(data_pot_value-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: - // Audio, Audio+Midi, MIDI - - break; - - case 2: - // Waveform - vWavetableSelector=data_pot_value>>7; - break; - - case 3: - // 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(midi_channel, 0xB0, 0x7B, 0x00); - old_midi_channel == midi_channel; - } - break; - - case 4: - // Rod antenna mode - break; - - case 5: - // 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 6: - // Volume trigger - midi_volume_trigger = (uint8_t)((data_pot_value >> 3) & 0x007F); - break; - - default: - // Loop antenna mode - 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 "mcpDac.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 uint16_t new_midi_note =0; +static uint16_t old_midi_note =0; + +static uint16_t new_midi_volume =0; +static uint16_t old_midi_volume =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 midi_key_follow = 0.5; +static uint8_t midi_channel = 0; +static uint8_t old_midi_channel = 0; +static uint8_t midi_bend_range = 0; +static uint8_t midi_volume_trigger = 0; +static uint8_t flag_legato_on = 1; +static uint8_t flag_pitch_bend_on = 1; + +uint8_t registerValue = 0; + +uint16_t data_pot_value = 0; +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 + + mcpDacInit(); + +EEPROM.get(0,pitchDAC); +EEPROM.get(2,volumeDAC); + +mcpDac2ASend(pitchDAC); +mcpDac2BSend(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)/registerValue+2048-(pitchPotValue<<2)); 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); + vScaledVolume = vol_v >> 4; + + 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(); + mcpDacInit(); + + 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 + + + +mcpDac2BSend(1600); + +mcpDac2ASend(pitchXn0); +delay(100); +pitchfn0 = GetPitchMeasurement(); + +mcpDac2ASend(pitchXn1); +delay(100); +pitchfn1 = GetPitchMeasurement(); + + +while(abs(pitchfn0-pitchfn1)>CalibrationTolerance){ // max allowed pitch frequency offset + +mcpDac2ASend(pitchXn0); +delay(100); +pitchfn0 = GetPitchMeasurement()-pitchfn; + +mcpDac2ASend(pitchXn1); +delay(100); +pitchfn1 = GetPitchMeasurement()-pitchfn; + +pitchXn2=pitchXn1-((pitchXn1-pitchXn0)*pitchfn1)/(pitchfn1-pitchfn0); // new DAC value + + + +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(); + mcpDacInit(); + + +volumeXn0 = 0; +volumeXn1 = 4095; + +q0 = (16000000/qMeasurement*460765); +volumefn = q0-VolumeFreqOffset; + + + +mcpDac2BSend(volumeXn0); +delay_NOP(44316);//44316=100ms + +volumefn0 = GetVolumeMeasurement(); + +mcpDac2BSend(volumeXn1); + +delay_NOP(44316);//44316=100ms +volumefn1 = GetVolumeMeasurement(); + + + +while(abs(volumefn0-volumefn1)>CalibrationTolerance){ + +mcpDac2BSend(volumeXn0); +delay_NOP(44316);//44316=100ms +volumefn0 = GetVolumeMeasurement()-volumefn; + +mcpDac2BSend(volumeXn1); +delay_NOP(44316);//44316=100ms +volumefn1 = GetVolumeMeasurement()-volumefn; + +volumeXn2=volumeXn1-((volumeXn1-volumeXn0)*volumefn1)/(volumefn1-volumefn0); // calculate new DAC value + + + +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; + 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 double_log_freq; + double double_log_bend; + + // Calculate volume for midi + new_midi_volume = vScaledVolume >> 1; + new_midi_volume = min (new_midi_volume, 127); + + // State machine for MIDI + switch (_midistate) + { + case MIDI_SILENT: + // Synth sound could be in Release phase of ADSR or may have some delay or reverb effect so... + + // ... don't refresh pitch bend: + // Instruction "midi_key_follow = 0.5;" and unrefreshed notes would make pitch bend verry messy. + + // ... but always refresh midi volume value. + if (new_midi_volume != old_midi_volume) + { + midi_msg_send(midi_channel, 0xB0, 0x07, new_midi_volume); + old_midi_volume = new_midi_volume; + } + else + { + // do nothing + } + + // If player's hand moves away from volume antenna + if (new_midi_volume > 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; + + // Play the note + midi_msg_send(midi_channel, 0x90, new_midi_note, 0x45); + old_midi_note = new_midi_note; + + _midistate = MIDI_PLAYING; + } + else + { + // Do nothing + } + break; + + case MIDI_PLAYING: + // Always refresh midi volume value + if (new_midi_volume != old_midi_volume) + { + midi_msg_send(midi_channel, 0xB0, 0x07, new_midi_volume); + old_midi_volume = new_midi_volume; + } + else + { + // do nothing + } + + // If player's hand is far from volume antenna + if (new_midi_volume > 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) - 0.2; + } + 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, 0x45); + 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); + + // Mute long release notes + midi_msg_send(midi_channel, 0xB0, 0x07, 0); + old_midi_volume = 0; + + _midistate = MIDI_MUTE; + break; + + case MIDI_MUTE: + //do nothing + break; + + } +} + +void Application::calculate_note_bend () +{ + double double_log_freq; + double double_log_bend; + double double_norm_log_bend; + + + // Calculate log freq + if (vPointerIncrement < 18) + { + // Highest note + double_log_freq = 0; + } + else if (vPointerIncrement > 26315) + { + // Lowest note + double_log_freq = 127; + } + else + { + // Find note in the playing range + double_log_freq = (log (vPointerIncrement/17.152) / 0.057762265); // Precise note played in the logaritmic scale + } + + 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; + + // Transpose + registerValue = 4; + + // Audio, Audio+Midi, MIDI + + // Waveform + vWavetableSelector = 0; + + // Channel + midi_channel = 0; + + // Rod antenna mode + flag_legato_on = 1; + flag_pitch_bend_on = 1; + + // Pitch bend range + midi_bend_range = 2; + + // Volume trigger + midi_volume_trigger = 0; + + // Loop antenna mode +} + +void Application::set_parameters () +{ + uint16_t param_pot_value = 0; + uint16_t data_pot_value = 0; + + param_pot_value = analogRead(REGISTER_SELECT_POT); + data_pot_value = analogRead(WAVE_SELECT_POT); + + // If data pot moved + if (abs(data_pot_value-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: + // Audio, Audio+Midi, MIDI + + break; + + case 2: + // Waveform + vWavetableSelector=data_pot_value>>7; + break; + + case 3: + // 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(midi_channel, 0xB0, 0x7B, 0x00); + old_midi_channel == midi_channel; + } + break; + + case 4: + // 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 5: + // 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 6: + // Volume trigger + midi_volume_trigger = (uint8_t)((data_pot_value >> 3) & 0x007F); + break; + + default: + // Loop antenna mode + break; + } + + // Memorize data pot value to monitor changes + old_data_pot_value = data_pot_value; + } } diff --git a/Open_Theremin_V3/application.h b/Open_Theremin_V3/application.h index 48e4629..23cae07 100644 --- a/Open_Theremin_V3/application.h +++ b/Open_Theremin_V3/application.h @@ -67,7 +67,7 @@ class Application { void midi_setup(); void midi_msg_send(uint8_t channel, uint8_t midi_cmd1, uint8_t midi_cmd2, uint8_t midi_value); void midi_application (); - + void calculate_note_bend (); void init_parameters (); void set_parameters ();