From c543553b0f7ae8730e05c1285a6b46c0ab26b2b7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 3 Dec 2021 16:04:07 -0500 Subject: [PATCH] prepare for NES platform --- CMakeLists.txt | 1 + src/engine/platform/sound/nes/apu.c | 227 +++++++++ src/engine/platform/sound/nes/apu.h | 633 +++++++++++++++++++++++++ src/engine/platform/sound/nes/common.h | 76 +++ 4 files changed, 937 insertions(+) create mode 100644 src/engine/platform/sound/nes/apu.c create mode 100644 src/engine/platform/sound/nes/apu.h create mode 100644 src/engine/platform/sound/nes/common.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a1e89bef..7169c239 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/gb/apu.c src/engine/platform/sound/gb/timing.c src/engine/platform/sound/pce_psg.cpp +src/engine/platform/sound/nes/apu.c src/engine/blip_buf.c src/engine/safeReader.cpp diff --git a/src/engine/platform/sound/nes/apu.c b/src/engine/platform/sound/nes/apu.c new file mode 100644 index 00000000..d1ef3322 --- /dev/null +++ b/src/engine/platform/sound/nes/apu.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2010-2019 Fabio Cavallo (aka FHorse) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// additional modifications by tildearrow for furnace + +#include +#include "apu.h" + +void apu_tick(BYTE *hwtick) { + /* sottraggo il numero di cicli eseguiti */ + apu.cycles--; + /* + * questo flag sara' a TRUE solo nel ciclo + * in cui viene eseguito il length counter. + */ + apu.length_clocked = FALSE; + /* + * se e' settato il delay del $4017, essendo + * questo il ciclo successivo, valorizzo il + * registro. + */ +#if defined (VECCHIA_GESTIONE_JITTER) + if (r4017.jitter.delay) { + r4017.jitter.delay = FALSE; + r4017_jitter(); + } +#else + if (r4017.jitter.delay) { + r4017.jitter.delay = FALSE; + r4017_jitter(0) + } + r4017_reset_frame() +#endif + + /* quando apu.cycles e' a 0 devo eseguire uno step */ + if (!apu.cycles) { + switch (apu.step) { + case 0: + /* + * nel mode 1 devo eseguire il + * length counter e lo sweep. + */ + if (apu.mode == APU_48HZ) { + length_clock() + sweep_clock() + } + envelope_clock() + /* triangle's linear counter */ + linear_clock() + /* passo al prossimo step */ + apu_change_step(++apu.step); + break; + case 1: + /* nel mode 0 devo eseguire il length counter */ + if (apu.mode == APU_60HZ) { + length_clock() + sweep_clock() + } + envelope_clock() + /* triangle's linear counter */ + linear_clock() + /* passo al prossimo step */ + apu_change_step(++apu.step); + break; + case 2: + /* + * nel mode 1 devo eseguire il + * length counter e lo sweep. + */ + if (apu.mode == APU_48HZ) { + length_clock() + sweep_clock() + } + envelope_clock() + /* triangle's linear counter */ + linear_clock() + /* passo al prossimo step */ + apu_change_step(++apu.step); + break; + case 3: + /* + * gli step 3, 4 e 5 settano il bit 6 del $4015 + * ma solo nel 4 genero un IRQ. + */ + if (apu.mode == APU_60HZ) { + /* + * se e' a 0 il bit 6 del $4017 (interrupt + * inhibit flag) allora devo generare un IRQ. + */ + if (!(r4017.value & 0x40)) { + /* setto il bit 6 del $4015 */ + r4015.value |= 0x40; + } + } else { + /* nel mode 1 devo eseguire l'envelope */ + envelope_clock() + /* triangle's linear counter */ + linear_clock() + } + /* passo al prossimo step */ + apu_change_step(++apu.step); + break; + case 4: + /* + * gli step 3, 4 e 5 settano il bit 6 del $4015 + * ma solo nel 4 genero un IRQ. + */ + if (apu.mode == APU_60HZ) { + length_clock() + sweep_clock() + envelope_clock() + /* triangle's linear counter */ + linear_clock() + /* + * se e' a 0 il bit 6 del $4017 (interrupt + * inhibit flag) allora devo generare un IRQ. + */ + if (!(r4017.value & 0x40)) { + /* setto il bit 6 del $4015 */ + r4015.value |= 0x40; + } + } + /* passo al prossimo step */ + apu_change_step(++apu.step); + break; + case 5: + /* + * gli step 3, 4 e 5 settano il bit 6 del $4015 + * ma solo nel 4 genero un IRQ. + */ + if (apu.mode == APU_60HZ) { + /* + * se e' a 0 il bit 6 del $4017 (interrupt + * inhibit flag) allora devo generare un IRQ. + */ + if (!(r4017.value & 0x40)) { + /* setto il bit 6 del $4015 */ + r4015.value |= 0x40; + } + apu.step++; + } else { + /* nel mode 1 devo ricominciare il ciclo */ + apu.step = 0; + } + /* passo al prossimo step */ + apu_change_step(apu.step); + break; + case 6: + /* da qui ci passo solo nel mode 0 */ + envelope_clock() + /* triangle's linear counter */ + linear_clock() + /* questo e' il passaggio finale del mode 0 */ + apu.step = 1; + /* passo al prossimo step */ + apu_change_step(apu.step); + break; + } + } + + /* + * eseguo un ticket per ogni canale + * valorizzandone l'output. + */ + square_tick(S1, 0, apu) + square_tick(S2, 0, apu) + triangle_tick() + noise_tick() + dmc_tick() + + // TODO + /*if (snd_apu_tick) { + snd_apu_tick(); + }*/ + + r4011.cycles++; +} +void apu_turn_on(void) { + memset(&apu, 0x00, sizeof(apu)); + memset(&r4015, 0x00, sizeof(r4015)); + memset(&r4017, 0x00, sizeof(r4017)); + /* azzero tutte le variabili interne dei canali */ + memset(&S1, 0x00, sizeof(S1)); + memset(&S2, 0x00, sizeof(S2)); + memset(&TR, 0x00, sizeof(TR)); + memset(&NS, 0x00, sizeof(NS)); + memset(&DMC, 0x00, sizeof(DMC)); + /* al reset e' sempre settato a 60Hz */ + apu.mode = APU_60HZ; + apu.type = 0; + apu_change_step(apu.step); + /* valori iniziali dei vari canali */ + S1.frequency = 1; + S1.sweep.delay = 1; + S1.sweep.divider = 1; + S2.frequency = 1; + S2.sweep.delay = 1; + S2.sweep.divider = 1; + TR.frequency = 1; + TR.sequencer = 0; + NS.frequency = 1; + NS.shift = 1; + DMC.frequency = 1; + DMC.empty = TRUE; + DMC.silence = TRUE; + DMC.counter_out = 8; + // sembra che l'address del DMC al power on dia valorizzato a 0xC000 + // e la lunghezza del sample sia settato a 1 byte. + // http://forums.nesdev.com/viewtopic.php?f=3&t=18278 + DMC.length = 1; + DMC.address_start = 0xC000; +} diff --git a/src/engine/platform/sound/nes/apu.h b/src/engine/platform/sound/nes/apu.h new file mode 100644 index 00000000..5089220e --- /dev/null +++ b/src/engine/platform/sound/nes/apu.h @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2010-2019 Fabio Cavallo (aka FHorse) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// additional modifications by tildearrow for furnace + +#ifndef APU_H_ +#define APU_H_ + +#include "common.h" + +enum dmc_types_of_dma { DMC_NORMAL, DMC_CPU_WRITE, DMC_R4014, DMC_NNL_DMA }; +enum apu_channels { APU_S1, APU_S2, APU_TR, APU_NS, APU_DMC, APU_EXTRA, APU_MASTER }; +enum apu_mode { APU_60HZ, APU_48HZ }; + +int cpu_cycles; +int cpu_opcode_cycle; +unsigned char* addrSpace; + +#define apu_pre_amp 1.4f + +/* length counter */ +#define length_run(channel)\ + /*\ + * se non e' settato il flag halt e il length\ + * counter non e' 0 allora devo decrementarlo.\ + */\ + if (!channel.length.halt && channel.length.value) {\ + channel.length.value--;\ + } +#define length_clock()\ + apu.length_clocked = TRUE;\ + length_run(S1)\ + length_run(S2)\ + length_run(TR)\ + length_run(NS) +/* envelope */ +#define envelope_run(channel)\ + if (channel.envelope.enabled) {\ + channel.envelope.enabled = FALSE;\ + channel.envelope.counter = 15;\ + channel.envelope.delay = (channel.envelope.divider + 1);\ + } else if (!(--channel.envelope.delay)) {\ + channel.envelope.delay = (channel.envelope.divider + 1);\ + if (channel.envelope.counter | channel.length.halt) {\ + channel.envelope.counter = (channel.envelope.counter - 1) & 0x0F;\ + }\ + } +#define envelope_volume(channel)\ + /* setto il volume */\ + if (!channel.length.value) {\ + channel.volume = 0;\ + } else if (channel.envelope.constant_volume) {\ + channel.volume = channel.envelope.divider;\ + } else {\ + channel.volume = channel.envelope.counter;\ + } +#define envelope_clock()\ + envelope_run(S1)\ + envelope_run(S2)\ + envelope_run(NS) +/* sweep */ +#define sweep_run(channel, negative_adjust)\ + if (!(--channel.sweep.delay)) {\ + channel.sweep.delay = (channel.sweep.divider + 1);\ + if (channel.sweep.enabled && channel.sweep.shift && (channel.timer >= 8)) {\ + SWORD offset = channel.timer >> channel.sweep.shift;\ + if (channel.sweep.negate) {\ + channel.timer += ((SWORD) negative_adjust - offset);\ + } else if ((channel.timer + offset) <= 0x800) {\ + channel.timer += offset;\ + }\ + }\ + sweep_silence(channel)\ + }\ + if (channel.sweep.reload) {\ + channel.sweep.reload = FALSE;\ + channel.sweep.delay = (channel.sweep.divider + 1);\ + } +#define sweep_silence(channel)\ +{\ + WORD offset = channel.timer >> channel.sweep.shift;\ + channel.sweep.silence = FALSE;\ + if ((channel.timer <= 8) || (!channel.sweep.negate && ((channel.timer + offset) >= 0x800))) {\ + channel.sweep.silence = TRUE;\ + }\ +} +#define sweep_clock()\ + sweep_run(S1, -1)\ + sweep_run(S2, 0) +/* linear counter */ +#define linear_clock()\ + if (TR.linear.halt) {\ + TR.linear.value = TR.linear.reload;\ + } else if (TR.linear.value) {\ + TR.linear.value--;\ + }\ + if (!TR.length.halt) {\ + TR.linear.halt = FALSE;\ + } +/* output */ +#define square_output(square, swap)\ +{\ + envelope_volume(square)\ + if (square.sweep.silence) {\ + square.output = 0;\ + } else {\ + square.output = square_duty[swap][square.duty][square.sequencer] * square.volume;\ + }\ +} +#define triangle_output()\ + /*\ + * ai 2 cicli piu' bassi del timer, la frequenza\ + * risultante e' troppo alta (oltre i 20 kHz,\ + * quindi non udibile), percio' la taglio.\ + */\ + TR.output = triangle_duty[TR.sequencer];\ + if (TR.timer < 2) {\ + TR.output = triangle_duty[8];\ + } +#define noise_output()\ + envelope_volume(NS)\ + NS.output = 0;\ + if (NS.length.value && !(NS.shift & 0x0001)) {\ + NS.output = NS.volume;\ + } +#define dmc_output()\ + DMC.output = DMC.counter & 0x7F +/* tick */ +#define square_tick(square, swap, type)\ + if (!(--square.frequency)) {\ + square_output(square, swap)\ + square.frequency = (square.timer + 1) << 1;\ + square.sequencer = (square.sequencer + 1) & 0x07;\ + type.clocked = TRUE;\ + } +#define triangle_tick()\ + if (!(--TR.frequency)) {\ + TR.frequency = TR.timer + 1;\ + if (TR.length.value && TR.linear.value) {\ + TR.sequencer = (TR.sequencer + 1) & 0x1F;\ + triangle_output()\ + apu.clocked = TRUE;\ + }\ + } +#define noise_tick()\ + if (!(--NS.frequency)) {\ + if (NS.mode) {\ + NS.shift = (NS.shift >> 1) | (((NS.shift ^ (NS.shift >> 6)) & 0x0001) << 14);\ + } else {\ + NS.shift = (NS.shift >> 1) | (((NS.shift ^ (NS.shift >> 1)) & 0x0001) << 14);\ + }\ + NS.shift &= 0x7FFF;\ + noise_output()\ + NS.frequency = noise_timer[apu.type][NS.timer];\ + apu.clocked = TRUE;\ + } +#define dmc_tick()\ + if (!(--DMC.frequency)) {\ + if (!DMC.silence) {\ + if (!(DMC.shift & 0x01)) {\ + if (DMC.counter > 1) {\ + DMC.counter -= 2;\ + }\ + } else {\ + if (DMC.counter < 126) {\ + DMC.counter += 2;\ + }\ + }\ + }\ + DMC.shift >>= 1;\ + dmc_output();\ + if (!(--DMC.counter_out)) {\ + DMC.counter_out = 8;\ + if (!DMC.empty) {\ + DMC.shift = DMC.buffer;\ + DMC.empty = TRUE;\ + DMC.silence = FALSE;\ + } else {\ + DMC.silence = TRUE;\ + }\ + }\ + DMC.frequency = dmc_rate[apu.type][DMC.rate_index];\ + apu.clocked = TRUE;\ + }\ + if (DMC.empty && DMC.remain) {\ + BYTE tick = 4;\ + switch (DMC.tick_type) {\ + case DMC_CPU_WRITE:\ + tick = 3;\ + break;\ + case DMC_R4014:\ + tick = 2;\ + break;\ + case DMC_NNL_DMA:\ + tick = 1;\ + break;\ + }\ + {\ + DMC.buffer = addrSpace[DMC.address];\ + }\ + /* incremento gli hwtick da compiere */\ + if (hwtick) { hwtick[0] += tick; }\ + /* e naturalmente incremento anche quelli eseguiti dall'opcode */\ + cpu_cycles += tick;\ + /* salvo a che ciclo dell'istruzione avviene il dma */\ + DMC.dma_cycle = cpu_opcode_cycle;\ + /* il DMC non e' vuoto */\ + DMC.empty = FALSE;\ + if (++DMC.address > 0xFFFF) {\ + DMC.address = 0x8000;\ + }\ + if (!(--DMC.remain)) {\ + if (DMC.loop) {\ + DMC.remain = DMC.length;\ + DMC.address = DMC.address_start;\ + } else if (DMC.irq_enabled) {\ + r4015.value |= 0x80;\ + }\ + }\ + } + +#define apu_change_step(index)\ + apu.cycles += apuPeriod[apu.mode][apu.type][index] +#if defined (VECCHIA_GESTIONE_JITTER) +#define r4017_jitter()\ + r4017.value = (r4017.jitter.value & 0xC0);\ + /*\ + * se il bit 7 e' a zero, devo attivare la\ + * modalita' NTSC, se a uno quella PAL.\ + */\ + if (r4017.value & 0x80) {\ + apu.mode = APU_48HZ;\ + } else {\ + apu.mode = APU_60HZ;\ + }\ + if (r4017.value & 0x40) {\ + /* azzero il bit 6 del $4015 */\ + r4015.value &= 0xBF;\ + /* questo non e' affatto necessario sul forno */\ + }\ + /* riavvio il frame audio */\ + apu.step = apu.cycles = 0;\ + apu_change_step(apu.step) +#else +#define r4017_jitter(apc)\ + r4017.value = (r4017.jitter.value & 0xC0);\ + r4017.reset_frame_delay = 1;\ + if (apu.cycles == apc) {\ + if (apu.mode == APU_48HZ) {\ + r4017.reset_frame_delay += 1;\ + } else {\ + r4017.reset_frame_delay += 2;\ + }\ + }\ + /*\ + * se il bit 7 e' a zero, devo attivare la\ + * modalita' NTSC, se a uno quella PAL.\ + */\ + if (r4017.value & 0x80) {\ + apu.mode = APU_48HZ;\ + } else {\ + apu.mode = APU_60HZ;\ + }\ + if (r4017.value & 0x40) {\ + /* azzero il bit 6 del $4015 */\ + r4015.value &= 0xBF;\ + /* questo non e' affatto necessario sul forno */\ + } +#define r4017_reset_frame()\ + if (r4017.reset_frame_delay && (--r4017.reset_frame_delay == 0)) {\ + /* riavvio il frame audio */\ + apu.step = apu.cycles = 0;\ + apu_change_step(apu.step);\ + } +#endif +#define square_reg0(square)\ + /* duty */\ + square.duty = value >> 6;\ + /* length counter */\ + square.length.halt = value & 0x20;\ + /* envelope */\ + square.envelope.constant_volume = value & 0x10;\ + square.envelope.divider = value & 0x0F +#define square_reg1(square)\ + /* sweep */\ + square.sweep.reload = TRUE;\ + square.sweep.divider = (value >> 4) & 0x07;\ + square.sweep.shift = value & 0x07;\ + square.sweep.enabled = value & 0x80;\ + square.sweep.negate = value & 0x08 +#define square_reg2(square)\ + /* timer (low 8 bits) */\ + square.timer = (square.timer & 0x0700) | value +#define square_reg3(square)\ + /* length counter */\ + /*\ + * se non disabilitato, una scrittura in\ + * questo registro, carica immediatamente il\ + * length counter del canale, tranne nel caso\ + * in cui la scrittura avvenga nello stesso\ + * momento del clock di un length counter e\ + * con il length diverso da zero.\ + */\ + if (square.length.enabled && !(apu.length_clocked && square.length.value)) {\ + square.length.value = length_table[value >> 3];\ + }\ + /* envelope */\ + square.envelope.enabled = TRUE;\ + /* timer (high 3 bits) */\ + square.timer = (square.timer & 0x00FF) | ((value & 0x07) << 8);\ + /*The correct behaviour is to reset the duty cycle sequencers but not the clock dividers*/\ + /*square.frequency = 1;*/\ + /* sequencer */\ + square.sequencer = 0 +#define init_nla_table(p, t)\ +{\ + WORD i;\ + for (i = 0; i < LENGTH(nla_table.pulse); i++) {\ + double vl = 95.52 / (8128.0 / (double) i + 100.0);\ + nla_table.pulse[i] = (vl * p);\ + }\ + for (i = 0; i < LENGTH(nla_table.tnd); i++) {\ + double vl = 163.67 / (24329.0 / (double) i + 100.0);\ + nla_table.tnd[i] = (vl * t);\ + }\ +} +#define _apu_channel_volume_adjust(ch, index)\ + ((ch * cfg->apu.channel[index]) * ch_gain_ptnd(index)) +#define s1_out\ + _apu_channel_volume_adjust(S1.output, APU_S1) +#define s2_out\ + _apu_channel_volume_adjust(S2.output, APU_S2) +#define tr_out\ + _apu_channel_volume_adjust(TR.output, APU_TR) +#define ns_out\ + _apu_channel_volume_adjust(NS.output, APU_NS) +#define dmc_out\ + _apu_channel_volume_adjust(DMC.output, APU_DMC) +#define extra_out(ch)\ + (ch * cfg->apu.channel[APU_EXTRA]) +#define pulse_output()\ + nla_table.pulse[(int) (s1_out + s2_out)] +#define tnd_output()\ + nla_table.tnd[(int) ((tr_out * 3) + (ns_out * 2) + dmc_out)] + +typedef struct _config_apu { + BYTE channel[APU_MASTER + 1]; + double volume[APU_MASTER + 1]; +} _config_apu; +typedef struct _apu { + BYTE mode; + BYTE type; + BYTE step; + BYTE length_clocked; + BYTE DMC; + SWORD cycles; + + /* ------------------------------------------------------- */ + /* questi valori non e' necessario salvarli nei savestates */ + /* ------------------------------------------------------- */ + /* */ BYTE clocked; /* */ + /* ------------------------------------------------------- */ +} _apu; +typedef struct _r4011 { + BYTE value; + DBWORD frames; + DBWORD cycles; + SWORD output; +} _r4011; +typedef struct _r4015 { + BYTE value; +} _r4015; +typedef struct _r4017 { + BYTE value; + struct _r4017_litter { + BYTE value; + BYTE delay; + } jitter; + BYTE reset_frame_delay; +} _r4017; +typedef struct _envelope { + BYTE enabled; + BYTE divider; + BYTE counter; + BYTE constant_volume; + SBYTE delay; +} _envelope; +typedef struct _sweep { + BYTE enabled; + BYTE negate; + BYTE divider; + BYTE shift; + BYTE reload; + BYTE silence; + SBYTE delay; +} _sweep; +typedef struct _length_counter { + BYTE value; + BYTE enabled; + BYTE halt; +} _length_counter; +typedef struct _linear_counter { + BYTE value; + BYTE reload; + BYTE halt; +} _linear_counter; +typedef struct _apuSquare { + /* timer */ + DBWORD timer; + /* ogni quanti cicli devo generare un output */ + WORD frequency; + /* duty */ + BYTE duty; + /* envelope */ + _envelope envelope; + /* volume */ + BYTE volume; + /* sequencer */ + BYTE sequencer; + /* sweep */ + _sweep sweep; + /* length counter */ + _length_counter length; + /* output */ + SWORD output; +} _apuSquare; +typedef struct _apuTriangle { + /* timer */ + DBWORD timer; + /* ogni quanti cicli devo generare un output */ + WORD frequency; + /* linear counter */ + _linear_counter linear; + /* length counter */ + _length_counter length; + /* sequencer */ + BYTE sequencer; + /* output */ + SWORD output; +} _apuTriangle; +typedef struct _apuNoise { + /* timer */ + DBWORD timer; + /* ogni quanti cicli devo generare un output */ + WORD frequency; + /* envelope */ + _envelope envelope; + /* specifico del noise */ + BYTE mode; + /* volume */ + BYTE volume; + /* shift register */ + WORD shift; + /* length counter */ + _length_counter length; + /* sequencer */ + BYTE sequencer; + /* output */ + SWORD output; +} _apuNoise; +typedef struct _apuDMC { + /* ogni quanti cicli devo generare un output */ + WORD frequency; + + WORD remain; + BYTE irq_enabled; + BYTE loop; + BYTE rate_index; + WORD address_start; + DBWORD address; + WORD length; + BYTE counter; + BYTE empty; + BYTE buffer; + + /* DMA */ + BYTE dma_cycle; + + /* output unit */ + BYTE silence; + BYTE shift; + BYTE counter_out; + + /* output */ + SWORD output; + + /* misc */ + BYTE tick_type; +} _apuDMC; + +#if defined (__cplusplus) +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +EXTERNC struct _nla_table { + SWORD pulse[32]; + SWORD tnd[203]; +} nla_table; + +EXTERNC _apu apu; +EXTERNC _r4011 r4011; +EXTERNC _r4015 r4015; +EXTERNC _r4017 r4017; +EXTERNC _apuSquare S1, S2; +EXTERNC _apuTriangle TR; +EXTERNC _apuNoise NS; +EXTERNC _apuDMC DMC; + +/* apuPeriod[mode][type][cycles] */ +static const WORD apuPeriod[2][3][7] = { + /* + * Mode 0: 4-step sequence + * Action Envelopes & Length Counter& Interrupt Delay to next + * Linear Counter Sweep Units Flag NTSC PAL Dendy + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * $4017=$00 - - - 7459 8315 7459 + * Step 1 Clock - - 7456 8314 7456 + * Step 2 Clock Clock - 7458 8312 7458 + * Step 3 Clock - - 7458 8314 7458 + * Step 4 Clock Clock Set if enabled 7458 8314 7458 + */ + { + {7459, 7456, 7458, 7457, 1, 1, 7457}, + {8315, 8314, 8312, 8313, 1, 1, 8313}, + {7459, 7456, 7458, 7457, 1, 1, 7457} + }, + /* + * Mode 1: 5-step sequence + * Action Envelopes & Length Counter& Interrupt Delay to next + * Linear Counter Sweep Units Flag NTSC PAL Dendy + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * $4017=$80 - - - 1 1 1 + * Step 1 Clock Clock - 7458 8314 7458 + * Step 2 Clock - - 7456 8314 7456 + * Step 3 Clock Clock - 7458 8312 7458 + * Step 4 Clock - - 7458 8314 7458 + * Step 5 - - - 7452 8312 7452 + * + * Note: + * il 7452 e il 8312 dello step 5 diventano 7451 e 8311 + * nella mia tabella perche' il ciclo mancante lo eseguo + * all'inizio del ciclo successivo. + */ + { + {1, 7458, 7456, 7458, 7458, 7451, 0}, + {1, 8314, 8314, 8312, 8314, 8311, 0}, + {1, 7458, 7456, 7458, 7458, 7451, 0} + } +}; + +/* la tabella con i valori da caricare nel length counter del canale */ +static const BYTE length_table[32] = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, + 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, + 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E +}; + +static const BYTE square_duty[2][4][8] = { + { + { 1, 0, 0, 0, 0, 0, 0, 0}, + { 1, 1, 0, 0, 0, 0, 0, 0}, + { 1, 1, 1, 1, 0, 0, 0, 0}, + { 0, 0, 1, 1, 1, 1, 1, 1} + }, + { + { 1, 0, 0, 0, 0, 0, 0, 0}, + { 1, 1, 1, 1, 0, 0, 0, 0}, + { 1, 1, 0, 0, 0, 0, 0, 0}, + { 0, 0, 1, 1, 1, 1, 1, 1} + }, +}; + +static const BYTE triangle_duty[32] = { + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, +}; + +static const WORD noise_timer[3][16] = { + { + 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0060, 0x0080, 0x00A0, + 0x00CA, 0x00FE, 0x017C, 0x01FC, 0x02FA, 0x03F8, 0x07F2, 0x0FE4 + }, + { + 0x0004, 0x0007, 0x000E, 0x001E, 0x003C, 0x0058, 0x0076, 0x0094, + 0x00BC, 0x00EC, 0x0162, 0x01D8, 0x02C4, 0x03B0, 0x0762, 0x0EC2 + }, + { + 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0060, 0x0080, 0x00A0, + 0x00CA, 0x00FE, 0x017C, 0x01FC, 0x02FA, 0x03F8, 0x07F2, 0x0FE4 + } +}; + +static const WORD dmc_rate[3][16] = { + { + 0x01AC, 0x017C, 0x0154, 0x0140, 0x011E, 0x00FE, 0x00E2, 0x00D6, + 0x00BE, 0x00A0, 0x008E, 0x0080, 0x006A, 0x0054, 0x0048, 0x0036 + }, + { + 0x018E, 0x0162, 0x013C, 0x012A, 0x0114, 0x00EC, 0x00D2, 0x00C6, + 0x00B0, 0x0094, 0x0084, 0x0076, 0x0062, 0x004E, 0x0042, 0x0032 + }, + { + 0x01AC, 0x017C, 0x0154, 0x0140, 0x011E, 0x00FE, 0x00E2, 0x00D6, + 0x00BE, 0x00A0, 0x008E, 0x0080, 0x006A, 0x0054, 0x0048, 0x0036 + } +}; + +EXTERNC void apu_tick(BYTE *hwtick); +EXTERNC void apu_turn_on(void); + +#undef EXTERNC + +#endif /* APU_H_ */ diff --git a/src/engine/platform/sound/nes/common.h b/src/engine/platform/sound/nes/common.h new file mode 100644 index 00000000..8f7fefee --- /dev/null +++ b/src/engine/platform/sound/nes/common.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010-2019 Fabio Cavallo (aka FHorse) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +// additional modifications by tildearrow for furnace + +#ifndef COMMON_H_ +#define COMMON_H_ + +#include + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned int DBWORD; +typedef signed char SBYTE; +typedef signed short SWORD; +typedef signed int SDBWORD; + +#if !defined (FALSE) +enum false_value { FALSE, TRUE }; +#endif +enum exit_type { EXIT_OK, EXIT_ERROR }; +enum lower_value { LOWER, UPPER }; +enum machine_mode { AUTO, NTSC, PAL, DENDY, DEFAULT = 255 }; +enum reset_type { + RESET = 0x10, + HARD = 0x20, + CHANGE_ROM = 0x30, + CHANGE_MODE = 0x40, + POWER_UP = 0x50 +}; +/* le dimesioni dello screen da renderizzare */ +enum screen_dimension { SCR_LINES = 240, SCR_ROWS = 256 }; +enum type_of_system_info { HEADER, DATABASE }; +enum header_type { iNES_1_0, NES_2_0, UNIF_FORMAT, FDS_FORMAT, NSF_FORMAT, NSFE_FORMAT }; +enum length_file_name_type { + LENGTH_FILE_NAME = 512, + LENGTH_FILE_NAME_MID = 1024, + LENGTH_FILE_NAME_LONG = 2048, + LENGTH_FILE_NAME_MAX = 4096 +}; +enum forced_mirroring { UNK_HORIZONTAL, UNK_VERTICAL }; +enum max_chips_rom { MAX_CHIPS = 8 }; +enum languages { LNG_ENGLISH, LNG_ITALIAN, LNG_RUSSIAN }; +enum database_mode { + NODIPSWITCH = 0xFF00, + NOEXTRA = 0x0000, + VSZAPPER = 0x0001, + CHRRAM32K = 0x0002, + CHRRAM256K = 0x0004 +}; + +#define LENGTH(x) (sizeof(x)/sizeof(*(x))) +#define UNUSED(var) var __attribute__((unused)) + +#if defined (DEBUG) +#define INLINE +#else +#define INLINE inline __attribute__((always_inline)) +#endif + +#endif /* COMMON_H_ */