Skip to content

Commit a1f9b86

Browse files
committed
New sound backend support.
1 parent 047f604 commit a1f9b86

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+3958
-1046
lines changed

Diff for: docs/man/mame.6

+1-1
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ the audio output is overdriven. The default is ON (\-compressor).
944944
.TP
945945
.B \-volume, \-vol \fIvalue
946946
Sets the startup volume. It can later be changed with the user interface
947-
(see Keys section). The volume is an attenuation in dB: e.g.,
947+
(see Keys section). The volume is in dB: e.g.,
948948
"\-volume \-12" will start with \-12dB attenuation. The default is 0.
949949
.\" +++++++++++++++++++++++++++++++++++++++++++++++++++++++
950950
.\" SDL specific

Diff for: docs/source/commandline/commandline-all.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -2978,7 +2978,7 @@ Core Sound Options
29782978
**-volume** / **-vol** *<value>*
29792979
29802980
Sets the initial sound volume. It can be changed later with the user
2981-
interface (see Keys section). The volume is an attenuation in decibels:
2981+
interface (see Keys section). The volume is in decibels:
29822982
e.g. "**-volume -12**" will start with -12 dB attenuation. Note that if the
29832983
volume is changed in the user interface it will be saved to the
29842984
configuration file for the system. The value from the configuration file

Diff for: docs/source/luascript/ref-core.rst

+2-3
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,8 @@ sound.debugger_mute (read/write)
404404
sound.system_mute (read/write)
405405
A Boolean indicating whether sound output is muted at the request of the
406406
emulated system.
407-
sound.attenuation (read/write)
408-
The output volume attenuation in decibels. Should generally be a negative
409-
integer or zero.
407+
sound.volume (read/write)
408+
The output volume in decibels. Should generally be a negative or zero.
410409
sound.recording (read-only)
411410
A Boolean indicating whether sound output is currently being recorded to a
412411
WAV file.

Diff for: docs/source/techspecs/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ MAME’s source or working on scripts that run within the MAME framework.
2020
nscsi
2121
m6502
2222
poly_manager
23+
osd_audio

Diff for: docs/source/techspecs/osd_audio.rst

