gzdoom/src/sound/adlmidi/adlmidi_midiplay.cpp

2689 lines
87 KiB
C++
Raw Normal View History

/*
* libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation
*
* Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi>
* ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
*
* Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation:
* http://iki.fi/bisqwit/source/adlmidi.html
*
* 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
* 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 "adlmidi_private.hpp"
// Mapping from MIDI volume level to OPL level value.
static const uint32_t DMX_volume_mapping_table[] =
{
0, 1, 3, 5, 6, 8, 10, 11,
13, 14, 16, 17, 19, 20, 22, 23,
25, 26, 27, 29, 30, 32, 33, 34,
36, 37, 39, 41, 43, 45, 47, 49,
50, 52, 54, 55, 57, 59, 60, 61,
63, 64, 66, 67, 68, 69, 71, 72,
73, 74, 75, 76, 77, 79, 80, 81,
82, 83, 84, 84, 85, 86, 87, 88,
89, 90, 91, 92, 92, 93, 94, 95,
96, 96, 97, 98, 99, 99, 100, 101,
101, 102, 103, 103, 104, 105, 105, 106,
107, 107, 108, 109, 109, 110, 110, 111,
112, 112, 113, 113, 114, 114, 115, 115,
116, 117, 117, 118, 118, 119, 119, 120,
120, 121, 121, 122, 122, 123, 123, 123,
124, 124, 125, 125, 126, 126, 127, 127,
//Protection entries to avoid crash if value more than 127
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127,
};
static const uint8_t W9X_volume_mapping_table[32] =
{
63, 63, 40, 36, 32, 28, 23, 21,
19, 17, 15, 14, 13, 12, 11, 10,
9, 8, 7, 6, 5, 5, 4, 4,
3, 3, 2, 2, 1, 1, 0, 0
};
//static const char MIDIsymbols[256+1] =
//"PPPPPPhcckmvmxbd" // Ins 0-15
//"oooooahoGGGGGGGG" // Ins 16-31
//"BBBBBBBBVVVVVHHM" // Ins 32-47
//"SSSSOOOcTTTTTTTT" // Ins 48-63
//"XXXXTTTFFFFFFFFF" // Ins 64-79
//"LLLLLLLLpppppppp" // Ins 80-95
//"XXXXXXXXGGGGGTSS" // Ins 96-111
//"bbbbMMMcGXXXXXXX" // Ins 112-127
//"????????????????" // Prc 0-15
//"????????????????" // Prc 16-31
//"???DDshMhhhCCCbM" // Prc 32-47
//"CBDMMDDDMMDDDDDD" // Prc 48-63
//"DDDDDDDDDDDDDDDD" // Prc 64-79
//"DD??????????????" // Prc 80-95
//"????????????????" // Prc 96-111
//"????????????????"; // Prc 112-127
static const uint8_t PercussionMap[256] =
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"//GM
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 3 = bass drum
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 4 = snare
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 5 = tom
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 6 = cymbal
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 7 = hihat
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"//GP0
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"//GP16
//2 3 4 5 6 7 8 940 1 2 3 4 5 6 7
"\0\0\0\3\3\0\0\7\0\5\7\5\0\5\7\5"//GP32
//8 950 1 2 3 4 5 6 7 8 960 1 2 3
"\5\6\5\0\6\0\5\6\0\6\0\6\5\5\5\5"//GP48
//4 5 6 7 8 970 1 2 3 4 5 6 7 8 9
"\5\0\0\0\0\0\7\0\0\0\0\0\0\0\0\0"//GP64
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
inline bool isXgPercChannel(uint8_t msb, uint8_t lsb)
{
return (msb == 0x7E || msb == 0x7F) && (lsb == 0);
}
void MIDIplay::AdlChannel::AddAge(int64_t ms)
{
if(users.empty())
koff_time_until_neglible =
std::max(int64_t(koff_time_until_neglible - ms), static_cast<int64_t>(-0x1FFFFFFFl));
else
{
koff_time_until_neglible = 0;
for(users_t::iterator i = users.begin(); i != users.end(); ++i)
{
i->second.kon_time_until_neglible =
std::max(i->second.kon_time_until_neglible - ms, static_cast<int64_t>(-0x1FFFFFFFl));
i->second.vibdelay += ms;
}
}
}
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
MIDIplay::MidiEvent::MidiEvent() :
type(T_UNKNOWN),
subtype(T_UNKNOWN),
channel(0),
isValid(1),
absPosition(0)
{}
MIDIplay::MidiTrackRow::MidiTrackRow() :
time(0.0),
delay(0),
absPos(0),
timeDelay(0.0)
{}
void MIDIplay::MidiTrackRow::reset()
{
time = 0.0;
delay = 0;
absPos = 0;
timeDelay = 0.0;
events.clear();
}
void MIDIplay::MidiTrackRow::sortEvents(bool *noteStates)
{
typedef std::vector<MidiEvent> EvtArr;
EvtArr metas;
EvtArr noteOffs;
EvtArr controllers;
EvtArr anyOther;
metas.reserve(events.size());
noteOffs.reserve(events.size());
controllers.reserve(events.size());
anyOther.reserve(events.size());
for(size_t i = 0; i < events.size(); i++)
{
if(events[i].type == MidiEvent::T_NOTEOFF)
noteOffs.push_back(events[i]);
else if((events[i].type == MidiEvent::T_CTRLCHANGE)
|| (events[i].type == MidiEvent::T_PATCHCHANGE)
|| (events[i].type == MidiEvent::T_WHEEL)
|| (events[i].type == MidiEvent::T_CHANAFTTOUCH))
{
controllers.push_back(events[i]);
}
else if((events[i].type == MidiEvent::T_SPECIAL) && (events[i].subtype == MidiEvent::ST_MARKER))
metas.push_back(events[i]);
else
anyOther.push_back(events[i]);
}
/*
* If Note-Off and it's Note-On is on the same row - move this damned note off down!
*/
if(noteStates)
{
std::set<size_t> markAsOn;
for(size_t i = 0; i < anyOther.size(); i++)
{
const MidiEvent e = anyOther[i];
if(e.type == MidiEvent::T_NOTEON)
{
const size_t note_i = (e.channel * 255) + (e.data[0] & 0x7F);
//Check, was previously note is on or off
bool wasOn = noteStates[note_i];
markAsOn.insert(note_i);
// Detect zero-length notes are following previously pressed note
int noteOffsOnSameNote = 0;
for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end();)
{
//If note was off, and note-off on same row with note-on - move it down!
if(
((*j).channel == e.channel) &&
((*j).data[0] == e.data[0])
)
{
//If note is already off OR more than one note-off on same row and same note
if(!wasOn || (noteOffsOnSameNote != 0))
{
anyOther.push_back(*j);
j = noteOffs.erase(j);
markAsOn.erase(note_i);
continue;
} else {
//When same row has many note-offs on same row
//that means a zero-length note follows previous note
//it must be shuted down
noteOffsOnSameNote++;
}
}
j++;
}
}
}
//Mark other notes as released
for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end(); j++)
{
size_t note_i = (j->channel * 255) + (j->data[0] & 0x7F);
noteStates[note_i] = false;
}
for(std::set<size_t>::iterator j = markAsOn.begin(); j != markAsOn.end(); j++)
noteStates[*j] = true;
}
/***********************************************************************************/
events.clear();
events.insert(events.end(), noteOffs.begin(), noteOffs.end());
events.insert(events.end(), metas.begin(), metas.end());
events.insert(events.end(), controllers.begin(), controllers.end());
events.insert(events.end(), anyOther.begin(), anyOther.end());
}
#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
bool MIDIplay::buildTrackData()
{
fullSongTimeLength = 0.0;
loopStartTime = -1.0;
loopEndTime = -1.0;
musTitle.clear();
musCopyright.clear();
musTrackTitles.clear();
musMarkers.clear();
caugh_missing_instruments.clear();
caugh_missing_banks_melodic.clear();
caugh_missing_banks_percussion.clear();
trackDataNew.clear();
const size_t trackCount = TrackData.size();
trackDataNew.resize(trackCount, MidiTrackQueue());
invalidLoop = false;
bool gotLoopStart = false, gotLoopEnd = false, gotLoopEventInThisRow = false;
//! Tick position of loop start tag
uint64_t loopStartTicks = 0;
//! Tick position of loop end tag
uint64_t loopEndTicks = 0;
//! Full length of song in ticks
uint64_t ticksSongLength = 0;
//! Cache for error message strign
char error[150];
CurrentPositionNew.track.clear();
CurrentPositionNew.track.resize(trackCount);
//! Caches note on/off states.
bool noteStates[16 * 255];
/* This is required to carefully detect zero-length notes *
* and avoid a move of "note-off" event over "note-on" while sort. *
* Otherwise, after sort those notes will play infinite sound */
//Tempo change events
std::vector<MidiEvent> tempos;
/*
* TODO: Make this be safer for memory in case of broken input data
* which may cause going away of available track data (and then give a crash!)
*
* POST: Check this more carefully for possible vulnuabilities are can crash this
*/
for(size_t tk = 0; tk < trackCount; ++tk)
{
uint64_t abs_position = 0;
int status = 0;
MidiEvent event;
bool ok = false;
uint8_t *end = TrackData[tk].data() + TrackData[tk].size();
uint8_t *trackPtr = TrackData[tk].data();
std::memset(noteStates, 0, sizeof(noteStates));
//Time delay that follows the first event in the track
{
MidiTrackRow evtPos;
if(opl.m_musicMode == OPL3::MODE_RSXX)
ok = true;
else
evtPos.delay = ReadVarLenEx(&trackPtr, end, ok);
if(!ok)
{
int len = std::sprintf(error, "buildTrackData: Can't read variable-length value at begin of track %d.\n", (int)tk);
if((len > 0) && (len < 150))
errorString += std::string(error, (size_t)len);
return false;
}
//HACK: Begin every track with "Reset all controllers" event to avoid controllers state break came from end of song
for(uint8_t chan = 0; chan < 16; chan++)
{
MidiEvent event;
event.type = MidiEvent::T_CTRLCHANGE;
event.channel = chan;
event.data.push_back(121);
event.data.push_back(0);
evtPos.events.push_back(event);
}
evtPos.absPos = abs_position;
abs_position += evtPos.delay;
trackDataNew[tk].push_back(evtPos);
}
MidiTrackRow evtPos;
do
{
event = parseEvent(&trackPtr, end, status);
if(!event.isValid)
{
int len = std::sprintf(error, "buildTrackData: Fail to parse event in the track %d.\n", (int)tk);
if((len > 0) && (len < 150))
errorString += std::string(error, (size_t)len);
return false;
}
evtPos.events.push_back(event);
if(event.type == MidiEvent::T_SPECIAL)
{
if(event.subtype == MidiEvent::ST_TEMPOCHANGE)
{
event.absPosition = abs_position;
tempos.push_back(event);
}
else if(!invalidLoop && (event.subtype == MidiEvent::ST_LOOPSTART))
{
/*
* loopStart is invalid when:
* - starts together with loopEnd
* - appears more than one time in same MIDI file
*/
if(gotLoopStart || gotLoopEventInThisRow)
invalidLoop = true;
else
{
gotLoopStart = true;
loopStartTicks = abs_position;
}
//In this row we got loop event, register this!
gotLoopEventInThisRow = true;
}
else if(!invalidLoop && (event.subtype == MidiEvent::ST_LOOPEND))
{
/*
* loopEnd is invalid when:
* - starts before loopStart
* - starts together with loopStart
* - appars more than one time in same MIDI file
*/
if(gotLoopEnd || gotLoopEventInThisRow)
invalidLoop = true;
else
{
gotLoopEnd = true;
loopEndTicks = abs_position;
}
//In this row we got loop event, register this!
gotLoopEventInThisRow = true;
}
}
if(event.subtype != MidiEvent::ST_ENDTRACK)//Don't try to read delta after EndOfTrack event!
{
evtPos.delay = ReadVarLenEx(&trackPtr, end, ok);
if(!ok)
{
/* End of track has been reached! However, there is no EOT event presented */
event.type = MidiEvent::T_SPECIAL;
event.subtype = MidiEvent::ST_ENDTRACK;
}
}
if((evtPos.delay > 0) || (event.subtype == MidiEvent::ST_ENDTRACK))
{
evtPos.absPos = abs_position;
abs_position += evtPos.delay;
evtPos.sortEvents(noteStates);
trackDataNew[tk].push_back(evtPos);
evtPos.reset();
gotLoopEventInThisRow = false;
}
}
while((trackPtr <= end) && (event.subtype != MidiEvent::ST_ENDTRACK));
if(ticksSongLength < abs_position)
ticksSongLength = abs_position;
//Set the chain of events begin
if(trackDataNew[tk].size() > 0)
CurrentPositionNew.track[tk].pos = trackDataNew[tk].begin();
}
if(gotLoopStart && !gotLoopEnd)
{
gotLoopEnd = true;
loopEndTicks = ticksSongLength;
}
//loopStart must be located before loopEnd!
if(loopStartTicks >= loopEndTicks)
invalidLoop = true;
/********************************************************************************/
//Calculate time basing on collected tempo events
/********************************************************************************/
for(size_t tk = 0; tk < trackCount; ++tk)
{
fraction<uint64_t> currentTempo = Tempo;
double time = 0.0;
uint64_t abs_position = 0;
size_t tempo_change_index = 0;
MidiTrackQueue &track = trackDataNew[tk];
if(track.empty())
continue;//Empty track is useless!
#ifdef DEBUG_TIME_CALCULATION
std::fprintf(stdout, "\n============Track %" PRIuPTR "=============\n", tk);
std::fflush(stdout);
#endif
MidiTrackRow *posPrev = &(*(track.begin()));//First element
for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++)
{
#ifdef DEBUG_TIME_CALCULATION
bool tempoChanged = false;
#endif
MidiTrackRow &pos = *it;
if((posPrev != &pos) && //Skip first event
(!tempos.empty()) && //Only when in-track tempo events are available
(tempo_change_index < tempos.size())
)
{
// If tempo event is going between of current and previous event
if(tempos[tempo_change_index].absPosition <= pos.absPos)
{
//Stop points: begin point and tempo change points are before end point
std::vector<TempoChangePoint> points;
fraction<uint64_t> t;
TempoChangePoint firstPoint = {posPrev->absPos, currentTempo};
points.push_back(firstPoint);
//Collect tempo change points between previous and current events
do
{
TempoChangePoint tempoMarker;
MidiEvent &tempoPoint = tempos[tempo_change_index];
tempoMarker.absPos = tempoPoint.absPosition;
tempoMarker.tempo = InvDeltaTicks * fraction<uint64_t>(ReadBEint(tempoPoint.data.data(), tempoPoint.data.size()));
points.push_back(tempoMarker);
tempo_change_index++;
}
while((tempo_change_index < tempos.size()) &&
(tempos[tempo_change_index].absPosition <= pos.absPos));
// Re-calculate time delay of previous event
time -= posPrev->timeDelay;
posPrev->timeDelay = 0.0;
for(size_t i = 0, j = 1; j < points.size(); i++, j++)
{
/* If one or more tempo events are appears between of two events,
* calculate delays between each tempo point, begin and end */
uint64_t midDelay = 0;
//Delay between points
midDelay = points[j].absPos - points[i].absPos;
//Time delay between points
t = midDelay * currentTempo;
posPrev->timeDelay += t.value();
//Apply next tempo
currentTempo = points[j].tempo;
#ifdef DEBUG_TIME_CALCULATION
tempoChanged = true;
#endif
}
//Then calculate time between last tempo change point and end point
TempoChangePoint tailTempo = points.back();
uint64_t postDelay = pos.absPos - tailTempo.absPos;
t = postDelay * currentTempo;
posPrev->timeDelay += t.value();
//Store Common time delay
posPrev->time = time;
time += posPrev->timeDelay;
}
}
fraction<uint64_t> t = pos.delay * currentTempo;
pos.timeDelay = t.value();
pos.time = time;
time += pos.timeDelay;
//Capture markers after time value calculation
for(size_t i = 0; i < pos.events.size(); i++)
{
MidiEvent &e = pos.events[i];
if((e.type == MidiEvent::T_SPECIAL) && (e.subtype == MidiEvent::ST_MARKER))
{
MIDI_MarkerEntry marker;
marker.label = std::string((char *)e.data.data(), e.data.size());
marker.pos_ticks = pos.absPos;
marker.pos_time = pos.time;
musMarkers.push_back(marker);
}
}
//Capture loop points time positions
if(!invalidLoop)
{
// Set loop points times
if(loopStartTicks == pos.absPos)
loopStartTime = pos.time;
else if(loopEndTicks == pos.absPos)
loopEndTime = pos.time;
}
#ifdef DEBUG_TIME_CALCULATION
std::fprintf(stdout, "= %10" PRId64 " = %10f%s\n", pos.absPos, pos.time, tempoChanged ? " <----TEMPO CHANGED" : "");
std::fflush(stdout);
#endif
abs_position += pos.delay;
posPrev = &pos;
}
if(time > fullSongTimeLength)
fullSongTimeLength = time;
}
fullSongTimeLength += postSongWaitDelay;
//Set begin of the music
trackBeginPositionNew = CurrentPositionNew;
//Initial loop position will begin at begin of track until passing of the loop point
LoopBeginPositionNew = CurrentPositionNew;
/********************************************************************************/
//Resolve "hell of all times" of too short drum notes:
//move too short percussion note-offs far far away as possible
/********************************************************************************/
#if 1 //Use this to record WAVEs for comparison before/after implementing of this
if(opl.m_musicMode == OPL3::MODE_MIDI)//Percussion fix is needed for MIDI only, not for IMF/RSXX or CMF
{
//! Minimal real time in seconds
#define DRUM_NOTE_MIN_TIME 0.03
//! Minimal ticks count
#define DRUM_NOTE_MIN_TICKS 15
struct NoteState
{
double delay;
uint64_t delayTicks;
bool isOn;
char ___pad[7];
} drNotes[255];
uint16_t banks[16];
for(size_t tk = 0; tk < trackCount; ++tk)
{
std::memset(drNotes, 0, sizeof(drNotes));
std::memset(banks, 0, sizeof(banks));
MidiTrackQueue &track = trackDataNew[tk];
if(track.empty())
continue;//Empty track is useless!
for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++)
{
MidiTrackRow &pos = *it;
for(ssize_t e = 0; e < (ssize_t)pos.events.size(); e++)
{
MidiEvent *et = &pos.events[(size_t)e];
/* Set MSB/LSB bank */
if(et->type == MidiEvent::T_CTRLCHANGE)
{
uint8_t ctrlno = et->data[0];
uint8_t value = et->data[1];
switch(ctrlno)
{
case 0: // Set bank msb (GM bank)
banks[et->channel] = uint16_t(uint16_t(value) << 8) | uint16_t(banks[et->channel] & 0x00FF);
break;
case 32: // Set bank lsb (XG bank)
banks[et->channel] = (banks[et->channel] & 0xFF00) | (uint16_t(value) & 0x00FF);
break;
}
continue;
}
bool percussion = (et->channel == 9) ||
banks[et->channel] == 0x7E00 || //XG SFX1/SFX2 channel (16128 signed decimal)
banks[et->channel] == 0x7F00; //XG Percussion channel (16256 signed decimal)
if(!percussion)
continue;
if(et->type == MidiEvent::T_NOTEON)
{
uint8_t note = et->data[0] & 0x7F;
NoteState &ns = drNotes[note];
ns.isOn = true;
ns.delay = 0.0;
ns.delayTicks = 0;
}
else if(et->type == MidiEvent::T_NOTEOFF)
{
uint8_t note = et->data[0] & 0x7F;
NoteState &ns = drNotes[note];
if(ns.isOn)
{
ns.isOn = false;
if(ns.delayTicks < DRUM_NOTE_MIN_TICKS || ns.delay < DRUM_NOTE_MIN_TIME)//If note is too short
{
//Move it into next event position if that possible
for(MidiTrackQueue::iterator itNext = it;
itNext != track.end();
itNext++)
{
MidiTrackRow &posN = *itNext;
if(ns.delayTicks > DRUM_NOTE_MIN_TICKS && ns.delay > DRUM_NOTE_MIN_TIME)
{
//Put note-off into begin of next event list
posN.events.insert(posN.events.begin(), pos.events[(size_t)e]);
//Renive this event from a current row
pos.events.erase(pos.events.begin() + (int)e);
e--;
break;
}
ns.delay += posN.timeDelay;
ns.delayTicks += posN.delay;
}
}
ns.delay = 0.0;
ns.delayTicks = 0;
}
}
}
//Append time delays to sustaining notes
for(size_t no = 0; no < 128; no++)
{
NoteState &ns = drNotes[no];
if(ns.isOn)
{
ns.delay += pos.timeDelay;
ns.delayTicks += pos.delay;
}
}
}
}
#undef DRUM_NOTE_MIN_TIME
#undef DRUM_NOTE_MIN_TICKS
}
#endif
return true;
}
#endif
MIDIplay::MIDIplay(unsigned long sampleRate):
cmf_percussion_mode(false)
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
, fullSongTimeLength(0.0),
postSongWaitDelay(1.0),
loopStartTime(-1.0),
loopEndTime(-1.0),
tempoMultiplier(1.0),
atEnd(false),
loopStart(false),
loopEnd(false),
invalidLoop(false)
#endif
{
devices.clear();
m_setup.PCM_RATE = sampleRate;
m_setup.mindelay = 1.0 / (double)m_setup.PCM_RATE;
m_setup.maxdelay = 512.0 / (double)m_setup.PCM_RATE;
m_setup.AdlBank = 0;
m_setup.NumFourOps = 7;
m_setup.NumCards = 2;
m_setup.HighTremoloMode = -1;
m_setup.HighVibratoMode = -1;
m_setup.AdlPercussionMode = -1;
m_setup.LogarithmicVolumes = false;
m_setup.VolumeModel = ADLMIDI_VolumeModel_AUTO;
//m_setup.SkipForward = 0;
m_setup.loopingIsEnabled = false;
m_setup.ScaleModulators = -1;
m_setup.delay = 0.0;
m_setup.carry = 0.0;
m_setup.tick_skip_samples_delay = 0;
applySetup();
ChooseDevice("none");
realTime_ResetState();
}
void MIDIplay::applySetup()
{
m_setup.tick_skip_samples_delay = 0;
opl.HighTremoloMode = m_setup.HighTremoloMode == -1 ? adlbanksetup[m_setup.AdlBank].deepTremolo : (bool)m_setup.HighTremoloMode;
opl.HighVibratoMode = m_setup.HighVibratoMode == -1 ? adlbanksetup[m_setup.AdlBank].deepVibrato : (bool)m_setup.HighVibratoMode;
opl.AdlPercussionMode = m_setup.AdlPercussionMode == -1 ? adlbanksetup[m_setup.AdlBank].adLibPercussions : (bool)m_setup.AdlPercussionMode;
opl.ScaleModulators = m_setup.ScaleModulators == -1 ? adlbanksetup[m_setup.AdlBank].scaleModulators : (bool)m_setup.ScaleModulators;
opl.LogarithmicVolumes = m_setup.LogarithmicVolumes;
//opl.CartoonersVolumes = false;
opl.m_musicMode = OPL3::MODE_MIDI;
opl.ChangeVolumeRangesModel(static_cast<ADLMIDI_VolumeModels>(m_setup.VolumeModel));
if(m_setup.VolumeModel == ADLMIDI_VolumeModel_AUTO)//Use bank default volume model
opl.m_volumeScale = (OPL3::VolumesScale)adlbanksetup[m_setup.AdlBank].volumeModel;
opl.NumCards = m_setup.NumCards;
opl.NumFourOps = m_setup.NumFourOps;
cmf_percussion_mode = false;
opl.Reset(m_setup.PCM_RATE);
ch.clear();
ch.resize(opl.NumChannels);
}
uint64_t MIDIplay::ReadVarLen(uint8_t **ptr)
{
uint64_t result = 0;
for(;;)
{
uint8_t byte = *((*ptr)++);
result = (result << 7) + (byte & 0x7F);
if(!(byte & 0x80))
break;
}
return result;
}
uint64_t MIDIplay::ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok)
{
uint64_t result = 0;
ok = false;
for(;;)
{
if(*ptr >= end)
return 2;
unsigned char byte = *((*ptr)++);
result = (result << 7) + (byte & 0x7F);
if(!(byte & 0x80))
break;
}
ok = true;
return result;
}
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
double MIDIplay::Tick(double s, double granularity)
{
s *= tempoMultiplier;
#ifdef ENABLE_BEGIN_SILENCE_SKIPPING
if(CurrentPositionNew.began)
#endif
CurrentPositionNew.wait -= s;
CurrentPositionNew.absTimePosition += s;
int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing
while((CurrentPositionNew.wait <= granularity * 0.5) && (antiFreezeCounter > 0))
{
//std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait);
if(!ProcessEventsNew())
break;
if(CurrentPositionNew.wait <= 0.0)
antiFreezeCounter--;
}
if(antiFreezeCounter <= 0)
CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events
with zero delay are been detected */
for(uint16_t c = 0; c < opl.NumChannels; ++c)
ch[c].AddAge(static_cast<int64_t>(s * 1000.0));
UpdateVibrato(s);
UpdateArpeggio(s);
if(CurrentPositionNew.wait < 0.0)//Avoid negative delay value!
return 0.0;
return CurrentPositionNew.wait;
}
#endif
void MIDIplay::TickIteratos(double s)
{
for(uint16_t c = 0; c < opl.NumChannels; ++c)
ch[c].AddAge(static_cast<int64_t>(s * 1000.0));
UpdateVibrato(s);
UpdateArpeggio(s);
}
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
void MIDIplay::seek(double seconds)
{
if(seconds < 0.0)
return;//Seeking negative position is forbidden! :-P
const double granularity = m_setup.mindelay,
granualityHalf = granularity * 0.5,
s = seconds;//m_setup.delay < m_setup.maxdelay ? m_setup.delay : m_setup.maxdelay;
/* Attempt to go away out of song end must rewind position to begin */
if(seconds > fullSongTimeLength)
{
rewind();
return;
}
bool loopFlagState = m_setup.loopingIsEnabled;
// Turn loop pooints off because it causes wrong position rememberin on a quick seek
m_setup.loopingIsEnabled = false;
/*
* Seeking search is similar to regular ticking, except of next things:
* - We don't processsing arpeggio and vibrato
* - To keep correctness of the state after seek, begin every search from begin
* - All sustaining notes must be killed
* - Ignore Note-On events
*/
rewind();
/*
* Set "loop Start" to false to prevent overwrite of loopStart position with
* seek destinition position
*
* TODO: Detect & set loopStart position on load time to don't break loop while seeking
*/
loopStart = false;
while((CurrentPositionNew.absTimePosition < seconds) &&
(CurrentPositionNew.absTimePosition < fullSongTimeLength))
{
CurrentPositionNew.wait -= s;
CurrentPositionNew.absTimePosition += s;
int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing
double dstWait = CurrentPositionNew.wait + granualityHalf;
while((CurrentPositionNew.wait <= granualityHalf)/*&& (antiFreezeCounter > 0)*/)
{
//std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait);
if(!ProcessEventsNew(true))
break;
//Avoid freeze because of no waiting increasing in more than 10000 cycles
if(CurrentPositionNew.wait <= dstWait)
antiFreezeCounter--;
else
{
dstWait = CurrentPositionNew.wait + granualityHalf;
antiFreezeCounter = 10000;
}
}
if(antiFreezeCounter <= 0)
CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events
with zero delay are been detected */
}
if(CurrentPositionNew.wait < 0.0)
CurrentPositionNew.wait = 0.0;
m_setup.loopingIsEnabled = loopFlagState;
m_setup.delay = CurrentPositionNew.wait;
m_setup.carry = 0.0;
}
double MIDIplay::tell()
{
return CurrentPositionNew.absTimePosition;
}
double MIDIplay::timeLength()
{
return fullSongTimeLength;
}
double MIDIplay::getLoopStart()
{
return loopStartTime;
}
double MIDIplay::getLoopEnd()
{
return loopEndTime;
}
void MIDIplay::rewind()
{
Panic();
KillSustainingNotes(-1, -1);
CurrentPositionNew = trackBeginPositionNew;
atEnd = false;
loopStart = true;
loopEnd = false;
//invalidLoop = false;//No more needed here as this flag is set on load time
}
void MIDIplay::setTempo(double tempo)
{
tempoMultiplier = tempo;
}
#endif
void MIDIplay::realTime_ResetState()
{
for(size_t ch = 0; ch < Ch.size(); ch++)
{
MIDIchannel &chan = Ch[ch];
chan.volume = (opl.m_musicMode == OPL3::MODE_RSXX) ? 127 : 100;
chan.expression = 127;
chan.panning = 0x30;
chan.vibrato = 0;
chan.sustain = 0;
chan.bend = 0.0;
chan.bendsense = 2 / 8192.0;
chan.vibpos = 0.0;
chan.vibdepth = 0.5 / 127.0;
chan.vibdelay = 0;
chan.lastlrpn = 0;
chan.lastmrpn = 0;
chan.nrpn = false;
chan.brightness = 127;
NoteUpdate_All(uint16_t(ch), Upd_All);
NoteUpdate_All(uint16_t(ch), Upd_Off);
}
}
bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
{
if((opl.m_musicMode == OPL3::MODE_RSXX) && (velocity != 0))
{
// Check if this is just a note after-touch
MIDIchannel::activenoteiterator i = Ch[channel].activenotes.find(note);
if(i != Ch[channel].activenotes.end())
{
i->second.vol = velocity;
NoteUpdate(channel, i, Upd_Volume);
return false;
}
}
channel = channel % 16;
NoteOff(channel, note);
// On Note on, Keyoff the note first, just in case keyoff
// was omitted; this fixes Dance of sugar-plum fairy
// by Microsoft. Now that we've done a Keyoff,
// check if we still need to do a Keyon.
// vol=0 and event 8x are both Keyoff-only.
if(velocity == 0)
return false;
size_t midiins = Ch[channel].patch;
bool isPercussion = (channel % 16 == 9);
bool isXgPercussion = false;
uint16_t bank = 0;
if(Ch[channel].bank_msb || Ch[channel].bank_lsb)
{
bank = (uint16_t(Ch[channel].bank_msb) * 256) + uint16_t(Ch[channel].bank_lsb);
//0x7E00 - XG SFX1/SFX2 channel (16128 signed decimal)
//0x7F00 - XG Percussion channel (16256 signed decimal)
if(bank == 0x7E00 || bank == 0x7F00)
{
//Let XG SFX1/SFX2 bank will have LSB==1 (128...255 range in WOPN file)
//Let XG Percussion bank will use (0...127 range in WOPN file)
bank = (uint16_t)midiins + ((bank == 0x7E00) ? 128 : 0); // MIDI instrument defines the patch
midiins = opl.dynamic_percussion_offset + note; // Percussion instrument
isXgPercussion = true;
isPercussion = false;
}
}
if(isPercussion)
{
bank = (uint16_t)midiins; // MIDI instrument defines the patch
midiins = opl.dynamic_percussion_offset + note; // Percussion instrument
}
//Set bank bank
if(bank > 0)
{
if(isPercussion || isXgPercussion)
{
OPL3::BankMap::iterator b = opl.dynamic_percussion_banks.find(bank);
if(b != opl.dynamic_percussion_banks.end())
midiins += b->second * 128;
else
if(hooks.onDebugMessage)
{
if(!caugh_missing_banks_melodic.count(bank))
{
hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Playing missing percussion bank %i (patch %i)", channel, bank, midiins);
caugh_missing_banks_melodic.insert(bank);
}
}
}
else
{
OPL3::BankMap::iterator b = opl.dynamic_melodic_banks.find(bank);
if(b != opl.dynamic_melodic_banks.end())
midiins += b->second * 128;
else
if(hooks.onDebugMessage)
{
if(!caugh_missing_banks_percussion.count(bank))
{
hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Playing missing melodic bank %i (patch %i)", channel, bank, midiins);
caugh_missing_banks_percussion.insert(bank);
}
}
}
}
/*
if(MidCh%16 == 9 || (midiins != 32 && midiins != 46 && midiins != 48 && midiins != 50))
break; // HACK
if(midiins == 46) vol = (vol*7)/10; // HACK
if(midiins == 48 || midiins == 50) vol /= 4; // HACK
*/
//if(midiins == 56) vol = vol*6/10; // HACK
//int meta = banks[opl.AdlBank][midiins];
const size_t meta = opl.GetAdlMetaNumber(midiins);
const adlinsdata &ains = opl.GetAdlMetaIns(meta);
int16_t tone = note;
if(ains.tone)
{
/*if(ains.tone < 20)
tone += ains.tone;
else*/
if(ains.tone < 128)
tone = ains.tone;
else
tone -= ains.tone - 128;
}
//uint16_t i[2] = { ains.adlno1, ains.adlno2 };
bool pseudo_4op = ains.flags & adlinsdata::Flag_Pseudo4op;
MIDIchannel::NoteInfo::Phys voices[2] =
{
{ains.adlno1, false},
{ains.adlno2, pseudo_4op}
};
if((opl.AdlPercussionMode == 1) && PercussionMap[midiins & 0xFF])
voices[1] = voices[0];//i[1] = i[0];
if(hooks.onDebugMessage)
{
if(!caugh_missing_instruments.count(static_cast<uint8_t>(midiins)) && (ains.flags & adlinsdata::Flag_NoSound))
{
hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Playing missing instrument %i", channel, midiins);
caugh_missing_instruments.insert(static_cast<uint8_t>(midiins));
}
}
// Allocate AdLib channel (the physical sound channel for the note)
int32_t adlchannel[2] = { -1, -1 };
for(uint32_t ccount = 0; ccount < 2; ++ccount)
{
if(ccount == 1)
{
if(voices[0] == voices[1])
break; // No secondary channel
if(adlchannel[0] == -1)
break; // No secondary if primary failed
}
int32_t c = -1;
int32_t bs = -0x7FFFFFFFl;
for(size_t a = 0; a < (size_t)opl.NumChannels; ++a)
{
if(ccount == 1 && static_cast<int32_t>(a) == adlchannel[0]) continue;
// ^ Don't use the same channel for primary&secondary
if(voices[0].insId == voices[1].insId || pseudo_4op/*i[0] == i[1] || pseudo_4op*/)
{
// Only use regular channels
uint8_t expected_mode = 0;
if(opl.AdlPercussionMode == 1)
{
if(cmf_percussion_mode)
expected_mode = channel < 11 ? 0 : (3 + channel - 11); // CMF
else
expected_mode = PercussionMap[midiins & 0xFF];
}
if(opl.four_op_category[a] != expected_mode)
continue;
}
else
{
if(ccount == 0)
{
// Only use four-op master channels
if(opl.four_op_category[a] != 1)
continue;
}
else
{
// The secondary must be played on a specific channel.
if(a != static_cast<uint32_t>(adlchannel[0]) + 3)
continue;
}
}
int64_t s = CalculateAdlChannelGoodness(a, voices[ccount], channel);
if(s > bs)
{
bs = (int32_t)s; // Best candidate wins
c = static_cast<int32_t>(a);
}
}
if(c < 0)
{
if(hooks.onDebugMessage)
hooks.onDebugMessage(hooks.onDebugMessage_userData,
"ignored unplaceable note [bank %i, inst %i, note %i, MIDI channel %i]",
bank, Ch[channel].patch, note, channel);
continue; // Could not play this note. Ignore it.
}
PrepareAdlChannelForNewNote(static_cast<size_t>(c), voices[ccount]);
adlchannel[ccount] = c;
}
if(adlchannel[0] < 0 && adlchannel[1] < 0)
{
// The note could not be played, at all.
return false;
}
//if(hooks.onDebugMessage)
// hooks.onDebugMessage(hooks.onDebugMessage_userData, "i1=%d:%d, i2=%d:%d", i[0],adlchannel[0], i[1],adlchannel[1]);
// Allocate active note for MIDI channel
std::pair<MIDIchannel::activenoteiterator, bool>
ir = Ch[channel].activenotes.insert(std::make_pair(note, MIDIchannel::NoteInfo()));
ir.first->second.vol = velocity;
ir.first->second.tone = tone;
ir.first->second.midiins = midiins;
ir.first->second.insmeta = meta;
for(unsigned ccount = 0; ccount < 2; ++ccount)
{
int32_t c = adlchannel[ccount];
if(c < 0)
continue;
uint16_t chipChan = static_cast<uint16_t>(adlchannel[ccount]);
ir.first->second.phys[chipChan] = voices[ccount];
}
NoteUpdate(channel, ir.first, Upd_All | Upd_Patch);
return true;
}
void MIDIplay::realTime_NoteOff(uint8_t channel, uint8_t note)
{
channel = channel % 16;
NoteOff(channel, note);
}
void MIDIplay::realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal)
{
channel = channel % 16;
MIDIchannel::activenoteiterator
i = Ch[channel].activenotes.find(note);
if(i == Ch[channel].activenotes.end())
{
// Ignore touch if note is not active
return;
}
i->second.vol = 127 - atVal;
NoteUpdate(channel, i, Upd_Volume);
}
void MIDIplay::realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal)
{
// TODO: Verify, is this correct action?
channel = channel % 16;
for(MIDIchannel::activenoteiterator
i = Ch[channel].activenotes.begin();
i != Ch[channel].activenotes.end();
++i)
{
// Set this pressure to all active notes on the channel
i->second.vol = 127 - atVal;
}
NoteUpdate_All(channel, Upd_Volume);
}
void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value)
{
channel = channel % 16;
switch(type)
{
case 1: // Adjust vibrato
//UI.PrintLn("%u:vibrato %d", MidCh,value);
Ch[channel].vibrato = value;
break;
case 0: // Set bank msb (GM bank)
Ch[channel].bank_msb = value;
Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb);
break;
case 32: // Set bank lsb (XG bank)
Ch[channel].bank_lsb = value;
Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb);
break;
case 5: // Set portamento msb
Ch[channel].portamento = static_cast<uint16_t>((Ch[channel].portamento & 0x7F) | (value << 7));
//UpdatePortamento(MidCh);
break;
case 37: // Set portamento lsb
Ch[channel].portamento = (Ch[channel].portamento & 0x3F80) | (value);
//UpdatePortamento(MidCh);
break;
case 65: // Enable/disable portamento
// value >= 64 ? enabled : disabled
//UpdatePortamento(MidCh);
break;
case 7: // Change volume
Ch[channel].volume = value;
NoteUpdate_All(channel, Upd_Volume);
break;
case 74: // Change brightness
Ch[channel].brightness = value;
NoteUpdate_All(channel, Upd_Volume);
break;
case 64: // Enable/disable sustain
Ch[channel].sustain = value;
if(!value) KillSustainingNotes(channel);
break;
case 11: // Change expression (another volume factor)
Ch[channel].expression = value;
NoteUpdate_All(channel, Upd_Volume);
break;
case 10: // Change panning
Ch[channel].panning = 0x00;
if(value < 64 + 32) Ch[channel].panning |= 0x10;
if(value >= 64 - 32) Ch[channel].panning |= 0x20;
NoteUpdate_All(channel, Upd_Pan);
break;
case 121: // Reset all controllers
Ch[channel].bend = 0;
Ch[channel].volume = 100;
Ch[channel].expression = 127;
Ch[channel].sustain = 0;
Ch[channel].vibrato = 0;
Ch[channel].vibspeed = 2 * 3.141592653 * 5.0;
Ch[channel].vibdepth = 0.5 / 127;
Ch[channel].vibdelay = 0;
Ch[channel].panning = 0x30;
Ch[channel].portamento = 0;
Ch[channel].brightness = 127;
//UpdatePortamento(MidCh);
NoteUpdate_All(channel, Upd_Pan + Upd_Volume + Upd_Pitch);
// Kill all sustained notes
KillSustainingNotes(channel);
break;
case 123: // All notes off
NoteUpdate_All(channel, Upd_Off);
break;
case 91:
break; // Reverb effect depth. We don't do per-channel reverb.
case 92:
break; // Tremolo effect depth. We don't do...
case 93:
break; // Chorus effect depth. We don't do.
case 94:
break; // Celeste effect depth. We don't do.
case 95:
break; // Phaser effect depth. We don't do.
case 98:
Ch[channel].lastlrpn = value;
Ch[channel].nrpn = true;
break;
case 99:
Ch[channel].lastmrpn = value;
Ch[channel].nrpn = true;
break;
case 100:
Ch[channel].lastlrpn = value;
Ch[channel].nrpn = false;
break;
case 101:
Ch[channel].lastmrpn = value;
Ch[channel].nrpn = false;
break;
case 113:
break; // Related to pitch-bender, used by missimp.mid in Duke3D
case 6:
SetRPN(channel, value, true);
break;
case 38:
SetRPN(channel, value, false);
break;
case 103:
cmf_percussion_mode = (value != 0);
break; // CMF (ctrl 0x67) rhythm mode
default:
break;
//UI.PrintLn("Ctrl %d <- %d (ch %u)", ctrlno, value, MidCh);
}
}
void MIDIplay::realTime_PatchChange(uint8_t channel, uint8_t patch)
{
channel = channel % 16;
Ch[channel].patch = patch;
}
void MIDIplay::realTime_PitchBend(uint8_t channel, uint16_t pitch)
{
channel = channel % 16;
Ch[channel].bend = (uint32_t(pitch) - 8192) * Ch[channel].bendsense;
NoteUpdate_All(channel, Upd_Pitch);
}
void MIDIplay::realTime_PitchBend(uint8_t channel, uint8_t msb, uint8_t lsb)
{
channel = channel % 16;
Ch[channel].bend = (int(lsb) + int(msb) * 128 - 8192) * Ch[channel].bendsense;
NoteUpdate_All(channel, Upd_Pitch);
}
void MIDIplay::realTime_BankChangeLSB(uint8_t channel, uint8_t lsb)
{
channel = channel % 16;
Ch[channel].bank_lsb = lsb;
}
void MIDIplay::realTime_BankChangeMSB(uint8_t channel, uint8_t msb)
{
channel = channel % 16;
Ch[channel].bank_msb = msb;
}
void MIDIplay::realTime_BankChange(uint8_t channel, uint16_t bank)
{
channel = channel % 16;
Ch[channel].bank_lsb = uint8_t(bank & 0xFF);
Ch[channel].bank_msb = uint8_t((bank >> 8) & 0xFF);
}
void MIDIplay::realTime_panic()
{
Panic();
KillSustainingNotes(-1, -1);
}
void MIDIplay::NoteUpdate(uint16_t MidCh,
MIDIplay::MIDIchannel::activenoteiterator i,
unsigned props_mask,
int32_t select_adlchn)
{
MIDIchannel::NoteInfo &info = i->second;
const int16_t tone = info.tone;
const uint8_t vol = info.vol;
const int midiins = info.midiins;
const size_t insmeta = info.insmeta;
const adlinsdata &ains = opl.GetAdlMetaIns(insmeta);
AdlChannel::Location my_loc;
my_loc.MidCh = MidCh;
my_loc.note = i->first;
for(MIDIchannel::NoteInfo::PhysMap::iterator
jnext = info.phys.begin();
jnext != info.phys.end();
)
{
MIDIchannel::NoteInfo::PhysMap::iterator j(jnext++);
uint16_t c = j->first;
const MIDIchannel::NoteInfo::Phys &ins = j->second;
if(select_adlchn >= 0 && c != select_adlchn) continue;
if(props_mask & Upd_Patch)
{
opl.Patch(c, ins.insId);
AdlChannel::LocationData &d = ch[c].users[my_loc];
d.sustained = false; // inserts if necessary
d.vibdelay = 0;
d.kon_time_until_neglible = ains.ms_sound_kon;
d.ins = ins;
}
}
for(MIDIchannel::NoteInfo::PhysMap::iterator
jnext = info.phys.begin();
jnext != info.phys.end();
)
{
MIDIchannel::NoteInfo::PhysMap::iterator j(jnext++);
uint16_t c = j->first;
const MIDIchannel::NoteInfo::Phys &ins = j->second;
if(select_adlchn >= 0 && c != select_adlchn) continue;
if(props_mask & Upd_Off) // note off
{
if(Ch[MidCh].sustain == 0)
{
AdlChannel::users_t::iterator k = ch[c].users.find(my_loc);
if(k != ch[c].users.end())
ch[c].users.erase(k);
if(hooks.onNote)
hooks.onNote(hooks.onNote_userData, c, tone, midiins, 0, 0.0);
if(ch[c].users.empty())
{
opl.NoteOff(c);
ch[c].koff_time_until_neglible =
ains.ms_sound_koff;
}
}
else
{
// Sustain: Forget about the note, but don't key it off.
// Also will avoid overwriting it very soon.
AdlChannel::LocationData &d = ch[c].users[my_loc];
d.sustained = true; // note: not erased!
if(hooks.onNote)
hooks.onNote(hooks.onNote_userData, c, tone, midiins, -1, 0.0);
}
info.phys.erase(j);
continue;
}
if(props_mask & Upd_Pan)
opl.Pan(c, Ch[MidCh].panning);
if(props_mask & Upd_Volume)
{
uint32_t volume;
bool is_percussion = (MidCh == 9) || Ch[MidCh].is_xg_percussion;
uint8_t brightness = is_percussion ? 127 : Ch[MidCh].brightness;
switch(opl.m_volumeScale)
{
case OPL3::VOLUME_Generic:
case OPL3::VOLUME_CMF:
{
volume = vol * Ch[MidCh].volume * Ch[MidCh].expression;
/* If the channel has arpeggio, the effective volume of
* *this* instrument is actually lower due to timesharing.
* To compensate, add extra volume that corresponds to the
* time this note is *not* heard.
* Empirical tests however show that a full equal-proportion
* increment sounds wrong. Therefore, using the square root.
*/
//volume = (int)(volume * std::sqrt( (double) ch[c].users.size() ));
if(opl.LogarithmicVolumes)
volume = volume * 127 / (127 * 127 * 127) / 2;
else
{
// The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A)
volume = volume > 8725 ? static_cast<uint32_t>(std::log(static_cast<double>(volume)) * 11.541561 + (0.5 - 104.22845)) : 0;
// The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A)
//opl.Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0);
}
opl.Touch_Real(c, volume, brightness);
//opl.Touch(c, volume);
}
break;
case OPL3::VOLUME_DMX:
{
volume = 2 * ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129) + 1;
//volume = 2 * (Ch[MidCh].volume) + 1;
volume = (DMX_volume_mapping_table[vol] * volume) >> 9;
opl.Touch_Real(c, volume, brightness);
}
break;
case OPL3::VOLUME_APOGEE:
{
volume = ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129);
volume = ((64 * (vol + 0x80)) * volume) >> 15;
//volume = ((63 * (vol + 0x80)) * Ch[MidCh].volume) >> 15;
opl.Touch_Real(c, volume, brightness);
}
break;
case OPL3::VOLUME_9X:
{
//volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume /** Ch[MidCh].expression*/) * 127 / 16129 /*2048383*/) >> 2)];
volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 2048383) >> 2)];
//volume = W9X_volume_mapping_table[vol >> 2] + volume;
opl.Touch_Real(c, volume, brightness);
}
break;
}
/* DEBUG ONLY!!!
static uint32_t max = 0;
if(volume == 0)
max = 0;
if(volume > max)
max = volume;
printf("%d\n", max);
fflush(stdout);
*/
}
if(props_mask & Upd_Pitch)
{
AdlChannel::LocationData &d = ch[c].users[my_loc];
// Don't bend a sustained note
if(!d.sustained)
{
double bend = Ch[MidCh].bend + opl.GetAdlIns(ins.insId).finetune;
double phase = 0.0;
if((ains.flags & adlinsdata::Flag_Pseudo4op) && ins.pseudo4op)
{
phase = ains.voice2_fine_tune;//0.125; // Detune the note slightly (this is what Doom does)
}
if(Ch[MidCh].vibrato && d.vibdelay >= Ch[MidCh].vibdelay)
bend += Ch[MidCh].vibrato * Ch[MidCh].vibdepth * std::sin(Ch[MidCh].vibpos);
#ifdef ADLMIDI_USE_DOSBOX_OPL
#define BEND_COEFFICIENT 172.00093
#else
#define BEND_COEFFICIENT 172.4387
#endif
opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (tone + bend + phase)));
#undef BEND_COEFFICIENT
if(hooks.onNote)
hooks.onNote(hooks.onNote_userData, c, tone, midiins, vol, Ch[MidCh].bend);
}
}
}
if(info.phys.empty())
Ch[MidCh].activenotes.erase(i);
}
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
bool MIDIplay::ProcessEventsNew(bool isSeek)
{
if(CurrentPositionNew.track.size() == 0)
atEnd = true;//No MIDI track data to play
if(atEnd)
return false;//No more events in the queue
loopEnd = false;
const size_t TrackCount = CurrentPositionNew.track.size();
const PositionNew RowBeginPosition(CurrentPositionNew);
#ifdef DEBUG_TIME_CALCULATION
double maxTime = 0.0;
#endif
for(size_t tk = 0; tk < TrackCount; ++tk)
{
PositionNew::TrackInfo &track = CurrentPositionNew.track[tk];
if((track.status >= 0) && (track.delay <= 0))
{
//Check is an end of track has been reached
if(track.pos == trackDataNew[tk].end())
{
track.status = -1;
break;
}
// Handle event
for(size_t i = 0; i < track.pos->events.size(); i++)
{
const MidiEvent &evt = track.pos->events[i];
#ifdef ENABLE_BEGIN_SILENCE_SKIPPING
if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON))
CurrentPositionNew.began = true;
#endif
if(isSeek && (evt.type == MidiEvent::T_NOTEON))
continue;
HandleEvent(tk, evt, track.status);
if(loopEnd)
break;//Stop event handling on catching loopEnd event!
}
#ifdef DEBUG_TIME_CALCULATION
if(maxTime < track.pos->time)
maxTime = track.pos->time;
#endif
// Read next event time (unless the track just ended)
if(track.status >= 0)
{
track.delay += track.pos->delay;
track.pos++;
}
}
}
#ifdef DEBUG_TIME_CALCULATION
std::fprintf(stdout, " \r");
std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition);
std::fflush(stdout);
#endif
// Find shortest delay from all track
uint64_t shortest = 0;
bool shortest_no = true;
for(size_t tk = 0; tk < TrackCount; ++tk)
{
PositionNew::TrackInfo &track = CurrentPositionNew.track[tk];
if((track.status >= 0) && (shortest_no || track.delay < shortest))
{
shortest = track.delay;
shortest_no = false;
}
}
//if(shortest > 0) UI.PrintLn("shortest: %ld", shortest);
// Schedule the next playevent to be processed after that delay
for(size_t tk = 0; tk < TrackCount; ++tk)
CurrentPositionNew.track[tk].delay -= shortest;
fraction<uint64_t> t = shortest * Tempo;
#ifdef ENABLE_BEGIN_SILENCE_SKIPPING
if(CurrentPositionNew.began)
#endif
CurrentPositionNew.wait += t.value();
//if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel());
if(loopStart)
{
LoopBeginPositionNew = RowBeginPosition;
loopStart = false;
}
if(shortest_no || loopEnd)
{
//Loop if song end or loop end point has reached
loopEnd = false;
shortest = 0;
if(!m_setup.loopingIsEnabled)
{
atEnd = true; //Don't handle events anymore
CurrentPositionNew.wait += postSongWaitDelay;//One second delay until stop playing
return true;//We have caugh end here!
}
CurrentPositionNew = LoopBeginPositionNew;
}
return true;//Has events in queue
}
#endif
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
MIDIplay::MidiEvent MIDIplay::parseEvent(uint8_t **pptr, uint8_t *end, int &status)
{
uint8_t *&ptr = *pptr;
MIDIplay::MidiEvent evt;
if(ptr + 1 > end)
{
//When track doesn't ends on the middle of event data, it's must be fine
evt.type = MidiEvent::T_SPECIAL;
evt.subtype = MidiEvent::ST_ENDTRACK;
return evt;
}
unsigned char byte = *(ptr++);
bool ok = false;
if(byte == MidiEvent::T_SYSEX || byte == MidiEvent::T_SYSEX2)// Ignore SysEx
{
uint64_t length = ReadVarLenEx(pptr, end, ok);
if(!ok || (ptr + length > end))
{
errorString += "parseEvent: Can't read SysEx event - Unexpected end of track data.\n";
evt.isValid = 0;
return evt;
}
ptr += (size_t)length;
return evt;
}
if(byte == MidiEvent::T_SPECIAL)
{
// Special event FF
uint8_t evtype = *(ptr++);
uint64_t length = ReadVarLenEx(pptr, end, ok);
if(!ok || (ptr + length > end))
{
errorString += "parseEvent: Can't read Special event - Unexpected end of track data.\n";
evt.isValid = 0;
return evt;
}
std::string data(length ? (const char *)ptr : 0, (size_t)length);
ptr += (size_t)length;
evt.type = byte;
evt.subtype = evtype;
evt.data.insert(evt.data.begin(), data.begin(), data.end());
/* TODO: Store those meta-strings separately and give ability to read them
* by external functions (to display song title and copyright in the player) */
if(evt.subtype == MidiEvent::ST_COPYRIGHT)
{
if(musCopyright.empty())
{
musCopyright = std::string((const char *)evt.data.data(), evt.data.size());
if(hooks.onDebugMessage)
hooks.onDebugMessage(hooks.onDebugMessage_userData, "Music copyright: %s", musCopyright.c_str());
}
else if(hooks.onDebugMessage)
{
std::string str((const char *)evt.data.data(), evt.data.size());
hooks.onDebugMessage(hooks.onDebugMessage_userData, "Extra copyright event: %s", str.c_str());
}
}
else if(evt.subtype == MidiEvent::ST_SQTRKTITLE)
{
if(musTitle.empty())
{
musTitle = std::string((const char *)evt.data.data(), evt.data.size());
if(hooks.onDebugMessage)
hooks.onDebugMessage(hooks.onDebugMessage_userData, "Music title: %s", musTitle.c_str());
}
else if(hooks.onDebugMessage)
{
//TODO: Store track titles and associate them with each track and make API to retreive them
std::string str((const char *)evt.data.data(), evt.data.size());
musTrackTitles.push_back(str);
hooks.onDebugMessage(hooks.onDebugMessage_userData, "Track title: %s", str.c_str());
}
}
else if(evt.subtype == MidiEvent::ST_INSTRTITLE)
{
if(hooks.onDebugMessage)
{
std::string str((const char *)evt.data.data(), evt.data.size());
hooks.onDebugMessage(hooks.onDebugMessage_userData, "Instrument: %s", str.c_str());
}
}
else if(evt.subtype == MidiEvent::ST_MARKER)
{
//To lower
for(size_t i = 0; i < data.size(); i++)
{
if(data[i] <= 'Z' && data[i] >= 'A')
data[i] = data[i] - ('Z' - 'z');
}
if(data == "loopstart")
{
//Return a custom Loop Start event instead of Marker
evt.subtype = MidiEvent::ST_LOOPSTART;
evt.data.clear();//Data is not needed
return evt;
}
if(data == "loopend")
{
//Return a custom Loop End event instead of Marker
evt.subtype = MidiEvent::ST_LOOPEND;
evt.data.clear();//Data is not needed
return evt;
}
}
if(evtype == MidiEvent::ST_ENDTRACK)
status = -1;//Finalize track
return evt;
}
// Any normal event (80..EF)
if(byte < 0x80)
{
byte = static_cast<uint8_t>(status | 0x80);
ptr--;
}
//Sys Com Song Select(Song #) [0-127]
if(byte == MidiEvent::T_SYSCOMSNGSEL)
{
if(ptr + 1 > end)
{
errorString += "parseEvent: Can't read System Command Song Select event - Unexpected end of track data.\n";
evt.isValid = 0;
return evt;
}
evt.type = byte;
evt.data.push_back(*(ptr++));
return evt;
}
//Sys Com Song Position Pntr [LSB, MSB]
if(byte == MidiEvent::T_SYSCOMSPOSPTR)
{
if(ptr + 2 > end)
{
errorString += "parseEvent: Can't read System Command Position Pointer event - Unexpected end of track data.\n";
evt.isValid = 0;
return evt;
}
evt.type = byte;
evt.data.push_back(*(ptr++));
evt.data.push_back(*(ptr++));
return evt;
}
uint8_t midCh = byte & 0x0F, evType = (byte >> 4) & 0x0F;
status = byte;
evt.channel = midCh;
evt.type = evType;
switch(evType)
{
case MidiEvent::T_NOTEOFF://2 byte length
case MidiEvent::T_NOTEON:
case MidiEvent::T_NOTETOUCH:
case MidiEvent::T_CTRLCHANGE:
case MidiEvent::T_WHEEL:
if(ptr + 2 > end)
{
errorString += "parseEvent: Can't read regular 2-byte event - Unexpected end of track data.\n";
evt.isValid = 0;
return evt;
}
evt.data.push_back(*(ptr++));
evt.data.push_back(*(ptr++));
if((evType == MidiEvent::T_NOTEON) && (evt.data[1] == 0))
evt.type = MidiEvent::T_NOTEOFF; // Note ON with zero velocity is Note OFF!
//111'th loopStart controller (RPG Maker and others)
else if((evType == MidiEvent::T_CTRLCHANGE) && (evt.data[0] == 111))
{
//Change event type to custom Loop Start event and clear data
evt.type = MidiEvent::T_SPECIAL;
evt.subtype = MidiEvent::ST_LOOPSTART;
evt.data.clear();
}
return evt;
case MidiEvent::T_PATCHCHANGE://1 byte length
case MidiEvent::T_CHANAFTTOUCH:
if(ptr + 1 > end)
{
errorString += "parseEvent: Can't read regular 1-byte event - Unexpected end of track data.\n";
evt.isValid = 0;
return evt;
}
evt.data.push_back(*(ptr++));
return evt;
}
return evt;
}
#endif
const std::string &MIDIplay::getErrorString()
{
return errorStringOut;
}
void MIDIplay::setErrorString(const std::string &err)
{
errorStringOut = err;
}
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
void MIDIplay::HandleEvent(size_t tk, const MIDIplay::MidiEvent &evt, int &status)
{
if(hooks.onEvent)
{
hooks.onEvent(hooks.onEvent_userData,
evt.type,
evt.subtype,
evt.channel,
evt.data.data(),
evt.data.size());
}
if(evt.type == MidiEvent::T_SYSEX || evt.type == MidiEvent::T_SYSEX2) // Ignore SysEx
{
//std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length );
//UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/);
return;
}
if(evt.type == MidiEvent::T_SPECIAL)
{
// Special event FF
uint8_t evtype = evt.subtype;
uint64_t length = (uint64_t)evt.data.size();
std::string data(length ? (const char *)evt.data.data() : 0, (size_t)length);
if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track
{
status = -1;
return;
}
if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change
{
Tempo = InvDeltaTicks * fraction<uint64_t>(ReadBEint(evt.data.data(), evt.data.size()));
return;
}
if(evtype == MidiEvent::ST_MARKER)//Meta event
{
//Do nothing! :-P
return;
}
if(evtype == MidiEvent::ST_DEVICESWITCH)
{
current_device[tk] = ChooseDevice(data);
return;
}
//if(evtype >= 1 && evtype <= 6)
// UI.PrintLn("Meta %d: %s", evtype, data.c_str());
//Turn on Loop handling when loop is enabled
if(m_setup.loopingIsEnabled && !invalidLoop)
{
if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib
{
loopStart = true;
return;
}
if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib
{
loopEnd = true;
return;
}
}
if(evtype == MidiEvent::ST_RAWOPL) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib
{
uint8_t i = static_cast<uint8_t>(data[0]), v = static_cast<uint8_t>(data[1]);
if((i & 0xF0) == 0xC0)
v |= 0x30;
//std::printf("OPL poke %02X, %02X\n", i, v);
//std::fflush(stdout);
opl.PokeN(0, i, v);
return;
}
return;
}
// Any normal event (80..EF)
// if(evt.type < 0x80)
// {
// byte = static_cast<uint8_t>(CurrentPosition.track[tk].status | 0x80);
// CurrentPosition.track[tk].ptr--;
// }
if(evt.type == MidiEvent::T_SYSCOMSNGSEL ||
evt.type == MidiEvent::T_SYSCOMSPOSPTR)
return;
/*UI.PrintLn("@%X Track %u: %02X %02X",
CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte,
TrackData[tk][CurrentPosition.track[tk].ptr]);*/
uint8_t midCh = evt.channel;//byte & 0x0F, EvType = byte >> 4;
midCh += (uint8_t)current_device[tk];
status = evt.type;
switch(evt.type)
{
case MidiEvent::T_NOTEOFF: // Note off
{
uint8_t note = evt.data[0];
realTime_NoteOff(midCh, note);
break;
}
case MidiEvent::T_NOTEON: // Note on
{
uint8_t note = evt.data[0];
uint8_t vol = evt.data[1];
/*if(*/ realTime_NoteOn(midCh, note, vol); /*)*/
//CurrentPosition.began = true;
break;
}
case MidiEvent::T_NOTETOUCH: // Note touch
{
uint8_t note = evt.data[0];
uint8_t vol = evt.data[1];
realTime_NoteAfterTouch(midCh, note, vol);
break;
}
case MidiEvent::T_CTRLCHANGE: // Controller change
{
uint8_t ctrlno = evt.data[0];
uint8_t value = evt.data[1];
realTime_Controller(midCh, ctrlno, value);
break;
}
case MidiEvent::T_PATCHCHANGE: // Patch change
realTime_PatchChange(midCh, evt.data[0]);
break;
case MidiEvent::T_CHANAFTTOUCH: // Channel after-touch
{
// TODO: Verify, is this correct action?
uint8_t vol = evt.data[0];
realTime_ChannelAfterTouch(midCh, vol);
break;
}
case MidiEvent::T_WHEEL: // Wheel/pitch bend
{
uint8_t a = evt.data[0];
uint8_t b = evt.data[1];
realTime_PitchBend(midCh, b, a);
break;
}
}
}
#endif
long MIDIplay::CalculateAdlChannelGoodness(unsigned c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t) const
{
long s = -ch[c].koff_time_until_neglible;
// Same midi-instrument = some stability
//if(c == MidCh) s += 4;
for(AdlChannel::users_t::const_iterator
j = ch[c].users.begin();
j != ch[c].users.end();
++j)
{
s -= 4000;
if(!j->second.sustained)
s -= (long)j->second.kon_time_until_neglible;
else
s -= (long)(j->second.kon_time_until_neglible / 2);
MIDIchannel::activenotemap_t::const_iterator
k = Ch[j->first.MidCh].activenotes.find(j->first.note);
if(k != Ch[j->first.MidCh].activenotes.end())
{
// Same instrument = good
if(j->second.ins == ins)
{
s += 300;
// Arpeggio candidate = even better
if(j->second.vibdelay < 70
|| j->second.kon_time_until_neglible > 20000)
s += 0;
}
// Percussion is inferior to melody
s += 50 * (k->second.midiins / 128);
/*
if(k->second.midiins >= 25
&& k->second.midiins < 40
&& j->second.ins != ins)
{
s -= 14000; // HACK: Don't clobber the bass or the guitar
}
*/
}
// If there is another channel to which this note
// can be evacuated to in the case of congestion,
// increase the score slightly.
unsigned n_evacuation_stations = 0;
for(unsigned c2 = 0; c2 < opl.NumChannels; ++c2)
{
if(c2 == c) continue;
if(opl.four_op_category[c2]
!= opl.four_op_category[c]) continue;
for(AdlChannel::users_t::const_iterator
m = ch[c2].users.begin();
m != ch[c2].users.end();
++m)
{
if(m->second.sustained) continue;
if(m->second.vibdelay >= 200) continue;
if(m->second.ins != j->second.ins) continue;
n_evacuation_stations += 1;
}
}
s += n_evacuation_stations * 4;
}
return s;
}
void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo::Phys &ins)
{
if(ch[c].users.empty()) return; // Nothing to do
//bool doing_arpeggio = false;
for(AdlChannel::users_t::iterator
jnext = ch[c].users.begin();
jnext != ch[c].users.end();
)
{
AdlChannel::users_t::iterator j(jnext++);
if(!j->second.sustained)
{
// Collision: Kill old note,
// UNLESS we're going to do arpeggio
MIDIchannel::activenoteiterator i
(Ch[j->first.MidCh].activenotes.find(j->first.note));
// Check if we can do arpeggio.
if((j->second.vibdelay < 70
|| j->second.kon_time_until_neglible > 20000)
&& j->second.ins == ins)
{
// Do arpeggio together with this note.
//doing_arpeggio = true;
continue;
}
KillOrEvacuate(c, j, i);
// ^ will also erase j from ch[c].users.
}
}
// Kill all sustained notes on this channel
// Don't keep them for arpeggio, because arpeggio requires
// an intact "activenotes" record. This is a design flaw.
KillSustainingNotes(-1, static_cast<int32_t>(c));
// Keyoff the channel so that it can be retriggered,
// unless the new note will be introduced as just an arpeggio.
if(ch[c].users.empty())
opl.NoteOff(c);
}
void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator j, MIDIplay::MIDIchannel::activenoteiterator i)
{
// Before killing the note, check if it can be
// evacuated to another channel as an arpeggio
// instrument. This helps if e.g. all channels
// are full of strings and we want to do percussion.
// FIXME: This does not care about four-op entanglements.
for(uint32_t c = 0; c < opl.NumChannels; ++c)
{
uint16_t cs = static_cast<uint16_t>(c);
if(c > std::numeric_limits<uint32_t>::max())
break;
if(c == from_channel)
continue;
if(opl.four_op_category[c] != opl.four_op_category[from_channel])
continue;
for(AdlChannel::users_t::iterator
m = ch[c].users.begin();
m != ch[c].users.end();
++m)
{
if(m->second.vibdelay >= 200
&& m->second.kon_time_until_neglible < 10000) continue;
if(m->second.ins != j->second.ins)
continue;
if(hooks.onNote)
{
hooks.onNote(hooks.onNote_userData,
(int)from_channel,
i->second.tone,
i->second.midiins, 0, 0.0);
hooks.onNote(hooks.onNote_userData,
(int)c,
i->second.tone,
i->second.midiins,
i->second.vol, 0.0);
}
i->second.phys.erase(static_cast<uint16_t>(from_channel));
i->second.phys[cs] = j->second.ins;
ch[cs].users.insert(*j);
ch[from_channel].users.erase(j);
return;
}
}
/*UI.PrintLn(
"collision @%u: [%ld] <- ins[%3u]",
c,
//ch[c].midiins<128?'M':'P', ch[c].midiins&127,
ch[c].age, //adlins[ch[c].insmeta].ms_sound_kon,
ins
);*/
// Kill it
NoteUpdate(j->first.MidCh,
i,
Upd_Off,
static_cast<int32_t>(from_channel));
}
void MIDIplay::Panic()
{
for(uint8_t chan = 0; chan < Ch.size(); chan++)
{
for(uint8_t note = 0; note < 128; note++)
realTime_NoteOff(chan, note);
}
}
void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
{
uint32_t first = 0, last = opl.NumChannels;
if(this_adlchn >= 0)
{
first = static_cast<uint32_t>(this_adlchn);
last = first + 1;
}
for(unsigned c = first; c < last; ++c)
{
if(ch[c].users.empty()) continue; // Nothing to do
for(AdlChannel::users_t::iterator
jnext = ch[c].users.begin();
jnext != ch[c].users.end();
)
{
AdlChannel::users_t::iterator j(jnext++);
if((MidCh < 0 || j->first.MidCh == MidCh)
&& j->second.sustained)
{
int midiins = '?';
if(hooks.onNote)
hooks.onNote(hooks.onNote_userData, (int)c, j->first.note, midiins, 0, 0.0);
ch[c].users.erase(j);
}
}
// Keyoff the channel, if there are no users left.
if(ch[c].users.empty())
opl.NoteOff(c);
}
}
void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB)
{
bool nrpn = Ch[MidCh].nrpn;
unsigned addr = Ch[MidCh].lastmrpn * 0x100 + Ch[MidCh].lastlrpn;
switch(addr + nrpn * 0x10000 + MSB * 0x20000)
{
case 0x0000 + 0*0x10000 + 1*0x20000: // Pitch-bender sensitivity
Ch[MidCh].bendsense = value / 8192.0;
break;
case 0x0108 + 1*0x10000 + 1*0x20000: // Vibrato speed
if(value == 64) Ch[MidCh].vibspeed = 1.0;
else if(value < 100) Ch[MidCh].vibspeed = 1.0 / (1.6e-2 * (value ? value : 1));
else Ch[MidCh].vibspeed = 1.0 / (0.051153846 * value - 3.4965385);
Ch[MidCh].vibspeed *= 2 * 3.141592653 * 5.0;
break;
case 0x0109 + 1*0x10000 + 1*0x20000: // Vibrato depth
Ch[MidCh].vibdepth = ((value - 64) * 0.15) * 0.01;
break;
case 0x010A + 1*0x10000 + 1*0x20000: // Vibrato delay in millisecons
Ch[MidCh].vibdelay = value ? int64_t(0.2092 * std::exp(0.0795 * (double)value)) : 0;
break;
default:/* UI.PrintLn("%s %04X <- %d (%cSB) (ch %u)",
"NRPN"+!nrpn, addr, value, "LM"[MSB], MidCh);*/
break;
}
}
//void MIDIplay::UpdatePortamento(unsigned MidCh)
//{
// // mt = 2^(portamento/2048) * (1.0 / 5000.0)
// /*
// double mt = std::exp(0.00033845077 * Ch[MidCh].portamento);
// NoteUpdate_All(MidCh, Upd_Pitch);
// */
// //UI.PrintLn("Portamento %u: %u (unimplemented)", MidCh, Ch[MidCh].portamento);
//}
void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask)
{
for(MIDIchannel::activenoteiterator
i = Ch[MidCh].activenotes.begin();
i != Ch[MidCh].activenotes.end();
)
{
MIDIchannel::activenoteiterator j(i++);
NoteUpdate(MidCh, j, props_mask);
}
}
void MIDIplay::NoteOff(uint16_t MidCh, uint8_t note)
{
MIDIchannel::activenoteiterator
i = Ch[MidCh].activenotes.find(note);
if(i != Ch[MidCh].activenotes.end())
NoteUpdate(MidCh, i, Upd_Off);
}
void MIDIplay::UpdateVibrato(double amount)
{
for(size_t a = 0, b = Ch.size(); a < b; ++a)
{
if(Ch[a].vibrato && !Ch[a].activenotes.empty())
{
NoteUpdate_All(static_cast<uint16_t>(a), Upd_Pitch);
Ch[a].vibpos += amount * Ch[a].vibspeed;
}
else
Ch[a].vibpos = 0.0;
}
}
uint64_t MIDIplay::ChooseDevice(const std::string &name)
{
std::map<std::string, uint64_t>::iterator i = devices.find(name);
if(i != devices.end())
return i->second;
size_t n = devices.size() * 16;
devices.insert(std::make_pair(name, n));
Ch.resize(n + 16);
return n;
}
void MIDIplay::UpdateArpeggio(double) // amount = amount of time passed
{
// If there is an adlib channel that has multiple notes
// simulated on the same channel, arpeggio them.
#if 0
const unsigned desired_arpeggio_rate = 40; // Hz (upper limit)
#if 1
static unsigned cache = 0;
amount = amount; // Ignore amount. Assume we get a constant rate.
cache += MaxSamplesAtTime * desired_arpeggio_rate;
if(cache < PCM_RATE) return;
cache %= PCM_RATE;
#else
static double arpeggio_cache = 0;
arpeggio_cache += amount * desired_arpeggio_rate;
if(arpeggio_cache < 1.0) return;
arpeggio_cache = 0.0;
#endif
#endif
static unsigned arpeggio_counter = 0;
++arpeggio_counter;
for(uint32_t c = 0; c < opl.NumChannels; ++c)
{
retry_arpeggio:
if(c > uint32_t(std::numeric_limits<int32_t>::max()))
break;
size_t n_users = ch[c].users.size();
if(n_users > 1)
{
AdlChannel::users_t::const_iterator i = ch[c].users.begin();
size_t rate_reduction = 3;
if(n_users >= 3)
rate_reduction = 2;
if(n_users >= 4)
rate_reduction = 1;
std::advance(i, (arpeggio_counter / rate_reduction) % n_users);
if(i->second.sustained == false)
{
if(i->second.kon_time_until_neglible <= 0l)
{
NoteUpdate(
i->first.MidCh,
Ch[ i->first.MidCh ].activenotes.find(i->first.note),
Upd_Off,
static_cast<int32_t>(c));
goto retry_arpeggio;
}
NoteUpdate(
i->first.MidCh,
Ch[ i->first.MidCh ].activenotes.find(i->first.note),
Upd_Pitch | Upd_Volume | Upd_Pan,
static_cast<int32_t>(c));
}
}
}
}
#ifndef ADLMIDI_DISABLE_CPP_EXTRAS
ADLMIDI_EXPORT AdlInstrumentTester::AdlInstrumentTester(ADL_MIDIPlayer *device)
{
cur_gm = 0;
ins_idx = 0;
play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
if(!play)
return;
opl = &play->opl;
}
ADLMIDI_EXPORT AdlInstrumentTester::~AdlInstrumentTester()
{}
ADLMIDI_EXPORT void AdlInstrumentTester::FindAdlList()
{
const unsigned NumBanks = (unsigned)adl_getBanksCount();
std::set<unsigned> adl_ins_set;
for(unsigned bankno = 0; bankno < NumBanks; ++bankno)
adl_ins_set.insert(banks[bankno][cur_gm]);
adl_ins_list.assign(adl_ins_set.begin(), adl_ins_set.end());
ins_idx = 0;
NextAdl(0);
opl->Silence();
}
ADLMIDI_EXPORT void AdlInstrumentTester::Touch(unsigned c, unsigned volume) // Volume maxes at 127*127*127
{
if(opl->LogarithmicVolumes)
opl->Touch_Real(c, volume * 127 / (127 * 127 * 127) / 2);
else
{
// The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A)
opl->Touch_Real(c, volume > 8725 ? static_cast<unsigned int>(std::log((double)volume) * 11.541561 + (0.5 - 104.22845)) : 0);
// The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A)
//Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0);
}
}
ADLMIDI_EXPORT void AdlInstrumentTester::DoNote(int note)
{
if(adl_ins_list.empty()) FindAdlList();
const unsigned meta = adl_ins_list[ins_idx];
const adlinsdata &ains = opl->GetAdlMetaIns(meta);
int tone = (cur_gm & 128) ? (cur_gm & 127) : (note + 50);
if(ains.tone)
{
/*if(ains.tone < 20)
tone += ains.tone;
else */
if(ains.tone < 128)
tone = ains.tone;
else
tone -= ains.tone - 128;
}
double hertz = 172.00093 * std::exp(0.057762265 * (tone + 0.0));
int i[2] = { ains.adlno1, ains.adlno2 };
int32_t adlchannel[2] = { 0, 3 };
if(i[0] == i[1])
{
adlchannel[1] = -1;
adlchannel[0] = 6; // single-op
if(play->hooks.onDebugMessage)
{
play->hooks.onDebugMessage(play->hooks.onDebugMessage_userData,
"noteon at %d(%d) for %g Hz\n", adlchannel[0], i[0], hertz);
}
}
else
{
if(play->hooks.onDebugMessage)
{
play->hooks.onDebugMessage(play->hooks.onDebugMessage_userData,
"noteon at %d(%d) and %d(%d) for %g Hz\n", adlchannel[0], i[0], adlchannel[1], i[1], hertz);
}
}
opl->NoteOff(0);
opl->NoteOff(3);
opl->NoteOff(6);
for(unsigned c = 0; c < 2; ++c)
{
if(adlchannel[c] < 0) continue;
opl->Patch((uint16_t)adlchannel[c], (uint16_t)i[c]);
opl->Touch_Real((uint16_t)adlchannel[c], 127 * 127 * 100);
opl->Pan((uint16_t)adlchannel[c], 0x30);
opl->NoteOn((uint16_t)adlchannel[c], hertz);
}
}
ADLMIDI_EXPORT void AdlInstrumentTester::NextGM(int offset)
{
cur_gm = (cur_gm + 256 + (uint32_t)offset) & 0xFF;
FindAdlList();
}
ADLMIDI_EXPORT void AdlInstrumentTester::NextAdl(int offset)
{
if(adl_ins_list.empty()) FindAdlList();
const unsigned NumBanks = (unsigned)adl_getBanksCount();
ins_idx = (uint32_t)((int32_t)ins_idx + (int32_t)adl_ins_list.size() + offset) % adl_ins_list.size();
#if 0
UI.Color(15);
std::fflush(stderr);
std::printf("SELECTED G%c%d\t%s\n",
cur_gm < 128 ? 'M' : 'P', cur_gm < 128 ? cur_gm + 1 : cur_gm - 128,
"<-> select GM, ^v select ins, qwe play note");
std::fflush(stdout);
UI.Color(7);
std::fflush(stderr);
#endif
for(unsigned a = 0; a < adl_ins_list.size(); ++a)
{
const unsigned i = adl_ins_list[a];
const adlinsdata &ains = opl->GetAdlMetaIns(i);
char ToneIndication[8] = " ";
if(ains.tone)
{
/*if(ains.tone < 20)
std::sprintf(ToneIndication, "+%-2d", ains.tone);
else*/
if(ains.tone < 128)
std::sprintf(ToneIndication, "=%-2d", ains.tone);
else
std::sprintf(ToneIndication, "-%-2d", ains.tone - 128);
}
std::printf("%s%s%s%u\t",
ToneIndication,
ains.adlno1 != ains.adlno2 ? "[2]" : " ",
(ins_idx == a) ? "->" : "\t",
i
);
for(unsigned bankno = 0; bankno < NumBanks; ++bankno)
if(banks[bankno][cur_gm] == i)
std::printf(" %u", bankno);
std::printf("\n");
}
}
ADLMIDI_EXPORT bool AdlInstrumentTester::HandleInputChar(char ch)
{
static const char notes[] = "zsxdcvgbhnjmq2w3er5t6y7ui9o0p";
// c'd'ef'g'a'bC'D'EF'G'A'Bc'd'e
switch(ch)
{
case '/':
case 'H':
case 'A':
NextAdl(-1);
break;
case '*':
case 'P':
case 'B':
NextAdl(+1);
break;
case '-':
case 'K':
case 'D':
NextGM(-1);
break;
case '+':
case 'M':
case 'C':
NextGM(+1);
break;
case 3:
#if !((!defined(__WIN32__) || defined(__CYGWIN__)) && !defined(__DJGPP__))
case 27:
#endif
return false;
default:
const char *p = std::strchr(notes, ch);
if(p && *p)
DoNote((int)(p - notes) - 12);
}
return true;
}
#endif//ADLMIDI_DISABLE_CPP_EXTRAS