Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

470
audio/mods/infogrames.cpp Normal file
View File

@@ -0,0 +1,470 @@
/* 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 "audio/mods/infogrames.h"
#include "common/endian.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/textconsole.h"
namespace Audio {
Infogrames::Instruments::Instruments() {
init();
}
Infogrames::Instruments::~Instruments() {
delete[] _sampleData;
}
void Infogrames::Instruments::init() {
int i;
for (i = 0; i < 32; i++) {
_samples[i].data = nullptr;
_samples[i].dataRepeat = nullptr;
_samples[i].length = 0;
_samples[i].lengthRepeat = 0;
}
_count = 0;
_sampleData = nullptr;
}
bool Infogrames::Instruments::load(const char *ins) {
Common::File f;
if (f.open(ins))
return load(f);
return false;
}
bool Infogrames::Instruments::load(Common::SeekableReadStream &ins) {
int i;
int32 fsize;
int32 offset[32];
int32 offsetRepeat[32];
int32 dataOffset;
unload();
fsize = ins.readUint32BE();
dataOffset = fsize;
for (i = 0; (i < 32) && !ins.eos(); i++) {
offset[i] = ins.readUint32BE();
offsetRepeat[i] = ins.readUint32BE();
if ((offset[i] > fsize) || (offsetRepeat[i] > fsize) ||
(offset[i] < (ins.pos() + 4)) ||
(offsetRepeat[i] < (ins.pos() + 4))) {
// Definitely no real entry anymore
ins.seek(-8, SEEK_CUR);
break;
}
dataOffset = MIN(dataOffset, MIN(offset[i], offsetRepeat[i]));
ins.skip(4); // Unknown
_samples[i].length = ins.readUint16BE() * 2;
_samples[i].lengthRepeat = ins.readUint16BE() * 2;
}
if (dataOffset >= fsize)
return false;
_count = i;
_sampleData = new int8[fsize - dataOffset];
ins.seek(dataOffset + 4);
ins.read(_sampleData, fsize - dataOffset);
for (i--; i >= 0; i--) {
_samples[i].data = _sampleData + (offset[i] - dataOffset);
_samples[i].dataRepeat = _sampleData + (offsetRepeat[i] - dataOffset);
}
return true;
}
void Infogrames::Instruments::unload() {
delete[] _sampleData;
init();
}
const uint8 Infogrames::tickCount[] =
{2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96};
const uint16 Infogrames::periods[] =
{0x6ACC, 0x64CC, 0x5F25, 0x59CE, 0x54C3, 0x5003, 0x4B86, 0x4747, 0x4346,
0x3F8B, 0x3BF3, 0x3892, 0x3568, 0x3269, 0x2F93, 0x2CEA, 0x2A66, 0x2801,
0x2566, 0x23A5, 0x21AF, 0x1FC4, 0x1DFE, 0x1C4E, 0x1ABC, 0x1936, 0x17CC,
0x1676, 0x1533, 0x1401, 0x12E4, 0x11D5, 0x10D4, 0x0FE3, 0x0EFE, 0x0E26,
0x0D5B, 0x0C9B, 0x0BE5, 0x0B3B, 0x0A9B, 0x0A02, 0x0972, 0x08E9, 0x0869,
0x07F1, 0x077F, 0x0713, 0x06AD, 0x064D, 0x05F2, 0x059D, 0x054D, 0x0500,
0x04B8, 0x0475, 0x0435, 0x03F8, 0x03BF, 0x038A, 0x0356, 0x0326, 0x02F9,
0x02CF, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, 0x01FC, 0x01E0, 0x01C5,
0x01AB, 0x0193, 0x017D, 0x0167, 0x0153, 0x0140, 0x012E, 0x011D, 0x010D,
0x00FE, 0x00F0, 0x00E2, 0x00D6, 0x00CA, 0x00BE, 0x00B4, 0x00AA, 0x00A0,
0x0097, 0x008F, 0x0087, 0x007F, 0x0078, 0x0070, 0x0060, 0x0050, 0x0040,
0x0030, 0x0020, 0x0010, 0x0000, 0x0000, 0x0020, 0x2020, 0x2020, 0x2020,
0x2020, 0x3030, 0x3030, 0x3020, 0x2020, 0x2020, 0x2020, 0x2020, 0x2020,
0x2020, 0x2020, 0x2020, 0x2090, 0x4040, 0x4040, 0x4040, 0x4040, 0x4040,
0x4040, 0x4040, 0x400C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C40, 0x4040,
0x4040, 0x4040, 0x0909, 0x0909, 0x0909, 0x0101, 0x0101, 0x0101, 0x0101,
0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x0101, 0x4040, 0x4040, 0x4040,
0x0A0A, 0x0A0A, 0x0A0A, 0x0202, 0x0202, 0x0202, 0x0202, 0x0202, 0x0202,
0x0202, 0x0202, 0x0202, 0x0202, 0x4040, 0x4040, 0x2000};
Infogrames::Infogrames(Instruments &ins, bool stereo, int rate,
int interruptFreq) : Paula(stereo, rate, interruptFreq) {
_instruments = &ins;
_data = nullptr;
_repCount = -1;
reset();
}
Infogrames::~Infogrames() {
delete[] _data;
}
void Infogrames::init() {
int i;
_volume = 0;
_period = 0;
_sample = 0;
_speedCounter = _speed;
for (i = 0; i < 4; i++) {
_chn[i].cmds = nullptr;
_chn[i].cmdBlocks = nullptr;
_chn[i].volSlide.finetuneNeg = 0;
_chn[i].volSlide.finetunePos = 0;
_chn[i].volSlide.data = nullptr;
_chn[i].volSlide.amount = 0;
_chn[i].volSlide.dataOffset = 0;
_chn[i].volSlide.flags = 0;
_chn[i].volSlide.curDelay1 = 0;
_chn[i].volSlide.curDelay2 = 0;
_chn[i].periodSlide.finetuneNeg = 0;
_chn[i].periodSlide.finetunePos = 0;
_chn[i].periodSlide.data = nullptr;
_chn[i].periodSlide.amount = 0;
_chn[i].periodSlide.dataOffset = 0;
_chn[i].periodSlide.flags = 0;
_chn[i].periodSlide.curDelay1 = 0;
_chn[i].periodSlide.curDelay2 = 0;
_chn[i].period = 0;
_chn[i].flags = 0x81;
_chn[i].ticks = 0;
_chn[i].tickCount = 0;
_chn[i].periodMod = 0;
}
_end = (_data == nullptr);
}
void Infogrames::reset() {
int i;
stopPlay();
init();
_volSlideBlocks = nullptr;
_periodSlideBlocks = nullptr;
_subSong = nullptr;
_cmdBlocks = nullptr;
_speedCounter = 0;
_speed = 0;
for (i = 0; i < 4; i++)
_chn[i].cmdBlockIndices = nullptr;
}
bool Infogrames::load(const char *dum) {
Common::File f;
if (f.open(dum))
return load(f);
return false;
}
bool Infogrames::load(Common::SeekableReadStream &dum) {
int subSong = 0;
int i;
uint32 size;
size = dum.size();
if (size < 20)
return false;
_data = new uint8[size];
dum.seek(0);
dum.read(_data, size);
Common::MemoryReadStream dataStr(_data, size);
dataStr.seek(subSong * 2);
dataStr.seek(dataStr.readUint16BE());
_subSong = _data + dataStr.pos();
if (_subSong > (_data + size))
return false;
_speedCounter = dataStr.readUint16BE();
_speed = _speedCounter;
_volSlideBlocks = _subSong + dataStr.readUint16BE();
_periodSlideBlocks = _subSong + dataStr.readUint16BE();
for (i = 0; i < 4; i++) {
_chn[i].cmdBlockIndices = _subSong + dataStr.readUint16BE();
_chn[i].flags = 0x81;
}
_cmdBlocks = _data + dataStr.pos() + 2;
if ((_volSlideBlocks > (_data + size)) ||
(_periodSlideBlocks > (_data + size)) ||
(_chn[0].cmdBlockIndices > (_data + size)) ||
(_chn[1].cmdBlockIndices > (_data + size)) ||
(_chn[2].cmdBlockIndices > (_data + size)) ||
(_chn[3].cmdBlockIndices > (_data + size)) ||
(_cmdBlocks > (_data + size)))
return false;
startPaula();
return true;
}
void Infogrames::unload() {
stopPlay();
delete[] _data;
_data = nullptr;
clearVoices();
reset();
}
void Infogrames::getNextSample(Channel &chn) {
byte *data;
byte cmdBlock = 0;
uint16 cmd;
bool cont = false;
if (chn.flags & 64)
return;
if (chn.flags & 1) {
chn.flags &= ~1;
chn.cmdBlocks = chn.cmdBlockIndices;
} else {
chn.flags &= ~1;
if (_speedCounter == 0)
chn.ticks--;
if (chn.ticks != 0) {
_volume = MAX((int16) 0, tune(chn.volSlide, 0));
_period = tune(chn.periodSlide, chn.period);
return;
} else {
chn.ticks = chn.tickCount;
cont = true;
}
}
while (1) {
while (cont || ((cmdBlock = *chn.cmdBlocks) != 0xFF)) {
if (!cont) {
chn.cmdBlocks++;
chn.cmds = _subSong +
READ_BE_UINT16(_cmdBlocks + (cmdBlock * 2));
} else
cont = false;
while ((cmd = *chn.cmds) != 0xFF) {
chn.cmds++;
if (cmd & 128)
{
switch (cmd & 0xE0) {
case 0x80: // 100xxxxx - Set ticks
chn.ticks = tickCount[cmd & 0xF];
chn.tickCount = tickCount[cmd & 0xF];
break;
case 0xA0: // 101xxxxx - Set sample
_sample = cmd & 0x1F;
break;
case 0xC0: // 110xxxxx - Set volume slide/finetune
data = _volSlideBlocks + (cmd & 0x1F) * 13;
chn.volSlide.flags = (*data & 0x80) | 1;
chn.volSlide.amount = *data++ & 0x7F;
chn.volSlide.data = data;
chn.volSlide.dataOffset = 0;
chn.volSlide.finetunePos = 0;
chn.volSlide.finetuneNeg = 0;
chn.volSlide.curDelay1 = 0;
chn.volSlide.curDelay2 = 0;
break;
case 0xE0: // 111xxxxx - Extended
switch (cmd & 0x1F) {
case 0: // Set period modifier
chn.periodMod = (int8) *chn.cmds++;
break;
case 1: // Set continuous period slide
chn.periodSlide.data =
_periodSlideBlocks + *chn.cmds++ * 13 + 1;
chn.periodSlide.amount = 0;
chn.periodSlide.dataOffset = 0;
chn.periodSlide.finetunePos = 0;
chn.periodSlide.finetuneNeg = 0;
chn.periodSlide.curDelay1 = 0;
chn.periodSlide.curDelay2 = 0;
chn.periodSlide.flags = 0x81;
break;
case 2: // Set non-continuous period slide
chn.periodSlide.data =
_periodSlideBlocks + *chn.cmds++ * 13 + 1;
chn.periodSlide.amount = 0;
chn.periodSlide.dataOffset = 0;
chn.periodSlide.finetunePos = 0;
chn.periodSlide.finetuneNeg = 0;
chn.periodSlide.curDelay1 = 0;
chn.periodSlide.curDelay2 = 0;
chn.periodSlide.flags = 1;
break;
case 3: // NOP
break;
default:
warning("Unknown Infogrames command: %X", cmd);
break;
}
break;
default:
break;
}
} else { // 0xxxxxxx - Set period
if (cmd != 0)
cmd += chn.periodMod;
chn.period = periods[cmd];
chn.volSlide.dataOffset = 0;
chn.volSlide.finetunePos = 0;
chn.volSlide.finetuneNeg = 0;
chn.volSlide.curDelay1 = 0;
chn.volSlide.curDelay2 = 0;
chn.volSlide.flags |= 1;
chn.volSlide.flags &= ~4;
chn.periodSlide.dataOffset = 0;
chn.periodSlide.finetunePos = 0;
chn.periodSlide.finetuneNeg = 0;
chn.periodSlide.curDelay1 = 0;
chn.periodSlide.curDelay2 = 0;
chn.periodSlide.flags |= 1;
chn.periodSlide.flags &= ~4;
_volume = MAX((int16) 0, tune(chn.volSlide, 0));
_period = tune(chn.periodSlide, chn.period);
return;
}
}
}
if (!(chn.flags & 32)) {
chn.flags |= 0x40;
_volume = 0;
return;
} else
chn.cmdBlocks = chn.cmdBlockIndices;
}
}
int16 Infogrames::tune(Slide &slide, int16 start) const {
byte *data;
uint8 off;
data = slide.data + slide.dataOffset;
if (slide.flags & 1)
slide.finetunePos += (int8) data[1];
slide.flags &= ~1;
start += slide.finetunePos - slide.finetuneNeg;
if (start < 0)
start = 0;
if (slide.flags & 4)
return start;
slide.curDelay1++;
if (slide.curDelay1 != data[2])
return start;
slide.curDelay2++;
slide.curDelay1 = 0;
if (slide.curDelay2 == data[0]) {
slide.curDelay2 = 0;
off = slide.dataOffset + 3;
if (off == 12) {
if (slide.flags == 0) {
slide.flags |= 4;
return start;
} else {
slide.curDelay2 = 0;
slide.finetuneNeg += slide.amount;
off = 3;
}
}
slide.dataOffset = off;
}
slide.flags |= 1;
return start;
}
void Infogrames::interrupt() {
int chn;
if (!_data) {
clearVoices();
return;
}
_speedCounter--;
_sample = 0xFF;
for (chn = 0; chn < 4; chn++) {
_volume = 0;
_period = 0;
getNextSample(_chn[chn]);
setChannelVolume(chn, _volume);
setChannelPeriod(chn, _period);
if ((_sample != 0xFF) && (_sample < _instruments->_count)) {
setChannelData(chn,
_instruments->_samples[_sample].data,
_instruments->_samples[_sample].dataRepeat,
_instruments->_samples[_sample].length,
_instruments->_samples[_sample].lengthRepeat);
_sample = 0xFF;
}
}
if (_speedCounter == 0)
_speedCounter = _speed;
// End reached?
if ((_chn[0].flags & 64) && (_chn[1].flags & 64) &&
(_chn[2].flags & 64) && (_chn[3].flags & 64)) {
if (_repCount > 0) {
_repCount--;
init();
} else if (_repCount != -1) {
stopPaula();
} else {
init();
}
}
}
} // End of namespace Audio

147
audio/mods/infogrames.h Normal file
View File

@@ -0,0 +1,147 @@
/* 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/>.
*
*/
/**
* @file
* Sound decoder used in engines:
* - gob
*/
#ifndef AUDIO_MODS_INFOGRAMES_H
#define AUDIO_MODS_INFOGRAMES_H
#include "audio/mods/paula.h"
namespace Common {
class SeekableReadStream;
}
namespace Audio {
/** A player for the Infogrames/RobHubbard2 format */
class Infogrames : public Paula {
public:
class Instruments {
public:
Instruments();
template<typename T> Instruments(T ins) {
init();
bool result = load(ins);
assert(result);
}
~Instruments();
bool load(Common::SeekableReadStream &ins);
bool load(const char *ins);
void unload();
uint8 getCount() const { return _count; }
protected:
struct Sample {
int8 *data;
int8 *dataRepeat;
uint32 length;
uint32 lengthRepeat;
} _samples[32];
uint8 _count;
int8 *_sampleData;
void init();
friend class Infogrames;
};
Infogrames(Instruments &ins, bool stereo = false, int rate = 44100,
int interruptFreq = 0);
~Infogrames();
Instruments *getInstruments() const { return _instruments; }
bool getRepeating() const { return _repCount != 0; }
void setRepeating (int32 repCount) { _repCount = repCount; }
bool load(Common::SeekableReadStream &dum);
bool load(const char *dum);
void unload();
void restart() {
if (_data) {
// Use the mutex here to ensure we do not call init()
// while data is being read by the mixer thread.
_mutex.lock();
init();
startPlay();
_mutex.unlock();
}
}
protected:
Instruments *_instruments;
static const uint8 tickCount[];
static const uint16 periods[];
byte *_data;
int32 _repCount;
byte *_subSong;
byte *_cmdBlocks;
byte *_volSlideBlocks;
byte *_periodSlideBlocks;
uint8 _speedCounter;
uint8 _speed;
uint16 _volume;
int16 _period;
uint8 _sample;
struct Slide {
byte *data;
int8 amount;
uint8 dataOffset;
int16 finetuneNeg;
int16 finetunePos;
uint8 curDelay1;
uint8 curDelay2;
uint8 flags; // 0: Apply finetune modifier, 2: Don't slide, 7: Continuous
};
struct Channel {
byte *cmdBlockIndices;
byte *cmdBlocks;
byte *cmds;
uint8 ticks;
uint8 tickCount;
Slide volSlide;
Slide periodSlide;
int16 period;
int8 periodMod;
uint8 flags; // 0: Need init, 5: Loop cmdBlocks, 6: Ignore channel
} _chn[4];
void init();
void reset();
void getNextSample(Channel &chn);
int16 tune(Slide &slide, int16 start) const;
virtual void interrupt();
};
} // End of namespace Audio
#endif

