Add YMZ280B support

This commit is contained in:
Natt Akuma 2022-05-18 13:55:33 +07:00
parent 2faf1c1d23
commit 0ae0c6f703
14 changed files with 1523 additions and 45 deletions

View File

@ -318,6 +318,8 @@ src/engine/platform/sound/vrcvi/vrcvi.cpp
src/engine/platform/sound/scc/scc.cpp
src/engine/platform/sound/ymz280b.cpp
src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp
src/engine/platform/ym2610Interface.cpp
@ -382,6 +384,7 @@ src/engine/platform/pet.cpp
src/engine/platform/vic20.cpp
src/engine/platform/vrc6.cpp
src/engine/platform/scc.cpp
src/engine/platform/ymz280b.cpp
src/engine/platform/dummy.cpp
)

View File

@ -58,6 +58,7 @@
#include "platform/fds.h"
#include "platform/mmc5.h"
#include "platform/scc.h"
#include "platform/ymz280b.h"
#include "platform/dummy.h"
#include "../ta-log.h"
#include "platform/zxbeeper.h"
@ -347,6 +348,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
dispatch=new DivPlatformSCC;
((DivPlatformSCC*)dispatch)->setChipModel(true);
break;
case DIV_SYSTEM_YMZ280B:
dispatch=new DivPlatformYMZ280B;
((DivPlatformYMZ280B*)dispatch)->setChipModel(280);
break;
case DIV_SYSTEM_SOUND_UNIT:
dispatch=new DivPlatformSoundUnit;
break;

View File

