From 23dc19065c182e61bce9c997f31e23b001a85f2c Mon Sep 17 00:00:00 2001
From: Takamichi Horikawa <takamichiho@gmail.com>
Date: Wed, 1 Mar 2017 00:22:33 +0900
Subject: gtk: add tone viewer

---
 gtk/Makefile.am     |   2 +
 gtk/configure.ac    |   1 +
 gtk/main.c          |  25 +++++++++++-
 gtk/toneview.c      | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/toneview.h      |  14 +++++++
 tonedata/tonedata.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 tonedata/tonedata.h |  48 +++++++++++++++++++++++
 7 files changed, 302 insertions(+), 2 deletions(-)
 create mode 100644 gtk/toneview.c
 create mode 100644 gtk/toneview.h
 create mode 100644 tonedata/tonedata.c
 create mode 100644 tonedata/tonedata.h

diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index e765244..665f3ca 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -15,6 +15,8 @@ FMDSP_SRC=../fmdsp/fmdsp.c \
           ../fmdsp/font_fmdsp_small.c
 
 fmplayer_SOURCES=main.c \
+                 toneview.c \
+                 ../tonedata/tonedata.c \
                  $(LIBOPNA_SRC) \
                  $(FMDRIVER_SRC) \
                  $(FMDSP_SRC)
diff --git a/gtk/configure.ac b/gtk/configure.ac
index bde5f6e..8e13a34 100644
--- a/gtk/configure.ac
+++ b/gtk/configure.ac
@@ -1,5 +1,6 @@
 AC_INIT([fmplayer], [0.1.0])
 AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
+AM_SILENT_RULES([yes])
 AC_PROG_CC_C99
 
 dnl AM_PATH_SDL2([2.0.5])
diff --git a/gtk/main.c b/gtk/main.c
index 9e2e989..cd3ddb2 100644
--- a/gtk/main.c
+++ b/gtk/main.c
@@ -5,12 +5,14 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include <cairo.h>
+#include <stdatomic.h>
 
 #include "fmdriver/fmdriver_fmp.h"
 #include "fmdriver/ppz8.h"
 #include "libopna/opna.h"
 #include "libopna/opnatimer.h"
 #include "fmdsp/fmdsp.h"
+#include "toneview.h"
 
 #define DATADIR "/.local/share/fmplayer/"
 //#define FMDSP_2X
@@ -64,6 +66,10 @@ static void on_menu_quit(GtkMenuItem *menuitem, gpointer ptr) {
   quit();
 }
 
+static void on_tone_view(GtkMenuItem *menuitem, gpointer ptr) {
+  show_toneview();
+}
+
 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,
@@ -81,6 +87,13 @@ static int pastream_cb(const void *inptr, void *outptr, unsigned long frames,
   int16_t *buf = (int16_t *)outptr;
   memset(outptr, 0, sizeof(int16_t)*frames*2);
   opna_timer_mix(timer, buf, frames);
+
+  bool xchg = false;
+  if (atomic_compare_exchange_weak_explicit(&toneview_g.flag, &xchg, true,
+      memory_order_acquire, memory_order_relaxed)) {
+    tonedata_from_opna(&toneview_g.tonedata, &g.opna);
+    atomic_store_explicit(&toneview_g.flag, false, memory_order_release);
+  }  
   return paContinue;
 }
 
@@ -401,6 +414,14 @@ static GtkWidget *create_menubar() {
   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);
+
+  GtkWidget *window = gtk_menu_item_new_with_label("Window");
+  GtkWidget *filemenu = gtk_menu_new();
+  gtk_menu_item_set_submenu(GTK_MENU_ITEM(window), filemenu);
+  gtk_menu_shell_append(GTK_MENU_SHELL(menubar), window);
+  GtkWidget *toneview = gtk_menu_item_new_with_label("Tone view");
+  g_signal_connect(toneview, "activate", G_CALLBACK(on_tone_view), 0);
+  gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), toneview);
   return menubar;
 }
 
