audio: a volume per channel under the master (effects, wildlife, ambient, interface) - aud_channel_set / aud_emit_on, aud_ui on the interface channel, and Audio.music_volume for the ambience loop on the runtime's music channel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
87587b00b6
commit
b2a45c2fda
7 changed files with 74 additions and 2 deletions
|
|
@ -18,6 +18,13 @@ aud_emit_at(axe, tree.x, tree.z, 90.0) # heard within 90 m, pa
|
|||
aud_ui(click)
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
A volume per kind of sound, under the master: `AUD_CH_EFFECTS` (what the player does), `AUD_CH_WILDLIFE`,
|
||||
`AUD_CH_AMBIENT` (its loop is the runtime's music channel, `Audio.music_volume`) and `AUD_CH_UI`
|
||||
(`aud_ui` plays on it). `aud_channel_set(ch, v)` holds it to 0..1; `aud_emit_on(ch, h, ...)` is
|
||||
`aud_emit` scaled by it. `aud_emit` itself stays raw, so its numbers are what a test reads.
|
||||
|
||||
## Ports
|
||||
|
||||
```ludic
|
||||
|
|
|
|||
24
packages/ludic.audio/channels.ludic
Normal file
24
packages/ludic.audio/channels.ludic
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# channels.ludic - a volume per kind of sound, under the master: what the player does, the
|
||||
# animals, the ambience (its loop is the runtime's music channel) and the interface
|
||||
export const AUD_CH_EFFECTS: int = 0
|
||||
export const AUD_CH_WILDLIFE: int = 1
|
||||
export const AUD_CH_AMBIENT: int = 2
|
||||
export const AUD_CH_UI: int = 3
|
||||
export const AUD_CH_N: int = 4
|
||||
|
||||
export function aud_channel(audio_st: AudioState, ch: int) -> float {
|
||||
if ch < 0 or ch >= AUD_CH_N { return 1.0 }
|
||||
return audio_st.ch_gain[ch]
|
||||
}
|
||||
|
||||
export function aud_channel_set(audio_st: mut AudioState, ch: int, v: float) -> void {
|
||||
if ch < 0 or ch >= AUD_CH_N { return }
|
||||
let g = Math.clamp(v, 0.0, 1.0)
|
||||
audio_st.ch_gain[ch] = g
|
||||
if ch == AUD_CH_AMBIENT { Audio.music_volume(fixed(g)) }
|
||||
}
|
||||
|
||||
# a sound in the world on a channel: its gain from where it is, scaled by the channel's
|
||||
export function aud_emit_on(audio_st: mut AudioState, ch: int, h: int, gain: float, pitch: float, pan: float) -> void {
|
||||
aud_emit(audio_st, h, gain * aud_channel(audio_st, ch), pitch, pan)
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ export state AudioState {
|
|||
emitted: int = 0
|
||||
tried: int = 0
|
||||
live: int = 0
|
||||
ch_gain: []float = [1.0, 1.0, 1.0, 1.0] # per channel (channels.ludic), under the master
|
||||
}
|
||||
|
||||
export function aud_emit(audio_st: mut AudioState, h: int, gain: float, pitch: float, pan: float) -> void {
|
||||
|
|
@ -32,7 +33,7 @@ export function aud_emit_at(audio_st: mut AudioState, h: int, x: float, z: float
|
|||
export function aud_ui(audio_st: mut AudioState, h: int) -> void {
|
||||
if h == 0 { return }
|
||||
audio_st.emitted += 1
|
||||
Audio.play(h)
|
||||
Audio.play_at(h, fixed(aud_channel(audio_st, AUD_CH_UI)), fixed(1.0), fixed(0.0))
|
||||
}
|
||||
|
||||
export function aud_music(h: int) -> void { if h != 0 { Audio.play_music(h) } }
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@ numbers float
|
|||
import "ports.ludic"
|
||||
import "place.ludic"
|
||||
import "emit.ludic"
|
||||
import "channels.ludic"
|
||||
import "load.ludic"
|
||||
import "wav.ludic"
|
||||
|
|
|
|||
23
packages/ludic.audio/tests/channels_test.ludic
Normal file
23
packages/ludic.audio/tests/channels_test.ludic
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# channels_test.ludic - a channel scales what is emitted on it, holds to 0..1, and leaves the others
|
||||
import "ludic.audio"
|
||||
program ChannelsTest {
|
||||
numbers float
|
||||
function t_x() -> float { return 0.0 }
|
||||
bind AudioListener { x: fn t_x, z: fn t_x }
|
||||
|
||||
test "a channel's volume scales an emit on it and no other" (audio_st: mut AudioState) {
|
||||
aud_channel_set(audio_st, AUD_CH_WILDLIFE, 0.5)
|
||||
aud_emit_on(audio_st, AUD_CH_WILDLIFE, 7, 0.8, 1.0, 0.0)
|
||||
expect_near(aud_last_gain(audio_st), 0.4, 0.0001)
|
||||
aud_emit_on(audio_st, AUD_CH_EFFECTS, 7, 0.8, 1.0, 0.0)
|
||||
expect_near(aud_last_gain(audio_st), 0.8, 0.0001)
|
||||
}
|
||||
|
||||
test "a channel holds to 0..1, and an unknown one is full" (audio_st: mut AudioState) {
|
||||
aud_channel_set(audio_st, AUD_CH_UI, 3.0)
|
||||
expect_near(aud_channel(audio_st, AUD_CH_UI), 1.0, 0.0001)
|
||||
aud_channel_set(audio_st, AUD_CH_AMBIENT, -1.0)
|
||||
expect_near(aud_channel(audio_st, AUD_CH_AMBIENT), 0.0, 0.0001)
|
||||
expect_near(aud_channel(audio_st, 9), 1.0, 0.0001)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ export state RtAudioState {
|
|||
snd_master: fixed = 1.0 # master volume, applied to every play (and to audio_play_at's gain)
|
||||
snd_rate: fixed = 1.0 # playback rate / pitch (1.0 = normal)
|
||||
snd_music: int = 0 # the handle currently playing as music (0 = none)
|
||||
snd_music_gain: fixed = 1.0 # the music channel's own volume, under the master
|
||||
snd_bank_name: pointers = null
|
||||
snd_bank_id: words = null
|
||||
snd_bank_n: int = 0
|
||||
|
|
@ -138,7 +139,7 @@ function audio_play_music(rt_audio_st: mut RtAudioState, id: int) -> void {
|
|||
if p == null { return }
|
||||
audio_stop_music(rt_audio_st)
|
||||
rt_audio_st.snd_music = id
|
||||
snd_play(p, -1, rt_audio_st.snd_rate, rt_audio_st.snd_master)
|
||||
snd_play(p, -1, rt_audio_st.snd_rate, rt_audio_st.snd_master * rt_audio_st.snd_music_gain)
|
||||
}
|
||||
|
||||
# Stop one sound.
|
||||
|
|
@ -183,6 +184,20 @@ function audio_volume(rt_audio_st: mut RtAudioState, v: fixed) -> void {
|
|||
if rt_audio_st.snd_tab[i] != null { snd_set_volume(rt_audio_st.snd_tab[i], v) }
|
||||
i += 1
|
||||
}
|
||||
audio_music_volume(rt_audio_st, rt_audio_st.snd_music_gain)
|
||||
}
|
||||
|
||||
# The music channel's own volume (0.0 .. 1.0), under the master: a game's ambience loop has a
|
||||
# slider of its own without scaling every one-shot with it.
|
||||
function audio_music_volume(rt_audio_st: mut RtAudioState, v: fixed) -> void {
|
||||
var g = v
|
||||
if g < 0.0 { g = 0.0 }
|
||||
if g > 1.0 { g = 1.0 }
|
||||
rt_audio_st.snd_music_gain = g
|
||||
if not is_windowed() { return }
|
||||
if rt_audio_st.snd_music == 0 { return }
|
||||
let p = audio_get(rt_audio_st, rt_audio_st.snd_music)
|
||||
if p != null { snd_set_volume(p, rt_audio_st.snd_master * g) }
|
||||
}
|
||||
|
||||
# Playback rate / pitch (1.0 = normal, 0.5 = an octave down, 2.0 = up).
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ namespace Audio {
|
|||
alias stop_music() = audio_stop_music
|
||||
alias stop_all() = audio_stop_all
|
||||
alias volume(v) = audio_volume
|
||||
alias music_volume(v) = audio_music_volume # the music channel under the master
|
||||
alias pitch(v) = audio_pitch
|
||||
alias is_playing(id) = audio_is_playing
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue