1
+ import { MidiParser } from "./midi_parser/midi_parser.js" ;
2
+ import { MidiManager } from "./midi_visualizer/midi_manager.js" ;
3
+
4
+ import { SoundFont2Parser } from "./soundfont2_parser/soundfont_parser.js" ;
5
+ import { ShiftableUint8Array } from "./utils/shiftable_array.js" ;
6
+
7
+ /**
8
+ * Parses the midi file (kinda)
9
+ *
10
+ * @param {File } midiFile
11
+ */
12
+ async function parseMidi ( midiFile )
13
+ {
14
+ let buffer = await midiFile . arrayBuffer ( ) ;
15
+ let p = new MidiParser ( ) ;
16
+ return await p . parse ( Array . from ( new Uint8Array ( buffer ) ) , t => titleMessage . innerText = t ) ;
17
+ }
18
+
19
+ /**
20
+ * @param fileName {"soundfont.sf2"|"gm.sf2"|"Touhou.sf2"|"FluidR3_GM.sf2"|"alex_gm.sf2"|"zunpet.sf2"|"pc98.sf2"|"zunfont.sf2"}
21
+ * @param callback {function(number)}
22
+ * @returns {Promise<ShiftableUint8Array> }
23
+ */
24
+ async function fetchFont ( fileName , callback )
25
+ {
26
+ let url = `http://localhost:80/other/soundfonts/${ fileName } ` ;
27
+ let response = await fetch ( url ) ;
28
+ let size = response . headers . get ( "content-length" ) ;
29
+ let reader = await ( await response . body ) . getReader ( ) ;
30
+ let done = false ;
31
+ let dataArray = new ShiftableUint8Array ( size ) ;
32
+ let offset = 0 ;
33
+ do {
34
+ let readData = await reader . read ( ) ;
35
+ if ( readData . value ) {
36
+ dataArray . set ( readData . value , offset ) ;
37
+ offset += readData . value . length ;
38
+ }
39
+ done = readData . done ;
40
+ let percent = Math . round ( ( offset / size ) * 100 ) ;
41
+ callback ( percent ) ;
42
+ } while ( ! done ) ;
43
+ return dataArray ;
44
+ }
45
+
46
+ /**
47
+ * @param midiFile {File}
48
+ */
49
+ function startMidi ( midiFile )
50
+ {
51
+
52
+ parseMidi ( midiFile ) . then ( parsedMid => {
53
+ manager . play ( parsedMid , true , true ) ;
54
+ document . getElementById ( "file_upload" ) . innerText = midiFile . name ;
55
+ } ) ;
56
+ }
57
+
58
+ /**
59
+ * @param url {string}
60
+ * @param callback {function(string)}
61
+ * @returns {Promise<ShiftableUint8Array> }
62
+ */
63
+ // async function fetchFontHeaderManipulation(url, callback) {
64
+ // // 50MB
65
+ // const chunkSize = 1024 * 1024 * 50;
66
+ // const fileSize = (await fetch(url, {method: "HEAD"})).headers.get("content-length");
67
+ // const chunksAmount = Math.ceil(fileSize / chunkSize);
68
+ // /**
69
+ // * @type {Promise[] }
70
+ // */
71
+ // let loaderWorkers = [];
72
+ // let startIndex = 0;
73
+ // let loadedWorkersAmount = 0;
74
+ // for (let i = 0; i < chunksAmount; i++)
75
+ // {
76
+ // let thisChunkSize =
77
+ // fileSize < startIndex + chunkSize ?
78
+ // fileSize - startIndex
79
+ // :
80
+ // chunkSize;
81
+ //
82
+ // let bytesRange = [startIndex, startIndex + thisChunkSize - 1];
83
+ // let loaderWorker = new Promise(resolve =>
84
+ // {
85
+ // let w = new Worker("soundfont2_parser/soundfont_loader_worker.js");
86
+ //
87
+ // w.onmessage = d => {
88
+ // callback(`Downloading Soundfont... (${++loadedWorkersAmount}/${chunksAmount})`);
89
+ // resolve(d.data);
90
+ // }
91
+ //
92
+ // w.postMessage({
93
+ // range: bytesRange,
94
+ // url: window.location.href + url
95
+ // });
96
+ // });
97
+ // loaderWorkers.push(loaderWorker);
98
+ // startIndex += thisChunkSize
99
+ // }
100
+ // /**
101
+ // * @type {Uint8Array[] }
102
+ // */
103
+ // let data = await Promise.all(loaderWorkers);
104
+ // let joinedData = new ShiftableUint8Array(fileSize);
105
+ // let index = 0;
106
+ // let totalDatalen = 0;
107
+ // for(let arr of data)
108
+ // {
109
+ // totalDatalen += arr.length;
110
+ // }
111
+ // for(let arr of data)
112
+ // {
113
+ // joinedData.set(arr, index);
114
+ // index += arr.length;
115
+ // }
116
+ // return joinedData;
117
+ // }
118
+
119
+ document . getElementById ( "midi_file_input" ) . focus ( ) ;
120
+
121
+ /**
122
+ * @type {HTMLHeadingElement }
123
+ */
124
+ let titleMessage = document . getElementById ( "title" ) ;
125
+ /**
126
+ * @type {HTMLDivElement }
127
+ */
128
+ let progressBar = document . getElementById ( "progress_bar" ) ;
129
+ /**
130
+ * @type {HTMLInputElement }
131
+ */
132
+ let fileInput = document . getElementById ( "midi_file_input" ) ;
133
+
134
+ // remove the old files
135
+ fileInput . value = "" ;
136
+
137
+ document . body . onclick = ( ) =>
138
+ {
139
+ // user has clicked, we can create the ui
140
+ if ( ! window . audioContextMain ) {
141
+ window . audioContextMain = new AudioContext ( ) ;
142
+ if ( window . soundFontParser ) {
143
+ titleMessage . innerText = "SpessaSynth: MIDI Soundfont2 Player" ;
144
+ // prepare midi interface
145
+ window . manager = new MidiManager ( audioContextMain , soundFontParser ) ;
146
+ }
147
+ }
148
+ document . body . onclick = null ;
149
+ }
150
+
151
+ titleMessage . innerText = "Downloading soundfont..." ;
152
+
153
+ // gm.sf2, soundfont.sf2, FluidR3_GM.sf2
154
+ fetchFont ( "soundfont.sf2" , percent => progressBar . style . width = `${ ( percent / 100 ) * titleMessage . offsetWidth } px` )
155
+ . then ( data => {
156
+ titleMessage . innerText = "Parsing soundfont..." ;
157
+ setTimeout ( ( ) => {
158
+ window . soundFontParser = new SoundFont2Parser ( data , m => titleMessage . innerText = m ) ;
159
+
160
+ titleMessage . innerText = "SpessaSynth: MIDI Soundfont2 Player" ;
161
+ progressBar . style . width = "0" ;
162
+
163
+ // prepare the preset selector
164
+ let pNames = soundFontParser . presets . map ( p => p . presetName ) ;
165
+ pNames . sort ( ) ;
166
+ for ( let pName of pNames )
167
+ {
168
+ let option = document . createElement ( "option" ) ;
169
+ option . value = pName ;
170
+ option . innerText = pName ;
171
+ document . getElementById ( "preset_selector" ) . appendChild ( option ) ;
172
+ }
173
+
174
+ if ( ! fileInput . files [ 0 ] ) {
175
+ fileInput . onchange = e => {
176
+ if ( ! e . target . files [ 0 ] ) {
177
+ return ;
178
+ }
179
+ startMidi ( fileInput . files [ 0 ] ) ;
180
+ fileInput . onchange = null ;
181
+ } ;
182
+ }
183
+ else
184
+ {
185
+ startMidi ( fileInput . files [ 0 ] ) ;
186
+ }
187
+
188
+ // prompt the user to click if needed
189
+ if ( ! window . audioContextMain )
190
+ {
191
+ titleMessage . innerText = "Press anywhere to start the app" ;
192
+ return ;
193
+ }
194
+ // prepare midi interface
195
+ window . manager = new MidiManager ( audioContextMain , soundFontParser ) ;
196
+
197
+ } ) ;
198
+ } ) ;
0 commit comments