diff --git a/Classes/BuboBoot.sc b/Classes/BuboBoot.sc index b7b22b3..07f57c0 100644 --- a/Classes/BuboBoot.sc +++ b/Classes/BuboBoot.sc @@ -119,5 +119,22 @@ Boot { ~type = \note; currentEnvironment.play; }); + + Event.addEventType(\buboGranular, { + arg server; + if (~sp.notNil, { + if (BuboUtils.stringIsNumber(~sp), {}, { + ~sp = BuboUtils.cleanSampleName(~sp); + ~nb = BuboUtils.cleanSampleIndex(~nb); + if (~sp !== "", { + ~buf = Bank(~sp)[~nb % Bank(~sp).paths.size]; + ~instrument = 'grainSampler'; + }); + }); + }); + ~type = \note; + currentEnvironment.play; + }); } + } diff --git a/Classes/BuboNodeProxy.sc b/Classes/BuboNodeProxy.sc index b0b654b..59a584c 100644 --- a/Classes/BuboNodeProxy.sc +++ b/Classes/BuboNodeProxy.sc @@ -81,6 +81,19 @@ ^this } + /* Granular Sampler */ + +=> { + arg pattern; + var quant = BuboUtils.getQuantFromPattern(pattern); + var fade = BuboUtils.getFadeFromPattern(pattern); + "Hello granular".postln; + pattern = EventShortener.process(pattern, this.key, 'granular', 1); + pattern = EffectChain.process(pattern, this.key); + this[0] = Pbind(*pattern); + this.prepareToPlay(this, quant, fade); + ^this + } + /* Pmono player */ -> { arg pattern; diff --git a/Classes/BuboUtils.sc b/Classes/BuboUtils.sc index e4d4084..686f8c8 100644 --- a/Classes/BuboUtils.sc +++ b/Classes/BuboUtils.sc @@ -99,7 +99,4 @@ BuboUtils { arg pattern; ^this.getValueFromPattern(pattern, 'fade', 0.01) } - - - } diff --git a/Classes/Configuration/Synthdefs.scd b/Classes/Configuration/Synthdefs.scd index 2927902..8711281 100644 --- a/Classes/Configuration/Synthdefs.scd +++ b/Classes/Configuration/Synthdefs.scd @@ -25,10 +25,35 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); }; d.player = z; ); +/* +* Granular Sampler +*/ +( + z = SynthDef('grainSampler', { + arg out, buf; + var sound = GrainBuf.ar( + numChannels: buf.numChannels, + trigger: Impulse.kr(\grain.kr(4)), + dur: 1, sndbuf: buf, + rate: \rate.kr(4), + pos: \pos.kr(0.0), + interp: 2, + pan: \pan.kr(0.0), + envbufnum: \env.kr(-1), + ); + sound = sound * Env.perc( + \attack.kr(0.1), \release.kr(0.5) + ).kr(2); + sound = sound * \amp.kr(-6).dbamp; + OffsetOut.ar(out, sound); + }).add; + d.grainSampler = z; +); /* * Stereo variant */ + ( z = SynthDef.new(\splayer, { arg buf, out; diff --git a/Classes/EventShortener.sc b/Classes/EventShortener.sc index c484248..7ce22db 100644 --- a/Classes/EventShortener.sc +++ b/Classes/EventShortener.sc @@ -3,9 +3,6 @@ EventShortener { *process { arg pattern, key, type, time; var new_pattern; - var additionalKeys = Dictionary.newFrom([ - \looper, [type: \buboLoopEvent, legato: 1, time: time], - ]); new_pattern = this.findShortcuts(pattern); new_pattern = this.functionsToNdef(new_pattern, key); new_pattern = switch(type, @@ -14,6 +11,7 @@ EventShortener { 'midi', this.patternMidi(new_pattern), 'midicc', this.patternMidiCC(new_pattern), 'looper', this.patternLooper(new_pattern), + 'granular', this.patternGranular(new_pattern), ); ^new_pattern } @@ -46,12 +44,11 @@ EventShortener { ^new_pattern } - *patternBuboEvent { + *patternGranular { arg pattern; var new_pattern = List(); var sp_position = nil; var nb_position = nil; - // Check if the sp key already exists in the pattern if (pattern.includes('sp'), { pattern.do({ arg e, i; @@ -60,7 +57,6 @@ EventShortener { }) }); }); - // Check if the nb key already exists in the pattern if (pattern.includes('nb'), { pattern.do({ arg e, i; @@ -69,7 +65,64 @@ EventShortener { }) }); }); + new_pattern = new_pattern ++ [\type, 'buboGranular']; + pattern.doAdjacentPairs({ | a, b, index | + if (index % 2 == 0, { + if (a === 'pat', { + var temp = Pmini(b); + new_pattern = new_pattern ++ [ + [\trig, \delta, \dur, \str, \num], Pmini(b), + ]; + new_pattern = new_pattern ++ [ + degree: Pfunc({ |e| + if (e.trig > 0, { + e.str.asInteger + }, { + \rest + }); + }); + ]; + if (sp_position.notNil, { + new_pattern = new_pattern ++ [ + sp: pattern[sp_position + 1], + nb: pattern[nb_position + 1], + ]; + }, { + new_pattern = new_pattern ++ [ + sp: Pfunc { |e| e.str ? "" }, + nb: Pfunc { |e| e.num ? 0 } + ]; + }); + }, { + new_pattern = new_pattern ++ [a, b]; + }); + }); + }); + new_pattern.postln; + ^new_pattern + } + *patternBuboEvent { + arg pattern; + var new_pattern = List(); + var sp_position = nil; + var nb_position = nil; + // Check if the sp key already exists in the pattern + if (pattern.includes('sp'), { + pattern.do({ arg e, i; + if (e == 'sp', { + sp_position = i; + }) + }); + }); + // Check if the nb key already exists in the pattern + if (pattern.includes('nb'), { + pattern.do({ arg e, i; + if (e == 'nb', { + nb_position = i; + }) + }); + }); new_pattern = new_pattern ++ [\type, 'buboEvent']; pattern.doAdjacentPairs({ | a, b, index | if (index % 2 == 0, { diff --git a/README.md b/README.md index 1b9d24f..64a28cf 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ I personally dislike the `Pbind(\qdklsj)` or `Ndef(\qkljsdf)` syntax. The `\` sy - Operators for creating SuperCollider patterns on-the-fly: - `=>` (Pbind): basic musical pattern + - `+=>` (Pbind): granular sampler - `->` (Pmono): monophonic expression pattern - `==>` (Looper): looper/sampler (**WIP**, currently broken) - `>>` (Note): MIDI Note Pattern @@ -216,7 +217,24 @@ This is pitched sample playback. Note that you can also decompose patterns with ) ``` -All the remaining keys in patterns are behaving just like regular SuperCollider patterns. +Here is some granular sampling: + +```cpp +( +~test +=> [ + sp: "casio", nb: [0, 2, 4].pseq(inf), amp: 1, + grain: {SinOsc.ar(1/4).range(1,20)}, + pos: {LFNoise2.kr(1/8).range(0, 0.25)}, + rate: Pwhite(1, 2, inf) +]; +~test.play; +~test.fx(100, 0.5, { + arg in; MiVerb.ar(in, time: 0.5); +}) +) +``` + +All the remaining pattern keys you can think of are behaving just like regular SuperCollider patterns. ### Controlling synthesizers diff --git a/test_granular.scd b/test_granular.scd new file mode 100644 index 0000000..1e5e486 --- /dev/null +++ b/test_granular.scd @@ -0,0 +1,44 @@ +Boot( + samplePath: "/Users/bubo/.config/livecoding/samples" +) + +( +~test +=> [ + sp: "casio", nb: [0, 2, 4].pseq(inf), amp: 1, + grain: {SinOsc.ar(1/4).range(1,20)}, + pos: {LFNoise2.kr(1/8).range(0, 0.25)}, + rate: Pwhite(1, 2, inf) +]; +~test.play; +~test.fx(100, 0.5, { + arg in; MiVerb.ar(in, time: 0.5); +}) +) + +~test ++> []; + + +/* +* Granular Sampler +*/ +( + z = SynthDef('grainSampler', { + arg out, buf; + var sound = GrainBuf.ar( + numChannels: buf.numChannels, + trigger: Impulse.kr(\grain.kr(4)), + dur: 1, sndbuf: buf, + rate: \rate.kr(4), + pos: \pos.kr(0.0), + interp: 2, + pan: \pan.kr(0.0), + envbufnum: \env.kr(-1), + ); + sound = sound * Env.perc( + \attack.kr(0.1), \release.kr(0.5) + ).kr(2); + sound = sound * \amp.kr(-6).dbamp; + OffsetOut.ar(out, sound); + }).add; + d.grainPlayer = z; +)