ASW Lib
A.D.S. Games SDL Wrapper Library. A library targeted at Allegro4 users who want to switch to SDL3 and use modern c++.
Loading...
Searching...
No Matches
sound.cpp
Go to the documentation of this file.
2
3#include <SDL3_mixer/SDL_mixer.h>
4#include <algorithm>
5#include <array>
6#include <atomic>
7#include <cmath>
8
10#include "./asw/modules/log.h"
12
13namespace {
14constexpr size_t NUM_VOICES = 32;
15constexpr size_t NUM_BUSES = static_cast<size_t>(asw::sound::Bus::Count);
16constexpr int MAX_FILTER_CHANNELS = 8;
17
18// Cutoff at full occlusion, and the level it takes the volume down to
19constexpr float OCCLUDED_CUTOFF_HZ = 500.0F;
20constexpr float OPEN_CUTOFF_HZ = 20000.0F;
21constexpr float OCCLUDED_GAIN = 0.5F;
22
23// Keep doppler musical, even for very fast sources
24constexpr float MIN_DOPPLER = 0.5F;
25constexpr float MAX_DOPPLER = 2.0F;
26
27// One pole low pass run on the audio thread. The game thread only writes the
28// cutoff, the audio thread owns the filter state.
29struct LowPass {
30 std::atomic<float> cutoff_hz { OPEN_CUTOFF_HZ };
31 std::atomic<bool> reset { true };
32 std::array<float, MAX_FILTER_CHANNELS> state {};
33};
34
35struct Voice {
36 MIX_Track* track = nullptr;
37 uint32_t generation = 0;
38
39 bool positional = false;
43
44 float volume = 1.0F;
45 float pan = 0.0F;
46 float pitch = 1.0F;
47 float occlusion = 0.0F;
49 int priority = 0;
50
51 // Gain after bus, distance and occlusion, used to pick a voice to steal
52 float effective_gain = 0.0F;
53
54 // What was last sent to the track. Each SDL_mixer call takes the mixer
55 // lock, so unchanged values are not sent again.
56 struct Sent {
57 enum class Placement { None, Stereo, Point };
59 float a = 0.0F;
60 float b = 0.0F;
61 float gain = -1.0F;
62 float ratio = -1.0F;
63 } sent;
64
66};
67
68struct Duck {
69 float gain = 1.0F;
70 float target = 1.0F;
71 float hold_s = 0.0F;
72 float fade_s = 0.2F;
73};
74
75float master_volume = 1.0F;
76std::array<float, NUM_BUSES> bus_volume = { 1.0F, 1.0F, 1.0F, 1.0F, 1.0F };
77std::array<Duck, NUM_BUSES> ducks {};
78
79std::array<Voice, NUM_VOICES> voices;
80MIX_Track* music_track = nullptr;
81float music_base_volume = 1.0F;
82float music_sent_gain = -1.0F;
83MIX_Mixer* mixer = nullptr;
84
85// Counts shutdowns, each one frees all loaded audio
86uint32_t session = 0;
87
91float doppler_factor = 1.0F;
92float speed_of_sound = 3000.0F;
93
94uint64_t last_update_ns = 0;
95
97{
98 const auto i = static_cast<size_t>(bus);
99 return bus_volume[i] * ducks[i].gain;
100}
101
102bool is_active(const Voice& v)
103{
104 return v.track != nullptr && (MIX_TrackPlaying(v.track) || MIX_TrackPaused(v.track));
105}
106
107void SDLCALL low_pass_callback(
108 void* userdata, MIX_Track* /*track*/, const SDL_AudioSpec* spec, float* pcm, int samples)
109{
110 auto* filter = static_cast<LowPass*>(userdata);
111 const int channels = spec->channels;
112 if (channels <= 0 || channels > MAX_FILTER_CHANNELS || samples < channels) {
113 return;
114 }
115
116 auto& state = filter->state;
117 if (filter->reset.exchange(false)) {
118 state.fill(0.0F);
119 }
120
121 const int frames = samples / channels;
122 const float cutoff = filter->cutoff_hz.load(std::memory_order_relaxed);
123
124 // Pass through, but keep the state on the signal so turning the filter
125 // on later does not click
126 if (cutoff >= OPEN_CUTOFF_HZ) {
127 for (int c = 0; c < channels; ++c) {
128 state[c] = pcm[((frames - 1) * channels) + c];
129 }
130 return;
131 }
132
133 const float alpha = 1.0F
134 - std::exp((-2.0F * SDL_PI_F * cutoff) / static_cast<float>(std::max(1, spec->freq)));
135
136 for (int f = 0; f < frames; ++f) {
137 for (int c = 0; c < channels; ++c) {
138 float& sample = pcm[(f * channels) + c];
139 state[c] += alpha * (sample - state[c]);
140 sample = state[c];
141 }
142 }
143}
144
145float attenuate(const asw::sound::Attenuation& att, float distance)
146{
147 const float near = std::max(att.min_distance, 0.001F);
148 const float far = std::max(att.max_distance, near + 0.001F);
149
150 if (distance <= near) {
151 return 1.0F;
152 }
153 if (distance >= far) {
154 return 0.0F;
155 }
156
157 // Inverse curves are shifted and scaled to hit exactly zero at far, so
158 // sounds fade out instead of cutting off
159 switch (att.rolloff) {
161 return 1.0F - ((distance - near) / (far - near));
162
164 const float floor = near / far;
165 return ((near / distance) - floor) / (1.0F - floor);
166 }
167
169 const float floor = (near * near) / (far * far);
170 return (((near * near) / (distance * distance)) - floor) / (1.0F - floor);
171 }
172 }
173
174 return 1.0F;
175}
176
177float doppler(const Voice& v)
178{
179 if (doppler_factor <= 0.0F || speed_of_sound <= 0.0F) {
180 return 1.0F;
181 }
182
183 // Same model as OpenAL, along the line from source to listener
184 const auto to_listener = listener_position - v.position;
185 const float distance = to_listener.magnitude();
186 if (distance < 0.001F) {
187 return 1.0F;
188 }
189
190 const auto dir = to_listener / distance;
191 const float limit = speed_of_sound * 0.99F;
192 const float listener_speed
193 = std::clamp(listener_velocity.dot(dir) * doppler_factor, -limit, limit);
194 const float source_speed = std::clamp(v.velocity.dot(dir) * doppler_factor, -limit, limit);
195
196 const float ratio = (speed_of_sound - listener_speed) / (speed_of_sound - source_speed);
197 return std::clamp(ratio, MIN_DOPPLER, MAX_DOPPLER);
198}
199
200void set_stereo_pan(Voice& v, float pan)
201{
202 pan = std::clamp(pan, -1.0F, 1.0F);
203 if (v.sent.placement == Voice::Sent::Placement::Stereo && v.sent.a == pan) {
204 return;
205 }
206 v.sent = { .placement = Voice::Sent::Placement::Stereo,
207 .a = pan,
208 .gain = v.sent.gain,
209 .ratio = v.sent.ratio };
210
211 // Equal power panning
212 MIX_StereoGains gains;
213 gains.left = std::sqrt((1.0F - pan) * 0.5F);
214 gains.right = std::sqrt((1.0F + pan) * 0.5F);
215 MIX_SetTrackStereo(v.track, &gains);
216}
217
218void set_point(Voice& v, float x, float z)
219{
220 if (v.sent.placement == Voice::Sent::Placement::Point && v.sent.a == x && v.sent.b == z) {
221 return;
222 }
223 v.sent = { .placement = Voice::Sent::Placement::Point,
224 .a = x,
225 .b = z,
226 .gain = v.sent.gain,
227 .ratio = v.sent.ratio };
228
229 const MIX_Point3D point { x, 0.0F, z };
230 MIX_SetTrack3DPosition(v.track, &point);
231}
232
233// Push a voice's state to its track
234void apply(Voice& v)
235{
236 float gain = v.volume * bus_gain(v.bus);
237 float ratio = v.pitch;
238
239 if (v.positional) {
240 const auto delta = v.position - listener_position;
241 const float distance = delta.magnitude();
242 gain *= attenuate(v.attenuation, distance);
243 ratio *= doppler(v);
244
246 // Unit length, so SDL_mixer only picks the direction and our
247 // attenuation sets the volume. Down the screen is behind.
248 if (distance > 0.001F) {
249 set_point(v, delta.x / distance, delta.y / distance);
250 } else {
251 set_point(v, 0.0F, 0.0F);
252 }
253 } else {
254 // Sine of the angle to the listener, centred inside min_distance.
255 // Kept off the extremes so the far ear still hears a little.
256 const float spread = std::max(distance, v.attenuation.min_distance);
257 set_stereo_pan(v, (delta.x / std::max(spread, 0.001F)) * 0.8F);
258 }
259 } else {
260 set_stereo_pan(v, v.pan);
261 }
262
263 gain *= 1.0F - ((1.0F - OCCLUDED_GAIN) * v.occlusion);
264 v.filter.cutoff_hz.store(v.occlusion > 0.0F
267 std::memory_order_relaxed);
268
269 v.effective_gain = std::clamp(gain, 0.0F, 1.0F);
270 if (v.sent.gain != v.effective_gain) {
272 MIX_SetTrackGain(v.track, v.effective_gain);
273 }
274
275 ratio = std::clamp(ratio, 0.01F, 100.0F);
276 if (v.sent.ratio != ratio) {
277 v.sent.ratio = ratio;
278 MIX_SetTrackFrequencyRatio(v.track, ratio);
279 }
280}
281
283{
284 const float gain = std::clamp(music_base_volume * bus_gain(asw::sound::Bus::Music), 0.0F, 1.0F);
285 if (music_sent_gain != gain) {
286 music_sent_gain = gain;
287 MIX_SetTrackGain(music_track, gain);
288 }
289}
290
291// Free voice, or the quietest voice this priority may replace
292int find_voice(int priority)
293{
294 int best = -1;
295 for (size_t i = 0; i < voices.size(); ++i) {
296 const auto& v = voices[i];
297 if (v.track == nullptr) {
298 continue;
299 }
300 if (!is_active(v)) {
301 return static_cast<int>(i);
302 }
303 if (v.priority > priority) {
304 continue;
305 }
306
307 if (best < 0 || v.priority < voices[best].priority
308 || (v.priority == voices[best].priority
309 && v.effective_gain < voices[best].effective_gain)) {
310 best = static_cast<int>(i);
311 }
312 }
313 return best;
314}
315
316float vary(float value, float variation)
317{
318 if (variation <= 0.0F) {
319 return value;
320 }
321 return value * (1.0F + asw::random::between(-variation, variation));
322}
323
324} // namespace
325
326// ---- SoundHandle ----
327//
328
329asw::sound::SoundHandle::SoundHandle(int voice, uint32_t generation)
330 : voice(voice)
331 , generation(generation)
332{
333}
334
335namespace {
336// Voice the handle points at, or nullptr if the handle is stale
337Voice* resolve(int voice, uint32_t generation)
338{
339 if (voice < 0 || static_cast<size_t>(voice) >= voices.size()) {
340 return nullptr;
341 }
342
343 auto& v = voices[voice];
344 if (v.generation != generation || !is_active(v)) {
345 return nullptr;
346 }
347 return &v;
348}
349} // namespace
350
352{
353 return resolve(voice, generation) != nullptr;
354}
355
356void asw::sound::SoundHandle::stop(float fade_out_s) const
357{
358 if (auto* v = resolve(voice, generation)) {
359 MIX_StopTrack(v->track,
360 MIX_TrackMSToFrames(v->track, static_cast<Sint64>(fade_out_s * 1000.0F)));
361 }
362}
363
365{
366 if (auto* v = resolve(voice, generation)) {
367 MIX_PauseTrack(v->track);
368 }
369}
370
372{
373 if (auto* v = resolve(voice, generation)) {
374 MIX_ResumeTrack(v->track);
375 }
376}
377
379{
380 if (auto* v = resolve(voice, generation)) {
381 v->volume = std::clamp(volume, 0.0F, 1.0F);
382 apply(*v);
383 }
384}
385
387{
388 if (auto* v = resolve(voice, generation)) {
389 v->pitch = pitch;
390 apply(*v);
391 }
392}
393
395{
396 if (auto* v = resolve(voice, generation)) {
397 v->positional = false;
398 v->pan = pan;
399 apply(*v);
400 }
401}
402
404{
405 if (auto* v = resolve(voice, generation)) {
406 v->positional = true;
407 v->position = position;
408 apply(*v);
409 }
410}
411
413{
414 if (auto* v = resolve(voice, generation)) {
415 v->velocity = velocity;
416 apply(*v);
417 }
418}
419
421{
422 if (auto* v = resolve(voice, generation)) {
423 v->occlusion = std::clamp(amount, 0.0F, 1.0F);
424 apply(*v);
425 }
426}
427
429{
430 if (auto* v = resolve(voice, generation)) {
431 v->attenuation = attenuation;
432 apply(*v);
433 }
434}
435
436// ---- Module ----
437//
438
440{
441 return mixer;
442}
443
445{
446 auto* m = mixer;
447 mixer = nullptr;
448 if (m != nullptr) {
449 // Destroying the mixer destroys its tracks
450 MIX_DestroyMixer(m);
451 }
452 for (auto& v : voices) {
453 v.track = nullptr;
454 }
455 music_track = nullptr;
456 MIX_Quit();
457 session++;
458}
459
461{
462 return session;
463}
464
466{
467 // Before MIX_Init, so the init count matches the one MIX_Quit in _shutdown
468 if (mixer != nullptr) {
469 asw::log::warn("Mixer already initialized");
470 return true;
471 }
472
473 if (!MIX_Init()) {
474 asw::log::error("Failed to initialize SDL_mixer: {}", SDL_GetError());
475 return false;
476 }
477
478 // Initialize SDL_mixer
479 SDL_AudioSpec spec;
480 spec.format = SDL_AUDIO_S16LE;
481 spec.freq = 44100;
482 spec.channels = 2;
483
484 mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec);
485 if (mixer == nullptr) {
486 asw::log::error("Failed to create mixer: {}", SDL_GetError());
487 return false;
488 }
489
490 for (auto& v : voices) {
491 v.track = MIX_CreateTrack(mixer);
492 v.sent = { };
493 if (v.track == nullptr) {
494 asw::log::error("Failed to create track: {}", SDL_GetError());
495 return false;
496 }
497 MIX_SetTrackCookedCallback(v.track, low_pass_callback, &v.filter);
498 }
499
500 music_track = MIX_CreateTrack(mixer);
501 music_sent_gain = -1.0F;
502 if (music_track == nullptr) {
503 asw::log::error("Failed to create music track: {}", SDL_GetError());
504 return false;
505 }
506
507 last_update_ns = SDL_GetTicksNS();
508 return true;
509}
510
512{
513 if (mixer == nullptr) {
514 return;
515 }
516
517 const uint64_t now = SDL_GetTicksNS();
518 const float dt = static_cast<float>(now - last_update_ns) / 1'000'000'000.0F;
519 last_update_ns = now;
520
521 // Ducking: fade towards the target while held, then back to full
522 for (auto& d : ducks) {
523 float target = 1.0F;
524 if (d.hold_s > 0.0F) {
525 d.hold_s -= dt;
526 target = d.target;
527 } else {
528 d.target = 1.0F;
529 }
530
531 const float step = dt / std::max(d.fade_s, 0.001F);
532 d.gain = d.gain < target ? std::min(d.gain + step, target) : std::max(d.gain - step, target);
533 }
534
535 for (auto& v : voices) {
536 if (is_active(v)) {
537 apply(v);
538 }
539 }
540
541 apply_music();
542}
543
544// Creates handles, which only the sound module may do
546 static SoundHandle make(int voice, uint32_t generation) { return { voice, generation }; }
547};
548
549namespace {
550// Start a sample on a voice. Position is set before playback starts, so a
551// positional sound never plays a few samples unpanned at full volume.
553 const asw::Vec2<float>* position)
554{
555 if (mixer == nullptr || sample == nullptr || static_cast<size_t>(options.bus) >= NUM_BUSES) {
556 return {};
557 }
558
559 const int index = find_voice(options.priority);
560 if (index < 0) {
561 return {};
562 }
563
564 auto& v = voices[index];
565 if (is_active(v)) {
566 MIX_StopTrack(v.track, 0);
567 }
568
569 v.generation++;
570 v.positional = position != nullptr;
571 v.position = position != nullptr ? *position : asw::Vec2<float>(0.0F, 0.0F);
572 v.velocity = asw::Vec2<float>(0.0F, 0.0F);
573 v.attenuation = options.attenuation;
574 v.volume = std::clamp(vary(options.volume, options.volume_variation), 0.0F, 1.0F);
575 v.pan = options.pan;
576 v.pitch = vary(options.pitch, options.pitch_variation);
577 v.occlusion = 0.0F;
578 v.bus = options.bus;
579 v.priority = options.priority;
580 v.filter.reset.store(true);
581
582 // Send everything for the new sound
583 v.sent = { };
584 MIX_SetTrackAudio(v.track, sample.get());
585 apply(v);
586
587 const SDL_PropertiesID props = SDL_CreateProperties();
588 SDL_SetNumberProperty(props, MIX_PROP_PLAY_LOOPS_NUMBER, options.loop ? -1 : 0);
589 if (options.fade_in_s > 0.0F) {
590 SDL_SetNumberProperty(props, MIX_PROP_PLAY_FADE_IN_MILLISECONDS_NUMBER,
591 static_cast<Sint64>(options.fade_in_s * 1000.0F));
592 }
593 const bool played = MIX_PlayTrack(v.track, props);
594 SDL_DestroyProperties(props);
595
596 if (!played) {
597 return {};
598 }
599
600 return asw::sound::HandleAccess::make(index, v.generation);
601}
602} // namespace
603
605{
606 return start(sample, options, nullptr);
607}
608
610 const asw::Sample& sample, float volume, float pan, bool loop)
611{
612 PlayOptions options;
613 options.volume = volume;
614 options.pan = pan;
615 options.loop = loop;
616 return play(sample, options);
617}
618
620 const asw::Sample& sample, const Vec2<float>& position, const PlayOptions& options)
621{
622 // Skip one shots nobody can hear, so they do not take a voice
623 const float distance = (position - listener_position).magnitude();
624 if (!options.loop && attenuate(options.attenuation, distance) <= 0.0F) {
625 return {};
626 }
627
628 return start(sample, options, &position);
629}
630
631asw::sound::SoundHandle asw::sound::play_at(const asw::Sample& sample, float x, float volume)
632{
633 PlayOptions options;
634 options.volume = volume;
635 return play_at(sample, x, options);
636}
637
639 const asw::Sample& sample, float x, const PlayOptions& options)
640{
641 const auto width = static_cast<float>(asw::display::get_logical_size().x);
642 if (width <= 0.0F) {
643 return play(sample, options);
644 }
645
646 // -1 at the left edge, 1 at the right edge
647 const float center = width / 2.0F;
648 const float offset = (x - center) / center;
649
650 // Full volume on screen, silent a screen width past either edge. The
651 // edges are at +-1, so a screen width past them is +-3
652 const float falloff = std::clamp((3.0F - std::abs(offset)) / 2.0F, 0.0F, 1.0F);
653 if (falloff <= 0.0F) {
654 return {};
655 }
656
657 PlayOptions panned = options;
658 panned.volume = std::clamp(options.volume * falloff, 0.0F, 1.0F);
659 panned.pan = std::clamp(offset * 0.7F, -1.0F, 1.0F);
660 return play(sample, panned);
661}
662
663void asw::sound::set_listener(const Vec2<float>& position, const Vec2<float>& velocity)
664{
665 listener_position = position;
666 listener_velocity = velocity;
667}
668
670{
671 return listener_position;
672}
673
675{
676 spatial_mode = mode;
677}
678
679void asw::sound::set_doppler(float factor, float speed)
680{
681 doppler_factor = std::max(0.0F, factor);
682 speed_of_sound = std::max(1.0F, speed);
683}
684
685void asw::sound::set_bus_volume(Bus bus, float volume)
686{
687 if (bus == Bus::Count) {
688 return;
689 }
690 bus_volume[static_cast<size_t>(bus)] = std::clamp(volume, 0.0F, 1.0F);
691}
692
694{
695 if (bus == Bus::Count) {
696 return 0.0F;
697 }
698 return bus_volume[static_cast<size_t>(bus)];
699}
700
701void asw::sound::duck(Bus bus, float gain, float hold_s, float fade_s)
702{
703 if (bus == Bus::Count) {
704 return;
705 }
706
707 auto& d = ducks[static_cast<size_t>(bus)];
708 d.target = std::min(d.hold_s > 0.0F ? d.target : 1.0F, std::clamp(gain, 0.0F, 1.0F));
709 d.hold_s = std::max(d.hold_s, hold_s);
710 d.fade_s = std::max(0.0F, fade_s);
711}
712
713void asw::sound::play_music(const asw::Music& sample, float volume, float fade_in_s)
714{
715 music_base_volume = std::clamp(volume, 0.0F, 1.0F);
716 apply_music();
717 MIX_SetTrackAudio(music_track, sample.get());
718
719 const SDL_PropertiesID options = SDL_CreateProperties();
720 SDL_SetNumberProperty(options, MIX_PROP_PLAY_LOOPS_NUMBER, -1);
721 SDL_SetNumberProperty(
722 options, MIX_PROP_PLAY_FADE_IN_MILLISECONDS_NUMBER, static_cast<int>(fade_in_s * 1000.0F));
723 MIX_PlayTrack(music_track, options);
724 SDL_DestroyProperties(options);
725}
726
727void asw::sound::stop_music(float fade_out_s)
728
729{
730 const auto fade_out_frames
731 = MIX_TrackMSToFrames(music_track, static_cast<Sint64>(fade_out_s * 1000.0F));
732 MIX_StopTrack(music_track, fade_out_frames);
733}
734
736{
737 MIX_PauseTrack(music_track);
738}
739
741{
742 MIX_ResumeTrack(music_track);
743}
744
746{
747 return MIX_TrackPlaying(music_track);
748}
749
751{
752 return MIX_TrackPaused(music_track);
753}
754
756{
757 master_volume = std::clamp(volume, 0.0F, 1.0F);
758 MIX_SetMixerGain(mixer, master_volume);
759}
760
762{
763 set_bus_volume(Bus::Sfx, volume);
764}
765
767{
768 set_bus_volume(Bus::Music, volume);
769 apply_music();
770}
771
773{
774 return master_volume;
775}
776
781
A 2D vector in space.
Definition geometry.h:27
T x
The x component of the vector.
Definition geometry.h:277
Real magnitude() const
Calculate the magnitude of the vector.
Definition geometry.h:138
T dot(const Vec2 &other) const
Calculate the dot product of two vectors.
Definition geometry.h:119
A handle to a playing sound, used to change it while it plays.
Definition sound.h:109
void set_occlusion(float amount) const
Muffle the sound, as if heard through a wall.
Definition sound.cpp:420
void set_velocity(const Vec2< float > &velocity) const
Set how fast the sound is moving, for doppler pitch shift.
Definition sound.cpp:412
void set_attenuation(const Attenuation &attenuation) const
Change the distance fall off.
Definition sound.cpp:428
void set_position(const Vec2< float > &position) const
Set where the sound is in the world. Makes the sound positional.
Definition sound.cpp:403
bool is_playing() const
Check if the sound is still playing or paused.
Definition sound.cpp:351
void stop(float fade_out_s=0.0F) const
Stop the sound.
Definition sound.cpp:356
void set_pitch(float pitch) const
Set the playback speed and pitch.
Definition sound.cpp:386
SoundHandle()=default
Create a handle that refers to nothing.
void pause() const
Pause the sound.
Definition sound.cpp:364
void set_pan(float pan) const
Set the panning. Makes a positional sound non positional.
Definition sound.cpp:394
void resume() const
Resume a paused sound.
Definition sound.cpp:371
void set_volume(float volume) const
Set the volume.
Definition sound.cpp:378
Display and window routines for the ASW library.
Structured logging system.
constexpr float OCCLUDED_GAIN
Definition sound.cpp:21
constexpr int MAX_FILTER_CHANNELS
Definition sound.cpp:16
constexpr float MAX_DOPPLER
Definition sound.cpp:25
std::array< float, NUM_BUSES > bus_volume
Definition sound.cpp:76
void SDLCALL low_pass_callback(void *userdata, MIX_Track *, const SDL_AudioSpec *spec, float *pcm, int samples)
Definition sound.cpp:107
constexpr float MIN_DOPPLER
Definition sound.cpp:24
void set_stereo_pan(Voice &v, float pan)
Definition sound.cpp:200
float bus_gain(asw::sound::Bus bus)
Definition sound.cpp:96
constexpr float OCCLUDED_CUTOFF_HZ
Definition sound.cpp:19
asw::sound::SoundHandle start(const asw::Sample &sample, const asw::sound::PlayOptions &options, const asw::Vec2< float > *position)
Definition sound.cpp:552
std::array< Voice, NUM_VOICES > voices
Definition sound.cpp:79
constexpr size_t NUM_BUSES
Definition sound.cpp:15
bool is_active(const Voice &v)
Definition sound.cpp:102
std::array< Duck, NUM_BUSES > ducks
Definition sound.cpp:77
asw::Vec2< float > listener_position
Definition sound.cpp:88
asw::sound::SpatialMode spatial_mode
Definition sound.cpp:90
Voice * resolve(int voice, uint32_t generation)
Definition sound.cpp:337
int find_voice(int priority)
Definition sound.cpp:292
float attenuate(const asw::sound::Attenuation &att, float distance)
Definition sound.cpp:145
float doppler(const Voice &v)
Definition sound.cpp:177
asw::Vec2< float > listener_velocity
Definition sound.cpp:89
void set_point(Voice &v, float x, float z)
Definition sound.cpp:218
constexpr float OPEN_CUTOFF_HZ
Definition sound.cpp:20
constexpr size_t NUM_VOICES
Definition sound.cpp:14
float vary(float value, float variation)
Definition sound.cpp:316
asw::Vec2< int > get_logical_size()
Get the logical size of the window. This may differ from the actual size if scaling is enabled.
Definition display.cpp:198
void error(const std::string &message)
Log an error message.
Definition log.cpp:196
void warn(const std::string &message)
Log a warning message.
Definition log.cpp:191
int between(int min, int max)
Generate a random integer between min and max.
Definition random.cpp:19
bool is_music_paused()
Check if music is paused.
Definition sound.cpp:750
void set_doppler(float factor, float speed_of_sound=3000.0F)
Set how strong doppler pitch shift is.
Definition sound.cpp:679
void pause_music()
Pause the currently playing music.
Definition sound.cpp:735
float get_master_volume()
Get the current master volume multiplier.
Definition sound.cpp:772
void play_music(const asw::Music &sample, float volume=1.0F, float fade_in_s=0.0F)
Play a music sample.
Definition sound.cpp:713
bool is_music_playing()
Check if music is currently playing.
Definition sound.cpp:745
void set_sfx_volume(float volume)
Set the SFX volume multiplier. Same as the Sfx bus volume.
Definition sound.cpp:761
float get_music_volume()
Get the current music volume multiplier.
Definition sound.cpp:782
void resume_music()
Resume paused music.
Definition sound.cpp:740
Bus
Mix buses. Each sound plays on one bus, and each bus has its own volume and can be ducked.
Definition sound.h:22
void _shutdown()
Shut down the sound module. Called automatically by asw::core::shutdown().
Definition sound.cpp:444
MIX_Mixer * get_mixer()
Get the SDL mixer device.
Definition sound.cpp:439
float get_sfx_volume()
Get the current SFX volume multiplier.
Definition sound.cpp:777
void set_listener(const Vec2< float > &position, const Vec2< float > &velocity={ 0.0F, 0.0F })
Set where the listener is, usually the player or camera centre.
Definition sound.cpp:663
void set_music_volume(float volume)
Set the music volume multiplier. Same as the Music bus volume.
Definition sound.cpp:766
void set_master_volume(float volume)
Set the master volume multiplier (affects all audio).
Definition sound.cpp:755
void set_bus_volume(Bus bus, float volume)
Set a bus volume.
Definition sound.cpp:685
void stop_music(float fade_out_s=0.0F)
Stop the currently playing music.
Definition sound.cpp:727
SoundHandle play(const asw::Sample &sample, float volume=1.0F, float pan=0.0F, bool loop=false)
Play a sample.
Definition sound.cpp:609
bool _init()
Initialize the sound module. Called automatically by asw::core::init().
Definition sound.cpp:465
SpatialMode
How positional sounds are placed on the speakers.
Definition sound.h:44
@ Stereo
Left/right panning. Best for headphones and side on games.
Vec2< float > get_listener()
Get the listener position.
Definition sound.cpp:669
void duck(Bus bus, float gain, float hold_s, float fade_s=0.2F)
Turn a bus down for a while, for example music under dialogue or everything under a big explosion....
Definition sound.cpp:701
@ Linear
Straight line from full volume at min_distance to silent at max_distance.
@ Inverse
Natural sounding, loud close up and a long quiet tail.
@ InverseSquare
Like Inverse but drops faster.
float get_bus_volume(Bus bus)
Get a bus volume.
Definition sound.cpp:693
uint32_t _get_session()
Get the number of times the sound module has been shut down.
Definition sound.cpp:460
void set_spatial_mode(SpatialMode mode)
Choose how positional sounds are placed on the speakers.
Definition sound.cpp:674
SoundHandle play_at(const asw::Sample &sample, float x, float volume=1.0F)
Play a sample panned by where it happens on screen. Sounds pan towards the side they are on and fade ...
Definition sound.cpp:631
SoundHandle play_positional(const asw::Sample &sample, const Vec2< float > &position, const PlayOptions &options={})
Play a sample at a point in the world. It is panned, faded and pitch shifted relative to the listener...
Definition sound.cpp:619
void _update()
Update fades, ducking and positional sounds. Called automatically by asw::core::update().
Definition sound.cpp:511
std::shared_ptr< MIX_Audio > Sample
Alias for a shared pointer to an MIX_Audio.
Definition types.h:82
std::shared_ptr< MIX_Audio > Music
Alias for a shared pointer to an MIX_Audio.
Definition types.h:85
Random module for the ASW library.
Sound module for the ASW library.
std::array< float, MAX_FILTER_CHANNELS > state
Definition sound.cpp:32
asw::sound::Attenuation attenuation
Definition sound.cpp:42
struct anonymous_namespace{sound.cpp}::Voice::Sent sent
Distance settings for positional sounds, in world units (pixels).
Definition sound.h:54
float min_distance
Full volume at this distance or closer.
Definition sound.h:56
float max_distance
Silent at this distance or further.
Definition sound.h:59
Rolloff rolloff
Shape of the fall off between the two.
Definition sound.h:62
static SoundHandle make(int voice, uint32_t generation)
Definition sound.cpp:546
Options for playing a sound.
Definition sound.h:67
Bus bus
Bus to play on.
Definition sound.h:88
float fade_in_s
Fade in time in seconds.
Definition sound.h:95
bool loop
Loop forever.
Definition sound.h:75
float pitch
Playback speed and pitch. 1.0 is normal, 2.0 is an octave up.
Definition sound.h:78
float volume_variation
Random volume change per play, as a fraction.
Definition sound.h:85
float pan
Panning (-1.0 left - 1.0 right). Ignored for positional sounds.
Definition sound.h:72
float volume
Playback volume (0.0 - 1.0).
Definition sound.h:69
Attenuation attenuation
Distance fall off, used by positional sounds.
Definition sound.h:98