// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    sound.h

    Core sound interface functions and definitions.

****************************************************************************

    In MAME, sound is represented as a graph of sound "streams". Each
    stream has a fixed number of inputs and outputs, and is responsible
    for producing sound on demand.

    The graph is driven from the outputs, which are speaker devices.
    These devices are updated on a regular basis (~50 times per second),
    and when an update occurs, the graph is walked from the speaker
    through each input, until all connected streams are up to date.

    Individual streams can also be updated manually. This is important
    for sound chips and CPU-driven devices, who should force any
    affected streams to update prior to making changes.

    Sound streams are *not* part of the device execution model. This is
    very important to understand. If the process of producing the ouput
    stream affects state that might be consumed by an executing device
    (e.g., a CPU), then care must be taken to ensure that the stream is
    updated frequently enough

    The model for timing sound samples is very important and explained
    here. Each stream source has a clock (aka sample rate). Each clock
    edge represents a sample that is held for the duration of one clock
    period. This model has interesting effects:

    For example, if you have a 10Hz clock, and call stream.update() at
    t=0.91, it will compute 10 samples (for clock edges 0.0, 0.1, 0.2,
    ..., 0.7, 0.8, and 0.9). And then if you ask the stream what its
    current end time is (via stream.sample_time()), it will say t=1.0,
    which is in the future, because it knows it will hold that last
    sample until 1.0s.

    Sound generation callbacks are presented with a std::vector of inputs
    and outputs. The vectors contain objects of read_stream_view and
    write_stream_view respectively, which wrap access to a circular buffer
    of samples. Sound generation callbacks are expected to fill all the
    samples described by the outputs' write_stream_view objects. At the
    moment, all outputs have the same sample rate, so the number of samples
    that need to be generated will be consistent across all outputs.

    By default, the inputs will have been resampled to match the output
    sample rate, unless otherwise specified.

***************************************************************************/

#pragma once

#ifndef __EMU_H__
#error Dont include this file directly; include emu.h instead.
#endif

#ifndef MAME_EMU_SOUND_H
#define MAME_EMU_SOUND_H

#include "wavwrite.h"


//**************************************************************************
//  CONSTANTS
//**************************************************************************

// special sample-rate values
constexpr u32 SAMPLE_RATE_INVALID = 0xffffffff;
constexpr u32 SAMPLE_RATE_INPUT_ADAPTIVE = 0xfffffffe;
constexpr u32 SAMPLE_RATE_OUTPUT_ADAPTIVE = 0xfffffffd;

// anything below this sample rate is effectively treated as "off"
constexpr u32 SAMPLE_RATE_MINIMUM = 50;



//**************************************************************************
//  DEBUGGING
//**************************************************************************

// turn this on to enable aggressive assertions and other checks
#ifdef MAME_DEBUG
#define SOUND_DEBUG (1)
#else
#define SOUND_DEBUG (0)
#endif

// if SOUND_DEBUG is on, make assertions fire regardless of MAME_DEBUG
#if (SOUND_DEBUG)
#define sound_assert(x) do { if (!(x)) { osd_printf_error("sound_assert: " #x "\n"); osd_break_into_debugger("sound_assert: " #x "\n"); } } while (0)
#else
#define sound_assert assert
#endif



//**************************************************************************
//  TYPE DEFINITIONS
//**************************************************************************

// ======================> stream_buffer

class stream_buffer
{
	// stream_buffer is an internal class, not directly accessed
	// outside of the classes below
	friend class read_stream_view;
	friend class write_stream_view;
	friend class sound_stream;
	friend class sound_stream_output;

public:
	// the one public bit is the sample type
	using sample_t = float;

private:
	// constructor/destructor
	stream_buffer(u32 sample_rate = 48000);
	~stream_buffer();

	// disable copying of stream_buffers directly
	stream_buffer(stream_buffer const &src) = delete;
	stream_buffer &operator=(stream_buffer const &rhs) = delete;

