diff --git a/MIDI Open Theremin V3 HMI.bmp b/MIDI Open Theremin V3 HMI.bmp index a00eb12..6fe07fd 100644 Binary files a/MIDI Open Theremin V3 HMI.bmp and b/MIDI Open Theremin V3 HMI.bmp differ diff --git a/Open_Theremin_V3/OTPinDefs.h b/Open_Theremin_V3/OTPinDefs.h deleted file mode 100644 index cdd9996..0000000 --- a/Open_Theremin_V3/OTPinDefs.h +++ /dev/null @@ -1,56 +0,0 @@ -/** - * \file - * Pin definitions - */ - -#ifndef WavePinDefs_h -#define WavePinDefs_h - -//------------------------------------------------------------------------------ -// DAC pin definitions - -// LDAC may be connected to ground to save a pin -/** Set USE_MCP_DAC_LDAC to 0 if LDAC is grounded. */ -#define USE_MCP_DAC_LDAC 1 - -// use arduino pins 2, 3, 4, 5 for DAC - -// pin 2 is DAC chip select - -/** Data direction register for DAC chip select. */ -#define MCP_DAC_CS_DDR DDRB -#define MCP_DAC2_CS_DDR DDRB -/** Port register for DAC chip select. */ -#define MCP_DAC_CS_PORT PORTB -/** Port bit number for DAC chip select. */ -#define MCP_DAC_CS_BIT 2 -#define MCP_DAC2_CS_BIT 1 - -// pin 3 is DAC serial clock -/** Data direction register for DAC clock. */ -#define MCP_DAC_SCK_DDR DDRB -/** Port register for DAC clock. */ -#define MCP_DAC_SCK_PORT PORTB -/** Port bit number for DAC clock. */ -#define MCP_DAC_SCK_BIT 5 - -// pin 4 is DAC serial data in - -/** Data direction register for DAC serial in. */ -#define MCP_DAC_SDI_DDR DDRB -/** Port register for DAC clock. */ -#define MCP_DAC_SDI_PORT PORTB -/** Port bit number for DAC clock. */ -#define MCP_DAC_SDI_BIT 3 - -// pin 5 is LDAC if used -#if USE_MCP_DAC_LDAC -/** Data direction register for Latch DAC Input. */ -#define MCP_DAC_LDAC_DDR DDRD -/** Port register for Latch DAC Input. */ -#define MCP_DAC_LDAC_PORT PORTD -/** Port bit number for Latch DAC Input. */ -#define MCP_DAC_LDAC_BIT 7 -#endif // USE_MCP_DAC_LDAC - -#endif // WavePinDefs_h diff --git a/Open_Theremin_V3/Open_Theremin_V3.ino b/Open_Theremin_V3/Open_Theremin_V3.ino index 4022591..b70f0b9 100644 --- a/Open_Theremin_V3/Open_Theremin_V3.ino +++ b/Open_Theremin_V3/Open_Theremin_V3.ino @@ -1,6 +1,8 @@ /* * Open Theremin V3 with MIDI interface control software for Arduino UNO * Based on Open Theremin V3 version 3.0 Copyright (C) 2010-2016 by Urs Gaudenz + * + * Also integrate changes from Open Theremin V3 Version 3.1 Copyright (C) 2010-2020 by Urs Gaudenz * * * Open Theremin V3 with MIDI interface control software is free software: @@ -19,9 +21,10 @@ * the Open Theremin V3 with MIDI interface control software. * If not, see . * - * Urs Gaudenz also credits for their important contributions to Open Theremin V3: + * Also credited for their important contributions to Open Theremin V3: * David Harvey * Michael Margolis + * "Theremingenieur" Thierry Frenkel */ /* Midi added by Vincent Dhamelincourt - September 2017. diff --git a/Open_Theremin_V3/SPImcpDAC.h b/Open_Theremin_V3/SPImcpDAC.h new file mode 100644 index 0000000..e87f9e8 --- /dev/null +++ b/Open_Theremin_V3/SPImcpDAC.h @@ -0,0 +1,104 @@ +/* Control the mcp 4921/4922 DACs with hardware SPI of the Arduino UNO + * ...without all the overhead of the Arduino SPI lib... + * Just the needed functions in a runtime optimized way by "Theremingenieur" Thierry Frenkel + * This file is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +#ifndef SPImcpDac_h +#define SPImcpDac_h + +#include + +// Data direction & Port register & Bit number for DAC Latch: +#define MCP_DAC_LDAC_DDR DDRD +#define MCP_DAC_LDAC_PORT PORTD +#define MCP_DAC_LDAC_BIT 7 +// Data direction & Port register & Bit number for DAC CS +#define MCP_DAC_CS_DDR DDRB +#define MCP_DAC_CS_PORT PORTB +#define MCP_DAC_CS_BIT 2 +// Data direction & Port register & Bit number for DAC2 CS +#define MCP_DAC2_CS_DDR DDRB +#define MCP_DAC2_CS_PORT PORTB +#define MCP_DAC2_CS_BIT 1 +// Data direction & Port registers & Bit numbers for Hardware SPI +#define HW_SPI_DDR DDRB +#define HW_SPI_SCK_BIT 5 +#define HW_SPI_MISO_BIT 4 // unused in this configuration +#define HW_SPI_MOSI_BIT 3 + +static inline void SPImcpDACinit() +{ + // initialize the latch pin: + MCP_DAC_LDAC_DDR |= _BV(MCP_DAC_LDAC_BIT); + MCP_DAC_LDAC_PORT |= _BV(MCP_DAC_LDAC_BIT); + // initialize the CS pins: + MCP_DAC_CS_DDR |= _BV(MCP_DAC_CS_BIT); + MCP_DAC_CS_PORT |= _BV(MCP_DAC_CS_BIT); + MCP_DAC2_CS_DDR |= _BV(MCP_DAC2_CS_BIT); + MCP_DAC2_CS_PORT |= _BV(MCP_DAC2_CS_BIT); + // initialize the hardware SPI pins: + HW_SPI_DDR |= _BV(HW_SPI_SCK_BIT); + HW_SPI_DDR |= _BV(HW_SPI_MOSI_BIT); + // initialize the hardware SPI registers + SPCR = _BV(SPE) | _BV(MSTR); // no interrupt, SPI enable, MSB first, SPI master, SPI mode 0, clock = f_osc/4 (maximum) + SPSR = _BV(SPI2X); // double the SPI clock, ideally we get 8 MHz, so that a 16bit word goes out in 3.5us (5.6us when called from an interrupt) including CS asserting/deasserting +} + +static inline void SPImcpDACtransmit(uint16_t data) +{ + // Send highbyte and wait for complete + SPDR = highByte(data); + asm("nop"); + while (!(SPSR & _BV(SPIF))) + ; + // Send lowbyte and wait for complete + SPDR = lowByte(data); + asm("nop"); + while (!(SPSR & _BV(SPIF))) + ; +} + +static inline void SPImcpDAClatch() +{ + MCP_DAC_LDAC_PORT &= ~_BV(MCP_DAC_LDAC_BIT); + MCP_DAC_LDAC_PORT |= _BV(MCP_DAC_LDAC_BIT); +} + +static inline void SPImcpDACsend(uint16_t data) +{ + MCP_DAC_CS_PORT &= ~_BV(MCP_DAC_CS_BIT); + // Sanitize input data and add DAC config MSBs + data &= 0x0FFF; + data |= 0x7000; + SPImcpDACtransmit(data); + MCP_DAC_CS_PORT |= _BV(MCP_DAC_CS_BIT); + // Do not latch immpediately, let's do it at the very beginning of the next interrupt to get consistent timing +} + +static inline void SPImcpDAC2Asend(uint16_t data) +{ + MCP_DAC2_CS_PORT &= ~_BV(MCP_DAC2_CS_BIT); + // Sanitize input data and add DAC config MSBs + data &= 0x0FFF; + data |= 0x7000; + SPImcpDACtransmit(data); + MCP_DAC2_CS_PORT |= _BV(MCP_DAC2_CS_BIT); + SPImcpDAClatch(); +} + +static inline void SPImcpDAC2Bsend(uint16_t data) +{ + MCP_DAC2_CS_PORT &= ~_BV(MCP_DAC2_CS_BIT); + // Sanitize input data and add DAC config MSBs + data &= 0x0FFF; + data |= 0xF000; + SPImcpDACtransmit(data); + MCP_DAC2_CS_PORT |= _BV(MCP_DAC2_CS_BIT); + SPImcpDAClatch(); +} + +#endif diff --git a/Open_Theremin_V3/application.cpp b/Open_Theremin_V3/application.cpp index adca14b..e692342 100644 --- a/Open_Theremin_V3/application.cpp +++ b/Open_Theremin_V3/application.cpp @@ -1,936 +1,958 @@ -#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 uint8_t new_midi_note =0; -static uint8_t old_midi_note =0; - -static uint8_t new_midi_loop_cc_val =0; -static uint8_t old_midi_loop_cc_val =0; - -static uint8_t midi_velocity = 0; - -static uint8_t new_midi_rod_cc_val =0; -static uint8_t old_midi_rod_cc_val =0; - -static uint16_t new_midi_bend =0; -static uint16_t old_midi_bend = 0; -static uint8_t midi_bend_low; -static uint8_t midi_bend_high; - -static double double_log_freq = 0; -static double midi_key_follow = 0.5; - -// Configuration parameters -static uint8_t registerValue = 4; - // wavetable selector is defined and initialized in ihandlers.cpp -static uint8_t midi_channel = 0; -static uint8_t old_midi_channel = 0; -static uint8_t midi_bend_range = 2; -static uint8_t midi_volume_trigger = 0; -static uint8_t flag_legato_on = 1; -static uint8_t flag_pitch_bend_on = 1; -static uint8_t loop_midi_cc = 7; -static uint8_t rod_midi_cc = 255; - -// tweakable paramameters -#define VELOCITY_SENS 9 // How easy it is to reach highest velocity (127). Something betwen 5 and 12. -#define PLAYER_ACCURACY 0.2 // between 0 (very accurate players) and 0.5 (not accurate at all) - -static uint16_t data_pot_value = 0; -static uint16_t old_data_pot_value = 0; - -Application::Application() - : _state(PLAYING), - _mode(NORMAL) { -}; - -void Application::setup() { - - HW_LED1_ON;HW_LED2_OFF; - - pinMode(Application::BUTTON_PIN, INPUT_PULLUP); - pinMode(Application::LED_PIN_1, OUTPUT); - pinMode(Application::LED_PIN_2, OUTPUT); - - digitalWrite(Application::LED_PIN_1, HIGH); // turn the LED off by making the voltage LOW - - 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 - -delay(100); - - -pitchXn0 = pitchXn1; -pitchXn1 = pitchXn2; - -HW_LED2_TOGGLE; - -} -delay(100); - -EEPROM.put(0,pitchXn0); - -} - -void Application::calibrate_volume() -{ - - -static int16_t volumeXn0 = 0; -static int16_t volumeXn1 = 0; -static int16_t volumeXn2 = 0; -static float q0 = 0; -static long volumefn0 = 0; -static long volumefn1 = 0; -static long volumefn = 0; - - - InitialiseVolumeMeasurement(); - interrupts(); - 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 - -delay_NOP(44316);//44316=100ms - -volumeXn0 = volumeXn1; -volumeXn1 = volumeXn2; -HW_LED2_TOGGLE; - -} - -EEPROM.put(2,volumeXn0); - - -} - -void Application::hzToAddVal(float hz) { - setWavetableSampleAdvance((uint16_t)(hz * HZ_ADDVAL_FACTOR)); -} - -void Application::playNote(float hz, uint16_t milliseconds = 500, uint8_t volume = 255) { - vScaledVolume = volume; - hzToAddVal(hz); - millitimer(milliseconds); - vScaledVolume = 0; -} - -void Application::playStartupSound() { - playNote(MIDDLE_C, 150, 25); - playNote(MIDDLE_C * 2, 150, 25); - playNote(MIDDLE_C * 4, 150, 25); -} - -void Application::playCalibratingCountdownSound() { - playNote(MIDDLE_C * 2, 150, 25); - playNote(MIDDLE_C * 2, 150, 25); -} - -void Application::playModeSettingSound() { - for (int i = 0; i <= _mode; i++) { - playNote(MIDDLE_C * 2, 200, 25); - millitimer(100); - } -} - -void Application::delay_NOP(unsigned long time) { - volatile unsigned long i = 0; - for (i = 0; i < time; i++) { - __asm__ __volatile__ ("nop"); - } -} - - - -void Application::midi_setup() -{ - // Set MIDI baud rate: - Serial.begin(115200); // Baudrate for midi to serial. Use a serial to midi router http://projectgus.github.com/hairless-midiserial/ - //Serial.begin(31250); // Baudrate for real midi. Use din connection https://www.arduino.cc/en/Tutorial/Midi or HIDUINO https://github.com/ddiakopoulos/hiduino - - _midistate = MIDI_SILENT; -} - - -void Application::midi_msg_send(uint8_t channel, uint8_t midi_cmd1, uint8_t midi_cmd2, uint8_t midi_value) -{ - uint8_t mixed_cmd1_channel; - - mixed_cmd1_channel = (midi_cmd1 & 0xF0)| (channel & 0x0F); - - Serial.write(mixed_cmd1_channel); - Serial.write(midi_cmd2); - Serial.write(midi_value); -} - -// midi_application sends note and volume and uses pitch bend to simulate continuous picth. -// Calibrate pitch bend and other parameters accordingly to the receiver synth (see midi_calibrate). -// New notes won't be generated as long as pitch bend will do the job. -// The bigger is synth's pitch bend range the beter is the effect. -// If pitch bend range = 1 no picth bend is generated (portamento will do a better job) -void Application::midi_application () -{ - double delta_loop_cc_val = 0; - double calculated_velocity = 0; - - - // Calculate loop antena cc value for midi - new_midi_loop_cc_val = 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) || (vPointerIncrement > 65518)) - { - // Lowest note - double_log_freq = 0; - } - else if ((vPointerIncrement > 26315) && (vPointerIncrement < 39221)) - { - // Highest note - double_log_freq = 127; - } - else if (vPointerIncrement < 32768) - { - // Positive frequencies - // Find note in the playing range - double_log_freq = (log (vPointerIncrement/17.152) / 0.057762265); // Precise note played in the logaritmic scale - } - else - { - // Negative frequencies - // Find note in the playing range - double_log_freq = (log ((65535-vPointerIncrement+1)/17.152) / 0.057762265); // Precise note played in the logaritmic scale - } - - // Calculate rod antena cc value for midi - new_midi_rod_cc_val = round (double_log_freq); - - // State machine for MIDI - switch (_midistate) - { - case MIDI_SILENT: - // Always refresh midi loop antena cc. - if (new_midi_loop_cc_val != old_midi_loop_cc_val) - { - midi_msg_send(midi_channel, 0xB0, loop_midi_cc, new_midi_loop_cc_val); - old_midi_loop_cc_val = new_midi_loop_cc_val; - } - else - { - // do nothing - } - - // Always refresh midi rod antena cc if applicable. - if ((rod_midi_cc != 255) && (new_midi_rod_cc_val != old_midi_rod_cc_val)) - { - midi_msg_send(midi_channel, 0xB0, rod_midi_cc, new_midi_rod_cc_val); - old_midi_rod_cc_val = new_midi_rod_cc_val; - } - else - { - // do nothing - } - - // If player's hand moves away from volume antenna - if (new_midi_loop_cc_val > midi_volume_trigger) - { - // Set key follow to the minimum in order to use closest note played as the center note - midi_key_follow = 0.5; - - // Calculate note and associated pitch bend - calculate_note_bend (); - - // Send pitch bend to reach precise played note (send 8192 (no pitch bend) in case of midi_bend_range == 1) - midi_msg_send(midi_channel, 0xE0, midi_bend_low, midi_bend_high); - old_midi_bend = new_midi_bend; - - // Calculate velocity - if (midi_timer != 0) - { - calculated_velocity = (64 * (127 - (double)midi_volume_trigger) / 127) + (VELOCITY_SENS * (double)midi_volume_trigger * delta_loop_cc_val / (double)midi_timer); - midi_velocity = min (round (abs (calculated_velocity)), 127); - } - else - { - // should not happen - midi_velocity = 64; - } - - - // Play the note - midi_msg_send(midi_channel, 0x90, new_midi_note, midi_velocity); - old_midi_note = new_midi_note; - - _midistate = MIDI_PLAYING; - } - else - { - // Do nothing - } - break; - - case MIDI_PLAYING: - // Always refresh midi loop antena cc. - if (new_midi_loop_cc_val != old_midi_loop_cc_val) - { - midi_msg_send(midi_channel, 0xB0, loop_midi_cc, new_midi_loop_cc_val); - old_midi_loop_cc_val = new_midi_loop_cc_val; - } - else - { - // do nothing - } - - // Always refresh midi rod antena cc if applicable. - if ((rod_midi_cc != 255) && (new_midi_rod_cc_val != old_midi_rod_cc_val)) - { - midi_msg_send(midi_channel, 0xB0, rod_midi_cc, new_midi_rod_cc_val); - old_midi_rod_cc_val = new_midi_rod_cc_val; - } - else - { - // do nothing - } - - // If player's hand is far from volume antenna - if (new_midi_loop_cc_val > midi_volume_trigger) - { - if ( flag_legato_on == 1) - { - // Set key follow so as next played note will be at limit of pitch bend range - midi_key_follow = (double)(midi_bend_range) - PLAYER_ACCURACY; - } - else - { - // Set key follow to max so as no key follows - midi_key_follow = 127; - } - - // Calculate note and associated pitch bend - calculate_note_bend (); - - // Refresh midi pitch bend value - if (new_midi_bend != old_midi_bend) - { - midi_msg_send(midi_channel, 0xE0, midi_bend_low, midi_bend_high); - old_midi_bend = new_midi_bend; - } - else - { - // do nothing - } - - // Refresh midi note - if (new_midi_note != old_midi_note) - { - // Play new note before muting old one to play legato on monophonic synth - // (pitch pend management tends to break expected effect here) - midi_msg_send(midi_channel, 0x90, new_midi_note, midi_velocity); - midi_msg_send(midi_channel, 0x90, old_midi_note, 0); - old_midi_note = new_midi_note; - } - else - { - // do nothing - } - } - else // Means that player's hand moves to the volume antenna - { - // Send note off - midi_msg_send(midi_channel, 0x90, old_midi_note, 0); - - _midistate = MIDI_SILENT; - } - break; - - case MIDI_STOP: - // Send all note off - midi_msg_send(midi_channel, 0xB0, 0x7B, 0x00); - - _midistate = MIDI_MUTE; - break; - - case MIDI_MUTE: - //do nothing - break; - - } -} - -void Application::calculate_note_bend () -{ - double double_log_bend; - double double_norm_log_bend; - - double_log_bend = double_log_freq - old_midi_note; // How far from last played midi chromatic note we are - - // If too much far from last midi chromatic note played (midi_key_follow depends on pitch bend range) - if ((abs (double_log_bend) >= midi_key_follow) && (midi_key_follow != 127)) - { - new_midi_note = round (double_log_freq); // Select the new midi chromatic note - double_log_bend = double_log_freq - new_midi_note; // calculate bend to reach precise note played - } - else - { - new_midi_note = old_midi_note; // No change - } - - // If pitch bend activated - if (flag_pitch_bend_on == 1) - { - // use it to reach precise note played - double_norm_log_bend = (double_log_bend / midi_bend_range); - if (double_norm_log_bend > 1) - { - double_norm_log_bend = 1; - } - else if (double_norm_log_bend < -1) - { - double_norm_log_bend = -1; - } - new_midi_bend = 8192 + (8191 * double_norm_log_bend); // Calculate midi pitch bend - } - else - { - // Don't use pitch bend - new_midi_bend = 8192; - } - - - // Prepare the 2 bites of picth bend midi message - midi_bend_low = (int8_t) (new_midi_bend & 0x007F); - midi_bend_high = (int8_t) ((new_midi_bend & 0x3F80)>> 7); -} - - - -void Application::init_parameters () -{ - // init data pot value to avoid 1st position to be taken into account - data_pot_value = analogRead(WAVE_SELECT_POT); - old_data_pot_value = data_pot_value; - -} - -void Application::set_parameters () -{ - uint16_t param_pot_value = 0; - - param_pot_value = analogRead(REGISTER_SELECT_POT); - data_pot_value = analogRead(WAVE_SELECT_POT); - - // If data pot moved - if (abs((int32_t)data_pot_value - (int32_t)old_data_pot_value) >= 8) - { - // Modify selected parameter - switch (param_pot_value >> 7) - { - case 0: - // Transpose - registerValue=4-(data_pot_value>>8); - break; - - case 1: - // Waveform - vWavetableSelector=data_pot_value>>7; - break; - - case 2: - // Channel - midi_channel = (uint8_t)((data_pot_value >> 6) & 0x000F); - if (old_midi_channel != midi_channel) - { - // Send all note off to avoid stuck notes - midi_msg_send(old_midi_channel, 0xB0, 0x7B, 0x00); - old_midi_channel = midi_channel; - } - break; - - case 3: - // Rod antenna mode - switch (data_pot_value >> 7) - { - case 0: - case 1: - flag_legato_on = 0; - flag_pitch_bend_on = 0; - break; - case 2: - case 3: - flag_legato_on = 0; - flag_pitch_bend_on = 1; - break; - case 4: - case 5: - flag_legato_on = 1; - flag_pitch_bend_on = 0; - break; - default: - flag_legato_on = 1; - flag_pitch_bend_on = 1; - break; - } - break; - - case 4: - // Pitch bend range - switch (data_pot_value >> 7) - { - case 0: - midi_bend_range = 1; - break; - case 1: - case 2: - midi_bend_range = 2; - break; - case 3: - case 4: - midi_bend_range = 7; - break; - case 5: - case 6: - midi_bend_range = 12; - break; - default: - midi_bend_range = 24; - break; - } - break; - - case 5: - // Volume trigger - midi_volume_trigger = (uint8_t)((data_pot_value >> 3) & 0x007F); - break; - - case 6: - //Rod antenna cc - switch (data_pot_value >> 7) - { - case 0: - rod_midi_cc = 255; // Nothing - break; - case 1: - case 2: - rod_midi_cc = 8; // Balance - break; - case 3: - case 4: - rod_midi_cc = 10; // Pan - break; - case 5: - case 6: - rod_midi_cc = 16; // Ribbon Controler - break; - default: - rod_midi_cc = 74; // Cutoff (exists of both loop and rod) - break; - } - break; - - - default: - // Loop antenna cc - switch (data_pot_value >> 7) - { - case 0: - loop_midi_cc = 1; // Modulation - break; - case 1: - loop_midi_cc = 7; // Volume - break; - case 2: - loop_midi_cc = 11; // Expression - break; - case 3: - loop_midi_cc = 71; // Resonnance - break; - case 4: - loop_midi_cc = 74; // Cutoff (exists of both loop and rod) - break; - case 5: - loop_midi_cc = 91; // Reverb - break; - case 6: - loop_midi_cc = 93; // Chorus - break; - default: - loop_midi_cc = 95; // Phaser - break; - } - break; - } - - // Memorize data pot value to monitor changes - old_data_pot_value = data_pot_value; - } +#include "Arduino.h" + +#include "application.h" + +#include "hw.h" +#include "SPImcpDAC.h" +#include "ihandlers.h" +#include "timer.h" +#include "EEPROM.h" + +const AppMode AppModeValues[] = {MUTE,NORMAL}; +const int16_t CalibrationTolerance = 15; +const int16_t PitchFreqOffset = 700; +const int16_t VolumeFreqOffset = 700; +const int8_t HYST_VAL = 40; + +static int32_t pitchCalibrationBase = 0; +static int32_t pitchCalibrationBaseFreq = 0; +static int32_t pitchCalibrationConstant = 0; +static int32_t pitchSensitivityConstant = 70000; +static int16_t pitchDAC = 0; +static int16_t volumeDAC = 0; +static float qMeasurement = 0; + +static int32_t volCalibrationBase = 0; + +static uint8_t new_midi_note =0; +static uint8_t old_midi_note =0; + +static uint8_t new_midi_loop_cc_val =0; +static uint8_t old_midi_loop_cc_val =0; + +static uint8_t midi_velocity = 0; + +static uint8_t loop_hand_pos = 0; +static uint8_t new_midi_rod_cc_val =0; +static uint8_t old_midi_rod_cc_val =0; + +static uint16_t new_midi_bend =0; +static uint16_t old_midi_bend = 0; +static uint8_t midi_bend_low; +static uint8_t midi_bend_high; + +static double double_log_freq = 0; +static double midi_key_follow = 0.5; + +// Configuration parameters +static uint8_t registerValue = 2; + // wavetable selector is defined and initialized in ihandlers.cpp +static uint8_t midi_channel = 0; +static uint8_t old_midi_channel = 0; +static uint8_t midi_bend_range = 2; +static uint8_t midi_volume_trigger = 0; +static uint8_t flag_legato_on = 1; +static uint8_t flag_pitch_bend_on = 1; +static uint8_t loop_midi_cc = 7; +static uint8_t rod_midi_cc = 255; + +// tweakable paramameters +#define VELOCITY_SENS 9 // How easy it is to reach highest velocity (127). Something betwen 5 and 12. +#define PLAYER_ACCURACY 0.2 // between 0 (very accurate players) and 0.5 (not accurate at all) + +static uint16_t data_pot_value = 0; +static uint16_t old_data_pot_value = 0; + +Application::Application() + : _state(PLAYING), + _mode(NORMAL) { +}; + +void Application::setup() { + + HW_LED1_ON;HW_LED2_OFF; + + pinMode(Application::BUTTON_PIN, INPUT_PULLUP); + pinMode(Application::LED_PIN_1, OUTPUT); + pinMode(Application::LED_PIN_2, OUTPUT); + + digitalWrite(Application::LED_PIN_1, HIGH); // turn the LED off by making the voltage LOW + + SPImcpDACinit(); + +EEPROM.get(0,pitchDAC); +EEPROM.get(2,volumeDAC); + +SPImcpDAC2Asend(pitchDAC); +SPImcpDAC2Bsend(volumeDAC); + + +initialiseTimer(); +initialiseInterrupts(); + + + EEPROM.get(4,pitchCalibrationBase); + EEPROM.get(8,volCalibrationBase); + + init_parameters(); + midi_setup(); + +} + +void Application::initialiseTimer() { + ihInitialiseTimer(); +} + +void Application::initialiseInterrupts() { + ihInitialiseInterrupts(); +} + +void Application::InitialisePitchMeasurement() { + ihInitialisePitchMeasurement(); +} + +void Application::InitialiseVolumeMeasurement() { + ihInitialiseVolumeMeasurement(); +} + +unsigned long Application::GetQMeasurement() +{ + int qn=0; + + TCCR1B = (1<>2); + pitch_l=pitch_v; + + +//HW_LED2_ON; + + + // set wave frequency for each mode + switch (_mode) { + case MUTE : /* NOTHING! */; break; + case NORMAL : setWavetableSampleAdvance(((pitchCalibrationBase-pitch_v)+2048-(pitchPotValue<<2))>>registerValue); break; + }; + + // HW_LED2_OFF; + + pitchValueAvailable = false; + } + + if (volumeValueAvailable) { + vol = max(vol, 5000); + + vol_v=vol; // Averaging volume values + vol_v=vol_l+((vol_v-vol_l)>>2); + vol_l=vol_v; + + switch (_mode) { + case MUTE: vol_v = 0; break; + case NORMAL: vol_v = MAX_VOLUME-(volCalibrationBase-vol_v)/2+(volumePotValue<<2)-1024; break; + }; + + // Limit and set volume value + vol_v = min(vol_v, 4095); + // vol_v = vol_v - (1 + MAX_VOLUME - (volumePotValue << 2)); + vol_v = vol_v ; + vol_v = max(vol_v, 0); + loop_hand_pos = vol_v >> 4; + + // Give vScaledVolume a pseudo-exponential characteristic: + vScaledVolume = loop_hand_pos * (loop_hand_pos + 2); + + volumeValueAvailable = false; + } + + if (midi_timer > 100) // run midi app every 100 ticks equivalent to approximatevely 3 ms to avoid synth's overload + { + midi_application (); + midi_timer = 0; + } + + goto mloop; // End of main loop +} + +void Application::calibrate() +{ + resetPitchFlag(); + resetTimer(); + savePitchCounter(); + while (!pitchValueAvailable && timerUnexpiredMillis(10)) + ; // NOP + pitchCalibrationBase = pitch; + pitchCalibrationBaseFreq = FREQ_FACTOR/pitchCalibrationBase; + pitchCalibrationConstant = FREQ_FACTOR/pitchSensitivityConstant/2+200; + + resetVolFlag(); + resetTimer(); + saveVolCounter(); + while (!volumeValueAvailable && timerUnexpiredMillis(10)) + ; // NOP + volCalibrationBase = vol; + + + EEPROM.put(4,pitchCalibrationBase); + EEPROM.put(8,volCalibrationBase); + +} + +void Application::calibrate_pitch() +{ + +static int16_t pitchXn0 = 0; +static int16_t pitchXn1 = 0; +static int16_t pitchXn2 = 0; +static float q0 = 0; +static long pitchfn0 = 0; +static long pitchfn1 = 0; +static long pitchfn = 0; + + + + InitialisePitchMeasurement(); + interrupts(); + SPImcpDACinit(); + + qMeasurement = GetQMeasurement(); // Measure Arudino clock frequency + +q0 = (16000000/qMeasurement*500000); //Calculated set frequency based on Arudino clock frequency + +pitchXn0 = 0; +pitchXn1 = 4095; + +pitchfn = q0-PitchFreqOffset; // Add offset calue to set frequency + + + +SPImcpDAC2Bsend(1600); + +SPImcpDAC2Asend(pitchXn0); +delay(100); +pitchfn0 = GetPitchMeasurement(); + +SPImcpDAC2Asend(pitchXn1); +delay(100); +pitchfn1 = GetPitchMeasurement(); + + +while(abs(pitchfn0-pitchfn1)>CalibrationTolerance){ // max allowed pitch frequency offset + +SPImcpDAC2Asend(pitchXn0); +delay(100); +pitchfn0 = GetPitchMeasurement()-pitchfn; + +SPImcpDAC2Asend(pitchXn1); +delay(100); +pitchfn1 = GetPitchMeasurement()-pitchfn; + +pitchXn2=pitchXn1-((pitchXn1-pitchXn0)*pitchfn1)/(pitchfn1-pitchfn0); // new DAC value + +delay(100); + + +pitchXn0 = pitchXn1; +pitchXn1 = pitchXn2; + +HW_LED2_TOGGLE; + +} +delay(100); + +EEPROM.put(0,pitchXn0); + +} + +void Application::calibrate_volume() +{ + + +static int16_t volumeXn0 = 0; +static int16_t volumeXn1 = 0; +static int16_t volumeXn2 = 0; +static float q0 = 0; +static long volumefn0 = 0; +static long volumefn1 = 0; +static long volumefn = 0; + + + InitialiseVolumeMeasurement(); + interrupts(); + SPImcpDACinit(); + + +volumeXn0 = 0; +volumeXn1 = 4095; + +q0 = (16000000/qMeasurement*460765); +volumefn = q0-VolumeFreqOffset; + + + +SPImcpDAC2Bsend(volumeXn0); +delay_NOP(44316);//44316=100ms + +volumefn0 = GetVolumeMeasurement(); + +SPImcpDAC2Bsend(volumeXn1); + +delay_NOP(44316);//44316=100ms +volumefn1 = GetVolumeMeasurement(); + + + +while(abs(volumefn0-volumefn1)>CalibrationTolerance){ + +SPImcpDAC2Bsend(volumeXn0); +delay_NOP(44316);//44316=100ms +volumefn0 = GetVolumeMeasurement()-volumefn; + +SPImcpDAC2Bsend(volumeXn1); +delay_NOP(44316);//44316=100ms +volumefn1 = GetVolumeMeasurement()-volumefn; + +volumeXn2=volumeXn1-((volumeXn1-volumeXn0)*volumefn1)/(volumefn1-volumefn0); // calculate new DAC value + +delay_NOP(44316);//44316=100ms + +volumeXn0 = volumeXn1; +volumeXn1 = volumeXn2; +HW_LED2_TOGGLE; + +} + +EEPROM.put(2,volumeXn0); + + +} + +void Application::hzToAddVal(float hz) { + setWavetableSampleAdvance((uint16_t)(hz * HZ_ADDVAL_FACTOR)); +} + +void Application::playNote(float hz, uint16_t milliseconds = 500, uint8_t volume = 255) { + vScaledVolume = volume * (volume + 2); + hzToAddVal(hz); + millitimer(milliseconds); + vScaledVolume = 0; +} + +void Application::playStartupSound() { + playNote(MIDDLE_C, 150, 25); + playNote(MIDDLE_C * 2, 150, 25); + playNote(MIDDLE_C * 4, 150, 25); +} + +void Application::playCalibratingCountdownSound() { + playNote(MIDDLE_C * 2, 150, 25); + playNote(MIDDLE_C * 2, 150, 25); +} + +void Application::playModeSettingSound() { + for (int i = 0; i <= _mode; i++) { + playNote(MIDDLE_C * 2, 200, 25); + millitimer(100); + } +} + +void Application::delay_NOP(unsigned long time) { + volatile unsigned long i = 0; + for (i = 0; i < time; i++) { + __asm__ __volatile__ ("nop"); + } +} + + + +void Application::midi_setup() +{ + // Set MIDI baud rate: + Serial.begin(115200); // Baudrate for midi to serial. Use a serial to midi router http://projectgus.github.com/hairless-midiserial/ + //Serial.begin(31250); // Baudrate for real midi. Use din connection https://www.arduino.cc/en/Tutorial/Midi or HIDUINO https://github.com/ddiakopoulos/hiduino + + _midistate = MIDI_SILENT; +} + + +void Application::midi_msg_send(uint8_t channel, uint8_t midi_cmd1, uint8_t midi_cmd2, uint8_t midi_value) +{ + uint8_t mixed_cmd1_channel; + + mixed_cmd1_channel = (midi_cmd1 & 0xF0)| (channel & 0x0F); + + Serial.write(mixed_cmd1_channel); + Serial.write(midi_cmd2); + Serial.write(midi_value); +} + +// midi_application sends note and volume and uses pitch bend to simulate continuous picth. +// Calibrate pitch bend and other parameters accordingly to the receiver synth (see midi_calibrate). +// New notes won't be generated as long as pitch bend will do the job. +// The bigger is synth's pitch bend range the beter is the effect. +// If pitch bend range = 1 no picth bend is generated (portamento will do a better job) +void Application::midi_application () +{ + double delta_loop_cc_val = 0; + double calculated_velocity = 0; + + + // Calculate loop antena cc value for midi + new_midi_loop_cc_val = loop_hand_pos >> 1; + new_midi_loop_cc_val = min (new_midi_loop_cc_val, 127); + delta_loop_cc_val = (double)new_midi_loop_cc_val - (double)old_midi_loop_cc_val; + + // Calculate log freq + if ((vPointerIncrement < 18) || (vPointerIncrement > 65518)) + { + // Lowest note + double_log_freq = 0; + } + else if ((vPointerIncrement > 26315) && (vPointerIncrement < 39221)) + { + // Highest note + double_log_freq = 127; + } + else if (vPointerIncrement < 32768) + { + // Positive frequencies + // Find note in the playing range + double_log_freq = (log (vPointerIncrement/17.152) / 0.057762265); // Precise note played in the logaritmic scale + } + else + { + // Negative frequencies + // Find note in the playing range + double_log_freq = (log ((65535-vPointerIncrement+1)/17.152) / 0.057762265); // Precise note played in the logaritmic scale + } + + // Calculate rod antena cc value for midi + new_midi_rod_cc_val = round (double_log_freq); + + // State machine for MIDI + switch (_midistate) + { + case MIDI_SILENT: + // Always refresh midi loop antena cc. + if (new_midi_loop_cc_val != old_midi_loop_cc_val) + { + midi_msg_send(midi_channel, 0xB0, loop_midi_cc, new_midi_loop_cc_val); + old_midi_loop_cc_val = new_midi_loop_cc_val; + } + else + { + // do nothing + } + + // Always refresh midi rod antena cc if applicable. + if ((rod_midi_cc != 255) && (new_midi_rod_cc_val != old_midi_rod_cc_val)) + { + midi_msg_send(midi_channel, 0xB0, rod_midi_cc, new_midi_rod_cc_val); + old_midi_rod_cc_val = new_midi_rod_cc_val; + } + else + { + // do nothing + } + + // If player's hand moves away from volume antenna + if (new_midi_loop_cc_val > midi_volume_trigger) + { + // Set key follow to the minimum in order to use closest note played as the center note + midi_key_follow = 0.5; + + // Calculate note and associated pitch bend + calculate_note_bend (); + + // Send pitch bend to reach precise played note (send 8192 (no pitch bend) in case of midi_bend_range == 1) + midi_msg_send(midi_channel, 0xE0, midi_bend_low, midi_bend_high); + old_midi_bend = new_midi_bend; + + // Calculate velocity + if (midi_timer != 0) + { + calculated_velocity = (64 * (127 - (double)midi_volume_trigger) / 127) + (VELOCITY_SENS * (double)midi_volume_trigger * delta_loop_cc_val / (double)midi_timer); + midi_velocity = min (round (abs (calculated_velocity)), 127); + } + else + { + // should not happen + midi_velocity = 64; + } + + + // Play the note + midi_msg_send(midi_channel, 0x90, new_midi_note, midi_velocity); + old_midi_note = new_midi_note; + + _midistate = MIDI_PLAYING; + } + else + { + // Do nothing + } + break; + + case MIDI_PLAYING: + // Always refresh midi loop antena cc. + if (new_midi_loop_cc_val != old_midi_loop_cc_val) + { + midi_msg_send(midi_channel, 0xB0, loop_midi_cc, new_midi_loop_cc_val); + old_midi_loop_cc_val = new_midi_loop_cc_val; + } + else + { + // do nothing + } + + // Always refresh midi rod antena cc if applicable. + if ((rod_midi_cc != 255) && (new_midi_rod_cc_val != old_midi_rod_cc_val)) + { + midi_msg_send(midi_channel, 0xB0, rod_midi_cc, new_midi_rod_cc_val); + old_midi_rod_cc_val = new_midi_rod_cc_val; + } + else + { + // do nothing + } + + // If player's hand is far from volume antenna + if (new_midi_loop_cc_val > midi_volume_trigger) + { + if ( flag_legato_on == 1) + { + // Set key follow so as next played note will be at limit of pitch bend range + midi_key_follow = (double)(midi_bend_range) - PLAYER_ACCURACY; + } + else + { + // Set key follow to max so as no key follows + midi_key_follow = 127; + } + + // Calculate note and associated pitch bend + calculate_note_bend (); + + // Refresh midi pitch bend value + if (new_midi_bend != old_midi_bend) + { + midi_msg_send(midi_channel, 0xE0, midi_bend_low, midi_bend_high); + old_midi_bend = new_midi_bend; + } + else + { + // do nothing + } + + // Refresh midi note + if (new_midi_note != old_midi_note) + { + // Play new note before muting old one to play legato on monophonic synth + // (pitch pend management tends to break expected effect here) + midi_msg_send(midi_channel, 0x90, new_midi_note, midi_velocity); + midi_msg_send(midi_channel, 0x90, old_midi_note, 0); + old_midi_note = new_midi_note; + } + else + { + // do nothing + } + } + else // Means that player's hand moves to the volume antenna + { + // Send note off + midi_msg_send(midi_channel, 0x90, old_midi_note, 0); + + _midistate = MIDI_SILENT; + } + break; + + case MIDI_STOP: + // Send all note off + midi_msg_send(midi_channel, 0xB0, 0x7B, 0x00); + + _midistate = MIDI_MUTE; + break; + + case MIDI_MUTE: + //do nothing + break; + + } +} + +void Application::calculate_note_bend () +{ + double double_log_bend; + double double_norm_log_bend; + + double_log_bend = double_log_freq - old_midi_note; // How far from last played midi chromatic note we are + + // If too much far from last midi chromatic note played (midi_key_follow depends on pitch bend range) + if ((abs (double_log_bend) >= midi_key_follow) && (midi_key_follow != 127)) + { + new_midi_note = round (double_log_freq); // Select the new midi chromatic note + double_log_bend = double_log_freq - new_midi_note; // calculate bend to reach precise note played + } + else + { + new_midi_note = old_midi_note; // No change + } + + // If pitch bend activated + if (flag_pitch_bend_on == 1) + { + // use it to reach precise note played + double_norm_log_bend = (double_log_bend / midi_bend_range); + if (double_norm_log_bend > 1) + { + double_norm_log_bend = 1; + } + else if (double_norm_log_bend < -1) + { + double_norm_log_bend = -1; + } + new_midi_bend = 8192 + (8191 * double_norm_log_bend); // Calculate midi pitch bend + } + else + { + // Don't use pitch bend + new_midi_bend = 8192; + } + + + // Prepare the 2 bites of picth bend midi message + midi_bend_low = (int8_t) (new_midi_bend & 0x007F); + midi_bend_high = (int8_t) ((new_midi_bend & 0x3F80)>> 7); +} + + + +void Application::init_parameters () +{ + // init data pot value to avoid 1st position to be taken into account + data_pot_value = analogRead(WAVE_SELECT_POT); + old_data_pot_value = data_pot_value; + +} + +void Application::set_parameters () +{ + uint16_t param_pot_value = 0; + + param_pot_value = analogRead(REGISTER_SELECT_POT); + data_pot_value = analogRead(WAVE_SELECT_POT); + + // If data pot moved + if (abs((int32_t)data_pot_value - (int32_t)old_data_pot_value) >= 8) + { + // Modify selected parameter + switch (param_pot_value >> 7) + { + case 0: + // Transpose + switch (data_pot_value >> 8) + { + case 0: + registerValue=3; // -1 Octave + break; + case 1: + case 2: + registerValue=2; // Center + break; + default: + registerValue=1; // +1 Octave + break; + } + break; + + case 1: + // Waveform + vWavetableSelector=data_pot_value>>7; + break; + + case 2: + // Channel + midi_channel = (uint8_t)((data_pot_value >> 6) & 0x000F); + if (old_midi_channel != midi_channel) + { + // Send all note off to avoid stuck notes + midi_msg_send(old_midi_channel, 0xB0, 0x7B, 0x00); + old_midi_channel = midi_channel; + } + break; + + case 3: + // Rod antenna mode + switch (data_pot_value >> 7) + { + case 0: + case 1: + flag_legato_on = 0; + flag_pitch_bend_on = 0; + break; + case 2: + case 3: + flag_legato_on = 0; + flag_pitch_bend_on = 1; + break; + case 4: + case 5: + flag_legato_on = 1; + flag_pitch_bend_on = 0; + break; + default: + flag_legato_on = 1; + flag_pitch_bend_on = 1; + break; + } + break; + + case 4: + // Pitch bend range + switch (data_pot_value >> 7) + { + case 0: + midi_bend_range = 1; + break; + case 1: + midi_bend_range = 2; + break; + case 2: + midi_bend_range = 4; + break; + case 3: + midi_bend_range = 5; + break; + case 4: + midi_bend_range = 7; + break; + case 5: + midi_bend_range = 12; + break; + case 6: + midi_bend_range = 24; + break; + default: + midi_bend_range = 48; + break; + } + break; + + case 5: + // Volume trigger + midi_volume_trigger = (uint8_t)((data_pot_value >> 3) & 0x007F); + break; + + case 6: + //Rod antenna cc + switch (data_pot_value >> 7) + { + case 0: + rod_midi_cc = 255; // Nothing + break; + case 1: + case 2: + rod_midi_cc = 8; // Balance + break; + case 3: + case 4: + rod_midi_cc = 10; // Pan + break; + case 5: + case 6: + rod_midi_cc = 16; // Ribbon Controler + break; + default: + rod_midi_cc = 74; // Cutoff (exists of both loop and rod) + break; + } + break; + + + default: + // Loop antenna cc + switch (data_pot_value >> 7) + { + case 0: + loop_midi_cc = 1; // Modulation + break; + case 1: + loop_midi_cc = 7; // Volume + break; + case 2: + loop_midi_cc = 11; // Expression + break; + case 3: + loop_midi_cc = 71; // Resonnance + break; + case 4: + loop_midi_cc = 74; // Cutoff (exists of both loop and rod) + break; + case 5: + loop_midi_cc = 91; // Reverb + break; + case 6: + loop_midi_cc = 93; // Chorus + break; + default: + loop_midi_cc = 95; // Phaser + break; + } + break; + } + + // Memorize data pot value to monitor changes + old_data_pot_value = data_pot_value; + } } diff --git a/Open_Theremin_V3/ihandlers.cpp b/Open_Theremin_V3/ihandlers.cpp index a4da417..32a1e4b 100644 --- a/Open_Theremin_V3/ihandlers.cpp +++ b/Open_Theremin_V3/ihandlers.cpp @@ -1,7 +1,7 @@ #include "Arduino.h" #include "ihandlers.h" -#include "mcpDac.h" +#include "SPImcpDAC.h" #include "timer.h" #include "build.h" @@ -15,7 +15,7 @@ #include "theremin_sintable7.c" #include "theremin_sintable8.c" -const int16_t* const wavetables[] PROGMEM = { +const int16_t* const wavetables[] = { //Fixed following a suggestion by Michael Freitas, does not need to be in PROGMEM sine_table, sine_table2, sine_table3, @@ -26,12 +26,12 @@ const int16_t* const wavetables[] PROGMEM = { sine_table8 }; -static const uint32_t MCP_DAC_BASE = 2047; +static const uint32_t MCP_DAC_BASE = 2048; #define INT0_STATE (PIND & (1<>6) & 0x3ff; #if CV_ENABLED // Generator for CV output vPointerIncrement = min(vPointerIncrement, 4095); - mcpDacSend(vPointerIncrement); //Send result to Digital to Analogue Converter (audio out) (9.6 us) + SPImcpDACsend(vPointerIncrement); //Send result to Digital to Analogue Converter (audio out) (5.5 us) #else //Play sound - // Read next wave table value (3.0us) - // The slightly odd tactic here is to provide compile-time expressions for the wavetable - // positions. Making addr1 the index into the wavtables array breaks the time limit for - // the interrupt handler - switch (vWavetableSelector) { - case 1: waveSample = (int16_t) pgm_read_word_near(wavetables[1] + offset); break; - case 2: waveSample = (int16_t) pgm_read_word_near(wavetables[2] + offset); break; - case 3: waveSample = (int16_t) pgm_read_word_near(wavetables[3] + offset); break; - case 4: waveSample = (int16_t) pgm_read_word_near(wavetables[4] + offset); break; - case 5: waveSample = (int16_t) pgm_read_word_near(wavetables[5] + offset); break; - case 6: waveSample = (int16_t) pgm_read_word_near(wavetables[6] + offset); break; - case 7: waveSample = (int16_t) pgm_read_word_near(wavetables[7] + offset); break; - default: waveSample = (int16_t) pgm_read_word_near(wavetables[0] + offset); break; - }; + // Read next wave table value + waveSample = (int16_t)pgm_read_word_near(wavetables[vWavetableSelector] + offset); + + scaledSample = ((int32_t)waveSample * (uint32_t)vScaledVolume) >> 16; // The compiler optimizes this better than any assembly written by hand !!! - if (waveSample > 0) { // multiply 16 bit wave number by 8 bit volume value (11.2us / 5.4us) - scaledSample = MCP_DAC_BASE + (mul_16_8(waveSample, vScaledVolume) >> 8); - } else { - scaledSample = MCP_DAC_BASE - (mul_16_8(-waveSample, vScaledVolume) >> 8); - } - mcpDacSend(scaledSample); //Send result to Digital to Analogue Converter (audio out) (9.6 us) + SPImcpDACsend(scaledSample + MCP_DAC_BASE); //Send result to Digital to Analogue Converter (audio out) (5.5 us) - pointer = pointer + vPointerIncrement; // increment table pointer (ca. 2us) + pointer += vPointerIncrement; // increment table pointer #endif //CV play sound incrementTimer(); // update 32us timer diff --git a/Open_Theremin_V3/ihandlers.h b/Open_Theremin_V3/ihandlers.h index d6de5d0..07c3c71 100644 --- a/Open_Theremin_V3/ihandlers.h +++ b/Open_Theremin_V3/ihandlers.h @@ -3,7 +3,7 @@ extern volatile uint16_t pitch; // Pitch value extern volatile uint16_t vol; // Volume value -extern volatile uint8_t vScaledVolume; // Volume byte +extern volatile uint16_t vScaledVolume; // Volume byte extern volatile uint16_t pitch_counter; // Pitch counter extern volatile uint16_t pitch_counter_l; // Last value of pitch counter @@ -41,4 +41,4 @@ void resetVolFlag(); void savePitchCounter(); void saveVolCounter(); -#endif // _IHANDLERS_H +#endif // _IHANDLERS_H diff --git a/Open_Theremin_V3/mcpDac.h b/Open_Theremin_V3/mcpDac.h deleted file mode 100644 index ddbb1ce..0000000 --- a/Open_Theremin_V3/mcpDac.h +++ /dev/null @@ -1,151 +0,0 @@ -/* Arduino WaveHC Library - * Copyright (C) 2009 by William Greiman - * - * This file is part of the Arduino WaveHC Library - * - * This Library is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This Library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with the Arduino WaveHC Library. If not, see - * . - */ -/** - * Macros and inline functions for MCP4921 DAC - */ -#ifndef mcpDac_h -#define mcpDac_h - -#include -#include "OTPinDefs.h" - - -//------------------------------------------------------------------------------ -#define mcpDacCsLow() MCP_DAC_CS_PORT &= ~_BV(MCP_DAC_CS_BIT) -#define mcpDacCsHigh() MCP_DAC_CS_PORT |= _BV(MCP_DAC_CS_BIT) - -#define mcpDac2CsLow() MCP_DAC_CS_PORT &= ~_BV(MCP_DAC2_CS_BIT) -#define mcpDac2CsHigh() MCP_DAC_CS_PORT |= _BV(MCP_DAC2_CS_BIT) - -#define mcpDacSckLow() MCP_DAC_SCK_PORT &= ~_BV(MCP_DAC_SCK_BIT) -#define mcpDacSckHigh() MCP_DAC_SCK_PORT |= _BV(MCP_DAC_SCK_BIT) -#define mcpDacSckPulse() {mcpDacSckHigh();mcpDacSckLow();} - -#define mcpDacSdiLow() MCP_DAC_SDI_PORT &= ~_BV(MCP_DAC_SDI_BIT) -#define mcpDacSdiHigh() MCP_DAC_SDI_PORT |= _BV(MCP_DAC_SDI_BIT) -#define mcpDacSdiSet(v) if(v){mcpDacSdiHigh();}else{mcpDacSdiLow();} - -// send bit b of d -#define mcpDacSendBit(d, b) {mcpDacSdiSet(d&_BV(b));mcpDacSckPulse();} - -//------------------------------------------------------------------------------ -// init dac I/O ports -inline void mcpDacInit(void) { - // set all to output mode - MCP_DAC_CS_DDR |= _BV(MCP_DAC_CS_BIT); - MCP_DAC2_CS_DDR |= _BV(MCP_DAC2_CS_BIT); - - MCP_DAC_SCK_DDR |= _BV(MCP_DAC_SCK_BIT); - MCP_DAC_SDI_DDR |= _BV(MCP_DAC_SDI_BIT); - // chip select high - mcpDacCsHigh(); - mcpDac2CsHigh(); - -#if USE_MCP_DAC_LDAC - // LDAC low always - use unbuffered mode - MCP_DAC_LDAC_DDR |= _BV(MCP_DAC_LDAC_BIT); - MCP_DAC_LDAC_PORT &= ~_BV(MCP_DAC_LDAC_BIT); -#endif // USE_MCP_DAC_LDAC -} -//------------------------------------------------------------------------------ -// send 12 bits to dac -// trusted compiler to optimize and it does -// csLow to csHigh takes 8 - 9 usec on a 16 MHz Arduino -inline void mcpDacSend(uint16_t data) { - mcpDacCsLow(); - // send DAC config bits - mcpDacSdiLow(); - mcpDacSckPulse(); // DAC A - mcpDacSdiHigh(); - mcpDacSckPulse(); // buffered REF - - mcpDacSckPulse(); // 1X gain - mcpDacSckPulse(); // no SHDN - // send 12 data bits - mcpDacSendBit(data, 11); - mcpDacSendBit(data, 10); - mcpDacSendBit(data, 9); - mcpDacSendBit(data, 8); - mcpDacSendBit(data, 7); - mcpDacSendBit(data, 6); - mcpDacSendBit(data, 5); - mcpDacSendBit(data, 4); - mcpDacSendBit(data, 3); - mcpDacSendBit(data, 2); - mcpDacSendBit(data, 1); - mcpDacSendBit(data, 0); - mcpDacCsHigh(); -} - -inline void mcpDac2ASend(uint16_t data) { - mcpDac2CsLow(); - // send DAC config bits - mcpDacSdiLow(); - mcpDacSckPulse(); // DAC A - mcpDacSdiHigh(); - mcpDacSckPulse(); // buffered REF - - mcpDacSckPulse(); // 1X gain - mcpDacSckPulse(); // no SHDN - // send 12 data bits - mcpDacSendBit(data, 11); - mcpDacSendBit(data, 10); - mcpDacSendBit(data, 9); - mcpDacSendBit(data, 8); - mcpDacSendBit(data, 7); - mcpDacSendBit(data, 6); - mcpDacSendBit(data, 5); - mcpDacSendBit(data, 4); - mcpDacSendBit(data, 3); - mcpDacSendBit(data, 2); - mcpDacSendBit(data, 1); - mcpDacSendBit(data, 0); - mcpDac2CsHigh(); -} - -inline void mcpDac2BSend(uint16_t data) { - mcpDac2CsLow(); - // send DAC config bits - mcpDacSdiHigh(); - mcpDacSckPulse(); // DAC A - mcpDacSdiHigh(); - mcpDacSckPulse(); // buffered REF - - mcpDacSckPulse(); // 1X gain - mcpDacSckPulse(); // no SHDN - // send 12 data bits - mcpDacSendBit(data, 11); - mcpDacSendBit(data, 10); - mcpDacSendBit(data, 9); - mcpDacSendBit(data, 8); - mcpDacSendBit(data, 7); - mcpDacSendBit(data, 6); - mcpDacSendBit(data, 5); - mcpDacSendBit(data, 4); - mcpDacSendBit(data, 3); - mcpDacSendBit(data, 2); - mcpDacSendBit(data, 1); - mcpDacSendBit(data, 0); - mcpDac2CsHigh(); -} - - - -#endif //mcpDac_h diff --git a/Quick guide open theremin midi.bmp b/Quick guide open theremin midi.bmp index 942cd30..072e7bb 100644 Binary files a/Quick guide open theremin midi.bmp and b/Quick guide open theremin midi.bmp differ diff --git a/README.md b/README.md index c0234b2..3a0905f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,22 @@ -## Open Theremin V3 with MIDI interface control software V2.0 for Arduino UNO +## Open Theremin V3 with MIDI interface control software V2.3 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 -Urs also made a very clear presentation of this MIDI feature on his website: http://www.gaudi.ch/OpenTheremin/index.php?option=com_content&view=article&id=200&Itemid=121, many thanks ! +This Open Theremin V3 with MIDI version V2.3 also takes into account +Changes added in Open.Theremin version 3.1 (all by @Theremingenieur): + + Fix a wavetable addressing issue (found by @miguelfreitas) + Use the Arduino's hardware SPI to control the DACS and use the Latch signal to reduce audio jitter + Improve the register switch to transpose by clean octaves and keep the tone spacing and pitch tuning consistent + Improve the volume response to give a smoother start and wider dynamics (*) + +(*) This relies on a recent gcc compiler version. Make sure to compile it with the Arduino IDE >= 1.8.10 + +Pitch Bend Range choice is also extended (Allows 4 octaves Bend) + +Urs also made a very clear presentation of the MIDI feature on his website: http://www.gaudi.ch/OpenTheremin/index.php?option=com_content&view=article&id=200&Itemid=121, many thanks ! ### Don't click on the files! Click on the "clone or download" Button to the right. Then unpack the archive. @@ -63,7 +75,7 @@ Let's consider a Fade-in / Picth Variation / Fade-out sequence (I use right hand 2. Pitch variation - When right hand moves next to PITCH ANTENNA (ROD), PITCH BEND messages are generated (if activated) to reach exact pitch as long as pitch bend range will do. Beyond, a new NOTE ON followed by a NOTE OFF for the previous note are generated if legato mode is activated. Pitch bend range can be configured (1, 2, 7, 12 or 24 semitones) to align with synth's maximum capabilities. + When right hand moves next to PITCH ANTENNA (ROD), PITCH BEND messages are generated (if activated) to reach exact pitch as long as pitch bend range will do. Beyond, a new NOTE ON followed by a NOTE OFF for the previous note are generated if legato mode is activated. Pitch bend range can be configured (1, 2, 4, 5, 7, 12, 24 or 48 semitones) to align with synth's maximum capabilities. 3. Fade-Out @@ -75,12 +87,12 @@ Let's consider a Fade-in / Picth Variation / Fade-out sequence (I use right hand "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 + 1. Register: 3 positions (-1 Octave, center, +1 Octave) as in original Open Theremin V3 (version V3.1) 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). + 5. Pitch bend range: 8 positions (1, 2, 4, 5, 7, 12, 24, 48 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 / Velocity sensitivity (how fast moves the volume loop's hand): 128 positions (0 to 127) @@ -101,7 +113,7 @@ Volume trigger = 127 (Maximum) won't generate any NOTE ON. It can be used to gen 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 MIDI CC = None, Loop antenna MIDI CC = 7-Volume. +Default configuration is: Register = Center, 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: @@ -146,7 +158,7 @@ I'll try to answer you if I can. ### LICENSE -Original project ,Open Theremin, was written by Urs Gaudenz, GaudiLabs, in 2016 +Original project, Open Theremin, was written by Urs Gaudenz, GaudiLabs, in 2016 GNU license. This Project inherits this 2016 GNU License. Check LICENSE file for more information diff --git a/README.md~ b/README.md~ deleted file mode 100644 index 128f3a7..0000000 --- a/README.md~ +++ /dev/null @@ -1,22 +0,0 @@ -## Open.Theremin V3 control software - -Arduino UNO Software for the Open.Theremin - -### 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. - -### Open Source Theremin based on the Arduino Platform - -Open.Theremin is an arduino shield to build the legendary music instrument invented by Leon Theremin back in 1920. The theremin is played with two antennas, one to control the pitch and one for volume. The electronic shield with two ports to connect those antennas comprises two heterodyne oscillators to measure the distance of the hand to the antenna when playing the instrument. The resulting signal is fed into the arduino. After linearization and filtering the arduino generates the instruments sound that is then played through a high quality digital analog audio converter on the board. The characteristics of the sound can be determined by a wave table on the arduino. - -For more info on the open source project and on availability of ready made shield see: - -http://www.gaudi.ch/OpenTheremin/ - -### Installation -1. Open up the Arduino IDE -2. Open the File "Open_Theremin_V3.ino" -3. Selecting the correct usb port on Tools -> Serial Port -4. Select the correct arduino board from Tools -> Board -5. Upload the code by clicking on the upload button.