/*! \mainpage FilterCalcs (c) 1971 .. 2016 Roger Burghall The original of this code was written in Fortran for the Digital Equipment Corporation PDP-12, beginning in the early 1970s, and then ported to the BBC Model B, and extended to provide more of the functions described below. This port took place in the late 1980s. A second port to Windows in the 1990's was abandoned in favour of a third port, to a Linux PC, only begun in 2013. No responsibility is accepted for the consequences of using this software, and no guarantee is given for its correctness. It is necessary for the user to understand what they are doing! Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. This program is intended to enable the user to design active or digital filters, first in terms of T and q values and zero or one simple lag, before carrying out circuit design to realise these stages as either Sallen and Key or Rauch circuits, or as digital filters. The intention is to permit the frequency and step responses to be computed, the response to an arbitrary waveform (eventually; not implemented in version 1.0), and to design Sallen and Key or Rauch circuits, or transform the filter to the z-domain. These functions were already implemented in the BBC Model B version of the suite. Thanks are due to Graham Watts for help with the mathematics of impulse and step response functions. Versions 0.1 to 0.51 RB Developing code to design filters, compute frequency and time responses etc. Takes specification via control window; returns information on graphics window and terminal window. Version 0.6 June 2016 RB Added circuit diagram window and code to show and hide diagrams. Version 0.7 August 2016 RB Normalise the cut-off frequency of the prototype filter (0.71: actually works!) Version 0.8 August 2016 RB Added a slider at the top of the graphics window. Version 1.0 September 2016 RB First usable version of this port. */ /*! \file rbfilter.cpp rbfilter.cpp contains code to display windows to provide a GUI for controlling the calculation of active filters and in order to display results. If porting to another OS this file is expected to need considerable modification. It is intended that operating system dependant code should be kept in this file only. */ #include #include #include #include #include #include "Drawing.h" #include "Calcs.h" #include #include #define VERSION 1.0 #if SHOW_FILE_COMPILING #pragma message "Compiling Drawing.cpp" // or #warning "Compiling Drawing.cpp"? #endif #define XSIZE 800 #define YSIZE 300 #pragma GCC diagnostic ignored "-Wwrite-strings" #pragma GCC diagnostic ignored "-Wformat-security" static int slider_position; extern TFilter filter; GtkWidget *app, /* *window, */ *cct_diagram; GtkWidget *image; double xyscale; gchar str[256]; GtkWidget *Window1, *Area1; GtkWidget *scale1; GtkObject *scale_adj1; static GdkPixmap *pixmap = NULL; bool square = FALSE; GdkColor TriColour; // Combi boxes GtkWidget *Ripple = NULL, *Bandwidth, *Type, *Type2, *Circuit, *Freq, *Atten, *SamplingFreq, *Order; GtkWidget *LabelR, *LabelB, *LabelS; // Buttons. static GtkWidget *vbox1, *vbox2, *sp_button, *t_button, *f_button, *s_button /*, *z_button*/ ; DrawMode Draw_mode; class callback { public: static void clicked(GtkWidget *button, gpointer data); static gint quit(GtkWidget *Widget, GdkEvent *event, gpointer data); }; /** Define a method to be called if a button is clicked. Read all the combo-boxes and correct the filter object in case calculations have already been done. Currently we do not check for valid entries. */ void callback::clicked(GtkWidget *button, gpointer data) { void DrawMap(GtkWidget *, gdouble, gdouble); void Draw_poles(GtkWidget *widget, gdouble x, gdouble y); void DrawStep(GtkWidget *widget, gdouble x, gdouble y); void DrawBode(GtkWidget *widget, gdouble x, gdouble y); double scale(TFilter); gchar *text; int w, h; int n; char *string = (char *)data; // OK button clicked. g_print("\n"); g_print(string); text = (gchar *)gtk_combo_box_get_active_text(GTK_COMBO_BOX(Type)); if(strncmp(text, "L", 1) == 0) filter.fclass = Lowpass; else if(strncmp(text, "H", 1) == 0) filter.fclass = Highpass; else if(strncmp(text, "B", 1) == 0) filter.fclass = Bandpass; text = (gchar *)gtk_combo_box_get_active_text(GTK_COMBO_BOX(Type2)); if(strncmp(text, "Be", 2) == 0) filter.sclass = Bessel; else if(strncmp(text, "Bu", 2) == 0) filter.sclass = Butterworth; else if(strncmp(text, "C", 1) == 0) filter.sclass = Chebyshev; text = (gchar *)gtk_combo_box_get_active_text(GTK_COMBO_BOX(Circuit)); if(strncmp(text, "S", 1) == 0) filter.cclass = SallKey; else if(strncmp(text, "R", 1) == 0) filter.cclass = Rauch; else if(strncmp(text, "Z", 1) == 0) filter.cclass = ZDomain; text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(Freq)->entry)); if(strlen(text)) n = sscanf(text, "%lf", &filter.frequency); if((n == 0) || (filter.frequency <= 0.0)) { filter.frequency = 1.0; gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(Freq)->entry), "?"); } text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(Order)->entry)); if(strlen(text)) sscanf(text, "%d", &filter.poles); if((n == 0) || (filter.poles < 2) || ((filter.poles < 3) && (filter.fclass != Bandpass))) { filter.poles = 3; gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(Order)->entry), "?"); } text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(Ripple)->entry)); if(strlen(text)) sscanf(text, "%lf", &filter.ripple); if((n == 0) || (filter.ripple < 0.0)) { filter.ripple = 1.0; gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(Ripple)->entry), "?"); } text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(Bandwidth)->entry)); if(strlen(text)) sscanf(text, "%lf", &filter.bandwidth); if((n == 0) || (filter.bandwidth <= 2)) { filter.bandwidth = filter.frequency/2.0; gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(Bandwidth)->entry), "?"); } text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(SamplingFreq)->entry)); if(strlen(text)) sscanf(text, "%lf", &filter.samplingfreq); filter.Calculate( ); bode_calc(filter); filter.step_calc( ); xyscale = scale(filter); gdk_window_get_size(Window1->window, &w, &h); if(strncmp(string, "Syn", 3) == 0) { switch(filter.cclass) { case Rauch: filter.gain = -1.0; filter.Synth_Rauch( ); switch(filter.fclass) { case Lowpass: gtk_image_set_from_file ((GtkImage *)image, "./Circuit_diagrams/low_pass_MF.png"); break; case Bandpass: gtk_image_set_from_file ((GtkImage *)image, "./Circuit_diagrams/band_pass_MF.png"); break; case Highpass: gtk_image_set_from_file ((GtkImage *)image, "./Circuit_diagrams/high_pass_MF.png"); break; } gtk_widget_show(image); gtk_widget_show(cct_diagram); break; case SallKey: filter.gain = 1.0; filter.Synth_SallKey( ); switch(filter.fclass) { case Lowpass: gtk_image_set_from_file ((GtkImage *)image, "./Circuit_diagrams/low_pass_SK.png"); break; case Bandpass: gtk_image_set_from_file ((GtkImage *)image, "./Circuit_diagrams/band_pass_SK.png"); break; case Highpass: gtk_image_set_from_file ((GtkImage *)image, "./Circuit_diagrams/high_pass_SK.png"); break; } gtk_widget_show(image); gtk_widget_show(cct_diagram); break; case ZDomain: filter.gain = 1.0; filter.bilinear( ); gtk_image_set_from_file ((GtkImage *)image, "./Circuit_diagrams/DirectForm1.png"); gtk_widget_show(image); gtk_widget_show(cct_diagram); break; default: cout << "Unknown cclass!\n"; exit(-1); } } if(strncmp(string, "S-p", 2) == 0) { gtk_widget_hide(scale1); Draw_mode = Splane; Draw_poles(Area1, 250, 250); } if(strncmp(string, "T-d", 2) == 0) { gtk_adjustment_set_value (GTK_ADJUSTMENT(scale_adj1), 0.0); gtk_widget_show(scale1); Draw_mode = Step; DrawStep(Area1, 250, 250); } if(strncmp(string, "F-d", 2) == 0) { gtk_adjustment_set_value (GTK_ADJUSTMENT(scale_adj1), (double)FSTEPS/2.0); gtk_widget_show(scale1); Draw_mode = Bode; DrawBode(Area1, 250, 250); } g_print("\n"); } /// Define a method to be called if the close icon is clicked. gint callback::quit(GtkWidget *Widget, GdkEvent *event, gpointer data) { g_print("'Quit' callback\n"); gtk_main_quit( ); return FALSE; } /// I would like to make the application ignore the second window 'quit' event, but since I can't I'll quit the application. gint pseudo_quit(GtkWidget *Widget, GdkEvent *event, gpointer data) { g_print("Second window 'Quit' callback\n"); gtk_main_quit( ); return FALSE; } /// Draw the Bode diagram. void DrawBode(GtkWidget *widget, gdouble x, gdouble y) { extern double n_to_freq(int n, double f1, int nmax=FSTEPS, int decades=2); char str[80]; gint i, w, h; gint xx, yy; gint xx1, yy1, xx2, yy2; gint width, height; double a, f; GdkFont* Font1; GdkColor Colour1, Colour2; GdkGC *gc1= gdk_gc_new(widget->window); GdkGC *gc2= gdk_gc_new(widget->window); #if VERBOSE g_print("\n"); #endif gtk_window_set_title ((GtkWindow *)Window1, (gchar *)"Bode diagram"); // Let's colour 'gc1' blue and set the foreground colour to it. Colour1.red=32000; Colour1.green=32000; Colour1.blue=62000; gdk_gc_set_rgb_fg_color(gc1, &Colour1); Colour2.red=32000; Colour2.green=62000; Colour2.blue=32000; gdk_gc_set_rgb_fg_color(gc2, &Colour2); // g_print("draw brush\n"); Font1 = gdk_font_load("*trebuchet*12*"); if(Font1 == NULL) Font1 = gdk_font_load("*arial*"); if(Font1 == NULL) Font1 = gdk_font_load("*sans*"); if(Font1 == NULL) Font1 = gdk_font_load("*helvetica*--14*"); if(Font1 == NULL) Font1 = gdk_font_load("*helvetica*"); if(Font1 == NULL) Font1 = gdk_font_load("-misc-fixed-*"); if(Font1 == NULL) Font1 = gdk_font_load("-bitstream-bitstream charter-*"); if(Font1 == NULL) Font1 = gdk_font_load("*clean*"); if(Font1 == NULL) g_print("Failed to find font\n"); gdk_window_get_size(widget->window, &w, &h); // OR gdk_drawable_get_size(widget->window, &width, &height); GdkRectangle update_rect; update_rect.x = (gint)1; update_rect.y = (gint)1; update_rect.width = (gint)w - 2; update_rect.height = (gint)h - 2; gdk_draw_rectangle(widget->window, widget->style->white_gc, TRUE, 0, 0, w, h); gdk_draw_line(widget->window, gc1, 10, h/5, w-10, h/5); gdk_draw_line(widget->window, gc1, (w)/10, h, (w)/10, 1); gdk_draw_string(widget->window, Font1, widget->style->black_gc, (w)/10, h/5, "0,0"); yy = h/5 - 0.5*log10(0.708) * 3*h/5; gdk_draw_line(widget->window, gc2, w/10, yy, 9*w/10, yy); gdk_draw_string(widget->window, Font1, gc2, (w)/10, yy, "-3dB"); yy = h/5 - 0.5*log10(0.708*0.708) * 3*h/5; gdk_draw_line(widget->window, gc2, w/10, yy, 9*w/10, yy); gdk_draw_string(widget->window, Font1, gc2, (w)/10, yy, "-6dB"); yy = h/5 - 0.5*log10(0.708*0.708*0.708) * 3*h/5; gdk_draw_line(widget->window, gc2, w/10, yy, 9*w/10, yy); gdk_draw_string(widget->window, Font1, gc2, (w)/10, yy, "-9dB"); if(filter.fclass == Bandpass) gdk_draw_string(widget->window, Font1, widget->style->black_gc, (w)/10, yy/10, "Gain normalised to 1.0"); /// Draw the frequency response curve. for(i=1; iwindow, gc1, xx1, yy1, xx2, yy2); } /// Draw line at measurement frequency f = n_to_freq(slider_position, filter.fmax); xx1 = w/10 + (slider_position-1) * 8*w/10 / FSTEPS; gdk_draw_line(widget->window, gc1, xx1, h, xx1, 1); sprintf(str, "f = %0.2f Hz", f); gdk_draw_string(widget->window, Font1, widget->style->black_gc, (7*w)/10, h/10, str); // a = log10(filter.freq_resp[slider_position-1]); a = filter.freq_resp[slider_position-1]; sprintf(str, "Gain = %0.2f ", a); gdk_draw_string(widget->window, Font1, widget->style->black_gc, (7*w)/10, h/8, str); #if VERBOSE g_print("\n"); #endif } /// Draw the s-plane diagram. void Draw_poles(GtkWidget *widget, gdouble x, gdouble y) { double scale(TFilter); char str[80]; gint i, w, h; gint xx, yy; gint width, height; GdkFont* Font1; GdkColor Colour1; GdkGC *gc1= gdk_gc_new(widget->window); #if VERBOSE g_print("\n"); #endif gtk_window_set_title ((GtkWindow *)Window1, (gchar *)"'s' plane"); // Let's colour 'gc1' blue and set the foreground colour to it. Colour1.red=32000; Colour1.green=32000; Colour1.blue=62000; gdk_gc_set_rgb_fg_color(gc1, &Colour1); // g_print("draw brush\n"); Font1 = gdk_font_load("*trebuchet*12*"); if(Font1 == NULL) Font1 = gdk_font_load("*arial*"); if(Font1 == NULL) Font1 = gdk_font_load("*sans*"); if(Font1 == NULL) Font1 = gdk_font_load("*helvetica*--14*"); if(Font1 == NULL) Font1 = gdk_font_load("*helvetica*"); if(Font1 == NULL) Font1 = gdk_font_load("-misc-fixed-*"); if(Font1 == NULL) Font1 = gdk_font_load("-bitstream-bitstream charter-*"); if(Font1 == NULL) Font1 = gdk_font_load("*clean*"); if(Font1 == NULL) g_print("Failed to find font\n"); gdk_window_get_size(widget->window, &w, &h); // OR gdk_drawable_get_size(widget->window, &width, &height); GdkRectangle update_rect; update_rect.x = (gint)1; update_rect.y = (gint)1; update_rect.width = (gint)w - 2; update_rect.height = (gint)h - 2; gdk_draw_rectangle(widget->window, widget->style->white_gc, TRUE, 0, 0, w, h); gdk_draw_line(widget->window, gc1, 10, h/2, w-10, h/2); gdk_draw_line(widget->window, gc1, (4*w)/5, h, (4*w)/5, 1); gdk_draw_string(widget->window, Font1, widget->style->black_gc, (4*w)/5+5, h/2-5, "0,0"); xyscale = scale(filter); for(i=0; iwindow, widget->style->black_gc, xx-10, h/2 + yy, xx+10, h/2 + yy); gdk_draw_line(widget->window, widget->style->black_gc, xx, h/2 + yy - 10, xx, h/2 + yy + 10); sprintf(str, "%1.3lf, %1.2lf", filter.st[i].pole.real(), filter.st[i].pole.imag()); gdk_draw_string(widget->window, Font1, gc1, xx - 80, h/2 + yy - 15, str); } if(filter.type != '0') for(i=0; iwindow, Font1, widget->style->black_gc, w / 10, 20 + i * 15, str); } if(filter.zeroes) { gdk_draw_arc(widget->window, widget->style->black_gc, false, (4*w)/5 - 10, h/2 - 10, 20, 20, 0, 360*64); sprintf(str, "* %1d", filter.zeroes); gdk_draw_string(widget->window, Font1, gc1, (4*w)/5 - 20, h/2 + 20, str); } #if VERBOSE g_print("\n"); #endif } /// Draw the filter's step response. void DrawStep(GtkWidget *widget, gdouble x, gdouble y) { char str[80]; gint i, w, h; gint xx1, yy1, xx2, yy2; gint width, height; double t, v; GdkFont* Font1; GdkColor Colour1; GdkGC *gc1= gdk_gc_new(widget->window); #if VERBOSE g_print("\n"); #endif gtk_window_set_title ((GtkWindow *)Window1, (gchar *)"Time domain"); /// Let's colour 'gc1' blue and set the foreground colour to it. Colour1.red=32000; Colour1.green=32000; Colour1.blue=62000; gdk_gc_set_rgb_fg_color(gc1, &Colour1); /// Select a suitable font. // g_print("draw brush\n"); Font1 = gdk_font_load("*trebuchet*12*"); if(Font1 == NULL) Font1 = gdk_font_load("*arial*"); if(Font1 == NULL) Font1 = gdk_font_load("*sans*"); if(Font1 == NULL) Font1 = gdk_font_load("*helvetica*--14*"); if(Font1 == NULL) Font1 = gdk_font_load("*helvetica*"); if(Font1 == NULL) Font1 = gdk_font_load("-misc-fixed-*"); if(Font1 == NULL) Font1 = gdk_font_load("-bitstream-bitstream charter-*"); if(Font1 == NULL) Font1 = gdk_font_load("*clean*"); if(Font1 == NULL) g_print("Failed to find font\n"); /// Check the size of the window then define an update rectangle. gdk_window_get_size(widget->window, &w, &h); // OR gdk_drawable_get_size(widget->window, &width, &height); GdkRectangle update_rect; update_rect.x = (gint)1; update_rect.y = (gint)1; update_rect.width = (gint)w - 2; update_rect.height = (gint)h - 2; filter.step_calc( ); /// Draw suitable axes. gdk_draw_rectangle(widget->window, widget->style->white_gc, TRUE, 0, 0, w, h); switch(filter.fclass) { case Lowpass: gdk_draw_line(widget->window, gc1, 10, 4*h/5, w-10, 4*h/5); break; case Bandpass: gdk_draw_line(widget->window, gc1, 10, h/2, w-10, h/2); break; case Highpass: gdk_draw_line(widget->window, gc1, 10, 3*h/5, w-10, 3*h/5); break; } gdk_draw_line(widget->window, gc1, w/10, h, w/10, 1); /// Draw the step response curve. for(i=1; iwindow, gc1, xx1, yy1, xx2, yy2); } /// Draw line at measurement time t = filter.tmax * slider_position / TSTEPS; xx1 = w/10 + (slider_position-1) * 8*w/10 / FSTEPS; gdk_draw_line(widget->window, gc1, xx1, h, xx1, 1); sprintf(str, "t = %0.2f ", t); gdk_draw_string(widget->window, Font1, widget->style->black_gc, (7*w)/10, h/8, str); v = filter.step_resp[slider_position-1]; sprintf(str, "v = %0.2f at ", v); gdk_draw_string(widget->window, Font1, widget->style->black_gc, (7*w)/10, h/10, str); /// List the stage Ts and qs etc. if(filter.type != '0') for(i=0; iwindow, Font1, widget->style->black_gc, w / 10, 20 + i * 15, str); } #if VERBOSE g_print("\n"); #endif } /** Draw a rectangle on the screen, and add the axes, poles and the Ts and qs. */ void DrawMap(GtkWidget *widget, gdouble x, gdouble y) { #if VERBOSE g_print("\n"); #endif switch(Draw_mode) { case Splane: Draw_poles(widget, 250, 250); break; case Step: DrawStep(widget, 250, 250); break; case Bode: DrawBode(widget, 250, 250); break; default: cout << "\n\n***UNKNOWN DRAW MODE!***\n\n"; break; } #if VERBOSE g_print("\n"); #endif } /** Define a function to be called if the window is re-exposed. */ static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event) { #if VERBOSE g_print("\n"); #endif DrawMap(widget, 0, 0); #if VERBOSE g_print("\n"); #endif return(TRUE); } /** If a button is pressed, do what? */ static gboolean button_press_event( GtkWidget *widget, GdkEventButton *event ) { gint w, h; #if VERBOSE g_print("\n"); #endif gdk_window_get_size(widget->window, &w, &h); g_print("button press callback\n"); gdk_draw_rectangle(widget->window, widget->style->white_gc, TRUE, 0, 0, w, h); gdk_draw_rectangle(widget->window, widget->style->black_gc, FALSE, (w/2)-4, (h/2)-4, 8, 8); if (event->button == 1) DrawMap (widget, event->x, event->y); #if VERBOSE g_print("\n"); #endif return TRUE; } /** Redraw the diagram if pointer moves. */ static gboolean motion_notify_event( GtkWidget *widget, GdkEventMotion *event ) { int x, y; GdkModifierType state; #if VERBOSE g_print("\n"); #endif if (event->is_hint) gdk_window_get_pointer (event->window, &x, &y, &state); else { x = (gint)event->x; y = (gint)event->y; state = (GdkModifierType)event->state; } // if (state & GDK_BUTTON1_MASK) DrawMap (window, x, y); DrawMap(widget, x, y); #if VERBOSE g_print("\n"); #endif return TRUE; } /*! 'configure_event( )' redraws the window during resizing. */ static gboolean configure_event( GtkWidget *widget, GdkEventConfigure *event ) { gint x, y; #if VERBOSE g_print("\n"); #endif if(pixmap) g_object_unref(pixmap); pixmap = gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1); gdk_draw_rectangle (widget->window, widget->style->white_gc, TRUE, 0, 0, x = widget->allocation.width, y = widget->allocation.height); DrawMap(widget, x, y); #if VERBOSE g_print("\n"); #endif return TRUE; } bool Check_band(void) { const gchar *txt = "Impossible request"; // Buttons are:- sp_button, t_button, f_button, s_button if(filter.fclass == Bandpass && filter.poles % 2) { gtk_widget_set_sensitive(sp_button, false); gtk_widget_set_sensitive(t_button, false); gtk_widget_set_sensitive(f_button, false); gtk_widget_set_sensitive(s_button, false); /* GtkWidget* msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, txt); gtk_widget_show(GTK_WIDGET(msg)); */ } else { gtk_widget_set_sensitive(sp_button, true); gtk_widget_set_sensitive(t_button, true); gtk_widget_set_sensitive(f_button, true); gtk_widget_set_sensitive(s_button, true); } } /*! 'Type_event' call-back function to process changed data in filter class combo-box. */ void Type_event(GtkComboBox *widget, gpointer data) { gchar *entry_text; gtk_widget_hide(cct_diagram); /// Get the text box contents. entry_text = gtk_combo_box_get_active_text(widget); if(strncmp(entry_text, "L", 1) == 0) filter.fclass = Lowpass; else if(strncmp(entry_text, "H", 1) == 0) filter.fclass = Highpass; else if(strncmp(entry_text, "B", 1) == 0) { filter.fclass = Bandpass; gtk_widget_show(GTK_WIDGET(LabelB)); gtk_widget_show(GTK_WIDGET(Bandwidth)); } else { gtk_widget_hide(GTK_WIDGET(LabelB)); gtk_widget_hide(GTK_WIDGET(Bandwidth)); } // Check_band( ); printf("Class: <%s>\n", entry_text); } /*! 'Type2_event' call-back function to process changed data in filter class combo-box. */ void Type2_event(GtkComboBox *widget, gpointer data) { gchar *entry_text; gtk_widget_hide(image); // Get the text box contents. entry_text = gtk_combo_box_get_active_text(widget); if(strncmp(entry_text, "Be", 2) == 0) filter.sclass = Bessel; else if(strncmp(entry_text, "Bu", 2) == 0) filter.sclass = Butterworth; else if(strncmp(entry_text, "C", 1) == 0) { filter.sclass = Chebyshev; gtk_widget_show(GTK_WIDGET(LabelR)); gtk_widget_show(GTK_WIDGET(Ripple)); } else { gtk_widget_hide(GTK_WIDGET(LabelR)); gtk_widget_hide(GTK_WIDGET(Ripple)); } // Check_band( ); printf("Type: <%s>\n", entry_text); } /*! 'Circuit_event' call-back function to process changed data in filter class combo-box. */ void Circuit_event(GtkComboBox *widget, gpointer data) { gchar *entry_text; gtk_widget_hide(cct_diagram); // Get the text box contents. entry_text = gtk_combo_box_get_active_text(widget); if(strncmp(entry_text, "S", 1) == 0) filter.cclass = SallKey; else if(strncmp(entry_text, "R", 1) == 0) filter.cclass = Rauch; else if(strncmp(entry_text, "D", 1) == 0) { filter.cclass = ZDomain; gtk_widget_show(GTK_WIDGET(LabelS)); gtk_widget_show(GTK_WIDGET(SamplingFreq)); } else { gtk_widget_hide(GTK_WIDGET(LabelS)); gtk_widget_hide(GTK_WIDGET(SamplingFreq)); } // if(strncmp(entry_text, "B", 1) == 0) filter.cclass = ????; // Check_band( ); printf("Type: <%s>\n", entry_text); } /*! 'Freq_callback' call-back function to process changed data in frequency combo-box. */ void Freq_callback(GtkWidget *widget, GtkWidget *textbox) { int n; gchar *entry_text; gtk_widget_hide(cct_diagram); double T1; // Get the text box contents. entry_text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(textbox)->entry)); if(strlen(entry_text)) n = sscanf(entry_text, "%lf", &filter.frequency); // Check_band( ); printf("Entry contents 1: <%s>\n", entry_text); // Clear box by writing empty string? May cause a problem when you type text! /* if((n == 0) || (filter.frequency <= 0.0)) { gtk_entry_set_text (GTK_ENTRY(widget), "1.0"); filter.frequency = 1.0; } */ } /*! 'Atten_callback' call-back function to process changed data in attenuation combo-box. */ void Atten_callback(GtkWidget *widget, GtkWidget *textbox) { int n; double a; gchar *entry_text; gtk_widget_hide(cct_diagram); double T1; // Get the text box contents. entry_text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(textbox)->entry)); if(strlen(entry_text)) n = sscanf(entry_text, "%lf", &a); // Clear box by writing empty string? May cause a problem when you type text! // if((n == 0) || (a > 0.0)) { gtk_entry_set_text (GTK_ENTRY(widget), "-3.0"); a = -3.0; } /** dB = 20 * log10(ratio) e.g. if ratio = 0.707, dB = 3.01 Thus, if we have the value in dB: ratio = pow10(dB / 20) */ filter.a0 = pow10(-abs(a) / 20.0); // Check_band( ); printf("Entry contents 1a: <%s>\n", entry_text); } /*! 'SFreq_callback' call-back function to process changed data in frequency combo-box. */ void SFreq_callback(GtkWidget *widget, GtkWidget *textbox) { int n; gchar *entry_text; gtk_widget_hide(cct_diagram); double T1; // Get the text box contents. entry_text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(textbox)->entry)); if(strlen(entry_text)) n = sscanf(entry_text, "%lf", &filter.frequency); /* Check_band( ); */ printf("Entry contents 1a: <%s>\n", entry_text); // Clear box by writing empty string? May cause a problem when you type text! /* if(n == 0) { gtk_entry_set_text (GTK_ENTRY(widget), "1.0"); filter.frequency = 1.0; } */ } /*! 'q_callback' call-back function to process changed data in filter 'q' combo-box. */ void q_callback(GtkWidget *widget, GtkWidget *textbox) { int n; gchar *entry_text; gtk_widget_hide(cct_diagram); // Get the text box contents. entry_text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(textbox)->entry)); // if(strlen(entry_text)) n = sscanf(entry_text, "%lf", &filter.q); // Check_band( ); printf("Entry contents 2: <%s>\n", entry_text); // Clear box by writing empty string? May cause a problem when you type text! // if(n == 0) gtk_entry_set_text (GTK_ENTRY(widget), "?"); } /*! 'order_callback' call-back function to process changed data in filter 'order' combo-box. */ void order_callback(GtkWidget *widget, GtkWidget *textbox) { int n; gchar *entry_text; gtk_widget_hide(cct_diagram); // Get the text box contents. entry_text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(textbox)->entry)); if(strlen(entry_text)) n = sscanf(entry_text, "%d", &filter.poles); // Check_band( ); printf("Entry contents 3: <%s>\n", entry_text); // Clear box by writing empty string? May cause a problem when you type text! // if(n == 0) gtk_entry_set_text (GTK_ENTRY(widget), "?"); } /*! 'ripple_callback' call-back function to process changed data in filter 'ripple' combo-box. */ void ripple_callback(GtkWidget *widget, GtkWidget *textbox) { int n; gchar *entry_text; gtk_widget_hide(cct_diagram); // Get the text box contents. entry_text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(textbox)->entry)); if(strlen(entry_text)) n = sscanf(entry_text, "%lf", &filter.ripple); // Check_band( ); printf("Entry contents 4: <%s>\n", entry_text); // Clear box by writing empty string? May cause a problem when you type text! // if(n == 0) gtk_entry_set_text(GTK_ENTRY(widget), "?"); } /*! 'bandwidth_callback' call-back function to process changed data in filter 'ripple' combo-box. */ void bandwidth_callback(GtkWidget *widget, GtkWidget *textbox) { int n; gchar *entry_text; gtk_widget_hide(cct_diagram); // Get the text box contents. entry_text = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(textbox)->entry)); if(strlen(entry_text)) n = sscanf(entry_text, "%lf", &filter.bandwidth); // Check_band( ); printf("Entry contents 5: <%s>\n", entry_text); // Clear box by writing empty string? May cause a problem when you type text! // if(n == 0) gtk_entry_set_text (GTK_ENTRY(widget), "?"); } /** 'time_event' reads slider value at regular intervals. This avoids large numbers of redrawings as the slider is moved. */ gboolean time_event(GtkWidget *widget) { static int last_value = -1; if (widget->window == NULL) return FALSE; GDateTime *now = g_date_time_new_now_local(); gchar *my_time = g_date_time_format(now, "%H:%M:%S"); slider_position = gtk_adjustment_get_value(GTK_ADJUSTMENT(scale_adj1)); if(slider_position != last_value) { sprintf(str, "Scale position = %d\n", slider_position); last_value = slider_position; #if VERBOSE g_print(str); #endif gtk_widget_queue_draw (widget); } g_free(my_time); g_date_time_unref(now); return TRUE; } int main(int argc, char *argv[ ]) { std::cout << "'rbfilter' version " << VERSION << "\n"; std::cout << "Copyright (c) Roger Burghall 2013..2016\n"; ofstream logfile; logfile.open("./rbfilter.log", ios::trunc); logfile << "rbfilter log file.\n******************\n"; logfile.close( ); GtkWidget *hbox, *vbox_draw, *vbox_main; GList *glist, *glist1; GtkWidget *vbox_cct; //* Initialise Gnome; similar to gtk_init(). */ gnome_init("Filter-designer", "3.01", argc, argv); /// First window, for controls. app = gnome_app_new("drawing-example", "Control"); assert(app != NULL); /// Draw a second window for graphs etc. Window1 = gtk_window_new(GTK_WINDOW_TOPLEVEL); assert(Window1 != NULL); /// Centre the graphics window. gtk_window_set_position ((GtkWindow *)Window1, GTK_WIN_POS_CENTER); gtk_widget_set_size_request(GTK_WIDGET(Window1), 600, 650); gtk_window_set_title ((GtkWindow *)Window1, (gchar *)"Graphics"); vbox_draw = gtk_vbox_new(FALSE, 5); scale_adj1 = gtk_adjustment_new (FSTEPS/2.0, 0.0, (double)FSTEPS, 10.0, 10.0, 10.0); scale1 = gtk_hscale_new (GTK_ADJUSTMENT(scale_adj1)); gtk_scale_set_draw_value ((GtkScale*)scale1, FALSE); /// Put a vbox in the window and the slider in the vbox. gtk_container_add(GTK_CONTAINER(Window1), vbox_draw); gtk_box_pack_start(GTK_BOX(vbox_draw), scale1, FALSE, FALSE, 0); gtk_window_set_default_size (GTK_WINDOW(Window1), 400, 300); /// Create a drawing area which must receive extra space when window is enlarged. Area1 = gtk_drawing_area_new( ); gtk_widget_set_size_request(GTK_WIDGET(Area1), XSIZE, YSIZE); gtk_box_pack_start(GTK_BOX(vbox_draw), Area1, TRUE, TRUE, 0); gtk_widget_show(vbox_draw); gtk_widget_show(Area1); gtk_widget_hide(scale1); /// Draw a third window, for showing the circuit diagrams cct_diagram = gtk_window_new(GTK_WINDOW_TOPLEVEL); assert(cct_diagram != NULL); gtk_window_set_position ((GtkWindow *)cct_diagram, GTK_WIN_POS_NONE); gtk_widget_set_size_request(GTK_WIDGET(cct_diagram), 650, 365); gtk_window_set_title ((GtkWindow *)cct_diagram, (gchar *)"Realisation"); GtkWidget *cct_box = gtk_hbox_new(false, 0); image = gtk_image_new( ); gtk_container_add(GTK_CONTAINER(cct_diagram), cct_box); gtk_box_pack_start(GTK_BOX(cct_box), image, false, false, 3); /// Remove close icon from the realisation window. gtk_window_set_deletable((GtkWindow *)cct_diagram, false); gtk_widget_show(image); gtk_widget_show(cct_box); gtk_widget_hide(cct_diagram); Draw_mode = Splane; /// Create a 'vbox' and place it in the application. vbox_main = gtk_vbox_new(FALSE, 5); gnome_app_set_contents(GNOME_APP(app), vbox_main); gtk_widget_show(vbox_main); /// Position the control window to the left of the screen. /// Check size of screen. gint scr_w = gdk_screen_width( ); gint scr_h = gdk_screen_height( ); /// Place the window. gtk_window_move ((GtkWindow *)app, scr_w/10, scr_h/5); /// Create an 'hbox' and put it in the 'vbox' hbox = gtk_hbox_new(FALSE, 5); gtk_container_add (GTK_CONTAINER (vbox_main), hbox); gtk_widget_show(hbox); /// Create button 'vbox'es and place them in the 'hbox'. vbox1 = gtk_vbox_new(FALSE, 5); vbox2 = gtk_vbox_new(FALSE, 5); gtk_box_pack_start (GTK_BOX (hbox), vbox1, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0); gtk_widget_show(vbox1); gtk_widget_show(vbox2); /// Put data entry boxes in 'vbox1':- /// Combobox permitting only preset entries for filter class GtkWidget *Label = gtk_label_new ("Class"); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Label), TRUE, FALSE, 0); gtk_widget_show(GTK_WIDGET(Label)); Type = /* GTK_COMBO_BOX */ (gtk_combo_box_new_text( )); gtk_combo_box_append_text(GTK_COMBO_BOX(Type), "Low-pass"); gtk_combo_box_append_text(GTK_COMBO_BOX(Type), "High-pass"); gtk_combo_box_append_text(GTK_COMBO_BOX(Type), "Band-pass"); gtk_combo_box_set_active(GTK_COMBO_BOX(Type), 0); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Type), TRUE, TRUE, 5); g_signal_connect(GTK_OBJECT(Type), "changed", GTK_SIGNAL_FUNC(Type_event), Type); gtk_widget_show(GTK_WIDGET(Type)); Type2 = /* GTK_COMBO_BOX */ (gtk_combo_box_new_text( )); gtk_combo_box_append_text(GTK_COMBO_BOX(Type2), "Bessel"); gtk_combo_box_append_text(GTK_COMBO_BOX(Type2), "Butterworth"); gtk_combo_box_append_text(GTK_COMBO_BOX(Type2), "Chebyshev"); gtk_combo_box_set_active(GTK_COMBO_BOX(Type2), 0); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Type2), TRUE, TRUE, 5); g_signal_connect(GTK_OBJECT(Type2), "changed", GTK_SIGNAL_FUNC(Type2_event), Type2); gtk_widget_show(GTK_WIDGET(Type2)); /// Combobox permitting only preset entries for filter class Label = gtk_label_new ("Circuit"); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Label), TRUE, FALSE, 0); gtk_widget_show(GTK_WIDGET(Label)); Circuit = /* GTK_COMBO_BOX */ (gtk_combo_box_new_text( )); gtk_combo_box_append_text(GTK_COMBO_BOX(Circuit), "Sallen and Key"); gtk_combo_box_append_text(GTK_COMBO_BOX(Circuit), "Rauch"); gtk_combo_box_append_text(GTK_COMBO_BOX(Circuit), "Discrete"); gtk_combo_box_set_active(GTK_COMBO_BOX(Circuit), 0); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Circuit), TRUE, TRUE, 5); g_signal_connect(GTK_OBJECT(Circuit), "changed", GTK_SIGNAL_FUNC(Circuit_event), Circuit); gtk_widget_show(GTK_WIDGET(Circuit)); /// Combobox permitting non-standard entries for cut-off frequency Label = gtk_label_new ("Freq"); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Label), TRUE, FALSE, 0); gtk_widget_show(GTK_WIDGET(Label)); Freq = gtk_combo_new( ); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Freq), TRUE, FALSE, 0); glist = NULL; glist = g_list_append(glist, (gpointer)"1.0"); glist = g_list_append(glist, (gpointer)"10.0"); glist = g_list_append(glist, (gpointer)"100.0"); gtk_combo_set_popdown_strings(GTK_COMBO(Freq), glist); g_signal_connect(GTK_COMBO(Freq)->entry, "changed", G_CALLBACK(Freq_callback), Freq); gtk_widget_show(GTK_WIDGET(Freq)); /// Combobox permitting non-standard entries for cut-off attenuation Label = gtk_label_new ("Atten (dB)"); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Label), TRUE, FALSE, 0); gtk_widget_show(GTK_WIDGET(Label)); Atten = gtk_combo_new( ); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Atten), TRUE, FALSE, 0); glist = NULL; glist = g_list_append(glist, (gpointer)"-3.0"); glist = g_list_append(glist, (gpointer)"-6.0"); glist = g_list_append(glist, (gpointer)"-9.0"); gtk_combo_set_popdown_strings(GTK_COMBO(Atten), glist); g_signal_connect(GTK_COMBO(Atten)->entry, "changed", G_CALLBACK(Atten_callback), Atten); gtk_widget_show(GTK_WIDGET(Atten)); /// Combobox permitting non-standard entries for sampling frequency LabelS = gtk_label_new ("Sampling Freq"); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(LabelS), TRUE, FALSE, 0); // gtk_widget_show(GTK_WIDGET(Label)); SamplingFreq = gtk_combo_new( ); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(SamplingFreq), TRUE, FALSE, 0); glist = NULL; glist = g_list_append(glist, (gpointer)"100.0"); glist = g_list_append(glist, (gpointer)"1000.0"); glist = g_list_append(glist, (gpointer)"100000.0"); gtk_combo_set_popdown_strings(GTK_COMBO(SamplingFreq), glist); g_signal_connect(GTK_COMBO(Freq)->entry, "changed", G_CALLBACK(SFreq_callback), SamplingFreq); // gtk_widget_show(GTK_WIDGET(SamplingFreq)); /// Combobox permitting non-standard entries for filter order Label = gtk_label_new ("Prototype order"); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Label), TRUE, FALSE, 0); gtk_widget_show(GTK_WIDGET(Label)); Order = gtk_combo_new( ); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Order), TRUE, FALSE, 0); glist = NULL; glist = g_list_append(glist, (gpointer)"3"); glist = g_list_append(glist, (gpointer)"4"); glist = g_list_append(glist, (gpointer)"5"); gtk_combo_set_popdown_strings(GTK_COMBO(Order), glist); g_signal_connect(GTK_COMBO(Order)->entry, "changed", G_CALLBACK(order_callback), Order); gtk_widget_show(GTK_WIDGET(Order)); /// Combobox permitting non-standard entries for filter ripple (Chebyshev only) LabelR = gtk_label_new ("ripple"); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(LabelR), TRUE, FALSE, 0); Ripple = gtk_combo_new( ); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Ripple), TRUE, FALSE, 0); glist1 = NULL; glist1 = g_list_append(glist1, (gpointer)"0.5"); glist1 = g_list_append(glist1, (gpointer)"1.0"); glist1 = g_list_append(glist1, (gpointer)"2.0"); gtk_combo_set_popdown_strings(GTK_COMBO(Ripple), glist1); g_signal_connect(GTK_COMBO(Ripple)->entry, "changed", G_CALLBACK(ripple_callback), Ripple); /// Combobox permitting non-standard entries for filter bandwidth (Bandpass only) LabelB = gtk_label_new ("bandwidth"); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(LabelB), TRUE, FALSE, 0); Bandwidth = gtk_combo_new( ); gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(Bandwidth), TRUE, FALSE, 0); glist1 = NULL; glist1 = g_list_append(glist1, (gpointer)"0.25"); glist1 = g_list_append(glist1, (gpointer)"0.125"); glist1 = g_list_append(glist1, (gpointer)"0.0625"); gtk_combo_set_popdown_strings(GTK_COMBO(Bandwidth), glist1); g_signal_connect(GTK_COMBO(Bandwidth)->entry, "changed", G_CALLBACK(bandwidth_callback), Bandwidth); /// Create "S-p", "T-d" and "F-d" buttons and place them in 'vbox2'. sp_button = gtk_button_new_with_label ("s-plane"); t_button = gtk_button_new_with_label ("Time"); f_button = gtk_button_new_with_label ("Freq"); s_button = gtk_button_new_with_label ("Synth"); // z_button = gtk_button_new_with_label ("Bilinear"); gtk_box_pack_start (GTK_BOX (vbox2), sp_button, TRUE, FALSE, 20); gtk_box_pack_start (GTK_BOX (vbox2), t_button, TRUE, FALSE, 20); gtk_box_pack_start (GTK_BOX (vbox2), f_button, TRUE, FALSE, 20); gtk_box_pack_start (GTK_BOX (vbox2), s_button, TRUE, FALSE, 20); // gtk_box_pack_start (GTK_BOX (vbox2), z_button, TRUE, FALSE, 20); gtk_widget_show(sp_button); gtk_widget_show(t_button); gtk_widget_show(f_button); gtk_widget_show(s_button); // gtk_widget_show(z_button); /// Connect button signals gchar *Event2 = (gchar *)"clicked"; gchar *Ctrl1 = (gchar *)"S-p\n"; gchar *Ctrl2 = (gchar *)"T-d\n"; gchar *Ctrl3 = (gchar *)"F-d\n"; gchar *Ctrl4 = (gchar *)"Syn\n"; gchar *Ctrl5 = (gchar *)"Z-t\n"; gtk_signal_connect(GTK_OBJECT (sp_button), Event2, GTK_SIGNAL_FUNC (callback::clicked), Ctrl1); gtk_signal_connect(GTK_OBJECT (t_button), Event2, GTK_SIGNAL_FUNC (callback::clicked), Ctrl2); gtk_signal_connect(GTK_OBJECT (f_button), Event2, GTK_SIGNAL_FUNC (callback::clicked), Ctrl3); gtk_signal_connect(GTK_OBJECT (s_button), Event2, GTK_SIGNAL_FUNC (callback::clicked), Ctrl4); // gtk_signal_connect(GTK_OBJECT (z_button), Event2, GTK_SIGNAL_FUNC (callback::clicked), Ctrl5); //* Bind "destroy" event to gtk_main_quit. */ gchar *Event1 = "destroy"; gtk_signal_connect(GTK_OBJECT (app), Event1, GTK_SIGNAL_FUNC (callback::quit), NULL); gtk_signal_connect(GTK_OBJECT (Window1), Event1, GTK_SIGNAL_FUNC (pseudo_quit), NULL); gtk_signal_connect(GTK_OBJECT(Area1), "expose_event", GTK_SIGNAL_FUNC(expose_event), NULL); gtk_signal_connect (GTK_OBJECT(Area1),"configure_event", (GtkSignalFunc) configure_event, NULL); gtk_signal_connect (GTK_OBJECT (Area1), "motion_notify_event", (GtkSignalFunc) motion_notify_event, NULL); gtk_signal_connect (GTK_OBJECT (Area1), "button_press_event", GTK_SIGNAL_FUNC(button_press_event), NULL); g_timeout_add(500, (GSourceFunc) time_event, (gpointer) Window1); time_event(Window1); gtk_widget_set_events (Window1, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); gtk_drawing_area_size((GtkDrawingArea*)Area1, XSIZE, YSIZE); gtk_widget_show(app); gtk_widget_show(Area1); gtk_widget_show(Area1); gtk_widget_show(Window1); gtk_main( ); return 0; }