xm_player/src/interpreter.cpp

271 lines
7.6 KiB
C++

#include <stdint.h>
#include "printf/printf.h"
#include "aica/aica.hpp"
#include "interpreter.hpp"
#include "sound.hpp"
namespace interpreter {
struct interpreter_state state = {};
// quater-semitones
//
// for i in range(48):
// round(1024 * (2 ** (i / 48) - 1))
//
const static int16_t cent_to_fns[] = {
0, 15, 30, 45, 61, 77, 93, 109, 125, 142, 159, 176,
194, 211, 229, 248, 266, 285, 304, 323, 343, 363, 383, 403,
424, 445, 467, 488, 510, 533, 555, 578, 601, 625, 649, 673,
698, 723, 749, 774, 801, 827, 854, 881, 909, 937, 966, 995
};
const int cent_to_fns_length = (sizeof (cent_to_fns)) / (sizeof (cent_to_fns[0]));
uint16_t
note_to_oct_fns(const int8_t note)
{
// log(8363 / 44100) / log(2)
const float base_ratio = -2.3986861877015477;
float c4_note = (float)note - 49.0;
float ratio = base_ratio + (c4_note / 12.0);
float whole = (int)ratio;
float fraction;
if (ratio < 0) {
if (whole > ratio)
whole -= 1;
fraction = -(whole - ratio);
} else {
fraction = ratio - whole;
}
assert(fraction >= 0.0);
assert(fraction < 1.0);
int fns = cent_to_fns[(int)(fraction * cent_to_fns_length)];
return aica::oct_fns::OCT((int)whole) | aica::oct_fns::FNS((int)fns);
}
const static int8_t volume_table[] = {
0, 3, 5, 6, 7, 8, 8, 9, 9, 9, 10, 10, 10, 10, 11, 11,
11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
15
};
void _play_note(int ch, const xm_pattern_format_t * pf)
{
int instrument = (pf->instrument != 0) ? pf->instrument : state.channel[ch].instrument;
if (instrument == 0)
instrument = 1;
state.channel[ch].instrument = instrument;
xm_sample_header_t * sample_header = state.xm.sample_header[instrument - 1];
int start = state.xm.sample_data_offset[instrument - 1];
int sample_type = ((sample_header->type & (1 << 4)) != 0);
int bytes_per_sample = 1 + sample_type;
int loop_type = sample_header->type & 0b11;
int lpctl = (loop_type == 0) ? 0 : 1;
int lsa = s32(&sample_header->sample_loop_start) / bytes_per_sample;
int len = s32(&sample_header->sample_loop_length) / bytes_per_sample;
if (len == 0) {
len = s32(&sample_header->sample_length) / bytes_per_sample;
}
if (len >= 65535) {
len = 65532;
}
assert(start >= 0);
assert(lsa >= 0);
assert(len >= 0);
if (loop_type == 2) // bidirectional
len += len - 2;
assert(sample_header->volume >= 0 && sample_header->volume <= 64);
int disdl = volume_table[sample_header->volume];
bool pcms = !sample_type;
wait(); aica_sound.channel[ch].PCMS(pcms);
wait(); aica_sound.channel[ch].SA(start);
wait(); aica_sound.channel[ch].LPCTL(lpctl);
wait(); aica_sound.channel[ch].LSA((lsa) & ~(0b11));
wait(); aica_sound.channel[ch].LEA((lsa + len) & ~(0b11));
wait(); aica_sound.channel[ch].DISDL(disdl);
wait(); aica_sound.channel[ch].oct_fns = note_to_oct_fns(pf->note + sample_header->relative_note_number);
if (pf->effect_type == 0x04) { // vibrato
wait(); aica_sound.channel[ch].LFOF(0x12);
wait(); aica_sound.channel[ch].ALFOWS(2);
wait(); aica_sound.channel[ch].PLFOWS(2);
wait(); aica_sound.channel[ch].ALFOS(0);
wait(); aica_sound.channel[ch].PLFOS(4);
} else {
//wait(); aica_sound.channel[ch].LFOF(0x11);
//wait(); aica_sound.channel[ch].ALFOWS(2);
//wait(); aica_sound.channel[ch].PLFOWS(2);
wait(); aica_sound.channel[ch].ALFOS(0);
wait(); aica_sound.channel[ch].PLFOS(0);
}
wait(); aica_sound.channel[ch].KYONB(1);
}
void play_note_effect(int ch, const xm_pattern_format_t * pf)
{
int effect_tick = (state.tick / 2) % state.ticks_per_line;
switch (pf->effect_type) {
case 0x04: // 4 vibrato
wait(); aica_sound.channel[ch].LFOF(0x12);
wait(); aica_sound.channel[ch].ALFOWS(2);
wait(); aica_sound.channel[ch].PLFOWS(2);
wait(); aica_sound.channel[ch].ALFOS(0);
wait(); aica_sound.channel[ch].PLFOS(4);
break;
case 0x0d: // D pattern break
state.pattern_break = pf->effect_parameter;
break;
case 0x0e: // E
switch (pf->effect_parameter & 0xf0) {
case 0xd0: // ED note delay
if (effect_tick == (pf->effect_parameter & 0x0f)) {
_play_note(ch, pf);
}
break;
}
break;
case 0x14: // K delayed tick
if (effect_tick == pf->effect_parameter) {
wait(); aica_sound.channel[ch].KYONB(0);
}
break;
}
}
void play_note(int ch, const xm_pattern_format_t * pf)
{
if (pf->note == 97) {
wait(); aica_sound.channel[ch].KYONB(0);
} else if (pf->note != 0) {
bool note_delay = (pf->effect_type == 0xe) && ((pf->effect_parameter & 0xf0) == 0xd0); // ED note delay
if (!note_delay)
_play_note(ch, pf);
}
play_note_effect(ch, pf);
}
/*
void play_debug_note(int ch, xm_pattern_format_t * pf)
{
debug_note(ch, pf);
play_note(ch, pf);
}
*/
void rekey_note(int ch, const xm_pattern_format_t * pf)
{
if (pf->note == 97) {
} else if (pf->note != 0) {
wait(); aica_sound.channel[ch].KYONB(0);
}
}
static inline void next_pattern()
{
if (state.pattern_break >= 0)
state.next_line_index = state.pattern_break;
else
state.next_line_index = 0;
state.pattern_break = -1;
state.pattern_order_table_index += 1;
if (state.pattern_order_table_index >= state.xm.song_length)
state.pattern_order_table_index = 0;
printf("pattern_order_table_index: %d\n", state.pattern_order_table_index);
state.pattern_index = state.xm.header->pattern_order_table[state.pattern_order_table_index];
printf("note_count: %d\n", state.xm.pattern_note_count[state.pattern_index]);
}
template <void (*F)(int, const xm_pattern_format_t *)>
void execute_line(int line_index)
{
int line_pattern_index = line_index * state.xm.number_of_channels;
for (int ch = 0; ch < state.xm.number_of_channels; ch++) {
xm_pattern_format_t * pattern = state.xm.pattern[state.pattern_index];
F(ch, &pattern[line_pattern_index + ch]);
}
}
void interrupt()
{
bool keyoff_tick = (state.tick + 1) % (state.ticks_per_line * 2) == 0;
bool note_tick = state.tick % (state.ticks_per_line * 2) == 0;
bool effect_tick = (state.tick & 1) == 0;
bool pattern_break_tick = (state.tick % (state.ticks_per_line * 2)) == (state.ticks_per_line * 2 - 1);
if (keyoff_tick) {
// execute keyoffs
execute_line<rekey_note>(state.next_line_index);
}
if (state.pattern_break >= 0 && pattern_break_tick) {
printf("pattern_break\n");
next_pattern();
}
if (note_tick) {
// execute notes
state.line_index = state.next_line_index;
state.next_line_index += 1;
execute_line<play_note>(state.line_index);
} else if (effect_tick) {
// execute effects
execute_line<play_note_effect>(state.line_index);
}
wait(); aica_sound.channel[0].KYONEX(1);
int note_index = state.next_line_index * state.xm.number_of_channels;
bool end_of_pattern = note_index >= state.xm.pattern_note_count[state.pattern_index];
if (end_of_pattern && pattern_break_tick) {
printf("end_of_pattern\n");
next_pattern();
}
state.tick += 1;
}
void init()
{
// 195 = 1ms
// 2500 / bpm milliseconds
int default_bpm = s16(&state.xm.header->default_bpm);
int default_tempo = s16(&state.xm.header->default_tempo);
int tick_rate = 195.32 * 2500 / default_bpm;
printf("default_bpm %d\n", default_bpm);
printf("default_tempo %d\n", default_tempo);
printf("tick_rate %d\n", tick_rate);
state.tick_rate = tick_rate;
state.ticks_per_line = default_tempo;
state.tick = 0;
state.line_index = 0;
state.pattern_order_table_index = -1;
next_pattern();
printf("tick_rate %d\n", state.tick_rate);
}
}