diff options
author | Filippos Karapetis | 2013-11-01 14:53:43 +0200 |
---|---|---|
committer | Filippos Karapetis | 2013-11-01 19:26:51 +0200 |
commit | 4f807ee53e8a5a8f92b234e405337aecf9c77afc (patch) | |
tree | e65792d89d5c191e75dacab9ab71e3b25ec1fd9e /engines/scumm/players/player_nes.cpp | |
parent | 79ecd017e24515e5d9a77e32110a932c7d04b582 (diff) | |
download | scummvm-rg350-4f807ee53e8a5a8f92b234e405337aecf9c77afc.tar.gz scummvm-rg350-4f807ee53e8a5a8f92b234e405337aecf9c77afc.tar.bz2 scummvm-rg350-4f807ee53e8a5a8f92b234e405337aecf9c77afc.zip |
SCUMM: More renaming for players
Rename "player" directory to "players", and reintroduce the "player_"
file prefix. This has been done after sev's request.
"players/player_foo.*" is more descriptive, and avoids potential name
clashes in libraries
Diffstat (limited to 'engines/scumm/players/player_nes.cpp')
-rw-r--r-- | engines/scumm/players/player_nes.cpp | 1067 |
1 files changed, 1067 insertions, 0 deletions
diff --git a/engines/scumm/players/player_nes.cpp b/engines/scumm/players/player_nes.cpp new file mode 100644 index 0000000000..f55f1f9edd --- /dev/null +++ b/engines/scumm/players/player_nes.cpp @@ -0,0 +1,1067 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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 + * aint32 with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef DISABLE_NES_APU + +#include "engines/engine.h" +#include "scumm/players/player_nes.h" +#include "scumm/scumm.h" +#include "audio/mixer.h" + +namespace Scumm { + +static const byte channelMask[4] = {1, 2, 4, 8}; + +static const uint16 freqTable[64] = { + 0x07F0, 0x077E, 0x0712, 0x06AE, 0x064E, 0x05F3, 0x059E, 0x054D, + 0x0501, 0x04B9, 0x0475, 0x0435, 0x03F8, 0x03BF, 0x0389, 0x0357, + 0x0327, 0x02F9, 0x02CF, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, + 0x01FC, 0x01DF, 0x01C4, 0x01AB, 0x0193, 0x017C, 0x0167, 0x0152, + 0x013F, 0x012D, 0x011C, 0x010C, 0x00FD, 0x00EE, 0x00E1, 0x00D4, + 0x00C8, 0x00BD, 0x00B2, 0x00A8, 0x009F, 0x0096, 0x008D, 0x0085, + 0x007E, 0x0076, 0x0070, 0x0069, 0x0063, 0x005E, 0x0058, 0x0053, + 0x004F, 0x004A, 0x0046, 0x0042, 0x003E, 0x003A, 0x0037, 0x0034 +}; + +static const byte instChannel[16] = { + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 1, 3, 3, 3 +}; +static const byte startCmd[16] = { + 0x05, 0x03, 0x06, 0x08, 0x0B, 0x01, 0x01, 0x1A, + 0x16, 0x06, 0x04, 0x17, 0x02, 0x10, 0x0E, 0x0D +}; +static const byte releaseCmd[16] = { + 0x0F, 0x00, 0x00, 0x09, 0x00, 0x14, 0x15, 0x00, + 0x00, 0x00, 0x1B, 0x1B, 0x0F, 0x0F, 0x0F, 0x0F +}; +static const byte nextCmd[28] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0x17, 0xFF, 0x07, 0xFF, + 0xFF, 0x0A, 0x09, 0x0C, 0x00, 0x00, 0x00, 0x00, + 0x11, 0x12, 0x11, 0x03, 0xFF, 0xFF, 0x18, 0x00, + 0x19, 0x00, 0x00, 0x00 +}; +static const byte nextDelay[28] = { + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x05, 0x08, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x00, 0x00 +}; + +namespace APUe { + +static const byte LengthCounts[32] = { + 0x0A,0xFE, + 0x14,0x02, + 0x28,0x04, + 0x50,0x06, + 0xA0,0x08, + 0x3C,0x0A, + 0x0E,0x0C, + 0x1A,0x0E, + + 0x0C,0x10, + 0x18,0x12, + 0x30,0x14, + 0x60,0x16, + 0xC0,0x18, + 0x48,0x1A, + 0x10,0x1C, + 0x20,0x1E +}; + +class SoundGen { +protected: + byte wavehold; + uint32 freq; // short + uint32 CurD; + +public: + byte Timer; + int32 Pos; + uint32 Cycles; // short + + inline byte GetTimer() const { return Timer; } +}; + +class Square : public SoundGen { +protected: + byte volume, envelope, duty, swpspeed, swpdir, swpstep, swpenab; + byte Vol; + byte EnvCtr, Envelope, BendCtr; + bool Enabled, ValidFreq, Active; + bool EnvClk, SwpClk; + + void CheckActive(); + +public: + void Reset(); + void Write(int Reg, byte Val); + void Run(); + void QuarterFrame(); + void HalfFrame(); +}; + +static const int8 Duties[4][8] = { + {-4,+4,-4,-4,-4,-4,-4,-4}, + {-4,+4,+4,-4,-4,-4,-4,-4}, + {-4,+4,+4,+4,+4,-4,-4,-4}, + {+4,-4,-4,+4,+4,+4,+4,+4} +}; + +void Square::Reset() { + memset(this, 0, sizeof(*this)); + Cycles = 1; + EnvCtr = 1; + BendCtr = 1; +} + +void Square::CheckActive() { + ValidFreq = (freq >= 0x8) && ((swpdir) || !((freq + (freq >> swpstep)) & 0x800)); + Active = Timer && ValidFreq; + Pos = Active ? (Duties[duty][CurD] * Vol) : 0; +} + +void Square::Write(int Reg, byte Val) { + switch (Reg) { + case 0: + volume = Val & 0xF; + envelope = Val & 0x10; + wavehold = Val & 0x20; + duty = (Val >> 6) & 0x3; + Vol = envelope ? volume : Envelope; + break; + + case 1: + swpstep = Val & 0x07; + swpdir = Val & 0x08; + swpspeed = (Val >> 4) & 0x7; + swpenab = Val & 0x80; + SwpClk = true; + break; + + case 2: + freq &= 0x700; + freq |= Val; + break; + + case 3: + freq &= 0xFF; + freq |= (Val & 0x7) << 8; + + if (Enabled) + Timer = LengthCounts[(Val >> 3) & 0x1F]; + + CurD = 0; + EnvClk = true; + break; + + case 4: + Enabled = (Val != 0); + if (!Enabled) + Timer = 0; + break; + } + CheckActive(); +} + +void Square::Run() { + Cycles = (freq + 1) << 1; + CurD = (CurD + 1) & 0x7; + + if (Active) + Pos = Duties[duty][CurD] * Vol; +} + +void Square::QuarterFrame() { + if (EnvClk) { + EnvClk = false; + Envelope = 0xF; + EnvCtr = volume + 1; + } else if (!--EnvCtr) { + EnvCtr = volume + 1; + + if (Envelope) + Envelope--; + else + Envelope = wavehold ? 0xF : 0x0; + } + + Vol = envelope ? volume : Envelope; + CheckActive(); +} + +void Square::HalfFrame() { + if (!--BendCtr) { + BendCtr = swpspeed + 1; + + if (swpenab && swpstep && ValidFreq) { + int sweep = freq >> swpstep; + // FIXME: Is -sweep or ~sweep correct??? + freq += swpdir ? -sweep : sweep; + } + } + + if (SwpClk) { + SwpClk = false; + BendCtr = swpspeed + 1; + } + + if (Timer && !wavehold) + Timer--; + + CheckActive(); +} + + +class Triangle : public SoundGen { +protected: + byte linear; + byte LinCtr; + bool Enabled, Active; + bool LinClk; + + void CheckActive(); + +public: + void Reset(); + void Write(int Reg, byte Val); + void Run(); + void QuarterFrame(); + void HalfFrame(); +}; + +static const int8 TriDuty[32] = { + -8,-7,-6,-5,-4,-3,-2,-1, + +0,+1,+2,+3,+4,+5,+6,+7, + +7,+6,+5,+4,+3,+2,+1,+0, + -1,-2,-3,-4,-5,-6,-7,-8 +}; + +void Triangle::Reset() { + memset(this, 0, sizeof(*this)); + Cycles = 1; +} + +void Triangle::CheckActive() { + Active = Timer && LinCtr; + + if (freq < 4) + Pos = 0; // beyond hearing range + else + Pos = TriDuty[CurD] * 8; +} + +void Triangle::Write(int Reg, byte Val) { + switch (Reg) { + case 0: + linear = Val & 0x7F; + wavehold = (Val >> 7) & 0x1; + break; + + case 2: + freq &= 0x700; + freq |= Val; + break; + + case 3: + freq &= 0xFF; + freq |= (Val & 0x7) << 8; + + if (Enabled) + Timer = LengthCounts[(Val >> 3) & 0x1F]; + + LinClk = true; + break; + + case 4: + Enabled = (Val != 0); + if (!Enabled) + Timer = 0; + break; + } + CheckActive(); +} + +void Triangle::Run() { + Cycles = freq + 1; + + if (Active) { + CurD++; + CurD &= 0x1F; + + if (freq < 4) + Pos = 0; // beyond hearing range + else + Pos = TriDuty[CurD] * 8; + } +} + +void Triangle::QuarterFrame() { + if (LinClk) + LinCtr = linear; + else if (LinCtr) + LinCtr--; + + if (!wavehold) + LinClk = false; + + CheckActive(); +} + +void Triangle::HalfFrame() { + if (Timer && !wavehold) + Timer--; + + CheckActive(); +} + +class Noise : public SoundGen { +protected: + byte volume, envelope, datatype; + byte Vol; + byte EnvCtr, Envelope; + bool Enabled; + bool EnvClk; + + void CheckActive(); + +public: + void Reset(); + void Write(int Reg, byte Val); + void Run(); + void QuarterFrame(); + void HalfFrame(); +}; + +static const uint32 NoiseFreq[16] = { + 0x004,0x008,0x010,0x020,0x040,0x060,0x080,0x0A0, + 0x0CA,0x0FE,0x17C,0x1FC,0x2FA,0x3F8,0x7F2,0xFE4 +}; + +void Noise::Reset() { + memset(this, 0, sizeof(*this)); + CurD = 1; + Cycles = 1; + EnvCtr = 1; + +} + +void Noise::Write(int Reg, byte Val) { + switch (Reg) { + case 0: + volume = Val & 0x0F; + envelope = Val & 0x10; + wavehold = Val & 0x20; + Vol = envelope ? volume : Envelope; + + if (Timer) + Pos = ((CurD & 0x4000) ? -2 : 2) * Vol; + break; + + case 2: + freq = Val & 0xF; + datatype = Val & 0x80; + break; + + case 3: + if (Enabled) + Timer = LengthCounts[(Val >> 3) & 0x1F]; + + EnvClk = true; + break; + + case 4: + Enabled = (Val != 0); + if (!Enabled) + Timer = 0; + break; + } +} + +void Noise::Run() { + Cycles = NoiseFreq[freq]; /* no + 1 here */ + + if (datatype) + CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 8)) & 0x1); + else + CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 13)) & 0x1); + + if (Timer) + Pos = ((CurD & 0x4000) ? -2 : 2) * Vol; +} + +void Noise::QuarterFrame() { + if (EnvClk) { + EnvClk = false; + Envelope = 0xF; + EnvCtr = volume + 1; + } else if (!--EnvCtr) { + EnvCtr = volume + 1; + + if (Envelope) + Envelope--; + else + Envelope = wavehold ? 0xF : 0x0; + } + + Vol = envelope ? volume : Envelope; + + if (Timer) + Pos = ((CurD & 0x4000) ? -2 : 2) * Vol; +} + +void Noise::HalfFrame() { + if (Timer && !wavehold) + Timer--; +} + +class APU { +protected: + int BufPos; + int SampleRate; + + Square _square0; + Square _square1; + Triangle _triangle; + Noise _noise; + + struct { + uint32 Cycles; + int Num; + } Frame; + +public: + APU(int rate) : SampleRate(rate) { + Reset(); + } + + void WriteReg(int Addr, byte Val); + byte Read4015(); + void Reset (); + int16 GetSample(); +}; + +void APU::WriteReg(int Addr, byte Val) { + switch (Addr) { + case 0x000: _square0.Write(0,Val); break; + case 0x001: _square0.Write(1,Val); break; + case 0x002: _square0.Write(2,Val); break; + case 0x003: _square0.Write(3,Val); break; + case 0x004: _square1.Write(0,Val); break; + case 0x005: _square1.Write(1,Val); break; + case 0x006: _square1.Write(2,Val); break; + case 0x007: _square1.Write(3,Val); break; + case 0x008: _triangle.Write(0,Val); break; + case 0x009: _triangle.Write(1,Val); break; + case 0x00A: _triangle.Write(2,Val); break; + case 0x00B: _triangle.Write(3,Val); break; + case 0x00C: _noise.Write(0,Val); break; + case 0x00D: _noise.Write(1,Val); break; + case 0x00E: _noise.Write(2,Val); break; + case 0x00F: _noise.Write(3,Val); break; + case 0x015: _square0.Write(4,Val & 0x1); + _square1.Write(4,Val & 0x2); + _triangle.Write(4,Val & 0x4); + _noise.Write(4,Val & 0x8); + break; + } +} + +byte APU::Read4015() { + byte result = + (( _square0.GetTimer()) ? 0x01 : 0) | + (( _square1.GetTimer()) ? 0x02 : 0) | + ((_triangle.GetTimer()) ? 0x04 : 0) | + (( _noise.GetTimer()) ? 0x08 : 0); + return result; +} + +void APU::Reset () { + BufPos = 0; + + _square0.Reset(); + _square1.Reset(); + _triangle.Reset(); + _noise.Reset(); + + Frame.Num = 0; + Frame.Cycles = 1; +} + +template<class T> +int step(T &obj, int sampcycles, uint frame_Cycles, int frame_Num) { + int samppos = 0; + while (sampcycles) { + // Compute the maximal amount we can step ahead before triggering + // an action (i.e. compute the minimum of sampcycles, frame_Cycles + // and obj.Cycles). + uint max_step = sampcycles; + if (max_step > frame_Cycles) + max_step = frame_Cycles; + if (max_step > obj.Cycles) + max_step = obj.Cycles; + + // During all but the last of these steps, we just add the value of obj.Pos + // to samppos -- so we can to that all at once with a simple multiplication: + samppos += obj.Pos * (max_step - 1); + + // Now step ahead... + sampcycles -= max_step; + frame_Cycles -= max_step; + obj.Cycles -= max_step; + + if (!frame_Cycles) { + frame_Cycles = 7457; + + if (frame_Num < 4) { + obj.QuarterFrame(); + + if (frame_Num & 1) + frame_Cycles++; + else + obj.HalfFrame(); + + frame_Num++; + } else + frame_Num = 0; + } + + if (!obj.Cycles) + obj.Run(); + + samppos += obj.Pos; + } + + return samppos; +} + +int16 APU::GetSample() { + int samppos = 0; + + const int sampcycles = 1+(1789773-BufPos-1)/SampleRate; + BufPos = BufPos + sampcycles * SampleRate - 1789773; + + samppos += step( _square0, sampcycles, Frame.Cycles, Frame.Num); + samppos += step( _square1, sampcycles, Frame.Cycles, Frame.Num); + samppos += step(_triangle, sampcycles, Frame.Cycles, Frame.Num); + samppos += step( _noise, sampcycles, Frame.Cycles, Frame.Num); + + uint tmp = sampcycles; + while (tmp >= Frame.Cycles) { + tmp -= Frame.Cycles; + Frame.Cycles = 7457; + + if (Frame.Num < 4) { + if (Frame.Num & 1) + Frame.Cycles++; + Frame.Num++; + } else + Frame.Num = 0; + } + + Frame.Cycles -= tmp; + + return (samppos << 6) / sampcycles; +} + +} // End of namespace APUe + +Player_NES::Player_NES(ScummEngine *scumm, Audio::Mixer *mixer) { + int i; + _vm = scumm; + _mixer = mixer; + _sampleRate = _mixer->getOutputRate(); + _apu = new APUe::APU(_sampleRate); + + _samples_per_frame = _sampleRate / 60; + _current_sample = 0; + + for (i = 0; i < NUMSLOTS; i++) { + _slot[i].id = -1; + _slot[i].framesleft = 0; + _slot[i].type = 0; + _slot[i].offset = 0; + _slot[i].data = NULL; + } + + for (i = 0; i < NUMCHANS; i++) { + _mchan[i].command = 0; + _mchan[i].framedelay = 0; + _mchan[i].pitch = 0; + _mchan[i].volume = 0; + _mchan[i].voldelta = 0; + _mchan[i].envflags = 0; + _mchan[i].cmdlock = 0; + } + isSFXplaying = wasSFXplaying = false; + + auxData1 = auxData2 = NULL; + numNotes = 0; + + APU_writeControl(0); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_NES::~Player_NES() { + _mixer->stopHandle(_soundHandle); + delete _apu; +} + +void Player_NES::setMusicVolume (int vol) { + _maxvol = vol; +} + +int Player_NES::readBuffer(int16 *buffer, const int numSamples) { + for (int n = 0; n < numSamples; n++) { + buffer[n] = _apu->GetSample() * _maxvol / 255; + _current_sample++; + + if (_current_sample == _samples_per_frame) { + _current_sample = 0; + sound_play(); + } + } + return numSamples; +} +void Player_NES::stopAllSounds() { + for (int i = 0; i < NUMSLOTS; i++) { + _slot[i].framesleft = 0; + _slot[i].type = 0; + _slot[i].id = -1; + } + + isSFXplaying = 0; + checkSilenceChannels(0); +} + +void Player_NES::stopSound(int nr) { + if (nr == -1) + return; + + for (int i = 0; i < NUMSLOTS; i++) { + if (_slot[i].id != nr) + continue; + + isSFXplaying = 0; + _slot[i].framesleft = 0; + _slot[i].type = 0; + _slot[i].id = -1; + checkSilenceChannels(i); + } +} + +void Player_NES::startSound(int nr) { + byte *data = _vm->getResourceAddress(rtSound, nr) + 2; + assert(data); + + int soundType = data[1]; + int chan = data[0]; + + if (chan == 4) { + if (_slot[2].framesleft) + return; + chan = 0; + } + + if (soundType < _slot[chan].type) + return; + + _slot[chan].type = soundType; + _slot[chan].id = nr; + _slot[chan].data = data; + _slot[chan].offset = 2; + _slot[chan].framesleft = 1; + checkSilenceChannels(chan); + if (chan == 2) { + numNotes = _slot[chan].data[2]; + auxData1 = _slot[chan].data + 3; + auxData2 = auxData1 + numNotes; + _slot[chan].data = auxData2 + numNotes; + _slot[chan].offset = 0; + + for (int i = 0; i < NUMCHANS; i++) + _mchan[i].cmdlock = 0; + } +} + +void Player_NES::checkSilenceChannels(int chan) { + for (chan--; chan >= 0; chan--) { + if (_slot[chan].framesleft) + return; + } + APU_writeControl(0); +} + +void Player_NES::sound_play() { + if (_slot[0].framesleft) + playSFX(0); + else if (_slot[1].framesleft) + playSFX(1); + + playMusic(); +} + +void Player_NES::playSFX (int nr) { + if (--_slot[nr].framesleft) + return; + + while (1) { + int a = _slot[nr].data[_slot[nr].offset++]; + if (a < 16) { + a >>= 2; + APU_writeControl(APU_readStatus() | channelMask[a]); + isSFXplaying = true; + APU_writeChannel(a, 0, _slot[nr].data[_slot[nr].offset++]); + APU_writeChannel(a, 1, _slot[nr].data[_slot[nr].offset++]); + APU_writeChannel(a, 2, _slot[nr].data[_slot[nr].offset++]); + APU_writeChannel(a, 3, _slot[nr].data[_slot[nr].offset++]); + } else if (a == 0xFE) { + _slot[nr].offset = 2; + } else if (a == 0xFF) { + _slot[nr].id = -1; + _slot[nr].type = 0; + isSFXplaying = false; + APU_writeControl(0); + + if (!nr && _slot[1].framesleft) { + _slot[1].framesleft = 1; + isSFXplaying = true; + } + return; + } else { + _slot[nr].framesleft = _slot[nr].data[_slot[nr].offset++]; + return; + } + } +} + +void Player_NES::playMusic() { + if (!_slot[2].framesleft) + return; + + if (wasSFXplaying && !isSFXplaying) + for (int x = 1; x >= 0; x--) + if (_mchan[x].cmdlock) { + _mchan[x].command = _mchan[x].cmdlock; + _mchan[x].framedelay = 1; + } + + wasSFXplaying = isSFXplaying; + if (!--_slot[2].framesleft) { +top: + int b = _slot[2].data[_slot[2].offset++]; + if (b == 0xFF) { + _slot[2].id = -1; + _slot[2].type = 0; + b = 0; + } else if (b == 0xFE) { + _slot[2].offset = 0; + goto top; + } else { + if (b < numNotes) { + int inst = auxData1[b]; + int ch = instChannel[inst]; + _mchan[ch].pitch = auxData2[b]; + _mchan[ch].cmdlock = startCmd[inst]; + _mchan[ch].command = startCmd[inst]; + _mchan[ch].framedelay = 1; + goto top; + } + b -= numNotes; + if (b < 16) { + int inst = b; + int ch = instChannel[inst]; + _mchan[ch].cmdlock = 0; + _mchan[ch].command = releaseCmd[inst]; + _mchan[ch].framedelay = 1; + goto top; + } + b -= 16; + } + _slot[2].framesleft = b; + } + + for (int x = NUMCHANS - 1; x >= 0; x--) { + if (_slot[0].framesleft || _slot[1].framesleft) { + _mchan[x].volume = 0; + _mchan[x].framedelay = 0; + continue; + } + + if (_mchan[x].framedelay && !--_mchan[x].framedelay) { + switch (_mchan[x].command) { + case 0x00: + case 0x13: + _mchan[x].voldelta = -10; + break; + + case 0x01: + case 0x03: + case 0x08: + case 0x16: + _mchan[x].envflags = 0x30; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x02: + _mchan[x].envflags = 0xB0; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x84); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x04: + _mchan[x].envflags = 0x80; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x05: + _mchan[x].envflags = 0xF0; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = -15; + + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x06: + _mchan[x].pitch += 0x18; + _mchan[x].envflags = 0x80; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x07: + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch - 0x0C] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch - 0x0C] >> 8); + + chainCommand(x); + break; + + case 0x09: + _mchan[x].voldelta = -2; + + APU_writeChannel(x, 1, 0x7F); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x0A: + APU_writeChannel(x, 1, 0x86); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x0B: case 0x1A: + _mchan[x].envflags = 0x70; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x0C: + _mchan[x].envflags = 0xB0; + + chainCommand(x); + break; + + case 0x0D: + _mchan[x].envflags = 0x30; + _mchan[x].volume = 0x5F; + _mchan[x].voldelta = -22; + + APU_writeChannel(x, 0, 0x00); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, _mchan[x].pitch & 0xF); + APU_writeChannel(x, 3, 0xFF); + + chainCommand(x); + break; + + case 0x0E: + case 0x10: + _mchan[x].envflags = 0x30; + _mchan[x].volume = 0x5F; + _mchan[x].voldelta = -6; + + APU_writeChannel(x, 0, 0x00); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, _mchan[x].pitch & 0xF); + APU_writeChannel(x, 3, 0xFF); + + chainCommand(x); + break; + + case 0x0F: + chainCommand(x); + break; + + case 0x11: + APU_writeChannel(x, 2, _mchan[x].pitch & 0xF); + APU_writeChannel(x, 3, 0xFF); + + chainCommand(x); + break; + + case 0x12: + APU_writeChannel(x, 2, (_mchan[x].pitch + 3) & 0xF); + APU_writeChannel(x, 3, 0xFF); + + chainCommand(x); + break; + + case 0x14: + _mchan[x].voldelta = -12; + + APU_writeChannel(x, 1, 0x8C); + + chainCommand(x); + break; + + case 0x15: + _mchan[x].voldelta = -12; + + APU_writeChannel(x, 1, 0x84); + + chainCommand(x); + break; + + case 0x17: + _mchan[x].pitch += 0x0C; + _mchan[x].envflags = 0x80; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x18: + _mchan[x].envflags = 0x70; + + chainCommand(x); + break; + + case 0x19: + _mchan[x].envflags = 0xB0; + + chainCommand(x); + break; + + case 0x1B: + _mchan[x].envflags = 0x00; + _mchan[x].voldelta = -10; + break; + } + } + + _mchan[x].volume += _mchan[x].voldelta; + + if (_mchan[x].volume < 0) + _mchan[x].volume = 0; + if (_mchan[x].volume > MAXVOLUME) + _mchan[x].volume = MAXVOLUME; + + APU_writeChannel(x, 0, (_mchan[x].volume >> 3) | _mchan[x].envflags); + } +} + +void Player_NES::chainCommand(int c) { + int i = _mchan[c].command; + _mchan[c].command = nextCmd[i]; + _mchan[c].framedelay = nextDelay[i]; +} + +int Player_NES::getSoundStatus(int nr) const { + for (int i = 0; i < NUMSLOTS; i++) + if (_slot[i].id == nr) + return 1; + return 0; +} + +void Player_NES::APU_writeChannel(int chan, int offset, byte value) { + _apu->WriteReg(0x000 + 4 * chan + offset, value); +} +void Player_NES::APU_writeControl(byte value) { + _apu->WriteReg(0x015, value); +} +byte Player_NES::APU_readStatus() { + return _apu->Read4015(); +} + +} // End of namespace Scumm + +#endif // DISABLE_NES_APU |