Files
luma/Sources/LumaCore/Audio/EventChime.swift
Ole André Vadla Ravnås ad7ffcdabc Add a synthesiser that sounds arriving events
Eight channels, each with its own patch and pattern on its own step
clock, mixed on the audio device's thread: storage allocated once,
reached through unsafe pointers, and a lock-free ring for control,
so nothing under the callback allocates, retains or locks.

The image performs a tune, names it, and sounds chords.
2026-08-13 12:46:09 +02:00

47 lines
1.5 KiB
Swift

import Foundation
/// Sounds arriving events: the same activity that drives a shader effect
/// picks a degree of a pentatonic scale, so a busy target climbs. Off until
/// asked for -- instrumentation that makes noise unbidden is not a feature.
@MainActor
public final class EventChime {
public var isEnabled = false {
didSet {
guard isEnabled != oldValue else { return }
if isEnabled {
synth.start()
synth.use(.blip, channel: chimeChannel)
} else {
synth.stop()
}
}
}
public let synth: Synth
/// Semitone offsets of a minor pentatonic, spanning two octaves.
private let degrees: [Float] = [0, 3, 5, 7, 10, 12, 15, 17, 19, 22]
private let rootHz: Float = 196
private let minimumSpacing: TimeInterval = 0.06
private var lastSoundedAt: TimeInterval = 0
/// Its own channel, so sounding events never restyles a tune's voices.
private let chimeChannel = SynthEngine.channelCount - 1
public init(synth: Synth = Synth()) {
self.synth = synth
}
public func report(activity: Float, at now: TimeInterval = Date.timeIntervalSinceReferenceDate) {
guard isEnabled, now - lastSoundedAt >= minimumSpacing else { return }
lastSoundedAt = now
let degree = degrees[min(Int(activity * Float(degrees.count)), degrees.count - 1)]
synth.play(
frequency: rootHz * pow(2, degree / 12),
velocity: 0.35 + activity * 0.5,
channel: chimeChannel)
}
}