@@ -452,7 +473,7 @@ static void mask_update(void) {
 
 static void mask_set(unsigned mask, bool shift) {
   if (shift) {
-    opna_set_mask(&g.opna, mask);
+    opna_set_mask(&g.opna, ~mask);
   } else {
     opna_set_mask(&g.opna, opna_get_mask(&g.opna) ^ mask);
   }
@@ -470,7 +491,7 @@ static gboolean key_press_cb(GtkWidget *w,
       return TRUE;
     }
   }
-  bool shift = e->key.state & GDK_SHIFT_MASK;
+  bool shift = e->key.state & GDK_CONTROL_MASK;
   switch (e->key.keyval) {
   case GDK_KEY_F6:
     if (g.current_uri) {
diff --git a/gtk/toneview.c b/gtk/toneview.c
new file mode 100644
index 0000000..79750fe
--- /dev/null
+++ b/gtk/toneview.c
@@ -0,0 +1,107 @@
+#include <gtk/gtk.h>
+#include "toneview.h"
+#include <stdatomic.h>
+#include <stdbool.h>
+
+struct toneview_g toneview_g;
+
+static struct {
+  GtkWidget *tonewin;
+  GtkWidget *label[6];
+  struct fmplayer_tonedata tonedata;
+  struct fmplayer_tonedata tonedata_n;
+  char strbuf[FMPLAYER_TONEDATA_STR_SIZE];
+  enum fmplayer_tonedata_format format;
+  bool normalize;
+  GtkClipboard *clipboard;
+} g;
+
+static void on_destroy(GtkWidget *w, gpointer ptr) {
+  (void)w;
+  (void)ptr;
+  g.tonewin = 0;
+}
+
+gboolean tick_cb(GtkWidget *widget, GdkFrameClock *clock, gpointer ptr) {
+  (void)widget;
+  (void)clock;
+  (void)ptr;
+  bool xchg = false;
+  if (atomic_compare_exchange_weak_explicit(&toneview_g.flag, &xchg, true,
+      memory_order_acquire, memory_order_relaxed)) {
+    g.tonedata = toneview_g.tonedata;
+    atomic_store_explicit(&toneview_g.flag, false, memory_order_release);
+  }
+  g.tonedata_n = g.tonedata;
+  for (int c = 0; c < 6; c++) {
+    if (g.normalize) {
+      tonedata_ch_normalize_tl(&g.tonedata_n.ch[c]);
+    }
+    tonedata_ch_string(g.format, g.strbuf, &g.tonedata_n.ch[c], 0);
+    gtk_label_set_text(GTK_LABEL(g.label[c]), g.strbuf);
+  }
+  return G_SOURCE_CONTINUE;
+}
+
+static void on_format_changed(GtkComboBox *widget, gpointer ptr) {
+  (void)ptr;
+  g.format = gtk_combo_box_get_active(widget);
+}
+
+static void on_normalize_toggled(GtkToggleButton *widget, gpointer ptr) {
+  (void)ptr;
+  g.normalize = gtk_toggle_button_get_active(widget);
+}
+
+static void on_copy_clicked(GtkButton *button, gpointer ptr) {
+  (void)button;
+  int c = (intptr_t)ptr;
+  if (!g.clipboard) {
+    GdkDisplay *disp = gdk_display_get_default();
+    if (disp) {
+      g.clipboard = gtk_clipboard_get_default(disp);
+    }
+  }
+  if (g.clipboard) {
+    tonedata_ch_string(g.format, g.strbuf, &g.tonedata_n.ch[c], 0);
+    gtk_clipboard_set_text(g.clipboard, g.strbuf, -1);
+  }
+}
+
+void show_toneview(void) {
+  if (!g.tonewin) {
+    g.tonewin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_title(GTK_WINDOW(g.tonewin), "FM Tone Viewer");
+    g_signal_connect(g.tonewin, "destroy", G_CALLBACK(on_destroy), 0);
+    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
+    gtk_container_add(GTK_CONTAINER(g.tonewin), box);
+    GtkWidget *ctrlbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
+    gtk_container_add(GTK_CONTAINER(box), ctrlbox);
+    GtkWidget *format = gtk_combo_box_text_new();
+    gtk_box_pack_start(GTK_BOX(ctrlbox), format, FALSE, TRUE, 0);
+    gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(format), "PMD");
+    gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(format), "FMP");
+    gtk_combo_box_set_active(GTK_COMBO_BOX(format), g.format);
+    g_signal_connect(format, "changed", G_CALLBACK(on_format_changed), 0);
+    GtkWidget *normalizecheck = gtk_check_button_new_with_label("Normalize");
+    gtk_box_pack_start(GTK_BOX(ctrlbox), normalizecheck, FALSE, TRUE, 0);
+    g_signal_connect(normalizecheck, "toggled", G_CALLBACK(on_normalize_toggled), 0);
+    for (int c = 0; c < 6; c++) {
+      GtkWidget *cbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
+      gtk_box_pack_start(GTK_BOX(box), cbox, TRUE, TRUE, 0);
+      GtkWidget *tonetext = gtk_label_new(0);
+      PangoAttrList *pattrl = pango_attr_list_new();
+      PangoAttribute *pattr = pango_attr_family_new("monospace");
+      pango_attr_list_insert(pattrl, pattr);
+      gtk_label_set_attributes(GTK_LABEL(tonetext), pattrl);
+      pango_attr_list_unref(pattrl);
+      gtk_box_pack_start(GTK_BOX(cbox), tonetext, TRUE, TRUE, 0);
+      GtkWidget *copybutton = gtk_button_new_with_label("Copy");
+      g_signal_connect(copybutton, "clicked", G_CALLBACK(on_copy_clicked), (gpointer)((intptr_t)c));
+      gtk_box_pack_start(GTK_BOX(cbox), copybutton, FALSE, TRUE, 0);
+      g.label[c] = tonetext;
+    }
+    gtk_widget_add_tick_callback(g.tonewin, tick_cb, 0, 0);
+    gtk_widget_show_all(g.tonewin);
+  }
+}
diff --git a/gtk/toneview.h b/gtk/toneview.h
new file mode 100644
index 0000000..51c1769
--- /dev/null
+++ b/gtk/toneview.h
@@ -0,0 +1,14 @@
+#ifndef MYON_FMPLAYER_GTK_TONEVIEW_H_INCLUDED
+#define MYON_FMPLAYER_GTK_TONEVIEW_H_INCLUDED
+
+#include "tonedata/tonedata.h"
+#include <stdatomic.h>
+
+extern struct toneview_g {
+  struct fmplayer_tonedata tonedata;
+  atomic_bool flag;
+} toneview_g;
+
+void show_toneview();
+
+#endif // MYON_FMPLAYER_GTK_TONEVIEW_H_INCLUDED
diff --git a/tonedata/tonedata.c b/tonedata/tonedata.c
new file mode 100644
index 0000000..0d5daa6
--- /dev/null
+++ b/tonedata/tonedata.c
@@ -0,0 +1,107 @@
+#include "tonedata.h"
+#include "libopna/opna.h"
+#include <stdio.h>
+void tonedata_from_opna(
+  struct fmplayer_tonedata *tonedata,
+  const struct opna *opna
+) {
+  const struct opna_fm *fm = &opna->fm;
+  for (int c = 0; c < 6; c++) {
+    const struct opna_fm_channel *op_ch = &fm->channel[c];
+    struct fmplayer_tonedata_channel *td_ch = &tonedata->ch[c];
+    for (int s = 0; s < 4; s++) {
+      const struct opna_fm_slot *op_sl = &op_ch->slot[s];
+      struct fmplayer_tonedata_slot *td_sl = &td_ch->slot[s];
+      td_sl->ar = op_sl->ar;
+      td_sl->dr = op_sl->dr;
+      td_sl->sr = op_sl->sr;
+      td_sl->rr = op_sl->rr;
+      td_sl->sl = op_sl->sl;
+      td_sl->tl = op_sl->tl;
+      td_sl->ks = op_sl->ks;
+      td_sl->ml = op_sl->mul;
+      td_sl->dt = op_sl->det;
+      // TODO: hardware LFO not implemented
+      td_sl->ams = 0;
+    }
+    td_ch->alg = op_ch->alg;
+    td_ch->fb = op_ch->fb;
+  }
+}
+
+void tonedata_ch_normalize_tl(
+  struct fmplayer_tonedata_channel *ch
+) {
+  static const uint8_t alg_out[8] = {
+    0x8, 0x8, 0x8, 0x8, 0xa, 0xe, 0xe, 0xf
+  };
+  uint8_t outbit = alg_out[ch->alg & 7];
+  uint8_t max_tl = 127;
+  for (int s = 0; s < 4; s++) {
+    if (!(outbit & (1<<s))) continue;
+    if (max_tl > ch->slot[s].tl) max_tl = ch->slot[s].tl;
+  }
+  for (int s = 0; s < 4; s++) {
+    if (!(outbit & (1<<s))) continue;
+    ch->slot[s].tl -= max_tl;
+  }
+}
+
+void tonedata_ch_string(
+  enum fmplayer_tonedata_format format,
+  char *buf,
+  const struct fmplayer_tonedata_channel *ch,
+  uint8_t tonenum
+) {
+  switch (format) {
+  case FMPLAYER_TONEDATA_FMT_PMD:
+    snprintf(buf, FMPLAYER_TONEDATA_STR_SIZE,
+            "@%3d %1d %1d\n"
+            " %2d %2d %2d %2d %2d %3d %1d %2d %1d %1d\n"
+            " %2d %2d %2d %2d %2d %3d %1d %2d %1d %1d\n"
+            " %2d %2d %2d %2d %2d %3d %1d %2d %1d %1d\n"
+            " %2d %2d %2d %2d %2d %3d %1d %2d %1d %1d\n",
+            tonenum, ch->alg, ch->fb,
+            ch->slot[0].ar, ch->slot[0].dr, ch->slot[0].sr, ch->slot[0].rr,
+            ch->slot[0].sl, ch->slot[0].tl, ch->slot[0].ks, ch->slot[0].ml,
+            ch->slot[0].dt, ch->slot[0].ams,
+            ch->slot[1].ar, ch->slot[1].dr, ch->slot[1].sr, ch->slot[1].rr,
+            ch->slot[1].sl, ch->slot[1].tl, ch->slot[1].ks, ch->slot[1].ml,
+            ch->slot[1].dt, ch->slot[1].ams,
+            ch->slot[2].ar, ch->slot[2].dr, ch->slot[2].sr, ch->slot[2].rr,
+            ch->slot[2].sl, ch->slot[2].tl, ch->slot[2].ks, ch->slot[2].ml,
+            ch->slot[2].dt, ch->slot[2].ams,
+            ch->slot[3].ar, ch->slot[3].dr, ch->slot[3].sr, ch->slot[3].rr,
+            ch->slot[3].sl, ch->slot[3].tl, ch->slot[3].ks, ch->slot[3].ml,
+            ch->slot[3].dt, ch->slot[3].ams
+    );
+    break;
+  case FMPLAYER_TONEDATA_FMT_FMP:
+    snprintf(buf, FMPLAYER_TONEDATA_STR_SIZE,
+            "'@%3d\n"
+            "'@ %2d, %2d, %2d, %2d, %2d, %3d, %1d, %2d, %1d\n"
+            "'@ %2d, %2d, %2d, %2d, %2d, %3d, %1d, %2d, %1d\n"
+            "'@ %2d, %2d, %2d, %2d, %2d, %3d, %1d, %2d, %1d\n"
+            "'@ %2d, %2d, %2d, %2d, %2d, %3d, %1d, %2d, %1d\n"
+            "'@ %1d, %1d",
+            tonenum,
+            ch->slot[0].ar, ch->slot[0].dr, ch->slot[0].sr, ch->slot[0].rr,
+            ch->slot[0].sl, ch->slot[0].tl, ch->slot[0].ks, ch->slot[0].ml,
+            ch->slot[0].dt,
+            ch->slot[1].ar, ch->slot[1].dr, ch->slot[1].sr, ch->slot[1].rr,
+            ch->slot[1].sl, ch->slot[1].tl, ch->slot[1].ks, ch->slot[1].ml,
+            ch->slot[1].dt,
+            ch->slot[2].ar, ch->slot[2].dr, ch->slot[2].sr, ch->slot[2].rr,
+            ch->slot[2].sl, ch->slot[2].tl, ch->slot[2].ks, ch->slot[2].ml,
+            ch->slot[2].dt,
+            ch->slot[3].ar, ch->slot[3].dr, ch->slot[3].sr, ch->slot[3].rr,
+            ch->slot[3].sl, ch->slot[3].tl, ch->slot[3].ks, ch->slot[3].ml,
+            ch->slot[3].dt,
+            ch->alg, ch->fb
+    );
+    break;
+  default:
+    buf[0] = 0;
+    break;
+  }
+}
diff --git a/tonedata/tonedata.h b/tonedata/tonedata.h
new file mode 100644
index 0000000..c8dd03c
--- /dev/null
+++ b/tonedata/tonedata.h
@@ -0,0 +1,48 @@
+#ifndef MYON_FMPLAYER_TONEDATA_H_INCLUDED
+#define MYON_FMPLAYER_TONEDATA_H_INCLUDED
+
+#include <stdint.h>
+
+struct fmplayer_tonedata {
+  struct fmplayer_tonedata_channel {
+    struct fmplayer_tonedata_slot {
+      uint8_t ar;
+      uint8_t dr;
+      uint8_t sr;
+      uint8_t rr;
+      uint8_t sl;
+      uint8_t tl;
+      uint8_t ks;
+      uint8_t ml;
+      uint8_t dt;
+      uint8_t ams;
+    } slot[4];
+    uint8_t fb;
+    uint8_t alg;
+  } ch[6];
+};
+
+struct opna;
+void tonedata_from_opna(
+  struct fmplayer_tonedata *tonedata,
+  const struct opna *opna
+);
+
+enum fmplayer_tonedata_format {
+  FMPLAYER_TONEDATA_FMT_PMD,
+  FMPLAYER_TONEDATA_FMT_FMP
+};
+
+enum {
+  FMPLAYER_TONEDATA_STR_SIZE = 0x100
+};
+void tonedata_ch_normalize_tl(struct fmplayer_tonedata_channel *ch);
+void tonedata_ch_string(
+  enum fmplayer_tonedata_format format,
+  char *buf,
+  const struct fmplayer_tonedata_channel *ch,
+  uint8_t tonenum
+);
+
+#endif // MYON_FMPLAYER_TONEDATA_H_INCLUDED
+
-- 
cgit v1.2.3