shithub: choc

Download patch

ref: e434c7c10278a47fc686024fae04d361a0282ab8
parent: b96d0e706d15778c264f8e824343b050ea4a72d6
author: Simon Howard <fraggle@gmail.com>
date: Wed Mar 7 14:08:27 EST 2007

Use native endianness for sound output, rather than always LSB. Add PC
speaker code!

Subversion-branch: /trunk/chocolate-doom
Subversion-revision: 844

diff: cannot open b/src/pcsound//null: file does not exist: 'b/src/pcsound//null'
--- a/configure.in
+++ b/configure.in
@@ -74,6 +74,7 @@
 setup/Makefile
 man/Makefile
 src/Makefile
+src/pcsound/Makefile
 src/chocolate-doom-res.rc
 setup/chocolate-setup-res.rc
 ])
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,6 +22,8 @@
 chocolate_server_SOURCES=$(DEDSERV_FILES)
 chocolate_server_LDADD = @LDFLAGS@ @SDL_LIBS@ @SDLNET_LIBS@ 
 
+SUBDIRS=pcsound
+
 SOURCE_FILES=\
 am_map.c             am_map.h              \
 deh_ammo.c                                 \
@@ -62,6 +64,7 @@
 i_main.c                                   \
 info.c               info.h                \
 i_scale.c            i_scale.h             \
+i_pcsound.c          i_pcsound.h           \
 i_sound.c            i_sound.h             \
 i_system.c           i_system.h            \
 i_timer.c            i_timer.h             \
@@ -138,7 +141,7 @@
 chocolate_doom_SOURCES=$(SOURCE_FILES)
 endif
 
-chocolate_doom_LDADD = ../textscreen/libtextscreen.a @LDFLAGS@ @SDL_LIBS@ @SDLMIXER_LIBS@ @SDLNET_LIBS@ 
+chocolate_doom_LDADD = ../textscreen/libtextscreen.a pcsound/libpcsound.a @LDFLAGS@ @SDL_LIBS@ @SDLMIXER_LIBS@ @SDLNET_LIBS@ 
 
 EXTRA_DIST =                               \
         chocolate_doom_icon.c              \
