/* gretl - The Gnu Regression, Econometrics and Time-series Library
* Copyright (C) 2004 Allin Cottrell
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* gretl audio graph plugin */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "libgretl.h"
#include "miditypes.h"
#include "midi_utils.h"
#include <glib.h>
#include <gtk/gtk.h>
#define ADEBUG 0
#ifdef HAVE_FLITE
# include <flite/flite.h>
extern cst_voice *register_cmu_us_kal (void);
#endif
#ifdef G_OS_WIN32
# define COBJMACROS
# include <sapi.h>
#endif
#if defined(HAVE_FLITE) || defined(G_OS_WIN32)
# define DO_SPEECH
# include "libgretl.h"
#endif
const char *track_hdr = "MTrk";
enum dataset_comments {
TS_COMMENT = 0,
YLABEL_COMMENT,
XLABEL_COMMENT,
XRANGE_COMMENT,
N_COMMENTS
};
#define DEFAULT_FORCE 96 /* volume of MIDI notes */
#ifndef G_OS_WIN32
# define DEFAULT_MIDI_PROG "timidity" /* MIDI player to be used */
# define DEFAULT_MIDI_OPT "-A500" /* option to pass to MIDI player */
#endif
typedef struct _datapoint datapoint;
typedef struct _dataset dataset;
typedef struct _midi_spec midi_spec;
typedef struct _midi_track midi_track;
typedef struct _note_event note_event;
struct _datapoint {
double x;
double y;
};
struct _dataset {
int pd;
int n;
int series2;
double intercept;
double slope;
datapoint *points;
double *y2;
gchar *comments[N_COMMENTS];
};
struct _midi_spec {
int ntracks;
int nticks;
int nsecs;
dataset *dset;
FILE *fp;
};
struct _midi_track {
unsigned char channel;
unsigned char patch;
int n_notes;
note_event *notes;
};
struct _note_event {
double dtime;
double duration;
unsigned char pitch;
unsigned char force;
unsigned char channel;
};
#define delta_time_zero(f) (putc(0x00, f))
#define YMAX 60
static int write_note_event (note_event *event, midi_spec *spec)
{
int len;
len = delta_time(event->dtime, spec->nticks, spec->fp);
putc(MIDI_NOTE_ON + event->channel, spec->fp);
putc(event->pitch, spec->fp);
putc(event->force, spec->fp);
len += 3;
/* use "running status" */
len += delta_time(event->duration, spec->nticks, spec->fp);
putc(event->pitch, spec->fp);
putc(0, spec->fp);
len += 2;
return len;
}
static void write_midi_track (midi_track *track,
midi_spec *spec)
{
unsigned char bal = 0x7f * (track->channel % 2);
char tmp[32];
long len = 0;
int i, n;
fwrite(track_hdr, 1, 4, spec->fp);
write_be_long(0, spec->fp); /* revisited below */
delta_time_zero(spec->fp);
len++;
sprintf(tmp, "Series %d", track->channel + 1);
n = strlen(tmp);
len += write_midi_meta(MIDI_TRACK_NAME, spec->fp);
len += write_var_len(n, spec->fp);
for (i=0; i<n; i++) {
putc(tmp[i], spec->fp);
}
len += n;
delta_time_zero(spec->fp);
len++;
strcpy(tmp, get_patch_name(track->patch));
n = strlen(tmp);
len += write_midi_meta(MIDI_INSTR_NAME, spec->fp);
len += write_var_len(n, spec->fp);
for (i=0; i<n; i++) {
putc(tmp[i], spec->fp);
}
len += n;
/* Stereo separation of series */
delta_time_zero(spec->fp);
fputc(0xb0 + track->channel, spec->fp); /* Controller on channel */
fputc(MIDI_PAN_MSB, spec->fp);
fputc(bal, spec->fp); /* MSB = 0 or 0x7f */
delta_time_zero(spec->fp);
fputc(0xb0 + track->channel, spec->fp);
fputc(MIDI_PAN_LSB, spec->fp);
fputc(bal, spec->fp); /* LSB = 0 or 0x7f */
len += 8;
delta_time_zero(spec->fp);
len++;
len += write_midi_byte(MIDI_PROGRAM_CHANGE + track->channel, spec->fp);
len += write_midi_byte(track->patch, spec->fp);
for (i=0; i<track->n_notes; i++) {
track->notes[i].channel = track->channel;
len += write_note_event(&track->notes[i], spec);
}
delta_time_zero(spec->fp);
len++;
len += write_midi_eot(spec->fp);
fseek(spec->fp, -(len + 4), SEEK_CUR);
/* now fix the length */
write_be_long(len, spec->fp);
fseek(spec->fp, len, SEEK_CUR);
}
static void four_four_header (midi_spec *spec)
{
long min_note_time = 40000;
long max_note_time = 180000;
int ticklen, len = 0;
long pos, pos1;
write_midi_header(1, spec->ntracks + 1, spec->nticks, spec->fp);
/* tempo/time track */
fwrite(track_hdr, 1, 4, spec->fp);
pos = ftell(spec->fp);
write_be_long(0, spec->fp); /* revisited below */
#if 0
/* time signature (4/4 is the MIDI default) */
delta_time_zero(spec->fp);
len++;
len += write_midi_meta(MIDI_TIME_SIG, spec->fp);
/* size (bytes) */
len += write_midi_byte(4, spec->fp);
/* 4/4 time */
len += write_midi_byte(4, spec->fp);
len += write_midi_byte(2, spec->fp);
/* 24 MIDI clocks per metronome click */
len += write_midi_byte(24, spec->fp);
/* 8 32nd notes per tick */
len += write_midi_byte(8, spec->fp);
#endif
/* tempo */
delta_time_zero(spec->fp);
len++;
len += write_midi_meta(MIDI_TEMPO, spec->fp);
/* bytes */
len += write_midi_byte(3, spec->fp);
if (spec->dset == NULL) {
/* microseconds per quarter note */
len += write_be_24(500000, spec->fp);
} else {
/* total number of microseconds */
double nms = spec->nsecs * 1.0e6;
/* microseconds per quarter note */
long msq = nms / spec->dset->n;
#if ADEBUG
fprintf(stderr, "Secs = %d, microsecs = %g, msq = %d\n",
spec->nsecs, nms, (int) msq);
#endif
if (msq > max_note_time) {
msq = max_note_time;
} else if (msq < min_note_time) {
msq = min_note_time;
}
#if ADEBUG
fprintf(stderr, "After adj, msq = %d\n", (int) msq);
#endif
len += write_be_24(msq, spec->fp);
}
/* end */
if (spec->dset == NULL) {
ticklen = 4 * spec->nticks;
#if ADEBUG
fprintf(stderr, "dset is NULL, ticklen = %d\n", ticklen);
#endif
} else {
ticklen = spec->dset->n * spec->nticks;
#if ADEBUG
fprintf(stderr, "ticklen = %d * %d = %d\n",
spec->dset->n, spec->nticks, ticklen);
#endif
}
len += write_var_len(ticklen, spec->fp);
len += write_midi_eot(spec->fp);
/* length of header */
pos1 = ftell(spec->fp);
fseek(spec->fp, pos, SEEK_SET);
write_be_long(len, spec->fp);
fseek(spec->fp, pos1, SEEK_SET);
}
static void dataset_init (dataset *dset)
{
int i;
dset->points = NULL;
dset->y2 = NULL;
dset->n = 0;
dset->pd = 0;
dset->series2 = 0;
dset->intercept = NADBL;
dset->slope = NADBL;
for (i=0; i<N_COMMENTS; i++) {
dset->comments[i] = NULL;
}
}
static void dataset_free (dataset *dset)
{
int i;
free(dset->points);
if (dset->y2 != NULL) {
free(dset->y2);
}
for (i=0; i<N_COMMENTS; i++) {
if (dset->comments[i] != NULL) {
g_free(dset->comments[i]);
}
}
}
const char *cent_str (int cent)
{
switch (cent) {
case 14:
return "fourteen";
case 15:
return "fifteen";
case 16:
return "sixteen";
case 17:
return "seventeen";
case 18:
return "eighteen";
case 19:
return "nineteen";
case 20:
return "two thousand";
default:
return "unknown";
}
}
static int make_xrange_comment (const char *line, dataset *dset)
{
double x1, x2;
if (sscanf(line, "set xrange [%lf:%lf]", &x1, &x2) != 2) {
return 0;
}
if (dset->pd == 0) {
dset->comments[XRANGE_COMMENT] =
g_strdup_printf("x range %.4g to %.4g", x1, x2);
} else {
char tmp1[64], tmp2[64];
int ix1 = x1;
int ix2 = x2;
int cent, yr;
cent = x1 / 100;
yr = ix1 - 100 * cent;
sprintf(tmp1, "%s %d", cent_str(cent), yr);
cent = x2 / 100;
yr = ix2 - 100 * cent;
sprintf(tmp2, "%s %d", cent_str(cent), yr);
dset->comments[XRANGE_COMMENT] =
g_strdup_printf("%s to %s", tmp1, tmp2);
}
return 1;
}
static int make_axis_label_comment (const char *line, dataset *dset)
{
char tmp[16];
int ret = 0;
if (sscanf(line, "set ylabel '%15[^']", tmp) == 1) {
if (dset->pd > 0) {
dset->comments[YLABEL_COMMENT] = g_strdup(tmp);
} else {
dset->comments[YLABEL_COMMENT] =
g_strdup_printf("y variable %s", tmp);
}
ret = 1;
} else if (sscanf(line, "set xlabel '%15[^']", tmp) == 1) {
if (dset->pd > 0) {
dset->comments[XLABEL_COMMENT] = g_strdup(tmp);
} else {
dset->comments[XLABEL_COMMENT] =
g_strdup_printf("x variable %s", tmp);
}
ret = 1;
}
return ret;
}
static void make_ts_comment (const char *line, dataset *dset)
{
const char *pdstr = NULL;
sscanf(line, "# timeseries %d", &dset->pd);
switch (dset->pd) {
case 1:
pdstr = "Annual";
break;
case 4:
pdstr = "Quarterly";
break;
case 5:
case 7:
pdstr = "Daily";
break;
case 12:
pdstr = "Monthly";
break;
case 24:
pdstr = "Hourly";
break;
default:
break;
}
if (pdstr != NULL) {
dset->comments[TS_COMMENT] =
g_strdup_printf("%s time series", pdstr);
} else {
dset->comments[TS_COMMENT] = g_strdup("time series");
}
}
static int get_comment (const char *line, dataset *dset)
{
int ret = 0;
if (!strncmp(line, "# timeseries", 12)) {
make_ts_comment(line, dset);
ret = 1;
} else if (!strncmp(line, "set ylabel", 10) ||
!strncmp(line, "set xlabel", 10)) {
make_axis_label_comment(line, dset);
ret = 1;
} else if (!strncmp(line, "set xrange", 10)) {
make_xrange_comment(line, dset);
ret = 1;
}
return ret;
}
#ifdef PLAY_AUTOFIT_LINE
static int get_fit_params (char *line, dataset *dset)
{
double a, b;
char *p = strchr(line, '*');
if (p != NULL) *p = '\0';
if (sscanf(line, "%lf + %lf", &a, &b) == 2) {
dset->intercept = a;
dset->slope = b;
dset->series2 = 1;
return 1;
}
return 0;
}
#endif
static int get_data_x_y (const char *line, double *x, double *y)
{
if (sscanf(line, "%lf %lf", x, y) == 2) return 0;
if (sscanf(line, "%lf ?", x) == 1) {
*y = NADBL;
return 0;
}
return 1;
}
static int read_datafile (const char *fname, dataset *dset)
{
char line[256];
int i, err = 0;
int got_e = 0, y2data = 0, fitline = 0;
FILE *fdat;
dataset_init(dset);
fdat = gretl_fopen(fname, "r");
if (fdat == NULL) {
fprintf(stderr, "Couldn't open '%s'\n", fname);
return 1;
} else {
fprintf(stderr, "Reading %s...\n", fname);
}
while (fgets(line, sizeof line, fdat)) {
tailstrip(line);
if (get_comment(line, dset)) {
continue;
} else if (!strcmp(line, "e")) {
fprintf(stderr, "Got end of data marker\n");
got_e++;
if (got_e == 2) {
/* can't handle more than two series! */
break;
}
} else if (strstr(line, "automatic fitted")) {
fitline = 1;
} else if (isdigit((unsigned char) line[0])) {
if (strstr(line, "title")) {
#ifdef PLAY_AUTOFIT_LINE
if (fitline) {
get_fit_params(line, dset);
}
#endif
continue;
}
if (!got_e) {
dset->n += 1;
} else if (!y2data) {
y2data = 1;
}
}
}
if (dset->n == 0) {
fprintf(stderr, "No data in '%s'\n", fname);
err = 1;
goto bailout;
}
dset->points = malloc(dset->n * sizeof *dset->points);
if (dset->points == NULL) {
err = 1;
fputs("Out of memory\n", stderr);
goto bailout;
}
if (y2data) {
dset->y2 = malloc(dset->n * sizeof *dset->y2);
if (dset->y2 == NULL) {
err = 1;
fputs("Out of memory\n", stderr);
goto bailout;
}
dset->series2 = 1;
}
rewind(fdat);
i = got_e = 0;
while (!err && fgets(line, 256, fdat)) {
tailstrip(line);
if (!strcmp(line, "e")) {
got_e++;
if (got_e == 2) {
break;
}
i = 0;
} else if (isdigit((unsigned char) line[0])) {
double x, y;
if (strstr(line, "title")) {
continue;
}
if (get_data_x_y(line, &x, &y)) {
fprintf(stderr, "Couldn't read data on line %d\n", i + 1);
err = 1;
} else {
if (!got_e) {
dset->points[i].x = x;
dset->points[i].y = y;
} else {
dset->y2[i] = y;
}
}
i++;
}
}
bailout:
fclose(fdat);
if (err) {
dataset_free(dset);
}
return err;
}
static void points_min_max (const datapoint *points,
double *xmin, double *xmax,
double *ymin, double *ymax,
int n)
{
int i;
*xmin = *xmax = points[0].x;
*ymin = *ymax = points[0].y;
for (i=1; i<n; i++) {
if (points[i].x < *xmin) *xmin = points[i].x;
if (points[i].x > *xmax) *xmax = points[i].x;
if (!na(points[i].y)) {
if (points[i].y < *ymin) *ymin = points[i].y;
if (points[i].y > *ymax) *ymax = points[i].y;
}
}
}
static void min_max (const double *x, double *min, double *max,
int n)
{
int i;
*min = *max = x[0];
for (i=1; i<n; i++) {
if (!na(x[i])) {
if (x[i] < *min) *min = x[i];
if (x[i] > *max) *max = x[i];
}
}
}
#if defined(HAVE_FLITE)
#if 0
static int save_dataset_comments (const dataset *dset)
{
int i, j;
cst_voice *v;
cst_wave *w, *fullw = NULL;
flite_init();
v = register_cmu_us_kal();
j = 0;
for (i=0; i<N_COMMENTS; i++) {
if (dset->comments[i] != NULL) {
if (j == 0) {
fullw = flite_text_to_wave(dset->comments[i], v);
} else {
w = flite_text_to_wave(dset->comments[i], v);
concat_wave(fullw, w);
delete_wave(w);
}
j++;
}
}
cst_wave_save_riff(fullw, "gretl_flite.wav");
delete_wave(fullw);
return 0;
}
#endif
static void speak_dataset_comments (const dataset *dset)
{
int i;
cst_voice *v;
flite_init();
v = register_cmu_us_kal();
for (i=0; i<N_COMMENTS; i++) {
if (dset->comments[i] != NULL) {
flite_text_to_speech(dset->comments[i], v, "play");
}
}
}
#elif defined(G_OS_WIN32)
static WCHAR *wide_string (const char *s)
{
unsigned char *ret;
int i, j, n = strlen(s);
ret = malloc(2 * (n + 1));
if (ret == NULL) return NULL;
j = 0;
for (i=0; i<=n; i++) {
ret[j++] = s[i];
ret[j++] = '\0';
}
return (WCHAR *) ret;
}
static void speak_dataset_comments (const dataset *dset)
{
int i;
ISpVoice *v = NULL;
HRESULT hr;
hr = CoInitialize(NULL);
if (!SUCCEEDED(hr)) {
fprintf(stderr, "CoInitialize failed\n");
return;
}
hr = CoCreateInstance(&CLSID_SpVoice,
NULL,
CLSCTX_ALL,
&IID_ISpVoice,
(void **) &v);
if (SUCCEEDED(hr)) {
for (i=0; i<N_COMMENTS; i++) {
if (dset->comments[i] != NULL) {
WCHAR *w = wide_string(dset->comments[i]);
ISpVoice_Speak(v, w, 0, NULL);
free(w);
}
}
ISpVoice_Release(v);
}
CoUninitialize();
}
#endif
static void print_dataset_comments (const dataset *dset)
{
int i;
for (i=0; i<N_COMMENTS; i++) {
if (dset->comments[i] != NULL) {
printf("Comment[%d]: %s\n", i+1, dset->comments[i]);
}
}
#ifdef DO_SPEECH
speak_dataset_comments(dset);
#endif
}
static void audio_graph_error (const char *msg)
{
#ifdef HAVE_FLITE
cst_voice *v;
#endif
#ifdef G_OS_WIN32
ISpVoice *v = NULL;
HRESULT hr;
#endif
fprintf(stderr, "%s\n", msg);
#ifdef HAVE_FLITE
flite_init();
v = register_cmu_us_kal();
flite_text_to_speech(msg, v, "play");
#endif
#ifdef G_OS_WIN32
hr = CoInitialize(NULL);
if (!SUCCEEDED(hr)) return;
hr = CoCreateInstance(&CLSID_SpVoice,
NULL,
CLSCTX_ALL,
&IID_ISpVoice,
(void **) &v);
if (SUCCEEDED(hr)) {
wchar_t *w = wide_string(msg);
ISpVoice_Speak(v, w, 0, NULL);
free(w);
ISpVoice_Release(v);
}
#endif
}
static int compare_points (const void *a, const void *b)
{
const datapoint *pa = (const datapoint *) a;
const datapoint *pb = (const datapoint *) b;
return (pa->x > pb->x) - (pa->x < pb->x);
}
static int play_dataset (midi_spec *spec, midi_track *track,
const dataset *dset)
{
double xmin, xmax, xavg;
double ymin, ymax;
double xscale, yscale;
int i;
track->notes = malloc(dset->n * sizeof *track->notes);
if (track->notes == NULL) {
fputs("out of memory\n", stderr);
return 1;
}
track->channel = 0;
track->patch = PC_GRAND;
track->n_notes = dset->n;
if (dset->pd == 0) {
/* scatter plot: sort data by x value */
qsort((void *) dset->points, (size_t) dset->n,
sizeof dset->points[0], compare_points);
}
points_min_max(dset->points, &xmin, &xmax, &ymin, &ymax, dset->n);
if (dset->y2 != NULL) {
double y2min, y2max;
min_max(dset->y2, &y2min, &y2max, dset->n);
if (y2min < ymin) ymin = y2min;
if (y2max > ymax) ymax = y2max;
}
xavg = (xmax - xmin) / (dset->n - 1);
/* normalize average x step to quarter note */
xscale = 1.0 / xavg;
#if ADEBUG
fprintf(stderr, "xavg = %g, xscale = %g\n", xavg, xscale);
#endif
yscale = YMAX / (ymax - ymin);
for (i=0; i<dset->n; i++) {
double dtx, dux, ypos;
if (!na(dset->points[i].y)) {
ypos = (dset->points[i].y - ymin) * yscale;
} else {
ypos = NADBL;
}
if (i == 0) {
dtx = 0.0;
} else {
dtx = xscale * (dset->points[i].x - dset->points[i-1].x);
}
if (i == dset->n - 1) {
dux = xscale * xavg;
} else {
dux = xscale * (dset->points[i+1].x - dset->points[i].x);
}
track->notes[i].dtime = dtx;
track->notes[i].duration = dux;
if (!na(ypos)) {
track->notes[i].pitch = 36 + (int) (ypos + 0.5);
track->notes[i].force = DEFAULT_FORCE;
} else {
track->notes[i].pitch = 36;
track->notes[i].force = 0;
}
#if ADEBUG
fprintf(stderr, "Obs %d: x = %g, y = %g, ypos = %g\n", i,
dset->points[i].x, dset->points[i].y, ypos);
fprintf(stderr, " dtime=%g, duration=%g, pitch=%d\n",
track->notes[i].dtime, track->notes[i].duration,
track->notes[i].pitch);
#endif
}
write_midi_track(track, spec);
if (dset->series2) {
for (i=0; i<dset->n; i++) {
double yi, ypos = 0;
if (dset->y2 != NULL) {
yi = dset->y2[i];
} else {
yi = dset->intercept + dset->slope * dset->points[i].x;
}
if (!na(yi)) {
ypos = (yi - ymin) * yscale;
track->notes[i].pitch = 36 + (int) (ypos + 0.5);
track->notes[i].force = DEFAULT_FORCE;
} else {
track->notes[i].pitch = 36;
track->notes[i].force = 0;
}
#if ADEBUG
fprintf(stderr, "Series2, Obs %d: x = %g, y = %g, ypos = %g\n", i,
dset->points[i].x, yi, ypos);
fprintf(stderr, " dtime=%g, duration=%g, pitch=%d\n",
track->notes[i].dtime, track->notes[i].duration,
track->notes[i].pitch);
#endif
}
track->channel = 1;
/* below: was PC_MARIMBA, but that's not in freepats */
track->patch = PC_CELLO;
write_midi_track(track, spec);
}
free(track->notes);
return 0;
}
#if defined(G_OS_WIN32)
static int midi_fork (const char *fname, const char *midiplayer)
{
int err = 0;
if ((long) ShellExecute(NULL, "open", fname, NULL, NULL, SW_SHOW) < 32)
err = 1;
return err;
}
#else /* non-Windows version */
static char **get_midi_args (int *argc, const char *fname,
const char *midiplayer)
{
char **argv = NULL;
const char *midistr;
if (midiplayer != NULL && *midiplayer != '\0') {
midistr = midiplayer;
} else {
midistr = (const char *) getenv("GRETL_MIDI_PLAYER");
}
if (midistr == NULL || *midistr == '\0') {
*argc = 3;
argv = malloc((*argc + 1) * sizeof *argv);
if (argv == NULL) return NULL;
argv[0] = g_strdup(DEFAULT_MIDI_PROG);
argv[1] = g_strdup(DEFAULT_MIDI_OPT);
argv[2] = g_strdup(fname);
argv[3] = NULL;
} else {
char *tmp = g_strdup(midistr);
char *s = tmp;
int i, ntoks = 1;
while (*s) {
if (*s == ' ') ntoks++;
s++;
}
*argc = ntoks + 1;
argv = malloc((*argc + 1) * sizeof *argv);
if (argv == NULL) {
g_free(tmp);
return NULL;
}
if (ntoks > 1) {
argv[0] = g_strdup(strtok(tmp, " "));
for (i=1; i<ntoks; i++) {
argv[i] = g_strdup(strtok(NULL, " "));
}
} else {
argv[0] = g_strdup(tmp);
i = 1;
}
argv[i++] = g_strdup(fname);
argv[i] = NULL;
g_free(tmp);
}
return argv;
}
static int real_midi_fork (char **argv)
{
gboolean run;
run = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
NULL, NULL, NULL, NULL);
return !run;
}
static int midi_fork (const char *fname, const char *midiplayer)
{
int i, err, argc;
char **argv;
argv = get_midi_args(&argc, fname, midiplayer);
if (argv == NULL) return 1;
err = real_midi_fork(argv);
for (i=0; i<argc; i++) {
g_free(argv[i]);
}
free(argv);
return err;
}
#endif /* ! MS Windows */
int midi_play_graph (const char *fname, const char *userdir,
const char *midiplayer)
{
char outname[FILENAME_MAX];
midi_spec spec;
midi_track track;
dataset dset;
sprintf(outname, "%sgretl.mid", userdir);
spec.fp = gretl_fopen(outname, "wb");
if (spec.fp == NULL) {
fprintf(stderr, "Couldn't write to '%s'\n", outname);
return 1;
}
if (read_datafile(fname, &dset)) {
audio_graph_error("Error reading data file");
fclose(spec.fp);
return 1;
}
spec.ntracks = 1 + dset.series2;
spec.nticks = 96;
spec.dset = &dset;
spec.nsecs = 8;
four_four_header(&spec);
print_dataset_comments(&dset);
play_dataset(&spec, &track, &dset);
dataset_free(&dset);
fclose(spec.fp);
midi_fork(outname, midiplayer);
return 0;
}
#ifdef DO_SPEECH
# include "audioprint.c"
#endif
syntax highlighted by Code2HTML, v. 0.9.1