Skip to content
Snippets Groups Projects
Select Git revision
  • 82d9a25580496546cc182484d177c29a6d057b4b
  • main default protected
2 results

RP2040-VCO-bom.xlsx

Blame
  • looper.h 11.04 KiB
    #pragma once
    
    #include "luts.h"
    
    namespace atoav {
    
    enum RecPitchMode {
      REC_PITCH_MODE_NORMAL,
      REC_PITCH_MODE_UNPITCHED,
      REC_PITCH_MODE_LAST,
    };
    
    enum RecStartMode {
      REC_START_MODE_BUFFER,
      REC_START_MODE_LOOP,
      REC_START_MODE_PLAYHEAD,
      REC_START_MODE_LAST,
    };
    
    enum PlaybackState {
      PLAYBACK_STATE_STOPPED,
      PLAYBACK_STATE_LOOP,
      PLAYBACK_STATE_MULTILOOP,
      PLAYBACK_STATE_MIDI,
      PLAYBACK_STATE_LAST,
    };
    
    enum RecordingState {
      REC_STATE_EMPTY,
      REC_STATE_RECORDING,
      REC_STATE_OVERDUBBING,
      REC_STATE_ERASING,
      REC_STATE_NONE,
      REC_STATE_LAST,
    };
    
    // =================================================
    // =                   H E A D                     =
    // =================================================
    
    class Head {
      public:
        Head();
        void activate();
        void deactivate();
        bool isActive();
        void setPosition(float value);
        void reset();
        void setIncrement(float value);
        void incrementBy(float value);
        void slowDown();
        void reverse();
        void speedUp();
        void update();
        float read();
        float increment = 1.0f;
        float variation = 0.0f;
        float variation_amount = 0.0f;
        float speed_multiplier = 1.0f;
      private:
        bool active = true;
        float position = 0.0f;
    };
    
    Head::Head() {
      variation = random(-0.1f, 0.1f);
    }
    void Head::activate() {
      this->active = true;
    }
    void Head::deactivate() {
      this->active = false;
    }
    bool Head::isActive() {
      return active;
    }
    void Head::reset() {
      this->position = 0.0f;
    }
    void Head::setPosition(float value) {
      this->position = value * speed_multiplier;
    }
    void Head::setIncrement(float value) {
      this->increment = value;
    }
    void Head::incrementBy(float value) {
      this->position += value;
    }
    void Head::slowDown() {
      this->speed_multiplier = max(0.0f, this->speed_multiplier - 0.0025f);
    }
    void Head::reverse() {
      this->speed_multiplier = -abs(this->speed_multiplier );
    }
    void Head::speedUp() {
      this->speed_multiplier = 1.0f;
    }
    void Head::update() {
      this->position += (this->increment + (variation * variation_amount)) * speed_multiplier;
    }
    float Head::read() {
      return this->position;
    }
    
    
    // =================================================
    // =                 L O O P E R                   =
    // =================================================
    
    class Looper {
    	public:
    		Looper();
        void Init(float *buf, size_t buf_size);
        void SetRecord();
        void SetOverdub();
        void SetErase();
        void SetStopWriting();
        bool isWriting();
        void ResetRecHead();
        void SetLoop(float loop_start_time, float loop_length_time);
        void Record(float in);
        float Process();
        float* getBuffer();
        size_t getBufferLength();
        void setRecPitchMode(RecPitchMode mode);
        void setRecStartMode(RecStartMode mode);
        float GetPlayhead();
        float* GetPlayheads();
        uint8_t GetPlayheadCount();
        float GetRecHead();
        bool toggleRecMode();
        void setRecModeFull();
        void setRecModeLoop();
        void setRecModeFullShot();
        void setRecModeLoopShot();
        void setPlaybackSpeed(float increment);
        void addToPlayhead(float value);
        void slowDown();
        void reverse();
        void restart();
        void speedUp();
        float loop_start_f = 0.0f;
        float loop_length_f = 1.0f;
        uint8_t grain_count = 8;
        float grain_spread = 2.0f;
        float grain_variation = 0.0f;
        RecPitchMode rec_pitch_mode = REC_PITCH_MODE_NORMAL;
        RecStartMode rec_start_mode = REC_START_MODE_BUFFER;
        PlaybackState playback_state = PLAYBACK_STATE_LOOP;
        RecordingState recording_state = REC_STATE_EMPTY;
    
      private:
        static const size_t kFadeLength = 200;
        static const size_t kMinLoopLength = 2 * kFadeLength;
    
        float* buffer;
        size_t buffer_length = 0;
    
        Head playheads[9];
        Head rec_head;
    
        size_t loop_start = 0;
        size_t loop_length = 48000;
    
        bool stop_after_recording = false;
        bool stay_within_loop = true;
    
    };
    
    
    Looper::Looper() {}
    
    void Looper::Init(float *buf, size_t buf_size) {
      buffer = buf;
      buffer_length = buf_size;
      memset(buffer, 0, sizeof(float) * buffer_length);
    }
    
    void Looper::SetRecord() {
      recording_state = REC_STATE_RECORDING;
      ResetRecHead();
      rec_head.activate();
    }
    
    void Looper::SetOverdub() {
      recording_state = REC_STATE_OVERDUBBING;
      ResetRecHead();
      rec_head.activate();
    }
    
    void Looper::SetErase() {
      recording_state = REC_STATE_ERASING;
      ResetRecHead();
      rec_head.activate();
    }
    
    void Looper::SetStopWriting() {
      recording_state = REC_STATE_NONE;
      rec_head.deactivate();
    }
    
    bool Looper::isWriting() {
      return recording_state == REC_STATE_RECORDING 
        || recording_state == REC_STATE_OVERDUBBING 
        || recording_state == REC_STATE_ERASING; 
    }
    
    void Looper::ResetRecHead() {
      if (isWriting()) {
        switch (rec_start_mode) {
          case REC_START_MODE_LOOP:
            rec_head.setPosition(loop_start % buffer_length);
            break;
          case REC_START_MODE_BUFFER:
            rec_head.setPosition(0.0f);
            break;
          case REC_START_MODE_PLAYHEAD:
            rec_head.setPosition(fmod(loop_start + playheads[0].read(), float(buffer_length)));
            break;
        }
      }
    }
    
    void Looper::SetLoop(float loop_start_time, float loop_length_time) {
      if (!isnan(loop_start_time)) {
        loop_start_f = loop_start_time;
        loop_start = static_cast<size_t>(loop_start_time * (buffer_length - 1));
      }
      if (!isnan(loop_length_time)) {
        loop_length_f = loop_length_time;
        loop_length = max(kMinLoopLength, static_cast<size_t>(loop_length_time * buffer_length));
      }
    }
    
    void Looper::Record(float in) {
      // Overwrite/Add/Erase the buffer depending on the mode
      switch (recording_state) {
        case REC_STATE_RECORDING:
          buffer[int(rec_head.read())] = in;
          break;
        case REC_STATE_OVERDUBBING:
          buffer[int(rec_head.read())] += in;
          break;
        case REC_STATE_ERASING:
          buffer[int(rec_head.read())] = 0.0f;
          break;
      }
    
      // Advance the recording head if needed
      if (isWriting()) {
        // Set Recording head increment depending on the mode
        switch (rec_pitch_mode) {
          case REC_PITCH_MODE_NORMAL: 
            rec_head.setIncrement(1.0f); 
            break;
          case REC_PITCH_MODE_UNPITCHED: 
            rec_head.setIncrement(playheads[0].increment); 
            break;
        }
        // Increment recording head
        rec_head.update();
    
        // Limit the position of the rec head depending on the active mode
        if (!stop_after_recording) {
          if (!stay_within_loop) {
            // record into whole buffer
            rec_head.setPosition(fmod(rec_head.read(), float(buffer_length)));
          } else {
            // Limit rec head to stay inside the loop
            rec_head.setPosition(fmod(rec_head.read(), float(loop_start + loop_length)));
            rec_head.setPosition(max(float(loop_start), rec_head.read()));
          }
        } else {
          // Stop at end (either end of buffer or end of loop)
          if (!stay_within_loop) {
            if (rec_head.read() > buffer_length) { SetStopWriting(); }
          } else {
            if (rec_head.read() > loop_start + loop_length) { SetStopWriting(); }
          }
        }
    
        // Ensure the Rec-Head is never without bounds, even when running backwards
        if (rec_head.read() > buffer_length) {
          rec_head.setPosition(0.0f);
        } else if (rec_head.read() < 0) {
          rec_head.setPosition(buffer_length);
        }
      }
    }
    
    float Looper::Process() {
      // Early return if buffer is empty or not playing to save performance
      if (recording_state == REC_STATE_EMPTY 
        || playback_state == PLAYBACK_STATE_STOPPED) {
        return 0;
      }
    
      // Deactivate all playheads except first if playback state is Loop
      switch (playback_state) {
        case PLAYBACK_STATE_LOOP:
          playheads[0].activate();
          for (size_t i=1; i<9; i++) {
            playheads[i].deactivate();
          }
          break;
        case PLAYBACK_STATE_MULTILOOP:
          for (size_t i=0; i<9; i++) {
            playheads[i].activate();
          }
          break;
        case PLAYBACK_STATE_STOPPED:
          for (size_t i=0; i<9; i++) {
            playheads[i].deactivate();
          }
          break;
      }
    
      double mix = 0.0;
    
      for (size_t i=0; i<grain_count-1; i++) {
        // Skip inactive playheads
        if (!playheads[i].isActive()) continue;
    
        // Ensure we are actually inside the buffer
        int from_start = int(playheads[i].read()) % loop_length;
        int play_pos = loop_start + from_start;
    
        float vol = 1.0f;
        if (from_start <= kFadeLength) {
          vol = from_start / float(kFadeLength);
        }
        int from_end = abs(int(loop_length) - from_start);
        if (from_end <= kFadeLength) {
          vol = from_end / float(kFadeLength);
        }
    
        // Read from the buffer
        mix += buffer[play_pos] * vol;
    
        // Advance the playhead
        playheads[i].update();
    
        // Ensure the playhead stays within bounds of the loop
        float pos = playheads[i].read();
        if (pos >= loop_length || pos <= 0.0f) {
          playheads[i].setPosition(fmod(pos, float(loop_length)));
        }
      }
      return mix;
    }
    
    float Looper::GetPlayhead() {
      return float(int(playheads[0].read()) % loop_length) / float(buffer_length);
    }
    
    float* Looper::GetPlayheads() {
      static float playhead_positions[9];
      for (size_t i=0; i<9; i++) {
        playhead_positions[i] = float(int(playheads[i].read()) % loop_length) / float(buffer_length);
      }
      return playhead_positions;
    }
    
    uint8_t Looper::GetPlayheadCount() {
      return grain_count;
    }
    
    float Looper::GetRecHead() {
      return  float(rec_head.read()) / float(buffer_length);
    }
    
    bool Looper::toggleRecMode() {
      stay_within_loop = !stay_within_loop;
      return stay_within_loop;
    }
    
    void Looper::setRecModeFull() {
      stay_within_loop = false;
      stop_after_recording = false;
    }
    
    void Looper::setRecModeLoop() {
      stay_within_loop = true;
      stop_after_recording = false;
    }
    
    void Looper::setRecModeFullShot() {
      stay_within_loop = false;
      stop_after_recording = true;
    }
    
    void Looper::setRecModeLoopShot() {
      stay_within_loop = true;
      stop_after_recording = true;
    }
    
    void Looper::setPlaybackSpeed(float increment) {
      switch (playback_state) {
        case PLAYBACK_STATE_LOOP:
          playheads[0].setIncrement(increment);
          break;
        case PLAYBACK_STATE_MULTILOOP:
          playheads[0].setIncrement(increment);
          for (size_t i=1; i<9; i++) {
            playheads[i].variation_amount = grain_variation;
            playheads[i].setIncrement(increment + increment*grain_spread);
          }
          break;
      }
    }
    
    void Looper::addToPlayhead(float value) {
      switch (playback_state) {
        case PLAYBACK_STATE_LOOP:
          playheads[0].incrementBy(value);
          break;
        case PLAYBACK_STATE_MULTILOOP:
          playheads[0].incrementBy(value);
          for (size_t i=1; i<9; i++) {
            playheads[i].incrementBy(value + value/(1+i));
          }
          break;
      }
    }
    
    void Looper::slowDown() {
      for (size_t i=0; i<9; i++) {
        playheads[i].slowDown();
      }
    }
    
    void Looper::reverse() {
      for (size_t i=0; i<9; i++) {
        playheads[i].reverse();
      }
    }
    
    void Looper::restart() {
      for (size_t i=0; i<9; i++) {
        playheads[i].reset();
      }
    }
    
    void Looper::speedUp() {
      for (size_t i=0; i<9; i++) {
        playheads[i].speedUp();
      }
    }
    
    float* Looper::getBuffer() {
      return buffer;
    }
    
    size_t Looper::getBufferLength() {
      return buffer_length;
    }
    
    void Looper::setRecPitchMode(RecPitchMode mode) {
      rec_pitch_mode = mode;
    }
    
    void Looper::setRecStartMode(RecStartMode mode) {
      rec_start_mode = mode;
    }
    
    
    
    }; // namespace atoav