mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-15 17:25:06 +00:00
commit
2cb60c7488
14 changed files with 1518 additions and 23 deletions
|
@ -319,6 +319,8 @@ src/engine/platform/sound/vrcvi/vrcvi.cpp
|
||||||
|
|
||||||
src/engine/platform/sound/scc/scc.cpp
|
src/engine/platform/sound/scc/scc.cpp
|
||||||
|
|
||||||
|
src/engine/platform/sound/ymz280b.cpp
|
||||||
|
|
||||||
src/engine/platform/oplAInterface.cpp
|
src/engine/platform/oplAInterface.cpp
|
||||||
src/engine/platform/ym2608Interface.cpp
|
src/engine/platform/ym2608Interface.cpp
|
||||||
src/engine/platform/ym2610Interface.cpp
|
src/engine/platform/ym2610Interface.cpp
|
||||||
|
@ -384,6 +386,7 @@ src/engine/platform/pet.cpp
|
||||||
src/engine/platform/vic20.cpp
|
src/engine/platform/vic20.cpp
|
||||||
src/engine/platform/vrc6.cpp
|
src/engine/platform/vrc6.cpp
|
||||||
src/engine/platform/scc.cpp
|
src/engine/platform/scc.cpp
|
||||||
|
src/engine/platform/ymz280b.cpp
|
||||||
src/engine/platform/namcowsg.cpp
|
src/engine/platform/namcowsg.cpp
|
||||||
src/engine/platform/dummy.cpp
|
src/engine/platform/dummy.cpp
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
#include "platform/fds.h"
|
#include "platform/fds.h"
|
||||||
#include "platform/mmc5.h"
|
#include "platform/mmc5.h"
|
||||||
#include "platform/scc.h"
|
#include "platform/scc.h"
|
||||||
|
#include "platform/ymz280b.h"
|
||||||
#include "platform/dummy.h"
|
#include "platform/dummy.h"
|
||||||
#include "../ta-log.h"
|
#include "../ta-log.h"
|
||||||
#include "platform/zxbeeper.h"
|
#include "platform/zxbeeper.h"
|
||||||
|
@ -351,6 +352,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
||||||
dispatch=new DivPlatformSCC;
|
dispatch=new DivPlatformSCC;
|
||||||
((DivPlatformSCC*)dispatch)->setChipModel(true);
|
((DivPlatformSCC*)dispatch)->setChipModel(true);
|
||||||
break;
|
break;
|
||||||
|
case DIV_SYSTEM_YMZ280B:
|
||||||
|
dispatch=new DivPlatformYMZ280B;
|
||||||
|
((DivPlatformYMZ280B*)dispatch)->setChipModel(280);
|
||||||
|
break;
|
||||||
case DIV_SYSTEM_SOUND_UNIT:
|
case DIV_SYSTEM_SOUND_UNIT:
|
||||||
dispatch=new DivPlatformSoundUnit;
|
dispatch=new DivPlatformSoundUnit;
|
||||||
break;
|
break;
|
||||||
|
|
786
src/engine/platform/sound/ymz280b.cpp
Normal file
786
src/engine/platform/sound/ymz280b.cpp
Normal 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)
|
||||||
|
{
|
||||||
|
u32 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)
|
||||||
|
{
|
||||||
|
u32 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)
|
||||||
|
{
|
||||||
|
u32 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 t = (base == 0) ? curr : m_scratch[base - 1];
|
||||||
|
for (u32 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));
|
||||||
|
}
|
105
src/engine/platform/sound/ymz280b.h
Normal file
105
src/engine/platform/sound/ymz280b.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
// 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 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
|
460
src/engine/platform/ymz280b.cpp
Normal file
460
src/engine/platform/ymz280b.cpp
Normal file
|
@ -0,0 +1,460 @@
|
||||||
|
/**
|
||||||
|
* 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]
|
||||||
|
};
|
||||||
|
size_t pos=start;
|
||||||
|
while (len > 0) {
|
||||||
|
size_t blockLen = MIN(len, 256);
|
||||||
|
ymz280b.sound_stream_update(bufPtrs, blockLen);
|
||||||
|
for (size_t 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 3: 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==3 && 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=MIN(start+s->getCurBufLen(),getSampleMemCapacity()-1);
|
||||||
|
if (chan[i].audPos>0) {
|
||||||
|
switch (s->depth) {
|
||||||
|
case 3: start+=chan[i].audPos/2; break;
|
||||||
|
case 8: start+=chan[i].audPos; break;
|
||||||
|
case 16: start+=chan[i].audPos*2; break;
|
||||||
|
}
|
||||||
|
start=MIN(start,end);
|
||||||
|
}
|
||||||
|
if (s->loopStart>=0) {
|
||||||
|
switch (s->depth) {
|
||||||
|
case 3: loop=start+s->loopStart/2; break;
|
||||||
|
case 8: loop=start+s->loopStart; break;
|
||||||
|
case 16: loop=start+s->loopStart*2; break;
|
||||||
|
}
|
||||||
|
loop=MIN(loop,end);
|
||||||
|
}
|
||||||
|
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((int)(getSampleMemCapacity()-memPos),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];
|
||||||
|
}
|
||||||
|
}
|
104
src/engine/platform/ymz280b.h
Normal file
104
src/engine/platform/ymz280b.h
Normal 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 _YMZ280B_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
|
|
@ -96,8 +96,9 @@ bool DivSample::initInternal(unsigned char d, int count) {
|
||||||
case 3: // YMZ ADPCM
|
case 3: // YMZ ADPCM
|
||||||
if (dataZ!=NULL) delete[] dataZ;
|
if (dataZ!=NULL) delete[] dataZ;
|
||||||
lengthZ=(count+1)/2;
|
lengthZ=(count+1)/2;
|
||||||
dataZ=new unsigned char[(lengthZ+255)&(~0xff)];
|
// for padding AICA sample
|
||||||
memset(dataZ,0,(lengthZ+255)&(~0xff));
|
dataZ=new unsigned char[(lengthZ+3)&(~0x03)];
|
||||||
|
memset(dataZ,0,(lengthZ+3)&(~0x03));
|
||||||
break;
|
break;
|
||||||
case 4: // QSound ADPCM
|
case 4: // QSound ADPCM
|
||||||
if (dataQSoundA!=NULL) delete[] dataQSoundA;
|
if (dataQSoundA!=NULL) delete[] dataQSoundA;
|
||||||
|
@ -711,7 +712,7 @@ void DivSample::render() {
|
||||||
}
|
}
|
||||||
if (depth!=3) { // YMZ ADPCM
|
if (depth!=3) { // YMZ ADPCM
|
||||||
if (!initInternal(3,samples)) return;
|
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 (depth!=4) { // QSound ADPCM
|
||||||
if (!initInternal(4,samples)) return;
|
if (!initInternal(4,samples)) return;
|
||||||
|
|
|
@ -86,7 +86,7 @@ struct DivSample {
|
||||||
|
|
||||||
unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthBRR, lengthVOX;
|
unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthBRR, lengthVOX;
|
||||||
unsigned int off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offBRR, offVOX;
|
unsigned int off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offBRR, offVOX;
|
||||||
unsigned int offSegaPCM, offQSound, offX1_010, offSU;
|
unsigned int offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B;
|
||||||
|
|
||||||
unsigned int samples;
|
unsigned int samples;
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,7 @@ enum DivSystem {
|
||||||
DIV_SYSTEM_SOUND_UNIT,
|
DIV_SYSTEM_SOUND_UNIT,
|
||||||
DIV_SYSTEM_MSM6295,
|
DIV_SYSTEM_MSM6295,
|
||||||
DIV_SYSTEM_MSM6258,
|
DIV_SYSTEM_MSM6258,
|
||||||
|
DIV_SYSTEM_YMZ280B,
|
||||||
DIV_SYSTEM_NAMCO,
|
DIV_SYSTEM_NAMCO,
|
||||||
DIV_SYSTEM_NAMCO_15XX,
|
DIV_SYSTEM_NAMCO_15XX,
|
||||||
DIV_SYSTEM_NAMCO_CUS30,
|
DIV_SYSTEM_NAMCO_CUS30,
|
||||||
|
|
|
@ -1966,6 +1966,15 @@ void DivEngine::registerSystems() {
|
||||||
{DIV_INS_AMIGA}
|
{DIV_INS_AMIGA}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sysDefs[DIV_SYSTEM_YMZ280B]=new DivSysDef(
|
||||||
|
"Yamaha YMZ280B", NULL, 0xb8, 0, 8, false, true, 0x151, false,
|
||||||
|
"used in some arcade boards. Can play back 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_NAMCO]=new DivSysDef(
|
sysDefs[DIV_SYSTEM_NAMCO]=new DivSysDef(
|
||||||
"Namco WSG", NULL, 0xb9, 0, 3, false, true, 0, false,
|
"Namco WSG", NULL, 0xb9, 0, 3, false, true, 0, false,
|
||||||
"a wavetable sound chip used in Pac-Man, among other early Namco arcade games.",
|
"a wavetable sound chip used in Pac-Man, among other early Namco arcade games.",
|
||||||
|
|
|
@ -761,6 +761,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
||||||
logW("SCC+: writing to unmapped address %.2x!",write.addr);
|
logW("SCC+: writing to unmapped address %.2x!",write.addr);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case DIV_SYSTEM_YMZ280B:
|
||||||
|
w->writeC(0x0d|baseAddr1);
|
||||||
|
w->writeC(write.addr&0xff);
|
||||||
|
w->writeC(write.val&0xff);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
logW("write not handled!");
|
logW("write not handled!");
|
||||||
break;
|
break;
|
||||||
|
@ -882,6 +887,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
int writeSegaPCM=0;
|
int writeSegaPCM=0;
|
||||||
DivDispatch* writeX1010[2]={NULL,NULL};
|
DivDispatch* writeX1010[2]={NULL,NULL};
|
||||||
DivDispatch* writeQSound[2]={NULL,NULL};
|
DivDispatch* writeQSound[2]={NULL,NULL};
|
||||||
|
DivDispatch* writeZ280[2]={NULL,NULL};
|
||||||
|
|
||||||
for (int i=0; i<song.systemLen; i++) {
|
for (int i=0; i<song.systemLen; i++) {
|
||||||
willExport[i]=false;
|
willExport[i]=false;
|
||||||
|
@ -1243,6 +1249,19 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
howManyChips++;
|
howManyChips++;
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1505,8 +1524,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
delete[] pcmMem;
|
delete[] pcmMem;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADPCM (OPNA)
|
|
||||||
for (int i=0; i<2; i++) {
|
for (int i=0; i<2; i++) {
|
||||||
|
// ADPCM (OPNA)
|
||||||
if (writeADPCM_OPNA[i]!=NULL && writeADPCM_OPNA[i]->getSampleMemUsage(0)>0) {
|
if (writeADPCM_OPNA[i]!=NULL && writeADPCM_OPNA[i]->getSampleMemUsage(0)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1516,10 +1535,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeADPCM_OPNA[i]->getSampleMem(0),writeADPCM_OPNA[i]->getSampleMemUsage(0));
|
w->write(writeADPCM_OPNA[i]->getSampleMem(0),writeADPCM_OPNA[i]->getSampleMemUsage(0));
|
||||||
}
|
}
|
||||||
}
|
// ADPCM-A (OPNB)
|
||||||
|
|
||||||
// ADPCM-A (OPNB)
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(0)>0) {
|
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(0)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1529,10 +1545,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeADPCM_OPNB[i]->getSampleMem(0),writeADPCM_OPNB[i]->getSampleMemUsage(0));
|
w->write(writeADPCM_OPNB[i]->getSampleMem(0),writeADPCM_OPNB[i]->getSampleMemUsage(0));
|
||||||
}
|
}
|
||||||
}
|
// ADPCM-B (OPNB)
|
||||||
|
|
||||||
// ADPCM-B (OPNB)
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(1)>0) {
|
if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(1)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1542,10 +1555,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeADPCM_OPNB[i]->getSampleMem(1),writeADPCM_OPNB[i]->getSampleMemUsage(1));
|
w->write(writeADPCM_OPNB[i]->getSampleMem(1),writeADPCM_OPNB[i]->getSampleMemUsage(1));
|
||||||
}
|
}
|
||||||
}
|
// ADPCM (Y8950)
|
||||||
|
|
||||||
// ADPCM (Y8950)
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeADPCM_Y8950[i]!=NULL && writeADPCM_Y8950[i]->getSampleMemUsage(0)>0) {
|
if (writeADPCM_Y8950[i]!=NULL && writeADPCM_Y8950[i]->getSampleMemUsage(0)>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1555,9 +1565,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
|
w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
||||||
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
||||||
if (blockSize > 0x1000000) {
|
if (blockSize > 0x1000000) {
|
||||||
|
@ -1571,9 +1578,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeQSound[i]->getSampleMem(),blockSize);
|
w->write(writeQSound[i]->getSampleMem(),blockSize);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (int i=0; i<2; i++) {
|
|
||||||
if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) {
|
if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) {
|
||||||
w->writeC(0x67);
|
w->writeC(0x67);
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
|
@ -1583,6 +1587,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage());
|
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
|
// initialize streams
|
||||||
|
|
|
@ -880,6 +880,7 @@ const int availableSystems[]={
|
||||||
DIV_SYSTEM_MMC5,
|
DIV_SYSTEM_MMC5,
|
||||||
DIV_SYSTEM_SCC,
|
DIV_SYSTEM_SCC,
|
||||||
DIV_SYSTEM_SCC_PLUS,
|
DIV_SYSTEM_SCC_PLUS,
|
||||||
|
DIV_SYSTEM_YMZ280B,
|
||||||
DIV_SYSTEM_MSM6258,
|
DIV_SYSTEM_MSM6258,
|
||||||
DIV_SYSTEM_MSM6295,
|
DIV_SYSTEM_MSM6295,
|
||||||
DIV_SYSTEM_NAMCO,
|
DIV_SYSTEM_NAMCO,
|
||||||
|
|
|
@ -260,6 +260,12 @@ void FurnaceGUI::initSystemPresets() {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
cat.systems.push_back(FurnaceGUISysDef(
|
||||||
|
"Yamaha YMZ280B", {
|
||||||
|
DIV_SYSTEM_YMZ280B, 64, 0, 0,
|
||||||
|
0
|
||||||
|
}
|
||||||
|
));
|
||||||
sysCategories.push_back(cat);
|
sysCategories.push_back(cat);
|
||||||
|
|
||||||
cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound.");
|
cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound.");
|
||||||
|
|
|
@ -399,6 +399,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
|
||||||
case DIV_SYSTEM_PET:
|
case DIV_SYSTEM_PET:
|
||||||
case DIV_SYSTEM_SCC:
|
case DIV_SYSTEM_SCC:
|
||||||
case DIV_SYSTEM_SCC_PLUS:
|
case DIV_SYSTEM_SCC_PLUS:
|
||||||
|
case DIV_SYSTEM_YMZ280B:
|
||||||
ImGui::Text("nothing to configure");
|
ImGui::Text("nothing to configure");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
Loading…
Reference in a new issue