aboutsummaryrefslogtreecommitdiff
path: root/engines/titanic/sound/qmixer.h
blob: 6a25484c291f40775af3d50e30d44b4ccb72b5b1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * aint with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#ifndef TITANIC_QMIXER_H
#define TITANIC_QMIXER_H

#include "audio/mixer.h"
#include "titanic/sound/wave_file.h"

namespace Titanic {

enum QMixFlag {
	QMIX_OPENSINGLE		= 0,	// Open the single channel specified by iChannel
	QMIX_OPENALL		= 1,	// Opens all the channels, iChannel ignored
	QMIX_OPENCOUNT		= 2,	// Open iChannel Channels (eg. if iChannel = 4 will create channels 0-3)
	QMIX_OPENAVAILABLE	= 3,	// Open the first unopened channel, and return channel number

	// Channel function flags
	QMIX_ALL				= 0x01,	// apply to all channels
	QMIX_NOREMIX			= 0x02,	// don't remix
	QMIX_CONTROL_NOREMIX	= 0x04,	// don't remix
	QMIX_USEONCE			= 0x10	// settings are temporary
};

// qsWaveMixEnableChannel flags:  if mode==0, use conventional, high-performance
// stereo mixer.  Non-zero modes imply some form of additional processing.
enum QMixChannelFlag {
	QMIX_CHANNEL_STEREO			= 0x0000,	// Perform stereo mixing
	QMIX_CHANNEL_QSOUND			= 0x0001,	// Perform QSound localization (default)
	QMIX_CHANNEL_DOPPLER		= 0x0002,	// Calculate velocity using position updates
	QMIX_CHANNEL_RANGE			= 0x0004,	// Do range effects
	QMIX_CHANNEL_ELEVATION		= 0x0008,	// Do elevation effects
	QMIX_CHANNEL_NODOPPLERPITCH	= 0x0010,	// Disable Doppler pitch shift for this channel
	QMIX_CHANNEL_PITCH_COPY		= 0x0000,	// Pitch shifting using copying (fastest)
	QMIX_CHANNEL_PITCH_LINEAR	= 0x0100,	// Pitch shifting using linear interpolation (better but slower)
	QMIX_CHANNEL_PITCH_SPLINE	= 0x0200,	// Pitch shifting using spline interpolation (better yet, but much slower)
	QMIX_CHANNEL_PITCH_FILTER	= 0x0300,	// Pitch shifting using FIR filter (best, but slowest)
	QMIX_CHANNEL_PITCH_MASK		= 0x0700	// Bits reserved for pitch types
};

/**
 * Options for dwFlags parameter in QSWaveMixPlayEx.
 *
 * Notes: The QMIX_USELRUCHANNEL flag has two roles.  When QMIX_CLEARQUEUE is also set,
 * the channel that has been playing the longest (least-recently-used) is cleared and
 * the buffer played.  When QMIX_QUEUEWAVE is set, the channel that  will first finish
 * playing will be selected and the buffer queued to play. Of course, if an unused
 * channel is found, it will be selected instead.
 * If QMIX_WAIT hasn't been specified, then the channel number will be returned
 * in the iChannel field.
 */
enum QMixPlayFlag {
	QMIX_QUEUEWAVE			= 0x0000,	// Queue on channel
	QMIX_CLEARQUEUE			= 0x0001,	// Clear queue on channel
	QMIX_USELRUCHANNEL		= 0x0002,	// See notes above
	QMIX_HIGHPRIORITY		= 0x0004,
	QMIX_WAIT				= 0x0008,	// Queue to be played with other sounds
	QMIX_IMMEDIATE			= 0x0020,	// Apply volume/pan changes without interpolation

	QMIX_PLAY_SETEVENT		= 0x0100,	// Calls SetEvent in the original library when done
	QMIX_PLAY_PULSEEVENT	= 0x0200,	// Calls PulseEvent in the original library when done
	QMIX_PLAY_NOTIFYSTOP	= 0x0400	// Do callback even when stopping or flushing sound
};

/**
 * Mixer configuration structure for qsWaveMixInitEx
 */
struct QMIXCONFIG {
	uint32 dwSize;
	uint32 dwFlags;
	uint32 dwSamplingRate;   // Sampling rate in Hz
	void *lpIDirectSound;
	const void *lpGuid;
	int iChannels;          // Number of channels
	int iOutput;            // if 0, uses best output device
	int iLatency;           // (in ms) if 0, uses default for output device
	int iMath;              // style of math
	uint hwnd;