	// return the current sample rate
	u32 sample_rate() const { return m_sample_rate; }

	// set a new sample rate
	void set_sample_rate(u32 rate, bool resample);

	// return the current sample period in attoseconds
	attoseconds_t sample_period_attoseconds() const { return m_sample_attos; }
	attotime sample_period() const { return attotime(0, m_sample_attos); }

	// return the attotime of the current end of buffer
	attotime end_time() const { return index_time(m_end_sample); }

	// set the ending time (for forced resyncs; generally not used)
	void set_end_time(attotime time)
	{
		m_end_second = time.seconds();
		m_end_sample = u32(time.attoseconds() / m_sample_attos);
	}

	// return the effective buffer size; currently it is a full second of audio
	// at the current sample rate, but this maybe change in the future
	u32 size() const { return m_sample_rate; }

	// read the sample at the given index (clamped); should be valid in all cases
	sample_t get(s32 index) const
	{
		sound_assert(u32(index) < size());
		sample_t value = m_buffer[index];
#if (SOUND_DEBUG)
		sound_assert(!std::isnan(value));
#endif
		return value;
	}

	// write the sample at the given index (clamped)
	void put(s32 index, sample_t data)
	{
		sound_assert(u32(index) < size());
		m_buffer[index] = data;
	}

	// simple helpers to step indexes
	u32 next_index(u32 index) { index++; return (index == size()) ? 0 : index; }
	u32 prev_index(u32 index) { return (index == 0) ? (size() - 1) : (index - 1); }

	// clamp an index to the size of the buffer; allows for indexing +/- one
	// buffers' worth of range
	u32 clamp_index(s32 index) const
	{
		if (index < 0)
			index += size();
		else if (index >= size())
			index -= size();
		sound_assert(index >= 0 && index < size());
		return index;
	}

	// fill the buffer with the given value
	void fill(sample_t value) { std::fill_n(&m_buffer[0], m_buffer.size(), value); }

	// return the attotime of a given index within the buffer
	attotime index_time(s32 index) const;

	// given an attotime, return the buffer index corresponding to it
	u32 time_to_buffer_index(attotime time, bool round_up, bool allow_expansion = false);

	// downsample from our buffer into a temporary buffer
	void backfill_downsample(sample_t *dest, int samples, attotime newend, attotime newperiod);

	// upsample from a temporary buffer into our buffer
	void backfill_upsample(sample_t const *src, int samples, attotime prevend, attotime prevperiod);

	// internal state
	u32 m_end_second;                     // current full second of the buffer end
	u32 m_end_sample;                     // current sample number within the final second
	u32 m_sample_rate;                    // sample rate of the data in the buffer
	attoseconds_t m_sample_attos;         // pre-computed attoseconds per sample
	std::vector<sample_t> m_buffer;       // vector of actual buffer data

#if (SOUND_DEBUG)
public:
	// for debugging, provide an interface to write a WAV stream
	void open_wav(char const *filename);
	void flush_wav();

private:
	// internal debugging state
	util::wav_file_ptr m_wav_file;        // pointer to the current WAV file
	u32 m_last_written = 0;               // last written sample index
#endif
};


// ======================> read_stream_view

class read_stream_view
{
public:
	using sample_t = stream_buffer::sample_t;

protected:
	// private constructor used by write_stream_view that allows for expansion
	read_stream_view(stream_buffer &buffer, attotime start, attotime end) :
		read_stream_view(&buffer, 0, buffer.time_to_buffer_index(end, true, true), 1.0)
	{
		// start has to be set after end, since end can expand the buffer and
		// potentially invalidate start
		m_start = buffer.time_to_buffer_index(start, false);
		normalize_start_end();
	}

public:
	// base constructor to simplify some of the code
	read_stream_view(stream_buffer *buffer, s32 start, s32 end, sample_t gain) :
		m_buffer(buffer),
		m_end(end),
		m_start(start),
		m_gain(gain)
	{
		normalize_start_end();
	}

