Compare commits

...

52 Commits

Author SHA1 Message Date
bombinans 3578b74059 Merge pull request 'feature-synchronisation' (#11) from feature-synchronisation into main
Reviewed-on: #11
2023-10-25 23:47:39 +00:00
bombinans 5e0e9df5f4 reinstated step playback 2023-10-26 10:44:21 +11:00
bombinans 9f7b83a44e Quantised speed controls are back 2023-10-25 15:19:31 +11:00
bombinans 4f4132fdcc Fixed playback issues 2023-10-25 14:34:23 +11:00
bombinans 58117762a6 First working version of position buffers as envelopes triggered from
a Pattern so that they are guaranteed to be in sync with the tempoclock
2023-10-25 12:54:05 +11:00
bombinans 5b646677c4 Added all the changes made in Looptober 2023 so far 2023-10-23 11:52:15 +11:00
bombinans 3ddc8cff47 Fixed phase bugs in position control synths 2023-10-07 16:17:45 +11:00
bombinans d0be4a3b67 Merge pull request 'MIDI sequencer and timing improvements' (#8) from feature-midi-sequencer into main
Reviewed-on: #8
2023-10-06 21:00:42 +00:00
bombinans fc1b68da06 Put all of the timing stuff in main 2023-10-07 07:58:14 +11:00
bombinans bc282aa727 Sorted out separation between sequencer and interface code, added
harmonic quantisation for chorus
2023-10-01 14:58:20 +11:00
bombinans 90e4ad3032 Moved metronome out to sequencer.scd 2023-10-01 13:21:40 +11:00
bombinans e1c1a5b82d Very rough metronome 2023-09-30 18:21:29 +10:00
bombinans 9ba8700d8a A few little bugs cleaned up 2023-09-30 18:11:55 +10:00
bombinans de332e6c48 Fixed position buffer amplitude bug 2023-09-30 17:24:29 +10:00
bombinans e22b46066a Removed redundant interface code block 2023-09-30 17:24:15 +10:00
bombinans 1f6210c6d8 Fixed reset bug 2023-09-30 17:24:01 +10:00
Mike Lynch 37f2d6677c Merge branch 'feature-multitrack' 2023-09-23 15:07:21 +10:00
Mike Lynch 49133e06c6 removed quantspeed guff 2023-09-23 15:06:41 +10:00
Mike Lynch 31172455cc Made slope default to off 2023-09-23 15:06:26 +10:00
Mike Lynch 3d08975c3f Made the record button save and restore level 2023-09-23 15:06:05 +10:00
Mike Lynch 3ac1c3cd47 Fixed quantspeed 2023-09-23 15:05:33 +10:00
Mike Lynch 932c04717a Added routine to dump all four buffers 2023-09-23 15:05:08 +10:00
Mike Lynch f880ac5831 Added a trigger to sync the recorder with the playback buffer
Also turns off granulator input mix when recording stops
2023-09-17 10:11:31 +10:00
Mike Lynch a83b8be362 Separated input from output effects 2023-04-24 16:49:36 +10:00
Mike Lynch b5010ed3e4 Merge branch 'feature-multitrack' 2023-04-24 15:56:26 +10:00
Mike Lynch d27c8388d3 added .gitignore 2023-04-24 15:56:14 +10:00
Mike Lynch e249d39f7f Got server syncing working, moved granulator setup to its own file 2023-04-24 15:54:24 +10:00
Mike Lynch 4c5cc176f3 Fixed interface glitches, made trigger/size have an xy control 2023-04-16 17:45:15 +10:00
Mike Lynch c8ab06dfc4 Working out a lot of kinks in the interface 2023-04-15 16:03:16 +10:00
Mike Lynch d2564c5757 Still trying to get all the little bits of the interface code right 2023-04-10 17:13:43 +10:00
Mike Lynch 90c7df0227 Further bout of refactoring - pitch and trigger are now in the
Granulator class
2023-04-10 16:25:04 +10:00
Mike Lynch 0040840850 Track selection is just about not broken 2023-04-09 16:44:59 +10:00
Mike Lynch dd1292f5bd TouchOSC interface basics are all working 2023-04-09 15:32:36 +10:00
Mike Lynch 7149bca2e3 About half of the touchosc stuff is now working 2023-04-09 15:10:04 +10:00
Mike Lynch dfc508ee02 Multiple pb control synths working in basic ways 2023-04-09 12:32:11 +10:00
Mike Lynch 735a1712ea spare parts 2023-04-09 12:31:39 +10:00
Mike Lynch 7b8fde4afc TouchOSC stuff to interface.scd 2023-04-09 12:12:36 +10:00
Mike Lynch e32a30b6fc added .gitignore 2023-04-09 11:48:46 +10:00
Mike Lynch 0b91c0dc9e grains -> main 2023-04-09 11:46:19 +10:00
Mike Lynch 30202f3add Split more stuff out 2023-04-09 11:46:05 +10:00
Mike Lynch 0b589bc092 Effects into separate files 2023-04-09 11:42:17 +10:00
Mike Lynch fdf92ef4ba Refactoring with granulator in a quark 2023-04-07 16:16:05 +10:00
Mike Lynch 86fe427429 Multitrack is working - needed a separate recorder and granulator
for each buffer, and it's still creaky
2023-04-02 17:59:53 +10:00
Mike Lynch 8a74f8612f Multitrack is working for recording 2023-04-02 16:31:52 +10:00
Mike Lynch 000a6d3586 Cacky, not-working multitrack 2023-04-02 16:10:23 +10:00
Mike Lynch 5a86740af4 Cleaned up some old bugs, replaced the triangle grain pattern with
a step
2023-03-29 18:21:32 +11:00
Mike Lynch 6b3ace1dfd Improved the harmonics, made the chorus and detune be triggered by the trigger bus, fixed a bug in dust triggers 2022-04-24 16:35:34 +10:00
Mike Lynch 2ddf66b288 Added multiple LFO mods and control of base and quantised pitch 2022-04-23 14:28:37 +10:00
Mike Lynch 69c0d16a65 Effects chain works 2022-04-19 08:29:26 +10:00
Mike Lynch e5bfc0d79d Sorted out a lot of bugs 2022-04-16 16:35:31 +10:00
Mike Lynch d42a177db9 Trigger and rate now coming from their own synths 2022-04-16 15:46:54 +10:00
Mike Lynch 382c720b6d To-do 2022-04-16 11:37:15 +10:00
13 changed files with 673 additions and 401 deletions

1
.gitignore vendored 100644
View File

@ -0,0 +1 @@
.DS_Store

View File

@ -1,21 +1,53 @@
TODO
# TODO
- Convert the granulator (including the buffer stuff) to a class
- quantise playback speed to rational values
- display the playback and harmonics when quantised
- Convert the TouchOSC interface library to a class
- multiple grain buffers - TouchOsc interface to select which to send to
- play back all buffers?
These will involve some disintermingling of code
- refactor for multitrack
- which controls are per-track and which are global? sort these out in the UI
- encapsulate a grainstrack in an object?
Ideally I want to be able to use the granulator, TouchOSC and the
midi controller as part of the same session, so do stuff like
- auto-mix: base the mix level on how loud the incoming signal is so that tracks don't fade out
- fancier playback:
- intertwine different rates and directions
t = TouchOSC("192.168.0.30")
- rhythm controls
- number of steps in step granulator
- modulate grain level in time with playback
- sync LFOs to playback
g = Granulator()
TODO list - touchosch
k = MidiKnobs();
URL TO SC
grains/buflen Y Y
grains/reset Y Y
grains/record0..3 Y Y
grains/mode0..3 Y Y
grains/speed0..3 Y Y
grains/dust Y Y
grains/slope Y Y
grains/back Y Y
grains/trigger Y Y
grains/speedlock
grains/speedquant
grains/mix0 Y
grains/mix1 Y
grains/mix2 Y
grains/mix3 Y
t.bind('/grain', 0, 1, 0.5, { |self| g.set("amp", self.v) });
trackselect
t.bind(
track/record
track/mode
track/speed
track/size
track/blur
track/mix
track/pan
track/track
track/jitter

44
TODO.md
View File

@ -1,37 +1,27 @@
TODO
====
## Basic interface stuff
Monday-Tuesday to-do
Write default settings to the interface on startup <-- done
Use server.sync to speed up booting
Try to get all the common interfaces on one page
- re-route the effects so that it goes
input -> filter -> delay -> granulator -> reverb
not input -> granulator -> effects
Synchronise speeds across granulators
Quantise speeds
that's enough!
--
## Musical
Later:
Test things like really rapid playback
vibrato and tremolo
Pitch-shifting (tuned and untuned)
LFO Modulate the filter <-- done
LFO Modulate the granulator settings
Separate panel for input effects: distort and overdrive
Sync timining of granule playback to buffer length / speed
Timing based on beat detection
## Advanced interface
Save current patch / load patch <-- Done
Save the current buffer! - if this is incorporated with current settings, it's a way to save how the granulator is playing, and then resume. Which is good for live stuff and also for overdubbing
SuperCollider seems to have the ability to read and write files, but not scan directories, so the patch-saver will have to maintain its own index file
patch = file with settings, including a link to the buffer sample
more playback modes

32
control.scd 100644
View File

@ -0,0 +1,32 @@
(
//input mixing, effects and lfos
~inputb = Bus.audio(s, 1); // bypassing this one for now
~infxb = Bus.audio(s, 1);
~inmixer = SynthDef(
\input_null,
{
arg in1 = 2, in2 = 3, out = 4;
Out.ar(out, In.ar(in1) + In.ar(in2));
}
).play(s, [\in1, ~usbinput1, \in2, ~usbinput2, \out, ~infxb]);
// LFO buses and synths
~lfoab = Bus.control(s, 1);
~lfobb = Bus.control(s, 1);
~lfocb = Bus.control(s, 1);
~lfoa = Synth(\lfo, [\out, ~lfoab ]);
~lfob = Synth(\lfo, [\out, ~lfobb ]);
~lfoc = Synth(\lfo, [\out, ~lfocb ]);
"LFOs running".postln;
)

76
effects.scd 100644
View File

@ -0,0 +1,76 @@
// this is just output effects now
(
~outfxb = Bus.audio(s, 2);
~filterb = Bus.audio(s, 2);
~delayb = Bus.audio(s, 2);
~reverbb = Bus.audio(s, 2);
~grainmixer = SynthDef(
\grain_mixer,
{
arg in=2, grainb=4, out=0, passthrough=0.75, grains=1;
Out.ar(out, (grains * In.ar(grainb, 2)) + (passthrough * In.ar(in, 1) ! 2));
}
).play(s, [\in, ~infxb, \grainb, ~grainsb, \out, ~outfxb ], \addToTail);
// filter is after grains again
~filtermodb = Bus.control(s, 1);
~filtermod = SynthDef(
\filtermod, {
arg out, a = 1.0, b = 0.0, c = 0.0;
var siga, sigb, sigc;
siga = In.kr(~lfoab) * a;
sigb = In.kr(~lfobb) * b;
sigc = In.kr(~lfocb) * c;
Out.kr(out, Wrap.kr(siga + sigb + sigc, -1, 1));
}
).play(s, [\out, ~filtermodb, \a, 1, \b, 0, \c, 0 ]);
~filter = SynthDef(
\filter, {
arg in, out, mod, freq=10000, res=0.3, amp=1.0;
var filt, lfo;
lfo = LinExp.kr(In.kr(mod, 1), -1, 1, freq * 0.5, freq * 2);
filt = RLPF.ar(In.ar(in, 2) * amp, lfo, res);
Out.ar(out, filt);
}
).play(s, [ \in, ~outfxb, \out, ~filterb, \mod, ~filtermodb, \amp, 0.5], \addToTail);
// delay always passes through 100% of its input + amp % of the delay
~delay = SynthDef(
\delay, {
arg in, out, maxdelay=1, delaytime=0.2, decaytime=0.1, amp=0.5;
var sig = In.ar(in, 2), del;
del = CombC.ar(sig, maxdelay, delaytime, decaytime, amp);
Out.ar(out, sig + del);
}
).play(s, [ \in, ~filterb, \out, ~delayb ], \addToTail);
~reverb = SynthDef(
\reverb, {
arg in, out, mix=0.33, room=0.5, damp=0.5, amp=0.25;
var input = In.ar(in, 2);
Out.ar(out, input + FreeVerb2.ar(input[0], input[1], mix, room, damp, amp));
}
).play(s, [ \in, ~delayb, \out, 0 ], \addToTail);
"Effects running".postln;
)

View File

@ -1,363 +0,0 @@
Server.killAll
// Execute this before booting the server
Server.default.options.inDevice_("Scarlett 2i2 USB");
// TODO - FIXME
// [X] mode switching isn't working
// [ ] playback speed isn't working
// [ ] lfo filter modulation isn't working
// [ ] setting parameters from defaults at startup like passthrough
s.sampleRate
(
~to = TouchOSC("192.168.0.209", 9000);
~usbinput = 2;
~buflen = 4.0;
~beatsperbar = 4;
// trying setting the playback LFOs before the controls
// granulator playback modes
// each of these is a control bus with a synth that drives the pattern
// the granulator mode control switches between them
// more ideas for modules: scramble - do a permutation of ABCDEFGH slots
// todo - encapsulate these in a class
~grainsinb = Bus.control(s, 1);
~grainsin = SynthDef(
\grainsin,
{
arg out=5, speed=1;
Out.kr(out, 0.5 + SinOsc.kr(speed, 0, 0.5));
}
).play(s, [\out, ~grainsinb, \speed, 1]);
~grainsawb = Bus.control(s, 1);
~grainsaw = SynthDef(
\grainsaw,
{
arg out=5, speed=1;
Out.kr(out, 0.5 + LFSaw.kr(speed, 0, 0.5));
}
).play(s, [\out, ~grainsawb, \speed, 1]);
~grainreverseb = Bus.control(s, 1);
~grainreverse = SynthDef(
\grainreverse,
{
arg out=5, speed=1;
Out.kr(out, 0.5 - LFSaw.kr(speed, 0, 0.5));
}
).play(s, [\out, ~grainreverseb, \speed, 1]);
~graintrib = Bus.control(s, 1);
~graintri = SynthDef(
\graintri,
{
arg out=5, speed=1;
Out.kr(out, 0.5 + LFTri.kr(speed, 0, 0.5));
}
).play(s, [\out, ~graintrib, \speed, 1]);
~grainrandb = Bus.control(s, 1);
~grainrand = SynthDef(
\grainrand,
{
arg out=5, speed=1;
Out.kr(out, 0.5 + WhiteNoise.kr(0.5));
}
).play(s, [\out, ~grainrandb, \speed, 1]);
~modes = [
[ ~grainsaw, ~grainsawb, "saw" ],
[ ~grainreverse, ~grainreverseb, "reverse", ],
[ ~grainsin, ~grainsinb, "sine" ],
[ ~graintri, ~graintrib, "triangle" ],
[ ~grainrand, ~grainrandb, "random" ]
];
~playbacklfo = ~modes[0][0];
~playbacklfob = ~modes[0][1];
// audio buses
// recordb = input to bufrecorder
// granulatorb = output from granulator
~recordb = Bus.audio(s, 1);
~infilter = SynthDef(
\input_null,
{
arg in = 2, out = 4;
Out.ar(out, In.ar(in));
}
).play(s, [\in, ~usbinput, \out, ~recordb]);
~granulatorb = Bus.audio(s, 2);
// LFO bus and synth used to modulate the filter
~lfob = Bus.control(s, 1);
~lfo = SynthDef(
\lfo,
{
arg out=5, freq=1, amp=0;
Out.kr(out, SinOsc.kr(freq, 0, amp));
}
).play(s, [\out, ~lfob, \freq, 1, \amp, 0]);
// buffer recorder
~frippbuffer = Buffer.alloc(s, s.sampleRate * ~buflen, 1);
~bufrecorder = SynthDef(
\fripp_record,
{
arg in = 2, fb = 4, buffer = 0, mix = 0.25, record = 0.0, feedback = 0.0;
var insig, fbsig;
insig = record * In.ar(in, 1);
fbsig = feedback * Mix.ar(In.ar(fb, 2));
RecordBuf.ar(insig + fbsig, buffer, 0, mix, 1 - mix, loop: 1)
}
).play(s, [\in, ~recordb, \record, 1.0, \fb, ~granulatorb, \out, 0, \buffer, ~frippbuffer], \addToTail);
// the main granulator synth
// todo - different styles of trigger
~granulator = SynthDef(
\grainsynth,
{
arg out=0, modb, trate=120, size=12, rate=1, posb=5, amp=1.0, freq=10000, rq=0.3, pan=0, track=0.25, jitter=0, chorus=0.0, blur=0.0, dust = 0, buffer;
var dur, blen, clk, chor, pos, pans, grains, filtfreq;
dur = size / trate;
clk = (Impulse.kr(trate) * (1 - dust)) + (Dust.kr(trate) * dust);
chor = chorus * 2.pow((LFNoise0.kr(trate) + 0.5).floor) + (1 - chorus);
blen = BufDur.kr(buffer);
pos = Wrap.kr(In.kr(posb, 1) + WhiteNoise.kr(blur), 0, 1);
pans = pan + WhiteNoise.kr(jitter) + (track * (In.kr(posb, 1) - 1));
filtfreq = (In.kr(modb, 1) * freq * 0.5) + freq;
grains = TGrains.ar(2, clk, buffer, chor * rate, pos * blen, dur, pans, amp);
Out.ar(out, RLPF.ar(grains, filtfreq, rq));
}
).play(s, [\out, ~granulatorb, \buffer, ~frippbuffer, \posb, ~grainsawb, \modb, ~lfob]);
~mixerb = Bus.audio(s, 2); // this is what we will record from
~mixer = SynthDef(
\mixer_synth,
{
arg in = 2, gbus = 4, out = 0, amp = 1.0, passthrough = 0.0;
//Out.ar(out, In.ar(gbus, 2));
Out.ar(out, (amp * In.ar(gbus, 2)) + (passthrough * In.ar(~recordb, 1) ! 2));
}
).play(s, [\in, ~usbinput, \out, ~mixerb, \gbus, ~granulatorb, \amp, 1.0, \passthrough, 0.0], \addToTail);
~monitor = SynthDef(
\monitor_synth,
{
arg in=2, out=0;
Out.ar(out, In.ar(in, 2))
}
).play(s, [\in, ~mixerb, \out, 0 ], \addToTail);
// sync the server so that all the synths are ready for the touchosc stuff
)
// s.sync(); // this needs to be done in a routine because it calls yield
// sidebar -
ServerMeter.new(s, 8, 8);
~mixer.set(\in, 0);
~recordb;
~monitor.set(\in, ~usbinput);
~mixer.set(\in, 0);
(
OSCdef.freeAll;
~to.button('/record', 1, { | v | ~bufrecorder.set(\record, v) });
~to.button('/reset', 0, { | v |
if( v > 0, {
var sp = ~to.v('/grains/speed')[0];
~buflen = ~to.v('/grains/buflen');
[ "resetting buffer to", ~buflen ].postln;
~newbuffer = Buffer.alloc(s, s.sampleRate * ~buflen, 1);
~granulator.set(\buffer, ~newbuffer);
~bufrecorder.set(\buffer, ~newbuffer);
if( ~frippbuffer.isNil.not, { ~frippbuffer.free });
~frippbuffer = ~newbuffer;
~playbacklfo.set(\speed, sp / ~buflen);
});
});
~to.slider('/mix', 0.25, TouchOSCScale(0, 1), { |v| ~bufrecorder.set(\mix, v) } );
~to.slider('/gain', 0.5, TouchOSCScale(0, 1), { |v| ~granulator.set(\amp, v) } );
~to.slider('/passthrough', 0.5, TouchOSCScale(0, 1), { |v| ~mixer.set(\passthrough, v) } );
~to.slider('/feedback', 0, TouchOSCScale(0, 0.25), { |v|
~bufrecorder.set(\feedback, v) } );
~to.button('/grains/bpm', "~", {});
~tapper = TapBeats();
~to.button('/grains/tap', 0, { | v, t |
if( v > 0, {
~tapper.tap(t);
if( ~tapper.bpm.isNil.not, {
~to.v_('/grains/bpm', ~tapper.bpm.round(1));
})
})
});
~to.button('/grains/bpmsend', 0, { | v |
if( ~tapper.bpm.isNil.not, {
var bl = ~beatsperbar * ~tapper.bpm;
~to.v_('/grains/buflen', bl);
~to.v_('/reset', 1);
});
});
// note: ~buflen is the variable for buffer length, which only gets set to
// ~to.v('/grains/buflen') when the buffer is reset with the clear button
~to.slider('/grains/buflen', ~buflen, TouchOSCScale(0.1, 10.0), {});
// this is a write-only control to display where the buffer playback is at
~to.slider('/grains/buffer', 0, TouchOSCScale(0, 1), {});
~to.xy('/grains/speed', [ 1, 40 ], TouchOSCScale(0, 2), TouchOSCScale(0, 120), { | v |
~playbacklfo.set(\speed, v[0] / ~buflen);
~granulator.set(\trate, v[1] / ~buflen);
});
~to.button('/grains/mode', 0, { |v|
var mode = ~modes[v];
"mode".postln;
[ v, mode ].postln;
if( mode.isNil.not, {
~granulator.set(\posb, mode[1]);
~playbacklfo = mode[0];
~playbacklfob = mode[1];
~playbacklfo.set(\speed, ~to.v('/grains/speed')[0]);
}, {
[ "Bad mode index", v ].postln;
});
});
~to.button('/grains/dust', 0, { |v| ~granulator.set(\dust, v) });
~to.slider('/grainf/blur', 0, TouchOSCScale(0, 1), { |v| ~granulator.set(\blur, v) });
// todo vvv quantise speed should be swappable
// var trate, qspeed;
// qspeed = 2.pow(v[0].floor);
// ~playbacklfo.set(\speed, qspeed / ~buflen);
// [ "speed", v[0], qspeed, qspeed / ~buflen ].postln;
// trate = 2.pow(v[1].floor) / ~buflen;
// ~granulator.set(\trate, trate);
~to.slider('/grains/size', 12, TouchOSCScale(0, 20),{ |v| ~granulator.set(\size, v) });
// Page 2: grainfx
~to.slider('/grains/blur', 0, TouchOSCScale(0, 1), { |v| ~granulator.set(\blur, v) });
~to.button('/grainfx/back', 0, { |v| ~granulator.set(\rate, if( v > 0, { -1 }, { 1}))});
~to.button('/grainfx/slope', 1, { |v| });
~to.button('/grainfx/chorus', 0, { |v| ~granulator.set(\chorus, v) });
~to.slider('/grainfx/pan', 0, TouchOSCScale(-1, 1), { |v| ~granulator.set(\pan, v) });
~to.slider('/grainfx/track', 0, TouchOSCScale(-1, 1), { |v| ~granulator.set(\track, v) });
~to.slider('/grainfx/jitter', 0, TouchOSCScale(0, 1), { |v| ~granulator.set(\jitter, v) });
// pitch gets quantised to octaves from 3 below to 3 above.
// NOTE: the pitch TouchOSC control is -1 to 1, not 0 to 1
// min/max gets ignored because I'm overloading the ctrlset/get
// TODO: fixme,
// ~to.slider('/grainfx/pitch', -1, 1, 1,
// { |self| ~granulator.set("rate", self.v) },
// { |self, ctrlv | self.v = 2.pow((ctrlv * 3).floor) },
// { |self| self.v.log2.floor / 3; }
// );
~to.xy(
'/fx/filter',
[ 10000, 0.3 ],
TouchOSCScale(200, 10000),
TouchOSCScale(0.1, 1),
{ |v|
~granulator.set(\freq, v[0]);
~granulator.set(\res, v[1]);
}
);
~to.slider('/fx/lfofreq', 0.5,TouchOSCScale(0.001, 4), { |v| ~lfo.set(\freq, v) } );
~to.slider('/fx/lfoamp', 0, TouchOSCScale(0, 1), { |v| ~lfo.set(\amp, v) });
)
(
~posdisplay = Task.new({
{
~playbacklfob.get({ | v |
~to.v_('/grains/buffer', v)
});
0.02.wait;
}.loop;
});
~posdisplay.start;
)
~to.slider('/grains/blur', 0, TouchOSCScale(0, 1), { |v| ~granulator.set(\blur, v) });
~posdisplay.stop;
~playbacklfob;
~posb;

98
granulator.scd 100644
View File

@ -0,0 +1,98 @@
(
~modes = [
[ "saw", \pos_saw ],
[ "reverse", \pos_reverse ],
[ "sine", \pos_sine ],
[ "step", \pos_step ],
];
~outputDir = Platform.recordingsDir +/+ "GrainBuffers";
~grainsb = Bus.audio(s, 2);
~granulators = Array.new(4);
~grainmodes = [ 0, 0, 0, 0 ]; // keep track of mode so don't swap if not needed
~speeds = [ 1, 1, 1, 1 ]; // hacky speed quantisation
~posb = Array.new(4);
~rectriggerb = Array.new(4);
~patterns = [ nil, nil, nil, nil ];
~players = [ nil, nil, nil, nil ];
~loopsynths = [ nil, nil, nil, nil ];
// create the control busses
(0..3).do({
~posb.add(Bus.control(s, 1));
~rectriggerb.add(Bus.control(s, 1));
});
// start the granulators
(0..3).do({ |i|
var pb = ~posb[i], rtb = ~rectriggerb[i];
~granulators.add(Granulator.new(~buflen, ~infxb, ~grainsb, pb, rtb));
});
// set up the Patterns which drive the position synths
// (0..3).do({ |i|
// ~patterns.add(~makePattern.value(i, 0, ~speeds[i]));
// });
//
// (0..3).do({|i|
// ~players.add(~patterns[i].play(~tc, quant: ~beatsperbar))
// });
~setmode = {
arg track, mode;
~grainmodes[track] = mode;
if(~players[track].isNil.not,{
~players[track].stop;
~patterns[track].free;
});
~patterns[track] = ~makePattern.value(track, mode, ~speeds[track]);
~players[track] = ~patterns[track].play(~tc, quant: ~beatsperbar);
};
~setspeed = {
arg track, speed;
if( ~speeds[track] != speed, {
~speeds[track] = speed;
~players[track].stop;
~patterns[track].free;
~patterns[track] = ~makePattern.value(track, ~grainmodes[track], ~speeds[track]);
~players[track] = ~patterns[track].play(~tc, quant: ~beatsperbar);
});
};
~makePattern = {
arg track, mode, speed;
var ptrig, ppos, synth = ~modes[mode][1];
// note: trigger is going off the base tempoclock, not the playback speed - I think this is
// the right thing to do but I'm not sure yet
ptrig = Pbind(
\instrument, \trigger,
\dur, ~beatsperbar,
\out, ~rectriggerb[track]
);
ppos = Pbind(
\instrument, synth,
\dur, ~beatsperbar / speed,
\length, ~buflen / speed,
\out, ~posb[track]
);
Ppar([ptrig, ppos]);
};
~dumpbuffers = { |prefix|
(0..3).do({|i|
var filename = ~outputDir +/+ prefix ++ 'buffer' ++ i.asString ++ '.aiff';
~granulators[i].buffer.write(filename);
});
}
)

53
input_effects.scd 100644
View File

@ -0,0 +1,53 @@
(
~fuzzbox = SynthDef(
\fuzzbox,
{
arg in=2, out=4, distort=0.1, decay=0.999;
var raw, cross, pf;
raw = In.ar(in, 1).softclip;
cross = CrossoverDistortion.ar(raw, 0.5, 0.5);
pf = PeakFollower.ar(raw, decay);
Out.ar(out, ((1 - distort) * raw) + (distort * pf * cross));
}
).play(s, [\in, ~usbinput, \out, ~recordb, \distort, 0 ]);
// ~decimator = SynthDef(
// \decimator,
// {
// arg in=2, out=4, modb, rate=10000, smooth=0.5;
// var raw, mod, decimated;
// raw = In.ar(in, 1);
// mod = In.kr (modb, 1);
// decimated = SmoothDecimator.ar(raw, rate + (0.2 * rate * mod), smooth);
// Out.ar(out, decimated);
// }
// ).play(s, [\in, ~usbinput, \out, ~recordb, \modb, ~lfob, \rate, 10000 ]);
//
// ~localmax = SynthDef(
// \localmax,
// {
// arg in=2, out=4, threshold=25;
// var chain;
// chain = FFT(LocalBuf(2048), In.ar(in, 1).distort);
// chain = PV_LocalMax(chain, threshold);
// Out.ar(out, IFFT.ar(chain));
// }
// ).play(s, [\in, ~usbinput, \out, ~recordb, \threshold, 25 ]);
//
// ~scramble = SynthDef(
// \scramble,
// {
// arg in=2, out=4, shift=1;
// var chain;
// chain = FFT(LocalBuf(2048), In.ar(in, 1).distort);
// chain = PV_BinScramble(chain, 0.5, 0.2, Impulse.kr(shift));
// Out.ar(out, IFFT.ar(chain));
// }
// ).play(s, [\in, ~usbinput, \out, ~recordb, \shift, 1 ]);
//
)

200
interface.scd 100644
View File

@ -0,0 +1,200 @@
(
~to = TouchOSC(~touchosc_ip, 9000);
~tracknum = 0;
~granulator = ~granulators[0];
~speedlock = 0;
~speedquant = 0;
OSCdef.freeAll;
~to.button('/grains/reset', 0, { | v |
if( v > 0, {
~buflen = ~to.v('/grains/buflen');
(0..3).do({|i|
~granulators[i].reset(~buflen);
});
});
});
~quantspeed = { |v| 2.pow((v * 4 + 0.5).round - 5) };
~quantharmonics = {
arg f, quantise=0;
if(quantise != 0, {
var fraction = f.asFraction(7, false);
fraction[0] / fraction[1];
},
{ f }
);
};
// setrecord: toggles record on (1) or off (0) for a track, and also sets the
// track's input mix to 0 so that it doesn't start fadeing out. If the track
// is the currently selected track, set its touchosc control to 0.
// keeps the level for each track in an array and resets it
~mixlevel = Array.new(4);
(0..3).do({~mixlevel.add(0.25)});
~setrecord = { | track, v |
~granulators[track].record_(v);
if(v == 0, {
~mixlevel[track] = ~granulators[track].mix;
~granulators[track].mix_(0);
},
{
~granulators[track].mix_(~mixlevel[track]);
});
if( track == ~tracknum, { ~to.v_('/track/mix', ~granulators[track].mix); })
};
// note: ~buflen is the variable for buffer length, which only gets set to
// ~to.v('/grains/buflen') when the buffer is reset with the clear button
~to.slider('/grains/buflen', ~buflen, TouchOSCScale(0.1, 10.0), {});
~to.button('/grains/mode0', 0, { |v| ~setmode.value(0, v) });
~to.button('/grains/mode1', 0, { |v| ~setmode.value(1, v) });
~to.button('/grains/mode2', 0, { |v| ~setmode.value(2, v) });
~to.button('/grains/mode3', 0, { |v| ~setmode.value(3, v) });
// needs work
~to.slider('/grains/speed0', 1, TouchOSCScale(0, 2), { |v| ~setspeed.value(0, ~quantspeed.value(v)) });
~to.slider('/grains/speed1', 1, TouchOSCScale(0, 2), { |v| ~setspeed.value(1, ~quantspeed.value(v)) });
~to.slider('/grains/speed2', 1, TouchOSCScale(0, 2), { |v| ~setspeed.value(2, ~quantspeed.value(v)) });
~to.slider('/grains/speed3', 1, TouchOSCScale(0, 2), { |v| ~setspeed.value(3, ~quantspeed.value(v)) });
~to.slider('/grains/passthrough', 0.75, TouchOSCScale(0, 1), { |v| ~grainmixer.set(\passthrough, v) });
~to.slider('/grains/mix0', 0.5, TouchOSCScale(0, 1), { | v | ~granulators[0].gain_(v) });
~to.slider('/grains/mix1', 0.5, TouchOSCScale(0, 1),{ | v | ~granulators[1].gain_(v) });
~to.slider('/grains/mix2', 0.5, TouchOSCScale(0, 1),{ | v | ~granulators[2].gain_(v) });
~to.slider('/grains/mix3', 0.5, TouchOSCScale(0, 1),{ | v | ~granulators[3].gain_(v) });
~to.button('/grains/lock', 0, { |v| ~speedlock = v });
~to.button('/grains/quant', 1, { |v| ~speedquant = v });
~to.button('/grains/metronome', 0, { |v|
if( v == 1, {
~bps = ~beatsperbar / ~buflen;
~tc.tempo_(~bps);
});
~metromix.set(\amp, v)
});
// Page 2: track
~to.xy('/track/triggersize', [ 100, 0.125 ], TouchOSCScale(0, 200), TouchOSCScale(0, 1), { |v|
~granulator.trigger_(v[0]);
~granulator.size_(v[1]);
});
~to.slider('/track/blur', 0, TouchOSCScale(0, 1.0), { |v| ~granulator.blur_(v) });
~to.button('/track/dust', 0, { |v| ~granulator.dust_(v) });
~to.button('/track/back', 0, { |v| ~granulator.back_(v)});
~to.button('/track/slope', 1, { |v| ~granulator.slope_(v) });
~to.button('/track/chorus', 0, { |v| ~granulator.chorus_(v) });
~to.slider('/track/harmonics', 2, TouchOSCScale(0.5, 3), { |v|
~granulator.harmonics_(~quantharmonics.value(v, 1))
});
~to.slider('/track/detune', 0, TouchOSCScale(0, 0.059), { |v| ~granulator.detune_(v) });
~to.slider('/track/pitch', 0, TouchOSCScale(-2, 2), { |v| ~granulator.pitch_(v.round) });
~to.slider('/track/mix', 0.25, TouchOSCScale(0, 1), { |v| ~granulator.mix_(v); });
~to.slider('/track/pan', 0, TouchOSCScale(-1, 1), { |v| ~granulator.pan_(v) });
~to.slider('/track/track', 0.5, TouchOSCScale(-1, 1), { |v| ~granulator.track_(v) });
~to.slider('/track/jitter', 0.25, TouchOSCScale(0, 1), { |v| ~granulator.jitter_(v) });
~to.button('/trackselect', 0, { |v|
~tracknum = v.asInteger;
~granulator = ~granulators[~tracknum];
~to.v_('/track/triggersize', [~granulator.trigger, ~granulator.size]);
~to.v_('/track/blur', ~granulator.blur);
~to.v_('/track/mix', ~granulator.mix);
~to.v_('/track/pan', ~granulator.pan);
~to.v_('/track/track', ~granulator.track);
~to.v_('/track/jitter', ~granulator.jitter);
~to.v_('/track/dust', ~granulator.dust);
~to.v_('/track/slope', ~granulator.slope);
~to.v_('/track/back', ~granulator.back);
~to.v_('/track/chorus', ~granulator.chorus);
~to.v_('/track/harmonics', ~granulator.harmonics);
~to.v_('/track/detune', ~granulator.detune);
~to.v_('/track/pitch', ~granulator.pitch);
});
// set up the record buttons on the front page now because /track/mix has been defined
~to.button('/grains/record0', 0, { | v | ~setrecord.value(0, v) });
~to.button('/grains/record1', 0, { | v | ~setrecord.value(1, v) });
~to.button('/grains/record2', 0, { | v | ~setrecord.value(2, v) });
~to.button('/grains/record3', 0, { | v | ~setrecord.value(3, v) });
~to.slider(
'/fx/filterfreq',
10000, TouchOSCScaleExp(100, 10000), { |v| ~filter.set(\freq, v) }
);
~to.slider('/fx/grainmix', 1.0, TouchOSCScale(0, 1), { |v| ~grainmixer.set(\grains, v) } );
~to.slider('/fx/filtermix', 0.8, TouchOSCScale(0, 1), { |v| ~filter.set(\amp, v) } );
~to.button('/fx/filtermoda', 1, { |v| ~filtermod.set(\a, v) });
~to.button('/fx/filtermodb', 0, { |v| ~filtermod.set(\b, v) });
~to.button('/fx/filtermodc', 0, { |v| ~filtermod.set(\c, v) });
~to.slider('/fx/delay', 0.2,TouchOSCScale(0, 1), { |v| ~delay.set(\delaytime, v) } );
~to.slider('/fx/decay', 1, TouchOSCScale(0, 5), { |v| ~delay.set(\decaytime, v) } );
~to.slider('/fx/delaymix', 0.2, TouchOSCScale(0, 1), { |v| ~delay.set(\amp, v) } );
~to.slider('/fx/reverbwet', 0.33,TouchOSCScale(0, 1), { |v| ~reverb.set(\mix, v) } );
~to.slider('/fx/reverbroom', 0.5,TouchOSCScale(0, 1), { |v| ~reverb.set(\room, v) } );
~to.slider('/fx/reverbdamp', 0.5,TouchOSCScale(0, 1), { |v| ~reverb.set(\damp, v) } );
~to.slider('/fx/reverbmix', 0.2, TouchOSCScale(0, 1), { |v| ~reverb.set(\amp, v) } );
// note - the three LFOs have different rate ranges
~to.slider('/lfos/afreq', 0.5,TouchOSCScale(0.001, 2), { |v| ~lfoa.set(\freq, v) } );
~to.slider('/lfos/aamp', 0, TouchOSCScale(0, 1), { |v| ~lfoa.set(\amp, v) });
~to.slider('/lfos/bfreq', 0.5,TouchOSCScale(0.01, 20), { |v| ~lfob.set(\freq, v) } );
~to.slider('/lfos/bamp', 0, TouchOSCScale(0, 1), { |v| ~lfob.set(\amp, v) });
~to.slider('/lfos/cfreq', 0.5,TouchOSCScale(0.1, 200), { |v| ~lfoc.set(\freq, v) } );
~to.slider('/lfos/camp', 0, TouchOSCScale(0, 1), { |v| ~lfoc.set(\amp, v) });
)

46
main.scd 100644
View File

@ -0,0 +1,46 @@
// Execute this before booting the server
(
Server.default.options.inDevice_("Scarlett 2i2 USB");
Server.default.options.hardwareBufferSize_(1024);
Server.default.options.outDevice_("Scarlett 2i2 USB");
//Server.default.options.outDevice_("External Headphones");
)
Server.killAll;
(
Routine.run({
~usbinput = 2;
~usbinput1 = 2;
~usbinput2 = 3;
~bpm = 90;
~bps = ~bpm / 60;
~beatsperbar = 4;
~buflen = ~beatsperbar / ~bps;
[ "bpm", ~bpm ].postln;
[ "buffer length", ~buflen ].postln;
~tc = TempoClock.new(~bps);
~touchosc_ip = "192.168.0.209";
("./synths.scd").loadRelative;
Granulator.init(s);
s.sync;
("./control.scd").loadRelative;
s.sync;
("./granulator.scd").loadRelative;
s.sync;
("./effects.scd").loadRelative;
s.sync;
("./sequencer.scd").loadRelative;
s.sync;
"please wait for the interface to load...".postln;
~buflen.sleep;
("./interface.scd").loadRelative;
"ok go!".postln;
});
)

36
monitor.scd 100644
View File

@ -0,0 +1,36 @@
~frippbuffers[~currentfripp].write("/Users/mike/Music/SuperCollider Recordings/test.aiff");
~frippbuffer.write("/Users/mike/Music/SuperCollider Recordings/slow.aiff");
~frippbuffer.isNil;
(
~monitor = SynthDef(
\monitor_synth,
{
arg in=2, out=0;
Out.ar(out, In.ar(in, 2))
}
).play(s, [\in, ~fxb, \out, 0 ], \addToTail);
)
~monitor.set(\in, ~reverbb)
~monitor.free
~pitchb.scope()
~frippbuffers.plot;
~bufrecorder.set(\buffer, ~frippbuffers[1]);
~granulators[0].get(\buffer, {|v| v.postln});
(
~frippbuffers.do({
|b, i|
b.write("/Users/mike/Music/SuperCollider Recordings/buffer" ++ i.asString ++ ".aiff");
});
)

26
sequencer.scd 100644
View File

@ -0,0 +1,26 @@
(
~metrob = Bus.audio(s, 2);
~metromix = SynthDef(\metromix, {
arg in=1, out=0, amp=1;
Out.ar(out, amp * In.ar(in, 2));
}).play(s, [\in, ~metrob, \out, 0, \amp, 0]);
~metronome = Pbind(
\instrument, \metronome,
\dur, ~beatsperbar,
\amp, 0.5,
\pan, 0,
\out, ~metrob
).play(~tc);
)

45
synths.scd 100644
View File

@ -0,0 +1,45 @@
(
SynthDef(\pos_saw, {
arg out, length=1;
Out.kr(out, EnvGen.kr(Env([0, 1], length), doneAction: Done.freeSelf))
}).add();
SynthDef(\pos_sine, {
arg out, length=1;
Out.kr(out, EnvGen.kr(Env.sine(length, 1), doneAction: Done.freeSelf))
}).add();
SynthDef(\pos_reverse, {
arg out, length=1;
Out.kr(out, EnvGen.kr(Env([1, 0], length), doneAction: Done.freeSelf))
}).add();
SynthDef(\pos_step, {
arg out, length=1;
var levels = (0..8) / 8, times = (length / 8) ! 7;
Out.kr(out, EnvGen.kr(Env(levels: levels, times: times, curve: \hold), doneAction: Done.freeSelf));
}).add();
SynthDef(\lfo, {
arg out, freq=1, amp=0;
Out.kr(out, SinOsc.kr(freq, 0, amp));
}).add;
SynthDef(\trigger, {
arg out=1;
Out.kr(out, EnvGen.kr(Env.perc(0.001, 0.2, 2), levelScale:2.0, levelBias:-1,doneAction:Done.freeSelf));
}).add;
SynthDef(\metronome, {
arg out=0, amp=1, pan=0, filter=1000, atk=0.01, rel=0.1;
var sig, env;
env = EnvGen.kr(Env.perc(atk, rel, amp), doneAction: Done.freeSelf);
sig = HPF.ar(WhiteNoise.ar(), filter);
Out.ar(out, Pan2.ar(sig * env, pan));
}
).add;
)