Skip to content

Commit c981c25

Browse files
authored
Merge pull request #16 from ryohey/faster-load
Faster load
2 parents c92a2c8 + d85cb96 commit c981c25

File tree

16 files changed

+392
-210
lines changed

16 files changed

+392
-210
lines changed

.github/workflows/node.js.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3+
4+
name: Node.js CI
5+
6+
on:
7+
push:
8+
branches: ["main"]
9+
pull_request:
10+
branches: ["main"]
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
16+
strategy:
17+
matrix:
18+
node-version: [18.x]
19+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
20+
21+
steps:
22+
- uses: actions/checkout@v3
23+
- name: Use Node.js ${{ matrix.node-version }}
24+
uses: actions/setup-node@v3
25+
with:
26+
node-version: ${{ matrix.node-version }}
27+
cache: "npm"
28+
- run: npm ci
29+
- run: npm run build --if-present
30+
- run: npm test

.github/workflows/npm-publish.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3+
4+
name: Node.js Package
5+
6+
on:
7+
release:
8+
types: [created]
9+
10+
jobs:
11+
publish-npm:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v3
15+
- uses: actions/setup-node@v3
16+
with:
17+
node-version: 18
18+
registry-url: https://registry.npmjs.org/
19+
- run: npm ci
20+
- run: npm run build
21+
- run: npm run publish
22+
env:
23+
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

example/src/index.ts

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
AudioData,
33
audioDataToAudioBuffer,
44
CancelMessage,
5-
getSamplesFromSoundFont,
5+
getSampleEventsFromSoundFont,
66
OutMessage,
77
renderAudio,
88
StartMessage,
@@ -13,7 +13,8 @@ import { encode } from "wav-encoder"
1313
import { MIDIPlayer } from "./MIDIPlayer"
1414
import { midiToSynthEvents } from "./midiToSynthEvents"
1515

16-
const soundFontUrl = "soundfonts/A320U.sf2"
16+
// const soundFontUrl = "soundfonts/A320U.sf2"
17+
const soundFontUrl = "soundfonts/SGM-V2.01.sf2"
1718

1819
const Sleep = (time: number) =>
1920
new Promise<void>((resolve) => setTimeout(resolve, time))
@@ -44,20 +45,24 @@ const main = async () => {
4445
}
4546