1053
audio/mods/maxtrax.cpp Normal file

File diff suppressed because it is too large Load Diff

219
audio/mods/maxtrax.h Normal file
View File

@@ -0,0 +1,219 @@
/* 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/>.
*
*/
// Only compiled if Kyra is built-in or we're building for dynamic modules
#if !defined(AUDIO_MODS_MAXTRAX_H) && (defined(ENABLE_KYRA) || defined(DYNAMIC_MODULES))
#define AUDIO_MODS_MAXTRAX_H
// #define MAXTRAX_HAS_MODULATION
// #define MAXTRAX_HAS_MICROTONAL
#include "audio/mods/paula.h"
namespace Audio {
class MaxTrax : public Paula {
public:
MaxTrax(int rate, bool stereo, uint16 vBlankFreq = 50, uint16 maxScores = 128);
virtual ~MaxTrax();
bool load(Common::SeekableReadStream &musicData, bool loadScores = true, bool loadSamples = true);
bool playSong(int songIndex, bool loop = false);
void advanceSong(int advance = 1);
int playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide);
void setVolume(const byte volume) { Common::StackLock lock(_mutex); _playerCtx.volume = volume; }
void setTempo(const uint16 tempo);
void stopMusic();
/**
* Set a callback function for sync-events.
* @param callback Callback function, will be called synchronously, so DONT modify the player
* directly in response
*/
void setSignalCallback(void (*callback) (int));
protected:
void interrupt();
private:
enum { kNumPatches = 64, kNumVoices = 4, kNumChannels = 16, kNumExtraChannels = 1 };
enum { kPriorityScore, kPriorityNote, kPrioritySound };
#ifdef MAXTRAX_HAS_MICROTONAL
int16 _microtonal[128];
#endif
struct Event {
uint16 startTime;
uint16 stopTime;
byte command;
byte parameter;
};
const struct Score {
const Event *events;
uint32 numEvents;
} *_scores;
int _numScores;
struct {
uint32 sineValue;
uint16 vBlankFreq;
int32 ticks;
int32 tickUnit;
uint16 frameUnit;
uint16 maxScoreNum;
uint16 tempo;
uint16 tempoInitial;
uint16 tempoStart;
int16 tempoDelta;
int32 tempoTime;
int32 tempoTicks;
byte volume;
bool filterOn;
bool handleVolume;
bool musicLoop;
int scoreIndex;
const Event *nextEvent;
int32 nextEventTime;
void (*syncCallBack) (int);
const Event *repeatPoint[4];
byte repeatCount[4];
} _playerCtx;
struct Envelope {
uint16 duration;
uint16 volume;
};
struct Patch {
const Envelope *attackPtr;
//Envelope *releasePtr;
uint16 attackLen;
uint16 releaseLen;
int16 tune;
uint16 volume;
// this was the SampleData struct in the assembler source
const int8 *samplePtr;
uint32 sampleTotalLen;
uint32 sampleAttackLen;
uint16 sampleOctaves;
} _patch[kNumPatches];
struct ChannelContext {
const Patch *patch;
uint16 regParamNumber;
uint16 modulation;
uint16 modulationTime;
int16 microtonal;
uint16 portamentoTime;
int16 pitchBend;
int16 pitchReal;
int8 pitchBendRange;
uint8 volume;
// uint8 voicesActive;
enum {
kFlagRightChannel = 1 << 0,
kFlagPortamento = 1 << 1,
kFlagDamper = 1 << 2,
kFlagMono = 1 << 3,
kFlagMicrotonal = 1 << 4,
kFlagModVolume = 1 << 5
};
byte flags;
bool isAltered;
uint8 lastNote;
// uint8 program;
} _channelCtx[kNumChannels + kNumExtraChannels];
struct VoiceContext {
ChannelContext *channel;
const Patch *patch;
const Envelope *envelope;
// uint32 uinqueId;
int32 preCalcNote;
uint32 ticksLeft;
int32 portaTicks;
int32 incrVolume;
// int32 periodOffset;
uint16 envelopeLeft;
uint16 noteVolume;
uint16 baseVolume;
uint16 lastPeriod;
byte baseNote;
byte endNote;
byte octave;
// byte number;
// byte link;
enum {
kStatusFree,
kStatusHalt,
kStatusDecay,
kStatusRelease,
kStatusSustain,
kStatusAttack,
kStatusStart
};
uint8 isBlocked;
uint8 priority;
byte status;
byte lastVolume;
byte tieBreak;
bool hasDamper;
bool hasPortamento;
byte dmaOff;
int32 stopEventTime;
} _voiceCtx[kNumVoices];
void controlCh(ChannelContext &channel, byte command, byte data);
void freePatches();
void freeScores();
void resetChannel(ChannelContext &chan, bool rightChannel);
void resetPlayer();
int8 pickvoice(uint pick, int16 pri);
uint16 calcNote(const VoiceContext &voice);
int8 noteOn(ChannelContext &channel, byte note, uint16 volume, uint16 pri);
void killVoice(byte num);
void freeResources(bool loadScores, bool loadSamples);
static void outPutEvent(const Event &ev, int num = -1);
static void outPutScore(const Score &sc, int num = -1);
};
} // End of namespace Audio
#endif // !defined(AUDIO_MODS_MAXTRAX_H)

1422
audio/mods/mod_xm_s3m.cpp Normal file

File diff suppressed because it is too large Load Diff

99
audio/mods/mod_xm_s3m.h Normal file
View File

@@ -0,0 +1,99 @@
/* 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/>.
*
*/
/*
* This code is based on IBXM mod player
*
* Copyright (c) 2015, Martin Cameron
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the
* following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* * Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* * Neither the name of the organization nor the names of
* its contributors may be used to endorse or promote
* products derived from this software without specific
* prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef AUDIO_MODS_MOD_XM_S3M_H
#define AUDIO_MODS_MOD_XM_S3M_H
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class AudioStream;
/**
* Factory function for ModXmS3mStream streams. Reads all data from the
* given ReadStream and creates an AudioStream from this. No reference
* to the 'stream' object is kept, so you can safely delete it after
* invoking this factory.
*
* This stream may be infinitely long if the mod contains a loop.
*
* @param stream the ReadStream from which to read the tracker sound data
* @param disposeAfterUse whether to delete the stream after use
* @param initialPos initial track to start playback from
* @param interpolation interpolation effect level
*/
RewindableAudioStream *makeModXmS3mStream(Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse,
int initialPos = 0,
int interpolation = 0);
/**
* Check if the stream is one of the supported formats
*/
bool probeModXmS3m(Common::SeekableReadStream *stream);
} // End of namespace Audio
#endif

250
audio/mods/module.cpp Normal file
View File

@@ -0,0 +1,250 @@
/* 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 "audio/mods/module.h"
#include "common/util.h"
#include "common/endian.h"
#include "common/stream.h"
#include "common/textconsole.h"
namespace Modules {
const int16 Module::periods[16][60] = {
{1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960 , 906,
856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480 , 453,
428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240 , 226,
214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120 , 113,
107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 , 56 },
{1700, 1604, 1514, 1430, 1348, 1274, 1202, 1134, 1070, 1010, 954 , 900,
850 , 802 , 757 , 715 , 674 , 637 , 601 , 567 , 535 , 505 , 477 , 450,
425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 239 , 225,
213 , 201 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119 , 113,
106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 , 56 },
{1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948 , 894,
844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474 , 447,
422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237 , 224,
211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118 , 112,
105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 , 56 },
{1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940 , 888,
838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470 , 444,
419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235 , 222,
209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118 , 111,
104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 , 55 },
{1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 990 , 934 , 882,
832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 495 , 467 , 441,
416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233 , 220,
208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 124 , 117 , 110,
104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 62 , 58 , 55 },
{1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926 , 874,
826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463 , 437,
413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232 , 219,
206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116 , 109,
103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 , 54 },
{1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920 , 868,
820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460 , 434,
410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230 , 217,
205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115 , 109,
102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 , 54 },
{1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914 , 862,
814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457 , 431,
407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228 , 216,
204 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114 , 108,
102 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 , 54 },
{1814, 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960,
907 , 856 , 808 , 762 , 720 , 678 , 640 , 604 , 570 , 538 , 508 , 480,
453 , 428 , 404 , 381 , 360 , 339 , 320 , 302 , 285 , 269 , 254 , 240,
226 , 214 , 202 , 190 , 180 , 170 , 160 , 151 , 143 , 135 , 127 , 120,
113 , 107 , 101 , 95 , 90 , 85 , 80 , 75 , 71 , 67 , 63 , 60 },
{1800, 1700, 1604, 1514, 1430, 1350, 1272, 1202, 1134, 1070, 1010, 954,
900 , 850 , 802 , 757 , 715 , 675 , 636 , 601 , 567 , 535 , 505 , 477,
450 , 425 , 401 , 379 , 357 , 337 , 318 , 300 , 284 , 268 , 253 , 238,
225 , 212 , 200 , 189 , 179 , 169 , 159 , 150 , 142 , 134 , 126 , 119,
112 , 106 , 100 , 94 , 89 , 84 , 79 , 75 , 71 , 67 , 63 , 59 },
{1788, 1688, 1592, 1504, 1418, 1340, 1264, 1194, 1126, 1064, 1004, 948,
894 , 844 , 796 , 752 , 709 , 670 , 632 , 597 , 563 , 532 , 502 , 474,
447 , 422 , 398 , 376 , 355 , 335 , 316 , 298 , 282 , 266 , 251 , 237,
223 , 211 , 199 , 188 , 177 , 167 , 158 , 149 , 141 , 133 , 125 , 118,
111 , 105 , 99 , 94 , 88 , 83 , 79 , 74 , 70 , 66 , 62 , 59 },
{1774, 1676, 1582, 1492, 1408, 1330, 1256, 1184, 1118, 1056, 996 , 940,
887 , 838 , 791 , 746 , 704 , 665 , 628 , 592 , 559 , 528 , 498 , 470,
444 , 419 , 395 , 373 , 352 , 332 , 314 , 296 , 280 , 264 , 249 , 235,
222 , 209 , 198 , 187 , 176 , 166 , 157 , 148 , 140 , 132 , 125 , 118,
111 , 104 , 99 , 93 , 88 , 83 , 78 , 74 , 70 , 66 , 62 , 59 },
{1762, 1664, 1570, 1482, 1398, 1320, 1246, 1176, 1110, 1048, 988 , 934,
881 , 832 , 785 , 741 , 699 , 660 , 623 , 588 , 555 , 524 , 494 , 467,
441 , 416 , 392 , 370 , 350 , 330 , 312 , 294 , 278 , 262 , 247 , 233,
220 , 208 , 196 , 185 , 175 , 165 , 156 , 147 , 139 , 131 , 123 , 117,
110 , 104 , 98 , 92 , 87 , 82 , 78 , 73 , 69 , 65 , 61 , 58 },
{1750, 1652, 1558, 1472, 1388, 1310, 1238, 1168, 1102, 1040, 982 , 926,
875 , 826 , 779 , 736 , 694 , 655 , 619 , 584 , 551 , 520 , 491 , 463,
437 , 413 , 390 , 368 , 347 , 328 , 309 , 292 , 276 , 260 , 245 , 232,
219 , 206 , 195 , 184 , 174 , 164 , 155 , 146 , 138 , 130 , 123 , 116,
109 , 103 , 97 , 92 , 87 , 82 , 77 , 73 , 69 , 65 , 61 , 58 },
{1736, 1640, 1548, 1460, 1378, 1302, 1228, 1160, 1094, 1032, 974 , 920,
868 , 820 , 774 , 730 , 689 , 651 , 614 , 580 , 547 , 516 , 487 , 460,
434 , 410 , 387 , 365 , 345 , 325 , 307 , 290 , 274 , 258 , 244 , 230,
217 , 205 , 193 , 183 , 172 , 163 , 154 , 145 , 137 , 129 , 122 , 115,
108 , 102 , 96 , 91 , 86 , 81 , 77 , 72 , 68 , 64 , 61 , 57 },
{1724, 1628, 1536, 1450, 1368, 1292, 1220, 1150, 1086, 1026, 968 , 914,
862 , 814 , 768 , 725 , 684 , 646 , 610 , 575 , 543 , 513 , 484 , 457,
431 , 407 , 384 , 363 , 342 , 323 , 305 , 288 , 272 , 256 , 242 , 228,
216 , 203 , 192 , 181 , 171 , 161 , 152 , 144 , 136 , 128 , 121 , 114,
108 , 101 , 96 , 90 , 85 , 80 , 76 , 72 , 68 , 64 , 60 , 57 }};
const uint32 Module::signatures[] = {
MKTAG('M','.','K','.'), MKTAG('M','!','K','!'), MKTAG('F','L','T','4')
};
bool Module::load(Common::SeekableReadStream &st, int offs) {
if (offs) {
// Load the module with the common sample data
load(st, 0);
}
st.seek(offs);
st.read(songname, 20);
songname[20] = '\0';
for (int i = 0; i < NUM_SAMPLES; ++i) {
st.read(sample[i].name, 22);
sample[i].name[22] = '\0';
sample[i].len = 2 * st.readUint16BE();
sample[i].finetune = st.readByte();
assert(sample[i].finetune < 0x10);
sample[i].vol = st.readByte();
sample[i].repeat = 2 * st.readUint16BE();
sample[i].replen = 2 * st.readUint16BE();
}
songlen = st.readByte();
undef = st.readByte();
st.read(songpos, 128);
sig = st.readUint32BE();
bool foundSig = false;
for (int i = 0; i < ARRAYSIZE(signatures); i++) {
if (sig == signatures[i]) {
foundSig = true;
break;
}
}
if (!foundSig) {
warning("No known signature found in protracker module");
return false;
}
int maxpattern = 0;
for (int i = 0; i < 128; ++i)
if (maxpattern < songpos[i])
maxpattern = songpos[i];
pattern = new pattern_t[maxpattern + 1];
for (int i = 0; i <= maxpattern; ++i) {
for (int j = 0; j < 64; ++j) {
for (int k = 0; k < 4; ++k) {
uint32 note = st.readUint32BE();
pattern[i][j][k].sample = (note & 0xf0000000) >> 24 | (note & 0x0000f000) >> 12;
pattern[i][j][k].period = (note >> 16) & 0xfff;
pattern[i][j][k].effect = note & 0xfff;
pattern[i][j][k].note = periodToNote((note >> 16) & 0xfff);
}
}
}
for (int i = 0; i < NUM_SAMPLES; ++i) {
if (offs) {
// Restore information for modules that use common sample data
for (int j = 0; j < NUM_SAMPLES; ++j) {
if (!scumm_stricmp((const char *)commonSamples[j].name, (const char *)sample[i].name)) {
sample[i].len = commonSamples[j].len;
st.seek(commonSamples[j].offs);
break;
}
}
} else {
// Store information for modules that use common sample data
memcpy(commonSamples[i].name, sample[i].name, 22);
commonSamples[i].len = sample[i].len;
commonSamples[i].offs = st.pos();
}
if (!sample[i].len) {
sample[i].data = nullptr;
} else {
sample[i].data = new int8[sample[i].len];
st.read((byte *)sample[i].data, sample[i].len);
}
}
return true;
}
Module::Module() {
pattern = nullptr;
for (int i = 0; i < NUM_SAMPLES; ++i) {
sample[i].data = nullptr;
}
}
Module::~Module() {
delete[] pattern;
for (int i = 0; i < NUM_SAMPLES; ++i) {
delete[] sample[i].data;
}
}
byte Module::periodToNote(int16 period, byte finetune) {
int16 diff1;
int16 diff2;
diff1 = ABS(periods[finetune][0] - period);
if (diff1 == 0)
return 0;
for (int i = 1; i < 60; i++) {
diff2 = ABS(periods[finetune][i] - period);
if (diff2 == 0)
return i;
else if (diff2 > diff1)
return i-1;
diff1 = diff2;
}
return 59;
}
int16 Module::noteToPeriod(byte note, byte finetune) {
if (finetune > 15)
finetune = 15;
if (note > 59)
note = 59;
return periods[finetune][note];
}
} // End of namespace Modules

90
audio/mods/module.h Normal file
View File

@@ -0,0 +1,90 @@
/* 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/>.
*
*/
#ifndef AUDIO_MODS_MODULE_H
#define AUDIO_MODS_MODULE_H
#include "common/scummsys.h"
namespace Common {
class SeekableReadStream;
}
namespace Modules {
#include "common/pack-start.h" // START STRUCT PACKING
struct note_t {
byte sample;
byte note;
uint16 period;
uint16 effect;
} PACKED_STRUCT;
#include "common/pack-end.h" // END STRUCT PACKING
typedef note_t pattern_t[64][4];
struct sample_t {
byte name[23];
uint16 len;
byte finetune;
byte vol;
uint16 repeat;
uint16 replen;
int8 *data;
};
struct sample_offs {
byte name[23];
uint16 len;
uint32 offs;
};
class Module {
public:
byte songname[21];
static const int NUM_SAMPLES = 31;
sample_t sample[NUM_SAMPLES];
sample_offs commonSamples[NUM_SAMPLES];
byte songlen;
byte undef;
byte songpos[128];
uint32 sig;
pattern_t *pattern;
Module();
virtual ~Module();
virtual bool load(Common::SeekableReadStream &stream, int offs);
static byte periodToNote(int16 period, byte finetune = 0);
static int16 noteToPeriod(byte note, byte finetune = 0);
protected:
static const int16 periods[16][60];
static const uint32 signatures[];
};
} // End of namespace Modules
#endif