--- /dev/null
+++ b/src/i_pcsound.c
@@ -1,0 +1,238 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2007 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// DESCRIPTION:
+//	System interface for PC speaker sound.
+//
+//-----------------------------------------------------------------------------
+
+#include "SDL.h"
+
+#include "doomdef.h"
+#include "doomtype.h"
+
+#include "i_pcsound.h"
+#include "i_sound.h"
+#include "sounds.h"
+
+#include "w_wad.h"
+#include "z_zone.h"
+
+#include "pcsound/pcsound.h"
+
+static boolean pcs_initialised = false;
+
+static SDL_mutex *sound_lock;
+
+static uint8_t *current_sound_lump = NULL;
+static uint8_t *current_sound_pos = NULL;
+static unsigned int current_sound_remaining = 0;
+static int current_sound_handle = 0;
+
+static float frequencies[] = {
+    0, 175.00, 180.02, 185.01, 190.02, 196.02, 202.02, 208.01, 214.02, 220.02,
+    226.02, 233.04, 240.02, 247.03, 254.03, 262.00, 269.03, 277.03, 285.04,
+    294.03, 302.07, 311.04, 320.05, 330.06, 339.06, 349.08, 359.06, 370.09,
+    381.08, 392.10, 403.10, 415.01, 427.05, 440.12, 453.16, 466.08, 480.15,
+    494.07, 508.16, 523.09, 539.16, 554.19, 571.17, 587.19, 604.14, 622.09,
+    640.11, 659.21, 679.10, 698.17, 719.21, 740.18, 762.41, 784.47, 807.29,
+    831.48, 855.32, 880.57, 906.67, 932.17, 960.69, 988.55, 1017.20, 1046.64,
+    1077.85, 1109.93, 1141.79, 1175.54, 1210.12, 1244.19, 1281.61, 1318.43,
+    1357.42, 1397.16, 1439.30, 1480.37, 1523.85, 1569.97, 1614.58, 1661.81,
+    1711.87, 1762.45, 1813.34, 1864.34, 1921.38, 1975.46, 2036.14, 2093.29,
+    2157.64, 2217.80, 2285.78, 2353.41, 2420.24, 2490.98, 2565.97, 2639.77,
+};
+
+#define NUM_FREQUENCIES (sizeof(frequencies) / sizeof(*frequencies))
+
+void PCSCallbackFunc(int *duration, int *freq)
+{
+    int tone;
+
+    *duration = 1000 / 140;
+
+    if (SDL_LockMutex(sound_lock) < 0)
+    {
+        *freq = 0;
+        return;
+    }
+    
+    if (current_sound_lump != NULL && current_sound_remaining > 0)
+    {
+        // Read the next tone
+
+        tone = *current_sound_pos;
+
+        // Use the tone -> frequency lookup table.  See pcspkr10.zip
+        // for a full discussion of this.
+        // Check we don't overflow the frequency table.
+
+        if (tone < NUM_FREQUENCIES)
+        {
+            *freq = (int) frequencies[tone];
+        }
+        else
+        {
+            *freq = 0;
+        }
+
+        ++current_sound_pos;
+        --current_sound_remaining;
+    }
+    else
+    {
+        *freq = 0;
+    }
+
+    SDL_UnlockMutex(sound_lock);
+}
+
+static boolean CachePCSLump(int sound_id)
+{
+    int lumplen;
+    int headerlen;
+
+    // Free the current sound lump back to the cache
+ 
+    if (current_sound_lump != NULL)
+    {
+        Z_ChangeTag(current_sound_lump, PU_CACHE);
+        current_sound_lump = NULL;
+    }
+
+    // Load from WAD
+
+    current_sound_lump = W_CacheLumpNum(S_sfx[sound_id].lumpnum, PU_STATIC);
+    lumplen = W_LumpLength(S_sfx[sound_id].lumpnum);
+
+    // Read header
+  
+    if (current_sound_lump[0] != 0x00 || current_sound_lump[1] != 0x00)
+    {
+        return false;
+    }
+
+    headerlen = (current_sound_lump[3] << 8) | current_sound_lump[2];
+
+    if (headerlen > lumplen - 4)
+    {
+        return false;
+    }
+
+    // Header checks out ok
+
+    current_sound_remaining = headerlen;
+    current_sound_pos = current_sound_lump + 4;
+
+    return true;
+}
+
+int I_PCS_StartSound(int id,
+                     int channel,
+                     int vol,
+                     int sep,
+                     int pitch,
+                     int priority)
+{
+    int result;
+
+    if (!pcs_initialised)
+    {
+        return -1;
+    }
+
+    // These PC speaker sounds are not played - this can be seen in the 
+    // Heretic source code, where there are remnants of this left over
+    // from Doom.
+
+    if (id == sfx_posact || id == sfx_bgact || id == sfx_dmact
+     || id == sfx_dmpain || id == sfx_popain || id == sfx_sawidl)
+    {
+        return -1;
+    }
+
+    if (SDL_LockMutex(sound_lock) < 0)
+    {
+        return -1;
+    }
+
+    result = CachePCSLump(id);
+
+    if (result)
+    {
+        current_sound_handle = channel;
+    }
+
+    SDL_UnlockMutex(sound_lock);
+
+    if (result)
+    {
+        return channel;
+    }
+    else
+    {
+        return -1;
+    }
+}
+
+void I_PCS_StopSound(int handle)
+{
+    if (!pcs_initialised)
+    {
+        return;
+    }
+
+    if (SDL_LockMutex(sound_lock) < 0)
+    {
+        return;
+    }
+
+    // If this is the channel currently playing, immediately end it.
+
+    if (current_sound_handle == handle)
+    {
+        current_sound_remaining = 0;
+    }
+    
+    SDL_UnlockMutex(sound_lock);
+}
+
+int I_PCS_SoundIsPlaying(int handle)
+{
+    if (!pcs_initialised)
+    {
+        return false;
+    }
+
+    if (handle != current_sound_handle)
+    {
+        return false;
+    }
+
+    return current_sound_lump != NULL && current_sound_remaining > 0;
+}
+
+void I_PCS_InitSound(void)
+{
+    pcs_initialised = PCSound_Init(PCSCallbackFunc);
+
+    sound_lock = SDL_CreateMutex();
+}
+
--- /dev/null
+++ b/src/i_pcsound.h
@@ -1,0 +1,40 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2007 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// DESCRIPTION:
+//	System interface for PC speaker sound.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef __I_PCSOUND_H__
+#define __I_PCSOUND_H__
+
+int I_PCS_StartSound(int id,
+                     int channel,
+                     int vol,
+                     int sep,
+                     int pitch,
+                     int priority);
+void I_PCS_StopSound(int handle);
+int I_PCS_SoundIsPlaying(int handle);
+void I_PCS_InitSound(void);
+
+#endif /* #ifndef __I_PCSOUND_H__ */
+
--- a/src/i_sound.c
+++ b/src/i_sound.c
@@ -39,6 +39,7 @@
 #include "z_zone.h"
 
 #include "i_system.h"
