1 /************************************************************************
2    io_xaw.c  -- shows midi events in X11 using Xaw widgets
3 
4    Copyright (C) 1995-1996 Nathan I. Laredo
5 
6    This program is modifiable/redistributable under the terms
7    of the GNU General Public Licence.
8 
9    You should have received a copy of the GNU General Public License
10    along with this program; if not, write to the Free Software
11    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
12    Send your comments and all your spare pocket change to
13    laredo@gnu.ai.mit.edu (Nathan Laredo) or to PSC 1, BOX 709, 2401
14    Kelly Drive, Lackland AFB, TX 78236-5128, USA.
15  *************************************************************************/
16 #include "playmidi.h"
17 #include "bitmaps.h"
18 #include <X11/Intrinsic.h>
19 #include <X11/Shell.h>
20 #include <X11/Xaw/Form.h>
21 #include <X11/Xaw/Label.h>
22 #include <X11/Xaw/Toggle.h>
23 #include <X11/Xaw/Command.h>
24 #include <X11/Xaw/AsciiText.h>
25 #include <X11/Xaw/Scrollbar.h>
26 #include <X11/StringDefs.h>
27 #include <sys/time.h>
28 #include <unistd.h>
29 
30 extern int chanmask, play_gus, play_fm, play_ext, gus_dev, sb_dev, ext_dev;
31 extern int graphics, verbose, perc, ntrks;
32 extern int note_vel[16][128];
33 extern unsigned long int ticks;
34 extern char *filename, *gmvoice[256];
35 extern float skew;
36 extern void seq_reset();
37 extern struct chanstate channel[16];
38 extern void seq_stop_note(int, int, int, int);
39 extern struct timeval start_time;
40 struct timeval want_time, now_time;
41 
42 int cdeltat(t1, t2)
43 struct timeval *t1;
44 struct timeval *t2;
45 {
46     int d1, d2;
47 
48     d1 = t1->tv_sec - t2->tv_sec;
49     if((d2 = t1->tv_usec - t2->tv_usec) < 0)
50 	(d2 += 1000000, d1 -= 1);
51     d2 /= 10000;
52     return (d2 + d1 * 100);
53 }
54 
55 XtAppContext context;
56 Widget toplevel, manager, titlebar, status, text, exit_button;
57 Widget scrollbar, skew_label, nextsong, repeatsong, prevsong;
58 Pixmap Exit, Next, Play, Prev;	/* button icons */
59 Widget cbutton[16];		/* program for each channel (also toggles) */
60 Widget cmeter[16];		/* vu meter for each channel */
61 #define TEXTSIZE	8192
62 char textbuf[TEXTSIZE];
63 
64 int i, return_do_what;
65 int vu_level[16], vu_delta[16];
66 
67 char *drumset[11] =
68 {
69  "Standard Kit", "Room Kit", "Power Kit", "Electronic Kit", "TR-808 Kit",
70     "Jazz Kit", "Brush Kit", "Orchestra Kit", "Sound FX Kit",
71     "Program Kit", "MT-32 Kit"
72 };
73 
74 #define SET(x) (x == 8 ? 1 : x >= 16 && x <= 23 ? 2 : x == 24 ? 3 : \
75 		x == 25  ? 4 : x == 32 ? 5 : x >= 40 && x <= 47 ? 6 : \
76 		x == 48 ? 7 : x == 56 ? 8 : x >= 96 && x <= 111 ? 9 : \
77 		x == 127 ? 10 : 0)
78 
79 void close_show(error)
80 int error;
81 {
82     exit(error);
83 }
84 
85 void ExitCallback(w, client_data, call_data)
86 Widget w;
87 XtPointer client_data;
88 XtPointer call_data;
89 {
90     close_show(0);
91 }
92 
93 void MaskCallback(w, client_data, call_data)
94 Widget w;
95 XtPointer client_data;
96 XtPointer call_data;
97 {
98     int i, j, k;
99     for (i = 0; i < 16 && cbutton[i] != w; i++);
100     if (i < 0 || i >= 16)
101 	return;
102     if (call_data)		/* channel should be enabled */
103 	chanmask |= (1 << i);
104     else {			/* need to stop all notes in channel */
105 	chanmask &= (~(1 << i));
106 	if (ISGUS(i))
107 	    k = gus_dev;
108 	else if (ISFM(i))
109 	    k = sb_dev;
110 	else
111 	    k = ext_dev;
112 	for (j = 0; j < 128; j++)
113 	    if (note_vel[i][j])
114 		seq_stop_note(k, i, j, note_vel[i][j] = 0);
115     }
116 }
117 
118 void SkewCallback(w, client_data, call_data)
119 Widget w;
120 XtPointer client_data;
121 XtPointer call_data;
122 {
123     float position;
124 
125     position = *(float *) call_data;
126     skew = (position * 3.75) + 0.25;
127     sprintf(textbuf, "%1.2f", skew);
128     XtVaSetValues(skew_label, XtNlabel, textbuf, NULL);
129 };
130 
131 void SkewIncCallback(w, client_data, call_data)
132 Widget w;
133 XtPointer client_data;
134 XtPointer call_data;
135 {
136     double s_position;
137     int movewhere;
138 
139     movewhere = (int) call_data;
140     if (movewhere < 0) {
141 	skew += 0.01;
142 	if (skew > 4.0)
143 	    skew = 4.0;
144     } else if (movewhere > 0) {
145 	skew -= 0.01;
146 	if (skew < 0.25)
147 	    skew = 0.25;
148     } else
149 	return;
150     s_position = (skew - 0.25) / 3.75;	/* scale is 0.0 - 1.0 */
151     XawScrollbarSetThumb(scrollbar, s_position, 0.01);
152     sprintf(textbuf, "%1.2f", skew);
153     XtVaSetValues(skew_label, XtNlabel, textbuf, NULL);
154 };
155 
156 void PrevSongCallback(w, client_data, call_data)
157 Widget w;
158 XtPointer client_data;
159 XtPointer call_data;
160 {
161     if ((skew += 0.01) > 4)
162 	seq_reset();
163     return_do_what = -1;
164 }
165 
166 void RepeatCallback(w, client_data, call_data)
167 Widget w;
168 XtPointer client_data;
169 XtPointer call_data;
170 {
171     seq_reset();
172     return_do_what = 0;
173 }
174 
175 void NextSongCallback(w, client_data, call_data)
176 Widget w;
177 XtPointer client_data;
178 XtPointer call_data;
179 {
180     seq_reset();
181     return_do_what = 1;
182 }
183 
184 void updateMeter(chn)
185 int chn;
186 {
187     XtVaSetValues(cmeter[chn], XtNwidth, 5 + vu_level[chn], NULL);
188 }
189 
190 XtIntervalId mytimerid;
191 Boolean return_now;
192 int oldsec = 0, oldusec = 0;
193 
194 void TimerCallback(w, client_data, timerid)
195 Widget w;
196 XtPointer client_data;
197 XtIntervalId timerid;
198 {
199     int i, sec, usec;
200 
201     /* update vu meter values */
202     for (i = 0; i < 16; i++)
203 	if (vu_delta[i]) {	/* decay meter */
204 	    vu_level[i] -= vu_delta[i];
205 	    if (vu_level[i] < 0)
206 		vu_level[i] = vu_delta[i] = 0;
207 	    updateMeter(i);
208 	}
209     gettimeofday(&now_time, NULL);
210     sec = now_time.tv_sec - start_time.tv_sec;
211     usec = now_time.tv_usec - start_time.tv_usec;
212     if (usec < 0)
213 	(usec += 1000000, sec -= 1);
214     usec /= 100000;	/* change to 10ths of a second */
215     sprintf(textbuf, "%02d:%02d.%d", sec / 60, sec % 60, usec);
216     if (sec > oldsec || usec > oldusec) {
217 	XtVaSetValues(status, XtNstring, textbuf, NULL);
218 	oldsec = sec; oldusec = usec;
219     }
220     if (return_do_what != NO_EXIT || cdeltat(&want_time, &now_time) < 15)
221 	return_now = True;
222     else
223 	mytimerid = XtAppAddTimeOut(context, 100, TimerCallback, NULL);
224 }
225 
226 XEvent event;
227 
228 int updatestatus()
229 {
230     XtInputMask mask;
231 
232     return_now = False;
233     want_time.tv_sec = start_time.tv_sec + (ticks / 100);
234     want_time.tv_usec = start_time.tv_usec + (ticks % 100) * 10000;
235     if (want_time.tv_usec > 1000000)
236 	(want_time.tv_usec -= 1000000, want_time.tv_sec++);
237     return_do_what = NO_EXIT;
238     TimerCallback(context, NULL, 0);		/* start timer */
239     do {	/* toolkit main loop */
240 	if ((mask = XtAppPending(context)))	/* non-blocking */
241 	    XtAppProcessEvent(context, mask);
242 #ifndef HOG_CPU
243 	if (!mask && !return_now) {	/* sleep 1/10th of a second */
244 	    usleep(750);
245 	}
246 #endif
247     } while (!return_now || mask);
248     return return_do_what;
249 }
250 
251 
252 void AppendText(s)
253 char *s;
254 {
255     XawTextBlock textblk;
256     static XawTextPosition whereami;
257 
258 
259     if (s == NULL) {	/* reset text to empty */
260 	XtVaSetValues(text, XtNstring, "", NULL);
261 	whereami = 0;
262 	return;
263     }
264     textblk.firstPos = 0;
265     textblk.length = strlen(s);
266     textblk.ptr = s;
267     textblk.format = XawFmt8Bit;
268     XawTextReplace(text, whereami, whereami, &textblk);
269     whereami += textblk.length;
270 }
271 
272 #define TEXTBUF		(&textbuf[strlen(textbuf)])
273 
274 void showevent(cmd, data, length)
275 int cmd;
276 unsigned char *data;
277 int length;
278 {
279     int chn, note, vel, i;
280 
281     chn = (cmd & 0x0f);
282     note = (data[0] & 0x7f);
283     vel = (data[1] & 0x7f);
284     if (cmd < 8 && cmd > 0) {
285 	strncpy(textbuf, data, length < TEXTSIZE - 4 ? length : TEXTSIZE - 3);
286 	textbuf[(length > TEXTSIZE + 2) ? TEXTSIZE - 2 : length] = '\0';
287 	sprintf(TEXTBUF, "\n");
288 	AppendText(textbuf);
289 	if (verbose > 2)
290 	    printf(textbuf);
291     } else if (cmd & 0x80)
292 	switch (cmd & 0xf0) {
293 	case MIDI_NOTEON:
294 	    if (!vel && vu_level[chn])
295 		vu_delta[chn] = 32;
296 	    else
297 		(vu_level[chn] = vel, vu_delta[chn] = 0);
298 	    updateMeter(chn);
299 	    break;
300 	case MIDI_NOTEOFF:
301 	    if (vu_level[chn])
302 		vu_delta[chn] = vel / 2;
303 	    updateMeter(chn);
304 	    break;
305 	case MIDI_PGM_CHANGE:
306 	    if (!ISPERC(chn))
307 		XtVaSetValues(cbutton[chn], XtNlabel, gmvoice[note], NULL);
308 	    else
309 		XtVaSetValues(cbutton[chn], XtNlabel, drumset[SET(note)],
310 		NULL);
311 	    break;
312 	case 0xf0:
313 	case 0xf7:
314 	    sprintf(textbuf, "Sysex(%2x): ", cmd);
315 	    for (i = 0; i < length && i < (TEXTSIZE / 2) - 16; i++)
316 		sprintf(TEXTBUF, "%02x", data[i]);
317 	    sprintf(TEXTBUF, "\n");
318 	    AppendText(textbuf);
319 	    if (verbose > 2)
320 		printf(textbuf);
321 	    break;
322 	default:
323 	    break;
324 	}
325 }
326 
327 void init_show()
328 {
329     int i;
330     double s_position;
331 
332     AppendText(NULL);	/* reset text window */
333     s_position = (skew - 0.25) / 3.75;	/* scale is 0.0 - 1.0 */
334     sprintf(textbuf, "%1.2f", skew);
335     XawScrollbarSetThumb(scrollbar, s_position, 0.01);
336     XtVaSetValues(skew_label, XtNlabel, textbuf, NULL);
337     XStoreName(XtDisplay(toplevel), XtWindow(toplevel), filename);
338     oldsec = oldusec = 0;
339     for (i = 0; i < 16; i++) {
340 	vu_level[i] = vu_delta[i] = 0;
341 	updateMeter(i);
342 	sprintf(textbuf, "channel%02d", i + 1);
343 	XtVaSetValues(cbutton[i], XtNlabel, textbuf, NULL);
344     }
345     XtVaSetValues(status, XtNlabel, "00:00.0" , NULL);
346 }
347 
348 void setup_show(argc, argv)
349 int argc;
350 char **argv;
351 {
352     int i;
353     Pixel bg, fg;
354     Display *mydisp;
355     Screen *myscreen;
356     Window mywin;
357 
358     graphics++;			/* force -r option if not selected */
359     toplevel = XtVaOpenApplication(&context, "XPlaymidi", NULL, 0,
360 	&argc, argv, NULL, sessionShellWidgetClass, XtNminWidth, 425,
361 	XtNwidth, 425, XtNminHeight, 479, XtNheight, 500, NULL);
362     manager = XtVaCreateManagedWidget("controls", formWidgetClass,
363 	toplevel, XtNdefaultDistance, 2, NULL);
364     titlebar = XtVaCreateManagedWidget("titlebar", labelWidgetClass,
365 	manager, XtNtop, XawChainTop, XtNright, XawChainRight, XtNleft,
366 	XawChainLeft, XtNborderWidth, 0, XtNfromVert, cbutton[15],
367 	XtNlabel, "X" RELEASE " by Nathan Laredo" , NULL);
368     skew_label = XtVaCreateManagedWidget("skew", labelWidgetClass, manager,
369 	XtNborderWidth, 0, XtNleft, XawChainLeft, XtNfromVert, titlebar,
370 	XtNlabel, "1.00", NULL);
371     scrollbar = XtVaCreateManagedWidget("scrollbar", scrollbarWidgetClass,
372 	manager, XtNorientation, XtorientHorizontal, XtNlength, 365,
373 	XtNfromVert, titlebar, XtNfromHoriz, skew_label, NULL);
374     text = XtVaCreateManagedWidget("text", asciiTextWidgetClass, manager,
375 	XtNbottom, XawChainBottom, XtNleft, XawChainLeft, XtNfromVert,
376 	skew_label, XtNeditType, XawtextAppend, XtNheight, 400,
377 	XtNwidth, 132, XtNscrollHorizontal, XawtextScrollAlways,
378 	XtNscrollVertical, XawtextScrollAlways, XtNtype, XawAsciiString,
379 	XtNdisplayCaret, False, NULL);
380     for (i = 0; i < 16; i++) {
381 	sprintf(textbuf, "channel%02d", i + 1);
382 	cbutton[i] = XtVaCreateManagedWidget(textbuf, toggleWidgetClass,
383 	    manager, XtNstate, (ISPLAYING(i) ? True : False),
384 	    XtNwidth, 100, XtNjustify, XtJustifyLeft, XtNfromHoriz, text,
385 	    XtNresizable, False, XtNresize, False, NULL);
386 	if (!i)
387 	    XtVaSetValues(cbutton[i], XtNfromVert, scrollbar, NULL);
388 	else
389 	    XtVaSetValues(cbutton[i], XtNfromVert, cbutton[i - 1], NULL);
390 	XtAddCallback(cbutton[i], XtNcallback, MaskCallback, NULL);
391 	sprintf(textbuf, "meter%02d", i + 1);
392 	cmeter[i] = XtVaCreateManagedWidget(textbuf, toggleWidgetClass,
393 	    manager, XtNsensitive, False, XtNfromHoriz, cbutton[i],
394 	    XtNlabel, "", XtNresize, False, XtNjustify, XtJustifyLeft,
395 	    XtNwidth, 5, XtNresizable, True, NULL);
396 	if (!i)
397 	    XtVaSetValues(cmeter[i], XtNfromVert, scrollbar, NULL);
398 	else
399 	    XtVaSetValues(cmeter[i], XtNfromVert, cmeter[i - 1], NULL);
400     }
401     exit_button = XtVaCreateManagedWidget("exit", commandWidgetClass,
402 	manager, XtNfromHoriz, text, XtNfromVert, cbutton[15],
403 	XtNinternalWidth, 1, XtNinternalHeight, 0, XtNborderWidth, 0, NULL);
404     prevsong = XtVaCreateManagedWidget("prevfile", commandWidgetClass,
405 	manager, XtNfromHoriz, exit_button, XtNfromVert, cbutton[15],
406 	XtNinternalWidth, 1, XtNinternalHeight, 0, XtNborderWidth, 0, NULL);
407     repeatsong = XtVaCreateManagedWidget("repeat", commandWidgetClass,
408 	manager, XtNfromHoriz, prevsong, XtNfromVert, cbutton[15],
409 	XtNinternalWidth, 1, XtNinternalHeight, 0, XtNborderWidth, 0, NULL);
410     nextsong = XtVaCreateManagedWidget("nextfile", commandWidgetClass,
411 	manager, XtNfromHoriz, repeatsong, XtNfromVert, cbutton[15],
412 	XtNinternalWidth, 1, XtNinternalHeight, 0, XtNborderWidth, 0, NULL);
413     status = XtVaCreateManagedWidget("status", asciiTextWidgetClass,
414 	manager, XtNfromHoriz, nextsong, XtNfromVert, cbutton[15],
415 	XtNborderWidth, 0, XtNstring, "--:--.-", XtNeditType, XawtextRead,
416 	XtNdisplayCaret, False, XtNuseStringInPlace, True, NULL);
417     XtAddCallback(scrollbar, XtNjumpProc, SkewCallback, NULL);
418     XtAddCallback(scrollbar, XtNscrollProc, SkewIncCallback, NULL);
419     XtAddCallback(exit_button, XtNcallback, ExitCallback, NULL);
420     XtAddCallback(nextsong, XtNcallback, NextSongCallback, NULL);
421     XtAddCallback(repeatsong, XtNcallback, RepeatCallback, NULL);
422     XtAddCallback(prevsong, XtNcallback, PrevSongCallback, NULL);
423     myscreen = XtScreen(toplevel);
424     mydisp = DisplayOfScreen(myscreen);
425     mywin = RootWindowOfScreen(myscreen);
426     fg = WhitePixelOfScreen(myscreen);
427     bg = BlackPixelOfScreen(myscreen);
428     Exit = XCreatePixmapFromBitmapData(mydisp, mywin, Eject_bits,
429 	Eject_width, Eject_height, fg, bg, 1);
430     XtVaSetValues(exit_button, XtNbitmap, Exit, NULL);
431     Prev = XCreatePixmapFromBitmapData(mydisp, mywin, Prev_bits,
432 	Prev_width, Prev_height, fg, bg, 1);
433     XtVaSetValues(prevsong, XtNbitmap, Prev, NULL);
434     Play = XCreatePixmapFromBitmapData(mydisp, mywin, Play_bits,
435 	Play_width, Play_height, fg, bg, 1);
436     XtVaSetValues(repeatsong, XtNbitmap, Play, NULL);
437     Next = XCreatePixmapFromBitmapData(mydisp, mywin, Next_bits,
438 	Next_width, Next_height, fg, bg, 1);
439     XtVaSetValues(nextsong, XtNbitmap, Next, NULL);
440     XtRealizeWidget(toplevel);
441 }
442