View File

@@ -0,0 +1,951 @@
/* 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/>.
*
*/
/*
* This code is based on IBXM mod player
*
* Copyright (c) 2015, Martin Cameron
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the
* following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* * Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* * Neither the name of the organization nor the names of
* its contributors may be used to endorse or promote
* products derived from this software without specific
* prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "common/debug.h"
#include "common/endian.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "module_mod_xm_s3m.h"
namespace Modules {
const int ModuleModXmS3m::FP_SHIFT = 0xF;
const int ModuleModXmS3m::FP_ONE = 0x8000;
const int ModuleModXmS3m::FP_MASK = 0x7FFF;
const int ModuleModXmS3m::exp2table[] = {
32768, 32946, 33125, 33305, 33486, 33667, 33850, 34034,
34219, 34405, 34591, 34779, 34968, 35158, 35349, 35541,
35734, 35928, 36123, 36319, 36516, 36715, 36914, 37114,
37316, 37518, 37722, 37927, 38133, 38340, 38548, 38757,
38968, 39180, 39392, 39606, 39821, 40037, 40255, 40473,
40693, 40914, 41136, 41360, 41584, 41810, 42037, 42265,
42495, 42726, 42958, 43191, 43425, 43661, 43898, 44137,
44376, 44617, 44859, 45103, 45348, 45594, 45842, 46091,
46341, 46593, 46846, 47100, 47356, 47613, 47871, 48131,
48393, 48655, 48920, 49185, 49452, 49721, 49991, 50262,
50535, 50810, 51085, 51363, 51642, 51922, 52204, 52488,
52773, 53059, 53347, 53637, 53928, 54221, 54515, 54811,
55109, 55408, 55709, 56012, 56316, 56622, 56929, 57238,
57549, 57861, 58176, 58491, 58809, 59128, 59449, 59772,
60097, 60423, 60751, 61081, 61413, 61746, 62081, 62419,
62757, 63098, 63441, 63785, 64132, 64480, 64830, 65182,
65536
};
int ModuleModXmS3m::moduleExp2(int x) {
int c, m, y;
int x0 = (x & FP_MASK) >> (FP_SHIFT - 7);
c = exp2table[x0];
m = exp2table[x0 + 1] - c;
y = (m * (x & (FP_MASK >> 7)) >> 8) + c;
return (y << FP_SHIFT) >> (FP_SHIFT - (x >> FP_SHIFT));
}
int ModuleModXmS3m::moduleLog2(int x) {
int y = 16 << FP_SHIFT;
for (int step = y; step > 0; step >>= 1) {
if (moduleExp2(y - step) >= x) {
y -= step;
}
}
return y;
}
bool ModuleModXmS3m::load(Common::SeekableReadStream &st) {
int32 setPos = st.pos();
// xm file
char sigXm[18] = { 0 };
st.read(sigXm, 17);
if (!memcmp(sigXm, "Extended Module: ", 17)) {
return loadXm(st);
}
st.seek(setPos);
// s3m file
char sigS3m[4];
st.skip(44);
st.read(sigS3m, 4);
if (!memcmp(sigS3m, "SCRM", 4)) {
st.seek(setPos);
return loadS3m(st);
}
st.seek(setPos);
// amf file
char sigAmf[25] = {};
st.read(sigAmf, 24);
if (!memcmp(sigAmf, "ASYLUM Music Format V1.0", 24)) {
return loadAmf(st);
}
st.seek(setPos);
// mod file
return loadMod(st);
}
ModuleModXmS3m::ModuleModXmS3m() {
sequenceLen = 1;
sequence = nullptr;
restartPos = 0;
// patterns
numChannels = 4;
numPatterns = 1;
patterns = nullptr;
// instruments
numInstruments = 1;
instruments = nullptr;
// others
defaultGvol = 64;
defaultSpeed = 6;
defaultTempo = 125;
c2Rate = 8287;
gain = 64;
linearPeriods = false;
fastVolSlides = false;
defaultPanning = nullptr; //{ 51, 204, 204, 51 };
}
ModuleModXmS3m::~ModuleModXmS3m() {
// free song position
if (sequence) {
delete[] sequence;
sequence = nullptr;
}
// free instruments
if (instruments) {
for (int i = 0; i <= numInstruments; ++i) {
// free samples
for (int j = 0; j < instruments[i].numSamples; ++j) {
if (instruments[i].samples[j].data) {
delete[] instruments[i].samples[j].data;
instruments[i].samples[j].data = nullptr;
}
}
delete[] instruments[i].samples;
instruments[i].samples = nullptr;
}
delete[] instruments;
instruments = nullptr;
}
// free patterns
if (patterns) {
for (int i = 0; i < numPatterns; ++i) {
delete []patterns[i].notes;
}
delete[] patterns;
patterns = nullptr;
}
// free default values
if (defaultPanning) {
delete[] defaultPanning;
defaultPanning = nullptr;
}
}
bool ModuleModXmS3m::loadMod(Common::SeekableReadStream &st) {
// load song name
st.read(name, 20);
name[20] = '\0';
// load instruments
numInstruments = 31;
instruments = new Instrument[numInstruments + 1]();
instruments[0].numSamples = 1;
instruments[0].samples = new Sample[1];
memset(&instruments[0].samples[0], 0, sizeof(Sample));
for (int i = 1; i <= numInstruments; ++i) {
instruments[i].numSamples = 1;
instruments[i].samples = new Sample[1];
memset(&instruments[i].samples[0], 0, sizeof(Sample));
// load sample
Sample &sample = instruments[i].samples[0];
st.read((byte *)sample.name, 22);
sample.name[22] = '\0';
sample.length = 2 * st.readUint16BE();
sample.finetune = st.readByte() & 0xF;
sample.volume = st.readByte();
sample.loopStart = 2 * st.readUint16BE();
sample.loopLength = 2 * st.readUint16BE();
if (sample.loopStart + sample.loopLength > sample.length) {
sample.loopLength = sample.length - sample.loopStart;
}
if (sample.loopLength < 4) {
sample.loopStart = sample.length;
sample.loopLength = 0;
}
}
sequenceLen = st.readByte();
if (sequenceLen > 128)
sequenceLen = 128;
restartPos = 0;
st.readByte(); // undefined byte, should be 127
sequence = new byte[128];
st.read(sequence, 128);
// check signature
byte xx[2];
st.read(xx, 2); // first 2 bytes of the signature
switch (st.readUint16BE()) {
case MKTAG16('K', '.'): /* M.K. */
// Fall Through intended
case MKTAG16('K', '!'): /* M!K! */
// Fall Through intended
case MKTAG16('T', '4'): /* FLT4 */
// Fall Through intended
numChannels = 4;
c2Rate = 8287;
gain = 64;
break;
case MKTAG16('H', 'N'): /* xCHN */
numChannels = xx[0] - '0';
c2Rate = 8363;
gain = 32;
break;
case MKTAG16('C', 'H'): /* xxCH */
numChannels = (xx[0] - '0') * 10 + xx[1] - '0';
c2Rate = 8363;
gain = 32;
break;
default:
warning("No known signature found in micromod module");
return false;
}
// default values
defaultGvol = 64;
defaultSpeed = 6;
defaultTempo = 125;
defaultPanning = new byte[numChannels];
for (int i = 0; i < numChannels; ++i) {
defaultPanning[i] = 51;
if ((i & 3) == 1 || (i & 3) == 2) {
defaultPanning[i] = 204;
}
}
// load patterns
numPatterns = 0;
for (int i = 0; i < 128; ++i)
if (numPatterns < sequence[i])
numPatterns = sequence[i];
++numPatterns;
// load patterns
patterns = new Pattern[numPatterns];
for (int i = 0; i < numPatterns; ++i) {
patterns[i].numChannels = numChannels;
patterns[i].numRows = 64;
// load notes
/*
Old (amiga) noteinfo:
_____byte 1_____ byte2_ _____byte 3_____ byte4_
/ \ / \ / \ / \
0000 0000-00000000 0000 0000-00000000
Upper four 12 bits for Lower four Effect command.
bits of sam- note period. bits of sam-
ple number. ple number.
*/
int numNotes = patterns[i].numChannels * patterns[i].numRows;
patterns[i].notes = new Note[numNotes]();
for (int idx = 0; idx < numNotes; ++idx) {
byte first = st.readByte();
byte second = st.readByte();
byte third = st.readByte();
byte fourth = st.readByte();
// period, key
uint period = (first & 0xF) << 8;
period = (period | second) * 4;
if (period >= 112 && period <= 6848) {
int key = -12 * moduleLog2((period << FP_SHIFT) / 29021);
key = (key + (key & (FP_ONE >> 1))) >> FP_SHIFT;
patterns[i].notes[idx].key = key;
}
// instrument
uint ins = (third & 0xF0) >> 4;
ins = ins | (first & 0x10);
patterns[i].notes[idx].instrument = ins;
// effect, param
byte effect = third & 0x0F;
byte param = fourth & 0xff;
if (param == 0 && (effect < 3 || effect == 0xA)) {
effect = 0;
}
if (param == 0 && (effect == 5 || effect == 6)) {
effect -= 2;
}
if (effect == 8) {
if (numChannels == 4) {
effect = param = 0;
} else if (param > 128) {
param = 128;
} else {
param = (param * 255) >> 7;
}
}
patterns[i].notes[idx].effect = effect;
patterns[i].notes[idx].param = param;
}
}
// load data for the sample of instruments
for (int i = 1; i <= numInstruments; ++i) {
Sample &sample = instruments[i].samples[0];
if (!sample.length) {
sample.data = nullptr;
} else {
sample.data = new int16[sample.length + 1];
readSampleSint8(st, sample.length, sample.data);
sample.data[sample.loopStart + sample.loopLength] = sample.data[sample.loopStart];
}
}
return true;
}
bool ModuleModXmS3m::loadXm(Common::SeekableReadStream &st) {
st.read(name, 20);
name[20] = '\0';
st.readByte(); // reserved byte
byte trackername[20];
st.read(trackername, 20);
bool deltaEnv = !memcmp(trackername, "DigiBooster Pro", 15);
uint16 version = st.readUint16LE();
if (version != 0x0104) {
warning("XM format version must be 0x0104!");
return false;
}
uint offset = st.pos() + st.readUint32LE();
sequenceLen = st.readUint16LE();
restartPos = st.readUint16LE();
numChannels = st.readUint16LE();
numPatterns = st.readUint16LE();
numInstruments = st.readUint16LE();
linearPeriods = st.readUint16LE() & 0x1;
defaultGvol = 64;
defaultSpeed = st.readUint16LE();
defaultTempo = st.readUint16LE();
c2Rate = 8363;
gain = 64;
defaultPanning = new byte[numChannels];
for (int i = 0; i < numChannels; ++i) {
defaultPanning[i] = 128;
}
sequence = new byte[sequenceLen];
for (uint i = 0; i < sequenceLen; ++i) {
int entry = st.readByte();
sequence[i] = entry < numPatterns ? entry : 0;
}
// load patterns
patterns = new Pattern[numPatterns];
for (int i = 0; i < numPatterns; ++i) {
st.seek(offset, SEEK_SET);
offset += st.readUint32LE();
if (st.readByte()) {
warning("Unknown pattern packing type!");
return false;
}
patterns[i].numRows = st.readUint16LE();
if (patterns[i].numRows < 1)
patterns[i].numRows = 1;
uint16 patDataLength = st.readUint16LE();
offset += patDataLength;
// load notes
patterns[i].numChannels = numChannels;
int numNotes = patterns[i].numRows * numChannels;
patterns[i].notes = new Note[numNotes]();
if (patDataLength > 0) {
for (int j = 0; j < numNotes; ++j) {
Note &note = patterns[i].notes[j];
byte cmp = st.readByte();
if (cmp & 0x80) {
if (cmp & 1)
note.key = st.readByte();
if (cmp & 2)
note.instrument = st.readByte();
if (cmp & 4)
note.volume = st.readByte();
if (cmp & 8)
note.effect = st.readByte();
if (cmp & 16)
note.param = st.readByte();
} else {
note.key = cmp;
note.instrument = st.readByte();
note.volume = st.readByte();
note.effect = st.readByte();
note.param = st.readByte();
}
if( note.effect >= 0x40 ) {
note.effect = note.param = 0;
}
}
}
}
// load instruments
instruments = new Instrument[numInstruments + 1]();
instruments[0].samples = new Sample[1]();
for (int i = 1; i <= numInstruments; ++i) {
st.seek(offset, SEEK_SET);
offset += st.readUint32LE();
Instrument &ins = instruments[i];
st.read(ins.name, 22);
ins.name[22] = '\0';
st.readByte(); // Instrument type (always 0)
// load sample number
int nSamples = st.readUint16LE();
ins.numSamples = nSamples > 0 ? nSamples : 1;
ins.samples = new Sample[ins.numSamples]();
st.readUint32LE(); // skip 4 byte
// load instrument informations
if (nSamples > 0) {
for (int k = 0; k < 96; ++k) {
ins.keyToSample[k + 1] = st.readByte();
}
int pointTick = 0;
for (int p = 0; p < 12; ++p) {
pointTick = (deltaEnv ? pointTick : 0) + st.readUint16LE();
ins.volEnv.pointsTick[p] = pointTick;
ins.volEnv.pointsAmpl[p] = st.readUint16LE();
}
pointTick = 0;
for (int p = 0; p < 12; ++p) {
pointTick = (deltaEnv ? pointTick : 0) + st.readUint16LE();
ins.panEnv.pointsTick[p] = pointTick;
ins.panEnv.pointsAmpl[p] = st.readUint16LE();
}
ins.volEnv.numPoints = st.readByte();
if (ins.volEnv.numPoints > 12)
ins.volEnv.numPoints = 0;
ins.panEnv.numPoints = st.readByte();
if (ins.panEnv.numPoints > 12)
ins.panEnv.numPoints = 0;
ins.volEnv.sustainTick = ins.volEnv.pointsTick[st.readByte() & 0xF];
ins.volEnv.loopStartTick = ins.volEnv.pointsTick[st.readByte() & 0xF];
ins.volEnv.loopEndTick = ins.volEnv.pointsTick[st.readByte() & 0xF];
ins.panEnv.sustainTick = ins.panEnv.pointsTick[st.readByte() & 0xF];
ins.panEnv.loopStartTick = ins.panEnv.pointsTick[st.readByte() & 0xF];
ins.panEnv.loopEndTick = ins.panEnv.pointsTick[st.readByte() & 0xF];
byte volParam = st.readByte();
ins.volEnv.enabled = ins.volEnv.numPoints > 0 && (volParam & 0x1);
ins.volEnv.sustain = (volParam & 0x2) > 0;
ins.volEnv.looped = (volParam & 0x4) > 0;
byte panParam = st.readByte();
ins.panEnv.enabled = ins.panEnv.numPoints > 0 && (panParam & 0x1);
ins.panEnv.sustain = (panParam & 0x2) > 0;
ins.panEnv.looped = (panParam & 0x4) > 0;
ins.vibType = st.readByte();
ins.vibSweep = st.readByte();
ins.vibDepth = st.readByte();
ins.vibRate = st.readByte();
ins.volFadeout = st.readUint16LE();
}
// load samples
uint samHeadOffset = offset;
offset += nSamples * 40; // offset for sample data
for (int j = 0; j < nSamples; ++j) {
// load sample head
st.seek(samHeadOffset, SEEK_SET);
samHeadOffset += 40; // increment
Sample &sample = ins.samples[j];
uint samDataBytes = st.readUint32LE();
uint samLoopStart = st.readUint32LE();
uint samLoopLength = st.readUint32LE();
sample.volume = st.readByte();
sample.finetune = st.readSByte();
byte loopType = st.readByte();
bool looped = (loopType & 0x3) > 0;
bool pingPong = (loopType & 0x2) > 0;
bool sixteenBit = (loopType & 0x10) > 0;
sample.panning = st.readByte() + 1;
sample.relNote = st.readSByte();
st.readByte(); // reserved byte
st.read(sample.name, 22);
sample.name[22] = '\0';
uint samDataSamples = samDataBytes;
if (sixteenBit) {
samDataSamples = samDataSamples >> 1;
samLoopStart = samLoopStart >> 1;
samLoopLength = samLoopLength >> 1;
}
if (!looped || (samLoopStart + samLoopLength) > samDataSamples) {
samLoopStart = samDataSamples;
samLoopLength = 0;
}
sample.loopStart = samLoopStart;
sample.loopLength = samLoopLength;
// load sample data
st.seek(offset, SEEK_SET);
offset += samDataBytes; // increment
sample.data = new int16[samDataSamples + 1];
if (sixteenBit) {
readSampleSint16LE(st, samDataSamples, sample.data);
} else {
readSampleSint8(st, samDataSamples, sample.data);
}
int amp = 0;
for (uint idx = 0; idx < samDataSamples; idx++) {
amp = amp + sample.data[idx];
amp = (amp & 0x7FFF) - (amp & 0x8000);
sample.data[idx] = amp;
}
sample.data[samLoopStart + samLoopLength] = sample.data[samLoopStart];
if (pingPong) {
SamplePingPong(sample);
}
}
}
return true;
}
bool ModuleModXmS3m::loadS3m(Common::SeekableReadStream &st) {
st.read(name, 28);
name[28] = '\0';
st.skip(4); // skip 4 bytes
sequenceLen = st.readUint16LE();
numInstruments = st.readUint16LE();
numPatterns = st.readUint16LE();
uint16 flags = st.readUint16LE();
uint16 version = st.readUint16LE();
fastVolSlides = ((flags & 0x40) == 0x40) || version == 0x1300;
bool signedSamples = st.readUint16LE() == 1;
// check signature
if (st.readUint32BE() != MKTAG('S', 'C', 'R', 'M')) {
warning("Not an S3M file!");
return false;
}
defaultGvol = st.readByte();
defaultSpeed = st.readByte();
defaultTempo = st.readByte();
c2Rate = 8363;
byte mastermult = st.readByte();
gain = mastermult & 0x7F;
bool stereoMode = (mastermult & 0x80) == 0x80;
st.readByte(); // skip ultra-click
bool defaultPan = st.readByte() == 0xFC;
st.skip(10); // skip 10 bytes
// load channel map
numChannels = 0;
int channelMap[32];
for (int i = 0; i < 32; ++i) {
channelMap[i] = -1;
if (st.readByte() < 16) {
channelMap[i] = numChannels++;
}
}
// load sequence
sequence = new byte[sequenceLen];
st.read(sequence, sequenceLen);
int moduleDataIndex = st.pos();
// load instruments
instruments = new Instrument[numInstruments + 1]();
instruments[0].numSamples = 1;
instruments[0].samples = new Sample[1]();
for (int i = 1; i <= numInstruments; ++i) {
Instrument &instrum = instruments[i];
instrum.numSamples = 1;
instrum.samples = new Sample[1]();
Sample &sample = instrum.samples[0];
// get instrument offset
st.seek(moduleDataIndex, SEEK_SET);
int instOffset = st.readUint16LE() << 4;
moduleDataIndex += 2;
st.seek(instOffset, SEEK_SET);
// load instrument, sample
if (st.readByte() == 1) { // type
st.skip(12); // skip file name
int sampleOffset = (st.readByte() << 20) + (st.readUint16LE() << 4);
uint sampleLength = st.readUint32LE();
uint loopStart = st.readUint32LE();
uint loopLength = st.readUint32LE() - loopStart;
sample.volume = st.readByte();
st.skip(1); // skip dsk
if (st.readByte() != 0) {
warning("Packed samples not supported for S3M files");
return false;
}
byte samParam = st.readByte();
if (loopStart + loopLength > sampleLength) {
loopLength = sampleLength - loopStart;
}
if (loopLength < 1 || !(samParam & 0x1)) {
loopStart = sampleLength;
loopLength = 0;
}
sample.loopStart = loopStart;
sample.loopLength = loopLength;
bool sixteenBit = samParam & 0x4;
int tune = (moduleLog2(st.readUint32LE()) - moduleLog2(c2Rate)) * 12;
sample.relNote = tune >> FP_SHIFT;
sample.finetune = (tune & FP_MASK) >> (FP_SHIFT - 7);
st.skip(12); // skip unused bytes
st.read(instrum.name, 28);
// load sample data
sample.data = new int16[sampleLength + 1];
st.seek(sampleOffset, SEEK_SET);
if (sixteenBit) {
readSampleSint16LE(st, sampleLength, sample.data);
} else {
readSampleSint8(st, sampleLength, sample.data);
}
if (!signedSamples) {
for (uint idx = 0; idx < sampleLength; ++idx) {
sample.data[idx] = (sample.data[idx] & 0xFFFF) - 32768;
}
}
sample.data[loopStart + loopLength] = sample.data[loopStart];
}
}
// load patterns
patterns = new Pattern[numPatterns]();
for (int i = 0; i < numPatterns; ++i) {
patterns[i].numChannels = numChannels;
patterns[i].numRows = 64;
// get pattern data offset
st.seek(moduleDataIndex, SEEK_SET);
int patOffset = (st.readUint16LE() << 4) + 2;
st.seek(patOffset, SEEK_SET);
// load notes
patterns[i].notes = new Note[numChannels * 64]();
int row = 0;
while (row < 64) {
byte token = st.readByte();
if (token) {
byte key = 0;
byte ins = 0;
if ((token & 0x20) == 0x20) {
/* Key + Instrument.*/
key = st.readByte();
ins = st.readByte();
if (key < 0xFE) {
key = (key >> 4) * 12 + (key & 0xF) + 1;
} else if (key == 0xFF) {
key = 0;
}
}
byte volume = 0;
if ((token & 0x40) == 0x40) {
/* Volume Column.*/
volume = (st.readByte() & 0x7F) + 0x10;
if (volume > 0x50) {
volume = 0;
}
}
byte effect = 0;
byte param = 0;
if ((token & 0x80) == 0x80) {
/* Effect + Param.*/
effect = st.readByte();
param = st.readByte();
if (effect < 1 || effect >= 0x40) {
effect = param = 0;
} else if (effect > 0) {
effect += 0x80;
}
}
int chan = channelMap[token & 0x1F];
if (chan >= 0) {
int noteIndex = row * numChannels + chan;
patterns[i].notes[noteIndex].key = key;
patterns[i].notes[noteIndex].instrument = ins;
patterns[i].notes[noteIndex].volume = volume;
patterns[i].notes[noteIndex].effect = effect;
patterns[i].notes[noteIndex].param = param;
}
} else {
row++;
}
}
// increment index
moduleDataIndex += 2;
}
// load default panning
defaultPanning = new byte[numChannels]();
for (int chan = 0; chan < 32; ++chan) {
if (channelMap[chan] >= 0) {
byte panning = 7;
if (stereoMode) {
panning = 12;
st.seek(64 + chan, SEEK_SET);
if (st.readByte() < 8) {
panning = 3;
}
}
if (defaultPan) {
st.seek(moduleDataIndex + chan, SEEK_SET);
flags = st.readByte();
if ((flags & 0x20) == 0x20) {
panning = flags & 0xF;
}
}
defaultPanning[channelMap[chan]] = panning * 17;
}
}
return true;
}
bool ModuleModXmS3m::loadAmf(Common::SeekableReadStream &st) {
// already skipped the signature ("ASYLUM Music Format V1.0")
// total signature length is 32 bytes (the rest are null)
st.skip(8);
memcpy(name, "Asylum Module", 14);
numChannels = 8;
defaultSpeed = st.readByte();
defaultTempo = st.readByte();
numInstruments = st.readByte(); // actually number of samples, but we'll do 1:1 mapping
numPatterns = st.readByte();
sequenceLen = st.readByte();
restartPos = st.readByte();
sequence = new byte[256];
st.read(sequence, 256); // Always 256 bytes in the file.
// Read sample headers..
instruments = new Instrument[numInstruments + 1]();
instruments[0].numSamples = 1;
instruments[0].samples = new Sample[1];
memset(&instruments[0].samples[0], 0, sizeof(Sample));
for (int i = 1; i <= numInstruments; ++i) {
instruments[i].numSamples = 1;
instruments[i].samples = new Sample[1];
memset(&instruments[i].samples[0], 0, sizeof(Sample));
// load sample
Sample &sample = instruments[i].samples[0];
st.read((byte *)sample.name, 22);
sample.name[22] = '\0';
sample.finetune = st.readSByte();
sample.volume = st.readByte();
sample.relNote = st.readSByte(); // aka "transpose"
sample.length = st.readUint32LE();
sample.loopStart = st.readUint32LE();
sample.loopLength = st.readUint32LE();
if (sample.loopStart + sample.loopLength > sample.length) {
sample.loopLength = sample.length - sample.loopStart;
}
if (sample.loopLength < 4) {
sample.loopStart = sample.length;
sample.loopLength = 0;
}
// Sample data comes later.
}
st.skip((64 - numInstruments) * 37); // 37 == sample header len
// load patterns
patterns = new Pattern[numPatterns]();
for (int i = 0; i < numPatterns; ++i) {
// Always 8 channels, 64 rows.
patterns[i].numChannels = 8;
patterns[i].numRows = 64;
// load notes
patterns[i].notes = new Note[8 * 64]();
for (int row = 0; row < 64; row++) {
for (int channel = 0; channel < 8; channel++) {
Note &n = patterns[i].notes[row * 8 + channel];
uint8 note = st.readByte();
if (note != 0) {
note = note + 1;
}
n.key = note;
n.instrument = st.readByte();
n.effect = st.readByte();
n.param = st.readByte();
// TODO: copied from libmodplug .. is this needed?
if (n.effect < 1 || n.effect > 0x0f) {
n.effect = n.param = 0;
}
// TODO: copied from mod loader.. is this needed?
if (n.param == 0 && (n.effect < 3 || n.effect == 0xA))
n.effect = 0;
if (n.param == 0 && (n.effect == 5 || n.effect == 6))
n.effect -= 2;
if (n.effect == 8) {
if (n.param > 128) {
n.param = 128;
} else {
n.param = (n.param * 255) >> 7;
}
}
}
}
}
// Load sample data
for (int i = 1; i <= numInstruments; ++i) {
Sample &sample = instruments[i].samples[0];
sample.data = new int16[sample.length];
readSampleSint8(st, sample.length, sample.data);
}
// default to panning to middle?
defaultPanning = new byte[numChannels];
for (int i = 0; i < numChannels; ++i) {
defaultPanning[i] = 128;
}
return true;
}
void ModuleModXmS3m::readSampleSint8(Common::SeekableReadStream &stream, int length, int16 *dest) {
for (int i = 0; i < length; ++i) {
dest[i] = static_cast<int16>(stream.readSByte() * 256);
dest[i] = (dest[i] & 0x7FFF) - (dest[i] & 0x8000);
}
}
void ModuleModXmS3m::readSampleSint16LE(Common::SeekableReadStream &stream, int length, int16 *dest) {
for (int i = 0; i < length; ++i) {
dest[i] = stream.readSint16LE();
dest[i] = (dest[i] & 0x7FFF) - (dest[i] & 0x8000);
}
}
void ModuleModXmS3m::SamplePingPong(Sample &sample) {
int loopStart = sample.loopStart;
int loopLength = sample.loopLength;
int loopEnd = loopStart + loopLength;
int16 *sampleData = sample.data;
int16 *newData = new int16[loopEnd + loopLength + 1];
if (newData) {
memcpy(newData, sampleData, loopEnd * sizeof(int16));
for (int idx = 0; idx < loopLength; idx++) {
newData[loopEnd + idx] = sampleData[loopEnd - idx - 1];
}
delete []sample.data;
sample.data = newData;
sample.loopLength *= 2;
sample.data[loopStart + sample.loopLength] = sample.data[loopStart];
}
}
} // End of namespace Modules

