- For the BIOS wait hack, use the NT API instead of the Win32 one.
- Remove unneeded DOS-exported functions.
- Use a PitGetReloadValue function for retrieving the reload value of a given PIT channel, instead of directly reading the reload value member, for functions *outside* of the PIT module.
- Implement basic Pulse-Width Modulation code for the PC speaker emulation.

svn path=/trunk/; revision=65333
This commit is contained in:
Hermès Bélusca-Maïto 2014-11-08 21:45:20 +00:00
parent 7cfea2fbb6
commit 6990cd2172
7 changed files with 236 additions and 61 deletions

View file

@ -31,6 +31,9 @@
#include "hardware/pic.h" #include "hardware/pic.h"
#include "hardware/timer.h" #include "hardware/timer.h"
/* Extra PSDK/NDK Headers */
#include <ndk/kefuncs.h>
/* PRIVATE VARIABLES **********************************************************/ /* PRIVATE VARIABLES **********************************************************/
CALLBACK16 BiosContext; CALLBACK16 BiosContext;
@ -166,9 +169,11 @@ static VOID WINAPI BiosMiscService(LPWORD Stack)
* See Ralf Brown: http://www.ctyme.com/intr/rb-1525.htm * See Ralf Brown: http://www.ctyme.com/intr/rb-1525.htm
* for more information. * for more information.
*/ */
LARGE_INTEGER TimeOut;
TimeOut.QuadPart = MAKELONG(getDX(), getCX()) * -10LL;
// HACK: For now, use the Win32 API (that takes time in milliseconds). // HACK: For now, use the NT API (time in hundreds of nanoseconds).
Sleep(MAKELONG(getDX(), getCX()) / 1000); NtDelayExecution(FALSE, &TimeOut);
/* Clear CF */ /* Clear CF */
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;

View file

@ -239,11 +239,6 @@ DWORD DosStartProcess(IN LPCSTR ExecutablePath,
VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode); VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode);
BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle); BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle);
VOID WINAPI DosInt20h(LPWORD Stack);
VOID WINAPI DosInt21h(LPWORD Stack);
VOID WINAPI DosBreakInterrupt(LPWORD Stack);
VOID WINAPI DosInt2Fh(LPWORD Stack);
BOOLEAN DosKRNLInitialize(VOID); BOOLEAN DosKRNLInitialize(VOID);
#endif // _DOS_H_ #endif // _DOS_H_

View file

@ -293,8 +293,8 @@ static VOID WINAPI Port61hWrite(USHORT Port, BYTE Data)
DPRINT("Speaker %s\n", Port61hState & 0x02 ? "on" : "off"); DPRINT("Speaker %s\n", Port61hState & 0x02 ? "on" : "off");
// SpeakerStateChange = TRUE; // SpeakerStateChange = TRUE;
} }
// if (SpeakerStateChange) SpeakerChange(); // if (SpeakerStateChange) SpeakerChange(Port61hState);
SpeakerChange(); SpeakerChange(Port61hState);
} }
static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State) static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State)
@ -347,7 +347,7 @@ static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State)
if ((OldPort61hState ^ Port61hState) & 0x20) if ((OldPort61hState ^ Port61hState) & 0x20)
{ {
DPRINT("PitChan2Out -- Port61hState changed\n"); DPRINT("PitChan2Out -- Port61hState changed\n");
SpeakerChange(); SpeakerChange(Port61hState);
} }
} }

View file

