From 27ab301e99d3336250ca92f431a33026e5ff8ffe Mon Sep 17 00:00:00 2001 From: c vw Date: Tue, 23 Apr 2019 19:26:29 +0200 Subject: [PATCH] MIDI support, and some corrections --- Makefile.mac | 29 ++-- ext.c | 63 +++++++-- ext.h | 12 ++ gpio.c | 2 +- hpsdrsim.c | 360 +++++++++++++++++++++++++++--------------------- mac_midi.c | 142 +++++++++++++++++++ midi.h | 192 ++++++++++++++++++++++++++ midi.inp | 33 +++++ midi2.c | 257 ++++++++++++++++++++++++++++++++++ midi3.c | 233 +++++++++++++++++++++++++++++++ old_discovery.c | 4 + ps_menu.c | 8 +- radio.c | 8 ++ rigctl.c | 5 +- sliders.c | 18 ++- sliders.h | 2 +- 16 files changed, 1177 insertions(+), 191 deletions(-) create mode 100644 mac_midi.c create mode 100644 midi.h create mode 100644 midi.inp create mode 100644 midi2.c create mode 100644 midi3.c diff --git a/Makefile.mac b/Makefile.mac index cc5a7c8..45aa79f 100644 --- a/Makefile.mac +++ b/Makefile.mac @@ -36,6 +36,9 @@ STEMLAB_FIX_OPTION=-DSTEMLAB_FIX # uncomment the line below to include support for Pi SDR #PI_SDR_INCLUDE=PI_SDR +# uncomment the line below to include MIDI support +MIDI_INCLUDE=MIDI + #uncomment the line below for the platform being compiled on (actually not used) UNAME_N=raspberrypi #UNAME_N=odroid @@ -49,6 +52,14 @@ UNAME_N=raspberrypi CC=gcc LINK=gcc +ifeq ($(MIDI_INCLUDE),MIDI) +MIDI_OPTIONS=-D MIDI +MIDI_SOURCES= mac_midi.c midi2.c midi3.c +MIDI_HEADERS= midi.h +MIDI_OBJS= mac_midi.o midi2.o midi3.o +MIDI_LIBS= -framework CoreMIDI -framework Foundation +endif + ifeq ($(PURESIGNAL_INCLUDE),PURESIGNAL) PURESIGNAL_OPTIONS=-D PURESIGNAL PURESIGNAL_SOURCES= \ @@ -187,11 +198,11 @@ GTKLIBS=`pkg-config --libs gtk+-3.0` PORTAUDIO_OPTIONS=-DPORTAUDIO AUDIO_LIBS=-lportaudio -OPTIONS=-g -Wno-deprecated-declarations $(PURESIGNAL_OPTIONS) $(REMOTE_OPTIONS) $(USBOZY_OPTIONS) $(I2C_OPTIONS) $(GPIO_OPTIONS) $(LIMESDR_OPTIONS) \ +OPTIONS=-g -Wno-deprecated-declarations $(MIDI_OPTIONS) $(PURESIGNAL_OPTIONS) $(REMOTE_OPTIONS) $(USBOZY_OPTIONS) $(I2C_OPTIONS) $(GPIO_OPTIONS) $(LIMESDR_OPTIONS) \ $(FREEDV_OPTIONS) $(LOCALCW_OPTIONS) $(RADIOBERRY_OPTIONS) $(PI_SDR_OPTIONS) $(PSK_OPTIONS) $(STEMLAB_OPTIONS) $(STEMLAB_FIX_OPTION) \ -D GIT_DATE='"$(GIT_DATE)"' -D GIT_VERSION='"$(GIT_VERSION)"' $(PORTAUDIO_OPTIONS) $(DEBUG_OPTION) -O3 -LIBS=-lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(PSKLIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(FREEDVLIBS) $(STEMLAB_LIBS) +LIBS=-lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(PSKLIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(FREEDVLIBS) $(STEMLAB_LIBS) $(MIDI_LIBS) INCLUDES=$(GTKINCLUDES) COMPILE=$(CC) $(OPTIONS) $(INCLUDES) @@ -413,10 +424,10 @@ error_handler.o \ cwramp.o \ portaudio.o -$(PROGRAM): $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(PSK_OBJS) $(PURESIGNAL_OBJS) $(STEMLAB_OBJS) - $(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(PURESIGNAL_OBJS) $(STEMLAB_OBJS) $(LIBS) +$(PROGRAM): $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(PSK_OBJS) $(PURESIGNAL_OBJS) $(MIDI_OBJS) $(STEMLAB_OBJS) + $(LINK) -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(PURESIGNAL_OBJS) $(MIDI_OBJS) $(STEMLAB_OBJS) $(LIBS) -all: prebuild $(PROGRAM) $(HEADERS) $(REMOTE_HEADERS) $(USBOZY_HEADERS) $(LIMESDR_HEADERS) $(FREEDV_HEADERS) $(LOCALCW_HEADERS) $(I2C_HEADERS) $(GPIO_HEADERS) $(PSK_HEADERS) $(PURESIGNAL_HEADERS) $(STEMLAB_HEADERS) $(SOURCES) $(REMOTE_SOURCES) $(USBOZY_SOURCES) $(LIMESDR_SOURCES) $(FREEDV_SOURCES) $(I2C_SOURCES) $(GPIO_SOURCES) $(PSK_SOURCES) $(PURESIGNAL_SOURCES) $(STEMLAB_SOURCES) +all: prebuild $(PROGRAM) $(HEADERS) $(REMOTE_HEADERS) $(USBOZY_HEADERS) $(LIMESDR_HEADERS) $(FREEDV_HEADERS) $(LOCALCW_HEADERS) $(I2C_HEADERS) $(GPIO_HEADERS) $(PSK_HEADERS) $(PURESIGNAL_HEADERS) $(MIDI_HEADERS) $(STEMLAB_HEADERS) $(SOURCES) $(REMOTE_SOURCES) $(USBOZY_SOURCES) $(LIMESDR_SOURCES) $(FREEDV_SOURCES) $(I2C_SOURCES) $(GPIO_SOURCES) $(PSK_SOURCES) $(PURESIGNAL_SOURCES) $(MIDI_SOURCES) $(STEMLAB_SOURCES) prebuild: rm -f version.o @@ -474,11 +485,11 @@ hpsdrsim: hpsdrsim.o ############################################################################# app: $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) \ $(LOCALCW_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(PSK_OBJS) \ - $(PURESIGNAL_OBJS) $(STEMLAB_OBJS) + $(PURESIGNAL_OBJS) $(MIDI_OBJS) $(STEMLAB_OBJS) $(LINK) -headerpad_max_install_names -o $(PROGRAM) $(OBJS) $(REMOTE_OBJS) \ $(USBOZY_OBJS) $(I2C_OBJS) $(GPIO_OBJS) $(LIMESDR_OBJS) \ $(FREEDV_OBJS) $(LOCALCW_OBJS) $(PSK_OBJS) $(PURESIGNAL_OBJS) \ - $(STEMLAB_OBJS) $(LIBS) + $(MIDI_OBJS) $(STEMLAB_OBJS) $(LIBS) @rm -rf pihpsdr.app @mkdir -p pihpsdr.app/Contents/MacOS @mkdir -p pihpsdr.app/Contents/Frameworks @@ -497,11 +508,13 @@ app: $(OBJS) $(REMOTE_OBJS) $(USBOZY_OBJS) $(LIMESDR_OBJS) $(FREEDV_OBJS) \ install_name_tool -change "$$lib" "@executable_path/../Frameworks/$$libfn" pihpsdr.app/Contents/MacOS/pihpsdr-bin; \ done # -# Make "app" and copy wdspWisdom and props file to app bundle +# Make "app" and copy local files app bundle # localapp: app cp wdspWisdom pihpsdr.app/Contents/Resources cp *.props pihpsdr.app/Contents/Resources + cp midi.inp pihpsdr.app/Contents/Resources + cp ip.addr pihpsdr.app/Contents/Resources ############################################################################# diff --git a/ext.c b/ext.c index 6550d12..0838bcb 100644 --- a/ext.c +++ b/ext.c @@ -60,7 +60,7 @@ int ext_vfo_update(void *data) { } int ext_vfo_filter_changed(void *data) { - vfo_filter_changed((uintptr_t)data); + vfo_filter_changed((int)(uintptr_t)data); return 0; } @@ -75,39 +75,36 @@ int ext_band_update(void *data) { int ext_mode_update(void *data) { start_mode(); - // DL1YCF added return statement return 0; } int ext_filter_update(void *data) { start_filter(); - // DL1YCF added return statement return 0; } int ext_noise_update(void *data) { start_noise(); - // DL1YCF added return statement return 0; } int ext_ptt_update(void *data) { - ptt_update((uintptr_t)data); + ptt_update((int)(uintptr_t)data); return 0; } int ext_mox_update(void *data) { - mox_update((uintptr_t)data); + mox_update((int)(uintptr_t)data); return 0; } int ext_tune_update(void *data) { - tune_update((uintptr_t)data); + tune_update((int)(uintptr_t)data); return 0; } int ext_vox_changed(void *data) { - vox_changed((uintptr_t)data); + vox_changed((int)(uintptr_t)data); return 0; } @@ -128,17 +125,17 @@ int ext_calc_drive_level(void *data) { } int ext_vfo_band_changed(void *data) { - vfo_band_changed((uintptr_t)data); + vfo_band_changed((int)(uintptr_t)data); return 0; } int ext_radio_change_sample_rate(void *data) { - radio_change_sample_rate((uintptr_t)data); + radio_change_sample_rate((int)(uintptr_t)data); return 0; } int ext_update_squelch(void *data) { - set_squelch(active_receiver); + set_squelch(); return 0; } @@ -160,3 +157,47 @@ int ext_vfo_step(void *data) { vfo_step(step); return 0; } + +int ext_set_mic_gain(void * data) { + double d=*(double *)data; + set_mic_gain(d); + free(data); + return 0; +} + +int ext_set_agc_gain(void *data) { + double d=*(double *)data; + set_agc_gain(d); + free(data); + return 0; +} + +int ext_set_drive(void *data) { + double d=*(double *)data; + set_drive(d); + free(data); + return 0; +} + +int ext_vfo_a_swap_b(void *data) { + vfo_a_swap_b(); + return 0; +} + +int ext_update_att_preamp(void *data) { + update_att_preamp(); + return 0; +} + +int ext_set_alex_attenuation(void *data) { + int val=(int)(uintptr_t)data; + set_alex_attenuation(val); + return 0; +} + +int ext_set_attenuation_value(void *data) { + double d=*(double *)data; + set_attenuation_value(d); + free(data); + return 0; +} diff --git a/ext.h b/ext.h index 1b850db..070a215 100644 --- a/ext.h +++ b/ext.h @@ -18,7 +18,10 @@ */ +// // The following calls functions can be called usig g_idle_add +// Use these calls from within the rigclt daemon, or the GPIO or MIDI stuff +// extern int ext_discovery(void *data); extern int ext_vfo_update(void *data); @@ -52,5 +55,14 @@ extern int ext_noise_update(void *data); extern int ext_tx_set_ps(void *data); extern int ext_ps_twotone(void *data); #endif + int ext_vfo_step(void *data); int ext_vfo_mode_changed(void *data); +int ext_set_af_gain(void *data); +int ext_set_mic_gain(void *data); +int ext_set_agc_gain(void *data); +int ext_set_drive(void *data); +int ext_vfo_a_swap_b(void *data); +int ext_update_att_preamp(void *data); +int ext_set_alex_attenuation(void *data); +int ext_set_attenuation_value(void *data); diff --git a/gpio.c b/gpio.c index b00a0c4..a34510d 100644 --- a/gpio.c +++ b/gpio.c @@ -1407,7 +1407,7 @@ static void encoder_changed(int action,int pos) { value=100.0; } active_receiver->squelch=value; - set_squelch(active_receiver); + set_squelch(); break; case ENCODER_COMP: value=(double)transmitter->compressor_level; diff --git a/hpsdrsim.c b/hpsdrsim.c index 7f49b1d..7860577 100755 --- a/hpsdrsim.c +++ b/hpsdrsim.c @@ -19,17 +19,20 @@ * RF3 respects the "TX drive" and "TX ATT" settings * RF4 is the TX signal multiplied with 0.4 (ignores TX_DRIVE and TX_ATT). * - * We support 4 receivers, they see - * RX1: RF1 - * RX2: RF2 while receiving, RF3 while transmitting/48000 - * RX3: RF2 while receiving, RF3 while transmitting/48000 - * RX4: RF3 + * Depending on the device type, the receivers see different signals + * (This is necessary for PURESIGNAL). We chose the association such that + * it works both with and without PURESIGNAL. Upon receiving, the association + * is as follows: + * RX1=RF1, RX2=RF2, RX3=RF2, * - * RX5 to RX8: no signal - * - * This is the setting for PURESIGNAL with HERMES boards. - * To simulate Orion2 boards (Anan7000 etc), we should connect RX4 with RF2 and RX5 with RF3. + * The connection upon transmitting depends on the DEVICE, namely + * + * DEVICE=METIS: RX1=RF3, RX2=RF4 + * DEVICE=HERMES: RX3=RF3, RX4=RF4 (also for DEVICE=StemLab) + * DEVICE=ORION2: RX4=RF3, RX5=RF5 (also for DEVICE=ANGELIA and ORION) * + * Note that currently, RF3 and RF4 only exist when using 48000 Hz sample rate. + * */ #include #include @@ -66,75 +69,75 @@ static int sock_TCP_Client = -1; * Whenevery they are changed, this is reported. */ -int AlexTXrel = -1; -int alexRXout = -1; -int alexRXant = -1; -int MicTS = -1; -int duplex = -1; -int receivers = -1; -int rate = -1; -int preamp = -1; -int LTdither = -1; -int LTrandom = -1; -int AlexRXant = -1; -int AlexRXout = -1; -int ref10 = -1; -int src122 = -1; -int PMconfig = -1; -int MicSrc = -1; -int txdrive = 0; -int txatt = 0; -int sidetone_volume = -1; -int cw_internal = -1; -int rx_att[2] = {-1,-1}; -int rx1_attE = -1; -int rx_preamp[4] = {-1,-1,-1,-1}; -int MerTxATT0 = -1; -int MerTxATT1 = -1; -int MetisDB9 = -1; -int PeneSel = -1; -int PureSignal = -1; -int LineGain = -1; -int MicPTT = -1; -int tip_ring = -1; -int MicBias = -1; -int ptt=-1; -int att=-1; -int TX_class_E = -1; -int OpenCollectorOutputs=-1; -long tx_freq=-1; -long rx_freq[7] = {-1,-1,-1,-1,-1,-1,-1}; -int hermes_config=-1; -int alex_lpf=-1; -int alex_hpf=-1; -int c25_ext_board_i2c_data=-1; -int rx_adc[7]={0,1,1,2,-1,-1,-1}; -int cw_hang = -1; -int cw_reversed = -1; -int cw_speed = -1; -int cw_mode = -1; -int cw_weight = -1; -int cw_spacing = -1; -int cw_delay = -1; -int CommonMercuryFreq = -1; -int freq=-1; +static int AlexTXrel = -1; +static int alexRXout = -1; +static int alexRXant = -1; +static int MicTS = -1; +static int duplex = -1; +static int receivers = -1; +static int rate = -1; +static int preamp = -1; +static int LTdither = -1; +static int LTrandom = -1; +static int AlexRXant = -1; +static int AlexRXout = -1; +static int ref10 = -1; +static int src122 = -1; +static int PMconfig = -1; +static int MicSrc = -1; +static int txdrive = 0; +static int txatt = 0; +static int sidetone_volume = -1; +static int cw_internal = -1; +static int rx_att[2] = {-1,-1}; +static int rx1_attE = -1; +static int rx_preamp[4] = {-1,-1,-1,-1}; +static int MerTxATT0 = -1; +static int MerTxATT1 = -1; +static int MetisDB9 = -1; +static int PeneSel = -1; +static int PureSignal = -1; +static int LineGain = -1; +static int MicPTT = -1; +static int tip_ring = -1; +static int MicBias = -1; +static int ptt=-1; +static int att=-1; +static int TX_class_E = -1; +static int OpenCollectorOutputs=-1; +static long tx_freq=-1; +static long rx_freq[7] = {-1,-1,-1,-1,-1,-1,-1}; +static int hermes_config=-1; +static int alex_lpf=-1; +static int alex_hpf=-1; +static int c25_ext_board_i2c_data=-1; +static int rx_adc[7]={0,1,1,2,-1,-1,-1}; +static int cw_hang = -1; +static int cw_reversed = -1; +static int cw_speed = -1; +static int cw_mode = -1; +static int cw_weight = -1; +static int cw_spacing = -1; +static int cw_delay = -1; +static int CommonMercuryFreq = -1; +static int freq=-1; // floating-point represeners of TX att, RX att, and RX preamp settings -double txdrv_dbl = 1.0; -double txatt_dbl = 1.0; -double rxatt_dbl[4] = {1.0, 1.0, 1.0, 1.0}; // this reflects both ATT and PREAMP +static double txdrv_dbl = 1.0; +static double txatt_dbl = 1.0; +static double rxatt_dbl[4] = {1.0, 1.0, 1.0, 1.0}; // this reflects both ATT and PREAMP -int sock_ep2; +static int sock_ep2; -struct sockaddr_in addr_ep6; +static struct sockaddr_in addr_ep6; /* * These two variables monitor whether the TX thread is active */ -int enable_thread = 0; -int active_thread = 0; +static int enable_thread = 0; +static int active_thread = 0; void process_ep2(uint8_t *frame); void *handler_ep6(void *arg); @@ -146,10 +149,12 @@ void *handler_ep6(void *arg); // 63 * 130, RTXLEN must be an even multiple of 63! #define RTXLEN 8190 -double isample[RTXLEN]; -double qsample[RTXLEN]; -int txptr=0; -int rxptr=0; +static double isample[RTXLEN]; +static double qsample[RTXLEN]; +static int txptr=0; +static int rxptr=0; + +static int ismetis,isorion,ishermes,isc25; int main(int argc, char *argv[]) { @@ -157,6 +162,7 @@ int main(int argc, char *argv[]) struct sched_param param; pthread_attr_t attr; pthread_t thread; + int DEVICE; uint8_t reply[11] = { 0xef, 0xfe, 2, 0, 0, 0, 0, 0, 0, 32, 1 }; @@ -179,6 +185,31 @@ int main(int argc, char *argv[]) int bytes_read, bytes_left; uint32_t *code0 = (uint32_t *) buffer; // fast access to code of first buffer +/* + * Examples for METIS: ANAN10E, ANAN100B + * Examples for HERMES: HERMES, ANAN10, ANAN100 + * Examples for ORION: ANAN100D, ANAN200D + * Examples for ORION2: ANAN7000D, ANAN8000D + */ + + DEVICE=1; // default is Hermes + if (argc > 1) { + if (!strncmp(argv[1],"-metis" ,6)) DEVICE=0; + if (!strncmp(argv[1],"-hermes" ,7)) DEVICE=1; + if (!strncmp(argv[1],"-orion" , 6)) DEVICE=5; + if (!strncmp(argv[1],"-orion2" ,7)) DEVICE=10; // Anan7000 in old protocol + if (!strncmp(argv[1],"-c25" ,8)) DEVICE=100; // the same as hermes + } + ismetis=ishermes=isorion=isc25; + switch (DEVICE) { + case 0: fprintf(stderr,"DEVICE is METIS\n"); ismetis=1; break; + case 1: fprintf(stderr,"DEVICE is HERMES\n"); ishermes=1; break; + case 5: fprintf(stderr,"DEVICE is ORION\n"); isorion=1; break; + case 10: fprintf(stderr,"DEVICE is ORION2\n"); isorion=1; break; + case 100: fprintf(stderr,"DEVICE is StemLab\n"); ishermes=1; isc25=1; break; + } + reply[10]=DEVICE; + if ((sock_ep2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); @@ -422,11 +453,6 @@ int main(int argc, char *argv[]) memset(buffer, 0, 60); memcpy(buffer, reply, 11); - // ab-use some of the "unused" bytes in the reply block to indicate we can do TCP - // This information can be used by SDR programs - buffer[11]='T'; - buffer[12]='C'; - buffer[13]='P'; if (sock_TCP_Client > -1) { // We will get into trouble if we respond via TCP while the radio is @@ -568,8 +594,8 @@ void process_ep2(uint8_t *frame) chk_data((frame[3] & 0x03) >> 0, att, "ALEX Attenuator"); chk_data((frame[3] & 0x04) >> 2, preamp, "ALEX preamp"); - chk_data((frame[3] & 0x08) >> 2, LTdither, "LT2208 Dither"); - chk_data((frame[3] & 0x10) >> 2, LTrandom, "LT2208 Random"); + chk_data((frame[3] & 0x08) >> 3, LTdither, "LT2208 Dither"); + chk_data((frame[3] & 0x10) >> 4, LTrandom, "LT2208 Random"); chk_data((frame[3] & 0x60) >> 5, alexRXant, "ALEX RX ant"); chk_data((frame[3] & 0x80) >> 7, alexRXout, "ALEX RX out"); @@ -578,6 +604,13 @@ void process_ep2(uint8_t *frame) chk_data(((frame[4] >> 3) & 7) + 1, receivers, "RECEIVERS"); chk_data(((frame[4] >> 6) & 1), MicTS, "TimeStampMic"); chk_data(((frame[4] >> 7) & 1), CommonMercuryFreq,"Common Mercury Freq"); + + if (isc25) { + // Charly25: has two 18-dB preamps that are switched with "preamp" and "dither" + // and a step-attenuator triggered by Alex ATT ONLY RX1! + rxatt_dbl[0]=pow(10.0, -0.05*(12*att-18*LTdither-18*preamp)); + rxatt_dbl[1]=1.0; + } break; case 2: @@ -651,11 +684,13 @@ void process_ep2(uint8_t *frame) chk_data((frame[4] & 0x1F) >> 0, rx_att[0], "RX1 ATT"); chk_data((frame[4] & 0x20) >> 5, rx1_attE, "RX1 ATT enable"); - // Set RX amplification factors. Assume 20 dB preamps - rxatt_dbl[0]=pow(10.0, -0.05*(rx_att[0]-20*rx_preamp[0])); - rxatt_dbl[1]=pow(10.0, -0.05*(rx_att[1]-20*rx_preamp[1])); - rxatt_dbl[2]=pow(10.0, (double) rx_preamp[2]); - rxatt_dbl[3]=pow(10.0, (double) rx_preamp[3]); + if (!isc25) { + // Set RX amplification factors. Assume 20 dB preamps + rxatt_dbl[0]=pow(10.0, -0.05*(rx_att[0]-20*rx_preamp[0])); + rxatt_dbl[1]=pow(10.0, -0.05*(rx_att[1]-20*rx_preamp[1])); + rxatt_dbl[2]=pow(10.0, (double) rx_preamp[2]); + rxatt_dbl[3]=pow(10.0, (double) rx_preamp[3]); + } break; case 22: @@ -740,7 +775,13 @@ void *handler_ep6(void *arg) 127, 127, 127, 24, 0, 0, 0, 0, 127, 127, 127, 32, 66, 66, 66, 66 }; - int32_t sample; + int32_t rf1isample,rf1qsample; + int32_t rf2isample,rf2qsample; + int32_t rf3isample,rf3qsample; + int32_t rf4isample,rf4qsample; + + int32_t myisample,myqsample; + struct timespec delay; #ifdef __APPLE__ struct timespec now; @@ -834,80 +875,87 @@ void *handler_ep6(void *arg) pointer += 8; memset(pointer, 0, 504); for (j=0; j> 16) & 0xFF; - *pointer++ = (sample >> 8) & 0xFF; - *pointer++ = (sample >> 0) & 0xFF; - sample=noiseQtab[noiseIQpt] * 8388607.0; - sample += T0800Qtab[pt0800] * 83.886070 *rxatt_dbl[0]; // 100 dB below peak - *pointer++ = (sample >> 16) & 0xFF; - *pointer++ = (sample >> 8) & 0xFF; - *pointer++ = (sample >> 0) & 0xFF; - break; - case 1: // RX2 and RX3 see RF2 upon receiving, RF3 upon transmitting - case 2: - if (rate == 0 && ptt) { - // - // RF3: - // Distorted (feed-back) TX signal - // Note we first add distortion, then adjust level - // Therefore we first multiply with txdrv_dbl, then - // distort, and then attenuate with txatt_dbl. - // - i1=isample[rxptr]*txdrv_dbl; - q1=qsample[rxptr]*txdrv_dbl; - fac=IM3a+IM3b*(i1*i1+q1*q1); - sample= txatt_dbl*i1*fac * 8388607.0; - *pointer++ = (sample >> 16) & 0xFF; - *pointer++ = (sample >> 8) & 0xFF; - *pointer++ = (sample >> 0) & 0xFF; - sample= txatt_dbl*q1*fac * 8388607.0; - *pointer++ = (sample >> 16) & 0xFF; - *pointer++ = (sample >> 8) & 0xFF; - *pointer++ = (sample >> 0) & 0xFF; - } else { - // - // RF2: noise + weak 2000 Hz tone - // - sample= noiseItab[noiseIQpt] * 8388607.0; - sample += T2000Itab[pt2000] * 838.86070 * rxatt_dbl[1]; // 80 dB below peak - *pointer++ = (sample >> 16) & 0xFF; - *pointer++ = (sample >> 8) & 0xFF; - *pointer++ = (sample >> 0) & 0xFF; - sample=noiseQtab[noiseIQpt] * 8388607.0; - sample += T2000Qtab[pt2000] * 838.86070 * rxatt_dbl[1]; // 80 dB below peak - *pointer++ = (sample >> 16) & 0xFF; - *pointer++ = (sample >> 8) & 0xFF; - *pointer++ = (sample >> 0) & 0xFF; - } - break; - case 3: // RX4 sees TX outgoing signal (no distortion, no attenuation) - // - // RF4: TX signal with HWPeak = 0.4 - // - if (rate == 0 && ptt) { - sample= isample[rxptr] * 0.400 * 8388607.0; - *pointer++ = (sample >> 16) & 0xFF; - *pointer++ = (sample >> 8) & 0xFF; - *pointer++ = (sample >> 0) & 0xFF; - sample= qsample[rxptr] * 0.400 * 8388607.0; - *pointer++ = (sample >> 16) & 0xFF; - *pointer++ = (sample >> 8) & 0xFF; - *pointer++ = (sample >> 0) & 0xFF; - } else { - pointer +=6; - } - break; - default: - pointer +=6; + case 0: // RX1 + if (rate == 0 && ptt && ismetis) { + myisample=rf3isample; + myqsample=rf3qsample; + } else { + myisample=rf1isample; + myqsample=rf1qsample; + } + break; + case 1: // RX2 + if (rate == 0 && ptt && ismetis) { + myisample=rf4isample; + myqsample=rf4qsample; + } else { + myisample=rf2isample; + myqsample=rf2qsample; + } + break; + case 2: + if (rate == 0 && ptt && ishermes) { + myisample=rf3isample; + myqsample=rf3qsample; + } else { + myisample=rf2isample; + myqsample=rf2qsample; + } + break; + case 3: // RX4 + if (rate == 0 && ptt && ishermes) { + myisample=rf4isample; + myqsample=rf4qsample; + } else if (rate == 0 && ptt && isorion) { + myisample=rf3isample; + myqsample=rf3qsample; + } + break; + case 4: // RX5 + if (rate == 0 && ptt && isorion) { + myisample=rf4isample; + myqsample=rf4qsample; + } + break; } + *pointer++ = (myisample >> 16) & 0xFF; + *pointer++ = (myisample >> 8) & 0xFF; + *pointer++ = (myisample >> 0) & 0xFF; + *pointer++ = (myqsample >> 16) & 0xFF; + *pointer++ = (myqsample >> 8) & 0xFF; + *pointer++ = (myqsample >> 0) & 0xFF; } // Microphone samples: silence pointer += 2; diff --git a/mac_midi.c b/mac_midi.c new file mode 100644 index 0000000..ad9885b --- /dev/null +++ b/mac_midi.c @@ -0,0 +1,142 @@ +/* + * MIDI support for pihpsdr + * (C) Christoph van Wullen, DL1YCF. + * + * This is the "Layer-1" for Apple Macintosh. + * + * This file implements the function register_midi_device, + * which is called with the name of a supported MIDI device and + * the number of characters in the name that must be equal to + * the name of the MIDI device as known in the operating system. + * + * For example, the call + * register_midi_device("COMPANY MIDI X",13) + * will also use a MIDI device named "COMPANY MIDI Y". + * + * This file must generate calls to Layer-2 NewMidiEvent(). + * Some type of messages are not consideres (pressure change, etc.), + * they are silently dropped but must be processed. + * + */ + +#include "midi.h" + +#ifdef __APPLE__ + +#include + +#include +#include +#include + +// +// MIDI callback function +// +static void ReadMIDIdevice(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) { + int i,j,k,command,chan; + MIDIPacket *packet = (MIDIPacket *)pktlist->packet; + + + // loop through all packets in the current list + for (j=0; j < pktlist->numPackets; ++j) { + if (packet->length > 0) { + for ( i = 0; i<(packet->length); ) { + command=packet->data[i]; + if ((command & 128) != 128) continue; + + chan = command & 0x0F; + + switch (command & 0xF0) { + case 0x80: // Note off + NewMidiEvent(MIDI_NOTE, chan, packet->data[i+1], 0); + fprintf(stderr,"NOTE OFF: Note=%d Chan=%d Vel=%d\n", packet->data[i+1], chan, packet->data[i+2]); + i +=3; + break; + case 0x90: // Note on + NewMidiEvent(MIDI_NOTE, chan, packet->data[i+1], 1); + fprintf(stderr,"NOTE ON : Note=%d Chan=%d Vel=%d\n", packet->data[i+1], chan, packet->data[i+2]); + i +=3; + break; + case 0xA0: // Polyph. Press. + fprintf(stderr,"PolPress: Note=%d Chan=%d Prs=%d\n", packet->data[i+1], chan, packet->data[i+2]); + i +=3; + break; + case 0xB0: // Control change + NewMidiEvent(MIDI_CTRL, chan, packet->data[i+1], packet->data[i+2]); + fprintf(stderr,"CtlChang: Ctrl=%d Chan=%d Val=%d\n", packet->data[i+1], chan, packet->data[i+2]); + i +=3; + break; + case 0xC0: // Program change + fprintf(stderr,"PgmChang: Prog=%d Chan=%d\n", packet->data[i+1], chan); + i +=2; + break; + case 0xD0: // Channel Pressure + fprintf(stderr, "ChanPres: Pres=%d Chan=%d\n", packet->data[i+1], chan); + i +=2; + break; + case 0xE0: // Pitch Bend + NewMidiEvent(MIDI_PITCH, chan, 0, packet->data[i+1] + 128*packet->data[i+2]); + fprintf(stderr,"Pitch : val =%d Chan=%d\n", packet->data[i+1] + 128*packet->data[i+2], chan); + i +=3; + break; + case 0xF0: + fprintf(stderr, "System : %x", command); + while ((command = (packet->data[++i]) & 128) != 128) { + fprintf(stderr," %x",command); + } + fprintf(stderr,"\n"); + break; + } + } // i-loop through the packet + packet = MIDIPacketNext(packet); + } // if packet length > 1 + } // j-loop through the list of packets +} + + +void register_midi_device(char *myname) { + unsigned long nDevices; + int i; + CFStringRef pname; + char name[100]; + int FoundMIDIref=-1; + int mylen=strlen(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>>%s<<<\n", name); + if (!strncmp(name, myname, mylen)) { + FoundMIDIref=i; + fprintf(stderr,"MIDI device found and selected: >>>%s<<<\n", name); + } else { + fprintf(stderr,"MIDI device we were looking for : >>>%s<<<\n", myname); + fprintf(stderr,"MIDI device found BUT NOT SELECTED: >>>%s<<<\n", name); + } + } + } + +// +// If we found "our" device, register a callback routine +// + + if (FoundMIDIref >= 0) { + MIDIClientRef client = 0; + MIDIPortRef myMIDIport = 0; + //Create client + MIDIClientCreate(CFSTR("piHPSDR"),NULL,NULL, &client); + MIDIInputPortCreate(client, CFSTR("FromMIDI"), ReadMIDIdevice, NULL, &myMIDIport); + MIDIPortConnectSource(myMIDIport,MIDIGetSource(FoundMIDIref), NULL); + } +} +#endif diff --git a/midi.h b/midi.h new file mode 100644 index 0000000..98a6191 --- /dev/null +++ b/midi.h @@ -0,0 +1,192 @@ +/* + * MIDI support for pihpsdr + * + * (C) Christoph van Wullen, DL1YCF. + * + * Midi support works in three layers + * + * Layer-1: hardware specific + * -------------------------- + * + * Layer1 either implements a callback function (if the operating system + * supports MIDI) or a separate thread polling MIDI data. Whenever a + * MIDI command arrives, such as Note on/off or Midi-Controller value + * changed, it calls Layer 2. + * + * Layer-2: MIDI device specific + * ----------------------------- + * + * Layer2 translates MIDI commands into pihpsdr actions. This is done with + * a table-driven algorithm, such that the same translator can be used for + * any MIDI device provided the tables have been set up correctly. + * It seems overly complicated to create a user interface for setting up + * these tables, instead a standard text file describing the MIDI device + * is read and the tables are set up. + * Layer-2 has SDR applications in mind, but is not necessarily specific + * to pihpsr. It calls the Layer-3 function. + * + * Layer-3: pihpsdr specific + * ------------------------- + * + * Layer 3, finally, implements all the "actions" we can make, such as TUNE + * or VFO. This Layer calls pihpsdr functions. + * + * One word to MIDI channels. Usually, a MIDI device can be configured to use + * a specific channel, such that different keyboards use different channels. + * The Layer-2 tables can either specify that the MIDI command has to come from + * a specific channel, or can specify that the action will be taken not matter which + * channel the MIDI message comes from. The latter case should be the default, but + * if we want to connect more than one MIDI device, we need to speficy the channel. + * + * In principle this supports more than one MIDI device, but in this case they + * must generate MIDI events on different channels + */ + +// +// MIDIaction encodes the "action" to be taken in Layer3 +// +enum MIDIaction { + ACTION_NONE=0, + VFO, + TUNE, + MOX, + AF_GAIN, + MIC_VOLUME, + TX_DRIVE, + ATT, + PRE, + AGC, + COMPRESS, + RIT_ONOFF, + RIT_VAL, + PAN_HIGH, + PAN_LOW, + BAND_UP, + BAND_DOWN, + FILTER_UP, + FILTER_DOWN, + MODE_UP, + MODE_DOWN, + SWAP_VFO +}; + +// +// MIDItype encodes the type of MIDI control. This info +// is passed from Layer-2 to Layer-3 +// +// MIDI_KEY has no parameters and indicates that some +// button has been pressed. +// +// MIDI_KNOB has a "value" parameter (between 0 and 100) +// and indicates that some knob has been set to a specific +// position. +// +// MIDI_WHEEL has a "direction" parameter and indicates that +// some knob has been turned left/down or right/ip. The value +// can be +// +// -100 very fast going down +// -10 fast going down +// -1 going down +// 1 going up +// 10 fast going up +// 100 very fast going up +// + +enum MIDItype { + TYPE_NONE=0, + MIDI_KEY, // Button (press event) + MIDI_KNOB, // Knob (value between 0 and 100) + MIDI_WHEEL // Wheel (direction and speed) +}; + +// +// MIDIevent encodes the actual MIDI event "seen" in Layer-1 and +// passed to Layer-2. MIDI_NOTE events end up as MIDI_KEY and +// MIDI_PITCH as MIDI_KNOB, while MIDI_CTRL can end up both as +// MIDI_KNOB or MIDI_WHEEL, depending on the device description. +// +enum MIDIevent { + EVENT_NONE=0, + MIDI_NOTE, + MIDI_CTRL, + MIDI_PITCH +}; + +// +// Data structure for Layer-2 +// + +// +// There is linked list of all specified MIDI events for a given "Note" value, +// which contains the defined actions for all MIDI_NOTE and MIDI_CTRL events +// with that given note and for all channels +// Note on wheel delay: +// If using a wheel for cycling through a menu, it is difficult to "hit" the correct +// menu item if wheel events are generated at a very high rate. Therefore we can define +// a delay: once a wheel event is reported upstream, any such events are suppressed during +// the delay. +// +// Note that with a MIDI KEY, you can choose that an action is +// generated only for a NOTE_ON event or both for NOTE_ON and +// NOTE_OFF. In the first case, if the key is associated to MOX, +// then MOX is toggled each time the key is pressed. This behaves +// very much like point-and-clicking the MOX buttion in the GUI. +// +// If an action is generated both on NOTE_ON and NOTE_OFF, +// then MOX is engaged when pressing the key and disengaged +// when releasing it. For MOX this makes little send but you +// might want to configure the TUNE button this way. +// The latter behaviour is triggered when the line assigning the key +// or "NOTE OFF". The table speficying the behaviour of layer-2 thus +// contains the key word "ONOFF". This is stored in the field "onoff" +// in struct desc. + +struct desc { + int channel; // -1 for ANY channel + enum MIDIevent event; // type of event (NOTE on/off, Controller change, Pitch value) + int onoff; // 1: generate upstream event both for Note-on and Note-off + enum MIDItype type; // Key, Knob, or Wheel + int low_thr3; // Wheel only: If controller value is <= this value, generate "very fast down" + int low_thr2; // Wheel only: If controller value is <= this value, generate " fast down" + int low_thr1; // Wheel only: If controller value is <= this value, generate " down" + int up_thr1; // Wheel only: If controller value is <= this value, generate " up " + int up_thr2; // Wheel only: If controller value is <= this value, generate " fast up " + int up_thr3; // Wheel only: If controller value is <= this value, generate "very fast up " + int delay; // Wheel only: delay (msec) + enum MIDIaction action; // SDR "action" to generate + struct desc *next; // Next defined action for a controller/key with that note value. +}; + +struct { + struct desc *desc[128]; // description for Note On/Off and ControllerChange + struct desc *pitch; // description for PitchChanges +} MidiCommandsTable; + +// +// 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); + +// +// Layer-2 entry point (called by Layer1) +// +// When Layer-1 has received a MIDI message, it calls +// NewMidiEvent. +// +// MIDIstartup looks for files containing descriptions for MIDI +// devices and calls the Layer-1 function register_midi_device +// for each device description that was successfully read. + +void NewMidiEvent(enum MIDIevent event, int channel, int note, int val); +void MIDIstartup(); + +// +// Layer-3 entry point (called by Layer2). In Layer-3, all the pihpsdr +// actions (such as changing the VFO frequency) are performed. +// The implementation of DoTheMIDI is tightly bound to pihpsr. +// + +void DoTheMidi(enum MIDIaction code, enum MIDItype type, int val); diff --git a/midi.inp b/midi.inp new file mode 100644 index 0000000..88076ce --- /dev/null +++ b/midi.inp @@ -0,0 +1,33 @@ +# +# Sample midi.inp file, suitable for a Behringer CMD PL-1 MIDI controller +# +# Note that the Attenuator is implemented twice, as a key and as a wheel +# The key is suitable for radios with a step (ALEX) attenuator, the wheel +# fits best for radios with a programmable attenuator (0-31 dB) +# +DEVICE=CMD PL 1 +CTRL=31 WHEEL THR=59 61 63 65 67 69 ACTION=VFO # Big wheel: : main VFO knob +PITCH ACTION=AFGAIN # Big slider : AF gain +KEY=16 ACTION=PREAMP # Key 1 : Cycle through Preamp settings +KEY=17 ACTION=ATT # Key 2 : Cycle through ATT (Alex ATT) settings +KEY=18 ACTION=RITTOGGLE # Key 3 : RIT on/off +KEY=19 ACTION=NONE # Key 4 : +KEY=20 ACTION=NONE # Key 5 : +KEY=21 ACTION=NONE # Key 6 : +KEY=22 ACTION=NONE # Key 7 : +KEY=23 ACTION=NONE # Key 8 : +KEY=24 ACTION=TUNE # LOAD button: TUNE on/off +KEY=27 ACTION=SWAPVFO # SCRATCH button: Swap VFOs A and B +KEY=34 ACTION=MOX # CUE button: MOX on/off +KEY=36 ACTION=MODEDOWN # << button: Mode down +KEY=37 ACTION=MODEUP # >> button: Mode up +KEY=38 ACTION=BANDDOWN # - button: Band down +KEY=39 ACTION=BANDUP # + button: Band up +CTRL=0 WHEEL THR=-1 -1 63 65 128 128 ACTION=ATT # Knob 1 : RX att +CTRL=1 WHEEL THR=-1 -1 63 65 128 128 ACTION=COMPRESS # Knob 2 : TX compression +CTRL=2 WHEEL THR=-1 -1 63 65 128 128 ACTION=RITVAL # Knob 3 : RIT value +CTRL=3 WHEEL THR=-1 -1 63 65 128 128 ACTION=PANLOW # Knob 4 : Panadapter low +CTRL=4 WHEEL THR=-1 -1 63 65 128 128 ACTION=AGC # Knob 5 : AGC +CTRL=5 WHEEL THR=-1 -1 63 65 128 128 ACTION=MICGAIN # Knob 6 : MIC gain +CTRL=6 WHEEL THR=-1 -1 63 65 128 128 ACTION=RFPOWER # Knob 7 : TX drive +CTRL=7 WHEEL THR=-1 -1 63 65 128 128 ACTION=FILTERUP # Knob 8 : cycle through the filters diff --git a/midi2.c b/midi2.c new file mode 100644 index 0000000..1e737c1 --- /dev/null +++ b/midi2.c @@ -0,0 +1,257 @@ +/* + * Layer-2 of MIDI support + * + * (C) Christoph van Wullen, DL1YCF + * + * Using the data in MIDICommandsTable, this subroutine translates the low-level + * MIDI events into MIDI actions in the SDR console. + */ + +#include +#include +#include +#include +#include "midi.h" + +void NewMidiEvent(enum MIDIevent event, int channel, int note, int val) { + + struct desc *desc; + int new; + static enum MIDIaction last_wheel_action; + static struct timespec tp, last_wheel_tp={0,0}; + long delta; + + if (event == MIDI_PITCH) { + desc=MidiCommandsTable.pitch; + } else { + desc=MidiCommandsTable.desc[note]; + } + while (desc) { + if ((desc->channel == channel || desc->channel == -1) && (desc->event == event)) { + // Found matching entry + switch (desc->event) { + case MIDI_NOTE: + if ((val == 1 || (val == 0 && desc->onoff == 1)) && desc->type == MIDI_KEY) { + DoTheMidi(desc->action, desc->type, 0); + } + break; + case MIDI_CTRL: + if (desc->type == MIDI_KNOB) { + // normalize value to range 0 - 100 + new = (val*100)/127; + DoTheMidi(desc->action, desc->type, new); + } else if (desc->type == MIDI_WHEEL) { + if (desc->delay > 0) { + clock_gettime(CLOCK_MONOTONIC, &tp); + delta=1000*(tp.tv_sec - last_wheel_tp.tv_sec); + delta += (tp.tv_nsec - last_wheel_tp.tv_nsec)/1000000; + if (delta < desc->delay) break; + last_wheel_tp = tp; + } + // translate value to direction + if (val <= desc->low_thr1) new=-1; + if (val <= desc->low_thr2) new=-10; + if (val <= desc->low_thr3) new=-100; + if (val >= desc->up_thr1 ) new=1; + if (val >= desc->up_thr2 ) new=10; + if (val >= desc->up_thr3 ) new=100; + DoTheMidi(desc->action, desc->type, new); + last_wheel_action=desc->action; + } + break; + case MIDI_PITCH: + if (desc->type == MIDI_KNOB) { + // normalize value to 0 - 100 + new = (val*100)/16383; + fprintf(stderr,"PITCH calc: val=%d new=%d\n", val,new); + DoTheMidi(desc->action, desc->type, new); + } + break; + default: + break; + } + break; + } else { + desc=desc->next; + } + } +} + +/* + * This data structre connects names as used in the midi.inp file with + * our MIDIaction enum values + */ + +static struct { + enum MIDIaction action; + const char *str; +} ActionTable[] = { + { VFO, "VFO"}, + { TUNE, "TUNE"}, + { MOX, "MOX"}, + { AF_GAIN, "AFGAIN"}, + { MIC_VOLUME, "MICGAIN"}, + { TX_DRIVE, "RFPOWER"}, + { ATT, "ATT"}, + { PRE, "PREAMP"}, + { AGC, "AGC"}, + { COMPRESS, "COMPRESS"}, + { RIT_ONOFF, "RITTOGGLE"}, + { RIT_VAL, "RITVAL"}, + { PAN_HIGH, "PANHIGH"}, + { PAN_LOW, "PANLOW"}, + { BAND_UP, "BANDUP"}, + { BAND_DOWN, "BANDDOWN"}, + { FILTER_UP, "FILTERUP"}, + { FILTER_DOWN, "FILTERDOWN"}, + { MODE_UP, "MODEUP"}, + { MODE_DOWN, "MODEDOWN"}, + { SWAP_VFO, "SWAPVFO"}, + { ACTION_NONE, NULL} +}; + +/* + * Translation from keyword in midi.inp file to MIDIaction + */ + +static enum MIDIaction keyword2action(char *s) { + int i=0; + + for (i=0; 1; i++) { + if (ActionTable[i].str == NULL) return ACTION_NONE; + if (!strcmp(s, ActionTable[i].str)) return ActionTable[i].action; + } + /* NOTREACHED */ +} + +/* + * Here we read in a MIDI description file "midi.def" and fill the MidiCommandsTable + * data structure + */ + +void MIDIstartup() { + FILE *fpin,*fpout; + char zeile[255]; + char *cp,*cq; + int key; + enum MIDIaction action; + int chan; + int is_wheel; + int lt3,lt2,lt1,ut1,ut2,ut3; + int onoff, delay; + struct desc *desc,*dp; + enum MIDItype type; + enum MIDIevent event; + int i; + + for (i=0; i<128; i++) MidiCommandsTable.desc[i]=NULL; + MidiCommandsTable.pitch=NULL; + + fpin=fopen("midi.inp", "r"); + fpout=stderr; + if (!fpin) return; + + for (;;) { + if (fgets(zeile, 255, fpin) == NULL) break; + + // ignore comments + cp=index(zeile,'#'); + if (cp == zeile) continue; // comment line + if (cp) *cp=0; // ignore trailing comment + + if ((cp = strstr(zeile, "DEVICE="))) { + // Delete trailing blanks and newlines + cq=cp+7; + while (*cq != 0 && *cq != '\n' && *cq != ' ' && *cq != '\t') cq++; + *cq=0; + register_midi_device(cp+7); + continue; // nothing more in this line + } + chan=-1; // default: any channel + lt3=lt2=lt1=0; + ut3=ut2=ut1=127; + onoff=0; + event=EVENT_NONE; + type=TYPE_NONE; + key=0; + delay=0; + + if ((cp = strstr(zeile, "KEY="))) { + sscanf(cp+4, "%d", &key); + event=MIDI_NOTE; + type=MIDI_KEY; + } + if ((cp = strstr(zeile, "CTRL="))) { + sscanf(cp+5, "%d", &key); + event=MIDI_CTRL; + type=MIDI_KNOB; + } + if ((cp = strstr(zeile, "PITCH "))) { + event=MIDI_PITCH; + type=MIDI_KNOB; + } + if ((cp = strstr(zeile, "CHAN="))) { + sscanf(cp+5, "%d", &chan); + chan--; + if (chan<0 || chan>15) chan=-1; + } + if ((cp = strstr(zeile, "WHEEL"))) { + // change type from MIDI_KNOB to MIDI_WHEEL + type=MIDI_WHEEL; + } + if ((cp = strstr(zeile, "ONOFF"))) { + onoff=1; + } + if ((cp = strstr(zeile, "DELAY="))) { + sscanf(cp+6, "%d", &delay); + } + if ((cp = strstr(zeile, "THR="))) { + sscanf(cp+4, "%d %d %d %d %d %d", <3, <2, <1, &ut1, &ut2, &ut3); + is_wheel=1; + } + if ((cp = strstr(zeile, "ACTION="))) { + // cut zeile at the first blank character following + cq=cp+7; + while (*cq != 0 && *cq != '\n' && *cq != ' ' && *cq != '\t') cq++; + *cq=0; + action=keyword2action(cp+7); + } + if (event == EVENT_NONE || type == TYPE_NONE || key < 0 || key > 127) continue; + // Now all entries of the line have been read. Construct descriptor + fprintf(fpout,"K=%d C=%d T=%d E=%d A=%d OnOff=%d THRs=%d %d %d %d %d %d\n", + key,chan,type, event, action, onoff, lt3,lt2,lt1,ut1,ut2,ut3); + desc = (struct desc *) malloc(sizeof(struct desc)); + desc->next = NULL; + desc->action = action; + desc->type = type; + desc->event = event; + desc->onoff = onoff; + desc->delay = delay; + desc->low_thr3 = lt3; + desc->low_thr2 = lt2; + desc->low_thr1 = lt1; + desc->up_thr1 = ut1; + desc->up_thr2 = ut2; + desc->up_thr3 = ut3; + desc->channel = chan; + // insert descriptor + if (event == MIDI_PITCH) { + dp = MidiCommandsTable.pitch; + if (dp == NULL) { + MidiCommandsTable.pitch = desc; + } else { + while (dp->next != NULL) dp=dp->next; + dp->next=desc; + } + } + if (event == MIDI_KEY || event == MIDI_CTRL) { + dp = MidiCommandsTable.desc[key]; + if (dp == NULL) { + MidiCommandsTable.desc[key]=desc; + } else { + while (dp->next != NULL) dp=dp->next; + dp->next=desc; + } + } + } +} diff --git a/midi3.c b/midi3.c new file mode 100644 index 0000000..f3989d0 --- /dev/null +++ b/midi3.c @@ -0,0 +1,233 @@ +/* + * Layer-3 of MIDI support + * + * (C) Christoph van Wullen, DL1YCF + * + * + * In most cases, a certain action only makes sense for a specific + * type. For example, changing the VFO frequency will only be implemeted + * for MIDI_WHEEL, and TUNE off/on only with MIDI_KNOB. + * + * However, changing the volume makes sense both with MIDI_KNOB and MIDI_WHEEL. + */ +#include "radio.h" +#include "vfo.h" +#include "filter.h" +#include "band.h" +#include "mode.h" +#include "new_menu.h" +#include "sliders.h" +#include "ext.h" +#include "midi.h" + +void DoTheMidi(enum MIDIaction action, enum MIDItype type, int val) { + + int new; + double dnew; + double *dp; + + switch (action) { + case SWAP_VFO: // only key supported + if (type == MIDI_KEY) { + g_idle_add(ext_vfo_a_swap_b,NULL); + } + break; + case VFO: // only wheel supported + if (type == MIDI_WHEEL) g_idle_add(ext_vfo_step, (gpointer)(uintptr_t) val); + break; + case TUNE: // only key supported + if (type == MIDI_KEY) { + new = !tune; + g_idle_add(ext_tune_update, (gpointer)(long) new); + } + break; + case MOX: // only key supported + if (type == MIDI_KEY) { + new = !mox; + g_idle_add(ext_mox_update, (gpointer)(long) new); + } + break; + case AF_GAIN: // knob or wheel supported + if (type == MIDI_KNOB) { + active_receiver->volume = 0.01*val; + } else if (type == MIDI_WHEEL) { + dnew=active_receiver->volume += 0.01*val; + if (dnew < 0.0) dnew=0.0; if (dnew > 1.0) dnew=1.0; + active_receiver->volume = dnew; + } else { + break; + } + g_idle_add(ext_update_af_gain, NULL); + break; + case MIC_VOLUME: // knob or wheel supported + if (type == MIDI_KNOB) { + dnew=-10.0 + 0.6*val; + } else if (type == MIDI_WHEEL) { + dnew = mic_gain + val; + if (dnew < -10.0) dnew=-10.0; if (dnew > 50.0) dnew=50.0; + } else { + break; + } + dp=malloc(sizeof(double)); + *dp=dnew; + g_idle_add(ext_set_mic_gain, (gpointer) dp); + break; + case AGC: // knob or wheel supported + if (type == MIDI_KNOB) { + dnew = -20.0 + 1.4*val; + } else if (type == MIDI_WHEEL) { + dnew=active_receiver->agc_gain + val; + if (dnew < -20.0) dnew=-20.0; if (dnew > 120.0) dnew=120.0; + } else { + break; + } + dp=malloc(sizeof(double)); + *dp=dnew; + g_idle_add(ext_set_agc_gain, (gpointer) dp); + break; + case TX_DRIVE: // knob or wheel supported + if (type == MIDI_KNOB) { + dnew = val; + } else if (type == MIDI_WHEEL) { + dnew=transmitter->drive + val; + if (dnew < 0.0) dnew=0.0; if (dnew > 100.0) dnew=100.0; + } else { + break; + } + dp=malloc(sizeof(double)); + *dp=dnew; + g_idle_add(ext_set_drive, (gpointer) dp); + break; + case BAND_UP: // key or wheel supported + case BAND_DOWN: // key or wheel supported + if (type == MIDI_KEY) { + new=(action == BAND_UP) ? 1 : -1; + } else if (type == MIDI_WHEEL) { + new=val; + } else { + break; + } + new+=vfo[active_receiver->id].band; + if (new >= BANDS) new=0; + if (new < 0) new=BANDS-1; + g_idle_add(ext_vfo_band_changed, (gpointer) (uintptr_t) new); + break; + case FILTER_UP: // key or wheel supported + case FILTER_DOWN: // key or wheel supported + if (type == MIDI_KEY) { + new=(action == FILTER_UP) ? 1 : -1; + } else if (type == MIDI_WHEEL) { + new=val; + } else { + break; + } + new+=vfo[active_receiver->id].filter; + if (new >= FILTERS) new=0; + if (new <0) new=FILTERS-1; + g_idle_add(ext_vfo_filter_changed, (gpointer) (uintptr_t) new); + break; + case MODE_UP: // key or wheel supported + case MODE_DOWN: // key or wheel supported + if (type == MIDI_KEY) { + new=(action == MODE_UP) ? 1 : -1; + } else if (type == MIDI_WHEEL) { + new=val; + } else { + break; + } + new+=vfo[active_receiver->id].mode; + if (new >= MODES) new=0; + if (new <0) new=MODES-1; + g_idle_add(ext_vfo_mode_changed, (gpointer) (uintptr_t) new); + break; + case PAN_LOW: // only wheel supported + if (type == MIDI_WHEEL) { + if (isTransmitting()) { + // TX panadapter affected + transmitter->panadapter_low += val; + } else { + active_receiver->panadapter_low += val; + } + } + break; + case RIT_ONOFF: // only key supported + if (type == MIDI_KEY) { + vfo[active_receiver->id].rit_enabled = !vfo[active_receiver->id].rit_enabled; + g_idle_add(ext_vfo_update, NULL); + } + break; + case RIT_VAL: // only wheel supported + if (type == MIDI_WHEEL) { + new = vfo[active_receiver->id].rit + val*rit_increment; + if (new > 9999) new= 9999; + if (new < -9999) new=-9999; + vfo[active_receiver->id].rit = new; + g_idle_add(ext_vfo_update, NULL); + } + break; + case PAN_HIGH: // only wheel supported + if (type == MIDI_WHEEL) { + if (mox) { + // TX panadapter affected + transmitter->panadapter_high += val; + } else { + active_receiver->panadapter_high += val; + } + } + break; + case PRE: // only key supported + if (filter_board == CHARLY25) { + new = active_receiver->preamp + active_receiver->dither; + new++; + if (new >2) new=0; + switch (new) { + case 0: + active_receiver->preamp=0; + active_receiver->dither=0; + break; + case 1: + active_receiver->preamp=1; + active_receiver->dither=0; + break; + case 2: + active_receiver->preamp=1; + active_receiver->dither=1; + break; + } + g_idle_add(ext_update_att_preamp, NULL); + } else { + new=active_receiver->preamp+1; + if (new > 1) new=0; + active_receiver->preamp= (new == 1); + } + break; + case ATT: // Key for ALEX attenuator, wheel or knob for slider + switch(type) { + case MIDI_KEY: + new=active_receiver->alex_attenuation + 1; + if (new > 3) new=0; + g_idle_add(ext_set_alex_attenuation, (gpointer)(uintptr_t)new); + g_idle_add(ext_update_att_preamp, NULL); + break; + case MIDI_WHEEL: + case MIDI_KNOB: + if (type == MIDI_WHEEL) { + new=adc_attenuation[active_receiver->adc] + val; + if (new > 31) new=31; + if (new < 0 ) new=0; + } else { + new=(31*val)/100; + } + dp=malloc(sizeof(double)); + *dp=new; + g_idle_add(ext_set_attenuation_value,(gpointer) dp); + break; + default: + break; + } + break; + default: + fprintf(stderr,"Unimplemented in DoTheMidi: A=%d T=%d val=%d\n", action, type, val); + break; + } +} diff --git a/old_discovery.c b/old_discovery.c index 024f99e..235ca12 100644 --- a/old_discovery.c +++ b/old_discovery.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "discovered.h" #include "discovery.h" @@ -81,9 +82,12 @@ static void discover(struct ifaddrs* iface) { // // We make a time-out of 3 secs, otherwise we might "hang" in connect() // + flags=fcntl(discovery_socket, F_GETFL, 0); + fcntl(discovery_socket, F_SETFL, flags | O_NONBLOCK); tv.tv_sec=3; tv.tv_usec=0; setsockopt(discovery_socket, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv)); + if (connect(discovery_socket, (const struct sockaddr *)&to_addr, sizeof(to_addr)) < 0) { perror("discover: connect() failed for TCP discovery_socket:"); diff --git a/ps_menu.c b/ps_menu.c index 37a5c60..d6d5924 100644 --- a/ps_menu.c +++ b/ps_menu.c @@ -221,7 +221,7 @@ static int info_thread(gpointer arg) { if (transmitter->auto_on) { double ddb; - int new_att; + static int new_att; int newcal=info[5]!=old5_2; old5_2=info[5]; switch(state) { @@ -244,6 +244,7 @@ static int info_thread(gpointer arg) { // Actually, we first adjust the attenuation (state=0), // then do a PS reset (state=1), and then restart PS (state=2). if (transmitter->attenuation != new_att) { + SetPSControl(transmitter->id, 1, 0, 0, 0); transmitter->attenuation=new_att; state=1; } @@ -268,11 +269,10 @@ static void enable_cb(GtkWidget *widget, gpointer data) { static void auto_cb(GtkWidget *widget, gpointer data) { transmitter->auto_on=gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); - if(transmitter->auto_on) { - transmitter->attenuation=31; - } else { + if(!transmitter->auto_on) { transmitter->attenuation=0; } + } static void resume_cb(GtkWidget *widget, gpointer data) { diff --git a/radio.c b/radio.c index c9bb32c..b47623e 100644 --- a/radio.c +++ b/radio.c @@ -69,6 +69,9 @@ #ifdef LOCALCW #include "iambic.h" #endif +#ifdef MIDI +#include "midi.h" +#endif #define min(x,y) (x= -10) && (new_vol <= 50)) { double *p_mic_gain=malloc(sizeof(double)); *p_mic_gain=new_vol; - g_idle_add(update_mic_gain,(void *)p_mic_gain); + g_idle_add(ext_set_mic_gain,(void *)p_mic_gain); } else { send_resp(client_sock,"?;"); } diff --git a/sliders.c b/sliders.c index c31afef..8c71cae 100644 --- a/sliders.c +++ b/sliders.c @@ -188,12 +188,22 @@ void set_attenuation_value(double value) { void update_att_preamp(void) { // CHARLY25: update the ATT/Pre buttons to the values of the active RX + // We should also set the attenuation for use in meter.c if (filter_board == CHARLY25) { char id[] = "x"; + if (active_receiver->id != 0) { + active_receiver->alex_attenuation=0; + active_receiver->preamp=0; + active_receiver->dither=0; + adc_attenuation[active_receiver->adc] = 0; + } sprintf(id, "%d", active_receiver->alex_attenuation); + adc_attenuation[active_receiver->adc] = 12*active_receiver->alex_attenuation; gtk_combo_box_set_active_id(GTK_COMBO_BOX(c25_att_combobox), id); sprintf(id, "%d", active_receiver->preamp + active_receiver->dither); gtk_combo_box_set_active_id(GTK_COMBO_BOX(c25_preamp_combobox), id); + } else { + adc_attenuation[active_receiver->adc] = 10*active_receiver->alex_attenuation; } } @@ -385,12 +395,6 @@ void set_mic_gain(double value) { } } -int update_mic_gain(void *data) { - set_mic_gain(*(double*)data); - free(data); - return 0; -} - void set_linein_gain(int value) { linein_gain=value; if(display_sliders) { @@ -489,7 +493,7 @@ static void compressor_enable_cb(GtkWidget *widget, gpointer data) { transmitter_set_compressor(transmitter,gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))); } -void set_squelch(RECEIVER* rx) { +void set_squelch() { setSquelch(active_receiver); if(display_sliders) { gtk_range_set_value (GTK_RANGE(squelch_scale),active_receiver->squelch); diff --git a/sliders.h b/sliders.h index 87904f7..a33ac7f 100644 --- a/sliders.h +++ b/sliders.h @@ -43,7 +43,7 @@ extern GtkWidget *sliders_init(int my_width, int my_height); extern void sliders_update(); -extern void set_squelch(RECEIVER* rx); +extern void set_squelch(); extern void set_compression(TRANSMITTER *tx); #endif -- 2.45.2