]> git.rkrishnan.org Git - pihpsdr.git/commitdiff
Merged MIDI menu (not yet complete)
authorc vw <dl1ycf@darc.de>
Thu, 6 May 2021 09:41:10 +0000 (11:41 +0200)
committerc vw <dl1ycf@darc.de>
Thu, 6 May 2021 09:41:10 +0000 (11:41 +0200)
alsa_midi.h [new file with mode: 0644]
mac_midi.c
midi.h
midi2.c
midi3.c
midi_menu.c [new file with mode: 0644]
midi_menu.h [new file with mode: 0644]
new_menu.c
radio.c
radio.h

diff --git a/alsa_midi.h b/alsa_midi.h
new file mode 100644 (file)
index 0000000..02acc84
--- /dev/null
@@ -0,0 +1,12 @@
+typedef struct _midi_device {
+  char *name;
+  char *port;
+} MIDI_DEVICE;
+
+#define MAX_MIDI_DEVICES 10
+
+extern MIDI_DEVICE midi_devices[MAX_MIDI_DEVICES];
+extern int n_midi_devices;
+
+extern void get_midi_devices();
+extern int register_midi_device(char *myname);
index 60c7b01d6222c399eb69115b95b9f766b7b24279..36570b5d40fca4ae3a1ec542a95bae8a28590fa3 100644 (file)
  *
  */
 
+#include <gtk/gtk.h>
+#include "discovered.h"
+#include "receiver.h"
+#include "transmitter.h"
+#include "receiver.h"
+#include "adc.h"
+#include "dac.h"
+#include "radio.h"
 #include "midi.h"
+#include "midi_menu.h"
 
 #ifdef __APPLE__
 
+typedef struct _midi_device {
+  char *name;
+  char *port;
+} MIDI_DEVICE;
+
+#define MAX_MIDI_DEVICES 10
+
+MIDI_DEVICE midi_devices[MAX_MIDI_DEVICES];
+int n_midi_devices;
+
+
 /*
  * For MacOS, things are easy:
  * The OS takes care of everything, we only have to register a callback
 #include <CoreAudio/HostTime.h>
 #include <CoreAudio/CoreAudio.h>
 
+MIDI_DEVICE midi_devices[MAX_MIDI_DEVICES];
+int n_midi_devices;
+int running;
+
 //
 // MIDI callback function
 // called by MacOSX when data from the specified MIDI device arrives.
@@ -69,6 +93,8 @@ static enum {
         CMD_PITCH,
 } command;
 
+static gboolean configure=FALSE;
+
 static void ReadMIDIdevice(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) {
     int i,j,byte,chan,arg1,arg2;
     MIDIPacket *packet = (MIDIPacket *)pktlist->packet;
@@ -119,19 +145,39 @@ static void ReadMIDIdevice(const MIDIPacketList *pktlist, void *refCon, void *co
                            // messages with velocity == 0 when releasing
                            // a push-button.
                            if (arg2 == 0) {
-                             NewMidiEvent(MIDI_EVENT_NOTE, chan, arg1, 0);
+                             if(configure) {
+                               NewMidiConfigureEvent(MIDI_EVENT_NOTE, chan, arg1, 0);
+                             } else {
+                               NewMidiEvent(MIDI_EVENT_NOTE, chan, arg1, 0);
+                             }
                            } else {
-                             NewMidiEvent(MIDI_EVENT_NOTE, chan, arg1, 1);
+                             if(configure) {
+                               NewMidiConfigureEvent(MIDI_EVENT_NOTE, chan, arg1, 1);
+                             } else {
+                               NewMidiEvent(MIDI_EVENT_NOTE, chan, arg1, 1);
+                             }
                            }
                            break;
                         case CMD_NOTEOFF:
-                           NewMidiEvent(MIDI_EVENT_NOTE, chan, arg1, 0);
+                           if(configure) {
+                             NewMidiConfigureEvent(MIDI_EVENT_NOTE, chan, arg1, 0);
+                           } else {
+                             NewMidiEvent(MIDI_EVENT_NOTE, chan, arg1, 0);
+                           }
                            break;
                         case CMD_CTRL:
-                           NewMidiEvent(MIDI_EVENT_CTRL, chan, arg1, arg2);
+                           if(configure) {
+                             NewMidiConfigureEvent(MIDI_EVENT_CTRL, chan, arg1, arg2);
+                           } else {
+                             NewMidiEvent(MIDI_EVENT_CTRL, chan, arg1, arg2);
+                           }
                            break;
                         case CMD_PITCH:
-                           NewMidiEvent(MIDI_EVENT_PITCH, chan, 0, arg1+128*arg2);
+                           if(configure) {
+                             NewMidiConfigureEvent(MIDI_EVENT_PITCH, chan, 0, arg1+128*arg2);
+                           } else {
+                             NewMidiEvent(MIDI_EVENT_PITCH, chan, 0, arg1+128*arg2);
+                           }
                            break;
                     }
                     state=STATE_SKIP;
@@ -142,40 +188,36 @@ static void ReadMIDIdevice(const MIDIPacketList *pktlist, void *refCon, void *co
     } // j-loop through the list of packets
 }
 
+void close_midi_device() {
+    fprintf(stderr,"%s\n",__FUNCTION__);
+}
 
-void register_midi_device(char *myname) {
-    unsigned long nDevices;
+int register_midi_device(char *myname) {
     int i;
     CFStringRef pname;
     char name[100];
     int FoundMIDIref=-1;
-    int mylen=strlen(myname);
+    int mylen;
+    int ret;
 
+    configure=false;
+    if (myname == NULL) {
+      g_print("%s: myname is NULL\n", __FUNCTION__);
+      return -1;
+    }
+    mylen=strlen(myname);
 
+    g_print("%s: %s\n",__FUNCTION__,myname);
 //
 // Go through the list of MIDI devices and
 // look whether the one we are looking for is there
 //
-
-    nDevices=MIDIGetNumberOfSources();
-    for (i=0; i<nDevices; i++) {
-       MIDIEndpointRef dev = MIDIGetSource(i);
-       if (dev != 0) {
-           MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &pname);
-           CFStringGetCString(pname, name, sizeof(name), 0);
-           CFRelease(pname);
-            //
-            // Some users have reported that MacOS reports a string of length zero
-            // for some MIDI devices. In this case, we replace the name by
-            // "NoPort"
-            //
-            if (strlen(name) == 0) strcpy(name,"NoPort");
-           if (!strncmp(name, myname, mylen)) {
-               FoundMIDIref=i;
-               fprintf(stderr,"MIDI: registering device >%s<\n", name);
-           } else {
-               fprintf(stderr,"MIDI: looking for >%s< so >%s< does not match\n", myname,name);
-           }
+    for (i=0; i<n_midi_devices; i++) {
+        if(!strncmp(midi_devices[i].name, myname, mylen)) {
+           FoundMIDIref=i;
+           fprintf(stderr,"MIDI device found and selected: >>>%s<<<\n", midi_devices[i].name);
+       } else {
+           fprintf(stderr,"MIDI device found BUT NOT SELECTED: >>>%s<<<\n", midi_devices[i].name);
        }
     }
 
@@ -190,6 +232,46 @@ void register_midi_device(char *myname) {
         MIDIClientCreate(CFSTR("piHPSDR"),NULL,NULL, &client);
         MIDIInputPortCreate(client, CFSTR("FromMIDI"), ReadMIDIdevice, NULL, &myMIDIport);
         MIDIPortConnectSource(myMIDIport,MIDIGetSource(FoundMIDIref), NULL);
+        ret=0;
+    } else {
+        ret=-1;
+    }
+
+    return ret;
+}
+
+void get_midi_devices() {
+    int n;
+    int i;
+    CFStringRef pname;
+    char name[100];
+    int FoundMIDIref=-1;
+
+    n=MIDIGetNumberOfSources();
+    n_midi_devices=0;
+    for (i=0; i<n; i++) {
+        MIDIEndpointRef dev = MIDIGetSource(i);
+        if (dev != 0) {
+            MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &pname);
+            CFStringGetCString(pname, name, sizeof(name), 0);
+            CFRelease(pname);
+            //
+            // Some users have reported that MacOS reports a string of length zero
+            // for some MIDI devices. In this case, we replace the name by
+            // "NoPort"
+            //
+            if (strlen(name) == 0) strcpy(name,"NoPort");
+            g_print("%s: %s\n",__FUNCTION__,name);
+            midi_devices[n_midi_devices].name=g_new(gchar,strlen(name)+1);
+            strcpy(midi_devices[n_midi_devices].name,name);
+            n_midi_devices++;
+        }
     }
+    g_print("%s: devices=%d\n",__FUNCTION__,n_midi_devices);
 }
+
+void configure_midi_device(gboolean state) {
+  configure=state;
+}
+
 #endif
diff --git a/midi.h b/midi.h
index 54b14f42a736b211fe636cc8c2f8dec75f35d5a6..72cd62a659c00c948c53ba53f6fbe0c214a2001b 100644 (file)
--- a/midi.h
+++ b/midi.h
@@ -56,17 +56,39 @@ enum MIDIaction {
   MIDI_ACTION_ANF,             // ANF:                 toggel ANF on/off
   MIDI_ACTION_ATT,             // ATT:                 Step attenuator or Programmable attenuator
   MIDI_ACTION_VFO_B2A,         // B2A:                 VFO B -> A
+  MIDI_ACTION_BAND_10,          // BAND10
+  MIDI_ACTION_BAND_12,          // BAND12
+  MIDI_ACTION_BAND_1240,        // BAND1240
+  MIDI_ACTION_BAND_144,         // BAND144
+  MIDI_ACTION_BAND_15,          // BAND15
+  MIDI_ACTION_BAND_160,         // BAND160
+  MIDI_ACTION_BAND_17,          // BAND17
+  MIDI_ACTION_BAND_20,          // BAND20
+  MIDI_ACTION_BAND_220,         // BAND220
+  MIDI_ACTION_BAND_2300,        // BAND2300
+  MIDI_ACTION_BAND_30,          // BAND30
+  MIDI_ACTION_BAND_3400,        // BAND3400
+  MIDI_ACTION_BAND_40,          // BAND40
+  MIDI_ACTION_BAND_430,         // BAND430
+  MIDI_ACTION_BAND_6,           // BAND6
+  MIDI_ACTION_BAND_60,          // BAND60
+  MIDI_ACTION_BAND_70,          // BAND70
+  MIDI_ACTION_BAND_80,          // BAND80
+  MIDI_ACTION_BAND_902,         // BAND902
+  MIDI_ACTION_BAND_AIR,         // BANDAIR
   MIDI_ACTION_BAND_DOWN,       // BANDDOWN:            cycle through bands downwards
+  MIDI_ACTION_BAND_GEN,                // BANDGEN
   MIDI_ACTION_BAND_UP,         // BANDUP:              cycle through bands upwards
+  MIDI_ACTION_BAND_WWV,                // BANDWWVUP:           cycle through bands upwards
   MIDI_ACTION_COMPRESS,                // COMPRESS:            TX compressor value
   MIDI_ACTION_CTUN,            // CTUN:                toggle CTUN on/off
   MIDI_ACTION_VFO,             // CURRVFO:             change VFO frequency
-  MIDI_ACTION_CWKEY,           // CWKEY:               Unconditional CW key-down/up (outside keyer)
-  MIDI_ACTION_CWL,             // CWL:                 Left paddle pressed (use with ONOFF)
-  MIDI_ACTION_CWR,             // CWR:                 Right paddle pressed (use with ONOFF)
+  MIDI_ACTION_CWKEYER,         // CW(Keyer):           Unconditional CW key-down/up (outside keyer)
+  MIDI_ACTION_CWLEFT,          // CWLEFT:              Left paddle pressed (use with ONOFF)
+  MIDI_ACTION_CWRIGHT,         // CWRIGHT:             Right paddle pressed (use with ONOFF)
   MIDI_ACTION_CWSPEED,         // CWSPEED:             Set speed of (iambic) CW keyer
   MIDI_ACTION_DIV_COARSEGAIN,  // DIVCOARSEGAIN:       change DIVERSITY gain in large increments
-  MIDI_ACTION_DIV_COARSEPHASE, // DIVPHASE:            change DIVERSITY phase in large increments
+  MIDI_ACTION_DIV_COARSEPHASE, // DIVCOARSEPHASE:      change DIVERSITY phase in large increments
   MIDI_ACTION_DIV_FINEGAIN,    // DIVFINEGAIN:         change DIVERSITY gain in small increments
   MIDI_ACTION_DIV_FINEPHASE,   // DIVFINEPHASE:        change DIVERSITY phase in small increments
   MIDI_ACTION_DIV_GAIN,                // DIVGAIN:             change DIVERSITY gain in medium increments
@@ -76,6 +98,13 @@ enum MIDIaction {
   MIDI_ACTION_FILTER_DOWN,     // FILTERDOWN:          cycle through filters downwards
   MIDI_ACTION_FILTER_UP,       // FILTERUP:            cycle through filters upwards
   MIDI_ACTION_LOCK,            // LOCK:                lock VFOs, disable frequency changes
+  MIDI_ACTION_MEM_RECALL_M0,    // RECALLM0:           load current freq/mode/filter from memory slot #0
+  MIDI_ACTION_MEM_RECALL_M1,    // RECALLM1:           load current freq/mode/filter from memory slot #1
+  MIDI_ACTION_MEM_RECALL_M2,    // RECALLM2:           load current freq/mode/filter from memory slot #2
+  MIDI_ACTION_MEM_RECALL_M3,    // RECALLM3:           load current freq/mode/filter from memory slot #3
+  MIDI_ACTION_MEM_RECALL_M4,    // RECALLM4:           load current freq/mode/filter from memory slot #4
+  MIDI_ACTION_MENU_FILTER,      // MENU_FILTER
+  MIDI_ACTION_MENU_MODE,        // MENU_MODE
   MIDI_ACTION_MIC_VOLUME,      // MICGAIN:             MIC gain
   MIDI_ACTION_MODE_DOWN,       // MODEDOWN:            cycle through modes downwards
   MIDI_ACTION_MODE_UP,         // MODEUP:              cycle through modes upwards
@@ -83,17 +112,24 @@ enum MIDIaction {
   MIDI_ACTION_MUTE,            // MUTE:                toggle mute on/off
   MIDI_ACTION_NB,              // NOISEBLANKER:        cycle through NoiseBlanker states (none, NB, NB2)
   MIDI_ACTION_NR,              // NOISEREDUCTION:      cycle through NoiseReduction states (none, NR, NR2)
+  MIDI_ACTION_NUMPAD_0,         // NUMPAD0
+  MIDI_ACTION_NUMPAD_1,         // NUMPAD1
+  MIDI_ACTION_NUMPAD_2,         // NUMPAD2
+  MIDI_ACTION_NUMPAD_3,         // NUMPAD3
+  MIDI_ACTION_NUMPAD_4,         // NUMPAD4
+  MIDI_ACTION_NUMPAD_5,         // NUMPAD5
+  MIDI_ACTION_NUMPAD_6,         // NUMPAD6
+  MIDI_ACTION_NUMPAD_7,         // NUMPAD7
+  MIDI_ACTION_NUMPAD_8,         // NUMPAD8
+  MIDI_ACTION_NUMPAD_9,         // NUMPAD9
+  MIDI_ACTION_NUMPAD_CL,        // NUMPADCL
+  MIDI_ACTION_NUMPAD_ENTER,     // NUMPADENTER
   MIDI_ACTION_PAN,             // PAN:                 change panning of panadater/waterfall when zoomed
   MIDI_ACTION_PAN_HIGH,                // PANHIGH:             "high" value of current panadapter
   MIDI_ACTION_PAN_LOW,         // PANLOW:              "low" value of current panadapter
   MIDI_ACTION_PRE,             // PREAMP:              preamp on/off
-  MIDI_ACTION_PTTONOFF,                // PTT:                 set PTT state to "on" or "off"
+  MIDI_ACTION_PTTKEYER,                // PTT(Keyer):                  set PTT state to "on" or "off"
   MIDI_ACTION_PS,              // PURESIGNAL:          toggle PURESIGNAL on/off
-  MIDI_ACTION_MEM_RECALL_M0,    // RECALLM0:           load current freq/mode/filter from memory slot #0
-  MIDI_ACTION_MEM_RECALL_M1,    // RECALLM1:           load current freq/mode/filter from memory slot #1
-  MIDI_ACTION_MEM_RECALL_M2,    // RECALLM2:           load current freq/mode/filter from memory slot #2
-  MIDI_ACTION_MEM_RECALL_M3,    // RECALLM3:           load current freq/mode/filter from memory slot #3
-  MIDI_ACTION_MEM_RECALL_M4,    // RECALLM4:           load current freq/mode/filter from memory slot #4
   MIDI_ACTION_RF_GAIN,         // RFGAIN:              receiver RF gain
   MIDI_ACTION_TX_DRIVE,                // RFPOWER:             adjust TX RF output power
   MIDI_ACTION_RIT_CLEAR,       // RITCLEAR:            clear RIT and XIT value
@@ -148,12 +184,15 @@ enum MIDIaction {
 //
 
 enum MIDItype {
- MIDI_TYPE_NONE=0,
- MIDI_TYPE_KEY,          // Button (press event)
- MIDI_TYPE_KNOB,         // Knob   (value between 0 and 100)
- MIDI_TYPE_WHEEL         // Wheel  (direction and speed)
+ MIDI_TYPE_NONE =0,
+ MIDI_TYPE_KEY  =1,      // Button (press event)
+ MIDI_TYPE_KNOB =2,      // Knob   (value between 0 and 100)
+ MIDI_TYPE_WHEEL=4       // Wheel  (direction and speed)
 };
 
+extern gchar *midi_types[];
+extern gchar *midi_events[];
+
 //
 // MIDIevent encodes the actual MIDI event "seen" in Layer-1 and
 // passed to Layer-2. MIDI_NOTE events end up as MIDI_KEY and
@@ -167,6 +206,15 @@ enum MIDIevent {
  MIDI_EVENT_PITCH
 };
 
+typedef struct _action_table {
+  enum MIDIaction action;
+  const char *str;
+  enum MIDItype type;
+  int onoff;
+} ACTION_TABLE;
+
+extern ACTION_TABLE ActionTable[];
+
 //
 // Data structure for Layer-2
 //
@@ -212,17 +260,18 @@ struct desc {
    struct desc       *next;       // Next defined action for a controller/key with that note value (NULL for end of list)
 };
 
-struct cmdtable{
-   struct desc *desc[128];    // description for Note On/Off and ControllerChange
-   struct desc *pitch;        // description for PitchChanges
-};
+extern struct desc *MidiCommandsTable[129];
+
+extern int midi_debug;
 
 //
 // Layer-1 entry point, called once for all the MIDI devices
 // that have been defined. This is called upon startup by
 // Layer-2 through the function MIDIstartup.
 //
-void register_midi_device(char *name);
+int register_midi_device(char *name);
+void close_midi_device();
+void configure_midi_device(gboolean state);
 
 //
 // Layer-2 entry point (called by Layer1)
@@ -235,7 +284,9 @@ void register_midi_device(char *name);
 // for each device description that was successfully read.
 
 void NewMidiEvent(enum MIDIevent event, int channel, int note, int val);
-void MIDIstartup();
+int MIDIstartup(char *filename);
+void MidiAddCommand(int note, struct desc *desc);
+void MidiReleaseCommands();
 
 //
 // Layer-3 entry point (called by Layer2). In Layer-3, all the pihpsdr
diff --git a/midi2.c b/midi2.c
index 50c67438afae13011311a278957e2ba944c627a6..e13b36e11d05b664fd788a90d77b4e0abb4ae6f3 100644 (file)
--- a/midi2.c
+++ b/midi2.c
@@ -8,6 +8,7 @@
  */
 
 #include <gtk/gtk.h>
