/* 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. * */ #include "audio/mods/protracker.h" #include "audio/mods/paula.h" #include "audio/mods/module.h" #include "common/textconsole.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; byte lastSample; uint16 period; Offset offset; byte vol; byte finetune; // For effect 0x0 - Arpeggio bool arpeggio; byte arpeggioNotes[3]; // For effect 0x3 - Porta to note uint16 portaToNote; byte portaToNoteSpeed; // For effect 0x4 - Vibrato int vibrato; byte vibratoPos; byte vibratoSpeed; byte vibratoDepth; // For effect 0xED - Delay sample byte delaySample; byte delaySampleTick; } _track[4]; public: ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo); Modules::Module *getModule() { // Ordinarily, the Module is not meant to be seen outside of // this class, but occasionally, it's useful to be able to // manipulate it directly. The Hopkins engine uses this to // repair a broken song. return &_module; } private: void interrupt(); void 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 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].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; } 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, 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