Skip to content
Snippets Groups Projects
Select Git revision
  • 499233bbcf4d1eaf1c90501eddfbadb0386bb6fd
  • main default protected
  • 1.1.0
  • 1.0
4 results

daisy-looper.ino

Blame
  • daisy-looper.ino 11.04 KiB
    #include "DaisyDuino.h"
    #include <Wire.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SH110X.h>
    #include <MIDI.h>
    
    #include "leds.h"
    #include "potentiometers.h"
    #include "buttons.h"
    #include "looper.h"
    #include "env_follower.h"
    #include "helpers.h"
    #include "luts.h"
    #include "ui.h"
    
    MIDI_CREATE_DEFAULT_INSTANCE();
    #define BUFFER_LENGTH_SECONDS 5
    
    #define DEBUGMODE
    
    static const size_t buffer_length = 48000 * BUFFER_LENGTH_SECONDS;
    static float DSY_SDRAM_BSS buffer[buffer_length];
    static float DSY_SDRAM_BSS buffer_b[buffer_length];
    static float DSY_SDRAM_BSS buffer_c[buffer_length];
    static float DSY_SDRAM_BSS buffer_d[buffer_length];
    static float DSY_SDRAM_BSS buffer_e[buffer_length];
    
    
    // Create instances of audio stuff
    atoav::Looper looper_a, looper_b, looper_c, looper_d, looper_e;
    static atoav::EnvelopeFollower input_envelope_follower;
    DelayLine<float, 24000> delayline;
    DSY_SDRAM_BSS ReverbSc reverb;
    static Compressor compressor;
    Oscillator lfo;
    static SampleHold sample_and_hold;
    static WhiteNoise noise;
    static Metro tick;
    static Easer easer;
    
    // Initialize Buttons  
    Button button_1 = Button(D7);
    Button button_2 = Button(D8);
    Button button_3 = Button(D9);
    Button button_4 = Button(D10);
    Button button_5 = Button(D13);
    Button button_6 = Button(D27);
    
    // Initialize Potentiometers
    Potentiometer pot_1 = Potentiometer(A0);
    Potentiometer pot_2 = Potentiometer(A1);
    Potentiometer pot_3 = Potentiometer(A3);
    Potentiometer pot_4 = Potentiometer(A2);
    Potentiometer pot_5 = Potentiometer(A4);
    Potentiometer pot_6 = Potentiometer(A5);
    Potentiometer pot_7 = Potentiometer(A6);
    
    // RGB LED               R    G    B
    RGBLed rgb_led = RGBLed(A10, A11, A11);
    
    
    // OLED Display
    #define SCREEN_WIDTH 128 // OLED display width, in pixels
    #define SCREEN_HEIGHT 64 // OLED display height, in pixels
    #define OLED_RESET -1   //   QT-PY / XIAO
    Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
    
    // User Interface
    Ui ui;
    
    // Daisy
    DaisyHardware hw;
    
    // Variables for the Audio-Callback
    size_t num_channels;
    float blocksize;
    float drywetmix = 0.0f;
    float delaymix = 0.0f;
    float delaytime = 100.0f;
    float reverbmix = 0.0f;
    float lfo_amount = 0.0f;
    float pitch_val = 0.5f;
    float midi_pitch_offset = 0.0f;
    
    float pressure = 0.0f;
    
    // Actual audio-processing is orchestrated here
    void AudioCallback(float **in, float **out, size_t size) {
      float output = 0.0f;
      float no_delay = 0.0f;
      float wet_delay;
      float out1, out2;
      // set the delay
      delayline.SetDelay(delaytime);
      
      // Iterate through the samples in the buffer
      for (size_t i = 0; i < size; i++) {
        uint8_t trig = tick.Process();
        float lfo_value = lfo.Process();
        float noise_value = noise.Process();
        float rand = easer.Process(
          sample_and_hold.Process(
            trig, 
            noise_value * 5.0f, 
            sample_and_hold.MODE_SAMPLE_HOLD
        ));
        if (ui.rec_source == REC_SOURCE_NOISE) {
         ui.activeLooper()->Record(noise_value * 0.7f);
        }
        // When the metro ticks, trigger the envelope to start.
        float random_amount = (lfo_amount -0.5f) * 2.0;
        if (trig) {
          // tick.SetFreq(rand / (0.1 + random_amount*1000.0f) + 1);
              // If the dial is over 50% jump instead
          if (lfo_amount > 0.5f) {
            ui.activeLooper()->addToPlayhead(rand * random_amount* 48000.0f);
            ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset);
          }
        }
    
        // Add the LFO to the 
        if (lfo_amount > 0.5f) {
          ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset);
        } else {
          ui.activeLooper()->setPlaybackSpeed(pitch_val + lfo_value * lfo_amount + midi_pitch_offset);
        }
    
        float looper_out;
        
        // res.SetDamping(0.1+delaymix*0.2f);
        // Record into the buffer
        if (ui.rec_source == REC_SOURCE_PRE) {
         ui.activeLooper()->Record(in[1][i]);
        }
        
        // Process the input envelope
        input_envelope_follower.Process(in[1][i]);
    
        // Process the Looper
        switch (ui.buffer_summing_mode) {
          case BUFFER_SUM_MODE_SOLO:
            // Only play active looper
            looper_out = saturate(ui.activeLooper()->Process());
            break;
          case BUFFER_SUM_MODE_SUM:
            // Sum all loopers
            looper_out = looper_a.Process();
            looper_out += looper_b.Process();
            looper_out += looper_c.Process();
            looper_out += looper_d.Process();
            looper_out += looper_e.Process();
            looper_out = saturate(looper_out);
            break;
          case BUFFER_SUM_MODE_RING:
            // Sum all loopers and ringmodulate with input
            looper_out = looper_a.Process();
            looper_out += looper_b.Process();
            looper_out += looper_c.Process();
            looper_out += looper_d.Process();
            looper_out += looper_e.Process();
            float deadbanded_input;
            if (in[1][i] < 0.0f) {
              deadbanded_input = min(0.0f, in[1][i] + 0.05f);
            } else {
              deadbanded_input = max(0.0f, in[1][i] - 0.05f);
            }
            looper_out *= deadbanded_input*2.0f;
            looper_out = saturate(looper_out);
            break;
        }
    
        // looper_out = pressure * looper_out;
    
        // Mix the dry/Wet of the looper
        output = drywetmix * looper_out + in[1][i] * (1.0f - drywetmix);
        
        // output = output * (1.0f - resmix) + res.Process(output*resmix);
        no_delay = output;
        wet_delay = delayline.Read();
        delayline.Write((wet_delay * delaymix * 0.95f)/2.0f + (no_delay*delaymix)/2.0f);
    
        // Add the delay
        output += wet_delay * delaymix;
    
        // Compress the signal
        compressor.Process(output);
    
        // Process reverb
        reverb.Process(output, output, &out1, &out2);
    
        // Mix reverb with the dry signal depending on the amount dialed
        output = output * (1.0f - reverbmix) + out1 * reverbmix;
    
        // Record the output if needed
        if (ui.rec_source == REC_SOURCE_OUT) {
         ui.activeLooper()->Record(output);
        }
        
        out[0][i] = out[1][i] = output;
      }
    }
    
    // TODO: Add Voice Stealing/Multiple Playheads?
    // TODO: Is USB Midi possible? https://github.com/electro-smith/DaisyExamples/blob/master/seed/USB_MIDI/USB_MIDI.cpp
    
    void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) {
      #ifdef DEBUGMODE
      Serial.print("[MIDI ON] chn<");
      Serial.print((int) inChannel);
      Serial.print("> note<");
      Serial.print((int) inNote);
      Serial.print("> velocity<");
      Serial.print((int) inVelocity);
      Serial.println(">");
      #endif
      // Note Off can come in as Note On w/ 0 Velocity
      if (inVelocity == 0.0f) {
        midi_pitch_offset = 0.0f;
      } 
      else {
        midi_pitch_offset = (int(inNote)-36.0)/12.0f;
      }
    }
    
    
    
    void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) {
      #ifdef DEBUGMODE
      Serial.print("[MIDI OFF] chn<");
      Serial.print((int) inChannel);
      Serial.print("> note<");
      Serial.print((int) inNote);
      Serial.print("> velocity<");
      Serial.print((int) inVelocity);
      Serial.println(">");
      #endif
      midi_pitch_offset = 0.0f;
    }
    
    void handleAftertouch(byte channel, byte channel_pressure) {
      #ifdef DEBUGMODE
      Serial.print("[MIDI AFTER] chn<");
      Serial.print((int) channel);
      Serial.print("> pressure<");
      Serial.print((int) channel_pressure);
      Serial.println(">");
      #endif
      pressure = float(channel_pressure)/127.0f;
    }
    
    
    
    void setup() {
      float sample_rate;
      // Initialize for Daisy pod at 48kHz
      hw = DAISY.init(DAISY_SEED, AUDIO_SR_48K);
      DAISY.SetAudioBlockSize(64);
      num_channels = hw.num_channels;
      sample_rate = DAISY.get_samplerate();
      blocksize = 64.0f;
    
      // Create a Tick and a noise source for the Sample and Hold
      tick.Init(10, sample_rate);
      noise.Init();
      
      // Initialize Looper with the buffer
      looper_a.Init(buffer, buffer_length);
      looper_b.Init(buffer_b, buffer_length);
      looper_c.Init(buffer_c, buffer_length);
      looper_d.Init(buffer_d, buffer_length);
      looper_e.Init(buffer_e, buffer_length);
    
      // Initialize Envelope Follower for the Level LED
      input_envelope_follower.Init(sample_rate);
      input_envelope_follower.SetAttack(100.0);
      input_envelope_follower.SetDecay(1000.0);
    
      // Initialize Reverb
      reverb.Init(sample_rate);
      reverb.SetFeedback(0.95f);
      reverb.SetLpFreq(18000.0f);
    
      // Initialize Compressor
      compressor.SetThreshold(-64.0f);
      compressor.SetRatio(2.0f);
      compressor.SetAttack(0.005f);
      compressor.SetRelease(0.1250);
    
      // Initialize the LFO for modulations
      lfo.Init(sample_rate);
      lfo.SetWaveform(Oscillator::WAVE_TRI);
      lfo.SetAmp(1);
      lfo.SetFreq(8.0);
    
      // Initialize the Delay at a length of 1 second
      delayline.Init();
      delayline.SetDelay(sample_rate);
    
      // Easer for the random jumps
      easer.setFactor(0.001);
    
      // Start serial communications
      Serial.begin(250000);
    
      // Initialize Display
      display.begin(0x3C, true);
      delay(50);
      ui.Render();
      
      // Initialize the LED
      rgb_led.init();
    
      // Set the analog read and write resolution to 12 bits
      analogReadResolution(12);
    
      // Setup MIDI handlers
      MIDI.setHandleNoteOn(handleNoteOn);
      MIDI.setHandleNoteOff(handleNoteOff);
      MIDI.setHandleAfterTouchChannel(handleAftertouch);
      
      MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages
    
      // Set Knob names and display functions
      pot_1.name = "Start";
      pot_2.name = "Length";
      pot_3.setDisplayMode("Pitch", 1000.0f, POT_DISPLAY_MODE_PERCENT);
      pot_4.setDisplayMode("Mix", 100.0f, POT_DISPLAY_MODE_PERCENT);
      pot_5.setDisplayMode("LFO", 100.0f, POT_DISPLAY_MODE_PERCENT);
      pot_6.setDisplayMode("Delay", 100.0f, POT_DISPLAY_MODE_PERCENT);
      pot_7.setDisplayMode("Reverb", 100.0f, POT_DISPLAY_MODE_PERCENT);
    
      // Set Knob Scaling Modes
      // pot_3.setBipolar();
      pot_3.setPitch();
    
      // Initialize Buttons (callbacks are assigned in the Ui class)
      button_1.init();
      button_2.init();
      button_3.init();
      button_4.init();
      button_5.init();
      button_6.init();
    
      // Start the audio Callback
      DAISY.begin(AudioCallback);
    }
    
    void loop() {
      // Read the values from the potentiometers
      float p1 = pot_1.read();
      float p2 = pot_2.read();
      float p3 = pot_3.read();
      float p4 = pot_4.read();
      float p5 = pot_5.read();
      float p6 = pot_6.read();
      float p7 = pot_7.read();
    
      // Update the UI
      ui.update();
    
      // Read the buttons
      button_1.read();
      button_2.read();
      button_3.read();
      button_4.read();
      button_5.read();
      button_6.read();
    
      // Set loop-start and loop-length with the potentiometers
      ui.setLoop(p1, p2);
      
      // Tune the pitch to the ten-fold of the potentiometer 3,
      // a bipolar pot, so it returns values from -1.0 to +1.0
      // Pitch should go 10 octaves up/down (10*1.0 = 1000%)
      if (!isnan(p3)) {pitch_val = 10.0f * p3; }
    
      // Set other parameters (from 0.0 to 1.0)
      if (!isnan(p4)) { drywetmix = p4; }
      if (!isnan(p5)) { delaymix = p5; }
      // Delaytime is in samples
      if (!isnan(p5)) {  lfo_amount = p5; }
      if (!isnan(p6)) {  delaytime = 100.0f + p6 * 23900.0f; }
      if (!isnan(p7)) {  reverbmix = p7; Serial.print("Reverb was not NaN: "); Serial.println(reverbmix); }
    
      // Render the UI (frame rate limited by UI_MAX_FPS in ui.h)
      // double start = millis();
      ui.Render();
      // Serial.print("ui Render took ");
      // Serial.print(millis()-start);
      // Serial.println("ms");
    
      // Set the Color and brightness of the RGB LED in 8 bits
      rgb_led.setAudioLevelIndicator(input_envelope_follower.getValue());
    
      // MIDI
      MIDI.read();
    }