	// empty constructor so we can live in an array or vector
	read_stream_view() :
		read_stream_view(nullptr, 0, 0, 1.0)
	{
	}

	// constructor that covers the given time period
	read_stream_view(stream_buffer &buffer, attotime start, attotime end, sample_t gain) :
		read_stream_view(&buffer, buffer.time_to_buffer_index(start, false), buffer.time_to_buffer_index(end, true), gain)
	{
	}

	// copy constructor
	read_stream_view(read_stream_view const &src) :
		read_stream_view(src.m_buffer, src.m_start, src.m_end, src.m_gain)
	{
	}

	// copy constructor that sets a different start time
	read_stream_view(read_stream_view const &src, attotime start) :
		read_stream_view(src.m_buffer, src.m_buffer->time_to_buffer_index(start, false), src.m_end, src.m_gain)
	{
	}

	// copy assignment
	read_stream_view &operator=(read_stream_view const &rhs)
	{
		m_buffer = rhs.m_buffer;
		m_start = rhs.m_start;
		m_end = rhs.m_end;
		m_gain = rhs.m_gain;
		normalize_start_end();
		return *this;
	}

	// return the local gain
	sample_t gain() const { return m_gain; }

	// return the sample rate of the data
	u32 sample_rate() const { return m_buffer->sample_rate(); }

	// return the sample period (in attoseconds) of the data
	attoseconds_t sample_period_attoseconds() const { return m_buffer->sample_period_attoseconds(); }
	attotime sample_period() const { return m_buffer->sample_period(); }

	// return the number of samples represented by the buffer
	u32 samples() const { return m_end - m_start; }

	// return the starting or ending time of the buffer
	attotime start_time() const { return m_buffer->index_time(m_start); }
	attotime end_time() const { return m_buffer->index_time(m_end); }

	// set the gain
	read_stream_view &set_gain(float gain) { m_gain = gain; return *this; }

	// apply an additional gain factor
	read_stream_view &apply_gain(float gain) { m_gain *= gain; return *this; }

	// safely fetch a gain-scaled sample from the buffer
	sample_t get(s32 index) const
	{
		sound_assert(u32(index) < samples());
		index += m_start;
		if (index >= m_buffer->size())
			index -= m_buffer->size();
		return m_buffer->get(index) * m_gain;
	}

	// safely fetch a raw sample from the buffer; if you use this, you need to
	// apply the gain yourself for correctness
	sample_t getraw(s32 index) const
	{
		sound_assert(u32(index) < samples());
		index += m_start;
		if (index >= m_buffer->size())
			index -= m_buffer->size();
		return m_buffer->get(index);
	}

protected:
	// normalize start/end
	void normalize_start_end()
	{
		// ensure that end is always greater than start; we'll
		// wrap to the buffer length as needed
		if (m_end < m_start && m_buffer != nullptr)
			m_end += m_buffer->size();
		sound_assert(m_end >= m_start);
	}

	// internal state
	stream_buffer *m_buffer;              // pointer to the stream buffer we're viewing
	s32 m_end;                            // ending sample index (always >= start)
	s32 m_start;                          // starting sample index
	sample_t m_gain;                      // overall gain factor
};


// ======================> write_stream_view

class write_stream_view : public read_stream_view
{

public:
	// empty constructor so we can live in an array or vector
	write_stream_view()
	{
	}

	// constructor that covers the given time period
	write_stream_view(stream_buffer &buffer, attotime start, attotime end) :
		read_stream_view(buffer, start, end)
	{
	}

	// constructor that converts from a read_stream_view
	write_stream_view(read_stream_view const &src) :
		read_stream_view(src)
	{
	}

	// safely write a sample to the buffer
	void put(s32 start, sample_t sample)
	{
		m_buffer->put(index_to_buffer_index(start), sample);
	}

