/* C++ player code for Reality Adlib Tracker 2.0a (file version 2.1). Please note, this is just the player code. This does no checking of the tune data before it tries to play it, as most use cases will be a known tune being used in a production. So if you're writing an application that loads unknown tunes in at run time then you'll want to do more validity checking. To use: - Instantiate the RADPlayer object - Initialise player for your tune by calling the Init() method. Supply a pointer to the tune file and a function for writing to the OPL3 registers. - Call the Update() method a number of times per second as returned by GetHertz(). If your tune is using the default BPM setting you can safely just call it 50 times a second, unless it's a legacy "slow-timer" tune then it'll need to be 18.2 times a second. - When you're done, stop calling Update() and call the Stop() method to turn off all sound and reset the OPL3 hardware. */ #include #ifndef RAD_DETECT_REPEATS #define RAD_DETECT_REPEATS 0 #endif //================================================================================================== // RAD player class. //================================================================================================== class RADPlayer { // Various constants enum { kTracks = 100, kChannels = 9, kTrackLines = 64, kRiffTracks = 10, kInstruments = 127, cmPortamentoUp = 0x1, cmPortamentoDwn = 0x2, cmToneSlide = 0x3, cmToneVolSlide = 0x5, cmVolSlide = 0xA, cmSetVol = 0xC, cmJumpToLine = 0xD, cmSetSpeed = 0xF, cmIgnore = ('I' - 55), cmMultiplier = ('M' - 55), cmRiff = ('R' - 55), cmTranspose = ('T' - 55), cmFeedback = ('U' - 55), cmVolume = ('V' - 55), }; enum e_Source { SNone, SRiff, SIRiff, }; enum { fKeyOn = 1 << 0, fKeyOff = 1 << 1, fKeyedOn = 1 << 2, }; struct CInstrument { uint8_t Feedback[2]; uint8_t Panning[2]; uint8_t Algorithm; uint8_t Detune; uint8_t Volume; uint8_t RiffSpeed; uint8_t * Riff; uint8_t Operators[4][5]; }; struct CEffects { int8_t PortSlide; int8_t VolSlide; uint16_t ToneSlideFreq; uint8_t ToneSlideOct; uint8_t ToneSlideSpeed; int8_t ToneSlideDir; }; struct CChannel { uint8_t LastInstrument; CInstrument * Instrument; uint8_t Volume; uint8_t DetuneA; uint8_t DetuneB; uint8_t KeyFlags; uint16_t CurrFreq; int8_t CurrOctave; CEffects FX; struct CRiff { CEffects FX; uint8_t * Track; uint8_t * TrackStart; uint8_t Line; uint8_t Speed; uint8_t SpeedCnt; int8_t TransposeOctave; int8_t TransposeNote; uint8_t LastInstrument; } Riff, IRiff; }; public: RADPlayer() : Initialised(false) {} void Init(const void *tune, void (*opl3)(void *, uint16_t, uint8_t), void *arg); void Stop(); bool Update(); int GetHertz() const { return Hertz; } int GetPlayTimeInSeconds() const { return PlayTime / Hertz; } int GetTunePos() const { return Order; } int GetTuneLength() const { return OrderListSize; } int GetTuneLine() const { return Line; } void SetMasterVolume(int vol) { MasterVol = vol; } int GetMasterVolume() const { return MasterVol; } int GetSpeed() const { return Speed; } #if RAD_DETECT_REPEATS uint32_t ComputeTotalTime(); #endif private: bool UnpackNote(uint8_t *&s, uint8_t &last_instrument); uint8_t * GetTrack(); uint8_t * SkipToLine(uint8_t *trk, uint8_t linenum, bool chan_riff = false); void PlayLine(); void PlayNote(int channum, int8_t notenum, int8_t octave, uint16_t instnum, uint8_t cmd = 0, uint8_t param = 0, e_Source src = SNone, int op = 0); void LoadInstrumentOPL3(int channum); void PlayNoteOPL3(int channum, int8_t octave, int8_t note); void ResetFX(CEffects *fx); void TickRiff(int channum, CChannel::CRiff &riff, bool chan_riff); void ContinueFX(int channum, CEffects *fx); void SetVolume(int channum, uint8_t vol); void GetSlideDir(int channum, CEffects *fx); void LoadInstMultiplierOPL3(int channum, int op, uint8_t mult); void LoadInstVolumeOPL3(int channum, int op, uint8_t vol); void LoadInstFeedbackOPL3(int channum, int which, uint8_t fb); void Portamento(uint16_t channum, CEffects *fx, int8_t amount, bool toneslide); void Transpose(int8_t note, int8_t octave); void SetOPL3(uint16_t reg, uint8_t val) { OPL3Regs[reg] = val; OPL3(OPL3Arg, reg, val); } uint8_t GetOPL3(uint16_t reg) const { return OPL3Regs[reg]; } void (*OPL3)(void *, uint16_t, uint8_t); void * OPL3Arg; CInstrument Instruments[kInstruments]; CChannel Channels[kChannels]; uint32_t PlayTime; #if RAD_DETECT_REPEATS uint32_t OrderMap[4]; bool Repeating; #endif int16_t Hertz; uint8_t * OrderList; uint8_t * Tracks[kTracks]; uint8_t * Riffs[kRiffTracks][kChannels]; uint8_t * Track; bool Initialised; uint8_t Speed; uint8_t OrderListSize; uint8_t SpeedCnt; uint8_t Order; uint8_t Line; int8_t Entrances; uint8_t MasterVol; int8_t LineJump; uint8_t OPL3Regs[512]; // Values exported by UnpackNote() int8_t NoteNum; int8_t OctaveNum; uint8_t InstNum; uint8_t EffectNum; uint8_t Param; bool LastNote; static const int8_t NoteSize[]; static const uint16_t ChanOffsets3[9], Chn2Offsets3[9]; static const uint16_t NoteFreq[]; static const uint16_t OpOffsets3[9][4]; static const bool AlgCarriers[7][4]; }; //-------------------------------------------------------------------------------------------------- const int8_t RADPlayer::NoteSize[] = { 0, 2, 1, 3, 1, 3, 2, 4 }; const uint16_t RADPlayer::ChanOffsets3[9] = { 0, 1, 2, 0x100, 0x101, 0x102, 6, 7, 8 }; // OPL3 first channel const uint16_t RADPlayer::Chn2Offsets3[9] = { 3, 4, 5, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108 }; // OPL3 second channel const uint16_t RADPlayer::NoteFreq[] = { 0x16b,0x181,0x198,0x1b0,0x1ca,0x1e5,0x202,0x220,0x241,0x263,0x287,0x2ae }; const uint16_t RADPlayer::OpOffsets3[9][4] = { { 0x00B, 0x008, 0x003, 0x000 }, { 0x00C, 0x009, 0x004, 0x001 }, { 0x00D, 0x00A, 0x005, 0x002 }, { 0x10B, 0x108, 0x103, 0x100 }, { 0x10C, 0x109, 0x104, 0x101 }, { 0x10D, 0x10A, 0x105, 0x102 }, { 0x113, 0x110, 0x013, 0x010 }, { 0x114, 0x111, 0x014, 0x011 }, { 0x115, 0x112, 0x015, 0x012 } }; const bool RADPlayer::AlgCarriers[7][4] = { { true, false, false, false }, // 0 - 2op - op < op { true, true, false, false }, // 1 - 2op - op + op { true, false, false, false }, // 2 - 4op - op < op < op < op { true, false, false, true }, // 3 - 4op - op < op < op + op { true, false, true, false }, // 4 - 4op - op < op + op < op { true, false, true, true }, // 5 - 4op - op < op + op + op { true, true, true, true }, // 6 - 4op - op + op + op + op }; //================================================================================================== // Initialise a RAD tune for playback. This assumes the tune data is valid and does minimal data // checking. //================================================================================================== void RADPlayer::Init(const void *tune, void (*opl3)(void *, uint16_t, uint8_t), void *arg) { Initialised = false; // Version check; we only support version 2.1 tune files if (*((uint8_t *)tune + 0x10) != 0x21) { Hertz = -1; return; } // The OPL3 call-back OPL3 = opl3; OPL3Arg = arg; for (int i = 0; i < kTracks; i++) Tracks[i] = 0; for (int i = 0; i < kRiffTracks; i++) for (int j = 0; j < kChannels; j++) Riffs[i][j] = 0; uint8_t *s = (uint8_t *)tune + 0x11; uint8_t flags = *s++; Speed = flags & 0x1F; // Is BPM value present? Hertz = 50; if (flags & 0x20) { Hertz = (s[0] | (int(s[1]) << 8)) * 2 / 5; s += 2; } // Slow timer tune? Return an approximate hz if (flags & 0x40) Hertz = 18; // Skip any description while (*s) s++; s++; // Unpack the instruments while (1) { // Instrument number, 0 indicates end of list uint8_t inst_num = *s++; if (inst_num == 0) break; // Skip instrument name s += *s++; CInstrument &inst = Instruments[inst_num - 1]; uint8_t alg = *s++; inst.Algorithm = alg & 7; inst.Panning[0] = (alg >> 3) & 3; inst.Panning[1] = (alg >> 5) & 3; if (inst.Algorithm < 7) { uint8_t b = *s++; inst.Feedback[0] = b & 15; inst.Feedback[1] = b >> 4; b = *s++; inst.Detune = b >> 4; inst.RiffSpeed = b & 15; inst.Volume = *s++; for (int i = 0; i < 4; i++) { uint8_t *op = inst.Operators[i]; for (int j = 0; j < 5; j++) op[j] = *s++; } } else { // Ignore MIDI instrument data s += 6; } // Instrument riff? if (alg & 0x80) { int size = s[0] | (int(s[1]) << 8); s += 2; inst.Riff = s; s += size; } else inst.Riff = 0; } // Get order list OrderListSize = *s++; OrderList = s; s += OrderListSize; // Locate the tracks while (1) { // Track number uint8_t track_num = *s++; if (track_num >= kTracks) break; // Track size in bytes int size = s[0] | (int(s[1]) << 8); s += 2; Tracks[track_num] = s; s += size; } // Locate the riffs while (1) { // Riff id uint8_t riffid = *s++; uint8_t riffnum = riffid >> 4; uint8_t channum = riffid & 15; if (riffnum >= kRiffTracks || channum > kChannels) break; // Track size in bytes int size = s[0] | (int(s[1]) << 8); s += 2; Riffs[riffnum][channum - 1] = s; s += size; } // Done parsing tune, now set up for play for (int i = 0; i < 512; i++) OPL3Regs[i] = 255; Stop(); Initialised = true; } //================================================================================================== // Stop all sounds and reset the tune. Tune will play from the beginning again if you continue to // Update(). //================================================================================================== void RADPlayer::Stop() { // Clear all registers for (uint16_t reg = 0x20; reg < 0xF6; reg++) { // Ensure envelopes decay all the way uint8_t val = (reg >= 0x60 && reg < 0xA0) ? 0xFF : 0; SetOPL3(reg, val); SetOPL3(reg + 0x100, val); } // Configure OPL3 SetOPL3(1, 0x20); // Allow waveforms SetOPL3(8, 0); // No split point SetOPL3(0xbd, 0); // No drums, etc. SetOPL3(0x104, 0); // Everything 2-op by default SetOPL3(0x105, 1); // OPL3 mode on #if RAD_DETECT_REPEATS // The order map keeps track of which patterns we've played so we can detect when the tune // starts to repeat. Jump markers can't be reliably used for this PlayTime = 0; Repeating = false; for (int i = 0; i < 4; i++) OrderMap[i] = 0; #endif // Initialise play values SpeedCnt = 1; Order = 0; Track = GetTrack(); Line = 0; Entrances = 0; MasterVol = 64; // Initialise channels for (int i = 0; i < kChannels; i++) { CChannel &chan = Channels[i]; chan.LastInstrument = 0; chan.Instrument = 0; chan.Volume = 0; chan.DetuneA = 0; chan.DetuneB = 0; chan.KeyFlags = 0; chan.Riff.SpeedCnt = 0; chan.IRiff.SpeedCnt = 0; } } //================================================================================================== // Playback update. Call BPM * 2 / 5 times a second. Use GetHertz() for this number after the // tune has been initialised. Returns true if tune is starting to repeat. //================================================================================================== bool RADPlayer::Update() { if (!Initialised) return false; // Run riffs for (int i = 0; i < kChannels; i++) { CChannel &chan = Channels[i]; TickRiff(i, chan.IRiff, false); TickRiff(i, chan.Riff, true); } // Run main track PlayLine(); // Run effects for (int i = 0; i < kChannels; i++) { CChannel &chan = Channels[i]; ContinueFX(i, &chan.IRiff.FX); ContinueFX(i, &chan.Riff.FX); ContinueFX(i, &chan.FX); } // Update play time. We convert to seconds when queried PlayTime++; #if RAD_DETECT_REPEATS return Repeating; #else return false; #endif } //================================================================================================== // Unpacks a single RAD note. //================================================================================================== bool RADPlayer::UnpackNote(uint8_t *&s, uint8_t &last_instrument) { uint8_t chanid = *s++; InstNum = 0; EffectNum = 0; Param = 0; // Unpack note data uint8_t note = 0; if (chanid & 0x40) { uint8_t n = *s++; note = n & 0x7F; // Retrigger last instrument? if (n & 0x80) InstNum = last_instrument; } // Do we have an instrument? if (chanid & 0x20) { InstNum = *s++; last_instrument = InstNum; } // Do we have an effect? if (chanid & 0x10) { EffectNum = *s++; Param = *s++; } NoteNum = note & 15; OctaveNum = note >> 4; return ((chanid & 0x80) != 0); } //================================================================================================== // Get current track as indicated by order list. //================================================================================================== uint8_t *RADPlayer::GetTrack() { // If at end of tune start again from beginning if (Order >= OrderListSize) Order = 0; uint8_t track_num = OrderList[Order]; // Jump marker? Note, we don't recognise multiple jump markers as that could put us into an // infinite loop if (track_num & 0x80) { Order = track_num & 0x7F; track_num = OrderList[Order] & 0x7F; } #if RAD_DETECT_REPEATS // Check for tune repeat, and mark order in order map if (Order < 128) { int byte = Order >> 5; uint32_t bit = uint32_t(1) << (Order & 31); if (OrderMap[byte] & bit) Repeating = true; else OrderMap[byte] |= bit; } #endif return Tracks[track_num]; } //================================================================================================== // Skip through track till we reach the given line or the next higher one. Returns null if none. //================================================================================================== uint8_t *RADPlayer::SkipToLine(uint8_t *trk, uint8_t linenum, bool chan_riff) { while (1) { uint8_t lineid = *trk; if ((lineid & 0x7F) >= linenum) return trk; if (lineid & 0x80) break; trk++; // Skip channel notes uint8_t chanid; do { chanid = *trk++; trk += NoteSize[(chanid >> 4) & 7]; } while (!(chanid & 0x80) && !chan_riff); } return 0; } //================================================================================================== // Plays one line of current track and advances pointers. //================================================================================================== void RADPlayer::PlayLine() { SpeedCnt--; if (SpeedCnt > 0) return; SpeedCnt = Speed; // Reset channel effects for (int i = 0; i < kChannels; i++) ResetFX(&Channels[i].FX); LineJump = -1; // At the right line? uint8_t *trk = Track; if (trk && (*trk & 0x7F) <= Line) { uint8_t lineid = *trk++; // Run through channels bool last; do { int channum = *trk & 15; CChannel &chan = Channels[channum]; last = UnpackNote(trk, chan.LastInstrument); PlayNote(channum, NoteNum, OctaveNum, InstNum, EffectNum, Param); } while (!last); // Was this the last line? if (lineid & 0x80) trk = 0; Track = trk; } // Move to next line Line++; if (Line >= kTrackLines || LineJump >= 0) { if (LineJump >= 0) Line = LineJump; else Line = 0; // Move to next track in order list Order++; Track = GetTrack(); } } //================================================================================================== // Play a single note. Returns the line number in the next pattern to jump to if a jump command was // found, or -1 if none. //================================================================================================== void RADPlayer::PlayNote(int channum, int8_t notenum, int8_t octave, uint16_t instnum, uint8_t cmd, uint8_t param, e_Source src, int op) { CChannel &chan = Channels[channum]; // Recursion detector. This is needed as riffs can trigger other riffs, and they could end up // in a loop if (Entrances >= 8) return; Entrances++; // Select which effects source we're using CEffects *fx = &chan.FX; if (src == SRiff) fx = &chan.Riff.FX; else if (src == SIRiff) fx = &chan.IRiff.FX; bool transposing = false; // For tone-slides the note is the target if (cmd == cmToneSlide) { if (notenum > 0 && notenum <= 12) { fx->ToneSlideOct = octave; fx->ToneSlideFreq = NoteFreq[notenum - 1]; } goto toneslide; } // Playing a new instrument? if (instnum > 0) { CInstrument *oldinst = chan.Instrument; CInstrument *inst = &Instruments[instnum - 1]; chan.Instrument = inst; // Ignore MIDI instruments if (inst->Algorithm == 7) { Entrances--; return; } LoadInstrumentOPL3(channum); // Bounce the channel chan.KeyFlags |= fKeyOff | fKeyOn; ResetFX(&chan.IRiff.FX); if (src != SIRiff || inst != oldinst) { // Instrument riff? if (inst->Riff && inst->RiffSpeed > 0) { chan.IRiff.Track = chan.IRiff.TrackStart = inst->Riff; chan.IRiff.Line = 0; chan.IRiff.Speed = inst->RiffSpeed; chan.IRiff.LastInstrument = 0; // Note given with riff command is used to transpose the riff if (notenum >= 1 && notenum <= 12) { chan.IRiff.TransposeOctave = octave; chan.IRiff.TransposeNote = notenum; transposing = true; } else { chan.IRiff.TransposeOctave = 3; chan.IRiff.TransposeNote = 12; } // Do first tick of riff chan.IRiff.SpeedCnt = 1; TickRiff(channum, chan.IRiff, false); } else chan.IRiff.SpeedCnt = 0; } } // Starting a channel riff? if (cmd == cmRiff || cmd == cmTranspose) { ResetFX(&chan.Riff.FX); uint8_t p0 = param / 10; uint8_t p1 = param % 10; chan.Riff.Track = p1 > 0 ? Riffs[p0][p1 - 1] : 0; if (chan.Riff.Track) { chan.Riff.TrackStart = chan.Riff.Track; chan.Riff.Line = 0; chan.Riff.Speed = Speed; chan.Riff.LastInstrument = 0; // Note given with riff command is used to transpose the riff if (cmd == cmTranspose && notenum >= 1 && notenum <= 12) { chan.Riff.TransposeOctave = octave; chan.Riff.TransposeNote = notenum; transposing = true; } else { chan.Riff.TransposeOctave = 3; chan.Riff.TransposeNote = 12; } // Do first tick of riff chan.Riff.SpeedCnt = 1; TickRiff(channum, chan.Riff, true); } else chan.Riff.SpeedCnt = 0; } // Play the note if (!transposing && notenum > 0) { // Key-off? if (notenum == 15) chan.KeyFlags |= fKeyOff; if (!chan.Instrument || chan.Instrument->Algorithm < 7) PlayNoteOPL3(channum, octave, notenum); } // Process effect switch (cmd) { case cmSetVol: SetVolume(channum, param); break; case cmSetSpeed: if (src == SNone) { Speed = param; SpeedCnt = param; } else if (src == SRiff) { chan.Riff.Speed = param; chan.Riff.SpeedCnt = param; } else if (src == SIRiff) { chan.IRiff.Speed = param; chan.IRiff.SpeedCnt = param; } break; case cmPortamentoUp: fx->PortSlide = param; break; case cmPortamentoDwn: fx->PortSlide = -int8_t(param); break; case cmToneVolSlide: case cmVolSlide: { int8_t val = param; if (val >= 50) val = -(val - 50); fx->VolSlide = val; if (cmd != cmToneVolSlide) break; } // Fall through! case cmToneSlide: { toneslide: uint8_t speed = param; if (speed) fx->ToneSlideSpeed = speed; GetSlideDir(channum, fx); break; } case cmJumpToLine: { if (param >= kTrackLines) break; // Note: jump commands in riffs are checked for within TickRiff() if (src == SNone) LineJump = param; break; } case cmMultiplier: { if (src == SIRiff) LoadInstMultiplierOPL3(channum, op, param); break; } case cmVolume: { if (src == SIRiff) LoadInstVolumeOPL3(channum, op, param); break; } case cmFeedback: { if (src == SIRiff) { uint8_t which = param / 10; uint8_t fb = param % 10; LoadInstFeedbackOPL3(channum, which, fb); } break; } } Entrances--; } //================================================================================================== // Sets the OPL3 registers for a given instrument. //================================================================================================== void RADPlayer::LoadInstrumentOPL3(int channum) { CChannel &chan = Channels[channum]; const CInstrument *inst = chan.Instrument; if (!inst) return; uint8_t alg = inst->Algorithm; chan.Volume = inst->Volume; chan.DetuneA = (inst->Detune + 1) >> 1; chan.DetuneB = inst->Detune >> 1; // Turn on 4-op mode for algorithms 2 and 3 (algorithms 4 to 6 are simulated with 2-op mode) if (channum < 6) { uint8_t mask = 1 << channum; SetOPL3(0x104, (GetOPL3(0x104) & ~mask) | (alg == 2 || alg == 3 ? mask : 0)); } // Left/right/feedback/algorithm SetOPL3(0xC0 + ChanOffsets3[channum], ((inst->Panning[1] ^ 3) << 4) | inst->Feedback[1] << 1 | (alg == 3 || alg == 5 || alg == 6 ? 1 : 0)); SetOPL3(0xC0 + Chn2Offsets3[channum], ((inst->Panning[0] ^ 3) << 4) | inst->Feedback[0] << 1 | (alg == 1 || alg == 6 ? 1 : 0)); // Load the operators for (int i = 0; i < 4; i++) { static const uint8_t blank[] = { 0, 0x3F, 0, 0xF0, 0 }; const uint8_t *op = (alg < 2 && i >= 2) ? blank : inst->Operators[i]; uint16_t reg = OpOffsets3[channum][i]; uint16_t vol = ~op[1] & 0x3F; // Do volume scaling for carriers if (AlgCarriers[alg][i]) { vol = vol * inst->Volume / 64; vol = vol * MasterVol / 64; } SetOPL3(reg + 0x20, op[0]); SetOPL3(reg + 0x40, (op[1] & 0xC0) | ((vol ^ 0x3F) & 0x3F)); SetOPL3(reg + 0x60, op[2]); SetOPL3(reg + 0x80, op[3]); SetOPL3(reg + 0xE0, op[4]); } } //================================================================================================== // Play note on OPL3 hardware. //================================================================================================== void RADPlayer::PlayNoteOPL3(int channum, int8_t octave, int8_t note) { CChannel &chan = Channels[channum]; uint16_t o1 = ChanOffsets3[channum]; uint16_t o2 = Chn2Offsets3[channum]; // Key off the channel if (chan.KeyFlags & fKeyOff) { chan.KeyFlags &= ~(fKeyOff | fKeyedOn); SetOPL3(0xB0 + o1, GetOPL3(0xB0 + o1) & ~0x20); SetOPL3(0xB0 + o2, GetOPL3(0xB0 + o2) & ~0x20); } if (note == 15) return; bool op4 = (chan.Instrument && chan.Instrument->Algorithm >= 2); uint16_t freq = NoteFreq[note - 1]; uint16_t frq2 = freq; chan.CurrFreq = freq; chan.CurrOctave = octave; // Detune. We detune both channels in the opposite direction so the note retains its tuning freq += chan.DetuneA; frq2 -= chan.DetuneB; // Frequency low byte if (op4) SetOPL3(0xA0 + o1, frq2 & 0xFF); SetOPL3(0xA0 + o2, freq & 0xFF); // Frequency high bits + octave + key on if (chan.KeyFlags & fKeyOn) chan.KeyFlags = (chan.KeyFlags & ~fKeyOn) | fKeyedOn; if (op4) SetOPL3(0xB0 + o1, (frq2 >> 8) | (octave << 2) | ((chan.KeyFlags & fKeyedOn) ? 0x20 : 0)); else SetOPL3(0xB0 + o1, 0); SetOPL3(0xB0 + o2, (freq >> 8) | (octave << 2) | ((chan.KeyFlags & fKeyedOn) ? 0x20 : 0)); } //================================================================================================== // Prepare FX for new line. //================================================================================================== void RADPlayer::ResetFX(CEffects *fx) { fx->PortSlide = 0; fx->VolSlide = 0; fx->ToneSlideDir = 0; } //================================================================================================== // Tick the channel riff. //================================================================================================== void RADPlayer::TickRiff(int channum, CChannel::CRiff &riff, bool chan_riff) { uint8_t lineid; if (riff.SpeedCnt == 0) { ResetFX(&riff.FX); return; } riff.SpeedCnt--; if (riff.SpeedCnt > 0) return; riff.SpeedCnt = riff.Speed; uint8_t line = riff.Line++; if (riff.Line >= kTrackLines) riff.SpeedCnt = 0; ResetFX(&riff.FX); // Is this the current line in track? uint8_t *trk = riff.Track; if (trk && (*trk & 0x7F) == line) { lineid = *trk++; if (chan_riff) { // Channel riff: play current note UnpackNote(trk, riff.LastInstrument); Transpose(riff.TransposeNote, riff.TransposeOctave); PlayNote(channum, NoteNum, OctaveNum, InstNum, EffectNum, Param, SRiff); } else { // Instrument riff: here each track channel is an extra effect that can run, but is not // actually a different physical channel bool last; do { int col = *trk & 15; last = UnpackNote(trk, riff.LastInstrument); if (EffectNum != cmIgnore) Transpose(riff.TransposeNote, riff.TransposeOctave); PlayNote(channum, NoteNum, OctaveNum, InstNum, EffectNum, Param, SIRiff, col > 0 ? (col - 1) & 3 : 0); } while (!last); } // Last line? if (lineid & 0x80) trk = 0; riff.Track = trk; } // Special case; if next line has a jump command, run it now if (!trk || (*trk++ & 0x7F) != riff.Line) return; UnpackNote(trk, lineid); // lineid is just a dummy here if (EffectNum == cmJumpToLine && Param < kTrackLines) { riff.Line = Param; riff.Track = SkipToLine(riff.TrackStart, Param, chan_riff); } } //================================================================================================== // This continues any effects that operate continuously (eg. slides). //================================================================================================== void RADPlayer::ContinueFX(int channum, CEffects *fx) { CChannel &chan = Channels[channum]; if (fx->PortSlide) Portamento(channum, fx, fx->PortSlide, false); if (fx->VolSlide) { int8_t vol = chan.Volume; vol -= fx->VolSlide; if (vol < 0) vol = 0; SetVolume(channum, vol); } if (fx->ToneSlideDir) Portamento(channum, fx, fx->ToneSlideDir, true); } //================================================================================================== // Sets the volume of given channel. //================================================================================================== void RADPlayer::SetVolume(int channum, uint8_t vol) { CChannel &chan = Channels[channum]; // Ensure volume is within range if (vol > 64) vol = 64; chan.Volume = vol; // Scale volume to master volume vol = vol * MasterVol / 64; CInstrument *inst = chan.Instrument; if (!inst) return; uint8_t alg = inst->Algorithm; // Set volume of all carriers for (int i = 0; i < 4; i++) { uint8_t *op = inst->Operators[i]; // Is this operator a carrier? if (!AlgCarriers[alg][i]) continue; uint8_t opvol = uint16_t((op[1] & 63) ^ 63) * vol / 64; uint16_t reg = 0x40 + OpOffsets3[channum][i]; SetOPL3(reg, (GetOPL3(reg) & 0xC0) | (opvol ^ 0x3F)); } } //================================================================================================== // Starts a tone-slide. //================================================================================================== void RADPlayer::GetSlideDir(int channum, CEffects *fx) { CChannel &chan = Channels[channum]; int8_t speed = fx->ToneSlideSpeed; if (speed > 0) { uint8_t oct = fx->ToneSlideOct; uint16_t freq = fx->ToneSlideFreq; uint16_t oldfreq = chan.CurrFreq; uint8_t oldoct = chan.CurrOctave; if (oldoct > oct) speed = -speed; else if (oldoct == oct) { if (oldfreq > freq) speed = -speed; else if (oldfreq == freq) speed = 0; } } fx->ToneSlideDir = speed; } //================================================================================================== // Load multiplier value into operator. //================================================================================================== void RADPlayer::LoadInstMultiplierOPL3(int channum, int op, uint8_t mult) { uint16_t reg = 0x20 + OpOffsets3[channum][op]; SetOPL3(reg, (GetOPL3(reg) & 0xF0) | (mult & 15)); } //================================================================================================== // Load volume value into operator. //================================================================================================== void RADPlayer::LoadInstVolumeOPL3(int channum, int op, uint8_t vol) { uint16_t reg = 0x40 + OpOffsets3[channum][op]; SetOPL3(reg, (GetOPL3(reg) & 0xC0) | ((vol & 0x3F) ^ 0x3F)); } //================================================================================================== // Load feedback value into instrument. //================================================================================================== void RADPlayer::LoadInstFeedbackOPL3(int channum, int which, uint8_t fb) { if (which == 0) { uint16_t reg = 0xC0 + Chn2Offsets3[channum]; SetOPL3(reg, (GetOPL3(reg) & 0x31) | ((fb & 7) << 1)); } else if (which == 1) { uint16_t reg = 0xC0 + ChanOffsets3[channum]; SetOPL3(reg, (GetOPL3(reg) & 0x31) | ((fb & 7) << 1)); } } //================================================================================================== // This adjusts the pitch of the given channel's note. There may also be a limiting value on the // portamento (for tone slides). //================================================================================================== void RADPlayer::Portamento(uint16_t channum, CEffects *fx, int8_t amount, bool toneslide) { CChannel &chan = Channels[channum]; uint16_t freq = chan.CurrFreq; uint8_t oct = chan.CurrOctave; freq += amount; if (freq < 0x156) { if (oct > 0) { oct--; freq += 0x2AE - 0x156; } else freq = 0x156; } else if (freq > 0x2AE) { if (oct < 7) { oct++; freq -= 0x2AE - 0x156; } else freq = 0x2AE; } if (toneslide) { if (amount >= 0) { if (oct > fx->ToneSlideOct || (oct == fx->ToneSlideOct && freq >= fx->ToneSlideFreq)) { freq = fx->ToneSlideFreq; oct = fx->ToneSlideOct; } } else { if (oct < fx->ToneSlideOct || (oct == fx->ToneSlideOct && freq <= fx->ToneSlideFreq)) { freq = fx->ToneSlideFreq; oct = fx->ToneSlideOct; } } } chan.CurrFreq = freq; chan.CurrOctave = oct; // Apply detunes uint16_t frq2 = freq - chan.DetuneB; freq += chan.DetuneA; // Write value back to OPL3 uint16_t chan_offset = Chn2Offsets3[channum]; SetOPL3(0xA0 + chan_offset, freq & 0xFF); SetOPL3(0xB0 + chan_offset, (freq >> 8 & 3) | oct << 2 | (GetOPL3(0xB0 + chan_offset) & 0xE0)); chan_offset = ChanOffsets3[channum]; SetOPL3(0xA0 + chan_offset, frq2 & 0xFF); SetOPL3(0xB0 + chan_offset, (frq2 >> 8 & 3) | oct << 2 | (GetOPL3(0xB0 + chan_offset) & 0xE0)); } //================================================================================================== // Transpose the note returned by UnpackNote(). // Note: due to RAD's wonky legacy middle C is octave 3 note number 12. //================================================================================================== void RADPlayer::Transpose(int8_t note, int8_t octave) { if (NoteNum >= 1 && NoteNum <= 12) { int8_t toct = octave - 3; if (toct != 0) { OctaveNum += toct; if (OctaveNum < 0) OctaveNum = 0; else if (OctaveNum > 7) OctaveNum = 7; } int8_t tnot = note - 12; if (tnot != 0) { NoteNum += tnot; if (NoteNum < 1) { NoteNum += 12; if (OctaveNum > 0) OctaveNum--; else NoteNum = 1; } } } } //================================================================================================== // Compute total time of tune if it didn't repeat. Note, this stops the tune so should only be done // prior to initial playback. //================================================================================================== #if RAD_DETECT_REPEATS static void RADPlayerDummyOPL3(void *arg, uint16_t reg, uint8_t data) {} //-------------------------------------------------------------------------------------------------- uint32_t RADPlayer::ComputeTotalTime() { Stop(); void (*old_opl3)(void *, uint16_t, uint8_t) = OPL3; OPL3 = RADPlayerDummyOPL3; while (!Update()) ; uint32_t total = PlayTime; Stop(); OPL3 = old_opl3; return total / Hertz; } #endif