summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Pavone <pavone@retrodev.com>2013-05-27 09:54:58 -0700
committerMike Pavone <pavone@retrodev.com>2013-05-27 09:54:58 -0700
commit140052b8720dabfda7a66f2b754a81ce902e3d34 (patch)
treeec0a59a9ba12c3a1179ab87858881b786606b476
parent1def7c1cb5605bfe1417cdc5b64e5a4c6aac0007 (diff)
YM2612 WIP snapshot before register refactor
-rw-r--r--Makefile2
-rw-r--r--ym2612.c186
-rw-r--r--ym2612.h8
3 files changed, 164 insertions, 32 deletions
diff --git a/Makefile b/Makefile
index 9c73393..3dee3fa 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ endif
all : dis trans stateview blastem
blastem : blastem.o 68kinst.o gen_x86.o m68k_to_x86.o z80inst.o z80_to_x86.o x86_backend.o runtime.o zruntime.o mem.o vdp.o ym2612.o psg.o render_sdl.o
- $(CC) -o blastem blastem.o 68kinst.o gen_x86.o m68k_to_x86.o z80inst.o z80_to_x86.o x86_backend.o runtime.o zruntime.o mem.o vdp.o ym2612.o psg.o render_sdl.o `pkg-config --libs $(LIBS)`
+ $(CC) -ggdb -o blastem blastem.o 68kinst.o gen_x86.o m68k_to_x86.o z80inst.o z80_to_x86.o x86_backend.o runtime.o zruntime.o mem.o vdp.o ym2612.o psg.o render_sdl.o `pkg-config --libs $(LIBS)`
dis : dis.o 68kinst.o
$(CC) -o dis dis.o 68kinst.o
diff --git a/ym2612.c b/ym2612.c
index 9be8b6c..6f0fe9b 100644
--- a/ym2612.c
+++ b/ym2612.c
@@ -1,13 +1,27 @@
#include <string.h>
+#include <math.h>
+#include <stdio.h>
#include "ym2612.h"
#define BUSY_CYCLES 17
#define TIMERA_UPDATE_PERIOD 144
-#define REG_TIMERA_HIGH 0x3 // 0x24
-#define REG_TIMERA_LOW 0x4 // 0x25
-#define REG_TIMERB 0x5 // 0x26
-#define REG_TIME_CTRL 0x6 // 0x27
+#define REG_TIMERA_HIGH 0x03 // 0x24
+#define REG_TIMERA_LOW 0x04 // 0x25
+#define REG_TIMERB 0x05 // 0x26
+#define REG_TIME_CTRL 0x06 // 0x27
+#define REG_DAC 0x0A // 0x2A
+#define REG_DAC_ENABLE 0x0B // 0x2B
+
+//offset to add to "shared" regs when looking for them in Part I
+#define REG_SHARED 0x10
+
+
+#define REG_ALG_FEEDBACK (0xB0-0x30)
+#define REG_ATTACK_KS (0x50-0x30)
+#define REG_DECAY_AM (0x60-0x30)
+#define REG_SUSTAIN_RATE (0x70-0x30)
+
#define BIT_TIMERA_ENABLE 0x1
#define BIT_TIMERB_ENABLE 0x2
@@ -19,45 +33,155 @@
#define BIT_STATUS_TIMERA 0x1
#define BIT_STATUS_TIMERB 0x2
+enum {
+ PHASE_ATTACK,
+ PHASE_DECAY,
+ PHASE_SUSTAIN,
+ PHASE_RELEASE
+};
+
+uint8_t did_tbl_init = 0;
+//According to Nemesis, real hardware only uses a 256 entry quarter sine table; however,
+//memory is cheap so using a half sine table will probably save some cycles
+//a full sine table would be nice, but negative numbers don't get along with log2
+#define SINE_TABLE_SIZE 512
+uint16_t sine_table[SINE_TABLE_SIZE];
+//Similar deal here with the power table for log -> linear conversion
+//According to Nemesis, real hardware only uses a 256 entry table for the fractional part
+//and uses the whole part as a shift amount.
+#define POW_TABLE_SIZE (1 << 13)
+uint16_t pow_table[POW_TABLE_SIZE];
+
+
+uint16_t round_fixed_point(double value, int dec_bits)
+{
+ return value * (1 << dec_bits) + 0.5;
+}
+
void ym_init(ym2612_context * context)
{
memset(context, 0, sizeof(*context));
+ if (!did_tbl_init) {
+ //populate sine table
+ for (int32_t i = 0; i < 512; i++) {
+ double sine = sin( ((double)(i*2+1) / SINE_TABLE_SIZE) * M_PI_2 );
+
+ //table stores 4.8 fixed pointed representation of the base 2 log
+ sine_table[i] = round_fixed_point(-log2(sine), 8);
+ }
+ //populate power table
+ for (int32_t i = 0; i < POW_TABLE_SIZE; i++) {
+ double linear = pow(2, -((double)((i & 0xFF)+1) / 256.0));
+ int32_t tmp = round_fixed_point(linear, 11);
+ int32_t shift = (i >> 8) - 2;
+ if (shift < 0) {
+ tmp <<= 0-shift;
+ } else {
+ tmp >>= shift;
+ }
+ pow_table[i] = tmp;
+ }
+ }
}
void ym_run(ym2612_context * context, uint32_t to_cycle)
{
- uint32_t delta = to_cycle - context->current_cycle;
- //Timers won't be perfect with this, but it's good enough for now
- //once actual FM emulation is in place the timers should just be
- //decremented/reloaded on the appropriate ticks
- uint32_t timer_delta = to_cycle / TIMERA_UPDATE_PERIOD;
- if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERA_ENABLE) {
- if (timer_delta > context->timer_a) {
- if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERA_OVEREN) {
- context->status |= BIT_STATUS_TIMERA;
+ for (; context->current_cycle < to_cycle; context->current_cycle += 6) {
+ uint32_t update_cyc = context->current_cycle % 144;
+ //Update timers at beginning of 144 cycle period
+ if (!update_cyc && context->part1_regs[REG_TIME_CTRL] & BIT_TIMERA_ENABLE) {
+ if (context->timer_a) {
+ context->timer_a--;
+ } else {
+ if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERA_OVEREN) {
+ context->status |= BIT_STATUS_TIMERA;
+ }
+ context->timer_a = (context->part1_regs[REG_TIMERA_HIGH] << 2) | (context->part1_regs[REG_TIMERA_LOW] & 0x3);
+ }
+ if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERB_ENABLE) {
+ uint32_t b_cyc = (context->current_cycle / 144) % 16;
+ if (!b_cyc) {
+ if (context->timer_b) {
+ context->timer_b--;
+ } else {
+ if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERB_OVEREN) {
+ context->status |= BIT_STATUS_TIMERB;
+ }
+ context->timer_b = context->part1_regs[REG_TIMERB];
+ }
+ }
}
- uint32_t rem_delta = timer_delta - (context->timer_a+1);
- uint16_t timer_val = (context->part1_regs[REG_TIMERA_HIGH] << 2) | (context->part1_regs[REG_TIMERA_LOW] & 0x3);
- context->timer_a = timer_val - (rem_delta % (timer_val + 1));
- } else {
- context->timer_a -= timer_delta;
}
- }
- timer_delta /= 16; //Timer B runs at 1/16th the speed of Timer A
- if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERB_ENABLE) {
- if (timer_delta > context->timer_b) {
- if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERB_OVEREN) {
- context->status |= BIT_STATUS_TIMERB;
+ //Update Envelope Generator
+ if (update_cyc == 0 || update_cyc == 72) {
+ uint32_t env_cyc = context->current_cycle / 72;
+ uint32_t op = env_cyc % 24;
+ env_cyc /= 24;
+ uint8_t rate;
+ switch(context->env_phase[op])
+ {
+ case PHASE_ATTACK:
+ rate = (op < 3 ? context->part1_regs[REG_SHARED + REG_ATTACK_KS + op] : context->part2_regs[REG_ATTACK_KS + op - 3]) & 0x1F;
+ break;
+ case PHASE_DECAY:
+ rate = (op < 3 ? context->part1_regs[REG_SHARED + REG_DECAY_AM + op] : context->part2_regs[REG_DECAY_AM + op - 3]) & 0x1F;
+ break;
+ case PHASE_SUSTAIN:
+ rate = (op < 3 ? context->part1_regs[REG_SHARED + REG_SUSTAIN_RATE + op] : context->part2_regs[REG_SUSTAIN_RATE + op - 3]) & 0x1F;
+ break;
+ case PHASE_RELEASE:
+ rate = (op < 3 ? context->part1_regs[REG_SHARED + REG_DECAY_AM + op] : context->part2_regs[REG_DECAY_AM + op - 3]) << 1 & 0x1E | 1;
+ break;
+ }
+ if (rate) {
+ //apply key scaling
+ uint8_t shift = (op < 3 ?
+ context->part1_regs[REG_SHARED + REG_ATTACK_KS + op] :
+ context->part2_regs[REG_ATTACK_KS + op - 3]
+ ) >> 6;
+ uint8_t ks = context->keycode[op] >> (3 - shift);
+ rate = rate*2 + ks;
+ if (rate > 63) {
+ rate = 63;
+ }
+ }
+ }
+ //Update Phase Generator
+ uint32_t channel = update_cyc / 24;
+ if (channel != 6 || !(context->part1_regs[REG_DAC_ENABLE] & 0x80)) {
+ uint32_t op = (update_cyc) / 6;
+ uint8_t alg;
+ if (op < 3) {
+ alg = context->part1_regs[REG_SHARED + REG_ALG_FEEDBACK + op] & 0x7;
+ } else {
+ alg = context->part2_regs[REG_ALG_FEEDBACK + op-3] & 0x7;
+ }
+ context->phase_counter[op] += context->phase_inc[op];
+ uint16_t phase = context->phase_counter[op] >> 10 & 0x3FF;
+ //TODO: Modulate phase if necessary
+ uint16_t output = pow_table[sine_table[phase & 0x1FF] + context->envelope[op]];
+ if (phase & 0x200) {
+ output = -output;
+ }
+ context->op_out[op] = output;
+ //Update the channel output if we've updated all operators
+ if (op % 4 == 3) {
+ if (alg < 4) {
+ context->channel_out[channel] = context->op_out[channel * 4 + 3];
+ } else if(alg == 4) {
+ context->channel_out[channel] = context->op_out[channel * 4 + 3] + context->op_out[channel * 4 + 1];
+ } else {
+ output = 0;
+ for (uint32_t op = ((alg == 7) ? 0 : 1) + channel*4; op < (channel+1)*4; op++) {
+ output += context->op_out[op];
+ }
+ context->channel_out[channel] = output;
+ }
}
- uint32_t rem_delta = timer_delta - (context->timer_b+1);
- uint8_t timer_val = context->part1_regs[REG_TIMERB];
- context->timer_b = timer_val - (rem_delta % (timer_val + 1));
- } else {
- context->timer_a -= timer_delta;
}
+
}
- context->current_cycle = to_cycle;
- if (to_cycle >= context->write_cycle + BUSY_CYCLES) {
+ if (context->current_cycle >= context->write_cycle + BUSY_CYCLES) {
context->status &= 0x7F;
}
}
diff --git a/ym2612.h b/ym2612.h
index 6b99dd0..f66a526 100644
--- a/ym2612.h
+++ b/ym2612.h
@@ -5,12 +5,20 @@
#define NUM_SHARED_REGS (0x30-0x21)
#define NUM_PART_REGS (0xB7-0x30)
+#define NUM_OPERATORS (4*6)
typedef struct {
uint32_t current_cycle;
uint32_t write_cycle;
uint8_t *selected_reg;
+ uint32_t phase_inc[NUM_OPERATORS];
+ uint32_t phase_counter[NUM_OPERATORS];
+ uint16_t envelope[NUM_OPERATORS];
+ uint16_t op_out[NUM_OPERATORS];
+ uint16_t channel_out[6];
uint16_t timer_a;
+ uint8_t env_phase[NUM_OPERATORS];
+ uint8_t keycode[NUM_OPERATORS];
uint8_t timer_b;
uint8_t reg_num;
uint8_t status;