Mercurial > web
comparison js/chiptune2.js @ 58:e6c574c6f8e0
music: reupload music to the music folder, fix links
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Mon, 19 Dec 2022 18:31:02 -0500 |
parents | |
children | 629553bdc8aa |
comparison
equal
deleted
inserted
replaced
57:ac1900c0e376 | 58:e6c574c6f8e0 |
---|---|
1 // constants | |
2 const OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT = 2 | |
3 const OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH = 3 | |
4 | |
5 // audio context | |
6 var ChiptuneAudioContext = window['AudioContext'] || window['webkitAudioContext']; | |
7 | |
8 // config | |
9 var ChiptuneJsConfig = function (repeatCount, stereoSeparation, interpolationFilter, context) | |
10 { | |
11 this.repeatCount = repeatCount; | |
12 this.stereoSeparation = stereoSeparation; | |
13 this.interpolationFilter = interpolationFilter; | |
14 this.context = context; | |
15 } | |
16 | |
17 ChiptuneJsConfig.prototype.constructor = ChiptuneJsConfig; | |
18 | |
19 // player | |
20 var ChiptuneJsPlayer = function (config) { | |
21 this.config = config; | |
22 this.context = config.context || new ChiptuneAudioContext(); | |
23 this.currentPlayingNode = null; | |
24 this.handlers = []; | |
25 this.touchLocked = true; | |
26 } | |
27 | |
28 ChiptuneJsPlayer.prototype.constructor = ChiptuneJsPlayer; | |
29 | |
30 // event handlers section | |
31 ChiptuneJsPlayer.prototype.fireEvent = function (eventName, response) { | |
32 var handlers = this.handlers; | |
33 if (handlers.length) { | |
34 handlers.forEach(function (handler) { | |
35 if (handler.eventName === eventName) { | |
36 handler.handler(response); | |
37 } | |
38 }) | |
39 } | |
40 } | |
41 | |
42 ChiptuneJsPlayer.prototype.addHandler = function (eventName, handler) { | |
43 this.handlers.push({eventName: eventName, handler: handler}); | |
44 } | |
45 | |
46 ChiptuneJsPlayer.prototype.onEnded = function (handler) { | |
47 this.addHandler('onEnded', handler); | |
48 } | |
49 | |
50 ChiptuneJsPlayer.prototype.onError = function (handler) { | |
51 this.addHandler('onError', handler); | |
52 } | |
53 | |
54 // metadata | |
55 ChiptuneJsPlayer.prototype.duration = function() { | |
56 return libopenmpt._openmpt_module_get_duration_seconds(this.currentPlayingNode.modulePtr); | |
57 } | |
58 | |
59 ChiptuneJsPlayer.prototype.getCurrentRow = function() { | |
60 return libopenmpt._openmpt_module_get_current_row(this.currentPlayingNode.modulePtr); | |
61 } | |
62 | |
63 ChiptuneJsPlayer.prototype.getCurrentPattern = function() { | |
64 return libopenmpt._openmpt_module_get_current_pattern(this.currentPlayingNode.modulePtr); | |
65 } | |
66 | |
67 ChiptuneJsPlayer.prototype.getCurrentOrder = function() { | |
68 return libopenmpt._openmpt_module_get_current_order(this.currentPlayingNode.modulePtr); | |
69 } | |
70 | |
71 ChiptuneJsPlayer.prototype.getCurrentTime = function () { | |
72 return libopenmpt._openmpt_module_get_position_seconds(this.currentPlayingNode.modulePtr); | |
73 }; | |
74 | |
75 ChiptuneJsPlayer.prototype.getTotalOrder = function () { | |
76 return libopenmpt._openmpt_module_get_num_orders(this.currentPlayingNode.modulePtr); | |
77 }; | |
78 | |
79 ChiptuneJsPlayer.prototype.getTotalPatterns = function () { | |
80 return libopenmpt._openmpt_module_get_num_patterns(this.currentPlayingNode.modulePtr); | |
81 }; | |
82 | |
83 ChiptuneJsPlayer.prototype.metadata = function() { | |
84 var data = {}; | |
85 var keys = UTF8ToString(libopenmpt._openmpt_module_get_metadata_keys(this.currentPlayingNode.modulePtr)).split(';'); | |
86 var keyNameBuffer = 0; | |
87 for (var i = 0; i < keys.length; i++) { | |
88 keyNameBuffer = libopenmpt._malloc(keys[i].length + 1); | |
89 writeAsciiToMemory(keys[i], keyNameBuffer); | |
90 data[keys[i]] = UTF8ToString(libopenmpt._openmpt_module_get_metadata(this.currentPlayingNode.modulePtr, keyNameBuffer)); | |
91 libopenmpt._free(keyNameBuffer); | |
92 } | |
93 return data; | |
94 } | |
95 | |
96 ChiptuneJsPlayer.prototype.module_ctl_set = function(ctl, value) { | |
97 return libopenmpt.ccall('openmpt_module_ctl_set', 'number', ['number', 'string', 'string'], [this.currentPlayingNode.modulePtr, ctl, value]) === 1; | |
98 } | |
99 | |
100 // playing, etc | |
101 ChiptuneJsPlayer.prototype.unlock = function() { | |
102 | |
103 var context = this.context; | |
104 var buffer = context.createBuffer(1, 1, 22050); | |
105 var unlockSource = context.createBufferSource(); | |
106 | |
107 unlockSource.buffer = buffer; | |
108 unlockSource.connect(context.destination); | |
109 unlockSource.start(0); | |
110 | |
111 this.touchLocked = false; | |
112 } | |
113 | |
114 ChiptuneJsPlayer.prototype.load = function(input, callback) { | |
115 | |
116 if (this.touchLocked) { | |
117 this.unlock(); | |
118 } | |
119 | |
120 var player = this; | |
121 | |
122 if (input instanceof File) { | |
123 var reader = new FileReader(); | |
124 reader.onload = function() { | |
125 return callback(reader.result); // no error | |
126 }.bind(this); | |
127 reader.readAsArrayBuffer(input); | |
128 } else { | |
129 var xhr = new XMLHttpRequest(); | |
130 xhr.open('GET', input, true); | |
131 xhr.responseType = 'arraybuffer'; | |
132 xhr.onload = function(e) { | |
133 if (xhr.status === 200) { | |
134 return callback(xhr.response); // no error | |
135 } else { | |
136 player.fireEvent('onError', {type: 'onxhr'}); | |
137 } | |
138 }.bind(this); | |
139 xhr.onerror = function() { | |
140 player.fireEvent('onError', {type: 'onxhr'}); | |
141 }; | |
142 xhr.onabort = function() { | |
143 player.fireEvent('onError', {type: 'onxhr'}); | |
144 }; | |
145 xhr.send(); | |
146 } | |
147 } | |
148 | |
149 ChiptuneJsPlayer.prototype.play = function(buffer) { | |
150 this.stop(); | |
151 var processNode = this.createLibopenmptNode(buffer, this.config); | |
152 if (processNode == null) { | |
153 return; | |
154 } | |
155 | |
156 // set config options on module | |
157 libopenmpt._openmpt_module_set_repeat_count(processNode.modulePtr, this.config.repeatCount); | |
158 libopenmpt._openmpt_module_set_render_param(processNode.modulePtr, OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT, this.config.stereoSeparation); | |
159 libopenmpt._openmpt_module_set_render_param(processNode.modulePtr, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, this.config.interpolationFilter); | |
160 | |
161 this.currentPlayingNode = processNode; | |
162 processNode.connect(this.context.destination); | |
163 } | |
164 | |
165 ChiptuneJsPlayer.prototype.stop = function() { | |
166 if (this.currentPlayingNode != null) { | |
167 this.currentPlayingNode.disconnect(); | |
168 this.currentPlayingNode.cleanup(); | |
169 this.currentPlayingNode = null; | |
170 } | |
171 } | |
172 | |
173 ChiptuneJsPlayer.prototype.togglePause = function() { | |
174 if (this.currentPlayingNode != null) { | |
175 this.currentPlayingNode.togglePause(); | |
176 } | |
177 } | |
178 | |
179 ChiptuneJsPlayer.prototype.createLibopenmptNode = function(buffer, config) { | |
180 // TODO error checking in this whole function | |
181 | |
182 var maxFramesPerChunk = 4096; | |
183 var processNode = this.context.createScriptProcessor(2048, 0, 2); | |
184 processNode.config = config; | |
185 processNode.player = this; | |
186 var byteArray = new Int8Array(buffer); | |
187 var ptrToFile = libopenmpt._malloc(byteArray.byteLength); | |
188 libopenmpt.HEAPU8.set(byteArray, ptrToFile); | |
189 processNode.modulePtr = libopenmpt._openmpt_module_create_from_memory(ptrToFile, byteArray.byteLength, 0, 0, 0); | |
190 processNode.paused = false; | |
191 processNode.leftBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk); | |
192 processNode.rightBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk); | |
193 processNode.cleanup = function() { | |
194 if (this.modulePtr != 0) { | |
195 libopenmpt._openmpt_module_destroy(this.modulePtr); | |
196 this.modulePtr = 0; | |
197 } | |
198 if (this.leftBufferPtr != 0) { | |
199 libopenmpt._free(this.leftBufferPtr); | |
200 this.leftBufferPtr = 0; | |
201 } | |
202 if (this.rightBufferPtr != 0) { | |
203 libopenmpt._free(this.rightBufferPtr); | |
204 this.rightBufferPtr = 0; | |
205 } | |
206 } | |
207 processNode.stop = function() { | |
208 this.disconnect(); | |
209 this.cleanup(); | |
210 } | |
211 processNode.pause = function() { | |
212 this.paused = true; | |
213 } | |
214 processNode.unpause = function() { | |
215 this.paused = false; | |
216 } | |
217 processNode.togglePause = function() { | |
218 this.paused = !this.paused; | |
219 } | |
220 processNode.onaudioprocess = function(e) { | |
221 var outputL = e.outputBuffer.getChannelData(0); | |
222 var outputR = e.outputBuffer.getChannelData(1); | |
223 var framesToRender = outputL.length; | |
224 if (this.ModulePtr == 0) { | |
225 for (var i = 0; i < framesToRender; ++i) { | |
226 outputL[i] = 0; | |
227 outputR[i] = 0; | |
228 } | |
229 this.disconnect(); | |
230 this.cleanup(); | |
231 return; | |
232 } | |
233 if (this.paused) { | |
234 for (var i = 0; i < framesToRender; ++i) { | |
235 outputL[i] = 0; | |
236 outputR[i] = 0; | |
237 } | |
238 return; | |
239 } | |
240 var framesRendered = 0; | |
241 var ended = false; | |
242 var error = false; | |
243 while (framesToRender > 0) { | |
244 var framesPerChunk = Math.min(framesToRender, maxFramesPerChunk); | |
245 var actualFramesPerChunk = libopenmpt._openmpt_module_read_float_stereo(this.modulePtr, this.context.sampleRate, framesPerChunk, this.leftBufferPtr, this.rightBufferPtr); | |
246 if (actualFramesPerChunk == 0) { | |
247 ended = true; | |
248 // modulePtr will be 0 on openmpt: error: openmpt_module_read_float_stereo: ERROR: module * not valid or other openmpt error | |
249 error = !this.modulePtr; | |
250 } | |
251 var rawAudioLeft = libopenmpt.HEAPF32.subarray(this.leftBufferPtr / 4, this.leftBufferPtr / 4 + actualFramesPerChunk); | |
252 var rawAudioRight = libopenmpt.HEAPF32.subarray(this.rightBufferPtr / 4, this.rightBufferPtr / 4 + actualFramesPerChunk); | |
253 for (var i = 0; i < actualFramesPerChunk; ++i) { | |
254 outputL[framesRendered + i] = rawAudioLeft[i]; | |
255 outputR[framesRendered + i] = rawAudioRight[i]; | |
256 } | |
257 for (var i = actualFramesPerChunk; i < framesPerChunk; ++i) { | |
258 outputL[framesRendered + i] = 0; | |
259 outputR[framesRendered + i] = 0; | |
260 } | |
261 framesToRender -= framesPerChunk; | |
262 framesRendered += framesPerChunk; | |
263 } | |
264 if (ended) { | |
265 this.disconnect(); | |
266 this.cleanup(); | |
267 error ? processNode.player.fireEvent('onError', {type: 'openmpt'}) : processNode.player.fireEvent('onEnded'); | |
268 } | |
269 } | |
270 return processNode; | |
271 } |