/* * ALSA mixer console for Echoaudio soundcards. * Copyright (C) 2003 Giuliano Pochini * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * 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 */ #define EM_VERSION "%s Echomixer v" VERSION /******* Remove the "//" if you want to compile Echomixer in reverse mode. *******/ //#define REVERSE /******* Constants marked with *M* can be modified to customize the interface. *******/ #define BORDER 6 // *M* Inner border of GTK containers #define SPACING 8 // *M* Spacing of control sections // Graphic mixer constants #define GM_BARWIDTH 5 // *M* Width of meters bars #define XCELLBORDER 2 // *M* Space between the grid lines and the content of the cell #define YCELLBORDER 2 // *M* #define XCELLDIM 20 // *M* Width of the cell #define YCELLDIM 32 // Height of the cell #define XCELLTOT (1+XCELLBORDER*2+XCELLDIM) // line + left border + cell + right border #define YCELLTOT (1+YCELLBORDER*2+YCELLDIM) #define XVOLUME (1+XCELLBORDER+3) // Position of the volume slider #define XMETER (1+XCELLBORDER-GM_BARWIDTH/2+13) // Position of the VU bar // VU-meter constants #define VU_XGRAF 30 // Left margin of the graphic #define VU_YGRAF 20 // Top margin #define VU_BARWIDTH 6 // *M* Width of VU-meters bars #define VU_BARSEP 2 // *M* Space between bars #define SHORTSTEP 1 // *M* 1dB (when the users moves a slider with cursor keys) #define LONGSTEP 6 // *M* 6dB (with Page up/down or clicking the background) #define DIGITAL_MODES 16 // Max number of digital modes #define ECHO_CLOCKS 8 // Max number of clock sources #define INPUT 0 #define OUTPUT 1 #define ECHO_MAXAUDIO_IOS 32 // The maximum number of inputs + outputs #define ECHO_MAXAUDIOINPUTS 32 // Max audio input channels #define ECHO_MAXAUDIOOUTPUTS 32 // Max audio output channels #define ECHOGAIN_MUTED (-128) // Minimum possible gain #define ECHOGAIN_MINOUT (-128) // Min output gain (unit is 1dB) #define ECHOGAIN_MAXOUT 6 // Max output gain (unit is 1dB) #define ECHOGAIN_MININP (-50) // Min input gain (unit is 0.5dB) #define ECHOGAIN_MAXINP 50 // Max input gain (unit is 0.5dB) // GTK+ adjustment widgets have the mininum value at top and maximum at bottom, // position, but we need the opposite. This function puts the scale upside-down. #define INVERT(x) (ECHOGAIN_MINOUT+ECHOGAIN_MAXOUT-(x)) #define IN_INVERT(x) (ECHOGAIN_MININP+ECHOGAIN_MAXINP-(x)) // REAL is for debugging only. #define REAL //#define CTLID_DEBUG(x) printf x #define CTLID_DEBUG(x) #define UI_DEBUG(x) //#define UI_DEBUG(x) printf x #include #include #include #include #include #include #include char card[64], cardId[16]; char dmodeName[DIGITAL_MODES][64], clocksrcName[DIGITAL_MODES][64], spdifmodeName[DIGITAL_MODES][64]; int nLOut, nIn, fdIn, fdOut, nPOut, ClockMask; int ndmodes, nclocksrc, nspdifmodes; int GMixerRow, GMixerColumn, Gang, AutoClock; int lineinId, pcmoutId, lineoutId, mixerId, vmixerId, p4InId, p4OutId, dmodeId; int clocksrcId, spdifmodeId, vuswitchId, vumetersId, channelsId, phantomId, automuteId; int metersStreams, metersNumber, metersTypes; int outvolCount; int mouseY, mouseButton; int dmodeVal, clocksrcVal, spdifmodeVal; int VUwidth, VUheight, Mixwidth, Mixheight; #define DONT_DRAW (ECHOGAIN_MUTED-1) #define DONT_CHANGE (1<<31) #define NOPOS 999999 struct geometry { int st; // window status: 0 = hidden ; 1 = visible ; NOPOS = no stored setting GtkWidget *toggler; // The toggle button that controls this window int x, y; int w, h; } Mainw_geom, Miscw_geom, PVw_geom, LVw_geom, Mixerw_geom, Vmixerw_geom, VUw_geom, GMw_geom; // This structure contains the first and the last row of each section of the graphic mixer window struct { int VmixerFirst, VmixerLast; // Rows int LineOut; // There is only one row int Inputs; // Rows int Outputs; // Columns } GMixerSection; struct mixel { int id; int Gain; }; snd_ctl_t *ctlhandle; struct mixerControl_s { int input, inputs; int output, outputs; int id; GtkWidget *window; GtkWidget *volume[ECHO_MAXAUDIOOUTPUTS]; GtkWidget *label[ECHO_MAXAUDIOOUTPUTS]; GtkAdjustment *adj[ECHO_MAXAUDIOOUTPUTS]; GtkWidget *outsel[ECHO_MAXAUDIOOUTPUTS]; GtkWidget *inpsel[ECHO_MAXAUDIOINPUTS]; GtkWidget *vchsel[ECHO_MAXAUDIOOUTPUTS]; struct mixel mixer[ECHO_MAXAUDIOOUTPUTS][ECHO_MAXAUDIOOUTPUTS]; } mixerControl, vmixerControl; struct VolumeControl_s { int input, output; // Currently selected channels int inputs, outputs; int id; GtkWidget *window; GtkWidget *volume[ECHO_MAXAUDIOOUTPUTS]; GtkWidget *label[ECHO_MAXAUDIOOUTPUTS]; GtkAdjustment *adj[ECHO_MAXAUDIOOUTPUTS]; int Gain[ECHO_MAXAUDIOOUTPUTS]; } lineinControl, lineoutControl, pcmoutControl; struct NominalLevelControl_s { int id; int Channels; char Level[ECHO_MAXAUDIOINPUTS]; GtkWidget *Button[ECHO_MAXAUDIOINPUTS]; } NominalIn, NominalOut; struct SwitchControl_s { int id; int value; GtkWidget *Button; } PhantomPower, Automute; GtkWidget *clocksrc_menuitem[ECHO_CLOCKS]; GtkWidget *dmodeOpt, *clocksrcOpt, *spdifmodeOpt, *phantomChkbutton, *autoclockChkbutton; GtkWidget *window, *Mainwindow, *Miscwindow, *LVwindow, *VUwindow, *GMwindow; GtkWidget *VUdarea, *Mixdarea; gint VUtimer, Mixtimer, clocksrctimer; cairo_t *cr = NULL; static cairo_surface_t *VUpixmap = NULL; static cairo_surface_t *Mixpixmap = NULL; PangoFontDescription *fnt = NULL; void Clock_source_activate(GtkWidget *widget, gpointer clk); int CountBits(int n) { int c; c=0; while (n) { c++; n&=(n-1); } return(c); } void ClampOutputVolume(int *v) { if (*v>ECHOGAIN_MAXOUT) *v=ECHOGAIN_MAXOUT; else if (*vECHOGAIN_MAXINP) *v=ECHOGAIN_MAXINP; else if (*v=fdIn+2) mixerControl.input=0; } #else // REVERSE // Enable/disable widgets that control ADAT digital channels void SetSensitivity(int enable) { int i; for (i=fdIn+2; i=fdOut+2) mixerControl.output=0; if (vmixerId && !enable && vmixerControl.output>=fdOut+2) vmixerControl.output=0; } #endif // REVERSE // Read current control settings. int ReadControl(int *vol, int channels, int numid, int iface) { snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; int err, ch; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_id_set_interface(id, iface); snd_ctl_elem_id_set_numid(id, numid); snd_ctl_elem_value_set_id(control, id); if ((err=snd_ctl_elem_read(ctlhandle, control))<0) { printf("Control %s element read error: %s\n", card, snd_strerror(err)); return(err); } for (ch=0; chid); snd_ctl_elem_value_set_id(control, id); if ((err=snd_ctl_elem_read(ctlhandle, control))<0) printf("Control %s element read error: %s\n", card, snd_strerror(err)); for (i=0; iChannels; i++) NominalLevel->Level[i]=snd_ctl_elem_value_get_integer(control, i); } int SetMixerGain(struct mixel *mxl, int Gain) { snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; int err; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_id_set_numid(id, mxl->id); snd_ctl_elem_value_set_id(control, id); snd_ctl_elem_value_set_integer(control, 0, Gain); if ((err = snd_ctl_elem_write(ctlhandle, control)) < 0) { printf("Control %s element write error: %s\n", card, snd_strerror(err)); return(err); } return(0); } // Read current (v)mixer settings. void ReadMixer(struct mixerControl_s *mixer) { int err, in, out; snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; #ifndef REAL return; #endif snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); for (out=0; outoutputs; out++) { for (in=0; ininputs; in++) { snd_ctl_elem_id_set_numid(id, mixer->mixer[out][in].id); snd_ctl_elem_value_set_id(control, id); if ((err=snd_ctl_elem_read(ctlhandle, control))<0) printf("InitMixer - Control %s element read error: %s\n", card, snd_strerror(err)); mixer->mixer[out][in].Gain=snd_ctl_elem_value_get_integer(control, 0); } } } void GetChannels(void) { snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; int err; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_HWDEP); snd_ctl_elem_id_set_numid(id, channelsId); snd_ctl_elem_value_set_id(control, id); if ((err = snd_ctl_elem_read(ctlhandle, control)) < 0) { printf("GetChannels() read error: %s\n", snd_strerror(err)); exit(1); } if (!nIn) { // Only read the first time (mainly for debugging, see #define REAL) nIn=snd_ctl_elem_value_get_integer(control, 0); // Number of input channels fdIn=snd_ctl_elem_value_get_integer(control, 1); // First digital in (= number of analog input channels) nLOut=snd_ctl_elem_value_get_integer(control, 2); // Number of output channels fdOut=snd_ctl_elem_value_get_integer(control, 3); // First digital out nPOut=snd_ctl_elem_value_get_integer(control, 4); // Number of virtual output channels (==nLOut on non-vmixer cards) mixerControl.outputs = nLOut; mixerControl.inputs = nIn; if (vmixerId) { vmixerControl.outputs = nLOut; vmixerControl.inputs = nPOut; /* For outputs and inputs. */ metersStreams = 2; } else { /* For outputs, inputs and system outputs. */ metersStreams = 3; } /* For the number of channels. */ metersNumber = 16; /* For each of levels and peaks. */ metersTypes = 2; } ClockMask=snd_ctl_elem_value_get_integer(control, 5); // Bitmask of available input clocks } // Read what input clocks are valid and (de)activate the pop-down menu items accordingly gint CheckInputs(gpointer unused) { int clk, valid, source; GetChannels(); source=-1; // Switch to internal if the source is not available if (AutoClock>=0 && !(ClockMask & (1<=0 && source!=clocksrcVal) { // Set the clock source, but do not change the value of AutoClock Clock_source_activate(clocksrc_menuitem[source], (gpointer)(long)(source|DONT_CHANGE)); gtk_combo_box_set_active(GTK_COMBO_BOX(clocksrcOpt), clocksrcVal); } return(TRUE); } void DrawBar(int x, int y, int level, int peak, int gain) { GdkColor Bars={0x00FF00, 0, 0, 0}; GdkColor Bars1={0x000000, 0, 0, 0}; GdkColor Peak={0x1BABFF, 0, 0, 0}; GdkColor Level={0xC0B000, 0, 0, 0}; int db; // Get the drawing context from the widget’s window cr = gdk_cairo_create(gtk_widget_get_window(window)); x=XMETER+XCELLTOT*x; y=YCELLTOT*y+YCELLBORDER; if (level>ECHOGAIN_MUTED) { // Draw the "integer" part of the bar db=level>>2; GdkRGBA rgba_color = { .red = Bars.red / 65535.0, .green = Bars.green / 65535.0, .blue = Bars.blue / 65535.0, .alpha = 1.0 // Assuming full opacity }; // Set the color (Bars should be a GdkRGBA) gdk_cairo_set_source_rgba(cr, &rgba_color); // Draw the rectangle cairo_rectangle(cr, x, y - db, GM_BARWIDTH, YCELLDIM + db); cairo_fill(cr); // Fill the rectangle with the set color // Draw the antialiased part Bars1.pixel=(level&3) << (6 + 8); // 4 levels (256/4==64==2^6) of green (2^8) if (Bars1.pixel) { GdkRGBA rgba_color = { .red = Bars1.red / 65535.0, .green = Bars1.green / 65535.0, .blue = Bars1.blue / 65535.0, .alpha = 1.0 // Assuming full opacity }; // Set the color (Bars1 should be a GdkRGBA) gdk_cairo_set_source_rgba(cr, &rgba_color); // Draw the thin rectangle (1px height) cairo_rectangle(cr, x, y - db - 1, GM_BARWIDTH, 1); cairo_fill(cr); } } // Draw the peak if (peak>ECHOGAIN_MUTED) { db=peak>>2; GdkRGBA rgba_color = { .red = Peak.red / 65535.0, .green = Peak.green / 65535.0, .blue = Peak.blue / 65535.0, .alpha = 1.0 // Assuming full opacity }; // Set the color (Peak should be a GdkRGBA) gdk_cairo_set_source_rgba(cr, &rgba_color); // Draw the peak indicator (1px height) cairo_rectangle(cr, x, y - db, GM_BARWIDTH, 1); cairo_fill(cr); } // Draw the mixer gain if (gain>=ECHOGAIN_MUTED) { db=gain>>2; GdkRGBA rgba_color = { .red = Level.red / 65535.0, .green = Level.green / 65535.0, .blue = Level.blue / 65535.0, .alpha = 1.0 // Assuming full opacity }; gdk_cairo_set_source_rgba(cr, &rgba_color); // Draw the first rectangle (vertical line) cairo_rectangle(cr, x - XMETER + XVOLUME, y, 1, YCELLDIM); // Draw the second rectangle (horizontal line) cairo_rectangle(cr, x - XMETER + XVOLUME - 2, y - db, 5, 1); cairo_fill(cr); } // Clean up cairo_destroy(cr); } // Draw the matrix mixer gint DrawMixer(gpointer unused) { cairo_t *cr; int InLevel[ECHO_MAXAUDIOINPUTS]; int InPeak[ECHO_MAXAUDIOINPUTS]; int OutLevel[ECHO_MAXAUDIOOUTPUTS]; int OutPeak[ECHO_MAXAUDIOOUTPUTS]; int VirLevel[ECHO_MAXAUDIOOUTPUTS]; int VirPeak[ECHO_MAXAUDIOOUTPUTS]; static int InClip[ECHO_MAXAUDIOINPUTS]; static int OutClip[ECHO_MAXAUDIOOUTPUTS]; char str[8]; int i, o, dB; if (!Mixpixmap) return TRUE; GetVUmeters(InLevel, InPeak, OutLevel, OutPeak, VirLevel, VirPeak); GdkWindow *window = gtk_widget_get_window(Mixdarea); if (!window) return TRUE; cr = gdk_cairo_create(window); // Clear the background to black cairo_set_source_rgb(cr, 0, 0, 0); cairo_rectangle(cr, 0, 0, Mixwidth, Mixheight); cairo_fill(cr); // Highlight cairo_set_source_rgb(cr, 0, 0, 0.47); cairo_rectangle(cr, 0, YCELLTOT * mixerControl.input, Mixwidth, YCELLTOT); cairo_fill(cr); if (vmixerId) { cairo_set_source_rgb(cr, 0.38, 0, 0); cairo_rectangle(cr, 0, YCELLTOT * (GMixerSection.VmixerFirst + vmixerControl.input), Mixwidth, YCELLTOT); cairo_fill(cr); } // Draw the grid and labels for (i = 0; i < GMixerSection.LineOut; i++) { cairo_set_source_rgb(cr, 0.47, 0.47, 0.47); cairo_move_to(cr, 0, YCELLTOT * (i + 1) - 1); cairo_line_to(cr, Mixwidth, YCELLTOT * (i + 1) - 1); cairo_stroke(cr); } for (o = 0; o < nLOut; o++) { cairo_move_to(cr, XCELLTOT * (o + 1), 0); cairo_line_to(cr, XCELLTOT * (o + 1), Mixheight); cairo_stroke(cr); } // Draw input levels and peaks for (i = 0; i < GMixerSection.Inputs; i++) DrawBar(0, i, InLevel[i], InPeak[i], DONT_DRAW); if (vmixerId) { for (i = 0; i < vmixerControl.inputs; i++) DrawBar(0, i + GMixerSection.VmixerFirst, VirLevel[i], VirPeak[i], DONT_DRAW); } for (o = 0; o < GMixerSection.Outputs; o++) DrawBar(o + 1, GMixerSection.LineOut, OutLevel[o], OutPeak[o], lineoutControl.Gain[o]); for (o = 0; o < GMixerSection.Outputs; o++) { for (i = 0; i < GMixerSection.Inputs; i++) { dB = Add_dB(mixerControl.mixer[o][i].Gain, InLevel[i]); DrawBar(o + 1, i, dB, DONT_DRAW, mixerControl.mixer[o][i].Gain); } } if (vmixerId) { for (o = 0; o < GMixerSection.Outputs; o++) for (i = 0; i < vmixerControl.inputs; i++) { dB = Add_dB(vmixerControl.mixer[o][i].Gain, VirLevel[i]); DrawBar(o + 1, i + GMixerSection.VmixerFirst, dB, DONT_DRAW, vmixerControl.mixer[o][i].Gain); } } cairo_destroy(cr); return TRUE; } // Draw the VU-meter gint DrawVUmeters(gpointer unused) { GdkRectangle update_rect; int InLevel[ECHO_MAXAUDIOINPUTS]; int InPeak[ECHO_MAXAUDIOINPUTS]; int OutLevel[ECHO_MAXAUDIOOUTPUTS]; int OutPeak[ECHO_MAXAUDIOOUTPUTS]; int VirLevel[ECHO_MAXAUDIOOUTPUTS]; int VirPeak[ECHO_MAXAUDIOOUTPUTS]; static int InClip[ECHO_MAXAUDIOINPUTS]; static int OutClip[ECHO_MAXAUDIOOUTPUTS]; int i, x, dB; char str[16]; GdkRGBA Selected = {0xC8 / 255.0, 0, 0, 1}; GdkRGBA Grid = {0x96 / 255.0, 0x94 / 255.0, 0xC4 / 255.0, 1}; GdkRGBA Grid2 = {0x64 / 255.0, 0x63 / 255.0, 0x83 / 255.0, 1}; GdkRGBA dBValues = {0, 0.69, 0, 1}; GdkRGBA AnBars = {0, 0.88, 0.72, 1}; GdkRGBA DiBars = {0.60, 0.88, 0, 1}; GdkRGBA ClipPeak = {0, 0, 0, 0}; GdkRGBA Peak = {0, 1, 0, 1}; if (!VUpixmap) return TRUE; update_rect.x = 0; update_rect.y = 0; update_rect.width = VUwidth; update_rect.height = VUheight; GetVUmeters(InLevel, InPeak, OutLevel, OutPeak, VirLevel, VirPeak); if (!cr) { // Cairo context cr = gdk_cairo_create(gtk_widget_get_window(VUdarea)); for (i = 0; i < nIn; i++) InClip[i] = 0; for (i = 0; i < nLOut; i++) OutClip[i] = 0; } // Clear the image cairo_set_source_rgba(cr, 0, 0, 0, 1); cairo_paint(cr); // Draw the dB scale and the grid PangoLayout *layout = gtk_widget_create_pango_layout(VUdarea, NULL); pango_layout_set_font_description(layout, fnt); cairo_set_source_rgba(cr, Peak.red, Peak.green, Peak.blue, 1); cairo_move_to(cr, 2, VU_YGRAF - 12 + 4); cairo_show_text(cr, " dB"); for (i = 0; i <= 120; i += 12) { sprintf(str, "%4d", -i); cairo_set_source_rgba(cr, dBValues.red, dBValues.green, dBValues.blue, 1); cairo_move_to(cr, 2, VU_YGRAF + i + 4); cairo_show_text(cr, str); cairo_set_source_rgba(cr, Grid.red, Grid.green, Grid.blue, 1); cairo_rectangle(cr, VU_XGRAF, VU_YGRAF + i, VUwidth - VU_XGRAF, 1); cairo_fill(cr); } cairo_set_source_rgba(cr, Grid2.red, Grid2.green, Grid2.blue, 1); cairo_rectangle(cr, VU_XGRAF, VU_YGRAF + 128, VUwidth - VU_XGRAF, 1); cairo_fill(cr); x = VU_XGRAF + VU_BARSEP; // Draw inputs for (i = 0; i < nIn; i++) { if (i < fdIn) cairo_set_source_rgba(cr, AnBars.red, AnBars.green, AnBars.blue, 1); else cairo_set_source_rgba(cr, DiBars.red, DiBars.green, DiBars.blue, 1); dB = InLevel[i]; cairo_rectangle(cr, x, VU_YGRAF - dB, VU_BARWIDTH, 129 + VU_YGRAF - (VU_YGRAF - dB)); cairo_fill(cr); dB = InPeak[i]; if (dB == 0) InClip[i] = 64; if (InClip[i]) { InClip[i]--; ClipPeak.red = (InClip[i] << 18) / 255.0; ClipPeak.green = (255 - (InClip[i] * 3)) / 255.0; cairo_set_source_rgba(cr, ClipPeak.red, ClipPeak.green, ClipPeak.blue, 1); } else { cairo_set_source_rgba(cr, Peak.red, Peak.green, Peak.blue, 1); } cairo_rectangle(cr, x, VU_YGRAF - dB, VU_BARWIDTH, 1); cairo_fill(cr); if (mixerControl.input == i) { cairo_set_source_rgba(cr, Selected.red, Selected.green, Selected.blue, 1); cairo_rectangle(cr, x + 1, VU_YGRAF + 128 + 3, VU_BARWIDTH - 2, 1); cairo_fill(cr); cairo_rectangle(cr, x, VU_YGRAF + 128 + 4, VU_BARWIDTH, 1); cairo_fill(cr); } x += VU_BARWIDTH + VU_BARSEP; } // Draw outputs x += VU_BARWIDTH + VU_BARSEP; for (i = 0; i < nLOut; i++) { if (i < fdOut) cairo_set_source_rgba(cr, AnBars.red, AnBars.green, AnBars.blue, 1); else cairo_set_source_rgba(cr, DiBars.red, DiBars.green, DiBars.blue, 1); dB = OutLevel[i]; cairo_rectangle(cr, x, VU_YGRAF - dB, VU_BARWIDTH, 129 + VU_YGRAF - (VU_YGRAF - dB)); cairo_fill(cr); dB = OutPeak[i]; if (dB == 0) OutClip[i] = 64; if (OutClip[i]) { OutClip[i]--; ClipPeak.red = (OutClip[i] << 18) / 255.0; ClipPeak.green = (255 - (OutClip[i] * 3)) / 255.0; cairo_set_source_rgba(cr, ClipPeak.red, ClipPeak.green, ClipPeak.blue, 1); } else { cairo_set_source_rgba(cr, Peak.red, Peak.green, Peak.blue, 1); } cairo_rectangle(cr, x, VU_YGRAF - dB, VU_BARWIDTH, 1); cairo_fill(cr); if (mixerControl.output == i) { cairo_set_source_rgba(cr, Selected.red, Selected.green, Selected.blue, 1); cairo_rectangle(cr, x + 1, VU_YGRAF + 128 + 3, VU_BARWIDTH - 2, 1); cairo_fill(cr); cairo_rectangle(cr, x, VU_YGRAF + 128 + 4, VU_BARWIDTH, 1); cairo_fill(cr); } x += VU_BARWIDTH + VU_BARSEP; } // Update the widget gtk_widget_queue_draw_area(VUdarea, update_rect.x, update_rect.y, update_rect.width, update_rect.height); return TRUE; } ///////////////////// GUI events #ifdef REVERSE void Mixer_Output_selector_clicked(GtkWidget *widget, gpointer och) { int ich, val; snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; if (mixerControl.output==(int)och) return; mixerControl.output=(int)och; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); for (ich=0; ichy/YCELLTOT; GMixerColumn=(int)event->x/XCELLTOT-1; if (GMixerColumn<0 || GMixerColumn>=GMixerSection.Outputs) return TRUE; /* grab_focus must follow set_active because the latter causes Vmixer_*_selector_clicked() to be called and, in turn, Vmixer_volume_changed() which changes mixerControl.input (or .output in non-reverse mode). grab_focus then causes Vmixer_volume_clicked() to be called and that event handler finally sets the correct value of mixerControl.input */ if (GMixerRow=GMixerSection.VmixerFirst && GMixerRow<=GMixerSection.VmixerLast) { if (GMixerColumn!=vmixerControl.output) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vmixerControl.outsel[GMixerColumn]), TRUE); if (GMixerRow!=vmixerControl.input) gtk_widget_grab_focus(GTK_WIDGET(vmixerControl.volume[GMixerRow-GMixerSection.VmixerFirst])); } if (event->button==1) { mouseY=event->y; mouseButton=1; } return(TRUE); } #else //REVERSE static gint Gmixer_button_press(GtkWidget *widget, GdkEventButton *event) { GMixerRow=(int)event->y/YCELLTOT; GMixerColumn=(int)event->x/XCELLTOT-1; if (GMixerColumn<0 || GMixerColumn>=GMixerSection.Outputs) return TRUE; // See the note above if (GMixerRow=GMixerSection.VmixerFirst && GMixerRow<=GMixerSection.VmixerLast) { if (GMixerRow!=vmixerControl.input+GMixerSection.VmixerFirst) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vmixerControl.vchsel[GMixerRow-GMixerSection.VmixerFirst]), TRUE); if (GMixerColumn!=vmixerControl.output) gtk_widget_grab_focus(GTK_WIDGET(vmixerControl.volume[GMixerColumn])); } if (event->button==1) { mouseY=event->y; mouseButton=1; } return(TRUE); } #endif //REVERSE static gint Gmixer_button_release(GtkWidget *widget, GdkEventButton *event) { if (event->state & GDK_BUTTON1_MASK) mouseButton=0; return TRUE; } // Gets how many pixels the mouse pointer was moved of and updates // the currently active mixer/vmixer/line_out control. static gint Gmixer_motion_notify(GtkWidget *widget, GdkEventMotion *event) { int x, y; GdkModifierType state; float val; if (event->is_hint) gdk_window_get_pointer(event->window, &x, &y, &state); else { x=event->x; y=event->y; state=event->state; } // Check if the button is still pressed because the release event can // fall in another window, so we may miss it. Ignore the event if there // isn't a backing pixmap or the user clicked an invalid cell. We also // have to check mouseButton because the button_press event may arrive // after the respective motion_notify event. if (!(state & GDK_BUTTON1_MASK) || !mouseButton || !Mixpixmap || GMixerColumn<0 || GMixerColumn>=GMixerSection.Outputs) { mouseButton=0; return(TRUE); } if (GMixerRow=GMixerSection.VmixerFirst && GMixerRow<=GMixerSection.VmixerLast) { val=INVERT(vmixerControl.mixer[vmixerControl.output][vmixerControl.input].Gain); val+=y-mouseY; mouseY=y; #ifdef REVERSE gtk_adjustment_set_value(GTK_ADJUSTMENT(vmixerControl.adj[vmixerControl.input]), (gfloat)val); #else gtk_adjustment_set_value(GTK_ADJUSTMENT(vmixerControl.adj[vmixerControl.output]), (gfloat)val); #endif } else if (GMixerRow==GMixerSection.LineOut) { val=INVERT(lineoutControl.Gain[GMixerColumn]); val+=y-mouseY; mouseY=y; gtk_adjustment_set_value(GTK_ADJUSTMENT(lineoutControl.adj[GMixerColumn]), (gfloat)val); } return(TRUE); } void Monitor_volume_changed(GtkWidget *widget, gpointer cnl) { int val, rval, ch; int i, o; char str[16]; UI_DEBUG(("Monitor_volume_changed() %d %d\n",mixerControl.input,mixerControl.output)); val = rval = INVERT((int)gtk_adjustment_get_value(GTK_ADJUSTMENT(widget))); ch=(int)(long)cnl; #ifdef REVERSE i=ch; o=mixerControl.output; #else i=mixerControl.input; o=ch; #endif // Emulate the line-out volume if this card can't do it in hw. if (!lineoutId) { rval=Add_dB(val, lineoutControl.Gain[o]); ClampOutputVolume(&rval); } SetMixerGain(&mixerControl.mixer[o][i], rval); //@ we should restore the old adj position on error mixerControl.mixer[o][i].Gain=val; if (Gang) { SetMixerGain(&mixerControl.mixer[o^1][i^1], rval); mixerControl.mixer[o^1][i^1].Gain=val; } gtk_label_set_text(GTK_LABEL(mixerControl.label[ch]), strOutGain(str, val)); } void Monitor_volume_clicked(GtkWidget *widget, gpointer ch) { #ifdef REVERSE mixerControl.input=(int)(long)ch; #else mixerControl.output=(int)(long)ch; #endif } void Gang_button_toggled(GtkWidget *widget, gpointer unused) { Gang=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); } void PCM_volume_changed(GtkWidget *widget, gpointer ch) { snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; char str[16]; int err, channel, val, rval; struct VolumeControl_s *vol; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); if ((int)(long)chlabel[channel]), str); snd_ctl_elem_id_set_numid(id, vol->id); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_value_set_id(control, id); if ((err=snd_ctl_elem_read(ctlhandle, control))<0) { printf("Control %s element read error: %s\n", card, snd_strerror(err)); return; } snd_ctl_elem_value_set_integer(control, channel, rval); if ((err=snd_ctl_elem_write(ctlhandle, control))<0) { printf("Control %s element write error: %s\n", card, snd_strerror(err)); } else { vol->Gain[channel]=val; } if (Gang) gtk_adjustment_set_value(vol->adj[channel^1], (gfloat)gtk_adjustment_get_value(GTK_ADJUSTMENT(widget))); } // Changes the PCM volume according to the current Line-out volume for non-vmixer cards void UpdatePCMVolume(int outchannel) { snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; int err, val; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_id_set_numid(id, pcmoutId); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); snd_ctl_elem_value_set_id(control, id); if ((err=snd_ctl_elem_read(ctlhandle, control))<0) printf("Control %s element read error: %s\n", card, snd_strerror(err)); val=Add_dB(pcmoutControl.Gain[outchannel], lineoutControl.Gain[outchannel]); ClampOutputVolume(&val); snd_ctl_elem_value_set_integer(control, outchannel, val); if ((err=snd_ctl_elem_write(ctlhandle, control))<0) printf("Control %s element write error: %s\n", card, snd_strerror(err)); } // Changes the monitor mixer volume according to the current Line-out volume for non-vmixer cards. void UpdateMixerVolume(int outchannel) { snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; int err, val, ch; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); for (ch=0; ch=0; c--) { val=INVERT(vmixerControl.mixer[vmixerControl.output][c].Gain); gtk_adjustment_set_value(vmixerControl.adj[c], (gfloat)val); } } #else void Vmixer_vchannel_selector_clicked(GtkWidget *widget, gpointer ch) { int c, val; snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; if (vmixerControl.input==(int)(long)ch) return; vmixerControl.input=(int)(long)ch; UI_DEBUG(("Vmixer_selector_clicked vch=%d\n",vmixerControl.input)); snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); for (c=vmixerControl.outputs-1; c>=0; c--) { val=INVERT(vmixerControl.mixer[c][vmixerControl.input].Gain); gtk_adjustment_set_value(vmixerControl.adj[c], (gfloat)val); } } #endif void Nominal_level_toggled(GtkWidget *widget, gpointer ch) { snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; int err, val, channel; struct NominalLevelControl_s *NominalLevel; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); val=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); if ((int)(long)chLevel[channel]=!val; snd_ctl_elem_id_set_numid(id, NominalLevel->id); snd_ctl_elem_value_set_id(control, id); if ((err=snd_ctl_elem_read(ctlhandle, control))<0) printf("Control %s element read error: %s\n", card, snd_strerror(err)); snd_ctl_elem_value_set_integer(control, channel, !val); // FALSE is +4 if ((err=snd_ctl_elem_write(ctlhandle, control))<0) printf("Control %s element write error: %s\n", card, snd_strerror(err)); if (Gang) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(NominalLevel->Button[channel^1]), val); } void Switch_toggled(GtkWidget *widget, gpointer Ctl) { snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; int err; struct SwitchControl_s *Switch=(struct SwitchControl_s *)Ctl; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); Switch->value=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD); snd_ctl_elem_id_set_numid(id, Switch->id); snd_ctl_elem_value_set_id(control, id); snd_ctl_elem_value_set_integer(control, 0, Switch->value); if ((err=snd_ctl_elem_write(ctlhandle, control))<0) printf("Control %s element write error: %s\n", card, snd_strerror(err)); } void AutoClock_toggled(GtkWidget *widget, gpointer unused) { char str[32]; if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { AutoClock = clocksrcVal; snprintf(str, sizeof(str), "Autoclock [%s]", clocksrcName[AutoClock]); str[sizeof(str)-1] = '\0'; // Ensure null termination gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(widget))), str); } else { AutoClock = -1; gtk_label_set_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(widget))), "Autoclock"); } } void Digital_mode_activate(GtkWidget *widget, gpointer mode) { int adat; if (SetEnum(dmodeId, (int)(long)mode)<0) { // Restore old value if it failed gtk_combo_box_set_active(GTK_COMBO_BOX(dmodeOpt), dmodeVal); return; } dmodeVal=(int)(long)mode; // When I change the digital mode, the clock source can change too clocksrcVal=GetEnum(clocksrcId); gtk_combo_box_set_active(GTK_COMBO_BOX(clocksrcOpt), clocksrcVal); adat=!memcmp(dmodeName[dmodeVal], "ADAT", 4); SetSensitivity(adat); if (adat) { GMixerSection.Inputs=nIn; GMixerSection.Outputs=nLOut; } else { GMixerSection.Inputs=fdIn+2; // S/PDIF has only 2 channels GMixerSection.Outputs=fdOut+2; } } void Clock_source_activate(GtkWidget *widget, gpointer clk) { unsigned int source; source=(unsigned int)(long)clk & 0xff; if (SetEnum(clocksrcId, source)<0) { gtk_combo_box_set_active(GTK_COMBO_BOX(clocksrcOpt), clocksrcVal); } else { clocksrcVal=(int)(long)clk & 0xff; // Change only when the user triggers it if (((int)(long)clk & DONT_CHANGE)==0 && AutoClock>=0) { AutoClock=clocksrcVal; AutoClock_toggled(autoclockChkbutton, NULL); } } } void SPDIF_mode_activate(GtkWidget *widget, gpointer mode) { SetEnum(spdifmodeId, (int)(long)mode); // This one should never fail spdifmodeVal=(int)(long)mode; } // Create a new backing pixmap of the appropriate size static gint VU_configure_event(GtkWidget *widget, GdkEventConfigure *event) { // Release the previous surface if it exists if (VUpixmap) cairo_surface_destroy(VUpixmap); // Destroy the Cairo surface // Create a Cairo context for drawing cairo_t *cr = cairo_create(VUpixmap); // Set a background color (black in this case) cairo_set_source_rgb(cr, 0, 0, 0); // Black color // Get the widget's allocation (position and size) GtkAllocation allocation; gtk_widget_get_allocation(widget, &allocation); // Create a new Cairo surface for the widget cairo_set_source_surface( cr, VUpixmap, allocation.width, // Width of the widget allocation.height // Height of the widget ); cairo_paint(cr); // Fill the surface with the black color // Clean up the Cairo context cairo_destroy(cr); return TRUE; } // Redraw the screen from the backing surface (pixmap equivalent) static gint VU_expose(GtkWidget *widget, GdkEventExpose *event) { GdkWindow *window = gtk_widget_get_window(widget); if (VUpixmap) { // Create a cairo context for the widget's window cr = cairo_create(VUpixmap); // Set the source surface to VUpixmap (this is the backing surface) cairo_set_source_surface(cr, VUpixmap, event->area.x, event->area.y); // Paint the surface onto the widget's window cairo_paint(cr); // Clean up the cairo context cairo_destroy(cr); } return FALSE; } // Create a new backing surface of the appropriate size static gint Gmixer_configure_event(GtkWidget *widget, GdkEventConfigure *event) { // Release the previous surface if it exists if (Mixpixmap) cairo_surface_destroy(Mixpixmap); // Destroy the Cairo surface // Create a Cairo context for drawing cairo_t *cr = cairo_create(Mixpixmap); // Set a background color (black in this case) cairo_set_source_rgb(cr, 0, 0, 0); // Black color // Get the widget's allocation (position and size) GtkAllocation allocation; gtk_widget_get_allocation(widget, &allocation); // Create a new Cairo surface for the widget cairo_set_source_surface( cr, Mixpixmap, allocation.width, // Width of the widget allocation.height // Height of the widget ); cairo_paint(cr); // Fill the surface with the black color // Clean up the Cairo context cairo_destroy(cr); return TRUE; } // Redraw the screen from the backing surface (cairo equivalent to pixmap) static gint Gmixer_expose(GtkWidget *widget, GdkEventExpose *event) { GdkWindow *window = gtk_widget_get_window(widget); if (Mixpixmap) { // Create a cairo context for the widget's window cr = cairo_create(Mixpixmap); // Set the source surface to Mixpixmap (this is the backing surface) cairo_set_source_surface(cr, Mixpixmap, event->area.x, event->area.y); // Paint the surface onto the widget's window cairo_paint(cr); // Clean up the cairo context cairo_destroy(cr); } return FALSE; } gint CloseWindow(GtkWidget *widget, GdkEvent *event, gpointer geom) { struct geometry *g=geom; gdk_window_get_root_origin(gtk_widget_get_window(widget), &g->x, &g->y); gtk_window_get_size(GTK_WINDOW(widget), &g->w, &g->h); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->toggler), FALSE); // This hides the window gtk_window_move(GTK_WINDOW(widget), g->x, g->y); return(TRUE); // Do not destroy it } gint Mainwindow_delete(GtkWidget *widget, GdkEvent *event, gpointer geom) { struct geometry *g=geom; if (VUwindow) { gdk_window_get_root_origin(gtk_widget_get_window(VUwindow), &VUw_geom.x, &VUw_geom.y); gtk_widget_destroy(VUwindow); } if (GMwindow) { gdk_window_get_root_origin(gtk_widget_get_window(GMwindow), &GMw_geom.x, &GMw_geom.y); gtk_widget_destroy(GMwindow); } gdk_window_get_root_origin(gtk_widget_get_window(Mainwindow), &g->x, &g->y); gtk_main_quit(); return(FALSE); } gint VUwindow_delete(GtkWidget *widget, GdkEvent *event, gpointer geom) { struct geometry *g=geom; gdk_window_get_root_origin(gtk_widget_get_window(widget), &g->x, &g->y); g->st=0; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->toggler), FALSE); return(FALSE); } gint VUwindow_destroy(GtkWidget *widget, gpointer unused) { SetVUmeters(0); g_source_remove(VUtimer); //@@@del gc and fnt VUwindow=0; return(TRUE); } gint GMwindow_delete(GtkWidget *widget, GdkEvent *event, gpointer geom) { struct geometry *g=geom; gdk_window_get_root_origin(gtk_widget_get_window(widget), &g->x, &g->y); g->st=0; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->toggler), FALSE); return(FALSE); } gint GMwindow_destroy(GtkWidget *widget, gpointer unused) { SetVUmeters(0); g_source_remove(Mixtimer); //@@@del gc and fnt GMwindow=0; return(TRUE); } void VUmeters_button_click(GtkWidget *widget, gpointer unused) { char str[64]; if (VUwindow && !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { VUw_geom.st=0; gtk_widget_destroy(VUwindow); } else if (!VUwindow && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { // Create VU-meter window VUwidth=VU_XGRAF+(VU_BARWIDTH+VU_BARSEP)*(nIn+nLOut+1)+VU_BARSEP; VUheight=160; SetVUmeters(1); VUwindow=gtk_window_new(GTK_WINDOW_TOPLEVEL); sprintf(str, "%s VU-meters", cardId); gtk_window_set_title (GTK_WINDOW (VUwindow), str); gtk_window_set_wmclass(GTK_WINDOW(VUwindow), "vumeters", "Emixer"); g_signal_connect(VUwindow, "destroy", G_CALLBACK(VUwindow_destroy), NULL); g_signal_connect(VUwindow, "delete_event", G_CALLBACK(VUwindow_delete), (gpointer)&VUw_geom); gtk_window_set_resizable(GTK_WINDOW(VUwindow), FALSE); // Disable window resizing if (VUw_geom.st!=NOPOS) gtk_window_move(GTK_WINDOW(VUwindow), VUw_geom.x, VUw_geom.y); gtk_widget_show(VUwindow); VUdarea=gtk_drawing_area_new(); gtk_widget_set_events(VUdarea, GDK_EXPOSURE_MASK); gtk_widget_set_size_request(GTK_WIDGET(VUdarea), VUwidth, VUheight); gtk_container_add(GTK_CONTAINER(VUwindow), VUdarea); gtk_widget_show(VUdarea); g_signal_connect(VUdarea, "expose_event", G_CALLBACK(VU_expose), NULL); g_signal_connect(VUdarea, "configure_event", G_CALLBACK(VU_configure_event), NULL); VUtimer=g_timeout_add(30, DrawVUmeters, 0); // The hw updates the meters about 30 times/s gtk_widget_queue_draw(GTK_WIDGET(VUdarea)); VUw_geom.st=1; } } void GMixer_button_click(GtkWidget *widget, gpointer unused) { char str[64]; if (GMwindow && !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { GMw_geom.st=0; gtk_widget_destroy(GMwindow); } else if (!GMwindow && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { // Create graphic mixer window Mixwidth=XCELLTOT*(nLOut+1); Mixheight=YCELLTOT*(GMixerSection.LineOut+1)+9; SetVUmeters(1); GMwindow=gtk_window_new(GTK_WINDOW_TOPLEVEL); sprintf(str, "%s Mixer", cardId); gtk_window_set_title (GTK_WINDOW (GMwindow), str); gtk_window_set_wmclass(GTK_WINDOW(GMwindow), "gridmixer", "Emixer"); g_signal_connect(GMwindow, "destroy", G_CALLBACK(GMwindow_destroy), NULL); g_signal_connect(GMwindow, "delete_event", G_CALLBACK(GMwindow_delete), (gpointer)&GMw_geom); gtk_window_set_resizable(GTK_WINDOW(GMwindow), FALSE); // Disable window resizing if (GMw_geom.st!=NOPOS) gtk_window_move(GTK_WINDOW(GMwindow), GMw_geom.x, GMw_geom.y); gtk_widget_show(GMwindow); Mixdarea=gtk_drawing_area_new(); gtk_widget_set_events(Mixdarea, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); gtk_widget_set_size_request(GTK_WIDGET(Mixdarea), Mixwidth, Mixheight); gtk_container_add(GTK_CONTAINER(GMwindow), Mixdarea); gtk_widget_show(Mixdarea); g_signal_connect(Mixdarea, "expose_event", G_CALLBACK(Gmixer_expose), NULL); g_signal_connect(Mixdarea, "configure_event", G_CALLBACK(Gmixer_configure_event), NULL); g_signal_connect(Mixdarea, "motion_notify_event", G_CALLBACK(Gmixer_motion_notify), NULL); g_signal_connect(Mixdarea, "button_press_event", G_CALLBACK(Gmixer_button_press), NULL); g_signal_connect(Mixdarea, "button_release_event", G_CALLBACK(Gmixer_button_release), NULL); Mixtimer=g_timeout_add(30, DrawMixer, 0); // The hw updates the meters about 30 times/s gtk_widget_queue_draw(GTK_WIDGET(Mixdarea)); GMw_geom.st=1; } } void ToggleWindow(GtkWidget *widget, gpointer window) { if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) gtk_widget_show(GTK_WIDGET(window)); else gtk_widget_hide(GTK_WIDGET(window)); } // Scan all controls and sets up the structures needed to access them. int OpenControls(const char *card, const char *cardname) { int err, i, o; int numid, count, items, item; snd_hctl_t *handle; snd_hctl_elem_t *elem; snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; pcmoutId=lineoutId=vmixerId=p4InId=p4OutId=dmodeId=clocksrcId=spdifmodeId=vuswitchId=vumetersId=mixerId=0; memset(&vmixerControl, 0, sizeof(vmixerControl)); memset(&mixerControl, 0, sizeof(mixerControl)); memset(&lineoutControl, 0, sizeof(struct VolumeControl_s)); memset(&lineinControl, 0, sizeof(struct VolumeControl_s)); memset(&pcmoutControl, 0, sizeof(struct VolumeControl_s)); ndmodes=nclocksrc=nspdifmodes=0; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_info_alloca(&info); if ((err=snd_hctl_open(&handle, card, 0))<0) { printf("Control %s open error: %s", card, snd_strerror(err)); return err; } if ((err=snd_hctl_load(handle))<0) { printf("Control %s local error: %s\n", card, snd_strerror(err)); return err; } for (elem=snd_hctl_first_elem(handle); elem; elem=snd_hctl_elem_next(elem)) { if ((err=snd_hctl_elem_info(elem, info))<0) { printf("Control %s snd_hctl_elem_info error: %s\n", card, snd_strerror(err)); return err; } if (snd_ctl_elem_info_is_inactive(info)) continue; snd_hctl_elem_get_id(elem, id); numid=snd_ctl_elem_id_get_numid(id); count=snd_ctl_elem_info_get_count(info); if (!strcmp("Monitor Mixer Volume", snd_ctl_elem_id_get_name(id))) { if (!mixerId) { mixerId=numid; CTLID_DEBUG(("First Mixer id=%d\n", mixerId)); } } else if (!strcmp("VMixer Volume", snd_ctl_elem_id_get_name(id))) { if (!vmixerId) { vmixerId=vmixerControl.id=numid; CTLID_DEBUG(("First Vmixer id=%d\n", vmixerId)); } } else if (!strcmp("PCM Playback Volume", snd_ctl_elem_id_get_name(id))) { pcmoutId=pcmoutControl.id=numid; CTLID_DEBUG(("PCM Playback Volume id=%d [%d]\n", pcmoutId, count)); } else if (!strcmp("Line Playback Volume", snd_ctl_elem_id_get_name(id))) { lineoutId=numid; CTLID_DEBUG(("Line Volume id=%d\n", lineoutId)); } else if (!strcmp("Line Capture Volume", snd_ctl_elem_id_get_name(id))) { lineinId=lineinControl.id=numid; CTLID_DEBUG(("Capture Volume id=%d [%d]\n", lineinId, count)); } else if (!strcmp("Line Playback Switch (-10dBV)", snd_ctl_elem_id_get_name(id))) { p4OutId=NominalOut.id=numid; CTLID_DEBUG(("Playback nominal id=%d [%d]\n", p4OutId, count)); } else if (!strcmp("Line Capture Switch (-10dBV)", snd_ctl_elem_id_get_name(id))) { p4InId=NominalIn.id=numid; CTLID_DEBUG(("Capture nominal id=%d [%d]\n", p4InId, count)); } else if (!strcmp("Digital mode Switch", snd_ctl_elem_id_get_name(id))) { dmodeId=numid; items=snd_ctl_elem_info_get_items(info); ndmodes=items; for (item=0; item1) cardnum=atoi(argv[1])-1; while (snd_card_next(&cardnum)>=0 && cardnum>=0) { sprintf(hwname, "hw:%d", cardnum); if ((err=snd_ctl_open(&ctlhandle, hwname, 0))<0) { printf("snd_ctl_open(%s) Error: %s\n", hwname, snd_strerror(err)); continue; } if ((err=snd_ctl_card_info(ctlhandle, hw_info))>=0) { if (!strncmp(snd_ctl_card_info_get_driver(hw_info), "Echo_", 5)) { strncpy(card, hwname, 7); hwname[7]=0; strncpy(cardname, snd_ctl_card_info_get_name(hw_info), 31); cardname[31]=0; strncpy(cardId, snd_ctl_card_info_get_name(hw_info), 15); cardId[15]=0; CTLID_DEBUG(("Card found: %s (%s)\n", snd_ctl_card_info_get_longname(hw_info), hwname)); /*printf("card = %d\n", snd_ctl_card_info_get_card(hw_info)); printf("id = %s\n", snd_ctl_card_info_get_id(hw_info)); printf("driver = %s\n", snd_ctl_card_info_get_driver(hw_info)); printf("name = %s\n", snd_ctl_card_info_get_name(hw_info)); printf("longname = %s\n", snd_ctl_card_info_get_longname(hw_info)); printf("mixername = %s\n", snd_ctl_card_info_get_mixername(hw_info)); printf("components = %s\n", snd_ctl_card_info_get_components(hw_info));*/ break; } } else { printf("snd_ctl_card_info(%s) Error: %s\n", hwname, snd_strerror(err)); } snd_ctl_close(ctlhandle); ctlhandle=0; } if (!ctlhandle) { printf("No Echoaudio cards found, sorry.\n"); return(0); } // Reads available controls if (OpenControls(card, cardname)) exit(1); mouseButton=0; Gang=0; // Set the gang button off, because has annoying side effects during initialization Mainw_geom.st=NOPOS; PVw_geom.st=NOPOS; LVw_geom.st=NOPOS; VUw_geom.st=NOPOS; Mixerw_geom.st=NOPOS; Vmixerw_geom.st=NOPOS; VUwindow=GMwindow=0; GMixerSection.Inputs=nIn; // The correct value is set by Digital_mode_activate() GMixerSection.Outputs=nLOut; GMixerSection.VmixerFirst=nIn; GMixerSection.VmixerLast=nIn+vmixerControl.inputs-1; GMixerSection.LineOut=GMixerSection.VmixerLast+1; // Read current mixer setting. if (mixerId) ReadMixer(&mixerControl); if (vmixerId) ReadMixer(&vmixerControl); if (pcmoutId) ReadControl(pcmoutControl.Gain, nPOut, pcmoutControl.id, SND_CTL_ELEM_IFACE_MIXER); if (lineinId) ReadControl(lineinControl.Gain, nIn, lineinControl.id, SND_CTL_ELEM_IFACE_MIXER); if (lineoutId) ReadControl(lineoutControl.Gain, nLOut, lineoutId, SND_CTL_ELEM_IFACE_MIXER); if (p4InId) ReadNominalLevels(&NominalIn); if (p4OutId) ReadNominalLevels(&NominalOut); //@@ check the values if (load) { FILE *f; snprintf(str, 255, "%s/.Emixer_%s", getenv("HOME"), cardId); str[255]=0; if ((f=fopen(str, "r"))) { str[255]=0; while (fgets(str, 255, f)) { if (!strncmp("LineOut ", str, 8)) { sscanf(str+8, "%d %d", &o, &n); if (o>=0 && o=0 && i=0 && o=0 && o=0 && i=0 && o=0 && i=0 && o=0 && i1) { // Digital mode switch frame=gtk_frame_new("Digital mode"); gtk_widget_show(frame); gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, FALSE, 0); hbox=gtk_hbox_new(FALSE, 0); gtk_widget_show(hbox); gtk_container_add(GTK_CONTAINER(frame), hbox); dmodeOpt = gtk_combo_box_text_new(); gtk_widget_show(dmodeOpt); menu=gtk_menu_new(); gtk_widget_show(menu); for (i=0; i1) { // Clock source switch frame=gtk_frame_new("Clock source"); gtk_widget_show(frame); gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, FALSE, 0); hbox=gtk_hbox_new(FALSE, 0); gtk_widget_show(hbox); gtk_container_add(GTK_CONTAINER(frame), hbox); clocksrcOpt = gtk_combo_box_text_new(); // For a combo box with text items gtk_widget_show(clocksrcOpt); menu=gtk_menu_new(); gtk_widget_show(menu); for (i=0; i1) { // S/PDIF mode switch frame=gtk_frame_new("S/PDIF mode"); gtk_widget_show(frame); gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, FALSE, 0); hbox=gtk_hbox_new(FALSE, 0); gtk_widget_show(hbox); gtk_container_add(GTK_CONTAINER(frame), hbox); spdifmodeOpt = gtk_combo_box_text_new(); // For a combo box with text items gtk_widget_show(spdifmodeOpt); menu=gtk_menu_new(); gtk_widget_show(menu); for (i=0; i1) || (clocksrcId && nclocksrc>1) || (spdifmodeId && nspdifmodes>1)) { button=gtk_toggle_button_new_with_label("Misc"); gtk_widget_show(button); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1); g_signal_connect(button, "toggled", G_CALLBACK(ToggleWindow), (gpointer)Miscwindow); Miscw_geom.toggler=button; if (Miscw_geom.st==1) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); } if (mixerId) { // Graphical mixer button button=gtk_toggle_button_new_with_label("GrMix"); gtk_widget_show(button); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1); g_signal_connect(button, "toggled", G_CALLBACK(GMixer_button_click), 0); GMw_geom.toggler=button; if (GMw_geom.st==1) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); // Mixer button button=gtk_toggle_button_new_with_label("Mixer"); gtk_widget_show(button); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1); g_signal_connect(button, "toggled", G_CALLBACK(ToggleWindow), (gpointer)mixerControl.window); Mixerw_geom.toggler=button; if (Mixerw_geom.st==1) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); } if (vmixerId) { // Vmixer button button=gtk_toggle_button_new_with_label("Vmixer"); gtk_widget_show(button); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1); g_signal_connect(button, "toggled", G_CALLBACK(ToggleWindow), (gpointer)vmixerControl.window); Vmixerw_geom.toggler=button; if (Vmixerw_geom.st==1) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); } if (pcmoutId) { // PCM volume button button=gtk_toggle_button_new_with_label("PCM"); gtk_widget_show(button); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1); g_signal_connect(button, "toggled", G_CALLBACK(ToggleWindow), (gpointer)pcmoutControl.window); PVw_geom.toggler=button; if (PVw_geom.st==1) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); } /* ********** GTK-main ********** */ Gang=1; if (dmodeId) Digital_mode_activate(dmodeOpt, (gpointer)(long)dmodeVal); // Also calls SetSensitivity() gtk_widget_show(Mainwindow); gtk_main(); if (save) { FILE *f; if (snprintf(str, 255, "%s/.Emixer_%s", getenv("HOME"), cardId)>0) { str[255]=0; if ((f=fopen(str, "w"))) { fprintf(f, "-- LineOut \n"); for (i=0; i \n"); for (i=0; i \n"); for (i=0; i \n"); for (i=0; i \n"); for (o=0; o \n"); for (o=0; o \n"); for (o=0; o \n"); fprintf(f, "MainWindow %d %d %d %d\n", Mainw_geom.x, Mainw_geom.y, Mainw_geom.w, Mainw_geom.h); if (VUwindow) gdk_window_get_root_origin(gtk_widget_get_window(VUwindow), &VUw_geom.x, &VUw_geom.y); fprintf(f, "VUmetersWindow %d %d %d\n", VUw_geom.x, VUw_geom.y, VUw_geom.st); if (GMwindow) gdk_window_get_root_origin(gtk_widget_get_window(GMwindow), &VUw_geom.x, &VUw_geom.y); fprintf(f, "GfxMixerWindow %d %d %d\n", GMw_geom.x, GMw_geom.y, GMw_geom.st); if (pcmoutId) { GdkWindow* pcmwin = gtk_widget_get_window(pcmoutControl.window); if (pcmwin) { gdk_window_get_root_origin(pcmwin, &PVw_geom.x, &PVw_geom.y); gtk_window_get_size(GTK_WINDOW(pcmoutControl.window), &PVw_geom.w, &PVw_geom.h); } fprintf(f, "PcmVolumeWindow %d %d %d %d %d\n", PVw_geom.x, PVw_geom.y, PVw_geom.w, PVw_geom.h, !!gtk_widget_get_visible(pcmoutControl.window)); } if (LVwindow) { gdk_window_get_root_origin(gtk_widget_get_window(LVwindow), &LVw_geom.x, &LVw_geom.y); gtk_window_get_size(GTK_WINDOW(LVwindow), &LVw_geom.w, &LVw_geom.h); } fprintf(f, "LineVolumeWindow %d %d %d %d %d\n", LVw_geom.x, LVw_geom.y, LVw_geom.w, LVw_geom.h, !!gtk_widget_get_visible(LVwindow)); if (Miscwindow) { gdk_window_get_root_origin(gtk_widget_get_window(Miscwindow), &Miscw_geom.x, &Miscw_geom.y); gtk_window_get_size(GTK_WINDOW(Miscwindow), &Miscw_geom.w, &Miscw_geom.h); } fprintf(f, "MiscControlsWindow %d %d %d %d %d\n", Miscw_geom.x, Miscw_geom.y, Miscw_geom.w, Miscw_geom.h, !!gtk_widget_get_visible(Miscwindow)); if (mixerId) { if (mixerControl.window) { gdk_window_get_root_origin(gtk_widget_get_window(mixerControl.window), &Mixerw_geom.x, &Mixerw_geom.y); gtk_window_get_size(GTK_WINDOW(mixerControl.window), &Mixerw_geom.w, &Mixerw_geom.h); } fprintf(f, "MixerWindow %d %d %d %d %d\n", Mixerw_geom.x, Mixerw_geom.y, Mixerw_geom.w, Mixerw_geom.h, !!gtk_widget_get_visible(mixerControl.window)); } if (vmixerId) { if (vmixerControl.window) { gdk_window_get_root_origin(gtk_widget_get_window(vmixerControl.window), &Vmixerw_geom.x, &Vmixerw_geom.y); gtk_window_get_size(GTK_WINDOW(vmixerControl.window), &Vmixerw_geom.w, &Vmixerw_geom.h); } fprintf(f, "VmixerWindow %d %d %d %d %d\n", Vmixerw_geom.x, Vmixerw_geom.y, Vmixerw_geom.w, Vmixerw_geom.h, !!gtk_widget_get_visible(vmixerControl.window)); } fprintf(f, "\n"); fclose(f); } } } if (VUwindow) { SetVUmeters(0); g_source_remove(VUtimer); } if (GMwindow) { SetVUmeters(0); g_source_remove(Mixtimer); } snd_ctl_close(ctlhandle); return(0); }