/* * SSEQ Player - Channel structures * By Naram Qashat (CyberBotX) [cyberbotx@cyberbotx.com] * * Adapted from source code of FeOS Sound System * By fincs * https://github.com/fincs/FSS * * Some code/concepts from DeSmuME * http://desmume.org/ */ #pragma once #include #include #include #include #include "common.h" #include "consts.h" struct SWAV; struct Track; /* * This structure is meant to be similar to what is stored in the actual * Nintendo DS's sound registers. Items that were not being used by this * player have been removed, and items which help the simulated registers * have been added. */ struct NDSSoundRegister { // Control Register std::uint8_t volumeMul; std::uint8_t volumeDiv; std::uint8_t panning; std::uint8_t waveDuty; std::uint8_t repeatMode; std::uint8_t format; bool enable; // Data Source Register const SWAV *source; // Timer Register std::uint16_t timer; // PSG Handling, not a DS register std::uint16_t psgX; std::int16_t psgLast; std::uint32_t psgLastCount; // The following are taken from DeSmuME double samplePosition; double sampleIncrease; // Loopstart Register std::uint32_t loopStart; // Length Register std::uint32_t length; std::uint32_t totalLength; NDSSoundRegister(); void ClearControlRegister(); void SetControlRegister(std::uint32_t reg); }; /* * From FeOS Sound System, this is temporary storage of what will go into * the Nintendo DS sound registers. It is kept separate as the original code * from FeOS Sound System utilized this to hold data prior to passing it into * the DS's registers. */ struct TempSndReg { std::uint32_t CR; const SWAV *SOURCE; std::uint16_t TIMER; std::uint32_t REPEAT_POINT, LENGTH; TempSndReg(); }; struct Player; /* * This creates a ring buffer, which will store N samples of SWAV * data, duplicated. The way it is duplicated is done as follows: * the samples are stored in the center of the buffer, and on both * sides is half of the data, the first half being after the data * and the second half begin before the data. This in essence * mirrors the data while allowing a pointer to always be retrieved * and no extra copies of the buffer are created. Part of the idea * for this came from kode54's original buffer implementation, but * this has been designed to make sure that there are no delays in * accessing the SWAVs samples and also doesn't use 0s before the * start of the SWAV or use 0s after the end of a non-looping SWAV. */ template struct RingBuffer { std::int16_t buffer[N * 2]; std::size_t bufferPos, getPos; RingBuffer() : bufferPos(N / 2), getPos(N / 2) { std::fill_n(&this->buffer[0], N * 2, 0); } void Clear() { std::fill_n(&this->buffer[0], N * 2, 0); this->bufferPos = this->getPos = N / 2; } void PushSample(std::int16_t sample) { this->buffer[this->bufferPos] = sample; if (this->bufferPos >= N) this->buffer[this->bufferPos - N] = sample; else this->buffer[this->bufferPos + N] = sample; ++this->bufferPos; if (this->bufferPos >= N * 3 / 2) this->bufferPos -= N; } void PushSamples(const std::int16_t *samples, std::size_t size) { if (this->bufferPos + size > N * 3 / 2) { std::size_t free = N * 3 / 2 - this->bufferPos; std::copy_n(&samples[0], free, &this->buffer[this->bufferPos]); std::copy(&samples[free], &samples[size], &this->buffer[N / 2]); } else std::copy_n(&samples[0], size, &this->buffer[this->bufferPos]); std::size_t rightFree = this->bufferPos < N ? N - this->bufferPos : 0; if (rightFree < size) { if (!rightFree) { std::size_t leftStart = this->bufferPos - N; std::size_t leftSize = std::min(N / 2 - leftStart, size); std::copy_n(&samples[0], leftSize, &this->buffer[leftStart]); if (leftSize < size) std::copy(&samples[leftSize], &samples[size], &this->buffer[N * 3 / 2]); } else { std::copy_n(&samples[0], rightFree, &this->buffer[this->bufferPos + N]); std::copy(&samples[rightFree], &samples[size], &this->buffer[0]); } } else std::copy_n(&samples[0], size, &this->buffer[this->bufferPos + N]); this->bufferPos += size; if (this->bufferPos >= N * 3 / 2) this->bufferPos -= N; } const std::int16_t *GetBuffer() const { return &this->buffer[this->getPos]; } void NextSample() { ++this->getPos; if (this->getPos >= N * 3 / 2) this->getPos -= N; } }; struct Channel { std::int8_t chnId; TempSndReg tempReg; ChannelState state; std::int8_t trackId; // -1 = none std::uint8_t prio; bool manualSweep; std::bitset flags; std::int8_t pan; // -64 .. 63 std::int16_t extAmpl; std::int16_t velocity; std::int8_t extPan; std::uint8_t key; int ampl; // 7 fractionary bits int extTune; // in 64ths of a semitone std::uint8_t orgKey; std::uint8_t modType, modSpeed, modDepth, modRange; std::uint16_t modDelay, modDelayCnt, modCounter; std::uint32_t sweepLen, sweepCnt; std::int16_t sweepPitch; std::uint8_t attackLvl, sustainLvl; std::uint16_t decayRate, releaseRate; /* * These were originally global variables in FeOS Sound System, but * since they were linked to a certain channel anyways, I moved them * into this class. */ int noteLength; std::uint16_t vol; const Player *ply; NDSSoundRegister reg; /* * Lookup tables for the Sinc interpolation, to * avoid the need to call the sin/cos functions all the time. * These are static as they will not change between channels or runs * of the program. */ static bool initializedLUTs; static const unsigned SINC_RESOLUTION = 8192; static const unsigned SINC_WIDTH = 8; static const unsigned SINC_SAMPLES = SINC_RESOLUTION * SINC_WIDTH; static double sinc_lut[SINC_SAMPLES + 1]; static double window_lut[SINC_SAMPLES + 1]; RingBuffer ringBuffer; Channel(); void UpdateVol(const Track &trk); void UpdatePan(const Track &trk); void UpdateTune(const Track &trk); void UpdateMod(const Track &trk); void UpdatePorta(const Track &trk); void Release(); void Kill(); void UpdateTrack(); void Update(); std::int32_t Interpolate(); std::int32_t GenerateSample(); void IncrementSample(); };