#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 midi_bend_range = 0; static uint8_t midi_volume_trigger = 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); 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<= 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); if (_state == PLAYING && HW_BUTTON_PRESSED) { _state = CALIBRATING; _midistate = MIDI_STOP; resetTimer(); } if (_state == CALIBRATING && HW_BUTTON_RELEASED) { if (timerExpired(1500)) { _mode = nextMode(); if (_mode==NORMAL) { HW_LED1_ON;HW_LED2_OFF; _midistate = MIDI_SILENT; } else { HW_LED1_OFF;HW_LED2_ON; }; // playModeSettingSound(); } _state = PLAYING; }; if (_state == CALIBRATING && timerExpired(15000)) { HW_LED1_OFF; HW_LED2_ON; playStartupSound(); if (registerPotValue < 512) // if register pot turned CCW { // calibrate heterodyne parameters calibrate_pitch(); calibrate_volume(); initialiseTimer(); initialiseInterrupts(); playCalibratingCountdownSound(); calibrate(); } else // if register turned CW { // calibrate midi parameters midi_calibrate (); }; _mode=NORMAL; HW_LED1_ON;HW_LED2_OFF; while (HW_BUTTON_PRESSED) ; // NOP _state = PLAYING; _midistate = MIDI_SILENT; }; #if CV_ENABLED OCR0A = pitch & 0xff; #endif if (pitchValueAvailable) { // If capture event pitch_v=pitch; // Averaging pitch values pitch_v=pitch_l+((pitch_v-pitch_l)>>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() { 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 _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.5; _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; } } // 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 () { uint16_t pot_channel; uint16_t pot_bend_range; uint16_t pot_volume_trigger; uint16_t bend_range_scale; // 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, 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 >> 8; if (bend_range_scale == 0) { midi_bend_range = 1; } else if (bend_range_scale == 1) { midi_bend_range = 7; } else if (bend_range_scale == 2) { midi_bend_range = 12; } else { midi_bend_range = 24; } 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); }