	// write a sample to the buffer, clamping to +/- the clamp value
	void put_clamp(s32 index, sample_t sample, sample_t clamp = 1.0)
	{
		assert(clamp >= sample_t(0));
		put(index, std::clamp(sample, -clamp, clamp));
	}

	// write a sample to the buffer, converting from an integer with the given maximum
	void put_int(s32 index, s32 sample, s32 max)
	{
		put(index, sample_t(sample) * (1.0f / sample_t(max)));
	}

	// write a sample to the buffer, converting from an integer with the given maximum
	void put_int_clamp(s32 index, s32 sample, s32 maxclamp)
	{
		assert(maxclamp >= 0);
		put_int(index, std::clamp(sample, -maxclamp, maxclamp), maxclamp);
	}

	// safely add a sample to the buffer
	void add(s32 start, sample_t sample)
	{
		u32 index = index_to_buffer_index(start);
		m_buffer->put(index, m_buffer->get(index) + sample);
	}

	// add a sample to the buffer, converting from an integer with the given maximum
	void add_int(s32 index, s32 sample, s32 max)
	{
		add(index, sample_t(sample) * (1.0f / sample_t(max)));
	}

	// fill part of the view with the given value
	void fill(sample_t value, s32 start, s32 count)
	{
		if (start + count > samples())
			count = samples() - start;
		u32 index = index_to_buffer_index(start);
		for (s32 sampindex = 0; sampindex < count; sampindex++)
		{
			m_buffer->put(index, value);
			index = m_buffer->next_index(index);
		}
	}
	void fill(sample_t value, s32 start) { fill(value, start, samples() - start); }
	void fill(sample_t value) { fill(value, 0, samples()); }

	// copy data from another view
	void copy(read_stream_view const &src, s32 start, s32 count)
	{
		if (start + count > samples())
			count = samples() - start;
		u32 index = index_to_buffer_index(start);
		for (s32 sampindex = 0; sampindex < count; sampindex++)
		{
			m_buffer->put(index, src.get(start + sampindex));
			index = m_buffer->next_index(index);
		}
	}
	void copy(read_stream_view const &src, s32 start) { copy(src, start, samples() - start); }
	void copy(read_stream_view const &src) { copy(src, 0, samples()); }

	// add data from another view to our current values
	void add(read_stream_view const &src, s32 start, s32 count)
	{
		if (start + count > samples())
			count = samples() - start;
		u32 index = index_to_buffer_index(start);
		for (s32 sampindex = 0; sampindex < count; sampindex++)
		{
			m_buffer->put(index, m_buffer->get(index) + src.get(start + sampindex));
			index = m_buffer->next_index(index);
		}
	}
	void add(read_stream_view const &src, s32 start) { add(src, start, samples() - start); }
	void add(read_stream_view const &src) { add(src, 0, samples()); }

private:
	// given a stream starting offset, return the buffer index
	u32 index_to_buffer_index(s32 start) const
	{
		sound_assert(u32(start) < samples());
		u32 index = start + m_start;
		if (index >= m_buffer->size())
			index -= m_buffer->size();
		return index;
	}
};


// ======================> sound_stream_output

class sound_stream_output
{
#if (SOUND_DEBUG)
	friend class sound_stream;
#endif

public:
	// construction/destruction
	sound_stream_output();

	// initialization
	void init(sound_stream &stream, u32 index, char const *tag_base);

	// no copying allowed
	sound_stream_output(sound_stream_output const &src) = delete;
	sound_stream_output &operator=(sound_stream_output const &rhs) = delete;

	// simple getters
	sound_stream &stream() const { sound_assert(m_stream != nullptr); return *m_stream; }
	attotime end_time() const { return m_buffer.end_time(); }
	u32 index() const { return m_index; }
	stream_buffer::sample_t gain() const { return m_gain; }
	u32 buffer_sample_rate() const { return m_buffer.sample_rate(); }

