From b497ca13dcdb8b4d67e8e65ce5af6751c3ef59a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Thu, 2 May 2024 23:28:00 +0200 Subject: [PATCH] First mockup of audio looper (==) I am trying to add an audio looper to play breakbeats. It works .. meh for now but I'm sure that it'll slowly get better! --- Classes/BuboBoot.sc | 52 +++++++++++++++-------- Classes/BuboNodeProxy.sc | 65 ++++++++++++++++++++++++----- Classes/Configuration/Startup.scd | 3 ++ Classes/Configuration/Synthdefs.scd | 49 ++++++++++++++++++++++ test_breakbeat.scd | 33 +++++++++++++++ 5 files changed, 175 insertions(+), 27 deletions(-) create mode 100644 test_breakbeat.scd diff --git a/Classes/BuboBoot.sc b/Classes/BuboBoot.sc index db37f87..4ae3929 100644 --- a/Classes/BuboBoot.sc +++ b/Classes/BuboBoot.sc @@ -51,22 +51,22 @@ Boot { // Post actions: installing behavior after server boot Server.default.waitForBoot({ - d = (); - // Exceptional Dual Sardine Boot - d.dirt = SuperDirt(2, s); - d.dirt.fileExtensions = ["wav","aif","aiff","aifc","mp3"]; - d.dirt.loadSoundFiles("/Users/bubo/Library/Application\ Support/Sardine/SON/*"); - d.dirt.loadSoundFiles("/Users/bubo/.config/livecoding/samples/*"); - d.dirt.doNotReadYet = true; - d.dirt.start(57120, [ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]); - ( - d.d1 = d.dirt.orbits[0]; d.d2 = d.dirt.orbits[1]; d.d3 = d.dirt.orbits[2]; - d.d4 = d.dirt.orbits[3]; d.d5 = d.dirt.orbits[4]; d.d6 = d.dirt.orbits[5]; - d.d7 = d.dirt.orbits[6]; d.d8 = d.dirt.orbits[7]; d.d9 = d.dirt.orbits[8]; - d.d10 = d.dirt.orbits[9]; d.d11 = d.dirt.orbits[10]; d.d12 = d.dirt.orbits[11]; - ); - d.dirt.soundLibrary.addMIDI(\midi, MIDIOut.newByName("MIDI", "Bus 1")); - d.dirt.soundLibrary.addMIDI(\midi2, MIDIOut.newByName("MIDI", "Bus 2")); + // d = (); + // // Exceptional Dual Sardine Boot + // d.dirt = SuperDirt(2, s); + // d.dirt.fileExtensions = ["wav","aif","aiff","aifc","mp3"]; + // d.dirt.loadSoundFiles("/Users/bubo/Library/Application\ Support/Sardine/SON/*"); + // d.dirt.loadSoundFiles("/Users/bubo/.config/livecoding/samples/*"); + // d.dirt.doNotReadYet = true; + // d.dirt.start(57120, [ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]); + // ( + // d.d1 = d.dirt.orbits[0]; d.d2 = d.dirt.orbits[1]; d.d3 = d.dirt.orbits[2]; + // d.d4 = d.dirt.orbits[3]; d.d5 = d.dirt.orbits[4]; d.d6 = d.dirt.orbits[5]; + // d.d7 = d.dirt.orbits[6]; d.d8 = d.dirt.orbits[7]; d.d9 = d.dirt.orbits[8]; + // d.d10 = d.dirt.orbits[9]; d.d11 = d.dirt.orbits[10]; d.d12 = d.dirt.orbits[11]; + // ); + // d.dirt.soundLibrary.addMIDI(\midi, MIDIOut.newByName("MIDI", "Bus 1")); + // d.dirt.soundLibrary.addMIDI(\midi2, MIDIOut.newByName("MIDI", "Bus 2")); s.latency = 0.3; // Resume normal boot sequence @@ -92,8 +92,26 @@ Boot { Server.default.avgCPU.round(2), Server.default.peakCPU.round(2)), 40) }, Server.default); + + // This custom event is used for audio looping + Event.addEventType(\buboLoopEvent, { + arg server; + if (~sp.notNil && ~nb.notNil, { + ~sp = ~sp ?? 'default'; + ~nb = ~nb ?? 0; + ~buf = Bank(~sp)[~nb % Bank(~sp).paths.size]; + if (Bank(~sp).metadata[~nb % Bank(~sp).size][\numChannels] == 1) { + ~instrument = \looperMono; + } { + ~instrument = \looperStereo; + }; + }); + ~type = \note; + currentEnvironment.play; + }); + + // This custom event makes it easier to play samples Event.addEventType(\buboEvent, { - // This is a custom event that makes it easier to play samples arg server; if (~sp.notNil && ~nb.notNil, { ~sp = ~sp ?? 'default'; diff --git a/Classes/BuboNodeProxy.sc b/Classes/BuboNodeProxy.sc index 98def6f..85f01f7 100644 --- a/Classes/BuboNodeProxy.sc +++ b/Classes/BuboNodeProxy.sc @@ -40,34 +40,59 @@ /* Syntax for sending MIDI messages */ >> { arg pattern; + var quant = this.getQuantFromPattern(pattern); + var fade = this.getFadeFromPattern(pattern); pattern = EventShortener.findShortcuts(pattern); pattern = pattern ++ [type: 'midi']; this[0] = Pbind(*pattern); - this.quant = 4; this.fadeTime = 0.01; + this.quant = quant; + this.fadeTime = fade; + this.play; ^this } - /* Player-like syntax sugar */ + /* Player syntax sugar */ => { arg pattern; + var quant = this.getQuantFromPattern(pattern); + var fade = this.getFadeFromPattern(pattern); pattern = EventShortener.findShortcuts(pattern); pattern = pattern ++ [\type, \buboEvent]; this[0] = Pbind(*pattern); - this.quant = 4; this.fadeTime = 0.01; + this.quant = quant; + this.fadeTime = fade; + this.play; ^this } - /* FIX: Completely broken. What is the event type - * BuboEvent should fall back to after tweaking - * the pattern to my liking? - */ + /* Audio Looper (sample playback) */ + == { + arg pattern; + var quant = this.getQuantFromPattern(pattern); + var fade = this.getFadeFromPattern(pattern); + pattern = EventShortener.findShortcuts(pattern); + pattern = pattern ++ [\type, \buboLoopEvent]; + pattern = pattern ++ [\legato, 1]; + pattern = pattern ++ [ + \time, Pkey(\dur) / Pfunc { currentEnvironment.clock.tempo } + ]; + this[0] = Pbind(*pattern); + this.quant = quant; + this.fadeTime = fade; + this.play; + ^this + } + + /* FIX: Rewrite this part, slightly broken */ -> { arg pattern; + var quant = this.getQuantFromPattern(pattern); + var fade = this.getFadeFromPattern(pattern); pattern = EventShortener.findShortcuts(pattern); - // pattern = pattern ++ [\type, \buboMonoEvent]; this[0] = Pmono(*pattern); - this.quant = 4; - this.fadeTime = 0.01; + this.quant = quant; + this.fadeTime = fade; + this.play; } f { @@ -96,4 +121,24 @@ ^this } + getQuantFromPattern { + arg pattern; var quant; + var quantIndex = pattern.indexOf('quant'); + if (quantIndex.notNil) { + ^pattern[quantIndex + 1] + } { + ^0 + } + } + + getFadeFromPattern { + arg pattern; var fade; + var fadeIndex = pattern.indexOf('fade'); + if (fadeIndex.notNil) { + ^pattern[fadeIndex + 1] + } { + ^0.01 + } + } + } diff --git a/Classes/Configuration/Startup.scd b/Classes/Configuration/Startup.scd index d28cb49..51f56bf 100644 --- a/Classes/Configuration/Startup.scd +++ b/Classes/Configuration/Startup.scd @@ -7,3 +7,6 @@ c = currentEnvironment.clock; // Space for loading custom SynthDefs "Loading SynthDefs...".postln; "Synthdefs.scd".loadRelative; + +// Debug during development +// OSCFunc.trace(true); diff --git a/Classes/Configuration/Synthdefs.scd b/Classes/Configuration/Synthdefs.scd index 12fdb98..959c405 100644 --- a/Classes/Configuration/Synthdefs.scd +++ b/Classes/Configuration/Synthdefs.scd @@ -53,6 +53,55 @@ d.list = { arg obj; obj.keys.do({arg i; i.postln}); }; d.splayer = z; ); + +( + /* Crafted with some help from Bruno Gola */ + z = SynthDef(\looperMono, + { + arg out; + var sig, env, index; + index = Select.kr(\direction.kr(1) > 0, [\index.kr(0) + 1, \index.kr]); + sig = PlayBuf.ar( + 1, + \buf.kr(0), + (BufRateScale.kr(\buf.kr) * (BufSamples.kr(\buf.kr) + / \slices.kr(1) / BufSampleRate.kr(\buf.kr)) / \time.kr * \direction.kr) / 2, + 1, BufSamples.kr(\buf.kr) * (index / \slices.kr), doneAction: 0 + ); + env = EnvGen.ar( + Env.asr(0.01, 1, 0.01), \gate.kr(1), doneAction: 2 + ); + sig = sig * env; + sig = sig * \amp.kr(-6.dbamp); + OffsetOut.ar(out,Pan2.ar(sig,\pan.kr(0))); + }).add; + d.looperMono = z; +); + +( + /* Crafted with some help from Bruno Gola */ + z = SynthDef(\looperStereo, + { + arg out; + var sig, env, index; + index = Select.kr(\direction.kr(1) > 0, [\index.kr(0) + 1, \index.kr]); + sig = PlayBuf.ar( + 2, + \buf.kr(0), + (BufRateScale.kr(\buf.kr) * (BufSamples.kr(\buf.kr) + / \slices.kr(1) / BufSampleRate.kr(\buf.kr)) / \time.kr * \direction.kr) / 2, + 1, BufSamples.kr(\buf.kr) * (index / \slices.kr), doneAction: 0 + ); + env = EnvGen.ar( + Env.asr(0.01, 1, 0.01), \gate.kr(1), doneAction: 2 + ); + sig = sig * env; + sig = sig * \amp.kr(-6.dbamp); + OffsetOut.ar(out,Pan2.ar(sig,\pan.kr(0))); + }).add; + d.looperStereo = z; +); + ( z = SynthDef(\sinfb, { arg out; diff --git a/test_breakbeat.scd b/test_breakbeat.scd new file mode 100644 index 0000000..4136c6d --- /dev/null +++ b/test_breakbeat.scd @@ -0,0 +1,33 @@ +~a => [sp: ["kick", \].pseq(inf), nb: 0, db: 0]; + +~a.play; + +~a.clear; + +Scope() + +( +var sampleStr = "kick:2(1) snare:4(1/4) . hihat:1(1/8) ."; +a = { |sampleStr, defaultRestDuration = 1.0| + var pattern = "(\\w+):(\\d+)\\(([\\d/]+)\\)|\\."; + var matches = sampleStr.findAllRegexp(pattern); + matches.collect { |match| + if(match.size == 1) { + [ 'sp': 'rest', 'nb': 0, 'dur': defaultRestDuration ] + } { + var sampleName = match[0]; + var sampleNumber = match[1].asInteger; + var duration = match[2]; + var durationValue; + if(duration.contains("/")) { + var nums = duration.split("/"); + durationValue = nums[0].asInteger / nums[1].asInteger; + } { + durationValue = duration.asFloat; + }; + [ 'sp': sampleName, 'nb': sampleNumber, 'dur': durationValue ] + } + } +}; +a.value(sampleStr); +)