summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/sound.h
blob: 15f6a5743a2549f47040cedbbde8112f16ad9085 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
// 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)
	{
		sound_assert(u32(start) < samples());
		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)
	{
		sound_assert(u32(start) < samples());
		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
	{
		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();

	// handle updates before a save state load
	void presave();

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

	// timer callback for synchronous streams
	void sync_update(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

	attotime m_last_update_end_time;               // last end_time() in update

	// 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(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