📄 platformsound.pas
字号:
//////////////////////////////////////////////////////////////////////
// //
// platformSound.pas: Platform dependant sound code //
// //
// The contents of this file are subject to the Bottled Light //
// Public License Version 1.0 (the "License"); you may not use this //
// file except in compliance with the License. You may obtain a //
// copy of the License at http://www.bottledlight.com/BLPL/ //
// //
// Software distributed under the License is distributed on an //
// "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or //
// implied. See the License for the specific language governing //
// rights and limitations under the License. //
// //
// The Original Code is the Mappy VM User Interface, released //
// April 1st, 2003. The Initial Developer of the Original Code is //
// Bottled Light, Inc. Portions created by Bottled Light, Inc. are //
// Copyright (C) 2001-2003 Bottled Light, Inc. All Rights Reserved. //
// //
// Author(s): //
// Michael Noland (joat), michael@bottledlight.com //
// //
// Changelog: //
// 1.0: First public release (April 1st, 2003) //
// //
// Notes: //
// MVM sound output frankly sucks right now, and I'm not certain //
// if its the fault of this code or the simluation code in the //
// core. //
// //
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
unit platformSound; //////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
interface ////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
uses
Windows, Classes, SysUtils, Forms, MMSystem, Math,
DirectSound, nexus, console;
//////////////////////////////////////////////////////////////////////
type
TPlatformSound = class
private
// State variables
FEnabled: boolean;
FAvailable: boolean;
FThreadPriority: TThreadPriority;
FFrequency: integer;
protected
procedure SetEnabled(Value: boolean); virtual; abstract;
procedure SetAvailable(Value: boolean); virtual; abstract;
procedure SetFrequency(Value: integer); virtual; abstract;
procedure SetPriority(Value: TThreadPriority); virtual; abstract;
public
constructor Create;
destructor Destroy; override;
property Enabled: Boolean read FEnabled write SetEnabled;
property Available: Boolean read FAvailable write SetAvailable;
property ThreadPriority: TThreadPriority read FThreadPriority write SetPriority;
property Frequency: integer read FFrequency write SetFrequency;
end;
TDirectSoundDriver = class;
TMixerThread = class(TThread)
FParent: TDirectSoundDriver;
procedure Execute; override;
end;
TDirectSoundDriver = class(TPlatformSound)
private
// Playback related
DSound: IDirectSound;
secondaryBuffer: IDirectSoundBuffer;
FTimerThread: TMixerThread;
lastPos: longword;
lastTime: integer;
function InitSound: boolean;
procedure FreeSound;
protected
procedure Timer;
procedure SetEnabled(Value: boolean); override;
procedure SetAvailable(Value: boolean); override;
procedure SetFrequency(Value: integer); override;
procedure SetPriority(Value: TThreadPriority); override;
public
constructor Create;
destructor Destroy; override;
procedure OnSoundReady(data: pointer; length: integer);
end;
//////////////////////////////////////////////////////////////////////
type
TObserverOnSoundReady = procedure (data: pointer; length: integer) of object;
var
sysSound: TDirectSoundDriver;
soundObserverCallback: TObserverOnSoundReady;
//////////////////////////////////////////////////////////////////////
implementation ///////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// TPlatformSound ////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
constructor TPlatformSound.Create;
begin
FEnabled := false;
FAvailable := false;
FThreadPriority := tpNormal;
FFrequency := 22050;
end;
//////////////////////////////////////////////////////////////////////
destructor TPlatformSound.Destroy;
begin
inherited;
end;
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
procedure HackSoundReady(data: pointer; length: integer);
begin
sysSound.onSoundReady(data, length);
if @soundObserverCallback <> nil then soundObserverCallback(data, length);
end;
//////////////////////////////////////////////////////////////////////
// TMixerThread //////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
procedure TMixerThread.Execute;
begin
Priority := FParent.FThreadPriority;
repeat
SleepEx(50, False);
Synchronize(FParent.Timer);
until Terminated;
end;
/////////////////////////////////////////////////////////////////////
// TDirectSoundDriver ////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
constructor TDirectSoundDriver.Create;
begin
inherited;
// Playback related
DSound := nil;
secondaryBuffer := nil;
FTimerThread := TMixerThread.Create(true);
FTimerThread.Priority := FThreadPriority;
FTimerThread.FParent := self;
lastPos := 0;
lastTime := timeGetTime;
end;
//////////////////////////////////////////////////////////////////////
destructor TDirectSoundDriver.Destroy;
begin
FreeSound;
FTimerThread.Free;
inherited;
end;
//////////////////////////////////////////////////////////////////////
procedure TDirectSoundDriver.SetEnabled(value: boolean);
begin
if FAvailable then begin
if value and not FEnabled then begin
secondaryBuffer.Play(0, 0, DSBPLAY_LOOPING);
FTimerThread.Resume;
FEnabled := true;
end;
if FEnabled and not value then begin
secondaryBuffer.Stop;
FTimerThread.Suspend;
FEnabled := false;
end;
end;
end;
//////////////////////////////////////////////////////////////////////
procedure TDirectSoundDriver.SetAvailable(value: boolean);
begin
if FAvailable xor value then begin
if value then
FAvailable := InitSound
else
FreeSound;
end;
end;
//////////////////////////////////////////////////////////////////////
procedure TDirectSoundDriver.SetFrequency(value: integer);
var
wasAvailable: boolean;
wasEnabled: boolean;
begin
wasAvailable := FAvailable;
wasEnabled := FEnabled;
FFrequency := value;
SetAvailable(false);
SetAvailable(wasAvailable);
SetEnabled(wasEnabled);
end;
//////////////////////////////////////////////////////////////////////
procedure TDirectSoundDriver.SetPriority(Value: TThreadPriority);
begin
if Value <> FThreadPriority then begin
FThreadPriority := Value;
FTimerThread.Priority := Value;
end;
end;
//////////////////////////////////////////////////////////////////////
var
nilspace: array[0..16383] of byte;
procedure TDirectSoundDriver.Timer;
var
curTime, delta: integer;
data: pointer;
length: integer;
begin
if FAvailable and FEnabled then begin
curTime := timeGetTime;
delta := curTime-lastTime;
if delta < 10 then Exit;
vmGetAudioData(data, length);
if length = 0 then begin
length := min(16384, FFrequency div 20);
data := @nilspace;
end;
OnSoundReady(data, length);
if @soundObserverCallback <> nil then soundObserverCallback(data, length);
lastTime := curTime;
end;
end;
//////////////////////////////////////////////////////////////////////
function TDirectSoundDriver.InitSound: boolean;
var
primaryBuffer: IDirectSoundBuffer;
bufferInfo: TDSBufferDesc;
waveFormat: TWaveFormatEx;
begin
Result := false;
// Create the DirectSound interface
logWriteLn('PlatformSound: Creating DirectSound interface');
if Failed(DirectSoundCreate(nil, DSound, nil)) then begin
logWriteLn(' DirectSoundCreate failed, sound output unavailable');
Exit;
end;
// Set up a wave format structure with our data type of choice (8-bit stereo at 44100 Hz)
FillChar(waveFormat, SizeOf(waveFormat), 0);
with waveFormat do begin
wFormatTag := WAVE_FORMAT_PCM;
nChannels := 2;
nSamplesPerSec := FFrequency;
wBitsPerSample := 8;
nBlockAlign := (wBitsPerSample shr 3) * nChannels;
nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
end;
// Set the DSound priority level
if Failed(DSound.SetCooperativeLevel(application.Handle, DSSCL_PRIORITY)) then begin
logWriteLn(' SetCooperativeLevel with DSSCL_PRIORITY failed, trying DSSCL_NORMAL');
if Failed(DSound.SetCooperativeLevel(application.Handle, DSSCL_NORMAL)) then begin
logWriteLn(' Unable to set priority level, sound output unavailable.');
Exit;
end;
end;
// Try to capture the primary buffer
FillChar(bufferInfo, SizeOf(bufferInfo), 0);
bufferInfo.dwSize := SizeOf(bufferInfo);
bufferInfo.dwFlags := DSBCAPS_PRIMARYBUFFER;
if Failed(DSound.CreateSoundBuffer(bufferInfo, primaryBuffer, nil)) then
logWriteLn(' Primary buffer capture failed')
else begin
if Failed(primaryBuffer.SetFormat(waveFormat)) then
logWriteLn(' SetFormat on primary buffer failed');
end;
if Assigned(primaryBuffer) then primaryBuffer := nil;
// Create the secondary buffer
FillChar(bufferInfo, SizeOf(bufferInfo), 0);
bufferInfo.dwSize := SizeOf(bufferInfo);
bufferInfo.dwFlags := DSBCAPS_GETCURRENTPOSITION2 or DSBCAPS_GLOBALFOCUS or DSBCAPS_CTRLPOSITIONNOTIFY;
bufferInfo.dwBufferBytes := waveFormat.nAvgBytesPerSec div 5; // 200 ms buffer
bufferInfo.lpwfxFormat := @waveFormat;
if Failed(DSound.CreateSoundBuffer(bufferInfo, secondaryBuffer, nil)) then begin
logWriteLn(' CreateSoundBuffer failed, sound output unavailable.');
Exit;
end;
// Whee, we're done!
Result := true;
if coreLoaded then begin
vmSetAudioRate(FFrequency);
vmSetOnSound(HackSoundReady);
end;
end;
//////////////////////////////////////////////////////////////////////
procedure TDirectSoundDriver.FreeSound;
begin
if coreLoaded then vmSetOnSound(nil);
FAvailable := false;
SetEnabled(false);
logWriteLn('PlatformSound: Releasing DirectSound interfaces');
if Assigned(secondaryBuffer) then secondaryBuffer := nil;
if Assigned(DSound) then DSound := nil;
end;
//////////////////////////////////////////////////////////////////////
procedure TDirectSoundDriver.OnSoundReady(data: pointer; length: integer);
var
size1, size2: longword;
bank1, bank2: pointer;
hr: HResult;
begin
// Get some mixing action going on
if FAvailable and FEnabled then begin
// Attempt to lock just after our last write
hr := secondaryBuffer.Lock(lastPos, length*2, bank1, size1, bank2, size2, 0);
if Failed(hr) then begin
// That didn't work, so lock at the current writable position instead
// logwrite('f');
secondaryBuffer.GetCurrentPosition(nil, @lastPos);
hr := secondaryBuffer.Lock(lastPos, length*2, bank1, size1, bank2, size2, 0);
end;
// Copy from the synthesis buffer to the output buffer
if not Failed(hr) then begin
Move(data^, bank1^, size1);
if size2 > 0 then begin
data := pointer(longword(data) + size1);
Move(data^, bank2^, size2);
lastPos := size2;
end else
lastPos := lastPos + size1;
// Unlock the buffer
secondaryBuffer.Unlock(bank1, size1, bank2, size2);
end;
end;
end;
//////////////////////////////////////////////////////////////////////
begin
soundObserverCallback := nil;
end.
//////////////////////////////////////////////////////////////////////
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -