#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
#include <EventDelay.h>
#include <mozzi_midi.h>
#include <LowPassFilter.h>
#define CONTROL_RATE 128 // powers of 2 please
// Create two oscillators
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin1(SIN2048_DATA);
Oscil<SIN2048_NUM_CELLS, AUDIO_RATE> aSin2(SIN2048_DATA);
LowPassFilter lpf;
// for sequencing gain
EventDelay kGainChangeDelay;
char gain = 1;
const int buttonPins[] = {2, 3, 4, 5, 6, 7, 8}; // Button pins for notes C4 to B4
const int numButtons = 7;
const int notes[] = {261, 293, 329, 349, 392, 440, 493}; // Frequencies for C4 to B4
int pressedNotes[7]; // Array to store indices of pressed notes
int numPressedNotes = 0; // Number of pressed notes
int currentNoteIndex = 0; // Index of the current note being played
unsigned long lastUpdateTime = 0; // Last time the note was updated
const int OSC_ONE_PIN = A2; // pitch
const int OSC_TWO_PIN = A3; // phase
const int MODA_PIN = A0; // rate
const int MODB_PIN = A1; // legato
const int MODC_PIN = A4; // filter
// division constants
// We're doing some common division operations ahead of time so that we can avoid division in the main loops
// which will slow things down when Mozzi is trying to do its thing. Doing the division here allows us to use
// multiplication in the main loop which is much faster
const float DIV1023 = 1.0 / 1023.0; // division constant for pot value range
// global control params
int OSC_ONE_OFFSET = 36; // amount to offset the original midi note (12 is one octave)
int OSC_TWO_OFFSET = 2; // amount to offset the second midi note from the osc one's midi note (5 is a fifth, two is a second, etc)
int ARP_RATE_MIN = 32; // minimum arpeggiator rate (in millisecs)
int ARP_RATE_MAX = 1024; // maximum arpeggiator rate (in millisecs)
int LEGATO_MIN = 32; // minimum note length (capping at 32 to avoid rollover artifacts)
int LEGATO_MAX = 1024; // maximum note length
int LPF_CUTOFF_MIN = 10; // low pass filter min cutoff frequency
int LPF_CUTOFF_MAX = 245; // low pass filter max cutoff frequency
void setup() {
for (int i = 0; i < numButtons; i++) {
pinMode(buttonPins[i], INPUT_PULLUP); // Set button pins as input with internal pull-up resistors
//IS IT BETTER A PHYSICAL RESISTOR??
}
// Initialize Mozzi objects
startMozzi(CONTROL_RATE);
aSin1.setFreq(48);
aSin2.setFreq(51);
lpf.setResonance(128u);
kGainChangeDelay.set(1000);
// Serial.begin(9600); // Serial output can cause audio artifacting so this is commented out by default. Can be uncommented for debugging purposes.
}
void updateControl() {
// Read pot values
int oscOne_val = mozziAnalogRead(OSC_ONE_PIN);
int oscTwo_val = mozziAnalogRead(OSC_TWO_PIN);
int modA_val = mozziAnalogRead(MODA_PIN);
int modB_val = mozziAnalogRead(MODB_PIN);
int modC_val = mozziAnalogRead(MODC_PIN);
// Map pot values
float oscOne_offset = OSC_ONE_OFFSET * (oscOne_val * DIV1023 - 0.5); // Offset by semitones
float oscTwo_offset = (oscTwo_val * DIV1023) * OSC_TWO_OFFSET; // Detune by semitones
float modA_freq = ARP_RATE_MIN + (ARP_RATE_MAX - ARP_RATE_MIN) * (1 - (modA_val * DIV1023)); // Arpeggiator rate from 32 milliseconds to ~= 1 second
float modB_duration = LEGATO_MIN + (LEGATO_MAX - LEGATO_MIN) * (1 - (modB_val * DIV1023)); // Legato from 32 milliseconds to 1 second
int modC_freq = LPF_CUTOFF_MIN + (LPF_CUTOFF_MAX - LPF_CUTOFF_MIN) * (modC_val * DIV1023); // Low-pass filter cutoff freq ~=100Hz-8kHz
// Set low pass filter cutoff frequency
lpf.setCutoffFreq(modC_freq);
// Check which buttons are pressed
numPressedNotes = 0;
for (int i = 0; i < numButtons; i++) {
if (digitalRead(buttonPins[i]) == LOW) { // Button pressed (active low)
pressedNotes[numPressedNotes] = notes[i]; // Store the note frequency
numPressedNotes++;
}
}
// Using an EventDelay to cycle through the sequence and play each note
kGainChangeDelay.set(modA_freq); // Set the delay frequency
if (numPressedNotes > 0 && kGainChangeDelay.ready()) {
unsigned long currentTime = mozziMicros();
if (currentTime - lastUpdateTime >= modA_freq) {
lastUpdateTime = currentTime;
// Set the frequency of the oscillators based on the current note
float currentFreq = pressedNotes[currentNoteIndex] * pow(2.0f, oscOne_offset / 12.0f);
aSin1.setFreq(static_cast<float>(currentFreq));
aSin2.setFreq(static_cast<float>(currentFreq * pow(2.0f, oscTwo_offset / 12.0f)));
currentNoteIndex = (currentNoteIndex + 1) % numPressedNotes;
}
} else {
// Stop the oscillators if no notes are pressed
aSin1.setFreq(0);
aSin2.setFreq(0);
}
}
int updateAudio() {
// Calculating the output audio signal as follows:
// 1. Summing the waveforms from the two oscillators
// 2. Shifting their bits by 2 to keep them in a valid output range
// 3. Multiplying the combined waveform by the gain (volume)
// 4. Passing the signal through the low pass filter
return lpf.next(((aSin1.next() + aSin2.next()) >> 2) * gain);
}
void loop() {
audioHook();
}