Ever wanted to host a Winamp plug-in? No? Hmm... Ever wanted an exercise in good code writing practices? Not even sometimes? OK... How about this... Have you ever wanted to write a coin-op juke box app, based on Winamp, that could provide the control over its plug-ins that only Winamp can? I did, and this article is the result.
This article will not tell you how to make a plug-in. There's enough information floating around in other places which would make this article redundant. Instead, this article will show you how to properly host a Winamp output plug-in. As far as the plug-in is concerned, your app is Winamp.
This article is part of a possible series on the Winamp plug-ins and system.
Winamp, a freeware program developed by Nullsoft, is a program that was originally written to play music files. It originally had a unique user-interface, and became widely known when some tech-savvy users decided to toy around with the resources in the program. This led to an underground community of people who would write 'skins' for Winamp. Nullsoft embraced this community, and made skinning easier in future releases.
Nullsoft also developed a plug-in system that allowed third-party developers to add functionality to Winamp, such as adding file format support, visualizations, DSP support, as well as a multitude of other types of functionality.
Output plug-ins allow the audio to be routed to different locations. The output plug-ins included with Winamp allow the audio to be routed to your soundcard through the wave mapper, through DirectSound (to allow music when you're playing a game), or to a wave file.
Using the code
To use the code, add a reference to the Winamp Plugin Host. Create an instance of the plug-in like so:
Dim Plugin As New OutputPlugin("full path to plugin.dll")
I'm not going to get into the nuts and bolts of the Winamp SDK at this time, but suffice it to say, the only thing this plug-in host does for you automatically is pass the
hDLLInstance to the plug-in. Everything else is up to you.
To properly initialize the plug-in, you should call these methods:
Plugin.MainWindow = Me
Opening the device
The best analogy to an output plug-in is a hardware device or a file. To do anything, you first open the device.
Dim LatencyMS As Integer = Plugin.Open(44100, 2, 16, 100, 0)
Writing data to the device
To get actual sound, you write to the device. The plug-in expects raw, uncompressed, sound data, and will play it with the parameters specified when you opened the device.
Dim data As Byte()
Closing the device
When you are no longer expecting to write data to the device, close it. Some plug-ins have exclusive access to the device (e.g., the Wave mapper plug-in). If such a plug-in is open, no other application can have access to the device. This may result in sounds from other applications not being heard. Close the device to prevent this from happening:
Some plug-ins may have settings that may need to be saved, or file system transactions (it could happen...) that need to be finalized. Before you exit the program, call:
Points of interest
Plug-in systems, in general, and Winamp plug-ins, in particular, have always fascinated me. I've spent a lot of time trying to design new systems and implement older ones. While I have yet to actually write a Winamp plug-in, I do plan to at some point. The one thing I may run into is that while VB.NET can import functions from a DLL file (using
LoadLibrary, no less), it can't yet export them that I know of.
One particular hoop I had to jump through to get this to work is that the
winampGetOutModule function exported by the plug-ins returns a pointer to the
OutModule structure. Attempting to attach a delegate to a function that returns a pointer was an interesting experience, to say the least. The exception that I was getting was non-specific, and actually had me thinking that this couldn't be done. If you're familiar with the exception
"Method's type signature is not PInvoke compatible.", you'll understand why. Somebody on VBForums mentioned a way to dynamically bind an imported function at runtime that used
DLLImportAttribute, and said that should help.
When I saw the article, it seemed overly complicated for what I was trying to do, so instead I rolled my own. The method used was to write a piece of template code and replace the DLL filename in it at runtime. The code would then be compiled and called. When I wrote that, it worked, that is, until it tried to call the DLL. It simply gave the same exception. This time, though, I had to write a few dozen lines of code to read an array of errors to find that out.
I solved the problem by having the imported function declared 'As
Integer' instead of 'As
OutModule'. Then, as can be seen in the code, I used
PtrToStructure to get the structure from the pointer. It was then that I realized that I no longer needed to compile a template and all that other stuff. In eight hours (on and off), I managed to write, then delete about 300 lines of code.
- Sunday, Dec 3, 2006: Article submitted.
When I add more Winamp related articles, I will add links to them here.