|
| 1 | +// license:BSD-3-Clause |
| 2 | +// copyright-holders:MagikalUnicorn |
| 3 | + |
| 4 | +/* |
| 5 | + * Texas Instruments TMS320AV110 MPEG-1 audio decoder (preliminary) |
| 6 | + * |
| 7 | + * The implemented subset consists of the host register window, memory-mapped |
| 8 | + * compressed-data input, reset-controlled REQ signalling, and MPEG-1 Audio |
| 9 | + * Layer II reconstruction. Input-buffer-full backpressure and other |
| 10 | + * control/status register behaviour are not yet fully implemented. |
| 11 | + * |
| 12 | + * Reference: Texas Instruments data sheet SCSS013C, revised August 1995. |
| 13 | + */ |
| 14 | + |
| 15 | +#include "emu.h" |
| 16 | +#include "tms320av110.h" |
| 17 | + |
| 18 | +#include "mpeg_audio.h" |
| 19 | + |
| 20 | +namespace { |
| 21 | + |
| 22 | +// The host interface has seven address pins, SADDR6 through SADDR0. |
| 23 | +constexpr unsigned HOST_ADDRESS_BITS = 7; |
| 24 | +constexpr offs_t HOST_ADDRESS_MASK = (1U << HOST_ADDRESS_BITS) - 1; |
| 25 | + |
| 26 | +// DATAIN is the data sheet's memory-mapped compressed-audio input register. |
| 27 | +constexpr offs_t REG_DATAIN = 0x18; |
| 28 | + |
| 29 | +// MPEG audio is presented as left and right PCM output slots, including mono streams. |
| 30 | +constexpr unsigned LEFT_CHANNEL = 0; |
| 31 | +constexpr unsigned RIGHT_CHANNEL = 1; |
| 32 | +constexpr unsigned OUTPUT_CHANNELS = 2; |
| 33 | + |
| 34 | +// Used for sound scheduling until an MPEG header supplies the stream rate. |
| 35 | +constexpr u32 INITIAL_SAMPLE_RATE = 44'100; |
| 36 | + |
| 37 | +// MPEG-1 Layer II produces 1,152 samples per channel for each decoded frame. |
| 38 | +constexpr unsigned MPEG_LAYER_II_SAMPLES_PER_FRAME = 1'152; |
| 39 | + |
| 40 | +// A Layer II frame is largest at the maximum bit rate and minimum sample rate. |
| 41 | +constexpr unsigned MAX_MPEG1_LAYER_II_BIT_RATE = 384'000; |
| 42 | +constexpr unsigned MIN_MPEG1_SAMPLE_RATE = 32'000; |
| 43 | +constexpr unsigned MPEG1_LAYER_II_FRAME_SCALE = 144; |
| 44 | +constexpr unsigned MPEG_FRAME_PADDING_BYTES = 1; |
| 45 | +constexpr unsigned MAX_MPEG_AUDIO_FRAME_BYTES = |
| 46 | + (MPEG1_LAYER_II_FRAME_SCALE * MAX_MPEG1_LAYER_II_BIT_RATE / MIN_MPEG1_SAMPLE_RATE) + MPEG_FRAME_PADDING_BYTES; |
| 47 | + |
| 48 | +// Retain two maximum-sized frames so the frame-based decoder can accept streaming input. |
| 49 | +constexpr unsigned INPUT_BUFFER_BYTES = 2 * MAX_MPEG_AUDIO_FRAME_BYTES; |
| 50 | + |
| 51 | +} // anonymous namespace |
| 52 | + |
| 53 | +DEFINE_DEVICE_TYPE(TMS320AV110, tms320av110_device, "tms320av110", "Texas Instruments TMS320AV110 MPEG Audio Decoder") |
| 54 | + |
| 55 | +struct tms320av110_device::decoder_state |
| 56 | +{ |
| 57 | + std::array<u8, INPUT_BUFFER_BYTES> input{}; |
| 58 | + std::array<s16, MPEG_LAYER_II_SAMPLES_PER_FRAME * OUTPUT_CHANNELS> pcm{}; |
| 59 | + std::unique_ptr<mpeg_audio> decoder; |
| 60 | + unsigned input_bytes = 0; |
| 61 | + unsigned input_bit_position = 0; |
| 62 | + unsigned pcm_position = 0; |
| 63 | + unsigned pcm_count = 0; |
| 64 | + unsigned pcm_channels = 0; |
| 65 | +}; |
| 66 | + |
| 67 | +tms320av110_device::tms320av110_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : |
| 68 | + device_t(mconfig, TMS320AV110, tag, owner, clock), |
| 69 | + device_sound_interface(mconfig, *this), |
| 70 | + m_stream(nullptr), |
| 71 | + m_req_cb(*this), |
| 72 | + m_decoder(std::make_unique<decoder_state>()), |
| 73 | + m_reset_asserted(true) |
| 74 | +{ |
| 75 | +} |
| 76 | + |
| 77 | +tms320av110_device::~tms320av110_device() = default; |
| 78 | + |
| 79 | +void tms320av110_device::device_start() |
| 80 | +{ |
| 81 | + m_stream = stream_alloc(0, OUTPUT_CHANNELS, INITIAL_SAMPLE_RATE); |
| 82 | + decoder_create(); |
| 83 | + |
| 84 | + save_item(NAME(m_decoder->input)); |
| 85 | + save_item(NAME(m_decoder->pcm)); |
| 86 | + save_item(NAME(m_decoder->input_bytes)); |
| 87 | + save_item(NAME(m_decoder->input_bit_position)); |
| 88 | + save_item(NAME(m_decoder->pcm_position)); |
| 89 | + save_item(NAME(m_decoder->pcm_count)); |
| 90 | + save_item(NAME(m_decoder->pcm_channels)); |
| 91 | + save_item(NAME(m_reset_asserted)); |
| 92 | +} |
| 93 | + |
| 94 | +void tms320av110_device::device_reset() |
| 95 | +{ |
| 96 | + decoder_reset(); |
| 97 | + m_reset_asserted = true; |
| 98 | + update_req(); |
| 99 | +} |
| 100 | + |
| 101 | +void tms320av110_device::decoder_create() |
| 102 | +{ |
| 103 | + m_decoder->decoder = std::make_unique<mpeg_audio>(m_decoder->input.data(), mpeg_audio::L2, false, 0); |
| 104 | +} |
| 105 | + |
| 106 | +void tms320av110_device::decoder_reset() |
| 107 | +{ |
| 108 | + if (m_stream) |
| 109 | + { |
| 110 | + m_stream->update(); |
| 111 | + m_stream->set_sample_rate(INITIAL_SAMPLE_RATE); |
| 112 | + } |
| 113 | + |
| 114 | + m_decoder->input.fill(0); |
| 115 | + m_decoder->pcm.fill(0); |
| 116 | + m_decoder->input_bytes = 0; |
| 117 | + m_decoder->input_bit_position = 0; |
| 118 | + m_decoder->pcm_position = 0; |
| 119 | + m_decoder->pcm_count = 0; |
| 120 | + m_decoder->pcm_channels = 0; |
| 121 | + decoder_create(); |
| 122 | +} |
| 123 | + |
| 124 | +bool tms320av110_device::decode_frame() |
| 125 | +{ |
| 126 | + int position = m_decoder->input_bit_position; |
| 127 | + int sample_count = 0; |
| 128 | + int sample_rate = 0; |
| 129 | + int channels = 0; |
| 130 | + if (!m_decoder->decoder->decode_buffer( |
| 131 | + position, |
| 132 | + m_decoder->input_bytes * 8, |
| 133 | + m_decoder->pcm.data(), |
| 134 | + sample_count, |
| 135 | + sample_rate, |
| 136 | + channels)) |
| 137 | + { |
| 138 | + return false; |
| 139 | + } |
| 140 | + |
| 141 | + assert(sample_count <= MPEG_LAYER_II_SAMPLES_PER_FRAME); |
| 142 | + assert((channels == 1) || (channels == OUTPUT_CHANNELS)); |
| 143 | + m_decoder->pcm_position = 0; |
| 144 | + m_decoder->pcm_count = sample_count; |
| 145 | + m_decoder->pcm_channels = channels; |
| 146 | + |
| 147 | + const unsigned bytes_consumed = position / 8; |
| 148 | + if (bytes_consumed) |
| 149 | + { |
| 150 | + std::move( |
| 151 | + m_decoder->input.begin() + bytes_consumed, |
| 152 | + m_decoder->input.begin() + m_decoder->input_bytes, |
| 153 | + m_decoder->input.begin()); |
| 154 | + m_decoder->input_bytes -= bytes_consumed; |
| 155 | + } |
| 156 | + m_decoder->input_bit_position = position & 7; |
| 157 | + update_req(); |
| 158 | + |
| 159 | + if (sample_rate && (sample_rate != m_stream->sample_rate())) |
| 160 | + m_stream->set_sample_rate(sample_rate); |
| 161 | + |
| 162 | + return true; |
| 163 | +} |
| 164 | + |
| 165 | +void tms320av110_device::fifo_w(u8 data) |
| 166 | +{ |
| 167 | + if (m_decoder->input_bytes == m_decoder->input.size()) |
| 168 | + { |
| 169 | + logerror("%s: compressed-audio input overflow\n", machine().describe_context()); |
| 170 | + return; |
| 171 | + } |
| 172 | + |
| 173 | + m_decoder->input[m_decoder->input_bytes++] = data; |
| 174 | + update_req(); |
| 175 | +} |
| 176 | + |
| 177 | +void tms320av110_device::update_req() |
| 178 | +{ |
| 179 | + // REQ is active low and is held inactive during reset or while the input buffer is full. |
| 180 | + m_req_cb((m_reset_asserted || (m_decoder->input_bytes == m_decoder->input.size())) ? ASSERT_LINE : CLEAR_LINE); |
| 181 | +} |
| 182 | + |
| 183 | +void tms320av110_device::reset_w(int state) |
| 184 | +{ |
| 185 | + const bool asserted = !state; |
| 186 | + if (asserted && !m_reset_asserted) |
| 187 | + decoder_reset(); |
| 188 | + |
| 189 | + m_reset_asserted = asserted; |
| 190 | + update_req(); |
| 191 | +} |
| 192 | + |
| 193 | +u8 tms320av110_device::read(offs_t offset) |
| 194 | +{ |
| 195 | + if (m_reset_asserted) |
| 196 | + return 0; // The data sheet specifies that RESET low disables host accesses. |
| 197 | + |
| 198 | + offset &= HOST_ADDRESS_MASK; |
| 199 | + if (!machine().side_effects_disabled()) |
| 200 | + logerror("%s: unimplemented register read %02x\n", machine().describe_context(), offset); |
| 201 | + return 0; |
| 202 | +} |
| 203 | + |
| 204 | +void tms320av110_device::write(offs_t offset, u8 data) |
| 205 | +{ |
| 206 | + if (m_reset_asserted) |
| 207 | + return; // The data sheet specifies that RESET low disables host accesses. |
| 208 | + |
| 209 | + offset &= HOST_ADDRESS_MASK; |
| 210 | + if (offset == REG_DATAIN) |
| 211 | + { |
| 212 | + fifo_w(data); |
| 213 | + return; |
| 214 | + } |
| 215 | + |
| 216 | + logerror("%s: unimplemented register write %02x = %02x\n", machine().describe_context(), offset, data); |
| 217 | +} |
| 218 | + |
| 219 | +void tms320av110_device::sound_stream_update(sound_stream &stream) |
| 220 | +{ |
| 221 | + for (int sample = 0; sample < stream.samples(); sample++) |
| 222 | + { |
| 223 | + if ((m_decoder->pcm_position == m_decoder->pcm_count) && !decode_frame()) |
| 224 | + { |
| 225 | + stream.put(LEFT_CHANNEL, sample, 0.0F); |
| 226 | + stream.put(RIGHT_CHANNEL, sample, 0.0F); |
| 227 | + continue; |
| 228 | + } |
| 229 | + |
| 230 | + const unsigned position = m_decoder->pcm_position * m_decoder->pcm_channels; |
| 231 | + const s16 left = m_decoder->pcm[position]; |
| 232 | + const s16 right = (m_decoder->pcm_channels == OUTPUT_CHANNELS) |
| 233 | + ? m_decoder->pcm[position + RIGHT_CHANNEL] |
| 234 | + : left; |
| 235 | + stream.put_int(LEFT_CHANNEL, sample, left, 32'768); |
| 236 | + stream.put_int(RIGHT_CHANNEL, sample, right, 32'768); |
| 237 | + m_decoder->pcm_position++; |
| 238 | + } |
| 239 | +} |
0 commit comments