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

Waveout driver #466

Merged
merged 14 commits into from
Dec 1, 2018
Merged

Waveout driver #466

merged 14 commits into from
Dec 1, 2018

Conversation

carlo-bramini
Copy link
Contributor

This is a new driver that adds sound output by using Windows WaveOut APIs.
It is an alternative to DirectSound driver for Windows based platforms.

It has been tested under some Operating Systems, like Windows CE x86 and Windows Mobile 2003 for ARMv5. Those OSs do not support DirectSound at all, so there is no other choice to use WaveOut APIs if you want to get sound output. These platforms also supports only UNICODE functions, I already implemented support for widechars. Windows NT 4.0 supports just DirectSound 3.0a, but I remember that in the past I was never able to make it working properly, so I would suggest to use WaveOut driver also on that system. I tested it also on Windows 98 SE, Windows XP 32bit and Windows 7 64 bits without problems.

@derselbst
Copy link
Member

derselbst commented Nov 20, 2018 via email

@carlo-bramini
Copy link
Contributor Author

I implemented a new way for closing the driver, I hope you will like more than the previous one.
Basically, my changes are:

  1. I moved the call to waveOutUnprepareHeader() inside the callback function, in that point we are sure that the sample buffer is not playing anymore, so I could remove the block of code with Sleep() inside delete_fluid_waveout_audio_driver().
  2. I added an event that will be signaled when all allocated sample buffers are played, so I could replace the second Sleep() with a WaitForSingleObject().
  3. I noticed a small delay with the response of my MIDI controller, so I changed some values.

@derselbst
Copy link
Member

While this looks better, pls. review the documentation of waveOutProc callback function. It says:

Applications should not call any system-defined functions from inside a callback function, except for [...]. Calling other wave functions will cause deadlock.

I.e. calling waveOutUnprepareHeader() in the callback will cause deadlocks. Why don't you use waveOutReset() to stop playback and get the buffers back?

@carlo-bramini
Copy link
Contributor Author

Because waveOutReset() is also known to cause a deadlock if it is called in the same thread of the callback context. It should be used with CALLBACK_THREAD, or with CALLBACK_FUNCTION when there are no buffer playing at the same time (the sample that you linked is using streaming interface for this purpose).

@derselbst
Copy link
Member

I thought about calling waveOutReset() in delete_fluid_waveout_audio_driver()?

Copy link
Member

@derselbst derselbst left a comment

Choose a reason for hiding this comment

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

Seems that I just now after your change fully understand your previous comment. I didn't expect it to be that hard. Anyway, this looks better. Still, I have two questions:

if (dev->hWaveOut != NULL)
{
waveOutReset(dev->hWaveOut);
waveOutClose(dev->hWaveOut);
Copy link
Member

Choose a reason for hiding this comment

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

Why are you closing waveOut before unpreparing the headers? Shouldn't it be

waveOutUnprepareHeader()
waveOutClose()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I did a copy/paste mistake ^^;
Fixed.

PostThreadMessage(dev->dwThread, WM_CLOSE, 0, 0);
WaitForSingleObject(dev->hThread, INFINITE);

CloseHandle(dev->hThread);
Copy link
Member

Choose a reason for hiding this comment

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

Why do you quit the thread after waveOut has been closed? This seems wrong. The thread could be preempted during the call to fluid_synth_write_*(). During that time, waveOut could be closed. When the thread wakes up again, it will call a disposed waveOut instance, which is likely to crash.

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 suppose that such call to waveOutWrite will return MMSYSERR_INVALHANDLE.
If the thread is closed before the waveOut handle, I'm a bit feared about what it could happen inside the waveOut functions, because I have no control on them. Perhaps, we could call waveOutReset, close the thread and finally call waveOutClose, probably it should be less risky.

Copy link
Member

Choose a reason for hiding this comment

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

If the thread is closed before the waveOut handle, I'm a bit feared about what it could happen inside the waveOut functions

I guess it would just keep filling the message queue. Should be unproblematic. WinMidi does it the same way.

Perhaps, we could call waveOutReset, close the thread and finally call waveOutClose

This should be well defined, but could result in a short sequence of audible sound. Think of:

waveOutReset()
// playback stopped, but thread is still busy inside `fluid_synth_write_*()`
waveOutWrite()
// thread finishes
waveOutUnprepareHeader()
waveOutClose()

@carlo-bramini
Copy link
Contributor Author

I was also thinking that perhaps it would be worth to unify the winmidi and waveout drivers in a single source. They will both use the same thread, the MIDI for adding the buffers for SYSEX, the waveout will play the samples with waveOutWrite(). I will try to do some experiments.

@derselbst
Copy link
Member

I was also thinking that perhaps it would be worth to unify the winmidi and waveout drivers in a single source.

We have separate cmake options and separate *_SUPPORT macros. I would prefer to keep them separate.

@carlo-bramini
Copy link
Contributor Author

carlo-bramini commented Nov 25, 2018

I would prefer to keep them separate.

They are. I just made it.
I will share the code.

In my opinion, letting the waveOut to flush the buffers for few milliseconds is still the best solution
@derselbst
Copy link
Member

1d5ebce I'm not quite convinced that it's correct to call waveOutUnprepareHeader() in the CALLBACK_THREAD (although not explicitly documented). What makes you so sure that waveOut does not ask to fill the same buffer twice? E.g. in case of an XRun? The cleanup may try to unprepare the same buffer multiple times and leak the other ones.

What's the problem with the previous solution?

@carlo-bramini
Copy link
Contributor Author

What makes you so sure that waveOut does not ask to fill the same buffer twice?

Because the buffer is in the DONE state.

What's the problem with the previous solution?

Actually, the problem of previous solution was that the waveOutReset put all buffers in DONE state, but it does not unprepare them. If the waveOutReset is called when the thread is executing for example the fluid_synth_write_s16, then it will play it again, and the later waveOutUnprepareHeader() will fail with WAVERR_STILLPLAYING return value.

@derselbst
Copy link
Member

Because the buffer is in the DONE state.

Ofc., I was missing that.

If the waveOutReset is called when the thread is executing for example the fluid_synth_write_s16, then it will play it again, and the later waveOutUnprepareHeader() will fail with WAVERR_STILLPLAYING return value.

Ok, then let's take the current approach. Would be nice if you could re-test this on Win98, NT4.0 and XP on occasion.

@carlo-bramini
Copy link
Contributor Author

I just re-tested on XP and Windows 98, no problems found. I have not NT 4.0 at the moment, but I'm confident that it works fine.

@derselbst
Copy link
Member

Good, if it works on Win98 it probably works everywhere else. Thanks!

@derselbst derselbst added this to the 2.1 milestone Dec 1, 2018
@derselbst derselbst changed the base branch from master to 2.1-testing December 1, 2018 14:03
@derselbst derselbst merged commit cfe2d15 into FluidSynth:2.1-testing Dec 1, 2018
@carlo-bramini carlo-bramini deleted the waveout-driver branch December 4, 2018 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants