aboutsummaryrefslogtreecommitdiff
path: root/audio/mods/protracker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'audio/mods/protracker.cpp')
-rw-r--r--audio/mods/protracker.cpp466
1 files changed, 466 insertions, 0 deletions
diff --git a/audio/mods/protracker.cpp b/audio/mods/protracker.cpp
new file mode 100644
index 0000000000..6051338900
--- /dev/null
+++ b/audio/mods/protracker.cpp
@@ -0,0 +1,466 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "audio/mods/protracker.h"
+#include "audio/mods/paula.h"
+#include "audio/mods/module.h"
+
+#include "audio/audiostream.h"
+
+namespace Modules {
+
+class ProtrackerStream : public ::Audio::Paula {
+private:
+ Module _module;
+
+ 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 {
+ byte sample;
+ 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);
+
+private:
+ void interrupt();
+
+ void doPorta(int track) {
+ if (_track[track].portaToNote && _track[track].portaToNoteSpeed) {
+ if (_track[track].period < _track[track].portaToNote) {
+ _track[track].period += _track[track].portaToNoteSpeed;
+ if (_track[track].period > _track[track].portaToNote)
+ _track[track].period = _track[track].portaToNote;
+ } else if (_track[track].period > _track[track].portaToNote) {
+ _track[track].period -= _track[track].portaToNoteSpeed;
+ if (_track[track].period < _track[track].portaToNote)
+ _track[track].period = _track[track].portaToNote;
+ }
+ }
+ }
+ void 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 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;
+ }
+
+ void updateRow();
+ void updateEffects();
+
+};
+
+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(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) :
+ Paula(stereo, rate, rate/50) {
+ bool result = _module.load(*stream, offs);
+ assert(result);
+
+ _tick = _row = _pos = 0;
+
+ _speed = 6;
+ _bpm = 125;
+
+ _hasJumpToPattern = false;
+ _jumpToPattern = 0;
+
+ _hasPatternBreak = false;
+ _skipRow = 0;
+
+ _hasPatternLoop = false;
+ _patternLoopCount = 0;
+ _patternLoopRow = 0;
+
+ _patternDelay = 0;
+
+ memset(_track, 0, sizeof(_track));
+
+ startPaula();
+}
+
+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].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);
+ }
+ }
+
+ 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;
+ if (note.period) {
+ _track[track].arpeggioNotes[0] = note.note;
+ _track[track].arpeggioNotes[1] = note.note + ex;
+ _track[track].arpeggioNotes[2] = note.note + 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;
+ }
+ 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 : 0,
+ sample.len,
+ sample.replen);
+ setChannelOffset(track, _track[track].offset);
+ _track[track].sample = 0;
+ }
+ }
+}
+
+} // End of namespace Modules
+
+namespace Audio {
+
+AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) {
+ return new Modules::ProtrackerStream(stream, offs, rate, stereo);
+}
+
+} // End of namespace Audio