@dan_derks @declutter
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 and pre 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 and pre 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.