From 7138f2f73b0c45486b59f96da84be03c0d2e15dc Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Sun, 12 Nov 2017 17:40:07 +0100 Subject: [PATCH 01/38] Add files via upload Changed setting mode: "Register" pot becomes "Parameter" pot "Timbre" pot becomes "Value" pot --- Open_Theremin_V3/application.cpp | 210 ++++++++++++++++++------------- Open_Theremin_V3/application.h | 4 +- 2 files changed, 127 insertions(+), 87 deletions(-) diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index 42cb400..910c353 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -30,9 +30,14 @@ 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), @@ -65,8 +70,9 @@ initialiseInterrupts(); EEPROM.get(4,pitchCalibrationBase); EEPROM.get(8,volCalibrationBase); + init_parameters (); midi_setup(); - + } void Application::initialiseTimer() { @@ -169,25 +175,14 @@ void Application::loop() { uint16_t volumePotValue = 0; uint16_t pitchPotValue = 0; - int registerPotValue,registerPotValueL = 0; - int wavePotValue,wavePotValueL = 0; - uint8_t registerValue = 0; - - mloop: // Main loop avoiding the GCC "optimization" pitchPotValue = analogRead(PITCH_POT); volumePotValue = analogRead(VOLUME_POT); - registerPotValue = analogRead(REGISTER_SELECT_POT); - wavePotValue = analogRead(WAVE_SELECT_POT); - if ((registerPotValue-registerPotValueL) >= HYST_VAL || (registerPotValueL-registerPotValue) >= HYST_VAL) registerPotValueL=registerPotValue; - if (((wavePotValue-wavePotValueL) >= HYST_VAL) || ((wavePotValueL-wavePotValue) >= HYST_VAL)) wavePotValueL=wavePotValue; - - vWavetableSelector=wavePotValueL>>7; - registerValue=4-(registerPotValueL>>8); - + set_parameters (); + if (_state == PLAYING && HW_BUTTON_PRESSED) { _state = CALIBRATING; @@ -222,24 +217,16 @@ void Application::loop() { playStartupSound(); - if (registerPotValue < 512) // if register pot turned CCW - { - // calibrate heterodyne parameters - calibrate_pitch(); - calibrate_volume(); + // calibrate heterodyne parameters + calibrate_pitch(); + calibrate_volume(); - initialiseTimer(); - initialiseInterrupts(); + initialiseTimer(); + initialiseInterrupts(); - playCalibratingCountdownSound(); - calibrate(); - } - else // if register turned CW - { - // calibrate midi parameters - midi_calibrate (); - }; + playCalibratingCountdownSound(); + calibrate(); _mode=NORMAL; HW_LED1_ON;HW_LED2_OFF; @@ -501,11 +488,6 @@ void Application::delay_NOP(unsigned long time) { void Application::midi_setup() { - - EEPROM.get(12,midi_channel); - EEPROM.get(13,midi_bend_range); - EEPROM.get(14,midi_volume_trigger); - // 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 @@ -705,59 +687,115 @@ void Application::midi_application () } } -// midi_calibrate allows the user to set some midi parameters -// Set potentiometer accordingly to comments bellow BEFORE entering in midi calibration mode. -// Hear may help somewhat to determine entered values -void Application::midi_calibrate () + +void Application::init_parameters () { - uint16_t pot_channel; - uint16_t pot_bend_range; - uint16_t pot_volume_trigger; + // 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=0; - uint16_t bend_range_scale; + // Audio, Audio+Midi, MIDI - // Midi channel uses "Timbre" pot. - // Waveform may help user do determine which couple of channel is chosen (WF 1 Lo -> Ch1, WF 1 Hi -> Ch2, WF 2 Lo -> Ch3, etc...) - pot_channel = analogRead(WAVE_SELECT_POT); - midi_channel = (uint8_t)((pot_channel >> 6) & 0x000F); - EEPROM.put(12,midi_channel); - - - // Pitch bend range and associated distance between notes jumps use "Pitch" pot. - // The user shall set synth's pitch bend range acordingly to the selected Theremin's pitch bend range: - // 1 semitone, 2 semitones (standard), 7 semitones (a fifth), 12 semitones (an octave) or 24 semitones (two octaves). - // The "1 semitone" setting blocks pitch bend generation (use portamento on the synth) - pot_bend_range = analogRead(PITCH_POT); - bend_range_scale = pot_bend_range >> 7; - switch (bend_range_scale) - { - 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; - } - - EEPROM.put(13,midi_bend_range); - - // Volume trigger uses "Volume" pot - // Select a high value if some percussive sounds are played (so as it is heard when volume is not null) - pot_volume_trigger = analogRead(VOLUME_POT); - midi_volume_trigger = (uint8_t)((pot_volume_trigger >> 3) & 0x007F); - EEPROM.put(14,midi_volume_trigger); + // 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; + } +} diff --git a/Open_Theremin_V3/application.h b/Open_Theremin_V3/application.h index 659576d..48e4629 100644 --- a/Open_Theremin_V3/application.h +++ b/Open_Theremin_V3/application.h @@ -67,7 +67,9 @@ 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 midi_calibrate (); + + void init_parameters (); + void set_parameters (); }; From 51520927e7d33fdb5e555964cdd66bf3cdfb9d1a Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Sun, 12 Nov 2017 20:51:19 +0100 Subject: [PATCH 02/38] Update application.cpp Corrected an horrible division by zero. --- Open_Theremin_V3/application.cpp | 1600 +++++++++++++++--------------- 1 file changed, 800 insertions(+), 800 deletions(-) diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index 910c353..fa9c5c2 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -1,801 +1,801 @@ -#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=0; - - // 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 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; + } } From a309ab3faba057c27a12c1a7ee365dfd21e79d02 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Sun, 12 Nov 2017 21:12:27 +0100 Subject: [PATCH 03/38] Add files via upload --- Open_Theremin_V3/application.cpp | 1658 ++++++++++++++++-------------- Open_Theremin_V3/application.h | 2 +- 2 files changed, 859 insertions(+), 801 deletions(-) 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 (); From 4e0ee3f658119ccc21c6acbae3d9d50719d336a8 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Sun, 12 Nov 2017 22:52:51 +0100 Subject: [PATCH 04/38] Add files via upload --- Open_Theremin_V3/application.cpp | 80 ++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index c1ef3c1..d417112 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -42,8 +42,10 @@ 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; +static uint8_t loop_midi_cc = 7; +static uint8_t rod_midi_cc = 255; -uint8_t registerValue = 0; +uint8_t registerValue = 4; uint16_t data_pot_value = 0; uint16_t old_data_pot_value = 0; @@ -731,8 +733,6 @@ void Application::init_parameters () // Transpose registerValue = 4; - // Audio, Audio+Midi, MIDI - // Waveform vWavetableSelector = 0; @@ -749,7 +749,11 @@ void Application::init_parameters () // Volume trigger midi_volume_trigger = 0; - // Loop antenna mode + // Rod antenna cc + rod_midi_cc = 255; + + // Loop antenna cc + loop_midi_cc = 7; } void Application::set_parameters () @@ -772,16 +776,11 @@ void Application::set_parameters () break; case 1: - // Audio, Audio+Midi, MIDI - - break; - - case 2: // Waveform vWavetableSelector=data_pot_value>>7; break; - case 3: + case 2: // Channel midi_channel = (uint8_t)((data_pot_value >> 6) & 0x000F); if (old_midi_channel != midi_channel) @@ -792,7 +791,7 @@ void Application::set_parameters () } break; - case 4: + case 3: // Rod antenna mode switch (data_pot_value >> 7) { @@ -818,7 +817,7 @@ void Application::set_parameters () } break; - case 5: + case 4: // Pitch bend range switch (data_pot_value >> 7) { @@ -843,13 +842,66 @@ void Application::set_parameters () } break; - case 6: + 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 + break; + } + break; + + default: - // Loop antenna mode + // Loop antenna cc + switch (data_pot_value >> 7) + { + case 0: + loop_midi_cc = 1; // Modulation + break; + case 1: + loop_midi_cc = 2; // Breath controler + break; + case 2: + loop_midi_cc = 4; // Foot controler + break; + case 3: + loop_midi_cc = 7; // Volume + break; + case 4: + loop_midi_cc = 11; // Expression + break; + case 5: + loop_midi_cc = 71; // Resonnance + break; + case 6: + loop_midi_cc = 91; // Reverb + break; + default: + loop_midi_cc = 93; // Chorus + break; + } break; } From cbf80c15c06edb1c8abf13f4acae26ea06678803 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Mon, 13 Nov 2017 22:20:53 +0100 Subject: [PATCH 05/38] Add files via upload --- Open_Theremin_V3/application.cpp | 122 +++++++++++++++++-------------- 1 file changed, 69 insertions(+), 53 deletions(-) diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index d417112..0fd2097 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -27,15 +27,20 @@ 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_loop_cc_val =0; +static uint16_t old_midi_loop_cc_val =0; + +static uint16_t new_midi_rod_cc_val =0; +static uint16_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; + static uint8_t midi_channel = 0; static uint8_t old_midi_channel = 0; static uint8_t midi_bend_range = 0; @@ -525,27 +530,50 @@ void Application::midi_msg_send(uint8_t channel, uint8_t midi_cmd1, uint8_t midi // 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 loop antena cc value for midi + new_midi_loop_cc_val = vScaledVolume >> 1; + new_midi_loop_cc_val = min (new_midi_loop_cc_val, 127); - // Calculate volume for midi - new_midi_volume = vScaledVolume >> 1; - new_midi_volume = min (new_midi_volume, 127); + // 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 + } + + // 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: - // 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) + // 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 { - midi_msg_send(midi_channel, 0xB0, 0x07, new_midi_volume); - old_midi_volume = new_midi_volume; + // 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 { @@ -553,7 +581,7 @@ void Application::midi_application () } // If player's hand moves away from volume antenna - if (new_midi_volume > midi_volume_trigger) + 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; @@ -578,11 +606,22 @@ void Application::midi_application () break; case MIDI_PLAYING: - // Always refresh midi volume value - if (new_midi_volume != old_midi_volume) + // Always refresh midi loop antena cc. + if (new_midi_loop_cc_val != old_midi_loop_cc_val) { - midi_msg_send(midi_channel, 0xB0, 0x07, new_midi_volume); - old_midi_volume = new_midi_volume; + 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 { @@ -590,7 +629,7 @@ void Application::midi_application () } // If player's hand is far from volume antenna - if (new_midi_volume > midi_volume_trigger) + if (new_midi_loop_cc_val > midi_volume_trigger) { if ( flag_legato_on == 1) { @@ -644,10 +683,6 @@ void Application::midi_application () // 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; @@ -660,27 +695,8 @@ void Application::midi_application () 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 @@ -867,7 +883,7 @@ void Application::set_parameters () rod_midi_cc = 16; // Ribbon Controler break; default: - rod_midi_cc = 74; // Cutoff + rod_midi_cc = 74; // Cutoff (exists of both loop and rod) break; } break; @@ -881,25 +897,25 @@ void Application::set_parameters () loop_midi_cc = 1; // Modulation break; case 1: - loop_midi_cc = 2; // Breath controler + loop_midi_cc = 7; // Volume break; case 2: - loop_midi_cc = 4; // Foot controler + loop_midi_cc = 11; // Expression break; case 3: - loop_midi_cc = 7; // Volume + loop_midi_cc = 71; // Resonnance break; case 4: - loop_midi_cc = 11; // Expression + loop_midi_cc = 74; // Cutoff (exists of both loop and rod) break; case 5: - loop_midi_cc = 71; // Resonnance + loop_midi_cc = 91; // Reverb break; case 6: - loop_midi_cc = 91; // Reverb + loop_midi_cc = 93; // Chorus break; default: - loop_midi_cc = 93; // Chorus + loop_midi_cc = 95; // Phaser break; } break; From 52676ee02e34ef064e88f6998986b54d80ef2c47 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Mon, 13 Nov 2017 23:08:36 +0100 Subject: [PATCH 06/38] Update README.md --- README.md | 81 +++++++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 4daaec5..5d258dc 100644 --- a/README.md +++ b/README.md @@ -42,71 +42,62 @@ Serial port is used to send midi messages now. ### How does it works ? -PITCH : -It uses first note detected at volume rise to generate a NOTEON. -Then it uses PITCHBEND to reach pitch as long as pitch bend range will do. -Beyond it generates a new NOTEON followed by a NOTEOFF for the previous note (legato). +PITCH ANTENNA (ROD): + +It uses first note detected as hand moves away from from volume loop antenna to generate a NOTEON. +Then it can use PITCHBEND (if activated) to reach exact pitch as long as pitch bend range will do. +Beyond it can generate a new NOTEON followed by a NOTEOFF for the previous note if legato mode activated. Pitch bend range can be configured (1, 2, 7, 12 or 24 semitones) to align with synth's maximum capabilities. -One exception is that I desactivated pitch bend in 1 semitone mode because portamento does a better job then. -VOLUME: -It generates VOLUME continuous controler, starting NOTEON and ending NOTE OFF (when playing staccato). +It can also generate a midi continuous controler change if one is selected. + +VOLUME ANTENNA (LOOP): + +It generates selected midi continuous controler change, starting NOTEON and ending NOTE OFF (when playing staccato). The trigger volume can be configured so as we have some volume at note attack on percussive sounds. -CONFIGURATION: -There is two calibration mode: - 1. If REGISTER POT turned counter clockwise at entering in calibration mode - -> Runs normal calibration of antennas. +CALIBRATION: + +This device runs normal calibration of antennas after pushing button for 3 seconds as per initial project - 2. If REGISTER POT turned clockwise at entering in calibration mode - -> Records midi settings as per pot position BEFORE entering in calibration mode: - - VOLUME POT : sets volume trigger level - - PITCH POT : sets pitch bend range (1, 2, 7, 12 or 24 semitones) - Use exactly same pitch bend range on your synth. Maximum setting possible is recomended. - - TIMBRE POT : sets Channel. In the absence of graduation, timbre variation may help - (Wave Form 1 low = CH1, WF 1 High = CH2, WF 2 Low = CH3, etc...) - + SETTINGS: + + "Register" Pot becomes "Selected Parameter" pot and have 8 positions. + "Timbre" pot become "Parameter's Value" and have variable number a position depending on selected parameter: + + 1. Register: 4 positions as in original Open Theremin V3 + 2. Timbre: 8 positions as in original Open Theremin V3 + 3. Channel: 16 positions (channel 1 to 16) + 4. Rod antenna mode: 4 positions + (Legato off/Pitch Bend off, Legato off/Pitch Bend on, Legato on/Pitch Bend off, Legato on/Pitch Bend on) + 5. Pitch bend range: 5 positions (1, 2, 7, 12, 24 Semitones) + 6. Volume trigger: 16 positions (0 to 127) + 7. Rod antenna cc: 5 positions + (None, 8-Balance, 10-Pan, 16-Ribbon controler, 74-cutoff) + 8. Loop antenna cc: 8 positions + (1-Modulation, 7-Volume, 11-Expression, 71-Resonnance, 74-Cutoff, 91-Reverb, 93-Chorus, 95-Phaser) + + MUTE BUTTON: + Sends ALL NOTE OFF on selected channel and stay in mute until it's pushed again. AUDIO: + Audio processing from antennas to output jack, including pots, LEDs and button functions, is exactly the same as in open theremin V3. ### What can I do to get a theremin like glissando? -Set pitch bend range of the theremin with a high value (12 semitones or 24 semitones). - +Activate picth bend and set pitch bend range of the theremin with a high value (12 semitones or 24 semitones). Set pitch bend range of the synth with the same value -Closest to real theremin settings (pitch bend range = 24 semitones): - - 1. Set pots like this: Volume = Min, Pitch = Max, Register = Max, Timbre = Midi channel. - - 2. Push button for two seconds. - - 3. Then set pots as for audio (Example : Volume = Mid, Pitch = Mid, Register = Wanted octave, Timbre = any) - - 4. Play (you can mix synth and audio if you want) - - ### If I do not trigger with the volume hand it also seems to trigger a new tone with the pitch antenna. Guess this is how MIDI works. -Yes, with settings above, if you trigger a note (with volume loop) and go in one direction (with pitch antenna) a new note will be triggered after two octaves. - -This is a limitation of midi. Maybe some synth can perform pitch bend on more that 2 octaves but none of mine does... - -### Tweak -In the following lines of application.cpp: - - // 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; +When legato mode is activated if you trigger a note (with volume loop) and go in one direction (with pitch antenna) a new note will be triggered at the limit of pitch bend range. -The "-0.2" could be changed into another value from "0" to "-0.5" depending on how good you are to reach center of the note that you play. "0" is for very good players. "-0.5" is very permissive and generates note toggling in 1 semitone mode. "-0.2" is the limit where my favourite chromatic tuner's green LED turns off (and it is OK for me). +Legato mode is used as a workaround for a limitation of midi (max 24 semitone pitch bend). Maybe some synth can perform pitch bend on more that 2 octaves but none of mine does... ### LICENSE From 135dbf61e2185e9e29dd3ad5711ce36fc14f4098 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Mon, 13 Nov 2017 23:23:58 +0100 Subject: [PATCH 07/38] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d258dc..b6601fc 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,12 @@ This device runs normal calibration of antennas after pushing button for 3 secon (None, 8-Balance, 10-Pan, 16-Ribbon controler, 74-cutoff) 8. Loop antenna cc: 8 positions (1-Modulation, 7-Volume, 11-Expression, 71-Resonnance, 74-Cutoff, 91-Reverb, 93-Chorus, 95-Phaser) - + +Select a Parameter and move "Parameter's Value" to change corresponding setting. + +Manipulation of "Rod antenna cc" and "Loop antenna cc" is not error proof. MIDI newbies should be advised change their value in MUTE mode. + +Default configuration is: Register = Lowest Register, Timbre = 1st Waveform, Channel = MIDI Channel 1, Rod antenna mode = Legato on/Pitch Bend on, Pitch bend range = 2 Semitones, Volume trigger = 0, Rod antenna cc = None, Loop antenna cc = 7-Volume. MUTE BUTTON: From baaf05d7f8b85526c3ff473ecc8287a46ab496be Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Mon, 13 Nov 2017 23:35:50 +0100 Subject: [PATCH 08/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6601fc..58cbd25 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ This device runs normal calibration of antennas after pushing button for 3 secon Select a Parameter and move "Parameter's Value" to change corresponding setting. -Manipulation of "Rod antenna cc" and "Loop antenna cc" is not error proof. MIDI newbies should be advised change their value in MUTE mode. +Manipulation of "Rod antenna cc" and "Loop antenna cc" is not error proof. MIDI newbies should be advised to change their value in MUTE mode. Default configuration is: Register = Lowest Register, Timbre = 1st Waveform, Channel = MIDI Channel 1, Rod antenna mode = Legato on/Pitch Bend on, Pitch bend range = 2 Semitones, Volume trigger = 0, Rod antenna cc = None, Loop antenna cc = 7-Volume. From 9831820e2a637288b4c2b5d8a25042865b166bbb Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Mon, 13 Nov 2017 23:37:24 +0100 Subject: [PATCH 09/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58cbd25..032529f 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Sends ALL NOTE OFF on selected channel and stay in mute until it's pushed again. AUDIO: -Audio processing from antennas to output jack, including pots, LEDs and button functions, is exactly the same as in open theremin V3. +Audio processing from antennas to output jack, including volume and pitch pots, LEDs and button functions, is exactly the same as in open theremin V3. ### What can I do to get a theremin like glissando? From 3d3e08919e3912991728a45ba82fb6e8fdd7dcdd Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Mon, 13 Nov 2017 23:39:08 +0100 Subject: [PATCH 10/38] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 032529f..2cac726 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,9 @@ Set pitch bend range of the synth with the same value ### If I do not trigger with the volume hand it also seems to trigger a new tone with the pitch antenna. Guess this is how MIDI works. -When legato mode is activated if you trigger a note (with volume loop) and go in one direction (with pitch antenna) a new note will be triggered at the limit of pitch bend range. +When legato mode is activated, if you trigger a note (with volume loop) and go in one direction (with pitch antenna) a new note will be triggered at the limit of pitch bend range. -Legato mode is used as a workaround for a limitation of midi (max 24 semitone pitch bend). Maybe some synth can perform pitch bend on more that 2 octaves but none of mine does... +Legato mode is used as a workaround for a limitation of midi (max 24 semitones pitch bend). Maybe some synth can perform pitch bend on more that 2 octaves but none of mine does... ### LICENSE From ec7bc87c0d72cb34785430435a5f0bf9f5d3568e Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Tue, 14 Nov 2017 20:16:05 +0100 Subject: [PATCH 11/38] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2cac726..450d8d3 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,10 @@ This device runs normal calibration of antennas after pushing button for 3 secon 3. Channel: 16 positions (channel 1 to 16) 4. Rod antenna mode: 4 positions (Legato off/Pitch Bend off, Legato off/Pitch Bend on, Legato on/Pitch Bend off, Legato on/Pitch Bend on) - 5. Pitch bend range: 5 positions (1, 2, 7, 12, 24 Semitones) - 6. Volume trigger: 16 positions (0 to 127) + 5. Pitch bend range: 5 positions (1, 2, 7, 12, 24 Semitones) + For classical glissando and in order to have same note on audio and MIDI, use exactly same pitch bend range on your synth. + Maximum setting possible is recomended. + 6. Volume trigger: 128 positions (0 to 127) 7. Rod antenna cc: 5 positions (None, 8-Balance, 10-Pan, 16-Ribbon controler, 74-cutoff) 8. Loop antenna cc: 8 positions From 022e6d5ead7abd7a2b96ce5615f3b6610fba78f5 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Tue, 14 Nov 2017 20:17:01 +0100 Subject: [PATCH 12/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 450d8d3..96fadf8 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ This device runs normal calibration of antennas after pushing button for 3 secon 3. Channel: 16 positions (channel 1 to 16) 4. Rod antenna mode: 4 positions (Legato off/Pitch Bend off, Legato off/Pitch Bend on, Legato on/Pitch Bend off, Legato on/Pitch Bend on) - 5. Pitch bend range: 5 positions (1, 2, 7, 12, 24 Semitones) + 5. Pitch bend range: 5 positions (1, 2, 7, 12, 24 Semitones). For classical glissando and in order to have same note on audio and MIDI, use exactly same pitch bend range on your synth. Maximum setting possible is recomended. 6. Volume trigger: 128 positions (0 to 127) From 63ab93c47da662e32870f6bd1bf21bbeb6bdd246 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Tue, 14 Nov 2017 20:20:47 +0100 Subject: [PATCH 13/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 96fadf8..c36d382 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ https://github.com/GaudiLabs/OpenTheremin_V3. Serial port is used to send midi messages now. -### How does it works ? +### How does it work ? PITCH ANTENNA (ROD): From ea3f502cbf2d54c0b4c55a255bdddc3be5f573ef Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Tue, 14 Nov 2017 21:20:29 +0100 Subject: [PATCH 14/38] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index c36d382..53b949b 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,7 @@ Urs makes also a very clear presentation of this MIDI feature on his website (ma http://www.gaudi.ch/OpenTheremin/index.php?option=com_content&view=article&id=200&Itemid=121 ### Don't click on the files! -Click on the "Download ZIP" Button to the right or [Click here](https://github.com/GaudiLabs/OpenTheremin_V3/archive/master.zip) -Then unpack the archive. +Click on the "clone or download" Button to the right. Then unpack the archive. ### Open Source Theremin based on the Arduino Platform From ba2496f630fe1fe98a7970f22b2819e54926def0 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Wed, 15 Nov 2017 21:39:34 +0100 Subject: [PATCH 15/38] Add files via upload --- Open_Theremin_V3/application.cpp | 43 ++++++++------------------------ 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index 0fd2097..2ce8d8f 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -40,20 +40,22 @@ 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 = 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; -uint8_t registerValue = 4; -uint16_t data_pot_value = 0; -uint16_t old_data_pot_value = 0; +static uint16_t data_pot_value = 0; +static uint16_t old_data_pot_value = 0; Application::Application() : _state(PLAYING), @@ -746,42 +748,17 @@ void Application::init_parameters () data_pot_value = analogRead(WAVE_SELECT_POT); old_data_pot_value = data_pot_value; - // Transpose - registerValue = 4; - - // 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; - - // Rod antenna cc - rod_midi_cc = 255; - - // Loop antenna cc - loop_midi_cc = 7; } 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) + if (abs((int32_t)data_pot_value - (int32_t)old_data_pot_value) >= 8) { // Modify selected parameter switch (param_pot_value >> 7) @@ -802,8 +779,8 @@ void Application::set_parameters () 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; + midi_msg_send(old_midi_channel, 0xB0, 0x7B, 0x00); + old_midi_channel = midi_channel; } break; From 6dc28e69e1ecf5463620b0f5bec65ec3c510002b Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 16 Nov 2017 21:30:08 +0100 Subject: [PATCH 16/38] Add files via upload --- Open_Theremin_V3/application.cpp | 41 +++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index 2ce8d8f..f3a4eb3 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -24,14 +24,16 @@ static float qMeasurement = 0; static int32_t volCalibrationBase = 0; -static uint16_t new_midi_note =0; -static uint16_t old_midi_note =0; +static uint8_t new_midi_note =0; +static uint8_t old_midi_note =0; -static uint16_t new_midi_loop_cc_val =0; -static uint16_t old_midi_loop_cc_val =0; +static uint8_t new_midi_loop_cc_val =0; +static uint8_t old_midi_loop_cc_val =0; -static uint16_t new_midi_rod_cc_val =0; -static uint16_t old_midi_rod_cc_val =0; +static uint8_t midi_velocity = 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; @@ -53,6 +55,9 @@ 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 7 // test and trial should help +#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; @@ -532,9 +537,14 @@ void Application::midi_msg_send(uint8_t channel, uint8_t midi_cmd1, uint8_t midi // 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 = vScaledVolume >> 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) @@ -594,9 +604,22 @@ void Application::midi_application () // 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, 0x45); + midi_msg_send(midi_channel, 0x90, new_midi_note, midi_velocity); old_midi_note = new_midi_note; _midistate = MIDI_PLAYING; @@ -636,7 +659,7 @@ void Application::midi_application () 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; + midi_key_follow = (double)(midi_bend_range) - PLAYER_ACCURACY; } else { @@ -663,7 +686,7 @@ void Application::midi_application () { // 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, new_midi_note, midi_velocity); midi_msg_send(midi_channel, 0x90, old_midi_note, 0); old_midi_note = new_midi_note; } From b77aea1b44658c802cc7895040caa4322af8ba0a Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 16 Nov 2017 21:56:34 +0100 Subject: [PATCH 17/38] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 53b949b..fe2b0b3 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,10 @@ It can also generate a midi continuous controler change if one is selected. VOLUME ANTENNA (LOOP): It generates selected midi continuous controler change, starting NOTEON and ending NOTE OFF (when playing staccato). + The trigger volume can be configured so as we have some volume at note attack on percussive sounds. +The trigger volume setting is also used to set sensitivity for velocity (how fast volume loop hand is moving when note is triggered). +Matter of fact, the higher is this setting, less margin we have for volume variation. It is compensated by increase of velocity sensitivity. CALIBRATION: @@ -72,7 +75,7 @@ This device runs normal calibration of antennas after pushing button for 3 secon 5. Pitch bend range: 5 positions (1, 2, 7, 12, 24 Semitones). For classical glissando and in order to have same note on audio and MIDI, use exactly same pitch bend range on your synth. Maximum setting possible is recomended. - 6. Volume trigger: 128 positions (0 to 127) + 6. Volume trigger / Velocity sensitivity (how fast moves the volume loop's hand): 128 positions (0 to 127) 7. Rod antenna cc: 5 positions (None, 8-Balance, 10-Pan, 16-Ribbon controler, 74-cutoff) 8. Loop antenna cc: 8 positions From 6f0965cc2cbeb2d6022b2a26e99d895661265a20 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 16 Nov 2017 22:06:18 +0100 Subject: [PATCH 18/38] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe2b0b3..a1c13d3 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,8 @@ VOLUME ANTENNA (LOOP): It generates selected midi continuous controler change, starting NOTEON and ending NOTE OFF (when playing staccato). -The trigger volume can be configured so as we have some volume at note attack on percussive sounds. -The trigger volume setting is also used to set sensitivity for velocity (how fast volume loop hand is moving when note is triggered). +The Volume trigger can be configured so as we have some volume at note attack on percussive sounds. +The Volume trigger setting is also used to set sensitivity for velocity (how fast volume loop hand is moving when note is triggered). Matter of fact, the higher is this setting, less margin we have for volume variation. It is compensated by increase of velocity sensitivity. CALIBRATION: From 9e76208b169370602d70c6efc049eafa86df00ec Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 16 Nov 2017 22:07:53 +0100 Subject: [PATCH 19/38] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a1c13d3..848479a 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,8 @@ VOLUME ANTENNA (LOOP): It generates selected midi continuous controler change, starting NOTEON and ending NOTE OFF (when playing staccato). -The Volume trigger can be configured so as we have some volume at note attack on percussive sounds. -The Volume trigger setting is also used to set sensitivity for velocity (how fast volume loop hand is moving when note is triggered). +The volume trigger can be configured so as we have some volume at note attack on percussive sounds. +The volume trigger setting is also used to set sensitivity for velocity (how fast volume loop hand is moving when note is triggered). Matter of fact, the higher is this setting, less margin we have for volume variation. It is compensated by increase of velocity sensitivity. CALIBRATION: From 263d716a03f068290e491df935a997eb4f3d79e5 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 16 Nov 2017 22:48:17 +0100 Subject: [PATCH 20/38] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 848479a..d8a39a7 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,15 @@ Select a Parameter and move "Parameter's Value" to change corresponding setting. Manipulation of "Rod antenna cc" and "Loop antenna cc" is not error proof. MIDI newbies should be advised to change their value in MUTE mode. Default configuration is: Register = Lowest Register, Timbre = 1st Waveform, Channel = MIDI Channel 1, Rod antenna mode = Legato on/Pitch Bend on, Pitch bend range = 2 Semitones, Volume trigger = 0, Rod antenna cc = None, Loop antenna cc = 7-Volume. - + + +TWEAKABLE PARAMETERS (in application.cpp): + +"#define VELOCITY_SENS 7" -> How easy it is to reach highest velocity (127). Something betwen 5 and 12. + +"#define PLAYER_ACCURACY 0.2" -> Pitch accuracy of player. Tolerance on note center for changing notes when playin legato. 0 (very accurate players) and 0.5 (may generate note toggling). + + MUTE BUTTON: Sends ALL NOTE OFF on selected channel and stay in mute until it's pushed again. From f0ad74f95889e07858aff9b1a8f28ce48450845d Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 16 Nov 2017 22:52:11 +0100 Subject: [PATCH 21/38] Update README.md --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d8a39a7..0d77484 100644 --- a/README.md +++ b/README.md @@ -88,12 +88,6 @@ Manipulation of "Rod antenna cc" and "Loop antenna cc" is not error proof. MIDI Default configuration is: Register = Lowest Register, Timbre = 1st Waveform, Channel = MIDI Channel 1, Rod antenna mode = Legato on/Pitch Bend on, Pitch bend range = 2 Semitones, Volume trigger = 0, Rod antenna cc = None, Loop antenna cc = 7-Volume. -TWEAKABLE PARAMETERS (in application.cpp): - -"#define VELOCITY_SENS 7" -> How easy it is to reach highest velocity (127). Something betwen 5 and 12. - -"#define PLAYER_ACCURACY 0.2" -> Pitch accuracy of player. Tolerance on note center for changing notes when playin legato. 0 (very accurate players) and 0.5 (may generate note toggling). - MUTE BUTTON: @@ -117,6 +111,15 @@ When legato mode is activated, if you trigger a note (with volume loop) and go i Legato mode is used as a workaround for a limitation of midi (max 24 semitones pitch bend). Maybe some synth can perform pitch bend on more that 2 octaves but none of mine does... +### Tweakable parameters (in application.cpp): +Changing this to your taste may require some test and trial. + +"#define VELOCITY_SENS 7" -> How easy it is to reach highest velocity (127). Something betwen 5 and 12. + +"#define PLAYER_ACCURACY 0.2" -> Pitch accuracy of player. Tolerance on note center for changing notes when playin legato. 0 (very accurate players) and 0.5 (may generate note toggling). + + + ### LICENSE Original project written by Urs Gaudenz, GaudiLabs, 2016 GNU license. This Project inherits this 2016 GNU License. From 1c903f531a436faccb6798bcda4734258cc3128f Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 16 Nov 2017 22:53:44 +0100 Subject: [PATCH 22/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d77484..fe4b8b5 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Changing this to your taste may require some test and trial. "#define VELOCITY_SENS 7" -> How easy it is to reach highest velocity (127). Something betwen 5 and 12. -"#define PLAYER_ACCURACY 0.2" -> Pitch accuracy of player. Tolerance on note center for changing notes when playin legato. 0 (very accurate players) and 0.5 (may generate note toggling). +"#define PLAYER_ACCURACY 0.2" -> Pitch accuracy of player. Tolerance on note center for changing notes when playin legato. From 0 (very accurate players) to 0.5 (may generate note toggling). From dfe91486d70670e3cbb65e02002e86f2b0b96f80 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 16 Nov 2017 22:55:00 +0100 Subject: [PATCH 23/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe4b8b5..46ef25e 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Changing this to your taste may require some test and trial. "#define VELOCITY_SENS 7" -> How easy it is to reach highest velocity (127). Something betwen 5 and 12. -"#define PLAYER_ACCURACY 0.2" -> Pitch accuracy of player. Tolerance on note center for changing notes when playin legato. From 0 (very accurate players) to 0.5 (may generate note toggling). +"#define PLAYER_ACCURACY 0.2" -> Pitch accuracy of player. Tolerance on note center for changing notes when playing legato. From 0 (very accurate players) to 0.5 (may generate note toggling). From 582cafda6016867a19c82a9b0dd22bf4c255df9d Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Mon, 20 Nov 2017 19:53:03 +0100 Subject: [PATCH 24/38] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 46ef25e..2a829f9 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ This device runs normal calibration of antennas after pushing button for 3 secon SETTINGS: - "Register" Pot becomes "Selected Parameter" pot and have 8 positions. - "Timbre" pot become "Parameter's Value" and have variable number a position depending on selected parameter: + "Register" pot becomes "Selected Parameter" pot and have 8 positions. + "Timbre" pot becomes "Parameter's Value" and have a variable number of positions depending on selected parameter: 1. Register: 4 positions as in original Open Theremin V3 2. Timbre: 8 positions as in original Open Theremin V3 From 2b89a326c5b882e25195198e947bd788f72076fc Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Tue, 21 Nov 2017 00:02:17 +0100 Subject: [PATCH 25/38] Update README.md --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 2a829f9..c6ec224 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ http://www.gaudi.ch/OpenTheremin/ //Serial.begin(31250); // Baudrate for real midi. Use din connection https://www.arduino.cc/en/Tutorial/Midi or HIDUINO https://github.com/ddiakopoulos/hiduino - I tested "Hiduino" and "midi to serial" modes, both are OK. + I tested "Hiduino" and "MIDI to serial" modes, both are OK. 4. Selecting the correct usb port on Tools -> Serial Port 5. Select the correct arduino board from Tools -> Board @@ -37,22 +37,22 @@ Serial communication implemented for program monitoring purpose was removed (Par If you need to monitor calibration for antenna problem fixing, please use original master branch from https://github.com/GaudiLabs/OpenTheremin_V3. -Serial port is used to send midi messages now. +Serial port is used to send MIDI messages now. ### How does it work ? PITCH ANTENNA (ROD): -It uses first note detected as hand moves away from from volume loop antenna to generate a NOTEON. -Then it can use PITCHBEND (if activated) to reach exact pitch as long as pitch bend range will do. -Beyond it can generate a new NOTEON followed by a NOTEOFF for the previous note if legato mode activated. +It uses first note detected as hand moves away from from volume loop antenna to generate a NOTE ON. +Then it can use PITCH BEND messages (if activated) to reach exact pitch as long as pitch bend range will do. +Beyond it can generate a new NOTE ON followed by a NOTE OFF for the previous note if legato mode activated. Pitch bend range can be configured (1, 2, 7, 12 or 24 semitones) to align with synth's maximum capabilities. -It can also generate a midi continuous controler change if one is selected. +It can also generate a MIDI Continuous Controler (MIDI CC) changes if one is selected. VOLUME ANTENNA (LOOP): -It generates selected midi continuous controler change, starting NOTEON and ending NOTE OFF (when playing staccato). +It generates selected MIDI Continuous Controler (MIDI CC) changes, starting NOTE ON and ending NOTE OFF (when playing staccato). The volume trigger can be configured so as we have some volume at note attack on percussive sounds. The volume trigger setting is also used to set sensitivity for velocity (how fast volume loop hand is moving when note is triggered). @@ -76,22 +76,22 @@ This device runs normal calibration of antennas after pushing button for 3 secon For classical glissando and in order to have same note on audio and MIDI, use exactly same pitch bend range on your synth. Maximum setting possible is recomended. 6. Volume trigger / Velocity sensitivity (how fast moves the volume loop's hand): 128 positions (0 to 127) - 7. Rod antenna cc: 5 positions + 7. Rod antenna MIDI CC: 5 positions (None, 8-Balance, 10-Pan, 16-Ribbon controler, 74-cutoff) - 8. Loop antenna cc: 8 positions + 8. Loop antenna MIDI CC: 8 positions (1-Modulation, 7-Volume, 11-Expression, 71-Resonnance, 74-Cutoff, 91-Reverb, 93-Chorus, 95-Phaser) Select a Parameter and move "Parameter's Value" to change corresponding setting. -Manipulation of "Rod antenna cc" and "Loop antenna cc" is not error proof. MIDI newbies should be advised to change their value in MUTE mode. +Manipulation of "Rod antenna MIDI CC" and "Loop antenna MIDI CC" is not error proof. MIDI newbies should be advised to change their value in MUTE mode. -Default configuration is: Register = Lowest Register, Timbre = 1st Waveform, Channel = MIDI Channel 1, Rod antenna mode = Legato on/Pitch Bend on, Pitch bend range = 2 Semitones, Volume trigger = 0, Rod antenna cc = None, Loop antenna cc = 7-Volume. +Default configuration is: Register = Lowest Register, Timbre = 1st Waveform, Channel = MIDI Channel 1, Rod antenna mode = Legato on/Pitch Bend on, Pitch bend range = 2 Semitones, Volume trigger = 0, Rod antenna MIDI CC = None, Loop antenna MIDI CC = 7-Volume. MUTE BUTTON: -Sends ALL NOTE OFF on selected channel and stay in mute until it's pushed again. +Sends ALL NOTE OFF on selected channel and stay in mute until it is pushed again. AUDIO: @@ -101,14 +101,14 @@ Audio processing from antennas to output jack, including volume and pitch pots, ### What can I do to get a theremin like glissando? Activate picth bend and set pitch bend range of the theremin with a high value (12 semitones or 24 semitones). -Set pitch bend range of the synth with the same value +Set pitch bend range of the synth with the same value. ### If I do not trigger with the volume hand it also seems to trigger a new tone with the pitch antenna. Guess this is how MIDI works. When legato mode is activated, if you trigger a note (with volume loop) and go in one direction (with pitch antenna) a new note will be triggered at the limit of pitch bend range. -Legato mode is used as a workaround for a limitation of midi (max 24 semitones pitch bend). Maybe some synth can perform pitch bend on more that 2 octaves but none of mine does... +Legato mode is used as a workaround for a limitation of MIDI (max 24 semitones pitch bend). Maybe some synth can perform pitch bend on more that 2 octaves but none of mine does... ### Tweakable parameters (in application.cpp): From 63cf31c634a73d0a4317b29a827246929badb609 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Tue, 21 Nov 2017 00:12:38 +0100 Subject: [PATCH 26/38] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c6ec224..0deadc1 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,8 @@ Select a Parameter and move "Parameter's Value" to change corresponding setting. Manipulation of "Rod antenna MIDI CC" and "Loop antenna MIDI CC" is not error proof. MIDI newbies should be advised to change their value in MUTE mode. +Volume trigger = 127 (Maximum) won't generate any NOTE ON. It can be used to generate MIDI CC only. + Default configuration is: Register = Lowest Register, Timbre = 1st Waveform, Channel = MIDI Channel 1, Rod antenna mode = Legato on/Pitch Bend on, Pitch bend range = 2 Semitones, Volume trigger = 0, Rod antenna MIDI CC = None, Loop antenna MIDI CC = 7-Volume. From a1fb12a820bc9a30b23a528561301e62b04eb84c Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Tue, 21 Nov 2017 11:23:11 +0100 Subject: [PATCH 27/38] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0deadc1..6730f3d 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,11 @@ Then it can use PITCH BEND messages (if activated) to reach exact pitch as long Beyond it can generate a new NOTE ON followed by a NOTE OFF for the previous note if legato mode activated. Pitch bend range can be configured (1, 2, 7, 12 or 24 semitones) to align with synth's maximum capabilities. -It can also generate a MIDI Continuous Controler (MIDI CC) changes if one is selected. +It can also generate a MIDI Continuous Controler changes (MIDI CC) if one is selected. VOLUME ANTENNA (LOOP): -It generates selected MIDI Continuous Controler (MIDI CC) changes, starting NOTE ON and ending NOTE OFF (when playing staccato). +It generates selected MIDI Continuous Controler changes (MIDI CC), starting NOTE ON and ending NOTE OFF (when playing staccato). The volume trigger can be configured so as we have some volume at note attack on percussive sounds. The volume trigger setting is also used to set sensitivity for velocity (how fast volume loop hand is moving when note is triggered). From 25c0f9cf17d73a5f40c5916adc662b9db24ce2de Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Tue, 21 Nov 2017 13:32:02 +0100 Subject: [PATCH 28/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6730f3d..1d55848 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Then it can use PITCH BEND messages (if activated) to reach exact pitch as long Beyond it can generate a new NOTE ON followed by a NOTE OFF for the previous note if legato mode activated. Pitch bend range can be configured (1, 2, 7, 12 or 24 semitones) to align with synth's maximum capabilities. -It can also generate a MIDI Continuous Controler changes (MIDI CC) if one is selected. +It can also generate selected MIDI Continuous Controler changes (MIDI CC). VOLUME ANTENNA (LOOP): From d6bee6c7f66d22b258ffa4698f0efe7ff12638ab Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 30 Nov 2017 13:59:16 +0100 Subject: [PATCH 29/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d55848..a364ccd 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Serial port is used to send MIDI messages now. PITCH ANTENNA (ROD): -It uses first note detected as hand moves away from from volume loop antenna to generate a NOTE ON. +It uses first note detected as hand moves away from volume loop antenna to generate a NOTE ON. Then it can use PITCH BEND messages (if activated) to reach exact pitch as long as pitch bend range will do. Beyond it can generate a new NOTE ON followed by a NOTE OFF for the previous note if legato mode activated. Pitch bend range can be configured (1, 2, 7, 12 or 24 semitones) to align with synth's maximum capabilities. From d5f0109e6e6c5fb123668e95fbcd10e9779b7025 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Sat, 2 Dec 2017 14:15:52 +0100 Subject: [PATCH 30/38] Add files via upload --- Open_Theremin_V3/application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index f3a4eb3..8ca0a15 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -56,7 +56,7 @@ static uint8_t loop_midi_cc = 7; static uint8_t rod_midi_cc = 255; // tweakable paramameters -#define VELOCITY_SENS 7 // test and trial should help +#define VELOCITY_SENS 9 // test and trial should help #define PLAYER_ACCURACY 0.2 // between 0 (very accurate players) and 0.5 (not accurate at all) static uint16_t data_pot_value = 0; From 4e106899a3c492c05a9fd1d9b0bf1e693c43f98b Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Sat, 2 Dec 2017 14:19:03 +0100 Subject: [PATCH 31/38] Add files via upload --- Open_Theremin_V3/application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index 8ca0a15..bed0f5c 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -56,7 +56,7 @@ static uint8_t loop_midi_cc = 7; static uint8_t rod_midi_cc = 255; // tweakable paramameters -#define VELOCITY_SENS 9 // test and trial should help +#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; From af8bf368f9260c8cfe3fda64be1c04a54454aec4 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Sat, 2 Dec 2017 14:21:37 +0100 Subject: [PATCH 32/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a364ccd..461da1c 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Legato mode is used as a workaround for a limitation of MIDI (max 24 semitones p ### Tweakable parameters (in application.cpp): Changing this to your taste may require some test and trial. -"#define VELOCITY_SENS 7" -> How easy it is to reach highest velocity (127). Something betwen 5 and 12. +"#define VELOCITY_SENS 9" -> How easy it is to reach highest velocity (127). Something betwen 5 and 12. "#define PLAYER_ACCURACY 0.2" -> Pitch accuracy of player. Tolerance on note center for changing notes when playing legato. From 0 (very accurate players) to 0.5 (may generate note toggling). From d435d6210e362ab631f2f74d8b925fb3c039165f Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Sat, 2 Dec 2017 14:28:11 +0100 Subject: [PATCH 33/38] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 461da1c..35a8df1 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ Serial port is used to send MIDI messages now. PITCH ANTENNA (ROD): It uses first note detected as hand moves away from volume loop antenna to generate a NOTE ON. -Then it can use PITCH BEND messages (if activated) to reach exact pitch as long as pitch bend range will do. -Beyond it can generate a new NOTE ON followed by a NOTE OFF for the previous note if legato mode activated. +Then, it can use PITCH BEND messages (if activated) to reach exact pitch as long as pitch bend range will do. +Beyond, it can generate a new NOTE ON followed by a NOTE OFF for the previous note if legato mode activated. Pitch bend range can be configured (1, 2, 7, 12 or 24 semitones) to align with synth's maximum capabilities. It can also generate selected MIDI Continuous Controler changes (MIDI CC). @@ -97,7 +97,7 @@ Sends ALL NOTE OFF on selected channel and stay in mute until it is pushed again AUDIO: -Audio processing from antennas to output jack, including volume and pitch pots, LEDs and button functions, is exactly the same as in open theremin V3. +Audio processing from antennas to output jack, including volume and pitch pots, LEDs and button functions, is exactly the same as in open theremin V3. You can play the Audio and the MIDI side by side. ### What can I do to get a theremin like glissando? From 33b5169db58e856baa7e28fab5486afa10307303 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Sun, 3 Dec 2017 18:21:22 +0100 Subject: [PATCH 34/38] Add files via upload --- MIDI Open Theremin V3 HMI.bmp | Bin 0 -> 601446 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 MIDI Open Theremin V3 HMI.bmp diff --git a/MIDI Open Theremin V3 HMI.bmp b/MIDI Open Theremin V3 HMI.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c417f5a592d468254d717bd9c1a5e245266d2ce8 GIT binary patch literal 601446 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 Date: Sun, 3 Dec 2017 18:39:08 +0100 Subject: [PATCH 35/38] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 35a8df1..ee39ee1 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ This device runs normal calibration of antennas after pushing button for 3 secon Select a Parameter and move "Parameter's Value" to change corresponding setting. +The picture at https://github.com/MrDham/OpenTheremin_V3_with_MIDI/blob/V2-Project/MIDI%20Open%20Theremin%20V3%20HMI.bmp gives an example of possible HMI: on "Value" pot, red lines have 4 positions, grey lines have 5 positions and yellow lines have 8 positions. On "Parameter" pot you see coloured lines indicating which colour to follow for the "Value" pot. + Manipulation of "Rod antenna MIDI CC" and "Loop antenna MIDI CC" is not error proof. MIDI newbies should be advised to change their value in MUTE mode. Volume trigger = 127 (Maximum) won't generate any NOTE ON. It can be used to generate MIDI CC only. From 0146247e2df19887108bbf1aadcf6873218e310d Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 7 Dec 2017 20:17:41 +0100 Subject: [PATCH 36/38] Better centering of pot axes an graduations. --- MIDI Open Theremin V3 HMI.bmp | Bin 601446 -> 601446 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/MIDI Open Theremin V3 HMI.bmp b/MIDI Open Theremin V3 HMI.bmp index c417f5a592d468254d717bd9c1a5e245266d2ce8..733d3acc2affbac2cddc7ce80128767a160f5ee8 100644 GIT binary patch delta 28378 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 delta 30028 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 From 0c614d1974fe5add006e805184aadf4e38723be3 Mon Sep 17 00:00:00 2001 From: MrDham <32593747+MrDham@users.noreply.github.com> Date: Thu, 7 Dec 2017 20:23:15 +0100 Subject: [PATCH 37/38] Add files via upload --- MIDI Open Theremin V3 HMI.bmp | Bin 601446 -> 601446 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/MIDI Open Theremin V3 HMI.bmp b/MIDI Open Theremin V3 HMI.bmp index 733d3acc2affbac2cddc7ce80128767a160f5ee8..a00eb12178603a432498b33f0f4ffa3df7f530b8 100644 GIT binary patch delta 1841 zcmaJ?aZDR!6yM#oZzDm+hKY3@WOcPhfymKGhEsZ&Oi{8L{6h%|@&}4(AOkU`E{-sg z5}`xr*8*SNKMET+M-{@D=UW4-{0TU(^Fep%fBC05-IIf ziFb1c%*KMdB)oFlS)q8zF_}yn?$*MC7RL>S?EV3Ed0~?w8GR1 zAeW(`A-?0TV>G3`XtfG;OY7M6DI}3rS0bU}(-R)ae9vF8l77K^Nym1OLOaqgY zQ@XGOB5H#&JUq-jt&Vo4eZY?$1#nr=d?ONxaL@1C7R&&$guAxt5gL_fG|KBH`PyXW=X4*?0s(=k(TVlWsdnK_i-g!)C-=dH}x%@+3lGlc?EvW>0iI^79 zl}N;b>sV9*M`RRa;#G~A042%eSLG1D zk%R@N72@MB!+sHM3a=m3nTL>=|LkOHmrygywA)q??07xrBE4a_C=Ka!N}V-Eg7jR) zzu(e1bt%2Qy&Jyp`J_AG9vmFZ4bp_3 zEOz(iia|#MQ#1N>xh;gvn3$O0btN(la=R(j8lI2Pu0rZI-g+FCHU(2Gd~Mhmj%i$r z>Rj7I6PS#=q?2t&+fq2s{Hl1FHf<5$Gbg~$zw%Mlzhv+O3Esl*a9Ap}VQovKzSt|_ z<%FgAX%UIex0XCkpL(f6Hl%GSru*iMD9@P5X&ot&Ik0CHzu(V4uaui%baa$#DmV{L z)413Io+B`;fpMpv{hZ_0(8Cc#Fvth)jKOf+`XLMYHIMh2jI+*$szkHsK&iri* zn;gR7FpZ+le>N7Rqxb6>9eof;avim2v<-xdczh z*!`U)=fwibl?FSJNHEop*)){N1iFZcG*nYUPvVX%po&+Qa5)13WxMs4Wf*v95nd4A V(_H&Pf{&{f#cwNCEpFS_@*mWh1*HH0 delta 932 zcmZuwUr3Wt6leGDJ0>j@MOaQolc6Au#KrLIq3t2FV2DVN7D<1MsG&qb7)bOm38tiO z4_Ctw#!PV~8rn%=5$qubg1*>;ffRT9r;weyTq*pOvBl+!LT zM(odN9yz-TZ475-mw`?X<-rYx(^#5ESZ#EJJ*pRA?*TX|RDV9qn5i*JNqkle?aWVN zq8L0<9g7Q_3iLt&i^@t#v~##|kj;|4)}pBHP){qMT2*>r5wEzQ-a?FVbulC1|Ae0w z?5cn}Y&5Q%4sb>FA|#6+LJj%Jh(a%tTQM_6@zr6f18rVXsK60Oa_E-o!~jtS5uDw{yBRiuus8l zp=xXWe)Tp`0IQ8gnneE3qL(zks?q2lR`ixYyw{v@&5HV`=b7{E~rY#wlFQ cfM4SAB?vN3tH2WrJg8aXFAr;$_8q Date: Thu, 7 Dec 2017 21:03:38 +0100 Subject: [PATCH 38/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee39ee1..03c9773 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Open Theremin V3 with MIDI interface control software for Arduino UNO +## Open Theremin V3 with MIDI interface control software V2.0 for Arduino UNO Based on Arduino UNO Software for the Open.Theremin version 3.0 Copyright (C) 2010-2016 by Urs Gaudenz https://github.com/GaudiLabs/OpenTheremin_V3