View File

@@ -0,0 +1,174 @@
/* 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/>.
*
*/
/*
* This code is based on IBXM mod player
*
* Copyright (c) 2015, Martin Cameron
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the
* following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
*
* * Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* * Neither the name of the organization nor the names of
* its contributors may be used to endorse or promote
* products derived from this software without specific
* prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef AUDIO_MODS_MODULE_MOD_XM_S3M_H
#define AUDIO_MODS_MODULE_MOD_XM_S3M_H
#include "common/scummsys.h"
namespace Common {
class SeekableReadStream;
}
namespace Modules {
struct Note {
byte key;
byte instrument;
byte volume;
byte effect; // effect type
byte param; // parameter of effect
};
struct Pattern {
int numChannels, numRows;
Note *notes;
Note getNote(int row, int chan) const {
Note res;
if (row >= 0 && chan >= 0 && row < numRows && chan < numChannels)
res = notes[row * numChannels + chan];
else
memset(&res, 0, sizeof(struct Note));
return res;
}
};
struct Sample {
char name[32]; // sample name
int16 finetune; // fine tune
int16 volume; // volume
int length; // loop start
int loopStart; // loop start
int loopLength; // loop length
int16 panning;
int16 relNote;
int16 *data;
};
struct Envelope {
byte enabled, sustain, looped, numPoints;
uint16 sustainTick, loopStartTick, loopEndTick;
uint16 pointsTick[16], pointsAmpl[16];
};
struct Instrument {
int numSamples, volFadeout;
char name[32], keyToSample[97];
int8 vibType, vibSweep, vibDepth, vibRate;
Envelope volEnv, panEnv;
Sample *samples;
};
struct ModuleModXmS3m {
private:
static const int FP_SHIFT;
static const int FP_ONE;
static const int FP_MASK;
static const int exp2table[];
public:
// sound properties
byte name[32];
uint sequenceLen;
int restartPos;
byte *sequence;
// patterns
int numChannels;
int numPatterns;
Pattern *patterns;
// instruments
int numInstruments;
Instrument *instruments;
// others
int defaultGvol, defaultSpeed, defaultTempo, c2Rate, gain;
bool linearPeriods, fastVolSlides;
byte *defaultPanning;
ModuleModXmS3m();
~ModuleModXmS3m();
bool load(Common::SeekableReadStream &stream);
// math functions
static int moduleLog2(int x);
static int moduleExp2(int x);
private:
bool loadMod(Common::SeekableReadStream &stream);
bool loadXm(Common::SeekableReadStream &stream);
bool loadS3m(Common::SeekableReadStream &stream);
bool loadAmf(Common::SeekableReadStream &st);
void readSampleSint8(Common::SeekableReadStream &stream, int length, int16 *dest);
void readSampleSint16LE(Common::SeekableReadStream &stream, int length, int16 *dest);
void SamplePingPong(Sample &sample);
};
} // End of namespace Modules
#endif

334
audio/mods/paula.cpp Normal file
View File

@@ -0,0 +1,334 @@
/* 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/>.
*
*/
/*
* The low-pass filter code is based on UAE's audio filter code
* found in audio.c. UAE is licensed under the terms of the GPLv2.
*
* audio.c in UAE states the following:
* Copyright 1995, 1996, 1997 Bernd Schmidt
* Copyright 1996 Marcus Sundberg
* Copyright 1996 Manfred Thole
* Copyright 2006 Toni Wilen
*/
#include <math.h>
#include "common/scummsys.h"
#include "common/translation.h"
#include "audio/mixer.h"
#include "audio/mods/paula.h"
#include "audio/null.h"
namespace Audio {
Paula::Paula(bool stereo, int rate, uint interruptFreq, FilterMode filterMode, int periodScaleDivisor) :
_stereo(stereo), _rate(rate), _periodScale((double)kPalPaulaClock / (rate * periodScaleDivisor)), _intFreq(interruptFreq), _mutex(g_system->getMixer()->mutex()) {
_filterState.mode = filterMode;
_filterState.ledFilter = false;
filterResetState();
_filterState.a0[0] = filterCalculateA0(rate, 6200);
_filterState.a0[1] = filterCalculateA0(rate, 20000);
_filterState.a0[2] = filterCalculateA0(rate, 7000);
clearVoices();
_voice[0].panning = PANNING_RIGHT;
_voice[1].panning = PANNING_LEFT;
_voice[2].panning = PANNING_LEFT;
_voice[3].panning = PANNING_RIGHT;
if (_intFreq == 0)
_intFreq = _rate;
_curInt = 0;
_timerBase = 1;
_playing = false;
_end = true;
}
Paula::~Paula() {
}
void Paula::clearVoice(byte voice) {
assert(voice < NUM_VOICES);
_voice[voice].data = nullptr;
_voice[voice].dataRepeat = nullptr;
_voice[voice].length = 0;
_voice[voice].lengthRepeat = 0;
_voice[voice].period = 0;
_voice[voice].volume = 0;
_voice[voice].offset = Offset(0);
_voice[voice].dmaCount = 0;
_voice[voice].interrupt = false;
}
int Paula::readBuffer(int16 *buffer, const int numSamples) {
Common::StackLock lock(_mutex);
memset(buffer, 0, numSamples * 2);
if (!_playing) {
return numSamples;
}
if (_stereo)
return readBufferIntern<true>(buffer, numSamples);
else
return readBufferIntern<false>(buffer, numSamples);
}
/* Denormals are very small floating point numbers that force FPUs into slow
* mode. All lowpass filters using floats are suspectible to denormals unless
* a small offset is added to avoid very small floating point numbers.
*/
#define DENORMAL_OFFSET (1E-10)
/* Based on UAE.
* Original comment in UAE:
*
* Amiga has two separate filtering circuits per channel, a static RC filter
* on A500 and the LED filter. This code emulates both.
*
* The Amiga filtering circuitry depends on Amiga model. Older Amigas seem
* to have a 6 dB/oct RC filter with cutoff frequency such that the -6 dB
* point for filter is reached at 6 kHz, while newer Amigas have no filtering.
*
* The LED filter is complicated, and we are modelling it with a pair of
* RC filters, the other providing a highboost. The LED starts to cut
* into signal somewhere around 5-6 kHz, and there's some kind of highboost
* in effect above 12 kHz. Better measurements are required.
*
* The current filtering should be accurate to 2 dB with the filter on,
* and to 1 dB with the filter off.
*/
inline int32 filter(int32 input, Paula::FilterState &state, int voice) {
float normalOutput, ledOutput;
switch (state.mode) {
case Paula::kFilterModeA500:
state.rc[voice][0] = state.a0[0] * input + (1 - state.a0[0]) * state.rc[voice][0] + DENORMAL_OFFSET;
state.rc[voice][1] = state.a0[1] * state.rc[voice][0] + (1-state.a0[1]) * state.rc[voice][1];
normalOutput = state.rc[voice][1];
state.rc[voice][2] = state.a0[2] * normalOutput + (1 - state.a0[2]) * state.rc[voice][2];
state.rc[voice][3] = state.a0[2] * state.rc[voice][2] + (1 - state.a0[2]) * state.rc[voice][3];
state.rc[voice][4] = state.a0[2] * state.rc[voice][3] + (1 - state.a0[2]) * state.rc[voice][4];
ledOutput = state.rc[voice][4];
break;
case Paula::kFilterModeA1200:
normalOutput = input;
state.rc[voice][1] = state.a0[2] * normalOutput + (1 - state.a0[2]) * state.rc[voice][1] + DENORMAL_OFFSET;
state.rc[voice][2] = state.a0[2] * state.rc[voice][1] + (1 - state.a0[2]) * state.rc[voice][2];
state.rc[voice][3] = state.a0[2] * state.rc[voice][2] + (1 - state.a0[2]) * state.rc[voice][3];
ledOutput = state.rc[voice][3];
break;
case Paula::kFilterModeNone:
default:
return input;
}
return CLIP<int32>(state.ledFilter ? ledOutput : normalOutput, -32768, 32767);
}
template<bool stereo>
inline int mixBuffer(int16 *&buf, const int8 *data, Paula::Offset &offset, frac_t rate, int neededSamples, uint bufSize, byte volume, byte panning, Paula::FilterState &filterState, int voice) {
int samples;
for (samples = 0; samples < neededSamples && offset.int_off < bufSize; ++samples) {
const int32 tmp = filter(((int32) data[offset.int_off]) * volume, filterState, voice);
if (stereo) {
*buf++ += (tmp * (255 - panning)) >> 7;
*buf++ += (tmp * (panning)) >> 7;
} else
*buf++ += tmp;
// Step to next source sample
offset.rem_off += rate;
if (offset.rem_off >= (frac_t)FRAC_ONE) {
offset.int_off += fracToInt(offset.rem_off);
offset.rem_off &= FRAC_LO_MASK;
}
}
return samples;
}
template<bool stereo>
int Paula::readBufferIntern(int16 *buffer, const int numSamples) {
int samples = stereo ? numSamples / 2 : numSamples;
while (samples > 0) {
// Handle 'interrupts'. This gives subclasses the chance to adjust the channel data
// (e.g. insert new samples, do pitch bending, whatever).
if (_curInt == 0) {
_curInt = _intFreq;
interrupt();
}
// Compute how many samples to generate: at most the requested number of samples,
// of course, but we may stop earlier when an 'interrupt' is expected.
const uint nSamples = MIN((uint)samples, _curInt);
// Loop over the four channels of the emulated Paula chip
for (int voice = 0; voice < NUM_VOICES; voice++) {
// No data, or paused -> skip channel
if (!_voice[voice].data || (_voice[voice].period <= 0))
continue;
// The Paula chip apparently run at 7.0937892 MHz in the PAL
// version and at 7.1590905 MHz in the NTSC version. We divide this
// by the requested the requested output sampling rate _rate
// (typically 44.1 kHz or 22.05 kHz) obtaining the value _periodScale.
// This is then divided by the "period" of the channel we are
// processing, to obtain the correct output 'rate'.
frac_t rate = doubleToFrac(_periodScale / _voice[voice].period);
// Cap the volume
_voice[voice].volume = MIN((byte) 0x40, _voice[voice].volume);
Channel &ch = _voice[voice];
int16 *p = buffer;
int neededSamples = nSamples;
// NOTE: A Protracker (or other module format) player might actually
// push the offset past the sample length in its interrupt(), in which
// case the first mixBuffer() call should not mix anything, and the loop
// should be triggered.
// Thus, doing an assert(ch.offset.int_off < ch.length) here is wrong.
// An example where this happens is a certain Protracker module played
// by the OS/2 version of Hopkins FBI.
// Mix the generated samples into the output buffer
neededSamples -= mixBuffer<stereo>(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning, _filterState, voice);
// Wrap around if necessary
if (ch.offset.int_off >= ch.length) {
// Important: Wrap around the offset *before* updating the voice length.
// Otherwise, if length != lengthRepeat we would wrap incorrectly.
// Note: If offset >= 2*len ever occurs, the following would be wrong;
// instead of subtracting, we then should compute the modulus using "%=".
// Since that requires a division and is slow, and shouldn't be necessary
// in practice anyway, we only use subtraction.
ch.offset.int_off -= ch.length;
ch.dmaCount++;
ch.data = ch.dataRepeat;
ch.length = ch.lengthRepeat;
// The Paula chip can generate an interrupt after it copies a channel's
// location and length values to its internal registers, signaling that
// it's safe to modify them. Some sound engines use this feature in order
// to control sound looping.
// NOTE: the real Paula would also do this during enableChannel() and in
// the middle of setChannelData(); for simplicity, we only do it here.
if (ch.interrupt)
interruptChannel(voice);
}
// If we have not yet generated enough samples, and looping is active: loop!
if (neededSamples > 0 && ch.length > 2) {
// Repeat as long as necessary.
while (neededSamples > 0) {
// Mix the generated samples into the output buffer
neededSamples -= mixBuffer<stereo>(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning, _filterState, voice);
if (ch.offset.int_off >= ch.length) {
// Wrap around. See also the note above.
ch.offset.int_off -= ch.length;
ch.dmaCount++;
}
}
}
}
buffer += stereo ? nSamples * 2 : nSamples;
_curInt -= nSamples;
samples -= nSamples;
}
return numSamples;
}
void Paula::filterResetState() {
for (int i = 0; i < NUM_VOICES; i++)
for (int j = 0; j < 5; j++)
_filterState.rc[i][j] = 0.0f;
}
/* Based on UAE.
* Original comment in UAE:
*
* This computes the 1st order low-pass filter term b0.
* The a1 term is 1.0 - b0. The center frequency marks the -3 dB point.
*/
float Paula::filterCalculateA0(int rate, int cutoff) {
float omega;
/* The BLT correction formula below blows up if the cutoff is above nyquist. */
if (cutoff >= rate / 2)
return 1.0;
omega = 2 * M_PI * cutoff / rate;
/* Compensate for the bilinear transformation. This allows us to specify the
* stop frequency more exactly, but the filter becomes less steep further
* from stopband. */
omega = tan(omega / 2) * 2;
return 1 / (1 + 1 / omega);
}
} // End of namespace Audio
// Plugin interface
// (This can only create a null driver since apple II gs support seeems not to be implemented
// and also is not part of the midi driver architecture. But we need the plugin for the options
// menu in the launcher and for MidiDriver::detectDevice() which is more or less used by all engines.)
class AmigaMusicPlugin : public NullMusicPlugin {
public:
const char *getName() const override {
return _s("Amiga Audio emulator");
}
const char *getId() const override {
return "amiga";
}
MusicDevices getDevices() const override;
};
MusicDevices AmigaMusicPlugin::getDevices() const {
MusicDevices devices;
devices.push_back(MusicDevice(this, "", MT_AMIGA));
return devices;
}
//#if PLUGIN_ENABLED_DYNAMIC(AMIGA)
//REGISTER_PLUGIN_DYNAMIC(AMIGA, PLUGIN_TYPE_MUSIC, AmigaMusicPlugin);
//#else
REGISTER_PLUGIN_STATIC(AMIGA, PLUGIN_TYPE_MUSIC, AmigaMusicPlugin);
//#endif

