Forums > MaxMSP

resampling "on-the-fly"

Mar 08 2009 | 7:08 pm


I want to implement resampling functionality into my looper app, basically the "output" of the channel signal network (containing sub loops, effects etc.) should be recorded at the end of the chain. Once recording is finished, the contents of the original buffer should be replaced by what has been recorded.

I assumed that I couldn’t record into the same buffer that I am reading from, so I would need a second temporary buffer for recording. But I haven’t found a way to "copy" the second buffer to the first.

Obviously, one option would be to sfrecord~ + replace (which I would like to avoid), the other option would be the "endless resampling" app. Just not sure whether this would work in my case (read sample 1 –> fx proc –> write sample 1 always in the right order ??)

to summarize, I guess my question is whether it is possible to play/record from the same buffer, and which issues might arise in terms of the playheads.

thx nick

Mar 08 2009 | 8:11 pm

use Java/mxj. instantiate a new object in a blank patch, name it and call up the help file for it to check out the "copyInto" message that is specific to the buf.Op Java file. This accomplishes copying from one buffer~ to another almost instantaneously without having to go through an sfrecord~/record~ process.

read the Java-Docs for more info on Java and mxj.

…one more thing, it is possible to record and play from the same buffer~ keeping the play head ahead or behind the record head but there are additional amp-windowing issues you’ll need to address(if you want to preserve the length of the recording more easily and copy instantly… you may as well just use ).

Mar 09 2009 | 4:12 pm

thx for the advice, sounds good as it allows to have fully independent buffers for playback and recording.

Unfortunately, I am having issues before it gets to copying. Upon generating the record trigger, the new size of the record buffer is calculated and set, then recording is started. The sync outlet of record is used to check for the end of recording (signal value == 1).

For some reason, the end is never reached. Let’s say the buffer originally has a length of 2000ms (full loop), and I want to record an 1/8 note, the new size would be 250ms. Once recording has commenced, the sync outlet stops at 0.125 as if the whole recording buffer still has the original size of 2000ms.

When viewing the buffer contents though, it seems to have the correct size (250ms). I even triggered a "set mybuffer" message to the record object after resizing the buffer and before actual recording.

Is there an issue with resizing being delayed to actual recording ?

Mar 09 2009 | 4:28 pm

ok, reset did the job

Mar 09 2009 | 6:51 pm


Does mxj buf.Op resize the target buffer according to the source buffer, or do I have to resize it "manually" before copying ?

Mar 09 2009 | 8:18 pm

…getting hard to follow everything written, might be easier to post the patch, but i think i understand what you’re asking…

Even without mxj buf.Op, you would need to resize the recording and copy-over buffer~. So ya, you need to manually resize each time but that shouldn’t cause a noticeable delay.

To resize a buffer~, use the size or sizeinsamps message to both buffers~(see help-file for ). Alternately, you can send directly to the messages, "setLength 2 [length-in-ms]" or "setSize 2 [length-in-samps]".

You just need to be careful of the order of operations and remember to reset the record~ head to the recording-buffer~ once that buffer~ is resized so that record~ knows the buffer~ has changed. Here’s a patch to demonstrate:

— Pasted Max Patch, click to expand. —

Best of luck! (…sometimes i need to stop and restart audio to get it going the first time, not sure why but it works and stays working after that… then again, i seem to have to do that often so that could be some local weirdness on my comp)

Mar 09 2009 | 9:22 pm

thx for the patch, mine is stretched via multiple sub patchers, that’s why I was hoping to get away without.

Comparing it to what I have done, it is pretty much the same. The only thing I didn’t do was resizing the destination buffer, I checked out the java code for buf.Op.