	QMIXCONFIG() : dwSize(40), dwFlags(0), dwSamplingRate(0), lpIDirectSound(nullptr),
		lpGuid(nullptr), iChannels(0), iOutput(0), iLatency(0), iMath(0), hwnd(0) {}
	QMIXCONFIG(uint32 rate, int channels, int latency) : dwSize(40),  dwFlags(0),
		dwSamplingRate(rate), iChannels(channels), iLatency(latency),
		lpIDirectSound(nullptr), lpGuid(nullptr), iOutput(0), iMath(0), hwnd(0) {}
};

/**
 * Vector positioning in metres
 */
struct QSVECTOR {
	double x;
	double y;
	double z;

	QSVECTOR() : x(0.0), y(0.0), z(0.0) {}
	QSVECTOR(double xp, double yp, double zp) : x(xp), y(yp), z(zp) {}
};

/**
 * Polar positioning
 */
struct QSPOLAR {
	double azimuth;		// degrees
	double range;		// meters
	double elevation;	// degrees

	QSPOLAR() : azimuth(0.0), range(0.0), elevation(0.0) {}
	QSPOLAR(double azimuth_, double range_, double elevation_) :
		azimuth(azimuth_), range(range_), elevation(elevation_) {}
};

struct QMIX_DISTANCES {
	int cbSize;			// Structure size
	double minDistance;	// sounds are at full volume if closer than this
	double maxDistance;	// sounds are muted if further away than this
	double scale;		// relative amount to adjust rolloff by

	QMIX_DISTANCES() : cbSize(16), minDistance(0.0), maxDistance(0.0), scale(0.0) {}
	QMIX_DISTANCES(double minDistance_, double maxDistance_, double scale_) :
		cbSize(16), minDistance(minDistance_), maxDistance(maxDistance_), scale(scale_) {}
};

typedef void (*LPQMIXDONECALLBACK)(int iChannel, CWaveFile *lpWave, void *dwUser);

struct QMIXPLAYPARAMS {
	uint dwSize;		// Size of the play structure
	void *lpImage;		// Additional preprocessed audio for high performance
	uint hwndNotify;	// if set, WOM_OPEN and WOM_DONE messages sent to that window
	LPQMIXDONECALLBACK callback;	// Callback function
	void *dwUser;					// User data accompanying callback
	int lStart;
	int lStartLoop;
	int lEndLoop;
	int lEnd;
	const void *lpChannelParams;	// initialize with these parameters
	// Properties introduced by ScummVM
	Audio::Mixer::SoundType _soundType;

	QMIXPLAYPARAMS() : dwSize(36), lpImage(nullptr), hwndNotify(0), callback(nullptr),
		dwUser(nullptr), lStart(0), lStartLoop(0), lEndLoop(0), lEnd(0), 
		lpChannelParams(nullptr), _soundType(Audio::Mixer::kPlainSoundType)  {}
};

/**
 * This class represents an interface to the QMixer library developed by
 * QSound Labs, Inc. Which itself is apparently based on Microsoft's
 * WaveMix API.
 *
 * It does not currently have any actual code from the library, 
 * and instead remaps calls to ScummVM's existing mixer where possible.
 * This means that advanced features of the QMixer library, like being
 * able to set up both the player and sounds at different positions are
 * currently ignored, and all sounds play at full volume.
 */
class QMixer {
	struct SoundEntry {
		bool _started;
		CWaveFile *_waveFile;
		Audio::SoundHandle _soundHandle;
		LPQMIXDONECALLBACK _callback;
		int _loops;
		void *_userData;
		SoundEntry() : _started(false), _waveFile(nullptr), _callback(nullptr),
			_loops(0), _userData(nullptr) {}