244
audio/mods/paula.h Normal file
View File

@@ -0,0 +1,244 @@
/* 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/>.
*
*/
#ifndef AUDIO_MODS_PAULA_H
#define AUDIO_MODS_PAULA_H
#include "audio/audiostream.h"
#include "common/frac.h"
#include "common/mutex.h"
namespace Audio {
/**
* Emulation of the "Paula" Amiga music chip
* The interrupt frequency specifies the number of mixed wavesamples between
* calls of the interrupt method
*/
class Paula : public AudioStream {
public:
static const int NUM_VOICES = 4;
// Default panning value for left channels.
static const int PANNING_LEFT = 63;
// Default panning value for right channels.
static const int PANNING_RIGHT = 191;
enum {
kPalSystemClock = 7093790,
kNtscSystemClock = 7159090,
kPalCiaClock = kPalSystemClock / 10,
kNtscCiaClock = kNtscSystemClock / 10,
kPalPaulaClock = kPalSystemClock / 2,
kNtscPaulaClock = kNtscSystemClock / 2
};
enum FilterMode {
kFilterModeNone = 0,
kFilterModeA500,
kFilterModeA1200,
#if defined(__DS__)
kFilterModeDefault = kFilterModeNone
#else
kFilterModeDefault = kFilterModeA1200
#endif
};
/* TODO: Document this */
struct Offset {
uint int_off; // integral part of the offset
frac_t rem_off; // fractional part of the offset, at least 0 and less than 1
explicit Offset(int off = 0) : int_off(off), rem_off(0) {}
};
struct FilterState {
FilterMode mode;
bool ledFilter;
float a0[3];
float rc[NUM_VOICES][5];
};
Paula(bool stereo = false, int rate = 44100, uint interruptFreq = 0,
FilterMode filterMode = kFilterModeDefault, int periodScaleDivisor = 1);
~Paula();
bool playing() const { return _playing; }
void setTimerBaseValue( uint32 ticksPerSecond ) { _timerBase = ticksPerSecond; }
uint32 getTimerBaseValue() { return _timerBase; }
void setSingleInterrupt(uint sampleDelay) { assert(sampleDelay < _intFreq); _curInt = sampleDelay; }
void setSingleInterruptUnscaled(uint timerDelay) {
setSingleInterrupt((uint)(((double)timerDelay * getRate()) / _timerBase));
}
void setInterruptFreq(uint sampleDelay) { _intFreq = sampleDelay; _curInt = 0; }
void setInterruptFreqUnscaled(uint timerDelay) {
setInterruptFreq((uint)(((double)timerDelay * getRate()) / _timerBase));
}
void clearVoice(byte voice);
void clearVoices() { for (int i = 0; i < NUM_VOICES; ++i) clearVoice(i); }
void startPlay() { filterResetState(); _playing = true; }
void stopPlay() { _playing = false; }
void pausePlay(bool pause) { _playing = !pause; }
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples);
bool isStereo() const { return _stereo; }
bool endOfData() const { return _end; }
int getRate() const { return _rate; }
protected:
struct Channel {
const int8 *data;
const int8 *dataRepeat;
uint32 length;
uint32 lengthRepeat;
int16 period;
byte volume;
Offset offset;
byte panning; // For stereo mixing: 0 = far left, 255 = far right
int dmaCount;
bool interrupt;
};
bool _end;
Common::Mutex &_mutex;
virtual void interrupt() = 0;
virtual void interruptChannel(byte channel) { }
void startPaula() {
_playing = true;
_end = false;
}
void stopPaula() {
_playing = false;
_end = true;
}
void setChannelPanning(byte channel, byte panning) {
assert(channel < NUM_VOICES);
_voice[channel].panning = panning;
}
void disableChannel(byte channel) {
assert(channel < NUM_VOICES);
_voice[channel].data = 0;
}
void enableChannel(byte channel) {
assert(channel < NUM_VOICES);
Channel &ch = _voice[channel];
ch.data = ch.dataRepeat;
ch.length = ch.lengthRepeat;
// actually first 2 bytes are dropped?
ch.offset = Offset(0);
// ch.period = ch.periodRepeat;
}
void setChannelInterrupt(byte channel, bool enable) {
assert(channel < NUM_VOICES);
_voice[channel].interrupt = enable;
}
void setChannelPeriod(byte channel, int16 period) {
assert(channel < NUM_VOICES);
_voice[channel].period = period;
}
void setChannelVolume(byte channel, byte volume) {
assert(channel < NUM_VOICES);
_voice[channel].volume = volume;
}
void setChannelSampleStart(byte channel, const int8 *data) {
assert(channel < NUM_VOICES);
_voice[channel].dataRepeat = data;
}
void setChannelSampleLen(byte channel, uint32 length) {
assert(channel < NUM_VOICES);
assert(length < 32768/2);
_voice[channel].lengthRepeat = 2 * length;
}
void setChannelData(uint8 channel, const int8 *data, const int8 *dataRepeat, uint32 length, uint32 lengthRepeat, int32 offset = 0) {
assert(channel < NUM_VOICES);
Channel &ch = _voice[channel];
ch.dataRepeat = data;
ch.lengthRepeat = length;
enableChannel(channel);
ch.offset = Offset(offset);
ch.dataRepeat = dataRepeat;
ch.lengthRepeat = lengthRepeat;
}
void setChannelOffset(byte channel, Offset offset) {
assert(channel < NUM_VOICES);
_voice[channel].offset = offset;
}
Offset getChannelOffset(byte channel) {
assert(channel < NUM_VOICES);
return _voice[channel].offset;
}
int getChannelDmaCount(byte channel) {
assert(channel < NUM_VOICES);
return _voice[channel].dmaCount;
}
void setChannelDmaCount(byte channel, int dmaVal = 0) {
assert(channel < NUM_VOICES);
_voice[channel].dmaCount = dmaVal;
}
void setAudioFilter(bool enable) {
_filterState.ledFilter = enable;
}
private:
Channel _voice[NUM_VOICES];
const bool _stereo;
const int _rate;
const double _periodScale;
uint _intFreq;
uint _curInt;
uint32 _timerBase;
bool _playing;
FilterState _filterState;
template<bool stereo>
int readBufferIntern(int16 *buffer, const int numSamples);
void filterResetState();
float filterCalculateA0(int rate, int cutoff);
};
} // End of namespace Audio
#endif

