Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/backend/pattern/extra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { audioState } from '../wsAudio'
export const getHeartbeatColor: IColorGetter = (_, time) => {
const beat = settings.syncToMusic && audioState.bpm ? 60000 / audioState.bpm : 1000
const cycle = beat
const t = (time * settings.effectSpeed) % cycle
const offset = settings.syncToMusic ? audioState.beatTime : 0
const t = ((time - offset) * settings.effectSpeed) % cycle

if (t < lastHeartbeat) {
prevHeartbeat = nextHeartbeat
Expand Down Expand Up @@ -36,7 +37,8 @@ function pulseIntensity(t: number, offset: number, cycle: number) {

export const getStrobeColor: IColorGetter = (_, time) => {
const interval = settings.syncToMusic && audioState.bpm ? 60000 / audioState.bpm : 200
const t = (time * settings.effectSpeed) % interval
const offset = settings.syncToMusic ? audioState.beatTime : 0
const t = ((time - offset) * settings.effectSpeed) % interval
if (t < lastStrobe) {
strobeColor = hslToRgb(Math.random() * 360, 1, 0.5)
}
Expand All @@ -47,7 +49,8 @@ export const getStrobeColor: IColorGetter = (_, time) => {

export const getPulseColor: IColorGetter = (_, time) => {
const cycle = settings.syncToMusic && audioState.bpm ? 60000 / audioState.bpm : 1000
const t = (time * settings.effectSpeed) % cycle
const offset = settings.syncToMusic ? audioState.beatTime : 0
const t = ((time - offset) * settings.effectSpeed) % cycle
if (t < lastPulse) {
pulseColor = hslToRgb(Math.random() * 360, 1, 0.5)
}
Expand Down
18 changes: 13 additions & 5 deletions src/backend/wsAudio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ export interface AudioState {
level: number
freq: number
bpm: number
beatTime: number
bins: number[]
}

export const audioState: AudioState = { hue: 0, level: 0, freq: 0, bpm: 0, bins: [] }
export const audioState: AudioState = { hue: 0, level: 0, freq: 0, bpm: 0, beatTime: 0, bins: [] }

export function startAudioServer(port = 8081) {
const wss = new WebSocketServer({ port })
Expand All @@ -26,7 +27,7 @@ export function startAudioServer(port = 8081) {
}

const sampleRateDefault = 44100
const bpmWindow = sampleRateDefault * 8
const bpmWindow = sampleRateDefault * 12
const sampleBuffer = new RingBuffer<number>(bpmWindow)
let lastBpmUpdate = 0

Expand All @@ -53,12 +54,18 @@ export function processAudio(buffer: Buffer, sampleRate = sampleRateDefault) {
audioState.level = max / (mags.length / 2)

const now = Date.now()
if (now - lastBpmUpdate > 2000 && sampleBuffer.getBufferLength() >= sampleRate * 4) {
if (now - lastBpmUpdate > 500 && sampleBuffer.getBufferLength() >= sampleRate * 4) {
lastBpmUpdate = now
try {
const mt = new MusicTempo(Float32Array.from(sampleBuffer.toArray()))
const data = Float32Array.from(sampleBuffer.toArray())
const mt = new MusicTempo(data)
const tempo = parseFloat(String(mt.tempo))
if (!Number.isNaN(tempo)) audioState.bpm = tempo
if (!Number.isNaN(tempo)) {
audioState.bpm = audioState.bpm ? (audioState.bpm * 3 + tempo) / 4 : tempo
const duration = sampleBuffer.getBufferLength() / sampleRate
const beatSec = mt.beats[mt.beats.length - 1] ?? 0
audioState.beatTime = now - (duration - beatSec) * 1000
}
} catch {
// ignore errors
}
Expand All @@ -70,6 +77,7 @@ export function resetAudioState() {
audioState.level = 0
audioState.freq = 0
audioState.bpm = 0
audioState.beatTime = 0
audioState.bins = []
sampleBuffer.clear()
lastBpmUpdate = 0
Expand Down
7 changes: 7 additions & 0 deletions tests/wsAudio.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,11 @@ describe('processAudio', () => {
expect(audioState.bpm).toBeLessThan(130)
})

test('detects slow bpm', () => {
const buf = genBeat(80, 8, 44100)
processAudio(buf)
expect(audioState.bpm).toBeGreaterThan(70)
expect(audioState.bpm).toBeLessThan(90)
})

})