Old 19th September 2014, 17:44   #1
LoRd_MuldeR
Major Dude
 
LoRd_MuldeR's Avatar
 
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
DSP plug-in - how to detect *seeking* operations?

Hello.

I'm working on a DSP plug-in for Winamp that requires a rather large FIFO buffer internally. So, at the beginning of the file, I need to fill the buffer with a certain minimum number of input samples first, before I can actually return some processed output samples.

Thankfully, the modify_samples() function can specify the number of available output samples via its return value. So, at the beginning of the file, I can have several modify_samples() calls where I consume all input samples but return zero output samples. This way I can fill my internal buffer without introducing a delay. That works nice! Playback in Winamp starts almost instantly without any noticeable delay.

Now, the problem is: As soon as the user seeks, it will take several seconds until the audio updates - which is annoying for the user. It's clear why that is: It will take some time until the audio has propagated through the FIFO buffer after the seeking operation. It's also clear what I would need to do: reset the buffer on every seek operation and then re-fill the buffer, just like at the beginning of the file. But currently I have no way to know when a seeking operation has occurred, because AFAIK modify_samples() doesn't indicate this.

So, what is the proper way to detect a seek operation for a Winamp DSP plug-in ???

Thanks in advance!

My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc
My source of inspiration: http://youtu.be/lCwY4_0W1YI
LoRd_MuldeR is offline   Reply With Quote
Old 19th September 2014, 18:01   #2
DrO
 
Join Date: Sep 2003
Posts: 27,873
from a quick look through the source code i cannot find anything which leads to the DSP chain being notified of a seek event as you're sort of just meant to process things as you get passed it and what you've described isn't how things were designed to be used.

the only option i can otherwise think off is setting up a hook and look for any of the notification messages which plug-ins subclassing the main window can see. obviously you cannot do subclassing as a DSP and have to consider using SetWindowsHookEx (using WH_GETMESSAGE iirc) to catch say IPC_CB_MISC or one of those sorts of notifications.

though what you're trying to do has me intrigued...
DrO is offline   Reply With Quote
Old 19th September 2014, 18:24   #3
LoRd_MuldeR
Major Dude
 
LoRd_MuldeR's Avatar
 
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
Thanks for your reply!

Quote:
Originally Posted by DrO View Post
from a quick look through the source code i cannot find anything which leads to the DSP chain being notified of a seek event as you're sort of just meant to process things as you get passed it and what you've described isn't how things were designed to be used.
Too bad...

Quote:
Originally Posted by DrO View Post
the only option i can otherwise think off is setting up a hook and look for any of the notification messages which plug-ins subclassing the main window can see. obviously you cannot do subclassing as a DSP and have to consider using SetWindowsHookEx (using WH_GETMESSAGE iirc) to catch say IPC_CB_MISC or one of those sorts of notifications.
That sounds interesting. Do you have any more details about this method, e.g. a very simply example on how to use SetWindowsHookEx() inside a plug-in to retrieve info from the main program? That would be great...

My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc
My source of inspiration: http://youtu.be/lCwY4_0W1YI
LoRd_MuldeR is offline   Reply With Quote
Old 19th September 2014, 19:33   #4
LoRd_MuldeR
Major Dude
 
LoRd_MuldeR's Avatar
 
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
UPDATE

I think I figured out a way! I can use the Winamp IPC API to get the current position:
Quote:
SendMessage(this_mod->hwndParent, WM_WA_IPC, 0, IPC_GETOUTPUTTIME);
Now, if I do this in every modify_samples() call, I can check if the position is increasing steadily. If I notice a "gap" between the current and the previous position or if the current position is before the previous one, I can assume that a seek must have occurred

This feels more like a hack and may be a bit inefficient, but unless somebody has a better idea...

My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc
My source of inspiration: http://youtu.be/lCwY4_0W1YI
LoRd_MuldeR is offline   Reply With Quote
Old 19th September 2014, 22:07   #5
DrO
 