+#include "i_pcsound.h"
 #include "i_sound.h"
 #include "deh_main.h"
 #include "m_argv.h"
@@ -122,7 +123,7 @@
 static void ExpandSoundData(byte *data, int samplerate, int length,
                             Mix_Chunk *destination)
 {
-    byte *expanded = (byte *) destination->abuf;
+    Sint16 *expanded = (Sint16 *) destination->abuf;
     int expanded_length;
     int expand_ratio;
     int i;
@@ -135,15 +136,13 @@
 
         for (i=0; i<length; ++i)
         {
-            Uint16 sample;
+            Sint16 sample;
 
             sample = data[i] | (data[i] << 8);
             sample -= 32768;
 
-            expanded[i * 8] = expanded[i * 8 + 2]
-              = expanded[i * 8 + 4] = expanded[i * 8 + 6] = sample & 0xff;
-            expanded[i * 8 + 1] = expanded[i * 8 + 3]
-              = expanded[i * 8 + 5] = expanded[i * 8 + 7] = (sample >> 8) & 0xff;
+            expanded[i * 4] = expanded[i * 4 + 1]
+                = expanded[i * 4 + 2] = expanded[i * 4 + 3] = sample;
         }
     }
     else if (samplerate == 22050)
@@ -150,13 +149,12 @@
     {
         for (i=0; i<length; ++i)
         {
-            Uint16 sample;
+            Sint16 sample;
 
             sample = data[i] | (data[i] << 8);
             sample -= 32768;
 
-            expanded[i * 4] = expanded[i * 4 + 2] = sample & 0xff;
-            expanded[i * 4 + 1] = expanded[i * 4 + 3] = (sample >> 8) & 0xff;
+            expanded[i * 2] = expanded[i * 2 + 1] = sample;
         }
     }
     else
@@ -170,7 +168,7 @@
 
         for (i=0; i<expanded_length; ++i)
         {
-            Uint16 sample;
+            Sint16 sample;
             int src;
 
             src = (i * expand_ratio) >> 8;
@@ -180,8 +178,7 @@
 
             // expand 8->16 bits, mono->stereo
 
-            expanded[i * 4] = expanded[i * 4 + 2] = sample & 0xff;
-            expanded[i * 4 + 1] = expanded[i * 4 + 3] = (sample >> 8) & 0xff;
+            expanded[i * 2] = expanded[i * 2 + 1] = sample;
         }
     }
 }
@@ -200,7 +197,7 @@
 
     // need to load the sound
 
-    lumpnum = I_GetSfxLumpNum(&S_sfx[sound]);
+    lumpnum = S_sfx[sound].lumpnum;
     data = W_CacheLumpNum(lumpnum, PU_STATIC);
     lumplen = W_LumpLength(lumpnum);
 
@@ -213,7 +210,7 @@
 
         return false;
     }
-    
+
     // 16 bit sample rate field, 32 bit length field
 
     samplerate = (data[3] << 8) | data[2];
@@ -288,9 +285,24 @@
 int I_GetSfxLumpNum(sfxinfo_t* sfx)
 {
     char namebuf[9];
-    sprintf(namebuf, "ds%s", DEH_String(sfx->name));
+    char *prefix;
+
+    // Different prefix for PC speaker sound effects.
+
+    if (snd_sfxdevice == SNDDEVICE_PCSPEAKER)
+    {
+        prefix = "dp";
+    }
+    else
+    {
+        prefix = "ds";
+    }
+
+    sprintf(namebuf, "%s%s", prefix, DEH_String(sfx->name));
+    
     return W_GetNumForName(namebuf);
 }
+
 //
 // Starting a sound means adding it
 //  to the current list of active sounds
@@ -317,6 +329,11 @@
     if (!sound_initialised)
         return 0;
 
+    if (snd_sfxdevice == SNDDEVICE_PCSPEAKER)
+    {
+        return I_PCS_StartSound(id, channel, vol, sep, pitch, priority);
+    }
+
     // Release a sound effect if there is already one playing
     // on this channel
 
