// // SBaGen - Sequenced Binaural Beat Generator // // (c) 1999-2005 Jim Peters . All Rights Reserved. // For latest version see http://sbagen.sf.net/ or // http://uazu.net/sbagen/. Released under the GNU GPL version 2. // Use at your own risk. // // " 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, version 2. // // This program 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. " // // See the file COPYING for details of this license. // // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // // Some code fragments in the Win32 audio handling are based on // code from PLIB (c) 2001 by Steve Baker, originally released // under the LGPL (slDSP.cxx and sl.h). For the original source, // see the PLIB project: http://plib.sf.net // // The code for the Mac audio output was based on code from the // FINK project's patches to ESounD, by Shawn Hsiao and Masanori // Sekino. See: http://fink.sf.net #define VERSION "1.4.3" // This should be built with one of the following target macros // defined, which selects options for that platform, or else with some // of the individual named flags #defined as listed later. // // T_LINUX To build the LINUX version with /dev/dsp support // T_MINGW To build for Windows using MinGW and Win32 calls // T_MSVC To build for Windows using MSVC and Win32 calls // T_MACOSX To build for MacOSX using CoreAudio // T_POSIX To build for simple file output on any Posix-compliant OS // // Ogg and MP3 support is handled separately from the T_* macros. // Define OSS_AUDIO to use /dev/dsp for audio output // Define WIN_AUDIO to use Win32 calls // Define MAC_AUDIO to use Mac CoreAudio calls // Define NO_AUDIO if no audio output device is usable // Define UNIX_TIME to use UNIX calls for getting time // Define WIN_TIME to use Win32 calls for getting time // Define ANSI_TTY to use ANSI sequences to clear/redraw lines // Define UNIX_MISC to use UNIX calls for various miscellaneous things // Define WIN_MISC to use Windows calls for various miscellaneous things // Define EXIT_KEY to require the user to hit RETURN before exiting after error // Define OGG_DECODE to include OGG support code // Define MP3_DECODE to include MP3 support code #ifdef T_LINUX #define OSS_AUDIO #define UNIX_TIME #define UNIX_MISC #define ANSI_TTY #endif #ifdef T_MINGW #define WIN_AUDIO #define WIN_TIME #define WIN_MISC #define EXIT_KEY #endif #ifdef T_MSVC #define WIN_AUDIO #define WIN_TIME #define WIN_MISC #define EXIT_KEY #endif #ifdef T_MACOSX #define MAC_AUDIO #define UNIX_TIME #define UNIX_MISC #define ANSI_TTY #endif #ifdef T_POSIX #define NO_AUDIO #define UNIX_TIME #define UNIX_MISC #endif // Make sure NO_AUDIO is set if necessary #ifndef OSS_AUDIO #ifndef MAC_AUDIO #ifndef WIN_AUDIO #define NO_AUDIO #endif #endif #endif // Make sure one of the _TIME macros is set #ifndef UNIX_TIME #ifndef WIN_TIME #error UNIX_TIME or WIN_TIME not defined. Maybe you did not define one of T_LINUX/T_MINGW/T_MACOSX/etc ? #endif #endif // Make sure one of the _MISC macros is set #ifndef UNIX_MISC #ifndef WIN_MISC #error UNIX_MISC or WIN_MISC not defined. Maybe you did not define one of T_LINUX/T_MINGW/T_MACOSX/etc ? #endif #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef T_MSVC #include #define write _write #define vsnprintf _vsnprintf typedef long long S64; // I have no idea if this is correct for MSVC #else #include #include typedef long long S64; #endif #ifdef T_MINGW #define vsnprintf _vsnprintf #endif #ifdef OSS_AUDIO #include //WAS: #include #endif #ifdef WIN_AUDIO #include #include #endif #ifdef MAC_AUDIO #include #include #endif #ifdef UNIX_TIME #include #include #endif #ifdef UNIX_MISC #include #endif typedef struct Channel Channel; typedef struct Voice Voice; typedef struct Period Period; typedef struct NameDef NameDef; typedef struct BlockDef BlockDef; typedef unsigned char uchar; int inbuf_loop(void *vp) ; int inbuf_read(int *dst, int dlen) ; void inbuf_start(int(*rout)(int*,int), int len) ; inline int t_per24(int t0, int t1) ; inline int t_per0(int t0, int t1) ; inline int t_mid(int t0, int t1) ; int main(int argc, char **argv) ; void status(char *) ; void dispCurrPer( FILE* ) ; void init_sin_table() ; void debug(char *fmt, ...) ; void warn(char *fmt, ...) ; void * Alloc(size_t len) ; char * StrDup(char *str) ; inline int calcNow() ; void loop() ; void outChunk() ; void corrVal(int ) ; int readLine() ; char * getWord() ; void badSeq() ; void readSeqImm(int ac, char **av) ; void readSeq(int ac, char **av) ; void readPreProg(int ac, char **av) ; void correctPeriods(); void setup_device(void) ; void readNameDef(); void readTimeLine(); int voicesEq(Voice *, Voice *); void error(char *fmt, ...) ; int sprintTime(char *, int); int sprintVoice(char *, Voice *, Voice *); int readTime(char *, int *); void writeWAV(); void writeOut(char *, int); void sinc_interpolate(double *, int, int *); inline int userTime(); void find_wav_data_start(FILE *in); int raw_mix_in(int *dst, int dlen); int scanOptions(int *acp, char ***avp); void handleOptions(char *p); void setupOptC(char *spec) ; extern int out_rate, out_rate_def; void create_drop(int ac, char **av); void create_slide(int ac, char **av); #define ALLOC_ARR(cnt, type) ((type*)Alloc((cnt) * sizeof(type))) #define uint unsigned int #ifdef OGG_DECODE #include "oggdec.c" #endif #ifdef MP3_DECODE #include "mp3dec.c" #endif #ifdef WIN_AUDIO void CALLBACK win32_audio_callback(HWAVEOUT, UINT, DWORD, DWORD, DWORD); #endif #ifdef MAC_AUDIO OSStatus mac_callback(AudioDeviceID, const AudioTimeStamp *, const AudioBufferList *, const AudioTimeStamp *, AudioBufferList *, const AudioTimeStamp *, void *inClientData); #endif #define NL "\n" void help() { printf("SBaGen - Sequenced Binaural Beat Generator, version " VERSION NL "Copyright (c) 1999-2004 Jim Peters, http://uazu.net/, all rights " NL " reserved, released under the GNU GPL v2. See file COPYING." NL NL "Usage: sbagen [options] seq-file ..." NL " sbagen [options] -i tone-specs ..." NL " sbagen [options] -p pre-programmed-sequence-specs ..." NL NL "Options: -h Display this help-text" NL " -Q Quiet - don't display running status" NL " -D Display the full interpreted sequence instead of playing it" NL " -i Immediate. Take the remainder of the command line to be" NL " tone-specifications, and play them continuously" NL " -p Pre-programmed sequence. Take the remainder of the command" NL " line to be a type and arguments, e.g. \"drop 00ds+\"" NL " -q mult Quick. Run through quickly (real time x 'mult') from the" NL " start time, rather than wait for real time to pass" NL NL " -r rate Select the output rate (default is 44100 Hz, or from -m)" #ifndef MAC_AUDIO NL " -b bits Select the number bits for output (8 or 16, default 16)" #endif NL " -L time Select the length of time (hh:mm or hh:mm:ss) to output" NL " for. Default is to output forever." NL " -S Output from the first tone-set in the sequence (Start)," NL " instead of working in real-time. Equivalent to '-q 1'." NL " -E Output until the last tone-set in the sequence (End)," NL " instead of outputting forever." NL " -T time Start at the given clock-time (hh:mm)" NL NL " -o file Output raw data to the given file instead of /dev/dsp" NL " -O Output raw data to the standard output" NL " -W Output a WAV-format file instead of raw data" NL " -m file Read audio data from the given file and mix it with the" NL " generated binaural beats; may be " #ifdef OGG_DECODE "ogg/" #endif #ifdef MP3_DECODE "mp3/" #endif "wav/raw format" NL " -M Read raw audio data from the standard input and mix it" NL " with the generated binaural beats (raw only)" NL NL " -R rate Select rate in Hz that frequency changes are recalculated" NL " (for file/pipe output only, default is 10Hz)" NL " -F fms Fade in/out time in ms (default 60000ms, or 1min)" #ifdef OSS_AUDIO NL " -d dev Select a different output device instead of /dev/dsp" #endif NL " -c spec Compensate for low-frequency headphone roll-off; see docs" NL ); exit(0); } void usage() { error("SBaGen - Sequenced Binaural Beat Generator, version " VERSION NL "Copyright (c) 1999-2004 Jim Peters, http://uazu.net/, all rights " NL " reserved, released under the GNU GPL v2. See file COPYING." NL NL "Usage: sbagen [options] seq-file ..." NL " sbagen [options] -i tone-specs ..." NL " sbagen [options] -p pre-programmed-sequence-specs ..." NL NL "For full usage help, type 'sbagen -h'. For latest version see" NL "http://uazu.net/sbagen/ or http://sbagen.sf.net/" #ifdef EXIT_KEY NL NL "Windows users please note that this utility is designed to be run as the" NL "associated application for SBG files. This should have been set up for you by" NL "the installer. You can run all the SBG files directly from the desktop by" NL "double-clicking on them, and edit them using NotePad from the right-click menu." NL "Alternatively, SBaGen may be run from the MS-DOS prompt (CMD on WinXP), or from" NL "BAT files. SBaGen is powerful software -- it is worth the effort of figuring" NL "all this out. See SBAGEN.TXT for the full documentation." NL NL "Editing the SBG files gives you access to the full tweakable power of SBaGen, " NL "but if you want a simple GUI interface to the most basic features, you could " NL "look at a user-contributed tool called SBaGUI:" NL NL " http://sbagen.opensrc.org/wiki.php?page=SBaGUI" #endif NL); } #define DEBUG_CHK_UTIME 0 // Check how much user time is being consumed #define DEBUG_DUMP_WAVES 0 // Dump out wave tables (to plot with gnuplot) #define DEBUG_DUMP_AMP 0 // Dump output amplitude to stdout per chunk #define N_CH 16 // Number of channels struct Voice { int typ; // Voice type: 0 off, 1 binaural, 2 pink noise, 3 bell, 4 spin, // 5 mix, 6 mixspin, 7 mixbeat, -1 to -100 wave00 to wave99 double amp; // Amplitude level (0-4096 for 0-100%) double carr; // Carrier freq (for binaural/bell), width (for spin) double res; // Resonance freq (-ve or +ve) (for binaural/spin) }; struct Channel { Voice v; // Current voice setting (updated from current period) int typ; // Current type: 0 off, 1 binaural, 2 pink noise, 3 bell, 4 spin, // 5 mix, 6 mixspin, 7 mixbeat, -1 to -100 wave00 to wave99 int amp, amp2; // Current state, according to current type int inc1, off1; // :: (for binaural tones, offset + increment into sine int inc2, off2; // :: table * 65536) }; struct Period { Period *nxt, *prv; // Next/prev in chain int tim; // Start time (end time is ->nxt->tim) Voice v0[N_CH], v1[N_CH]; // Start and end voices int fi, fo; // Temporary: Fade-in, fade-out modes }; struct NameDef { NameDef *nxt; char *name; // Name of definition BlockDef *blk; // Non-zero for block definition Voice vv[N_CH]; // Voice-set for it (unless a block definition) }; struct BlockDef { BlockDef *nxt; // Next in chain char *lin; // StrDup'd line }; #define ST_AMP 0x7FFFF // Amplitude of wave in sine-table #define NS_ADJ 12 // Noise is generated internally with amplitude ST_AMP<amp value #define AMP_AD(amp) ((amp) / 40.96) // Amplitude value to display %age int *waves[100]; // Pointers are either 0 or point to a sin_table[]-style array of int Channel chan[N_CH]; // Current channel states int now; // Current time (milliseconds from midnight) Period *per= 0; // Current period NameDef *nlist; // Full list of name definitions int *tmp_buf; // Temporary buffer for 20-bit mix values short *out_buf; // Output buffer int out_bsiz; // Output buffer size (bytes) int out_blen; // Output buffer length (samples) (1.0* or 0.5* out_bsiz) int out_bps; // Output bytes per sample (2 or 4) int out_buf_ms; // Time to output a buffer-ful in ms int out_buf_lo; // Time to output a buffer-ful, fine-tuning in ms/0x10000 int out_fd; // Output file descriptor int out_rate= 44100; // Sample rate int out_rate_def= 1; // Sample rate is default value, not set by user int out_mode= 1; // Output mode: 0 unsigned char[2], 1 short[2], 2 swapped short[2] int out_prate= 10; // Rate of parameter change (for file and pipe output only) int fade_int= 60000; // Fade interval (ms) FILE *in; // Input sequence file int in_lin; // Current input line char buf[4096]; // Buffer for current line char buf_copy[4096]; // Used to keep unmodified copy of line char *lin; // Input line (uses buf[]) char *lin_copy; // Copy of input line double spin_carr_max; // Maximum 'carrier' value for spin (really max width in us) #define NS_BIT 10 int ns_tbl[1<), or -1 int bigendian; // Is this platform Big-endian? int mix_flag= 0; // Has 'mix/*' been used in the sequence? int opt_c; // Number of -c option points provided (max 16) struct AmpAdj { double freq, adj; } ampadj[16]; // List of maximum 16 (freq,adj) pairs, freq-increasing order char *pdir; // Program directory (used as second place to look for -m files) #ifdef WIN_AUDIO #define BUFFER_COUNT 8 #define BUFFER_SIZE 8192*4 HWAVEOUT aud_handle; WAVEHDR *aud_head[BUFFER_COUNT]; int aud_current; // Current header int aud_cnt; // Number of headers in use #endif #ifdef MAC_AUDIO #define BUFFER_COUNT 8 #define BUFFER_SIZE 4096*4 char aud_buf[BUFFER_COUNT][BUFFER_SIZE]; int aud_rd; // Next buffer to read out of list (to send to device) int aud_wr; // Next buffer to write. aud_rd==aud_wr means empty buffer list static AudioDeviceID aud_dev; #endif // // Delay for a short period of time (in ms) // #ifdef UNIX_MISC void delay(int ms) { struct timespec ts; ts.tv_sec= ms / 1000; ts.tv_nsec= (ms % 1000) * 1000000; nanosleep(&ts, 0); } #endif #ifdef WIN_MISC void delay(int ms) { Sleep(ms); } #endif // // WAV/OGG/MP3 input data buffering // int *inbuf; // Buffer for input data (as 20-bit samples) int ib_len; // Length of input buffer (in ints) volatile int ib_rd; // Read-offset in inbuf volatile int ib_wr; // Write-offset in inbuf volatile int ib_eof; // End of file flag int ib_cycle= 100; // Time in ms for a complete loop through the buffer int (*ib_read)(int*,int); // Routine to refill buffer int inbuf_loop(void *vp) { int now= -1; int waited= 0; // Used to bail out if the main thread dies for some reason int a; while (1) { int rv; int rd= ib_rd; int wr= ib_wr; int cnt= (rd-1-wr) & (ib_len-1); if (cnt > ib_len-wr) cnt= ib_len-wr; if (cnt > ib_len/8) cnt= ib_len/8; // Choose to only work in ib_len/8 units, although this is not // 100% necessary if (cnt < ib_len/8) { // Wait a little while for the buffer to empty (minimum 1ms) if (waited > 10000 + ib_cycle) error("Mix stream halted for more than 10 seconds; aborting"); delay(a= 1+ib_cycle/4); waited += a; continue; } waited= 0; rv= ib_read(inbuf+wr, cnt); //debug("ib_read %d-%d (%d) -> %d", wr, wr+cnt-1, cnt, rv); if (rv != cnt) { ib_eof= 1; return 0; } ib_wr= (wr + rv) & (ib_len-1); // Whenever we roll over, recalculate 'ib_cycle' if (ib_wr < wr) { int prev= now; now= calcNow(); if (prev >= 0 && now > prev) ib_cycle= now - prev; //debug("Input buffer cycle duration is now %dms", ib_cycle); } } return 0; } // // Read a chunk of int data from the input buffer. This will // always return enough data unless we have hit the end of the // file, in which case it returns a lower number or 0. If not // enough data has been read by the input thread, then this // thread pauses until data is ready -- but this should hopefully // never happen. // int inbuf_read(int *dst, int dlen) { int rv= 0; int waited= 0; // As a precaution, bail out if other thread hangs for some reason int a; while (dlen > 0) { int rd= ib_rd; int wr= ib_wr; int avail= (wr-rd) & (ib_len-1); int toend= ib_len-rd; if (avail > toend) avail= toend; if (avail > dlen) avail= dlen; if (avail == 0) { if (ib_eof) return rv; // Necessary to wait for incoming mix data. This should // never happen in normal running, though, unless we are // outputting to a file if (waited > 10000) error("Mix stream problem; waited more than 10 seconds for data; aborting"); //debug("Waiting for input thread (%d)", ib_eof); delay(a= ib_cycle/4 > 100 ? 100 : 1+ib_cycle/4); waited += a; continue; } waited= 0; memcpy(dst, inbuf+rd, avail * sizeof(int)); dst += avail; dlen -= avail; rv += avail; ib_rd= (rd + avail) & (ib_len-1); } return rv; } // // Start off the thread that fills the buffer // void inbuf_start(int(*rout)(int*,int), int len) { if (0 != (len & (len-1))) error("inbuf_start() called with length not a power of two"); ib_read= rout; ib_len= len; inbuf= ALLOC_ARR(ib_len, int); ib_rd= 0; ib_wr= 0; ib_eof= 0; if (!opt_Q) warn("Initialising %d-sample buffer for mix stream", ib_len/2); // Preload 75% of the buffer -- or at least attempt to do so; // errors/eof/etc will be picked up in the inbuf_loop() routine ib_wr= ib_read(inbuf, ib_len*3/4); // Start the thread off #ifdef UNIX_MISC { pthread_t thread; if (0 != pthread_create(&thread, NULL, (void*)&inbuf_loop, NULL)) error("Failed to start input buffering thread"); } #endif #ifdef WIN_MISC { DWORD tmp; if (0 == CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&inbuf_loop, 0, 0, &tmp)) error("Failed to start input buffering thread"); } #endif } // // Time-keeping functions // #define H24 (86400000) // 24 hours #define H12 (43200000) // 12 hours inline int t_per24(int t0, int t1) { // Length of period starting at t0, ending at t1. int td= t1 - t0; // NB for t0==t1 this gives 24 hours, *NOT 0* return td > 0 ? td : td + H24; } inline int t_per0(int t0, int t1) { // Length of period starting at t0, ending at t1. int td= t1 - t0; // NB for t0==t1 this gives 0 hours return td >= 0 ? td : td + H24; } inline int t_mid(int t0, int t1) { // Midpoint of period from t0 to t1 return ((t1 < t0) ? (H24 + t0 + t1) / 2 : (t0 + t1) / 2) % H24; } // // M A I N // int main(int argc, char **argv) { short test= 0x1100; int rv; char *p; pdir= StrDup(argv[0]); p= strchr(pdir, 0); while (p > pdir && p[-1] != '/' && p[-1] != '\\') *--p= 0; argc--; argv++; init_sin_table(); bigendian= ((char*)&test)[0] != 0; // Process all the options rv= scanOptions(&argc, &argv); if (argc < 1) usage(); if (rv == 'i') { // Immediate mode readSeqImm(argc, argv); } else if (rv == 'p') { // Pre-programmed sequence readPreProg(argc, argv); } else { // Sequenced mode -- sequence may include options, so options // are not settled until below this point if (argc < 1) usage(); readSeq(argc, argv); } if (opt_W && !opt_o && !opt_O) error("Use -o or -O with the -W option"); if (opt_W && opt_L < 0 && !opt_E) error("Use -L or -E with -W option to give the length of the WAV file"); mix_in= 0; if (opt_M || opt_m) { char *p; char tmp[4]; int raw= 1; if (opt_M) { mix_in= stdin; tmp[0]= 0; } if (opt_m) { // Pick up # on end of filename p= strchr(opt_m, 0); mix_cnt= -1; if (p > opt_m && isdigit(p[-1])) { mix_cnt= 0; while (p > opt_m && isdigit(p[-1])) mix_cnt= mix_cnt * 10 + *--p - '0'; if (p > opt_m && p[-1] == '#') *--p= 0; else { p= strchr(opt_m, 0); mix_cnt= -1; } } // p points to end of filename (NUL) // Open file mix_in= fopen(opt_m, "rb"); if (!mix_in && opt_m[0] != '/') { int len= strlen(opt_m) + strlen(pdir) + 1; char *tmp= ALLOC_ARR(len, char); strcpy(tmp, pdir); strcat(tmp, opt_m); mix_in= fopen(tmp, "rb"); free(tmp); } if (!mix_in) error("Can't open -m option mix input file: %s", opt_m); // Pick up extension if (p-opt_m >= 4 && p[-4] == '.') { tmp[0]= tolower(p[-3]); tmp[1]= tolower(p[-2]); tmp[2]= tolower(p[-1]); tmp[3]= 0; } } if (0 == strcmp(tmp, "wav")) // Skip header on WAV files find_wav_data_start(mix_in); if (0 == strcmp(tmp, "ogg")) { #ifdef OGG_DECODE ogg_init(); raw= 0; #else error("Sorry: Ogg support wasn't compiled into this executable"); #endif } if (0 == strcmp(tmp, "mp3")) { #ifdef MP3_DECODE mp3_init(); raw= 0; #else error("Sorry: MP3 support wasn't compiled into this executable"); #endif } // If this is a raw/wav data stream, setup a 256*1024-int // buffer (3s@44.1kHz) if (raw) inbuf_start(raw_mix_in, 256*1024); } loop(); return 0; } // // Scan options. Returns a flag indicating what is expected to // interpret the rest of the arguments: 0 normal, 'i' immediate // (-i option), 'p' -p option. // int scanOptions(int *acp, char ***avp) { int argc= *acp; char **argv= *avp; int val; char dmy; int rv= 0; // Scan options while (argc > 0 && argv[0][0] == '-' && argv[0][1]) { char opt, *p= 1 + *argv++; argc--; while ((opt= *p++)) { // Check options that are available on both switch (opt) { case 'Q': opt_Q= 1; break; case 'E': opt_E= 1; break; case 'm': if (argc-- < 1) error("-m option expects filename"); // Earliest takes precedence, so command-line overrides sequence file if (!opt_m) opt_m= *argv++; break; case 'S': opt_S= 1; if (!fast_mult) fast_mult= 1; // Don't try to sync with real time break; case 'L': if (argc-- < 1 || 0 == (val= readTime(*argv, &opt_L)) || 1 == sscanf(*argv++ + val, " %c", &dmy)) error("-L expects hh:mm or hh:mm:ss time"); break; case 'T': if (argc-- < 1 || 0 == (val= readTime(*argv, &opt_T)) || 1 == sscanf(*argv++ + val, " %c", &dmy)) error("-T expects hh:mm or hh:mm:ss time"); if (!fast_mult) fast_mult= 1; // Don't try to sync with real time break; case 'F': if (argc-- < 1 || 1 != sscanf(*argv++, "%d %c", &fade_int, &dmy)) error("-F expects fade-time in ms"); break; case 'c': if (argc-- < 1) error("-c expects argument"); setupOptC(*argv++); break; case 'i': rv= 'i'; break; case 'p': rv= 'p'; break; case 'h': help(); break; case 'D': opt_D= 1; break; case 'M': opt_M= 1; break; case 'O': opt_O= 1; if (!fast_mult) fast_mult= 1; // Don't try to sync with real time break; case 'W': opt_W= 1; if (!fast_mult) fast_mult= 1; // Don't try to sync with real time break; case 'q': opt_S= 1; if (argc-- < 1 || 1 != sscanf(*argv++, "%d %c", &fast_mult, &dmy)) error("Expecting an integer after -q"); if (fast_mult < 1) fast_mult= 1; break; case 'r': if (argc-- < 1 || 1 != sscanf(*argv++, "%d %c", &out_rate, &dmy)) error("Expecting an integer after -r"); out_rate_def= 0; break; #ifndef MAC_AUDIO case 'b': if (argc-- < 1 || 1 != sscanf(*argv++, "%d %c", &val, &dmy) || !(val == 8 || val == 16)) error("Expecting -b 8 or -b 16"); out_mode= (val == 8) ? 0 : 1; break; #endif case 'o': if (argc-- < 1) error("Expecting filename after -o"); opt_o= *argv++; if (!fast_mult) fast_mult= 1; // Don't try to sync with real time break; #ifdef OSS_AUDIO case 'd': if (argc-- < 1) error("Expecting device filename after -d"); opt_d= *argv++; break; #endif case 'R': if (argc-- < 1 || 1 != sscanf(*argv++, "%d %c", &out_prate, &dmy)) error("Expecting integer after -R"); break; default: error("Option -%c not known; run 'sbagen -h' for help", opt); } } } *acp= argc; *avp= argv; return rv; } // // Handle an option string, breaking it into an (argc/argv) list // for scanOptions. // void handleOptions(char *str0) { // Always StrDup() string and don't bother to free(), as normal // argv[] strings stick around for the life of the program char *str= StrDup(str0); int const max_argc= 32; char *argv[max_argc+1]; int argc= 0; while (*str) { if (argc >= max_argc) error("Too many options at line: %d\n %s", in_lin, lin_copy); argv[argc++]= str; while (*str && !isspace(*str)) str++; if (!*str) continue; *str++= 0; // NUL-term this word while (isspace(*str)) str++; } argv[argc]= 0; // Terminate argv list with a NULL // Process the options { char **av= argv; int ac= argc; int rv; rv= scanOptions(&ac, &av); if (rv == 'i') { // Immediate mode readSeqImm(ac, av); } else if (rv == 'p') { // Pre-programmed sequence readPreProg(ac, av); } else if (ac) error("Trailing garbage after options at line: %d\n %s", in_lin, lin_copy); } } // // Setup the ampadj[] array from the given -c spec-string // void setupOptC(char *spec) { char *p= spec, *q; int a, b; while (1) { while (isspace(*p) || *p == ',') p++; if (!*p) break; if (opt_c >= sizeof(ampadj) / sizeof(ampadj[0])) error("Too many -c option frequencies; maxmimum is %d", sizeof(ampadj) / sizeof(ampadj[0])); ampadj[opt_c].freq= strtod(p, &q); if (p == q) goto bad; if (*q++ != '=') goto bad; ampadj[opt_c].adj= strtod(q, &p); if (p == q) goto bad; opt_c++; } // Sort the list for (a= 0; a ampadj[b].freq) { double tmp; tmp= ampadj[a].freq; ampadj[a].freq= ampadj[b].freq; ampadj[b].freq= tmp; tmp= ampadj[a].adj; ampadj[a].adj= ampadj[b].adj; ampadj[b].adj= tmp; } return; bad: error("Bad -c option spec; expecting =[,=]...:\n %s", spec); } // // If this is a WAV file we've been given, skip forward to the // 'data' section. Don't bother checking any of the 'fmt ' // stuff. If they didn't give us a valid 16-bit stereo file at // the right rate, then tough! // void find_wav_data_start(FILE *in) { unsigned char buf[16]; if (1 != fread(buf, 12, 1, in)) goto bad; if (0 != memcmp(buf, "RIFF", 4)) goto bad; if (0 != memcmp(buf+8, "WAVE", 4)) goto bad; while (1) { int len; if (1 != fread(buf, 8, 1, in)) goto bad; if (0 == memcmp(buf, "data", 4)) return; // We're in the right place! len= buf[4] + (buf[5]<<8) + (buf[6]<<16) + (buf[7]<<24); if (len & 1) len++; if (out_rate_def && 0 == memcmp(buf, "fmt ", 4)) { // Grab the sample rate to use as the default if available if (1 != fread(buf, 8, 1, in)) goto bad; len -= 8; out_rate= buf[4] + (buf[5]<<8) + (buf[6]<<16) + (buf[7]<<24); out_rate_def= 0; } if (0 != fseek(in, len, SEEK_CUR)) goto bad; } bad: warn("WARNING: Not a valid WAV file, treating as RAW"); rewind(in); } // // Input raw audio data from the 'mix_in' stream, and convert to // 32-bit values (max 'dlen') // int raw_mix_in(int *dst, int dlen) { short *tmp= (void*)(dst + dlen/2); int a, rv; rv= fread(tmp, 2, dlen, mix_in); if (rv == 0) { if (feof(mix_in)) return 0; error("Read error on mix input:\n %s", strerror(errno)); } // Now convert 16-bit little-endian input data into 20-bit native // int values if (bigendian) { char *rd= (void*)tmp; for (a= 0; a 1 && chan[nch-1].v.typ == 0) nch--; for (a= 0; a p-p0) *p++= ' '; #endif tty_erase= p1-p0; // Characters that will need erasing fprintf(stderr, "%s\r", buf); fflush(stderr); } void // Display current period details dispCurrPer(FILE *fp) { int a; Voice *v0, *v1; char *p0, *p1; int len0, len1; int nch= N_CH; if (opt_Q) return; p0= buf; p1= buf_copy; p0 += sprintf(p0, "* "); p0 += sprintTime(p0, per->tim); p1 += sprintf(p1, " "); p1 += sprintTime(p1, per->nxt->tim); v0= per->v0; v1= per->v1; while (nch > 1 && v0[nch-1].typ == 0) nch--; for (a= 0; atyp) { case 0: return sprintf(p, " -"); case 1: if (dup && vp->carr == dup->carr && vp->res == dup->res && vp->amp == dup->amp) return sprintf(p, " ::"); return sprintf(p, " %.2f%+.2f/%.2f", vp->carr, vp->res, AMP_AD(vp->amp)); case 2: if (dup && vp->amp == dup->amp) return sprintf(p, " ::"); return sprintf(p, " pink/%.2f", AMP_AD(vp->amp)); case 3: if (dup && vp->carr == dup->carr && vp->amp == dup->amp) return sprintf(p, " ::"); return sprintf(p, " bell%+.2f/%.2f", vp->carr, AMP_AD(vp->amp)); case 4: if (dup && vp->carr == dup->carr && vp->res == dup->res && vp->amp == dup->amp) return sprintf(p, " ::"); return sprintf(p, " spin:%.2f%+.2f/%.2f", vp->carr, vp->res, AMP_AD(vp->amp)); case 5: if (dup && vp->amp == dup->amp) return sprintf(p, " ::"); return sprintf(p, " mix/%.2f", AMP_AD(vp->amp)); default: if (vp->typ < -100 || vp->typ > -1) return sprintf(p, " ERROR"); if (dup && vp->typ == dup->typ && vp->carr == dup->carr && vp->res == dup->res && vp->amp == dup->amp) return sprintf(p, " ::"); return sprintf(p, " wave%02d:%.2f%+.2f/%.2f", -1-vp->typ, vp->carr, vp->res, AMP_AD(vp->amp)); } } void init_sin_table() { int a; int *arr= (int*)Alloc(ST_SIZ * sizeof(int)); for (a= 0; a to continue: "); fflush(stderr); getchar(); #endif exit(1); } void debug(char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); } void warn(char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); } void * Alloc(size_t len) { void *p= calloc(1, len); if (!p) error("Out of memory"); return p; } char * StrDup(char *str) { char *rv= strdup(str); if (!rv) error("Out of memory"); return rv; } #ifdef UNIX_TIME // Precalculate a reference timestamp to accelerate calcNow(). This // can be any recent time. We recalculate it every 10 minutes. The // only reason for doing this is to cope with clocks going forwards or // backwards when entering or leaving summer time so that people wake // up on time on these two dates; an hour of the sequence will be // repeated or skipped. The 'time_ref*' variables will be initialised // on the first call to calcNow(). static int time_ref_epoch= 0; // Reference time compared to UNIX epoch static int time_ref_ms; // Reference time in sbagen 24-hour milliseconds void setupRefTime() { struct tm *tt; time_t tim= time(0); tt= localtime(&tim); time_ref_epoch= tim; time_ref_ms= 1000*tt->tm_sec + 60000*tt->tm_min + 3600000*tt->tm_hour; } inline int calcNow() { struct timeval tv; if (0 != gettimeofday(&tv, 0)) error("Can't get current time"); if (tv.tv_sec - time_ref_epoch > 600) setupRefTime(); return (time_ref_ms + (tv.tv_sec - time_ref_epoch) * 1000 + tv.tv_usec / 1000) % H24; } #endif #ifdef WIN_TIME inline int calcNow() { SYSTEMTIME st; GetLocalTime(&st); return st.wMilliseconds + 1000*st.wSecond + 60000*st.wMinute + 3600000*st.wHour; } #endif #if DEBUG_CHK_UTIME inline int userTime() { struct tms buf; times(&buf); return buf.tms_utime; } #else // Dummy to avoid complaints on MSVC int userTime() { return 0; } #endif // // Simple random number generator. Generates a repeating // sequence of 65536 odd numbers in the range -65535->65535. // // Based on ZX Spectrum random number generator: // seed= (seed+1) * 75 % 65537 - 1 // #define RAND_MULT 75 static int seed= 2; //inline int qrand() { // return (seed= seed * 75 % 131074) - 65535; //} // // Generate next sample for simulated pink noise, with same // scaling as the sin_table[]. This version uses an inlined // random number generator, and smooths the lower frequency bands // as well. // #define NS_BANDS 9 typedef struct Noise Noise; struct Noise { int val; // Current output value int inc; // Increment }; Noise ntbl[NS_BANDS]; int nt_off; int noise_buf[256]; uchar noise_off= 0; static inline int noise2() { int tot; int off= nt_off++; int cnt= 1; Noise *ns= ntbl; Noise *ns1= ntbl + NS_BANDS; tot= ((seed= seed * RAND_MULT % 131074) - 65535) * (NS_AMP / 65535 / (NS_BANDS + 1)); while ((cnt & off) && ns < ns1) { int val= ((seed= seed * RAND_MULT % 131074) - 65535) * (NS_AMP / 65535 / (NS_BANDS + 1)); tot += ns->val += ns->inc= (val - ns->val) / (cnt += cnt); ns++; } while (ns < ns1) { tot += (ns->val += ns->inc); ns++; } return noise_buf[noise_off++]= (tot >> NS_ADJ); } // // // // Generate next sample for simulated pink noise, scaled the same // // as the sin_table[]. This version uses a library random number // // generator, and no smoothing. // // // // inline double // noise() { // int tot= 0; // int bit= ~0; // int a; // int off; // // ns_tbl[ns_off]= (rand() - (RAND_MAX / 2)) / (NS_BIT + 1); // off= ns_off; // for (a= 0; a<=NS_BIT; a++, bit <<= 1) { // off &= bit; // tot += ns_tbl[off]; // } // ns_off= (ns_off + 1) & ((1< 20; // Very fast - update status line often int utime= 0; int now_lo= 0; // Low-order 16 bits of 'now' (fractional) int err_lo= 0; int ms_inc; setup_device(); spin_carr_max= 127.0 / 1E-6 / out_rate; cnt= 1 + 1999 / out_buf_ms; // Update every 2 seconds or so now= opt_S ? fast_tim0 : calcNow(); if (opt_T != -1) now= opt_T; err= fast ? out_buf_ms * (fast_mult - 1) : 0; if (opt_L) byte_count= out_bps * (S64)(opt_L * 0.001 * out_rate); if (opt_E) byte_count= out_bps * (S64)(t_per0(now, fast_tim1) * 0.001 * out_rate / (fast ? fast_mult : 1)); // Do byte-swapping if bigendian and outputting to a file or stream if ((opt_O || opt_o) && out_mode == 1 && bigendian) out_mode= 2; if (opt_W) writeWAV(); if (!opt_Q) fprintf(stderr, "\n"); corrVal(0); // Get into correct period dispCurrPer(stderr); // Display status(0); while (1) { for (c= 0; c < cnt; c++) { corrVal(1); outChunk(); ms_inc= out_buf_ms + err; now_lo += out_buf_lo + err_lo; if (now_lo >= 0x10000) { ms_inc += now_lo >> 16; now_lo &= 0xFFFF; } now += ms_inc; if (now > H24) now -= H24; if (vfast && (c&1)) status(0); } if (fast) { if (!vfast) status(0); } else { // Synchronize with real clock, gently over the next second or so char buf[32]; int diff= calcNow() - now; if (abs(diff) > H12) diff= 0; sprintf(buf, "(%d)", diff); err_lo= diff * 0x10000 / cnt; err= err_lo >> 16; err_lo &= 0xFFFF; if (DEBUG_CHK_UTIME) { int prev= utime; utime= userTime(); sprintf(buf, "%d ticks", utime-prev); // Replaces standard message } status(buf); } } } // // Output a chunk of sound (a buffer-ful), then return // // Note: Optimised for 16-bit output. Eight-bit output is // slower, but then it probably won't have to run at as high a // sample rate. // int rand0, rand1; void outChunk() { int off= 0; if (mix_in) { int rv= inbuf_read(tmp_buf, out_blen); if (rv == 0) { if (!opt_Q) warn("\nEnd of mix input audio stream"); exit(0); } while (rv < out_blen) tmp_buf[rv++]= 0; } while (off < out_blen) { int ns= noise2(); // Use same pink noise source for everything int tot1, tot2; // Left and right channels int mix1, mix2; // Incoming mix signals int val, a; Channel *ch; int *tab; mix1= tmp_buf[off]; mix2= tmp_buf[off+1]; // Do default mixing at 100% if no mix/* stuff is present if (!mix_flag) { tot1= mix1 << 12; tot2= mix2 << 12; } else { tot1= tot2= 0; } ch= &chan[0]; for (a= 0; atyp) { case 0: break; case 1: // Binaural tones ch->off1 += ch->inc1; ch->off1 &= (ST_SIZ << 16) - 1; tot1 += ch->amp * sin_table[ch->off1 >> 16]; ch->off2 += ch->inc2; ch->off2 &= (ST_SIZ << 16) - 1; tot2 += ch->amp2 * sin_table[ch->off2 >> 16]; break; case 2: // Pink noise val= ns * ch->amp; tot1 += val; tot2 += val; break; case 3: // Bell if (ch->off2) { ch->off1 += ch->inc1; ch->off1 &= (ST_SIZ << 16) - 1; val= ch->off2 * sin_table[ch->off1 >> 16]; tot1 += val; tot2 += val; if (--ch->inc2 < 0) { ch->inc2= out_rate/20; ch->off2 -= 1 + ch->off2 / 12; // Knock off 10% each 50 ms } } break; case 4: // Spinning pink noise ch->off1 += ch->inc1; ch->off1 &= (ST_SIZ << 16) - 1; val= (ch->inc2 * sin_table[ch->off1 >> 16]) >> 24; tot1 += ch->amp * noise_buf[(uchar)(noise_off+128+val)]; tot2 += ch->amp * noise_buf[(uchar)(noise_off+128-val)]; break; case 5: // Mix level tot1 += mix1 * ch->amp; tot2 += mix2 * ch->amp; break; default: // Waveform-based binaural tones tab= waves[-1 - ch->typ]; ch->off1 += ch->inc1; ch->off1 &= (ST_SIZ << 16) - 1; tot1 += ch->amp * tab[ch->off1 >> 16]; ch->off2 += ch->inc2; ch->off2 &= (ST_SIZ << 16) - 1; tot2 += ch->amp * tab[ch->off2 >> 16]; break; } // // Add pink noise as dithering // tot1 += (ns >> NS_DITHER) + 0x8000; // tot2 += (ns >> NS_DITHER) + 0x8000; // // Add white noise as dithering // tot1 += (seed >> 1) + 0x8000; // tot2 += (seed >> 1) + 0x8000; // White noise dither; you could also try (rand0-rand1) for a // dither with more high frequencies rand0= rand1; rand1= (rand0 * 0x660D + 0xF35F) & 0xFFFF; if (tot1 <= 0x7FFF0000) tot1 += rand0; if (tot2 <= 0x7FFF0000) tot2 += rand0; out_buf[off++]= tot1 >> 16; out_buf[off++]= tot2 >> 16; } // Generate debugging amplitude output if (DEBUG_DUMP_AMP) { short *sp= out_buf; short *end= out_buf + out_blen; int max= 0; while (sp < end) { int val= (int)sp[0] + (int)sp[1]; sp += 2; if (val < 0) val= -val; if (val > max) max= val; } max /= 328; while (max-- > 0) putc('#', stdout); printf("\n"); fflush(stdout); } // Rewrite buffer for 8-bit mode if (out_mode == 0) { short *sp= out_buf; short *end= out_buf + out_blen; char *cp= (char*)out_buf; while (sp < end) *cp++= (*sp++ >> 8) + 128; } // Rewrite buffer for 16-bit byte-swapping if (out_mode == 2) { char *cp= (char*)out_buf; char *end= (char*)(out_buf + out_blen); while (cp < end) { char tmp= *cp++; cp[-1]= cp[0]; *cp++= tmp; } } // Check and update the byte count if necessary if (byte_count > 0) { if (byte_count <= out_bsiz) { writeOut((char*)out_buf, byte_count); exit(0); // All done } else { writeOut((char*)out_buf, out_bsiz); byte_count -= out_bsiz; } } else writeOut((char*)out_buf, out_bsiz); } void writeOut(char *buf, int siz) { int rv; #ifdef WIN_AUDIO if (out_fd == -9999) { // Win32 output: write it to a header and send it off MMRESULT rv; //debug_win32_buffer_status(); //while (aud_cnt == BUFFER_COUNT) { //while (aud_head[aud_current]->dwFlags & WHDR_INQUEUE) { while (!(aud_head[aud_current]->dwFlags & WHDR_DONE)) { //debug("SLEEP %d", out_buf_ms / 2 + 1); Sleep(out_buf_ms / 2 + 1); //debug_win32_buffer_status(); } memcpy(aud_head[aud_current]->lpData, buf, siz); aud_head[aud_current]->dwBufferLength= (DWORD)siz; //debug("Output buffer %d", aud_current); rv= waveOutWrite(aud_handle, aud_head[aud_current], sizeof(WAVEHDR)); if (rv != MMSYSERR_NOERROR) { char buf[255]; waveOutGetErrorText(rv, buf, sizeof(buf)-1); error("Error writing a fragment to the audio device:\n %s", buf); } aud_cnt++; aud_current++; aud_current %= BUFFER_COUNT; return; } #endif #ifdef MAC_AUDIO if (out_fd == -9999) { int new_wr= (aud_wr + 1) % BUFFER_COUNT; // Wait until there is space while (new_wr == aud_rd) delay(20); memcpy(aud_buf[aud_wr], buf, siz); aud_wr= new_wr; return; } #endif while (-1 != (rv= write(out_fd, buf, siz))) { if (0 == (siz -= rv)) return; buf += rv; } error("Output error"); } // // Calculate amplitude adjustment factor for frequency 'freq' // double ampAdjust(double freq) { int a; struct AmpAdj *p0, *p1; if (!opt_c) return 1.0; if (freq <= ampadj[0].freq) return ampadj[0].adj; if (freq >= ampadj[opt_c-1].freq) return ampadj[opt_c-1].adj; for (a= 1; aadj + (p1->adj - p0->adj) * (freq - p0->freq) / (p1->freq - p0->freq); } // // Correct channel values and types according to current period, // and current time // void corrVal(int running) { int a; int t0= per->tim; int t1= per->nxt->tim; Channel *ch; Voice *v0, *v1, *vv; double rat0, rat1; int trigger= 0; // Move to the correct period while ((now >= t0) ^ (now >= t1) ^ (t1 > t0)) { per= per->nxt; t0= per->tim; t1= per->nxt->tim; if (running) { if (tty_erase) { #ifdef ANSI_TTY fprintf(stderr, "\033[K"); #else fprintf(stderr, "%*s\r", tty_erase, ""); tty_erase= 0; #endif } dispCurrPer(stderr); status(0); } trigger= 1; // Trigger bells or whatever } // Run through to calculate voice settings for current time rat1= t_per0(t0, now) / (double)t_per24(t0, t1); rat0= 1 - rat1; for (a= 0; av0[a]; v1= &per->v1[a]; vv= &ch->v; if (vv->typ != v0->typ) { switch (vv->typ= ch->typ= v0->typ) { case 1: ch->off1= ch->off2= 0; break; case 2: break; case 3: ch->off1= ch->off2= 0; break; case 4: ch->off1= ch->off2= 0; break; case 5: break; default: ch->off1= ch->off2= 0; break; } } // Setup vv->* switch (vv->typ) { case 1: vv->amp= rat0 * v0->amp + rat1 * v1->amp; vv->carr= rat0 * v0->carr + rat1 * v1->carr; vv->res= rat0 * v0->res + rat1 * v1->res; break; case 2: vv->amp= rat0 * v0->amp + rat1 * v1->amp; break; case 3: vv->amp= v0->amp; // No need to slide, as bell only rings briefly vv->carr= v0->carr; break; case 4: vv->amp= rat0 * v0->amp + rat1 * v1->amp; vv->carr= rat0 * v0->carr + rat1 * v1->carr; vv->res= rat0 * v0->res + rat1 * v1->res; if (vv->carr > spin_carr_max) vv->carr= spin_carr_max; // Clipping sweep width if (vv->carr < -spin_carr_max) vv->carr= -spin_carr_max; break; case 5: vv->amp= rat0 * v0->amp + rat1 * v1->amp; break; default: // Waveform based binaural vv->amp= rat0 * v0->amp + rat1 * v1->amp; vv->carr= rat0 * v0->carr + rat1 * v1->carr; vv->res= rat0 * v0->res + rat1 * v1->res; break; } } // Check and limit amplitudes if -c option in use if (opt_c) { double tot_beat= 0, tot_other= 0; for (a= 0; atyp == 1) { double adj1= ampAdjust(vv->carr + vv->res/2); double adj2= ampAdjust(vv->carr - vv->res/2); if (adj2 > adj1) adj1= adj2; tot_beat += vv->amp * adj1; } else if (vv->typ) { tot_other += vv->amp; } } if (tot_beat + tot_other > 4096) { double adj_beat= (tot_beat > 4096) ? 4096 / tot_beat : 1.0; double adj_other= (4096 - tot_beat * adj_beat) / tot_other; for (a= 0; atyp == 1) vv->amp *= adj_beat; else if (vv->typ) vv->amp *= adj_other; } } } // Setup Channel data from Voice data for (a= 0; av; // Setup ch->* from vv->* switch (vv->typ) { double freq1, freq2; case 1: freq1= vv->carr + vv->res/2; freq2= vv->carr - vv->res/2; if (opt_c) { ch->amp= vv->amp * ampAdjust(freq1); ch->amp2= vv->amp * ampAdjust(freq2); } else ch->amp= ch->amp2= (int)vv->amp; ch->inc1= (int)(freq1 / out_rate * ST_SIZ * 65536); ch->inc2= (int)(freq2 / out_rate * ST_SIZ * 65536); break; case 2: ch->amp= (int)vv->amp; break; case 3: ch->amp= (int)vv->amp; ch->inc1= (int)(vv->carr / out_rate * ST_SIZ * 65536); if (trigger) { // Trigger the bell only on entering the period ch->off2= ch->amp; ch->inc2= out_rate/20; } break; case 4: ch->amp= (int)vv->amp; ch->inc1= (int)(vv->res / out_rate * ST_SIZ * 65536); ch->inc2= (int)(vv->carr * 1E-6 * out_rate * (1<<24) / ST_AMP); break; case 5: ch->amp= (int)vv->amp; break; default: // Waveform based binaural ch->amp= (int)vv->amp; ch->inc1= (int)((vv->carr + vv->res/2) / out_rate * ST_SIZ * 65536); ch->inc2= (int)((vv->carr - vv->res/2) / out_rate * ST_SIZ * 65536); if (ch->inc1 > ch->inc2) ch->inc2= -ch->inc2; else ch->inc1= -ch->inc1; break; } } } // // Setup audio device // void setup_device(void) { // Handle output to files and pipes if (opt_O || opt_o) { if (opt_O) out_fd= 1; // stdout else { FILE *out; // Need to create a stream to set binary mode for DOS if (!(out= fopen(opt_o, "wb"))) error("Can't open \"%s\", errno %d", opt_o, errno); out_fd= fileno(out); } out_blen= out_rate * 2 / out_prate; // 10 fragments a second by default while (out_blen & (out_blen-1)) out_blen &= out_blen-1; // Make power of two out_bsiz= out_blen * (out_mode ? 2 : 1); out_bps= out_mode ? 4 : 2; out_buf= (short*)Alloc(out_blen * sizeof(short)); out_buf_lo= (int)(0x10000 * 1000.0 * 0.5 * out_blen / out_rate); out_buf_ms= out_buf_lo >> 16; out_buf_lo &= 0xFFFF; tmp_buf= (int*)Alloc(out_blen * sizeof(int)); if (!opt_Q && !opt_W) // Informational message for opt_W is written later warn("Outputting %d-bit raw audio data at %d Hz with %d-sample blocks, %d ms per block", out_mode ? 16 : 8, out_rate, out_blen/2, out_buf_ms); return; } #ifdef OSS_AUDIO // Normal /dev/dsp output { int stereo, rate, fragsize, numfrags, enc; int targ_ms= 400; // How much buffering we want, ideally int afmt_req, afmt; int test= 1; audio_buf_info info; int retry= 0; fragsize= 14; // Ask for fragments of 2^14 == 16384 bytes == 4096 samples while (1) { if (0 > (out_fd= open(opt_d, O_WRONLY))) error("Can't open %s, errno %d", opt_d, errno); afmt= afmt_req= ((out_mode == 0) ? AFMT_U8 : ((char*)&test)[0] ? AFMT_S16_LE : AFMT_S16_BE); stereo= 1; rate= out_rate; numfrags= (out_rate * 4 * targ_ms / 1000) >> fragsize; if (numfrags < 1) numfrags= 1; enc= (numfrags<<16) | fragsize; if (0 > ioctl(out_fd, SNDCTL_DSP_SETFRAGMENT, &enc) || 0 > ioctl(out_fd, SNDCTL_DSP_SAMPLESIZE, &afmt) || 0 > ioctl(out_fd, SNDCTL_DSP_STEREO, &stereo) || 0 > ioctl(out_fd, SNDCTL_DSP_SPEED, &rate)) error("Can't configure %s, errno %d", opt_d, errno); if (afmt != afmt_req) error("Can't open device in %d-bit mode", out_mode ? 16 : 8); if (!stereo) error("Can't open device in stereo"); out_rate= rate; if (-1 == ioctl(out_fd, SNDCTL_DSP_GETOSPACE, &info)) error("Can't get audio buffer info, errno %d", errno); if (!retry && info.fragsize != (1<> 16; out_buf_lo &= 0xFFFF; tmp_buf= (int*)Alloc(out_blen * sizeof(int)); if (!opt_Q) warn("Outputting %d-bit audio at %d Hz with %d %d-sample fragments, %d ms per fragment", out_mode ? 16 : 8, out_rate, info.fragstotal, out_blen/2, out_buf_ms); } #endif #ifdef WIN_AUDIO // Output using Win32 calls { MMRESULT rv; WAVEFORMATEX fmt; int a; fmt.wFormatTag= WAVE_FORMAT_PCM; fmt.nChannels= 2; fmt.nSamplesPerSec= out_rate; fmt.wBitsPerSample= out_mode ? 16 : 8; fmt.nBlockAlign= 1; fmt.nAvgBytesPerSec= out_rate * 2 * (out_mode ? 2 : 1); fmt.cbSize= 0; aud_handle= NULL; // if (MMSYSERR_NOERROR != // waveOutOpen(&aud_handle, WAVE_MAPPER, &fmt, 0, // 0L, WAVE_FORMAT_QUERY)) // error("Windows is rejecting our audio request (%d-bit stereo, %dHz)", // out_mode ? 16 : 8, out_rate); if (MMSYSERR_NOERROR != (rv= waveOutOpen(&aud_handle, WAVE_MAPPER, (WAVEFORMATEX*)&fmt, (DWORD)win32_audio_callback, (DWORD)0, CALLBACK_FUNCTION))) { char buf[255]; waveOutGetErrorText(rv, buf, sizeof(buf)-1); error("Can't open audio device (%d-bit stereo, %dHz):\n %s", out_mode ? 16 : 8, out_rate, buf); } if (fmt.nChannels != 2) error("Can't open audio device in stereo"); if (fmt.wBitsPerSample != (out_mode ? 16 : 8)) error("Can't open audio device in %d-bit mode", out_mode ? 16 : 8); aud_current= 0; aud_cnt= 0; for (a= 0; alpData= (LPSTR)p + sizeof(WAVEHDR); w->dwBufferLength= (DWORD)BUFFER_SIZE; w->dwBytesRecorded= 0L; w->dwUser= 0; w->dwFlags= 0; w->dwLoops= 0; w->lpNext= 0; w->reserved= 0; rv= waveOutPrepareHeader(aud_handle, w, sizeof(WAVEHDR)); if (rv != MMSYSERR_NOERROR) { char buf[255]; waveOutGetErrorText(rv, buf, sizeof(buf)-1); error("Can't setup a wave header %d:\n %s", a, buf); } w->dwFlags |= WHDR_DONE; } out_rate= fmt.nSamplesPerSec; out_bsiz= BUFFER_SIZE; out_blen= out_mode ? out_bsiz/2 : out_bsiz; out_bps= out_mode ? 4 : 2; out_buf= (short*)Alloc(out_blen * sizeof(short)); out_buf_lo= (int)(0x10000 * 1000.0 * 0.5 * out_blen / out_rate); out_buf_ms= out_buf_lo >> 16; out_buf_lo &= 0xFFFF; out_fd= -9999; tmp_buf= (int*)Alloc(out_blen * sizeof(int)); if (!opt_Q) warn("Outputting %d-bit audio at %d Hz with %d %d-sample fragments, " "%d ms per fragment", out_mode ? 16 : 8, out_rate, BUFFER_COUNT, out_blen/2, out_buf_ms); } #endif #ifdef MAC_AUDIO // Mac CoreAudio for OS X { char deviceName[256]; OSStatus err; UInt32 propertySize, bufferByteCount; struct AudioStreamBasicDescription streamDesc; int old_out_rate= out_rate; out_bsiz= BUFFER_SIZE; out_blen= out_mode ? out_bsiz/2 : out_bsiz; out_bps= out_mode ? 4 : 2; out_buf= (short*)Alloc(out_blen * sizeof(short)); out_buf_lo= (int)(0x10000 * 1000.0 * 0.5 * out_blen / out_rate); out_buf_ms= out_buf_lo >> 16; out_buf_lo &= 0xFFFF; tmp_buf= (int*)Alloc(out_blen * sizeof(int)); // N.B. Both -r and -b flags are totally ignored for CoreAudio -- // we just use whatever the default device is set to, and feed it // floats. out_mode= 1; out_fd= -9999; // Find default device propertySize= sizeof(aud_dev); if ((err= AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &aud_dev))) error("Get default output device failed, status = %d", (int)err); if (aud_dev == kAudioDeviceUnknown) error("No default audio device found"); // Get device name propertySize= sizeof(deviceName); if ((err= AudioDeviceGetProperty(aud_dev, 1, 0, kAudioDevicePropertyDeviceName, &propertySize, deviceName))) error("Get audio device name failed, status = %d", (int)err); // Get device properties propertySize= sizeof(streamDesc); if ((err= AudioDeviceGetProperty(aud_dev, 1, 0, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc))) error("Get audio device properties failed, status = %d", (int)err); out_rate= (int)streamDesc.mSampleRate; if (streamDesc.mChannelsPerFrame != 2) error("SBaGen requires a stereo output device -- \n" "default output has %d channels", streamDesc.mChannelsPerFrame); if (streamDesc.mFormatID != kAudioFormatLinearPCM || !(streamDesc.mFormatFlags & kLinearPCMFormatFlagIsFloat)) error("Expecting a 32-bit float linear PCM output stream -- \n" "default output uses another format"); // Set buffer size bufferByteCount= BUFFER_SIZE / 2 * sizeof(float); propertySize= sizeof(bufferByteCount); if ((err= AudioDeviceSetProperty(aud_dev, 0, 0, 0, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount))) error("Set audio output buffer size failed, status = %d", (int)err); // Setup callback and start it err= AudioDeviceAddIOProc(aud_dev, mac_callback, (void *)1); err= AudioDeviceStart(aud_dev, mac_callback); // Report settings if (!opt_Q) { if (old_out_rate != out_rate && !out_rate_def) warn("*** WARNING: Non-default sampling rates not yet supported on OS X ***"); warn("Outputting %d-bit audio at %d Hz to \"%s\",\n" " using %d %d-sample fragments, %d ms per fragment", (int)streamDesc.mBitsPerChannel, out_rate, deviceName, BUFFER_COUNT, out_blen/2, out_buf_ms); } } #endif #ifdef NO_AUDIO error("Direct output to soundcard not supported on this platform.\n" "Use -o or -O to write raw data, or -Wo or -WO to write a WAV file."); #endif } // // Audio callback for Win32 // #ifdef WIN_AUDIO void CALLBACK win32_audio_callback(HWAVEOUT hand, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) { switch (uMsg) { case WOM_CLOSE: break; case WOM_OPEN: break; case WOM_DONE: aud_cnt--; //debug("Buffer done (cnt==%d)", aud_cnt); //debug_win32_buffer_status(); break; } } void debug_win32_buffer_status() { char tmp[80]; char *p= tmp; int a; for (a= 0; adwFlags & WHDR_INQUEUE) ? 'I' : '-'; *p++= (aud_head[a]->dwFlags & WHDR_DONE) ? 'D' : '-'; *p++= ' '; } p[-1]= 0; debug(tmp); } #endif // // Audio callback for Mac OS X // #ifdef MAC_AUDIO OSStatus mac_callback(AudioDeviceID inDevice, const AudioTimeStamp *inNow, const AudioBufferList *inInputData, const AudioTimeStamp *inInputTime, AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, void *inClientData) { float *fp= outOutputData->mBuffers[0].mData; int cnt= BUFFER_SIZE / 2; short *sp; if (aud_rd == aud_wr) { // Nothing in buffer list, so fill with silence while (cnt-- > 0) *fp++= 0.0; } else { // Consume a buffer sp= (short*)aud_buf[aud_rd]; while (cnt-- > 0) *fp++= *sp++ * (1/32768.0); aud_rd= (aud_rd + 1) % BUFFER_COUNT; } return kAudioHardwareNoError; } #endif // // Write a WAV header, and setup out_mode if byte-swapping is // required. 'byte_count' should have been set up by this point. // #define addU4(xx) { int a= xx; *p++= a; *p++= (a >>= 8); *p++= (a >>= 8); *p++= (a >>= 8); } #define addStr(xx) { char *q= xx; *p++= *q++; *p++= *q++; *p++= *q++; *p++= *q++; } void writeWAV() { char buf[44], *p= buf; if (byte_count + 36 != (int)(byte_count + 36)) { int tmp; byte_count= 0xFFFFFFF8-36; tmp= byte_count/out_bps/out_rate; warn("WARNING: Selected length is too long for the WAV format; truncating to %dh%02dm%02ds", tmp/3600, tmp/60%60, tmp%60); } addStr("RIFF"); addU4(byte_count + 36); addStr("WAVE"); addStr("fmt "); addU4(16); addU4(0x00020001); addU4(out_rate); addU4(out_rate * out_bps); addU4(0x0004 + 0x10000*(out_bps*4)); // 2,4 -> 8,16 - always assume stereo addStr("data"); addU4(byte_count); writeOut(buf, 44); if (!opt_Q) warn("Outputting %d-bit WAV data at %d Hz, file size %d bytes", out_mode ? 16 : 8, out_rate, byte_count + 44); } // // Read a line, discarding blank lines and comments. Rets: // Another line? Comments starting with '##' are displayed on // stderr. // int readLine() { char *p; lin= buf; while (1) { if (!fgets(lin, sizeof(buf), in)) { if (feof(in)) return 0; error("Read error on sequence file"); } in_lin++; while (isspace(*lin)) lin++; p= strchr(lin, '#'); if (p && p[1] == '#') fprintf(stderr, "%s", p); p= p ? p : strchr(lin, 0); while (p > lin && isspace(p[-1])) p--; if (p != lin) break; } *p= 0; lin_copy= buf_copy; strcpy(lin_copy, lin); return 1; } // // Get next word at '*lin', moving lin onwards, or return 0 // char * getWord() { char *rv, *end; while (isspace(*lin)) lin++; if (!*lin) return 0; rv= lin; while (*lin && !isspace(*lin)) lin++; end= lin; if (*lin) lin++; *end= 0; return rv; } // // Bad sequence file // void badSeq() { error("Bad sequence file content at line: %d\n %s", in_lin, lin_copy); } // Convenience for situations where buffer is being filled by // something other than readLine() void readNameDef2() { lin= buf; lin_copy= buf_copy; strcpy(lin_copy, lin); readNameDef(); } void readTimeLine2() { lin= buf; lin_copy= buf_copy; strcpy(lin_copy, lin); readTimeLine(); } // Convenience for creating sequences on the fly void formatNameDef(char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); readNameDef2(); } void formatTimeLine(int tim, char *fmt, ...) { va_list ap; char *p= buf + sprintf(buf, "%02d:%02d:%02d ", tim/3600, tim/60%60, tim%60); va_start(ap, fmt); vsnprintf(p, buf + sizeof(buf) - p, fmt, ap); readTimeLine2(); } // // Generate a list of Period structures, based on the tone-specs // passed in (ac,av) // void readSeqImm(int ac, char **av) { char *p= buf; in_lin= 0; p += sprintf(p, "immediate:"); while (ac-- > 0) p += sprintf(p, " %s", *av++); readNameDef2(); strcpy(buf, "00:00 immediate"); readTimeLine2(); correctPeriods(); } // // Read a list of sequence files, and generate a list of Period // structures // void readSeq(int ac, char **av) { // Setup a 'now' value to use for NOW in the sequence file now= calcNow(); while (ac-- > 0) { char *fnam= *av++; int start= 1; in= (0 == strcmp("-", fnam)) ? stdin : fopen(fnam, "r"); if (!in) error("Can't open sequence file: %s", fnam); in_lin= 0; while (readLine()) { char *p= lin; // Blank lines if (!*p) continue; // Look for options if (*p == '-') { if (!start) error("Options are only permitted at start of sequence file:\n %s", p); handleOptions(p); continue; } // Check to see if it fits the form of : start= 0; if (!isalpha(*p)) p= 0; else { while (isalnum(*p) || *p == '_' || *p == '-') p++; if (*p++ != ':' || !isspace(*p)) p= 0; } if (p) readNameDef(); else readTimeLine(); } if (in != stdin) fclose(in); } correctPeriods(); } // // Fill in all the correct information for the Periods, assuming // they have just been loaded using readTimeLine() // void correctPeriods() { // Get times all correct { Period *pp= per; do { if (pp->fi == -2) { pp->tim= pp->nxt->tim; pp->fi= -1; } pp= pp->nxt; } while (pp != per); } // Make sure that the transitional periods each have enough time { Period *pp= per; do { if (pp->fi == -1) { int per= t_per0(pp->tim, pp->nxt->tim); if (per < fade_int) { int adj= (fade_int - per) / 2, adj0, adj1; adj0= t_per0(pp->prv->tim, pp->tim); adj0= (adj < adj0) ? adj : adj0; adj1= t_per0(pp->nxt->tim, pp->nxt->nxt->tim); adj1= (adj < adj1) ? adj : adj1; pp->tim= (pp->tim - adj0 + H24) % H24; pp->nxt->tim= (pp->nxt->tim + adj1) % H24; } } pp= pp->nxt; } while (pp != per); } // Fill in all the voice arrays, and sort out details of // transitional periods { Period *pp= per; do { if (pp->fi < 0) { int fo, fi; int a; int midpt= 0; Period *qq= (Period*)Alloc(sizeof(*qq)); qq->prv= pp; qq->nxt= pp->nxt; qq->prv->nxt= qq->nxt->prv= qq; qq->tim= t_mid(pp->tim, qq->nxt->tim); memcpy(pp->v0, pp->prv->v1, sizeof(pp->v0)); memcpy(qq->v1, qq->nxt->v0, sizeof(qq->v1)); // Special handling for bells for (a= 0; av0[a].typ == 3 && pp->fi != -3) pp->v0[a].typ= 0; if (qq->v1[a].typ == 3 && pp->fi == -3) qq->v1[a].typ= 0; } fo= pp->prv->fo; fi= qq->nxt->fi; // Special handling for -> slides: // always slide, and stretch slide if possible if (pp->fi == -3) { fo= fi= 2; // Force slides for -> for (a= 0; av0[a]; Voice *vq= &qq->v1[a]; if (vp->typ == 0 && vq->typ != 0 && vq->typ != 3) { memcpy(vp, vq, sizeof(*vp)); vp->amp= 0; } else if (vp->typ != 0 && vq->typ == 0) { memcpy(vq, vp, sizeof(*vq)); vq->amp= 0; } } } memcpy(pp->v1, pp->v0, sizeof(pp->v1)); memcpy(qq->v0, qq->v1, sizeof(qq->v0)); for (a= 0; av1[a]; Voice *vq= &qq->v0[a]; if ((fo == 0 || fi == 0) || // Fade in/out to silence (vp->typ != vq->typ) || // Different types ((fo == 1 || fi == 1) && // Fade thru, but different pitches (vp->typ == 1 || vp->typ < 0) && (vp->carr != vq->carr || vp->res != vq->res)) ) { vp->amp= vq->amp= 0; // To silence midpt= 1; // Definitely need the mid-point if (vq->typ == 3) { // Special handling for bells vq->amp= qq->v1[a].amp; qq->nxt->v0[a].typ= qq->nxt->v1[a].typ= 0; } } else if (vp->typ == 3) { // Else smooth transition - for bells not so smooth qq->v0[a].typ= qq->v1[a].typ= 0; } else { // Else smooth transition vp->amp= vq->amp= (vp->amp + vq->amp) / 2; if (vp->typ == 1 || vp->typ == 4 || vp->typ < 0) { vp->carr= vq->carr= (vp->carr + vq->carr) / 2; vp->res= vq->res= (vp->res + vq->res) / 2; } } } // If we don't really need the mid-point, then get rid of it if (!midpt) { memcpy(pp->v1, qq->v1, sizeof(pp->v1)); qq->prv->nxt= qq->nxt; qq->nxt->prv= qq->prv; free(qq); } else pp= qq; } pp= pp->nxt; } while (pp != per); } // Clear out zero length sections, and duplicate sections { Period *pp; while (per != per->nxt) { pp= per; do { if (voicesEq(pp->v0, pp->v1) && voicesEq(pp->v0, pp->nxt->v0) && voicesEq(pp->v0, pp->nxt->v1)) pp->nxt->tim= pp->tim; if (pp->tim == pp->nxt->tim) { if (per == pp) per= per->prv; pp->prv->nxt= pp->nxt; pp->nxt->prv= pp->prv; free(pp); pp= 0; break; } pp= pp->nxt; } while (pp != per); if (pp) break; } } // Make sure that the total is 24 hours only (not more !) if (per->nxt != per) { int tot= 0; Period *pp= per; do { tot += t_per0(pp->tim, pp->nxt->tim); pp= pp->nxt; } while (pp != per); if (tot > H24) { warn("Total time is greater than 24 hours. Probably two times are\n" "out of order. Suspicious intervals are:\n"); pp= per; do { if (t_per0(pp->tim, pp->nxt->tim) >= H12) warn(" %02d:%02d:%02d -> %02d:%02d:%02d", pp->tim % 86400000 / 3600000, pp->tim % 3600000 / 60000, pp->tim % 60000 / 1000, pp->nxt->tim % 86400000 / 3600000, pp->nxt->tim % 3600000 / 60000, pp->nxt->tim % 60000 / 1000); pp= pp->nxt; } while (pp != per); error("\nCheck the sequence around these times and try again"); } } // Print the whole lot out if (opt_D) { Period *pp; if (per->nxt != per) while (per->prv->tim < per->tim) per= per->nxt; pp= per; do { dispCurrPer(stdout); per= per->nxt; } while (per != pp); printf("\n"); exit(0); // All done } } int voicesEq(Voice *v0, Voice *v1) { int a= N_CH; while (a-- > 0) { if (v0->typ != v1->typ) return 0; switch (v0->typ) { case 1: case 4: default: if (v0->amp != v1->amp || v0->carr != v1->carr || v0->res != v1->res) return 0; break; case 2: case 5: if (v0->amp != v1->amp) return 0; break; case 3: if (v0->amp != v1->amp || v0->carr != v1->carr) return 0; break; } v0++; v1++; } return 1; } // // Read a name definition // void readNameDef() { char *p, *q; NameDef *nd; int ch; if (!(p= getWord())) badSeq(); q= strchr(p, 0) - 1; if (*q != ':') badSeq(); *q= 0; for (q= p; *q; q++) if (!isalnum(*q) && *q != '-' && *q != '_') error("Bad name \"%s\" in definition, line %d:\n %s", p, in_lin, lin_copy); // Waveform definition ? if (0 == memcmp(p, "wave", 4) && isdigit(p[4]) && isdigit(p[5]) && !p[6]) { int ii= (p[4] - '0') * 10 + (p[5] - '0'); int siz= ST_SIZ * sizeof(int); int *arr= (int*)Alloc(siz); double *dp0= (double*)arr; double *dp1= (double*)(siz + (char*)arr); double *dp= dp0; double dmax= 0, dmin= 1; int np; if (waves[ii]) error("Waveform %02d already defined, line %d:\n %s", ii, in_lin, lin_copy); waves[ii]= arr; while ((p= getWord())) { double dd; char dmy; if (1 != sscanf(p, "%lf %c", &dd, &dmy)) error("Expecting floating-point numbers on this waveform " "definition line, line %d:\n %s", in_lin, lin_copy); if (dp >= dp1) error("Too many samples on line (maximum %d), line %d:\n %s", dp1-dp0, in_lin, lin_copy); *dp++= dd; if (dmax < dmin) dmin= dmax= dd; else { if (dd > dmax) dmax= dd; if (dd < dmin) dmin= dd; } } dp1= dp; np= dp1 - dp0; if (np < 2) error("Expecting at least two samples in the waveform, line %d:\n %s", in_lin, lin_copy); // Adjust to range 0-1 for (dp= dp0; dp < dp1; dp++) *dp= (*dp - dmin) / (dmax - dmin); sinc_interpolate(dp0, np, arr); if (DEBUG_DUMP_WAVES) { int a; printf("Dumping wave%02d:\n", ii); for (a= 0; aname= StrDup(p); // Block definition ? if (*lin == '{') { BlockDef *bd, **prvp; if (!(p= getWord()) || 0 != strcmp(p, "{") || 0 != (p= getWord())) badSeq(); prvp= &nd->blk; while (readLine()) { if (*lin == '}') { if (!(p= getWord()) || 0 != strcmp(p, "}") || 0 != (p= getWord())) badSeq(); if (!nd->blk) error("Empty blocks not permitted, line %d:\n %s", in_lin, lin_copy); nd->nxt= nlist; nlist= nd; return; } if (*lin != '+') error("All lines in the block must have relative time, line %d:\n %s", in_lin, lin_copy); bd= (BlockDef*) Alloc(sizeof(*bd)); *prvp= bd; prvp= &bd->nxt; bd->lin= StrDup(lin); } // Hit EOF before } error("End-of-file within block definition (missing '}')"); } // Normal line-definition for (ch= 0; ch < N_CH && (p= getWord()); ch++) { char dmy; double amp, carr, res; int wave; // Interpret word into Voice nd->vv[ch] if (0 == strcmp(p, "-")) continue; if (1 == sscanf(p, "pink/%lf %c", &, &dmy)) { nd->vv[ch].typ= 2; nd->vv[ch].amp= AMP_DA(amp); continue; } if (2 == sscanf(p, "bell%lf/%lf %c", &carr, &, &dmy)) { nd->vv[ch].typ= 3; nd->vv[ch].carr= carr; nd->vv[ch].amp= AMP_DA(amp); continue; } if (1 == sscanf(p, "mix/%lf %c", &, &dmy)) { nd->vv[ch].typ= 5; nd->vv[ch].amp= AMP_DA(amp); mix_flag= 1; continue; } if (4 == sscanf(p, "wave%d:%lf%lf/%lf %c", &wave, &carr, &res, &, &dmy)) { if (wave < 0 || wave >= 100) error("Only wave00 to wave99 is permitted at line: %d\n %s", in_lin, lin_copy); if (!waves[wave]) error("Waveform %02d has not been defined, line: %d\n %s", wave, in_lin, lin_copy); nd->vv[ch].typ= -1-wave; nd->vv[ch].carr= carr; nd->vv[ch].res= res; nd->vv[ch].amp= AMP_DA(amp); continue; } if (3 == sscanf(p, "%lf%lf/%lf %c", &carr, &res, &, &dmy)) { nd->vv[ch].typ= 1; nd->vv[ch].carr= carr; nd->vv[ch].res= res; nd->vv[ch].amp= AMP_DA(amp); continue; } if (2 == sscanf(p, "%lf/%lf %c", &carr, &, &dmy)) { nd->vv[ch].typ= 1; nd->vv[ch].carr= carr; nd->vv[ch].res= 0; nd->vv[ch].amp= AMP_DA(amp); continue; } if (3 == sscanf(p, "spin:%lf%lf/%lf %c", &carr, &res, &, &dmy)) { nd->vv[ch].typ= 4; nd->vv[ch].carr= carr; nd->vv[ch].res= res; nd->vv[ch].amp= AMP_DA(amp); continue; } badSeq(); } nd->nxt= nlist; nlist= nd; } // // Bad time // void badTime(char *tim) { error("Badly constructed time \"%s\", line %d:\n %s", tim, in_lin, lin_copy); } // // Read a time-line of either type // void readTimeLine() { char *p, *tim_p; int nn; int fo, fi; Period *pp; NameDef *nd; static int last_abs_time= -1; int tim, rtim; if (!(p= getWord())) badSeq(); tim_p= p; // Read the time represented tim= -1; if (0 == memcmp(p, "NOW", 3)) { last_abs_time= tim= now; p += 3; } while (*p) { if (*p == '+') { if (tim < 0) { if (last_abs_time < 0) error("Relative time without previous absolute time, line %d:\n %s", in_lin, lin_copy); tim= last_abs_time; } p++; } else if (tim != -1) badTime(tim_p); if (0 == (nn= readTime(p, &rtim))) badTime(tim_p); p += nn; if (tim == -1) last_abs_time= tim= rtim; else tim= (tim + rtim) % H24; } if (fast_tim0 < 0) fast_tim0= tim; // First time fast_tim1= tim; // Last time if (!(p= getWord())) badSeq(); fi= fo= 1; if (!isalpha(*p)) { switch (p[0]) { case '<': fi= 0; break; case '-': fi= 1; break; case '=': fi= 2; break; default: badSeq(); } switch (p[1]) { case '>': fo= 0; break; case '-': fo= 1; break; case '=': fo= 2; break; default: badSeq(); } if (p[2]) badSeq(); if (!(p= getWord())) badSeq(); } for (nd= nlist; nd && 0 != strcmp(p, nd->name); nd= nd->nxt) ; if (!nd) error("Name \"%s\" not defined, line %d:\n %s", p, in_lin, lin_copy); // Check for block name-def if (nd->blk) { char *prep= StrDup(tim_p); // Put this at the start of each line BlockDef *bd= nd->blk; while (bd) { lin= buf; lin_copy= buf_copy; sprintf(lin, "%s%s", prep, bd->lin); strcpy(lin_copy, lin); readTimeLine(); // This may recurse, and that's why we're StrDuping the string bd= bd->nxt; } free(prep); return; } // Normal name-def pp= (Period*)Alloc(sizeof(*pp)); pp->tim= tim; pp->fi= fi; pp->fo= fo; memcpy(pp->v0, nd->vv, N_CH * sizeof(Voice)); memcpy(pp->v1, nd->vv, N_CH * sizeof(Voice)); if (!per) per= pp->nxt= pp->prv= pp; else { pp->nxt= per; pp->prv= per->prv; pp->prv->nxt= pp->nxt->prv= pp; } // Automatically add a transitional period pp= (Period*)Alloc(sizeof(*pp)); pp->fi= -2; // Unspecified transition pp->nxt= per; pp->prv= per->prv; pp->prv->nxt= pp->nxt->prv= pp; if (0 != (p= getWord())) { if (0 != strcmp(p, "->")) badSeq(); pp->fi= -3; // Special '->' transition pp->tim= tim; } } int readTime(char *p, int *timp) { // Rets chars consumed, or 0 error int nn, hh, mm, ss; if (3 > sscanf(p, "%2d:%2d:%2d%n", &hh, &mm, &ss, &nn)) { ss= 0; if (2 > sscanf(p, "%2d:%2d%n", &hh, &mm, &nn)) return 0; } if (hh < 0 || hh >= 24 || mm < 0 || mm >= 60 || ss < 0 || ss >= 60) return 0; *timp= ((hh * 60 + mm) * 60 + ss) * 1000; return nn; } // // Takes a set of points and repeats them twice, inverting the // second set, and then interpolates them using a periodic sinc // function (see http://www-ccrma.stanford.edu/~jos/resample/) // and writes them to arr[] in the same format as the sin_table[]. // void sinc_interpolate(double *dp, int np, int *arr) { double *sinc; // Temporary sinc-table double *out; // Temporary output table int a, b; double dmax, dmin; double adj, off; // Generate a modified periodic sin(x)/x function to be used for // each of the points. Really this should be sin(x)/x modified // by the sum of an endless series. However, this doesn't // converge very quickly, so to save time I'm approximating this // series by 1-4*t*t where t ranges from 0 to 0.5 over the first // half of the periodic cycle. If you do the maths, this is at // most 5% out. This will have to do - it's smooth, and I don't // know enough maths to make this series converge quicker. sinc= (double *)Alloc(ST_SIZ * sizeof(double)); sinc[0]= 1.0; for (a= ST_SIZ/2; a>0; a--) { double tt= a * 1.0 / ST_SIZ; double t2= tt*tt; double adj= 1 - 4 * t2; double xx= 2 * np * 3.14159265358979323846 * tt; double vv= adj * sin(xx) / xx; sinc[a]= vv; sinc[ST_SIZ-a]= vv; } // Build waveform into buffer out= (double *)Alloc(ST_SIZ * sizeof(double)); for (b= 0; b dmax) dmax= out[a]; if (out[a] < dmin) dmin= out[a]; } // Write out to output buffer off= -0.5 * (dmax + dmin); adj= ST_AMP / ((dmax - dmin) / 2); for (a= 0; a] []" NL " is [....][s|k][+][^][/]" NL "The optional is t,,, all times" NL " in minutes (the default is equivalent to 't30,30,3')." NL "The optional let you mix other stuff with the drop" NL " sequence like pink noise or a mix soundtrack, e.g 'pink/20' or 'mix/60'"); } // // Generate a p-drop sequence // // Credits: Jonathan Bisson created the first version of this C // code. This is a rewrite to make it fit with the rest of the // code better. // void create_drop(int ac, char **av) { char *fmt; char *p, *q; int a; int slide, n_step, islong, wakeup; double carr, amp, c0, c2; double beat_target; double beat[40]; static double beat_targets[]= { 4.4, 3.7, 3.1, 2.5, 2.0, 1.5, 1.2, 0.9, 0.7, 0.5, 0.4, 0.3 }; char extra[256]; int len, len0= 1800, len1= 1800, len2= 180; int steplen, end; #define BAD bad_drop() // Pick up optional time-spec if (ac < 1) BAD; if (av[0][0] == 't') { double v0, v1, v2; char dmy; if (3 != sscanf(av[0]+1, "%lf,%lf,%lf %c", &v0, &v1, &v2, &dmy)) BAD; len0= 60 * (int)v0; // Whole minutes only len1= 60 * (int)v1; len2= 60 * (int)v2; ac--; av++; } // Handle argument list if (ac < 1) BAD; fmt= *av++; ac--; p= extra; *p= 0; while (ac > 0) { if (p + strlen(av[0]) + 2 > extra + sizeof(extra)) error("Too many extra tone-specs after -p drop"); p += sprintf(p, " %s", av[0]); ac--; av++; } // Scan the format carr= 200 - 2 * strtod(fmt, &p); if (p == fmt || carr < 0) BAD; a= tolower(*p) - 'a'; p++; if (a < 0 || a >= sizeof(beat_targets) / sizeof(beat_targets[0])) BAD; beat_target= beat_targets[a]; slide= 0; steplen= 180; if (*p == 's') { p++; slide= 1; steplen= 60; } else if (*p == 'k') { p++; steplen= 60; } n_step= 1 + (len0-1) / steplen; // Round up len0= n_step * steplen; if (!slide) len1= (1 + (len1-1) / steplen) * steplen; islong= 0; if (*p == '+') { islong= 1; p++; } wakeup= 0; if (*p == '^') { wakeup= 1; p++; } amp= 1.0; if (*p == '/') { p++; q= p; amp= strtod(p, &p); if (p == q) BAD; } while (isspace(*p)) p++; if (*p) error("Trailing rubbish after -p drop spec: \"%s\"", p); #undef BAD // Sort out carriers len= islong ? len0 + len1 : len0; c0= carr + 5.0; c2= carr; // Calculate beats for (a= 0; a"); // 23:59:55 if (slide) { // Slide version for (a= 0; a", a); } if (islong) { formatNameDef("tsend: %g+%g/%g %s", c2, beat[n_step-1], amp, extra); formatTimeLine(len, "== tsend ->"); } end= len; } else { // Step version int lim= len / steplen; int stepslide= steplen < 90 ? 5 : 10; // Seconds slide between steps for (a= 0; a=n_step) ? n_step-1 : a], amp, extra); formatTimeLine(tim0, "== ts%02d ->", a); formatTimeLine(tim1-stepslide, "== ts%02d ->", a); } end= len-stepslide; } // Wake-up and ending if (wakeup) { formatNameDef("tswake: %g+%g/%g %s", c0, beat[0], amp, extra); formatTimeLine(end+len2, "== tswake ->"); end += len2; } formatTimeLine(end+10, "== off"); correctPeriods(); } // // Generate a -p slide sequence // // The idea of this is to hold the beat frequency constant, but // to slide down through the carrier frequencies from about 200Hz. // // -p slide [t] +/ [extra tone-sets] void bad_slide() { error("Bad arguments: expecting -p slide [] []" NL " is just like a tone-spec: /" NL "The optional is t, giving length of session in" NL " minutes (the default is equivalent to 't30')." NL "The optional let you mix other stuff with the drop" NL " sequence like pink noise or a mix soundtrack, e.g 'pink/20' or 'mix/60'"); } void create_slide(int ac, char **av) { int len= 1800; char *p, dmy; double val, c0, c1, beat, amp; char extra[256]; #define BAD bad_slide() // Handle arguments if (ac < 1) BAD; if (av[0][0] == 't') { val= strtod(av[0]+1, &p); if (p == av[0] + 1 || *p) BAD; len= 60.0 * val; ac--; av++; } if (ac < 1) BAD; if (3 != sscanf(av[0], "%lf%lf/%lf %c", &c0, &beat, &, &dmy)) BAD; c1= beat/2; ac--; av++; #undef BAD // Gather 'extra' p= extra; *p= 0; while (ac > 0) { if (p + strlen(av[0]) + 2 > extra + sizeof(extra)) error("Too many extra tone-specs after -p slide"); p += sprintf(p, " %s", av[0]); ac--; av++; } // Summary warn("SLIDE summary:"); warn(" Sliding carrier from %gHz to %gHz over %g minutes", c0, c1, len/60.0); warn(" Holding beat constant at %gHz", beat); // Generate sequence handleOptions("-SE"); in_lin= 0; formatNameDef("off: -"); formatTimeLine(86395, "== off ->"); // 23:59:55 formatNameDef("ts0: %g%+g/%g %s", c0, beat, amp, extra); formatTimeLine(0, "== ts0 ->"); formatNameDef("ts1: %g%+g/%g %s", c1, beat, amp, extra); formatTimeLine(len, "== ts1 ->"); formatTimeLine(len+10, "== off"); correctPeriods(); } // END //