refactor(audio,stage-ui): simplify code

This commit is contained in:
Neko Ayaka
2026-08-07 04:36:50 +08:00
parent 8df21d09d6
commit c124e0147d
7 changed files with 95 additions and 120 deletions
+2 -1
View File
@@ -42,7 +42,8 @@
"scripts": {
"dev": "pnpm run build",
"build": "tsdown",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"test:run": "vitest run"
},
"peerDependencies": {
"vue": ">=3"
+33
View File
@@ -0,0 +1,33 @@
import { describe, expect, it } from 'vitest'
import { toWav, toWavFromPCM16 } from './wav'
describe('toWav', () => {
it('converts Float32 samples to PCM16 bytes by default', () => {
const samples = new Float32Array([-1, 0, 1])
const wav = toWav(samples.buffer, 24000)
const view = new DataView(wav)
expect(view.getInt16(44, true)).toBe(-32768)
expect(view.getInt16(46, true)).toBe(0)
expect(view.getInt16(48, true)).toBe(32767)
})
it('preserves PCM16 bytes and writes the WAV metadata', () => {
const pcmBytes = new Uint8Array([0x00, 0x80, 0xFF, 0x7F])
const wav = toWavFromPCM16(pcmBytes, 24000)
const view = new DataView(wav)
expect(new TextDecoder().decode(new Uint8Array(wav, 0, 4))).toBe('RIFF')
expect(view.getUint32(4, true)).toBe(40)
expect(new TextDecoder().decode(new Uint8Array(wav, 8, 4))).toBe('WAVE')
expect(view.getUint16(20, true)).toBe(1)
expect(view.getUint16(22, true)).toBe(1)
expect(view.getUint32(24, true)).toBe(24000)
expect(view.getUint32(28, true)).toBe(48000)
expect(view.getUint16(32, true)).toBe(2)
expect(view.getUint16(34, true)).toBe(16)
expect(view.getUint32(40, true)).toBe(pcmBytes.byteLength)
expect([...new Uint8Array(wav, 44)]).toEqual([...pcmBytes])
})
})
+42 -22
View File
@@ -6,46 +6,66 @@ function writeString(dataView: DataView, offset: number, string: string) {
}
}
export function toWav(buffer: ArrayBufferLike, sampleRate: number, channel = 1) {
const samples = new Float32Array(buffer) // allows indexing
const numChannels = channel
const numSamples = samples.length
// Create the WAV file container
const arrayBuffer = new ArrayBuffer(44 + numSamples * 2)
function createWavBuffer(dataSize: number, sampleRate: number, channel: number): ArrayBuffer {
const bitsPerSample = 16
const byteRate = sampleRate * channel * (bitsPerSample / 8)
const blockAlign = channel * (bitsPerSample / 8)
const arrayBuffer = new ArrayBuffer(44 + dataSize)
const dataView = new DataView(arrayBuffer)
// RIFF chunk descriptor
writeString(dataView, 0, 'RIFF')
dataView.setUint32(4, 36 + numSamples * 2, true)
dataView.setUint32(4, 36 + dataSize, true)
writeString(dataView, 8, 'WAVE')
// fmt sub-chunk
writeString(dataView, 12, 'fmt ')
dataView.setUint32(16, 16, true)
dataView.setUint16(20, 1, true) // PCM format
dataView.setUint16(22, numChannels, true)
dataView.setUint16(20, 1, true)
dataView.setUint16(22, channel, true)
dataView.setUint32(24, sampleRate, true)
dataView.setUint32(28, sampleRate * numChannels * 2, true) // byte rate
dataView.setUint16(32, numChannels * 2, true) // block align
dataView.setUint32(28, byteRate, true)
dataView.setUint16(32, blockAlign, true)
dataView.setUint16(34, bitsPerSample, true)
dataView.setUint16(34, 16, true) // bits per sample
// data sub-chunk
writeString(dataView, 36, 'data')
dataView.setUint32(40, numSamples * 2, true)
dataView.setUint32(40, dataSize, true)
// PCM samples
const offset = 44
for (let i = 0; i < numSamples; i++) {
return arrayBuffer
}
/**
* Encodes Float32 samples as a WAV file.
*
* @example
* toWav(float32Samples.buffer, 24000)
* // => WAV data with converted PCM16 samples
*/
export function toWav(buffer: ArrayBufferLike, sampleRate: number, channel = 1): ArrayBuffer {
const samples = new Float32Array(buffer)
const arrayBuffer = createWavBuffer(samples.length * 2, sampleRate, channel)
const dataView = new DataView(arrayBuffer)
for (let i = 0; i < samples.length; i++) {
const sample = Math.max(-1, Math.min(1, samples[i]))
const value = sample < 0 ? sample * 0x8000 : sample * 0x7FFF
dataView.setInt16(offset + i * 2, value, true)
dataView.setInt16(44 + i * 2, value, true)
}
return arrayBuffer
}
/**
* Wraps raw signed 16-bit PCM samples in a WAV file.
*
* @example
* toWavFromPCM16(pcmBytes, 24000)
* // => WAV data with the original PCM16 bytes
*/
export function toWavFromPCM16(pcmBytes: Uint8Array, sampleRate: number, channel = 1): ArrayBuffer {
const arrayBuffer = createWavBuffer(pcmBytes.byteLength, sampleRate, channel)
new Uint8Array(arrayBuffer, 44).set(pcmBytes)
return arrayBuffer
}
export function toWAVBase64(buffer: ArrayBufferLike, sampleRate: number) {
return encodeBase64(toWav(buffer, sampleRate))
}
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
include: ['src/**/*.test.ts'],
},
})