aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakamichi Horikawa <takamichiho@gmail.com>2016-11-30 23:01:06 +0900
committerTakamichi Horikawa <takamichiho@gmail.com>2016-11-30 23:01:06 +0900
commitae78fca8bf5835ceccdbdc902197fe082b8def30 (patch)
tree1784d80a55455e88aad56c794e59d432a5f0f33b
parent6fa6b5535a9f514ae527af58a2c0964645d26e6f (diff)
added GTK UI
-rw-r--r--.gitignore11
-rw-r--r--curses/.gitignore11
-rw-r--r--curses/main.c59
-rw-r--r--fmdriver/fmdriver.h29
-rw-r--r--fmdriver/fmdriver_common.h5
-rw-r--r--fmdriver/fmdriver_fmp.c275
-rw-r--r--fmdriver/fmdriver_fmp.h23
-rw-r--r--fmdsp/fmdsp.c252
-rw-r--r--fmdsp/fmdsp.h37
-rw-r--r--fmdsp/fmdsp_sprites.h318
-rw-r--r--gtk/.gitignore1
-rw-r--r--gtk/Makefile.am23
-rw-r--r--gtk/configure.ac10
-rw-r--r--gtk/main.c466
-rw-r--r--libopna/opnadrum.h2
15 files changed, 1432 insertions, 90 deletions
diff --git a/.gitignore b/.gitignore
index 729dc37..bbb1194 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,14 @@
*.o
.deps
.dirstamp
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+configure
+depcomp
+install-sh
+missing
+Makefile
+config.log
+config.status
diff --git a/curses/.gitignore b/curses/.gitignore
index bab26a8..ebac467 100644
--- a/curses/.gitignore
+++ b/curses/.gitignore
@@ -1,12 +1 @@
-Makefile.in
-aclocal.m4
-autom4te.cache
-compile
-configure
-depcomp
-install-sh
-missing
-Makefile
-config.log
-config.status
fmpc
diff --git a/curses/main.c b/curses/main.c
index bfcaa51..cbde86a 100644
--- a/curses/main.c
+++ b/curses/main.c
@@ -265,7 +265,7 @@ static bool readrom(struct opna *opna) {
const char *home = getenv("HOME");
char *dpath = 0;
if (home) {
- const char *datadir = "/.local/share/libopna/";
+ const char *datadir = "/.local/share/fmplayer/";
dpath = malloc(strlen(home)+strlen(datadir)+strlen(path) + 1);
if (dpath) {
strcpy(dpath, home);
@@ -443,10 +443,11 @@ int main(int argc, char **argv) {
work.opna_writereg = opna_writereg_libopna;
work.opna_status = opna_status_libopna;
work.opna = &timer;
- if (!fmp_init(&work, &fmp, g_data, filelen)) {
+ if (!fmp_load(&fmp, g_data, filelen)) {
fprintf(stderr, "not fmp\n");
return 1;
}
+ fmp_init(&work, &fmp);
bool pvi_loaded = loadpvi(&work, &fmp, argv[1]);
bool ppz_loaded = loadppzpvi(&work, &fmp, argv[1]);
@@ -465,18 +466,41 @@ int main(int argc, char **argv) {
}
SDL_PauseAudioDevice(ad, 0);
-
setlocale(LC_CTYPE, "");
+
+ initscr();
+ cbreak();
+ noecho();
+ clear();
+ refresh();
+
+ timeout(20);
+
+ static const char pdzf_mode_str[3][9] = {
+ "OFF", "STANDARD", "ENHANCED"
+ };
+ mvprintw(0, 0, "PART PTR TONE LEN VOL NOTE DET FREQ PQRAWE");
+ mvprintw(14, 61, "PPZ8");
+ mvprintw(16, 0, "FM RHYTHM SSG ADPCM");
+ mvprintw(17, 0, "TL ENV TL PTR LV LV");
+ mvprintw(21, 48, "NZ");
+ mvprintw(24, 0, "PPZ: %c%8s PVI: %c%8s PDZF: %s",
+ ppz_loaded ? ' ' : '!',
+ fmp.ppz_name,
+ pvi_loaded ? ' ' : '!',
+ fmp.pvi_name,
+ pdzf_mode_str[fmp.pdzf.mode]
+ );
+
enum {
- //TBUFLEN = 80*3*2,
- TBUFLEN = 0x10000
+ TBUFLEN = 80*2*2
};
char titlebuf[TBUFLEN+1] = {0};
- if (work.title) {
+ for (int l = 0; l < 3; l++) {
iconv_t cd = iconv_open("//IGNORE", "CP932");
if (cd != (iconv_t)-1) {
char titlebufcrlf[TBUFLEN+1] = {0};
- const char *in = work.title;
+ const char *in = work.comment[l];
size_t inleft = strlen(in)+1;
char *out = titlebufcrlf;
size_t outleft = TBUFLEN;
@@ -495,28 +519,9 @@ int main(int argc, char **argv) {
if (!titlebufcrlf[i]) break;
}
}
+ mvprintw(25+l, 0, "%s", titlebuf);
}
- initscr();
- cbreak();
- noecho();
- clear();
- refresh();
-
- timeout(20);
-
- mvprintw(0, 0, "PART PTR TONE LEN VOL NOTE DET FREQ PQRAWE");
- mvprintw(14, 61, "PPZ8");
- mvprintw(16, 0, "FM RHYTHM SSG ADPCM");
- mvprintw(17, 0, "TL ENV TL PTR LV LV");
- mvprintw(21, 48, "NZ");
- mvprintw(24, 0, "PPZ: %c%8s PVI: %c%8s",
- ppz_loaded ? ' ' : '!',
- fmp.ppz_name,
- pvi_loaded ? ' ' : '!',
- fmp.pvi_name);
- mvprintw(25, 0, "%s", titlebuf);
-
int cont = 1;
int pause = 0;
while (cont) {
diff --git a/fmdriver/fmdriver.h b/fmdriver/fmdriver.h
index 67557cc..b9946da 100644
--- a/fmdriver/fmdriver.h
+++ b/fmdriver/fmdriver.h
@@ -5,6 +5,30 @@
#include <stdbool.h>
#include "ppz8.h"
+enum {
+ FMDRIVER_TRACK_NUM = 10,
+ // 1 line = 80 characters, may contain half-width doublebyte characters
+ FMDRIVER_TITLE_BUFLEN = 80*2+1,
+};
+
+enum fmdriver_track_type {
+ FMDRIVER_TRACK_FM,
+ FMDRIVER_TRACK_SSG,
+ FMDRIVER_TRACK_ADPCM,
+ FMDRIVER_TRACK_PPZ8
+};
+
+struct fmdriver_track_status {
+ bool playing;
+ enum fmdriver_track_type type;
+ uint8_t num;
+ uint8_t ticks;
+ uint8_t ticks_left;
+ uint8_t key;
+ // key after pitchbend, LFO, etc. applied
+ uint8_t actual_key;
+};
+
struct fmdriver_work {
// set by driver, called by opna
void (*driver_opna_interrupt)(struct fmdriver_work *work);
@@ -22,8 +46,11 @@ struct fmdriver_work {
const struct ppz8_functbl *ppz8_functbl;
struct ppz8 *ppz8;
- const char *title;
+ // CP932 encoded
+ //const char *title;
+ char comment[3][FMDRIVER_TITLE_BUFLEN];
// driver status
+ struct fmdriver_track_status track_status[FMDRIVER_TRACK_NUM];
// fm3ex part map
};
diff --git a/fmdriver/fmdriver_common.h b/fmdriver/fmdriver_common.h
index cd804b8..23600cb 100644
--- a/fmdriver/fmdriver_common.h
+++ b/fmdriver/fmdriver_common.h
@@ -13,7 +13,10 @@ static inline int16_t u16s16(uint16_t v) {
return (v & 0x8000) ? ((int32_t)v)-0x10000 : v;
}
+#if 0
#include <stdio.h>
#define FMDRIVER_DEBUG(...) fprintf(stderr, __VA_ARGS__)
-
+#else
+#define FMDRIVER_DEBUG(...)
+#endif
#endif // MYON_FMDRIVER_COMMON_H_INCLUDED
diff --git a/fmdriver/fmdriver_fmp.c b/fmdriver/fmdriver_fmp.c
index 76e3798..9195276 100644
--- a/fmdriver/fmdriver_fmp.c
+++ b/fmdriver/fmdriver_fmp.c
@@ -73,6 +73,41 @@ static uint16_t fmp_fm_freq(uint8_t note) {
return freqtab[note%0xc] + ((note/0xc)<<(3+8));
}
+static uint8_t fmp_fm_freq2key(uint16_t freq) {
+ int block = freq >> (8+3);
+ int f_num = freq & ((1<<(8+3))-1);
+ if (!f_num) return 0x00;
+ while (!(f_num & (1<<(8+3-1)))) {
+ f_num <<= 1;
+ block--;
+ }
+ static const uint16_t freqtab[0xc] = {
+ 0x042e, // < 9 (a)
+ 0x046e,
+ 0x04b1,
+ 0x04f9,
+ 0x0544,
+ 0x0595,
+ 0x05ea,
+ 0x0644,
+ 0x06a3,
+ 0x0708,
+ 0x0773,
+ 0x07e4,
+ };
+ int note = 0;
+ for (; note < 12; note++) {
+ if (f_num < freqtab[note]) break;
+ }
+ note += 9;
+ block += (note/12);
+ note %= 12;
+
+ if (block < 0) return 0x00;
+ if (block > 8) return 0x8b;
+ return (block << 4) | note;
+}
+
static uint8_t fmp_ssg_octave(uint8_t note) {
return note/0xc;
}
@@ -116,6 +151,41 @@ static uint16_t fmp_part_ssg_freq(struct fmp_part *part, uint8_t note) {
return part->detune + freq;
}
+static uint8_t fmp_ssg_freq2key(uint16_t freq) {
+ if (!freq) return 0x00;
+ int octave = -5;
+ while (!(freq & 0x8000)) {
+ freq <<= 1;
+ octave++;
+ }
+ // 7987200.0 / (64*440*(2**((i+2+0.5)/12))/(1<<8))
+ static const uint16_t freqtab[0xc] = {
+ 0xf57f, // > 0 (c)
+ 0xe7b8,
+ 0xdab7,
+ 0xce70,
+ 0xc2da,
+ 0xb7ea,
+ 0xad98,
+ 0xa3da,
+ 0x9aa7,
+ 0x91f9,
+ 0x89c8,
+ 0x820c,
+ };
+ int note = 0;
+ for (; note < 12; note++) {
+ if (freq > freqtab[note]) break;
+ }
+ note += 11;
+ octave += (note/12);
+ note %= 12;
+
+ if (octave < 0) return 0x00;
+ if (octave > 8) return 0x8b;
+ return (octave << 4) | note;
+}
+
// 3172
static uint16_t fmp_adpcm_freq(uint8_t note) {
static const uint16_t freqtab[4][0xc] = {
@@ -2466,7 +2536,7 @@ static void fmp_part_cmd(struct fmdriver_work *work, struct driver_fmp *fmp,
// 1d26
cmd &= 0x7f;
if (cmd >= 0x62) {
- if (!fmp_part_cmd_exec2(work, fmp, part, cmd)) return;
+ if (!fmp_part_cmd_exec2(work, fmp, part, cmd | 0x80)) return;
continue;
} else {
// 1d33
@@ -2476,6 +2546,7 @@ static void fmp_part_cmd(struct fmdriver_work *work, struct driver_fmp *fmp,
// 1d36
// note
part->tonelen_cnt = len;
+ part->tonelen = len;
if (cmd == 0x61) {
// 1d3e
part->status.tie = false;
@@ -2641,6 +2712,62 @@ static void fmp_part_cmd_rhythm(struct fmdriver_work *work,
}
}
+static uint8_t fmp_note2key(uint8_t note) {
+ uint8_t octave = note / 0xc;
+ uint8_t key = note % 0xc;
+ key |= octave << 4;
+ return key;
+}
+
+static void fmp_work_status_update(struct fmdriver_work *work,
+ struct driver_fmp *fmp) {
+ static const uint8_t fmp_track_map[FMDRIVER_TRACK_NUM] = {
+ FMP_PART_FM_1,
+ FMP_PART_FM_2,
+ FMP_PART_FM_3,
+ FMP_PART_FM_4,
+ FMP_PART_FM_5,
+ FMP_PART_FM_6,
+ FMP_PART_SSG_1,
+ FMP_PART_SSG_2,
+ FMP_PART_SSG_3,
+ FMP_PART_ADPCM,
+ };
+
+ for (int t = 0; t < FMDRIVER_TRACK_NUM; t++) {
+ struct fmdriver_track_status *track = &work->track_status[t];
+ struct fmp_part *part = &fmp->parts[fmp_track_map[t]];
+ track->playing = !part->status.off;
+ track->num = (part->pdzf.mode ? part->pdzf.ppz8_channel : part->opna_keyon_out)+1;
+ if (part->type.adpcm) {
+ track->type = FMDRIVER_TRACK_ADPCM;
+ track->actual_key = 0xff;
+ } else if (part->type.ssg) {
+ if (part->u.ssg.env_f.ppz || part->pdzf.mode) {
+ track->type = FMDRIVER_TRACK_PPZ8;
+ track->actual_key = 0xff;
+ } else {
+ track->type = FMDRIVER_TRACK_SSG;
+ track->actual_key = part->status.rest ? 0xff : fmp_ssg_freq2key(part->prev_freq);
+ }
+ } else {
+ if (part->pdzf.mode) {
+ track->type = FMDRIVER_TRACK_PPZ8;
+ track->actual_key = 0xff;
+ } else {
+ track->type = FMDRIVER_TRACK_FM;
+ track->actual_key = part->status.rest ? 0xff : fmp_fm_freq2key(part->prev_freq);
+ }
+ }
+ if (part->type.fm && part->opna_keyon_out > 3) {
+ track->num--;
+ }
+ track->ticks = part->status.off ? 0 : part->tonelen-1;
+ track->ticks_left = part->tonelen_cnt;
+ track->key = part->status.rest ? 0xff : fmp_note2key(part->prev_note);
+ }
+}
+
// 17f8-1903
static void fmp_timerb(struct fmdriver_work *work, struct driver_fmp *fmp) {
// 1805
@@ -2693,6 +2820,7 @@ static void fmp_timerb(struct fmdriver_work *work, struct driver_fmp *fmp) {
if (!fmp->rhythm.status) {
fmp_part_cmd_rhythm(work, fmp);
}
+ fmp_work_status_update(work, fmp);
}
static void fmp_init_parts(struct fmdriver_work *work,
@@ -2839,7 +2967,7 @@ static void fmp_init_parts(struct fmdriver_work *work,
static void fmp_struct_init(struct fmdriver_work *work,
struct driver_fmp *fmp) {
// TODO
- fmp->pdzf.mode = 2;
+ //fmp->pdzf.mode = 2;
// 4e87
fmp->ssg_mix = 0x38;
// 3bb7
@@ -2942,26 +3070,97 @@ static void fmp_opna_interrupt(struct fmdriver_work *work) {
}
}
+// copy title string (CP932) to fmdriver_work struct,
+// and detect which PDZF(/Z8X) mode to use
static void fmp_title(struct fmdriver_work *work,
- uint8_t *data, uint16_t datalen,
+ struct driver_fmp *fmp,
uint16_t offset) {
- for (unsigned i = 0; ; i++) {
- int newline = 0;
- if ((offset + i) >= datalen) return;
- //if (i > 80*3*2) return;
- //if (data[offset+i] == 0x0d)
- if (data[offset+i] == 0) break;
- /*
- if ((data[offset+i] == 0x0d) || (data[offset+i] == 0x0a)){
- data[offset+i] = 0;
+ int l = 0;
+ int i = 0;
+ static const uint8_t pdzf_str[] = "using PDZF";
+ const uint8_t *data = fmp->data;
+ uint16_t datalen = fmp->datalen;
+ const uint8_t *pdzf_ptr = pdzf_str;
+ fmp->pdzf.mode = 0;
+ enum {
+ STATE_NORMAL,
+ STATE_ESC,
+ STATE_CSI,
+ STATE_SYNC,
+ } esc_state;
+ for (int si = 0; ; si++) {
+ if ((offset + i) >= datalen) {
+ work->comment[l][0] = 0;
+ return;
+ }
+ if (i > (FMDRIVER_TITLE_BUFLEN-1)) {
+ return;
+ }
+ uint8_t c = data[offset+si];
+
+ if (l >= 3) {
+ if (c) {
+ if (!fmp->pdzf.mode) {
+ fmp->pdzf.mode = 1;
+ }
+ }
+ return;
+ }
+ if (!c) return;
+ switch (esc_state) {
+ case STATE_SYNC:
+ esc_state = STATE_NORMAL;
+ continue;
+ case STATE_ESC:
+ if (c == '[') {
+ esc_state = STATE_CSI;
+ } else if (c == '!') {
+ esc_state = STATE_SYNC;
+ } else {
+ esc_state = STATE_NORMAL;
+ }
+ continue;
+ case STATE_CSI:
+ if (('0' <= c && c <= '9') || c == ';') {
+ continue;
+ } else {
+ esc_state = STATE_NORMAL;
+ continue;
+ }
+ default:
+ break;
+ }
+ // pdzf detection
+ if (c == *pdzf_ptr++) {
+ if (!*pdzf_ptr) {
+ fmp->pdzf.mode = 2;
+ }
+ } else {
+ pdzf_ptr = pdzf_str;
+ }
+
+ work->comment[l][i] = c;
+ switch (c) {
+ case 0:
+ return;
+ case '\r':
+ break;
+ case '\n':
+ work->comment[l][i] = 0;
+ l++;
+ i = 0;
+ break;
+ case 0x1b:
+ esc_state = STATE_ESC;
+ break;
+ default:
+ i++;
break;
}
- */
}
- work->title = data+offset;
}
-bool fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp,
+bool fmp_load(struct driver_fmp *fmp,
uint8_t *data, uint16_t datalen)
{
uint16_t offset = read16le(data);
@@ -3179,9 +3378,25 @@ bool fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp,
FMDRIVER_DEBUG(" FMTONEPTR: %04X\n", fmp->datainfo.fmtoneptr);
FMDRIVER_DEBUG(" SSGTONEPTR: %04X\n", fmp->datainfo.ssgtoneptr);
FMDRIVER_DEBUG(" data version: 0x%01X\n", fmp->data_version);
+ uint16_t pcmptr = read16le(data)-0x12;
+ if (pcmptr <= datalen && (pcmptr+16) < datalen) {
+ for (int i = 0; i < 8; i++) {
+ if (pviname_valid) {
+ fmp->pvi_name[i] = data[pcmptr+8+i];
+ }
+ if (pviname_valid && fmp->datainfo.flags.ppz) {
+ fmp->ppz_name[i] = data[pcmptr+0+i];
+ }
+ }
+ }
fmp->bar_tickcnt = fmp->datainfo.bar;
fmp->data = data;
fmp->datalen = datalen;
+ return true;
+}
+
+void fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp) {
+ fmp_title(work, fmp, read16le(fmp->data)+4);
fmp_struct_init(work, fmp);
fmp_init_parts(work, fmp);
uint16_t fmtoneptr = fmp->datainfo.fmtoneptr;
@@ -3189,39 +3404,9 @@ bool fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp,
fmp->data[fmtoneptr+0x18]&0x7,
(fmp->data[fmtoneptr+0x18]>>3)&0x7
);
- for (int i = 0; i < 4; i++) {
- static const uint8_t t[4] = {
- 0, 2, 1, 3,
- };
- FMDRIVER_DEBUG(" %03d %03d %03d %03d %03d %03d %03d %03d %03d\n",
- fmp->data[fmtoneptr+0x08+t[i]]&0x1f,
- fmp->data[fmtoneptr+0x0c+t[i]]&0x1f,
- fmp->data[fmtoneptr+0x10+t[i]]&0x1f,
- fmp->data[fmtoneptr+0x14+t[i]]&0x0f,
- fmp->data[fmtoneptr+0x14+t[i]]>>4,
- fmp->data[fmtoneptr+0x04+t[i]]&0x7f,
- fmp->data[fmtoneptr+0x08+t[i]]>>6,
- fmp->data[fmtoneptr+0x00+t[i]]&0x0f,
- (fmp->data[fmtoneptr+0x00+t[i]]>>4)&0x7
- );
- }
fmp_set_tempo(work, fmp);
work->driver = fmp;
work->driver_opna_interrupt = fmp_opna_interrupt;
- fmp_title(work, data, datalen, read16le(data)+4);
-// uint16_t pcmptr = read16le(data+read16le(data)-2);
- uint16_t pcmptr = read16le(data)-0x12;
- if (pcmptr <= datalen && (pcmptr+16) < datalen) {
- for (int i = 0; i < 8; i++) {
- if (pviname_valid) {
- fmp->pvi_name[i] = data[pcmptr+8+i];
- }
- if (pviname_valid && fmp->datainfo.flags.ppz) {
- fmp->ppz_name[i] = data[pcmptr+0+i];
- }
- }
- }
- return true;
}
// 4235
diff --git a/fmdriver/fmdriver_fmp.h b/fmdriver/fmdriver_fmp.h
index 8ed9451..909387c 100644
--- a/fmdriver/fmdriver_fmp.h
+++ b/fmdriver/fmdriver_fmp.h
@@ -1,6 +1,10 @@
#ifndef MYON_FMDRIVER_FMP_H_INCLUDED
#define MYON_FMDRIVER_FMP_H_INCLUDED
+#ifdef __cplusplus
+extern "C" {
+#endif
+
#include "fmdriver.h"
#include <stddef.h>
@@ -349,7 +353,8 @@ struct fmp_part {
uint16_t deltat;
} adpcm;
} u;
-
+
+ uint8_t tonelen;
struct {
uint32_t loopstart32;
uint32_t loopend32;
@@ -516,9 +521,15 @@ struct driver_fmp {
} pdzf;
};
-// warning: will overwrite data
-bool fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp,
- uint8_t *data, uint16_t datalen);
+// first: call fmp_load with zero_initialized struct driver_fmp and data
+// returns true if valid data
+// warning: will overwrite data during playback
+bool fmp_load(struct driver_fmp *fmp, uint8_t *data, uint16_t datalen);
+// then call fmp_init
+// this will set the fmp pointer to fmdriver_work::driver
+// this function will access opna
+void fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp);
+// load adpcm data
// this function will access opna
bool fmp_adpcm_load(struct fmdriver_work *work,
uint8_t *data, size_t datalen);
@@ -526,4 +537,8 @@ bool fmp_adpcm_load(struct fmdriver_work *work,
// 1da8
// 6190: fmp external characters
+#ifdef __cplusplus
+}
+#endif
+
#endif // MYON_FMDRIVER_FMP_H_INCLUDED
diff --git a/fmdsp/fmdsp.c b/fmdsp/fmdsp.c
new file mode 100644
index 0000000..4698842
--- /dev/null
+++ b/fmdsp/fmdsp.c
@@ -0,0 +1,252 @@
+#include "fmdsp.h"
+#include "fmdsp_sprites.h"
+#include "fmdriver/fmdriver.h"
+
+static void vramblit(uint8_t *vram, int x, int y,
+ const uint8_t *data, int w, int h) {
+ for (int yi = 0; yi < h; yi++) {
+ for (int xi = 0; xi < w; xi++) {
+ vram[(y+yi)*PC98_W+(x+xi)] = data[yi*w+xi];
+ }
+ }
+}
+
+static void vramblit_color(uint8_t *vram, int x, int y,
+ const uint8_t *data, int w, int h,
+ uint8_t color) {
+ for (int yi = 0; yi < h; yi++) {
+ for (int xi = 0; xi < w; xi++) {
+ vram[(y+yi)*PC98_W+(x+xi)] = data[yi*w+xi] ? color : 0;
+ }
+ }
+}
+
+static void vramblit_key(uint8_t *vram, int x, int y,
+ const uint8_t *data, int w, int h,
+ uint8_t key, uint8_t color) {
+ for (int yi = 0; yi < h; yi++) {
+ for (int xi = 0; xi < w; xi++) {
+ uint8_t d = data[yi*w+xi];
+ if (d == (key+1)) {
+ vram[(y+yi)*PC98_W+(x+xi)] = color;
+ }
+ }
+ }
+}
+
+void fmdsp_init(struct fmdsp *fmdsp) {
+ for (int i = 0; i < FMDSP_PALETTE_COLORS; i++) {
+ fmdsp->palette[i*3+0] = s_palettes[0][i*3+0];
+ fmdsp->palette[i*3+1] = s_palettes[0][i*3+1];
+ fmdsp->palette[i*3+2] = s_palettes[0][i*3+2];
+ }
+}
+
+
+static bool sjis_is_mb_start(uint8_t c) {
+ if (0x81 <= c && c <= 0x9f) return true;
+ if (0xe0 <= c && c <= 0xef) return true;
+ return false;
+}
+
+static uint16_t sjis2jis(uint8_t sjis_1st, uint8_t sjis_2nd) {
+ uint16_t jis;
+ if (sjis_1st >= 0xe0) sjis_1st -= 0x40;
+ sjis_1st -= 0x81;
+ jis = sjis_1st << 9;
+ if (sjis_2nd >= 0x80) sjis_2nd--;
+ if (sjis_2nd >= 0x9e) {
+ jis |= 0x100 | (sjis_2nd - 0x9e);
+ } else {
+ jis |= (sjis_2nd - 0x40);
+ }
+ jis += 0x2121;
+ return jis;
+}
+
+static void vram_putchar(uint16_t ptr, uint8_t *vram, const uint8_t *font,
+ int x, int y, uint8_t color) {
+ for (int yi = 0; yi < 16; yi++) {
+ for (int xi = 0; xi < 8; xi++) {
+ if (font[(ptr<<4)+yi] & (1<<(7-xi))) {
+ vram[(y+yi)*PC98_W+(x+xi)] = color;
+ }
+ }
+ }
+}
+
+static void fmdsp_putline(const char *strptr, uint8_t *vram, const uint8_t *font,
+ int y, uint8_t color) {
+ const uint8_t *cp932str = (const uint8_t *)strptr;
+ bool sjis_is2nd = false;
+ uint8_t sjis_1st;
+ int x = 0;
+
+ while (*cp932str) {
+ if (!sjis_is2nd) {
+ if (!sjis_is_mb_start(*cp932str)) {
+ if (*cp932str == '\t') {
+ if ((x+8*8) > PC98_W) return;
+ x += 8*8;
+ x &= ~(8*8-1);
+ cp932str++;
+ } else {
+ if ((x+8) > PC98_W) return;
+ vram_putchar(0x8000+*cp932str++, vram, font, x, y, color);
+ x += 8;
+ }
+ } else {
+ sjis_is2nd = true;
+ sjis_1st = *cp932str++;
+ }
+ } else {
+ uint8_t sjis_2nd = *cp932str++;
+ uint16_t jis = sjis2jis(sjis_1st, sjis_2nd);
+ uint8_t jis_1st = jis >> 8;
+ uint8_t jis_2nd = jis;
+ bool half = (jis_1st == 0x29);
+ if ((x+(half ? 8 : 16)) > PC98_W) return;
+ vram_putchar((jis_2nd<<8) | (jis_1st-0x20), vram, font, x, y, color);
+ x += 8;
+ if (!half) {
+ vram_putchar((jis_2nd<<8) | (jis_1st-0x20+0x80), vram, font, x, y, color);
+ x += 8;
+ }
+ sjis_is2nd = false;
+ }
+ }
+}
+
+void fmdsp_vram_init(struct fmdsp *fmdsp,
+ struct fmdriver_work *work,
+ const uint8_t *font,
+ uint8_t *vram) {
+ for (int y = 0; y < PC98_H; y++) {
+ for (int x = 0; x < PC98_W; x++) {
+ vram[y*PC98_W+x] = 0;
+ }
+ }
+ for (int t = 0; t < 10; t++) {
+ vramblit(vram, 1, TRACK_H*t+7, s_track, TNAME_W, TNAME_H);
+ vramblit(vram, KEY_LEFT_X, TRACK_H*t+KEY_Y, s_key_left, KEY_LEFT_W, KEY_H);
+ for (int i = 0; i < KEY_OCTAVES; i++) {
+ vramblit(vram, KEY_X+KEY_W*i, TRACK_H*t+KEY_Y,
+ s_key_bg, KEY_W, KEY_H);
+ }
+ vramblit(vram, KEY_X+KEY_W*KEY_OCTAVES, TRACK_H*t+KEY_Y,
+ s_key_right, KEY_RIGHT_W, KEY_H);
+ vramblit_color(vram, BAR_L_X, TRACK_H*t+BAR_Y,
+ s_bar_l, BAR_L_W, BAR_H, 3);
+ for (int i = 0; i < BAR_CNT; i++) {
+ vramblit_color(vram, BAR_X+BAR_W*i, TRACK_H*t+BAR_Y,
+ s_bar, BAR_W, BAR_H, 3);
+ }
+ }
+ vramblit(vram, PLAYING_X, PLAYING_Y,
+ s_playing, PLAYING_W, PLAYING_H);
+ for (int x = 74; x < PC98_W; x++) {
+ vram[332*PC98_W+x] = 7;
+ }
+ int height = (16+3)*3+8;
+ for (int y = PC98_H-height; y < PC98_H; y++) {
+ for (int x = 0; x < PC98_W; x++) {
+ vram[y*PC98_W+x] = (y&1)^(x&1) ? 3 : 0;
+ }
+ }
+ vram[(PC98_H-height)*PC98_W] = 0;
+ vram[(PC98_H-1)*PC98_W] = 0;
+ for (int i = 0; i < 3; i++) {
+ fmdsp_putline(work->comment[i], vram, font, COMMENT_Y+COMMENT_H*i, 2);
+ }
+}
+
+void fmdsp_update(struct fmdsp *fmdsp,
+ const struct fmdriver_work *work, uint8_t *vram) {
+ for (int t = 0; t < 10; t++) {
+ struct fmdriver_track_status *track = &work->track_status[t];
+ uint8_t *track_type;
+ switch (track->type) {
+ case FMDRIVER_TRACK_FM:
+ track_type = s_t_fm;
+ break;
+ case FMDRIVER_TRACK_SSG:
+ track_type = s_t_ssg;
+ break;
+ case FMDRIVER_TRACK_ADPCM:
+ track_type = s_t_adpcm;
+ break;
+ case FMDRIVER_TRACK_PPZ8:
+ track_type = s_t_ppz8;
+ break;
+ }
+ vramblit(vram, 1, TRACK_H*t+1, track_type, TNAME_W, TNAME_H);
+ vramblit(vram, NUM_X+NUM_W*0, TRACK_H*t+1, s_num[(track->num/10)%10], NUM_W, NUM_H);
+ vramblit(vram, NUM_X+NUM_W*1, TRACK_H*t+1, s_num[track->num%10], NUM_W, NUM_H);
+ for (int i = 0; i < KEY_OCTAVES; i++) {
+ vramblit(vram, KEY_X+KEY_W*i, TRACK_H*t+KEY_Y,
+ s_key_bg, KEY_W, KEY_H);
+ if (track->playing) {
+ if (track->actual_key >> 4 == i) {
+ vramblit_key(vram, KEY_X+KEY_W*i, TRACK_H*t+KEY_Y,
+ s_key_mask, KEY_W, KEY_H,
+ track->actual_key & 0xf, 8);
+ }
+ if (track->key >> 4 == i) {
+ vramblit_key(vram, KEY_X+KEY_W*i, TRACK_H*t+KEY_Y,
+ s_key_mask, KEY_W, KEY_H,
+ track->key & 0xf, 6);
+ }
+ }
+ }
+ uint8_t color_on = track->key == 0xff ? 7 : 2;
+ if (!track->playing) color_on = 3;
+ vramblit_color(vram, BAR_L_X, TRACK_H*t+BAR_Y,
+ s_bar_l, BAR_L_W, BAR_H, color_on);
+ for (int i = 0; i < BAR_CNT; i++) {
+ int c = (i < (track->ticks_left>>2)) ? color_on : 3;
+ vramblit_color(vram, BAR_X+BAR_W*i, TRACK_H*t+BAR_Y,
+ s_bar, BAR_W, BAR_H, c);
+ }
+ vramblit_color(vram, BAR_X+BAR_W*(track->ticks>>2), TRACK_H*t+BAR_Y,
+ s_bar, BAR_W, BAR_H, 7);
+ }
+}
+
+void fmdsp_vrampalette(struct fmdsp *fmdsp, const uint8_t *vram, uint8_t *vram32, int stride) {
+ for (int y = 0; y < PC98_H; y++) {
+ for (int x = 0; x < PC98_W; x++) {
+ uint8_t r = fmdsp->palette[vram[y*PC98_W+x]*3+0];
+ uint8_t g = fmdsp->palette[vram[y*PC98_W+x]*3+1];
+ uint8_t b = fmdsp->palette[vram[y*PC98_W+x]*3+2];
+ uint32_t data = (((uint32_t)r)<<16) | (((uint32_t)g)<<8) | ((uint32_t)b);
+ uint32_t *row = (uint32_t *)(vram32 + y*stride);
+ row[x] = data;
+ }
+ }
+}
+
+//2/1 - 7/14
+// 0x21 - 0x7e
+static void fontrom_copy_rows(uint8_t *font, const uint8_t *fontrom,
+ int rowstart, int rowend) {
+ for (int row = rowstart; row < rowend; row++) {
+ for (int cell = 0x20; cell < 0x80; cell++) {
+ for (int y = 0; y < 16; y++) {
+ // left
+ font[0x000+((row-0x20)<<4)+(cell<<12)+y] = fontrom[0x800+(0x60*16*2*(row-0x20))+(cell<<5)+y];
+ // right
+ font[0x800+((row-0x20)<<4)+(cell<<12)+y] = fontrom[0x800+(0x60*16*2*(row-0x20))+(cell<<5)+y+16];
+ }
+ }
+ }
+}
+
+void fmdsp_font_from_fontrom(uint8_t *font, const uint8_t *fontrom) {
+ // ANK
+ for (int i = 0; i < 256*16; i++) {
+ font[0x80000+i] = fontrom[0x800+i];
+ }
+ fontrom_copy_rows(font, fontrom, 0x21, 0x50);
+ fontrom_copy_rows(font, fontrom, 0x50, 0x76);
+ fontrom_copy_rows(font, fontrom, 0x78, 0x7d);
+}
diff --git a/fmdsp/fmdsp.h b/fmdsp/fmdsp.h
new file mode 100644
index 0000000..4c6af0c
--- /dev/null
+++ b/fmdsp/fmdsp.h
@@ -0,0 +1,37 @@
+#ifndef MYON_FMDSP_H_INCLUDED
+#define MYON_FMDSP_H_INCLUDED
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum {
+ PC98_W = 640,
+ PC98_H = 400
+};
+
+enum {
+ FMDSP_PALETTE_COLORS = 9
+};
+
+struct fmdsp {
+ uint8_t palette[FMDSP_PALETTE_COLORS*3];
+ uint8_t target_palette[FMDSP_PALETTE_COLORS*3];
+};
+
+struct fmdriver_work;
+void fmdsp_init(struct fmdsp *fmdsp);
+void fmdsp_vram_init(struct fmdsp *fmdsp,
+ struct fmdriver_work *work,
+ const uint8_t *font,
+ uint8_t *vram);
+void fmdsp_update(struct fmdsp *fmdsp, const struct fmdriver_work *work, uint8_t *vram);
+void fmdsp_vrampalette(struct fmdsp *fmdsp, const uint8_t *vram, uint8_t *vram32, int stride);
+void fmdsp_font_from_fontrom(uint8_t *font, const uint8_t *fontrom);
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MYON_FMDSP_H_INCLUDED
diff --git a/fmdsp/fmdsp_sprites.h b/fmdsp/fmdsp_sprites.h
new file mode 100644
index 0000000..8554985
--- /dev/null
+++ b/fmdsp/fmdsp_sprites.h
@@ -0,0 +1,318 @@
+static const uint8_t test[] = {
+ 0, 2, 2, 2,
+ 2, 0, 0, 0,
+ 2, 0, 2, 2,
+ 2, 0, 0, 0,
+ 2, 0, 0, 0,
+};
+
+enum {
+ TRACK_H = 32,
+ TNAME_W = 26,
+ TNAME_H = 5,
+ NUM_X = 31,
+ NUM_W = 8,
+ NUM_H = 11,
+ KEY_X = 8,
+ KEY_Y = 14,
+ KEY_W = 35,
+ KEY_H = 17,
+ KEY_LEFT_X = 1,
+ KEY_LEFT_W = 6,
+ KEY_RIGHT_W = 11,
+ KEY_OCTAVES = 8,
+ BAR_L_X = 68,
+ BAR_L_W = 14,
+ BAR_X = BAR_L_X + BAR_L_W,
+ BAR_Y = 1,
+ BAR_W = 2,
+ BAR_H = 4,
+ BAR_CNT = 64,
+ COMMENT_Y = 340,
+ COMMENT_H = 19,
+ PLAYING_X = 0,
+ PLAYING_Y = 324,
+ PLAYING_W = 72,
+ PLAYING_H = 9,
+};
+
+static const uint8_t s_palettes[1][FMDSP_PALETTE_COLORS*3] = {
+ {
+ 0, 0, 0,
+ 170, 170, 153,
+ 102, 136, 255,
+ 68, 68, 119,
+ 204, 204, 187,
+ 102, 102, 85,
+ 136, 255, 68,
+ 51, 51, 238,
+ 0, 187, 255,
+ }
+};
+
+static const uint8_t s_track[TNAME_W*TNAME_H] = {
+ 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0,
+ 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0,
+ 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1,
+};
+static const uint8_t s_t_fm[TNAME_W*TNAME_H] = {
+ 0, 2, 2, 2, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 2, 2, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+static const uint8_t s_t_ssg[TNAME_W*TNAME_H] = {
+ 0, 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 2, 0, 0, 0, 2, 2, 0, 0, 2, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+static const uint8_t s_t_adpcm[TNAME_W*TNAME_H] = {
+ 0, 2, 2, 0, 0, 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0, 2, 0, 0, 2, 0, 0,
+ 2, 0, 0, 2, 0, 2, 0, 0, 2, 0, 2, 0, 0, 2, 0, 2, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0,
+ 2, 0, 0, 2, 0, 2, 0, 0, 2, 0, 2, 0, 0, 2, 0, 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0,
+ 2, 0, 2, 2, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0,
+ 2, 0, 0, 2, 0, 2, 2, 2, 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, 0, 0, 2, 0, 0,
+};
+static const uint8_t s_t_ppz8[TNAME_W*TNAME_H] = {
+ 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 2, 0, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 2, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 2, 0, 0, 2, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+static const uint8_t s_num[10][NUM_W*NUM_H] = {
+ {
+ 0, 0, 0, 2, 2, 2, 0, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 0, 3, 3, 0, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 0, 2, 2, 2, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 3, 3, 3, 0, 0,
+ 0, 0, 3, 0, 0, 0, 3, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0,
+ 0, 0, 0, 3, 3, 0, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 0, 3, 3, 3, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 2, 2, 2, 0, 0,
+ 0, 0, 3, 0, 0, 0, 3, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0,
+ 0, 0, 0, 2, 2, 0, 0, 0,
+ 0, 2, 0, 0, 0, 3, 0, 0,
+ 0, 2, 0, 0, 0, 3, 0, 0,
+ 0, 2, 0, 0, 0, 3, 0, 0,
+ 0, 2, 0, 0, 0, 3, 0, 0,
+ 0, 0, 2, 2, 2, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 2, 2, 2, 0, 0,
+ 0, 0, 3, 0, 0, 0, 3, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0,
+ 0, 0, 3, 0, 0, 0, 2, 0,
+ 0, 0, 0, 2, 2, 0, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 0, 2, 2, 2, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 3, 3, 3, 0, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 0, 2, 2, 0, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 0, 3, 3, 3, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 2, 2, 2, 0, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 0, 2, 2, 0, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 0, 2, 2, 2, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 2, 2, 2, 0, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 0, 2, 2, 0, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 0, 2, 2, 2, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 2, 2, 2, 0, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 0, 3, 3, 0, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 0, 3, 3, 3, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 2, 2, 2, 0, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 0, 2, 2, 0, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 2, 0, 0, 0, 2, 0, 0,
+ 0, 0, 2, 2, 2, 0, 0, 0,
+ },
+ {
+ 0, 0, 0, 2, 2, 2, 0, 0,
+ 0, 0, 2, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 0, 2, 2, 0, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 3, 0, 0, 0, 2, 0, 0,
+ 0, 0, 2, 2, 2, 0, 0, 0,
+ }
+};
+static const uint8_t s_key_bg[KEY_W*KEY_H] = {
+ 4,4,4,0,0,0,4,4,0,0,0,4,4,4,0,4,4,4,0,0,0,4,4,0,0,0,4,4,0,0,0,4,4,4,0,
+ 4,4,4,0,0,0,4,4,0,0,0,4,4,4,0,4,4,4,0,0,0,4,4,0,0,0,4,4,0,0,0,4,4,4,0,
+ 4,4,4,0,0,0,4,4,0,0,0,4,4,4,0,4,4,4,0,0,0,4,4,0,0,0,4,4,0,0,0,4,4,4,0,
+ 4,4,4,0,0,0,4,4,0,0,0,4,4,4,0,4,4,4,0,0,0,4,4,0,0,0,4,4,0,0,0,4,4,4,0,
+ 4,4,4,0,0,0,4,4,0,0,0,4,4,4,0,4,4,4,0,0,0,4,4,0,0,0,4,4,0,0,0,4,4,4,0,
+ 4,4,4,0,0,0,4,4,0,0,0,4,4,4,0,4,4,4,0,0,0,4,4,0,0,0,4,4,0,0,0,4,4,4,0,
+ 4,4,4,0,0,0,4,4,0,0,0,4,4,4,0,4,4,4,0,0,0,4,4,0,0,0,4,4,0,0,0,4,4,4,0,
+ 4,4,4,5,0,0,4,4,5,0,0,4,4,4,0,4,4,4,5,0,0,4,4,5,0,0,4,4,5,0,0,4,4,4,0,
+ 4,4,4,5,5,0,4,4,5,5,0,4,4,4,0,4,4,4,5,5,0,4,4,5,5,0,4,4,5,5,0,4,4,4,0,
+ 4,4,4,0,0,0,4,4,0,0,0,4,4,4,0,4,4,4,0,0,0,4,4,0,0,0,4,4,0,0,0,4,4,4,0,
+ 4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,
+ 4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,
+ 4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,
+ 4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,
+ 4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,
+ 4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,4,4,4,4,0,
+ 5,4,4,5,0,5,4,4,5,0,5,4,4,5,0,5,4,4,5,0,5,4,4,5,0,5,4,4,5,0,5,4,4,5,0,
+};
+static const uint8_t s_key_left[KEY_LEFT_W*KEY_H] = {
+ 0, 0, 0, 4, 4, 4,
+ 0, 0, 0, 4, 4, 4,
+ 0, 0, 0, 4, 4, 4,
+ 0, 0, 0, 4, 4, 4,
+ 0, 0, 0, 4, 4, 4,
+ 0, 0, 0, 4, 4, 4,
+ 0, 0, 0, 4, 4, 4,
+ 5, 0, 0, 4, 4, 4,
+ 5, 5, 0, 4, 4, 4,
+ 0, 0, 0, 4, 4, 4,
+ 4, 0, 4, 4, 4, 4,
+ 4, 0, 4, 4, 4, 4,
+ 4, 0, 4, 4, 4, 4,
+ 4, 0, 4, 4, 4, 4,
+ 4, 0, 4, 4, 4, 4,
+ 4, 0, 4, 4, 4, 4,
+ 5, 0, 5, 4, 4, 5,
+};
+static const uint8_t s_key_right[KEY_RIGHT_W*KEY_H] = {
+ 4, 4, 4, 0, 0, 0, 4, 4, 0, 0, 0,
+ 4, 4, 4, 0, 0, 0, 4, 4, 0, 0, 0,
+ 4, 4, 4, 0, 0, 0, 4, 4, 0, 0, 0,
+ 4, 4, 4, 0, 0, 0, 4, 4, 0, 0, 0,
+ 4, 4, 4, 0, 0, 0, 4, 4, 0, 0, 0,
+ 4, 4, 4, 0, 0, 0, 4, 4, 0, 0, 0,
+ 4, 4, 4, 0, 0, 0, 4, 4, 0, 0, 0,
+ 4, 4, 4, 5, 0, 0, 4, 4, 5, 0, 0,
+ 4, 4, 4, 5, 5, 0, 4, 4, 5, 5, 0,
+ 4, 4, 4, 0, 0, 0, 4, 4, 0, 0, 0,
+ 4, 4, 4, 4, 0, 4, 4, 4, 4, 0, 4,
+ 4, 4, 4, 4, 0, 4, 4, 4, 4, 0, 4,
+ 4, 4, 4, 4, 0, 4, 4, 4, 4, 0, 4,
+ 4, 4, 4, 4, 0, 4, 4, 4, 4, 0, 4,
+ 4, 4, 4, 4, 0, 4, 4, 4, 4, 0, 4,
+ 4, 4, 4, 4, 0, 4, 4, 4, 4, 0, 4,
+ 5, 4, 4, 5, 0, 5, 4, 4, 5, 0, 5,
+};
+static const uint8_t s_key_mask[KEY_W*KEY_H] = {
+ 1,1,1,2,2,2,3,3,4,4,4,5,5,5,0,6,6,6,7,7,7,8,8,9,9,9, 10,10,11,11,11,12,12,12,0,
+ 1,1,1,2,2,2,3,3,4,4,4,5,5,5,0,6,6,6,7,7,7,8,8,9,9,9, 10,10,11,11,11,12,12,12,0,
+ 1,1,1,2,2,2,3,3,4,4,4,5,5,5,0,6,6,6,7,7,7,8,8,9,9,9, 10,10,11,11,11,12,12,12,0,
+ 1,1,1,2,2,2,3,3,4,4,4,5,5,5,0,6,6,6,7,7,7,8,8,9,9,9, 10,10,11,11,11,12,12,12,0,
+ 1,1,1,2,2,2,3,3,4,4,4,5,5,5,0,6,6,6,7,7,7,8,8,9,9,9, 10,10,11,11,11,12,12,12,0,
+ 1,1,1,2,2,2,3,3,4,4,4,5,5,5,0,6,6,6,7,7,7,8,8,9,9,9, 10,10,11,11,11,12,12,12,0,
+ 1,1,1,2,2,2,3,3,4,4,4,5,5,5,0,6,6,6,7,7,7,8,8,9,9,9, 10,10,11,11,11,12,12,12,0,
+ 1,1,1,0,2,2,3,3,0,4,4,5,5,5,0,6,6,6,0,7,7,8,8,0,9,9, 10,10,0, 11,11,12,12,12,0,
+ 1,1,1,0,0,2,3,3,0,0,4,5,5,5,0,6,6,6,0,0,7,8,8,0,0,9, 10,10,0, 0, 11,12,12,12,0,
+ 1,1,1,2,2,2,3,3,4,4,4,5,5,5,0,6,6,6,7,7,7,8,8,9,9,9, 10,10,11,11,11,12,12,12,0,
+ 1,1,1,1,0,3,3,3,3,0,5,5,5,5,0,6,6,6,6,0,8,8,8,8,0,10,10,10,10,0, 12,12,12,12,0,
+ 1,1,1,1,0,3,3,3,3,0,5,5,5,5,0,6,6,6,6,0,8,8,8,8,0,10,10,10,10,0, 12,12,12,12,0,
+ 1,1,1,1,0,3,3,3,3,0,5,5,5,5,0,6,6,6,6,0,8,8,8,8,0,10,10,10,10,0, 12,12,12,12,0,
+ 1,1,1,1,0,3,3,3,3,0,5,5,5,5,0,6,6,6,6,0,8,8,8,8,0,10,10,10,10,0, 12,12,12,12,0,
+ 1,1,1,1,0,3,3,3,3,0,5,5,5,5,0,6,6,6,6,0,8,8,8,8,0,10,10,10,10,0, 12,12,12,12,0,
+ 1,1,1,1,0,3,3,3,3,0,5,5,5,5,0,6,6,6,6,0,8,8,8,8,0,10,10,10,10,0, 12,12,12,12,0,
+ 0,1,1,0,0,0,3,3,0,0,0,5,5,0,0,0,6,6,0,0,0,8,8,0,0,0, 10,10,0, 0, 0, 12,12,0, 0,
+};
+static const uint8_t s_bar_l[BAR_L_W*BAR_H] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+};
+static const uint8_t s_bar[BAR_W*BAR_H] = {
+ 1, 0,
+ 1, 0,
+ 1, 0,
+ 1, 0,
+};
+static const uint8_t s_playing[PLAYING_W*PLAYING_H] = {
+ 2,2,2,2,2,2,2,0,0,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,2,2,0,0,0,0,2,2,0,2,2,0,2,2,0,0,0,0,2,2,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 2,2,0,0,0,0,2,2,0,2,2,0,0,0,0,0,0,2,2,0,0,0,0,2,2,0,0,2,2,0,0,2,2,0,0,2,2,0,2,2,2,0,0,0,2,2,0,2,2,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 2,2,0,0,0,0,2,2,0,2,2,0,0,0,0,0,0,2,2,0,0,0,0,2,2,0,0,0,2,2,2,2,0,0,0,2,2,0,2,2,2,2,0,0,2,2,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 2,2,0,0,0,0,2,2,0,2,2,0,0,0,0,0,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,2,2,0,2,2,0,2,2,0,2,2,0,0,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 2,2,2,2,2,2,2,0,0,2,2,0,0,0,0,0,0,2,2,2,2,2,2,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,2,2,0,0,2,2,2,2,0,2,2,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 2,2,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,2,2,0,0,0,2,2,2,0,2,2,0,0,0,0,2,2,0,0,2,2,0,0,2,2,0,0,2,2,0,0,2,2,0,
+ 2,2,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,2,2,0,0,0,0,2,2,0,0,2,2,2,2,2,2,0,0,0,2,2,0,0,2,2,0,0,2,2,0,0,2,2,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+};
diff --git a/gtk/.gitignore b/gtk/.gitignore
new file mode 100644
index 0000000..ea5e662
--- /dev/null
+++ b/gtk/.gitignore
@@ -0,0 +1 @@
+fmplayer
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
new file mode 100644
index 0000000..9a4790b
--- /dev/null
+++ b/gtk/Makefile.am
@@ -0,0 +1,23 @@
+bin_PROGRAMS=fmplayer
+
+LIBOPNA_SRC=../libopna/opnaadpcm.c \
+ ../libopna/opnadrum.c \
+ ../libopna/opnafm.c \
+ ../libopna/opnassg.c \
+ ../libopna/opnatimer.c \
+ ../libopna/opna.c
+
+FMDRIVER_SRC=../fmdriver/fmdriver_fmp.c \
+ ../fmdriver/ppz8.c
+
+FMDSP_SRC=../fmdsp/fmdsp.c
+
+fmplayer_SOURCES=main.c \
+ $(LIBOPNA_SRC) \
+ $(FMDRIVER_SRC) \
+ $(FMDSP_SRC)
+
+fmplayer_CPPFLAGS=-Wall -Wextra -pedantic \
+ -I.. \
+ $(GTK3_CFLAGS) $(PORTAUDIO_CFLAGS)
+fmplayer_LDADD=$(GTK3_LIBS) $(PORTAUDIO_LIBS)
diff --git a/gtk/configure.ac b/gtk/configure.ac
new file mode 100644
index 0000000..bde5f6e
--- /dev/null
+++ b/gtk/configure.ac
@@ -0,0 +1,10 @@
+AC_INIT([fmplayer], [0.1.0])
+AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
+AC_PROG_CC_C99
+
+dnl AM_PATH_SDL2([2.0.5])
+PKG_CHECK_MODULES([PORTAUDIO], [portaudio-2.0])
+PKG_CHECK_MODULES([GTK3], [gtk+-3.0 cairo])
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/gtk/main.c b/gtk/main.c
new file mode 100644
index 0000000..7dd183a
--- /dev/null
+++ b/gtk/main.c
@@ -0,0 +1,466 @@
+#include <gtk/gtk.h>
+#include <portaudio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <cairo.h>
+
+#include "fmdriver/fmdriver_fmp.h"
+#include "fmdriver/ppz8.h"
+#include "libopna/opna.h"
+#include "libopna/opnatimer.h"
+#include "fmdsp/fmdsp.h"
+
+#define DATADIR "/.local/share/fmplayer/"
+
+enum {
+ SRATE = 55467,
+ PPZ8MIX = 0xa000,
+ FONT_ROM_SIZE = 0x84000,
+ FONT_ROM_FILESIZE = 0x46800,
+ AUDIOBUFLEN = 0,
+};
+
+static struct {
+ GtkWidget *mainwin;
+ bool pa_initialized;
+ PaStream *pastream;
+ struct opna opna;
+ struct opna_timer opna_timer;
+ struct ppz8 ppz8;
+ struct fmdriver_work work;
+ struct fmdsp fmdsp;
+ char drum_rom[OPNA_ROM_SIZE];
+ bool drum_rom_loaded;
+ char adpcm_ram[OPNA_ADPCM_RAM_SIZE];
+ struct driver_fmp *fmp;
+ void *fmpdata;
+ void *ppzbuf;
+ uint8_t vram[PC98_W*PC98_H];
+ uint8_t font[FONT_ROM_SIZE];
+ void *vram32;
+ int vram32_stride;
+} g;
+
+static void quit(void) {
+ if (g.pastream) {
+ Pa_CloseStream(g.pastream);
+ }
+ if (g.pa_initialized) Pa_Terminate();
+ free(g.fmp);
+ free(g.fmpdata);
+ free(g.ppzbuf);
+ gtk_main_quit();
+}
+
+static void on_destroy(GtkWidget *w, gpointer ptr) {
+ quit();
+}
+
+static void on_menu_quit(GtkMenuItem *menuitem, gpointer ptr) {
+ quit();
+}
+
+static void msgbox_err(const char *msg) {
+ GtkWidget *d = gtk_message_dialog_new(GTK_WINDOW(g.mainwin), GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ msg);
+ gtk_dialog_run(GTK_DIALOG(d));
+ gtk_widget_destroy(d);
+}
+
+
+static int pastream_cb(const void *inptr, void *outptr, unsigned long frames,
+ const PaStreamCallbackTimeInfo *timeinfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userdata) {
+ struct opna_timer *timer = (struct opna_timer *)userdata;
+ int16_t *buf = (int16_t *)outptr;
+ memset(outptr, 0, sizeof(int16_t)*frames*2);
+ opna_timer_mix(timer, buf, frames);
+ return paContinue;
+}
+
+static void opna_int_cb(void *userptr) {
+ struct fmdriver_work *work = (struct fmdriver_work *)userptr;
+ work->driver_opna_interrupt(work);
+}
+
+static void opna_mix_cb(void *userptr, int16_t *buf, unsigned samples) {
+ struct ppz8 *ppz8 = (struct ppz8 *)userptr;
+ ppz8_mix(ppz8, buf, samples);
+}
+
+static void opna_writereg_libopna(struct fmdriver_work *work, unsigned addr, unsigned data) {
+ struct opna_timer *timer = (struct opna_timer *)work->opna;
+ opna_timer_writereg(timer, addr, data);
+}
+
+static uint8_t opna_status_libopna(struct fmdriver_work *work, bool a1) {
+ struct opna_timer *timer = (struct opna_timer *)work->opna;
+ uint8_t status = opna_timer_status(timer);
+ if (!a1) {
+ status &= 0x83;
+ }
+ return status;
+}
+
+static FILE *pvisearch(const char *filename, const char *pvibase) {
+ // TODO: not SJIS aware
+ char pviname[8+3+2] = {0};
+ char pviname_l[8+3+2] = {0};
+ strcpy(pviname, pvibase);
+ strcat(pviname, ".PVI");
+ strcpy(pviname_l, pviname);
+ for (char *c = pviname_l; *c; c++) {
+ if (('A' <= *c) && (*c <= 'Z')) {
+ *c += ('a' - 'A');
+ }
+ }
+ FILE *pvifile = fopen(pviname, "r");
+ if (pvifile) return pvifile;
+ pvifile = fopen(pviname_l, "r");
+ if (pvifile) return pvifile;
+ char *slash = strrchr(filename, '/');
+ if (!slash) return 0;
+ char *pvipath = malloc((slash-filename)+1+sizeof(pviname));
+ if (!pvipath) return 0;
+ memcpy(pvipath, filename, slash-filename+1);
+ pvipath[slash-filename+1] = 0;
+ strcat(pvipath, pviname);
+ pvifile = fopen(pvipath, "r");
+ if (pvifile) {
+ free(pvipath);
+ return pvifile;
+ }
+ pvipath[slash-filename+1] = 0;
+ strcat(pvipath, pviname_l);
+ pvifile = fopen(pvipath, "r");
+ if (pvifile) {
+ free(pvipath);
+ return pvifile;
+ }
+ free(pvipath);
+ return 0;
+}
+
+static bool loadpvi(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ const char *filename) {
+ // no need to load, always success
+ if(strlen(fmp->pvi_name) == 0) return true;
+ FILE *pvifile = pvisearch(filename, fmp->pvi_name);
+ if (!pvifile) goto err;
+ if (fseek(pvifile, 0, SEEK_END) != 0) goto err_file;
+ size_t fsize;
+ {
+ long size = ftell(pvifile);
+ if (size < 0) goto err_file;
+ fsize = size;
+ }
+ if (fseek(pvifile, 0, SEEK_SET) != 0) goto err_file;
+ void *data = malloc(fsize);
+ if (!data) goto err_file;
+ if (fread(data, 1, fsize, pvifile) != fsize) goto err_memory;
+ if (!fmp_adpcm_load(work, data, fsize)) goto err_memory;
+ free(data);
+ fclose(pvifile);
+ return true;
+err_memory:
+ free(data);
+err_file:
+ fclose(pvifile);
+err:
+ return false;
+}
+
+static bool loadppzpvi(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ const char *filename) {
+ // no need to load, always success
+ if(strlen(fmp->ppz_name) == 0) return true;
+ FILE *pvifile = pvisearch(filename, fmp->ppz_name);
+ if (!pvifile) goto err;
+ if (fseek(pvifile, 0, SEEK_END) != 0) goto err_file;
+ size_t fsize;
+ {
+ long size = ftell(pvifile);
+ if (size < 0) goto err_file;
+ fsize = size;
+ }
+ if (fseek(pvifile, 0, SEEK_SET) != 0) goto err_file;
+ void *data = malloc(fsize);
+ if (!data) goto err_file;
+ if (fread(data, 1, fsize, pvifile) != fsize) goto err_memory;
+ int16_t *decbuf = calloc(ppz8_pvi_decodebuf_samples(fsize), sizeof(int16_t));
+ if (!decbuf) goto err_memory;
+ if (!ppz8_pvi_load(work->ppz8, 0, data, fsize, decbuf)) goto err_decbuf;
+ free(g.ppzbuf);
+ g.ppzbuf = decbuf;
+ free(data);
+ fclose(pvifile);
+ return true;
+err_decbuf:
+ free(decbuf);
+err_memory:
+ free(data);
+err_file:
+ fclose(pvifile);
+err:
+ return false;
+}
+
+static void load_drumrom(void) {
+ const char *path = "ym2608_adpcm_rom.bin";
+ const char *home = getenv("HOME");
+ char *dpath = 0;
+ if (home) {
+ const char *datadir = DATADIR;
+ dpath = malloc(strlen(home)+strlen(datadir)+strlen(path) + 1);
+ if (dpath) {
+ strcpy(dpath, home);
+ strcat(dpath, datadir);
+ strcat(dpath, path);
+ path = dpath;
+ }
+ }
+ FILE *rhythm = fopen(path, "r");
+ free(dpath);
+ if (!rhythm) goto err;
+ if (fseek(rhythm, 0, SEEK_END) != 0) goto err_file;
+ long size = ftell(rhythm);
+ if (size != OPNA_ROM_SIZE) goto err_file;
+ if (fseek(rhythm, 0, SEEK_SET) != 0) goto err_file;
+ if (fread(g.drum_rom, 1, OPNA_ROM_SIZE, rhythm) != OPNA_ROM_SIZE) goto err_file;
+ fclose(rhythm);
+ g.drum_rom_loaded = true;
+ return;
+err_file:
+ fclose(rhythm);
+err:
+ return;
+}
+
+static void load_fontrom(void) {
+ const char *path = "font.rom";
+ const char *home = getenv("HOME");
+ char *dpath = 0;
+ if (home) {
+ const char *datadir = DATADIR;
+ dpath = malloc(strlen(home)+strlen(datadir)+strlen(path) + 1);
+ if (dpath) {
+ strcpy(dpath, home);
+ strcat(dpath, datadir);
+ strcat(dpath, path);
+ path = dpath;
+ }
+ }
+ FILE *font = fopen(path, "r");
+ free(dpath);
+ if (!font) goto err;
+ if (fseek(font, 0, SEEK_END) != 0) goto err_file;
+ long size = ftell(font);
+ if (size != FONT_ROM_FILESIZE) goto err_file;
+ if (fseek(font, 0, SEEK_SET) != 0) goto err_file;
+ uint8_t *fontbuf = malloc(FONT_ROM_FILESIZE);
+ if (!fontbuf) goto err_file;
+ if (fread(fontbuf, 1, FONT_ROM_FILESIZE, font) != FONT_ROM_FILESIZE) goto err_fontbuf;
+ fmdsp_font_from_fontrom(g.font, fontbuf);
+ free(fontbuf);
+ fclose(font);
+ return;
+err_fontbuf:
+ free(fontbuf);
+err_file:
+ fclose(font);
+err:
+ return;
+}
+
+static bool openfile(const char *path) {
+ if (!g.pa_initialized) {
+ msgbox_err("Could not initialize Portaudio");
+ goto err;
+ }
+ FILE *fmfile = fopen(path, "r");
+ if (!fmfile) {
+ msgbox_err("Cannot open file");
+ goto err;
+ }
+ if (fseek(fmfile, 0, SEEK_END) != 0) {
+ msgbox_err("cannot seek file to end");
+ goto err_file;
+ }
+ size_t filelen;
+ {
+ long tfilelen = ftell(fmfile);
+ if ((tfilelen < 0) || (tfilelen > 0xffff)) {
+ msgbox_err("invalid file length");
+ goto err_file;
+ }
+ filelen = tfilelen;
+ }
+ if (fseek(fmfile, 0, SEEK_SET) != 0) {
+ msgbox_err("cannot seek file to beginning");
+ goto err_file;
+ }
+ void *fmbuf = malloc(filelen);
+ if (!fmbuf) {
+ msgbox_err("cannot allocate memory for file");
+ goto err_file;
+ }
+ if (fread(fmbuf, 1, filelen, fmfile) != filelen) {
+ msgbox_err("cannot read file");
+ goto err_fmbuf;
+ }
+ struct driver_fmp *fmp = calloc(1, sizeof(struct driver_fmp));
+ if (!fmp) {
+ msgbox_err("cannot allocate memory for fmp");
+ goto err_fmbuf;
+ }
+ if (!fmp_load(fmp, fmbuf, filelen)) {
+ msgbox_err("invalid FMP file");
+ goto err_fmp;
+ }
+ if (!g.pastream) {
+ PaError pe = Pa_OpenDefaultStream(&g.pastream, 0, 2, paInt16, SRATE, AUDIOBUFLEN,
+ pastream_cb, &g.opna_timer);
+ if (pe != paNoError) {
+ msgbox_err("cannot open portaudio stream");
+ goto err_fmp;
+ }
+ } else {
+ PaError pe = Pa_StopStream(g.pastream);
+ if (pe != paNoError) {
+ msgbox_err("Portaudio Error");
+ goto err_fmp;
+ }
+ }
+ free(g.fmp);
+ g.fmp = fmp;
+ free(g.fmpdata);
+ g.fmpdata = fmbuf;
+ opna_reset(&g.opna);
+ if (!g.drum_rom_loaded) {
+ load_drumrom();
+ }
+ if (g.drum_rom_loaded) {
+ opna_drum_set_rom(&g.opna.drum, g.drum_rom);
+ }
+ opna_adpcm_set_ram_256k(&g.opna.adpcm, g.adpcm_ram);
+ ppz8_init(&g.ppz8, SRATE, PPZ8MIX);
+ opna_timer_reset(&g.opna_timer, &g.opna);
+ memset(&g.work, 0, sizeof(g.work));
+ g.work.opna_writereg = opna_writereg_libopna;
+ g.work.opna_status = opna_status_libopna;
+ g.work.opna = &g.opna_timer;
+ g.work.ppz8 = &g.ppz8;
+ g.work.ppz8_functbl = &ppz8_functbl;
+ opna_timer_set_int_callback(&g.opna_timer, opna_int_cb, &g.work);
+ opna_timer_set_mix_callback(&g.opna_timer, opna_mix_cb, &g.ppz8);
+ fmp_init(&g.work, g.fmp);
+ fmdsp_vram_init(&g.fmdsp, &g.work, g.font, g.vram);
+ loadpvi(&g.work, g.fmp, path);
+ loadppzpvi(&g.work, g.fmp, path);
+ fclose(fmfile);
+ Pa_StartStream(g.pastream);
+ return true;
+err_fmp:
+ free(fmp);
+err_fmbuf:
+ free(fmbuf);
+err_file:
+ fclose(fmfile);
+err:
+ return false;
+}
+
+static void on_file_activated(GtkFileChooser *chooser, gpointer ptr) {
+ gchar *filename = gtk_file_chooser_get_filename(chooser);
+ if (filename) {
+ openfile(filename);
+ g_free(filename);
+ }
+}
+
+static GtkWidget *create_menubar() {
+ GtkWidget *menubar = gtk_menu_bar_new();
+ GtkWidget *menu = gtk_menu_new();
+ GtkWidget *file = gtk_menu_item_new_with_label("File");
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(file), menu);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menubar), file);
+ GtkWidget *open = gtk_menu_item_new_with_label("Open");
+ //g_signal_connect(open, "activate", G_CALLBACK(on_menu_open), 0);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), open);
+ GtkWidget *quit = gtk_menu_item_new_with_label("Quit");
+ g_signal_connect(quit, "activate", G_CALLBACK(on_menu_quit), 0);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), quit);
+ return menubar;
+}
+
+static gboolean draw_cb(GtkWidget *w,
+ cairo_t *cr,
+ gpointer p) {
+ fmdsp_update(&g.fmdsp, &g.work, g.vram);
+ fmdsp_vrampalette(&g.fmdsp, g.vram, g.vram32, g.vram32_stride);
+ cairo_surface_t *s = cairo_image_surface_create_for_data(
+ g.vram32, CAIRO_FORMAT_RGB24, PC98_W, PC98_H, g.vram32_stride);
+ cairo_scale(cr, 2.0, 2.0);
+ cairo_set_source_surface(cr, s, 0.0, 0.0);
+ cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
+ cairo_paint(cr);
+ cairo_surface_destroy(s);
+ return FALSE;
+}
+
+static gboolean tick_cb(GtkWidget *w,
+ GdkFrameClock *frame_clock,
+ gpointer p) {
+ (void)frame_clock;
+ gtk_widget_queue_draw(GTK_WIDGET(p));
+ return G_SOURCE_CONTINUE;
+}
+
+static void destroynothing(gpointer p) {
+ (void)p;
+}
+
+int main(int argc, char **argv) {
+ load_fontrom();
+ gtk_init(&argc, &argv);
+ GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ g.mainwin = w;
+ //gtk_window_set_resizable(GTK_WINDOW(w), FALSE);
+ gtk_window_set_title(GTK_WINDOW(w), "FMPlayer");
+ g_signal_connect(w, "destroy", G_CALLBACK(on_destroy), 0);
+ GtkWidget *box = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(w), box);
+
+ GtkWidget *menubar = create_menubar();
+ gtk_box_pack_start(GTK_BOX(box), menubar, FALSE, TRUE, 0);
+
+ GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
+
+ GtkWidget *drawarea = gtk_drawing_area_new();
+ gtk_widget_set_size_request(drawarea, PC98_W*2, PC98_H*2);
+ g_signal_connect(drawarea, "draw", G_CALLBACK(draw_cb), 0);
+ gtk_box_pack_start(GTK_BOX(hbox), drawarea, FALSE, TRUE, 0);
+
+ GtkWidget *filechooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_OPEN);
+ g_signal_connect(filechooser, "file-activated", G_CALLBACK(on_file_activated), 0);
+ gtk_box_pack_start(GTK_BOX(hbox), filechooser, TRUE, TRUE, 0);
+
+
+ g.pa_initialized = (Pa_Initialize() == paNoError);
+ fmdsp_init(&g.fmdsp);
+ fmdsp_vram_init(&g.fmdsp, &g.work, g.font, g.vram);
+ g.vram32_stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, PC98_W);
+ g.vram32 = malloc((g.vram32_stride*PC98_H)*4);
+
+ gtk_widget_show_all(w);
+ gtk_widget_add_tick_callback(w, tick_cb, drawarea, destroynothing);
+ gtk_main();
+ return 0;
+}
diff --git a/libopna/opnadrum.h b/libopna/opnadrum.h
index 24bcd48..7c13098 100644
--- a/libopna/opnadrum.h
+++ b/libopna/opnadrum.h
@@ -14,7 +14,7 @@ extern "C" {
#define OPNA_ROM_HH_START 0x1b80
#define OPNA_ROM_TOM_START 0x1d00
#define OPNA_ROM_RIM_START 0x1f80
-#define OPNA_ROM_SIZE 0x2000
+#define OPNA_ROM_SIZE 0x2000
#define OPNA_ROM_BD_SIZE ((OPNA_ROM_SD_START-OPNA_ROM_BD_START)*2*3)
#define OPNA_ROM_SD_SIZE ((OPNA_ROM_TOP_START-OPNA_ROM_SD_START)*2*3)