Initial commit
This commit is contained in:
470
audio/mods/infogrames.cpp
Normal file
470
audio/mods/infogrames.cpp
Normal 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
147
audio/mods/infogrames.h
Normal 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
1053
audio/mods/maxtrax.cpp
Normal file
File diff suppressed because it is too large
Load Diff
219
audio/mods/maxtrax.h
Normal file
219
audio/mods/maxtrax.h
Normal 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
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
99
audio/mods/mod_xm_s3m.h
Normal 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
250
audio/mods/module.cpp
Normal 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
90
audio/mods/module.h
Normal 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
|
||||
951
audio/mods/module_mod_xm_s3m.cpp
Normal file
951
audio/mods/module_mod_xm_s3m.cpp
Normal 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 ¬e = 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
|
||||
174
audio/mods/module_mod_xm_s3m.h
Normal file
174
audio/mods/module_mod_xm_s3m.h
Normal 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
334
audio/mods/paula.cpp
Normal 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
244
audio/mods/paula.h
Normal 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
419
audio/mods/protracker.cpp
Normal 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
152
audio/mods/protracker.h
Normal 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
586
audio/mods/rjp1.cpp
Normal 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
48
audio/mods/rjp1.h
Normal 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
281
audio/mods/soundfx.cpp
Normal 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
51
audio/mods/soundfx.h
Normal 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
1195
audio/mods/tfmx.cpp
Normal file
File diff suppressed because it is too large
Load Diff
277
audio/mods/tfmx.h
Normal file
277
audio/mods/tfmx.h
Normal 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)
|
||||
408
audio/mods/universaltracker.cpp
Normal file
408
audio/mods/universaltracker.cpp
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
56
audio/mods/universaltracker.h
Normal file
56
audio/mods/universaltracker.h
Normal 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
|
||||
Reference in New Issue
Block a user