419
audio/mods/protracker.cpp Normal file
View File

@@ -0,0 +1,419 @@
/* 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 "audio/mods/protracker.h"
#include "audio/mods/paula.h"
#include "audio/mods/module.h"
#include "common/textconsole.h"
#include "common/util.h"
namespace Modules {
const int16 ProtrackerStream::sinetable[64] = {
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,
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
};
ProtrackerStream::ProtrackerStream(int rate, bool stereo) : Paula(stereo, rate, rate / 50) {
_module = nullptr;
_tick = _row = _pos = 0;
_speed = 6;
_bpm = 125;
_hasJumpToPattern = false;
_jumpToPattern = 0;
_hasPatternBreak = false;
_skipRow = 0;
_hasPatternLoop = false;
_patternLoopCount = 0;
_patternLoopRow = 0;
_patternDelay = 0;
ARRAYCLEAR(_track);
}
ProtrackerStream::ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) :
ProtrackerStream(rate, stereo) {
_module = new Module();
bool result = _module->load(*stream, offs);
assert(result);
(void)result;
startPaula();
}
ProtrackerStream::~ProtrackerStream() {
if (_module) {
delete _module;
_module = nullptr;
}
}
void ProtrackerStream::updateRow() {
for (int track = 0; track < 4; track++) {
_track[track].arpeggio = false;
_track[track].vibrato = 0;
_track[track].delaySampleTick = 0;
const note_t note =
_module->pattern[_module->songpos[_pos]][_row][track];
const int effect = note.effect >> 8;
if (note.sample) {
if (_track[track].sample != note.sample) {
_track[track].vibratoPos = 0;
}
_track[track].sample = note.sample;
_track[track].lastSample = note.sample;
_track[track].finetune = _module->sample[note.sample - 1].finetune;
_track[track].vol = _module->sample[note.sample - 1].vol;
}
if (note.period) {
if (effect != 3 && effect != 5) {
if (_track[track].finetune)
_track[track].period = _module->noteToPeriod(note.note, _track[track].finetune);
else
_track[track].period = note.period;
_track[track].offset = Offset(0);
_track[track].sample = _track[track].lastSample;
}
}
const byte exy = note.effect & 0xff;
const byte ex = (note.effect >> 4) & 0xf;
const byte ey = note.effect & 0xf;
int vol;
switch (effect) {
case 0x0:
if (exy) {
_track[track].arpeggio = true;
byte trackNote = _module->periodToNote(_track[track].period);
_track[track].arpeggioNotes[0] = trackNote;
_track[track].arpeggioNotes[1] = trackNote + ex;
_track[track].arpeggioNotes[2] = trackNote + ey;
}
break;
case 0x1:
break;
case 0x2:
break;
case 0x3:
if (note.period)
_track[track].portaToNote = note.period;
if (exy)
_track[track].portaToNoteSpeed = exy;
break;
case 0x4:
if (exy) {
_track[track].vibratoSpeed = ex;
_track[track].vibratoDepth = ey;
}
break;
case 0x5:
doPorta(track);
doVolSlide(track, ex, ey);
break;
case 0x6:
doVibrato(track);
doVolSlide(track, ex, ey);
break;
case 0x9: // Set sample offset
if (exy) {
_track[track].offset = Offset(exy * 256);
setChannelOffset(track, _track[track].offset);
}
break;
case 0xA:
break;
case 0xB:
_hasJumpToPattern = true;
_jumpToPattern = exy;
break;
case 0xC:
_track[track].vol = exy;
break;
case 0xD:
_hasPatternBreak = true;
_skipRow = ex * 10 + ey;
break;
case 0xE:
switch (ex) {
case 0x0: // Switch filters off
break;
case 0x1: // Fine slide up
_track[track].period -= exy;
break;
case 0x2: // Fine slide down
_track[track].period += exy;
break;
case 0x5: // Set finetune
_track[track].finetune = ey;
_module->sample[_track[track].sample].finetune = ey;
if (note.period) {
if (ey)
_track[track].period = _module->noteToPeriod(note.note, ey);
else
_track[track].period = note.period;
}
break;
case 0x6:
if (ey == 0) {
_patternLoopRow = _row;
} else {
_patternLoopCount++;
if (_patternLoopCount <= ey)
_hasPatternLoop = true;
else
_patternLoopCount = 0;
}
break;
case 0x9:
break; // Retrigger note
case 0xA: // Fine volume slide up
vol = _track[track].vol + ey;
if (vol > 64)
vol = 64;
_track[track].vol = vol;
break;
case 0xB: // Fine volume slide down
vol = _track[track].vol - ey;
if (vol < 0)
vol = 0;
_track[track].vol = vol;
break;
case 0xD: // Delay sample
_track[track].delaySampleTick = ey;
_track[track].delaySample = _track[track].sample;
_track[track].sample = 0;
_track[track].vol = 0;
break;
case 0xE: // Pattern delay
_patternDelay = ey;
break;
default:
warning("Unimplemented effect %X", note.effect);
}
break;
case 0xF:
if (exy < 0x20) {
_speed = exy;
} else {
_bpm = exy;
setInterruptFreq((int)(getRate() / (_bpm * 0.4)));
}
break;
default:
warning("Unimplemented effect %X", note.effect);
}
}
}
void ProtrackerStream::updateEffects() {
for (int track = 0; track < 4; track++) {
_track[track].vibrato = 0;
const note_t note =
_module->pattern[_module->songpos[_pos]][_row][track];
const int effect = note.effect >> 8;
const int exy = note.effect & 0xff;
const int ex = (note.effect >> 4) & 0xf;
const int ey = (note.effect) & 0xf;
switch (effect) {
case 0x0:
if (exy) {
const int idx = (_tick == 1) ? 0 : (_tick % 3);
_track[track].period =
_module->noteToPeriod(_track[track].arpeggioNotes[idx],
_track[track].finetune);
}
break;
case 0x1:
_track[track].period -= exy;
break;
case 0x2:
_track[track].period += exy;
break;
case 0x3:
doPorta(track);
break;
case 0x4:
doVibrato(track);
break;
case 0x5:
doPorta(track);
doVolSlide(track, ex, ey);
break;
case 0x6:
doVibrato(track);
doVolSlide(track, ex, ey);
break;
case 0xA:
doVolSlide(track, ex, ey);
break;
case 0xE:
switch (ex) {
case 0x6:
break; // Pattern loop
case 0x9: // Retrigger note
if (ey && (_tick % ey) == 0)
_track[track].offset = Offset(0);
break;
case 0xD: // Delay sample
if (_tick == _track[track].delaySampleTick) {
_track[track].sample = _track[track].delaySample;
_track[track].offset = Offset(0);
if (_track[track].sample)
_track[track].vol = _module->sample[_track[track].sample - 1].vol;
}
break;
default:
break;
}
break;
default:
break;
}
}
}
void ProtrackerStream::interrupt() {
int track;
for (track = 0; track < 4; track++) {
_track[track].offset = getChannelOffset(track);
if (_tick == 0 && _track[track].arpeggio) {
_track[track].period = _module->noteToPeriod(_track[track].arpeggioNotes[0],
_track[track].finetune);
}
}
if (_tick == 0) {
if (_hasJumpToPattern) {
_hasJumpToPattern = false;
_pos = _jumpToPattern;
_row = 0;
} else if (_hasPatternBreak) {
_hasPatternBreak = false;
_row = _skipRow;
_pos = (_pos + 1) % _module->songlen;
_patternLoopRow = 0;
} else if (_hasPatternLoop) {
_hasPatternLoop = false;
_row = _patternLoopRow;
}
if (_row >= 64) {
_row = 0;
_pos = (_pos + 1) % _module->songlen;
_patternLoopRow = 0;
}
updateRow();
} else
updateEffects();
_tick = (_tick + 1) % (_speed + _patternDelay * _speed);
if (_tick == 0) {
_row++;
_patternDelay = 0;
}
for (track = 0; track < 4; track++) {
setChannelVolume(track, _track[track].vol);
setChannelPeriod(track, _track[track].period + _track[track].vibrato);
if (_track[track].sample) {
sample_t &sample = _module->sample[_track[track].sample - 1];
setChannelData(track,
sample.data,
sample.replen > 2 ? sample.data + sample.repeat : nullptr,
sample.len,
sample.replen);
setChannelOffset(track, _track[track].offset);
_track[track].sample = 0;
}
}
}
void ProtrackerStream::doPorta(int track) {
if (_track[track].portaToNote && _track[track].portaToNoteSpeed) {
int distance = _track[track].period - _track[track].portaToNote;
int sign = distance > 0 ? 1 : -1;
if ((sign * distance) > _track[track].portaToNoteSpeed)
_track[track].period -= sign * _track[track].portaToNoteSpeed;
else
_track[track].period = _track[track].portaToNote;
}
}
void ProtrackerStream::doVibrato(int track) {
_track[track].vibrato =
(_track[track].vibratoDepth * sinetable[_track[track].vibratoPos]) / 128;
_track[track].vibratoPos += _track[track].vibratoSpeed;
_track[track].vibratoPos %= 64;
}
void ProtrackerStream::doVolSlide(int track, byte ex, byte ey) {
int vol = _track[track].vol;
if (ex == 0)
vol -= ey;
else if (ey == 0)
vol += ex;
if (vol < 0)
vol = 0;
else if (vol > 64)
vol = 64;
_track[track].vol = vol;
}
} // End of namespace Modules
namespace Audio {
AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo, Modules::Module **module) {
Modules::ProtrackerStream *protrackerStream = new Modules::ProtrackerStream(stream, offs, rate, stereo);
if (module) {
*module = protrackerStream->getModule();
}
return (AudioStream *)protrackerStream;
}
} // End of namespace Audio

152
audio/mods/protracker.h Normal file
View File

@@ -0,0 +1,152 @@
/* 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/>.
*
*/
/**
* @file
* Sound decoder used in engines:
* - agos
* - parallaction
* - gob
* - hopkins
* - chewy (subclass)
*/
#ifndef AUDIO_MODS_PROTRACKER_H
#define AUDIO_MODS_PROTRACKER_H
#include "audio/mods/paula.h"
#include "audio/mods/module.h"
namespace Common {
class SeekableReadStream;
}
namespace Modules {
class ProtrackerStream : public ::Audio::Paula {
protected:
Module *_module;
private:
int _tick;
int _row;
int _pos;
int _speed;
int _bpm;
// For effect 0xB - Jump To Pattern;
bool _hasJumpToPattern;
int _jumpToPattern;
// For effect 0xD - PatternBreak;
bool _hasPatternBreak;
int _skipRow;
// For effect 0xE6 - Pattern Loop
bool _hasPatternLoop;
int _patternLoopCount;
int _patternLoopRow;
// For effect 0xEE - Pattern Delay
byte _patternDelay;
static const int16 sinetable[];
struct Track {
byte sample;
byte lastSample;
uint16 period;
Offset offset;
byte vol;
byte finetune;
// For effect 0x0 - Arpeggio
bool arpeggio;
byte arpeggioNotes[3];
// For effect 0x3 - Porta to note
uint16 portaToNote;
byte portaToNoteSpeed;
// For effect 0x4 - Vibrato
int vibrato;
byte vibratoPos;
byte vibratoSpeed;
byte vibratoDepth;
// For effect 0xED - Delay sample
byte delaySample;
byte delaySampleTick;
} _track[4];
public:
ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo);
protected:
ProtrackerStream(int rate, bool stereo);
public:
virtual ~ProtrackerStream();
Modules::Module *getModule() {
// Ordinarily, the Module is not meant to be seen outside of
// this class, but occasionally, it's useful to be able to
// manipulate it directly. The Hopkins engine uses this to
// repair a broken song.
return _module;
}
private:
void interrupt() override;
void doPorta(int track);
void doVibrato(int track);
void doVolSlide(int track, byte ex, byte ey);
void updateRow();
void updateEffects();
};
}
namespace Audio {
class AudioStream;
/*
* Factory function for ProTracker streams. Reads all data from the
* given ReadStream and creates an AudioStream from this. No reference
* to the 'stream' object is kept, so you can safely delete it after
* invoking this factory.
*
* @param stream the ReadStream from which to read the ProTracker data
* @param rate TODO
* @param stereo TODO
* @param module can be used to return the Module object (rarely useful)
* @return a new AudioStream, or NULL, if an error occurred
*/
AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs = 0, int rate = 44100, bool stereo = true, Modules::Module **module = 0);
} // End of namespace Audio
#endif

