sample-accurate record and playback

Tim Lloyd's icon

I've made a test patch to see if I can get sample-accurate recording and playback all synced to a master phasor~.

I tried using the same phasor~ to drive poke~ to record and to play back from index, but it caused a change in recording speed, resulting in distortion and pitch-shifting.

So I have had to compromise and use count~ into poke and phasor~ for playback. The problem here is that because count~ and phasor~ are not in sync with each other, playback never starts from the beginning of the buffer, there are a few milliseconds of "lag" after recording ends.

I could use the master phasor~ that drives playback to also drive poke~, but only once the loop length has been determined (that will make sense when looking at the patch). So its kind of a catch-22 as far as that's concerned, because the loop length is determined by the time between the start record and stop record buttons being pressed.

Also, I'm sure that there is a better way to go about triggering the recording and playback, as the trigger objects I'm using are just contradicting the idea of the patch being sample accurate.......but I've not found a way around that.

I'm going to add overdubbing, multiple channels and replace index~ with elasticindex~ to make use of the freezing and granular fun that can be had with it, but only after I can get this simple element working 100%

Some tips/suggestions/corrections would be great!

heres the patch :

Max Patch
Copy patch and select New From Clipboard in Max.

cheers everyone

-tim

Holland Hopson's icon

Can you use the sync output of the [record~] object to drive your playback and ducking patch? But maybe this isn't any better than the solution you've already made.

Tim Lloyd's icon

I've not tried to do that exactly, but am I right in thinking that if I had 6 (for example) record~ objects all triggerd "at the same time" via a message of 1, due to the right-left priority these wouldn't syncronized to eachother? It would be really useful if record~ accepted a signal of 1 to start recording and 0 to stop, rather than just messages.

