From 746dc202f593993d0853186252edf20547283924 Mon Sep 17 00:00:00 2001 From: c vw Date: Fri, 24 May 2019 17:25:39 +0200 Subject: [PATCH] - Added MIDI level-1 code for ALSA/LINUX - Fixed TX panadapter display - Fixed WDSP wisdom stuff --- Makefile | 36 ++++++-- alsa_midi.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++ mac_midi.c | 129 +++++++++++++++----------- main.c | 42 +++------ midi.inp | 2 +- midi2.c | 6 +- radio.c | 1 + tx_panadapter.c | 45 +++++---- 8 files changed, 387 insertions(+), 110 deletions(-) create mode 100644 alsa_midi.c diff --git a/Makefile b/Makefile index ef76aed..ad0dbd8 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,9 @@ GIT_VERSION := $(shell git describe --abbrev=0 --tags) # 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 @@ -52,6 +55,14 @@ LINK=gcc # uncomment the line below for various debug facilities #DEBUG_OPTION=-D DEBUG +ifeq ($(MIDI_INCLUDE),MIDI) +MIDI_OPTIONS=-D MIDI +MIDI_SOURCES= alsa_midi.c midi2.c midi3.c +MIDI_HEADERS= midi.h +MIDI_OBJS= alsa_midi.o midi2.o midi3.o +MIDI_LIBS= -lasound +endif + ifeq ($(PURESIGNAL_INCLUDE),PURESIGNAL) PURESIGNAL_OPTIONS=-D PURESIGNAL PURESIGNAL_SOURCES= \ @@ -201,11 +212,12 @@ GTKLIBS=`pkg-config --libs gtk+-3.0` AUDIO_LIBS=-lasound #AUDIO_LIBS=-lsoundio -OPTIONS=-g -Wno-deprecated-declarations $(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)"' $(DEBUG_OPTION) -O3 +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)"' $(DEBUG_OPTION) -O3 -LIBS=-lrt -lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(PSKLIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(FREEDVLIBS) $(STEMLAB_LIBS) +LIBS=-lrt -lm -lwdsp -lpthread $(AUDIO_LIBS) $(USBOZY_LIBS) $(PSKLIBS) $(GTKLIBS) $(GPIO_LIBS) $(SOAPYSDRLIBS) $(FREEDVLIBS) $(STEMLAB_LIBS) $(MIDI_LIBS) INCLUDES=$(GTKINCLUDES) COMPILE=$(CC) $(OPTIONS) $(INCLUDES) @@ -425,10 +437,18 @@ ext.o \ error_handler.o \ cwramp.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) - -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) +$(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) $(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 diff --git a/alsa_midi.c b/alsa_midi.c new file mode 100644 index 0000000..6893f8c --- /dev/null +++ b/alsa_midi.c @@ -0,0 +1,236 @@ +/* + * MIDI support for pihpsdr + * (C) Christoph van Wullen, DL1YCF. + * + * This is the "Layer-1" for ALSA-MIDI (Linux) + * For further comments see file mac_midi.c + */ + +/* + * ALSA: MIDI devices are sub-devices to sound cards. + * Therefore we have to loop through the sound cards + * and then, for each sound card, through the + * sub-devices until we have found "our" MIDI + * input device. + * + * The procedure how to find and talk with + * a MIDI device is taken from the sample + * program amidi.c in alsautils. + */ + +#include "midi.h" + +#ifndef __APPLE__ + +#include +#include + +static pthread_t midi_thread_id; +static void* midi_thread(void *); + +static char portname[64]; + +static enum { + STATE_SKIP, // skip bytes + STATE_ARG1, // one arg byte to come + STATE_ARG2, // two arg bytes to come +} state=STATE_SKIP; + +static enum { + CMD_NOTEON, + CMD_NOTEOFF, + CMD_CTRL, + CMD_PITCH, +} command; + +static void *midi_thread(void *arg) { + int ret; + snd_rawmidi_t *input; + int npfds; + struct pollfd *pfds; + unsigned char buf[32]; + unsigned char byte; + unsigned short revents; + int i; + int chan,arg1,arg2; + + if ((ret = snd_rawmidi_open(&input, NULL, portname, SND_RAWMIDI_NONBLOCK)) < 0) { + fprintf(stderr,"cannot open port \"%s\": %s\n", portname, snd_strerror(ret)); + return NULL; + } + snd_rawmidi_read(input, NULL, 0); /* trigger reading */ + + npfds = snd_rawmidi_poll_descriptors_count(input); + pfds = alloca(npfds * sizeof(struct pollfd)); + snd_rawmidi_poll_descriptors(input, pfds, npfds); + for (;;) { + ret = poll(pfds, npfds, 250); + if (ret < 0) { + fprintf(stderr,"poll failed: %s\n", strerror(errno)); + // Do not give up, but also do not fire too rapidly + usleep(250000); + } + if (ret <= 0) continue; // nothing arrived, do next poll() + if ((ret = snd_rawmidi_poll_descriptors_revents(input, pfds, npfds, &revents)) < 0) { + fprintf(stderr,"cannot get poll events: %s\n", snd_strerror(errno)); + continue; + } + if (revents & (POLLERR | POLLHUP)) continue; + if (!(revents & POLLIN)) continue; + // something has arrived + ret = snd_rawmidi_read(input, buf, 64); + if (ret == 0) continue; + if (ret < 0) { + fprintf(stderr,"cannot read from port \"%s\": %s\n", portname, snd_strerror(ret)); + continue; + } + // process bytes in buffer. Since they no not necessarily form complete messages + // we need a state machine here. + for (i=0; i< ret; i++) { + byte=buf[i]; + switch (state) { + case STATE_SKIP: + chan=byte & 0x0F; + switch (byte & 0xF0) { + case 0x80: // Note-OFF command + command=CMD_NOTEOFF; + state=STATE_ARG2; + break; + case 0x90: // Note-ON command + command=CMD_NOTEON; + state=STATE_ARG2; + break; + case 0xB0: // Controller Change + command=CMD_CTRL; + state=STATE_ARG2; + break; + case 0xE0: // Pitch Bend + command=CMD_PITCH; + state=STATE_ARG2; + break; + case 0xA0: // Polyphonic Pressure + case 0xC0: // Program change + case 0xD0: // Channel pressure + case 0xF0: // System Message: continue waiting for bit7 set + default: // Remain in STATE_SKIP until bit7 is set + break; + } + break; + case STATE_ARG2: + arg1=byte; + state=STATE_ARG1; + break; + case STATE_ARG1: + arg2=byte; + // We have a command! + switch (command) { + case CMD_NOTEON: + NewMidiEvent(MIDI_NOTE, chan, arg1, 1); + break; + case CMD_NOTEOFF: + NewMidiEvent(MIDI_NOTE, chan, arg1, 0); + break; + case CMD_CTRL: + NewMidiEvent(MIDI_CTRL, chan, arg1, arg2); + break; + case CMD_PITCH: + NewMidiEvent(MIDI_PITCH, chan, 0, arg1+128*arg2); + break; + } + state=STATE_SKIP; + break; + } + } + } +} + +void register_midi_device(char *myname) { + + int mylen=strlen(myname); + snd_ctl_t *ctl; + snd_rawmidi_info_t *info; + int card, device, subs, sub, ret; + const char *devnam, *subnam; + int found=0; + char name[64]; + + card=-1; + if ((ret = snd_card_next(&card)) < 0) { + fprintf(stderr,"cannot determine card number: %s\n", snd_strerror(ret)); + return; + } + while (card >= 0) { + fprintf(stderr,"Found Sound Card=%d\n",card); + sprintf(name,"hw:%d", card); + if ((ret = snd_ctl_open(&ctl, name, 0)) < 0) { + fprintf(stderr,"cannot open control for card %d: %s\n", card, snd_strerror(ret)); + return; + } + device = -1; + // loop through devices of the card + for (;;) { + if ((ret = snd_ctl_rawmidi_next_device(ctl, &device)) < 0) { + fprintf(stderr,"cannot determine device number: %s\n", snd_strerror(ret)); + break; + } + if (device < 0) break; + fprintf(stderr,"Found Device=%d on Card=%d\n", device, card); + // found sub-device + snd_rawmidi_info_alloca(&info); + snd_rawmidi_info_set_device(info, device); + snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_INPUT); + ret = snd_ctl_rawmidi_info(ctl, info); + if (ret >= 0) { + subs = snd_rawmidi_info_get_subdevices_count(info); + } else { + subs = 0; + } + fprintf(stderr,"Number of MIDI input devices: %d\n", subs); + if (!subs) break; + // subs: number of sub-devices to device on card + for (sub = 0; sub < subs; ++sub) { + snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_INPUT); + snd_rawmidi_info_set_subdevice(info, sub); + ret = snd_ctl_rawmidi_info(ctl, info); + if (ret < 0) { + fprintf(stderr,"cannot get rawmidi information %d:%d:%d: %s\n", + card, device, sub, snd_strerror(ret)); + break; + } + if (found) break; + devnam = snd_rawmidi_info_get_name(info); + subnam = snd_rawmidi_info_get_subdevice_name(info); + // If there is only one sub-device and it has no name, we use + // devnam for comparison and make a portname of form "hw:x,y", + // else we use subnam for comparison and make a portname of form "hw:x,y,z". + if (sub == 0 && subnam[0] == '\0') { + sprintf(portname,"hw:%d,%d", card, device); + } else { + sprintf(portname,"hw:%d,%d,%d", card, device, sub); + devnam=subnam; + } + if (!strncmp(myname, devnam, mylen)) { + found=1; + fprintf(stderr,"MIDI device %s selected (PortName=%s)\n", devnam, portname); + } else { + fprintf(stderr,"MIDI device found BUT NOT SELECTED: %s\n", devnam); + } + } + } + snd_ctl_close(ctl); + // next card + if ((ret = snd_card_next(&card)) < 0) { + fprintf(stderr,"cannot determine card number: %s\n", snd_strerror(ret)); + break; + } + } + if (!found) { + fprintf(stderr,"MIDI device %s NOT FOUND!\n", myname); + } + // Found our MIDI input device. Spawn off a thread reading data + ret = pthread_create(&midi_thread_id, NULL, midi_thread, NULL); + if (ret < 0) { + fprintf(stderr,"Failed to create MIDI read thread\n"); + } +} +#endif diff --git a/mac_midi.c b/mac_midi.c index 06c84ca..2229f44 100644 --- a/mac_midi.c +++ b/mac_midi.c @@ -49,64 +49,89 @@ // We process *all* data but only generate calls to layer-2 for Note On/Off // and ControllerChange events. // + +// +// Although MacOS does all the grouping of MIDI bytes into commands for us, +// we use here the same state machine as we have in the ALSA MIDI implementation +// That is, we look at each MIDI byte separately +// + +static enum { + STATE_SKIP, // skip bytes until command bit is set + STATE_ARG1, // one arg byte to come + STATE_ARG2, // two arg bytes to come +} state=STATE_SKIP; + +static enum { + CMD_NOTEON, + CMD_NOTEOFF, + CMD_CTRL, + CMD_PITCH, +} command; + static void ReadMIDIdevice(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) { - int i,j,k,command,chan; + int i,j,k,byte,chan,arg1,arg2; 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 + for (i=0; ilength; i++) { + byte=packet->data[i]; + switch (state) { + case STATE_SKIP: + chan=byte & 0x0F; + switch (byte & 0xF0) { + case 0x80: // Note-OFF command + command=CMD_NOTEOFF; + state=STATE_ARG2; + break; + case 0x90: // Note-ON command + command=CMD_NOTEON; + state=STATE_ARG2; + break; + case 0xB0: // Controller Change + command=CMD_CTRL; + state=STATE_ARG2; + break; + case 0xE0: // Pitch Bend + command=CMD_PITCH; + state=STATE_ARG2; + break; + case 0xA0: // Polyphonic Pressure: skip args + case 0xC0: // Program change: skip args + case 0xD0: // Channel pressure: skip args + case 0xF0: // System Message: skip args + default: // Remain in STATE_SKIP until "interesting" command seen + break; + } + break; + case STATE_ARG2: // store byte as first argument + arg1=byte; + state=STATE_ARG1; + break; + case STATE_ARG1: // store byte as second argument, process command + arg2=byte; + // We have a command! + switch (command) { + case CMD_NOTEON: + NewMidiEvent(MIDI_NOTE, chan, arg1, 1); + break; + case CMD_NOTEOFF: + NewMidiEvent(MIDI_NOTE, chan, arg1, 0); + break; + case CMD_CTRL: + NewMidiEvent(MIDI_CTRL, chan, arg1, arg2); + break; + case CMD_PITCH: + NewMidiEvent(MIDI_PITCH, chan, 0, arg1+128*arg2); + break; + } + state=STATE_SKIP; + break; + } + } // i-loop through the packet + packet = MIDIPacketNext(packet); } // j-loop through the list of packets } diff --git a/main.c b/main.c index 4265922..ffb0242 100644 --- a/main.c +++ b/main.c @@ -65,12 +65,6 @@ gint full_screen=1; static GtkWidget *discovery_dialog; -#ifdef __APPLE__ -static sem_t *wisdom_sem; -#else -static sem_t wisdom_sem; -#endif - static GdkCursor *cursor_arrow; static GdkCursor *cursor_watch; @@ -97,16 +91,11 @@ static gint save_cb(gpointer data) { } static pthread_t wisdom_thread_id; +static int wisdom_running=0; static void* wisdom_thread(void *arg) { - fprintf(stderr,"Securing wisdom file in directory: %s\n", (char *)arg); - status_text("Creating FFTW Wisdom file ..."); WDSPwisdom ((char *)arg); -#ifdef __APPLE__ - sem_post(wisdom_sem); -#else - sem_post(&wisdom_sem); -#endif + wisdom_running=0; return NULL; } @@ -161,7 +150,6 @@ gboolean main_delete (GtkWidget *widget) { static int init(void *data) { char *res; char wisdom_directory[1024]; - char wisdom_file[1024]; int rc; fprintf(stderr,"init\n"); @@ -177,25 +165,21 @@ static int init(void *data) { // Let WDSP (via FFTW) check for wisdom file in current dir // If there is one, the "wisdom thread" takes no time // Depending on the WDSP version, the file is wdspWisdom or wdspWisdom00. + // sem_trywait() is not elegant, replaced this with wisdom_running variable. // res=getcwd(wisdom_directory, sizeof(wisdom_directory)); strcpy(&wisdom_directory[strlen(wisdom_directory)],"/"); - strcpy(wisdom_file,wisdom_directory); -#ifdef __APPLE__ - wisdom_sem=sem_open("WISDOM", O_CREAT, 0700, 0); -#else - rc=sem_init(&wisdom_sem, 0, 0); -#endif - rc=pthread_create(&wisdom_thread_id, NULL, wisdom_thread, (void *)wisdom_directory); -#ifdef __APPLE__ - while(sem_trywait(wisdom_sem)<0) { -#else - while(sem_trywait(&wisdom_sem)<0) { -#endif - status_text("WDSP wisdom done."); - while (gtk_events_pending ()) - gtk_main_iteration (); + fprintf(stderr,"Securing wisdom file in directory: %s\n", wisdom_directory); + status_text("Creating FFTW Wisdom file ..."); + wisdom_running=1; + rc=pthread_create(&wisdom_thread_id, NULL, wisdom_thread, wisdom_directory); + while (wisdom_running) { + // wait for the wisdom thread to complete, meanwhile + // handling any GTK events. usleep(100000); // 100ms + while (gtk_events_pending ()) { + gtk_main_iteration (); + } } g_idle_add(ext_discovery,NULL); diff --git a/midi.inp b/midi.inp index 88076ce..0e6ebca 100644 --- a/midi.inp +++ b/midi.inp @@ -5,7 +5,7 @@ # 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 +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 diff --git a/midi2.c b/midi2.c index 377a86e..0d4d300 100644 --- a/midi2.c +++ b/midi2.c @@ -160,9 +160,11 @@ void MIDIstartup() { if (cp) *cp=0; // ignore trailing comment if ((cp = strstr(zeile, "DEVICE="))) { - // Delete trailing blanks and newlines + // Delete comments and trailing blanks cq=cp+7; - while (*cq != 0 && *cq != '\n' && *cq != ' ' && *cq != '\t') cq++; + while (*cq != 0 && *cq != '#') cq++; + *cq--=0; + while (cq > cp+7 && (*cq == ' ' || *cq == '\t')) cq--; *cq=0; register_midi_device(cp+7); continue; // nothing more in this line diff --git a/radio.c b/radio.c index 3c029b4..74a4445 100644 --- a/radio.c +++ b/radio.c @@ -760,6 +760,7 @@ static void rxtx(int state) { SetChannelState(transmitter->id,1,0); tx_set_displaying(transmitter,1); } else { + // switch to rx SetChannelState(transmitter->id,0,1); if(protocol==NEW_PROTOCOL) { schedule_high_priority(); diff --git a/tx_panadapter.c b/tx_panadapter.c index 852220f..7a8d590 100644 --- a/tx_panadapter.c +++ b/tx_panadapter.c @@ -208,25 +208,34 @@ void tx_panadapter_update(TRANSMITTER *tx) { cairo_rectangle(cr, filter_left, 0.0, filter_right-filter_left, (double)display_height); cairo_fill(cr); - // plot the levels - int V = (int)(tx->panadapter_high - tx->panadapter_low); - int numSteps = V / 20; - for (i = 1; i < numSteps; i++) { - int num = tx->panadapter_high - i * 20; - int y = (int)floor((tx->panadapter_high - num) * display_height / V); - cairo_set_source_rgb (cr, 0.0, 1.0, 1.0); - cairo_set_line_width(cr, 1.0); - cairo_move_to(cr,0.0,(double)y); - cairo_line_to(cr,(double)display_width,(double)y); - - cairo_set_source_rgb (cr, 0.0, 1.0, 1.0); - cairo_select_font_face(cr, "FreeMono", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); - cairo_set_font_size(cr, 12); + // plot the levels 0, -20, 40, ... dBm (green line with label) + // also plot gray lines at -10, -30, -50, ... dBm (without label) + double dbm_per_line=(double)display_height/((double)tx->panadapter_high-(double)tx->panadapter_low); + cairo_set_source_rgb (cr, 0.00, 1.00, 1.00); + cairo_set_line_width(cr, 1.0); + cairo_select_font_face(cr, "FreeMono", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size(cr, 12); + + for(i=tx->panadapter_high;i>=tx->panadapter_low;i--) { char v[32]; - sprintf(v,"%d dBm",num); - cairo_move_to(cr, 1, (double)y); - cairo_show_text(cr, v); - cairo_stroke(cr); + if((abs(i)%10) ==0) { + double y = (double)(tx->panadapter_high-i)*dbm_per_line; + if ((abs(i) % 20) == 0) { + cairo_set_source_rgb (cr, 0.00, 1.00, 1.00); + cairo_move_to(cr,0.0,y); + cairo_line_to(cr,(double)display_width,y); + + sprintf(v,"%d dBm",i); + cairo_move_to(cr, 1, y); + cairo_show_text(cr, v); + cairo_stroke(cr); + } else { + cairo_set_source_rgb (cr, 0.25, 0.25, 0.25); + cairo_move_to(cr,0.0,y); + cairo_line_to(cr,(double)display_width,y); + cairo_stroke(cr); + } + } } // plot frequency markers -- 2.45.2