Les Instruments

DaisyBed -- a soft landing for my adventures into Electro-Smith Daisy audio platform.

patches/awful-paraphonic-synth/src/awful-paraphonic.cppcpp · 3660B
#include "daisy_patch_sm.h"
#include "daisysp.h"

using namespace daisy;
using namespace patch_sm;
using namespace daisysp;

static const size_t NUM_VOICES = 8;

// Small Voice abstraction
struct Voice
{
  Oscillator oscillator;
  AdEnv envelope;

  void Init(float sample_rate)
  {
    oscillator.Init(sample_rate);
    oscillator.SetWaveform(oscillator.WAVE_POLYBLEP_SAW);
    oscillator.SetFreq(220);
    oscillator.SetAmp(0.0f); // amplitude will come from envelope
    envelope.Init(sample_rate);
    envelope.SetTime(ADENV_SEG_ATTACK, 0.f);
    envelope.SetTime(ADENV_SEG_DECAY, 0.35f);
    envelope.SetMin(0.0f);
    envelope.SetMax(1.f);
    envelope.SetCurve(0.f); // linear
  }

  // Trigger the envelope and set a new freq
  void Trigger(float freq)
  {
    oscillator.SetFreq(freq);
    envelope.Trigger();
  }

  float Process()
  {
    float env_sig = envelope.Process();
    // Scale oscillator amplitude by envelope.
    oscillator.SetAmp(env_sig * 0.2f);
    return oscillator.Process(); // same levels as your original
  }
};

DaisyPatchSM hw;                      // Hardware layer
static Voice voices[NUM_VOICES];      // Voices
static size_t active_voice_index = 0; // Active voice index
Svf svf;                              // Single filter on the sum of voices
Switch gate;                          // Gate input for triggering voices

// Main audio callback of the program
static void AudioCallback(AudioHandle::InputBuffer in,
                          AudioHandle::OutputBuffer out,
                          size_t size)
{

  hw.ProcessAllControls();
  gate.Debounce();

  // monitor the gate input and output it to CV_OUT_2
  if (gate.RawState())
  {
    hw.WriteCvOut(CV_OUT_2, 5.f);
  }
  else
  {
    hw.WriteCvOut(CV_OUT_2, 0.f);
  }

  // On a gate rising edge, set the frequency of the active voice
  // and then trigger it. then move to the next voice
  if (gate.RisingEdge())
  {
    float coarse_knob = hw.GetAdcValue(CV_1);
    float coarse = fmap(coarse_knob, 0.f, 96.f);
    float voct_cv = hw.GetAdcValue(CV_5); // TODO: This is not accurate v/oct conversion at all!
    float voct = fmap(voct_cv, 0.f, 60.f);
    float midi_nn = fclamp(coarse + voct, 0.f, 127.f);
    float freq = mtof(midi_nn); // Convert note to freq
    voices[active_voice_index].Trigger(freq);

    // naive Round-robin voice steal
    int next_voice = (active_voice_index + 1) % NUM_VOICES;
    active_voice_index = next_voice;
  }

  float filterCutKnob = hw.GetAdcValue(CV_3);
  float filterCutoff = fmap(filterCutKnob, 0.f, 3000.f);
  float rel_knob = hw.GetAdcValue(CV_4);
  float releaseTime = fmap(rel_knob, 0.01f, 2.f);
  float att_knob = hw.GetAdcValue(CV_2);
  float attackTime = fmap(att_knob, 0.01f, 1.f);

  // Apply envelope times to *all* voices
  for (size_t v = 0; v < NUM_VOICES; v++)
  {
    voices[v].envelope.SetTime(ADENV_SEG_ATTACK, attackTime);
    voices[v].envelope.SetTime(ADENV_SEG_DECAY, releaseTime);
  }

  // Update filter freq
  svf.SetFreq(filterCutoff);

  for (size_t i = 0; i < size; i++)
  {
    float mix = 0.f;
    // Sum all voices
    for (size_t v = 0; v < NUM_VOICES; v++)
    {
      mix += voices[v].Process();
    }

    // Process sum through the single filter
    svf.Process(mix);

    OUT_L[i] = svf.Low();
    OUT_R[i] = svf.Low();
  }
}

int main(void)
{
  hw.Init();

  // Initialize all voices
  for (size_t v = 0; v < NUM_VOICES; v++)
  {
    voices[v].Init(hw.AudioSampleRate());
  }

  svf.Init(hw.AudioSampleRate());
  svf.SetFreq(1000.f);
  svf.SetRes(0.7f);

  gate.Init(hw.B10, hw.AudioCallbackRate());

  // Start audio engine
  hw.StartAudio(AudioCallback);

  while (1)
  {
  }
}