final section completed. any feedback would be appreciated!

2 Likes

I seem to be having issues loading a stereo file. I have stereo and mono versions of a 48k file; loading the mono file with the following:

softcut.buffer_read_mono("/home/we/dust/audio/tape_hiss_m.wav", 0, 100, -1, 1, 1)

This stereo load results in the following error:

softcut.buffer_read_stereo("/home/we/dust/audio/tape_hiss_s.wav", 0, 100, -1)

liblo error: lo_send, lo_message_add, or lo_message_add_varargs called with mismatching types and data at…/matron/src/oracle.c:622, exiting.

Works as expected loading the mono file into one or both buffers simultaneously. If I replace the mono file loads with the stereo code, I get the error. Suggestions?

Bug, will fix

PR

1 Like

Great patches, thank you.

One slightly obsessive question: why the ‘convention’ of reading the start of the sound file into position 1 (as opposed to 0) in the buffer ?

softcut.buffer_read_mono(file,0,1,-1,1,1)

. . . and then having to keep track of that when calculating positions (eg pos-1).

. . . is there an advantage to this ?

Maybe I am missing something here !

I’m struggling to understand the behavior of softcut.rec. I figured it would just enable/disable the voice “tape head” abstraction from writing to the buffer or not, but would otherwise not change its behavior. However, I’m seeing that it seems to change the timing of the recording in mysterious ways.

A minimal reproduction example: take study 4, and swap line 56 from softcut.rec_level(1,rec) to softcut.rec(1,rec). Then, with pre at 1.0 use K2 to “punch in” short segments. You’ll quickly see that the segments when looped are not where they originally were recorded. E.g., record pitch 1 for one second, then immediately turn recording off. When the loop comes back around, immediately when pitch 1 is complete, toggle record on and record pitch 2 for one second, then toggle recording back off. Then repeat again for pitch 3. Instead of pitch 1 and pitch 2 and pitch 3 being back to back, pitch 2 or 3 or both are now at a seemingly random time, often significantly overlapping with pitch 1. (sorry that’s probably hard to follow, but I couldn’t think of a simpler way to explain it).

Is there something I’m misunderstanding about rec, and how it offsets the record head? I’ve even tried using softcut.rec_offset(1, 0) (which I’d think would sync the play head and record head) but that didn’t seem to help either.

sounds like this bug:

since the development-branch rewrite, i haven’t run into this.

but using rec flag dynamically is not ideal anyways; rec_level is better.

Am I reading this correctly to mean “in a yet-unreleased version of norns, we believe the problem is fixed”?

The reason I’m trying to use rec instead of rec_level is I still want pre to apply (e.g., with pre at 0.5, the loop slowly decays in volume, even when rec is off)

EDIT: in my use case, as I’m just using the ADC inputs, I can use level_input_cut to achieve what I was trying to do :+1:

yep

unless i’ve lost my mind, rec_level and pre_level function independently in just this way

each frame:
output = (output * prelevel) + (input + reclevel)

the only reason to toggle rec off completely is to save CPU. it’s not even useful in the present version because you can’t have more than 6 voices regardless. thinking of making a larger number of voices available but only allowing N simultaneous writers. (writing is several times more expensive than reading.)

1 Like

I’ve been trying a bunch of different things out, so I may have misremembered the test results, but I believe that if rec_level is 0, then even if pre_level is 0 for the duration of a whole loop, when you turn pre_level back to 1, the content is still in the buffer. Let me test again real quick :zipper_mouth_face:

EDIT: i was wrong, pre_level still applies even when rec_level is 0

you may be right. if that’s the case it’s an oversight (an attempted “optimization” maybe.)

but i will say:
if rec is zero (the flag, not the level) then the entire write routine is skipped, and there will be no erasure. so occams razor thinks: maybe misremembered.

1 Like

actually i don’t think there is a good reason for this. post-roll material is needed for crossfading. (pre-roll if rate is negative.) but sudden silence in the pre/post period doesn’t help and will cause clicks. it is more important to set loop points such that there is some material after the loop end and before the loop start.

I think what happened was I am still on the fence about whether or not I wanted pre_level to deteriorate the loop when recording is “disarmed” lol. So I just remembered “huh, changing rec_level didn’t do what I wanted” (i.e., didn’t allow the loop to stop deteriorating), but then when I changed what I wanted, I assumed that changing rec_level did the other thing.

Anyway, I think I have the tools now to go either direction depending on which of those I choose – I’ll change rec_level no matter what, and I’ll either also change pre_level to 1.0 when “disarming”, or I’ll leave pre_level alone :+1: Thanks for your help! And happy to hear that the bug is fixed in some upcoming version :smile:

your last suggestion is exactly what i do in mlr, works wonderfully once you wrap your brain around it

1 Like

