Merge pull request #1 from MrDham/Midi-implementation

Midi implementation
pull/2/head
MrDham 7 years ago committed by GitHub
commit ab69a1be89
  1. 29
      Open_Theremin_V3/Open_Theremin_V3.ino
  2. 374
      Open_Theremin_V3/application.cpp
  3. 11
      Open_Theremin_V3/application.h
  4. 2
      Open_Theremin_V3/build.h
  5. 1
      Open_Theremin_V3/ihandlers.cpp
  6. 1
      Open_Theremin_V3/timer.cpp
  7. 8
      Open_Theremin_V3/timer.h
  8. 5
      README.md

@ -1,34 +1,38 @@
/*
* Open.Theremin control software for Arduino UNO
* Version 3.0
* Copyright (C) 2010-2016 by Urs Gaudenz
* 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
*
* Open.Theremin control software is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
*
* Open Theremin V3 with MIDI interface control software 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.
*
* Open.Theremin control software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* Open Theremin V3 with MIDI interface control software 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 Open.Theremin control software. If not, see <http://www.gnu.org/licenses/>.
* the Open Theremin V3 with MIDI interface control software.
* If not, see <http://www.gnu.org/licenses/>.
*
* With important contributions by
* Urs Gaudenz also credits for their important contributions to Open Theremin V3:
* David Harvey
* Michael Margolis
*/
/* Midi added by Vincent Dhamelincourt - September 2017.
* Serial com' removed from the original Open Theremin V3 's code for midi purpose.
*/
/**
Building the code
=================
build.h contains #defines that control the compilation of the code
ENABLE_SERIAL - if non-0, the build will include code to write the detected
pitch to the serial connection every 100 milliseconds. Set serial
receive baud to 115200
ENABLE_CV - if non-0, emit cv output on pin 6 (EXPERIMENTAL!)
@ -43,6 +47,7 @@ Main application object. Holds the state of the app (playing, calibrating), deal
with initialisation and the app main loop, reads pitch and volume changed flags
from the interrupt handlers and sets pitch and volume values which the timer
interrupt sends to the DAC.
Midi is also managed here
** OTPinDefs.h **
Pin definitions for the DAC.

@ -24,15 +24,22 @@ 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() {
#if SERIAL_ENABLED
Serial.begin(Application::BAUD);
#endif
HW_LED1_ON;HW_LED2_OFF;
@ -58,7 +65,7 @@ initialiseInterrupts();
EEPROM.get(4,pitchCalibrationBase);
EEPROM.get(8,volCalibrationBase);
midi_setup();
}
@ -181,62 +188,73 @@ void Application::loop() {
vWavetableSelector=wavePotValueL>>7;
registerValue=4-(registerPotValueL>>8);
if (_state == PLAYING && HW_BUTTON_PRESSED) {
if (_state == PLAYING && HW_BUTTON_PRESSED)
{
_state = CALIBRATING;
_midistate = MIDI_STOP;
resetTimer();
}
if (_state == CALIBRATING && HW_BUTTON_RELEASED) {
if (timerExpired(1500)) {
if (_state == CALIBRATING && HW_BUTTON_RELEASED)
{
if (timerExpired(1500))
{
_mode = nextMode();
if (_mode==NORMAL) {HW_LED1_ON;HW_LED2_OFF;} else {HW_LED1_OFF;HW_LED2_ON;};
// playModeSettingSound();
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)) {
if (_state == CALIBRATING && timerExpired(15000))
{
HW_LED2_ON;
HW_LED2_ON;
playStartupSound();
calibrate_pitch();
calibrate_volume();
initialiseTimer();
initialiseInterrupts();
playCalibratingCountdownSound();
calibrate();
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_LED2_OFF;
_mode=NORMAL;
HW_LED2_OFF;
while (HW_BUTTON_PRESSED)
; // NOP
_state = PLAYING;
_midistate = MIDI_SILENT;
};
#if CV_ENABLED
OCR0A = pitch & 0xff;
#endif
#if SERIAL_ENABLED
if (timerExpired(TICKS_100_MILLIS)) {
resetTimer();
Serial.write(pitch & 0xff); // Send char on serial (if used)
Serial.write((pitch >> 8) & 0xff);
}
#endif
if (pitchValueAvailable) { // If capture event
@ -281,6 +299,12 @@ void Application::loop() {
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
}
@ -319,8 +343,6 @@ static long pitchfn0 = 0;
static long pitchfn1 = 0;
static long pitchfn = 0;
Serial.begin(115200);
Serial.println("\nPITCH CALIBRATION\n");
HW_LED1_OFF;
HW_LED2_ON;
@ -330,8 +352,6 @@ static long pitchfn = 0;
mcpDacInit();
qMeasurement = GetQMeasurement(); // Measure Arudino clock frequency
Serial.print("Arudino Freq: ");
Serial.println(qMeasurement);
q0 = (16000000/qMeasurement*500000); //Calculated set frequency based on Arudino clock frequency
@ -340,8 +360,6 @@ pitchXn1 = 4095;
pitchfn = q0-PitchFreqOffset; // Add offset calue to set frequency
Serial.print("\nPitch Set Frequency: ");
Serial.println(pitchfn);
mcpDac2BSend(1600);
@ -354,11 +372,6 @@ mcpDac2ASend(pitchXn1);
delay(100);
pitchfn1 = GetPitchMeasurement();
Serial.print ("Frequency tuning range: ");
Serial.print(pitchfn0);
Serial.print(" to ");
Serial.println(pitchfn1);
while(abs(pitchfn0-pitchfn1)>CalibrationTolerance){ // max allowed pitch frequency offset
@ -372,14 +385,6 @@ pitchfn1 = GetPitchMeasurement()-pitchfn;
pitchXn2=pitchXn1-((pitchXn1-pitchXn0)*pitchfn1)/(pitchfn1-pitchfn0); // new DAC value
Serial.print("\nDAC value L: ");
Serial.print(pitchXn0);
Serial.print(" Freq L: ");
Serial.println(pitchfn0);
Serial.print("DAC value H: ");
Serial.print(pitchXn1);
Serial.print(" Freq H: ");
Serial.println(pitchfn1);
pitchXn0 = pitchXn1;
@ -406,8 +411,6 @@ static long volumefn0 = 0;
static long volumefn1 = 0;
static long volumefn = 0;
Serial.begin(115200);
Serial.println("\nVOLUME CALIBRATION");
InitialiseVolumeMeasurement();
interrupts();
@ -420,8 +423,6 @@ volumeXn1 = 4095;
q0 = (16000000/qMeasurement*460765);
volumefn = q0-VolumeFreqOffset;
Serial.print("\nVolume Set Frequency: ");
Serial.println(volumefn);
mcpDac2BSend(volumeXn0);
@ -435,11 +436,6 @@ delay_NOP(44316);//44316=100ms
volumefn1 = GetVolumeMeasurement();
Serial.print ("Frequency tuning range: ");
Serial.print(volumefn0);
Serial.print(" to ");
Serial.println(volumefn1);
while(abs(volumefn0-volumefn1)>CalibrationTolerance){
@ -453,14 +449,6 @@ volumefn1 = GetVolumeMeasurement()-volumefn;
volumeXn2=volumeXn1-((volumeXn1-volumeXn0)*volumefn1)/(volumefn1-volumefn0); // calculate new DAC value
Serial.print("\nDAC value L: ");
Serial.print(volumeXn0);
Serial.print(" Freq L: ");
Serial.println(volumefn0);
Serial.print("DAC value H: ");
Serial.print(volumeXn1);
Serial.print(" Freq H: ");
Serial.println(volumefn1);
volumeXn0 = volumeXn1;
@ -474,7 +462,6 @@ EEPROM.put(2,volumeXn0);
HW_LED2_OFF;
HW_LED1_ON;
Serial.println("\nCALIBRATION COMPTLETED\n");
}
void Application::hzToAddVal(float hz) {
@ -515,4 +502,247 @@ 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
_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:
// 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;
// Send volume to reach precise played volume
midi_msg_send(midi_channel, 0xB0, 0x07, new_midi_volume);
old_midi_volume = new_midi_volume;
// 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:
// 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 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
}
// 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 volume = 0
midi_msg_send(midi_channel, 0xB0, 0x07, 0);
old_midi_volume = 0;
// 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);
_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);
}

@ -7,6 +7,7 @@
enum AppState {CALIBRATING = 0, PLAYING};
enum AppMode {MUTE = 0, NORMAL};
enum AppMidiState {MIDI_SILENT = 0, MIDI_PLAYING, MIDI_STOP, MIDI_MUTE};
class Application {
public:
@ -32,12 +33,10 @@ class Application {
#if SERIAL_ENABLED
static const int BAUD = 115200;
#endif
AppState _state;
AppMode _mode;
AppMidiState _midistate;
void calibrate();
void calibrate_pitch();
@ -64,6 +63,12 @@ class Application {
void playCalibratingCountdownSound();
void playModeSettingSound();
void delay_NOP(unsigned long time);
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 ();
};
#endif // _APPLICATION_H

@ -4,8 +4,6 @@
#define _BUILD_H
// Set to build with serial support
#define SERIAL_ENABLED 0
// Set to build with control voltage output (experimental)
#define CV_ENABLED 0

@ -163,6 +163,7 @@ ISR (INT1_vect) {
#endif //CV play sound
incrementTimer(); // update 32us timer
incrementMidiTimer(); // update 32us miditimer
if (PC_STATE) debounce_p++;
if (debounce_p == 3) {

@ -3,6 +3,7 @@
#include "timer.h"
volatile uint16_t timer = 0;
volatile uint16_t midi_timer = 0;
void ticktimer (uint16_t ticks) {
resetTimer();

@ -2,6 +2,7 @@
#define _TIMER_H
extern volatile uint16_t timer;
extern volatile uint16_t midi_timer;
inline uint16_t millisToTicks(uint16_t milliseconds) {
return milliseconds * (1000.0f/32);
@ -15,6 +16,10 @@ inline void incrementTimer() {
timer++;
}
inline void incrementMidiTimer() {
midi_timer++;
}
inline bool timerExpired(uint16_t ticks) {
return timer >= ticks;
}
@ -34,8 +39,5 @@ inline bool timerUnexpiredMillis(uint16_t milliseconds) {
void ticktimer (uint16_t ticks);
void millitimer (uint16_t milliseconds);
#if SERIAL_ENABLED
const uint16_t TICKS_100_MILLIS = millisToTicks(100);
#endif //SERIAL_ENABLED
#endif // _TIMER_H

@ -1,6 +1,7 @@
## Open.Theremin V3 control software
## Open Theremin V3 with MIDI interface control software for Arduino UNO
Arduino UNO Software for the Open.Theremin
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
### 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)

Loading…
Cancel
Save