#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_CONFIG_OPTION68_H
# include <config_option68.h>
#else
# include "default_option68.h"
#endif
#include "ymemul.h"
#include "emu68/assert68.h"
#include <sc68/msg68.h>
#include <sc68/option68.h>
#include <string.h>
#ifndef BREAKPOINT68
# define BREAKPOINT68 assert(!"breakpoint")
#endif
#ifndef DEBUG_YM_O
# define DEBUG_YM_O 0
#endif
int ym_cat = msg68_DEFAULT;
int ym_default_chans = 7;
#include "ym_linear_table.inc"
#include "ym_atarist_table.inc"
static s16 ymout5[32*32*32];
static inline
void access_list_reset(ym_waccess_list_t * const access_list,
const char * name,
const cycle68_t ymcycle)
{
if (!name) name = "und";
access_list->name[0] = name[0];
access_list->name[1] = name[1];
access_list->name[2] = name[2];
access_list->name[3] = 0;
access_list->head = access_list->tail = 0;
}
static void access_list_add(ym_t * const ym,
ym_waccess_list_t * const access_list,
const int reg, const int val,
const cycle68_t ymcycle)
{
ym_waccess_t * free_access = ym->waccess_nxt;
if (free_access >= ym->waccess+ym->waccess_max) {
TRACE68(msg68_CRITICAL,
"ym-2149: access list *%s* -- *OVERFLOW*\n", access_list->name);
return;
}
ym->waccess_nxt = free_access+1;
free_access->ymcycle = ymcycle;
free_access->reg = reg;
free_access->val = val;
free_access->link = 0;
if (access_list->tail) {
access_list->tail->link = free_access;
} else {
access_list->head = free_access;
}
access_list->tail = free_access;
}
static void access_adjust_cycle(ym_waccess_list_t * const access_list,
const cycle68_t ymcycles)
{
ym_waccess_t * access;
for (access = access_list->head; access; access = access->link) {
access->ymcycle -= ymcycles;
}
}
int ym_reset(ym_t * const ym, const cycle68_t ymcycle)
{
int ret = -1;
if (ym) {
int i;
if (ym->cb_reset) {
ym->cb_reset(ym,ymcycle);
}
for (i=0; i<sizeof(ym->reg.index)/sizeof(*ym->reg.index); ++i) {
ym->reg.index[i] = 0;
}
ym->ctrl = 0;
ym->reg.name.ctl_mixer = 077;
ym->reg.name.env_shape = 0x0A;
access_list_reset(&ym->ton_regs, "Ton", ymcycle);
access_list_reset(&ym->noi_regs, "Noi", ymcycle);
access_list_reset(&ym->env_regs, "Env", ymcycle);
for (i=0; i<sizeof(ym->reg.index)/sizeof(*ym->reg.index); ++i) {
ym->shadow.index[i] = ym->reg.index[i];
}
ret = 0;
}
return ret;
}
#ifndef YM_ENGINE
# define YM_ENGINE YM_ENGINE_BLEP
#endif
#ifndef YM_VOL_TABLE
# define YM_VOL_TABLE YM_VOL_ATARIST
#endif
static ym_parms_t default_parms;
static const int output_level = 0xCAFE;
static const char prefix[] = "sc68-";
static const char engcat[] = "ym-2149";
static option68_t opts[] = {
{ option68_STR, prefix, "ym-engine", engcat,
"set ym-2149 engine [pulse|blep|dump]" },
{ option68_STR, prefix, "ym-volmodel", engcat,
"set ym-2149 volume model [atari|linear|atari4]" },
{ option68_INT, prefix, "ym-chans", engcat,
"set ym-2149 active channel [bit-0:A ... bit-2:C]" }
};
int ym_init(int * argc, char ** argv)
{
option68_t * opt;
ym_cat = msg68_cat("ym","ym-2149 emulator",DEBUG_YM_O);
default_parms.engine = YM_ENGINE;
default_parms.volmodel = YM_VOL_TABLE;
default_parms.clock = YM_CLOCK_ATARIST;
default_parms.hz = SAMPLING_RATE_DEF;
option68_append(opts,sizeof(opts)/sizeof(*opts));
*argc = option68_parse(*argc,argv,0);
opt = option68_get("ym-engine",1);
if (opt) {
int k;
if (!strcmp(opt->val.str,"pulse")) {
k = YM_ENGINE_PULS;
} else if (!strcmp(opt->val.str,"blep")) {
k = YM_ENGINE_BLEP;
} else if (!strcmp(opt->val.str,"dump")) {
k = YM_ENGINE_DUMP;
} else {
k = YM_ENGINE_DEFAULT;
}
ym_engine(0, k);
}
opt = option68_get("ym-volmodel",1);
if (opt) {
if (!strcmp(opt->val.str,"linear")) {
default_parms.volmodel = YM_VOL_LINEAR;
} else if (!strcmp(opt->val.str,"atari")) {
default_parms.volmodel = YM_VOL_ATARIST;
} else if (!strcmp(opt->val.str,"atari4")) {
default_parms.volmodel = YM_VOL_ATARIST_4BIT;
}
}
opt = option68_get("ym-chans",1);
if (opt) {
ym_default_chans = opt->val.num & 7;
}
switch (default_parms.volmodel) {
case YM_VOL_LINEAR:
ym_create_5bit_linear_table(ymout5, output_level);
break;
case YM_VOL_ATARIST_4BIT:
ym_create_4bit_atarist_table(ymout5, output_level);
break;
case YM_VOL_DEFAULT:
case YM_VOL_ATARIST:
default:
ym_create_5bit_atarist_table(ymout5, output_level);
}
*argc = ym_puls_options(*argc, argv);
return 0;
}
void ym_shutdown(void)
{
}
int ym_run(ym_t * const ym, s32 * output, const cycle68_t ymcycles)
{
if (!ymcycles) {
return 0;
}
if ( (ymcycles&31) || !output) {
return -1;
}
return ym->cb_run(ym,output,ymcycles);
}
void ym_writereg(ym_t * const ym,
const int val, const cycle68_t ymcycle)
{
const int reg = ym->ctrl;
if (reg >= 0 && reg < 16) {
ym->shadow.index[reg] = val;
switch(reg) {
case YM_PERL(0): case YM_PERH(0):
case YM_PERL(1): case YM_PERH(1):
case YM_PERL(2): case YM_PERH(2):
case YM_VOL(0): case YM_VOL(1): case YM_VOL(2):
access_list_add(ym, &ym->ton_regs, reg, val, ymcycle);
break;
case YM_ENVL: case YM_ENVH: case YM_ENVTYPE:
access_list_add(ym, &ym->env_regs, reg, val, ymcycle);
break;
case YM_MIXER:
access_list_add(ym, &ym->ton_regs, reg, val, ymcycle);
case YM_NOISE:
access_list_add(ym, &ym->noi_regs, reg, val, ymcycle);
break;
default:
break;
}
}
}
void ym_adjust_cycle(ym_t * const ym, const cycle68_t ymcycles)
{
if (ym) {
access_adjust_cycle(&ym->ton_regs, ymcycles);
access_adjust_cycle(&ym->noi_regs, ymcycles);
access_adjust_cycle(&ym->env_regs, ymcycles);
}
}
extern const int ym_smsk_table[];
int ym_active_channels(ym_t * const ym, const int clr, const int set)
{
int v = 0;
if (ym) {
const int voice_mute = ym->voice_mute;
v = ( voice_mute & 1 ) | ( (voice_mute>>5) & 2 ) | ( (voice_mute>>10) & 4);
v = ( (v & ~clr ) | set ) & 7;
ym->voice_mute = ym_smsk_table[v];
msg68_notice("ym-2149: active channels -- *%c%c%c*\n",
(v&1)?'A':'.', (v&2)?'B':'.', (v&4)?'C':'.');
}
return v;
}
static
const char * ym_engine_name(int emul)
{
switch (emul) {
case YM_ENGINE_PULS: return "PULSE";
case YM_ENGINE_BLEP: return "BLEP";
case YM_ENGINE_DUMP: return "DUMP";
}
return 0;
}
int ym_engine(ym_t * const ym, int engine)
{
switch (engine) {
case YM_ENGINE_QUERY:
engine = ym ? ym->engine : default_parms.engine;
break;
default:
msg68_warning("ym-2149: unknown ym-engine -- *%d*\n", engine);
case YM_ENGINE_DEFAULT:
engine = default_parms.engine;
case YM_ENGINE_PULS:
case YM_ENGINE_BLEP:
case YM_ENGINE_DUMP:
if (!ym) {
default_parms.engine = engine;
msg68_notice("ym-2149: default engine -- *%s*\n",
ym_engine_name(engine));
} else {
ym->engine = engine;
}
break;
}
return engine;
}
int ym_clock(ym_t * const ym, int clock)
{
switch (clock) {
case YM_CLOCK_QUERY:
clock = ym ? ym->clock : default_parms.clock;
break;
case YM_CLOCK_DEFAULT:
clock = default_parms.clock;
default:
if (clock != YM_CLOCK_ATARIST) {
msg68_warning("ym-2149: unsupported clock -- %u\n",
(unsigned int)clock);
}
clock = YM_CLOCK_ATARIST;
if (!ym) {
default_parms.clock = clock;
msg68_notice("ym-2149: default clock -- *ATARI-ST*\n",
(unsigned int)clock);
} else {
clock = ym->clock;
}
break;
}
return clock;
}
static
const char * ym_volmodel_name(int model)
{
switch (model) {
case YM_VOL_LINEAR: return "LINEAR";
case YM_VOL_ATARIST: return "ATARI-ST";
}
return 0;
}
int ym_volume_model(ym_t * const ym, int model)
{
switch (model) {
case YM_VOL_QUERY:
model = default_parms.volmodel;
break;
default:
msg68_warning("ym-2149: unknown volume model -- %d\n", model);
case YM_VOL_DEFAULT:
model = default_parms.volmodel;
case YM_VOL_LINEAR:
case YM_VOL_ATARIST:
if (ym) {
model = ym->volmodel;
} else {
ym->volmodel = model;
assert(model == YM_VOL_LINEAR || model == YM_VOL_ATARIST);
if ( model == YM_VOL_LINEAR ) {
ym_create_5bit_linear_table(ymout5, output_level);
} else {
ym_create_5bit_atarist_table(ymout5, output_level);
}
msg68_notice("ym-2149: default volume model -- *%s*\n",
ym_volmodel_name(model));
}
break;
}
return model;
}
int ym_sampling_rate(ym_t * const ym, int hz)
{
switch (hz) {
case YM_VOL_QUERY:
hz = ym ? ym->hz : default_parms.hz;
break;
case YM_VOL_DEFAULT:
hz = default_parms.hz;
default:
if (hz < SAMPLING_RATE_MIN) hz = SAMPLING_RATE_MIN;
if (hz > SAMPLING_RATE_MAX) hz = SAMPLING_RATE_MAX;
if (ym->cb_sampling_rate) {
hz = ym->cb_sampling_rate(ym,hz);
}
if (ym) {
ym->hz = hz;
} else {
default_parms.hz = hz;
}
msg68_notice("ym-2149: %ssampling rate -- *%dhz*\n",
ym ? "" : "default ", hz);
}
return hz;
}
int ym_configure(ym_t * const ym, ym_parms_t * const parms)
{
if (!parms) {
msg68_error("ym-2149: nothing to configure\n");
return -1;
}
parms->engine = ym_engine(ym, parms->engine);
parms->volmodel = ym_volume_model(ym, parms->volmodel);
parms->clock = ym_clock(ym, parms->clock);
parms->hz = ym_sampling_rate(ym, parms->hz);
return 0;
}
int ym_setup(ym_t * const ym, ym_parms_t * const parms)
{
ym_parms_t * const p = parms ? parms : &default_parms;
int err = -1;
if (p->engine == YM_ENGINE_DEFAULT) {
p->engine = default_parms.engine;
}
if (p->hz == 0) {
p->hz = default_parms.hz;
}
switch (p->clock) {
case YM_CLOCK_ATARIST:
break;
case YM_CLOCK_DEFAULT:
default:
p->clock = default_parms.clock;
}
TRACE68(ym_cat,"ym-2149: setup -- engine:%d rate:%d clock:%d level:%d\n",
p->engine,p->hz,p->clock,256);
if (ym) {
ym->ymout5 = ymout5;
ym->waccess_max = sizeof(ym->static_waccess)/sizeof(*ym->static_waccess);
ym->waccess = ym->static_waccess;
ym->waccess_nxt = ym->waccess;
ym->clock = p->clock;
ym->voice_mute = ym_smsk_table[7 & ym_default_chans];
ym->cb_sampling_rate = 0;
ym_sampling_rate(ym, p->hz);
ym->engine = p->engine;
TRACE68(ym_cat,"ym-2149: engine -- *%d*\n", p->engine);
switch (p->engine) {
case YM_ENGINE_PULS:
err = ym_puls_setup(ym);
break;
case YM_ENGINE_BLEP:
err = ym_blep_setup(ym);
break;
case YM_ENGINE_DUMP:
err = ym_dump_setup(ym);
break;
default:
msg68_critical("ym-2149: engine %d -- *invalid*\n", p->engine);
err = -1;
}
if (!err)
msg68_notice("ym-2149: engine -- *%s*\n", ym_engine_name(ym->engine));
ym_sampling_rate(ym, ym->hz);
}
ym_active_channels(ym,0,0);
msg68(ym_cat,"ym-2149: trace level -- *active*\n");
return err ? err : ym_reset(ym, 0);
}
void ym_cleanup(ym_t * const ym)
{
TRACE68(ym_cat,"%s","ym-2149: cleanup\n");
if (ym && ym->cb_cleanup) {
ym->cb_cleanup(ym);
}
}
uint68_t ym_buffersize(const ym_t * ym, const cycle68_t ymcycles)
{
return ym->cb_buffersize(ym,ymcycles);
}