@@ -349,6 +366,12 @@
     if (!sound_initialised)
         return;
 
+    if (snd_sfxdevice == SNDDEVICE_PCSPEAKER)
+    {
+        I_PCS_StopSound(handle);
+        return;
+    }
+    
     Mix_HaltChannel(handle);
 
     // Sound data is no longer needed; release the
@@ -366,7 +389,14 @@
     if (handle < 0)
         return false;
 
-    return Mix_Playing(handle);
+    if (snd_sfxdevice == SNDDEVICE_PCSPEAKER)
+    {
+        return I_PCS_SoundIsPlaying(handle);
+    }
+    else
+    {
+        return Mix_Playing(handle);
+    }
 }
 
 
@@ -383,6 +413,11 @@
     if (!sound_initialised)
         return;
 
+    if (snd_sfxdevice == SNDDEVICE_PCSPEAKER)
+    {
+        return;
+    }
+
     // Check all channels to see if a sound has finished
 
     for (i=0; i<NUM_CHANNELS; ++i)
@@ -425,6 +460,11 @@
     if (!sound_initialised)
         return;
 
+    if (snd_sfxdevice == SNDDEVICE_PCSPEAKER)
+    {
+        return;
+    }
+
     left = ((254 - sep) * vol) / 127;
     right = ((sep) * vol) / 127;
 
@@ -472,7 +512,7 @@
     {
         nomusicparm = true;
     }
-
+ 
     //!
     // Disable sound effects.
     //
@@ -479,11 +519,27 @@
 
     nosfxparm = M_CheckParm("-nosfx") > 0;
 
-    if (snd_sfxdevice < SNDDEVICE_SB)
+    // If the SFX device is 0 (none), then disable sound effects,
+    // just like if we specified -nosfx.  However, we still continue
+    // with initialising digital sound output even if we are using
+    // the PC speaker, because we might be using the SDL PC speaker
+    // emulation.
+
+    if (snd_sfxdevice == SNDDEVICE_NONE)
     {
         nosfxparm = true;
     }
 
+    //!
+    // Disable sound effects and music.
+    //
+
+    if (M_CheckParm("-nosound") > 0)
+    {
+        nosfxparm = true;
+        nomusicparm = true;
+    }
+
     // When trying to run with music enabled on OSX, display
     // a warning message.
 
@@ -498,16 +554,6 @@
     }
 #endif
 
-    //!
-    // Disable sound effects and music.
-    //
-
-    if (M_CheckParm("-nosound") > 0)
-    {
-        nosfxparm = true;
-        nomusicparm = true;
-    }
-
     // If music or sound is going to play, we need to at least
     // initialise SDL
     // No sound in screensaver mode.
@@ -521,7 +567,7 @@
         return;
     }
 
-    if (Mix_OpenAudio(22050, AUDIO_S16LSB, 2, 1024) < 0)
+    if (Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 1024) < 0)
     {
         fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError());
         return;
@@ -530,6 +576,13 @@
     Mix_AllocateChannels(NUM_CHANNELS);
     
     SDL_PauseAudio(0);
+
+    // If we are using the PC speaker, we now need to initialise it.
+
+    if (snd_sfxdevice == SNDDEVICE_PCSPEAKER)
+    {
+        I_PCS_InitSound();
+    }
 
     if (!nomusicparm)
         music_initialised = true;