+299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
OSD audio support
2+
=================
3+
4+
Introduction
5+
------------
6+
7+
The audio support in Mame tries to allow the user to freely map
8+
between the emulated system audio outputs (called speakers) and the
9+
host system audio. A part of it is the OSD support, where a
10+
host-specific module ensures the interface between Mame and the host.
11+
This is the documentation for that module.
12+
13+
Note: this is currenty output-only, but input should follow.
14+
15+
16+
Capabitilies
17+
------------
18+
19+
The OSD interface is designed to allow three levels of support,
20+
depending on what the API allows and the amount of effort to expend.
21+
Those are:
22+
23+
* Level 1: One or more audio targets, only one stream allowed per target (aka exclusive mode)
24+
* Level 2: One or more audio targets, multiple streams per target
25+
* Level 3: One or more audio targets, multiple streams per target, user-visible per-stream-channel volume control
26+
27+
In any case we support having the user use an external interface to
28+
change the target of a stream and, in level 3, change the volumes. By
29+
support we mean storing the information in the per-game configuration
30+
and keeping in the internal UI in sync.
31+
32+
33+
Terminology
34+
-----------
35+
36+
For this module, we use the terms:
37+
38+
* node: some object we can send audio to. Can be physical, like speakers, or virtual, like an effect system. It should have a unique, user-presentable name for the UI.
39+
* port: a channel of a node, has a name (non-unique, like "front left") and a 3D position
40+
* stream: a connection to a node with allows to send audio to it
41+
42+
43+
Reference documentation
44+
-----------------------
45+
46+
Adding a module
47+
~~~~~~~~~~~~~~~
48+
49+
Adding a module is done by adding a cpp file to src/osd/modules/sound
50+
which follows this structure,
51+
52+
.. code-block:: C++
53+
54+
// License/copyright
55+
#include "sound_module.h"
56+
#include "modules/osdmodules.h"
57+
58+
#ifdef MODULE_SUPPORT_KEY
59+
60+
#include "modules/lib/osdobj_common.h"
61+
62+
// [...]
63+
namespace osd {
64+
namespace {
65+
66+
class sound_module_class : public osd_module, public sound_module
67+
{
68+
sound_module_class() : osd_module(OSD_SOUND_PROVIDER, "module_name"),
69+
sound_module()
70+
// ...
71+
};
72+
73+
}
74+
}
75+
#else
76+
namespace osd { namespace {
77+
MODULE_NOT_SUPPORTED(sound_module_class, OSD_SOUND_PROVIDER, "module_name")
78+
}}
79+
#endif
80+
81+
MODULE_DEFINITION(SOUND_MODULE_KEY, osd::sound_module_class)
82+
83+
In that code, four names must be chosen:
84+
85+
* MODULE_SUPPORT_KEY some #define coming from the genie scripts to tell that this particular module can be compiled (like NO_USE_PIPEWIRE or SDLMAME_MACOSX)
86+
* sound_module_class is the name of the class which makes up the module (like sound_coreaudio)
87+
* module_name is the name to be used in -sound <xxx> to select that particular module (like coreaudio)
88+
* SOUND_MODULE_KEY is a symbol that represents the module internally (like SOUND_COREAUDIO)
89+
90+
The file path needs to be added to scripts/src/osd/modules.lua in
91+
osdmodulesbuild() and the module reference to
92+
src/osd/modules/lib/osdobj_common.cpp in
93+
osd_common_t::register_options with the line:
94+
95+
.. code-block:: C++
96+
97+
REGISTER_MODULE(m_mod_man, SOUND_MODULE_KEY);
98+
99+
This should ensure that the module is reachable through -sound <xxx>
100+
on the appropriate hosts.
101+
102+
103+
Interface
104+
~~~~~~~~~
105+
106+
The full interface is:
107+
108+
.. code-block:: C++
109+
110+
virtual bool split_streams_per_source() const override;
111+
virtual bool external_per_channel_volume() const override;
112+
113+
virtual int init(osd_interface &osd, osd_options const &options) override;
114+
virtual void exit() override;
115+
116+
virtual uint32_t get_generation() override;
117+
virtual osd::audio_info get_information() override;
118+
virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override;
119+
virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) override;
120+
virtual void stream_close(uint32_t id) override;
121+
virtual void stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override;
122+
123+
124+
The class sound_module provides default for minimum capabilities: one
125+
stereo target and stream at default sample rate. To support that,
126+
only *init*, *exit* and *stream_update* need to be implemented.
127+
*init* is called at startup and *exit* when quitting and can do
128+
whatever they need to do. *stream_update* will be called on a regular
129+
basis with a buffer of sample_this_frame*2*int16_t with the audio
130+
to play. From this point in the documentation we'll assume more than
131+
a single stereo channel is wanted.
132+
133+
134+
Capabilities
135+
~~~~~~~~~~~~
136+
137+
Two methods are used by the module to indicate the level of capability
138+
of the module:
139+
140+
* split_streams_per_source() should return true when having multiple streams for one target is expected (e.g. Level 2 or 3)
141+
* external_per_channel_volume() should return true when the streams have per-channel volume control that can be externally controlled (e.g. Level 3)
142+
143+
144+
Hardware information and generations
145+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
146+
147+
The core runs on the assumption that the host hardware capabilities
148+
can change at any time (bluetooth devices coming and going, usb
149+
hot-plugging...) and that the module has some way to keep tabs on what
150+
is happening, possibly using multi-threading. To keep it
151+
lightweight-ish, we use the concept of a *generation* which is a
152+
32-bits number that is incremented by the module every time something
153+
changes. The core checks the current generation value at least once
154+
every update (once per frame, usually) and if it changed asks for the
155+
new state and detects and handles the differences. *generation*
156+
should be "eventually stable", e.g. it eventually stops changing when
157+
the user stops changing things all the time. A systematic increment
158+
every frame would be a bad idea.
159+
160+
.. code-block:: C++
161+
162+
virtual uint32_t get_generation() override;
163+
164+
That method returns the current generation number. It's called at a
165+
minimum once per update, which usually means per frame. It whould be
166+
reasonably lightweight when nothing special happens.
167+
168+
.. code-block: C++
169+
170+
virtual osd::audio_info get_information() override;
171+
172+
struct audio_rate_range {
173+
uint32_t m_default_rate;
174+
uint32_t m_min_rate;
175+
uint32_t m_max_rate;
176+
};
177+
178+
struct audio_info {
179+
struct port_info {
180+
std::string m_name;
181+
std::array<double, 3> m_position;
182+
};
183+
184+
struct node_info {
185+
std::string m_name;
186+
uint32_t m_id;
187+
audio_rate_range m_rate;
188+
std::vector<port_info> m_sinks;
189+
std::vector<port_info> m_sources;
190+
};
191+
192+
struct stream_info {
193+
uint32_t m_id;
194+
uint32_t m_node;
195+
std::vector<float> m_volumes;
196+
};
197+
198+
uint32_t m_generation;
199+
uint32_t m_default_sink;
200+
uint32_t m_default_source;
201+
std::vector<node_info> m_nodes;
202+
std::vector<stream_info> m_streams;
203+
};
204+
205+
This method must provide all the information about the current state
206+
of the host and the module. This state is:
207+
208+
* m_generation: The current generation number
209+
* m_nodes: The vector available nodes (*node_info*)
210+
211+
* m_name: The name of the node
212+
* m_id: The numeric ID of the node
213+
* m_rate: The minimum, maximum and preferred sample rate for the node
214+
* m_sinks: The vector of sink (output) ports of the node (*port_info*)
215+
216+
* m_name: The name of the port
217+
* m_position: The 3D position of the port. Refer to src/emu/speaker.h for the "standard" positions
218+
219+
* m_sources: The vector of source (input) ports of the node. Currently unused
220+
221+
* m_default_sink: ID of the node that is the current "system default" for audio output, 0 if there's no such concept
222+
* m_default_source: same for audio input (currently unused)
223+
* m_streams: The vector of active streams (*stream_info*)
224+
225+
* m_id: The numeric ID of the stream
226+
* m_node: The target node of the stream
227+
* m_volumes: empty if *external_per_channel_volume* is false, current volume value per-channel otherwise
228+
229+
IDs, for nodes and streams, are (independant) 32-bit unsigned non-zero
230+
values associated to respectively nodes and streams. IDs should not
231+
be reused. A node that goes away then comes back should get a new ID.
232+
A stream that is closed should not enable reuse of its ID.
233+
234+
When external control exists, a module should change the value of
235+
*stream_info::m_node* when the user changes it, and same for
236+
*stream_info::m_volumes*. Generation number should be incremented
237+
when this happens, so that the core knows to look for changes.
238+
239+
Volumes are floats in dB, where 0 means 100% and -96 means no sound.
240+
audio.h provides osd::db_to_linear and osd::linear_to_db if such a
241+
conversion is needed.
242+
243+
There is an inherent race condition with this system, because things
244+
can change at any point after returning for the method. The idea is
245+
that the information returned must be internally consistent (a stream
246+
should not point to a node ID that does not exist in the structure,
247+
same for default sink) and that any external change from that state
248+
should increment the generation number, but that's it. Through the
249+
generation system the core will eventually be in sync with the
250+
reality.
251+
252+
253+
Output streams
254+
~~~~~~~~~~~~~~
255+
256+
.. code-block: C++
257+
258+
virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override;
259+
virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) override;
260+
virtual void stream_close(uint32_t id) override;
261+
virtual void stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override;
262+
263+
Streams are the concept used to send audio to the host audio system.
264+
A stream is first opened through *stream_sink_open* and targets a
265+
specific node at a specific sample rate. It is given a name for use
266+
by the host sound services for user UI purposes (currently the game
267+
name if split_streams_per_source is false, the speaker_device tag if
268+
true). The returned ID must be a non-zero, never-used-before for
269+
streams value in case of success. Failures, like when the node went
270+
away between the get_information call and the open one, should be
271+
silent and return zero.
272+
273+
*stream_set_volumes* is used only then *external_per_channel_volume*
274+
is true and is used by the core to set the per-channel volume. The
275+
call should just be ignored if the stream ID does not exist (or is
276+
zero). Do not try to apply volumes in the module if the host API
277+
doesn't provide for it, let the core handle it.
278+
279+
*stream_close* closes a stream, The call should just be ignored if the
280+
stream ID does not exist (or is zero).
281+
282+
Opening a stream, closing a stream or changing the volume does not
283+
need to touch the generation number.
284+
285+
*stream_update* is the method used to send data to the node through a
286+
given stream. It provides a buffer of *samples_this_frame* * *node
287+
channel count* channel-interleaved int16_t values. The lifetime of
288+
the data in the buffer or the buffer pointer itself is undefined after
289+
return from the method call. The call should just be ignored if the
290+
stream ID does not exist (or is zero).
291+
292+
When a stream goes away because the target node is lost it should just
293+
be removed from the information, and the core will pick up the node
294+
departure and close the stream.
295+
296+
Given the assumed raceness of the interface, all the methods should be
297+
tolerant of obsolete or zero IDs being used by the core, and that is
298+
why ID reuse must be avoided.
299+

