/*
* 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 <algorithm>
#include <bitset>
#include <cstddef>
#include <cstdint>
#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<std::size_t N> 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<ToIntegral(ChannelFlag::Bits)> 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<SINC_WIDTH * 2> 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();
};