--- /dev/null
+++ b/src/pcsound/Makefile.am
@@ -1,0 +1,10 @@
+
+AM_CFLAGS= @SDL_CFLAGS@ @SDLMIXER_CFLAGS@
+
+noinst_LIBRARIES=libpcsound.a
+
+libpcsound_a_SOURCES =                            \
+        pcsound.c           pcsound.h             \
+        pcsound_sdl.c                             \
+        pcsound_win32.c     
+
--- /dev/null
+++ b/src/pcsound/pcsound.c
@@ -1,0 +1,114 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2007 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// DESCRIPTION:
+//    PC speaker interface.
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pcsound.h"
+
+#ifdef _WIN32
+extern pcsound_driver_t pcsound_win32_driver;
+#endif
+extern pcsound_driver_t pcsound_sdl_driver;
+
+static pcsound_driver_t *drivers[] = 
+{
+#ifdef _WIN32
+    &pcsound_win32_driver,
+#endif
+    &pcsound_sdl_driver,
+    NULL,
+};
+
+static pcsound_driver_t *pcsound_driver = NULL;
+
+int PCSound_Init(pcsound_callback_func callback_func)
+{
+    char *driver_name;
+    int i;
+
+    if (pcsound_driver != NULL)
+    {
+        return 1;
+    }
+
+    // Check if the environment variable is set
+
+    driver_name = getenv("PCSOUND_DRIVER");
+
+    if (driver_name != NULL)
+    {
+        for (i=0; drivers[i] != NULL; ++i)
+        {
+            if (!strcasecmp(drivers[i]->name, driver_name))
+            {
+                // Found the driver!
+
+                if (drivers[i]->init_func(callback_func))
+                {
+                    pcsound_driver = drivers[i];
+                }
+                else
+                {
+                    printf("Failed to initialise PC sound driver: %s\n",
+                           drivers[i]->name);
+                    break;
+                }
+            }
+        }
+    }
+    else
+    {
+        // Try all drivers until we find a working one
+
+        for (i=0; drivers[i] != NULL; ++i)
+        {
+            if (drivers[i]->init_func(callback_func)) 
+            {
+                pcsound_driver = drivers[i];
+                break;
+            }
+        }
+    }
+    
+    if (pcsound_driver != NULL)
+    {
+        printf("Using PC sound driver: %s\n", pcsound_driver->name);
+        return 1;
+    }
+    else
+    {
+        printf("Failed to find a working PC sound driver.\n");
+        return 0;
+    }
+}
+
+void PCSound_Shutdown(void)
+{
+    pcsound_driver->shutdown_func();
+    pcsound_driver = NULL;
+}
+
--- /dev/null
+++ b/src/pcsound/pcsound.h
@@ -1,0 +1,45 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2007 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// DESCRIPTION:
+//    PC speaker interface.
+//
+//-----------------------------------------------------------------------------
+
+#ifndef PCSOUND_H
+#define PCSOUND_H
+
+typedef struct pcsound_driver_s pcsound_driver_t;
+typedef void (*pcsound_callback_func)(int *duration, int *frequency);
+typedef int (*pcsound_init_func)(pcsound_callback_func callback);
+typedef void (*pcsound_shutdown_func)(void);
+
+struct pcsound_driver_s
+{
+    char *name;
+    pcsound_init_func init_func;
+    pcsound_shutdown_func shutdown_func;
+};
+
+int PCSound_Init(pcsound_callback_func callback_func);
+void PCSound_Shutdown(void);
+
+#endif /* #ifndef PCSOUND_H */
+
--- /dev/null
+++ b/src/pcsound/pcsound_sdl.c
@@ -1,0 +1,179 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2007 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// DESCRIPTION:
+//    PC speaker interface.
+//
+//-----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "SDL.h"
+#include "SDL_mixer.h"
+
+#include "pcsound.h"
+
+#define SQUARE_WAVE_AMP 0x2000
+
+static pcsound_callback_func callback;
+
+// Output sound format
+
+static int mixing_freq;
+static Uint16 mixing_format;
+static int mixing_channels;
+
+// Currently playing sound
+// current_remaining is the number of remaining samples that must be played
+// before we invoke the callback to get the next frequency.
+
+static int current_remaining;
+static int current_freq;
+
+static int phase_offset = 0;
+
+// Mixer function that does the PC speaker emulation
+
+static void PCSound_Mix_Callback(void *udata, Uint8 *stream, int len)
+{
+    Sint16 *leftptr;
+    Sint16 *rightptr;
+    Sint16 this_value;
+    int oldfreq;
+    int i;
+    int nsamples;
+
+    // Number of samples is quadrupled, because of 16-bit and stereo
+
+    nsamples = len / 4;
+
+    leftptr = (Sint16 *) stream;
+    rightptr = ((Sint16 *) stream) + 1;
+    
+    // Fill the output buffer
+
+    for (i=0; i<nsamples; ++i)
+    {
+        // Has this sound expired? If so, invoke the callback to get 
+        // the next frequency.
+
+        while (current_remaining == 0) 
+        {
+            oldfreq = current_freq;
+
+            // Get the next frequency to play
+
+            callback(&current_remaining, &current_freq);
+
+            if (current_freq != 0)
+            {
+                // Adjust phase to match to the new frequency.
+                // This gives us a smooth transition between different tones,
+                // with no impulse changes.
+
+                phase_offset = (phase_offset * oldfreq) / current_freq;
+            }
+
+            current_remaining = (current_remaining * mixing_freq) / 1000;
+        }
+
+        // Set the value for this sample.
+        
+        if (current_freq == 0)
+        {
+            // Silence
+
+            this_value = 0;
+        }
+        else 
+        {
+            int frac;
+
+            // Determine whether we are at a peak or trough in the current
+            // sound.  Multiply by 2 so that frac % 2 will give 0 or 1 
+            // depending on whether we are at a peak or trough.
+
+            frac = (phase_offset * current_freq * 2) / mixing_freq;
+
+            if ((frac % 2) == 0) 
+            {
+                this_value = SQUARE_WAVE_AMP;
+            }
+            else
+            {
+                this_value = -SQUARE_WAVE_AMP;
+            }
+
+            ++phase_offset;
+        }
+
+        --current_remaining;
+
+        // Use the same value for the left and right channels.
+
+        *leftptr += this_value;
+        *rightptr += this_value;
+
+        leftptr += 2;
+        rightptr += 2;
+    }
+}
+
+static int PCSound_SDL_Init(pcsound_callback_func callback_func)
+{
+    // Check that SDL_mixer has been opened already
+    // If not, fail
+
+    if (!Mix_QuerySpec(&mixing_freq, &mixing_format, &mixing_channels))
+    {
+        return 0;
+    }
+
+    // Only supports AUDIO_S16SYS
+
+    if (mixing_format != AUDIO_S16SYS || mixing_channels != 2)
+    {
+        fprintf(stderr, 
+                "PCSound_SDL only supports native signed 16-bit LSB, "
+                "stereo format!\n");
+        return 0;
+    }
+
+    callback = callback_func;
+    current_freq = 0;
+    current_remaining = 0;
+
+    Mix_SetPostMix(PCSound_Mix_Callback, NULL);
+
+    return 1;
+}
+
+static void PCSound_SDL_Shutdown(void)
+{
+}
+
+pcsound_driver_t pcsound_sdl_driver = 
+{
+    "SDL",
+    PCSound_SDL_Init,
+    PCSound_SDL_Shutdown,
+};
+
--- /dev/null
+++ b/src/pcsound/pcsound_win32.c
@@ -1,0 +1,113 @@
+// Emacs style mode select   -*- C++ -*- 
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 2007 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+// DESCRIPTION:
+//    PC speaker interface.
+//
+//-----------------------------------------------------------------------------
+
+#ifdef _WIN32
+
+#include <SDL.h>
+#include <windows.h>
+
+#include "pcsound.h"
+
+static SDL_Thread *sound_thread_handle;
+static int sound_thread_running;
+static pcsound_callback_func callback;
+
+static void SoundThread(void *unused)
+{
+    int frequency;
+    int duration;
+    
+    while (sound_thread_running)
+    {
+        callback(&duration, &frequency);
+
+        if (frequency != 0) 
+        {
+            Beep(frequency, duration);
+        }
+        else
+        {
+            Sleep(duration);
+        }
+    }    
+}
+
+static int PCSound_Win32_Init(pcsound_callback_func callback_func)
+{
+    OSVERSIONINFO osvi;
+    BOOL result;
+
+    // Temporarily disabled - the Windows scheduler is strange and 
+    // stupid.
+   
+    return 0;
+
+    // Find the OS version
+
+    osvi.dwOSVersionInfoSize = sizeof(osvi);
+
+    result = GetVersionEx(&osvi);
+
+    if (!result)
+    {
+        return 0;
+    }
+
+    // Beep() ignores its arguments on win9x, so this driver will
+    // not work there.
+
+    if (osvi.dwPlatformId != VER_PLATFORM_WIN32_NT)
+    {
+        // TODO: Use _out() to write directly to the PC speaker on
+        // win9x: See PC/winsound.c in the Python standard library.
+
+        return 0;
+    }
+    
+    // Start a thread to play sound.
+
+    callback = callback_func;
+    sound_thread_running = 1;
+
+    sound_thread_handle = SDL_CreateThread(SoundThread, NULL);
+
+    return 1;
+}
+
+static void PCSound_Win32_Shutdown(void)
+{
+    sound_thread_running = 0;
+    SDL_WaitThread(sound_thread_handle, NULL);
+}
+
+pcsound_driver_t pcsound_win32_driver = 
+{
+    "Windows",
+    PCSound_Win32_Init,
+    PCSound_Win32_Shutdown,
+};
+
+#endif /* #ifdef _WIN32 */
+