(I have tried using Thomas Grill's xrecord~, but never managed to get it to work)

If people think I'm being overly pedantic about wanting to have everything perfectly sample-synced, they are right .........but I'm finding it really useful to be so picky, as I hope to learn a lot from it, as well as end up with a really efficient looper.

I guess the ideal way (that I can think of at the moment) would be to have ONE master phasor~ which syncronized everything - the recording of poke~ and the playback of index~, so that everything was controlled at signal rate, referenced to a master "clock" - the phasor~.

I'm very aware that I could be mistaken about the inaccuracy of using record~, I'm really hoping that someone can put me on the right track regarding all the waffly questions I'm asking

I'll try using the sync output of record and see where that takes me.

Tim Lloyd's icon

I just had a -very- minor brainwave in that if the 0 (first order) outlet of the stop recording trigger (in the patch I posted above) is connected to the phase inlet of the phasor~ the ramp "resets" every time recording stops/playback starts.

.........it works to keep the patch in apparent syncronization - but it still isn't sample accurate.

suggestions still very welcome!

Andrew Pask's icon

It's not really clear to me what "sample accurate" means in this context, given that the events you use to trigger the recording process do not happen in the audio domain. But this the sort of thing I tend do all the time, and if I want some kind of sync I usually use stutter~

-A

Max Patch
Copy patch and select New From Clipboard in Max.

Tim Lloyd's icon

The triggering is something I need to drastically improve. You're right that if I use max events to trigger the recording and playback the patch will never be 100% signal controlled/syncronized . But what I'm hoping for is a method by which I can use a MIDI note on input to start and stop the recording, and everything after the initial midi trigger being 100% signal domain, which would mean that relative to eachother, all of the poke~ and index~ objects could be 100% syncronized. I guess thats what I mean when I say sample-accurate, I mean that all the recording and playback is fully syncronized relative to eachother (if that makes sense).

You're patch looks interesting, and I'll work out how stutter~ is functioning, but for what I'm building, I need to be using index~ for playback, so that I can swap in elasticindex~ (if my cpu will allow it!) to use for its freezing and smooth playback at very slow speeds.

-edit-

Also it might be useful to know that the patch will be used live, looping live audio on multiple channels, with overdubbing (into separate buffers), which is why I'm having issues syncing poke~ and index~. Not sure if I've explained very well why that is a problem. I have been using the loop length to determine the phasor~ frequency which gives normal speed recording and playback, but I won't know the loop length before I start recording.

hmm....apologies if I'm not making sense, I feel that I'm not communicating my intentions and problems very well

Robin Price's icon

Eric Lyon's samm~ is a sample accurate metronome with click signal articulation. Don't know if that's any use to you

Tim Lloyd's icon

el.samm~ seems to be exactly what I'm looking for!

My only gripe with is that it doesn't seems to recognize a bpm argument of less than 1, so it means that it limits the recording max recording length to 60 seconds. Absolutely not an issue for what I'm doing at the moment, but it might be in the future - would have to experiment with delay~ to lengthen it.

I'm know I'm still being massively picky about the accuracy of the recording and playback, but would I be correct in thinking that if they were syncronized to the sample, there would be no click at the end/beginning of a loop? I would rather try and eliminate clicks by sorting out the sync instead of windowing them out - if that is actually possible!

As it is, this patch works as a simple 1 channel looper, but still has clicking issues. It would be interesting to get some opinions on it, and if it's worth trying to get rid of the clicks by sync rather than windowing.

cheers for the help guys!

here is the patch - you need el.samm~ el.mask~ and el.clickhold~ to use it :

Max Patch
Copy patch and select New From Clipboard in Max.

Tim Lloyd's icon

I'm being silly, the clicks are because I was looking a constant waveform, and the loop starts and ends at a different point in it's phase. Seems that el.samm~ and co did the trick!

Although, I tried using trunc~ to sort out the occasionally floating point sample index values being sent to index~, and with a sine wave it results in slight distortion, which strangely goes away after about the 4th loop. It seems to make no difference to the sound quality of an audio sample being looped to use/not use trunc~ though. Since index~ does not interp though, it seems like a good idea to send it a stream of signal integer values somehow.

Eric Lyon's icon

timlloyd wrote on Fri, 26 June 2009 23:53el.samm~ seems to be exactly what I'm looking for!

My only gripe with is that it doesn't seems to recognize a bpm argument of less than 1, so it means that it limits the recording max recording length to 60 seconds. Absolutely not an issue for what I'm doing at the moment, but it might be in the future - would have to experiment with delay~ to lengthen it.

You can specify the tempo in samples. You can also specify beat streams in fractions of a beat. Say you want a click every 4 minutes. Initialize samm~ with arguments "1 0.25" or (at SR = 44100) send a message of "sampbeats 10584000".

Good luck,
Eric

monohusche's icon

hey tim,

I built a 7-channel looper myself (to be controlled with a monome) where all the channels are driven by a master phasor (using elasticindex), and I (kind of unsuccessfully) tried to implement overdubbing (or better: resampling) without really achieving sampling accuracy. to be found here: http://code.google.com/p/monohad/

anyway, quite interested in what you are trying to achieve, so keep us posted. will check out the stuff you already made.

By the way, I like the el.* stuff as well, the only issue I had with el.samm (to be used as metronome for my drum sequencer unit) that it couldn't be driven/synced from/by an external master phasor

Tim Lloyd's icon

Here is what I've come up with using the el. objects for triggering events.

I'm not sure whether or not this is sample accurate in terms of the transition between recording and playback, I will welcome anyone who wants to dissect it and be really critical!

At the moment it is simply one channel of recording and playback, as a test of the el. stuff, I have yet to try overdubbing, but it shouldn't be a problem.

In case you wonder why I'm using index~ and not elasticindex~ for normal playback, it is because it seems to affect transients and adds an annoying flutter to the audio (at least on my computer it does).

There is a bug I've not sorted yet, which is due to the nature of el.samm~, being a metronome, so it will retrigger itself after a certain amount of time, and I havn't sorted that out properly for the freeze/thaw toggle.

-edit- I just rememberd the other bug to do with using el.samm~ etc.. to control playback. I'm not sure how to make the toggle on always trigger play and off always trigger pause. I may just go back to using buttons instead of the toggles - as the patch will be controlled via midi floorboard anyway.

Anyway, heres the test patch, I'm interested to get some critical feedback:

Max Patch
Copy patch and select New From Clipboard in Max.

Eric Lyon's icon

monohusche wrote on Sat, 27 June 2009 22:37hey tim,

By the way, I like the el.* stuff as well, the only issue I had with el.samm (to be used as metronome for my drum sequencer unit) that it couldn't be driven/synced from/by an external master phasor

If you already have a sample-accurate external master phasor, you don't need samm~. You can start, stop or pause samm~ (see the help file) so you can certainly sync a samm~ to the start of a Max event. But there's no point in having two master clocks for the same tempo.

Eric

Tim Lloyd's icon

I've been trying to add overdubbing to the last patch I posted, very straightforward to do it badly, however......

what I'm trying to achieve is for overdub to only start recording at the beginning of the loop - and then stop automatically at the end of the loop.

I have the first part working, in that no matter when the overdub button is pressed, recording will always start when the loop retriggers, however due to the way I have to have el.mask~ it records one full loop, then waits one loop and then records on top of itself over one loop. That will make sense if you check out the patch, but for some reason I can't get my head around how to disable the output automatically at the end of the overdub. It really shouldn't be complex at all, I just can't seem to get my head around it, using gating didn't work because then the close gate message (from snapshot) prevents the gate from ever opening again - same issue when using the mute $1 message to el.mask.

I'm having some issues with using the el.mask etc for triggering - I don't know how to reset el.mask so that it will output the first number in it's sequence (i need something like the reset to number immediately like on the counter object). So sometimes after loading, it needs to be triggered twice to start recording, same issue for the playback. Hoping Mr Lyon will chime in again! Otherwise I'll switch to using the tap.counter~ object, which has a reset message.

Cheers for the help so far, it shouldn't be taking me this long to sort out this patch!!

Apologies for the slightly messy layout - too much late-night patching:

Max Patch
Copy patch and select New From Clipboard in Max.

Eric Lyon's icon

timlloyd wrote on Tue, 30 June 2009 02:34
I have the first part working, in that no matter when the overdub button is pressed, recording will always start when the loop retriggers, however due to the way I have to have el.mask~ it records one full loop, then waits one loop and then records on top of itself over one loop. That will make sense if you check out the patch, but for some reason I can't get my head around how to disable the output automatically at the end of the overdub. It really shouldn't be complex at all, I just can't seem to get my head around it, using gating didn't work because then the close gate message (from snapshot) prevents the gate from ever opening again - same issue when using the mute $1 message to el.mask.

I'm having some issues with using the el.mask etc for triggering - I don't know how to reset el.mask so that it will output the first number in it's sequence (i need something like the reset to number immediately like on the counter object).

Cheers for the help so far, it shouldn't be taking me this long to sort out this patch!!

Apologies for the slightly messy layout - too much late-night patching:

I'm afraid this patch is too complex to make much sense out of, so I can't determine the gap between what my objects are doing and what you want them to do. This complexity may also be why you're having a hard time solving your problem. That said, it may be possible that you're trying to use the objects for something they were not designed to do. If you want two events - a start and then later a stop, you don't need a metronome, you need a delay. You can reset mask~ to the beginning of its cycle with "gozero" but again, I don't know if it's actually the right object for your patch. If you can make a much simplified version of your patch, documenting the behavior of mask~ v.s. what you would like it to do, I'd be happy to take another look.

Eric

Tim Lloyd's icon

Hi, all I'm using el.mask~ >> el.clickhold~ for is to toggle between a signal of 1 and 2, which I use to toggle between inlets of the selector~ objects. It works exactly how I need it to now that I know how to reset it to its first value.

Its the building of an overdub function which is perfectly syncronized to the first loop that is a problem. Of course, I could just have a button to start overdubbing and one to stop, but that contradicts the point of the rest of the syncronization of the patch.

The first looper I made was based around record~ and groove, and I make a way that would "quantize" the triggering of overdubbing to the length of the loop. No matter where in the playback of the loop the button was pressed, overdubbing would only start when it next reached the beginning, and would automatically stop at the end.

I'm trying to improve on that looper by patching the whole thing in the signal domain, and I've not worked out how to get the overdubbing to stop at the end of the loop - I'm staying away from edge~ as its output isn't signal rate. Also, I'll try doing this with delay~ and seeing if it's accurate enough.

I've taken most of the patch out, and commented on how it works as best I can, the overdubbing issue will become clear as soon as you try it out on the patch.

cheers for the help!

-tim

Max Patch
Copy patch and select New From Clipboard in Max.

monohusche's icon

Hi tim,

as I might have mentioned (or not), I once tried to implement that as well, basically "quantized" resampling/overdubbing where it would start recording at the beginning of the phase (phasor~ = 0) and stopping at the end (phasor~ = 1).

I would have to dig it out, but I remember that there was always a small lag/delay of some ms that the samples got shifted towards the beginning (e.g. sample no 100 was written to sample 0 in the new buffer).

will have a look but keep me posted how you are going with your edge-less signal domain recording.

thx nick

Tim Lloyd's icon

I'm pretty sure that the patch I posted works for the start of overdubbing, but I've not managed to get it to stop at the end of the phasor ramp.

I'll check my patch and see if it has the same lag that you mentioned. Is there an easy way to do that?

.....I actually just had an idea - inverting the output of phasor~ so that the 0. signal of the normal upward 0-1 ramp starts the overdub, and the 0. signal of an inverted 1-0 ramp stops might work!

I'll try it out and post back.

hmmmmm

monohusche's icon

I don't have max right now at work, so can't see your patch and not sure what you are using, but if you are using phasor (??), isn't there an option to make it one-shot so that phasor doesn't loop. That should take care of recording a single loop, isn't it ?

but maybe you are off on a different tangent

thx

Tim Lloyd's icon

I am using phasor~ but I'm trying to use only one in the entire patch, that is used to continually control playback of all loops/overdubs and also to write samples into the buffers with poke.

The idea is that once the first loop is recorded, its length determines the frequency of phasor~ that will playback and overdub at normal speed. So after the initial recording of the loop, absolutely everything is synced to that one master phasor~.

but.....if phasor~ can do a one-shot ramp (I'll try it out), I could use a separate one to overdub, then there is just the issue that you can't reset the phase of phasor~ with a signal/impulse to sync the master to the overdub. An updated phasor~ that accepted a signal in its phase inlet would solve a lot of problems. If only I knew how to make my own externals!

Heres a pic of the commented patch I posted - sorry for the poor quality, it kept saying the file was too big.

monohusche's icon

now it occurred to me that rate~ could/should be the solution as it allows you to scale a signal and if I am not mistaken it allows you to specify one shot.

so in this case, you would have a temporary "recording" phasor being synced to the continous master one...

Tim Lloyd's icon

Ahhhh, it does do oneshot. I'm not sure why I never thought about using rate~ as this is exactly what it's for! I used it in an LFO abstraction a while ago and got irritated with it because it distorts its output when the multiplier value is changed with either sync mode on.

I have to go to work soon, so I'll try out a oneshot rate this evening, thanks for the suggestion!

Tim Lloyd's icon

rate~ with oneshot is proving really disappointing, it would work perfectly, but with oneshot mode on, I have to delete and remake the rate~ object before it gives any output again, even after turning off oneshot mode. I didn't see anything in the helpfile about how to retrigger its output after the one ramp is over, I tried both sync modes and using the reset and go to messages, and they don't work reliably. If there was an alternative object that had a oneshot mode but still let the signal thru with it off it would be perfect. Very unfortunate.

I got it working a different way, but it's not reliable either. It works accurately when I first load the patch (null test between loop 1 and overdub fully cancels out) but after that, it results in strange aliasing-ish sounds recorded into the overdub, and the two loops don't null - which means the sync isn't working. It's proving much more tricky that I thought it should be to start with. I must be missing something obvious!

Mike S's icon

i have not had chance to read the whole thread but i think i get the jist of it.

have you tried count~ in to poke~ for recording and the same count~ in to index~ for playback?

i have a looper that is pretty sharp using this technique

Eric Lyon's icon

In case you're on OS X and don't mind beta testing an external, I've attached a new one called sarec~ that does sample accurate recording. You can use it in conjunction with a sample-accurate player to keep everything in the signal domain, such that recording commences on the same sample as playback. This example requires player~ from LyonPotpourri.

Eric

Tim Lloyd's icon

I did use count~ for recording, but I didn't try it for playback - I latched onto phasor~ because it loops automatically. I'll have a go rebuilding from the beginning using count~.

Also, the reason I shied away from that method to begin with was that count~ can't be triggered on/off with a signal, but I just found out about tap.count~ which can.

I'll try using Eric Lyon's sarec~ as well, I don't mind beta testing at all, I'll find it interesting. However, I really want to be using index~ for playback, so I'll see if I can merge the two ideas. I'm going to try and make the patch very much like an expanded Gibson/Oberheim Echoplex, with the replace feature etc.... and granular functions as well, so elasticindex~ will be a part of playback that definitely needs to be properly synced.

Cheers for the suggestions!

Tim Lloyd's icon

One quick question about el.sarec~. I just looked at the test patch, and is there a way of stopping recording using a click~? If not it would be great for the first impulse to start recording, and the second to stop, unless the buffer~ is filled, after which the second click becomes the first again, if that makes sense. Or a new inlet which uses an impulse to stop recording.

Eric Lyon's icon

timlloyd wrote on Mon, 06 July 2009 20:18One quick question about el.sarec~. I just looked at the test patch, and is there a way of stopping recording using a click~? If not it would be great for the first impulse to start recording, and the second to stop, unless the buffer~ is filled, after which the second click becomes the first again, if that makes sense. Or a new inlet which uses an impulse to stop recording.

My plan is to stop recording with a click of value 2.0. But that feature is not yet implemented. For now, once initiated, the recording will proceed from the beginning to the end of the buffer and then stop.

Eric

Tim Lloyd's icon

I've realized why my phasor~ sync method has not been working properly, it seems to be fundamentally flawed due to the accuracy of the 0 to 1 ramp of phasor~.

To record the first loop, the signal is an increasing integer value (delta~ output of 1.), but the signal used for overdubbing is the loop length in samples multiplied by the 0. t0 1. ramp of phasor~. That means that due to the limited accuracy of the ramp, the overdubbing signal sent to poke~ is not an integer. Delta~ shows fluctuations of above and below 1, so I'll need to completely rethink the patch, as the accuracy of the phasor~ ramp creates issues.

I tried using trunc~ but the patch still produces artifacts when overdubbing.

I'll have to re-do the patch using count~ for all recording/overdubbing like the msp tutorials suggest, wish it didn't take me such a long time to figure out the problem!

Here is the troublesome patch if anyone is interested:

Max Patch
Copy patch and select New From Clipboard in Max.

Jabbo's icon

I have very little time now and I just passed here for the sake of curiosity, so I have no idea if it can help you, but why not using a timer/record/buffer/groove combination for your "master" looper and sync all the others to the ramp of a groove~ object?

So actually its the groove to be master?

Agh, I get it u want to start recording also at samp.acc level, ok.
sorry, but then you should also consider a ramp option with zigzag, or?

Tim Lloyd's icon

I had a look at zigzag~ but couldn't think how to integrate it. I have a working patch now that only occasionally isn't fully accurate.

As a test of the accuracy I'm recording loop 1 and then feeding the output of the loop 1 index~ into poke~ two so that I can null test the two loops. Most of the time the patch results in complete cancellation between loop 1 and the overdub, but sometimes is slightly out, leaving high frequencies remaining after inverting the phase of one of them (I'm in a rush sorry for the bad explanation).

I'm just trying to work out why the patch sometimes isn't 100% accurate and then fix it.

Heres the working test patch - it uses el.mask~, el.clickhold and tap.count :

I'm interested in what people think and getting suggestion to improve the concept.

Max Patch
Copy patch and select New From Clipboard in Max.

nit's icon

is tap.count~ a free external or can you get it only form tap.tools?

Tim Lloyd's icon

It's only from tap.tools, but I actually replaced it with the free CNMAT accumulate~ external. It works similarly to the msp +=~ object. I'm going to replace the +=~ with accumulate~ as well just so the two loops are recorded using a signal from the same kind of object.

There is a bug I don't understand as well as the occasional inaccuracy - sometimes it seems like the poke~ and buffer objects get confused and the original loop records into both buffers. Not sure why, but happens very rarely.

I'm experimenting putting bits into poly~ and up-sampling as well to see if that minimizes the accuracy errors.

Heres the patch with accumulate~

Hope it works for you!

Max Patch
Copy patch and select New From Clipboard in Max.

nit's icon

thanks, i'll try this out soon.

Eric Lyon's icon

I'm enclosing an example of how to do some of this with sample-accurate click objects. The example patch will record into one channel, then immediately (sample accurate) start recording into the second channel. Then it shows how to overdub on both channels at an arbitrary location into the buffer. This uses a new version of sarec~ and a bunch of objects from LyonPotpourri.

Eric

nit's icon

i'll try this out when i'm back from dropping of my girlfriend. did you include the sarec object? i'm actually not doing anything with overdubbing but i'm busy with doing sampleaccurate recording so maybe it would be helpfull.

i would like to ask if anyone knows a manner of creating something like the onebang object wich accepts signals(clicks if possible) and puts out signals(clicks if possible)i can't get my head around it.