diff --git a/Classes/Bank.sc b/Classes/Bank.sc index a2da1b9..42919ef 100644 --- a/Classes/Bank.sc +++ b/Classes/Bank.sc @@ -3,329 +3,725 @@ * I have added a method to list the samples in the bank. */ +// Bank : Singleton { +// classvar <>root, <>extensions, <>lazyLoading=true; +// var buffers.size) { buffers = buffers.extend(paths.size) }; +// +// paths.do { +// |path, i| +// var buffer; +// +// buffer = buffers[i]; +// +// if (path.notNil) { +// if (lazyLoading.not) { +// this.bufferAt(i) +// } +// } { +// if (buffer.notNil) { +// buffer.free; +// buffers[i] = buffer = nil; +// } +// } +// }; +// +// buffers.extend(paths.size); +// } +// } +// +// doOnServerBoot { +// if (paths.size > 0) { +// buffers = []; +// this.prUpdateBuffers(); +// "***Loaded samples for %***".format(this.asString).postln; +// } +// } +// +// doOnServerQuit { +// buffers = []; +// } +// +// pat { +// |keyPat| +// ^Pindex(Pseq([this], inf), keyPat) +// } +// +// asBuffer { +// ^this.singleSampleWrap(nil) +// } +// +// asControlInput { +// |...args| +// ^this.prSingleSampleWrap(\asControlInput, *args) +// } +// +// play { +// |...args| +// ^this.prSingleSampleWrap(\play, *args) +// } +// +// prSingleSampleWrap { +// |method ...args| +// var buffer; +// if (buffers.size == 1) { +// buffer = this.bufferAt(0); +// +// if (method.isNil) { +// ^buffer +// } { +// if (buffer.numFrames.isNil) { +// fork { +// Server.default.sync; +// buffer.performList(method, args) +// }; +// ^nil; +// } { +// ^buffer.performList(method, args) +// } +// } +// } { +// Error("Trying to % a bank with multiple buffers".format(method)).throw; +// } +// } +// } + Bank : Singleton { - classvar <>root, <>extensions, <>lazyLoading=true; - var root, <>extensions, <>lazyLoading=true; + var buffers.size) { buffers = buffers.extend(paths.size) }; - - paths.do { - |path, i| - var buffer; - - buffer = buffers[i]; - - if (path.notNil) { - if (lazyLoading.not) { - this.bufferAt(i) - } - } { - if (buffer.notNil) { - buffer.free; - buffers[i] = buffer = nil; - } - } - }; - - buffers.extend(paths.size); - } - } - - doOnServerBoot { - if (paths.size > 0) { - buffers = []; - this.prUpdateBuffers(); - "***Loaded samples for %***".format(this.asString).postln; - } - } - - doOnServerQuit { - buffers = []; - } - - pat { - |keyPat| - ^Pindex(Pseq([this], inf), keyPat) - } - - asBuffer { - ^this.singleSampleWrap(nil) - } - - asControlInput { - |...args| - ^this.prSingleSampleWrap(\asControlInput, *args) - } - - play { - |...args| - ^this.prSingleSampleWrap(\play, *args) - } - - prSingleSampleWrap { - |method ...args| - var buffer; - if (buffers.size == 1) { - buffer = this.bufferAt(0); - - if (method.isNil) { - ^buffer - } { - if (buffer.numFrames.isNil) { - fork { - Server.default.sync; - buffer.performList(method, args) - }; - ^nil; - } { - ^buffer.performList(method, args) - } - } - } { - Error("Trying to % a bank with multiple buffers".format(method)).throw; - } - } + *list { + PathName(Bank.root).entries.do({ + arg item; item.folderName.postln; + }) + } + + *new { + |path, channels| + ^super.new(path, channels); + } + + size { + ^paths.size + } + + printOn { + |stream| + super.printOn(stream); + stream << "[%]".format(paths.size) + } + + init { + var currentRoot, currentExtensions, foundPaths=[], attempts = List(); + var fixedName; + + buffers = []; + ServerQuit.add(this); + ServerBoot.add(this); + + fixedName = name.asString + .replace("[", "\\[") + .replace("]", "\\]") + .replace("(", "\\(") + .replace(")", "\\)"); + + if (foundRootModTime.notNil) { + if (foundRoot.notNil and: { File.exists(foundRoot) } and: { File.mtime(foundRoot) == foundRootModTime }) { + ^this; // no changes, so early return! + } + }; + + currentExtensions = this.class.extensions; + currentRoot = thisProcess.nowExecutingPath; + + // Try an absolute path resolve first + foundPaths = Require.resolvePaths(fixedName, [], currentExtensions, attempts); + if (foundPaths.isEmpty.not) { + currentRoot = PathName(fixedName).parentPath; + } { + if (currentRoot.notNil) { + currentRoot = PathName(currentRoot).parentPath; + foundPaths = Require.resolvePaths(fixedName, currentRoot, currentExtensions, attempts); + }; + + if (foundPaths.isEmpty and: { currentRoot.notNil }) { + currentRoot = currentRoot +/+ fixedName.asString; + foundPaths = Require.resolvePaths("*", currentRoot, currentExtensions, attempts); + }; + + if (foundPaths.isEmpty) { + currentRoot = this.class.root; + foundPaths = Require.resolvePaths(fixedName, currentRoot, currentExtensions, attempts); + }; + + if (foundPaths.isEmpty) { + currentRoot = currentRoot +/+ fixedName; + foundPaths = Require.resolvePaths("*", currentRoot, currentExtensions, attempts); + }; + }; + + if (foundPaths.isEmpty) { + foundRoot = nil; + foundRootModTime = nil; + "No samples found, attempted paths: ".warn; + attempts.do { + |a| + "\t%.{%}".format(a, currentExtensions.join(",")).warn + }; + } { + foundRoot = currentRoot; + foundRootModTime = File.mtime(foundRoot) ?? {0}; + }; + + foundPaths = foundPaths.sort({ + |a, b| + var pair; + #a, b = [a, b].collect { + |p| + p = p.toLower; + p = p.split($.).first; + p = p.split($/).reverse; + }; + pair = [a, b].flop.detect({ + |pair| + pair[0] != pair[1] + }); + pair !? { + pair[0] < pair[1] + } ?? { false } + }); + + atCache = (); + paths = foundPaths; + this.metadata; + } + + prGetMetadata { + ^paths.collect { + |path| + SoundFile.openRead(path) !? { + |sf| + var e = ( + sampleRate: sf.sampleRate, + numChannels: sf.numChannels, + numFrames: sf.numFrames, + sampleFormat: sf.sampleFormat, + headerFormat: sf.headerFormat, + ); + sf.close; + e + }; + } + } + + metadata { + |key| + metadata = metadata ?? this.prGetMetadata(_); + if (key.isNil) { + ^metadata + } { + ^metadata[this.indexForKey(key)] + } + } + + lazyLoading_{ + |lazy| + if (lazyLoading != lazy) { + lazyLoading = lazy; + this.prUpdateBuffers(); + } + } + + buffers { + ^paths.size.collect { + |i| + this.bufferAt(i) + } + } + + gui { + var view, sampleViews, button, name; + var playNode; + + this.lazyLoading = false; + + view = View().layout_(GridLayout.rows()); + paths.do { + |path, i| + var sampleView; + name = PathName(path).fileNameWithoutExtension; + view.layout.add( + sampleView = DragSource() + .object_("%(%)[%]".format( + this.class.name, + channels !? { + "'%', %".format(this.name, channels) + } ?? { + "'%'".format(this.name) + }, + name.quote + )) + .string_(name) + .canFocus_(true) + .font_(Font("M+ 2c", 10, false)) + .minWidth_(100) + .mouseDownAction_({ + |v| + if (v.focus) { + playNode.free; + playNode = this.bufferAt(i).play + } + }) + .focusGainedAction_({ this.bufferAt(i).play(mul:[1, 1]) }), + (i / 4).floor, + i % 4 + ); + sampleViews = sampleViews.add(sampleView); + }; + + view.keyUpAction = { + |view, char, modifiers, unicode, keycode, key| + switch( + keycode.postln, + 123, { + sampleViews[-1 + (sampleViews.detectIndex({ |v| v.hasFocus }) ? 0)].focus + }, + 124, { + sampleViews[ 1 + (sampleViews.detectIndex({ |v| v.hasFocus }) ? 0)].focus + }, + 125, { + sampleViews[ 4 + (sampleViews.detectIndex({ |v| v.hasFocus }) ? 0)].focus + }, + 126, { + sampleViews[-4 + (sampleViews.detectIndex({ |v| v.hasFocus }) ? 0)].focus + } + ) + }; + + ScrollView(bounds:500@600).canvas_(view).front; + } + + set { + |inChannels| + if (channels != inChannels) { + channels = inChannels; + this.clear(); + this.prUpdateBuffers(); + }; + } + + clear { + paths = []; + atCache = (); + this.prUpdateBuffers(); + } + + bufferAt { + |index| + var sf; + ^buffers !? { + buffers[index] ?? { + if (Server.default.serverRunning) { + buffers = buffers.extend(index + 1, nil); + if (channels.isNil) { + buffers[index] = Buffer.read(Server.default, paths[index]); + buffers[index].numChannels = metadata[index][\numChannels]; + buffers[index]; + } { + buffers[index] = Buffer.readChannel(Server.default, paths[index], channels:Array.series(channels)); + }; + }; + + buffers[index]; + } + } + } + + indexForKey { + |key| + var index; + + if (key.isArray && key.isString.not) { + ^key.collect(this.at(_)) + }; + + if (key.isInteger) { + index = key + } { + index = atCache[key.asSymbol]; + if (index.isNil) { + index = paths.detectIndex({ + |path| + key.asString.toLower.replace("*", ".*").matchRegexp( + path.asString.toLower + ); + }); + atCache[key.asSymbol] = index; + } + }; + + ^index + } + + at { + |key| + ^this.bufferAt(this.indexForKey(key)); + } + + markers { + ^markersCache ?? { + markersCache = paths.collect({ + |path| + SoundFile(path).extractMarkers + }) + } + } + + wrapAt { + |index| + if (index.isInteger) { + index = index % buffers.size; + }; + ^this.at(index); + } + + do { |...args| buffers.size.collect(this.bufferAt(_)).do(*args) } + collect { |...args| ^buffers.size.collect(this.bufferAt(_)).collect(*args) } + + prUpdateBuffers { + if (Server.default.serverBooting or: { + Server.default.hasBooted && Server.default.serverRunning.not + }) { + Server.default.doWhenBooted { + this.prUpdateBuffers(); + }; + ^this; + }; + + if (Server.default.serverRunning.not) { + buffers = []; + } { + if (paths.size > buffers.size) { buffers = buffers.extend(paths.size) }; + + paths.do { + |path, i| + var buffer; + + buffer = buffers[i]; + + if (path.notNil) { + if (lazyLoading.not) { + this.bufferAt(i) + } + } { + if (buffer.notNil) { + buffer.free; + buffers[i] = buffer = nil; + } + } + }; + + buffers[paths.size..].do { + |b| + b.free; + }; + buffers.extend(paths.size); + } + } + + doOnServerBoot { + if (paths.size > 0) { + buffers = []; + this.prUpdateBuffers(); + "***Loaded samples for %***".format(this.asString).postln; + } + } + + doOnServerQuit { + buffers = []; + } + + pat { + |keyPat| + ^Pindex(Pseq([this], inf), keyPat) + } + + // Single buffer support + asBuffer { ^this.singleSampleWrap(nil) } + asControlInput { |...args| ^this.prSingleSampleWrap(\asControlInput, *args) } + play { |...args| ^this.prSingleSampleWrap(\play, *args) } + + prSingleSampleWrap { + |method ...args| + var buffer; + if (buffers.size == 1) { + buffer = this.bufferAt(0); + + if (method.isNil) { + ^buffer + } { + if (buffer.numFrames.isNil) { + fork { + Server.default.sync; + buffer.performList(method, args) + }; + ^nil; + } { + ^buffer.performList(method, args) + } + } + } { + Error("Trying to % a SAMP with multiple buffers".format(method)).throw; + } + } } diff --git a/Classes/BuboBoot.sc b/Classes/BuboBoot.sc index 233ad88..356b84b 100644 --- a/Classes/BuboBoot.sc +++ b/Classes/BuboBoot.sc @@ -14,7 +14,7 @@ Boot { { "-> Booting using default server configuration".postln; s = Server.default; - s.options.numBuffers = 2048 * 512; + s.options.numBuffers = (2048 * 2048) * 2; // Some arbitrary number s.options.memSize = 8192 * 64; s.options.numWireBufs = 2048; s.options.maxNodes = 1024 * 32; @@ -25,6 +25,8 @@ Boot { { "-> Booting using user server configuration".postln; s = Server.default; + // Imposing a very high number of buffers! + serverOptions.numBuffers = (2048 * 512) * 2; s.options = serverOptions; }, ); @@ -72,7 +74,7 @@ Boot { ~sp = ~sp ?? 'default'; ~nb = ~nb ?? 0; ~buf = Bank(~sp)[~nb % Bank(~sp).paths.size]; - if (~buf.numChannels == 1) { + if (Bank(~sp).metadata[~nb % Bank(~sp).size][\numChannels] == 1) { ~instrument = \player; } { ~instrument = \splayer;