Join Date: Sep 2003
Posts: 27,873
Quote:
Originally Posted by LoRd_MuldeR View Post
That sounds interesting. Do you have any more details about this method, e.g. a very simply example on how to use SetWindowsHookEx() inside a plug-in to retrieve info from the main program? That would be great...
it shows how things have moved on if people generally don't know about such things anymore (makes me feel old).
code:
HHOOK nowPlayingHook2 = SetWindowsHookExW(WH_GETMESSAGE, GetMsgProc, instance, GetCurrentThreadId());
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION) {
LPMSG msg = (LPMSG)lParam;
if (msg->message == WM_WA_IPC) {
if (msg->lParam == IPC_CB_MISC && msg->wParam == IPC_CB_MISC_STATUS) {
// doing stuff
} else if (msg->lParam == IPC_UPDTITLE) {
// doing stuff
}
}
}
return (nowPlayingHook2 ? CallNextHookEx(nowPlayingHook2, nCode, wParam, lParam) : 0);
}

you may also need to consider looking at WH_CALLWNDPROC for some of the other WM_WA_IPC messages (is it bit quirky in that some will go with the first of what i posted and others will go with the second option).

Quote:
Originally Posted by LoRd_MuldeR View Post
This feels more like a hack and may be a bit inefficient, but unless somebody has a better idea...
i expect you've not looked at the CPU usage as you're polling that state far far too often and it'll cripple Winamp on lesser hardware.
DrO is offline   Reply With Quote
Old 19th September 2014, 22:31   #6
LoRd_MuldeR
Major Dude
 
LoRd_MuldeR's Avatar
 
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
Quote:
Originally Posted by DrO View Post
i expect you've not looked at the CPU usage as you're polling that state far far too often and it'll cripple Winamp on lesser hardware.
Of course I have

CPU usage is during playback was around ~2% overall. And that's on my antiquated Core2 machine. Quite okay, I suppose.


Quote:
Originally Posted by DrO View Post
it shows how things have moved on if people generally don't know about such things anymore (makes me feel old).
code:
HHOOK nowPlayingHook2 = SetWindowsHookExW(WH_GETMESSAGE, GetMsgProc, instance, GetCurrentThreadId());
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode == HC_ACTION) {
LPMSG msg = (LPMSG)lParam;
if (msg->message == WM_WA_IPC) {
if (msg->lParam == IPC_CB_MISC && msg->wParam == IPC_CB_MISC_STATUS) {
// doing stuff
} else if (msg->lParam == IPC_UPDTITLE) {
// doing stuff
}
}
}
return (nowPlayingHook2 ? CallNextHookEx(nowPlayingHook2, nCode, wParam, lParam) : 0);
}

you may also need to consider looking at WH_CALLWNDPROC for some of the other WM_WA_IPC messages (is it bit quirky in that some will go with the first of what i posted and others will go with the second option).
Thank you! I will give that method a try too and see how well it goes.

Regards,
MuldeR

My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc
My source of inspiration: http://youtu.be/lCwY4_0W1YI
LoRd_MuldeR is offline   Reply With Quote
Old 19th September 2014, 22:37   #7
DrO
 
Join Date: Sep 2003
Posts: 27,873
you'll need to determine the notification you need, but it's far better to just react to what is sent rather than polling things - yes it might be ok for you but when you factor in skin differences, other plug-ins, machine and OS, etc, it's just not a great thing to be polling all the time imho (based on doing something like it myself and it noticeably affecting performance).
DrO is offline   Reply With Quote
Old 19th September 2014, 22:46   #8
LoRd_MuldeR
Major Dude
 
LoRd_MuldeR's Avatar
 
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
Yes, I know "polling" is not an ideal solution here. But as long as there's no better way...

I tried your SetWindowsHookExW() method and it turns out a WM_WA_IPC message with lparam=IPC_CB_MISC and wParam=IPC_CB_MISC_STATUS is received by the hook when I start or stop playback, but not when seeking. Actually I also logged all WM_WA_IPC messages received by the hook and it turns out no message at all is received on seeking. Bummer!