586
audio/mods/rjp1.cpp Normal file
View File

@@ -0,0 +1,586 @@
/* 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) {
uint8 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

48
audio/mods/rjp1.h Normal file
View File

@@ -0,0 +1,48 @@
/* 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/>.
*
*/
/**
* @file
* Sound decoder used in engines:
* - queen
*/
#ifndef AUDIO_MODS_RJP1_H
#define AUDIO_MODS_RJP1_H
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class AudioStream;
/*
* Factory function for RichardJoseph1 modules. Reads all data from the
* given songData and instrumentsData streams and creates an AudioStream
* from this. No references to these stream objects are kept.
*/
AudioStream *makeRjp1Stream(Common::SeekableReadStream *songData, Common::SeekableReadStream *instrumentsData, int num, int rate = 44100, bool stereo = true);
} // End of namespace Audio
#endif

281
audio/mods/soundfx.cpp Normal file
View File

@@ -0,0 +1,281 @@
/* 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/endian.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "audio/mods/paula.h"
#include "audio/mods/soundfx.h"
namespace Audio {
struct SoundFxInstrument {
char name[23];
uint16 len;
uint8 finetune;
uint8 volume;
uint16 repeatPos;
uint16 repeatLen;
int8 *data;
};
class SoundFx : public Paula {
public:
enum {
NUM_CHANNELS = 4,
NUM_INSTRUMENTS = 15
};
SoundFx(int rate, bool stereo, bool repeat, int periodScaleDivisor = 1);
virtual ~SoundFx();
bool load(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb);
void play();
protected:
void handlePattern(int ch, uint32 pat);
void updateEffects(int ch);
void handleTick();
void disablePaulaChannel(uint8 channel);
void setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen);
void interrupt() override;
uint8 _ticks;
uint16 _delay;
SoundFxInstrument _instruments[NUM_INSTRUMENTS];
uint8 _numOrders;
uint8 _curOrder;
uint16 _curPos;
uint8 _ordersTable[128];
uint8 *_patternData;
uint16 _effects[NUM_CHANNELS];
bool _repeat;
};
SoundFx::SoundFx(int rate, bool stereo, bool repeat, int periodScaleDivisor)
: Paula(stereo, rate, 0, kFilterModeDefault, periodScaleDivisor) {
setTimerBaseValue(kPalCiaClock);
_ticks = 0;
_delay = 0;
memset(_instruments, 0, sizeof(_instruments));
_numOrders = 0;
_curOrder = 0;
_curPos = 0;
memset(_ordersTable, 0, sizeof(_ordersTable));
_patternData = nullptr;
memset(_effects, 0, sizeof(_effects));
_repeat = repeat;
}
SoundFx::~SoundFx() {
free(_patternData);
for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
free(_instruments[i].data);
}
}
bool SoundFx::load(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb) {
int instrumentsSize[15];
if (!loadCb) {
for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
instrumentsSize[i] = data->readUint32BE();
}
}
uint8 tag[4];
data->read(tag, 4);
if (memcmp(tag, "SONG", 4) != 0) {
return false;
}
_delay = data->readUint16BE();
data->skip(7 * 2);
for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
SoundFxInstrument *ins = &_instruments[i];
data->read(ins->name, 22); ins->name[22] = 0;
ins->len = data->readUint16BE();
ins->finetune = data->readByte();
ins->volume = data->readByte();
ins->repeatPos = data->readUint16BE();
ins->repeatLen = data->readUint16BE();
}
_numOrders = data->readByte();
data->skip(1);
data->read(_ordersTable, 128);
int maxOrder = 0;
for (int i = 0; i < _numOrders; ++i) {
if (_ordersTable[i] > maxOrder) {
maxOrder = _ordersTable[i];
}
}
int patternSize = (maxOrder + 1) * 4 * 4 * 64;
_patternData = (uint8 *)malloc(patternSize);
if (!_patternData) {
return false;
}
data->read(_patternData, patternSize);
for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
SoundFxInstrument *ins = &_instruments[i];
if (!loadCb) {
if (instrumentsSize[i] != 0) {
assert(ins->len <= 1 || ins->len * 2 <= instrumentsSize[i]);
assert(ins->repeatLen <= 1 || (ins->repeatPos + ins->repeatLen) * 2 <= instrumentsSize[i]);
ins->data = (int8 *)malloc(instrumentsSize[i]);
if (!ins->data) {
return false;
}
data->read(ins->data, instrumentsSize[i]);
}
} else {
if (ins->name[0]) {
ins->name[22] = '\0';
ins->data = (int8 *)(*loadCb)(ins->name, nullptr);
if (!ins->data) {
return false;
}
}
}
}
return true;
}
void SoundFx::play() {
_curPos = 0;
_curOrder = 0;
_ticks = 0;
setInterruptFreqUnscaled(_delay);
startPaula();
}
void SoundFx::handlePattern(int ch, uint32 pat) {
uint16 note1 = pat >> 16;
uint16 note2 = pat & 0xFFFF;
if (note1 == 0xFFFD) { // PIC
_effects[ch] = 0;
return;
}
_effects[ch] = note2;
if (note1 == 0xFFFE) { // STP
disablePaulaChannel(ch);
return;
}
int ins = (note2 & 0xF000) >> 12;
if (ins != 0) {
SoundFxInstrument *i = &_instruments[ins - 1];
setupPaulaChannel(ch, i->data, i->len, i->repeatPos, i->repeatLen);
int effect = (note2 & 0xF00) >> 8;
int volume = i->volume;
switch (effect) {
case 5: // volume up
volume += (note2 & 0xFF);
if (volume > 63) {
volume = 63;
}
break;
case 6: // volume down
volume -= (note2 & 0xFF);
if (volume < 0) {
volume = 0;
}
break;
default:
break;
}
setChannelVolume(ch, volume);
}
if (note1 != 0) {
setChannelPeriod(ch, note1);
}
}
void SoundFx::updateEffects(int ch) {
// updateEffects() is a no-op in all Delphine Software games using SoundFx : FW,OS,Cruise,AW
if (_effects[ch] != 0) {
switch (_effects[ch]) {
case 1: // appreggiato
case 2: // pitchbend
case 3: // ledon, enable low-pass filter
case 4: // ledoff, disable low-pass filter
case 7: // set step up
case 8: // set step down
warning("Unhandled effect %d", _effects[ch]);
break;
default:
break;
}
}
}
void SoundFx::handleTick() {
++_ticks;
if (_ticks != 6) {
for (int ch = 0; ch < 4; ++ch) {
updateEffects(ch);
}
} else {
_ticks = 0;
const uint8 *patternData = _patternData + _ordersTable[_curOrder] * 1024 + _curPos;
for (int ch = 0; ch < 4; ++ch) {
handlePattern(ch, READ_BE_UINT32(patternData));
patternData += 4;
}
_curPos += 4 * 4;
if (_curPos >= 1024) {
_curPos = 0;
++_curOrder;
if (_curOrder == _numOrders) {
if (_repeat)
_curOrder = 0;
else
stopPaula();
}
}
}
}
void SoundFx::disablePaulaChannel(uint8 channel) {
disableChannel(channel);
}
void SoundFx::setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen) {
if (data && len > 1) {
setChannelData(channel, data, data + repeatPos * 2, len * 2, repeatLen * 2);
}
}
void SoundFx::interrupt() {
handleTick();
}
AudioStream *makeSoundFxStream(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb, int rate, bool stereo, bool repeat, int periodScaleDivisor) {
SoundFx *stream = new SoundFx(rate, stereo, repeat, periodScaleDivisor);
if (stream->load(data, loadCb)) {
stream->play();
return stream;
}
delete stream;
return nullptr;
}
} // End of namespace Audio

51
audio/mods/soundfx.h Normal file
View File

@@ -0,0 +1,51 @@
/* 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/>.
*
*/
/**
* @file
* Sound decoder used in engines:
* - cine
*/
#ifndef AUDIO_MODS_SOUNDFX_H
#define AUDIO_MODS_SOUNDFX_H
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class AudioStream;
typedef byte *(*LoadSoundFxInstrumentCallback)(const char *name, uint32 *size);
/*
* Factory function for SoundFX modules. Reads all data from the
* given data stream and creates an AudioStream from this (no references to the
* stream object is kept). If loadCb is non 0, then instruments are loaded using
* it, buffers returned are free'd at the end of playback.
*/
AudioStream *makeSoundFxStream(Common::SeekableReadStream *data, LoadSoundFxInstrumentCallback loadCb, int rate = 44100, bool stereo = true, bool repeat = true, int periodScaleDivisor = 1);
} // End of namespace Audio
#endif

1195
audio/mods/tfmx.cpp Normal file

File diff suppressed because it is too large Load Diff

277
audio/mods/tfmx.h Normal file
View File

