summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--src/devices/sound/mas3507d.cpp112
-rw-r--r--src/devices/sound/mas3507d.h29
-rw-r--r--src/devices/sound/samples.cpp42
-rw-r--r--src/devices/sound/samples.h20
-rw-r--r--src/mame/drivers/ksys573.cpp5
-rw-r--r--src/mame/machine/k573dio.cpp44
-rw-r--r--src/mame/machine/k573dio.h9
-rw-r--r--src/mame/machine/k573fpga.cpp416
-rw-r--r--src/mame/machine/k573fpga.h74
9 files changed, 216 insertions, 535 deletions
diff --git a/src/devices/sound/mas3507d.cpp b/src/devices/sound/mas3507d.cpp
index 519136cfb4c..cd04e163c6b 100644
--- a/src/devices/sound/mas3507d.cpp
+++ b/src/devices/sound/mas3507d.cpp
@@ -7,20 +7,31 @@
#include "emu.h"
#include "mas3507d.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"
+
// device type definition
DEFINE_DEVICE_TYPE(MAS3507D, mas3507d_device, "mas3507d", "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)
- , m_samples(nullptr)
{
}
void mas3507d_device::device_start()
{
+ current_rate = 44100;
+ stream = stream_alloc(0, 2, current_rate);
+ mp3dec_init(&mp3_dec);
+ cb_sample.resolve();
}
void mas3507d_device::device_reset()
@@ -31,6 +42,7 @@ void mas3507d_device::device_reset()
i2c_bus_address = UNKNOWN;
i2c_bus_curbit = -1;
i2c_bus_curval = 0;
+ total_sample_count = 0;
}
void mas3507d_device::i2c_scl_w(bool line)
@@ -272,11 +284,6 @@ void mas3507d_device::mem_write(int bank, uint32_t adr, uint32_t val)
case 0x0032f: logerror("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));
-
- if (m_samples != nullptr) {
- m_samples->set_volume(0, gain_to_percentage(val));
- }
-
break;
case 0x107f9:
logerror("MAS3507D: left->right gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_to_percentage(val));
@@ -286,11 +293,6 @@ void mas3507d_device::mem_write(int bank, uint32_t adr, uint32_t val)
break;
case 0x107fb:
logerror("MAS3507D: right->right gain = %05x (%d dB, %f%%)\n", val, gain_to_db(val), gain_to_percentage(val));
-
- if (m_samples != nullptr) {
- m_samples->set_volume(1, gain_to_percentage(val));
- }
-
break;
default: logerror("MAS3507D: %d:%04x = %05x\n", bank, adr, val); break;
}
@@ -317,6 +319,94 @@ void mas3507d_device::run_program(uint32_t adr)
}
}
+void mas3507d_device::fill_buffer()
+{
+ while(mp3_count < mp3data.size()) {
+ u16 v = cb_sample();
+ mp3data[mp3_count++] = v >> 8;
+ mp3data[mp3_count++] = v;
+ }
+
+ int scount = mp3dec_decode_frame(&mp3_dec, mp3data.begin(), mp3_count, samples.begin(), &mp3_info);
+
+ 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;
+ return;
+ }
+
+ std::copy(mp3data.begin() + mp3_info.frame_bytes, mp3data.end(), mp3data.begin());
+ mp3_count -= mp3_info.frame_bytes;
+
+ if(mp3_info.channels == 1)
+ sample_count = scount;
+ else
+ sample_count = scount;
+
+ if(mp3_info.hz != current_rate) {
+ current_rate = mp3_info.hz;
+ stream->set_sample_rate(current_rate);
+ }
+}
+
+void mas3507d_device::append_buffer(stream_sample_t **outputs, int &pos, int scount)
+{
+ if(!sample_count)
+ return;
+
+ int s1 = scount - pos;
+ if(s1 > sample_count)
+ s1 = sample_count;
+
+ if(mp3_info.channels == 1) {
+ for(int i=0; i<s1; i++) {
+ stream_sample_t v = samples[i];
+ outputs[0][i+pos] = v;
+ outputs[1][i+pos] = v;
+ }
+ } else {
+ for(int i=0; i<s1; i++) {
+ outputs[0][i+pos] = samples[i*2];
+ outputs[1][i+pos] = samples[i*2+1];
+ }
+ }
+
+ if(s1 == sample_count) {
+ pos += s1;
+ 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());
+
+ pos += s1;
+ sample_count -= s1;
+}
+
void mas3507d_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
+ int pos = 0;
+ total_sample_count += samples;
+ append_buffer(outputs, pos, samples);
+ for(;;) {
+ if(pos == samples)
+ return;
+ fill_buffer();
+ if(!sample_count) {
+ for(int i=pos; i != samples; i++) {
+ outputs[0][i] = 0;
+ outputs[1][i] = 0;
+ }
+ return;
+ }
+ append_buffer(outputs, pos, samples);
+ }
}
diff --git a/src/devices/sound/mas3507d.h b/src/devices/sound/mas3507d.h
index a1c852f0eeb..d937e9c6414 100644
--- a/src/devices/sound/mas3507d.h
+++ b/src/devices/sound/mas3507d.h
@@ -5,13 +5,9 @@
#pragma once
-#include <queue>
-
-#include "sound/samples.h"
-
-//**************************************************************************
-// TYPE DEFINITIONS
-//**************************************************************************
+#define MINIMP3_ONLY_MP3
+#define MINIMP3_NO_STDIO
+#include "minimp3/minimp3.h"
class mas3507d_device : public device_t, public device_sound_interface
{
@@ -19,12 +15,15 @@ public:
// construction/destruction
mas3507d_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
+ auto sample_cb() { return cb_sample.bind(); }
+
int i2c_scl_r();
int i2c_sda_r();
void i2c_scl_w(bool line);
void i2c_sda_w(bool line);
- void set_samples(samples_device *sample) { m_samples = sample; }
+ void reset_sample_count() { total_sample_count = 0; }
+ u32 get_sample_count() const { return total_sample_count; }
protected:
virtual void device_start() override;
@@ -32,12 +31,23 @@ protected:
virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override;
private:
+ devcb_read16 cb_sample;
+
enum { IDLE, STARTED, NAK, ACK, ACK2 } i2c_bus_state;
enum { UNKNOWN, VALIDATED, WRONG } i2c_bus_address;
+ std::array<u8, 8000> mp3data;
+ std::array<mp3d_sample_t, MINIMP3_MAX_SAMPLES_PER_FRAME> samples;
bool i2c_scli, i2c_sclo, i2c_sdai, i2c_sdao;
int i2c_bus_curbit;
uint8_t i2c_bus_curval;
+ int mp3_count, sample_count, current_rate;
+ u32 total_sample_count;
+
+ mp3dec_t mp3_dec;
+ mp3dec_frame_info_t mp3_info;
+
+ sound_stream *stream;
void i2c_nak();
bool i2c_device_got_address(uint8_t address);
@@ -54,7 +64,8 @@ private:
void run_program(uint32_t adr);
void reg_write(uint32_t adr, uint32_t val);
- samples_device *m_samples;
+ void fill_buffer();
+ void append_buffer(stream_sample_t **outputs, int &pos, int samples);
};
diff --git a/src/devices/sound/samples.cpp b/src/devices/sound/samples.cpp
index 21617148669..40ff619cdce 100644
--- a/src/devices/sound/samples.cpp
+++ b/src/devices/sound/samples.cpp
@@ -119,27 +119,6 @@ void samples_device::start_raw(uint8_t channel, const int16_t *sampledata, uint3
chan.loop = loop;
}
-void samples_device::update_raw(uint8_t channel, const int16_t *sampledata, uint32_t samples, uint32_t frequency, bool loop)
-{
- assert(channel < m_channels);
-
- // update the parameters
- channel_t &chan = m_channel[channel];
- bool is_update = chan.source != nullptr;
-
- chan.source = sampledata;
- chan.source_length = samples;
- chan.basefreq = frequency;
- chan.step = (int64_t(chan.basefreq) << FRAC_BITS) / machine().sample_rate();
- chan.loop = loop;
-
- if (!is_update) {
- chan.source_num = -1;
- chan.pos = 0;
- chan.frac = 0;
- }
-}
-
//-------------------------------------------------
// set_frequency - set the playback frequency of
@@ -342,13 +321,8 @@ void samples_device::device_post_load()
void samples_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
- if (!m_samples_update_cb.isnull()) {
- m_samples_update_cb();
- }
-
// find the channel with this stream
for (int channel = 0; channel < m_channels; channel++)
- {
if (&stream == m_channel[channel].stream)
{
channel_t &chan = m_channel[channel];
@@ -381,9 +355,7 @@ void samples_device::sound_stream_update(sound_stream &stream, stream_sample_t *
if (pos >= sample_length)
{
if (chan.loop)
- {
pos %= sample_length;
- }
else
{
chan.source = nullptr;
@@ -403,7 +375,6 @@ void samples_device::sound_stream_update(sound_stream &stream, stream_sample_t *
memset(buffer, 0, samples * sizeof(*buffer));
break;
}
- }
}
@@ -667,16 +638,3 @@ bool samples_device::load_samples()
}
return ok;
}
-
-uint32_t samples_device::get_position(uint8_t channel)
-{
- assert(channel < m_channels);
-
- channel_t &chan = m_channel[channel];
-
- if (chan.source == nullptr) {
- return 0;
- }
-
- return chan.pos;
-}
diff --git a/src/devices/sound/samples.h b/src/devices/sound/samples.h
index ea50f51ea30..f5f1ca088f0 100644
--- a/src/devices/sound/samples.h
+++ b/src/devices/sound/samples.h
@@ -26,7 +26,6 @@ DECLARE_DEVICE_TYPE(SAMPLES, samples_device)
//**************************************************************************
#define SAMPLES_START_CB_MEMBER(_name) void _name()
-#define SAMPLES_UPDATE_CB_MEMBER(_name) void _name()
// ======================> samples_device
@@ -35,7 +34,6 @@ class samples_device : public device_t,
{
public:
typedef device_delegate<void ()> start_cb_delegate;
- typedef device_delegate<void ()> update_cb_delegate;
// construction/destruction
samples_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
@@ -55,30 +53,13 @@ public:
set_samples_start_callback(start_cb_delegate(callback, name, nullptr, static_cast<FunctionClass *>(nullptr)));
}
- // update callback helpers
- void set_samples_update_callback(update_cb_delegate callback) {
- m_samples_update_cb = callback;
- m_samples_update_cb.bind_relative_to(*owner());
- }
-
- template <class FunctionClass> void set_samples_update_callback(const char *devname, void (FunctionClass::*callback)(), const char *name)
- {
- set_samples_update_callback(update_cb_delegate(callback, name, devname, static_cast<FunctionClass *>(nullptr)));
- }
- template <class FunctionClass> void set_samples_update_callback(void (FunctionClass::*callback)(), const char *name)
- {
- set_samples_update_callback(update_cb_delegate(callback, name, nullptr, static_cast<FunctionClass *>(nullptr)));
- }
-
// getters
bool playing(uint8_t channel) const;
uint32_t base_frequency(uint8_t channel) const;
- uint32_t get_position(uint8_t channel);
// start/stop helpers
void start(uint8_t channel, uint32_t samplenum, bool loop = false);
void start_raw(uint8_t channel, const int16_t *sampledata, uint32_t samples, uint32_t frequency, bool loop = false);
- void update_raw(uint8_t channel, const int16_t *sampledata, uint32_t samples, uint32_t frequency, bool loop = false);
void pause(uint8_t channel, bool pause = true);
void stop(uint8_t channel);
void stop_all();
@@ -135,7 +116,6 @@ protected:
bool load_samples();
start_cb_delegate m_samples_start_cb; // optional callback
- start_cb_delegate m_samples_update_cb; // optional callback
// internal state
std::vector<channel_t> m_channel;
diff --git a/src/mame/drivers/ksys573.cpp b/src/mame/drivers/ksys573.cpp
index 76a3b7f3cbd..7ac47336dc2 100644
--- a/src/mame/drivers/ksys573.cpp
+++ b/src/mame/drivers/ksys573.cpp
@@ -2395,7 +2395,6 @@ void ksys573_state::drmn9m(machine_config &config)
{
k573d(config);
subdevice<k573dio_device>("k573dio")->output_callback().set(FUNC(ksys573_state::drmn_output_callback));
- subdevice<k573dio_device>("k573dio")->set_mp3_dynamic_base(0x00540000);
casszi(config);
@@ -2406,7 +2405,6 @@ void ksys573_state::drmn10m(machine_config &config)
{
k573d(config);
subdevice<k573dio_device>("k573dio")->output_callback().set(FUNC(ksys573_state::drmn_output_callback));
- subdevice<k573dio_device>("k573dio")->set_mp3_dynamic_base(0x00500000);
casszi(config);
@@ -2454,13 +2452,11 @@ void ksys573_state::gtfrk10m(machine_config &config)
k573d(config);
casszi(config);
pccard1_32mb(config);
- subdevice<k573dio_device>("k573dio")->set_mp3_dynamic_base(0x00540000);
}
void ksys573_state::gtfrk10mb(machine_config &config)
{
gtrfrk7m(config);
- subdevice<k573dio_device>("k573dio")->set_mp3_dynamic_base(0x00540000);
KONAMI_573_NETWORK_PCB_UNIT(config, "k573npu", 0);
}
@@ -2470,7 +2466,6 @@ void ksys573_state::gtfrk11m(machine_config &config)
k573d(config);
casszi(config);
pccard1_32mb(config);
- subdevice<k573dio_device>("k573dio")->set_mp3_dynamic_base(0x00500000);
}
// Miscellaneous
diff --git a/src/mame/machine/k573dio.cpp b/src/mame/machine/k573dio.cpp
index 86c92e3c11f..370db9a2089 100644
--- a/src/mame/machine/k573dio.cpp
+++ b/src/mame/machine/k573dio.cpp
@@ -110,6 +110,7 @@ k573dio_device::k573dio_device(const machine_config &mconfig, const char *tag, d
: device_t(mconfig, KONAMI_573_DIGITAL_IO_BOARD, tag, owner, clock),
k573fpga(*this, "k573fpga"),
digital_id(*this, "digital_id"),
+ mas3507d(*this, "mpeg"),
output_cb(*this),
is_ddrsbm_fpga(false)
{
@@ -119,14 +120,11 @@ void k573dio_device::device_start()
{
output_cb.resolve_safe();
- ram = std::make_unique<uint16_t[]>(32 * 1024 * 1024);
- save_pointer(NAME(ram), 32 * 1024 * 1024 );
+ ram = std::make_unique<uint16_t[]>(0x2000000/2);
+ save_pointer(NAME(ram), 0x2000000/2 );
k573fpga->set_ram(ram.get());
k573fpga->set_ddrsbm_fpga(is_ddrsbm_fpga);
- k573fpga->set_mp3_dynamic_base(mp3_dynamic_base);
-
- device_reset();
}
void k573dio_device::device_reset()
@@ -149,8 +147,12 @@ const tiny_rom_entry *k573dio_device::device_rom_region() const
void k573dio_device::device_add_mconfig(machine_config &config)
{
- KONAMI_573_DIGITAL_FPGA(config, "k573fpga");
- DS2401(config, "digital_id");
+ KONAMI_573_DIGITAL_FPGA(config, k573fpga);
+ DS2401(config, digital_id);
+ MAS3507D(config, mas3507d);
+ mas3507d->sample_cb().set(k573fpga, FUNC(k573fpga_device::get_decrypted));
+ mas3507d->add_route(0, ":lspeaker", 1.0);
+ mas3507d->add_route(1, ":rspeaker", 1.0);
}
void k573dio_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
@@ -207,13 +209,16 @@ READ16_MEMBER(k573dio_device::a80_r)
WRITE16_MEMBER(k573dio_device::mpeg_start_adr_high_w)
{
logerror("FPGA MPEG start address high %04x\n", data);
- k573fpga->set_mp3_start_adr((k573fpga->get_mp3_start_adr() & 0x0000ffff) | (data << 16)); // high
+ k573fpga->set_mp3_cur_adr((k573fpga->get_mp3_cur_adr() & 0x0000ffff) | (data << 16)); // high
}
WRITE16_MEMBER(k573dio_device::mpeg_start_adr_low_w)
{
logerror("FPGA MPEG start address low %04x\n", data);
- k573fpga->set_mp3_start_adr((k573fpga->get_mp3_start_adr() & 0xffff0000) | data); // low
+ k573fpga->set_mp3_cur_adr((k573fpga->get_mp3_cur_adr() & 0xffff0000) | data); // low
+ if(is_ddrsbm_fpga)
+ k573fpga->set_crypto_key3(0);
+ mas3507d->reset_sample_count();
}
WRITE16_MEMBER(k573dio_device::mpeg_end_adr_high_w)
@@ -230,24 +235,27 @@ WRITE16_MEMBER(k573dio_device::mpeg_end_adr_low_w)
READ16_MEMBER(k573dio_device::mpeg_key_1_r)
{
- return crypto_key1;
+ return k573fpga->get_crypto_key1();
}
WRITE16_MEMBER(k573dio_device::mpeg_key_1_w)
{
logerror("FPGA MPEG key 1/3 %04x\n", data);
k573fpga->set_crypto_key1(data);
- crypto_key1 = data;
}
READ16_MEMBER(k573dio_device::mas_i2c_r)
{
- return k573fpga->i2c_read();
+ int scl = mas3507d->i2c_scl_r() << 13;
+ int sda = mas3507d->i2c_sda_r() << 12;
+
+ return scl | sda;
}
WRITE16_MEMBER(k573dio_device::mas_i2c_w)
{
- k573fpga->i2c_write(data);
+ mas3507d->i2c_scl_w(data & 0x2000);
+ mas3507d->i2c_sda_w(data & 0x1000);
}
READ16_MEMBER(k573dio_device::mpeg_ctrl_r)
@@ -274,21 +282,21 @@ WRITE16_MEMBER(k573dio_device::ram_write_adr_low_w)
READ16_MEMBER(k573dio_device::ram_r)
{
- uint16_t res = ram[ram_read_adr >> 1];
+ uint16_t res = ram[(ram_read_adr & 0x7fffff) >> 1];
ram_read_adr += 2;
return res;
}
WRITE16_MEMBER(k573dio_device::ram_w)
{
- ram[ram_adr >> 1] = data;
+ ram[(ram_adr & 0x1ffffff) >> 1] = data;
ram_adr += 2;
}
WRITE16_MEMBER(k573dio_device::ram_read_adr_high_w)
{
// read and write address are shared
- ram_read_adr = (ram_read_adr & 0x0000ffff) | (data << 16);
+ ram_read_adr = (ram_read_adr & 0x001fffff) | (data << 16);
}
WRITE16_MEMBER(k573dio_device::ram_read_adr_low_w)
@@ -300,7 +308,7 @@ WRITE16_MEMBER(k573dio_device::ram_read_adr_low_w)
READ16_MEMBER(k573dio_device::mp3_playback_high_r)
{
//logerror("mp3_playback_high_r: %08x (%08x)\n", mp3_playback & 0xffff0000, mp3_playback);
- return (k573fpga->get_mp3_playback() & 0xffff0000) >> 16;
+ return (mas3507d->get_sample_count() & 0xffff0000) >> 16;
}
WRITE16_MEMBER(k573dio_device::mp3_playback_high_w)
@@ -312,7 +320,7 @@ WRITE16_MEMBER(k573dio_device::mp3_playback_high_w)
READ16_MEMBER(k573dio_device::mp3_playback_low_r)
{
//logerror("mp3_playback_low_r: %08x (%08x) %08x %08x\n", mp3_playback & 0x0000ffff, mp3_playback, m_samples->get_position(0), m_samples->get_position(1));
- return k573fpga->get_mp3_playback() & 0x0000ffff;
+ return mas3507d->get_sample_count() & 0x0000ffff;
}
WRITE16_MEMBER(k573dio_device::mp3_playback_low_w)
diff --git a/src/mame/machine/k573dio.h b/src/mame/machine/k573dio.h
index e75618139ed..7f41088c515 100644
--- a/src/mame/machine/k573dio.h
+++ b/src/mame/machine/k573dio.h
@@ -15,12 +15,8 @@ public:
auto output_callback() { return output_cb.bind(); }
- required_device<k573fpga_device> k573fpga;
- required_device<ds2401_device> digital_id;
-
void amap(address_map &map);
void set_ddrsbm_fpga(bool flag) { is_ddrsbm_fpga = flag; }
- void set_mp3_dynamic_base(uint32_t base) { mp3_dynamic_base = base; }
DECLARE_READ16_MEMBER(a00_r);
DECLARE_READ16_MEMBER(a02_r);
@@ -74,6 +70,9 @@ protected:
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
private:
+ required_device<k573fpga_device> k573fpga;
+ required_device<ds2401_device> digital_id;
+ required_device<mas3507d_device> mas3507d;
devcb_write8 output_cb;
std::unique_ptr<uint16_t[]> ram;
@@ -83,8 +82,6 @@ private:
void output(int offset, uint16_t data);
bool is_ddrsbm_fpga;
- uint32_t mp3_dynamic_base;
- uint16_t crypto_key1;
};
DECLARE_DEVICE_TYPE(KONAMI_573_DIGITAL_IO_BOARD, k573dio_device)
diff --git a/src/mame/machine/k573fpga.cpp b/src/mame/machine/k573fpga.cpp
index eb2197f7c77..4c8dc5b1177 100644
--- a/src/mame/machine/k573fpga.cpp
+++ b/src/mame/machine/k573fpga.cpp
@@ -5,122 +5,29 @@
#include "k573fpga.h"
-// This will delay playback until there are SAMPLES_BUFFER samples in the buffer.
-// This fixes some timing issues. Increase value to delay playback.
-// The current value was decided by finding a delay that allowed the Konami logo
-// to playback on the Drummania 10th Mix loading sequence, but finish just in time
-// for the game to send the stop playback command before it looped again.
-// TODO: Verify this with real hardware somehow.
-#define SAMPLES_BUFFER 1152 * 30
-#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"
-
-k573fpga_device::k573fpga_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+k573fpga_device::k573fpga_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
device_t(mconfig, KONAMI_573_DIGITAL_FPGA, tag, owner, clock),
- mas3507d(*this, "mpeg"),
- m_samples(*this, "samples"),
use_ddrsbm_fpga(false)
{
}
void k573fpga_device::device_start()
{
- ram_swap = std::make_unique<uint16_t[]>(32 * 1024 * 1024);
- save_pointer(NAME(ram_swap), 32 * 1024 * 1024);
-
- device_reset();
}
void k573fpga_device::device_reset()
{
- mp3_start_adr = 0;
+ mp3_cur_adr = 0;
mp3_end_adr = 0;
crypto_key1 = 0;
crypto_key2 = 0;
crypto_key3 = 0;
- mp3_last_adr = 0;
- mp3_last_decrypt_adr = 0;
- mp3_next_sync = 0;
- last_copied_samples = 0;
- decrypt_finished = false;
-
- if (!channel_l_pcm) {
- free(channel_l_pcm);
- }
-
- channel_l_pcm = nullptr;
- last_buffer_size_channel_l = 0;
-
- if (!channel_r_pcm) {
- free(channel_r_pcm);
- }
-
- channel_r_pcm = nullptr;
- last_buffer_size_channel_r = 0;
-
- memset((void*)ram_swap.get(), 0, 32 * 1024 * 1024 * 2);
- memset(&mp3_info, 0, sizeof(mp3_info));
-
- mas3507d->set_samples(m_samples);
-
- last_position_update = 0;
- position_diff = 0;
-}
-
-void k573fpga_device::device_add_mconfig(machine_config &config)
-{
- MAS3507D(config, "mpeg");
-
- /* sound hardware */
- SPEAKER(config, "lspeaker").front_left();
- SPEAKER(config, "rspeaker").front_right();
- SAMPLES(config, m_samples);
- m_samples->set_channels(2);
- m_samples->set_samples_update_callback(FUNC(k573fpga_device::k573fpga_stream_update));
- m_samples->add_route(0, "lspeaker", 1.0);
- m_samples->add_route(1, "rspeaker", 1.0);
-}
-
-uint32_t k573fpga_device::get_mp3_playback() {
- if (m_samples->get_position(0) == 0 && decrypt_finished) {
- // The game is still requesting position updates after the song has ended.
- // This happens in some games like DDR 4th Mix Plus where it uses
- // the position in song for actual game engine timings.
- // The counter will properly end when the game signals to the FPGA to stop playback.
- last_position_update += position_diff;
- } else {
- if (m_samples->get_position(0) - last_position_update > 0) {
- position_diff = m_samples->get_position(0) - last_position_update;
- }
-
- last_position_update = m_samples->get_position(0);
- }
-
- return last_position_update;
-}
-
-uint16_t k573fpga_device::i2c_read()
-{
- int scl = mas3507d->i2c_scl_r() << 13;
- int sda = mas3507d->i2c_sda_r() << 12;
-
- return scl | sda;
}
-void k573fpga_device::i2c_write(uint16_t data)
+u16 k573fpga_device::get_mpeg_ctrl()
{
- mas3507d->i2c_scl_w(data & 0x2000);
- mas3507d->i2c_sda_w(data & 0x1000);
-}
-
-uint16_t k573fpga_device::get_mpeg_ctrl()
-{
- if (mpeg_ctrl_flag == 0xe000 || m_samples->get_position(0) > 0) {
+ if ((mpeg_ctrl_flag & 0xe000) == 0xe000) {
// This has been tested with real hardware, but this flag is always held 0x1000 when the audio is being played
return 0x1000;
}
@@ -128,50 +35,20 @@ uint16_t k573fpga_device::get_mpeg_ctrl()
return 0x0000;
}
-void k573fpga_device::set_mpeg_ctrl(uint16_t data)
+void k573fpga_device::set_mpeg_ctrl(u16 data)
{
logerror("FPGA MPEG control %c%c%c | %08x %08x\n",
data & 0x8000 ? '#' : '.',
data & 0x4000 ? '#' : '.',
data & 0x2000 ? '#' : '.',
- mp3_start_adr, mp3_end_adr);
+ mp3_cur_adr, mp3_end_adr);
mpeg_ctrl_flag = data;
-
- if (data == 0xa000) {
- // Stop playback
- m_samples->stop(0);
- m_samples->stop(1);
- }
-
- if ((data & 0xa000) == 0xa000) {
- // Reset playback state for start and stop events
- mp3_next_sync = 0;
- mp3_last_adr = mp3_start_adr;
- mp3_last_decrypt_adr = mp3_start_adr;
- last_copied_samples = 0;
- last_buffer_size_channel_l = 0;
- last_buffer_size_channel_r = 0;
-
- last_position_update = 0;
- position_diff = 0;
- decrypt_finished = false;
-
- if (channel_l_pcm) {
- free(channel_l_pcm);
- channel_l_pcm = nullptr;
- }
-
- if (channel_r_pcm) {
- free(channel_r_pcm);
- channel_r_pcm = nullptr;
- }
- }
}
-inline uint16_t k573fpga_device::fpga_decrypt_byte_real(uint16_t v)
+u16 k573fpga_device::decrypt_default(u16 v)
{
- uint16_t m = crypto_key1 ^ crypto_key2;
+ u16 m = crypto_key1 ^ crypto_key2;
v = bitswap<16>(
v,
@@ -203,20 +80,27 @@ inline uint16_t k573fpga_device::fpga_decrypt_byte_real(uint16_t v)
(BIT(m, 0x0) << 0);
v ^= bitswap<16>(
- (uint16_t)crypto_key3,
+ (u16)crypto_key3,
7, 0, 6, 1,
5, 2, 4, 3,
3, 4, 2, 5,
1, 6, 0, 7
);
- return (v >> 8) | ((v & 0xff) << 8);
+ crypto_key1 = (crypto_key1 & 0x8000) | ((crypto_key1 << 1) & 0x7FFE) | ((crypto_key1 >> 14) & 1);
+
+ if(((crypto_key1 >> 15) ^ crypto_key1) & 1)
+ crypto_key2 = (crypto_key2 << 1) | (crypto_key2 >> 15);
+
+ crypto_key3++;
+
+ return v;
}
-inline uint16_t k573fpga_device::fpga_decrypt_byte_ddrsbm(uint16_t data, uint32_t crypto_idx)
+u16 k573fpga_device::decrypt_ddrsbm(u16 data)
{
- uint8_t key[16] = {0};
- uint16_t key_state = bitswap<16>(
+ u8 key[16] = {0};
+ u16 key_state = bitswap<16>(
crypto_key1,
13, 11, 9, 7,
5, 3, 1, 15,
@@ -224,258 +108,46 @@ inline uint16_t k573fpga_device::fpga_decrypt_byte_ddrsbm(uint16_t data, uint32_
6, 4, 2, 0
);
- for (int i = 0; i < 8; i++) {
+ for(int i = 0; i < 8; i++) {
key[i * 2] = key_state & 0xff;
key[i * 2 + 1] = (key_state >> 8) & 0xff;
- key_state = (((((key_state >> 8) >> 7) & 1) | (((key_state >> 8) << 1) & 0xff)) << 8) |
- (((key_state & 0xff) >> 7) & 1) | (((key_state & 0xff) << 1) & 0xff);
+ key_state = ((key_state & 0x8080) >> 7) | ((key_state & 0x7f7f) << 1);
}
- uint16_t output_word = 0;
- for (int cur_bit = 0; cur_bit < 8; cur_bit++) {
- int even_bit_shift = (cur_bit * 2) & 0xff;
- int odd_bit_shift = (cur_bit * 2 + 1) & 0xff;
- int is_even_bit_set = (data & (1 << even_bit_shift)) != 0;
- int is_odd_bit_set = (data & (1 << odd_bit_shift)) != 0;
- int is_key_bit_set = (key[crypto_idx % 16] & (1 << cur_bit)) != 0;
- int is_scramble_bit_set = (key[(crypto_idx - 1) % 16] & (1 << cur_bit)) != 0;
+ u16 output_word = 0;
+ for(int cur_bit = 0; cur_bit < 8; cur_bit++) {
+ int even_bit_shift = cur_bit * 2;
+ int odd_bit_shift = cur_bit * 2 + 1;
+ bool is_even_bit_set = data & (1 << even_bit_shift);
+ bool is_odd_bit_set = data & (1 << odd_bit_shift);
+ bool is_key_bit_set = key[crypto_key3 & 15] & (1 << cur_bit);
+ bool is_scramble_bit_set = key[(crypto_key3 - 1) & 15] & (1 << cur_bit);
- if (is_scramble_bit_set == 1) {
- int t = is_even_bit_set;
- is_even_bit_set = is_odd_bit_set;
- is_odd_bit_set = t;
- }
+ if(is_scramble_bit_set)
+ std::swap(is_even_bit_set, is_odd_bit_set);
- if (((is_even_bit_set ^ is_key_bit_set)) == 1) {
+ if(is_even_bit_set ^ is_key_bit_set)
output_word |= 1 << even_bit_shift;
- }
- if ((is_odd_bit_set) == 1) {
+ if(is_odd_bit_set)
output_word |= 1 << odd_bit_shift;
- }
}
- return (output_word >> 8) | ((output_word & 0xff) << 8);
+ crypto_key3++;
+
+ return output_word;
}
-SAMPLES_UPDATE_CB_MEMBER(k573fpga_device::k573fpga_stream_update)
+u16 k573fpga_device::get_decrypted()
{
- if (mp3_last_adr >= mp3_end_adr) {
- mp3_next_sync = 0;
- return;
- }
-
- if ((mpeg_ctrl_flag & 0xe000) != 0xe000 || (m_samples->get_position(0) < mp3_next_sync && mp3_next_sync != 0)) {
- return;
- }
-
- int new_samples = mp3_info.samples;
- int decrypt_buffer_speed = buffer_speed;
-
- if (mp3_next_sync == 0 && last_copied_samples == 0) {
- crypto_key1 = orig_crypto_key1;
- crypto_key2 = orig_crypto_key2;
- crypto_key3 = orig_crypto_key3;
-
- mp3_last_decrypt_adr = mp3_start_adr;
-
- // Cover a large chunk of ID3 or junk at the beginning of a file when decrypting first frame
- decrypt_buffer_speed = buffer_speed = 0xe00;
-
- last_position_update = 0;
- position_diff = 0;
- decrypt_finished = false;
- }
-
- // Decrypt chunk of data
- if (mp3_last_decrypt_adr < mp3_end_adr && mp3_last_adr + decrypt_buffer_speed >= mp3_last_decrypt_adr) {
- int32_t cur_idx;
+ if(mp3_cur_adr >= mp3_end_adr || (mpeg_ctrl_flag & 0xe000) != 0xe000)
+ return 0;
- for (cur_idx = mp3_last_decrypt_adr; cur_idx - mp3_last_decrypt_adr < decrypt_buffer_speed && cur_idx < mp3_end_adr; cur_idx += 2) {
- if (use_ddrsbm_fpga) {
- ram_swap[cur_idx >> 1] = fpga_decrypt_byte_ddrsbm(ram[cur_idx >> 1], (cur_idx - mp3_start_adr) / 2);
- } else {
- ram_swap[cur_idx >> 1] = fpga_decrypt_byte_real(ram[cur_idx >> 1]);
- }
+ u16 src = ram[(mp3_cur_adr & 0x1ffffff) >> 1];
+ u16 result = use_ddrsbm_fpga ? decrypt_ddrsbm(src) : decrypt_default(src);
+ mp3_cur_adr += 2;
- if (!use_ddrsbm_fpga) {
- crypto_key1 = (crypto_key1 & 0x8000) | ((crypto_key1 << 1) & 0x7FFE) | ((crypto_key1 >> 14) & 1);
-
- if ((((crypto_key1 >> 15) ^ crypto_key1) & 1) != 0) {
- crypto_key2 = (crypto_key2 << 1) | (crypto_key2 >> 15);
- }
- }
-
- crypto_key3++;
- }
-
- mp3_last_decrypt_adr = cur_idx;
- }
-
- int samples;
- int free_format_bytes = 0, frame_size = 0;
- int buf_size = decrypt_buffer_speed;
-
- uint8_t *buf = (uint8_t*)ram_swap.get() + mp3_last_adr;
-
- // Detect first frame in MP3 data
- if (mp3_next_sync == 0 && last_copied_samples == 0) {
- buf_size = 0xe00;
-
- // Everything on the System 573 has a header 0x600 or less from what I've seen, but just ot be sure, check the first 0xe00 bytes
- uint32_t first_frame = mp3d_find_frame(buf, buf_size, &free_format_bytes, &frame_size);
- buf += first_frame;
- mp3_last_adr += first_frame;
- buf_size -= first_frame;
-
- buffer_speed = hdr_frame_bytes(buf, free_format_bytes) * 2;
- }
-
- if (mp3_last_adr >= mp3_end_adr) {
- mp3_next_sync = 0;
- return;
- }
-
- if (mp3_next_sync == 0 && last_copied_samples == 0) {
- // For the first iteration, we need to set up the MP3 state and such
- if (mp3_info.buffer != NULL) {
- free(mp3_info.buffer);
- }
-
- memset(&mp3_info, 0, sizeof(mp3_info));
- memset(&mp3_frame_info, 0, sizeof(mp3_frame_info));
- new_samples = 0;
-
- /* try to make allocation size assumption by first frame */
- mp3dec_init(&mp3_dec);
-
- samples = mp3dec_decode_frame(&mp3_dec, buf, buf_size, mp3_pcm, &mp3_frame_info);
-
- if (!samples) {
- return;
- }
-
- samples *= mp3_frame_info.channels;
-
- mp3_allocated = (buf_size / mp3_frame_info.frame_bytes) * samples * sizeof(mp3d_sample_t) + MINIMP3_MAX_SAMPLES_PER_FRAME * sizeof(mp3d_sample_t);
- mp3_info.buffer = (mp3d_sample_t*)malloc(mp3_allocated);
-
- if (!mp3_info.buffer) {
- return;
- }
-
- mp3_info.samples = samples;
- memcpy(mp3_info.buffer, mp3_pcm, mp3_info.samples * sizeof(mp3d_sample_t));
-
- /* save info */
- mp3_info.channels = mp3_frame_info.channels;
- mp3_info.hz = mp3_frame_info.hz;
- mp3_info.layer = mp3_frame_info.layer;
- mp3_last_adr += mp3_frame_info.frame_bytes;
- } else {
- /* decode rest frames */
- buf_size = decrypt_buffer_speed * 2;
-
- if (buf < (uint8_t*)ram_swap.get() + mp3_end_adr) {
- if (buf + buf_size > (uint8_t*)ram_swap.get() + mp3_end_adr) {
- buf_size = ((uint8_t*)ram_swap.get() + mp3_end_adr) - buf;
- }
- if ((mp3_allocated - mp3_info.samples * sizeof(mp3d_sample_t)) < MINIMP3_MAX_SAMPLES_PER_FRAME * sizeof(mp3d_sample_t)) {
- mp3_allocated *= 2;
- mp3_info.buffer = (mp3d_sample_t*)realloc(mp3_info.buffer, mp3_allocated);
- }
-
- samples = mp3dec_decode_frame(&mp3_dec, buf, buf_size, mp3_info.buffer + mp3_info.samples, &mp3_frame_info);
-
- if (!samples) {
- mp3_last_decrypt_adr = mp3_start_adr; // Force it to redecrypt everything in case the previous decrypt was too fast to find a full frame
- crypto_key1 = orig_crypto_key1;
- crypto_key2 = orig_crypto_key2;
- crypto_key3 = orig_crypto_key3;
-
- if (buf + buf_size >= (uint8_t*)ram_swap.get() + mp3_end_adr) {
- // Some songs have a weird frame at the very end of the song which can be skipped usually.
- mp3_last_adr += buf_size;
- }
- } else if (mp3_info.hz == mp3_frame_info.hz && mp3_info.layer == mp3_frame_info.layer && mp3_info.channels && mp3_info.channels == mp3_frame_info.channels) {
- mp3_last_adr += mp3_frame_info.frame_bytes;
- mp3_info.samples += samples * mp3_frame_info.channels;
- }
- }
- }
-
- new_samples = mp3_info.samples - new_samples;
-
- if (new_samples > 0) {
- size_t buffer_size = 0;
-
- if (mp3_info.channels == 1) {
- buffer_size = mp3_info.samples;
- } else {
- buffer_size = mp3_info.samples / 2;
- }
-
- if (channel_l_pcm == nullptr) {
- last_buffer_size_channel_l = buffer_size * 2;
-
- if (last_buffer_size_channel_l == 0) {
- last_buffer_size_channel_l = 0x1000;
- }
-
- channel_l_pcm = (mp3d_sample_t*)calloc(last_buffer_size_channel_l, sizeof(mp3d_sample_t));
- } else if (buffer_size > last_buffer_size_channel_l) {
- // realloc
- last_buffer_size_channel_l = buffer_size * 2;
-
- if (last_buffer_size_channel_l == 0) {
- last_buffer_size_channel_l = 0x1000;
- }
-
- channel_l_pcm = (mp3d_sample_t*)realloc(channel_l_pcm, last_buffer_size_channel_l * sizeof(mp3d_sample_t));
- }
-
- if (channel_r_pcm == nullptr) {
- last_buffer_size_channel_r = buffer_size * 2;
-
- if (last_buffer_size_channel_r == 0) {
- last_buffer_size_channel_r = 0x1000;
- }
-
- channel_r_pcm = (mp3d_sample_t*)calloc(last_buffer_size_channel_r, sizeof(mp3d_sample_t));
- } else if (buffer_size > last_buffer_size_channel_r) {
- // realloc
- last_buffer_size_channel_r = buffer_size * 2;
-
- if (last_buffer_size_channel_r == 0) {
- last_buffer_size_channel_r = 0x1000;
- }
-
- channel_r_pcm = (mp3d_sample_t*)realloc(channel_r_pcm, last_buffer_size_channel_r * sizeof(mp3d_sample_t));
- }
-
- if (mp3_info.channels == 1) {
- memcpy(((int8_t*)channel_l_pcm) + (last_copied_samples * sizeof(mp3d_sample_t)), ((int8_t*)mp3_info.buffer) + (last_copied_samples * sizeof(mp3d_sample_t)), (buffer_size - last_copied_samples) * sizeof(mp3d_sample_t));
- memcpy(((int8_t*)channel_r_pcm) + (last_copied_samples * sizeof(mp3d_sample_t)), ((int8_t*)mp3_info.buffer) + (last_copied_samples * sizeof(mp3d_sample_t)), (buffer_size - last_copied_samples) * sizeof(mp3d_sample_t));
- } else {
- for (size_t i = last_copied_samples; i < buffer_size; i++) {
- channel_l_pcm[i] = mp3_info.buffer[i * sizeof(mp3d_sample_t)];
- channel_r_pcm[i] = mp3_info.buffer[i * sizeof(mp3d_sample_t) + 1];
- }
- }
-
- last_copied_samples = buffer_size;
-
- if (last_copied_samples > SAMPLES_BUFFER) {
- m_samples->update_raw(0, channel_l_pcm, buffer_size, mp3_info.hz, mp3_start_adr != mp3_dynamic_base);
- m_samples->update_raw(1, channel_r_pcm, buffer_size, mp3_info.hz, mp3_start_adr != mp3_dynamic_base);
-
- mp3_next_sync = buffer_size - (buffer_size / 4); // Grab more data sometime before the current buffer ends. This is arbitrary and kinda hacky, but it worked best between various games in my testing.
- }
- }
-
- if (mp3_last_adr >= mp3_end_adr) {
- decrypt_finished = true;
- }
+ return result;
}
DEFINE_DEVICE_TYPE(KONAMI_573_DIGITAL_FPGA, k573fpga_device, "k573fpga", "Konami 573 Digital I/O FPGA")
diff --git a/src/mame/machine/k573fpga.h b/src/mame/machine/k573fpga.h
index 71b949b4419..807c32b8d5d 100644
--- a/src/mame/machine/k573fpga.h
+++ b/src/mame/machine/k573fpga.h
@@ -8,82 +8,52 @@
#include "sound/mas3507d.h"
#include "machine/ds2401.h"
-#include "sound/samples.h"
-
-#define MINIMP3_ONLY_MP3
-#define MINIMP3_NO_STDIO
-#include "minimp3/minimp3.h"
-#include "minimp3/minimp3_ex.h"
-
DECLARE_DEVICE_TYPE(KONAMI_573_DIGITAL_FPGA, k573fpga_device)
class k573fpga_device : public device_t
{
public:
- k573fpga_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
-
- required_device<mas3507d_device> mas3507d;
- required_device<samples_device> m_samples;
+ k573fpga_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0);
void set_ddrsbm_fpga(bool flag) { use_ddrsbm_fpga = flag; }
- void set_ram(uint16_t *v) { ram = v; }
+ void set_ram(u16 *v) { ram = v; }
+ u16 get_decrypted();
- void set_crypto_key1(uint16_t v) { orig_crypto_key1 = crypto_key1 = v; }
- void set_crypto_key2(uint16_t v) { orig_crypto_key2 = crypto_key2 = v; }
- void set_crypto_key3(uint8_t v) { orig_crypto_key3 = crypto_key3 = v; }
+ void set_crypto_key1(u16 v) { crypto_key1 = v; }
+ void set_crypto_key2(u16 v) { crypto_key2 = v; }
+ void set_crypto_key3(u8 v) { crypto_key3 = v; }
+ u16 get_crypto_key1() const { return crypto_key1; }
+ u16 get_crypto_key2() const { return crypto_key2; }
+ u8 get_crypto_key3() const { return crypto_key3; }
- uint32_t get_mp3_start_adr() { return mp3_start_adr; }
- void set_mp3_start_adr(uint32_t v) { mp3_start_adr = v; }
+ uint32_t get_mp3_cur_adr() { return mp3_cur_adr; }
+ void set_mp3_cur_adr(u32 v) { mp3_cur_adr = v; }
uint32_t get_mp3_end_adr() { return mp3_end_adr; }
- void set_mp3_end_adr(uint32_t v) { mp3_end_adr = v; }
-
- uint32_t get_mp3_playback();
-
- uint16_t i2c_read();
- void i2c_write(uint16_t data);
+ void set_mp3_end_adr(u32 v) { mp3_end_adr = v; }
- uint16_t get_mpeg_ctrl();
- void set_mpeg_ctrl(uint16_t data);
+ u16 i2c_read();
+ void i2c_write(u16 data);
- void set_mp3_dynamic_base(uint32_t base) { mp3_dynamic_base = base; }
+ u16 get_mpeg_ctrl();
+ void set_mpeg_ctrl(u16 data);
protected:
virtual void device_start() override;
virtual void device_reset() override;
- virtual void device_add_mconfig(machine_config &config) override;
private:
- uint16_t *ram;
- std::unique_ptr<uint16_t[]> ram_swap;
+ u16 *ram;
- uint16_t crypto_key1, crypto_key2, orig_crypto_key1, orig_crypto_key2;
- uint8_t crypto_key3, orig_crypto_key3;
+ u16 crypto_key1, crypto_key2;
+ u8 crypto_key3;
- uint32_t mp3_start_adr, mp3_end_adr, mpeg_ctrl_flag;
+ u32 mp3_cur_adr, mp3_end_adr, mpeg_ctrl_flag;
bool use_ddrsbm_fpga;
- uint32_t mp3_last_adr, mp3_next_sync, mp3_last_decrypt_adr;
- int16_t *channel_l_pcm, *channel_r_pcm;
- size_t last_buffer_size_channel_l, last_buffer_size_channel_r, last_copied_samples;
- uint32_t last_position_update, position_diff;
- bool decrypt_finished;
-
- mp3d_sample_t mp3_pcm[MINIMP3_MAX_SAMPLES_PER_FRAME];
- mp3dec_file_info_t mp3_info;
- mp3dec_t mp3_dec;
- mp3dec_frame_info_t mp3_frame_info;
- size_t mp3_allocated;
-
- uint32_t buffer_speed, mp3_dynamic_base;
-
- int32_t find_enc_key();
-
- uint16_t fpga_decrypt_byte_real(uint16_t data);
- uint16_t fpga_decrypt_byte_ddrsbm(uint16_t data, uint32_t crypto_idx);
-
- SAMPLES_UPDATE_CB_MEMBER(k573fpga_stream_update);
+ u16 decrypt_default(u16 data);
+ u16 decrypt_ddrsbm(u16 data);
};
#endif // MAME_MACHINE_K573FPGA_H