#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; // --- 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); 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 } } const int BATCH_SIZE = 16; // Ensure we don't generate samples faster than the I2S can consume them. // We write 2 samples (Left + Right) for every 1 synth sample. if (i2s.availableForWrite() < BATCH_SIZE * 2) { return; } // Process a small batch of samples static int16_t samples[BATCH_SIZE]; // Generate sound samples if (globalSynth) { // using synth engine globalSynth->process(samples, BATCH_SIZE); } else { // using fallback sawtooth for (int i = 0; i < BATCH_SIZE; ++i) { if (currentFrequency > 0) { //samples[i] = (int16_t)(sin(phase) * AMPLITUDE); phase += 2.0 * M_PI * currentFrequency / SAMPLE_RATE; if (phase >= 2.0 * M_PI) phase -= 2.0 * M_PI; samples[i] = phase * 0.1f * AMPLITUDE; } else { samples[i] = 0; } } } // write out stereo samples for (int i = 0; i < BATCH_SIZE; ++i) { i2s.write(samples[i]); i2s.write(samples[i]); } }