		SoundEntry(CWaveFile *waveFile, LPQMIXDONECALLBACK callback, int loops, void *userData) :
			_started(false), _waveFile(waveFile), _callback(callback), _loops(loops), _userData(userData) {}
	};
	struct ChannelEntry {
		// Currently playing and any following queued sounds for the channel
		Common::List<SoundEntry> _sounds;
		// Current channel volume
		byte _volume;				
		// Current time in milliseconds for paning (volume) changes
		uint _panRate;
		// Fields used to transition between volume levels
		uint _volumeChangeStart;
		uint _volumeChangeEnd;
		byte _volumeStart;
		byte _volumeEnd;

		ChannelEntry() : _volume(0), _panRate(0), _volumeChangeStart(0),
			_volumeChangeEnd(0), _volumeStart(0), _volumeEnd(0) {}
	};
private:
	Audio::Mixer *_mixer;
	Common::Array<ChannelEntry> _channels;
public:
	QMixer(Audio::Mixer *mixer);
	virtual ~QMixer() {}

	/**
	 * Initializes the mixer
	 */
	bool qsWaveMixInitEx(const QMIXCONFIG &config);

	/**
	 * Activates the mixer
	 */
	void qsWaveMixActivate(bool fActivate);

	/**
	 * Opens channels in the mixer for access
	 */
	int qsWaveMixOpenChannel(int iChannel, QMixFlag mode);

	/**
	 * Enables a given channel
	 */
	int qsWaveMixEnableChannel(int iChannel, uint flags, bool enabled);

	/**
	 * Closes down the mixer
	 */
	void qsWaveMixCloseSession();

	/**
	 * Stops a sound from playing
	 */
	void qsWaveMixFreeWave(Audio::SoundHandle &handle);

	/**
	 * Flushes a channel
	 */
	void qsWaveMixFlushChannel(int iChannel, uint flags = 0);

	/**
	 * Sets the amount of time, in milliseconds, to effect a change in
	 * a channel property (e.g. volume, position).  Non-zero values
	 * smooth out changes
	 * @param iChannel		Channel to change
	 * @param flags			Flags
	 * @param rate			Pan rate in milliseconds
	*/
	void qsWaveMixSetPanRate(int iChannel, uint flags, uint rate);

	/**
	* Sets the volume for a channel
	*/
	void qsWaveMixSetVolume(int iChannel, uint flags, uint volume);

	/**
	 * Sets the relative position of a channel
	 * @param iChannel		Channel number
	 * @param Flags			Flags
	 * @param position		Vector position for channel
	 */
	void qsWaveMixSetSourcePosition(int iChannel, uint flags, const QSVECTOR &position);

	/**
	 * Sets the relative position of a channel using polar co-ordinates
	 * @param iChannel		Channel number
	 * @param Flags			Flags
	 * @param position		Polar position for channel
	 */
	void qsWaveMixSetPolarPosition(int iChannel, uint flags, const QSPOLAR &position);

	/**
	 * Sets the listener position
	 */
	void qsWaveMixSetListenerPosition(const QSVECTOR &position, uint flags = 0);

	/**
	 * Sets the listener orientation
	 */
	void qsWaveMixSetListenerOrientation(const QSVECTOR &direction, const QSVECTOR &up, uint flags = 0);

	/**
	 * Sets the mapping ditance range
	 */
	void qsWaveMixSetDistanceMapping(int iChannel, uint flags, const QMIX_DISTANCES &distances);

	/**
	 *
	 */
	void qsWaveMixSetFrequency(int iChannel, uint flags, uint frequency);

	/**
	 * Sets the velocity of the source (listener)
	 */
	void qsWaveMixSetSourceVelocity(int iChannel, uint flags, const QSVECTOR &velocity);

	/**
	 * Plays sound
	 * @param iChannel		The channel number to be played on
	 * @param flags			Play flags
	 * @param mixWave		Data for the sound to be played
	 * @param loops			Number of loops to play (-1 for forever)
	 * @param params		Playback parameter data
	 */
	int qsWaveMixPlayEx(int iChannel, uint flags, CWaveFile *waveFile, int loops, const QMIXPLAYPARAMS &params);

	/**
	 * Returns true if there are no more buffers playing or queued on the channel
	 */
	bool qsWaveMixIsChannelDone(int iChannel) const;

	/**
	 * Handles regularly updating the mixer
	 */
	void qsWaveMixPump();
};

} // End of namespace Titanic

#endif /* TITANIC_QMIXER_H */