#include "AudioThread.h" #include "SharedState.h" // --- MIDI --- // Create a MIDI object listening on Serial1 (GP0/GP1) MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); // --- Forward Declarations --- void fill_audio_buffer(int16_t* buffer, size_t buffer_size); void handleNoteOn(byte channel, byte note, byte velocity); void handleNoteOff(byte channel, byte note, byte velocity); #ifdef TEST_OUT // C Natural Minor Scale notes (C3 to C5) for testing const byte c_minor_scale_notes[] = { 48, 50, 51, 53, 55, 56, 58, // C3 octave 60, 62, 63, 65, 67, 68, 70, // C4 octave 72 // C5 }; #endif void setupAudio() { #ifndef TEST_OUT // --- Initialize MIDI --- // The optocoupler circuit inverts the signal, so we must enable inverse logic. Serial1.setRX(MIDI_RX_PIN); Serial1.setTX(0); // Not using TX Serial1.begin(31250); Serial1.setRXInverse(true); MIDI.setHandleNoteOn(handleNoteOn); MIDI.setHandleNoteOff(handleNoteOff); MIDI.begin(MIDI_CHANNEL_OMNI); Serial.println("MIDI Initialized."); #else Serial.println("TEST_OUT mode enabled. Playing random C minor notes."); // Seed random from noise on the volume pot ADC pin randomSeed(analogRead(VOL_POT_PIN)); #endif // --- Initialize I2S Audio --- i2s.setBCLK(I2S_BCLK_PIN); i2s.setLRCK(I2S_LRCK_PIN); i2s.setDATA(I2S_DATA_PIN); // Set the audio callback function i2s.setBufferCallback(fill_audio_buffer); if (!i2s.begin(I2S_STEREO, SAMPLE_RATE, BITS_PER_SAMPLE)) { Serial.println("Failed to initialize I2S!"); while (1); // Stop forever } Serial.println("I2S Initialized."); } void loopAudio() { #ifdef TEST_OUT static uint32_t last_note_event = 0; static bool is_playing_test_note = false; const uint16_t note_duration = 250; // ms const uint16_t note_gap = 50; // ms // Check if it's time to turn off the current note if (is_playing_test_note && (millis() - last_note_event > note_duration)) { handleNoteOff(1, 0, 0); // Turn note off is_playing_test_note = false; last_note_event = millis(); // Reset timer for the gap } // Check if it's time to play a new note if (!is_playing_test_note && (millis() - last_note_event > note_gap)) { // Pick a random note from the scale int note_index = random(sizeof(c_minor_scale_notes) / sizeof(c_minor_scale_notes[0])); byte midi_note = c_minor_scale_notes[note_index]; // Call NoteOn to set frequency and state handleNoteOn(1, midi_note, 127); // Channel and velocity don't matter here is_playing_test_note = true; last_note_event = millis(); // Reset timer for the duration } #else // Listen for incoming MIDI messages MIDI.read(); #endif } // --- Audio Generation Callback --- // This function is called by the I2S library on the second core (by default) // to fill the audio buffer. It must be fast and should not do any allocations. void fill_audio_buffer(int16_t* buffer, size_t buffer_size) { if (!g_note_on || g_note_frequency <= 0.0) { // If no note is playing, fill the buffer with silence. memset(buffer, 0, buffer_size * sizeof(int16_t)); return; } // Calculate how much to increment the phase for each sample to get the desired frequency. float phase_increment = (2.0 * PI * g_note_frequency) / SAMPLE_RATE; // The maximum amplitude for a 16-bit signed integer. const int16_t max_amplitude = 32767; for (size_t i = 0; i < buffer_size; i += 2) { // Process in stereo pairs // Generate a sawtooth wave sample (-1.0 to 1.0) float sample = (g_phase / PI) - 1.0; // Increment and wrap the phase g_phase += phase_increment; if (g_phase >= 2.0 * PI) { g_phase -= 2.0 * PI; } // Apply volume and scale to 16-bit integer range int16_t final_sample = static_cast(sample * max_amplitude * g_volume); // Write the same sample to both left and right channels buffer[i] = final_sample; // Left channel buffer[i + 1] = final_sample; // Right channel } } // --- MIDI Callback Functions --- void handleNoteOn(byte channel, byte note, byte velocity) { // Convert MIDI note number to frequency g_note_frequency = 440.0 * pow(2.0, (note - 69.0) / 12.0); g_note_on = true; g_phase = 0.0; // Reset phase for a clean attack } void handleNoteOff(byte channel, byte note, byte velocity) { g_note_on = false; g_note_frequency = 0.0; }