+
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include "MacOS.h"  // emulate clock_gettime on old MacOS systems
 #endif
 
+#include "receiver.h"
+#include "discovered.h"
+#include "adc.h"
+#include "dac.h"
+#include "transmitter.h"
+#include "radio.h"
+#include "main.h"
 #include "midi.h"
 
 static double midi_startup_time;
 static int    midi_wait_startup=0;
 
-struct cmdtable MidiCommandsTable;
+struct desc *MidiCommandsTable[129];
 
 void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) {
 
@@ -53,13 +61,13 @@ void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) {
       midi_wait_startup=0;
     }
     if (event == MIDI_EVENT_PITCH) {
-       desc=MidiCommandsTable.pitch;
+       desc=MidiCommandsTable[128];
     } else {
-       desc=MidiCommandsTable.desc[note];
+       desc=MidiCommandsTable[note];
     }
-//fprintf(stderr,"MIDI:init DESC=%p\n",desc);
+//g_print("MIDI:init DESC=%p\n",desc);
     while (desc) {
-//fprintf(stderr,"DESC=%p next=%p CHAN=%d EVENT=%d\n", desc,desc->next,desc->channel,desc->event);
+//g_print("DESC=%p next=%p CHAN=%d EVENT=%d\n", desc,desc->next,desc->channel,desc->event);
        if ((desc->channel == channel || desc->channel == -1) && (desc->event == event)) {
            // Found matching entry
            switch (desc->event) {
@@ -83,15 +91,18 @@ void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) {
                        }
                        // translate value to direction
                        new=0;
+                       new=val-64;   // FIXME: allow for different speed
+                        /*
                        if ((val >= desc->vfl1) && (val <= desc->vfl2)) new=-100;
                        if ((val >= desc-> fl1) && (val <= desc-> fl2)) new=-10;
                        if ((val >= desc->lft1) && (val <= desc->lft2)) new=-1;
                        if ((val >= desc->rgt1) && (val <= desc->rgt2)) new= 1;
                        if ((val >= desc-> fr1) && (val <= desc-> fr2)) new= 10;
                        if ((val >= desc->vfr1) && (val <= desc->vfr2)) new= 100;
-//                     fprintf(stderr,"WHEEL: val=%d new=%d thrs=%d/%d, %d/%d, %d/%d, %d/%d, %d/%d, %d/%d\n",
-//                                  val, new, desc->vfl1, desc->vfl2, desc->fl1, desc->fl2, desc->lft1, desc->lft2,
-//                                       desc->rgt1, desc->rgt2, desc->fr1, desc->fr2, desc->vfr1, desc->vfr2);
+                        */
+//                     g_print("WHEEL: val=%d new=%d thrs=%d/%d, %d/%d, %d/%d, %d/%d, %d/%d, %d/%d\n",
+//                               val, new, desc->vfl1, desc->vfl2, desc->fl1, desc->fl2, desc->lft1, desc->lft2,
+//                              desc->rgt1, desc->rgt2, desc->fr1, desc->fr2, desc->vfr1, desc->vfr2);
                        if (new != 0) DoTheMidi(desc->action, desc->type, new);
                        last_wheel_action=desc->action;
                    }
@@ -113,101 +124,139 @@ void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) {
     }
     if (!desc) {
       // Nothing found. This is nothing to worry about, but log the key to stderr
-      if (event == MIDI_EVENT_PITCH) fprintf(stderr, "Unassigned PitchBend Value=%d\n", val);
-      if (event == MIDI_EVENT_NOTE ) fprintf(stderr, "Unassigned Key Note=%d Val=%d\n", note, val);
-      if (event == MIDI_EVENT_CTRL ) fprintf(stderr, "Unassigned Controller Ctl=%d Val=%d\n", note, val);
+      if (event == MIDI_EVENT_PITCH) g_print("Unassigned PitchBend Value=%d\n", val);
+      if (event == MIDI_EVENT_NOTE ) g_print("Unassigned Key Note=%d Val=%d\n", note, val);
+      if (event == MIDI_EVENT_CTRL ) g_print("Unassigned Controller Ctl=%d Val=%d\n", note, val);
     }
 }
 
+gchar *midi_types[] = {"NONE","KEY","KNOB/SLIDER","*INVALID*","WHEEL"};
+gchar *midi_events[] = {"NONE","NOTE","CTRL","PITCH"};
+
 /*
  * This data structre connects names as used in the midi.props file with
  * our MIDIaction enum values.
- * Take care that no key word is contained in another one!
- * Example: use "CURRVFO" not "VFO" otherwise there is possibly
- * a match for "VFO" when the key word is "VFOA".
+ *
+ * At some places in the code, it is assumes that ActionTable[i].action == i
+ * so keep the entries strictly in the order the enum is defined, and
+ * add one entry with ACTION_NONE at the end.
  */
 