My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc
My source of inspiration: http://youtu.be/lCwY4_0W1YI
LoRd_MuldeR is offline   Reply With Quote
Old 19th September 2014, 22:50   #9
DrO
 
Join Date: Sep 2003
Posts: 27,873
as i said, not all WM_WA_IPC messages will come in on what i posted (and it was just an example and not a specific case for what you need - i cannot check things to find out what is the best message at the moment).

the other option is to look at everything via something like spy++ and if that shows messages which you're not seeing then you'll need to try the other hook type (as what i posted is what the SHOUTcast DSP plug-in uses for tracking some of the song / metadata changes and for that to work against all of the WM_WA_IPC messages it needed to hook both hook methods).
DrO is offline   Reply With Quote
Old 19th September 2014, 23:04   #10
LoRd_MuldeR
Major Dude
 
LoRd_MuldeR's Avatar
 
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
I logged all messages (of type WM_WA_IPC) that were received by the hook. Either via WH_GETMESSAGE or WH_CALLWNDPROC. But no luck. No message appears on seeking operations, only on start/stop. So I guess I'm stuck with the current method...

My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc
My source of inspiration: http://youtu.be/lCwY4_0W1YI
LoRd_MuldeR is offline   Reply With Quote
Old 19th September 2014, 23:06   #11
DrO
 
Join Date: Sep 2003
Posts: 27,873
when i can next look in the source code i'll see if there's anything in there. as i really really don't like you polling things (does that come across? ).
DrO is offline   Reply With Quote
Old 19th September 2014, 23:13   #12
LoRd_MuldeR
Major Dude
 
LoRd_MuldeR's Avatar
 
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
Quote:
Originally Posted by DrO View Post
when i can next look in the source code i'll see if there's anything in there. as i really really don't like you polling things (does that come across? ).
Well, thanks!

Though, if you have source access, the best solution IMHO would be adding a modify_samples_ex() function to the winampDSPModule struct, which has an additional "status" parameter that is either STATUS_BEGIN (i.e. this is the first call after playback was started), STATUS_RESUME (i.e. this is the first call immediately after playback was paused and resumed), STATUS_SEEK (i.e. this is the first call immediately after a seeking operation) or STATUS_CONTINUE (default case when playback was not interrupted recently)

My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc
My source of inspiration: http://youtu.be/lCwY4_0W1YI
LoRd_MuldeR is offline   Reply With Quote
Old 22nd September 2014, 15:22   #13
DrO
 
Join Date: Sep 2003
Posts: 27,873
ok, i've finally had a chance to have a check and there's no notification message at all and it transpires that gen_ff (modern skin support) is doing as you are and polling things (though it's caching things so as not to hammer it too often).

with the suggestion, i'm not keen on such a method as it complicates what needs to be done within Winamp (as seeking isn't really tracked and what the UI displays is updated based on the input plug-in response to the seek request) and it's too specific to just one type of plug-in for what is a somewhat niche usage case.

so i think i'm going to add an extra IPC_CB_MISC parameter of #define IPC_CB_MISC_SEEK 9 which is sent when the seek request has just been passed onto the input plug-in at the time. that is then generally plug-in agnostic (yes it's not ideal it'll need hooking / subclassing but for most of the plug-ins that poll, this shouldn't be an issue as they'd be hooking / subclassing anyway).

otherwise you'll need to keep doing the polling thing (and i'll need to at some point move gen_ff over to using this callback instead of polling things *joy*).
DrO is offline   Reply With Quote
Old 22nd September 2014, 15:52   #14
LoRd_MuldeR
Major Dude
 
LoRd_MuldeR's Avatar
 
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
Quote:
Originally Posted by DrO View Post
so i think i'm going to add an extra IPC_CB_MISC parameter of #define IPC_CB_MISC_SEEK 9 which is sent when the seek request has just been passed onto the input plug-in at the time. that is then generally plug-in agnostic (yes it's not ideal it'll need hooking / subclassing but for most of the plug-ins that poll, this shouldn't be an issue as they'd be hooking / subclassing anyway).
Thanks! If there will ever be a new Winamp release with that functionality added, I'll happily use it. Though, how would I go about detecting whether IPC_CB_MISC_SEEK messages are going to be sent or not? I'd probably want to still support older Winamp versions too, which means polling needs to be used if IPC_CB_MISC_SEEK won't be sent. Do I need to make an educated guess based on the Winamp version or is there some recommended to query available features at runtime?

