Last active
May 31, 2024 09:09
-
-
Save scztt/73a2ae402d9765294ae8f72979d1720e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| SAMP : Singleton { | |
| classvar <>root, <>extensions, <>lazyLoading=true; | |
| var <paths, buffers, <channels, <foundRoot, <foundRootModTime, markersCache, atCache; | |
| var sortFunc; | |
| var metadata; | |
| *initClass { | |
| root = "~/Desktop".standardizePath; | |
| extensions = ["wav", "aiff", "aif", "flac", "ogg"]; | |
| } | |
| *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 | |
| }; | |
| } | |
| } | |
| sortFunc_{ | |
| |func| | |
| sortFunc = func; | |
| } | |
| 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, | |
| 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; | |
| buffers.do(_.free); | |
| buffers = []; | |
| this.prUpdateBuffers(); | |
| }; | |
| } | |
| clear { | |
| paths = []; | |
| atCache = (); | |
| this.prUpdateBuffers(); | |
| } | |
| bufferAt { | |
| |index| | |
| var sf; | |
| // if (index.isNil) { ^nil }; | |
| ^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.indexForKey(key) !? this.bufferAt(_)); | |
| } | |
| 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; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment