scummvm/audio/mods/rjp1.cpp
2021-12-26 18:48:43 +01:00

587 lines
15 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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/>.
*
*/
#include "common/debug.h"
#include "common/endian.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "audio/mods/paula.h"
#include "audio/mods/rjp1.h"
namespace Audio {
struct Rjp1Channel {
const int8 *waveData;
const int8 *modulatePeriodData;
const int8 *modulateVolumeData;
const int8 *envelopeData;
uint16 volumeScale;
int16 volume;
uint16 modulatePeriodBase;
uint32 modulatePeriodLimit;
uint32 modulatePeriodIndex;
uint16 modulateVolumeBase;
uint32 modulateVolumeLimit;
uint32 modulateVolumeIndex;
uint8 freqStep;
uint32 freqInc;
uint32 freqInit;
const uint8 *noteData;
const uint8 *sequenceOffsets;
const uint8 *sequenceData;
uint8 loopSeqCount;
uint8 loopSeqCur;
uint8 loopSeq2Count;
uint8 loopSeq2Cur;
bool active;
int16 modulatePeriodInit;
int16 modulatePeriodNext;
bool setupNewNote;
int8 envelopeMode;
int8 envelopeScale;
int8 envelopeEnd1;
int8 envelopeEnd2;
int8 envelopeStart;
int8 envelopeVolume;
uint8 currentInstrument;
const int8 *data;
uint16 pos;
uint16 len;
uint16 repeatPos;
uint16 repeatLen;
bool isSfx;
};
class Rjp1 : public Paula {
public:
struct Vars {
int8 *instData;
uint8 *songData[7];
uint8 activeChannelsMask;
uint8 currentChannel;
int subsongsCount;
int instrumentsCount;
};
Rjp1(int rate, bool stereo);
virtual ~Rjp1();
bool load(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData);
void unload();
void startPattern(int ch, int pat);
void startSong(int song);
protected:
void startSequence(uint8 channelNum, uint8 seqNum);
void turnOffChannel(Rjp1Channel *channel);
void playChannel(Rjp1Channel *channel);
void turnOnChannel(Rjp1Channel *channel);
bool executeSfxSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p);
bool executeSongSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p);
void playSongSequence(Rjp1Channel *channel);
void modulateVolume(Rjp1Channel *channel);
void modulatePeriod(Rjp1Channel *channel);
void setupNote(Rjp1Channel *channel, int16 freq);
void setupInstrument(Rjp1Channel *channel, uint8 num);
void setRelease(Rjp1Channel *channel);
void modulateVolumeEnvelope(Rjp1Channel *channel);
void setSustain(Rjp1Channel *channel);
void setDecay(Rjp1Channel *channel);
void modulateVolumeWaveform(Rjp1Channel *channel);
void setVolume(Rjp1Channel *channel);
void stopPaulaChannel(uint8 channel);
void setupPaulaChannel(uint8 channel, const int8 *waveData, uint16 offset, uint16 len, uint16 repeatPos, uint16 repeatLen);
void interrupt() override;
Vars _vars;
Rjp1Channel _channelsTable[4];
static const int16 _periodsTable[];
static const int _periodsCount;
};
Rjp1::Rjp1(int rate, bool stereo)
: Paula(stereo, rate, rate / 50) {
memset(&_vars, 0, sizeof(_vars));
memset(_channelsTable, 0, sizeof(_channelsTable));
}
Rjp1::~Rjp1() {
unload();
}
bool Rjp1::load(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData) {
if (songData->readUint32BE() == MKTAG('R','J','P','1') && songData->readUint32BE() == MKTAG('S','M','O','D')) {
for (int i = 0; i < 7; ++i) {
uint32 size = songData->readUint32BE();
_vars.songData[i] = (uint8 *)malloc(size);
if (!_vars.songData[i])
return false;
songData->read(_vars.songData[i], size);
switch (i) {
case 0:
_vars.instrumentsCount = size / 32;
break;
case 1:
break;
case 2:
// sequence index to offsets, 1 per channel
_vars.subsongsCount = size / 4;
break;
case 3:
case 4:
// sequence offsets
break;
case 5:
case 6:
// sequence data
break;
default:
break;
}
}
if (instrumentsData->readUint32BE() == MKTAG('R','J','P','1')) {
uint32 size = instrumentsData->size() - 4;
_vars.instData = (int8 *)malloc(size);
if (!_vars.instData)
return false;
instrumentsData->read(_vars.instData, size);
}
}
debug(5, "Rjp1::load() _instrumentsCount = %d _subsongsCount = %d", _vars.instrumentsCount, _vars.subsongsCount);
return true;
}
void Rjp1::unload() {
for (int i = 0; i < 7; ++i) {
free(_vars.songData[i]);
}
free(_vars.instData);
memset(&_vars, 0, sizeof(_vars));
memset(_channelsTable, 0, sizeof(_channelsTable));
}
void Rjp1::startPattern(int ch, int pat) {
Rjp1Channel *channel = &_channelsTable[ch];
_vars.activeChannelsMask |= 1 << ch;
channel->sequenceData = READ_BE_UINT32(_vars.songData[4] + pat * 4) + _vars.songData[6];
channel->loopSeqCount = 6;
channel->loopSeqCur = channel->loopSeq2Cur = 1;
channel->active = true;
channel->isSfx = true;
// "start" Paula audiostream
startPaula();
}
void Rjp1::startSong(int song) {
if (song == 0 || song >= _vars.subsongsCount) {
warning("Invalid subsong number %d, defaulting to 1", song);
song = 1;
}
const uint8 *p = _vars.songData[2] + (song & 0x3F) * 4;
for (int i = 0; i < 4; ++i) {
uint8 seq = *p++;
if (seq) {
startSequence(i, seq);
}
}
// "start" Paula audiostream
startPaula();
}
void Rjp1::startSequence(uint8 channelNum, uint8 seqNum) {
Rjp1Channel *channel = &_channelsTable[channelNum];
_vars.activeChannelsMask |= 1 << channelNum;
if (seqNum != 0) {
const uint8 *p = READ_BE_UINT32(_vars.songData[3] + seqNum * 4) + _vars.songData[5];
uint8 seq = *p++;
channel->sequenceOffsets = p;
channel->sequenceData = READ_BE_UINT32(_vars.songData[4] + seq * 4) + _vars.songData[6];
channel->loopSeqCount = 6;
channel->loopSeqCur = channel->loopSeq2Cur = 1;
channel->active = true;
} else {
channel->active = false;
turnOffChannel(channel);
}
}
void Rjp1::turnOffChannel(Rjp1Channel *channel) {
stopPaulaChannel(channel - _channelsTable);
}
void Rjp1::playChannel(Rjp1Channel *channel) {
if (channel->active) {
turnOnChannel(channel);
if (channel->sequenceData) {
playSongSequence(channel);
}
modulateVolume(channel);
modulatePeriod(channel);
}
}
void Rjp1::turnOnChannel(Rjp1Channel *channel) {
if (channel->setupNewNote) {
channel->setupNewNote = false;
setupPaulaChannel(channel - _channelsTable, channel->data, channel->pos, channel->len, channel->repeatPos, channel->repeatLen);
}
}
bool Rjp1::executeSfxSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p) {
bool loop = true;
switch (code & 7) {
case 0:
_vars.activeChannelsMask &= ~(1 << _vars.currentChannel);
loop = false;
stopPaula();
break;
case 1:
setRelease(channel);
loop = false;
break;
case 2:
channel->loopSeqCount = *p++;
break;
case 3:
channel->loopSeq2Count = *p++;
break;
case 4:
code = *p++;
if (code != 0) {
setupInstrument(channel, code);
}
break;
case 7:
loop = false;
break;
default:
break;
}
return loop;
}
bool Rjp1::executeSongSequenceOp(Rjp1Channel *channel, uint8 code, const uint8 *&p) {
bool loop = true;
const uint8 *offs;
switch (code & 7) {
case 0:
offs = channel->sequenceOffsets;
channel->loopSeq2Count = 1;
while (1) {
code = *offs++;
if (code != 0) {
channel->sequenceOffsets = offs;
p = READ_BE_UINT32(_vars.songData[4] + code * 4) + _vars.songData[6];
break;
} else {
code = offs[0];
if (code == 0) {
p = nullptr;
channel->active = false;
_vars.activeChannelsMask &= ~(1 << _vars.currentChannel);
loop = false;
break;
} else if (code & 0x80) {
code = offs[1];
offs = READ_BE_UINT32(_vars.songData[3] + code * 4) + _vars.songData[5];
} else {
offs -= code;
}
}
}
break;
case 1:
setRelease(channel);
loop = false;
break;
case 2:
channel->loopSeqCount = *p++;
break;
case 3:
channel->loopSeq2Count = *p++;
break;
case 4:
code = *p++;
if (code != 0) {
setupInstrument(channel, code);
}
break;
case 5:
channel->volumeScale = *p++;
break;
case 6:
channel->freqStep = *p++;
channel->freqInc = READ_BE_UINT32(p); p += 4;
channel->freqInit = 0;
break;
case 7:
loop = false;
break;
default:
break;
}
return loop;
}
void Rjp1::playSongSequence(Rjp1Channel *channel) {
const uint8 *p = channel->sequenceData;
--channel->loopSeqCur;
if (channel->loopSeqCur == 0) {
--channel->loopSeq2Cur;
if (channel->loopSeq2Cur == 0) {
bool loop = true;
do {
uint8 code = *p++;
if (code & 0x80) {
if (channel->isSfx) {
loop = executeSfxSequenceOp(channel, code, p);
} else {
loop = executeSongSequenceOp(channel, code, p);
}
} else {
code >>= 1;
if (code < _periodsCount) {
setupNote(channel, _periodsTable[code]);
}
loop = false;
}
} while (loop);
channel->sequenceData = p;
channel->loopSeq2Cur = channel->loopSeq2Count;
}
channel->loopSeqCur = channel->loopSeqCount;
}
}
void Rjp1::modulateVolume(Rjp1Channel *channel) {
modulateVolumeEnvelope(channel);
modulateVolumeWaveform(channel);
setVolume(channel);
}
void Rjp1::modulatePeriod(Rjp1Channel *channel) {
if (channel->modulatePeriodData) {
uint32 per = channel->modulatePeriodIndex;
int period = (channel->modulatePeriodData[per] * channel->modulatePeriodInit) / 128;
period = -period;
if (period < 0) {
period /= 2;
}
channel->modulatePeriodNext = period + channel->modulatePeriodInit;
++per;
if (per == channel->modulatePeriodLimit) {
per = channel->modulatePeriodBase * 2;
}
channel->modulatePeriodIndex = per;
}
if (channel->freqStep != 0) {
channel->freqInit += channel->freqInc;
--channel->freqStep;
}
setChannelPeriod(channel - _channelsTable, channel->freqInit + channel->modulatePeriodNext);
}
void Rjp1::setupNote(Rjp1Channel *channel, int16 period) {
const uint8 *note = channel->noteData;
if (note) {
channel->modulatePeriodInit = channel->modulatePeriodNext = period;
channel->freqInit = 0;
const int8 *e = (const int8 *)_vars.songData[1] + READ_BE_UINT16(note + 12);
channel->envelopeData = e;
channel->envelopeStart = e[1];
channel->envelopeScale = e[1] - e[0];
channel->envelopeEnd2 = e[2];
channel->envelopeEnd1 = e[2];
channel->envelopeMode = 4;
channel->data = channel->waveData;
channel->pos = READ_BE_UINT16(note + 16);
channel->len = channel->pos + READ_BE_UINT16(note + 18);
channel->setupNewNote = true;
}
}
void Rjp1::setupInstrument(Rjp1Channel *channel, uint8 num) {
if (channel->currentInstrument != num) {
channel->currentInstrument = num;
const uint8 *p = _vars.songData[0] + num * 32;
channel->noteData = p;
channel->repeatPos = READ_BE_UINT16(p + 20);
channel->repeatLen = READ_BE_UINT16(p + 22);
channel->volumeScale = READ_BE_UINT16(p + 14);
channel->modulatePeriodBase = READ_BE_UINT16(p + 24);
channel->modulatePeriodIndex = 0;
channel->modulatePeriodLimit = READ_BE_UINT16(p + 26) * 2;
channel->modulateVolumeBase = READ_BE_UINT16(p + 28);
channel->modulateVolumeIndex = 0;
channel->modulateVolumeLimit = READ_BE_UINT16(p + 30) * 2;
channel->waveData = _vars.instData + READ_BE_UINT32(p);
uint32 off = READ_BE_UINT32(p + 4);
if (off) {
channel->modulatePeriodData = _vars.instData + off;
}
off = READ_BE_UINT32(p + 8);
if (off) {
channel->modulateVolumeData = _vars.instData + off;
}
}
}
void Rjp1::setRelease(Rjp1Channel *channel) {
const int8 *e = channel->envelopeData;
if (e) {
channel->envelopeStart = 0;
channel->envelopeScale = -channel->envelopeVolume;
channel->envelopeEnd2 = e[5];
channel->envelopeEnd1 = e[5];
channel->envelopeMode = -1;
}
}
void Rjp1::modulateVolumeEnvelope(Rjp1Channel *channel) {
if (channel->envelopeMode) {
int16 es = channel->envelopeScale;
if (es) {
int8 m = channel->envelopeEnd1;
if (m == 0) {
es = 0;
} else {
es *= m;
m = channel->envelopeEnd2;
if (m == 0) {
es = 0;
} else {
es /= m;
}
}
}
channel->envelopeVolume = channel->envelopeStart - es;
--channel->envelopeEnd1;
if (channel->envelopeEnd1 == -1) {
switch (channel->envelopeMode) {
case 0:
break;
case 2:
setSustain(channel);
break;
case 4:
setDecay(channel);
break;
case -1:
setSustain(channel);
break;
default:
error("Unhandled envelope mode %d", channel->envelopeMode);
break;
}
return;
}
}
channel->volume = channel->envelopeVolume;
}
void Rjp1::setSustain(Rjp1Channel *channel) {
channel->envelopeMode = 0;
}
void Rjp1::setDecay(Rjp1Channel *channel) {
const int8 *e = channel->envelopeData;
if (e) {
channel->envelopeStart = e[3];
channel->envelopeScale = e[3] - e[1];
channel->envelopeEnd2 = e[4];
channel->envelopeEnd1 = e[4];
channel->envelopeMode = 2;
}
}
void Rjp1::modulateVolumeWaveform(Rjp1Channel *channel) {
if (channel->modulateVolumeData) {
uint32 i = channel->modulateVolumeIndex;
channel->volume += channel->modulateVolumeData[i] * channel->volume / 128;
++i;
if (i == channel->modulateVolumeLimit) {
i = channel->modulateVolumeBase * 2;
}
channel->modulateVolumeIndex = i;
}
}
void Rjp1::setVolume(Rjp1Channel *channel) {
channel->volume = (channel->volume * channel->volumeScale) / 64;
channel->volume = CLIP<int16>(channel->volume, 0, 64);
setChannelVolume(channel - _channelsTable, channel->volume);
}
void Rjp1::stopPaulaChannel(uint8 channel) {
clearVoice(channel);
}
void Rjp1::setupPaulaChannel(uint8 channel, const int8 *waveData, uint16 offset, uint16 len, uint16 repeatPos, uint16 repeatLen) {
if (waveData) {
setChannelData(channel, waveData, waveData + repeatPos * 2, len * 2, repeatLen * 2, offset * 2);
}
}
void Rjp1::interrupt() {
for (int i = 0; i < 4; ++i) {
_vars.currentChannel = i;
playChannel(&_channelsTable[i]);
}
}
const int16 Rjp1::_periodsTable[] = {
0x01C5, 0x01E0, 0x01FC, 0x021A, 0x023A, 0x025C, 0x0280, 0x02A6, 0x02D0,
0x02FA, 0x0328, 0x0358, 0x00E2, 0x00F0, 0x00FE, 0x010D, 0x011D, 0x012E,
0x0140, 0x0153, 0x0168, 0x017D, 0x0194, 0x01AC, 0x0071, 0x0078, 0x007F,
0x0087, 0x008F, 0x0097, 0x00A0, 0x00AA, 0x00B4, 0x00BE, 0x00CA, 0x00D6
};
const int Rjp1::_periodsCount = ARRAYSIZE(_periodsTable);
AudioStream *makeRjp1Stream(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData, int num, int rate, bool stereo) {
Rjp1 *stream = new Rjp1(rate, stereo);
if (stream->load(songData, instrumentsData)) {
if (num < 0) {
stream->startPattern(3, -num);
} else {
stream->startSong(num);
}
return stream;
}
delete stream;
return nullptr;
}
} // End of namespace Audio