From 6558578f5423b3e1f072a4a06e12a51f48e91102 Mon Sep 17 00:00:00 2001 From: Simei Yin Date: Mon, 11 Sep 2017 03:06:45 +0200 Subject: AUDIO: Import micromod code, xm/s3m/mod decoder --- audio/mods/mod_xm_s3m.cpp | 1350 ++++++++++++++++++++++++++++++++++++++ audio/mods/mod_xm_s3m.h | 92 +++ audio/mods/module_mod_xm_s3m.cpp | 841 ++++++++++++++++++++++++ audio/mods/module_mod_xm_s3m.h | 174 +++++ audio/module.mk | 2 + 5 files changed, 2459 insertions(+) create mode 100644 audio/mods/mod_xm_s3m.cpp create mode 100644 audio/mods/mod_xm_s3m.h create mode 100644 audio/mods/module_mod_xm_s3m.cpp create mode 100644 audio/mods/module_mod_xm_s3m.h (limited to 'audio') diff --git a/audio/mods/mod_xm_s3m.cpp b/audio/mods/mod_xm_s3m.cpp new file mode 100644 index 0000000000..fdacbbbadc --- /dev/null +++ b/audio/mods/mod_xm_s3m.cpp @@ -0,0 +1,1350 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * 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/file.h" +#include "common/memstream.h" + +#include "audio/audiostream.h" +#include "audio/mods/mod_xm_s3m.h" +#include "audio/mods/module_mod_xm_s3m.h" + +namespace Modules { + +class ModXmS3mStream : public Audio::AudioStream { +private: + struct Channel { + Instrument *instrument; + Sample *sample; + Note note; + int keyOn, randomSeed, plRow; + int sampleOff, sampleIdx, sampleFra, freq, ampl, pann; + int volume, panning, fadeoutVol, volEnvTick, panEnvTick; + int period, portaPeriod, retrigCount, fxCount, avCount; + int portaUpParam, portaDownParam, tonePortaParam, offsetParam; + int finePortaUpParam, finePortaDownParam, xfinePortaParam; + int arpeggioParam, volSlideParam, gvolSlideParam, panSlideParam; + int fineVslideUpParam, fineVslideDownParam; + int retrigVolume, retrigTicks, tremorOnTicks, tremorOffTicks; + int vibratoType, vibratoPhase, vibratoSpeed, vibratoDepth; + int tremoloType, tremoloPhase, tremoloSpeed, tremoloDepth; + int tremoloAdd, vibratoAdd, arpeggioAdd; + }; + + ModuleModXmS3m _module; + bool _loadSuccess; + int _sampleRate, _interpolation, _globalVol; + int _seqPos, _breakPos, _row, _nextRow, _tick; + int _speed, _tempo, _plCount, _plChan; + int *_rampBuf; + int8 **_playCount; + Channel *_channels; + int _dataLeft; + + // mix buffer to keep a partially consumed decoded tick. + int *_mixBuffer; + int _mixBufferSamples; // number of samples kept in _mixBuffer + + static const int FP_SHIFT; + static const int FP_ONE; + static const int FP_MASK; + static const int16 sinetable[]; + + int calculateDuration(); + int calculateTickLength() { return (_sampleRate * 5) / (_tempo * 2); } + int calculateMixBufLength() { return (calculateTickLength() + 65) * 4; } + + int initPlayCount(int8 **playCount); + void setSequencePos(int pos); + int tick(); + void updateRow(); + int seek(int samplePos); + + // Sample + void downsample(int *buf, int count); + void resample(Channel &channel, int *mixBuf, int offset, int count, int sampleRate); + void updateSampleIdx(Channel &channel, int count, int sampleRate); + + // Channel + void initChannel(int idx); + void tickChannel(Channel &channel); + void updateChannelRow(Channel &channel, Note note); + + // Effects + int waveform(Channel &channel, int phase, int type); + void vibrato(Channel &channel, int fine); + void autoVibrato(Channel &channel); + void portaUp(Channel &channel, int param); + void portaDown(Channel &channel, int param); + void tonePorta(Channel &channel); + void volumeSlide(Channel &channel); + void retrigVolSlide(Channel &channel); + void tremolo(Channel &channel); + void tremor(Channel &channel); + void trigger(Channel &channel); + void calculateFreq(Channel &channel); + void calculateAmpl(Channel &channel); + + // Envelopes + void updateEnvelopes(Channel &channel); + int envelopeNextTick(Envelope &envelope, int tick, int keyOn); + int calculateAmpl(Envelope &envelope, int tick); + + // Read stream + int getAudio(int *mixBuf); + void volumeRamp(int *mixBuf, int tickLen); + +public: + // Check if module loading succeeds + bool loadSuccess() const { return _loadSuccess; } + + // Implement virtual functions + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return true; } + virtual int getRate() const { return _sampleRate; } + virtual bool endOfData() const { return _dataLeft <= 0; } + + ModXmS3mStream(Common::SeekableReadStream *stream, int rate, int interpolation); + ~ModXmS3mStream(); +}; + +const int ModXmS3mStream::FP_SHIFT = 0xF; +const int ModXmS3mStream::FP_ONE = 0x8000; +const int ModXmS3mStream::FP_MASK = 0x7FFF; +const short ModXmS3mStream::sinetable[] = { + 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 + }; + +ModXmS3mStream::ModXmS3mStream(Common::SeekableReadStream *stream, int rate, int interpolation) { + _rampBuf = nullptr; + _playCount = nullptr; + _channels = nullptr; + + if (!_module.load(*stream)) { + warning("It's not a valid Mod/S3m/Xm sound file"); + _loadSuccess = false; + return; + } + + // assign values + _loadSuccess = true; + _mixBufferSamples = 0; + _sampleRate = rate; + _interpolation = interpolation; + _rampBuf = new int[128]; + _channels = new Channel[_module.numChannels]; + _dataLeft = calculateDuration() * 4; // stereo and uint16 + _mixBuffer = nullptr; +} + +ModXmS3mStream::~ModXmS3mStream() { + if (_rampBuf) { + delete[] _rampBuf; + _rampBuf = nullptr; + } + + if (_playCount) { + delete[] _playCount; + _playCount = nullptr; + } + + if (_channels) { + delete[] _channels; + _channels = nullptr; + } + + if (_mixBuffer) { + delete []_mixBuffer; + _mixBuffer = nullptr; + } +} + +int ModXmS3mStream::initPlayCount(int8 **playCount) { + int len = 0; + for (int idx = 0; idx < _module.sequenceLen; ++idx) { + int pat = _module.sequence[idx]; + int rows = (pat < _module.numPatterns) ? _module.patterns[pat].numRows : 0; + if (playCount) { + playCount[idx] = playCount[0] ? &playCount[0][len] : nullptr; + } + len += rows; + } + return len; +} + +void ModXmS3mStream::initChannel(int idx) { + memset(&_channels[idx], 0, sizeof(Channel)); + _channels[idx].panning = _module.defaultPanning[idx]; + _channels[idx].instrument = &_module.instruments[0]; + _channels[idx].sample = &_module.instruments[0].samples[0]; + _channels[idx].randomSeed = (idx + 1) * 0xABCDEF; +} + +void ModXmS3mStream::tickChannel(Channel &channel) { + channel.vibratoAdd = 0; + channel.fxCount++; + channel.retrigCount++; + if (!(channel.note.effect == 0x7D && channel.fxCount <= channel.note.param)) { + switch (channel.note.volume & 0xF0) { + case 0x60: /* Vol Slide Down.*/ + channel.volume -= channel.note.volume & 0xF; + if (channel.volume < 0) { + channel.volume = 0; + } + break; + case 0x70: /* Vol Slide Up.*/ + channel.volume += channel.note.volume & 0xF; + if (channel.volume > 64) { + channel.volume = 64; + } + break; + case 0xB0: /* Vibrato.*/ + channel.vibratoPhase += channel.vibratoSpeed; + vibrato(channel, 0); + break; + case 0xD0: /* Pan Slide Left.*/ + channel.panning -= channel.note.volume & 0xF; + if (channel.panning < 0) { + channel.panning = 0; + } + break; + case 0xE0: /* Pan Slide Right.*/ + channel.panning += channel.note.volume & 0xF; + if (channel.panning > 255) { + channel.panning = 255; + } + break; + case 0xF0: /* Tone Porta.*/ + tonePorta(channel); + break; + } + } + switch (channel.note.effect) { + case 0x01: + case 0x86: /* Porta Up. */ + portaUp(channel, channel.portaUpParam); + break; + case 0x02: + case 0x85: /* Porta Down. */ + portaDown(channel, channel.portaDownParam); + break; + case 0x03: + case 0x87: /* Tone Porta. */ + tonePorta(channel); + break; + case 0x04: + case 0x88: /* Vibrato. */ + channel.vibratoPhase += channel.vibratoSpeed; + vibrato(channel, 0); + break; + case 0x05: + case 0x8C: /* Tone Porta + Vol Slide. */ + tonePorta(channel); + volumeSlide(channel); + break; + case 0x06: + case 0x8B: /* Vibrato + Vol Slide. */ + channel.vibratoPhase += channel.vibratoSpeed; + vibrato(channel, 0); + volumeSlide(channel); + break; + case 0x07: + case 0x92: /* Tremolo. */ + channel.tremoloPhase += channel.tremoloSpeed; + tremolo(channel); + break; + case 0x0A: + case 0x84: /* Vol Slide. */ + volumeSlide(channel); + break; + case 0x11: /* Global Volume Slide. */ + _globalVol = _globalVol + (channel.gvolSlideParam >> 4) - (channel.gvolSlideParam & 0xF); + if (_globalVol < 0) { + _globalVol = 0; + } + if (_globalVol > 64) { + _globalVol = 64; + } + break; + case 0x19: /* Panning Slide. */ + channel.panning = channel.panning + (channel.panSlideParam >> 4) - (channel.panSlideParam & 0xF); + if (channel.panning < 0) { + channel.panning = 0; + } + if (channel.panning > 255) { + channel.panning = 255; + } + break; + case 0x1B: + case 0x91: /* Retrig + Vol Slide. */ + retrigVolSlide(channel); + break; + case 0x1D: + case 0x89: /* Tremor. */ + tremor(channel); + break; + case 0x79: /* Retrig. */ + if (channel.fxCount >= channel.note.param) { + channel.fxCount = 0; + channel.sampleIdx = channel.sampleFra = 0; + } + break; + case 0x7C: + case 0xFC: /* Note Cut. */ + if (channel.note.param == channel.fxCount) { + channel.volume = 0; + } + break; + case 0x7D: + case 0xFD: /* Note Delay. */ + if (channel.note.param == channel.fxCount) { + trigger(channel); + } + break; + case 0x8A: /* Arpeggio. */ + if (channel.fxCount == 1) { + channel.arpeggioAdd = channel.arpeggioParam >> 4; + } else if (channel.fxCount == 2) { + channel.arpeggioAdd = channel.arpeggioParam & 0xF; + } else { + channel.arpeggioAdd = channel.fxCount = 0; + } + break; + case 0x95: /* Fine Vibrato. */ + channel.vibratoPhase += channel.vibratoSpeed; + vibrato(channel, 1); + break; + } + autoVibrato(channel); + calculateFreq(channel); + calculateAmpl(channel); + updateEnvelopes(channel); +} + +void ModXmS3mStream::volumeSlide(Channel &channel) { + int up = channel.volSlideParam >> 4; + int down = channel.volSlideParam & 0xF; + if (down == 0xF && up > 0) { + /* Fine slide up.*/ + if (channel.fxCount == 0) { + channel.volume += up; + } + } else if (up == 0xF && down > 0) { + /* Fine slide down.*/ + if (channel.fxCount == 0) { + channel.volume -= down; + } + } else if (channel.fxCount > 0 || _module.fastVolSlides) { + /* Normal.*/ + channel.volume += up - down; + } + if (channel.volume > 64) { + channel.volume = 64; + } + if (channel.volume < 0) { + channel.volume = 0; + } +} + +void ModXmS3mStream::portaUp(Channel &channel, int param) { + switch (param & 0xF0) { + case 0xE0: /* Extra-fine porta.*/ + if (channel.fxCount == 0) { + channel.period -= param & 0xF; + } + break; + case 0xF0: /* Fine porta.*/ + if (channel.fxCount == 0) { + channel.period -= (param & 0xF) << 2; + } + break; + default:/* Normal porta.*/ + if (channel.fxCount > 0) { + channel.period -= param << 2; + } + break; + } + if (channel.period < 0) { + channel.period = 0; + } +} + +void ModXmS3mStream::portaDown(Channel &channel, int param) { + if (channel.period > 0) { + switch (param & 0xF0) { + case 0xE0: /* Extra-fine porta.*/ + if (channel.fxCount == 0) { + channel.period += param & 0xF; + } + break; + case 0xF0: /* Fine porta.*/ + if (channel.fxCount == 0) { + channel.period += (param & 0xF) << 2; + } + break; + default:/* Normal porta.*/ + if (channel.fxCount > 0) { + channel.period += param << 2; + } + break; + } + if (channel.period > 65535) { + channel.period = 65535; + } + } +} + +void ModXmS3mStream::tonePorta(Channel &channel) { + if (channel.period > 0) { + if (channel.period < channel.portaPeriod) { + channel.period += channel.tonePortaParam << 2; + if (channel.period > channel.portaPeriod) { + channel.period = channel.portaPeriod; + } + } else { + channel.period -= channel.tonePortaParam << 2; + if (channel.period < channel.portaPeriod) { + channel.period = channel.portaPeriod; + } + } + } +} + +int ModXmS3mStream::waveform(Channel &channel, int phase, int type) { + int amplitude = 0; + switch (type) { + default: /* Sine. */ + amplitude = sinetable[phase & 0x1F]; + if ((phase & 0x20) > 0) { + amplitude = -amplitude; + } + break; + case 6: /* Saw Up.*/ + amplitude = (((phase + 0x20) & 0x3F) << 3) - 255; + break; + case 1: + case 7: /* Saw Down. */ + amplitude = 255 - (((phase + 0x20) & 0x3F) << 3); + break; + case 2: + case 5: /* Square. */ + amplitude = (phase & 0x20) > 0 ? 255 : -255; + break; + case 3: + case 8: /* Random. */ + amplitude = (channel.randomSeed >> 20) - 255; + channel.randomSeed = (channel.randomSeed * 65 + 17) & 0x1FFFFFFF; + break; + } + return amplitude; +} + +void ModXmS3mStream::vibrato(Channel &channel, int fine) { + int wave = waveform(channel, channel.vibratoPhase, channel.vibratoType & 0x3); + channel.vibratoAdd = wave * channel.vibratoDepth >> (fine ? 7 : 5); +} + +void ModXmS3mStream::tremolo(Channel &channel) { + int wave = waveform(channel, channel.tremoloPhase, channel.tremoloType & 0x3); + channel.tremoloAdd = wave * channel.tremoloDepth >> 6; +} + +void ModXmS3mStream::tremor(Channel &channel) { + if (channel.retrigCount >= channel.tremorOnTicks) { + channel.tremoloAdd = -64; + } + if (channel.retrigCount >= (channel.tremorOnTicks + channel.tremorOffTicks)) { + channel.tremoloAdd = channel.retrigCount = 0; + } +} + +void ModXmS3mStream::retrigVolSlide(Channel &channel) { + if (channel.retrigCount >= channel.retrigTicks) { + channel.retrigCount = channel.sampleIdx = channel.sampleFra = 0; + switch (channel.retrigVolume) { + case 0x1: + channel.volume = channel.volume - 1; + break; + case 0x2: + channel.volume = channel.volume - 2; + break; + case 0x3: + channel.volume = channel.volume - 4; + break; + case 0x4: + channel.volume = channel.volume - 8; + break; + case 0x5: + channel.volume = channel.volume - 16; + break; + case 0x6: + channel.volume = channel.volume * 2 / 3; + break; + case 0x7: + channel.volume = channel.volume >> 1; + break; + case 0x8: /* ? */ + break; + case 0x9: + channel.volume = channel.volume + 1; + break; + case 0xA: + channel.volume = channel.volume + 2; + break; + case 0xB: + channel.volume = channel.volume + 4; + break; + case 0xC: + channel.volume = channel.volume + 8; + break; + case 0xD: + channel.volume = channel.volume + 16; + break; + case 0xE: + channel.volume = channel.volume * 3 / 2; + break; + case 0xF: + channel.volume = channel.volume << 1; + break; + } + if (channel.volume < 0) { + channel.volume = 0; + } + if (channel.volume > 64) { + channel.volume = 64; + } + } +} + +void ModXmS3mStream::trigger(Channel &channel) { + int ins = channel.note.instrument; + if (ins > 0 && ins <= _module.numInstruments) { + channel.instrument = &_module.instruments[ins]; + int key = channel.note.key < 97 ? channel.note.key : 0; + int sam = channel.instrument->keyToSample[key]; + Sample *sample = &channel.instrument->samples[sam]; + channel.volume = sample->volume >= 64 ? 64 : sample->volume & 0x3F; + if (sample->panning > 0) { + channel.panning = (sample->panning - 1) & 0xFF; + } + if (channel.period > 0 && sample->loopLength > 1) { + /* Amiga trigger.*/ + channel.sample = sample; + } + channel.sampleOff = 0; + channel.volEnvTick = channel.panEnvTick = 0; + channel.fadeoutVol = 32768; + channel.keyOn = 1; + } + if (channel.note.effect == 0x09 || channel.note.effect == 0x8F) { + /* Set Sample Offset. */ + if (channel.note.param > 0) { + channel.offsetParam = channel.note.param; + } + channel.sampleOff = channel.offsetParam << 8; + } + if (channel.note.volume >= 0x10 && channel.note.volume < 0x60) { + channel.volume = channel.note.volume < 0x50 ? channel.note.volume - 0x10 : 64; + } + switch (channel.note.volume & 0xF0) { + case 0x80: /* Fine Vol Down.*/ + channel.volume -= channel.note.volume & 0xF; + if (channel.volume < 0) { + channel.volume = 0; + } + break; + case 0x90: /* Fine Vol Up.*/ + channel.volume += channel.note.volume & 0xF; + if (channel.volume > 64) { + channel.volume = 64; + } + break; + case 0xA0: /* Set Vibrato Speed.*/ + if ((channel.note.volume & 0xF) > 0) { + channel.vibratoSpeed = channel.note.volume & 0xF; + } + break; + case 0xB0: /* Vibrato.*/ + if ((channel.note.volume & 0xF) > 0) { + channel.vibratoDepth = channel.note.volume & 0xF; + } + vibrato(channel, 0); + break; + case 0xC0: /* Set Panning.*/ + channel.panning = (channel.note.volume & 0xF) * 17; + break; + case 0xF0: /* Tone Porta.*/ + if ((channel.note.volume & 0xF) > 0) { + channel.tonePortaParam = channel.note.volume & 0xF; + } + break; + } + if (channel.note.key > 0) { + if (channel.note.key > 96) { + channel.keyOn = 0; + } else { + int porta = (channel.note.volume & 0xF0) == 0xF0 || channel.note.effect == 0x03 || channel.note.effect == 0x05 || channel.note.effect == 0x87 || channel.note.effect == 0x8C; + if (!porta) { + ins = channel.instrument->keyToSample[channel.note.key]; + channel.sample = &channel.instrument->samples[ins]; + } + int finetune = channel.sample->finetune; + if (channel.note.effect == 0x75 || channel.note.effect == 0xF2) { + /* Set Fine Tune. */ + finetune = ((channel.note.param & 0xF) << 4) - 128; + } + int key = channel.note.key + channel.sample->relNote; + if (key < 1) { + key = 1; + } + if (key > 120) { + key = 120; + } + int period = (key << 6) + (finetune >> 1); + if (_module.linearPeriods) { + channel.portaPeriod = 7744 - period; + } else { + channel.portaPeriod = 29021 * ModuleModXmS3m::exp2((period << FP_SHIFT) / -768) >> FP_SHIFT; + } + if (!porta) { + channel.period = channel.portaPeriod; + channel.sampleIdx = channel.sampleOff; + channel.sampleFra = 0; + if (channel.vibratoType < 4) { + channel.vibratoPhase = 0; + } + if (channel.tremoloType < 4) { + channel.tremoloPhase = 0; + } + channel.retrigCount = channel.avCount = 0; + } + } + } +} + +void ModXmS3mStream::updateEnvelopes(Channel &channel) { + if (channel.instrument->volEnv.enabled) { + if (!channel.keyOn) { + channel.fadeoutVol -= channel.instrument->volFadeout; + if (channel.fadeoutVol < 0) { + channel.fadeoutVol = 0; + } + } + channel.volEnvTick = envelopeNextTick(channel.instrument->volEnv, channel.volEnvTick, channel.keyOn); + } + if (channel.instrument->panEnv.enabled) { + channel.panEnvTick = envelopeNextTick(channel.instrument->panEnv, channel.panEnvTick, channel.keyOn); + } +} + +void ModXmS3mStream::autoVibrato(Channel &channel) { + int depth = channel.instrument->vibDepth & 0x7F; + if (depth > 0) { + int sweep = channel.instrument->vibSweep & 0x7F; + int rate = channel.instrument->vibRate & 0x7F; + int type = channel.instrument->vibType; + if (channel.avCount < sweep) { + depth = depth * channel.avCount / sweep; + } + int wave = waveform(channel, channel.avCount * rate >> 2, type + 4); + channel.vibratoAdd += wave * depth >> 8; + channel.avCount++; + } +} + +void ModXmS3mStream::calculateFreq(Channel &channel) { + int per = channel.period + channel.vibratoAdd; + if (_module.linearPeriods) { + per = per - (channel.arpeggioAdd << 6); + if (per < 28 || per > 7680) { + per = 7680; + } + channel.freq = ((_module.c2Rate >> 4) * ModuleModXmS3m::exp2(((4608 - per) << FP_SHIFT) / 768)) >> (FP_SHIFT - 4); + } else { + if (per > 29021) { + per = 29021; + } + per = (per << FP_SHIFT) / ModuleModXmS3m::exp2((channel.arpeggioAdd << FP_SHIFT) / 12); + if (per < 28) { + per = 29021; + } + channel.freq = _module.c2Rate * 1712 / per; + } +} + +void ModXmS3mStream::calculateAmpl(Channel &channel) { + int envPan = 32, envVol = channel.keyOn ? 64 : 0; + if (channel.instrument->volEnv.enabled) { + envVol = calculateAmpl(channel.instrument->volEnv, channel.volEnvTick); + } + int vol = channel.volume + channel.tremoloAdd; + if (vol > 64) { + vol = 64; + } + if (vol < 0) { + vol = 0; + } + vol = (vol * _module.gain * FP_ONE) >> 13; + vol = (vol * channel.fadeoutVol) >> 15; + channel.ampl = (vol * _globalVol * envVol) >> 12; + if (channel.instrument->panEnv.enabled) { + envPan = calculateAmpl(channel.instrument->panEnv, channel.panEnvTick); + } + int range = (channel.panning < 128) ? channel.panning : (255 - channel.panning); + channel.pann = channel.panning + (range * (envPan - 32) >> 5); +} + +void ModXmS3mStream::updateChannelRow(Channel &channel, Note note) { + channel.note = note; + channel.retrigCount++; + channel.vibratoAdd = channel.tremoloAdd = channel.arpeggioAdd = channel.fxCount = 0; + if (!((note.effect == 0x7D || note.effect == 0xFD) && note.param > 0)) { + /* Not note delay.*/ + trigger(channel); + } + switch (channel.note.effect) { + case 0x01: + case 0x86: /* Porta Up. */ + if (channel.note.param > 0) { + channel.portaUpParam = channel.note.param; + } + portaUp(channel, channel.portaUpParam); + break; + case 0x02: + case 0x85: /* Porta Down. */ + if (channel.note.param > 0) { + channel.portaDownParam = channel.note.param; + } + portaDown(channel, channel.portaDownParam); + break; + case 0x03: + case 0x87: /* Tone Porta. */ + if (channel.note.param > 0) { + channel.tonePortaParam = channel.note.param; + } + break; + case 0x04: + case 0x88: /* Vibrato. */ + if ((channel.note.param >> 4) > 0) { + channel.vibratoSpeed = channel.note.param >> 4; + } + if ((channel.note.param & 0xF) > 0) { + channel.vibratoDepth = channel.note.param & 0xF; + } + vibrato(channel, 0); + break; + case 0x05: + case 0x8C: /* Tone Porta + Vol Slide. */ + if (channel.note.param > 0) { + channel.volSlideParam = channel.note.param; + } + volumeSlide(channel); + break; + case 0x06: + case 0x8B: /* Vibrato + Vol Slide. */ + if (channel.note.param > 0) { + channel.volSlideParam = channel.note.param; + } + vibrato(channel, 0); + volumeSlide(channel); + break; + case 0x07: + case 0x92: /* Tremolo. */ + if ((channel.note.param >> 4) > 0) { + channel.tremoloSpeed = channel.note.param >> 4; + } + if ((channel.note.param & 0xF) > 0) { + channel.tremoloDepth = channel.note.param & 0xF; + } + tremolo(channel); + break; + case 0x08: /* Set Panning.*/ + channel.panning = (channel.note.param < 128) ? (channel.note.param << 1) : 255; + break; + case 0x0A: + case 0x84: /* Vol Slide. */ + if (channel.note.param > 0) { + channel.volSlideParam = channel.note.param; + } + volumeSlide(channel); + break; + case 0x0C: /* Set Volume. */ + channel.volume = channel.note.param >= 64 ? 64 : channel.note.param & 0x3F; + break; + case 0x10: + case 0x96: /* Set Global Volume. */ + _globalVol = channel.note.param >= 64 ? 64 : channel.note.param & 0x3F; + break; + case 0x11: /* Global Volume Slide. */ + if (channel.note.param > 0) { + channel.gvolSlideParam = channel.note.param; + } + break; + case 0x14: /* Key Off. */ + channel.keyOn = 0; + break; + case 0x15: /* Set Envelope Tick. */ + channel.volEnvTick = channel.panEnvTick = channel.note.param & 0xFF; + break; + case 0x19: /* Panning Slide. */ + if (channel.note.param > 0) { + channel.panSlideParam = channel.note.param; + } + break; + case 0x1B: + case 0x91: /* Retrig + Vol Slide. */ + if ((channel.note.param >> 4) > 0) { + channel.retrigVolume = channel.note.param >> 4; + } + if ((channel.note.param & 0xF) > 0) { + channel.retrigTicks = channel.note.param & 0xF; + } + retrigVolSlide(channel); + break; + case 0x1D: + case 0x89: /* Tremor. */ + if ((channel.note.param >> 4) > 0) { + channel.tremorOnTicks = channel.note.param >> 4; + } + if ((channel.note.param & 0xF) > 0) { + channel.tremorOffTicks = channel.note.param & 0xF; + } + tremor(channel); + break; + case 0x21: /* Extra Fine Porta. */ + if (channel.note.param > 0) { + channel.xfinePortaParam = channel.note.param; + } + switch (channel.xfinePortaParam & 0xF0) { + case 0x10: + portaUp(channel, 0xE0 | (channel.xfinePortaParam & 0xF)); + break; + case 0x20: + portaDown(channel, 0xE0 | (channel.xfinePortaParam & 0xF)); + break; + } + break; + case 0x71: /* Fine Porta Up. */ + if (channel.note.param > 0) { + channel.finePortaUpParam = channel.note.param; + } + portaUp(channel, 0xF0 | (channel.finePortaUpParam & 0xF)); + break; + case 0x72: /* Fine Porta Down. */ + if (channel.note.param > 0) { + channel.finePortaDownParam = channel.note.param; + } + portaDown(channel, 0xF0 | (channel.finePortaDownParam & 0xF)); + break; + case 0x74: + case 0xF3: /* Set Vibrato Waveform. */ + if (channel.note.param < 8) { + channel.vibratoType = channel.note.param; + } + break; + case 0x77: + case 0xF4: /* Set Tremolo Waveform. */ + if (channel.note.param < 8) { + channel.tremoloType = channel.note.param; + } + break; + case 0x7A: /* Fine Vol Slide Up. */ + if (channel.note.param > 0) { + channel.fineVslideUpParam = channel.note.param; + } + channel.volume += channel.fineVslideUpParam; + if (channel.volume > 64) { + channel.volume = 64; + } + break; + case 0x7B: /* Fine Vol Slide Down. */ + if (channel.note.param > 0) { + channel.fineVslideDownParam = channel.note.param; + } + channel.volume -= channel.fineVslideDownParam; + if (channel.volume < 0) { + channel.volume = 0; + } + break; + case 0x7C: + case 0xFC: /* Note Cut. */ + if (channel.note.param <= 0) { + channel.volume = 0; + } + break; + case 0x8A: /* Arpeggio. */ + if (channel.note.param > 0) { + channel.arpeggioParam = channel.note.param; + } + break; + case 0x95: /* Fine Vibrato.*/ + if ((channel.note.param >> 4) > 0) { + channel.vibratoSpeed = channel.note.param >> 4; + } + if ((channel.note.param & 0xF) > 0) { + channel.vibratoDepth = channel.note.param & 0xF; + } + vibrato(channel, 1); + break; + case 0xF8: /* Set Panning. */ + channel.panning = channel.note.param * 17; + break; + } + autoVibrato(channel); + calculateFreq(channel); + calculateAmpl(channel); + updateEnvelopes(channel); +} + +int ModXmS3mStream::tick() { + int count = 1; + if (--_tick <= 0) { + _tick = _speed; + updateRow(); + } else { + for (int idx = 0; idx < _module.numChannels; idx++) { + tickChannel(_channels[idx]); + } + } + if (_playCount && _playCount[0]) { + count = _playCount[_seqPos][_row] - 1; + } + return count; +} + +void ModXmS3mStream::updateRow() { + if (_nextRow < 0) { + _breakPos = _seqPos + 1; + _nextRow = 0; + } + if (_breakPos >= 0) { + if (_breakPos >= _module.sequenceLen) { + _breakPos = _nextRow = 0; + } + while (_module.sequence[_breakPos] >= _module.numPatterns) { + _breakPos++; + if (_breakPos >= _module.sequenceLen) { + _breakPos = _nextRow = 0; + } + } + _seqPos = _breakPos; + for (int idx = 0; idx < _module.numChannels; idx++) { + _channels[idx].plRow = 0; + } + _breakPos = -1; + } + Pattern &pattern = _module.patterns[_module.sequence[_seqPos]]; + _row = _nextRow; + if (_row >= pattern.numRows) { + _row = 0; + } + if (_playCount && _playCount[0]) { + int count = _playCount[_seqPos][_row]; + if (_plCount < 0 && count < 127) { + _playCount[_seqPos][_row] = count + 1; + } + } + _nextRow = _row + 1; + if (_nextRow >= pattern.numRows) { + _nextRow = -1; + } + for (int idx = 0; idx < _module.numChannels; ++idx) { + Note note = pattern.getNote(_row, idx); + if (note.effect == 0xE) { + note.effect = 0x70 | (note.param >> 4); + note.param &= 0xF; + } + if (note.effect == 0x93) { + note.effect = 0xF0 | (note.param >> 4); + note.param &= 0xF; + } + if (note.effect == 0 && note.param > 0) { + note.effect = 0x8A; + } + + Channel &channel = _channels[idx]; + updateChannelRow(channel, note); + switch (note.effect) { + case 0x81: /* Set Speed. */ + if (note.param > 0) { + _tick = _speed = note.param; + } + break; + case 0xB: + case 0x82: /* Pattern Jump.*/ + if (_plCount < 0) { + _breakPos = note.param; + _nextRow = 0; + } + break; + case 0xD: + case 0x83: /* Pattern Break.*/ + if (_plCount < 0) { + if (_breakPos < 0) { + _breakPos = _seqPos + 1; + } + _nextRow = (note.param >> 4) * 10 + (note.param & 0xF); + } + break; + case 0xF: /* Set Speed/Tempo.*/ + if (note.param > 0) { + if (note.param < 32) { + _tick = _speed = note.param; + } else { + _tempo = note.param; + } + } + break; + case 0x94: /* Set Tempo.*/ + if (note.param > 32) { + _tempo = note.param; + } + break; + case 0x76: + case 0xFB: /* Pattern Loop.*/ + if (note.param == 0) { + /* Set loop marker on this channel. */ + channel.plRow = _row; + } + if (channel.plRow < _row && _breakPos < 0) { + /* Marker valid. */ + if (_plCount < 0) { + /* Not already looping, begin. */ + _plCount = note.param; + _plChan = idx; + } + if (_plChan == idx) { + /* Next Loop.*/ + if (_plCount == 0) { + /* Loop finished. Invalidate current marker. */ + channel.plRow = _row + 1; + } else { + /* Loop. */ + _nextRow = channel.plRow; + } + _plCount--; + } + } + break; + case 0x7E: + case 0xFE: /* Pattern Delay.*/ + _tick = _speed + _speed * note.param; + break; + } + } +} + +int ModXmS3mStream::envelopeNextTick(Envelope &envelope, int tick, int keyOn) { + tick++; + if (envelope.looped && tick >= envelope.loopEndTick) { + tick = envelope.loopStartTick; + } + if (envelope.sustain && keyOn && tick >= envelope.sustainTick) { + tick = envelope.sustainTick; + } + return tick; +} + +int ModXmS3mStream::calculateAmpl(Envelope &envelope, int tick) { + int idx, point, dt, da; + int ampl = envelope.pointsAmpl[envelope.numPoints - 1]; + if (tick < envelope.pointsTick[envelope.numPoints - 1]) { + point = 0; + for (idx = 1; idx < envelope.numPoints; idx++) { + if (envelope.pointsTick[idx] <= tick) { + point = idx; + } + } + dt = envelope.pointsTick[point + 1] - envelope.pointsTick[point]; + da = envelope.pointsAmpl[point + 1] - envelope.pointsAmpl[point]; + ampl = envelope.pointsAmpl[point]; + ampl += ((da << 24) / dt) * (tick - envelope.pointsTick[point]) >> 24; + } + return ampl; +} + +int ModXmS3mStream::calculateDuration() { + int count = 0, duration = 0; + setSequencePos(0); + while (count < 1) { + duration += calculateTickLength(); + count = tick(); + } + setSequencePos(0); + return duration; +} + +/* Seek to approximately the specified sample position. + The actual sample position reached is returned. */ +int ModXmS3mStream::seek(int samplePos) { + int currentPos = 0; + setSequencePos(0); + int tickLen = calculateTickLength(); + while ((samplePos - currentPos) >= tickLen) { + for (int idx = 0; idx < _module.numChannels; ++idx) { + updateSampleIdx(_channels[idx], tickLen * 2, _sampleRate * 2); + } + currentPos += tickLen; + tick(); + tickLen = calculateTickLength(); + } + return currentPos; +} + +void ModXmS3mStream::resample(Channel &channel, int *mixBuf, int offset, int count, int sampleRate) { + Sample *sample = channel.sample; + int lGain = 0, rGain = 0, samIdx = 0, samFra = 0, step = 0; + int loopLen = 0, loopEnd = 0, outIdx = 0, outEnd = 0, y = 0, m = 0, c = 0; + int16 *sampleData = channel.sample->data; + if (channel.ampl > 0) { + lGain = channel.ampl * (255 - channel.pann) >> 8; + rGain = channel.ampl * channel.pann >> 8; + samIdx = channel.sampleIdx; + samFra = channel.sampleFra; + step = (channel.freq << (FP_SHIFT - 3)) / (sampleRate >> 3); + loopLen = sample->loopLength; + loopEnd = sample->loopStart + loopLen; + outIdx = offset * 2; + outEnd = (offset + count) * 2; + if (_interpolation) { + while (outIdx < outEnd) { + if (samIdx >= loopEnd) { + if (loopLen > 1) { + while (samIdx >= loopEnd) { + samIdx -= loopLen; + } + } else { + break; + } + } + c = sampleData[samIdx]; + m = sampleData[samIdx + 1] - c; + y = ((m * samFra) >> FP_SHIFT) + c; + mixBuf[outIdx++] += (y * lGain) >> FP_SHIFT; + mixBuf[outIdx++] += (y * rGain) >> FP_SHIFT; + samFra += step; + samIdx += samFra >> FP_SHIFT; + samFra &= FP_MASK; + } + } else { + while (outIdx < outEnd) { + if (samIdx >= loopEnd) { + if (loopLen > 1) { + while (samIdx >= loopEnd) { + samIdx -= loopLen; + } + } else { + break; + } + } + if (samIdx < 0) + samIdx = 0; + y = sampleData[samIdx]; + mixBuf[outIdx++] += (y * lGain) >> FP_SHIFT; + mixBuf[outIdx++] += (y * rGain) >> FP_SHIFT; + samFra += step; + samIdx += samFra >> FP_SHIFT; + samFra &= FP_MASK; + } + } + } +} + +void ModXmS3mStream::updateSampleIdx(Channel &channel, int count, int sampleRate) { + Sample *sample = channel.sample; + int step = (channel.freq << (FP_SHIFT - 3)) / (sampleRate >> 3); + channel.sampleFra += step * count; + channel.sampleIdx += channel.sampleFra >> FP_SHIFT; + if (channel.sampleIdx > (int)sample->loopStart) { + if (sample->loopLength > 1) { + channel.sampleIdx = sample->loopStart + (channel.sampleIdx - sample->loopStart) % sample->loopLength; + } else { + channel.sampleIdx = sample->loopStart; + } + } + channel.sampleFra &= FP_MASK; +} + +void ModXmS3mStream::volumeRamp(int *mixBuf, int tickLen) { + int rampRate = 256 * 2048 / _sampleRate; + for (int idx = 0, a1 = 0; a1 < 256; idx += 2, a1 += rampRate) { + int a2 = 256 - a1; + mixBuf[idx] = (mixBuf[idx] * a1 + _rampBuf[idx] * a2) >> 8; + mixBuf[idx + 1] = (mixBuf[idx + 1] * a1 + _rampBuf[idx + 1] * a2) >> 8; + } + memcpy(_rampBuf, &mixBuf[tickLen * 2], 128 * sizeof(int)); +} + +/* 2:1 downsampling with simple but effective anti-aliasing. Buf must contain count * 2 + 1 stereo samples. */ +void ModXmS3mStream::downsample(int *buf, int count) { + int outLen = count * 2; + for (int idx = 0, outIdx = 0; outIdx < outLen; idx += 4, outIdx += 2) { + buf[outIdx] = (buf[idx] >> 2) + (buf[idx + 2] >> 1) + (buf[idx + 4] >> 2); + buf[outIdx + 1] = (buf[idx + 1] >> 2) + (buf[idx + 3] >> 1) + (buf[idx + 5] >> 2); + } +} + +/* Generates audio and returns the number of stereo samples written into mixBuf. */ +int ModXmS3mStream::getAudio(int *mixBuf) { + if (_mixBuffer) { + memcpy(mixBuf, _mixBuffer, _mixBufferSamples * sizeof(int)); + delete []_mixBuffer; + _mixBuffer = nullptr; + return _mixBufferSamples; + } + + int tickLen = calculateTickLength(); + /* Clear output buffer. */ + memset(mixBuf, 0, (tickLen + 65) * 4 * sizeof(int)); + /* Resample. */ + for (int idx = 0; idx < _module.numChannels; idx++) { + Channel &channel = _channels[idx]; + resample(channel, mixBuf, 0, (tickLen + 65) * 2, _sampleRate * 2); + updateSampleIdx(channel, tickLen * 2, _sampleRate * 2); + } + downsample(mixBuf, tickLen + 64); + volumeRamp(mixBuf, tickLen); + tick(); + return tickLen * 2; // stereo samples +} + +int ModXmS3mStream::readBuffer(int16 *buffer, const int numSamples) { + int samplesRead = 0; + while (samplesRead < numSamples && _dataLeft >= 0) { + int *mixBuf = new int[calculateMixBufLength()]; + int samples = getAudio(mixBuf); + if (samplesRead + samples > numSamples) { + _mixBufferSamples = samplesRead + samples - numSamples; + samples -= _mixBufferSamples; + _mixBuffer = new int[_mixBufferSamples]; + memcpy(_mixBuffer, mixBuf + samples, _mixBufferSamples * sizeof(int)); + } + for (int idx = 0; idx < samples; ++idx) { + int ampl = mixBuf[idx]; + if (ampl > 32767) { + ampl = 32767; + } + if (ampl < -32768) { + ampl = -32768; + } + *buffer++ = ampl; + } + samplesRead += samples; + delete []mixBuf; // free + } + _dataLeft -= samplesRead * 2; + + return samplesRead; +} + +void ModXmS3mStream::setSequencePos(int pos) { + if (pos >= _module.sequenceLen) { + pos = 0; + } + _breakPos = pos; + _nextRow = 0; + _tick = 1; + _globalVol = _module.defaultGvol; + _speed = _module.defaultSpeed > 0 ? _module.defaultSpeed : 6; + _tempo = _module.defaultTempo > 0 ? _module.defaultTempo : 125; + _plCount = _plChan = -1; + + // play count + if (_playCount) { + delete[] _playCount[0]; + delete[] _playCount; + } + _playCount = new int8 *[_module.sequenceLen]; + memset(_playCount, 0, _module.sequenceLen * sizeof(int8 *)); + int len = initPlayCount(_playCount); + _playCount[0] = new int8[len]; + memset(_playCount[0], 0, len * sizeof(int8)); + initPlayCount(_playCount); + + for (int idx = 0; idx < _module.numChannels; ++idx) { + initChannel(idx); + } + memset(_rampBuf, 0, 128 * sizeof(int)); + tick(); +} + +} // End of namespace Modules + +namespace Audio { + +AudioStream *makeModXmS3mStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, int rate, int interpolation) { + Modules::ModXmS3mStream *soundStream = new Modules::ModXmS3mStream(stream, rate, interpolation); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + if (!soundStream->loadSuccess()) { + delete soundStream; + return nullptr; + } + + return (AudioStream *)soundStream; +} + +} // End of namespace Audio diff --git a/audio/mods/mod_xm_s3m.h b/audio/mods/mod_xm_s3m.h new file mode 100644 index 0000000000..904adae952 --- /dev/null +++ b/audio/mods/mod_xm_s3m.h @@ -0,0 +1,92 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * 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. + * + * @param stream the ReadStream from which to read the tracker sound data + * @param disposeAfterUse whether to delete the stream after use + * @param rate sample rate + * @param interpolation interpolation effect level + */ +AudioStream *makeModXmS3mStream(Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse, + int rate = 48000, int interpolation = 0); + +} // End of namespace Audio + +#endif diff --git a/audio/mods/module_mod_xm_s3m.cpp b/audio/mods/module_mod_xm_s3m.cpp new file mode 100644 index 0000000000..6283dbe726 --- /dev/null +++ b/audio/mods/module_mod_xm_s3m.cpp @@ -0,0 +1,841 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * 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::exp2(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::log2(int x) { + int y = 16 << FP_SHIFT; + for (int step = y; step > 0; step >>= 1) { + if (exp2(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); + + // 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]; + memset(instruments, 0, sizeof(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(); + assert(sample.finetune < 0x10); + + 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]; + memset(patterns[i].notes, 0, numNotes * sizeof(Note)); + 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 * log2((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 && numChannels == 4) { + effect = param = 0; + } + 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 = 0; + } 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 (int 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]; + memset(patterns[i].notes, 0, numNotes * sizeof(Note)); + + 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]; + memset(instruments, 0, (numInstruments + 1) * sizeof(Instrument)); + instruments[0].samples = new Sample[1]; + memset(instruments[0].samples, 0, sizeof(Sample)); + 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]; + memset(ins.samples, 0, ins.numSamples * sizeof(Sample)); + 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]; + memset(instruments, 0, sizeof(Instrument) * (numInstruments + 1)); + instruments[0].numSamples = 1; + instruments[0].samples = new Sample[1]; + memset(instruments[0].samples, 0, sizeof(Sample)); + for (int i = 1; i <= numInstruments; ++i) { + Instrument &instrum = instruments[i]; + instrum.numSamples = 1; + instrum.samples = new Sample[1]; + memset(instrum.samples, 0, sizeof(Sample)); + 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 = (log2(st.readUint32LE()) - log2(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]; + memset(patterns, 0, numPatterns * sizeof(Pattern)); + 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]; + memset(patterns[i].notes, 0, numChannels * 64 * sizeof(Note)); + 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]; + memset(defaultPanning, 0, 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; +} + +void ModuleModXmS3m::readSampleSint8(Common::SeekableReadStream &stream, int length, int16 *dest) { + for (int i = 0; i < length; ++i) { + dest[i] = (stream.readSByte() << 8); + 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 diff --git a/audio/mods/module_mod_xm_s3m.h b/audio/mods/module_mod_xm_s3m.h new file mode 100644 index 0000000000..d92ac6679c --- /dev/null +++ b/audio/mods/module_mod_xm_s3m.h @@ -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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * 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) { + 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]; + int 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 log2(int x); + static int exp2(int x); + +private: + bool loadMod(Common::SeekableReadStream &stream); + bool loadXm(Common::SeekableReadStream &stream); + bool loadS3m(Common::SeekableReadStream &stream); + + 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 diff --git a/audio/module.mk b/audio/module.mk index 9e002d57ba..0d95ecc6f1 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -34,7 +34,9 @@ MODULE_OBJS := \ decoders/xa.o \ mods/infogrames.o \ mods/maxtrax.o \ + mods/mod_xm_s3m.o \ mods/module.o \ + mods/module_mod_xm_s3m.o \ mods/protracker.o \ mods/paula.o \ mods/rjp1.o \ -- cgit v1.2.3