OXYGENHUB
◆ main openmsx / src / sound / SoundDevice.hh
10.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#ifndef SOUNDDEVICE_HH
2
#define SOUNDDEVICE_HH
3
4
#include "EmuTime.hh"
5
#include "WavWriter.hh"
6
#include "static_string_view.hh"
7
8
#include <array>
9
#include <optional>
10
#include <string>
11
#include <string_view>
12
13
namespace openmsx {
14
15
class DeviceConfig;
16
class DynamicClock;
17
class MSXMixer;
18
19
class SoundDevice
20
{
21
public:
22
	static constexpr unsigned MAX_CHANNELS = 24;
23
24
	SoundDevice(const SoundDevice&) = delete;
25
	SoundDevice(SoundDevice&&) = delete;
26
	SoundDevice& operator=(const SoundDevice&) = delete;
27
	SoundDevice& operator=(SoundDevice&&) = delete;
28
29
	/** Get the unique name that identifies this sound device.
30
	  * Used to create setting names.
31
	  */
32
	[[nodiscard]] const std::string& getName() const { return name; }
33
34
	/** Gets a description of this sound device,
35
	  * to be presented to the user.
36
	  */
37
	[[nodiscard]] std::string_view getDescription() const { return description; }
38
39
	/** How many channels does this device have?
40
	 */
41
	[[nodiscard]] unsigned getNumChannels() const { return numChannels; }
42
43
	/** Are the individual channels of this device stereo?
44
	 */
45
	[[nodiscard]] bool hasStereoChannels() const {
46
		return stereo == 2;
47
	}
48
49
	/** Is the full output of this device stereo?
50
	  */
51
	[[nodiscard]] bool isStereo() const {
52
		return hasStereoChannels() || !balanceCenter;
53
	}
54
55
	/** Gets this device its 'amplification factor'.
56
	  *
57
	  * Each sample generated by the 'updateBuffer' method will get
58
	  * multiplied by this factor during mixing (see MSXMixer).
59
	  *
60
	  * This factor is the combination of:
61
	  *  - a fixed per device factor (see getAmplificationFactorImpl()).
62
	  *  - a MSX software controlled volume setting (e.g. used in
63
	  *    Carnivore2 emulation).
64
	  * The influence of the different volume settings is not part of this
65
	  * factor.
66
	  */
67
	struct AmplificationFactors {
68
		float left;
69
		float right;
70
	};
71
	[[nodiscard]] AmplificationFactors getAmplificationFactor() const {
72
		auto f = getAmplificationFactorImpl();
73
		return {.left = f * softwareVolumeLeft, .right = f * softwareVolumeRight};
74
	}
75
76
	/** Change the 'software volume' of this sound device.
77
	  *
78
	  * Some devices like 'Carnivore2' include sound chips (e.g. SCC and
79
	  * fm-pac) and allow the MSX software to configure the volume of
80
	  * those chips. To be clear: these are not the regular per channel
81
	  * volume registers of the SCC/YM2413 sound chips, but another
82
	  * register that controls the total output of those chips.
83
	  *
84
	  * This method allows to change that per-chip volume.
85
	  */
86
	void setSoftwareVolume(float volume, EmuTime time);
87
	void setSoftwareVolume(float left, float right, EmuTime time);
88
89
	void recordChannel(unsigned channel, const std::string& filename);
90
	void muteChannel  (unsigned channel, bool muted);
91
92
	/** Change the balance of a single channel.
93
	 * @param channel [0, numChannels)
94
	 * @param balance [-1, +1], -1: fully left, 0: center, +1: fully right
95
	 *                 The sum of the energy of the left and right channel is kept constant.
96
	 * NOTE: after calling setBalance() a number of times you must also call postSetBalance() once.
97
	 */
98
	void setBalance(unsigned channel, float balance);
99
	virtual void postSetBalance();
100
101
	/** Query the last generated audio signal for a specific channel.
102
	  * The length of this buffer is fixed (for a specific sound device),
103
	  * see getLastBufferSize().
104
	  * It's possible that no data is available yet. In that case an empty
105
	  * buffer will be returned. For example this will be the case the first
106
	  * time you call this method. Collecting this data is not free, so by
107
	  * default we don't do it.
108
	  * Requesting this data will automatically start the
109
	  * collecting-process. So, some time later, when you call this method
110
	  * again, there is a good chance that it will succeed.
111
	  * And similarly, if you stop calling this method for some time, we
112
	  * will automatically stop collecting this data.
113
	  * This method is meant to be used by the GUI. There it's acceptable
114
	  * that the first few frames no data is available yet.
115
	  */
116
	[[nodiscard]] std::span<const float> getLastBuffer(unsigned channel);
117
118
	/** The samples returned by 'getLastBuffer()' have this sample rate.
119
	  */
120
	[[nodiscard]] float getNativeSampleRate() const { return float(inputSampleRate); }
121
122
	/** getLastBuffer() with return buffers containing this many samples.
123
	  * This number depends on the native-sample-rate of the device. For all
124
	  * devices it will be (approximately) represent the same time duration.
125
	  */
126
	[[nodiscard]] unsigned getLastMonoBufferSize() const {
127
		// Tuned so that both the ImGui waveform and spectrum viewer
128
		// look 'nice' (and aren't too expensive to calculate).
129
		return inputSampleRate / 7; // ~0.14 seconds
130
	}
131
	[[nodiscard]] unsigned getLastBufferSize() const {
132
		return getLastMonoBufferSize() * stereo;
133
	}
134
135
protected:
136
	/** Constructor.
137
	  * @param mixer The Mixer object
138
	  * @param name Name for this device, will be made unique
139
	  * @param description Description for this sound device
140
	  * @param numChannels The number of channels for this device
141
	  * @param inputRate The sample rate of this sound device
142
	  * @param stereo Is this a stereo device
143
	  */
144
	SoundDevice(MSXMixer& mixer, std::string_view name, static_string_view description,
145
	            unsigned numChannels, unsigned inputRate, bool stereo);
146
	~SoundDevice();
147
148
	/** Get amplification/attenuation factor for this device.
149
	  * Normally the outputBuffer() method should scale the output to
150
	  * the range [-1.0..+1.0]. But sometimes it's more convenient to
151
	  * generate another output range. In later stages the output
152
	  * is anyway still multiplied by some factor. This method tells which
153
	  * factor should be used to scale the output to the correct range.
154
	  * The default implementation returns '1.0 / 32768.0'.
155
	  */
156
	[[nodiscard]] virtual float getAmplificationFactorImpl() const;
157
158
	/**
159
	 * Registers this sound device with the Mixer.
160
	 * Call this method when the sound device is ready to start receiving
161
	 * calls to updateBuffer, so after all initialisation is done.
162
	 * @param config Configuration data for this sound device.
163
	 */
164
	void registerSound(const DeviceConfig& config);
165
166
	/**
167
	 * Unregisters this sound device with the Mixer.
168
	 * Call this method before any deallocation is done.
169
	 */
170
	void unregisterSound();
171
172
	/** @see Mixer::updateStream */
173
	void updateStream(EmuTime time);
174
175
	void setInputRate(unsigned sampleRate) { inputSampleRate = sampleRate; }
176
	[[nodiscard]] unsigned getInputRate() const { return inputSampleRate; }
177
178
public: // Will be called by Mixer:
179
	/**
180
	 * When a SoundDevice registers itself with the Mixer, the Mixer sets
181
	 * the required sampleRate through this method. All sound devices share
182
	 * a common sampleRate.
183
	 */
184
	virtual void setOutputRate(unsigned hostSampleRate, double speed) = 0;
185
186
	/** Generate sample data
187
	  * @param length The number of required samples
188
	  * @param buffer This buffer should be filled
189
	  * @param time current time
190
	  * @result false iff the output is empty. IOW filling the buffer with
191
	  *         zeros or returning false has the same effect, but the latter
192
	  *         can be more efficient
193
	  *
194
	  * This method is regularly called from the Mixer, it should return a
195
	  * pointer to a buffer filled with the required number of samples.
196
	  * Samples are always floats, later they are converted to the systems
197
	  * native format (e.g. 16-bit signed).
198
	  *
199
	  * Note: To enable various optimizations (like SSE), this method can
200
	  * fill the output buffer with up to 3 extra samples. Those extra
201
	  * samples should be ignored, though the caller must make sure the
202
	  * buffer has enough space to hold them.
203
	  */
204
	[[nodiscard]] virtual bool updateBuffer(size_t length, float* buffer,
205
	                                        EmuTime time) = 0;
206
207
protected:
208
	/** Adds a number of samples that all have the same value.
209
	  * Can be used to synthesize segments of a square wave.
210
	  * @param buffer Pointer to the position in a sample buffer where the
211
	  *               samples should be added. This pointer is updated to
212
	  *               the position right after the written samples.
213
	  * @param value Sample value (amplitude).
214
	  * @param num The number of samples.
215
	  */
216
	static void addFill(float*& buffer, float value, unsigned num);
217
218
	/** Abstract method to generate the actual sound data.
219
	  * @param buffers An array of pointer to buffers. Each buffer must
220
	  *                be big enough to hold 'num' samples.
221
	  * @param num The number of samples.
222
	  *
223
	  * This method should fill each buffer with sound data that
224
	  * corresponds to one channel of the sound device. The same channel
225
	  * should each time be written to the same buffer (needed for record).
226
	  *
227
	  * If a certain channel is muted it is allowed to set the buffer
228
	  * pointer to nullptr. This has exactly the same effect as filling the
229
	  * buffer completely with zeros, but it can be more efficient.
230
	  */
231
	virtual void generateChannels(std::span<float*> buffers, unsigned num) = 0;
232
233
	/** Calls generateChannels() and combines the output to a single
234
	  * channel.
235
	  * @param dataOut Output buffer, must be big enough to hold
236
	  *                'samples' number of samples
237
	  * @param samples The number of samples
238
	  * @result true iff at least one channel was unmuted
239
	  *
240
	  * Note: To enable various optimizations (like SSE), this method can
241
	  * fill the output buffer with up to 3 extra samples. Those extra
242
	  * samples should be ignored, though the caller must make sure the
243
	  * buffer has enough space to hold them.
244
	  */
245
	[[nodiscard]] bool mixChannels(float* dataOut, size_t samples);
246
247
	/** See MSXMixer::getHostSampleClock(). */
248
	[[nodiscard]] const DynamicClock& getHostSampleClock() const;
249
	[[nodiscard]] double getEffectiveSpeed() const;
250
251
private:
252
	struct Balance { // amplitude multiplication factors
253
		float left, right;
254
	};
255
256
	MSXMixer& mixer;
257
	const std::string name;
258
	const static_string_view description;
259
260
	std::array<std::optional<Wav16Writer>, MAX_CHANNELS> writer;
261
262
	float softwareVolumeLeft = 1.0f;
263
	float softwareVolumeRight = 1.0f;
264
	unsigned inputSampleRate;
265
	const unsigned numChannels;
266
	const unsigned stereo;
267
	unsigned numRecordChannels = 0;
268
	std::array<Balance,  MAX_CHANNELS> channelBalance;
269
	std::array<bool, MAX_CHANNELS> channelMuted;
270
	bool balanceCenter = true;
271
272
	// When channel data needs to be collected separately (e.g. because
273
	// we're recording the channel, or because we want to present it in the
274
	// GUI), we store that data in this struct.
275
	struct PerChannelBuffer {
276
		std::vector<float> buffer; // dynamically grows when needed
277
		unsigned stopIdx = 0; // buffer only contains valid data at indices [0, stopIdx)
278
		unsigned requestCounter = 0; // != 0 means we're interested in collecting this data
279
		unsigned silent = 999999; // how many 0 samples are at the end of the buffer
280
	};
281
	std::array<PerChannelBuffer, MAX_CHANNELS> channelBuffers = {};
282
};
283
284
} // namespace openmsx
285
286
#endif