Skip to content

File looper.h

File List > DaisySP > Source > Utility > looper.h

Go to the documentation of this file

Source Code

/*
Copyright (c) 2020 Electrosmith, Corp

Use of this source code is governed by an MIT-style
license that can be found in the LICENSE file or at
https://opensource.org/licenses/MIT.
*/

#pragma once
#include <algorithm>
#include "dsp.h"

namespace daisysp
{
class Looper
{
  public:
    Looper() {}
    ~Looper() {}

    enum class Mode
    {
        NORMAL,
        ONETIME_DUB,
        REPLACE,
        FRIPPERTRONICS,
    };

    void Init(float *mem, size_t size)
    {
        buffer_size_ = size;
        buff_        = mem;

        InitBuff();
        state_      = State::EMPTY;
        mode_       = Mode::NORMAL;
        half_speed_ = false;
        reverse_    = false;
        rec_queue_  = false;
        win_idx_    = 0;

        increment_size = 1.0;
    }

    float Process(const float input)
    {
        float sig = 0.f;
        float inc;
        bool  hitloop = false;
        // Record forward at normal speed during the first loop no matter what.
        inc = state_ == State::EMPTY || state_ == State::REC_FIRST
                  ? 1.f
                  : GetIncrementSize();
        win_ = WindowVal(win_idx_ * kWindowFactor);
        switch(state_)
        {
            case State::EMPTY:
                sig      = 0.0f;
                pos_     = 0;
                recsize_ = 0;
                break;
            case State::REC_FIRST:
                sig = 0.f;
                Write(pos_, input * win_);
                if(win_idx_ < kWindowSamps - 1)
                    win_idx_ += 1;
                recsize_ = pos_;
                pos_ += inc;
                if(pos_ > buffer_size_ - 1)
                {
                    state_   = State::PLAYING;
                    recsize_ = pos_ - 1;
                    pos_     = 0;
                }
                break;
            case State::PLAYING:
                sig = Read(pos_);
                if(win_idx_ < kWindowSamps - 1)
                {
                    Write(pos_, sig + input * (1.f - win_));
                    win_idx_ += 1;
                }

                pos_ += inc;
                if(pos_ > recsize_ - 1)
                {
                    pos_    = 0;
                    hitloop = true;
                }
                else if(pos_ < 0)
                {
                    pos_    = recsize_ - 1;
                    hitloop = true;
                }
                if(hitloop)

                {
                    if(rec_queue_ && mode_ == Mode::ONETIME_DUB)
                    {
                        rec_queue_ = false;
                        state_     = State::REC_DUB;
                        win_idx_   = 0;
                    }
                }
                break;
            case State::REC_DUB:
                sig = Read(pos_);
                switch(mode_)
                {
                    case Mode::REPLACE: Write(pos_, input * win_); break;
                    case Mode::FRIPPERTRONICS:
                        Write(pos_, (input * win_) + (sig * kFripDecayVal));
                        break;
                    case Mode::NORMAL:
                    case Mode::ONETIME_DUB:
                    default: Write(pos_, (input * win_) + sig); break;
                }
                if(win_idx_ < kWindowSamps - 1)
                    win_idx_ += 1;
                pos_ += inc;
                if(pos_ > recsize_ - 1)
                {
                    pos_    = 0;
                    hitloop = true;
                }
                else if(pos_ < 0)
                {
                    pos_    = recsize_ - 1;
                    hitloop = true;
                }
                if(hitloop && mode_ == Mode::ONETIME_DUB)
                {
                    state_   = State::PLAYING;
                    win_idx_ = 0;
                }

                break;
            default: break;
        }
        near_beginning_ = state_ != State::EMPTY && !Recording() && pos_ < 4800
                              ? true
                              : false;

        return sig;
    }

    inline void Clear() { state_ = State::EMPTY; }

    inline void TrigRecord()
    {
        switch(state_)
        {
            case State::EMPTY:
                pos_        = 0;
                recsize_    = 0;
                state_      = State::REC_FIRST;
                half_speed_ = false;
                reverse_    = false;
                break;
            case State::REC_FIRST:
            case State::REC_DUB: state_ = State::PLAYING; break;
            case State::PLAYING:
                if(mode_ == Mode::ONETIME_DUB)
                    rec_queue_ = true;
                else
                    state_ = State::REC_DUB;
                break;
            default: state_ = State::EMPTY; break;
        }
        if(!rec_queue_)
            win_idx_ = 0;
    }

    inline const bool Recording() const
    {
        return state_ == State::REC_DUB || state_ == State::REC_FIRST;
    }

    inline const bool RecordingQueued() const { return rec_queue_; }

    inline void IncrementMode()
    {
        int m = static_cast<int>(mode_);
        m     = m + 1;
        if(m > kNumModes - 1)
            m = 0;
        mode_ = static_cast<Mode>(m);
    }

    inline void SetMode(Mode mode) { mode_ = mode; }

    inline const Mode GetMode() const { return mode_; }

    inline void ToggleReverse() { reverse_ = !reverse_; }
    inline void SetReverse(bool state) { reverse_ = state; }
    inline bool GetReverse() const { return reverse_; }

    inline void ToggleHalfSpeed() { half_speed_ = !half_speed_; }
    inline void SetHalfSpeed(bool state) { half_speed_ = state; }
    inline bool GetHalfSpeed() const { return half_speed_; }

    inline bool IsNearBeginning() const { return near_beginning_; }

    inline float GetIncrementSize() const
    {
        float inc = increment_size;

        if(half_speed_)
            inc *= 0.5f;

        return reverse_ ? -inc : inc;
    }

    void SetIncrementSize(float increment) { increment_size = increment; }

    inline float  GetPos() const { return pos_; }
    inline size_t GetRecSize() const { return recsize_; }

  private:
    static constexpr float kFripDecayVal      = 0.7071067811865476f;
    static constexpr int   kNumModes          = 4;
    static constexpr int   kNumPlaybackSpeeds = 3;
    static constexpr int   kWindowSamps       = 1200;
    static constexpr float kWindowFactor      = (1.f / kWindowSamps);

    void InitBuff() { std::fill(&buff_[0], &buff_[buffer_size_ - 1], 0); }

    inline const float Read(size_t pos) const { return buff_[pos]; }

    float ReadF(float pos)
    {
        float    a, b, frac;
        uint32_t i_idx = static_cast<uint32_t>(pos);
        frac           = pos - i_idx;
        a              = buff_[i_idx];
        b              = buff_[(i_idx + 1) % buffer_size_];
        return a + (b - a) * frac;
    }

    inline void Write(size_t pos, float val) { buff_[pos] = val; }

    float WindowVal(float in) { return sin(HALFPI_F * in); }

    // Private Enums

    enum class State
    {
        EMPTY,
        REC_FIRST,
        PLAYING,
        REC_DUB,
    };

    Mode   mode_;
    State  state_;
    float *buff_;
    size_t buffer_size_;
    float  pos_, win_;
    size_t win_idx_;
    bool   half_speed_;
    bool   reverse_;
    size_t recsize_;
    bool   rec_queue_;
    bool   near_beginning_;
    float  increment_size;
};

} // namespace daisysp