From dd657ad3f1428b026486db3ec36691df17ddf515 Mon Sep 17 00:00:00 2001 From: "Steve M. Robbins" Date: Sat, 22 Oct 2011 04:54:51 +0200 Subject: Import nyquist_3.05.orig.tar.gz [dgit import orig nyquist_3.05.orig.tar.gz] --- cmt/cext.c | 107 ++++ cmt/cext.h | 194 ++++++ cmt/cleanup.c | 62 ++ cmt/cleanup.h | 9 + cmt/cmdline.c | 479 ++++++++++++++ cmt/cmdline.h | 8 + cmt/cmtcmd.c | 53 ++ cmt/cmtcmd.h | 35 ++ cmt/cmtio.c | 161 +++++ cmt/cmtio.h | 9 + cmt/hash.h | 23 + cmt/hashrout.h | 220 +++++++ cmt/mem.c | 91 +++ cmt/mem.h | 12 + cmt/mfmidi.h | 19 + cmt/midibuff.h | 23 + cmt/midicode.h | 62 ++ cmt/midierr.h | 17 + cmt/midifile.c | 618 ++++++++++++++++++ cmt/midifile.h | 27 + cmt/midifns.c | 1886 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmt/midifns.h | 137 ++++ cmt/midimgr.c | 784 +++++++++++++++++++++++ cmt/midimgr.h | 14 + cmt/moxc.c | 669 ++++++++++++++++++++ cmt/moxc.h | 44 ++ cmt/musiprog.h | 31 + cmt/pitch.h | 9 + cmt/record.c | 638 +++++++++++++++++++ cmt/record.h | 6 + cmt/seq.c | 1199 +++++++++++++++++++++++++++++++++++ cmt/seq.h | 295 +++++++++ cmt/seqdecls.h | 11 + cmt/seqmread.c | 448 +++++++++++++ cmt/seqmread.h | 4 + cmt/seqmwrite.c | 658 +++++++++++++++++++ cmt/seqmwrite.h | 4 + cmt/seqread.c | 1890 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmt/seqread.h | 7 + cmt/seqwrite.c | 282 +++++++++ cmt/seqwrite.h | 6 + cmt/swlogic.h | 197 ++++++ cmt/tempomap.c | 124 ++++ cmt/tempomap.h | 25 + cmt/timebase.c | 322 ++++++++++ cmt/timebase.h | 85 +++ cmt/userio.c | 1285 +++++++++++++++++++++++++++++++++++++ cmt/userio.h | 96 +++ 48 files changed, 13385 insertions(+) create mode 100644 cmt/cext.c create mode 100644 cmt/cext.h create mode 100644 cmt/cleanup.c create mode 100644 cmt/cleanup.h create mode 100644 cmt/cmdline.c create mode 100644 cmt/cmdline.h create mode 100644 cmt/cmtcmd.c create mode 100644 cmt/cmtcmd.h create mode 100644 cmt/cmtio.c create mode 100644 cmt/cmtio.h create mode 100644 cmt/hash.h create mode 100644 cmt/hashrout.h create mode 100644 cmt/mem.c create mode 100644 cmt/mem.h create mode 100644 cmt/mfmidi.h create mode 100644 cmt/midibuff.h create mode 100644 cmt/midicode.h create mode 100644 cmt/midierr.h create mode 100644 cmt/midifile.c create mode 100644 cmt/midifile.h create mode 100644 cmt/midifns.c create mode 100644 cmt/midifns.h create mode 100644 cmt/midimgr.c create mode 100644 cmt/midimgr.h create mode 100644 cmt/moxc.c create mode 100644 cmt/moxc.h create mode 100644 cmt/musiprog.h create mode 100644 cmt/pitch.h create mode 100644 cmt/record.c create mode 100644 cmt/record.h create mode 100644 cmt/seq.c create mode 100644 cmt/seq.h create mode 100644 cmt/seqdecls.h create mode 100644 cmt/seqmread.c create mode 100644 cmt/seqmread.h create mode 100644 cmt/seqmwrite.c create mode 100644 cmt/seqmwrite.h create mode 100644 cmt/seqread.c create mode 100644 cmt/seqread.h create mode 100644 cmt/seqwrite.c create mode 100644 cmt/seqwrite.h create mode 100644 cmt/swlogic.h create mode 100644 cmt/tempomap.c create mode 100644 cmt/tempomap.h create mode 100644 cmt/timebase.c create mode 100644 cmt/timebase.h create mode 100644 cmt/userio.c create mode 100644 cmt/userio.h (limited to 'cmt') diff --git a/cmt/cext.c b/cmt/cext.c new file mode 100644 index 0000000..452e18f --- /dev/null +++ b/cmt/cext.c @@ -0,0 +1,107 @@ +/**************************************************************************** + cext.c + Copyright 1989 Carnegie Mellon University + + August 3, 1987 + Author: Frits Habermann +---------------------------------------------------------------------------- + 02-May-1988 | JCD : portable & AMIGA version. + 17-Oct-1988 | JCD : more portability (FREE). + 28-Apr-2003 | DM : changed includes for portability +****************************************************************************/ + +#include "switches.h" + +#include +#include + +#include "cext.h" +#include "userio.h" + +#define calc_middle(top, bot) (((top - bot) / 2 ) + bottom ) + +#define kbyte 1000 +#define outof_mem(blocksize) (blocksize == 0 ) +#define done_search(top, bot, middle) ( (( (top - bot) < kbyte ) && \ +( !toomuch_mem(middle)) ) || \ + ( outof_mem( middle )) ) + +private boolean toomuch_mem(ushort maximum) +{ + char *test; + boolean istoo_much; + istoo_much = ( (test = (char *) MALLOC(maximum)) == NULL ); + if (test) FREE(test); + return( istoo_much ); +} + +private boolean toolittle_mem(maximum) +ushort maximum; +{ + char *test; + boolean istoo_little; + istoo_little = !( (test = (char *) MALLOC(maximum)) == NULL ); + if (test) FREE( test ); + return(istoo_little); +} + +private ushort get_biggest_block( maximum ) +ushort maximum; +{ + ushort maxblock; + ushort top = maximum; + ushort bottom = 0; + if (!toomuch_mem(maximum)) return(maximum); /* If there's enough memory */ + else { + gprintf(ERROR, "Running out of memory...\n"); + maxblock = calc_middle( top, bottom ); + while( !done_search(top, bottom, maxblock) ) { + if( toomuch_mem(maxblock) ) { + top = maxblock; + maxblock = calc_middle(top,bottom); + } + else if (toolittle_mem(maxblock)) { + bottom = maxblock; + maxblock = calc_middle(top,bottom); + } + } + } + return( maxblock ); +} + +public ulong MyMaxMem(ushort *growbytes) +{ + ulong x; + if( growbytes != NULL ) *growbytes = 0; + x=( (ulong)get_biggest_block((ushort)BIGGEST_BLOCK)); +/* gprintf(TRANS,"cext: MyMaxMem %ld\n",x); */ + return x; +} + +/* note: EXIT is defined to be cmt_exit */ + +void cmt_exit(n) + int n; +{ + cu_cleanup(); +/* For protection, exit is #defined to hide it. Expose it and call it. */ +#undef exit + exit(n); +} + + +#ifdef AMIGA +#ifdef LATTICE +/* for some reason, these don't seem to be defined + anywhere in the standard libraries + */ +#include "signal.h" + +int _FPERR; +int (*_SIGFPE)(int) = SIG_DFL; + +int _oserr; + +#endif +#endif + diff --git a/cmt/cext.h b/cmt/cext.h new file mode 100644 index 0000000..665c13d --- /dev/null +++ b/cmt/cext.h @@ -0,0 +1,194 @@ +/****************************************************************** +* modified JCD 27 Apr-88 for AMIGA +* cext.h -- extensions to c to make it more portable +* Copyright 1989 Carnegie Mellon University +* +******************************************************************* + +cext must provide the following definitions: + +true -- a constant +false -- a boolean constant +private -- defined as static, used to declare local functions +public -- defined as empty string, used to declare exported functions +boolean -- a new type +byte -- unsigned 8-bit quantity +ushort -- unsigned 16-bit quantity +ulong -- unsigned 32-bit quantity +Pointer -- pointer to char, a generic pointer +ABS() -- absolute value of any type of number +MAX() -- maximum of two numbers +MIN() -- minimum of two numbers +ROUND() -- round a double to long + +NULL -- pointer to nothing, a constant +EOS -- end of string, a constant '\0' +MALLOC(x) -- allocates x bytes +FREE(x) -- frees something from MALLOC +AVAILMEM -- tells how much memory is available. + (N.B.: no parens, no args.) +EXIT(n) -- calls exit(n) after shutting down/deallocating resources + +*****************************************************************************/ + +/* CHANGE LOG + * -------------------------------------------------------------------- + * 28Apr03 dm many changes for new conditional compilation switches + * 28Apr03 rbd removed macro redefinitions: min, max + */ + +#ifndef CEXT_H +#ifndef SWITCHES +#include "switches.h" +#endif + +#include +#include +#include + +#if HAS_STDLIB_H +#include +#endif + +#if HAS_SYS_TYPES_H +#include +#endif + +#if HAS_MALLOC_H +#include +#endif + +#if NEED_ULONG +typedef unsigned long ulong; +#endif + +#if NEED_USHORT +typedef unsigned long ushort; +#endif + +#if NEED_BYTE +typedef unsigned char byte; +#endif + +/* There's a name conflict between true/false as an enum type in + * Apple #includes:Types.h on the Mac, and true/false as #defined below + */ +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#define private static +#define public + +#if NEED_DEFINE_MALLOC +public void *malloc(); +#endif + +typedef char *Pointer; + +#ifdef UNIX_MACH +typedef int boolean; +#else +/* hopefully, unsigned short will save sign extension instructions */ +typedef unsigned char boolean; +#endif + +#ifndef ABS +#define ABS(a) (((a) > 0) ? (a) : -(a)) +#endif +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#define MAXULONG 0xffffffff + +#ifndef NULL +#define NULL 0L +#endif + +#ifndef EOS +#define EOS '\0' +#endif + +#define SAFETYBUF 10 /* Safety buffer when allocating memory */ +#define BIGGEST_BLOCK 32765 /* Should find a happy medium for this */ + +#ifdef MACINTOSH /*DMH: gets AVAILMEM in record.c*/ +#include +#define MALLOC(x) malloc((size_t)(x)) /*DMH: size_t is ulong, for MAC*/ +#define FREE(x) free((char *)(x)) +#define AVAILMEM MyMaxMem(NULL)/*???*/ +#endif + +#ifdef LATTICE322 +#define MALLOC malloc +#define FREE free +#define AVAILMEM MyMaxMem(NULL) + +#else + +#ifdef DOS /* was MICROSOFT */ +#define MALLOC malloc +#define FREE free +#define AVAILMEM MyMaxMem(NULL) +#endif +#endif + +#ifdef UNIX +#define MALLOC malloc +#define FREE free +#define AVAILMEM 10000000 /* since we have virtual memory, assume 10Mb */ +#endif + +#ifdef AMIGA +#define MALLOC malloc +#define FREE free +#define AVAILMEM 128000 +#endif + +public ulong MyMaxMem(ushort *); + +#ifndef MEM +#include "mem.h" +#endif + +#ifndef CLEANUP +#include "cleanup.h" +#endif + +#ifdef CMTSTUFF +#define EXIT cmt_exit +public void EXIT(int); +/* don't allow anyone to call exit directly */ +#define exit(n) PLEASE_CALL_EXIT_NOT_exit +#else +#define EXIT(n) exit(n) +#endif + +#define _cext + +#ifndef MALLOC +MALLOC is not defined! +#endif + +#define ROUND(x) ((long) ((x) + 0.5)) + +/* for compatibility */ +#ifdef NEED_ROUND +#define round ROUND +#endif + +#ifndef min +#define min MIN +#define max MAX +#endif + +#define CEXT_H +#endif diff --git a/cmt/cleanup.c b/cmt/cleanup.c new file mode 100644 index 0000000..a0797d9 --- /dev/null +++ b/cmt/cleanup.c @@ -0,0 +1,62 @@ +/* cleanup.c -- registers work to do upon exit */ + +#include "stdio.h" +#include "cext.h" + +typedef struct cleanup_struct { + struct cleanup_struct *next; + cu_fn_type fn; + cu_parm_type obj; +} cleanup_node, *cleanup_type; + +cleanup_type cleanup_list = NULL; + +/* cu_register -- remember function and arg to call in order to clean up */ +/**/ +void cu_register(cu_fn_type fn, cu_parm_type obj) +{ + cleanup_type cu = (cleanup_type) memget(sizeof(cleanup_node)); + cu->fn = fn; + cu->obj = obj; + cu->next = cleanup_list; + cleanup_list = cu; +} + + +/* cu_unregister -- erase memory of obj (should be unique in cleanup list) */ +/**/ +void cu_unregister(obj) + void *obj; +{ + cleanup_type *cu = &cleanup_list; + while (*cu) { + if ((*cu)->obj == obj) { + cleanup_type found = *cu; + *cu = (*cu)->next; /* splice out found */ + memfree((char *) found, sizeof(cleanup_node)); + return; + } + cu = &((*cu)->next); + } +} + + +/* cu_cleanup -- call the registered functions */ +/**/ +void cu_cleanup() +{ + while (cleanup_list) { + cleanup_type cu = cleanup_list; +#ifdef CU_TRACE + gprintf(GTRANS, "cu_cleanup: node %lx fn %lx obj %lx\n", + cu, cu->fn, cu->obj); +#endif + cu->fn(cu->obj); + cleanup_list = cu->next; + memfree((char *) cu, sizeof(cleanup_node)); + } +#ifdef CU_TRACE + gprintf(GTRANS, "cu_cleanup done.\n"); + fflush(stdout); +#endif +} diff --git a/cmt/cleanup.h b/cmt/cleanup.h new file mode 100644 index 0000000..62aaf6f --- /dev/null +++ b/cmt/cleanup.h @@ -0,0 +1,9 @@ +/* cleanup.c -- registers work to do upon exit */ + +typedef void *cu_parm_type; +typedef void (*cu_fn_type)(cu_parm_type); +void cu_register(cu_fn_type fn, cu_parm_type obj); +void cu_unregister(void *obj); +void cu_cleanup(void); + +#define CLEANUP diff --git a/cmt/cmdline.c b/cmt/cmdline.c new file mode 100644 index 0000000..a215b08 --- /dev/null +++ b/cmt/cmdline.c @@ -0,0 +1,479 @@ +/* cmdline.c -- command line parsing routines */ +/* Copyright 1989 Carnegie Mellon University */ +/* + * This module is designed to allow various modules to scan (and rescan) + * the command line for applicable arguments. The goal is to hide as + * much information about switches and their names as possible so that + * switches become more consistent across applications and so that the + * author of an application need not do a lot of work to provide numerous + * options. Instead, each module scans the command line for its own + * arguments. + * + * Command lines are of the following form: + * command -s1 -s2 opt2 -s3 arg1 arg2 -s4 opt4 arg3 + * Note that there are three kinds of command line parameters: + * (1) A Switch is a "-" followed by a name, e.g. "-s1" + * (2) An Option is a Switch followed by a space and name, e.g. "-s2 opt2" + * (3) An Argument is a name by itself, e.g. "arg1" + * Note also that a switch followed by an argument looks just like an + * option, so a list of valid option names is necessary to disambiguate. + * + * Long names are good for readability, but single character abbreviations + * are nice for experienced users. cmdline.c allows single character + * abbreviations provided that they are unambiguous. These are + * recognized with no extra work by the programmer. If an + * isolated '?' is encountered in the command line, then all of + * the options and switches are printed as help and for debugging. + * + * Given that we must tell this module about option names and switch + * names, how should we do it? We can't wait until modules are + * initialized, since often modules want to read the command line + * at initialization time. In the original implementation, the + * main program was supposed to provide names for the whole program, + * but this violates modularity: when an option is added to a module, + * the main program has to be modified too. This is a real pain when + * different machines support different options and you want to have + * a single machine-independent main program. The solution is to + * have the main program import strings describing the options and + * switches used by each module. These are passed into cmdline.c + * before initialization of other modules is begun. + * + * A main program that uses cmdline.c should do the following: + * call cl_syntax(s) for each module's option/switch string. + * The string s should have the following format: + * "opt1description;opt2description;...;switch1description;..." + * where opt1 and opt2 are option names (without the preceding "-"), and + * switch1 is a switch name. The and indicate whether the + * name is an option or a switch. The descriptions are arbitrary strings + * (without semicolons) that are printed out for the user when "?" + * is typed on the command line. + * + * After calling cl_syntax, main() should call + * cl_init(argv, argc) + * cl_init will report an error (to STDERR) if it finds any illegal + * switch or option names in argv, and help will be printed if "?" + * is found in argv. If cl_init returns false, then the user has been + * given an error message or help, and main should probably exit. + * + * Afterward, switches, options, and arguments can be accessed by + * calling cl_switch, cl_option, and cl_arg. If cl_switch or cl_option + * is called with a switch name that was not mentioned in the call to + * cl_init, an error will result. This indicates that the application + * author omitted a valid switch or option name when calling cl_init. + * This is an error because the full set of names is needed for error + * checking and to distinguish arguments from options. + * + */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 13-Jun-86 | Created Change Log +* 6-Aug-86 | Modified for Lattice 3.0 -- use "void" to type some routines +* 20-Sep-89 | Redesigned the interface, adding cl_syntax call. +* 2-Apr-91 | JDW : further changes +* 27-Dec-93 | "@file" as first arg reads command line args from file +* 11-Mar-94 | PLu: Add private to cl_search() definition. +* 28-Apr-03 | DM: true->TRUE, false->FALSE +*****************************************************************************/ + +/* stdlib.h not on PMAX */ +#ifndef mips +#include "stdlib.h" +#endif +#include "stdio.h" +#include "cext.h" +#include "userio.h" +#include "cmdline.h" +#include "ctype.h" +#include "string.h" + +#define syntax_max 10 /* allow for 10 syntax strings */ +private char *syntax[syntax_max]; +private int n_syntax = 0; /* number of strings so far */ +private char **argv; /* command line argument vector */ +private int argc; /* length of argv */ + +private boolean cl_rdy = FALSE; /* set to TRUE when initialized */ + +#define cl_OPT 1 +#define cl_SW 2 +#define cl_INIT 3 +#define cl_ARG 4 + +/***************************************************************************** +* Routines local to this module +*****************************************************************************/ +private char *cl_search(); +private int find_string(); +private void indirect_command(char *filename, char *oldarg0); +private void ready_check(); + +/**************************************************************** +* cl_arg +* Inputs: +* n: the index of the arg needed +* Results: +* pointer to the nth arg, or NULL if none exists +* arg 0 is the command name +*****************************************************************/ + +char *cl_arg(n) + int n; +{ + return (n <= 0 ? argv[0] : + cl_search((char *)NULL, cl_ARG, n)); +} + +/* cl_help -- print help from syntax strings */ +/**/ +void cl_help() +{ + register int i, j; + int count = 0; /* see if there are any switches or flags */ + + for (i = 0; i < n_syntax; i++) { + register char *ptr = syntax[i]; + register char c = *ptr++; + while (c != EOS) { + while (c != EOS && !(isalnum(c))) c = *ptr++; + if (c != EOS) { + count++; + gprintf(TRANS, "-"); + j = 1; + while (c != EOS && c != '<') { + gprintf(TRANS, "%c", c); + c = *ptr++; + j++; + } + if (c != EOS) { + c = *ptr++; + if (c == 'o') { + gprintf(TRANS, " xxx"); + j += 4; + } + } + /* attempt to tab */ + do { + gprintf(TRANS, " "); + } while (j++ < 16); + while (c != EOS && c != '>') c = *ptr++; + if (c != EOS) c = *ptr++; + while (c != EOS && c != ';') { + gprintf(TRANS, "%c", c); + c = *ptr++; + } + gprintf(TRANS, "\n"); + } + } + } + if (!count) gprintf(TRANS, "No switches or options exist.\n"); +} + +/***************************************************************************** +* cl_init +* Inputs: +* char *switches[]: array of switch names +* int nsw: number of switch names +* char *options[]: array of option names +* int nopt: number of option names +* char *av: array of command line fields (argv) +* int ac: number of command line fields (argc) +* Effect: +* Checks that all command line entries are valid. +* Saves info for use by other routines. +* Returns: +* TRUE if syntax checks OK, otherwise false +*****************************************************************************/ + +boolean cl_init(av, ac) + char *av[]; + int ac; +{ + argv = av; + argc = ac; + + /* check for help request */ + if (argc == 2 && strcmp(argv[1], "?") == 0) { + cl_help(); + return FALSE; /* avoid cl_search which would complain about "?" */ + } + /* check for indirection */ + if (argc == 2 && *(argv[1]) == '@') { + /* read new args from file */ + indirect_command(av[1] + 1, av[0]); + } + /* check command line syntax: */ + cl_rdy = TRUE; + return (cl_rdy = (cl_search("true", cl_INIT, 0) != NULL)); +} + + +/**************************************************************** +* cl_int_option +* Inputs: +* char *name: name of option +* long default: default value for option +* Result: +* returns long encoding of the option, deflt if none +* Implementation: +* call cl_option and sscanf result +*****************************************************************/ + +long cl_int_option(name, deflt) + char *name; + long deflt; +{ + char *opt = cl_option(name); + if (opt) { + if (sscanf(opt, "%ld", &deflt) != 1) { + gprintf(TRANS, "Warning: option %s %s not an integer, ignored\n", + name, opt); + } + } + return deflt; +} + + +/**************************************************************** +* cl_search +* Inputs: +* char *name: name of field, must be non-null if opt_sw == cl_INIT +* int opt_sw: option, switch, init, or arg +* int n: argument number (if opt_sw is cl_ARG) +* Result: +* returns pointer to option value/switch if one exists, otherwise null +* Implementation: +* parse the command line until name or arg is found +* see if the option is followed by a string that does +* not start with "-" +*****************************************************************/ + +private char *cl_search(name, opt_sw, n) + char *name; + int opt_sw; + int n; /* if opt_sw is cl_ARG, n > 0 tells which one */ +{ + register int i = 1; /* index into command line */ + boolean abbr; + boolean result = TRUE; + + ready_check(); + + /* parse command line: */ + while (i < argc) { + register char *arg = argv[i]; + /* arguments that start with '-' should be quoted and quotes must + be removed by the application + */ + if (*arg == '-') { + int arg_type = find_string(arg + 1, &abbr); + if (arg_type == cl_OPT) { + i += 1; /* skip name and option */ + /* don't look for '-' because the option might be a + * negative number + */ + if (i >= argc /* || *arg == '-' */) { + if (opt_sw == cl_INIT) { + gprintf(ERROR, "missing argument after %s\n", arg); + result = FALSE; + } + } else if (opt_sw == cl_OPT && + (strcmp(arg + 1, name) == 0 || + (abbr && *(arg + 1) == name[0]))) { + return argv[i]; + } + } else if (arg_type == cl_SW) { + if (opt_sw == cl_SW && + (strcmp(arg + 1, name) == 0 || + (abbr && *(arg + 1) == name[0]))) + return arg; + } else if (opt_sw == cl_INIT) { + gprintf(ERROR, "invalid switch: %s\n", arg); + result = FALSE; + } + } else if (opt_sw == cl_ARG) { + if (n == 1) return arg; + n--; + } + i++; /* skip to next field */ + } + if (opt_sw == cl_INIT) { + /* return name or NULL to represent TRUE or FALSE */ + return (result ? name : NULL); + } + return NULL; +} + +/**************************************************************** +* cl_option +* Inputs: +* char *name: option name +* Outputs: +* returns char *: the option string if found, otherwise null +****************************************************************/ + +char *cl_option(name) +char *name; +{ + return cl_search(name, cl_OPT, 0); +} + +/**************************************************************** +* cl_switch +* Inputs: +* char *name: switch name +* Outputs: +* boolean: TRUE if switch found +****************************************************************/ + +boolean cl_switch(name) +char *name; +{ + return (boolean)(cl_search(name, cl_SW, 0) != NULL); +} + +/* cl_syntax -- install a string specifying options and switches */ +/**/ +boolean cl_syntax(char *s) +{ + if (n_syntax < syntax_max) { + syntax[n_syntax++] = s; + return TRUE; + } else { + gprintf(ERROR, "cl_syntax: out of room\n"); + return FALSE; + } +} + +/**************************************************************** +* find_string +* Inputs: +* char *s: string to find, terminated by any non-alphanumeric +* boolean *abbr: set TRUE if s is an abbreviation, otherwise false +* Effect: +* Looks for s in syntax strings +* Returns: +* 0 = FALSE = not found, 1 = cl_OPT = option, 2 = cl_SW = switch +*****************************************************************/ + +private int find_string(s, abbr) + char *s; + boolean *abbr; +{ + int found_it = FALSE; + int i; + *abbr = FALSE; + for (i = 0; i < n_syntax; i++) { /* loop through strings */ + register char *syntax_ptr = syntax[i]; + while (*syntax_ptr != EOS) { + register char *s_ptr = s; + while (*syntax_ptr != EOS && + !(isalnum(*syntax_ptr))) syntax_ptr++; + while (*s_ptr != EOS && (*s_ptr++ == *syntax_ptr)) + syntax_ptr++; /* only increment if there's a match */ + if (!(isalnum(*s_ptr)) && *syntax_ptr == '<') { + syntax_ptr++; /* advance to the type field */ + if (*syntax_ptr == 's') return cl_SW; + if (*syntax_ptr != 'o') + gprintf(ERROR, + "(internal error) bad cl_syntax string: %s\n", + syntax[i]); + return cl_OPT; + } + /* no match, so go to next */ + while (*syntax_ptr != ';' && *syntax_ptr != EOS) syntax_ptr++; + if (*syntax_ptr == ';') syntax_ptr++; + } + } + + /* no match, maybe there is a single character match */ + if (s[0] == EOS || s[1] != EOS) return FALSE; + + for (i = 0; i < n_syntax; i++) { /* loop through strings */ + char *syntax_ptr = syntax[i]; + while (*syntax_ptr != EOS) { + while (*syntax_ptr != EOS && + !(isalnum(*syntax_ptr))) syntax_ptr++; + if (s[0] == *syntax_ptr) { + if (found_it) return FALSE; /* ambiguous */ + /* else, find the type */ + while (*syntax_ptr != '<' && *syntax_ptr != EOS) + syntax_ptr++; + syntax_ptr++; + if (*syntax_ptr == 's') found_it = cl_SW; + else if (*syntax_ptr == 'o') found_it = cl_OPT; + else return FALSE; /* error in string syntax */ + } + /* no match, so go to next */ + while (*syntax_ptr != ';' && *syntax_ptr != EOS) syntax_ptr++; + if (*syntax_ptr == ';') syntax_ptr++; + } + } + if (found_it) *abbr = TRUE; + return found_it; +} + + +/* get_arg -- get an argument from a file */ +/**/ +boolean get_arg(file, arg) + FILE *file; + char *arg; +{ + int c; + while ((c = getc(file)) != EOF && isspace(c)) ; + if (c == EOF) return FALSE; + ungetc(c, file); + while ((c = getc(file)) != EOF && !isspace(c)) { + *arg++ = c; + } + *arg = 0; + return TRUE; +} + + +/* indirect_command -- get argv, argc from a file */ +/**/ +private void indirect_command(filename, oldarg0) + char *filename; + char *oldarg0; +{ + FILE *argfile = fopen(filename, "r"); + if (!argfile) { + argv = (char **) malloc(sizeof(char *)); + argv[0] = oldarg0; + argc = 1; + } else { + int i = 1; + char arg[100]; + while (get_arg(argfile, arg)) i++; + fclose(argfile); + argfile = fopen(filename, "r"); + argv = (char **) malloc(sizeof(char *) * i); + argv[0] = oldarg0; + argc = i; + i = 1; + while (get_arg(argfile, arg)) { + argv[i] = (char *) malloc(strlen(arg) + 1); + strcpy(argv[i], arg); + i++; + } + fclose(argfile); + } +} + +/**************************************************************** +* ready_check +* Effect: +* Halt program if cl_rdy is not true. +*****************************************************************/ +private void ready_check() +{ + if (!cl_rdy) { + gprintf(ERROR, + "Internal error: cl_init was not called, see cmdline.c\n"); + EXIT(1); + } +} diff --git a/cmt/cmdline.h b/cmt/cmdline.h new file mode 100644 index 0000000..06ff4c3 --- /dev/null +++ b/cmt/cmdline.h @@ -0,0 +1,8 @@ +/* Copyright 1989 Carnegie Mellon University */ + +char *cl_arg(int n); +boolean cl_init(char *av[], int ac); +long cl_int_option(char *name, long deflt); +char *cl_option(char *name); +boolean cl_switch(char *name); +boolean cl_syntax(char *name); diff --git a/cmt/cmtcmd.c b/cmt/cmtcmd.c new file mode 100644 index 0000000..2881467 --- /dev/null +++ b/cmt/cmtcmd.c @@ -0,0 +1,53 @@ +/* cmtcmd.c -- routines for the moxc side of the command interface */ + +#include "switches.h" +#include "stdio.h" +#ifdef AMIGA +#include "exec/types.h" +#include "exec/exec.h" +#endif +#include "cmtcmd.h" +#include "cext.h" +#include "userio.h" +#include "string.h" + +#define HASHELEM(p) ((p).symbol_name) +#define HASHVAL 50 +#define HASHENTRIES 50 +#define HASHENTER lookup +#define HASHNOCOPY + +#include "hashrout.h" + +void defvar(name, addr) + char *name; + int *addr; +{ + int i = lookup(name); + HASHENTRY(i).symb_type = var_symb_type; + HASHENTRY(i).ptr.intptr = addr; +} + + +void defun(name, addr) + char *name; + int (*addr)(); +{ + int i = lookup(name); + HASHENTRY(i).symb_type = fn_symb_type; + HASHENTRY(i).ptr.routine = addr; +} + + +void defvec(name, addr, size) + char *name; + int *addr; + int size; +{ + int i = lookup(name); + HASHENTRY(i).symb_type = vec_symb_type; + HASHENTRY(i).size = size; + HASHENTRY(i).ptr.intptr = addr; +} + + diff --git a/cmt/cmtcmd.h b/cmt/cmtcmd.h new file mode 100644 index 0000000..9ad1cac --- /dev/null +++ b/cmt/cmtcmd.h @@ -0,0 +1,35 @@ +/* cmtcmd.h -- header for remote action and variable setting interface */ + +#define var_symb_type 333 +#define fn_symb_type 555 +#define vec_symb_type 777 + +#ifdef AMIGA +struct cmd_msg { + struct Message msg; + long symb_type; /* one of var_, fn_, or vec_symb_type */ + char *symbol_name; + long the_args[8]; /* args for function call. If var_symb_type, + the_args[0] is the new value. If vec_symb_type, + the_args[0] is index, the_args[1] is new value. */ +}; +#endif + +typedef struct symb_descr { + char *symbol_name; + int symb_type; + int size; /* for array bounds checking */ + union { + int *intptr; + int (*routine)(); + } ptr; +} symb_descr_node; + +int lookup(char *s); +void defvar(char *name, int *addr); +void defvec(char *name, int *addr, int size); +typedef int (*defun_type)(); +void defun(char *name, defun_type addr); + +#define HASHTYPE symb_descr_node +#include "hash.h" diff --git a/cmt/cmtio.c b/cmt/cmtio.c new file mode 100644 index 0000000..e6f1ebc --- /dev/null +++ b/cmt/cmtio.c @@ -0,0 +1,161 @@ +/* + ********************************************************************** + * File io.c + ********************************************************************** + * + * Non blocking input routine + * Works by puttng the terminal in CBREAK mode and using the FIONREAD + * ioctl call to determine the number of characters in the input queue + */ + +#include "stdio.h" +#include "io.h" +#include +#include +#include +#include +#include "cext.h" + +int IOinputfd; /* input file descriptor (usually 0) */ + +int IOnochar; /* Value to be returned by IOgetchar() + where there is no input to be had */ + +static struct sgttyb IOoldmodes, IOcurrentmodes; + /* Initial and current tty modes */ + +/* + * IOsetup(inputfd) + * Args: + * inputfd - input file descriptor (should be zero for standard input) + * Returns: + * 0 - if all goes well + * -1 - if an ioctl fails (also calls perror) + * Side Effects: + * Puts the terminal in CBREAK mode - before process termination + * IOcleanup() should be called to restore old terminal modes + * Catch's interrupts (if they are not already being caught) and + * calls IOcleanup before exiting + * + */ + +#define ERROR(s) return (perror(s), -1) + +IOsetup(inputfd) +{ + static IOdiegracefully(); + int (*interrupt_handler)(); + + IOinputfd = inputfd; + IOnochar = NOCHAR; + if(ioctl(IOinputfd, TIOCGETP, &IOoldmodes) < 0) + ERROR("IOsetup"); + + IOcurrentmodes = IOoldmodes; + IOcurrentmodes.sg_flags |= CBREAK; + IOcurrentmodes.sg_flags &= ~ECHO; + if(ioctl(IOinputfd, TIOCSETP, &IOcurrentmodes)) + ERROR("IOsetup-2"); + + if( (interrupt_handler = signal(SIGINT, IOdiegracefully)) != 0) + signal(SIGINT, interrupt_handler); + return 0; +} + +static +IOdiegracefully() +{ + write(2, "\nBye\n", 5); + IOcleanup(); + EXIT(2); +} + +/* + * IOcleanup() + * Returns: + * 0 - if all goes well + * -1 - if an ioctl fails (also calls perror) + * Side Effects: + * Restores initial terminal modes + */ + +IOcleanup() +{ + if(ioctl(IOinputfd, TIOCSETP, &IOoldmodes) < 0) + ERROR("IOclean"); + return 0; +} + + +/* + * IOgetchar() + * Returns: + * A character off the input queue if there is one, + * IOnochar if there is no character waiting to be read, + * -1 if an ioctl fails (shouldn't happen if IOsetup went OK) + */ + +#ifndef UNIX_MACH + +IOgetchar() +{ + int n; + char c; + + if(ioctl(IOinputfd, FIONREAD, &n) < 0) + ERROR("IOgetchar"); + if(n <= 0) + return IOnochar; + switch(read(IOinputfd, &c, 1)) { + case 1: + return c; + case 0: + return EOF; + default: + ERROR("IOgetchar-read"); + } +} + +#ifdef IOGETCHAR2 +IOgetchar2() +{ + int nfds, readfds = 1 << IOinputfd; + char c; + static struct timeval zero; + + if(IOinputfd < 0 || IOinputfd >= 32) { + printf("IOgetchar2: bad IOinputfd (%d)%s\n", IOinputfd, + IOinputfd == -1 ? "Did you call IOsetup(fd)?" : ""); + } + nfds = select(32, &readfds, 0, 0, &zero); + if(nfds > 0) { + switch(read(IOinputfd, &c, 1)) { + case 0: + return EOF; + case 1: + return c; + default: + printf("IOgetchar2: read failed!\n"); + return NOCHAR; + } + } + else if(nfds < 0) + printf("IOgetchar2: select failed!\n"); + return NOCHAR; +} +#endif + +/* + * IOwaitchar() + * Returns: + * A character off the input queue. Waits if necessary. + */ + +int IOwaitchar() +{ + char c; + if (read(IOinputfd, &c, 1) == 1) return c; + else return EOF; +} + +#endif /* not UNIX_MACH */ diff --git a/cmt/cmtio.h b/cmt/cmtio.h new file mode 100644 index 0000000..3e50ad9 --- /dev/null +++ b/cmt/cmtio.h @@ -0,0 +1,9 @@ +#define NOCHAR -2 + +int IOinputfd; +int IOnochar; + +int IOsetup(int inputfd); +int IOcleanup(void); +int IOgetchar(void); +int IOwaitchar(void); diff --git a/cmt/hash.h b/cmt/hash.h new file mode 100644 index 0000000..0257cfe --- /dev/null +++ b/cmt/hash.h @@ -0,0 +1,23 @@ +/* + * This file should be included in all files that use + * the HASHENTRY macro; see hashrout.h for details + */ +/* Copyright 1989 Carnegie Mellon University */ + +#ifndef HASHTYPE +# include "-- HASHTYPE undefined" +#endif + +/* + * An element really is a HASHTYPE along with a h_next entry, + * which chains together entries of the same hash value. + */ + +typedef struct hashelem { + HASHTYPE h_elem; + struct hashelem *h_next; +} hashelem; + +extern hashelem hashfirstchunk[]; + +#define HASHENTRY(i) (hashfirstchunk[i].h_elem) diff --git a/cmt/hashrout.h b/cmt/hashrout.h new file mode 100644 index 0000000..a566b9f --- /dev/null +++ b/cmt/hashrout.h @@ -0,0 +1,220 @@ +/* hashrout.h -- Rubine's hash table package */ +/* Copyright 1989 Carnegie Mellon University */ + +/* ChangeLog: + * 2-jan-85 rbd Added option to count entries: define COUNTER and + * HASHENTER routine will increment it on new entries + * 28-Apr-03 DM Explicit declaration of int type for HASHENTER() + */ + +/* + * Generic symbol table functions + * + * After writing this code over for a bunch of interpreters + * I think I know how to do it once and for all, generally + * enough for most application. + * There are enough settable parameters to suit anyone, and + * the defaults usually do something sane. + * + * The basic idea is that you have a bunch of symbol table entries + * that need to be entered or looked up given a character string. + * Symbol table entries are usually structures, and one element + * of the structure must be the character string of the key. + * The structure should be given a type name, and these routines + * are informed of the type name via + * #define HASHTYPE type-name-of-symbol-table-entry + * You must inform the routines how to access the character string + * given the symbol table entry; for example + * #define HASHELEM(p) ((p).name) + * There are two size parameters - the number of different hash + * values and the number of symbol table entries to allocate at one + * time. Both default to 256. + * #define HASHVAL 128 + * #define HASHENTRIES 512 + * The name of the function that performs both entry and lookup is + * enter(name) - it takes one argument, a character string. The name + * of the function may be changed via + * #define HASHENTER new-name-for-enter-routine + * Note that if there is no entry in the table for name, name is entered + * automatically. The returned structure will have only the name + * field filled in; the rest of the fields are guarenteed to be zero + * so an application can check if this is the first time name was entered. + * If HASHPOINT is defined, the enter routine returns a pointer to a hash + * table entry. In the default case, i.e. HASHPOINT undefined, the + * enter routine returns an integer between zero and HASHENTRIES. + * The macro HASHENTRY(i) will, given that integer, return the + * table element (not a pointer to it), so for example, + * HASHENTRY(enter(x)).name == x. + * Any file that wishes to use HASHENTRY should have at the top + * #define HASHTYPE type-name-of-symbol-table-entry + * #include "hash.h" + * Note in this case at most HASHENTRIES entries will be allocated + * before hash table overflow. If HASHPOINT is defined, allocations + * will take place until memory runs out. + * By default, hash strings are copied into space obtained from malloc + * before being placed in new entry. This copying can be supressed + * by defining HASHNOCOPY. + * The following is an example of using the hash table stuff. + +typedef struct { + char *n_name; + int n_value; +} symbolentry; + +#define HASHTYPE symbolentry +#define HASHELEM(p) ((p).n_name) +#define HASHENTRIES 1024 + +#include "hashrout.h" + +*/ + + +/* + * OK, now the meat. + */ + +#ifndef HASHTYPE +# include "-- HASHTYPE undefined" +#endif + +#ifndef HASHELEM +# include "-- HASHELEM undefined" +#endif + +#ifndef HASHVAL +# define HASHVAL 256 +#endif + +#ifndef HASHENTRIES +# define HASHENTRIES 256 +#endif + +#ifndef HASHENTER +# define HASHENTER enter +#endif + +/* + * HASHNOCOPY, HASHPOINT are undefined by default + */ + +/* + * get definition of hash elem structure + */ + +#ifndef HASHENTRY +# include "hash.h" +#endif + +/* + * Table of pointers, indexed by hash values + */ + +hashelem *hashtab[HASHVAL]; + +/* + * First chunk of elements, pointer to the start, + * and index for allocation + */ + +hashelem hashfirstchunk[HASHENTRIES]; +hashelem *hashchunk = hashfirstchunk; +int hashindex = 0; + +/* + * stdio.h if we don't have it yet + */ + +#ifndef _NFILE +# include +#endif + +/* + * Declare counter if necessary + */ + +#ifdef COUNTER +extern COUNTER; +#endif + +/* + * The enter routine + */ + +#ifdef HASHPOINT +HASHTYPE * +#else +int +#endif +HASHENTER (s) +char *s; +{ + register int i, hash; + register hashelem *elem; + + /* + * Compute s's hash value + * I really should look up some good hash functions, but + * I haven't bothered. + */ + +for(i = 0, hash = 0; s[i] != '\0' && i < 15; i++) + hash += (i + 1) * s[i]; +hash %= HASHVAL; + +/* + * search for s in the table + */ + +for(elem = hashtab[hash]; elem != NULL; elem = elem->h_next) + if(strcmp(s, HASHELEM((elem->h_elem))) == 0) { /* found it */ +#ifdef HASHPOINT + return(&elem->h_elem); +#else + return(elem - hashfirstchunk); +#endif + } + +if(hashindex >= HASHENTRIES) { +#ifdef HASHPOINT + char *calloc(); + + hashindex = 0; + hashchunk = (hashelem *) calloc(HASHENTRIES, sizeof(hashelem)); + if(hashchunk == NULL) { + gprintf(FATAL, "No mem for hash symbol table\n"); + EXIT(1); +#ifdef COUNTER + COUNTER++; /* optional symbol counter */ +#endif + } +#else + gprintf(FATAL, "No hash table space, increase HASHENTRIES\n"); + EXIT(1); +#endif +} + +/* + * Splice a new entry into the list and fill in the string field + */ + +elem = &hashchunk[hashindex++]; +elem->h_next = hashtab[hash]; +hashtab[hash] = elem; + +#ifdef HASHNOCOPY +HASHELEM((elem->h_elem)) = s; +#else +{ + char *strcpy(); + HASHELEM((elem->h_elem)) = memget((strlen(s) + 1)); + strcpy(HASHELEM((elem->h_elem)), s); +} +#endif + +#ifdef HASHPOINT +return(&elem->h_elem); +#else +return(elem - hashfirstchunk); +#endif +} diff --git a/cmt/mem.c b/cmt/mem.c new file mode 100644 index 0000000..7256188 --- /dev/null +++ b/cmt/mem.c @@ -0,0 +1,91 @@ +/* mem.c -- fast memory allocation/deallocation module */ + +/* Allocate large chunks of memory using malloc. From the chunks, + allocate memory as needed on long-word boundaries. Memory is + freed by linking memory onto a freelist. An array of freelists, + one for each size, is maintained and checked before going to the + chunck for more memory. The freelist array only holds lists of + nodes up to a certain size. After that, malloc is used directly. + */ +/* CHANGE LOG + ---------------------------------------------------------------------- + 28-Apr-03 | DM : fix #includes for portability + ---------------------------------------------------------------------- + */ + +#include "switches.h" + +#include +#include + +#include "cext.h" +#include "userio.h" + +/* how many bytes in the largest node managed in mem_free_list array */ +#define MAX_SIZE_FOR_FREELIST 256 + +long *mem_free_list[MAX_SIZE_FOR_FREELIST/4]; + +#define MEM_CHUNK_SIZE 4096 +char *mem_chunk; +long mem_chunk_remaining = 0; + +void meminit() +{ + int i; + for (i = 0; i < MAX_SIZE_FOR_FREELIST/4; i++) { + mem_free_list[i] = NULL; + } +} + + +void *memget(register size_t size) +{ + if (size > MAX_SIZE_FOR_FREELIST) { +/* gprintf(TRANS, "memget calling MALLOC\n"); */ + return MALLOC(size); + } else { + long **p = mem_free_list + ((size - 1) >> 2); + if (*p) { + register long *result = *p; + *p = (long *) *result; +/* gprintf(TRANS, "memget->%lx\n", result); */ + return (char *) result; + } else if ((size_t) mem_chunk_remaining >= size) { + register char *result = mem_chunk; + size = (size + 3) & ~3; /* round up to multiple of 4 */ + mem_chunk += size; + mem_chunk_remaining -= size; +/* gprintf(TRANS, "memget->%lx\n", result); */ + return result; + /* note that we throw away remaining chunk when there isn't enough */ + } else if ((mem_chunk = (char *) MALLOC(MEM_CHUNK_SIZE))) { + register char *result = mem_chunk; +/* gprintf(TRANS, "mem_chunk at %lx\n", mem_chunk); */ + size = (size + 3) & ~3; /* round up to multiple of 4 */ + mem_chunk += size; + mem_chunk_remaining = MEM_CHUNK_SIZE - size; +/* gprintf(TRANS, "memget->%lx\n", result); */ + return result; + } else { + return NULL; + } + } +} + + +void memfree(register void *ptr, register size_t size) +{ + register long **p = (long **) ptr; + if (size > MAX_SIZE_FOR_FREELIST) { + FREE(ptr); + } else { + register long **head_ptr = mem_free_list + ((size - 1) >> 2); + *p = *head_ptr; + *head_ptr = (long *) p; + } +} + + + + diff --git a/cmt/mem.h b/cmt/mem.h new file mode 100644 index 0000000..ea33ba9 --- /dev/null +++ b/cmt/mem.h @@ -0,0 +1,12 @@ +#ifdef AMIGA +#include "stddef.h" +#endif + +extern long *mem_free_list[]; + +void *memget(register size_t size); +void memfree(register void *ptr, register size_t size); + +void meminit(void); + +#define MEM diff --git a/cmt/mfmidi.h b/cmt/mfmidi.h new file mode 100644 index 0000000..9621c67 --- /dev/null +++ b/cmt/mfmidi.h @@ -0,0 +1,19 @@ +#define NOTEOFF 0x80 +#define NOTEON 0x90 +#define PRESSURE 0xa0 +#define CONTROLLER 0xb0 +#define PITCHBEND 0xe0 +#define PROGRAM 0xc0 +#define CHANPRESSURE 0xd0 + +/* These are the strings used in keynote to identify Standard MIDI File */ +/* meta text messages. */ + +#define METATEXT "Text Event" +#define METACOPYRIGHT "Copyright Notice" +#define METASEQUENCE "Sequence/Track Name" +#define METAINSTRUMENT "Instrument Name" +#define METALYRIC "Lyric" +#define METAMARKER "Marker" +#define METACUE "Cue Point" +#define METAUNRECOGNIZED "Unrecognized" diff --git a/cmt/midibuff.h b/cmt/midibuff.h new file mode 100644 index 0000000..f8dc1c1 --- /dev/null +++ b/cmt/midibuff.h @@ -0,0 +1,23 @@ +/* midibuff.h -- defines the size of the midi input buffer */ + +/* midi input buffer */ +/* WARNING: BUFF_SIZE must be a power of 2 so we can use masking to wrap */ +#define EVENT_COUNT 128 +#define EVENT_SIZE 4 +#define BUFF_SIZE (EVENT_COUNT * EVENT_SIZE) +#define BUFF_MASK (BUFF_SIZE - 1) + +#ifdef WINDOWS +#define huge +#endif + +extern byte huge *xbuff; /* application-supplied sysex buffer */ +extern long xbufmask; /* mask for circular buffer */ +extern long xbufhead; /* buffer head and tail offsets */ +extern long xbuftail; +extern int midi_error; +/* midi input buffer */ +/* data buffer, declared long to get 32-bit alignment: */ +extern long buff[BUFF_SIZE/4]; +extern int buffhead; /* buffer head and tail pointers */ +extern int bufftail; diff --git a/cmt/midicode.h b/cmt/midicode.h new file mode 100644 index 0000000..26361d3 --- /dev/null +++ b/cmt/midicode.h @@ -0,0 +1,62 @@ +/************************************************************************ +* +* Midi codes +* Copyright 1989 Carnegie Mellon University +* +************************************************************************* +* Change Log +* Date | Change +*-----------+------------------------------------------------------------ +* 11-Mar-94 | PLu : Port to IRI +************************************************************************/ + +#define MIDI_DATA(d) (0x7f & (d)) +#define MIDI_CHANNEL(c) (0x0f & ((c) - 1)) +#define MIDI_PORT(c) (((c) - 1) >> 4) +#define MIDI_PROGRAM(p) MIDI_DATA((p) - 1) + +#define MIDI_STATUS_BIT 0x80 +#define MIDI_COMMON 0x70 +#define MIDI_CODE_MASK 0xf0 +#define MIDI_CHN_MASK 0x0f +#define MIDI_REALTIME 0xf8 +#define MIDI_CHAN_MODE 0xfa + +#define MIDI_OFF_NOTE 0x80 +#define MIDI_ON_NOTE 0x90 +#define MIDI_POLY_TOUCH 0xa0 +#define MIDI_CTRL 0xb0 +#define MIDI_CH_PROGRAM 0xc0 +#define MIDI_TOUCH 0xd0 +#define MIDI_BEND 0xe0 + +#ifdef UNIX_IRIX_MIDIFNS +#define CMT_MIDI_SYSEX 0xf0 +#define CMT_MIDI_EOX 0xf7 +#else +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 +#endif +#define MIDI_Q_FRAME 0xf1 +#define MIDI_SONG_POINTER 0xf2 +#define MIDI_SONG_SELECT 0xf3 +#define MIDI_F4 0xf4 +#define MIDI_F5 0xf5 +#define MIDI_TUNE_REQ 0xf6 +#define MIDI_TIME_CLOCK 0xf8 +#define MIDI_F9 0xf9 +#define MIDI_START 0xfa +#define MIDI_CONTINUE 0xfb +#define MIDI_STOP 0xfc +#define MIDI_FD 0xfd +#define MIDI_ACTIVE_SENSING 0xfe +#define MIDI_SYS_RESET 0xff + +#define MIDI_LOCAL 0x7a +#define MIDI_LOCAL_OFF 0x00 +#define MIDI_LOCAL_ON 0x7f +#define MIDI_ALL_OFF 0x7b +#define MIDI_OMNI_OFF 0x7c +#define MIDI_OMNI_ON 0x7d +#define MIDI_MONO_ON 0x7e +#define MIDI_POLY_ON 0x7f diff --git a/cmt/midierr.h b/cmt/midierr.h new file mode 100644 index 0000000..d00ce4c --- /dev/null +++ b/cmt/midierr.h @@ -0,0 +1,17 @@ +/* midierr.h -- error codes */ + +#define NESTERR 1 /* nested interrupts */ +#define BUFFOVFL 2 /* input buffer overflow */ +#define CMDERR 3 /* unknown command from mpu-401 */ +#define TIMEOUTERR 4 /* interface timeout */ +#define MSGERR 5 /* invalid message */ +#define SYSEXOVFL 6 /* sysex buffer overflow */ +#define MEMERR 7 /* internal out of memory */ +#define RECVERR 8 /* receive error or device buffer overflow */ +#define MIDIMGRERR 9 /* error reported by midi manager (Macintosh) */ + +#define SYSEX_ERR ((1< +#include + +#include "mfmidi.h" +#include "midifile.h" +#include "cext.h" +#include "userio.h" +#include "string.h" + +#define MIDIFILE_ERROR -1 + +#ifdef PROTOTYPES +#define NOARGS void +#else +#define NOARGS +#endif + +/* public stuff */ +extern int abort_flag; + +/* Functions to be called while processing the MIDI file. */ +void (*Mf_starttrack)(NOARGS) = 0; +void (*Mf_endtrack)(NOARGS) = 0; +int (*Mf_getc)(NOARGS) = 0; +void (*Mf_eot)(NOARGS) = 0; +#ifdef PROTOTYPES +void (*Mf_error)(char *) = 0; +void (*Mf_header)(int,int,int) = 0; +void (*Mf_on)(int,int,int) = 0; +void (*Mf_off)(int,int,int) = 0; +void (*Mf_pressure)(int,int,int) = 0; +void (*Mf_controller)(int,int,int) = 0; +void (*Mf_pitchbend)(int,int,int) = 0; +void (*Mf_program)(int,int) = 0; +void (*Mf_chanpressure)(int,int) = 0; +void (*Mf_sysex)(int,char*) = 0; +void (*Mf_arbitrary)(int,char*) = 0; +void (*Mf_metamisc)(int,int,char*) = 0; +void (*Mf_seqnum)(int) = 0; +void (*Mf_smpte)(int,int,int,int,int) = 0; +void (*Mf_timesig)(int,int,int,int) = 0; +void (*Mf_tempo)(int) = 0; +void (*Mf_keysig)(int,int) = 0; +void (*Mf_sqspecific)(int,char*) = 0; +void (*Mf_text)(int,int,char*) = 0; +#else +void (*Mf_error)() = 0; +void (*Mf_header)() = 0; +void (*Mf_on)() = 0; +void (*Mf_off)() = 0; +void (*Mf_pressure)() = 0; +void (*Mf_controller)() = 0; +void (*Mf_pitchbend)() = 0; +void (*Mf_program)() = 0; +void (*Mf_chanpressure)() = 0; +void (*Mf_sysex)() = 0; +void (*Mf_arbitrary)() = 0; +void (*Mf_metamisc)() = 0; +void (*Mf_seqnum)() = 0; +void (*Mf_smpte)() = 0; +void (*Mf_tempo)() = 0; +void (*Mf_timesig)() = 0; +void (*Mf_keysig)() = 0; +void (*Mf_sqspecific)() = 0; +void (*Mf_text)() = 0; +#endif + +int Mf_nomerge = 0; /* 1 => continue'ed system exclusives are */ + /* not collapsed. */ +long Mf_currtime = 0L; /* current time in delta-time units */ +int Mf_skipinit = 0; /* 1 if initial garbage should be skipped */ + +/* private stuff */ + +static long Mf_toberead = 0L; + +static long readvarinum(NOARGS); +static long read32bit(NOARGS); +static int read16bit(NOARGS); +static void msgenlarge(NOARGS); +static char *msg(NOARGS); +static int readheader(NOARGS); +static void readtrack(NOARGS); +static void sysex(NOARGS), msginit(NOARGS); +static int egetc(NOARGS); +static int msgleng(NOARGS); + +#ifdef PROTOTYPES +static int readmt(char*,int); +static long to32bit(int,int,int,int); +static int to16bit(int,int); +static void mferror(char *); +static void badbyte(int); +static void metaevent(int); +static void msgadd(int); +static void chanmessage(int,int,int); +#else +static long to32bit(); +static int to16bit(); +static void mferror(); +static void badbyte(); +static void metaevent(); +static void msgadd(); +static void chanmessage(); +#endif + +static int midifile_error; + +void +midifile() /* The only non-static function in this file. */ +{ + int ntrks; + midifile_error = 0; + + if ( Mf_getc == 0 ) { + mferror("mf.h() called without setting Mf_getc"); + return; + } + ntrks = readheader(); + if (midifile_error) return; + if ( ntrks <= 0 ) { + mferror("No tracks!"); + /* no need to return since midifile_error is set */ + } + while ( ntrks-- > 0 && !midifile_error && !check_aborted()) + readtrack(); + if (check_aborted()) { + mferror("Midifile read aborted\n\ +\tthe rest of your file will be ignored.\n"); + if (abort_flag == BREAK_LEVEL) abort_flag = 0; + } +} + +static int +readmt(s,skip) /* read through the "MThd" or "MTrk" header string */ +char *s; +int skip; /* if 1, we attempt to skip initial garbage. */ +{ + int nread = 0; + char b[4]; + char buff[32]; + int c; + char *errmsg = "expecting "; + + retry: + while ( nread<4 ) { + c = (*Mf_getc)(); + if ( c == EOF ) { + errmsg = "EOF while expecting "; + goto err; + } + b[nread++] = c; + } + /* See if we found the 4 characters we're looking for */ + if ( s[0]==b[0] && s[1]==b[1] && s[2]==b[2] && s[3]==b[3] ) + return(0); + if ( skip ) { + /* If we are supposed to skip initial garbage, */ + /* try again with the next character. */ + b[0]=b[1]; + b[1]=b[2]; + b[2]=b[3]; + nread = 3; + goto retry; + } + err: + (void) strcpy(buff,errmsg); + (void) strcat(buff,s); + mferror(buff); + return(0); +} + +static int +egetc() /* read a single character and abort on EOF */ +{ + int c = (*Mf_getc)(); + + if ( c == EOF ) { + mferror("premature EOF"); + return EOF; + } + Mf_toberead--; + return(c); +} + +static int +readheader() /* read a header chunk */ +{ + int format, ntrks, division; + + if ( readmt("MThd",Mf_skipinit) == EOF ) + return(0); + + Mf_toberead = read32bit(); + if (midifile_error) return MIDIFILE_ERROR; + format = read16bit(); + if (midifile_error) return MIDIFILE_ERROR; + ntrks = read16bit(); + if (midifile_error) return MIDIFILE_ERROR; + division = read16bit(); + if (midifile_error) return MIDIFILE_ERROR; + + if ( Mf_header ) + (*Mf_header)(format,ntrks,division); + + /* flush any extra stuff, in case the length of header is not 6 */ + while ( Mf_toberead > 0 && !midifile_error) + (void) egetc(); + return(ntrks); +} + +static void +readtrack() /* read a track chunk */ +{ + /* This array is indexed by the high half of a status byte. It's */ + /* value is either the number of bytes needed (1 or 2) for a channel */ + /* message, or 0 (meaning it's not a channel message). */ + static int chantype[] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 through 0x70 */ + 2, 2, 2, 2, 1, 1, 2, 0 /* 0x80 through 0xf0 */ + }; + long lookfor, lng; + int c, c1, type; + int sysexcontinue = 0; /* 1 if last message was an unfinished sysex */ + int running = 0; /* 1 when running status used */ + int status = 0; /* (possibly running) status byte */ + int needed; + + if ( readmt("MTrk",0) == EOF ) + return; + + Mf_toberead = read32bit(); + + if (midifile_error) return; + + Mf_currtime = 0L; + + if ( Mf_starttrack ){ + + (*Mf_starttrack)(); + } + while ( Mf_toberead > 0 ) { + + Mf_currtime += readvarinum(); /* delta time */ + if (midifile_error) return; + + c = egetc(); if (midifile_error) return; + + if ( sysexcontinue && c != 0xf7 ) { + mferror("didn't find expected continuation of a sysex"); + return; + } + if ( (c & 0x80) == 0 ) { /* running status? */ + if ( status == 0 ) { + mferror("unexpected running status"); + return; + } + running = 1; + } + else { + status = c; + running = 0; + } + + needed = chantype[ (status>>4) & 0xf ]; + + if ( needed ) { /* ie. is it a channel message? */ + + if ( running ) + c1 = c; + else { + c1 = egetc(); + if (midifile_error) return; + } + chanmessage( status, c1, (needed>1) ? egetc() : 0 ); + if (midifile_error) return; + continue;; + } + + switch ( c ) { + + case 0xff: /* meta event */ + + type = egetc(); + if (midifile_error) return; + /* watch out - Don't combine the next 2 statements */ + lng = readvarinum(); + if (midifile_error) return; + lookfor = Mf_toberead - lng; + msginit(); + + while ( Mf_toberead > lookfor ) { + char c = egetc(); + if (midifile_error) return; + msgadd(c); + } + metaevent(type); + break; + + case 0xf0: /* start of system exclusive */ + + /* watch out - Don't combine the next 2 statements */ + lng = readvarinum(); + if (midifile_error) return; + lookfor = Mf_toberead - lng; + msginit(); + msgadd(0xf0); + + while ( Mf_toberead > lookfor ) { + c = egetc(); + if (midifile_error) return; + msgadd(c); + } + if ( c==0xf7 || Mf_nomerge==0 ) + sysex(); + else + sysexcontinue = 1; /* merge into next msg */ + break; + + case 0xf7: /* sysex continuation or arbitrary stuff */ + + /* watch out - Don't combine the next 2 statements */ + lng = readvarinum(); + if (midifile_error) return; + lookfor = Mf_toberead - lng; + + if ( ! sysexcontinue ) + msginit(); + + while ( Mf_toberead > lookfor ) { + c = egetc(); + if (midifile_error) return; + msgadd(c); + } + if ( ! sysexcontinue ) { + if ( Mf_arbitrary ) + (*Mf_arbitrary)(msgleng(),msg()); + } + else if ( c == 0xf7 ) { + sysex(); + sysexcontinue = 0; + } + break; + default: + + badbyte(c); + + break; + } + } + if ( Mf_endtrack ) + (*Mf_endtrack)(); + return; +} + +static void +badbyte(c) +int c; +{ + char buff[32]; + + (void) sprintf(buff,"unexpected byte: 0x%02x",c); + mferror(buff); +} + +static void +metaevent(type) +{ + int leng = msgleng(); + char *m = msg(); + + switch ( type ) { + case 0x00: + if ( Mf_seqnum ) + (*Mf_seqnum)(to16bit(m[0],m[1])); + break; + case 0x01: /* Text event */ + case 0x02: /* Copyright notice */ + case 0x03: /* Sequence/Track name */ + case 0x04: /* Instrument name */ + case 0x05: /* Lyric */ + case 0x06: /* Marker */ + case 0x07: /* Cue point */ + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + /* These are all text events */ + if ( Mf_text ) + (*Mf_text)(type,leng,m); + break; + case 0x2f: /* End of Track */ + if ( Mf_eot ) + (*Mf_eot)(); + break; + case 0x51: /* Set tempo */ + if ( Mf_tempo ) + (*Mf_tempo)(to32bit(0,m[0],m[1],m[2])); + break; + case 0x54: + if ( Mf_smpte ) + (*Mf_smpte)(m[0],m[1],m[2],m[3],m[4]); + break; + case 0x58: + if ( Mf_timesig ) + (*Mf_timesig)(m[0],m[1],m[2],m[3]); + break; + case 0x59: + if ( Mf_keysig ) + (*Mf_keysig)(m[0],m[1]); + break; + case 0x7f: + if ( Mf_sqspecific ) + (*Mf_sqspecific)(leng,m); + break; + default: + if ( Mf_metamisc ) + (*Mf_metamisc)(type,leng,m); + } +} + +static void +sysex() +{ + if ( Mf_sysex ) + (*Mf_sysex)(msgleng(),msg()); +} + +static void +chanmessage(status,c1,c2) +int status; +int c1, c2; +{ + int chan = status & 0xf; + + switch ( status & 0xf0 ) { + case NOTEOFF: + if ( Mf_off ) + (*Mf_off)(chan,c1,c2); + break; + case NOTEON: + if ( Mf_on ) + (*Mf_on)(chan,c1,c2); + break; + case PRESSURE: + if ( Mf_pressure ) + (*Mf_pressure)(chan,c1,c2); + break; + case CONTROLLER: + if ( Mf_controller ) + (*Mf_controller)(chan,c1,c2); + break; + case PITCHBEND: + if ( Mf_pitchbend ) + (*Mf_pitchbend)(chan,c1,c2); + break; + case PROGRAM: + if ( Mf_program ) + (*Mf_program)(chan,c1); + break; + case CHANPRESSURE: + if ( Mf_chanpressure ) + (*Mf_chanpressure)(chan,c1); + break; + } +} + +/* readvarinum - read a varying-length number, and return the */ +/* number of characters it took. */ + +static long +readvarinum() +{ + long value; + int c; + + c = egetc(); + if (midifile_error) return 0; + + value = (long) c; + if ( c & 0x80 ) { + value &= 0x7f; + do { + c = egetc(); + if (midifile_error) return 0; + value = (value << 7) + (c & 0x7f); + } while (c & 0x80); + } + return (value); +} + +static long +to32bit(c1,c2,c3,c4) +{ + long value = 0L; + + value = (c1 & 0xff); + value = (value<<8) + (c2 & 0xff); + value = (value<<8) + (c3 & 0xff); + value = (value<<8) + (c4 & 0xff); + return (value); +} + +static int +to16bit(c1,c2) +int c1, c2; +{ + return ((c1 & 0xff ) << 8) + (c2 & 0xff); +} + +static long +read32bit() +{ + int c1, c2, c3, c4; + + c1 = egetc(); if (midifile_error) return 0; + c2 = egetc(); if (midifile_error) return 0; + c3 = egetc(); if (midifile_error) return 0; + c4 = egetc(); if (midifile_error) return 0; + return to32bit(c1,c2,c3,c4); +} + +static int +read16bit() +{ + int c1, c2; + c1 = egetc(); if (midifile_error) return 0; + c2 = egetc(); if (midifile_error) return 0; + return to16bit(c1,c2); +} + +static void +mferror(s) +char *s; +{ + if ( Mf_error ) + (*Mf_error)(s); + midifile_error = 1; +} + +/* The code below allows collection of a system exclusive message of */ +/* arbitrary length. The Msgbuff is expanded as necessary. The only */ +/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */ + +#define MSGINCREMENT 128 +static char *Msgbuff = 0; /* message buffer */ +static int Msgsize = 0; /* Size of currently allocated Msg */ +static int Msgindex = 0; /* index of next available location in Msg */ + +static void +msginit() +{ + Msgindex = 0; +} + +static char * +msg() +{ + return(Msgbuff); +} + +static int +msgleng() +{ + return(Msgindex); +} + +static void +msgadd(c) +int c; +{ + /* If necessary, allocate larger message buffer. */ + if ( Msgindex >= Msgsize ) + msgenlarge(); + Msgbuff[Msgindex++] = c; +} + +static void +msgenlarge() +{ + char *newmess; + char *oldmess = Msgbuff; + int oldleng = Msgsize; + + Msgsize += MSGINCREMENT; + newmess = MALLOC((sizeof(char)*Msgsize) ); + + /* copy old message into larger new one */ + if ( oldmess != 0 ) { + register char *p = newmess; + register char *q = oldmess; + register char *endq = &oldmess[oldleng]; + + for ( ; q!=endq ; p++,q++ ) + *p = *q; + free(oldmess); + } + Msgbuff = newmess; +} diff --git a/cmt/midifile.h b/cmt/midifile.h new file mode 100644 index 0000000..a759d76 --- /dev/null +++ b/cmt/midifile.h @@ -0,0 +1,27 @@ +extern void midifile(void); +extern int (*Mf_getc)(void); +extern void (*Mf_header)(int,int,int); +extern void (*Mf_starttrack)(void); +extern void (*Mf_endtrack)(void); +extern void (*Mf_on)(int,int,int); +extern void (*Mf_off)(int,int,int); +extern void (*Mf_pressure)(int,int,int); +extern void (*Mf_controller)(int,int,int); +extern void (*Mf_pitchbend)(int,int,int); +extern void (*Mf_program)(int,int); +extern void (*Mf_chanpressure)(int,int); +extern void (*Mf_sysex)(int,char*); +extern void (*Mf_metamisc)(int,int,char*); +extern void (*Mf_sqspecific)(int,char*); +extern void (*Mf_seqnum)(int); +extern void (*Mf_text)(int,int,char*); +extern void (*Mf_eot)(void); +extern void (*Mf_timesig)(int,int,int,int); +extern void (*Mf_smpte)(int,int,int,int,int); +extern void (*Mf_tempo)(int); +extern void (*Mf_keysig)(int,int); +extern void (*Mf_arbitrary)(int,char*); +extern void (*Mf_error)(char *); +extern long Mf_currtime; +extern int Mf_nomerge; +extern int Mf_skipinit; diff --git a/cmt/midifns.c b/cmt/midifns.c new file mode 100644 index 0000000..e8f7482 --- /dev/null +++ b/cmt/midifns.c @@ -0,0 +1,1886 @@ +/***************************************************************************** +* midifns.c +* Copyright 1989 Carnegie Mellon University +* Date | Change +*-----------+----------------------------------------------------------------- +* 29-Mar-88 | Created from IBM PC version of mpu.c +* | Added settime() +* 02-May-88 | AMIGA 2000 version. portable version. +* 12-Oct-88 | JCD : Exclusive AMIGA Version. +* 13-Apr-89 | JCD : New portable version. +* 19-Apr-89 | JCD : Amiga CAMD Version added. +* 5-Apr-91 | JDW : Further modification +* 17-Feb-92 | GWL : incorporate JMN's new mpu.c +* 8-Jun-92 | JDZ : add support for ITC midi interface +* 16-Dec-92 | RBD : replace JMN's mpu.c with LMS's mpu.c +* 11-Mar-94 | PLu : port to IRIX +* 25-Apr-97 | RBD : it looks like SGI changed their interface. I +* | made it compile again, but MIDI does not work, so +* | took out calls to actually send/recv MIDI data +* 28-Apr-03 | DM : Renamed random -> cmtrand, true->TRUE, false->FALSE +* | Use features rather than system names in #ifdef's +*****************************************************************************/ + +#include "switches.h" + +#ifdef UNIX +#include +#include +#ifndef OPEN_MAX +/* this is here for compiling the UNIX version under AIX. This is a BSDism */ +#define OPEN_MAX 2000 +#endif /* OPEN_MAX */ +#endif /* UNIX */ + +#ifdef UNIX_MACH +#include "machmidi.h" +#endif + + +#ifdef AMIGA +#ifdef AZTEC +#include "functions.h" +#endif /* AZTEC */ +#include "midi/camd.h" +#include "clib/camd_protos.h" +/* note: azt_camd_pragmas.h was produced by running MAPFD on the + * lib/camd_lib.fd file included in the CAMD disk from Commodore. + * The "CamdClient" calls are manually removed from + */ +#ifdef AZTEC +#include "pragmas/azt_camd_pragmas.h" +#else /* !AZTEC */ +#include "pragmas/lat_camd_pragmas.h" +#endif /* AZTEC */ +#include "camdmidi.h" +#include "ctype.h" +#endif /* AMIGA */ + +#ifdef UNIX_IRIX +/* #define UNIX_IRIX_MIDIFNS -- this would enable actual midi I/O + * if the actual midi I/O code worked + */ +/* IRIX changed the MIDI interface, + * retain this for older systems: + */ +#ifdef UNIX_IRIX_MIDIFNS +#include +#endif +#endif + +#include "stdio.h" +#include "cext.h" +#include "midicode.h" +#include "cmdline.h" +#include "pitch.h" +#include "midifns.h" +#include "userio.h" +#include "string.h" +#ifdef MACINTOSH_OR_DOS +#ifndef WINDOWS +#include "midibuff.h" +#endif +#endif + +#ifdef UNIX_ITC /* was ITC */ +#include "sys/param.h" +/* since boolean is defined, block its definition in midistruct.h. + * CMT defines boolean as ushort, but midistruct.h uses int. + * This is not a problem on RS/6000s, but beware! + */ +/* the following would be included if we had the BSD switch set. I think + we should try to avoid BSDisms; when fixed, the following should be + removed + */ +#define NBBY 8 +#include "sys/select.h" /* defines fd_set */ +#define MIDI_HAS_BOOLEAN +#include "midistruct.h" +#include "cmtio.h" +#endif /* UNIX_ITC */ + +#ifdef DOS +#ifndef WINDOWS +#include "timer.h" +#include "mpu.h" +#endif /* ifndef WINDOWS */ +#endif /* ifdef DOS */ + +#ifndef BREAKTEST +#define BREAKTEST +#endif + +#ifdef __APPLE__ +#include +#include +#include +#else +#ifdef UNIX +#ifndef UNIX_IRIX +#include "sys/time.h" +#include "sys/timeb.h" +#include "cmtio.h" +#else +#include +#include +#include + +#ifdef UNIX_IRIX_MIDIFNS +#include +#include +#endif /* UNIX_IRIX_MIDIFNS */ +#endif /* UNIX_IRIX */ +#endif /* UNIX */ +#endif /* __APPLE__ */ + +#ifdef ITC +static int ignore_realtime = 0; +#endif /* ITC */ + +#ifdef MACINTOSH + +/* added for ThinkC 7: */ +#include + +/* port numbers are in the range 0..MAX_PORTS-1 */ +#define CHANNELS_PER_PORT 16 +#define MAX_PORTS ((MAX_CHANNELS + CHANNELS_PER_PORT - 1) / CHANNELS_PER_PORT) + +/* here are some MIDIMGR specific definitions */ +#ifdef MIDIMGR +#include "MIDI.h" +#include "midimgr.h" + +#define TICKS_TO_MS(t) t +#define MS_TO_TICKS(t) t + +#else +/* here are some non-MIDIMGR definitions for the Mac */ +/**************************************************************************** +* +* DMH: constants from macmidi.c +* +****************************************************************************/ + +/* the modem port, also called port A */ +#define portA 0 + +/* the printer port, also called port B */ +#define portB 1 + +/* a tick is 1/60 of a second + * + * the following tables and routines are used to convert + * between ticks and milliseconds + */ +#define TICKS_TO_MS(t) (((t) * 50) / 3) +#define MS_TO_TICKS(t) (((t) * 3) / 50) +#endif /* def MIDIMGR */ +#endif /* def MACINTOSH */ + +#ifdef WINDOWS +#define huge +#endif + +/**************************************************************************** +* +* exported flags +* +****************************************************************************/ + +boolean miditrace = FALSE; /* enables printed trace of MIDI output */ +boolean musictrace = FALSE; /* enables printed trace of commands */ +#ifdef MACINTOSH_OR_DOS +boolean ctrlFilter = TRUE; /* suppress continuous controller data */ +boolean exclFilter = TRUE; /* suppress exclusive messages */ +boolean realFilter = TRUE; /* suppress realtime messages */ +#endif + +/**************************************************************************** +* +* exported variables +* +****************************************************************************/ + +public int keyloud; /* set to velocity of last getkey event */ +/* public long error; */ +public short midi_error_flags = 0; + +/* The following midifns_syntax lists command line switches and options. + Since these are machine dependent, use conditional compilation. + Conditional compilation within a string is a bit tricky: you want to + write "\" for line continuation within the string, but "\" gets eaten + by the macro preprocessor. + That's why we define macros like AMIGAINPORT. + Regretably it doesn't work for all compilers. + */ + + +/* Lattice and RT/Unix aren't happy expanding the embedded macros below, so + I made a separate declaration of midifns_syntax for Unix + */ +#ifdef UNIX +public char *midifns_syntax = "blockTurn off midi THRU;\ + miditraceTrace low-level midi functions;\ + noalloffDo not send alloff message when done;\ + traceTrace music operations;\ + tuneLoad a tuning file"; +#else +#ifdef MACINTOSH +#ifdef MIDIMGR +public char *midifns_syntax = "miditraceTrace low-level midi functions;\ + noalloffDo not send alloff message when done;\ + patchRemember/reuse Midi Mgr patches;\ + traceTrace music operations;\ + keepKeep other processes running;\ + tuneLoad a tuning file"; +#else /* no MIDIMGR */ +public char *midifns_syntax = "miditraceTrace low-level midi functions;\ + noalloffDo not send alloff message when done;\ + patchRemember/reuse Midi Mgr patches;\ + traceTrace music operations;\ + tuneLoad a tuning file"; +#endif /* MIDIMGR */ +#else +#ifdef AMIGA +public char *midifns_syntax = "blockTurn off midi THRU;\ + inportInpur port number;\ + miditraceTrace low-level midi functions;\ + noalloffDo not send alloff message when done;\ + outportOutput port number;\ + traceTrace music operations;\ + tuneLoad a tuning file"; +#else /* not UNIX or MACINTOSH or MIDIMGR or AMIGA */ +#ifdef DOS +public char *midifns_syntax = "miditraceTrace low-level midi functions;\ + noalloffDo not send alloff message when done;\ + traceTrace music operations;\ + tuneLoad a tuning file"; +#endif /* DOS */ +#endif /* AMIGA */ +#endif /* MACINTOSH */ +#endif /* UNIX */ + +#ifdef MACINTOSH +boolean do_midi_thru = FALSE; /* exported: copy midi in to midi out */ +#endif + + +/**************************************************************************** +* +* local module variables +* +****************************************************************************/ + +private int initialized = FALSE; /* set by musicinit, cleared by musicterm */ +private boolean tune_flag = FALSE; /* set by musicinit, never cleared */ +#ifdef DOS +private boolean metroflag = FALSE; /* flag to turn on metronome */ +#endif +private int user_scale = FALSE; /* TRUE if user-defined scale */ +private int bend[MAX_CHANNELS]; /* current pitch bend on channel */ +short cur_midi_prgm[MAX_CHANNELS]; +private pitch_table pit_tab[128]; /* scale definition */ + +#ifdef DOS +private ulong timeoffset = 0; +public boolean exclerr = FALSE; +public byte xcodemask; /* mask (00 or FF) */ +public byte xcode; /* mfr code */ +#endif + +#ifdef MACINTOSH_OR_DOS +boolean sysex_pending = FALSE; +#endif + +#ifdef AMIGA + +#define CONTCONT ((CMF_Ctrl & ~CMF_CtrlSwitch) | CMF_PitchBend | \ + CMF_ChanPress) +#endif /* def AMIGA */ + +#ifdef UNIX +private ulong timeoffset = 0; +#endif + +#ifdef UNIX_IRIX_MIDIFNS +static MIport *miport; +static int ignore_realtime = 0; + +private byte *sysex_p; +private int sysex_n; +#endif + +#ifdef ITC +mi_id midiconn; +#endif + +#ifdef MACINTOSH +private ulong ticksAtStart = 0L; + /* clock ticks at time of last musicinit or timereset + * ASSUME: tick clock never wraps. this is a good assumption, since + * the tick clock is set to zero when the power is turned on and the + * tick counter is 32 bits. the Macintosh would need to be on for + * 828.5 days for the tick counter to wrap around! */ + +#endif /* def MACINTOSH */ + +/**************************************************************************** +* +* functions declared in this module +* +****************************************************************************/ + +private void fixup(); +private void midi_init(); +extern boolean check_ascii(); /*userio.c*/ +private void musicterm(); + + +/**************************************************************************** +* alloff +* Inputs: +* none +* Effect: +* Sends MIDI all notes off command on every channel. +****************************************************************************/ + +#define ALL_NOTES_OFF 0x7B /*DMH: from macmidi.c*/ + +void alloff() +{ + int c; + + if (!initialized) fixup(); + if (musictrace) + gprintf(TRANS,"alloff()\n"); + for (c = 1; c <= MAX_CHANNELS; c++) { + midi_write(3, MIDI_PORT(c), (byte) (0xb0 | MIDI_CHANNEL(c)), ALL_NOTES_OFF, 0); + } +} + + + +/*************************************************************** +* eventwait +* +* Input : wakeup time, -1 means forever +* Output : none +* Return: none +* Effect: waits until ascii or midi input or timeout +***************************************************************/ + +#ifdef UNIX_ITC +void eventwait(timeout) + long timeout; +{ + struct timeval unix_timeout; + struct timeval *waitspec = NULL; + fd_set readfds; + struct rlimit file_limit; + + FD_ZERO(&readfds); + FD_SET(MI_CONNECTION(midiconn), &readfds); + FD_SET(fileno(stdin), &readfds); + if (timeout >= 0) { + timeout -= gettime(); /* convert to millisecond delay */ + unix_timeout.tv_sec = timeout / 1000; + /* remainder become microsecs: */ + unix_timeout.tv_usec = (timeout - (unix_timeout.tv_sec * 1000)) * 1000; + waitspec = &unix_timeout; + } + getrlimit(RLIMIT_NOFILE, &file_limit); + select(file_limit.rlim_max+1, &readfds, 0, 0, waitspec); + return; +} +#else /* !UNIX_ITC */ +#ifdef UNIX +/* see machmidi.c for UNIX_MACH implementation */ +#ifndef UNIX_MACH +#ifdef UNIX_IRIX_MIDIFNS +void eventwait(timeout) + long timeout; +{ + struct timeval unix_timeout; + struct timeval *waitspec = NULL; + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(mdGetFd(miport), &readfds); + FD_SET(fileno(stdin), &readfds); + if (timeout >= 0) { + + timeout -= gettime(); /* convert to millisecond delay */ + unix_timeout.tv_sec = timeout / 1000; + /* remainder become microsecs: */ + unix_timeout.tv_usec = (timeout - (unix_timeout.tv_sec * 1000)) * 1000; + waitspec = &unix_timeout; + } + select(FD_SETSIZE, &readfds, 0, 0, waitspec); + + return; +} +#else +#ifdef BUFFERED_SYNCHRONOUS_INPUT +void eventwait(timeout) + long timeout; +{ + struct timeval unix_timeout; + struct timeval *waitspec = NULL; + struct rlimit file_limit; + + if (timeout >= 0) { + timeout -= gettime(); /* convert to millisecond delay */ + unix_timeout.tv_sec = timeout / 1000; + /* remainder become microsecs: */ + unix_timeout.tv_usec = (timeout - (unix_timeout.tv_sec * 1000)) * 1000; + waitspec = &unix_timeout; + getrlimit(RLIMIT_NOFILE, &file_limit); + select(file_limit.rlim_max+1, 0, 0, 0, waitspec); + } else { + int c = getc(stdin); + ungetc(c, stdin); + } + return; +} +#else +void eventwait(timeout) + long timeout; +{ + struct timeval unix_timeout; + struct timeval *waitspec = NULL; + int readfds = 1 << IOinputfd; + struct rlimit file_limit; + + if (timeout >= 0) { + timeout -= gettime(); /* convert to millisecond delay */ + unix_timeout.tv_sec = timeout / 1000; + /* remainder become microsecs: */ + unix_timeout.tv_usec = (timeout - (unix_timeout.tv_sec * 1000)) * 1000; + waitspec = &unix_timeout; + } + getrlimit(RLIMIT_NOFILE, &file_limit); + select(file_limit.rlim_max+1, &readfds, 0, 0, waitspec); + return; +} +#endif /* BUFFERED_SYNCHRONOUS_INPUT */ +#endif /* UNIX_IRIX */ +#endif /* UNIX_MACH */ +#endif /* UNIX */ /* I wanted to put an else here, but this confused a Unix C compiler */ +#endif /* UNIX_ITC */ +#ifdef AMIGA +/* see camdmidi.c for Amiga implementation */ +#else +#ifndef UNIX /* since I couldn't use an else above, have to check UNIX here */ +#ifdef WINDOWS +void eventwait(timeout) + long timeout; +{ + if (timeout >= 0) { + gprintf(TRANS, "eventwait: not implemented\n"); + return; + } else { + int c = getc(stdin); + ungetc(c, stdin); + } + return; +} +#else +void eventwait(timeout) + long timeout; +{ + while (timeout > gettime() || timeout == -1) { + if (check_ascii() || check_midi()) return; + } +} +#endif /* WINDOWS */ +#endif /* UNIX */ +#endif /* AMIGA */ + + +/**************************************************************************** +* exclusive +* Inputs: +* boolean onflag -- set to TRUE to receive midi exclusive data +* Effect: +* Tells module to read exclusive messages into buffer +****************************************************************************/ + +void exclusive(boolean onflag) +{ + if (!initialized) fixup(); + if (musictrace) gprintf(TRANS, "exclusive: %d\n", onflag); +#ifdef AMIGA + if (onflag) SetMidiFilters(cmt_mi, + cmt_mi->PortFilter, cmt_mi->TypeFilter | CMF_SysEx, cmt_mi->ChanFilter); + else SetMidiFilters(cmt_mi, + cmt_mi->PortFilter, cmt_mi->TypeFilter & ~CMF_SysEx, cmt_mi->ChanFilter); +#endif +#ifdef MACINTOSH_OR_DOS + exclFilter = !onflag; +#endif +} + + +/**************************************************************************** +* fixup +* Effect: +* Print error message and call musicinit +****************************************************************************/ + +private void fixup() +{ + gprintf(ERROR, "You forgot to call musicinit. I'll do it for you.\n"); + musicinit(); +} + +#ifdef UNIX_IRIX_MIDIFNS +private void flush_sysex(void); +#endif + +long get_excl(byte *buffer, long len) +{ + long ret = 0; +#ifdef UNIX_IRIX_MIDIFNS + byte *sxp = sysex_p; + long l = len; +#endif +#ifdef UNIX_ITC /* was ITC */ + ret = mi_getx(midiconn, FALSE, len, (char *) buffer); +#endif +#ifdef UNIX_MACH + ret = mi_getx(midiconn, FALSE, len, (unsigned char *)buffer); +#endif +#ifdef UNIX_IRIX_MIDIFNS + if (!sysex_p) return 0; + if (len > sysex_n) len = sysex_n; + while (l--) + { + *buffer = *(sxp++); + if (*(buffer++) == CMT_MIDI_EOX) + { + flush_sysex(); + break; + } + } + ret = len - l - 1; +#endif +#ifdef AMIGA + ret = GetSysEx(cmt_mi, (UBYTE *) buffer, len); + AMIGA_ERROR_CHECK; +#endif +#ifdef MACINTOSH_OR_DOS +#ifndef WINDOWS + /* I'm not sure the following line is a good thing: it forces the + * caller to wait until a full sysex message is received and the + * 1st 4 bytes are fetched via getbuf() before a sysex message can + * be read via get_excl(). Without this, both mm.c and exget.c + * were fetching ahead and getting out of sync with getbuf(). I + * fixed mm.c and exget.c to work (by checking for EOX), but I added + * this line (which should never have any effect) just to make the + * DOS interface behave more like the Amiga and Mac interfaces. The + * drawback is that you can't fetch bytes until the EOX is seen, + * because nothing goes into the getbuf() buffer until then. + */ + if (!sysex_pending) return 0; + while (len-- && (xbufhead != xbuftail)) { + *buffer = xbuff[xbufhead++]; + ret++; + if (*buffer == MIDI_EOX) { + sysex_pending = FALSE; + break; + } + buffer++; + xbufhead &= xbufmask; + } +#endif +#endif + return ret; +} + +/**************************************************************************** +* getbuf +* Inputs: +* boolean waitflag: TRUE if routine should wait for data +* byte * p: Pointer to data destination +* Result: boolean +* TRUE if data was written to *p +* FALSE if data not written to *p +* Effect: +* copies data from buffer to *p +* will wait for buffer to become nonempty if waitflag is TRUE +* +* Modified 24 May 1988 for AMIGA (JCD) +****************************************************************************/ + +#ifdef UNIX_IRIX_MIDIFNS + private void setup_sysex(MDevent *event, u_char *buffer); +#endif /* UNIX_IRIX */ +boolean getbuf(boolean waitflag, unsigned char * p) +{ +#ifdef UNIX_IRIX_MIDIFNS + MDevent event; + int ret; +#endif /* UNIX_IRIX */ + + if (!initialized) fixup(); +#ifdef UNIX +#ifdef UNIX_IRIX_MIDIFNS +/* current IRIX version ignores the waitflag (it never waits) */ + + if (sysex_p) flush_sysex(); + if (ignore_realtime == 0) { + ret = mdReceive(miport, &event, 1); + if (ret) { + if (event.msg[0] != 0xF0) { + *((u_long*) p) = *((u_long*) event.msg); + } else { + setup_sysex(&event, p); + } + } + return ret; + } else { + do /* skip realtime messages */ + { + ret = mdReceive(miport, &event, 1); + if (ret == -1) return ret; + } while (event.msg[0] == 0xf8); + if (event.msg[0] != 0xF0) { + *((u_long*) p) = *((u_long*) event.msg); + } else { + setup_sysex(&event, p); + } + return ret; + } +#endif /* UNIX_IRIX */ +#ifdef UNIX_ITC + if (ignore_realtime == 0) { + return(mi_get(midiconn, waitflag, (char *) p)); + } + else { + boolean ret=false; + /* filter out realtime msgs */ + do { + ret = mi_get(midiconn, waitflag, (char *) p); + if (ret == FALSE) + return(ret); + } while(p[0] == 0xf8); + return(ret); + } +#else /* UNIX_ITC */ +#ifndef UNIX_IRIX + if (waitflag) { + gprintf(ERROR, "getbuf called with waitflag!"); + EXIT(1); + } + return FALSE; +#endif /* UNIX_IRIX */ +#endif /* UNIX_ITC */ +#endif /* UNIX */ + +#ifdef MACINTOSH_OR_DOS +#ifndef WINDOWS + if (sysex_pending) { /* flush sysex to keep buffers in sync */ + while (xbuff[xbufhead++] != MIDI_EOX) { + xbufhead &= xbufmask; + if (xbufhead == xbuftail) break; + } + sysex_pending = FALSE; + } + if (waitflag) while (buffhead == bufftail) /* wait */ ; + else if (buffhead == bufftail) return(false); + *(long *)p = *(long *)(((char *)buff)+buffhead); + buffhead = (buffhead + 4) & BUFF_MASK; + if (*p == MIDI_SYSEX) { /* if sys-ex, remember to fetch from xbuff */ + sysex_pending = TRUE; + } + return(true); +#else + return FALSE; +#endif /* WINDOWS */ +#endif /* MACINTOSH_OR_DOS */ + +#ifdef AMIGA + if (waitflag) { + do { + WaitMidi(cmt_mi, &cmt_msg); + AMIGA_ERROR_CHECK; + } while (amigaerrflags); + } else { + AMIGA_ERROR_CHECK; + if (!GetMidi(cmt_mi, &cmt_msg)) return(false); + } + *(long *)p = *(long *)&cmt_msg; + clearmsg(cmt_msg); + return(true); +#endif /* AMIGA */ +} + +#ifdef UNIX_IRIX_MIDIFNS + +private void setup_sysex(MDevent *event, u_char *buffer) +/* N.B. do not leak memory remember to call free(sysex_p) */ +{ + u_char *sxp = (u_char *) event->sysexmsg; + int i; + + for (i=0;i<4;i++) + *(buffer++) = *(sxp++); + sysex_p = event->sysexmsg; + sysex_n = event->msglen; +} + +private void flush_sysex() +{ + mdFree(sysex_p); + sysex_p = 0; + sysex_n = 0; +} +#endif + +#ifdef MACINTOSH_OR_DOS +#ifndef WINDOWS +public boolean check_midi() +{ + if (buffhead == bufftail) return FALSE; + else return TRUE; +} +#endif +#endif + + +/**************************************************************************** +* getkey +* Inputs: +* boolean waitflag: TRUE if wait until key depression, FALSE if +* return immediately +* Result: int +* key number of key which has been depressed +* It returns -1 if waitflag is FALSE and no key has been pressed +* If waitflag is TRUE this routine will block until a key is pressed +* Effect: +* reads a key +****************************************************************************/ + +/*DMH: in previous version, macmidi.c subtracted 12 from msg to get key at each occurence...*/ + +short getkey(boolean waitflag) +{ + byte msg[4]; + short k; + + if (!initialized) fixup(); + + while (TRUE) { /* process data until you find a note */ + /* look for data and exit if none found */ + /* NOTE: waitflag will force waiting until data arrives */ + if (!getbuf(waitflag, msg)) { /* nothing there */ + k = -1; + break; + } else if ((msg[0] & MIDI_CODE_MASK) == MIDI_ON_NOTE) { + if (msg[2] == 0) { /* velocity 0 -> note off */ + keyloud = 0; + k = msg[1] + 128; + } else { + keyloud = msg[2]; + k = msg[1]; + } + break; + } else if ((msg[0] & MIDI_CODE_MASK) == MIDI_OFF_NOTE) { + keyloud = 0; + k = msg[1] + 128; + break; + } + } + if (musictrace) { + if (k != -1) gprintf(TRANS,"getkey got %d\n", k); + } + return k; +} + + +/**************************************************************************** +* gettime +* Result: ulong +* current timestamp since the last call to +* musicinit or timereset +* Effect: +* fakes it +****************************************************************************/ + +ulong gettime() /*DMH: ulong is from mpu->midifns conversion, for Mac*/ +{ +#if HAS_GETTIMEOFDAY + struct timeval timeval; +#endif +#if HAS_FTIME + struct timeb ftime_res; +#endif + register ulong ticks = 0L; + + BREAKTEST /* abort if user typed Ctrl Break */ + if (!initialized) fixup(); + +#ifdef MACINTOSH +#ifdef MIDIMGR + ticks = MIDIGetCurTime(OutputRefNum) - ticksAtStart; +#else + ticks = TickCount() - ticksAtStart; +#endif + if (initialized) abort_check(); /* give user a chance to abort */ + ticks = TICKS_TO_MS(ticks); +#endif + +#ifdef AMIGA + ticks = (*camdtime - timeoffset) << 1; /* return milliseconds */ +#endif + +#ifdef DOS +#ifndef WINDOWS + ticks = elapsedtime(timeoffset, readtimer()); /* return milliseconds */ + /* gprintf(TRANS, "currtime = %ld, timeoffset = %ld\n", currtime, timeoffset); */ +#endif +#endif /* ifdef DOS */ + +#if HAS_GETTIMEOFDAY + gettimeofday(&timeval, 0); + ticks = timeval.tv_sec * 1000 + timeval.tv_usec / 1000 - timeoffset; +#endif +#if HAS_FTIME + ftime(&ftime_res); + ticks = ((ftime_res.time - timeoffset) * 1000) + ftime_res.millitm; +#endif + + /* if (miditrace) gprintf(TRANS, "."); */ + return(ticks); +} + + +/**************************************************************************** +* l_rest +* Inputs: +* long time: Amount of time to rest +* Effect: +* Waits until the amount of time specified has lapsed +****************************************************************************/ + +void l_rest(time) +long time; +{ + if (!initialized) fixup(); + l_restuntil(time + gettime()); +} + +/**************************************************************************** +* l_restuntil +* Inputs: +* long time: Event time to rest until +* Effect: +* Waits until the specified time has been reached (absolute time) +****************************************************************************/ + +void l_restuntil(time) +long time; +{ +#ifdef MACINTOSH + ulong now = gettime(); + ulong junk; /* changed from ulong for ThinkC 7, back to ulong for CW5 */ +#endif + +#ifdef AMIGA + while (time > gettime()) eventwait(time); +#else + for(; (time_type) time > gettime();); +#endif + +#ifdef MACINTOSH + now = gettime(); + + if (time > now) Delay(MS_TO_TICKS(time - now), &junk); + /* else time <= now, so return immediately */ +#endif + +} + +/**************************************************************************** +* metronome +* Inputs: +* boolean onflag: TRUE or FALSE +* Effect: +* enables (true) or disables (false) MPU-401 metronome function. +* must be called before musicinit +****************************************************************************/ + +void metronome(boolean onflag) +{ +#ifdef DOS +metroflag = onflag; +#endif +} + + +/**************************************************************************** +* midi_bend +* Inputs: +* int channel: midi channel on which to send data +* int value: pitch bend value +* Effect: +* Sends a midi pitch bend message +****************************************************************************/ + +void midi_bend(int channel, int value) +{ + if (!initialized) fixup(); + if (musictrace) + gprintf(TRANS,"midi_bend: ch %d, val %d\n", channel, value - (1 << 13)); + bend[MIDI_CHANNEL(channel)] = value; + + midi_write(3, MIDI_PORT(channel), (byte) (MIDI_BEND | MIDI_CHANNEL(channel)), + (byte) MIDI_DATA(value), (byte) MIDI_DATA(value >> 7)); +} + + +/**************************************************************************** +* midi_buffer +* Inputs: +* byte * buffer: the buffer address +* int size: number of bytes in buffer +* Returns: +* FALSE if size is less than 16 or buffer is NULL, otherwise TRUE +* Effect: DOS, MAC: +* tells interrupt routine to store system exclusive messages in +* buffer. The largest power of 2 bytes less than size will be +* used. xbufhead and xbuftail will be initialized to zero, +* and xbuftail will be one greater than the index of the last +* system exclusive byte read. Since there may already be a buffer +* and therefore the normal midi message buffer may have the first +* 4 bytes of some sysex messages, clear the normal midi buffer too. +* AMIGA: +* adds buffer to midi interface +* +****************************************************************************/ + +boolean midi_buffer(byte huge *buffer, ulong size) +{ + if (!buffer) return FALSE; +#ifdef AMIGA + if (!SetSysExQueue(cmt_mi, (UBYTE *) buffer, (ULONG) size)) return(false); + cu_register(remove_sysex_buffer, buffer); +#endif + +#ifdef MACINTOSH_OR_DOS +#ifndef WINDOWS + { + int mask = 0x000F; + + if (size < 16) return(false); + while (mask < size && mask > 0) mask = ((mask << 1) | 1); + midi_flush(); + xbuff = NULL; /* turn off buffering */ + xbufmask = mask >> 1; + xbufhead = xbuftail = 0; + xbuff = buffer; /* set buffer, turn on buffering */ + } +#endif +#endif +#ifdef UNIX + return FALSE; +#else + exclusive(TRUE); + return TRUE; +#endif +} + + +/* midi_clock -- send a midi time clock message */ +/**/ +void midi_clock() +{ + if (!initialized) fixup(); + if (musictrace) gprintf(TRANS, "+"); + midi_write(1, 0, MIDI_TIME_CLOCK, 0, 0); +} + + +/**************************************************************************** +* midi_cont +* Inputs: +* boolean onflag: TRUE or FALSE +* Effect: +* enables (true) or disables (false) continuous control +****************************************************************************/ + +void midi_cont(boolean onflag) +{ + if (!initialized) fixup(); + if (onflag) { +#ifdef AMIGA + SetMidiFilters(cmt_mi, cmt_mi->PortFilter, + cmt_mi->TypeFilter | CONTCONT, cmt_mi->ChanFilter); +#endif +#ifdef DOS +#ifndef WINDOWS + mPutCmd(BENDERON); +#endif +#endif + } else { +#ifdef AMIGA + SetMidiFilters(cmt_mi, cmt_mi->PortFilter, + cmt_mi->TypeFilter & ~CONTCONT, cmt_mi->ChanFilter); +#endif + } +#ifdef MACINTOSH_OR_DOS + ctrlFilter = !onflag; +#endif + if (musictrace) gprintf(TRANS,"midi_cont: %d\n", onflag); +} + + +/**************************************************************************** +* midi_ctrl +* Inputs: +* int channel: midi channel on which to send data +* int control: control number +* int value: control value +* Effect: +* Sends a midi control change message +****************************************************************************/ + +void midi_ctrl(int channel, int control, int value) +{ + if (!initialized) fixup(); + if (musictrace) + gprintf(TRANS,"midi_ctrl: ch %d, ctrl %d, val %d\n", + channel, control, value); + + midi_write(3, MIDI_PORT(channel), (byte) (MIDI_CTRL | MIDI_CHANNEL(channel)), + (byte) MIDI_DATA(control), (byte) MIDI_DATA(value)); +} + + +/**************************************************************************** +* midi_exclusive +* Inputs: +* byte *msg: pointer to a midi exclusive message, terminated by 0xF7 +* Effect: +* Sends a midi exclusive message +* Bugs: +* 18-mar-94 PLu : This function does not know which port to send to in +* case of multiple midi-ports (MAC, IRIX) +****************************************************************************/ + +#ifdef MACINTOSH +#define INTERBYTE_DELAY 10 +#endif + +void midi_exclusive(msg) +unsigned char *msg; /* the data to be sent */ +{ +#ifdef ITC + int count, done, tosend, willsend; + unsigned char *m; + mi_status ret; +#endif +#ifdef UNIX_IRIX_MIDIFNS + unsigned char *m; + MDevent mdevent; +#endif + +#ifdef MACINTOSH +#ifndef NYQUIST + int i; /* for DX7 delay loop */ + int count = 0; /* counter for formatting midi byte trace */ + MIDIPacket TheMIDIPacket; + unsigned char prev = 0; + boolean first_packet = TRUE; +#endif +#endif + + /* + * if user mistakenly called midi_exclusive instead of exclusive, + * the argument will be TRUE or FALSE, both of which are highly + * unlikely valid arguments for midi_exclusive: + */ + + if (msg == (byte *) FALSE || msg == (byte *) TRUE) { + gprintf(ERROR,"midi_exclusive: invalid argument %u.\n", msg); + EXIT(1); + } + if (!initialized) fixup(); + if (musictrace) gprintf(TRANS,"midi_exclusive\n"); + +#ifdef AMIGA + PutSysEx(cmt_mi, msg); +#endif +#ifdef MACINTOSH +#ifndef NYQUIST /* if NYQUIST, do nothing */ +#ifdef MIDIMGR + while (prev != MIDI_EOX) { + int len = 0; + while (prev != MIDI_EOX && len < 249) { + TheMIDIPacket.data[len++] = prev = *msg++; + } + TheMIDIPacket.len = 6 + len; + TheMIDIPacket.tStamp = 0; + if (first_packet && (prev != MIDI_EOX)) { + TheMIDIPacket.flags = midiTimeStampCurrent + midiStartCont; + first_packet = FALSE; + } else if (first_packet) { + TheMIDIPacket.flags = midiTimeStampCurrent + midiNoCont; + } else if (prev == MIDI_EOX) { + TheMIDIPacket.flags = midiTimeStampCurrent + midiEndCont; + } else { + TheMIDIPacket.flags = midiTimeStampCurrent + midiMidCont; + } + MIDIWritePacket(OutputRefNum, &TheMIDIPacket); + } +#else + while (*msg != MIDI_EOX) { + Xmit(0, *msg); + msg++; + count++; + /* this is a delay loop, without which your DX7 will crash */ + for (i = INTERBYTE_DELAY; i > 0; i--) + abort_check(); + } + Xmit(0, MIDI_EOX); +#endif /* MIDIMGR */ +#endif /* NYQUIST */ +#endif /* MACINTOSH */ + +#ifdef DOS +#ifndef WINDOWS + do { + mPutData(*msg); + } while (*msg++ != MIDI_EOX); +#endif +#endif +#ifdef ITC + for (m = msg, tosend = 1; (*m) != MIDI_EOX; m++, tosend++); + for (count = 0; count < tosend; count += done) { + willsend = min(16384, tosend); + ret = mi_exclusive(midiconn, 1, msg, (short) willsend); + if (ret != MI_SUCCESS) { + gprintf(GWARN, "Got %d from mi_exclusive\n", ret); + } + done = willsend; + } +#endif +#ifdef UNIX_IRIX_MIDIFNS +/* we don't know which device to sent SYSEX messages to so port zero is + assumed. */ + for (m = msg, mdevent.msglen = 1; (*m) != CMT_MIDI_EOX; m++, mdevent.msglen++); + mdevent.sysexmsg = msg; + if (mdSend(miport, &mdevent, 1) == -1) { + gprintf(GWARN, "could not send SYSEX message\n"); + } +#endif + + if (miditrace) { + do { gprintf(TRANS, "~%2x", *msg); +#ifdef UNIX_IRIX_MIDIFNS + } while (*msg++ != CMT_MIDI_EOX); +#else + } while (*msg++ != MIDI_EOX); +#endif + } +} + + +/**************************************************************************** +* midi_note +* Inputs: +* int channel: midi channel on which to send data +* int pitch: midi pitch code +* int velocity: velocity with which to sound it (0=> release) +* Effect: +* Sends a midi note-play request out +****************************************************************************/ + +void midi_note(int channel, int pitch, int velocity) +{ + if (!initialized) fixup(); + if (musictrace) gprintf(TRANS,"midi_note: ch %d, key %d, vel %d\n", + channel, pitch, velocity); + if (user_scale) { + /* check for correct pitch bend */ + if ((pit_tab[pitch].pbend != bend[MIDI_CHANNEL(channel)]) + && (velocity != 0)) { + midi_bend(channel, pit_tab[pitch].pbend); + bend[channel] = pit_tab[pitch].pbend; + } + pitch = pit_tab[pitch].ppitch; + } + midi_write(3, MIDI_PORT(channel), (byte) (MIDI_ON_NOTE | MIDI_CHANNEL(channel)), + (byte) MIDI_DATA(pitch), (byte) MIDI_DATA(velocity)); +} + + +/**************************************************************************** +* midi_program +* Inputs: +* int channel: Channel on which to send midi program change request +* int program: Program number to send (decremented by 1 before +* being sent as midi data) +* Effect: +* Sends a program change request out the channel +****************************************************************************/ + +void midi_program(int channel, int program) +{ +#ifdef MACINTOSH + int port, midi_chan; +#endif + + if (!initialized) fixup(); + if (musictrace) gprintf(TRANS,"midi_program: ch %d, prog %d\n", + channel, program); + channel = MIDI_CHANNEL(channel); + if (cur_midi_prgm[channel] != program) { + midi_write(2, MIDI_PORT(channel), (byte) (MIDI_CH_PROGRAM | channel), + (byte) (MIDI_PROGRAM(program)), 0); + cur_midi_prgm[channel] = program; + } +} + + +/**************************************************************************** +* midi_real +* Inputs: +* boolean onflag: TRUE or FALSE +* Effect: +* enables (true) or disables (false) midi realtime messages F8-FF +****************************************************************************/ + +void midi_real(boolean onflag) +{ + if (!initialized) fixup(); +#ifdef UNIX_ITC + { + mi_status ret; + + ret = mi_realtime(midiconn, onflag); + if (ret != MI_SUCCESS) { + gprintf(ERROR, "Warning: bad ret = %d in midi_real\n", ret); + } + } +#endif /* UNIX_ITC */ +#ifdef ITC + ignore_realtime = !onflag; +#endif /* ITC */ +#ifdef AMIGA + if (onflag) { + SetMidiFilters(cmt_mi, cmt_mi->PortFilter, + cmt_mi->TypeFilter | CMF_RealTime, cmt_mi->ChanFilter); + } else { + SetMidiFilters(cmt_mi, cmt_mi->PortFilter, + cmt_mi->TypeFilter & ~CMF_RealTime, cmt_mi->ChanFilter); + } +#endif +#ifdef MACINTOSH_OR_DOS + realFilter = !onflag; +#endif + + if (musictrace) gprintf(TRANS,"midi_real: %d\n", onflag); +} + + +/* midi_start -- send a midi start message */ +/**/ +void midi_start() +{ + if (!initialized) fixup(); + if (musictrace) gprintf(TRANS, "`"); + midi_write(1, 0, MIDI_START, 0, 0); +} + + +/* midi_stop -- send a midi stop message */ +/**/ +void midi_stop() +{ + if (!initialized) fixup(); + if (musictrace) gprintf(TRANS, "'"); + midi_write(1, 0 /* ignored */, MIDI_STOP, 0, 0); +} + + +/**************************************************************************** +* midi_thru +* Inputs: +* boolean onflag: TRUE or FALSE +* Effect: +* DOS: enables (true) or disables (false) midi thru info from +* MPU-401 to host. (Default is set; reset with cmdline -block.) +* AMIGA: enables (true) or disables (false) midi route from AMIGA +* midi input to AMIGA midi output. +****************************************************************************/ + +void midi_thru(boolean onflag) /* DMH: midi thru is not supported on the MAC or DOS */ +{ + if (!initialized) fixup(); +#ifndef MIDI_THRU + gprintf(ERROR, "midi_thru called but not implemented\n"); +#else +#ifdef AMIGA + MidiThru(0L, (long) onflag); +#endif +#ifdef MACINTOSH + /* this currently does not do anything - Mac driver doesn't + * support THRU + */ + do_midi_thru = onflag; +#endif +#endif + + if (musictrace) gprintf(TRANS,"midi_thru: %d\n", onflag); +} + + +/**************************************************************************** +* midi_touch +* Inputs: +* int channel: midi channel on which to send data +* int value: control value +* Effect: +* Sends a midi after touch message +****************************************************************************/ + +void midi_touch(int channel, int value) +{ + if (!initialized) fixup(); + if (musictrace) gprintf(TRANS,"midi_touch: ch %d, val %d\n",channel,value); + midi_write(2, MIDI_PORT(channel), (byte) (MIDI_TOUCH | MIDI_CHANNEL(channel)), + (byte) MIDI_DATA(value), 0); +} + + +/**************************************************************************** +* midi_write +* Inputs: +* UBYTE n: number of characters to send (1, 2 or 3); + int port: the port number (usually 0), on MAC, this may be 1 +* char c1,c2,c3: Character(s) to write to MIDI data port +* Effect: +* Writes the data to the serial interface designated by port +**************************************************************************** +* Change log +* Date | Change +*-----------+---------------------------------------------------------------- +* 15-Mar-94 | PLu : Added IRIX version +****************************************************************************/ + +#ifdef UNIX +#ifdef UNIX_IRIX_MIDIFNS +void midi_write(int n, int port, unsigned char c1, unsigned char c2, unsigned char c3) +{ + MDevent event; + + if (port < 0) return; + + * ((u_long *) event.msg) = 0xe0000000 | ((port & 0x1f) << 24) | (c1 << 16) | + (c2 << 8) | c3; + if (mdSend(miport, &event, 1) == -1) + gprintf(ERROR, "Can not send midi message in midi_write"); + + midi_write_trace(n, port, c1, c2, c3); +} +#else + +#ifdef ITC +void midi_write(int n, int port, + unsigned char c1, unsigned char c2, unsigned char c3) +{ + unsigned char outb[3]; + mi_channel mch; + mi_status ret; + + if (port < 0) return; + outb[0] = c1; + outb[1] = c2; + outb[2] = c3; + mch = (16*port)+((int)MI_CHANNEL(c1)); + ret = mi_put(midiconn, mch, outb); + if (ret != MI_SUCCESS) + gprintf(ERROR, "Warning: bad ret = %d in midi_write\n", (int)ret); + midi_write_trace(n, port, c1, c2, c3); +} +#else +void midi_write(int n, int port, + unsigned char c1, unsigned char c2, unsigned char c3) +{ + /* no output */ + midi_write_trace(n, port, c1, c2, c3); +} +#endif /* ITC */ +#endif /* UNIX_IRIX */ +#endif /* UNIX */ + +#ifdef DOS +#ifndef WINDOWS +void midi_write(int n, int port, + unsigned char c1, unsigned char c2, unsigned char c3) +{ + if (n >= 1) mPutData(c1); + if (n >= 2) mPutData(c2); + if (n >= 3) mPutData(c3); + midi_write_trace(n, port, c1, c2, c3); +} +#else +void midi_write(int n, int port, + unsigned char c1, unsigned char c2, unsigned char c3) +{ + midi_write_trace(n, port, c1, c2, c3); +} +#endif +#endif + +#ifdef MACINTOSH +#ifdef MIDIMGR +void midi_write(int n, int port, + unsigned char c1, unsigned char c2, unsigned char c3) +{ + MIDIPacket TheMIDIPacket; + + TheMIDIPacket.flags = midiTimeStampCurrent; + TheMIDIPacket.len = 6 + n; + TheMIDIPacket.tStamp = 0; + TheMIDIPacket.data[0] = c1; + TheMIDIPacket.data[1] = c2; + TheMIDIPacket.data[2] = c3; + MIDIWritePacket(OutputRefNum, &TheMIDIPacket); + midi_write_trace(n, port, c1, c2, c3); +} +#else +void midi_write(int n, int port, unsigned char c1, unsigned char c2, unsigned char c3) +{ +#ifndef NYQUIST + Xmit(port, c1); + if (n >= 2) Xmit(port, c2); + if (n >= 3) Xmit(port, c3); +#endif + midi_write_trace(n, port, c1, c2, c3); +} +#endif +#endif + +void midi_write_trace(int n, int port, + unsigned char c1, unsigned char c2, unsigned char c3) +{ + if (miditrace) { + /* to indicate bytes going out on port 1, put message in brackets + * with the port number, e.g. [1:~90~3c~64] + */ + if (port > 0) gprintf(TRANS, "[%d:", port); + if (n >= 1) gprintf(TRANS, "~%2x", c1); + if (n >= 2) gprintf(TRANS, "~%2x", c2); + if (n >= 3) gprintf(TRANS, "~%2x", c3); + if (port > 0) gprintf(TRANS, "]", port); + } +} + + + +/***************************************************************** +* set_pitch_default +*****************************************************************/ + +private void set_pitch_default() +{ + int i; + + for (i = 0; i < 128; i++) { + pit_tab[i].pbend = 8192; + pit_tab[i].ppitch = i; + } +} + +/***************************************************************** +* read_tuning +*****************************************************************/ + +void read_tuning(filename) +char *filename; +{ + int index, pit, lineno = 0; + float bend; + FILE *fpp; + + user_scale = TRUE; + set_pitch_default(); + + fpp = fileopen(filename, "tun", "r", "Tuning definition file"); + while ((fscanf(fpp, "%d %d %f\n", &index, &pit, &bend) > 2) && + (lineno < 128)) { + lineno++; + if (index >= 0 && index <= 127) { + pit_tab[index].pbend = (int)(8192 * bend/100 + 8192); + pit_tab[index].ppitch = pit; + } + } +} + + +/**************************************************************************** +* musicinit +* Effect: +****************************************************************************/ + +void musicinit() +{ + int i; + char *filename; + + if (!tune_flag) { /* do this code only once */ + miditrace = cl_switch("miditrace"); + musictrace = cl_switch("trace"); + } + + if (!initialized) { + cu_register((cu_fn_type) musicterm, NULL); + midi_init(); + } + initialized = TRUE; + /* this does some random cleanup activity */ + +#ifndef APPLICATION + if (!tune_flag) { /* do this code only once */ +#ifdef DOS +#ifndef WINDOWS +#if 0 + version = mPutGetCmd(GETMPUVER); + revision = mPutGetCmd(GETMPUREV); + gprintf(TRANS, "MPU version %d.%d%c\n", version >> 4, version & 0x0f, + revision + 'A' - 1); +#endif + mPutCmd(UARTMODE); + mPutCmd(NOREALTIME); /* initially prevent Real Time MIDI info */ + mPutCmd(EXCLUSIVOFF); /* initially prevent Sys-Ex data */ +#endif +#endif + tune_flag = TRUE; + filename = cl_option("tune"); + if (filename != NULL) read_tuning(filename); + } + /* now that flags are set, print the trace message */ + if (musictrace) gprintf(TRANS, "musicinit()\n"); + + if (user_scale) { + for (i = 0; i < MAX_CHANNELS; i++) { + midi_bend(i, 8192); + bend[i] = 8192; + } + } +#endif /* ifndef APPLICATION */ + + for (i = 0; i < MAX_CHANNELS; i++) { + /* initialize to impossible values so that the + * next call to midi_bend or midi_program will + * not match and therefore send an output: + */ + bend[i] = -1; + cur_midi_prgm[i] = -1; + } +#ifdef MIDI_THRU + midi_thru(!(cl_switch("block"))); /* set MIDI thru */ +#endif + timereset(); /* Reset clock */ +#ifdef AMIGA + event_mask |= (1L << ascii_signal()) | (1L << cmt_mi->AlarmSigBit) | + (1L << cmt_mi->RecvSigBit); +#endif +} + + +/**************************************************************************** +* musicterm +* Effect: +* Miscellaneous cleanup of things done by musicinit. +****************************************************************************/ + +private void musicterm() +{ + if (musictrace) gprintf(TRANS, "musicterm()\n"); + initialized = FALSE; +} + + +/**************************************************************************** +* cmtrand +* Inputs: +* int lo: Lower limit of value +* int hi: Upper limit of value +* Result: int +* random number (lo <= result <= hi) +****************************************************************************/ + +long randseed = 1534781L; + +short cmtrand(short lo, short hi) +{ + randseed *= 13L; + randseed += 1874351L; + return((short)(lo + (((hi + 1 - lo) * ((0x00ffff00 & randseed) >> 8)) >> 16))); +} + + +#ifdef AMIGA +/* remove_sysex_buffer -- a cleanup procedure for the Amiga */ +/**/ +void remove_sysex_buffer(void *obj) +{ + ClearSysExQueue(cmt_mi); +} +#endif /* AMIGA */ + + +/**************************************************************************** +* settime +* Inputs: new time +* Effect: +* Sets the current time to the new time. +* DMH: for MAC, sets the clock to absTime +* implemented by adjusting ticksATStart +****************************************************************************/ + +void settime(newtime) + time_type newtime; +{ + if (musictrace) gprintf(TRANS, "settime(%lu)\n", newtime); +#ifdef AMIGA + timeoffset = *camdtime - (newtime >> 1); +#endif + +#ifdef MACINTOSH +#ifdef MIDIMGR + ticksAtStart = MIDIGetCurTime(OutputRefNum); +#else + ticksAtStart = TickCount() - MS_TO_TICKS(newtime); +#endif +#endif +} + +/**************************************************************************** +* timereset +* Effect: +* Resets the time. +* DMH: for MAC, implemented by setting ticksAtStart to +* current value of system tick counter +* JMN: for DOS, resets the time on the MPU-401. Ticks is reset to 0 +****************************************************************************/ + +void timereset() +{ +#if HAS_GETTIMEOFDAY + struct timeval timeval; +#endif +#if HAS_FTIME + struct timeb ftime_res; +#endif + + if (!initialized) fixup(); + if (musictrace) gprintf(TRANS,"timereset()\n"); + +#ifdef AMIGA + timeoffset = *camdtime; +#endif + +#ifdef DOS +#ifndef WINDOWS + timeoffset = (ulong) readtimer(); +#endif +#endif + +#ifdef MACINTOSH +#ifdef MIDIMGR + ticksAtStart = MIDIGetCurTime(OutputRefNum); +#else + ticksAtStart = TickCount(); +#endif +#endif + +#if HAS_GETTIMEOFDAY + gettimeofday(&timeval, 0); + timeoffset = timeval.tv_sec * 1000 + timeval.tv_usec / 1000 - timeoffset; +#endif +#if HAS_FTIME + ftime(&ftime_res); + timeoffset = ftime_res.time; +#endif +} + + +/**************************************************************************** +* trace +* Inputs: +* boolean flag: TRUE for trace on +* Effect: +* turns tracing on (flag == TRUE) or off (flag == FALSE) +****************************************************************************/ + +void trace(boolean flag) +{ + musictrace = flag; +} + +/**************************************************************************** +* tracemidi +* Inputs: +* boolean flag: TRUE for trace on +* Effect: +* turns midi tracing on (flag == TRUE) or off (flag == FALSE) +****************************************************************************/ + +void tracemidi(boolean flag) +{ + miditrace = flag; +} + + + +/*********************************************************************** +* +* midi and timer initialization +* +***********************************************************************/ + +#ifdef DOS + +/* binary value of hex char */ + +private int xval(int c) +{ + int i; + static char t[]="0123456789abcdef"; + + for (i=0; i<16; i++) + if(tolower(c)==t[i]) return(i); + return (-1); +} + +/* binary value of hex string */ + +private int atox(char *t) +{ + int i=0; + int x; + while(*t) + { + if ((x=xval(*t++))<0)return (0); + i=(i<<4)+x; + } + return (i); +} +#endif /* def DOS */ + + +private void midi_init() +{ +#ifdef UNIX_IRIX_MIDIFNS +#define PBUFLEN 4 + MIconfig *config; + static u_int pbuf[] = { MI_STAMPING, MINOSTAMP, MI_BLOCKING, MINONBLOCKING}; +#endif + +#ifdef UNIX_MACH + mach_midi_init(); +#else +#ifdef ITC + midiconn = mi_open(NULL); + if (midiconn == NULL) { + gprintf(FATAL, "could not open a MIDI device\n"); + EXIT(1); + } + cu_register((cu_fn_type) mi_close, (void *) midiconn); +#endif +#endif +#ifdef AMIGA + amiga_midi_init(); +#endif /* def AMIGA */ +#ifdef DOS +#ifndef WINDOWS + int err; + int irq=SEARCHIRQ; + int base=MPUBASEADDR; + char *t; + + if (t=getenv("MPUIRQ")) { + if (musictrace) + gprintf(TRANS,"MPUIRQ %s\n",t); + irq=atoi(t); + } + if (t=getenv("MPUBASE")) { + if (musictrace) + gprintf(TRANS,"MPUBASE %s\n",t); + base=atox(t); + } + if(err = mOpen(base, irq)) { + mClose(err); + EXIT(1); + } + cu_register((cu_fn_type) mClose, 0); + cu_register((cu_fn_type) mPutCmd, (cu_parm_type) MPURESET); + initializetimer(); + cu_register((cu_fn_type) restoretimer, NULL); +#endif +#endif + +#ifdef MACINTOSH +#ifndef NYQUIST /* if NYQUIST, do nothing */ +#ifdef MIDIMGR + setup_midimgr(); /* this registers itself for cleanup */ +#else + init_abort_handler(); + cu_register(cleanup_abort_handler, NULL); + setupMIDI(portA, 0x80); + cu_register(restoreMIDI, (long) portA); + /* only initialize portB if necessary */ + if (MAX_CHANNELS > CHANNELS_PER_PORT) { + setupMIDI(portB, 0x80); + cu_register(restoreMIDI, (long) portB); + } +#endif +#endif /* NYQUIST */ +#ifdef MIDIMGR + ticksAtStart = MIDIGetCurTime(OutputRefNum); +#else + ticksAtStart = TickCount(); /* reset the clock */ +#endif +#endif /* def MACINTOSH */ + + if (!(cl_switch("noalloff"))) + cu_register((cu_fn_type) alloff, NULL); +} + +#ifdef DOS +/**************************************************************************** +* set_x_mfr +* Inputs: +* unsigned char mfr: Manufacturer ID for MIDI +* Result: void +* +* Effect: +* Sets the xcode and xcodemask to allow only these sysex messages +****************************************************************************/ + +void set_x_mfr(mfr) +unsigned char mfr; +{ + xcode = mfr; + xcodemask = 0xFF; +} + +/**************************************************************************** +* clear_x_mfr +* Result: void +* +* Effect: +* Clears sysex manufacturer code filter; accepts all sysex messages +****************************************************************************/ + +void clear_x_mfr() +{ + xcode = 0; + xcodemask = 0; +} +#endif /* DOS */ diff --git a/cmt/midifns.h b/cmt/midifns.h new file mode 100644 index 0000000..99042d3 --- /dev/null +++ b/cmt/midifns.h @@ -0,0 +1,137 @@ +/* midifns.h -- definitions for users of midifns.c */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 5-Mar-92 | GWL : insert definitions and logs from JMN's mpu.h +* for LATTICE322, only variable type in prototypes +* 28-Apr-03 | DM : random() is now named cmtrand() to avoid conflicts +*****************************************************************************/ + +#ifndef _MIDIFNS_H_ +#define _MIDIFNS_H_ + +/* declaration types */ + +typedef unsigned long time_type; +typedef long sgnd_time_type; + +/* Maximum time value: */ +#define MAXTIME 0xFFFFFFFFL + +#define delay_type long + +/* controller numbers */ + +#define MODWHEEL 1 +#define BREATH 2 +#define FOOT 4 +#define PORTARATE 5 +#define VOLUME 7 +#define SUSTAIN 64 +#define PORTASWITCH 65 + +#include "midierr.h" + +extern char *midifns_syntax; + +/* support for allocating sysex buffer - examples in mm.c & exget.c */ +#ifdef DOS +#define midibuff_alloc(size) (byte huge *) halloc(size, 1) +#endif +#ifndef midibuff_alloc +#define midibuff_alloc (byte *) MALLOC +#endif + +/* DMH: from mpu.h -- definitions for users of mpu.c */ + +#ifdef OLD_PROTOTYPES + +void eventwait(); +void exclusive(boolean); +boolean getbuf(boolean, unsigned char * ); +long get_excl(); +boolean getxbuf(); +boolean testxbuf(); +short getkey(boolean); +ulong gettime(void); /*DMH: note- now unsigned*/ +void l_rest(long); +void l_restuntil(long); +void metronome(boolean); +void midi_bend(short,short); +boolean midi_buffer(byte * , ulong); +void midi_cont(boolean); +void midi_clock(); +void midi_ctrl(short, short, short); +void midi_exclusive(unsigned char * ); +void midi_note(short, short, short); +void midi_program(short, short); +void midi_real(); +void midi_start(); +void midi_stop(); +#ifdef AMIGA +/* MIDI_THRU defined means that it is really implemented. */ +#define MIDI_THRU +#endif +void midi_thru();/*boolean onflag*/ +void midi_touch(short, short); +void midi_write(); +void musicinit(); +short cmtrand(short, short); +void read_tuning();/*char *filename*/ +void settime(); +void synth_init();/*void*/ +void timereset(); +void trace(); +void tracemidi(); +boolean is_exclusive(void); +unsigned char get_exclusive(void); + +#else + +void alloff(void); +void eventwait(long timeout); +void exclusive(boolean onflag); +long get_excl(byte *buffer, long len); +boolean getbuf(boolean waitflag, unsigned char * p); +short getkey(boolean waitflag); +ulong gettime(void); +void l_rest(long time); +void l_restuntil(long time); +void metronome(boolean onflag); +void midi_bend(int channel, int value); +boolean midi_buffer(byte *buffer, ulong size); +void midi_clock(void); +void midi_cont(boolean onflag); +void midi_ctrl(int channel, int control, int value); +void midi_exclusive(unsigned char *msg); +void midi_flush(); +void midi_note(int channel, int pitch, int velocity); +void midi_program(int channel, int program); +void midi_real(boolean onflag); +void midi_start(void); +void midi_stop(void); +void midi_thru(boolean onflag); +void midi_touch(int channel, int value); +void read_tuning(char *filename); +void midi_write(int n, int port, unsigned char c1, unsigned char c2, unsigned char c3); +void midi_write_trace(int n, int port, + unsigned char c1, unsigned char c2, unsigned char c3); +void musicinit(void); +void settime(time_type newtime); +void timereset(void); +void trace(boolean flag); +void tracemidi(boolean flag); + + +boolean check_midi(void); +#endif /* ifdef OLD_PROTOTYPES */ + + +#ifdef AMIGA +byte *head_of_excl(); +byte *tail_of_excl(); +#endif + +#endif /* _MIDIFNS_H_ */ diff --git a/cmt/midimgr.c b/cmt/midimgr.c new file mode 100644 index 0000000..e7db136 --- /dev/null +++ b/cmt/midimgr.c @@ -0,0 +1,784 @@ +/* midimgr.c -- this file contains interface code to support use of Apple Midi Manager */ +/* + * This code is based on code supplied with the Apple Midi Manager. + * Copyright 1991, Carnegie Mellon University + */ + +/* BUGS: + * If exclusive() is called to turn exclusive messages on or off DURING the + * receipt of an exclusive message, incoming data will be garbled. The correct + * handling would be to record when receipt of an exclusive message is in + * progress, then properly remove any partial message when exclusive is turned + * off, and ignore any remaining message part when exclusive is turned on. + * The present code does neither. + */ + +#include "cext.h" +#undef round +#ifdef THINK_C +#include /* for ThinkC 7 */ +#endif + +#include "stdio.h" +#include "userio.h" +#include "MIDI.h" +#include "midifns.h" +#include "midibuff.h" +#include "midierr.h" +#include "midimgr.h" +#include "midicode.h" +#include "cmdline.h" + +/* Needed for KillEverybody */ +#include +#include +#include +#include +#include +#include +#include + + +#define CMTclientID 'CMT ' +/* note the following are in alphabetical order for Patcher display */ +#define timePortID 'Atim' +#define inputPortID 'Bin ' +#define outputPortID 'Cout' +#define noClient ' ' + +#define noTimeBaseRefNum 0 +#define noReadHook 0L +#define zeroTime 0L +#define timePortBuffSize 0L +#define inputPortBuffSize 2048 +#define outputPortBuffSize 0L +#define refCon0 0L + +pascal short CMTreader(MIDIPacket *ThePacketPtr, long TheRefCon); + +/* "patch" switch from command line. This switch is cached in patch_flag and tells + whether to look in the resource fork for a patch, or just hook up to midi in and + out. If the resource fork is used, the patch will be saved upon exit. */ +private boolean patch_flag; +extern boolean ctrlFilter; +extern boolean exclFilter; +extern boolean realFilter; + +private midi_read_lock = false; /* used to stop input during data structure manipulation */ + +private void set_error(int bit); +#ifndef NYQUIST +void PatchPorts(void); +void SavePatch(OSType PortID, short PortInfoResID, char *PortInfoResName); +#endif + +/* exported: */ +public short InputRefNum; /* Input port reference number. */ +public short OutputRefNum; /* Output port reference number. */ +public short TimeRefNum; /* Time base port reference number. */ + +Boolean GManualPatch; /* True if not launched by a PatchBay Config. File. */ + +/**************************************************************************** +* +* variables shared with other modules +* +****************************************************************************/ + +/* midi input buffer */ +long buff[BUFF_SIZE/4]; /* data buffer, declared long to get 32-bit alignment */ +int buffhead = 0; /* buffer head and tail pointers */ +int bufftail = 0; + +/* user supplied system exclusive buffer */ +byte *xbuff = NULL; /* address of the user-supplied buffer */ +public long xbufmask; /* mask for circular buffer address calculation */ +long xbufhead = 0; /* buffer head and tail pointers */ +long xbuftail = 0; +boolean xbuf_flush = true; /* says to flush remainder of sysex message */ + +#ifdef SYSEXDEBUG +int sysexcount = 0; /* for debugging */ +int sysexdone = 0; +int sysexheadcount = 0; +byte sysexfirst = 0; +int sysexsysex = 0; +#endif + +/* midi_flush -- empty out buffers */ +/**/ +void midi_flush() +{ + midi_read_lock = true; + buffhead = 0; + bufftail = 0; + xbufhead = 0; + xbuftail = 0; + xbuf_flush = true; /* in case sysex continuation messages are still coming */ + midi_read_lock = false; +} + + +/* Nyquist only uses CMT for Midi and Adagio file IO */ +#ifndef NYQUIST +/* Get String representation of MIDI Mgr Version Num.*/ +/* See Mac Tech Note #189 for details. */ +char *StdMacVerNumToStr(long VerNum, char *VerStr) +{ + char *RetVal; + char MajVer, MinVer, VerStage, VerRev, BugFixVer = 0; + + if (VerNum == 0) + { + RetVal = NULL; + } + else + { + MajVer = (VerNum & 0xFF000000) >> 24; + MinVer = (VerNum & 0x00FF0000) >> 16; + VerStage = (VerNum & 0x0000FF00) >> 8; + VerRev = (VerNum & 0x000000FF) >> 0; + BugFixVer = MinVer & 0x0F; + + switch (VerStage) + { + case 0x20: + VerStage = 'd'; + break; + case 0x40: + VerStage = 'a'; + break; + case 0x60: + VerStage = 'b'; + break; + case 0x80: + VerStage = ' '; + break; + default: + VerStage = '?'; + break; + } + + if (BugFixVer == 0) + { + sprintf(VerStr,"%X.%X%c%X", + MajVer, MinVer>>4, VerStage, VerRev); + } + else + { + sprintf(VerStr,"%X.%X.%X%c%X", + MajVer, MinVer >> 4, MinVer & 0x0F, VerStage, VerRev); + } + + RetVal = VerStr; + } + + return(RetVal); +} + + +/* C2PStrCpy -- Convert a C String (from Cstr) into a Pascal string */ +/* + * NOTE: this is not the same code as shipped with midi manager example + */ +char *C2PStrCpy(char *Cstr, Str255 Pstr) +{ + char *c = Cstr; + char *p = ((char *) Pstr) + 1; + + while (*c) *p++ = *c++; + *Pstr = c - Cstr; + return( (char *) Pstr ); +} + +/* This checks to see if THINK C is running under System 7, + and ONLY WORKS UNDER SYSTEM 7! Don't use unless you check! */ +boolean ThinkCRunning(void) +{ + ProcessSerialNumber processSN; + OSErr myErr; + ProcessInfoRec infoRec; + + processSN.lowLongOfPSN = kNoProcess; + processSN.highLongOfPSN = kNoProcess; + do { + myErr = GetNextProcess(&processSN); + + infoRec.processInfoLength = sizeof(ProcessInfoRec); + infoRec.processName = 0L; + infoRec.processAppSpec = 0L; + myErr = GetProcessInformation(&processSN, &infoRec); + if (!myErr) { + if (infoRec.processSignature == 'KAHL') { + return(true); + } + } + } while (myErr == noErr); + return(false); +} + +/* This kills off all the other running processes... + ONLY WORKS UNDER SYSTEM 7! Don't use unless you check! */ +void KillEverybody(void) +{ + ProcessSerialNumber myProc, processSN; + ProcessSerialNumber finderPSN; + ProcessInfoRec infoRec; + Str31 processName; + FSSpec procSpec; + + OSErr myErr = noErr; + OSErr otherError; + AppleEvent theEvent; + AEDesc theAddress; + Boolean ourFlag, notFinder; + Boolean finderFound = false; + + GetCurrentProcess(&myProc); + /* Preset the PSN to no PSN, see IM VI, the Process Manager */ + processSN.lowLongOfPSN = kNoProcess; + processSN.highLongOfPSN = kNoProcess; + finderPSN.lowLongOfPSN = 0UL; /* brk: was nil */ + finderPSN.highLongOfPSN = 0UL; /* brk: was nil */ + + do { + myErr = GetNextProcess(&processSN); + /* See if it's us first */ + notFinder = true; + SameProcess(&myProc, &processSN, &ourFlag); + + infoRec.processInfoLength = sizeof(ProcessInfoRec); + infoRec.processName = (StringPtr) &processName; + infoRec.processAppSpec = &procSpec; + GetProcessInformation(&processSN, &infoRec); + if (!ourFlag && !finderFound) { + /* see if it's the Finder, we have to kill the finder LAST */ + /* or else non-sys 7 apps won't get killed */ + /* since the Finder must be there to convert the AppleEvent to Puppet Strings */ + /* if the app is not APpleEvent aware */ + /* Also, FileShare HAS to be killed before the Finder */ + /* or your life will be unpleasant */ + + if (infoRec.processSignature == 'MACS' && infoRec.processType == 'FNDR') { + /* save this number for later */ + finderPSN = processSN; + notFinder = false; + finderFound = true; + + } else { + notFinder = true; + } + } + if (!myErr && !ourFlag && notFinder) { + otherError = AECreateDesc(typeProcessSerialNumber, (Ptr)&processSN, sizeof(processSN), &theAddress); + if (!otherError) + otherError = AECreateAppleEvent(kCoreEventClass, kAEQuitApplication, &theAddress, kAutoGenerateReturnID, + kAnyTransactionID, &theEvent); + if (!otherError) + AEDisposeDesc(&theAddress); + /* Again, the Finder will convert the AppleEvent to puppetstrings if */ + /* the application is a System 6 or non-AE aware app. This ONLY */ + /* happens for the 4 required (oapp,odoc,pdoc, and quit) AppleEvents */ + /* and ONLY if you use the PSN for the address */ + if (!otherError) + AESend(&theEvent, 0L, kAENoReply + kAEAlwaysInteract + kAECanSwitchLayer, kAENormalPriority, kAEDefaultTimeout, + 0L, 0L); + AEDisposeDesc(&theEvent); + } + } while (!myErr); + + /* Now, if the finder was running, it's safe to kill it */ + if (finderPSN.lowLongOfPSN || finderPSN.highLongOfPSN) { + otherError = AECreateDesc(typeProcessSerialNumber, (Ptr)&finderPSN, sizeof(processSN), &theAddress); + if (!otherError) + otherError = AECreateAppleEvent(kCoreEventClass, kAEQuitApplication, &theAddress, kAutoGenerateReturnID, + kAnyTransactionID, &theEvent); + if (!otherError) + AEDisposeDesc(&theAddress); + if (!otherError) + AESend(&theEvent, 0L, kAENoReply + kAEAlwaysInteract + kAECanSwitchLayer, kAENormalPriority, kAEDefaultTimeout, 0L, + 0L); + AEDisposeDesc(&theEvent); + } +} + +/* Sign into the MIDI Manager. */ +/* Set up time, input, and output ports. */ +/* Start our time base clock. */ +void setup_midimgr(void) +{ + MIDIPortParams Init; /* MIDI Mgr Init data structure */ + Handle TheIconHndl; + OSErr TheErr; + long MIDIMgrVerNum; /* MIDI Manager Ver (Std Mac Ver #) */ + Str255 name = "\pCMU MIDI Toolkit"; + char MIDIMgrVerStr[256]; /* MIDI Manager Ver (Std Mac Ver # String) */ + long vers; + EventRecord theEvent; + + Gestalt(gestaltSystemVersion, &vers); + vers = (vers >> 8) & 0xf; /* shift result over and mask out major version number */ + if ((vers >= 7) && (!cl_switch("keep")) && (!ThinkCRunning())) { + gprintf(TRANS,"Killing other processes...\n"); + KillEverybody(); + for (vers=0; vers<100; ++vers) { + while (WaitNextEvent(everyEvent, &theEvent, 0L, 0L)) ; + } + } + + /* Make sure MIDIMgr is installed and save version num. */ + MIDIMgrVerNum = SndDispVersion(midiToolNum); + if (MIDIMgrVerNum == 0) { + gprintf(ERROR, "The MIDI Manager is not installed! Exiting...\n"); + EXIT(1); + } else { + StdMacVerNumToStr(MIDIMgrVerNum, MIDIMgrVerStr); + gprintf(TRANS,"MIDI Manager Version %s\n", MIDIMgrVerStr); + } + + + /* Sign in to the MIDI Manager. */ + TheIconHndl = GetResource('ICN#', 1); + TheErr = MIDISignIn(CMTclientID, + 0L, + TheIconHndl, + name); + if (TheErr) { + gprintf(ERROR, "Trouble signing into MIDI Manager! Aborting..."); + EXIT(1); + } + + /* Assume not a Patchbay configuration. */ + GManualPatch = true; + + /* Add time port. */ + Init.portID = timePortID; + Init.portType = midiPortTypeTime; + Init.timeBase = noTimeBaseRefNum; + Init.readHook = noReadHook; + Init.initClock.syncType = midiInternalSync; + Init.initClock.curTime = zeroTime; + Init.initClock.format = midiFormatMSec; + Init.refCon = SetCurrentA5(); + C2PStrCpy("TimeBase", Init.name); + TheErr = MIDIAddPort(CMTclientID, timePortBuffSize, &TimeRefNum, &Init); + /* Has a PatchBay connection been resolved? */ + if (TheErr == midiVConnectMade) { + GManualPatch = false; + } else if (TheErr == memFullErr) { + gprintf(ERROR, "Not enough room in heap zone to add time port! Aborting..."); + MIDISignOut(CMTclientID); + EXIT(1); + } + + /* Add an input port. */ + Init.portID = inputPortID; + Init.portType = midiPortTypeInput; + Init.timeBase = TimeRefNum; + Init.offsetTime = midiGetCurrent; + Init.readHook = NewMIDIReadHookProc(CMTreader); + Init.refCon = SetCurrentA5(); + C2PStrCpy("InputPort", Init.name); + TheErr = MIDIAddPort(CMTclientID, inputPortBuffSize, &InputRefNum, &Init); + /* Has a PatchBay connection been resolved? */ + if (TheErr == midiVConnectMade) { + GManualPatch = false; + } else if (TheErr == memFullErr) { + gprintf(ERROR, "Not enough room in heap zone to add input port! Aborting..."); + MIDISignOut(CMTclientID); + EXIT(1); + } + + /* Add an output port. */ + Init.portID = outputPortID; + Init.portType = midiPortTypeOutput; + Init.timeBase = TimeRefNum; + Init.offsetTime = midiGetCurrent; + Init.readHook = NULL; + Init.refCon = refCon0; + C2PStrCpy("OutputPort", Init.name); + TheErr = MIDIAddPort(CMTclientID, outputPortBuffSize, &OutputRefNum, &Init); + /* Has a PatchBay connection been resolved? */ + if (TheErr == midiVConnectMade) { + GManualPatch = false; + } else if (TheErr == memFullErr) { + printf("Not enough room in heap zone to add output port! Aborting..."); + MIDISignOut(CMTclientID); + EXIT(1); + } + + if (GManualPatch) { + PatchPorts(); /* connect ports as they were */ + } + /* to clean this up (later) call finish_midimgr() */ + cu_register((cu_fn_type) finish_midimgr, (cu_parm_type) finish_midimgr); + + /* Start our Clock. */ + MIDIStartTime(TimeRefNum); +} + + +/* The Read Hook Function. */ + +/* 1st 4 bytes of sysex message get saved here and enqueued later */ +char save_sysex_head[4]; +int save_sysex_head_x = 0; + +void sysex_insert(unsigned char data) { + if (save_sysex_head_x < 4) { + save_sysex_head[save_sysex_head_x++] = data; + } + xbuff[xbuftail++] = data; + xbuftail &= xbufmask; + if (xbuftail == xbufhead) { + set_error(SYSEXOVFL); + } + if (data == MIDI_EOX) { /* we're done with the message */ + *((long *) (((byte *) buff) + bufftail)) = *((long *)save_sysex_head); + bufftail = (bufftail + 4) & BUFF_MASK; + if (bufftail == buffhead) { + set_error(BUFFOVFL); + } + } +} + +/* Read all incomming MIDI data. */ + +pascal short CMTreader(MIDIPacket *ThePacketPtr, long TheRefCon) +{ + /* Set up our A5 world. */ + long SysA5 = SetA5(TheRefCon); + short RetVal = midiMorePacket, i, j; + unsigned char *mm_data = ThePacketPtr->data; + register byte data1 = mm_data[1]; + + if (midi_read_lock) { + /* Don't want to read packet now, get it later */ + /* DOES THIS REALLY WORK? WHAT WILL CAUSE AN INTERRUPT + * TO OCCUR LATER? THIS IS ONLY USED BY midi_flush, IS + * BASED ON THE MidiArp CODE FROM APPLE, AND IS UNTESTED - RBD + */ + RetVal = midiKeepPacket; + goto alldone; + } + + /* see if Packet is an error message */ + if (((ThePacketPtr->flags & midiTypeMask) == midiMgrType) && + *((short *) (&(ThePacketPtr->data))) < midiMaxErr) { + set_error(MIDIMGRERR); + goto alldone; + } + + /* filter out control changes */ + if (ctrlFilter) { + register int hibits = *mm_data & 0xF0; + if (hibits == 0xD0 || /* Chan Pressure */ + hibits == 0xE0 || /* Pitch Bend */ + hibits == 0xA0 || /* Poly Pressure */ + ((hibits == 0xB0) && /* Control change (don't count switches) */ + ((data1 < 64) || (data1 > 121)))) { + /* CONTROL MESSAGE HAS BEEN FILTERED */ + goto alldone; + } + } else if (realFilter) { + register int hibits = *mm_data & 0xF0; + if (hibits >= 0xF8) goto alldone; + } + + + /* if not a continuation, copy the data into cmt_data */ + /* The logic to detect a non-continued + * packet or a first packet is: "flags bit 1 is clear". + */ + if ((((ThePacketPtr->flags & midiContMask) == midiNoCont)) && + (*mm_data != MIDI_SYSEX)) { + register byte *cmt_data = ((byte *) buff) + bufftail; + *((long *) cmt_data) = *((long *) mm_data); + + bufftail = (bufftail + 4) & BUFF_MASK; + if (bufftail == buffhead) { + /* filled buffer faster than client emptied it */ + set_error(BUFFOVFL); + } + } + + /* see if we have a sysex message to copy to buffer */ + if (xbuff && !exclFilter && + ((ThePacketPtr->flags & midiContMask) || *mm_data == MIDI_SYSEX)) { + int i; + register byte *x_data = xbuff + xbuftail; + + /* iterate over data in message */ + /* NOTE: in the previous implementation, I thought Sysex messages were + * always starting at the beginning of the buffer, but that didn't work. + * This implementation assumes nothing -- it is slower because of additional + * testing and parsing inside the loop, but seems to work. + */ + for (i = ThePacketPtr->len - 6; i > 0; i--) { + if (xbuf_flush) { /* we're searching for beginning of message */ + if (*mm_data == MIDI_SYSEX) { + xbuf_flush = false; + sysex_insert(MIDI_SYSEX); + } + } else { /* we're scanning to the end of the message */ + if (*mm_data == MIDI_SYSEX) { /* found it, insert proper EOX */ + sysex_insert(MIDI_EOX); + sysex_insert(MIDI_SYSEX); + } else if (*mm_data == MIDI_EOX) { /* found it */ + sysex_insert(MIDI_EOX); + xbuf_flush = true; + } else sysex_insert(*mm_data); + + } + mm_data++; + } + } +alldone: + + /* Restore the systems A5 world. */ + SetA5(SysA5); + + return(RetVal); +} + + +/* Sign out from the MIDI Manager. */ +void finish_midimgr(void) +{ + if (GManualPatch && patch_flag) { + SavePatch(timePortID, timePortResInfoID, "timePortInfo"); + SavePatch(inputPortID, inputPortResInfoID, "inputPortInfo"); + SavePatch(outputPortID, outputPortResInfoID, "outputPortInfo"); + } + MIDISignOut(CMTclientID); +} + + + +/* Alert user to Resource Manager Error. */ +void +ReportResError(char *Msg) +{ + OSErr TheErr; + char Buf[256]; + + if ( (TheErr = ResError()) != noErr) { + gprintf(ERROR, "ResError %d: %s...Aborting.", TheErr, Msg); + EXIT(1); + } else { + /* gprintf(ERROR, "%s OK\n", Msg); */ + } +} + + +/**************************************************************************** +* error handling +* Effect: +* various error conditions are flagged by setting bits in +* the global midi_error_flags. it is up to the client to clear this +* word when necessary. +****************************************************************************/ + +private void set_error(int bit) +{ + midi_error_flags |= (1 << bit); +} + + +void midi_show_errors() +{ + if (midi_error_flags & (1<numIDs; i++) { + OSType id = (*clients)->list[i]; + gprintf(TRANS, "%d: %c%c%c%c\n", i, (char) (id>>24), + (char) ((id >> 16) & 0xFF), (char) ((id >> 8) & 0xFF), + (char) (id & 0xFF)); + } + ports = MIDIGetPorts('amdr'); + HLock((Handle) ports); + for (i = 0; i < (*ports)->numIDs; i++) { + OSType id = (*ports)->list[i]; + gprintf(TRANS, "%d: %c%c%c%c\n", i, (char) (id>>24), + (char) ((id >> 16) & 0xFF), (char) ((id >> 8) & 0xFF), + (char) (id & 0xFF)); + } + HUnlock((Handle) ports); + HUnlock((Handle) clients); +#endif + /* the work starts here */ + err = MIDIConnectData('CMT ', 'Cout', 'amdr', 'Aout'); + /* gprintf(TRANS, "Connected CMT.Cout to amdr.Aout: %d\n", err); */ + err = MIDIConnectData('amdr', 'Ain ', 'CMT ', 'Bin '); + /* gprintf(TRANS, "Connected amdr.Ain to CMT.Bin: %d\n", err); */ + + return; + } + HLock((Handle) PortInfoH); + PortInfoP = *PortInfoH; + if (GetHandleSize((Handle) PortInfoH) != 0) + { + /* Were we supposed to be sync'd to another client? */ + if (PortInfoP->timeBase.clientID != noClient) + { + /* Yes, so make that client our time base. */ + TheErr = MIDIConnectTime( + PortInfoP->timeBase.clientID, + PortInfoP->timeBase.portID, + CMTclientID, + timePortID + ); +#ifdef IGNORE + /* Is the client still signed in? */ + if (TheErr != midiVConnectErr) + { + /* Yes, so set our sync mode to external. */ + MIDISetSync(ArpGlobals.TimeRefNum, midiExternalSync); + } +#endif + + } + /* Were we somebody else's time base? */ + for (i=0; inumConnects; i++) + { + MIDIConnectTime(CMTclientID, + timePortID, + PortInfoP->cList[i].clientID, + PortInfoP->cList[i].portID); + } + } + HUnlock((Handle) PortInfoH); + ReleaseResource((Handle) PortInfoH); + ReportResError("PatchPorts/ReleaseResource()"); + + /* SET UP INPUT PORT CONNECTIONS. */ + PortInfoH = (MIDIPortInfoHdl) GetResource(portResType, inputPortResInfoID); + if (PortInfoH == NULL) + { + ReportResError("PatchPorts/GetResource()"); + } + HLock((Handle) PortInfoH); + PortInfoP = *PortInfoH; + if (GetHandleSize((Handle) PortInfoH) != 0) + { + /* Were we connected to anyone? */ + for (i=0; inumConnects; i++) + { + MIDIConnectData(CMTclientID, + inputPortID, + PortInfoP->cList[i].clientID, + PortInfoP->cList[i].portID); + } + } + HUnlock((Handle) PortInfoH); + ReleaseResource((Handle) PortInfoH); + ReportResError("PatchPorts/GetResource()"); + + /* SET UP OUTPUT PORT CONNECTIONS. */ + PortInfoH = (MIDIPortInfoHdl) GetResource(portResType, outputPortResInfoID); + if (PortInfoH == NULL) + { + ReportResError("PatchPorts/GetResource()"); + } + HLock((Handle) PortInfoH); + PortInfoP = *PortInfoH; + if (GetHandleSize((Handle) PortInfoH) != 0) { + /* Were we connected to anyone? */ + for (i=0; inumConnects; i++) + { + MIDIConnectData(CMTclientID, + outputPortID, + PortInfoP->cList[i].clientID, + PortInfoP->cList[i].portID); + } + } + HUnlock((Handle) PortInfoH); + ReleaseResource((Handle) PortInfoH); + ReportResError("PatchPorts/ReleaseResource()"); + +} + +/* Save current port connections (port info records) */ +/* to application's 'port' resource. */ +void +SavePatch(OSType PortID, short PortInfoResID, char *PortInfoResName) +{ + Handle PortResH; /* Handle to ptch resource. */ + CursHandle WatchCurs; + + WatchCurs = GetCursor(watchCursor); + HLock((Handle) WatchCurs); + SetCursor(*WatchCurs); + HUnlock((Handle) WatchCurs); + + + /* Remove existing port info resource. */ + PortResH = GetResource(portResType, PortInfoResID); + /* gprintf(TRANS, "PortResH: %lx, *PortResH: %lx\n", PortResH, *PortResH); */ + if (PortResH) { + ReportResError("SavePatch/GetResource()"); + RmveResource(PortResH); + ReportResError("SavePatch/RmveResource()"); + DisposHandle(PortResH); + UpdateResFile(CurResFile()); + ReportResError("SavePatch/UpdateResFile()"); + } + + /* Get new configurateion. */ + PortResH = (Handle) MIDIGetPortInfo(CMTclientID, PortID); + + /* Save new configurateion. */ + CtoPstr(PortInfoResName); + AddResource(PortResH, portResType, PortInfoResID, + (ConstStr255Param) PortInfoResName); + PtoCstr((unsigned char *) PortInfoResName); + + ReportResError("SavePatch/AddResource()"); + WriteResource(PortResH); + ReportResError("SavePatch/WriteResource()"); + UpdateResFile(CurResFile()); + ReportResError("SavePatch/UpdateResFile()"); + ReleaseResource(PortResH); + ReportResError("SavePatch/ReleaseResource()"); + + InitCursor(); +} +#endif /* NYQUIST */ diff --git a/cmt/midimgr.h b/cmt/midimgr.h new file mode 100644 index 0000000..790fc06 --- /dev/null +++ b/cmt/midimgr.h @@ -0,0 +1,14 @@ +#define portResType 'port' +#define timePortResInfoID 128 +#define inputPortResInfoID 129 +#define outputPortResInfoID 130 + + + +extern short InputRefNum; /* Input port reference number. */ +extern short OutputRefNum; /* Output port reference number. */ +extern short TimeRefNum; /* Time base port reference number. */ + +void setup_midimgr(void); +void finish_midimgr(void); +void midi_show_errors(); diff --git a/cmt/moxc.c b/cmt/moxc.c new file mode 100644 index 0000000..f62d65d --- /dev/null +++ b/cmt/moxc.c @@ -0,0 +1,669 @@ +/* MOXC -- a C version of Collinge's MOXIE language */ +/* Copyright 1989 Carnegie Mellon University */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 31-Dec-85 | Modified for use with midi +* 5-Feb-86 | Added m_rest and m_restuntil allowing rests at top level +* 28-May-86 | Added command line parsing +* 4-Jun-86 | changed keyevent to separate calls for each event type +* 10-Jul-86 | put loop in mainscore with prompt to play and replay +* 03-Jun-88 | modified for portability (AMIGA) -JCD +* 07-Jul-89 | time bases -RBD +* 31-Jan-90 | GWL : cleaned up for LATTICE +* 30-Jun-90 | RBD : further changes +* 2-Apr-91 | JDW : further changes +* 4-Mar-91 | GWL : DOS allows odd inst addrs +* 10-Oct-94 | nix : posicionador tridimensionale interface +* 28-Apr-03 | DM : true->TRUE, false->FALSE +*****************************************************************************/ + +#include "switches.h" + +#ifdef AMIGA +#ifdef AZTEC +#include "functions.h" +#else +#include "amiga.h" +#endif +#include "exec/exec.h" +#include "cmtcmd.h" + +extern long event_mask; /* imported from midifns.c */ +#endif +extern int abort_flag; /*DMH: taken out of ifdef AMIGA for moxcrun*/ + +#include "stdio.h" +#include "cext.h" +#include "userio.h" +#include "midifns.h" + +#include "cmdline.h" +#include "midicode.h" +#include "timebase.h" +#include "moxc.h" + +#ifdef AMIGA /*DMH: only AMIGA cares about AMIGA's "proportional controllers"*/ +#include "prop1.h" +#endif +#ifdef POSICIONADOR_3D +#include "pos3d.h" +#include "pos3dbuf.h" +#endif /* POSICIONADOR_3D */ + +extern char *app_syntax; + +/*************************************************************************** +* +* IMPORTS: +* asciievent(k) user-defined action for terminal input +* bendchange(ch, val) user-defined pitch bend handler +* ctrlchange(ch, c, val) user-defined control change handler +* keydown(ch, p, v) user-defined MIDI note on handler +* keyup(ch, p) user-defined MIDI note off handler +* mainscore() user-defined first action(s) +* musicfns lots of time and io functions +* peddown(ch) user-defined pedal down handler +* pedup(ch) user-defined pedal up handler +* touchchange(ch, val) user-defined aftertouch handler +* app_syntax string defining extra command line options +* +* EXPORTS: +* +* cause(delay, routine, p1, p2, ..., p8) +* moxcdone -- set to TRUE to quit +* eventtime -- ideallized current time +* +*****************************************************************************/ + +#define SAFEMOXC TRUE +#define BREAKKEY 0x03 + +int moxcdone; /* flag to halt execution */ +time_type eventtime; /* time of current call -- used to avoid */ + /* timing errors due to finite execution speed */ +time_type virttime; /* virtual time of current call */ +timebase_type timebase; /* time base of current call */ +int mididecode = TRUE; /* whether to decode messages or just call midievent */ + +int debug = FALSE; +int moxcdebug = FALSE; +time_type next_wakeup; +timebase_type default_base; + +#ifdef AMIGA +int pub_port_signal; +struct MsgPort pub_port; +#endif + +/***************************************************************************** +* Routines local to this module +*****************************************************************************/ + +private void callrun(); +private void decode(); +private void moxcterm(); + +/**************************************************************************** +* callallcancel +* Inputs: +* timebase_queue +* Effect: +* return all calls to free list +* Implementation: +* If timebase_queue is not empty, there's a pending call. Remove the call +* (not necessarily the timebase) and repeat. +****************************************************************************/ + +void callallcancel() +{ + if (moxcdebug) gprintf(GDEBUG, "cancel all calls\n"); + while (timebase_queue) { + timebase = timebase_queue; + timebase_queue = timebase->next; + while (timebase->heap_size > 0) { + call_free(remove_call(timebase)); + } + insert_base(timebase); + } +} + +/* catchup -- bring current timebase up to date by running its calls */ +/**/ +void catchup() +{ + register call_type call; + /* Remember where we're going in virtual time because setting the + * rate will also modify timebase->virt_base. We don't want catchup + * to stop short: + */ + time_type target_time = timebase->virt_base; + /* remember timebase here because it's possible that a call will do + * a timebase_use() and change it: + */ + register timebase_type my_base = timebase; + + while (my_base->heap_size != 0 && + (my_base->heap[1]->u.e.time < target_time)) { + /* eventtime is the real time at which something was scheduled */ + eventtime = (my_base->next_time) >> 8; + call = remove_call(my_base); + virttime = call->u.e.time; + (*(call->u.e.routine))(CALLARGS(call)); + call_free(call); + } + /* now that we've possibly pulled events out of the timebase, adjust + * the position in the timebase queue (and possibly remove it). + */ + remove_base(my_base); + insert_base(my_base); +} + + + +/**************************************************************************** +* cause +* Inputs: +* delay_type (long) delay: time before this call should occur +* int (*routine)(): routine that implements the call +* int p1 through p8: parameters to pass to routine +* Effect: +* builds a call and puts it in pending queue for later scheduling +****************************************************************************/ + +#ifndef DOTS_FOR_ARGS +void cause(delay, routine, p) + delay_type delay; + int (*routine)(); + call_args_node p; +#else +#include + +void cause(delay_type delay, ...) +/* note: the routine parameter is not checked because any routine type can + be passed as a parameter, but in the call struct it's an int (*)() + */ +#endif +{ + register call_type call = call_alloc(); +#ifdef DOTS_FOR_ARGS + va_list xp; +#endif + + if (!call) { + gprintf(ERROR, "cause: out of memory\n"); + EXIT(1); + } + +#ifdef DOTS_FOR_ARGS + call->u.e.time = virttime + delay; + call->u.e.priority = 128; /* default priority */ + va_start(xp, delay); + call->u.e.routine = (int (*)()) va_arg(xp, long *); + call->u.e.p = va_arg(xp, call_args_node); + va_end(xp); +#else + call->u.e.time = virttime + delay; + call->u.e.priority = 128; /* default priority */ + call->u.e.routine = routine; + call->u.e.p = p; +#endif +#ifdef SAFEMOXC + if (call->u.e.routine == 0) { + gprintf(ERROR,"cause called with NULL routine\n"); + EXIT(1); +#ifndef DOS /* IBM allows odd addresses */ +#if (__APPLE__ != 1 || __i386__ != 1) /* Intel Mac allows odd addresses */ + } else if (((long) call->u.e.routine) & 1) { + gprintf(ERROR, "cause called with bad routine address: 0x%lx\n", + call->u.e.routine); +#ifndef GCC_MODEL_CPU +#define GCC_MODEL_CPU "GCC_MODEL_CPU is undefined for this compilation" +#endif + gprintf(ERROR, GCC_MODEL_CPU); + EXIT(1); +#endif +#endif + } +#endif + /* put call in default queue */ + callinsert(timebase, call); + if (moxcdebug) { + gprintf(GDEBUG,"(cause) call is pending on timebase 0x%x:\n", timebase); + callshow(call); + } +} + +/**************************************************************************** +* causepri +* Inputs: +* int delay: time before this call should occur +* int pri: priority, lowest priority goes first +* int (*routine)(): routine that implements the call +* int p1 through p8: parameters to pass to routine +* Effect: +* builds a call and puts it in pending queue for later scheduling +****************************************************************************/ + +#ifndef DOTS_FOR_ARGS +void causepri(delay, pri, routine, p) + delay_type delay; + int pri; + int (*routine)(); + call_args_node p; +#else +/* already included stdarg.h */ + +void causepri(delay_type delay, int pri, ...) +#endif +{ + register call_type call = call_alloc(); +#ifdef DOTS_FOR_ARGS + va_list xp; +#endif + + if (!call) { + gprintf(ERROR, "cause: out of memory\n"); + EXIT(1); + } + +#ifdef DOTS_FOR_ARGS + call->u.e.time = virttime + delay; + call->u.e.priority = pri; /* default priority */ + va_start(xp, pri); + call->u.e.routine = (int (*)()) va_arg(xp, long *); + call->u.e.p = va_arg(xp, call_args_node); + va_end(xp); +#else + call->u.e.time = virttime + delay; + call->u.e.priority = pri; /* default priority */ + call->u.e.routine = routine; + call->u.e.p = p; +#endif +#ifdef SAFEMOXC + if (call->u.e.routine == 0) { + gprintf(ERROR,"cause called with NULL routine\n"); + EXIT(1); +#ifndef DOS /* IBM allows odd addresses */ +#if (__APPLE__ != 1 || __i386__ != 1) /* Intel Mac allows odd addresses */ + } else if (((long) call->u.e.routine) & 1) { + gprintf(ERROR, "causepri called with bad routine address: 0x%lx\n", + call->u.e.routine); + EXIT(1); +#endif +#endif + } +#endif + /* put call in default queue */ + callinsert(timebase, call); + if (moxcdebug) { + gprintf(GDEBUG,"(cause) call is pending:"); + callshow(call); + } +} + +/**************************************************************************** +* callrun +* Inputs: +* call_type call: the call to execute +* Effect: +* executes the previously scheduled call call and deallocates it +****************************************************************************/ + +private void callrun() +{ + call_type call; + if (moxcdebug) { + gprintf(GDEBUG,"(callrun) running a call: \n"); + } + /* remove from head of queue */ + while (!timebase_queue) gprintf(TRANS, "callrun fatal error\n"); + timebase = timebase_queue; + timebase_queue = timebase->next; + + if (debug) gprintf(TRANS, "callrun time %ld\n", timebase->next_time); + eventtime = (timebase->next_time) >> 8; /* real time of the call */ + + /* remove first call from timebase */ + call = remove_call(timebase); + if (debug) gprintf(TRANS, "callrun call %lx\n", (ulong)call); + insert_base(timebase); + virttime = call->u.e.time; /* virtual time of the call */ + if (moxcdebug) callshow(call); + (*(call->u.e.routine))(CALLARGS(call)); + call_free(call); +} + +/**************************************************************************** +* m_restuntil +* Inputs: +* int time: call time to rest until +* Effect: +* Waits until the specified time has been reached (absolute time). +* Other "caused" calls will take place during the rest provided +* this routine is called from "mainscore" (see m_rest description). +****************************************************************************/ +void m_restuntil(time) + time_type time; +{ + time = virt_to_real(timebase, time); + while(time > gettime()) { + moxcwait(time); + } +} + + +/**************************************************************************** +* m_rest +* Inputs: +* int time: Amount of time to rest +* Effect: +* Waits until the amount of time specified has lapsed +* Assumes: +* Must not be called from a "caused" routine. Must only be called +* from "mainscore" or a routine called directly or indirectly from +* "mainscore" without using "cause". +****************************************************************************/ + +void m_rest(time) + time_type time; +{ + m_restuntil(time + real_to_virt(timebase, gettime())); +} + +/**************************************************************************** +* moxcinit +* Inputs: +* int argc: number of command line arguments +* char * argv: command line argument array +* Effect: initializes moxc system +****************************************************************************/ + +boolean moxcinit(argc, argv) + int argc; + char * argv[]; +{ + meminit(); + io_init(); +#ifdef AMIGA + pub_port_signal = AllocSignal(-1L); + pub_port.mp_Node.ln_Type = NT_MSGPORT; + pub_port.mp_SigBit = pub_port_signal; + pub_port.mp_SigTask = FindTask(0L); + pub_port.mp_Flags = PA_SIGNAL; + pub_port.mp_Node.ln_Name = "CMTcmdport"; + pub_port.mp_MsgList.lh_Head = + (struct Node *)&pub_port.mp_MsgList.lh_Tail; + pub_port.mp_MsgList.lh_TailPred = + (struct Node *)&pub_port.mp_MsgList.lh_Head; + event_mask |= (1L << pub_port_signal); + AddPort(&pub_port); +#endif + cu_register((cu_fn_type) moxcterm, NULL); + cl_syntax(midifns_syntax); + cl_syntax("debugEnable verbose debugging;\ + moxcEnable moxc debug mode;"); + cl_syntax(app_syntax); + + if (!cl_init(argv, argc)) { + /* make sure user gets to read the error message(s): */ + gprintf(TRANS, "Type anything to exit..."); +#ifdef DOS + wait_ascii(); +#else + ggetchar(); +#endif + return FALSE; + } + debug = cl_switch("debug"); + moxcdebug = cl_switch("moxc"); + timebase = default_base = timebase_create(100); + default_base->rate = 2560L; + + eventtime = 0L; + next_wakeup = MAXTIME; + musicinit(); +#ifdef POSICIONADOR_3D + ptInit(); +#endif + moxcdone = 0; + return TRUE; +} + + +/**************************************************************************** +* moxcwait +* Input: +* -1 => wait for next keyboard or midi event or queued event +* 0 => don't wait +* T => wait up to T for next keyboard or midi event or queued event +* (this is used by m_restuntil) +* Assume: there is work to do (npending > 0 || evqueue) ?? +* Effect: dispatch on user inputs, cause calls +****************************************************************************/ + +void moxcwait(dateoftimeout) + time_type dateoftimeout; +{ + time_type maxtime = dateoftimeout; + + if (timebase_queue) { + if ((timebase_queue->next_time >> 8) < maxtime) + maxtime = (timebase_queue->next_time) >> 8; + } + eventwait(maxtime); + decode(); +} + + +/**************************************************************************** +* decode +* Effect: dispatch on user inputs, cause calls +****************************************************************************/ + +private void decode() +{ + /* It is important that midi_data is on a word boundary because we + copy to it by doing a word transfer. + */ + byte midi_data[4]; + time_type now; + byte code; + char k; +#ifdef AMIGA + struct cmd_msg *cmd; +#endif + + now = gettime(); + timebase = default_base; + eventtime = now; + virttime = 0L; + +/* gprintf(GDEBUG, "decode at time %ld\n", now); */ + +/********************************************** +* poll for and decode midi keyboard input +***********************************************/ + + while (getbuf(FALSE, midi_data)) { + /* only divide if necessary, divides take 100us on 8MHz 68000: */ + if (virttime == 0) + virttime = real_to_virt(default_base, now); + + /* short-circuit midi decoding */ + if (!mididecode) { + midievent(midi_data); + continue; + } + + code = midi_data[0] & MIDI_CODE_MASK; + if (code == MIDI_ON_NOTE) { + if (midi_data[2] == 0) { /* velocity 0 -> note off */ + keyup(1+(midi_data[0] & MIDI_CHN_MASK), midi_data[1]); + } else { + keydown((midi_data[0] & MIDI_CHN_MASK)+1, + midi_data[1], midi_data[2]); + } + } else if (code == MIDI_OFF_NOTE) { + keyup((midi_data[0] & MIDI_CHN_MASK)+1, midi_data[1]); + } else if (code == MIDI_TOUCH) { + touchchange((midi_data[0] & MIDI_CHN_MASK)+1,midi_data[1]); + } else if (code == MIDI_BEND) { + bendchange((midi_data[0] & MIDI_CHN_MASK)+1, + midi_data[1] + (midi_data[2] << 7)); + } else if (code == MIDI_CTRL && midi_data[1] == SUSTAIN) { + if (midi_data[2] == 0) pedup((midi_data[0] & MIDI_CHN_MASK) + 1); + else peddown((midi_data[0] & MIDI_CHN_MASK) + 1); + } else if (code == MIDI_CTRL) { + ctrlchange((midi_data[0] & MIDI_CHN_MASK) + 1, + midi_data[1], midi_data[2]); + } else if (code == MIDI_CH_PROGRAM) { + prgmchange((midi_data[0] & MIDI_CHN_MASK) + 1, midi_data[1] + 1); +/* think C midi driver doesn't handle sysex the way the Amiga drivers do (yet) */ +#ifndef MACINTOSH + } else if (code == MIDI_SYSEX) { + sysex(); +#endif + } + } + +/********************************************** +* poll for ASCII keyboard input +***********************************************/ + while (get_ascii(&k)) { + virttime = real_to_virt(default_base, now); + asciievent(k); + /* if user doesn't handle abort char in asciievent, + we should exit now to avoid an infinite loop */ + if (abort_flag) EXIT(1); + } + +#ifdef POSICIONADOR_3D +/********************************************** +* poll for posicionador tridimensionale input +**********************************************/ + { + pt_value pt_data; + while (ptGetValue(&pt_data)) { + /* only divide if necessary, divides take 100us on 8MHz 68000: */ + if (virttime == 0) + virttime = real_to_virt(default_base, now); + ptevent(&pt_data); + } + } +#endif /* POSICIONADOR_3D */ + +#ifdef AMIGA +/********************************************** +* poll for proportional controller port +**********************************************/ + if (prop_1_events) { + int events; + + Disable(); + events = prop_1_events; + prop_1_events = 0; + Enable(); + + if (events & BUTTON_1_RIGHT_CHANGE) + buttonchange(3, prop_1_right_button); + if (events & BUTTON_1_LEFT_CHANGE) + buttonchange(2, prop_1_left_button); + if (events & PROP_1_LEFT_CHANGE) + propchange(2, prop_1_left_data); + if (events & PROP_1_RIGHT_CHANGE) + propchange(3, prop_1_right_data); + } + +/********************************************** +* poll for input from public command port +***********************************************/ + + while (cmd = (struct cmd_msg *) GetMsg(&pub_port)) { + struct symb_descr *desc = &HASHENTRY(lookup(cmd->symbol_name)); +/* gprintf(TRANS, "got %lx (%s) from pub_port\n", cmd, cmd->symbol_name); */ + virttime = real_to_virt(default_base, now); + if (!desc) { + gprintf(TRANS, "Error, symbol %s undefined.\n", cmd->symbol_name); + } else if (desc->symb_type != cmd->symb_type) { + gprintf(TRANS, "Error, wrong type for symbol %s\n", + cmd->symbol_name); + } else if (cmd->symb_type == fn_symb_type) { +/* gprintf(TRANS, "Calling routine\n"); */ + (*(desc->ptr.routine))( + (int) cmd->the_args[0], (int) cmd->the_args[1], + (int) cmd->the_args[2], (int) cmd->the_args[3], + (int) cmd->the_args[4], (int) cmd->the_args[5], + (int) cmd->the_args[6], (int) cmd->the_args[7] + ); + } else if (cmd->symb_type == var_symb_type) { + *(desc->ptr.intptr) = (int) cmd->the_args[0]; + } else if (cmd->symb_type == vec_symb_type) { + if (cmd->the_args[0] >= desc->size) { + gprintf(TRANS, "Error: Vector %s is of size %d\n", + cmd->symbol_name, desc->size); + } else { + (desc->ptr.intptr)[cmd->the_args[0]] = cmd->the_args[1]; +/* gprintf(TRANS, "vec: setting %lx\n", + &(desc->ptr.intptr)[cmd->the_args[0]]); */ + } + } else gprintf(TRANS, "Symbol Type Error\n"); + ReplyMsg(&(cmd->msg)); + } +#endif +/********************************************** +* poll for next call in queue +***********************************************/ + now = (now + 1) << 8; /* shift because next_time is also scaled, + * add 256 because next_time has added priority */ + if (debug) + gprintf(TRANS, "now %ld next_time %ld\n", + now, (timebase_queue ? timebase_queue->next_time : 1234)); + /* give pending events priority, but every 100 events, loop to allow + input processing (user may want to give a "quit" command) */ + for (k = 0; + k < 100 && timebase_queue && (now > timebase_queue->next_time); + k++) { + callrun(); + } +/******************* +* flush text output +********************/ +#ifdef MACINTOSH_OR_UNIX + gflush(); +#endif +} + + +/**************************************************************************** +* quit +* Effect: tells moxc to shut down +****************************************************************************/ + +void quit() +{ + moxcdone = TRUE; +} + +/* moxcrun -- schedule events until done */ +/**/ +void moxcrun() +{ + moxcdone = FALSE; + while (!moxcdone && !abort_flag) { /* test for finish */ + if (!timebase_queue) moxcdone = TRUE; + else moxcwait(MAXTIME); /* do work */ + } +} + +/* moxcterm -- clean up after moxcinit */ +/**/ +private void moxcterm() +{ +#ifdef AMIGA + FreeSignal((long) pub_port_signal); + RemPort(&pub_port); +#endif +} diff --git a/cmt/moxc.h b/cmt/moxc.h new file mode 100644 index 0000000..975ebb9 --- /dev/null +++ b/cmt/moxc.h @@ -0,0 +1,44 @@ +/* moxc.h -- functions exported by moxie.c */ +/* Copyright 1989 Carnegie Mellon University */ + +#define maxparms 8 + +extern timebase_type timebase; +extern time_type eventtime, virttime; +extern int debug; +extern int mididecode; +extern int moxcdone; + +void catchup(void); +void callallcancel(void); +#ifdef DOTS_FOR_ARGS +void cause(delay_type delay, ...); +void causepri(delay_type delay, int pri, ...); +#else +void cause(); +void causepri(); +#endif +void m_rest(time_type time); +void m_restuntil(time_type time); +void quit(void); +boolean moxcinit(int argc, char * argv[]); +void moxcrun(void); +void moxcwait(time_type dateoftimeout); + +void asciievent(char k); +void bendchange(int chan, int value); +void coda(void); +void ctrlchange(int chan, int ctrl, int value); +void keydown(int chan, int key, int vel); +void keyup(int chan, int key); +void mainscore(void); +void midievent(byte midi_data[4]); +void peddown(int chan); +void pedup(int chan); +void prgmchange(int chan, int prgm); +void touchchange(int chan, int value); +#ifdef AMIGA +void buttonchange(int number, int value); +void propchange(int number, int value); +#endif +void sysex(void); diff --git a/cmt/musiprog.h b/cmt/musiprog.h new file mode 100644 index 0000000..ddd9f95 --- /dev/null +++ b/cmt/musiprog.h @@ -0,0 +1,31 @@ +/* musiprog.h -- include file for cmt application programs */ + +#include "stdio.h" +#include "cext.h" +#include "midifns.h" +#include "userio.h" +#include "timebase.h" +#include "moxc.h" + +/* + * override the definition of l_rest - l_rest is not recommended because + * it stops polling for input. If you really want to use it, use #undef + * to make it visible. + */ +#define l_rest(d) m_rest(d) +#define l_restuntil(t) m_restuntil(t) + +/* + * The default implementation of rest() and restuntil() poll for + * input during the rest. You might call rest() or restuntil() from + * mainscore(), but it is generally a bad idea to rest at all. If + * you are in a rest(), you get an event, e.g. keydown(), and you + * make a nested call to rest(), the original rest will be locked out + * until the nested one returns. It's better to use cause(). + */ +#define rest(x) l_rest( (long) x ) +#define restuntil(x) l_restuntil( (long) x) + +#define repeat(var, count) {int var; for (var=1; var <= count; var++) { +#define endrep ;}} + diff --git a/cmt/pitch.h b/cmt/pitch.h new file mode 100644 index 0000000..ab6dd9c --- /dev/null +++ b/cmt/pitch.h @@ -0,0 +1,9 @@ +/* mbc code */ +/* Copyright 1989 Carnegie Mellon University */ + +typedef struct pitch_struct { + int ppitch; + int pbend; +} +pitch_table; +/* end */ diff --git a/cmt/record.c b/cmt/record.c new file mode 100644 index 0000000..44f2373 --- /dev/null +++ b/cmt/record.c @@ -0,0 +1,638 @@ +/* record.c -- keyboard to adagio recorder + * Copyright 1989 Carnegie Mellon University + * + * the interface consists of three routines: + * rec_init() -- initialization + * rec_event(byte *data) -- called to insert (record) midi data, + * -- returns FALSE if no more space + * rec_final() -- called to finish up + */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 27-Feb-86 | Created changelog +* | Use pedal information when computing durations (code taken +* | from transcribe.c) +* 23-Mar-86 | Determine size of transcription when rec_init is called. +* 21-May-86 | Major rewrite to use continuous controls (code taken +* | from transcribe.c) +* 1-Aug-87 | F.H. Changed rec_init() to new memory handling. +* 17-Oct-88 | JCD : portable version. +* 31-Jan-90 | GWL : cleaned up for LATTICE +* 30-Jun-90 | RBD : further changes +* 2-Apr-91 | JDW : further changes +* 28-Apr-03 | DM : changed for portability; true->TRUE, false->FALSE +*****************************************************************************/ + +#include "switches.h" + +#include +#include + +#include "cext.h" +#include "midifns.h" +#include "userio.h" +#include "midicode.h" +#include "record.h" +#include "cmdline.h" + +extern long space; /* how much space is left? */ + +int debug_rec = FALSE; /* verbose debug flag for this module */ + +long max_notes = -1L; /* -1 is flag that space must be allocated */ + +time_type previous_time; + +/**************************************************************** +* data structure notes: the midi stream is stored as an array +* of 4-byte records, each of which is either a time or midi +* data. Midi data always begins with a control byte (high +* order bit set), and it is assumed times are positive (high +* order bit clear), so the two are easy to distinguish +* IF THE COMPILER PUTS THESE BITS IN THE SAME PLACE. It looks +* like the high order byte of the time lines up with the last +* byte of a 4 byte array, so we will always set the high order +* bit of the last array byte when the first 3 bytes are filled +* with MIDI data. This is refered to as the "tag" bit. +* WARNING: Lattice C longs are UNSIGNED, therefore always +* positive. Test the high order bit with a mask. +****************************************************************/ + +#define MIDI_CMD_BIT 0x80 +#define HIGH_BIT 0x80000000 +#define istime(note) (!(((note)->when) & HIGH_BIT)) + +typedef union note_struct { + byte n[4]; + long when; +} +*note_type, note_node; + +private note_type event_buff; /* pointer to allocated buffer */ +private note_type next; /* pointer to next entry in buffer */ +private note_type last; /* pointer to last entry in buffer */ +private int pile_ups; /* inner loop iteration count */ +private int max_pile_up; /* maximum of pile_ups */ +private boolean fixed_octave; /* used to avoid many error messages */ + +/**************************************************************************** +* Routines local to this module +****************************************************************************/ +private void bend_filter(); +private void byteorder(); +private void ctrl_filter(); +private int event_bend(); +private void filter(); +private long getdur(); +private long getnext(); +private char map_ctrl(); +private void output(); + +/**************************************************************************** +* bend_filter +* Inputs: +* note_type note: the current note +* note_type last: the last recorded event +* long now: the current time +* Effect: +* remove pitch bend events in same 0.01 sec time slot +* Implementation: +* If the current event is a pitch bend that bends again +* in the same time slot, make it a no-op by replacing it with +* the time. +****************************************************************************/ + +private void bend_filter(note, last, now) +note_type note; /* current note */ +note_type last; /* the last recorded event */ +long now; /* the current time */ +{ + /* first see if there is another bend in this time + * slot. + */ + note_type note2 = note + 1; + while (note2 < last) { + if (istime(note2) && (note2->when > now)) { + break; /* new time slot */ + } + else if (note->n[0] == note2->n[0]) { + note->when = now; + return; /* found another bend */ + } + note2++; + } +} + +/**************************************************************************** +* byteorder +* Effect: +* check out assumptions about byte order and placement +****************************************************************************/ + +private void byteorder() +{ + note_node test_event; + if ((sizeof(test_event) != 4) || + (sizeof(test_event.when) != 4) || + (sizeof(test_event.n[0]) != 1)) { + gprintf(ERROR, "implementation error: size problem\n"); + EXIT(1); + } + test_event.n[0] = 0x12; + test_event.n[1] = 0x34; + test_event.n[2] = 0x56; + test_event.n[3] = 0x78; + if ((test_event.when != 0x78563412) && + (test_event.when != 0x12345678)) { + gprintf(ERROR, "implementation error: layout problem\n"); + EXIT(1); + } +} + +/**************************************************************************** +* ctrl_filter +* Inputs: +* note_type note: the current note +* note_type last: the last recorded event +* long now: the current time +* Effect: +* remove ctrl change events in same 0.01 sec time slot +* Implementation: +* If the current event is a control change that changes again +* in the same time slot, make it a no-op by replacing it with +* the time. +****************************************************************************/ + +private void ctrl_filter(note, last, now) +note_type note; /* the current note */ +note_type last; /* the last recorded event */ +long now; /* the current time */ +{ + /* see if there is another control change in this time + * slot. + */ + note_type note2 = note+1; + while (note2 < last) { + if (istime(note2) && (note2->when > now)) { + break; /* new time slot */ + } + else if ((note->n[0] == note2->n[0]) && + (note->n[1] == note2->n[1])) { + note->when = now; + return; /* found another change */ + } + note2++; + } +} + +/**************************************************************************** +* event_bend +* Inputs: +* note_type note: pointer to a pitch bend event +* Outputs: +* returns int: an 8 bit pitch bend number +****************************************************************************/ + +private int event_bend(note) +note_type note; +{ + return((int) (((note->n[1]) >> 6) + ((note->n[2]) << 1))); +} + +/**************************************************************************** +* filter +* Inputs: +* note_type last: the last note recorded +* Effect: allow only one control change per time slot (0.01 sec) +* Implementation: +* call ctrl_filter and bend_filter to overwrite control changes with +* noop data (the current time is used as a noop) +****************************************************************************/ + +private void filter(last) +note_type last; +{ + note_type note; /* loop control variable */ + long now=0; /* last time seen */ + int command; /* command pointed to by note */ + + for (note = event_buff; note <= last; note++) { + if (istime(note)) { + now = note->when; + } + else { + command = note->n[0] & MIDI_CODE_MASK; + + if (command == MIDI_CTRL && + note->n[1] == SUSTAIN) { + /* do nothing */; + } + else if (command == MIDI_CTRL) { + ctrl_filter(note, last, now); + } + else if (command == MIDI_TOUCH) { + bend_filter(note, last, now); /* bend and touch use the */ + } + else if (command == MIDI_BEND) { /* same filter routines */ + bend_filter(note, last, now); + } + } + } +} + + +/**************************************************************************** +* getdur +* Inputs: +* int i: index of the note +* note_type last: pointer to the last event recorded +* int ped: TRUE if pedal is down at event i +* long now: the time at event i +* Outputs: +* returns long: the duration of note i +* Assumes: +* assumes i is a note +* Implementation: +* This is tricky because of pedal messages. The note is kept on by +* either the key or the pedal. Keep 2 flags, key and ped. Key is +* turned off when a key is released, ped goes off and on with pedal. +* Note ends when (1) both key and ped are FALSE, (2) key is +* pressed (this event will also start another note). +****************************************************************************/ + +private long getdur(i, last, ped, now) +int i; +note_type last; +int ped; +long now; +{ + int key = TRUE; /* flag that says if note is on */ + long start = now; + int chan = event_buff[i].n[0] & MIDI_CHN_MASK; + int pitch = event_buff[i].n[1]; + note_type note = &(event_buff[i+1]); + int noteon; /* TRUE if a noteon message received on chan */ + int keyon; /* TRUE if noteon message had non-zero velocity */ + + /* search from the next event (i+1) to the end of the buffer: + */ + for (; note < last; note++) { + if (istime(note)) { + now = note->when; + } + else { + noteon = keyon = FALSE; + if ((note->n[0] & MIDI_CHN_MASK) == chan) { + noteon = ((note->n[0] & MIDI_CODE_MASK) == MIDI_ON_NOTE) && + (note->n[1] == pitch); + keyon = noteon && (note->n[2] != 0); + if ((noteon && (note->n[2] == 0)) || + (((note->n[0] & MIDI_CODE_MASK) == MIDI_OFF_NOTE) && + (note->n[1] == pitch))) key = FALSE; + if (((note->n[0] & MIDI_CODE_MASK) == MIDI_CTRL) && + note->n[1] == SUSTAIN && note->n[2] == 127) ped = TRUE; + if (((note->n[0] & MIDI_CODE_MASK) == MIDI_CTRL) && + note->n[1] == SUSTAIN && note->n[2] == 0) ped = FALSE; + + if ((!key && !ped) || keyon) + return(now - start); + } + } + } + return(last->when - start); +} + +/**************************************************************************** +* getnext +* Inputs: +* int i: the index of the current note +* note_type last: pointer to last valid data +* long now: the current time +* Outputs: +* returns long: the time of the next note, program, or control change +* (returns time of last event if nothing else is found) +****************************************************************************/ + +private long getnext(i, last, now) +int i; /* the index of the current note */ +note_type last; /* pointer to last valid data */ +long now; /* the current time */ +{ + i++; /* advance to next item */ + for (; event_buff + i < last; i++) { + note_type note = &(event_buff[i]); + int cmd = note->n[0] & MIDI_CODE_MASK; + + if (istime(note)) { + now = note->when; + } + else if (((cmd == MIDI_ON_NOTE) && + (note->n[2] != 0)) /* note on */ || + (cmd == MIDI_CH_PROGRAM) /* program change */ || + ((cmd == MIDI_CTRL) && + (note->n[1] != SUSTAIN) /* control change */ ) || + (cmd == MIDI_TOUCH) || + (cmd == MIDI_BEND)) { + return(now); + } + } + return(last->when); +} + +/**************************************************************************** +* map_ctrl +* Inputs: +* int control: a midi control number +* Outputs: +* returns char: an adagio control change command letter, EOS if +* control change is not one of PORTARATE, PORTASWITCH, +* MODWHEEL, FOOT +****************************************************************************/ + +private char map_ctrl(control) +int control; +{ + switch (control) { +/* 'J' is no longer code for PORTARATE + case PORTARATE: + return 'J'; */ + case PORTASWITCH: + return 'K'; + case MODWHEEL: + return 'M'; + case VOLUME: + return 'X'; + default: + return EOS; + } +#ifdef LATTICE322 + return EOS; /* make Lattice C type checker happy */ +#endif +} + +/**************************************************************************** +* output +* Inputs: +* FILE *fp: an opened file pointer +* note_type last: the last data in the buffer +* boolean absflag: set to TRUE if first line of the adagio score should +* include the absolute time +* Effect: +* write adagio file using data in event_buff +* Implementation: +* NOTE: put all program changes in rests +* use N(ext) notation for all timing +* output no more than one continuous parameter change per +* clock tick for each continuous change parameter +****************************************************************************/ + +private void output(fp, last, absflag) +FILE *fp; +note_type last; +boolean absflag; +{ + int i; /* loop counter */ + int command; /* the current command */ + int voice; /* the midi channel of the current event */ + int last_velocity = -1; /* used to filter repeated Lnn attributes */ + int last_voice = 0; /* the default adagio channel (1) */ + int ped = FALSE; /* flag maintains state of pedal */ + int how_many = last - event_buff; + long now=0; /* the time of the next event */ + + if (fp == NULL) { + gprintf(ERROR, "internal error: output called with NULL file.\n"); + EXIT(1); + } + + if (debug_rec) + gprintf(GDEBUG,"hint: if file is not being closed, decrease MAXSPACE\n"); + + + fprintf(fp, "!MSEC\n"); /* times will be in milliseconds */ + /* set the initial absolute time, all other times are relative */ + + if (absflag) { + now = event_buff[0].when; + if (now < 0) { + fprintf(fp, "* First event took place at Adagio time %d,\n", + (int)now); + fprintf(fp, "* but Adagio cannot represent negative times,\n"); + fprintf(fp, "* so this entire score will be %d ms late\n", + (int)-now); + gprintf(TRANS, "First event took place at Adagio time %d!\n", + (int)now); + gprintf(TRANS, "All events times will be %d ms late\n", + (int)-now); + now = 0L; + } + fprintf(fp, "T%ld ", now); + } + + for (i = 0; i < how_many; i++) { + if (debug_rec) { + gprintf(GDEBUG,"ev %d: %x %x %x (%ld)\n", i, event_buff[i].n[0], + event_buff[i].n[1], event_buff[i].n[2], event_buff[i].when); + } + + if (istime(event_buff+i)) { + now = event_buff[i].when; + if (debug_rec) gprintf(GDEBUG,"i = %d, now = %ld\n", i, now); + } else { + boolean needs_voice = TRUE; + command = event_buff[i].n[0] & MIDI_CODE_MASK; + voice = event_buff[i].n[0] & MIDI_CHN_MASK; + + if (command == MIDI_ON_NOTE && event_buff[i].n[2] != 0) { + int velocity = event_buff[i].n[2]; + write_pitch(fp, event_buff[i].n[1]); + fprintf(fp, " U%ld", getdur(i, last, ped, now)); + if (last_velocity != velocity) { + fprintf(fp, " L%d", velocity); + last_velocity = velocity; + } + } else if (command == MIDI_CH_PROGRAM) { + fprintf(fp, "Z%d", event_buff[i].n[1] + 1); + } else if (command == MIDI_CTRL && + event_buff[i].n[1] == SUSTAIN) { + ped = (event_buff[i].n[2] != 0); + needs_voice = FALSE; + } else if (command == MIDI_CTRL) { + char c = map_ctrl(event_buff[i].n[1]); + if (c != EOS) fprintf(fp, "%c%d", c, event_buff[i].n[2]); + else fprintf(fp, "~%d(%d)", event_buff[i].n[1], event_buff[i].n[2]); + } else if (command == MIDI_TOUCH) { + fprintf(fp, "O%d", event_buff[i].n[1]); + } else if (command == MIDI_BEND) { + fprintf(fp, "Y%d", event_bend(&event_buff[i])); + } else if (command == MIDI_ON_NOTE || command == MIDI_OFF_NOTE) { + needs_voice = FALSE; /* ignore note-offs */ + } else { + gprintf(ERROR, "Command 0x%x ignored\n", command); + needs_voice = FALSE; + } + if (needs_voice) { + if (last_voice != voice) { + fprintf(fp, " V%d", voice + 1); + last_voice = voice; + } + fprintf(fp, " N%d", (int)(getnext(i, last, now) - now)); + fprintf(fp, "\n"); + } + } + } +} + + +/**************************************************************************** +* write_pitch +* Inputs: +* FILE *fp: an open file +* int p: a pitch number +* Effect: write out the pitch name for a given number +****************************************************************************/ + +void write_pitch(FILE *fp, int p) +{ + static char *ptos[] = { + "C", "CS", "D", "EF", "E", "F", "FS", "G", + "GS", "A", "BF", "B" }; + /* avoid negative numbers: adagio can't express lowest octave: */ + while (p < 12) { + if (!fixed_octave) { + gprintf(ERROR, "%s%s%s", + "A low note was transposed up an octave\n", + "(Adagio cannot express the lowest MIDI octave).\n", + "This message will appear only once.\n"); + fixed_octave = TRUE; + } + p += 12; + } + fprintf(fp, "%s%d", ptos[p % 12], (p / 12) - 1); +} + +/********************************************************************** +* rec_final +* Inputs: +* boolean absflag: output absolute time of first note if TRUE +* Effect: +* Write recorded data to a file +**********************************************************************/ + +void rec_final(FILE *fp, boolean absflag) +{ + next->when = gettime(); + last = next; + if (debug_rec) gprintf(GDEBUG,"max_pile_up = %d, ", max_pile_up); + gprintf(TRANS,"%ld times and events recorded.\n", + (long) (last - event_buff)); + filter(last); + output(fp, last, absflag); + fclose(fp); + FREE(event_buff); + max_notes = -1; +} + +/**************************************************************************** +* rec_init +* Inputs: +* char *file: pointer to file name from command line (if any) +* boolean bender: TRUE if pitch bend should be enabled +* Outputs: +* return TRUE if initialization succeeds +* Effect: +* prepares module to record midi input +****************************************************************************/ + +/* ENOUGH_ROOM says if we have room for 10000 events + 10000 timestamps = + * 20000 note_struct's, then that's "enough room" for recording a sequence. + * If more ram is available, it won't be used. If less is available, we'll + * use as much as we can get, minus "SPACE_FOR_PLAY", which leaves a little + * bit of spare ram in case Moxc or stdio need to allocate some space. + * For DOS, we limit recording space to 64K. + */ +#ifdef DOS +#define ENOUGH_ROOM 64000L +#else +#define ENOUGH_ROOM (20000L * sizeof(union note_struct)) +#endif + + +boolean rec_init(boolean bender) +{ + size_t biggestChunk, spaceForRecord; + + debug_rec = cl_switch("debug"); + byteorder(); + pile_ups = 0; + max_pile_up = 0; + previous_time = (unsigned) -1L; /* this will force putting in initial timestamp */ + fixed_octave = FALSE; + + if (max_notes == -1) { /* allocate space 1st time rec_init called */ + biggestChunk = AVAILMEM; + if (biggestChunk <= SPACE_FOR_PLAY) { + /* not enough memory; give up */ + return(FALSE); + } + else { + spaceForRecord = + MIN((biggestChunk - SPACE_FOR_PLAY), ENOUGH_ROOM); + /* leave SPACE_FOR_PLAY contiguous bytes of memory */ + } + max_notes = spaceForRecord / sizeof(note_node); + /* gprintf(GDEBUG,"max_notes = %d\n", max_notes);*/ + event_buff = (note_type) MALLOC(spaceForRecord); + if (event_buff == NULL) { + /* should never happen */ + gprintf(FATAL, "Implementation error (record.c): getting memory."); + return FALSE; + } + } + next = event_buff; + last = event_buff + max_notes - 2; /* it is critical that last not point + * to the very last storage loc */ + midi_cont(bender); + return((boolean)(max_notes > 10)); + /* it would be silly to record with only room enough for 10 notes! */ +} + + +/**************************************************************************** +* rec_event +* Inputs: +* long time: the current time +* long data: midi data to record +* Outputs: +* returns FALSE if there is no more memory +* Effect: reads and stores any input +* Implementation: +* time stamps and midi events share the same buffer of 4-byte events +* save time at most once per call to rec_poll +* save time only if it changes +****************************************************************************/ + +boolean rec_event(long *data, time_type time) +{ + /* can't allow negative time because sign bit distinguishes + * data from time: */ + if (time < 0) time = 0; + + if (previous_time != time) { + next++->when = previous_time = time; + if (next >= last) goto overflow; + } + + next->when = *data; + next++->n[3] = MIDI_CMD_BIT; /* set tag bit */ + if (next >= last) goto overflow; + return TRUE; + +overflow: + next = last; /* last doesn't really point to last storage */ + gprintf(ERROR, "No more memory.\n"); + return FALSE; +} diff --git a/cmt/record.h b/cmt/record.h new file mode 100644 index 0000000..de1479d --- /dev/null +++ b/cmt/record.h @@ -0,0 +1,6 @@ +/* Copyright 1989 Carnegie Mellon University */ + +boolean rec_init(boolean bender); +boolean rec_event(long *data, time_type time); +void rec_final(FILE *fp, boolean absflag); +void write_pitch(FILE *fp, int p); diff --git a/cmt/seq.c b/cmt/seq.c new file mode 100644 index 0000000..de54e47 --- /dev/null +++ b/cmt/seq.c @@ -0,0 +1,1199 @@ +/* seq.c -- implement adagio scores as abstract data type */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 2-Apr-91 | JDW : further changes +* 16-Feb-92 | GWL : use reg_timebase in seq_play() +* 28-Apr-03 | DM : false->FALSE, true->TRUE, portability changes +* 19-May-03 | RBD : no longer assume seq->current remains untouched between +* | note inserts +*****************************************************************************/ + +#include "stdio.h" +#include "cext.h" +#include "userio.h" +#include "midicode.h" +#include "midifns.h" +#include "timebase.h" +#include "moxc.h" +#include "seq.h" +#include "string.h" + +extern int moxcdebug; +extern timebase_type default_base; + +boolean seq_print = FALSE; /* debugging print switch */ + +seq_type sequence; /* this is a global to be accessed by routines called + * from the sequence */ + +/* clock state: */ +time_type clock_ticksize; /* millisec per tick shifted 16 bits */ +boolean clock_running = FALSE; /* TRUE if clock is running */ +boolean external_midi_clock = FALSE; +boolean suppress_midi_clock = FALSE; + +private void insert_event(seq_type, register event_type); +private void process_event(seq_type); + +private char *chunk_alloc(seq_type seq, int size); +private void clock_tick(seq_type seq, time_type fraction); +private void ramp_event(seq_type seq, event_type event, unsigned int value, + unsigned int to_value, int increment, time_type step, int n); +/*private*/ void send_macro(register unsigned char *ptr, int voice, + short parameter[], int parm_num, int value, int nline); + +/* chunk_alloc -- allocate data for a sequence */ +/* + * NOTE: This assumes one chunk is already allocated. + * The first chunk holds shared sequence information in + * the info struct, and by convention this is always in + * the first chunk. + */ +private char *chunk_alloc(seq_type seq, int size) +{ + chunk_type chunk = seq->chunklist->u.info.last_chunk; + /* gprintf(TRANS, "chunk_alloc: seq %lx size %d\n", seq, size); */ + if (size & 1) size++; /* make it even */ + if (chunk->free + size >= CHUNK_SIZE) { + chunk_type new_chunk = chunk_create(FALSE); + if (!new_chunk) + { + gprintf(FATAL, "Out of memory while reading seq\n"); + return NULL; + } + /* add new_chunk to chunk chain */ + seq->chunklist->u.info.last_chunk = new_chunk; + chunk->next = new_chunk; + chunk = new_chunk; + } + chunk->free += size; + return &(chunk->u.data[chunk->free - size]); +} + + +/* chunk_create -- create a new chunk for seq data */ +/* + * If this is the first chunk, set first_flag to reserve + * space for the info structure. + */ +chunk_type chunk_create(boolean first_flag) +{ + chunk_type result = (chunk_type) memget(sizeof(chunk_node)); + if (result) { + result->next = NULL; + result->u.info.refcount = 1; /* pre-initialize for caller */ + result->free = 0; + if (first_flag) { + result->free = sizeof(struct info_struct); + result->u.info.last_chunk = result; + result->u.info.dictionary = NULL; + result->u.info.eventlist = NULL; + result->u.info.ctrlcount = 0; + result->u.info.notecount = 0; + result->u.info.duration = 0; + result->u.info.used_mask = 0; + } + } + /* gprintf(TRANS, "chunk_create: got %lx (size %d)\n", */ + /* result, sizeof(chunk_node)); */ + return result; +} + +/* clock_tick -- advance the clock and send a tick */ +/**/ +private void clock_tick(seq_type seq, time_type fraction) +{ + int delay; + fraction += clock_ticksize; + delay = fraction >> 16; + fraction &= 0xFFFF; + if (seq->runflag && clock_ticksize && seq->note_enable) { + midi_clock(); + cause((delay_type)delay, clock_tick, seq, fraction); + } else { + clock_running = FALSE; + midi_stop(); + midi_clock(); /* stop takes effect on next clock, so provide one */ + } +} + +private void cycle(seq_type seq) +{ + seq_reset(seq); + seq_play(seq); +} + + + +/**************************************************************************** +* event_create +* Inputs: +* seq_type seq: the seq to hold the event +* int size: the size of the event in bytes +* time_type etime: the time of the event +* int eline: the line number of the event +* Returns: +* event_type: a new event structure or +* NULL if there is not enough memory left +* Effect: +* allocates memory from the chunk, then heap as needed +* Implementation: +* to reduce the per block storage overhead, we allocate memory in +* large chunks and do our own allocation. Allocate from first +* chunk first. If full, allocate a new chunk. +* WARNING: this implementation assumes that individual events are never freed!! +****************************************************************************/ + +private event_type event_create(seq, size, etime, eline) + seq_type seq; + int size; + time_type etime; + int eline; +{ + event_type result = (event_type) chunk_alloc(seq, size); + if (result) { + result->ntime = etime; + result->nline = eline; + /* since we know the time, we can insert now: */ + insert_event(seq, result); + seq_duration(seq) = MAX(seq_duration(seq), etime); + } + return result; +} + + +/* insert_call -- add a call event to the seq */ +/**/ +event_type insert_call(seq, ctime, cline, voice, addr, value, n) + seq_type seq; + time_type ctime; + int cline; + int voice; + int (*addr)(); + long value[SEQ_MAX_PARMS]; + int n; +{ + int i; + register event_type event = event_create(seq, callsize, ctime, cline); + if (seq_print) { + gprintf(TRANS, + "call(%lx): time %ld, line %d, voice %d, fn %lx,\n\tvalues:", + event, ctime, cline, voice, addr); + for (i = 0; i < n; i++) gprintf(TRANS, " %ld", value[i]); + gprintf(TRANS, "\n"); + } + if (event) { + seq_used_mask(seq) |= 1 << (voice - 1); + event->nvoice = ctrl_voice(ESC_CTRL, voice); + event->value = CALL_VALUE; + event->u.call.routine = addr; + /* save the arguments */ + for (i = 0; i < n; i++) event->u.call.args.a[i] = value[i]; + seq_ctrlcount(seq)++; + } + return event; +} + + +/* insert_clock -- add a clock cmd to the seq */ +/**/ +event_type insert_clock(seq, ctime, cline, ticksize) + seq_type seq; + time_type ctime; + int cline; + time_type ticksize; +{ + register event_type event = event_create(seq, clocksize, ctime, cline); + + if (seq_print) { + gprintf(TRANS, "clock(%lx): time %ld, line %d\n", event, ctime, cline); + } + if (event) { + event->nvoice = ctrl_voice(ESC_CTRL, 1); + event->value = CLOCK_VALUE; + event->u.clock.ticksize = ticksize; + seq_ctrlcount(seq)++; + } + return event; +} + + +/* insert_ctrl -- add a control to the seq */ +/**/ +event_type insert_ctrl(seq, ctime, cline, ctrl, voice, value) + seq_type seq; + time_type ctime; + int cline; + int ctrl; + int voice; + int value; +{ + register event_type event = event_create(seq, ctrlsize, ctime, cline); + if (seq_print) { + gprintf(TRANS, + "ctrl(%lx): time %ld, line %d, ctrl %d, voice %d, value %d\n", + event, ctime, cline, ctrl, voice, value); + } + if (event) { + seq_used_mask(seq) |= 1 << (voice - 1); + event->nvoice = ctrl_voice(ctrl, voice); + event->value = value; + seq_ctrlcount(seq)++; + } + return event; +} + + +/* insert_ctrlramp -- add a control ramp event to the seq */ +/**/ +event_type insert_ctrlramp(seq, rtime, rline, voice, step, dur, ctrl, v1, v2) + seq_type seq; + time_type rtime; + int rline; + int voice; + time_type step; + time_type dur; + int ctrl; + int v1, v2; +{ + register event_type event = event_create(seq, ctrlrampsize, rtime, rline); + if (seq_print) { + gprintf(TRANS, + "ctrlramp(%lx): time %ld, line %d, step %ld, dur %ld, ctrl %d, voice %d\n", + event, rtime, rline, step, dur, ctrl, voice); + gprintf(TRANS, "\tfrom %d to %d\n", v1, v2); + } + + if (event) { + seq_used_mask(seq) |= 1 << (voice - 1); + event->nvoice = ctrl_voice(ESC_CTRL, voice); + event->value = CTRLRAMP_VALUE; + if (dur <= 0) dur = 1L; /* don't allow zero duration */ + event->u.ramp.dur = dur; + event->u.ramp.ctrl = ctrl; + if (step <= 0) step = 1; /* don't allow zero step size */ + event->u.ramp.step = (short) step; + event->u.ramp.u.ctrl.from_value = v1; + event->u.ramp.u.ctrl.to_value = v2; + seq_ctrlcount(seq)++; + seq_duration(seq) = MAX(seq_duration(seq), rtime + dur); + } + return event; +} + + +/* insert_def -- add a definition to the dictionary */ +/**/ +def_type insert_def(seq, symbol, definition, deflen) + seq_type seq; + char *symbol; + unsigned char *definition; + int deflen; +{ + int i; + def_type defn = (def_type) chunk_alloc(seq, sizeof(def_node)); + defn->symbol = chunk_alloc(seq, strlen(symbol) + 1); + defn->definition = (unsigned char *) chunk_alloc(seq, deflen); + strcpy(defn->symbol, symbol); + for (i = 0; i < deflen; i++) { + defn->definition[i] = definition[i]; + } + defn->next = seq_dictionary(seq); + seq_dictionary(seq) = defn; + if (seq_print) { + gprintf(TRANS, "def(%ld): symbol %s defn \n", defn, symbol); + for (i = 0; i < deflen; i++) gprintf(TRANS, "%x", definition[i]); + gprintf(TRANS, "\n"); + } + return defn; +} + + +/* insert_deframp -- add a def ramp event to the seq */ +/**/ +event_type insert_deframp(seq, rtime, rline, voice, step, dur, + def, nparms, parms, parm_num, to_value) + seq_type seq; + time_type rtime; + int rline; + int voice; + time_type step; + time_type dur; + def_type def; + int nparms; /* number of parameters for macro */ + short parms[]; /* actual parameter vector */ + int parm_num; /* which of the actual parameters to ramp */ + int to_value; /* final destination of ramp */ +{ + register event_type event = event_create(seq, deframpsize, rtime, rline); + if (seq_print) { + int i; + gprintf(TRANS, + "deframp(%ld): time %ld, line %d, voice %d, step %ld, dur %ld\n", + event, rtime, rline, voice, step, dur); + gprintf(TRANS, "def %ld, parms"); + for (i = 0; i < nparms; i++) gprintf(TRANS, " %d", parms[i]); + gprintf(TRANS, "parm_num %d to %d\n", parm_num, to_value); + } + if (event) { + int i; + seq_used_mask(seq) |= 1 << (voice - 1); + event->nvoice = ctrl_voice(ESC_CTRL, voice); + event->value = DEFRAMP_VALUE; + if (dur <= 0) dur = 1L; /* don't allow zero duration */ + event->u.ramp.dur = dur; + event->u.ramp.ctrl = 0; + if (step <= 0) step = 1; /* don't allow zero step size */ + event->u.ramp.step = (short) step; + event->u.ramp.u.def.definition = def->definition; + for (i = 0; i < nmacroparms; i++) { + event->u.ramp.u.def.parameter[i] = (i < nparms ? parms[i] : 0); + } + event->u.ramp.u.def.parm_num = parm_num; + event->u.ramp.u.def.to_value = to_value; + seq_ctrlcount(seq)++; + seq_duration(seq) = MAX(seq_duration(seq), rtime + dur); + } + return event; +} + + +/**************************************************************************** +* insert_event +* Inputs: +* seq_type seq: where to put the event +* event_type event: the event to insert +* Effect: +* inserts event into the event list +* NOTE: it is inserted *after* previously inserted events with the same time +* Implementation: +* adagio files often contain many independent voices. Although each voice +* consists of events in sequence, the voices need not be inter-twined in +* the input file. Rather, all the events of voice 1 appear followed by all +* the events of voice 2, and so forth. As phase one merges these event +* sequences, it must make many passes over an increasingly long list of +* events: expensive if we always start from the beginning of the list! +* we can exploit the fact that each voice is sequential by starting the +* search for the proper point of insertion at the last event inserted. +* the variable "last_event" is used to remember this hint. We could +* also snapshot "last_event" in "ref_event" when a !tempo or !rate +* command occurs as another hint, but we don't. +****************************************************************************/ + +private void insert_event(seq, event) + seq_type seq; + register event_type event; +{ + event_type *evlptr = &(seq_eventlist(seq)); + if ((*evlptr == NULL) || + (event->ntime < (*evlptr)->ntime)) { + /* insert at the head of the list */ + event->next = *evlptr; + *evlptr = event; + seq->current = event; + } else { + /* insert somewhere after the head of the list + * do not assume: current is not NULL. Although we always leave + * it set, the client may access the sequence before the next + * insert. + */ + register event_type previous; + register event_type insert_before; + + if (!seq->current) { + seq->current = seq_eventlist(seq); + } + if (event->ntime >= seq->current->ntime) { + /* insertion point is after current */ + previous = seq->current; + insert_before = previous->next; + } else { + /* insertion point is before current; start at beginning */ + /* assume: not inserting at very head of list; that would + * have been taken care of above */ + previous = seq_events(seq); + insert_before = previous->next; + } + + while ((insert_before != NULL) && + (event->ntime >= insert_before->ntime)) { + previous = insert_before; + insert_before = insert_before->next; + } + previous->next = event; + event->next = insert_before; + seq->current = event; + } +} + + +/* insert_macctrl -- add a control to the seq */ +/**/ +event_type insert_macctrl(seq, ctime, cline, ctrl, voice, value) + seq_type seq; + time_type ctime; + int cline; + int ctrl; + int voice; + int value; +{ + register event_type event = event_create(seq, macctrlsize, ctime, cline); + if (seq_print) { + gprintf(TRANS, + "macctrl(%lx): time %ld, line %d, ctrl %d, voice %d, value %d\n", + event, ctime, cline, ctrl, voice, value); + } + if (event) { + seq_used_mask(seq) |= 1 << (voice - 1); + event->nvoice = ctrl_voice(ESC_CTRL, voice); + event->value = MACCTRL_VALUE; + event->u.macctrl.ctrl_number = ctrl; + event->u.macctrl.value = value; + seq_ctrlcount(seq)++; + } + return event; +} + + +/* insert_macro -- insert a macro call seq */ +/**/ +event_type insert_macro(seq, ctime, cline, def, voice, nparms, parms) + seq_type seq; + time_type ctime; + int cline; + def_type def; + int voice; + int nparms; + short *parms; +{ + register event_type event = event_create(seq, macrosize, ctime, cline); + if (seq_print) { + int i; + gprintf(TRANS, + "macro(%lx): time %ld, line %d, def %ld, voice %d, parms", + event, ctime, cline, def, voice); + for (i = 0; i < nparms; i++) gprintf(TRANS, " %d", parms[i]); + gprintf(TRANS, "\n"); + } + if (event) { + seq_used_mask(seq) |= 1 << (voice - 1); + event->nvoice = ctrl_voice(ESC_CTRL, voice); + event->value = MACRO_VALUE; + event->u.macro.definition = def->definition; + while (nparms-- > 0) { + event->u.macro.parameter[nparms] = parms[nparms]; + } + seq_ctrlcount(seq)++; + } + return event; +} + + +/* insert_note -- add a note to the seq */ +/**/ +event_type insert_note(seq, ntime, nline, voice, pitch, dur, loud) + seq_type seq; + time_type ntime; + int nline; + int voice; + int pitch; + time_type dur; + int loud; +{ + register event_type event = event_create(seq, notesize, ntime, nline); + + if (seq_print) { + gprintf(TRANS, + "note(%lx): time %ld, line %d, dur %ld, pitch %d, voice %d, loudness %d\n", + event, ntime, nline, dur, pitch, voice, loud); + } + + if (event) { + seq_used_mask(seq) |= 1 << (voice - 1); + event->nvoice = voice - 1; + event->value = pitch; + event->u.note.ndur = (dur << 8) + loud; + seq_notecount(seq)++; + seq_duration(seq) = MAX(seq_duration(seq), ntime + dur); + } + return event; +} + + +/* insert_seti -- add a seti event to the seq */ +/**/ +event_type insert_seti(seq, stime, sline, voice, addr, value) + seq_type seq; + time_type stime; + int sline; + int voice; + int *addr; + int value; +{ + register event_type event = event_create(seq, setisize, stime, sline); + if (seq_print) { + gprintf(TRANS, + "seti(%ld): time %ld, line %d, voice %d, addr %ld, value %d\n", + event, stime, sline, voice, addr, value); + } + if (event) { + event->nvoice = ctrl_voice(ESC_CTRL, voice); + event->value = SETI_VALUE; + event->u.seti.int_to_set = addr; + event->u.seti.value = value; + seq_ctrlcount(seq)++; + } + return event; +} + + +/* noop -- just returns, the default stopfunc for sequences */ +/**/ +void noop(seq_type seq) {} + + +private void process_event(seq) + seq_type seq; +{ + register event_type event; + if (!seq->runflag) return; + while ((event = seq->current) && (event->ntime <= virttime)) { + int voice; + /* process all current (and earlier) events */ + if (is_note(event)) { /*** play a note or rest ***/ + /* if this note is not a rest, play it and schedule an off event */ + if (event->value != NO_PITCH && + (seq_channel_mask(seq) & + (1 << ((voice = vc_voice(event->nvoice)) - 1)))) { + seq_noteon(seq, voice, event->value, + (int) event->u.note.ndur & 0xFF); + if (debug) { + gprintf(TRANS, "play pitch %d at %ld\n", + event->value, event->ntime); + } + seq_cause_noteoff(seq, (event->u.note.ndur) >> 8, + voice, event->value); + + } + } else { /*** send a control command ***/ + int n; + time_type step; + int delta; + long increment; + int voice = vc_voice(event->nvoice); + ulong enabled = seq_channel_mask(seq) & (1 << (voice - 1)); + + switch (vc_ctrl(event->nvoice)) { + case PSWITCH_CTRL: + if (!enabled) break; + seq_midi_ctrl(seq, voice, PORTASWITCH, event->value); + break; + case MODWHEEL_CTRL: + if (!enabled) break; + seq_midi_ctrl(seq, voice, MODWHEEL, event->value); + break; + case TOUCH_CTRL: + if (!enabled) break; + seq_midi_touch(seq, voice, event->value); + break; + case VOLUME_CTRL: + if (!enabled) break; + seq_midi_ctrl(seq, voice, VOLUME, event->value); + break; + case BEND_CTRL: + if (!enabled) break; + seq_midi_bend(seq, voice, (event->value << 6)); + break; + case PROGRAM_CTRL: + if (!enabled) break; + seq_midi_program(seq, voice, event->value + 1); + break; + case ESC_CTRL: + switch (event->value) { + case CALL_VALUE: + sequence = seq; + (*(event->u.call.routine))(event->u.call.args); + break; + case CLOCK_VALUE: + clock_ticksize = event->u.clock.ticksize; + if (!clock_running && !suppress_midi_clock && + !external_midi_clock) { + clock_running = TRUE; + midi_start(); + clock_tick(seq, 0L); + } + break; + case MACCTRL_VALUE: + if (!enabled) break; + seq_midi_ctrl(seq, voice, event->u.macctrl.ctrl_number, + event->u.macctrl.value); + break; + case MACRO_VALUE: { + if (!enabled) break; + send_macro(event->u.macro.definition, voice, + event->u.macro.parameter, -1, 0, + event->nline); + break; + } + case CTRLRAMP_VALUE: + case DEFRAMP_VALUE: { + int from, to; + if (!enabled) break; + + step = event->u.ramp.step; + if (event->value == CTRLRAMP_VALUE) { + from = event->u.ramp.u.ctrl.from_value; + to = event->u.ramp.u.ctrl.to_value; + } else { + from = event->u.ramp.u.def.parameter[ + event->u.ramp.u.def.parm_num]; + to = event->u.ramp.u.def.to_value; + } + delta = to - from; + increment = delta; + if (delta < 0) delta = -delta; + /* Note: Step is always non-zero */ + n = event->u.ramp.dur / step; + increment = (increment << 8) / n; + ramp_event(seq, event, from << 8, to << 8, + (int) increment, step, n); + seq->noteoff_count++; + break; + } + case SETI_VALUE: + *(event->u.seti.int_to_set) = event->u.seti.value; + break; + default: + gprintf(TRANS, "unexpected ESC_CTRL value\n"); + break; + } + break; + default: + gprintf(TRANS, "unexpected seq data\n"); + break; + } + } + seq->current = event->next; + } + if (seq->current) { + cause((delay_type)(event->ntime - virttime), process_event, seq); + } else if (seq->noteoff_count == 0 && seq->note_enable) { + /* if we're just advancing to a start point, note_enable will be + * FALSE and this won't get called: + */ + if (seq->stopfunc) { + (*(seq->stopfunc))(seq); + } + } +} + + +/* ramp_event -- generate a ramp */ +/**/ +private void ramp_event(seq, event, value, to_value, increment, step, n) + seq_type seq; + register event_type event; + unsigned int value; + unsigned int to_value; + int increment; + time_type step; + int n; +{ + if (seq->runflag) { + int voice = vc_voice(event->nvoice); +/* printf("ramp_event: value %d to_value %d increment %d step %d n %d time %d\n", + value, to_value, increment, step, n, virttime); */ + if (n == 0) value = to_value; + else { + causepri((delay_type)step, 5, ramp_event, seq, event, value + increment, + to_value, increment, step, n - 1); + } + if (event->value == CTRLRAMP_VALUE) { + int ctrl = event->u.ramp.ctrl; + if (ctrl == -TOUCH_CTRL) midi_touch(voice, value >> 8); + else if (ctrl == -BEND_CTRL) midi_bend(voice, value >> 2); + else midi_ctrl(voice, ctrl, value >> 8); + } else { /* must be DEFRAMP_VALUE */ + send_macro(event->u.ramp.u.def.definition, + vc_voice(event->nvoice), + event->u.ramp.u.def.parameter, + event->u.ramp.u.def.parm_num, value >> 8, + event->nline); + } + if (n == 0) seq_end_event(seq); + } +} + + +/* report_enabled_channels -- print out concise listing of channels */ +/* + * to fit on one line, write out ranges, e.g. 1-5 9-11 + */ +void report_enabled_channels(seq) + seq_type seq; +{ + ulong mask = seq_channel_mask(seq); + int i, range_open_at = 0; + + for (i = 1; i <= MAX_CHANNELS; i++) { + if (!range_open_at && (mask & 1)) { + gprintf(TRANS, " %d", i); + range_open_at = i; + } else if (range_open_at && !(mask & 1)) { + if (i > (range_open_at + 1)) { + gprintf(TRANS, "-%d", i - 1); + } + range_open_at = 0; /* FALSE */ + } + mask = mask >> 1; + } + if (range_open_at) gprintf(TRANS, "-%d", MAX_CHANNELS); +} + + +/* send_macro -- instantiate macro and send it */ +/* + * note: to support ramping, "value" is used in place of + * parameter["parm_num"] + */ +/*private*/ +void send_macro(ptr, voice, parameter, parm_num, value, nline) + register unsigned char *ptr; + int voice; + short parameter[]; + int parm_num; + int value; + int nline; +{ + register unsigned char code, *loc; + while ((code = *ptr++)) { + loc = ptr + *ptr; + ptr++; + if (code <= nmacroparms) { + code--; + *loc = (code == parm_num ? value : parameter[code]) & 0x7f; + } else if (code == nmacroparms + 1) { + /* take old high order bits and OR in 4 voice bits */ + *loc = (*loc & 0xF0) | ((voice - 1) & 0xF); + } else { + code -= (nmacroparms + 2); + *loc = ((code == parm_num ? value : parameter[code]) >> 7) & 0x7F; + } + } + if (ptr[1] == MIDI_SYSEX) { + midi_exclusive(ptr + 1); + } else { + /* make sure user didn't try to send more than 3 bytes. This test + * could be done at sequence read time, but it's tricky because the + * first byte could be a parameter, so in general you need to + * plug the actual parameters into the message and then do the test. + * Currently, this is the only place parameters are plugged in. + */ + if (*ptr > 3) { + gprintf(ERROR, + "Non-sysex macro longer than 3 bytes ignored, line %d.\n", + nline); + } else { + midi_write((int) *ptr, MIDI_PORT(voice), ptr[1], ptr[2], ptr[3]); + } + } +} + + +/* seq_alloc -- a utility function to allocate a seq struct */ +/**/ +seq_type seq_alloc() +{ + seq_type seq; + seq = (seq_type) memget(sizeof(seq_node)); + return seq; +} + + +/* seq_at_end -- set the function to be called at sequence end */ +/**/ +void seq_at_end(seq, fn) + seq_type seq; + void (*fn)(seq_type); +{ + if (!fn) fn = noop; + seq->stopfunc = fn; +} + + +/* seq_cause_noteoff_meth -- turn off a note in the future */ +/**/ +void seq_cause_noteoff_meth(seq_type seq, time_type delay, int voice, int pitch) +{ + if (seq->note_enable) { + pitch += seq->transpose; + while (pitch < 0) pitch += 12; + while (pitch > 127) pitch -= 12; + seq->noteoff_count++; + causepri((delay_type) delay, 10, seq->noteoff_fn, + seq, voice, pitch); + } +} + +/* seq_copy -- copy a sequence, share the eventlist */ +/**/ +seq_type seq_copy(from_seq) + seq_type from_seq; +{ + register seq_type seq = seq_init(seq_alloc(), FALSE); + if (!seq) return NULL; + seq->chunklist = from_seq->chunklist; + seq->current = seq_events(seq); + seq->chunklist->u.info.refcount++; + seq->transpose = from_seq->transpose; + seq->loudness = from_seq->loudness; + seq->rate = from_seq->rate; + seq->paused = from_seq->paused; + seq->noteoff_count = 0; + return seq; +} + + +/* seq_create -- create a seq structure and an initial event chunk */ +/**/ +seq_type seq_create() +{ + return seq_init(seq_alloc(), TRUE); +} + + +/* seq_cycle -- set parameters for cycling a sequence */ +/**/ +void seq_cycle(seq_type seq, boolean flag, time_type dur) +{ + seq->cycleflag = flag; + seq->cycledur = dur; +} + + +/* seq_end_event -- call this when an score-generated event ends */ +/* + * Assumes that noteoff_count was incremented when event started. + */ +void seq_end_event(seq) + seq_type seq; +{ + /*gprintf(TRANS, "nd");*/ + seq->noteoff_count--; + if (seq->current == NULL /* finished seq */ && + seq->noteoff_count == 0 /* finished noteoff's */ && + seq->runflag /* we've not been stopped */) { + if (seq->cycleflag) { + cause((delay_type) (seq->cycledur - virttime), cycle, seq); + } else if (seq->stopfunc) { + (*(seq->stopfunc))(seq); + } + } +} + + + +/**************************************************************************** +* seq_free_meth +* Input: a seq_type +* Effect: +* frees storage occupied by a seq +****************************************************************************/ + +private void seq_free_meth(seq) + seq_type seq; +{ + seq_free_chunks(seq); + if (seq->timebase) timebase_free(seq->timebase); + memfree((void *) seq, sizeof(seq_node)); +} + + +/* seq_free_chunks -- free storage for note list */ +/* + * NOTE: in its original form, this routine was perhaps more readable, + * but would not compile under Microsoft C V7.00 due to a compiler bug. + * I rewrote the code until the bug disappeared, hopefully without + * changing the semantics! If you change this code, make sure it still + * compiles under Microsoft C. + * + * This module frees chunks from a seq_type in preparation for freeing + * the seq_type itself. Reference counts are checked and chunks are + * only freed when the last reference is removed. + */ +public void seq_free_chunks(seq) + seq_type seq; +{ + chunk_type tail; + chunk_type head; + + head = seq->chunklist; + if (((head->u.info.refcount)--) != 0) return; + + while (head != NULL) { + tail = head->next; + memfree((void *) head, sizeof(chunk_node)); + head = tail; + seq->chunklist = head; + } +} + + +seq_type seq_init(seq, create_chunk) + seq_type seq; + int create_chunk; +{ + if (!seq || !(seq->timebase = timebase_create(50))) { + return NULL; + } + seq->chunklist = NULL; + if (create_chunk) { + seq->chunklist = chunk_create(TRUE); + if (!seq->chunklist) { + seq_free(seq); + return NULL; + } + } + seq->cause_noteoff_fn = seq_cause_noteoff_meth; + seq->midi_bend_fn = seq_midi_bend_meth; + seq->midi_ctrl_fn = seq_midi_ctrl_meth; + seq->midi_program_fn = seq_midi_program_meth; + seq->midi_touch_fn = seq_midi_touch_meth; + seq->noteoff_fn = seq_noteoff_meth; + seq->noteon_fn = seq_noteon_meth; + seq->free_fn = seq_free_meth; + seq->reset_fn = seq_reset_meth; + + seq->current = NULL; + seq->transpose = 0; + seq->loudness = 0; + seq->cycleflag = FALSE; + seq->cycledur = 0L; + seq->rate = 256L; + seq->paused = FALSE; + seq->stopfunc = noop; + seq->channel_mask = 0xFFFFFFFFL; + seq->runflag = seq->note_enable = FALSE; + return seq; +} + + +/* seq_midi_bend_meth -- send a midi bend */ +/**/ +void seq_midi_bend_meth(seq_type seq, int voice, int value) +{ + midi_bend(voice, value); +} + + +/* seq_midi_ctrl_meth -- send a midi ctrl change */ +/**/ +void seq_midi_ctrl_meth(seq_type seq, int voice, int ctrl, int value) +{ + midi_ctrl(voice, ctrl, value); +} + + +/* seq_midi_program_meth -- send a midi program change */ +/**/ +void seq_midi_program_meth(seq_type seq, int voice, int prog) +{ + midi_bend(voice, prog); +} + + +/* seq_midi_touch_meth -- send a midi touch */ +/**/ +void seq_midi_touch_meth(seq_type seq, int voice, int value) +{ + midi_touch(voice, value); +} + + +/* seq_noteoff_meth -- turn a seq note off */ +/**/ +void seq_noteoff_meth(seq_type seq, int voice, int pitch) +{ + midi_note(voice, pitch, 0); + /*gprintf(TRANS, "_e");*/ + seq_end_event(seq); +} + + +/* seq_noteon_meth -- play a note with transformations */ +/**/ +void seq_noteon_meth(seq_type seq, int chan, int pitch, int vel) +{ + if (seq->note_enable) { + pitch += seq->transpose; + while (pitch < 0) pitch += 12; + while (pitch > 127) pitch -= 12; + + vel += seq->loudness; + if (vel <= 0) vel = 1; + else if (vel > 127) vel = 127; + + midi_note(chan, pitch, vel); + } +} + + +/* seq_pause -- stop playing momentarily or resume playing */ +/**/ +time_type seq_pause(seq_type seq, boolean flag) +{ + if (!seq->paused && flag) { + seq->paused = TRUE; + seq->rate = seq->timebase->rate; + set_rate(seq->timebase, STOPRATE); + } else if (seq->paused && !flag) { + seq_play(seq); + } + return (time_type) seq->timebase->virt_base; +} + + +/* seq_play -- play a sequence from the current event forward */ +/**/ +void seq_play(seq) + seq_type seq; +{ + timebase_type prev_timebase = timebase; + register timebase_type reg_timebase = seq->timebase; + + if (!seq->runflag) { + seq_reset(seq); + } + if (!seq->paused) return; + eventtime = gettime(); + + /* assume that virt_base is correct virtual time as the result + of seq_start_time or seq_reset + */ + timebase = reg_timebase; + virttime = reg_timebase->virt_base; + /* note that set_rate will set reg_timebase->real_base to eventtime */ + set_rate(reg_timebase, seq->rate); + seq->paused = FALSE; /* in case the score had been paused; note that + seq_pause() has no effect if paused is TRUE */ + seq->runflag = TRUE; + seq->note_enable = TRUE; + + /* restore previous timebase */ + timebase_use(prev_timebase); +} + + +/* seq_reset_meth -- reset a sequence to start back at the first event */ +/**/ +void seq_reset_meth(seq_type seq) +{ + timebase_type old_timebase = timebase; + + if (seq->runflag) { + /* maybe this seq is already reset, and process_event is + * already scheduled. If so, don't schedule another one. + */ + if ((seq->timebase->virt_base == 0) && + (seq->timebase->rate == STOPRATE)) { + /* in case the reader just iterated through the list without + * cause'ing events, reset the event list + */ + seq->current = seq_events(seq); + return; + } + /* Otherwise, the seq is running, so stop it. */ + seq_stop(seq); + } + + timebase_use(seq->timebase); + set_rate(seq->timebase, STOPRATE); + set_virttime(seq->timebase, 0L); + seq->current = seq_events(seq); + seq->noteoff_count = 0L; + seq->runflag = TRUE; + seq->paused = TRUE; + if (seq->current) + cause((delay_type)(seq->current->ntime - virttime), process_event, seq); + timebase_use(old_timebase); +} + + +/* seq_set_loudness -- set the loudness offset of a sequence */ +/**/ +void seq_set_loudness(seq, loud) + seq_type seq; + int loud; +{ + seq->loudness = loud; +} + +/* seq_set_rate -- set the rate of a sequence */ +/**/ +void seq_set_rate(seq, rate) + seq_type seq; + time_type rate; +{ + seq->rate = rate; + if (!seq->paused) set_rate(seq->timebase, rate); +} + + +/* seq_set_transpose -- set the sequence transposition */ +/**/ +void seq_set_transpose(seq, trans) + seq_type seq; + int trans; +{ + seq->transpose = trans; +} + + +/* seq_start_time -- set the current pointer so the sequence starts here */ +/**/ +void seq_start_time(seq, start_time) + seq_type seq; + time_type start_time; +{ + timebase_type prev_timebase = timebase; + if (!seq->runflag) { + seq_reset(seq); + } + if (real_to_virt(seq->timebase, eventtime) > start_time) { + seq_reset(seq); + } + timebase_use(seq->timebase); + seq->note_enable = FALSE; + /* prime the pump */ + set_rate(timebase, STOPRATE); + set_virttime(timebase, start_time); + catchup(); + seq->note_enable = TRUE; + seq->paused = TRUE; + /* restore previous timebase */ + timebase_use(prev_timebase); +} + + +/* seq_stop -- stop a sequence, clear out all pending events */ +/**/ +void seq_stop(seq) + seq_type seq; +{ + timebase_type prev_timebase = timebase; + + if (seq->runflag) { + if (moxcdebug) + gprintf(TRANS, "seq_reset swap from timebase 0x%x to 0x%x\n", + timebase, seq->timebase); + timebase = seq->timebase; + seq->runflag = FALSE; + set_rate(timebase, STOPRATE); + set_virttime(timebase, MAXTIME); + catchup(); + } + timebase_use(prev_timebase); +} diff --git a/cmt/seq.h b/cmt/seq.h new file mode 100644 index 0000000..4177d2c --- /dev/null +++ b/cmt/seq.h @@ -0,0 +1,295 @@ +/* seq.h -- definitions for seq, the MIDI Toolkit sequence data type */ + +#define minpitch 0 +#define maxpitch 127 +#define NO_PITCH (maxpitch+1) +#define minprogram 1 +#define maxprogram 128 + +/* keep these two lines in sync */ +#define nmacroparms 4 +#define parm_expected_error "Parameter number [1-4] expected" + +#define seq_dflt_loud 127 +#define seq_dflt_voice 1 +#define seq_dflt_pitch 60 + +struct clock_struct { + time_type ticksize; +}; + + +struct ctrlramp_struct { + unsigned char from_value; + unsigned char to_value; +}; + + +struct deframp_struct { + unsigned char *definition; + short parameter[nmacroparms]; + unsigned char parm_num; + short to_value; +}; + + +struct macctrl_struct { + unsigned char ctrl_number; + unsigned char value; +}; + + +struct macro_struct { + unsigned char *definition; + short parameter[nmacroparms]; +}; + + +struct note_struct { + long ndur; /* duration */ + /* char nloud; loudness (MIDI velocity) now stored as low order byte + * of ndur + */ +}; + + +struct ramp_struct { + time_type dur; + short ctrl; /* encode pitch bend and after touch as negative */ + short step; + union { + struct ctrlramp_struct ctrl; + struct deframp_struct def; + } u; +}; + + +struct seti_struct { + int *int_to_set; + int value; +}; + + +#define SEQ_MAX_PARMS 8 +struct cause_struct { + int (*routine)(); + /* make a structure so we can copy by value */ + struct seq_arg_struct { + long a[SEQ_MAX_PARMS]; + } args; +}; + + +typedef struct event_struct { + struct event_struct *next; + time_type ntime; /* start time */ + short nline; /* line number from source code */ + unsigned char nvoice; /* adagio voice (MIDI Channel) + * if this is a control change, high order 4 bits + * contain the control number, otherwise high order + * 4 bits are 0 (see is_note macro below) + */ + unsigned char value; + /* this is a note pitch or a control value. It goes + * here rather than in the union to achieve word + * alignment (!). Also, value is used for extra commands + * such as call, seti, setv. + */ + union { + struct cause_struct call; + struct clock_struct clock; + struct macctrl_struct macctrl; + struct macro_struct macro; + struct note_struct note; + struct ramp_struct ramp; + struct seti_struct seti; + } u; +} event_node, *event_type; + +#define PSWITCH_CTRL 1 +#define MODWHEEL_CTRL 2 +#define TOUCH_CTRL 3 +#define VOLUME_CTRL 4 +#define BEND_CTRL 5 +#define PROGRAM_CTRL 6 +#define ESC_CTRL 7 + +#define CALL_VALUE 0 +#define CLOCK_VALUE 1 +#define MACCTRL_VALUE 2 +#define MACRO_VALUE 3 +#define CTRLRAMP_VALUE 4 +#define DEFRAMP_VALUE 5 +#define SETI_VALUE 6 + +#define commonsize (sizeof(struct event_struct) - sizeof(struct cause_struct)) +#define rampcommon (sizeof(struct ramp_struct) - sizeof(struct deframp_struct)) +#define ctrlsize commonsize +#define callsize (commonsize + sizeof(struct cause_struct)) +#define clocksize (commonsize + sizeof(struct clock_struct)) +#define ctrlrampsize (commonsize + rampcommon + sizeof(struct ctrlramp_struct)) +#define deframpsize (commonsize + sizeof(struct ramp_struct)) +#define macctrlsize (commonsize + sizeof(struct macctrl_struct)) +#define macrosize (commonsize + sizeof(struct macro_struct)) +#define notesize (commonsize + sizeof(struct note_struct)) +#define setisize (commonsize + sizeof(struct seti_struct)) +#define ctrl_voice(c, v) (((c) << 5) + ((v) - 1)) +#define vc_ctrl(v) ((v) >> 5) +#define vc_voice(v) (((v) & 0x1F) + 1) + +#define is_note(n) (((n)->nvoice & 0xE0) == 0) + +#define CHUNK_SIZE 2000 + +typedef struct def_struct { + struct def_struct *next; + char *symbol; + unsigned char *definition; +} def_node, *def_type; + + +typedef struct chunk_struct { + struct chunk_struct *next; + short free; + union { + char data[CHUNK_SIZE]; + struct info_struct { + short refcount; + struct chunk_struct *last_chunk; /* where to allocate memory */ + def_type dictionary; /* macro defns, routine addresses */ + event_type eventlist; /* first event in sequence */ + ulong used_mask; /* tells what channels are actually present */ + long ctrlcount; + long notecount; + time_type duration; /* time of the last event+dur in score */ + } info; + } u; +} chunk_node, *chunk_type; + + +typedef struct seq_struct { + void (*cause_noteoff_fn)(struct seq_struct *, time_type, int, int); + void (*midi_bend_fn)(struct seq_struct * seq, int voice, int value); + void (*midi_ctrl_fn)(struct seq_struct * seq, int voice, int ctrl, int value); + void (*midi_program_fn)(struct seq_struct * seq, int voice, int prog); + void (*midi_touch_fn)(struct seq_struct * seq, int voice, int value); + void (*noteoff_fn)(struct seq_struct * seq, int voice, int pitch); + void (*noteon_fn)(struct seq_struct * seq, int chan, int pitch, int vel); + void (*free_fn)(struct seq_struct * seq); + void (*reset_fn)(struct seq_struct * seq); + void (*stopfunc)(struct seq_struct *); + chunk_type chunklist; + /* event_type eventlist; + seq->eventlist is now seq->eventlist->chunklist */ + event_type current; + boolean runflag; /* normally true, set to false as a flag for + * processes started by score to terminate */ + boolean note_enable; /* normally true, set to false as a flag to + * suppress note output, e.g. when indexing + * to a particular time point */ + boolean cycleflag; /* normally false, set to true to make the + * sequence cycle every cycledur */ + int transpose; + int loudness; + time_type cycledur; + timebase_type timebase; + time_type rate; /* remembers rate across pauses */ + boolean paused; /* remembers if seq has been stopped or not */ + short noteoff_count; /* keeps track of pending events, such as + * note off commands. When this count goes + * to zero, the score is finished */ + ulong channel_mask; +} seq_node, *seq_type; + +extern seq_type sequence; + +chunk_type chunk_create(boolean first_flag); + +#define seq_cause_noteoff(seq, delay, voice, pitch) \ + (*(((seq_type) seq)->cause_noteoff_fn))(seq, delay, voice, pitch) +#define seq_midi_bend(seq, voice, value) \ + (*(((seq_type) seq)->midi_bend_fn))(seq, voice, value) +#define seq_midi_ctrl(seq, voice, ctrl, value) \ + (*(((seq_type) seq)->midi_ctrl_fn))(seq, voice, ctrl, value) +#define seq_midi_program(seq, voice, prog) \ + (*(((seq_type) seq)->midi_program_fn))(seq, voice, prog) +#define seq_midi_touch(seq, voice, value) \ + (*(((seq_type) seq)->midi_touch_fn))(seq, voice, value) +#define seq_noteoff(seq, voice, pitch) \ + (*(((seq_type) seq)->noteoff_fn))(seq, voice, pitch) +#define seq_noteon(seq, voice, pitch, vel) \ + (*(((seq_type) seq)->noteon_fn))(seq, voice, pitch, vel) +#define seq_free(seq) (*(((seq_type) seq)->free_fn))(seq) +#define seq_register(seq) \ + cu_register((cu_fn_type) (((seq_type) seq)->free_fn), seq) +#define seq_reset(seq) (*(((seq_type) seq)->reset_fn))(seq) + /* LISP: void (SEQ-RESET SEQ) */ + +extern boolean seq_print; /* debugging switch */ + +void seq_extensions(void); /* to be defined outside of seq -- user dependent */ +event_type insert_call(seq_type seq, time_type ctime, int cline, + int voice, int (*addr)(), long value[], int n); +event_type insert_clock(seq_type seq, time_type ctime, int cline, + time_type ticksize); +event_type insert_ctrl(seq_type seq, time_type ctime, int cline, int ctrl, + int voice, int value); + /* LISP: (SEQ-INSERT-CTRL SEQ FIXNUM FIXNUM FIXNUM FIXNUM FIXNUM) */ +event_type insert_ctrlramp(seq_type seq, time_type rtime, int rline, int voice, + time_type step, time_type dur, int ctrl, int v1, int v2); + /* LISP: (SEQ-INSERT-RAMP SEQ FIXNUM FIXNUM FIXNUM FIXNUM FIXNUM FIXNUM FIXNUM FIXNUM) */ +def_type insert_def(seq_type seq, char *symbol, unsigned char *definition, + int deflen); +event_type insert_deframp(seq_type seq, time_type rtime, int rline, int voice, + time_type step, time_type dur, def_type def, + int nparms, short parms[], int parm_num, int to_value); +event_type insert_macctrl(seq_type seq, time_type ctime, int cline, int ctrl, + int voice, int value); + /* LISP: (SEQ-INSERT-MACCTRL SEQ FIXNUM FIXNUM FIXNUM FIXNUM FIXNUM) */ +event_type insert_macro(seq_type seq, time_type ctime, int cline, + def_type def, int voice, int nparms, short *parms); +event_type insert_note(seq_type seq, time_type ntime, int nline, int voice, + int pitch, time_type dur, int loud); + /* LISP: (SEQ-INSERT-NOTE SEQ FIXNUM FIXNUM FIXNUM FIXNUM FIXNUM FIXNUM) */ +event_type insert_seti(seq_type seq, time_type stime, int sline, int voice, + int *addr, int value); +void noop(seq_type seq); +seq_type seq_alloc(void); +void seq_at_end(seq_type seq, void (*fn)(seq_type)); +void seq_cause_noteoff_meth(seq_type seq, time_type delay, int voice, int pitch); +#define seq_channel_mask(seq) ((seq)->channel_mask) +seq_type seq_copy(seq_type from_seq); /* LISP: (SEQ-COPY SEQ) */ +seq_type seq_create(void); /* LISP: (SEQ-CREATE) */ +void seq_cycle(seq_type seq, boolean flag, time_type dur); +#define seq_duration(seq) (((seq_type) seq)->chunklist->u.info.duration) +void seq_end_event(seq_type seq); +#define seq_events(seq) (((seq_type) seq)->chunklist ? \ + (((seq_type) seq)->chunklist->u.info.eventlist) : NULL) +#define seq_dictionary(seq) (seq)->chunklist->u.info.dictionary +#define seq_eventlist(seq) (seq)->chunklist->u.info.eventlist +#define seq_ctrlcount(seq) (seq)->chunklist->u.info.ctrlcount +#define seq_notecount(seq) (seq)->chunklist->u.info.notecount +#define seq_used_mask(seq) (seq)->chunklist->u.info.used_mask +void seq_free_chunks(seq_type seq); +seq_type seq_init(seq_type seq, int create_chunk); +#define seq_loudness(seq) (((seq_type) seq)->loudness) +void seq_midi_bend_meth(seq_type seq, int voice, int value); +void seq_midi_ctrl_meth(seq_type seq, int voice, int ctrl, int value); +void seq_midi_program_meth(seq_type seq, int voice, int prog); +void seq_midi_touch_meth(seq_type seq, int voice, int value); +void seq_noteon_meth(seq_type seq, int voice, int pitch, int vel); +void seq_noteoff_meth(seq_type seq, int chan, int pitch); +time_type seq_pause(seq_type seq, boolean flag); +void seq_play(seq_type seq); +#define seq_rate(seq) ((seq_type) seq)->rate +void seq_reset_meth(seq_type seq); +#define seq_runflag(seq) ((seq_type) seq)->runflag +#define seq_set_channel_mask(seq, cm) ((seq)->channel_mask) = (cm) +void seq_set_loudness(seq_type seq, int offset); +void seq_set_rate(seq_type seq, time_type rate); +#define seq_set_timebase(seq, tb) ((seq_type) seq)->timebase = (tb) +void seq_set_transpose(seq_type seq, int trans); +void seq_start_time(seq_type seq, time_type start_time); +void seq_stop(seq_type seq); +#define seq_timebase(seq) ((seq_type) seq)->timebase +#define seq_transpose(seq) ((seq_type) seq)->transpose diff --git a/cmt/seqdecls.h b/cmt/seqdecls.h new file mode 100644 index 0000000..654d12a --- /dev/null +++ b/cmt/seqdecls.h @@ -0,0 +1,11 @@ +/* This .h file declares everything you need to include seq.h */ +/* + * This file gets included by an interface to seq generated by intgen + */ +#include "switches.h" +#include "stdio.h" +#include "cext.h" +#include "userio.h" +#include "midifns.h" +#include "timebase.h" +#include "moxc.h" diff --git a/cmt/seqmread.c b/cmt/seqmread.c new file mode 100644 index 0000000..52f0c57 --- /dev/null +++ b/cmt/seqmread.c @@ -0,0 +1,448 @@ +/* + * seqmread.c + * + * Convert a MIDI file to a seq. + */ + +/* Copyright 1989 Carnegie Mellon University */ + +/***************************************************************************** +* Change Log +* Date | who : Change +*-----------+----------------------------------------------------------------- +* 17-Feb-92 | GWL : only one stdio.h +* : fix to satisfy compiler: + void returns, time_type giotime(), int filegetc() +*****************************************************************************/ + +#include "switches.h" +#include "stdio.h" +#include "cext.h" +#include "cmdline.h" +#include "midifns.h" /* to get time_type */ +#include "timebase.h" +#include "moxc.h" /* to get debug declared */ +#include "seq.h" +#include "seqread.h" /* to get scale */ +#include "seqmread.h" +#include "userio.h" +#include "ctype.h" + +#include "midifile.h" +#include "tempomap.h" + +int filegetc(); +void initfuncs(); +void prtime(); +void snding_free(); + +typedef struct snding_struct { + struct snding_struct *next; + event_type event_ptr; + int pitch; + int channel; +} snding_node, *snding_type; + +#define snding_alloc() (snding_type) memget(sizeof(snding_node)) +#define snding_free(s) memfree(s, sizeof(snding_node)) + +snding_type snding_list = NULL; + +tempomap_type the_tempomap; +event_type initial_clock; /* remember the first clock event */ +long prev_ticksize; /* remember the previous ticksize */ +int sysex_id = 0; + + +void smf_noteoff(); +void smf_error(); +void smf_header(); +void smf_trackstart(); +void smf_trackend(); +void smf_noteon(); +void smf_pressure(); +void smf_parameter(); +void smf_pitchbend(); +void smf_program(); +void smf_chanpressure(); +void smf_sysex(); +void smf_metamisc(); +void smf_metaseq(); +void smf_metaeot(); +void smf_timesig(); +void smf_smpte(); +void smf_tempo(); +void smf_keysig(); +void smf_metaspecial(); +void smf_metatext(); +void smf_arbitrary(); + +private seq_type the_score; + +static FILE *F; + +int filegetc() +{ +/* int temp = getc(F); + printf(" %x ", temp);*/ + return(int)(getc(F)); +} + +void seq_read_smf(seq, fp) + seq_type seq; + FILE *fp; +{ + F = fp; + initfuncs(); + sysex_id = 0; /* sysex in seq has to correspond to a symbol */ + the_score = seq; /* current sequence is a global within this module */ + if (!seq) return; + the_tempomap = tempomap_create(); + /* insert an initial clock to correspond to the default midifile tempo + (tempomap_create creates a corresponding initial entry in the tempomap) + (see smf_tempo for explanation of the scale() call) + */ + initial_clock = insert_clock(the_score, 0L, 0, 500L << 16); + /* scale(24 * 500000, 1 << 16, 24000) */ + if (!the_tempomap) return; + Mf_getc = filegetc; + midifile(); + /* fmac_close(F); -- do not close the file because the caller might try to + * close it (in fact XLISP insists on closing it as a side effect of + * garbage collection. + */ + gprintf(TRANS, "\nLoaded Midi file with %ld note(s), %ld ctrl(s).\n\n", + seq_notecount(seq), seq_ctrlcount(seq)); + seq_reset(seq); + while (snding_list) { + snding_type snding = snding_list; + snding_list = snding_list->next; + gprintf(TRANS, "Note-on (key %d, chan %d) has no matching noteoff\n", + snding->pitch, snding->channel + 1); + snding_free(snding); + } + tempomap_free(the_tempomap); +} + + +/* gio_time -- get the time in millisec for Adagio */ +/* + * Since Adagio times are (in their precise form) 1/256 ms, we want + * a similar time for midifiles, whose natural unit would be microseconds. + * We'll shift the microsecond time by 2 to get 1/250 ms = 4 us units + * and convert using the scale function when necessary. + * Real time is the time of the last tempo change (last_tempo_time) + * which is in 4us units + elapsed time. + * Elapsed time is the elapsed beats times the beat duration. + * Elapsed beats is Mf_currtime - last_tempo_beat. + * Beat duration is the specified tempo / division, where specified tempo + * is in microseconds, and division is parts per quarternote. + */ +unsigned long divisions = 24L; + +time_type gio_time() +{ + return (tempomap_lookup(the_tempomap, Mf_currtime) + 125L) / 250L; +} + + +void smf_header(format,ntrks,division) +{ +/* gprintf(TRANS, "Header format=%d ntrks=%d division=%d\n", + format,ntrks,division); */ + if (format > 1) gprintf(TRANS, + "Warning: format %d midi file may not work.\n", + format); + divisions = division; + /* adjust the initial tempochange */ + the_tempomap->entries->tempo = 500000L / division; +} + + +void smf_trackstart() +{ +/* gprintf(TRANS, "Track start\n"); */ +} + +void smf_trackend() +{ +/* gprintf(TRANS, "Track end\n"); */ +} + +void smf_noteon(chan,pitch,vol) +{ + snding_type snding; + if (vol == 0) { /* convert to a noteoff */ + smf_noteoff(chan, pitch, 0); + return; + } +/* prtime(); + gprintf(TRANS, "Note on, chan=%d pitch=%d vol=%d\n",chan+1,pitch,vol); +*/ + /* get ready to remember the sounding note */ + snding = snding_alloc(); + snding->next = snding_list; + snding_list = snding; + /* enter an event into score and remember it */ + snding->event_ptr = insert_note(the_score, gio_time(), 0, + chan + 1, pitch, 0L, vol); + snding->pitch = pitch; + snding->channel = chan; +} + +void smf_noteoff(chan,pitch,vol) +{ + snding_type *snding_ptr; + register snding_type snding; +/* prtime(); + gprintf(TRANS, "Note off, chan=%d pitch=%d vol=%d\n",chan+1,pitch,vol); +*/ /* search for the snding record */ + for (snding_ptr = &snding_list; + (snding = *snding_ptr) && + ((snding->pitch != pitch) || (snding->channel != chan)); + snding_ptr = &(snding->next)) /* printf("* search *\n") */; + if (!snding) { + gprintf(TRANS, "Note off %d, channel %d ignored: no note on\n", + pitch, chan + 1); + } else { + event_type event = snding->event_ptr; + event->u.note.ndur += (gio_time() - event->ntime) << 8; + /* free the snding record */ + *snding_ptr = snding->next; + snding_free(snding); + } +} + + +void smf_pressure(chan,pitch,press) +{ + prtime(); + gprintf(TRANS, "Pressure, chan=%d pitch=%d press=%d (IGNORED)\n", + chan + 1, pitch, press); +} + +void smf_parameter(chan,control,value) +{ + int ctrl = 0; +/* prtime(); + gprintf(TRANS, "Parameter, chan=%d c1=%d c2=%d\n",chan+1,control,value); + */ /* see if the control is one of the standard Adagio controls that + can be encoded in a special way. If not, ctrl remains at zero. + */ + switch (control) { + case PORTASWITCH: ctrl = PSWITCH_CTRL; break; + case MODWHEEL: ctrl = MODWHEEL_CTRL; break; + case VOLUME: ctrl = VOLUME_CTRL; break; + } + if (ctrl) /* then do special ctrl insert and save storage */ + insert_ctrl(the_score, gio_time(), 0, ctrl, chan + 1, value); + else insert_macctrl(the_score, gio_time(), 0, control, chan + 1, value); +} + + +/* smf_pitchbend -- handle a pitch bend event */ +/* + * NOTE: the midifile code from Tim Thompson has the msb and lsb bytes swapped. + * Thus the parameter msb is really the low order byte and lsb is high order. + */ +void smf_pitchbend(chan,msb,lsb) +{ +/* prtime(); + gprintf(TRANS, "Pitchbend, chan=%d msb=%d lsb=%d\n",chan+1,msb,lsb); */ + insert_ctrl(the_score, gio_time(), 0, BEND_CTRL, chan + 1, + ((lsb << 7) + msb) >> 6); +} + +void smf_program(chan,program) +{ +/* prtime(); + gprintf(TRANS, "Program, chan=%d program=%d\n",chan+1,program); */ + insert_ctrl(the_score, gio_time(), 0, PROGRAM_CTRL, chan + 1, program); +} + +void smf_chanpressure(chan,press) +{ +/* prtime(); + gprintf(TRANS, "Channel pressure, chan=%d pressure=%d\n",chan+1,press); + */ + insert_ctrl(the_score, gio_time(), 0, TOUCH_CTRL, chan + 1, press); +} + +void smf_sysex(leng,mess) +int leng; +char *mess; +{ + char symb[10]; + def_type defn; + int i; + sprintf(symb, "X%d", sysex_id++); + if (leng > 255) { + gprintf(TRANS, "sysex too long (%d bytes), ignored\n", leng - 2); + return; + } + /* need to end up with a prefix of [0][length], so add 2 to length; + note that this will copy past the end of the message -- this is + slightly dangerous and definitely crufty: + */ + defn = insert_def(the_score, symb, (unsigned char *) mess, leng + 2); + /* now fix up the definition by inserting the prefix bytes: */ + for (i = leng + 1; i > 1; i--) + defn->definition[i] = defn->definition[i - 2]; + defn->definition[0] = 0; + defn->definition[1] = leng; + insert_macro(the_score, gio_time(), 0, defn, 1, 0, NULL); +/* prtime(); + gprintf(TRANS, "Sysex, leng=%d (IGNORED)\n",leng); */ +} + +void smf_metamisc(type,leng,mess) +char *mess; +{ + prtime(); + gprintf(TRANS, + "Meta event, unrecognized, type=0x%02x leng=%d (IGNORED)\n", + type, leng); +} + +void smf_metaspecial(type,leng,mess) +char *mess; +{ + prtime(); + gprintf(TRANS, + "Meta event, sequencer-specific, type=0x%02x leng=%d (IGNORED)\n", + type, leng); +} + +void smf_metatext(type,leng,mess) +char *mess; +{ + static char *ttype[] = { + NULL, + "Text Event", /* type=0x01 */ + "Copyright Notice", /* type=0x02 */ + "Sequence/Track Name", + "Instrument Name", /* ... */ + "Lyric", + "Marker", + "Cue Point", /* type=0x07 */ + "Unrecognized" + }; + int unrecognized = (sizeof(ttype)/sizeof(char *)) - 1; + + if ( type < 1 || type > unrecognized ) + type = unrecognized; +} + +void smf_metaseq(num) +{ + prtime(); + gprintf(TRANS, "Meta event, sequence number = %d (IGNORED)\n",num); +} + +void smf_metaeot() +{ +/* prtime(); + gprintf(TRANS, "Meta event, end of track\n"); */ +} + +void smf_keysig(sf,mi) +{ +/* prtime(); + gprintf(TRANS, "Key signature, sharp/flats=%d minor=%d\n",sf,mi); */ +} + +/* smf_tempo -- handle a midifile tempo change */ +/* + * NOTE: if divisions is positive, it gives time units per quarter, and + * tempo is microsec per division. The product is microsec per quarter. + * To convert to ticksize (parameter to insert_clock), we divide by 24*1000 + * to get units of millisec and 24ths of quarter notes. insert_clock + * expects this to have a 16 bit fractional part. + */ +void smf_tempo(tempo) +long tempo; +{ + time_type ctime = gio_time(); + long ticksize = scale(tempo, 1024L, 375L); +/* (tempo / 24000) << 16; microsec/clock converted to ms/quarter, shifted 16*/ + +/* prtime(); + gprintf(TRANS, "Tempo, microseconds-per-MIDI-quarter-note = %ld\n",tempo); +*/ + tempomap_insert(the_tempomap, Mf_currtime, tempo / divisions); + if (ctime == 0) { + /* we already have a clock event at t=0 -> fix it */ + initial_clock->u.clock.ticksize = ticksize; + } else { /* we need a new one */ + /* NOTE: after the first clock, insert clock events 1/2 tick early + to make sure ticksize is set before clock_tick() wakes up and + reads it. + */ + insert_clock(the_score, ctime - (prev_ticksize >> 17), 0, ticksize); + prev_ticksize = ticksize; + } +} + +void smf_timesig(nn,dd,cc,bb) +{ +/* int denom = 1; + while ( dd-- > 0 ) + denom *= 2; + prtime(); + gprintf(TRANS, +"Time signature=%d/%d MIDI-clocks/click=%d 32nd-notes/24-MIDI-clocks=%d\n", + nn,denom,cc,bb); */ +} + +void smf_smpte(hr,mn,se,fr,ff) +{ + prtime(); + gprintf(TRANS, + "SMPTE, hour=%d minute=%d second=%d frame=%d fract-frame=%d (IGNORED)\n", + hr, mn, se, fr, ff); +} + +void smf_arbitrary(leng,mess) +char *mess; +{ + prtime(); + gprintf(TRANS, "Arbitrary bytes, leng=%d (IGNORED)\n",leng); +} + +void smf_error(msg) + char *msg; +{ + gprintf(ERROR, msg); +} + + +void prtime() +{ + gprintf(TRANS, "Time=%ld/%ld ",Mf_currtime, gio_time()); +} + +void initfuncs() +{ + Mf_error = smf_error; + Mf_header = smf_header; + Mf_starttrack = smf_trackstart; + Mf_endtrack = smf_trackend; + Mf_on = smf_noteon; + Mf_off = smf_noteoff; + Mf_pressure = smf_pressure; + Mf_controller = smf_parameter; + Mf_pitchbend = smf_pitchbend; + Mf_program = smf_program; + Mf_chanpressure = smf_chanpressure; + Mf_sysex = smf_sysex; + Mf_metamisc = smf_metamisc; + Mf_seqnum = smf_metaseq; + Mf_eot = smf_metaeot; + Mf_timesig = smf_timesig; + Mf_smpte = smf_smpte; + Mf_tempo = smf_tempo; + Mf_keysig = smf_keysig; + Mf_sqspecific = smf_metaspecial; + Mf_text = smf_metatext; + Mf_arbitrary = smf_arbitrary; +} diff --git a/cmt/seqmread.h b/cmt/seqmread.h new file mode 100644 index 0000000..a03da03 --- /dev/null +++ b/cmt/seqmread.h @@ -0,0 +1,4 @@ +/* seqmread.h -- midi file reader */ + +void seq_read_smf(seq_type seq, FILE *fp); /* LISP: (SEQ-READ-SMF SEQ FILE) */ + diff --git a/cmt/seqmwrite.c b/cmt/seqmwrite.c new file mode 100644 index 0000000..4e0b918 --- /dev/null +++ b/cmt/seqmwrite.c @@ -0,0 +1,658 @@ +/* created by DMH (damonhorowitz): write seq_type as standard midifile */ +/*************************************************************************** +* Change Log +* Date | Change +*-----------+--------------------------------------------------------------- +* 11-Mar-94 | Created Change Log +* 11-Mar-94 | PLu : Added private to function defs. +* 28-Apr-03 | DM : Change #include's for portability +****************************************************************************/ + +#include "switches.h" + +#include + +#include "cext.h" +#include "userio.h" +#include "midicode.h" +#include "mfmidi.h" +#include "midifns.h" +#include "timebase.h" +#include "moxc.h" +#include "seq.h" +#include "seqread.h" /* to get scale() */ +#include "seqmwrite.h" + +long chunk_size_marker; +int seti_counter; +extern time_type a_start_time; + +long last_event; /*time from last_clock_event to the last event*/ +time_type last_clock_event; +time_type last_tick_size; /* millisec per tick shifted 16 bits */ + +struct smf_write_seq { + seq_type seq; + int track; + FILE *outfile; + } smfw_seq; +extern seq_type sequence; /* this is a global to be accessed by + * routines called from the sequence */ +/* clock state: */ +extern time_type clock_ticksize; /* millisec per tick shifted 16 bits */ +extern boolean clock_running; /* TRUE if clock is running */ +extern boolean use_midi_clock; + +private void smfw_bend(); +private void smfw_cause_noteoff(); +private void smfw_ctrl(); +private void smfw_deltatime(); +private void smfw_dotrack(); +private void smfw_exclusive(); +private void smfw_noteoff(); +private void smfw_noteon(); +private void smfw_process_event(); +private void smfw_ramp_event(seq_type seq, event_type event, + unsigned int value, unsigned int to_value, int increment, + time_type step, int n); +private void smfw_send_macro(); +private void smfw_touch(seq_type seq, int voice, int value); +private void writevarlen(); + + +/* smfw_bend -- write a pitch bend to a midi file */ +/**/ +private void smfw_bend(seq_type seq, int voice, int value) +{ + if(debug) gprintf(TRANS, "smfw_bend %d\n", value); + smfw_deltatime(); + putc(MIDI_BEND | (voice - 1), smfw_seq.outfile); + putc(0xFF & ((value & 0x1) << 6) , smfw_seq.outfile); + putc(0xFF & (value >> 1), smfw_seq.outfile); +} + + +/* smfw_cause_noteoff -- schedule a noteoff for midi file */ +/* + * NOTE: this is called by smfw_process_event when it handles a note + * event node in a seq_type's event list. The ordinary moxc scheduler + * is used to "schedule" the noteoff in the future. In reality, the + * output is done as fast as possible (by attempting an infinite rate), + * so no real timing delays occur. The effect is to sort events by their + * specified time. + */ +private void smfw_cause_noteoff(seq, delay, voice, pitch) + seq_type seq; + time_type delay; + int voice; + int pitch; +{ + if(debug) gprintf(TRANS, "cause noteoff at %ld...", virttime + delay); + pitch += seq->transpose; + while (pitch <= 0) pitch += 12; + while (pitch >= 127) pitch -= 12; + seq->noteoff_count++; + causepri((delay_type) delay, 10, seq->noteoff_fn, + seq, voice, pitch); + +} + +private void smfw_clock_event(old_ticksize, new_ticksize) + time_type old_ticksize, new_ticksize; +{ + time_type temp_ticksize = new_ticksize; + new_ticksize = scale(new_ticksize, 375L, 1024L); +/* (new_ticksize >> 16) * 24000 ms/clock becomes us/midiquarter */ + + if(debug) gprintf(TRANS, "smfw_clock: write %ld (time:%ld) ->->->tempo %ld\n", + new_ticksize, virttime, 2500L / (new_ticksize / 24000)); + + /*use old ticksize to write the delta for the clock event*/ + last_tick_size = old_ticksize; + smfw_deltatime(); + last_tick_size = temp_ticksize;/* reset to = new_tick_size */ + putc(0xFF, smfw_seq.outfile); + putc(0x51, smfw_seq.outfile); + putc(0x03, smfw_seq.outfile); + putc((int) ((new_ticksize >> 16) & 0xFF), smfw_seq.outfile); + putc((int) ((new_ticksize >> 8) & 0xFF), smfw_seq.outfile); + putc((int) (new_ticksize & 0xFF), smfw_seq.outfile); + + last_clock_event = virttime; + last_event = 0L; + /*no time expired between last clockevent and last event(they are the same).*/ + /*next clock event will be exactly the next this_event from last_clock_event*/ +} + + +/* smfw_ctrl -- write a control change to a midi file */ +/**/ +private void smfw_ctrl(seq_type seq, int voice, int ctrl_name, int value) +{ + if(debug) gprintf(TRANS, "smfw_ctrl %d: %d\n", ctrl_name, value); + smfw_deltatime(); + putc(MIDI_CTRL | (voice - 1), smfw_seq.outfile); + putc(ctrl_name, smfw_seq.outfile); + putc(value, smfw_seq.outfile); +} + + +/* smfw_deltatime -- write the time difference between now an previous event */ +/**/ +private void smfw_deltatime() +{ + /* if last_ and clock_ are different, use last_ for clock deltatime*/ + time_type use_ticksize = (clock_ticksize != last_tick_size) ? + last_tick_size : clock_ticksize; + time_type this_event = virttime - last_clock_event; + if(debug) gprintf(TRANS, "delta! ticksize: %lu Lastev: %ld ThisevScaled: %lu Thisev: %lu ", + clock_ticksize, last_event, (this_event * ((2500L << 16) / use_ticksize)) / 100, + this_event); + + this_event = ((virttime - last_clock_event) * ((2500L << 16) / use_ticksize)) / 100; + + if(debug) gprintf(TRANS, "--- deltatime: %lu\n", this_event - last_event); + writevarlen((long) (this_event - last_event)); + + last_event = this_event; +} + + +/* smfw_dotrack -- write the remainder of a track */ +private void smfw_dotrack(seq) + seq_type seq; +{ + long end_marker; + timebase_type old_timebase = timebase; + unsigned long chunk_size; + + if (seq->runflag) { + if ((seq->timebase->virt_base == 0) && + (seq->timebase->rate == STOPRATE)) + /*we just set these last time through... do nothing*/; + seq_stop(seq); + } + timebase_use(seq->timebase); + set_rate(seq->timebase, STOPRATE); + set_virttime(seq->timebase, 0L); + seq->current = seq_events(seq); + seq->noteoff_count = 0L; + seq->runflag = TRUE; + seq->paused = TRUE; + last_clock_event = 0L; + last_event = 0L; + if(debug) gprintf(TRANS, "dotrack (reset) %d %ld (%lu) \n", + smfw_seq.track, last_event, virttime); + + if (seq->current) + cause((delay_type)(seq->current->ntime - virttime), smfw_process_event, + seq); + set_virttime(timebase, MAXTIME); + catchup(); + + putc(0x00, smfw_seq.outfile); + putc(0xFF, smfw_seq.outfile);/*end of track chunk*/ + putc(0x2F, smfw_seq.outfile); + putc(0x00, smfw_seq.outfile); + end_marker = ftell(smfw_seq.outfile); + fseek(smfw_seq.outfile, chunk_size_marker, 0);/*go back to enter chunksize*/ + chunk_size = (end_marker - chunk_size_marker) - 4;/* - 4 for 4 size bytes*/ + if(debug) gprintf(TRANS, "bytes written in previous track: %ld \n\n", chunk_size); + putc((int) ((0xFF & (chunk_size >> 24))), smfw_seq.outfile); + putc((int) ((0xFF & (chunk_size >> 16))), smfw_seq.outfile); + putc((int) ((0xFF & (chunk_size >> 8))), smfw_seq.outfile); + putc((int) ((0xFF & chunk_size)), smfw_seq.outfile); + fseek(smfw_seq.outfile, end_marker, 0);/*return file pointer to end of track*/ + timebase_use(old_timebase); +} + + +/* smfw_exclusive -- write a system excl. msg to midi file */ +private void smfw_exclusive(length, msg) +int length; +unsigned char *msg; +{ + int length_count = 0; + + if(debug) gprintf(TRANS, "SYSEX (time:%ld)\n", virttime); + + smfw_deltatime(); + + while (length > length_count){ /* *(msg-1) != MIDI_EOX) { */ + putc(*msg++, smfw_seq.outfile); + length_count++; + } + if(*(--msg) != MIDI_EOX) gprintf(TRANS, "ERROR: no end of sysex\n"); +} + +private void smfw_msg_write(n,c1,c2,c3) + int n; + unsigned char c1,c2,c3; +{ + if(debug) gprintf(TRANS, "MSGWRITE %d bytes (time:%ld)\n", n, virttime); + smfw_deltatime(); + switch(n) { + case 1: putc(c1, smfw_seq.outfile); + break; + case 2: putc(c1, smfw_seq.outfile); + putc(c2, smfw_seq.outfile); + break; + case 3: putc(c1, smfw_seq.outfile); + putc(c2, smfw_seq.outfile); + putc(c3, smfw_seq.outfile); + break; + } +} + + +/* smfw_noteoff -- write noteoff to midi file */ +/**/ +private void smfw_noteoff(seq_type seq, int voice, int pitch) +{ + if(debug) gprintf(TRANS, "smfw_noteoff %d: %d (time:%ld)\n", voice, pitch, virttime); + smfw_deltatime(); + putc(NOTEOFF | (voice - 1), smfw_seq.outfile); + putc(pitch, smfw_seq.outfile); + putc(0x40, smfw_seq.outfile); +} + + +/* smfw_noteon -- write noteon to midi file */ +/* + * NOTE: the seq parameter is not used here, but is passed in by the + * seq_noteon macro, so we have to have a placeholder for it. + */ +private void smfw_noteon(seq, voice, pitch, vel) + seq_type seq; + int voice, pitch, vel; +{ + if(debug) gprintf(TRANS, "smfw_noteon %d: %d %d(time:%ld)\n", voice, pitch, vel, virttime); + smfw_deltatime(); + putc(NOTEON | (voice - 1), smfw_seq.outfile); + putc(pitch, smfw_seq.outfile); + putc(vel, smfw_seq.outfile); +} + + +/* smfw_process_event -- write a seq event to a midi file */ +/**/ +private void smfw_process_event(seq) + seq_type seq; +{ + register event_type event; + if (!seq->runflag) return; + while ((event = seq->current) && (event->ntime <= virttime)) { + unsigned int voice; + if ((vc_voice(event->nvoice) == smfw_seq.track) || /*if on current track*/ + (((vc_voice(event->nvoice) - 16) == smfw_seq.track) + && (smfw_seq.track > 0)) || + /* acknowledge clock change on all tracks*/ + (event->value == CLOCK_VALUE && vc_ctrl(event->nvoice) == ESC_CTRL)) { + + /* process all current (and earlier) events */ + if (is_note(event)) { /*** play a note or rest ***/ + /* if this note is not a rest, play it and schedule an off event */ + if (event->value != NO_PITCH && + (seq_channel_mask(seq) & + (1 << ((voice = vc_voice(event->nvoice)) - 1)))) { + seq_noteon(seq, voice, (0xFF & event->value), + (int) (event->u.note.ndur & 0xFF)); + + seq_cause_noteoff(seq, (event->u.note.ndur) >> 8, + voice, (0xFF & event->value)); + } + } else { /*** send a control command ***/ + int n; + time_type step; + int delta; + int increment; + int voice = vc_voice(event->nvoice); + ulong enabled = seq_channel_mask(seq) & (1 << (voice - 1)); + + switch (vc_ctrl(event->nvoice)) { + case PSWITCH_CTRL: + if (!enabled) break; + if(debug) gprintf(TRANS, "porta %d (time:%ld)... ", event->value, virttime); + seq_midi_ctrl(seq, voice, PORTASWITCH, 0xFF & event->value); + break; + case MODWHEEL_CTRL: + if (!enabled) break; + if(debug) gprintf(TRANS, "modw %d (time:%ld)...", event->value, virttime); + seq_midi_ctrl(seq, voice, MODWHEEL, 0xFF & event->value); + break; + case TOUCH_CTRL: + if (!enabled) break; + if(debug) gprintf(TRANS, "touch %d (time:%ld)... ", event->value, virttime); + seq_midi_touch(seq, voice, 0xFF & event->value); + break; + case VOLUME_CTRL: + if (!enabled) break; + if(debug) gprintf(TRANS, "ftvol %d (time:%ld)...", event->value, virttime); + seq_midi_ctrl(seq, voice, VOLUME, 0xFF & event->value); + break; + case BEND_CTRL: + if (!enabled) break; + if(debug) gprintf(TRANS, "bend %d (time:%ld)... ", event->value, virttime); + seq_midi_bend(seq, voice, event->value); + break; + case PROGRAM_CTRL: + if (!enabled) break; + if(debug) gprintf(TRANS, "prog %d (time:%ld)\n", event->value, virttime); + smfw_deltatime(); + putc(MIDI_CH_PROGRAM | (voice - 1), smfw_seq.outfile); + putc(0xFF & event->value, smfw_seq.outfile); + break; + case ESC_CTRL: + switch (event->value) { + time_type this_event; + case CALL_VALUE: /*called routine will write to midifile in execution */ + sequence = seq; + (*(event->u.call.routine))(event->u.call.args); + break; + case CLOCK_VALUE: + clock_ticksize = event->u.clock.ticksize; + if(debug) gprintf(TRANS, "clockevent! ticksize: %lu (time:%ld)\n", + clock_ticksize, virttime); + + if (virttime > 0) { /* any clock before this is already recorded in the header */ + if (smfw_seq.track == 0) { /* record clock event on tempo track = 0 */ + /* cause clock write in half a newtick, because it was written .5 tick early*/ + cause((delay_type) (clock_ticksize >> 17), smfw_clock_event, + last_tick_size, clock_ticksize); + last_tick_size = clock_ticksize; /*set new ticksize*/ + } else { /*not on tempo track*/ + this_event = ((virttime - last_clock_event) * + ((2500L << 16) / last_tick_size)) / 100; + if(debug) gprintf(TRANS, "track != 0: Lastev: %ld Thisev: %ld NewLast: %ld\n", + last_event, this_event, this_event - last_event); + last_event = 0L - (this_event - last_event); + last_clock_event = virttime; + /*last_event is negative, so will be ADDED to next this_event*/ + + last_tick_size = clock_ticksize; + } + } else if (debug) gprintf(TRANS, "IGNORED\n");/* if virttime <= 0 */ + break; + case MACCTRL_VALUE: + if (!enabled) break; + if (debug) gprintf(TRANS, "MACCTRL %d: %d (time:%ld)\n", + event->u.macctrl.ctrl_number, event->u.macctrl.value, virttime); + smfw_deltatime(); + putc(MIDI_CTRL | (voice - 1), smfw_seq.outfile); + putc(0xFF & event->u.macctrl.ctrl_number, smfw_seq.outfile); + putc(0xFF & event->u.macctrl.value, smfw_seq.outfile); + break; + case MACRO_VALUE: + if (!enabled) break; + if (debug) gprintf(TRANS, "MACRO sent to...\n"); + smfw_send_macro(event->u.macro.definition, + voice, event->u.macro.parameter, -1, 0); + break; + case CTRLRAMP_VALUE: + case DEFRAMP_VALUE: { + int from, to; + if (!enabled) break; + step = event->u.ramp.step; + if (event->value == CTRLRAMP_VALUE) { + if(debug) gprintf(TRANS, "CTRLRAMP (time:%ld)...", virttime); + from = event->u.ramp.u.ctrl.from_value; + to = event->u.ramp.u.ctrl.to_value; + } else { + if (debug) gprintf(TRANS, "DEFRAMP (time:%ld)...", virttime); + from = event->u.ramp.u.def.parameter[ + event->u.ramp.u.def.parm_num]; + to = event->u.ramp.u.def.to_value; + } + delta = to - from; + increment = delta; + if (delta < 0) delta = -delta; + /* RBD - Note: Step is always non-zero */ + n = event->u.ramp.dur / step; + increment = (increment << 8) / n; + smfw_ramp_event(seq, event, from << 8, to << 8, + increment, step, n); + seq->noteoff_count++; + break; + } + case SETI_VALUE: + seti_counter++; /*will be printed after writing is completed*/ + *(event->u.seti.int_to_set) = event->u.seti.value; + break; + default: + gprintf(TRANS, "unexpected ESC_CTRL value\n"); + break; + } + break; + default: + gprintf(TRANS, "unexpected seq data\n"); + break; + } + } + } + seq->current = event->next; + } + if (seq->current) { /* if there is an event: delay, and then process again */ + cause((delay_type)(event->ntime - virttime), smfw_process_event, seq); + } +} + +/* smfw_ramp_event -- generate a ramp to write*/ +private void smfw_ramp_event(seq, event, value, to_value, increment, step, n) + seq_type seq; + register event_type event; + unsigned int value; + unsigned int to_value; + int increment; + time_type step; + int n; +{ + if(debug) gprintf(TRANS, "ramp of %d: %d to %d\n", event->u.ramp.ctrl, value >> 8, + to_value >> 8); + if (seq->runflag) { + int voice = vc_voice(event->nvoice); + if (n == 0) value = to_value; + else cause((delay_type)step, smfw_ramp_event, seq, event, value + increment, + to_value, increment, step, n - 1); + if (event->value == CTRLRAMP_VALUE) { + int ctrl = event->u.ramp.ctrl; + if (ctrl == -TOUCH_CTRL) smfw_touch(seq, voice, value >> 8); + else if (ctrl == -BEND_CTRL) smfw_bend(seq, voice, value >> 8); + else smfw_ctrl(seq, voice, ctrl, value >> 8); + } else { /* must be DEFRAMP_VALUE */ + smfw_send_macro(event->u.ramp.u.def.definition, + vc_voice(event->nvoice), + event->u.ramp.u.def.parameter, + event->u.ramp.u.def.parm_num, value >> 8); + } + if (n == 0) seq_end_event(seq); + } +} + + +/* smfw_send_macro -- write msg to midi file from a seq "macro" event */ +/**/ +private void smfw_send_macro(ptr, voice, parameter, parm_num, value) + register unsigned char *ptr; + int voice; + short parameter[]; + int parm_num; + int value; +{ + register unsigned char code, *loc; + while ((code = *ptr++)) { + loc = ptr + *ptr; + ptr++; + if (code <= nmacroparms) { + code--; + *loc = (code == parm_num ? value : parameter[code]) & 0x7f; + } + else if (code == nmacroparms + 1) { + *loc = ((voice - 1) & 0xF) | *loc; + } + else { + code -= (nmacroparms + 2); + *loc = ((code == parm_num ? value : parameter[code]) >> 7) & 0x7F; + } + } + if (ptr[1] == MIDI_SYSEX) + smfw_exclusive(*ptr, ptr + 1); + else + smfw_msg_write(*ptr, ptr[1], ptr[2], ptr[3]); +} + + +/* smfw_touch -- write aftertouch msg to midi file */ +/**/ +private void smfw_touch(seq_type seq, int voice, int value) +{ + if(debug) gprintf(TRANS, "smfw_touch %d\n", value); + smfw_deltatime(); + putc(MIDI_TOUCH | (voice - 1), smfw_seq.outfile); + putc(value, smfw_seq.outfile); +} + + +void seq_write_smf(seq, outfile) + seq_type seq; + FILE *outfile; +{ + time_type put_tick_size; + int i; + /* ticksize is milliseconds << 16, and tickrate = 24*tempo + 60000ms/min / (24 * tempo) = 2500/tempo = 25 + 25 << 16 = 1638400 */ + time_type starting_ticksize = 1638400L; /*default midifile tempo 100*/ + int track_count = 0; + long track_count_marker; + register event_type event; + + seti_counter = 0; + + /*initialize the smfw_seq struct*/ + smfw_seq.outfile = outfile; + smfw_seq.seq = seq_copy(seq); + + smfw_seq.seq->cause_noteoff_fn = smfw_cause_noteoff; + smfw_seq.seq->midi_bend_fn = smfw_bend; + smfw_seq.seq->midi_ctrl_fn = smfw_ctrl; + smfw_seq.seq->midi_touch_fn = smfw_touch; + smfw_seq.seq->noteoff_fn = smfw_noteoff; + smfw_seq.seq->noteon_fn = smfw_noteon; + + event = seq_events(smfw_seq.seq); + + /* search for clock events up till start of score */ + /* careful: there may be no events at all */ + while(event && event->ntime <= 0){ + if(debug) gprintf(TRANS, "event (time:%ld)\n", event->ntime); + if(vc_ctrl(event->nvoice) == ESC_CTRL && event->value == CLOCK_VALUE) { + if(debug) gprintf(TRANS, "clock %lu at 0\n", event->u.clock.ticksize); + starting_ticksize = event->u.clock.ticksize; + break; + } + event = event->next; + } + + putc(0x4D, smfw_seq.outfile); /*header: MThd*/ + putc(0x54, smfw_seq.outfile); + putc(0x68, smfw_seq.outfile); + putc(0x64, smfw_seq.outfile); + putc(0x00, smfw_seq.outfile); + putc(0x00, smfw_seq.outfile); + putc(0x00, smfw_seq.outfile); + putc(0x06, smfw_seq.outfile); + putc(0x00, smfw_seq.outfile); + putc(0x01, smfw_seq.outfile); /*format 1 */ + putc(0x00, smfw_seq.outfile); + + track_count_marker = ftell(smfw_seq.outfile); /*number of tracks will be written later*/ + putc(0x00, smfw_seq.outfile); /*will be filled by track_count_marker*/ + + putc(0x02, smfw_seq.outfile);/*division resolution of 600*/ + putc(0x58, smfw_seq.outfile); + + for (i = 0; i < 17; i++) { /* for each track... */ + if (((seq_used_mask(smfw_seq.seq) >> (i - 1)) & 0x1) || (i == 0)) { + if (debug) gprintf(TRANS, "write track %d \n", i); + track_count++; + clock_ticksize = starting_ticksize; + last_tick_size = starting_ticksize; + putc(0x4D, smfw_seq.outfile);/*track header: MTrk*/ + putc(0x54, smfw_seq.outfile); + putc(0x72, smfw_seq.outfile); + putc(0x6B, smfw_seq.outfile); + + chunk_size_marker = ftell(smfw_seq.outfile); + /* size of chunk will be written later */ + /* will be filled by chunk_size_marker */ + putc(0x00, smfw_seq.outfile); + putc(0x00, smfw_seq.outfile); + putc(0x00, smfw_seq.outfile); + putc(0x00, smfw_seq.outfile); + + if (i == 0) { /* tempo and time signature track */ + putc(0x00, smfw_seq.outfile);/* default time sig stuff*/ + putc(0xFF, smfw_seq.outfile); + putc(0x58, smfw_seq.outfile); + putc(0x04, smfw_seq.outfile); + putc(0x04, smfw_seq.outfile); + putc(0x02, smfw_seq.outfile); + putc(0x18, smfw_seq.outfile); + putc(0x08, smfw_seq.outfile); + putc(0x00, smfw_seq.outfile); + + /* TEMPO: inserted here in case default is used */ + putc(0xFF, smfw_seq.outfile); + putc(0x51, smfw_seq.outfile); + putc(0x03, smfw_seq.outfile); + /* ticksize is in ms<<16, so to get milliseconds per tick, it's + ticksize / 65536. To get beat durations, multiply by 24 + to get ticksize * 24 / 65536. To get microseconds, + multiply by 1000: ticksize * 24000 / 65536. Divide both + constants by 64 to get ticksize * 375 / 1024 = + microseconds per quarter note. + */ + put_tick_size = scale(clock_ticksize, 375L, 1024L); + putc((int) ((put_tick_size >> 16) & 0xFF), smfw_seq.outfile); + putc((int) ((put_tick_size >> 8) & 0xFF), smfw_seq.outfile); + putc((int) (put_tick_size & 0xFF), smfw_seq.outfile); + } + smfw_seq.track = i; + smfw_dotrack(smfw_seq.seq); + } + } + if (seti_counter) + gprintf(TRANS, "%d SETI events IGNORED!\n", seti_counter); + seq_stop(smfw_seq.seq); + /* go back and insert number of tracks */ + fseek(smfw_seq.outfile, track_count_marker, 0); + putc(0xFF & track_count, smfw_seq.outfile); + fclose(smfw_seq.outfile); +} + + +/* writevarlen -- write a variable length integer to midi file */ +/**/ +private void writevarlen(value) + register long value; +{ + register ulong buffer; + + if(debug) gprintf(TRANS, "variable length quantity..."); + + buffer = value & 0x7f; + + while((value >>= 7) > 0) { + buffer <<= 8; + buffer |= 0x80; + buffer += (value & 0x7f); + } + + for(;;) { + if(debug) gprintf(TRANS, " byte "); + putc((int) (buffer & 0xFF), smfw_seq.outfile); + if (buffer & 0x80) buffer >>= 8; + else break; + } + if(debug) gprintf(TRANS, "written!\n"); +} diff --git a/cmt/seqmwrite.h b/cmt/seqmwrite.h new file mode 100644 index 0000000..458336f --- /dev/null +++ b/cmt/seqmwrite.h @@ -0,0 +1,4 @@ +/* seqmwrite.h -- midi file writer */ + +void seq_write_smf(seq_type seq, FILE *outfile); /* LISP: (SEQ-WRITE-SMF SEQ FILE) */ + diff --git a/cmt/seqread.c b/cmt/seqread.c new file mode 100644 index 0000000..62d759d --- /dev/null +++ b/cmt/seqread.c @@ -0,0 +1,1890 @@ +/**************************************************************************** + seqread.c -- Phase 1 of adagio compilation... + + this module parses adagio programs and builds a linked list structure + consisting of notes and control changes in time order. + + Copyright 1989 Carnegie Mellon University +*****************************************************************************/ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 31-Dec-85 | Created changelog +* 31-Dec-85 | Add c:\ to include directives +* 31-Dec-85 | Added standard command scanner, metronome variable, need to add +* | cmdline_help procedure +* 31-Dec-85 | Call intr_init +* 31-Dec-85 | Set musictrace from command line via -trace +* 31-Dec-85 | Added -poll +* 1-Jan-86 | Put error messages out to stderr +* 1-Jan-86 | Set IsAT. Can be later overridden by -at and -xt switches, +* | currently only used for diagnostics (may be needed for +* | compatibles, who knows? In which case remove the tests which +* | confirm the type of processor) +* 1-Jan-86 | Removed dur-adjusted message +* 1-Jan-86 | Added miditrace +* 18-Jan-86 | Shortened durations by 1/200 s to avoid roundoff problems -- +* | see buildnote for details. +* 3-Mar-86 | Allow octave and accidentals in either order after pitch name. +* | Default octave is now one that gets nearest previous pitch, +* | the tritone (half an octave) interval is descending by default. +* | Special commands handled by table search, !Rate command added +* | to scale all times by a percentage (50 = half speed). +* 9-Mar-86 | Use space to limit amount of storage allocation. Otherwise +* | exhausting storage in phase1 caused phase2 to fail. +* 12-Mar-86 | Broke off command line parser into adagio.c, only parser remains +* 24-Mar-86 | Changed representation from note_struct to event_struct +* | Parse M, N, O, X, and Y as control change commands +* 23-May-86 | Added , and ; syntax: "," means "N0\n", ";" means "\n" +* 16-Jul-86 | modify to only call toupper/lower with upper/lower case as +* | parameter to be compatible with standard C functions +* 7-Aug-86 | fixed bug with default pitches and rests +* 5-Jul-87 | F.H: Introduced new memory handling from Mac version. +* | Changed: init() +* | ins_event() +* | ins_ctrl() +* | ins_note() +* | Deleted: reverse() +* | nalloc() +* | Introduced: event_alloc() +* | phase1_FreeMem() +* | system.h & system.c dependencies +* 10-Feb-88 | fixed parseend to accept blanks and tabs, +* | fixed rate scaling of durations +* 11-Jun-88 | commented out gprintf of \n to ERROR after parsing finished. +* 13-Oct-88 | JCD : exclusive AMIGA version. +* 13-Apr-89 | JCD : New portable version. +* 31-Jan-90 | GWL : Cleaned up for LATTICE +* 30-Jun-90 | RBD : further changes +* 2-Apr-91 | JDW : further changes +* 30-Jun-91 | RBD : parse '+' and '/' in durations, * after space is comment +* 28-Apr-03 | DM : changes for portability +*****************************************************************************/ + +#include "switches.h" + +#include +#include + +#include "cext.h" +#include "cmdline.h" +#include "midifns.h" /* to get time_type */ +#include "timebase.h" +#include "moxc.h" /* to get debug declared */ +#include "seq.h" +#include "seqread.h" +#include "userio.h" +/* ctype.h used to be included only by UNIX and AMIGA, + surely everyone wants this? */ +#include "ctype.h" + +#ifndef toupper +/* we're already taking precautions, so inline version of toupper is ok: */ +#define toupper(c) ((c)-'a'+'A') +/* CAUTION: AZTEC V5.0 defines an inline version of toupper called _toupper, + but they got it wrong! + */ +#endif + +/* cmtcmd.h references amiga message ports */ +#ifdef AMIGA +#ifdef LATTICE +#include "amiga.h" +#endif +#include "exec/exec.h" +#endif +#include "cmtcmd.h" + +/* public stuff */ +extern long space; /* remaining free bytes */ +extern int abort_flag; + +/**************************************************************************** + The following are used to simulate fixed point with the radix point + 8 bits from the right: +****************************************************************************/ + +#define precise(x) (((time_type) x) << 8) +#define seqround(x) ((((time_type) x) + 128) >> 8) +#define trunc(x) (((time_type) x) >> 8) + +#define nullstring(s) (s[0] == EOS) + + +/**************************************************************************** +* Routines local to this module: +****************************************************************************/ +private void do_a_rest(); +private time_type doabsdur(); +private int doabspitch(); +private void doclock(); +private void docomment(); +private void doctrl(); +private void dodef(); +private time_type dodur(); +private void doerror(); +private int doloud(); +void domacro(); +private void donextdur(); +private int dopitch(); +private void doprogram(); +private void dorate(); +private void doset(); +private void dospecial(); +private time_type dosymdur(); +private void dotempo(); +private void dotime(); +private void dovoice(); +private void fferror(); +private void init(); +private int issymbol(); +private void marker(); +private void parseend(); +private void parsefield(); +private boolean parsenote(); +private boolean parseparm(); +private int scan(); +private int scan1(); +private long scanint(); +private void scansymb(); +private long scansgnint(); + +/**************************************************************************** +* data structures for parser lookup tables +****************************************************************************/ + +struct durt { /* duration translation table */ + char symbol; + time_type value; +}; + +#define durtable_len 7 +struct durt durtable[durtable_len] = { + {'W', 4800L}, + {'H', 2400L}, + {'Q', 1200L}, + {'I', 600L}, + {'S', 300L}, + {'%', 150L}, + {'^', 75L} +}; + +struct loudt { /* loudness translation table */ + char symbol[4]; + int value; +}; + +struct loudt loudtable[] = { + {"PPP", 20}, + {"PP\0", 26}, + {"P\0\0", 34}, + {"MP\0", 44}, + {"MF\0", 58}, + {"F\0\0", 75}, + {"FF\0", 98}, + {"FFF", 127} +}; + +char too_many_error[] = "Too many parameters"; + +private char *ssymbols[] = {"TEMPO", "RATE", "CSEC", "MSEC", + "SETI", "SETV", "CALL", "RAMP", + "CLOCK", "DEF", "END"}; + +#define sym_tempo 0 +#define sym_rate 1 +#define sym_csec 2 +#define sym_msec 3 +#define sym_seti 4 +#define sym_setv 5 +#define sym_call 6 +#define sym_ramp 7 +#define sym_clock 8 +#define sym_def 9 +#define sym_end 10 + +/* number of symbols */ +#define sym_n 11 + +#define linesize 100 +private char line[linesize]; /* the input line */ +private char token[linesize]; /* a token scanned from the input line */ + +private boolean pitch_flag; /* set when a pitch is indicated */ +/* (if controls changes are given, only allocate a note event if + * a pitch was specified -- i.e. when pitch_flag is set) + */ +private boolean rest_flag; /* set when a rest (R) is found */ +/* this flag is NOT inherited by the next line */ + +private boolean symbolic_dur_flag; +/* TRUE if last dur was not absolute + * (if this is set, then the default duration is changed + * accordingly when the tempo is changed.) + */ + + +#define nctrl 8 + +private boolean ctrlflag[nctrl]; +/* TRUE if control change was present + * ctrlflag[0] TRUE if ANY control change + * was present + */ +private int ctrlval[nctrl]; +/* the new value of the control */ +#define nmacroctrl 10 +short macctrlx; /* index into the following: */ +short macctrlnum[nmacroctrl]; /* macro ctrl number, e.g. for ~4(67), or + * number of parameters for a symbolic macro */ +short macctrlparmx[nmacroctrl]; /* ctrl value for ctrl change, or index of + * parameters for symbolic macro */ +short macctrlparms[nmacroctrl*nmacroparms]; /* parameters for symbolic macros */ +short macctrlnextparm; +def_type macctrldef[nmacroctrl]; /* definition for symbolic macro */ + +private time_type time_scale; /* 1000 if centisec, 100 if millisec */ +/* note: user_specified_time * (time_scale / rate) = millisec */ + + + +/**************************************************************************** +* +* variables private to this module +* +****************************************************************************/ + +private boolean end_flag = FALSE; /* set "true" when "!END" is seen */ + +/**************************************************************************** +* state variables +* Because each line of an Adagio score inherits properties from the previous +* line, it makes sense to implement the parser as a collection of routines +* that make small changes to some global state. For example, pitch is a +* global variable. When the field G4 is encountered, the dopitch routine +* assigns the pitch number for G4 to the variable pitch. After all fields +* are processed, these variables describe the current note and contain the +* default parameters for the next note as well. +* +* Global variables that are used in this way by the parsing rountines are: +****************************************************************************/ +private int +linex, /* index of the next character to be scanned */ +lineno, /* current line number */ +fieldx, /* index of the current character within a field */ +pitch, /* pitch of note */ +loud, /* loudness of note */ +voice, /* voice (midi channel) of note */ +artic; /* articulation (a percentage of duration) */ + +private boolean ndurp; /* set when a next (N) is indicated */ +/* (next time defaults to the current time plus duration unless + * overridden by a next (N) command whose presence is signalled + * by ndurp.) + */ + +private time_type +thetime, /* the starting time of the note */ +rate, /* time rate -- scales time and duration, default = 100 */ +ntime, /* the starting time of the next note */ +dur, /* the duration of the note */ +tempo, /* the current tempo */ +start, /* the reference time (time of last !tempo or !rate cmd) */ +ticksize; /* set by !clock command, zero for no clock */ + +private int pitchtable[7] = { + 69, 71, 60, 62, 64, 65, 67 }; + +extern char score_na[name_length]; + +private seq_type the_score; /* this is the score we are parsing */ + + +/* def_append -- append a byte to the current definition */ +/* + * The def data structure: + * [code][offset][code][offset]...[0][length][data][data][data]... + * where code is 1:nmacroparms for %n, + * nmacroparms+1 for %v, + * nmacroparms+2:nmacroparms*2+1 for ^n + * and offset is the byte offset (from the offset byte) to the data + * where the parameter should be substituted + * and length is the number of data bytes + */ +boolean def_append(def, nparms, data) + unsigned char def[]; + int nparms; + int data; +{ + int base = (nparms << 1) + 1; /* this byte is the length */ + /* first parameter has to be able to reference last byte: */ + if ((def[base])++ >= (254 - (nparms << 1))) { + fferror("Data too long"); + return FALSE; + } + def[base + def[base]] = data; + return TRUE; +} + + +def_type def_lookup(symbol) + char *symbol; +{ + def_type defn = seq_dictionary(the_score); + while (defn) { + if (strcmp(defn->symbol, symbol) == 0) { + return defn; + } + defn = defn->next; + } + return NULL; +} + + +void def_parm(def, nparms, code) + unsigned char def[]; + int nparms; + int code; +{ + int i, j; + /* in order to insert a 2-byte parameter descriptor, the offsets from + * previous descriptors (that precede the data) need to be increased by 2: + */ + for (i = 1; i < (nparms << 1); i += 2) { + def[i] += 2; + } + /* now i is index of length; work backwards from the last byte, moving + * everything up by 2 bytes to make room for the new descriptor: + */ + for (j = i + def[i] + 2; j > i; j--) { + def[j] = def[j - 2]; + } + /* now i is index of offset; insert the descriptor code (first byte) + * and the offset to the parameter location in the message (second byte) + */ + def[i - 1] = code; + def[i] = def[i + 2] + 2; +} + +/**************************************************************************** +* do_a_rest +* Effect: parses a rest (R) command +****************************************************************************/ + +private void do_a_rest() +{ + if (token[fieldx]) + fferror("Nothing expected after rest"); + rest_flag = TRUE; +} + +/**************************************************************************** +* doabsdur +* Effect: parses an absolute dur (U) command +****************************************************************************/ + +private time_type doabsdur() +{ + time_type result=1000L; + register char c; + if (isdigit(token[fieldx])) { + result = precise(scanint()); + /* allow comma or paren for use in parameter lists */ + if ((c = token[fieldx]) && (c != ',') && (c != ')') && (c != '+')) { + fferror("U must be followed by digits only"); + } + if (time_scale == 1000) result *= 10; /* convert to ms */ + } else fferror("No digit after U"); + return result; +} + +/**************************************************************************** +* doabspitch +* Effect: parses an absolute pitch (P) command +****************************************************************************/ + +private int doabspitch() +{ + int result = 60; + int startx = fieldx; + register char c; + int savex; + if (isdigit (token[fieldx])) { + result = (int) scanint(); + /* allow comma or paren for abspitch in parameter */ + if ((c = token[fieldx]) && c != ',' && c != ')') + fferror("P must be followed by digits only"); + else if (result < minpitch) { + savex = fieldx; + fieldx = startx; + fferror("Minimum pitch of 0 will be used"); + result = minpitch; + fieldx = savex; + } else if (result > maxpitch) { + savex = fieldx; + fieldx = startx; + fferror("Maximum pitch of 127 will be used"); + result = maxpitch; + fieldx = savex; + } + } else fferror("No digits after P"); + return result; +} + + +/* doartic -- compute an articulation factor */ +/* + NOTE: artic is a percentage that scales the duration + of notes but not the time to the next note onset. It + is applied to the final computed duration after all + other scaling is applied. + */ +private void doartic() +{ + if (isdigit(token[fieldx])) { + artic = (int) scanint(); + if (token[fieldx]) + fferror("Only digits were expected here"); + } else fferror("No digits after /"); +} + + +/* docall -- parse a call in the form !CALL fn(p1,p2,p3) */ +/**/ +private void docall() +{ + boolean error_flag = TRUE; + ndurp = FALSE; + + linex += scan(); + + if (token[0] == 0) fferror("Function name expected"); + else { + char symbol[100]; + struct symb_descr *desc; + long value[SEQ_MAX_PARMS]; + int i=0; + + scansymb(symbol); + if (fieldx == 1) fferror("Routine name expected"); + else if (token[fieldx] != '(') fferror("Open paren expected"); + else { + desc = &HASHENTRY(lookup(symbol)); + if (!desc->symb_type) { + fieldx = 0; + fferror("Function not defined"); + } else if (desc->symb_type != fn_symb_type) { + fieldx = 0; + gprintf(TRANS, "desc->symb_type is %d\n", desc->symb_type); + fferror("This is not a function"); + } else { + error_flag = FALSE; + fieldx++; /* skip over paren */ + for (i = 0; i < SEQ_MAX_PARMS; i++) value[i] = 0; + i = 0; + /* note that no params "()" is legal */ + while (i < SEQ_MAX_PARMS && token[fieldx] != ')' && + parseparm(&value[i])) { + i++; + if (token[fieldx] == ',') { + fieldx++; + } else if (token[fieldx] != ')') { + fferror("Unexpected character"); + error_flag = TRUE; + break; + } + } + fieldx++; + if (i > SEQ_MAX_PARMS) fferror("Too many parameters"); + } + while (TRUE) { + linex += scan(); + if (nullstring(token)) { + break; + } + switch (token[0]) { + case 'T': + fieldx = 1; + dotime(); + break; + case 'V': + fieldx = 1; + dovoice(); + break; + case 'N': + fieldx = 1; + donextdur(); + break; + default: + fferror("Unexpected character"); + } + } + if (!error_flag) + insert_call(the_score, seqround(thetime), lineno, voice, + desc->ptr.routine, value, i); + /* advance the time only if an N field was given */ + if (ndurp) thetime += ntime; + } + } +} + + +/* doclock -- insert a clock command */ +/* + * derivation: if there is no previous clock running, then start the + * clock on time. Otherwise, start the clock half a tick early. + * ticksize = (beattime / 24) = ((60sec/tempo)/24) = + * ((60000ms/tempo)/24) = (60000/24)/tempo = 2500/tempo + */ +private void doclock() +{ + int oldticksize = ticksize; + ticksize = (2500L << 16) / tempo; + insert_clock(the_score, seqround(thetime) - (oldticksize >> 17), + lineno, ticksize); +} + + +/**************************************************************************** +* docomment +* Effect: parses a comment (*) command +****************************************************************************/ + +private void docomment() +{ + line[linex] = '\n'; /* force end of line to skip comment line */ + line[linex+1] = EOS; +} + +/**************************************************************************** +* doctrl +* Inputs: +* n: control number +* Effect: parses a control (K, M, O, X, or Y) command +****************************************************************************/ + +private void doctrl(n) +int n; +{ + ctrlval[n] = (int) scanint(); + if (token[fieldx]) { + fferror("Only digits expected here"); + } else { + ctrlflag[n] = TRUE; + ctrlflag[0] = TRUE; /* ctrlflag[0] set if any flag is set */ + } +} + + +private void dodef() +{ + /* maximum def size is 256 + 9 parms * 2 + 2 = 276 */ + unsigned char def[280]; + char symbol[100]; + int nparms = 0; + int nibcount = 0; + int data = 0; + register char c; + + linex += scan(); + + if (!token[0]) fferror("Symbol expected"); + else { + strcpy(symbol, token); + def[0] = def[1] = 0; + while (TRUE) { + linex += scan1(&line[linex]); + c = token[0]; + if (!c) { + linex--; + if (nibcount & 1) { + fferror("Expected pairs of hex digits: one missing"); + return; + } + break; + } else if (c == ' ' || c == '\t' || c == '\n') continue; + else if (isdigit(c)) { + data = (data << 4) + (c - '0'); + nibcount++; + if (!(nibcount & 1)) { + if (!def_append(def, nparms, data)) + return; + data = 0; + } + } else if ('A' <= c && c <= 'F') { + data = (data << 4) + (c - 'A') + 10; + nibcount++; + if (!(nibcount & 1)) { + if (!def_append(def, nparms, data)) + return; + data = 0; + } + } else if (c == 'V') { + data = data << 4; + nibcount++; + /* v without a leading nibble is equivalent to 0v: */ + if (nibcount & 1) nibcount++; + if (!def_append(def, nparms, data)) + return; + def_parm(def, nparms++, nmacroparms+1); + } else if (c == '%') { + linex += scan1(&line[linex]); + c = token[0]; + if (c < '1' || c > ('0' + nmacroparms)) { + fferror(parm_expected_error); + break; + } + if (!def_append(def, nparms, 0)) + return; + def_parm(def, nparms++, c - '0'); + } else if (c == '^') { + linex += scan1(&line[linex]); + c = token[0]; + if (c < '1' || c > ('0' + nmacroparms)) { + fferror(parm_expected_error); + break; + } + if (!def_append(def, nparms, 0)) + return; + def_parm(def, nparms++, (c - '0') + nmacroparms + 1); + } else { /* something unexpected here -- just exit */ + linex--; + fferror("Unexpected data"); + return; + } + } + insert_def(the_score, symbol, def, + (nparms << 1) + def[(nparms << 1) + 1] + 2); + } +} + +/**************************************************************************** +* dodur +* Effect: parses a duration (sum of dosymdur and/or doabsdur) +* sets symbolic_dur_flag (according to the first addend in mixed arithmetic) +* +* Returns: duration in "precise" units +****************************************************************************/ +private time_type dodur() +{ + time_type result = 0L; + symbolic_dur_flag = TRUE; + + if (token[fieldx-1] == 'U') { + result = doabsdur(); + symbolic_dur_flag = FALSE; + } else result = dosymdur(); + while (token[fieldx] == '+') { + fieldx += 2; + if (token[fieldx-1] == 'U') result += doabsdur(); + else result += dosymdur(); + } + return scale(result, 100L, rate); +} + +/**************************************************************************** +* doerror +* Effect: parse an unrecognized field by reporting an error +****************************************************************************/ + +private void doerror() +{ + fieldx = 0; + fferror("Bad field"); +} + +/**************************************************************************** +* doloud +* Effect: parse a loudness (L) command +****************************************************************************/ + +private int doloud() +{ + int i, j; + int result; + int oldfieldx = fieldx; + int newfieldx; + char symbol[100]; + + if (!token[fieldx] || token[fieldx]==')' || token[fieldx]==',') { + fferror("L must be followed by loudness indication"); + return 100; + } + if (isdigit(token[fieldx])) { + result = (int) scanint(); + newfieldx = fieldx; + if (token[fieldx] && token[fieldx]!=')' && token[fieldx]!=',') + fferror("Digits expected after L"); + else if (result > 127) { + fieldx = oldfieldx; + fferror("Maximum loudness of 127 will be used"); + fieldx = newfieldx; + result = 127; + } else if (result == 0) { + fieldx = oldfieldx; + fferror("Minimum loudness of 1 will be used"); + fieldx = newfieldx; + result = 1; + } + return result; + } + scansymb(symbol); + newfieldx = fieldx; + if ((i = strlen(symbol)) > 3 ) { /* maximum is 3, e.g. "ppp" */ + fieldx = oldfieldx; + fferror("Loudness field too long"); + fieldx = newfieldx; + return 100; + } + symbol[i + 1] = '\0'; /* pad short symbols with 0 */ + /* e.g. "p\0" -> "p\0\0" */ + for (i = 0; i <= 7; i++) { /* loop through possibilities */ + for (j = 0; j <= 2; j++) { /* test 3 characters */ + if (symbol[j] != loudtable[i].symbol[j]) + break; + } + if (j == 3) { + return loudtable[i].value; + } + } + fieldx = oldfieldx; + fferror("Bad loudness indication"); + fieldx = newfieldx; + return 100; +} + + +void domacro() +{ + int control_num; + int value; + if (isdigit(token[1])) { + control_num = (int) scanint(); + if (token[fieldx] == '(') { + fieldx++; + if (!isdigit(token[fieldx])) { + fferror("Control value expected"); + } else { + value = (int) scanint(); + if (token[fieldx] != ')') { + fferror("Missing close paren"); + } else { + fieldx++; + if (token[fieldx]) + fferror("Nothing expected after paren"); + else if (macctrlx < nmacroctrl - 1) { + macctrlnum[macctrlx] = control_num; + macctrlparmx[macctrlx] = value; + macctrldef[macctrlx] = NULL; + macctrlx++; + } else fferror("Too many controls"); + } + } + } else fferror("Missing paren"); + } else { + def_type def; + char symbol[100]; + scansymb(symbol); + if (fieldx == 1) fferror("Macro name expected"); + else if (token[fieldx] != '(') fferror("Open paren expected"); + else { + fieldx++; + def = def_lookup(symbol); + if (!def) { + fieldx = 1; + fferror("Undefined macro"); + } else { + long val; + macctrlnum[macctrlx] = 0; + macctrlparmx[macctrlx] = macctrlnextparm; + macctrldef[macctrlx] = def; + while (token[fieldx] != ')' && parseparm(&val)) { + macctrlparms[macctrlnextparm++] = (short) val; + macctrlnum[macctrlx]++; + if (token[fieldx] == ',') { + fieldx++; + } else if (token[fieldx] != ')') { + fferror("Unexpected character"); + break; + } + } + fieldx++; + macctrlx++; + } + } + } +} + + +/**************************************************************************** +* donextdur +* Effect: parse a next (N) command +* Implementation: +* The syntax is N followed by a duration, so save dur and use dosymdur() +* to parse the duration field. +* The form N is parsed directly with scanint(). +****************************************************************************/ + +private void donextdur() +{ + ndurp = TRUE; /* flag that N was given */ + if (isdigit(token[fieldx])) { + ntime = precise(scanint()); + ntime = scale(ntime, (ulong)time_scale, rate); + if (token[fieldx]) + fferror("Only digits were expected here"); + } else { + fieldx++; + ntime = dodur(); + } +} + +/**************************************************************************** +* dopitch +* Effect: parses a pitch command +****************************************************************************/ + +private int dopitch() +{ + int p, octave=0; + int octflag = FALSE; /* set if octave is specified */ + int oldfieldx = fieldx; + + p = pitchtable[token[fieldx-1]-'A']; + while (TRUE) { + if (token[fieldx] == 'S') { /* sharp */ + p++; + fieldx++; + } + else if (token[fieldx] == 'N') { /* skip */ + fieldx++; + } + else if (token[fieldx] == 'F') { /* flat */ + p--; + fieldx++; + } + else if (isdigit(token[fieldx]) && !octflag) { /* octave */ + octave = (int) scanint(); + octflag = TRUE; + } + else break; /* none of the above */ + } + if (octflag) p = (p-48) + 12 * octave; /* adjust p to given octave */ + else { /* adjust p to note nearest the default pitch */ + int octdiff = (p + 126 - pitch) / 12; + p = p + 120 - (octdiff * 12); + } + if (p > maxpitch) { /* pitch in range? */ + int newfield = fieldx; + fieldx = oldfieldx; + fferror("Pitch too high"); + fieldx = newfield; + p = maxpitch; + } + /* We really should test for end-of-field, but we don't know if we're + in a parameter list, so comma may or may not be legal */ + return p; +} + +/**************************************************************************** +* doprogram +* Effect: parses a program change (Z) command +****************************************************************************/ + +private void doprogram() +{ + register int program = (int) scanint(); + ctrlflag[PROGRAM_CTRL] = ctrlflag[0] = TRUE; + if (token[fieldx]) { + fferror("Z must be followed by digits only"); + } else if (program < minprogram) { + fieldx = 1; + fferror("Minimum program of 1 will be used"); + program = minprogram; + } else if (program > maxprogram) { + fieldx = 1; + fferror("Maximum program of 128 will be used"); + program = maxprogram; + } + ctrlval[PROGRAM_CTRL] = program - 1; +} + + +private void doramp() +{ + int values[2]; + time_type stepsize = 100L; /* default 10 per second */ + int index = 0; + ndurp = FALSE; + values[0] = values[1] = 0; + while (TRUE) { + linex += scan(); + fieldx = 1; + if (nullstring(token)) { + break; + } else if (index == 2) { /* must be stepsize in dur syntax */ + stepsize = dodur(); + } else { + int ctrlx = 0; + static int ctrl_map[] = { -BEND_CTRL, VOLUME, -TOUCH_CTRL, MODWHEEL }; + + switch (token[0]) { + case 'M': ctrlx++; /* modwheel */ + case 'O': ctrlx++; /* aftertouch */ + case 'X': ctrlx++; /* volume */ + case 'Y': /* pitch bend */ + + if (index < 2) { + macctrlnum[index] = ctrl_map[ctrlx]; + macctrlparmx[index] = (int) scanint(); + if (token[fieldx]) + fferror("Only digits expected here"); + macctrldef[index] = NULL; + } else fferror("Unexpected control"); + break; + case '~': + if (index < 2) { + domacro(); + if (token[fieldx]) fferror("Unexpected character"); + } else fferror("Unexpected control"); + break; + case 'T': + if (index < 2) fferror("Control expected"); + dotime(); + break; + case 'V': + if (index < 2) fferror("Control expected"); + dovoice(); + break; + case 'N': + if (index < 2) fferror("Control expected"); + donextdur(); + break; + default: + if (index < 2) fferror("Control expected"); + dur = dodur(); + break; + } + if (index == 1 && (macctrlnum[0] != macctrlnum[1] || + macctrldef[0] != macctrldef[1])) { + fferror("Controls do not match"); + } + } + index++; + } + if (index < 3) fferror("Expected 2 controls and step size"); + else { + if (macctrldef[0]) { + int i, j, n; + n = 0; + i = macctrlparmx[0]; + j = macctrlparmx[1]; + while (n < macctrlnum[0]) { + if (macctrlparms[i] != macctrlparms[j]) break; + n++; i++; j++; + } + if (n >= macctrlnum[0]) n = 0; + /* Note: duration shortened to prevent overlap with next ramp */ + insert_deframp(the_score, seqround(thetime), lineno, voice, + seqround(stepsize), trunc(dur) - 1, macctrldef[0], macctrlnum[0], + macctrlparms + macctrlparmx[0], n, macctrlparms[j]); + } else { + /* Note: duration shortened to prevent overlap with next ramp */ + insert_ctrlramp(the_score, seqround(thetime), lineno, voice, + seqround(stepsize), trunc(dur) - 1, + macctrlnum[0], macctrlparmx[0], macctrlparmx[1]); + } + } + /* advance the time only if an N field was given */ + if (ndurp) thetime += ntime; + else thetime += dur; +} + + +/**************************************************************************** +* dorate +* Effect: parses a !rate command +****************************************************************************/ + +private void dorate() +{ + linex += scan(); + if (!token[0]) + fferror("rate number expected"); + else { + long oldrate = rate; + rate = (int) scanint(); + if (token[fieldx]) + fferror("Only digits expected here"); + if (rate == 0) { + fieldx = 0; + fferror("Rate 100 will be used here"); + rate = 100L; + } + start = thetime; + /* adjust dur in case it is inherited by next note */ + dur = (dur * oldrate); + dur = dur / rate; + } +} + + +private void doset(vec_flag) + boolean vec_flag; +{ + ndurp = FALSE; + linex += scan(); + if (!token[0]) fferror("Variable name expected"); + else { + struct symb_descr *desc = &HASHENTRY(lookup(token)); + if (!desc->symb_type) fferror("Called function not defined"); + else if (vec_flag && (desc->symb_type != vec_symb_type)) { + fferror("This is not an array"); + } else if (!vec_flag && (desc->symb_type != var_symb_type)) { + fferror("This is not a variable"); + } else { + int numargs = 1 + vec_flag; + int value[2]; + int i; + int *address = desc->ptr.intptr; + value[0] = value[1] = 0; + i = 0; + while (TRUE) { + linex += scan(); + if (nullstring(token)) { + break; + } else if (isdigit(token[0]) || token[0] == '-' || + token[0] == '+') { + if (i < numargs) { + value[i++] = (int) scansgnint(); + if (token[fieldx]) + fferror("Only digits expected here"); + } else fferror(too_many_error); + } else { + switch (token[0]) { + case 'T': + fieldx = 1; + dotime(); + break; + case 'V': + fieldx = 1; + dovoice(); + break; + case 'N': + fieldx = 1; + donextdur(); + break; + default: + fieldx++; + if (i < numargs) { + value[i++] = seqround(dodur()); + } else fferror(too_many_error); + break; + } + } + } + if (vec_flag && i != 2) fferror("No index given"); + if (vec_flag) { + if (value[0] >= desc->size) { + fferror("Subscript out of bounds"); + return; + } + /* reduce to the seti case: */ + address += value[0]; /* compute the vector address */ + value[0] = value[1]; /* set up value[] and i as if */ + i--; /* this were seti, not setv */ + } + if (i != 1) fferror("No value given"); + insert_seti(the_score, seqround(thetime), lineno, voice, + address, value[0]); + /* advance the time only if an N field was given */ + if (ndurp) thetime += ntime; + } + } +} + +/**************************************************************************** +* dospecial +* Effect: parses special (those starting with "!") commands +****************************************************************************/ + +private void dospecial() +{ + switch (issymbol()) { + case sym_tempo: + dotempo(); + break; + case sym_rate: + dorate(); + break; + case sym_csec: + /* adjust dur for inheritance by next note */ + dur = (dur * 1000L) / time_scale; + time_scale = 1000L; + break; + case sym_msec: + dur = (dur * 100L) / time_scale; + time_scale = 100L; + break; + case sym_seti: + doset(FALSE); + break; + case sym_setv: + doset(TRUE); + break; + case sym_call: + docall(); + break; + case sym_ramp: + doramp(); + break; + case sym_clock: + doclock(); + break; + case sym_def: + dodef(); + break; + case sym_end: + end_flag = TRUE; + break; + default: + fferror("Special command expected"); + } + parseend(); /* flush the rest of the line */ +} + +/**************************************************************************** +* dosymdur +* Effect: parses a duration (^, %, S, I, Q, H, or W) command +****************************************************************************/ + +private time_type dosymdur() +{ + int i, dotcnt = 0; + long dotfactor; + time_type result = 0; + + for (i = 0; i < durtable_len; i++) { + if (durtable[i].symbol == token[fieldx-1]) { + /* the shift right is because durs are stored doubled because + * otherwise a 64th note would have the value 75/2: */ + result = precise(durtable[i].value) >> 1; + break; + } + } + if (i == durtable_len) { + fieldx--; + fferror("Duration expected: one of W, H, Q, I, S, %, or ^"); + return 0L; + } + while (token[fieldx]) { + if (token[fieldx] == 'T') { /* triplet notation */ + result = (result * 2) / 3; /* lose a bit but avoid scale() */ + fieldx++; + } + else if (token[fieldx] == '.') { /* dotted notation */ + dotcnt++; + fieldx++; + } + else if (token[fieldx] == '/') { + long divisor; + fieldx++; + divisor = scanint(); + if (divisor > 0) result = result / divisor; + else fferror("non-zero integer expected"); + } + else if (isdigit(token[fieldx])) { /* numbers are multipliers */ + result = result * scanint(); + } + else break; + } + dotfactor = 1L; + for (i=1; i<=dotcnt; i++) + dotfactor = dotfactor * 2; + result = (2 * result) - (result / dotfactor); + + return scale(result, 100L, tempo); /* time in milliseconds */ +} + +/**************************************************************************** +* dotempo +* Effect: parses a !tempo command +****************************************************************************/ + +private void dotempo() +{ + linex += scan(); + if (!token[0]) + fferror("Tempo number expected"); + else { + long oldtempo = tempo; + tempo = scanint(); + if (token[fieldx]) + fferror("Only digits expected here"); + if (tempo == 0) { + fieldx = 0; + fferror("Tempo 100 will be used here"); + tempo = 100L; + } + start = thetime; + /* adjust dur in case it is inherited by next note */ + if (symbolic_dur_flag) { + dur = (dur * oldtempo); + dur = dur / tempo; + } + } +} + +/**************************************************************************** +* dotime +* Effect: parses a time (T) command +* Implementation: see implementation of donextdur() +****************************************************************************/ + +private void dotime() +{ + if (isdigit(token[fieldx])) { + thetime = precise(scanint()); + thetime = scale(thetime, (ulong)time_scale, rate); + if (token[fieldx] ) + fferror("Only digits were expected here"); + } else { + fieldx++; + thetime = dodur(); + } + thetime += start; /* time is relative to start */ +} + +/**************************************************************************** +* dovoice +* Effect: parse a voice (V) command (the voice is the MIDI channel) +****************************************************************************/ + +private void dovoice() +{ + if (isdigit(token[fieldx])) { + voice = (int) scanint(); + if (token[fieldx]) + fferror("V must be followed by digits only"); + if (voice > MAX_CHANNELS) { + char msg[40]; + sprintf(msg, "number too high, using %d instead", MAX_CHANNELS); + fferror(msg); + voice = MAX_CHANNELS; + } + else if (voice < 1) { + fferror("number too low, using 1 instead"); + voice = 1; + } + } + else fferror("No digit after V"); +} + +/**************************************************************************** +* fferror +* Inputs: +* char *s: an error message string +* Effect: +* prints the line with the error +* puts a cursor (^) at the error location +* prints the error message (s) +* Implementation: +* this routine prints a carat under the character that +* was copied into token[fieldx]. E.g. if fieldx = 0, the +* carat will point to the first character in the field. +****************************************************************************/ + +private void fferror(s) + char *s; +{ + gprintf(ERROR, "%3d | %s", lineno, line); + marker(linex-strlen(token)+fieldx+1+6); + gprintf(ERROR, "Error: %s.\n", s); +} + +/**************************************************************************** +* init +* Effect: +* initializes the state variables +****************************************************************************/ + +private void init() +{ + int i; + + end_flag = FALSE; + + /* initial (default) values for all state variables */ + symbolic_dur_flag = TRUE; /* default dur is symbolic */ + for (i = 0; i < nctrl; i++) { + /* no initial control changes */ + ctrlflag[i] = FALSE; + ctrlval[i] = 0; + } + + lineno = 0; + pitch = seq_dflt_pitch; + loud = seq_dflt_loud; + voice = seq_dflt_voice; + time_scale = 1000L; + tempo = 100L; + rate = 100L; + dur = precise(600); /* default dur is quarter note */ + thetime = precise(0); + start = thetime; + ntime = 0L; + ticksize = 0L; + artic = 100; +} + +/**************************************************************************** +* ins_a_note +* Returns: +* boolean: TRUE on success, FALSE if not enough memory +* Effect: +* note events (if any) corresponding to the current line are inserted +* Implementation: +* if a note on should occur after a note off and doesn't, and the +* two notes have the same pitch, then the note off can cancel the +* note on. to make it unlikely that roundoff will cause this situation, +* dur is decreased by one half of a clock tick before rounding. +* also, phase2 gives precedence to note-offs that are simultaneous +* with note-ons. +****************************************************************************/ + +private boolean ins_a_note() +{ + long the_dur = (trunc(dur) * artic + 50) / 100; + int the_pitch = pitch; + event_type note; + if (rest_flag) the_pitch = NO_PITCH; + note = insert_note(the_score, seqround(thetime), lineno, voice, + the_pitch, the_dur, loud); + if (!note) return FALSE; + return TRUE; /* success! */ +} + +/**************************************************************************** +* ins_ctrls +* Returns: +* boolean: TRUE on success, FALSE if not enough memory +* Effect: +* control events corresponding to current line are inserted in score +* Implementation: +* ctrlflag[i] is TRUE if control i was specified in this line, so +* insert one control change for each ctrlflag[i] that is TRUE +****************************************************************************/ + +private boolean ins_ctrls() +{ + int i; + event_type ctrl; + + for (i = 1; i < nctrl; i++) { + if (ctrlflag[i]) { + ctrl = insert_ctrl(the_score, seqround(thetime), lineno, i, voice, + ctrlval[i]); + if (!ctrl) return FALSE; + ctrlflag[i] = FALSE; + ctrlval[i] = 0; + } + } + return TRUE; /* success! */ +} + +/**************************************************************************** +* issymbol +* Outputs: returns symbol number, or -1 if no match +* Assumes: token[1] has the symbol to look up (token[0] == '!') +****************************************************************************/ + +private int issymbol() +{ + int i, symb_num; + char *sym; + + for (symb_num = 0; symb_num < sym_n; symb_num++) { + sym = ssymbols[symb_num]; + i = 1; + while (TRUE) { + if (token[i] != *sym) break; + if (*sym == 0) return symb_num; + sym++; + i++; + } + } + return -1; +} + +/**************************************************************************** +* marker +* Inputs: +* int count: the number of characters to indent +* Effect: +* prints a carat (^) at the position specified on file stderr +****************************************************************************/ + +private void marker(count) +int count; +{ + int i; + char s[128]; + for (i=0; i<(count-1); s[i++]=' ') /* */ ; + s[count-1] = '^'; + s[count] = '\0'; + gprintf(ERROR,"%s\n",s); +} + +/***************************************************************** +* parseend +* Effect: +* parse the note terminator, either ",", ";", EOS or "\n" +* +****************************************************************/ + +private void parseend() +{ + boolean done = FALSE; + while (!done) { + linex += scan1(&line[linex]); + switch (token[0]) { + case ',': + ndurp = TRUE; /* switch that next time was specified */ + ntime = 0L; + done = TRUE; + break; + case ';': + case '\n': + case EOS: + done = TRUE; + break; + case ' ': + case '\t': + break; /* skip over blanks and scan1 again */ + default: + fferror("Unexpected token"); + linex += scan(); /* flush the token */ + break; + } + } +} + +/**************************************************************************** +* parsefield +* Effect: looks at first character of token and calls a parsing routine +* +****************************************************************************/ + +private void parsefield() +{ + fieldx = 1; + switch (token[0]) { + case 'T' : + dotime(); + break; + case 'U': + case 'W': + case 'H': + case 'Q': + case 'S': + case 'I': + case '%': + case '^': + dur = dodur(); + break; + case 'R': + do_a_rest(); + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + pitch = dopitch(); + pitch_flag = TRUE; + break; + case 'P': + pitch = doabspitch(); + pitch_flag = TRUE; + break; + case 'L': + loud = doloud(); + break; + case 'N': + donextdur(); + break; +/* case 'J': + * doctrl(1); + * break; + */ + case 'K': + doctrl(PSWITCH_CTRL); + break; + case 'M': + doctrl(MODWHEEL_CTRL); + break; + case 'O': + doctrl(TOUCH_CTRL); + break; + case 'X': + doctrl(VOLUME_CTRL); + break; + case 'Y': + doctrl(BEND_CTRL); + break; + case 'Z': + doprogram(); + break; + case 'V': + dovoice(); + break; + case '~': + domacro(); + break; + case '*': + docomment(); + break; + case '#': + doartic(); + break; + default : + doerror(); + break; + } +} + +/**************************************************************************** +* parsenote +* Effect: +* parses a note line -- control events (if any) and note event (if +* present) are inserted into score +* Assumes: +* line contains a string to be parsed +****************************************************************************/ + +private boolean parsenote() +{ + boolean out_of_memory = FALSE; + int i; + + ndurp = FALSE; + rest_flag = FALSE; + + /* this loop reads tokens for a note */ + while (token[0]) { + parsefield(); + linex += scan(); + } + + parseend(); /* take care of note terminator */ + + /* + * insert ctrl's first so that will come before the note. + */ + if (ctrlflag[0]) { + out_of_memory |= !ins_ctrls(); + /* don't reset ctrlflag[0], it's used below */ + } + + /* + * insert macro's + */ + for (i = 0; i < macctrlx; i++) { + event_type ctrl; + if (macctrldef[i] == NULL) { + ctrl = insert_macctrl(the_score, seqround(thetime), lineno, + macctrlnum[i], voice, macctrlparmx[i]); + } else { + ctrl = insert_macro(the_score, seqround(thetime), lineno, + macctrldef[i], voice, macctrlnum[i], + &(macctrlparms[macctrlparmx[i]])); + } + out_of_memory |= (ctrl == NULL); + } + + /* insert a note if + * (1) a pitch was specified OR + * (2) no control was specified and this is not a rest + * (it's a pitch by default) + * + * NOTE: program changes during rests are advised since + * synthesizers may not be able to process a program + * change followed immediately by a note-on. In fact, this + * is why we insert notes whose pitch is NO_PITCH -- so that + * the program change can be processed during the rest. + */ + if (pitch_flag || + (!ctrlflag[0] && !rest_flag && (macctrlx == 0))) { + out_of_memory |= !ins_a_note(); + } + + if (ndurp) thetime += ntime; + else thetime += dur; + + return out_of_memory; +} + + +private boolean parseparm(valptr) + long *valptr; +{ + register char c = token[fieldx]; + if (isdigit(c) || c == '-') { + *valptr = scansgnint(); + return TRUE; + } else { + switch (c) { + case 'P': + fieldx++; + *valptr = doabspitch(); + return TRUE; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + fieldx++; + *valptr = dopitch(); + return TRUE; + case 'U': + case 'W': + case 'H': + case 'Q': + case 'I': + case 'S': + case '%': + case '^': + fieldx++; + *valptr = seqround(dodur()); + return TRUE; + case 'L': + fieldx++; + *valptr = doloud(); + return TRUE; + case '\'': + fieldx++; + *valptr = token[fieldx]; + fieldx++; + if (token[fieldx] != '\'') { + fferror("single quote expected"); + } + fieldx++; + return TRUE; + default: + fferror("Parameter expected"); + return FALSE; + } + } +} + + + +/**************************************************************************** +* scale +* Inputs: +* time_type x +* int (ulong?) n, d +* Outputs: +* returns time_type: result of scaling x by n/d +****************************************************************************/ + +public time_type scale(x, n, d) + ulong x; + ulong n, d; +{ + ulong lo = (x & 0xFFFFL) * n; + ulong hi = (x >> 16) * n; + ulong res = hi / d; + lo = (((hi - (res * d)) << 16) + lo + (d >> 1)) / d; + return (time_type)( (res << 16) + lo ); +} + +/**************************************************************************** +* scan +* Inputs: +* char *start: the string to scan +* Outputs: +* returns int: the index of the next char in start to scan +* Effect: +* skips over leading blanks +* copies characters from start into token, converting to upper case +* scanning stops on delimiter: one of space, tab, newline, semicolon +****************************************************************************/ + +private int scan() +{ + char *start = line + linex; + register char c; + register int i = 0; + register int j = 0; + register int parens = 0; + + while (((c = start[i]) == ' ') || (c == '\t')) i++; + + while ((c = start[i]) != ' ' && c != '\n' && c != '\t' && c != EOS && + (c != ',' || token[0] == '~' || parens > 0) && c != ';') { + + if (islower(start[i])) token[j] = toupper(start[i]); + else token[j] = start[i]; + if (c == '(') parens++; + else if (c == ')') parens--; + j++; + i++; + } + token[j] = '\0'; + + fieldx = 0; + if (parens) fferror("Unbalanced parens"); + + return i; +} + +/**************************************************************************** +* scan1 +* Inputs: +* char *start: the string to scan +* Outputs: +* returns int: the index of the next char in start to scan +* Effect: +* copies one char from start into token, converting to upper case +****************************************************************************/ + +private int scan1(start) +char *start; +{ + int i = 0; + + token[0] = *start; + if (islower(token[0])) token[0] = toupper(token[0]); + + if (!nullstring(token)) { + token[1] = '\0'; + i = 1; + } + fieldx = 0; + return i; +} + +/**************************************************************************** +* scanint +* Outputs: +* returns long: the scanned integer +* Effect: +* scans an unsigned long from token, starting at fieldx +* fieldx is incremented to end of the integer +****************************************************************************/ + +private long scanint() +{ + long i = 0; + char c; + while ((c = token[fieldx])) { + if (isdigit(c)) { + i = (i*10) + (c - '0'); + fieldx++; + } else return i; + } + return i; +} + +private long scansgnint() +{ + if (token[fieldx] == '-') { + fieldx++; + return -scanint(); + } else { + if (token[fieldx] == '+') { + fieldx++; + } + return scanint(); + } +} + + +/* scansymb -- scan a symbol from the token */ +/**/ +private void scansymb(str) + char *str; +{ + char c; + while ((c = token[fieldx])) { + if (isdigit(c) || isalpha(c) || c == '_') { + *str++ = c; + fieldx++; + } else break; + } + *str = EOS; +} + +/**************************************************************************** +* seq_read +* Inputs: +* seq_type seq: the sequence to receive the score +* FILE *fp: input file +* Outputs: +* none +* Effect: +* parses score from input file and builds score data structure +****************************************************************************/ + +void seq_read(seq, fp) + seq_type seq; + FILE *fp; +{ + boolean out_of_memory = FALSE; /* set when no more memory */ + /* printf("seq_read: chunklist is 0x%x\n", seq->chunklist); */ + the_score = seq; /* current sequence is a global within this module */ + if (!seq) return; + init(); + lineno = 0; + + /* make sure line is well terminated or scan might run off the end */ + line[linesize - 1] = EOS; + line[linesize - 2] = '\n'; + + /* this loop reads lines */ + while ((fgets(line, linesize - 2, fp) != NULL) && !out_of_memory && + !check_aborted() && !end_flag) { + lineno++; + linex = 0; + /* this loop reads notes from a line */ + while ((line[linex] != EOS) && !out_of_memory) { + /* loop invariant: line[linex] is first char of next note */ + ctrlflag[0] = FALSE; /* other ctrlflags are reset by ins_ctrls() */ + macctrlx = 0; + macctrlnextparm = 0; + pitch_flag = FALSE; + linex += scan(); + if (!nullstring(token)) { + if (token[0] == '*') docomment(); + else if (token[0] == '!') dospecial(); + else out_of_memory = parsenote(); + } + else parseend(); + } + } + + if (out_of_memory) { + gprintf(ERROR, "Out of note memory at line %d,\n", lineno-1); + gprintf(ERROR, " the rest of your file will be ignored.\n"); + } + + if (check_aborted()) { + gprintf(ERROR, "User aborted score input,\n"); + gprintf(ERROR, " the rest of your file will be ignored.\n"); + if (abort_flag == BREAK_LEVEL) abort_flag = 0; + } + + /* fclose(fp); -- don't close the file; if you do, Nyquist's garbage + collector will close Nyquist's copy, and closing the file twice + in Linux will crash Nyquist */ + + gprintf(TRANS, "\nLoaded Adagio file with %ld note(s), %ld ctrl(s).\n\n", + seq_notecount(the_score), seq_ctrlcount(the_score)); +} diff --git a/cmt/seqread.h b/cmt/seqread.h new file mode 100644 index 0000000..1348574 --- /dev/null +++ b/cmt/seqread.h @@ -0,0 +1,7 @@ +/* seqread.h -- header file for users of seqread.c */ +/* Copyright 1989 Carnegie Mellon University */ + +#define name_length 255 +void seq_read(seq_type seq, FILE *fp); /* LISP: (SEQ-READ SEQ FILE) */ +time_type scale(ulong, ulong, ulong); /*now public for smf_write, smf_read*/ + diff --git a/cmt/seqwrite.c b/cmt/seqwrite.c new file mode 100644 index 0000000..fde0746 --- /dev/null +++ b/cmt/seqwrite.c @@ -0,0 +1,282 @@ +/* seqwrite -- write a seq in Adagio format to a file */ +/*************************************************************************** +* Change Log +* Date | Change +*-----------+-------------------------------------------------------------- +* 11-Mar-94 | Created Change Log +* 11-Mar-94 | PLu : Added private to function defs. +* 28-Apr-03 | DM : changed for portability, true->TRUE, false->FALSE +****************************************************************************/ + +#include "switches.h" + +#include + +#include "cext.h" +#include "midifns.h" +#include "timebase.h" +#include "seq.h" +#include "seqwrite.h" +#include "userio.h" +#include "record.h" + +private boolean next_event_time(); +private void write_event(); +private void write_velocity(FILE *f, int velocity); +private void write_voice(); +private void write_rest(FILE *f, event_type ev, boolean abs_flag); +private void write_time(); + +private boolean clock_started; +private long clock_half_tick; +private int new_tempo; +/* non-zero indicates a pending tempo change... */ +private time_type tempo_change_at; + +private int macro_count; +private int call_count; +private int deframp_count; +private int seti_count; + +private int last_velocity; +private int last_voice = seq_dflt_voice; + + +/* next_event_time -- get the time of the next event */ +/* + * NOTE: clock events are ignored (unless this is the first clock event) + */ +private boolean next_event_time(event, next_time) + event_type event; + time_type *next_time; +{ + while (event) { + if (vc_ctrl(event->nvoice) == ESC_CTRL && + event->value == CLOCK_VALUE && + clock_started) { + /* this is a clock event, ignore it: */ + event = event->next; + } else { + *next_time = event->ntime; + return TRUE; + } + } + return FALSE; +} + + +/* seq_write -- write a seq to a file */ +/* + * NOTE: if abs_flag, then write using absolute times ('T'), otherwise use + * relative timing ('N'). Also, only selected channels are written. + */ +void seq_write(seq_type seq, FILE *f, boolean abs_flag) +{ + event_type ev = seq_events(seq); + + last_velocity = seq_dflt_loud; + last_voice = seq_dflt_voice; + + fprintf(f, "!MSEC\n"); + clock_started = FALSE; + tempo_change_at = 0; + + macro_count = 0; + call_count = 0; + deframp_count = 0; + seti_count = 0; + + while (ev) { + write_event(seq, ev, f, abs_flag); + ev = ev->next; + } + if (macro_count) gprintf(TRANS, "%d macros ignored.\n", macro_count); + if (call_count) gprintf(TRANS, "%d calls ignored.\n", call_count); + if (deframp_count) gprintf(TRANS, "%d deframps ignored.\n", deframp_count); + if (seti_count) gprintf(TRANS, "%d setis ignored.\n", seti_count); +} + + +private char ctrl_letter[] = "?KMOXYZ"; + +/* write_event -- write a single event to a file */ +/**/ +private void write_event(seq, event, f, abs_flag) + seq_type seq; + event_type event; + FILE *f; + boolean abs_flag; +{ + int voice = vc_voice(event->nvoice); + + /* process all current (and earlier) events */ + if (is_note(event)) { /*** a note or rest ***/ + /* if this note is not a rest, write it */ + if (event->value != NO_PITCH && + (seq_channel_mask(seq) & + (1 << (voice - 1)))) { + write_pitch(f, event->value); + fprintf(f, " U%ld ", event->u.note.ndur >> 8); + write_velocity(f, (int) (event->u.note.ndur & 0xFF)); + write_voice(f, voice); + write_time(f, event, abs_flag); + } + } else { /*** a control command ***/ + switch (vc_ctrl(event->nvoice)) { + case PSWITCH_CTRL: + case MODWHEEL_CTRL: + case TOUCH_CTRL: + case VOLUME_CTRL: + case BEND_CTRL: + fprintf(f, "%c%d ", ctrl_letter[vc_ctrl(event->nvoice)], + event->value); + write_voice(f, voice); + write_time(f, event, abs_flag); + break; + case PROGRAM_CTRL: + fprintf(f, "Z%d ", event->value + 1); + write_voice(f, voice); + write_time(f, event, abs_flag); + break; + case ESC_CTRL: + switch (event->value) { + case CALL_VALUE: + call_count++; + write_rest(f, event, abs_flag); + /* !call's are not written because there isn't enough + * information. A future version of seq should store + * the hash table entry instead of the address of the + * routine (then we could get string name of the call) + * and the number of arguments. + */ + break; + case CLOCK_VALUE: + /* design for !clock: for absolute (absflag) files, put the + clocks (if any) at the end where they can be edited out + easily. For relative files, keep the clocks in-line. + On each clock, write a !tempo command inferred by the + clock and followed by the !clock. Because the clock + event comes before the actual tempo change, the chnage + must be delayed by a half tick except for the first one. + */ + if (abs_flag) break; + new_tempo = (2500L << 16) / event->u.clock.ticksize; + if (clock_started) { + tempo_change_at = event->ntime + clock_half_tick; + } else { + fprintf(f, "!tempo %d\n", new_tempo); + fprintf(f, "!clock\n"); + clock_started = TRUE; + } + clock_half_tick = (event->u.clock.ticksize) >> 17; + break; + case MACCTRL_VALUE: + fprintf(f, "~%d(%d) ", event->u.macctrl.ctrl_number, + event->u.macctrl.value); + write_voice(f, voice); + write_time(f, event, abs_flag); + break; + case MACRO_VALUE: + macro_count++; + write_rest(f, event, abs_flag); + /* macros are not written because there isn't enough + * information. A future version of seq should store + * the number of arguments in the event, or better yet, + * in the definition. Send complaints to RBD! + */ + break; + case CTRLRAMP_VALUE: + fprintf(f, "!ramp ~%d(%d) ~%d(%d) U%d U%d ", + event->u.ramp.ctrl, event->u.ramp.u.ctrl.from_value, + event->u.ramp.ctrl, event->u.ramp.u.ctrl.to_value, + (int)event->u.ramp.step, + (int)event->u.ramp.dur); + write_voice(f, voice); + write_time(f, event, abs_flag); + break; + case DEFRAMP_VALUE: + deframp_count++; + write_rest(f, event, abs_flag); + /* See MACRO_VALUE above for why this isn't implemented. */ + break; + case SETI_VALUE: + seti_count++; + write_rest(f, event, abs_flag); + /* !seti and !setv are not implemented -- a future version + * of seq should save more information so we can reconstruct + * the Adagio source command. + */ + break; + default: + gprintf(TRANS, "unexpected ESC_CTRL value\n"); + break; + } + break; + default: + gprintf(TRANS, "unexpected seq data\n"); + break; + } + } +} + + +/* write_rest -- write a rest (in place of an event) */ +/**/ +private void write_rest(FILE *f, event_type ev, boolean abs_flag) +{ + fprintf(f, "R "); + write_time(f, ev, abs_flag); +} + + +/* write_time -- write the final field on the line with N or T command */ +/**/ +private void write_time(f, event, abs_flag) + FILE *f; + event_type event; + boolean abs_flag; +{ + time_type next_time; + + if (abs_flag) { + fprintf(f, "T%ld\n", event->ntime); + return; + } + + if (next_event_time(event->next, &next_time)) { + if (tempo_change_at && (next_time >= tempo_change_at)) { + fprintf(f, "N%ld\n!TEMPO %d\n!CLOCK\nR U%ld\n", + tempo_change_at - event->ntime, + new_tempo, next_time - tempo_change_at); + tempo_change_at = 0; + } else { + fprintf(f, "N%ld\n", next_time - event->ntime); + } + } else { + fprintf(f, "\n"); + } +} + + +/* write_velocity -- write the velocity field */ +/**/ +private void write_velocity(FILE *f, int velocity) +{ + if (last_velocity != velocity) { + last_velocity = velocity; + fprintf(f, "L%d ", velocity); + } +} + + +/* write_voice -- write the voice field */ +/**/ +private void write_voice(f, voice) + FILE *f; + int voice; +{ + if (last_voice != voice) { + last_voice = voice; + fprintf(f, "V%d ", voice); + } +} diff --git a/cmt/seqwrite.h b/cmt/seqwrite.h new file mode 100644 index 0000000..3f9ab6b --- /dev/null +++ b/cmt/seqwrite.h @@ -0,0 +1,6 @@ +/* seqwrite.h -- write adagio format file */ +/* Copyright 1994 Carnegie Mellon University */ + +void seq_write(seq_type seq, FILE *f, boolean abs_flag); + /* LISP: (SEQ-WRITE SEQ FILE BOOLEAN) */ + diff --git a/cmt/swlogic.h b/cmt/swlogic.h new file mode 100644 index 0000000..e09bd6c --- /dev/null +++ b/cmt/swlogic.h @@ -0,0 +1,197 @@ +/******************************************************************** +* swlogic.h - switch logic, loaded from various versions of switch.h +* +* Copyright 1989 Carnegie Mellon University +* +*********************************************************************/ + +/* + * When included, one of the following should be defined: + * AZTEC (manx compiler, implies AMIGA) + * THINK_C (Think C compiler, implies Macintosh) + * __MWERKS__ (Metrowerks C compiler, implies Macintosh) + * LATTICE & DOS (Lattice compiler for IBM PC/AT/XT/CLONES) + * MICROSOFT & DOS (Microsoft compiler, implies IBM PC/AT/XT/CLONES) + * UNIX (emulator for UNIX) + * UNIX_ITC (ITC code for RS6000) + * UNIX_MACH (MACH ukernel system) + */ + +/*------------------------------------------*/ +/* Other switches that might be defined in switches.h are as follows: */ +/* APPLICATION, SPACE_FOR_PLAY, MAX_CHANNELS */ +/*------------------------------------------*/ + +/* We're moving toward the elimination of switches.h, so try to map + * predefined constants into our standard constants shown above: + */ + +/* CHANGE LOG + * -------------------------------------------------------------------- + * 28Apr03 dm new conditional compilation structure + * 28Apr03 rbd remove macro redefinitions: MICROSOFT + */ + + + +/* Microsoft C compiler: */ +#ifdef _MSC_VER +#endif +#ifdef _MSDOS +#define DOS +#endif + +/* Quick C compiler: */ +#ifndef DOS +#ifdef MICROSOFT +#define DOS +#endif +#endif + +/* Borland C compiler: */ +#ifdef __BORLANDC__ +#define BORLAND +#define DOS +#endif + +/* Borland Turbo C compiler: */ +#ifdef __TURBOC__ +#define BORLAND] +#define DOS +#endif + +/* SGI systems */ +#ifdef sgi +#ifndef UNIX +#define UNIX +#endif +#define UNIX_IRIX +#define MAX_CHANNELS 32 +#endif + +/* APPLICATION -- define APPLICATION if you want to disable + * looking for command line switches in the midi interface. + * I think this feature is here for the Piano Tutor project + * and you should not define APPLICATION for CMU Midi Toolkit + * projects (APPLICATION is a poor choice of terms): + */ + + +/* memory space management (system dependent): + * SPACE_FOR_PLAY must be enough space to allow + * seq to play a score. This may include space for + * note-off events, I/O buffers, etc. + */ +#ifndef SPACE_FOR_PLAY +#define SPACE_FOR_PLAY 10000L +#endif + + +/* How many MIDI channels are there? MACINTOSH can use 2 ports, + * so it supports 32 channels. Others have one port, 16 channels. + * On the other hand, if you don't have all the MIDI ports plugged + * into MIDI interfaces, CMT will just hang, so I'll compile with + * just 16 channels. The 32 channel option for the Mac is untested. + */ +#ifndef MAX_CHANNELS +#define MAX_CHANNELS 16 +#endif + + +/*------------------------------------------*/ +/* Now we get to the "logic": define things as a function of what + * was defined in switches.h + */ + +#ifdef THINK_C +#define MACINTOSH +#endif + +#ifdef __MWERKS__ +#define MACINTOSH +#endif + +#ifdef MACINTOSH +#define MACINTOSH_OR_DOS +#define MACINTOSH_OR_UNIX +/* I don't know if THINK_C defines this and we need it for a few prototypes... */ +#ifndef __STDC__ +#define __STDC__ +#endif +#ifndef TAB_WIDTH +#define TAB_WIDTH 4 +#endif +#endif + +#ifndef TAB_WIDTH +#define TAB_WIDTH 8 +#endif + +/* + * If MIDIMGR is defined, compile for the Apple MIDI Manager + * (Non MIDI manager code is no longer supported) + */ +#ifdef MACINTOSH +/* under Nyquist, the MidiMgr is not used, so you can't + * receive or send Midi as in CMU MIDI Toolkit; however, + * much of CMU MIDI Toolkit is used for Midi file IO + */ +#ifndef NYQUIST +#define MIDIMGR +#endif +#define USE_VSPRINTF +#endif + +#ifdef BORLAND +#define DOS +#endif + +#ifdef LATTICE322 +#define DOS +#define OLD_PROTOTYPES +#endif + +#ifdef UNIX_ITC +#define UNIX +#define ITC +#endif + +#ifdef UNIX_MACH +#define UNIX +#define ITC +#endif + +/* USE_VSPRINTF says vsprintf() is defined */ +#ifdef ITC +#define USE_VSPRINTF +#endif +#ifdef AZTEC +#define USE_VSPRINTF +#endif + + +/* DOTS_FOR_ARGS says ANSI "..." notation is recognized */ +#ifdef __STDC__ +#define DOTS_FOR_ARGS +#endif +#ifdef UNIX_ITC +#define DOTS_FOR_ARGS +#endif +#ifdef BORLAND +#define DOTS_FOR_ARGS +#endif +#ifdef MICROSOFT +#define DOTS_FOR_ARGS +#endif + +#ifdef DOS +#define MACINTOSH_OR_DOS +#else +#define huge +#endif + +#ifdef UNIX +#define MACINTOSH_OR_UNIX +#endif + +#define SWITCHES diff --git a/cmt/tempomap.c b/cmt/tempomap.c new file mode 100644 index 0000000..e56b9bf --- /dev/null +++ b/cmt/tempomap.c @@ -0,0 +1,124 @@ +/* tempomap.c -- used by midifile reader to record and lookup time maps */ +/* + * This module is designed to provide tempo change list insert and + * lookup facilities. The exact interpretation of beat and tempo are + * defined in one function, elapsed_time, which takes a tempo and a + * number of beats, and returns time. + */ + +#include "cext.h" +#include "midifns.h" +#include "timebase.h" +#include "moxc.h" +#include "seq.h" +#include "tempomap.h" + +static time_type elapsed_time(); + +/* tempomap_create -- create a tempomap */ +/**/ +tempomap_type tempomap_create() +{ + tempochange_type tempochange = tempochange_alloc(); + tempomap_type tempomap = tempomap_alloc(); + tempomap->hint = tempomap->entries = tempochange; + tempochange->beat = 0L; + /* default tempo is 120 (= 50000microsec/beat) and 24 divisions/quarter */ + /* this may be overridden by a tempomap_insert */ + tempochange->tempo = 500000L / 24; + tempochange->rtime = 0L; + tempochange->next = NULL; + return tempomap; +} + + +/* tempomap_free -- deallocate storage for tempo map */ +/**/ +void tempomap_free(tm) + tempomap_type tm; +{ + while (tm->entries) { + tempochange_type tc = tm->entries; + tm->entries = tc->next; + tempochange_free(tc); + } + memfree(tm, sizeof(tempomap_node)); +} + + +/* tempomap_insert -- insert a tempo change into the map */ +/**/ +void tempomap_insert(tempomap, beat, tempo) + tempomap_type tempomap; + long beat; /* beat division number */ + long tempo; /* microseconds per beat division */ +{ + tempochange_type tempochange = tempochange_alloc(); + register tempochange_type prev; + register tempochange_type next; + + tempochange->tempo = tempo; + tempochange->beat = beat; + + if ((!(tempomap->hint->next)) || + (tempomap->hint->beat > beat)) + tempomap->hint = tempomap->entries; + + /* find the insert point */ + for (prev = tempomap->hint; + (next = prev->next) && (next->beat <= beat); + prev = next); + + /* make the insert */ + tempochange->next = next; + prev->next = tempochange; + tempomap->hint = prev; + + /* update the real time of each change */ + for (tempochange = prev; + tempochange->next; + tempochange = tempochange->next) { + tempochange->next->rtime = + tempochange->rtime + + elapsed_time(tempochange->tempo, + tempochange->next->beat - tempochange->beat); + } +} + + +/* tempomap_lookup -- convert beat to 4us time */ +/* + * The returned time is real time in units of 4us. + */ +time_type tempomap_lookup(tempomap, beat) + tempomap_type tempomap; + long beat; +{ + register tempochange_type prev; + register tempochange_type next; + + if ((!(tempomap->hint->next)) || + (tempomap->hint->beat > beat)) + tempomap->hint = tempomap->entries; + + /* find the last inflection point */ + for (prev = tempomap->hint; + (next = prev->next) && (next->beat <= beat); + prev = next); + + /* interpolate */ + return prev->rtime + + elapsed_time(prev->tempo, beat - prev->beat); +} + + +/* elapsed_time -- compute the real elapsed time at a given tempo */ +/* + * the time returned is in units of 4us. + */ +static time_type elapsed_time(tempo, beat) + long tempo; + long beat; +{ + return (time_type)((tempo * beat) >> 2); +} diff --git a/cmt/tempomap.h b/cmt/tempomap.h new file mode 100644 index 0000000..a437c25 --- /dev/null +++ b/cmt/tempomap.h @@ -0,0 +1,25 @@ +/* tempomap.h -- list of tempo changes */ + +typedef struct tempochange_struct { + struct tempochange_struct *next; + time_type rtime; + long beat; + long tempo; +} tempochange_node, *tempochange_type; + + +typedef struct tempomap_struct { + tempochange_type entries; + tempochange_type hint; +} tempomap_node, *tempomap_type; + + +#define tempochange_alloc() (tempochange_type) memget(sizeof(tempochange_node)) +#define tempochange_free(tc) memfree(tc, sizeof(tempochange_node)) +#define tempomap_alloc() (tempomap_type) memget(sizeof(tempomap_node)) +tempomap_type tempomap_create(void); +void tempomap_free(tempomap_type tm); +void tempomap_insert(tempomap_type tempomap, long beat, long tempo); +time_type tempomap_lookup(tempomap_type tempomap, long beat); + + diff --git a/cmt/timebase.c b/cmt/timebase.c new file mode 100644 index 0000000..0bb1713 --- /dev/null +++ b/cmt/timebase.c @@ -0,0 +1,322 @@ +/* timebase.c -- management of calls, time bases and heaps for moxc */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 2-Apr-91 | JDW : further changes +* 21-Mar-92 | GWL : abort recovery +* 28-Apr-03 | DM : true->TRUE +*****************************************************************************/ + + +#include "stdio.h" +#include "cext.h" +#include "userio.h" +#include "midifns.h" +#include "timebase.h" +#include "moxc.h" + + +timebase_type timebase_queue = NULL; /* waiting to run timebase queue */ +call_type callfree = NULL; /* free list */ + +private void fatal(); + + +/**************************************************************************** +* timebase_create +* Inputs: +* maxsize is the initial size of the heap +* Outputs: +* returns an initialized timebase_type +****************************************************************************/ + +timebase_type timebase_create(maxsize) + int maxsize; +{ + static char *error_msg = "Out of memory in timebase_create()"; + timebase_type base = (timebase_type) memget(sizeof(timebase_node)); + if (!base) fatal(error_msg); + base->next = NULL; + base->next_time = MAXTIME; + base->virt_base = 0L; + base->real_base = 0L; + base->rate = 256L; + base->heap_size = 0; + base->heap_max = maxsize; + base->heap = (call_type *) memget(sizeof(call_type) * maxsize); + if (!base->heap) fatal(error_msg); + return base; +} + +/**************************************************************************** +* callinsert +* Inputs: +* timebase_type base: the time base and heap +* call_type call: the call to insert in heap +* Outputs: + none +* Implementation: +* linear insertion; to be changed to heap +****************************************************************************/ + +void callinsert(base, call) + timebase_type base; + call_type call; +{ + int i; + register call_type *heap = base->heap; + + /* handle overflow -- this should never be executed if the user + * gives the right initial heap size + */ + base->heap_size++; + if (base->heap_size >= base->heap_max) { + call_type *new_heap = (call_type *) + memget((base->heap_max << 1) * sizeof(call_type)); + int i; + call_type *oldptr; + call_type *newptr; + if (!new_heap) { + gprintf(TRANS, "Out of space, can't allocate new heap\n"); + EXIT(1); + } + + oldptr = base->heap; + newptr = new_heap; + + for (i = base->heap_max; i > 0; i--) *newptr++ = *oldptr++; + memfree((char *) heap, base->heap_max * sizeof(call_type)); + base->heap = heap = new_heap; + base->heap_max = (base->heap_max << 1); + } + + /* now do the heap insert */ + i = base->heap_size; + while (i > 1) { + int parent = i >> 1; + if (heap[parent]->u.e.time < call->u.e.time || + (heap[parent]->u.e.time == call->u.e.time && + heap[parent]->u.e.priority <= call->u.e.priority)) break; + heap[i] = heap[parent]; + i = parent; + } + heap[i] = call; + + /* if next_time might change, reinsert base into queue */ + if (heap[1] == call) { + remove_base(base); + insert_base(base); + } +} + +/**************************************************************************** +* callshow +* Inputs: +* calltype call: the call to show +* Effect: +* prints a description of call +* Assumes: +* call is not null +****************************************************************************/ +void callshow(call) + call_type call; +{ + int i; + gprintf(TRANS,"address: %lx\n", (ulong)call); + gprintf(TRANS,"time: %ld\n", call->u.e.time); + gprintf(TRANS,"routine: %lx\n", (ulong)call->u.e.routine); + gprintf(TRANS,"parameters:"); + for (i = 0; i < MAX_CALL_ARGS; i++) { + gprintf(TRANS, " %d", call->u.e.p.arg[i]); + } + gprintf(TRANS, "\n"); +} + +/*************************************************************** +* fatal +* +* Input : msg: a message to be displayed +* Effect: print message and exit program +***************************************************************/ + +private void fatal(msg) + char *msg; +{ + gprintf(FATAL, msg); + EXIT(1); +} + +/*************************************************************** +* timebase_free +* +* Input : a time base +* Effect: deallocate the time base +***************************************************************/ + +void timebase_free(timebase) + timebase_type timebase; +{ + remove_base(timebase); + if (timebase->heap) { + memfree((char *) timebase->heap, + (timebase->heap_max * sizeof(call_type))); + } + memfree((char *) timebase, sizeof(timebase_node)); +} + +/*************************************************************** +* insert_base +* +* Input : a time base not in the list +* Effect: insert timebase at the appropriate spot in the list +* computes the next_time field from the top of the heap +***************************************************************/ + +void insert_base(timebase) + timebase_type timebase; +{ + register timebase_type *ptr = &timebase_queue; + register time_type next_time = MAXTIME; + /* compute next_time */ + if (timebase->heap_size != 0) { + register call_type call = timebase->heap[1]; + /* virt to real calculation */ + next_time = (virt_to_real_256(timebase, call->u.e.time) & + 0xFFFFFF00) + call->u.e.priority; +/* gprintf(TRANS, + "insert next_time is %ld, vt %ld, rb %ld, vb %ld rt %ld\n", + next_time, timebase->heap[1]->u.e.time, + timebase->real_base, timebase->virt_base, timebase->rate); + */ + } + timebase->next_time = next_time; + + if (next_time != MAXTIME) { + /* insert into the list */ + while (TRUE) { + if (! *ptr) { + *ptr = timebase; + timebase->next = NULL; + return; + } else if ((*ptr)->next_time >= next_time) { + timebase->next = *ptr; + *ptr = timebase; + return; + } else ptr = &((*ptr)->next); + } + } +} + +/*************************************************************** +* remove_base +* +* Input : timebase +* Effect: if timebase is in the queue, remove it +***************************************************************/ + +void remove_base(timebase) + timebase_type timebase; +{ + timebase_type *ptr = &timebase_queue; + while (*ptr) { + if (*ptr == timebase) { + *ptr = timebase->next; + return; + } else ptr = &((*ptr)->next); + } +} + +/*************************************************************** +* remove_call +* +* Input : a timebase -- passed as a global +* Assumes: a_timebase->heap_size > 0 +* Returns: the earliest call in the queue +* Effect: removes the earliest call in the queue +***************************************************************/ + +call_type remove_call(timebase_type a_timebase) +{ + register call_type *heap = a_timebase->heap; + call_type result = heap[1]; + register call_type large; + int i = 1; + int child = i << 1;; + large = heap[a_timebase->heap_size--]; + while (child <= a_timebase->heap_size) { + if (child + 1 <= a_timebase->heap_size) { + if (heap[child + 1]->u.e.time < heap[child]->u.e.time || + (heap[child + 1]->u.e.time == heap[child]->u.e.time && + heap[child + 1]->u.e.priority < heap[child]->u.e.priority)) + child++; + } + /* child is now the index of the least child */ + if (large->u.e.time < heap[child]->u.e.time || + (large->u.e.time == heap[child]->u.e.time && + large->u.e.priority <= heap[child]->u.e.priority)) break; + /* swap */ + heap[i] = heap[child]; + i = child; + child = i << 1; + } + heap[i] = large; + return result; +} + +/*************************************************************** +* set_rate +* +* Input : timebase and new rate +* Effect: makes the current rate of timebase be rate +***************************************************************/ + +void set_rate(base, rate) + timebase_type base; + time_type rate; +{ + if (base == timebase) base->virt_base = virttime; + else base->virt_base = real_to_virt(base, eventtime); + base->real_base = eventtime; + base->rate = rate; +/* gprintf(TRANS, "new real_base %ld virt_base %ld\n", + base->real_base, base->virt_base); + */ + remove_base(base); + insert_base(base); +} + +/*************************************************************** +* set_virttime +* +* Input : virtual time +* Effect: makes the current virtual time of timebase be vtime +***************************************************************/ + +void set_virttime(base, vtime) + timebase_type base; + time_type vtime; +{ + base->real_base = eventtime; + base->virt_base = vtime; + if (base == timebase) virttime = vtime; + remove_base(base); + insert_base(base); +} + +/*************************************************************** +* timebase_use +* +* Input : a timebase to use for scheduling +* Effect: sets up globals: timebase, virttime +***************************************************************/ + +void timebase_use(base) + register timebase_type base; +{ + if (timebase != base) { + timebase = base; + virttime = real_to_virt(base, eventtime); + } +} diff --git a/cmt/timebase.h b/cmt/timebase.h new file mode 100644 index 0000000..9da4e90 --- /dev/null +++ b/cmt/timebase.h @@ -0,0 +1,85 @@ +/* timebase.h -- management of calls, time bases and heaps for moxc */ + +#define STOPRATE 0xFFFFL + +/*************************************************************************** +* call structure +****************************************************************************/ + +/* ---NOTE!!! if you change MAX_CALL_ARGS, change CALLARGS macro below--- */ +#define MAX_CALL_ARGS 8 +typedef struct call_args_struct { + long arg[MAX_CALL_ARGS]; +} call_args_node; + +typedef struct call { + union { + struct { + time_type time; /* virtual time of this call */ + int priority; /* an 8-bit the priority, low priority first */ + int (*routine)(); /* who to call */ + call_args_node p; /* what to pass */ + } e; + struct call *p; /* used to link free calls */ + } u; +} *call_type, call_node; + +/* CALLARGS - given a call_type, this macro generates an argument list */ +/* + * NOTE: originally, I thought call->u.e.p (a structure), would do it, but + * Lattice C for the Amiga has a compiler bug, and even in places where the + * bug doesn't show up, the code generated for the structure passing is + * a sequence of two loops: one to copy data to a local area on the stack, + * and one to push this data (a byte at a time!) to the top of the stack. + * With Lattice (and perhaps others, I haven't checked), it's better to + * push the data in-line. + */ +#ifdef LATTICE +#define CARG(n) call->u.e.p.arg[n] +#define CALLARGS(call) CARG(0), CARG(1), CARG(2), CARG(3), \ + CARG(4), CARG(5), CARG(6), CARG(7) +#else +#define CALLARGS(call) call->u.e.p +#endif + +/*************************************************************************** +* timebase structure +****************************************************************************/ + +typedef struct timebase_struct { + struct timebase_struct *next; /* used for list */ + time_type next_time; + time_type virt_base; + time_type real_base; + time_type rate; /* ratio of real/virt time, STOPRATE or more is infinity */ + short heap_size; + short heap_max; + call_type *heap; +} timebase_node, *timebase_type; + +extern timebase_type timebase_queue; + +#define call_alloc() ((call_type) memget(sizeof(call_node))) +#define call_free(c) memfree((char *) (c), sizeof(call_node)) + +timebase_type timebase_create(int maxsize); +void callinsert(timebase_type base, call_type call); +void callshow(call_type call); +void timebase_free(timebase_type timebase); +void insert_base(timebase_type timebase); +void remove_base(timebase_type timebase); +call_type remove_call(timebase_type a_timebase); +void set_rate(timebase_type base, time_type rate); +void set_virttime(timebase_type base, time_type vtime); +void timebase_use(timebase_type base); + +#define real_to_virt(base, rtime) ((base)->rate == 0 ? MAXTIME : \ + ((base)->virt_base + (((rtime) - (base)->real_base) << 8) / (base)->rate)) + +#define virt_to_real(base, vtime) ((base)->rate >= STOPRATE ? \ + ((base)->virt_base > vtime ? (base)->real_base : MAXTIME) : \ + (base)->real_base + ((((vtime) - (base)->virt_base) * (base)->rate) >> 8)) + +#define virt_to_real_256(base, vtime) ((base)->rate >= STOPRATE ? \ + ((base)->virt_base > vtime ? (base)->real_base << 8 : MAXTIME) : \ + ((base)->real_base << 8) + ((((vtime) - (base)->virt_base) * (base)->rate))) diff --git a/cmt/userio.c b/cmt/userio.c new file mode 100644 index 0000000..e8d4fdb --- /dev/null +++ b/cmt/userio.c @@ -0,0 +1,1285 @@ +/* userio.c -- handy user interface functions */ +/* Copyright 1989 Carnegie Mellon University */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 21-May-86 | Created +* 11-Aug-87 | F.H: Added clear_abort(), stop() +* May-88 | JCD : AMIGA VERSION +* 11-Jun-88 | RBD: disable printing of GDEBUG messages +* 12-Oct-88 | JCD : EXCLUSIVE AMIGA VERSION +* 13-Apr-89 | JCD : New portable version. +* 5-Apr | JDW : Further changes +* 2-Mar-92 | GWL : Little changes to satisfy compiler +* 19-Nov-92 | JDZ : Mach tty io threads +* 28-Apr-03 | DM : portability changes. true->TRUE, false->FALSE +*****************************************************************************/ + +/* Notes on ascii input: + +Input is complicated because different systems have varying input models, +especially with regard to handling ^C. The CMT model handles ^C and ^G as +special characters, and these do not cause software interrupts. Also, the +lowest level of the CMT model does not support line editing: Every character +is acted upon immediately. This has two implications: +(1) CMT must "read ahead" looking for ^C and ^G characters. This is handled +by the check_aborted() procedure, which reads characters into the +type_ahead[] array. +(2) CMT must do its own line editing. This is handled by the ggets() routine. + +A number of functions support ascii input, only some of which are visible +to the application programmer. Let's start at the top-level and work down; +each of the following calls the routine below it: + +ggets() - gets a string with line editing support. This function is fairly +machine independent, except for some backspace-and-erase control character +code sequences. + +ggetchar() - gets a raw character. This function calls wait_ascii() + and echoes it. Note that it may return ABORT_CHAR or BREAK_CHAR. + +wait_ascii() - gets a raw character without echo and without character +code translation. wait_ascii() either polls get_ascii() or uses some +kind of system-dependent event waiting. Returns ABORT_CHAR or BREAK_CHAR +immediately if abort_flag has been set, regardless of whether there is +new ascii input. + +get_ascii() - checks to see if a character is available. (Using +check_aborted().) +The only dependency here is on the Amiga, we restart input when buffer goes +from full to non-full. + +check_aborted() - looks for input by calling ascii_input. If found, +put the input into the type_ahead[] buffer. Returns abort_flag. + +ascii_input() - lowest level of input; just gets a character if there is +one. Does conversion from RETURN (\r) to EOL (\n). The Amiga handles +this in-line directly in check_aborted(). + +Here's a quick summary: + +ggets = ggetchar + line editing & string building +ggetchar = wait_ascii + character echo +wait_ascii = get_ascii + wait for character +get_ascii = check_aborted + pull char from buffer +check_aborted = ascii_input + test for ^C,^G + put in buffer +ascii_input = poll for char + CR->EOL conversion + +*/ + +#include "switches.h" + +#include +#include +#if HAS_STDLIB_H +#include /* normal case */ +#endif + + +#ifdef MACINTOSH +# include "StandardFile.h" + /* added for ThinkC 7 */ +# ifdef THINK_C +# include +# endif +#endif + +#ifdef AMIGA + +# ifdef AZTEC +# include "functions.h" +# else /* LATTICE */ +# include "amiga.h" +# include "stdarg.h" +# endif + +# include "intuition/intuition.h" +# include "devices/console.h" +#endif + +#include "ctype.h" +#include "stdio.h" +#include "cext.h" +#include "userio.h" + +#ifdef MICROSOFT +#include "signal.h" +#endif + +#ifdef UNIX_MACH +#include +#include +extern char a_in; +extern int a_in_flag; +extern int i_am_running; +#ifdef RTMach +extern itc_mutex_t a_mutex; +extern itc_condition_t a_cond, a_in_cond; +#define A_LOCK() itc_mutex_lock(&a_mutex) +#define A_UNLOCK() itc_mutex_unlock(&a_mutex) +#else /* RTMach */ +extern struct mutex a_mutex; +extern struct condition a_cond, a_in_cond; +#define A_LOCK() mutex_lock(&a_mutex) +#define A_UNLOCK() mutex_unlock(&a_mutex) +#endif /* RTMach */ +#endif + +#ifdef DOTS_FOR_ARGS +#include +#endif + +#ifdef UNIX +#include +#include +#include "cmtio.h" +#ifdef _IBMR2 +#define NBBY 8 +#define OPEN_MAX 2000 +#endif +#include +#endif + +#ifdef linux +#include /* for FD_ZERO / FD_SET */ +#endif + +extern int debug; + +#ifdef NYQUIST +/* get definitions for stdputstr, etc. */ +#include "xlisp.h" +#endif + + +/**************************************************************************** +* +* routines private to this module +* +****************************************************************************/ + +int GetReadFileName(); +int GetWriteFileName(); + +#ifdef MACINTOSH +private void PtoC_StrCopy(char *p1, char *p2); +#endif + +#ifdef AMIGA + char ConGetChar(); + ConMayGetChar(); +private void ConRead(); +private void ConPutStr(); +private void ConPutChar(); +UBYTE ascii_signal(); +UBYTE KeybSig(); +#endif + + +/**************************************************************************** +* +* variables shared with other modules +* +****************************************************************************/ + +public int abort_flag; /* control C or control G equivalent */ +public int redirect_flag; /* check whether the I/O has been redirected-- + Added by Ning Hu Apr.2001*/ +/* extern void musicterm(); */ /*DMH: from macmidi.c, to allow abort_check*/ +public boolean ascii_input(); + +/**************************************************************************** +* +* variables private to this module +* +****************************************************************************/ + +#ifdef AMIGA +struct IntuitionBase *IntuitionBase; +private struct IOStdReq *ConOutReq; +private struct MsgPort *ConOutPort; +private struct IOStdReq *ConInReq; +private struct MsgPort *ConInPort; +private char KeyBuff[16]; +private struct Window *Window; +private struct NewWindow NewWindow = { + 0,11,640,189, + 0,1, + NULL, + SMART_REFRESH | ACTIVATE | WINDOWDRAG | WINDOWDEPTH | + WINDOWSIZING, + NULL,NULL, + (STRPTR) "Carnegie Mellon University MIDI Toolkit for Commodore AMIGA", + NULL,NULL, + 100,25,640,200, + WBENCHSCREEN }; +#endif + +#ifdef MACINTOSH +private OSType io_file_type = 0x3F3F3F3F; /* '????' */ +private OSType io_file_creator = 0x3F3F3F3F; /* '????' */ +#endif + +#define type_ahead_max 100 +char type_ahead[100]; +int type_ahead_head = 0; +int type_ahead_tail = 0; +int type_ahead_count = 0; + + +#ifdef DOS +#ifdef BORLAND +int c_break(void) +{ + gprintf(TRANS, " BREAK "); + abort_flag = ABORT_LEVEL; + return 1; /* non-zero means do not exit program */ +} +#endif +#ifdef MICROSOFT +void c_break(int sig) +{ + abort_flag = ABORT_LEVEL; + /* The CTRL+C interrupt must be reset to our handler since + * by default it is reset to the system handler. + */ + signal(SIGINT, c_break); /* assume this succeeds */ +} +#endif +#endif + +#ifdef MACINTOSH +#ifdef NYQUIST +void FlushOutput (void); +#endif +#endif + +/* gflush -- flush output produced by gprintf, etc. */ +/**/ +void gflush(void) +{ +#ifdef MACINTOSH +#ifdef NYQUIST + FlushOutput(); +#else + fflush(stdout); /* make sure any prompts or errors have been output */ + fflush(STDERR); +#endif /* NYQUIST */ +#endif /* MACINTOSH */ +#ifdef UNIX + fflush(stdout); /* make sure any prompts or errors have been output */ + fflush(STDERR); +#endif +} + + +/**************************************************************************** +* io_init +* +* I added this init function for the AMIGA version. +* +* io_init : opens a window +* and exits if initialisation can not be done properly. +* registers cleanup calls to carefully deallocate resources. +* +* io_init is not amiga specific : the simplest version +* of io_init could be a clear screen statement for example, and a +* printf("Good bye !\n") on exit. +* +* for the Mac, it seems that ascii_input doesn't work unless getchar() is +* called first. I assume this is because getchar() initializes the ability +* of the window to process type-in, so there is probably a way to set this +* directly. If you figure it out, let me know. -RBD +* +*****************************************************************************/ + +void +io_init() +{ + +#ifdef AMIGA + int error; + + /* Window and console initialisation */ + IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",1L); + if (IntuitionBase == NULL) EXIT(1); + cu_register((cu_fn_type) CloseLibrary, IntuitionBase); + + ConOutPort = CreatePort("conoutport", 0L); + if (ConOutPort == NULL) EXIT(1); + cu_register((cu_fn_type) DeletePort, ConOutPort); + + ConOutReq = CreateStdIO(ConOutPort); + if (ConOutReq == NULL) EXIT(1); + cu_register((cu_fn_type) DeleteStdIO, ConOutReq); + + ConInPort = CreatePort("coninport", 0L); + if (ConInPort == NULL) EXIT(1); + cu_register((cu_fn_type) DeletePort, ConInPort); + + ConInReq = CreateStdIO(ConInPort); + if (ConInReq == NULL) EXIT(1); + cu_register((cu_fn_type) DeleteStdIO, ConInReq); + + Window = OpenWindow(&NewWindow); + if (Window == NULL) EXIT(1); + cu_register((cu_fn_type) CloseWindow, Window); + + ConOutReq->io_Data = (APTR)Window; + ConOutReq->io_Length = sizeof(*Window); + error = OpenDevice("console.device", 0L, (struct IORequest *) ConOutReq, 0L); + ConInReq->io_Device = ConOutReq->io_Device; + ConInReq->io_Unit = ConOutReq->io_Unit; + if (error != NULL) EXIT(1); + cu_register((cu_fn_type) CloseDevice, ConOutReq); + + ConInReq->io_Command = CMD_READ; + ConInReq->io_Data = (APTR)KeyBuff; + ConInReq->io_Length = 1; + SendIO((struct IORequest *) ConInReq); +#endif + +#ifdef UNIX +#ifndef BUFFERED_SYNCHRONOUS_INPUT + IOsetup(0 /* standard input */); + cu_register((cu_fn_type) IOcleanup, NULL); +#endif +#endif + +#ifdef MACINTOSH +#ifndef NYQUIST /* don't need this if we're in Nyquist */ + char s[100]; + printf("Type to start: "); + fgets(s, 100, stdin); +#endif /* NYQUIST */ +#endif + +#ifdef DOS +#ifdef MICROSOFT + if (signal(SIGINT, c_break) == SIG_ERR) { + gprintf(ERROR, "Couldn't set Ctrl C handler\n"); + EXIT(1); + } +#else +#ifdef BORLAND + ctrlbrk(c_break); +#else + ... we are in DOS, but neither MICROSOFT nor BORLAND, + please set up a control C handler here... +#endif +#endif +#endif +} + +#ifdef MACINTOSH + +/**************************************************************************** +* abort_check +* Effect: +* exit nicely if the aborted flag is set +****************************************************************************/ + +public void abort_check() +{ + if (abort_flag) clean_exit(); +} + + +/**************************************************************************** +* clean_exit +* Effect: +* clean up and exit +****************************************************************************/ + +public void clean_exit() +{ + gprintf(TRANS, "Exiting...\n"); + EXIT(1); +} + +#ifdef MPW +/**************************************************************************** +* cleanup_abort_handler +* Effect: +* shuts down abort watcher +****************************************************************************/ + +public void cleanup_abort_handler() +{ + (void) sigset(SIGINT, SIG_DFL); /* deactivate abort watcher */ +} + + +/**************************************************************************** +* init_abort_handler +* Effect: +* starts abort watcher +* aborted flag is set to FALSE +****************************************************************************/ + +public void init_abort_handler() +{ + abort_flag = FALSE; + (void) sigset(SIGINT, abort_watcher); /* activate abort watcher */ +} +#endif + +#endif + + +/**************************************************************************** +* askbool +* Inputs: +* char *prompt: string to prompt for user input +* int deflt: TRUE or FALSE default +* Returns: +* boolean: TRUE or FALSE as entered by user +* Effect: +* prompts user for yes or no input, returns result +****************************************************************************/ + +int askbool(prompt, deflt) +char *prompt; +int deflt; +{ +#define undefined -1 + char defchar; /* the default answer */ + char c; /* user input */ + char in_string[100]; + int result = -1; /* the result: -1 = undefined, 0 = FALSE, 1 = TRUE */ + if (deflt) defchar = 'y'; + else defchar = 'n'; + while (result == undefined) { + gprintf(TRANS, "%s? [%c]: ", prompt, defchar); + ggets(in_string); + c = in_string[0]; + if (islower(c)) c = toupper(c); + if (c == 'Y') result = TRUE; + else if (c == 'N') result = FALSE; + else if (c == EOS) result = deflt; + else if (abort_flag) result = deflt; + /* space before Please to separate from user's type-in: */ + else gprintf(TRANS, " Please type Y or N.\n"); + } + if (abort_flag == BREAK_LEVEL) { + abort_flag = 0; + result = deflt; + gprintf(TRANS, "\n"); + } + return result; +} + + +/**************************************************************************** +* fileopen +* Inputs: +* char *deflt: the default file name (e.g. from command line) +* char *extension: default extension +* char *mode: read ("r") or write ("w") +* char *prompt: prompt for user +* Returns: +* opened file pointer +* Effect: +* opens file, prompts for user input if necessary and warns about +* possible confusion. If deflt is a null string or NULL, the user will +* be prompted for a name. The routine loops until a file is opened. +* If the mode is "r", a check is made to see if the file exists +* with and without the extension. If both exist a warning is given. +* For mode "w", a check is made to see if the file will be overwritten. +* The extension is automatically added if the default or user-typed +* file has no "." At the bottom of the loop body, if no file has +* been opened, the user is prompted for another file name. +****************************************************************************/ + +char fileopen_name[100]; /* name of the opened file */ + +FILE *fileopen(deflt, extension, mode, prompt) + char *deflt; + char *extension; /* default extension */ + char *mode; /* read "r" or write "w" */ + char *prompt; /* prompt for user */ +{ + char extname[100]; /* trial name with extension added */ + FILE *fp = NULL; /* file corresponding to filename */ + FILE *fpext; /* file corresponding to extname */ + char *problem = NULL; /* tells user why he has to try again */ + + if (!deflt) deflt = ""; /* treat NULL as the empty string */ + strcpy(fileopen_name, deflt); + /* keep trying until a good file is found: */ + while (fp == NULL) { + /* avoid null file names: */ + while (strlen(fileopen_name) == 0) { +#ifndef MACINTOSH + gprintf(TRANS, "%s : ", prompt); + ggets(fileopen_name); + if (abort_flag) { + if (abort_flag == BREAK_LEVEL) { + abort_flag = 0; + /* type return since user didn't... */ + gprintf(TRANS, "\n"); + } + return NULL; + } +#else /* use Macintosh file dialog */ + if (mode[0] == 'r') { + if (!GetReadFileName(fileopen_name)) return NULL; + } else if (mode[0] == 'w') { + if (!(GetWriteFileName(fileopen_name, prompt))) return NULL; + } else { + gprintf(ERROR, "(fileopen) internal error: bad mode\n"); + } +#endif /* MACINTOSH */ + } + if (mode[0] == 'r') { + strcpy(extname, fileopen_name); + strcat(extname, "."); + strcat(extname, extension); + fp = fopen(fileopen_name, mode); + fpext = fopen(extname, mode); + if (fp != NULL && fpext != NULL) { + gprintf(TRANS, + "warning: both %s and %s exist. %s will be used.\n", + fileopen_name, extname, fileopen_name); + fclose(fpext); + } else if (fpext != NULL) { + fp = fpext; + strcpy(fileopen_name, extname); /* remember what was opened */ + } + if (fp == NULL) problem = "Couldn't find %s.\n"; + } else if (mode[0] == 'w') { + boolean added_extension = FALSE; + + /* add the extension if there is no '.' in the file name */ + if (!strchr(fileopen_name, '.')) { + strcat(fileopen_name, "."); + strcat(fileopen_name, extension); + added_extension = TRUE; + } + if (TRUE +#ifdef MACINTOSH + /* file open dialog already asked user to confirm unless we're + * adding an extension + */ + && added_extension +#endif + ) { + fp = fopen(fileopen_name, "r"); + if (fp != NULL) { + char question[100]; + fclose(fp); + strcpy(question, "OK to overwrite "); + strcat(question, fileopen_name); + if (!askbool(question, FALSE)) { + fp = NULL; + problem = "\n"; + goto tryagain; + } + } + } + fp = fopen(fileopen_name, mode); + if (fp == NULL) problem = "Couldn't create %s.\n"; + } + tryagain: + if (fp == NULL) { + gprintf(TRANS, problem, fileopen_name); + gprintf(TRANS,"Try again.\n"); + fileopen_name[0] = EOS; + } + } + return fp; +} + + +#ifdef MACINTOSH + +static int GetReadFileName(name) +char *name; +{ + static Point p = {100,100}; + SFReply loadfile; + SFTypeList mytypes; + + mytypes[0] = 0x54455854; /* 'TEXT' */ + mytypes[1] = 0x4D696469; /* 'Midi' */ + mytypes[2] = 0x3F3F3F3F; /* '????' */ +/* could put any filter here (i.e. giofilefileter) */ + SFGetFile(p, "\p", NULL, 3, mytypes, 0L, &loadfile); + if (loadfile.good) { + SetVol(0L,loadfile.vRefNum); + PtoC_StrCopy((char *) &loadfile.fName, name); + return(true); + } else return(false); +} + + +static int GetWriteFileName(fn, str) +char *fn, *str; +{ + static Point SFPwhere = { 106, 104 }; + unsigned char Pstr[100], Pfn[100]; + SFReply reply; + + strcpy((char *)Pstr, str); + CtoPstr((char *)Pstr); + strcpy((char *)Pfn, fn); + CtoPstr((char *)Pfn); + + SFPutFile(SFPwhere, (ConstStr255Param) Pstr, (ConstStr255Param) Pfn, + 0L, &reply); + if (reply.good) { + SetVol (0L,reply.vRefNum); + PtoC_StrCopy((char *) &reply.fName, fn); + return(true); + } + else return(false); +} + + +void PtoC_StrCopy(p1, p2) + register char *p1, *p2; +/* copies a pascal string from p1 to p2 */ +{ + register int len; + + len = *p1++; + while (--len>=0) *p2++=*p1++; + *p2 = '\0'; +} + + +boolean get_file_info(char *filename, OSType *file_type, OSType *file_creator) +{ + short rc; /* toolbox return code */ + FInfo fi; /* toolbox file info */ + char fn[101]; /* temporary file name */ + + strcpy(fn, filename); + CtoPstr(fn); + if (rc = GetFInfo((byte*)fn, 0, &fi)) { + gprintf(ERROR, "rc from GetFInfo=%d\n", rc); + gprintf(ERROR, "unable to get file type\n"); + *file_type = 0x3F3F3F3F; /* '????' */ + *file_creator = 0x3F3F3F3F; /* '????' */ + return FALSE; + } else /* set file type & creator */ { + if (debug) gprintf(TRANS, "File Type: '%.4s' File Creator: '%.4s'\n", + &fi.fdType, &fi.fdCreator ); + *file_type = fi.fdType; + *file_creator = fi.fdCreator; + } + return TRUE; +} + + +boolean put_file_info(char *filename, OSType file_type, OSType file_creator) +{ + short rc; /* toolbox return code */ + FInfo fi; /* toolbox file info */ + char fn[101]; /* temporary file name */ + + if (debug) gprintf(TRANS,"set file %s to become type '%.4s'\n", filename, &file_type); + strcpy(fn, filename); + CtoPstr(fn); + if (rc = GetFInfo((byte*)fn, 0, &fi)) { + gprintf(TRANS, "rc from GetFInfo=%d\n", rc); + gprintf(TRANS, "unable to set file type\n"); + } else /* set file type & creator */ { + if (debug) gprintf(TRANS, "File Type: '%.4s' File Creator: '%.4s'\n", + &fi.fdType, &fi.fdCreator ); + fi.fdType = file_type; + fi.fdCreator = file_creator; + if (rc=SetFInfo((byte*)fn, 0, &fi)) { + gprintf(TRANS, "rc from SetFInfo=%d\n", rc); + gprintf(TRANS, "unable to set file type\n"); + } else if (rc=GetFInfo((byte*)fn, 0, &fi)) { + gprintf(TRANS, "rc from GetFInfo=%d\n", rc); + gprintf(TRANS, "unable to verify file type\n"); + } else { + if (debug) gprintf(TRANS, "File Type: '%.4s' File Creator: '%.4s'\n", + &fi.fdType, &fi.fdCreator ); + } + } +} +#endif /* MACINTOSH */ + + + +#ifdef AMIGA +/*************************************************************** +* ascii_signal +* +* Input : none +* Ouput : none +* Return: the signal that will be raised on ascii input +* Effect: none +***************************************************************/ + +UBYTE ascii_signal() +{ + return ConInPort->mp_SigBit; +} +#endif + +/* check_aborted -- see if any characters are available, check for ctrl C */ + +int check_aborted() +{ + char in_c; +#ifdef AMIGA + if (GetMsg(ConInPort)) { + in_c = KeyBuff[0]; + if (in_c == '\r') in_c = '\n'; +#endif +#ifndef AMIGA /* DOS or MACINTOSH or UNIX */ + if (type_ahead_count < type_ahead_max && ascii_input(&in_c)) { +#endif + type_ahead[type_ahead_tail] = in_c; + if (in_c == ABORT_CHAR) abort_flag = ABORT_LEVEL; + else if (!abort_flag && in_c == BREAK_CHAR) + abort_flag = BREAK_LEVEL; + + /* go ahead and insert anything into buffer, including ^C, ^G: */ + type_ahead_count++; + type_ahead_tail++; + if (type_ahead_tail == type_ahead_max) type_ahead_tail = 0; + +#ifdef AMIGA + if (type_ahead_count < type_ahead_max) ConRead(); +#endif + } + return abort_flag; +} + + +/**************************************************************************** +* readln +* Inputs: +* FILE * fp: File to read from +* Effect: +* Reads and discards characters until a newline is seen +****************************************************************************/ + +void readln(fp) + register FILE *fp; +{ + register int c; + while (((c = getc(fp)) != '\n') && (c != EOF)) ; +} + + +/**************************************************************************** +* gprintf +* Inputs: +* int * handler: pointer to output handler (say, a window) +* or one of {TRANS, ERROR, FATAL, GDEBUG} from userio.h +* char * format: a null-terminated printf style format string +* int arg0 through arg14: a variable number of arguments for printf +* Effect: +* formats and outputs the specified information to an output handler. +* this is a system-independent approach to output. On +* a simple machine, it is like printf. on a more complex machine, +* output is directed to the appropriate window. +* Implementation +* Note that to handle the variable argument list, a number of different +* approaches are implemented. The first part of the implementation selects +* one of 4 ways to build temp1, a formatted string. The 4 ways arise from +* use or non-use of vsprintf, and use or non-use of ... in the arg list. +* After building temp1, non-Amiga systems write to stdout or stderr, +* whereas AMIGA writes to a special console. Why? Because the Amiga +* needs a new console so we can set up a signal upon character typein. +****************************************************************************/ + +#ifndef gprintf +#define GPRINTF_MESSAGE_LEN 512 +#ifdef USE_VSPRINTF +#ifdef DOTS_FOR_ARGS + +/* define with ... in arg list and use vsprintf to get temp1 */ +public void gprintf(long where, char *format, ...) +{ + char temp1[GPRINTF_MESSAGE_LEN]; +#ifdef AMIGA + char temp2[GPRINTF_MESSAGE_LEN]; +#endif + va_list ap; + + va_start(ap, format); + vsprintf(temp1, format, ap); + va_end(ap); + +#else /* !DOTS_FOR_ARGS */ + +/* define with va_alist and use vsprintf to get temp1 */ +public void gprintf(where, format, va_alist) +long where; +char *format; +va_dcl +{ + char temp1[GPRINTF_MESSAGE_LEN]; + va_list pvar; +/* this is a syntax error - if you don't have to remove this, */ +/* then this whole section of code is unnecessary. */ + va_start(pvar); + vsprintf(temp1, format, pvar); + va_end(pvar); + +#endif /* DOTS_FOR_ARGS */ + +#else /* !USE_VSPRINTF */ +#define MAX_GPRINTF_ARGS 10 +typedef struct gp_args_struct { + long arg[MAX_GPRINTF_ARGS]; +} gp_args_node; + +#ifdef DOTS_FOR_ARGS +/* use ... but not vsprintf */ +public void gprintf(long where, char *format, ...) +{ + char temp1[GPRINTF_MESSAGE_LEN]; +#ifdef AMIGA + char temp2[GPRINTF_MESSAGE_LEN]; +#endif + va_list ap; + gp_args_node args; + va_start(ap, format); + args = va_arg(ap, gp_args_node); + va_end(ap); +#else /* !DOTS_FOR_ARGS */ +/* don't use ... and don't use vsprintf */ +public void gprintf(where, format, args) + long where; + char *format; + gp_args_node args; +{ + char temp1[GPRINTF_MESSAGE_LEN]; +#ifdef AMIGA + char temp2[GPRINTF_MESSAGE_LEN]; +#endif /* AMIGA*/ +#endif /* DOTS_FOR_ARGS */ + + sprintf(temp1, format, args); + +#endif /* USE_VSPRINTF */ + +/* + * Now we've got formatted output in temp1. Write it out. + */ +#ifdef NYQUIST + switch ((long) where) { + case TRANS: + stdputstr(temp1); + break; + case ERROR: + errputstr(temp1); + break; + case FATAL: + errputstr("FATAL: "); + errputstr(temp1); + break; + case GDEBUG: + errputstr("DEBUG: "); + errputstr(temp1); + break; + default: + errputstr("UNKNOWN: "); + errputstr(temp1); + break; + } + gflush(); +#else /* not NYQUIST */ +#ifdef AMIGA + + switch((long) where) { + case TRANS: + strcpy(temp2, temp1); + break; + case ERROR: + strcpy(temp2, temp1); + break; + case FATAL: + strcpy(temp2, "FATAL: "); + strcat(temp2, temp1); + break; + case GDEBUG: + strcpy(temp2,"DEBUG: "); + strcat(temp2, temp1); + break; + default: + strcpy(temp2, "UNKNOWN: "); + strcat(temp2, temp1); + break; + } + ConOutReq->io_Command = CMD_WRITE; + ConOutReq->io_Data = (APTR)temp2; + ConOutReq->io_Length = -1; /* NULL terminated string */ + DoIO((struct IORequest *) ConOutReq); +#else /* not NYQUIST or AMIGA */ + switch(where) { + case TRANS: + printf("%s", temp1); + break; + case ERROR: + fprintf(STDERR, "%s", temp1); + break; + case GDEBUG: + fprintf(STDERR, "DEBUG %s", temp1); + break; + case FATAL: + fprintf(STDERR, "FATAL %s", temp1); + break; + default: + fprintf(STDERR, "UNKNOWN %s", temp1); + break; + } +#endif /* AMIGA */ +#endif /* NYQUIST */ +} + +#endif /* ifndef gprintf */ + + +/************************************************************************** +* gputchar +* General putchar +**************************************************************************/ + +#ifndef gputchar + +#ifdef AMIGA +public int gputchar(c) +int c; +{ + ConPutChar((char)c); + return(c); +} +#else +public int gputchar(c) +int c; +{ + putchar((char)c); + return(c); +} +#endif + +#endif /* ifndef gputchar */ + +/************************************************************************** +* ggetchar +* General getchar +**************************************************************************/ + +public int ggetchar() +{ +#ifdef BUFFERED_SYNCHRONOUS_INPUT + return getchar(); +#else + int key = wait_ascii(); + if (key != ABORT_CHAR && key != '\b') gputchar((char)key); + return(key); +#endif +} + + +/************************************************************************** +* ggets +* General gets +**************************************************************************/ + + +#ifndef ggets + +public char *ggets(str) + char *str; +{ + char *s = str; + int c; + + do { + c = ggetchar(); + if (c == '\b' /* backspace */) { + if (s != str) { + gputchar('\b'); + gputchar((int)' '); + gputchar('\b'); + s--; + } else { +#ifdef AMIGA + gputchar((int)0x9b); + gputchar((int)0x43); +#else + /* gputchar((int)' '); */ +#endif + gputchar((int)0x07); + } + } else *s++ = (char) c; + } while (c != (int) '\n' && !abort_flag); + + *(s-1) = EOS; + if (abort_flag) *str = EOS; + return str; +} + +#endif /* ifndef ggets */ + + +/**************************************************************************** +* get_ascii +* Returns: +* boolean: TRUE if a character was found +* int * c: pointer to int into which to store the character, if any +* Effect: +* polls (doesn't wait) for an ascii character and says if it got one +* the character is returned in *c. +****************************************************************************/ + +public boolean get_ascii(c) + char *c; +{ + check_aborted(); /* input buffer check */ + if (type_ahead_count == 0) return FALSE; +#ifdef AMIGA + /* if the buffer is full, then there is no outstanding read, restart it: */ + if (type_ahead_count == type_ahead_max) ConRead(); +#endif + type_ahead_count--; + *c = type_ahead[type_ahead_head++]; + if (type_ahead_head == type_ahead_max) type_ahead_head = 0; + return TRUE; +} + +#ifdef MACINTOSH /** Macintosh direct ascii input**/ +public boolean ascii_input(c) +char *c; +{ + EventRecord theEvent; + + (void) GetNextEvent((keyDownMask | autoKeyMask), &theEvent); + if ((theEvent.what == keyDown) || (theEvent.what == autoKey)) { + *c = theEvent.message & charCodeMask; + if (*c == '\r') *c = '\n'; + return(true); + } + else { + return(false); + } +} +#endif + +#ifdef WINDOWS +#include "conio.h" +#define kbhit _kbhit +#define getch _getch +#endif + +#ifdef DOS +public boolean ascii_input(c) +char *c; +{ + if (abort_flag == ABORT_LEVEL) { + *c=ABORT_CHAR; + return((boolean)TRUE); + } + if (kbhit()) { /* If the keyboard was hit */ + *c = getch(); /* Don't echo it */ +// printf("now break"); + if (*c == '\r') *c = '\n'; + return((boolean)TRUE); + } + return((boolean)FALSE); /* Keeps Lattice compiler happy */ +} +#endif + +#ifdef UNIX +public boolean ascii_input(c) +char *c; +{ +#ifdef UNIX_MACH + /* + * we can't read from stdin directly, because the ascii + * input thread is already doing so, so instead we'll + * wait for that thread to read a character and then take + * it + */ + boolean ret = FALSE; + + A_LOCK(); + if (a_in_flag) { + (*c) = a_in; + a_in_flag = 0; + ret = TRUE; + } + A_UNLOCK(); + if (ret) { +#ifdef RTMach + itc_condition_signal(&a_cond); +#else /* RTMach */ + condition_signal(&a_cond); +#endif /* RTMach */ + } + if ((*c) == '\r') + (*c) = '\n'; + return(ret); +#else /* UNIX_MACH */ +#ifndef BUFFERED_SYNCHRONOUS_INPUT + int input = IOgetchar(); + if (input != IOnochar) { + *c = input; + if (*c == '\r') *c = '\n'; + return TRUE; + } +#endif /* BUFFERED_SYNCHRONOUS_INPUT */ + return FALSE; +#endif /* UNIX_MACH */ +} +#endif + +#ifndef AMIGA /*DOS and MAC and UNIX */ +public void unget_ascii(char c) +{ + if (type_ahead_head == 0) type_ahead_head = type_ahead_max; + type_ahead_head--; + type_ahead[type_ahead_head] = c; + type_ahead_count++; +} + + +public boolean check_ascii() +{ + char c; + + if(get_ascii(&c)) { + unget_ascii(c); + return TRUE; + } + else return FALSE; +} +#endif + + +/**************************************************************************** +* wait_ascii +* Returns: +* int: character for key pressed +* Effect: +* waits for the user to type a key on the terminal keyboard +* (versus the synthesizer keyboard) and returns the key typed +****************************************************************************/ + +#ifdef MACINTOSH +public int wait_ascii() +{ + char key ; /* key typed */ + + if (abort_flag == ABORT_LEVEL) return ABORT_CHAR; + if (abort_flag == BREAK_LEVEL) return BREAK_CHAR; + gflush(); + while (!get_ascii(&key)) ; + return(key); +} +#endif + +#ifdef DOS +public int wait_ascii() +{ + char key ; /* key typed */ + + if (abort_flag == ABORT_LEVEL) return ABORT_CHAR; + if (abort_flag == BREAK_LEVEL) return BREAK_CHAR; + if (!get_ascii(&key)) { + key = _getch(); // block until we get an input + } + /* GWL - check for abort on previos line */ + return (int)key; +} +#endif + +#ifndef MACINTOSH +#ifndef DOS +public int wait_ascii() +{ +#ifdef UNIX /* was defined (UNIX) || defined(ITC) */ +#ifndef UNIX_MACH + fd_set readfds; +#endif /* !UNIX_MACH */ +#endif + char c; + struct rlimit file_limit; + + if (abort_flag == ABORT_LEVEL) return ABORT_CHAR; + if (abort_flag == BREAK_LEVEL) return BREAK_CHAR; + while (!get_ascii(&c)) { +#ifdef AMIGA + WaitPort(ConInPort); +#endif +#ifdef UNIX + fflush(stdout); +#ifdef UNIX_MACH + /* + * we can't select, since another thread is reading + * from stdin, and we don't want to have an input war + * so instead, the ascii input thread will signal + * a_in_cond when it gets input, so we just wait + * for that to happen + */ + A_LOCK(); +#ifdef RTMach + itc_condition_wait(&a_in_cond, &a_mutex); +#else /* RTMach */ + condition_wait(&a_in_cond, &a_mutex); +#endif /* RTMach */ + A_UNLOCK(); +#else /* UNIX_MACH */ + FD_ZERO(&readfds); + FD_SET(IOinputfd, &readfds); + gflush(); + getrlimit(RLIMIT_NOFILE, &file_limit); + select(file_limit.rlim_max+1, &readfds, 0, 0, NULL); +#endif /* !UNIX_MACH */ +#endif /* ifdef UNIX */ + } + return (int) c; +} +#endif +#endif + +#ifdef AMIGA +/****************************************************************** + AMIGA 2000. + Console IO Functions + JCD 25-Apr-88 +*******************************************************************/ + +UBYTE KeybSig() +{ + return ConInPort->mp_SigBit; +} + +private void ConPutChar(c) +char c; +{ + ConOutReq->io_Command = CMD_WRITE; + ConOutReq->io_Data = (APTR)&c; + ConOutReq->io_Length = 1; + DoIO((struct IORequest *) ConOutReq); +} + +private void ConPutStr(str) +char *str; +{ + ConOutReq->io_Command = CMD_WRITE; + ConOutReq->io_Data = (APTR)str; + ConOutReq->io_Length = -1; + DoIO((struct IORequest *) ConOutReq); +} + +private void ConRead() +{ + ConInReq->io_Command = CMD_READ; + ConInReq->io_Data = (APTR)KeyBuff; + ConInReq->io_Length = 1; + SendIO((struct IORequest *) ConInReq); +} +#endif diff --git a/cmt/userio.h b/cmt/userio.h new file mode 100644 index 0000000..7c05e96 --- /dev/null +++ b/cmt/userio.h @@ -0,0 +1,96 @@ +/* Copyright 1989 Carnegie Mellon University */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 5-Apr |JDW : Further changes +*****************************************************************************/ + +/* classes of output for gprintf */ +#ifdef MACINTOSH +#undef false +#undef true +#include +#define TRANS (long) 0 +#define ERROR (long) 1 +#define FATAL (long) 2 +#define GDEBUG (long) 3 +#endif + +#ifdef DONT_USE_CMT_IO +#define TRANS stdout +#define ERROR stdout +#define FATAL stdout +#define GDEBUG stdout +#endif + +#ifndef TRANS /* default */ +#define TRANS 0 +#define ERROR 1 +#define FATAL 2 +#define GDEBUG 3 +#endif + +#define CR '\n' +#define ABORT_CHAR 0x03 +#ifdef NYQUIST +#define BREAK_CHAR 0x02 +#else +#define BREAK_CHAR 0x07 +#endif + +#define BREAK_LEVEL 1 +#define ABORT_LEVEL 2 + +#define read_to_eol(ch) if (ch != CR) { char temp[100]; ggets(temp); } + +extern char fileopen_name[]; +extern int abort_flag; +extern int redirect_flag; /* added by Ning Hu, Apr 2001 */ + +boolean get_ascii(char *c); /* polls for an ascii character */ +#ifdef DOTS_FOR_ARGS +/* was (defined(ITC_MACH) && defined(__STDC__)) || defined(MACINTOSH) || defined(AZTEC) || (defined(AMIGA) && defined(LATTICE)) || defined(UNIX_ITC) */ +void gprintf(long where, char *format, ...); /* general printf */ +#else +void gprintf(); +#endif + +char *ggets(char *str); /* general gets */ +int wait_ascii(void); /* a waiting version of get_ascii */ +void clean_exit(void); /* exit the program after cleaning up */ +void io_init(void); /* overall initialization */ +void abort_check(void); /* exit if aborted */ + +int check_aborted(void); /* looks to see if user typed ctrl-C */ + +int askbool(char *prompt, int deflt); +FILE *fileopen(char *deflt, char *extension, char *mode, char *prompt); +void readln(FILE *fp); +void gflush(void); +int gputchar(int c); +int ggetchar(); +char *ggets(char *str); +boolean ascii_input(char *c); +void unget_ascii(char c); +boolean check_ascii(void); + +#ifdef MACINTOSH +boolean get_file_info(char *filename, OSType *file_type, OSType *file_creator); +boolean put_file_info(char *filename, OSType file_type, OSType file_creator); +#endif + +#ifdef DONT_USE_CMT_IO +#define ggetchar getchar +#define ggets gets +#define gprintf fprintf +#define gputchar putchar +#define gprintf fprintf +#define gputchar putchar +#endif + +#ifdef MICROSOFT +void c_break(int sig); +#endif + -- cgit v1.2.3