1
/*================================================================
3
* The Tcl/Tk interface for Timidity
4
* written by Takashi Iwai (iwai@dragon.mm.t.u-tokyo.ac.jp)
6
* Most of the following codes are derived from both motif_ctl.c
7
* and motif_pipe.c. The communication between Tk program and
8
* timidity is established by a pipe stream as in Motif interface.
9
* On the contrast to motif, the stdin and stdout are assigned
10
* as pipe i/o in Tk interface.
12
*================================================================*/
20
#include <sys/ioctl.h>
22
#include <sys/ioctl.h>
36
static void ctl_refresh(void);
37
static void ctl_total_time(uint32 tt);
38
static void ctl_master_volume(int mv);
39
static void ctl_file_name(char *name);
40
static void ctl_current_time(uint32 ct);
41
static void ctl_note(int v);
42
static void ctl_note_display(void);
43
static void ctl_program(int ch, int val, const char *name);
44
static void ctl_volume(int ch, int val);
45
static void ctl_expression(int ch, int val);
46
static void ctl_panning(int ch, int val);
47
static void ctl_sustain(int ch, int val);
48
static void ctl_pitch_bend(int ch, int val);
49
static void ctl_reset(void);
50
static int ctl_open(int using_stdin, int using_stdout);
51
static void ctl_close(void);
52
static int ctl_read(int32 *valp);
53
static int cmsg(int type, int verbosity_level, const char *fmt, ...);
54
static void ctl_pass_playing_list(int number_of_files, const char *list_of_files[]);
55
static int ctl_blocking_read(int32 *valp);
57
static void pipe_printf(const char *fmt, ...);
58
static void pipe_puts(const char *str);
59
static int pipe_gets(char *str, int maxlen);
60
static void pipe_open();
61
static void pipe_error(char *st);
62
static int pipe_read_ready();
64
static int AppInit(Tcl_Interp *interp);
65
static int ExitAll(ClientData clientData, Tcl_Interp *interp,
66
int argc, char *argv[]);
67
static int TraceCreate(ClientData clientData, Tcl_Interp *interp,
68
int argc, char *argv[]);
69
static int TraceUpdate(ClientData clientData, Tcl_Interp *interp,
70
int argc, char *argv[]);
71
static int TraceReset(ClientData clientData, Tcl_Interp *interp,
72
int argc, char *argv[]);
73
static void trace_volume(int ch, int val);
74
static void trace_panning(int ch, int val);
75
static void trace_prog_init(int ch);
76
static void trace_prog(int ch, int val);
77
static void trace_bank(int ch, int val);
78
static void trace_sustain(int ch, int val);
80
static void get_child(int sig);
81
static void shm_alloc(void);
82
static void shm_free(int sig);
84
static void start_panel(void);
86
#define MAX_MIDI_CHANNELS 16
92
int32 last_time, cur_time;
94
char v_flags[MAX_MIDI_CHANNELS];
95
int16 cnote[MAX_MIDI_CHANNELS];
96
int16 cvel[MAX_MIDI_CHANNELS];
97
int16 ctotal[MAX_MIDI_CHANNELS];
99
char c_flags[MAX_MIDI_CHANNELS];
100
Channel channel[MAX_MIDI_CHANNELS];
103
extern MidiEvent *current_event;
106
uint8 status, channel, note, velocity, voices;
109
#define MAX_OLD_NOTES 500
110
static OldVoice old_note[MAX_OLD_NOTES];
111
static int leading_pointer=0, trailing_pointer=0;
112
static uint32 current_centiseconds = 0;
113
static int songoffset = 0, current_voices = 0;
115
/**********************************************/
117
#define ctl tk_control_mode
121
"Tcl/Tk interface", 'k',
123
ctl_open, ctl_pass_playing_list, ctl_close, ctl_read, cmsg,
124
ctl_refresh, ctl_reset, ctl_file_name, ctl_total_time, ctl_current_time,
126
ctl_master_volume, ctl_program, ctl_volume,
127
ctl_expression, ctl_panning, ctl_sustain, ctl_pitch_bend
130
#define FLAG_NOTE_OFF 1
131
#define FLAG_NOTE_ON 2
138
static PanelInfo *Panel;
140
static int shmid; /* shared memory id */
141
static int child_killed = 0;
143
static int pipeAppli[2],pipePanel[2]; /* Pipe for communication with Tcl/Tk process */
144
static int fpip_in, fpip_out; /* in and out depends in which process we are */
145
static int child_pid; /* Pid for child process */
148
/***********************************************************************/
149
/* Put controls on the pipe */
150
/***********************************************************************/
152
static int cmsg(int type, int verbosity_level, const char *fmt, ...)
155
#define TOO_LONG 2000
158
if ((type==CMSG_TEXT || type==CMSG_INFO || type==CMSG_WARNING) &&
159
ctl.verbosity<verbosity_level)
164
if (strlen(fmt) > TOO_LONG)
168
vfprintf(stderr, fmt, ap);
169
fprintf(stderr, "\n");
170
} else if (type == CMSG_ERROR) {
173
vsprintf(local, fmt, ap);
174
pipe_printf("CERR %d", type);
176
while ((rc = ctl_blocking_read(&val)) != RC_NEXT)
179
vsprintf(local, fmt, ap);
180
pipe_printf("CMSG %d", type);
187
static void ctl_refresh(void)
192
static void ctl_total_time(uint32 tt)
194
int centisecs=(int)tt/(play_mode->rate/100);
197
current_centiseconds = 0;
198
pipe_printf("TIME %d", centisecs);
201
static void ctl_master_volume(int mv)
203
pipe_printf("MVOL %d", mv);
206
static void ctl_file_name(char *name)
208
pipe_printf("FILE %s", name);
211
static void ctl_current_time(uint32 ct)
213
int centisecs, realct, sec;
214
realct = play_mode->output_count(ct);
215
if (realct < 0) realct = 0;
216
else realct += songoffset;
217
centisecs = realct / (play_mode->rate/100);
218
current_centiseconds = (uint32)centisecs;
219
sec = centisecs / 100;
221
/* Panel->cur_time = (int)ct / (play_mode->rate/100) / 100;*/ /* sec order */
222
Panel->cur_time = sec;
226
static void ctl_channel_note(int ch, int note, int vel)
230
if (note == Panel->cnote[ch])
231
Panel->v_flags[ch] = FLAG_NOTE_OFF;
233
} else if (vel > Panel->cvel[ch]) {
234
Panel->cvel[ch] = vel;
235
Panel->cnote[ch] = note;
236
Panel->ctotal[ch] = vel * Panel->channel[ch].volume *
237
Panel->channel[ch].expression / (127*127);
238
Panel->v_flags[ch] = FLAG_NOTE_ON;
243
static void ctl_note(int v)
247
if (!ctl.trace_playing)
250
ch = voice[v].channel;
252
if (ch < 0 || ch >= MAX_MIDI_CHANNELS) return;
254
note = voice[v].note;
255
if (voice[v].status != VOICE_ON)
258
vel = voice[v].velocity;
259
ctl_channel_note(ch, note, vel);
263
static void ctl_tk_note(int status, int ch, int note, int vel)
266
if (ch < 0 || ch >= MAX_MIDI_CHANNELS) return;
268
if (status != VOICE_ON)
270
ctl_channel_note(ch, note, vel);
273
static void ctl_note(int v)
276
if (!ctl.trace_playing)
278
if (voice[v].clone_type != 0) return;
280
old_note[leading_pointer].status = voice[v].status;
281
old_note[leading_pointer].channel = voice[v].channel;
282
old_note[leading_pointer].note = voice[v].note;
283
old_note[leading_pointer].velocity = voice[v].velocity;
284
old_note[leading_pointer].time = current_event->time / (play_mode->rate/100);
288
if (voice[i].status!=VOICE_FREE) n++;
289
old_note[leading_pointer].voices = n;
291
if (leading_pointer == MAX_OLD_NOTES) leading_pointer = 0;
295
static void ctl_note_display(void)
297
int v = trailing_pointer;
300
then = old_note[v].time;
302
while (then <= current_centiseconds && v != leading_pointer)
304
ctl_tk_note(old_note[v].status, old_note[v].channel, old_note[v].note, old_note[v].velocity);
305
current_voices = old_note[v].voices;
307
if (v == MAX_OLD_NOTES) v = 0;
308
then = old_note[v].time;
310
trailing_pointer = v;
313
static void ctl_program(int ch, int val, const char *name)
315
if (!ctl.trace_playing)
318
if (ch < 0 || ch >= MAX_MIDI_CHANNELS) return;
319
Panel->channel[ch].program = val;
320
Panel->c_flags[ch] |= FLAG_PROG;
323
static void ctl_volume(int ch, int val)
325
if (!ctl.trace_playing)
328
Panel->channel[ch].volume = val;
329
ctl_channel_note(ch, Panel->cnote[ch], Panel->cvel[ch]);
332
static void ctl_expression(int ch, int val)
334
if (!ctl.trace_playing)
337
Panel->channel[ch].expression = val;
338
ctl_channel_note(ch, Panel->cnote[ch], Panel->cvel[ch]);
341
static void ctl_panning(int ch, int val)
343
if (!ctl.trace_playing)
346
Panel->channel[ch].panning = val;
347
Panel->c_flags[ch] |= FLAG_PAN;
350
static void ctl_sustain(int ch, int val)
352
if (!ctl.trace_playing)
355
Panel->channel[ch].sustain = val;
356
Panel->c_flags[ch] |= FLAG_SUST;
359
static void ctl_pitch_bend(int ch, int val)
363
static void ctl_reset(void)
367
pipe_printf("RSET %d", ctl.trace_playing);
368
if (!ctl.trace_playing)
370
for (i = 0; i < MAX_MIDI_CHANNELS; i++) {
371
ctl_program(i, channel[i].program, channel[i].name);
372
ctl_volume(i, channel[i].volume);
373
ctl_expression(i, channel[i].expression);
374
ctl_panning(i, channel[i].panning);
375
ctl_sustain(i, channel[i].sustain);
376
ctl_pitch_bend(i, channel[i].pitchbend);
377
ctl_channel_note(i, Panel->cnote[i], 0);
379
for (i=0; i<MAX_OLD_NOTES; i++)
381
old_note[i].time = 0;
383
leading_pointer = trailing_pointer = current_voices = 0;
386
/***********************************************************************/
387
/* OPEN THE CONNECTION */
388
/***********************************************************************/
389
static int ctl_open(int using_stdin, int using_stdout)
397
signal(SIGCHLD, get_child);
398
signal(SIGTERM, shm_free);
399
signal(SIGINT, shm_free);
405
/* Tells the window to disapear */
406
static void ctl_close(void)
409
kill(child_pid, SIGTERM);
417
* Read information coming from the window in a BLOCKING way
420
/* commands are: PREV, NEXT, QUIT, STOP, LOAD, JUMP, VOLM */
422
static int ctl_blocking_read(int32 *valp)
424
char buf[256], *tok, *arg;
427
int new_centiseconds;
429
rc = pipe_gets(buf, sizeof(buf)-1);
430
tok = strtok(buf, " ");
432
while (1)/* Loop after pause sleeping to treat other buttons! */
436
if ((arg = strtok(NULL, " ")) != NULL) {
437
new_volume = atoi(arg);
438
*valp= new_volume - amplification ;
439
return RC_CHANGE_VOLUME;
444
if ((arg = strtok(NULL, " ")) != NULL) {
445
new_centiseconds = atoi(arg);
446
*valp= new_centiseconds*(play_mode->rate / 100) ;
461
/*return RC_REALLY_PREVIOUS;*/
468
*valp=play_mode->rate;
472
*valp=play_mode->rate;
478
pipe_gets(buf, sizeof(buf)-1);
479
tok = strtok(buf, " ");
481
return RC_NONE; /* Resume where we stopped */
484
fprintf(stderr,"UNKNOWN RC_MESSAGE %s\n", tok);
492
* Read information coming from the window in a non blocking way
494
static int ctl_read(int32 *valp)
501
num = last_rc_command;
505
/* We don't wan't to lock on reading */
506
num=pipe_read_ready();
511
return(ctl_blocking_read(valp));
514
static void ctl_pass_playing_list(int number_of_files, const char *list_of_files[])
521
/* Pass the list to the interface */
522
pipe_printf("LIST %d", number_of_files);
523
for (i=0;i<number_of_files;i++)
524
pipe_puts(list_of_files[i]);
526
/* Ask the interface for a filename to play -> begin to play automatically */
527
/*pipe_puts("NEXT");*/
528
command = ctl_blocking_read(&val);
533
if (command==RC_LOAD_FILE)
535
/* Read a LoadFile command */
536
pipe_gets(local, sizeof(local)-1);
537
command=play_midi_file(local);
541
if (command==RC_QUIT) {
543
pipe_gets(local, sizeof(local)-1);
546
/* only stop playing..*/
548
else if (command==RC_ERROR)
549
command=RC_TUNE_END; /* Launch next file */
550
else if (command==RC_CHANGE_VOLUME) /* init volume */
551
amplification += val;
558
case RC_REALLY_PREVIOUS:
569
command = ctl_blocking_read(&val);
575
/* open pipe and fork child process */
576
static void pipe_open(void)
580
res = pipe(pipeAppli);
581
if (res!=0) pipe_error("PIPE_APPLI CREATION");
583
res = pipe(pipePanel);
584
if (res!=0) pipe_error("PIPE_PANEL CREATION");
586
if ((child_pid = fork()) == 0) {
591
/* redirect to stdin/out */
592
dup2(pipePanel[0], fileno(stdin));
594
dup2(pipeAppli[1], fileno(stdout));
600
fpip_in= pipeAppli[0];
601
fpip_out= pipePanel[1];
606
#include <sys/time.h>
610
#include <sys/filio.h>
613
int pipe_read_ready(void)
618
struct timeval timeout;
621
FD_SET(fpip_in, &fds);
622
timeout.tv_sec = timeout.tv_usec = 0;
623
if((cnt = select(fpip_in + 1, &fds, NULL, NULL, &timeout)) < 0)
629
return cnt > 0 && FD_ISSET(fpip_in, &fds) != 0;
633
if(ioctl(fpip_in,FIONREAD,&num) < 0) /* see how many chars in buffer. */
635
perror("ioctl: FIONREAD");
643
/***********************************************************************/
644
/* PIPE COMUNICATION */
645
/***********************************************************************/
647
static void pipe_error(char *st)
649
fprintf(stderr,"CONNECTION PROBLEM WITH TCL/TK PROCESS IN %s BECAUSE:%s\n",
656
static void pipe_printf(const char *fmt, ...)
661
vsprintf(buf, fmt, ap);
665
static void pipe_puts(const char *str)
670
write(fpip_out, str, len);
671
write(fpip_out, &lf, 1);
675
int pipe_gets(char *str, int maxlen)
677
/* blocking reading */
681
/* at least 5 letters (4+\n) command */
683
for (p = str; ; p++) {
694
/*----------------------------------------------------------------
695
* shared memory handling
696
*----------------------------------------------------------------*/
698
static void get_child(int sig)
703
static void shm_alloc(void)
705
shmid = shmget(IPC_PRIVATE, sizeof(PanelInfo),
708
fprintf(stderr, "can't allocate shared memory\n");
711
Panel = (PanelInfo *)shmat(shmid, 0, 0);
712
Panel->reset_panel = 0;
713
Panel->multi_part = 0;
716
static void shm_free(int sig)
718
kill(child_pid, SIGTERM);
719
while (!child_killed)
721
shmctl(shmid, IPC_RMID,NULL);
722
shmdt((char *)Panel);
727
/*----------------------------------------------------------------
728
* start Tk window panel
729
*----------------------------------------------------------------*/
731
static void start_panel(void)
738
/* argv[argc++] = TKPROGPATH; */
739
argv[argc++] = TIMID_DIR "/tkmidity.tcl";
741
if (ctl.trace_playing) {
742
argv[argc++] = "-mode";
743
argv[argc++] = "trace";
746
/* call Tk main routine */
747
Tk_Main(argc, argv, AppInit);
753
/*----------------------------------------------------------------
754
* initialize Tcl application
755
*----------------------------------------------------------------*/
757
static Tcl_Interp *my_interp;
759
static int AppInit(Tcl_Interp *interp)
763
if (Tcl_Init(interp) == TCL_ERROR) {
766
if (Tk_Init(interp) == TCL_ERROR) {
770
Tcl_CreateCommand(interp, "TraceCreate", TraceCreate,
771
(ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
772
Tcl_CreateCommand(interp, "TraceUpdate", TraceUpdate,
773
(ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
774
Tcl_CreateCommand(interp, "TraceReset", TraceReset,
775
(ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
776
Tcl_CreateCommand(interp, "ExitAll", ExitAll,
777
(ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
778
Tcl_CreateCommand(interp, "TraceUpdate", TraceUpdate,
779
(ClientData)NULL, (Tcl_CmdDeleteProc*)NULL);
783
static int ExitAll(ClientData clientData, Tcl_Interp *interp,
784
int argc, char *argv[])
786
/* window is killed; kill the parent process, too */
787
kill(getppid(), SIGTERM);
792
/* evaluate Tcl script */
793
static char *v_eval(const char *fmt, ...)
798
vsprintf(buf, fmt, ap);
799
Tcl_Eval(my_interp, buf);
801
return my_interp->result;
804
static char *v_get2(const char *v1, const char *v2)
806
return Tcl_GetVar2(my_interp, (char *)v1, (char *)v2, 0);
810
/*----------------------------------------------------------------
811
* update Tcl timer / trace window
812
*----------------------------------------------------------------*/
814
#define FRAME_WIN ".body.trace"
815
#define CANVAS_WIN FRAME_WIN ".c"
819
#define WIN_WID (BAR_WID * 16)
820
#define WIN_HGT (BAR_HGT + 11 + 17)
821
#define BAR_HALF_HGT (WIN_HGT / 2 - 11 - 17)
824
static int TraceCreate(ClientData clientData, Tcl_Interp *interp,
825
int argc, char *argv[])
828
v_eval("frame %s -bg black", FRAME_WIN);
829
v_eval("canvas %s -width %d -height %d -bd 0 -bg black "
830
"-highlightthickness 0",
831
CANVAS_WIN, WIN_WID, WIN_HGT);
832
v_eval("pack %s -side top -fill x", CANVAS_WIN);
833
for (i = 0; i < 32; i++) {
835
v_eval("%s create text 0 0 -anchor n -fill white -text 00 "
836
"-tags prog%d", CANVAS_WIN, i);
837
v_eval("%s create poly 0 0 0 0 0 0 -fill yellow -tags pos%d",
839
color = (i == 9 || i == 25) ? "red" : "green";
840
v_eval("%s create rect 0 0 0 0 -fill %s -tags bar%d "
841
"-outline \"\"", CANVAS_WIN, color, i);
843
v_eval("set Stat(TimerId) -1");
844
v_eval("TraceReset");
848
static void trace_bank(int ch, int val)
850
v_eval("%s itemconfigure bar%d -fill %s",
852
(val == 128 ? "red" : "green"));
855
static void trace_prog(int ch, int val)
857
v_eval("%s itemconfigure prog%d -text %02X",
858
CANVAS_WIN, ch, val);
861
static void trace_sustain(int ch, int val)
863
v_eval("%s itemconfigure prog%d -fill %s",
865
(val == 127 ? "green" : "white"));
868
static void trace_prog_init(int ch)
870
int item, yofs, bar, x, y;
874
bar = Panel->multi_part ? BAR_HALF_HGT : BAR_HGT;
878
if (!Panel->multi_part)
881
x = ch * BAR_WID + BAR_WID/2;
883
v_eval("%s coords prog%d %d %d", CANVAS_WIN, item, x, y);
886
static void trace_volume(int ch, int val)
888
int item, bar, yofs, x1, y1, x2, y2;
891
bar = Panel->multi_part ? BAR_HALF_HGT : BAR_HGT;
895
if (!Panel->multi_part)
900
x2 = x1 + BAR_WID - 1;
901
y2 = y1 - bar * val / 127;
902
v_eval("%s coords bar%d %d %d %d %d", CANVAS_WIN,
903
item, x1, y1, x2, y2);
906
static void trace_panning(int ch, int val)
911
v_eval("%s coords pos%d -1 0 -1 0 -1 0", CANVAS_WIN, ch);
917
bar = Panel->multi_part ? BAR_HALF_HGT : BAR_HGT;
921
if (!Panel->multi_part)
926
ap = BAR_WID * val / 127;
927
bp = BAR_WID - ap - 1;
928
v_eval("%s coords pos%d %d %d %d %d %d %d", CANVAS_WIN, item,
929
ap + x, bar + 5 + yofs,
930
bp + x, bar + 1 + yofs,
931
bp + x, bar + 9 + yofs);
934
static int TraceReset(ClientData clientData, Tcl_Interp *interp,
935
int argc, char *argv[])
938
for (i = 0; i < 32; i++) {
940
trace_panning(i, -1);
944
Panel->ctotal[i] = 0;
946
Panel->v_flags[i] = 0;
947
Panel->c_flags[i] = 0;
957
static void update_notes(void)
960
imax = Panel->multi_part ? 32 : 16;
961
for (i = 0; i < imax; i++) {
962
if (Panel->v_flags[i]) {
963
if (Panel->v_flags[i] == FLAG_NOTE_OFF) {
964
Panel->ctotal[i] -= DELTA_VEL;
965
if (Panel->ctotal[i] <= 0) {
966
Panel->ctotal[i] = 0;
967
Panel->v_flags[i] = 0;
970
Panel->v_flags[i] = 0;
972
trace_volume(i, Panel->ctotal[i]);
975
if (Panel->c_flags[i]) {
976
if (Panel->c_flags[i] & FLAG_PAN)
977
trace_panning(i, Panel->channel[i].panning);
978
if (Panel->c_flags[i] & FLAG_BANK)
979
trace_bank(i, Panel->channel[i].bank);
980
if (Panel->c_flags[i] & FLAG_PROG)
981
trace_prog(i, Panel->channel[i].program);
982
if (Panel->c_flags[i] & FLAG_SUST)
983
trace_sustain(i, Panel->channel[i].sustain);
984
Panel->c_flags[i] = 0;
989
static int TraceUpdate(ClientData clientData, Tcl_Interp *interp,
990
int argc, char *argv[])
992
char *playing = v_get2("Stat", "Playing");
993
if (playing && *playing != '0') {
994
if (Panel->reset_panel) {
995
v_eval("TraceReset");
996
Panel->reset_panel = 0;
998
if (Panel->last_time != Panel->cur_time) {
999
v_eval("SetTime %d", Panel->cur_time);
1000
Panel->last_time = Panel->cur_time;
1002
if (ctl.trace_playing)
1005
v_eval("set Stat(TimerId) [after 50 TraceUpdate]");