/* * SSEQ Player - Player structure * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com] * Last modification on 2014-10-23 * * Adapted from source code of FeOS Sound System * By fincs * https://github.com/fincs/FSS */ #include "Player.h" #include "common.h" #if (defined(__GNUC__) || defined(__clang__)) && !defined(_LIBCPP_VERSION) std::locale::id std::codecvt::id; std::locale::id std::codecvt::id; #endif Player::Player() : prio(0), nTracks(0), tempo(0), tempoCount(0), tempoRate(0), masterVol(0), sseqVol(0), sseq(nullptr), sampleRate(0), interpolation(INTERPOLATION_NONE) { memset(this->trackIds, 0, sizeof(this->trackIds)); for (size_t i = 0; i < 16; ++i) { this->channels[i].chnId = i; this->channels[i].ply = this; } memset(this->variables, -1, sizeof(this->variables)); } // Original FSS Function: Player_Setup bool Player::Setup(const SSEQ *sseqToPlay) { this->sseq = sseqToPlay; int firstTrack = this->TrackAlloc(); if (firstTrack == -1) return false; this->tracks[firstTrack].Init(firstTrack, this, nullptr, 0); this->nTracks = 1; this->trackIds[0] = firstTrack; this->tracks[firstTrack].startPos = this->tracks[firstTrack].pos = &this->sseq->data[0]; this->secondsPerSample = 1.0 / this->sampleRate; this->ClearState(); return true; } // Original FSS Function: Player_ClearState void Player::ClearState() { this->tempo = 120; this->tempoCount = 0; this->tempoRate = 0x100; this->masterVol = 0; // this is actually the highest level memset(this->variables, -1, sizeof(this->variables)); this->secondsIntoPlayback = 0; this->secondsUntilNextClock = SecondsPerClockCycle; } // Original FSS Function: Player_FreeTracks void Player::FreeTracks() { for (uint8_t i = 0; i < this->nTracks; ++i) this->tracks[this->trackIds[i]].Free(); this->nTracks = 0; } // Original FSS Function: Player_Stop void Player::Stop(bool bKillSound) { this->ClearState(); for (uint8_t i = 0; i < this->nTracks; ++i) { uint8_t trackId = this->trackIds[i]; this->tracks[trackId].ClearState(); for (int j = 0; j < 16; ++j) { Channel &chn = this->channels[j]; if (chn.state != CS_NONE && chn.trackId == trackId) { if (bKillSound) chn.Kill(); else chn.Release(); } } } this->FreeTracks(); } // Original FSS Function: Chn_Alloc int Player::ChannelAlloc(int type, int priority) { static const uint8_t pcmChnArray[] = { 4, 5, 6, 7, 2, 0, 3, 1, 8, 9, 10, 11, 14, 12, 15, 13 }; static const uint8_t psgChnArray[] = { 8, 9, 10, 11, 12, 13 }; static const uint8_t noiseChnArray[] = { 14, 15 }; static const uint8_t arraySizes[] = { sizeof(pcmChnArray), sizeof(psgChnArray), sizeof(noiseChnArray) }; static const uint8_t *const arrayArray[] = { pcmChnArray, psgChnArray, noiseChnArray }; auto chnArray = arrayArray[type]; int arraySize = arraySizes[type]; int curChnNo = -1; for (int i = 0; i < arraySize; ++i) { int thisChnNo = chnArray[i]; Channel &thisChn = this->channels[thisChnNo]; Channel &curChn = this->channels[curChnNo]; if (curChnNo != -1 && thisChn.prio >= curChn.prio) { if (thisChn.prio != curChn.prio) continue; if (curChn.vol <= thisChn.vol) continue; } curChnNo = thisChnNo; } if (curChnNo == -1 || priority < this->channels[curChnNo].prio) return -1; this->channels[curChnNo].noteLength = -1; this->channels[curChnNo].vol = 0x7FF; this->channels[curChnNo].clearHistory(); return curChnNo; } // Original FSS Function: Track_Alloc int Player::TrackAlloc() { for (int i = 0; i < FSS_MAXTRACKS; ++i) { Track &thisTrk = this->tracks[i]; if (!thisTrk.state[TS_ALLOCBIT]) { thisTrk.Zero(); thisTrk.state.set(TS_ALLOCBIT); thisTrk.updateFlags.reset(); return i; } } return -1; } // Original FSS Function: Player_Run void Player::Run() { while (this->tempoCount > 240) { this->tempoCount -= 240; for (uint8_t i = 0; i < this->nTracks; ++i) this->tracks[this->trackIds[i]].Run(); } this->tempoCount += (static_cast(this->tempo) * static_cast(this->tempoRate)) >> 8; } void Player::UpdateTracks() { for (int i = 0; i < 16; ++i) this->channels[i].UpdateTrack(); for (int i = 0; i < FSS_MAXTRACKS; ++i) this->tracks[i].updateFlags.reset(); } // Original FSS Function: Snd_Timer void Player::Timer() { this->UpdateTracks(); for (int i = 0; i < 16; ++i) this->channels[i].Update(); this->Run(); } static inline int32_t muldiv7(int32_t val, uint8_t mul) { return mul == 127 ? val : ((val * mul) >> 7); } void Player::GenerateSamples(std::vector &buf, unsigned offset, unsigned samples) { unsigned long mute = this->mutes.to_ulong(); for (unsigned smpl = 0; smpl < samples; ++smpl) { this->secondsIntoPlayback += this->secondsPerSample; int32_t leftChannel = 0, rightChannel = 0; // I need to advance the sound channels here for (int i = 0; i < 16; ++i) { Channel &chn = this->channels[i]; if (chn.state > CS_NONE) { int32_t sample = chn.GenerateSample(); chn.IncrementSample(); if (mute & BIT(i)) continue; uint8_t datashift = chn.reg.volumeDiv; if (datashift == 3) datashift = 4; sample = muldiv7(sample, chn.reg.volumeMul) >> datashift; leftChannel += muldiv7(sample, 127 - chn.reg.panning); rightChannel += muldiv7(sample, chn.reg.panning); } } clamp(leftChannel, -0x8000, 0x7FFF); clamp(rightChannel, -0x8000, 0x7FFF); buf[offset++] = leftChannel & 0xFF; buf[offset++] = (leftChannel >> 8) & 0xFF; buf[offset++] = rightChannel & 0xFF; buf[offset++] = (rightChannel >> 8) & 0xFF; if (this->secondsIntoPlayback > this->secondsUntilNextClock) { this->Timer(); this->secondsUntilNextClock += SecondsPerClockCycle; } } }