1 /** @file sfxchannel.cpp Logical sound channel (for sound effects).
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
5 * @authors Copyright © 2006-2007 Jamie Jones <jamie_jones_au@yahoo.com.au>
6 *
7 * @par License
8 * GPL: http://www.gnu.org/licenses/gpl.html
9 *
10 * <small>This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by the
12 * Free Software Foundation; either version 2 of the License, or (at your
13 * option) any later version. This program is distributed in the hope that it
14 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16 * Public License for more details. You should have received a copy of the GNU
17 * General Public License along with this program; if not, see:
18 * http://www.gnu.org/licenses</small>
19 */
20
21 #include "audio/sfxchannel.h"
22
23 #include "world/thinkers.h"
24 #include "dd_main.h" // remove me
25 #include "def_main.h" // ::defs
26 #include <de/Log>
27 #include <de/GLInfo>
28 #include <de/timer.h> // TICSPERSEC
29 #include <de/vector1.h> // remove me
30 #include <QList>
31 #include <QtAlgorithms>
32
33 // Debug visual headers:
34 #include "audio/s_cache.h"
35 #include "gl/gl_main.h"
36 #include "api_fontrender.h"
37 #include "render/rend_font.h"
38 #include "ui/ui_main.h"
39 #include <de/concurrency.h>
40
41 using namespace de;
42
43 namespace audio {
44
DENG2_PIMPL_NOREF(SfxChannel)45 DENG2_PIMPL_NOREF(SfxChannel)
46 {
47 dint flags = 0; ///< SFXCF_* flags.
48 dfloat frequency = 0; ///< Frequency adjustment: 1.0 is normal.
49 dfloat volume = 0; ///< Sound volume: 1.0 is max.
50
51 mobj_t const *emitter = nullptr;///< Mobj emitter for the sound, if any (not owned).
52 coord_t origin[3]; ///< Emit from here (synced with emitter).
53
54 sfxbuffer_t *buffer = nullptr; ///< Assigned sound buffer, if any (not owned).
55 dint startTime = 0; ///< When the assigned sound sample was last started.
56
57 Impl() { zap(origin); }
58
59 Vector3d findOrigin() const
60 {
61 // Originless sounds have no fixed/moveable emission point.
62 if (flags & SFXCF_NO_ORIGIN)
63 {
64 return Vector3d();
65 }
66
67 // When tracking an emitter - use it's origin.
68 if (emitter)
69 {
70 Vector3d point(emitter->origin);
71
72 // Position on the Z axis at the map-object's center?
73 if (Thinker_IsMobj(&emitter->thinker))
74 {
75 point.z += emitter->height / 2;
76 }
77 return point;
78 }
79
80 // Use the fixed origin.
81 return Vector3d(origin);
82 }
83
84 inline void updateOrigin()
85 {
86 findOrigin().decompose(origin);
87 }
88 };
89
SfxChannel()90 SfxChannel::SfxChannel() : d(new Impl)
91 {}
92
~SfxChannel()93 SfxChannel::~SfxChannel()
94 {}
95
hasBuffer() const96 bool SfxChannel::hasBuffer() const
97 {
98 return d->buffer != nullptr;
99 }
100
buffer()101 sfxbuffer_t &SfxChannel::buffer()
102 {
103 if (d->buffer) return *d->buffer;
104 /// @throw MissingBufferError No sound buffer is currently assigned.
105 throw MissingBufferError("SfxChannel::buffer", "No sound buffer is assigned");
106 }
107
buffer() const108 sfxbuffer_t const &SfxChannel::buffer() const
109 {
110 return const_cast<SfxChannel *>(this)->buffer();
111 }
112
setBuffer(sfxbuffer_t * newBuffer)113 void SfxChannel::setBuffer(sfxbuffer_t *newBuffer)
114 {
115 d->buffer = newBuffer;
116 }
117
stop()118 void SfxChannel::stop()
119 {
120 if (!d->buffer) return;
121
122 /// @todo AudioSystem should observe. -ds
123 App_AudioSystem().sfx()->Stop(d->buffer);
124 }
125
flags() const126 dint SfxChannel::flags() const
127 {
128 return d->flags;
129 }
130
131 /// @todo Use QFlags -ds
setFlags(dint newFlags)132 void SfxChannel::setFlags(dint newFlags)
133 {
134 d->flags = newFlags;
135 }
136
frequency() const137 dfloat SfxChannel::frequency() const
138 {
139 return d->frequency;
140 }
141
setFrequency(dfloat newFrequency)142 void SfxChannel::setFrequency(dfloat newFrequency)
143 {
144 d->frequency = newFrequency;
145 }
146
volume() const147 dfloat SfxChannel::volume() const
148 {
149 return d->volume;
150 }
151
setVolume(dfloat newVolume)152 void SfxChannel::setVolume(dfloat newVolume)
153 {
154 d->volume = newVolume;
155 }
156
emitter() const157 mobj_t const *SfxChannel::emitter() const
158 {
159 return d->emitter;
160 }
161
setEmitter(mobj_t const * newEmitter)162 void SfxChannel::setEmitter(mobj_t const *newEmitter)
163 {
164 d->emitter = newEmitter;
165 }
166
setFixedOrigin(Vector3d const & newOrigin)167 void SfxChannel::setFixedOrigin(Vector3d const &newOrigin)
168 {
169 d->origin[0] = newOrigin.x;
170 d->origin[1] = newOrigin.y;
171 d->origin[2] = newOrigin.z;
172 }
173
origin() const174 Vector3d SfxChannel::origin() const
175 {
176 return d->origin;
177 }
178
priority() const179 dfloat SfxChannel::priority() const
180 {
181 if (!d->buffer || !(d->buffer->flags & SFXBF_PLAYING))
182 return SFX_LOWEST_PRIORITY;
183
184 if (d->flags & SFXCF_NO_ORIGIN)
185 return App_AudioSystem().rateSoundPriority(0, 0, d->volume, d->startTime);
186
187 // d->origin is set to emitter->xyz during updates.
188 return App_AudioSystem().rateSoundPriority(0, d->origin, d->volume, d->startTime);
189 }
190
191 /// @todo AudioSystem should observe. -ds
updatePriority()192 void SfxChannel::updatePriority()
193 {
194 // If no sound buffer is assigned we've no need to update.
195 sfxbuffer_t *sbuf = d->buffer;
196 if (!sbuf) return;
197
198 // Disabled?
199 if (d->flags & SFXCF_NO_UPDATE) return;
200
201 // Update the sound origin if needed.
202 if (d->emitter)
203 {
204 d->updateOrigin();
205 }
206
207 // Frequency is common to both 2D and 3D sounds.
208 App_AudioSystem().sfx()->Set(sbuf, SFXBP_FREQUENCY, d->frequency);
209
210 if (sbuf->flags & SFXBF_3D)
211 {
212 // Volume is affected only by maxvol.
213 App_AudioSystem().sfx()->Set(sbuf, SFXBP_VOLUME, d->volume * ::sfxVolume / 255.0f);
214 if (d->emitter && d->emitter == App_AudioSystem().sfxListener())
215 {
216 // Emitted by the listener object. Go to relative position mode
217 // and set the position to (0,0,0).
218 dfloat vec[3]; vec[0] = vec[1] = vec[2] = 0;
219 App_AudioSystem().sfx()->Set(sbuf, SFXBP_RELATIVE_MODE, true);
220 App_AudioSystem().sfx()->Setv(sbuf, SFXBP_POSITION, vec);
221 }
222 else
223 {
224 // Use the channel's map space origin.
225 dfloat origin[3];
226 V3f_Copyd(origin, d->origin);
227 App_AudioSystem().sfx()->Set(sbuf, SFXBP_RELATIVE_MODE, false);
228 App_AudioSystem().sfx()->Setv(sbuf, SFXBP_POSITION, origin);
229 }
230
231 // If the sound is emitted by the listener, speed is zero.
232 if (d->emitter && d->emitter != App_AudioSystem().sfxListener() &&
233 Thinker_IsMobj(&d->emitter->thinker))
234 {
235 dfloat vec[3];
236 vec[0] = d->emitter->mom[0] * TICSPERSEC;
237 vec[1] = d->emitter->mom[1] * TICSPERSEC;
238 vec[2] = d->emitter->mom[2] * TICSPERSEC;
239 App_AudioSystem().sfx()->Setv(sbuf, SFXBP_VELOCITY, vec);
240 }
241 else
242 {
243 // Not moving.
244 dfloat vec[3]; vec[0] = vec[1] = vec[2] = 0;
245 App_AudioSystem().sfx()->Setv(sbuf, SFXBP_VELOCITY, vec);
246 }
247 }
248 else
249 {
250 dfloat dist = 0;
251 dfloat pan = 0;
252
253 // This is a 2D buffer.
254 if ((d->flags & SFXCF_NO_ORIGIN) ||
255 (d->emitter && d->emitter == App_AudioSystem().sfxListener()))
256 {
257 dist = 1;
258 pan = 0;
259 }
260 else
261 {
262 // Calculate roll-off attenuation. [.125/(.125+x), x=0..1]
263 dist = Mobj_ApproxPointDistance(App_AudioSystem().sfxListener(), d->origin);
264 if (dist < ::soundMinDist || (d->flags & SFXCF_NO_ATTENUATION))
265 {
266 // No distance attenuation.
267 dist = 1;
268 }
269 else if (dist > ::soundMaxDist)
270 {
271 // Can't be heard.
272 dist = 0;
273 }
274 else
275 {
276 dfloat const normdist = (dist - ::soundMinDist) / (::soundMaxDist - ::soundMinDist);
277
278 // Apply the linear factor so that at max distance there
279 // really is silence.
280 dist = .125f / (.125f + normdist) * (1 - normdist);
281 }
282
283 // And pan, too. Calculate angle from listener to emitter.
284 if (mobj_t *listener = App_AudioSystem().sfxListener())
285 {
286 dfloat angle = (M_PointToAngle2(listener->origin, d->origin) - listener->angle) / (dfloat) ANGLE_MAX * 360;
287
288 // We want a signed angle.
289 if (angle > 180)
290 angle -= 360;
291
292 // Front half.
293 if (angle <= 90 && angle >= -90)
294 {
295 pan = -angle / 90;
296 }
297 else
298 {
299 // Back half.
300 pan = (angle + (angle > 0 ? -180 : 180)) / 90;
301 // Dampen sounds coming from behind.
302 dist *= (1 + (pan > 0 ? pan : -pan)) / 2;
303 }
304 }
305 else
306 {
307 // No listener mobj? Can't pan, then.
308 pan = 0;
309 }
310 }
311
312 App_AudioSystem().sfx()->Set(sbuf, SFXBP_VOLUME, d->volume * dist * ::sfxVolume / 255.0f);
313 App_AudioSystem().sfx()->Set(sbuf, SFXBP_PAN, pan);
314 }
315 }
316
startTime() const317 int SfxChannel::startTime() const
318 {
319 return d->startTime;
320 }
321
setStartTime(dint newStartTime)322 void SfxChannel::setStartTime(dint newStartTime)
323 {
324 d->startTime = newStartTime;
325 }
326
DENG2_PIMPL(SfxChannels)327 DENG2_PIMPL(SfxChannels)
328 {
329 QList<SfxChannel *> all;
330
331 Impl(Public *i) : Base(i) {}
332 ~Impl() { clearAll(); }
333
334 void clearAll()
335 {
336 qDeleteAll(all);
337 }
338
339 /// @todo support dynamically resizing in both directions. -ds
340 void resize(dint newSize)
341 {
342 if (newSize < 0) newSize = 0;
343
344 clearAll();
345 for (dint i = 0; i < newSize; ++i)
346 {
347 all << new SfxChannel;
348 }
349 }
350 };
351
SfxChannels(dint count)352 SfxChannels::SfxChannels(dint count) : d(new Impl(this))
353 {
354 d->resize(count);
355 }
356
count() const357 dint SfxChannels::count() const
358 {
359 return d->all.count();
360 }
361
countPlaying(dint id)362 dint SfxChannels::countPlaying(dint id)
363 {
364 DENG2_ASSERT( App_AudioSystem().sfxIsAvailable() ); // sanity check
365
366 dint count = 0;
367 forAll([&id, &count] (SfxChannel &ch)
368 {
369 if (ch.hasBuffer())
370 {
371 sfxbuffer_t &sbuf = ch.buffer();
372 if ((sbuf.flags & SFXBF_PLAYING) && sbuf.sample && sbuf.sample->id == id)
373 {
374 count += 1;
375 }
376 }
377 return LoopContinue;
378 });
379 return count;
380 }
381
tryFindVacant(bool use3D,dint bytes,dint rate,dint sampleId) const382 SfxChannel *SfxChannels::tryFindVacant(bool use3D, dint bytes, dint rate, dint sampleId) const
383 {
384 for (SfxChannel *ch : d->all)
385 {
386 if (!ch->hasBuffer()) continue;
387 sfxbuffer_t const &sbuf = ch->buffer();
388
389 if ((sbuf.flags & SFXBF_PLAYING)
390 || use3D != ((sbuf.flags & SFXBF_3D) != 0)
391 || sbuf.bytes != bytes
392 || sbuf.rate != rate)
393 continue;
394
395 // What about the sample?
396 if (sampleId > 0)
397 {
398 if (!sbuf.sample || sbuf.sample->id != sampleId)
399 continue;
400 }
401 else if (sampleId == 0)
402 {
403 // We're trying to find a channel with no sample already loaded.
404 if (sbuf.sample)
405 continue;
406 }
407
408 // This is perfect, take this!
409 return ch;
410 }
411
412 return nullptr; // None suitable.
413 }
414
refreshAll()415 void SfxChannels::refreshAll()
416 {
417 forAll([] (SfxChannel &ch)
418 {
419 if (ch.hasBuffer() && (ch.buffer().flags & SFXBF_PLAYING))
420 {
421 App_AudioSystem().sfx()->Refresh(&ch.buffer());
422 }
423 return LoopContinue;
424 });
425 }
426
forAll(std::function<LoopResult (SfxChannel &)> func) const427 LoopResult SfxChannels::forAll(std::function<LoopResult (SfxChannel &)> func) const
428 {
429 for (SfxChannel *ch : d->all)
430 {
431 if (auto result = func(*ch)) return result;
432 }
433 return LoopContinue;
434 }
435
436 } // namespace audio
437
438 using namespace audio;
439
440 // Debug visual: -----------------------------------------------------------------
441
442 dint showSoundInfo;
443 byte refMonitor;
444
Sfx_ChannelDrawer()445 void Sfx_ChannelDrawer()
446 {
447 if (!::showSoundInfo) return;
448
449 DENG_ASSERT_IN_MAIN_THREAD();
450 DENG_ASSERT_GL_CONTEXT_ACTIVE();
451
452 // Go into screen projection mode.
453 DGL_MatrixMode(DGL_PROJECTION);
454 DGL_PushMatrix();
455 DGL_LoadIdentity();
456 DGL_Ortho(0, 0, DENG_GAMEVIEW_WIDTH, DENG_GAMEVIEW_HEIGHT, -1, 1);
457
458 DGL_Enable(DGL_TEXTURE_2D);
459
460 FR_SetFont(fontFixed);
461 FR_LoadDefaultAttrib();
462 FR_SetColorAndAlpha(1, 1, 0, 1);
463
464 dint const lh = FR_SingleLineHeight("Q");
465 if (!App_AudioSystem().sfxIsAvailable())
466 {
467 FR_DrawTextXY("Sfx disabled", 0, 0);
468 DGL_Disable(DGL_TEXTURE_2D);
469 return;
470 }
471
472 if (::refMonitor)
473 FR_DrawTextXY("!", 0, 0);
474
475 // Sample cache information.
476 duint cachesize, ccnt;
477 App_AudioSystem().sfxSampleCache().info(&cachesize, &ccnt);
478 char buf[200]; sprintf(buf, "Cached:%i (%i)", cachesize, ccnt);
479
480 FR_SetColor(1, 1, 1);
481 FR_DrawTextXY(buf, 10, 0);
482
483 // Print a line of info about each channel.
484 dint idx = 0;
485 App_AudioSystem().sfxChannels().forAll([&lh, &idx] (audio::SfxChannel &ch)
486 {
487 if (ch.hasBuffer() && (ch.buffer().flags & SFXBF_PLAYING))
488 {
489 FR_SetColor(1, 1, 1);
490 }
491 else
492 {
493 FR_SetColor(1, 1, 0);
494 }
495
496 Block emitterText;
497 if (ch.emitter())
498 {
499 emitterText = ( " mobj:" + String::number(ch.emitter()->thinker.id)
500 + " pos:" + ch.origin().asText()
501 ).toLatin1();
502 }
503
504 char buf[200];
505 sprintf(buf, "%02i: %c%c%c v=%3.1f f=%3.3f st=%i et=%u%s",
506 idx,
507 !(ch.flags() & SFXCF_NO_ORIGIN ) ? 'O' : '.',
508 !(ch.flags() & SFXCF_NO_ATTENUATION) ? 'A' : '.',
509 ch.emitter() ? 'E' : '.',
510 ch.volume(), ch.frequency(), ch.startTime(),
511 ch.hasBuffer() ? ch.buffer().endTime : 0,
512 emitterText.constData());
513 FR_DrawTextXY(buf, 5, lh * (1 + idx * 2));
514
515 if (ch.hasBuffer())
516 {
517 sfxbuffer_t &sbuf = ch.buffer();
518
519 sprintf(buf, " %c%c%c%c id=%03i/%-8s ln=%05i b=%i rt=%2i bs=%05i (C%05i/W%05i)",
520 (sbuf.flags & SFXBF_3D ) ? '3' : '.',
521 (sbuf.flags & SFXBF_PLAYING) ? 'P' : '.',
522 (sbuf.flags & SFXBF_REPEAT ) ? 'R' : '.',
523 (sbuf.flags & SFXBF_RELOAD ) ? 'L' : '.',
524 sbuf.sample ? sbuf.sample->id : 0,
525 sbuf.sample ? DED_Definitions()->sounds[sbuf.sample->id].id : "",
526 sbuf.sample ? sbuf.sample->size : 0,
527 sbuf.bytes, sbuf.rate / 1000, sbuf.length,
528 sbuf.cursor, sbuf.written);
529 FR_DrawTextXY(buf, 5, lh * (2 + idx * 2));
530 }
531
532 idx += 1;
533 return LoopContinue;
534 });
535
536 DGL_Disable(DGL_TEXTURE_2D);
537
538 // Back to the original.
539 DGL_MatrixMode(DGL_PROJECTION);
540 DGL_PopMatrix();
541 }
542