temporary

This commit is contained in:
2025-09-30 10:56:38 +02:00
commit 95845e8af8
23 changed files with 3414 additions and 0 deletions

View File

@ -0,0 +1,154 @@
import type { BytebeatOptions, BitDepth } from './types'
import { encodeWAV } from './wavEncoder'
import { EffectsChain } from './EffectsChain'
import type { EffectValues } from '../../types/effects'
export class BytebeatGenerator {
private sampleRate: number
private duration: number
private formula: string | null = null
private compiledFormula: ((t: number) => number) | null = null
private audioBuffer: Float32Array | null = null
private audioContext: AudioContext | null = null
private sourceNode: AudioBufferSourceNode | null = null
private effectsChain: EffectsChain | null = null
private effectValues: EffectValues = {}
private startTime: number = 0
private pauseTime: number = 0
private isLooping: boolean = true
constructor(options: BytebeatOptions = {}) {
this.sampleRate = options.sampleRate ?? 8000
this.duration = options.duration ?? 10
}
setFormula(formula: string): void {
this.formula = formula
try {
this.compiledFormula = new Function('t', `return ${formula}`) as (t: number) => number
this.audioBuffer = null
} catch (error) {
throw new Error(`Invalid formula: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}
generate(): Float32Array {
if (!this.compiledFormula) {
throw new Error('No formula set. Call setFormula() first.')
}
const numSamples = Math.floor(this.sampleRate * this.duration)
const buffer = new Float32Array(numSamples)
for (let t = 0; t < numSamples; t++) {
try {
const value = this.compiledFormula(t)
const byteValue = value & 0xFF
buffer[t] = (byteValue - 128) / 128
} catch (error) {
buffer[t] = 0
}
}
this.audioBuffer = buffer
return buffer
}
setEffects(values: EffectValues): void {
this.effectValues = values
if (this.effectsChain) {
this.effectsChain.updateEffects(values)
}
}
getPlaybackPosition(): number {
if (!this.audioContext || !this.sourceNode || this.startTime === 0) {
return 0
}
const elapsed = this.audioContext.currentTime - this.startTime
return (elapsed % this.duration) / this.duration
}
play(): void {
if (!this.audioBuffer) {
this.generate()
}
if (!this.audioContext) {
this.audioContext = new AudioContext({ sampleRate: this.sampleRate })
}
if (!this.effectsChain) {
this.effectsChain = new EffectsChain(this.audioContext)
this.effectsChain.updateEffects(this.effectValues)
}
if (this.sourceNode) {
this.sourceNode.stop()
}
const audioBuffer = this.audioContext.createBuffer(1, this.audioBuffer!.length, this.sampleRate)
audioBuffer.getChannelData(0).set(this.audioBuffer!)
this.sourceNode = this.audioContext.createBufferSource()
this.sourceNode.buffer = audioBuffer
this.sourceNode.loop = this.isLooping
this.sourceNode.connect(this.effectsChain.getInputNode())
this.effectsChain.getOutputNode().connect(this.audioContext.destination)
if (this.pauseTime > 0) {
this.sourceNode.start(0, this.pauseTime)
this.startTime = this.audioContext.currentTime - this.pauseTime
this.pauseTime = 0
} else {
this.sourceNode.start(0)
this.startTime = this.audioContext.currentTime
}
}
pause(): void {
if (this.sourceNode && this.audioContext) {
this.pauseTime = this.audioContext.currentTime - this.startTime
this.sourceNode.stop()
this.sourceNode = null
}
}
stop(): void {
if (this.sourceNode) {
this.sourceNode.stop()
this.sourceNode = null
}
this.startTime = 0
this.pauseTime = 0
}
exportWAV(bitDepth: BitDepth = 8): Blob {
if (!this.audioBuffer) {
this.generate()
}
return encodeWAV(this.audioBuffer!, this.sampleRate, bitDepth)
}
downloadWAV(filename: string = 'bytebeat.wav', bitDepth: BitDepth = 8): void {
const blob = this.exportWAV(bitDepth)
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
}
dispose(): void {
this.stop()
if (this.effectsChain) {
this.effectsChain.dispose()
this.effectsChain = null
}
if (this.audioContext) {
this.audioContext.close()
this.audioContext = null
}
}
}