	// simple setters
	void set_gain(float gain) { m_gain = gain; }

	// return a friendly name
	std::string name() const;

	// handle a changing sample rate
	void sample_rate_changed(u32 rate) { m_buffer.set_sample_rate(rate, true); }

	// return an output view covering a time period
	write_stream_view view(attotime start, attotime end) { return write_stream_view(m_buffer, start, end); }

	// resync the buffer to the given end time
	void set_end_time(attotime end) { m_buffer.set_end_time(end); }

	// attempt to optimize resamplers by reusing them where possible
	sound_stream_output &optimize_resampler(sound_stream_output *input_resampler);

private:
	// internal state
	sound_stream *m_stream;               // owning stream
	stream_buffer m_buffer;               // output buffer
	u32 m_index;                          // output index within the stream
	stream_buffer::sample_t m_gain;       // gain to apply to the output
	std::vector<sound_stream_output *> m_resampler_list; // list of resamplers we're connected to
};


// ======================> sound_stream_input

class sound_stream_input
{
#if (SOUND_DEBUG)
	friend class sound_stream;
#endif

public:
	// construction/destruction
	sound_stream_input();

	// initialization
	void init(sound_stream &stream, u32 index, char const *tag_base, sound_stream_output *resampler);

	// no copying allowed
	sound_stream_input(sound_stream_input const &src) = delete;
	sound_stream_input &operator=(sound_stream_input const &rhs) = delete;

	// simple getters
	bool valid() const { return (m_native_source != nullptr); }
	sound_stream &owner() const { sound_assert(valid()); return *m_owner; }
	sound_stream_output &source() const { sound_assert(valid()); return *m_native_source; }
	u32 index() const { return m_index; }
	stream_buffer::sample_t gain() const { return m_gain; }
	stream_buffer::sample_t user_gain() const { return m_user_gain; }

	// simple setters
	void set_gain(float gain) { m_gain = gain; }
	void set_user_gain(float gain) { m_user_gain = gain; }

	// return a friendly name
	std::string name() const;

	// connect the source
	void set_source(sound_stream_output *source);

	// update and return an reading view
	read_stream_view update(attotime start, attotime end);

	// tell inputs to apply sample rate changes
	void apply_sample_rate_changes(u32 updatenum, u32 downstream_rate);

private:
	// internal state
	sound_stream *m_owner;                   // pointer to the owning stream
	sound_stream_output *m_native_source;    // pointer to the native sound_stream_output
	sound_stream_output *m_resampler_source; // pointer to the resampler output
	u32 m_index;                             // input index within the stream
	stream_buffer::sample_t m_gain;          // gain to apply to this input
	stream_buffer::sample_t m_user_gain;     // user-controlled gain to apply to this input
};


// ======================> stream_update_delegate

// new-style callback
using stream_update_delegate = delegate<void (sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)>;


// ======================> sound_stream_flags

enum sound_stream_flags : u32
{
	// default is no special flags
	STREAM_DEFAULT_FLAGS = 0x00,

	// specify that updates should be forced to one sample at a time, in real time
	// this implicitly creates a timer that runs at the stream's output frequency
	// so only use when strictly necessary
	STREAM_SYNCHRONOUS = 0x01,

	// specify that input streams should not be resampled; stream update handler
	// must be able to accommodate multiple strams of differing input rates
	STREAM_DISABLE_INPUT_RESAMPLING = 0x02
};


// ======================> sound_stream

class sound_stream
{
	friend class sound_manager;

	// private common constructopr
	sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, sound_stream_flags flags);

