Obviously, Winamp itself does not have facility for interfacing to the user interface I designed, so some software of my own has to work in between. There is two obvious means of implementing this; a Winamp plugin, or a stand-alone application.
Winamp plugins are usually just DLL's which are loaded and run by Winamp, (usually written in C, but not necessarily) The plugin option is less attractive as I don't know much about writing DLL's and the Winamp Software Development Kit is not written with Visual Basic in mind. So I took the easier way out and developed a simple application which works with the hardware interface and tells Winamp what to do.
Below is a block diagram, which may best illustrate the purpose of the host software and how it works.
The maroon/brown arrow represents the serial port control lines which the host software monitors for button presses. These data lines are tied to the RTS line, so negating RTS from within the software will disable detection of button presses because pressing the buttons will no longer cause a transition from negative to positive.
The blue/navy arrow represents a "ping" from the host software, which occurs every 100ms. It consists of a small (five byte) data packet sent serially to the PIC16F628 containing updated data on the display and system status. Most of this data is forwarded directly to the displays as it is already correctly formatted by the host software. (it's quicker and easier to perform processing and calculations in Visual Basic than it is in PIC assembly language....)
The green arrow is a three byte response to the ping which allows the host software to determine the firmware version, hardware status and receive any input from the rotary encoder.
The hardware can be directly reset by pulsing the DTR line, as described on the Hardware Interface page. This is the orange arrow.
Finally, the host software queues messages for Winamp which request the required action when a button is pressed on the hardware itself. This is illustrated by the teal arrow.
The protocol used to keep hardware and software in sync with each other is actually quite simple. A simple protocol for a simple task, usually not, but one can hope....
Anyway, the host software transmits a five byte packet to the hardware containing the information which is to be shown on the 7-segment displays and indicator LED's. The hardware waits until it has received all five bytes and a valid checksum before processing the data and displaying it. A single data packet is shown in detail below. (a packet is transmitted once every 100ms)
The hardware interface adds up bytes 1 to 5, complements the result and discards the packet if it doesn't match byte 6. This is a simple method of ensuring the data was received as it was transmitted, which can almost be taken for granted. If the checksum is valid then the first three bytes are copied to DisplayH, DisplayL and DisplayM respectively, updating the display in the process. DisplayH and DisplayL are hexadecimal representations of the display output, (eg. if DisplayH = 19h and DisplayL = 86h, then the 7-segment display will read "1986") and DisplayM contains decimal point data as a bit mask in the lower nibble. (If DisplayM = 04h then the display would actually show "19.86")
Processing bytes 4 and 5 is a little more complicated. "DevStatus" is an 8-bit register in the PIC16F628 which contains control and status bits for the operation of the interface. Only the lower four bits are used in at the moment:
At first I tried just having the hardware and software exchange DevStatus each time a data packet was sent, thus keeping up to date, but there is a problem with this approach. Because the host software transmits first, what it thinks should be in DevStatus overrides what the hardware decides should be in DevStatus resulting in no changes to DevStatus unless they are initiated by the host software. If the user presses the mode button on the hardware interface, the mode will change for a few tens of milliseconds before it is set back to what the host software thinks it is.
My solution was to transmit a second byte which contains flags for each bit of DevStatus which indicates that the information represented by that bit has just been changed by the host software and needs to be updated. So the hardware goes through byte five and sets the matching bit in DevStatus only if the corresponding bit in byte four is set. This arrangement means that DevStatus is only overwritten in the hardware when it needs to be and that changes to DevStatus can also be initiated from the hardware end.
After processing the data packet from the host software, the hardware transmits a three byte reply consisting of DevStatus, a byte indicating any changes in the position of the dial, (rotary encoder) and the firmware version. This is illustrated below.
Byte 1 is DevStatus as the hardware sees it, which tells the host software what mode the system is in (vital for decoding button presses) and how to report the display state. Byte two indicates the direction and magnitude of any input with the rotary encoder, which is not actually processed by the host software yet as the firmware code which reads the encoder isn't very good at detecting clockwise rotation. (I'm working on this and will update this page when things have changed) Byte 3 is an encoded firmware revision which can represent versions from 1.00 to 3.55. As per the formula above, 0 (00h) will represent version 1.00 and 255 (FFh) will represent 3.55.
This section describes the core functions of the host software, hopefully providing an insight into how the program functions as a whole. You may find it necessary to refer to aspects of the source code itself as I haven't reproduced much of it here. (the source code can be found in the Downloads section below) If you don't need to know anything about the workings of the host software and just want to use it, click here to skip this section.
The host software itself is a relatively simple Visual Basic application which runs minimised on the system tray. The control panel, (which you configure how you want and then leave it alone) is pictured here.
This source code for this software is largely without commenting, so I will attempt to explain the basic workings on this page. I'm also happy to answer questions if need be. (contact details)
Most of what the software does occurs in a the timer event for the IdleTimer control, which occurs every 100ms. The code which executes when this timer overflows can be found in the sub routine IdleTimer_Timer. This routine performs three tasks; it gets the the window handle for Winamp, (which also determines whether Winamp is running or not) assembles and transmits a data packet as described above and reads the response.
Finding the hInstance (Window handle) for Winamp is done with the window class name rather than the window name or caption. This is because the caption changes when a song is playing, as you will notice if you have Winamp on your toolbar or system tray. The host software uses the Win32 API function FindWindow to retrieve the hInstance for Winamp with the following code.
It took almost half a day to find out the Winamp window class, which is "Winamp v1.x" for all versions of Winamp 1.xx and 2.xx.
Although the Visual Basic documentation recommends against storing hInstances in variables, (see hWnd Property in the Visual Basic help files) this is generally not a problem as the variable is updated ten times a second and Winamp's hInstance should not change unless the program is restarted.
The IdleTimer_Timer sub then transmits a data packet as described above.
Simple? Not really. The entire data packet is constructed by the function AssemblePacket() which returns a string representing a data packet based on the settings saved from the control panel. The AssemblePacket() function is not exactly the simplest part of the program and won't be posted in full here. Below is a small piece of the AssemblePacket()
function which queries Winamp for the track time and converts the response (milliseconds) to a four character string (mmss) which is then condensed to the two byte hex encoded DisplayH and DisplayL part of the data packet. (this last step is not shown in the code below)
The important part of this code is the SendMessage function. This function is part of the Windows API and is used by one application to queue messages for another application referenced by the window handle or hInstance. Messages are the basis of C/C++ applications running on Windows, which usually consist of a WinMain() function which runs a loop retrieving messages from the program's message queue. (Messages are posted for the program for almost all interactions, including user actions like clicking the maximise button) A message consists of an identifier, (16 bits, I think) wParam (16 bit argument) and lParam. (32 bit argument) SendMessage queues a message with these arguments and returns the target program's response as a Long number.
Nullsoft were kind enough to implement a set of messages which could be used to externally control Winamp, often referred to as IPC functions. (this message library may actually be for plugins, I'm not really an expert in this field) The above code uses the IPC_GETOUTPUTTIME message to retrieve the current position from within the playing track in milliseconds. If nothing is playing the return value is -1, hence the conditional statement converting -1 to 0.
Given that the AssemblePacket() function creates a data packet as described in the Communications Protocol section above, any competent Visual Basic programmer should be able to nut out the code itself.
Finally, the IdleTimer_Timer sub routine reads the response from the hardware. See the end of the IdleTimer_Timer sub routine for details on this. (it's quite simple)
Moving on to what the host software does when it receives user input from the hardware interface. Because the push-buttons on the interface are wired to the COM port control lines CD, DSR, CTS and RI (Carrier Detect, Data Set Ready, Clear To Send and Ring Indicator) the automatically trigger the MSComm_OnComm event. (search the web for "MSComm Control" for details) The host software simply checks the CommEvent property to find out which button was pressed. A simplified version of this code is below.
The actions tied to each button are determined and executed by the DecodeButton(BtnVar As Integer) function, where BtnVar is action index for the button pressed in the current mode. This argument is usually a configuration variable, this being where the action index for each button is stored. The DecodeButton(BtnVar As Integer) function queues messages for Winamp where required. (see the source code itself for more details)
The two statements above and below each decode sequence implement basic debouncing for each button by enabling a 500ms timer (tweak this interval for your own push buttons) and aborting the decode sequence if the timer is still running. When the DebounceTimer_Timer event occurs, the debounce timer is obviously disabled by setting the Enabled property to false. The DebounceTimer_Timer routine also calculates the auto-repeat interval from the slider on the control panel, (see above image) and enabling the auto-repeat timer. Auto-repeat is only possible for the buttons wired to the CD, DSR and CTS lines because these lines can be checked for release via the CDHolding, DSRHolding and CTSHolding properties of the MSComm control. No such property exists for the RI indicator line, so the button is seen to be pressed every time the UART detects a change on RI.
Last of all, I will briefly describe the process of saving and retrieving configuration data. When the program is launched, part of the startup routine is to call the sub LoadCFG() which opens "WinampHardware.ini" from the current directory and reads the information into a set of variables which can be accessed while the program runs. This set of variables is prefixed with "CFG_" for all settings contained within the Options frame, (see image above) or "MAP_" for all settings inside the Hardware Mapping frame. So the Winamp executable path is held in the string variable CFG_WinampPath, and the action index of button two in "alternate" mode will be stored in MAP_AlternateButton2. (this is also the ListIndex property of the drop down list on the control panel itself, beware of this when adding or removing items from this list)
Because Visual Basic sets the current directory to the Visual Basic executable folder while running in the interpreter, (development environment) one of the first things the host software does is to set the current directory to the folder where "WinampHardware.ini" resides. I do this with the following code, which sets the application variable ExePath which the LoadCFG() sub routine uses to open the configuration file.
Of course, when the program is compiled it will still look in this location rather than the current directory. The purpose of the commented line is to load ExePath with the current directory when the program is not running in the interpreter. This is a method I carried over from Visual Basic 3.0, which did not support some of the conditional statements which remove code from compiled versions.
Before compiling, but after saving, the code should be changed to this:
To save the configuration variables back into "WinampHardware.ini" the host software calls the SaveCFG() sub routine after copying the contents of the controls (list boxes, check boxes, sliders... etc) into the variables. This is what happens when the "Save Settings" button is pressed.
That's about it for the core functions of the program. There's much more to the software that discussed here, but there's no way I have the time to document the source code for this program in full. If you are trying to modify the software but can't understand some aspect of the source, please contact me with your problem.
Download the software with or without run-times:
Download - Program executable, sample configuration file and required run-times. (1066KB)
Download - Program executable and sample configuration file only, requires run-times below. (23KB)
The following run-times are required, if you don't have them already:
Download the source code for Visual Basic 4.0:
Download - Source code with sample configuration file (14KB)
If you have any comments or questions please don't hesitate to contact me.
|Return To Top||Last Updated: 12/02/2007||Home Page|