Skip to content

Commit 2744467

Browse files
authored
Merge pull request cloudwu#97 from yuchanns/feat-audio
feat(audio): support sound manipulation
2 parents 2ba9407 + d50e0ca commit 2744467

10 files changed

Lines changed: 942 additions & 86 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
bin
22
build
3+
compile_commands.json
4+
.cache
35
node_modules
46
.pnpm-store
57
pnpm-debug.log*

asset/sounds.dl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
--
22
name : bloop
33
filename : asset/sounds/bloop_x.wav
4+
5+
--
6+
name : bloop_loop
7+
filename : asset/sounds/bloop_x.wav
8+
group : music
9+
volume : 0.6
10+
pitch : 0.8
11+
loop : true
12+
stream : true

docs/soluna.lua

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,95 @@
1212
--- Sprite bundle mapping sprite names to IDs
1313
---@alias SpriteBundle table<string, Sprite?>
1414

15+
--- Audio playback options
16+
---
17+
--- Values provided here override defaults from `sounds.dl`.
18+
--- `stream = true` is intended for long-running audio such as background music.
19+
---
20+
---@class soluna.AudioPlayOptions
21+
---@field group? string Audio bus name
22+
---@field volume? number Linear volume multiplier
23+
---@field pan? number Stereo pan in range `[-1.0, 1.0]`
24+
---@field pitch? number Pitch multiplier
25+
---@field loop? boolean Whether playback should loop
26+
---@field stream? boolean Whether to stream instead of fully decoding up front
27+
28+
--- Audio voice handle returned by `soluna.play_sound()`
29+
---
30+
--- Represents one active playback instance.
31+
--- When a voice finishes naturally, `:playing()` returns `false`.
32+
--- Mutating methods return `false` when the voice is no longer valid.
33+
---
34+
---@class soluna.AudioVoice
35+
local AudioVoice = {}
36+
37+
---
38+
--- Stops playback
39+
---
40+
---@param fade_seconds? number Optional fade-out duration in seconds
41+
---@return boolean ok False when the voice is no longer valid
42+
function AudioVoice:stop(fade_seconds) end
43+
44+
---
45+
--- Checks whether the voice is still playing
46+
---
47+
---@return boolean playing
48+
function AudioVoice:playing() end
49+
50+
---
51+
--- Sets voice volume
52+
---
53+
---@param volume number Linear volume multiplier
54+
---@return boolean ok False when the voice is no longer valid
55+
function AudioVoice:set_volume(volume) end
56+
57+
---
58+
--- Sets voice pan
59+
---
60+
---@param pan number Stereo pan in range `[-1.0, 1.0]`
61+
---@return boolean ok False when the voice is no longer valid
62+
function AudioVoice:set_pan(pan) end
63+
64+
---
65+
--- Sets voice pitch
66+
---
67+
---@param pitch number Pitch multiplier
68+
---@return boolean ok False when the voice is no longer valid
69+
function AudioVoice:set_pitch(pitch) end
70+
71+
---
72+
--- Enables or disables looping
73+
---
74+
---@param loop boolean
75+
---@return boolean ok False when the voice is no longer valid
76+
function AudioVoice:set_loop(loop) end
77+
78+
---
79+
--- Seeks to a playback position in seconds
80+
---
81+
---@param seconds number Target playback time in seconds
82+
---@return boolean ok False when the voice is no longer valid
83+
function AudioVoice:seek(seconds) end
84+
85+
---
86+
--- Returns the current playback position in seconds
87+
---
88+
---@return number? seconds Current playback time
89+
---@return string? err Error message when the voice is no longer valid
90+
function AudioVoice:tell() end
91+
92+
--- Audio bus handle returned by `soluna.audio_bus()`
93+
---
94+
---@class soluna.AudioBus
95+
local AudioBus = {}
96+
97+
---
98+
--- Sets bus volume
99+
---
100+
---@param volume number Linear volume multiplier
101+
---@return boolean ok False when the bus name is invalid
102+
function AudioBus:set_volume(volume) end
103+
15104
---@class soluna
16105
local soluna = {}
17106

@@ -65,4 +154,50 @@ function soluna.gamedir(name) end
65154
---@return SpriteBundle sprites Sprite bundle mapping sprite names to IDs
66155
function soluna.load_sprites(filename) end
67156

157+
---
158+
--- Loads audio definitions from a datalist file
159+
---
160+
--- Each entry in `sounds.dl` must define:
161+
--- - `name`
162+
--- - `filename`
163+
---
164+
--- Optional entry fields:
165+
--- - `group` (defaults to `"sound"`)
166+
--- - `volume`
167+
--- - `pan`
168+
--- - `pitch`
169+
--- - `loop`
170+
--- - `stream`
171+
---
172+
--- `load_sounds()` may be called multiple times. Each bundle registers more sound definitions.
173+
--- Re-loading the same bundle is ignored. Sound names must stay unique across loaded bundles.
174+
--- Audio buses are created from the `group` field of loaded sound entries and reused by name.
175+
---
176+
---@param filename string Path to audio definition file (.dl format)
177+
function soluna.load_sounds(filename) end
178+
179+
---
180+
--- Plays a sound and returns a voice handle
181+
---
182+
--- The returned handle represents the active playback instance, not the sound definition.
183+
--- Background music uses the same API; a typical music entry sets `group = "music"`,
184+
--- `loop = true`, and `stream = true` in `sounds.dl`.
185+
---
186+
---@param name string Sound definition name from `sounds.dl`
187+
---@param opts? soluna.AudioPlayOptions Per-playback option overrides
188+
---@return soluna.AudioVoice? voice Active playback handle
189+
---@return string? err Error message on failure
190+
function soluna.play_sound(name, opts) end
191+
192+
---
193+
--- Returns an audio bus handle
194+
---
195+
--- Buses are created from the `group` field in loaded `sounds.dl` entries.
196+
--- When a sound entry omits `group`, it uses the default bus name `"sound"`.
197+
---
198+
---@param name string Audio bus name
199+
---@return soluna.AudioBus? bus Bus handle
200+
---@return string? err Error message when the bus does not exist
201+
function soluna.audio_bus(name) end
202+
68203
return soluna

make.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,19 @@ lm:dll "sample" {
162162

163163
lm:import "script/act_targets.lua"
164164

165+
lm:runlua "cc" {
166+
script = "script/compile_commands.lua",
167+
args = {
168+
"build/build.ninja",
169+
"compile_commands.json",
170+
},
171+
inputs = {
172+
"build/build.ninja",
173+
"script/compile_commands.lua",
174+
},
175+
outputs = {
176+
"compile_commands.json",
177+
},
178+
}
179+
165180
lm:default "soluna"

script/compile_commands.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
local subprocess = require "bee.subprocess"
2+
3+
local ninja_file, output = ...
4+
5+
assert(ninja_file, "missing ninja file path")
6+
assert(output, "missing output path")
7+
8+
local process = assert(subprocess.spawn {
9+
"ninja",
10+
"-f",
11+
ninja_file,
12+
"-t",
13+
"compdb",
14+
"-x",
15+
searchPath = true,
16+
stdout = true,
17+
})
18+
19+
local content = process.stdout:read "a"
20+
local code = process:wait()
21+
if code ~= 0 then
22+
os.exit(code, true)
23+
end
24+
25+
local file <close> = assert(io.open(output, "wb"))
26+
file:write(content)

0 commit comments

Comments
 (0)