-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMusic.vb
More file actions
252 lines (223 loc) · 14.9 KB
/
Music.vb
File metadata and controls
252 lines (223 loc) · 14.9 KB
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
'This class's imports and settings.
Option Compare Binary
Option Explicit On
Option Infer Off
Option Strict On
Imports System
Imports System.Collections.Generic
Imports System.Convert
Imports System.Diagnostics
Imports System.Environment
Imports System.Globalization
Imports System.IO
Imports System.Linq
Imports System.Text
Imports System.Windows.Forms
'This class contains the music related procedures.
Public Class MusicClass
Inherits DataFileClass
'This enumeration lists the locations of known values inside a music file.
Private Enum LocationsE As Integer
AdlibMIDIChannelInitializationTable = &H22% 'The Adlib MIDI channel initialization table.
CMSMIDIChannelInitializationTable = &H2B% 'The CMS MIDI channel initialization table.
CMSMIDIChannelFinetuneOffsetTable = &H37% 'The CMS MIDI channel finetune offset table.
MIDITrackOffset = &H0% 'The music's MIDI track offset.
PCSpeakerPitchAndSpeed = &H4B% 'The PC-Speaker pitch and speed.
RandomDataBlock = &H4D% 'Random data block.
RolandMIDIChannelOffOnTable = &H2% 'The Roland MIDI channel off/on table.
RolandMIDIVolumeTable = &H12% 'The Roland global MIDI volume table.
TandySoundChipInitializationTable = &H43% 'Tandy sound chip initialization table.
End Enum
'This enumeration lists the locations of the music template sections.
Private Enum TemplateLinesE As Integer
AdlibMIDIChannelInitializationTable = 2 'The Adlib MIDI channel initialization table.
CMSMIDIChannelInitializationTable = 3 'The CMS MIDI channel initialization table.
CMSMIDIChannelFinetuneOffsetTable = 4 'The CMS MIDI channel finetune offset table.
MIDITrack = 7 'The MIDI track.
PCSpeakerPitchAndSpeed = 6 'The PC-Speaker pitch and speed.
RepeatFlag = 8 'The repeat flag.
RolandGlobalMIDIVolumeTable = 1 'The Roland global MIDI volume table.
RolandMIDIChannelOffOnTable = 0 'The Roland MIDI channel off/on table.
TandySoundChipInitializationTable = 5 'Tandy sound chip initialization table.
End Enum
Private Const ADLIB_MIDI_CHANNEL_INITIALIZATION_SIZE As Integer = &H9% 'Defines the Adlib MIDI channel initialization table size.
Private Const CMS_MIDI_CHANNEL_INITIALIZATION_SIZE As Integer = &HC% 'Defines the CMS MIDI channel initialization table size.
Private Const CMS_MIDI_CHANNEL_FINETUNE_OFFSETS_SIZE As Integer = &HC% 'Defines the CMS MIDI channel finetune offset table size.
Private Const FOOTER_SIZE As Integer = &H2% 'Defines the footer size.
Private Const MIDI_EVENT_STOP_PLAYBACK As Integer = &HFC% 'Defines the stop playback MIDI event.
Private Const PLAY_ONCE As Integer = &H81% 'Indicates that the music is played once.
Private Const PLAY_REPEATEDLY As Integer = &H80% 'Indicates that the music is played repeatedly.
Private Const ROLAND_MIDI_CHANNELS_SIZE As Integer = &H10% 'Defines the Roland MIDI channel off/on table size.
Private Const ROLAND_MIDI_VOLUMES_SIZE As Integer = &H10% 'Defines the Roland global MIDI volume table size.
Private Const TANDY_SOUND_CHIP_INITIALIZATION_TABLE_SIZE As Integer = &H8% 'Defines the Tandy sound chip related data size.
'The menu items used by this class.
Private WithEvents DisplayDataMenu As New ToolStripMenuItem With {.ShortcutKeys = Keys.F1, .Text = "Display &Data"}
Private WithEvents DisplayInformationMenu As New ToolStripMenuItem With {.ShortcutKeys = Keys.F2, .Text = "Display &Information"}
'This procedure initializes this class.
Public Sub New(PathO As String, Optional DataFileMenu As ToolStripMenuItem = Nothing)
Try
If Path.GetExtension(PathO).ToLower() = ".txt" Then PathO = Import(PathO)
If DataFile(MusicPath:=PathO).Data.Count > 0 AndAlso DataFileMenu IsNot Nothing Then
With DataFileMenu
.DropDownItems.Clear()
.DropDownItems.AddRange({DisplayDataMenu, DisplayInformationMenu})
.Text = "&Music"
.Visible = True
End With
End If
Catch ExceptionO As Exception
DisplayException(ExceptionO)
End Try
End Sub
'This procedures manages the music's data file.
Private Function DataFile(Optional MusicPath As String = Nothing) As DataFileStr
Try
Static CurrentFile As New DataFileStr With {.Data = Nothing, .Path = Nothing}
If Not MusicPath = Nothing Then
With CurrentFile
.Data = New List(Of Byte)(File.ReadAllBytes(MusicPath))
If Not .Data.Any Then .Data.Clear()
Select Case .Data.Last
Case PLAY_ONCE, PLAY_REPEATEDLY
.Path = MusicPath
DisplayInformationMenu.PerformClick()
Case Else
MessageBox.Show("Invalid music file.", My.Application.Info.Title, MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
.Data = Nothing
End Select
End With
End If
Return CurrentFile
Catch ExceptionO As Exception
DisplayException(ExceptionO)
End Try
Return Nothing
End Function
'This procedure displays the current music's data.
Private Sub DisplayDataMenu_Click(sender As Object, e As EventArgs) Handles DisplayDataMenu.Click
Try
Dim MIDITrackOffset As Integer = BitConverter.ToUInt16(DataFile().Data.ToArray(), LocationsE.MIDITrackOffset)
With New StringBuilder
.Append($"Music data:{NewLine}")
.Append($"-Relative MIDI track offset: {MIDITrackOffset:X}{NewLine}")
.Append($"-Roland MIDI channel off/on table: {Escape(GetString(DataFile().Data, LocationsE.RolandMIDIChannelOffOnTable, ROLAND_MIDI_CHANNELS_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}")
.Append($"-Roland global MIDI volume table: {Escape(GetString(DataFile().Data, LocationsE.RolandMIDIVolumeTable, ROLAND_MIDI_VOLUMES_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}")
.Append($"-Adlib MIDI channel initialization table: {Escape(GetString(DataFile().Data, LocationsE.AdlibMIDIChannelInitializationTable, ADLIB_MIDI_CHANNEL_INITIALIZATION_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}")
.Append($"-CMS MIDI channel initialization table: {Escape(GetString(DataFile().Data, LocationsE.CMSMIDIChannelInitializationTable, CMS_MIDI_CHANNEL_INITIALIZATION_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}")
.Append($"-CMS MIDI channel finetune offset table: {Escape(GetString(DataFile().Data, LocationsE.CMSMIDIChannelFinetuneOffsetTable, CMS_MIDI_CHANNEL_FINETUNE_OFFSETS_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}")
.Append($"-Tandy sound chip initialization table: {Escape(GetString(DataFile().Data, LocationsE.TandySoundChipInitializationTable, TANDY_SOUND_CHIP_INITIALIZATION_TABLE_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}")
.Append($"-PC-Speaker pitch and speed: {BitConverter.ToUInt16(DataFile().Data.ToArray(), LocationsE.PCSpeakerPitchAndSpeed):X2}")
.Append($"{NewLine}{NewLine}Random data block:{NewLine}")
.Append(Escape(GetString(DataFile().Data, LocationsE.RandomDataBlock, MIDITrackOffset - LocationsE.RandomDataBlock), " "c, EscapeAll:=True).Trim())
.Append($"{NewLine}{NewLine}MIDI track data:{NewLine}")
.Append(Escape(GetString(DataFile().Data, MIDITrackOffset, (DataFile().Data.Count - MIDITrackOffset) - FOOTER_SIZE), " "c, EscapeAll:=True).Trim())
.Append($"{NewLine}{NewLine}Footer:{NewLine}")
.Append(Escape(GetString(DataFile().Data, DataFile().Data.Count - FOOTER_SIZE, FOOTER_SIZE), " "c, EscapeAll:=True).Trim())
UpdateDataBox(.ToString())
End With
Catch ExceptionO As Exception
DisplayException(ExceptionO)
End Try
End Sub
'This procedure displays the general information for the current music.
Private Sub DisplayInformationMenu_Click(sender As Object, e As EventArgs) Handles DisplayInformationMenu.Click
Try
With New StringBuilder
.Append(GeneralFileInformation(DataFile().Path))
.Append(NewLine)
.Append($"MIDI track offset: {BitConverter.ToUInt16(DataFile().Data.ToArray(), LocationsE.MIDITrackOffset)} byte(s).{NewLine}")
.Append($"Play repeatedly: {IsRepeatingMusic()}")
UpdateDataBox(.ToString())
End With
Catch ExceptionO As Exception
DisplayException(ExceptionO)
End Try
End Sub
'This procedure exports the current music.
Public Overloads Sub Export(ExportPath As String)
Try
Dim Exported As New StringBuilder($"[{MUSIC_TEMPLATE}]{NewLine}{NewLine}")
Dim MIDITrackOffset As Integer = BitConverter.ToUInt16(DataFile().Data.ToArray(), LocationsE.MIDITrackOffset)
Dim MusicName As String = Path.GetFileNameWithoutExtension(DataFile.Path).ToLower()
Dim MusicPath As String = DataFile().Path
With Exported
.Append($"{TEMPLATE_COMMENT} Name:{NewLine}")
.Append($"{MusicName}{NewLine}{NewLine}")
.Append($"{TEMPLATE_COMMENT} Roland MIDI channel off/on table:{NewLine}")
.Append($"{Escape(GetString(DataFile().Data, LocationsE.RolandMIDIChannelOffOnTable, ROLAND_MIDI_CHANNELS_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}{NewLine}")
.Append($"{TEMPLATE_COMMENT} Roland global MIDI volume table:{NewLine}")
.Append($"{Escape(GetString(DataFile().Data, LocationsE.RolandMIDIVolumeTable, ROLAND_MIDI_VOLUMES_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}{NewLine}")
.Append($"{TEMPLATE_COMMENT} Adlib MIDI channel initialization table:{NewLine}")
.Append($"{Escape(GetString(DataFile().Data, LocationsE.AdlibMIDIChannelInitializationTable, ADLIB_MIDI_CHANNEL_INITIALIZATION_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}{NewLine}")
.Append($"{TEMPLATE_COMMENT} CMS MIDI channel initialization table:{NewLine}")
.Append($"{Escape(GetString(DataFile().Data, LocationsE.CMSMIDIChannelInitializationTable, CMS_MIDI_CHANNEL_INITIALIZATION_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}{NewLine}")
.Append($"{TEMPLATE_COMMENT} CMS MIDI channel finetune offset table:{NewLine}")
.Append($"{Escape(GetString(DataFile().Data, LocationsE.CMSMIDIChannelFinetuneOffsetTable, CMS_MIDI_CHANNEL_FINETUNE_OFFSETS_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}{NewLine}")
.Append($"{TEMPLATE_COMMENT} Tandy sound chip initialization table:{NewLine}")
.Append($"{Escape(GetString(DataFile().Data, LocationsE.TandySoundChipInitializationTable, TANDY_SOUND_CHIP_INITIALIZATION_TABLE_SIZE), " "c, EscapeAll:=True).Trim()}{NewLine}{NewLine}")
.Append($"{TEMPLATE_COMMENT} PC-Speaker pitch and speed:{NewLine}")
.Append($"{BitConverter.ToUInt16(DataFile().Data.ToArray(), LocationsE.PCSpeakerPitchAndSpeed):X2}")
.Append($"{NewLine}{NewLine}{TEMPLATE_COMMENT} MIDI track data:{NewLine}")
.Append(Escape(GetString(DataFile().Data, MIDITrackOffset, (DataFile().Data.Count - MIDITrackOffset) - FOOTER_SIZE), " "c, EscapeAll:=True).Trim())
.Append($"{NewLine}{NewLine}{TEMPLATE_COMMENT} Repeats:{NewLine}")
.Append($"{If(IsRepeatingMusic(), "yes", "no")}{NewLine}")
File.WriteAllText(Path.Combine(ExportPath, $"{MusicName}.txt"), .ToString())
Process.Start(New ProcessStartInfo With {.FileName = ExportPath, .WindowStyle = ProcessWindowStyle.Normal})
End With
Catch ExceptionO As Exception
DisplayException(ExceptionO)
End Try
End Sub
'This procedure imports the specified music template.
Private Function Import(ImportPath As String) As String
Try
Dim Data As New List(Of Byte)
Dim Header As New List(Of Byte)
Dim MIDITrackOffset As New Integer
Dim TemplateLines As New List(Of String)(TrimAllLines((From Item In Template() Skip 1 Where Not (Item.Trim().StartsWith(TEMPLATE_COMMENT) OrElse Item.Trim = Nothing)).ToList()))
Dim MusicName As String = TemplateLines.First()
Dim MusicPath As String = Path.Combine(Path.GetDirectoryName(ImportPath), $"{MusicName}.mus")
TemplateLines.RemoveAt(0)
With Header
.AddRange(TEXT_TO_BYTES(Unescape($" {TemplateLines(TemplateLinesE.RolandMIDIChannelOffOnTable).Trim()}", EscapeCharacter:=" "c)))
.AddRange(TEXT_TO_BYTES(Unescape($" {TemplateLines(TemplateLinesE.RolandGlobalMIDIVolumeTable).Trim()}", EscapeCharacter:=" "c)))
.AddRange(TEXT_TO_BYTES(Unescape($" {TemplateLines(TemplateLinesE.AdlibMIDIChannelInitializationTable).Trim()}", EscapeCharacter:=" "c)))
.AddRange(TEXT_TO_BYTES(Unescape($" {TemplateLines(TemplateLinesE.CMSMIDIChannelInitializationTable).Trim()}", EscapeCharacter:=" "c)))
.AddRange(TEXT_TO_BYTES(Unescape($" {TemplateLines(TemplateLinesE.CMSMIDIChannelFinetuneOffsetTable).Trim()}", EscapeCharacter:=" "c)))
.AddRange(TEXT_TO_BYTES(Unescape($" {TemplateLines(TemplateLinesE.TandySoundChipInitializationTable).Trim()}", EscapeCharacter:=" "c)))
.AddRange(BitConverter.GetBytes(CUShort(Integer.Parse(TemplateLines(TemplateLinesE.PCSpeakerPitchAndSpeed).Trim(), NumberStyles.HexNumber))))
End With
With Data
.AddRange(BitConverter.GetBytes(CUShort(Header.Count + &H2%)))
.AddRange(Header)
.AddRange(TEXT_TO_BYTES(Unescape($" {TemplateLines(TemplateLinesE.MIDITrack).Trim()}", EscapeCharacter:=" "c)))
.Add(MIDI_EVENT_STOP_PLAYBACK)
Select Case TemplateLines(TemplateLinesE.RepeatFlag).Trim().ToLower()
Case "no"
.Add(&H81%)
Case "yes"
.Add(&H80%)
End Select
File.WriteAllBytes(MusicPath, .ToArray())
End With
Return MusicPath
Catch ExceptionO As Exception
DisplayException(ExceptionO)
End Try
Return Nothing
End Function
'This procedure returns whether or not the music is played repeatedly.
Private Function IsRepeatingMusic() As Boolean
Try
Select Case DataFile().Data.Last
Case PLAY_ONCE
Return False
Case PLAY_REPEATEDLY
Return True
End Select
Catch ExceptionO As Exception
DisplayException(ExceptionO)
End Try
Return Nothing
End Function
End Class