Mworks sound issue

Hi Chris,

I tested out the sound function of the new mworks release and encounter the following problem:

The task first runs as desired. Sound keeps playing after hitting pause or stop if resuming the task. However, if I close the experiment and reload it, sound no longer plays. No error message.
Only a full restart of the computer seems to help. [This sometimes leads to an inability to connect to localhost, which I solve with more restarts.]

I have this both on the mac in the rig and on my laptop. Both with mac external sound and headphones.
Loading a different task before loading the sound task again does not help.

My laptop is ventura 13.2.1 and the rig is on big sur 11.7.

Task is attached.

There are also delays when displaying the stimuli with the nextFrame requirement. Removing this requirement does not solve the issue. However, when this requirement is in place and the sound disappears when reloading the experiment, there are no more warnings about delays.

Alina

Hi Alina,

The task first runs as desired. Sound keeps playing after hitting pause or stop if resuming the task. However, if I close the experiment and reload it, sound no longer plays. No error message. Only a full restart of the computer seems to help.

Thanks for reporting this. It’s definitely a bug – actually, more like an interaction of several bugs, old and new. I’m working on untangling them.

In the meantime, you can work around it by closing and re-opening the MWServer application before loading a new experiment. There’s no need to restart your Mac.

This sometimes leads to an inability to connect to localhost, which I solve with more restarts.

That’s an unrelated problem that I see sometimes, too: Immediately after a restart, MWClient won’t connect to MWServer, and the problem persists until I restart again. It’s been hard to investigate because it doesn’t happen frequently.

There are also delays when displaying the stimuli with the nextFrame requirement.

Yes, that’s a result of something I mentioned in this discussion (under “Start time”). In short, the next frame time is too close to the time at which you invoke play_sound. As I said previously, I’m not sure what the right solution is.

One thing worth noting is that next_frame_time() gives the predicted time at which the next frame will start to appear on the display. With most displays, the frame will be drawn, top to bottom, over the full refresh period of the display (e.g. ~17ms for a 60Hz display). This means that it might actually make sense to start sound playback at the middle or end of the refresh period. For example:

play_sound (
    sound = sound_stimuli[sound_stim_index]
    // Start playback in the middle of the draw cycle for the
    // next frame
    start_time = next_frame_time() + 0.5e6 / refresh_rate()
    )

This also might give the sound enough extra time to avoid the “sound is starting later than requested” warning.

Alternatively, you could just omit start_time and let the sound start playing as soon as it can. As I said in the other discussion, the actual start time shouldn’t be more than 10-20ms after the play_sound action. If that uncertainty is unacceptable, then we’ll have to think about other ways to get audio and video in sync. Probably this will involve calling play_sound several frames in advance and then ensuring that stimuli don’t appear on screen until the desired time.

Cheers,
Chris

Thanks for reporting this. It’s definitely a bug – actually, more like an interaction of several bugs, old and new. I’m working on untangling them.

In the meantime, you can work around it by closing and re-opening the MWServer application before loading a new experiment. There’s no need to restart your Mac.

Good to know, thanks! Is this bug also related to this older issue that when I play several videos in a single trial, the sound disappears after the initial trial?

That’s an unrelated problem that I see sometimes, too: Immediately after a restart, MWClient won’t connect to MWServer, and the problem persists until I restart again. It’s been hard to investigate because it doesn’t happen frequently.

It seems to only happen with newer releases? It happens more to me nowadays I think.

Yes, that’s a result of something I mentioned in this discussion (under “Start time”). In short, the next frame time is too close to the time at which you invoke play_sound. As I said previously, I’m not sure what the right solution is.

I want to time the photodiode onset with the audio onset to have independent verification of the actual sound timing. For visual stimuli, the ‘stimulus onset’ in the mworks file and the true stimulus onset are variable, and the delays sometimes are different in different parts of an experimental session. This jittering is on a level that affects neural data quite a bit. And sound responses are if anything more time-sensitive.

Would this style of code below (ignoring delays) ensure simultaneous start of visual and auditory stimuli? Or would this induce a fixed 1 refresh delay?

play_sound (
    sound = sound_stimuli[sound_stim_index]
    start_time = next_frame_time() + 0.5e6 / refresh_rate()
    )
update_display ()

Delaying to the middle of a 60Hz refresh doesn’t solve the delay issue, and my experimental rig has a 120Hz refresh.