@ -0,0 +1,786 @@
// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/*
Yamaha YMZ280B driver
by Aaron Giles
YMZ280B 8-Channel PCMD8 PCM/ADPCM Decoder
Features as listed in LSI-4MZ280B3 data sheet:
Voice data stored in external memory can be played back simultaneously for up to eight voices
Voice data format can be selected from 4-bit ADPCM, 8-bit PCM and 16-bit PCM
Control of voice data external memory
Up to 16M bytes of ROM or SRAM (x 8 bits, access time 150ms max) can be connected
Continuous access is possible
Loop playback between selective addresses is possible
Voice data playback frequency control
4-bit ADPCM ................ 0.172 to 44.1kHz in 256 steps
8-bit PCM, 16-bit PCM ...... 0.172 to 88.2kHz in 512 steps
256 steps total level and 16 steps panpot can be set
Voice signal is output in stereo 16-bit 2's complement MSB-first format
TODO:
- Is memory handling 100% correct? At the moment, Konami firebeat.c is the only
hardware currently emulated that uses external handlers.
It also happens to be the only one using 16-bit PCM.
Some other drivers (eg. bishi.cpp, bfm_sc4/5.cpp) also use ROM readback.
*/
#include "ymz280b.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define MAX_SAMPLE_CHUNK 10000
static constexpr unsigned FRAC_BITS = 8;
static constexpr s32 FRAC_ONE = 1 << FRAC_BITS;
/* step size index shift table */
static constexpr int index_scale[8] = { 0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266 };
/* lookup table for the precomputed difference */
static int diff_lookup[16];
void ymz280b_device::update_step(struct YMZ280BVoice *voice)
{
int frequency;
/* compute the frequency */
if (voice->mode == 1)
frequency = voice->fnum & 0x0ff;
else
frequency = voice->fnum & 0x1ff;
voice->output_step = frequency + 1; // ((fnum + 1) * (input clock / 384)) / 256
}
void ymz280b_device::update_volumes(struct YMZ280BVoice *voice)
{
if (voice->pan == 8)
{
voice->output_left = voice->level;
voice->output_right = voice->level;
}
else if (voice->pan < 8)
{
voice->output_left = voice->level;
/* pan 1 is hard-left, what's pan 0? for now assume same as pan 1 */
voice->output_right = (voice->pan == 0) ? 0 : voice->level * (voice->pan - 1) / 7;
}
else
{
voice->output_left = voice->level * (15 - voice->pan) / 7;
voice->output_right = voice->level;
}
}
/**********************************************************************************************
compute_tables -- compute the difference tables
***********************************************************************************************/
static void compute_tables()
{
/* loop over all nibbles and compute the difference */
for (int nib = 0; nib < 16; nib++)
{
int value = (nib & 0x07) * 2 + 1;
diff_lookup[nib] = (nib & 0x08) ? -value : value;
}
}
/**********************************************************************************************
generate_adpcm -- general ADPCM decoding routine
***********************************************************************************************/
int ymz280b_device::generate_adpcm(struct YMZ280BVoice *voice, s16 *buffer, int samples)
{
int position = voice->position;
int signal = voice->signal;
int step = voice->step;
int val;
/* two cases: first cases is non-looping */
if (!voice->looping)
{
/* loop while we still have samples to generate */
while (samples)
{
/* compute the new amplitude and update the current step */
val = m_ext_mem[position / 2] >> ((~position & 1) << 2);
signal += (step * diff_lookup[val & 15]) / 8;
/* clamp to the maximum */
if (signal > 32767)
signal = 32767;
else if (signal < -32768)
signal = -32768;
/* adjust the step size and clamp */
step = (step * index_scale[val & 7]) >> 8;
if (step > 0x6000)
step = 0x6000;
else if (step < 0x7f)
step = 0x7f;
/* output to the buffer, scaling by the volume */
*buffer++ = signal;
samples--;
/* next! */
position++;
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* second case: looping */
else
{
/* loop while we still have samples to generate */
while (samples)
{
/* compute the new amplitude and update the current step */
val = m_ext_mem[position / 2] >> ((~position & 1) << 2);
signal += (step * diff_lookup[val & 15]) / 8;
/* clamp to the maximum */
if (signal > 32767)
signal = 32767;
else if (signal < -32768)
signal = -32768;
/* adjust the step size and clamp */
step = (step * index_scale[val & 7]) >> 8;
if (step > 0x6000)
step = 0x6000;
else if (step < 0x7f)
step = 0x7f;
/* output to the buffer, scaling by the volume */
*buffer++ = signal;
samples--;
/* next! */
position++;
if (position == voice->loop_start && voice->loop_count == 0)
{
voice->loop_signal = signal;
voice->loop_step = step;
}
if (position >= voice->loop_end)
{
if (voice->keyon)
{
position = voice->loop_start;
signal = voice->loop_signal;
step = voice->loop_step;
voice->loop_count++;
}
}
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* update the parameters */
voice->position = position;
voice->signal = signal;
voice->step = step;
return samples;
}
/**********************************************************************************************
generate_pcm8 -- general 8-bit PCM decoding routine
***********************************************************************************************/
int ymz280b_device::generate_pcm8(struct YMZ280BVoice *voice, s16 *buffer, int samples)
{
int position = voice->position;
int val;
/* two cases: first cases is non-looping */
if (!voice->looping)
{
/* loop while we still have samples to generate */
while (samples)
{
/* fetch the current value */
val = m_ext_mem[position / 2];
/* output to the buffer, scaling by the volume */
*buffer++ = (s8)val * 256;
samples--;
/* next! */
position += 2;
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* second case: looping */
else
{
/* loop while we still have samples to generate */
while (samples)
{
/* fetch the current value */
val = m_ext_mem[position / 2];
/* output to the buffer, scaling by the volume */
*buffer++ = (s8)val * 256;
samples--;
/* next! */
position += 2;
if (position >= voice->loop_end)
{
if (voice->keyon)
position = voice->loop_start;
}
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* update the parameters */
voice->position = position;
return samples;
}
/**********************************************************************************************
generate_pcm16 -- general 16-bit PCM decoding routine
***********************************************************************************************/
int ymz280b_device::generate_pcm16(struct YMZ280BVoice *voice, s16 *buffer, int samples)
{
int position = voice->position;
int val;
/* two cases: first cases is non-looping */
if (!voice->looping)
{
/* loop while we still have samples to generate */
while (samples)
{
/* fetch the current value */
val = (s16)((m_ext_mem[position / 2 + 1] << 8) + m_ext_mem[position / 2 + 0]);
/* output to the buffer, scaling by the volume */
*buffer++ = val;
samples--;
/* next! */
position += 4;
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* second case: looping */
else
{
/* loop while we still have samples to generate */
while (samples)
{
/* fetch the current value */
val = (s16)((m_ext_mem[position / 2 + 1] << 8) + m_ext_mem[position / 2 + 0]);
/* output to the buffer, scaling by the volume */
*buffer++ = val;
samples--;
/* next! */
position += 4;
if (position >= voice->loop_end)
{
if (voice->keyon)
position = voice->loop_start;
}
if (position >= voice->stop)
{
voice->ended = true;
break;
}
}
}
/* update the parameters */
voice->position = position;
return samples;
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void ymz280b_device::sound_stream_update(s16 **outputs, int samples)
{
int v;
/* loop over voices */
for (v = 0; v < 8; v++)
{
struct YMZ280BVoice *voice = &m_voice[v];
s16 prev = voice->last_sample;
s16 curr = voice->curr_sample;
s16 *curr_data = m_scratch.get();
s16 *ldest = outputs[v*2];
s16 *rdest = outputs[v*2+1];
s32 sampindex = 0;
u32 new_samples, samples_left;
u32 final_pos;
int remaining = samples;
int lvol = voice->output_left;
int rvol = voice->output_right;
/* quick out if we're not playing and we're at 0 */
if (!voice->playing && curr == 0 && prev == 0)
{
memset(ldest, 0, samples * sizeof(s16));
memset(rdest, 0, samples * sizeof(s16));
/* make sure next sound plays immediately */
voice->output_pos = FRAC_ONE;
continue;
}
/* finish off the current sample */
/* interpolate */
while (remaining > 0 && voice->output_pos < FRAC_ONE)
{
s32 interp_sample = ((s32(prev) * (FRAC_ONE - voice->output_pos)) + (s32(curr) * voice->output_pos)) >> FRAC_BITS;
ldest[sampindex] = (s16)(interp_sample * lvol / 256);
rdest[sampindex] = (s16)(interp_sample * rvol / 256);
sampindex++;
voice->output_pos += voice->output_step;
remaining--;
}
/* if we're over, continue; otherwise, we're done */
if (voice->output_pos >= FRAC_ONE)
voice->output_pos -= FRAC_ONE;
else
continue;
/* compute how many new samples we need */
final_pos = voice->output_pos + remaining * voice->output_step;
new_samples = (final_pos + FRAC_ONE) >> FRAC_BITS;
if (new_samples > MAX_SAMPLE_CHUNK)
new_samples = MAX_SAMPLE_CHUNK;
samples_left = new_samples;
/* generate them into our buffer */
switch (voice->playing << 7 | voice->mode)
{
case 0x81: samples_left = generate_adpcm(voice, m_scratch.get(), new_samples); break;
case 0x82: samples_left = generate_pcm8(voice, m_scratch.get(), new_samples); break;
case 0x83: samples_left = generate_pcm16(voice, m_scratch.get(), new_samples); break;
default: samples_left = 0; memset(m_scratch.get(), 0, new_samples * sizeof(m_scratch[0])); break;
}
if (samples_left || voice->ended)
{
voice->ended = false;
/* if there are leftovers, ramp back to 0 */
int base = new_samples - samples_left;
int i, t = (base == 0) ? curr : m_scratch[base - 1];
for (i = 0; i < samples_left; i++)
{
if (t < 0) t = -((-t * 15) >> 4);
else if (t > 0) t = (t * 15) >> 4;
m_scratch[base + i] = t;
}
/* if we hit the end and IRQs are enabled, signal it */
if (base != 0)
{
voice->playing = 0;
/* set update_irq_state_timer. IRQ is signaled on next CPU execution. */
voice->irq_schedule = 1;
}
}
/* advance forward one sample */
prev = curr;
curr = *curr_data++;
/* then sample-rate convert with linear interpolation */
while (remaining > 0)
{
/* interpolate */
while (remaining > 0 && voice->output_pos < FRAC_ONE)
{
int interp_sample = ((s32(prev) * (FRAC_ONE - voice->output_pos)) + (s32(curr) * voice->output_pos)) >> FRAC_BITS;
ldest[sampindex] = (s16)(interp_sample * lvol / 256);
rdest[sampindex] = (s16)(interp_sample * rvol / 256);
sampindex++;
voice->output_pos += voice->output_step;
remaining--;
}
/* if we're over, grab the next samples */
if (voice->output_pos >= FRAC_ONE)
{
voice->output_pos -= FRAC_ONE;
prev = curr;
curr = *curr_data++;
}
}
/* remember the last samples */
voice->last_sample = prev;
voice->curr_sample = curr;
}
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void ymz280b_device::device_start(u8 *ext_mem)
{
m_ext_mem = ext_mem;
/* compute ADPCM tables */
compute_tables();
/* allocate memory */
assert(MAX_SAMPLE_CHUNK < 0x10000);
m_scratch = std::make_unique<s16[]>(MAX_SAMPLE_CHUNK);
for (auto & elem : m_voice)
{
update_step(&elem);
}
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void ymz280b_device::device_reset()
{
/* initial clear registers */
for (int i = 0xff; i >= 0; i--)
{
m_current_register = i;
write_to_register(0);
}
m_current_register = 0;
m_status_register = 0;
m_ext_mem_address = 0;
/* clear other voice parameters */
for (auto &elem : m_voice)
{
struct YMZ280BVoice *voice = &elem;
voice->curr_sample = 0;
voice->last_sample = 0;
voice->output_pos = FRAC_ONE;
voice->playing = 0;
}
}
/**********************************************************************************************
write_to_register -- handle a write to the current register
***********************************************************************************************/
void ymz280b_device::write_to_register(int data)
{
struct YMZ280BVoice *voice;
int i;
/* lower registers follow a pattern */
if (m_current_register < 0x80)
{
voice = &m_voice[(m_current_register >> 2) & 7];
switch (m_current_register & 0xe3)
{
case 0x00: /* pitch low 8 bits */
voice->fnum = (voice->fnum & 0x100) | (data & 0xff);
update_step(voice);
break;
case 0x01: /* pitch upper 1 bit, loop, key on, mode */
voice->fnum = (voice->fnum & 0xff) | ((data & 0x01) << 8);
voice->looping = (data & 0x10) >> 4;
if ((data & 0x60) == 0) data &= 0x7f; /* ignore mode setting and set to same state as KON=0 */
else voice->mode = (data & 0x60) >> 5;
if (!voice->keyon && (data & 0x80) && m_keyon_enable)
{
voice->playing = 1;
voice->position = voice->start;
voice->signal = voice->loop_signal = 0;
voice->step = voice->loop_step = 0x7f;
voice->loop_count = 0;
/* if update_irq_state_timer is set, cancel it. */
voice->irq_schedule = 0;
}
else if (voice->keyon && !(data & 0x80))
{
voice->playing = 0;
/* if update_irq_state_timer is set, cancel it. */
voice->irq_schedule = 0;
}
voice->keyon = (data & 0x80) >> 7;
update_step(voice);
break;
case 0x02: /* total level */
voice->level = data;
update_volumes(voice);
break;
case 0x03: /* pan */
voice->pan = data & 0x0f;
update_volumes(voice);
break;
case 0x20: /* start address high */
voice->start = (voice->start & (0x00ffff << 1)) | (data << 17);
break;
case 0x21: /* loop start address high */
voice->loop_start = (voice->loop_start & (0x00ffff << 1)) | (data << 17);
break;
case 0x22: /* loop end address high */
voice->loop_end = (voice->loop_end & (0x00ffff << 1)) | (data << 17);
break;
case 0x23: /* stop address high */
voice->stop = (voice->stop & (0x00ffff << 1)) | (data << 17);
break;
case 0x40: /* start address middle */
voice->start = (voice->start & (0xff00ff << 1)) | (data << 9);
break;
case 0x41: /* loop start address middle */
voice->loop_start = (voice->loop_start & (0xff00ff << 1)) | (data << 9);
break;
case 0x42: /* loop end address middle */
voice->loop_end = (voice->loop_end & (0xff00ff << 1)) | (data << 9);
break;
case 0x43: /* stop address middle */
voice->stop = (voice->stop & (0xff00ff << 1)) | (data << 9);
break;
case 0x60: /* start address low */
voice->start = (voice->start & (0xffff00 << 1)) | (data << 1);
break;
case 0x61: /* loop start address low */
voice->loop_start = (voice->loop_start & (0xffff00 << 1)) | (data << 1);
break;
case 0x62: /* loop end address low */
voice->loop_end = (voice->loop_end & (0xffff00 << 1)) | (data << 1);
break;
case 0x63: /* stop address low */
voice->stop = (voice->stop & (0xffff00 << 1)) | (data << 1);
break;
default:
if (data != 0)
printf("YMZ280B: unknown register write %02X = %02X\n", m_current_register, data);
break;
}
}
/* upper registers are special */
else
{
switch (m_current_register)
{
/* DSP related (not implemented yet) */
case 0x80: // d0-2: DSP Rch, d3: enable Rch (0: yes, 1: no), d4-6: DSP Lch, d7: enable Lch (0: yes, 1: no)
case 0x81: // d0: enable control of $82 (0: yes, 1: no)
case 0x82: // DSP data
printf("YMZ280B: DSP register write %02X = %02X\n", m_current_register, data);
break;
case 0x84: /* ROM readback / RAM write (high) */
m_ext_mem_address_hi = data << 16;
break;
case 0x85: /* ROM readback / RAM write (middle) */
m_ext_mem_address_mid = data << 8;
break;
case 0x86: /* ROM readback / RAM write (low) -> update latch */
m_ext_mem_address = m_ext_mem_address_hi | m_ext_mem_address_mid | data;
if (m_ext_mem_enable)
m_ext_readlatch = m_ext_mem[m_ext_mem_address];
break;
case 0x87: /* RAM write */
if (m_ext_mem_enable)
{
m_ext_mem[m_ext_mem_address] = data;
m_ext_mem_address = (m_ext_mem_address + 1) & 0xffffff;
}
break;
case 0xfe: /* IRQ mask */
m_irq_mask = data;
break;
case 0xff: /* IRQ enable, test, etc */
m_ext_mem_enable = (data & 0x40) >> 6;
m_irq_enable = (data & 0x10) >> 4;
if (m_keyon_enable && !(data & 0x80))
{
for (i = 0; i < 8; i++)
{
m_voice[i].playing = 0;
/* if update_irq_state_timer is set, cancel it. */
m_voice[i].irq_schedule = 0;
}
}
else if (!m_keyon_enable && (data & 0x80))
{
for (i = 0; i < 8; i++)
{
if (m_voice[i].keyon && m_voice[i].looping)
m_voice[i].playing = 1;
}
}
m_keyon_enable = (data & 0x80) >> 7;
break;
default:
if (data != 0)
printf("YMZ280B: unknown register write %02X = %02X\n", m_current_register, data);
break;
}
}
}
/**********************************************************************************************
compute_status -- determine the status bits
***********************************************************************************************/
int ymz280b_device::compute_status()
{
u8 result;
result = m_status_register;
/* clear the IRQ state */
m_status_register = 0;
return result;
}
/**********************************************************************************************
read/write -- handle external accesses
***********************************************************************************************/
u8 ymz280b_device::read(offs_t offset)
{
if ((offset & 1) == 0)
{
if (!m_ext_mem_enable)
return 0xff;
/* read from external memory */
u8 ret = m_ext_readlatch;
m_ext_readlatch = m_ext_mem[m_ext_mem_address];
m_ext_mem_address = (m_ext_mem_address + 1) & 0xffffff;
return ret;
}
else
return compute_status();
}
void ymz280b_device::write(offs_t offset, u8 data)
{
if ((offset & 1) == 0)
m_current_register = data;
else
{
write_to_register(data);
}
}
ymz280b_device::ymz280b_device()
: m_current_register(0)
, m_status_register(0)
, m_irq_state(0)
, m_irq_mask(0)
, m_irq_enable(0)
, m_keyon_enable(0)
, m_ext_mem_enable(0)
, m_ext_readlatch(0)
, m_ext_mem_address_hi(0)
, m_ext_mem_address_mid(0)
, m_ext_mem_address(0)
{
memset(m_voice, 0, sizeof(m_voice));
}

View File

@ -0,0 +1,106 @@
// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/**********************************************************************************************
*
* Yamaha YMZ280B driver
* by Aaron Giles
*
**********************************************************************************************/
#include <memory>
#ifndef MAME_SOUND_YMZ280B_H
#define MAME_SOUND_YMZ280B_H
#pragma once
namespace ymz280b
{
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned int u32;
typedef signed int s32;
typedef signed int offs_t;
}
using namespace ymz280b;
class ymz280b_device
{
public:
ymz280b_device();
u8 read(offs_t offset);
void write(offs_t offset, u8 data);
void device_start(u8 *ext_mem);
void device_reset();
void device_update();
void sound_stream_update(s16 **outputs, int samples);
private:
/* struct describing a single playing ADPCM voice */
struct YMZ280BVoice
{
u8 playing; /* 1 if we are actively playing */
bool ended; /* indicate voice has ended in case samples_left is 0 */
u8 keyon; /* 1 if the key is on */
u8 looping; /* 1 if looping is enabled */
u8 mode; /* current playback mode */
u16 fnum; /* frequency */
u8 level; /* output level */
u8 pan; /* panning */
u32 start; /* start address, in nibbles */
u32 stop; /* stop address, in nibbles */
u32 loop_start; /* loop start address, in nibbles */
u32 loop_end; /* loop end address, in nibbles */
u32 position; /* current position, in nibbles */
s32 signal; /* current ADPCM signal */
s32 step; /* current ADPCM step */
s32 loop_signal; /* signal at loop start */
s32 loop_step; /* step at loop start */
u32 loop_count; /* number of loops so far */
s32 output_left; /* output volume (left) */
s32 output_right; /* output volume (right) */
s32 output_step; /* step value for frequency conversion */
s32 output_pos; /* current fractional position */
s16 last_sample; /* last sample output */
s16 curr_sample; /* current sample target */
u8 irq_schedule; /* 1 if the IRQ state is updated by timer */
};
void update_step(struct YMZ280BVoice *voice);
void update_volumes(struct YMZ280BVoice *voice);
int generate_adpcm(struct YMZ280BVoice *voice, s16 *buffer, int samples);
int generate_pcm8(struct YMZ280BVoice *voice, s16 *buffer, int samples);
int generate_pcm16(struct YMZ280BVoice *voice, s16 *buffer, int samples);
void write_to_register(int data);
int compute_status();
// internal state
struct YMZ280BVoice m_voice[8]; /* the 8 voices */
u8 m_current_register; /* currently accessible register */
u8 m_status_register; /* current status register */
u8 m_irq_state; /* current IRQ state */
u8 m_irq_mask; /* current IRQ mask */
u8 m_irq_enable; /* current IRQ enable */
u8 m_keyon_enable; /* key on enable */
u8 m_ext_mem_enable; /* external memory enable */
u8 m_ext_readlatch; /* external memory prefetched data */
u32 m_ext_mem_address_hi;
u32 m_ext_mem_address_mid;
u32 m_ext_mem_address; /* where the CPU can read the ROM */
u8 *m_ext_mem;
std::unique_ptr<s16[]> m_scratch;
};
#endif // MAME_SOUND_YMZ280B_H

View File

@ -0,0 +1,459 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ymz280b.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#include <map>
#define CHIP_FREQBASE 98304
#define rWrite(a,v) {if(!skipRegisterWrites) {ymz280b.write(0,a); ymz280b.write(1,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }}
const char* regCheatSheetYMZ280B[]={
"CHx_Freq", "00+x*4",
"CHx_Control", "01+x*4",
"CHx_Volume", "02+x*4",
"CHx_Panning", "03+x*4",
"CHx_StartH", "20+x*4",
"CHx_LoopStartH", "21+x*4",
"CHx_LoopEndH", "22+x*4",
"CHx_EndH", "23+x*4",
"CHx_StartM", "40+x*4",
"CHx_LoopStartM", "41+x*4",
"CHx_LoopEndM", "42+x*4",
"CHx_EndM", "43+x*4",
"CHx_StartL", "60+x*4",
"CHx_LoopStartL", "61+x*4",
"CHx_LoopEndL", "62+x*4",
"CHx_EndL", "63+x*4",
"DSP_Channel", "80",
"DSP_Enable", "81",
"DSP_Data", "82",
"RAM_AddrH", "84",
"RAM_AddrM", "85",
"RAM_AddrL", "86",
"RAM_Data", "87",
"IRQ_Enable", "E0",
"Enable", "FF",
NULL
};
const char** DivPlatformYMZ280B::getRegisterSheet() {
return regCheatSheetYMZ280B;
}
const char* DivPlatformYMZ280B::getEffectName(unsigned char effect) {
return NULL;
}
void DivPlatformYMZ280B::acquire(short* bufL, short* bufR, size_t start, size_t len) {
short buf[16][256];
short *bufPtrs[16]={
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],
buf[8],buf[9],buf[10],buf[11],buf[12],buf[13],buf[14],buf[15]
};
int dataL,dataR;
size_t pos=start;
while (len > 0) {
size_t blockLen = MIN(len, 256);
ymz280b.sound_stream_update(bufPtrs, blockLen);
for (int i=0; i<blockLen; i++) {
int dataL=0;
int dataR=0;
for (int j=0; j<8; j++) {
dataL+=buf[j*2][i];
dataR+=buf[j*2+1][i];
oscBuf[j]->data[oscBuf[j]->needle++]=(short)(((int)buf[j*2][i]+buf[j*2+1][i])/2);
}
bufL[pos]=(short)(dataL/8);
bufR[pos]=(short)(dataR/8);
pos++;
}
len-=blockLen;
}
}
void DivPlatformYMZ280B::tick(bool sysTick) {
for (int i=0; i<8; i++) {
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6;
writeOutVol(i);
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val;
CLAMP_VAR(chan[i].pitch2,-2048,2048);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].std.panL.had) { // panning
chan[i].panning=MIN((chan[i].std.panL.val*15/16+15)/2+1,15);
rWrite(0x03+i*4,chan[i].panning);
}
if (chan[i].setPos) {
// force keyon
chan[i].keyOn=true;
chan[i].setPos=false;
} else {
chan[i].audPos=0;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
DivSample* s=parent->getSample(chan[i].sample);
unsigned char ctrl;
switch (s->depth) {
case 2: ctrl=0x20; break;
case 8: ctrl=0x40; break;
case 16: ctrl=0x60; break;
default: ctrl=0;
}
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE))-1;
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>511) chan[i].freq=511;
// ADPCM has half the range
if (s->depth==2 && chan[i].freq>255) chan[i].freq=255;
ctrl|=(chan[i].active?0x80:0)|((s->loopStart>=0)?0x10:0)|(chan[i].freq>>8);
if (chan[i].keyOn) {
unsigned int start=s->offYMZ280B;
unsigned int loop=0;
unsigned int end=start+s->getCurBufLen();
if (chan[i].audPos>0) {
switch (s->depth) {
case 2: start+=chan[i].audPos/2; break;
case 8: start+=chan[i].audPos; break;
case 16: start+=chan[i].audPos*2; break;
}
}
if (s->loopStart>=0) {
switch (s->depth) {
case 2: loop=start+s->loopStart/2; break;
case 8: loop=start+s->loopStart; break;
case 16: loop=start+s->loopStart*2; break;
}
}
rWrite(0x01+i*4,ctrl&~0x80); // force keyoff first
rWrite(0x20+i*4,(start>>16)&0xff);
rWrite(0x21+i*4,(loop>>16)&0xff);
rWrite(0x22+i*4,(end>>16)&0xff);
rWrite(0x23+i*4,(end>>16)&0xff);
rWrite(0x40+i*4,(start>>8)&0xff);
rWrite(0x41+i*4,(loop>>8)&0xff);
rWrite(0x42+i*4,(end>>8)&0xff);
rWrite(0x43+i*4,(end>>8)&0xff);
rWrite(0x60+i*4,start&0xff);
rWrite(0x61+i*4,loop&0xff);
rWrite(0x62+i*4,end&0xff);
rWrite(0x63+i*4,end&0xff);
if (!chan[i].std.vol.had) {
chan[i].outVol=chan[i].vol;
writeOutVol(i);
}
chan[i].keyOn=false;
}
if (chan[i].keyOff) {
chan[i].keyOff=false;
}
if (chan[i].freqChanged) {
rWrite(0x00+i*4,chan[i].freq&0xff);
chan[i].freqChanged=false;
}
rWrite(0x01+i*4,ctrl);
}
}
}
int DivPlatformYMZ280B::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
}
if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].sample=-1;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
writeOutVol(c.chan);
}
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.vol.has) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PANNING:
chan[c.chan].panning=MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,15)+1,15);
rWrite(0x03+c.chan*4,chan[c.chan].panning);
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=NOTE_FREQUENCY(c.value2);
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
}
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].audPos=c.value;
chan[c.chan].setPos=true;
break;
case DIV_CMD_GET_VOLMAX:
return 255;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
break;
default:
break;
}
return 1;
}
void DivPlatformYMZ280B::writeOutVol(int ch) {
unsigned char val=isMuted[ch]?0:chan[ch].outVol;
rWrite(0x02+ch*4,val);
}
void DivPlatformYMZ280B::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
writeOutVol(ch);
}
void DivPlatformYMZ280B::forceIns() {
for (int i=0; i<8; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].sample=-1;
}
}
void* DivPlatformYMZ280B::getChanState(int ch) {
return &chan[ch];
}
DivDispatchOscBuffer* DivPlatformYMZ280B::getOscBuffer(int ch) {
return oscBuf[ch];
}
void DivPlatformYMZ280B::reset() {
memset(regPool,0,256);
ymz280b.device_reset();
rWrite(0xff,0x80); // enable keyon
for (int i=0; i<8; i++) {
chan[i]=DivPlatformYMZ280B::Channel();
chan[i].std.setEngine(parent);
rWrite(0x02+i*4,255);
rWrite(0x03+i*4,8);
}
}
bool DivPlatformYMZ280B::isStereo() {
return true;
}
void DivPlatformYMZ280B::notifyInsChange(int ins) {
for (int i=0; i<8; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformYMZ280B::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformYMZ280B::notifyInsDeletion(void* ins) {
for (int i=0; i<8; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformYMZ280B::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformYMZ280B::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
unsigned char* DivPlatformYMZ280B::getRegisterPool() {
return regPool;
}
int DivPlatformYMZ280B::getRegisterPoolSize() {
return 256;
}
float DivPlatformYMZ280B::getPostAmp() {
// according to MAME core's mixing
return 4.0f;
}
const void* DivPlatformYMZ280B::getSampleMem(int index) {
return index == 0 ? sampleMem : NULL;
}
size_t DivPlatformYMZ280B::getSampleMemCapacity(int index) {
return index == 0 ? 16777216 : 0;
}
size_t DivPlatformYMZ280B::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
void DivPlatformYMZ280B::renderSamples() {
memset(sampleMem,0,getSampleMemCapacity());
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int length=s->getCurBufLen();
unsigned char* src=(unsigned char*)s->getCurBuf();
int actualLength=MIN(getSampleMemCapacity()-memPos-length,length);
if (actualLength>0) {
memcpy(&sampleMem[memPos],src,actualLength);
s->offYMZ280B=memPos;
memPos+=length;
}
if (actualLength<length) {
logW("out of YMZ280B PCM memory for sample %d!",i);
break;
}
}
sampleMemLen=memPos;
}
void DivPlatformYMZ280B::setChipModel(int type) {
chipType=type;
}
int DivPlatformYMZ280B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<8; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
rate=(chipType==759)?32000:44100;
chipClock=rate*384;
sampleMem=new unsigned char[getSampleMemCapacity()];
sampleMemLen=0;
ymz280b.device_start(sampleMem);
reset();
for (int i=0; i<8; i++) {
oscBuf[i]->rate=rate;
}
return 8;
}
void DivPlatformYMZ280B::quit() {
delete[] sampleMem;
for (int i=0; i<8; i++) {
delete oscBuf[i];
}
}

View File

@ -0,0 +1,104 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _YMZ280B_H
#define _QSOUND_H
#include "../dispatch.h"
#include <queue>
#include "../macroInt.h"
#include "sound/ymz280b.h"
class DivPlatformYMZ280B: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2;
unsigned int audPos;
int sample, wave, ins;
int note;
int panning;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, setPos;
int vol, outVol;
DivMacroInt std;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
audPos(0),
sample(-1),
ins(-1),
note(0),
panning(8),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
setPos(false),
vol(255),
outVol(255) {}
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];
bool isMuted[8];
int chipType;
unsigned char* sampleMem;
size_t sampleMemLen;
ymz280b_device ymz280b;
unsigned char regPool[256];
friend void putDispatchChan(void*,int,int);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();
void forceIns();
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
float getPostAmp();
bool isStereo();
void setChipModel(int type);
void notifyInsChange(int ins);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
const void* getSampleMem(int index = 0);
size_t getSampleMemCapacity(int index = 0);
size_t getSampleMemUsage(int index = 0);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
private:
void writeOutVol(int ch);
};
#endif

