271 lines
7.6 KiB
C++
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);
|
|
}
|
|
|
|
}
|