Super A'Can: DMA sample support and corrected sample length (#8160) [Ryan Holtz, Ian Karlsson]
This commit is contained in:
parent
2038fe9e5f
commit
4e5b9cd7ed
@ -22,9 +22,11 @@ acan_sound_device::acan_sound_device(const machine_config &mconfig, const char *
|
||||
, device_sound_interface(mconfig, *this)
|
||||
, m_stream(nullptr)
|
||||
, m_timer(nullptr)
|
||||
, m_irq_handler(*this)
|
||||
, m_timer_irq_handler(*this)
|
||||
, m_dma_irq_handler(*this)
|
||||
, m_ram_read(*this)
|
||||
, m_active_channels(0)
|
||||
, m_dma_channels(0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -34,11 +36,13 @@ void acan_sound_device::device_start()
|
||||
m_stream = stream_alloc(0, 2, clock() / 16 / 5);
|
||||
m_timer = timer_alloc(0);
|
||||
|
||||
m_irq_handler.resolve();
|
||||
m_timer_irq_handler.resolve_safe();
|
||||
m_dma_irq_handler.resolve_safe();
|
||||
m_ram_read.resolve_safe(0);
|
||||
|
||||
// register for savestates
|
||||
save_item(NAME(m_active_channels));
|
||||
save_item(NAME(m_dma_channels));
|
||||
save_item(STRUCT_MEMBER(m_channels, pitch));
|
||||
save_item(STRUCT_MEMBER(m_channels, length));
|
||||
save_item(STRUCT_MEMBER(m_channels, start_addr));
|
||||
@ -46,6 +50,7 @@ void acan_sound_device::device_start()
|
||||
save_item(STRUCT_MEMBER(m_channels, end_addr));
|
||||
save_item(STRUCT_MEMBER(m_channels, addr_increment));
|
||||
save_item(STRUCT_MEMBER(m_channels, frac));
|
||||
save_item(STRUCT_MEMBER(m_channels, register9));
|
||||
save_item(STRUCT_MEMBER(m_channels, envelope));
|
||||
save_item(STRUCT_MEMBER(m_channels, volume));
|
||||
save_item(STRUCT_MEMBER(m_channels, volume_l));
|
||||
@ -57,19 +62,19 @@ void acan_sound_device::device_start()
|
||||
void acan_sound_device::device_reset()
|
||||
{
|
||||
m_active_channels = 0;
|
||||
m_dma_channels = 0;
|
||||
std::fill(std::begin(m_regs), std::end(m_regs), 0);
|
||||
|
||||
m_timer->reset();
|
||||
if (!m_irq_handler.isnull())
|
||||
m_irq_handler(0);
|
||||
m_timer_irq_handler(0);
|
||||
m_dma_irq_handler(0);
|
||||
}
|
||||
|
||||
void acan_sound_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
|
||||
{
|
||||
if (m_regs[0x14] & 0x40)
|
||||
{
|
||||
if (!m_irq_handler.isnull())
|
||||
m_irq_handler(1);
|
||||
m_timer_irq_handler(1);
|
||||
|
||||
// Update frequency
|
||||
uint16_t period = (m_regs[0x12] << 8) + m_regs[0x11];
|
||||
@ -83,7 +88,7 @@ void acan_sound_device::sound_stream_update(sound_stream &stream, std::vector<re
|
||||
|
||||
std::fill_n(&mix[0], outputs[0].samples() * 2, 0);
|
||||
|
||||
for (int i = 0; i < 15 && m_active_channels != 0; i++)
|
||||
for (int i = 0; i < 16 && m_active_channels != 0; i++)
|
||||
{
|
||||
if (BIT(m_active_channels, i))
|
||||
{
|
||||
@ -104,7 +109,12 @@ void acan_sound_device::sound_stream_update(sound_stream &stream, std::vector<re
|
||||
|
||||
if (channel.curr_addr >= channel.end_addr)
|
||||
{
|
||||
if (channel.one_shot)
|
||||
if (channel.register9)
|
||||
{
|
||||
m_dma_irq_handler(1);
|
||||
keyon_voice(i);
|
||||
}
|
||||
else if (channel.one_shot)
|
||||
{
|
||||
m_active_channels &= ~(1 << i);
|
||||
}
|
||||
@ -129,13 +139,28 @@ uint8_t acan_sound_device::read(offs_t offset)
|
||||
{
|
||||
if (offset == 0x14)
|
||||
{
|
||||
// acknowledge timer (?)
|
||||
if (!m_irq_handler.isnull())
|
||||
m_irq_handler(0);
|
||||
// acknowledge timer IRQ?
|
||||
m_timer_irq_handler(0);
|
||||
}
|
||||
else if (offset == 0x16)
|
||||
{
|
||||
// acknowledge DMA IRQ?
|
||||
m_dma_irq_handler(0);
|
||||
}
|
||||
return m_regs[offset];
|
||||
}
|
||||
|
||||
void acan_sound_device::keyon_voice(uint8_t voice)
|
||||
{
|
||||
acan_channel &channel = m_channels[voice];
|
||||
channel.curr_addr = channel.start_addr << 6;
|
||||
channel.end_addr = channel.curr_addr + channel.length;
|
||||
|
||||
m_active_channels |= (1 << voice);
|
||||
|
||||
//printf("Keyon voice %d\n", voice);
|
||||
}
|
||||
|
||||
void acan_sound_device::write(offs_t offset, uint8_t data)
|
||||
{
|
||||
const uint8_t upper = (offset >> 4) & 0x0f;
|
||||
@ -145,107 +170,128 @@ void acan_sound_device::write(offs_t offset, uint8_t data)
|
||||
|
||||
switch (upper)
|
||||
{
|
||||
case 0x1:
|
||||
if (lower == 0x7) // Keyon/keyoff
|
||||
case 0x1:
|
||||
switch (lower)
|
||||
{
|
||||
case 0x1: // Timer frequency (low byte)
|
||||
LOG("%s: Sound timer frequency (low byte) = %02x\n", machine().describe_context(), data);
|
||||
break;
|
||||
|
||||
case 0x2: // Timer frequency (high byte)
|
||||
LOG("%s: Sound timer frequency (high byte) = %02x\n", machine().describe_context(), data);
|
||||
break;
|
||||
|
||||
case 0x4: // Timer control
|
||||
// The meaning of the data that is actually written is unknown
|
||||
LOG("%s: Sound timer control = %02x\n", machine().describe_context(), data);
|
||||
if (BIT(data, 7))
|
||||
{
|
||||
const uint16_t mask = 1 << (data & 0xf);
|
||||
if (data & 0xf0)
|
||||
m_active_channels |= mask;
|
||||
else
|
||||
m_active_channels &= ~mask;
|
||||
}
|
||||
else if (lower == 0x4) // Timer control
|
||||
{
|
||||
if (data & 0x80)
|
||||
{
|
||||
// Update frequency
|
||||
uint16_t period = (m_regs[0x12] << 8) + m_regs[0x11];
|
||||
m_timer->adjust(clocks_to_attotime(10 * (0x10000 - period)), 0);
|
||||
}
|
||||
// the meaning of the data that is actually written is unknown
|
||||
LOG("Sound timer control: %02x = %02x\n", offset, data);
|
||||
}
|
||||
else if (lower != 0x1 && lower != 0x2)
|
||||
{
|
||||
LOG("Unknown audio register: %02x = %02x\n", offset, data);
|
||||
// Update frequency
|
||||
uint16_t period = (m_regs[0x12] << 8) + m_regs[0x11];
|
||||
m_timer->adjust(clocks_to_attotime(10 * (0x10000 - period)), 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x2: // Pitch (low byte)
|
||||
if (lower < 0xf)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.pitch &= 0xff00;
|
||||
channel.pitch |= data;
|
||||
channel.addr_increment = (uint32_t)channel.pitch << 6;
|
||||
channel.frac = 0;
|
||||
}
|
||||
case 0x6: // DMA-driven channel flags?
|
||||
// The meaning of the data that is actually written is unknown
|
||||
m_dma_channels = data << 8;
|
||||
LOG("%s: DMA-driven channel flag(?) = %02x\n", machine().describe_context(), data);
|
||||
break;
|
||||
|
||||
case 0x3: // Pitch (high byte)
|
||||
if (lower < 0xf)
|
||||
case 0x7: // Keyon/keyoff
|
||||
{
|
||||
LOG("%s: Sound key control, voice %02x key%s\n", machine().describe_context(), data & 0xf, (data & 0xf0) ? "on" : "off");
|
||||
const uint16_t mask = 1 << (data & 0xf);
|
||||
if (data & 0xf0)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.pitch &= 0x00ff;
|
||||
channel.pitch |= data << 8;
|
||||
channel.addr_increment = (uint32_t)channel.pitch << 6;
|
||||
channel.frac = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x5: // Waveform length
|
||||
if (lower < 0xf)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
//channel.length = (data & ~0x01) << 6;
|
||||
channel.length = (data & 0x0e) << 6; // Temporary hack to make jttlaugh audible. Not the proper fix!
|
||||
channel.end_addr = channel.curr_addr + channel.length;
|
||||
channel.one_shot = BIT(data, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x6: // Waveform address (divided by 0x40, high byte)
|
||||
if (lower < 0xf)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.start_addr &= 0x00ff;
|
||||
channel.start_addr |= data << 8;
|
||||
channel.curr_addr = channel.start_addr << 6;
|
||||
channel.end_addr = channel.curr_addr + channel.length;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x7: // Waveform address (divided by 0x40, low byte)
|
||||
if (lower < 0xf)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.start_addr &= 0xff00;
|
||||
channel.start_addr |= data;
|
||||
channel.curr_addr = channel.start_addr << 6;
|
||||
channel.end_addr = channel.curr_addr + channel.length;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xa: // Envelope Parameters? (not yet known)
|
||||
case 0xb:
|
||||
case 0xc:
|
||||
case 0xd:
|
||||
if (lower < 0xf)
|
||||
m_channels[lower].envelope[upper - 0xa] = data;
|
||||
break;
|
||||
|
||||
case 0xe: // Volume
|
||||
if (lower < 0xf)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.volume = data;
|
||||
channel.volume_l = (data & 0xf0) | (data >> 4);
|
||||
channel.volume_r = (data & 0x0f) | (data << 4);
|
||||
keyon_voice(data & 0xf);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_active_channels &= ~mask;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG("Unknown audio register: %02x = %02x\n", offset, data);
|
||||
LOG("Unknown sound register: %02x = %02x\n", offset, data);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x2: // Pitch (low byte)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.pitch &= 0xff00;
|
||||
channel.pitch |= data;
|
||||
channel.addr_increment = (uint32_t)channel.pitch << 6;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x3: // Pitch (high byte)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.pitch &= 0x00ff;
|
||||
channel.pitch |= data << 8;
|
||||
channel.addr_increment = (uint32_t)channel.pitch << 6;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x5: // Waveform length
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.length = 0x40 << ((data & 0x0e) >> 1);
|
||||
channel.one_shot = BIT(data, 0);
|
||||
LOG("%s: Waveform length and attributes (voice %02x): %02x\n", machine().describe_context(), lower, data);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x6: // Waveform address (divided by 0x40, high byte)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.start_addr &= 0x00ff;
|
||||
channel.start_addr |= data << 8;
|
||||
LOG("%s: Waveform address (high) (voice %02x): %02x, will be %04x\n", machine().describe_context(), lower, data, channel.start_addr << 6);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x7: // Waveform address (divided by 0x40, low byte)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.start_addr &= 0xff00;
|
||||
channel.start_addr |= data;
|
||||
LOG("%s: Waveform address (low) (voice %02x): %02x, will be %04x\n", machine().describe_context(), lower, data, channel.start_addr << 6);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x9: // Unknown (set to 0xFF for DMA-driven channels)
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.register9 = data;
|
||||
LOG("%s: Unknown voice register 9 (voice %02x): %02x\n", machine().describe_context(), lower, data);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xa: // Envelope Parameters? (not yet known)
|
||||
case 0xb:
|
||||
case 0xc:
|
||||
case 0xd:
|
||||
m_channels[lower].envelope[upper - 0xa] = data;
|
||||
LOG("%s: Envelope parameter %d (voice %02x) = %02x\n", machine().describe_context(), upper - 0xa, lower, data);
|
||||
break;
|
||||
|
||||
case 0xe: // Volume
|
||||
{
|
||||
acan_channel &channel = m_channels[lower];
|
||||
channel.volume = data;
|
||||
channel.volume_l = (data & 0xf0) | (data >> 4);
|
||||
channel.volume_r = (data & 0x0f) | (data << 4);
|
||||
LOG("%s: Volume register (voice %02x) = = %02x\n", machine().describe_context(), lower, data);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG("Unknown sound register: %02x = %02x\n", offset, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ public:
|
||||
acan_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
|
||||
|
||||
auto ram_read() { return m_ram_read.bind(); }
|
||||
auto irq_handler() { return m_irq_handler.bind(); }
|
||||
auto timer_irq_handler() { return m_timer_irq_handler.bind(); }
|
||||
auto dma_irq_handler() { return m_dma_irq_handler.bind(); }
|
||||
|
||||
uint8_t read(offs_t offset);
|
||||
void write(offs_t offset, uint8_t data);
|
||||
@ -41,6 +42,7 @@ private:
|
||||
uint16_t end_addr;
|
||||
uint32_t addr_increment;
|
||||
uint32_t frac;
|
||||
uint8_t register9;
|
||||
uint8_t envelope[4];
|
||||
uint8_t volume;
|
||||
uint8_t volume_l;
|
||||
@ -48,12 +50,16 @@ private:
|
||||
bool one_shot;
|
||||
};
|
||||
|
||||
void keyon_voice(uint8_t voice);
|
||||
|
||||
sound_stream *m_stream;
|
||||
emu_timer *m_timer;
|
||||
devcb_write_line m_irq_handler;
|
||||
devcb_write_line m_timer_irq_handler;
|
||||
devcb_write_line m_dma_irq_handler;
|
||||
devcb_read8 m_ram_read;
|
||||
uint16_t m_active_channels;
|
||||
acan_channel m_channels[15];
|
||||
uint16_t m_dma_channels;
|
||||
acan_channel m_channels[16];
|
||||
uint8_t m_regs[256];
|
||||
};
|
||||
|
||||
|
@ -26,14 +26,14 @@ STATUS:
|
||||
|
||||
The driver is begging for a re-write or at least a split into video/supracan.c. It will happen eventually.
|
||||
|
||||
Sound chip is completely unknown.
|
||||
Sound chip is completely custom, and partially implemented.
|
||||
|
||||
There are 6 interrupt sources on the 6502 side, all of which use the IRQ line.
|
||||
The register at 0x411 is bitmapped to indicate what source(s) are active.
|
||||
In priority order from most to least important, they are:
|
||||
|
||||
411 value How acked Notes
|
||||
0x40 read reg 0x16 of sound chip likely timer. snd regs 0x16/0x17 are time constant, write 0 to reg 0x9f to start
|
||||
0x40 read reg 0x16 of sound chip used for DMA-driven sample playback. Register 0x16 may contain which DMA-driven samples are active.
|
||||
0x04 read at 0x405 latch 1? 0xcd is magic value
|
||||
0x08 read at 0x404 latch 2? 0xcd is magic value
|
||||
0x10 read at 0x409 unknown, dispatched but not used in startup 6502 code
|
||||
@ -261,6 +261,7 @@ private:
|
||||
TILE_GET_INFO_MEMBER(get_roz_tile_info);
|
||||
void palette_init(palette_device &palette) const;
|
||||
void sound_timer_irq(int state);
|
||||
void sound_dma_irq(int state);
|
||||
uint32_t screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
|
||||
TIMER_CALLBACK_MEMBER(hbl_callback);
|
||||
TIMER_CALLBACK_MEMBER(line_on_callback);
|
||||
@ -1197,6 +1198,11 @@ void supracan_state::sound_timer_irq(int state)
|
||||
set_sound_irq(7, state);
|
||||
}
|
||||
|
||||
void supracan_state::sound_dma_irq(int state)
|
||||
{
|
||||
set_sound_irq(6, state);
|
||||
}
|
||||
|
||||
void supracan_state::dma_w(int offset, uint16_t data, uint16_t mem_mask, int ch)
|
||||
{
|
||||
address_space &mem = m_maincpu->space(AS_PROGRAM);
|
||||
@ -1566,8 +1572,19 @@ uint16_t supracan_state::sound_r(offs_t offset, uint16_t mem_mask)
|
||||
|
||||
switch (offset)
|
||||
{
|
||||
case 0x04/2:
|
||||
data = (m_soundram[0x40c] << 8) | m_soundram[0x40d];
|
||||
LOGMASKED(LOG_SOUND, "%s: sound_r: DMA Request address from 6502, %08x: %04x & %04x\n", machine().describe_context(), 0xe90000 + (offset << 1), data, mem_mask);
|
||||
break;
|
||||
|
||||
case 0x0c/2:
|
||||
data = m_soundram[0x40a];
|
||||
LOGMASKED(LOG_SOUND, "%s: sound_r: DMA Request flag from 6502, %08x: %04x & %04x\n", machine().describe_context(), 0xe90000 + (offset << 1), data, mem_mask);
|
||||
machine().debug_break();
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGMASKED(LOG_SOUND | LOG_UNKNOWNS, "%s: sound_r: Unknown register: (%08x) & %04x\n", machine().describe_context(), 0xe90000 + (offset << 1), mem_mask);
|
||||
LOGMASKED(LOG_SOUND | LOG_UNKNOWNS, "%s: sound_r: Unknown register: %08x & %04x\n", machine().describe_context(), 0xe90000 + (offset << 1), mem_mask);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2113,7 +2130,8 @@ void supracan_state::supracan(machine_config &config)
|
||||
|
||||
ACANSND(config, m_sound, XTAL(3'579'545));
|
||||
m_sound->ram_read().set(FUNC(supracan_state::sound_ram_read));
|
||||
m_sound->irq_handler().set(FUNC(supracan_state::sound_timer_irq));
|
||||
m_sound->timer_irq_handler().set(FUNC(supracan_state::sound_timer_irq));
|
||||
m_sound->dma_irq_handler().set(FUNC(supracan_state::sound_dma_irq));
|
||||
m_sound->add_route(0, "lspeaker", 1.0);
|
||||
m_sound->add_route(1, "rspeaker", 1.0);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user