#include #include "AudioThread.h" #include "SharedState.h" #include #include #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; }