public:
	// construction/destruction
	sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags = STREAM_DEFAULT_FLAGS);
	virtual ~sound_stream();

	// simple getters
	sound_stream *next() const { return m_next; }
	device_t &device() const { return m_device; }
	std::string name() const { return m_name; }
	bool input_adaptive() const { return m_input_adaptive || m_synchronous; }
	bool output_adaptive() const { return m_output_adaptive; }
	bool synchronous() const { return m_synchronous; }
	bool resampling_disabled() const { return m_resampling_disabled; }

	// input and output getters
	u32 input_count() const { return m_input.size(); }
	u32 output_count() const { return m_output.size(); }
	u32 output_base() const { return m_output_base; }
	sound_stream_input &input(int index) { sound_assert(index >= 0 && index < m_input.size()); return m_input[index]; }
	sound_stream_output &output(int index) { sound_assert(index >= 0 && index < m_output.size()); return m_output[index]; }

	// sample rate and timing getters
	u32 sample_rate() const { return (m_pending_sample_rate != SAMPLE_RATE_INVALID) ? m_pending_sample_rate : m_sample_rate; }
	attotime sample_time() const { return m_output[0].end_time(); }
	attotime sample_period() const { return attotime(0, sample_period_attoseconds()); }
	attoseconds_t sample_period_attoseconds() const { return (m_sample_rate != SAMPLE_RATE_INVALID) ? HZ_TO_ATTOSECONDS(m_sample_rate) : ATTOSECONDS_PER_SECOND; }

	// set the sample rate of the stream; will kick in at the next global update
	void set_sample_rate(u32 sample_rate);

	// connect the output 'outputnum' of given input_stream to this stream's input 'inputnum'
	void set_input(int inputnum, sound_stream *input_stream, int outputnum = 0, float gain = 1.0f);

	// force an update to the current time
	void update();

	// force an update to the current time, returning a view covering the given time period
	read_stream_view update_view(attotime start, attotime end, u32 outputnum = 0);

	// apply any pending sample rate changes; should only be called by the sound manager
	void apply_sample_rate_changes(u32 updatenum, u32 downstream_rate);

#if (SOUND_DEBUG)
	// print one level of the sound graph and recursively tell our inputs to do the same
	void print_graph_recursive(int indent, int index);
#endif

protected:
	// protected state
	std::string m_name;                            // name of this stream

private:
	// perform most of the initialization here
	void init_common(u32 inputs, u32 outputs, u32 sample_rate, sound_stream_flags flags);

	// if the sample rate has changed, this gets called to update internals
	void sample_rate_changed();

	// handle updates after a save state load
	void postload();

	// re-print the synchronization timer
	void reprime_sync_timer();

	// timer callback for synchronous streams
	void sync_update(void *, s32);

	// return a view of 0 data covering the given time period
	read_stream_view empty_view(attotime start, attotime end);

	// linking information
	device_t &m_device;                            // owning device
	sound_stream *m_next;                          // next stream in the chain

	// general information
	u32 m_sample_rate;                             // current live sample rate
	u32 m_pending_sample_rate;                     // pending sample rate for dynamic changes
	u32 m_last_sample_rate_update;                 // update number of last sample rate change
	bool m_input_adaptive;                         // adaptive stream that runs at the sample rate of its input
	bool m_output_adaptive;                        // adaptive stream that runs at the sample rate of its output
	bool m_synchronous;                            // synchronous stream that runs at the rate of its input
	bool m_resampling_disabled;                    // is resampling of input streams disabled?
	emu_timer *m_sync_timer;                       // update timer for synchronous streams

	// input information
	std::vector<sound_stream_input> m_input;       // list of streams we directly depend upon
	std::vector<read_stream_view> m_input_view;    // array of output views for passing to the callback
	std::vector<std::unique_ptr<sound_stream>> m_resampler_list; // internal list of resamplers
	stream_buffer m_empty_buffer;                  // empty buffer for invalid inputs

	// output information
	u32 m_output_base;                             // base index of our outputs, relative to our device
	std::vector<sound_stream_output> m_output;     // list of streams which directly depend upon us
	std::vector<write_stream_view> m_output_view;  // array of output views for passing to the callback

	// callback information
	stream_update_delegate m_callback_ex;          // extended callback function
};