-static struct {
-  enum MIDIaction action;    // the MIDI action
-  const char *str;           // the key word in the midi.props file
-  int   onoff;               // =1 if action both on press + release
-} ActionTable[] = {
-       { MIDI_ACTION_VFO_A2B,          "A2B",                 0},
-        { MIDI_ACTION_AF_GAIN,         "AFGAIN",              0},
-       { MIDI_ACTION_AGCATTACK,        "AGCATTACK",           0},
-        { MIDI_ACTION_AGC,                     "AGCVAL",              0},
-        { MIDI_ACTION_ANF,                     "ANF",                 0},
-        { MIDI_ACTION_ATT,             "ATT",                 0},
-       { MIDI_ACTION_VFO_B2A,          "B2A",                 0},
-        { MIDI_ACTION_BAND_DOWN,       "BANDDOWN",            0},
-        { MIDI_ACTION_BAND_UP,         "BANDUP",              0},
-        { MIDI_ACTION_COMPRESS,        "COMPRESS",            0},
-       { MIDI_ACTION_CTUN,             "CTUN",                0},
-       { MIDI_ACTION_VFO,              "CURRVFO",             0},
-       { MIDI_ACTION_CWKEY,            "CWKEY",               1},
-       { MIDI_ACTION_CWL,              "CWL",                 1},
-       { MIDI_ACTION_CWR,              "CWR",                 1},
-       { MIDI_ACTION_CWSPEED,          "CWSPEED",             0},
-       { MIDI_ACTION_DIV_COARSEGAIN,   "DIVCOARSEGAIN",       0},
-       { MIDI_ACTION_DIV_COARSEPHASE,  "DIVCOARSEPHASE",      0},
-       { MIDI_ACTION_DIV_FINEGAIN,     "DIVFINEGAIN",         0},
-       { MIDI_ACTION_DIV_FINEPHASE,    "DIVFINEPHASE",        0},
-       { MIDI_ACTION_DIV_GAIN,         "DIVGAIN",             0},
-       { MIDI_ACTION_DIV_PHASE,        "DIVPHASE",            0},
-       { MIDI_ACTION_DIV_TOGGLE,       "DIVTOGGLE",           0},
-       { MIDI_ACTION_DUP,              "DUP",                 0},
-        { MIDI_ACTION_FILTER_DOWN,     "FILTERDOWN",          0},
-        { MIDI_ACTION_FILTER_UP,       "FILTERUP",            0},
-       { MIDI_ACTION_LOCK,             "LOCK",                0},
-        { MIDI_ACTION_MIC_VOLUME,      "MICGAIN",             0},
-       { MIDI_ACTION_MODE_DOWN,        "MODEDOWN",            0},
-       { MIDI_ACTION_MODE_UP,          "MODEUP",              0},
-        { MIDI_ACTION_MOX,                     "MOX",                 0},
-       { MIDI_ACTION_MUTE,             "MUTE",                0},
-       { MIDI_ACTION_NB,               "NOISEBLANKER",        0},
-       { MIDI_ACTION_NR,               "NOISEREDUCTION",      0},
-        { MIDI_ACTION_PAN,             "PAN",                 0},
-        { MIDI_ACTION_PAN_HIGH,        "PANHIGH",             0},
-        { MIDI_ACTION_PAN_LOW,         "PANLOW",              0},
-        { MIDI_ACTION_PRE,             "PREAMP",              0},
-       { MIDI_ACTION_PTTONOFF,         "PTT",                 1},
-       { MIDI_ACTION_PS,               "PURESIGNAL",          0},
-        { MIDI_ACTION_MEM_RECALL_M0,   "RECALLM0",            0},
-        { MIDI_ACTION_MEM_RECALL_M1,   "RECALLM1",            0},
-        { MIDI_ACTION_MEM_RECALL_M2,   "RECALLM2",            0},
-        { MIDI_ACTION_MEM_RECALL_M3,   "RECALLM3",            0},
-        { MIDI_ACTION_MEM_RECALL_M4,   "RECALLM4",            0},
-       { MIDI_ACTION_RF_GAIN,          "RFGAIN",              0},
-        { MIDI_ACTION_TX_DRIVE,        "RFPOWER",             0},
-       { MIDI_ACTION_RIT_CLEAR,        "RITCLEAR",            0},
-       { MIDI_ACTION_RIT_STEP,         "RITSTEP",             0},
-        { MIDI_ACTION_RIT_TOGGLE,      "RITTOGGLE",           0},
-        { MIDI_ACTION_RIT_VAL,         "RITVAL",              0},
-        { MIDI_ACTION_SAT,                     "SAT",                 0},
-        { MIDI_ACTION_SNB,             "SNB",                 0},
-       { MIDI_ACTION_SPLIT,            "SPLIT",               0},
-        { MIDI_ACTION_MEM_STORE_M0,     "STOREM0",             0},
-        { MIDI_ACTION_MEM_STORE_M1,     "STOREM1",             0},
-        { MIDI_ACTION_MEM_STORE_M2,     "STOREM2",             0},
-        { MIDI_ACTION_MEM_STORE_M3,     "STOREM3",             0},
-        { MIDI_ACTION_MEM_STORE_M4,     "STOREM4",             0},
-       { MIDI_ACTION_SWAP_RX,          "SWAPRX",              0},
-       { MIDI_ACTION_SWAP_VFO,         "SWAPVFO",             0},
-        { MIDI_ACTION_TUNE,                    "TUNE",                0},
-        { MIDI_ACTION_VFOA,            "VFOA",                0},
-        { MIDI_ACTION_VFOB,            "VFOB",                0},
-       { MIDI_ACTION_VFO_STEP_UP,      "VFOSTEPUP",           0},
-       { MIDI_ACTION_VFO_STEP_DOWN,    "VFOSTEPDOWN",         0},
-       { MIDI_ACTION_VOX,              "VOX",                 0},
-       { MIDI_ACTION_VOXLEVEL,         "VOXLEVEL",            0},
-       { MIDI_ACTION_XIT_CLEAR,        "XITCLEAR",            0},
-       { MIDI_ACTION_XIT_VAL,          "XITVAL",              0},
-       { MIDI_ACTION_ZOOM,             "ZOOM",                0},
-       { MIDI_ACTION_ZOOM_UP,          "ZOOMUP",              0},
-       { MIDI_ACTION_ZOOM_DOWN,        "ZOOMDOWN",            0},
-        { MIDI_ACTION_NONE,            "NONE",                0}
+ACTION_TABLE ActionTable[] = {
+        { MIDI_ACTION_NONE,            "NONE",                 MIDI_TYPE_NONE,                                 0},
+        { MIDI_ACTION_VFO_A2B,                 "A2B",                  MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_AF_GAIN,                 "AFGAIN",               MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_AGCATTACK,               "AGCATTACK",            MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_AGC,                     "AGCVAL",               MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+       { MIDI_ACTION_ANF,              "ANF",                  MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_ATT,                     "ATT",                  MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_VFO_B2A,         "B2A",                  MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_10,                 "BAND10",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_12,                 "BAND12",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_1240,               "BAND1240",             MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_144,                "BAND144",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_15,                 "BAND15",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_160,                "BAND160",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_17,                 "BAND17",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_20,                 "BAND20",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_220,                "BAND220",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_2300,               "BAND2300",             MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_30,                 "BAND30",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_3400,               "BAND3400",             MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_40,                 "BAND40",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_430,                "BAND430",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_6,                  "BAND6",                MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_60,                 "BAND60",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_70,                 "BAND70",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_80,                 "BAND80",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_902,                "BAND902",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_AIR,                "BANDAIR",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_DOWN,       "BANDDOWN",             MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_GEN,        "BANDGEN",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_UP,                 "BANDUP",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_BAND_WWV,                "BANDWWV",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_COMPRESS,                "COMPRESS",             MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_CTUN,                    "CTUN",                 MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_VFO,                     "CURRVFO",              MIDI_TYPE_WHEEL,                                0},
+        { MIDI_ACTION_CWKEYER,                 "CW(Keyer)",            MIDI_TYPE_KEY,                                  1},
+        { MIDI_ACTION_CWLEFT,          "CWLEFT",               MIDI_TYPE_KEY,                                  1},
+        { MIDI_ACTION_CWRIGHT,                 "CWRIGHT",              MIDI_TYPE_KEY,                                  1},
+        { MIDI_ACTION_CWSPEED,                 "CWSPEED",              MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_DIV_COARSEGAIN,          "DIVCOARSEGAIN",        MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_DIV_COARSEPHASE,  "DIVCOARSEPHASE",       MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                        0},
+        { MIDI_ACTION_DIV_FINEGAIN,     "DIVFINEGAIN",         MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_DIV_FINEPHASE,    "DIVFINEPHASE",        MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_DIV_GAIN,         "DIVGAIN",             MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_DIV_PHASE,        "DIVPHASE",            MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_DIV_TOGGLE,       "DIVTOGGLE",           MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_DUP,                     "DUP",                  MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_FILTER_DOWN,      "FILTERDOWN",          MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_FILTER_UP,        "FILTERUP",            MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_LOCK,                    "LOCK",                 MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MEM_RECALL_M0,    "RECALLM0",            MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MEM_RECALL_M1,    "RECALLM1",            MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MEM_RECALL_M2,    "RECALLM2",            MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MEM_RECALL_M3,    "RECALLM3",            MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MEM_RECALL_M4,    "RECALLM4",            MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MENU_FILTER,      "MENU_FILTER",         MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MENU_MODE,        "MENU_MODE",           MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MIC_VOLUME,       "MICGAIN",             MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_MODE_DOWN,        "MODEDOWN",            MIDI_TYPE_KEY|MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,   0},
+        { MIDI_ACTION_MODE_UP,          "MODEUP",              MIDI_TYPE_KEY|MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,   0},
+        { MIDI_ACTION_MOX,                     "MOX",                  MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MUTE,                    "MUTE",                 MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NB,              "NOISEBLANKER",         MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NR,                      "NOISEREDUCTION",       MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_0,                "NUMPAD0",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_1,                "NUMPAD1",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_2,                "NUMPAD2",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_3,                "NUMPAD3",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_4,                "NUMPAD4",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_5,                "NUMPAD5",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_6,                "NUMPAD6",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_7,                "NUMPAD7",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_8,                "NUMPAD8",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_9,                "NUMPAD9",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_CL,               "NUMPADCL",             MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NUMPAD_ENTER,            "NUMPADENTER",          MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_PAN,                     "PAN",                  MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_PAN_HIGH,                "PANHIGH",              MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_PAN_LOW,                 "PANLOW",               MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_PRE,                     "PREAMP",               MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_PTTKEYER,                "PTT(Keyer)",           MIDI_TYPE_KEY,                                  1},
+        { MIDI_ACTION_PS,               "PURESIGNAL",          MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_RF_GAIN,                 "RFGAIN",               MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_TX_DRIVE,         "RFPOWER",             MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_RIT_CLEAR,               "RITCLEAR",             MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_RIT_STEP,         "RITSTEP",             MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_RIT_TOGGLE,       "RITTOGGLE",           MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_RIT_VAL,          "RITVAL",              MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_SAT,                     "SAT",                  MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_SNB,              "SNB",                 MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_SPLIT,                   "SPLIT",                MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MEM_STORE_M0,     "STOREM0",                     MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MEM_STORE_M1,     "STOREM1",                     MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MEM_STORE_M2,     "STOREM2",                     MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_MEM_STORE_M3,     "STOREM3",              MIDI_TYPE_KEY,                                 0},
+        { MIDI_ACTION_MEM_STORE_M4,     "STOREM4",              MIDI_TYPE_KEY,                                 0},
+        { MIDI_ACTION_SWAP_RX,          "SWAPRX",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_SWAP_VFO,         "SWAPVFO",             MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_TUNE,                    "TUNE",                 MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_VFOA,             "VFOA",                MIDI_TYPE_WHEEL,                                0},
+        { MIDI_ACTION_VFOB,             "VFOB",                MIDI_TYPE_WHEEL,                                0},
+        { MIDI_ACTION_VFO_STEP_UP,      "VFOSTEPUP",           MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_VFO_STEP_DOWN,    "VFOSTEPDOWN",         MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_VOX,              "VOX",                 MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_VOXLEVEL,         "VOXLEVEL",            MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_XIT_CLEAR,               "XITCLEAR",             MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_XIT_VAL,          "XITVAL",              MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_ZOOM,                    "ZOOM",                 MIDI_TYPE_KNOB|MIDI_TYPE_WHEEL,                 0},
+        { MIDI_ACTION_ZOOM_UP,          "ZOOMUP",              MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_ZOOM_DOWN,        "ZOOMDOWN",            MIDI_TYPE_KEY,                                  0},
+        { MIDI_ACTION_NONE,            "NONE",                 MIDI_TYPE_NONE,                                 0}
 };
 
+
 /*
  * Translation from keyword in midi.props file to MIDIaction
  */
@@ -227,12 +276,64 @@ static void keyword2action(char *s, enum MIDIaction *action, int *onoff) {
     *onoff  = 0;
 }
 
+int MIDIstop() {
+  midi_enabled=FALSE;
+  close_midi_device();
+  return 0;
+}
+
+/*
+ * Release data from MidiCommandsTable
+ */
+
+void MidiReleaseCommands() {
+  int i;
+  struct desc *loop;
+  for (i=0; i<129; i++) {
+    loop = MidiCommandsTable[i];
+    while (loop != NULL) {
+      free(loop);
+      loop = loop->next;
+    }
+    MidiCommandsTable[i]=NULL;
+  }
+}
+
+/*
+ * Add a command to MidiCommandsTable
+ */
+
+void MidiAddCommand(int note, struct desc *desc) {
+  struct desc *loop;
+
+  if (note < 0 || note > 128) return;
+
+  //
+  // Actions with channel == -1 (ANY) must go to the end of the list
+  //
+  if (MidiCommandsTable[note] == NULL) {
+    // initialize linked list
+    MidiCommandsTable[note]=desc;
+  } else if (desc->channel >= 0) {
+    // add to top of the list
+    desc->next = MidiCommandsTable[note];
+    MidiCommandsTable[note]=desc;
+  } else {
+    // add to tail of the list
+    loop = MidiCommandsTable[note];
+    while (loop->next != NULL) {
+      loop = loop->next;
+    }
+    loop->next=desc;
+  }
+}
+
 /*
- * Here we read in a MIDI description file "midi.def" and fill the MidiCommandsTable
+ * Here we read in a MIDI description file and fill the MidiCommandsTable
  * data structure
  */
 
-void MIDIstartup() {
+int MIDIstartup(char *filename) {
     FILE *fpin;
     char zeile[255];
     char *cp,*cq;
@@ -248,11 +349,16 @@ void MIDIstartup() {
     char c;
     struct timespec ts;
 
-    for (i=0; i<128; i++) MidiCommandsTable.desc[i]=NULL;
-    MidiCommandsTable.pitch=NULL;
+    MidiReleaseCommands();
+
+    g_print("%s: %s\n",__FUNCTION__,filename);
+    fpin=fopen(filename, "r");
 
-    fpin=fopen("midi.props", "r");
-    if (!fpin) return;
+    g_print("%s: fpin=%p\n",__FUNCTION__,fpin);
+    if (!fpin) {
+      g_print("%s: failed to open MIDI device\n",__FUNCTION__);
+      return -1;
+    }
 
     for (;;) {
       if (fgets(zeile, 255, fpin) == NULL) break;
@@ -277,7 +383,7 @@ void MIDIstartup() {
        cp++;
       }
       
-//fprintf(stderr,"\nMIDI:INP:%s\n",zeile);
+g_print("\n%s:INP:%s\n",__FUNCTION__,zeile);
 
       if ((cp = strstr(zeile, "DEVICE="))) {
         // Delete comments and trailing blanks
@@ -290,17 +396,17 @@ void MIDIstartup() {
         midi_wait_startup=1;
         clock_gettime(CLOCK_MONOTONIC, &ts);
         midi_startup_time=ts.tv_sec + 1E-9*ts.tv_nsec;
-       register_midi_device(cp+7);
+       int result=register_midi_device(cp+7);
         continue; // nothing more in this line
       }
       chan=-1;  // default: any channel
       t1=t3=t5=t7= t9=t11=128;  // range that never occurs
       t2=t4=t6=t8=t10=t12=-1;   // range that never occurs
+      onoff=0;
       event=MIDI_EVENT_NONE;
       type=MIDI_TYPE_NONE;
       key=0;
       delay=0;
-      onoff=0;
 
       //
       // The KEY=, CTRL=, and PITCH= cases are mutually exclusive
@@ -311,18 +417,18 @@ void MIDIstartup() {
         sscanf(cp+4, "%d", &key);
         event=MIDI_EVENT_NOTE;
        type=MIDI_TYPE_KEY;
-//fprintf(stderr,"MIDI:KEY:%d\n", key);
+g_print("%s: MIDI:KEY:%d\n",__FUNCTION__, key);
       }
       if ((cp = strstr(zeile, "CTRL="))) {
         sscanf(cp+5, "%d", &key);
        event=MIDI_EVENT_CTRL;
        type=MIDI_TYPE_KNOB;
-//fprintf(stderr,"MIDI:CTL:%d\n", key);
+g_print("%s: MIDI:CTL:%d\n",__FUNCTION__, key);
       }
       if ((cp = strstr(zeile, "PITCH "))) {
         event=MIDI_EVENT_PITCH;
        type=MIDI_TYPE_KNOB;
-//fprintf(stderr,"MIDI:PITCH\n");
+g_print("%s: MIDI:PITCH\n",__FUNCTION__);
       }
       //
       // If event is still undefined, skip line
@@ -342,16 +448,16 @@ void MIDIstartup() {
         sscanf(cp+5, "%d", &chan);
        chan--;
         if (chan<0 || chan>15) chan=-1;
-//fprintf(stderr,"MIDI:CHA:%d\n",chan);
+g_print("%s:CHAN:%d\n",__FUNCTION__,chan);
       }
       if ((cp = strstr(zeile, "WHEEL")) && (type == MIDI_TYPE_KNOB)) {
        // change type from MIDI_TYPE_KNOB to MIDI_TYPE_WHEEL
         type=MIDI_TYPE_WHEEL;
-//fprintf(stderr,"MIDI:WHEEL\n");
+g_print("%s:WHEEL\n",__FUNCTION__);
       }
       if ((cp = strstr(zeile, "DELAY="))) {
         sscanf(cp+6, "%d", &delay);
-//fprintf(stderr,"MIDI:DELAY:%d\n",delay);
+g_print("%s:DELAY:%d\n",__FUNCTION__,delay);
       }
       if ((cp = strstr(zeile, "THR="))) {
         sscanf(cp+4, "%d %d %d %d %d %d %d %d %d %d %d %d",
@@ -364,7 +470,7 @@ void MIDIstartup() {
         while (*cq != 0 && *cq != '\n' && *cq != ' ' && *cq != '\t') cq++;
        *cq=0;
         keyword2action(cp+7, &action, &onoff);
-//fprintf(stderr,"MIDI:ACTION:%s (%d), onoff=%d\n",cp+7, action, onoff);
+g_print("MIDI:ACTION:%s (%d), onoff=%d\n",cp+7, action, onoff);
       }
       //
       // All data for a descriptor has been read. Construct it!
@@ -395,23 +501,12 @@ void MIDIstartup() {
       //
       if (event == MIDI_EVENT_PITCH) {
 //fprintf(stderr,"MIDI:TAB:Insert desc=%p in PITCH table\n",desc);
-       dp = MidiCommandsTable.pitch;
-       if (dp == NULL) {
-         MidiCommandsTable.pitch = desc;
-       } else {
-         while (dp->next != NULL) dp=dp->next;
-         dp->next=desc;
-       }
+        MidiAddCommand(129, desc);
       }
       if (event == MIDI_EVENT_NOTE || event == MIDI_EVENT_CTRL) {
-//fprintf(stderr,"MIDI:TAB:Insert desc=%p in CMDS[%d] table\n",desc,key);
-       dp = MidiCommandsTable.desc[key];
-       if (dp == NULL) {
-         MidiCommandsTable.desc[key]=desc;
-       } else {
-         while (dp->next != NULL) dp=dp->next;
-         dp->next=desc;
-       }
+g_print("%s:TAB:Insert desc=%p in CMDS[%d] table\n",__FUNCTION__,desc,key);
+        MidiAddCommand(key, desc);
       }
     }
+    return 0;
 }
diff --git a/midi3.c b/midi3.c
index 38ea44ad0f325c190cd26c0c3a482cc1aced31b1..ab5045e42d407f5c686d7b8c9c7990bdb4525029 100644 (file)
--- a/midi3.c
+++ b/midi3.c
@@ -39,7 +39,7 @@
 // code below, one can queue the "big switch statement" into the GTK
 // idle queue and exectue all GUI functions directly.
 //
-// However, this is not wanted for CWKEY, CWL and CWR since
+// However, this is not wanted for CWKEYER, CWLEFT and CWRIGHT since
 // these have to be processed with minimal delay (and do not call GUI functions).
 //
 // Therefore, these three cases are already handled in the MIDI callback
@@ -61,7 +61,8 @@ typedef struct _MIDIcmd MIDIcmd;
 static int DoTheRestOfTheMIDI(void *data);
 
 void DoTheMidi(enum MIDIaction action, enum MIDItype type, int val) {
-    if (action == MIDI_ACTION_CWKEY) {
+    g_print("%s: ACTION=%d TYPE=%d VAL=%d\n", __FUNCTION__, action, type, val);
+    if (action == MIDI_ACTION_CWKEYER) {
           //
           // This is a CW key-up/down which uses functions from the keyer
           // that by-pass the interrupt-driven standard action.
@@ -85,11 +86,11 @@ void DoTheMidi(enum MIDIaction action, enum MIDItype type, int val) {
           return;
     }
 #ifdef LOCALCW
-    if (action == MIDI_ACTION_CWL) {
+    if (action == MIDI_ACTION_CWLEFT) {
          keyer_event(1, val);
          return;
     }
-    if (action == MIDI_ACTION_CWR) {
+    if (action == MIDI_ACTION_CWRIGHT) {
          keyer_event(0, val);
          return;
     }
@@ -511,7 +512,7 @@ int DoTheRestOfTheMIDI(void *data) {
        /////////////////////////////////////////////////////////// "MOX"
        case MIDI_ACTION_MOX: // only key supported
            // Note this toggles the PTT state without knowing the
-            // actual state. See MIDI_ACTION_PTTONOFF for actually
+            // actual state. See MIDI_ACTION_PTTKEYER for actually
             // *setting* PTT
            if (type == MIDI_TYPE_KEY && can_transmit) {
                new = !mox;
@@ -666,8 +667,8 @@ int DoTheRestOfTheMIDI(void *data) {
                 update_att_preamp();
            }
            break;
-       /////////////////////////////////////////////////////////// "PTTONOFF"
-        case MIDI_ACTION_PTTONOFF:  // key only
+       /////////////////////////////////////////////////////////// "PTT(Keyer)"
+        case MIDI_ACTION_PTTKEYER:  // key only
             // always use with "ONOFF"
            if (type == MIDI_TYPE_KEY && can_transmit) {
                 mox_update(val);
diff --git a/midi_menu.c b/midi_menu.c
new file mode 100644 (file)
index 0000000..766e7dd
--- /dev/null
@@ -0,0 +1,1253 @@
+/* Copyright (C)
+* 2020 - John Melton, G0ORX/N6LYT
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+*
+*/
+
+#include <gtk/gtk.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <termios.h>
+
+#include "discovered.h"
+#include "mode.h"
+#include "filter.h"
+#include "band.h"
+#include "receiver.h"
+#include "transmitter.h"
+#include "receiver.h"
+#include "adc.h"
+#include "dac.h"
+#include "radio.h"
+#include "midi.h"
+#include "alsa_midi.h"
+#include "new_menu.h"
+#include "midi_menu.h"
+#include "property.h"
+
+enum {
+  EVENT_COLUMN,
+  CHANNEL_COLUMN,
+  NOTE_COLUMN,
+  TYPE_COLUMN,
+  ACTION_COLUMN,
+  N_COLUMNS
+};
+
+static GtkWidget *parent_window=NULL;
+static GtkWidget *menu_b=NULL;
+static GtkWidget *dialog=NULL;
+
+static GtkWidget *midi_enable_b;
+
+static GtkListStore *store;
+static GtkWidget *view;
+static GtkWidget *scrolled_window=NULL;
+static gulong selection_signal_id;
+GtkTreeSelection *selection;
+static GtkTreeModel *model;
+static GtkTreeIter iter;
+struct desc *current_cmd;
+
+static GtkWidget *filename;
+
+static GtkWidget *newEvent;
+static GtkWidget *newChannel;
+static GtkWidget *newNote;
+static GtkWidget *newVal;
+static GtkWidget *newType;
+static GtkWidget *newMin;
+static GtkWidget *newMax;
+static GtkWidget *newAction;
+static GtkWidget *configure_b;
+static GtkWidget *add_b;
+static GtkWidget *update_b;
+static GtkWidget *delete_b;
+
+static enum MIDIevent thisEvent=MIDI_EVENT_NONE;
+static int thisChannel;
+static int thisNote;
+static int thisVal;
+static int thisMin;
+static int thisMax;
+static enum MIDItype thisType;
+static enum MIDIaction thisAction;
+
+gchar *midi_device_name=NULL;
+static gint device_index=-1;
+
+enum {
+  UPDATE_NEW,
+  UPDATE_CURRENT,
+  UPDATE_EXISTING
+};
+
+static int update(void *data);
+static void load_store();
+static void add_store(int key,struct desc *cmd);
+
+static void cleanup() {
+  configure_midi_device(FALSE);
+  if(dialog!=NULL) {
+    gtk_widget_destroy(dialog);
+    dialog=NULL;
+    sub_menu=NULL;
+  }
+}
+
+static gboolean close_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) {
+  cleanup();
+  return TRUE;
+}
+
+static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
+  cleanup();
+  return FALSE;
+}
+
+static gboolean midi_enable_cb(GtkWidget *widget,gpointer data) {
+  if(midi_enabled) {
+    close_midi_device();
+  }
+  midi_enabled=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (widget));
+  if(midi_enabled) {
+    if(register_midi_device(midi_device_name)<0) {
+      midi_enabled=FALSE;
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (widget), midi_enabled);
+    }
+  }
+  return TRUE;
+}
+
+static void configure_cb(GtkWidget *widget, gpointer data) {
+  gboolean conf=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (widget));
+  configure_midi_device(conf);
+}
+
+static void device_changed_cb(GtkWidget *widget, gpointer data) {
+  device_index = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
+  if(midi_device_name!=NULL) {
+    g_free(midi_device_name);
+  }
+  midi_device_name=g_new(gchar,strlen(midi_devices[device_index].name)+1);
+  strcpy(midi_device_name,midi_devices[device_index].name);
+  if(midi_enabled) {
+    close_midi_device();
+    if(register_midi_device(midi_device_name)) {
+      midi_enabled=FALSE;
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(midi_enable_b), midi_enabled);
+    }
+  }
+}
+
+static void type_changed_cb(GtkWidget *widget, gpointer data) {
+  int i=1;
+  int j=1;
+
+  // update actions available for the type
+  gchar *type=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(widget));
+
+  g_print("%s: type=%s action=%d\n",__FUNCTION__,type,thisAction);
+  gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(newAction));
+  gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,ActionTable[0].str);
+  if(type==NULL || strcmp(type,"NONE")==0) {
+    // leave empty
+    gtk_combo_box_set_active (GTK_COMBO_BOX(newAction),0);
+  } else if(strcmp(type,"KEY")==0) {
+    // add all the Key actions
+    while(ActionTable[i].action!=MIDI_ACTION_NONE) {
+      if(ActionTable[i].type&MIDI_TYPE_KEY) {
+        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,ActionTable[i].str);
+       if(ActionTable[i].action==thisAction) {
+          gtk_combo_box_set_active(GTK_COMBO_BOX(newAction),j);
+       }
+       j++;
+      }
+      i++;
+    }
+  } else if(strcmp(type,"KNOB/SLIDER")==0) {
+    // add all the Knob actions
+    while(ActionTable[i].action!=MIDI_ACTION_NONE) {
+      if(ActionTable[i].type&MIDI_TYPE_KNOB) {
+        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,ActionTable[i].str);
+       if(ActionTable[i].action==thisAction) {
+          gtk_combo_box_set_active (GTK_COMBO_BOX(newAction),j);
+       }
+       j++;
+      }
+      i++;
+    }
+  } else if(strcmp(type,"WHEEL")==0) {
+    // add all the Wheel actions
+    while(ActionTable[i].action!=MIDI_ACTION_NONE) {
+      if(ActionTable[i].type&MIDI_TYPE_WHEEL || ActionTable[i].type&MIDI_TYPE_KNOB) {
+        gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,ActionTable[i].str);
+       if(ActionTable[i].action==thisAction) {
+          gtk_combo_box_set_active (GTK_COMBO_BOX(newAction),j);
+       }
+       j++;
+      }
+      i++;
+    }
+  }
+}
+
+static void row_inserted_cb(GtkTreeModel *tree_model,GtkTreePath *path, GtkTreeIter *iter,gpointer user_data) {
+  //g_print("%s\n",__FUNCTION__);
+  gtk_tree_view_set_cursor(GTK_TREE_VIEW(view),path,NULL,FALSE);
+}
+
+
+static void tree_selection_changed_cb (GtkTreeSelection *selection, gpointer data) {
+  char *str_event;
+  char *str_channel;
+  char *str_note;
+  char *str_type;
+  char *str_action;
+
+  //g_print("%s\n",__FUNCTION__);
+  //if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(configure_b))) {
+    if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+      gtk_tree_model_get(model, &iter, EVENT_COLUMN, &str_event, -1);
+      gtk_tree_model_get(model, &iter, CHANNEL_COLUMN, &str_channel, -1);
+      gtk_tree_model_get(model, &iter, NOTE_COLUMN, &str_note, -1);
+      gtk_tree_model_get(model, &iter, TYPE_COLUMN, &str_type, -1);
+      gtk_tree_model_get(model, &iter, ACTION_COLUMN, &str_action, -1);
+
+      g_print("%s: %s %s %s %s %s\n",__FUNCTION__,str_event,str_channel,str_note,str_type,str_action);
+
+      if(str_event!=NULL && str_channel!=NULL && str_note!=NULL && str_type!=NULL && str_action!=NULL) {
+
+        if(strcmp(str_event,"CTRL")==0) {
+          thisEvent=MIDI_EVENT_CTRL;
+        } else if(strcmp(str_event,"PITCH")==0) {
+          thisEvent=MIDI_EVENT_PITCH;
+        } else if(strcmp(str_event,"NOTE")==0) {
+          thisEvent=MIDI_EVENT_NOTE;
+        } else {
+          thisEvent=MIDI_EVENT_NONE;
+        }
+        thisChannel=atoi(str_channel);
+        thisNote=atoi(str_note);
+        thisVal=0;
+        thisMin=0;
+        thisMax=0;
+        if(strcmp(str_type,"KEY")==0) {
+          thisType=MIDI_TYPE_KEY;
+        } else if(strcmp(str_type,"KNOB/SLIDER")==0) {
+          thisType=MIDI_TYPE_KNOB;
+        } else if(strcmp(str_type,"WHEEL")==0) {
+          thisType=MIDI_TYPE_WHEEL;
+        } else {
+          thisType=MIDI_TYPE_NONE;
+        }
+        thisAction=MIDI_ACTION_NONE;
+        int i=1;
+        while(ActionTable[i].action!=MIDI_ACTION_NONE) {
+          if(strcmp(ActionTable[i].str,str_action)==0) {
+            thisAction=ActionTable[i].action;
+            break;
+          }
+          i++;
+        }
+        g_idle_add(update,GINT_TO_POINTER(UPDATE_EXISTING));
+      }
+    }
+  //}
+}
+
+static void find_current_cmd() {
+  struct desc *cmd;
+  g_print("%s:\n",__FUNCTION__);
+  cmd=MidiCommandsTable[thisNote];
+  while(cmd!=NULL) {
+    if((cmd->channel==thisChannel || cmd->channel==-1) && cmd->type==thisType && cmd->action==thisAction) {
+      g_print("%s: found cmd %p\n",__FUNCTION__,cmd);
+      break;
+    }
+    cmd=cmd->next;
+  }
+  current_cmd=cmd;  // NULL if not found
+}
+
+static void clear_cb(GtkWidget *widget,gpointer user_data) {
+  struct desc *cmd;
+  struct desc *next;
+
+  g_signal_handler_block(G_OBJECT(selection), selection_signal_id);
+  gtk_list_store_clear(store);
+  MidiReleaseCommands();  
+  g_signal_handler_unblock(G_OBJECT(selection), selection_signal_id);
+}
+
+static void save_cb(GtkWidget *widget,gpointer user_data) {
+  GtkWidget *save_dialog;
+  GtkFileChooser *chooser;
+  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
+  gchar *filename;
+  gint res;
+  struct desc *cmd;
+
+  save_dialog = gtk_file_chooser_dialog_new ("Save File",
+                                      GTK_WINDOW(dialog),
+                                      action,
+                                      "_Cancel",
+                                      GTK_RESPONSE_CANCEL,
+                                      "_Save",
+                                      GTK_RESPONSE_ACCEPT,
+                                      NULL);
+  chooser = GTK_FILE_CHOOSER (save_dialog);
+  gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
+  if(midi_device_name==NULL) {
+    filename=g_new(gchar,10);
+    sprintf(filename,"midi.midi");
+  } else {
+    filename=g_new(gchar,strlen(midi_device_name)+6);
+    sprintf(filename,"%s.midi",midi_device_name);
+  }
+  gtk_file_chooser_set_current_name(chooser,filename);
+  res = gtk_dialog_run (GTK_DIALOG (save_dialog));
+  if(res==GTK_RESPONSE_ACCEPT) {
+    char *savefilename=gtk_file_chooser_get_filename(chooser);
+    clearProperties();
+    midi_save_state();
+    saveProperties(savefilename);
+    g_free(savefilename);
+  }
+  gtk_widget_destroy(save_dialog);
+  g_free(filename);
+}
+
+static void load_cb(GtkWidget *widget,gpointer user_data) {
+  GtkWidget *load_dialog;
+  GtkFileChooser *chooser;
+  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
+  gchar *filename;
+  gint res;
+  struct desc *cmd;
+
+  load_dialog = gtk_file_chooser_dialog_new ("Open MIDI File",
+                                      GTK_WINDOW(dialog),
+                                      action,
+                                      "_Cancel",
+                                      GTK_RESPONSE_CANCEL,
+                                      "_Save",
+                                      GTK_RESPONSE_ACCEPT,
+                                      NULL);
+  chooser = GTK_FILE_CHOOSER (load_dialog);
+  if(midi_device_name==NULL) {
+    filename=g_new(gchar,10);
+    sprintf(filename,"midi.midi");
+  } else {
+    filename=g_new(gchar,strlen(midi_device_name)+6);
+    sprintf(filename,"%s.midi",midi_device_name);
+  }
+  gtk_file_chooser_set_current_name(chooser,filename);
+  res = gtk_dialog_run (GTK_DIALOG (load_dialog));
+  if(res==GTK_RESPONSE_ACCEPT) {
+    char *loadfilename=gtk_file_chooser_get_filename(chooser);
+    clear_cb(NULL,NULL);
+    clearProperties();
+    loadProperties(loadfilename);
+    midi_restore_state();
+    load_store();
+    g_free(loadfilename);
+  }
+  gtk_widget_destroy(load_dialog);
+  g_free(filename);
+}
+
+static void load_original_cb(GtkWidget *widget,gpointer user_data) {
+  GtkWidget *load_dialog;
+  GtkFileChooser *chooser;
+  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
+  gchar *filename;
+  gint res;
+  struct desc *cmd;
+
+  load_dialog = gtk_file_chooser_dialog_new ("Open ORIGINAL MIDI File",
+                                      GTK_WINDOW(dialog),
+                                      action,
+                                      "_Cancel",
+                                      GTK_RESPONSE_CANCEL,
+                                      "_Save",
+                                      GTK_RESPONSE_ACCEPT,
+                                      NULL);
+  chooser = GTK_FILE_CHOOSER (load_dialog);
+  if(midi_device_name==NULL) {
+    filename=g_new(gchar,10);
+    sprintf(filename,"midi.midi");
+  } else {
+    filename=g_new(gchar,strlen(midi_device_name)+6);
+    sprintf(filename,"%s.midi",midi_device_name);
+  }
+  gtk_file_chooser_set_current_name(chooser,filename);
+  res = gtk_dialog_run (GTK_DIALOG (load_dialog));
+  if(res==GTK_RESPONSE_ACCEPT) {
+    char *loadfilename=gtk_file_chooser_get_filename(chooser);
+    clear_cb(NULL,NULL);
+    MIDIstartup(loadfilename);
+    load_store();
+    g_free(loadfilename);
+  }
+  gtk_widget_destroy(load_dialog);
+  g_free(filename);
+}
+
+static void add_store(int key,struct desc *cmd) {
+  char str_event[16];
+  char str_channel[16];
+  char str_note[16];
+  char str_type[32];
+  char str_action[32];
+
+  //g_print("%s: key=%d desc=%p\n",__FUNCTION__,key,cmd);
+  switch(cmd->event) {
+    case MIDI_EVENT_NONE:
+      strcpy(str_event,"NONE");
+      break;
+    case MIDI_EVENT_NOTE:
+      strcpy(str_event,"NOTE");
+      break;
+    case MIDI_EVENT_CTRL:
+      strcpy(str_event,"CTRL");
+      break;
+    case MIDI_EVENT_PITCH:
+      strcpy(str_event,"PITCH");
+      break;
+  }
+  if (cmd->channel >= 0) {
+    sprintf(str_channel,"%d",cmd->channel);
+  } else {
+    sprintf(str_channel,"%s","Any");
+  }
+  sprintf(str_note,"%d",key);
+  switch(cmd->type) {
+    case MIDI_TYPE_NONE:
+      strcpy(str_type,"NONE");
+      break;
+    case MIDI_TYPE_KEY:
+      strcpy(str_type,"KEY");
+      break;
+    case MIDI_TYPE_KNOB:
+      strcpy(str_type,"KNOB/SLIDER");
+      break;
+    case MIDI_TYPE_WHEEL:
+      strcpy(str_type,"WHEEL");
+      break;
+  }
+  strcpy(str_action,ActionTable[cmd->action].str);
+  
+  g_print("%s: Event=%s Channel=%s Note=%s Type=%s Action=%s\n", __FUNCTION__, str_event, str_channel, str_note, str_type, str_action);
+  gtk_list_store_prepend(store,&iter);
+  gtk_list_store_set(store,&iter,
+      EVENT_COLUMN,str_event,
+      CHANNEL_COLUMN,str_channel,
+      NOTE_COLUMN,str_note,
+      TYPE_COLUMN,str_type,
+      ACTION_COLUMN,str_action,
+      -1);
+
+  if(scrolled_window!=NULL) {
+    GtkAdjustment *adjustment=gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW(scrolled_window));
+    //g_print("%s: adjustment=%f lower=%f upper=%f\n",__FUNCTION__,gtk_adjustment_get_value(adjustment),gtk_adjustment_get_lower(adjustment),gtk_adjustment_get_upper(adjustment));
+    if(gtk_adjustment_get_value(adjustment)!=0.0) {
+      gtk_adjustment_set_value(adjustment,0.0);
+    }
+  }
+}
+
+static void load_store() {
+  struct desc *cmd;
+  gtk_list_store_clear(store);
+  for(int i=127;i>=0;i--) {
+    cmd=MidiCommandsTable[i];
+    while(cmd!=NULL) {
+      add_store(i,cmd);
+      cmd=cmd->next;
+    }
+  }
+}
+
+static void add_cb(GtkButton *widget,gpointer user_data) {
+
+  gchar *str_type=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(newType));
+  gchar *str_action=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(newAction));
+;
+
+  gint i;
+  gint type;
+  gint action;
+  gint onoff;
+
+  if(str_type==NULL || str_action==NULL) {
+    return;
+  }
+
+  if(strcmp(str_type,"KEY")==0) {
+    type=MIDI_TYPE_KEY;
+  } else if(strcmp(str_type,"KNOB/SLIDER")==0) {
+    type=MIDI_TYPE_KNOB;
+  } else if(strcmp(str_type,"WHEEL")==0) {
+    type=MIDI_TYPE_WHEEL;
+  } else {
+    type=MIDI_TYPE_NONE;
+  }
+
+  action=MIDI_ACTION_NONE;
+  i=1;
+  while(ActionTable[i].action!=MIDI_ACTION_NONE) {
+    if(strcmp(ActionTable[i].str,str_action)==0) {
+      action=ActionTable[i].action;
+      onoff=ActionTable[i].onoff;
+      break;
+    }
+    i++;
+  }
+
+  g_print("%s: type=%s (%d) action=%s (%d)\n",__FUNCTION__,str_type,type,str_action,action);
+
+  struct desc *desc;
+  desc = (struct desc *) malloc(sizeof(struct desc));
+  desc->next = NULL;
+  desc->action = action; // MIDIaction
+  desc->type = type; // MIDItype
+  desc->event = thisEvent; // MIDevent
+  desc->onoff = onoff;
+  desc->delay = 0;
+  desc->vfl1  = -1;
+  desc->vfl2  = -1;
+  desc->fl1   = -1;
+  desc->fl2   = -1;
+  desc->lft1  = -1;
+  desc->lft2  = 63;
+  desc->rgt1  = 64;
+  desc->rgt2  = 128;
+  desc->fr1   = 128;
+  desc->fr2   = 128;
+  desc->vfr1  = 128;
+  desc->vfr2  = 128;
+  desc->channel  = thisChannel;
+
+  gint key=thisNote;
+  if(key<0) key=0;
+  if(key>127) key=0;
+
+
+  MidiAddCommand(key, desc);
+  add_store(key,desc);
+
+  gtk_widget_set_sensitive(add_b,FALSE);
+  gtk_widget_set_sensitive(update_b,TRUE);
+  gtk_widget_set_sensitive(delete_b,TRUE);
+
+}
+
+static void update_cb(GtkButton *widget,gpointer user_data) {
+  char str_event[16];
+  char str_channel[16];
+  char str_note[16];
+  int i;
+
+  if (current_cmd == NULL) {
+    g_print("%s: current_cmd is NULL!\n", __FUNCTION__);
+    return;
+  }
+
+  gchar *str_type=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(newType));
+  gchar *str_action=gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(newAction));
+;
+  //g_print("%s: type=%s action=%s\n",__FUNCTION__,str_type,str_action);
+
+  if(strcmp(str_type,"KEY")==0) {
+    thisType=MIDI_TYPE_KEY;
+  } else if(strcmp(str_type,"KNOB/SLIDER")==0) {
+    thisType=MIDI_TYPE_KNOB;
+  } else if(strcmp(str_type,"WHEEL")==0) {
+    thisType=MIDI_TYPE_WHEEL;
+  } else {
+    thisType=MIDI_TYPE_NONE;
+  }
+
+  thisAction=MIDI_ACTION_NONE;
+  i=1;
+  while(ActionTable[i].action!=MIDI_ACTION_NONE) {
+    if(strcmp(ActionTable[i].str,str_action)==0) {
+      thisAction=ActionTable[i].action;
+      break;
+    }
+    i++;
+  }
+
+  current_cmd->channel=thisChannel;
+  current_cmd->type=thisType;
+  current_cmd->action=thisAction;
+
+  switch(current_cmd->event) {
+    case MIDI_EVENT_NONE:
+      strcpy(str_event,"NONE");
+      break;
+    case MIDI_EVENT_NOTE:
+      strcpy(str_event,"NOTE");
+      break;
+    case MIDI_EVENT_CTRL:
+      strcpy(str_event,"CTRL");
+      break;
+    case MIDI_EVENT_PITCH:
+      strcpy(str_event,"PITCH");
+      break;
+  }
+  sprintf(str_channel,"%d",current_cmd->channel);
+  sprintf(str_note,"%d",thisNote);
+
+  g_print("%s: event=%s channel=%s note=%s type=%s action=%s\n",
+          __FUNCTION__,str_event,str_channel,str_note,str_type,str_action);
+  gtk_list_store_set(store,&iter,
+      EVENT_COLUMN,str_event,
+      CHANNEL_COLUMN,str_channel,
+      NOTE_COLUMN,str_note,
+      TYPE_COLUMN,str_type,
+      ACTION_COLUMN,str_action,
+      -1);
+}
+
+static void delete_cb(GtkButton *widget,gpointer user_data) {
+  struct desc *previous_cmd;
+  struct desc *next_cmd;
+  GtkTreeIter saved_iter;
+  g_print("%s: thisNote=%d current_cmd=%p\n",__FUNCTION__,thisNote,current_cmd);
+
+  if (current_cmd == NULL) {
+    g_print("%s: current_cmd is NULL!\n", __FUNCTION__);
+    return;
+  }
+
+  saved_iter=iter;
+
+
+  // remove from MidiCommandsTable
+  if(MidiCommandsTable[thisNote]==current_cmd) {
+    g_print("%s: remove first\n",__FUNCTION__);
+    MidiCommandsTable[thisNote]=current_cmd->next;
+    g_free(current_cmd);
+  } else {
+    previous_cmd=MidiCommandsTable[thisNote];
+    while(previous_cmd->next!=NULL) {
+      next_cmd=previous_cmd->next;
+      if(next_cmd==current_cmd) {
+        g_print("%s: remove next\n",__FUNCTION__);
+       previous_cmd->next=next_cmd->next;
+       g_free(next_cmd);
+       break;
+      }
+      previous_cmd=next_cmd;
+    }
+  }
+
+  // remove from list store
+  gtk_list_store_remove(store,&saved_iter);
+
+  gtk_widget_set_sensitive(add_b,TRUE);
+  gtk_widget_set_sensitive(update_b,FALSE);
+  gtk_widget_set_sensitive(delete_b,FALSE);
+
+}
+
+void midi_menu(GtkWidget *parent) {
+  int i;
+  int col=0;
+  int row=0;
+  GtkCellRenderer *renderer;
+
+  dialog=gtk_dialog_new();
+  gtk_window_set_transient_for(GTK_WINDOW(dialog),GTK_WINDOW(parent_window));
+  char title[64];
+  sprintf(title,"piHPSDR - MIDI");
+  gtk_window_set_title(GTK_WINDOW(dialog),title);
+  g_signal_connect (dialog, "delete_event", G_CALLBACK (delete_event), NULL);
+
+  GdkRGBA color;
+  color.red = 1.0;
+  color.green = 1.0;
+  color.blue = 1.0;
+  color.alpha = 1.0;
+  gtk_widget_override_background_color(dialog,GTK_STATE_FLAG_NORMAL,&color);
+
+  GtkWidget *content=gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+  GtkWidget *grid=gtk_grid_new();
+  gtk_grid_set_column_spacing (GTK_GRID(grid),2);
+
+  row=0;
+  col=0;
+
+  get_midi_devices();
+  if(n_midi_devices>0) {
+    GtkWidget *devices_label=gtk_label_new("Select MIDI device: ");
+    gtk_grid_attach(GTK_GRID(grid),devices_label,col,row,3,1);
+    col+=3;
+
+    GtkWidget *devices=gtk_combo_box_text_new();
+    for(int i=0;i<n_midi_devices;i++) {
+      gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(devices),NULL,midi_devices[i].name);
+      if(midi_device_name!=NULL) {
+        if(strcmp(midi_device_name,midi_devices[i].name)==0) {
+          device_index=i;
+        }
+      }
+    }
+    gtk_grid_attach(GTK_GRID(grid),devices,col,row,6,1);
+    gtk_combo_box_set_active(GTK_COMBO_BOX(devices),device_index);
+    g_signal_connect(devices,"changed",G_CALLBACK(device_changed_cb),NULL);
+  } else {
+    GtkWidget *message=gtk_label_new("No MIDI devices found!");
+    gtk_grid_attach(GTK_GRID(grid),message,col,row,1,1);
+  }
+  row++;
+  col=0;
+
+  midi_enable_b=gtk_check_button_new_with_label("MIDI Enable");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (midi_enable_b), midi_enabled);
+  gtk_grid_attach(GTK_GRID(grid),midi_enable_b,col,row,3,1);
+  g_signal_connect(midi_enable_b,"toggled",G_CALLBACK(midi_enable_cb),NULL);
+
+  row++;
+  col=0;
+
+  GtkWidget *clear_b=gtk_button_new_with_label("Clear");
+  gtk_grid_attach(GTK_GRID(grid),clear_b,col,row,1,1);
+  g_signal_connect(clear_b,"clicked",G_CALLBACK(clear_cb),NULL);
+  col++;
+
+  GtkWidget *save_b=gtk_button_new_with_label("Save");
+  gtk_grid_attach(GTK_GRID(grid),save_b,col,row,1,1);
+  g_signal_connect(save_b,"clicked",G_CALLBACK(save_cb),NULL);
+  col++;
+
+  GtkWidget *load_b=gtk_button_new_with_label("Load");
+  gtk_grid_attach(GTK_GRID(grid),load_b,col,row,1,1);
+  g_signal_connect(load_b,"clicked",G_CALLBACK(load_cb),NULL);
+  col++;
+
+  GtkWidget *load_original_b=gtk_button_new_with_label("Load Original");
+  gtk_grid_attach(GTK_GRID(grid),load_original_b,col,row,1,1);
+  g_signal_connect(load_original_b,"clicked",G_CALLBACK(load_original_cb),NULL);
+  col++;
+
+  row++;
+  col=0;
+
+  configure_b=gtk_check_button_new_with_label("MIDI Configure");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (configure_b), FALSE);
+  gtk_grid_attach(GTK_GRID(grid),configure_b,col,row,3,1);
+  g_signal_connect(configure_b,"toggled",G_CALLBACK(configure_cb),NULL);
+
+
+  row++;
+  col=0;
+  GtkWidget *label=gtk_label_new("Evt");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Ch");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Note");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Type");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Value");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Min");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Max");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+  label=gtk_label_new("Action");
+  gtk_grid_attach(GTK_GRID(grid),label,col++,row,1,1);
+
+
+  row++;
+  col=0;
+  newEvent=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newEvent,col++,row,1,1);
+  newChannel=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newChannel,col++,row,1,1);
+  newNote=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newNote,col++,row,1,1);
+  newType=gtk_combo_box_text_new();
+  gtk_grid_attach(GTK_GRID(grid),newType,col++,row,1,1);
+  g_signal_connect(newType,"changed",G_CALLBACK(type_changed_cb),NULL);
+  newVal=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newVal,col++,row,1,1);
+  newMin=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newMin,col++,row,1,1);
+  newMax=gtk_label_new("");
+  gtk_grid_attach(GTK_GRID(grid),newMax,col++,row,1,1);
+  newAction=gtk_combo_box_text_new();
+  gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(newAction),5);
+  gtk_grid_attach(GTK_GRID(grid),newAction,col++,row,1,1);
+
+  add_b=gtk_button_new_with_label("Add");
+  g_signal_connect(add_b, "pressed", G_CALLBACK(add_cb),NULL);
+  gtk_grid_attach(GTK_GRID(grid),add_b,col++,row,1,1);
+  gtk_widget_set_sensitive(add_b,FALSE);
+
+
+  update_b=gtk_button_new_with_label("Update");
+  g_signal_connect(update_b, "pressed", G_CALLBACK(update_cb),NULL);
+  gtk_grid_attach(GTK_GRID(grid),update_b,col++,row,1,1);
+  gtk_widget_set_sensitive(update_b,FALSE);
+
+  delete_b=gtk_button_new_with_label("Delete");
+  g_signal_connect(delete_b, "pressed", G_CALLBACK(delete_cb),NULL);
+  gtk_grid_attach(GTK_GRID(grid),delete_b,col++,row,1,1);
+  gtk_widget_set_sensitive(delete_b,FALSE);
+  row++;
+  col=0;
+
+  scrolled_window=gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),GTK_POLICY_AUTOMATIC,GTK_POLICY_ALWAYS);
+  gtk_widget_set_size_request(scrolled_window,400,300);
+
+  view=gtk_tree_view_new();
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "Event", renderer, "text", EVENT_COLUMN, NULL);
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "CHANNEL", renderer, "text", CHANNEL_COLUMN, NULL);
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "NOTE", renderer, "text", NOTE_COLUMN, NULL);
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "TYPE", renderer, "text", TYPE_COLUMN, NULL);
+
+  renderer=gtk_cell_renderer_text_new();
+  gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, "ACTION", renderer, "text", ACTION_COLUMN, NULL);
+
+  store=gtk_list_store_new(N_COLUMNS,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING,G_TYPE_STRING);
+
+  load_store();
+
+  gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
+
+  gtk_container_add(GTK_CONTAINER(scrolled_window),view);
+
+  gtk_grid_attach(GTK_GRID(grid), scrolled_window, col, row, 6, 10);
+
+  model=gtk_tree_view_get_model(GTK_TREE_VIEW(view));
+  g_signal_connect(model,"row-inserted",G_CALLBACK(row_inserted_cb),NULL);
+
+  selection=gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
+  gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
+
+  selection_signal_id=g_signal_connect(G_OBJECT(selection),"changed",G_CALLBACK(tree_selection_changed_cb),NULL);
+
+  gtk_container_add(GTK_CONTAINER(content),grid);
+  sub_menu=dialog;
+  gtk_widget_show_all(dialog);
+}
+
+static int update(void *data) {
+  int state=GPOINTER_TO_INT(data);
+  gchar text[32];
+  gint i=1;
+  gint j;
+
+  switch(state) {
+    case UPDATE_NEW:
+      g_print("%s: UPDATE_NEW\n",__FUNCTION__);
+      switch(thisEvent) {
+        case MIDI_EVENT_NONE:
+          gtk_label_set_text(GTK_LABEL(newEvent),"NONE");
+          break;
+        case MIDI_EVENT_NOTE:
+          gtk_label_set_text(GTK_LABEL(newEvent),"NOTE");
+          break;
+        case MIDI_EVENT_CTRL:
+          gtk_label_set_text(GTK_LABEL(newEvent),"CTRL");
+          break;
+        case MIDI_EVENT_PITCH:
+          gtk_label_set_text(GTK_LABEL(newEvent),"PITCH");
+          break;
+      }
+      sprintf(text,"%d",thisChannel);
+      gtk_label_set_text(GTK_LABEL(newChannel),text);
+      sprintf(text,"%d",thisNote);
+      gtk_label_set_text(GTK_LABEL(newNote),text);
+      gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(newType));
+      gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"NONE");
+      switch(thisEvent) {
+        case MIDI_EVENT_NONE:
+          gtk_combo_box_set_active (GTK_COMBO_BOX(newType),0);
+          break;
+        case MIDI_EVENT_NOTE:
+        case MIDI_EVENT_PITCH:
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"KEY");
+          gtk_combo_box_set_active (GTK_COMBO_BOX(newType),1);
+          break;
+        case MIDI_EVENT_CTRL:
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"KNOB/SLIDER");
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"WHEEL");
+          gtk_combo_box_set_active (GTK_COMBO_BOX(newType),0);
+          break;
+      }
+      gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(newAction));
+      gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,"NONE");
+      gtk_combo_box_set_active (GTK_COMBO_BOX(newAction),0);
+      if(thisEvent==MIDI_EVENT_PITCH || thisEvent==MIDI_EVENT_NOTE) {
+       i=1;
+       j=0;
+       while(ActionTable[i].action!=MIDI_ACTION_NONE) {
+          if(ActionTable[i].type&MIDI_TYPE_KEY) {
+            gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newAction),NULL,ActionTable[i].str);
+            if(ActionTable[i].action==thisAction) {
+              gtk_combo_box_set_active(GTK_COMBO_BOX(newAction),j);
+            }
+            j++;
+          }
+          i++;
+        }
+
+      }
+      sprintf(text,"%d",thisVal);
+      gtk_label_set_text(GTK_LABEL(newVal),text);
+      sprintf(text,"%d",thisMin);
+      gtk_label_set_text(GTK_LABEL(newMin),text);
+      sprintf(text,"%d",thisMax);
+      gtk_label_set_text(GTK_LABEL(newMax),text);
+
+      gtk_widget_set_sensitive(add_b,TRUE);
+      gtk_widget_set_sensitive(update_b,FALSE);
+      gtk_widget_set_sensitive(delete_b,FALSE);
+      break;
+
+    case UPDATE_CURRENT:
+      g_print("%s: UPDATE_CURRENT\n",__FUNCTION__);
+      sprintf(text,"%d",thisVal);
+      gtk_label_set_text(GTK_LABEL(newVal),text);
+      sprintf(text,"%d",thisMin);
+      gtk_label_set_text(GTK_LABEL(newMin),text);
+      sprintf(text,"%d",thisMax);
+      gtk_label_set_text(GTK_LABEL(newMax),text);
+      break;
+
+    case UPDATE_EXISTING:
+      g_print("%s: UPDATE_EXISTING\n",__FUNCTION__);
+      switch(thisEvent) {
+        case MIDI_EVENT_NONE:
+          gtk_label_set_text(GTK_LABEL(newEvent),"NONE");
+          break;
+        case MIDI_EVENT_NOTE:
+          gtk_label_set_text(GTK_LABEL(newEvent),"NOTE");
+          break;
+        case MIDI_EVENT_CTRL:
+          gtk_label_set_text(GTK_LABEL(newEvent),"CTRL");
+          break;
+        case MIDI_EVENT_PITCH:
+          gtk_label_set_text(GTK_LABEL(newEvent),"PITCH");
+          break;
+      }
+      sprintf(text,"%d",thisChannel);
+      gtk_label_set_text(GTK_LABEL(newChannel),text);
+      sprintf(text,"%d",thisNote);
+      gtk_label_set_text(GTK_LABEL(newNote),text);
+      gtk_combo_box_text_remove_all(GTK_COMBO_BOX_TEXT(newType));
+      gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"NONE");
+      switch(thisEvent) {
+        case MIDI_EVENT_NONE:
+         gtk_combo_box_set_active (GTK_COMBO_BOX(newType),0);
+          break;
+        case MIDI_EVENT_NOTE:
+        case MIDI_EVENT_PITCH:
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"KEY");
+         if(thisType==MIDI_TYPE_NONE) {
+           gtk_combo_box_set_active (GTK_COMBO_BOX(newType),0);
+         } else if(thisType==MIDI_TYPE_KEY) {
+           gtk_combo_box_set_active (GTK_COMBO_BOX(newType),1);
+         }
+          break;
+        case MIDI_EVENT_CTRL:
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"KNOB/SLIDER");
+          gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(newType),NULL,"WHEEL");
+         if(thisType==MIDI_TYPE_NONE) {
+           gtk_combo_box_set_active (GTK_COMBO_BOX(newType),0);
+         } else if(thisType==MIDI_TYPE_KNOB) {
+           gtk_combo_box_set_active (GTK_COMBO_BOX(newType),1);
+         } else if(thisType==MIDI_TYPE_WHEEL) {
+            gtk_combo_box_set_active (GTK_COMBO_BOX(newType),2);
+          }
+          break;
+      }
+      sprintf(text,"%d",thisVal);
+      gtk_label_set_text(GTK_LABEL(newVal),text);
+      sprintf(text,"%d",thisMin);
+      gtk_label_set_text(GTK_LABEL(newMin),text);
+      sprintf(text,"%d",thisMax);
+      gtk_label_set_text(GTK_LABEL(newMax),text);
+  
+      find_current_cmd();
+      g_print("%s: current_cmd %p\n",__FUNCTION__,current_cmd);
+
+      gtk_widget_set_sensitive(add_b,FALSE);
+      gtk_widget_set_sensitive(update_b,TRUE);
+      gtk_widget_set_sensitive(delete_b,TRUE);
+      break;
+
+  }
+
+  return 0;
+}
+
+void NewMidiConfigureEvent(enum MIDIevent event, int channel, int note, int val) {
+
+  gboolean valid;
+  char *str_event;
+  char *str_channel;
+  char *str_note;
+  char *str_type;
+  char *str_action;
+
+  gint tree_event;
+  gint tree_channel;
+  gint tree_note;
+
+  //g_print("%s: event=%d channel=%d note=%d val=%d\n", __FUNCTION__,event,channel,note,val);
+
+  if(event==thisEvent && channel==thisChannel && note==thisNote) {
+    //g_print("%s: current event\n",__FUNCTION__);
+    thisVal=val;
+    if(val<thisMin) thisMin=val;
+    if(val>thisMax) thisMax=val;
+    g_idle_add(update,GINT_TO_POINTER(UPDATE_CURRENT));
+  } else {
+    //g_print("%s: new or existing event\n",__FUNCTION__);
+    thisEvent=event;
+    thisChannel=channel;
+    thisNote=note;
+    thisVal=val;
+    thisMin=val;
+    thisMax=val;
+    thisType=MIDI_TYPE_NONE;
+    thisAction=MIDI_ACTION_NONE;
+
+    // search tree to see if it is existing event
+    valid=gtk_tree_model_get_iter_first(model,&iter);
+    while(valid) {
+      gtk_tree_model_get(model, &iter, EVENT_COLUMN, &str_event, -1);
+      gtk_tree_model_get(model, &iter, CHANNEL_COLUMN, &str_channel, -1);
+      gtk_tree_model_get(model, &iter, NOTE_COLUMN, &str_note, -1);
+      gtk_tree_model_get(model, &iter, TYPE_COLUMN, &str_type, -1);
+      gtk_tree_model_get(model, &iter, ACTION_COLUMN, &str_action, -1);
+
+      //g_print("%s: %s %s %s %s %s\n",__FUNCTION__,str_event,str_channel,str_note,str_type,str_action);
+
+      if(str_event!=NULL && str_channel!=NULL && str_note!=NULL && str_type!=NULL && str_action!=NULL) {
+        if(strcmp(str_event,"CTRL")==0) {
+          tree_event=MIDI_EVENT_CTRL;
+        } else if(strcmp(str_event,"PITCH")==0) {
+          tree_event=MIDI_EVENT_PITCH;
+        } else if(strcmp(str_event,"NOTE")==0) {
+          tree_event=MIDI_EVENT_NOTE;
+        } else {
+          tree_event=MIDI_EVENT_NONE;
+        }
+        tree_channel=atoi(str_channel);
+        tree_note=atoi(str_note);
+
+       if(thisEvent==tree_event && (thisChannel==tree_channel || tree_channel==-1) && thisNote==tree_note) {
+          thisVal=0;
+          thisMin=0;
+          thisMax=0;
+          if(strcmp(str_type,"KEY")==0) {
+            thisType=MIDI_TYPE_KEY;
+          } else if(strcmp(str_type,"KNOB/SLIDER")==0) {
+            thisType=MIDI_TYPE_KNOB;
+          } else if(strcmp(str_type,"WHEEL")==0) {
+            thisType=MIDI_TYPE_WHEEL;
+          } else {
+            thisType=MIDI_TYPE_NONE;
+          }
+          thisAction=MIDI_ACTION_NONE;
+          int i=1;
+          while(ActionTable[i].action!=MIDI_ACTION_NONE) {
+            if(strcmp(ActionTable[i].str,str_action)==0) {
+              thisAction=ActionTable[i].action;
+              break;
+            }
+            i++;
+          }
+         gtk_tree_view_set_cursor(GTK_TREE_VIEW(view),gtk_tree_model_get_path(model,&iter),NULL,FALSE);
+          g_idle_add(update,GINT_TO_POINTER(UPDATE_EXISTING));
+          return;
+       }
+      }
+
+      valid=gtk_tree_model_iter_next(model,&iter);
+    }
+    
+    g_idle_add(update,GINT_TO_POINTER(UPDATE_NEW));
+  }
+}
+
+void midi_save_state() {
+  char name[80];
+  char value[80];
+  struct desc *cmd;
+  gint index;
+
+  if(device_index!=-1) {
+    setProperty("midi_device",midi_devices[device_index].name);
+    // the value i=128 is for the PitchBend
+    for(int i=0;i<129;i++) {
+      index=0;
+      cmd=MidiCommandsTable[i];
+      while(cmd!=NULL) {
+        g_print("%s:  channel=%d key=%d event=%s onoff=%d type=%s action=%s\n",__FUNCTION__,cmd->channel,i,midi_events[cmd->event],cmd->onoff,midi_types[cmd->type],ActionTable[cmd->action].str);
+
+        //
+        // There might be events that share the channel and the note value (one NOTE and one CTRL, for example)
+        // These must not share the same key in the property database so the "running index" must be part of the key
+        //
+
+        sprintf(name,"midi[%d].channel[%d].index[%d].event",i,cmd->channel,index);
+        setProperty(name,midi_events[cmd->event]);
+
+        sprintf(name,"midi[%d].channel[%d].index[%d].type",i,cmd->channel,index);
+        setProperty(name,midi_types[cmd->type]);
+
+        sprintf(name,"midi[%d].channel[%d].index[%d].action",i,cmd->channel,index);
+        setProperty(name,(char *) ActionTable[cmd->action].str);
+
+        cmd=cmd->next;
+       index++;
+      }
+
+      if(index!=0) {
+        sprintf(name,"midi[%d].indices",i);
+        sprintf(value,"%d",index);
+        setProperty(name,value);
+      }
+
+    }
+  }
+}
+
+void midi_restore_state() {
+  char name[80];
+  char *value;
+  gint indices;
+  gint channel;
+  gint event;
+  gint onoff;
+  gint type;
+  gint action;
+
+  struct desc *cmd, *loop;
+
+  get_midi_devices();
+  MidiReleaseCommands();
+
+  //g_print("%s\n",__FUNCTION__);
+  value=getProperty("midi_device");
+  if(value) {
+    //g_print("%s: device=%s\n",__FUNCTION__,value);
+    midi_device_name=g_new(gchar,strlen(value)+1);
+    strcpy(midi_device_name,value);
+
+    
+    for(int i=0;i<n_midi_devices;i++) {
+      if(strcmp(midi_devices[i].name,value)==0) {
+        device_index=i;
+        g_print("%s: found device at %d\n",__FUNCTION__,i);
+        break;
+      }
+    }
+  }
+
+  // the value i=128 is for the PitchBend
+  for(int i=0;i<129;i++) {
+    sprintf(name,"midi[%d].indices",i);
+    value=getProperty(name);
+    if(value) {
+      indices=atoi(value);
+      for(int index=0; index<indices; index++) {
+        sprintf(name,"midi[%d].channel[%d].index[%d].event",i,channel,index);
+        value=getProperty(name);
+       event=MIDI_EVENT_NONE;
+        if(value) {
+          for(int j=0;j<4;j++) {
+           if(strcmp(value,midi_events[j])==0) {
+             event=j;
+             break;
+            }
+          }
+       }
+        sprintf(name,"midi[%d].channel[%d].index[%d],type",i,channel,index);
+        value=getProperty(name);
+       type=MIDI_TYPE_NONE;
+        if(value) {
+          for(int j=0;j<5;j++) {
+            if(strcmp(value,midi_types[j])==0) {
+              type=j;
+              break;
+            }
+          }
+       }
+        sprintf(name,"midi[%d].channel[%d].index[%d].action",i,channel,index);
+        value=getProperty(name);
+       action=MIDI_ACTION_NONE;
+        if(value) {
+         int j=1;
+         while(ActionTable[j].type!=MIDI_ACTION_NONE) {
+            if(strcmp(value,ActionTable[j].str)==0) {
+              action=ActionTable[j].action;
+             break;
+            }
+           j++;
+         }
+       }
+
+       struct desc *desc;
+        desc = (struct desc *) malloc(sizeof(struct desc));
+        desc->next = NULL;
+        desc->action = action; // MIDIaction
+        desc->type = type; // MIDItype
+        desc->event = event; // MIDevent
+        desc->onoff = ActionTable[action].onoff;
+        desc->delay = 0;
+        desc->vfl1  = -1;
+        desc->vfl2  = -1;
+        desc->fl1   = -1;
+        desc->fl2   = -1;
+        desc->lft1  = -1;
+        desc->lft2  = 63;
+        desc->rgt1  = 64;
+        desc->rgt2  = 128;
+        desc->fr1   = 128;
+        desc->fr2   = 128;
+        desc->vfr1  = 128;
+        desc->vfr2  = 128;
+        desc->channel  = channel;
+
+        g_print("DESK INIT Note=%d Action=%d Type=%d Event=%d OnOff=%d Chan=%d\n", i, action, type, event, onoff, channel);
+
+        MidiAddCommand(i, desc);
+      } // index
+    }   // if (value)
+  }     // for (int i)
+}
+
diff --git a/midi_menu.h b/midi_menu.h
new file mode 100644 (file)
index 0000000..bed7e0a
--- /dev/null
@@ -0,0 +1,25 @@
+/* Copyright (C)
+* 2021 - John Melton, G0ORX/N6LYT
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+*
+*/
+
+extern gchar *midi_device_name;
+extern void midi_menu(GtkWidget *parent);
+extern void NewMidiConfigureEvent(enum MIDIevent event, int channel, int note, int val);
+extern void midi_save_state();
+extern void midi_restore_state();
+
index 9c7bf7ca3600190761d9308ed9c14ac67943e2e6..cf9b4a01743f1eb4ff00194b4afc40032c9db801 100644 (file)
@@ -63,6 +63,9 @@
 #ifdef CLIENT_SERVER
 #include "server_menu.h"
 #endif