@ -12,7 +12,6 @@
#include "emulator.h" #include "emulator.h"
#include "speaker.h" #include "speaker.h"
#include "io.h"
#include "timer.h" #include "timer.h"
/* Extra PSDK/NDK Headers */ /* Extra PSDK/NDK Headers */
@ -20,6 +19,9 @@
#include <ndk/obfuncs.h> #include <ndk/obfuncs.h>
#include <ndk/rtlfuncs.h> #include <ndk/rtlfuncs.h>
/* Extra PSDK/NDK Headers */
#include <ndk/kefuncs.h>
/* DDK Driver Headers */ /* DDK Driver Headers */
#include <ntddbeep.h> #include <ntddbeep.h>
@ -27,26 +29,56 @@
static HANDLE hBeep = NULL; static HANDLE hBeep = NULL;
static LARGE_INTEGER FreqCount, CountStart;
static ULONG PulseTickCount = 0, FreqPulses = 0;
#define SPEAKER_RESPONSE 200 // in milliseconds
#define MIN_AUDIBLE_FREQ 20 // BEEP_FREQUENCY_MINIMUM
#define MAX_AUDIBLE_FREQ 20000 // BEEP_FREQUENCY_MAXIMUM
#define CLICK_FREQ 100
/* PRIVATE FUNCTIONS **********************************************************/ /* PRIVATE FUNCTIONS **********************************************************/
static DWORD OldReloadValue = 0; static
static PIT_MODE OldMode = 0; VOID
MakeBeep(ULONG Frequency,
/* PUBLIC FUNCTIONS ***********************************************************/ ULONG Duration)
VOID PlaySound(DWORD Frequency,
DWORD Duration)
{ {
/* Adapted from kernel32:Beep() */ static ULONG LastFrequency = 0, LastDuration = 0;
IO_STATUS_BLOCK IoStatusBlock; IO_STATUS_BLOCK IoStatusBlock;
BEEP_SET_PARAMETERS BeepSetParameters; BEEP_SET_PARAMETERS BeepSetParameters;
/* Set beep data */ /*
* Do nothing if we are replaying exactly the same sound
* (this avoids hiccups due to redoing the same beeps).
*/
if (Frequency == LastFrequency && Duration == LastDuration) return;
/* A null frequency means we stop beeping */
if (Frequency == 0) Duration = 0;
/*
* For small durations we automatically reset the beep so
* that we can replay short beeps like clicks immediately.
*/
if (Duration < 10)
{
LastFrequency = 0;
LastDuration = 0;
}
else
{
LastFrequency = Frequency;
LastDuration = Duration;
}
/* Set the data and do the beep */
BeepSetParameters.Frequency = Frequency; BeepSetParameters.Frequency = Frequency;
BeepSetParameters.Duration = Duration; BeepSetParameters.Duration = Duration;
/* Send the beep */
NtDeviceIoControlFile(hBeep, NtDeviceIoControlFile(hBeep,
NULL, NULL,
NULL, NULL,
@ -59,44 +91,173 @@ VOID PlaySound(DWORD Frequency,
0); 0);
} }
VOID SpeakerChange(VOID) static
VOID PulseSample(VOID)
{ {
BYTE Port61hState = IOReadB(CONTROL_SYSTEM_PORT61H); static ULONG Pulses = 0, CountStartTick = 0, LastPulsesFreq = 0;
BOOLEAN IsConnectedToPITChannel2 = !!(Port61hState & 0x01); ULONG LastPulseTickCount, CurrPulsesFreq;
BOOLEAN SpeakerDataOn = !!(Port61hState & 0x02); LARGE_INTEGER Counter;
LONGLONG Elapsed;
if (PitChannel2 && IsConnectedToPITChannel2 && SpeakerDataOn) /*
* Check how far away was the previous pulse and
* if it was >= 200ms away then restart counting.
*/
LastPulseTickCount = PulseTickCount;
PulseTickCount = GetTickCount();
if (PulseTickCount - LastPulseTickCount >= SPEAKER_RESPONSE)
{ {
/* Start beeping */ CountStart.QuadPart = 0;
Pulses = 0;
FreqPulses = 0;
return;
}
DWORD Frequency, Duration; /* We have closely spaced pulses. Start counting. */
if (CountStart.QuadPart == 0)
{
NtQueryPerformanceCounter(&CountStart, NULL);
CountStartTick = PulseTickCount;
Pulses = 0;
FreqPulses = 0;
return;
}
DWORD PitChannel2ReloadValue = PitChannel2->ReloadValue; /* A pulse is ongoing */
if (PitChannel2ReloadValue == 0) PitChannel2ReloadValue = 65536; ++Pulses;
DPRINT("(1) PitChannel2(Mode = %d ; ReloadValue = %d)\n", PitChannel2->Mode, PitChannel2ReloadValue); /* We require some pulses to have some statistics */
if (PulseTickCount - CountStartTick <= (SPEAKER_RESPONSE >> 1)) return;
if (OldMode == PitChannel2->Mode && OldReloadValue == PitChannel2ReloadValue) /* Get count time */
return; NtQueryPerformanceCounter(&Counter, NULL);
OldMode = PitChannel2->Mode; /*
OldReloadValue = PitChannel2ReloadValue; * Get the number of speaker hundreds of microseconds that have passed
* since we started counting.
*/
Elapsed = (Counter.QuadPart - CountStart.QuadPart) * 10000 / FreqCount.QuadPart;
if (Elapsed == 0) ++Elapsed;
DPRINT("(2) PitChannel2(Mode = %d ; ReloadValue = %d)\n", PitChannel2->Mode, PitChannel2ReloadValue); /* Update counting for next pulses */
CountStart = Counter;
CountStartTick = PulseTickCount;
Frequency = (PIT_BASE_FREQUENCY / PitChannel2ReloadValue); // HACKHACK!! I need to check why we need to double the number
Duration = INFINITE; // of pulses in order to have the correct frequency...
Pulses <<= 1;
PlaySound(Frequency, Duration); /* Get the current pulses frequency */
CurrPulsesFreq = 10000 * Pulses / Elapsed;
/* Round the current pulses frequency up and align */
if ((CurrPulsesFreq & 0x0F) > 7) CurrPulsesFreq += 0x10;
CurrPulsesFreq &= ~0x0F;
/* Reinitialize frequency counters if necessary */
if (LastPulsesFreq == 0) LastPulsesFreq = CurrPulsesFreq;
if (FreqPulses == 0) FreqPulses = LastPulsesFreq;
/* Fix up the current pulses frequency if needed */
if (LastPulsesFreq != 0 && CurrPulsesFreq == 0)
CurrPulsesFreq = LastPulsesFreq;
/*
* Magic begins there...
*/
#ifndef ABS
#define ABS(x) ((x) < 0 ? -(x) : (x))
#endif
if (ABS(CurrPulsesFreq - LastPulsesFreq) > 7)
{
/*
* This can be a "large" fluctuation so ignore it for now, but take
* it into account if it happens to be a real frequency change.
*/
CurrPulsesFreq = (CurrPulsesFreq + LastPulsesFreq) >> 1;
} }
else else
{ {
/* Stop beeping */ // FreqPulses = ((FreqPulses << 2) + LastPulsesFreq + CurrPulsesFreq) / 6;
FreqPulses = ((FreqPulses << 1) + LastPulsesFreq + CurrPulsesFreq) >> 2;
}
OldMode = 0; /* Round the pulses frequency up and align */
OldReloadValue = 0; if ((FreqPulses & 0x0F) > 7) FreqPulses += 0x10;
FreqPulses &= ~0x0F;
PlaySound(0x00, 0x00); DPRINT("FreqPulses = %d, LastPulsesFreq = %d, CurrPulsesFreq = %d, Pulses = %d, Elapsed = %d\n",
FreqPulses, LastPulsesFreq, CurrPulsesFreq, Pulses, Elapsed);
LastPulsesFreq = CurrPulsesFreq;
Pulses = 0;
}
/* PUBLIC FUNCTIONS ***********************************************************/
// SpeakerPulse
VOID SpeakerChange(UCHAR Port61hValue)
{
static BOOLEAN OldSpeakerOff = TRUE;
BOOLEAN Timer2Gate = !!(Port61hValue & 0x01);
BOOLEAN SpeakerOn = !!(Port61hValue & 0x02);
DPRINT("SpeakerChange -- Timer2Gate == %s ; SpeakerOn == %s\n",
Timer2Gate ? "true" : "false", SpeakerOn ? "true" : "false");
if (Timer2Gate)
{
if (SpeakerOn)
{
/* Start beeping */
ULONG Frequency = (PIT_BASE_FREQUENCY / PitGetReloadValue(2));
if (Frequency < MIN_AUDIBLE_FREQ || MAX_AUDIBLE_FREQ < Frequency)
Frequency = 0;
MakeBeep(Frequency, INFINITE);
}
else
{
/* Stop beeping */
MakeBeep(0, 0);
}
}
else
{
if (SpeakerOn)
{
if (OldSpeakerOff)
{
OldSpeakerOff = FALSE;
PulseSample();
}
if (FreqPulses >= MIN_AUDIBLE_FREQ)
MakeBeep(FreqPulses, INFINITE);
else if (CountStart.QuadPart != 0)
MakeBeep(CLICK_FREQ, 1); /* Click */
else
MakeBeep(0, 0); /* Stop beeping */
}
else
{
OldSpeakerOff = TRUE;
/*
* Check how far away was the previous pulse and if
* it was >= (200 + eps) ms away then stop beeping.
*/
if (GetTickCount() - PulseTickCount >= SPEAKER_RESPONSE + (SPEAKER_RESPONSE >> 3))
{
CountStart.QuadPart = 0;
FreqPulses = 0;
/* Stop beeping */
MakeBeep(0, 0);
}
}
} }
} }
@ -107,9 +268,14 @@ VOID SpeakerInitialize(VOID)
OBJECT_ATTRIBUTES ObjectAttributes; OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock; IO_STATUS_BLOCK IoStatusBlock;
/* Adapted from kernel32:Beep() */ /* Retrieve the performance frequency and initialize the timer ticks */
NtQueryPerformanceCounter(&CountStart, &FreqCount);
if (FreqCount.QuadPart == 0)
{
wprintf(L"FATAL: Performance counter not available\n");
}
/* Open the device */ /* Open the BEEP device */
RtlInitUnicodeString(&BeepDevice, L"\\Device\\Beep"); RtlInitUnicodeString(&BeepDevice, L"\\Device\\Beep");
InitializeObjectAttributes(&ObjectAttributes, &BeepDevice, 0, NULL, NULL); InitializeObjectAttributes(&ObjectAttributes, &BeepDevice, 0, NULL, NULL);
Status = NtCreateFile(&hBeep, Status = NtCreateFile(&hBeep,
@ -125,7 +291,8 @@ VOID SpeakerInitialize(VOID)
0); 0);
if (!NT_SUCCESS(Status)) if (!NT_SUCCESS(Status))
{ {
DPRINT1("Failed to open Beep driver, Status 0x%08lx\n", Status); DPRINT1("Failed to open the Beep driver, Status 0x%08lx\n", Status);
// hBeep = INVALID_HANDLE_VALUE;
} }
} }

