aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakamichi Horikawa <takamichiho@gmail.com>2017-03-27 23:33:40 +0900
committerTakamichi Horikawa <takamichiho@gmail.com>2017-03-27 23:33:40 +0900
commit30c59a00956142aafda87c0bdc71c46d1a2218ff (patch)
tree0bf1c81767dc8edb86ade2a4e224392e2ac5280b
parent0073f2b8befc6163f2970cb7a01e75fffc95994e (diff)
add oscilloscope view
-rw-r--r--gtk/oscilloview.c83
-rw-r--r--gtk/oscilloview.h17
-rw-r--r--libopna/opna.c18
-rw-r--r--libopna/opna.h6
-rw-r--r--libopna/opnafm.c43
-rw-r--r--libopna/opnafm.h3
-rw-r--r--libopna/opnassg.c67
-rw-r--r--libopna/opnassg.h7
-rw-r--r--libopna/opnatimer.c10
-rw-r--r--libopna/opnatimer.h2
-rw-r--r--oscillo/oscillo.h17
-rw-r--r--win32/oscilloview.c139
-rw-r--r--win32/oscilloview.h19
13 files changed, 405 insertions, 26 deletions
diff --git a/gtk/oscilloview.c b/gtk/oscilloview.c
new file mode 100644
index 0000000..724ada9
--- /dev/null
+++ b/gtk/oscilloview.c
@@ -0,0 +1,83 @@
+#include <gtk/gtk.h>
+#include <cairo.h>
+#include "oscilloview.h"
+#include <string.h>
+
+struct oscilloview oscilloview_g = {
+ .flag = ATOMIC_FLAG_INIT
+};
+
+enum {
+ VIEW_SAMPLES = 1024,
+ VIEW_SKIP = 2,
+ WIDTH = 600,
+ HEIGHT = 300,
+};
+
+static struct {
+ GtkWidget *win;
+ struct oscillodata oscillodata[LIBOPNA_OSCILLO_TRACK_COUNT];
+} g;
+
+static void on_destroy(GtkWidget *w, gpointer ptr) {
+ (void)w;
+ (void)ptr;
+ g.win = 0;
+}
+
+static void draw_track(cairo_t *cr,
+ double x, double y, double w, double h,
+ const struct oscillodata *data) {
+ int start = OSCILLO_SAMPLE_COUNT - VIEW_SAMPLES;
+ start -= (data->offset >> OSCILLO_OFFSET_SHIFT);
+ if (start < 0) start = 0;
+ for (int i = 0; i < (VIEW_SAMPLES / VIEW_SKIP); i++) {
+ cairo_line_to(cr, x + ((i)*w)/(VIEW_SAMPLES / VIEW_SKIP), y + h/2.0 - (data[0].buf[start + i*VIEW_SKIP] / 16384.0) * h/2);
+ }
+ cairo_stroke(cr);
+}
+
+static gboolean draw_cb(GtkWidget *w,
+ cairo_t *cr,
+ gpointer ptr) {
+ (void)w;
+ (void)ptr;
+ cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
+ if (!atomic_flag_test_and_set_explicit(
+ &oscilloview_g.flag, memory_order_acquire)) {
+ memcpy(g.oscillodata,
+ oscilloview_g.oscillodata,
+ sizeof(oscilloview_g.oscillodata));
+ atomic_flag_clear_explicit(&oscilloview_g.flag, memory_order_release);
+ }
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
+ cairo_paint(cr);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 3; y++) {
+ draw_track(cr, x*WIDTH, y*HEIGHT, WIDTH, HEIGHT, &g.oscillodata[x*3+y]);
+ }
+ }
+ return FALSE;
+}
+
+static gboolean tick_cb(GtkWidget *w, GdkFrameClock *clock, gpointer ptr) {
+ (void)clock;
+ (void)ptr;
+ gtk_widget_queue_draw(w);
+ return G_SOURCE_CONTINUE;
+}
+
+void show_oscilloview(void) {
+ if (!g.win) {
+ g.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(g.win), "Oscilloscope view");
+ g_signal_connect(g.win, "destroy", G_CALLBACK(on_destroy), 0);
+ }
+ GtkWidget *drawarea = gtk_drawing_area_new();
+ gtk_container_add(GTK_CONTAINER(g.win), drawarea);
+ gtk_widget_set_size_request(drawarea, WIDTH*3, HEIGHT*3);
+ g_signal_connect(drawarea, "draw", G_CALLBACK(draw_cb), 0);
+ gtk_widget_add_tick_callback(drawarea, tick_cb, 0, 0);
+ gtk_widget_show_all(g.win);
+}
diff --git a/gtk/oscilloview.h b/gtk/oscilloview.h
new file mode 100644
index 0000000..edabcd9
--- /dev/null
+++ b/gtk/oscilloview.h
@@ -0,0 +1,17 @@
+#ifndef MYON_FMPLAYER_GTK_OSCILLOVIEW_H_INCLUDED
+#define MYON_FMPLAYER_GTK_OSCILLOVIEW_H_INCLUDED
+
+#include "libopna/opna.h"
+#include "oscillo/oscillo.h"
+
+#include <stdatomic.h>
+
+extern struct oscilloview {
+ atomic_flag flag;
+ struct oscillodata oscillodata[LIBOPNA_OSCILLO_TRACK_COUNT];
+} oscilloview_g;
+
+void show_oscilloview(void);
+
+#endif // MYON_FMPLAYER_GTK_OSCILLOVIEW_H_INCLUDED
+
diff --git a/libopna/opna.c b/libopna/opna.c
index 10155d0..1b0aa6c 100644
--- a/libopna/opna.c
+++ b/libopna/opna.c
@@ -1,4 +1,6 @@
#include "opna.h"
+#include "oscillo/oscillo.h"
+#include <string.h>
void opna_reset(struct opna *opna) {
opna_fm_reset(&opna->fm);
@@ -23,8 +25,20 @@ unsigned opna_readreg(const struct opna *opna, unsigned reg) {
}
void opna_mix(struct opna *opna, int16_t *buf, unsigned samples) {
- opna_fm_mix(&opna->fm, buf, samples);
- opna_ssg_mix_55466(&opna->ssg, &opna->resampler, buf, samples);
+ opna_mix_oscillo(opna, buf, samples, 0);
+}
+
+void opna_mix_oscillo(struct opna *opna, int16_t *buf, unsigned samples, struct oscillodata *oscillo) {
+ if (oscillo) {
+ for (int i = 0; i < LIBOPNA_OSCILLO_TRACK_COUNT; i++) {
+ memmove(&oscillo[i].buf[0],
+ &oscillo[i].buf[samples],
+ (OSCILLO_SAMPLE_COUNT - samples)*sizeof(oscillo[i].buf[0]));
+ }
+ }
+ unsigned offset = OSCILLO_SAMPLE_COUNT - samples;
+ opna_fm_mix(&opna->fm, buf, samples, &oscillo[0], offset);
+ opna_ssg_mix_55466(&opna->ssg, &opna->resampler, buf, samples, &oscillo[6], offset);
opna_drum_mix(&opna->drum, buf, samples);
opna_adpcm_mix(&opna->adpcm, buf, samples);
}
diff --git a/libopna/opna.h b/libopna/opna.h
index 7d7d722..2ebca0d 100644
--- a/libopna/opna.h
+++ b/libopna/opna.h
@@ -30,6 +30,10 @@ enum {
LIBOPNA_CHAN_ADPCM = 0x8000,
};
+enum {
+ LIBOPNA_OSCILLO_TRACK_COUNT = 11
+};
+
struct opna {
struct opna_fm fm;
struct opna_ssg ssg;
@@ -43,6 +47,8 @@ void opna_reset(struct opna *opna);
void opna_writereg(struct opna *opna, unsigned reg, unsigned val);
unsigned opna_readreg(const struct opna *opna, unsigned reg);
void opna_mix(struct opna *opna, int16_t *buf, unsigned samples);
+struct oscillodata;
+void opna_mix_oscillo(struct opna *opna, int16_t *buf, unsigned samples, struct oscillodata *oscillo);
unsigned opna_get_mask(const struct opna *opna);
void opna_set_mask(struct opna *opna, unsigned mask);
diff --git a/libopna/opnafm.c b/libopna/opnafm.c
index 3f5367f..abc36fa 100644
--- a/libopna/opnafm.c
+++ b/libopna/opnafm.c
@@ -1,4 +1,5 @@
#include "opnafm.h"
+#include "oscillo/oscillo.h"
#include "opnatables.h"
@@ -539,7 +540,46 @@ void opna_fm_chan_env(struct opna_fm_channel *chan) {
}
}
-void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples) {
+static int gcd(int a, int b) {
+ if (a < b) {
+ int t = a;
+ a = b;
+ b = t;
+ }
+ for (;;) {
+ int r = a % b;
+ if (!r) break;
+ a = b;
+ b = r;
+ }
+ return b;
+}
+
+
+
+void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples,
+ struct oscillodata *oscillo, unsigned offset) {
+ if (oscillo) {
+ for (unsigned c = 0; c < 6; c++) {
+ const struct opna_fm_channel *ch = &fm->channel[c];
+ unsigned freq = blkfnum2freq(ch->blk, ch->fnum);
+ int mul[4];
+ for (int i = 0; i < 4; i++) {
+ mul[i] = ch->slot[i].mul << 1;
+ if (!mul[i]) mul[i] = 1;
+ }
+ freq *= gcd(gcd(gcd(mul[0], mul[1]), mul[2]), mul[3]);
+ freq /= 2;
+ unsigned period = 0;
+ if (freq) period = (1u<<(20+OSCILLO_OFFSET_SHIFT)) / freq;
+ if (period) {
+ oscillo[c].offset += (samples << OSCILLO_OFFSET_SHIFT);
+ oscillo[c].offset %= period;
+ } else {
+ oscillo[c].offset = 0;
+ }
+ }
+ }
for (unsigned i = 0; i < samples; i++) {
if (!fm->env_div3) {
for (int c = 0; c < 6; c++) {
@@ -554,6 +594,7 @@ void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples) {
for (int c = 0; c < 6; c++) {
int16_t o = opna_fm_chanout(&fm->channel[c]);
+ if (oscillo) oscillo[c].buf[offset+i] = o*2;
// TODO: CSM
if (c == 2 && fm->ch3.mode != CH3_MODE_NORMAL) {
opna_fm_chan_phase_se(&fm->channel[c], fm);
diff --git a/libopna/opnafm.h b/libopna/opnafm.h
index 1a7f55a..b62556e 100644
--- a/libopna/opnafm.h
+++ b/libopna/opnafm.h
@@ -88,7 +88,8 @@ struct opna_fm {
};
void opna_fm_reset(struct opna_fm *fm);
-void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples);
+struct oscillodata;
+void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples, struct oscillodata *oscillo, unsigned offset);
void opna_fm_writereg(struct opna_fm *fm, unsigned reg, unsigned val);
//
diff --git a/libopna/opnassg.c b/libopna/opnassg.c
index 223758c..ec03437 100644
--- a/libopna/opnassg.c
+++ b/libopna/opnassg.c
@@ -1,4 +1,5 @@
#include "opnassg.h"
+#include "oscillo/oscillo.h"
/*
static const float voltable[32] = {
0.0f, 0.0f, 0x1.ae89f9p-8f, 0x1.000000p-7f,
@@ -126,10 +127,12 @@ static bool opna_ssg_tone_out(const struct opna_ssg *ssg, int chan) {
return (ssg->ch[chan].out || (reg & 0x1)) && ((ssg->lfsr & 1) || (reg & 0x8));
}
+#if 0
static bool opna_ssg_tone_silent(const struct opna_ssg *ssg, int chan) {
unsigned reg = ssg->regs[0x7] >> chan;
return (reg & 0x1) && (reg & 0x8);
}
+#endif
static int opna_ssg_noise_period(const struct opna_ssg *ssg) {
return ssg->regs[0x6] & 0x1f;
@@ -149,6 +152,10 @@ int opna_ssg_channel_level(const struct opna_ssg *ssg, int ch) {
: (opna_ssg_tone_volume(ssg, ch) << 1) + 1;
}
+#define COEFF 0x3fff
+#define COEFFSH 14
+
+// 3 samples per frame
void opna_ssg_generate_raw(struct opna_ssg *ssg, int16_t *buf, int samples) {
for (int i = 0; i < samples; i++) {
if (((++ssg->noise_counter) >> 1) >= opna_ssg_noise_period(ssg)) {
@@ -173,31 +180,38 @@ void opna_ssg_generate_raw(struct opna_ssg *ssg, int16_t *buf, int samples) {
}
}
- int16_t out = 0;
+ //int16_t out = 0;
for (int ch = 0; ch < 3; ch++) {
+ buf[i*3+ch] = 0;
if (++ssg->ch[ch].tone_counter >= opna_ssg_tone_period(ssg, ch)) {
ssg->ch[ch].tone_counter = 0;
ssg->ch[ch].out = !ssg->ch[ch].out;
}
- if (ssg->mask & (1<<ch)) continue;
#if 1
- // may output DC offset
// YMF288 seems to disable output when 0 <= Tp < 8
+ int32_t previntmp = 0;
if (opna_ssg_tone_out(ssg, ch)) {
int level = opna_ssg_chan_env(ssg, ch)
? opna_ssg_env_level(ssg)
: (opna_ssg_tone_volume(ssg, ch) << 1) + 1;
- out += voltable[level];
+ //out += voltable[level];
+ previntmp = voltable[level]/2;
}
+ previntmp *= COEFF;
+ ssg->prevout[ch] = previntmp - ssg->previn[ch] + ((((int64_t)COEFF)*ssg->prevout[ch]) >> COEFFSH);
+ ssg->previn[ch] = previntmp;
+ buf[i*3+ch] = ssg->prevout[ch] >> COEFFSH;
+ //buf[i*3+ch] = voltable[level]/2;
#else
if (!opna_ssg_tone_silent(ssg, ch)) {
int level = opna_ssg_channel_level(ssg, ch);
- out += (opna_ssg_tone_out(ssg, ch) ? voltable[level] : -voltable[level]) / 2;
+ //out += (opna_ssg_tone_out(ssg, ch) ? voltable[level] : -voltable[level]) / 2;
+ buf[i*3+ch] = (opna_ssg_tone_out(ssg, ch) ? voltable[level] : -voltable[level]) / 4;
}
#endif
}
- buf[i] = out / 2;
+ //buf[i] = out / 2;
}
}
@@ -205,27 +219,46 @@ void opna_ssg_generate_raw(struct opna_ssg *ssg, int16_t *buf, int samples) {
void opna_ssg_mix_55466(
struct opna_ssg *ssg, struct opna_ssg_resampler *resampler,
- int16_t *buf, int samples) {
+ int16_t *buf, int samples,
+ struct oscillodata *oscillo, unsigned offset
+) {
+ if (oscillo) {
+ for (unsigned c = 0; c < 3; c++) {
+ unsigned period = (opna_ssg_tone_period(ssg, c) << OSCILLO_OFFSET_SHIFT) * 2 * 32 / 144;
+ if (period) {
+ oscillo[c].offset += (samples << OSCILLO_OFFSET_SHIFT);
+ oscillo[c].offset %= period;
+ } else {
+ oscillo[c].offset = 0;
+ }
+ }
+ }
for (int i = 0; i < samples; i++) {
{
int ssg_samples = ((resampler->index + 9)>>1) - ((resampler->index)>>1);
- int16_t ssgbuf[5];
+ int16_t ssgbuf[15];
opna_ssg_generate_raw(ssg, ssgbuf, ssg_samples);
for (int j = 0; j < ssg_samples; j++) {
- resampler->buf[BUFINDEX(j)] = ssgbuf[j];
+ resampler->buf[BUFINDEX(j)*3+0] = ssgbuf[j*3+0];
+ resampler->buf[BUFINDEX(j)*3+1] = ssgbuf[j*3+1];
+ resampler->buf[BUFINDEX(j)*3+2] = ssgbuf[j*3+2];
}
resampler->index += 9;
}
int32_t sample = 0;
- for (int j = 0; j < SINCTABLELEN; j++) {
- unsigned sincindex = j*2;
- if (!(resampler->index&1)) sincindex++;
- bool sincsign = sincindex & (1<<(SINCTABLEBIT));
- unsigned sincmask = ((1<<(SINCTABLEBIT))-1);
- sincindex = (sincindex & sincmask) ^ (sincsign ? sincmask : 0);
- sample += (resampler->buf[BUFINDEX(j)] * sinctable[sincindex])>>2;
+ for (int ch = 0; ch < 3; ch++) {
+ int32_t chsample = 0;
+ for (int j = 0; j < SINCTABLELEN; j++) {
+ unsigned sincindex = j*2;
+ if (!(resampler->index&1)) sincindex++;
+ bool sincsign = sincindex & (1<<(SINCTABLEBIT));
+ unsigned sincmask = ((1<<(SINCTABLEBIT))-1);
+ sincindex = (sincindex & sincmask) ^ (sincsign ? sincmask : 0);
+ chsample += (resampler->buf[BUFINDEX(j)*3+ch] * sinctable[sincindex])>>2;
+ }
+ if (oscillo) oscillo[ch].buf[offset+i] = chsample >> 13;
+ if (!(ssg->mask & (1<<ch))) sample += chsample;
}
-
sample >>= 16;
sample *= 13000;
sample >>= 14;
diff --git a/libopna/opnassg.h b/libopna/opnassg.h
index 8a59d91..0321163 100644
--- a/libopna/opnassg.h
+++ b/libopna/opnassg.h
@@ -25,10 +25,12 @@ struct opna_ssg {
bool env_hld;
bool env_holding;
unsigned mask;
+ int32_t previn[3];
+ int32_t prevout[3];
};
struct opna_ssg_resampler {
- int16_t buf[(1<<7)];
+ int16_t buf[(1<<7)*3];
unsigned index;
};
@@ -46,9 +48,10 @@ void opna_ssg_generate_raw(struct opna_ssg *ssg, int16_t *buf, int samples);
// call to buffer written with OPNA output
// samplerate: 7987200/144 Hz
// (55466.66..) Hz
+struct oscillodata;
void opna_ssg_mix_55466(
struct opna_ssg *ssg, struct opna_ssg_resampler *resampler,
- int16_t *buf, int samples);
+ int16_t *buf, int samples, struct oscillodata *oscillo, unsigned offset);
void opna_ssg_writereg(struct opna_ssg *ssg, unsigned reg, unsigned val);
unsigned opna_ssg_readreg(const struct opna_ssg *ssg, unsigned reg);
// channel level (0 - 31)
diff --git a/libopna/opnatimer.c b/libopna/opnatimer.c
index 789c28e..142176f 100644
--- a/libopna/opnatimer.c
+++ b/libopna/opnatimer.c
@@ -1,5 +1,6 @@
#include "opnatimer.h"
#include "opna.h"
+#include "oscillo/oscillo.h"
enum {
TIMERA_BITS = 10,
@@ -65,9 +66,12 @@ void opna_timer_writereg(struct opna_timer *timer, unsigned reg, unsigned val) {
}
}
}
-#include <stdio.h>
-#include <stdlib.h>
+
void opna_timer_mix(struct opna_timer *timer, int16_t *buf, unsigned samples) {
+ opna_timer_mix_oscillo(timer, buf, samples, 0);
+}
+
+void opna_timer_mix_oscillo(struct opna_timer *timer, int16_t *buf, unsigned samples, struct oscillodata *oscillo) {
do {
unsigned generate_samples = samples;
if (timer->timerb_enable && timer->timerb_load) {
@@ -82,7 +86,7 @@ void opna_timer_mix(struct opna_timer *timer, int16_t *buf, unsigned samples) {
generate_samples = timera_samples;
}
}
- opna_mix(timer->opna, buf, generate_samples);
+ opna_mix_oscillo(timer->opna, buf, generate_samples, oscillo);
if (timer->mix_cb) {
timer->mix_cb(timer->mix_userptr, buf, generate_samples);
}
diff --git a/libopna/opnatimer.h b/libopna/opnatimer.h
index ad0fe0b..c7b9511 100644
--- a/libopna/opnatimer.h
+++ b/libopna/opnatimer.h
@@ -37,6 +37,8 @@ void opna_timer_set_mix_callback(struct opna_timer *timer,
opna_timer_mix_cb_t func, void *userptr);
void opna_timer_writereg(struct opna_timer *timer, unsigned reg, unsigned val);
void opna_timer_mix(struct opna_timer *timer, int16_t *buf, unsigned samples);
+struct oscillodata;
+void opna_timer_mix_oscillo(struct opna_timer *timer, int16_t *buf, unsigned samples, struct oscillodata *oscillo);
#ifdef __cplusplus
}
diff --git a/oscillo/oscillo.h b/oscillo/oscillo.h
new file mode 100644
index 0000000..451f9f4
--- /dev/null
+++ b/oscillo/oscillo.h
@@ -0,0 +1,17 @@
+#ifndef MYON_FMPLAYER_OSCILLO_H_INCLUDED
+#define MYON_FMPLAYER_OSCILLO_H_INCLUDED
+
+#include <stdint.h>
+
+enum {
+ OSCILLO_SAMPLE_COUNT = 8192,
+ OSCILLO_OFFSET_SHIFT = 10,
+};
+
+struct oscillodata {
+ int16_t buf[OSCILLO_SAMPLE_COUNT];
+ unsigned offset;
+
+};
+
+#endif // MYON_FMPLAYER_OSCILLO_H_INCLUDED
diff --git a/win32/oscilloview.c b/win32/oscilloview.c
new file mode 100644
index 0000000..9759cc1
--- /dev/null
+++ b/win32/oscilloview.c
@@ -0,0 +1,139 @@
+#include "oscilloview.h"
+#include <mmsystem.h>
+#include <shellapi.h>
+#include <windowsx.h>
+
+enum {
+ TIMER_UPDATE = 1
+};
+
+struct oscilloview oscilloview_g = {
+ .flag = ATOMIC_FLAG_INIT
+};
+
+enum {
+ VIEW_SAMPLES = 1024,
+ VIEW_SKIP = 2,
+};
+
+static struct {
+ HINSTANCE hinst;
+ HWND parent;
+ HWND oscilloview;
+ ATOM oscilloview_class;
+ struct oscillodata oscillodata[LIBOPNA_OSCILLO_TRACK_COUNT];
+ UINT mmtimer;
+} g;
+
+static void on_destroy(HWND hwnd) {
+ g.oscilloview = 0;
+ timeKillEvent(g.mmtimer);
+}
+
+static void CALLBACK mmtimer_cb(UINT timerid, UINT msg,
+ DWORD_PTR userptr,
+ DWORD_PTR dw1, DWORD_PTR dw2) {
+ PostMessage(g.oscilloview, WM_USER, 0, 0);
+}
+
+static bool on_create(HWND hwnd, const CREATESTRUCT *cs) {
+ ShowWindow(hwnd, SW_SHOW);
+ //SetTimer(hwnd, TIMER_UPDATE, 16, 0);
+ g.mmtimer = timeSetEvent(16, 16, mmtimer_cb, 0, TIME_PERIODIC);
+ DragAcceptFiles(hwnd, TRUE);
+ return true;
+}
+
+static void draw_track(HDC dc,
+ int x, int y, int w, int h,
+ const struct oscillodata *data) {
+ int start = OSCILLO_SAMPLE_COUNT - VIEW_SAMPLES;
+ start -= (data->offset >> OSCILLO_OFFSET_SHIFT);
+ if (start < 0) start = 0;
+ MoveToEx(dc, x, y + h/2.0 - (data->buf[start] / 16384.0) * h/2, 0);
+ for (int i = 0; i < (VIEW_SAMPLES / VIEW_SKIP); i++) {
+ LineTo(dc, (double)x + ((i)*w)/(VIEW_SAMPLES / VIEW_SKIP), y + h/2.0 - (data->buf[start + i*VIEW_SKIP] / 16384.0) * h/2);
+ }
+}
+
+static void on_paint(HWND hwnd) {
+ RECT cr;
+ GetClientRect(hwnd, &cr);
+ PAINTSTRUCT ps;
+ HDC dc = BeginPaint(hwnd, &ps);
+ HDC mdc = CreateCompatibleDC(dc);
+ HBITMAP bitmap = CreateCompatibleBitmap(dc, cr.right, cr.bottom);
+ SelectObject(mdc, bitmap);
+
+ FillRect(mdc, &cr, GetStockObject(BLACK_BRUSH));
+ SelectObject(mdc, GetStockObject(WHITE_PEN));
+ int width = cr.right / 3;
+ int height = cr.bottom / 3;
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 3; y++) {
+ draw_track(mdc, x*width, y*height, width, height, &g.oscillodata[x*3+y]);
+ }
+ }
+
+ BitBlt(dc, 0, 0, cr.right, cr.bottom, mdc, 0, 0, SRCCOPY);
+ SelectObject(mdc, 0);
+ DeleteObject(bitmap);
+ DeleteDC(mdc);
+ EndPaint(hwnd, &ps);
+}
+
+static LRESULT CALLBACK wndproc(
+ HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam
+) {
+ switch (msg) {
+ HANDLE_MSG(hwnd, WM_DESTROY, on_destroy);
+ HANDLE_MSG(hwnd, WM_CREATE, on_create);
+ //HANDLE_MSG(hwnd, WM_TIMER, on_timer);
+ HANDLE_MSG(hwnd, WM_PAINT, on_paint);
+ case WM_ERASEBKGND:
+ return 1;
+ case WM_USER:
+ if (!atomic_flag_test_and_set_explicit(
+ &oscilloview_g.flag, memory_order_acquire)) {
+ memcpy(g.oscillodata,
+ oscilloview_g.oscillodata,
+ sizeof(oscilloview_g.oscillodata));
+ atomic_flag_clear_explicit(&oscilloview_g.flag, memory_order_release);
+ }
+ InvalidateRect(hwnd, 0, FALSE);
+ return 0;
+ case WM_DROPFILES:
+ return SendMessage(g.parent, msg, wParam, lParam);
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+void show_oscilloview(HINSTANCE hinst, HWND parent) {
+ g.hinst = hinst;
+ g.parent = parent;
+ if (!g.oscilloview) {
+ if (!g.oscilloview_class) {
+ WNDCLASS wc = {0};
+ wc.style = 0;
+ wc.lpfnWndProc = wndproc;
+ wc.hInstance = g.hinst;
+ wc.hIcon = LoadIcon(g.hinst, MAKEINTRESOURCE(1));
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
+ wc.lpszClassName = L"myon_fmplayer_ym2608_oscilloviewer";
+ g.oscilloview_class = RegisterClass(&wc);
+ }
+ if (!g.oscilloview_class) {
+ MessageBox(parent, L"Cannot register oscilloviewer class", L"Error", MB_ICONSTOP);
+ return;
+ }
+ g.oscilloview = CreateWindowEx(0,
+ MAKEINTATOM(g.oscilloview_class),
+ L"FMPlayer Oscilloview",
+ WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN | WS_SIZEBOX | WS_MAXIMIZEBOX,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ parent, 0, g.hinst, 0);
+ } else {
+ SetForegroundWindow(g.oscilloview);
+ }
+}
diff --git a/win32/oscilloview.h b/win32/oscilloview.h
new file mode 100644
index 0000000..8849830
--- /dev/null
+++ b/win32/oscilloview.h
@@ -0,0 +1,19 @@
+#ifndef MYON_FMPLAYER_WIN32_OSCILLOVIEW_H_INCLUDED
+#define MYON_FMPLAYER_WIN32_OSCILLOVIEW_H_INCLUDED
+
+#include "libopna/opna.h"
+#include "oscillo/oscillo.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <stdatomic.h>
+
+extern struct oscilloview {
+ atomic_flag flag;
+ struct oscillodata oscillodata[LIBOPNA_OSCILLO_TRACK_COUNT];
+} oscilloview_g;
+
+void show_oscilloview(HINSTANCE hinst, HWND parent);
+
+#endif // MYON_FMPLAYER_WIN32_OSCILLOVIEW_H_INCLUDED
+