Skip to content
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

Merged
merged 19 commits into from
Sep 18, 2018
Merged

Conversation

patrickdupuis
Copy link
Contributor

@patrickdupuis patrickdupuis commented May 16, 2018

This PR introduces a few fixes and documentation improvements to SoundFile's cue method.

  • This PR fixes a mistake introduces in a previous PR (SoundFile: fix SynthDef and Helpfile to match #3713) by correctly renaming the event key to ~sustainTime
  • The main fix here is related to the closeWhenDone argument. Previously, this arg had no effect. Now, the OSCFunc correctly fires which closes the buffer and SoundFile.
  • The documentation was improved to indicate the closing of the buffer by 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.
  • The sample rate is now obtained by server.sampleRate instead of looking it up in server.options.
  • An unnecessary Condition was removed.
  • A test for the cue method was added to TestSoundFile TestSoundFile_Server

@patrickdupuis patrickdupuis added bug Issues that relate to unexpected/unwanted behavior. Don't use for PRs. enhancement comp: class library SC class library labels May 16, 2018
@patrickdupuis patrickdupuis added this to the 3.10 milestone May 16, 2018
@@ -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.
Copy link
Member

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;
Copy link
Member

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});
Copy link
Member

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.

Copy link
Contributor Author

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)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, makes sense.

Copy link
Contributor

@mossheim mossheim left a 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;
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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;

}
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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.

@patrickdupuis
Copy link
Contributor Author

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 closeWhenDone is simply bad design. Maybe it should be left unused or removed?

@mossheim
Copy link
Contributor

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!

@patrickdupuis
Copy link
Contributor Author

patrickdupuis commented May 31, 2018

It look like the real intention of closeWhenDone is to free the buffer, not the soundfile, when finished playing. I had this idea early on and I wish I had gone with my instinct. Would it be ok to rename this argument freeWhenDone (or similar), document it properly, and remove the this.close from within the OSCFunc?

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 DiskIn.

Copy link
Contributor

@jamshark70 jamshark70 left a 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});
Copy link
Contributor

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;
Copy link
Contributor

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.

@telephon
Copy link
Member

telephon commented Jun 9, 2018

You could use a condition with a hang value for time out:

(
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;
	
	
}
)

@jamshark70
Copy link
Contributor

You could use a condition with a hang value for time out.

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.

@telephon
Copy link
Member

telephon commented Jun 9, 2018

Ah I see. The method hang was never intended to do this, so it is just a "clever idea" in that (harmless) sense.

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 } }) };
)

@telephon
Copy link
Member

telephon commented Jun 9, 2018

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 fork { dt.wait; c.test = true; c.signal }; schedules past the hang time.

@mossheim
Copy link
Contributor

mossheim commented Jun 9, 2018

Could this discussion go in the mailing list thread about hang/wait please? That's why I started it.

@telephon
Copy link
Member

telephon commented Jun 9, 2018

what is the subject of the email? I can't find the thread.

@alln4tural
Copy link

Testing async code

@patrickdupuis
Copy link
Contributor Author

Please have another look at the tests. I've decided that closeWhenDone only needs to close the buffer. This makes the most sense. I believe the documentation has been update correctly. Thanks 🍰

@patrickdupuis
Copy link
Contributor Author

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.

@mossheim mossheim self-requested a review September 16, 2018 00:39
Copy link
Contributor

@mossheim mossheim left a 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);
Copy link
Contributor

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 };
Copy link
Contributor

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

@mossheim mossheim merged commit f377b09 into supercollider:develop Sep 18, 2018
@mossheim
Copy link
Contributor

Should be picked to 3.10 right @snappizz ?

@patrickdupuis
Copy link
Contributor Author

Oh dear, that's a messy commit history. I should have force pushed a rebased version of this... my bad

@patrickdupuis patrickdupuis deleted the topic/sf-cue1 branch September 18, 2018 18:55
mossheim added a commit that referenced this pull request Sep 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issues that relate to unexpected/unwanted behavior. Don't use for PRs. comp: class library SC class library enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants