chiptune3 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ All code in this project is MIT (X11) licensed. The only exception are the compiled libopenmpt parts which remain under the OpenMPT project BSD license.
2
+
3
+ License text below:
4
+
5
+ Copyright © 2013-2024 The chiptune2.js and chiptune3 contributers.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # Chiptune.js
2
+ #### Version 3
3
+ Modernized ES6 module version with libopenmpt AudioWorklet backend
4
+
5
+ See: https://DrSnuggles.github.io/chiptune
6
+
7
+ Modland demo player: https://DrSnuggles.github.io/chiptune/demo.html
8
+
9
+ Drop in your favorite songs.
10
+
11
+ ## Build
12
+ Docker was used to build the library
13
+
14
+ Exported_Functions: '_malloc','_free','stackAlloc','stackSave','stackRestore','UTF8ToString'
15
+
16
+ CD into docker and run build.bat (Win) or build.sh (Linux)
17
+
18
+ Afterwards you can minify by "npm run minify"
19
+
20
+ ## History
21
+ - 2024-03-13: Bumped to 0.7.4
22
+ - 2024-02-04: Metadata contains song, bugfixes and minify
23
+ - 2024-01-24: Added config object, Modland player
24
+ - 2024-01-23: Drag'n'Drop files. Build library using Docker.
25
+ - 2024-01-22: Libopenmpt 0.7.3 compiled with Emscripten 3.1.51
26
+
27
+ #### Version 2
28
+ This is a javascript library that can play module music files. It is based on the [libopenmpt](https://lib.openmpt.org/libopenmpt) C/C++ library. To translate libopenmpt into Javascript [emscripten](https://github.com/kripken/emscripten) was used. For audio output inside the browser WebAudio API is used.
29
+
30
+ **Please note**: The compiled `libopenmpt.js` in this repository is based on an outdated version of libopenmpt. Newer versions contain bugfixes and other improvements. Download the latest version from the libopenmpt developers [here](https://lib.openmpt.org/libopenmpt/download/) and replace `libopenmpt.js.mem` and `libopenmpt.js`.
31
+ If you prefer to compile `libopenmpt.js` yourself (to save space or make custom changes) follow the instructions in the "Development" section.
32
+
33
+ ## Features
34
+
35
+ * Play all tracker formats supported by libopenmpt (including mod, xm, s3m, it)
36
+ * Simple Javascript API
37
+ * Pause/Resume
38
+ * Tested with Google Chrome and Firefox
39
+ * Play local (HTML5) and remote files (XHR)
40
+ * Stereo playback
41
+ * Module metadata
42
+ * Looping mode
43
+
44
+ ## To do
45
+
46
+ * Playback information (e.g. position, speed, bpm)
47
+ * Mixer settings (e.g. sampling rate, resolution)
48
+ * Seekbar support
49
+
50
+ ## Demo
51
+
52
+ See it in action [here](https://deskjet.github.io/chiptune2.js/).
53
+
54
+ Just drop a module (e.g. from [modarchive.org](https://modarchive.org)) on the demo page and enjoy.
55
+
56
+ ## Development
57
+ Download the latest [emscripten](https://emscripten.org/) SDK. Follow the instructions for your OS. Make sure `emcc` and `em++` commands are available in the PATH.
58
+ Next download and extract the libopenmpt source code (unix like) from https://lib.openmpt.org/libopenmpt/.
59
+ Use this command inside the libopenmpt source folder to build:
60
+
61
+ make CONFIG=emscripten
62
+
63
+ If it compiles successfully these files (and a few more) will be created in the `bin` directory:
64
+
65
+ 1. `libopenmpt.js` is libopenmpt compiled to JavaScript.
66
+ 2. `libopenmpt.js.mem` is the memory initialization file `libopenmpt.js` will download/require it when loaded.
67
+ 3. `libopenmpt_test.js` (and `libopenmpt_test.j.mem`) is the libopenmpt test suite and can be run in NodeJS.
68
+
69
+ Only the first two files are needed for chiptune2.js.
70
+
71
+ ### Building Stylesheet
72
+ The stylesheet is built with [SASS+Compass](http://compass-style.org/). To build this yourself, follow the Compass [installation instructions](http://compass-style.org/install/), then change into the chiptune2.js directory and use this command:
73
+
74
+ compass watch
75
+
76
+ The stylesheet .css file with rebuild every time you make a change to the source file in the `sass` folder.
77
+
78
+ ## Contributers (in order of appearance)
79
+ - [deskjet](https://github.com/deskjet)
80
+ - [manx](https://github.com/manxorist)
81
+ - [nyov](https://github.com/nyov)
82
+ - [DanialOaks](https://github.com/DanielOaks)
83
+ - [onikienko](https://github.com/onikienko)
84
+ - [okaybenji](https://github.com/okaybenji)
85
+ - [photonstorm](https://github.com/photonstorm)
86
+
87
+ Thanks to everyone!
88
+
89
+ ## License
90
+
91
+ All code in this project is MIT (X11) licensed. The only exception are the compiled libopenmpt parts which remain under the OpenMPT project BSD license.
92
+
93
+ License text below:
94
+
95
+ >Copyright © 2013-2024 The chiptune2.js contributers.
96
+ >
97
+ >Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
98
+ >
99
+ >The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
100
+ >
101
+ >THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
102
+
package/chiptune3.js ADDED
@@ -0,0 +1,127 @@
1
+ /*
2
+ chiptune3 (worklet version)
3
+ based on: https://deskjet.github.io/chiptune2.js/
4
+ */
5
+
6
+ const defaultCfg = {
7
+ repeatCount: -1, // -1 = play endless, 0 = play once, do not repeat
8
+ stereoSeparation: 100, // percents
9
+ interpolationFilter: 0, // https://lib.openmpt.org/doc/group__openmpt__module__render__param.html
10
+ context: false,
11
+ }
12
+
13
+ export class ChiptuneJsPlayer {
14
+ constructor(cfg) {
15
+ this.config = {...defaultCfg, ...cfg}
16
+
17
+ if (this.config.context) {
18
+ if (!this.config.context.destination) {
19
+ //console.error('This is not an audio context.')
20
+ throw('ChiptuneJsPlayer: This is not an audio context')
21
+ }
22
+ this.context = this.config.context
23
+ this.destination = false
24
+ } else {
25
+ this.context = new AudioContext()
26
+ this.destination = this.context.destination // output to speakers
27
+ }
28
+ delete this.config.context // remove from config, just used here and after init not changeable
29
+
30
+ // make gainNode
31
+ this.gain = this.context.createGain()
32
+ this.gain.gain.value = 1
33
+
34
+ this.handlers = []
35
+
36
+ // worklet
37
+ this.context.audioWorklet.addModule( new URL('./chiptune3.worklet.js', import.meta.url) )
38
+ .then(()=>{
39
+ this.processNode = new AudioWorkletNode(this.context, 'libopenmpt-processor', {
40
+ numberOfInputs: 0,
41
+ numberOfOutputs: 1,
42
+ outputChannelCount: [2]
43
+ })
44
+ // message port
45
+ this.processNode.port.onmessage = this.handleMessage_.bind(this)
46
+ this.processNode.port.postMessage({cmd:'config', val:this.config})
47
+ this.fireEvent('onInitialized')
48
+
49
+ // audio routing
50
+ this.processNode.connect(this.gain)
51
+ if (this.destination) this.gain.connect(this.destination) // also connect to output if no gainNode was given
52
+ })
53
+ .catch(e=>console.error(e))
54
+ }
55
+
56
+ // msg from worklet
57
+ handleMessage_(msg) {
58
+ switch (msg.data.cmd) {
59
+ case 'meta':
60
+ this.meta = msg.data.meta
61
+ this.duration = msg.data.meta.dur
62
+ this.fireEvent('onMetadata', this.meta)
63
+ break
64
+ case 'pos':
65
+ //this.meta.pos = msg.data.pos
66
+ this.currentTime = msg.data.pos
67
+ this.order = msg.data.order
68
+ this.pattern = msg.data.pattern
69
+ this.row = msg.data.row
70
+ this.fireEvent('onProgress', msg.data)
71
+ break
72
+ case 'end':
73
+ this.fireEvent('onEnded')
74
+ break
75
+ case 'err':
76
+ this.fireEvent('onError', {type: msg.data.val})
77
+ break
78
+ default:
79
+ console.log('Received unknown message',msg.data)
80
+ }
81
+ }
82
+
83
+ // handlers
84
+ fireEvent(eventName, response) {
85
+ const handlers = this.handlers
86
+ if (handlers.length) {
87
+ handlers.forEach(function (handler) {
88
+ if (handler.eventName === eventName) {
89
+ handler.handler(response)
90
+ }
91
+ })
92
+ }
93
+ }
94
+ addHandler(eventName, handler) { this.handlers.push({eventName: eventName, handler: handler}) }
95
+ onInitialized(handler) { this.addHandler('onInitialized', handler) }
96
+ onEnded(handler) { this.addHandler('onEnded', handler) }
97
+ onError(handler) { this.addHandler('onError', handler) }
98
+ onMetadata(handler) { this.addHandler('onMetadata', handler) }
99
+ onProgress(handler) { this.addHandler('onProgress', handler) }
100
+
101
+ // methods
102
+ postMsg(cmd, val) {
103
+ if (this.processNode)
104
+ this.processNode.port.postMessage({cmd:cmd,val:val})
105
+ }
106
+ load(url) {
107
+ fetch(url)
108
+ .then(response => response.arrayBuffer())
109
+ .then(arrayBuffer => this.play(arrayBuffer))
110
+ .catch(e=>{this.fireEvent('onError', {type: 'Load'})})
111
+ }
112
+ play(val) { this.postMsg('play', val) }
113
+ stop() { this.postMsg('stop') }
114
+ pause() { this.postMsg('pause') }
115
+ unpause() { this.postMsg('unpause') }
116
+ togglePause() { this.postMsg('togglePause') }
117
+ setRepeatCount(val) { this.postMsg('repeatCount', val) }
118
+ setPitch(val) { this.postMsg('setPitch', val) }
119
+ setTempo(val) { this.postMsg('setTempo', val) }
120
+ setPos(val) { this.postMsg('setPos', val) }
121
+ setOrderRow(o,r) { this.postMsg('setOrderRow', {o:o,r:r}) }
122
+ setVol(val) { this.gain.gain.value = val }
123
+ selectSubsong(val) { this.postMsg('selectSubsong', val) }
124
+ // compatibility
125
+ seek(val) { this.setPos(val) }
126
+ getCurrentTime() { return this.currentTime }
127
+ }
@@ -0,0 +1,335 @@
1
+ /*
2
+ AudioWorklet: DrSnuggles
3
+ */
4
+
5
+ import libopenmptPromise from './libopenmpt.worklet.js'
6
+
7
+ // consts
8
+ const OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT = 2
9
+ const OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH = 3
10
+
11
+ // vars
12
+ let libopenmpt
13
+
14
+ // init
15
+ libopenmptPromise()
16
+ .then(res => {
17
+ libopenmpt = res
18
+
19
+ if (!libopenmpt.stackSave) return
20
+ // set libopenmpt version to display later
21
+ let stack = libopenmpt.stackSave()
22
+ libopenmpt.version = libopenmpt.UTF8ToString(libopenmpt._openmpt_get_string(asciiToStack('library_version')))
23
+ libopenmpt.build = libopenmpt.UTF8ToString(libopenmpt._openmpt_get_string(asciiToStack('build')))
24
+ libopenmpt.stackRestore(stack)
25
+ })
26
+ .catch(e => console.error(e))
27
+
28
+ //
29
+ // Helpers
30
+ //
31
+ function asciiToStack(str) {
32
+ const stackStr = libopenmpt.stackAlloc(str.length + 1) // DrS: needed to export in emscripten
33
+ writeAsciiToMemory(str, stackStr) // no longer in Emscripten, see below
34
+ return stackStr
35
+ }
36
+ function writeAsciiToMemory(str,buffer,dontAddNull){for(let i=0;i<str.length;++i){libopenmpt.HEAP8[buffer++>>0]=str.charCodeAt(i)}if(!dontAddNull)libopenmpt.HEAP8[buffer>>0]=0}
37
+
38
+
39
+ //
40
+ // Processor
41
+ //
42
+ class MPT extends AudioWorkletProcessor {
43
+ constructor() {
44
+ super()
45
+ this.port.onmessage = this.handleMessage_.bind(this)
46
+ this.paused = false
47
+ this.config = {
48
+ repeatCount: -1, // -1 = play endless, 0 = play once, do not repeat
49
+ stereoSeparation: 100, // percents
50
+ interpolationFilter: 0, // https://lib.openmpt.org/doc/group__openmpt__module__render__param.html
51
+ }
52
+ this.channels = 0
53
+ }
54
+
55
+ process(inputList, outputList, parameters) {
56
+ if (!this.modulePtr || !this.leftPtr || !this.rightPtr || this.paused) return true //silence
57
+
58
+ const left = outputList[0][0]
59
+ const right = outputList[0][1]
60
+
61
+ const actualFramesPerChunk = libopenmpt._openmpt_module_read_float_stereo(this.modulePtr, sampleRate, left.length, this.leftPtr, this.rightPtr)
62
+ if (actualFramesPerChunk == 0) {
63
+ //this.paused = true
64
+ // modulePtr will be 0 on openmpt: error: openmpt_module_read_float_stereo: ERROR: module * not valid or other openmpt error
65
+ const error = !this.modulePtr
66
+ if (error) {
67
+ this.port.postMessage({cmd:'err',val:'Process'})
68
+ } else {
69
+ this.port.postMessage({cmd:'end'})
70
+ }
71
+ return true
72
+ }
73
+
74
+ left.set(libopenmpt.HEAPF32.subarray(this.leftPtr / 4, this.leftPtr / 4 + actualFramesPerChunk))
75
+ right.set(libopenmpt.HEAPF32.subarray(this.rightPtr / 4, this.rightPtr / 4 + actualFramesPerChunk))
76
+
77
+ // post progress
78
+ // openmpt_module_get_current_order
79
+
80
+ let msg = {
81
+ cmd: 'pos',
82
+ pos: libopenmpt._openmpt_module_get_position_seconds(this.modulePtr),
83
+ // pos in song
84
+ order: libopenmpt._openmpt_module_get_current_order(this.modulePtr),
85
+ pattern: libopenmpt._openmpt_module_get_current_pattern(this.modulePtr),
86
+ row: libopenmpt._openmpt_module_get_current_row(this.modulePtr),
87
+ // channel volumes
88
+ //chVol: [], // ch0Left, ch0Right, ch1Left, ...
89
+ }
90
+ /*
91
+ for (let i = 0; i < this.channels; i++) {
92
+ msg.chVol.push( {
93
+ left: libopenmpt._openmpt_module_get_current_channel_vu_left(this.modulePtr, i),
94
+ right: libopenmpt._openmpt_module_get_current_channel_vu_right(this.modulePtr, i),
95
+ })
96
+ }
97
+ */
98
+
99
+ this.port.postMessage( msg )
100
+
101
+ return true // def. needed for Chrome
102
+ }
103
+
104
+ handleMessage_(msg) {
105
+ //console.log('[Processor:Received]',msg.data)
106
+ const v = msg.data.val
107
+ switch (msg.data.cmd) {
108
+ case 'config':
109
+ this.config = v
110
+ break
111
+ case 'play':
112
+ this.play(v)
113
+ break
114
+ case 'pause':
115
+ this.paused = true
116
+ break
117
+ case 'unpause':
118
+ this.paused = false
119
+ break
120
+ case 'togglePause':
121
+ this.paused = !this.paused
122
+ break
123
+ case 'stop':
124
+ this.stop()
125
+ break
126
+ case 'meta':
127
+ this.meta()
128
+ break
129
+ case 'repeatCount':
130
+ this.config.repeatCount = v
131
+ if (!this.modulePtr) return
132
+ libopenmpt._openmpt_module_set_repeat_count(this.modulePtr, this.config.repeatCount)
133
+ break
134
+ case 'setPitch':
135
+ if (!libopenmpt.stackSave || !this.modulePtr) return
136
+ libopenmpt._openmpt_module_ctl_set(this.modulePtr, asciiToStack('play.pitch_factor'), asciiToStack(v.toString()))
137
+ break
138
+ case 'setTempo':
139
+ if (!libopenmpt.stackSave || !this.modulePtr) return
140
+ libopenmpt._openmpt_module_ctl_set(this.modulePtr, asciiToStack('play.tempo_factor'), asciiToStack(v.toString()))
141
+ break
142
+ case 'selectSubsong':
143
+ if (!this.modulePtr) return
144
+ libopenmpt._openmpt_module_select_subsong(this.modulePtr, v)
145
+ //this.meta()
146
+ break
147
+ case 'setPos':
148
+ if (!this.modulePtr) return
149
+ libopenmpt._openmpt_module_set_position_seconds(this.modulePtr, v)
150
+ break
151
+ case 'setOrderRow':
152
+ if (!this.modulePtr) return
153
+ libopenmpt._openmpt_module_set_position_order_row(this.modulePtr, v.o, v.r)
154
+ break
155
+ /*
156
+ case 'toggleMute'
157
+ // openmpt_module_ext_get_interface(mod_ext, interface_id, interface, interface_size)
158
+ // openmpt_module_ext_interface_interactive
159
+ // set_channel_mute_status
160
+ // https://lib.openmpt.org/doc/group__libopenmpt__ext__c.html#ga0275a35da407cd092232a20d3535c9e4
161
+ if (!this.modulePtr) return
162
+ //const extPtr = libopenmpt.openmpt_module_ext_get_interface(mod_ext, interface_id, interface, interface_size)
163
+ break
164
+ */
165
+ default:
166
+ console.log('Received unknown message',msg.data)
167
+ }
168
+ } // handleMessage_
169
+
170
+ play(buffer) {
171
+ this.stop()
172
+
173
+ const maxFramesPerChunk = 128 // thats what worklet is using
174
+ const byteArray = new Int8Array(buffer)
175
+ const ptrToFile = libopenmpt._malloc(byteArray.byteLength)
176
+ libopenmpt.HEAPU8.set(byteArray, ptrToFile)
177
+ this.modulePtr = libopenmpt._openmpt_module_create_from_memory(ptrToFile, byteArray.byteLength, 0, 0, 0)
178
+
179
+ if(this.modulePtr === 0) {
180
+ // could not create module
181
+ this.port.postMessage({cmd:'err',val:'ptr'})
182
+ return
183
+ }
184
+
185
+ if (libopenmpt.stackSave) {
186
+ const stack = libopenmpt.stackSave()
187
+ libopenmpt._openmpt_module_ctl_set(this.modulePtr, asciiToStack('render.resampler.emulate_amiga'), asciiToStack('1'))
188
+ libopenmpt._openmpt_module_ctl_set(this.modulePtr, asciiToStack('render.resampler.emulate_amiga_type'), asciiToStack('a1200'))
189
+ //libopenmpt._openmpt_module_ctl_set('play.pitch_factor', e.target.value.toString());
190
+
191
+ libopenmpt.stackRestore(stack)
192
+ }
193
+
194
+ this.paused = false
195
+ this.leftPtr = libopenmpt._malloc(4 * maxFramesPerChunk) // 4x = float
196
+ this.rightPtr = libopenmpt._malloc(4 * maxFramesPerChunk)
197
+
198
+ // set config options on module
199
+ libopenmpt._openmpt_module_set_repeat_count(this.modulePtr, this.config.repeatCount)
200
+ libopenmpt._openmpt_module_set_render_param(this.modulePtr, OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT, this.config.stereoSeparation)
201
+ libopenmpt._openmpt_module_set_render_param(this.modulePtr, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, this.config.interpolationFilter)
202
+
203
+ // post back tracks metadata
204
+ this.meta()
205
+ }
206
+ stop() {
207
+ if (!this.modulePtr) return
208
+ if (this.modulePtr != 0) {
209
+ libopenmpt._openmpt_module_destroy(this.modulePtr)
210
+ this.modulePtr = 0
211
+ }
212
+ if (this.leftBufferPtr != 0) {
213
+ libopenmpt._free(this.leftBufferPtr)
214
+ this.leftBufferPtr = 0
215
+ }
216
+ if (this.rightBufferPtr != 0) {
217
+ libopenmpt._free(this.rightBufferPtr)
218
+ this.rightBufferPtr = 0
219
+ }
220
+ this.channels = 0
221
+ }
222
+ meta() {
223
+ this.port.postMessage({cmd: 'meta', meta: this.getMeta()})
224
+ }
225
+ getSong() {
226
+ if (!libopenmpt.UTF8ToString || !this.modulePtr) return false
227
+
228
+ // https://lib.openmpt.org/doc/
229
+ let song = {
230
+ channels: [],
231
+ instruments: [],
232
+ samples: [],
233
+ orders: [],
234
+ numSubsongs: libopenmpt._openmpt_module_get_num_subsongs(this.modulePtr),
235
+ patterns: [],
236
+ }
237
+ // channels
238
+ const chNum = libopenmpt._openmpt_module_get_num_channels(this.modulePtr)
239
+ this.channel = chNum
240
+ for (let i = 0; i < chNum; i++) {
241
+ song.channels.push( libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_channel_name(this.modulePtr, i)) )
242
+ }
243
+ // instruments
244
+ for (let i = 0, e = libopenmpt._openmpt_module_get_num_instruments(this.modulePtr); i < e; i++) {
245
+ song.instruments.push( libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_instrument_name(this.modulePtr, i)) )
246
+ }
247
+ // samples
248
+ for (let i = 0, e = libopenmpt._openmpt_module_get_num_samples(this.modulePtr); i < e; i++) {
249
+ song.samples.push( libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_sample_name(this.modulePtr, i)) )
250
+ }
251
+ // orders
252
+ for (let i = 0, e = libopenmpt._openmpt_module_get_num_orders(this.modulePtr); i < e; i++) {
253
+ song.orders.push( {
254
+ name: libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_order_name(this.modulePtr, i)),
255
+ pat: libopenmpt._openmpt_module_get_order_pattern(this.modulePtr, i),
256
+ })
257
+ }
258
+ // patterns
259
+ for (let patIdx = 0, patNum = libopenmpt._openmpt_module_get_num_patterns(this.modulePtr); patIdx < patNum; patIdx++) {
260
+ const pattern = {
261
+ name: libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_pattern_name(this.modulePtr, patIdx)),
262
+ rows: [],
263
+ }
264
+ // rows
265
+ for(let rowIdx = 0, rowNum = libopenmpt._openmpt_module_get_pattern_num_rows(this.modulePtr, patIdx); rowIdx < rowNum; rowIdx++) {
266
+ const row = []
267
+ // channels
268
+ for (let chIdx = 0; chIdx < chNum; chIdx++) {
269
+ const channel = []
270
+ for (let comIdx = 0; comIdx < 6; comIdx++) {
271
+ /* commands
272
+ OPENMPT_MODULE_COMMAND_NOTE = 0
273
+ OPENMPT_MODULE_COMMAND_INSTRUMENT = 1
274
+ OPENMPT_MODULE_COMMAND_VOLUMEEFFECT = 2
275
+ OPENMPT_MODULE_COMMAND_EFFECT = 3
276
+ OPENMPT_MODULE_COMMAND_VOLUME = 4
277
+ OPENMPT_MODULE_COMMAND_PARAMETER = 5
278
+ */
279
+ channel.push( libopenmpt._openmpt_module_get_pattern_row_channel_command(this.modulePtr, patIdx, rowIdx, chIdx, comIdx) )
280
+ }
281
+ row.push( channel )
282
+ }
283
+ pattern.rows.push( row )
284
+ }
285
+ song.patterns.push( pattern )
286
+ }
287
+
288
+
289
+ return song
290
+ }
291
+ getMeta() {
292
+ if (!libopenmpt.UTF8ToString || !this.modulePtr) return false
293
+
294
+ const data = {}
295
+ data.dur = libopenmpt._openmpt_module_get_duration_seconds(this.modulePtr)
296
+ if (data.dur == 0) {
297
+ // looks like an error occured reading the mod
298
+ this.port.postMessage({cmd:'err',val:'dur'})
299
+ }
300
+ const keys = libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_metadata_keys(this.modulePtr)).split(';')
301
+ for (let i = 0; i < keys.length; i++) {
302
+ const keyNameBuffer = libopenmpt._malloc(keys[i].length + 1)
303
+ writeAsciiToMemory(keys[i], keyNameBuffer)
304
+ data[keys[i]] = libopenmpt.UTF8ToString(libopenmpt._openmpt_module_get_metadata(this.modulePtr, keyNameBuffer))
305
+ libopenmpt._free(keyNameBuffer)
306
+ }
307
+ data.song = this.getSong()
308
+ data.totalOrders = data.song.orders.length // libopenmpt._openmpt_module_get_num_orders(this.modulePtr)
309
+ data.totalPatterns = data.song.patterns.length// libopenmpt._openmpt_module_get_num_patterns(this.modulePtr)
310
+ data.songs = this.getSongs()
311
+ data.libopenmptVersion = libopenmpt.version
312
+ data.libopenmptBuild = libopenmpt.build
313
+ return data
314
+ }
315
+ getSongs() {
316
+ if (!libopenmpt.UTF8ToString) return '' // todo: ?? why string here
317
+
318
+ const subsongs = libopenmpt._openmpt_module_get_num_subsongs(this.modulePtr)
319
+ const names = []
320
+ for (let i = 0; i < subsongs; i++) {
321
+ const namePtr = libopenmpt._openmpt_module_get_subsong_name(this.modulePtr, i)
322
+ const name = libopenmpt.UTF8ToString(namePtr)
323
+ if(name != "") {
324
+ names.push(name)
325
+ } else {
326
+ names.push("Subsong " + (i + 1))
327
+ }
328
+ libopenmpt._openmpt_free_string(namePtr)
329
+ }
330
+ return names
331
+ }
332
+
333
+ }
334
+
335
+ registerProcessor('libopenmpt-processor', MPT)