summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu')
-rw-r--r--src/emu/diexec.cpp2
-rw-r--r--src/emu/resampler.cpp142
-rw-r--r--src/emu/resampler.h41
-rw-r--r--src/emu/sound.cpp111
-rw-r--r--src/emu/sound.h26
5 files changed, 283 insertions, 39 deletions
diff --git a/src/emu/diexec.cpp b/src/emu/diexec.cpp
index 1813dbff611..1cd5266c518 100644
--- a/src/emu/diexec.cpp
+++ b/src/emu/diexec.cpp
@@ -438,7 +438,7 @@ void device_execute_interface::interface_post_reset()
if (m_vblank_interrupt_screen != nullptr)
{
// get the screen that will trigger the VBLANK
- screen_device * screen = device().siblingdevice<screen_device>(m_vblank_interrupt_screen);
+ screen_device *const screen = device().siblingdevice<screen_device>(m_vblank_interrupt_screen);
assert(screen != nullptr);
screen->register_vblank_callback(vblank_state_delegate(&device_execute_interface::on_vblank, this));
diff --git a/src/emu/resampler.cpp b/src/emu/resampler.cpp
index b95fe80dfba..b86e028ce04 100644
--- a/src/emu/resampler.cpp
+++ b/src/emu/resampler.cpp
@@ -149,7 +149,7 @@
// Having the sum of coefficients being 1 ensures that.
-audio_resampler::audio_resampler(u32 fs, u32 ft)
+audio_resampler_hq::audio_resampler_hq(u32 fs, u32 ft, float latency, u32 max_order_per_lane, u32 max_lanes)
{
m_ft = ft;
m_fs = fs;
@@ -160,13 +160,13 @@ audio_resampler::audio_resampler(u32 fs, u32 ft)
m_fsm = ft / gcd;
// Compute the per-phase filter length to limit the latency to 5ms and capping it
- m_order_per_lane = u32(fs * 0.005 * 2);
- if(m_order_per_lane > 400)
- m_order_per_lane = 400;
+ m_order_per_lane = u32(fs * latency * 2);
+ if(m_order_per_lane > max_order_per_lane)
+ m_order_per_lane = max_order_per_lane;
- // Reduce the number of phases to be less than 200
+ // Reduce the number of phases to be less than max_lanes
m_phase_shift = 0;
- while(((m_fsm - 1) >> m_phase_shift) >= 200)
+ while(((m_fsm - 1) >> m_phase_shift) >= max_lanes)
m_phase_shift ++;
m_phases = ((m_fsm - 1) >> m_phase_shift) + 1;
@@ -217,7 +217,7 @@ audio_resampler::audio_resampler(u32 fs, u32 ft)
m_skip = m_ftm / m_fsm;
}
-u32 audio_resampler::compute_gcd(u32 fs, u32 ft)
+u32 audio_resampler_hq::compute_gcd(u32 fs, u32 ft)
{
u32 v1 = fs > ft ? fs : ft;
u32 v2 = fs > ft ? ft : fs;
@@ -229,7 +229,12 @@ u32 audio_resampler::compute_gcd(u32 fs, u32 ft)
return v1;
}
-void audio_resampler::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const
+u32 audio_resampler_hq::history_size() const
+{
+ return m_order_per_lane;
+}
+
+void audio_resampler_hq::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const
{
u32 seconds = dest_sample / m_ft;
u32 dsamp = dest_sample % m_ft;
@@ -255,7 +260,7 @@ void audio_resampler::apply(const emu::detail::output_buffer_flat<sample_t> &src
}
}
-void audio_resampler::apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const
+void audio_resampler_hq::apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const
{
u32 seconds = dest_sample / m_ft;
u32 dsamp = dest_sample % m_ft;
@@ -287,7 +292,7 @@ void audio_resampler::apply(const emu::detail::output_buffer_interleaved<s16> &s
}
-void audio_resampler::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const
+void audio_resampler_hq::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const
{
u32 seconds = dest_sample / m_ft;
u32 dsamp = dest_sample % m_ft;
@@ -315,3 +320,120 @@ void audio_resampler::apply(const emu::detail::output_buffer_flat<sample_t> &src
}
}
}
+
+
+// Now for the lo-fi version
+//
+// We mostly forget about filtering, and just try to do a decent
+// interpolation. There's a nice 4-point formula used in yamaha
+// devices from around 2000:
+// f0(t) = (t - t**3)/6
+// f1(t) = t + (t**2 - t**3)/2
+//
+// The polynoms are used with the decimal part 'p' (as in phase) of
+// the sample position. The computation from the four samples s0..s3
+// is:
+// s = - s0 * f0(1-p) + s1 * f1(1-p) + s2 * f1(p) - s3 * f0(p)
+//
+// The target sample must be between s1 and s2.
+//
+// When upsampling, that's enough. When downsampling, it feels like a
+// good idea to filter a little with a moving average, dividing the
+// source frequency by an integer just big enough to make the final
+// source frequency lower.
+
+// Sample interpolation functions f0 and f1
+
+const std::array<std::array<float, 0x1001>, 2> audio_resampler_lofi::interpolation_table = []() {
+ std::array<std::array<float, 0x1001>, 2> result;
+
+ // The exact way of doing the computations replicate the values
+ // actually used by the chip (which are very probably a rom, of
+ // course).
+
+ for(u32 i=1; i != 4096; i++) {
+ float p = i / 4096.0;
+ result[0][i] = (p - p*p*p) / 6;
+ }
+ for(u32 i=1; i != 2049; i++) {
+ float p = i / 4096.0;
+ result[1][i] = p + (p*p - p*p*p) / 2;
+ }
+ for(u32 i=2049; i != 4096; i++)
+ // When interpolating, f1 is added and f0 is subtracted, and the total must be 1
+ result[1][i] = 1.0 + result[0][i] + result[0][4096-i] - result[1][4096-i];
+
+ result[0][ 0] = 0.0;
+ result[0][0x1000] = 0.0;
+ result[1][ 0] = 0.0;
+ result[1][0x1000] = 1.0;
+ return result;
+}();
+
+audio_resampler_lofi::audio_resampler_lofi(u32 fs, u32 ft)
+{
+ m_fs = fs;
+ m_ft = ft;
+
+ m_source_divide = fs <= ft ? 1 : 1+fs/ft;
+ m_step = u64(fs) * 0x1000 / ft / m_source_divide;
+}
+
+
+u32 audio_resampler_lofi::history_size() const
+{
+ return 5 * m_source_divide;
+}
+
+void audio_resampler_lofi::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const
+{
+ u32 seconds = dest_sample / m_ft;
+ u32 dsamp = dest_sample % m_ft;
+ u64 ssamp = (u64(dsamp) * m_fs * 0x1000) / m_ft;
+ u64 ssample = (ssamp >> 12) + u64(m_fs) * seconds;
+ u32 phase = ssamp & 0xfff;
+ if(m_source_divide > 1) {
+ u32 delta = ssample % m_source_divide;
+ phase = (phase | (delta << 12)) / m_source_divide;
+ ssample -= delta;
+ }
+
+ // We're getting 2 samples latency, which is small enough
+
+ ssample -= 4*m_source_divide;
+
+ const sample_t *s = src.ptrs(srcc, ssample - src.sync_sample());
+
+ std::function<sample_t()> reader;
+ if(m_source_divide == 1)
+ reader = [s]() mutable -> sample_t { return *s++; };
+ else
+ reader = [s, count = m_source_divide]() mutable -> sample_t { sample_t sm = 0; for(u32 i=0; i != count; i++) { sm += *s++; } return sm / count; };
+
+ sample_t s0 = reader();
+ sample_t s1 = reader();
+ sample_t s2 = reader();
+ sample_t s3 = reader();
+
+ sample_t *d = dest.data();
+ for(u32 sample = 0; sample != samples; sample++) {
+ *d++ += gain * (- s0 * interpolation_table[0][0x1000-phase] + s1 * interpolation_table[1][0x1000-phase] + s2 * interpolation_table[1][phase] - s3 * interpolation_table[0][phase]);
+
+ phase += m_step;
+ if(phase & 0x1000) {
+ phase &= 0xfff;
+ s0 = s1;
+ s1 = s2;
+ s2 = s3;
+ s3 = reader();
+ }
+ }
+}
+
+void audio_resampler_lofi::apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const
+{
+}
+
+void audio_resampler_lofi::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const
+{
+}
diff --git a/src/emu/resampler.h b/src/emu/resampler.h
index c1b2f88f3e9..d0758b5ae7e 100644
--- a/src/emu/resampler.h
+++ b/src/emu/resampler.h
@@ -15,13 +15,26 @@ class audio_resampler
public:
using sample_t = sound_stream::sample_t;
- audio_resampler(u32 fs, u32 ft);
+ virtual ~audio_resampler() = default;
- u32 history_size() const { return m_order_per_lane; }
+ virtual u32 history_size() const = 0;
- void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const;
- void apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const;
- void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const;
+ virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const = 0;
+ virtual void apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const = 0;
+ virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const = 0;
+};
+
+class audio_resampler_hq : public audio_resampler
+{
+public:
+ audio_resampler_hq(u32 fs, u32 ft, float latency, u32 max_order_per_lane, u32 max_lanes);
+ virtual ~audio_resampler_hq() = default;
+
+ virtual u32 history_size() const override;
+
+ virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const override;
+ virtual void apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const override;
+ virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const override;
private:
u32 m_order_per_lane, m_ftm, m_fsm, m_ft, m_fs, m_delta, m_skip, m_phases, m_phase_shift;
@@ -31,5 +44,23 @@ private:
static u32 compute_gcd(u32 fs, u32 ft);
};
+class audio_resampler_lofi : public audio_resampler
+{
+public:
+ audio_resampler_lofi(u32 fs, u32 ft);
+ virtual ~audio_resampler_lofi() = default;
+
+ virtual u32 history_size() const override;
+
+ virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const override;
+ virtual void apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const override;
+ virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const override;
+
+private:
+ static const std::array<std::array<float, 0x1001>, 2> interpolation_table;
+ u32 m_source_divide, m_fs, m_ft;
+ u32 m_step;
+};
+
#endif
diff --git a/src/emu/sound.cpp b/src/emu/sound.cpp
index 5ff9e86e1ed..06dd6923753 100644
--- a/src/emu/sound.cpp
+++ b/src/emu/sound.cpp
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Aaron Giles
+// copyright-holders:O. Galibert, Aaron Giles
/***************************************************************************
sound.cpp
@@ -23,6 +23,8 @@
#include "osdepend.h"
+#include "util/language.h"
+
#include <algorithm>
//**************************************************************************
@@ -36,7 +38,7 @@
#define LOG_OSD_STREAMS (1U << 3)
#define LOG_ORDER (1U << 4)
-#define VERBOSE 0
+#define VERBOSE -1
#include "logmacro.h"
@@ -189,7 +191,7 @@ template<typename S> void emu::detail::output_buffer_flat<S>::resample(u32 previ
return;
auto si = [](attotime time, u32 rate) -> s64 {
- return time.m_seconds * rate + ((time.m_attoseconds / 100'000'000) * rate) / 10'000'000'000LL;
+ return time.m_seconds * rate + ((time.m_attoseconds / 100000000) * rate) / 10000000000LL;
};
auto cv = [](u32 source_rate, u32 dest_rate, s64 time) -> std::pair<s64, double> {
@@ -635,7 +637,7 @@ void sound_stream::reprime_sync_timer()
u64 next_sample = m_output_buffer.write_sample() + 1;
attotime next_time = sample_to_time(next_sample);
- next_time.m_attoseconds += 1'000'000'000; // Go to the next nanosecond
+ next_time.m_attoseconds += 1'000'000'000; // Go to the next nanosecond '
m_sync_timer->adjust(next_time - m_device.machine().time());
}
@@ -657,7 +659,11 @@ sound_manager::sound_manager(running_machine &machine) :
m_muted(0),
m_nosound_mode(machine.osd().no_sound()),
m_unique_id(0),
- m_wavfile()
+ m_wavfile(),
+ m_resampler_type(RESAMPLER_LOFI),
+ m_resampler_hq_latency(0.005),
+ m_resampler_hq_length(400),
+ m_resampler_hq_phases(200)
{
// register callbacks
machine.configuration().config_register(
@@ -838,13 +844,8 @@ void sound_manager::after_devices_init()
m_record_buffer.resize(m_outputs_count * machine().sample_rate(), 0);
m_record_samples = 0;
- // Have all streams create their initial resamplers
- for(auto &stream : m_stream_list)
- stream->create_resamplers();
-
- // Then get the initial history sizes
- for(auto &stream : m_stream_list)
- stream->lookup_history_sizes();
+ // Create resamplers and setup history
+ rebuild_all_resamplers();
m_effects_done = false;
@@ -911,6 +912,7 @@ void sound_manager::output_push(int id, sound_stream &stream)
*outb1 = std::clamp(int(*inb++ * 32768), -32768, 32767);
outb1 += m_outputs_count;
}
+ outb++;
}
}
@@ -1116,12 +1118,24 @@ void sound_manager::config_load(config_type cfg_type, config_level cfg_level, ut
// In the global config, get the default effect chain configuration
util::xml::data_node const *efl_node = parentnode->get_child("default_audio_effects");
- for(util::xml::data_node const *ef_node = efl_node->get_child("effect"); ef_node != nullptr; ef_node = ef_node->get_next_sibling("effect")) {
- unsigned int id = ef_node->get_attribute_int("step", 0);
- std::string type = ef_node->get_attribute_string("type", "");
- if(id >= 1 && id <= m_default_effects.size() && audio_effect::effect_names[m_default_effects[id-1]->type()] == type)
- m_default_effects[id-1]->config_load(ef_node);
+ if(efl_node) {
+ for(util::xml::data_node const *ef_node = efl_node->get_child("effect"); ef_node != nullptr; ef_node = ef_node->get_next_sibling("effect")) {
+ unsigned int id = ef_node->get_attribute_int("step", 0);
+ std::string type = ef_node->get_attribute_string("type", "");
+ if(id >= 1 && id <= m_default_effects.size() && audio_effect::effect_names[m_default_effects[id-1]->type()] == type)
+ m_default_effects[id-1]->config_load(ef_node);
+ }
+ }
+
+ // and the resampler configuration
+ util::xml::data_node const *rs_node = parentnode->get_child("resampler");
+ if(rs_node) {
+ m_resampler_type = rs_node->get_attribute_int("type", RESAMPLER_LOFI);
+ m_resampler_hq_latency = rs_node->get_attribute_float("hq_latency", 0.0050);
+ m_resampler_hq_length = rs_node->get_attribute_int("hq_length", 400);
+ m_resampler_hq_phases = rs_node->get_attribute_int("hq_phases", 200);
}
+ rebuild_all_resamplers();
break;
}
@@ -1211,6 +1225,12 @@ void sound_manager::config_save(config_type cfg_type, util::xml::data_node *pare
ef_node->set_attribute("type", audio_effect::effect_names[e->type()]);
e->config_save(ef_node);
}
+
+ util::xml::data_node *const rs_node = parentnode->add_child("resampler", nullptr);
+ rs_node->set_attribute_int("type", m_resampler_type);
+ rs_node->set_attribute_float("hq_latency", m_resampler_hq_latency);
+ rs_node->set_attribute_int("hq_length", m_resampler_hq_length);
+ rs_node->set_attribute_int("hq_phases", m_resampler_hq_phases);
break;
}
@@ -2390,7 +2410,7 @@ void sound_manager::mapping_update()
u64 sound_manager::rate_and_time_to_index(attotime time, u32 sample_rate) const
{
- return time.m_seconds * sample_rate + ((time.m_attoseconds / 100'000'000) * sample_rate) / 10'000'000'000LL;
+ return time.m_seconds * sample_rate + ((time.m_attoseconds / 100'000'000) * sample_rate) / 10'000'000'000LL; //'
}
void sound_manager::update(s32)
@@ -2456,20 +2476,69 @@ void sound_manager::streams_update()
machine().osd().add_audio_to_recording(m_record_buffer.data(), m_record_samples);
machine().video().add_sound_to_recording(m_record_buffer.data(), m_record_samples);
if(m_wavfile)
- util::wav_add_data_16(*m_wavfile, m_record_buffer.data(), m_record_samples);
+ util::wav_add_data_16(*m_wavfile, m_record_buffer.data(), m_record_samples * m_outputs_count);
m_effects_condition.notify_all();
}
//**// Resampler management
-
const audio_resampler *sound_manager::get_resampler(u32 fs, u32 ft)
{
auto key = std::make_pair(fs, ft);
auto i = m_resamplers.find(key);
if(i != m_resamplers.end())
return i->second.get();
- auto *res = new audio_resampler(fs, ft);
+
+ audio_resampler *res;
+ if(m_resampler_type == RESAMPLER_HQ)
+ res = new audio_resampler_hq(fs, ft, m_resampler_hq_latency, m_resampler_hq_length, m_resampler_hq_phases);
+ else
+ res = new audio_resampler_lofi(fs, ft);
m_resamplers[key].reset(res);
return res;
}
+
+void sound_manager::rebuild_all_resamplers()
+{
+ m_resamplers.clear();
+
+ for(auto &stream : m_stream_list)
+ stream->create_resamplers();
+
+ for(auto &stream : m_stream_list)
+ stream->lookup_history_sizes();
+}
+
+void sound_manager::set_resampler_type(u32 type)
+{
+ m_resampler_type = type;
+ rebuild_all_resamplers();
+}
+
+void sound_manager::set_resampler_hq_latency(double latency)
+{
+ m_resampler_hq_latency = latency;
+ rebuild_all_resamplers();
+}
+
+void sound_manager::set_resampler_hq_length(u32 length)
+{
+ m_resampler_hq_length = length;
+ rebuild_all_resamplers();
+}
+
+void sound_manager::set_resampler_hq_phases(u32 phases)
+{
+ m_resampler_hq_phases = phases;
+ rebuild_all_resamplers();
+}
+
+const char *sound_manager::resampler_type_names(u32 type) const
+{
+ using util::lang_translate;
+
+ if(type == RESAMPLER_HQ)
+ return _("HQ");
+ else
+ return _("LoFi");
+}
diff --git a/src/emu/sound.h b/src/emu/sound.h
index 8039948362b..6d699dbb8d9 100644
--- a/src/emu/sound.h
+++ b/src/emu/sound.h
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Aaron Giles
+// copyright-holders:O. Galibert, Aaron Giles
/***************************************************************************
sound.h
@@ -379,6 +379,11 @@ class sound_manager
public:
using sample_t = sound_stream::sample_t;
+ enum {
+ RESAMPLER_LOFI,
+ RESAMPLER_HQ
+ };
+
struct mapping {
struct node_mapping {
u32 m_node;
@@ -465,6 +470,18 @@ public:
void mapping_update();
+ const char *resampler_type_names(u32 type) const;
+
+ u32 resampler_type() const { return m_resampler_type; }
+ double resampler_hq_latency() const { return m_resampler_hq_latency; }
+ u32 resampler_hq_length() const { return m_resampler_hq_length; }
+ u32 resampler_hq_phases() const { return m_resampler_hq_phases; }
+
+ void set_resampler_type(u32 type);
+ void set_resampler_hq_latency(double latency);
+ void set_resampler_hq_length(u32 length);
+ void set_resampler_hq_phases(u32 phases);
+
private:
struct effect_step {
std::unique_ptr<audio_effect> m_effect;
@@ -583,7 +600,7 @@ private:
void update_osd_streams();
void update_osd_input();
void speakers_update(attotime endtime);
-
+ void rebuild_all_resamplers();
void run_effects();
u64 rate_and_time_to_index(attotime time, u32 sample_rate) const;
@@ -642,6 +659,11 @@ private:
std::vector<std::unique_ptr<sound_stream>> m_stream_list; // list of streams
std::vector<sound_stream *> m_ordered_streams; // Streams in update order
u32 m_outputs_count;
+
+ // resampler data
+ u32 m_resampler_type;
+ double m_resampler_hq_latency;
+ u32 m_resampler_hq_length, m_resampler_hq_phases;
};