diff --git a/Makefile.am b/Makefile.am index e9ae9cd..27dd4ad 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..8573f44 100644 --- a/src/algo.c +++ b/src/algo.c @@ -18,17 +18,29 @@ #include "tg.h" +#define PERIOD_SHIFT 15 + 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); @@ -64,19 +76,26 @@ 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 = 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); @@ -85,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 @@ -99,16 +118,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); @@ -436,10 +455,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; @@ -542,17 +561,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]; } @@ -633,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, @@ -641,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); @@ -677,10 +702,13 @@ 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; } -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)); @@ -706,40 +734,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); + 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; - 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); + 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); 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) @@ -800,7 +837,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; 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..08f196f --- /dev/null +++ b/src/audio_settings.c @@ -0,0 +1,287 @@ +/* + * settings.c + * + * Created on: Apr 28, 2020 + * Author: jlewis + */ + + +#include "audio.h" +#include "gtk/gtkHelper.h" +#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 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)); +} + + + +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 nSamples = sizeof(sampleRates)/sizeof(sampleRates[0]); + int activeSampleIndex = 0; + int added = 0; + 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]; + 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) { + 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), + 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); + + + // 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 + + 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..d9ef16a 100644 --- a/src/computer.c +++ b/src/computer.c @@ -17,12 +17,13 @@ */ #include "tg.h" +#include "audio.h" 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; @@ -113,16 +114,16 @@ 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]; 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) @@ -130,17 +131,17 @@ 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]); } s->events_from = p->timestamp - ceil(p->period); } else { - s->events_from = get_timestamp(s->is_light); + s->events_from = get_timestamp(); } } @@ -150,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; @@ -185,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) { @@ -201,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); @@ -233,10 +234,19 @@ 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; isignal = 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/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..fde7723 --- /dev/null +++ b/src/gtk/gtkHelper.c @@ -0,0 +1,183 @@ +/* + 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 "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 = 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); + +} + + + +// 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..e3b248a 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,8 @@ void print_debug(char *format,...) va_end(args); } + + void error(char *format,...) { char s[100]; @@ -166,7 +170,7 @@ static guint computer_terminated(struct main_window *w) } else { debug("Restarting computer"); - struct computer *c = start_computer(w->nominal_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); @@ -229,7 +233,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 +266,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; @@ -279,9 +307,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); } } @@ -440,10 +473,21 @@ 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); } +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(); @@ -570,7 +614,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); @@ -744,7 +788,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); @@ -773,6 +817,17 @@ 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); + 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] = 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); + 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(); @@ -799,6 +854,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 +883,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); @@ -881,7 +947,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); @@ -900,9 +966,26 @@ 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; } +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); @@ -917,10 +1000,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 +1016,33 @@ 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); + + w->lpfCutoff = FILTER_CUTOFF; + w->hpfCutoff = FILTER_CUTOFF; + 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) @@ -941,7 +1050,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/output_panel.c b/src/output_panel.c index 0e7fc8b..ccd9682 100644 --- a/src/output_panel.c +++ b/src/output_panel.c @@ -18,7 +18,13 @@ #include "tg.h" -cairo_pattern_t *black,*white,*red,*green,*blue,*blueish,*yellow; +#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) { @@ -34,6 +40,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) @@ -112,7 +121,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 +157,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 +212,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; @@ -259,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 @@ -312,7 +330,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); @@ -406,7 +425,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); @@ -450,14 +469,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; } @@ -487,14 +506,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); } @@ -530,19 +549,20 @@ 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 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; - if(snst->events_count && snst->events[snst->events_wp]) - slope = - snst->rate * zoom_factor / (3600. * 24.); + zoom_factor = PAPERSTRIP_ZOOM * paperstrip_zoom_var; + if(snst->events_count && snst->events[snst->events_wp]!=NULL_EVENT_TIME) + slope = - snst->rate * PAPERSTRIP_ZOOM / (3600. * 24.); } cairo_init(c); @@ -555,9 +575,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; } @@ -608,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); @@ -618,24 +638,32 @@ 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); - int column = floor(fmod(event, (sweep / zoom_factor)) * strip_width / (sweep / zoom_factor)); - int row = floor(event / sweep); + 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); + } + 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); } @@ -712,7 +740,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); @@ -726,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 = 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; @@ -746,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); } @@ -765,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; @@ -820,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 789f66c..ca0a4bc 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 @@ -76,6 +81,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; @@ -108,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); @@ -117,8 +127,12 @@ 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); +void setFilter(gboolean bLpf, double freq); +void pb_setFilter(struct processing_buffers *b, gboolean bLpf, double freq); -/* audio.c */ +/* audio.c moved to audio.h struct processing_data { struct processing_buffers *buffers; uint64_t last_tic; @@ -131,6 +145,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 { @@ -190,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 { @@ -220,6 +237,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; @@ -228,6 +248,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; @@ -256,6 +277,13 @@ struct main_window { guint kick_timeout; guint save_timeout; + + char * audioInputStr; + char * audioInterfaceStr; + + double lpfCutoff; + double hpfCutoff; + }; extern int preset_bph[]; @@ -266,13 +294,23 @@ 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); +#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) \ 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; @@ -287,3 +325,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_ */