You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OpenTheremin_V3_with_MIDI/Open_Theremin_V3/application.cpp

514 lines
11 KiB

#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;
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;
Application::Application()
: _state(PLAYING),
_mode(NORMAL) {
};
void Application::setup() {
#if SERIAL_ENABLED
Serial.begin(Application::BAUD);
#endif
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);
}
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<<CS10);
while(!(PIND & (1<<PORTD3)));
while((PIND & (1<<PORTD3)));
TCNT1 = 0;
timer_overflow_counter = 0;
while(qn<31250){
while(!(PIND & (1<<PORTD3)));
qn++;
while((PIND & (1<<PORTD3)));
};
TCCR1B = 0;
unsigned long frequency = TCNT1;
unsigned long temp = 65536*(unsigned long)timer_overflow_counter;
frequency += temp;
return frequency;
}
unsigned long Application::GetPitchMeasurement()
{
TCNT1 = 0;
timer_overflow_counter = 0;
TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10);
delay(1000);
TCCR1B = 0;
unsigned long frequency = TCNT1;
unsigned long temp = 65536*(unsigned long)timer_overflow_counter;
frequency += temp;
return frequency;
}
unsigned long Application::GetVolumeMeasurement()
{timer_overflow_counter = 0;
TCNT0=0;
TCNT1=49911;
TCCR0B = (1<<CS02) | (1<<CS01) | (1<<CS00); // //External clock source on T0 pin. Clock on rising edge.
TIFR1 = (1<<TOV1); //Timer1 INT Flag Reg: Clear Timer Overflow Flag
while(!(TIFR1&((1<<TOV1)))); // on Timer 1 overflow (1s)
TCCR0B = 0; // Stop TimerCounter 0
unsigned long frequency = TCNT0; // get counter 0 value
unsigned long temp = (unsigned long)timer_overflow_counter; // and overflow counter
frequency += temp*256;
return frequency;
}
#if CV_ENABLED // Initialise PWM Generator for CV output
void initialiseCVOut() {
}
#endif
AppMode Application::nextMode() {
return _mode == NORMAL ? MUTE : AppModeValues[_mode + 1];
}
void Application::loop() {
int32_t pitch_v = 0, pitch_l = 0; // Last value of pitch (for filtering)
int32_t vol_v = 0, vol_l = 0; // Last value of volume (for filtering)
int32_t pitch_f = 0; // pitch in frequency (for filtering)
uint16_t volumePotValue = 0;
uint16_t pitchPotValue = 0;
uint16_t registerPotValue = 0;
mloop: // Main loop avoiding the GCC "optimization"
pitchPotValue = analogRead(PITCH_POT);
volumePotValue = analogRead(VOLUME_POT);
registerPotValue = (analogRead(REGISTER_SELECT_POT)>>8)+1;
vWavetableSelector = analogRead(WAVE_SELECT_POT) >> 7;
if (_state == PLAYING && HW_BUTTON_PRESSED) {
_state = CALIBRATING;
resetTimer();
}
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();
}
_state = PLAYING;
};
if (_state == CALIBRATING && timerExpired(15000)) {
HW_LED2_ON;
playStartupSound();
calibrate_pitch();
calibrate_volume();
initialiseTimer();
initialiseInterrupts();
playCalibratingCountdownSound();
calibrate();
_mode=NORMAL;
HW_LED2_OFF;
while (HW_BUTTON_PRESSED)
; // NOP
_state = PLAYING;
};
#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
pitch_v=pitch; // Averaging pitch values
pitch_v=pitch_l+((pitch_v-pitch_l)>>2);
pitch_l=pitch_v;
pitch_f=FREQ_FACTOR/pitch_v;
//HW_LED2_ON;
// set wave frequency for each mode
switch (_mode) {
case MUTE : /* NOTHING! */; break;
case NORMAL : setWavetableSampleAdvance((pitchCalibrationBase-pitch_v)/registerPotValue+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;
}
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;
Serial.begin(115200);
Serial.println("\nPITCH CALIBRATION\n");
HW_LED1_OFF;
HW_LED2_ON;
InitialisePitchMeasurement();
interrupts();
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
pitchXn0 = 0;
pitchXn1 = 4095;
pitchfn = q0-PitchFreqOffset; // Add offset calue to set frequency
Serial.print("\nPitch Set Frequency: ");
Serial.println(pitchfn);
mcpDac2BSend(1600);
mcpDac2ASend(pitchXn0);
delay(100);
pitchfn0 = GetPitchMeasurement();
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
mcpDac2ASend(pitchXn0);
delay(100);
pitchfn0 = GetPitchMeasurement()-pitchfn;
mcpDac2ASend(pitchXn1);
delay(100);
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;
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;
Serial.begin(115200);
Serial.println("\nVOLUME CALIBRATION");
InitialiseVolumeMeasurement();
interrupts();
mcpDacInit();
volumeXn0 = 0;
volumeXn1 = 4095;
q0 = (16000000/qMeasurement*460765);
volumefn = q0-VolumeFreqOffset;
Serial.print("\nVolume Set Frequency: ");
Serial.println(volumefn);
mcpDac2BSend(volumeXn0);
delay_NOP(44316);//44316=100ms
volumefn0 = GetVolumeMeasurement();
mcpDac2BSend(volumeXn1);
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){
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
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;
volumeXn1 = volumeXn2;
HW_LED2_TOGGLE;
}
EEPROM.put(2,volumeXn0);
HW_LED2_OFF;
HW_LED1_ON;
Serial.println("\nCALIBRATION COMPTLETED\n");
}
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");
}
}