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