+#ifdef MIDI
+#include "midi_menu.h"
+#endif
 
 
 static GtkWidget *menu_b=NULL;
@@ -466,6 +469,18 @@ static gboolean server_cb (GtkWidget *widget, GdkEventButton *event, gpointer da
 }
 #endif
 
+#ifdef MIDI
+void start_midi() {
+  cleanup();
+  midi_menu(top_window);
+}
+
+static gboolean midi_cb (GtkWidget *widget, GdkEventButton *event, gpointer data) {
+  start_midi();
+  return TRUE;
+}
+#endif
+
 void new_menu()
 {
   int i;
@@ -625,6 +640,13 @@ void new_menu()
     }
 #endif
 
+#ifdef MIDI
+    GtkWidget *midi_b=gtk_button_new_with_label("MIDI");
+    g_signal_connect (midi_b, "button-press-event", G_CALLBACK(midi_cb), NULL);
+    gtk_grid_attach(GTK_GRID(grid),midi_b,(i%5),i/5,1,1);
+    i++;
+#endif
+
 //
 //  We need at least two receivers and two ADCs to do DIVERSITY
 //
diff --git a/radio.c b/radio.c
index 2e7c64984cdb72a67e883fae218eae026e9c094c..e086865ec3b8510c4ad0756ff2caf28a2a65e42f 100644 (file)
--- a/radio.c
+++ b/radio.c
 #endif
 #include "rigctl_menu.h"
 #ifdef MIDI
