What we’re aiming for

In the previous article, we saw how to create a basic looper in shell.

This creates our background sound.

Lets add an “overdub” function, which is a way to add a layer on top of the background,

Here is a diagram showing what we’re aiming for:

timeline

If it’s still not clear, here is a video which explains how to use a looper pedal from JustinGuitar youtube channel (see 8:10 timecode):

how we’ll proceed

Like in previous part, we’ll record the background in its own wav file (background.wav), based on pedal pushes. Then we’ll play it in a loop. On the third push, we’ll record the layer in layer.wav, and play it in subsequent loops, in parallel with background.wav.

IPC

Because terminal input can only be done in the foreground, We’ll have to capture the overdub pedal push in the foreground, and transfer the push to the main loop running in the background.

To do that, we’ll use a very basic aproach: a text file which will contain false if the pedal was not pushed, and true if the pedal was pushed.

 ╭────────────╮             ╭───────────╮             ╭─────────────╮
 │            │   reads     │           │   writes    │             │
 │ main loop  ├────────────>│ text file │<────────────┤ pedal input │
 │            │             │           │             │             │
 ╰────────────╯             ╰───────────╯             ╰─────────────╯

let’s do it

First, we will start the script by removing files from previous recordings:

rm background.wav layer.wav

As in previous part, we’ll be using this function to capture key presses.

input() {
  stty raw
  dd bs=1 count=1 2> /dev/null
  stty -raw
}

then, let’s record background.wav - this is the same thing as in previous article

record_background() {
  input
  arecord -f S16_LE -r 48000 -D hw:1,0 background.wav &
  pid=$!
  sleep 1
  input
  kill $pid
}

As mentioned earlier, we’ll be using a file named should_record_layer to notify the main_loop that it should start recording the layer (when it contains true), and initialize it at false.

  echo false > should_record_layer

main loop

then let’s start the main loop

  while true
  do
    play_layer
    record_layer  
    aplay background.wav
    [ $? -ne 0 ] && break
    [ -n $record_layer_pid ] && kill $record_layer_pid
    [ -n $play_layer_pid ] && kill $play_layer_pid
  done

Let’s have a look at what we do here.

First, if a layer was recorded, we play it in the background, and set a variable with aplay pid:

play_layer() {
  play_layer_pid=""
  if [ -e "layer.wav" ]
  then
    aplay layer.wav &
    play_layer_pid=$!
  fi
}

Then, if should_record_layer file contains true, we record in the background, and set a variable with arecord pid. Once arecord is started, we can set should_record_layer to false.

record_layer() {
  record_layer_pid=""
  if $(cat should_record_layer)
  then
    arecord -f S16_LE -r 48000 -D hw:1,0 layer.wav &
    record_layer_pid=$!
    echo false > should_record_layer
  fi
}

Then we can start playing background.wav:

aplay background.wav

When we’re done playing background.wav, we kill arecord (the layer recorder) if it is running. We also kill aplay (the layer player) if it is running.

    [ -n $record_layer_pid ] && kill $record_layer_pid
    [ -n $play_layer_pid ] && kill $play_layer_pid

let’s finish it

Then, we call main_loop in the background, wait for a pedal push, and notify main_loop via should_record_layer file.

main_loop &
main_loop_pid=$!
input
echo true > should_record_layer

Finally, we wait for main_loop to stop:

wait $main_loop_pid

The whole script

Here is the whole script

#!/usr/bin/env sh

input() {
  stty raw
  dd bs=1 count=1 2> /dev/null
  stty -raw
}

record_background() {
  input
  arecord -f S16_LE -r 48000 -D hw:1,0 background.wav &
  pid=$!
  sleep 1
  input
  kill $pid
}

play_layer() {
  play_layer_pid=""
  if [ -e "layer.wav" ]
  then
    aplay layer.wav &
    play_layer_pid=$!
  fi
}

record_layer() {
  record_layer_pid=""
  if $(cat should_record_layer)
  then
    arecord -f S16_LE -r 48000 -D hw:1,0 layer.wav &
    record_layer_pid=$!
    echo false > should_record_layer
  fi
}

main_loop() {
  echo false > should_record_layer
  while true
  do
    play_layer
    record_layer  
    aplay background.wav
    [ $? -ne 0 ] && break
    [ -n $record_layer_pid ] && kill $record_layer_pid
    [ -n $play_layer_pid ] && kill $play_layer_pid
  done
}

rm background.wav layer.wav
record_background
main_loop &
main_loop_pid=$!
input
echo true > should_record_layer
wait $main_loop_pid

to conclude

With less than 60 lines of shell, we’ve made a software pedal with a layer.

With a cheap USB pedal like this which sends a key press, this script is really nice to use:

timeline