mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-23 21:15:11 +00:00
Merge branch 'cam900-x1_010'
This commit is contained in:
commit
43ce6d1e1f
29 changed files with 1754 additions and 38 deletions
|
@ -267,6 +267,8 @@ src/engine/platform/sound/lynx/Mikey.cpp
|
|||
|
||||
src/engine/platform/sound/qsound.c
|
||||
|
||||
src/engine/platform/sound/x1_010/x1_010.cpp
|
||||
|
||||
src/engine/platform/sound/swan.cpp
|
||||
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
@ -310,6 +312,7 @@ src/engine/platform/amiga.cpp
|
|||
src/engine/platform/pcspkr.cpp
|
||||
src/engine/platform/segapcm.cpp
|
||||
src/engine/platform/qsound.cpp
|
||||
src/engine/platform/x1_010.cpp
|
||||
src/engine/platform/lynx.cpp
|
||||
src/engine/platform/swan.cpp
|
||||
src/engine/platform/vera.cpp
|
||||
|
|
|
@ -10,7 +10,7 @@ double-click to open the instrument editor.
|
|||
|
||||
every instrument can be renamed and have its type changed.
|
||||
|
||||
depending on the instrument type, there are currently 12 different types of an instrument editor:
|
||||
depending on the instrument type, there are currently 13 different types of an instrument editor:
|
||||
|
||||
- [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610.
|
||||
- [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives.
|
||||
|
@ -22,9 +22,10 @@ depending on the instrument type, there are currently 12 different types of an i
|
|||
- [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source.
|
||||
- [TIA](tia.md) - for use with Atari 2600 system.
|
||||
- [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610.
|
||||
- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode.
|
||||
- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM, X1-010 and PC Engine's sample playback mode.
|
||||
- [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console.
|
||||
- [VERA](vera.md) - for use with Commander X16 VERA.
|
||||
- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010.
|
||||
|
||||
# macros
|
||||
|
||||
|
|
11
papers/doc/4-instrument/x1_010.md
Normal file
11
papers/doc/4-instrument/x1_010.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# X1-010 instrument editor
|
||||
|
||||
X1-010 instrument editor consists of 7 macros.
|
||||
|
||||
- [Volume] - volume levels sequence
|
||||
- [Arpeggio]- pitch sequence
|
||||
- [Waveform] - spicifies wavetables sequence
|
||||
- [Envelope Mode] - allows shaping an envelope
|
||||
- [Envelope] - spicifies envelope shape sequence, it's also wavetable.
|
||||
- [Auto envelope numerator] - sets the envelope to the channel's frequency multiplied by numerator
|
||||
- [Auto envelope denominator] - ets the envelope to the channel's frequency multiplied by denominator
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy.
|
||||
|
||||
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms as of now, with 16-level height for GB and WS, and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit within the constraints of the system.
|
||||
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope and WS, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system.
|
||||
|
|
|
@ -12,7 +12,8 @@ As of Furnace 0.5.5, the following sound chips have sample support:
|
|||
- PC Engine/TurboGrafx 16/Huc6280 (same conditions as above)
|
||||
- Amiga/Paula (on all channels AND resamplable, but you need to make an instrument with the Amiga format and tie it to a sample first)
|
||||
- Arcade/SEGA PCM (same as above but you don't need to make an instrument for it and you have to use the `20xx` effect command to resample your samples)
|
||||
- Neo Geo/Neo Geo EXT-Ch2 (on the last 5 channels only and can be resampled the same way as above)
|
||||
- Neo Geo/Neo Geo EXT-Ch2 (on the last 7 channels only and can be resampled the same way as above)
|
||||
- Seta/Allumer X1-010 (same as above, and both `1701` and `20xx` effect commands are affected on all 16 channels)
|
||||
|
||||
Furnace also has a feature where you can make an Amiga formarted instrument on the YM2612 and Huc6280 to resample a sample you have in the module.
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ this is a list of systems that Furnace supports, including each system's effects
|
|||
- [Atari 2600](tia.md)
|
||||
- [Philips SAA1099](saa1099.md)
|
||||
- [Microchip AY8930](ay8930.md)
|
||||
- [Seta/Allumer X1-010](x1_010.md)
|
||||
- [WonderSwan](wonderswan.md)
|
||||
|
||||
Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all.
|
||||
|
|
47
papers/doc/7-systems/x1_010.md
Normal file
47
papers/doc/7-systems/x1_010.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Seta/Allumer X1-010
|
||||
|
||||
One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s.
|
||||
It has 2 output channels, but no known hardware using this feature for stereo sound.
|
||||
Later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision.
|
||||
Allumer one is just rebadged Seta's thing for use in their arcade hardwares.
|
||||
|
||||
It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode.
|
||||
Wavetable needs to paired with envelope, this feature is similar as AY PSG, but its shape are stored at RAM: it means it is user-definable.
|
||||
|
||||
In furnace, this chip is can be configurable for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output.
|
||||
|
||||
# waveform type
|
||||
|
||||
This chip supports 2 type waveforms, needs to paired external 8 KB RAM for use these features:
|
||||
|
||||
One is signed 8 bit mono waveform, it's operated like other wavetable based sound systems.
|
||||
These are stored at the bottom half of RAM at common case.
|
||||
|
||||
Another one ("Envelope") is 4 bit stereo waveform, it's multiplied with above and calculates final output, Each nibble is used for each output channels.
|
||||
These are stored at the upper half of RAM at common case.
|
||||
|
||||
Both waveforms are 128 byte fixed size, it's freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once.
|
||||
In furnace, You can set envelope shape split mode. When it sets, its waveform will be split to left half and right half for each outputs. each max size are 128 bytes, total 256 bytes.
|
||||
|
||||
# effects
|
||||
|
||||
- `10xx`: change wave.
|
||||
- `11xx`: change envelope shape. (also wavetable)
|
||||
- `17xx`: toggle PCM mode.
|
||||
- `20xx`: set PCM frequency. (1 to FF)*
|
||||
- `22xx`: set envelope mode.
|
||||
- bit 0 sets whether envelope will affect this channel.
|
||||
- bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended.
|
||||
- bit 2 sets whether envelope shape split mode. when it sets, envelope shape will splitted to left half and right half.
|
||||
- bit 3/5 sets whether the right/left shape will mirror the original one.
|
||||
- bit 4/6 sets whether the right/left output will mirror the original one.
|
||||
- `23xx`: set envelope period.
|
||||
- `25xx`: slide envelope period up.
|
||||
- `26xx`: slide envelope period down.
|
||||
- `29xy`: enable auto-envelope mode.
|
||||
- in this mode the envelope period is set to the channel's notes, multiplied by a fraction.
|
||||
- `x` is the numerator.
|
||||
- `y` is the denominator.
|
||||
- if `x` or `y` are 0 this will disable auto-envelope mode.
|
||||
|
||||
* PCM frequency: 255 step, fomula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz.
|
|
@ -109,6 +109,13 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_QSOUND_ECHO_DELAY,
|
||||
DIV_CMD_QSOUND_ECHO_LEVEL,
|
||||
|
||||
DIV_CMD_X1_010_ENVELOPE_SHAPE,
|
||||
DIV_CMD_X1_010_ENVELOPE_ENABLE,
|
||||
DIV_CMD_X1_010_ENVELOPE_MODE,
|
||||
DIV_CMD_X1_010_ENVELOPE_PERIOD,
|
||||
DIV_CMD_X1_010_ENVELOPE_SLIDE,
|
||||
DIV_CMD_X1_010_AUTO_ENVELOPE,
|
||||
|
||||
DIV_CMD_WS_SWEEP_TIME,
|
||||
DIV_CMD_WS_SWEEP_AMOUNT,
|
||||
|
||||
|
|
|
@ -41,9 +41,10 @@
|
|||
#include "platform/pcspkr.h"
|
||||
#include "platform/segapcm.h"
|
||||
#include "platform/qsound.h"
|
||||
#include "platform/lynx.h"
|
||||
#include "platform/swan.h"
|
||||
#include "platform/vera.h"
|
||||
#include "platform/x1_010.h"
|
||||
#include "platform/swan.h"
|
||||
#include "platform/lynx.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "../ta-log.h"
|
||||
#include "song.h"
|
||||
|
@ -257,6 +258,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_SEGAPCM_COMPAT:
|
||||
dispatch=new DivPlatformSegaPCM;
|
||||
break;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
dispatch=new DivPlatformX1_010;
|
||||
break;
|
||||
case DIV_SYSTEM_SWAN:
|
||||
dispatch=new DivPlatformSwan;
|
||||
break;
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
#include "dataErrors.h"
|
||||
#include "song.h"
|
||||
#include <cstddef>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "engine.h"
|
||||
#include "instrument.h"
|
||||
|
@ -532,6 +531,36 @@ void DivEngine::renderSamples() {
|
|||
memPos+=length+16;
|
||||
}
|
||||
qsoundMemLen=memPos+256;
|
||||
|
||||
// step 4: allocate x1-010 pcm samples
|
||||
if (x1_010Mem==NULL) x1_010Mem=new unsigned char[1048576];
|
||||
memset(x1_010Mem,0,1048576);
|
||||
|
||||
memPos=0;
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
DivSample* s=song.sample[i];
|
||||
int paddedLen=(s->length8+4095)&(~0xfff);
|
||||
// fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!)
|
||||
if (paddedLen>131072) {
|
||||
paddedLen=131072;
|
||||
}
|
||||
if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) {
|
||||
memPos=(memPos+0x1ffff)&0xfe0000;
|
||||
}
|
||||
if (memPos>=1048576) {
|
||||
logW("out of X1-010 memory for sample %d!\n",i);
|
||||
break;
|
||||
}
|
||||
if (memPos+paddedLen>=1048576) {
|
||||
memcpy(x1_010Mem+memPos,s->data8,1048576-memPos);
|
||||
logW("out of X1-010 memory for sample %d!\n",i);
|
||||
} else {
|
||||
memcpy(x1_010Mem+memPos,s->data8,paddedLen);
|
||||
}
|
||||
s->offX1_010=memPos;
|
||||
memPos+=paddedLen;
|
||||
}
|
||||
x1_010MemLen=memPos+256;
|
||||
}
|
||||
|
||||
void DivEngine::createNew(const int* description) {
|
||||
|
@ -956,6 +985,8 @@ int DivEngine::getEffectiveSampleRate(int rate) {
|
|||
return 18518;
|
||||
case DIV_SYSTEM_VERA:
|
||||
return (48828*MIN(128,(rate*128/48828)))/128;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -632,6 +632,8 @@ class DivEngine {
|
|||
size_t qsoundAMemLen;
|
||||
unsigned char* dpcmMem;
|
||||
size_t dpcmMemLen;
|
||||
unsigned char* x1_010Mem;
|
||||
size_t x1_010MemLen;
|
||||
|
||||
DivEngine():
|
||||
output(NULL),
|
||||
|
|
|
@ -52,6 +52,8 @@ enum DivInstrumentType {
|
|||
DIV_INS_SWAN=22,
|
||||
DIV_INS_MIKEY=23,
|
||||
DIV_INS_VERA=24,
|
||||
DIV_INS_X1_010=25,
|
||||
DIV_INS_MAX,
|
||||
};
|
||||
|
||||
// FM operator structure:
|
||||
|
|
224
src/engine/platform/sound/x1_010/x1_010.cpp
Normal file
224
src/engine/platform/sound/x1_010/x1_010.cpp
Normal file
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holders: cam900
|
||||
Seta/Allumer X1-010 Emulation core
|
||||
|
||||
the chip has 16 voices, all voices can be switchable to Wavetable or PCM sample playback mode.
|
||||
It has also 2 output channels, but no known hardware using this feature for stereo sound.
|
||||
|
||||
Wavetable needs to paired with envelope, it's always enabled and similar as AY PSG's one
|
||||
but its shape is stored at RAM.
|
||||
|
||||
PCM volume is stored by each register.
|
||||
|
||||
Both volume is 4bit per output.
|
||||
|
||||
Everything except PCM sample is stored at paired 8 bit RAM.
|
||||
|
||||
RAM layout (common case: Address bit 12 is swapped when RAM is shared with CPU)
|
||||
|
||||
-----------------------------
|
||||
0000...007f Voice Registers
|
||||
|
||||
0000...0007 Voice 0 Register
|
||||
|
||||
Address Bits Description
|
||||
7654 3210
|
||||
0 x--- ---- Frequency divider*
|
||||
---- -x-- Envelope one-shot mode
|
||||
---- --x- Sound format
|
||||
---- --0- PCM
|
||||
---- --1- Wavetable
|
||||
---- ---x Keyon/off
|
||||
PCM case:
|
||||
1 xxxx xxxx Volume (Each nibble is for each output)
|
||||
|
||||
2 xxxx xxxx Frequency*
|
||||
|
||||
4 xxxx xxxx Start address / 4096
|
||||
|
||||
5 xxxx xxxx 0x100 - (End address / 4096)
|
||||
Wavetable case:
|
||||
1 ---x xxxx Wavetable data select
|
||||
|
||||
2 xxxx xxxx Frequency LSB*
|
||||
3 xxxx xxxx "" MSB
|
||||
|
||||
4 xxxx xxxx Envelope period (.10 fixed point, Low 8 bit)
|
||||
|
||||
5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers)
|
||||
|
||||
0008...000f Voice 1 Register
|
||||
...
|
||||
0078...007f Voice 15 Register
|
||||
-----------------------------
|
||||
0080...0fff Envelope shape data (Same as volume; Each nibble is for each output)
|
||||
|
||||
0080...00ff Envelope shape data 1
|
||||
0100...017f Envelope shape data 2
|
||||
...
|
||||
0f80...0fff Envelope shape data 31
|
||||
-----------------------------
|
||||
1000...1fff Wavetable data
|
||||
|
||||
1000...107f Wavetable data 0
|
||||
1080...10ff Wavetable data 1
|
||||
...
|
||||
1f80...1fff Wavetable data 31
|
||||
-----------------------------
|
||||
|
||||
* Frequency is 4.4 fixed point for PCM,
|
||||
6.10 for Wavetable.
|
||||
Frequency divider is higher precision or just right shift?
|
||||
needs verification.
|
||||
*/
|
||||
|
||||
#include "x1_010.hpp"
|
||||
|
||||
void x1_010_core::tick()
|
||||
{
|
||||
// reset output
|
||||
m_out[0] = m_out[1] = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
voice_t &v = m_voice[i];
|
||||
v.tick();
|
||||
m_out[0] += v.data * v.vol_out[0];
|
||||
m_out[1] += v.data * v.vol_out[1];
|
||||
}
|
||||
}
|
||||
|
||||
void x1_010_core::voice_t::tick()
|
||||
{
|
||||
data = vol_out[0] = vol_out[1] = 0;
|
||||
if (flag.keyon)
|
||||
{
|
||||
if (flag.wavetable) // Wavetable
|
||||
{
|
||||
// envelope, each nibble is for each output
|
||||
u8 vol = m_host.m_envelope[(bitfield(end_envshape, 0, 5) << 7) | bitfield(env_acc, 10, 7)];
|
||||
vol_out[0] = bitfield(vol, 4, 4);
|
||||
vol_out[1] = bitfield(vol, 0, 4);
|
||||
env_acc += start_envfreq;
|
||||
if (flag.env_oneshot && bitfield(env_acc, 17))
|
||||
flag.keyon = false;
|
||||
else
|
||||
env_acc = bitfield(env_acc, 0, 17);
|
||||
// get wavetable data
|
||||
data = m_host.m_wave[(bitfield(vol_wave, 0, 5) << 7) | bitfield(acc, 10, 7)];
|
||||
acc = bitfield(acc + (freq >> flag.div), 0, 17);
|
||||
}
|
||||
else // PCM sample
|
||||
{
|
||||
// volume register, each nibble is for each output
|
||||
vol_out[0] = bitfield(vol_wave, 4, 4);
|
||||
vol_out[1] = bitfield(vol_wave, 0, 4);
|
||||
// get PCM sample
|
||||
data = m_host.m_intf.read_byte(bitfield(acc, 4, 20));
|
||||
acc += bitfield(freq, 0, 8) >> flag.div;
|
||||
if ((acc >> 16) > (0xff ^ end_envshape))
|
||||
flag.keyon = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 x1_010_core::ram_r(u16 offset)
|
||||
{
|
||||
if (offset & 0x1000) // wavetable data
|
||||
return m_wave[offset & 0xfff];
|
||||
else if (offset & 0xf80) // envelope shape data
|
||||
return m_envelope[offset & 0xfff];
|
||||
else // channel register
|
||||
return m_voice[bitfield(offset, 3, 4)].reg_r(offset & 0x7);
|
||||
}
|
||||
|
||||
void x1_010_core::ram_w(u16 offset, u8 data)
|
||||
{
|
||||
if (offset & 0x1000) // wavetable data
|
||||
m_wave[offset & 0xfff] = data;
|
||||
else if (offset & 0xf80) // envelope shape data
|
||||
m_envelope[offset & 0xfff] = data;
|
||||
else // channel register
|
||||
m_voice[bitfield(offset, 3, 4)].reg_w(offset & 0x7, data);
|
||||
}
|
||||
|
||||
u8 x1_010_core::voice_t::reg_r(u8 offset)
|
||||
{
|
||||
switch (offset & 0x7)
|
||||
{
|
||||
case 0x00: return (flag.div << 7)
|
||||
| (flag.env_oneshot << 2)
|
||||
| (flag.wavetable << 1)
|
||||
| (flag.keyon << 0);
|
||||
case 0x01: return vol_wave;
|
||||
case 0x02: return bitfield(freq, 0, 8);
|
||||
case 0x03: return bitfield(freq, 8, 8);
|
||||
case 0x04: return start_envfreq;
|
||||
case 0x05: return end_envshape;
|
||||
default: break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void x1_010_core::voice_t::reg_w(u8 offset, u8 data)
|
||||
{
|
||||
switch (offset & 0x7)
|
||||
{
|
||||
case 0x00:
|
||||
{
|
||||
const bool prev_keyon = flag.keyon;
|
||||
flag.div = bitfield(data, 7);
|
||||
flag.env_oneshot = bitfield(data, 2);
|
||||
flag.wavetable = bitfield(data, 1);
|
||||
flag.keyon = bitfield(data, 0);
|
||||
if (!prev_keyon && flag.keyon) // Key on
|
||||
{
|
||||
acc = flag.wavetable ? 0 : (u32(start_envfreq) << 16);
|
||||
env_acc = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x01:
|
||||
vol_wave = data;
|
||||
break;
|
||||
case 0x02:
|
||||
freq = (freq & 0xff00) | data;
|
||||
break;
|
||||
case 0x03:
|
||||
freq = (freq & 0x00ff) | (u16(data) << 8);
|
||||
break;
|
||||
case 0x04:
|
||||
start_envfreq = data;
|
||||
break;
|
||||
case 0x05:
|
||||
end_envshape = data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void x1_010_core::voice_t::reset()
|
||||
{
|
||||
flag.reset();
|
||||
vol_wave = 0;
|
||||
freq = 0;
|
||||
start_envfreq = 0;
|
||||
end_envshape = 0;
|
||||
acc = 0;
|
||||
env_acc = 0;
|
||||
data = 0;
|
||||
vol_out[0] = vol_out[1] = 0;
|
||||
}
|
||||
|
||||
void x1_010_core::reset()
|
||||
{
|
||||
for (auto & elem : m_voice)
|
||||
elem.reset();
|
||||
|
||||
std::fill_n(&m_envelope[0], 0x1000, 0);
|
||||
std::fill_n(&m_wave[0], 0x1000, 0);
|
||||
m_out[0] = m_out[1] = 0;
|
||||
}
|
127
src/engine/platform/sound/x1_010/x1_010.hpp
Normal file
127
src/engine/platform/sound/x1_010/x1_010.hpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
License: BSD-3-Clause
|
||||
see https://github.com/cam900/vgsound_emu/LICENSE for more details
|
||||
|
||||
Copyright holders: cam900
|
||||
Seta/Allumer X1-010 Emulation core
|
||||
|
||||
See x1_010.cpp for more info.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#ifndef _VGSOUND_EMU_X1_010_HPP
|
||||
#define _VGSOUND_EMU_X1_010_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef signed char s8;
|
||||
typedef signed int s32;
|
||||
|
||||
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
|
||||
{
|
||||
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
|
||||
}
|
||||
|
||||
class x1_010_mem_intf
|
||||
{
|
||||
public:
|
||||
virtual u8 read_byte(u32 address) { return 0; }
|
||||
};
|
||||
|
||||
class x1_010_core
|
||||
{
|
||||
friend class x1_010_mem_intf;
|
||||
public:
|
||||
// constructor
|
||||
x1_010_core(x1_010_mem_intf &intf)
|
||||
: m_voice{*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,
|
||||
*this,*this,*this,*this,
|
||||
*this,*this,*this,*this}
|
||||
, m_intf(intf)
|
||||
{
|
||||
m_envelope = std::make_unique<u8[]>(0x1000);
|
||||
m_wave = std::make_unique<u8[]>(0x1000);
|
||||
|
||||
std::fill_n(&m_envelope[0], 0x1000, 0);
|
||||
std::fill_n(&m_wave[0], 0x1000, 0);
|
||||
}
|
||||
|
||||
// register accessor
|
||||
u8 ram_r(u16 offset);
|
||||
void ram_w(u16 offset, u8 data);
|
||||
|
||||
// getters
|
||||
s32 output(u8 channel) { return m_out[channel & 1]; }
|
||||
|
||||
// internal state
|
||||
void reset();
|
||||
void tick();
|
||||
|
||||
private:
|
||||
// 16 voices in chip
|
||||
struct voice_t
|
||||
{
|
||||
// constructor
|
||||
voice_t(x1_010_core &host) : m_host(host) {}
|
||||
|
||||
// internal state
|
||||
void reset();
|
||||
void tick();
|
||||
|
||||
// register accessor
|
||||
u8 reg_r(u8 offset);
|
||||
void reg_w(u8 offset, u8 data);
|
||||
|
||||
// registers
|
||||
x1_010_core &m_host;
|
||||
struct flag_t
|
||||
{
|
||||
u8 div : 1;
|
||||
u8 env_oneshot : 1;
|
||||
u8 wavetable : 1;
|
||||
u8 keyon : 1;
|
||||
void reset()
|
||||
{
|
||||
div = 0;
|
||||
env_oneshot = 0;
|
||||
wavetable = 0;
|
||||
keyon = 0;
|
||||
}
|
||||
flag_t()
|
||||
: div(0)
|
||||
, env_oneshot(0)
|
||||
, wavetable(0)
|
||||
, keyon(0)
|
||||
{ }
|
||||
};
|
||||
flag_t flag;
|
||||
u8 vol_wave = 0;
|
||||
u16 freq = 0;
|
||||
u8 start_envfreq = 0;
|
||||
u8 end_envshape = 0;
|
||||
|
||||
// internal registers
|
||||
u32 acc = 0;
|
||||
u32 env_acc = 0;
|
||||
s8 data = 0;
|
||||
u8 vol_out[2] = {0};
|
||||
};
|
||||
voice_t m_voice[16];
|
||||
|
||||
// RAM
|
||||
std::unique_ptr<u8[]> m_envelope = nullptr;
|
||||
std::unique_ptr<u8[]> m_wave = nullptr;
|
||||
|
||||
// output data
|
||||
s32 m_out[2] = {0};
|
||||
|
||||
x1_010_mem_intf &m_intf;
|
||||
};
|
||||
|
||||
#endif
|
884
src/engine/platform/x1_010.cpp
Normal file
884
src/engine/platform/x1_010.cpp
Normal file
|
@ -0,0 +1,884 @@
|
|||
/**
|
||||
* 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 "x1_010.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
//#define rWrite(a,v) pendingWrites[a]=v;
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) { x1_010->ram_w(a,v); if (dumpWrites) { addWrite(a,v); } }
|
||||
|
||||
#define chRead(c,a) x1_010->ram_r((c<<3)|(a&7))
|
||||
#define chWrite(c,a,v) rWrite((c<<3)|(a&7),v)
|
||||
#define waveWrite(c,a,v) rWrite(0x1000|(chan[c].waveBank<<11)|(c<<7)|(a&0x7f),(v-128)&0xff)
|
||||
#define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol)
|
||||
#define envWrite(c,a,l,r) rWrite(0x800|(c<<7)|(a&0x7f),(((chan[c].lvol*(l))/15)<<4)|((chan[c].rvol*(r))/15))
|
||||
|
||||
#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable && chan[c].env.flag.envOneshot)?7:3)):0);
|
||||
|
||||
#define CHIP_FREQBASE 4194304
|
||||
|
||||
const char* regCheatSheetX1_010[]={
|
||||
// Channel registers
|
||||
"Ch00_Control", "0000",
|
||||
"Ch00_PCMVol_WavSel", "0001",
|
||||
"Ch00_FreqL", "0002",
|
||||
"Ch00_FreqH", "0003",
|
||||
"Ch00_Start_EnvFrq", "0004",
|
||||
"Ch00_End_EnvSel", "0005",
|
||||
"Ch01_Control", "0008",
|
||||
"Ch01_PCMVol_WavSel", "0009",
|
||||
"Ch01_FreqL", "000A",
|
||||
"Ch01_FreqH", "000B",
|
||||
"Ch01_Start_EnvFrq", "000C",
|
||||
"Ch01_End_EnvSel", "000D",
|
||||
"Ch02_Control", "0010",
|
||||
"Ch02_PCMVol_WavSel", "0011",
|
||||
"Ch02_FreqL", "0012",
|
||||
"Ch02_FreqH", "0013",
|
||||
"Ch02_Start_EnvFrq", "0014",
|
||||
"Ch02_End_EnvSel", "0015",
|
||||
"Ch03_Control", "0018",
|
||||
"Ch03_PCMVol_WavSel", "0019",
|
||||
"Ch03_FreqL", "001A",
|
||||
"Ch03_FreqH", "001B",
|
||||
"Ch03_Start_EnvFrq", "001C",
|
||||
"Ch03_End_EnvSel", "001D",
|
||||
"Ch04_Control", "0020",
|
||||
"Ch04_PCMVol_WavSel", "0021",
|
||||
"Ch04_FreqL", "0022",
|
||||
"Ch04_FreqH", "0023",
|
||||
"Ch04_Start_EnvFrq", "0024",
|
||||
"Ch04_End_EnvSel", "0025",
|
||||
"Ch05_Control", "0028",
|
||||
"Ch05_PCMVol_WavSel", "0029",
|
||||
"Ch05_FreqL", "002A",
|
||||
"Ch05_FreqH", "002B",
|
||||
"Ch05_Start_EnvFrq", "002C",
|
||||
"Ch05_End_EnvSel", "002D",
|
||||
"Ch06_Control", "0030",
|
||||
"Ch06_PCMVol_WavSel", "0031",
|
||||
"Ch06_FreqL", "0032",
|
||||
"Ch06_FreqH", "0033",
|
||||
"Ch06_Start_EnvFrq", "0034",
|
||||
"Ch06_End_EnvSel", "0035",
|
||||
"Ch07_Control", "0038",
|
||||
"Ch07_PCMVol_WavSel", "0039",
|
||||
"Ch07_FreqL", "003A",
|
||||
"Ch07_FreqH", "003B",
|
||||
"Ch07_Start_EnvFrq", "003C",
|
||||
"Ch07_End_EnvSel", "003D",
|
||||
"Ch08_Control", "0040",
|
||||
"Ch08_PCMVol_WavSel", "0041",
|
||||
"Ch08_FreqL", "0042",
|
||||
"Ch08_FreqH", "0043",
|
||||
"Ch08_Start_EnvFrq", "0044",
|
||||
"Ch08_End_EnvSel", "0045",
|
||||
"Ch09_Control", "0048",
|
||||
"Ch09_PCMVol_WavSel", "0049",
|
||||
"Ch09_FreqL", "004A",
|
||||
"Ch09_FreqH", "004B",
|
||||
"Ch09_Start_EnvFrq", "004C",
|
||||
"Ch09_End_EnvSel", "004D",
|
||||
"Ch10_Control", "0050",
|
||||
"Ch10_PCMVol_WavSel", "0051",
|
||||
"Ch10_FreqL", "0052",
|
||||
"Ch10_FreqH", "0053",
|
||||
"Ch10_Start_EnvFrq", "0054",
|
||||
"Ch10_End_EnvSel", "0055",
|
||||
"Ch11_Control", "0058",
|
||||
"Ch11_PCMVol_WavSel", "0059",
|
||||
"Ch11_FreqL", "005A",
|
||||
"Ch11_FreqH", "005B",
|
||||
"Ch11_Start_EnvFrq", "005C",
|
||||
"Ch11_End_EnvSel", "005D",
|
||||
"Ch12_Control", "0060",
|
||||
"Ch12_PCMVol_WavSel", "0061",
|
||||
"Ch12_FreqL", "0062",
|
||||
"Ch12_FreqH", "0063",
|
||||
"Ch12_Start_EnvFrq", "0064",
|
||||
"Ch12_End_EnvSel", "0065",
|
||||
"Ch13_Control", "0068",
|
||||
"Ch13_PCMVol_WavSel", "0069",
|
||||
"Ch13_FreqL", "006A",
|
||||
"Ch13_FreqH", "006B",
|
||||
"Ch13_Start_EnvFrq", "006C",
|
||||
"Ch13_End_EnvSel", "006D",
|
||||
"Ch14_Control", "0070",
|
||||
"Ch14_PCMVol_WavSel", "0071",
|
||||
"Ch14_FreqL", "0072",
|
||||
"Ch14_FreqH", "0073",
|
||||
"Ch14_Start_EnvFrq", "0074",
|
||||
"Ch14_End_EnvSel", "0075",
|
||||
"Ch15_Control", "0078",
|
||||
"Ch15_PCMVol_WavSel", "0079",
|
||||
"Ch15_FreqL", "007A",
|
||||
"Ch15_FreqH", "007B",
|
||||
"Ch15_Start_EnvFrq", "007C",
|
||||
"Ch15_End_EnvSel", "007D",
|
||||
// Envelope data
|
||||
"Env01Data", "0080",
|
||||
"Env02Data", "0100",
|
||||
"Env03Data", "0180",
|
||||
"Env04Data", "0200",
|
||||
"Env05Data", "0280",
|
||||
"Env06Data", "0300",
|
||||
"Env07Data", "0380",
|
||||
"Env08Data", "0400",
|
||||
"Env09Data", "0480",
|
||||
"Env10Data", "0500",
|
||||
"Env11Data", "0580",
|
||||
"Env12Data", "0600",
|
||||
"Env13Data", "0680",
|
||||
"Env14Data", "0700",
|
||||
"Env15Data", "0780",
|
||||
"Env16Data", "0800",
|
||||
"Env17Data", "0880",
|
||||
"Env18Data", "0900",
|
||||
"Env19Data", "0980",
|
||||
"Env20Data", "0A00",
|
||||
"Env21Data", "0A80",
|
||||
"Env22Data", "0B00",
|
||||
"Env23Data", "0B80",
|
||||
"Env24Data", "0C00",
|
||||
"Env25Data", "0C80",
|
||||
"Env26Data", "0D00",
|
||||
"Env27Data", "0D80",
|
||||
"Env28Data", "0E00",
|
||||
"Env29Data", "0E80",
|
||||
"Env30Data", "0F00",
|
||||
"Env31Data", "0F80",
|
||||
// Wavetable data
|
||||
"Wave00Data", "1000",
|
||||
"Wave01Data", "1080",
|
||||
"Wave02Data", "1100",
|
||||
"Wave03Data", "1180",
|
||||
"Wave04Data", "1200",
|
||||
"Wave05Data", "1280",
|
||||
"Wave06Data", "1300",
|
||||
"Wave07Data", "1380",
|
||||
"Wave08Data", "1400",
|
||||
"Wave09Data", "1480",
|
||||
"Wave10Data", "1500",
|
||||
"Wave11Data", "1580",
|
||||
"Wave12Data", "1600",
|
||||
"Wave13Data", "1680",
|
||||
"Wave14Data", "1700",
|
||||
"Wave15Data", "1780",
|
||||
"Wave16Data", "1800",
|
||||
"Wave17Data", "1880",
|
||||
"Wave18Data", "1900",
|
||||
"Wave19Data", "1980",
|
||||
"Wave20Data", "1A00",
|
||||
"Wave21Data", "1A80",
|
||||
"Wave22Data", "1B00",
|
||||
"Wave23Data", "1B80",
|
||||
"Wave24Data", "1C00",
|
||||
"Wave25Data", "1C80",
|
||||
"Wave26Data", "1D00",
|
||||
"Wave27Data", "1D80",
|
||||
"Wave28Data", "1E00",
|
||||
"Wave29Data", "1E80",
|
||||
"Wave30Data", "1F00",
|
||||
"Wave31Data", "1F80",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformX1_010::getRegisterSheet() {
|
||||
return regCheatSheetX1_010;
|
||||
}
|
||||
|
||||
const char* DivPlatformX1_010::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Change envelope shape";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Toggle PCM mode";
|
||||
break;
|
||||
case 0x20:
|
||||
return "20xx: Set PCM frequency (1 to FF)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3/5: H.invert right/left, bit 4/6: V.invert right/left)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "23xx: Set envelope period";
|
||||
break;
|
||||
case 0x25:
|
||||
return "25xx: Envelope slide up";
|
||||
break;
|
||||
case 0x26:
|
||||
return "26xx: Envelope slide down";
|
||||
break;
|
||||
case 0x29:
|
||||
return "29xy: Set auto-envelope (x: numerator; y: denominator)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
x1_010->tick();
|
||||
|
||||
signed int tempL=x1_010->output(0);
|
||||
signed int tempR=x1_010->output(1);
|
||||
|
||||
if (tempL<-32768) tempL=-32768;
|
||||
if (tempL>32767) tempL=32767;
|
||||
if (tempR<-32768) tempR=-32768;
|
||||
if (tempR>32767) tempR=32767;
|
||||
|
||||
//printf("tempL: %d tempR: %d\n",tempL,tempR);
|
||||
bufL[h]=stereo?tempL:((tempL+tempR)>>1);
|
||||
bufR[h]=stereo?tempR:bufL[h];
|
||||
}
|
||||
}
|
||||
|
||||
double DivPlatformX1_010::NoteX1_010(int ch, int note) {
|
||||
if (chan[ch].pcm) { // PCM note
|
||||
double off=1.0;
|
||||
int sample=chan[ch].sample;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=s->centerRate/8363.0;
|
||||
}
|
||||
}
|
||||
return off*parent->calcBaseFreq(chipClock,8192,note,false);
|
||||
}
|
||||
// Wavetable note
|
||||
return NOTE_FREQUENCY(note);
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::updateWave(int ch) {
|
||||
DivWavetable* wt=parent->getWave(chan[ch].wave);
|
||||
if (chan[ch].active) {
|
||||
chan[ch].waveBank ^= 1;
|
||||
}
|
||||
for (int i=0; i<128; i++) {
|
||||
if (wt->max<1 || wt->len<1) {
|
||||
waveWrite(ch,i,0);
|
||||
} else {
|
||||
waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max);
|
||||
}
|
||||
}
|
||||
if (!chan[ch].pcm) {
|
||||
chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf));
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::updateEnvelope(int ch) {
|
||||
if (!chan[ch].pcm) {
|
||||
if (isMuted[ch]) {
|
||||
for (int i=0; i<128; i++) {
|
||||
rWrite(0x800|(ch<<7)|(i&0x7f),0);
|
||||
}
|
||||
} else {
|
||||
if (!chan[ch].env.flag.envEnable) {
|
||||
for (int i=0; i<128; i++) {
|
||||
envFill(ch,i);
|
||||
}
|
||||
} else {
|
||||
DivWavetable* wt=parent->getWave(chan[ch].env.shape);
|
||||
for (int i=0; i<128; i++) {
|
||||
if (wt->max<1 || wt->len<1) {
|
||||
envFill(ch,i);
|
||||
} else if (chan[ch].env.flag.envSplit || chan[ch].env.flag.envHinvR || chan[ch].env.flag.envVinvR || chan[ch].env.flag.envHinvL || chan[ch].env.flag.envVinvL) { // Stereo config
|
||||
int la=i,ra=i;
|
||||
int lo,ro;
|
||||
if (chan[ch].env.flag.envHinvR) { ra=127-i; } // horizontal invert right envelope
|
||||
if (chan[ch].env.flag.envHinvL) { la=127-i; } // horizontal invert left envelope
|
||||
if (chan[ch].env.flag.envSplit) { // Split shape to left and right half
|
||||
lo=wt->data[la*(wt->len/128/2)]*15/wt->max;
|
||||
ro=wt->data[(ra+128)*(wt->len/128/2)]*15/wt->max;
|
||||
} else {
|
||||
lo=wt->data[la*wt->len/128]*15/wt->max;
|
||||
ro=wt->data[ra*wt->len/128]*15/wt->max;
|
||||
}
|
||||
if (chan[ch].env.flag.envVinvR) { ro=15-ro; } // vertical invert right envelope
|
||||
if (chan[ch].env.flag.envVinvL) { lo=15-lo; } // vertical invert left envelope
|
||||
envWrite(ch,i,lo,ro);
|
||||
} else {
|
||||
int out=wt->data[i*wt->len/128]*15/wt->max;
|
||||
envWrite(ch,i,out,out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
chWrite(ch,5,0x10|(ch&0xf));
|
||||
} else {
|
||||
chWrite(ch,1,(chan[ch].lvol<<4)|chan[ch].rvol);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::tick() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.hadVol) {
|
||||
signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol))/(chan[i].furnacePCM?64:15);
|
||||
if ((!isMuted[i]) && (macroVol!=chan[i].outVol)) {
|
||||
chan[i].outVol=macroVol;
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
if ((!chan[i].pcm) || chan[i].furnacePCM) {
|
||||
if (chan[i].std.hadArp) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arpMode) {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp);
|
||||
} else {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
|
||||
chan[i].baseFreq=NoteX1_010(i,chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadWave && !chan[i].pcm) {
|
||||
if (chan[i].wave!=chan[i].std.wave) {
|
||||
chan[i].wave=chan[i].std.wave;
|
||||
if (!chan[i].pcm) {
|
||||
updateWave(i);
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadEx1) {
|
||||
bool nextEnable=(chan[i].std.ex1&1);
|
||||
if (nextEnable!=(chan[i].env.flag.envEnable)) {
|
||||
chan[i].env.flag.envEnable=nextEnable;
|
||||
if (!chan[i].pcm) {
|
||||
if (!isMuted[i]) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
refreshControl(i);
|
||||
}
|
||||
}
|
||||
bool nextOneshot=(chan[i].std.ex1&2);
|
||||
if (nextOneshot!=(chan[i].env.flag.envOneshot)) {
|
||||
chan[i].env.flag.envOneshot=nextOneshot;
|
||||
if (!chan[i].pcm) {
|
||||
refreshControl(i);
|
||||
}
|
||||
}
|
||||
bool nextSplit=(chan[i].std.ex1&4);
|
||||
if (nextSplit!=(chan[i].env.flag.envSplit)) {
|
||||
chan[i].env.flag.envSplit=nextSplit;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvR=(chan[i].std.ex1&8);
|
||||
if (nextHinvR!=(chan[i].env.flag.envHinvR)) {
|
||||
chan[i].env.flag.envHinvR=nextHinvR;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvR=(chan[i].std.ex1&16);
|
||||
if (nextVinvR!=(chan[i].env.flag.envVinvR)) {
|
||||
chan[i].env.flag.envVinvR=nextVinvR;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvL=(chan[i].std.ex1&32);
|
||||
if (nextHinvL!=(chan[i].env.flag.envHinvL)) {
|
||||
chan[i].env.flag.envHinvL=nextHinvL;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvL=(chan[i].std.ex1&64);
|
||||
if (nextVinvL!=(chan[i].env.flag.envVinvL)) {
|
||||
chan[i].env.flag.envVinvL=nextVinvL;
|
||||
if (!isMuted[i] && !chan[i].pcm) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadEx2) {
|
||||
if (chan[i].env.shape!=chan[i].std.ex2) {
|
||||
chan[i].env.shape=chan[i].std.ex2;
|
||||
if (!chan[i].pcm) {
|
||||
if (chan[i].env.flag.envEnable && (!isMuted[i])) {
|
||||
chan[i].envChanged=true;
|
||||
}
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadEx3) {
|
||||
chan[i].autoEnvNum=chan[i].std.ex3;
|
||||
if (!chan[i].pcm) {
|
||||
chan[i].freqChanged=true;
|
||||
if (!chan[i].std.willAlg) chan[i].autoEnvDen=1;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.hadAlg) {
|
||||
chan[i].autoEnvDen=chan[i].std.alg;
|
||||
if (!chan[i].pcm) {
|
||||
chan[i].freqChanged=true;
|
||||
if (!chan[i].std.willEx3) chan[i].autoEnvNum=1;
|
||||
}
|
||||
}
|
||||
if (chan[i].envChanged) {
|
||||
if (!isMuted[i]) {
|
||||
chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15;
|
||||
chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15;
|
||||
}
|
||||
updateEnvelope(i);
|
||||
chan[i].envChanged=false;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false);
|
||||
if (chan[i].pcm) {
|
||||
if (chan[i].freq<1) chan[i].freq=1;
|
||||
if (chan[i].freq>255) chan[i].freq=255;
|
||||
chWrite(i,2,chan[i].freq&0xff);
|
||||
} else {
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
chWrite(i,2,chan[i].freq&0xff);
|
||||
chWrite(i,3,(chan[i].freq>>8)&0xff);
|
||||
if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) {
|
||||
chan[i].env.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>12;
|
||||
chWrite(i,4,chan[i].env.period);
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOn || chan[i].keyOff || (chRead(i,0)&1)) {
|
||||
refreshControl(i);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].env.slide!=0) {
|
||||
chan[i].env.slidefrac+=chan[i].env.slide;
|
||||
while (chan[i].env.slidefrac>0xf) {
|
||||
chan[i].env.slidefrac-=0x10;
|
||||
if (chan[i].env.period<0xff) {
|
||||
chan[i].env.period++;
|
||||
if (!chan[i].pcm) {
|
||||
chWrite(i,4,chan[i].env.period);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (chan[i].env.slidefrac<-0xf) {
|
||||
chan[i].env.slidefrac+=0x10;
|
||||
if (chan[i].env.period>0) {
|
||||
chan[i].env.period--;
|
||||
if (!chan[i].pcm) {
|
||||
chWrite(i,4,chan[i].env.period);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformX1_010::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
chWrite(c.chan,0,0); // reset previous note
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
|
||||
if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) {
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
chan[c.chan].furnacePCM=true;
|
||||
} else {
|
||||
chan[c.chan].furnacePCM=false;
|
||||
}
|
||||
if (skipRegisterWrites) break;
|
||||
if (chan[c.chan].furnacePCM) {
|
||||
chan[c.chan].pcm=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
chan[c.chan].sample=ins->amiga.initSample;
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
|
||||
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
|
||||
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
|
||||
chWrite(c.chan,0,0); // reset
|
||||
chWrite(c.chan,1,0);
|
||||
chWrite(c.chan,2,0);
|
||||
chWrite(c.chan,4,0);
|
||||
chWrite(c.chan,5,0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].std.init(NULL);
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) {
|
||||
chWrite(c.chan,0,0); // reset
|
||||
chWrite(c.chan,1,0);
|
||||
chWrite(c.chan,2,0);
|
||||
chWrite(c.chan,4,0);
|
||||
chWrite(c.chan,5,0);
|
||||
break;
|
||||
}
|
||||
DivSample* s=parent->getSample(12*sampleBank+c.value%12);
|
||||
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
|
||||
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
|
||||
chWrite(c.chan,5,(0x100-(end>>12))&0xff);
|
||||
chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
} else if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].envChanged=true;
|
||||
chan[c.chan].std.init(ins);
|
||||
refreshControl(c.chan);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].pcm=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].std.init(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.hasVol) {
|
||||
if (chan[c.chan].outVol!=c.value) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.hasVol) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
chan[c.chan].wave=c.value;
|
||||
updateWave(c.chan);
|
||||
chan[c.chan].keyOn=true;
|
||||
break;
|
||||
case DIV_CMD_X1_010_ENVELOPE_SHAPE:
|
||||
if (chan[c.chan].env.shape!=c.value) {
|
||||
chan[c.chan].env.shape=c.value;
|
||||
if (!chan[c.chan].pcm) {
|
||||
if (chan[c.chan].env.flag.envEnable && (!isMuted[c.chan])) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NoteX1_010(c.chan,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_SAMPLE_MODE:
|
||||
if (chan[c.chan].pcm!=(c.value&1)) {
|
||||
chan[c.chan].pcm=c.value&1;
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (chan[c.chan].pan!=c.value) {
|
||||
chan[c.chan].pan=c.value;
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp&&!chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
if (chan[c.chan].pcm) {
|
||||
chan[c.chan].freq=MAX(1,c.value&0xff);
|
||||
chWrite(c.chan,2,chan[c.chan].freq&0xff);
|
||||
if (chRead(c.chan,0)&1) {
|
||||
refreshControl(c.chan);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_X1_010_ENVELOPE_MODE: {
|
||||
bool nextEnable=c.value&1;
|
||||
if (nextEnable!=(chan[c.chan].env.flag.envEnable)) {
|
||||
chan[c.chan].env.flag.envEnable=nextEnable;
|
||||
if (!chan[c.chan].pcm) {
|
||||
if (!isMuted[c.chan]) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
refreshControl(c.chan);
|
||||
}
|
||||
}
|
||||
bool nextOneshot=c.value&2;
|
||||
if (nextOneshot!=(chan[c.chan].env.flag.envOneshot)) {
|
||||
chan[c.chan].env.flag.envOneshot=nextOneshot;
|
||||
if (!chan[c.chan].pcm) {
|
||||
refreshControl(c.chan);
|
||||
}
|
||||
}
|
||||
bool nextSplit=c.value&4;
|
||||
if (nextSplit!=(chan[c.chan].env.flag.envSplit)) {
|
||||
chan[c.chan].env.flag.envSplit=nextSplit;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvR=c.value&8;
|
||||
if (nextHinvR!=(chan[c.chan].env.flag.envHinvR)) {
|
||||
chan[c.chan].env.flag.envHinvR=nextHinvR;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvR=c.value&16;
|
||||
if (nextVinvR!=(chan[c.chan].env.flag.envVinvR)) {
|
||||
chan[c.chan].env.flag.envVinvR=nextVinvR;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextHinvL=c.value&32;
|
||||
if (nextHinvL!=(chan[c.chan].env.flag.envHinvL)) {
|
||||
chan[c.chan].env.flag.envHinvL=nextHinvL;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
bool nextVinvL=c.value&64;
|
||||
if (nextVinvL!=(chan[c.chan].env.flag.envVinvL)) {
|
||||
chan[c.chan].env.flag.envVinvL=nextVinvL;
|
||||
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
|
||||
chan[c.chan].envChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_X1_010_ENVELOPE_PERIOD:
|
||||
chan[c.chan].env.period=c.value;
|
||||
if (!chan[c.chan].pcm) {
|
||||
chWrite(c.chan,4,chan[c.chan].env.period);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_X1_010_ENVELOPE_SLIDE:
|
||||
chan[c.chan].env.slide=c.value;
|
||||
break;
|
||||
case DIV_CMD_X1_010_AUTO_ENVELOPE:
|
||||
chan[c.chan].autoEnvNum=c.value>>4;
|
||||
chan[c.chan].autoEnvDen=c.value&15;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 15;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
chan[ch].envChanged=true;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::forceIns() {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].envChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformX1_010::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformX1_010::getRegisterPool() {
|
||||
for (int i=0; i<0x2000; i++) {
|
||||
regPool[i]=x1_010->ram_r(i);
|
||||
}
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformX1_010::getRegisterPoolSize() {
|
||||
return 0x2000;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::reset() {
|
||||
memset(regPool,0,0x2000);
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i]=DivPlatformX1_010::Channel();
|
||||
chan[i].reset();
|
||||
}
|
||||
x1_010->reset();
|
||||
sampleBank=0;
|
||||
// set per-channel initial panning
|
||||
for (int i=0; i<16; i++) {
|
||||
chWrite(i,0,0);
|
||||
}
|
||||
}
|
||||
|
||||
bool DivPlatformX1_010::isStereo() {
|
||||
return stereo;
|
||||
}
|
||||
|
||||
bool DivPlatformX1_010::keyOffAffectsArp(int ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::notifyWaveChange(int wave) {
|
||||
for (int i=0; i<16; i++) {
|
||||
if (chan[i].wave==wave) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<16; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::setFlags(unsigned int flags) {
|
||||
switch (flags&15) {
|
||||
case 0: // 16MHz (earlier hardwares)
|
||||
chipClock=16000000;
|
||||
break;
|
||||
case 1: // 16.67MHz (later hardwares)
|
||||
chipClock=50000000.0/3.0;
|
||||
break;
|
||||
// Other clock is used?
|
||||
}
|
||||
rate=chipClock/512;
|
||||
stereo=flags&16;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
stereo=false;
|
||||
for (int i=0; i<16; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
setFlags(flags);
|
||||
intf.parent=parent;
|
||||
x1_010=new x1_010_core(intf);
|
||||
x1_010->reset();
|
||||
reset();
|
||||
return 16;
|
||||
}
|
||||
|
||||
void DivPlatformX1_010::quit() {
|
||||
delete x1_010;
|
||||
}
|
||||
|
||||
DivPlatformX1_010::~DivPlatformX1_010() {
|
||||
}
|
144
src/engine/platform/x1_010.h
Normal file
144
src/engine/platform/x1_010.h
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* 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 _X1_010_H
|
||||
#define _X1_010_H
|
||||
|
||||
#include <queue>
|
||||
#include "../dispatch.h"
|
||||
#include "../engine.h"
|
||||
#include "../macroInt.h"
|
||||
#include "sound/x1_010/x1_010.hpp"
|
||||
|
||||
class DivX1_010Interface: public x1_010_mem_intf {
|
||||
public:
|
||||
DivEngine* parent;
|
||||
int sampleBank;
|
||||
virtual u8 read_byte(u32 address) override {
|
||||
if (parent->x1_010Mem==NULL) return 0;
|
||||
return parent->x1_010Mem[address & 0xfffff];
|
||||
}
|
||||
DivX1_010Interface(): parent(NULL), sampleBank(0) {}
|
||||
};
|
||||
|
||||
class DivPlatformX1_010: public DivDispatch {
|
||||
struct Channel {
|
||||
struct Envelope {
|
||||
struct EnvFlag {
|
||||
unsigned char envEnable : 1;
|
||||
unsigned char envOneshot : 1;
|
||||
unsigned char envSplit : 1;
|
||||
unsigned char envHinvR : 1;
|
||||
unsigned char envVinvR : 1;
|
||||
unsigned char envHinvL : 1;
|
||||
unsigned char envVinvL : 1;
|
||||
void reset() {
|
||||
envEnable=0;
|
||||
envOneshot=0;
|
||||
envSplit=0;
|
||||
envHinvR=0;
|
||||
envVinvR=0;
|
||||
envHinvL=0;
|
||||
envVinvL=0;
|
||||
}
|
||||
EnvFlag():
|
||||
envEnable(0),
|
||||
envOneshot(0),
|
||||
envSplit(0),
|
||||
envHinvR(0),
|
||||
envVinvR(0),
|
||||
envHinvL(0),
|
||||
envVinvL(0) {}
|
||||
};
|
||||
int shape, period, slide, slidefrac;
|
||||
EnvFlag flag;
|
||||
void reset() {
|
||||
shape=-1;
|
||||
period=0;
|
||||
flag.reset();
|
||||
}
|
||||
Envelope():
|
||||
shape(-1),
|
||||
period(0),
|
||||
slide(0),
|
||||
slidefrac(0) {}
|
||||
};
|
||||
int freq, baseFreq, pitch, note;
|
||||
int wave, sample, ins;
|
||||
unsigned char pan, autoEnvNum, autoEnvDen;
|
||||
bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm;
|
||||
int vol, outVol, lvol, rvol;
|
||||
unsigned char waveBank;
|
||||
Envelope env;
|
||||
DivMacroInt std;
|
||||
void reset() {
|
||||
freq = baseFreq = pitch = note = 0;
|
||||
wave = sample = ins = -1;
|
||||
pan = 255;
|
||||
autoEnvNum = autoEnvDen = 0;
|
||||
active = false;
|
||||
insChanged = envChanged = freqChanged = true;
|
||||
keyOn = keyOff = inPorta = furnacePCM = pcm = false;
|
||||
vol = outVol = lvol = rvol = 15;
|
||||
waveBank = 0;
|
||||
}
|
||||
Channel():
|
||||
freq(0), baseFreq(0), pitch(0), note(0),
|
||||
wave(-1), sample(-1), ins(-1),
|
||||
pan(255), autoEnvNum(0), autoEnvDen(0),
|
||||
active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false),
|
||||
vol(15), outVol(15), lvol(15), rvol(15),
|
||||
waveBank(0) {}
|
||||
};
|
||||
Channel chan[16];
|
||||
bool isMuted[16];
|
||||
bool stereo=false;
|
||||
unsigned char sampleBank;
|
||||
DivX1_010Interface intf;
|
||||
x1_010_core* x1_010;
|
||||
unsigned char regPool[0x2000];
|
||||
double NoteX1_010(int ch, int note);
|
||||
void updateWave(int ch);
|
||||
void updateEnvelope(int ch);
|
||||
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);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick();
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setFlags(unsigned int flags);
|
||||
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);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformX1_010();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -117,6 +117,16 @@ const char* cmdName[DIV_CMD_MAX]={
|
|||
"QSOUND_ECHO_DELAY",
|
||||
"QSOUND_ECHO_LEVEL",
|
||||
|
||||
"X1_010_ENVELOPE_SHAPE",
|
||||
"X1_010_ENVELOPE_ENABLE",
|
||||
"X1_010_ENVELOPE_MODE",
|
||||
"X1_010_ENVELOPE_PERIOD",
|
||||
"X1_010_ENVELOPE_SLIDE",
|
||||
"X1_010_AUTO_ENVELOPE",
|
||||
|
||||
"WS_SWEEP_TIME",
|
||||
"WS_SWEEP_AMOUNT",
|
||||
|
||||
"ALWAYS_SET_VOLUME"
|
||||
};
|
||||
|
||||
|
@ -251,6 +261,21 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
switch (effect) {
|
||||
case 0x10: // select waveform
|
||||
dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal));
|
||||
break;
|
||||
case 0x11: // select envelope shape
|
||||
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SHAPE,ch,effectVal));
|
||||
break;
|
||||
case 0x17: // PCM enable
|
||||
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0)));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_SWAN:
|
||||
switch (effect) {
|
||||
case 0x10: // select waveform
|
||||
|
@ -567,6 +592,30 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char
|
|||
}
|
||||
return false;
|
||||
break;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
switch (effect) {
|
||||
case 0x20: // PCM frequency
|
||||
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal));
|
||||
break;
|
||||
case 0x22: // envelope mode
|
||||
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_MODE,ch,effectVal));
|
||||
break;
|
||||
case 0x23: // envelope period
|
||||
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_PERIOD,ch,effectVal));
|
||||
break;
|
||||
case 0x25: // envelope slide up
|
||||
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,effectVal));
|
||||
break;
|
||||
case 0x26: // envelope slide down
|
||||
dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,-effectVal));
|
||||
break;
|
||||
case 0x29: // auto-envelope
|
||||
dispatchCmd(DivCommand(DIV_CMD_X1_010_AUTO_ENVELOPE,ch,effectVal));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -115,8 +115,9 @@ bool DivSample::initInternal(unsigned char d, int count) {
|
|||
case 8: // 8-bit
|
||||
if (data8!=NULL) delete[] data8;
|
||||
length8=count;
|
||||
data8=new signed char[length8];
|
||||
memset(data8,0,length8);
|
||||
// for padding X1-010 sample
|
||||
data8=new signed char[(count+4095)&(~0xfff)];
|
||||
memset(data8,0,(count+4095)&(~0xfff));
|
||||
break;
|
||||
case 9: // BRR
|
||||
if (dataBRR!=NULL) delete[] dataBRR;
|
||||
|
|
|
@ -49,7 +49,7 @@ struct DivSample {
|
|||
|
||||
unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
|
||||
unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX;
|
||||
unsigned int offSegaPCM, offQSound;
|
||||
unsigned int offSegaPCM, offQSound, offX1_010;
|
||||
|
||||
unsigned int samples;
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ enum DivSystem {
|
|||
DIV_SYSTEM_VERA,
|
||||
DIV_SYSTEM_YM2610B_EXT,
|
||||
DIV_SYSTEM_SEGAPCM_COMPAT,
|
||||
DIV_SYSTEM_X1_010
|
||||
};
|
||||
|
||||
struct DivSong {
|
||||
|
@ -242,6 +243,13 @@ struct DivSong {
|
|||
// - 2: YM2423
|
||||
// - 3: VRC7
|
||||
// - 4: custom (TODO)
|
||||
// - X1-010:
|
||||
// - bit 0-3: clock rate
|
||||
// - 0: 16MHz (Seta 1)
|
||||
// - 1: 16.67MHz (Seta 2)
|
||||
// - bit 4: stereo
|
||||
// - 0: mono
|
||||
// - 1: stereo
|
||||
unsigned int systemFlags[32];
|
||||
|
||||
// song information
|
||||
|
|
|
@ -137,6 +137,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) {
|
|||
return DIV_SYSTEM_SEGAPCM_COMPAT;
|
||||
case 0xac:
|
||||
return DIV_SYSTEM_VERA;
|
||||
case 0xb0:
|
||||
return DIV_SYSTEM_X1_010;
|
||||
case 0xde:
|
||||
return DIV_SYSTEM_YM2610B_EXT;
|
||||
case 0xe0:
|
||||
|
@ -262,6 +264,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) {
|
|||
return 0xa9;
|
||||
case DIV_SYSTEM_VERA:
|
||||
return 0xac;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return 0xb0;
|
||||
case DIV_SYSTEM_YM2610B_EXT:
|
||||
return 0xde;
|
||||
case DIV_SYSTEM_QSOUND:
|
||||
|
@ -339,7 +343,7 @@ int DivEngine::getChannelCount(DivSystem sys) {
|
|||
case DIV_SYSTEM_OPL3:
|
||||
return 18;
|
||||
case DIV_SYSTEM_MULTIPCM:
|
||||
return 24;
|
||||
return 28;
|
||||
case DIV_SYSTEM_PCSPKR:
|
||||
return 1;
|
||||
case DIV_SYSTEM_POKEY:
|
||||
|
@ -355,6 +359,7 @@ int DivEngine::getChannelCount(DivSystem sys) {
|
|||
case DIV_SYSTEM_POKEMINI:
|
||||
return 1;
|
||||
case DIV_SYSTEM_SEGAPCM:
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return 16;
|
||||
case DIV_SYSTEM_VBOY:
|
||||
return 6;
|
||||
|
@ -662,6 +667,8 @@ const char* DivEngine::getSystemName(DivSystem sys) {
|
|||
return "Capcom QSound";
|
||||
case DIV_SYSTEM_VERA:
|
||||
return "VERA";
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return "Seta/Allumer X1-010";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
@ -789,6 +796,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) {
|
|||
return "Capcom DL-1425";
|
||||
case DIV_SYSTEM_VERA:
|
||||
return "VERA";
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return "Seta/Allumer X1-010";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
@ -871,8 +880,8 @@ bool DivEngine::isSTDSystem(DivSystem sys) {
|
|||
sys!=DIV_SYSTEM_YM2151);
|
||||
}
|
||||
|
||||
const char* chanNames[38][24]={
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM/VERA
|
||||
const char* chanNames[38][32]={
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis
|
||||
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3)
|
||||
{"Square 1", "Square 2", "Square 3", "Noise"}, // SMS
|
||||
|
@ -900,7 +909,7 @@ const char* chanNames[38][24]={
|
|||
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24"}, // MultiPCM
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM
|
||||
{"Square"}, // PC Speaker/Pokémon Mini
|
||||
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC
|
||||
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B
|
||||
|
@ -912,8 +921,8 @@ const char* chanNames[38][24]={
|
|||
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3)
|
||||
};
|
||||
|
||||
const char* chanShortNames[38][24]={
|
||||
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759/VERA
|
||||
const char* chanShortNames[38][32]={
|
||||
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis
|
||||
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3)
|
||||
{"S1", "S2", "S3", "NO"}, // SMS
|
||||
|
@ -941,7 +950,7 @@ const char* chanShortNames[38][24]={
|
|||
{"F1", "F2", "F3", "S1", "S2", "S3"}, // OPN
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, // PC-98
|
||||
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, // OPL3
|
||||
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"}, // MultiPCM
|
||||
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, // MultiPCM
|
||||
{"SQ"}, // PC Speaker/Pokémon Mini
|
||||
{"CH1", "CH2", "CH3", "CH4", "CH5", "NO"}, // Virtual Boy/SCC
|
||||
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B
|
||||
|
@ -953,7 +962,7 @@ const char* chanShortNames[38][24]={
|
|||
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3)
|
||||
};
|
||||
|
||||
const int chanTypes[39][24]={
|
||||
const int chanTypes[40][32]={
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis
|
||||
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3)
|
||||
|
@ -982,7 +991,7 @@ const int chanTypes[39][24]={
|
|||
{0, 0, 0, 1, 1, 1}, // OPN
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound
|
||||
{1}, // PC Speaker/Pokémon Mini
|
||||
{3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B
|
||||
|
@ -993,9 +1002,10 @@ const int chanTypes[39][24]={
|
|||
{3, 3, 3, 3}, //Lynx
|
||||
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3)
|
||||
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA
|
||||
{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, // X1-010
|
||||
};
|
||||
|
||||
const DivInstrumentType chanPrefType[45][24]={
|
||||
const DivInstrumentType chanPrefType[46][28]={
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3)
|
||||
|
@ -1024,7 +1034,7 @@ const DivInstrumentType chanPrefType[45][24]={
|
|||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, // OPN
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // PC-98
|
||||
{DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL/OPL2/OPL3
|
||||
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound
|
||||
{DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound
|
||||
{DIV_INS_STD}, // PC Speaker/Pokémon Mini
|
||||
{DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}, // Virtual Boy
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B
|
||||
|
@ -1041,6 +1051,7 @@ const DivInstrumentType chanPrefType[45][24]={
|
|||
{DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx
|
||||
{DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3)
|
||||
{DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA
|
||||
{DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010
|
||||
};
|
||||
|
||||
const char* DivEngine::getChannelName(int chan) {
|
||||
|
@ -1145,6 +1156,7 @@ const char* DivEngine::getChannelName(int chan) {
|
|||
case DIV_SYSTEM_MULTIPCM:
|
||||
case DIV_SYSTEM_SEGAPCM:
|
||||
case DIV_SYSTEM_SEGAPCM_COMPAT:
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return chanNames[28][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_PCSPKR:
|
||||
|
@ -1287,6 +1299,7 @@ const char* DivEngine::getChannelShortName(int chan) {
|
|||
case DIV_SYSTEM_MULTIPCM:
|
||||
case DIV_SYSTEM_SEGAPCM:
|
||||
case DIV_SYSTEM_SEGAPCM_COMPAT:
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return chanShortNames[28][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_PCSPKR:
|
||||
|
@ -1463,6 +1476,9 @@ int DivEngine::getChannelType(int chan) {
|
|||
case DIV_SYSTEM_VERA:
|
||||
return chanTypes[38][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return chanTypes[39][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -1616,6 +1632,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
|
|||
case DIV_SYSTEM_VERA:
|
||||
return chanPrefType[44][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
return chanPrefType[45][dispatchChanOfChan[chan]];
|
||||
break;
|
||||
}
|
||||
return DIV_INS_FM;
|
||||
}
|
||||
|
@ -1644,6 +1663,7 @@ bool DivEngine::isVGMExportable(DivSystem which) {
|
|||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
case DIV_SYSTEM_X1_010:
|
||||
case DIV_SYSTEM_SWAN:
|
||||
return true;
|
||||
default:
|
||||
|
|
|
@ -154,6 +154,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeC(3);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
for (int i=0; i<16; i++) {
|
||||
w->writeC(0xc8);
|
||||
w->writeS(baseAddr2S+(i<<3));
|
||||
w->writeC(0);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610:
|
||||
case DIV_SYSTEM_YM2610_FULL:
|
||||
case DIV_SYSTEM_YM2610B:
|
||||
|
@ -395,6 +402,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
w->writeS(baseAddr2S|(write.addr&0xffff));
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
w->writeC(0xc8);
|
||||
w->writeS(baseAddr2S|(write.addr&0x1fff));
|
||||
w->writeC(write.val);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610:
|
||||
case DIV_SYSTEM_YM2610_FULL:
|
||||
case DIV_SYSTEM_YM2610B:
|
||||
|
@ -497,6 +509,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
int hasOPM=0;
|
||||
int hasSegaPCM=0;
|
||||
int segaPCMOffset=0xf8000d;
|
||||
int hasX1010=0;
|
||||
int hasRFC=0;
|
||||
int hasOPN=0;
|
||||
int hasOPNA=0;
|
||||
|
@ -568,6 +581,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
bool writePCESamples=false;
|
||||
bool writeADPCM=false;
|
||||
bool writeSegaPCM=false;
|
||||
bool writeX1010=false;
|
||||
bool writeQSound=false;
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
|
@ -651,6 +665,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_X1_010:
|
||||
if (!hasX1010) {
|
||||
hasX1010=disCont[i].dispatch->chipClock;
|
||||
willExport[i]=true;
|
||||
writeX1010=true;
|
||||
} else if (!(hasX1010&0x40000000)) {
|
||||
isSecond[i]=true;
|
||||
willExport[i]=true;
|
||||
hasX1010|=0x40000000;
|
||||
howManyChips++;
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610:
|
||||
case DIV_SYSTEM_YM2610_FULL:
|
||||
case DIV_SYSTEM_YM2610B:
|
||||
|
@ -995,6 +1021,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
|
|||
w->write(qsoundMem,blockSize);
|
||||
}
|
||||
|
||||
if (writeX1010 && x1_010MemLen>0) {
|
||||
w->writeC(0x67);
|
||||
w->writeC(0x66);
|
||||
w->writeC(0x91);
|
||||
w->writeI(x1_010MemLen+8);
|
||||
w->writeI(x1_010MemLen);
|
||||
w->writeI(0);
|
||||
w->write(x1_010Mem,x1_010MemLen);
|
||||
}
|
||||
|
||||
// initialize streams
|
||||
int streamID=0;
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "../engine/platform/tia.h"
|
||||
#include "../engine/platform/saa.h"
|
||||
#include "../engine/platform/amiga.h"
|
||||
#include "../engine/platform/x1_010.h"
|
||||
#include "../engine/platform/dummy.h"
|
||||
|
||||
#define GENESIS_DEBUG \
|
||||
|
@ -232,6 +233,47 @@ void putDispatchChan(void* data, int chanNum, int type) {
|
|||
ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta");
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_X1_010: {
|
||||
DivPlatformX1_010::Channel* ch=(DivPlatformX1_010::Channel*)data;
|
||||
ImGui::Text("> X1-010");
|
||||
ImGui::Text("* freq: %.4x",ch->freq);
|
||||
ImGui::Text(" - base: %d",ch->baseFreq);
|
||||
ImGui::Text(" - pitch: %d",ch->pitch);
|
||||
ImGui::Text("- note: %d",ch->note);
|
||||
ImGui::Text("- wave: %d",ch->wave);
|
||||
ImGui::Text("- sample: %d",ch->sample);
|
||||
ImGui::Text("- ins: %d",ch->ins);
|
||||
ImGui::Text("- pan: %d",ch->pan);
|
||||
ImGui::Text("* envelope:");
|
||||
ImGui::Text(" - shape: %d",ch->env.shape);
|
||||
ImGui::Text(" - period: %.2x",ch->env.period);
|
||||
ImGui::Text(" - slide: %.2x",ch->env.slide);
|
||||
ImGui::Text(" - slidefrac: %.2x",ch->env.slidefrac);
|
||||
ImGui::Text(" - autoEnvNum: %.2x",ch->autoEnvNum);
|
||||
ImGui::Text(" - autoEnvDen: %.2x",ch->autoEnvDen);
|
||||
ImGui::Text("- WaveBank: %d",ch->waveBank);
|
||||
ImGui::Text("- vol: %.2x",ch->vol);
|
||||
ImGui::Text("- outVol: %.2x",ch->outVol);
|
||||
ImGui::Text("- Lvol: %.2x",ch->lvol);
|
||||
ImGui::Text("- Rvol: %.2x",ch->rvol);
|
||||
ImGui::TextColored(ch->active?colorOn:colorOff,">> Active");
|
||||
ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged");
|
||||
ImGui::TextColored(ch->envChanged?colorOn:colorOff,">> EnvChanged");
|
||||
ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged");
|
||||
ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn");
|
||||
ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff");
|
||||
ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta");
|
||||
ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM");
|
||||
ImGui::TextColored(ch->pcm?colorOn:colorOff,">> PCM");
|
||||
ImGui::TextColored(ch->env.flag.envEnable?colorOn:colorOff,">> EnvEnable");
|
||||
ImGui::TextColored(ch->env.flag.envOneshot?colorOn:colorOff,">> EnvOneshot");
|
||||
ImGui::TextColored(ch->env.flag.envSplit?colorOn:colorOff,">> EnvSplit");
|
||||
ImGui::TextColored(ch->env.flag.envHinvR?colorOn:colorOff,">> EnvHinvR");
|
||||
ImGui::TextColored(ch->env.flag.envVinvR?colorOn:colorOff,">> EnvVinvR");
|
||||
ImGui::TextColored(ch->env.flag.envHinvL?colorOn:colorOff,">> EnvHinvL");
|
||||
ImGui::TextColored(ch->env.flag.envVinvL?colorOn:colorOff,">> EnvVinvL");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ImGui::Text("Unknown system! Help!");
|
||||
break;
|
||||
|
|
|
@ -1194,6 +1194,10 @@ void FurnaceGUI::drawInsList() {
|
|||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]);
|
||||
name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d\n",i,ins->name,i);
|
||||
break;
|
||||
case DIV_INS_X1_010:
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]);
|
||||
name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i);
|
||||
break;
|
||||
default:
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]);
|
||||
name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d\n",i,ins->name,i);
|
||||
|
@ -1350,7 +1354,7 @@ void FurnaceGUI::drawSampleEdit() {
|
|||
ImGui::Text("notes:");
|
||||
if (sample->loopStart>=0) {
|
||||
considerations=true;
|
||||
ImGui::Text("- sample won't loop on Neo Geo ADPCM-A");
|
||||
ImGui::Text("- sample won't loop on Neo Geo ADPCM-A and X1-010");
|
||||
if (sample->loopStart&1) {
|
||||
ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga");
|
||||
}
|
||||
|
@ -1366,10 +1370,18 @@ void FurnaceGUI::drawSampleEdit() {
|
|||
considerations=true;
|
||||
ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM.");
|
||||
}
|
||||
if (sample->samples&4095) {
|
||||
considerations=true;
|
||||
ImGui::Text("- sample length will be aligned and padded to 4096 sample units on X1-010.");
|
||||
}
|
||||
if (sample->samples>65535) {
|
||||
considerations=true;
|
||||
ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples");
|
||||
}
|
||||
if (sample->samples>131071) {
|
||||
considerations=true;
|
||||
ImGui::Text("- maximum sample length on X1-010 is 131072 samples");
|
||||
}
|
||||
if (sample->samples>2097151) {
|
||||
considerations=true;
|
||||
ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples");
|
||||
|
@ -2031,6 +2043,7 @@ void FurnaceGUI::drawStats() {
|
|||
String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024);
|
||||
String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024);
|
||||
String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024);
|
||||
String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024);
|
||||
ImGui::Text("ADPCM-A");
|
||||
ImGui::SameLine();
|
||||
ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str());
|
||||
|
@ -2040,6 +2053,9 @@ void FurnaceGUI::drawStats() {
|
|||
ImGui::Text("QSound");
|
||||
ImGui::SameLine();
|
||||
ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str());
|
||||
ImGui::Text("X1-010");
|
||||
ImGui::SameLine();
|
||||
ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str());
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS;
|
||||
ImGui::End();
|
||||
|
@ -4742,6 +4758,7 @@ bool FurnaceGUI::loop() {
|
|||
sysAddOption(DIV_SYSTEM_AY8930);
|
||||
sysAddOption(DIV_SYSTEM_LYNX);
|
||||
sysAddOption(DIV_SYSTEM_QSOUND);
|
||||
sysAddOption(DIV_SYSTEM_X1_010);
|
||||
sysAddOption(DIV_SYSTEM_SWAN);
|
||||
sysAddOption(DIV_SYSTEM_VERA);
|
||||
ImGui::EndMenu();
|
||||
|
@ -5036,6 +5053,23 @@ bool FurnaceGUI::loop() {
|
|||
} rightClickable
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_X1_010: {
|
||||
ImGui::Text("Clock rate:");
|
||||
if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) {
|
||||
e->setSysFlags(i,(flags&(~16))|0,restart);
|
||||
updateWindowTitle();
|
||||
}
|
||||
if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) {
|
||||
e->setSysFlags(i,(flags&(~16))|1,restart);
|
||||
updateWindowTitle();
|
||||
}
|
||||
bool x1_010Stereo=flags&16;
|
||||
if (ImGui::Checkbox("Stereo",&x1_010Stereo)) {
|
||||
e->setSysFlags(i,(flags&(~15))|(x1_010Stereo<<4),restart);
|
||||
updateWindowTitle();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_GB:
|
||||
case DIV_SYSTEM_SWAN:
|
||||
case DIV_SYSTEM_VERA:
|
||||
|
@ -5097,6 +5131,7 @@ bool FurnaceGUI::loop() {
|
|||
sysChangeOption(i,DIV_SYSTEM_AY8930);
|
||||
sysChangeOption(i,DIV_SYSTEM_LYNX);
|
||||
sysChangeOption(i,DIV_SYSTEM_QSOUND);
|
||||
sysChangeOption(i,DIV_SYSTEM_X1_010);
|
||||
sysChangeOption(i,DIV_SYSTEM_SWAN);
|
||||
sysChangeOption(i,DIV_SYSTEM_VERA);
|
||||
ImGui::EndMenu();
|
||||
|
@ -5673,7 +5708,8 @@ void FurnaceGUI::applyUISettings() {
|
|||
GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f));
|
||||
GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f));
|
||||
GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f));
|
||||
GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f))
|
||||
GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f));
|
||||
GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f));
|
||||
GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f));
|
||||
GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f));
|
||||
GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f));
|
||||
|
@ -6408,6 +6444,12 @@ FurnaceGUI::FurnaceGUI():
|
|||
0
|
||||
}
|
||||
));
|
||||
cat.systems.push_back(FurnaceGUISysDef(
|
||||
"Seta/Allumer X1-010", {
|
||||
DIV_SYSTEM_X1_010, 64, 0, 0,
|
||||
0
|
||||
}
|
||||
));
|
||||
sysCategories.push_back(cat);
|
||||
|
||||
cat=FurnaceGUISysCategory("Game consoles");
|
||||
|
@ -6705,6 +6747,18 @@ FurnaceGUI::FurnaceGUI():
|
|||
0
|
||||
}
|
||||
));
|
||||
cat.systems.push_back(FurnaceGUISysDef(
|
||||
"Seta 1", {
|
||||
DIV_SYSTEM_X1_010, 64, 0, 0,
|
||||
0
|
||||
}
|
||||
));
|
||||
cat.systems.push_back(FurnaceGUISysDef(
|
||||
"Seta 2", {
|
||||
DIV_SYSTEM_X1_010, 64, 0, 1,
|
||||
0
|
||||
}
|
||||
));
|
||||
sysCategories.push_back(cat);
|
||||
|
||||
cat=FurnaceGUISysCategory("DefleMask-compatible");
|
||||
|
|
|
@ -74,6 +74,7 @@ enum FurnaceGUIColors {
|
|||
GUI_COLOR_INSTR_SWAN,
|
||||
GUI_COLOR_INSTR_MIKEY,
|
||||
GUI_COLOR_INSTR_VERA,
|
||||
GUI_COLOR_INSTR_X1_010,
|
||||
GUI_COLOR_INSTR_UNKNOWN,
|
||||
GUI_COLOR_CHANNEL_FM,
|
||||
GUI_COLOR_CHANNEL_PULSE,
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#include <imgui.h>
|
||||
#include "plot_nolerp.h"
|
||||
|
||||
const char* insTypes[25]={
|
||||
const char* insTypes[DIV_INS_MAX]={
|
||||
"Standard",
|
||||
"FM (4-operator)",
|
||||
"Game Boy",
|
||||
|
@ -52,7 +52,8 @@ const char* insTypes[25]={
|
|||
"PC Beeper",
|
||||
"WonderSwan",
|
||||
"Atari Lynx",
|
||||
"VERA"
|
||||
"VERA",
|
||||
"X1-010"
|
||||
};
|
||||
|
||||
const char* ssgEnvTypes[8]={
|
||||
|
@ -157,6 +158,10 @@ const char* mikeyFeedbackBits[11] = {
|
|||
"0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL
|
||||
};
|
||||
|
||||
const char* x1_010EnvBits[8]={
|
||||
"enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL
|
||||
};
|
||||
|
||||
const char* oneBit[2]={
|
||||
"on", NULL
|
||||
};
|
||||
|
@ -792,9 +797,9 @@ void FurnaceGUI::drawInsEdit() {
|
|||
} else {
|
||||
DivInstrument* ins=e->song.ins[curIns];
|
||||
ImGui::InputText("Name",&ins->name);
|
||||
if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM;
|
||||
if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM;
|
||||
int insType=ins->type;
|
||||
if (ImGui::Combo("Type",&insType,insTypes,25,24)) {
|
||||
if (ImGui::Combo("Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) {
|
||||
ins->type=(DivInstrumentType)insType;
|
||||
}
|
||||
|
||||
|
@ -1449,11 +1454,18 @@ void FurnaceGUI::drawInsEdit() {
|
|||
|
||||
int ex1Max=(ins->type==DIV_INS_AY8930)?8:0;
|
||||
int ex2Max=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?4:0;
|
||||
bool ex2Bit=true;
|
||||
|
||||
if (ins->type==DIV_INS_C64) {
|
||||
ex1Max=4;
|
||||
ex2Max=15;
|
||||
}
|
||||
if (ins->type==DIV_INS_X1_010) {
|
||||
dutyMax=0;
|
||||
ex1Max=7;
|
||||
ex2Max=63;
|
||||
ex2Bit=false;
|
||||
}
|
||||
if (ins->type==DIV_INS_SAA1099) ex1Max=8;
|
||||
|
||||
if (settings.macroView==0) { // modern view
|
||||
|
@ -1478,6 +1490,8 @@ void FurnaceGUI::drawInsEdit() {
|
|||
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1MacroOpen,true,filtModeBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
|
||||
} else if (ins->type==DIV_INS_SAA1099) {
|
||||
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
|
||||
} else if (ins->type==DIV_INS_X1_010) {
|
||||
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
|
||||
} else {
|
||||
NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false);
|
||||
}
|
||||
|
@ -1486,13 +1500,13 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_C64) {
|
||||
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false);
|
||||
} else {
|
||||
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",64,ins->std.ex2MacroOpen,true,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false);
|
||||
NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false);
|
||||
}
|
||||
}
|
||||
if (ins->type==DIV_INS_C64) {
|
||||
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,2,"ex3","Special",32,ins->std.ex3MacroOpen,true,c64SpecialBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false);
|
||||
}
|
||||
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) {
|
||||
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_X1_010) {
|
||||
NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,15,"ex3","AutoEnv Num",96,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL,false);
|
||||
NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,15,"alg","AutoEnv Den",96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL,false);
|
||||
}
|
||||
|
|
|
@ -493,6 +493,7 @@ void FurnaceGUI::drawSettings() {
|
|||
UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan");
|
||||
UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx");
|
||||
UI_COLOR_CONFIG(GUI_COLOR_INSTR_VERA,"VERA");
|
||||
UI_COLOR_CONFIG(GUI_COLOR_INSTR_X1_010,"X1-010");
|
||||
UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown");
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
@ -1161,6 +1162,7 @@ void FurnaceGUI::commitSettings() {
|
|||
PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN);
|
||||
PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY);
|
||||
PUT_UI_COLOR(GUI_COLOR_INSTR_VERA);
|
||||
PUT_UI_COLOR(GUI_COLOR_INSTR_X1_010);
|
||||
PUT_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN);
|
||||
PUT_UI_COLOR(GUI_COLOR_CHANNEL_FM);
|
||||
PUT_UI_COLOR(GUI_COLOR_CHANNEL_PULSE);
|
||||
|
|
Loading…
Reference in a new issue