diff options
Diffstat (limited to 'docs/source/techspecs')
-rw-r--r-- | docs/source/techspecs/audio_effects.rst | 147 | ||||
-rw-r--r-- | docs/source/techspecs/device_sound_interface.rst | 286 | ||||
-rw-r--r-- | docs/source/techspecs/index.rst | 3 | ||||
-rw-r--r-- | docs/source/techspecs/m6502.rst | 4 | ||||
-rw-r--r-- | docs/source/techspecs/osd_audio.rst | 348 |
5 files changed, 786 insertions, 2 deletions
diff --git a/docs/source/techspecs/audio_effects.rst b/docs/source/techspecs/audio_effects.rst new file mode 100644 index 00000000000..12228b97444 --- /dev/null +++ b/docs/source/techspecs/audio_effects.rst @@ -0,0 +1,147 @@ +Audio effects +============= + +.. contents:: :local: + + +1. Generalities +--------------- + +The audio effects are effects that are applied to the sound between +the speaker devices and the actual sound output. In the current +implementation the effect chain is fixed (but not the effect +parameters), and the parameters are stored in the cfg files. They are +honestly not really designed for extensibility yet, if ever. + +Adding an effect requires working on four parts: + +* audio_effects/aeffects.* for effect object creation and "publishing" +* audio_effects/youreffect.* for the effect implementation +* frontend/mame/ui/audioeffects.cpp to be able to instantiate the effect configuration menu +* frontend/mame/ui/audioyoureffect.* to implement the effect configuration menu + +2. audio_effects/aeffects.* +--------------------------- + +The audio_effect class in the aeffect sources provides three things: + +* an enum value to designate the effect type and which must match its + position in the chain (iow, the effect chain follows the enum order), + in the .h +* the effect name in the audio_effect::effect_names array in the .cpp +* the creation of a correct effect object in audio_effect::create in the .cpp + + + +3. audio_effects/youreffect.* +----------------------------- + +This is where you implement the effect. It takes the shape of an +audio_effect_youreffect class which derives from audio_effect. + +The methods to implement are: + +.. code-block:: C++ + + audio_effect_youreffect(u32 sample_rate, audio_effect *def); + + virtual int type() const override; + virtual void config_load(util::xml::data_node const *ef_node) override; + virtual void config_save(util::xml::data_node *ef_node) const override; + virtual void default_changed() override; + virtual u32 history_size() const; // optional + +The constructor must pass the parameters to ``audio_effect`` and +initialize the effect parameters. ``type`` must return the enum value +for the effect. ``config_load`` and ``config_save`` should load or +save the effect parameters from/to the cfg file xml tree. +``default_changed`` is called when the parameters in ``m_default`` are +changed and the parameters may need to be updated. ``history_size`` +allows to tell how many samples should still be available of the +previous input frame. Note that this number must not depend on the +parameters and only on the sample rate. + +An effect has a number of parameters that can come from three sources: + +* fixed default value +* equivalent effect object from the default effect chain +* user setting through the UI + +The first two are recognized through the value of ``m_default`` which +gets the value of ``def`` in the constructor. When it's nullptr, the +value to use when not set by the user is the fixed one, otherwise it's +the one in ``m_default``. + +At minimum an effect should have a parameter allowing to bypass it. + +Managing a parameter uses four methods: + +* ``type param() const;`` returns the current parameter value +* ``void set_param(type value);`` sets the current parameter value and marks it as set by the user +* ``bool isset_param() const;`` returns true is the parameter was set by the user +* ``void reset_param();`` resets the parameter to the default value (from m_default or fixed) and marks it as not set by the user + +``config_save`` must only save the user-set parameters. +``config_load`` must retrieve the parameters that are present and mark +them as set by the user and reset all the others. + +Finally the actual implementation goes into the ``apply`` method: + +.. code-block:: C++ + + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override; + +That method takes two buffers with the same number of channels and has +to apply the effect to ``src`` to produce ``dest``. The +``output_buffer_flat`` is non-interleaved with independant per-channel +buffers. + +To make bypassing easier, the ``copy(src, dest)`` method of +audio_effect allows to copy the samples from ``src`` to ``dest`` +without changing them. + +The effect application part should looks like: + +.. code-block:: C++ + + u32 samples = src.available_samples(); + dest.prepare_space(samples); + u32 channels = src.channels(); + + // generate channels * samples results and put them in dest + + dest.commit(samples); + +To get pointers to the buffers, one uses: + +.. code-block:: C++ + + const sample_t *source = src.ptrs(channel, source_index); // source_index must be in [-history_size()..samples-1] + sample_t *destination = dest.ptrw(channel, destination_index); // destination_index must be in [0..samples-1] + +The samples pointed by source and destination are contiguous. The +number of channels will not change from one apply call to another, the +number of samples will vary though. Also the call happens in a +different thread than the main thread and also in a different thread +than the parameter setting calls are made from. + + + + +4. frontend/mame/ui/audioeffects.cpp +------------------------------------ + +Here it suffices to add a creation of the menu +menu_audio_effect_youreffect in menu_audio_effects::handle. The menu +effect will pick the effect names from audio_effect (in aeffect.*). + + +5. frontend/mame/ui/audioyoureffect.* +------------------------------------- + +This is used to implement the configuration menu for the effect. It's +a little complicated because it needs to generate the list of +parameters and their values, set left or right arrow flags depending +on the possible modifications, dim them (FLAG_INVERT) when not set by +the user, and manage the arrows and clear keys to change them. Just +copy an existing one and change it as needed. diff --git a/docs/source/techspecs/device_sound_interface.rst b/docs/source/techspecs/device_sound_interface.rst new file mode 100644 index 00000000000..859d2497477 --- /dev/null +++ b/docs/source/techspecs/device_sound_interface.rst @@ -0,0 +1,286 @@ +The device_sound_interface +========================== + +.. contents:: :local: + + +1. The sound system +------------------- + +The device sound interface is the entry point for devices that handle +sound input and/or output. The sound system is built on the concept +of *streams* which connect devices together with resampling and mixing +applied transparently as needed. Microphones (audio input) and +speakers (audio output) are specific known devices which use the same +interface. + +2. Devices using device_sound_interface +--------------------------------------- + +2.1 Initialisation +~~~~~~~~~~~~~~~~~~ + +Sound streams must be created in the device_start (or +interface_pre_start) method. + +.. code-block:: C++ + + sound_stream *stream_alloc(int inputs, int outputs, int sample_rate, sound_stream_flags flags = STREAM_DEFAULT_FLAGS); + +A stream is created with ``stream_alloc``. It takes the number of +input and output channels, the sample rate and optionally flags. + +The sample rate can be SAMPLE_RATE_INPUT_ADAPTIVE, +SAMPLE_RATE_OUTPUT_ADAPTIVE or SAMPLE_RATE_ADAPTIVE. In that case the +chosen sample rate is the highest one among the inputs, outputs or +both respectively. In case of loop, the chosen sample rate is the +configured global sample rate. + +The only available non-default flag is STREAM_SYNCHRONOUS. When set, +the sound generation method will be called for every sample +individually. It's necessary for DSPs that run a program on every +sample. but on the other hand it's expensive, so only to be used when +required. + +Devices can create multiple streams. It's rare though. Some Yamaha +chips should but don't. Inputs and outputs are numbered from 0 and +arrange all streams in the order they are created. + + +2.2 Sound input/output +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + virtual void sound_stream_update(sound_stream &stream); + +This method is required to be implemented to consume the input samples +and/or compute the output ones. The stream to update for is passed as +the parameter. See the streams section, specifically sample access, +to see how to write the method. + + +2.3 Stream information +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + int inputs() const; + int outputs() const; + std::pair<sound_stream *, int> input_to_stream_input(int inputnum) const; + std::pair<sound_stream *, int> output_to_stream_output(int outputnum) const; + +The method ``inputs`` returns the total number of inputs in the +streams created by the device. The method ``outputs`` similarly +counts the outputs. The other two methods allow to grab the stream +and channel number for the device corresponding to the global input or +output number. + + +2.4 Gain management +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + float input_gain(int inputnum) const; + float output_gain(int outputnum) const; + void set_input_gain(int inputnum, float gain); + void set_output_gain(int outputnum, float gain); + void set_route_gain(int source_channel, device_sound_interface *target, int target_channel, float gain); + + float user_output_gain() const; + float user_output_gain(int outputnum) const; + void set_user_output_gain(float gain); + void set_user_output_gain(int outputnum, float gain); + +Those methods allow to set the gain on every step of the routes +between streams. All gains are multipliers, with default value 1.0. +The steps are, from samples output in ``sound_stream_update`` to +samples read in the next device's ``sound_stream_update``: + +* Per-channel output gain +* Per-channel user output gain +* Per-device user output gain +* Per-route gain +* Per-channel input gain + +The user gains must not be set from the driver, they're for use by the +user interface (the sliders) and are saved in the game configuration. +The other gains are for driver/device use, and are saved in save +states. + + +2.5 Routing setup +~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + device_sound_interface &add_route(u32 output, const device_finder<T, R> &target, double gain, u32 channel = 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 &reset_routes(); + +Routes between devices, e.g. between streams, are set at configuration +time. The method ``add_route`` must be called on the source device +and gives the channel on the source device, the target device, the +gain, and optionally the channel on the target device. The constant +``ALL_OUTPUTS`` can be used to add a route from every channel of the +source to a given channel of the destination. + +The method ``reset_routes`` is used to remove all the routes setup on +a given source device. + + +.. code-block:: C++ + + u32 get_sound_requested_inputs() const; + u32 get_sound_requested_outputs() const; + u64 get_sound_requested_inputs_mask() const; + u64 get_sound_requested_outputs_mask() const; + +Those methods are useful for devices which want to behave differently +depending on what routes are set up on them. You get either the max +number of requested channel plus one (which is the number of channels +when all channels are routed, but is more useful when there are gaps) +or a mask of use for channels 0-63. Note that ``ALL_OUTPUTS`` does +not register any specific output or output count. + + + +3. Streams +---------- + +3.1 Generalities +~~~~~~~~~~~~~~~~ + +Streams are endpoints associated with devices and, when connected +together, ensure the transmission of audio data between them. A +stream has a number of inputs (which can be zero) and outputs (same) +and one sample rate which is common to all inputs and outputs. The +connections are set up at the machine configuration level and the sound +system ensures mixing and resampling is done transparently. + +Samples in streams are encoded as sample_t. In the current +implementation, this is a float. Nominal values are between -1 and 1, +but clamping at the device level is not recommended (unless that's +what happens in hardware of course) because the gain values, volume +and effects can easily avoid saturation. + +They are implemented in the class ``sound_stream``. + + +3.2 Characteristics +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + device_t &device() const; + bool input_adaptive() const; + bool output_adaptive() const; + bool synchronous() const; + u32 input_count() const; + u32 output_count() const; + u32 sample_rate() const; + attotime sample_period() const; + + +3.3 Sample access +~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + s32 samples() const; + + void put(s32 output, s32 index, sample_t sample); + void put_clamp(s32 output, s32 index, sample_t sample, sample_t clamp = 1.0); + void put_int(s32 output, s32 index, s32 sample, s32 max); + void put_int_clamp(s32 output, s32 index, s32 sample, s32 maxclamp); + void add(s32 output, s32 index, sample_t sample); + void add_int(s32 output, s32 index, s32 sample, s32 max); + void fill(s32 output, sample_t value, s32 start, s32 count); + void fill(s32 output, sample_t value, s32 start); + void fill(s32 output, sample_t value); + void copy(s32 output, s32 input, s32 start, s32 count); + void copy(s32 output, s32 input, s32 start); + void copy(s32 output, s32 input); + sample_t get(s32 input, s32 index) const; + sample_t get_output(s32 output, s32 index) const; + +Those are the methods used to implement ``sound_stream_update``. +First ``samples`` tells how many samples to consume and/or generate. +The to-generate samples, if any, are pre-cleared (e.g. set to zero). + +Input samples are retrieved with ``get``, where ``input`` is the +stream channel number and ``index`` the sample number. + +Generated samples are written with the put variants. ``put`` sets a +sample_t in channel ``output`` at position ``index``. ``put_clamp`` +does the same but first clamps the value to +/-``clamp``. ``put_int`` +does it with an integer ``sample`` but pre-divides it by ``max``. +``put_int_clamp`` does the same but also pre-clamps within +-``maxclamp`` and ``maxclamp``-1, which is the normal range for a +2-complement value. + +``add`` and ``add_int`` are similar but add the value of the sample to +what's there instead of replacing. ``get_output`` gets the currently +stored output value. + +``fill`` sets a range of an output channel to a given ``value``. +``start`` tells where to start (default index 0), ``count`` how many +(default up to the end of the buffer). + +``copy`` does the same as fill but gets its value from the indentical +position in an input channel. + +Note that clamping should not be used unless it actually happens in +hardware. Between gains and effects there is a fair chance saturation +can be avoided later in the chain. + + + +3.4 Gain management +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + float user_output_gain() const; + void set_user_output_gain(float gain); + float user_output_gain(s32 output) const; + void set_user_output_gain(s32 output, float gain); + + float input_gain(s32 input) const; + void set_input_gain(s32 input, float gain); + void apply_input_gain(s32 input, float gain); + float output_gain(s32 output) const; + void set_output_gain(s32 output, float gain); + void apply_output_gain(s32 output, float gain); + + +This is similar to the device gain control, with a twist: apply +multiplies the current gain by the given value. + + +3.5 Misc. actions +~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + void set_sample_rate(u32 sample_rate); + void update(); + +The method ``set_sample_rate`` allows to change the sample rate of the +stream. The method ``update`` triggers a call of +``sound_stream_update`` on the stream and the ones it depends on to +reach the current time in terms of samples. + + +4. Devices using device_mixer_interface +--------------------------------------- + +The device mixer interface is used for devices that want to relay +sound in the device tree without acting on it. It's very useful on +for instance slot devices connectors, where the slot device may have +an audio connection with the main system. They are routed like every +other sound device, create the streams automatically and copy input to +output. Nothing needs to be done in the device. diff --git a/docs/source/techspecs/index.rst b/docs/source/techspecs/index.rst index 71f74618965..72c678b4ca6 100644 --- a/docs/source/techspecs/index.rst +++ b/docs/source/techspecs/index.rst @@ -15,6 +15,7 @@ MAME’s source or working on scripts that run within the MAME framework. device_memory_interface device_rom_interface device_disasm_interface + device_sound_interface memory cpu_device floppy @@ -22,3 +23,5 @@ MAME’s source or working on scripts that run within the MAME framework. m6502 uml_instructions poly_manager + audio_effects + osd_audio diff --git a/docs/source/techspecs/m6502.rst b/docs/source/techspecs/m6502.rst index 7d67423abb8..f5d55fc9af4 100644 --- a/docs/source/techspecs/m6502.rst +++ b/docs/source/techspecs/m6502.rst @@ -92,7 +92,7 @@ At a minimum, the class must include a constructor and an enum picking up the co If the CPU has its own dispatch table, the class must also include the declaration (but not definition) of **disasm_entries**, **do_exec_full** and **do_exec_partial**, the declaration and definition of **disasm_disassemble** (identical for all classes but refers to the class-specific **disasm_entries** array) and include the .inc file (which provides the missing definitions). Support for the generation must also be added to CPU.mak. -If the CPU has in addition its own opcodes, their declaration must be done through a macro, see f.i. m65c02. The .inc file will provide the definitions. +If the CPU has in addition its own opcodes, their declaration must be done through a macro, see e.g. m65c02. The .inc file will provide the definitions. Dispatch tables @@ -365,7 +365,7 @@ A negative icount means that the CPU won't be able to do anything for some time Multi-dispatch variants ----------------------- -Some variants currently in the process of being supported change instruction set depending on an internal flag, either switching to a 16-bits mode or changing some register accesses to memory accesses. This is handled by having multiple dispatch tables for the CPU, the d<CPU>.lst not being 257 entries anymore but 256*n+1. The variable **inst_state_base** must select which instruction table to use at a given time. It must be a multiple of 256, and is in fact simply OR-ed to the first instruction byte to get the dispatch table index (aka inst_state). +Some variants currently in the process of being supported change instruction set depending on an internal flag, either switching to a 16-bit mode or changing some register accesses to memory accesses. This is handled by having multiple dispatch tables for the CPU, the d<CPU>.lst not being 257 entries anymore but 256*n+1. The variable **inst_state_base** must select which instruction table to use at a given time. It must be a multiple of 256, and is in fact simply OR-ed to the first instruction byte to get the dispatch table index (aka inst_state). Current TO-DO: -------------- diff --git a/docs/source/techspecs/osd_audio.rst b/docs/source/techspecs/osd_audio.rst new file mode 100644 index 00000000000..82e03d999ef --- /dev/null +++ b/docs/source/techspecs/osd_audio.rst @@ -0,0 +1,348 @@ +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 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 <xxx> 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 <xxx> +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 uint32_t stream_source_open(uint32_t node, std::string name, uint32_t rate) override; + virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) override; + virtual void stream_close(uint32_t id) override; + virtual void stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override; + virtual void stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame) override; + + +The class sound_module provides defaults 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_sink_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-bit 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 node_info { + std::string m_name; + uint32_t m_id; + audio_rate_range m_rate; + std::vector<std::string> m_port_names; + std::vector<std::array<double, 3>> m_port_positions; + uint32_t m_sinks; + uint32_t m_sources; + }; + + struct stream_info { + uint32_t m_id; + uint32_t m_node; + std::vector<float> m_volumes; + }; + + uint32_t m_generation; + uint32_t m_default_sink; + uint32_t m_default_source; + std::vector<node_info> m_nodes; + std::vector<stream_info> 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_port_names: The vector of port names + * m_port_positions: The vector of 3D position of the ports. Refer to + src/emu/speaker.h for the "standard" positions + * m_sinks: Number of sinks (inputs) + * m_sources: Number of sources (outputs) + +* 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 closing does not allow reuse of its ID. + +If a node has both sources and sinks, the sources are *monitors* of +the sinks, e.g. they're loopbacks. They should have the same count in +such a case. + +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 reality. + + +Input and output streams +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override; + virtual uint32_t stream_source_open(uint32_t node, std::string name, uint32_t rate) override; + virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) override; + virtual void stream_close(uint32_t id) override; + virtual void stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override; + virtual void stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame) override; + +Streams are the concept used to send or receive audio from/to the host +audio system. A stream is first opened through *stream_sink_open* for +speakers and *stream_source_open* for microphones 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/microphone_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 when *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_sink_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). + +*stream_source_update* is the equivalent to retrieve data from a node, +writing to the buffer instead of reading from it. The constraints are +identical. + +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. Also the update methods and the +open/close/volume ones may be called at the same time in different +threads. + + +Helper class *abuffer* +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: C++ + + class abuffer { + public: + abuffer(uint32_t channels); + void get(int16_t *data, uint32_t samples); + void push(const int16_t *data, uint32_t samples); + uint32_t channels() const; + }; + +The class *abuffer* is a helper provided by *sound_module* to buffer +audio input or output. It automatically drops data when there is +an overflow and duplicates the last sample on underflow. It must +first be initialized with the number of channels, which can be +retrieved with *channels()* if needed. *push* sends +*samples* * *channels* 16-bit samples in the buffer. *get* retrieves +*samples* * *channels* 16-bit samples from the buffer, on a FIFO basis. + +It is not protected against multithreading, but uses no class +variables. So just don't read and write from one specific abuffer +instance at the same time. The system sound interface mandated +locking should be enough to ensure that. |