diff options
Diffstat (limited to 'src/SPU.cpp')
-rw-r--r-- | src/SPU.cpp | 811 |
1 files changed, 811 insertions, 0 deletions
diff --git a/src/SPU.cpp b/src/SPU.cpp new file mode 100644 index 0000000..002cde6 --- /dev/null +++ b/src/SPU.cpp @@ -0,0 +1,811 @@ +/* + Copyright 2016-2017 StapleButter + + This file is part of melonDS. + + melonDS 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 3 of the License, or (at your option) + any later version. + + melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <string.h> +#include "NDS.h" +#include "SPU.h" + + +namespace SPU +{ + +const s8 ADPCMIndexTable[8] = {-1, -1, -1, -1, 2, 4, 6, 8}; + +const u16 ADPCMTable[89] = +{ + 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, + 0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F, + 0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042, + 0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F, + 0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133, + 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292, + 0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583, + 0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0, + 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954, + 0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, + 0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462, + 0x7FFF +}; + +const s16 PSGTable[8][8] = +{ + {-0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF}, + {-0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF}, + {-0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF}, + {-0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF}, + {-0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF}, + {-0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF}, + {-0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF}, + {-0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF} +}; + +const u32 OutputBufferSize = 2*1024; +s16 OutputBuffer[2 * OutputBufferSize]; +u32 OutputReadOffset; +u32 OutputWriteOffset; + + +u16 Cnt; +u8 MasterVolume; +u16 Bias; + +Channel* Channels[16]; +CaptureUnit* Capture[2]; + + +bool Init() +{ + for (int i = 0; i < 16; i++) + Channels[i] = new Channel(i); + + Capture[0] = new CaptureUnit(0); + Capture[1] = new CaptureUnit(1); + + return true; +} + +void DeInit() +{ + for (int i = 0; i < 16; i++) + delete Channels[i]; + + delete Capture[0]; + delete Capture[1]; +} + +void Reset() +{ + memset(OutputBuffer, 0, 2*OutputBufferSize*2); + OutputReadOffset = 0; + OutputWriteOffset = 0; + + Cnt = 0; + MasterVolume = 0; + Bias = 0; + + for (int i = 0; i < 16; i++) + Channels[i]->Reset(); + + Capture[0]->Reset(); + Capture[1]->Reset(); + + NDS::ScheduleEvent(NDS::Event_SPU, true, 1024*16, Mix, 16); +} + + +void SetBias(u16 bias) +{ + Bias = bias; +} + + +Channel::Channel(u32 num) +{ + Num = num; +} + +Channel::~Channel() +{ +} + +void Channel::Reset() +{ + SetCnt(0); + SrcAddr = 0; + TimerReload = 0; + LoopPos = 0; + Length = 0; + + Timer = 0; +} + +void Channel::Start() +{ + Timer = TimerReload; + + if (((Cnt >> 29) & 0x3) == 3) + Pos = -1; + else + Pos = -3; + + NoiseVal = 0x7FFF; + CurSample = 0; +} + +void Channel::NextSample_PCM8() +{ + Pos++; + if (Pos < 0) return; + if (Pos >= (LoopPos + Length)) + { + // TODO: what happens when mode 3 is used? + u32 repeat = (Cnt >> 27) & 0x3; + if (repeat == 2) + { + CurSample = 0; + Cnt &= ~(1<<31); + return; + } + else if (repeat == 1) + { + Pos = LoopPos; + } + } + + s8 val = (s8)NDS::ARM7Read8(SrcAddr + Pos); + CurSample = val << 8; +} + +void Channel::NextSample_PCM16() +{ + Pos++; + if (Pos < 0) return; + if ((Pos<<1) >= (LoopPos + Length)) + { + // TODO: what happens when mode 3 is used? + u32 repeat = (Cnt >> 27) & 0x3; + if (repeat == 2) + { + CurSample = 0; + Cnt &= ~(1<<31); + return; + } + else if (repeat == 1) + { + Pos = LoopPos>>1; + } + } + + s16 val = (s16)NDS::ARM7Read16(SrcAddr + (Pos<<1)); + CurSample = val; +} + +void Channel::NextSample_ADPCM() +{ + Pos++; + if (Pos < 8) + { + if (Pos == 0) + { + // setup ADPCM + u32 header = NDS::ARM7Read32(SrcAddr); + ADPCMVal = header & 0xFFFF; + ADPCMIndex = (header >> 16) & 0x7F; + if (ADPCMIndex > 88) ADPCMIndex = 88; + + ADPCMValLoop = ADPCMVal; + ADPCMIndexLoop = ADPCMIndex; + } + + return; + } + + if ((Pos>>1) >= (LoopPos + Length)) + { + // TODO: what happens when mode 3 is used? + u32 repeat = (Cnt >> 27) & 0x3; + if (repeat == 2) + { + CurSample = 0; + Cnt &= ~(1<<31); + return; + } + else if (repeat == 1) + { + Pos = LoopPos<<1; + ADPCMVal = ADPCMValLoop; + ADPCMIndex = ADPCMIndexLoop; + } + } + else + { + if (!(Pos & 0x1)) + ADPCMCurByte = NDS::ARM7Read8(SrcAddr + (Pos>>1)); + else + ADPCMCurByte >>= 4; + + u16 val = ADPCMTable[ADPCMIndex]; + u16 diff = val >> 3; + if (ADPCMCurByte & 0x1) diff += (val >> 2); + if (ADPCMCurByte & 0x2) diff += (val >> 1); + if (ADPCMCurByte & 0x4) diff += val; + + if (ADPCMCurByte & 0x8) + { + ADPCMVal -= diff; + if (ADPCMVal < -0x7FFF) ADPCMVal = -0x7FFF; + } + else + { + ADPCMVal += diff; + if (ADPCMVal > 0x7FFF) ADPCMVal = 0x7FFF; + } + + ADPCMIndex += ADPCMIndexTable[ADPCMCurByte & 0x7]; + if (ADPCMIndex < 0) ADPCMIndex = 0; + else if (ADPCMIndex > 88) ADPCMIndex = 88; + + if (Pos == (LoopPos<<1)) + { + ADPCMValLoop = ADPCMVal; + ADPCMIndexLoop = ADPCMIndex; + } + } + + CurSample = ADPCMVal; +} + +void Channel::NextSample_PSG() +{ + Pos++; + CurSample = PSGTable[(Cnt >> 24) & 0x7][Pos & 0x7]; +} + +void Channel::NextSample_Noise() +{ + if (NoiseVal & 0x1) + { + NoiseVal = (NoiseVal >> 1) ^ 0x6000; + CurSample = -0x7FFF; + } + else + { + NoiseVal >>= 1; + CurSample = 0x7FFF; + } +} + +template<u32 type> +void Channel::Run(s32* buf, u32 samples) +{ + for (u32 s = 0; s < samples; s++) + buf[s] = 0; + + for (u32 s = 0; s < samples; s++) + { + Timer += 512; // 1 sample = 512 cycles at 16MHz + + while (Timer >> 16) + { + Timer = TimerReload + (Timer - 0x10000); + + switch (type) + { + case 0: NextSample_PCM8(); break; + case 1: NextSample_PCM16(); break; + case 2: NextSample_ADPCM(); break; + case 3: NextSample_PSG(); break; + case 4: NextSample_Noise(); break; + } + } + + s32 val = (s32)CurSample; + val <<= VolumeShift; + val *= Volume; + buf[s] = val; + + if (!(Cnt & (1<<31))) break; + } +} + + +CaptureUnit::CaptureUnit(u32 num) +{ + Num = num; +} + +CaptureUnit::~CaptureUnit() +{ +} + +void CaptureUnit::Reset() +{ + SetCnt(0); + DstAddr = 0; + TimerReload = 0; + Length = 0; + + Timer = 0; +} + +void CaptureUnit::Run(s32 sample) +{ + Timer += 512; + + if (Cnt & 0x08) + { + while (Timer >> 16) + { + Timer = TimerReload + (Timer - 0x10000); + + NDS::ARM7Write8(DstAddr + Pos, (u8)(sample >> 8)); + Pos++; + if (Pos >= Length) + { + if (Cnt & 0x04) + { + Cnt &= 0x7F; + return; + } + else + Pos = 0; + } + } + } + else + { + while (Timer >> 16) + { + Timer = TimerReload + (Timer - 0x10000); + + NDS::ARM7Write16(DstAddr + Pos, (u16)sample); + Pos += 2; + if (Pos >= Length) + { + if (Cnt & 0x04) + { + Cnt &= 0x7F; + return; + } + else + Pos = 0; + } + } + } +} + + +void Mix(u32 samples) +{ + s32 channelbuf[32]; + s32 leftbuf[32], rightbuf[32]; + s32 ch1buf[32], ch3buf[32]; + s32 leftoutput[32], rightoutput[32]; + + for (u32 s = 0; s < samples; s++) + { + leftbuf[s] = 0; rightbuf[s] = 0; + leftoutput[s] = 0; rightoutput[s] = 0; + } + + if (Cnt & (1<<15)) + { + u32 mixermask = 0xFFFF; + if (Cnt & (1<<12)) mixermask &= ~(1<<1); + if (Cnt & (1<<13)) mixermask &= ~(1<<3); + + for (int i = 0; i < 16; i++) + { + if (!(mixermask & (1<<i))) continue; + Channel* chan = Channels[i]; + if (!(chan->Cnt & (1<<31))) continue; + + // TODO: what happens if we use type 3 on channels 0-7?? + chan->DoRun(channelbuf, samples); + + for (u32 s = 0; s < samples; s++) + { + s32 val = (s32)channelbuf[s]; + + s32 l = ((s64)val * (128-chan->Pan)) >> 10; + s32 r = ((s64)val * chan->Pan) >> 10; + + leftbuf[s] += l; + rightbuf[s] += r; + } + } + + // sound capture + // TODO: other sound capture sources, along with their bugs + + if (Capture[0]->Cnt & (1<<7)) + { + for (u32 s = 0; s < samples; s++) + { + s32 val = leftbuf[s]; + + val >>= 8; + if (val < -0x8000) val = -0x8000; + else if (val > 0x7FFF) val = 0x7FFF; + + Capture[0]->Run(val); + if (!((Capture[0]->Cnt & (1<<7)))) break; + } + } + + if (Capture[1]->Cnt & (1<<7)) + { + for (u32 s = 0; s < samples; s++) + { + s32 val = rightbuf[s]; + + val >>= 8; + if (val < -0x8000) val = -0x8000; + else if (val > 0x7FFF) val = 0x7FFF; + + Capture[1]->Run(val); + if (!((Capture[1]->Cnt & (1<<7)))) break; + } + } + + // final output + + if (Cnt & 0x0500) + { + // mix channel 1 if needed + Channels[1]->DoRun(ch1buf, samples); + } + if (Cnt & 0x0A00) + { + // mix channel 3 if needed + Channels[3]->DoRun(ch3buf, samples); + } + + switch (Cnt & 0x0300) + { + case 0x0000: // left mixer + { + for (u32 s = 0; s < samples; s++) + leftoutput[s] = leftbuf[s]; + } + break; + case 0x0100: // channel 1 + { + s32 pan = 128 - Channels[1]->Pan; + for (u32 s = 0; s < samples; s++) + leftoutput[s] = ((s64)ch1buf[s] * pan) >> 10; + } + break; + case 0x0200: // channel 3 + { + s32 pan = 128 - Channels[3]->Pan; + for (u32 s = 0; s < samples; s++) + leftoutput[s] = ((s64)ch3buf[s] * pan) >> 10; + } + break; + case 0x0300: // channel 1+3 + { + s32 pan1 = 128 - Channels[1]->Pan; + s32 pan3 = 128 - Channels[3]->Pan; + for (u32 s = 0; s < samples; s++) + leftoutput[s] = (((s64)ch1buf[s] * pan1) >> 10) + (((s64)ch3buf[s] * pan3) >> 10); + } + break; + } + + switch (Cnt & 0x0C00) + { + case 0x0000: // right mixer + { + for (u32 s = 0; s < samples; s++) + rightoutput[s] = rightbuf[s]; + } + break; + case 0x0400: // channel 1 + { + s32 pan = Channels[1]->Pan; + for (u32 s = 0; s < samples; s++) + rightoutput[s] = ((s64)ch1buf[s] * pan) >> 10; + } + break; + case 0x0800: // channel 3 + { + s32 pan = Channels[3]->Pan; + for (u32 s = 0; s < samples; s++) + rightoutput[s] = ((s64)ch3buf[s] * pan) >> 10; + } + break; + case 0x0C00: // channel 1+3 + { + s32 pan1 = Channels[1]->Pan; + s32 pan3 = Channels[3]->Pan; + for (u32 s = 0; s < samples; s++) + rightoutput[s] = (((s64)ch1buf[s] * pan1) >> 10) + (((s64)ch3buf[s] * pan3) >> 10); + } + break; + } + } + + for (u32 s = 0; s < samples; s++) + { + s32 l = leftoutput[s]; + s32 r = rightoutput[s]; + + l = ((s64)l * MasterVolume) >> 7; + r = ((s64)r * MasterVolume) >> 7; + + l >>= 8; + if (l < -0x8000) l = -0x8000; + else if (l > 0x7FFF) l = 0x7FFF; + r >>= 8; + if (r < -0x8000) r = -0x8000; + else if (r > 0x7FFF) r = 0x7FFF; + + OutputBuffer[OutputWriteOffset ] = l >> 1; + OutputBuffer[OutputWriteOffset + 1] = r >> 1; + OutputWriteOffset += 2; + OutputWriteOffset &= ((2*OutputBufferSize)-1); + } + + + NDS::ScheduleEvent(NDS::Event_SPU, true, 1024*16, Mix, 16); +} + + +void ReadOutput(s16* data, int samples) +{ + for (int i = 0; i < samples; i++) + { + *data++ = OutputBuffer[OutputReadOffset]; + *data++ = OutputBuffer[OutputReadOffset + 1]; + + if (OutputReadOffset != OutputWriteOffset) + { + OutputReadOffset += 2; + OutputReadOffset &= ((2*OutputBufferSize)-1); + } + } +} + + +u8 Read8(u32 addr) +{ + if (addr < 0x04000500) + { + Channel* chan = Channels[(addr >> 4) & 0xF]; + + switch (addr & 0xF) + { + case 0x0: return chan->Cnt & 0xFF; + case 0x1: return (chan->Cnt >> 8) & 0xFF; + case 0x2: return (chan->Cnt >> 16) & 0xFF; + case 0x3: return chan->Cnt >> 24; + } + } + else + { + switch (addr) + { + case 0x04000500: return Cnt & 0x7F; + case 0x04000501: return Cnt >> 8; + + case 0x04000508: return Capture[0]->Cnt; + case 0x04000509: return Capture[1]->Cnt; + } + } + + printf("unknown SPU read8 %08X\n", addr); + return 0; +} + +u16 Read16(u32 addr) +{ + if (addr < 0x04000500) + { + Channel* chan = Channels[(addr >> 4) & 0xF]; + + switch (addr & 0xF) + { + case 0x0: return chan->Cnt & 0xFFFF; + case 0x2: return chan->Cnt >> 16; + } + } + else + { + switch (addr) + { + case 0x04000500: return Cnt; + case 0x04000504: return Bias; + + case 0x04000508: return Capture[0]->Cnt | (Capture[1]->Cnt << 8); + } + } + + printf("unknown SPU read16 %08X\n", addr); + return 0; +} + +u32 Read32(u32 addr) +{ + if (addr < 0x04000500) + { + Channel* chan = Channels[(addr >> 4) & 0xF]; + + switch (addr & 0xF) + { + case 0x0: return chan->Cnt; + } + } + else + { + switch (addr) + { + case 0x04000500: return Cnt; + case 0x04000504: return Bias; + + case 0x04000508: return Capture[0]->Cnt | (Capture[1]->Cnt << 8); + + case 0x04000510: return Capture[0]->DstAddr; + case 0x04000518: return Capture[1]->DstAddr; + } + } + + printf("unknown SPU read32 %08X\n", addr); + return 0; +} + +void Write8(u32 addr, u8 val) +{ + if (addr < 0x04000500) + { + Channel* chan = Channels[(addr >> 4) & 0xF]; + + switch (addr & 0xF) + { + case 0x0: chan->SetCnt((chan->Cnt & 0xFFFFFF00) | val); return; + case 0x1: chan->SetCnt((chan->Cnt & 0xFFFF00FF) | (val << 8)); return; + case 0x2: chan->SetCnt((chan->Cnt & 0xFF00FFFF) | (val << 16)); return; + case 0x3: chan->SetCnt((chan->Cnt & 0x00FFFFFF) | (val << 24)); return; + } + } + else + { + switch (addr) + { + case 0x04000500: + Cnt = (Cnt & 0xBF00) | (val & 0x7F); + MasterVolume = Cnt & 0x7F; + if (MasterVolume == 127) MasterVolume++; + return; + case 0x04000501: + Cnt = (Cnt & 0x007F) | ((val & 0xBF) << 8); + return; + + case 0x04000508: + Capture[0]->SetCnt(val); + if (val & 0x03) printf("!! UNSUPPORTED SPU CAPTURE MODE %02X\n", val); + return; + case 0x04000509: + Capture[1]->SetCnt(val); + if (val & 0x03) printf("!! UNSUPPORTED SPU CAPTURE MODE %02X\n", val); + return; + } + } + + printf("unknown SPU write8 %08X %02X\n", addr, val); +} + +void Write16(u32 addr, u16 val) +{ + if (addr < 0x04000500) + { + Channel* chan = Channels[(addr >> 4) & 0xF]; + + switch (addr & 0xF) + { + case 0x0: chan->SetCnt((chan->Cnt & 0xFFFF0000) | val); return; + case 0x2: chan->SetCnt((chan->Cnt & 0x0000FFFF) | (val << 16)); return; + case 0x8: + chan->SetTimerReload(val); + if ((addr & 0xF0) == 0x10) Capture[0]->SetTimerReload(val); + else if ((addr & 0xF0) == 0x30) Capture[1]->SetTimerReload(val); + return; + case 0xA: chan->SetLoopPos(val); return; + + case 0xC: chan->SetLength((chan->Length & 0xFFFF0000) | val); return; + case 0xE: chan->SetLength((chan->Length & 0x0000FFFF) | (val << 16)); return; + } + } + else + { + switch (addr) + { + case 0x04000500: + Cnt = val & 0xBF7F; + MasterVolume = Cnt & 0x7F; + if (MasterVolume == 127) MasterVolume++; + return; + + case 0x04000504: + Bias = val & 0x3FF; + return; + + case 0x04000508: + Capture[0]->SetCnt(val & 0xFF); + Capture[1]->SetCnt(val >> 8); + if (val & 0x0303) printf("!! UNSUPPORTED SPU CAPTURE MODE %04X\n", val); + return; + + case 0x04000514: Capture[0]->SetLength(val); return; + case 0x0400051C: Capture[1]->SetLength(val); return; + } + } + + printf("unknown SPU write16 %08X %04X\n", addr, val); +} + +void Write32(u32 addr, u32 val) +{ + if (addr < 0x04000500) + { + Channel* chan = Channels[(addr >> 4) & 0xF]; + + switch (addr & 0xF) + { + case 0x0: chan->SetCnt(val); return; + case 0x4: chan->SetSrcAddr(val); return; + case 0x8: + chan->SetLoopPos(val >> 16); + val &= 0xFFFF; + chan->SetTimerReload(val); + if ((addr & 0xF0) == 0x10) Capture[0]->SetTimerReload(val); + else if ((addr & 0xF0) == 0x30) Capture[1]->SetTimerReload(val); + return; + case 0xC: chan->SetLength(val); return; + } + } + else + { + switch (addr) + { + case 0x04000500: + Cnt = val & 0xBF7F; + MasterVolume = Cnt & 0x7F; + if (MasterVolume == 127) MasterVolume++; + return; + + case 0x04000504: + Bias = val & 0x3FF; + return; + + case 0x04000508: + Capture[0]->SetCnt(val & 0xFF); + Capture[1]->SetCnt(val >> 8); + if (val & 0x0303) printf("!! UNSUPPORTED SPU CAPTURE MODE %04X\n", val); + return; + + case 0x04000510: Capture[0]->SetDstAddr(val); return; + case 0x04000514: Capture[0]->SetLength(val & 0xFFFF); return; + case 0x04000518: Capture[1]->SetDstAddr(val); return; + case 0x0400051C: Capture[1]->SetLength(val & 0xFFFF); return; + } + } +} + +} |