#include "Adafruit_MPR121.h"
#include "TeensyTimerTool.h"
/*...*/
#include <AnalogBufferDMA.h> #define OSC_DAC_CS_PIN 1
#define DAC_ADSR_AND_DISTANCE_CS 10
/*...*/ // Global variables
volatile byte SPIfree = true;
volatile byte I2Cfree = true;
boolean Gate = false;
/*...*/ // Timers
IntervalTimer OscillatorTimer; // Oscillator
PeriodicTimer ADSRtimer; // ADSR
PeriodicTimer LFOtimer; // LFO
/*...*/ // Watching dog
WDT_T4<WDT1> WDT; void setup(){
/* .... */
} // LED blinks once at startup
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
delay(10);
digitalWrite(LED_PIN, LOW); MIDIinit(); SPI.begin(); pinMode(GATE_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(GATE_PIN), GateInterrupt, CHANGE);
/*...*/ Wire1.begin();
Wire1.setClock(3400000); void loop(){
/* ... */
// Set oscillator frequency
Pitch();
/* ... */
// Checking button state
CheckButtonState();
/* ... */
// Feeding the Watchdog Timer to prevent system reset
WDT.feed();
} // Values are in microseconds
double V_OCT[] = {
238.891028,
237.759720,
236.633769
/* ... */
} ADC *adc = new ADC();
const uint32_t InitialAverageValue = 2;
const uint32_t BufferSize = 10;
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[BufferSize];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[BufferSize];
AnalogBufferDMA abdma(dma_adc_buff1, BufferSize, dma_adc_buff2, BufferSize); class LowPass
{
private:
float a[order];
float b[order+1];
float omega0;
/*...*/
} /*...*/
adc->adc0->setAveraging(1); // set number of averages
adc->adc0->setResolution(10); // set bits of resolution
adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // fastest conversion
adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // fastest sampling
/*...*/ void ADSRinterrupt(void){
if(oldEnvelope != Envelope){
if(SPIfree == true && PIT_CVAL0 > 15){
SPIfree = false;
DAC(Envelope, DAC_ADSR_AND_DISTANCE_CS, false);
SPIfree = true;
oldEnvelope = Envelope;
}
}
ADSRInterruptState = HIGH;
} void ADSR(void){
// Note on
if(Gate == true){
// ATTACK
if(DecayState == false){
/*...*/
}
// DECAY
if(Envelope < 95 && DecayState == false){
/*...*/
}
// Sustain
if(DecayState == true && Envelope <= map(Sust, 0, 255, 4095, 0)){
/*...*/
}
}
// Note Off
if(Gate != true){
// RELEASE
if(NoteIsActive == true){
/*...*/
}
}
} void CheckButtonState(void){
ButtonState = !digitalRead(BUTTON_PIN); // HIGH is Pressed!
} void HandleButtonActions(){
// Create a new landsacape after the button is released
if(ButtonState == LOW && MenuState == false && CreateFlag == true && millis() - ButtonPressTime > 40){
CreateNewLandscape();
CreateFlag = false;
}
// The menu settings are adjusted via the keyboard
// Pressing the button skips parameter selection and keeps the current values
if(ButtonState == LOW && SkipFlag == false && MenuState == true){
SkipFlag = true;
Skipped = false;
}
// Button is pressed!
if(ButtonState == HIGH){
// Create landscape after the button is released is menu is off
CreateFlag = true;
// Start debounce filter
if(ButtonPressTime == 0) ButtonPressTime = millis();
// Skip changes if the button is pressed
if(MenuState == true && MenuKeyPressed < 2 && Skipped == false && millis() - ButtonPressTime > 40){
/*...*/
MenuKeyPressed++;
/*...*/nalogWrite(LED_PIN, 0);
}
// Open menu by holding the button
if(millis() - ButtonPressTime > 1000 && !MenuState){
MenuState = true;
/*...*/
}
// When the button is not pressed
}else{
/*...*/
CreateFlag = false;
}
if(MenuState == true){
Menu();
}
// Save to EEPROM if settings were changed
SaveToEEPOMMenuSettings();
} // Change settings using the keyboard
void Menu(void){
// Change Element settings using the keyboard
if(MenuKeyPressed == 1 && MenuUpdated == LOW){
/*...*/
SelectElementFunction = KeybordKey;
/*...*/
}
// Change Scale settings using the keyboard
if(MenuKeyPressed > 1){
/*...*/
ScaleNumber = KeybordKey;
/*...*/
}
} void DAC(int Data, int CSpin, boolean Channel){
Data |=0xf000;// B15(A/B)=1 B, B14(BUF)=1 on, B13(GAn) 1=x1 B12(SHDNn) 1=off
if(Channel == true) Data &= ~0x8000; // for A-out
SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0));
digitalWrite(CSpin, LOW);
SPI.transfer((0xff00 & Data)>>8);
SPI.transfer(0x00ff & Data);
digitalWrite(CSpin, HIGH);
SPI.endTransaction();
} void DistanceSensorInit(void){
Sensor.setTimeout(500);
Sensor.init();
Sensor.setMeasurementTimingBudget(20000);
Sensor.startContinuous();
} void ReadDistanceSensor(void){
if(I2Cfree == true){
I2Cfree = false;
Wire1.beginTransmission(Address);
Wire1.write(0x14 + 10);
Wire1.endTransmission();
Wire1.requestFrom(0x29, 2);
DistanceValue = (uint16_t)Wire1.read() << 8;
DistanceValue |= Wire1.read();
I2Cfree = true;
}
} void SmoothSensorData(void){
DistanceValueBuffer[DistanceBufferCounter] = DistanceValue;
DistanceBufferCounter++;
if(DistanceBufferCounter > 4) DistanceBufferCounter = 0;
SmoothedDistance = (DistanceValueBuffer[0] + DistanceValueBuffer[1] + DistanceValueBuffer[2] + DistanceValueBuffer[3] + DistanceValueBuffer[4]) / 5;
if(SmoothedDistance > 350) SmoothedDistance = 350;
if(SmoothedDistance < 50) SmoothedDistance = 50;
} void DistanceUpdate(void){
ReadDistanceSensor();
SmoothSensorData();
if(oldDistance != SmoothedDistance){
/*...*/
DAC(DACmapped, DAC_ADSR_AND_DISTANCE_CS, true);
/*...*/
}
if(!Gate) analogWrite(LED_PIN, map(SmoothedDistance, 50, 350, 255, 0));
}
} void EEPROMreadSettings(void){
for(int i = EEPROM_SIZE; i >= 0; i--){
short data = EEPROM.read(i);
if(data != 0){
SelectElementFunction = data - 1;
int temp = i-1;
ScaleNumber = EEPROM.read(temp) - 1;
EEPROMaddress = i;
break;
}
}
}
void EEPROMwriteSettings(short ScaleNumber, short SelectElementFunction){
/*...*/
EEPROM.write(EEPROMaddress, SelectElementFunction);
/*...*/
}
void EraseEEPROM(void){
for(int i = 0; i <= EEPROM_SIZE;){
EEPROM.write(i, 0);
i++;
}
EEPROMaddress = 0;
} void ElementUpdate(void){
switch(SelectElementFunction){
case 0:
/*...*/
DigitalRandomNoise();
/*...*/
break;
case 1:
/*...*/
break;
case 2:
/*...*/
break;
case 3:
/*...*/
break;
default:
/*...*/
}
}
void DigitalRandomNoise(void){
/*...*/
DAC(random(0,4095), DAC_LFO_AND_ELEMENT_CS, false);
/*...*/
} Adafruit_MPR121 Left_MPR121 = Adafruit_MPR121();
Adafruit_MPR121 Right_MPR121 = Adafruit_MPR121(); void KeyboardRead(void){
currtouched_left = Left_MPR121.touched();
currtouched_right = Right_MPR121.touched();
if(digitalRead(KEYBOARD_INTERRUPT_PIN) == HIGH){
for(uint8_t i=0; i<11; i++){
if((currtouched_left & _BV(i)) && !(lasttouched_left & _BV(i))){
/*...*/
KeybordKey = i;
/*...*/
}
}
}
/*...*/
} void LfoInterrupt(void){
LFOCounter++;
/*...*/
if(LFOPointer > 1023) LFOPointer = 0;
if (LFOWaveformSelector == true){
LFOData = SINE[LFOPointer];
}else{
LFOData = PULSE[LFOPointer];
}
LFOCounter = 0;
/*...*/
DAC(LFOData, DAC_LFO_AND_ELEMENT_CS, true);
/*...*/
} #define SIZE 256 // Size of the landscape array
uint16_t Landscape[SIZE]; // Main landscape height array
/*...*/
int MinValue;
int MaxValue;
int ReboundZoneMin = 30; // Minimum height before rebound effect
int ReboundZoneMax = 190; // Maximum height before rebound effect
int ReboundZoneStrength = 5; // Strength of the rebound effect
int StartingHeight = 50; // Initial terrain height
int Roughness = 5; // Controls terrain steepness (higher values create steeper slopes)
int CurrentHeight = StartingHeight; // Current height of the terrain
int HeightIncrement = 0; // Change in height for each step
extern short OSCsliderSelect;
// Generates three random landscapes for all oscillator modes at startup
void CreateLandsacapes(void){
CreateLandscape(Landscape, SIZE);
/*...*/
}
// Wrapper function for generating a new landscape.
// Calls the parameterized function with predefined settings.
// Used in the menu and other general contexts.
void CreateNewLandscape(void){
CreateLandscape(Landscape, 256);
}
// Generates a new random landscape
void CreateLandscape(uint16_t *Landscape, int size){
for (int i = 1; i < size; i++) {
// Adjust height based on rebound zones
if (CurrentHeight <= ReboundZoneMin) {
HeightIncrement += random(0, ReboundZoneStrength);
}
if (CurrentHeight >= ReboundZoneMax) {
HeightIncrement -= random(0, ReboundZoneStrength);
}
// Apply random variation with respect to roughness
HeightIncrement += random(-1 - round(HeightIncrement / Roughness), 2 - round(HeightIncrement / Roughness));
CurrentHeight += HeightIncrement;
// Store the generated height in the array
Landscape[i] = CurrentHeight;
}
// Find the minimum and maximum height values
MinValue = 300;
MaxValue = 0;
for (int i = 1; i < size; i++) {
if (Landscape[i] > MaxValue) MaxValue = Landscape[i];
if (Landscape[i] < MinValue) MinValue = Landscape[i];
}
// Normalize the height values to the 0-4095 range
for (int i = 1; i < size; i++) {
Landscape[i] = map(Landscape[i], MinValue, MaxValue, 0, 4095);
}
}
void serialMIDIread(void){
if (MIDI.read()){
byte type = MIDI.getType();
switch (type){
case midi::NoteOn:
/*...*/
RecievedMIDINote = MIDI.getData1();
MIDIvelocity = MIDI.getData2();
Channel = MIDI.getChannel();
/*...*/
case midi::NoteOff:
RecievedMIDINote = MIDI.getData1();
Channel = MIDI.getChannel();
/*...*/
default:
break;
}
}
}
/*...*/
void USBMIDISendMessages(void){
/*...*/
}
// Values are in microseconds
double midiNote[] = { 0,
/*...*/
238.89102706071438,
225.48310397275563,
212.82770978361953,
200.8826082916328,
/*
...
*/
} void OscillatorUpdate(void){
noInterrupts();
WaveTablePoint++;
if(WaveTablePoint > 255) WaveTablePoint = 0;
if(OSCsliderSelect == 0) OscValue = LandscapeA[WaveTablePoint];
else if(OSCsliderSelect == 1) OscValue = Triangle[WaveTablePoint];
else if(OSCsliderSelect == 2) OscValue = Landscape[WaveTablePoint];
/*...*/
DAC(OscValue, OSC_DAC_CS_PIN, true);
/*...*/
interrupts();
} void Pitch(void){
/*...*/
if(MIDInoteRecieved == false){
/*...*/
KeyFind = FindTone(KeybordNote, ScaleNumber);
/*...*/
//int OSCArrayIndex = ADC_Pitch_Filtered + int((KeyFind*12.2));
int OSCArrayIndex = 0 + int((KeyFind*12.2));
/*...*/
OSC_Timer = V_OCT[OSCArrayIndex];
}
// If a MIDI note is received, sets frequency according to the MIDI pitch.
else{
OSC_Timer = midiNote[RecievedMIDINote];
}
/*...*/
// Updates the oscillator timer based on the calculated pitch frequency.
OscillatorTimer.update(OSC_Timer);
} int FindTone(short Key, short ScaleNumber){
short Chrom[] = { 1 }; // 1
short Minor[] = { 2, 1, 2, 2, 1, 2, 2 }; // 2
short Major[] = { 2, 2, 1, 2, 2, 2, 1 }; // 3
/*...*/
while(Counter != 0){
switch(ScaleNumber){
case 0:
if (Step == 1) Step = 0;
Scale = Chrom[Step];
Tone = Tone + Scale;
break;
case 1:
if (Step == 7) Step = 0;
Scale = Minor[Step];
Tone = Tone + Scale;
break;
/*...*/
default: break;
}
Step++;
Counter--;
}
return Tone;
} // Pulse for LFO
const int16_t PULSE[] = { 0, /*...*/ 4095 };
// Sine for LFO
const int16_t SINE[] = { 2048, /*...*/ 4095, /*...*/ 0, /*...*/ 2047 };
// Triangle for VCO
const int16_t Triangle[] = { 0, /*...*/ 4095, /*...*/ 0 };