If I have a running loop with content, and I want to layer on a one-shot recording, how would I do that? I know I can set softcut.loop(recording_voice, 0), but that will just record the portion of the loop from when it’s called until the end of the loop. E.g., if it’s called a second into the loop, then that first second will not be recorded. I’m thinking I want to somehow know exactly when the loop is starting, and call it right then. As far as I can tell, the closest you can get to doing that is using the phase polling system, something like:

softcut.phase_quant(recording_voice, loop_dur)
softcut.event_phase(function(voice, position)
  softcut.loop(recording_voice, 0)
  softcut.rec_level(recording_voice, 1.0)
end)
softcut.poll_start_phase()

Does this guarantee (via a phase of loop_dur, assuming that’s the length of the loop in seconds) that it’ll be called basically right at the start of the loop?

Then the next step: what if I don’t want to wait until the start of the loop? I want to record from right now back around to this same position in the loop again. I could use softcut.phase_quant(recording_voice, current_position) (and make some modifications to the event_phase callback) , but as far as I can tell there’s no way to know the current_position

I guess I could just choose a pretty short phase_quant, on first callback record the position, and treat that as “current position” (so: start recording, then inside the callback see if we’ve made our way back around yet, and if we have stop recording). That may be sufficient (with a very short phase_quant, the “lag” between button press and recording starting would be very short as well), but I was wondering if I was missing something

Does this guarantee (via a phase of loop_dur , assuming that’s the length of the loop in seconds) that it’ll be called basically right at the start of the loop?

if the loop start point is divisible by the phase quantum, then it should work fine to use the phase poll.

e.g. if loop start point is 6.25s and the phase is reported every 1.25 seconds then you can track the value of the poll and know when a loop has just begun.

in the new version (sorry) there is an additional poll that fires on loop points.

using polls, there will be some latency/jitter (audio block size -> OSC -> lua event loop,) but it may be OK for the purpose.

we could also consider adding “meta commands” like “record next loop from the beginning” (which would just modulate rec_level appropriately.) on the backend, this kind of thing can be sample-accurate.


finally, you could just not use softcut’s loop points at all, and instead use a metro on the lua side to trigger 1-shot record and playback. (you would want to do this anyway in a case where you wanted loop timing to be independent of playback rate; if you want it to act more like tape then the phase poll is probably the way to go for now.)

4 Likes

Thanks for the reply, I’d forgotten I’d asked the question . . . !

Thanks for this! This is what I ended up going with and it works just fine – I hadn’t thought to use “non-softcut” tools here, but metro is basically perfect.

I’m now trying to figure out how to “duplicate” a portion of the buffer most effectively back-to-back. I’ve thought about a level_cut_cut approach that uses spare record heads to pipe the currently playing loop into another head that’s writing to a different portion of the buffer, but that seemed more complicated (and also doesn’t happen offline). I’ve now started using buffer_write / buffer_read to basically bounce the buffers to disk then read them back out:

local id = string.match(os.clock(), "....$")
local full_path = "/home/we/dust/audio/boros/tmp-"..id..".wav"
softcut.buffer_write_stereo(full_path, 0, loop_dur)
softcut.buffer_read_stereo(full_path, 0, loop_dur, loop_dur)

this works, but it seems a little silly, so I was wondering if there’s some feature I’m missing? There’s also a small click at the discontinuity that gets introduced where the two buffer portions meet. The reason for doing this vs just looping on the shorter buffer is so that you can add a longer recording on top of a shorter loop.

1 Like

hm, we don’t have any commands like “copy/mix,” but they would be straightforward to add. the work is a little clunky because of (too many) glue layers:

on the crone/c++ side:

(note that for this we don’t need to add to Commands enum because there is no work to do on the audio thread.)

and then on the lua/matron/C side:


added GH issue in case anyone is interested.


i’m guessing that’s because you are reading and writing exactly the loop points, and not accounting for (pre/)post-roll.

with positive playback rates, the buffer is actually read from / written to up to (loop end + fade time).

with negative rates, up to (loop start - fade time).


you may event want to go for a clock if you’re interested in making the looper syncable with midi/link?

4 Likes

Even if I write to disk a bit past loop end, won’t there still be a discontinuity mid-loop because softcut.buffer_read_stereo fully overwrites at start_dst, vs the playheads which would have a bit of cross-fade?

Ooh that does sound interesting, esp. as it’s definitely meant to be used with MIDI/Link

if the playback rate is always positive: no.

crossfades in softcut begin fading in from the loop start point, and begin fading out from the loop endpoint.

this means that the very beginning of the material at (loop_start) will actually be silent.

but on the other hand, timing within the loop is respected: if you start playback at (wall clock = 0), then at (wall clock = 1s) you will be hearing material from exactly 1s from the beginning of the source media (if you read it into the buffer at loop_start.)

in other words: this is the only way to do it given that a command to initiate a crossfade can occur at any time and we want the xfade to begin “as soon as possible.” otherwise, (if we wanted the material at loop_start to be fully audible, after a pre-roll period,) accurate timing would require control latency of (fade_time / 2), or something.