From ac99fd8577cff9c8f98455b8f48914ea38584dd5 Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Thu, 4 Jun 2020 12:54:44 -0400 Subject: [PATCH 01/15] audio interface and audio input selection. Light subsampling removed. Signed-off-by: jakelewis3d --- Makefile.am | 7 +- src/algo.c | 4 +- src/audio.c | 301 +++++++++++++++++++++++++++++++------------ src/audio.h | 49 +++++++ src/audio_settings.c | 271 ++++++++++++++++++++++++++++++++++++++ src/computer.c | 8 +- src/config.c | 6 + src/gtk/gtkHelper.c | 182 ++++++++++++++++++++++++++ src/gtk/gtkHelper.h | 51 ++++++++ src/interface.c | 87 +++++++++++-- src/output_panel.c | 15 ++- src/tg.h | 25 +++- 12 files changed, 906 insertions(+), 100 deletions(-) create mode 100644 src/audio.h create mode 100644 src/audio_settings.c create mode 100644 src/gtk/gtkHelper.c create mode 100644 src/gtk/gtkHelper.h diff --git a/Makefile.am b/Makefile.am index e9ae9cd..58c2a33 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,12 @@ tg_timer_SOURCES = src/algo.c \ src/interface.c \ src/output_panel.c \ src/serializer.c \ - src/tg.h + src/tg.h \ + src/audio_settings.c \ + src/audio.h \ + src/gtk/gtkHelper.c \ + src/gtk/gtkHelper.h \ + tg_timer_dbg_SOURCES = $(tg_timer_SOURCES) tg_timer_prf_SOURCES = $(tg_timer_SOURCES) diff --git a/src/algo.c b/src/algo.c index 1c744cb..77e0f26 100644 --- a/src/algo.c +++ b/src/algo.c @@ -436,10 +436,10 @@ static int compute_period(struct processing_buffers *b, int bph) } new_estimate /= cycle; if(new_estimate < estimate - delta || new_estimate > estimate + delta) { - debug("cycle = %d new_estimate = %f invalid peak\n",cycle,new_estimate/b->sample_rate); + //debug("cycle = %d new_estimate = %f invalid peak\n",cycle,new_estimate/b->sample_rate); return 1; } else - debug("cycle = %d new_estimate = %f\n",cycle,new_estimate/b->sample_rate); + //debug("cycle = %d new_estimate = %f\n",cycle,new_estimate/b->sample_rate); if(inf > b->sample_count / 3) { sum += new_estimate; sq_sum += new_estimate * new_estimate; diff --git a/src/audio.c b/src/audio.c index 05574fe..cdc42c5 100644 --- a/src/audio.c +++ b/src/audio.c @@ -17,6 +17,7 @@ */ #include "tg.h" +#include "audio.h" #include /* Huge buffer of audio */ @@ -25,12 +26,112 @@ int write_pointer = 0; uint64_t timestamp = 0; pthread_mutex_t audio_mutex; +PaStream *stream = NULL; + /* Data for PA callback to use */ static struct callback_info { int channels; //!< Number of channels - bool light; //!< Light algorithm in use, copy half data + //bool light; //!< Light algorithm in use, copy half data } info; + +//audio interfaces +int audio_num_interfaces(){ + return Pa_GetHostApiCount(); +} + +const char * audio_interface_name(PaHostApiIndex i){ + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo(i); + return (hostInfo!=NULL)?hostInfo->name : NULL; +} + +PaHostApiIndex getHostAPIindex(const char* hostAPIname){ + if(hostAPIname != NULL){ + for(PaHostApiIndex i = 0; i < audio_num_interfaces(); i++){ + const char* iname = audio_interface_name(i); + if(iname && strcmp(hostAPIname, iname)==0) + return i; + } + } + return Pa_GetDefaultHostApi(); +} + + + + +//audio inputs +int audio_num_inputs(const char* hostAPIname){ + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo(getHostAPIindex(hostAPIname)); + return (hostInfo)?hostInfo->deviceCount:0; +} + +const char * audio_InputDeviceName(const char* hostAPIname, int hostDevIndex) { + PaDeviceIndex devIndex = Pa_HostApiDeviceIndexToDeviceIndex(getHostAPIindex(hostAPIname), hostDevIndex); + if(devIndex >= 0){ + const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(devIndex); + if(deviceInfo) + return deviceInfo->name; + } + return NULL; +} + + + +static const char * filterDev( PaDeviceIndex devIndex, int nominal_sample_rate){ + const PaDeviceInfo *info = Pa_GetDeviceInfo(devIndex); + if (info->maxInputChannels > 0){ + PaStreamParameters inputParameters; + inputParameters.channelCount = 2; // Stereo + inputParameters.sampleFormat = paFloat32; + // inputParameters.suggestedLatency = ; + inputParameters.hostApiSpecificStreamInfo = NULL; + inputParameters.device = devIndex; + if( nominal_sample_rate < 0 || Pa_IsFormatSupported( &inputParameters, NULL, nominal_sample_rate )) + return info->name; + debug("%d is not supported by dev %s \n", nominal_sample_rate, info->name); + } + return NULL; + } +//filterHostDev + +static gboolean audio_device_supports_rate(int devIndex, int nominal_sr){ + + if(devIndex >= 0){ + return filterDev(devIndex, nominal_sr)!=NULL; + } + return FALSE; +} + + +/* + * returns hostDevIndex +*/ +static PaDeviceIndex audio_deviceIndex(const char *hostAPIname, const char *hostdeviceName){ + PaHostApiIndex hostApiIndex = getHostAPIindex(hostAPIname); + if (!hostdeviceName || !strcmp(hostdeviceName,DEFAULT_AUDIOINPUTSTRING)){ + const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo(hostApiIndex); + if(hostInfo!=NULL){ + return hostInfo->defaultInputDevice; + } + } + int nInputs = audio_num_inputs(hostAPIname); + for(int hostDevIndex=0; hostDevIndexlight) { - static bool even = true; - /* Copy every other sample. It would be much more efficient to - * just drop the sample rate if the sound hardware supports it. - * This would also avoid the aliasing effects that this simple - * decimation without a low-pass filter causes. */ - if(info->channels == 1) { - for(i = even ? 0 : 1; i < frame_count; i += 2) { - pa_buffers[wp++] = input_samples[i]; - if (wp >= PA_BUFF_SIZE) wp -= PA_BUFF_SIZE; - } - } else { - for(i = even ? 0 : 2; i < frame_count*2; i += 4) { - pa_buffers[wp++] = input_samples[i] + input_samples[i+1]; - if (wp >= PA_BUFF_SIZE) wp -= PA_BUFF_SIZE; - } - } - /* Keep track if we have processed an even number of frames, so - * we know if we should drop the 1st or 2nd frame next callback. */ - if(frame_count % 2) even = !even; + + const unsigned len = MIN(frame_count, PA_BUFF_SIZE - wp); + if(info->channels == 1) { + memcpy(pa_buffers + wp, input_samples, len * sizeof(*pa_buffers)); + if(len < frame_count) + memcpy(pa_buffers, input_samples + len, (frame_count - len) * sizeof(*pa_buffers)); } else { - const unsigned len = MIN(frame_count, PA_BUFF_SIZE - wp); - if(info->channels == 1) { - memcpy(pa_buffers + wp, input_samples, len * sizeof(*pa_buffers)); - if(len < frame_count) - memcpy(pa_buffers, input_samples + len, (frame_count - len) * sizeof(*pa_buffers)); - } else { - for(i = 0; i < len; i++) - pa_buffers[wp + i] = input_samples[2u*i] + input_samples[2u*i + 1u]; - if(len < frame_count) - for(i = len; i < frame_count; i++) - pa_buffers[i - len] = input_samples[2u*i] + input_samples[2u*i + 1u]; - } - wp = (wp + frame_count) % PA_BUFF_SIZE; + for(i = 0; i < len; i++) + pa_buffers[wp + i] = input_samples[2u*i] + input_samples[2u*i + 1u]; + if(len < frame_count) + for(i = len; i < frame_count; i++) + pa_buffers[i - len] = input_samples[2u*i] + input_samples[2u*i + 1u]; } + wp = (wp + frame_count) % PA_BUFF_SIZE; + pthread_mutex_lock(&audio_mutex); write_pointer = wp; timestamp += frame_count; @@ -88,41 +169,105 @@ static int paudio_callback(const void *input_buffer, return 0; } -int start_portaudio(int *nominal_sample_rate, double *real_sample_rate) + +void resetBuffers(){ + pthread_mutex_lock(&audio_mutex); + memset(pa_buffers, 0, sizeof(pa_buffers)); + write_pointer = 0; + + timestamp = 0; + + pthread_mutex_unlock(&audio_mutex); +} + +int start_portaudio(int *nominal_sample_rate, double *real_sample_rate, + char* hostdeviceName, char*hostAPIname, + gboolean bShowError) { - PaStream *stream; + + debug("start_portaudio %d %s %s \n", *nominal_sample_rate, hostAPIname, hostdeviceName); + if(pthread_mutex_init(&audio_mutex,NULL)) { error("Failed to setup audio mutex"); return 1; } + + PaError err = Pa_Initialize(); if(err!=paNoError) goto error; -#ifdef DEBUG - if(testing) { - *nominal_sample_rate = PA_SAMPLE_RATE; - *real_sample_rate = PA_SAMPLE_RATE; - goto end; - } -#endif + resetBuffers(); - PaDeviceIndex default_input = Pa_GetDefaultInputDevice(); - if(default_input == paNoDevice) { - error("No default audio input device found"); - return 1; + + PaStreamParameters inputParameters; + inputParameters.channelCount = 2; // Stereo + inputParameters.sampleFormat = paFloat32; + // inputParameters.suggestedLatency = ; + inputParameters.hostApiSpecificStreamInfo = NULL; + inputParameters.device = Pa_GetDefaultInputDevice(); + + debug("Default Input Device %d\n",inputParameters.device); + + + + + PaDeviceIndex selectedDeviceIndex = audio_deviceIndex(hostAPIname, hostdeviceName); + if(selectedDeviceIndex >= 0) + inputParameters.device = selectedDeviceIndex; + + if(inputParameters.device == paNoDevice) { + error("No default audio input device found"); + return 1; } - long channels = Pa_GetDeviceInfo(default_input)->maxInputChannels; + + const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(inputParameters.device); + long long channels = deviceInfo->maxInputChannels; if(channels == 0) { - error("Default audio device has no input channels"); + error(" audio device has no input channels"); return 1; } if(channels > 2) channels = 2; - info.channels = channels; - info.light = false; - err = Pa_OpenDefaultStream(&stream,channels,0,paFloat32,PA_SAMPLE_RATE,paFramesPerBufferUnspecified,paudio_callback,&info); + + if(*nominal_sample_rate == USE_DEVICE_DEFAULT_AUDIORATE){ + *nominal_sample_rate = round(deviceInfo->defaultSampleRate); + if(*nominal_sample_rate > MAX_PA_SAMPLE_RATE) + *nominal_sample_rate = MAX_PA_SAMPLE_RATE; + debug("Using Device defaultSampleRate %d\n", *nominal_sample_rate); + } + + inputParameters.channelCount = info.channels = channels; + + if(inputParameters.device == Pa_GetDefaultInputDevice()){ + debug("Pa_OpenDefaultStream dev:%d channels:%d\n",inputParameters.device, inputParameters.channelCount); + err = Pa_OpenDefaultStream(&stream, + inputParameters.channelCount,0, + inputParameters.sampleFormat, + *nominal_sample_rate, + paFramesPerBufferUnspecified, + paudio_callback, + &info); + + }else{ + + + //if(audioDeviceName != NULL){ + debug("Pa_OpenStream dev:%d channels:%d\n",inputParameters.device, inputParameters.channelCount); + err = Pa_OpenStream(&stream, + &inputParameters, + NULL, // outputParameters + *nominal_sample_rate, + paFramesPerBufferUnspecified, // Frames per buffer + paNoFlag, + paudio_callback, + &info + ); + } + /*if(err==paNoError) + workingAudioDeviceName = g_strdup(audioDeviceName); + */ if(err!=paNoError) goto error; @@ -131,23 +276,39 @@ int start_portaudio(int *nominal_sample_rate, double *real_sample_rate) goto error; const PaStreamInfo *info = Pa_GetStreamInfo(stream); - *nominal_sample_rate = PA_SAMPLE_RATE; *real_sample_rate = info->sampleRate; -#ifdef DEBUG -end: -#endif + + + debug("sample rate: nominal = %d real = %f\n",*nominal_sample_rate,*real_sample_rate); return 0; error: - error("Error opening audio input: %s", Pa_GetErrorText(err)); + + if(bShowError) + error("Error opening audio input: %s", Pa_GetErrorText(err)); + else + debug("Error attempting audio input: %s\n", Pa_GetErrorText(err)); return 1; } int terminate_portaudio() { + debug("Closing portaudio\n"); + + if(stream){ + PaError closeErr = Pa_CloseStream(stream); + if(closeErr != paNoError) { + error("Error closing audio: %s", Pa_GetErrorText(closeErr)); + return 1; + } + } + stream = NULL; + + ///free (lpf); lpf = NULL; //this wasn't working as they may be being used in fill_buffers + ///free (hpf); hpf = NULL; PaError err = Pa_Terminate(); if(err != paNoError) { error("Error closing audio: %s", Pa_GetErrorText(err)); @@ -156,10 +317,10 @@ int terminate_portaudio() return 0; } -uint64_t get_timestamp(int light) +uint64_t get_timestamp() { pthread_mutex_lock(&audio_mutex); - uint64_t ts = light ? timestamp / 2 : timestamp; + uint64_t ts = timestamp; pthread_mutex_unlock(&audio_mutex); return ts; } @@ -171,9 +332,6 @@ static void fill_buffers(struct processing_buffers *ps, int light) int wp = write_pointer; pthread_mutex_unlock(&audio_mutex); - if(light) - ts /= 2; - int i; for(i = 0; i < NSTEPS; i++) { ps[i].timestamp = ts; @@ -225,21 +383,4 @@ int analyze_pa_data_cal(struct processing_data *pd, struct calibration_data *cd) return NSTEPS; } -/** Change to light mode - * - * Call to enable or disable light mode. Changing the mode will empty the audio - * buffer. Nothing will happen if the mode doesn't actually change. - * - * @param light True for light mode, false for normal - */ -void set_audio_light(bool light) -{ - if(info.light != light) { - pthread_mutex_lock(&audio_mutex); - info.light = light; - memset(pa_buffers, 0, sizeof(pa_buffers)); - write_pointer = 0; - timestamp = 0; - pthread_mutex_unlock(&audio_mutex); - } -} + diff --git a/src/audio.h b/src/audio.h new file mode 100644 index 0000000..b38908d --- /dev/null +++ b/src/audio.h @@ -0,0 +1,49 @@ +/* + * audio.h + * + * Created on: May 19, 2020 + * Author: jlewis + */ + +#ifndef SRC_AUDIO_H_ +#define SRC_AUDIO_H_ + +#include "tg.h" +#include + + + +struct calibration_data; + +/* audio.c */ +struct processing_data { + struct processing_buffers *buffers; + uint64_t last_tic; + int is_light; +}; + +void audio_setFiltersFreq(double lpfFreq, double hpfFreq); +int start_portaudio(int *nominal_sample_rate, double *real_sample_rate, char* audioDeviceName, char* audioInterfaceStr, + gboolean bShowError); +int terminate_portaudio(); +void audio_setHPF(double hpfCutOffFreq); +void audio_setLPF( double lpfCutOffFreq); +uint64_t get_timestamp(); +int analyze_pa_data(struct processing_data *pd, int bph, double la, uint64_t events_from); +int analyze_pa_data_cal(struct processing_data *pd, struct calibration_data *cd); +void markFreshData_audio(); +int audio_nValidInputs(char* hostAPIname, int nominal_sample_rate); + +//interfaces +int audio_num_interfaces(); +const char * audio_interface_name(PaHostApiIndex i); + +//devices +int audio_num_inputs(const char* hostAPIname); +const char * audio_InputDeviceName(const char* hostAPIname, int hostDevIndex); +gboolean audio_devicename_supports_rate(const char *hostAPIname, const char* hostDevName, int nominal_sr); +int audio_HostDeviceIndex(char* hostAPIname, const char* audioDeviceName, int nominal_sample_rate); + +gboolean restartPortAudio(); + +#endif /* SRC_AUDIO_H_ */ diff --git a/src/audio_settings.c b/src/audio_settings.c new file mode 100644 index 0000000..cf9cf93 --- /dev/null +++ b/src/audio_settings.c @@ -0,0 +1,271 @@ +/* + * settings.c + * + * Created on: Apr 28, 2020 + * Author: jlewis + */ + + +#include "audio.h" + +#include +#include +#include +#include +#include +#include +#include + + + +void handle_light(GtkToggleButton *b, struct main_window *w); +int sampleRateChange( struct main_window *w, int newSampleRate); +void handle_AudioInputChange (GtkComboBox *audioInputCombo, struct main_window *w); +void handle_SampleRateChange(GtkComboBox *Combo, struct main_window *w); + +GtkWidget *audioSampleRateCombo; +GtkWidget *audioInputCombo; + + + + +static char* fillInputComboBox(char* audioInputStr, struct main_window *w){ + g_signal_handlers_disconnect_by_func(audioInputCombo, G_CALLBACK(handle_AudioInputChange), w); + gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT(audioInputCombo)); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(audioInputCombo), DEFAULT_AUDIOINPUTSTRING); + int active = 0; + int added = 0; + char * activeInputName = NULL; + for (int n=0; n <= audio_num_inputs(w->audioInterfaceStr); n++) { + const char * inputname = audio_InputDeviceName(w->audioInterfaceStr, n); + if(inputname!=NULL){ + if(audio_devicename_supports_rate(w->audioInterfaceStr, inputname, -1)) + { + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(audioInputCombo), inputname); + added++; + if (strcmp(audioInputStr, inputname) == 0){ + active= added; + activeInputName=strdup(inputname); + } + } + } + } + gtk_combo_box_set_active(GTK_COMBO_BOX(audioInputCombo), active); + g_signal_connect (audioInputCombo, "changed", G_CALLBACK(handle_AudioInputChange), w); + return activeInputName; +} + +static void fillRatesComboBox(char*activeAudioInput, struct main_window *w){ + g_signal_handlers_disconnect_by_func(audioSampleRateCombo, G_CALLBACK(handle_SampleRateChange), w); + gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT(audioSampleRateCombo)); + //if(activeAudioInput && strcmp(activeAudioInput, DEFAULT_AUDIOINPUTSTRING)==0) + // activeAudioInput = NULL; + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(audioSampleRateCombo), DEFAULT_AUDIORATESTRING, DEFAULT_AUDIORATESTRING); + + int sampleRates[] = {48000, 44100, 32768, 24000, 22050, 16384, 11025}; + int activeSampleIndex = 0; + int added = 0; + for (int n=0; n < sizeof(sampleRates)/sizeof(sampleRates[0]); n++) { + int sampleRate = sampleRates[n]; + if( audio_devicename_supports_rate(w->audioInterfaceStr, activeAudioInput, sampleRate)>=0 ){ + char sampleRateString[1024]; + sprintf(sampleRateString,"%d", sampleRate); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(audioSampleRateCombo), sampleRateString, sampleRateString); + added++; + if (w->nominal_sr == sampleRate) + activeSampleIndex=added; + + } + } + gtk_combo_box_set_active(GTK_COMBO_BOX(audioSampleRateCombo), activeSampleIndex); // Fill in value from settings + g_signal_connect (audioSampleRateCombo, "changed", G_CALLBACK(handle_SampleRateChange), w); + +} + +void handle_AudioInterfaceChange(GtkComboBox *audioInterfaceCombo, struct main_window *w){ + char* audioInterfaceName = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(audioInterfaceCombo)); + terminate_portaudio(); + double real_sr; + char *useAudioInterfaceName = audioInterfaceName; + + + w->nominal_sr = USE_DEVICE_DEFAULT_AUDIORATE; + start_portaudio(&w->nominal_sr, &real_sr, DEFAULT_AUDIOINPUTSTRING, useAudioInterfaceName, TRUE); + + // useAudioInterfaceName = DEFAULT_AUDIOINTERFACESTRING; + + + + g_free(w->audioInterfaceStr); + w->audioInterfaceStr = g_strdup(audioInterfaceName); + g_free(w->audioInputStr); + w->audioInputStr = g_strdup(DEFAULT_AUDIOINPUTSTRING); + save_config(w); + + char* audioInputStr = fillInputComboBox(DEFAULT_AUDIOINPUTSTRING, w); + fillRatesComboBox(audioInputStr, w); + g_free(audioInputStr); + + g_free(audioInterfaceName); +} + +void handle_AudioInputChange (GtkComboBox *audioInputCombo, struct main_window *w){ + char* audioInputName = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(audioInputCombo)); + if(audioInputName){ + terminate_portaudio(); + double real_sr; + char * useAudioInputName = audioInputName; + w->nominal_sr = USE_DEVICE_DEFAULT_AUDIORATE; + int nTry = 2; + for(int i=0;inominal_sr, &real_sr, useAudioInputName, w->audioInterfaceStr, i==nTry-1)==0){ + g_free(w->audioInputStr); w->audioInputStr = g_strdup(useAudioInputName); + fillRatesComboBox(w->audioInputStr, w); + save_config(w); + break; + } + if(i==1) useAudioInputName = DEFAULT_AUDIOINPUTSTRING; + } + g_free(audioInputName); + } +} + + + + +void handle_SampleRateChange(GtkComboBox *Combo, struct main_window *w){ + char* sampleRateString = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(Combo)); + int sampleRate = PA_SAMPLE_RATE; + if(sampleRateString != NULL){ + if( strcmp(sampleRateString,DEFAULT_AUDIORATESTRING)==0){ + sampleRate = USE_DEVICE_DEFAULT_AUDIORATE; + }else{ + sscanf(sampleRateString, "%d", &sampleRate); + } + sampleRateChange(w, sampleRate); + gchar id[1024]; + sprintf(id,"%d",w->nominal_sr); + if(!gtk_combo_box_set_active_id (GTK_COMBO_BOX(Combo), id)){ + //add it on if not already there. + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(Combo), id, id); + gtk_combo_box_set_active_id (GTK_COMBO_BOX(Combo), id); + } + } +} + + + + +/* Display the Settings dialog */ +//Thanks to Rob Wahlstedt for this code ;) https://github.com/wahlstedt/tg +void show_preferences(GtkButton *button, struct main_window *w) { + GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT /* | GTK_DIALOG_MODAL*/; + GtkWidget *dialog = gtk_dialog_new_with_buttons("Audio Settings", + GTK_WINDOW(w->window), + flags, + // ("Cancel"), GTK_RESPONSE_CANCEL, + "Done", GTK_RESPONSE_ACCEPT, + NULL); + gtk_window_set_modal (GTK_WINDOW(dialog), FALSE); //not working on Windows - it's still modal + //gtk_window_set_transient_for (GTK_WINDOW(dialog), GTK_WINDOW(w->window)); + // gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); // Non-resizable + + int yPos = 0; + + GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_set_border_width(GTK_CONTAINER(content_area), 5); + GtkWidget *prefs_grid = gtk_grid_new(); + gtk_grid_set_column_spacing(GTK_GRID(prefs_grid), 6); + gtk_grid_set_row_spacing(GTK_GRID(prefs_grid), 4); + gtk_container_set_border_width(GTK_CONTAINER(prefs_grid), 10); + gtk_widget_set_halign(prefs_grid, GTK_ALIGN_CENTER); // Keep grid centered in case user resizes window + gtk_container_add(GTK_CONTAINER(content_area), prefs_grid); // Add the grid to the dialog + + + //audio interface + GtkWidget *interface_label = gtk_label_new("Audio interface:"); + gtk_widget_set_tooltip_text(interface_label, "What audio interface to connect to"); + gtk_widget_set_halign(interface_label, GTK_ALIGN_START); // Right aligned + gtk_grid_attach(GTK_GRID(prefs_grid), interface_label, 0,yPos++,1,1); + + GtkWidget *audioInterfaceCombo = gtk_combo_box_text_new(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(audioInterfaceCombo), DEFAULT_AUDIOINTERFACESTRING); + int activeInterface = 0; + //char * activeInputName = NULL; + int added = 0; + for (int n=0; n <= audio_num_interfaces(); n++) { + const char * interfacename = audio_interface_name(n); + if(interfacename!=NULL){ + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(audioInterfaceCombo), interfacename); + added++; + if (strcmp(w->audioInterfaceStr, interfacename) == 0){ + activeInterface=added; + //activeInputName=strdup(interfacename); + } + + } + } + gtk_combo_box_set_active(GTK_COMBO_BOX(audioInterfaceCombo), activeInterface); // Fill in value from settings + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), audioInterfaceCombo, interface_label, GTK_POS_RIGHT, 2, 1); + g_signal_connect (audioInterfaceCombo, "changed", G_CALLBACK(handle_AudioInterfaceChange), w); + + //audio input + GtkWidget *input_label = gtk_label_new("Audio input:"); + gtk_widget_set_tooltip_text(input_label, "What audio input to listen to"); + gtk_widget_set_halign(input_label, GTK_ALIGN_START); // Right aligned + gtk_grid_attach(GTK_GRID(prefs_grid), input_label, 0,yPos++,1,1); + + + audioInputCombo = gtk_combo_box_text_new(); + char* activeInputName = fillInputComboBox(w->audioInputStr, w); + + + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), audioInputCombo, input_label, GTK_POS_RIGHT, 2, 1); + + + + //sample rate + GtkWidget *sample_label = gtk_label_new("Sample Rate:"); + gtk_widget_set_tooltip_text(sample_label, "Higher gives better quality, but requires more computational power"); + gtk_widget_set_halign(sample_label, GTK_ALIGN_START); // Right aligned + gtk_grid_attach(GTK_GRID(prefs_grid), sample_label, 0,yPos++,1,1); + + audioSampleRateCombo = gtk_combo_box_text_new(); + fillRatesComboBox(activeInputName, w); + + if(activeInputName!=NULL) + free(activeInputName); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), audioSampleRateCombo, sample_label, GTK_POS_RIGHT, 2, 1); + + + + //check boxes + + int xpos = 0; int xwidth = 2; + + + gtk_grid_attach(GTK_GRID(prefs_grid), + addCheckButton("Light algorithm", w->is_light, G_CALLBACK(handle_light), w), + xpos, yPos, xwidth, 1); + + + + + + gtk_widget_show_all(dialog); + + int res = gtk_dialog_run(GTK_DIALOG(dialog)); // Show the dialog + + if (res == GTK_RESPONSE_ACCEPT) { + // Save the dialog data in the settings variable and then save it out to disk + //w->conf.audio_input = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(input)); + //w->conf.rate_adjustment = atof(gtk_entry_get_text(GTK_ENTRY(adjustment))); + //w->conf.precision_mode = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cpu)); + //w->conf.dark_theme = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dark)); + + //save_settings(&w->conf); + } + + // Get rid of the dialog + gtk_widget_destroy(dialog); +} diff --git a/src/computer.c b/src/computer.c index 03942d3..6d9ff55 100644 --- a/src/computer.c +++ b/src/computer.c @@ -17,6 +17,7 @@ */ #include "tg.h" +#include "audio.h" static int count_events(struct snapshot *s) { @@ -122,7 +123,7 @@ static void compute_events_cal(struct computer *c) s->events[s->events_wp] = d->events[i]; debug("event at %llu\n",s->events[s->events_wp]); } - s->events_from = get_timestamp(s->is_light); + s->events_from = get_timestamp(); } static void compute_events(struct computer *c) @@ -140,7 +141,7 @@ static void compute_events(struct computer *c) } s->events_from = p->timestamp - ceil(p->period); } else { - s->events_from = get_timestamp(s->is_light); + s->events_from = get_timestamp(); } } @@ -235,8 +236,7 @@ void computer_destroy(struct computer *c) struct computer *start_computer(int nominal_sr, int bph, double la, int cal, int light) { - if(light) nominal_sr /= 2; - set_audio_light(light); + struct processing_buffers *p = malloc(NSTEPS * sizeof(struct processing_buffers)); int first_step = light ? FIRST_STEP_LIGHT : FIRST_STEP; diff --git a/src/config.c b/src/config.c index 18d1ee9..29c11ce 100644 --- a/src/config.c +++ b/src/config.c @@ -75,6 +75,9 @@ void load_config(struct main_window *w) } CONFIG_FIELDS(LOAD); + + w->audioInputStr = g_key_file_get_string(w->config_file, "tg", "audioInputStr", NULL); + w->audioInterfaceStr= g_key_file_get_string(w->config_file, "tg", "audioInterfaceStr", NULL); } void save_config(struct main_window *w) @@ -89,6 +92,9 @@ void save_config(struct main_window *w) CONFIG_FIELDS(SAVE); + g_key_file_set_string(w->config_file, "tg", "audioInputStr", w->audioInputStr); + g_key_file_set_string(w->config_file, "tg", "audioInterfaceStr", w->audioInterfaceStr); + #ifdef DEBUG GError *ge = NULL; g_key_file_save_to_file(w->config_file, w->config_file_name, &ge); diff --git a/src/gtk/gtkHelper.c b/src/gtk/gtkHelper.c new file mode 100644 index 0000000..cc68e5d --- /dev/null +++ b/src/gtk/gtkHelper.c @@ -0,0 +1,182 @@ +/* + * gtkHelper.c + * + * Created on: Oct 24, 2019 + * Author: jlewis + * This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include "gtk/gtkHelper.h" + + +//this is stored here as it issues a compiler warning +void setWidgetColor(GtkWidget *widget, cairo_pattern_t *cairocolor){ + double red,green,blue,alpha; + cairo_pattern_get_rgba (cairocolor, + &red,&green,&blue,&alpha); + char rgbaStr[1024]; + sprintf(rgbaStr, "rgba(%f,%f,%f,%f)", 255*red, 255*green, 255*blue, alpha); + + GdkRGBA gdkColor; + + gdk_rgba_parse (&gdkColor, rgbaStr); + gtk_widget_override_color (widget, GTK_STATE_FLAG_NORMAL, &gdkColor); +} + + + +// menus +GtkWidget *addSubMenu(const char* label, GtkWidget *parent_menu){ + GtkWidget *custom_item = gtk_menu_item_new_with_label(label); + gtk_menu_shell_append(GTK_MENU_SHELL(parent_menu), custom_item); + + GtkWidget *custom_menu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM (custom_item), custom_menu); + return custom_menu; +} + +GtkWidget* addMenuItem(const char *label, GtkWidget *parent_menu, GCallback activateCallback, gpointer callbackData ){ + GtkWidget* menuItem = gtk_menu_item_new_with_label(label); + gtk_menu_shell_append(GTK_MENU_SHELL(parent_menu), menuItem); + if(activateCallback) + g_signal_connect(menuItem, "activate", G_CALLBACK(activateCallback), callbackData); + return menuItem; +} + +void addMenuSeperator(GtkWidget *parent_menu){ + gtk_menu_shell_append(GTK_MENU_SHELL(parent_menu), gtk_separator_menu_item_new()); +} + + +GtkWidget *addMenuCheckButton(const char *label, GtkWidget *parent_menu, gboolean bInitialState, + GCallback toggleCallback, gpointer callbackData){ + + GtkWidget* chkBtn = gtk_check_menu_item_new_with_label(label); + gtk_menu_shell_append(GTK_MENU_SHELL(parent_menu), chkBtn); + g_signal_connect(chkBtn, "toggled", G_CALLBACK(toggleCallback), callbackData); + gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(chkBtn), bInitialState); + + return chkBtn; +} + +/// Scale +GtkWidget *addScaleCallback(const gchar*name, double min, double max, double value, GCallback changedCallback, gpointer callbackData ){ + GtkScale* scale = GTK_SCALE(gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, + min, max, 1)); + //gtk_scale_set_draw_value(scale, TRUE); the default anyway + gtk_range_set_value (GTK_RANGE(scale), value); + g_signal_connect(scale, "value_changed", changedCallback, callbackData); + gtk_widget_show (GTK_WIDGET(scale)); + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); + if(name!=NULL){ + gtk_widget_set_name(GTK_WIDGET(scale), name); + GtkWidget *label = gtk_label_new(name); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + } + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(scale), TRUE, TRUE, 0); + + return hbox; +} + +void setScaleValue(GtkWidget* hbox, double value){ + if(GTK_IS_CONTAINER(hbox)) { + GList *children = gtk_container_get_children(GTK_CONTAINER(hbox)); + GList * child = children; + while(child != NULL){ + if(GTK_IS_RANGE(child->data)){ + gtk_range_set_value (GTK_RANGE(child->data), value); + child = NULL; + }else{ + child = child->next;} + } + + } + +} + + +static void handle_Range_value_changed(GtkRange *r, gpointer _value){ + double * value = _value; + *value = gtk_range_get_value (r); + //debug("Range %s: %lf", (gtk_widget_get_name(GTK_WIDGET(r))), *value); +} + +GtkWidget *addScale(const gchar*name, double min, double max, double* value){ + return addScaleCallback(name, min, max, *value, G_CALLBACK(handle_Range_value_changed), value); +} + + +// spin button + +GtkWidget * createSpinButtonContainer(const gchar*name, + double min, double max, + double step, double initValue, + GCallback changedCallback, gpointer callbackData + ){ + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); + GtkWidget * spin_button = gtk_spin_button_new_with_range(min, max, step); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin_button), initValue); + g_signal_connect(spin_button, "value_changed", G_CALLBACK(changedCallback), callbackData); + + if(name!=NULL){ + gtk_widget_set_name(GTK_WIDGET(spin_button), name); + GtkWidget *label = gtk_label_new(name); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + } + + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(spin_button), TRUE, TRUE, 0); + + return hbox; +} + +GtkWidget *getSpinButton(GtkWidget *spinButtonContainer){ + if(GTK_IS_SPIN_BUTTON(spinButtonContainer)) //in case of confusion + return spinButtonContainer; + if(GTK_IS_CONTAINER(spinButtonContainer)) { + GList *children = gtk_container_get_children(GTK_CONTAINER(spinButtonContainer)); + GList * child = children; + while(child != NULL){ + if(GTK_IS_SPIN_BUTTON(child->data)){ + return child->data; + }else{ + child = child->next;} + } + } + + return NULL; +} + +//checkbuttons +GtkWidget *addCheckButton(const char *label, gboolean bInitialState, + GCallback toggleCallback, gpointer callbackData){ + + GtkWidget* chkBtn = gtk_check_button_new_with_label(label); + g_signal_connect(chkBtn, "toggled", G_CALLBACK(toggleCallback), callbackData); + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(chkBtn), bInitialState); + return chkBtn; +} + +static void handle_BooleanToggle( GtkToggleButton *b, gboolean *iBoolean){ + *iBoolean = gtk_toggle_button_get_active(b); + +} + + +GtkWidget *addCheckButtonBoolean(const char *label, gboolean *iBoolean){ + + return addCheckButton(label, *iBoolean, G_CALLBACK(handle_BooleanToggle), (gpointer) iBoolean); +} + + diff --git a/src/gtk/gtkHelper.h b/src/gtk/gtkHelper.h new file mode 100644 index 0000000..ce70dca --- /dev/null +++ b/src/gtk/gtkHelper.h @@ -0,0 +1,51 @@ +/* + * #include + * + * Created on: Oct 24, 2019 + * Author: jlewis + * This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef SRC_GTK_GTKHELPER_H_ +#define SRC_GTK_GTKHELPER_H_ + +#include +void setWidgetColor(GtkWidget *widget, cairo_pattern_t *cairocolor); + +//check button +GtkWidget *addCheckButton(const char *label, gboolean bInitialState, GCallback toggleCallback, gpointer callbackData); +GtkWidget *addCheckButtonBoolean(const char *label, gboolean *iBoolean); + +GtkWidget *addSubMenu( const char* label, GtkWidget *parent_menu); +GtkWidget* addMenuItem(const char *label, GtkWidget *parent_menu, + GCallback activateCallback, gpointer callbackData); +GtkWidget *addMenuCheckButton(const char *label, GtkWidget *parent_menu, gboolean bInitialState, + GCallback toggleCallback, gpointer callbackData); +void addMenuSeperator(GtkWidget *parent_menu); + + +//scale , range +GtkWidget *addScaleCallback(const gchar*name, double min, double max, double value, GCallback changedCallback, gpointer callbackData ); +GtkWidget *addScale(const gchar*name, double min, double max, double* value); +void setScaleValue(GtkWidget* hbox, double value); + +//spin +GtkWidget * createSpinButtonContainer(const gchar*name, + double min, double max, + double step, double initValue, + GCallback changedCallback, gpointer callbackData + ); +GtkWidget *getSpinButton(GtkWidget *spinButtonContainer); + +#endif /* SRC_GTK_GTKHELPER_H_ */ diff --git a/src/interface.c b/src/interface.c index 7c1d3ca..12a6888 100644 --- a/src/interface.c +++ b/src/interface.c @@ -17,6 +17,8 @@ */ #include "tg.h" +#include "gtk/gtkHelper.h" + #include #include #include @@ -37,6 +39,17 @@ void print_debug(char *format,...) va_end(args); } + +void console(char *format,...)//this is better for eclipse development +{ +#ifdef DEBUG + va_list args; + va_start(args,format); + vfprintf(stdout,format,args); + va_end(args); +#endif +} + void error(char *format,...) { char s[100]; @@ -229,7 +242,8 @@ static void recompute(struct main_window *w) w->computer_timeout = 0; lock_computer(w->computer); if(w->computer->recompute >= 0) { - if(w->is_light != w->computer->actv->is_light) { + if(w->is_light != w->computer->actv->is_light || + w->nominal_sr != w->computer->actv->nominal_sr) { kill_computer(w); } else { w->computer->bph = w->bph; @@ -261,15 +275,38 @@ static void handle_calibrate(GtkCheckMenuItem *b, struct main_window *w) } } -static void handle_light(GtkCheckMenuItem *b, struct main_window *w) +//audio_settings uses a button not menu item. +//void handle_light(GtkCheckMenuItem *b, struct main_window *w) +//{ +// int button_state = gtk_check_menu_item_get_active(b) == TRUE; + +void handle_light(GtkToggleButton *b, struct main_window *w) { - int button_state = gtk_check_menu_item_get_active(b) == TRUE; + int button_state = gtk_toggle_button_get_active(b) == TRUE; if(button_state != w->is_light) { w->is_light = button_state; recompute(w); } } +int sampleRateChange( struct main_window *w, int newSampleRate){ + if(newSampleRate != w->nominal_sr){ + debug("sampleRateChange %d to %d\n", w->nominal_sr, newSampleRate); + + terminate_portaudio(); + + w->nominal_sr = newSampleRate; + double real_sr; + if(start_portaudio(&w->nominal_sr, &real_sr, w->audioInputStr, w->audioInterfaceStr, FALSE)){ + //if fails, try the default rate + w->nominal_sr = USE_DEVICE_DEFAULT_AUDIORATE; //will be set to actual default value in the method below + start_portaudio(&w->nominal_sr, &real_sr, w->audioInputStr, w->audioInterfaceStr, TRUE); + } + recompute(w); + } + return w->nominal_sr; +} + static void controls_active(struct main_window *w, int active) { w->controls_active = active; @@ -440,7 +477,7 @@ static void handle_snapshot(GtkButton *b, struct main_window *w) UNUSED(b); if(w->active_snapshot->calibrate) return; struct snapshot *s = snapshot_clone(w->active_snapshot); - s->timestamp = get_timestamp(s->is_light); + s->timestamp = get_timestamp(); add_new_tab(s, NULL, w); } @@ -570,7 +607,7 @@ static void save_current(GtkMenuItem *m, struct main_window *w) snapshot = snapshot_clone(snapshot); if(!snapshot->timestamp) - snapshot->timestamp = get_timestamp(snapshot->is_light); + snapshot->timestamp = get_timestamp(); FILE *f = choose_file_for_save(w, "Save current display", name); @@ -799,6 +836,16 @@ static void init_main_window(struct main_window *w) gtk_menu_button_set_popup(GTK_MENU_BUTTON(command_menu_button), command_menu); gtk_box_pack_end(GTK_BOX(hbox), command_menu_button, FALSE, FALSE, 0); + + //Audio + + GtkWidget *restartAudio_item = gtk_menu_item_new_with_label("Audio Settings"); + gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), restartAudio_item); + g_signal_connect(restartAudio_item, "activate", G_CALLBACK(show_preferences), w); + + addMenuSeperator(command_menu); + + // ... Open GtkWidget *open_item = gtk_menu_item_new_with_label("Open"); gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), open_item); @@ -818,12 +865,13 @@ static void init_main_window(struct main_window *w) gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), gtk_separator_menu_item_new()); + /* moved to settings.c // ... Light checkbox GtkWidget *light_checkbox = gtk_check_menu_item_new_with_label("Light algorithm"); gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), light_checkbox); g_signal_connect(light_checkbox, "toggled", G_CALLBACK(handle_light), w); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(light_checkbox), w->is_light); - +*/ // ... Calibrate checkbox w->cal_button = gtk_check_menu_item_new_with_label("Calibrate"); gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), w->cal_button); @@ -917,10 +965,11 @@ static void start_interface(GApplication* app, void *p) struct main_window *w = malloc(sizeof(struct main_window)); - if(start_portaudio(&w->nominal_sr, &real_sr)) { + /*moved to after load_config + if(start_portaudio(&w->nominal_sr, &real_sr)) { g_application_quit(app); return; - } + }*/ w->app = GTK_APPLICATION(app); @@ -932,8 +981,30 @@ static void start_interface(GApplication* app, void *p) w->calibrate = 0; w->is_light = 0; + w->nominal_sr = PA_SAMPLE_RATE; + w->audioInputStr = g_strdup(DEFAULT_AUDIOINPUTSTRING); + w->audioInterfaceStr= g_strdup(DEFAULT_AUDIOINTERFACESTRING); + load_config(w); + + + if(start_portaudio(&w->nominal_sr, &real_sr, w->audioInputStr, w->audioInterfaceStr, FALSE)) { + //if it fails, try the default input + g_free(w->audioInputStr); w->audioInputStr = g_strdup(DEFAULT_AUDIOINPUTSTRING); + if(start_portaudio(&w->nominal_sr, &real_sr, w->audioInputStr, w->audioInterfaceStr, FALSE)) { + //if that fails, try the default interface + g_free(w->audioInterfaceStr); w->audioInterfaceStr = g_strdup(DEFAULT_AUDIOINTERFACESTRING); + if(start_portaudio(&w->nominal_sr, &real_sr, w->audioInputStr, w->audioInterfaceStr, TRUE)){ + //if that fails, quit. + g_application_quit(app); + return; + } + + } + } + + if(w->la < MIN_LA || w->la > MAX_LA) w->la = DEFAULT_LA; if(w->bph < MIN_BPH || w->bph > MAX_BPH) w->bph = 0; if(w->cal < MIN_CAL || w->cal > MAX_CAL) diff --git a/src/output_panel.c b/src/output_panel.c index 0e7fc8b..b0e6b03 100644 --- a/src/output_panel.c +++ b/src/output_panel.c @@ -112,7 +112,7 @@ static double amplitude_to_time(double lift_angle, double amp) return asin(lift_angle / (2 * amp)) / M_PI; } -static double draw_watch_icon(cairo_t *c, int signal, int happy, int light) +static double draw_watch_icon(cairo_t *c, int signal, int happy, int light, int sampleRate) { happy = !!happy; cairo_set_line_width(c,3); @@ -148,6 +148,15 @@ static double draw_watch_icon(cairo_t *c, int signal, int happy, int light) cairo_line_to(c, OUTPUT_WINDOW_HEIGHT * 0.5 + 0.3*l, OUTPUT_WINDOW_HEIGHT * 0.2 + l + 1); cairo_stroke(c); } + + cairo_set_font_size(c, 12); + char sampleRateStr[1024]; sprintf(sampleRateStr,"%d", sampleRate ); + cairo_text_extents_t extents; + cairo_text_extents(c,sampleRateStr,&extents); + cairo_move_to(c, (OUTPUT_WINDOW_HEIGHT - extents.width )* 0.5 , OUTPUT_WINDOW_HEIGHT * 0.7); + + cairo_show_text(c,sampleRateStr); + return OUTPUT_WINDOW_HEIGHT + 3*l; } @@ -194,7 +203,7 @@ static gboolean output_draw_event(GtkWidget *widget, cairo_t *c, struct output_p struct processing_buffers *p = snst->pb; int old = snst->is_old; - double x = draw_watch_icon(c,snst->signal,snst->calibrate ? snst->signal==NSTEPS : snst->signal, snst->is_light); + double x = draw_watch_icon(c,snst->signal,snst->calibrate ? snst->signal==NSTEPS : snst->signal, snst->is_light, snst->nominal_sr); cairo_text_extents_t extents; @@ -530,7 +539,7 @@ static gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *c, struct outp { int i; struct snapshot *snst = op->snst; - uint64_t time = snst->timestamp ? snst->timestamp : get_timestamp(snst->is_light); + uint64_t time = snst->timestamp ? snst->timestamp : get_timestamp(); double sweep; int zoom_factor; double slope = 1000; // detected rate: 1000 -> do not display diff --git a/src/tg.h b/src/tg.h index 789f66c..8907b73 100644 --- a/src/tg.h +++ b/src/tg.h @@ -16,6 +16,9 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#ifndef SRC_TG_H_ +#define SRC_TG_H_ + #include #include #include @@ -27,6 +30,7 @@ #include #include #include +#include "audio.h" #ifdef __CYGWIN__ #define _WIN32 @@ -43,7 +47,8 @@ #define NSTEPS 4 #define PA_SAMPLE_RATE 44100u -#define PA_BUFF_SIZE (PA_SAMPLE_RATE << (NSTEPS + FIRST_STEP)) +#define MAX_PA_SAMPLE_RATE 48000u +#define PA_BUFF_SIZE (MAX_PA_SAMPLE_RATE << (NSTEPS + FIRST_STEP)) #define OUTPUT_FONT 40 #define OUTPUT_WINDOW_HEIGHT 70 @@ -118,7 +123,7 @@ void cal_data_destroy(struct calibration_data *cd); int test_cal(struct processing_buffers *p); int process_cal(struct processing_buffers *p, struct calibration_data *cd); -/* audio.c */ +/* audio.c moved to audio.h struct processing_data { struct processing_buffers *buffers; uint64_t last_tic; @@ -131,6 +136,8 @@ uint64_t get_timestamp(int light); int analyze_pa_data(struct processing_data *pd, int bph, double la, uint64_t events_from); int analyze_pa_data_cal(struct processing_data *pd, struct calibration_data *cd); void set_audio_light(bool light); +*/ + /* computer.c */ struct snapshot { @@ -256,6 +263,10 @@ struct main_window { guint kick_timeout; guint save_timeout; + + char * audioInputStr; + char * audioInterfaceStr; + }; extern int preset_bph[]; @@ -267,6 +278,14 @@ extern int testing; void print_debug(char *format,...); void error(char *format,...); + +//settings.c +void show_preferences(GtkButton *button, struct main_window *w); +#define DEFAULT_AUDIOINPUTSTRING "Default Audio Input" +#define DEFAULT_AUDIOINTERFACESTRING "Default Audio Interface" +#define DEFAULT_AUDIORATESTRING "Default Sample Rate" +#define USE_DEVICE_DEFAULT_AUDIORATE 0 //passes control of the sample rate to the device itself + /* config.c */ #define CONFIG_FIELDS(OP) \ OP(bph, bph, int) \ @@ -287,3 +306,5 @@ void close_config(struct main_window *w); /* serializer.c */ int write_file(FILE *f, struct snapshot **s, char **names, uint64_t cnt); int read_file(FILE *f, struct snapshot ***s, char ***names, uint64_t *cnt); + +#endif /* SRC_TG_H_ */ From 7392757ad318acabbc8f935f7ac6855eb13fb85f Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Thu, 4 Jun 2020 13:28:13 -0400 Subject: [PATCH 02/15] removed round() methods from inner loops to outer loops in compute_phase and compute_waveform These round() methods were profiling at 5%! Signed-off-by: jakelewis3d --- src/algo.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/algo.c b/src/algo.c index 77e0f26..82ce3e8 100644 --- a/src/algo.c +++ b/src/algo.c @@ -18,6 +18,8 @@ #include "tg.h" +#define PERIOD_SHIFT 16 + struct filter { double a0,a1,a2,b1,b2; }; @@ -542,17 +544,20 @@ static float tmean(float *x, int n) static void compute_phase(struct processing_buffers *p, double period) { - int i; + long i; double x = 0, y = 0; + long long periodShifted = lround(period * (1L<waveform[i] = 0; + + float total = 0.0; for(j=0;;j++) { - int n = round(i + j * period); - if(n >= p->sample_count) break; - p->waveform[i] += p->samples[n]; + n = i+((j * periodShifted) >> PERIOD_SHIFT); + if(n >= p->sample_count) + break; + total += p->samples[n]; } - p->waveform[i] /= j; + p->waveform[i] = total / j; } for(i=0; isample_rate; i++) p->waveform[i] = 0; + float bin[(int)ceil(1 + p->sample_count / wf_size)]; for(i=0; i < wf_size; i++) { - float bin[(int)ceil(1 + p->sample_count / wf_size)]; int j; - double k = fmod(i+p->phase,wf_size); + int k = round(fmod(i+p->phase,wf_size)); //moving the round() here speeds up the loop below for(j=0;;j++) { - int n = round(k+j*wf_size); + int n = k+j*wf_size; if(n >= p->sample_count) break; bin[j] = p->samples[n]; } From c608e170c2ff50f6e086176f2ea261ad9b3aa106 Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Thu, 4 Jun 2020 13:52:02 -0400 Subject: [PATCH 03/15] SIMD alignment of all fftw buffers. http://www.fftw.org/doc/SIMD-alignment-and-fftw_005fmalloc.html#SIMD-alignment-and-fftw_005fmalloc Signed-off-by: jakelewis3d --- src/algo.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/algo.c b/src/algo.c index 82ce3e8..7383b41 100644 --- a/src/algo.c +++ b/src/algo.c @@ -69,16 +69,16 @@ static void run_filter(struct filter *f, float *buff, int size) void setup_buffers(struct processing_buffers *b) { b->samples = fftwf_malloc(2 * b->sample_count * sizeof(float)); - b->samples_sc = malloc(2 * b->sample_count * sizeof(float)); - b->waveform = malloc(2 * b->sample_rate * sizeof(float)); - b->waveform_sc = malloc(2 * b->sample_rate * sizeof(float)); + b->samples_sc = fftwf_malloc(2 * b->sample_count * sizeof(float)); + b->waveform = fftwf_malloc(2 * b->sample_rate * sizeof(float)); + b->waveform_sc = fftwf_malloc(2 * b->sample_rate * sizeof(float)); b->fft = fftwf_malloc((b->sample_count + 1) * sizeof(fftwf_complex)); b->sc_fft = fftwf_malloc((b->sample_count + 1) * sizeof(fftwf_complex)); b->tic_wf = fftwf_malloc(b->sample_rate * sizeof(float)); b->slice_wf = fftwf_malloc(b->sample_rate * sizeof(float)); b->tic_fft = fftwf_malloc((b->sample_rate/2 + 1) * sizeof(fftwf_complex)); b->slice_fft = fftwf_malloc((b->sample_rate/2 + 1) * sizeof(fftwf_complex)); - b->tic_c = malloc(2 * b->sample_count * sizeof(float)); + b->tic_c = fftwf_malloc(2 * b->sample_count * sizeof(float)); b->plan_a = fftwf_plan_dft_r2c_1d(2 * b->sample_count, b->samples, b->fft, FFTW_ESTIMATE); b->plan_b = fftwf_plan_dft_c2r_1d(2 * b->sample_count, b->sc_fft, b->samples_sc, FFTW_ESTIMATE); b->plan_c = fftwf_plan_dft_r2c_1d(2 * b->sample_rate, b->waveform, b->sc_fft, FFTW_ESTIMATE); @@ -101,16 +101,16 @@ void setup_buffers(struct processing_buffers *b) void pb_destroy(struct processing_buffers *b) { fftwf_free(b->samples); - free(b->samples_sc); - free(b->waveform); - free(b->waveform_sc); + fftwf_free(b->samples_sc); + fftwf_free(b->waveform); + fftwf_free(b->waveform_sc); fftwf_free(b->fft); fftwf_free(b->sc_fft); fftwf_free(b->tic_wf); fftwf_free(b->slice_wf); fftwf_free(b->tic_fft); fftwf_free(b->slice_fft); - free(b->tic_c); + fftwf_free(b->tic_c); fftwf_destroy_plan(b->plan_a); fftwf_destroy_plan(b->plan_b); fftwf_destroy_plan(b->plan_c); From acb26019fd5d5bb412af40658c1242f635db58b8 Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Thu, 4 Jun 2020 16:54:17 -0400 Subject: [PATCH 04/15] cyan / magenta colored events a waveforms for tic / toc Signed-off-by: jakelewis3d --- src/algo.c | 45 ++++++++++++++++++++++++++++++++------------- src/computer.c | 18 +++++++++--------- src/interface.c | 2 +- src/output_panel.c | 37 ++++++++++++++++++++++--------------- src/tg.h | 8 ++++++++ 5 files changed, 72 insertions(+), 38 deletions(-) diff --git a/src/algo.c b/src/algo.c index 7383b41..2cb637c 100644 --- a/src/algo.c +++ b/src/algo.c @@ -24,13 +24,23 @@ struct filter { double a0,a1,a2,b1,b2; }; -static int int_cmp(const void *a, const void *b) +/*static int int_cmp(const void *a, const void *b) { int x = *(int*)a; int y = *(int*)b; return xy ? 1 : 0; +}*/ + +static int absint_cmp(const void *a, const void *b) +{ + int x = *(int*)a; + if(x<0)x=-x; + int y = *(int*)b; + if(y<0)y=-y; + return xy ? 1 : 0; } + static void make_hp(struct filter *f, double freq) { double K = tan(M_PI * freq); @@ -685,7 +695,7 @@ static int compute_parameters(struct processing_buffers *p) return 0; } -static void do_locate_events(int *events, struct processing_buffers *p, float *waveform, int last, int offset, int count) +static void do_locate_events(int *events, struct processing_buffers *p, float *waveform, int last, int offset, int count, int ticktock) { int i; memset(p->tic_wf, 0, p->sample_rate * sizeof(float)); @@ -711,40 +721,49 @@ static void do_locate_events(int *events, struct processing_buffers *p, float *w int a = round(last - offset - i*p->period - 0.02*p->sample_rate); int b = round(last - offset - i*p->period + 0.02*p->sample_rate); if(a < 0 || b >= p->sample_count - p->period/2) - events[i] = -1; + events[i] = BAD_EVENT_TIME; else { int peak = peak_detector(p->tic_c,a,b); - events[i] = peak >= 0 ? offset + peak : -1; + events[i] = peak >= 0 ? (offset + peak)*ticktock : BAD_EVENT_TIME; } } } +int absEventTime(int eventTime){ + return (eventTime>0)? eventTime: (eventTime==BAD_EVENT_TIME)? BAD_EVENT_TIME: -eventTime; +} + +int event_is_TIC_or_TOC(int eventTime){ + return (eventTime>0)?TIC:TOC; +} + static void locate_events(struct processing_buffers *p) { int count = 1 + ceil((p->timestamp - p->events_from) / p->period); if(count <= 0 || 2*count >= EVENTS_MAX) { - p->events[0] = 0; + p->events[0] = NULL_EVENT_TIME; return; } int events[2*count]; int half = p->tic < p->period/2 ? 0 : round(p->period / 2); int offset = p->tic - half - (p->tic_pulse - p->toc_pulse) / 2; - do_locate_events(events, p, p->waveform + half, (int)(p->last_tic + p->sample_count - p->timestamp), offset, count); + do_locate_events(events, p, p->waveform + half, (int)(p->last_tic + p->sample_count - p->timestamp), offset, count, TIC); half = p->toc < p->period/2 ? 0 : round(p->period / 2); offset = p->toc - half - (p->toc_pulse - p->tic_pulse) / 2; - do_locate_events(events+count, p, p->waveform + half, (int)(p->last_toc + p->sample_count - p->timestamp), offset, count); - qsort(events, 2*count, sizeof(int), int_cmp); + do_locate_events(events+count, p, p->waveform + half, (int)(p->last_toc + p->sample_count - p->timestamp), offset, count, TOC); + qsort(events, 2*count, sizeof(int), absint_cmp); int i,j; for(i=0, j=0; i < 2*count; i++) { - if(events[i] < 0 || - events[i] + p->timestamp < p->sample_count || - events[i] + p->timestamp - p->sample_count < p->events_from) + int aEventTime = absEventTime(events[i]); + if( aEventTime == BAD_EVENT_TIME || + aEventTime + p->timestamp < p->sample_count || + aEventTime + p->timestamp - p->sample_count < p->events_from) continue; - p->events[j++] = events[i] + p->timestamp - p->sample_count; + p->events[j++] = (aEventTime + p->timestamp - p->sample_count) * event_is_TIC_or_TOC(events[i]); } - p->events[j] = 0; + p->events[j] = NULL_EVENT_TIME; } static void compute_amplitude(struct processing_buffers *p, double la) diff --git a/src/computer.c b/src/computer.c index 6d9ff55..23546d1 100644 --- a/src/computer.c +++ b/src/computer.c @@ -23,7 +23,7 @@ static int count_events(struct snapshot *s) { int i, cnt = 0; if(!s->events_count) return 0; - for(i = s->events_wp; s->events[i];) { + for(i = s->events_wp; s->events[i]!=NULL_EVENT_TIME;) { cnt++; if(--i < 0) i = s->events_count - 1; if(i == s->events_wp) break; @@ -114,10 +114,10 @@ static void compute_events_cal(struct computer *c) struct snapshot *s = c->actv; int i; for(i=d->wp-1; i >= 0 && - d->events[i] > s->events[s->events_wp]; + absEventTime(d->events[i]) > absEventTime(s->events[s->events_wp]); i--); for(i++; iwp; i++) { - if(d->events[i] / s->nominal_sr <= s->events[s->events_wp] / s->nominal_sr) + if( absEventTime(d->events[i]) / s->nominal_sr <= absEventTime(s->events[s->events_wp]) / s->nominal_sr) continue; if(++s->events_wp == s->events_count) s->events_wp = 0; s->events[s->events_wp] = d->events[i]; @@ -131,10 +131,10 @@ static void compute_events(struct computer *c) struct snapshot *s = c->actv; struct processing_buffers *p = c->actv->pb; if(p && !s->is_old) { - uint64_t last = s->events[s->events_wp]; + uint64_t last = absEventTime(s->events[s->events_wp]); int i; - for(i=0; ievents[i]; i++) - if(p->events[i] > last + floor(p->period / 4)) { + for(i=0; ievents[i]!=NULL_EVENT_TIME; i++) + if( absEventTime(p->events[i]) > last + floor(p->period / 4)) { if(++s->events_wp == s->events_count) s->events_wp = 0; s->events[s->events_wp] = p->events[i]; debug("event at %llu\n",s->events[s->events_wp]); @@ -186,7 +186,7 @@ static void *computing_thread(void *void_computer) c->actv->cal_percent = 0; } if(calibrate != c->actv->calibrate) - memset(c->actv->events,0,c->actv->events_count*sizeof(uint64_t)); + memset(c->actv->events,NULL_EVENT_TIME,c->actv->events_count*sizeof(uint64_t)); c->actv->calibrate = calibrate; if(c->actv->calibrate) { @@ -202,7 +202,7 @@ static void *computing_thread(void *void_computer) snapshot_destroy(c->curr); if(c->clear_trace) { if(!calibrate) - memset(c->actv->events,0,c->actv->events_count*sizeof(uint64_t)); + memset(c->actv->events,NULL_EVENT_TIME,c->actv->events_count*sizeof(uint64_t)); c->clear_trace = 0; } c->curr = snapshot_clone(c->actv); @@ -264,7 +264,7 @@ struct computer *start_computer(int nominal_sr, int bph, double la, int cal, int s->signal = 0; s->events_count = EVENTS_COUNT; s->events = malloc(EVENTS_COUNT * sizeof(uint64_t)); - memset(s->events,0,EVENTS_COUNT * sizeof(uint64_t)); + memset(s->events,NULL_EVENT_TIME,EVENTS_COUNT * sizeof(uint64_t)); s->events_wp = 0; s->events_from = 0; s->trace_centering = 0; diff --git a/src/interface.c b/src/interface.c index 12a6888..d97073b 100644 --- a/src/interface.c +++ b/src/interface.c @@ -929,7 +929,7 @@ guint refresh(struct main_window *w) w->computer->curr = NULL; s->trace_centering = trace_centering; if(w->computer->clear_trace && !s->calibrate) - memset(s->events,0,s->events_count*sizeof(uint64_t)); + memset(s->events,NULL_EVENT_TIME,s->events_count*sizeof(uint64_t)); if(s->calibrate && s->cal_state == 1 && s->cal_result != w->cal) { w->cal = s->cal_result; gtk_spin_button_set_value(GTK_SPIN_BUTTON(w->cal_spin_button), s->cal_result); diff --git a/src/output_panel.c b/src/output_panel.c index b0e6b03..bb27b29 100644 --- a/src/output_panel.c +++ b/src/output_panel.c @@ -18,7 +18,7 @@ #include "tg.h" -cairo_pattern_t *black,*white,*red,*green,*blue,*blueish,*yellow; +cairo_pattern_t *black,*white,*red,*green,*blue,*blueish,*yellow, *ticColor, *tocColor; static void define_color(cairo_pattern_t **gc,double r,double g,double b) { @@ -34,6 +34,9 @@ void initialize_palette() define_color(&blue,0,0,1); define_color(&blueish,0,0,.5); define_color(&yellow,1,1,0); + + define_color(&ticColor, 0.5, 1, 1);//cyan + define_color(&tocColor, 1, 0.5, 1);//magenta } static void draw_graph(double a, double b, cairo_t *c, struct processing_buffers *p, GtkWidget *da) @@ -321,7 +324,8 @@ static void expose_waveform( GtkWidget *da, cairo_t *c, int (*get_offset)(struct processing_buffers*), - double (*get_pulse)(struct processing_buffers*)) + double (*get_pulse)(struct processing_buffers*), + cairo_pattern_t *tictocColor) { cairo_init(c); @@ -415,7 +419,7 @@ static void expose_waveform( draw_graph(a,b,c,p,da); - cairo_set_source(c,old?yellow:white); + cairo_set_source(c,old?yellow: tictocColor); cairo_stroke_preserve(c); cairo_fill(c); @@ -459,14 +463,14 @@ static double get_toc_pulse(struct processing_buffers *p) static gboolean tic_draw_event(GtkWidget *widget, cairo_t *c, struct output_panel *op) { UNUSED(widget); - expose_waveform(op, op->tic_drawing_area, c, get_tic, get_tic_pulse); + expose_waveform(op, op->tic_drawing_area, c, get_tic, get_tic_pulse, ticColor); return FALSE; } static gboolean toc_draw_event(GtkWidget *widget, cairo_t *c, struct output_panel *op) { UNUSED(widget); - expose_waveform(op, op->toc_drawing_area, c, get_toc, get_toc_pulse); + expose_waveform(op, op->toc_drawing_area, c, get_toc, get_toc_pulse, tocColor); return FALSE; } @@ -496,14 +500,14 @@ static gboolean period_draw_event(GtkWidget *widget, cairo_t *c, struct output_p cairo_line_to(c, (p->tic - a - NEGATIVE_SPAN*.001*snst->sample_rate) * width/p->period, height); cairo_line_to(c, (p->tic - a + POSITIVE_SPAN*.001*snst->sample_rate) * width/p->period, height); cairo_line_to(c, (p->tic - a + POSITIVE_SPAN*.001*snst->sample_rate) * width/p->period, 0); - cairo_set_source(c,blueish); + cairo_set_source(c,ticColor); cairo_fill(c); cairo_move_to(c, (toc - a - NEGATIVE_SPAN*.001*snst->sample_rate) * width/p->period, 0); cairo_line_to(c, (toc - a - NEGATIVE_SPAN*.001*snst->sample_rate) * width/p->period, height); cairo_line_to(c, (toc - a + POSITIVE_SPAN*.001*snst->sample_rate) * width/p->period, height); cairo_line_to(c, (toc - a + POSITIVE_SPAN*.001*snst->sample_rate) * width/p->period, 0); - cairo_set_source(c,blueish); + cairo_set_source(c,tocColor); cairo_fill(c); } @@ -550,7 +554,7 @@ static gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *c, struct outp } else { sweep = snst->sample_rate * 3600. / snst->guessed_bph; zoom_factor = PAPERSTRIP_ZOOM; - if(snst->events_count && snst->events[snst->events_wp]) + if(snst->events_count && snst->events[snst->events_wp]!=NULL_EVENT_TIME) slope = - snst->rate * zoom_factor / (3600. * 24.); } @@ -564,9 +568,9 @@ static gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *c, struct outp int stopped = 0; if( snst->events_count && - snst->events[snst->events_wp] && - time > 5 * snst->nominal_sr + snst->events[snst->events_wp]) { - time = 5 * snst->nominal_sr + snst->events[snst->events_wp]; + snst->events[snst->events_wp] != NULL_EVENT_TIME && + time > 5 * snst->nominal_sr + absEventTime(snst->events[snst->events_wp])) { + time = 5 * snst->nominal_sr + absEventTime(snst->events[snst->events_wp]); stopped = 1; } @@ -627,8 +631,11 @@ static gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *c, struct outp cairo_set_source(c,stopped?yellow:white); for(i = snst->events_wp;;) { - if(!snst->events_count || !snst->events[i]) break; - double event = now - snst->events[i] + snst->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * zoom_factor); + if(!snst->events_count || snst->events[i]==NULL_EVENT_TIME) break; + double event = now - absEventTime(snst->events[i]) + snst->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * zoom_factor); + if(!stopped){ + cairo_set_source(c, event_is_TIC_or_TOC(snst->events[i])==TIC?ticColor:tocColor); + } int column = floor(fmod(event, (sweep / zoom_factor)) * strip_width / (sweep / zoom_factor)); int row = floor(event / sweep); if(row >= height) break; @@ -721,7 +728,7 @@ static void handle_clear_trace(GtkButton *b, struct output_panel *op) if(op->computer) { lock_computer(op->computer); if(!op->snst->calibrate) { - memset(op->snst->events,0,op->snst->events_count*sizeof(uint64_t)); + memset(op->snst->events,NULL_EVENT_TIME,op->snst->events_count*sizeof(uint64_t)); op->computer->clear_trace = 1; } unlock_computer(op->computer); @@ -735,7 +742,7 @@ static void handle_center_trace(GtkButton *b, struct output_panel *op) struct snapshot *snst = op->snst; if(!snst || !snst->events) return; - uint64_t last_ev = snst->events[snst->events_wp]; + uint64_t last_ev = absEventTime(snst->events[snst->events_wp]); double new_centering; if(last_ev) { double sweep; diff --git a/src/tg.h b/src/tg.h index 8907b73..41534db 100644 --- a/src/tg.h +++ b/src/tg.h @@ -36,6 +36,7 @@ #define _WIN32 #endif + #define CONFIG_FILE_NAME "tg-timer.ini" #define FILTER_CUTOFF 3000 @@ -81,6 +82,11 @@ #define UNUSED(X) (void)(X) +#define TIC 1 +#define TOC -1 +#define BAD_EVENT_TIME -1 +#define NULL_EVENT_TIME 0 + /* algo.c */ struct processing_buffers { int sample_rate; @@ -122,6 +128,8 @@ void setup_cal_data(struct calibration_data *cd); void cal_data_destroy(struct calibration_data *cd); int test_cal(struct processing_buffers *p); int process_cal(struct processing_buffers *p, struct calibration_data *cd); +int absEventTime(int eventTime); +int event_is_TIC_or_TOC(int eventTime); /* audio.c moved to audio.h struct processing_data { From 8cb52ae845f71bfb8caec3ac7e605b2cc6c9a785 Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Thu, 4 Jun 2020 17:17:01 -0400 Subject: [PATCH 05/15] Changed Lift Angle spinner to allow floating points. 0.5 increments. Signed-off-by: jakelewis3d --- src/interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface.c b/src/interface.c index d97073b..753a82d 100644 --- a/src/interface.c +++ b/src/interface.c @@ -781,7 +781,7 @@ static void init_main_window(struct main_window *w) gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); // Lift angle spin button - w->la_spin_button = gtk_spin_button_new_with_range(MIN_LA, MAX_LA, 1); + w->la_spin_button = gtk_spin_button_new_with_range(MIN_LA, MAX_LA, 0.5); gtk_box_pack_start(GTK_BOX(hbox), w->la_spin_button, FALSE, FALSE, 0); gtk_spin_button_set_value(GTK_SPIN_BUTTON(w->la_spin_button), w->la); g_signal_connect(w->la_spin_button, "value_changed", G_CALLBACK(handle_la_change), w); From 0c1c20978fea3499e89f2b1513198994bece1022 Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Thu, 4 Jun 2020 18:16:08 -0400 Subject: [PATCH 06/15] Low and High Pass ranges added to Audio Settings Dialog Signed-off-by: jakelewis3d --- src/algo.c | 13 ++++++++++--- src/audio_settings.c | 14 ++++++++++++++ src/computer.c | 14 ++++++++++++-- src/interface.c | 22 ++++++++++++++++++++-- src/tg.h | 17 ++++++++++++----- 5 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/algo.c b/src/algo.c index 2cb637c..fc7372e 100644 --- a/src/algo.c +++ b/src/algo.c @@ -76,7 +76,14 @@ static void run_filter(struct filter *f, float *buff, int size) } } -void setup_buffers(struct processing_buffers *b) +void pb_setFilter(struct processing_buffers *b, gboolean bLpf, double freq){ + if(bLpf) + make_lp(b->lpf, freq/b->sample_rate); + else + make_hp(b->hpf, freq/b->sample_rate); +} + +void setup_buffers(struct processing_buffers *b, double lpfCutoff, double hpfCutoff) { b->samples = fftwf_malloc(2 * b->sample_count * sizeof(float)); b->samples_sc = fftwf_malloc(2 * b->sample_count * sizeof(float)); @@ -97,9 +104,9 @@ void setup_buffers(struct processing_buffers *b) b->plan_f = fftwf_plan_dft_r2c_1d(b->sample_rate, b->slice_wf, b->slice_fft, FFTW_ESTIMATE); b->plan_g = fftwf_plan_dft_c2r_1d(b->sample_rate, b->slice_fft, b->slice_wf, FFTW_ESTIMATE); b->hpf = malloc(sizeof(struct filter)); - make_hp(b->hpf,(double)FILTER_CUTOFF/b->sample_rate); + make_hp(b->hpf,hpfCutoff/b->sample_rate); b->lpf = malloc(sizeof(struct filter)); - make_lp(b->lpf,(double)FILTER_CUTOFF/b->sample_rate); + make_lp(b->lpf,lpfCutoff/b->sample_rate); b->events = malloc(EVENTS_MAX * sizeof(uint64_t)); b->ready = 0; #ifdef DEBUG diff --git a/src/audio_settings.c b/src/audio_settings.c index cf9cf93..3b2904a 100644 --- a/src/audio_settings.c +++ b/src/audio_settings.c @@ -26,6 +26,15 @@ void handle_SampleRateChange(GtkComboBox *Combo, struct main_window *w); GtkWidget *audioSampleRateCombo; GtkWidget *audioInputCombo; +static void handle_LPF_valueChanged(GtkRange *r, gpointer _w){ + struct main_window * w = _w; + interface_setFilter(w, TRUE, gtk_range_get_value (r)); +} + +static void handle_HPF_valueChanged(GtkRange *r, gpointer _w){ + struct main_window * w = _w; + interface_setFilter(w, FALSE, gtk_range_get_value (r)); +} @@ -238,6 +247,11 @@ void show_preferences(GtkButton *button, struct main_window *w) { gtk_grid_attach_next_to(GTK_GRID(prefs_grid), audioSampleRateCombo, sample_label, GTK_POS_RIGHT, 2, 1); + // PASS FILTER + GtkWidget *lpfScale = addScaleCallback("Low Pass Filter", 256, 8192, w->lpfCutoff, G_CALLBACK(handle_LPF_valueChanged), w ); + gtk_grid_attach(GTK_GRID(prefs_grid), lpfScale, 0, yPos++, 2, 1); + GtkWidget *hpfScale = addScaleCallback("High Pass Filter", 128, 8192, w->hpfCutoff, G_CALLBACK(handle_HPF_valueChanged), w ); + gtk_grid_attach(GTK_GRID(prefs_grid), hpfScale, 0, yPos++, 2, 1); //check boxes diff --git a/src/computer.c b/src/computer.c index 23546d1..c22d322 100644 --- a/src/computer.c +++ b/src/computer.c @@ -234,7 +234,17 @@ void computer_destroy(struct computer *c) free(c); } -struct computer *start_computer(int nominal_sr, int bph, double la, int cal, int light) +gboolean computer_setFilter(struct computer *c, gboolean bLpf, double freq){ + if(c->pdata && c->pdata->buffers){ + struct processing_buffers *p = c->pdata->buffers; + for(int i=0; inominal_sr, w->bph, w->la, w->cal, w->is_light); + struct computer *c = start_computer(w->nominal_sr, w->bph, w->la, w->cal, w->is_light, w->lpfCutoff, w->hpfCutoff); if(!c) { g_source_remove(w->kick_timeout); g_source_remove(w->save_timeout); @@ -951,6 +951,21 @@ guint refresh(struct main_window *w) return FALSE; } +gboolean interface_setFilter(struct main_window *w, gboolean bLpf, double freq){ + if(w->computer){ + if(computer_setFilter(w->computer, bLpf, freq)){ + if(bLpf) + w->lpfCutoff = freq; + else + w->hpfCutoff = freq; + save_config(w); + return TRUE; + } + } + return FALSE; +} + + static void computer_callback(void *w) { gdk_threads_add_idle((GSourceFunc)refresh,w); @@ -985,6 +1000,9 @@ static void start_interface(GApplication* app, void *p) w->audioInputStr = g_strdup(DEFAULT_AUDIOINPUTSTRING); w->audioInterfaceStr= g_strdup(DEFAULT_AUDIOINTERFACESTRING); + w->lpfCutoff = FILTER_CUTOFF; + w->hpfCutoff = FILTER_CUTOFF; + load_config(w); @@ -1012,7 +1030,7 @@ static void start_interface(GApplication* app, void *p) w->computer_timeout = 0; - w->computer = start_computer(w->nominal_sr, w->bph, w->la, w->cal, w->is_light); + w->computer = start_computer(w->nominal_sr, w->bph, w->la, w->cal, w->is_light, w->lpfCutoff, w->hpfCutoff); if(!w->computer) { error("Error starting computation thread"); g_application_quit(app); diff --git a/src/tg.h b/src/tg.h index 41534db..8f382a1 100644 --- a/src/tg.h +++ b/src/tg.h @@ -36,7 +36,6 @@ #define _WIN32 #endif - #define CONFIG_FILE_NAME "tg-timer.ini" #define FILTER_CUTOFF 3000 @@ -119,7 +118,7 @@ struct calibration_data { uint64_t *events; }; -void setup_buffers(struct processing_buffers *b); +void setup_buffers(struct processing_buffers *b, double lpfCutoff, double hpfCutoff); void pb_destroy(struct processing_buffers *b); struct processing_buffers *pb_clone(struct processing_buffers *p); void pb_destroy_clone(struct processing_buffers *p); @@ -130,6 +129,8 @@ int test_cal(struct processing_buffers *p); int process_cal(struct processing_buffers *p, struct calibration_data *cd); int absEventTime(int eventTime); int event_is_TIC_or_TOC(int eventTime); +void setFilter(gboolean bLpf, double freq); +void pb_setFilter(struct processing_buffers *b, gboolean bLpf, double freq); /* audio.c moved to audio.h struct processing_data { @@ -205,10 +206,11 @@ struct computer { struct snapshot *snapshot_clone(struct snapshot *s); void snapshot_destroy(struct snapshot *s); void computer_destroy(struct computer *c); -struct computer *start_computer(int nominal_sr, int bph, double la, int cal, int light); +struct computer *start_computer(int nominal_sr, int bph, double la, int cal, int light, double lpfCutoff, double hpfCutoff); void lock_computer(struct computer *c); void unlock_computer(struct computer *c); void compute_results(struct snapshot *s); +gboolean computer_setFilter(struct computer *c, gboolean bLpf, double freq); /* output_panel.c */ struct output_panel { @@ -275,6 +277,9 @@ struct main_window { char * audioInputStr; char * audioInterfaceStr; + double lpfCutoff; + double hpfCutoff; + }; extern int preset_bph[]; @@ -285,7 +290,7 @@ extern int testing; void print_debug(char *format,...); void error(char *format,...); - +gboolean interface_setFilter(struct main_window *w, gboolean bLpf, double freq); //settings.c void show_preferences(GtkButton *button, struct main_window *w); @@ -299,7 +304,9 @@ void show_preferences(GtkButton *button, struct main_window *w); OP(bph, bph, int) \ OP(lift_angle, la, double) \ OP(calibration, cal, int) \ - OP(light_algorithm, is_light, int) + OP(light_algorithm, is_light, int) \ + OP(lpfCutoff, lpfCutoff, double) \ + OP(hpfCutoff, hpfCutoff, double) struct conf_data { #define DEF(NAME,PLACE,TYPE) TYPE PLACE; From 844072897eea7f919a0aacfd0e787e9b2c58efce Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Thu, 4 Jun 2020 18:16:08 -0400 Subject: [PATCH 07/15] Low and High Pass ranges added to Audio Settings Dialog Signed-off-by: jakelewis3d --- src/algo.c | 13 ++++++++++--- src/audio_settings.c | 14 ++++++++++++++ src/computer.c | 14 ++++++++++++-- src/interface.c | 22 ++++++++++++++++++++-- src/tg.h | 17 ++++++++++++----- 5 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/algo.c b/src/algo.c index 2cb637c..fc7372e 100644 --- a/src/algo.c +++ b/src/algo.c @@ -76,7 +76,14 @@ static void run_filter(struct filter *f, float *buff, int size) } } -void setup_buffers(struct processing_buffers *b) +void pb_setFilter(struct processing_buffers *b, gboolean bLpf, double freq){ + if(bLpf) + make_lp(b->lpf, freq/b->sample_rate); + else + make_hp(b->hpf, freq/b->sample_rate); +} + +void setup_buffers(struct processing_buffers *b, double lpfCutoff, double hpfCutoff) { b->samples = fftwf_malloc(2 * b->sample_count * sizeof(float)); b->samples_sc = fftwf_malloc(2 * b->sample_count * sizeof(float)); @@ -97,9 +104,9 @@ void setup_buffers(struct processing_buffers *b) b->plan_f = fftwf_plan_dft_r2c_1d(b->sample_rate, b->slice_wf, b->slice_fft, FFTW_ESTIMATE); b->plan_g = fftwf_plan_dft_c2r_1d(b->sample_rate, b->slice_fft, b->slice_wf, FFTW_ESTIMATE); b->hpf = malloc(sizeof(struct filter)); - make_hp(b->hpf,(double)FILTER_CUTOFF/b->sample_rate); + make_hp(b->hpf,hpfCutoff/b->sample_rate); b->lpf = malloc(sizeof(struct filter)); - make_lp(b->lpf,(double)FILTER_CUTOFF/b->sample_rate); + make_lp(b->lpf,lpfCutoff/b->sample_rate); b->events = malloc(EVENTS_MAX * sizeof(uint64_t)); b->ready = 0; #ifdef DEBUG diff --git a/src/audio_settings.c b/src/audio_settings.c index cf9cf93..3b2904a 100644 --- a/src/audio_settings.c +++ b/src/audio_settings.c @@ -26,6 +26,15 @@ void handle_SampleRateChange(GtkComboBox *Combo, struct main_window *w); GtkWidget *audioSampleRateCombo; GtkWidget *audioInputCombo; +static void handle_LPF_valueChanged(GtkRange *r, gpointer _w){ + struct main_window * w = _w; + interface_setFilter(w, TRUE, gtk_range_get_value (r)); +} + +static void handle_HPF_valueChanged(GtkRange *r, gpointer _w){ + struct main_window * w = _w; + interface_setFilter(w, FALSE, gtk_range_get_value (r)); +} @@ -238,6 +247,11 @@ void show_preferences(GtkButton *button, struct main_window *w) { gtk_grid_attach_next_to(GTK_GRID(prefs_grid), audioSampleRateCombo, sample_label, GTK_POS_RIGHT, 2, 1); + // PASS FILTER + GtkWidget *lpfScale = addScaleCallback("Low Pass Filter", 256, 8192, w->lpfCutoff, G_CALLBACK(handle_LPF_valueChanged), w ); + gtk_grid_attach(GTK_GRID(prefs_grid), lpfScale, 0, yPos++, 2, 1); + GtkWidget *hpfScale = addScaleCallback("High Pass Filter", 128, 8192, w->hpfCutoff, G_CALLBACK(handle_HPF_valueChanged), w ); + gtk_grid_attach(GTK_GRID(prefs_grid), hpfScale, 0, yPos++, 2, 1); //check boxes diff --git a/src/computer.c b/src/computer.c index 23546d1..c22d322 100644 --- a/src/computer.c +++ b/src/computer.c @@ -234,7 +234,17 @@ void computer_destroy(struct computer *c) free(c); } -struct computer *start_computer(int nominal_sr, int bph, double la, int cal, int light) +gboolean computer_setFilter(struct computer *c, gboolean bLpf, double freq){ + if(c->pdata && c->pdata->buffers){ + struct processing_buffers *p = c->pdata->buffers; + for(int i=0; inominal_sr, w->bph, w->la, w->cal, w->is_light); + struct computer *c = start_computer(w->nominal_sr, w->bph, w->la, w->cal, w->is_light, w->lpfCutoff, w->hpfCutoff); if(!c) { g_source_remove(w->kick_timeout); g_source_remove(w->save_timeout); @@ -951,6 +951,21 @@ guint refresh(struct main_window *w) return FALSE; } +gboolean interface_setFilter(struct main_window *w, gboolean bLpf, double freq){ + if(w->computer){ + if(computer_setFilter(w->computer, bLpf, freq)){ + if(bLpf) + w->lpfCutoff = freq; + else + w->hpfCutoff = freq; + save_config(w); + return TRUE; + } + } + return FALSE; +} + + static void computer_callback(void *w) { gdk_threads_add_idle((GSourceFunc)refresh,w); @@ -985,6 +1000,9 @@ static void start_interface(GApplication* app, void *p) w->audioInputStr = g_strdup(DEFAULT_AUDIOINPUTSTRING); w->audioInterfaceStr= g_strdup(DEFAULT_AUDIOINTERFACESTRING); + w->lpfCutoff = FILTER_CUTOFF; + w->hpfCutoff = FILTER_CUTOFF; + load_config(w); @@ -1012,7 +1030,7 @@ static void start_interface(GApplication* app, void *p) w->computer_timeout = 0; - w->computer = start_computer(w->nominal_sr, w->bph, w->la, w->cal, w->is_light); + w->computer = start_computer(w->nominal_sr, w->bph, w->la, w->cal, w->is_light, w->lpfCutoff, w->hpfCutoff); if(!w->computer) { error("Error starting computation thread"); g_application_quit(app); diff --git a/src/tg.h b/src/tg.h index 41534db..8f382a1 100644 --- a/src/tg.h +++ b/src/tg.h @@ -36,7 +36,6 @@ #define _WIN32 #endif - #define CONFIG_FILE_NAME "tg-timer.ini" #define FILTER_CUTOFF 3000 @@ -119,7 +118,7 @@ struct calibration_data { uint64_t *events; }; -void setup_buffers(struct processing_buffers *b); +void setup_buffers(struct processing_buffers *b, double lpfCutoff, double hpfCutoff); void pb_destroy(struct processing_buffers *b); struct processing_buffers *pb_clone(struct processing_buffers *p); void pb_destroy_clone(struct processing_buffers *p); @@ -130,6 +129,8 @@ int test_cal(struct processing_buffers *p); int process_cal(struct processing_buffers *p, struct calibration_data *cd); int absEventTime(int eventTime); int event_is_TIC_or_TOC(int eventTime); +void setFilter(gboolean bLpf, double freq); +void pb_setFilter(struct processing_buffers *b, gboolean bLpf, double freq); /* audio.c moved to audio.h struct processing_data { @@ -205,10 +206,11 @@ struct computer { struct snapshot *snapshot_clone(struct snapshot *s); void snapshot_destroy(struct snapshot *s); void computer_destroy(struct computer *c); -struct computer *start_computer(int nominal_sr, int bph, double la, int cal, int light); +struct computer *start_computer(int nominal_sr, int bph, double la, int cal, int light, double lpfCutoff, double hpfCutoff); void lock_computer(struct computer *c); void unlock_computer(struct computer *c); void compute_results(struct snapshot *s); +gboolean computer_setFilter(struct computer *c, gboolean bLpf, double freq); /* output_panel.c */ struct output_panel { @@ -275,6 +277,9 @@ struct main_window { char * audioInputStr; char * audioInterfaceStr; + double lpfCutoff; + double hpfCutoff; + }; extern int preset_bph[]; @@ -285,7 +290,7 @@ extern int testing; void print_debug(char *format,...); void error(char *format,...); - +gboolean interface_setFilter(struct main_window *w, gboolean bLpf, double freq); //settings.c void show_preferences(GtkButton *button, struct main_window *w); @@ -299,7 +304,9 @@ void show_preferences(GtkButton *button, struct main_window *w); OP(bph, bph, int) \ OP(lift_angle, la, double) \ OP(calibration, cal, int) \ - OP(light_algorithm, is_light, int) + OP(light_algorithm, is_light, int) \ + OP(lpfCutoff, lpfCutoff, double) \ + OP(hpfCutoff, hpfCutoff, double) struct conf_data { #define DEF(NAME,PLACE,TYPE) TYPE PLACE; From 6bbf2f3e11dbf16d5fb9f2d30d4d3501659ef85f Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Mon, 8 Jun 2020 20:08:03 -0400 Subject: [PATCH 08/15] lowered PERIOD_SHIFT due to integer rollover in calibration mode. Signed-off-by: jakelewis3d --- src/algo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/algo.c b/src/algo.c index fc7372e..9bbedc5 100644 --- a/src/algo.c +++ b/src/algo.c @@ -18,7 +18,7 @@ #include "tg.h" -#define PERIOD_SHIFT 16 +#define PERIOD_SHIFT 15 struct filter { double a0,a1,a2,b1,b2; From a6577d3df12087e5cee7860fc4d9b8d6fb9868b2 Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Tue, 9 Jun 2020 11:02:47 -0400 Subject: [PATCH 09/15] Variable magnification on papertrace. Signed-off-by: jakelewis3d --- src/output_panel.c | 74 +++++++++++++++++++++++++++++++++------------- src/tg.h | 8 +++++ 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/output_panel.c b/src/output_panel.c index bb27b29..1c47074 100644 --- a/src/output_panel.c +++ b/src/output_panel.c @@ -18,8 +18,14 @@ #include "tg.h" +#define MAG_INC "Mag+" +#define MAG_DEC "Mag-" +#define MAG_SCALE 2.0 +float paperstrip_zoom_var = 1.0; + cairo_pattern_t *black,*white,*red,*green,*blue,*blueish,*yellow, *ticColor, *tocColor; + static void define_color(cairo_pattern_t **gc,double r,double g,double b) { *gc = cairo_pattern_create_rgb(r,g,b); @@ -545,17 +551,18 @@ static gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *c, struct outp struct snapshot *snst = op->snst; uint64_t time = snst->timestamp ? snst->timestamp : get_timestamp(); double sweep; - int zoom_factor; + double zoom_factor; double slope = 1000; // detected rate: 1000 -> do not display if(snst->calibrate) { + paperstrip_zoom_var = 1.0; sweep = snst->nominal_sr; zoom_factor = PAPERSTRIP_ZOOM_CAL; slope = (double) snst->cal * zoom_factor / (10 * 3600 * 24); } else { sweep = snst->sample_rate * 3600. / snst->guessed_bph; - zoom_factor = PAPERSTRIP_ZOOM; + zoom_factor = PAPERSTRIP_ZOOM * paperstrip_zoom_var; if(snst->events_count && snst->events[snst->events_wp]!=NULL_EVENT_TIME) - slope = - snst->rate * zoom_factor / (3600. * 24.); + slope = - snst->rate * PAPERSTRIP_ZOOM / (3600. * 24.); } cairo_init(c); @@ -621,7 +628,7 @@ static gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *c, struct outp double last_line = fmod(now/sweep, ten_s); int last_tenth = floor(now/(sweep*ten_s)); for(i=0;;i++) { - double y = 0.5 + round(last_line + i*ten_s); + double y = 0.5 + round((last_line + i*ten_s)*paperstrip_zoom_var); if(y > height) break; cairo_move_to(c, .5, y); cairo_line_to(c, width-.5, y); @@ -632,26 +639,31 @@ static gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *c, struct outp cairo_set_source(c,stopped?yellow:white); for(i = snst->events_wp;;) { if(!snst->events_count || snst->events[i]==NULL_EVENT_TIME) break; - double event = now - absEventTime(snst->events[i]) + snst->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * zoom_factor); + double event = now - absEventTime(snst->events[i]) + snst->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * zoom_factor); if(!stopped){ cairo_set_source(c, event_is_TIC_or_TOC(snst->events[i])==TIC?ticColor:tocColor); } - int column = floor(fmod(event, (sweep / zoom_factor)) * strip_width / (sweep / zoom_factor)); - int row = floor(event / sweep); + double phase = fmod(event, sweep) / sweep; // 0.0 -> 1.0 + double cycle = strip_width * ( 0.5 + (phase - 0.5) * zoom_factor); + int column = floor(fmod(cycle, strip_width)); + + + int row = floor(event * paperstrip_zoom_var / sweep); if(row >= height) break; + float tickSize = fmax(1.0, paperstrip_zoom_var); cairo_move_to(c,column,row); - cairo_line_to(c,column+1,row); - cairo_line_to(c,column+1,row+1); - cairo_line_to(c,column,row+1); + cairo_line_to(c,column+tickSize,row); + cairo_line_to(c,column+tickSize,row+tickSize); + cairo_line_to(c,column,row+tickSize); cairo_line_to(c,column,row); cairo_fill(c); if(column < width - strip_width && row > 0) { column += strip_width; row -= 1; cairo_move_to(c,column,row); - cairo_line_to(c,column+1,row); - cairo_line_to(c,column+1,row+1); - cairo_line_to(c,column,row+1); + cairo_line_to(c,column+tickSize,row); + cairo_line_to(c,column+tickSize,row+tickSize); + cairo_line_to(c,column,row+tickSize); cairo_line_to(c,column,row); cairo_fill(c); } @@ -742,15 +754,15 @@ static void handle_center_trace(GtkButton *b, struct output_panel *op) struct snapshot *snst = op->snst; if(!snst || !snst->events) return; - uint64_t last_ev = absEventTime(snst->events[snst->events_wp]); + uint64_t last_ev = absEventTime(snst->events[snst->events_wp]); double new_centering; if(last_ev) { double sweep; if(snst->calibrate) - sweep = (double) snst->nominal_sr / PAPERSTRIP_ZOOM_CAL; + sweep = (double) snst->nominal_sr; else - sweep = snst->sample_rate * 3600. / (PAPERSTRIP_ZOOM * snst->guessed_bph); - new_centering = fmod(last_ev + .5*sweep , sweep); + sweep = snst->sample_rate * 3600. / snst->guessed_bph; + new_centering = fmod(last_ev + .5 * sweep , sweep); } else new_centering = 0; snst->trace_centering = new_centering; @@ -762,10 +774,10 @@ static void shift_trace(struct output_panel *op, double direction) struct snapshot *snst = op->snst; double sweep; if(snst->calibrate) - sweep = (double) snst->nominal_sr / PAPERSTRIP_ZOOM_CAL; + sweep = (double) snst->nominal_sr; else - sweep = snst->sample_rate * 3600. / (PAPERSTRIP_ZOOM * snst->guessed_bph); - snst->trace_centering = fmod(snst->trace_centering + sweep * (1.+.1*direction), sweep); + sweep = snst->sample_rate * 3600. / snst->guessed_bph; + snst->trace_centering = fmod(snst->trace_centering + sweep * (1.+.1*direction/(PAPERSTRIP_ZOOM * paperstrip_zoom_var)), sweep); gtk_widget_queue_draw(op->paperstrip_drawing_area); } @@ -781,6 +793,18 @@ static void handle_right(GtkButton *b, struct output_panel *op) shift_trace(op,1); } +static void handle_zoom(GtkButton *b, struct output_panel *op) +{ + + UNUSED(b); + + if( strcmp(gtk_button_get_label(b) , MAG_INC)==0) + paperstrip_zoom_var *= MAG_SCALE; + else + paperstrip_zoom_var /= MAG_SCALE; + gtk_widget_queue_draw(op->paperstrip_drawing_area); +} + void op_set_snapshot(struct output_panel *op, struct snapshot *snst) { op->snst = snst; @@ -836,6 +860,16 @@ struct output_panel *init_output_panel(struct computer *comp, struct snapshot *s gtk_box_pack_start(GTK_BOX(hbox3), left_button, TRUE, TRUE, 0); g_signal_connect (left_button, "clicked", G_CALLBACK(handle_left), op); + + GtkWidget *magInc_button = gtk_button_new_with_label(MAG_INC); + gtk_box_pack_start(GTK_BOX(hbox3), magInc_button, TRUE, TRUE, 0); + g_signal_connect (magInc_button, "clicked", G_CALLBACK(handle_zoom), op); + // < button + GtkWidget *magDec_button = gtk_button_new_with_label(MAG_DEC); + gtk_box_pack_start(GTK_BOX(hbox3), magDec_button, TRUE, TRUE, 0); + g_signal_connect (magDec_button, "clicked", G_CALLBACK(handle_zoom), op); + + // CLEAR button if(comp) { op->clear_button = gtk_button_new_with_label("Clear"); diff --git a/src/tg.h b/src/tg.h index 8f382a1..27bf269 100644 --- a/src/tg.h +++ b/src/tg.h @@ -36,6 +36,14 @@ #define _WIN32 #endif +#ifdef ECLIPSE +#define VERSION "0.5.2" +#define PROGRAM_NAME "TG" +#define PACKAGE "TG" + + +#endif + #define CONFIG_FILE_NAME "tg-timer.ini" #define FILTER_CUTOFF 3000 From 7e1cd8458582a579ee34464e2035b8103e7fa480 Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Tue, 9 Jun 2020 11:57:14 -0400 Subject: [PATCH 10/15] Added snapshot buttons prelabeled for DU,DD,3U,6U,9U,12U. I just so tired of having to edit the names each time. Signed-off-by: jakelewis3d --- src/interface.c | 34 ++++++++++++++++++++++++++++++++++ src/tg.h | 4 ++++ 2 files changed, 38 insertions(+) diff --git a/src/interface.c b/src/interface.c index bdfd691..842c078 100644 --- a/src/interface.c +++ b/src/interface.c @@ -316,9 +316,14 @@ static void controls_active(struct main_window *w, int active) gtk_widget_set_sensitive(w->cal_button, active); if(active) { gtk_widget_show(w->snapshot_button); + for (int i = 0; i < POSITIONS; i++) + gtk_widget_show( w->snapshot_POS_button[i]); gtk_widget_hide(w->snapshot_name); } else { gtk_widget_hide(w->snapshot_button); + for (int i = 0; i < POSITIONS; i++) + if(w->snapshot_POS_button[i] != NULL) + gtk_widget_hide( w->snapshot_POS_button[i]); gtk_widget_show(w->snapshot_name); } } @@ -481,6 +486,17 @@ static void handle_snapshot(GtkButton *b, struct main_window *w) add_new_tab(s, NULL, w); } +static void handle_Positionsnapshot(GtkButton *b, struct main_window *w) +{ + UNUSED(b); + if(w->active_snapshot->calibrate) return; + struct snapshot *s = snapshot_clone(w->active_snapshot); + s->timestamp = get_timestamp(); + char* name = (char *)gtk_button_get_label(b); + add_new_tab(s, name , w); +} + + static void chooser_set_filters(GtkFileChooser *chooser) { GtkFileFilter *tgj_filter = gtk_file_filter_new(); @@ -810,6 +826,22 @@ static void init_main_window(struct main_window *w) gtk_widget_set_sensitive(w->snapshot_button, FALSE); g_signal_connect(w->snapshot_button, "clicked", G_CALLBACK(handle_snapshot), w); + w->snapshot_POS_button[0] = gtk_button_new_with_label("DD"); + w->snapshot_POS_button[1] = gtk_button_new_with_label("DU"); + w->snapshot_POS_button[2] = gtk_button_new_with_label("3U"); + w->snapshot_POS_button[3] = gtk_button_new_with_label("6U"); + w->snapshot_POS_button[4] = gtk_button_new_with_label("9U"); + w->snapshot_POS_button[5] = gtk_button_new_with_label("12U"); + for (int i = 0; i < POSITIONS; i++) { + GtkWidget *button = w->snapshot_POS_button[i]; + if(button != NULL){ + gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); + gtk_widget_set_sensitive(button, FALSE); + g_signal_connect(button, "clicked", G_CALLBACK(handle_Positionsnapshot), w); + } + } + + // Snapshot name field GtkWidget *name_label = gtk_label_new("Current snapshot:"); w->snapshot_name_entry = gtk_entry_new(); @@ -948,6 +980,8 @@ guint refresh(struct main_window *w) gtk_widget_queue_draw(w->notebook); } gtk_widget_set_sensitive(w->snapshot_button, photogenic); + for (int i = 0; i < POSITIONS; i++) + gtk_widget_set_sensitive(w->snapshot_POS_button[i], photogenic); return FALSE; } diff --git a/src/tg.h b/src/tg.h index 27bf269..f25f14e 100644 --- a/src/tg.h +++ b/src/tg.h @@ -245,6 +245,9 @@ void op_set_border(struct output_panel *op, int i); void op_destroy(struct output_panel *op); /* interface.c */ + +#define POSITIONS 6 + struct main_window { GtkApplication *app; @@ -253,6 +256,7 @@ struct main_window { GtkWidget *la_spin_button; GtkWidget *cal_spin_button; GtkWidget *snapshot_button; + GtkWidget *snapshot_POS_button[POSITIONS]; GtkWidget *snapshot_name; GtkWidget *snapshot_name_entry; GtkWidget *cal_button; From c64efb245869302e48675f6d3b71cedd8c72493b Mon Sep 17 00:00:00 2001 From: jakelewis3d Date: Tue, 9 Jun 2020 18:44:26 -0400 Subject: [PATCH 11/15] removed eclipse IDE requred defines Signed-off-by: jakelewis3d --- src/tg.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/tg.h b/src/tg.h index f25f14e..986ce98 100644 --- a/src/tg.h +++ b/src/tg.h @@ -36,13 +36,6 @@ #define _WIN32 #endif -#ifdef ECLIPSE -#define VERSION "0.5.2" -#define PROGRAM_NAME "TG" -#define PACKAGE "TG" - - -#endif #define CONFIG_FILE_NAME "tg-timer.ini" From 2907c095b3b938624d3677adf87b280f59618b5f Mon Sep 17 00:00:00 2001 From: Jake Lewis Date: Wed, 22 Jun 2022 18:16:17 -0400 Subject: [PATCH 12/15] pr 21 Tidying up. Signed-off-by: Jake Lewis --- Makefile.am | 4 ++-- src/gtk/gtkHelper.c | 27 ++++++++++++++------------- src/interface.c | 18 ++---------------- src/output_panel.c | 2 +- src/tg.h | 1 - 5 files changed, 19 insertions(+), 33 deletions(-) diff --git a/Makefile.am b/Makefile.am index 58c2a33..27dd4ad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,8 +16,8 @@ tg_timer_SOURCES = src/algo.c \ src/audio_settings.c \ src/audio.h \ src/gtk/gtkHelper.c \ - src/gtk/gtkHelper.h \ - + src/gtk/gtkHelper.h + tg_timer_dbg_SOURCES = $(tg_timer_SOURCES) tg_timer_prf_SOURCES = $(tg_timer_SOURCES) diff --git a/src/gtk/gtkHelper.c b/src/gtk/gtkHelper.c index cc68e5d..fde7723 100644 --- a/src/gtk/gtkHelper.c +++ b/src/gtk/gtkHelper.c @@ -1,9 +1,9 @@ /* - * gtkHelper.c - * - * Created on: Oct 24, 2019 - * Author: jlewis - * This program is free software; you can redistribute it and/or modify + gtkHelper.c + + Created on: Oct 24, 2019 + Author: jlewis + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. @@ -18,7 +18,7 @@ */ #include -#include "gtk/gtkHelper.h" +#include "gtkHelper.h" //this is stored here as it issues a compiler warning @@ -26,13 +26,14 @@ void setWidgetColor(GtkWidget *widget, cairo_pattern_t *cairocolor){ double red,green,blue,alpha; cairo_pattern_get_rgba (cairocolor, &red,&green,&blue,&alpha); - char rgbaStr[1024]; - sprintf(rgbaStr, "rgba(%f,%f,%f,%f)", 255*red, 255*green, 255*blue, alpha); + char *rgbaStr = g_strdup_printf("rgba(%f,%f,%f,%f)", 255*red, 255*green, 255*blue, alpha); GdkRGBA gdkColor; gdk_rgba_parse (&gdkColor, rgbaStr); gtk_widget_override_color (widget, GTK_STATE_FLAG_NORMAL, &gdkColor); + g_free(rgbaStr); + } @@ -120,11 +121,11 @@ GtkWidget *addScale(const gchar*name, double min, double max, double* value){ // spin button -GtkWidget * createSpinButtonContainer(const gchar*name, - double min, double max, - double step, double initValue, - GCallback changedCallback, gpointer callbackData - ){ +GtkWidget * createSpinButtonContainer( const gchar*name, + double min, double max, + double step, double initValue, + GCallback changedCallback, gpointer callbackData + ){ GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); GtkWidget * spin_button = gtk_spin_button_new_with_range(min, max, step); gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin_button), initValue); diff --git a/src/interface.c b/src/interface.c index 842c078..e3b248a 100644 --- a/src/interface.c +++ b/src/interface.c @@ -40,15 +40,6 @@ void print_debug(char *format,...) } -void console(char *format,...)//this is better for eclipse development -{ -#ifdef DEBUG - va_list args; - va_start(args,format); - vfprintf(stdout,format,args); - va_end(args); -#endif -} void error(char *format,...) { @@ -826,14 +817,9 @@ static void init_main_window(struct main_window *w) gtk_widget_set_sensitive(w->snapshot_button, FALSE); g_signal_connect(w->snapshot_button, "clicked", G_CALLBACK(handle_snapshot), w); - w->snapshot_POS_button[0] = gtk_button_new_with_label("DD"); - w->snapshot_POS_button[1] = gtk_button_new_with_label("DU"); - w->snapshot_POS_button[2] = gtk_button_new_with_label("3U"); - w->snapshot_POS_button[3] = gtk_button_new_with_label("6U"); - w->snapshot_POS_button[4] = gtk_button_new_with_label("9U"); - w->snapshot_POS_button[5] = gtk_button_new_with_label("12U"); + static const char* const labels[] = {"DD", "DU", "3U", "6U","9U","12U"}; for (int i = 0; i < POSITIONS; i++) { - GtkWidget *button = w->snapshot_POS_button[i]; + GtkWidget *button = w->snapshot_POS_button[i] = gtk_button_new_with_label(labels[i]); if(button != NULL){ gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); gtk_widget_set_sensitive(button, FALSE); diff --git a/src/output_panel.c b/src/output_panel.c index 1c47074..ae54e47 100644 --- a/src/output_panel.c +++ b/src/output_panel.c @@ -801,7 +801,7 @@ static void handle_zoom(GtkButton *b, struct output_panel *op) if( strcmp(gtk_button_get_label(b) , MAG_INC)==0) paperstrip_zoom_var *= MAG_SCALE; else - paperstrip_zoom_var /= MAG_SCALE; + paperstrip_zoom_var /= MAG_SCALE; gtk_widget_queue_draw(op->paperstrip_drawing_area); } diff --git a/src/tg.h b/src/tg.h index 986ce98..ca0a4bc 100644 --- a/src/tg.h +++ b/src/tg.h @@ -36,7 +36,6 @@ #define _WIN32 #endif - #define CONFIG_FILE_NAME "tg-timer.ini" #define FILTER_CUTOFF 3000 From a687111562dccde16e6f811d70980ae91b6de7fd Mon Sep 17 00:00:00 2001 From: Jake Lewis Date: Thu, 23 Jun 2022 11:39:38 -0400 Subject: [PATCH 13/15] pr 21 Tidying up. Signed-off-by: Jake Lewis --- Makefile.am | 4 ++-- src/audio_settings.c | 8 +++++--- src/gtk/gtkHelper.c | 27 ++++++++++++++------------- src/interface.c | 18 ++---------------- src/output_panel.c | 2 +- src/tg.h | 1 - 6 files changed, 24 insertions(+), 36 deletions(-) diff --git a/Makefile.am b/Makefile.am index 58c2a33..27dd4ad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,8 +16,8 @@ tg_timer_SOURCES = src/algo.c \ src/audio_settings.c \ src/audio.h \ src/gtk/gtkHelper.c \ - src/gtk/gtkHelper.h \ - + src/gtk/gtkHelper.h + tg_timer_dbg_SOURCES = $(tg_timer_SOURCES) tg_timer_prf_SOURCES = $(tg_timer_SOURCES) diff --git a/src/audio_settings.c b/src/audio_settings.c index 3b2904a..08f196f 100644 --- a/src/audio_settings.c +++ b/src/audio_settings.c @@ -7,8 +7,7 @@ #include "audio.h" - -#include +#include "gtk/gtkHelper.h" #include #include #include @@ -72,9 +71,11 @@ static void fillRatesComboBox(char*activeAudioInput, struct main_window *w){ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(audioSampleRateCombo), DEFAULT_AUDIORATESTRING, DEFAULT_AUDIORATESTRING); int sampleRates[] = {48000, 44100, 32768, 24000, 22050, 16384, 11025}; + int nSamples = sizeof(sampleRates)/sizeof(sampleRates[0]); int activeSampleIndex = 0; int added = 0; - for (int n=0; n < sizeof(sampleRates)/sizeof(sampleRates[0]); n++) { + int n; + for (n=0; n < nSamples; n++) { int sampleRate = sampleRates[n]; if( audio_devicename_supports_rate(w->audioInterfaceStr, activeAudioInput, sampleRate)>=0 ){ char sampleRateString[1024]; @@ -168,6 +169,7 @@ void handle_SampleRateChange(GtkComboBox *Combo, struct main_window *w){ /* Display the Settings dialog */ //Thanks to Rob Wahlstedt for this code ;) https://github.com/wahlstedt/tg void show_preferences(GtkButton *button, struct main_window *w) { + UNUSED(button); GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT /* | GTK_DIALOG_MODAL*/; GtkWidget *dialog = gtk_dialog_new_with_buttons("Audio Settings", GTK_WINDOW(w->window), diff --git a/src/gtk/gtkHelper.c b/src/gtk/gtkHelper.c index cc68e5d..fde7723 100644 --- a/src/gtk/gtkHelper.c +++ b/src/gtk/gtkHelper.c @@ -1,9 +1,9 @@ /* - * gtkHelper.c - * - * Created on: Oct 24, 2019 - * Author: jlewis - * This program is free software; you can redistribute it and/or modify + gtkHelper.c + + Created on: Oct 24, 2019 + Author: jlewis + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. @@ -18,7 +18,7 @@ */ #include -#include "gtk/gtkHelper.h" +#include "gtkHelper.h" //this is stored here as it issues a compiler warning @@ -26,13 +26,14 @@ void setWidgetColor(GtkWidget *widget, cairo_pattern_t *cairocolor){ double red,green,blue,alpha; cairo_pattern_get_rgba (cairocolor, &red,&green,&blue,&alpha); - char rgbaStr[1024]; - sprintf(rgbaStr, "rgba(%f,%f,%f,%f)", 255*red, 255*green, 255*blue, alpha); + char *rgbaStr = g_strdup_printf("rgba(%f,%f,%f,%f)", 255*red, 255*green, 255*blue, alpha); GdkRGBA gdkColor; gdk_rgba_parse (&gdkColor, rgbaStr); gtk_widget_override_color (widget, GTK_STATE_FLAG_NORMAL, &gdkColor); + g_free(rgbaStr); + } @@ -120,11 +121,11 @@ GtkWidget *addScale(const gchar*name, double min, double max, double* value){ // spin button -GtkWidget * createSpinButtonContainer(const gchar*name, - double min, double max, - double step, double initValue, - GCallback changedCallback, gpointer callbackData - ){ +GtkWidget * createSpinButtonContainer( const gchar*name, + double min, double max, + double step, double initValue, + GCallback changedCallback, gpointer callbackData + ){ GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); GtkWidget * spin_button = gtk_spin_button_new_with_range(min, max, step); gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin_button), initValue); diff --git a/src/interface.c b/src/interface.c index 842c078..e3b248a 100644 --- a/src/interface.c +++ b/src/interface.c @@ -40,15 +40,6 @@ void print_debug(char *format,...) } -void console(char *format,...)//this is better for eclipse development -{ -#ifdef DEBUG - va_list args; - va_start(args,format); - vfprintf(stdout,format,args); - va_end(args); -#endif -} void error(char *format,...) { @@ -826,14 +817,9 @@ static void init_main_window(struct main_window *w) gtk_widget_set_sensitive(w->snapshot_button, FALSE); g_signal_connect(w->snapshot_button, "clicked", G_CALLBACK(handle_snapshot), w); - w->snapshot_POS_button[0] = gtk_button_new_with_label("DD"); - w->snapshot_POS_button[1] = gtk_button_new_with_label("DU"); - w->snapshot_POS_button[2] = gtk_button_new_with_label("3U"); - w->snapshot_POS_button[3] = gtk_button_new_with_label("6U"); - w->snapshot_POS_button[4] = gtk_button_new_with_label("9U"); - w->snapshot_POS_button[5] = gtk_button_new_with_label("12U"); + static const char* const labels[] = {"DD", "DU", "3U", "6U","9U","12U"}; for (int i = 0; i < POSITIONS; i++) { - GtkWidget *button = w->snapshot_POS_button[i]; + GtkWidget *button = w->snapshot_POS_button[i] = gtk_button_new_with_label(labels[i]); if(button != NULL){ gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); gtk_widget_set_sensitive(button, FALSE); diff --git a/src/output_panel.c b/src/output_panel.c index 1c47074..ae54e47 100644 --- a/src/output_panel.c +++ b/src/output_panel.c @@ -801,7 +801,7 @@ static void handle_zoom(GtkButton *b, struct output_panel *op) if( strcmp(gtk_button_get_label(b) , MAG_INC)==0) paperstrip_zoom_var *= MAG_SCALE; else - paperstrip_zoom_var /= MAG_SCALE; + paperstrip_zoom_var /= MAG_SCALE; gtk_widget_queue_draw(op->paperstrip_drawing_area); } diff --git a/src/tg.h b/src/tg.h index 986ce98..ca0a4bc 100644 --- a/src/tg.h +++ b/src/tg.h @@ -36,7 +36,6 @@ #define _WIN32 #endif - #define CONFIG_FILE_NAME "tg-timer.ini" #define FILTER_CUTOFF 3000 From b4132de706cbace1809bccdb384c01e55b5d0cc8 Mon Sep 17 00:00:00 2001 From: Jake Lewis Date: Thu, 23 Jun 2022 18:59:44 -0400 Subject: [PATCH 14/15] Update algo.c calculation of beat error moved from first pulse (unlock), to third pulse (lock) --- src/algo.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/algo.c b/src/algo.c index 9bbedc5..4afb7a2 100644 --- a/src/algo.c +++ b/src/algo.c @@ -754,10 +754,10 @@ static void locate_events(struct processing_buffers *p) int events[2*count]; int half = p->tic < p->period/2 ? 0 : round(p->period / 2); - int offset = p->tic - half - (p->tic_pulse - p->toc_pulse) / 2; + int offset = p->tic - half; do_locate_events(events, p, p->waveform + half, (int)(p->last_tic + p->sample_count - p->timestamp), offset, count, TIC); half = p->toc < p->period/2 ? 0 : round(p->period / 2); - offset = p->toc - half - (p->toc_pulse - p->tic_pulse) / 2; + offset = p->toc - half; do_locate_events(events+count, p, p->waveform + half, (int)(p->last_toc + p->sample_count - p->timestamp), offset, count, TOC); qsort(events, 2*count, sizeof(int), absint_cmp); @@ -831,7 +831,6 @@ static void compute_amplitude(struct processing_buffers *p, double la) p->amp = (tic_amp_abs + toc_amp_abs) / 2; p->tic_pulse = tic_pulse; p->toc_pulse = toc_pulse; - p->be = p->period/2 - fabs(p->toc - p->tic + p->tic_pulse - p->toc_pulse); debug("amp: be = %.1f\n",fabs(p->be)*1000/p->sample_rate); debug("amp = %f\n", la * p->amp); break; From cc6fad4781a249a44bd4c861e56680534130200d Mon Sep 17 00:00:00 2001 From: Jake Lewis Date: Sat, 25 Jun 2022 15:00:56 -0400 Subject: [PATCH 15/15] signed beat error removed conversion of beat error to an absolute number. Beat Error now shows + or - --- src/algo.c | 12 +++++++++--- src/computer.c | 2 +- src/output_panel.c | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/algo.c b/src/algo.c index 4afb7a2..8573f44 100644 --- a/src/algo.c +++ b/src/algo.c @@ -655,6 +655,12 @@ static void smooth(float *in, float *out, int window, int size) } } +void compute_beaterror(struct processing_buffers *p){ + p->be = p->period/2 - fabs(p->toc - p->tic); + if(p->toc > p->tic) + p->be *= -1.0; +} + static int compute_parameters(struct processing_buffers *p) { int tic_to_toc = peak_detector(p->waveform_sc, @@ -663,9 +669,6 @@ static int compute_parameters(struct processing_buffers *p) if(tic_to_toc < 0) { debug("beat error = ---\n"); return 1; - } else { - p->be = p->period/2 - tic_to_toc; - debug("beat error = %.1f\n",fabs(p->be)*1000/p->sample_rate); } int wf_size = ceil(p->period); @@ -699,6 +702,9 @@ static int compute_parameters(struct processing_buffers *p) p->last_tic = p->timestamp - (uint64_t)round(fmod(apparent_phase, p->period)); + compute_beaterror(p); + debug("beat error = %.1f\n",fabs(p->be)*1000/p->sample_rate); + return 0; } diff --git a/src/computer.c b/src/computer.c index c22d322..d9ef16a 100644 --- a/src/computer.c +++ b/src/computer.c @@ -151,7 +151,7 @@ void compute_results(struct snapshot *s) if(s->pb) { s->guessed_bph = s->bph ? s->bph : guess_bph(s->pb->period / s->sample_rate); s->rate = (7200/(s->guessed_bph * s->pb->period / s->sample_rate) - 1)*24*3600; - s->be = fabs(s->pb->be) * 1000 / s->sample_rate; + s->be = s->pb->be * 1000 / s->sample_rate; s->amp = s->la * s->pb->amp; // 0 = not available if(s->amp < 135 || s->amp > 360) s->amp = 0; diff --git a/src/output_panel.c b/src/output_panel.c index ae54e47..ccd9682 100644 --- a/src/output_panel.c +++ b/src/output_panel.c @@ -277,7 +277,7 @@ static gboolean output_draw_event(GtkWidget *widget, cairo_t *c, struct output_p char rates[20]; sprintf(rates,"%s%d",rate > 0 ? "+" : rate < 0 ? "-" : "",abs(rate)); sprintf(outputs[0],"%4s",rates); - sprintf(outputs[2]," %4.1f",be); + sprintf(outputs[2]," %s%.1f",be > 0 ? "+" : be < 0 ? "-" : "",fabs(be)); if(snst->amp > 0) sprintf(outputs[4]," %3.0f",snst->amp); else