If I add a fixed (eg 20ms) delay in play_sound and then call the rest, could that work? Is this what you meant with calling it several frames in advance? It does seem to solve the delay warning. For me it is less important when the tone occurs rather than me knowing precisely when this was.

If I play a video with sound instead of using play_sound as I did before, do you have an idea how reliable the timing of the sound will be?

Perhaps the most accurate thing would be to have a video with a “photodiode object” hardcoded at the right location to find the onset of the video?

Thanks again!

Hi Alina,

A fix for the issue that broke sound playback after you unloaded your experiment is now in the nightly build. When have a chance, please test it out to confirm that you no longer experience the problem.

Is this bug also related to this older issue that when I play several videos in a single trial, the sound disappears after the initial trial?

I don’t think so. It involved stuff added in the recent audio revamp.

Is the older issue easy to reproduce? I’d like to look in to that, if possible.

It seems to only happen with newer releases? It happens more to me nowadays I think.

Yes, I think you’re right.

I want to time the photodiode onset with the audio onset to have independent verification of the actual sound timing.

There’s no way that’s ever going to give you independent verification of the actual sound timing. There are ways to tell MWorks (and thereby the OS) that a sound should start at the same time a display update is presented, but neither MWorks nor the OS can guarantee that it actually happens. Both the display and the audio output hardware can introduce their own delays, and there’s no way to know if they do without measuring.

What we really need is an audio equivalent of a photodiode. An analog microphone (meaning something without any digital processing) seems like the closest equivalent. If you fed both a photodiode signal and an analog microphone signal in to the same input device (e.g. neural recording system or DAQ), you would have a very accurate, independent measure both of when the visual and auditory stimuli were presented and how well the aligned with one another.

That said, a microphone could be a hassle due to it picking up environmental noise. Another possibility would be to send the analog audio output from the Mac through a splitter, with one end going to speakers and the other to the recording device. Assuming the speakers aren’t doing an kind of internal processing, it seems like that would be about as good as a microphone.

Anyway, that’s my non-expert take on this problem. I imagine folks who work with audio regularly have come up with solutions. Maybe reaching out to some of them would be helpful?

If I play a video with sound instead of using play_sound as I did before, do you have an idea how reliable the timing of the sound will be?

Under the hood, video playback uses the same audio/video synchronization tools as MWorks, so it would be subject to the same potential delays due to sample batching. It just wouldn’t provide any warning that the delays were happening.

Cheers,
Chris

A fix for the issue that broke sound playback after you unloaded your experiment is now in the nightly build. When have a chance, please test it out to confirm that you no longer experience the problem.

Yes it’s gone thank you!

Is this bug also related to this older issue that when I play several videos in a single trial, the sound disappears after the initial trial?

I don’t think so. It involved stuff added in the recent audio revamp.

Indeed I re-wrote my video task (hack to get audio stimuli…) as an audio task and now it appears to be working!

Though to make a sound group to work it is my impression that I need to add each individual sound as a resource (not just a folder). Otherwise I get a path not found error with an identical path. Not sure what’s going on there. This isn’t the case for visual stimuli as far as I remember…

Is the older issue easy to reproduce? I’d like to look in to that, if possible.

Not terribly easy in the sense that it is happening in my rig but with identical code and mworks version (the most recent included) not on my mac. The os version is different…

Anyway, that’s my non-expert take on this problem. I imagine folks who work with audio regularly have come up with solutions. Maybe reaching out to some of them would be helpful?

Yeah you’re probably right. The papers I read don’t mention anything but proper auditory researchers must be doing something… I suspect the splitter variant is more easily implemented if anything.

If I play a video with sound instead of using play_sound as I did before, do you have an idea how reliable the timing of the sound will be?

Under the hood, video playback uses the same audio/video synchronization tools as MWorks, so it would be subject to the same potential delays due to sample batching. It just wouldn’t provide any warning that the delays were happening.

Are these delays recorded somehow/ is the trigger taking these into account and as accurate as can be?

And just to be clear:

If I call the play_sound with next_Frame+x ms and then call update_display, the best estimate is that the tone has started x ms after the beginning of the screen refresh?

Hi Alina,

Apologies for the delayed reply.

Yes it’s gone thank you!

Great! FYI, I released MWorks 0.12.1 with this bug fix, as it seemed like it could potentially affect/annoy many users.

Indeed I re-wrote my video task (hack to get audio stimuli…) as an audio task and now it appears to be working!

Also good news!

