Untitled

 avatar
unknown
c_cpp
18 days ago
6.3 kB
7
Indexable
#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