aboutsummaryrefslogtreecommitdiff
path: root/src/adlmidi_midiplay.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/adlmidi_midiplay.cpp')
-rw-r--r--src/adlmidi_midiplay.cpp1854
1 files changed, 484 insertions, 1370 deletions
diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp
index 89c1a73..190a521 100644
--- a/src/adlmidi_midiplay.cpp
+++ b/src/adlmidi_midiplay.cpp
@@ -93,6 +93,8 @@ static const uint8_t PercussionMap[256] =
"\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";
+enum { MasterVolumeDefault = 127 };
+
inline bool isXgPercChannel(uint8_t msb, uint8_t lsb)
{
return (msb == 0x7E || msb == 0x7F) && (lsb == 0);
@@ -115,583 +117,14 @@ void MIDIplay::AdlChannel::AddAge(int64_t 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 = snprintf(error, 150, "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 = snprintf(error, 150, "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),
- m_arpeggioCounter(0),
- m_audioTickCounter(0)
-#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)
+ m_masterVolume(MasterVolumeDefault),
+ m_sysExDeviceId(0),
+ m_synthMode(Mode_XG),
+ m_arpeggioCounter(0)
+#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
+ , m_audioTickCounter(0)
#endif
{
devices.clear();
@@ -712,13 +145,15 @@ MIDIplay::MIDIplay(unsigned long sampleRate):
m_setup.LogarithmicVolumes = false;
m_setup.VolumeModel = ADLMIDI_VolumeModel_AUTO;
//m_setup.SkipForward = 0;
- m_setup.loopingIsEnabled = false;
m_setup.ScaleModulators = -1;
m_setup.fullRangeBrightnessCC74 = false;
m_setup.delay = 0.0;
m_setup.carry = 0.0;
m_setup.tick_skip_samples_delay = 0;
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
+ initSequencerInterface();
+#endif
applySetup();
ChooseDevice("none");
realTime_ResetState();
@@ -726,6 +161,8 @@ MIDIplay::MIDIplay(unsigned long sampleRate):
void MIDIplay::applySetup()
{
+ opl.m_musicMode = OPL3::MODE_MIDI;
+
m_setup.tick_skip_samples_delay = 0;
opl.runAtPcmRate = m_setup.runAtPcmRate;
@@ -745,10 +182,12 @@ void MIDIplay::applySetup()
opl.ScaleModulators = m_setup.ScaleModulators < 0 ?
opl.dynamic_bank_setup.scaleModulators :
(m_setup.ScaleModulators != 0);
+
if(m_setup.LogarithmicVolumes)
opl.ChangeVolumeRangesModel(ADLMIDI_VolumeModel_NativeOPL3);
- opl.m_musicMode = OPL3::MODE_MIDI;
- opl.ChangeVolumeRangesModel(static_cast<ADLMIDI_VolumeModels>(m_setup.VolumeModel));
+ else
+ 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)opl.dynamic_bank_setup.volumeModel;
@@ -764,191 +203,16 @@ void MIDIplay::applySetup()
m_arpeggioCounter = 0;
}
-#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
-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;
-}
-
-double MIDIplay::Tick(double s, double granularity)
+void MIDIplay::TickIterators(double s)
{
- 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 /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
-
-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;
+#if !defined(ADLMIDI_AUDIO_TICK_HANDLER)
+ UpdateGlide(s);
+#endif
}
-#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
void MIDIplay::realTime_ResetState()
{
@@ -961,9 +225,12 @@ void MIDIplay::realTime_ResetState()
chan.lastlrpn = 0;
chan.lastmrpn = 0;
chan.nrpn = false;
+ if((m_synthMode & Mode_GS) != 0)// Reset custom drum channels on GS
+ chan.is_xg_percussion = false;
NoteUpdate_All(uint16_t(ch), Upd_All);
NoteUpdate_All(uint16_t(ch), Upd_Off);
}
+ m_masterVolume = MasterVolumeDefault;
}
bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
@@ -996,32 +263,40 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
MIDIchannel &midiChan = Ch[channel];
size_t midiins = midiChan.patch;
- bool isPercussion = (channel % 16 == 9);
- bool isXgPercussion = false;
+ bool isPercussion = (channel % 16 == 9) || midiChan.is_xg_percussion;
uint16_t bank = 0;
if(midiChan.bank_msb || midiChan.bank_lsb)
{
- bank = (uint16_t(midiChan.bank_msb) * 256) + uint16_t(midiChan.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 = note; // Percussion instrument
- isXgPercussion = true;
- isPercussion = false;
- }
+ if((m_synthMode & Mode_GS) != 0) //in GS mode ignore LSB
+ bank = (uint16_t(midiChan.bank_msb) * 256);
+ else
+ bank = (uint16_t(midiChan.bank_msb) * 256) + uint16_t(midiChan.bank_lsb);
}
if(isPercussion)
{
- bank = (uint16_t)midiins; // MIDI instrument defines the patch
+ // == XG bank numbers ==
+ // 0x7E00 - XG "SFX Kits" SFX1/SFX2 channel (16128 signed decimal)
+ // 0x7F00 - XG "Drum Kits" Percussion channel (16256 signed decimal)
+
+ // MIDI instrument defines the patch:
+ if((m_synthMode & Mode_XG) != 0)
+ {
+ // Let XG SFX1/SFX2 bank will go in 128...255 range of LSB in WOPN file)
+ // Let XG Percussion bank will use (0...127 LSB range in WOPN file)
+
+ // Choose: SFX or Drum Kits
+ bank = (uint16_t)midiins + ((bank == 0x7E00) ? 128 : 0);
+ }
+ else
+ {
+ bank = (uint16_t)midiins;
+ }
midiins = note; // Percussion instrument
}
- if(isPercussion || isXgPercussion)
+
+ if(isPercussion)
bank += OPL3::PercussionTag;
const adlinsdata2 *ains = &OPL3::emptyInstrument;
@@ -1038,9 +313,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
ains = &bnk->ins[midiins];
else if(hooks.onDebugMessage)
{
- std::set<uint16_t> &missing = (isPercussion || isXgPercussion) ?
+ std::set<uint16_t> &missing = (isPercussion) ?
caugh_missing_banks_percussion : caugh_missing_banks_melodic;
- const char *text = (isPercussion || isXgPercussion) ?
+ const char *text = (isPercussion) ?
"percussion" : "melodic";
if(missing.insert(bank).second)
hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Playing missing %s MIDI bank %i (patch %i)", channel, text, bank, midiins);
@@ -1057,25 +332,15 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
ains = &bnk->ins[midiins];
}
- /*
- 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];
-
int16_t tone = note;
-
- if(!isPercussion && !isXgPercussion && (bank > 0)) // For non-zero banks
+ if(!isPercussion && (bank > 0)) // For non-zero banks
{
if(ains->flags & adlinsdata::Flag_NoSound)
{
if(hooks.onDebugMessage)
{
if(caugh_missing_instruments.insert(static_cast<uint8_t>(midiins)).second)
- hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Caugh a blank instrument %i (offset %i) in the MIDI bank %u", channel, Ch[channel].patch, midiins, bank);
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Caught a blank instrument %i (offset %i) in the MIDI bank %u", channel, Ch[channel].patch, midiins, bank);
}
bank = 0;
midiins = midiChan.patch;
@@ -1204,16 +469,37 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
//if(hooks.onDebugMessage)
// hooks.onDebugMessage(hooks.onDebugMessage_userData, "i1=%d:%d, i2=%d:%d", i[0],adlchannel[0], i[1],adlchannel[1]);
+ if(midiChan.softPedal) // Apply Soft Pedal level reducing
+ velocity = static_cast<uint8_t>(std::floor(static_cast<float>(velocity) * 0.8f));
+
// Allocate active note for MIDI channel
std::pair<MIDIchannel::activenoteiterator, bool>
ir = midiChan.activenotes_insert(note);
ir.first->vol = velocity;
ir.first->vibrato = midiChan.noteAftertouch[note];
- ir.first->tone = tone;
+ ir.first->noteTone = tone;
+ ir.first->currentTone = tone;
+ ir.first->glideRate = HUGE_VAL;
ir.first->midiins = midiins;
ir.first->ains = ains;
ir.first->chip_channels_count = 0;
+ int8_t currentPortamentoSource = midiChan.portamentoSource;
+ double currentPortamentoRate = midiChan.portamentoRate;
+ bool portamentoEnable =
+ midiChan.portamentoEnable && currentPortamentoRate != HUGE_VAL && !isPercussion;
+ // Record the last note on MIDI channel as source of portamento
+ midiChan.portamentoSource = static_cast<int8_t>(note);
+ // midiChan.portamentoSource = portamentoEnable ? (int8_t)note : (int8_t)-1;
+
+ // Enable gliding on portamento note
+ if (portamentoEnable && currentPortamentoSource >= 0)
+ {
+ ir.first->currentTone = currentPortamentoSource;
+ ir.first->glideRate = currentPortamentoRate;
+ ++midiChan.gliding_note_count;
+ }
+
for(unsigned ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount)
{
int32_t c = adlchannel[ccount];
@@ -1222,6 +508,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
uint16_t chipChan = static_cast<uint16_t>(adlchannel[ccount]);
ir.first->phys_ensure_find_or_create(chipChan)->assign(voices[ccount]);
}
+
NoteUpdate(channel, ir.first, Upd_All | Upd_Patch);
return true;
}
@@ -1271,27 +558,29 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value)
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);
+ if((m_synthMode & Mode_GS) == 0)// Don't use XG drums on GS synth mode
+ 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);
+ if((m_synthMode & Mode_GS) == 0)// Don't use XG drums on GS synth mode
+ 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);
+ UpdatePortamento(channel);
break;
case 37: // Set portamento lsb
Ch[channel].portamento = (Ch[channel].portamento & 0x3F80) | (value);
- //UpdatePortamento(MidCh);
+ UpdatePortamento(channel);
break;
case 65: // Enable/disable portamento
- // value >= 64 ? enabled : disabled
- //UpdatePortamento(MidCh);
+ Ch[channel].portamentoEnable = value >= 64;
+ UpdatePortamento(channel);
break;
case 7: // Change volume
@@ -1305,8 +594,20 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value)
break;
case 64: // Enable/disable sustain
- Ch[channel].sustain = value;
- if(!value) KillSustainingNotes(channel);
+ Ch[channel].sustain = (value >= 64);
+ if(!Ch[channel].sustain)
+ KillSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_Pedal);
+ break;
+
+ case 66: // Enable/disable sostenuto
+ if(value >= 64) //Find notes and mark them as sostenutoed
+ MarkSostenutoNotes(channel);
+ else
+ KillSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_Sostenuto);
+ break;
+
+ case 67: // Enable/disable soft-pedal
+ Ch[channel].softPedal = (value >= 64);
break;
case 11: // Change expression (another volume factor)
@@ -1324,14 +625,13 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value)
case 121: // Reset all controllers
Ch[channel].resetAllControllers();
- //UpdatePortamento(MidCh);
NoteUpdate_All(channel, Upd_Pan + Upd_Volume + Upd_Pitch);
// Kill all sustained notes
- KillSustainingNotes(channel);
+ KillSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_ANY);
break;
case 120: // All sounds off
- NoteUpdate_All(channel, Upt_OffMute);
+ NoteUpdate_All(channel, Upd_OffMute);
break;
case 123: // All notes off
@@ -1433,19 +733,270 @@ void MIDIplay::realTime_BankChange(uint8_t channel, uint16_t bank)
Ch[channel].bank_msb = uint8_t((bank >> 8) & 0xFF);
}
+void MIDIplay::setDeviceId(uint8_t id)
+{
+ m_sysExDeviceId = id;
+}
+
+bool MIDIplay::realTime_SysEx(const uint8_t *msg, size_t size)
+{
+ if(size < 4 || msg[0] != 0xF0 || msg[size - 1] != 0xF7)
+ return false;
+
+ unsigned manufacturer = msg[1];
+ unsigned dev = msg[2];
+ msg += 3;
+ size -= 4;
+
+ switch(manufacturer)
+ {
+ default:
+ break;
+ case Manufacturer_UniversalNonRealtime:
+ case Manufacturer_UniversalRealtime:
+ return doUniversalSysEx(
+ dev, manufacturer == Manufacturer_UniversalRealtime, msg, size);
+ case Manufacturer_Roland:
+ return doRolandSysEx(dev, msg, size);
+ case Manufacturer_Yamaha:
+ return doYamahaSysEx(dev, msg, size);
+ }
+
+ return false;
+}
+
+bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data, size_t size)
+{
+ bool devicematch = dev == 0x7F || dev == m_sysExDeviceId;
+ if(size < 2 || !devicematch)
+ return false;
+
+ unsigned address =
+ (((unsigned)data[0] & 0x7F) << 8) |
+ (((unsigned)data[1] & 0x7F));
+ data += 2;
+ size -= 2;
+
+ switch(((unsigned)realtime << 16) | address)
+ {
+ case (0 << 16) | 0x0901: // GM System On
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: GM System On");
+ m_synthMode = Mode_GM;
+ realTime_ResetState();
+ return true;
+ case (0 << 16) | 0x0902: // GM System Off
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: GM System Off");
+ m_synthMode = Mode_XG;//TODO: TEMPORARY, make something RIGHT
+ realTime_ResetState();
+ return true;
+ case (1 << 16) | 0x0401: // MIDI Master Volume
+ if(size != 2)
+ break;
+ unsigned volume =
+ (((unsigned)data[0] & 0x7F)) |
+ (((unsigned)data[1] & 0x7F) << 7);
+ m_masterVolume = volume >> 7;
+ for(size_t ch = 0; ch < Ch.size(); ch++)
+ NoteUpdate_All(uint16_t(ch), Upd_Volume);
+ return true;
+ }
+
+ return false;
+}
+
+bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size)
+{
+ bool devicematch = dev == 0x7F || (dev & 0x0F) == m_sysExDeviceId;
+ if(size < 6 || !devicematch)
+ return false;
+
+ unsigned model = data[0] & 0x7F;
+ unsigned mode = data[1] & 0x7F;
+ unsigned checksum = data[size - 1] & 0x7F;
+ data += 2;
+ size -= 3;
+
+#if !defined(ADLMIDI_SKIP_ROLAND_CHECKSUM)
+ {
+ unsigned checkvalue = 0;
+ for(size_t i = 0; i < size; ++i)
+ checkvalue += data[i] & 0x7F;
+ checkvalue = (128 - (checkvalue & 127)) & 127;
+ if(checkvalue != checksum)
+ {
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught invalid roland SysEx message!");
+ return false;
+ }
+ }
+#endif
+
+ unsigned address =
+ (((unsigned)data[0] & 0x7F) << 16) |
+ (((unsigned)data[1] & 0x7F) << 8) |
+ (((unsigned)data[2] & 0x7F));
+ unsigned target_channel = 0;
+
+ /* F0 41 10 42 12 40 00 7F 00 41 F7 */
+
+ if((address & 0xFFF0FF) == 0x401015) // Turn channel 1 into percussion
+ {
+ address = 0x401015;
+ target_channel = data[1] & 0x0F;
+ }
+
+ data += 3;
+ size -= 3;
+
+ if(mode != RolandMode_Send) // don't have MIDI-Out reply ability
+ return false;
+
+ // Mode Set
+ // F0 {41 10 42 12} {40 00 7F} {00 41} F7
+
+ // Custom drum channels
+ // F0 {41 10 42 12} {40 1<ch> 15} {<state> <sum>} F7
+
+ switch((model << 24) | address)
+ {
+ case (RolandModel_GS << 24) | 0x00007F: // System Mode Set
+ {
+ if(size != 1 || (dev & 0xF0) != 0x10)
+ break;
+ unsigned mode = data[0] & 0x7F;
+ ADL_UNUSED(mode);//TODO: Hook this correctly!
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland System Mode Set: %02X", mode);
+ m_synthMode = Mode_GS;
+ realTime_ResetState();
+ return true;
+ }
+ case (RolandModel_GS << 24) | 0x40007F: // Mode Set
+ {
+ if(size != 1 || (dev & 0xF0) != 0x10)
+ break;
+ unsigned value = data[0] & 0x7F;
+ ADL_UNUSED(value);//TODO: Hook this correctly!
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland Mode Set: %02X", value);
+ m_synthMode = Mode_GS;
+ realTime_ResetState();
+ return true;
+ }
+ case (RolandModel_GS << 24) | 0x401015: // Percussion channel
+ {
+ if(size != 1 || (dev & 0xF0) != 0x10)
+ break;
+ if(Ch.size() < 16)
+ break;
+ unsigned value = data[0] & 0x7F;
+ const uint8_t channels_map[16] =
+ {
+ 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15
+ };
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData,
+ "SysEx: Caught Roland Percussion set: %02X on channel %u (from %X)",
+ value, channels_map[target_channel], target_channel);
+ Ch[channels_map[target_channel]].is_xg_percussion = ((value == 0x01)) || ((value == 0x02));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size)
+{
+ bool devicematch = dev == 0x7F || (dev & 0x0F) == m_sysExDeviceId;
+ if(size < 1 || !devicematch)
+ return false;
+
+ unsigned model = data[0] & 0x7F;
+ ++data;
+ --size;
+
+ switch((model << 8) | (dev & 0xF0))
+ {
+ case (YamahaModel_XG << 8) | 0x10: // parameter change
+ {
+ if(size < 3)
+ break;
+
+ unsigned address =
+ (((unsigned)data[0] & 0x7F) << 16) |
+ (((unsigned)data[1] & 0x7F) << 8) |
+ (((unsigned)data[2] & 0x7F));
+ data += 3;
+ size -= 3;
+
+ switch(address)
+ {
+ case 0x00007E: // XG System On
+ if(size != 1)
+ break;
+ unsigned value = data[0] & 0x7F;
+ ADL_UNUSED(value);//TODO: Hook this correctly!
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Yamaha XG System On: %02X", value);
+ m_synthMode = Mode_XG;
+ realTime_ResetState();
+ return true;
+ }
+
+ break;
+ }
+ }
+
+ return false;
+}
+
void MIDIplay::realTime_panic()
{
Panic();
- KillSustainingNotes(-1, -1);
+ KillSustainingNotes(-1, -1, AdlChannel::LocationData::Sustain_ANY);
+}
+
+void MIDIplay::realTime_deviceSwitch(size_t track, const char *data, size_t length)
+{
+ const std::string indata(data, length);
+ current_device[track] = ChooseDevice(indata);
}
-void MIDIplay::AudioTick(uint32_t chipId, uint32_t /*rate*/)
+uint64_t MIDIplay::realTime_currentDevice(size_t track)
+{
+ return current_device[track];
+}
+
+void MIDIplay::realTime_rawOPL(uint8_t reg, uint8_t value)
+{
+ if((reg & 0xF0) == 0xC0)
+ value |= 0x30;
+ //std::printf("OPL poke %02X, %02X\n", reg, value);
+ //std::fflush(stdout);
+ opl.Poke(0, reg, value);
+}
+
+#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
+void MIDIplay::AudioTick(uint32_t chipId, uint32_t rate)
{
if(chipId != 0) // do first chip ticks only
return;
- /*uint32_t tickNumber = */m_audioTickCounter++;
+ uint32_t tickNumber = m_audioTickCounter++;
+ double timeDelta = 1.0 / rate;
+
+ enum { portamentoInterval = 32 }; // for efficiency, set rate limit on pitch updates
+
+ if(tickNumber % portamentoInterval == 0)
+ {
+ double portamentoDelta = timeDelta * portamentoInterval;
+ UpdateGlide(portamentoDelta);
+ }
}
+#endif
void MIDIplay::NoteUpdate(uint16_t MidCh,
MIDIplay::MIDIchannel::activenoteiterator i,
@@ -1453,7 +1004,8 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
int32_t select_adlchn)
{
MIDIchannel::NoteInfo &info = *i;
- const int16_t tone = info.tone;
+ const int16_t noteTone = info.noteTone;
+ const double currentTone = info.currentTone;
const uint8_t vol = info.vol;
const int midiins = static_cast<int>(info.midiins);
const adlinsdata2 &ains = *info.ains;
@@ -1474,7 +1026,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc);
if(d) // inserts if necessary
{
- d->sustained = false;
+ d->sustained = AdlChannel::LocationData::Sustain_None;
d->vibdelay = 0;
d->fixed_sustain = (ains.ms_sound_kon == static_cast<uint16_t>(adlNoteOnMaxTime));
d->kon_time_until_neglible = ains.ms_sound_kon;
@@ -1493,17 +1045,17 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
if(props_mask & Upd_Off) // note off
{
- if(Ch[MidCh].sustain == 0)
+ if(!Ch[MidCh].sustain)
{
AdlChannel::LocationData *k = ch[c].users_find(my_loc);
-
- if(k)
+ bool do_erase_user = (k && ((k->sustained & AdlChannel::LocationData::Sustain_Sostenuto) == 0));
+ if(do_erase_user)
ch[c].users_erase(k);
if(hooks.onNote)
- hooks.onNote(hooks.onNote_userData, c, tone, midiins, 0, 0.0);
+ hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, 0, 0.0);
- if(ch[c].users_empty())
+ if(do_erase_user && ch[c].users_empty())
{
opl.NoteOff(c);
if(props_mask & Upd_Mute) // Mute the note
@@ -1523,9 +1075,9 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
// Also will avoid overwriting it very soon.
AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc);
if(d)
- d->sustained = true; // note: not erased!
+ d->sustained |= AdlChannel::LocationData::Sustain_Pedal; // note: not erased!
if(hooks.onNote)
- hooks.onNote(hooks.onNote_userData, c, tone, midiins, -1, 0.0);
+ hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, -1, 0.0);
}
info.phys_erase_at(&ins); // decrements channel count
@@ -1553,10 +1105,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
switch(opl.m_volumeScale)
{
-
+ default:
case OPL3::VOLUME_Generic:
{
- volume = vol * Ch[MidCh].volume * Ch[MidCh].expression;
+ volume = vol * m_masterVolume * Ch[MidCh].volume * Ch[MidCh].expression;
/* If the channel has arpeggio, the effective volume of
* *this* instrument is actually lower due to timesharing.
@@ -1567,64 +1119,60 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
*/
//volume = (int)(volume * std::sqrt( (double) ch[c].users.size() ));
- // 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);
+ // The formula below: SOLVE(V=127^4 * 2^( (A-63.49999) / 8), A)
+ volume = volume > (8725 * 127) ? static_cast<uint32_t>(std::log(static_cast<double>(volume)) * 11.541560327111707 - 1.601379199767093e+02) : 0;
+ // The incorrect formula below: SOLVE(V=127^4 * (2^(A/63)-1), A)
+ //opl.Touch_Real(c, volume>(11210*127) ? 91.61112 * std::log((4.8819E-7/127)*volume + 1.0)+0.5 : 0);
}
break;
case OPL3::VOLUME_NATIVE:
{
volume = vol * Ch[MidCh].volume * Ch[MidCh].expression;
- volume = volume * 127 / (127 * 127 * 127) / 2;
- opl.Touch_Real(c, volume, brightness);
+ // volume = volume * m_masterVolume / (127 * 127 * 127) / 2;
+ volume = (volume * m_masterVolume) / 4096766;
}
break;
case OPL3::VOLUME_DMX:
{
- volume = 2 * ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129) + 1;
+ volume = 2 * (Ch[MidCh].volume * Ch[MidCh].expression * m_masterVolume / 16129) + 1;
//volume = 2 * (Ch[MidCh].volume) + 1;
volume = (DMX_volume_mapping_table[(vol < 128) ? vol : 127] * volume) >> 9;
- opl.Touch_Real(c, volume, brightness);
}
break;
case OPL3::VOLUME_APOGEE:
{
- volume = ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129);
+ volume = (Ch[MidCh].volume * Ch[MidCh].expression * m_masterVolume / 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 = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume /** Ch[MidCh].expression*/) * m_masterVolume / 16129 /*2048383*/) >> 2)];
+ volume = 63 - W9X_volume_mapping_table[((vol * Ch[MidCh].volume * Ch[MidCh].expression * m_masterVolume / 2048383) >> 2)];
//volume = W9X_volume_mapping_table[vol >> 2] + volume;
- opl.Touch_Real(c, volume, brightness);
}
break;
}
+ opl.Touch_Real(c, volume, brightness);
+
/* DEBUG ONLY!!!
- static uint32_t max = 0;
+ static uint32_t max = 0;
- if(volume == 0)
- max = 0;
+ if(volume == 0)
+ max = 0;
- if(volume > max)
- max = volume;
+ if(volume > max)
+ max = volume;
- printf("%d\n", max);
- fflush(stdout);
- */
+ printf("%d\n", max);
+ fflush(stdout);
+ */
}
if(props_mask & Upd_Pitch)
@@ -1632,7 +1180,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
AdlChannel::LocationData *d = ch[c].users_find(my_loc);
// Don't bend a sustained note
- if(!d || !d->sustained)
+ if(!d || (d->sustained == AdlChannel::LocationData::Sustain_None))
{
double midibend = Ch[MidCh].bend * Ch[MidCh].bendsense;
double bend = midibend + ins.ains.finetune;
@@ -1648,355 +1196,22 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
if(vibrato && (!d || d->vibdelay >= Ch[MidCh].vibdelay))
bend += static_cast<double>(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 * (static_cast<double>(tone) + bend + phase)));
+#define BEND_COEFFICIENT 172.4387
+ opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (currentTone + bend + phase)));
#undef BEND_COEFFICIENT
if(hooks.onNote)
- hooks.onNote(hooks.onNote_userData, c, tone, midiins, vol, midibend);
+ hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, vol, midibend);
}
}
}
if(info.chip_channels_count == 0)
- 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
-}
-
-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());
-
-#if 0 /* Print all tempo events */
- if(evt.subtype == MidiEvent::ST_TEMPOCHANGE)
- {
- if(hooks.onDebugMessage)
- hooks.onDebugMessage(hooks.onDebugMessage_userData, "Temp Change: %02X%02X%02X", evt.data[0], evt.data[1], evt.data[2]);
- }
-#endif
-
- /* 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++));
-
- /* TODO: Implement conversion of RSXX's note volumes out of synthesizer */
- /*if((opl.m_musicMode == OPL3::MODE_RSXX) && (evType == MidiEvent::T_NOTEON) && (evt.data[1] != 0))
- {
- //NOT WORKING YET
- evt.type = MidiEvent::T_NOTETOUCH;
- }
- else */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;
+ if(i->glideRate != HUGE_VAL)
+ --Ch[MidCh].gliding_note_count;
+ Ch[MidCh].activenotes_erase(i);
}
-
- return evt;
}
-#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
const std::string &MIDIplay::getErrorString()
{
@@ -2008,168 +1223,9 @@ 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.Poke(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 /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
-
int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t) const
{
- int64_t s = -ch[c].koff_time_until_neglible;
+ int64_t s = (opl.m_musicMode != OPL3::MODE_CMF) ? -ch[c].koff_time_until_neglible : 0;
// Same midi-instrument = some stability
//if(c == MidCh) s += 4;
@@ -2177,7 +1233,7 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteI
{
s -= 4000;
- if(!j->sustained)
+ if(j->sustained == AdlChannel::LocationData::Sustain_None)
s -= j->kon_time_until_neglible;
else
s -= (j->kon_time_until_neglible / 2);
@@ -2223,7 +1279,7 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteI
for(AdlChannel::LocationData *m = ch[c2].users_first; m; m = m->next)
{
- if(m->sustained) continue;
+ if(m->sustained != AdlChannel::LocationData::Sustain_None) continue;
if(m->vibdelay >= 200) continue;
if(m->ins != j->ins) continue;
n_evacuation_stations += 1;
@@ -2247,7 +1303,7 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo
AdlChannel::LocationData *j = jnext;
jnext = jnext->next;
- if(!j->sustained)
+ if(j->sustained == AdlChannel::LocationData::Sustain_None)
{
// Collision: Kill old note,
// UNLESS we're going to do arpeggio
@@ -2272,7 +1328,7 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo
// 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));
+ KillSustainingNotes(-1, static_cast<int32_t>(c), AdlChannel::LocationData::Sustain_ANY);
// Keyoff the channel so that it can be retriggered,
// unless the new note will be introduced as just an arpeggio.
@@ -2314,11 +1370,11 @@ void MIDIplay::KillOrEvacuate(size_t from_channel,
{
hooks.onNote(hooks.onNote_userData,
(int)from_channel,
- i->tone,
+ i->noteTone,
static_cast<int>(i->midiins), 0, 0.0);
hooks.onNote(hooks.onNote_userData,
(int)c,
- i->tone,
+ i->noteTone,
static_cast<int>(i->midiins),
i->vol, 0.0);
}
@@ -2355,7 +1411,7 @@ void MIDIplay::Panic()
}
}
-void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
+void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn, uint8_t sustain_type)
{
uint32_t first = 0, last = opl.NumChannels;
@@ -2365,9 +1421,10 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
last = first + 1;
}
- for(unsigned c = first; c < last; ++c)
+ for(uint32_t c = first; c < last; ++c)
{
- if(ch[c].users_empty()) continue; // Nothing to do
+ if(ch[c].users_empty())
+ continue; // Nothing to do
for(AdlChannel::LocationData *jnext = ch[c].users_first; jnext;)
{
@@ -2375,12 +1432,14 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
jnext = jnext->next;
if((MidCh < 0 || j->loc.MidCh == MidCh)
- && j->sustained)
+ && ((j->sustained & sustain_type) != 0))
{
int midiins = '?';
if(hooks.onNote)
hooks.onNote(hooks.onNote_userData, (int)c, j->loc.note, midiins, 0, 0.0);
- ch[c].users_erase(j);
+ j->sustained &= ~sustain_type;
+ if((j->sustained == AdlChannel::LocationData::Sustain_None))
+ ch[c].users_erase(j);//Remove only when note is clean from any holders
}
}
@@ -2390,6 +1449,24 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
}
}
+void MIDIplay::MarkSostenutoNotes(int32_t MidCh)
+{
+ uint32_t first = 0, last = opl.NumChannels;
+ for(uint32_t c = first; c < last; ++c)
+ {
+ if(ch[c].users_empty())
+ continue; // Nothing to do
+
+ for(AdlChannel::LocationData *jnext = ch[c].users_first; jnext;)
+ {
+ AdlChannel::LocationData *j = jnext;
+ jnext = jnext->next;
+ if((j->loc.MidCh == MidCh) && (j->sustained == AdlChannel::LocationData::Sustain_None))
+ j->sustained |= AdlChannel::LocationData::Sustain_Sostenuto;
+ }
+ }
+}
+
void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB)
{
bool nrpn = Ch[MidCh].nrpn;
@@ -2405,17 +1482,26 @@ void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB)
Ch[MidCh].bendsense_lsb = value;
Ch[MidCh].updateBendSensitivity();
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;
+ case 0x0108 + 1*0x10000 + 1*0x20000:
+ if((m_synthMode & Mode_XG) != 0) // 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;
+ case 0x0109 + 1*0x10000 + 1*0x20000:
+ if((m_synthMode & Mode_XG) != 0) // 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;
+ case 0x010A + 1*0x10000 + 1*0x20000:
+ if((m_synthMode & Mode_XG) != 0) // 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);*/
@@ -2423,15 +1509,14 @@ void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB)
}
}
-//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::UpdatePortamento(unsigned MidCh)
+{
+ double rate = HUGE_VAL;
+ uint16_t midival = Ch[MidCh].portamento;
+ if(Ch[MidCh].portamentoEnable && midival > 0)
+ rate = 350.0 * std::pow(2.0, -0.062 * (1.0 / 128) * midival);
+ Ch[MidCh].portamentoRate = rate;
+}
void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask)
{
@@ -2467,9 +1552,6 @@ void MIDIplay::UpdateVibrato(double amount)
}
}
-
-
-
uint64_t MIDIplay::ChooseDevice(const std::string &name)
{
std::map<std::string, uint64_t>::iterator i = devices.find(name);
@@ -2532,7 +1614,7 @@ retry_arpeggio:
n = 0; n < count; ++n)
i = i->next;
- if(i->sustained == false)
+ if(i->sustained == AdlChannel::LocationData::Sustain_None)
{
if(i->kon_time_until_neglible <= 0l)
{
@@ -2554,6 +1636,38 @@ retry_arpeggio:
}
}
+void MIDIplay::UpdateGlide(double amount)
+{
+ size_t num_channels = Ch.size();
+
+ for(size_t channel = 0; channel < num_channels; ++channel)
+ {
+ MIDIchannel &midiChan = Ch[channel];
+ if(midiChan.gliding_note_count == 0)
+ continue;
+
+ for(MIDIchannel::activenoteiterator it = midiChan.activenotes_begin();
+ it; ++it)
+ {
+ double finalTone = it->noteTone;
+ double previousTone = it->currentTone;
+
+ bool directionUp = previousTone < finalTone;
+ double toneIncr = amount * (directionUp ? +it->glideRate : -it->glideRate);
+
+ double currentTone = previousTone + toneIncr;
+ bool glideFinished = !(directionUp ? (currentTone < finalTone) : (currentTone > finalTone));
+ currentTone = glideFinished ? finalTone : currentTone;
+
+ if(currentTone != previousTone)
+ {
+ it->currentTone = currentTone;
+ NoteUpdate(static_cast<uint16_t>(channel), it, Upd_Pitch);
+ }
+ }
+ }
+}
+
#ifndef ADLMIDI_DISABLE_CPP_EXTRAS