diff --git a/docs/man/mame.6 b/docs/man/mame.6 index 3d83084e4c0b9..5d0123f486fe1 100644 --- a/docs/man/mame.6 +++ b/docs/man/mame.6 @@ -944,7 +944,7 @@ the audio output is overdriven. The default is ON (\-compressor). .TP .B \-volume, \-vol \fIvalue Sets the startup volume. It can later be changed with the user interface -(see Keys section). The volume is an attenuation in dB: e.g., +(see Keys section). The volume is in dB: e.g., "\-volume \-12" will start with \-12dB attenuation. The default is 0. .\" +++++++++++++++++++++++++++++++++++++++++++++++++++++++ .\" SDL specific diff --git a/docs/source/commandline/commandline-all.rst b/docs/source/commandline/commandline-all.rst index 89f7dac128794..33dc1dfe8ea30 100644 --- a/docs/source/commandline/commandline-all.rst +++ b/docs/source/commandline/commandline-all.rst @@ -2978,7 +2978,7 @@ Core Sound Options **-volume** / **-vol** ** Sets the initial sound volume. It can be changed later with the user - interface (see Keys section). The volume is an attenuation in decibels: + interface (see Keys section). The volume is in decibels: e.g. "**-volume -12**" will start with -12 dB attenuation. Note that if the volume is changed in the user interface it will be saved to the configuration file for the system. The value from the configuration file diff --git a/docs/source/luascript/ref-core.rst b/docs/source/luascript/ref-core.rst index f761b20f5deac..74aaa63de632f 100644 --- a/docs/source/luascript/ref-core.rst +++ b/docs/source/luascript/ref-core.rst @@ -404,9 +404,8 @@ sound.debugger_mute (read/write) sound.system_mute (read/write) A Boolean indicating whether sound output is muted at the request of the emulated system. -sound.attenuation (read/write) - The output volume attenuation in decibels. Should generally be a negative - integer or zero. +sound.volume (read/write) + The output volume in decibels. Should generally be a negative or zero. sound.recording (read-only) A Boolean indicating whether sound output is currently being recorded to a WAV file. diff --git a/docs/source/techspecs/index.rst b/docs/source/techspecs/index.rst index ee41de889d835..8e3787e0cb99d 100644 --- a/docs/source/techspecs/index.rst +++ b/docs/source/techspecs/index.rst @@ -20,3 +20,4 @@ MAME’s source or working on scripts that run within the MAME framework. nscsi m6502 poly_manager + osd_audio diff --git a/docs/source/techspecs/osd_audio.rst b/docs/source/techspecs/osd_audio.rst new file mode 100644 index 0000000000000..ed1a92ba420c0 --- /dev/null +++ b/docs/source/techspecs/osd_audio.rst @@ -0,0 +1,299 @@ +OSD audio support +================= + +Introduction +------------ + +The audio support in Mame tries to allow the user to freely map +between the emulated system audio outputs (called speakers) and the +host system audio. A part of it is the OSD support, where a +host-specific module ensures the interface between Mame and the host. +This is the documentation for that module. + +Note: this is currenty output-only, but input should follow. + + +Capabitilies +------------ + +The OSD interface is designed to allow three levels of support, +depending on what the API allows and the amount of effort to expend. +Those are: + +* Level 1: One or more audio targets, only one stream allowed per target (aka exclusive mode) +* Level 2: One or more audio targets, multiple streams per target +* Level 3: One or more audio targets, multiple streams per target, user-visible per-stream-channel volume control + +In any case we support having the user use an external interface to +change the target of a stream and, in level 3, change the volumes. By +support we mean storing the information in the per-game configuration +and keeping in the internal UI in sync. + + +Terminology +----------- + +For this module, we use the terms: + +* 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. +* port: a channel of a node, has a name (non-unique, like "front left") and a 3D position +* stream: a connection to a node with allows to send audio to it + + +Reference documentation +----------------------- + +Adding a module +~~~~~~~~~~~~~~~ + +Adding a module is done by adding a cpp file to src/osd/modules/sound +which follows this structure, + +.. code-block:: C++ + + // License/copyright + #include "sound_module.h" + #include "modules/osdmodules.h" + + #ifdef MODULE_SUPPORT_KEY + + #include "modules/lib/osdobj_common.h" + + // [...] + namespace osd { + namespace { + + class sound_module_class : public osd_module, public sound_module + { + sound_module_class() : osd_module(OSD_SOUND_PROVIDER, "module_name"), + sound_module() + // ... + }; + + } + } + #else + namespace osd { namespace { + MODULE_NOT_SUPPORTED(sound_module_class, OSD_SOUND_PROVIDER, "module_name") + }} + #endif + + MODULE_DEFINITION(SOUND_MODULE_KEY, osd::sound_module_class) + +In that code, four names must be chosen: + +* 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) +* sound_module_class is the name of the class which makes up the module (like sound_coreaudio) +* module_name is the name to be used in -sound to select that particular module (like coreaudio) +* SOUND_MODULE_KEY is a symbol that represents the module internally (like SOUND_COREAUDIO) + +The file path needs to be added to scripts/src/osd/modules.lua in +osdmodulesbuild() and the module reference to +src/osd/modules/lib/osdobj_common.cpp in +osd_common_t::register_options with the line: + +.. code-block:: C++ + + REGISTER_MODULE(m_mod_man, SOUND_MODULE_KEY); + +This should ensure that the module is reachable through -sound +on the appropriate hosts. + + +Interface +~~~~~~~~~ + +The full interface is: + +.. code-block:: C++ + + virtual bool split_streams_per_source() const override; + virtual bool external_per_channel_volume() const override; + + virtual int init(osd_interface &osd, osd_options const &options) override; + virtual void exit() override; + + virtual uint32_t get_generation() override; + virtual osd::audio_info get_information() override; + virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override; + virtual void stream_set_volumes(uint32_t id, const std::vector &db) override; + virtual void stream_close(uint32_t id) override; + virtual void stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override; + + +The class sound_module provides default for minimum capabilities: one +stereo target and stream at default sample rate. To support that, +only *init*, *exit* and *stream_update* need to be implemented. +*init* is called at startup and *exit* when quitting and can do +whatever they need to do. *stream_update* will be called on a regular +basis with a buffer of sample_this_frame*2*int16_t with the audio +to play. From this point in the documentation we'll assume more than +a single stereo channel is wanted. + + +Capabilities +~~~~~~~~~~~~ + +Two methods are used by the module to indicate the level of capability +of the module: + +* split_streams_per_source() should return true when having multiple streams for one target is expected (e.g. Level 2 or 3) +* external_per_channel_volume() should return true when the streams have per-channel volume control that can be externally controlled (e.g. Level 3) + + +Hardware information and generations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The core runs on the assumption that the host hardware capabilities +can change at any time (bluetooth devices coming and going, usb +hot-plugging...) and that the module has some way to keep tabs on what +is happening, possibly using multi-threading. To keep it +lightweight-ish, we use the concept of a *generation* which is a +32-bits number that is incremented by the module every time something +changes. The core checks the current generation value at least once +every update (once per frame, usually) and if it changed asks for the +new state and detects and handles the differences. *generation* +should be "eventually stable", e.g. it eventually stops changing when +the user stops changing things all the time. A systematic increment +every frame would be a bad idea. + +.. code-block:: C++ + + virtual uint32_t get_generation() override; + +That method returns the current generation number. It's called at a +minimum once per update, which usually means per frame. It whould be +reasonably lightweight when nothing special happens. + +.. code-block: C++ + + virtual osd::audio_info get_information() override; + + struct audio_rate_range { + uint32_t m_default_rate; + uint32_t m_min_rate; + uint32_t m_max_rate; + }; + + struct audio_info { + struct port_info { + std::string m_name; + std::array m_position; + }; + + struct node_info { + std::string m_name; + uint32_t m_id; + audio_rate_range m_rate; + std::vector m_sinks; + std::vector m_sources; + }; + + struct stream_info { + uint32_t m_id; + uint32_t m_node; + std::vector m_volumes; + }; + + uint32_t m_generation; + uint32_t m_default_sink; + uint32_t m_default_source; + std::vector m_nodes; + std::vector m_streams; + }; + +This method must provide all the information about the current state +of the host and the module. This state is: + +* m_generation: The current generation number +* m_nodes: The vector available nodes (*node_info*) + + * m_name: The name of the node + * m_id: The numeric ID of the node + * m_rate: The minimum, maximum and preferred sample rate for the node + * m_sinks: The vector of sink (output) ports of the node (*port_info*) + + * m_name: The name of the port + * m_position: The 3D position of the port. Refer to src/emu/speaker.h for the "standard" positions + + * m_sources: The vector of source (input) ports of the node. Currently unused + +* m_default_sink: ID of the node that is the current "system default" for audio output, 0 if there's no such concept +* m_default_source: same for audio input (currently unused) +* m_streams: The vector of active streams (*stream_info*) + + * m_id: The numeric ID of the stream + * m_node: The target node of the stream + * m_volumes: empty if *external_per_channel_volume* is false, current volume value per-channel otherwise + +IDs, for nodes and streams, are (independant) 32-bit unsigned non-zero +values associated to respectively nodes and streams. IDs should not +be reused. A node that goes away then comes back should get a new ID. +A stream that is closed should not enable reuse of its ID. + +When external control exists, a module should change the value of +*stream_info::m_node* when the user changes it, and same for +*stream_info::m_volumes*. Generation number should be incremented +when this happens, so that the core knows to look for changes. + +Volumes are floats in dB, where 0 means 100% and -96 means no sound. +audio.h provides osd::db_to_linear and osd::linear_to_db if such a +conversion is needed. + +There is an inherent race condition with this system, because things +can change at any point after returning for the method. The idea is +that the information returned must be internally consistent (a stream +should not point to a node ID that does not exist in the structure, +same for default sink) and that any external change from that state +should increment the generation number, but that's it. Through the +generation system the core will eventually be in sync with the +reality. + + +Output streams +~~~~~~~~~~~~~~ + +.. code-block: C++ + + virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override; + virtual void stream_set_volumes(uint32_t id, const std::vector &db) override; + virtual void stream_close(uint32_t id) override; + virtual void stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override; + +Streams are the concept used to send audio to the host audio system. +A stream is first opened through *stream_sink_open* and targets a +specific node at a specific sample rate. It is given a name for use +by the host sound services for user UI purposes (currently the game +name if split_streams_per_source is false, the speaker_device tag if +true). The returned ID must be a non-zero, never-used-before for +streams value in case of success. Failures, like when the node went +away between the get_information call and the open one, should be +silent and return zero. + +*stream_set_volumes* is used only then *external_per_channel_volume* +is true and is used by the core to set the per-channel volume. The +call should just be ignored if the stream ID does not exist (or is +zero). Do not try to apply volumes in the module if the host API +doesn't provide for it, let the core handle it. + +*stream_close* closes a stream, The call should just be ignored if the +stream ID does not exist (or is zero). + +Opening a stream, closing a stream or changing the volume does not +need to touch the generation number. + +*stream_update* is the method used to send data to the node through a +given stream. It provides a buffer of *samples_this_frame* * *node +channel count* channel-interleaved int16_t values. The lifetime of +the data in the buffer or the buffer pointer itself is undefined after +return from the method call. The call should just be ignored if the +stream ID does not exist (or is zero). + +When a stream goes away because the target node is lost it should just +be removed from the information, and the core will pick up the node +departure and close the stream. + +Given the assumed raceness of the interface, all the methods should be +tolerant of obsolete or zero IDs being used by the core, and that is +why ID reuse must be avoided. + diff --git a/makefile b/makefile index 3a886bbf6bbfd..da56910e501b6 100644 --- a/makefile +++ b/makefile @@ -34,6 +34,7 @@ # NO_USE_MIDI = 1 # NO_USE_PORTAUDIO = 1 # NO_USE_PULSEAUDIO = 1 +# NO_USE_PIPEWIRE = 1 # USE_TAPTUN = 1 # USE_PCAP = 1 # USE_QTDEBUG = 1 @@ -783,6 +784,10 @@ ifdef NO_USE_PULSEAUDIO PARAMS += --NO_USE_PULSEAUDIO='$(NO_USE_PULSEAUDIO)' endif +ifdef NO_USE_PIPEWIRE +PARAMS += --NO_USE_PIPEWIRE='$(NO_USE_PIPEWIRE)' +endif + ifdef USE_QTDEBUG PARAMS += --USE_QTDEBUG='$(USE_QTDEBUG)' endif diff --git a/scripts/src/mame/frontend.lua b/scripts/src/mame/frontend.lua index cee3aa4a7d78c..d3e51328307a0 100644 --- a/scripts/src/mame/frontend.lua +++ b/scripts/src/mame/frontend.lua @@ -89,6 +89,8 @@ files { MAME_DIR .. "src/frontend/mame/ui/about.h", MAME_DIR .. "src/frontend/mame/ui/analogipt.cpp", MAME_DIR .. "src/frontend/mame/ui/analogipt.cpp", + MAME_DIR .. "src/frontend/mame/ui/audiomix.cpp", + MAME_DIR .. "src/frontend/mame/ui/audiomix.h", MAME_DIR .. "src/frontend/mame/ui/auditmenu.cpp", MAME_DIR .. "src/frontend/mame/ui/auditmenu.h", MAME_DIR .. "src/frontend/mame/ui/barcode.cpp", diff --git a/scripts/src/osd/modules.lua b/scripts/src/osd/modules.lua index 43cb768704d5a..0301847abd91f 100644 --- a/scripts/src/osd/modules.lua +++ b/scripts/src/osd/modules.lua @@ -54,6 +54,7 @@ function osdmodulesbuild() MAME_DIR .. "src/osd/osdnet.h", MAME_DIR .. "src/osd/watchdog.cpp", MAME_DIR .. "src/osd/watchdog.h", + MAME_DIR .. "src/osd/interface/audio.h", MAME_DIR .. "src/osd/interface/inputcode.h", MAME_DIR .. "src/osd/interface/inputdev.h", MAME_DIR .. "src/osd/interface/inputfwd.h", @@ -133,6 +134,7 @@ function osdmodulesbuild() MAME_DIR .. "src/osd/modules/sound/none.cpp", MAME_DIR .. "src/osd/modules/sound/pa_sound.cpp", MAME_DIR .. "src/osd/modules/sound/pulse_sound.cpp", + MAME_DIR .. "src/osd/modules/sound/pipewire_sound.cpp", MAME_DIR .. "src/osd/modules/sound/sdl_sound.cpp", MAME_DIR .. "src/osd/modules/sound/sound_module.h", MAME_DIR .. "src/osd/modules/sound/xaudio2_sound.cpp", @@ -301,6 +303,22 @@ function osdmodulesbuild() } end + err = os.execute(pkgconfigcmd() .. " --exists libpipewire-0.3") + if not err then + _OPTIONS["NO_USE_PIPEWIRE"] = "1" + end + + if _OPTIONS["NO_USE_PIPEWIRE"]=="1" then + defines { + "NO_USE_PIPEWIRE", + } + else + buildoptions { + backtick(pkgconfigcmd() .. " --cflags libpipewire-0.3"), + } + end + + if _OPTIONS["NO_USE_MIDI"]=="1" then defines { "NO_USE_MIDI", @@ -550,6 +568,12 @@ function osdmodulestargetconf() ext_lib("pulse"), } end + + if _OPTIONS["NO_USE_PIPEWIRE"]=="0" then + local str = backtick(pkgconfigcmd() .. " --libs libpipewire-0.3") + addlibfromstring(str) + addoptionsfromstring(str) + end end @@ -652,6 +676,23 @@ if not _OPTIONS["NO_USE_PULSEAUDIO"] then end end +newoption { + trigger = "NO_USE_PIPEWIRE", + description = "Disable Pipewire interface", + allowed = { + { "0", "Enable Pipewire" }, + { "1", "Disable Pipewire" }, + }, +} + +if not _OPTIONS["NO_USE_PIPEWIRE"] then + if _OPTIONS["targetos"]=="linux" then + _OPTIONS["NO_USE_PIPEWIRE"] = "0" + else + _OPTIONS["NO_USE_PIPEWIRE"] = "1" + end +end + newoption { trigger = "MODERN_WIN_API", description = "Use Modern Windows APIs", diff --git a/src/devices/bus/plg1x0/plg100-vl.cpp b/src/devices/bus/plg1x0/plg100-vl.cpp index 177813052df6f..f41b289ad5b91 100644 --- a/src/devices/bus/plg1x0/plg100-vl.cpp +++ b/src/devices/bus/plg1x0/plg100-vl.cpp @@ -67,8 +67,8 @@ void plg100_vl_device::device_add_mconfig(machine_config &config) m_cpu->write_sci_tx<1>().set([this] (int state) { m_connector->do_midi_tx(state); }); DSPV(config, m_dspv, 22.5792_MHz_XTAL); - m_dspv->add_route(0, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 0); - m_dspv->add_route(1, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 1); + m_dspv->add_route(0, DEVICE_SELF_OWNER, 1.0, 0); + m_dspv->add_route(1, DEVICE_SELF_OWNER, 1.0, 1); } ROM_START( plg100_vl ) diff --git a/src/devices/bus/waveblaster/db50xg.cpp b/src/devices/bus/waveblaster/db50xg.cpp index 76cf8a473306e..3538897702cc3 100644 --- a/src/devices/bus/waveblaster/db50xg.cpp +++ b/src/devices/bus/waveblaster/db50xg.cpp @@ -71,8 +71,8 @@ void db50xg_device::device_add_mconfig(machine_config &config) m_cpu->write_sci_tx<0>().set([this] (int state) { m_connector->do_midi_tx(state); }); SWP00(config, m_swp00); - m_swp00->add_route(0, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 0); - m_swp00->add_route(1, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 1); + m_swp00->add_route(0, DEVICE_SELF_OWNER, 1.0, 0); + m_swp00->add_route(1, DEVICE_SELF_OWNER, 1.0, 1); } ROM_START( db50xg ) diff --git a/src/devices/bus/waveblaster/db60xg.cpp b/src/devices/bus/waveblaster/db60xg.cpp index 8ccd724e32077..0c16a503cd534 100644 --- a/src/devices/bus/waveblaster/db60xg.cpp +++ b/src/devices/bus/waveblaster/db60xg.cpp @@ -69,8 +69,8 @@ void db60xg_device::device_add_mconfig(machine_config &config) m_cpu->write_sci_tx<0>().set([this] (int state) { m_connector->do_midi_tx(state); }); SWP00(config, m_swp00); - m_swp00->add_route(0, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 0); - m_swp00->add_route(1, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 1); + m_swp00->add_route(0, DEVICE_SELF_OWNER, 1.0, 0); + m_swp00->add_route(1, DEVICE_SELF_OWNER, 1.0, 1); } ROM_START( db60xg ) diff --git a/src/devices/bus/waveblaster/omniwave.cpp b/src/devices/bus/waveblaster/omniwave.cpp index 913c85a6d3871..09c4497825ad0 100644 --- a/src/devices/bus/waveblaster/omniwave.cpp +++ b/src/devices/bus/waveblaster/omniwave.cpp @@ -47,8 +47,8 @@ void omniwave_device::midi_rx(int state) void omniwave_device::device_add_mconfig(machine_config &config) { KS0164(config, m_ks0164, 16.9344_MHz_XTAL); - m_ks0164->add_route(0, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 0); - m_ks0164->add_route(1, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 1); + m_ks0164->add_route(0, DEVICE_SELF_OWNER, 1.0, 0); + m_ks0164->add_route(1, DEVICE_SELF_OWNER, 1.0, 1); m_ks0164->midi_tx().set([this] (int state) { m_connector->do_midi_tx(state); }); } diff --git a/src/devices/bus/waveblaster/wg130.cpp b/src/devices/bus/waveblaster/wg130.cpp index 385a39581f3a7..8a9e903807a0a 100644 --- a/src/devices/bus/waveblaster/wg130.cpp +++ b/src/devices/bus/waveblaster/wg130.cpp @@ -77,8 +77,8 @@ void wg130_device::device_add_mconfig(machine_config &config) { GT913(config, m_gt913, 30_MHz_XTAL / 2); m_gt913->set_addrmap(AS_DATA, &wg130_device::map); - m_gt913->add_route(0, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 0); - m_gt913->add_route(1, DEVICE_SELF_OWNER, 1.0, AUTO_ALLOC_INPUT, 1); + m_gt913->add_route(0, DEVICE_SELF_OWNER, 1.0, 0); + m_gt913->add_route(1, DEVICE_SELF_OWNER, 1.0, 1); m_gt913->read_port1().set_constant(0xff); m_gt913->write_port1().set_nop(); m_gt913->read_port2().set_constant(0xff); diff --git a/src/devices/cpu/h6280/h6280.cpp b/src/devices/cpu/h6280/h6280.cpp index 7bb01c3f6d5df..dd13860bdfbb2 100644 --- a/src/devices/cpu/h6280/h6280.cpp +++ b/src/devices/cpu/h6280/h6280.cpp @@ -232,8 +232,8 @@ const h6280_device::ophandler h6280_device::s_opcodetable[256] = void h6280_device::device_add_mconfig(machine_config &config) { C6280(config, m_psg, DERIVED_CLOCK(1,2)); - m_psg->add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_psg->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_psg->add_route(0, *this, 1.0, 0); + m_psg->add_route(1, *this, 1.0, 1); } void h6280_device::device_start() diff --git a/src/devices/cpu/h8/gt913.cpp b/src/devices/cpu/h8/gt913.cpp index e32ad3f679ef5..219a492e98889 100644 --- a/src/devices/cpu/h8/gt913.cpp +++ b/src/devices/cpu/h8/gt913.cpp @@ -101,8 +101,8 @@ void gt913_device::device_add_mconfig(machine_config &config) GT913_SOUND(config, m_sound, DERIVED_CLOCK(1, 1)); m_sound->set_device_rom_tag(m_rom); - m_sound->add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_sound->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_sound->add_route(0, *this, 1.0, 0); + m_sound->add_route(1, *this, 1.0, 1); GT913_KBD_HLE(config, m_kbd, 0); m_kbd->irq_cb().set([this] (int val) { diff --git a/src/devices/cpu/h8/swx00.cpp b/src/devices/cpu/h8/swx00.cpp index 77d5355428709..620f9cfc984fc 100644 --- a/src/devices/cpu/h8/swx00.cpp +++ b/src/devices/cpu/h8/swx00.cpp @@ -316,8 +316,8 @@ void swx00_device::device_add_mconfig(machine_config &config) SWX00_SOUND(config, m_swx00); m_swx00->set_space(DEVICE_SELF, AS_S); - m_swx00->add_route(0, DEVICE_SELF, 1.0, AUTO_ALLOC_INPUT, 0); - m_swx00->add_route(1, DEVICE_SELF, 1.0, AUTO_ALLOC_INPUT, 1); + m_swx00->add_route(0, DEVICE_SELF, 1.0, 0); + m_swx00->add_route(1, DEVICE_SELF, 1.0, 1); } device_memory_interface::space_config_vector swx00_device::memory_space_config() const diff --git a/src/devices/cpu/m6502/gew7.cpp b/src/devices/cpu/m6502/gew7.cpp index 7fd85ad253861..1a42fc45b08de 100644 --- a/src/devices/cpu/m6502/gew7.cpp +++ b/src/devices/cpu/m6502/gew7.cpp @@ -37,8 +37,8 @@ void gew7_device::device_add_mconfig(machine_config &config) { GEW7_PCM(config, m_pcm, DERIVED_CLOCK(1, 1)); m_pcm->set_device_rom_tag(m_rom); - m_pcm->add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_pcm->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_pcm->add_route(0, *this, 1.0, 0); + m_pcm->add_route(1, *this, 1.0, 1); } void gew7_device::device_start() diff --git a/src/devices/cpu/m6502/rp2a03.cpp b/src/devices/cpu/m6502/rp2a03.cpp index 0d9153b42f758..61bf09fa71d81 100644 --- a/src/devices/cpu/m6502/rp2a03.cpp +++ b/src/devices/cpu/m6502/rp2a03.cpp @@ -84,7 +84,7 @@ void rp2a03_device::device_add_mconfig(machine_config &config) APU_2A03(config, m_apu, DERIVED_CLOCK(1,1)); m_apu->irq().set(FUNC(rp2a03_device::apu_irq)); m_apu->mem_read().set(FUNC(rp2a03_device::apu_read_mem)); - m_apu->add_route(ALL_OUTPUTS, *this, 1.0, AUTO_ALLOC_INPUT, 0); + m_apu->add_route(ALL_OUTPUTS, *this, 1.0, 0); } void rp2a03g_device::device_add_mconfig(machine_config &config) @@ -92,7 +92,7 @@ void rp2a03g_device::device_add_mconfig(machine_config &config) NES_APU(config, m_apu, DERIVED_CLOCK(1,1)); m_apu->irq().set(FUNC(rp2a03g_device::apu_irq)); m_apu->mem_read().set(FUNC(rp2a03g_device::apu_read_mem)); - m_apu->add_route(ALL_OUTPUTS, *this, 1.0, AUTO_ALLOC_INPUT, 0); + m_apu->add_route(ALL_OUTPUTS, *this, 1.0, 0); } diff --git a/src/devices/machine/generalplus_gpl16250soc.cpp b/src/devices/machine/generalplus_gpl16250soc.cpp index dc4505ce9ffda..40b7915921543 100644 --- a/src/devices/machine/generalplus_gpl16250soc.cpp +++ b/src/devices/machine/generalplus_gpl16250soc.cpp @@ -1845,8 +1845,8 @@ void sunplus_gcm394_base_device::device_add_mconfig(machine_config &config) m_spg_audio->write_irq_callback().set(FUNC(sunplus_gcm394_base_device::audioirq_w)); m_spg_audio->space_read_callback().set(FUNC(sunplus_gcm394_base_device::read_space)); - m_spg_audio->add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_spg_audio->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_spg_audio->add_route(0, *this, 1.0, 0); + m_spg_audio->add_route(1, *this, 1.0, 1); GCM394_VIDEO(config, m_spg_video, DERIVED_CLOCK(1, 1), DEVICE_SELF, m_screen); m_spg_video->write_video_irq_callback().set(FUNC(sunplus_gcm394_base_device::videoirq_w)); diff --git a/src/devices/machine/k054321.cpp b/src/devices/machine/k054321.cpp index 6a362f655b2b5..d53fd8d346f5f 100644 --- a/src/devices/machine/k054321.cpp +++ b/src/devices/machine/k054321.cpp @@ -59,8 +59,7 @@ void k054321_device::sound_map(address_map &map) k054321_device::k054321_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, K054321, tag, owner, clock), - m_left(*this, finder_base::DUMMY_TAG), - m_right(*this, finder_base::DUMMY_TAG), + m_speaker(*this, finder_base::DUMMY_TAG), m_soundlatch(*this, "soundlatch%u", 0) { } @@ -68,17 +67,12 @@ k054321_device::k054321_device(const machine_config &mconfig, const char *tag, d void k054321_device::device_start() { // make sure that device_sound_interface is configured - if (!m_left->inputs() && !m_right->inputs()) + if (!m_speaker->inputs()) throw device_missing_dependencies(); // remember initial input gains - m_left_gains = std::make_unique(m_left->inputs()); - m_right_gains = std::make_unique(m_right->inputs()); - - for (int i = 0; i < m_left->inputs(); i++) - m_left_gains[i] = m_left->input_gain(i); - for (int i = 0; i < m_right->inputs(); i++) - m_right_gains[i] = m_right->input_gain(i); + m_left_gain = m_speaker->input_gain(0); + m_right_gain = m_speaker->input_gain(1); // register for savestates save_item(NAME(m_volume)); @@ -133,8 +127,6 @@ void k054321_device::propagate_volume() { double vol = pow(2, (m_volume - 40)/10.0); - for (int i = 0; i < m_left->inputs(); i++) - m_left->set_input_gain(i, m_active & 2 ? vol * m_left_gains[i] : 0.0); - for (int i = 0; i < m_right->inputs(); i++) - m_right->set_input_gain(i, m_active & 1 ? vol * m_right_gains[i] : 0.0); + m_speaker->set_input_gain(0, m_active & 2 ? vol * m_left_gain : 0.0); + m_speaker->set_input_gain(1, m_active & 1 ? vol * m_right_gain : 0.0); } diff --git a/src/devices/machine/k054321.h b/src/devices/machine/k054321.h index 6c66659372d36..dbd2f2305ef78 100644 --- a/src/devices/machine/k054321.h +++ b/src/devices/machine/k054321.h @@ -11,16 +11,15 @@ class k054321_device : public device_t { public: - template - k054321_device(const machine_config &mconfig, const char *tag, device_t *owner, T &&left, U &&right) : - k054321_device(mconfig, tag, owner, 0) + k054321_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0); + + template + k054321_device(const machine_config &mconfig, const char *tag, device_t *owner, T &&speaker) : + k054321_device(mconfig, tag, owner) { - m_left.set_tag(std::forward(left)); - m_right.set_tag(std::forward(right)); + m_speaker.set_tag(std::forward(speaker)); } - k054321_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - void main_map(address_map &map); void sound_map(address_map &map); @@ -30,12 +29,10 @@ class k054321_device : public device_t virtual void device_add_mconfig(machine_config &config) override; private: - required_device m_left; - required_device m_right; + required_device m_speaker; required_device_array m_soundlatch; - std::unique_ptr m_left_gains; - std::unique_ptr m_right_gains; + float m_left_gain, m_right_gain; u8 m_volume; u8 m_active; diff --git a/src/devices/machine/spg110.cpp b/src/devices/machine/spg110.cpp index c31ca87ac7aaa..3bf85b47bfe19 100644 --- a/src/devices/machine/spg110.cpp +++ b/src/devices/machine/spg110.cpp @@ -100,8 +100,8 @@ void spg110_device::device_add_mconfig(machine_config &config) m_spg_audio->write_irq_callback().set(FUNC(spg110_device::audioirq_w)); m_spg_audio->space_read_callback().set(FUNC(spg110_device::space_r)); - m_spg_audio->add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_spg_audio->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_spg_audio->add_route(0, *this, 1.0, 0); + m_spg_audio->add_route(1, *this, 1.0, 1); } void spg110_device::internal_map(address_map &map) diff --git a/src/devices/machine/spg2xx.cpp b/src/devices/machine/spg2xx.cpp index 09e0e0c6f39d9..2ecba9a56d8a9 100644 --- a/src/devices/machine/spg2xx.cpp +++ b/src/devices/machine/spg2xx.cpp @@ -193,8 +193,8 @@ void spg24x_device::device_add_mconfig(machine_config &config) m_spg_audio->channel_irq_callback().set(FUNC(spg24x_device::audiochirq_w)); m_spg_audio->space_read_callback().set(FUNC(spg24x_device::space_r)); - m_spg_audio->add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_spg_audio->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_spg_audio->add_route(0, *this, 1.0, 0); + m_spg_audio->add_route(1, *this, 1.0, 1); SPG24X_IO(config, m_spg_io, DERIVED_CLOCK(1, 1), DEVICE_SELF, m_screen); diff --git a/src/devices/machine/stvcd.cpp b/src/devices/machine/stvcd.cpp index 6b46750d4ac54..94ccd5115f85b 100644 --- a/src/devices/machine/stvcd.cpp +++ b/src/devices/machine/stvcd.cpp @@ -120,8 +120,8 @@ void stvcd_device::device_add_mconfig(machine_config &config) TIMER(config, m_sh1_timer).configure_generic(FUNC(stvcd_device::stv_sh1_sim)); CDDA(config, m_cdda); - m_cdda->add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_cdda->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_cdda->add_route(0, *this, 1.0, 0); + m_cdda->add_route(1, *this, 1.0, 1); m_cdda->set_cdrom_tag("cdrom"); } diff --git a/src/devices/sound/ssi263hle.cpp b/src/devices/sound/ssi263hle.cpp index 738b6c6108498..a2f3e0ce29fe6 100644 --- a/src/devices/sound/ssi263hle.cpp +++ b/src/devices/sound/ssi263hle.cpp @@ -116,7 +116,7 @@ void ssi263hle_device::device_add_mconfig(machine_config &config) { VOTRAX_SC01(config, m_votrax, DERIVED_CLOCK(1, 1)); m_votrax->ar_callback().set(FUNC(ssi263hle_device::votrax_request)); - m_votrax->add_route(ALL_OUTPUTS, *this, 1.0, AUTO_ALLOC_INPUT, 0); + m_votrax->add_route(ALL_OUTPUTS, *this, 1.0, 0); } TIMER_CALLBACK_MEMBER(ssi263hle_device::phoneme_tick) diff --git a/src/devices/sound/xt446.cpp b/src/devices/sound/xt446.cpp index 8136f43ed9088..bc41f6670c429 100644 --- a/src/devices/sound/xt446.cpp +++ b/src/devices/sound/xt446.cpp @@ -91,7 +91,7 @@ void xt446_device::device_add_mconfig(machine_config &config) SWP30(config, m_swp30); m_swp30->set_addrmap(AS_DATA, &xt446_device::swp30_map); - m_swp30->add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_swp30->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_swp30->add_route(0, *this, 1.0, 0); + m_swp30->add_route(1, *this, 1.0, 1); } diff --git a/src/devices/video/315_5124.cpp b/src/devices/video/315_5124.cpp index bc35a956babca..879742f59c8a9 100644 --- a/src/devices/video/315_5124.cpp +++ b/src/devices/video/315_5124.cpp @@ -2094,7 +2094,7 @@ void sega315_5124_device::device_add_mconfig(machine_config &config) { PALETTE(config, m_palette_lut, FUNC(sega315_5124_device::sega315_5124_palette), SEGA315_5124_PALETTE_SIZE); - SEGAPSG(config, m_snsnd, DERIVED_CLOCK(1, 3)).add_route(ALL_OUTPUTS, *this, 1.0, AUTO_ALLOC_INPUT, 0); + SEGAPSG(config, m_snsnd, DERIVED_CLOCK(1, 3)).add_route(ALL_OUTPUTS, *this, 1.0, 0); } void sega315_5246_device::device_add_mconfig(machine_config &config) @@ -2121,8 +2121,8 @@ void sega315_5377_device::device_add_mconfig(machine_config &config) m_palette_lut->set_init(FUNC(sega315_5377_device::sega315_5377_palette)); GAMEGEAR(config.replace(), m_snsnd, DERIVED_CLOCK(1, 3)); - m_snsnd->add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_snsnd->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_snsnd->add_route(0, *this, 1.0, 0); + m_snsnd->add_route(1, *this, 1.0, 1); } //------------------------------------------------- diff --git a/src/devices/video/315_5313.cpp b/src/devices/video/315_5313.cpp index 57b32a642a98b..b926fb43c26a0 100644 --- a/src/devices/video/315_5313.cpp +++ b/src/devices/video/315_5313.cpp @@ -245,7 +245,7 @@ void sega315_5313_device::device_add_mconfig(machine_config &config) { sega315_5313_mode4_device::device_add_mconfig(config); - SEGAPSG(config.replace(), m_snsnd, DERIVED_CLOCK(1, 15)).add_route(ALL_OUTPUTS, *this, 0.5, AUTO_ALLOC_INPUT, 0); + SEGAPSG(config.replace(), m_snsnd, DERIVED_CLOCK(1, 15)).add_route(ALL_OUTPUTS, *this, 0.5, 0); PALETTE(config, m_gfx_palette, palette_device::BLACK).set_entries(PALETTE_PER_FRAME); PALETTE(config, m_gfx_palette_shadow, palette_device::BLACK).set_entries(PALETTE_PER_FRAME); diff --git a/src/emu/disound.cpp b/src/emu/disound.cpp index 83d2c2bf084cc..55cf51ecdec56 100644 --- a/src/emu/disound.cpp +++ b/src/emu/disound.cpp @@ -43,25 +43,26 @@ device_sound_interface::~device_sound_interface() // add_route - send sound output to a consumer //------------------------------------------------- -device_sound_interface &device_sound_interface::add_route(u32 output, const char *target, double gain, u32 input, u32 mixoutput) +device_sound_interface &device_sound_interface::add_route(u32 output, const char *target, double gain, u32 channel) { - return add_route(output, device().mconfig().current_device(), target, gain, input, mixoutput); + return add_route(output, device().mconfig().current_device(), target, gain, channel); } -device_sound_interface &device_sound_interface::add_route(u32 output, device_sound_interface &target, double gain, u32 input, u32 mixoutput) +device_sound_interface &device_sound_interface::add_route(u32 output, device_sound_interface &target, double gain, u32 channel) { - return add_route(output, target.device(), DEVICE_SELF, gain, input, mixoutput); + return add_route(output, target.device(), DEVICE_SELF, gain, channel); } -device_sound_interface &device_sound_interface::add_route(u32 output, speaker_device &target, double gain, u32 input, u32 mixoutput) +device_sound_interface &device_sound_interface::add_route(u32 output, speaker_device &target, double gain, u32 channel) { - return add_route(output, target, DEVICE_SELF, gain, input, mixoutput); + return add_route(output, target, DEVICE_SELF, gain, channel); } -device_sound_interface &device_sound_interface::add_route(u32 output, device_t &base, const char *target, double gain, u32 input, u32 mixoutput) +device_sound_interface &device_sound_interface::add_route(u32 output, device_t &base, const char *target, double gain, u32 channel) { assert(!device().started()); - m_route_list.emplace_back(sound_route{ output, input, mixoutput, float(gain), base, target }); + // Put the channel always in m_input, will be resolved in interface_pre_start + m_route_list.emplace_back(sound_route{ output, channel, 0, float(gain), base, target }); return *this; } @@ -299,7 +300,8 @@ void device_sound_interface::interface_pre_start() // see if we are the target of this route; if we are, make sure the source device is started if (!sound.device().started()) throw device_missing_dependencies(); - if (route.m_input != AUTO_ALLOC_INPUT) + device_mixer_interface *mixer; + if (!target_device->interface(mixer)) m_specified_inputs_mask |= 1 << route.m_input; } } @@ -314,8 +316,11 @@ void device_sound_interface::interface_pre_start() { // see if we are the target of this route device_t *const target_device = route.m_base.get().subdevice(route.m_target); - if (target_device == &device() && route.m_input == AUTO_ALLOC_INPUT) + device_mixer_interface *mixer; + if (target_device == &device() && target_device->interface(mixer)) { + // Mixers up to this point have the output channel in m_input + route.m_mixoutput = route.m_input; route.m_input = m_auto_allocated_inputs; m_auto_allocated_inputs += (route.m_output == ALL_OUTPUTS) ? sound.outputs() : 1; } diff --git a/src/emu/disound.h b/src/emu/disound.h index e48ba290bc6ba..56fae086f9a5f 100644 --- a/src/emu/disound.h +++ b/src/emu/disound.h @@ -26,7 +26,6 @@ //************************************************************************** constexpr int ALL_OUTPUTS = 65535; // special value indicating all outputs for the current chip -constexpr int AUTO_ALLOC_INPUT = 65535; @@ -48,8 +47,8 @@ class device_sound_interface : public device_interface { public: u32 m_output; // output index, or ALL_OUTPUTS - u32 m_input; // target input index - u32 m_mixoutput; // target mixer output + u32 m_input; // target input index (for sound devices, after allocation for mixers) + u32 m_mixoutput; // target mixer index float m_gain; // gain std::reference_wrapper m_base; // target search base std::string m_target; // target tag @@ -66,14 +65,14 @@ class device_sound_interface : public device_interface // configuration helpers template - device_sound_interface &add_route(u32 output, const device_finder &target, double gain, u32 input = AUTO_ALLOC_INPUT, u32 mixoutput = 0) + device_sound_interface &add_route(u32 output, const device_finder &target, double gain, u32 channel = 0) { const std::pair ft(target.finder_target()); - return add_route(output, ft.first, ft.second, gain, input, mixoutput); + return add_route(output, ft.first, ft.second, gain, channel); } - device_sound_interface &add_route(u32 output, const char *target, double gain, u32 input = AUTO_ALLOC_INPUT, u32 mixoutput = 0); - device_sound_interface &add_route(u32 output, device_sound_interface &target, double gain, u32 input = AUTO_ALLOC_INPUT, u32 mixoutput = 0); - device_sound_interface &add_route(u32 output, speaker_device &target, double gain, u32 input = AUTO_ALLOC_INPUT, u32 mixoutput = 0); + device_sound_interface &add_route(u32 output, const char *target, double gain, u32 channel = 0); + device_sound_interface &add_route(u32 output, device_sound_interface &target, double gain, u32 channel = 0); + device_sound_interface &add_route(u32 output, speaker_device &target, double gain, u32 channel = 0); device_sound_interface &reset_routes() { m_route_list.clear(); return *this; } // sound stream update overrides @@ -97,7 +96,7 @@ class device_sound_interface : public device_interface protected: // configuration access std::vector &routes() { return m_route_list; } - device_sound_interface &add_route(u32 output, device_t &base, const char *tag, double gain, u32 input, u32 mixoutput); + device_sound_interface &add_route(u32 output, device_t &base, const char *tag, double gain, u32 channel); // optional operation overrides virtual void interface_validity_check(validity_checker &valid) const override; diff --git a/src/emu/inpttype.h b/src/emu/inpttype.h index fa711b7eabd46..4e459c73bf20a 100644 --- a/src/emu/inpttype.h +++ b/src/emu/inpttype.h @@ -299,6 +299,8 @@ enum ioport_type : osd::u32 IPT_UI_FAVORITES, IPT_UI_EXPORT, IPT_UI_AUDIT, + IPT_UI_MIXER_ADD_FULL, + IPT_UI_MIXER_ADD_CHANNEL, // additional OSD-specified UI port types (up to 16) IPT_OSD_1, diff --git a/src/emu/inpttype.ipp b/src/emu/inpttype.ipp index 8c217a1d39c8f..a3ea7d7ef5a79 100644 --- a/src/emu/inpttype.ipp +++ b/src/emu/inpttype.ipp @@ -925,6 +925,8 @@ namespace { INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_FAVORITES, N_p("input-name", "UI Add/Remove Favorite"), input_seq(KEYCODE_LALT, KEYCODE_F) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_EXPORT, N_p("input-name", "UI Export List"), input_seq(KEYCODE_LALT, KEYCODE_E) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_AUDIT, N_p("input-name", "UI Audit Media"), input_seq(KEYCODE_F1, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_MIXER_ADD_FULL, N_p("input-name", "UI Audio add full mapping"), input_seq(KEYCODE_F) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_MIXER_ADD_CHANNEL, N_p("input-name", "UI Audio add channel mapping"), input_seq(KEYCODE_C) ) \ CORE_INPUT_TYPES_END() #define CORE_INPUT_TYPES_OSD \ diff --git a/src/emu/sound.cpp b/src/emu/sound.cpp index c96268551975b..10380057e5db9 100644 --- a/src/emu/sound.cpp +++ b/src/emu/sound.cpp @@ -20,6 +20,7 @@ #include "osdepend.h" +#include //************************************************************************** // DEBUGGING @@ -330,7 +331,7 @@ void stream_buffer::backfill_upsample(sample_t const *src, int samples, attotime if (srcindex >= samples) break; - // write this sample at the pevious position + // write this sample at the previous position end = prev_index(end); put(end, src[srcindex]); @@ -1070,17 +1071,16 @@ sound_manager::sound_manager(running_machine &machine) : m_update_timer(nullptr), m_update_number(0), m_last_update(attotime::zero), - m_finalmix_leftover(0), + m_comp_leftover(0), m_samples_this_update(0), - m_finalmix(machine.sample_rate()), - m_leftmix(machine.sample_rate()), - m_rightmix(machine.sample_rate()), + m_comp_samples_this_update(0), + m_record_buffer(machine.sample_rate()), m_compressor_scale(1.0), m_compressor_counter(0), m_compressor_enabled(machine.options().compressor()), m_muted(0), m_nosound_mode(machine.osd().no_sound()), - m_attenuation(0), + m_volume(0), m_unique_id(0), m_wavfile(), m_first_reset(true) @@ -1104,12 +1104,15 @@ sound_manager::sound_manager(running_machine &machine) : // register global states machine.save().save_item(NAME(m_last_update)); - // set the starting attenuation - set_attenuation(machine.options().volume()); + // set the starting volume + set_volume(machine.options().volume()); // start the periodic update flushing timer m_update_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_manager::update), this)); m_update_timer->adjust(STREAMS_UPDATE_ATTOTIME, 0, STREAMS_UPDATE_ATTOTIME); + + // mark the generation as "just starting" + m_osd_info.m_generation = 0xffffffff; } @@ -1172,14 +1175,12 @@ void sound_manager::stop_recording() //------------------------------------------------- -// set_attenuation - set the global volume +// set_volume - set the global volume //------------------------------------------------- -void sound_manager::set_attenuation(float attenuation) +void sound_manager::set_volume(float volume) { - // currently OSD only supports integral attenuation - m_attenuation = int(attenuation); - machine().osd().set_mastervolume(m_muted ? -32 : m_attenuation); + m_volume = volume; } @@ -1216,10 +1217,12 @@ bool sound_manager::indexed_mixer_input(int index, mixer_input &info) const // the current frame //------------------------------------------------- -void sound_manager::samples(s16 *buffer) +std::vector sound_manager::samples() { - for (int sample = 0; sample < m_samples_this_update * 2; sample++) - *buffer++ = m_finalmix[sample]; + std::vector samp(m_comp_samples_this_update*2); + for (int sample = 0; sample < m_comp_samples_this_update * 2; sample++) + samp[sample] = m_record_buffer[sample]; + return samp; } @@ -1236,7 +1239,7 @@ void sound_manager::mute(bool mute, u8 reason) m_muted &= ~reason; if(old_muted != (m_muted != 0)) - set_attenuation(m_attenuation); + set_volume(m_volume); } @@ -1273,10 +1276,7 @@ void sound_manager::apply_sample_rate_changes() // due to device removal, some speakers may end up with no outputs; just skip those if (stream != nullptr) - { - sound_assert(speaker.outputs() == 1); stream->apply_sample_rate_changes(m_update_number, machine().sample_rate()); - } } } @@ -1285,6 +1285,19 @@ void sound_manager::apply_sample_rate_changes() // reset - reset all sound chips //------------------------------------------------- +sound_manager::speaker_info::speaker_info(speaker_device &speaker, u32 size) : m_speaker(speaker) +{ + m_channels = speaker.outputs(); + m_buffers.resize(m_channels); + m_comp_buffers.resize(m_channels); + for(u32 channel = 0; channel != m_channels; channel++) { + m_buffers[channel].resize(size); + m_comp_buffers[channel].resize(size); + double x = speaker.get_position(channel)[0]; + m_record_mix.push_back(x < 0.0 ? 1 : x == 0 ? 2 : 3); + } +} + void sound_manager::reset() { // reset all the sound chips @@ -1310,8 +1323,7 @@ void sound_manager::reset() sound_stream *const output = speaker.output_to_stream_output(0, dummy); if (output) recursive_remove_stream_from_orphan_list(output); - - m_speakers.emplace_back(speaker); + m_speakers.emplace_back(speaker_info(speaker, machine().sample_rate())); } #if (SOUND_DEBUG) @@ -1367,13 +1379,13 @@ void sound_manager::config_load(config_type cfg_type, config_level cfg_level, ut if ((cfg_type != config_type::SYSTEM) || !parentnode) return; - // master volume attenuation - if (util::xml::data_node const *node = parentnode->get_child("attenuation")) + // master volume + if (util::xml::data_node const *node = parentnode->get_child("volume")) { // treat source INI files or more specific as higher priority than CFG // FIXME: leaky abstraction - this depends on a front-end implementation detail if ((OPTION_PRIORITY_NORMAL + 5) > machine().options().get_entry(OPTION_VOLUME)->priority()) - set_attenuation(std::clamp(int(node->get_attribute_int("value", 0)), -32, 0)); + set_volume(std::clamp(int(node->get_attribute_int("value", 0)), -32, 0)); } // iterate over channel nodes @@ -1390,22 +1402,19 @@ void sound_manager::config_load(config_type cfg_type, config_level cfg_level, ut } } - // iterate over speaker panning nodes - for (util::xml::data_node const *node = parentnode->get_child("panning"); node != nullptr; node = node->get_next_sibling("panning")) + // iterate over speakers for mapping + m_configs.clear(); + for (util::xml::data_node const *node = parentnode->get_child("speaker"); node != nullptr; node = node->get_next_sibling("speaker")) { - char const *const tag = node->get_attribute_string("tag", nullptr); - if (tag != nullptr) - { - for (speaker_device &speaker : speaker_device_enumerator(machine().root_device())) - { - if (!strcmp(tag, speaker.tag())) - { - float value = node->get_attribute_float("value", speaker.defpan()); - speaker.set_pan(value); - break; - } - } - } + m_configs.emplace_back(config_mapping { node->get_attribute_string("tag", "") }); + auto &config = m_configs.back(); + for (util::xml::data_node const *nmap = node->get_child("node_mapping"); nmap != nullptr; nmap = nmap->get_next_sibling("node_mapping")) + config.m_node_mappings.emplace_back(std::pair(nmap->get_attribute_string("node", ""), nmap->get_attribute_float("db", 0))); + for (util::xml::data_node const *cmap = node->get_child("channel_mapping"); cmap != nullptr; cmap = cmap->get_next_sibling("channel_mapping")) + config.m_channel_mappings.emplace_back(std::tuple(cmap->get_attribute_int("guest_channel", 0), + cmap->get_attribute_string("node", ""), + cmap->get_attribute_int("node_channel", 0), + cmap->get_attribute_float("db", 0))); } } @@ -1421,11 +1430,11 @@ void sound_manager::config_save(config_type cfg_type, util::xml::data_node *pare if (cfg_type != config_type::SYSTEM) return; - // master volume attenuation - if (m_attenuation != machine().options().volume()) + // master volume + if (m_volume != machine().options().volume()) { - if (util::xml::data_node *const node = parentnode->add_child("attenuation", nullptr)) - node->set_attribute_int("value", m_attenuation); + if (util::xml::data_node *const node = parentnode->add_child("volume", nullptr)) + node->set_attribute_float("value", m_volume); } // iterate over mixer channels for per-channel volume @@ -1447,20 +1456,28 @@ void sound_manager::config_save(config_type cfg_type, util::xml::data_node *pare } } - // iterate over speakers for panning + // iterate over speakers for mapping for (speaker_device &speaker : speaker_device_enumerator(machine().root_device())) - { - float const value = speaker.pan(); - if (value != speaker.defpan()) - { - util::xml::data_node *const node = parentnode->add_child("panning", nullptr); - if (node) + for (const auto &config : m_configs) + if (config.m_name == speaker.tag()) { - node->set_attribute("tag", speaker.tag()); - node->set_attribute_float("value", value); + util::xml::data_node *const sp_node = parentnode->add_child("speaker", nullptr); + sp_node->set_attribute("tag", speaker.tag()); + for (const auto &nmap : config.m_node_mappings) + { + util::xml::data_node *const node = sp_node->add_child("node_mapping", nullptr); + node->set_attribute("node", nmap.first.c_str()); + node->set_attribute_float("db", nmap.second); + } + for (const auto &cmap : config.m_channel_mappings) + { + util::xml::data_node *const node = sp_node->add_child("channel_mapping", nullptr); + node->set_attribute_int("guest_channel", std::get<0>(cmap)); + node->set_attribute("node", std::get<1>(cmap).c_str()); + node->set_attribute_int("node_channel", std::get<2>(cmap)); + node->set_attribute_float("db", std::get<3>(cmap)); + } } - } - } } @@ -1502,57 +1519,823 @@ stream_buffer::sample_t sound_manager::adjust_toward_compressor_scale(stream_buf return curscale; } - //------------------------------------------------- -// update - mix everything down to its final form -// and send it to the OSD layer +// mapping_update - check if the mixing mapping +// must be updated and if yes do it //------------------------------------------------- -void sound_manager::update(s32 param) +sound_manager::config_mapping &sound_manager::config_get_speaker(speaker_device *speaker) { - LOG("sound_update\n"); + for(auto &config : m_configs) + if(config.m_name == speaker->tag()) + return config; + m_configs.emplace_back(config_mapping { speaker->tag() }); + return m_configs.back(); +} - auto profile = g_profiler.start(PROFILER_SOUND); +void sound_manager::config_add_speaker_target_node(speaker_device *speaker, std::string name, float db) +{ + internal_config_add_speaker_target_node(speaker, name, db); + m_osd_info.m_generation --; +} - // determine the duration of this update - attotime update_period = machine().time() - m_last_update; - sound_assert(update_period.seconds() == 0); +void sound_manager::internal_config_add_speaker_target_node(speaker_device *speaker, std::string name, float db) +{ + auto &config = config_get_speaker(speaker); + for(auto &nmap : config.m_node_mappings) + if(nmap.first == name) + return; + config.m_node_mappings.emplace_back(std::pair(name, db)); +} - // use that to compute the number of samples we need from the speakers - attoseconds_t sample_rate_attos = HZ_TO_ATTOSECONDS(machine().sample_rate()); - m_samples_this_update = update_period.attoseconds() / sample_rate_attos; +void sound_manager::config_add_speaker_target_default(speaker_device *speaker, float db) +{ + internal_config_add_speaker_target_default(speaker, db); + m_osd_info.m_generation --; +} - // recompute the end time to an even sample boundary - attotime endtime = m_last_update + attotime(0, m_samples_this_update * sample_rate_attos); +void sound_manager::internal_config_add_speaker_target_default(speaker_device *speaker, float db) +{ + auto &config = config_get_speaker(speaker); + for(auto &nmap : config.m_node_mappings) + if(nmap.first == "") + return; + config.m_node_mappings.emplace_back(std::pair("", db)); +} + +void sound_manager::config_remove_speaker_target_node(speaker_device *speaker, std::string name) +{ + internal_config_remove_speaker_target_node(speaker, name); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_remove_speaker_target_node(speaker_device *speaker, std::string name) +{ + auto &config = config_get_speaker(speaker); + for(auto i = config.m_node_mappings.begin(); i != config.m_node_mappings.end(); i++) + if(i->first == name) { + config.m_node_mappings.erase(i); + return; + } +} + +void sound_manager::config_remove_speaker_target_default(speaker_device *speaker) +{ + internal_config_remove_speaker_target_default(speaker); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_remove_speaker_target_default(speaker_device *speaker) +{ + auto &config = config_get_speaker(speaker); + for(auto i = config.m_node_mappings.begin(); i != config.m_node_mappings.end(); i++) + if(i->first == "") { + config.m_node_mappings.erase(i); + return; + } +} + +void sound_manager::config_set_volume_speaker_target_node(speaker_device *speaker, std::string name, float db) +{ + internal_config_set_volume_speaker_target_node(speaker, name, db); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_set_volume_speaker_target_node(speaker_device *speaker, std::string name, float db) +{ + auto &config = config_get_speaker(speaker); + for(auto &nmap : config.m_node_mappings) + if(nmap.first == name) { + nmap.second = db; + return; + } +} + +void sound_manager::config_set_volume_speaker_target_default(speaker_device *speaker, float db) +{ + internal_config_set_volume_speaker_target_default(speaker, db); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_set_volume_speaker_target_default(speaker_device *speaker, float db) +{ + auto &config = config_get_speaker(speaker); + for(auto &nmap : config.m_node_mappings) + if(nmap.first == "") { + nmap.second = db; + return; + } +} + + +void sound_manager::config_add_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel, float db) +{ + internal_config_add_speaker_channel_target_node(speaker, guest_channel, name, node_channel, db); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_add_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel, float db) +{ + auto &config = config_get_speaker(speaker); + for(auto &cmap : config.m_channel_mappings) + if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == name && std::get<2>(cmap) == node_channel) + return; + config.m_channel_mappings.emplace_back(std::tuple(guest_channel, name, node_channel, db)); +} + +void sound_manager::config_add_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel, float db) +{ + internal_config_add_speaker_channel_target_default(speaker, guest_channel, node_channel, db); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_add_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel, float db) +{ + auto &config = config_get_speaker(speaker); + for(auto &cmap : config.m_channel_mappings) + if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == "" && std::get<2>(cmap) == node_channel) + return; + config.m_channel_mappings.emplace_back(std::tuple(guest_channel, "", node_channel, db)); +} + +void sound_manager::config_remove_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel) +{ + internal_config_remove_speaker_channel_target_node(speaker, guest_channel, name, node_channel); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_remove_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel) +{ + auto &config = config_get_speaker(speaker); + for(auto i = config.m_channel_mappings.begin(); i != config.m_channel_mappings.end(); i++) + if(std::get<0>(*i) == guest_channel && std::get<1>(*i) == name && std::get<2>(*i) == node_channel) { + config.m_channel_mappings.erase(i); + return; + } +} + +void sound_manager::config_remove_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel) +{ + internal_config_remove_speaker_channel_target_default(speaker, guest_channel, node_channel); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_remove_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel) +{ + auto &config = config_get_speaker(speaker); + for(auto i = config.m_channel_mappings.begin(); i != config.m_channel_mappings.end(); i++) + if(std::get<0>(*i) == guest_channel && std::get<1>(*i) == "" && std::get<2>(*i) == node_channel) { + config.m_channel_mappings.erase(i); + return; + } +} + +void sound_manager::config_set_volume_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel, float db) +{ + internal_config_set_volume_speaker_channel_target_node(speaker, guest_channel, name, node_channel, db); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_set_volume_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel, float db) +{ + auto &config = config_get_speaker(speaker); + for(auto &cmap : config.m_channel_mappings) + if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == name && std::get<2>(cmap) == node_channel) { + std::get<3>(cmap) = db; + return; + } +} + +void sound_manager::config_set_volume_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel, float db) +{ + internal_config_set_volume_speaker_channel_target_default(speaker, guest_channel, node_channel, db); + m_osd_info.m_generation --; +} + +void sound_manager::internal_config_set_volume_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel, float db) +{ + auto &config = config_get_speaker(speaker); + for(auto &cmap : config.m_channel_mappings) + if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == "" && std::get<2>(cmap) == node_channel) { + std::get<3>(cmap) = db; + return; + } +} + +void sound_manager::startup_cleanups() +{ + auto osd_info = machine().osd().sound_get_information(); + + // for every speaker that does not have a configuration entry, add a + // mapping to default + for (speaker_device &speaker : speaker_device_enumerator(machine().root_device())) + { + for (const auto &config : m_configs) + if (config.m_name == speaker.tag()) + goto has_config; + m_configs.emplace_back(config_mapping { speaker.tag() }); + m_configs.back().m_node_mappings.emplace_back(std::pair("", 0.0)); + has_config: + ; + } + + // If there's no default sink replace all the default sink config + // entries into the first sink available + if(!osd_info.m_default_sink) { + std::string first_sink_name; + for(const auto &node : osd_info.m_nodes) + if(!node.m_sinks.empty()) { + first_sink_name = node.m_name; + break; + } + + if(first_sink_name != "") + for(auto &config : m_configs) { + for(auto &nmap : config.m_node_mappings) + if(nmap.first == "") + nmap.first = first_sink_name; + for(auto &cmap : config.m_channel_mappings) + if(std::get<1>(cmap) == "") + std::get<1>(cmap) = first_sink_name; + } + } +} + +void sound_manager::osd_information_update() +{ + // Get a snapshot of the current information + m_osd_info = machine().osd().sound_get_information(); + + // Analyze the streams to see if anything changed, but only in the + // split stream case. + if(machine().osd().sound_split_streams_per_source()) + for(auto &stream : m_osd_streams) { + u32 sidx; + for(sidx = 0; sidx != m_osd_info.m_streams.size() && m_osd_info.m_streams[sidx].m_id != stream.m_id; sidx++); + // If the stream has been lost, continue. It will be cleared in update_osd_streams. + if(sidx == m_osd_info.m_streams.size()) + continue; + + // Check if the target and/or the volumes changed + bool node_changed = stream.m_node != m_osd_info.m_streams[sidx].m_node; + bool volume_changed = !std::equal(stream.m_volumes.begin(), stream.m_volumes.end(), m_osd_info.m_streams[sidx].m_volumes.begin(), m_osd_info.m_streams[sidx].m_volumes.end()); + + if(node_changed || volume_changed) { + // Check if a node change is just tracking the system default + bool system_default_tracking = node_changed && stream.m_is_system_default && m_osd_info.m_streams[sidx].m_node == m_osd_info.m_default_sink; + + // Find the config entry for the speaker + config_mapping *config = nullptr; + for(auto &conf : m_configs) + if(conf.m_name == stream.m_speaker->tag()) { + config = &conf; + break; + } + if(!config) + continue; + + // Retrieve the old node name, and, if it's different, the new node name + std::string old_node_name = stream.m_node_name; + std::string new_node_name; + if(node_changed) { + for(const auto &node : m_osd_info.m_nodes) + if(node.m_id == m_osd_info.m_streams[sidx].m_node) { + new_node_name = node.m_name; + break; + } + // That's really, really not supposed to happen + if(new_node_name.empty()) + continue; + } else + new_node_name = old_node_name; + + // Separate the cases on full mapping vs. channel mapping + if(stream.m_guest_channels.empty()) { + // Full mapping + // Find the index of the config mapping entry that generated the stream, if there's still one. + // Note that a default system stream has the empty string as a name + u32 index; + for(index = 0; index != config->m_node_mappings.size(); index++) + if(config->m_node_mappings[index].first == old_node_name) + break; + if(index == config->m_node_mappings.size()) + continue; + + // If the target node changed, write it down + if(node_changed) { + if(!system_default_tracking) { + config->m_node_mappings[index].first = new_node_name; + stream.m_node_name = new_node_name; + stream.m_is_system_default = false; + } + stream.m_node = m_osd_info.m_streams[sidx].m_node; + } + + // If the volume changed, there are two + // possibilities: either the channels split, or + // they didn't. + if(volume_changed) { + // Check is all the channel volumes are the same + float new_volume = m_osd_info.m_streams[sidx].m_volumes[0]; + bool same = true; + for(u32 i = 1; i != m_osd_info.m_streams[sidx].m_volumes.size(); i++) + if(m_osd_info.m_streams[sidx].m_volumes[i] != new_volume) { + same = false; + break; + } + if(same) { + // All the same volume, just note down the new volume + stream.m_volumes = m_osd_info.m_streams[sidx].m_volumes; + config->m_node_mappings[index].second = new_volume; + + } else { + const osd::audio_info::node_info *node = nullptr; + for(const auto &n : m_osd_info.m_nodes) + if(n.m_id == stream.m_node) { + node = &n; + break; + } + for(u32 channel = 0; channel != stream.m_speaker->outputs(); channel++) { + std::vector targets = find_channel_mapping(stream.m_speaker->get_position(channel), node); + for(u32 tchannel : targets) + if(stream.m_node_name == "") + internal_config_add_speaker_channel_target_default(stream.m_speaker, channel, tchannel, m_osd_info.m_streams[sidx].m_volumes[tchannel]); + else + internal_config_add_speaker_channel_target_node(stream.m_speaker, channel, stream.m_node_name, tchannel, m_osd_info.m_streams[sidx].m_volumes[tchannel]); + } + config->m_node_mappings.erase(config->m_node_mappings.begin() + index); + } + } + } else { + // Channel mapping + for(u32 channel = 0; channel != stream.m_channels; channel++) { + if(stream.m_guest_channels[channel] == 0xffffffff) + continue; + + // Find the index of the config mapping entry that generated the stream channel, if there's still one. + // Note that a default system stream has the empty string as a name + u32 index; + for(index = 0; index != config->m_channel_mappings.size(); index++) + if(std::get<0>(config->m_channel_mappings[index]) == stream.m_guest_channels[index] && + std::get<1>(config->m_channel_mappings[index]) == old_node_name && + std::get<2>(config->m_channel_mappings[index]) == channel) + break; + if(index == config->m_channel_mappings.size()) + continue; + + // If the target node changed, write it down + if(node_changed) { + if(!system_default_tracking) { + std::get<1>(config->m_channel_mappings[index]) = new_node_name; + stream.m_node_name = new_node_name; + stream.m_is_system_default = false; + } + stream.m_node = m_osd_info.m_streams[sidx].m_node; + } + + // If the volume changed, write in down too + if(volume_changed) { + std::get<3>(config->m_channel_mappings[index]) = m_osd_info.m_streams[sidx].m_volumes[channel]; + stream.m_volumes[channel] = m_osd_info.m_streams[sidx].m_volumes[channel]; + } + } + } + } + } +} + +void sound_manager::generate_mapping() +{ + auto find_node = [this](std::string name) -> u32 { + for(const auto &node : m_osd_info.m_nodes) + if(node.m_name == name) + return node.m_id; + return 0; + }; + + m_mappings.clear(); + for(speaker_info &speaker : m_speakers) { + auto &config = config_get_speaker(&speaker.m_speaker); + m_mappings.emplace_back(mapping { &speaker.m_speaker }); + auto &omap = m_mappings.back(); + + std::vector node_to_remove; + for(auto &nmap : config.m_node_mappings) { + if(nmap.first == "") + omap.m_node_mappings.emplace_back(mapping::node_mapping { m_osd_info.m_default_sink, nmap.second, true }); + else { + u32 node_id = find_node(nmap.first); + if(node_id != 0) + omap.m_node_mappings.emplace_back(mapping::node_mapping { node_id, nmap.second, false }); + else + node_to_remove.push_back(nmap.first); + } + } + + std::vector> channel_map_to_remove; + for(auto &cmap : config.m_channel_mappings) { + if(std::get<1>(cmap) == "") { + if(m_osd_info.m_default_sink) + omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), m_osd_info.m_default_sink, std::get<2>(cmap), std::get<3>(cmap), true }); + } else { + u32 node_id = find_node(std::get<1>(cmap)); + if(node_id != 0) + omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), node_id, std::get<2>(cmap), std::get<3>(cmap), false }); + else + channel_map_to_remove.push_back(std::tuple(std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap))); + } + } + + for(auto &cmap : channel_map_to_remove) + internal_config_remove_speaker_channel_target_node(&speaker.m_speaker, std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap)); + } +} + +// Find where to map a speaker channel into a node's channels depending on their positions + +std::vector sound_manager::find_channel_mapping(const std::array &position, const osd::audio_info::node_info *node) +{ + std::vector result; + if(position[0] == 0 && position[1] == 0 && position[2] == 0) + return result; + double best_dist = -1; + for(u32 port = 0; port != node->m_sinks.size(); port++) + if(node->m_sinks[port].m_position[0] || node->m_sinks[port].m_position[1] || node->m_sinks[port].m_position[2]) { + double dx = position[0] - node->m_sinks[port].m_position[0]; + double dy = position[1] - node->m_sinks[port].m_position[1]; + double dz = position[2] - node->m_sinks[port].m_position[2]; + double dist = dx*dx + dy*dy + dz*dz; + if(best_dist == -1 || dist < best_dist) { + best_dist = dist; + result.clear(); + result.push_back(port); + } else if(best_dist == dist) + result.push_back(port); + } + return result; +} + + +void sound_manager::update_osd_streams() +{ + auto current_streams = m_osd_streams; + m_osd_streams.clear(); + + // Find the index of a speaker_device in the speaker_info vector + + auto find_speaker_index = [this](speaker_device *speaker) -> u32 { + for(u32 si = 0; si != m_speakers.size(); si++) + if(&m_speakers[si].m_speaker == speaker) + return si; + return 0; // Can't happen + }; + + + // Find a pointer to a node_info from the node id + auto find_node_info = [this](u32 node) -> const osd::audio_info::node_info * { + for(const auto &ni : m_osd_info.m_nodes) { + if(ni.m_id == node) + return ∋ + } + // Can't happen + return nullptr; + }; + + // Two possible mapping methods depending on the osd capabilities + + m_mixing_steps.clear(); + + auto &osd = machine().osd(); + if(osd.sound_split_streams_per_source()) { + auto get_stream_for_node_and_source = [this, ¤t_streams] (const osd::audio_info::node_info *node, speaker_device *speaker, bool is_system_default) -> u32 { + // Create the default to-clear mask + u32 channels = node->m_sinks.size(); + u32 cmask = util::make_bitmask(channels); + + // Check if the osd stream already exists to pick it up in case. + // Clear the id in the current_streams structure to show it has been picked up, reset the clear mask. + // Clear the source channels and the volumes + // m_speaker will already be correct + + for(auto &os : current_streams) + if(os.m_id && os.m_node == node->m_id && os.m_speaker == speaker) { + u32 sid = m_osd_streams.size(); + m_osd_streams.emplace_back(std::move(os)); + os.m_id = 0; + auto &nos = m_osd_streams[sid]; + nos.m_guest_channels.clear(); + nos.m_volumes.clear(); + nos.m_clear_mask = cmask; + nos.m_is_system_default = is_system_default; + return sid; + } + + // If none exists, create one + u32 sid = m_osd_streams.size(); + m_osd_streams.emplace_back(osd_stream{ 0, node->m_id, is_system_default ? "" : node->m_name, channels, cmask, is_system_default, speaker }); + osd_stream &nos = m_osd_streams.back(); + u32 rate = machine().sample_rate(); + nos.m_buffer.resize(rate); + nos.m_id = machine().osd().sound_stream_sink_open(node->m_id, speaker->tag(), rate); + return sid; + }; + + auto get_stream_for_node_source_and_channel = [this, &get_stream_for_node_and_source] (const osd::audio_info::node_info *node, u32 node_channel, speaker_device *speaker, bool is_system_default) -> u32 { + // First check if there's an active stream with the correct channel not used yet + for(u32 sid = 0; sid != m_osd_streams.size(); sid++) { + auto &os = m_osd_streams[sid]; + if(os.m_node == node->m_id && os.m_speaker == speaker && os.m_clear_mask & (1 << node_channel) && !os.m_guest_channels.empty()) + return sid; + } + + // Otherwise used the default method + u32 sid = get_stream_for_node_and_source(node, speaker, is_system_default); + m_osd_streams[sid].m_guest_channels.resize(m_osd_streams[sid].m_channels, 0xffffffff); + return sid; + }; + + // Create/retrieve streams to apply the decided mapping + for(const auto &omap : m_mappings) { + u32 speaker_index = find_speaker_index(omap.m_speaker); + for(const auto &nm : omap.m_node_mappings) { + const auto *node = find_node_info(nm.m_node); + u32 stream_index = get_stream_for_node_and_source(node, omap.m_speaker, nm.m_is_system_default); + auto &stream = m_osd_streams[stream_index]; + u32 cmask = stream.m_clear_mask; + float linear_volume = 1.0; + + if(osd.sound_external_per_channel_volume()) { + stream.m_volumes.clear(); + stream.m_volumes.resize(m_speakers[speaker_index].m_channels, nm.m_db); + + } else + linear_volume = osd::db_to_linear(nm.m_db); + + for(u32 channel = 0; channel != m_speakers[speaker_index].m_channels; channel++) { + std::vector targets = find_channel_mapping(omap.m_speaker->get_position(channel), node); + for(u32 tchannel : targets) { + // If the channel is in the to clear mask, use + // load, otherwise use add. + // Apply the volume too if needed + m_mixing_steps.emplace_back(mixing_step { + osd.sound_external_per_channel_volume() ? + (cmask & (1 << tchannel)) ? mixing_step::COPY : mixing_step::ADD : + (cmask & (1 << tchannel)) ? mixing_step::COPYVOL : mixing_step::ADDVOL, + speaker_index, + channel, + stream_index, + tchannel, + linear_volume + }); + cmask &= ~(1 << tchannel); + } + } + stream.m_clear_mask = cmask; + } + + for(const auto &cm : omap.m_channel_mappings) { + const auto *node = find_node_info(cm.m_node); + u32 stream_index = get_stream_for_node_source_and_channel(node, cm.m_node_channel, omap.m_speaker, cm.m_is_system_default); + auto &stream = m_osd_streams[stream_index]; + float linear_volume = 1.0; + + if(osd.sound_external_per_channel_volume()) { + if(stream.m_volumes.empty()) + stream.m_volumes.resize(m_speakers[speaker_index].m_channels, -96); + stream.m_volumes[cm.m_node_channel] = cm.m_db; + + } else + linear_volume = osd::db_to_linear(cm.m_db); + + m_mixing_steps.emplace_back(mixing_step { + osd.sound_external_per_channel_volume() ? mixing_step::COPY : mixing_step::COPYVOL, + speaker_index, + cm.m_guest_channel, + stream_index, + cm.m_node_channel, + linear_volume + }); + stream.m_guest_channels[cm.m_node_channel] = cm.m_guest_channel; + stream.m_clear_mask &= ~(1 << cm.m_node_channel); + } + } + + } else { + // All sources need to be merged per-destination, max one stream per destination + + std::map stream_per_node; + + // Retrieve or create the one osd stream for a given + // destination. First check if we already have it, then + // whether it was previously created, then otherwise create + // it. + + auto get_stream_for_node = [this, ¤t_streams, &stream_per_node] (const osd::audio_info::node_info *node, bool is_system_default) -> u32 { + // Pick up the existing stream if there's one + auto si = stream_per_node.find(node->m_id); + if(si != stream_per_node.end()) + return si->second; + + // Create the default to-clear mask + u32 channels = node->m_sinks.size(); + u32 cmask = util::make_bitmask(channels); + + // Check if the osd stream already exists to pick it up in case. + // Clear the id in the current_streams structure to show it has been picked up, reset the clear mask. + // m_speaker will already be nullptr, m_source_channels and m_volumes empty. + + for(auto &os : current_streams) + if(os.m_id && os.m_node == node->m_id) { + u32 sid = m_osd_streams.size(); + m_osd_streams.emplace_back(std::move(os)); + os.m_id = 0; + m_osd_streams.back().m_clear_mask = cmask; + m_osd_streams.back().m_is_system_default = is_system_default; + stream_per_node[node->m_id] = sid; + return sid; + } + + // If none exists, create one + u32 sid = m_osd_streams.size(); + m_osd_streams.emplace_back(osd_stream{ 0, node->m_id, is_system_default ? "" : node->m_name, channels, cmask, is_system_default, nullptr }); + osd_stream &stream = m_osd_streams.back(); + u32 rate = machine().sample_rate(); + stream.m_buffer.resize(rate); + stream.m_id = machine().osd().sound_stream_sink_open(node->m_id, machine().system().name, rate); + stream_per_node[node->m_id] = sid; + return sid; + }; + + + // Create/retrieve streams to apply the decided mapping + + for(const auto &omap : m_mappings) { + u32 speaker_index = find_speaker_index(omap.m_speaker); + for(const auto &nm : omap.m_node_mappings) { + const auto *node = find_node_info(nm.m_node); + u32 stream_index = get_stream_for_node(node, nm.m_is_system_default); + u32 cmask = m_osd_streams[stream_index].m_clear_mask; + float linear_volume = osd::db_to_linear(nm.m_db); + + for(u32 channel = 0; channel != m_speakers[speaker_index].m_channels; channel++) { + std::vector targets = find_channel_mapping(omap.m_speaker->get_position(channel), node); + for(u32 tchannel : targets) { + // If the channel is in the to clear mask, use load, otherwise use add + // Apply the volume too + m_mixing_steps.emplace_back(mixing_step { + (cmask & (1 << tchannel)) ? mixing_step::COPYVOL : mixing_step::ADDVOL, + speaker_index, + channel, + stream_index, + tchannel, + linear_volume + }); + cmask &= ~(1 << tchannel); + } + } + m_osd_streams[stream_index].m_clear_mask = cmask; + } + + for(const auto &cm : omap.m_channel_mappings) { + const auto *node = find_node_info(cm.m_node); + u32 stream_index = get_stream_for_node(node, false); + u32 cmask = m_osd_streams[stream_index].m_clear_mask; + + // If the channel is in the to clear mask, use load, otherwise use add + // Apply the volume too + m_mixing_steps.emplace_back(mixing_step { + (cmask & (1 << cm.m_node_channel)) ? mixing_step::COPYVOL : mixing_step::ADDVOL, + speaker_index, + cm.m_guest_channel, + stream_index, + cm.m_node_channel, + osd::db_to_linear(cm.m_db) + }); + m_osd_streams[stream_index].m_clear_mask = cmask & ~(1 << cm.m_node_channel); + } + } + } + + // Add a clear step for all streams that need it + // Also set the volumes if supported + for(u32 stream_index = 0; stream_index != m_osd_streams.size(); stream_index++) { + auto &stream = m_osd_streams[stream_index]; + if(stream.m_clear_mask) { + for(u32 channel = 0; channel != stream.m_channels; channel ++) + if(stream.m_clear_mask & (1 << channel)) + m_mixing_steps.emplace_back(mixing_step { mixing_step::CLEAR, 0, 0, stream_index, channel, 0.0 }); + } + if(!stream.m_volumes.empty()) + osd.sound_stream_set_volumes(stream.m_id, stream.m_volumes); + } + + // Close all previous streams that haven't been picked up + for(const auto &stream : current_streams) + if(stream.m_id) + machine().osd().sound_stream_close(stream.m_id); +} + +void sound_manager::mapping_update() +{ + auto &osd = machine().osd(); + while(m_osd_info.m_generation != osd.sound_get_generation()) { + osd_information_update(); + + if(0) { + fprintf(stderr, "OSD information:\n"); + fprintf(stderr, "- generation %u\n", m_osd_info.m_generation); + fprintf(stderr, "- default sink %u\n", m_osd_info.m_default_sink); + fprintf(stderr, "- default source %u\n", m_osd_info.m_default_source); + fprintf(stderr, "- nodes:\n"); + for(const auto &node : m_osd_info.m_nodes) { + fprintf(stderr, " * %3u %s\n", node.m_id, node.m_name.c_str()); + for(const auto &pi : node.m_sinks) + fprintf(stderr, " > %s [%g %g %g]\n", pi.m_name.c_str(), pi.m_position[0], pi.m_position[1], pi.m_position[2]); + for(const auto &pi : node.m_sources) + fprintf(stderr, " < %s [%g %g %g]\n", pi.m_name.c_str(), pi.m_position[0], pi.m_position[1], pi.m_position[2]); + } + fprintf(stderr, "- streams:\n"); + for(const auto &stream : m_osd_info.m_streams) { + fprintf(stderr, " * %3u node %u", stream.m_id, stream.m_node); + if(!stream.m_volumes.empty()) { + fprintf(stderr, " volumes"); + for(float v : stream.m_volumes) + fprintf(stderr, " %g", v); + } + fprintf(stderr, "\n"); + } + } - // clear out the mix bufers - std::fill_n(&m_leftmix[0], m_samples_this_update, 0); - std::fill_n(&m_rightmix[0], m_samples_this_update, 0); + generate_mapping(); + if(0) { + fprintf(stderr, "MAPPING:\n"); + for(const auto &omap : m_mappings) { + fprintf(stderr, "- speaker %s\n", omap.m_speaker->tag()); + for(const auto &nm : omap.m_node_mappings) + fprintf(stderr, " * node %u volume %g%s\n", nm.m_node, nm.m_db, nm.m_is_system_default ? " (default)" : ""); + for(const auto &cm : omap.m_channel_mappings) + fprintf(stderr, " * channel %u <-> node %u:%i volume %g\n", cm.m_guest_channel, cm.m_node, cm.m_node_channel, cm.m_db); + } + } + + update_osd_streams(); + + if(0) { + fprintf(stderr, "OSD streams:\n"); + for(const auto &os : m_osd_streams) { + if(machine().osd().sound_split_streams_per_source()) { + fprintf(stderr, "- %3u %s node %u", os.m_id, os.m_speaker ? os.m_speaker->tag() : "-", os.m_node); + if(!os.m_guest_channels.empty()) { + fprintf(stderr, " map"); + for(u32 i = 0; i != os.m_channels; i++) + fprintf(stderr, "%c%u", i ? ',' : ' ', os.m_guest_channels[i]); + } + if(machine().osd().sound_external_per_channel_volume()) { + fprintf(stderr, " dB"); + for(u32 i = 0; i != os.m_channels; i++) + fprintf(stderr, " %g", os.m_volumes[i]); + } + fprintf(stderr, "\n"); + } else + fprintf(stderr, "- %3u node %u\n", os.m_id, os.m_node); + } + fprintf(stderr, "Mixing steps:\n"); + for(const auto &ms : m_mixing_steps) { + static const char *const modes[5] = { "clear", "copy", "copy+vol", "add", "add+vol" }; + fprintf(stderr, "- %s %u:%u -> %u:%u level %g\n", modes[ms.m_mode], ms.m_source_index, ms.m_source_channel, ms.m_stream_index, ms.m_stream_channel, ms.m_linear_volume); + } + } + } +} + +void sound_manager::speakers_update(attotime endtime) +{ // force all the speaker streams to generate the proper number of samples - for (speaker_device &speaker : m_speakers) - speaker.mix(&m_leftmix[0], &m_rightmix[0], m_last_update, endtime, m_samples_this_update, (m_muted & MUTE_REASON_SYSTEM)); + for (speaker_info &speaker : m_speakers) + for (u32 channel = 0; channel != speaker.m_channels; channel++) + speaker.m_speaker.update(channel, speaker.m_buffers[channel].data(), m_last_update, endtime, m_samples_this_update); +} +void sound_manager::compressor_update() +{ // determine the maximum in this section stream_buffer::sample_t curmax = 0; - for (int sampindex = 0; sampindex < m_samples_this_update; sampindex++) - { - auto sample = m_leftmix[sampindex]; - if (sample < 0) - sample = -sample; - if (sample > curmax) - curmax = sample; - - sample = m_rightmix[sampindex]; - if (sample < 0) - sample = -sample; - if (sample > curmax) - curmax = sample; + for (speaker_info &speaker : m_speakers) + { + for (std::vector &buffer : speaker.m_buffers) + for (int sampindex = 0; sampindex < m_samples_this_update; sampindex++) + { + auto sample = buffer[sampindex]; + if (sample < 0) + sample = -sample; + if (sample > curmax) + curmax = sample; + } } // pull in current compressor scale factor before modifying - stream_buffer::sample_t lscale = m_compressor_scale; - stream_buffer::sample_t rscale = m_compressor_scale; + stream_buffer::sample_t current_scale = m_compressor_scale; // if we're above what the compressor will handle, adjust the compression if (curmax * m_compressor_scale > 1.0) @@ -1574,67 +2357,186 @@ void sound_manager::update(s32 param) } #if (SOUND_DEBUG) - if (lscale != m_compressor_scale) + if (current_scale != m_compressor_scale) printf("scale=%.5f\n", m_compressor_scale); #endif - // track whether there are pending scale changes in left/right - stream_buffer::sample_t lprev = 0, rprev = 0; + float volume = osd::db_to_linear(m_volume) * 32767; - // now downmix the final result - u32 finalmix_step = machine().video().speed_factor(); - u32 finalmix_offset = 0; - s16 *finalmix = &m_finalmix[0]; - int sample; - for (sample = m_finalmix_leftover; sample < m_samples_this_update * 1000; sample += finalmix_step) - { - int sampindex = sample / 1000; - - // ensure that changing the compression won't reverse direction to reduce "pops" - stream_buffer::sample_t lsamp = m_leftmix[sampindex]; - if (lscale != m_compressor_scale && sample != m_finalmix_leftover) - lscale = adjust_toward_compressor_scale(lscale, lprev, lsamp); - - lprev = lsamp * lscale; - if (m_compressor_enabled) - lsamp = lprev; - - // clamp the left side - if (lsamp > 1.0) - lsamp = 1.0; - else if (lsamp < -1.0) - lsamp = -1.0; - finalmix[finalmix_offset++] = s16(lsamp * 32767.0); - - // ensure that changing the compression won't reverse direction to reduce "pops" - stream_buffer::sample_t rsamp = m_rightmix[sampindex]; - if (rscale != m_compressor_scale && sample != m_finalmix_leftover) - rscale = adjust_toward_compressor_scale(rscale, rprev, rsamp); - - rprev = rsamp * rscale; - if (m_compressor_enabled) - rsamp = rprev; - - // clamp the right side - if (rsamp > 1.0) - rsamp = 1.0; - else if (rsamp < -1.0) - rsamp = -1.0; - finalmix[finalmix_offset++] = s16(rsamp * 32767.0); - } - m_finalmix_leftover = sample - m_samples_this_update * 1000; + // now compute the final results pre-mixing + u32 comp_step = machine().video().speed_factor(); + u32 leftover = 0xffffffff; - // play the result - if (finalmix_offset > 0) - { - if (!m_nosound_mode) - machine().osd().update_audio_stream(finalmix, finalmix_offset / 2); - machine().osd().add_audio_to_recording(finalmix, finalmix_offset / 2); - machine().video().add_sound_to_recording(finalmix, finalmix_offset / 2); - if (m_wavfile) - util::wav_add_data_16(*m_wavfile, finalmix, finalmix_offset); + for (speaker_info &speaker : m_speakers) + for (u32 channel = 0; channel != speaker.m_channels; channel++) + { + const stream_buffer::sample_t *src = speaker.m_buffers[channel].data(); + s16 *dest = speaker.m_comp_buffers[channel].data(); + stream_buffer::sample_t scale = current_scale; + stream_buffer::sample_t prev = 0; + m_comp_samples_this_update = 0; + + int sample; + for (sample = m_comp_leftover; sample < m_samples_this_update * 1000; sample += comp_step) + { + int sampindex = sample / 1000; + + // ensure that changing the compression won't reverse direction to reduce "pops" + stream_buffer::sample_t samp = src[sampindex]; + if (scale != m_compressor_scale && sample != m_comp_leftover) + scale = adjust_toward_compressor_scale(scale, prev, samp); + + prev = samp * scale; + if (m_compressor_enabled) + samp = prev; + + if (samp > 1.0) + samp = 1.0; + else if (samp < -1.0) + samp = -1.0; + dest[m_comp_samples_this_update++] = s16(samp * volume); + + } + if (leftover == 0xffffffff) + leftover = sample - m_samples_this_update * 1000; + } + if (leftover == 0xffffffff) + leftover = 0; + m_comp_leftover = leftover; +} + +void sound_manager::osd_output() +{ + // Apply the mixing steps + for(const auto &step : m_mixing_steps) { + s16 *dest = m_osd_streams[step.m_stream_index].m_buffer.data() + step.m_stream_channel; + u32 skip = m_osd_streams[step.m_stream_index].m_channels; + const s16 *src = step.m_mode == mixing_step::CLEAR ? nullptr : m_speakers[step.m_source_index].m_comp_buffers[step.m_source_channel].data(); + + switch(step.m_mode) { + case mixing_step::CLEAR: + for(u32 sample = 0; sample != m_comp_samples_this_update; sample++) { + *dest = 0; + dest += skip; + } + break; + + case mixing_step::COPY: + for(u32 sample = 0; sample != m_comp_samples_this_update; sample++) { + *dest = *src++; + dest += skip; + } + break; + + case mixing_step::COPYVOL: + for(u32 sample = 0; sample != m_comp_samples_this_update; sample++) { + *dest = s16(*src++ * step.m_linear_volume); + dest += skip; + } + break; + + + case mixing_step::ADD: + for(u32 sample = 0; sample != m_comp_samples_this_update; sample++) { + *dest += *src++; + dest += skip; + } + break; + + case mixing_step::ADDVOL: + for(u32 sample = 0; sample != m_comp_samples_this_update; sample++) { + *dest += s16(*src++ * step.m_linear_volume); + dest += skip; + } + break; + } } + // Send the result to the osd + for(const auto &stream : m_osd_streams) + machine().osd().sound_stream_update(stream.m_id, stream.m_buffer.data(), m_comp_samples_this_update); +} + + +void sound_manager::recording_output() +{ + std::fill(m_record_buffer.begin(), m_record_buffer.begin() + m_comp_samples_this_update * 2, 0); + for (speaker_info &speaker : m_speakers) + for (u32 channel = 0; channel != speaker.m_channels; channel++) + { + s16 *dest = m_record_buffer.data(); + const s16 *src = speaker.m_comp_buffers[channel].data(); + switch (speaker.m_record_mix[channel]) { + case 0: default: break; + case 1: + { + for (u32 i=0; i != m_comp_samples_this_update; i++) + { + *dest ++ += *src++; + dest ++; + } + break; + } + case 2: + { + for (u32 i=0; i != m_comp_samples_this_update; i++) + { + dest ++; + *dest ++ += *src++; + } + break; + } + case 3: + { + for (u32 i=0; i != m_comp_samples_this_update; i++) + { + s16 samp = *src++; + *dest ++ += samp; + *dest ++ += samp; + } + break; + } + } + } + + machine().osd().add_audio_to_recording(m_record_buffer.data(), m_comp_samples_this_update); + machine().video().add_sound_to_recording(m_record_buffer.data(), m_comp_samples_this_update); + if (m_wavfile) + util::wav_add_data_16(*m_wavfile, m_record_buffer.data(), m_comp_samples_this_update); +} + + +//------------------------------------------------- +// update - mix everything down to its final form +// and send it to the OSD layer +//------------------------------------------------- + +void sound_manager::update(s32) +{ + LOG("sound_update\n"); + + auto profile = g_profiler.start(PROFILER_SOUND); + + // determine the duration of this update + attotime update_period = machine().time() - m_last_update; + sound_assert(update_period.seconds() == 0); + + // use that to compute the number of samples we need from the speakers + attoseconds_t sample_rate_attos = HZ_TO_ATTOSECONDS(machine().sample_rate()); + m_samples_this_update = update_period.attoseconds() / sample_rate_attos; + + // recompute the end time to an even sample boundary + attotime endtime = m_last_update + attotime(0, m_samples_this_update * sample_rate_attos); + + if(m_osd_info.m_generation == 0xffffffff) + startup_cleanups(); + + mapping_update(); + speakers_update(endtime); + compressor_update(); + osd_output(); + recording_output(); + // update any orphaned streams so they don't get too far behind for (auto &stream : m_orphan_stream_list) stream.first->update(); diff --git a/src/emu/sound.h b/src/emu/sound.h index 15f6a5743a254..35b88bcd570ca 100644 --- a/src/emu/sound.h +++ b/src/emu/sound.h @@ -62,7 +62,7 @@ #define MAME_EMU_SOUND_H #include "wavwrite.h" - +#include "interface/audio.h" //************************************************************************** // CONSTANTS @@ -755,6 +755,25 @@ class sound_manager static const attotime STREAMS_UPDATE_ATTOTIME; public: + struct mapping { + struct node_mapping { + u32 m_node; + float m_db; + bool m_is_system_default; + }; + + struct channel_mapping { + u32 m_guest_channel; + u32 m_node; + u32 m_node_channel; + float m_db; + bool m_is_system_default; + }; + speaker_device *m_speaker; + std::vector m_node_mappings; + std::vector m_channel_mappings; + }; + static constexpr int STREAMS_UPDATE_FREQUENCY = 50; // construction/destruction @@ -763,13 +782,15 @@ class sound_manager // getters running_machine &machine() const { return m_machine; } - int attenuation() const { return m_attenuation; } + float volume() const { return m_volume; } const std::vector> &streams() const { return m_stream_list; } attotime last_update() const { return m_last_update; } - int sample_count() const { return m_samples_this_update; } int unique_id() { return m_unique_id++; } stream_buffer::sample_t compressor_scale() const { return m_compressor_scale; } + const typename osd::audio_info &get_osd_info() const { return m_osd_info; } + const std::vector &get_mappings() const { return m_mappings; } + // allocate a new stream with a new-style callback sound_stream *stream_alloc(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags); @@ -779,8 +800,22 @@ class sound_manager bool start_recording(std::string_view filename); void stop_recording(); - // set the global OSD attenuation level - void set_attenuation(float attenuation); + // manage the speaker mapping and volume configuration + void config_add_speaker_target_node(speaker_device *speaker, std::string name, float db); + void config_add_speaker_target_default(speaker_device *speaker, float db); + void config_remove_speaker_target_node(speaker_device *speaker, std::string name); + void config_remove_speaker_target_default(speaker_device *speaker); + void config_set_volume_speaker_target_node(speaker_device *speaker, std::string name, float db); + void config_set_volume_speaker_target_default(speaker_device *speaker, float db); + void config_add_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel, float db); + void config_add_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel, float db); + void config_remove_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel); + void config_remove_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel); + void config_set_volume_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel, float db); + void config_set_volume_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel, float db); + + // set the global OSD volume level + void set_volume(float db); // mute sound for one of various independent reasons bool muted() const { return bool(m_muted); } @@ -795,9 +830,49 @@ class sound_manager bool indexed_mixer_input(int index, mixer_input &info) const; // fill the given buffer with 16-bit stereo audio samples - void samples(s16 *buffer); + std::vector samples(); private: + struct speaker_info { + speaker_device &m_speaker; + u32 m_channels; + std::vector> m_buffers; + std::vector> m_comp_buffers; + std::vector m_record_mix; + + speaker_info(speaker_device &speaker, u32 size); + }; + + struct osd_stream { + u32 m_id; + u32 m_node; + std::string m_node_name; + u32 m_channels; + u32 m_clear_mask; + bool m_is_system_default; + speaker_device *m_speaker; + std::vector m_guest_channels; + std::vector m_volumes; + std::vector m_buffer; + }; + + struct mixing_step { + enum { CLEAR, COPY, COPYVOL, ADD, ADDVOL }; + u32 m_mode; + u32 m_source_index; + u32 m_source_channel; + u32 m_stream_index; + u32 m_stream_channel; + float m_linear_volume; + }; + + struct config_mapping { + std::string m_name; + // "" to indicates default node + std::vector> m_node_mappings; + std::vector> m_channel_mappings; + }; + // set/reset the mute state for the given reason void mute(bool mute, u8 reason); @@ -822,35 +897,69 @@ class sound_manager stream_buffer::sample_t adjust_toward_compressor_scale(stream_buffer::sample_t curscale, stream_buffer::sample_t prevsample, stream_buffer::sample_t rawsample); // periodic sound update, called STREAMS_UPDATE_FREQUENCY per second - void update(s32 param = 0); + void update(s32); + + // handle mixing mapping update if needed + static std::vector find_channel_mapping(const std::array &position, const osd::audio_info::node_info *node); + void startup_cleanups(); + void mapping_update(); + void osd_information_update(); + void generate_mapping(); + void update_osd_streams(); + void speakers_update(attotime endtime); + void compressor_update(); + void osd_output(); + void recording_output(); + + // manage the speaker mapping and volume configuration, + // but don't change generation because we're in the update process + + config_mapping &config_get_speaker(speaker_device *speaker); + void internal_config_add_speaker_target_node(speaker_device *speaker, std::string name, float db); + void internal_config_add_speaker_target_default(speaker_device *speaker, float db); + void internal_config_remove_speaker_target_node(speaker_device *speaker, std::string name); + void internal_config_remove_speaker_target_default(speaker_device *speaker); + void internal_config_set_volume_speaker_target_node(speaker_device *speaker, std::string name, float db); + void internal_config_set_volume_speaker_target_default(speaker_device *speaker, float db); + void internal_config_add_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel, float db); + void internal_config_add_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel, float db); + void internal_config_remove_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel); + void internal_config_remove_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel); + void internal_config_set_volume_speaker_channel_target_node(speaker_device *speaker, u32 guest_channel, std::string name, u32 node_channel, float db); + void internal_config_set_volume_speaker_channel_target_default(speaker_device *speaker, u32 guest_channel, u32 node_channel, float db); + // internal state - running_machine &m_machine; // reference to the running machine - emu_timer *m_update_timer; // timer that runs the update function - std::vector > m_speakers; - - u32 m_update_number; // current update index; used for sample rate updates - attotime m_last_update; // time of the last update - u32 m_finalmix_leftover; // leftover samples in the final mix - u32 m_samples_this_update; // number of samples this update - std::vector m_finalmix; // final mix, in 16-bit signed format - std::vector m_leftmix; // left speaker mix, in native format - std::vector m_rightmix; // right speaker mix, in native format + running_machine &m_machine; // reference to the running machine + emu_timer *m_update_timer; // timer that runs the update function + std::vector m_speakers; + + u32 m_update_number; // current update index; used for sample rate updates + attotime m_last_update; // time of the last update + u32 m_comp_leftover; // leftover samples from the compressor + u32 m_samples_this_update; // number of samples this update + u32 m_comp_samples_this_update; // number of samples this update post compression and freq. scaling + std::vector m_record_buffer; // recording final mix, in 16-bit interleaved stereo signed format + osd::audio_info m_osd_info; // current state of the osd information + std::vector m_mappings; // current state of the mappings + std::vector m_osd_streams; // currently active osd streams + std::vector m_mixing_steps; // actions to take to fill the osd streams buffers + std::vector m_configs; // mapping user configuration stream_buffer::sample_t m_compressor_scale; // current compressor scale factor - int m_compressor_counter; // compressor update counter for backoff - bool m_compressor_enabled; // enable compressor (it will still be calculated for detecting overdrive) + int m_compressor_counter; // compressor update counter for backoff + bool m_compressor_enabled; // enable compressor (it will still be calculated for detecting overdrive) - u8 m_muted; // bitmask of muting reasons - bool m_nosound_mode; // true if we're in "nosound" mode - int m_attenuation; // current attentuation level (at the OSD) - int m_unique_id; // unique ID used for stream identification - util::wav_file_ptr m_wavfile; // WAV file for streaming + u8 m_muted; // bitmask of muting reasons + bool m_nosound_mode; // true if we're in "nosound" mode + float m_volume; // current volume level (at the OSD) + int m_unique_id; // unique ID used for stream identification + util::wav_file_ptr m_wavfile; // WAV file for streaming // streams data std::vector> m_stream_list; // list of streams std::map m_orphan_stream_list; // list of orphaned streams - bool m_first_reset; // is this our first reset? + bool m_first_reset; // is this our first reset? }; diff --git a/src/emu/speaker.cpp b/src/emu/speaker.cpp index 33b1e8d329a07..cabae5cb8cc0f 100644 --- a/src/emu/speaker.cpp +++ b/src/emu/speaker.cpp @@ -21,7 +21,29 @@ // device type definition DEFINE_DEVICE_TYPE(SPEAKER, speaker_device, "speaker", "Speaker") - +const speaker_device::position_name_mapping speaker_device::position_name_mappings[] = { + { 0.0, 0.0, 1.0, "Front center" }, + { -0.2, 0.0, 1.0, "Front left" }, + { 0.0, -0.5, 1.0, "Front floor" }, + { 0.2, 0.0, 1.0, "Front right" }, + { 0.0, 0.0, -0.5, "Rear center" }, + { -0.2, 0.0, -0.5, "Rear left" }, + { 0.2, 0.0, -0.5, "Read right" }, + { 0.0, 0.0, -0.1, "Headrest center" }, + { -0.1, 0.0, -0.1, "Headrest left" }, + { 0.1, 0.0, -0.1, "Headrest right" }, + { 0.0, -0.5, 0.0, "Seat" }, + { 0.0, -0.2, 0.1, "Backrest" }, + { } +}; + +std::string speaker_device::get_position_name(u32 channel) const +{ + for(unsigned int i = 0; position_name_mappings[i].m_name; i++) + if(m_positions[channel][0] == position_name_mappings[i].m_x && m_positions[channel][1] == position_name_mappings[i].m_y && m_positions[channel][2] == position_name_mappings[i].m_z) + return position_name_mappings[i].m_name; + return util::string_format("#%d", channel); +} //************************************************************************** // LIVE SPEAKER DEVICE @@ -31,14 +53,10 @@ DEFINE_DEVICE_TYPE(SPEAKER, speaker_device, "speaker", "Speaker") // speaker_device - constructor //------------------------------------------------- -speaker_device::speaker_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) - : device_t(mconfig, SPEAKER, tag, owner, clock) - , device_mixer_interface(mconfig, *this) - , m_x(0.0) - , m_y(0.0) - , m_z(0.0) - , m_pan(0.0) - , m_defpan(0.0) +speaker_device::speaker_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 channels) + : device_t(mconfig, SPEAKER, tag, owner, 0) + , device_mixer_interface(mconfig, *this, channels ? channels : 1) + , m_positions(channels ? channels : 1) , m_current_max(0) , m_samples_this_bucket(0) { @@ -58,35 +76,20 @@ speaker_device::~speaker_device() // set_position - set speaker position //------------------------------------------------- -speaker_device &speaker_device::set_position(double x, double y, double z) +speaker_device &speaker_device::set_position(u32 channel, double x, double y, double z) { - // as mentioned in the header file, y and z params currently have no effect - m_x = x; - m_y = y; - m_z = z; - - // hard pan to left - if (m_x < 0.0) - set_pan(-1.0f); - - // hard pan to right - else if (m_x > 0.0) - set_pan(1.0f); - - // center (mono) - else - set_pan(0.0f); - - m_defpan = m_pan; + m_positions[channel][0] = x; + m_positions[channel][1] = y; + m_positions[channel][2] = z; return *this; } //------------------------------------------------- -// mix - mix in samples from the speaker's stream +// update - output the samples from the speaker's stream //------------------------------------------------- -void speaker_device::mix(stream_buffer::sample_t *leftmix, stream_buffer::sample_t *rightmix, attotime start, attotime end, int expected_samples, bool suppress) +void speaker_device::update(u32 channel, stream_buffer::sample_t *output, attotime start, attotime end, int expected_samples) { // skip if no stream if (m_mixer_stream == nullptr) @@ -97,7 +100,7 @@ void speaker_device::mix(stream_buffer::sample_t *leftmix, stream_buffer::sample return; // get a view on the desired range - read_stream_view view = m_mixer_stream->update_view(start, end); + read_stream_view view = m_mixer_stream->update_view(start, end, channel); sound_assert(view.samples() >= expected_samples); // track maximum sample value for each 0.1s bucket @@ -116,33 +119,8 @@ void speaker_device::mix(stream_buffer::sample_t *leftmix, stream_buffer::sample } } - // mix if sound is enabled - if (!suppress) - { - // if the speaker is hard panned to the left, send only to the left - if (m_pan == -1.0f) - for (int sample = 0; sample < expected_samples; sample++) - leftmix[sample] += view.get(sample); - - // if the speaker is hard panned to the right, send only to the right - else if (m_pan == 1.0f) - for (int sample = 0; sample < expected_samples; sample++) - rightmix[sample] += view.get(sample); - - // otherwise, send to both - else - { - const float leftpan = (m_pan <= 0.0f) ? 1.0f : 1.0f - m_pan; - const float rightpan = (m_pan >= 0.0f) ? 1.0f : 1.0f + m_pan; - - for (int sample = 0; sample < expected_samples; sample++) - { - stream_buffer::sample_t cursample = view.get(sample); - leftmix[sample] += cursample * leftpan; - rightmix[sample] += cursample * rightpan; - } - } - } + for (int sample = 0; sample < expected_samples; sample++) + output[sample] = view.get(sample); } diff --git a/src/emu/speaker.h b/src/emu/speaker.h index 3cb0794d998ae..b01d3065c8a0b 100644 --- a/src/emu/speaker.h +++ b/src/emu/speaker.h @@ -15,9 +15,6 @@ * Positive z is in front of the observer * Negative z is behind the observer - Currently, MAME only considers the sign of the x coordinate (not its - magnitude), and completely ignores the y and z coordinates. - ***************************************************************************/ #ifndef MAME_EMU_SPEAKER_H @@ -39,8 +36,6 @@ DECLARE_DEVICE_TYPE(SPEAKER, speaker_device) // TYPE DEFINITIONS //************************************************************************** -// ======================> speaker_device - class speaker_device : public device_t, public device_mixer_interface { public: @@ -48,45 +43,48 @@ class speaker_device : public device_t, public device_mixer_interface speaker_device(const machine_config &mconfig, const char *tag, device_t *owner, double x, double y, double z) : speaker_device(mconfig, tag, owner, 0) { - set_position(x, y, z); + set_position(0, x, y, z); } - speaker_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0); + speaker_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 channels = 1); // Collides with clock, but not important virtual ~speaker_device(); // configuration helpers - speaker_device &set_position(double x, double y, double z); - speaker_device &front_center() { set_position( 0.0, 0.0, 1.0); return *this; } - speaker_device &front_left() { set_position(-0.2, 0.0, 1.0); return *this; } - speaker_device &front_floor() { set_position( 0.0, -0.5, 1.0); return *this; } - speaker_device &front_right() { set_position( 0.2, 0.0, 1.0); return *this; } - speaker_device &rear_center() { set_position( 0.0, 0.0, -0.5); return *this; } - speaker_device &rear_left() { set_position(-0.2, 0.0, -0.5); return *this; } - speaker_device &rear_right() { set_position( 0.2, 0.0, -0.5); return *this; } - speaker_device &headrest_center() { set_position( 0.0, 0.0, -0.1); return *this; } - speaker_device &headrest_left() { set_position(-0.1, 0.0, -0.1); return *this; } - speaker_device &headrest_right() { set_position( 0.1, 0.0, -0.1); return *this; } - speaker_device &seat() { set_position( 0.0, -0.5, 0.0); return *this; } - speaker_device &backrest() { set_position( 0.0, -0.2, 0.1); return *this; } + speaker_device &set_position(u32 channel, double x, double y, double z); + speaker_device &front_center(u32 channel = 0) { return set_position(channel, 0.0, 0.0, 1.0); } + speaker_device &front_left(u32 channel = 0) { return set_position(channel, -0.2, 0.0, 1.0); } + speaker_device &front_floor(u32 channel = 0) { return set_position(channel, 0.0, -0.5, 1.0); } + speaker_device &front_right(u32 channel = 0) { return set_position(channel, 0.2, 0.0, 1.0); } + speaker_device &rear_center(u32 channel = 0) { return set_position(channel, 0.0, 0.0, -0.5); } + speaker_device &rear_left(u32 channel = 0) { return set_position(channel, -0.2, 0.0, -0.5); } + speaker_device &rear_right(u32 channel = 0) { return set_position(channel, 0.2, 0.0, -0.5); } + speaker_device &headrest_center(u32 channel = 0) { return set_position(channel, 0.0, 0.0, -0.1); } + speaker_device &headrest_left(u32 channel = 0) { return set_position(channel, -0.1, 0.0, -0.1); } + speaker_device &headrest_right(u32 channel = 0) { return set_position(channel, 0.1, 0.0, -0.1); } + speaker_device &seat(u32 channel = 0) { return set_position(channel, 0.0, -0.5, 0.0); } + speaker_device &backrest(u32 channel = 0) { return set_position(channel, 0.0, -0.2, 0.1); } + speaker_device &front() { return front_left(0).front_right(1); } + speaker_device &rear() { return rear_left(0).rear_right(1); } + speaker_device &corners() { return front_left(0).front_right(1).rear_left(2).rear_right(3); } + std::array get_position(u32 channel) const { return m_positions[channel]; } + std::string get_position_name(u32 channel) const; // internally for use by the sound system - void mix(stream_buffer::sample_t *leftmix, stream_buffer::sample_t *rightmix, attotime start, attotime end, int expected_samples, bool suppress); - - // user panning configuration - void set_pan(float pan) { m_pan = std::clamp(pan, -1.0f, 1.0f); } - float pan() { return m_pan; } - float defpan() { return m_defpan; } + void update(u32 channel, stream_buffer::sample_t *output, attotime start, attotime end, int expected_samples); protected: + struct position_name_mapping { + double m_x, m_y, m_z; + const char *m_name; + }; + + static const position_name_mapping position_name_mappings[]; + // device-level overrides virtual void device_start() override ATTR_COLD; virtual void device_stop() override ATTR_COLD; // configuration state - double m_x; - double m_y; - double m_z; - float m_pan; - float m_defpan; + std::vector> m_positions; // internal state static constexpr int BUCKETS_PER_SECOND = 10; diff --git a/src/frontend/mame/luaengine.cpp b/src/frontend/mame/luaengine.cpp index c97cc2988bc10..3fb0c2eae0d12 100644 --- a/src/frontend/mame/luaengine.cpp +++ b/src/frontend/mame/luaengine.cpp @@ -2058,11 +2058,8 @@ void lua_engine::initialize() sound_type["get_samples"] = [] (sound_manager &sm, sol::this_state s) { - luaL_Buffer buff; - s32 const count = sm.sample_count() * 2 * 2; // 2 channels, 2 bytes per sample - s16 *const ptr = (s16 *)luaL_buffinitsize(s, &buff, count); - sm.samples(ptr); - luaL_pushresultsize(&buff, count); + std::vector samples = sm.samples(); + lua_pushlstring(s, (const char *)samples.data(), samples.size()*2); return sol::make_reference(s, sol::stack_reference(s, -1)); }; sound_type["muted"] = sol::property(&sound_manager::muted); @@ -2075,9 +2072,9 @@ void lua_engine::initialize() sound_type["system_mute"] = sol::property( static_cast(&sound_manager::system_mute), static_cast(&sound_manager::system_mute)); - sound_type["attenuation"] = sol::property( - &sound_manager::attenuation, - &sound_manager::set_attenuation); + sound_type["volume"] = sol::property( + &sound_manager::volume, + &sound_manager::set_volume); sound_type["recording"] = sol::property(&sound_manager::is_recording); diff --git a/src/frontend/mame/ui/audiomix.cpp b/src/frontend/mame/ui/audiomix.cpp new file mode 100644 index 0000000000000..263479a2128ec --- /dev/null +++ b/src/frontend/mame/ui/audiomix.cpp @@ -0,0 +1,899 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/********************************************************************* + + ui/audio_mixer.cpp + + Audio mixing/mapping control + +*********************************************************************/ + +#include "emu.h" +#include "ui/audiomix.h" + +#include "ui/ui.h" + +#include "osdepend.h" +#include "speaker.h" + +namespace ui { + +menu_audio_mixer::menu_audio_mixer(mame_ui_manager &mui, render_container &container) + : menu(mui, container) +{ + set_heading(_("Audio Mixer")); + m_generation = 0; + m_current_selection.m_maptype = MT_UNDEFINED; + m_current_selection.m_speaker = nullptr; + m_current_selection.m_guest_channel = 0; + m_current_selection.m_node = 0; + m_current_selection.m_node_channel = 0; + m_current_group = GRP_NODE; + + set_process_flags(PROCESS_LR_ALWAYS); +} + +menu_audio_mixer::~menu_audio_mixer() +{ +} + +bool menu_audio_mixer::handle(event const *ev) +{ + if(!ev) { + if(m_generation != machine().sound().get_osd_info().m_generation) { + reset(reset_options::REMEMBER_POSITION); + return true; + } + return false; + } + + switch(ev->iptkey) { + case IPT_UI_MIXER_ADD_FULL: + if(m_current_selection.m_maptype == MT_INTERNAL) + return false; + + if(full_mapping_available(m_current_selection.m_speaker, 0)) { + m_current_selection.m_node = 0; + machine().sound().config_add_speaker_target_default(m_current_selection.m_speaker, 0.0); + + } else { + uint32_t node = find_next_available_target_node(m_current_selection.m_speaker, 0); + if(node == 0xffffffff) + return false; + m_current_selection.m_node = node; + machine().sound().config_add_speaker_target_node(m_current_selection.m_speaker, find_node_name(node), 0.0); + } + + m_current_selection.m_maptype = MT_FULL; + m_current_selection.m_guest_channel = 0; + m_current_selection.m_node_channel = 0; + m_current_selection.m_db = 0.0; + reset(reset_options::REMEMBER_POSITION); + return true; + + case IPT_UI_MIXER_ADD_CHANNEL: { + if(m_current_selection.m_maptype == MT_INTERNAL) + return false; + + // Find a possible triplet, any triplet + const auto &info = machine().sound().get_osd_info(); + u32 guest_channel; + u32 node_index, node_id; + u32 node_channel; + for(node_index = info.m_default_sink == 0 ? 0 : 0xffffffff; node_index != info.m_nodes.size(); node_index++) { + node_id = node_index == 0xffffffff ? 0 : info.m_nodes[node_index].m_id; + u32 guest_channel_count = m_current_selection.m_speaker->outputs(); + u32 node_channel_count = 0; + if(node_index == 0xffffffff) { + for(u32 i = 0; i != info.m_nodes.size(); i++) + if(info.m_nodes[i].m_id == info.m_default_sink) { + node_channel_count = info.m_nodes[i].m_sinks.size(); + break; + } + } else + node_channel_count = info.m_nodes[node_index].m_sinks.size(); + + for(guest_channel = 0; guest_channel != guest_channel_count; guest_channel ++) + for(node_channel = 0; node_channel != node_channel_count; node_channel ++) + if(channel_mapping_available(m_current_selection.m_speaker, guest_channel, node_id, node_channel)) + goto found; + } + return false; + + found: + if(node_id) + machine().sound().config_add_speaker_channel_target_node(m_current_selection.m_speaker, guest_channel, info.m_nodes[node_index].m_name, node_channel, 0.0); + else + machine().sound().config_add_speaker_channel_target_default(m_current_selection.m_speaker, guest_channel, node_channel, 0.0); + m_current_selection.m_maptype = MT_CHANNEL; + m_current_selection.m_guest_channel = guest_channel; + m_current_selection.m_node = node_id; + m_current_selection.m_node_channel = node_channel; + m_current_selection.m_db = 0.0; + reset(reset_options::REMEMBER_POSITION); + return true; + } + + case IPT_UI_CLEAR: { + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_selection.m_node == 0) + machine().sound().config_remove_speaker_target_default(m_current_selection.m_speaker); + else + machine().sound().config_remove_speaker_target_node(m_current_selection.m_speaker, find_node_name(m_current_selection.m_node)); + } else { + if(m_current_selection.m_node == 0) + machine().sound().config_remove_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + else + machine().sound().config_remove_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, find_node_name(m_current_selection.m_node), m_current_selection.m_node_channel); + } + + // Find where the selection was + uint32_t cursel_index = 0; + for(uint32_t i = 0; i != m_selections.size(); i++) + if(m_selections[i] == m_current_selection) { + cursel_index = i; + break; + } + + // If the next item exists and is the same speaker, go there (visually, the cursor stays on the same line) + // Otherwise if the previous item exists and is the same speaker, go there (visually, the cursor goes up once) + // Otherwise create a MT_NONE, because one is going to appear at the same place + + if(cursel_index + 1 < m_selections.size() && m_selections[cursel_index+1].m_speaker == m_current_selection.m_speaker) + m_current_selection = m_selections[cursel_index+1]; + else if(cursel_index != 0 && m_selections[cursel_index-1].m_speaker == m_current_selection.m_speaker) + m_current_selection = m_selections[cursel_index-1]; + else { + m_current_selection.m_maptype = MT_NONE; + m_current_selection.m_guest_channel = 0; + m_current_selection.m_node = 0; + m_current_selection.m_node_channel = 0; + m_current_selection.m_db = 0.0; + } + + reset(reset_options::REMEMBER_POSITION); + return true; + } + + case IPT_UI_UP: + case IPT_UI_DOWN: + if(!ev->itemref) { + m_current_selection.m_maptype = MT_INTERNAL; + reset(reset_options::REMEMBER_POSITION); + return true; + } + + m_current_selection = *(select_entry *)(ev->itemref); + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_group == GRP_GUEST_CHANNEL || m_current_group == GRP_NODE_CHANNEL) + m_current_group = GRP_NODE; + } + reset(reset_options::REMEMBER_POSITION); + return true; + + case IPT_UI_NEXT_GROUP: + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_group == GRP_NODE) + m_current_group = GRP_DB; + else + m_current_group = GRP_NODE; + + } else if(m_current_selection.m_maptype == MT_CHANNEL) { + if(m_current_group == GRP_NODE) + m_current_group = GRP_NODE_CHANNEL; + else if(m_current_group == GRP_NODE_CHANNEL) + m_current_group = GRP_DB; + else if(m_current_group == GRP_DB) + m_current_group = GRP_GUEST_CHANNEL; + else + m_current_group = GRP_NODE; + } + reset(reset_options::REMEMBER_POSITION); + return true; + + case IPT_UI_PREV_GROUP: + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_group == GRP_NODE) + m_current_group = GRP_DB; + else + m_current_group = GRP_NODE; + + } else if(m_current_selection.m_maptype == MT_CHANNEL) { + if(m_current_group == GRP_NODE) + m_current_group = GRP_GUEST_CHANNEL; + else if(m_current_group == GRP_GUEST_CHANNEL) + m_current_group = GRP_DB; + else if(m_current_group == GRP_DB) + m_current_group = GRP_NODE_CHANNEL; + else + m_current_group = GRP_NODE; + } + reset(reset_options::REMEMBER_POSITION); + return true; + + case IPT_UI_LEFT: { + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + switch(m_current_group) { + case GRP_NODE: { + if(m_current_selection.m_maptype == MT_FULL) { + uint32_t prev_node = m_current_selection.m_node; + uint32_t next_node = find_previous_available_target_node(m_current_selection.m_speaker, prev_node); + if(next_node != 0xffffffff) { + m_current_selection.m_node = next_node; + if(prev_node) + machine().sound().config_remove_speaker_target_node(m_current_selection.m_speaker, find_node_name(prev_node)); + else + machine().sound().config_remove_speaker_target_default(m_current_selection.m_speaker); + if(next_node) + machine().sound().config_add_speaker_target_node(m_current_selection.m_speaker, find_node_name(next_node), m_current_selection.m_db); + else + machine().sound().config_add_speaker_target_default(m_current_selection.m_speaker, m_current_selection.m_db); + reset(reset_options::REMEMBER_POSITION); + return true; + } + } else if(m_current_selection.m_maptype == MT_CHANNEL) { + uint32_t prev_node = m_current_selection.m_node; + uint32_t next_node = find_previous_available_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, prev_node, m_current_selection.m_node_channel); + if(next_node != 0xffffffff) { + m_current_selection.m_node = next_node; + if(prev_node) + machine().sound().config_remove_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, find_node_name(prev_node), m_current_selection.m_node_channel); + else + machine().sound().config_remove_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + if(next_node) + machine().sound().config_add_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, find_node_name(next_node), m_current_selection.m_node_channel, m_current_selection.m_db); + else + machine().sound().config_add_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + + case GRP_DB: { + double db = dec_db(m_current_selection.m_db); + m_current_selection.m_db = db; + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_selection.m_node == 0) + machine().sound().config_set_volume_speaker_target_default(m_current_selection.m_speaker, db); + else + machine().sound().config_set_volume_speaker_target_node(m_current_selection.m_speaker, find_node_name(m_current_selection.m_node), db); + } else { + if(m_current_selection.m_node == 0) + machine().sound().config_set_volume_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel, db); + else + machine().sound().config_set_volume_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, find_node_name(m_current_selection.m_node), m_current_selection.m_node_channel, db); + } + reset(reset_options::REMEMBER_POSITION); + return true; + } + + case GRP_GUEST_CHANNEL: { + if(m_current_selection.m_maptype != MT_CHANNEL) + return false; + + u32 guest_channel_count = m_current_selection.m_speaker->outputs(); + if(guest_channel_count == 1) + return false; + u32 guest_channel = m_current_selection.m_guest_channel; + for(;;) { + if(guest_channel == 0) + guest_channel = guest_channel_count - 1; + else + guest_channel --; + if(guest_channel == m_current_selection.m_guest_channel) + return false; + if(channel_mapping_available(m_current_selection.m_speaker, guest_channel, m_current_selection.m_node, m_current_selection.m_node_channel)) { + if(m_current_selection.m_node) { + std::string node = find_node_name(m_current_selection.m_node); + machine().sound().config_remove_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, node, m_current_selection.m_node_channel); + machine().sound().config_add_speaker_channel_target_node(m_current_selection.m_speaker, guest_channel, node, m_current_selection.m_node_channel, m_current_selection.m_db); + } else { + machine().sound().config_remove_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + machine().sound().config_add_speaker_channel_target_default(m_current_selection.m_speaker, guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + } + m_current_selection.m_guest_channel = guest_channel; + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + + case GRP_NODE_CHANNEL: { + if(m_current_selection.m_maptype != MT_CHANNEL) + return false; + + u32 node_channel_count = find_node_channel_count(m_current_selection.m_node); + if(node_channel_count == 1) + return false; + u32 node_channel = m_current_selection.m_node_channel; + for(;;) { + if(node_channel == 0) + node_channel = node_channel_count - 1; + else + node_channel --; + if(node_channel == m_current_selection.m_node_channel) + return false; + if(channel_mapping_available(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node, node_channel)) { + if(m_current_selection.m_node) { + std::string node = find_node_name(m_current_selection.m_node); + machine().sound().config_remove_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, node, m_current_selection.m_node_channel); + machine().sound().config_add_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, node, node_channel, m_current_selection.m_db); + } else { + machine().sound().config_remove_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + machine().sound().config_add_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, node_channel, m_current_selection.m_db); + } + m_current_selection.m_node_channel = node_channel; + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + } + break; + } + + case IPT_UI_RIGHT: { + if(m_current_selection.m_maptype == MT_NONE || m_current_selection.m_maptype == MT_INTERNAL) + return false; + + switch(m_current_group) { + case GRP_NODE: { + if(m_current_selection.m_maptype == MT_FULL) { + uint32_t prev_node = m_current_selection.m_node; + uint32_t next_node = find_next_available_target_node(m_current_selection.m_speaker, prev_node); + if(next_node != 0xffffffff) { + m_current_selection.m_node = next_node; + if(prev_node) + machine().sound().config_remove_speaker_target_node(m_current_selection.m_speaker, find_node_name(prev_node)); + else + machine().sound().config_remove_speaker_target_default(m_current_selection.m_speaker); + if(next_node) + machine().sound().config_add_speaker_target_node(m_current_selection.m_speaker, find_node_name(next_node), m_current_selection.m_db); + else + machine().sound().config_add_speaker_target_default(m_current_selection.m_speaker, m_current_selection.m_db); + reset(reset_options::REMEMBER_POSITION); + return true; + } + } else if(m_current_selection.m_maptype == MT_CHANNEL) { + uint32_t prev_node = m_current_selection.m_node; + uint32_t next_node = find_next_available_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, prev_node, m_current_selection.m_node_channel); + if(next_node != 0xffffffff) { + m_current_selection.m_node = next_node; + if(prev_node) + machine().sound().config_remove_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, find_node_name(prev_node), m_current_selection.m_node_channel); + else + machine().sound().config_remove_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + if(next_node) + machine().sound().config_add_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, find_node_name(next_node), m_current_selection.m_node_channel, m_current_selection.m_db); + else + machine().sound().config_add_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + + case GRP_DB: { + double db = inc_db(m_current_selection.m_db); + m_current_selection.m_db = db; + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_selection.m_node == 0) + machine().sound().config_set_volume_speaker_target_default(m_current_selection.m_speaker, db); + else + machine().sound().config_set_volume_speaker_target_node(m_current_selection.m_speaker, find_node_name(m_current_selection.m_node), db); + } else { + if(m_current_selection.m_node == 0) + machine().sound().config_set_volume_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel, db); + else + machine().sound().config_set_volume_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, find_node_name(m_current_selection.m_node), m_current_selection.m_node_channel, db); + } + reset(reset_options::REMEMBER_POSITION); + return true; + } + + case GRP_GUEST_CHANNEL: { + if(m_current_selection.m_maptype != MT_CHANNEL) + return false; + + u32 guest_channel_count = m_current_selection.m_speaker->outputs(); + if(guest_channel_count == 1) + return false; + u32 guest_channel = m_current_selection.m_guest_channel; + for(;;) { + guest_channel ++; + if(guest_channel == guest_channel_count) + guest_channel = 0; + if(guest_channel == m_current_selection.m_guest_channel) + return false; + if(channel_mapping_available(m_current_selection.m_speaker, guest_channel, m_current_selection.m_node, m_current_selection.m_node_channel)) { + if(m_current_selection.m_node) { + std::string node = find_node_name(m_current_selection.m_node); + machine().sound().config_remove_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, node, m_current_selection.m_node_channel); + machine().sound().config_add_speaker_channel_target_node(m_current_selection.m_speaker, guest_channel, node, m_current_selection.m_node_channel, m_current_selection.m_db); + } else { + machine().sound().config_remove_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + machine().sound().config_add_speaker_channel_target_default(m_current_selection.m_speaker, guest_channel, m_current_selection.m_node_channel, m_current_selection.m_db); + } + m_current_selection.m_guest_channel = guest_channel; + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + + case GRP_NODE_CHANNEL: { + if(m_current_selection.m_maptype != MT_CHANNEL) + return false; + + u32 node_channel_count = find_node_channel_count(m_current_selection.m_node); + if(node_channel_count == 1) + return false; + u32 node_channel = m_current_selection.m_node_channel; + for(;;) { + node_channel ++; + if(node_channel == node_channel_count) + node_channel = 0; + if(node_channel == m_current_selection.m_node_channel) + return false; + if(channel_mapping_available(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node, node_channel)) { + if(m_current_selection.m_node) { + std::string node = find_node_name(m_current_selection.m_node); + machine().sound().config_remove_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, node, m_current_selection.m_node_channel); + machine().sound().config_add_speaker_channel_target_node(m_current_selection.m_speaker, m_current_selection.m_guest_channel, node, node_channel, m_current_selection.m_db); + } else { + machine().sound().config_remove_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, m_current_selection.m_node_channel); + machine().sound().config_add_speaker_channel_target_default(m_current_selection.m_speaker, m_current_selection.m_guest_channel, node_channel, m_current_selection.m_db); + } + m_current_selection.m_node_channel = node_channel; + reset(reset_options::REMEMBER_POSITION); + return true; + } + } + break; + } + } + break; + } + } + + return false; +} + + +//------------------------------------------------- +// menu_audio_mixer_populate - populate the audio_mixer +// menu +//------------------------------------------------- + +void menu_audio_mixer::populate() +{ + const auto &mapping = machine().sound().get_mappings(); + const auto &info = machine().sound().get_osd_info(); + m_generation = info.m_generation; + + auto find_node = [&info](u32 node_id) -> const osd::audio_info::node_info * { + for(const auto &node : info.m_nodes) + if(node.m_id == node_id) + return &node; + // Never happens + return nullptr; + }; + + // Rebuild the selections list + m_selections.clear(); + for(const auto &omap : mapping) { + for(const auto &nmap : omap.m_node_mappings) + m_selections.emplace_back(select_entry { MT_FULL, omap.m_speaker, 0, nmap.m_is_system_default ? 0 : nmap.m_node, 0, nmap.m_db }); + for(const auto &cmap : omap.m_channel_mappings) + m_selections.emplace_back(select_entry { MT_CHANNEL, omap.m_speaker, cmap.m_guest_channel, cmap.m_is_system_default ? 0 : cmap.m_node, cmap.m_node_channel, cmap.m_db }); + if(omap.m_node_mappings.empty() && omap.m_channel_mappings.empty()) + m_selections.emplace_back(select_entry { MT_NONE, omap.m_speaker, 0, 0, 0, 0 }); + } + + // If there's nothing, get out of there + if(m_selections.empty()) + return; + + // Find the line of the current selection, if any. + // Otherwise default to the first line + + u32 cursel_line = 0; + for(u32 i = 0; i != m_selections.size(); i++) + if(m_current_selection == m_selections[i]) { + cursel_line = i; + break; + } + + if(m_current_selection.m_maptype == MT_INTERNAL) + cursel_line = 0xffffffff; + else + m_current_selection = m_selections[cursel_line]; + + if(m_current_selection.m_maptype == MT_FULL) { + if(m_current_group == GRP_GUEST_CHANNEL || m_current_group == GRP_NODE_CHANNEL) + m_current_group = GRP_NODE; + } + + // (Re)build the menu + u32 curline = 0; + for(const auto &omap : mapping) { + item_append(omap.m_speaker->tag(), FLAG_UI_HEADING | FLAG_DISABLE, nullptr); + for(const auto &nmap : omap.m_node_mappings) { + const auto &node = find_node(nmap.m_node); + std::string lnode = nmap.m_is_system_default ? "[default]" : node->m_name; + if(curline == cursel_line && m_current_group == GRP_NODE) + lnode = "\xe2\x86\x90" + lnode + "\xe2\x86\x92"; + + std::string line = "> " + lnode; + + std::string db = util::string_format("%g dB", nmap.m_db); + if(curline == cursel_line && m_current_group == GRP_DB) + db = "\xe2\x86\x90" + db + "\xe2\x86\x92"; + + item_append(line, db, 0, m_selections.data() + curline); + curline ++; + } + for(const auto &cmap : omap.m_channel_mappings) { + const auto &node = find_node(cmap.m_node); + std::string guest_channel = omap.m_speaker->get_position_name(cmap.m_guest_channel); + if(curline == cursel_line && m_current_group == GRP_GUEST_CHANNEL) + guest_channel = "\xe2\x86\x90" + guest_channel + "\xe2\x86\x92"; + + std::string lnode = cmap.m_is_system_default ? "[default]" : node->m_name; + if(curline == cursel_line && m_current_group == GRP_NODE) + lnode = "\xe2\x86\x90" + lnode + "\xe2\x86\x92"; + + std::string lnode_channel = node->m_sinks[cmap.m_node_channel].m_name; + if(curline == cursel_line && m_current_group == GRP_NODE_CHANNEL) + lnode_channel = "\xe2\x86\x90" + lnode_channel + "\xe2\x86\x92"; + + std::string line = guest_channel + " > " + lnode + ":" + lnode_channel; + + std::string db = util::string_format("%g dB", cmap.m_db); + if(curline == cursel_line && m_current_group == GRP_DB) + db = "\xe2\x86\x90" + db + "\xe2\x86\x92"; + + item_append(line, db, 0, m_selections.data() + curline); + curline ++; + } + if(omap.m_node_mappings.empty() && omap.m_channel_mappings.empty()) { + item_append("[no mapping]", 0, m_selections.data() + curline); + curline ++; + } + } + item_append(menu_item_type::SEPARATOR); + item_append(util::string_format("%s: add a full mapping", ui().get_general_input_setting(IPT_UI_MIXER_ADD_FULL)), FLAG_DISABLE, nullptr); + item_append(util::string_format("%s: add a channel mapping", ui().get_general_input_setting(IPT_UI_MIXER_ADD_CHANNEL)), FLAG_DISABLE, nullptr); + item_append(util::string_format("%s: remove a mapping", ui().get_general_input_setting(IPT_UI_CLEAR)), FLAG_DISABLE, nullptr); + item_append(menu_item_type::SEPARATOR); + + if(cursel_line != 0xffffffff) + set_selection(m_selections.data() + cursel_line); +} + + +//------------------------------------------------- +// recompute_metrics - recompute metrics +//------------------------------------------------- + +void menu_audio_mixer::recompute_metrics(uint32_t width, uint32_t height, float aspect) +{ + menu::recompute_metrics(width, height, aspect); + + // set_custom_space(0.0f, 2.0f * line_height() + 2.0f * tb_border()); +} + + +//------------------------------------------------- +// menu_audio_mixer_custom_render - perform our special +// rendering +//------------------------------------------------- + +void menu_audio_mixer::custom_render(void *selectedref, float top, float bottom, float x1, float y1, float x2, float y2) +{ +} + + +//------------------------------------------------- +// menu_activated - handle menu gaining focus +//------------------------------------------------- + +void menu_audio_mixer::menu_activated() +{ + // scripts or the other form of the menu could have changed something in the mean time + reset(reset_options::REMEMBER_POSITION); +} + + +//------------------------------------------------- +// menu_deactivated - handle menu losing focus +//------------------------------------------------- + +void menu_audio_mixer::menu_deactivated() +{ +} + +uint32_t menu_audio_mixer::find_node_index(uint32_t node) const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t i = 0; i != info.m_nodes.size(); i++) + if(info.m_nodes[i].m_id == node) + return i; + // Can't happen in theory + return 0xffffffff; +} + +std::string menu_audio_mixer::find_node_name(uint32_t node) const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t i = 0; i != info.m_nodes.size(); i++) + if(info.m_nodes[i].m_id == node) + return info.m_nodes[i].m_name; + // Can't happen in theory + return ""; +} + +uint32_t menu_audio_mixer::find_node_channel_count(uint32_t node) const +{ + const auto &info = machine().sound().get_osd_info(); + if(!node) + node = info.m_default_sink; + for(uint32_t i = 0; i != info.m_nodes.size(); i++) + if(info.m_nodes[i].m_id == node) + return info.m_nodes[i].m_sinks.size(); + // Can't happen in theory + return 0; +} + + +uint32_t menu_audio_mixer::find_next_target_node_index(uint32_t index) const +{ + if(index == 0xffffffff) + return index; + + const auto &info = machine().sound().get_osd_info(); + for(uint32_t idx = index + 1; idx != info.m_nodes.size(); idx++) + if(!info.m_nodes[idx].m_sinks.empty()) + return idx; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_previous_target_node_index(uint32_t index) const +{ + if(index == 0xffffffff) + return index; + + const auto &info = machine().sound().get_osd_info(); + for(uint32_t idx = index - 1; idx != 0xffffffff; idx--) + if(!info.m_nodes[idx].m_sinks.empty()) + return idx; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_first_target_node_index() const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t index = 0; index != info.m_nodes.size(); index ++) + if(!info.m_nodes[index].m_sinks.empty()) + return index; + return 0xffffffff; +} + +uint32_t menu_audio_mixer::find_last_target_node_index() const +{ + const auto &info = machine().sound().get_osd_info(); + for(uint32_t index = info.m_nodes.size() - 1; index != 0xffffffff; index --) + if(!info.m_nodes[index].m_sinks.empty()) + return index; + return 0xffffffff; +} + +bool menu_audio_mixer::full_mapping_available(speaker_device *speaker, uint32_t node) const +{ + if(!node && machine().sound().get_osd_info().m_default_sink == 0) + return false; + + const auto &mapping = machine().sound().get_mappings(); + for(const auto &omap : mapping) + if(omap.m_speaker == speaker) { + for(const auto &nmap : omap.m_node_mappings) + if((node != 0 && nmap.m_node == node && !nmap.m_is_system_default) || (node == 0 && nmap.m_is_system_default)) + return false; + return true; + } + return true; +} + +bool menu_audio_mixer::channel_mapping_available(speaker_device *speaker, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const +{ + if(!node && machine().sound().get_osd_info().m_default_sink == 0) + return false; + + const auto &mapping = machine().sound().get_mappings(); + for(const auto &omap : mapping) + if(omap.m_speaker == speaker) { + for(const auto &cmap : omap.m_channel_mappings) + if(cmap.m_guest_channel == guest_channel && + ((node != 0 && cmap.m_node == node && !cmap.m_is_system_default) || (node == 0 && cmap.m_is_system_default)) + && cmap.m_node_channel == node_channel) + return false; + return true; + } + return true; +} + +uint32_t menu_audio_mixer::find_next_available_target_node(speaker_device *speaker, uint32_t node) const +{ + const auto &info = machine().sound().get_osd_info(); + + if(node == 0) { + uint32_t index = find_first_target_node_index(); + while(index != 0xffffffff && !full_mapping_available(speaker, info.m_nodes[index].m_id)) + index = find_next_target_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_next_target_node_index(index); + if(index != 0xffffffff && full_mapping_available(speaker, info.m_nodes[index].m_id)) + return info.m_nodes[index].m_id; + } + + if(info.m_default_sink != 0 && full_mapping_available(speaker, 0)) + return 0; + + index = find_first_target_node_index(); + while(index != 0xffffffff && !full_mapping_available(speaker, info.m_nodes[index].m_id)) + index = find_next_target_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; +} + +uint32_t menu_audio_mixer::find_previous_available_target_node(speaker_device *speaker, uint32_t node) const +{ + const auto &info = machine().sound().get_osd_info(); + + if(node == 0) { + uint32_t index = find_last_target_node_index(); + while(index != 0xffffffff && !full_mapping_available(speaker, info.m_nodes[index].m_id)) + index = find_previous_target_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_previous_target_node_index(index); + if(index != 0xffffffff && full_mapping_available(speaker, info.m_nodes[index].m_id)) + return info.m_nodes[index].m_id; + } + + if(info.m_default_sink != 0 && full_mapping_available(speaker, 0)) + return 0; + + index = find_last_target_node_index(); + while(index != 0xffffffff && !full_mapping_available(speaker, info.m_nodes[index].m_id)) + index = find_previous_target_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; +} + +uint32_t menu_audio_mixer::find_next_available_channel_target_node(speaker_device *speaker, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const +{ + const auto &info = machine().sound().get_osd_info(); + + if(node == 0) { + uint32_t index = find_first_target_node_index(); + while(index != 0xffffffff && !channel_mapping_available(speaker, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_next_target_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_next_target_node_index(index); + if(index != 0xffffffff && channel_mapping_available(speaker, guest_channel, info.m_nodes[index].m_id, node_channel)) + return info.m_nodes[index].m_id; + } + + if(info.m_default_sink != 0 && channel_mapping_available(speaker, guest_channel, 0, node_channel)) + return 0; + + index = find_first_target_node_index(); + while(index != 0xffffffff && !channel_mapping_available(speaker, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_next_target_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; +} + +uint32_t menu_audio_mixer::find_previous_available_channel_target_node(speaker_device *speaker, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const +{ + const auto &info = machine().sound().get_osd_info(); + + if(node == 0) { + uint32_t index = find_last_target_node_index(); + while(index != 0xffffffff && !channel_mapping_available(speaker, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_previous_target_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; + } + + uint32_t index = find_node_index(node); + while(index != 0xffffffff) { + index = find_previous_target_node_index(index); + if(index != 0xffffffff && channel_mapping_available(speaker, guest_channel, info.m_nodes[index].m_id, node_channel)) + return info.m_nodes[index].m_id; + } + + if(info.m_default_sink != 0 && channel_mapping_available(speaker, guest_channel, 0, node_channel)) + return 0; + + index = find_last_target_node_index(); + while(index != 0xffffffff && !channel_mapping_available(speaker, guest_channel, info.m_nodes[index].m_id, node_channel)) + index = find_previous_target_node_index(index); + return index == 0xffffffff ? 0xffffffff : info.m_nodes[index].m_id; +} + +float menu_audio_mixer::quantize_db(float db) +{ + if(db >= 12.0) + return 12.0; + if(db >= -12.0) + return floor(db*2 + 0.5) / 2; + if(db >= -24.0) + return floor(db + 0.5); + if(db >= -48.0) + return floor(db/2 + 0.5) * 2; + if(db >= -96.0) + return floor(db/4 + 0.5) * 4; + return -96.0; +} + +float menu_audio_mixer::inc_db(float db) +{ + db = quantize_db(db); + if(db >= 12) + return 12.0; + if(db >= -12.0) + return db + 0.5; + if(db >= -24.0) + return db + 1; + if(db >= -48.0) + return db + 2; + if(db >= -96.0 + 4) + return db + 4; + return -96.0 + 4; + +} + +float menu_audio_mixer::dec_db(float db) +{ + db = quantize_db(db); + if(db >= 12.5) + return 11.5; + if(db > -12.0) + return db - 0.5; + if(db > -24.0) + return db - 1; + if(db > -48.0) + return db - 2; + if(db >= -92.0) + return db - 4; + return -96.0; +} + +} // namespace ui + diff --git a/src/frontend/mame/ui/audiomix.h b/src/frontend/mame/ui/audiomix.h new file mode 100644 index 0000000000000..93e58e2dad97c --- /dev/null +++ b/src/frontend/mame/ui/audiomix.h @@ -0,0 +1,91 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + ui/audiomix.h + + Audio mixing/mapping control + +***************************************************************************/ + +#ifndef MAME_FRONTEND_UI_AUDIOMIX_H +#define MAME_FRONTEND_UI_AUDIOMIX_H + +#pragma once + +#include "ui/menu.h" + + +namespace ui { + +class menu_audio_mixer : public menu +{ +public: + menu_audio_mixer(mame_ui_manager &mui, render_container &container); + virtual ~menu_audio_mixer() override; + +protected: + virtual void recompute_metrics(uint32_t width, uint32_t height, float aspect) override; + virtual void custom_render(void *selectedref, float top, float bottom, float x, float y, float x2, float y2) override; + virtual void menu_activated() override; + virtual void menu_deactivated() override; + +private: + enum { + MT_UNDEFINED, // At startup + MT_NONE, // [no mapping] + MT_FULL, // Full mapping to node + MT_CHANNEL, // Channel-to-channel mapping + MT_INTERNAL // Go back to previous menu or other non-mapping entry + }; + + enum { + GRP_GUEST_CHANNEL, + GRP_NODE, + GRP_NODE_CHANNEL, + GRP_DB + }; + + struct select_entry { + u32 m_maptype; + speaker_device *m_speaker; + u32 m_guest_channel; + u32 m_node; + u32 m_node_channel; + float m_db; + + inline bool operator ==(const select_entry &sel) { + return sel.m_maptype == m_maptype && sel.m_speaker == m_speaker && sel.m_guest_channel == m_guest_channel && sel.m_node == m_node && sel.m_node_channel == m_node_channel; + } + }; + + uint32_t m_generation; + select_entry m_current_selection; + uint32_t m_current_group; + std::vector m_selections; + + virtual void populate() override; + virtual bool handle(event const *ev) override; + + uint32_t find_node_index(uint32_t node) const; + std::string find_node_name(uint32_t node) const; + uint32_t find_node_channel_count(uint32_t node) const; + uint32_t find_next_target_node_index(uint32_t index) const; + uint32_t find_previous_target_node_index(uint32_t index) const; + uint32_t find_first_target_node_index() const; + uint32_t find_last_target_node_index() const; + bool full_mapping_available(speaker_device *speaker, uint32_t node) const; + bool channel_mapping_available(speaker_device *speaker, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const; + uint32_t find_next_available_target_node(speaker_device *speaker, uint32_t node) const; + uint32_t find_previous_available_target_node(speaker_device *speaker, uint32_t node) const; + uint32_t find_next_available_channel_target_node(speaker_device *speaker, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const; + uint32_t find_previous_available_channel_target_node(speaker_device *speaker, uint32_t guest_channel, uint32_t node, uint32_t node_channel) const; + + static float quantize_db(float db); + static float inc_db(float db); + static float dec_db(float db); +}; + +} // namespace ui + +#endif // MAME_FRONTEND_UI_AUDIOMIX_H diff --git a/src/frontend/mame/ui/mainmenu.cpp b/src/frontend/mame/ui/mainmenu.cpp index 22a2c01f6c1ca..664cfac27448d 100644 --- a/src/frontend/mame/ui/mainmenu.cpp +++ b/src/frontend/mame/ui/mainmenu.cpp @@ -12,6 +12,7 @@ #include "ui/mainmenu.h" #include "ui/about.h" +#include "ui/audiomix.h" #include "ui/barcode.h" #include "ui/cheatopt.h" #include "ui/confswitch.h" @@ -56,6 +57,7 @@ enum : unsigned { TAPE_CONTROL, SLOT_DEVICES, NETWORK_DEVICES, + AUDIO_MIXER, SLIDERS, VIDEO_TARGETS, CROSSHAIR, @@ -157,6 +159,8 @@ void menu_main::populate() if (network_interface_enumerator(machine().root_device()).first() != nullptr) item_append(_("menu-main", "Network Devices"), 0, (void*)NETWORK_DEVICES); + item_append(_("menu-main", "Audio Mixer"), 0, (void *)AUDIO_MIXER); + item_append(_("menu-main", "Slider Controls"), 0, (void *)SLIDERS); item_append(_("menu-main", "Video Options"), 0, (void *)VIDEO_TARGETS); @@ -262,6 +266,10 @@ bool menu_main::handle(event const *ev) menu::stack_push(ui(), container()); break; + case AUDIO_MIXER: + menu::stack_push(ui(), container()); + break; + case SLIDERS: menu::stack_push(ui(), container(), false); break; diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp index 09a8d24bdd1b2..0b79b41d3d0da 100644 --- a/src/frontend/mame/ui/ui.cpp +++ b/src/frontend/mame/ui/ui.cpp @@ -1494,14 +1494,6 @@ std::vector mame_ui_manager::slider_init(running_machine &machine slider_alloc(std::move(str), 0, 1000, 4000, 20, std::bind(&mame_ui_manager::slider_mixervol, this, item, _1, _2)); } - // add speaker panning - for (speaker_device &speaker : speaker_device_enumerator(machine.root_device())) - { - int defpan = floorf(speaker.defpan() * 1000.0f + 0.5f); - std::string str = string_format(_("%s '%s' Panning"), speaker.name(), speaker.tag()); - slider_alloc(std::move(str), -1000, defpan, 1000, 20, std::bind(&mame_ui_manager::slider_panning, this, std::ref(speaker), _1, _2)); - } - // add analog adjusters for (auto &port : machine.ioport().ports()) { @@ -1642,9 +1634,9 @@ std::vector mame_ui_manager::slider_init(running_machine &machine int32_t mame_ui_manager::slider_volume(std::string *str, int32_t newval) { if (newval != SLIDER_NOCHANGE) - machine().sound().set_attenuation(newval); + machine().sound().set_volume(newval); - int32_t curval = machine().sound().attenuation(); + int32_t curval = machine().sound().volume(); if (str) *str = string_format(_(u8"%1$3d\u00a0dB"), curval); @@ -1681,45 +1673,6 @@ int32_t mame_ui_manager::slider_mixervol(int item, std::string *str, int32_t new } -//------------------------------------------------- -// slider_panning - speaker panning slider -// callback -//------------------------------------------------- - -int32_t mame_ui_manager::slider_panning(speaker_device &speaker, std::string *str, int32_t newval) -{ - if (newval != SLIDER_NOCHANGE) - speaker.set_pan(float(newval) * 0.001f); - - int32_t curval = floorf(speaker.pan() * 1000.0f + 0.5f); - if (str) - { - switch (curval) - { - // preset strings for exact center/left/right - case 0: - *str = _("Center"); - break; - - case -1000: - *str = _("Left"); - break; - - case 1000: - *str = _("Right"); - break; - - // otherwise show as floating point - default: - *str = string_format(_("%1$.3f"), float(curval) * 0.001f); - break; - } - } - - return curval; -} - - //------------------------------------------------- // slider_adjuster - analog adjuster slider // callback diff --git a/src/mame/atari/atarijsa.cpp b/src/mame/atari/atarijsa.cpp index 6886b2efe9078..44d80aa609e62 100644 --- a/src/mame/atari/atarijsa.cpp +++ b/src/mame/atari/atarijsa.cpp @@ -799,16 +799,16 @@ void atari_jsa_i_device::device_add_mconfig(machine_config &config) YM2151(config, m_ym2151, JSA_MASTER_CLOCK); m_ym2151->irq_handler().set(FUNC(atari_jsa_i_device::ym2151_irq_gen)); m_ym2151->port_write_handler().set(FUNC(atari_jsa_base_device::ym2151_port_w)); - m_ym2151->add_route(0, *this, 0.60, AUTO_ALLOC_INPUT, 0); - m_ym2151->add_route(1, *this, 0.60, AUTO_ALLOC_INPUT, 1); + m_ym2151->add_route(0, *this, 0.60, 0); + m_ym2151->add_route(1, *this, 0.60, 1); POKEY(config, m_pokey, JSA_MASTER_CLOCK/2); - m_pokey->add_route(ALL_OUTPUTS, *this, 0.40, AUTO_ALLOC_INPUT, 0); - m_pokey->add_route(ALL_OUTPUTS, *this, 0.40, AUTO_ALLOC_INPUT, 1); + m_pokey->add_route(ALL_OUTPUTS, *this, 0.40, 0); + m_pokey->add_route(ALL_OUTPUTS, *this, 0.40, 1); TMS5220C(config, m_tms5220, JSA_MASTER_CLOCK*2/11); // potentially JSA_MASTER_CLOCK/9 as well - m_tms5220->add_route(ALL_OUTPUTS, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_tms5220->add_route(ALL_OUTPUTS, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_tms5220->add_route(ALL_OUTPUTS, *this, 1.0, 0); + m_tms5220->add_route(ALL_OUTPUTS, *this, 1.0, 1); } @@ -930,10 +930,10 @@ void atari_jsa_ii_device::device_add_mconfig(machine_config &config) YM2151(config, m_ym2151, JSA_MASTER_CLOCK); m_ym2151->irq_handler().set(FUNC(atari_jsa_ii_device::ym2151_irq_gen)); m_ym2151->port_write_handler().set(FUNC(atari_jsa_base_device::ym2151_port_w)); - m_ym2151->add_route(ALL_OUTPUTS, *this, 0.60, AUTO_ALLOC_INPUT, 0); + m_ym2151->add_route(ALL_OUTPUTS, *this, 0.60, 0); OKIM6295(config, m_oki1, JSA_MASTER_CLOCK/3, okim6295_device::PIN7_HIGH); - m_oki1->add_route(ALL_OUTPUTS, *this, 0.75, AUTO_ALLOC_INPUT, 0); + m_oki1->add_route(ALL_OUTPUTS, *this, 0.75, 0); } @@ -1013,11 +1013,11 @@ void atari_jsa_iii_device::device_add_mconfig(machine_config &config) YM2151(config, m_ym2151, JSA_MASTER_CLOCK); m_ym2151->irq_handler().set(FUNC(atari_jsa_iii_device::ym2151_irq_gen)); m_ym2151->port_write_handler().set(FUNC(atari_jsa_base_device::ym2151_port_w)); - m_ym2151->add_route(ALL_OUTPUTS, *this, 0.60, AUTO_ALLOC_INPUT, 0); + m_ym2151->add_route(ALL_OUTPUTS, *this, 0.60, 0); OKIM6295(config, m_oki1, JSA_MASTER_CLOCK/3, okim6295_device::PIN7_HIGH); m_oki1->set_addrmap(0, &atari_jsa_iii_device::jsa3_oki1_map); - m_oki1->add_route(ALL_OUTPUTS, *this, 0.75, AUTO_ALLOC_INPUT, 0); + m_oki1->add_route(ALL_OUTPUTS, *this, 0.75, 0); } @@ -1057,10 +1057,10 @@ void atari_jsa_iiis_device::device_add_mconfig(machine_config &config) atari_jsa_iii_device::device_add_mconfig(config); m_ym2151->reset_routes(); - m_ym2151->add_route(0, *this, 0.60, AUTO_ALLOC_INPUT, 0); - m_ym2151->add_route(1, *this, 0.60, AUTO_ALLOC_INPUT, 1); + m_ym2151->add_route(0, *this, 0.60, 0); + m_ym2151->add_route(1, *this, 0.60, 1); OKIM6295(config, m_oki2, JSA_MASTER_CLOCK/3, okim6295_device::PIN7_HIGH); - m_oki2->add_route(ALL_OUTPUTS, *this, 0.75, AUTO_ALLOC_INPUT, 1); + m_oki2->add_route(ALL_OUTPUTS, *this, 0.75, 1); m_oki2->set_addrmap(0, &atari_jsa_iiis_device::jsa3_oki2_map); } diff --git a/src/mame/atari/atarisac.cpp b/src/mame/atari/atarisac.cpp index ac21f8ed86df0..6044c09d3afdc 100644 --- a/src/mame/atari/atarisac.cpp +++ b/src/mame/atari/atarisac.cpp @@ -265,12 +265,12 @@ void atari_sac_device::device_add_mconfig(machine_config &config) YM2151(config, m_ym2151, 14.318181_MHz_XTAL/4); m_ym2151->irq_handler().set(FUNC(atari_sac_device::ym2151_irq_gen)); m_ym2151->port_write_handler().set(FUNC(atari_sac_device::ym2151_port_w)); - m_ym2151->add_route(0, *this, 0.60, AUTO_ALLOC_INPUT, 0); - m_ym2151->add_route(1, *this, 0.60, AUTO_ALLOC_INPUT, 1); + m_ym2151->add_route(0, *this, 0.60, 0); + m_ym2151->add_route(1, *this, 0.60, 1); // FIXME: there is actually only one DAC (plus some analog switches) - AM6012(config, m_rdac).add_route(ALL_OUTPUTS, *this, 0.5, AUTO_ALLOC_INPUT, 1); // AM6012.6j - AM6012(config, m_ldac).add_route(ALL_OUTPUTS, *this, 0.5, AUTO_ALLOC_INPUT, 0); // AM6012.6j + AM6012(config, m_rdac).add_route(ALL_OUTPUTS, *this, 0.5, 1); // AM6012.6j + AM6012(config, m_ldac).add_route(ALL_OUTPUTS, *this, 0.5, 0); // AM6012.6j } diff --git a/src/mame/hp/hp80.cpp b/src/mame/hp/hp80.cpp index 3fa3bcaa22dc7..ed49a731ac923 100644 --- a/src/mame/hp/hp80.cpp +++ b/src/mame/hp/hp80.cpp @@ -275,8 +275,8 @@ void hp80_base_state::hp80_base(machine_config &config) // Beeper SPEAKER(config, "mono").front_center(); - DAC_1BIT(config, m_dac , 0).add_route(ALL_OUTPUTS, "mono", 0.5, AUTO_ALLOC_INPUT, 0); - BEEP(config, m_beep, CPU_CLOCK / 512).add_route(ALL_OUTPUTS, "mono", 0.5, AUTO_ALLOC_INPUT, 0); + DAC_1BIT(config, m_dac , 0).add_route(ALL_OUTPUTS, "mono", 0.5, 0); + BEEP(config, m_beep, CPU_CLOCK / 512).add_route(ALL_OUTPUTS, "mono", 0.5, 0); // Optional ROMs for (auto& finder : m_rom_drawers) { diff --git a/src/mame/hp/hp_ipc.cpp b/src/mame/hp/hp_ipc.cpp index db116a4f7d70b..2530c46282f0a 100644 --- a/src/mame/hp/hp_ipc.cpp +++ b/src/mame/hp/hp_ipc.cpp @@ -797,7 +797,7 @@ void hp_ipc_state::hp_ipc_base(machine_config &config) // Beeper COP452(config , m_spkr , 2_MHz_XTAL); SPEAKER(config, "mono").front_center(); - DAC_1BIT(config, m_dac , 0).add_route(ALL_OUTPUTS, "mono", 0.5, AUTO_ALLOC_INPUT, 0); + DAC_1BIT(config, m_dac , 0).add_route(ALL_OUTPUTS, "mono", 0.5, 0); m_spkr->oa_w().set(m_dac , FUNC(dac_1bit_device::write)); // IO slots diff --git a/src/mame/jaleco/jaleco_vj_pc.cpp b/src/mame/jaleco/jaleco_vj_pc.cpp index 234fce72cd540..5528f94420207 100644 --- a/src/mame/jaleco/jaleco_vj_pc.cpp +++ b/src/mame/jaleco/jaleco_vj_pc.cpp @@ -95,8 +95,8 @@ void jaleco_vj_pc_device::sound_config(device_t &device) { jaleco_vj_isa16_sound_device &sound = downcast(device); sound.set_steppingstage_mode(m_is_steppingstage); - sound.add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - sound.add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + sound.add_route(0, *this, 1.0, 0); + sound.add_route(1, *this, 1.0, 1); } void jaleco_vj_pc_device::boot_state_w(uint8_t data) diff --git a/src/mame/jaleco/jaleco_vj_sound.cpp b/src/mame/jaleco/jaleco_vj_sound.cpp index da22c550fb8da..658c445b54dc2 100644 --- a/src/mame/jaleco/jaleco_vj_sound.cpp +++ b/src/mame/jaleco/jaleco_vj_sound.cpp @@ -260,12 +260,12 @@ void jaleco_vj_isa16_sound_device::device_add_mconfig(machine_config &config) // BGM normal YMZ280B(config, m_ymz[0], 16.9344_MHz_XTAL); m_ymz[0]->set_addrmap(0, &jaleco_vj_isa16_sound_device::ymz280b_map); - m_ymz[0]->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 0); + m_ymz[0]->add_route(1, *this, 1.0, 0); // BGM subwoofer YMZ280B(config, m_ymz[1], 16.9344_MHz_XTAL); m_ymz[1]->set_addrmap(0, &jaleco_vj_isa16_sound_device::ymz280b_map2); - m_ymz[1]->add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_ymz[1]->add_route(1, *this, 1.0, 1); } void jaleco_vj_isa16_sound_device::device_start() diff --git a/src/mame/konami/firebeat.cpp b/src/mame/konami/firebeat.cpp index 426cf46e11f16..f3bdc81e67d35 100644 --- a/src/mame/konami/firebeat.cpp +++ b/src/mame/konami/firebeat.cpp @@ -1456,10 +1456,10 @@ void firebeat_bm3_state::firebeat_bm3(machine_config &config) m_rf5c400->add_route(3, "rspeaker", 0.5); KONAMI_FIREBEAT_EXTEND_SPECTRUM_ANALYZER(config, m_spectrum_analyzer, 0); - m_rf5c400->add_route(0, m_spectrum_analyzer, 0.5, AUTO_ALLOC_INPUT, 0); - m_rf5c400->add_route(1, m_spectrum_analyzer, 0.5, AUTO_ALLOC_INPUT, 1); - m_rf5c400->add_route(2, m_spectrum_analyzer, 0.5, AUTO_ALLOC_INPUT, 0); - m_rf5c400->add_route(3, m_spectrum_analyzer, 0.5, AUTO_ALLOC_INPUT, 1); + m_rf5c400->add_route(0, m_spectrum_analyzer, 0.5, 0); + m_rf5c400->add_route(1, m_spectrum_analyzer, 0.5, 1); + m_rf5c400->add_route(2, m_spectrum_analyzer, 0.5, 0); + m_rf5c400->add_route(3, m_spectrum_analyzer, 0.5, 1); } void firebeat_bm3_state::init_bm3() diff --git a/src/mame/konami/gijoe.cpp b/src/mame/konami/gijoe.cpp index ae14b8c7c8481..75ae298489c5d 100644 --- a/src/mame/konami/gijoe.cpp +++ b/src/mame/konami/gijoe.cpp @@ -542,15 +542,14 @@ void gijoe_state::gijoe(machine_config &config) K053251(config, m_k053251, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - K054321(config, m_k054321, "lspeaker", "rspeaker"); + K054321(config, m_k054321, "speaker"); k054539_device &k054539(K054539(config, "k054539", XTAL(18'432'000))); k054539.timer_handler().set_inputline("audiocpu", INPUT_LINE_NMI); - k054539.add_route(0, "rspeaker", 1.0); - k054539.add_route(1, "lspeaker", 1.0); + k054539.add_route(0, "speaker", 1.0, 1); + k054539.add_route(1, "speaker", 1.0, 0); } diff --git a/src/mame/konami/lethal.cpp b/src/mame/konami/lethal.cpp index 3c42cc1700121..cbd38b7afaee5 100644 --- a/src/mame/konami/lethal.cpp +++ b/src/mame/konami/lethal.cpp @@ -691,15 +691,14 @@ void lethal_state::lethalen(machine_config &config) K054000(config, "k054000", 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - K054321(config, m_k054321, "lspeaker", "rspeaker"); + K054321(config, m_k054321, "speaker"); k054539_device &k054539(K054539(config, "k054539", SOUND_CLOCK)); k054539.timer_handler().set_inputline("soundcpu", INPUT_LINE_NMI); - k054539.add_route(0, "rspeaker", 1.0); - k054539.add_route(1, "lspeaker", 1.0); + k054539.add_route(0, "speaker", 1.0, 0); + k054539.add_route(1, "speaker", 1.0, 1); } void lethal_state::lethalej(machine_config &config) diff --git a/src/mame/konami/moo.cpp b/src/mame/konami/moo.cpp index fc36490c79d0a..63fe3e99a418c 100644 --- a/src/mame/konami/moo.cpp +++ b/src/mame/konami/moo.cpp @@ -754,16 +754,15 @@ void moo_state::moo(machine_config &config) K054338(config, m_k054338, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - K054321(config, m_k054321, "lspeaker", "rspeaker"); + K054321(config, m_k054321, "speaker"); - YM2151(config, "ymsnd", XTAL(32'000'000)/8).add_route(0, "lspeaker", 0.50).add_route(1, "rspeaker", 0.50); // 4MHz verified + YM2151(config, "ymsnd", XTAL(32'000'000)/8).add_route(0, "speaker", 0.50, 0).add_route(1, "speaker", 0.50, 1); // 4MHz verified K054539(config, m_k054539, XTAL(18'432'000)); - m_k054539->add_route(0, "rspeaker", 0.75); - m_k054539->add_route(1, "lspeaker", 0.75); + m_k054539->add_route(0, "speaker", 0.75, 0); + m_k054539->add_route(1, "speaker", 0.75, 1); } void moo_state::moobl(machine_config &config) diff --git a/src/mame/konami/mystwarr.cpp b/src/mame/konami/mystwarr.cpp index fb12f79dea821..5e8291611f1be 100644 --- a/src/mame/konami/mystwarr.cpp +++ b/src/mame/konami/mystwarr.cpp @@ -1008,21 +1008,20 @@ void mystwarr_state::mystwarr(machine_config &config) MCFG_VIDEO_START_OVERRIDE(mystwarr_state, mystwarr) /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - K054321(config, m_k054321, "lspeaker", "rspeaker"); + K054321(config, m_k054321, "speaker"); K054539(config, m_k054539_1, XTAL(18'432'000)); m_k054539_1->set_device_rom_tag("k054539"); m_k054539_1->timer_handler().set(FUNC(mystwarr_state::k054539_nmi_gen)); - m_k054539_1->add_route(0, "rspeaker", 1.0); /* stereo channels are inverted */ - m_k054539_1->add_route(1, "lspeaker", 1.0); + m_k054539_1->add_route(0, "speaker", 1.0, 1); /* stereo channels are inverted */ + m_k054539_1->add_route(1, "speaker", 1.0, 0); K054539(config, m_k054539_2, XTAL(18'432'000)); m_k054539_2->set_device_rom_tag("k054539"); - m_k054539_2->add_route(0, "rspeaker", 1.0); /* stereo channels are inverted */ - m_k054539_2->add_route(1, "lspeaker", 1.0); + m_k054539_2->add_route(0, "speaker", 1.0, 1); /* stereo channels are inverted */ + m_k054539_2->add_route(1, "speaker", 1.0, 0); } void mystwarr_state::viostorm(machine_config &config) @@ -1069,8 +1068,8 @@ void mystwarr_state::viostormbl(machine_config &config) okim6295_device &oki(OKIM6295(config, "oki", 1'056'000, okim6295_device::PIN7_HIGH)); // frequency and pin 7 unverified oki.set_addrmap(0, &mystwarr_state::oki_map); - oki.add_route(ALL_OUTPUTS, "lspeaker", 1.0); - oki.add_route(ALL_OUTPUTS, "rspeaker", 1.0); + oki.add_route(ALL_OUTPUTS, "speaker", 1.0, 0); + oki.add_route(ALL_OUTPUTS, "speaker", 1.0, 1); } void mystwarr_state::metamrph(machine_config &config) diff --git a/src/mame/konami/plygonet.cpp b/src/mame/konami/plygonet.cpp index c91408f84262d..6c0711b1d832d 100644 --- a/src/mame/konami/plygonet.cpp +++ b/src/mame/konami/plygonet.cpp @@ -1077,15 +1077,14 @@ void polygonet_state::plygonet(machine_config &config) m_k053936->set_wrap(true); // Sound hardware - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - K054321(config, m_k054321, "lspeaker", "rspeaker"); + K054321(config, m_k054321, "speaker"); k054539_device &k054539(K054539(config, "k054539", XTAL(18'432'000))); k054539.timer_handler().set(FUNC(polygonet_state::k054539_nmi_gen)); - k054539.add_route(0, "lspeaker", 0.75); - k054539.add_route(1, "rspeaker", 0.75); + k054539.add_route(0, "speaker", 0.75, 0); + k054539.add_route(1, "speaker", 0.75, 1); } diff --git a/src/mame/konami/rungun.cpp b/src/mame/konami/rungun.cpp index 6c3316b8dad91..80ac3e9070dd8 100644 --- a/src/mame/konami/rungun.cpp +++ b/src/mame/konami/rungun.cpp @@ -689,23 +689,22 @@ void rungun_state::rng(machine_config &config) m_palette->enable_hilights(); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - K054321(config, m_k054321, "lspeaker", "rspeaker"); + K054321(config, m_k054321, "speaker"); // SFX K054539(config, m_k054539_1, 18.432_MHz_XTAL); m_k054539_1->set_device_rom_tag("k054539"); m_k054539_1->timer_handler().set(FUNC(rungun_state::k054539_nmi_gen)); - m_k054539_1->add_route(0, "rspeaker", 1.0); - m_k054539_1->add_route(1, "lspeaker", 1.0); + m_k054539_1->add_route(0, "speaker", 1.0, 1); + m_k054539_1->add_route(1, "speaker", 1.0, 0); // BGM, volumes handtuned to make SFXs audible (still not 100% right tho) K054539(config, m_k054539_2, 18.432_MHz_XTAL); m_k054539_2->set_device_rom_tag("k054539"); - m_k054539_2->add_route(0, "rspeaker", 0.6); - m_k054539_2->add_route(1, "lspeaker", 0.6); + m_k054539_2->add_route(0, "speaker", 0.6, 1); + m_k054539_2->add_route(1, "speaker", 0.6, 0); } // for dual-screen output Run and Gun requires the video de-multiplexer board connected to the Jamma output, this gives you 2 Jamma connectors, one for each screen. diff --git a/src/mame/konami/tmnt2.cpp b/src/mame/konami/tmnt2.cpp index 064026ecca4da..9e484d4145e9f 100644 --- a/src/mame/konami/tmnt2.cpp +++ b/src/mame/konami/tmnt2.cpp @@ -2435,14 +2435,13 @@ void tmnt2_state::lgtnfght(machine_config &config) K053251(config, m_k053251, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "lspeaker", 1.0).add_route(1, "rspeaker", 1.0); + YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "speaker", 1.0, 0).add_route(1, "speaker", 1.0, 1); K053260(config, m_k053260, XTAL(3'579'545)); - m_k053260->add_route(0, "lspeaker", 0.70); - m_k053260->add_route(1, "rspeaker", 0.70); + m_k053260->add_route(0, "speaker", 0.70, 0); + m_k053260->add_route(1, "speaker", 0.70, 1); m_k053260->sh1_cb().set(FUNC(tmnt2_state::z80_nmi_w)); } @@ -2491,14 +2490,13 @@ void tmnt2_state::blswhstl(machine_config &config) K054000(config, m_k054000, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "lspeaker", 0.70).add_route(1, "rspeaker", 0.70); + YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "speaker", 0.70, 0).add_route(1, "speaker", 0.70, 1); K053260(config, m_k053260, XTAL(3'579'545)); - m_k053260->add_route(0, "rspeaker", 0.50); /* fixed inverted stereo channels */ - m_k053260->add_route(1, "lspeaker", 0.50); + m_k053260->add_route(0, "speaker", 0.50, 1); /* fixed inverted stereo channels */ + m_k053260->add_route(1, "speaker", 0.50, 0); m_k053260->sh1_cb().set(FUNC(tmnt2_state::z80_nmi_w)); } @@ -2558,12 +2556,11 @@ void glfgreat_state::glfgreat(machine_config &config) K053251(config, m_k053251, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); K053260(config, m_k053260, XTAL(3'579'545)); - m_k053260->add_route(0, "lspeaker", 1.0); - m_k053260->add_route(1, "rspeaker", 1.0); + m_k053260->add_route(0, "speaker", 1.0, 1); + m_k053260->add_route(1, "speaker", 1.0, 0); m_k053260->sh1_cb().set(FUNC(glfgreat_state::z80_nmi_w)); } @@ -2622,15 +2619,14 @@ void prmrsocr_state::prmrsocr(machine_config &config) K053251(config, m_k053251, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - K054321(config, "k054321", "lspeaker", "rspeaker"); + K054321(config, "k054321", "speaker"); K054539(config, m_k054539, XTAL(18'432'000)); m_k054539->timer_handler().set_inputline("audiocpu", INPUT_LINE_NMI); - m_k054539->add_route(0, "lspeaker", 1.0); - m_k054539->add_route(1, "rspeaker", 1.0); + m_k054539->add_route(0, "speaker", 1.0, 0); + m_k054539->add_route(1, "speaker", 1.0, 1); } void tmnt2_state::tmnt2(machine_config &config) @@ -2679,14 +2675,13 @@ void tmnt2_state::tmnt2(machine_config &config) K053251(config, m_k053251, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "lspeaker", 1.0).add_route(1, "rspeaker", 1.0); + YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "speaker", 1.0, 0).add_route(1, "speaker", 1.0, 1); K053260(config, m_k053260, XTAL(3'579'545)); - m_k053260->add_route(0, "lspeaker", 0.75); - m_k053260->add_route(1, "rspeaker", 0.75); + m_k053260->add_route(0, "speaker", 0.75, 0); + m_k053260->add_route(1, "speaker", 0.75, 1); m_k053260->sh1_cb().set(FUNC(tmnt2_state::z80_nmi_w)); } @@ -2733,14 +2728,13 @@ void tmnt2_state::ssriders(machine_config &config) K053251(config, m_k053251, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "lspeaker", 1.0).add_route(1, "rspeaker", 1.0); + YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "speaker", 1.0, 0).add_route(1, "speaker", 1.0, 1); K053260(config, m_k053260, XTAL(3'579'545)); - m_k053260->add_route(0, "lspeaker", 0.70); - m_k053260->add_route(1, "rspeaker", 0.70); + m_k053260->add_route(0, "speaker", 0.70, 0); + m_k053260->add_route(1, "speaker", 0.70, 1); m_k053260->sh1_cb().set(FUNC(tmnt2_state::z80_nmi_w)); } @@ -2794,12 +2788,11 @@ void sunsetbl_state::sunsetbl(machine_config &config) K053251(config, m_k053251, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); okim6295_device &oki(OKIM6295(config, "oki", 1056000, okim6295_device::PIN7_HIGH)); // clock frequency & pin 7 not verified - oki.add_route(ALL_OUTPUTS, "lspeaker", 1.0); - oki.add_route(ALL_OUTPUTS, "rspeaker", 1.0); + oki.add_route(ALL_OUTPUTS, "speaker", 1.0, 0); + oki.add_route(ALL_OUTPUTS, "speaker", 1.0, 1); } void tmnt2_state::thndrx2(machine_config &config) @@ -2843,14 +2836,13 @@ void tmnt2_state::thndrx2(machine_config &config) /* sound hardware */ // NB: game defaults in mono - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "lspeaker", 0.25).add_route(1, "rspeaker", 0.25); + YM2151(config, "ymsnd", XTAL(3'579'545)).add_route(0, "speaker", 0.25, 0).add_route(1, "speaker", 0.25, 1); K053260(config, m_k053260, XTAL(3'579'545)); - m_k053260->add_route(0, "lspeaker", 0.50); - m_k053260->add_route(1, "rspeaker", 0.50); + m_k053260->add_route(0, "speaker", 0.50, 0); + m_k053260->add_route(1, "speaker", 0.50, 1); m_k053260->sh1_cb().set(FUNC(tmnt2_state::z80_nmi_w)); } diff --git a/src/mame/konami/xexex.cpp b/src/mame/konami/xexex.cpp index 6116a15b182fa..884e6df57c07f 100644 --- a/src/mame/konami/xexex.cpp +++ b/src/mame/konami/xexex.cpp @@ -737,10 +737,9 @@ void xexex_state::xexex(machine_config &config) K054338(config, m_k054338, 0); /* sound hardware */ - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - K054321(config, m_k054321, "lspeaker", "rspeaker"); + K054321(config, m_k054321, "speaker"); ym2151_device &ymsnd(YM2151(config, "ymsnd", XTAL(32'000'000)/8)); // 4MHz ymsnd.add_route(0, "filter1_l", 0.2); @@ -750,15 +749,15 @@ void xexex_state::xexex(machine_config &config) K054539(config, m_k054539, XTAL(18'432'000)); m_k054539->set_analog_callback(FUNC(xexex_state::ym_set_mixing)); - m_k054539->add_route(0, "lspeaker", 0.4); - m_k054539->add_route(0, "rspeaker", 0.4); - m_k054539->add_route(1, "lspeaker", 0.4); - m_k054539->add_route(1, "rspeaker", 0.4); - - FILTER_VOLUME(config, "filter1_l").add_route(ALL_OUTPUTS, "lspeaker", 1.0); - FILTER_VOLUME(config, "filter1_r").add_route(ALL_OUTPUTS, "rspeaker", 1.0); - FILTER_VOLUME(config, "filter2_l").add_route(ALL_OUTPUTS, "lspeaker", 1.0); - FILTER_VOLUME(config, "filter2_r").add_route(ALL_OUTPUTS, "rspeaker", 1.0); + m_k054539->add_route(0, "speaker", 0.4, 0); + m_k054539->add_route(0, "speaker", 0.4, 1); + m_k054539->add_route(1, "speaker", 0.4, 0); + m_k054539->add_route(1, "speaker", 0.4, 1); + + FILTER_VOLUME(config, "filter1_l").add_route(ALL_OUTPUTS, "speaker", 1.0, 0); + FILTER_VOLUME(config, "filter1_r").add_route(ALL_OUTPUTS, "speaker", 1.0, 1); + FILTER_VOLUME(config, "filter2_l").add_route(ALL_OUTPUTS, "speaker", 1.0, 0); + FILTER_VOLUME(config, "filter2_r").add_route(ALL_OUTPUTS, "speaker", 1.0, 1); } diff --git a/src/mame/konami/xmen.cpp b/src/mame/konami/xmen.cpp index 013ea020b4b1d..4beaad8300f18 100644 --- a/src/mame/konami/xmen.cpp +++ b/src/mame/konami/xmen.cpp @@ -642,16 +642,15 @@ void xmen_state::sound_hardware(machine_config &config) m_audiocpu->set_addrmap(AS_PROGRAM, &xmen_state::sound_map); // sound hardware - SPEAKER(config, "lspeaker").front_left(); - SPEAKER(config, "rspeaker").front_right(); + SPEAKER(config, "speaker", 2).front(); - K054321(config, "k054321", "lspeaker", "rspeaker"); + K054321(config, "k054321", "speaker"); - YM2151(config, "ymsnd", XTAL(16'000'000) / 4).add_route(0, "lspeaker", 0.20).add_route(1, "rspeaker", 0.20); // verified on PCB + YM2151(config, "ymsnd", XTAL(16'000'000) / 4).add_route(0, "speaker", 0.20, 1).add_route(1, "speaker", 0.20, 0); // verified on PCB k054539_device &k054539(K054539(config, "k054539", XTAL(18'432'000))); - k054539.add_route(0, "rspeaker", 1.00); - k054539.add_route(1, "lspeaker", 1.00); + k054539.add_route(0, "speaker", 1.00, 1); + k054539.add_route(1, "speaker", 1.00, 0); } void xmen_state::bootleg_sound_hardware(machine_config &config) diff --git a/src/mame/midway/midway.cpp b/src/mame/midway/midway.cpp index c32a0ea9c3600..86a91ff04445b 100644 --- a/src/mame/midway/midway.cpp +++ b/src/mame/midway/midway.cpp @@ -410,12 +410,12 @@ void midway_ssio_device::device_add_mconfig(machine_config &config) AY8910(config, m_ay0, DERIVED_CLOCK(1, 2*4)); m_ay0->port_a_write_callback().set(FUNC(midway_ssio_device::porta0_w)); m_ay0->port_b_write_callback().set(FUNC(midway_ssio_device::portb0_w)); - m_ay0->add_route(ALL_OUTPUTS, *this, 0.33, AUTO_ALLOC_INPUT, 0); + m_ay0->add_route(ALL_OUTPUTS, *this, 0.33, 0); AY8910(config, m_ay1, DERIVED_CLOCK(1, 2*4)); m_ay1->port_a_write_callback().set(FUNC(midway_ssio_device::porta1_w)); m_ay1->port_b_write_callback().set(FUNC(midway_ssio_device::portb1_w)); - m_ay1->add_route(ALL_OUTPUTS, *this, 0.33, AUTO_ALLOC_INPUT, 1); + m_ay1->add_route(ALL_OUTPUTS, *this, 0.33, 1); } diff --git a/src/mame/midway/seattle.cpp b/src/mame/midway/seattle.cpp index d9fb1f7de0f05..9d6a934427479 100644 --- a/src/mame/midway/seattle.cpp +++ b/src/mame/midway/seattle.cpp @@ -2153,20 +2153,17 @@ void seattle_state::sfrush(machine_config &config) { flagstaff(config); // 5 Channel output (4 Channel input connected to Quad Amp PCB) - SPEAKER(config, "flspeaker").front_left(); - SPEAKER(config, "frspeaker").front_right(); - SPEAKER(config, "rlspeaker").headrest_left(); - SPEAKER(config, "rrspeaker").headrest_right(); + SPEAKER(config, "speaker", 4).corners(); //SPEAKER(config, "subwoofer").seat(); Not implemented, Quad Amp PCB output; atari_cage_seattle_device &cage(ATARI_CAGE_SEATTLE(config, "cage", 0)); cage.set_speedup(0x5236); cage.irq_handler().set(m_ioasic, FUNC(midway_ioasic_device::cage_irq_handler)); // TODO: copied from atarigt.cpp; Same configurations as T-Mek? - cage.add_route(0, "frspeaker", 1.0); // Foward Right - cage.add_route(1, "rlspeaker", 1.0); // Back Left - cage.add_route(2, "flspeaker", 1.0); // Foward Left - cage.add_route(3, "rrspeaker", 1.0); // Back Right + cage.add_route(0, "speaker", 1.0, 1); // Forward Right + cage.add_route(1, "speaker", 1.0, 2); // Back Left + cage.add_route(2, "speaker", 1.0, 0); // Forward Left + cage.add_route(3, "speaker", 1.0, 3); // Back Right MIDWAY_IOASIC(config, m_ioasic, 0); m_ioasic->set_shuffle(MIDWAY_IOASIC_STANDARD); @@ -2180,21 +2177,17 @@ void seattle_state::sfrushrk(machine_config &config) { flagstaff(config); // 5 Channel output (4 Channel input connected to Quad Amp PCB) - SPEAKER(config, "flspeaker").front_left(); - SPEAKER(config, "frspeaker").front_right(); - SPEAKER(config, "rlspeaker").headrest_left(); - SPEAKER(config, "rrspeaker").headrest_right(); + SPEAKER(config, "speaker", 4).corners(); //SPEAKER(config, "subwoofer").seat(); Not implemented, Quad Amp PCB output; atari_cage_seattle_device &cage(ATARI_CAGE_SEATTLE(config, "cage", 0)); cage.set_speedup(0x5329); cage.irq_handler().set(m_ioasic, FUNC(midway_ioasic_device::cage_irq_handler)); // TODO: copied from atarigt.cpp; Same configurations as T-Mek? - cage.add_route(0, "frspeaker", 1.0); // Foward Right - cage.add_route(1, "rlspeaker", 1.0); // Back Left - cage.add_route(2, "flspeaker", 1.0); // Foward Left - cage.add_route(3, "rrspeaker", 1.0); // Back Right - + cage.add_route(0, "speaker", 1.0, 1); // Forward Right + cage.add_route(1, "speaker", 1.0, 2); // Back Left + cage.add_route(2, "speaker", 1.0, 0); // Forward Left + cage.add_route(3, "speaker", 1.0, 3); // Back Right MIDWAY_IOASIC(config, m_ioasic, 0); m_ioasic->set_shuffle(MIDWAY_IOASIC_SFRUSHRK); diff --git a/src/mame/nec/pce_cd.cpp b/src/mame/nec/pce_cd.cpp index 87ec67f30189c..9129af5b65f20 100644 --- a/src/mame/nec/pce_cd.cpp +++ b/src/mame/nec/pce_cd.cpp @@ -263,14 +263,14 @@ void pce_cd_device::device_add_mconfig(machine_config &config) MSM5205(config, m_msm, PCE_CD_CLOCK / 6); m_msm->vck_legacy_callback().set(FUNC(pce_cd_device::msm5205_int)); /* interrupt function */ m_msm->set_prescaler_selector(msm5205_device::S48_4B); /* 1/48 prescaler, 4bit data */ - m_msm->add_route(ALL_OUTPUTS, *this, 0.50, AUTO_ALLOC_INPUT, 0); - m_msm->add_route(ALL_OUTPUTS, *this, 0.50, AUTO_ALLOC_INPUT, 1); + m_msm->add_route(ALL_OUTPUTS, *this, 0.50, 0); + m_msm->add_route(ALL_OUTPUTS, *this, 0.50, 1); CDDA(config, m_cdda); m_cdda->set_cdrom_tag(m_cdrom); m_cdda->audio_end_cb().set(FUNC(pce_cd_device::cdda_end_mark_cb)); - m_cdda->add_route(0, *this, 1.00, AUTO_ALLOC_INPUT, 0); - m_cdda->add_route(1, *this, 1.00, AUTO_ALLOC_INPUT, 1); + m_cdda->add_route(0, *this, 1.00, 0); + m_cdda->add_route(1, *this, 1.00, 1); } void pce_cd_device::adpcm_stop(uint8_t irq_flag) diff --git a/src/mame/pinball/pinsnd88.cpp b/src/mame/pinball/pinsnd88.cpp index bcd66da5c405e..023142730fa65 100644 --- a/src/mame/pinball/pinsnd88.cpp +++ b/src/mame/pinball/pinsnd88.cpp @@ -208,16 +208,16 @@ void pinsnd88_device::device_add_mconfig(machine_config &config) // TODO: analog filters and "volume" controls for the two channels AD7224(config, m_dac, 0); - m_dac->add_route(ALL_OUTPUTS, *this, 0.41/2.0, AUTO_ALLOC_INPUT, 0); // 470K - m_dac->add_route(ALL_OUTPUTS, *this, 0.5/2.0, AUTO_ALLOC_INPUT, 1); // 330K + m_dac->add_route(ALL_OUTPUTS, *this, 0.41/2.0, 0); // 470K + m_dac->add_route(ALL_OUTPUTS, *this, 0.5/2.0, 1); // 330K GENERIC_LATCH_8(config, m_inputlatch); m_inputlatch->data_pending_callback().set_inputline(m_cpu, M6809_IRQ_LINE); YM2151(config, m_ym2151, XTAL(3'579'545)); // "3.58 MHz" on schematics and parts list m_ym2151->irq_handler().set_inputline(m_cpu, M6809_FIRQ_LINE); // IRQ is not true state, but neither is the M6809_FIRQ_LINE so we're fine. - m_ym2151->add_route(ALL_OUTPUTS, *this, 0.59/2.0, AUTO_ALLOC_INPUT, 0); // 330K - m_ym2151->add_route(ALL_OUTPUTS, *this, 0.5/2.0, AUTO_ALLOC_INPUT, 1); // 330K + m_ym2151->add_route(ALL_OUTPUTS, *this, 0.59/2.0, 0); // 330K + m_ym2151->add_route(ALL_OUTPUTS, *this, 0.5/2.0, 1); // 330K } void pinsnd88_device::device_start() diff --git a/src/mame/pinball/s11b.cpp b/src/mame/pinball/s11b.cpp index 48655ffec88b1..771ff941a70bd 100644 --- a/src/mame/pinball/s11b.cpp +++ b/src/mame/pinball/s11b.cpp @@ -388,10 +388,10 @@ void s11b_state::s11b_jokerz(machine_config &config) PINSND88(config, m_ps88); // the dac and cvsd volumes should be equally mixed on the s11 board send to the audio board, whatever type it is // the 4 gain values in the add_route statements are actually irrelevant, the ps88 device will override them - m_dac->add_route(ALL_OUTPUTS, m_ps88, 0.29, AUTO_ALLOC_INPUT, 0); - m_dac->add_route(ALL_OUTPUTS, m_ps88, 0.25, AUTO_ALLOC_INPUT, 1); - m_cvsd_filter2->add_route(ALL_OUTPUTS, m_ps88, (0.29*4.0), AUTO_ALLOC_INPUT, 0); - m_cvsd_filter2->add_route(ALL_OUTPUTS, m_ps88, (0.25*4.0), AUTO_ALLOC_INPUT, 1); + m_dac->add_route(ALL_OUTPUTS, m_ps88, 0.29, 0); + m_dac->add_route(ALL_OUTPUTS, m_ps88, 0.25, 1); + m_cvsd_filter2->add_route(ALL_OUTPUTS, m_ps88, (0.29*4.0), 0); + m_cvsd_filter2->add_route(ALL_OUTPUTS, m_ps88, (0.25*4.0), 1); m_pia34->ca2_handler().set(m_ps88, FUNC(pinsnd88_device::resetq_w)); m_ps88->syncq_cb().set(m_pia34, FUNC(pia6821_device::ca1_w)); // the sync connection comes from sound connector pin 16 to MCA1, not the usual pin 12 to MCB1 SPEAKER(config, "cabinet").front_floor(); // the cabinet speaker is aimed down underneath the pinball table itself diff --git a/src/mame/seta/st0016.cpp b/src/mame/seta/st0016.cpp index 425263fe8d173..4a6144c90e691 100644 --- a/src/mame/seta/st0016.cpp +++ b/src/mame/seta/st0016.cpp @@ -145,8 +145,8 @@ void st0016_cpu_device::device_add_mconfig(machine_config &config) st0016_device &stsnd(ST0016(config, "stsnd", DERIVED_CLOCK(1,1))); stsnd.set_addrmap(0, &st0016_cpu_device::charam_map); - stsnd.add_route(0, *this, 1.0, AUTO_ALLOC_INPUT, 0); - stsnd.add_route(1, *this, 1.0, AUTO_ALLOC_INPUT, 1); + stsnd.add_route(0, *this, 1.0, 0); + stsnd.add_route(1, *this, 1.0, 1); } diff --git a/src/mame/shared/ballysound.cpp b/src/mame/shared/ballysound.cpp index a9c6ce4d30fda..793f8cf498e1d 100644 --- a/src/mame/shared/ballysound.cpp +++ b/src/mame/shared/ballysound.cpp @@ -125,7 +125,7 @@ TIMER_CALLBACK_MEMBER(bally_as2888_device::sound_int_sync) void bally_as2888_device::device_add_mconfig(machine_config &config) { DISCRETE(config, m_discrete, as2888_discrete); - m_discrete->add_route(ALL_OUTPUTS, *this, 1.00, AUTO_ALLOC_INPUT, 0); + m_discrete->add_route(ALL_OUTPUTS, *this, 1.00, 0); TIMER(config, "timer_s_freq").configure_periodic(FUNC(bally_as2888_device::timer_s), attotime::from_hz(353000)); // Inverter clock on AS-2888 sound board TIMER(config, m_snd_sustain_timer).configure_generic(FUNC(bally_as2888_device::timer_as2888)); @@ -270,7 +270,7 @@ void bally_as3022_device::device_add_mconfig(machine_config &config) m_ay->add_route(1, "ay_filter1", 0.33); m_ay->add_route(2, "ay_filter2", 0.33); m_ay->port_a_read_callback().set(FUNC(bally_as3022_device::ay_io_r)); - m_ay->add_route(ALL_OUTPUTS, *this, 0.33, AUTO_ALLOC_INPUT, 0); + m_ay->add_route(ALL_OUTPUTS, *this, 0.33, 0); } @@ -528,7 +528,7 @@ void bally_cheap_squeak_device::device_add_mconfig(machine_config &config) m_cpu->in_p2_cb().set(FUNC(bally_cheap_squeak_device::in_p2_cb)); m_cpu->out_p2_cb().set(FUNC(bally_cheap_squeak_device::out_p2_cb)); - ZN429E(config, "dac", 0).add_route(ALL_OUTPUTS, *this, 1.00, AUTO_ALLOC_INPUT, 0); + ZN429E(config, "dac", 0).add_route(ALL_OUTPUTS, *this, 1.00, 0); } //------------------------------------------------- diff --git a/src/mame/shared/cage.cpp b/src/mame/shared/cage.cpp index e87188361ee8c..5a77d463c27d9 100644 --- a/src/mame/shared/cage.cpp +++ b/src/mame/shared/cage.cpp @@ -693,19 +693,19 @@ void atari_cage_device::device_add_mconfig(machine_config &config) GENERIC_LATCH_16(config, m_soundlatch); #if (DAC_BUFFER_CHANNELS == 4) - DMADAC(config, m_dmadac[0]).add_route(ALL_OUTPUTS, *this, 0.50, AUTO_ALLOC_INPUT, 0); + DMADAC(config, m_dmadac[0]).add_route(ALL_OUTPUTS, *this, 0.50, 0); - DMADAC(config, m_dmadac[1]).add_route(ALL_OUTPUTS, *this, 0.50, AUTO_ALLOC_INPUT, 1); + DMADAC(config, m_dmadac[1]).add_route(ALL_OUTPUTS, *this, 0.50, 1); - DMADAC(config, m_dmadac[2]).add_route(ALL_OUTPUTS, *this, 0.50, AUTO_ALLOC_INPUT, 2); + DMADAC(config, m_dmadac[2]).add_route(ALL_OUTPUTS, *this, 0.50, 2); - DMADAC(config, m_dmadac[3]).add_route(ALL_OUTPUTS, *this, 0.50, AUTO_ALLOC_INPUT, 3); + DMADAC(config, m_dmadac[3]).add_route(ALL_OUTPUTS, *this, 0.50, 3); #else - DMADAC(config, m_dmadac[0]).add_route(ALL_OUTPUTS, *this, 1.0, AUTO_ALLOC_INPUT, 0); + DMADAC(config, m_dmadac[0]).add_route(ALL_OUTPUTS, *this, 1.0, 0); - DMADAC(config, m_dmadac[1]).add_route(ALL_OUTPUTS, *this, 1.0, AUTO_ALLOC_INPUT, 1); + DMADAC(config, m_dmadac[1]).add_route(ALL_OUTPUTS, *this, 1.0, 1); #endif - //add_route(ALL_OUTPUTS, *this, 0.50, AUTO_ALLOC_INPUT, 4); Subwoofer output + //add_route(ALL_OUTPUTS, *this, 0.50, 4); Subwoofer output } // Embedded in San francisco Rush Motherboard, 4 channel output connected to Quad Amp PCB and expanded to 5 channel (4 channel + subwoofer) diff --git a/src/mame/sony/taito_zm.cpp b/src/mame/sony/taito_zm.cpp index 9e95ae49e9b3d..2ac0dfc538aed 100644 --- a/src/mame/sony/taito_zm.cpp +++ b/src/mame/sony/taito_zm.cpp @@ -198,8 +198,8 @@ void taito_zoom_device::device_add_mconfig(machine_config &config) m_tms57002->empty_callback().set_inputline(m_soundcpu, MN10200_IRQ1).invert(); m_tms57002->set_addrmap(AS_DATA, &taito_zoom_device::tms57002_map); - m_tms57002->add_route(2, *this, 1.0, AUTO_ALLOC_INPUT, 0); - m_tms57002->add_route(3, *this, 1.0, AUTO_ALLOC_INPUT, 1); + m_tms57002->add_route(2, *this, 1.0, 0); + m_tms57002->add_route(3, *this, 1.0, 1); ZSG2(config, m_zsg2, XTAL(25'000'000)); m_zsg2->add_route(0, *m_tms57002, 0.5, 0); // reverb effect diff --git a/src/mame/taito/taito_en.cpp b/src/mame/taito/taito_en.cpp index 50e534d421a07..c871295480597 100644 --- a/src/mame/taito/taito_en.cpp +++ b/src/mame/taito/taito_en.cpp @@ -241,8 +241,8 @@ void taito_en_device::device_add_mconfig(machine_config &config) /* sound hardware */ ESQ_5505_5510_PUMP(config, m_pump, XTAL(30'476'180) / (2 * 16 * 32)); m_pump->set_esp(m_esp); - m_pump->add_route(0, *this, 0.5, AUTO_ALLOC_INPUT, 0); - m_pump->add_route(1, *this, 0.5, AUTO_ALLOC_INPUT, 1); + m_pump->add_route(0, *this, 0.5, 0); + m_pump->add_route(1, *this, 0.5, 1); ES5505(config, m_ensoniq, XTAL(30'476'180) / 2); m_ensoniq->sample_rate_changed().set(FUNC(taito_en_device::es5505_clock_changed)); diff --git a/src/mame/taito/taito_z.cpp b/src/mame/taito/taito_z.cpp index b1edfb4650394..bc824985d3ce4 100644 --- a/src/mame/taito/taito_z.cpp +++ b/src/mame/taito/taito_z.cpp @@ -3218,7 +3218,7 @@ void contcirc_state::contcirc(machine_config &config) //OSC: 26.686, 24.000, 16. /* sound hardware */ SPEAKER(config, "front").front_center(); SPEAKER(config, "rear").rear_center(); - SPEAKER(config, "subwoofer").set_position(0.0, 0.0, 0.0); // FIXME: where is this speaker located? + SPEAKER(config, "subwoofer").set_position(0, 0.0, 0.0, 0.0); // FIXME: where is this speaker located? ym2610_device &ymsnd(YM2610(config, "ymsnd", XTAL(16'000'000)/2)); // 8 MHz ymsnd.irq_handler().set_inputline(m_audiocpu, 0); @@ -3277,7 +3277,7 @@ void chasehq_state::chasehq(machine_config &config) //OSC: 26.686, 24.000, 16.00 /* sound hardware */ SPEAKER(config, "front").front_center(); SPEAKER(config, "rear").rear_center(); - SPEAKER(config, "subwoofer").set_position(0.0, 0.0, 0.0); // FIXME: where is this speaker located? + SPEAKER(config, "subwoofer").set_position(0, 0.0, 0.0, 0.0); // FIXME: where is this speaker located? ym2610_device &ymsnd(YM2610(config, "ymsnd", XTAL(16'000'000)/2)); // 8 MHz ymsnd.irq_handler().set_inputline(m_audiocpu, 0); @@ -3539,7 +3539,7 @@ void nightstr_state::nightstr(machine_config &config) //OSC: 26.686, 24.000, 16. /* sound hardware */ SPEAKER(config, "front").front_center(); SPEAKER(config, "rear").rear_center(); - SPEAKER(config, "subwoofer").set_position(0.0, 0.0, 0.0); // FIXME: where is this located in the cabinet? + SPEAKER(config, "subwoofer").set_position(0, 0.0, 0.0, 0.0); // FIXME: where is this located in the cabinet? ym2610_device &ymsnd(YM2610(config, "ymsnd", XTAL(16'000'000)/2)); // 8 MHz ymsnd.irq_handler().set_inputline(m_audiocpu, 0); diff --git a/src/mame/virtual/vgmplay.cpp b/src/mame/virtual/vgmplay.cpp index cfcf5d84e7fc4..4b1c9f0088a94 100644 --- a/src/mame/virtual/vgmplay.cpp +++ b/src/mame/virtual/vgmplay.cpp @@ -3655,184 +3655,184 @@ void vgmplay_state::vgmplay(machine_config &config) config.set_default_layout(layout_vgmplay); SN76489(config, m_sn76489[0], 0); - m_sn76489[0]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_sn76489[0]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_sn76489[0]->add_route(0, m_mixer, 0.5, 0); + m_sn76489[0]->add_route(0, m_mixer, 0.5, 1); SN76489(config, m_sn76489[1], 0); - m_sn76489[1]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_sn76489[1]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_sn76489[1]->add_route(0, m_mixer, 0.5, 0); + m_sn76489[1]->add_route(0, m_mixer, 0.5, 1); YM2413(config, m_ym2413[0], 0); - m_ym2413[0]->add_route(ALL_OUTPUTS, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_ym2413[0]->add_route(ALL_OUTPUTS, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_ym2413[0]->add_route(ALL_OUTPUTS, m_mixer, 1, 0); + m_ym2413[0]->add_route(ALL_OUTPUTS, m_mixer, 1, 1); YM2413(config, m_ym2413[1], 0); - m_ym2413[1]->add_route(ALL_OUTPUTS, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_ym2413[1]->add_route(ALL_OUTPUTS, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_ym2413[1]->add_route(ALL_OUTPUTS, m_mixer, 1, 0); + m_ym2413[1]->add_route(ALL_OUTPUTS, m_mixer, 1, 1); YM2612(config, m_ym2612[0], 0); - m_ym2612[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_ym2612[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_ym2612[0]->add_route(0, m_mixer, 1, 0); + m_ym2612[0]->add_route(1, m_mixer, 1, 1); YM2612(config, m_ym2612[1], 0); - m_ym2612[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_ym2612[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_ym2612[1]->add_route(0, m_mixer, 1, 0); + m_ym2612[1]->add_route(1, m_mixer, 1, 1); YM2151(config, m_ym2151[0], 0); - m_ym2151[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_ym2151[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_ym2151[0]->add_route(0, m_mixer, 1, 0); + m_ym2151[0]->add_route(1, m_mixer, 1, 1); YM2151(config, m_ym2151[1], 0); - m_ym2151[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_ym2151[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_ym2151[1]->add_route(0, m_mixer, 1, 0); + m_ym2151[1]->add_route(1, m_mixer, 1, 1); SEGAPCM(config, m_segapcm[0], 0); m_segapcm[0]->set_addrmap(0, &vgmplay_state::segapcm_map<0>); - m_segapcm[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_segapcm[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_segapcm[0]->add_route(0, m_mixer, 1, 0); + m_segapcm[0]->add_route(1, m_mixer, 1, 1); SEGAPCM(config, m_segapcm[1], 0); m_segapcm[1]->set_addrmap(0, &vgmplay_state::segapcm_map<1>); - m_segapcm[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_segapcm[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_segapcm[1]->add_route(0, m_mixer, 1, 0); + m_segapcm[1]->add_route(1, m_mixer, 1, 1); RF5C68(config, m_rf5c68, 0); m_rf5c68->set_addrmap(0, &vgmplay_state::rf5c68_map<0>); - m_rf5c68->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_rf5c68->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_rf5c68->add_route(0, m_mixer, 1, 0); + m_rf5c68->add_route(1, m_mixer, 1, 1); // TODO: prevent error.log spew YM2203(config, m_ym2203[0], 0); - m_ym2203[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ym2203[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ym2203[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, 0); + m_ym2203[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, 1); YM2203(config, m_ym2203[1], 0); - m_ym2203[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ym2203[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ym2203[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, 0); + m_ym2203[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, 1); // TODO: prevent error.log spew YM2608(config, m_ym2608[0], 0); m_ym2608[0]->set_addrmap(0, &vgmplay_state::ym2608_map<0>); - m_ym2608[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ym2608[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); - m_ym2608[0]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ym2608[0]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ym2608[0]->add_route(0, m_mixer, 0.25, 0); + m_ym2608[0]->add_route(0, m_mixer, 0.25, 1); + m_ym2608[0]->add_route(1, m_mixer, 1.00, 0); + m_ym2608[0]->add_route(2, m_mixer, 1.00, 1); YM2608(config, m_ym2608[1], 0); m_ym2608[1]->set_addrmap(0, &vgmplay_state::ym2608_map<1>); - m_ym2608[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ym2608[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); - m_ym2608[1]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ym2608[1]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ym2608[1]->add_route(0, m_mixer, 0.25, 0); + m_ym2608[1]->add_route(0, m_mixer, 0.25, 1); + m_ym2608[1]->add_route(1, m_mixer, 1.00, 0); + m_ym2608[1]->add_route(2, m_mixer, 1.00, 1); // TODO: prevent error.log spew YM2610(config, m_ym2610[0], 0); m_ym2610[0]->set_addrmap(0, &vgmplay_state::ym2610_adpcm_a_map<0>); m_ym2610[0]->set_addrmap(1, &vgmplay_state::ym2610_adpcm_b_map<0>); - m_ym2610[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ym2610[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); - m_ym2610[0]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_ym2610[0]->add_route(2, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_ym2610[0]->add_route(0, m_mixer, 0.25, 0); + m_ym2610[0]->add_route(0, m_mixer, 0.25, 1); + m_ym2610[0]->add_route(1, m_mixer, 0.50, 0); + m_ym2610[0]->add_route(2, m_mixer, 0.50, 1); YM2610(config, m_ym2610[1], 0); m_ym2610[1]->set_addrmap(0, &vgmplay_state::ym2610_adpcm_a_map<1>); m_ym2610[1]->set_addrmap(1, &vgmplay_state::ym2610_adpcm_b_map<1>); - m_ym2610[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ym2610[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); - m_ym2610[1]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_ym2610[1]->add_route(2, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_ym2610[1]->add_route(0, m_mixer, 0.25, 0); + m_ym2610[1]->add_route(0, m_mixer, 0.25, 1); + m_ym2610[1]->add_route(1, m_mixer, 0.50, 0); + m_ym2610[1]->add_route(2, m_mixer, 0.50, 1); YM3812(config, m_ym3812[0], 0); - m_ym3812[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_ym3812[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_ym3812[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_ym3812[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); YM3812(config, m_ym3812[1], 0); - m_ym3812[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_ym3812[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_ym3812[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_ym3812[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); YM3526(config, m_ym3526[0], 0); - m_ym3526[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_ym3526[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_ym3526[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_ym3526[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); YM3526(config, m_ym3526[1], 0); - m_ym3526[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_ym3526[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_ym3526[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_ym3526[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); Y8950(config, m_y8950[0], 0); m_y8950[0]->set_addrmap(0, &vgmplay_state::y8950_map<0>); - m_y8950[0]->add_route(ALL_OUTPUTS, m_mixer, 0.40, AUTO_ALLOC_INPUT, 0); - m_y8950[0]->add_route(ALL_OUTPUTS, m_mixer, 0.40, AUTO_ALLOC_INPUT, 1); + m_y8950[0]->add_route(ALL_OUTPUTS, m_mixer, 0.40, 0); + m_y8950[0]->add_route(ALL_OUTPUTS, m_mixer, 0.40, 1); Y8950(config, m_y8950[1], 0); m_y8950[1]->set_addrmap(0, &vgmplay_state::y8950_map<1>); - m_y8950[1]->add_route(ALL_OUTPUTS, m_mixer, 0.40, AUTO_ALLOC_INPUT, 0); - m_y8950[1]->add_route(ALL_OUTPUTS, m_mixer, 0.40, AUTO_ALLOC_INPUT, 1); + m_y8950[1]->add_route(ALL_OUTPUTS, m_mixer, 0.40, 0); + m_y8950[1]->add_route(ALL_OUTPUTS, m_mixer, 0.40, 1); YMF262(config, m_ymf262[0], 0); - m_ymf262[0]->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf262[0]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); - m_ymf262[0]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf262[0]->add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf262[0]->add_route(0, m_mixer, 1.00, 0); + m_ymf262[0]->add_route(1, m_mixer, 1.00, 1); + m_ymf262[0]->add_route(2, m_mixer, 1.00, 0); + m_ymf262[0]->add_route(3, m_mixer, 1.00, 1); YMF262(config, m_ymf262[1], 0); - m_ymf262[1]->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf262[1]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); - m_ymf262[1]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf262[1]->add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf262[1]->add_route(0, m_mixer, 1.00, 0); + m_ymf262[1]->add_route(1, m_mixer, 1.00, 1); + m_ymf262[1]->add_route(2, m_mixer, 1.00, 0); + m_ymf262[1]->add_route(3, m_mixer, 1.00, 1); // TODO: prevent error.log spew YMF278B(config, m_ymf278b[0], 0); m_ymf278b[0]->set_addrmap(0, &vgmplay_state::ymf278b_map<0>); - m_ymf278b[0]->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf278b[0]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); - m_ymf278b[0]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf278b[0]->add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); - m_ymf278b[0]->add_route(4, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf278b[0]->add_route(5, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf278b[0]->add_route(0, m_mixer, 1.00, 0); + m_ymf278b[0]->add_route(1, m_mixer, 1.00, 1); + m_ymf278b[0]->add_route(2, m_mixer, 1.00, 0); + m_ymf278b[0]->add_route(3, m_mixer, 1.00, 1); + m_ymf278b[0]->add_route(4, m_mixer, 1.00, 0); + m_ymf278b[0]->add_route(5, m_mixer, 1.00, 1); YMF278B(config, m_ymf278b[1], 0); m_ymf278b[1]->set_addrmap(0, &vgmplay_state::ymf278b_map<1>); - m_ymf278b[1]->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf278b[1]->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); - m_ymf278b[1]->add_route(2, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf278b[1]->add_route(3, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); - m_ymf278b[1]->add_route(4, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_ymf278b[1]->add_route(5, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_ymf278b[1]->add_route(0, m_mixer, 1.00, 0); + m_ymf278b[1]->add_route(1, m_mixer, 1.00, 1); + m_ymf278b[1]->add_route(2, m_mixer, 1.00, 0); + m_ymf278b[1]->add_route(3, m_mixer, 1.00, 1); + m_ymf278b[1]->add_route(4, m_mixer, 1.00, 0); + m_ymf278b[1]->add_route(5, m_mixer, 1.00, 1); YMF271(config, m_ymf271[0], 0); m_ymf271[0]->set_addrmap(0, &vgmplay_state::ymf271_map<0>); - m_ymf271[0]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ymf271[0]->add_route(1, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); - m_ymf271[0]->add_route(2, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ymf271[0]->add_route(3, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ymf271[0]->add_route(0, m_mixer, 0.25, 0); + m_ymf271[0]->add_route(1, m_mixer, 0.25, 1); + m_ymf271[0]->add_route(2, m_mixer, 0.25, 0); + m_ymf271[0]->add_route(3, m_mixer, 0.25, 1); YMF271(config, m_ymf271[1], 0); m_ymf271[1]->set_addrmap(0, &vgmplay_state::ymf271_map<0>); - m_ymf271[1]->add_route(0, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ymf271[1]->add_route(1, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); - m_ymf271[1]->add_route(2, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_ymf271[1]->add_route(3, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_ymf271[1]->add_route(0, m_mixer, 0.25, 0); + m_ymf271[1]->add_route(1, m_mixer, 0.25, 1); + m_ymf271[1]->add_route(2, m_mixer, 0.25, 0); + m_ymf271[1]->add_route(3, m_mixer, 0.25, 1); // TODO: prevent error.log spew YMZ280B(config, m_ymz280b[0], 0); m_ymz280b[0]->set_addrmap(0, &vgmplay_state::ymz280b_map<0>); - m_ymz280b[0]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_ymz280b[0]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_ymz280b[0]->add_route(0, m_mixer, 0.50, 0); + m_ymz280b[0]->add_route(1, m_mixer, 0.50, 1); YMZ280B(config, m_ymz280b[1], 0); m_ymz280b[1]->set_addrmap(0, &vgmplay_state::ymz280b_map<1>); - m_ymz280b[1]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_ymz280b[1]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_ymz280b[1]->add_route(0, m_mixer, 0.50, 0); + m_ymz280b[1]->add_route(1, m_mixer, 0.50, 1); RF5C164(config, m_rf5c164, 0); m_rf5c164->set_addrmap(0, &vgmplay_state::rf5c164_map<0>); - m_rf5c164->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_rf5c164->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_rf5c164->add_route(0, m_mixer, 1, 0); + m_rf5c164->add_route(1, m_mixer, 1, 1); /// TODO: rewrite to generate audio without using DAC devices SEGA_32X_NTSC(config, m_sega32x, 0, "sega32x_maincpu", "sega32x_scanline_timer"); - m_sega32x->add_route(0, m_mixer, 1.00, AUTO_ALLOC_INPUT, 0); - m_sega32x->add_route(1, m_mixer, 1.00, AUTO_ALLOC_INPUT, 1); + m_sega32x->add_route(0, m_mixer, 1.00, 0); + m_sega32x->add_route(1, m_mixer, 1.00, 1); auto& sega32x_maincpu(M68000(config, "sega32x_maincpu", 0)); sega32x_maincpu.set_disable(); @@ -3844,240 +3844,240 @@ void vgmplay_state::vgmplay(machine_config &config) // TODO: prevent error.log spew AY8910(config, m_ay8910[0], 0); - m_ay8910[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 0); - m_ay8910[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 1); + m_ay8910[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, 0); + m_ay8910[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, 1); AY8910(config, m_ay8910[1], 0); - m_ay8910[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 0); - m_ay8910[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 1); + m_ay8910[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, 0); + m_ay8910[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, 1); DMG_APU(config, m_dmg[0], 0); - m_dmg[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_dmg[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_dmg[0]->add_route(0, m_mixer, 1, 0); + m_dmg[0]->add_route(0, m_mixer, 1, 1); DMG_APU(config, m_dmg[1], 0); - m_dmg[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_dmg[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_dmg[1]->add_route(0, m_mixer, 1, 0); + m_dmg[1]->add_route(0, m_mixer, 1, 1); RP2A03G(config, m_nescpu[0], 0); m_nescpu[0]->set_addrmap(AS_PROGRAM, &vgmplay_state::nescpu_map<0>); m_nescpu[0]->set_disable(); - m_nescpu[0]->add_route(ALL_OUTPUTS, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_nescpu[0]->add_route(ALL_OUTPUTS, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_nescpu[0]->add_route(ALL_OUTPUTS, m_mixer, 0.50, 0); + m_nescpu[0]->add_route(ALL_OUTPUTS, m_mixer, 0.50, 1); RP2A03G(config, m_nescpu[1], 0); m_nescpu[1]->set_addrmap(AS_PROGRAM, &vgmplay_state::nescpu_map<1>); m_nescpu[1]->set_disable(); - m_nescpu[1]->add_route(ALL_OUTPUTS, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_nescpu[1]->add_route(ALL_OUTPUTS, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_nescpu[1]->add_route(ALL_OUTPUTS, m_mixer, 0.50, 0); + m_nescpu[1]->add_route(ALL_OUTPUTS, m_mixer, 0.50, 1); MULTIPCM(config, m_multipcm[0], 0); m_multipcm[0]->set_addrmap(0, &vgmplay_state::multipcm_map<0>); - m_multipcm[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_multipcm[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_multipcm[0]->add_route(0, m_mixer, 1, 0); + m_multipcm[0]->add_route(1, m_mixer, 1, 1); MULTIPCM(config, m_multipcm[1], 0); m_multipcm[1]->set_addrmap(0, &vgmplay_state::multipcm_map<1>); - m_multipcm[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_multipcm[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_multipcm[1]->add_route(0, m_mixer, 1, 0); + m_multipcm[1]->add_route(1, m_mixer, 1, 1); UPD7759(config, m_upd7759[0], 0); m_upd7759[0]->drq().set(FUNC(vgmplay_state::upd7759_drq_w<0>)); m_upd7759[0]->set_addrmap(0, &vgmplay_state::upd7759_map<0>); - m_upd7759[0]->add_route(ALL_OUTPUTS, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); - m_upd7759[0]->add_route(ALL_OUTPUTS, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); + m_upd7759[0]->add_route(ALL_OUTPUTS, m_mixer, 1.0, 0); + m_upd7759[0]->add_route(ALL_OUTPUTS, m_mixer, 1.0, 1); UPD7759(config, m_upd7759[1], 0); m_upd7759[1]->drq().set(FUNC(vgmplay_state::upd7759_drq_w<1>)); m_upd7759[1]->set_addrmap(0, &vgmplay_state::upd7759_map<1>); - m_upd7759[1]->add_route(ALL_OUTPUTS, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); - m_upd7759[1]->add_route(ALL_OUTPUTS, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); + m_upd7759[1]->add_route(ALL_OUTPUTS, m_mixer, 1.0, 0); + m_upd7759[1]->add_route(ALL_OUTPUTS, m_mixer, 1.0, 1); OKIM6258(config, m_okim6258[0], 0); - m_okim6258[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_okim6258[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_okim6258[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_okim6258[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); OKIM6258(config, m_okim6258[1], 0); - m_okim6258[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_okim6258[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_okim6258[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_okim6258[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); OKIM6295(config, m_okim6295[0], 0, okim6295_device::PIN7_HIGH); m_okim6295[0]->set_addrmap(0, &vgmplay_state::okim6295_map<0>); - m_okim6295[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_okim6295[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_okim6295[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, 0); + m_okim6295[0]->add_route(ALL_OUTPUTS, m_mixer, 0.25, 1); OKIM6295(config, m_okim6295[1], 0, okim6295_device::PIN7_HIGH); m_okim6295[1]->set_addrmap(0, &vgmplay_state::okim6295_map<1>); - m_okim6295[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 0); - m_okim6295[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, AUTO_ALLOC_INPUT, 1); + m_okim6295[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, 0); + m_okim6295[1]->add_route(ALL_OUTPUTS, m_mixer, 0.25, 1); K051649(config, m_k051649[0], 0); - m_k051649[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 0); - m_k051649[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 1); + m_k051649[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, 0); + m_k051649[0]->add_route(ALL_OUTPUTS, m_mixer, 0.33, 1); K051649(config, m_k051649[1], 0); - m_k051649[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 0); - m_k051649[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, AUTO_ALLOC_INPUT, 1); + m_k051649[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, 0); + m_k051649[1]->add_route(ALL_OUTPUTS, m_mixer, 0.33, 1); K054539(config, m_k054539[0], 0); m_k054539[0]->set_addrmap(0, &vgmplay_state::k054539_map<0>); - m_k054539[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_k054539[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_k054539[0]->add_route(0, m_mixer, 1, 0); + m_k054539[0]->add_route(1, m_mixer, 1, 1); K054539(config, m_k054539[1], 0); m_k054539[1]->set_addrmap(0, &vgmplay_state::k054539_map<1>); - m_k054539[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_k054539[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_k054539[1]->add_route(0, m_mixer, 1, 0); + m_k054539[1]->add_route(1, m_mixer, 1, 1); // TODO: prevent error.log spew H6280(config, m_huc6280[0], 0); m_huc6280[0]->set_disable(); - m_huc6280[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_huc6280[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_huc6280[0]->add_route(0, m_mixer, 1, 0); + m_huc6280[0]->add_route(1, m_mixer, 1, 1); H6280(config, m_huc6280[1], 0); m_huc6280[1]->set_disable(); - m_huc6280[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_huc6280[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_huc6280[1]->add_route(0, m_mixer, 1, 0); + m_huc6280[1]->add_route(1, m_mixer, 1, 1); C140(config, m_c140[0], 0); m_c140[0]->set_addrmap(0, &vgmplay_state::c140_map<0>); - m_c140[0]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_c140[0]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_c140[0]->add_route(0, m_mixer, 0.50, 0); + m_c140[0]->add_route(1, m_mixer, 0.50, 1); C140(config, m_c140[1], 0); m_c140[1]->set_addrmap(0, &vgmplay_state::c140_map<1>); - m_c140[1]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_c140[1]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_c140[1]->add_route(0, m_mixer, 0.50, 0); + m_c140[1]->add_route(1, m_mixer, 0.50, 1); C219(config, m_c219[0], 0); m_c219[0]->set_addrmap(0, &vgmplay_state::c219_map<0>); - m_c219[0]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_c219[0]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_c219[0]->add_route(0, m_mixer, 0.50, 0); + m_c219[0]->add_route(1, m_mixer, 0.50, 1); C219(config, m_c219[1], 0); m_c219[1]->set_addrmap(0, &vgmplay_state::c219_map<1>); - m_c219[1]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_c219[1]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_c219[1]->add_route(0, m_mixer, 0.50, 0); + m_c219[1]->add_route(1, m_mixer, 0.50, 1); K053260(config, m_k053260[0], 0); m_k053260[0]->set_addrmap(0, &vgmplay_state::k053260_map<0>); - m_k053260[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_k053260[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_k053260[0]->add_route(0, m_mixer, 1, 0); + m_k053260[0]->add_route(1, m_mixer, 1, 1); K053260(config, m_k053260[1], 0); m_k053260[1]->set_addrmap(0, &vgmplay_state::k053260_map<1>); - m_k053260[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_k053260[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_k053260[1]->add_route(0, m_mixer, 1, 0); + m_k053260[1]->add_route(1, m_mixer, 1, 1); POKEY(config, m_pokey[0], 0); - m_pokey[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_pokey[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_pokey[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_pokey[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); POKEY(config, m_pokey[1], 0); - m_pokey[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_pokey[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_pokey[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_pokey[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); QSOUND(config, m_qsound, 0); m_qsound->set_addrmap(0, &vgmplay_state::qsound_map<0>); - m_qsound->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_qsound->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_qsound->add_route(0, m_mixer, 1, 0); + m_qsound->add_route(1, m_mixer, 1, 1); SCSP(config, m_scsp[0], 0); m_scsp[0]->set_addrmap(0, &vgmplay_state::scsp_map<0>); - m_scsp[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_scsp[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_scsp[0]->add_route(0, m_mixer, 1, 0); + m_scsp[0]->add_route(1, m_mixer, 1, 1); SCSP(config, m_scsp[1], 0); m_scsp[1]->set_addrmap(0, &vgmplay_state::scsp_map<1>); - m_scsp[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_scsp[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_scsp[1]->add_route(0, m_mixer, 1, 0); + m_scsp[1]->add_route(1, m_mixer, 1, 1); WSWAN_SND(config, m_wswan[0], 0); m_wswan[0]->set_addrmap(0, &vgmplay_state::wswan_map<0>); - m_wswan[0]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_wswan[0]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_wswan[0]->add_route(0, m_mixer, 0.50, 0); + m_wswan[0]->add_route(1, m_mixer, 0.50, 1); WSWAN_SND(config, m_wswan[1], 0); m_wswan[1]->set_addrmap(0, &vgmplay_state::wswan_map<1>); - m_wswan[1]->add_route(0, m_mixer, 0.50, AUTO_ALLOC_INPUT, 0); - m_wswan[1]->add_route(1, m_mixer, 0.50, AUTO_ALLOC_INPUT, 1); + m_wswan[1]->add_route(0, m_mixer, 0.50, 0); + m_wswan[1]->add_route(1, m_mixer, 0.50, 1); VBOYSND(config, m_vsu_vue[0], 0); - m_vsu_vue[0]->add_route(0, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); - m_vsu_vue[0]->add_route(1, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); + m_vsu_vue[0]->add_route(0, m_mixer, 1.0, 0); + m_vsu_vue[0]->add_route(1, m_mixer, 1.0, 1); VBOYSND(config, m_vsu_vue[1], 0); - m_vsu_vue[1]->add_route(0, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); - m_vsu_vue[1]->add_route(1, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); + m_vsu_vue[1]->add_route(0, m_mixer, 1.0, 0); + m_vsu_vue[1]->add_route(1, m_mixer, 1.0, 1); SAA1099(config, m_saa1099[0], 0); - m_saa1099[0]->add_route(0, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); - m_saa1099[0]->add_route(1, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); + m_saa1099[0]->add_route(0, m_mixer, 1.0, 0); + m_saa1099[0]->add_route(1, m_mixer, 1.0, 1); SAA1099(config, m_saa1099[1], 0); - m_saa1099[1]->add_route(0, m_mixer, 1.0, AUTO_ALLOC_INPUT, 0); - m_saa1099[1]->add_route(1, m_mixer, 1.0, AUTO_ALLOC_INPUT, 1); + m_saa1099[1]->add_route(0, m_mixer, 1.0, 0); + m_saa1099[1]->add_route(1, m_mixer, 1.0, 1); ES5503(config, m_es5503[0], 0); m_es5503[0]->set_channels(2); m_es5503[0]->set_addrmap(0, &vgmplay_state::es5503_map<0>); - m_es5503[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_es5503[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_es5503[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_es5503[0]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); ES5503(config, m_es5503[1], 0); m_es5503[1]->set_channels(2); m_es5503[1]->set_addrmap(0, &vgmplay_state::es5503_map<1>); - m_es5503[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_es5503[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_es5503[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 0); + m_es5503[1]->add_route(ALL_OUTPUTS, m_mixer, 0.5, 1); ES5505(config, m_es5505[0], 0); // TODO m_es5505[0]->set_addrmap(0, &vgmplay_state::es5505_map<0>); // TODO m_es5505[0]->set_addrmap(1, &vgmplay_state::es5505_map<0>); m_es5505[0]->set_channels(1); - m_es5505[0]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_es5505[0]->add_route(1, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_es5505[0]->add_route(0, m_mixer, 0.5, 0); + m_es5505[0]->add_route(1, m_mixer, 0.5, 1); ES5505(config, m_es5505[1], 0); // TODO m_es5505[1]->set_addrmap(0, &vgmplay_state::es5505_map<1>); // TODO m_es5505[1]->set_addrmap(1, &vgmplay_state::es5505_map<1>); m_es5505[1]->set_channels(1); - m_es5505[1]->add_route(0, m_mixer, 0.5, AUTO_ALLOC_INPUT, 0); - m_es5505[1]->add_route(1, m_mixer, 0.5, AUTO_ALLOC_INPUT, 1); + m_es5505[1]->add_route(0, m_mixer, 0.5, 0); + m_es5505[1]->add_route(1, m_mixer, 0.5, 1); X1_010(config, m_x1_010[0], 0); m_x1_010[0]->set_addrmap(0, &vgmplay_state::x1_010_map<0>); - m_x1_010[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_x1_010[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_x1_010[0]->add_route(0, m_mixer, 1, 0); + m_x1_010[0]->add_route(1, m_mixer, 1, 1); X1_010(config, m_x1_010[1], 0); m_x1_010[1]->set_addrmap(0, &vgmplay_state::x1_010_map<1>); - m_x1_010[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_x1_010[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_x1_010[1]->add_route(0, m_mixer, 1, 0); + m_x1_010[1]->add_route(1, m_mixer, 1, 1); C352(config, m_c352[0], 0, 1); m_c352[0]->set_addrmap(0, &vgmplay_state::c352_map<0>); - m_c352[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_c352[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); - m_c352[0]->add_route(2, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_c352[0]->add_route(3, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_c352[0]->add_route(0, m_mixer, 1, 0); + m_c352[0]->add_route(1, m_mixer, 1, 1); + m_c352[0]->add_route(2, m_mixer, 1, 0); + m_c352[0]->add_route(3, m_mixer, 1, 1); C352(config, m_c352[1], 0, 1); m_c352[1]->set_addrmap(0, &vgmplay_state::c352_map<1>); - m_c352[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_c352[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); - m_c352[1]->add_route(2, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_c352[1]->add_route(3, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_c352[1]->add_route(0, m_mixer, 1, 0); + m_c352[1]->add_route(1, m_mixer, 1, 1); + m_c352[1]->add_route(2, m_mixer, 1, 0); + m_c352[1]->add_route(3, m_mixer, 1, 1); IREMGA20(config, m_ga20[0], 0); m_ga20[0]->set_addrmap(0, &vgmplay_state::ga20_map<0>); - m_ga20[0]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_ga20[0]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_ga20[0]->add_route(0, m_mixer, 1, 0); + m_ga20[0]->add_route(1, m_mixer, 1, 1); IREMGA20(config, m_ga20[1], 0); m_ga20[1]->set_addrmap(0, &vgmplay_state::ga20_map<1>); - m_ga20[1]->add_route(0, m_mixer, 1, AUTO_ALLOC_INPUT, 0); - m_ga20[1]->add_route(1, m_mixer, 1, AUTO_ALLOC_INPUT, 1); + m_ga20[1]->add_route(0, m_mixer, 1, 0); + m_ga20[1]->add_route(1, m_mixer, 1, 1); VGMVIZ(config, m_mixer, 0); m_mixer->add_route(0, "lspeaker", 1); diff --git a/src/mame/zaccaria/zaccaria_a.cpp b/src/mame/zaccaria/zaccaria_a.cpp index dcce94ecef656..3876435e9e899 100644 --- a/src/mame/zaccaria/zaccaria_a.cpp +++ b/src/mame/zaccaria/zaccaria_a.cpp @@ -314,10 +314,10 @@ void zac1b11107_audio_device::device_add_mconfig(machine_config &config) m_melodycpu->set_addrmap(AS_PROGRAM, &zac1b11107_audio_device::zac1b11107_melody_map); m_melodypsg1->port_a_write_callback().set(FUNC(zac1b11107_audio_device::melodypsg1_porta_w)); - m_melodypsg1->add_route(ALL_OUTPUTS, *this, 0.5, AUTO_ALLOC_INPUT, 0); + m_melodypsg1->add_route(ALL_OUTPUTS, *this, 0.5, 0); m_melodypsg2->port_a_write_callback().set(FUNC(zac1b11107_audio_device::melodypsg2_porta_w)); - m_melodypsg2->add_route(ALL_OUTPUTS, *this, 0.5, AUTO_ALLOC_INPUT, 0); + m_melodypsg2->add_route(ALL_OUTPUTS, *this, 0.5, 0); } @@ -435,7 +435,7 @@ void zac1b11142_audio_device::device_add_mconfig(machine_config &config) m_pia_1i->writepa_handler().set(m_speech, FUNC(tms5220_device::data_w)); m_pia_1i->writepb_handler().set(FUNC(zac1b11142_audio_device::pia_1i_portb_w)); - //MC1408(config, "dac", 0).add_route(ALL_OUTPUTS, *this, 0.30, AUTO_ALLOC_INPUT, 0); // mc1408.1f + //MC1408(config, "dac", 0).add_route(ALL_OUTPUTS, *this, 0.30, 0); // mc1408.1f MC1408(config, "dac").add_route(ALL_OUTPUTS, "sound_nl", 1.0, 7); // mc1408.1f // There is no xtal, the clock is obtained from a RC oscillator as shown in the TMS5220 datasheet (R=100kOhm C=22pF) @@ -447,7 +447,7 @@ void zac1b11142_audio_device::device_add_mconfig(machine_config &config) NETLIST_SOUND(config, "sound_nl", 48000) .set_source(netlist_zac1b11142) - .add_route(ALL_OUTPUTS, *this, 1.0, AUTO_ALLOC_INPUT, 0); + .add_route(ALL_OUTPUTS, *this, 1.0, 0); NETLIST_LOGIC_INPUT(config, "sound_nl:ioa0", "I_IOA0.IN", 0); NETLIST_LOGIC_INPUT(config, "sound_nl:ioa1", "I_IOA1.IN", 0); diff --git a/src/osd/interface/audio.h b/src/osd/interface/audio.h new file mode 100644 index 0000000000000..ae4852cb23cd7 --- /dev/null +++ b/src/osd/interface/audio.h @@ -0,0 +1,54 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#ifndef MAME_OSD_INTERFACE_AUDIO_H +#define MAME_OSD_INTERFACE_AUDIO_H + +#pragma once + +#include +#include +#include +#include + +namespace osd { + +struct audio_rate_range { + uint32_t m_default_rate; + uint32_t m_min_rate; + uint32_t m_max_rate; +}; + +struct audio_info { + struct port_info { + std::string m_name; + std::array m_position; + }; + + struct node_info { + std::string m_name; + uint32_t m_id; + audio_rate_range m_rate; + std::vector m_sinks; + std::vector m_sources; + }; + + struct stream_info { + uint32_t m_id; + uint32_t m_node; + std::vector m_volumes; + }; + + uint32_t m_generation; + uint32_t m_default_sink; + uint32_t m_default_source; + std::vector m_nodes; + std::vector m_streams; +}; + +static inline float db_to_linear(float db) { return db <= -96 ? 0.0 : pow(10, db/20); } +static inline float linear_to_db(float linear) { return linear <= 1/65536.0 ? -96 : 20*log10(linear); } + +} + +#endif diff --git a/src/osd/modules/lib/osdobj_common.cpp b/src/osd/modules/lib/osdobj_common.cpp index 430f6ceeac922..7b47ba0e77b09 100644 --- a/src/osd/modules/lib/osdobj_common.cpp +++ b/src/osd/modules/lib/osdobj_common.cpp @@ -268,6 +268,9 @@ void osd_common_t::register_options() #endif #ifndef NO_USE_PULSEAUDIO REGISTER_MODULE(m_mod_man, SOUND_PULSEAUDIO); +#endif +#ifndef NO_USE_PIPEWIRE + REGISTER_MODULE(m_mod_man, SOUND_PIPEWIRE); #endif REGISTER_MODULE(m_mod_man, SOUND_NONE); @@ -464,7 +467,6 @@ void osd_common_t::update(bool skip_redraw) m_watchdog->reset(); update_slider_list(); - } @@ -506,39 +508,48 @@ void osd_common_t::debugger_update() } -//------------------------------------------------- -// update_audio_stream - update the stereo audio -// stream -//------------------------------------------------- +bool osd_common_t::sound_external_per_channel_volume() +{ + return m_sound->external_per_channel_volume(); +} -void osd_common_t::update_audio_stream(const int16_t *buffer, int samples_this_frame) +bool osd_common_t::sound_split_streams_per_source() { - // - // This method is called whenever the system has new audio data to stream. - // It provides an array of stereo samples in L-R order which should be - // output at the configured sample_rate. - // - m_sound->update_audio_stream(m_machine->video().throttled(), buffer,samples_this_frame); + return m_sound->split_streams_per_source(); } +uint32_t osd_common_t::sound_get_generation() +{ + return m_sound->get_generation(); +} -//------------------------------------------------- -// set_mastervolume - set the system volume -//------------------------------------------------- +osd::audio_info osd_common_t::sound_get_information() +{ + return m_sound->get_information(); +} -void osd_common_t::set_mastervolume(int attenuation) +uint32_t osd_common_t::sound_stream_sink_open(uint32_t node, std::string name, uint32_t rate) { - // - // Attenuation is the attenuation in dB (a negative number). - // To convert from dB to a linear volume scale do the following: - // volume = MAX_VOLUME; - // while (attenuation++ < 0) - // volume /= 1.122018454; // = (10 ^ (1/20)) = 1dB - // - if (m_sound != nullptr) - m_sound->set_mastervolume(attenuation); + return m_sound->stream_sink_open(node, name, rate); } +void osd_common_t::sound_stream_set_volumes(uint32_t id, const std::vector &db) +{ + m_sound->stream_set_volumes(id, db); +} + +void osd_common_t::sound_stream_close(uint32_t id) +{ + m_sound->stream_close(id); +} + +void osd_common_t::sound_stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) +{ + m_sound->stream_update(id, buffer, samples_this_frame); +} + + + //------------------------------------------------- // customize_input_type_list - provide OSD diff --git a/src/osd/modules/lib/osdobj_common.h b/src/osd/modules/lib/osdobj_common.h index 919bbc4f717de..db064dcdfdcc8 100644 --- a/src/osd/modules/lib/osdobj_common.h +++ b/src/osd/modules/lib/osdobj_common.h @@ -221,9 +221,15 @@ class osd_common_t : public osd_interface, osd_output virtual void wait_for_debugger(device_t &device, bool firststop) override; // audio overridables - virtual void update_audio_stream(const int16_t *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; virtual bool no_sound() override; + virtual bool sound_external_per_channel_volume() override; + virtual bool sound_split_streams_per_source() override; + virtual uint32_t sound_get_generation() override; + virtual osd::audio_info sound_get_information() override; + virtual uint32_t sound_stream_sink_open(uint32_t node, std::string name, uint32_t rate) override; + virtual void sound_stream_set_volumes(uint32_t id, const std::vector &db) override; + virtual void sound_stream_close(uint32_t id) override; + virtual void sound_stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override; // input overridables virtual void customize_input_type_list(std::vector &typelist) override; diff --git a/src/osd/modules/sound/coreaudio_sound.cpp b/src/osd/modules/sound/coreaudio_sound.cpp index 2cdb929116803..4038d6bc8d10d 100644 --- a/src/osd/modules/sound/coreaudio_sound.cpp +++ b/src/osd/modules/sound/coreaudio_sound.cpp @@ -46,7 +46,6 @@ class sound_coreaudio : public osd_module, public sound_module m_playpos(0), m_writepos(0), m_in_underrun(false), - m_scale(128), m_overflows(0), m_underflows(0) { @@ -60,8 +59,7 @@ class sound_coreaudio : public osd_module, public sound_module // sound_module - virtual void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; + virtual void stream_update(uint32_t, int16_t const *buffer, int samples_this_frame) override; private: struct node_detail @@ -83,14 +81,6 @@ class sound_coreaudio : public osd_module, public sound_module uint32_t buffer_avail() const { return ((m_writepos >= m_playpos) ? m_buffer_size : 0) + m_playpos - m_writepos; } uint32_t buffer_used() const { return ((m_playpos > m_writepos) ? m_buffer_size : 0) + m_writepos - m_playpos; } - void copy_scaled(void *dst, void const *src, uint32_t bytes) const - { - bytes /= sizeof(int16_t); - int16_t const *s = (int16_t const *)src; - for (int16_t *d = (int16_t *)dst; bytes > 0; bytes--, s++, d++) - *d = (*s * m_scale) >> 7; - } - bool create_graph(osd_options const &options); bool add_output(char const *name); bool add_device_output(char const *name); @@ -178,7 +168,6 @@ class sound_coreaudio : public osd_module, public sound_module uint32_t m_playpos; uint32_t m_writepos; bool m_in_underrun; - int32_t m_scale; unsigned m_overflows; unsigned m_underflows; }; @@ -240,7 +229,6 @@ int sound_coreaudio::init(osd_interface &osd, const osd_options &options) m_playpos = 0; m_writepos = m_headroom; m_in_underrun = false; - m_scale = 128; m_overflows = m_underflows = 0; // Initialise and start @@ -290,7 +278,7 @@ void sound_coreaudio::exit() } -void sound_coreaudio::update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) +void sound_coreaudio::stream_update(uint32_t, int16_t const *buffer, int samples_this_frame) { if ((m_sample_rate == 0) || !m_buffer) return; @@ -318,13 +306,6 @@ void sound_coreaudio::update_audio_stream(bool is_throttled, int16_t const *buff } -void sound_coreaudio::set_mastervolume(int attenuation) -{ - int const clamped_attenuation = std::clamp(attenuation, -32, 0); - m_scale = (-32 == clamped_attenuation) ? 0 : (int32_t)(pow(10.0, clamped_attenuation / 20.0) * 128); -} - - bool sound_coreaudio::create_graph(osd_options const &options) { OSStatus err; @@ -986,7 +967,7 @@ OSStatus sound_coreaudio::render( } uint32_t const chunk = std::min(m_buffer_size - m_playpos, number_bytes); - copy_scaled((int8_t *)data->mBuffers[0].mData, &m_buffer[m_playpos], chunk); + memcpy((int8_t *)data->mBuffers[0].mData, &m_buffer[m_playpos], chunk); m_playpos += chunk; if (m_playpos >= m_buffer_size) m_playpos = 0; @@ -995,7 +976,7 @@ OSStatus sound_coreaudio::render( { assert(0U == m_playpos); assert(m_writepos >= (number_bytes - chunk)); - copy_scaled((int8_t *)data->mBuffers[0].mData + chunk, &m_buffer[0], number_bytes - chunk); + memcpy((int8_t *)data->mBuffers[0].mData + chunk, &m_buffer[0], number_bytes - chunk); m_playpos += number_bytes - chunk; } diff --git a/src/osd/modules/sound/direct_sound.cpp b/src/osd/modules/sound/direct_sound.cpp index 0e89bb1ac0d1e..fac332027b548 100644 --- a/src/osd/modules/sound/direct_sound.cpp +++ b/src/osd/modules/sound/direct_sound.cpp @@ -120,13 +120,6 @@ class stream_buffer : public buffer_base assert(m_buffer); return m_buffer->Stop(); } - HRESULT set_volume(LONG volume) const - { - assert(m_buffer); - return m_buffer->SetVolume(volume); - } - HRESULT set_min_volume() { return set_volume(DSBVOLUME_MIN); } - HRESULT get_current_positions(DWORD &play_pos, DWORD &write_pos) const { assert(m_buffer); @@ -225,8 +218,7 @@ class sound_direct_sound : public osd_module, public sound_module virtual void exit() override; // sound_module - virtual void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; + virtual void stream_update(uint32_t, int16_t const *buffer, int samples_this_frame) override; private: HRESULT dsound_init(); @@ -297,11 +289,11 @@ void sound_direct_sound::exit() //============================================================ -// update_audio_stream +// stream_update //============================================================ -void sound_direct_sound::update_audio_stream( - bool is_throttled, +void sound_direct_sound::stream_update( + uint32_t, int16_t const *buffer, int samples_this_frame) { @@ -363,26 +355,6 @@ void sound_direct_sound::update_audio_stream( } -//============================================================ -// set_mastervolume -//============================================================ - -void sound_direct_sound::set_mastervolume(int attenuation) -{ - // clamp the attenuation to 0-32 range - attenuation = std::clamp(attenuation, -32, 0); - - // set the master volume - if (m_stream_buffer) - { - if (-32 == attenuation) - m_stream_buffer.set_min_volume(); - else - m_stream_buffer.set_volume(100 * attenuation); - } -} - - //============================================================ // dsound_init //============================================================ diff --git a/src/osd/modules/sound/js_sound.cpp b/src/osd/modules/sound/js_sound.cpp index 7375d01414151..dbf60460bd3ac 100644 --- a/src/osd/modules/sound/js_sound.cpp +++ b/src/osd/modules/sound/js_sound.cpp @@ -29,26 +29,16 @@ class sound_js : public osd_module, public sound_module // sound_module - virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) + virtual void stream_update(uint32_t, const int16_t *buffer, int samples_this_frame) { EM_ASM_ARGS( { // Forward audio stream update on to JS backend implementation. - jsmame_update_audio_stream($0, $1); + jsmame_steam_update($1, $2); }, (unsigned int)buffer, samples_this_frame); } - - virtual void set_mastervolume(int attenuation) - { - EM_ASM_ARGS( - { - // Forward volume update on to JS backend implementation. - jsmame_set_mastervolume($0); - }, - attenuation); - } }; #else // SDLMAME_EMSCRIPTEN diff --git a/src/osd/modules/sound/js_sound.js b/src/osd/modules/sound/js_sound.js index 571a5227f2bbc..81ad07dcef12d 100644 --- a/src/osd/modules/sound/js_sound.js +++ b/src/osd/modules/sound/js_sound.js @@ -98,30 +98,7 @@ function disconnect_old_event() { eventNode = null; }; -function set_mastervolume ( - // even though it's 'attenuation' the value is negative, so... - attenuation_in_decibels -) { - lazy_init(); - if (!context) return; - - // http://stackoverflow.com/questions/22604500/web-audio-api-working-with-decibels - // seemingly incorrect/broken. figures. welcome to Web Audio - // var gain_web_audio = 1.0 - Math.pow(10, 10 / attenuation_in_decibels); - - // HACK: Max attenuation in JSMESS appears to be 32. - // Hit ' then left/right arrow to test. - // FIXME: This is linear instead of log10 scale. - var gain_web_audio = 1.0 + (+attenuation_in_decibels / +32); - if (gain_web_audio < +0) - gain_web_audio = +0; - else if (gain_web_audio > +1) - gain_web_audio = +1; - - gain_node.gain.value = gain_web_audio; -}; - -function update_audio_stream ( +function stream_update ( pBuffer, // pointer into emscripten heap. int16 samples samples_this_frame // int. number of samples at pBuffer address. ) { @@ -207,14 +184,12 @@ function sample_count() { } return { - set_mastervolume: set_mastervolume, - update_audio_stream: update_audio_stream, + stream_update: stream_update, get_context: get_context, sample_count: sample_count }; })(); -window.jsmame_set_mastervolume = jsmame_web_audio.set_mastervolume; -window.jsmame_update_audio_stream = jsmame_web_audio.update_audio_stream; +window.jsmame_stream_update = jsmame_web_audio.stream_update; window.jsmame_sample_count = jsmame_web_audio.sample_count; diff --git a/src/osd/modules/sound/none.cpp b/src/osd/modules/sound/none.cpp index ba1085f436bbf..bdb219636d001 100644 --- a/src/osd/modules/sound/none.cpp +++ b/src/osd/modules/sound/none.cpp @@ -27,11 +27,6 @@ class sound_none : public osd_module, public sound_module virtual int init(osd_interface &osd, const osd_options &options) override { return 0; } virtual void exit() override { } - - // sound_module - - virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) override { } - virtual void set_mastervolume(int attenuation) override { } }; } // anonymous namespace diff --git a/src/osd/modules/sound/pa_sound.cpp b/src/osd/modules/sound/pa_sound.cpp index d5bb96a25af61..e3e82d310b9bb 100644 --- a/src/osd/modules/sound/pa_sound.cpp +++ b/src/osd/modules/sound/pa_sound.cpp @@ -52,8 +52,7 @@ class sound_pa : public osd_module, public sound_module // sound_module - virtual void update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; + virtual void stream_update(uint32_t, const s16 *buffer, int samples_this_frame) override; private: // Lock free SPSC ring buffer @@ -80,14 +79,14 @@ class sound_pa : public osd_module, public sound_module writepos.store((writepos + n) % size); } - int write(const T* src, int n, int attenuation) { + int write(const T* src, int n) { n = std::min(n, size - reserve - count()); if (writepos + n > size) { - att_memcpy(buf + writepos, src, sizeof(T) * (size - writepos), attenuation); - att_memcpy(buf, src + (size - writepos), sizeof(T) * (n - (size - writepos)), attenuation); + memcpy(buf + writepos, src, sizeof(T) * (size - writepos)); + memcpy(buf, src + (size - writepos), sizeof(T) * (n - (size - writepos))); } else { - att_memcpy(buf + writepos, src, sizeof(T) * n, attenuation); + memcpy(buf + writepos, src, sizeof(T) * n); } increment_writepos(n); @@ -128,13 +127,6 @@ class sound_pa : public osd_module, public sound_module return n; } - - void att_memcpy(T* dest, const T* data, int n, int attenuation) { - int level = powf(10.0, attenuation / 20.0) * 32768; - n /= sizeof(T); - while (n--) - *dest++ = (*data++ * level) >> 15; - } }; enum @@ -159,7 +151,6 @@ class sound_pa : public osd_module, public sound_module int m_sample_rate; int m_audio_latency; - int m_attenuation; audio_buffer* m_ab; @@ -193,7 +184,6 @@ int sound_pa::init(osd_interface &osd, osd_options const &options) unsigned long frames_per_callback = paFramesPerBufferUnspecified; double callback_interval; - m_attenuation = options.volume(); m_underflows = 0; m_overflows = 0; m_has_overflowed = false; @@ -389,7 +379,7 @@ int sound_pa::callback(s16* output_buffer, size_t number_of_samples) m_ab->read(output_buffer, buf_ct); std::memset(output_buffer + buf_ct, 0, (number_of_samples - buf_ct) * sizeof(s16)); - // if update_audio_stream has been called, note the underflow + // if stream_update has been called, note the underflow if (m_osd_ticks) m_has_underflowed = true; @@ -399,7 +389,7 @@ int sound_pa::callback(s16* output_buffer, size_t number_of_samples) return paContinue; } -void sound_pa::update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) +void sound_pa::stream_update(uint32_t, const s16 *buffer, int samples_this_frame) { if (!m_sample_rate) return; @@ -423,17 +413,12 @@ void sound_pa::update_audio_stream(bool is_throttled, const s16 *buffer, int sam m_has_overflowed = false; } - m_ab->write(buffer, samples_this_frame * 2, m_attenuation); + m_ab->write(buffer, samples_this_frame * 2); // for determining buffer overflows, take the sample here instead of in the callback m_osd_ticks = osd_ticks(); } -void sound_pa::set_mastervolume(int attenuation) -{ - m_attenuation = attenuation; -} - void sound_pa::exit() { if (!m_sample_rate) diff --git a/src/osd/modules/sound/pipewire_sound.cpp b/src/osd/modules/sound/pipewire_sound.cpp new file mode 100644 index 0000000000000..850b378d8bb8e --- /dev/null +++ b/src/osd/modules/sound/pipewire_sound.cpp @@ -0,0 +1,765 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert +/*************************************************************************** + + piepewire_sound.c + + PipeWire interface. + +***************************************************************************/ + +#include "sound_module.h" +#include "modules/osdmodule.h" + +#ifndef NO_USE_PIPEWIRE + +#define GNU_SOURCE +#include "modules/lib/osdobj_common.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +class sound_pipewire : public osd_module, public sound_module +{ +public: + sound_pipewire() + : osd_module(OSD_SOUND_PROVIDER, "pipewire"), sound_module() + { + } + virtual ~sound_pipewire() { } + + virtual int init(osd_interface &osd, osd_options const &options) override; + virtual void exit() override; + + virtual bool external_per_channel_volume() override { return true; } + virtual bool split_streams_per_source() override { return true; } + + virtual uint32_t get_generation() override; + virtual osd::audio_info get_information() override; + virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override; + virtual void stream_set_volumes(uint32_t id, const std::vector &db) override; + virtual void stream_close(uint32_t id) override; + virtual void stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override; + +private: + struct position_info { + uint32_t m_position; + std::array m_coords; + }; + + struct abuffer { + uint32_t m_cpos; + std::vector m_data; + }; + + static const position_info position_infos[]; + + static const char *const typenames[]; + enum { AREC, APLAY }; + + struct node_info { + sound_pipewire *m_wire; + uint32_t m_id, m_osdid; + int m_type; + std::string m_serial; + std::string m_name; + std::string m_text_id; + + // Audio node info + std::vector m_position_codes; + std::vector> m_positions; + std::vector m_sink_port_names; + std::vector m_source_port_names; + + osd::audio_rate_range m_rate; + bool m_has_s16; + bool m_has_iec958; + + pw_node *m_node; + spa_hook m_node_listener; + + node_info(sound_pipewire *wire, uint32_t id, uint32_t osdid, int type, std::string serial, std::string name, std::string text_id) : m_wire(wire), m_id(id), m_osdid(osdid), m_type(type), m_serial(serial), m_name(name), m_text_id(text_id), m_rate{0, 0, 0}, m_has_s16(false), m_has_iec958(false), m_node(nullptr) { + spa_zero(m_node_listener); + } + }; + + struct stream_info { + sound_pipewire *m_wire; + uint32_t m_osdid; + uint32_t m_source_node_id; + node_info *m_target_node; + uint32_t m_channels; + pw_stream *m_stream; + std::vector m_volumes; + std::vector m_buffers; + std::vector m_last_sample; + + stream_info(sound_pipewire *wire, uint32_t osdid) : m_wire(wire), m_osdid(osdid), m_stream(nullptr) {} + }; + + static const pw_core_events core_events; + static const pw_registry_events registry_events; + static const pw_node_events node_events; + static const pw_metadata_events default_events; + static const pw_stream_events stream_events; + + std::map m_nodes; + std::map m_node_osdid_to_id; + + std::map m_streams; + + pw_thread_loop *m_loop; + pw_context *m_context; + pw_core *m_core; + spa_hook m_core_listener; + pw_registry *m_registry; + spa_hook m_registry_listener; + pw_metadata *m_default; + spa_hook m_default_listener; + + std::string m_default_audio_sink; + std::string m_default_audio_source; + + uint32_t m_node_current_id, m_stream_current_id; + uint32_t m_generation; + bool m_wait_sync, m_wait_stream; + + void sync(); + + void core_event_done(uint32_t id, int seq); + static void s_core_event_done(void *data, uint32_t id, int seq); + + void register_node(uint32_t id, const spa_dict *props); + void register_port(uint32_t id, const spa_dict *props); + void register_link(uint32_t id, const spa_dict *props); + void register_default_metadata(uint32_t id); + void register_metadata(uint32_t id, const spa_dict *props); + void registry_event_global(uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props); + static void s_registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props); + + void registry_event_global_remove(uint32_t id); + static void s_registry_event_global_remove(void *data, uint32_t id); + + void node_event_param(node_info *node, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param); + static void s_node_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param); + + int default_event_property(uint32_t subject, const char *key, const char *type, const char *value); + static int s_default_event_property(void *data, uint32_t subject, const char *key, const char *type, const char *value); + + void stream_event_process(stream_info *stream); + static void s_stream_event_process(void *data); + + void stream_event_param_changed(stream_info *stream, uint32_t id, const spa_pod *param); + static void s_stream_event_param_changed(void *data, uint32_t id, const spa_pod *param); +}; + +// Try to more or less map to speaker.h positions + +const sound_pipewire::position_info sound_pipewire::position_infos[] = { + { SPA_AUDIO_CHANNEL_MONO, { 0.0, 0.0, 1.0 } }, + { SPA_AUDIO_CHANNEL_FL, { -0.2, 0.0, 1.0 } }, + { SPA_AUDIO_CHANNEL_FR, { 0.2, 0.0, 1.0 } }, + { SPA_AUDIO_CHANNEL_FC, { 0.0, 0.0, 1.0 } }, + { SPA_AUDIO_CHANNEL_LFE, { 0.0, -0.5, 1.0 } }, + { SPA_AUDIO_CHANNEL_RL, { -0.2, 0.0, -0.5 } }, + { SPA_AUDIO_CHANNEL_RR, { 0.2, 0.0, -0.5 } }, + { SPA_AUDIO_CHANNEL_RC, { 0.0, 0.0, -0.5 } }, + { SPA_AUDIO_CHANNEL_UNKNOWN, { 0.0, 0.0, 0.0 } } +}; + + +const char *const sound_pipewire::typenames[] = { + "Audio recorder", "Speaker" +}; + +const pw_core_events sound_pipewire::core_events = { + PW_VERSION_CORE_EVENTS, + nullptr, // info + s_core_event_done, + nullptr, // ping + nullptr, // error + nullptr, // remove_id + nullptr, // bound_id + nullptr, // add_mem + nullptr, // remove_mem + nullptr // bound_props +}; + +const pw_registry_events sound_pipewire::registry_events = { + PW_VERSION_REGISTRY_EVENTS, + s_registry_event_global, + s_registry_event_global_remove +}; + +const pw_node_events sound_pipewire::node_events = { + PW_VERSION_NODE_EVENTS, + nullptr, // info + s_node_event_param +}; + +const pw_metadata_events sound_pipewire::default_events = { + PW_VERSION_METADATA_EVENTS, + s_default_event_property +}; + +const pw_stream_events sound_pipewire::stream_events = { + PW_VERSION_STREAM_EVENTS, + nullptr, // destroy + nullptr, // state changed + nullptr, // control info + nullptr, // io changed + s_stream_event_param_changed, + nullptr, // add buffer + nullptr, // remove buffer + s_stream_event_process, + nullptr, // drained + nullptr, // command + nullptr // trigger done +}; + +void sound_pipewire::register_node(uint32_t id, const spa_dict *props) +{ + const spa_dict_item *cls = spa_dict_lookup_item(props, PW_KEY_MEDIA_CLASS); + const spa_dict_item *desc = spa_dict_lookup_item(props, PW_KEY_NODE_DESCRIPTION); + const spa_dict_item *name = spa_dict_lookup_item(props, PW_KEY_NODE_NAME); + const spa_dict_item *serial = spa_dict_lookup_item(props, PW_KEY_OBJECT_SERIAL); + if(!cls) + return; + int type; + if(!strcmp(cls->value, "Audio/Source")) + type = AREC; + else if(!strcmp(cls->value, "Audio/Sink")) + type = APLAY; + else + return; + + m_node_osdid_to_id[m_node_current_id] = id; + auto &node = m_nodes.emplace(id, node_info(this, id, m_node_current_id++, type, serial->value, desc ? desc->value : "?", name ? name->value : "?")).first->second; + + // printf("node %03x: %s %s %s | %s\n", node.m_id, serial->value, typenames[node.m_type], node.m_name.c_str(), node.m_text_id.c_str()); + + node.m_node = (pw_node *)pw_registry_bind(m_registry, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); + pw_node_add_listener(node.m_node, &node.m_node_listener, &node_events, &node); + pw_node_enum_params(node.m_node, 0, 3, 0, 0xffffffff, nullptr); + m_generation++; +} + +void sound_pipewire::register_port(uint32_t id, const spa_dict *props) +{ + uint32_t node = strtol(spa_dict_lookup_item(props, PW_KEY_NODE_ID)->value, nullptr, 10); + auto ind = m_nodes.find(node); + if(ind == m_nodes.end()) + return; + + const spa_dict_item *channel = spa_dict_lookup_item(props, PW_KEY_AUDIO_CHANNEL); + const spa_dict_item *dir = spa_dict_lookup_item(props, PW_KEY_PORT_DIRECTION); + uint32_t index = strtol(spa_dict_lookup_item(props, PW_KEY_PORT_ID)->value, nullptr, 10); + + auto &port_names = dir && !strcmp(dir->value, "in") ? ind->second.m_sink_port_names : ind->second.m_source_port_names; + if(port_names.size() <= index) + port_names.resize(index+1); + + port_names[index] = channel ? channel->value : "?"; + + m_generation++; + // printf("port %03x.%d %03x: %s\n", node, index, id, port_names[index].c_str()); +} + +void sound_pipewire::register_link(uint32_t id, const spa_dict *props) +{ + const spa_dict_item *input = spa_dict_lookup_item(props, PW_KEY_LINK_INPUT_NODE); + const spa_dict_item *output = spa_dict_lookup_item(props, PW_KEY_LINK_OUTPUT_NODE); + if(!input || !output) + return; + + uint32_t input_id = strtol(input->value, nullptr, 10); + uint32_t output_id = strtol(output->value, nullptr, 10); + + for(auto &si : m_streams) { + stream_info &stream = si.second; + if(stream.m_source_node_id == output_id && (stream.m_target_node && stream.m_target_node->m_id != input_id)) { + auto ni = m_nodes.find(input_id); + if(ni != m_nodes.end()) { + stream.m_target_node = &ni->second; + m_generation ++; + } + } + } +} + +void sound_pipewire::register_default_metadata(uint32_t id) +{ + m_default = (pw_metadata *)pw_registry_bind(m_registry, id, PW_TYPE_INTERFACE_Metadata, PW_VERSION_METADATA, 0); + pw_metadata_add_listener(m_default, &m_default_listener, &default_events, this); +} + +void sound_pipewire::register_metadata(uint32_t id, const spa_dict *props) +{ + const spa_dict_item *mn = spa_dict_lookup_item(props, PW_KEY_METADATA_NAME); + if(mn && !strcmp(mn->value, "default")) + register_default_metadata(id); +} + +void sound_pipewire::registry_event_global(uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const spa_dict *props) +{ + if(!strcmp(type, PW_TYPE_INTERFACE_Node)) + register_node(id, props); + else if(!strcmp(type, PW_TYPE_INTERFACE_Port)) + register_port(id, props); + else if(!strcmp(type, PW_TYPE_INTERFACE_Metadata)) + register_metadata(id, props); + else if(!strcmp(type, PW_TYPE_INTERFACE_Link)) + register_link(id, props); + else { + // printf("type %03x %s\n", id, type); + } +} + +void sound_pipewire::s_registry_event_global(void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const spa_dict *props) +{ + ((sound_pipewire *)data)->registry_event_global(id, permissions, type, version, props); +} + +void sound_pipewire::registry_event_global_remove(uint32_t id) +{ + auto ind = m_nodes.find(id); + if(ind == m_nodes.end()) + return; + + for(auto &istream : m_streams) + if(istream.second.m_target_node == &ind->second) + istream.second.m_target_node = nullptr; + m_nodes.erase(ind); + m_generation++; +} + +void sound_pipewire::s_registry_event_global_remove(void *data, uint32_t id) +{ + ((sound_pipewire *)data)->registry_event_global_remove(id); +} + +void sound_pipewire::node_event_param(node_info *node, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) +{ + if(id == SPA_PARAM_EnumFormat) { + const spa_pod_prop *subtype = spa_pod_find_prop(param, nullptr, SPA_FORMAT_mediaSubtype); + if(subtype) { + uint32_t sval; + if(!spa_pod_get_id(&subtype->value, &sval)) { + if(sval == SPA_MEDIA_SUBTYPE_raw) { + const spa_pod_prop *format = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_format); + const spa_pod_prop *rate = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate); + const spa_pod_prop *position = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position); + + if(format) { + uint32_t *entry; + SPA_POD_CHOICE_FOREACH((spa_pod_choice *)(&format->value), entry) { + if(*entry == SPA_AUDIO_FORMAT_S16) + node->m_has_s16 = true; + } + } + if(rate) { + if(rate->value.type == SPA_TYPE_Choice) { + struct spa_pod_choice_body *b = &((spa_pod_choice *)(&rate->value))->body; + uint32_t *choices = (uint32_t *)(b+1); + node->m_rate.m_default_rate = choices[0]; + if(b->type == SPA_CHOICE_Range) { + node->m_rate.m_min_rate = choices[1]; + node->m_rate.m_max_rate = choices[2]; + } else { + node->m_rate.m_min_rate = node->m_rate.m_default_rate; + node->m_rate.m_max_rate = node->m_rate.m_default_rate; + } + } + } + + if(position) { + uint32_t *entry; + node->m_position_codes.clear(); + node->m_positions.clear(); + SPA_POD_ARRAY_FOREACH((spa_pod_array *)(&position->value), entry) { + node->m_position_codes.push_back(*entry); + for(uint32_t i = 0;; i++) { + if((position_infos[i].m_position == *entry) || (position_infos[i].m_position == SPA_AUDIO_CHANNEL_UNKNOWN)) { + node->m_positions.push_back(position_infos[i].m_coords); + break; + } + } + } + } + } else if(sval == SPA_MEDIA_SUBTYPE_iec958) + node->m_has_iec958 = true; + } + } + m_generation++; + + } else + spa_debug_pod(2, nullptr, param); +} + +void sound_pipewire::s_node_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) +{ + node_info *n = (node_info *)data; + n->m_wire->node_event_param(n, seq, id, index, next, param); +} + +int sound_pipewire::default_event_property(uint32_t subject, const char *key, const char *type, const char *value) +{ + if(!value) + return 0; + std::string val = value; + if(!strcmp(type, "Spa:String:JSON")) { + rapidjson::Document json; + json.Parse(value); + if(json.IsObject() && json.HasMember("name") && json["name"].IsString()) + val = json["name"].GetString(); + } else + val = value; + + if(!strcmp(key, "default.audio.sink")) + m_default_audio_sink = val; + + else if(!strcmp(key, "default.audio.source")) + m_default_audio_source = val; + + return 0; +} + +int sound_pipewire::s_default_event_property(void *data, uint32_t subject, const char *key, const char *type, const char *value) +{ + return ((sound_pipewire *)data)->default_event_property(subject, key, type, value); +} + +int sound_pipewire::init(osd_interface &osd, osd_options const &options) +{ + spa_zero(m_core_listener); + spa_zero(m_registry_listener); + spa_zero(m_default_listener); + + m_node_current_id = 1; + m_stream_current_id = 1; + m_generation = 1; + + m_wait_sync = false; + m_wait_stream = false; + + pw_init(nullptr, nullptr); + m_loop = pw_thread_loop_new(nullptr, nullptr); + m_context = pw_context_new(pw_thread_loop_get_loop(m_loop), nullptr, 0); + m_core = pw_context_connect(m_context, nullptr, 0); + + if(!m_core) + return 1; + + pw_core_add_listener(m_core, &m_core_listener, &core_events, this); + + m_registry = pw_core_get_registry(m_core, PW_VERSION_REGISTRY, 0); + pw_registry_add_listener(m_registry, &m_registry_listener, ®istry_events, this); + + pw_thread_loop_start(m_loop); + + // The first sync ensures that the initial information request is + // completed, the second that the subsequent ones (parameters + // retrieval) are completed too. + sync(); + sync(); + + return 0; +} + +void sound_pipewire::core_event_done(uint32_t id, int seq) +{ + m_wait_sync = false; + pw_thread_loop_signal(m_loop, false); +} + +void sound_pipewire::s_core_event_done(void *data, uint32_t id, int seq) +{ + ((sound_pipewire *)data)->core_event_done(id, seq); +} + +void sound_pipewire::sync() +{ + pw_thread_loop_lock(m_loop); + m_wait_sync = true; + pw_core_sync(m_core, PW_ID_CORE, 0); + while(m_wait_sync) + pw_thread_loop_wait(m_loop); + pw_thread_loop_unlock(m_loop); +} + +void sound_pipewire::exit() +{ + pw_thread_loop_lock(m_loop); + for(const auto &si : m_streams) + pw_stream_destroy(si.second.m_stream); + for(const auto &ni : m_nodes) + pw_proxy_destroy((pw_proxy *)ni.second.m_node); + pw_proxy_destroy((pw_proxy *)m_default); + pw_proxy_destroy((pw_proxy *)m_registry); + pw_thread_loop_unlock(m_loop); + pw_core_disconnect(m_core); + pw_context_destroy(m_context); + pw_thread_loop_stop(m_loop); + pw_thread_loop_destroy(m_loop); + pw_deinit(); +} + +uint32_t sound_pipewire::get_generation() +{ + pw_thread_loop_lock(m_loop); + uint32_t result = m_generation; + pw_thread_loop_unlock(m_loop); + return result; +} + +osd::audio_info sound_pipewire::get_information() +{ + osd::audio_info result; + pw_thread_loop_lock(m_loop); + result.m_nodes.resize(m_nodes.size()); + result.m_default_sink = 0; + result.m_default_source = 0; + result.m_generation = m_generation; + uint32_t node = 0; + for(auto &inode : m_nodes) { + result.m_nodes[node].m_name = inode.second.m_name; + result.m_nodes[node].m_id = inode.second.m_osdid; + result.m_nodes[node].m_rate = inode.second.m_rate; + result.m_nodes[node].m_sinks.resize(inode.second.m_sink_port_names.size()); + + for(uint32_t port = 0; port != inode.second.m_sink_port_names.size(); port++) { + result.m_nodes[node].m_sinks[port].m_name = inode.second.m_sink_port_names[port]; + result.m_nodes[node].m_sinks[port].m_position = inode.second.m_positions[port]; + } + + result.m_nodes[node].m_sources.resize(inode.second.m_source_port_names.size()); + + for(uint32_t port = 0; port != inode.second.m_source_port_names.size(); port++) { + result.m_nodes[node].m_sources[port].m_name = inode.second.m_source_port_names[port]; + result.m_nodes[node].m_sources[port].m_position = inode.second.m_positions[port]; + } + + if(inode.second.m_text_id == m_default_audio_sink) + result.m_default_sink = inode.second.m_osdid; + if(inode.second.m_text_id == m_default_audio_source) + result.m_default_source = inode.second.m_osdid; + node ++; + } + + for(auto &istream : m_streams) + if(istream.second.m_target_node) + result.m_streams.emplace_back(osd::audio_info::stream_info { istream.second.m_osdid, istream.second.m_target_node->m_osdid, istream.second.m_volumes }); + + pw_thread_loop_unlock(m_loop); + return result; +} + +void sound_pipewire::stream_event_process(stream_info *stream) +{ + pw_buffer *buffer = pw_stream_dequeue_buffer(stream->m_stream); + if(!buffer) + return; + + spa_buffer *sbuf = buffer->buffer; + uint32_t request = buffer->requested; + int16_t *data = (int16_t *)(sbuf->datas[0].data); + uint32_t pos = 0; + while(pos != request) { + if(stream->m_buffers.empty()) { + while(pos != request) { + memcpy(data, stream->m_last_sample.data(), stream->m_channels*2); + data += stream->m_channels; + pos ++; + } + break; + } + + auto &buf = stream->m_buffers.front(); + uint32_t avail = buf.m_data.size() / stream->m_channels - buf.m_cpos; + if(avail > request - pos) { + avail = request - pos; + memcpy(data, buf.m_data.data() + buf.m_cpos * stream->m_channels, avail*2*stream->m_channels); + buf.m_cpos += avail; + break; + } + + memcpy(data, buf.m_data.data() + buf.m_cpos * stream->m_channels, avail*2*stream->m_channels); + stream->m_buffers.erase(stream->m_buffers.begin()); + pos += avail; + data += avail * stream->m_channels; + } + + sbuf->datas[0].chunk->offset = 0; + sbuf->datas[0].chunk->stride = stream->m_channels*2; + sbuf->datas[0].chunk->size = request*stream->m_channels*2; + + pw_stream_queue_buffer(stream->m_stream, buffer); +} + +void sound_pipewire::s_stream_event_process(void *data) +{ + stream_info *info = (stream_info *)(data); + info->m_wire->stream_event_process(info); +} + +void sound_pipewire::stream_event_param_changed(stream_info *stream, uint32_t id, const spa_pod *param) +{ + if(id == SPA_PARAM_Props) { + const spa_pod_prop *vols = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes); + if(vols) { + bool initial = stream->m_volumes.empty(); + stream->m_volumes.clear(); + float *entry; + SPA_POD_ARRAY_FOREACH((spa_pod_array *)(&vols->value), entry) { + stream->m_volumes.push_back(osd::linear_to_db(*entry)); + } + if(!stream->m_volumes.empty()) { + if(initial) { + m_wait_stream = false; + pw_thread_loop_signal(m_loop, false); + } else + m_generation++; + } + } + } +} + +void sound_pipewire::s_stream_event_param_changed(void *data, uint32_t id, const spa_pod *param) +{ + stream_info *info = (stream_info *)(data); + info->m_wire->stream_event_param_changed(info, id, param); +} + +uint32_t sound_pipewire::stream_sink_open(uint32_t node, std::string name, uint32_t rate) +{ + pw_thread_loop_lock(m_loop); + auto ni = m_node_osdid_to_id.find(node); + if(ni == m_node_osdid_to_id.end()) { + pw_thread_loop_unlock(m_loop); + return 0; + } + node_info &snode = m_nodes.find(ni->second)->second; + + uint32_t id = m_stream_current_id++; + auto &stream = m_streams.emplace(id, stream_info(this, id)).first->second; + + stream.m_stream = pw_stream_new_simple(pw_thread_loop_get_loop(m_loop), + name.c_str(), + pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Playback", + PW_KEY_MEDIA_ROLE, "Game", + PW_KEY_TARGET_OBJECT, snode.m_serial.c_str(), + nullptr), + &stream_events, + &stream); + stream.m_channels = snode.m_sink_port_names.size(); + stream.m_last_sample.resize(stream.m_channels); + stream.m_target_node = &snode; + + const spa_pod *params; + spa_audio_info_raw format = { + SPA_AUDIO_FORMAT_S16, + 0, + rate, + stream.m_channels + }; + for(uint32_t i=0; i != snode.m_sink_port_names.size(); i++) + format.position[i] = snode.m_position_codes[i]; + + uint8_t buffer[1024]; + spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &format); + + pw_stream_connect(stream.m_stream, + PW_DIRECTION_OUTPUT, + PW_ID_ANY, + pw_stream_flags(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS), + ¶ms, 1); + + m_wait_stream = true; + while(m_wait_stream) + pw_thread_loop_wait(m_loop); + pw_thread_loop_unlock(m_loop); + + stream.m_source_node_id = pw_stream_get_node_id(stream.m_stream); + + return id; +} + +void sound_pipewire::stream_set_volumes(uint32_t id, const std::vector &db) +{ + pw_thread_loop_lock(m_loop); + auto si = m_streams.find(id); + if(si == m_streams.end()) { + pw_thread_loop_unlock(m_loop); + return; + } + stream_info &stream = si->second; + stream.m_volumes = db; + std::vector linear; + for(float db1 : db) + linear.push_back(osd::db_to_linear(db1)); + pw_stream_set_control(stream.m_stream, SPA_PROP_channelVolumes, linear.size(), linear.data(), 0); + pw_thread_loop_unlock(m_loop); +} + +void sound_pipewire::stream_close(uint32_t id) +{ + pw_thread_loop_lock(m_loop); + auto si = m_streams.find(id); + if(si == m_streams.end()) { + pw_thread_loop_unlock(m_loop); + return; + } + stream_info &stream = si->second; + pw_stream_destroy(stream.m_stream); + m_streams.erase(si); + pw_thread_loop_unlock(m_loop); +} + +void sound_pipewire::stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) +{ + pw_thread_loop_lock(m_loop); + auto si = m_streams.find(id); + if(si == m_streams.end()) { + pw_thread_loop_unlock(m_loop); + return; + } + stream_info &stream = si->second; + + stream.m_buffers.resize(stream.m_buffers.size() + 1); + auto &buf = stream.m_buffers.back(); + buf.m_cpos = 0; + buf.m_data.resize(samples_this_frame*stream.m_channels); + memcpy(buf.m_data.data(), buffer, samples_this_frame*2*stream.m_channels); + memcpy(stream.m_last_sample.data(), buffer + (samples_this_frame-1)*stream.m_channels, 2*stream.m_channels); + + if(stream.m_buffers.size() > 10) + // If there way too many buffers, drop some so only 10 are left (roughly 0.2s) + stream.m_buffers.erase(stream.m_buffers.begin(), stream.m_buffers.begin() + stream.m_buffers.size() - 10); + + else if(stream.m_buffers.size() >= 5) + // If there are too many buffers, remove five sample per buffer + // to slowly resync to reduce latency (4 seconds to + // compensate one buffer roughly) + buf.m_cpos = 5; + + pw_thread_loop_unlock(m_loop); +} + +#else + MODULE_NOT_SUPPORTED(sound_pipewire, OSD_SOUND_PROVIDER, "pipewire") +#endif + +MODULE_DEFINITION(SOUND_PIPEWIRE, sound_pipewire) diff --git a/src/osd/modules/sound/pulse_sound.cpp b/src/osd/modules/sound/pulse_sound.cpp index 373913f9f9116..81c121ab10c0e 100644 --- a/src/osd/modules/sound/pulse_sound.cpp +++ b/src/osd/modules/sound/pulse_sound.cpp @@ -40,8 +40,7 @@ class sound_pulse : public osd_module, public sound_module virtual int init(osd_interface &osd, osd_options const &options) override; virtual void exit() override; - virtual void update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; + virtual void stream_update(uint32_t, const s16 *buffer, int samples_this_frame) override; private: struct abuffer { @@ -58,15 +57,10 @@ class sound_pulse : public osd_module, public sound_module std::vector m_buffers; u32 m_last_sample; - int m_new_volume_value; - bool m_setting_volume; - bool m_new_volume; int m_pipe_to_sub[2]; int m_pipe_to_main[2]; - static void i_volume_set_notify(pa_context *, int success, void *self); - void volume_set_notify(int success); static void i_context_notify(pa_context *, void *self); void context_notify(); static void i_stream_notify(pa_stream *, void *self); @@ -276,9 +270,6 @@ void sound_pulse::stop_mainloop(int err) int sound_pulse::init(osd_interface &osd, osd_options const &options) { m_last_sample = 0; - m_setting_volume = false; - m_new_volume = false; - m_new_volume_value = 0; m_mainloop = pa_mainloop_new(); m_context = pa_context_new(pa_mainloop_get_api(m_mainloop), "MAME"); @@ -327,7 +318,7 @@ int sound_pulse::init(osd_interface &osd, osd_options const &options) return 0; } -void sound_pulse::update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) +void sound_pulse::stream_update(uint32_t, const s16 *buffer, int samples_this_frame) { std::unique_lock lock(m_mutex); m_buffers.resize(m_buffers.size() + 1); @@ -348,41 +339,6 @@ void sound_pulse::update_audio_stream(bool is_throttled, const s16 *buffer, int buf.cpos = 5; } -void sound_pulse::volume_set_notify(int success) -{ - std::unique_lock lock(m_mutex); - if(m_new_volume) { - m_new_volume = false; - pa_cvolume vol; - pa_cvolume_set(&vol, 2, pa_sw_volume_from_dB(m_new_volume_value)); - pa_context_set_sink_input_volume(m_context, pa_stream_get_index(m_stream), &vol, i_volume_set_notify, this); - } else - m_setting_volume = false; -} - -void sound_pulse::i_volume_set_notify(pa_context *, int success, void *self) -{ - static_cast(self)->volume_set_notify(success); -} - -void sound_pulse::set_mastervolume(int attenuation) -{ - if(!m_stream) - return; - - - std::unique_lock lock(m_mutex); - if(m_setting_volume) { - m_new_volume = true; - m_new_volume_value = attenuation; - } else { - m_setting_volume = true; - pa_cvolume vol; - pa_cvolume_set(&vol, 2, pa_sw_volume_from_dB(attenuation)); - pa_context_set_sink_input_volume(m_context, pa_stream_get_index(m_stream), &vol, i_volume_set_notify, this); - } -} - void sound_pulse::exit() { if(!m_stream) diff --git a/src/osd/modules/sound/sdl_sound.cpp b/src/osd/modules/sound/sdl_sound.cpp index ba7082312eac8..0340fdaca0da7 100644 --- a/src/osd/modules/sound/sdl_sound.cpp +++ b/src/osd/modules/sound/sdl_sound.cpp @@ -54,7 +54,6 @@ class sound_sdl : public osd_module, public sound_module sample_rate(0), sdl_xfer_samples(SDL_XFER_SAMPLES), stream_in_initialized(0), - attenuation(0), buf_locked(0), stream_buffer(nullptr), stream_buffer_size(0), @@ -69,8 +68,7 @@ class sound_sdl : public osd_module, public sound_module // sound_module - virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) override; - virtual void set_mastervolume(int attenuation) override; + virtual void stream_update(uint32_t, const int16_t *buffer, int samples_this_frame) override; private: class ring_buffer @@ -93,15 +91,13 @@ class sound_sdl : public osd_module, public sound_module void lock_buffer(); void unlock_buffer(); - void attenuate(int16_t *data, int bytes); - void copy_sample_data(bool is_throttled, const int16_t *data, int bytes_to_copy); + void copy_sample_data(const int16_t *data, int bytes_to_copy); int sdl_create_buffers(); void sdl_destroy_buffers(); int sample_rate; int sdl_xfer_samples; int stream_in_initialized; - int attenuation; int buf_locked; std::unique_ptr stream_buffer; @@ -214,27 +210,11 @@ void sound_sdl::unlock_buffer() } -//============================================================ -// Apply attenuation -//============================================================ - -void sound_sdl::attenuate(int16_t *data, int bytes_to_copy) -{ - int level = (int) (pow(10.0, (double) attenuation / 20.0) * 128.0); - int count = bytes_to_copy / sizeof(*data); - while (count > 0) - { - *data = (*data * level) >> 7; /* / 128 */ - data++; - count--; - } -} - //============================================================ // copy_sample_data //============================================================ -void sound_sdl::copy_sample_data(bool is_throttled, const int16_t *data, int bytes_to_copy) +void sound_sdl::copy_sample_data(const int16_t *data, int bytes_to_copy) { lock_buffer(); int const err = stream_buffer->append(data, bytes_to_copy); @@ -246,10 +226,10 @@ void sound_sdl::copy_sample_data(bool is_throttled, const int16_t *data, int byt //============================================================ -// update_audio_stream +// stream_update //============================================================ -void sound_sdl::update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) +void sound_sdl::stream_update(uint32_t, const int16_t *buffer, int samples_this_frame) { // if nothing to do, don't do it if (sample_rate == 0 || !stream_buffer) @@ -279,7 +259,7 @@ void sound_sdl::update_audio_stream(bool is_throttled, const int16_t *buffer, in return; } - copy_sample_data(is_throttled, buffer, bytes_this_frame); + copy_sample_data(buffer, bytes_this_frame); size_t nfree_size = stream_buffer->free_size(); size_t ndata_size = stream_buffer->data_size(); @@ -289,24 +269,6 @@ void sound_sdl::update_audio_stream(bool is_throttled, const int16_t *buffer, in -//============================================================ -// set_mastervolume -//============================================================ - -void sound_sdl::set_mastervolume(int _attenuation) -{ - // clamp the attenuation to 0-32 range - attenuation = std::clamp(_attenuation, -32, 0); - - if (stream_in_initialized) - { - if (attenuation == -32) - SDL_PauseAudio(1); - else - SDL_PauseAudio(0); - } -} - //============================================================ // sdl_callback //============================================================ @@ -331,8 +293,6 @@ void sound_sdl::sdl_callback(void *userdata, Uint8 *stream, int len) if (LOG_SOUND && err) *thiz->sound_log << "Late detection of underflow. This shouldn't happen.\n"; - thiz->attenuate((int16_t *)stream, len); - if (LOG_SOUND) util::stream_format(*thiz->sound_log, "callback: xfer DS=%u FS=%u Len=%d\n", data_size, free_size, len); } @@ -398,7 +358,6 @@ int sound_sdl::init(osd_interface &osd, const osd_options &options) goto cant_create_buffers; // set the startup volume - set_mastervolume(attenuation); osd_printf_verbose("Audio: End initialization\n"); return 0; diff --git a/src/osd/modules/sound/sound_module.h b/src/osd/modules/sound/sound_module.h index 088da5ea8d0f4..b366e9ffd32ac 100644 --- a/src/osd/modules/sound/sound_module.h +++ b/src/osd/modules/sound/sound_module.h @@ -9,11 +9,12 @@ #pragma once -#include +#include -//============================================================ -// CONSTANTS -//============================================================ +#include +#include +#include +#include #define OSD_SOUND_PROVIDER "sound" @@ -22,8 +23,35 @@ class sound_module public: virtual ~sound_module() = default; - virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) = 0; - virtual void set_mastervolume(int attenuation) = 0; + virtual uint32_t get_generation() { return 1; } + virtual osd::audio_info get_information() { + osd::audio_info result; + result.m_generation = 1; + result.m_default_sink = 0; + result.m_default_source = 0; + result.m_nodes.resize(1); + result.m_nodes[0].m_name = "default"; + result.m_nodes[0].m_id = 1; + result.m_nodes[0].m_rate.m_default_rate = 0; // Magic value meaning "use configured sample rate" + result.m_nodes[0].m_rate.m_min_rate = 0; + result.m_nodes[0].m_rate.m_max_rate = 0; + result.m_nodes[0].m_sinks.resize(2); + result.m_nodes[0].m_sinks[0].m_name = "L"; + result.m_nodes[0].m_sinks[1].m_name = "R"; + result.m_nodes[0].m_sinks[0].m_position = std::array{ -0.2, 0.0, 1.0 }; + result.m_nodes[0].m_sinks[1].m_position = std::array{ 0.2, 0.0, 1.0 }; + result.m_streams.resize(1); + result.m_streams[0].m_id = 1; + result.m_streams[0].m_node = 1; + return result; + } + virtual bool external_per_channel_volume() { return false; } + virtual bool split_streams_per_source() { return false; } + + virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) { return 1; } + virtual void stream_set_volumes(uint32_t id, const std::vector &db) {} + virtual void stream_close(uint32_t id) {} + virtual void stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) {} }; #endif // MAME_OSD_SOUND_SOUND_MODULE_H diff --git a/src/osd/modules/sound/xaudio2_sound.cpp b/src/osd/modules/sound/xaudio2_sound.cpp index d0243ba1267ce..34973c4e0cb29 100644 --- a/src/osd/modules/sound/xaudio2_sound.cpp +++ b/src/osd/modules/sound/xaudio2_sound.cpp @@ -214,8 +214,7 @@ class sound_xaudio2 : public osd_module, public sound_module, public IXAudio2Voi void exit() override; // sound_module - void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override; - void set_mastervolume(int attenuation) override; + void stream_update(uint32_t, int16_t const *buffer, int samples_this_frame) override; private: // Xaudio callbacks @@ -376,11 +375,11 @@ void sound_xaudio2::exit() } //============================================================ -// update_audio_stream +// stream_update //============================================================ -void sound_xaudio2::update_audio_stream( - bool is_throttled, +void sound_xaudio2::stream_update( + uint32_t, int16_t const *buffer, int samples_this_frame) { @@ -413,32 +412,6 @@ void sound_xaudio2::update_audio_stream( SetEvent(m_hEventDataAvailable); } -//============================================================ -// set_mastervolume -//============================================================ - -void sound_xaudio2::set_mastervolume(int attenuation) -{ - if (!m_initialized) - return; - - assert(m_sourceVoice); - - HRESULT result; - - // clamp the attenuation to 0-32 range - attenuation = std::clamp(attenuation, -32, 0); - - // Ranges from 1.0 to XAUDIO2_MAX_VOLUME_LEVEL indicate additional gain - // Ranges from 0 to 1.0 indicate a reduced volume level - // 0 indicates silence - // We only support a reduction from 1.0, so we generate values in the range 0.0 to 1.0 - float scaledVolume = (32.0f + attenuation) / 32.0f; - - // set the master volume - HR_RETV(m_sourceVoice->SetVolume(scaledVolume)); -} - //============================================================ // IXAudio2VoiceCallback::OnBufferEnd //============================================================ diff --git a/src/osd/osdepend.h b/src/osd/osdepend.h index 9824f16ebfe1c..5f6013c703823 100644 --- a/src/osd/osdepend.h +++ b/src/osd/osdepend.h @@ -16,13 +16,14 @@ #include "emufwd.h" #include "bitmap.h" +#include "interface/audio.h" #include #include #include #include #include - +#include // forward references class input_type_entry; @@ -63,7 +64,6 @@ class osd_font class osd_interface { public: - // general overridables virtual void init(running_machine &machine) = 0; virtual void update(bool skip_redraw) = 0; @@ -76,9 +76,15 @@ class osd_interface virtual void wait_for_debugger(device_t &device, bool firststop) = 0; // audio overridables - virtual void update_audio_stream(const int16_t *buffer, int samples_this_frame) = 0; - virtual void set_mastervolume(int attenuation) = 0; virtual bool no_sound() = 0; + virtual bool sound_external_per_channel_volume() = 0; + virtual bool sound_split_streams_per_source() = 0; + virtual uint32_t sound_get_generation() = 0; + virtual osd::audio_info sound_get_information() = 0; + virtual uint32_t sound_stream_sink_open(uint32_t node, std::string name, uint32_t rate) = 0; + virtual void sound_stream_close(uint32_t id) = 0; + virtual void sound_stream_update(uint32_t id, const int16_t *buffer, int samples_this_frame) = 0; + virtual void sound_stream_set_volumes(uint32_t id, const std::vector &db) = 0; // input overridables virtual void customize_input_type_list(std::vector &typelist) = 0;