📄 cpusound.pas
字号:
//////////////////////////////////////////////////////////////////////
// //
// cpuSound.pas: Sound simulation code //
// This manages sound channels 1,2,3,4,A,B //
// //
// 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 Core, 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: //
// Channel 1 is a square wave with envelope and sweep control //
// Channel 2 is a square wave with only envelope control //
// Channel 3 is a 4 bit arbitrary waveform with envelope control //
// Channel 4 is a LFSR 'pseudonoise' channel /w envelope control //
// Channels A and B are 8 bit FIFO fed waveforms //
// //
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
unit cpuSound; ///////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
interface ////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
uses
SysUtils, Math, nexus, AddressSpace;
//////////////////////////////////////////////////////////////////////
// Overall control functions /////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// soundProcess() runs the audio simulator for a number of CPU cycles.
procedure soundProcess(cycles: integer);
// Signal the UI that there are sound samples ready, then clear the
// sample buffer.
procedure soundFlushBuffer;
// This function is called *before* any sound related registers are
// modified, to allow the sound simulation to catch up to the current
// time, using the original settings.
procedure soundChanging;
// vmSetAudioRate() sets the number of CPU cycles that should elapse
// between each generated sound sample.
procedure vmSetAudioRate(cyclesPerSample: integer);
// vmGetAudioData() is called to get a pointer to the sample buffer,
// which is stored in the data pointer. The number of valid samples
// in that buffer is stuffed in length. The buffer size is actually
// length * 2 bytes, since each sample consists of both a left and a
// right volume level (even bytes = L, odd bytes = R).
procedure vmGetAudioData(var data: pointer; var length: integer);
//////////////////////////////////////////////////////////////////////
// These functions are called from cpuMemory whenever the
// corresponding registers are modified
//////////////////////////////////////////////////////////////////////
procedure soundSetSound1Sweep;
procedure soundSetSound1Length;
procedure soundSetSound1Freq;
procedure soundSetSound2Length;
procedure soundSetSound2Freq;
procedure soundSetSound3CR;
procedure soundSetSound3Length;
procedure soundSetSound3Freq;
procedure soundSetSound4Length;
procedure soundSetSound4CR;
procedure soundIncrementFifoA;
procedure soundIncrementFifoB;
procedure soundWriteDSoundCR;
//////////////////////////////////////////////////////////////////////
// DS FIFO management ////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
procedure QueueTDSoundRecord(var r: TDSoundRecord; data: int8);
procedure ResetTDSoundRecord(var r: TDSoundRecord);
procedure DequeueTDSoundRecord(var r: TDSoundRecord);
//////////////////////////////////////////////////////////////////////
exports
vmSetAudioRate,
vmGetAudioData;
//////////////////////////////////////////////////////////////////////
var
// Flags that enable sound generation on the different audio
// channels, provided so the UI can filter channels.
enableGBC1, enableGBC2, enableGBC3, enableGBC4: boolean;
enableDSA, enableDSB: boolean;
//////////////////////////////////////////////////////////////////////
implementation ///////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
uses
cpuMemory, cpuMisc;
//////////////////////////////////////////////////////////////////////
// Globals shared between the sound functions ////////////////////////
//////////////////////////////////////////////////////////////////////
const
BUFFER_MASK = 32767;
//////////////////////////////////////////////////////////////////////
var
// even bytes = L, odd bytes = R
soundBuffer: array[0..BUFFER_MASK] of uint8;
soundBufPos: integer;
audioRate: integer;
//////////////////////////////////////////////////////////////////////
const
// Channel 1 and 2 waveforms at different duty cycles
SquareWaves: array[0..3, 0..7] of integer =
((+1,-1,-1,-1,-1,-1,-1,-1),
(+1,+1,-1,-1,-1,-1,-1,-1),
(+1,+1,+1,+1,-1,-1,-1,-1),
(+1,+1,+1,+1,+1,+1,-1,-1));
// ((-1,-1,1,-1,-1,-1,-1,-1),
// (-1,1,1,-1,-1,-1,-1,-1),
// (-1,1,1,1,1,-1,-1,-1),
// (1,-1,-1,1,1,1,1,1));
//////////////////////////////////////////////////////////////////////
// These functions are called from cpuMemory whenever the //
// corresponding registers are modified //
//////////////////////////////////////////////////////////////////////
procedure soundIncrementFifoA;
begin
QueueTDSoundRecord(soundA, registers[SOUNDA_FIFO+0]);
QueueTDSoundRecord(soundA, registers[SOUNDA_FIFO+1]);
QueueTDSoundRecord(soundA, registers[SOUNDA_FIFO+2]);
QueueTDSoundRecord(soundA, registers[SOUNDA_FIFO+3]);
end;
//////////////////////////////////////////////////////////////////////
procedure soundIncrementFifoB;
begin
QueueTDSoundRecord(soundB, registers[SOUNDB_FIFO+0]);
QueueTDSoundRecord(soundB, registers[SOUNDB_FIFO+1]);
QueueTDSoundRecord(soundB, registers[SOUNDB_FIFO+2]);
QueueTDSoundRecord(soundB, registers[SOUNDB_FIFO+3]);
end;
//////////////////////////////////////////////////////////////////////
procedure soundSetSound1Sweep;
var
r: byte;
begin
r := registers[SOUND1_SWEEP];
// the interval between sweeps is length/128 seconds, or length*131072 cycles
sound1.sweepRate := ((r shr 4) and 7) * 131072;
sound1.sweepDecrease := r and SOUND_SWEEP_DECREASE <> 0;
sound1.sweepStep := r and 7;
// Turn off the sound if theres a non-zero sweep rate when sweeping is disabled
if (sound1.sweepRate = 0) and (sound1.sweepStep > 0) and not sound1.sweepDecrease then sound1.enabled := false;
end;
//////////////////////////////////////////////////////////////////////
procedure soundSetSound1Length;
var
r: uint16;
begin
r := Puint16(@(registers[SOUND1_LENGTH]))^;
// total sound length is (64-length)/256 seconds, or (64-length)*65536 cycles
sound1.soundLength := (64 - r and 63)*65536;
sound1.waveDuty := (r shr 6) and 3;
if not sound1.enabled then begin
sound1.envIncrease := (r shr 8) and SOUND_ENVELOPE_INCREASE <> 0;
sound1.volume := r shr 12;
// envelope step rate is length/64 secs, or length*262144 cycles
sound1.envRate := ((r shr 8) and 3)*262144;
end;
end;
//////////////////////////////////////////////////////////////////////
procedure soundSetSound1Freq;
var
r: uint16;
begin
r := Puint16(@(registers[SOUND1_FREQUENCY]))^;
// If this bit is written, reset sound 1
if r and SOUND_RESTART <> 0 then begin
sound1.enabled := false;
soundSetSound1Sweep;
soundSetSound1Length;
sound1.index := 0;
// sound1.waveIndex := 0; according to belogic, no reset of this
sound1.envIndex := 0;
sound1.sweepIndex := 0;
sound1.enabled := true;
end;
sound1.timed := r and (1 shl 14) <> 0;
// the sound frequency is 131072/(2048-freq) Hz, every (2048-freq)*128 cycles
sound1.freq := (2048 - r and $7FF) * 128;
Puint16(@(registers[SOUND1_FREQUENCY]))^ := r and not SOUND_RESTART;
end;
//////////////////////////////////////////////////////////////////////
procedure soundSetSound2Length;
var
r: uint16;
begin
r := Puint16(@(registers[SOUND2_LENGTH]))^;
// total sound length is (64-length)/256 seconds, or (64-length)*65536 cycles
sound2.soundLength := (64 - r and 63)*65536;
sound2.waveDuty := (r shr 6) and 3;
if not sound2.enabled then begin
sound2.envIncrease := (r shr 8) and SOUND_ENVELOPE_INCREASE <> 0;
sound2.volume := r shr 12;
// envelope step rate is length/64 secs, or length*262144 cycles
sound2.envRate := ((r shr 8) and 3)*262144;
end;
end;
//////////////////////////////////////////////////////////////////////
procedure soundSetSound2Freq;
var
r: uint16;
begin
r := Puint16(@(registers[SOUND2_FREQUENCY]))^;
// If this bit is written, reset sound 2
if r and SOUND_RESTART <> 0 then begin
sound2.enabled := false;
soundSetSound2Length;
sound2.index := 0;
sound2.waveIndex := 0;
sound2.envIndex := 0;
sound2.enabled := true;
end;
sound2.timed := r and (1 shl 14) <> 0;
// the sound frequency is 131072/(2048-freq) Hz, every (2048-freq)*128 cycles
sound2.freq := (2048 - r and $7FF) * 128;
Puint16(@(registers[SOUND2_FREQUENCY]))^ := r and not SOUND_RESTART;
end;
//////////////////////////////////////////////////////////////////////
procedure sound3swapBanks;
var
temp: byte;
i: integer;
begin
for i := 0 to 15 do begin
temp := sound3.backBank[i];
sound3.backBank[i] := registers[SOUND3_PATTERN+i];
registers[SOUND3_PATTERN+i] := temp;
end;
sound3.curBank := not sound3.curBank;
end;
//////////////////////////////////////////////////////////////////////
procedure soundSetSound3CR;
var
r: byte;
bank: boolean;
begin
r := registers[SOUND3_CR];
// sound3.enabled := sound3.enabled or (r and SOUND3_ACTIVE <> 0);
sound3.doubleLength := r and SOUND3_64SAMPLES <> 0;
bank := r and SOUND3_BANK1 <> 0;
if bank <> sound3.curBank then sound3swapBanks;
end;
//////////////////////////////////////////////////////////////////////
procedure soundSetSound3Length;
// JSensebe says: 000=mute, 001=full vol, 010=1/2 vol, 011=1/4 vol, 1xx=3/4 vol
const
sound3volumes: array[0..7] of integer = (0,4,2,1,3,3,3,3);
var
r: uint16;
begin
r := Puint16(@(registers[SOUND3_LENGTH]))^;
// total sound length is (256-length)/256 seconds, or (256-length)*65536 cycles
sound3.soundLength := (256 - r and $FF)*65536;
sound3.volume := sound3volumes[r shr 13];
end;
//////////////////////////////////////////////////////////////////////
procedure soundSetSound3Freq;
var
r: uint16;
i: integer;
begin
r := Puint16(@(registers[SOUND3_FREQUENCY]))^;
// If this bit is written, reset sound 3
if r and SOUND_RESTART <> 0 then begin
sound3.enabled := false;
soundSetSound3Length;
sound3.index := 0;
sound3.waveIndex := 0;
sound3.enabled := registers[SOUND3_CR] and SOUND3_ACTIVE <> 0;
for i := 0 to 15 do begin
sound3.backBank[i] := 0;
registers[SOUND3_PATTERN+i] := 0;
end;
end;
sound3.timed := r and (1 shl 14) <> 0;
// the sound frequency is 131072/(2048-freq) Hz, every (2048-freq)*128 cycles
sound3.freq := (2048 - r and $7FF) * 128;
Puint16(@(registers[SOUND3_FREQUENCY]))^ := r and not SOUND_RESTART;
end;
//////////////////////////////////////////////////////////////////////
procedure soundSetSound4Length;
var
r: uint16;
begin
r := Puint16(@(registers[SOUND4_LENGTH]))^;
// total sound length is (64-length)/256 seconds, or (64-length)*65536 cycles
sound4.soundLength := (64 - r and 63)*65536;
if not sound4.enabled then begin
sound4.envIncrease := (r shr 8) and SOUND_ENVELOPE_INCREASE <> 0;
sound4.volume := r shr 12;
// envelope rate is length/64 secs, or length*262144 cycles
sound4.envRate := ((r shr 8) and 3)*262144;
end;
end;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -