A thread to discuss the SoftCut
engine on norns.
any shot you could give more info about this? I have a softcut
script that Iām trying to debug (Norns: code review) and I donāt know whatās wonky softcut
behavior vs poor code. if we could compare experiences, then itād be easier to file a report
Code review! Thatās the thread I was looking for. Should have posted there not here.
- Sometimes it doesnāt work straight after a reboot
- I havenāt worked out how to clear the buffer; and reset the loop end - so my workaround is to set a very long loop end when clearing the buffer
- I get random crashes when using this script. CPU usage is over 200% from the moment it starts recording, which seems excessive.
Iāll have a look at your script too, @dan_derks - much more elegant than mine.
oh, I hard disagree ā cranes is more gilded, but itās lacking a fundamental elegance like your use of if playing == 0 and recording == 0 then
, etc.
thank you for sharing your script! itās clarifying to read.
do you mean the sound engine is sort of bitcrushed upon reboot? or something else? Iāve run into the former a bit.
No, havenāt had that - have just had it refuse to start until I reload the script. But Iāve not done a rigorous test of when it happens or not, I need to work on that.
Now Iāve looked at your code in more detail Iām going to build something from your foundation rather than mine, I hope thatās OK!
Whatās up with reversing the softcut engine? If I run engine.rate(1,-1) while a loop is playing I get all kinds of colourful noise. Do you know anything about how to fix that?
go for it! you might want to build from the cranes in the norns update thatās dropping today, rather than the gist ā cleaned some stuff up over the last few days.
I logged a bug for this: https://github.com/monome/dust/issues/193. It might just be unknowns rather than a direct issue with the engine, since mlr is able to reverse the buffer playback, but Iām definitely excited to add it to cranes!
iām pretty sure this artifact is from writing while reversing. in MLR the write head is never also the play head. in halfsecond
the play head is also always writing, and just varying pre/rec gain levels.
iāll fix this, sorry iāve been so slow, busy couple of months.
completely understandable ā very grateful for the work youāve done. also, that makes total sense and thereās no reasonable expectation for that elasticity to be baked in. Iāll explore MLRās code more and see what I can learn about smarter buffer referencing.
no no, it absolutely should function properly with any parameter settings. the write functionality when rate != 1 is very broken in several ways and needs to be fixed. for now i recommend dedicating a single head for writing, and always leaving rate=1 on that head, but this is a stupid workaround for the lack of more naturalistic ātape-likeā behavior.
not sure how this applies, but in my non-norns explorations, Iāve found my favorite behavior is for the play head to follow the write head while overdubbing, and while not overdubbing for the play head and write head to be separate. this basically lets you use something both like a real time granulator and a normal delay (and all the in-betweens of those).
Yes, of course this is a typical application. (See also: aleph-lines.) Softcut ugen includes a ārecord offsetā parameter. It works. The issue is interpolating writes with fractional rates. More complex than interpolated reads. With rate lag, you sweep through fractional rates on the way to negative rates and there is a glitch. I will fix this given some spare time - there is a working build for x86 on softcut-resample
branch but it has some issue on ARM, so I need a proper debug session on the metal.
Finally I should point out that using softcut as a standard delay line, as you describe, it doesnāt really matter what rate itās running at (unless you are going for decimation artifacts on purpose, or something.) In exactly the same way that tape speed is not a critical parameter for tape delay if you can have arbitrary distance between heads. So just leave rate=1 in such applications and manipulate the offset parameter.
I think my problem with the delay line application was more with the play head and write head being out of phase with each other (which screwed up where the first beat of the delay fell (like, delaying the delays if that makes sense)), but I could just just be confusing myself. might also clarify that Iām saying this in an mlr/granular thing context where the heads are jumping around. but yea also different problem than what youāre describing.
took a deeper look at the norns MLR, but Iām running into a bit of a brick wall with understanding the buffer reference routing. for my final version of cranes, I thought I was setting up voice 2 to read the buffer created from voice 1, but negative values still destroy playback on voice 2.
Iāve kinda hacked together my understanding of softcut
's commands from the engineās sc code. Iām definitely not asking you to dive into my script, but if youāre able: what would a basic command workflow be to tell softcut
to record into a buffer with a single write head and reference that buffer with a separate read head that can be manipulated into reverse playback?
+1 to this question; and related - is engine(2,x) a separate play/record head or a separate āpiece of tapeā?
@dan_derks iām travelling right now, without norns or linux system, but will try to put something together in a couple days. meantime iāll see i can spot anything untoward using SoftCut in SC on mac, right now.
the former. there is actually just one large buffer in the engine. by default the different voices are set to access different, non-contiguous and non-overlapping regions of the buffer. each āvoiceā consists of a read and a write head, which move in lockstep with an arbitrary offset between them. by default the offset is a small (few samples) negative value, putting the write head just behind the read head. so the default behavior is that you hear the old material while writing new material. if you want it to act as a delay, the offset should be positive with rate > 0 and negative with rate < 0.
in fact now that i think about it, this could be related to the issue? if you have negative rate, and you want to hear old material, and you have record enabled on that voice, you should set the offset to be small and positive, rather than small and negative.
[edited the above bit: i confused myself and swapped signs the first time. offset is defined as (read position) - (write position)]
@andrew iām sorry but iām not really following your point (/question?). read/write offset with a buffer is just how you make a digital delay/looper, whether the timing is dynamic or whatever, itās not very mysterious and everything else is implementation detail. the norns SoftCut
engine allows you to arbitrarily run some combination of rd/wr heads (4 of each) synchronously, or asynchronously. and i absolutely agree that having both options is necessary to make a wide variety of applications and effects.
as for your problem (?) iām going to make a wild assumption and guess that you are using max/msp, and a 2nd assumption that you are setting head positions with max messages and (one way or another) running up against the inherent coarseness of timing in the messaging queue. (generally iād say, āthis is a job for gen
!ā (ā¢)) but of course i donāt really know - maybe there is some higher-level design difficulty. we could take a look at your app specifically, on another thread, if you like.
just built softcut for mac and loaded it up in supercollider. AFAICT it is working as expected. no lua layer here, but the SC code sends the same engine commands.
in a nutshell:
- voice 1 and 2 have the same loop endpoints.
- both are looping
- voice 1 records, voice 2 doesnāt (
rec_on
= 1, 0 respectively) - voice 2 playback routed to DAC 1+2
- ADC 1+2 routed to voice 1 record (*)
- voice 1 rate is always ==1
- voice 2 rate is changeable
UI:
- toggles
rec
andpre
levels for voice 1 - scrubs voice 2 rate between -2 and +2
- arbitrarily resets voice 2 position to an arbitrary location
couple other things to note:
-
rec_on
is a hard on/off flag for the record head activation. it will click if you toggle it.rec
andpre
are smoothed record/erase levels. use the former to put a voice in āplay onlyā mode, use the latter for modulations. - i set the loop start point to something > 0, by habit. otherwise, with negative rates, you get potential weirdness at the wrap point.
- if voice 1 and 2 have the same rate, this is a delay line. phase update is quite accurate and and their relative location will not drift significantly.
- oh yeah. when voice 1+2 have different rates, there will be clicks when they cross. there is a mechanism in the engine whereby you can set a āduckingā behavior to avoid this (playback level scales to zero when phases are close), but this probably needs a little more tweaking to be really useful.
so hereās the SC script. i should clarify: this needs the norns
and dust
classes installed, and it needs the norns ugens compiled and installed. it also uses mouse and Qt windows for the test UI. which is an odd setup - i can walk you through it if youāre interested.
s = Server.default;
s.waitForBoot {
Routine {
2.0.wait;
Crone.setEngine("Engine_SoftCut");
2.0.wait;
// our own address, where we will send engine commands
n = NetAddr.localAddr;
// voices 1 and 2 will access the same 4-second buffer region
~start = 1.0;
~end = 5.0;
n.sendMsg("/command/loop_start", 1, ~start);
n.sendMsg("/command/loop_end", 1, ~end);
n.sendMsg("/command/loop_on", 1, 1);
n.sendMsg("/command/loop_start", 2, ~start);
n.sendMsg("/command/loop_end", 2, ~end);
n.sendMsg("/command/loop_on", 2, 1);
// set up the patch matrix:
// - from both ADC channels to voice 1 record
// - from voice 2 playback to both DAC channels
n.sendMsg("/command/adc_rec", 1, 1, 1.0);
n.sendMsg("/command/adc_rec", 2, 1, 1.0);
n.sendMsg("/command/play_dac", 2, 1, 1.0);
n.sendMsg("/command/play_dac", 2, 2, 1.0);
// voice 1 records at constant speed, no overdub (default)
//--- this is a hard on/off flag for rec head
n.sendMsg("/command/rec_on", 1, 1);
//--- this is an overdub level, with smoothing
n.sendMsg("/command/rec", 1, 1.0);
// voice 2 does _not_ record!
n.sendMsg("/command/rec_on", 2, 0);
n.sendMsg("/command/amp", 2, 0.5);
// give voice 2 some exponential rate lag for stupid blzoops
n.sendMsg("/command/rate_lag", 2, 0.5);
// start the things
n.sendMsg("/command/pos", 1, ~start);
n.sendMsg("/command/pos", 2, ~start);
n.sendMsg("/command/start", 1);
n.sendMsg("/command/reset", 1);
n.sendMsg("/command/start", 2);
n.sendMsg("/command/reset", 2);
}.play;
};
// stupid UI:
// - mouse Y changes voice 2 rate when dragging
// - 'z' key jumps voice 2 to position indicated by mouse X.
// - 'x' key toggles recording on/off
w = Window.new(bounds:Rect(0, 0, 400, 400));
l = FlowLayout( w.view.bounds, 10@10, 20@5 );
w.view.decorator = l;
w.view.acceptsMouseOver = true;
w.view.mouseMoveAction = { arg view, x, y, mod, but, click;
n.sendMsg("/command/rate", 2, y.linlin(0, 400, 2, -2));
};
~pos = 1.0;
w.view.mouseOverAction = { arg view, x, y, mod, but, click;
~pos = x.linlin(0, view.bounds.height, ~start, ~end);
n.sendMsg("/command/pos", 2, ~pos);
~pos.postln;
};
~rec = true;
w.view.keyDownAction = { arg view, char, mod, uni, key;
if(char == $z, {
n.sendMsg("/command/reset", 2);
});
if(char == $x, {
if(~rec, {
~rec = false;
n.sendMsg("/command/rec", 1, 0.0);
n.sendMsg("/command/pre", 1, 1.0);
}, {
~rec = true;
n.sendMsg("/command/rec", 1, 1.0);
n.sendMsg("/command/pre", 1, 0.0);
});
});
};
w.front;
hope it helps⦠the norns lua version would be quite a bit tidier, and i can post it later - canāt test it now.
(*) BTW @tehn: i noticed that in halfsecond.lua
, the arguments for ADC routing are backwards. for both ADC and DAC routing commands, first argument is source, second is sink. so adc_rec(2, 1, [level])
routes the second ADC channel to voice 1 record input. i wonder if this is an issue elsewhere as well.
a great deal. thank you, e. i think my misuse of the commands are the culprit for most of my speedbumps. iāll try this flow tomorrow morning!
THIS WAS IT. iāve been keeping the start point 0 on my 2nd varispeed voice. was getting this insane BOOM noise every time it crossed the wrap point at negative rates.
holy moly, ezra, thank you for this direction and spending the time to answer these questions. very excited to pull reverse playback into cranes.
WIP version of cranes with reverse playback: https://gist.github.com/dndrks/c41e3d6a980265817671d17397343919
(mod edit: our discussion of cranes and @declutterās looper opened up a great deal of SoftCut-specific dialogue, to which @zebra + @andrew were kind enough to contribute thoughts + direction. to preserve the purpose of the code review, Iāve migrated that discussion to this thread)