2016-03-20 17:00:00 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* REminiscence - Flashback interpreter
|
2019-10-27 17:00:00 +01:00
|
|
|
* Copyright (C) 2005-2019 Gregory Montoir (cyx@users.sourceforge.net)
|
2015-08-02 18:00:00 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "file.h"
|
|
|
|
#include "mixer.h"
|
|
|
|
#include "mod_player.h"
|
2016-03-20 17:00:00 +01:00
|
|
|
#include "util.h"
|
2015-08-02 18:00:00 +02:00
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
#ifdef USE_MODPLUG
|
|
|
|
#include <libmodplug/modplug.h>
|
2015-08-02 18:00:00 +02:00
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
struct ModPlayer_impl {
|
|
|
|
|
|
|
|
ModPlugFile *_mf;
|
|
|
|
ModPlug_Settings _settings;
|
|
|
|
bool _repeatIntro;
|
|
|
|
|
|
|
|
ModPlayer_impl()
|
|
|
|
: _mf(0) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void init(const int rate) {
|
|
|
|
memset(&_settings, 0, sizeof(_settings));
|
|
|
|
ModPlug_GetSettings(&_settings);
|
|
|
|
_settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
|
|
|
|
_settings.mChannels = 1;
|
2016-05-09 18:00:00 +02:00
|
|
|
_settings.mBits = 16;
|
2016-03-20 17:00:00 +01:00
|
|
|
_settings.mFrequency = rate;
|
|
|
|
_settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
|
2018-01-14 17:00:00 +01:00
|
|
|
_settings.mLoopCount = -1;
|
2016-03-20 17:00:00 +01:00
|
|
|
ModPlug_SetSettings(&_settings);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool load(File *f) {
|
|
|
|
const uint32_t size = f->size();
|
|
|
|
uint8_t *data = (uint8_t *)malloc(size);
|
|
|
|
if (data) {
|
|
|
|
f->read(data, size);
|
|
|
|
_mf = ModPlug_Load(data, size);
|
|
|
|
}
|
|
|
|
return _mf != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void unload() {
|
|
|
|
if (_mf) {
|
|
|
|
ModPlug_Unload(_mf);
|
|
|
|
_mf = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-09 18:00:00 +02:00
|
|
|
bool mix(int16_t *buf, int len) {
|
2016-03-20 17:00:00 +01:00
|
|
|
if (_mf) {
|
|
|
|
const int order = ModPlug_GetCurrentOrder(_mf);
|
|
|
|
if (order == 3 && _repeatIntro) {
|
|
|
|
ModPlug_SeekOrder(_mf, 1);
|
|
|
|
_repeatIntro = false;
|
|
|
|
}
|
2016-05-09 18:00:00 +02:00
|
|
|
const int count = ModPlug_Read(_mf, buf, len * sizeof(int16_t));
|
2018-01-14 17:00:00 +01:00
|
|
|
// setting mLoopCount to non-zero does not trigger any looping in
|
|
|
|
// my test and ModPlug_Read returns 0.
|
|
|
|
// looking at the libmodplug-0.8.8 tarball, it seems the variable
|
|
|
|
// m_nRepeatCount is commented in sndmix.cpp. Not sure how if this
|
|
|
|
// is a known bug, we workaround it here.
|
|
|
|
if (count == 0) {
|
|
|
|
ModPlug_SeekOrder(_mf, 0);
|
|
|
|
}
|
|
|
|
return true;
|
2016-03-20 17:00:00 +01:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
struct ModPlayer_impl {
|
|
|
|
enum {
|
|
|
|
NUM_SAMPLES = 31,
|
|
|
|
NUM_TRACKS = 4,
|
|
|
|
NUM_PATTERNS = 128,
|
|
|
|
FRAC_BITS = 12,
|
|
|
|
PAULA_FREQ = 3546897
|
|
|
|
};
|
|
|
|
|
|
|
|
struct SampleInfo {
|
|
|
|
char name[23];
|
|
|
|
uint16_t len;
|
|
|
|
uint8_t fineTune;
|
|
|
|
uint8_t volume;
|
|
|
|
uint16_t repeatPos;
|
|
|
|
uint16_t repeatLen;
|
|
|
|
int8_t *data;
|
|
|
|
|
|
|
|
int8_t getPCM(int offset) const {
|
|
|
|
if (offset < 0) {
|
|
|
|
offset = 0;
|
|
|
|
} else if (offset >= (int)len) {
|
|
|
|
offset = len - 1;
|
|
|
|
}
|
|
|
|
return data[offset];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ModuleInfo {
|
|
|
|
char songName[21];
|
|
|
|
SampleInfo samples[NUM_SAMPLES];
|
|
|
|
uint8_t numPatterns;
|
|
|
|
uint8_t patternOrderTable[NUM_PATTERNS];
|
|
|
|
uint8_t *patternsTable;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Track {
|
|
|
|
SampleInfo *sample;
|
|
|
|
uint8_t volume;
|
|
|
|
int pos;
|
|
|
|
int freq;
|
|
|
|
uint16_t period;
|
|
|
|
uint16_t periodIndex;
|
|
|
|
uint16_t effectData;
|
|
|
|
int vibratoSpeed;
|
|
|
|
int vibratoAmp;
|
|
|
|
int vibratoPos;
|
|
|
|
int portamento;
|
|
|
|
int portamentoSpeed;
|
|
|
|
int retriggerCounter;
|
|
|
|
int delayCounter;
|
|
|
|
int cutCounter;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool _playing;
|
|
|
|
int _mixingRate;
|
|
|
|
ModuleInfo _modInfo;
|
|
|
|
uint8_t _currentPatternOrder;
|
|
|
|
uint8_t _currentPatternPos;
|
|
|
|
uint8_t _currentTick;
|
|
|
|
uint8_t _songSpeed;
|
|
|
|
uint8_t _songTempo;
|
|
|
|
int _patternDelay;
|
|
|
|
int _patternLoopPos;
|
|
|
|
int _patternLoopCount;
|
|
|
|
int _samplesLeft;
|
|
|
|
bool _repeatIntro;
|
|
|
|
Track _tracks[NUM_TRACKS];
|
|
|
|
|
|
|
|
ModPlayer_impl();
|
|
|
|
|
|
|
|
void init(const int rate);
|
|
|
|
uint16_t findPeriod(uint16_t period, uint8_t fineTune) const;
|
|
|
|
bool load(File *f);
|
|
|
|
void unload();
|
|
|
|
void handleNote(int trackNum, uint32_t noteData);
|
|
|
|
void handleTick();
|
|
|
|
void applyVolumeSlide(int trackNum, int amount);
|
|
|
|
void applyVibrato(int trackNum);
|
|
|
|
void applyPortamento(int trackNum);
|
|
|
|
void handleEffect(int trackNum, bool tick);
|
2019-10-27 17:00:00 +01:00
|
|
|
void mixSamples(int16_t *buf, int len);
|
2016-05-09 18:00:00 +02:00
|
|
|
bool mix(int16_t *buf, int len);
|
2016-03-20 17:00:00 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
ModPlayer_impl::ModPlayer_impl()
|
|
|
|
: _playing(false) {
|
2015-08-02 18:00:00 +02:00
|
|
|
memset(&_modInfo, 0, sizeof(_modInfo));
|
|
|
|
}
|
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
uint16_t ModPlayer_impl::findPeriod(uint16_t period, uint8_t fineTune) const {
|
2015-08-02 18:00:00 +02:00
|
|
|
for (int p = 0; p < 36; ++p) {
|
2016-03-20 17:00:00 +01:00
|
|
|
if (ModPlayer::_periodTable[p] == period) {
|
2015-08-02 18:00:00 +02:00
|
|
|
return fineTune * 36 + p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
error("Invalid period=%d", period);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
void ModPlayer_impl::init(const int rate) {
|
|
|
|
_mixingRate = rate;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ModPlayer_impl::load(File *f) {
|
2015-08-02 18:00:00 +02:00
|
|
|
f->read(_modInfo.songName, 20);
|
|
|
|
_modInfo.songName[20] = 0;
|
|
|
|
debug(DBG_MOD, "songName = '%s'", _modInfo.songName);
|
|
|
|
|
|
|
|
for (int s = 0; s < NUM_SAMPLES; ++s) {
|
|
|
|
SampleInfo *si = &_modInfo.samples[s];
|
|
|
|
f->read(si->name, 22);
|
|
|
|
si->name[22] = 0;
|
|
|
|
si->len = f->readUint16BE() * 2;
|
|
|
|
si->fineTune = f->readByte();
|
|
|
|
si->volume = f->readByte();
|
|
|
|
si->repeatPos = f->readUint16BE() * 2;
|
|
|
|
si->repeatLen = f->readUint16BE() * 2;
|
|
|
|
si->data = 0;
|
|
|
|
assert(si->len == 0 || si->repeatPos + si->repeatLen <= si->len);
|
|
|
|
debug(DBG_MOD, "sample=%d name='%s' len=%d vol=%d", s, si->name, si->len, si->volume);
|
|
|
|
}
|
|
|
|
_modInfo.numPatterns = f->readByte();
|
|
|
|
assert(_modInfo.numPatterns < NUM_PATTERNS);
|
|
|
|
f->readByte(); // 0x7F
|
|
|
|
f->read(_modInfo.patternOrderTable, NUM_PATTERNS);
|
|
|
|
f->readUint32BE(); // 'M.K.', Protracker, 4 channels
|
|
|
|
|
2019-10-27 17:00:00 +01:00
|
|
|
uint8_t n = 0;
|
2015-08-02 18:00:00 +02:00
|
|
|
for (int i = 0; i < NUM_PATTERNS; ++i) {
|
|
|
|
if (_modInfo.patternOrderTable[i] != 0) {
|
|
|
|
n = MAX(n, _modInfo.patternOrderTable[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
debug(DBG_MOD, "numPatterns=%d",n + 1);
|
2019-10-27 17:00:00 +01:00
|
|
|
const int patternsSize = (n + 1) * 64 * 4 * 4; // 64 lines of 4 notes per channel
|
|
|
|
_modInfo.patternsTable = (uint8_t *)malloc(patternsSize);
|
|
|
|
if (!_modInfo.patternsTable) {
|
|
|
|
warning("Unable to allocate %d bytes for .MOD patterns table", patternsSize);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
f->read(_modInfo.patternsTable, patternsSize);
|
2015-08-02 18:00:00 +02:00
|
|
|
|
|
|
|
for (int s = 0; s < NUM_SAMPLES; ++s) {
|
|
|
|
SampleInfo *si = &_modInfo.samples[s];
|
|
|
|
if (si->len != 0) {
|
|
|
|
si->data = (int8_t *)malloc(si->len);
|
|
|
|
if (si->data) {
|
|
|
|
f->read(si->data, si->len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-20 17:00:00 +01:00
|
|
|
|
|
|
|
_currentPatternOrder = 0;
|
|
|
|
_currentPatternPos = 0;
|
|
|
|
_currentTick = 0;
|
|
|
|
_patternDelay = 0;
|
|
|
|
_songSpeed = 6;
|
|
|
|
_songTempo = 125;
|
|
|
|
_patternLoopPos = 0;
|
|
|
|
_patternLoopCount = -1;
|
|
|
|
_samplesLeft = 0;
|
|
|
|
_repeatIntro = false;
|
|
|
|
memset(_tracks, 0, sizeof(_tracks));
|
|
|
|
_playing = true;
|
|
|
|
|
|
|
|
return true;
|
2015-08-02 18:00:00 +02:00
|
|
|
}
|
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
void ModPlayer_impl::unload() {
|
2015-08-02 18:00:00 +02:00
|
|
|
if (_modInfo.songName[0]) {
|
|
|
|
free(_modInfo.patternsTable);
|
|
|
|
for (int s = 0; s < NUM_SAMPLES; ++s) {
|
|
|
|
free(_modInfo.samples[s].data);
|
|
|
|
}
|
|
|
|
memset(&_modInfo, 0, sizeof(_modInfo));
|
|
|
|
}
|
2016-03-20 17:00:00 +01:00
|
|
|
_playing = false;
|
2015-08-02 18:00:00 +02:00
|
|
|
}
|
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
void ModPlayer_impl::handleNote(int trackNum, uint32_t noteData) {
|
2015-08-02 18:00:00 +02:00
|
|
|
Track *tk = &_tracks[trackNum];
|
|
|
|
uint16_t sampleNum = ((noteData >> 24) & 0xF0) | ((noteData >> 12) & 0xF);
|
|
|
|
uint16_t samplePeriod = (noteData >> 16) & 0xFFF;
|
|
|
|
uint16_t effectData = noteData & 0xFFF;
|
|
|
|
debug(DBG_MOD, "ModPlayer::handleNote(%d) p=%d/%d sampleNumber=0x%X samplePeriod=0x%X effectData=0x%X tk->period=%d", trackNum, _currentPatternPos, _currentPatternOrder, sampleNum, samplePeriod, effectData, tk->period);
|
|
|
|
if (sampleNum != 0) {
|
|
|
|
tk->sample = &_modInfo.samples[sampleNum - 1];
|
|
|
|
tk->volume = tk->sample->volume;
|
|
|
|
tk->pos = 0;
|
|
|
|
}
|
|
|
|
if (samplePeriod != 0) {
|
|
|
|
tk->periodIndex = findPeriod(samplePeriod, tk->sample->fineTune);
|
|
|
|
if ((effectData >> 8) != 0x3 && (effectData >> 8) != 0x5) {
|
2016-03-20 17:00:00 +01:00
|
|
|
tk->period = ModPlayer::_periodTable[tk->periodIndex];
|
2015-08-02 18:00:00 +02:00
|
|
|
tk->freq = PAULA_FREQ / tk->period;
|
|
|
|
} else {
|
2016-03-20 17:00:00 +01:00
|
|
|
tk->portamento = ModPlayer::_periodTable[tk->periodIndex];
|
2015-08-02 18:00:00 +02:00
|
|
|
}
|
|
|
|
tk->vibratoAmp = 0;
|
|
|
|
tk->vibratoSpeed = 0;
|
|
|
|
tk->vibratoPos = 0;
|
|
|
|
}
|
|
|
|
tk->effectData = effectData;
|
|
|
|
}
|
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
void ModPlayer_impl::applyVolumeSlide(int trackNum, int amount) {
|
2015-08-02 18:00:00 +02:00
|
|
|
debug(DBG_MOD, "ModPlayer::applyVolumeSlide(%d, %d)", trackNum, amount);
|
|
|
|
Track *tk = &_tracks[trackNum];
|
|
|
|
int vol = tk->volume + amount;
|
|
|
|
if (vol < 0) {
|
|
|
|
vol = 0;
|
|
|
|
} else if (vol > 64) {
|
|
|
|
vol = 64;
|
|
|
|
}
|
|
|
|
tk->volume = vol;
|
|
|
|
}
|
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
void ModPlayer_impl::applyVibrato(int trackNum) {
|
|
|
|
static const int8_t sineWaveTable[] = {
|
|
|
|
0, 24, 49, 74, 97, 120, -115, -95, -76, -59, -44, -32, -21, -12, -6, -3,
|
|
|
|
-1, -3, -6, -12, -21, -32, -44, -59, -76, -95, -115, 120, 97, 74, 49, 24,
|
|
|
|
0, -24, -49, -74, -97, -120, 115, 95, 76, 59, 44, 32, 21, 12, 6, 3,
|
|
|
|
1, 3, 6, 12, 21, 32, 44, 59, 76, 95, 115, -120, -97, -74, -49, -24
|
|
|
|
};
|
2015-08-02 18:00:00 +02:00
|
|
|
debug(DBG_MOD, "ModPlayer::applyVibrato(%d)", trackNum);
|
|
|
|
Track *tk = &_tracks[trackNum];
|
2016-03-20 17:00:00 +01:00
|
|
|
int vib = tk->vibratoAmp * sineWaveTable[tk->vibratoPos] / 128;
|
2015-08-02 18:00:00 +02:00
|
|
|
if (tk->period + vib != 0) {
|
|
|
|
tk->freq = PAULA_FREQ / (tk->period + vib);
|
|
|
|
}
|
|
|
|
tk->vibratoPos += tk->vibratoSpeed;
|
|
|
|
if (tk->vibratoPos >= 64) {
|
|
|
|
tk->vibratoPos = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
void ModPlayer_impl::applyPortamento(int trackNum) {
|
2015-08-02 18:00:00 +02:00
|
|
|
debug(DBG_MOD, "ModPlayer::applyPortamento(%d)", trackNum);
|
|
|
|
Track *tk = &_tracks[trackNum];
|
|
|
|
if (tk->period < tk->portamento) {
|
|
|
|
tk->period = MIN(tk->period + tk->portamentoSpeed, tk->portamento);
|
|
|
|
} else if (tk->period > tk->portamento) {
|
|
|
|
tk->period = MAX(tk->period - tk->portamentoSpeed, tk->portamento);
|
|
|
|
}
|
|
|
|
if (tk->period != 0) {
|
|
|
|
tk->freq = PAULA_FREQ / tk->period;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
void ModPlayer_impl::handleEffect(int trackNum, bool tick) {
|
2015-08-02 18:00:00 +02:00
|
|
|
Track *tk = &_tracks[trackNum];
|
|
|
|
uint8_t effectNum = tk->effectData >> 8;
|
|
|
|
uint8_t effectXY = tk->effectData & 0xFF;
|
|
|
|
uint8_t effectX = effectXY >> 4;
|
|
|
|
uint8_t effectY = effectXY & 0xF;
|
|
|
|
debug(DBG_MOD, "ModPlayer::handleEffect(%d) effectNum=0x%X effectXY=0x%X", trackNum, effectNum, effectXY);
|
|
|
|
switch (effectNum) {
|
|
|
|
case 0x0: // arpeggio
|
|
|
|
if (tick && effectXY != 0) {
|
|
|
|
uint16_t period = tk->period;
|
|
|
|
switch (_currentTick & 3) {
|
|
|
|
case 1:
|
2016-03-20 17:00:00 +01:00
|
|
|
period = ModPlayer::_periodTable[tk->periodIndex + effectX];
|
2015-08-02 18:00:00 +02:00
|
|
|
break;
|
|
|
|
case 2:
|
2016-03-20 17:00:00 +01:00
|
|
|
period = ModPlayer::_periodTable[tk->periodIndex + effectY];
|
2015-08-02 18:00:00 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
tk->freq = PAULA_FREQ / period;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x1: // portamento up
|
|
|
|
if (tick) {
|
|
|
|
tk->period -= effectXY;
|
|
|
|
if (tk->period < 113) { // note B-3
|
|
|
|
tk->period = 113;
|
|
|
|
}
|
|
|
|
tk->freq = PAULA_FREQ / tk->period;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2: // portamento down
|
|
|
|
if (tick) {
|
|
|
|
tk->period += effectXY;
|
|
|
|
if (tk->period > 856) { // note C-1
|
|
|
|
tk->period = 856;
|
|
|
|
}
|
|
|
|
tk->freq = PAULA_FREQ / tk->period;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x3: // tone portamento
|
|
|
|
if (!tick) {
|
|
|
|
if (effectXY != 0) {
|
|
|
|
tk->portamentoSpeed = effectXY;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
applyPortamento(trackNum);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x4: // vibrato
|
|
|
|
if (!tick) {
|
|
|
|
if (effectX != 0) {
|
|
|
|
tk->vibratoSpeed = effectX;
|
|
|
|
}
|
|
|
|
if (effectY != 0) {
|
|
|
|
tk->vibratoAmp = effectY;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
applyVibrato(trackNum);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x5: // tone portamento + volume slide
|
|
|
|
if (tick) {
|
|
|
|
applyPortamento(trackNum);
|
|
|
|
applyVolumeSlide(trackNum, effectX - effectY);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x6: // vibrato + volume slide
|
|
|
|
if (tick) {
|
|
|
|
applyVibrato(trackNum);
|
|
|
|
applyVolumeSlide(trackNum, effectX - effectY);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x9: // set sample offset
|
|
|
|
if (!tick) {
|
|
|
|
tk->pos = effectXY << (8 + FRAC_BITS);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xA: // volume slide
|
|
|
|
if (tick) {
|
|
|
|
applyVolumeSlide(trackNum, effectX - effectY);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xB: // position jump
|
|
|
|
if (!tick) {
|
|
|
|
_currentPatternOrder = effectXY;
|
|
|
|
_currentPatternPos = 0;
|
|
|
|
assert(_currentPatternOrder < _modInfo.numPatterns);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xC: // set volume
|
|
|
|
if (!tick) {
|
|
|
|
assert(effectXY <= 64);
|
|
|
|
tk->volume = effectXY;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xD: // pattern break
|
|
|
|
if (!tick) {
|
|
|
|
_currentPatternPos = effectX * 10 + effectY;
|
|
|
|
assert(_currentPatternPos < 64);
|
|
|
|
++_currentPatternOrder;
|
|
|
|
debug(DBG_MOD, "_currentPatternPos = %d _currentPatternOrder = %d", _currentPatternPos, _currentPatternOrder);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xE: // extended effects
|
|
|
|
switch (effectX) {
|
|
|
|
case 0x0: // set filter, ignored
|
|
|
|
break;
|
|
|
|
case 0x1: // fineslide up
|
|
|
|
if (!tick) {
|
|
|
|
tk->period -= effectY;
|
|
|
|
if (tk->period < 113) { // B-3 note
|
|
|
|
tk->period = 113;
|
|
|
|
}
|
|
|
|
tk->freq = PAULA_FREQ / tk->period;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x2: // fineslide down
|
|
|
|
if (!tick) {
|
|
|
|
tk->period += effectY;
|
|
|
|
if (tk->period > 856) { // C-1 note
|
|
|
|
tk->period = 856;
|
|
|
|
}
|
|
|
|
tk->freq = PAULA_FREQ / tk->period;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x6: // loop pattern
|
|
|
|
if (!tick) {
|
|
|
|
if (effectY == 0) {
|
|
|
|
_patternLoopPos = _currentPatternPos | (_currentPatternOrder << 8);
|
|
|
|
debug(DBG_MOD, "_patternLoopPos=%d/%d", _currentPatternPos, _currentPatternOrder);
|
|
|
|
} else {
|
|
|
|
if (_patternLoopCount == -1) {
|
|
|
|
_patternLoopCount = effectY;
|
|
|
|
_currentPatternPos = _patternLoopPos & 0xFF;
|
|
|
|
_currentPatternOrder = _patternLoopPos >> 8;
|
|
|
|
} else {
|
|
|
|
--_patternLoopCount;
|
|
|
|
if (_patternLoopCount != 0) {
|
|
|
|
_currentPatternPos = _patternLoopPos & 0xFF;
|
|
|
|
_currentPatternOrder = _patternLoopPos >> 8;
|
|
|
|
} else {
|
|
|
|
_patternLoopCount = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
debug(DBG_MOD, "_patternLoopCount=%d", _patternLoopCount);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x9: // retrigger sample
|
|
|
|
if (tick) {
|
|
|
|
tk->retriggerCounter = effectY;
|
|
|
|
} else {
|
|
|
|
if (tk->retriggerCounter == 0) {
|
|
|
|
tk->pos = 0;
|
|
|
|
tk->retriggerCounter = effectY;
|
|
|
|
debug(DBG_MOD, "retrigger sample=%d _songSpeed=%d", effectY, _songSpeed);
|
|
|
|
}
|
|
|
|
--tk->retriggerCounter;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xA: // fine volume slide up
|
|
|
|
if (!tick) {
|
|
|
|
applyVolumeSlide(trackNum, effectY);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xB: // fine volume slide down
|
|
|
|
if (!tick) {
|
|
|
|
applyVolumeSlide(trackNum, -effectY);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xC: // cut sample
|
|
|
|
if (!tick) {
|
|
|
|
tk->cutCounter = effectY;
|
|
|
|
} else {
|
|
|
|
--tk->cutCounter;
|
|
|
|
if (tk->cutCounter == 0) {
|
|
|
|
tk->volume = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case 0xD: // delay sample
|
|
|
|
if (!tick) {
|
|
|
|
tk->delayCounter = effectY;
|
|
|
|
} else {
|
|
|
|
if (tk->delayCounter != 0) {
|
|
|
|
--tk->delayCounter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xE: // delay pattern
|
|
|
|
if (!tick) {
|
|
|
|
debug(DBG_MOD, "ModPlayer::handleEffect() _currentTick=%d delay pattern=%d", _currentTick, effectY);
|
|
|
|
_patternDelay = effectY;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
warning("Unhandled extended effect 0x%X params=0x%X", effectX, effectY);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xF: // set speed
|
|
|
|
if (!tick) {
|
|
|
|
if (effectXY < 0x20) {
|
|
|
|
_songSpeed = effectXY;
|
|
|
|
} else {
|
|
|
|
_songTempo = effectXY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
warning("Unhandled effect 0x%X params=0x%X", effectNum, effectXY);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-20 17:00:00 +01:00
|
|
|
void ModPlayer_impl::handleTick() {
|
2015-08-02 18:00:00 +02:00
|
|
|
if (!_playing) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// if (_patternDelay != 0) {
|
|
|
|
// --_patternDelay;
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
if (_currentTick == 0) {
|
|
|
|
debug(DBG_MOD, "_currentPatternOrder=%d _currentPatternPos=%d", _currentPatternOrder, _currentPatternPos);
|
|
|
|
uint8_t currentPattern = _modInfo.patternOrderTable[_currentPatternOrder];
|
|
|
|
const uint8_t *p = _modInfo.patternsTable + (currentPattern * 64 + _currentPatternPos) * 16;
|
|
|
|
for (int i = 0; i < NUM_TRACKS; ++i) {
|
|
|
|
uint32_t noteData = READ_BE_UINT32(p);
|
|
|
|
handleNote(i, noteData);
|
|
|
|
p += 4;
|
|
|
|
}
|
|
|
|
++_currentPatternPos;
|
|
|
|
if (_currentPatternPos == 64) {
|
|
|
|
++_currentPatternOrder;
|
|
|
|
_currentPatternPos = 0;
|
|
|
|
debug(DBG_MOD, "ModPlayer::handleTick() _currentPatternOrder = %d/%d", _currentPatternOrder, _modInfo.numPatterns);
|
|
|
|
// On the amiga version, the introduction cutscene is shorter than the PC version ;
|
|
|
|
// so the music module doesn't synchronize at all with the PC datafiles, here we
|
|
|
|
// add a hack to let the music play longer
|
2016-03-20 17:00:00 +01:00
|
|
|
if (_currentPatternOrder == 3 && _repeatIntro) {
|
2015-08-02 18:00:00 +02:00
|
|
|
_currentPatternOrder = 1;
|
2016-03-20 17:00:00 +01:00
|
|
|
_repeatIntro = false;
|
2015-08-02 18:00:00 +02:00
|
|
|
// warning("Introduction module synchronization hack");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int i = 0; i < NUM_TRACKS; ++i) {
|
|
|
|
handleEffect(i, (_currentTick != 0));
|
|
|
|
}
|
|
|
|
++_currentTick;
|
|
|
|
if (_currentTick == _songSpeed) {
|
|
|
|
_currentTick = 0;
|
|
|
|
}
|
|
|
|
if (_currentPatternOrder == _modInfo.numPatterns) {
|
|
|
|
debug(DBG_MOD, "ModPlayer::handleEffect() _currentPatternOrder == _modInfo.numPatterns");
|
2018-01-14 17:00:00 +01:00
|
|
|
// _playing = false;
|
|
|
|
_currentPatternOrder = 0;
|
2015-08-02 18:00:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-27 17:00:00 +01:00
|
|
|
void ModPlayer_impl::mixSamples(int16_t *buf, int samplesLen) {
|
2015-08-02 18:00:00 +02:00
|
|
|
for (int i = 0; i < NUM_TRACKS; ++i) {
|
|
|
|
Track *tk = &_tracks[i];
|
|
|
|
if (tk->sample != 0 && tk->delayCounter == 0) {
|
2019-10-27 17:00:00 +01:00
|
|
|
int16_t *mixbuf = buf;
|
2015-08-02 18:00:00 +02:00
|
|
|
SampleInfo *si = tk->sample;
|
|
|
|
int len = si->len << FRAC_BITS;
|
|
|
|
int loopLen = si->repeatLen << FRAC_BITS;
|
|
|
|
int loopPos = si->repeatPos << FRAC_BITS;
|
2016-03-20 17:00:00 +01:00
|
|
|
int deltaPos = (tk->freq << FRAC_BITS) / _mixingRate;
|
2015-08-02 18:00:00 +02:00
|
|
|
int curLen = samplesLen;
|
|
|
|
int pos = tk->pos;
|
|
|
|
while (curLen != 0) {
|
|
|
|
int count;
|
|
|
|
if (loopLen > (2 << FRAC_BITS)) {
|
|
|
|
if (pos >= loopPos + loopLen) {
|
|
|
|
pos -= loopLen;
|
|
|
|
}
|
|
|
|
count = MIN(curLen, (loopPos + loopLen - pos - 1) / deltaPos + 1);
|
|
|
|
curLen -= count;
|
|
|
|
} else {
|
|
|
|
if (pos >= len) {
|
|
|
|
count = 0;
|
|
|
|
} else {
|
|
|
|
count = MIN(curLen, (len - pos - 1) / deltaPos + 1);
|
|
|
|
}
|
|
|
|
curLen = 0;
|
|
|
|
}
|
|
|
|
while (count--) {
|
2019-10-27 17:00:00 +01:00
|
|
|
const int out = si->getPCM(pos >> FRAC_BITS) * tk->volume / 64;
|
|
|
|
*mixbuf = ADDC_S16(*mixbuf, S8_to_S16(out));
|
2016-05-09 18:00:00 +02:00
|
|
|
++mixbuf;
|
2015-08-02 18:00:00 +02:00
|
|
|
pos += deltaPos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tk->pos = pos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-27 17:00:00 +01:00
|
|
|
bool ModPlayer_impl::mix(int16_t *buf, int len) {
|
|
|
|
memset(buf, 0, sizeof(int16_t) * len);
|
2015-08-02 18:00:00 +02:00
|
|
|
if (_playing) {
|
2016-03-20 17:00:00 +01:00
|
|
|
const int samplesPerTick = _mixingRate / (50 * _songTempo / 125);
|
2015-08-02 18:00:00 +02:00
|
|
|
while (len != 0) {
|
|
|
|
if (_samplesLeft == 0) {
|
|
|
|
handleTick();
|
|
|
|
_samplesLeft = samplesPerTick;
|
|
|
|
}
|
|
|
|
int count = _samplesLeft;
|
|
|
|
if (count > len) {
|
|
|
|
count = len;
|
|
|
|
}
|
|
|
|
_samplesLeft -= count;
|
|
|
|
len -= count;
|
|
|
|
mixSamples(buf, count);
|
|
|
|
buf += count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return _playing;
|
|
|
|
}
|
2016-03-20 17:00:00 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
ModPlayer::ModPlayer(Mixer *mixer, FileSystem *fs)
|
|
|
|
: _playing(false), _mix(mixer), _fs(fs) {
|
|
|
|
_impl = new ModPlayer_impl;
|
|
|
|
}
|
|
|
|
|
|
|
|
ModPlayer::~ModPlayer() {
|
|
|
|
delete _impl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ModPlayer::play(int num) {
|
|
|
|
if (num < _modulesFilesCount) {
|
|
|
|
File f;
|
|
|
|
for (uint8_t i = 0; i < ARRAYSIZE(_modulesFiles[num]); ++i) {
|
|
|
|
if (f.open(_modulesFiles[num][i], "rb", _fs)) {
|
|
|
|
_impl->init(_mix->getSampleRate());
|
|
|
|
if (_impl->load(&f)) {
|
|
|
|
_impl->_repeatIntro = (num == 0) && !_isAmiga;
|
|
|
|
_mix->setPremixHook(mixCallback, _impl);
|
|
|
|
_playing = true;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ModPlayer::stop() {
|
|
|
|
if (_playing) {
|
|
|
|
_mix->setPremixHook(0, 0);
|
|
|
|
_impl->unload();
|
2019-10-27 17:00:00 +01:00
|
|
|
_playing = false;
|
2016-03-20 17:00:00 +01:00
|
|
|
}
|
|
|
|
}
|
2015-08-02 18:00:00 +02:00
|
|
|
|
2016-05-09 18:00:00 +02:00
|
|
|
bool ModPlayer::mixCallback(void *param, int16_t *buf, int len) {
|
2016-03-20 17:00:00 +01:00
|
|
|
return ((ModPlayer_impl *)param)->mix(buf, len);
|
2015-08-02 18:00:00 +02:00
|
|
|
}
|