// ======================> default_resampler_stream

class default_resampler_stream : public sound_stream
{
public:
	// construction/destruction
	default_resampler_stream(device_t &device);

	// update handler
	void resampler_sound_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs);

private:
	// internal state
	u32 m_max_latency;
};


// ======================> sound_manager

// structure describing an indexed mixer
struct mixer_input
{
	device_mixer_interface *mixer;          // owning device interface
	sound_stream *          stream;         // stream within the device
	int                     inputnum;       // input on the stream
};

class sound_manager
{
	friend class sound_stream;

	// reasons for muting
	static constexpr u8 MUTE_REASON_PAUSE = 0x01;
	static constexpr u8 MUTE_REASON_UI = 0x02;
	static constexpr u8 MUTE_REASON_DEBUGGER = 0x04;
	static constexpr u8 MUTE_REASON_SYSTEM = 0x08;

	// stream updates
	static const attotime STREAMS_UPDATE_ATTOTIME;

public:
	static constexpr int STREAMS_UPDATE_FREQUENCY = 50;

	// construction/destruction
	sound_manager(running_machine &machine);
	~sound_manager();

	// getters
	running_machine &machine() const { return m_machine; }
	int attenuation() const { return m_attenuation; }
	const std::vector<std::unique_ptr<sound_stream>> &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; }

	// 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);

	// WAV recording
	bool is_recording() const { return bool(m_wavfile); }
	bool start_recording();
	bool start_recording(std::string_view filename);
	void stop_recording();

	// set the global OSD attenuation level
	void set_attenuation(float attenuation);

	// mute sound for one of various independent reasons
	bool muted() const { return bool(m_muted); }
	bool ui_mute() const { return bool(m_muted & MUTE_REASON_UI); }
	bool debugger_mute() const { return bool(m_muted & MUTE_REASON_DEBUGGER); }
	bool system_mute() const { return bool(m_muted & MUTE_REASON_SYSTEM); }
	void ui_mute(bool turn_off) { mute(turn_off, MUTE_REASON_UI); }
	void debugger_mute(bool turn_off) { mute(turn_off, MUTE_REASON_DEBUGGER); }
	void system_mute(bool turn_off) { mute(turn_off, MUTE_REASON_SYSTEM); }

	// return information about the given mixer input, by index
	bool indexed_mixer_input(int index, mixer_input &info) const;

	// fill the given buffer with 16-bit stereo audio samples
	void samples(s16 *buffer);

private:
	// set/reset the mute state for the given reason
	void mute(bool mute, u8 reason);

	// helper to remove items from the orphan list
	void recursive_remove_stream_from_orphan_list(sound_stream *stream);

	// apply pending sample rate changes
	void apply_sample_rate_changes();

	// reset all sound chips
	void reset();

	// pause/resume sound output
	void pause();
	void resume();

	// handle configuration load/save
	void config_load(config_type cfg_type, config_level cfg_lvl, util::xml::data_node const *parentnode);
	void config_save(config_type cfg_type, util::xml::data_node *parentnode);

	// helper to adjust scale factor toward a goal
	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(void *ptr = nullptr, s32 param = 0);

	// internal state
	running_machine &m_machine;           // reference to the running machine
	emu_timer *m_update_timer;            // timer that runs the update function
	std::vector<std::reference_wrapper<speaker_device> > 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<s16> m_finalmix;          // final mix, in 16-bit signed format
	std::vector<stream_buffer::sample_t> m_leftmix; // left speaker mix, in native format
	std::vector<stream_buffer::sample_t> m_rightmix; // right speaker mix, in native format

	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)

	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

	// streams data
	std::vector<std::unique_ptr<sound_stream>> m_stream_list; // list of streams
	std::map<sound_stream *, u8> m_orphan_stream_list; // list of orphaned streams
	bool m_first_reset;                   // is this our first reset?
};


#endif // MAME_EMU_SOUND_H
