11using System ;
22using System . Collections . Generic ;
33using System . Linq ;
4- using System . Text ;
4+ using System . Runtime . InteropServices ;
55using System . Threading . Tasks ;
66using Commons . Music . Midi ;
7- using Obsidian . Components . Devices . MIDI ;
87using Elements . Core ;
9- using FrooxEngine ;
108using Elements . Data ;
119
1210namespace Obsidian . Elements ;
1311
1412public struct TimestampedMidiEvent
1513{
16- public MidiEvent midiEvent ;
14+ public MidiEvent evt ;
1715 public long timestamp ;
18- public TimestampedMidiEvent ( MidiEvent _midiEvent , long _timestamp )
16+ public TimestampedMidiEvent ( MidiEvent _evt , long _timestamp )
1917 {
20- midiEvent = _midiEvent ;
18+ evt = _evt ;
2119 timestamp = _timestamp ;
2220 }
21+ public override string ToString ( )
22+ {
23+ return $ "{ timestamp } , { evt } ";
24+ }
2325}
2426
2527public interface IMidiInputListener
@@ -65,23 +67,19 @@ public class MidiInputConnection
6567
6668 public List < IMidiInputListener > Listeners = new ( ) ;
6769
68- // I am using this like a Queue so it could possibly be turned into a Queue instead...
6970 private List < TimestampedMidiEvent > _eventBuffer = new ( ) ;
7071
71- private const long MESSAGE_BUFFER_TIME_MILLISECONDS = 3 ;
72-
73- private long _lastMessageBufferStartTime = 0 ;
72+ private const long BUFFER_TIME_MILLISECONDS = 3 ;
7473
75- private int _bufferedMessagesToHandle = 0 ;
74+ private long _lastEventBufferStartTime = 0 ;
7675
7776 private const bool DEBUG = false ;
7877
7978 public void Initialize ( )
8079 {
8180 Input = null ;
8281 _eventBuffer . Clear ( ) ;
83- _lastMessageBufferStartTime = 0 ;
84- _bufferedMessagesToHandle = 0 ;
82+ _lastEventBufferStartTime = 0 ;
8583 Listeners . Clear ( ) ;
8684 }
8785
@@ -97,11 +95,9 @@ private ushort CombineBytes(byte First, byte Second)
9795
9896 private bool IsCCFineMessage ( )
9997 {
100- if ( _eventBuffer . Count == 0 ) return false ;
101- long timestamp = _eventBuffer [ 0 ] . timestamp ;
102- if ( _eventBuffer . Count >= 2
103- && _eventBuffer [ 0 ] . midiEvent . EventType == MidiEvent . CC && _eventBuffer [ 1 ] . midiEvent . EventType == MidiEvent . CC
104- && _eventBuffer [ 0 ] . midiEvent . Msb == _eventBuffer [ 1 ] . midiEvent . Msb - 32 )
98+ if ( _eventBuffer . Count < 2 ) return false ;
99+ if ( _eventBuffer [ 0 ] . evt . EventType == MidiEvent . CC && _eventBuffer [ 1 ] . evt . EventType == MidiEvent . CC
100+ && _eventBuffer [ 0 ] . evt . Msb == _eventBuffer [ 1 ] . evt . Msb - 32 )
105101 {
106102 return true ;
107103 }
@@ -121,175 +117,187 @@ private void FlushMessageBuffer()
121117
122118 while ( _eventBuffer . Count > 0 )
123119 {
124-
120+ // 14bit CC
125121 while ( IsCCFineMessage ( ) )
126122 {
127- var e1 = _eventBuffer [ 0 ] . midiEvent ;
123+ var e1 = _eventBuffer [ 0 ] ;
128124 if ( DEBUG ) UniLog . Log ( e1 . ToString ( ) ) ;
129- var e2 = _eventBuffer [ 1 ] . midiEvent ;
125+ var e2 = _eventBuffer [ 1 ] ;
130126 if ( DEBUG ) UniLog . Log ( e2 . ToString ( ) ) ;
131- var finalValue = CombineBytes ( e2 . Lsb , e1 . Lsb ) ;
127+ var finalValue = CombineBytes ( e2 . evt . Lsb , e1 . evt . Lsb ) ;
132128 if ( DEBUG ) UniLog . Log ( $ "CC fine. Value: " + finalValue . ToString ( ) ) ;
133- Listeners . ForEach ( l => l . TriggerControl ( new MIDI_CC_EventData ( e1 . Channel , e1 . Msb , finalValue , _coarse : false ) ) ) ;
129+ Listeners . ForEach ( l => l . TriggerControl ( new MIDI_CC_EventData ( e1 . evt . Channel , e1 . evt . Msb , finalValue , _coarse : false ) ) ) ;
134130 _eventBuffer . RemoveRange ( 0 , 2 ) ;
135- _bufferedMessagesToHandle -= 2 ;
136131 }
137132
138133 if ( _eventBuffer . Count == 0 ) break ;
139134
140- var e = _eventBuffer [ 0 ] . midiEvent ;
135+ var e = _eventBuffer [ 0 ] ;
141136 if ( DEBUG ) UniLog . Log ( e . ToString ( ) ) ;
142- switch ( e . EventType )
137+ switch ( _eventBuffer [ 0 ] . evt . EventType )
143138 {
144139 case MidiEvent . CC :
145140 if ( DEBUG ) UniLog . Log ( "CC" ) ;
146- Listeners . ForEach ( l => l . TriggerControl ( new MIDI_CC_EventData ( e . Channel , e . Msb , e . Lsb , _coarse : true ) ) ) ;
141+ Listeners . ForEach ( l => l . TriggerControl ( new MIDI_CC_EventData ( e . evt . Channel , e . evt . Msb , e . evt . Lsb , _coarse : true ) ) ) ;
147142 break ;
143+
148144 // Program events are buffered because they can be sent after a CC fine message for Bank Select, one of my devices sends consecutively: CC (Bank Select) -> CC (Bank Select Lsb) -> Program for some buttons
149145 case MidiEvent . Program :
150146 if ( DEBUG ) UniLog . Log ( "Program" ) ;
151- Listeners . ForEach ( l => l . TriggerProgram ( new MIDI_ProgramEventData ( e . Channel , e . Msb ) ) ) ;
147+ Listeners . ForEach ( l => l . TriggerProgram ( new MIDI_ProgramEventData ( e . evt . Channel , e . evt . Msb ) ) ) ;
152148 break ;
153149
154- // Unhandled events:
155-
156- //SysEx events are probably not worth handling
157- case MidiEvent . SysEx1 :
158- if ( DEBUG ) UniLog . Log ( "UnhandledEvent: SysEx1" ) ;
159- break ;
160- case MidiEvent . SysEx2 :
161- // Same as EndSysEx
162- if ( DEBUG ) UniLog . Log ( "UnhandledEvent: SysEx2" ) ;
163- break ;
164- case MidiEvent . MtcQuarterFrame :
165- if ( DEBUG ) UniLog . Log ( "UnhandledEvent: MtcQuarterFrame" ) ;
166- break ;
167- case MidiEvent . SongPositionPointer :
168- if ( DEBUG ) UniLog . Log ( "UnhandledEvent: SongPositionPointer" ) ;
169- break ;
170- case MidiEvent . SongSelect :
171- if ( DEBUG ) UniLog . Log ( "UnhandledEvent: SongSelect" ) ;
172- break ;
173- case MidiEvent . TuneRequest :
174- if ( DEBUG ) UniLog . Log ( "UnhandledEvent: TuneRequest" ) ;
175- break ;
176150 default :
151+ // This should never happen
152+ if ( DEBUG ) UniLog . Log ( $ "Unrecognized event type! { _eventBuffer [ 0 ] . evt . EventType } ") ;
177153 break ;
178154 }
179155 _eventBuffer . RemoveAt ( 0 ) ;
180- _bufferedMessagesToHandle -= 1 ;
181156 }
182157 if ( DEBUG ) UniLog . Log ( "Finished flushing message buffer from start time: " + batchStartTime . ToString ( ) ) ;
183- if ( _bufferedMessagesToHandle != 0 )
184- {
185- // Just in case some messages got lost somehow
186- UniLog . Warning ( "Did not handle all buffered messages! " + _bufferedMessagesToHandle . ToString ( ) ) ;
187- }
188- _bufferedMessagesToHandle = 0 ;
189158 }
190159
191160 public async void OnMessageReceived ( object sender , MidiReceivedEventArgs args )
192161 {
162+ if ( DEBUG ) UniLog . FlushEveryMessage = true ;
163+
193164 if ( DEBUG ) UniLog . Log ( $ "*** New midi message") ;
194165 if ( DEBUG ) UniLog . Log ( $ "* Received { args . Length } bytes") ;
195- if ( DEBUG ) UniLog . Log ( $ "* Timestamp : { args . Timestamp } ") ;
166+ if ( DEBUG ) UniLog . Log ( $ "* Start : { args . Start } ") ;
196167
197- var events = MidiEvent . Convert ( args . Data , args . Start , args . Length ) ;
168+ if ( DEBUG ) UniLog . Log ( $ "* Raw bytes: { string . Join ( "," , args . Data . Skip ( args . Start ) . Take ( args . Length ) . Select ( b => string . Format ( "{0:X}" , b ) ) ) } " ) ;
198169
199- if ( args . Length == 1 )
170+ long timestamp = args . Timestamp ;
171+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) ) // Timestamp is always zero on Linux with the Alsa Midi API
200172 {
201- // system realtime message, do not buffer these, execute immediately
202- if ( DEBUG ) UniLog . Log ( $ "* System realtime message") ;
203- foreach ( var e in events )
204- {
205- var str = e . ToString ( ) ;
206- if ( DEBUG ) UniLog . Log ( "* " + str ) ;
207- switch ( e . StatusByte )
208- {
209- case MidiEvent . MidiClock :
210- if ( DEBUG ) UniLog . Log ( "* MidiClock" ) ;
211- Listeners . ForEach ( l => l . TriggerMidiClock ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
212- break ;
213- case MidiEvent . MidiTick :
214- if ( DEBUG ) UniLog . Log ( "* MidiTick" ) ;
215- Listeners . ForEach ( l => l . TriggerMidiTick ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
216- break ;
217- case MidiEvent . MidiStart :
218- if ( DEBUG ) UniLog . Log ( "* MidiStart" ) ;
219- Listeners . ForEach ( l => l . TriggerMidiStart ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
220- break ;
221- case MidiEvent . MidiStop :
222- if ( DEBUG ) UniLog . Log ( "* MidiStop" ) ;
223- Listeners . ForEach ( l => l . TriggerMidiStop ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
224- break ;
225- case MidiEvent . MidiContinue :
226- if ( DEBUG ) UniLog . Log ( "* MidiContinue" ) ;
227- Listeners . ForEach ( l => l . TriggerMidiContinue ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
228- break ;
229- case MidiEvent . ActiveSense :
230- if ( DEBUG ) UniLog . Log ( "* ActiveSense" ) ;
231- Listeners . ForEach ( l => l . TriggerActiveSense ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
232- break ;
233- case MidiEvent . Reset :
234- // Same as Meta
235- if ( DEBUG ) UniLog . Log ( "* Reset" ) ;
236- Listeners . ForEach ( l => l . TriggerReset ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
237- break ;
238- }
239- }
240- return ;
173+ timestamp = DateTimeOffset . Now . ToUnixTimeMilliseconds ( ) ;
241174 }
242175
243- // other types of messages: channel message (voice or channel mode), system common message, system exclusive message
176+ if ( DEBUG ) UniLog . Log ( $ "* Timestamp: { timestamp } ") ;
177+
178+ var events = MidiEvent . Convert ( args . Data , args . Start , args . Length ) ;
179+
180+ // DO NOT CALL events.Count() IT BREAKS THE RUNNING STATUS ON LINUX
181+
244182 foreach ( var e in events )
245183 {
246- var str = e . ToString ( ) ;
247- if ( DEBUG ) UniLog . Log ( "* " + str ) ;
184+ if ( DEBUG )
185+ {
186+ var str = e . ToString ( ) ;
187+ UniLog . Log ( "* " + str ) ;
188+ }
248189
190+ bool shouldBuffer = false ;
249191 switch ( e . EventType )
250192 {
193+ // System realtime
194+ case MidiEvent . MidiClock :
195+ if ( DEBUG ) UniLog . Log ( "* MidiClock" ) ;
196+ Listeners . ForEach ( l => l . TriggerMidiClock ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
197+ break ;
198+ case MidiEvent . MidiTick :
199+ if ( DEBUG ) UniLog . Log ( "* MidiTick" ) ;
200+ Listeners . ForEach ( l => l . TriggerMidiTick ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
201+ break ;
202+ case MidiEvent . MidiStart :
203+ if ( DEBUG ) UniLog . Log ( "* MidiStart" ) ;
204+ Listeners . ForEach ( l => l . TriggerMidiStart ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
205+ break ;
206+ case MidiEvent . MidiStop :
207+ if ( DEBUG ) UniLog . Log ( "* MidiStop" ) ;
208+ Listeners . ForEach ( l => l . TriggerMidiStop ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
209+ break ;
210+ case MidiEvent . MidiContinue :
211+ if ( DEBUG ) UniLog . Log ( "* MidiContinue" ) ;
212+ Listeners . ForEach ( l => l . TriggerMidiContinue ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
213+ break ;
214+ case MidiEvent . ActiveSense :
215+ if ( DEBUG ) UniLog . Log ( "* ActiveSense" ) ;
216+ Listeners . ForEach ( l => l . TriggerActiveSense ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
217+ break ;
218+ case MidiEvent . Reset :
219+ // Same as Meta
220+ if ( DEBUG ) UniLog . Log ( "* Reset" ) ;
221+ Listeners . ForEach ( l => l . TriggerReset ( new MIDI_SystemRealtimeEventData ( ) ) ) ;
222+ break ;
223+
224+ // other types of messages: channel message (voice or channel mode), system common message, system exclusive message
251225 case MidiEvent . NoteOn :
252226 if ( DEBUG ) UniLog . Log ( "* NoteOn" ) ;
253227 if ( e . Lsb == 0 )
254228 {
255229 if ( DEBUG ) UniLog . Log ( "* Zero velocity, so it's actually a NoteOff" ) ;
256230 Listeners . ForEach ( l => l . TriggerNoteOff ( new MIDI_NoteEventData ( e . Channel , e . Msb , e . Lsb ) ) ) ;
257- return ;
231+ break ;
258232 }
259233 Listeners . ForEach ( l => l . TriggerNoteOn ( new MIDI_NoteEventData ( e . Channel , e . Msb , e . Lsb ) ) ) ;
260- return ;
234+ break ;
261235 case MidiEvent . NoteOff :
262236 if ( DEBUG ) UniLog . Log ( "* NoteOff" ) ;
263237 Listeners . ForEach ( l => l . TriggerNoteOff ( new MIDI_NoteEventData ( e . Channel , e . Msb , e . Lsb ) ) ) ;
264- return ;
238+ break ;
265239 case MidiEvent . CAf :
266240 if ( DEBUG ) UniLog . Log ( "* CAf" ) ;
267241 Listeners . ForEach ( l => l . TriggerChannelAftertouch ( new MIDI_ChannelAftertouchEventData ( e . Channel , e . Msb ) ) ) ;
268- return ;
242+ break ;
269243 case MidiEvent . Pitch :
270244 if ( DEBUG ) UniLog . Log ( "* Pitch" ) ;
271245 Listeners . ForEach ( l => l . TriggerPitchWheel ( new MIDI_PitchWheelEventData ( e . Channel , CombineBytes ( e . Msb , e . Lsb ) ) ) ) ;
272- return ;
246+ break ;
273247 case MidiEvent . PAf :
274248 if ( DEBUG ) UniLog . Log ( "* PAf" ) ;
275249 Listeners . ForEach ( l => l . TriggerPolyphonicAftertouch ( new MIDI_PolyphonicAftertouchEventData ( e . Channel , e . Msb , e . Lsb ) ) ) ;
276- return ;
250+ break ;
251+
252+ // Unhandled events:
253+
254+ case MidiEvent . SysEx1 :
255+ if ( DEBUG ) UniLog . Log ( "UnhandledEvent: SysEx1" ) ;
256+ break ;
257+ case MidiEvent . SysEx2 :
258+ // AKA EndSysEx
259+ if ( DEBUG ) UniLog . Log ( "UnhandledEvent: SysEx2" ) ;
260+ break ;
261+ case MidiEvent . MtcQuarterFrame :
262+ if ( DEBUG ) UniLog . Log ( "UnhandledEvent: MtcQuarterFrame" ) ;
263+ break ;
264+ case MidiEvent . SongPositionPointer :
265+ if ( DEBUG ) UniLog . Log ( "UnhandledEvent: SongPositionPointer" ) ;
266+ break ;
267+ case MidiEvent . SongSelect :
268+ if ( DEBUG ) UniLog . Log ( "UnhandledEvent: SongSelect" ) ;
269+ break ;
270+ case MidiEvent . TuneRequest :
271+ if ( DEBUG ) UniLog . Log ( "UnhandledEvent: TuneRequest" ) ;
272+ break ;
273+
277274 default :
275+ shouldBuffer = true ;
278276 break ;
279277 }
280278
281- // buffer CC messages because consecutive ones may need to be combined
282- // also buffer Program messages
283- _eventBuffer . Add ( new TimestampedMidiEvent ( e , args . Timestamp ) ) ;
284- _bufferedMessagesToHandle += 1 ;
279+ if ( shouldBuffer )
280+ {
281+ // buffer CC messages because consecutive ones may need to be combined
282+ // also buffer Program messages
283+ lock ( _eventBuffer )
284+ {
285+ _eventBuffer . Add ( new TimestampedMidiEvent ( e , timestamp ) ) ;
286+ }
287+ }
285288 }
286289
287- if ( events . Count ( ) > 0 && args . Timestamp - _lastMessageBufferStartTime > MESSAGE_BUFFER_TIME_MILLISECONDS )
290+ int count ;
291+ lock ( _eventBuffer )
292+ count = _eventBuffer . Count ;
293+
294+ if ( count > 0 && timestamp - _lastEventBufferStartTime > BUFFER_TIME_MILLISECONDS )
288295 {
289- _lastMessageBufferStartTime = args . Timestamp ;
290- if ( DEBUG ) UniLog . Log ( "* New message batch created: " + args . Timestamp . ToString ( ) ) ;
291- await Task . Delay ( ( int ) MESSAGE_BUFFER_TIME_MILLISECONDS ) ;
292- FlushMessageBuffer ( ) ;
296+ _lastEventBufferStartTime = timestamp ;
297+ if ( DEBUG ) UniLog . Log ( "* New message batch created: " + timestamp . ToString ( ) ) ;
298+ await Task . Delay ( ( int ) BUFFER_TIME_MILLISECONDS ) ;
299+ lock ( _eventBuffer )
300+ FlushMessageBuffer ( ) ;
293301 }
294302 }
295303}
0 commit comments