AVFAudio keeps a 64-byte AudioQueueOwner for good on every AVAudioPlayer play after the clip has finished - measured one a play, whatever is called around it (prepareToPlay, pause, no rewind) and still there after the player is released; a stop before the play made one every time. The windowed walk showed it as AudioQueueOwner 73 -> 87 in a minute. A sound is now decoded once into a PCM buffer and played by a voice of its own on one shared engine: a player node (the buffer scheduled again on each play, looping for -1), a varispeed (the rate) and a small mixer (volume and pan). snd_playing compares the uptime clock with the end worked out when the clip was played (asking the node where it is made two AVAudioTime objects a call), and snd_play drains an autorelease pool of its own. The C interface is unchanged. A harness driving snd_* directly (400 plays past the end, the playing flag checked during and after each, a loop and a stop, the setters) holds the heap flat to a block and gets the flag right 400 of 400; the windowed valley compiles and links against it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47 lines
1.8 KiB
Text
47 lines
1.8 KiB
Text
# load.ludic - a clip the runtime can actually open. Audio.load takes a real FILESYSTEM path (it
|
|
# bottoms out in AVAudioFile), not the pack-aware file_open every other asset arrives through: in a
|
|
# bundle every clip loads as 0 while Fs.exists, which IS pack-aware, says it is there. So try the
|
|
# runtime's path, and on 0 lift the bytes out of the pack into the cache directory and load that.
|
|
|
|
export function aud_load(audio_st: mut AudioState, path: string) -> int {
|
|
audio_st.tried += 1
|
|
if not is_windowed() { return 0 }
|
|
var h = Audio.load(path)
|
|
if h == 0 {
|
|
let out = aud_unpack(path, base_name(path))
|
|
if out != null { h = Audio.load(out) }
|
|
}
|
|
if h != 0 { audio_st.live += 1 }
|
|
return h
|
|
}
|
|
|
|
# how many clips were asked for and how many loaded: a silent game looks exactly like a working one
|
|
# unless something says so
|
|
export function aud_tried(audio_st: AudioState) -> int { return audio_st.tried }
|
|
export function aud_live(audio_st: AudioState) -> int { return audio_st.live }
|
|
|
|
# One packed file out to the cache, or null. A cached copy is trusted only if it is as long as the
|
|
# packed one: a write interrupted halfway would otherwise play truncated for ever.
|
|
export function aud_unpack(path: string, name: string) -> string {
|
|
let dir = AudioFiles.cache_dir()
|
|
if len(dir) == 0 { return null }
|
|
Fs.mkdir(dir)
|
|
let dst = dir + "/" + name
|
|
let f = file_open(path, "rb")
|
|
if f == null { return null }
|
|
file_seek(f, 0, 2)
|
|
let n = file_tell(f)
|
|
file_close(f)
|
|
if n <= 0 { return null }
|
|
if Fs.exists(dst) and Fs.size(dst) == n { return dst }
|
|
let buf = Fs.read_bytes(path)
|
|
if buf == null or len(buf) < n { return null }
|
|
if not Fs.write_bytes(dst, buf, n) { return null }
|
|
return dst
|
|
}
|
|
|
|
function base_name(path: string) -> string {
|
|
var i = len(path) - 1
|
|
while i >= 0 and path[i] != 47 { i -= 1 }
|
|
return path[i + 1..len(path)]
|
|
}
|