From 8f6c90f9d32d64a670069853019e7be70da486c3 Mon Sep 17 00:00:00 2001
From: Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
Date: Wed, 9 Nov 2022 12:37:12 +0530
Subject: [PATCH] CW: add new menu entry for enabling and syncing CW MIDI keyer

Automatically syncs the CW WPM speed
---
 alsa_midi.c |  3 ++-
 alsa_midi.h |  3 +++
 cw_menu.c   | 13 +++++++++++++
 midi.h      |  2 ++
 midi2.c     | 16 ++++++++++++++++
 radio.c     | 31 ++++++++++++++++++++++++++++++-
 radio.h     |  1 +
 rigctl.c    |  4 ++++
 8 files changed, 71 insertions(+), 2 deletions(-)

diff --git a/alsa_midi.c b/alsa_midi.c
index 9cae879..c55b25b 100644
--- a/alsa_midi.c
+++ b/alsa_midi.c
@@ -55,6 +55,7 @@ static enum {
 static gboolean configure=FALSE;
 
 static snd_rawmidi_t *input;
+snd_rawmidi_t *cw_midi_output = NULL;
 
 void configure_midi_device(gboolean state) {
   configure=state;
@@ -203,7 +204,7 @@ int register_midi_device(char *myname) {
     for(i=0;i<n_midi_devices;i++) {
         if(strcmp(myname,midi_devices[i].name)==0) {
 	    strcpy(portname,midi_devices[i].port);
-            if ((ret = snd_rawmidi_open(&input, NULL, midi_devices[i].port, SND_RAWMIDI_NONBLOCK)) < 0) {
+            if ((ret = snd_rawmidi_open(&input, &cw_midi_output, midi_devices[i].port, SND_RAWMIDI_NONBLOCK)) < 0) {
                g_print("%s: cannot open port \"%s\": %s\n", __FUNCTION__, midi_devices[i].port, snd_strerror(ret));
                break;
             }
diff --git a/alsa_midi.h b/alsa_midi.h
index 02acc84..2a62ca1 100644
--- a/alsa_midi.h
+++ b/alsa_midi.h
@@ -3,10 +3,13 @@ typedef struct _midi_device {
   char *port;
 } MIDI_DEVICE;
 
+#include <alsa/asoundlib.h>
+
 #define MAX_MIDI_DEVICES 10
 
 extern MIDI_DEVICE midi_devices[MAX_MIDI_DEVICES];
 extern int n_midi_devices;
+extern snd_rawmidi_t *cw_midi_output;
 
 extern void get_midi_devices();
 extern int register_midi_device(char *myname);
diff --git a/cw_menu.c b/cw_menu.c
index bc00e21..3cd5163 100644
--- a/cw_menu.c
+++ b/cw_menu.c
@@ -48,6 +48,7 @@ void cw_changed() {
 // NewProtocol: rely on periodically sent HighPrio packets
 #ifdef LOCALCW
   keyer_update();
+  midi_keyer_update();
 #endif
 //
 // speed and side tone frequency are displayed in the VFO bar
@@ -84,6 +85,11 @@ static void cw_keyer_internal_cb(GtkWidget *widget, gpointer data) {
   cw_changed();
 }
 
+static void cw_keyer_midi_cb(GtkWidget *widget, gpointer data) {
+  cw_keyer_midi = cw_keyer_midi == 1 ? 0: 1;
+  cw_changed();
+}
+
 static void cw_keyer_spacing_cb(GtkWidget *widget, gpointer data) {
   cw_keyer_spacing=cw_keyer_spacing==1?0:1;
   cw_changed();
@@ -288,6 +294,13 @@ void cw_menu(GtkWidget *parent) {
   gtk_widget_show(cw_keyer_spacing_b);
   gtk_grid_attach(GTK_GRID(grid),cw_keyer_spacing_b,0,11,1,1);
   g_signal_connect(cw_keyer_spacing_b,"toggled",G_CALLBACK(cw_keyer_spacing_cb),NULL);
+
+  GtkWidget *cw_keyer_midi_b=gtk_check_button_new_with_label("CW handled in MIDI Keyer");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (cw_keyer_midi_b), cw_keyer_midi);
+  gtk_widget_show(cw_keyer_midi_b);
+  gtk_grid_attach(GTK_GRID(grid),cw_keyer_midi_b,0,12,1,1);
+  g_signal_connect(cw_keyer_midi_b,"toggled",G_CALLBACK(cw_keyer_midi_cb),NULL);
+
 #endif
 
   gtk_container_add(GTK_CONTAINER(content),grid);
diff --git a/midi.h b/midi.h
index 841980a..ea3729e 100644
--- a/midi.h
+++ b/midi.h
@@ -291,3 +291,5 @@ int MIDIstop();
 
 void DoTheMidi(int code, enum ACTIONtype type, int val);
 #endif
+
+void midi_keyer_update();
diff --git a/midi2.c b/midi2.c
index 6b22790..a8967db 100644
--- a/midi2.c
+++ b/midi2.c
@@ -23,6 +23,7 @@
 #include "actions.h"
 #include "midi.h"
 
+#include "alsa_midi.h"
 
 struct cmdtable MidiCommandsTable;
 
@@ -430,3 +431,18 @@ g_print("%s:TAB:Insert desc=%p in CMDS[%d] table\n",__FUNCTION__,desc,key);
 
     return 0;
 }
+
+void midi_keyer_update(void) {
+    // read the global cw_keyer_speed and send midi commands
+    char wpmctrl[4] = {0xb1, 0, cw_keyer_speed}; // 0xb0, lower nibble is channel number.
+
+    int status;
+    if (cw_midi_output != NULL) {
+	fprintf(stderr, "MIDI Keyer: setting WPM to %d\n", cw_keyer_speed);
+	if ((status = snd_rawmidi_write(cw_midi_output, wpmctrl, 4)) < 0) {
+	    fprintf(stderr, "Problem writing to MIDI output: %s", snd_strerror(status));
+	}
+    } else {
+	fprintf(stderr, "MIDI device is not open yet\n");
+    }
+}
diff --git a/radio.c b/radio.c
index 49412b0..0287e98 100644
--- a/radio.c
+++ b/radio.c
@@ -253,6 +253,8 @@ int cw_keyer_mode = KEYER_MODE_A;
 int cw_keyer_weight = 50;              // 0-100
 int cw_keyer_spacing = 0;              // 0=on 1=off
 int cw_keyer_internal = 1;             // 0=external 1=internal
+int cw_keyer_midi = 0;                 // 0 = (external) midi keyer
+				       // disabled, 1 = enabled
 int cw_keyer_sidetone_volume = 50;     // 0-127
 int cw_keyer_ptt_delay = 20;           // 0-255ms
 int cw_keyer_hang_time = 500;          // ms
@@ -460,6 +462,11 @@ void reconfigure_radio() {
     if (!duplex) {
       reconfigure_transmitter(transmitter, display_width, rx_height);
     }
+#ifdef MIDI
+    if (cw_keyer_midi == 1) {
+      midi_keyer_update();
+    }
+#endif
   }
 }
 
@@ -670,6 +677,10 @@ static void create_visual() {
     g_print("Initialize keyer.....\n");
     keyer_update();
   }
+
+  if (cw_keyer_midi == 1) {
+      midi_keyer_update();
+  }
 #endif
 
 #ifdef CLIENT_SERVER
@@ -1214,6 +1225,9 @@ void start_radio() {
     if (register_midi_device(midi_device_name) < 0) {
       midi_enabled = FALSE;
     }
+    if (cw_keyer_midi == 1) {
+	midi_keyer_update();
+    }
   } else {
     midi_enabled = FALSE;
   }
@@ -1864,8 +1878,11 @@ void radioRestoreState() {
     if (value)
       cw_keys_reversed = atoi(value);
     value = getProperty("cw_keyer_speed");
-    if (value)
+    if (value) {
       cw_keyer_speed = atoi(value);
+      // if we have the midikeyer, set the speed to this value
+      midi_keyer_update();
+    }
     value = getProperty("cw_keyer_mode");
     if (value)
       cw_keyer_mode = atoi(value);
@@ -1879,6 +1896,11 @@ void radioRestoreState() {
     value = getProperty("cw_keyer_internal");
     if (value)
       cw_keyer_internal = atoi(value);
+
+    value = getProperty("cw_keyer_midi");
+    if (value)
+      cw_keyer_midi = atoi(value);
+
 #endif
     value = getProperty("cw_keyer_sidetone_volume");
     if (value)
@@ -2293,6 +2315,10 @@ void radioSaveState() {
     setProperty("cw_keyer_spacing", value);
     sprintf(value, "%d", cw_keyer_internal);
     setProperty("cw_keyer_internal", value);
+
+    sprintf(value, "%d", cw_keyer_midi);
+    setProperty("cw_keyer_midi", value);
+
     sprintf(value, "%d", cw_keyer_sidetone_volume);
     setProperty("cw_keyer_sidetone_volume", value);
     sprintf(value, "%d", cw_keyer_ptt_delay);
@@ -2600,6 +2626,9 @@ int remote_start(void *data) {
                         gdk_cursor_new(GDK_ARROW));
 #ifdef MIDI
   MIDIstartup();
+  if (cw_keyer_midi == 1) {
+      midi_keyer_update();
+  }
 #endif
   for (int i = 0; i < receivers; i++) {
     gint timer_id = gdk_threads_add_timeout_full(
diff --git a/radio.h b/radio.h
index 4e723a5..2cc368a 100644
--- a/radio.h
+++ b/radio.h
@@ -217,6 +217,7 @@ extern int cw_keyer_mode;
 extern int cw_keyer_weight;
 extern int cw_keyer_spacing;
 extern int cw_keyer_internal;
+extern int cw_keyer_midi;
 extern int cw_keyer_sidetone_volume;
 extern int cw_keyer_ptt_delay;
 extern int cw_keyer_hang_time;
diff --git a/rigctl.c b/rigctl.c
index ded7e85..b3a9ea2 100644
--- a/rigctl.c
+++ b/rigctl.c
@@ -1282,6 +1282,9 @@ gboolean parse_extended_cmd(char *command, CLIENT *client) {
                 cw_keyer_speed = speed;
 #ifdef LOCALCW
                 keyer_update();
+#endif
+#ifdef MIDI
+		midi_keyer_update();
 #endif
                 vfo_update();
             }
@@ -3147,6 +3150,7 @@ int parse_cmd(void *data) {
           cw_keyer_speed = speed;
 #ifdef LOCALCW
           keyer_update();
+	  midi_keyer_update();
 #endif
           vfo_update();
         }
-- 
2.45.2