Multitasking by ChrisMicro

From Hackteria Wiki
Jump to: navigation, search

Schema RecordPlayDua.jpg

/*
  recordAndPlayback MultiLooper

  What:
  This routine records a buttons pressed in a time series into the RAM memory
  and plays it afterwards in a loop.

  Why:
  This software is useful to controll leds ore motors in a repetitive manner.
  It can be used for music production, where you need repetitive beats.

  How:
  You need at least 2 buttons which are called "record" and "morse" button.
  Hold the "record" button and type in the series you want to play with the "morse" button.
  When you release the "record" button, the series is repeated on the actuator output.
  There a led ore other actuators can be connected.
  When you want to add new channels, take a look at the end of this code.
  Be aware: in the original version, the buttons where tight to 5V and external pull down
  resistors were used.
  In this version, the pullups in the microntroller are used and no external resistors are
  needed and the !!buttons have to be connected to GND!!

  Features of this version:
  In this version you have multiple record channels. For each channel you can add separate
  record buttons and separate actuator outputs.
  When you add more channels, more memory will be consumed. An Arduino Nano (Atmega328) has
  2K RAM which should be sufficient for 5 channels. If you want to have more channels or
  if you want to use a smaller processor you have to decrease the variable "maxSamples".
  With and Attiny85 two channels should be possible if you decrease "maxSamples" to 60.


  Hardware:
  - Arduino Nano
  - Buttons (connected to GND)
  - Actuators (LED with series resistor, active HIGH, connected to GND)


  2013-12-20 Owen Thomson (Mr Grok), committed b2d52c9
    https://bitbucket.org/mrgrok/mrgroks-arduino-sketches/src/b2d52c93640c9f582ac0cd0e867c2a67107274f4/recordAndPlaybackAtRate/recordAndPlaybackAtRate.ino?fileviewer=file-view-default

  2022-05-29 ChrisMicro, C++ Class Version for multiple loopers, description added


  Multitasking without delay by Adafruit:
  https://learn.adafruit.com/multi-tasking-the-arduino-part-1/a-classy-solution

*/

// maximum number of samples
// if you want to use more than 1 looper on an ATTINY85,
// you have to decrease the number of samples, because the ATTINY85 RAM is only 512 bytes
#define maxSamples 100

class Looper
{
    byte morseBtn;
    byte modeBtn;
    byte led; //led or motor control output

    const unsigned long sampleLength = 1;

    boolean inRecordMode = false;
    boolean wasInRecordMode = false;

    // buffers to record state/duration combinations
    boolean states[maxSamples];
    int durations[maxSamples];
    int currentSampleCycles;

    short idxPlayback = 0;
    short idxRecord = 0;

    unsigned long previousMillis;   // will store last time LED was updated

  private:
    void resetForRecording() {
      memset(states, 0, sizeof(states));
      memset(durations, 0, sizeof(durations));
      idxRecord = 0; // reset record idx just to make playback start point obvious

      idxPlayback = 0;
      currentSampleCycles = 0;
    }
    void recordLoop() {
      boolean state = (digitalRead(morseBtn) == LOW); // Low active input pin
      digitalWrite(led, state); // give feedback to person recording the loop

      if (states[idxRecord] == state) {
        // state not changed, add to duration of current state
        durations[idxRecord] += sampleLength;
      } else {
        // state changed, go to next idx and set default duration
        idxRecord++;
        if (idxRecord == maxSamples) {
          idxRecord = 0;  // reset idx if max array size reached
        }
        states[idxRecord] = state;
        durations[idxRecord] = sampleLength;
      }

      //  delay(sampleLength); // slow the loop to a time constant so we can reproduce the timelyness of the recording
    }

    void playbackLoop()
    {
      if (currentSampleCycles == 0 && durations[idxPlayback] != 0) {
        // state changed
        digitalWrite(led, states[idxPlayback]); // set led
      }

      if (idxPlayback == maxSamples) {
        // EOF recorded loop - repeat
        idxPlayback = 0;
        currentSampleCycles = 0;
      } else {
        //  if(durations[idxPlayback]*playbackMultiplier <= currentSampleCycles) // speed ctls deactivated
        if (durations[idxPlayback] * 1 <= currentSampleCycles)
        {
          // EOF current sample in recorder buffer
          idxPlayback++; // move to next sample
          currentSampleCycles = 0; // reset for next sample
        } else {
          // still in same sample, but in next cycle (==sampleLength approx)
          currentSampleCycles++;
        }
      }

      //delay(sampleLength); // keep time same as we had for record loop (approximately)
    }
  public:
    Looper( uint8_t modePin, uint8_t morsePin, uint8_t ledPin)
    {
      modeBtn = modePin;
      morseBtn = morsePin;
      led = ledPin;
      pinMode(modeBtn, INPUT_PULLUP); // use internal pullup, no need of external resistor
      pinMode(morseBtn, INPUT_PULLUP);
      pinMode(led, OUTPUT);
    }


    void update()
    {
      unsigned long currentMillis = millis();

      if (currentMillis - previousMillis >= sampleLength)
      {
        inRecordMode = (digitalRead(modeBtn) == LOW); // Low active input pin
        if (inRecordMode == true) {
          if (!wasInRecordMode) {
            resetForRecording();
          }
          recordLoop();
        } else {
          // continue playing loop
          playbackLoop();
        }

        wasInRecordMode = inRecordMode; // record prev state for next iteration so we know whether to reset the record arr index
      }
      previousMillis = currentMillis;  // Remember the time
    }

};

/**********************************************************************

   user section to add more channels

 *********************************************************************/
//Looper( uint8_t modePin, uint8_t morsePin, uint8_t ledPin)

Looper channel1(2, 0, 3);
Looper channel2(1, 0, 4);

// if you need more channels, add it here and in the main loop ("update")

void setup()
{

}

void loop()
{
  channel1.update();
  channel2.update();
  // add here more channels
  // don't forgett to define it above .. Looper channelx(modePin,morsePin,ledPin)

}