Diff for: makefile

+5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
# NO_USE_MIDI = 1
3535
# NO_USE_PORTAUDIO = 1
3636
# NO_USE_PULSEAUDIO = 1
37+
# NO_USE_PIPEWIRE = 1
3738
# USE_TAPTUN = 1
3839
# USE_PCAP = 1
3940
# USE_QTDEBUG = 1
@@ -783,6 +784,10 @@ ifdef NO_USE_PULSEAUDIO
783784
PARAMS += --NO_USE_PULSEAUDIO='$(NO_USE_PULSEAUDIO)'
784785
endif
785786

787+
ifdef NO_USE_PIPEWIRE
788+
PARAMS += --NO_USE_PIPEWIRE='$(NO_USE_PIPEWIRE)'
789+
endif
790+
786791
ifdef USE_QTDEBUG
787792
PARAMS += --USE_QTDEBUG='$(USE_QTDEBUG)'
788793
endif

Diff for: scripts/src/mame/frontend.lua

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ files {
8989
MAME_DIR .. "src/frontend/mame/ui/about.h",
9090
MAME_DIR .. "src/frontend/mame/ui/analogipt.cpp",
9191
MAME_DIR .. "src/frontend/mame/ui/analogipt.cpp",
92+
MAME_DIR .. "src/frontend/mame/ui/audiomix.cpp",
93+
MAME_DIR .. "src/frontend/mame/ui/audiomix.h",
9294
MAME_DIR .. "src/frontend/mame/ui/auditmenu.cpp",
9395
MAME_DIR .. "src/frontend/mame/ui/auditmenu.h",
9496
MAME_DIR .. "src/frontend/mame/ui/barcode.cpp",

0 commit comments

Comments
 (0)