public void copyInto(final String destbuf) {
MaxSystem.deferLow(new Executable() {
public void execute() {
long size = MSPBuffer.getSize(bufname);
int channels = MSPBuffer.getChannels(bufname);
–>> MSPBuffer.setSize(destbuf, channels, size);
MSPBuffer.poke(destbuf, MSPBuffer.peek(bufname));

Apparently (marked line), the dimensions of the record channel are duplicated for the destination channel. And actually, the whole thing works…..almost.

Once I recorded the audio snippet into my record buffer, and copy into the destination buffer (which is the one which was played from before), the attached waveform exactly shows the snippet as recorded with the right size. But nothing is selected.

Only when I try to manipulate the selection start and end inlets of the waveform (as I want the "new" audio to be selected), the buffer is cleared mysteriously, or at least the waveform doesn’t show anything anymre. And I am not sure why.

so to me, it’s more an issue about refreshhing the waveform than copying the buffer.

Btw. I also included resizing the destination buffer, didn’t change the behaviour. Also send a new "set" message to the waveform…

any ideas ?

Mar 09 2009 | 9:34 pm
monohusche wrote on Mon, 09 March 2009 15:22

Apparently (marked line), the dimensions of the record channel are duplicated for the destination channel. And actually, the whole thing works…..almost.

…any ideas?

ah, cool, didn’t even check that line in the java source.

as for the rest, i’m lost now because i’m not sure what you’re after(buffer~ copying? resizing? waveform-selection?), but hopefully someone else can help out.

i can only say real quickly that you may want to reset the buffer~ to which waveform~ is linked(‘set’ message) simply to refresh it after any buffer~ changes and then reselect the selection because waveform~ won’t remember the selections based on (buf.Op unless I’m mistaken, operates on buffer~ only)… but you already tried that so i’m out of ideas without seeing the patch.

Anyone else?

Mar 10 2009 | 2:59 pm

Sorry for the long winded explanations/questions.

I am after all of the things, e.g. recording *a part* of a source sample (+fx etc.) into a record buffer, copy the record buffer into the original source buffer and finally reset the selection of the attached waveform to include the complete (new) sample.

I played around with the order of operations etc., tried all kinds of combinations.

1. Select the part to be recorded via waveform
2. Resize recording buffer according to selected snippet
3. Reset the record~ object to make sure it knows about the changed buffer size
4. Play snippet (from source buffer) and record (into record buffer)
5. Copy record buffer into source buffer
6. Reset waveform selection of source buffer (0-sample length)
7. clear the record buffer

The crucial steps for me were 5. and 6. It only works when putting a delay in front of 6. If doing it straight in succession [trigger b b], the source buffer would be empty and consequently the waveform too.

For the delay, even 1 ms was enough to do the job. I am wondering whether this is related to some internal scheduling aspects as I read that resizing operations of buf.Op are deferred to the main thread and therefore have lower priority.

If my suspicion is right, extending the copyInto method of buf.Op to report successful copying and triggering the reset afterwards would do the job, but I am too lazy to dive into the java compiling world right now.

Mar 10 2009 | 3:57 pm

Ok, I assembled a not so little patch to demonstrate the problem.

The patch more or less does the same as in my main patch. The steps to be done are marked with numbers.

The interesting thing is in the bottom left corner. If one sets the delay time to 0 (for resetting the selection), the waveform will show nothing. Raising it above 3 ms or so, everything is fine.

Can anybody explain that ?

— Pasted Max Patch, click to expand. —
Mar 11 2009 | 9:27 am

No one ? Where are the experts ?

Mar 11 2009 | 2:04 pm

Ok, problem identified, but not solved yet.

Resetting the selection parameters automatically resizes the record buffer, so when doing the reset too early (before copying in the main thread has finished), the record buffer is cleared and so is the source buffer consequently.

In order to avoid putting delay objects in the flow, there are two solutions from my point of view:

1. Extend the buf.Op class to report back successful copying and tie resetting to that. That would be the cleanest solution.

2. Use two alternating record buffers, so the reset will resize a different record buffer.

Problem with solution 1 would be that the recompiled class would have to be copied into the respective folder.

let’s see


Dooohh, option 1 isn’t really an option as one gets into the dangerous waters of multithreaded programming. Scanning the java code though, I found another method (dataMove) which does without resizing the destination buffer first, but *most importantly* is executed in the scheduler thread (not deferred).

Problem solved.

buf.Op.dataInto/dataMove does the job although there is an bug wh results in the second channel being dropped during copying.

for (int c=0;c

needs to be changed to:

for (int c=1;c< =channels;c++)
			MSPBuffer.poke(tobuf, c, 0, MSPBuffer.peek(frombuf, c, 0, size));

as buffer index starts with 1 and not with 0. will cross post to the mxj forum.

cheers, nick

Mar 12 2009 | 3:41 am

glad you figured it out. just want to say one more thing, anytime you are sending a written message to an object(by way of a message object), you are depending on the scheduler. with a vector size of 128 and a sampling-rate of 44.1kHz you will always be in danger of having scheduler-action delayed from sample-rate actions by up to 2.9 milliseconds(128/44.1kHz converted from samples to ms; with scheduler in overdrive and audio-interrupt, the scheduler is set to poll once every perform method, meaning once every set of samples in a vector).

for this reason, even using the "dataInto" might still involve a minor delay… using buf.Op, though will always be faster than having to record back into the buffer~ in real-time…

most importantly, though, i wanted to point out that you should look into poke~ and peek~ for your record operations. These objects allow you to trigger recording with a signal and you will be able to sync better. If you do use record~, your method of detecting the end of the ramp will cut some of the recording off(>~0.99 -> edge~ will cause the values between 0.99 and 1.0 to be lost(samples have higher-precision)). Instead try something like this using delta~. delta~ detects the change in value between two consecutive samples, therefore, when record~ is at the end, the change in samples should stay at 0 and become easily detected:

— Pasted Max Patch, click to expand. —

other than that, it looks as though you can pretty much figure out the buf.Op stuff on your own, sorry no one else answered so far, i guess sometimes the community’s occupied all at once… Best of luck!

Mar 12 2009 | 7:04 am

Hi RabidRaja,

regarding the potential bug in buf.Op, I already replied in the other thread.

In terms of syncing recording, I already switched the begin ramp detection to using delta, and will do so for the end ramp as well.

Nevertheless, when playing back a looped sample and pressing "record" in the middle of it, the detection (via delta) of the next begin ramp (to really start synced recording) doesn’t seem to be sample accurate. When I do that repeatedly, the recorded sample shifts forward in the waveform (as the start is delayed)

That’s probably due to the mentioned message delay when sending "1" to record. Therefore, using peek/poke and the same phasor that drives the playback should be fine.

will check out and report back

Mar 12 2009 | 10:47 pm

Ok, I managed to replace the whole recording sub patch to use poke rather than record. Accidently, I had overdrive switched off during most of the patching time, and later on realised that when encountering some glitches during playback and recording.

When enabling overdrive, suddenly the patch stopped working, basically the copying of the record buffer to the play buffer doesn’t work anymore, although the record buffer DOES contain the proper samples.

The only reason I could think of is that the resizing of the buffer (via the message "size xxx") is somehow deferred so that the copying happens actually *before* the resizing which then clears the buffer.

does anyone know whether this is true, and is there a way to circumvent that ?

UPDATE: my assumption was true, copying without resizing works.

May 27 2015 | 10:23 pm

A lot of good information in this thread. I wonder if I were to ask a question of clarification, if I would get a response…

Raja mentions using delta~ instead of [>=~ .99]. Wouldn’t it be just as well to forgo delta~ and change the greater than object to [==~ 1.0]?

-- Pasted Max Patch, click to expand. --

May 28 2015 | 3:50 am

I have a question too, I usually use peek and uzi to copy buffers, I assume it’s slower than mxj buf.Op.
I made a looper with the multiply function.
I use a large buffer, let’s say 60 seconds.
I record 1 second, and then hit the multiply button, it will copy the buffer content from the beginning to 1 second into the 1 second to 2 seconds part of the same buffer.
Is there a way to do this with buf.Op ? or maybe with a few java code lines ?

May 28 2015 | 12:13 pm

Viewing 18 posts - 1 through 18 (of 18 total)

Forums > MaxMSP