-
Notifications
You must be signed in to change notification settings - Fork 761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SoundFile: fix cue method #3728
Conversation
HelpSource/Classes/SoundFile.schelp
Outdated
@@ -118,7 +118,7 @@ e = f[1].cue( (addAction: 2, group: 1) ); // synth will play ahead of the defaul | |||
|
|||
argument:: closeWhenDone | |||
|
|||
A flag to indicate wether the code::SoundFile:: will be closed after it's done playing. Default is False. | |||
A flag to indicate wether the code::SoundFile:: and buffer will be closed after playback is finished. Default is False. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wether -> whether
server.sendBundle(server.latency, ["/b_close", ev[\bufnum]], | ||
["/b_read", ev[\bufnum], path, ev[\firstFrame], ev[\bufferSize], 0, 1]); | ||
ev.close; | ||
this.close; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if someone adds a method extension:
+ Object {
close { ... do something ... }
}
the ev.close
will break.
better write:
ev[\close].value(ev)
~firstFrame = ~firstFrame ? 0; | ||
~lastFrame = ~lastFrame ? numFrames; | ||
~sustain = (~lastFrame - ~firstFrame)/(sampleRate ?? {server.options.sampleRate ? 44100}); | ||
~sustainTime = (~lastFrame - ~firstFrame)/(sampleRate ?? {server.sampleRate ? 44100}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why sustainTime
? sustain
is the standard value for the length of a sound event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See @jamshark70's comment in #3713 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See notes on tests below. Otherwise LGTM :)
|
||
// playNow argument | ||
cueEvent = soundFile.cue((), true); | ||
0.5.wait; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any way to wait on something specific here (other than 0.5)? if not, we should add a callback argument to cue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure there's a way to avoid waiting. I can look into this test again next week to see if I can improve it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formally, the right thing to wait for would be /n_go
coming back from the server. But, if you're waiting for that, then the test needs to fail if /n_go
doesn't come back. The only way I can think of is to include a timer.
If you don't handle the case of a broken server not sending /n_go
back (or any other reply), then the entire test suite hangs at this point. You can use the incoming reply to move forward with the test sooner than the wait time, but you can't get rid of a wait time altogether.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formally, the right thing to wait for would be /n_go coming back from the server. But, if you're waiting for that, then the test needs to fail if /n_go doesn't come back. The only way I can think of is to include a timer.
Then let's do that please. In the last few months we've established a pattern in tests for dealing with callback timeout - fork
a timeout thread that signals a condition variable. Since there is so so much async callback code in SC I think we should probably find a way to simplify it.
Buffer.freeAll; | ||
server.quit.remove; | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be split into several tests.; one for playNow
; one for setting controls; and one for closeWhenDone
. there should be additional tests for when playNow
is set to false, and when closeWhenDone
is set to false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind separating this into multiple tests. The reason I lumped them together is because they all need the server to be booted. We've already discussed why we don't want to make use of setUp
and tearDown
to boot the server in this particular UnitTest in a previous PR (#3712 (comment)). Maybe we need to create a separate UnitTest for cue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you could move those tests into a separate TestSoundFile_Cue
or TestSoundFile_Server
.
See @brianlheim 's comment here #3737 (comment). It came to my attention when working on this that a SoundFile is not required to be open in order to use cue. @brianlheim is making me think that |
I am definitely in favor of marking it unused. Since it's the final parameter in the parameter list, we could deprecate and remove it later! |
It look like the real intention of update: The argument's name can stay as is since the Buffer needs to be closed (not just freed) because it is being used by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, OK, apart from a couple of notes.
~firstFrame = ~firstFrame ? 0; | ||
~lastFrame = ~lastFrame ? numFrames; | ||
~sustain = (~lastFrame - ~firstFrame)/(sampleRate ?? {server.options.sampleRate ? 44100}); | ||
~sustainTime = (~lastFrame - ~firstFrame)/(sampleRate ?? {server.sampleRate ? 44100}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only a minor bit of style-guide fussiness here: generally we recommend spaces around binary ops (here I see spaces around -
in the numerator but no spaces around the higher-level /
operator), and spaces inside of curly braces.
~sustainTime = (~lastFrame - ~firstFrame) / (sampleRate ?? { server.sampleRate ? 44100 });
|
||
// playNow argument | ||
cueEvent = soundFile.cue((), true); | ||
0.5.wait; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formally, the right thing to wait for would be /n_go
coming back from the server. But, if you're waiting for that, then the test needs to fail if /n_go
doesn't come back. The only way I can think of is to include a timer.
If you don't handle the case of a broken server not sending /n_go
back (or any other reply), then the entire test suite hangs at this point. You can use the incoming reply to move forward with the test sooner than the wait time, but you can't get rid of a wait time altogether.
You could use a condition with a (
c = Condition.new;
fork {
"started".postln;
c.hang(2);
if(c.test) { "was activated" } { "timed out" }.postln;
}
)
(
c = Condition.new;
fork {
"started".postln;
fork { 1.wait; c.test = true; c.signal };
c.hang(2);
if(c.test) { "was activated" } { "timed out" }.postln;
}
) |
No, that was found to be unreliable. It doesn't cancel the scheduled wake-up if you unhang or signal the condition, so the thread is scheduled twice: once by unhang, and again by the wait time. It's a very clever idea. Like a lot of clever programming ideas, it seems to solve a problem without architecting a solution, but it causes other problems along the way. |
Ah I see. The method I am not completely sure it is true though, and why (I remember we had an exchange about this, but I can't find it). This is a bit off topic, sorry, here is a test: (
f = { |dt, maxTime, callback|
var c = Condition.new;
fork {
var t0 = thisThread.seconds;
fork { dt.wait; c.test = true; c.signal };
c.hang(maxTime);
callback.(thisThread.seconds - t0);
}
};
)
(
var c = 0;
(0..20).normalize(0, 1).do { |x, i| f.(x, 0.5, { |val| if(val <= 0.5) { c = c + 1 }; if(i == 19) { c.postln } }) };
)
|
Ok, I see, if you do anything after that callback, you are not guaranteed that hanging another time will succeed. This is because the line |
Could this discussion go in the mailing list thread about hang/wait please? That's why I started it. |
what is the subject of the email? I can't find the thread. |
Testing async code |
Please have another look at the tests. I've decided that |
I've applied these changes to 3.10 locally and all tests pass. @brianlheim please review when you get the chance. Please squash when merging. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 changes in the tests and then should be all good :) thanks for your patience
test_cue_playNow { | ||
var cueEvent, condition = Condition.new; | ||
|
||
OSCFunc({ condition.unhang }, '/n_go', server.addr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please clean this up at the end of the test in case it doesn't fire.
|
||
OSCFunc({ condition.unhang }, '/n_go', server.addr); | ||
cueEvent = soundFile.cue((), true); | ||
fork { 3.wait; condition.unhang }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stop this route at the end of the test so it doesn't fire and unhang a future hang
Should be picked to 3.10 right @snappizz ? |
Oh dear, that's a messy commit history. I should have force pushed a rebased version of this... my bad |
This PR introduces a few fixes and documentation improvements to SoundFile's cue method.
~sustainTime
closeWhenDone
argument. Previously, this arg had no effect. Now, the OSCFunc correctly fires which closes the bufferand SoundFile.closeWhenDone
.bufferSize
used to be hardcoded to a value, even though the documentation suggested that it was settable. This control is now settable and the documentation has been improved.server.sampleRate
instead of looking it up in server.options.TestSoundFileTestSoundFile_Server