4647
const loadSoundFont = async () => {
48+
let startDate = Date.now()
49+
console.log("Loading soundfont...")
4750
soundFontData = await (await fetch(soundFontUrl)).arrayBuffer()
51+
console.log(
52+
`Soundfont loaded. (${Date.now() - startDate}ms, ${
53+
soundFontData.byteLength
54+
} bytes)`
55+
)
4856

57+
startDate = Date.now()
4958
console.log("Parsing soundfont...")
50-
const parsed = getSamplesFromSoundFont(
51-
new Uint8Array(soundFontData),
52-
context
59+
const sampleEvents = getSampleEventsFromSoundFont(
60+
new Uint8Array(soundFontData)
5361
)
54-
console.log("Soundfont parsed.")
62+
console.log(`Soundfont parsed. (${Date.now() - startDate}ms)`)
5563

56-
for (const sample of parsed) {
57-
postSynthMessage(
58-
sample,
59-
[sample.sample.buffer] // transfer instead of copy
60-
)
64+
for (const event of sampleEvents) {
65+
postSynthMessage(event.event, event.transfer)
6166
}
6267
}
6368

@@ -157,9 +162,8 @@ const main = async () => {
157162
if (soundFontData === null) {
158163
return
159164
}
160-
const samples = getSamplesFromSoundFont(
161-
new Uint8Array(soundFontData),
162-
context
165+
const sampleEvents = getSampleEventsFromSoundFont(
166+
new Uint8Array(soundFontData)
163167
)
164168
const sampleRate = 44100
165169
const events = midiToSynthEvents(midi, sampleRate)
@@ -175,14 +179,18 @@ const main = async () => {
175179
cancelButton.onclick = () => (cancel = true)
176180
exportPanel.appendChild(cancelButton)
177181

178-
const result = await renderAudio(samples, events, {
179-
sampleRate,
180-
bufferSize: 256,
181-
cancel: () => cancel,
182-
waitForEventLoop: waitForAnimationFrame,
183-
onProgress: (numFrames, totalFrames) =>
184-
(progress.value = numFrames / totalFrames),
185-
})
182+
const result = await renderAudio(
183+
sampleEvents.map((event) => event.event),
184+
events,
185+
{
186+
sampleRate,
187+
bufferSize: 256,
188+
cancel: () => cancel,
189+
waitForEventLoop: waitForAnimationFrame,
190+
onProgress: (numFrames, totalFrames) =>
191+
(progress.value = numFrames / totalFrames),
192+
}
193+
)
186194

187195
cancelButton.remove()
188196

@@ -195,15 +203,14 @@ const main = async () => {
195203
return
196204
}
197205
const worker = new Worker("/js/rendererWorker.js")
198-
const samples = getSamplesFromSoundFont(
199-
new Uint8Array(soundFontData),
200-
context
206+
const sampleEvents = getSampleEventsFromSoundFont(
207+
new Uint8Array(soundFontData)
201208
)
202209
const sampleRate = 44100
203210
const events = midiToSynthEvents(midi, sampleRate)
204211
const message: StartMessage = {
205212
type: "start",
206-
samples,
213+
samples: sampleEvents.map((e) => e.event),
207214
events,
208215
sampleRate,
209216
bufferSize: 128,
File renamed without changes.

lib/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{
22
"name": "@ryohey/wavelet",
3-
"version": "0.6.3",
3+
"version": "0.7.0",
44
"description": "A wavetable synthesizer that never stops the UI thread created by AudioWorklet.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
77
"type": "module",
88
"scripts": {
99
"start": "rollup --config --watch",
1010
"build": "rollup --config",
11-
"test": "jest"
11+
"test": "jest --roots ./src"
1212
},
1313
"author": "ryohey",
1414
"license": "MIT",

lib/src/SynthEvent.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ export interface SampleLoop {
77
end: number
88
}
99

10-
export interface SampleData<BufferType> {
10+
export interface SampleParameter {
1111
name: string
12-
buffer: BufferType
12+
sampleID: number
1313
pitch: number
1414
loop: SampleLoop | null
1515
sampleStart: number
@@ -25,15 +25,25 @@ export interface SampleData<BufferType> {
2525
volume: number // 0 to 1
2626
}
2727

28-
export interface LoadSampleEvent {
29-
type: "loadSample"
30-
sample: SampleData<ArrayBuffer>
28+
export interface SampleRange {
3129
bank: number
3230
instrument: number // GM Patch Number
3331
keyRange: [number, number]
3432
velRange: [number, number]
3533
}
3634

35+
export interface LoadSampleEvent {
36+
type: "loadSample"
37+
data: ArrayBuffer
38+
sampleID: number
39+
}
40+
41+
export interface SampleParameterEvent {
42+
type: "sampleParameter"
43+
parameter: SampleParameter
44+
range: SampleRange
45+
}
46+
3747
export type MIDIEventBody = DistributiveOmit<AnyChannelEvent, "deltaTime">
3848

3949
export type MIDIEvent = {
@@ -44,7 +54,7 @@ export type MIDIEvent = {
4454
delayTime: number
4555
}
4656

47-
export type ImmediateEvent = LoadSampleEvent
57+
export type ImmediateEvent = LoadSampleEvent | SampleParameterEvent
4858
export type SynthEvent = ImmediateEvent | MIDIEvent
4959

5060
export const DrumInstrumentNumber = 128

lib/src/processor/SampleTable.ts

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,44 @@
1-
import { SampleData } from "../SynthEvent"
1+
import { SampleParameter, SampleRange } from "../SynthEvent"
22

3-
type Sample = SampleData<Float32Array>
4-
5-
export type SampleTableItem = Sample & {
3+
export type SampleTableItem = SampleParameter & {
64
velRange: [number, number]
75
}
86

7+
export type Sample = SampleParameter & {
8+
buffer: ArrayBuffer
9+
}
10+
911
export class SampleTable {
1012
private samples: {
13+
[sampleID: number]: Float32Array
14+
} = {}
15+
16+
private sampleParameters: {
1117
[bank: number]: {
1218
[instrument: number]: { [pitch: number]: SampleTableItem[] }
1319
}
1420
} = {}
1521

16-
addSample(
17-
sample: Sample,
18-
bank: number,
19-
instrument: number,
20-
keyRange: [number, number],
21-
velRange: [number, number]
22-
) {
22+
addSample(data: Float32Array, sampleID: number) {
23+
this.samples[sampleID] = data
24+
}
25+
26+
addSampleParameter(parameter: SampleParameter, range: SampleRange) {
27+
const { bank, instrument, keyRange, velRange } = range
2328
for (let i = keyRange[0]; i <= keyRange[1]; i++) {
24-
if (this.samples[bank] === undefined) {
25-
this.samples[bank] = {}
29+
if (this.sampleParameters[bank] === undefined) {
30+
this.sampleParameters[bank] = {}
2631
}
27-
if (this.samples[bank][instrument] === undefined) {
28-
this.samples[bank][instrument] = {}
32+
if (this.sampleParameters[bank][instrument] === undefined) {
33+
this.sampleParameters[bank][instrument] = {}
2934
}
30-
if (this.samples[bank][instrument][i] === undefined) {
31-
this.samples[bank][instrument][i] = []
35+
if (this.sampleParameters[bank][instrument][i] === undefined) {
36+
this.sampleParameters[bank][instrument][i] = []
3237
}
33-
this.samples[bank][instrument][i].push({ ...sample, velRange })
38+
this.sampleParameters[bank][instrument][i].push({
39+
...parameter,
40+
velRange,
41+
})
3442
}
3543
}
3644

@@ -40,11 +48,25 @@ export class SampleTable {
4048
pitch: number,
4149
velocity: number
4250
): Sample[] {
43-
const samples = this.samples?.[bank]?.[instrument]?.[pitch]
44-
return (
45-
samples?.filter(
51+
const parameters =
52+
this.sampleParameters?.[bank]?.[instrument]?.[pitch]?.filter(
4653
(s) => velocity >= s.velRange[0] && velocity <= s.velRange[1]
4754
) ?? []
48-
)
55+
56+
const samples: Sample[] = []
57+
58+
for (const parameter of parameters) {
59+
const buffer = this.samples[parameter.sampleID]
60+
if (buffer === undefined) {
61+
console.warn(`sample not found: ${parameter.sampleID}`)
62+
continue
63+
}
64+
samples.push({
65+
...parameter,
66+
buffer,
67+
})
68+
}
69+
70+
return samples
4971
}
5072
}

0 commit comments

Comments
 (0)