resampling "on-the-fly"

monohusche's icon

Hi,

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

monohusche's icon

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 ?

monohusche's icon

ok, reset did the job

monohusche's icon

@raja

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

monohusche's icon

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 ?

monohusche's icon

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.

monohusche's icon

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 ?

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

monohusche's icon

No one ? Where are the experts ?

monohusche's icon

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

EDIT:

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

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

cheers, nick

monohusche's icon

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

monohusche's icon

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.

to_the_sun's icon

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…

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

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]?

bertrandfraysse's icon

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 ?