BTW: Please make sure we also get the new message when a new track has started or when the user manually switches tracks.


Quote:
Originally Posted by DrO View Post
with the suggestion, i'm not keen on such a method as it complicates what needs to be done within Winamp (as seeking isn't really tracked and what the UI displays is updated based on the input plug-in response to the seek request) and it's too specific to just one type of plug-in for what is a somewhat niche usage case.
Is it really a "niche" usage case?

I would think that any DSP plug-in which somehow accesses "previous" samples, like "Echo", "Reverb" or anything in this vein, would need to detect seeking operations or track changes, in order to handle that situation gracefully. That's because such plug-in will usually assume we are processing a continuous stream of samples - which no longer is the case after a seek or track change. "Echoing" samples from the pre-seek playback location (or even from the previous track) into the new playback location would clearly sound bad/strange...


Quote:
Originally Posted by DrO View Post
otherwise you'll need to keep doing the polling thing (and i'll need to at some point move gen_ff over to using this callback instead of polling things *joy*).
One problem I'm facing with the "polling" method is: If I query the current position in each modify_samples() call and compute the delta to the previous position, I usually get delta's in the 30 ms range during normal playback. So, a threshold of ~150 ms seems to work fine for seek detection - most of the time. However, sometimes Winamp doesn't respond for a longer time, e.g. when I close the preferences dialog. Then, when Winmap does respond again, I get an unusually large delta, like ~700 ms, for a single time. My current workaround is to measure the time between two successful position updates, simply via GetTickCount() deltas, and use TICKCOUNT_DELTA + C as the threshold (where C is something like 150 ms). That seems to work okay, though seems a bit complicated. Is there a "cleaner" method?

Regards.

My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc
My source of inspiration: http://youtu.be/lCwY4_0W1YI
LoRd_MuldeR is offline   Reply With Quote
Old 22nd September 2014, 16:26   #15
DrO
 
Join Date: Sep 2003
Posts: 27,873
Quote:
Originally Posted by LoRd_MuldeR View Post
Thanks! If there will ever be a new Winamp release with that functionality added, I'll happily use it. Though, how would I go about detecting whether IPC_CB_MISC_SEEK messages are going to be sent or not? I'd probably want to still support older Winamp versions too, which means polling needs to be used if IPC_CB_MISC_SEEK won't be sent. Do I need to make an educated guess based on the Winamp version or is there some recommended to query available features at runtime?
you can use #define IPC_IS_COMPATIBILITY_ENABLED 3051 and then do a bitmask compare against 0x1 - that will return 1 if it's enabled on 6.x clients or 1 for all 2.x/5.x clients. though you can otherwise just do a version check for anything above and including 0x5080 to achieve the same thing. but (IPC_IS_COMPATIBILITY_ENABLED & 0x1) is going to be more reliable in the long-term.

Quote:
Originally Posted by LoRd_MuldeR View Post
BTW: Please make sure we also get the new message when a new track has started or when the user manually switches tracks.
there already are messages for getting notified of that IPC_PLAYINGFILEW (and also the misc_cb versions) as what you've described is the same action i.e. starting a track however it has been started is still starting a track. so i don't think there's anything i need to add to what is already provided.

Quote:
Originally Posted by LoRd_MuldeR View Post
Is it really a "niche" usage case?
yes it is and is why i asked at the start what you're trying to do as based on what you've said so far, it's a lot of work on my side for just one specific plug-in to use it. if anything, it'd probably be better to modify the DSP structure to allow for the wndproc to just be passed through so there's no need to do any hooking of things, thus making it easier to act as a dsp/gen hybrid as you seem to be trying to do.

