SuperDirt
SuperDirt copied to clipboard
sample repitching using smpl chunk metadata and tune argument
implements #273, now with the simplified approach suggested by @telephon.
thank you for the thoughtful review, i am learning a lot! :D
let me know what you think about this version!
thank you for the thoughtful review, i am learning a lot! :D
great :) The code is already very good, so it is just some tweaking.
sorry for the long delay! ive now addressed your previous comments.
still TBD is the name of the argument for enabling this feature :) do you think metatune
is acceptable? (ref. https://github.com/musikinformatik/SuperDirt/pull/274#discussion_r917135727)
while using this in my own livecoding, i noticed incorrect durations for some tuned notes.. i fixed this before but i think https://github.com/musikinformatik/SuperDirt/pull/274/commits/b53a29f89174cda02429a82e77a7c6a92e84473c broke it again. will take a look at it soon
note durations are now fixed. in the absence of further comments on the argument name i also took the liberty of renaming it to metatune
. unless you would like some other changes i believe this should be ready to merge now!
i agree it would be better, but i had some trouble making it work that way (the commit was sitting on my computer for a while so i dont remember the details anymore). i will give it another shot
right, the problem is that by the time the module function is invoked, DirtEvent.calcTimeSpan has already happened and it is too late to modify unitDuration.
i wrote down this pseudocode summary of the event construction control flow, in case it is helpful:
DirtOrbit.init {
makeDefaultParentEvent {
defaultParentEvent = Event.make {
...
~midinote = #{ ~note ? ~n + (~octave * 12) }
~freq = #{ ~midinote.value.midicps }
...
}
}
}
DirtSoundLibrary.loadSoundFile {
readSoundFile
readMetaData
addBuffer {
bufferEvents[name] = makeEventForBuffer {
builds event object, includes:
- baseFreqToMetaFreqRatio (constant)
- unitDuration (lambda, calls ~freq.value)
}
}
}
SuperDirt.connect {
defines playFunc { |msg, ...|
event = (latency: ...)
event.putPairs(msg[1..])
receiveAction.(event) // receiveAction is nil by default -> no-op
DirtEvent(..., event).play {
event.parent = orbit.defaultParentEvent
event.use {
// ignoring ~diversion != nil case
mergeSoundEvent {
currentEnvironment.proto = orbit.dirt.soundLibrary.getEvent
}
orderTimeSpan // swaps ~begin and ~end if ~speed < 0
calcTimeSpan {
applies ~unitDuration
}
playSynths {
modules.do(_.(this)) {
sound module called
}
}
}
}
}
registers playFunc as OSC handler for /play2
}
Ah yes, I see.
Maybe there is a better way to do this.
Can you try this:
~dirt.set(\freq, { ~midinote.value.midicps * ~metaTuneRatio.value }, \metaTuneRatio, 1.0);
and then you can just set the metaTuneRatio
in the SoundLibrary, I think.
If that works, we can extend the frequency calculation model of superdirt in DirtOrbit
accordingly.
COMMENT: actually, thinking about it, it is better as it is in this pull request. Saves some energy and keeps things together. I add some minor comments.
here is an example that perhaps helps clarify how this works in practice.
samples used: https://share.pulusound.fi/metatune-samples.zip
contains a subdirectory foo
with four samples. this is their smpl metadata:
$ find samples -iname '*.wav' -exec pitcheon '{}' ';'
samples/foo/00 hypersaw.wav
1 smpl chunk(s)
[0] note: 50.000 / freq: 146.832Hz / smpl: (50, 0)
samples/foo/01 bell.wav
1 smpl chunk(s)
[0] note: 71.410 / freq: 505.719Hz / smpl: (71, 1760936591)
samples/foo/02 piano.wav
0 smpl chunk(s)
samples/foo/03 pluck.wav
1 smpl chunk(s)
[0] note: 68.000 / freq: 415.305Hz / smpl: (68, 0)
note that 02 piano.wav
has none, and all the others are have different pitches.
superdirt startup code:
(
s = Server.local;
s.options.sampleRate_(44100);
s.options.memSize = 1024 * 1024;
s.options.numBuffers = 1024 * 256;
s.options.numWireBufs = 128;
s.options.maxNodes = 1024 * 32;
s.latency_(0.205);
s.newBusAllocators;
s.waitForBoot {
~dirt = SuperDirt(2, s);
~dirt.loadSoundFiles(thisProcess.nowExecutingPath.dirname +/+ "samples/*");
s.sync;
~dirt.start(outBusses: 0 ! 12);
};
)
after this, ~dirt.soundLibrary.metaDataEvents
looks like:
IdentityDictionary[
(foo -> Dictionary[
(0 -> ( 'baseFreqToMetaFreqRatio': 1.7817974362766, 'midinote': 50.0 )),
(1 -> ( 'baseFreqToMetaFreqRatio': 0.51733355006402, 'midinote': 71.4100000656 )),
(3 -> ( 'baseFreqToMetaFreqRatio': 0.6299605249486, 'midinote': 68.0 ))
])
]
tidal pattern:
d1
$ n "[0 1 2 3]*2"
# s "foo"
# legato 1
# pF "metatune" 0
compare values 0
and 1
for metatune
. the piano sample is unaffected due to the aforementioned lack of metadata.
good question! i forgot about the synth case. it might be best to factor out the dictionary-manipulating code into a function that is used by both addBuffer
and addSynth
.
i also recently noticed metadata not getting correctly updated when reloading samples from a folder which has been loaded before, will look into that too.
how about this?
i also recently noticed metadata not getting correctly updated when reloading samples from a folder which has been loaded before, will look into that too.
i could not reproduce this anymore, maybe i just did something wrong last time.
Thank you! This is good.