Though to make a sound group to work it is my impression that I need to add each individual sound as a resource (not just a folder). Otherwise I get a path not found error with an identical path. Not sure what’s going on there. This isn’t the case for visual stimuli as far as I remember…

I’m not seeing this. In your file sound_set_definition_puretones_set1.mwel, if I remove all lines of the form

resource("sounds/tone_001_n_150.wav")

and replace them with the single line

resource('sounds')

the experiment still works. Do you have an example where this fails?

If I play a video with sound instead of using play_sound as I did before, do you have an idea how reliable the timing of the sound will be?

The start of the audio is still subject to delays due to audio sample batching, so the latency will be similar to what you’d see when attempting to start a sound at the next frame time.

One difference with video playback vs. audio-only playback is that the OS prioritizes audio/visual synchronization in video playback. This means that, if the audio does start late, the audio samples that were missed are just skipped, rather than delayed. I believe this is also what happened with the old, MWorks 0.11 play_sound: MWorks asked the OS to start the sound immediately (start time = now), and the OS dropped any samples that corresponded to times before the first sample batch request. (I believe that was the root of the issue in this discussion, where the first click in the click train was dropped if it was within the first 16-20ms of the audio file.)

To be clear, I didn’t know about any of this until I implemented the MWorks 0.12 audio changes. As of those changes, play_sound does not drop late samples. Instead, it plays them late and issues a warning message, with the assumption that you really want to play the whole sound. However, nothing changes with regard to the audio track of a video file. In that case, the sound playback is handled entirely by the OS and is out of MWorks’ hands (although I think that prioritizing A/V sync is the correct behavior, so I don’t see this as an issue).

Are these delays recorded somehow/ is the trigger taking these into account and as accurate as can be?

As I said, playback of a video’s audio track is out of MWorks’ hands. If there are delays, MWorks has no knowledge of them. If you need to know precisely when a video’s sound started, you’ll have to measure it externally (e.g. with a microphone or other form of audio capture).

If I call the play_sound with next_Frame+x ms and then call update_display, the best estimate is that the tone has started x ms after the beginning of the screen refresh?

Yes, but you have to be careful about how you do it. In pureTones.mwel, you do this:

play_sound (
    sound = sound_stimuli[sound_stim_index]
    start_time = next_frame_time()
    )
update_display ()

This isn’t 100% reliable, because the “next” frame may have changed between play_sound and update_display. This would be better:

var sound_start_time = 0
...
update_display (predicted_output_time = sound_start_time)
play_sound (
    sound = sound_stimuli[sound_stim_index]
    start_time = sound_start_time
    )

This way, you know that you’re asking the sound to start playing at the same time that the frame associated with the update_display will begin to be drawn. However, this still isn’t perfect, because you’ve lost some time waiting for update_display to complete. The best approach is to do what I do in my Simon example and invoke play_sound within a Render Actions stimulus:

var current_sound_started = false
...
render_actions start_current_sound {
    if (not current_sound_started) {
        play_sound (
            sound = sound_stimuli[sound_stim_index]
            start_time = next_frame_time()
            )
        current_sound_started = true
    }
}
...
current_sound_started = false
queue_stimulus (start_current_sound)
queue_stimulus (photodiode_image)
update_display ()

With this approach, you’re calling next_frame_time and scheduling the sound as early as possible. However, you still may get warnings saying “Sound x is starting y ms later than requested”, due to audio sample batching.

That said, I think I’ve found a solution to that issue. Specifically, after some additional research and experimentation, I found a way to reduce the number of samples in the batches and thereby reduce the start latency for audio playback. With this change, I can run Simon.mwel on both my iMac (60Hz refresh rate) and iPad Pro (120Hz refresh rate) without any warnings. I want to run some more tests, and if everything looks good, I’ll get this change in to the nightly build so you can try it. I’ll let you know when it’s available.

Cheers,
Chris

Hi Alina,

I found a way to reduce the number of samples in the batches and thereby reduce the start latency for audio playback.

This change is now in the nightly build. When you have a chance, please try it out and see if it eliminates the “Sound x is starting y ms later than requested” warnings in your experiment.

Thanks,
Chris

Hi Chris,

I didn’t test separately, with this nightly build and the “cleanest” implementation - current_sound_started method you described, I see no warnings even on a 120Hz monitor refresh. Can’t tell if that’s expected with this method or if the batch samples helped as well. But it does seem to work now as desired :blush:

Great! Thanks for letting me know.

Chris