@@ -0,0 +1,277 @@
/* 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/>.
*
*/
// Only compiled if SCUMM is built-in or we're building for dynamic modules
#if !defined(AUDIO_MODS_TFMX_H) && (defined(ENABLE_SCUMM) || defined(DYNAMIC_MODULES))
#define AUDIO_MODS_TFMX_H
#include "audio/mods/paula.h"
namespace Audio {
class Tfmx : public Paula {
public:
Tfmx(int rate, bool stereo);
virtual ~Tfmx();
/**
* Stops a playing Song (but leaves macros running) and optionally also stops the player
*
* @param stopAudio stops player and audio output
* @param dataSize number of bytes to be written
* @return the number of bytes which were actually written.
*/
void stopSong(bool stopAudio = true) { Common::StackLock lock(_mutex); stopSongImpl(stopAudio); }
/**
* Stops currently playing Song (if any) and cues up a new one.
* if stopAudio is specified, the player gets reset before starting the new song
*
* @param songPos index of Song to play
* @param stopAudio stops player and audio output
* @param dataSize number of bytes to be written
* @return the number of bytes which were actually written.
*/
void doSong(int songPos, bool stopAudio = false);
/**
* plays an effect from the sfx-table, does not start audio-playback.
*
* @param sfxIndex index of effect to play
* @param unlockChannel overwrite higher priority effects
* @return index of the channel which now queued up the effect.
* -1 in case the effect couldnt be queued up
*/
int doSfx(uint16 sfxIndex, bool unlockChannel = false);
/**
* stop a running macro channel
*
* @param channel index of effect to stop
*/
void stopMacroEffect(int channel);
void doMacro(int note, int macro, int relVol = 0, int finetune = 0, int channelNo = 0);
int getTicks() const { return _playerCtx.tickCount; }
int getSongIndex() const { return _playerCtx.song; }
void setSignalPtr(uint16 *ptr, uint16 numSignals) { _playerCtx.signal = ptr; _playerCtx.numSignals = numSignals; }
void freeResources() { _deleteResource = true; freeResourceDataImpl(); }
bool load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData, bool autoDelete = true);
void setModuleData(Tfmx &otherPlayer);
protected:
void interrupt();
private:
enum { kPalDefaultCiaVal = 11822, kNtscDefaultCiaVal = 14320, kCiaBaseInterval = 0x1B51F8 };
enum { kNumVoices = 4, kNumChannels = 8, kNumSubsongs = 32, kMaxPatternOffsets = 128, kMaxMacroOffsets = 128 };
struct MdatResource {
const byte *mdatAlloc; ///< allocated Block of Memory
const byte *mdatData; ///< Start of mdat-File, might point before mdatAlloc to correct Offset
uint32 mdatLen;
uint16 headerFlags;
// uint32 headerUnknown;
// char textField[6 * 40];
struct Subsong {
uint16 songstart; ///< Index in Trackstep-Table
uint16 songend; ///< Last index in Trackstep-Table
uint16 tempo;
} subsong[kNumSubsongs];
uint32 trackstepOffset; ///< Offset in mdat
uint32 sfxTableOffset;
uint32 patternOffset[kMaxPatternOffsets]; ///< Offset in mdat
uint32 macroOffset[kMaxMacroOffsets]; ///< Offset in mdat
void boundaryCheck(const void *address, size_t accessLen = 1) const {
assert(mdatAlloc <= address && (const byte *)address + accessLen <= (const byte *)mdatData + mdatLen);
}
} const *_resource;
struct SampleResource {
const int8 *sampleData; ///< The whole sample-File
uint32 sampleLen;
void boundaryCheck(const void *address, size_t accessLen = 2) const {
assert(sampleData <= address && (const byte *)address + accessLen <= (const byte *)sampleData + sampleLen);
}
} _resourceSample;
bool _deleteResource;
bool hasResources() {
return _resource && _resource->mdatLen && _resourceSample.sampleLen;
}
struct ChannelContext {
byte paulaChannel;
// byte macroIndex;
uint16 macroWait;
uint32 macroOffset;
uint32 macroReturnOffset;
uint16 macroStep;
uint16 macroReturnStep;
uint8 macroLoopCount;
bool macroRun;
int8 macroSfxRun; ///< values are the following: -1 macro disabled, 0 macro init, 1 macro running
uint32 customMacro;
uint8 customMacroIndex;
uint8 customMacroPrio;
bool sfxLocked;
int16 sfxLockTime;
bool keyUp;
bool deferWait;
uint16 dmaIntCount;
uint32 sampleStart;
uint16 sampleLen;
uint16 refPeriod;
uint16 period;
int8 volume;
uint8 relVol;
uint8 note;
uint8 prevNote;
int16 fineTune; // always a signextended byte
uint8 portaSkip;
uint8 portaCount;
uint16 portaDelta;
uint16 portaValue;
uint8 envSkip;
uint8 envCount;
uint8 envDelta;
int8 envEndVolume;
uint8 vibLength;
uint8 vibCount;
int16 vibValue;
int8 vibDelta;
uint8 addBeginLength;
uint8 addBeginCount;
int32 addBeginDelta;
} _channelCtx[kNumVoices];
struct PatternContext {
uint32 offset; // patternStart, Offset from mdat
uint32 savedOffset; // for subroutine calls
uint16 step; // distance from patternStart
uint16 savedStep;
uint8 command;
int8 expose;
uint8 loopCount;
uint8 wait; ///< how many ticks to wait before next Command
} _patternCtx[kNumChannels];
struct TrackStepContext {
uint16 startInd;
uint16 stopInd;
uint16 posInd;
int16 loopCount;
} _trackCtx;
struct PlayerContext {
int8 song; ///< >= 0 if Song is running (means process Patterns)
uint16 patternCount;
uint16 patternSkip; ///< skip that amount of CIA-Interrupts
int8 volume; ///< Master Volume
uint8 fadeSkip;
uint8 fadeCount;
int8 fadeEndVolume;
int8 fadeDelta;
int tickCount;
uint16 *signal;
uint16 numSignals;
bool stopWithLastPattern; ///< hack to automatically stop the whole player if no Pattern is running
} _playerCtx;
const byte *getSfxPtr(uint16 index = 0) const {
const byte *sfxPtr = (const byte *)(_resource->mdatData + _resource->sfxTableOffset + index * 8);
_resource->boundaryCheck(sfxPtr, 8);
return sfxPtr;
}
const uint16 *getTrackPtr(uint16 trackstep = 0) const {
const uint16 *trackData = (const uint16 *)(_resource->mdatData + _resource->trackstepOffset + 16 * trackstep);
_resource->boundaryCheck(trackData, 16);
return trackData;
}
const uint32 *getPatternPtr(uint32 offset) const {
const uint32 *pattData = (const uint32 *)(_resource->mdatData + offset);
_resource->boundaryCheck(pattData, 4);
return pattData;
}
const uint32 *getMacroPtr(uint32 offset) const {
const uint32 *macroData = (const uint32 *)(_resource->mdatData + offset);
_resource->boundaryCheck(macroData, 4);
return macroData;
}
const int8 *getSamplePtr(const uint32 offset) const {
const int8 *sample = _resourceSample.sampleData + offset;
_resourceSample.boundaryCheck(sample, 2);
return sample;
}
static inline void initMacroProgramm(ChannelContext &channel);
static inline void clearEffects(ChannelContext &channel);
static inline void haltMacroProgramm(ChannelContext &channel);
static inline void unlockMacroChannel(ChannelContext &channel);
static inline void initPattern(PatternContext &pattern, uint8 cmd, int8 expose, uint32 offset);
void stopSongImpl(bool stopAudio = true);
static inline void setNoteMacro(ChannelContext &channel, uint note, int fineTune);
void initFadeCommand(const uint8 fadeTempo, const int8 endVol);
void setModuleData(const MdatResource *resource, const int8 *sampleData, uint32 sampleLen, bool autoDelete = true);
static const MdatResource *loadMdatFile(Common::SeekableReadStream &musicData);
static const int8 *loadSampleFile(uint32 &sampleLen, Common::SeekableReadStream &sampleStream);
void freeResourceDataImpl();
void effects(ChannelContext &channel);
void macroRun(ChannelContext &channel);
void advancePatterns();
bool patternRun(PatternContext &pattern);
bool trackRun(bool incStep = false);
void noteCommand(uint8 note, uint8 param1, uint8 param2, uint8 param3);
};
} // End of namespace Audio
#endif // !defined(AUDIO_MODS_TFMX_H)

View File

@@ -0,0 +1,408 @@
/* 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/>.
*
*/
// Disable symbol overrides for FILE and fseek as those are used in the
// mikmod headers.
#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
// On Windows, unlink and setjmp/longjmp may also be triggered.
#if defined(WIN32)
#define FORBIDDEN_SYMBOL_EXCEPTION_chdir
#define FORBIDDEN_SYMBOL_EXCEPTION_getcwd
#define FORBIDDEN_SYMBOL_EXCEPTION_mkdir
#define FORBIDDEN_SYMBOL_EXCEPTION_unlink
#define FORBIDDEN_SYMBOL_EXCEPTION_setjmp
#define FORBIDDEN_SYMBOL_EXCEPTION_longjmp
#endif
#include "common/ptr.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#ifdef USE_OPENMPT
#include <libopenmpt/libopenmpt.h>
#elif defined(USE_MIKMOD)
#include <mikmod.h>
#endif
#ifdef USE_OPENMPT
static size_t memoryReaderRead(void *stream, void *dst, size_t bytes) {
Common::SeekableReadStream *reader = (Common::SeekableReadStream *)stream;
if (!reader) {
return 0;
}
uint32 receivedBytes = reader->read(dst, bytes);
if (receivedBytes < bytes) {
return 0;
}
return receivedBytes;
}
static int memoryReaderSeek(void *stream, int64_t offset, int whence) {
Common::SeekableReadStream *reader = (Common::SeekableReadStream *)stream;
if (!reader) {
return -1;
}
bool ret = reader->seek(offset, whence);
if (ret)
return 0;
return -1;
}
static int64_t memoryReaderTell(void *stream) {
Common::SeekableReadStream *reader = (Common::SeekableReadStream *)stream;
if (reader) {
return reader->pos();
}
return -1;
}
#elif defined(USE_MIKMOD)
typedef struct MikMemoryReader {
MREADER core;
Common::SeekableReadStream *stream;
} MikMemoryReader;
static BOOL memoryReaderEof(MREADER *reader);
static BOOL memoryReaderRead(MREADER *reader, void *ptr, size_t size);
static int memoryReaderGet(MREADER *reader);
static int memoryReaderSeek(MREADER *reader, long offset, int whence);
static long memoryReaderTell(MREADER *reader);
MREADER *createMikMemoryReader(Common::SeekableReadStream *stream) {
MikMemoryReader *reader = (MikMemoryReader *)calloc(1, sizeof(MikMemoryReader));
if (reader) {
reader->core.Eof = &memoryReaderEof;
reader->core.Read = &memoryReaderRead;
reader->core.Get = &memoryReaderGet;
reader->core.Seek = &memoryReaderSeek;
reader->core.Tell = &memoryReaderTell;
reader->stream = stream;
}
return (MREADER *)reader;
}
static BOOL memoryReaderEof(MREADER *reader) {
MikMemoryReader *mr = (MikMemoryReader *)reader;
if (!mr)
return 1;
if (mr->stream && mr->stream->eos() == true)
return 1;
return 0;
}
static BOOL memoryReaderRead(MREADER *reader, void *ptr, size_t size) {
MikMemoryReader *mr;
mr = (MikMemoryReader *)reader;
if (!mr || !mr->stream)
return 0;
uint32 receivedBytes = mr->stream->read(ptr, size);
if (receivedBytes < size)
return 0; // not enough remaining bytes (or error)
return 1;
}
static int memoryReaderGet(MREADER *reader) {
MikMemoryReader *mr;
mr = (MikMemoryReader *)reader;
if (!mr->stream)
return -1;
return mr->stream->readByte();
}
static int memoryReaderSeek(MREADER *reader, long offset, int whence) {
MikMemoryReader *mr;
mr = (MikMemoryReader *)reader;
if (!reader || !mr->stream)
return -1;
return mr->stream->seek(offset, whence);
}
static long memoryReaderTell(MREADER *reader) {
if (reader)
return ((MikMemoryReader *)reader)->stream->pos();
return 0;
}
// End memory wrappper
#endif // USE_MIKMOD
#ifdef USE_OPENMPT
namespace Audio {
class UniversalTrackerMod : public RewindableAudioStream {
public:
UniversalTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
~UniversalTrackerMod();
// ImpulseTrackerMod functions
bool isLoaded() {
return _mpt_load_successful;
}
int readBuffer(int16 *buffer, const int numSamples) override {
openmpt_module_read_interleaved_stereo(_mod, getRate(), numSamples / 2, buffer);
return numSamples;
}
bool isStereo() const override {
return true;
}
int getRate() const override {
return _sampleRate;
}
bool endOfData() const override {
return _stream->eos();
}
bool endOfStream() const override {
return endOfData();
}
// RewindableAudioStream API
bool rewind() override {
openmpt_module_set_position_seconds(_mod, 0.0);
return true;
}
private:
DisposeAfterUse::Flag _dispose;
bool _mpt_load_successful = false;
Common::SeekableReadStream *_stream;
openmpt_module *_mod = nullptr;
int _sampleRate;
};
UniversalTrackerMod::UniversalTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
if (!stream) {
warning("UniversalTrackerMod::UniversalTrackerMod(): Input file/stream is invalid.");
return;
}
int mod_err;
const char *mod_err_str = NULL;
_stream = stream;
_dispose = disposeAfterUse;
_sampleRate = g_system->getMixer()->getOutputRate();
openmpt_stream_callbacks stream_callbacks;
stream_callbacks.read = &memoryReaderRead;
stream_callbacks.seek = &memoryReaderSeek;
stream_callbacks.tell = &memoryReaderTell;
_mod = openmpt_module_create2(stream_callbacks, _stream, NULL, NULL, NULL, NULL, &mod_err, &mod_err_str, NULL);
if (!_mod) {
mod_err_str = openmpt_error_string(mod_err);
warning("UniversalTrackerMod::UniversalTrackerMod(): Parsing mod error: %s ", mod_err_str);
openmpt_free_string(mod_err_str);
return;
}
_mpt_load_successful = true;
}
UniversalTrackerMod::~UniversalTrackerMod() {
if (_mod)
openmpt_module_destroy(_mod);
if (_dispose == DisposeAfterUse::Flag::YES)
delete _stream;
}
} // End of namespace Audio
#endif // #ifdef USE_OPENMPT
#ifdef USE_MIKMOD
namespace Audio {
class UniversalTrackerMod : public RewindableAudioStream {
public:
UniversalTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
~UniversalTrackerMod();
// ImpulseTrackerMod functions
bool isLoaded() {
return _mikmod_load_successful;
}
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override {
// Multiplied by 2 as VC_WriteBytes function expects 8 byte integer arrays, whereas buffer needs 16 ones.
VC_WriteBytes((SBYTE *)buffer, numSamples * 2);
return numSamples;
}
bool isStereo() const override {
return true;
}
int getRate() const override {
return _sampleRate;
}
bool endOfData() const override {
return !Player_Active();
}
bool endOfStream() const override {
return endOfData();
}
// RewindableAudioStream API
bool rewind() override {
Player_SetPosition(0);
return true;
}
private:
DisposeAfterUse::Flag _dispose;
bool _mikmod_load_successful = false;
Common::SeekableReadStream *_stream;
MREADER *_reader = nullptr;
MODULE *_mod = nullptr;
int _sampleRate;
};
UniversalTrackerMod::UniversalTrackerMod(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
if (!stream) {
warning("UniversalTrackerMod::UniversalTrackerMod(): Input file/stream is invalid.");
return;
}
_sampleRate = g_system->getMixer()->getOutputRate();
MikMod_InitThreads();
MikMod_RegisterDriver(&drv_nos);
// Set flags
md_mode |= DMODE_SOFT_MUSIC | DMODE_NOISEREDUCTION;
md_mixfreq = getRate();
if (MikMod_Init("")) {
warning("UniversalTrackerMod::UniversalTrackerMod(): Could not initialize sound, reason: %s",
MikMod_strerror(MikMod_errno));
return;
}
// Loading only impulse tracker loader!
MikMod_RegisterLoader(&load_it);
_stream = stream;
_dispose = disposeAfterUse;
// Load mod using custom loader class!
_reader = createMikMemoryReader(_stream);
_mod = Player_LoadGeneric(_reader, 64, 0);
if (!_mod) {
warning("UniversalTrackerMod::UniversalTrackerMod(): Parsing mod error: %s", MikMod_strerror(MikMod_errno));
return;
}
// Start mikmod playing, ie fill VC_Driver buffer with data
Player_Start(_mod);
_mikmod_load_successful = true;
}
UniversalTrackerMod::~UniversalTrackerMod() {
Player_Stop();
if (_mod)
Player_Free(_mod);
if (_reader)
free(_reader);
if (_dispose == DisposeAfterUse::Flag::YES)
delete _stream;
MikMod_Exit();
}
} // End of namespace Audio
#endif // #ifdef USE_MIKMOD
namespace Audio {
RewindableAudioStream *makeUniversalTrackerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
#if !defined(USE_OPENMPT) && !defined(USE_MIKMOD)
warning("Modplayer Support not compiled in");
if (disposeAfterUse == DisposeAfterUse::YES) {
delete stream;
}
return nullptr;
#else
UniversalTrackerMod *impulseTrackerMod = new UniversalTrackerMod(stream, disposeAfterUse);
if (!impulseTrackerMod->isLoaded()) {
delete impulseTrackerMod;
return nullptr;
}
return impulseTrackerMod;
#endif
}
}

View File

@@ -0,0 +1,56 @@
/* 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/>.
*
*/
/**
* @file
* Sound decoder used in engines:
* - sludge
*/
#ifndef AUDIO_UNIVERSALTRACKER_H
#define AUDIO_UNIVERSALTRACKER_H
#include "common/scummsys.h"
#include "common/types.h"
#if defined(USE_MIKMOD) && defined(USE_OPENMPT)
#error "MikMod and OpenMPT are mutually exclusive"
#endif
namespace Common {
class SeekableReadStream;
}
namespace Audio {
class RewindableAudioStream;
/**
* Create a new AudioStream from the Impulse Tracker data in the given stream.
*
* @param stream the SeekableReadStream from which to read the Ogg Vorbis data
* @param disposeAfterUse whether to delete the stream after use
* @return a new AudioStream, or NULL, if an error occurred
*/
RewindableAudioStream *makeUniversalTrackerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
} // End of namespace Audio
#endif // #ifndef AUDIO_UNIVERSALTRACKER_H