1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-09-08 12:20:15 +00:00
QB64-PE/internal/c/parts/audio/extras/radv2/player20.cpp
Matthew Kilgore 481906977e Add a740g's miniaudio backend
This is a single commit adding all of a740g's audio backend. Later
commits will connect it together with QB64PE itself.
2022-08-27 14:27:55 -04:00

1266 lines
38 KiB
C++

/*
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 <stdint.h>
#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