comparison semiconginev2/old/audio.nim @ 1218:56781cc0fc7c compiletime-tests

did: renamge main package
author sam <sam@basx.dev>
date Wed, 17 Jul 2024 21:01:37 +0700
parents semicongine/old/audio.nim@a3eb305bcac2
children
comparison
equal deleted inserted replaced
1217:f819a874058f 1218:56781cc0fc7c
1 import std/monotimes
2 import std/strformat
3 import std/times
4 import std/tables
5 import std/locks
6 import std/logging except Level
7
8 when defined(windows): # used for setting audio thread priority
9 import winim except Level
10 when defined(linux):
11 import std/posix
12
13 import ./core
14 import ./platform/audio
15 import ./resources
16
17 export audiotypes
18
19 const NBUFFERS = 32
20 const BUFFERSAMPLECOUNT = 256
21
22 type
23 Playback = object
24 sound: Sound
25 position: int
26 loop: bool
27 levelLeft: Level
28 levelRight: Level
29 paused: bool
30 Track = object
31 playing: Table[uint64, Playback]
32 level: Level
33 targetLevel: Level
34 fadeTime: float
35 fadeStep: float
36 Mixer* = object
37 playbackCounter: uint64
38 tracks: Table[string, Track]
39 sounds*: Table[string, Sound]
40 level: Level
41 device: NativeSoundDevice
42 lock: Lock
43 buffers: seq[SoundData]
44 currentBuffer: int
45 lastUpdate: MonoTime
46
47 proc initMixer(): Mixer =
48 result = Mixer(
49 tracks: {"": Track(level: 1'f)}.toTable,
50 level: 1'f,
51 )
52 result.lock.initLock()
53
54 proc setupDevice(mixer: var Mixer) =
55 # call this inside audio thread
56 var bufferaddresses: seq[ptr SoundData]
57 for i in 0 ..< NBUFFERS:
58 mixer.buffers.add newSeq[Sample](BUFFERSAMPLECOUNT)
59 for i in 0 ..< mixer.buffers.len:
60 bufferaddresses.add (addr mixer.buffers[i])
61 mixer.device = OpenSoundDevice(AUDIO_SAMPLE_RATE, bufferaddresses)
62
63 proc LoadSound*(mixer: var Mixer, name: string, resource: string) =
64 assert not (name in mixer.sounds)
65 mixer.sounds[name] = LoadAudio(resource)
66
67 proc AddSound*(mixer: var Mixer, name: string, sound: Sound) =
68 assert not (name in mixer.sounds)
69 mixer.sounds[name] = sound
70
71 proc ReplaceSound*(mixer: var Mixer, name: string, sound: Sound) =
72 assert (name in mixer.sounds)
73 mixer.sounds[name] = sound
74
75 proc AddTrack*(mixer: var Mixer, name: string, level: Level = 1'f) =
76 assert not (name in mixer.tracks)
77 mixer.lock.withLock():
78 mixer.tracks[name] = Track(level: level)
79
80 proc Play*(mixer: var Mixer, soundName: string, track = "", stopOtherSounds = false, loop = false, levelLeft, levelRight: Level): uint64 =
81 assert track in mixer.tracks, &"Track '{track}' does not exists"
82 assert soundName in mixer.sounds, soundName & " not loaded"
83 mixer.lock.withLock():
84 if stopOtherSounds:
85 mixer.tracks[track].playing.clear()
86 mixer.tracks[track].playing[mixer.playbackCounter] = Playback(
87 sound: mixer.sounds[soundName],
88 position: 0,
89 loop: loop,
90 levelLeft: levelLeft,
91 levelRight: levelRight,
92 paused: false,
93 )
94 result = mixer.playbackCounter
95 inc mixer.playbackCounter
96
97 proc Play*(mixer: var Mixer, soundName: string, track = "", stopOtherSounds = false, loop = false, level: Level = 1'f): uint64 =
98 Play(
99 mixer = mixer,
100 soundName = soundName,
101 track = track,
102 stopOtherSounds = stopOtherSounds,
103 loop = loop,
104 levelLeft = level,
105 levelRight = level
106 )
107
108 proc Stop*(mixer: var Mixer) =
109 mixer.lock.withLock():
110 for track in mixer.tracks.mvalues:
111 track.playing.clear()
112
113 proc GetLevel*(mixer: var Mixer): Level = mixer.level
114 proc GetLevel*(mixer: var Mixer, track: string): Level = mixer.tracks[track].level
115 proc GetLevel*(mixer: var Mixer, playbackId: uint64): (Level, Level) =
116 for track in mixer.tracks.mvalues:
117 if playbackId in track.playing:
118 return (track.playing[playbackId].levelLeft, track.playing[playbackId].levelRight)
119
120 proc SetLevel*(mixer: var Mixer, level: Level) = mixer.level = level
121 proc SetLevel*(mixer: var Mixer, track: string, level: Level) =
122 mixer.lock.withLock():
123 mixer.tracks[track].level = level
124 proc SetLevel*(mixer: var Mixer, playbackId: uint64, levelLeft, levelRight: Level) =
125 mixer.lock.withLock():
126 for track in mixer.tracks.mvalues:
127 if playbackId in track.playing:
128 track.playing[playbackId].levelLeft = levelLeft
129 track.playing[playbackId].levelRight = levelRight
130 proc SetLevel*(mixer: var Mixer, playbackId: uint64, level: Level) =
131 SetLevel(mixer, playbackId, level, level)
132
133 proc Stop*(mixer: var Mixer, track: string) =
134 assert track in mixer.tracks
135 mixer.lock.withLock():
136 mixer.tracks[track].playing.clear()
137
138 proc Stop*(mixer: var Mixer, playbackId: uint64) =
139 mixer.lock.withLock():
140 for track in mixer.tracks.mvalues:
141 if playbackId in track.playing:
142 track.playing.del(playbackId)
143 break
144
145 proc Pause*(mixer: var Mixer, value: bool) =
146 mixer.lock.withLock():
147 for track in mixer.tracks.mvalues:
148 for playback in track.playing.mvalues:
149 playback.paused = value
150
151 proc Pause*(mixer: var Mixer, track: string, value: bool) =
152 mixer.lock.withLock():
153 for playback in mixer.tracks[track].playing.mvalues:
154 playback.paused = value
155
156 proc Pause*(mixer: var Mixer, playbackId: uint64, value: bool) =
157 mixer.lock.withLock():
158 for track in mixer.tracks.mvalues:
159 if playbackId in track.playing:
160 track.playing[playbackId].paused = value
161
162 proc Pause*(mixer: var Mixer) = mixer.Pause(true)
163 proc Pause*(mixer: var Mixer, track: string) = mixer.Pause(track, true)
164 proc Pause*(mixer: var Mixer, playbackId: uint64) = mixer.Pause(playbackId, true)
165 proc Unpause*(mixer: var Mixer) = mixer.Pause(false)
166 proc Unpause*(mixer: var Mixer, track: string) = mixer.Pause(track, false)
167 proc Unpause*(mixer: var Mixer, playbackId: uint64) = mixer.Pause(playbackId, false)
168
169 proc FadeTo*(mixer: var Mixer, track: string, level: Level, time: float) =
170 mixer.tracks[track].targetLevel = level
171 mixer.tracks[track].fadeTime = time
172 mixer.tracks[track].fadeStep = level.float - mixer.tracks[track].level.float / time
173
174 proc IsPlaying*(mixer: var Mixer): bool =
175 mixer.lock.withLock():
176 for track in mixer.tracks.mvalues:
177 for playback in track.playing.values:
178 if not playback.paused:
179 return true
180 return false
181
182 proc IsPlaying*(mixer: var Mixer, track: string): bool =
183 mixer.lock.withLock():
184 if mixer.tracks.contains(track):
185 for playback in mixer.tracks[track].playing.values:
186 if not playback.paused:
187 return true
188 return false
189
190 func applyLevel(sample: Sample, levelLeft, levelRight: Level): Sample =
191 [int16(float(sample[0]) * levelLeft), int16(float(sample[1]) * levelRight)]
192
193 func clip(value: int32): int16 =
194 int16(max(min(int32(high(int16)), value), int32(low(int16))))
195
196 # used for combining sounds
197 func mix(a, b: Sample): Sample =
198 [
199 clip(int32(a[0]) + int32(b[0])),
200 clip(int32(a[1]) + int32(b[1])),
201 ]
202
203 proc updateSoundBuffer(mixer: var Mixer) =
204 let t = getMonoTime()
205
206 let tDebug = getTime()
207 # echo ""
208 # echo tDebug
209
210 let dt = (t - mixer.lastUpdate).inNanoseconds.float64 / 1_000_000_000'f64
211 mixer.lastUpdate = t
212
213 # update fadings
214 for track in mixer.tracks.mvalues:
215 if track.fadeTime > 0:
216 track.fadeTime -= dt
217 track.level = (track.level.float64 + track.fadeStep.float64 * dt).clamp(Level.low, Level.high)
218 if track.fadeTime <= 0:
219 track.level = track.targetLevel
220 # mix
221 for i in 0 ..< mixer.buffers[mixer.currentBuffer].len:
222 var mixedSample = [0'i16, 0'i16]
223 mixer.lock.withLock():
224 for track in mixer.tracks.mvalues:
225 var stoppedSounds: seq[uint64]
226 for (id, playback) in track.playing.mpairs:
227 if playback.paused:
228 continue
229 let sample = applyLevel(
230 playback.sound[][playback.position],
231 mixer.level * track.level * playback.levelLeft,
232 mixer.level * track.level * playback.levelRight,
233 )
234 mixedSample = mix(mixedSample, sample)
235 inc playback.position
236 if playback.position >= playback.sound[].len:
237 if playback.loop:
238 playback.position = 0
239 else:
240 stoppedSounds.add id
241 for id in stoppedSounds:
242 track.playing.del(id)
243 mixer.buffers[mixer.currentBuffer][i] = mixedSample
244 # send data to sound device
245 # echo getTime() - tDebug
246 mixer.device.WriteSoundData(mixer.currentBuffer)
247 # echo getTime() - tDebug
248 mixer.currentBuffer = (mixer.currentBuffer + 1) mod mixer.buffers.len
249
250 # DSP functions
251 # TODO: finish implementation, one day
252
253 #[
254 #
255 proc lowPassFilter(data: var SoundData, cutoff: int) =
256 let alpha = float(cutoff) / AUDIO_SAMPLE_RATE
257 var value = data[0]
258 for i in 0 ..< data.len:
259 value[0] += int16(alpha * float(data[i][0] - value[0]))
260 value[1] += int16(alpha * float(data[i][1] - value[1]))
261 data[i] = value
262
263 proc downsample(data: var SoundData, n: int) =
264 let newLen = (data.len - 1) div n + 1
265 for i in 0 ..< newLen:
266 data[i] = data[i * n]
267 data.setLen(newLen)
268
269 proc upsample(data: var SoundData, m: int) =
270 data.setLen(data.len * m)
271 var i = data.len - 1
272 while i < 0:
273 if i mod m == 0:
274 data[i] = data[i div m]
275 else:
276 data[i] = [0, 0]
277 i.dec
278
279 proc slowdown(data: var SoundData, m, n: int) =
280 data.upsample(m)
281 # TODO
282 # data.lowPassFilter(m)
283 data.downsample(n)
284
285 ]#
286
287 proc destroy(mixer: var Mixer) =
288 mixer.lock.deinitLock()
289 mixer.device.CloseSoundDevice()
290
291 # Threaded implementation, usually used for audio
292
293 var
294 mixer* = createShared(Mixer)
295 audiothread: Thread[void]
296
297 proc audioWorker() {.thread.} =
298 mixer[].setupDevice()
299 onThreadDestruction(proc() = mixer[].lock.withLock(mixer[].destroy()); freeShared(mixer))
300 while true:
301 mixer[].updateSoundBuffer()
302
303 proc StartMixerThread*() =
304 mixer[] = initMixer()
305 audiothread.createThread(audioWorker)
306 debug "Created audio thread"
307 when defined(windows):
308 SetThreadPriority(audiothread.handle(), THREAD_PRIORITY_TIME_CRITICAL)
309 when defined(linux):
310 discard pthread_setschedprio(Pthread(audiothread.handle()), cint(-20))