-// rather than including MIDI.h with all its internal stuff
-// (e.g. enum components) we just declare the single bit thereof
-// we need here to make a strict compiler happy.
-void MIDIstartup();
+#include "alsa_midi.h"
+#include "midi_menu.h"
 #endif
 #ifdef CLIENT_SERVER
 #include "client_server.h"
@@ -97,6 +95,10 @@ void MIDIstartup();
 #define TOOLBAR_HEIGHT (30)
 #define WATERFALL_HEIGHT (105)
 
+#ifdef MIDI
+gboolean midi_enabled = false;
+#endif
+
 GtkWidget *fixed;
 static GtkWidget *vfo_panel;
 static GtkWidget *meter;
@@ -1302,7 +1304,14 @@ void start_radio() {
   // running. So this is the last thing we do when starting the radio.
   //
 #ifdef MIDI
-  MIDIstartup();
+  g_print("%s: midi_enabled=%d midi_device_name=%s\n",__FUNCTION__,midi_enabled,midi_device_name);
+  if(midi_enabled && (midi_device_name!=NULL)) {
+    if(register_midi_device(midi_device_name)<0) {
+      midi_enabled=FALSE;
+    }
+  } else {
+    midi_enabled=FALSE;
+  }
 #endif
 
 #ifdef CLIENT_SERVER
@@ -2131,6 +2140,12 @@ g_print("radioRestoreState: %s\n",property_path);
     }
 #endif
 
