mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-25 12:05:53 +00:00
caca2ea61f
To implement a MOD variant for the Chewy engine, this commit makes some changes to make subclassing of the ProtrackerStream and associated Module class easier.
419 lines
10 KiB
C++
419 lines
10 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "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);
|
|
|
|
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
|