-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmodMusicConverter.vb
740 lines (661 loc) · 34.4 KB
/
modMusicConverter.vb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
Imports System.Linq
Imports NAudio
'https://github.com/TheBoxyBear/ChartTools/blob/stable/docs/FileFormats/midi.md
Public Class clsNoteInfo
Public noteNumber As Integer
Public noteValue As Integer
Public Sub New(_noteNumber As Integer, _noteValue As Integer)
noteNumber = _noteNumber
noteValue = _noteValue
End Sub
Public Overrides Function ToString() As String
Return String.Format("({0}, {1})", noteNumber, noteValue)
End Function
End Class
Friend Class clsNoteAction
Implements IComparable(Of clsNoteAction)
Public controller As Byte
Public msOffset As Integer
Public noteMask As Integer
Public press As Boolean
Public comment As String
Public Sub New(_controller As Byte, _note As Integer, _msOffset As Integer, _press As Boolean, _comment As String)
controller = _controller
noteMask = _note
msOffset = _msOffset
press = _press
comment = _comment
End Sub
Public Function CompareTo(other As clsNoteAction) As Integer Implements System.IComparable(Of clsNoteAction).CompareTo
If msOffset < other.msOffset Then Return -1
If msOffset > other.msOffset Then Return 1
If controller.CompareTo(other.controller) <> 0 Then Return controller.CompareTo(other.controller)
If press <> other.press Then Return IIf(press, -1, 1)
If noteMask < other.noteMask Then Return -1
If noteMask > other.noteMask Then Return 1
Return 0
End Function
End Class
Friend Class clsSectionEntry
Public tickStart As Integer
Public tickEnd As Integer
Public name As String
Public Sub New(_tickstart As Integer, _name As String)
tickStart = _tickstart
name = _name
End Sub
Public Sub New(_tickstart As Integer, _name As String, prevSection As clsSectionEntry)
tickStart = _tickstart
name = _name
tickEnd = Integer.MaxValue
If Not prevSection Is Nothing Then
prevSection.tickEnd = tickStart
End If
End Sub
End Class
Friend Class clsTempoEntry
Public tickStart As Integer
Public tickEnd As Integer
Public usStart As Decimal
Public rate As Decimal
Public Sub New(newtickstart As Integer, newrate As Integer)
tickStart = newtickstart
rate = newrate
End Sub
Public Sub New(newtickstart As Integer, newrate As Decimal, prevTempo As clsTempoEntry)
tickStart = newtickstart
rate = newrate
tickEnd = Integer.MaxValue
If prevTempo Is Nothing Then
usStart = 0
Else
Dim prevTicks As Integer = tickStart - prevTempo.tickStart
Dim prevUS As Decimal = prevTicks * prevTempo.rate
prevTempo.tickEnd = tickStart
usStart = prevTempo.usStart + prevUS
End If
End Sub
End Class
Friend Class clsNoteEntry
Implements IComparable(Of clsNoteEntry)
Public controller As Byte
Public tickOffset As Integer
Public tickDuration As Integer
Public msOffset As Integer
Public msDuration As Integer
Public noteInfo As clsNoteInfo
Public noteMask As Integer
Public hopo As Boolean = False
Public solo As Boolean = False
Public comment As String
Public eventIndex As Integer
Public section As String
Public Function clone() As clsNoteEntry
Dim n As New clsNoteEntry()
n.controller = controller
n.tickOffset = tickOffset
n.tickDuration = tickDuration
n.msOffset = msOffset
n.msDuration = msDuration
n.noteInfo = noteInfo
n.noteMask = noteMask
n.hopo = hopo
n.solo = solo
n.comment = comment
n.eventIndex = eventIndex
n.section = section
Return n
End Function
Private Sub New()
End Sub
Private Function formatMS(ms As Integer) As String
Dim t As New Date(10000 * CLng(ms))
Return t.ToString("mm:ss." & t.Millisecond.ToString.PadLeft(3, "0"))
End Function
Public Sub New(_controller As String, _track As clsTrack, _level As clsLevel, _nev As Midi.NoteOnEvent, _solo As Boolean, _hopo As Boolean, _eventIndex As Integer, _section As String, _powergig As Boolean)
controller = _controller
tickOffset = _nev.AbsoluteTime
tickDuration = _nev.NoteLength
solo = _solo
hopo = _hopo
If _powergig Then
Select Case _nev.NoteNumber
Case 60
noteInfo = New clsNoteInfo(_nev.NoteNumber, 7)
Case 62
noteInfo = New clsNoteInfo(_nev.NoteNumber, 0)
Case 64
noteInfo = New clsNoteInfo(_nev.NoteNumber, 1)
Case 65
noteInfo = New clsNoteInfo(_nev.NoteNumber, 2)
Case 67
noteInfo = New clsNoteInfo(_nev.NoteNumber, 3)
Case 69
noteInfo = New clsNoteInfo(_nev.NoteNumber, 4)
End Select
Else
noteInfo = New clsNoteInfo(_nev.NoteNumber, _level.noteValue(_track.name, _nev.NoteNumber))
End If
Select Case noteInfo.noteValue
Case 0
noteMask = _track.noteGreen(hopo, solo)
' Console.WriteLine(String.Format("{0} Green: ({1} | {2} | NM: {3})", _eventIndex, noteInfo.ToString(), _section, noteMask.ToString()))
Case 1
noteMask = _track.noteRed(hopo, solo)
' Console.WriteLine(String.Format("{0} Red: ({1} | {2} | NM: {3})", _eventIndex, noteInfo.ToString(), _section, noteMask.ToString()))
Case 2
noteMask = _track.noteYellow(hopo, solo)
' Console.WriteLine(String.Format("{0} Yellow: ({1} | {2} | NM: {3})", _eventIndex, noteInfo.ToString(), _section, noteMask.ToString()))
Case 3
noteMask = _track.noteBlue(hopo, solo)
' Console.WriteLine(String.Format("{0} Blue: ({1} | {2} | NM: {3})", _eventIndex, noteInfo.ToString(), _section, noteMask.ToString()))
Case 4
noteMask = _track.noteOrange(hopo, solo)
' Console.WriteLine(String.Format("{0} Orange: ({1} | {2} | NM: {3})", _eventIndex, noteInfo.ToString(), _section, noteMask.ToString()))
Case 5
noteMask = _track.noteGreen(hopo, solo, True)
' Console.WriteLine(String.Format("{0} Green (Fifth): ({1} | {2} | NM: {3})", _eventIndex, noteInfo.ToString(), _section, noteMask.ToString()))
Case 6
noteMask = _track.noteGreen(hopo, solo, False, True)
' Console.WriteLine(String.Format("{0} Green (Sixth): ({1} | {2} | NM: {3})", _eventIndex, noteInfo.ToString(), _section, noteMask.ToString()))
Case 7
noteMask = _track.noteWhite(hopo, solo)
End Select
eventIndex = _eventIndex
section = _section
End Sub
Public Sub setComment()
comment = section & "[" & eventIndex & "] " & formatMS(msOffset)
End Sub
Public Sub merge(ne As clsNoteEntry)
noteMask = noteMask Or ne.noteMask
hopo = False
End Sub
Public Function CompareTo(other As clsNoteEntry) As Integer Implements System.IComparable(Of clsNoteEntry).CompareTo
If tickOffset < other.tickOffset Then Return -1
If tickOffset > other.tickOffset Then Return 1
If controller.CompareTo(other.controller) <> 0 Then Return controller.CompareTo(other.controller)
If noteMask < other.noteMask Then Return -1
If noteMask > other.noteMask Then Return 1
Return 0
End Function
End Class
Module modMusicConverter
Private Function checkQ(nevTime As Long, q As Queue(Of Midi.NoteOnEvent)) As Boolean
If q.Count = 0 Then Return False
Dim startTime As Long = q.Peek.AbsoluteTime
If nevTime < startTime Then Return False
Dim endTime As Long = startTime + q.Peek.NoteLength
If nevTime <= startTime + q.Peek.NoteLength Then Return True
q.Dequeue()
Return checkQ(nevTime, q)
End Function
Private Function scanBeatQ(nevTime As Long, q As Queue(Of trainerInfo)) As trainerInfo
If q.Count = 0 Then Return Nothing
Dim startTime As Long = q.Peek.startTime
If nevTime < startTime Then Return Nothing
If nevTime <= q.Peek.endTime Then Return q.Peek
q.Dequeue()
Return scanBeatQ(nevTime, q)
End Function
Private Class trainerInfo
Public number As Integer
Public startTime As Long
Public endTime As Long
Public bpm As Integer
Public tempo As clsTempoEntry
Public notes As New List(Of clsNoteEntry)
Public Sub New(_number As Integer, _startTime As Long, _endTime As Long, _bpm As Integer, _tempo As clsTempoEntry)
number = _number
startTime = _startTime
endTime = _endTime
bpm = _bpm
tempo = _tempo
End Sub
End Class
Public Sub getTrainers(controller As Byte, track As clsTrack, evtTrackIdx As Integer)
Dim trainerList As New List(Of trainerInfo)
Dim trainerQ As New Queue(Of trainerInfo)
Dim mf As Midi.MidiFile = track.mf
If track.name = "BASS" Then track = New clsTrack(track.mf, track.index, "DRUMS", track._song)
Dim baseTempo As clsTempoEntry = Nothing
For Each mev As Midi.MidiEvent In mf.Events(0)
If mev.CommandCode = Midi.MidiCommandCode.MetaEvent AndAlso CType(mev, Midi.MetaEvent).MetaEventType = Midi.MetaEventType.SetTempo Then
Dim tev As Midi.TempoEvent = CType(mev, Midi.TempoEvent)
'Debug.Print(tev.ToString)
If baseTempo Is Nothing Then
baseTempo = New clsTempoEntry(tev.AbsoluteTime, tev.MicrosecondsPerQuarterNote / mf.DeltaTicksPerQuarterNote, Nothing)
Else
'MsgBox("too many tempo entries")
'Exit Sub
End If
End If
Next
Dim beatNumber As Integer = -1
Dim startTime As Integer = -1
Dim bpm As Integer = 60
Dim reTrainer As New Text.RegularExpressions.Regex("^\[start_drum_trainer_beat drum_trainer_beat_(?<number>\d+)\]$")
Dim reBPM As New Text.RegularExpressions.Regex("^\[bpm (?<bpm>\d+)\]$")
For Each mev As Midi.MidiEvent In mf.Events(evtTrackIdx)
If mev.CommandCode = Midi.MidiCommandCode.MetaEvent AndAlso CType(mev, Midi.MetaEvent).MetaEventType = Midi.MetaEventType.TextEvent Then
Dim tev As Midi.TextEvent = mev
'Debug.Print(tev.AbsoluteTime & ":" & tev.Text)
If beatNumber = -1 Then
Dim match As Text.RegularExpressions.Match = reTrainer.Match(tev.Text)
If match.Success Then
beatNumber = match.Groups("number").Value
startTime = tev.AbsoluteTime
bpm = 60
End If
Else
If tev.Text = "[end_drum_trainer_beat]" Then
'Debug.Print(beatNumber & "," & startTime & "," & tev.AbsoluteTime & "," & bpm)
Dim beatTempo As New clsTempoEntry(baseTempo.tickStart, (60000000 / CDec(bpm)) / mf.DeltaTicksPerQuarterNote)
Dim ti As New trainerInfo(beatNumber, startTime, tev.AbsoluteTime, bpm, beatTempo)
trainerQ.Enqueue(ti)
trainerList.Add(ti)
beatNumber = -1
startTime = -1
Else
Dim match As Text.RegularExpressions.Match = reBPM.Match(tev.Text)
If match.Success Then bpm = match.Groups("bpm").Value
End If
End If
End If
Next
Dim level As New clsLevel(enumLevel.lvlEasy)
Dim baseNote As Integer = level.baseNote
Dim curBeat As trainerInfo = Nothing
For i = 0 To mf.Events(track.index).Count - 1
Dim mev As NAudio.Midi.MidiEvent = mf.Events(track.index)(i)
Select Case mev.CommandCode
Case NAudio.Midi.MidiCommandCode.NoteOn
curBeat = scanBeatQ(mev.AbsoluteTime, trainerQ)
Dim nev As NAudio.Midi.NoteOnEvent = CType(mev, NAudio.Midi.NoteOnEvent)
If Not curBeat Is Nothing Then
'If nev.Velocity > 0 Then Debug.Print(curBeat.number & "," & mev.AbsoluteTime & "," & nev.NoteNumber)
If nev.NoteNumber >= baseNote And nev.NoteNumber <= baseNote + 4 And nev.Velocity > 0 Then
Dim ne As clsNoteEntry = Nothing
ne = New clsNoteEntry(controller, track, level, nev, False, False, i, vbNullString, False)
curBeat.notes.Add(ne)
'If ne.hopo Then Debug.Print(tmpNotes.Count & " : " & nev.AbsoluteTime & " : " & ne.tickDuration)
End If
Else
'Debug.Print("0," & mev.AbsoluteTime & "," & nev.NoteNumber)
End If
End Select
Next
For Each ti As trainerInfo In trainerList
If ti.notes.Count = 0 Then
MsgBox("Some beats had no notes. You may want to try selecting the [BASS] track." & vbCrLf & "The drum trainer notes tend to be stored in the bass track. Yes, it makes no sense.")
Exit Sub
End If
ti.notes.Sort()
For Each ne As clsNoteEntry In ti.notes
ne.msOffset = track._game.dilation * ti.tempo.rate * (ne.tickOffset - ti.startTime) / 1000
ne.msDuration = ne.tickDuration * ti.tempo.rate / 1000
ne.msDuration = ne.msDuration * track._game.dilation - track._game.truncation
If ne.msDuration < track._game.minimumDuration Then ne.msDuration = track._game.minimumDuration
ne.setComment()
Next
Dim mergedNotes As New List(Of clsNoteEntry)
Dim lastNote As clsNoteEntry = ti.notes(0)
mergedNotes.Add(lastNote)
For i = 1 To ti.notes.Count - 1
If ti.notes(i).tickOffset = lastNote.tickOffset Then
lastNote.merge(ti.notes(i))
Else
mergedNotes.Add(ti.notes(i))
lastNote = ti.notes(i)
End If
Next
Dim game As clsRhythmGame = track._game
Dim noteActions As New List(Of clsNoteAction)
For Each ne As clsNoteEntry In mergedNotes
noteActions.Add(New clsNoteAction(ne.controller, ne.noteMask, ne.msOffset + game.loadTime, True, ne.comment))
noteActions.Add(New clsNoteAction(ne.controller, ne.noteMask, ne.msOffset + ne.msDuration + game.loadTime, False, ne.comment))
Next
noteActions.Sort()
Dim actions As New List(Of clsAction)
Dim curOffset As Integer = 0
Dim a As clsAction
Dim startMS As Integer = Integer.MaxValue
Dim startController As String = vbNullString
Dim endMS As Integer = 0
If noteActions.Count > 0 Then
startMS = noteActions(0).msOffset
endMS = noteActions(noteActions.Count - 1).msOffset
startController = noteActions(0).controller
End If
Dim endSpan As DateTime = (New DateTime).AddMilliseconds(endMS)
Dim info As String = "[" & startController & "] starts at " & ((startMS - game.loadTime) / 1000) & "s (of " & endSpan.ToString("m:ss") & ")"
Dim curmask As Integer = 0
For i As Integer = 0 To noteActions.Count - 1
With noteActions(i)
Dim notemask As Integer = .noteMask And &HFFFF
Dim LT As Integer = IIf(.noteMask And &H10000, 255, -1)
Dim RT As Integer = IIf(.noteMask And &H20000, 255, -1)
If .msOffset > curOffset Then
If curOffset > 0 Then
a = New clsActionWait(.msOffset - curOffset, Nothing)
a.index = actions.Count
actions.Add(a)
End If
curOffset = .msOffset
End If
If .press Then
curmask = curmask Or .noteMask
a = New clsActionHold(.controller, notemask, LT, RT, New Point(-32768, -32768), New Point(-32768, -32768), Nothing)
a.comment = .comment
a.index = actions.Count
actions.Add(a)
Else
curmask = curmask And Not .noteMask
a = New clsActionRelease(.controller, notemask, LT, RT, New Point(-32768, -32768), New Point(-32768, -32768), Nothing)
a.comment = .comment
a.index = actions.Count
actions.Add(a)
End If
'End If
End With
Next
lastNote = mergedNotes(mergedNotes.Count - 1)
Dim beatLengthMS As Integer = (ti.endTime - ti.startTime) * track._game.dilation * ti.tempo.rate / 1000
Dim endWaitMS As Integer = beatLengthMS - (lastNote.msOffset + lastNote.msDuration)
If endWaitMS > 0 Then
a = New clsActionWait(endWaitMS, Nothing)
a.index = actions.Count
actions.Add(a)
End If
'a = New clsActionPress(1, clsController.XBButtons.btnGuide, -1, -1, New Point(-32768, -32768), New Point(-32768, -32768), 500, 500, 1, Nothing)
'a.index = actions.Count
'actions.Add(a)
Dim path As String = track._song.fi.FullName
path = IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location) & "\scripts\" & game.code & "-" & IO.Path.GetFileNameWithoutExtension(path) & "-" & ti.number & ".axb"
saveTrainer(game.name, "Drum Trainer [" & track._song.name & "] #" & ti.number, info, actions, path)
a = New clsActionLoop(actions(0), 50, Nothing)
a.index = actions.Count
actions.Add(a)
path = track._song.fi.FullName
path = IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location) & "\scripts\" & game.code & "-" & IO.Path.GetFileNameWithoutExtension(path) & "-" & ti.number & "_loop.axb"
saveTrainer(game.name, "Drum Trainer LOOP [" & track._song.name & "] #" & ti.number, info, actions, path)
Next
MsgBox("Scripts saved in " & IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location) & "\scripts")
End Sub
Public Sub saveTrainer(gameName As String, scriptTitle As String, description As String, actions As List(Of clsAction), path As String)
Dim doc As New Xml.XmlDocument
Dim root As Xml.XmlElement = doc.CreateElement("XBScript")
doc.AppendChild(root)
Dim desc As Xml.XmlElement = doc.CreateElement("Information")
root.AppendChild(desc)
desc.AppendChild(doc.CreateElement("Game")).InnerText = gameName
desc.AppendChild(doc.CreateElement("Title")).InnerText = scriptTitle
desc.AppendChild(doc.CreateElement("Description")).InnerText = description
desc.AppendChild(doc.CreateElement("Version")).InnerText = 2
Dim agsNode As Xml.XmlElement = doc.CreateElement("ActionGroups")
root.AppendChild(agsNode)
Dim agNode As Xml.XmlElement = doc.CreateElement("ActionGroup")
agsNode.AppendChild(agNode)
agNode.AppendChild(doc.CreateElement("Name")).InnerText = "[Main]"
For Each action As clsAction In actions
agNode.AppendChild(action.toXML(doc))
Next
Dim ws As New Xml.XmlWriterSettings()
ws.Indent = True
Dim w As Xml.XmlWriter = Xml.XmlWriter.Create(path, ws)
doc.WriteTo(w)
w.Close()
w.Dispose()
End Sub
Public Function getNotes(controller As Byte, Track As clsTrack, Level As clsLevel, HopoThreshold As Integer) As List(Of clsNoteEntry)
Dim baseNote As Integer = Level.baseNote
Dim mf As Midi.MidiFile = Track.mf
Dim tempos As New List(Of clsTempoEntry)
Dim tpq As Integer = mf.DeltaTicksPerQuarterNote
Dim prevTempo As clsTempoEntry = Nothing
Dim sections As New List(Of clsSectionEntry)
Dim evtTrack As Integer = -1
For i As Integer = 1 To mf.Tracks - 1
Dim mev As Midi.MidiEvent = mf.Events(i)(0)
If mev.CommandCode = Midi.MidiCommandCode.MetaEvent _
AndAlso CType(mev, Midi.MetaEvent).MetaEventType = Midi.MetaEventType.SequenceTrackName _
AndAlso CType(mev, Midi.TextEvent).Text = "EVENTS" Then
evtTrack = i
Exit For
End If
Next
Dim prevSection As clsSectionEntry = Nothing
If evtTrack > 0 Then
For i As Integer = 0 To mf.Events(evtTrack).Count - 1
Dim mev As Midi.MidiEvent = mf.Events(evtTrack)(i)
If mev.CommandCode = Midi.MidiCommandCode.MetaEvent _
AndAlso CType(mev, Midi.MetaEvent).MetaEventType = Midi.MetaEventType.TextEvent Then
Dim tev As Midi.TextEvent = CType(mev, Midi.TextEvent)
If tev.Text = "[drum_trainer_begin]" Then
Select Case MsgBox("This looks like a drum trainer track, do you want to create a file for each beat?", MsgBoxStyle.YesNoCancel)
Case MsgBoxResult.Yes
getTrainers(controller, Track, evtTrack)
Return Nothing
Case MsgBoxResult.No
Exit For
Case MsgBoxResult.Cancel
Return Nothing
End Select
End If
If tev.Text.StartsWith("[section ") Then
Dim sectName As String = CType(mev, Midi.TextEvent).Text.Substring(9)
sectName = sectName.Substring(0, sectName.Length - 1)
Dim section As clsSectionEntry = New clsSectionEntry(mev.AbsoluteTime, sectName, prevSection)
sections.Add(section)
prevSection = section
End If
End If
Next
End If
For Each mev As Midi.MidiEvent In mf.Events(0)
If mev.CommandCode = Midi.MidiCommandCode.MetaEvent AndAlso CType(mev, Midi.MetaEvent).MetaEventType = Midi.MetaEventType.SetTempo Then
Dim tev As Midi.TempoEvent = CType(mev, Midi.TempoEvent)
Dim tempo As New clsTempoEntry(tev.AbsoluteTime, tev.MicrosecondsPerQuarterNote / mf.DeltaTicksPerQuarterNote, prevTempo)
tempos.Add(tempo)
prevTempo = tempo
End If
Next
If sections.Count = 0 Then
sections.Add(New clsSectionEntry(0, "Full"))
sections(0).tickEnd = Integer.MaxValue
End If
Dim soloQ As New Queue(Of Midi.NoteOnEvent)
Dim forceHOPOQ As New Queue(Of Midi.NoteOnEvent)
Dim forceStrumQ As New Queue(Of Midi.NoteOnEvent)
Select Case Track.name
Case "GUITAR", "BASS"
For i As Integer = 0 To mf.Events(Track.index).Count - 1
Dim mev As Midi.MidiEvent = mf.Events(Track.index)(i)
If mev.CommandCode = Midi.MidiCommandCode.NoteOn Then
Dim nev As Midi.NoteOnEvent = mev
If nev.Velocity > 0 Then
Select Case nev.NoteNumber
Case 103
soloQ.Enqueue(nev)
Case baseNote + 5
forceHOPOQ.Enqueue(nev)
Case baseNote + 6
forceStrumQ.Enqueue(nev)
End Select
End If
End If
Next
End Select
Dim nevGroups As New List(Of List(Of Tuple(Of Integer, Midi.NoteOnEvent)))
Dim lastTime As Long = 0
Dim curGroup As List(Of Tuple(Of Integer, Midi.NoteOnEvent)) = Nothing
For i As Integer = 0 To mf.Events(Track.index).Count - 1
Dim mev As Midi.MidiEvent = mf.Events(Track.index)(i)
Select Case mev.CommandCode
Case Midi.MidiCommandCode.NoteOn
Dim nev As Midi.NoteOnEvent = CType(mev, Midi.NoteOnEvent)
' Console.WriteLine(String.Format("Index: {0} | NoteNumber: {1} | Time: {2} | Velocity: {3}", i, nev.NoteNumber, nev.AbsoluteTime, nev.Velocity))
If nev.NoteNumber >= baseNote And nev.NoteNumber <= baseNote + Level.maxNote(Track.name) And nev.Velocity > 0 Then
If nev.AbsoluteTime <> lastTime Then
lastTime = nev.AbsoluteTime
curGroup = New List(Of Tuple(Of Integer, Midi.NoteOnEvent))
nevGroups.Add(curGroup)
End If
curGroup.Add(New Tuple(Of Integer, Midi.NoteOnEvent)(i, nev))
End If
End Select
Next
' If Not lastnote Is Nothing AndAlso tickOffset - lastnote.tickOffset < dtpqn / 3 AndAlso tickOffset <> lastnote.tickOffset Then hopo = lastnote.noteValue <> noteValue
' hopo = False
Dim tmpNotes As New List(Of clsNoteEntry)
Dim curSection As Integer = 0
Dim inSolo As Boolean = False
Dim lastGroup As List(Of Tuple(Of Integer, Midi.NoteOnEvent)) = Nothing
For Each nevGroup As List(Of Tuple(Of Integer, Midi.NoteOnEvent)) In nevGroups
Dim nevTime As Long = nevGroup(0).Item2.AbsoluteTime
While nevTime >= sections(curSection).tickEnd
curSection += 1
End While
inSolo = checkQ(nevTime, soloQ)
If Track.hopo Then
Dim doHopo As Boolean = False
If lastGroup Is Nothing OrElse checkQ(nevTime, forceStrumQ) Then
doHopo = False
Else
If checkQ(nevTime, forceHOPOQ) Then
doHopo = True
Else
If nevTime - lastGroup(0).Item2.AbsoluteTime <= HopoThreshold Then
If nevGroup.Count = 1 Then
If Not lastGroup.Select(Function(x) x.Item2.NoteNumber).Contains(nevGroup(0).Item2.NoteNumber) Then
doHopo = True
End If
End If
End If
End If
End If
For Each nevTup As Tuple(Of Integer, Midi.NoteOnEvent) In nevGroup
Dim ne As clsNoteEntry = Nothing
ne = New clsNoteEntry(controller, Track, Level, nevTup.Item2, inSolo, doHopo, nevTup.Item1, sections(curSection).name & "-" & nevTup.Item1, False)
tmpNotes.Add(ne)
Next
Else
For Each nevTup As Tuple(Of Integer, Midi.NoteOnEvent) In nevGroup
Dim ne As clsNoteEntry = Nothing
ne = New clsNoteEntry(controller, Track, Level, nevTup.Item2, inSolo, False, nevTup.Item1, sections(curSection).name & "-" & nevTup.Item1, False)
tmpNotes.Add(ne)
Next
End If
lastGroup = nevGroup
Next
tmpNotes.Sort()
Dim t As Integer = 0
For Each ne As clsNoteEntry In tmpNotes
While ne.tickOffset >= tempos(t).tickEnd
t = t + 1
End While
ne.msOffset = Track._game.dilation * (tempos(t).usStart + tempos(t).rate * (ne.tickOffset - tempos(t).tickStart)) / 1000
If ne.tickDuration + ne.tickOffset <= tempos(t).tickEnd Then
ne.msDuration = ne.tickDuration * tempos(t).rate / 1000
Else
'If ne.tickDuration + ne.tickOffset > tempos(t + 1).tickEnd Then Stop
ne.msDuration = (tempos(t).tickEnd - ne.tickOffset) * tempos(t).rate / 1000 + (ne.tickDuration + ne.tickOffset - tempos(t + 1).tickStart) * tempos(t + 1).rate / 1000
End If
ne.msDuration = ne.msDuration * Track._game.dilation - Track._game.truncation
If ne.msDuration < Track._game.minimumDuration Then ne.msDuration = Track._game.minimumDuration
ne.setComment()
Next
Dim strum As Integer = Track._game.strum
Dim tmpStrums As New List(Of clsNoteEntry)
For Each ne As clsNoteEntry In tmpNotes
If ne.noteMask And strum And ne.msDuration > Track._game.minimumDuration Then
Dim neStrum As clsNoteEntry = ne.clone
ne.noteMask = ne.noteMask And (Not strum)
ne.comment = "note only"
neStrum.noteMask = strum
neStrum.msDuration = Track._game.minimumDuration
neStrum.comment = "strum only"
tmpStrums.Add(neStrum)
End If
Next
tmpNotes.AddRange(tmpStrums)
tmpNotes.Sort()
Return tmpNotes
End Function
Public Function getNotesPG(controller As Byte, Track As clsTrack, Level As clsLevel, HopoThreshold As Integer) As List(Of clsNoteEntry)
Dim baseNote As Integer = Level.baseNote
Dim mf As Midi.MidiFile = Track.mf
Dim tempos As New List(Of clsTempoEntry)
Dim tpq As Integer = mf.DeltaTicksPerQuarterNote
Dim prevTempo As clsTempoEntry = Nothing
Dim evtTrack As Integer = -1
For i As Integer = 1 To mf.Tracks - 1
Dim mev As Midi.MidiEvent = mf.Events(i)(0)
If mev.CommandCode = Midi.MidiCommandCode.MetaEvent _
AndAlso CType(mev, Midi.MetaEvent).MetaEventType = Midi.MetaEventType.SequenceTrackName _
AndAlso CType(mev, Midi.TextEvent).Text = "EVENTS" Then
evtTrack = i
Exit For
End If
Next
For Each mev As Midi.MidiEvent In mf.Events(0)
If mev.CommandCode = Midi.MidiCommandCode.MetaEvent AndAlso CType(mev, Midi.MetaEvent).MetaEventType = Midi.MetaEventType.SetTempo Then
Dim tev As Midi.TempoEvent = CType(mev, Midi.TempoEvent)
Dim tempo As New clsTempoEntry(tev.AbsoluteTime, tev.MicrosecondsPerQuarterNote / mf.DeltaTicksPerQuarterNote, prevTempo)
tempos.Add(tempo)
prevTempo = tempo
End If
Next
Dim nevGroups As New List(Of List(Of Tuple(Of Integer, Midi.NoteOnEvent)))
Dim lastTime As Long = 0
Dim curGroup As List(Of Tuple(Of Integer, Midi.NoteOnEvent)) = Nothing
For i As Integer = 0 To mf.Events(Track.index).Count - 1
Dim mev As Midi.MidiEvent = mf.Events(Track.index)(i)
Select Case mev.CommandCode
Case Midi.MidiCommandCode.NoteOn
Dim nev As Midi.NoteOnEvent = CType(mev, Midi.NoteOnEvent)
' Console.WriteLine(String.Format("Index: {0} | NoteNumber: {1} | Time: {2} | Velocity: {3}", i, nev.NoteNumber, nev.AbsoluteTime, nev.Velocity))
Select Case nev.NoteNumber
Case 60, 62, 64, 65, 67, 69
If nev.Velocity > 0 Then
If nev.AbsoluteTime <> lastTime Then
lastTime = nev.AbsoluteTime
curGroup = New List(Of Tuple(Of Integer, Midi.NoteOnEvent))
nevGroups.Add(curGroup)
End If
curGroup.Add(New Tuple(Of Integer, Midi.NoteOnEvent)(i, nev))
End If
End Select
End Select
Next
Dim tmpNotes As New List(Of clsNoteEntry)
Dim lastGroup As List(Of Tuple(Of Integer, Midi.NoteOnEvent)) = Nothing
For Each nevGroup As List(Of Tuple(Of Integer, Midi.NoteOnEvent)) In nevGroups
Dim nevTime As Long = nevGroup(0).Item2.AbsoluteTime
For Each nevTup As Tuple(Of Integer, Midi.NoteOnEvent) In nevGroup
Dim ne As clsNoteEntry = Nothing
ne = New clsNoteEntry(controller, Track, Level, nevTup.Item2, False, False, nevTup.Item1, "Full", True)
tmpNotes.Add(ne)
Next
lastGroup = nevGroup
Next
tmpNotes.Sort()
Dim t As Integer = 0
For Each ne As clsNoteEntry In tmpNotes
While ne.tickOffset >= tempos(t).tickEnd
t = t + 1
End While
ne.msOffset = Track._game.dilation * (tempos(t).usStart + tempos(t).rate * (ne.tickOffset - tempos(t).tickStart)) / 1000
If ne.tickDuration + ne.tickOffset <= tempos(t).tickEnd Then
ne.msDuration = ne.tickDuration * tempos(t).rate / 1000
Else
'If ne.tickDuration + ne.tickOffset > tempos(t + 1).tickEnd Then Stop
ne.msDuration = (tempos(t).tickEnd - ne.tickOffset) * tempos(t).rate / 1000 + (ne.tickDuration + ne.tickOffset - tempos(t + 1).tickStart) * tempos(t + 1).rate / 1000
End If
ne.msDuration = ne.msDuration * Track._game.dilation - Track._game.truncation
If ne.msDuration < Track._game.minimumDuration Then ne.msDuration = Track._game.minimumDuration
ne.setComment()
Next
Return tmpNotes
End Function
End Module