+#ifdef MIDI
+    midi_restore_state();
+    value=getProperty("radio.midi_enabled");
+    if(value) midi_enabled=atoi(value);
+#endif
+
     value=getProperty("radio.display_sequence_errors");
     if(value!=NULL) display_sequence_errors=atoi(value);
 
@@ -2457,6 +2472,12 @@ g_print("radioSaveState: %s\n",property_path);
   }
 #endif
 
+#ifdef MIDI
+  sprintf(value,"%d",midi_enabled);
+  setProperty("radio.midi_enabled",value);
+  midi_save_state();
+#endif
+
   saveProperties(property_path);
   g_mutex_unlock(&property_mutex);
 }
@@ -2564,9 +2585,6 @@ int remote_start(void *data) {
   reconfigure_radio();
   g_idle_add(ext_vfo_update,(gpointer)NULL);
   gdk_window_set_cursor(gtk_widget_get_window(top_window),gdk_cursor_new(GDK_ARROW));
-#ifdef MIDI
-  MIDIstartup();
-#endif
   for(int i=0;i<receivers;i++) {
     gint timer_id=gdk_threads_add_timeout_full(G_PRIORITY_DEFAULT_IDLE,100, start_spectrum, receiver[i], NULL);
   }
diff --git a/radio.h b/radio.h
index 564c9064892ae3601c7f63390ba8353729da0432..da9580f7116d4ea686aab7d314aba2151a8fb30a 100644 (file)
--- a/radio.h
+++ b/radio.h
@@ -105,6 +105,9 @@ extern RECEIVER *active_receiver;
 
 extern TRANSMITTER *transmitter;
 
+#ifdef MIDI
+extern gboolean midi_enabled;
+#endif
 
 #define PA_DISABLED 0
 #define PA_ENABLED 1