/********************************************************************** nyx.c Nyx: A very simple external interface to Nyquist Dominic Mazzoni **********************************************************************/ /* system includes */ #include #include #include #include #include #ifndef WIN32 #include #endif /* nyx includes */ #include "nyx.h" /* xlisp includes */ #include "switches.h" #include "xlisp.h" #include "cext.h" /* nyquist includes */ #include "sound.h" #include "samples.h" #include "falloc.h" /* use full copy */ #define NYX_FULL_COPY 1 /* show memory stats */ // #define NYX_MEMORY_STATS 1 /* show details of obarray copy */ // #define NYX_DEBUG_COPY 1 /* macro to compute the size of a segment (taken from xldmem.h) */ #define segsize(n) (sizeof(SEGMENT)+((n)-1)*sizeof(struct node)) /* xldmem external variables */ extern long nnodes; extern long nfree; extern long total; extern int nsegs; extern SEGMENT *segs; extern SEGMENT *lastseg; extern LVAL fnodes; /* nyquist externs */ extern LVAL a_sound; extern snd_list_type zero_snd_list; /* globals */ LOCAL nyx_os_callback nyx_os_cb = NULL; LOCAL void *nyx_os_ud; LOCAL nyx_output_callback nyx_output_cb; LOCAL void *nyx_output_ud; LOCAL int nyx_expr_pos; LOCAL int nyx_expr_len; LOCAL const char *nyx_expr_string; LOCAL LVAL nyx_result; LOCAL nyx_rval nyx_result_type = nyx_error; LOCAL XLCONTEXT nyx_cntxt; LOCAL int nyx_first_time = 1; LOCAL LVAL nyx_obarray; LOCAL FLOTYPE nyx_warp_stretch; /* Suspension node */ typedef struct nyx_susp_struct { snd_susp_node susp; // Must be first nyx_audio_callback callback; void *userdata; long len; int channel; } nyx_susp_node, *nyx_susp_type; #if defined(NYX_DEBUG_COPY) && NYX_DEBUG_COPY static const char *_types_[] = { "FREE_NODE", "SUBR", "FSUBR", "CONS", "SYMBOL", "FIXNUM", "FLONUM", "STRING", "OBJECT", "STREAM", "VECTOR", "CLOSURE", "CHAR", "USTREAM", "EXTERN" }; // Dump the contents of the obarray LOCAL void nyx_show_obarray() { LVAL array = getvalue(obarray); LVAL sym; int i; for (i = 0; i < HSIZE; i++) { for (sym = getelement(array, i); sym; sym = cdr(sym)) { LVAL syma = car(sym); printf("_sym_ = "); xlprint(getvalue(s_stdout), syma, TRUE); if (getvalue(syma)) { printf(" _type_ = %s _val_ = ", _types_[ntype(getvalue(syma))]); xlprint(getvalue(s_stdout), getvalue(syma), TRUE); } if (getfunction(syma)) { printf(" _type_ = %s _fun_ = ", _types_[ntype(getfunction(syma))]); xlprint(getvalue(s_stdout), getfunction(syma), TRUE); } printf("\n"); } } } #endif // // Free empty segments // LOCAL void freesegs() { SEGMENT *seg; SEGMENT *next; // Free up as many nodes as possible gc(); // Reset free node tracking fnodes = NIL; nfree = 0L; // Reset the last segment pointer lastseg = NULL; // Scan all segments for (seg = segs; seg != NULL; seg = next) { int n = seg->sg_size; int empty = TRUE; int i; LVAL p; // Check this segment for in-use nodes p = &seg->sg_nodes[0]; for (i = n; --i >= 0; ++p) { if (ntype(p) != FREE_NODE) { empty = FALSE; break; } } // Retain pointer to next segment next = seg->sg_next; // Was the current segment empty? if (empty) { // Free the segment; free((void *) seg); // Unlink it from the list. No need to worry about a NULL lastseg // pointer here since the fixnum and char segments will always exist // at the head of the list and they will always have nodes. So, lastseg // will have been set before we find any empty nodes. lastseg->sg_next = next; // Reduce the stats total -= (long) segsize(n); nsegs--; nnodes -= n; } else { // Not empty, so remember this node as the last segment lastseg = seg; // Add all of the free nodes in this segment to the free list p = &seg->sg_nodes[0]; for (i = n; --i >= 0; ++p) { if (ntype(p) == FREE_NODE) { rplaca(p, NIL); rplacd(p, fnodes); fnodes = p; nfree++; } } } } } #if defined(NYX_FULL_COPY) && NYX_FULL_COPY // Copy a node (recursively if appropriate) LOCAL LVAL nyx_dup_value(LVAL val) { LVAL nval = val; // Protect old and new values xlprot1(val); xlprot1(nval); // Copy the node if (val != NIL) { switch (ntype(val)) { case FIXNUM: nval = cvfixnum(getfixnum(val)); break; case FLONUM: nval = cvflonum(getflonum(val)); break; case CHAR: nval = cvchar(getchcode(val)); break; case STRING: nval = cvstring((char *) getstring(val)); break; case VECTOR: { int len = getsize(val); int i; nval = newvector(len); nval->n_type = ntype(val); for (i = 0; i < len; i++) { if (getelement(val, i) == val) { setelement(nval, i, val); } else { setelement(nval, i, nyx_dup_value(getelement(val, i))); } } } break; case CONS: nval = cons(nyx_dup_value(car(val)), nyx_dup_value(cdr(val))); break; case SUBR: case FSUBR: nval = cvsubr(getsubr(val), ntype(val), getoffset(val)); break; // Symbols should never be copied since their addresses are cached // all over the place. case SYMBOL: nval = val; break; // Streams are not copied (although USTREAM could be) and reference // the original value. case USTREAM: case STREAM: nval = val; break; // Externals aren't copied because I'm not entirely certain they can be. case EXTERN: nval = val; break; // For all other types, just allow them to reference the original // value. Probably not the right thing to do, but easier. case OBJECT: case CLOSURE: default: nval = val; break; } } xlpop(); xlpop(); return nval; } // Make a copy of the original obarray, leaving the original in place LOCAL void nyx_save_obarray() { LVAL newarray; int i; // This provide permanent protection for nyx_obarray as we do not want it // to be garbage-collected. xlprot1(nyx_obarray); nyx_obarray = getvalue(obarray); // Create and set the new vector. This allows us to use xlenter() to // properly add the new symbol. Probably slower than adding directly, // but guarantees proper hashing. newarray = newvector(HSIZE); setvalue(obarray, newarray); // Scan all obarray vectors for (i = 0; i < HSIZE; i++) { LVAL sym; // Scan all elements for (sym = getelement(nyx_obarray, i); sym; sym = cdr(sym)) { LVAL syma = car(sym); char *name = (char *) getstring(getpname(syma)); LVAL nsym = xlenter(name); // Ignore *OBARRAY* since there's no need to copy it if (strcmp(name, "*OBARRAY*") == 0) { continue; } // Duplicate the symbol's values setvalue(nsym, nyx_dup_value(getvalue(syma))); setplist(nsym, nyx_dup_value(getplist(syma))); setfunction(nsym, nyx_dup_value(getfunction(syma))); } } // Swap the obarrays, so that the original is put back into service setvalue(obarray, nyx_obarray); nyx_obarray = newarray; } // Restore the symbol values to their original value and remove any added // symbols. LOCAL void nyx_restore_obarray() { LVAL obvec = getvalue(obarray); int i; // Scan all obarray vectors for (i = 0; i < HSIZE; i++) { LVAL last = NULL; LVAL dcon; for (dcon = getelement(obvec, i); dcon; dcon = cdr(dcon)) { LVAL dsym = car(dcon); char *name = (char *)getstring(getpname(dsym)); LVAL scon; // Ignore *OBARRAY* since setting it causes the input array to be // truncated. if (strcmp(name, "*OBARRAY*") == 0) { continue; } // Find the symbol in the original obarray. for (scon = getelement(nyx_obarray, hash(name, HSIZE)); scon; scon = cdr(scon)) { LVAL ssym = car(scon); // If found, then set the current symbols value to the original. if (strcmp(name, (char *)getstring(getpname(ssym))) == 0) { setvalue(dsym, nyx_dup_value(getvalue(ssym))); setplist(dsym, nyx_dup_value(getplist(ssym))); setfunction(dsym, nyx_dup_value(getfunction(ssym))); break; } } // If we didn't find the symbol in the original obarray, then it must've // been added since and must be removed from the current obarray. if (scon == NULL) { if (last) { rplacd(last, cdr(dcon)); } else { setelement(obvec, i, cdr(dcon)); } } // Must track the last dcon for symbol removal last = dcon; } } } #else LOCAL LVAL copylist(LVAL from) { LVAL nsym; if (from == NULL) { return NULL; } return cons(car(from), copylist(cdr(from))); } /* Make a copy of the obarray so that we can erase any changes the user makes to global variables */ LOCAL void nyx_copy_obarray() { LVAL newarray; int i; // Create and set the new vector. newarray = newvector(HSIZE); setvalue(obarray, newarray); for (i = 0; i < HSIZE; i++) { LVAL from = getelement(nyx_obarray, i); if (from) { setelement(newarray, i, copylist(from)); } } } #endif void nyx_init() { if (nyx_first_time) { char *argv[1]; argv[0] = "nyquist"; xlisp_main_init(1, argv); nyx_os_cb = NULL; nyx_output_cb = NULL; nyx_first_time = 0; #if defined(NYX_FULL_COPY) && NYX_FULL_COPY // Save a copy of the original obarray's contents. nyx_save_obarray(); #else // Permanently protect the original obarray value. This is needed since // it would be unreferenced in the new obarray and would be garbage // collected. We want to keep it around so we can make copies of it to // refresh the execution state. xlprot1(nyx_obarray); nyx_obarray = getvalue(obarray); #endif } #if !defined(NYX_FULL_COPY) || !NYX_FULL_COPY // Create a copy of the original obarray nyx_copy_obarray(); #endif // Keep nyx_result from being garbage-collected xlprot1(nyx_result); #if defined(NYX_MEMORY_STATS) && NYX_MEMORY_STATS printf("\nnyx_init\n"); xmem(); #endif } void nyx_cleanup() { // Garbage-collect nyx_result xlpop(); #if defined(NYX_FULL_COPY) && NYX_FULL_COPY // Restore the original symbol values nyx_restore_obarray(); #else // Restore obarray to original state...but not the values setvalue(obarray, nyx_obarray); #endif // Make sure the sound nodes can be garbage-collected. Sounds are EXTERN // nodes whose value does not get copied during a full copy of the obarray. setvalue(xlenter("S"), NIL); // Free excess memory segments - does a gc() freesegs(); // No longer need the callbacks nyx_output_cb = NULL; nyx_os_cb = NULL; #if defined(NYX_MEMORY_STATS) && NYX_MEMORY_STATS printf("\nnyx_cleanup\n"); xmem(); #endif } LOCAL void nyx_susp_fetch(register nyx_susp_type susp, snd_list_type snd_list) { sample_block_type out; sample_block_values_type out_ptr; long n; int err; falloc_sample_block(out, "nyx_susp_fetch"); out_ptr = out->samples; snd_list->block = out; n = max_sample_block_len; if (susp->susp.current + n > susp->len) n = susp->len - susp->susp.current; err = susp->callback(out_ptr, susp->channel, susp->susp.current, n, 0, susp->userdata); if (err) { // The user canceled or some other error occurred, so we use // xlsignal() to jump back to our error handler. xlsignal(NULL, NULL); // never get here. } snd_list->block_len = (short)n; susp->susp.current += n; if (n == 0) { /* we didn't read anything, but can't return length zero, so convert snd_list to pointer to zero block */ snd_list_terminate(snd_list); } else if (n < max_sample_block_len) { /* should free susp */ snd_list_unref(snd_list->u.next); /* if something is in buffer, terminate by pointing to zero block */ snd_list->u.next = zero_snd_list; } } LOCAL void nyx_susp_free(nyx_susp_type susp) { ffree_generic(susp, sizeof(nyx_susp_node), "nyx_susp_free"); } LOCAL void nyx_susp_print_tree(nyx_susp_type susp, int n) { } void nyx_capture_output(nyx_output_callback callback, void *userdata) { nyx_output_cb = callback; nyx_output_ud = userdata; } void nyx_set_audio_params(double rate, long len) { double stretch_len = (len > 0 ? len / rate : 1.0); LVAL warp; /* Bind the sample rate to the "*sound-srate*" global */ setvalue(xlenter("*SOUND-SRATE*"), cvflonum(rate)); /* Bind selection len to "len" global */ setvalue(xlenter("LEN"), cvflonum(len)); /* Set the "*warp*" global based on the length of the audio */ xlprot1(warp); warp = cons(cvflonum(0), /* time offset */ cons(cvflonum(stretch_len), /* time stretch */ cons(NULL, /* cont. time warp */ NULL))); setvalue(xlenter("*WARP*"), warp); xlpop(); } void nyx_set_input_audio(nyx_audio_callback callback, void *userdata, int num_channels, long len, double rate) { sample_type scale_factor = 1.0; time_type t0 = 0.0; nyx_susp_type *susp; sound_type *snd; int ch; nyx_set_audio_params(rate, len); susp = (nyx_susp_type *)malloc(num_channels * sizeof(nyx_susp_type)); snd = (sound_type *)malloc(num_channels * sizeof(sound_type)); for(ch=0; ch < num_channels; ch++) { falloc_generic(susp[ch], nyx_susp_node, "nyx_set_input_audio"); susp[ch]->callback = callback; susp[ch]->userdata = userdata; susp[ch]->len = len; susp[ch]->channel = ch; susp[ch]->susp.fetch = nyx_susp_fetch; susp[ch]->susp.keep_fetch = NULL; susp[ch]->susp.free = nyx_susp_free; susp[ch]->susp.mark = NULL; susp[ch]->susp.print_tree = nyx_susp_print_tree; susp[ch]->susp.name = "nyx"; susp[ch]->susp.toss_cnt = 0; susp[ch]->susp.current = 0; susp[ch]->susp.sr = rate; susp[ch]->susp.t0 = t0; susp[ch]->susp.log_stop_cnt = 0; snd[ch] = sound_create((snd_susp_type)susp[ch], t0, rate, scale_factor); } if (num_channels > 1) { LVAL array = newvector(num_channels); for(ch=0; chstop); } while(result==0) { for(ch=0; chget_next(snd, &cnt); if (block == zero_block || cnt == 0) { result = -1; break; } /* copy the data to a temporary buffer and scale it by the appropriate scale factor */ if (cnt > bufferlen) { if (buffer) free(buffer); buffer = (float *)malloc(cnt * sizeof(float)); if (buffer == NULL) { goto finish; } bufferlen = cnt; } memcpy(buffer, block->samples, cnt * sizeof(float)); for(i=0; iscale; result = callback(buffer, ch, totals[ch], cnt, lens[ch], userdata); if (result != 0) { // The user canceled or some other error occurred, so we use // xlsignal() to jump back to our error handler. xlsignal(NULL, NULL); // never get here. } totals[ch] += cnt; } } success = TRUE; xltoplevel(); finish: gc(); if (buffer) { free(buffer); } if (lens) { free(lens); } if (totals) { free(totals); } if (snds) { free(snds); } #if defined(NYX_MEMORY_STATS) && NYX_MEMORY_STATS printf("\nnyx_get_audio after\n"); xmem(); #endif return success; } int nyx_get_int() { if (nyx_get_type(nyx_result) != nyx_int) return -1; return getfixnum(nyx_result); } double nyx_get_double() { if (nyx_get_type(nyx_result) != nyx_double) return -1.0; return getflonum(nyx_result); } const char *nyx_get_string() { if (nyx_get_type(nyx_result) != nyx_string) return NULL; return (const char *)getstring(nyx_result); } unsigned int nyx_get_num_labels() { LVAL s = nyx_result; int count = 0; if (nyx_get_type(nyx_result) != nyx_labels) return 0; while(s) { count++; s = cdr(s); } return count; } void nyx_get_label(unsigned int index, double *start_time, double *end_time, const char **label) { LVAL s = nyx_result; LVAL label_expr; LVAL t0_expr; LVAL t1_expr; LVAL str_expr; if (nyx_get_type(nyx_result) != nyx_labels) return; while(index) { index--; s = cdr(s); if (s == NULL) { // index was larger than number of labels return; } } /* We either have (t0 "label") or (t0 t1 "label") */ label_expr = car(s); t0_expr = car(label_expr); t1_expr = car(cdr(label_expr)); if (stringp(t1_expr)) { str_expr = t1_expr; t1_expr = t0_expr; } else str_expr = car(cdr(cdr(label_expr))); if (floatp(t0_expr)) *start_time = getflonum(t0_expr); else if (fixp(t0_expr)) *start_time = (double)getfixnum(t0_expr); if (floatp(t1_expr)) *end_time = getflonum(t1_expr); else if (fixp(t1_expr)) *end_time = (double)getfixnum(t1_expr); *label = (const char *)getstring(str_expr); } const char *nyx_get_error_str() { return NULL; } void nyx_set_os_callback(nyx_os_callback callback, void *userdata) { nyx_os_cb = callback; nyx_os_ud = userdata; } void nyx_stop() { xlflush(); xltoplevel(); } void nyx_break() { xlflush(); xlbreak("BREAK", s_unbound); } void nyx_continue() { xlflush(); xlcontinue(); } int ostgetc() { if (nyx_expr_pos < nyx_expr_len) { fflush(stdout); return (nyx_expr_string[nyx_expr_pos++]); } else if (nyx_expr_pos == nyx_expr_len) { /* Add whitespace at the end so that the parser knows that this is the end of the expression */ nyx_expr_pos++; return '\n'; } else return EOF; } /* osinit - initialize */ void osinit(char *banner) { } /* osfinish - clean up before returning to the operating system */ void osfinish(void) { } /* oserror - print an error message */ void oserror(char *msg) { printf("nyx error: %s\n", msg); } long osrand(long n) { return (((int) rand()) % n); } /* cd .. open - open an ascii file */ FILE *osaopen(name,mode) char *name,*mode; { FILE *fp; fp = fopen(name,mode); return fp; } /* osbopen - open a binary file */ FILE *osbopen(char *name, char *mode) { char bmode[10]; FILE *fp; strncpy(bmode, mode, 8); strcat(bmode,"b"); fp = fopen(name,bmode); return fp; } /* osclose - close a file */ int osclose(FILE *fp) { return (fclose(fp)); } /* osagetc - get a character from an ascii file */ int osagetc(FILE *fp) { return (getc(fp)); } /* osaputc - put a character to an ascii file */ int osaputc(int ch, FILE *fp) { return (putc(ch,fp)); } /* osoutflush - flush output to a file */ void osoutflush(FILE *fp) { fflush(fp); } extern int dbgflg; /* osbgetc - get a character from a binary file */ /* int osbgetc(fp) FILE *fp; {return (getc(fp));} */ #ifndef WIN32 // duplicated in winfun.c, per James Crook, 7/4/2003 int osbgetc(FILE *fp) { return (getc(fp)); } #endif /* osbputc - put a character to a binary file */ int osbputc(int ch, FILE *fp) { return (putc(ch,fp)); } /* ostputc - put a character to the terminal */ void ostputc(int ch) { oscheck(); /* check for control characters */ if (nyx_output_cb) nyx_output_cb(ch, nyx_output_ud); else putchar(((char) ch)); } /* ostoutflush - flush output buffer */ void ostoutflush() { if (!nyx_output_cb) fflush(stdout); } /* osflush - flush the terminal input buffer */ void osflush(void) { } /* oscheck - check for control characters during execution */ void oscheck(void) { if (nyx_os_cb) { nyx_os_cb(nyx_os_ud); } /* if they hit control-c: xflush(); xltoplevel(); return; */ } /* xsystem - execute a system command */ #ifndef WIN32 // duplicated in winfun.c, per James Crook, 7/4/2003 LVAL xsystem() { if (moreargs()) { unsigned char *cmd; cmd = (unsigned char *)getstring(xlgastring()); fprintf(stderr, "Will not execute system command: %s\n", cmd); } return s_true; } #endif #ifndef WIN32 /* xsetdir -- set current directory of the process */ LVAL xsetdir() { char *dir = (char *)getstring(xlgastring()); int result; LVAL cwd = NULL; xllastarg(); result = chdir(dir); if (result) { perror("SETDIR"); } dir = getcwd(NULL, 1000); if (dir) { cwd = cvstring(dir); free(dir); } return cwd; } #endif /* xgetkey - get a key from the keyboard */ #ifndef WIN32 // duplicated in winfun.c, per James Crook, 7/4/2003 LVAL xgetkey() {xllastarg(); return (cvfixnum((FIXTYPE)getchar()));} #endif /* ossymbols - enter os specific symbols */ #ifndef WIN32 // duplicated in winfun.c, per James Crook, 7/4/2003 void ossymbols(void) {} #endif /* xsetupconsole -- used to configure window in Win32 version */ #ifndef WIN32 // duplicated in winfun.c, per James Crook, 7/4/2003 LVAL xsetupconsole() { return NULL; } #endif const char os_pathchar = '/'; const char os_sepchar = ':'; /* control-C handling */ void ctcinit() {} /* xechoenabled -- set/clear echo_enabled flag (unix only) */ LVAL xechoenabled() { return NULL; } /* osdir_list_start -- open a directory listing */ int osdir_list_start(char *path) { return FALSE; } /* osdir_list_next -- read the next entry from a directory */ char *osdir_list_next() { return NULL; } /* osdir_list_finish -- close an open directory */ void osdir_list_finish() { return; } #ifndef WIN32 /* xget_temp_path -- get a path to create temp files */ LVAL xget_temp_path() { char *tmp = getenv("TMPDIR"); if (!tmp || !*tmp) { tmp = getenv("TMP"); if (!tmp || !*tmp) { tmp = "/tmp/"; } } return cvstring(tmp); } #endif #ifndef WIN32 /* xget_user -- get a string identifying the user, for use in file names */ LVAL xget_user() { char *user = getenv("USER"); if (!user || !*user) { user = getenv("USERNAME"); if (!user || !*user) { errputstr("Warning: could not get user ID, using 'nyquist'\n"); user = "nyquist"; } } return cvstring(user); } #endif