Closed ndr-brt closed 3 years ago
Good idea, but takes a bit of work.
This is how 'classic dirt' works, a good option for low spec hardware.
Yes, in fact before SuperDirt, the lazy-loading behavior of Dirt meant that the first time any longish sample played there would be a noticeable stutter while Dirt accessed the disk. Which led people to invent workarounds to force Dirt to pre-load a bunch of samples before starting a livecoding session!
IME on Raspberry Pi setups, "disk" access is still usually quite slow so I'd rather load all samples at startup, and just be careful about curating the directories loaded if RAM usage is an issue. Typically you can get a few hundred samples within 512 MB, so I've found 1GB total RAM (like on a Pi 3b) is ample enough.
It would be nice that this behaviour could be configured at startup.
I've written up a solution that is almost done, but it makes things a little bit more complicated. Wouldn't it be better if you could send a message from tidal to make superdirt load a small subset? This can be done anytime on the fly.
Yes, that can be a way to avoid the "stuttering" (it will happen also in superdirt?), but a fallback behavour like: "the sample is not loaded in memory, let's load it right now" should be needed
You can try that branch: https://github.com/musikinformatik/SuperDirt/tree/topic-read-on-demand
I forgot to say: with that branch, you can put this in your startup file, before you load the samples:
~dirt.soundLibrary.doNotReadYet = true;
This switches it on. When a sound is used first, it will post a message and read in the background.
@telephon it works! The only issue is that the sample is not played on the first event but only after the second one.
The only issue is that the sample is not played on the first event but only after the second one.
Well that would be magic, wouldn't it?
Classic dirt would keep samples in a queue until they were loaded, sometimes late but often inside the latency deadline for shorter samples. Looking at the code there is a flag to not play late events but it isn't actually used anywhere. There are multiple threaded 'workers' for loading samples, I'm not sure if that really speeds things up (I didn't write that bit). In practice the first time you used a large number of samples at once there would be some time glitching.
I see, yes, we could try, and given a large enough latency setting it may even work sometimes.
…I've changed it, give it a try
that's perfect, everything gets loaded without glitches, also the famous bev
sample!
my supercollider latency is set at 0.3
no, wait, it happened that, when load a sample, the server disconnected:
Buffer UGen: no buffer data
Server 'localhost' exited with exit code 0.
server 'localhost' disconnected shared memory interface
I noticed that this error appears only when I trigger a sample with once
function from tidal
Hm, interesting, this may point to a bug in the server, let's try and find it.
It could be easily caught, but that would mean an extra check for every buffer event, I'd like to save the electricity.
@ndr-brt The connection with once seems spurious. Maybe you can trace it down further?
once
does work a bit differently on the tidal side, missing out the scheduler - so it's feasible the 'artificial latency' calculation is buggy/different
I see. But even if the buffer isn't ever loaded, the server shouldn't crash.
I can reproduce it directly, will try and find a good reproducer.
It's easy to reproduce, emitting a lot of events with samples not yet loaded:
d1 $ n (segment 38 $ irand 38) # s "diphone"
why don't allocate an empty buffer when a doNotReadYet
buffer object is instantiated?
I've tried it out with a version that never loads any buffers. The problem seems to be that sometimes, when you send messages that post an error in sclang before the server has initialised the memory interface properly, then the server crashes. So if you wait till the server has booted it works. I've pushed a fix, please try!
the problem persists
thanks for trying, unfortunately I can't reproduce it any more.
When you start supercollider with superdirt and then wait for a little moment until the server is booted, and then begin to make sound, does it still happen?
Yes, it still happens, also after a minute or two. There's a way to enable stack trace console print?
there are cmake flags, described in the readmes here https://github.com/supercollider/supercollider.
a question: does this code crash the server as well?
(
s.reboot {
fork {
SynthDef(\test, { |out, bufnum, sustain = 1, begin = 0, end = 1, speed = 1, endSpeed = 1, freq = 440, pan = 0|
var sound, rate, phase, sawrate, numFrames;
// playback speed
rate = Line.kr(speed, endSpeed, sustain) * (freq / 60.midicps);
// sample phase
// BufSampleRate adjusts the rate if the sound file doesn't have the same rate as the soundcard
numFrames = BufFrames.ir(bufnum);
sawrate = rate * BufSampleRate.ir(bufnum) / (absdif(begin, end) * numFrames);
phase = (speed.sign * LFSaw.ar(sawrate, 1)).range(begin, end) * numFrames;
sound = BufRd.ar(
numChannels: 2,
bufnum: bufnum,
phase: phase,
loop: 0,
interpolation: 4 // cubic interpolation
);
Out.ar(out, sound)
}).add;
100.do {
Synth(\test, [\bufnum, 4711]);
0.03.wait
};
}
}
)
@telephon nope, a lot of Buffer UGen: no buffer data
but the server does not crash
I've found the culprit: a division by zero in the playback synth when NumFrames
is 0. Can you check the updated branch again? Thanks!
To be honest I don't know why we can't define x / 0 = 0
to solve this (and also round pi to 3.1)
I also think we should solve the underlying problem first and make 0 = 1
– after all, also zero is something, and it is being treated unfairly.
Yes or just exclude both 0 and 1 from the class of numbers, and start at 2. I think this is how Euclid did things in the elements. I had a go at implementing that here https://github.com/Kairotic/weavingcodes/blob/master/types/Dyadicv2.hs
And if you count down further, you go 4, 3, 2, -2, -3, …
@telephon it's working really well! I tested a lot, no problems encountered, no glitches, no delays... perfect :ok_hand: thanks
as noted in https://club.tidalcycles.org/t/superdirt-lazy-samples-loading/3148/8, when a sample does not exists there's an uncatched error printed in console:
CALL STACK:
DoesNotUnderstandError:reportError
arg this = <instance of DoesNotUnderstandError>
Nil:handleError
arg this = nil
arg error = <instance of DoesNotUnderstandError>
Thread:handleError
arg this = <instance of Thread>
arg error = <instance of DoesNotUnderstandError>
Object:throw
arg this = <instance of DoesNotUnderstandError>
Function:protect
arg this = <instance of Function>
arg handler = <instance of Function>
var result = <instance of DoesNotUnderstandError>
Environment:use
arg this = <instance of Event>
arg function = <instance of Function>
var result = nil
var saveEnvir = <instance of Environment>
DirtEvent:play
arg this = <instance of DirtEvent>
OSCFuncRecvPortMessageMatcher:value
arg this = <instance of OSCFuncRecvPortMessageMatcher>
arg msg = [*11]
arg time = 190.65873981826
arg addr = <instance of NetAddr>
arg testRecvPort = 57120
OSCMessageDispatcher:value
arg this = <instance of OSCMessageDispatcher>
arg msg = [*11]
arg time = 190.65873981826
arg addr = <instance of NetAddr>
arg recvPort = 57120
Main:recvOSCmessage
arg this = <instance of Main>
arg time = 190.65873981826
arg replyAddr = <instance of NetAddr>
arg recvPort = 57120
arg msg = [*11]
^^ The preceding error dump is for ERROR: Message 'at' not understood.
RECEIVER: nil
should be fixed now. Can you check again?
perfect!
@telephon seems like synthdefs are not working now, I didn't noticed it before
ERROR: Non Boolean in test.
RECEIVER:
nil
PROTECTED CALL STACK:
Meta_MethodError:new 0x55f9479008c0
arg this = MustBeBooleanError
arg what = nil
arg receiver = nil
Object:mustBeBoolean 0x55f9468dc540
arg this = nil
DirtSoundLibrary:getEvent 0x55f94870ca00
arg this = a DirtSoundLibrary
arg name = kk
arg index = none
var allEvents = nil
var event = ( 'instrument': kk, 'hash': 424851966 )
DirtEvent:mergeSoundEvent 0x55f9477fba40
arg this = a DirtEvent
var soundEvent = nil
a FunctionDef 0x55f9477faf40
sourceCode = "<an open Function>"
a FunctionDef 0x55f948cff680
sourceCode = "<an open Function>"
Function:prTry 0x55f947ba5c40
arg this = a Function
var result = nil
var thread = a Thread
var next = nil
var wasInProtectedFunc = false
CALL STACK:
MethodError:reportError
arg this = <instance of MustBeBooleanError>
Nil:handleError
arg this = nil
arg error = <instance of MustBeBooleanError>
Thread:handleError
arg this = <instance of Thread>
arg error = <instance of MustBeBooleanError>
Object:throw
arg this = <instance of MustBeBooleanError>
Function:protect
arg this = <instance of Function>
arg handler = <instance of Function>
var result = <instance of MustBeBooleanError>
Environment:use
arg this = <instance of Event>
arg function = <instance of Function>
var result = nil
var saveEnvir = <instance of Environment>
DirtEvent:play
arg this = <instance of DirtEvent>
OSCFuncRecvPortMessageMatcher:value
arg this = <instance of OSCFuncRecvPortMessageMatcher>
arg msg = [*11]
arg time = 26.461100750603
arg addr = <instance of NetAddr>
arg testRecvPort = 57120
OSCMessageDispatcher:value
arg this = <instance of OSCMessageDispatcher>
arg msg = [*11]
arg time = 26.461100750603
arg addr = <instance of NetAddr>
arg recvPort = 57120
Main:recvOSCmessage
arg this = <instance of Main>
arg time = 26.461100750603
arg replyAddr = <instance of NetAddr>
arg recvPort = 57120
arg msg = [*11]
^^ The preceding error dump is for ERROR: Non Boolean in test.
RECEIVER: nil
Ah yes, I see. This works again, now.
@telephon @ndr-brt , once again thanks a lot for this
I just tried a midi mapping (midi0
) to external synths and got the same uncaught errors as were happening above
From my startup.scd
:
(
MIDIClient.init;
~midithroughport0 = MIDIOut.newByName("Midi Through", "Midi Through Port-0");
~midithroughport0.latency = 0;
~dirt.soundLibrary.addMIDI(\midi0, ~midithroughport0);
)
@cleary thank you, could you make sure you have the newest version? Doing git log
you should have commit 064d0ac835537124ea55c7acb50a49bb3d6c3612
in the top line.
If the error still occurs, can you post the error message? I expect it to be slightly different.
@telephon confirmed I have the correct commit on the top line
The error still occurs (to note, if I just comment the doNotReadYet
line in startup.scd, using midi0
does work on the same develop
branch):
ERROR: Non Boolean in test.
RECEIVER:
nil
PROTECTED CALL STACK:
Meta_MethodError:new 0x564a247cbf00
arg this = MustBeBooleanError
arg what = nil
arg receiver = nil
Object:mustBeBoolean 0x564a24329640
arg this = nil
DirtSoundLibrary:getEvent 0x564a251513c0
arg this = a DirtSoundLibrary
arg name = midi0
arg index = none
var allEvents = [ ( 'hash': 1325261192, 'midiOutNotFoundError': a DirtPartTimeError, 'play': a Function, 'midiout': a MIDIOut ) ]
var event = ( 'hash': 1325261192, 'midiOutNotFoundError': a DirtPartTimeError, 'play': a Function, 'midiout': a MIDIOut )
DirtEvent:mergeSoundEvent 0x564a2478f080
arg this = a DirtEvent
var soundEvent = nil
a FunctionDef 0x564a2478e580
sourceCode = "<an open Function>"
a FunctionDef 0x564a2556d380
sourceCode = "<an open Function>"
Function:prTry 0x564a24a629c0
arg this = a Function
var result = nil
var thread = a Thread
var next = nil
var wasInProtectedFunc = false
CALL STACK:
MethodError:reportError
arg this = <instance of MustBeBooleanError>
Nil:handleError
arg this = nil
arg error = <instance of MustBeBooleanError>
Thread:handleError
arg this = <instance of Thread>
arg error = <instance of MustBeBooleanError>
Object:throw
arg this = <instance of MustBeBooleanError>
Function:protect
arg this = <instance of Function>
arg handler = <instance of Function>
var result = <instance of MustBeBooleanError>
Environment:use
arg this = <instance of Event>
arg function = <instance of Function>
var result = nil
var saveEnvir = <instance of Environment>
DirtEvent:play
arg this = <instance of DirtEvent>
OSCFuncBothMessageMatcher:value
arg this = <instance of OSCFuncBothMessageMatcher>
arg msg = [*13]
arg time = 57.22197841946
arg testAddr = <instance of NetAddr>
arg testRecvPort = 57120
OSCMessageDispatcher:value
arg this = <instance of OSCMessageDispatcher>
arg msg = [*13]
arg time = 57.22197841946
arg addr = <instance of NetAddr>
arg recvPort = 57120
Main:recvOSCmessage
arg this = <instance of Main>
arg time = 57.22197841946
arg replyAddr = <instance of NetAddr>
arg recvPort = 57120
arg msg = [*13]
^^ The preceding error dump is for ERROR: Non Boolean in test.
RECEIVER: nil
Ah I see, yes. This has nothing to do with midi. It happens for all synth events:
SuperDirt.default = ~dirt;
~dirt.soundLibrary.addSynth(\zeze, (instrument: \default));
(type:\dirt, orbit:0, s: \zeze).play;
I've just fixed it.
Would be nice to have the possibility to configure superdirt to load samples lazily. E.g. a dictionary with paths and info could be loaded in memory at startup, but the sample audio data could be loaded at the first event.
This would permit to improve performances especially on dated hardware.