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