121 lines
3.4 KiB
C++
121 lines
3.4 KiB
C++
#include <mutex>
|
|
#include "AudioThread.h"
|
|
#include "SharedState.h"
|
|
#include <I2S.h>
|
|
#include <math.h>
|
|
#include "synth_engine.h"
|
|
|
|
// I2S Pin definitions
|
|
// You may need to change these to match your hardware setup (e.g., for a specific DAC).
|
|
const int I2S_BCLK_PIN = 9; // Bit Clock (GP9)
|
|
const int I2S_LRC_PIN = 10; // Left-Right Clock (GP10)
|
|
const int I2S_DOUT_PIN = 11; // Data Out (GP11)
|
|
|
|
// Audio parameters
|
|
const int SAMPLE_RATE = 44100 / 2;
|
|
const int16_t AMPLITUDE = 16383 / 2; // Use a lower amplitude to avoid clipping (max is 32767 for 16-bit)
|
|
|
|
// Create an I2S output object
|
|
I2S i2s(OUTPUT);
|
|
|
|
extern SynthEngine* globalSynth;
|
|
|
|
// --- Synthesizer State ---
|
|
float currentFrequency = 440.0f;
|
|
double phase = 0.0;
|
|
unsigned long lastNoteChangeTime = 0;
|
|
// ---
|
|
|
|
// Ring Buffer
|
|
int16_t audioBuffer[AUDIO_BUFFER_SIZE];
|
|
int audioHead = 0;
|
|
int audioTail = 0;
|
|
bool audioBuffering = true;
|
|
|
|
void setupAudio() {
|
|
// Configure I2S pins
|
|
i2s.setBCLK(I2S_BCLK_PIN);
|
|
i2s.setDATA(I2S_DOUT_PIN);
|
|
|
|
// Set the sample rate and start I2S communication
|
|
i2s.setFrequency(SAMPLE_RATE);
|
|
if (!i2s.begin()) {
|
|
Serial.println("Failed to initialize I2S!");
|
|
while (1); // Halt on error
|
|
}
|
|
|
|
// Seed the random number generator from an unconnected analog pin
|
|
randomSeed(analogRead(A0));
|
|
|
|
// Initialize the portable synth engine
|
|
globalSynth = new SynthEngine(SAMPLE_RATE);
|
|
if (globalSynth) {
|
|
globalSynth->loadPreset(2);
|
|
}
|
|
}
|
|
|
|
void loopAudio() {
|
|
unsigned long now = millis();
|
|
|
|
// Every 500ms, pick a new random note to play
|
|
if (now - lastNoteChangeTime > 500) {
|
|
lastNoteChangeTime = now;
|
|
int noteIndex = random(0, SCALES[currentScaleIndex].numNotes + 2);
|
|
|
|
bool rest = noteIndex >= SCALES[currentScaleIndex].numNotes;
|
|
if (!rest) {
|
|
// Calculate frequency based on key, scale, and octave
|
|
const float baseFrequency = 261.63f; // C4
|
|
float keyFrequency = baseFrequency * pow(2.0f, currentKeyIndex / 12.0f);
|
|
int semitoneOffset = SCALES[currentScaleIndex].semitones[noteIndex];
|
|
currentFrequency = keyFrequency * pow(2.0f, semitoneOffset / 12.0f);
|
|
} else {
|
|
currentFrequency = 0;
|
|
}
|
|
|
|
if (globalSynth) {
|
|
globalSynth->setFrequency(currentFrequency > 0 ? currentFrequency : 440.0f);
|
|
globalSynth->setGate(!rest); // Trigger envelope
|
|
}
|
|
}
|
|
|
|
// Produce samples in a cyclic buffer
|
|
int nextHead = (audioHead + 1) % AUDIO_BUFFER_SIZE;
|
|
if (nextHead != audioTail) {
|
|
if (audioHead == audioTail) {
|
|
audioBuffering = true;
|
|
}
|
|
int16_t sample = 0;
|
|
if (globalSynth) {
|
|
globalSynth->process(&sample, 1);
|
|
} else {
|
|
if (currentFrequency > 0) {
|
|
phase += 2.0 * M_PI * currentFrequency / SAMPLE_RATE;
|
|
if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI;
|
|
sample = phase * 0.1f * AMPLITUDE;
|
|
} else {
|
|
sample = 0;
|
|
}
|
|
}
|
|
audioBuffer[audioHead] = sample;
|
|
audioHead = nextHead;
|
|
} else {
|
|
audioBuffering = false;
|
|
}
|
|
|
|
// Consume samples from this buffer whenever there is capacity in i2s
|
|
while (!audioBuffering && audioHead != audioTail) {
|
|
if (i2s.availableForWrite() < 2) {
|
|
break;
|
|
}
|
|
int16_t s = audioBuffer[audioTail];
|
|
i2s.write(s);
|
|
i2s.write(s);
|
|
audioTail = (audioTail + 1) % AUDIO_BUFFER_SIZE;
|
|
}
|
|
|
|
// Update usage stats
|
|
int usage = audioHead - audioTail;
|
|
if (usage < 0) usage += AUDIO_BUFFER_SIZE;
|
|
audioBufferUsage = usage;
|
|
} |