View File

@ -93,17 +93,12 @@ bool DivSample::initInternal(unsigned char d, int count) {
dataDPCM=new unsigned char[lengthDPCM];
memset(dataDPCM,0,lengthDPCM);
break;
case 2: // AICA ADPCM
if (dataAICA!=NULL) delete[] dataAICA;
lengthAICA=(count+1)/2;
dataAICA=new unsigned char[(lengthAICA+255)&(~0xff)];
memset(dataAICA,0,(lengthAICA+255)&(~0xff));
break;
case 3: // YMZ ADPCM
case 2: // YMZ ADPCM
if (dataZ!=NULL) delete[] dataZ;
lengthZ=(count+1)/2;
dataZ=new unsigned char[(lengthZ+255)&(~0xff)];
memset(dataZ,0,(lengthZ+255)&(~0xff));
// for padding AICA sample
dataZ=new unsigned char[(lengthZ+3)&(~0x03)];
memset(dataZ,0,(lengthZ+3)&(~0x03));
break;
case 4: // QSound ADPCM
if (dataQSoundA!=NULL) delete[] dataQSoundA;
@ -669,10 +664,7 @@ void DivSample::render() {
}
break;
}
case 2: // AICA ADPCM
aica_decode(dataAICA,data16,samples);
break;
case 3: // YMZ ADPCM
case 2: // YMZ ADPCM
ymz_decode(dataZ,data16,samples);
break;
case 4: // QSound ADPCM
@ -727,13 +719,9 @@ void DivSample::render() {
if (accum>127) accum=127;
}
}
if (depth!=2) { // AICA ADPCM
if (depth!=2) { // YMZ ADPCM
if (!initInternal(2,samples)) return;
aica_encode(data16,dataAICA,(samples+511)&(~0x1ff));
}
if (depth!=3) { // YMZ ADPCM
if (!initInternal(3,samples)) return;
ymz_encode(data16,dataZ,(samples+511)&(~0x1ff));
ymz_encode(data16,dataZ,(samples+7)&(~0x7));
}
if (depth!=4) { // QSound ADPCM
if (!initInternal(4,samples)) return;
@ -772,8 +760,6 @@ void* DivSample::getCurBuf() {
case 1:
return dataDPCM;
case 2:
return dataAICA;
case 3:
return dataZ;
case 4:
return dataQSoundA;
@ -802,8 +788,6 @@ unsigned int DivSample::getCurBufLen() {
case 1:
return lengthDPCM;
case 2:
return lengthAICA;
case 3:
return lengthZ;
case 4:
return lengthQSoundA;
@ -915,7 +899,6 @@ DivSample::~DivSample() {
if (data16) delete[] data16;
if (data1) delete[] data1;
if (dataDPCM) delete[] dataDPCM;
if (dataAICA) delete[] dataAICA;
if (dataZ) delete[] dataZ;
if (dataQSoundA) delete[] dataQSoundA;
if (dataA) delete[] dataA;

View File

@ -62,8 +62,7 @@ struct DivSample {
// valid values are:
// - 0: ZX Spectrum overlay drum (1-bit)
// - 1: 1-bit NES DPCM (1-bit)
// - 2: AICA ADPCM
// - 3: YMZ ADPCM
// - 2: YMZ ADPCM
// - 4: QSound ADPCM
// - 5: ADPCM-A
// - 6: ADPCM-B
@ -79,8 +78,7 @@ struct DivSample {
short* data16; // 16
unsigned char* data1; // 0
unsigned char* dataDPCM; // 1
unsigned char* dataAICA; // 2
unsigned char* dataZ; // 3
unsigned char* dataZ; // 2
unsigned char* dataQSoundA; // 4
unsigned char* dataA; // 5
unsigned char* dataB; // 6
@ -88,9 +86,9 @@ struct DivSample {
unsigned char* dataBRR; // 9
unsigned char* dataVOX; // 10
unsigned int length8, length16, length1, lengthDPCM, lengthAICA, lengthZ, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offAICA, offZ, offQSoundA, offA, offB, offX68, offBRR, offVOX;
unsigned int offSegaPCM, offQSound, offX1_010, offSU;
unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offX68, offBRR, offVOX;
unsigned int offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B;
unsigned int samples;
@ -222,7 +220,6 @@ struct DivSample {
data16(NULL),
data1(NULL),
dataDPCM(NULL),
dataAICA(NULL),
dataZ(NULL),
dataQSoundA(NULL),
dataA(NULL),
@ -234,7 +231,6 @@ struct DivSample {
length16(0),
length1(0),
lengthDPCM(0),
lengthAICA(0),
lengthZ(0),
lengthQSoundA(0),
lengthA(0),
@ -246,7 +242,6 @@ struct DivSample {
off16(0),
off1(0),
offDPCM(0),
offAICA(0),
offZ(0),
offQSoundA(0),
offA(0),

View File

@ -107,6 +107,7 @@ enum DivSystem {
DIV_SYSTEM_SOUND_UNIT,
DIV_SYSTEM_MSM6295,
DIV_SYSTEM_MSM6258,
DIV_SYSTEM_YMZ280B,
DIV_SYSTEM_DUMMY
};

View File

@ -1883,6 +1883,15 @@ void DivEngine::registerSystems() {
{DIV_INS_AMIGA}
);
sysDefs[DIV_SYSTEM_YMZ280B]=new DivSysDef(
"Yamaha YMZ280B", NULL, 0xb8, 0, 8, false, true, 0x151, false,
"used in some arcade boards. Has 8 channels of either 4-bit ADPCM, 8-bit PCM or 16-bit PCM.",
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8"},
{"1", "2", "3", "4", "5", "6", "7", "8"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}
);
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false,
"this is a system designed for testing purposes..",

View File

@ -675,6 +675,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
logW("SCC+: writing to unmapped address %.2x!",write.addr);
}
break;
case DIV_SYSTEM_YMZ280B:
w->writeC(0x0d|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val&0xff);
break;
default:
logW("write not handled!");
break;
@ -794,6 +799,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
int writeSegaPCM=0;
DivDispatch* writeX1010[2]={NULL,NULL};
DivDispatch* writeQSound[2]={NULL,NULL};
DivDispatch* writeZ280[2]={NULL,NULL};
for (int i=0; i<song.systemLen; i++) {
willExport[i]=false;
@ -1126,6 +1132,19 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
howManyChips++;
}
break;
case DIV_SYSTEM_YMZ280B:
if (!hasZ280) {
hasZ280=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeZ280[0]=disCont[i].dispatch;
} else if (!(hasZ280&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
writeZ280[1]=disCont[i].dispatch;
hasZ280|=0x40000000;
howManyChips++;
}
break;
default:
break;
}
@ -1398,9 +1417,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
w->writeI(0);
w->write(writeADPCM[i]->getSampleMem(0),writeADPCM[i]->getSampleMemUsage(0));
}
}
for (int i=0; i<2; i++) {
if (writeADPCM[i]!=NULL && writeADPCM[i]->getSampleMemUsage(1)>0) {
w->writeC(0x67);
w->writeC(0x66);
@ -1410,9 +1426,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
w->writeI(0);
w->write(writeADPCM[i]->getSampleMem(1),writeADPCM[i]->getSampleMemUsage(1));
}
}
for (int i=0; i<2; i++) {
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
if (blockSize > 0x1000000) {
@ -1426,9 +1439,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
w->writeI(0);
w->write(writeQSound[i]->getSampleMem(),blockSize);
}
}
for (int i=0; i<2; i++) {
if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) {
w->writeC(0x67);
w->writeC(0x66);
@ -1438,6 +1448,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
w->writeI(0);
w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage());
}
if (writeZ280[i]!=NULL && writeZ280[i]->getSampleMemUsage()>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x86);
w->writeI((writeZ280[i]->getSampleMemUsage()+8)|(i*0x80000000));
w->writeI(writeZ280[i]->getSampleMemCapacity());
w->writeI(0);
w->write(writeZ280[i]->getSampleMem(),writeZ280[i]->getSampleMemUsage());
}
}
// initialize streams

View File

@ -116,8 +116,8 @@ const char* insTypes[DIV_INS_MAX]={
const char* sampleDepths[17]={
"1-bit PCM",
"1-bit DPCM",
"Yamaha AICA",
"YMZ/YMU ADPCM",
"YMZ/YMU/AICA ADPCM",
NULL,
"QSound ADPCM",
"ADPCM-A",
"ADPCM-B",
@ -877,6 +877,7 @@ const int availableSystems[]={
DIV_SYSTEM_MMC5,
DIV_SYSTEM_SCC,
DIV_SYSTEM_SCC_PLUS,
DIV_SYSTEM_YMZ280B,
0 // don't remove this last one!
};

View File

@ -260,6 +260,12 @@ void FurnaceGUI::initSystemPresets() {
0
}
));
cat.systems.push_back(FurnaceGUISysDef(
"Yamaha YMZ280B", {
DIV_SYSTEM_YMZ280B, 64, 0, 0,
0
}
));
sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound.");

View File

@ -399,6 +399,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
case DIV_SYSTEM_PET:
case DIV_SYSTEM_SCC:
case DIV_SYSTEM_SCC_PLUS:
case DIV_SYSTEM_YMZ280B:
ImGui::Text("nothing to configure");
break;
default: