2018-03-24 14:52:52 +00:00
/*
* libOPNMIDI is a free MIDI to WAV conversion library with OPN2 ( YM2612 ) emulation
*
* MIDI parser and player ( Original code from ADLMIDI ) : Copyright ( c ) 2010 - 2014 Joel Yliluoma < bisqwit @ iki . fi >
* OPNMIDI Library and YM2612 support : Copyright ( c ) 2017 - 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 "opnmidi_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
} ;
inline bool isXgPercChannel ( uint8_t msb , uint8_t lsb )
{
return ( msb = = 0x7E | | msb = = 0x7F ) & & ( lsb = = 0 ) ;
}
void OPNMIDIplay : : OpnChannel : : 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 ;
}
}
}
2018-03-24 20:46:05 +00:00
# ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
OPNMIDIplay : : MidiEvent : : MidiEvent ( ) :
type ( T_UNKNOWN ) ,
subtype ( T_UNKNOWN ) ,
channel ( 0 ) ,
isValid ( 1 ) ,
absPosition ( 0 )
{ }
OPNMIDIplay : : MidiTrackRow : : MidiTrackRow ( ) :
time ( 0.0 ) ,
delay ( 0 ) ,
absPos ( 0 ) ,
timeDelay ( 0.0 )
{ }
void OPNMIDIplay : : MidiTrackRow : : reset ( )
{
time = 0.0 ;
delay = 0 ;
absPos = 0 ;
timeDelay = 0.0 ;
events . clear ( ) ;
}
void OPNMIDIplay : : 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 ( ) ) ;
}
2018-03-24 20:46:05 +00:00
# endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
2018-03-24 20:46:05 +00:00
# ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
bool OPNMIDIplay : : 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 ( opn . m_musicMode = = OPN2 : : MODE_RSXX )
ok = true ;
else
evtPos . delay = ReadVarLenEx ( & trackPtr , end , ok ) ;
if ( ! ok )
{
2018-03-27 00:21:32 +00:00
int len = std : : snprintf ( error , 150 , " buildTrackData: Can't read variable-length value at begin of track %d. \n " , ( int ) tk ) ;
2018-03-24 14:52:52 +00:00
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 )
{
2018-03-27 00:21:32 +00:00
int len = std : : snprintf ( error , 150 , " buildTrackData: Fail to parse event in the track %d. \n " , ( int ) tk ) ;
2018-03-24 14:52:52 +00:00
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 ( opn . m_musicMode = = OPN2 : : 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 ;
}
2018-03-24 20:46:05 +00:00
# endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
2018-03-24 20:46:05 +00:00
OPNMIDIplay : : OPNMIDIplay ( unsigned long sampleRate )
# ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
: fullSongTimeLength ( 0.0 ) ,
2018-03-24 14:52:52 +00:00
postSongWaitDelay ( 1.0 ) ,
loopStartTime ( - 1.0 ) ,
loopEndTime ( - 1.0 ) ,
tempoMultiplier ( 1.0 ) ,
atEnd ( false ) ,
loopStart ( false ) ,
loopEnd ( false ) ,
invalidLoop ( false )
2018-03-24 20:46:05 +00:00
# endif
2018-03-24 14:52:52 +00:00
{
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 . OpnBank = 0 ;
m_setup . NumCards = 2 ;
m_setup . LogarithmicVolumes = false ;
m_setup . VolumeModel = OPNMIDI_VolumeModel_AUTO ;
//m_setup.SkipForward = 0;
m_setup . loopingIsEnabled = false ;
m_setup . ScaleModulators = 0 ;
m_setup . delay = 0.0 ;
m_setup . carry = 0.0 ;
m_setup . tick_skip_samples_delay = 0 ;
applySetup ( ) ;
ChooseDevice ( " none " ) ;
realTime_ResetState ( ) ;
}
void OPNMIDIplay : : applySetup ( )
{
m_setup . tick_skip_samples_delay = 0 ;
opn . ScaleModulators = m_setup . ScaleModulators ;
opn . LogarithmicVolumes = m_setup . LogarithmicVolumes ;
opn . m_musicMode = OPN2 : : MODE_MIDI ;
opn . ChangeVolumeRangesModel ( static_cast < OPNMIDI_VolumeModels > ( m_setup . VolumeModel ) ) ;
if ( m_setup . VolumeModel = = OPNMIDI_VolumeModel_AUTO )
opn . m_volumeScale = OPN2 : : VOLUME_Generic ;
opn . NumCards = m_setup . NumCards ;
opn . Reset ( m_setup . PCM_RATE ) ;
ch . clear ( ) ;
ch . resize ( opn . NumChannels ) ;
}
uint64_t OPNMIDIplay : : 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 OPNMIDIplay : : 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 ;
}
2018-03-24 20:46:05 +00:00
# ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
double OPNMIDIplay : : 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 < opn . 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 ;
}
2018-03-24 20:46:05 +00:00
# endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
void OPNMIDIplay : : TickIteratos ( double s )
{
for ( uint16_t c = 0 ; c < opn . NumChannels ; + + c )
ch [ c ] . AddAge ( static_cast < int64_t > ( s * 1000.0 ) ) ;
UpdateVibrato ( s ) ;
UpdateArpeggio ( s ) ;
}
2018-03-24 20:46:05 +00:00
# ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
void OPNMIDIplay : : 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 OPNMIDIplay : : tell ( )
{
return CurrentPositionNew . absTimePosition ;
}
double OPNMIDIplay : : timeLength ( )
{
return fullSongTimeLength ;
}
double OPNMIDIplay : : getLoopStart ( )
{
return loopStartTime ;
}
double OPNMIDIplay : : getLoopEnd ( )
{
return loopEndTime ;
}
void OPNMIDIplay : : 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 OPNMIDIplay : : setTempo ( double tempo )
{
tempoMultiplier = tempo ;
}
2018-03-24 20:46:05 +00:00
# endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
void OPNMIDIplay : : realTime_ResetState ( )
{
for ( size_t ch = 0 ; ch < Ch . size ( ) ; ch + + )
{
MIDIchannel & chan = Ch [ ch ] ;
chan . volume = ( opn . m_musicMode = = OPN2 : : MODE_RSXX ) ? 127 : 100 ;
chan . expression = 127 ;
chan . panning = 0xC0 ;
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 OPNMIDIplay : : realTime_NoteOn ( uint8_t channel , uint8_t note , uint8_t velocity )
{
if ( ( opn . m_musicMode = = OPN2 : : 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 = opn . dynamic_percussion_offset + note ; // Percussion instrument
isXgPercussion = true ;
isPercussion = false ;
}
}
if ( isPercussion )
{
bank = ( uint16_t ) midiins ; // MIDI instrument defines the patch
midiins = opn . dynamic_percussion_offset + note ; // Percussion instrument
}
//Set bank bank
if ( bank > 0 )
{
if ( isPercussion | | isXgPercussion )
{
OPN2 : : BankMap : : iterator b = opn . dynamic_percussion_banks . find ( bank ) ;
if ( b ! = opn . dynamic_percussion_banks . end ( ) )
midiins + = b - > second * 128 ;
else
if ( hooks . onDebugMessage )
{
if ( ! caugh_missing_banks_melodic . count ( bank ) )
{
2018-03-27 00:21:32 +00:00
hooks . onDebugMessage ( hooks . onDebugMessage_userData , " [%i] Playing missing percussion MIDI bank %i (patch %i) " , channel , bank , midiins ) ;
2018-03-24 14:52:52 +00:00
caugh_missing_banks_melodic . insert ( bank ) ;
}
}
}
else
{
OPN2 : : BankMap : : iterator b = opn . dynamic_melodic_banks . find ( bank ) ;
if ( b ! = opn . dynamic_melodic_banks . end ( ) )
midiins + = b - > second * 128 ;
else
if ( hooks . onDebugMessage )
{
if ( ! caugh_missing_banks_melodic . count ( bank ) )
{
2018-03-27 00:21:32 +00:00
hooks . onDebugMessage ( hooks . onDebugMessage_userData , " [%i] Playing missing melodic MIDI bank %i (patch %i) " , channel , bank , midiins ) ;
2018-03-24 14:52:52 +00:00
caugh_missing_banks_melodic . 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
2018-03-27 00:21:32 +00:00
size_t meta = opn . GetAdlMetaNumber ( midiins ) ;
const opnInstMeta * ains = & opn . GetAdlMetaIns ( meta ) ;
2018-03-24 14:52:52 +00:00
int16_t tone = note ;
2018-03-27 00:21:32 +00:00
if ( ! isPercussion & & ! isXgPercussion & & ( bank > 0 ) ) // For non-zero banks
{
if ( ains - > flags & opnInstMeta : : Flag_NoSound )
{
if ( hooks . onDebugMessage )
{
if ( ! caugh_missing_instruments . count ( static_cast < uint8_t > ( midiins ) ) )
{
hooks . onDebugMessage ( hooks . onDebugMessage_userData , " [%i] Caugh a blank instrument %i (offset %i) in the MIDI bank %u " , channel , Ch [ channel ] . patch , midiins , bank ) ;
caugh_missing_instruments . insert ( static_cast < uint8_t > ( midiins ) ) ;
}
}
bank = 0 ;
midiins = Ch [ channel ] . patch ;
meta = opn . GetAdlMetaNumber ( midiins ) ;
ains = & opn . GetAdlMetaIns ( meta ) ;
}
}
if ( ains - > tone )
2018-03-24 14:52:52 +00:00
{
/*if(ains.tone < 20)
tone + = ains . tone ;
else */
2018-03-27 00:21:32 +00:00
if ( ains - > tone < 128 )
tone = ains - > tone ;
2018-03-24 14:52:52 +00:00
else
2018-03-27 00:21:32 +00:00
tone - = ains - > tone - 128 ;
2018-03-24 14:52:52 +00:00
}
2018-03-27 00:21:32 +00:00
uint16_t i [ 2 ] = { ains - > opnno1 , ains - > opnno2 } ;
2018-03-24 14:52:52 +00:00
//bool pseudo_4op = ains.flags & opnInstMeta::Flag_Pseudo8op;
//if((opn.AdlPercussionMode == 1) && PercussionMap[midiins & 0xFF]) i[1] = i[0];
if ( hooks . onDebugMessage )
{
2018-03-27 00:21:32 +00:00
if ( ! caugh_missing_instruments . count ( static_cast < uint8_t > ( midiins ) ) & & ( ains - > flags & opnInstMeta : : Flag_NoSound ) )
2018-03-24 14:52:52 +00:00
{
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 ( i [ 0 ] = = i [ 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 ) opn . NumChannels ; + + a )
{
if ( ccount = = 1 & & static_cast < int32_t > ( a ) = = adlchannel [ 0 ] ) continue ;
// ^ Don't use the same channel for primary&secondary
// ===== Kept for future pseudo-8-op mode
//if(i[0] == i[1] || pseudo_4op)
//{
// // Only use regular channels
// uint8_t expected_mode = 0;
// if(opn.AdlPercussionMode == 1)
// {
// if(cmf_percussion_mode)
// expected_mode = MidCh < 11 ? 0 : (3 + MidCh - 11); // CMF
// else
// expected_mode = PercussionMap[midiins & 0xFF];
// }
// if(opn.four_op_category[a] != expected_mode)
// continue;
//}
int64_t s = CalculateAdlChannelGoodness ( a , i [ 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 ) , i [ 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 ] = i [ ccount ] ;
}
NoteUpdate ( channel , ir . first , Upd_All | Upd_Patch ) ;
return true ;
}
void OPNMIDIplay : : realTime_NoteOff ( uint8_t channel , uint8_t note )
{
channel = channel % 16 ;
NoteOff ( channel , note ) ;
}
void OPNMIDIplay : : 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 OPNMIDIplay : : 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 OPNMIDIplay : : 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 | = 0x80 ;
if ( value > = 64 - 32 ) Ch [ channel ] . panning | = 0x40 ;
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 = 0xC0 ;
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 OPNMIDIplay : : realTime_PatchChange ( uint8_t channel , uint8_t patch )
{
channel = channel % 16 ;
Ch [ channel ] . patch = patch ;
}
void OPNMIDIplay : : 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 OPNMIDIplay : : 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 OPNMIDIplay : : realTime_BankChangeLSB ( uint8_t channel , uint8_t lsb )
{
channel = channel % 16 ;
Ch [ channel ] . bank_lsb = lsb ;
}
void OPNMIDIplay : : realTime_BankChangeMSB ( uint8_t channel , uint8_t msb )
{
channel = channel % 16 ;
Ch [ channel ] . bank_msb = msb ;
}
void OPNMIDIplay : : 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 OPNMIDIplay : : realTime_panic ( )
{
Panic ( ) ;
KillSustainingNotes ( - 1 , - 1 ) ;
}
void OPNMIDIplay : : NoteUpdate ( uint16_t MidCh ,
OPNMIDIplay : : 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 size_t midiins = info . midiins ;
const size_t insmeta = info . insmeta ;
const opnInstMeta & ains = opn . GetAdlMetaIns ( insmeta ) ;
OpnChannel : : 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 )
{
opn . Patch ( c , ins ) ;
OpnChannel : : 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 )
{
OpnChannel : : 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 , ( int ) midiins , 0 , 0.0 ) ;
if ( ch [ c ] . users . empty ( ) )
{
opn . 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.
OpnChannel : : LocationData & d = ch [ c ] . users [ my_loc ] ;
d . sustained = true ; // note: not erased!
if ( hooks . onNote )
hooks . onNote ( hooks . onNote_userData , c , tone , ( int ) midiins , - 1 , 0.0 ) ;
}
info . phys . erase ( j ) ;
continue ;
}
if ( props_mask & Upd_Pan )
opn . 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 ( opn . m_volumeScale )
{
case OPN2 : : VOLUME_Generic :
case OPN2 : : 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 ( opn . LogarithmicVolumes )
volume = volume * 127 / ( 2048383 /*127 * 127 * 127*/ ) ;
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 ) ) * 2.0 ) : 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);
}
opn . Touch_Real ( c , volume , brightness ) ;
//opl.Touch(c, volume);
}
break ;
case OPN2 : : 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 ;
opn . Touch_Real ( c , volume , brightness ) ;
}
break ;
case OPN2 : : 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;
volume * = 2 ; //OPN has 0~127 range
opn . Touch_Real ( c , volume , brightness ) ;
}
break ;
case OPN2 : : 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;
volume * = 2 ; //OPN has 0~127 range
opn . 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 )
{
OpnChannel : : LocationData & d = ch [ c ] . users [ my_loc ] ;
// Don't bend a sustained note
if ( ! d . sustained )
{
double bend = Ch [ MidCh ] . bend + opn . GetAdlIns ( ins ) . finetune ;
double phase = 0.0 ;
if ( ( ains . flags & opnInstMeta : : Flag_Pseudo8op ) & & ins = = ains . opnno2 )
{
phase = ains . 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 ) ;
# define BEND_COEFFICIENT 321.88557
opn . NoteOn ( c , BEND_COEFFICIENT * std : : exp ( 0.057762265 * ( tone + bend + phase ) ) ) ;
# undef BEND_COEFFICIENT
if ( hooks . onNote )
hooks . onNote ( hooks . onNote_userData , c , tone , ( int ) midiins , vol , Ch [ MidCh ] . bend ) ;
}
}
}
if ( info . phys . empty ( ) )
Ch [ MidCh ] . activenotes . erase ( i ) ;
}
2018-03-24 20:46:05 +00:00
# ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
bool OPNMIDIplay : : 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
}
2018-03-24 20:46:05 +00:00
# endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
2018-03-24 20:46:05 +00:00
# ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
OPNMIDIplay : : MidiEvent OPNMIDIplay : : parseEvent ( uint8_t * * pptr , uint8_t * end , int & status )
{
uint8_t * & ptr = * pptr ;
OPNMIDIplay : : 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 ;
}
2018-03-24 20:46:05 +00:00
# endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
const std : : string & OPNMIDIplay : : getErrorString ( )
{
return errorStringOut ;
}
void OPNMIDIplay : : setErrorString ( const std : : string & err )
{
errorStringOut = err ;
}
2018-03-24 20:46:05 +00:00
# ifndef OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
void OPNMIDIplay : : HandleEvent ( size_t tk , const OPNMIDIplay : : 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 ;
}
}
}
2018-03-24 20:46:05 +00:00
# endif //OPNMIDI_DISABLE_MIDI_SEQUENCER
2018-03-24 14:52:52 +00:00
2018-03-24 22:37:44 +00:00
int64_t OPNMIDIplay : : CalculateAdlChannelGoodness ( size_t c , uint16_t ins , uint16_t ) const
2018-03-24 14:52:52 +00:00
{
2018-03-24 22:37:44 +00:00
int64_t s = - ch [ c ] . koff_time_until_neglible ;
2018-03-24 14:52:52 +00:00
// Same midi-instrument = some stability
//if(c == MidCh) s += 4;
for ( OpnChannel : : users_t : : const_iterator
j = ch [ c ] . users . begin ( ) ;
j ! = ch [ c ] . users . end ( ) ;
+ + j )
{
s - = 4000 ;
if ( ! j - > second . sustained )
2018-03-24 22:37:44 +00:00
s - = j - > second . kon_time_until_neglible ;
2018-03-24 14:52:52 +00:00
else
2018-03-24 22:37:44 +00:00
s - = ( j - > second . kon_time_until_neglible / 2 ) ;
2018-03-24 14:52:52 +00:00
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
2018-03-24 22:37:44 +00:00
s + = 50 * ( int64_t ) ( k - > second . midiins / 128 ) ;
2018-03-24 14:52:52 +00:00
/*
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 < opn.NumChannels; ++c2)
// {
// if(c2 == c) continue;
// if(opn.four_op_category[c2]
// != opn.four_op_category[c]) continue;
// for(OpnChannel::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 OPNMIDIplay : : PrepareAdlChannelForNewNote ( size_t c , size_t ins )
{
if ( ch [ c ] . users . empty ( ) ) return ; // Nothing to do
//bool doing_arpeggio = false;
for ( OpnChannel : : users_t : : iterator
jnext = ch [ c ] . users . begin ( ) ;
jnext ! = ch [ c ] . users . end ( ) ;
)
{
OpnChannel : : 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 ( ) )
opn . NoteOff ( c ) ;
}
void OPNMIDIplay : : KillOrEvacuate ( size_t from_channel , OpnChannel : : users_t : : iterator j , OPNMIDIplay : : 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 < opn . 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(opn.four_op_category[c] != opn.four_op_category[from_channel])
// continue;
for ( OpnChannel : : 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 ,
( int ) i - > second . midiins , 0 , 0.0 ) ;
hooks . onNote ( hooks . onNote_userData ,
( int ) c ,
i - > second . tone ,
( int ) 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 OPNMIDIplay : : Panic ( )
{
for ( uint8_t chan = 0 ; chan < Ch . size ( ) ; chan + + )
{
for ( uint8_t note = 0 ; note < 128 ; note + + )
realTime_NoteOff ( chan , note ) ;
}
}
void OPNMIDIplay : : KillSustainingNotes ( int32_t MidCh , int32_t this_adlchn )
{
uint32_t first = 0 , last = opn . 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 ( OpnChannel : : users_t : : iterator
jnext = ch [ c ] . users . begin ( ) ;
jnext ! = ch [ c ] . users . end ( ) ;
)
{
OpnChannel : : 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 ( ) )
opn . NoteOff ( c ) ;
}
}
void OPNMIDIplay : : 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 OPNMIDIplay : : 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 OPNMIDIplay : : 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 OPNMIDIplay : : 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 OPNMIDIplay : : 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 OPNMIDIplay : : 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 < opn . 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 )
{
OpnChannel : : 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 ) ) ;
}
}
}
}
/* TODO */
//#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)
2018-03-27 00:21:32 +00:00
// std::snprintf(ToneIndication, 8, "+%-2d", ains.tone);
2018-03-24 14:52:52 +00:00
// else*/
// if(ains.tone < 128)
2018-03-27 00:21:32 +00:00
// std::snprintf(ToneIndication, 8, "=%-2d", ains.tone);
2018-03-24 14:52:52 +00:00
// else
2018-03-27 00:21:32 +00:00
// std::snprintf(ToneIndication, 8, "-%-2d", ains.tone - 128);
2018-03-24 14:52:52 +00:00
// }
// 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