Today we will be adding a tempo sync or tap tempo function to the simple delay we worked on in the last tutorial. We will be building on that patch, and if you haven’t done it already, we’d recommend starting with that tutorial which is available here.
This is a delay that can be quantised either by tapping the push button on the OWL or by using an external clock connected to the push input. Try it out and download the gendsp file here:
1.) We will begin by grabbing a ready made tap tempo analysis subpatch from the TXdelay patch. You can find that on our Github here:
We need to copy the [gen] object inside the gendsp patch, [gen~ @gen TXDelay]. The object called [gen] is a subpatch we’ve made to count the elapsed time between two presses of the trigger on the OWL. If you’re going to reuse a bit of code, you can make it into a subpatch or abstraction.
2.) Now we need to paste this [gen] subpatch back into [gen~ @gen SimpleDelay]. We then need to connect the [gen] subpatch to the push button / trigger input which comes into [param @name Push @min 0 @max 1].
3.) We can see what’s going on inside the [gen] subject by creating a third output from our gendsp patch with an object called [out 3] and visualising the output with a [scope~] in the main maxpat. Remember, we use [out] objects to get out of the gen and gendsp parts of the patch. The range of the [scope~] needs to be set to -1 1 in the inspector, accessible via View > Inspector Window, and audio needs to be turned on for it to work. We can now visualise what’s coming in to the push in by temporarily connecting [in 1] in the [gen] subpatch to an output, [out 2], and connecting the second output of [gen] subpatch to [out 3] in the main gendsp patch. When we click the bang in the top right corner of our main maxpat to simulate a trigger input, we can see that we are causing a value of one to be constantly transmitted, 48000 times per second in fact!
4.) We just want to know the moment the button is pressed, so we actually only need to see this value once, and this is where the [delta] object will help. This object tells us the difference between inputs it receives. Disconnecting [in 1] from [out 2], and connecting [delta] to [out 2], we can see two sharper spikes in our scope when we click the bang: one from 0 to 1 and one from 0 to -1. This is because pressing the button creates a value change from 0 to 1 – a difference of 1 – and releasing the button creates a value change from 1 to 0 – a difference of -1.
5.) This is handy because we only have the value of 1 once, but it creates a problem because we need to filter out the negative side of the ‘blip’, and gen gives us an object that does this, [gtep], which stands for Greater Than or Equal Pass. We know what value we want to filter out and what we want to pass. We can set the value we are wanting to pass with an argument, and so if we give it an argument of 1 we will only get the positive ‘blip’.
6.) Now, every time we hit the button or press the bang in our main maxpat, the value 1 will be transmitted to the next object which is called [accum]. This object accumulates amounts over time, and outputs it’s accumulated value every time it receives a new input. This then goes into the [counter] object. If you input the value 1 to the counter, it starts counting at the samplerate. That means that in 1 second it will have counted to 48000, because the samplerate of the OWL is 48000. If you input a value other than 1, it will multiply the output by this amount. That means that if we input a 2, it will start counting at 96000 per second.
The first output from [accum] outputs a 1, and this is good for us. However, the second time we output from [accum], we get a 2, and this is going to be a problem if it gets sent to [counter], and that’s why we’ve made a feedback loop.
7.) Remember: we want the first bang or ‘tap’ to start counting the length in samples, and the second tap should stop that counting and output the value. [==] is a logic operator and will only output 0 (false) or 1 (true). [== 2] is looking for 2. For any value that comes in that is not 2 it will always output 0, and as soon as it receives a 2 it will output 1. So, every time we hit the push button for a second time, [accum] outputs 2, this goes into the logic operator [==] which will output the value 1, which does two things:
i.) Every time [accum] receives a non-zero input in the second inlet it resets. The [history] object just creates a one sample delay which prevents a feedback loop that max won’t like! This goes into [counter] which we also want to reset.
ii.) Our final object, [latch], ‘latches’ the last value. It always stores the very last input, whenever it receives a non-zero value to the second inlet, it releases (or purges) the final count stored to the output. We want the [latch] to purge the final count before the [counter] and [accum] are reset. The [history] object creates a delay which means this happens in the correct order.
8.) So, [accum] triggers [counter] on the first push of the button, then on the second push, it first tells [latch] to purge the last number it received (ie, how high the counter got), then it resets [latch] and [counter]. We can test this by connecting [latch] to [out 2] and replacing the [scope~] with a [number~] box in the main maxpat. If the bang is pressed twice, [number~] will display the elapsed time (in samples) between presses. This now means that our trigger or push input gives us a delay time or tempo!
9.) We are going to divide the delay tempo by a varying amount, so as you turn the knob higher you get a faster delay time. We will replace our previous method for turning dial a into delay time. We can delete [* 48000] that is connected to [param @name A @min 0 @max 1].
Remember, in in the world of DSP, you must NEVER divide something by zero, because this will give us an answer of infinite which we won’t be able to compute. What we need to do is create a failsafe system. First of all, we want to offset what’s coming in from [param @name A @min 0 @max 1] using a [+] object with an argument of [0.001]. That means that instead of going from 0 to 1, the value from our input becomes 0.001 to 1.001. Then we can multiply it by 7 using [* 7] and finally pass it through the [ceil] object. The [ceil] object rounds its input to the nearest maximum integer, meaning the value will be truncated to 1, 2, 3, 4, 5, 6, 7 or 8. These are good ‘musical’ numbers to divide our delay time by.
10.) We now divide the time in samples from our tap tempo subpatch by the value coming in from dial a, then connect this into and the time input – the second inlet – for both [delay] objects. We delete [out 3], because the OWL only has two audio outputs.
11.) Now let’s test this! Upload this patch to the OWL using the online compiler at www.rebeltech.org. We can get a tightly synced delay by using feeding the clock into our trigger input!
Even if you don’t have an OWL, you can test the delay in the browser. You can find more instructions about compiling Gen patches for the OWL at http://www.rebeltech.org/2016/11/owl-max-tutorial-1-getting-started/