WinSPC Source
Quick Guide


Introduction

I'm writing this so, to make it easier for those who wish to learn and experiment Snes9x's altered source code to create an SPC player.


Code seperation

I've tried to seperate code as best as I could but this is how it seperates to :

Code Modules
Windows specific Main WinSPC.* (Application)
Resource.h (app resources)
GUI DlgWinSPC.* (Main Dialog)
DlgOptions.* (Options Dialog)
DlgSettings.* (Settings Dialog)
Sound Device port.* (most code)
Portable Emulation port.* (loops, timer)
apu.* (DSP)
apumem.h (Memory Map)
global.cpp (Global Variables)
spc700.* (SPC700)
Mixing soundux.*

As you can see it seperates alright, with the exception of port.* which is more of a bridge between windows sound device and emulation.

How WinSPC uses
Snes9x's SPC code

Lets see where to begin. Well, the main concern is port.*, as it is the one that uses both windows and snes9x's SPC code, so I'll start with it.
The first thing to understand is how sound works in windows (in this case not DirectSound but WinMultiMedias wave devices), In windows a program starts by finding the audio device we are going to use (this can be found in port.cpp in the function OpenSoundDevice), a call to waveOutOpen, with the structures filled in according to the sound device you want to open, they give you 3 options in how to handle playing audio with your own mixer 1) Use messages to tell you when you have to mix another buffer, 2) Use a loop to poll the buffers to see if one becomes available (this can be done from the main process or a seperate thread), 3) use a callback, in my case I have used a callback which gets called everytime a buffer needs to be filled, I chose this because 1) messages would be lagged, 2) Modal Dialog boxes in MFC are easier to create but do not have an ability to have a loop and a thread is more useful in a multiprocessor system, 3) a direct call to my function would be much easier to implement and faster.

Now the callback function waveOutProcSPC, is where the emulation and mixing will occur, it gets called every time a buffer needs to be refilled.

Playing a file would require prefilling the buffers and then sending each of them to the WaveOut device, by calling waveOutPrepareHeader to prepare them and waveOutWrite to send them.

Pausing is done by setting a global variable, and there fore signalling your callback to stop mixing and calling waveOutReset is supposed to do the same thing, but might not work with every system, so the variable is more important.

Stoping is exactly the same except you reset what ever you are playing back to the start.

A few last notes about Windows audio. Windows audio is slow, the maximum buffers you will get out of it is 4 buffers a second, the catch is that emulation for the SPC requires at least mixing 100 times a second to sound correctly for the majority of the spcs out there. I did a little work around for this, very simply, I just do a loop where I emulate for 1/100th of a second and then mix a section of the buffer, I do this until the buffer is full, with 4 buffers a second and needing to mix 100 times a second that is mixing 25 times in each buffer.
Now that we have covered the more windows specific part, its now to move on to the more emulation specific part.

The first place to start is in loadin the SPC files. I do this by first loading the SPC data into backup variables, I then call a function RestoreSPC, which copies the backup varibles into the actual variable that will be used in emulation, and calls a list of other functions to prepare for emulation, so emulation is corrected for the SPC. The data consists of SPC Registers, SPC RAM, DSP RAM, and ExtraRAM. (Examine LoadSPC)

Next thing to do is to Open the audio device, and setup the mixer variables and buffer mixing variables. Opening the audio device is windows specific and is pretty much covered already. The mixer variables are stored in a structure SoundStatus, which is the global variable so, it has stored in it the audio settings. the buffer mixing variables are bpb (bytes per buffer), bpm (bytes per mix), spm (samples per mix). The reason that these variables must be set are because they coincide with the audio device and need to know how mix the audio for it. (Examine OpenSoundDevice)

Next we will want to play audio but we first need to under stand how to mix it, since windows requires we send the buffers at a constant rate. So for mixing lets examine the code :
    // Buffer Mixing loop, loops until the buffer is filled
    // which is specified by MPB (Mixes per Buffer)
    for(i=0;i<MPB;i++){
        // Outer Emulation Loop, loops for every 32 cycles until
        // 1/100th of a second of emulation has occured
        for(int c=0;c<C32PM;c++){
            // Inner Emulation Loop of 32 cycles
            for(int ic=0;ic<32;ic++){
                // Tell the SPC code to emulate one cycle
                APU_EXECUTE1();
            }
            // Does the timer code
            IAPU.TimerErrorCounter++;
            DoTimer();
        }
        // Tell the SPC code to mix 100th of the buffer
        S9xMixSamples((unsigned char *)(&wh[b].lpData[i*bpm]),spm);
    }
OK, that is how mixing is done for WinSPC. When starting to play you do this for each buffer and then send the buffer to the audio device. (Examine PlaySPC)

I'll take this moment to explain how to alter this loop to use in something like a DOS emulator/player. In dos you would do emulation in the main loop, so you would just have a plain loop with out any thing related to mixing, and the main loop would be checking the keyboard (for example) to see if the user wants to exit, here is an example :
    while(!Key[SCAN_ESCAPE]){
        for(int ic=0;ic<32;ic++){
            APU_EXECUTE1();
        }
        IAPU.TimerErrorCounter++;
        DoTimer();
    }
Now in DOS mixing would be done durring a hardware interrupt (similar to the windows callback) in which a buffer is to be mixed again, so you simply call S9xMixSamples, with enough to mix the number of buffers a second you are set up for.

That is pretty much as far as I feel like going, I am tired and sleepy, as I write this so its best if I leave it as is.