View file

@ -17,7 +17,7 @@
/* FUNCTIONS ******************************************************************/ /* FUNCTIONS ******************************************************************/
VOID SpeakerChange(VOID); VOID SpeakerChange(UCHAR Port61hValue);
VOID SpeakerInitialize(VOID); VOID SpeakerInitialize(VOID);
VOID SpeakerCleanup(VOID); VOID SpeakerCleanup(VOID);

View file

@ -20,7 +20,6 @@
/* PRIVATE VARIABLES **********************************************************/ /* PRIVATE VARIABLES **********************************************************/
static PIT_CHANNEL PitChannels[PIT_CHANNELS]; static PIT_CHANNEL PitChannels[PIT_CHANNELS];
PPIT_CHANNEL PitChannel2 = &PitChannels[2];
/* PRIVATE FUNCTIONS **********************************************************/ /* PRIVATE FUNCTIONS **********************************************************/
@ -471,22 +470,19 @@ VOID PitSetGate(BYTE Channel, BOOLEAN State)
PitChannels[Channel].Gate = State; PitChannels[Channel].Gate = State;
} }
VOID PitClock(DWORD Count) WORD PitGetReloadValue(BYTE Channel)
{ {
UINT i; if (Channel >= PIT_CHANNELS) return 0xFFFF;
if (Count == 0) return; if (PitChannels[Channel].ReloadValue == 0)
return 0xFFFF;
for (i = 0; i < PIT_CHANNELS; i++) else
{ return PitChannels[Channel].ReloadValue;
// if (!PitChannels[i].Counting) continue;
PitDecrementCount(&PitChannels[i], Count);
}
} }
DWORD PitGetResolution(VOID) DWORD PitGetResolution(VOID)
{ {
INT i; UCHAR i;
DWORD MinReloadValue = 65536; DWORD MinReloadValue = 65536;
for (i = 0; i < PIT_CHANNELS; i++) for (i = 0; i < PIT_CHANNELS; i++)
@ -503,6 +499,19 @@ DWORD PitGetResolution(VOID)
return PIT_BASE_FREQUENCY / MinReloadValue; return PIT_BASE_FREQUENCY / MinReloadValue;
} }
VOID PitClock(DWORD Count)
{
UCHAR i;
if (Count == 0) return;
for (i = 0; i < PIT_CHANNELS; i++)
{
// if (!PitChannels[i].Counting) continue;
PitDecrementCount(&PitChannels[i], Count);
}
}
VOID PitInitialize(VOID) VOID PitInitialize(VOID)
{ {
/* Set up the timers to their default value */ /* Set up the timers to their default value */

View file

@ -73,15 +73,14 @@ typedef struct _PIT_CHANNEL
} PIT_CHANNEL, *PPIT_CHANNEL; } PIT_CHANNEL, *PPIT_CHANNEL;
extern PPIT_CHANNEL PitChannel2; // Needed for PC Speaker
/* FUNCTIONS ******************************************************************/ /* FUNCTIONS ******************************************************************/
VOID PitSetOutFunction(BYTE Channel, LPVOID Param, PIT_OUT_FUNCTION OutFunction); VOID PitSetOutFunction(BYTE Channel, LPVOID Param, PIT_OUT_FUNCTION OutFunction);
VOID PitSetGate(BYTE Channel, BOOLEAN State); VOID PitSetGate(BYTE Channel, BOOLEAN State);
WORD PitGetReloadValue(BYTE Channel);
VOID PitClock(DWORD Count);
DWORD PitGetResolution(VOID); DWORD PitGetResolution(VOID);
VOID PitClock(DWORD Count);
VOID PitInitialize(VOID); VOID PitInitialize(VOID);