Quote:
Originally Posted by LoRd_MuldeR View Post
One problem I'm facing with the "polling" method is: If I query the current position in each modify_samples() call and the compute the delta to the previous position, I usually get delta's in the 30 ms range during normal playback. So, a threshold of ~150 ms seems to work fine for seek detection - most of the time. However, sometimes Winamp doesn't respond for a longer time, e.g. when I close the preferences dialog. Then, when Winmap does respond again, I get an unusually large delta, like ~700 ms, for a single time. My current workaround is to measure the time between two successful position updates, simply via GetTickCount() deltas, and use TICKCOUNT_DELTA + C as the threshold (where C is something like 150 ms). That seems to work okay, though seems a bit complicated. Is there a "cleaner" method?Regards.
not that i can think off.
DrO is offline   Reply With Quote
Old 22nd September 2014, 16:57   #16
LoRd_MuldeR
Major Dude
 
LoRd_MuldeR's Avatar
 
Join Date: Sep 2005
Location: Somewhere over the Slaughterhouse
Posts: 797
Quote:
Originally Posted by DrO View Post
you can use #define IPC_IS_COMPATIBILITY_ENABLED 3051 and then do a bitmask compare against 0x1 - that will return 1 if it's enabled on 6.x clients or 1 for all 2.x/5.x clients. though you can otherwise just do a version check for anything above and including 0x5080 to achieve the same thing. but (IPC_IS_COMPATIBILITY_ENABLED & 0x1) is going to be more reliable in the long-term.
Thanks for the info.


Quote:
Originally Posted by DrO View Post
there already are messages for getting notified of that IPC_PLAYINGFILEW (and also the misc_cb versions) as what you've described is the same action i.e. starting a track however it has been started is still starting a track. so i don't think there's anything i need to add to what is already provided.
Okay, if IPC_PLAYINGFILEW reliably indicates track changes, it will suffice.


Quote:
Originally Posted by DrO View Post
yes it is
How do Echo/Reverb plug-ins and the like generally handle this case then? They just don't care?


Quote:
Originally Posted by DrO View Post
...and is why i asked at the start what you're trying to do as based on what you've said so far, it's a lot of work on my side for just one specific plug-in to use it.
Well, my core library requires a certain "look ahead" buffer. So, the library consumes N input samples and returns M output samples, in each call - where M can be smaller than N or even zero, until the internal buffer has been filled. From then on, always M == N. In other words, it takes a certain number of input samples until the first output becomes available, e.g. ~15 seconds. Just like a long FIFO buffer.

Now I'm building a Winamp DSP/Effect wrapper around the core library. Thankfully, Winamp's modify_samples() function is flexible enough, so that the number of input and output samples can differ. This way, the internal buffer of the core library can be filled without actually introducing a delay. And that works fine, e.g. when I start the very first track in Winamp. That's in contrast to VST, where each process() call always must return N output samples for N input samples and therefore a delay is unavoidable. Kudos to Winamp for being more flexible!

Only problem with Winamp is that, when a seeking operation (or track change) happens, it would take a long time until the new samples have propagated trough the internal buffer. So when the user seeks (or skips to another tracks), nothing seems to happen - the audio just continues to play. And then, after a few seconds, suddenly the "jump" happens. That's not very nice. So, the solution is to completely flush my internal buffer on each seeking operation (or track change). The core lib already has a reset() function for exactly that use case.

But that requires that the Winamp DSP plug-in actually has a way to detect these events...


Quote:
Originally Posted by DrO View Post
if anything, it'd probably be better to modify the DSP structure to allow for the wndproc to just be passed through so there's no need to do any hooking of things, thus making it easier to act as a dsp/gen hybrid as you seem to be trying to do.
Sounds good.

My Plugins: StdUtils | NSISList | CPUFeatures | ExecTimeout | KillProc
My source of inspiration: http://youtu.be/lCwY4_0W1YI
LoRd_MuldeR is offline   Reply With Quote
Reply
Go Back   Winamp & SHOUTcast Forums > Developer Center > Winamp Development

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump