summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/mas3507d.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/mas3507d.cpp')
-rw-r--r--src/devices/sound/mas3507d.cpp340
1 files changed, 210 insertions, 130 deletions
diff --git a/src/devices/sound/mas3507d.cpp b/src/devices/sound/mas3507d.cpp
index a5b7a7917d2..25f9d2ab1af 100644
--- a/src/devices/sound/mas3507d.cpp
+++ b/src/devices/sound/mas3507d.cpp
@@ -1,36 +1,89 @@
// license:BSD-3-Clause
// copyright-holders:Olivier Galibert
-//
-// MAS 3507D MPEG audio decoder
-//
+/*
+ Micronas MAS 3507D MPEG audio decoder
+
+ Datasheet: https://www.mas-player.de/mp3/download/mas3507d.pdf
+
+ TODO:
+ - Datasheet says it has DSP and internal program ROM,
+ but these are not dumped and hooked up
+ - Support Broadcast mode, MPEG Layer 2
+*/
#include "emu.h"
#include "mas3507d.h"
+#include "mp3_audio.h"
+
+#define LOG_READ (1U << 1)
+#define LOG_WRITE (1U << 2)
+#define LOG_REGISTER (1U << 3)
+#define LOG_CONFIG (1U << 4)
+#define LOG_OTHER (1U << 5)
+// #define VERBOSE (LOG_GENERAL | LOG_READ | LOG_WRITE | LOG_REGISTER | LOG_CONFIG | LOG_OTHER)
+// #define LOG_OUTPUT_STREAM std::cout
+
+#include "logmacro.h"
-#define MINIMP3_ONLY_MP3
-#define MINIMP3_NO_STDIO
-#define MINIMP3_IMPLEMENTATION
-#define MAX_FRAME_SYNC_MATCHES 1
-#include "minimp3/minimp3.h"
-#include "minimp3/minimp3_ex.h"
+#define LOGREAD(...) LOGMASKED(LOG_READ, __VA_ARGS__)
+#define LOGWRITE(...) LOGMASKED(LOG_WRITE, __VA_ARGS__)
+#define LOGREGISTER(...) LOGMASKED(LOG_REGISTER, __VA_ARGS__)
+#define LOGCONFIG(...) LOGMASKED(LOG_CONFIG, __VA_ARGS__)
+#define LOGOTHER(...) LOGMASKED(LOG_OTHER, __VA_ARGS__)
+
+ALLOW_SAVE_TYPE(mas3507d_device::i2c_bus_state_t)
+ALLOW_SAVE_TYPE(mas3507d_device::i2c_bus_address_t)
+ALLOW_SAVE_TYPE(mas3507d_device::i2c_subdest_t)
+ALLOW_SAVE_TYPE(mas3507d_device::i2c_command_t)
// device type definition
-DEFINE_DEVICE_TYPE(MAS3507D, mas3507d_device, "mas3507d", "MAS 3507D MPEG decoder")
+DEFINE_DEVICE_TYPE(MAS3507D, mas3507d_device, "mas3507d", "Micronas MAS 3507D MPEG decoder")
mas3507d_device::mas3507d_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, MAS3507D, tag, owner, clock)
, device_sound_interface(mconfig, *this)
- , cb_sample(*this)
- , i2c_bus_state(), i2c_bus_address(), i2c_scli(false), i2c_sclo(false), i2c_sdai(false), i2c_sdao(false)
- , i2c_bus_curbit(0), i2c_bus_curval(0), i2c_subdest(), i2c_command(), i2c_bytecount(0), i2c_io_bank(0), i2c_io_adr(0), i2c_io_count(0), i2c_io_val(0)
+ , cb_mpeg_frame_sync(*this), cb_demand(*this)
+ , i2c_bus_state(IDLE), i2c_bus_address(UNKNOWN), i2c_subdest(UNDEFINED), i2c_command(CMD_BAD)
+ , i2c_scli(false), i2c_sclo(false), i2c_sdai(false), i2c_sdao(false)
+ , i2c_bus_curbit(0), i2c_bus_curval(0), i2c_bytecount(0), i2c_io_bank(0), i2c_io_adr(0), i2c_io_count(0), i2c_io_val(0)
{
}
void mas3507d_device::device_start()
{
- current_rate = 44100;
- stream = stream_alloc(0, 2, current_rate);
- cb_sample.resolve();
+ stream = stream_alloc(0, 2, 44100);
+ mp3dec = std::make_unique<mp3_audio>(reinterpret_cast<const uint8_t *>(&mp3data[0]));
+
+ save_item(NAME(mp3data));
+ save_item(NAME(samples));
+ save_item(NAME(i2c_bus_state));
+ save_item(NAME(i2c_bus_address));
+ save_item(NAME(i2c_subdest));
+ save_item(NAME(i2c_command));
+ save_item(NAME(i2c_scli));
+ save_item(NAME(i2c_sclo));
+ save_item(NAME(i2c_sdai));
+ save_item(NAME(i2c_sdao));
+ save_item(NAME(i2c_bus_curbit));
+ save_item(NAME(i2c_bus_curval));
+
+ save_item(NAME(mp3data_count));
+ save_item(NAME(decoded_frame_count));
+ save_item(NAME(sample_count));
+ save_item(NAME(samples_idx));
+ save_item(NAME(is_muted));
+ save_item(NAME(gain_ll));
+ save_item(NAME(gain_rr));
+ save_item(NAME(i2c_bytecount));
+ save_item(NAME(i2c_io_bank));
+ save_item(NAME(i2c_io_adr));
+ save_item(NAME(i2c_io_count));
+ save_item(NAME(i2c_io_val));
+ save_item(NAME(i2c_sdao_data));
+
+ save_item(NAME(frame_channels));
+
+ mp3dec->register_save(*this);
}
void mas3507d_device::device_reset()
@@ -41,14 +94,21 @@ void mas3507d_device::device_reset()
i2c_bus_address = UNKNOWN;
i2c_bus_curbit = -1;
i2c_bus_curval = 0;
+ i2c_bytecount = 0;
+ i2c_io_bank = 0;
+ i2c_io_adr = 0;
+ i2c_io_count = 0;
+ i2c_io_val = 0;
+ i2c_sdao_data = 0;
- mp3dec_init(&mp3_dec);
- memset(mp3data.data(), 0, mp3data.size());
- memset(samples.data(), 0, samples.size());
- mp3_count = 0;
- sample_count = 0;
- total_frame_count = 0;
- buffered_frame_count = 0;
+ is_muted = false;
+ gain_ll = gain_rr = 1.0;
+
+ frame_channels = 2;
+
+ stream->set_sample_rate(44100);
+
+ reset_playback();
}
void mas3507d_device::i2c_scl_w(bool line)
@@ -56,11 +116,20 @@ void mas3507d_device::i2c_scl_w(bool line)
if(line == i2c_scli)
return;
i2c_scli = line;
-
+ // FIXME: sda output should only change state when clock is low.
+ // On wrong device-id ensure sda is left high (Think it's OK but not certain)
if(i2c_scli) {
if(i2c_bus_state == STARTED) {
if(i2c_sdai)
i2c_bus_curval |= 1 << i2c_bus_curbit;
+
+ if(i2c_subdest == DATA_READ)
+ i2c_sdao = BIT(i2c_sdao_data, i2c_bus_curbit + (i2c_bytecount * 8));
+ else {
+ i2c_sdao_data = 0;
+ i2c_sdao = false;
+ }
+
i2c_bus_curbit --;
if(i2c_bus_curbit == -1) {
if(i2c_bus_address == UNKNOWN) {
@@ -86,7 +155,7 @@ void mas3507d_device::i2c_scl_w(bool line)
i2c_bus_state = STARTED;
i2c_bus_curbit = 7;
i2c_bus_curval = 0;
- i2c_sdao = true;
+ i2c_sdao = false;
}
}
}
@@ -131,24 +200,30 @@ int mas3507d_device::i2c_sda_r()
bool mas3507d_device::i2c_device_got_address(uint8_t address)
{
- if (address == 0x3b) {
+ if(address == CMD_DEV_READ)
i2c_subdest = DATA_READ;
- } else {
+ else
i2c_subdest = UNDEFINED;
- }
- return (address & 0xfe) == 0x3a;
+ return (address & 0xfe) == CMD_DEV_WRITE;
}
void mas3507d_device::i2c_device_got_byte(uint8_t byte)
{
switch(i2c_subdest) {
case UNDEFINED:
- if(byte == 0x68)
+ if(byte == CMD_DATA_WRITE)
i2c_subdest = DATA_WRITE;
- else if(byte == 0x69)
+ else if(byte == CMD_DATA_READ) {
i2c_subdest = DATA_READ;
- else if(byte == 0x6a)
+
+ // Default read, returns the current frame count
+ i2c_sdao_data = ((decoded_frame_count >> 8) & 0xff)
+ | ((decoded_frame_count & 0xff) << 8)
+ | (((decoded_frame_count >> 24) & 0xff) << 16)
+ | (((decoded_frame_count >> 16) & 0xff) << 24);
+ }
+ else if(byte == CMD_CONTROL_WRITE)
i2c_subdest = CONTROL;
else
i2c_subdest = BAD;
@@ -159,19 +234,19 @@ void mas3507d_device::i2c_device_got_byte(uint8_t byte)
break;
case BAD:
- logerror("MAS I2C: Dropping byte %02x\n", byte);
+ LOGOTHER("MAS I2C: Dropping byte %02x\n", byte);
break;
case DATA_READ:
- // Default Read
- // This should return the current MPEGFrameCount value when called
+ switch(i2c_bytecount) {
+ case 0: i2c_io_val = byte; break;
+ case 1: i2c_io_val |= byte << 8; break;
+ case 2: i2c_nak(); return;
+ }
- // TODO: Figure out how to use this data exactly (chip docs are a little unclear to me)
- i2c_io_val <<= 8;
- i2c_io_val |= byte;
- i2c_bytecount++;
+ LOGREAD("MAS I2C: DATA_READ %d %02x %08x\n", i2c_bytecount, byte, i2c_io_val);
- logerror("MAS I2C: DATA_READ %d %08x\n", i2c_bytecount, i2c_io_val);
+ i2c_bytecount++;
break;
@@ -184,7 +259,7 @@ void mas3507d_device::i2c_device_got_byte(uint8_t byte)
break;
case 3:
i2c_command = CMD_READ_CTRL;
- logerror("MAS I2C: READ_CTRL\n");
+ LOGWRITE("MAS I2C: READ_CTRL\n");
break;
case 9:
i2c_io_adr = (byte & 15) << 4;
@@ -196,22 +271,22 @@ void mas3507d_device::i2c_device_got_byte(uint8_t byte)
break;
case 0xd:
i2c_command = CMD_READ_REG;
- logerror("MAS I2C: READ_REG\n");
+ LOGWRITE("MAS I2C: READ_REG\n");
break;
case 0xe: case 0xf:
i2c_io_bank = (byte >> 4) & 1;
i2c_command = CMD_READ_MEM;
- logerror("MAS I2C: READ_MEM\n");
+ LOGWRITE("MAS I2C: READ_MEM\n");
break;
default:
i2c_command = CMD_BAD;
- logerror("MAS I2C: BAD\n");
+ LOGWRITE("MAS I2C: BAD\n");
break;
}
} else {
switch(i2c_command) {
default:
- logerror("MAS I2C: Ignoring byte %02x\n", byte);
+ LOGWRITE("MAS I2C: Ignoring byte %02x\n", byte);
break;
case CMD_WRITE_REG:
@@ -260,24 +335,24 @@ void mas3507d_device::i2c_device_got_byte(uint8_t byte)
break;
case CONTROL:
- logerror("MAS I2C: Control byte %02x\n", byte);
+ LOGOTHER("MAS I2C: Control byte %02x\n", byte);
break;
}
}
void mas3507d_device::i2c_device_got_stop()
{
- logerror("MAS I2C: got stop\n");
+ LOGOTHER("MAS I2C: got stop\n");
}
-int gain_to_db(double val) {
+int mas3507d_device::gain_to_db(double val) {
return round(20 * log10((0x100000 - val) / 0x80000));
}
-float gain_to_percentage(int val) {
- if (val == 0) {
+float mas3507d_device::gain_to_percentage(int val)
+{
+ if(val == 0)
return 0; // Special case for muting it seems
- }
double db = gain_to_db(val);
@@ -287,113 +362,127 @@ float gain_to_percentage(int val) {
void mas3507d_device::mem_write(int bank, uint32_t adr, uint32_t val)
{
switch(adr | (bank ? 0x10000 : 0)) {
- case 0x0032f: logerror("MAS3507D: OutputConfig = %05x\n", val); break;
+ case 0x0032d: LOGCONFIG("MAS3507D: PLLOffset48 = %05x\n", val); break;
+ case 0x0032e: LOGCONFIG("MAS3507D: PLLOffset44 = %05x\n", val); break;
+ case 0x0032f: LOGCONFIG("MAS3507D: OutputConfig = %05x\n", val); break;
case 0x107f8:
- logerror("MAS3507D: left->left gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_to_percentage(val));
- set_output_gain(0, gain_to_percentage(val));
+ gain_ll = gain_to_percentage(val);
+ LOGCONFIG("MAS3507D: left->left gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_ll);
break;
case 0x107f9:
- logerror("MAS3507D: left->right gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_to_percentage(val));
+ LOGCONFIG("MAS3507D: left->right gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_to_percentage(val));
break;
case 0x107fa:
- logerror("MAS3507D: right->left gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_to_percentage(val));
+ LOGCONFIG("MAS3507D: right->left gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_to_percentage(val));
break;
case 0x107fb:
- logerror("MAS3507D: right->right gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_to_percentage(val));
- set_output_gain(1, gain_to_percentage(val));
+ gain_rr = gain_to_percentage(val);
+ LOGCONFIG("MAS3507D: right->right gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_rr);
break;
- default: logerror("MAS3507D: %d:%04x = %05x\n", bank, adr, val); break;
+ default: LOGCONFIG("MAS3507D: %d:%04x = %05x\n", bank, adr, val); break;
}
}
void mas3507d_device::reg_write(uint32_t adr, uint32_t val)
{
switch(adr) {
- case 0x8e: logerror("MAS3507D: DCCF = %05x\n", val); break;
- case 0xaa: logerror("MAS3507D: Mute/bypass = %05x\n", val); break;
- case 0xe6: logerror("MAS3507D: StartupConfig = %05x\n", val); break;
- case 0xe7: logerror("MAS3507D: Kprescale = %05x\n", val); break;
- case 0x6b: logerror("MAS3507D: Kbass = %05x\n", val); break;
- case 0x6f: logerror("MAS3507D: Ktreble = %05x\n", val); break;
- default: logerror("MAS3507D: reg %02x = %05x\n", adr, val); break;
+ case 0x8e: LOGCONFIG("MAS3507D: DCCF = %05x\n", val); break;
+ case 0xaa:
+ LOGCONFIG("MAS3507D: Mute/bypass = %05x\n", val);
+ is_muted = val == 1;
+ break;
+ case 0xe6: LOGCONFIG("MAS3507D: StartupConfig = %05x\n", val); break;
+ case 0xe7: LOGCONFIG("MAS3507D: Kprescale = %05x\n", val); break;
+ case 0x6b: LOGCONFIG("MAS3507D: Kbass = %05x\n", val); break;
+ case 0x6f: LOGCONFIG("MAS3507D: Ktreble = %05x\n", val); break;
+ default: LOGCONFIG("MAS3507D: reg %02x = %05x\n", adr, val); break;
}
}
void mas3507d_device::run_program(uint32_t adr)
{
switch(adr) {
- case 0xfcb: logerror("MAS3507D: validate OutputConfig\n"); break;
- default: logerror("MAS3507D: run %04x\n", adr); break;
+ case 0xfcb: LOGCONFIG("MAS3507D: validate OutputConfig\n"); break;
+ default: LOGCONFIG("MAS3507D: run %04x\n", adr); break;
}
}
-void mas3507d_device::fill_buffer()
+void mas3507d_device::sid_w(uint8_t byte)
{
- while(mp3_count + 2 < mp3data.size()) {
- u16 v = cb_sample();
- mp3data[mp3_count++] = v >> 8;
- mp3data[mp3_count++] = v;
+ if (mp3data_count >= mp3data.size()) {
+ std::copy(mp3data.begin() + 1, mp3data.end(), mp3data.begin());
+ mp3data_count--;
}
- int scount = mp3dec_decode_frame(&mp3_dec, static_cast<const uint8_t *>(&mp3data[0]), mp3_count, static_cast<mp3d_sample_t *>(&samples[0]), &mp3_info);
+ mp3data[mp3data_count++] = byte;
- if(!scount) {
- int to_drop = mp3_info.frame_bytes;
- // At 1MHz, we can transfer around 2082 bytes/video frame. So
- // that puts a boundary on how much we're ready to drop
- if(to_drop > 2082 || !to_drop)
- to_drop = 2082;
- std::copy(mp3data.begin() + to_drop, mp3data.end(), mp3data.begin());
- mp3_count -= to_drop;
+ cb_demand(mp3data_count < mp3data.size());
+}
+
+void mas3507d_device::fill_buffer()
+{
+ cb_mpeg_frame_sync(0);
+
+ int pos = 0, frame_sample_rate = 0;
+ bool decoded_frame = mp3dec->decode_buffer(pos, mp3data_count, &samples[0], sample_count, frame_sample_rate, frame_channels);
+ samples_idx = 0;
+
+ if (!decoded_frame || sample_count == 0) {
+ // Frame decode failed
+ if (mp3data_count >= mp3data.size()) {
+ std::copy(mp3data.begin() + 1, mp3data.end(), mp3data.begin());
+ mp3data_count--;
+ }
+
+ cb_demand(1); // always request more data when nothing could be decoded to force potentially stale data out of the buffer
return;
}
- std::copy(mp3data.begin() + mp3_info.frame_bytes, mp3data.end(), mp3data.begin());
- mp3_count -= mp3_info.frame_bytes;
+ std::copy(mp3data.begin() + pos, mp3data.end(), mp3data.begin());
+ mp3data_count -= pos;
- sample_count = scount;
+ stream->set_sample_rate(frame_sample_rate);
- if(mp3_info.hz != current_rate) {
- current_rate = mp3_info.hz;
- stream->set_sample_rate(current_rate);
- }
+ decoded_frame_count++;
+ cb_mpeg_frame_sync(1);
+
+ cb_demand(mp3data_count < mp3data.size());
}
void mas3507d_device::append_buffer(std::vector<write_stream_view> &outputs, int &pos, int scount)
{
- buffered_frame_count = scount;
+ const int bytes_per_sample = std::min(frame_channels, 2); // More than 2 channels is unsupported here
+ const int s1 = std::min(scount - pos, sample_count);
+ const stream_buffer::sample_t sample_scale = 1.0 / 32768.0;
+ const stream_buffer::sample_t mute_scale = is_muted ? 0.0 : 1.0;
- int s1 = scount - pos;
- if(s1 > sample_count)
- s1 = sample_count;
+ for(int i = 0; i < s1; i++) {
+ const stream_buffer::sample_t lsamp_mixed = stream_buffer::sample_t(samples[samples_idx * bytes_per_sample]) * sample_scale * mute_scale * gain_ll;
+ const stream_buffer::sample_t rsamp_mixed = stream_buffer::sample_t(samples[samples_idx * bytes_per_sample + (bytes_per_sample >> 1)]) * sample_scale * mute_scale * gain_rr;
- if(mp3_info.channels == 1) {
- for(int i=0; i<s1; i++) {
- outputs[0].put_int(i+pos, samples[i], 32768);
- outputs[1].put_int(i+pos, samples[i], 32768);
- }
- } else {
- for(int i=0; i<s1; i++) {
- outputs[0].put_int(i+pos, samples[i*2], 32768);
- outputs[1].put_int(i+pos, samples[i*2+1], 32768);
- }
- }
+ outputs[0].put(pos, lsamp_mixed);
+ outputs[1].put(pos, rsamp_mixed);
- if(s1 == sample_count) {
- pos += s1;
- sample_count = 0;
- total_frame_count += s1;
- return;
+ samples_idx++;
+ pos++;
+
+ if(samples_idx >= sample_count) {
+ sample_count = 0;
+ return;
+ }
}
+}
- if(mp3_info.channels == 1)
- std::copy(samples.begin() + s1, samples.begin() + sample_count, samples.begin());
- else
- std::copy(samples.begin() + s1*2, samples.begin() + sample_count*2, samples.begin());
+void mas3507d_device::reset_playback()
+{
+ std::fill(mp3data.begin(), mp3data.end(), 0);
+ std::fill(samples.begin(), samples.end(), 0);
- pos += s1;
- sample_count -= s1;
- total_frame_count += s1;
+ mp3dec->clear();
+ mp3data_count = 0;
+ sample_count = 0;
+ decoded_frame_count = 0;
+ samples_idx = 0;
}
void mas3507d_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
@@ -401,25 +490,16 @@ void mas3507d_device::sound_stream_update(sound_stream &stream, std::vector<read
int csamples = outputs[0].samples();
int pos = 0;
- append_buffer(outputs, pos, csamples);
- for(;;) {
- if(pos == csamples)
- return;
- fill_buffer();
- if(!sample_count) {
- // In the case of a bad frame or no frames being around, reset the state of the decoder
- mp3dec_init(&mp3_dec);
- memset(mp3data.data(), 0, mp3data.size());
- memset(samples.data(), 0, samples.size());
- mp3_count = 0;
- sample_count = 0;
- total_frame_count = 0;
- buffered_frame_count = 0;
+ while(pos < csamples) {
+ if(sample_count == 0)
+ fill_buffer();
+ if(sample_count <= 0) {
outputs[0].fill(0, pos);
outputs[1].fill(0, pos);
return;
}
+
append_buffer(outputs, pos, csamples);
}
}