Tasmota/lib/lib_audio/ESP8266Audio/src/AudioGeneratorMOD.cpp

877 lines
28 KiB
C++

/*
AudioGeneratorMOD
Audio output generator that plays Amiga MOD tracker files
Copyright (C) 2017 Earle F. Philhower, III
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 3 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define PGM_READ_UNALIGNED 0
#include "AudioGeneratorMOD.h"
/*
Ported/hacked out from STELLARPLAYER by Ronen K.
http://mobile4dev.blogspot.com/2012/11/stellaris-launchpad-mod-player.html
A version exists in GitHub at https://github.com/steveway/stellarplayer
and also at https://github.com/MikesModz/StellarPlayer
Both which were themselves a port of the PIC32 MOD player
https://www.youtube.com/watch?v=i3Yl0TISQBE (seems to no longer be available.)
Most changes involved reducing memory usage by changing data structures,
moving constants to PROGMEM and minor tweaks to allow non pow2 buffer sizes.
*/
#pragma GCC optimize ("O3")
#define NOTE(r, c) (Player.currentPattern.note8[r][c]==NONOTE8?NONOTE:8*Player.currentPattern.note8[r][c])
#ifndef min
#define min(X,Y) ((X) < (Y) ? (X) : (Y))
#endif
AudioGeneratorMOD::AudioGeneratorMOD()
{
sampleRate = 44100;
fatBufferSize = 6 * 1024;
stereoSeparation = 32;
mixerTick = 0;
usePAL = false;
UpdateAmiga();
running = false;
file = NULL;
output = NULL;
}
AudioGeneratorMOD::~AudioGeneratorMOD()
{
// Free any remaining buffers
for (int i = 0; i < CHANNELS; i++) {
FatBuffer.channels[i] = NULL;
}
}
bool AudioGeneratorMOD::stop()
{
// We may be stopping because of allocation failures, so always deallocate
for (int i = 0; i < CHANNELS; i++) {
free(FatBuffer.channels[i]);
FatBuffer.channels[i] = NULL;
}
if (file) file->close();
running = false;
output->stop();
return true;
}
bool AudioGeneratorMOD::loop()
{
if (!running) goto done; // Easy-peasy
// First, try and push in the stored sample. If we can't, then punt and try later
if (!output->ConsumeSample(lastSample)) goto done; // FIFO full, wait...
// Now advance enough times to fill the i2s buffer
do {
if (mixerTick == 0) {
running = RunPlayer();
if (!running) {
stop();
goto done;
}
mixerTick = Player.samplesPerTick;
}
GetSample( lastSample );
mixerTick--;
} while (output->ConsumeSample(lastSample));
done:
file->loop();
output->loop();
// We'll be left with one sample still in our buffer because it couldn't fit in the FIFO
return running;
}
bool AudioGeneratorMOD::begin(AudioFileSource *source, AudioOutput *out)
{
if (running) stop();
if (!source) return false;
file = source;
if (!out) return false;
output = out;
if (!file->isOpen()) return false; // Can't read the file!
// Set the output values properly
if (!output->SetRate(sampleRate)) return false;
if (!output->SetBitsPerSample(16)) return false;
if (!output->SetChannels(2)) return false;
if (!output->begin()) return false;
UpdateAmiga();
for (int i = 0; i < CHANNELS; i++) {
FatBuffer.channels[i] = reinterpret_cast<uint8_t*>(calloc(fatBufferSize, 1));
if (!FatBuffer.channels[i]) {
stop();
return false;
}
}
if (!LoadMOD()) {
stop();
return false;
}
running = true;
return true;
}
// Sorted Amiga periods
static const uint16_t amigaPeriods[296] PROGMEM = {
907, 900, 894, 887, 881, 875, 868, 862, // -8 to -1
856, 850, 844, 838, 832, 826, 820, 814, // C-1 to +7
808, 802, 796, 791, 785, 779, 774, 768, // C#1 to +7
762, 757, 752, 746, 741, 736, 730, 725, // D-1 to +7
720, 715, 709, 704, 699, 694, 689, 684, // D#1 to +7
678, 675, 670, 665, 660, 655, 651, 646, // E-1 to +7
640, 636, 632, 628, 623, 619, 614, 610, // F-1 to +7
604, 601, 597, 592, 588, 584, 580, 575, // F#1 to +7
570, 567, 563, 559, 555, 551, 547, 543, // G-1 to +7
538, 535, 532, 528, 524, 520, 516, 513, // G#1 to +7
508, 505, 502, 498, 494, 491, 487, 484, // A-1 to +7
480, 477, 474, 470, 467, 463, 460, 457, // A#1 to +7
453, 450, 447, 444, 441, 437, 434, 431, // B-1 to +7
428, 425, 422, 419, 416, 413, 410, 407, // C-2 to +7
404, 401, 398, 395, 392, 390, 387, 384, // C#2 to +7
381, 379, 376, 373, 370, 368, 365, 363, // D-2 to +7
360, 357, 355, 352, 350, 347, 345, 342, // D#2 to +7
339, 337, 335, 332, 330, 328, 325, 323, // E-2 to +7
320, 318, 316, 314, 312, 309, 307, 305, // F-2 to +7
302, 300, 298, 296, 294, 292, 290, 288, // F#2 to +7
285, 284, 282, 280, 278, 276, 274, 272, // G-2 to +7
269, 268, 266, 264, 262, 260, 258, 256, // G#2 to +7
254, 253, 251, 249, 247, 245, 244, 242, // A-2 to +7
240, 238, 237, 235, 233, 232, 230, 228, // A#2 to +7
226, 225, 223, 222, 220, 219, 217, 216, // B-2 to +7
214, 212, 211, 209, 208, 206, 205, 203, // C-3 to +7
202, 200, 199, 198, 196, 195, 193, 192, // C#3 to +7
190, 189, 188, 187, 185, 184, 183, 181, // D-3 to +7
180, 179, 177, 176, 175, 174, 172, 171, // D#3 to +7
170, 169, 167, 166, 165, 164, 163, 161, // E-3 to +7
160, 159, 158, 157, 156, 155, 154, 152, // F-3 to +7
151, 150, 149, 148, 147, 146, 145, 144, // F#3 to +7
143, 142, 141, 140, 139, 138, 137, 136, // G-3 to +7
135, 134, 133, 132, 131, 130, 129, 128, // G#3 to +7
127, 126, 125, 125, 123, 123, 122, 121, // A-3 to +7
120, 119, 118, 118, 117, 116, 115, 114, // A#3 to +7
113, 113, 112, 111, 110, 109, 109, 108 // B-3 to +7
};
#define ReadAmigaPeriods(a) (uint16_t)pgm_read_word(amigaPeriods + (a))
static const uint8_t sine[64] PROGMEM = {
0, 24, 49, 74, 97, 120, 141, 161,
180, 197, 212, 224, 235, 244, 250, 253,
255, 253, 250, 244, 235, 224, 212, 197,
180, 161, 141, 120, 97, 74, 49, 24
};
#define ReadSine(a) pgm_read_byte(sine + (a))
static inline uint16_t MakeWord(uint8_t h, uint8_t l) { return h << 8 | l; }
bool AudioGeneratorMOD::LoadHeader()
{
uint8_t i;
uint8_t temp[4];
uint8_t junk[22];
if (20 != file->read(/*Mod.name*/junk, 20)) return false; // Skip MOD name
for (i = 0; i < SAMPLES; i++) {
if (22 != file->read(junk /*Mod.samples[i].name*/, 22)) return false; // Skip sample name
if (2 != file->read(temp, 2)) return false;
Mod.samples[i].length = MakeWord(temp[0], temp[1]) * 2;
if (1 != file->read(reinterpret_cast<uint8_t*>(&Mod.samples[i].fineTune), 1)) return false;
if (Mod.samples[i].fineTune > 7) Mod.samples[i].fineTune -= 16;
if (1 != file->read(&Mod.samples[i].volume, 1)) return false;
if (2 != file->read(temp, 2)) return false;
Mod.samples[i].loopBegin = MakeWord(temp[0], temp[1]) * 2;
if (2 != file->read(temp, 2)) return false;
Mod.samples[i].loopLength = MakeWord(temp[0], temp[1]) * 2;
if (Mod.samples[i].loopBegin + Mod.samples[i].loopLength > Mod.samples[i].length)
Mod.samples[i].loopLength = Mod.samples[i].length - Mod.samples[i].loopBegin;
}
if (1 != file->read(&Mod.songLength, 1)) return false;
if (1 != file->read(temp, 1)) return false; // Discard this byte
Mod.numberOfPatterns = 0;
for (i = 0; i < 128; i++) {
if (1 != file->read(&Mod.order[i], 1)) return false;
if (Mod.order[i] > Mod.numberOfPatterns)
Mod.numberOfPatterns = Mod.order[i];
}
Mod.numberOfPatterns++;
// Offset 1080
if (4 != file->read(temp, 4)) return false;;
if (!strncmp(reinterpret_cast<const char*>(temp + 1), "CHN", 3))
Mod.numberOfChannels = temp[0] - '0';
else if (!strncmp(reinterpret_cast<const char*>(temp + 2), "CH", 2))
Mod.numberOfChannels = (temp[0] - '0') * 10 + temp[1] - '0';
else
Mod.numberOfChannels = 4;
return true;
}
void AudioGeneratorMOD::LoadSamples()
{
uint8_t i;
uint32_t fileOffset = 1084 + Mod.numberOfPatterns * ROWS * Mod.numberOfChannels * 4 - 1;
for (i = 0; i < SAMPLES; i++) {
if (Mod.samples[i].length) {
Mixer.sampleBegin[i] = fileOffset;
Mixer.sampleEnd[i] = fileOffset + Mod.samples[i].length;
if (Mod.samples[i].loopLength > 2) {
Mixer.sampleloopBegin[i] = fileOffset + Mod.samples[i].loopBegin;
Mixer.sampleLoopLength[i] = Mod.samples[i].loopLength;
Mixer.sampleLoopEnd[i] = Mixer.sampleloopBegin[i] + Mixer.sampleLoopLength[i];
} else {
Mixer.sampleloopBegin[i] = 0;
Mixer.sampleLoopLength[i] = 0;
Mixer.sampleLoopEnd[i] = 0;
}
fileOffset += Mod.samples[i].length;
}
}
}
bool AudioGeneratorMOD::LoadPattern(uint8_t pattern)
{
uint8_t row;
uint8_t channel;
uint8_t i;
uint8_t temp[4];
uint16_t amigaPeriod;
if (!file->seek(1084 + pattern * ROWS * Mod.numberOfChannels * 4, SEEK_SET)) return false;
for (row = 0; row < ROWS; row++) {
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
if (4 != file->read(temp, 4)) return false;
Player.currentPattern.sampleNumber[row][channel] = (temp[0] & 0xF0) + (temp[2] >> 4);
amigaPeriod = ((temp[0] & 0xF) << 8) + temp[1];
// Player.currentPattern.note[row][channel] = NONOTE;
Player.currentPattern.note8[row][channel] = NONOTE8;
for (i = 1; i < 37; i++)
if (amigaPeriod > ReadAmigaPeriods(i * 8) - 3 &&
amigaPeriod < ReadAmigaPeriods(i * 8) + 3)
Player.currentPattern.note8[row][channel] = i;
Player.currentPattern.effectNumber[row][channel] = temp[2] & 0xF;
Player.currentPattern.effectParameter[row][channel] = temp[3];
}
}
return true;
}
void AudioGeneratorMOD::Portamento(uint8_t channel)
{
if (Player.lastAmigaPeriod[channel] < Player.portamentoNote[channel]) {
Player.lastAmigaPeriod[channel] += Player.portamentoSpeed[channel];
if (Player.lastAmigaPeriod[channel] > Player.portamentoNote[channel])
Player.lastAmigaPeriod[channel] = Player.portamentoNote[channel];
}
if (Player.lastAmigaPeriod[channel] > Player.portamentoNote[channel]) {
Player.lastAmigaPeriod[channel] -= Player.portamentoSpeed[channel];
if (Player.lastAmigaPeriod[channel] < Player.portamentoNote[channel])
Player.lastAmigaPeriod[channel] = Player.portamentoNote[channel];
}
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
}
void AudioGeneratorMOD::Vibrato(uint8_t channel)
{
uint16_t delta;
uint16_t temp;
temp = Player.vibratoPos[channel] & 31;
switch (Player.waveControl[channel] & 3) {
case 0:
delta = ReadSine(temp);
break;
case 1:
temp <<= 3;
if (Player.vibratoPos[channel] < 0)
temp = 255 - temp;
delta = temp;
break;
case 2:
delta = 255;
break;
case 3:
delta = rand() & 255;
break;
}
delta *= Player.vibratoDepth[channel];
delta >>= 7;
if (Player.vibratoPos[channel] >= 0)
Mixer.channelFrequency[channel] = Player.amiga / (Player.lastAmigaPeriod[channel] + delta);
else
Mixer.channelFrequency[channel] = Player.amiga / (Player.lastAmigaPeriod[channel] - delta);
Player.vibratoPos[channel] += Player.vibratoSpeed[channel];
if (Player.vibratoPos[channel] > 31) Player.vibratoPos[channel] -= 64;
}
void AudioGeneratorMOD::Tremolo(uint8_t channel)
{
uint16_t delta;
uint16_t temp;
temp = Player.tremoloPos[channel] & 31;
switch (Player.waveControl[channel] & 3) {
case 0:
delta = ReadSine(temp);
break;
case 1:
temp <<= 3;
if (Player.tremoloPos[channel] < 0)
temp = 255 - temp;
delta = temp;
break;
case 2:
delta = 255;
break;
case 3:
delta = rand() & 255;
break;
}
delta *= Player.tremoloDepth[channel];
delta >>= 6;
if (Player.tremoloPos[channel] >= 0) {
if (Player.volume[channel] + delta > 64) delta = 64 - Player.volume[channel];
Mixer.channelVolume[channel] = Player.volume[channel] + delta;
} else {
if (Player.volume[channel] - delta < 0) delta = Player.volume[channel];
Mixer.channelVolume[channel] = Player.volume[channel] - delta;
}
Player.tremoloPos[channel] += Player.tremoloSpeed[channel];
if (Player.tremoloPos[channel] > 31) Player.tremoloPos[channel] -= 64;
}
bool AudioGeneratorMOD::ProcessRow()
{
bool jumpFlag;
bool breakFlag;
uint8_t channel;
uint8_t sampleNumber;
uint16_t note;
uint8_t effectNumber;
uint8_t effectParameter;
uint8_t effectParameterX;
uint8_t effectParameterY;
uint16_t sampleOffset;
if (!running) return false;
Player.lastRow = Player.row++;
jumpFlag = false;
breakFlag = false;
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
sampleNumber = Player.currentPattern.sampleNumber[Player.lastRow][channel];
note = NOTE(Player.lastRow, channel);
effectNumber = Player.currentPattern.effectNumber[Player.lastRow][channel];
effectParameter = Player.currentPattern.effectParameter[Player.lastRow][channel];
effectParameterX = effectParameter >> 4;
effectParameterY = effectParameter & 0xF;
sampleOffset = 0;
if (sampleNumber) {
Player.lastSampleNumber[channel] = sampleNumber - 1;
if (!(effectParameter == 0xE && effectParameterX == NOTEDELAY))
Player.volume[channel] = Mod.samples[Player.lastSampleNumber[channel]].volume;
}
if (note != NONOTE) {
Player.lastNote[channel] = note;
Player.amigaPeriod[channel] = ReadAmigaPeriods(note + Mod.samples[Player.lastSampleNumber[channel]].fineTune);
if (effectNumber != TONEPORTAMENTO && effectNumber != PORTAMENTOVOLUMESLIDE)
Player.lastAmigaPeriod[channel] = Player.amigaPeriod[channel];
if (!(Player.waveControl[channel] & 0x80)) Player.vibratoPos[channel] = 0;
if (!(Player.waveControl[channel] & 0x08)) Player.tremoloPos[channel] = 0;
}
switch (effectNumber) {
case TONEPORTAMENTO:
if (effectParameter) Player.portamentoSpeed[channel] = effectParameter;
Player.portamentoNote[channel] = Player.amigaPeriod[channel];
note = NONOTE;
break;
case VIBRATO:
if (effectParameterX) Player.vibratoSpeed[channel] = effectParameterX;
if (effectParameterY) Player.vibratoDepth[channel] = effectParameterY;
break;
case PORTAMENTOVOLUMESLIDE:
Player.portamentoNote[channel] = Player.amigaPeriod[channel];
note = NONOTE;
break;
case TREMOLO:
if (effectParameterX) Player.tremoloSpeed[channel] = effectParameterX;
if (effectParameterY) Player.tremoloDepth[channel] = effectParameterY;
break;
case SETCHANNELPANNING:
Mixer.channelPanning[channel] = effectParameter >> 1;
break;
case SETSAMPLEOFFSET:
sampleOffset = effectParameter << 8;
if (sampleOffset > Mod.samples[Player.lastSampleNumber[channel]].length)
sampleOffset = Mod.samples[Player.lastSampleNumber[channel]].length;
break;
case JUMPTOORDER:
Player.orderIndex = effectParameter;
if (Player.orderIndex >= Mod.songLength)
Player.orderIndex = 0;
Player.row = 0;
jumpFlag = true;
break;
case SETVOLUME:
if (effectParameter > 64) Player.volume[channel] = 64;
else Player.volume[channel] = effectParameter;
break;
case BREAKPATTERNTOROW:
Player.row = effectParameterX * 10 + effectParameterY;
if (Player.row >= ROWS)
Player.row = 0;
if (!jumpFlag && !breakFlag) {
Player.orderIndex++;
if (Player.orderIndex >= Mod.songLength)
Player.orderIndex = 0;
}
breakFlag = true;
break;
case 0xE:
switch (effectParameterX) {
case FINEPORTAMENTOUP:
Player.lastAmigaPeriod[channel] -= effectParameterY;
break;
case FINEPORTAMENTODOWN:
Player.lastAmigaPeriod[channel] += effectParameterY;
break;
case SETVIBRATOWAVEFORM:
Player.waveControl[channel] &= 0xF0;
Player.waveControl[channel] |= effectParameterY;
break;
case SETFINETUNE:
Mod.samples[Player.lastSampleNumber[channel]].fineTune = effectParameterY;
if (Mod.samples[Player.lastSampleNumber[channel]].fineTune > 7)
Mod.samples[Player.lastSampleNumber[channel]].fineTune -= 16;
break;
case PATTERNLOOP:
if (effectParameterY) {
if (Player.patternLoopCount[channel])
Player.patternLoopCount[channel]--;
else
Player.patternLoopCount[channel] = effectParameterY;
if (Player.patternLoopCount[channel])
Player.row = Player.patternLoopRow[channel] - 1;
} else
Player.patternLoopRow[channel] = Player.row;
break;
case SETTREMOLOWAVEFORM:
Player.waveControl[channel] &= 0xF;
Player.waveControl[channel] |= effectParameterY << 4;
break;
case FINEVOLUMESLIDEUP:
Player.volume[channel] += effectParameterY;
if (Player.volume[channel] > 64) Player.volume[channel] = 64;
break;
case FINEVOLUMESLIDEDOWN:
Player.volume[channel] -= effectParameterY;
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
break;
case NOTECUT:
note = NONOTE;
break;
case PATTERNDELAY:
Player.patternDelay = effectParameterY;
break;
case INVERTLOOP:
break;
}
break;
case SETSPEED:
if (effectParameter < 0x20)
Player.speed = effectParameter;
else
Player.samplesPerTick = sampleRate / (2 * effectParameter / 5);
break;
}
if (note != NONOTE || (Player.lastAmigaPeriod[channel] &&
effectNumber != VIBRATO && effectNumber != VIBRATOVOLUMESLIDE &&
!(effectNumber == 0xE && effectParameterX == NOTEDELAY)))
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
if (note != NONOTE)
Mixer.channelSampleOffset[channel] = sampleOffset << FIXED_DIVIDER;
if (sampleNumber)
Mixer.channelSampleNumber[channel] = Player.lastSampleNumber[channel];
if (effectNumber != TREMOLO)
Mixer.channelVolume[channel] = Player.volume[channel];
}
return true;
}
bool AudioGeneratorMOD::ProcessTick()
{
uint8_t channel;
uint8_t sampleNumber;
uint16_t note;
uint8_t effectNumber;
uint8_t effectParameter;
uint8_t effectParameterX;
uint8_t effectParameterY;
uint16_t tempNote;
if (!running) return false;
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
if (Player.lastAmigaPeriod[channel]) {
sampleNumber = Player.currentPattern.sampleNumber[Player.lastRow][channel];
// note = Player.currentPattern.note[Player.lastRow][channel];
note = NOTE(Player.lastRow, channel);
effectNumber = Player.currentPattern.effectNumber[Player.lastRow][channel];
effectParameter = Player.currentPattern.effectParameter[Player.lastRow][channel];
effectParameterX = effectParameter >> 4;
effectParameterY = effectParameter & 0xF;
switch (effectNumber) {
case ARPEGGIO:
if (effectParameter)
switch (Player.tick % 3) {
case 0:
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
break;
case 1:
tempNote = Player.lastNote[channel] + effectParameterX * 8 + Mod.samples[Player.lastSampleNumber[channel]].fineTune;
if (tempNote < 296) Mixer.channelFrequency[channel] = Player.amiga / ReadAmigaPeriods(tempNote);
break;
case 2:
tempNote = Player.lastNote[channel] + effectParameterY * 8 + Mod.samples[Player.lastSampleNumber[channel]].fineTune;
if (tempNote < 296) Mixer.channelFrequency[channel] = Player.amiga / ReadAmigaPeriods(tempNote);
break;
}
break;
case PORTAMENTOUP:
Player.lastAmigaPeriod[channel] -= effectParameter;
if (Player.lastAmigaPeriod[channel] < 113) Player.lastAmigaPeriod[channel] = 113;
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
break;
case PORTAMENTODOWN:
Player.lastAmigaPeriod[channel] += effectParameter;
if (Player.lastAmigaPeriod[channel] > 856) Player.lastAmigaPeriod[channel] = 856;
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
break;
case TONEPORTAMENTO:
Portamento(channel);
break;
case VIBRATO:
Vibrato(channel);
break;
case PORTAMENTOVOLUMESLIDE:
Portamento(channel);
Player.volume[channel] += effectParameterX - effectParameterY;
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
else if (Player.volume[channel] > 64) Player.volume[channel] = 64;
Mixer.channelVolume[channel] = Player.volume[channel];
break;
case VIBRATOVOLUMESLIDE:
Vibrato(channel);
Player.volume[channel] += effectParameterX - effectParameterY;
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
else if (Player.volume[channel] > 64) Player.volume[channel] = 64;
Mixer.channelVolume[channel] = Player.volume[channel];
break;
case TREMOLO:
Tremolo(channel);
break;
case VOLUMESLIDE:
Player.volume[channel] += effectParameterX - effectParameterY;
if (Player.volume[channel] < 0) Player.volume[channel] = 0;
else if (Player.volume[channel] > 64) Player.volume[channel] = 64;
Mixer.channelVolume[channel] = Player.volume[channel];
break;
case 0xE:
switch (effectParameterX) {
case RETRIGGERNOTE:
if (!effectParameterY) break;
if (!(Player.tick % effectParameterY)) {
Mixer.channelSampleOffset[channel] = 0;
}
break;
case NOTECUT:
if (Player.tick == effectParameterY)
Mixer.channelVolume[channel] = Player.volume[channel] = 0;
break;
case NOTEDELAY:
if (Player.tick == effectParameterY) {
if (sampleNumber) Player.volume[channel] = Mod.samples[Player.lastSampleNumber[channel]].volume;
if (note != NONOTE) Mixer.channelSampleOffset[channel] = 0;
Mixer.channelFrequency[channel] = Player.amiga / Player.lastAmigaPeriod[channel];
Mixer.channelVolume[channel] = Player.volume[channel];
}
break;
}
break;
}
}
}
return true;
}
bool AudioGeneratorMOD::RunPlayer()
{
if (!running) return false;
if (Player.tick == Player.speed) {
Player.tick = 0;
if (Player.row == ROWS) {
Player.orderIndex++;
if (Player.orderIndex == Mod.songLength)
{
//Player.orderIndex = 0;
// No loop, just say we're done!
return false;
}
Player.row = 0;
}
if (Player.patternDelay) {
Player.patternDelay--;
} else {
if (Player.orderIndex != Player.oldOrderIndex)
if (!LoadPattern(Mod.order[Player.orderIndex])) return false;
Player.oldOrderIndex = Player.orderIndex;
if (!ProcessRow()) return false;
}
} else {
if (!ProcessTick()) return false;
}
Player.tick++;
return true;
}
void AudioGeneratorMOD::GetSample(int16_t sample[2])
{
int16_t sumL;
int16_t sumR;
uint8_t channel;
uint32_t samplePointer;
int8_t current;
int8_t next;
int16_t out;
if (!running) return;
sumL = 0;
sumR = 0;
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
if (!Mixer.channelFrequency[channel] ||
!Mod.samples[Mixer.channelSampleNumber[channel]].length) continue;
Mixer.channelSampleOffset[channel] += Mixer.channelFrequency[channel];
if (!Mixer.channelVolume[channel]) continue;
samplePointer = Mixer.sampleBegin[Mixer.channelSampleNumber[channel]] +
(Mixer.channelSampleOffset[channel] >> FIXED_DIVIDER);
if (Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]]) {
if (samplePointer >= Mixer.sampleLoopEnd[Mixer.channelSampleNumber[channel]]) {
Mixer.channelSampleOffset[channel] -= Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]] << FIXED_DIVIDER;
samplePointer -= Mixer.sampleLoopLength[Mixer.channelSampleNumber[channel]];
}
} else {
if (samplePointer >= Mixer.sampleEnd[Mixer.channelSampleNumber[channel]]) {
Mixer.channelFrequency[channel] = 0;
samplePointer = Mixer.sampleEnd[Mixer.channelSampleNumber[channel]];
}
}
if (samplePointer < FatBuffer.samplePointer[channel] ||
samplePointer >= FatBuffer.samplePointer[channel] + fatBufferSize - 1 ||
Mixer.channelSampleNumber[channel] != FatBuffer.channelSampleNumber[channel]) {
uint16_t toRead = Mixer.sampleEnd[Mixer.channelSampleNumber[channel]] - samplePointer + 1;
if (toRead > fatBufferSize) toRead = fatBufferSize;
if (!file->seek(samplePointer, SEEK_SET)) {
stop();
return;
}
if (toRead != file->read(FatBuffer.channels[channel], toRead)) {
stop();
return;
}
FatBuffer.samplePointer[channel] = samplePointer;
FatBuffer.channelSampleNumber[channel] = Mixer.channelSampleNumber[channel];
}
current = FatBuffer.channels[channel][(samplePointer - FatBuffer.samplePointer[channel]) /*& (FATBUFFERSIZE - 1)*/];
next = FatBuffer.channels[channel][(samplePointer + 1 - FatBuffer.samplePointer[channel]) /*& (FATBUFFERSIZE - 1)*/];
out = current;
// Integer linear interpolation
out += (next - current) * (Mixer.channelSampleOffset[channel] & ((1 << FIXED_DIVIDER) - 1)) >> FIXED_DIVIDER;
// Upscale to BITDEPTH
out <<= BITDEPTH - 8;
// Channel volume
out = out * Mixer.channelVolume[channel] >> 6;
// Channel panning
sumL += out * min(128 - Mixer.channelPanning[channel], 64) >> 6;
sumR += out * min(Mixer.channelPanning[channel], 64) >> 6;
}
// Downscale to BITDEPTH
sumL /= Mod.numberOfChannels;
sumR /= Mod.numberOfChannels;
// Fill the sound buffer with unsigned values
sample[AudioOutput::LEFTCHANNEL] = sumL + (1 << (BITDEPTH - 1));
sample[AudioOutput::RIGHTCHANNEL] = sumR + (1 << (BITDEPTH - 1));
}
bool AudioGeneratorMOD::LoadMOD()
{
uint8_t channel;
if (!LoadHeader()) return false;
LoadSamples();
Player.amiga = AMIGA;
Player.samplesPerTick = sampleRate / (2 * 125 / 5); // Hz = 2 * BPM / 5
Player.speed = 6;
Player.tick = Player.speed;
Player.row = 0;
Player.orderIndex = 0;
Player.oldOrderIndex = 0xFF;
Player.patternDelay = 0;
for (channel = 0; channel < Mod.numberOfChannels; channel++) {
Player.patternLoopCount[channel] = 0;
Player.patternLoopRow[channel] = 0;
Player.lastAmigaPeriod[channel] = 0;
Player.waveControl[channel] = 0;
Player.vibratoSpeed[channel] = 0;
Player.vibratoDepth[channel] = 0;
Player.vibratoPos[channel] = 0;
Player.tremoloSpeed[channel] = 0;
Player.tremoloDepth[channel] = 0;
Player.tremoloPos[channel] = 0;
FatBuffer.samplePointer[channel] = 0;
FatBuffer.channelSampleNumber[channel] = 0xFF;
Mixer.channelSampleOffset[channel] = 0;
Mixer.channelFrequency[channel] = 0;
Mixer.channelVolume[channel] = 0;
switch (channel % 4) {
case 0:
case 3:
Mixer.channelPanning[channel] = stereoSeparation;
break;
default:
Mixer.channelPanning[channel] = 128 - stereoSeparation;
}
}
return true;
}