Untitled
#include <Audio.h> #include <Wire.h> #include <SPI.h> #include <SerialFlash.h> #include <MIDI.h> #include <vector> #define TOTAL_CHANNELS 5 // Number of waveform types #define VOICES_PER_CHANNEL 3 // Number of voices per waveform #define TOTAL_VOICES (TOTAL_CHANNELS * VOICES_PER_CHANNEL) // Total voices // Audio objects AudioSynthWaveform waveform[TOTAL_VOICES]; // Waveform generators AudioEffectEnvelope envelope[TOTAL_VOICES]; // Envelopes for waveforms AudioMixer4 mixers[TOTAL_CHANNELS]; // Mixer per waveform AudioMixer4 mainMixer; // Main mixer AudioOutputI2S i2s1; // Audio output AudioConnection* patchCords[TOTAL_VOICES * 2 + TOTAL_CHANNELS]; // Dynamic patch cords AudioConnection patchCordOutL(mainMixer, 0, i2s1, 0); AudioConnection patchCordOutR(mainMixer, 0, i2s1, 1); AudioControlSGTL5000 audioShield; // Waveform types for each channel const char* waveformNames[TOTAL_CHANNELS] = {"SINE", "SQUARE", "SAWTOOTH", "TRIANGLE", "PULSE"}; const int waveformTypes[TOTAL_CHANNELS] = { WAVEFORM_SINE, WAVEFORM_SQUARE, WAVEFORM_SAWTOOTH, WAVEFORM_TRIANGLE, WAVEFORM_PULSE}; // Per-channel envelope settings (Attack, Decay, Sustain, Release) const float envelopeSettings[TOTAL_CHANNELS][4] = { {10.0, 50.0, 0.8, 100.0}, // Sine {20.0, 40.0, 0.6, 200.0}, // Square {5.0, 30.0, 0.7, 50.0}, // Sawtooth {15.0, 60.0, 0.5, 150.0}, // Triangle {10.0, 20.0, 0.9, 80.0} // Pulse }; // Tracks active notes for each voice struct Voice { bool active; int channel; int note; int velocity; }; Voice voices[TOTAL_VOICES]; float pwmValue = 0.5; // Default PWM value for pulse waveform (50% duty cycle) void setup() { // Set up MIDI callbacks usbMIDI.setHandleNoteOn(handleNoteOnUSB); usbMIDI.setHandleNoteOff(handleNoteOffUSB); usbMIDI.setHandleControlChange(handleControlChangeUSB); usbMIDI.setHandleStop(handleStopEvent); // Audio setup AudioMemory(80); audioShield.enable(); audioShield.volume(0.8); // Configure the audio shield for line-level output audioShield.lineOutLevel(13); // Set line-out level to maximum int patchCordIndex = 0; // Initialize voices and assign them to mixers for (int i = 0; i < TOTAL_VOICES; i++) { int channel = i / VOICES_PER_CHANNEL; waveform[i].begin(0.5, 440, waveformTypes[channel]); // Apply per-channel envelope settings envelope[i].attack(envelopeSettings[channel][0]); envelope[i].decay(envelopeSettings[channel][1]); envelope[i].sustain(envelopeSettings[channel][2]); envelope[i].release(envelopeSettings[channel][3]); // Set default PWM for pulse waveform (channel 5) if (waveformTypes[channel] == WAVEFORM_PULSE) { waveform[i].pulseWidth(pwmValue); } patchCords[patchCordIndex++] = new AudioConnection(waveform[i], envelope[i]); patchCords[patchCordIndex++] = new AudioConnection(envelope[i], 0, mixers[channel], i % VOICES_PER_CHANNEL); voices[i] = {false, channel, -1, 0}; // Initialize voice state } // Connect mixers to the main mixer for (int i = 0; i < TOTAL_CHANNELS; i++) { patchCords[patchCordIndex++] = new AudioConnection(mixers[i], 0, mainMixer, i); mixers[i].gain(0, 0.8); // Set default gain } // Debug Serial.begin(9600); Serial.println("MIDI Synth Ready with PWM Modulation for Channel 5!"); } void loop() { // Process incoming MIDI messages usbMIDI.read(); } void handleNoteOnUSB(byte channel, byte note, byte velocity) { if (channel < 1 || channel > TOTAL_CHANNELS) { Serial.printf("Invalid MIDI Channel: %d\n", channel); return; // Ignore invalid channels } int channelIndex = channel - 1; // Find a free voice for this channel for (int i = channelIndex * VOICES_PER_CHANNEL; i < (channelIndex + 1) * VOICES_PER_CHANNEL; i++) { if (!voices[i].active) { voices[i].active = true; voices[i].note = note; voices[i].velocity = velocity; float frequency = pow(2, (note - 69) / 12.0) * 440.0; // MIDI Note to Hz float amplitude = velocity / 127.0 * 0.5; // Scale amplitude based on velocity waveform[i].frequency(frequency); waveform[i].amplitude(amplitude); // Apply PWM for pulse waveform if (waveformTypes[channelIndex] == WAVEFORM_PULSE) { waveform[i].pulseWidth(pwmValue); } envelope[i].noteOn(); Serial.printf("Channel %d: Voice %d Note On: Note=%d, Freq=%.2f Hz, Vel=%d, Waveform=%s, PWM=%.2f\n", channel, i, note, frequency, velocity, waveformNames[channelIndex], pwmValue); return; } } Serial.printf("Channel %d: No free voices available!\n", channel); } void handleNoteOffUSB(byte channel, byte note, byte velocity) { if (channel < 1 || channel > TOTAL_CHANNELS) { Serial.printf("Invalid MIDI Channel: %d\n", channel); return; // Ignore invalid channels } int channelIndex = channel - 1; // Find the voice playing this note for (int i = channelIndex * VOICES_PER_CHANNEL; i < (channelIndex + 1) * VOICES_PER_CHANNEL; i++) { if (voices[i].active && voices[i].note == note) { voices[i].active = false; envelope[i].noteOff(); Serial.printf("Channel %d: Voice %d Note Off: Note=%d, Waveform=%s\n", channel, i, note, waveformNames[channelIndex]); return; } } } void handleControlChangeUSB(byte channel, byte control, byte value) { if (channel != 5 || control != 1) return; // Only apply mod wheel (CC1) to channel 5 // Scale MIDI value (0–127) to PWM range (0.05–0.95) pwmValue = map(value, 0, 127, 5, 95) / 100.0; // Apply the new PWM value to all pulse voices for (int i = (TOTAL_CHANNELS - 1) * VOICES_PER_CHANNEL; i < TOTAL_VOICES; i++) { waveform[i].pulseWidth(pwmValue); } Serial.printf("Mod Wheel (Channel 5): PWM Updated to %.2f (MIDI Value: %d)\n", pwmValue, value); } void handleStopEvent() { Serial.println("MIDI Stop Event Received: Killing all notes..."); killAllNotes(); } void killAllNotes() { for (int i = 0; i < TOTAL_VOICES; i++) { if (voices[i].active) { voices[i].active = false; envelope[i].noteOff(); } } Serial.println("All voices stopped."); }
Leave a Comment