This is play.c in view mode; [Download] [Up]
// Play, a sound player by J. Laroche. January 1992 // Version 2.0 // Added to 1.0: additional useful features for converting from binary // files (no header), selecting right or left channel, swaping octects for // compatibility with PCs and DEC machines... // Also, "fixed" a problem with large factor conversions (when the cut-off // frequency gets negative!) // Also added to 1.0: DMA to DSP. The transfer is now faster and doesn't freeze // the NeXT while doing the conversion (like 1.0 and 2.0 used to). // Digital out is now possible: play can send the samples to the DSP port // for the AD64X converter to transmit via SP-DIFF format. // Also added: // conversion from stereo to mix, choice of filter length according to the // sampling rate conversion (when playing), general optimizing and rewriting. // The DSP file is now loaded from the Mach_O segment: play will work // anywhere. // When reading from a binary file, the file is mapped into memory, so the // access is faster, and playing starts almost as soon as you hit return. // #import <sound/sound.h> #import <sound/sounddriver.h> #import <streams/streams.h> #include <sys/file.h> #include <defaults.h> #import <mach.h> #import <math.h> #import <stdio.h> #include <strings.h> #include <signal.h> #define Error(A,B) if((A)) {fprintf(stderr,"%s\n",B); exit(0);} #define MAX(A,B) ((A) < (B) ? (B) : (A)) #define MIN(A,B) ((A) > (B) ? (B) : (A)) #define AES_HIGH 2 #define AES_LOW 1 #define LEFT 0 #define RIGHT 1 #define STEREO 2 #define SUM 3 #define MONO 4 //#define DMASIZE 1024 //#define MEMMAX 6000 #define DMASIZE 2048 #define MEMMAX 4000 #define WRITE_TAG 1 #define READ_TAG 2 // MEMMAX depends on how much memory you have on your DSP. // These are default values. If you have more memory than 8Kwords, // you should add the difference (in words) to MEMMAX, which would enable // longer filters, and therefore higher ratios. (MEMMAX is the amount of // DSP memory dedicated to the filter coefficients.) offset is the offset // value for cutoff frequency. It also depends on the length of the filter. static int done; static short *read_data; static int read_count; static void recorded_data(void *arg, int tag, void *p, int nbytes) { if(tag != READ_TAG) return; read_data = (short *)p; read_count = nbytes; done = 1; } static void read_completed(void *arg, int tag) { done = 2; } static void play_underrun(void *arg, int tag) { printf("The driver is running behind... aborting \n"); done = 3; } int max_order; float resize = 0.9; static port_t dev_port, owner_port,cmd_port; static port_t reply_port, read_port, write_port; void main (int argc, char *argv[]) { int i, j, protocol; kern_return_t k_err; SNDSoundStruct *sound; SNDSoundStruct faa; char *file[100]; char error_string[200]; SNDSoundStruct *dspStruct; snddriver_handlers_t handlers = { 0, 0, 0, read_completed, 0, 0, 0, play_underrun, recorded_data}; msg_header_t *reply_msg; NXStream *mapfile; int low_water = 48*1024; int high_water = 512*1024; short *location; int length; int stereo = 0; int verbose = 0; int BINARYIN = 0; int SWAP = 0; int CHANNEL = MONO; int AES = 0; int INIT = 0; int ster = 0; int U = 0, D = 0, K = 0, Filter_length = 0; int para[4],rat[2]; int *filtre; float cut, thresh, offset; int S_rateIn, S_rateOut, S=0; short *foo; int nb_iteration,inc,start; void cal_filtre(); void fract(); void usage(); void usage2(); void stopall(); int testfile(); /////////////// Initialize parameters, and scan input line ////////////////// signal(SIGINT, stopall); thresh = 0.003; offset=0.005; max_order = (int) (MEMMAX / 150); for(i=1,j=0;i<argc;i++) switch(argv[i][0]) { case '-' : switch(argv[i][1]) { case 'v' : verbose = 1; break ; case 'h' : usage2() ; case 'U' : U = atoi(argv[i]+2); break ; case 'D' : D = atoi(argv[i]+2); break ; case 'K' : K = atoi(argv[i]+2); break ; case 'S' : S = atoi(argv[i]+2); break ; case 'w' : resize = atof(argv[i]+2); break ; case 't' : thresh = atof(argv[i]+2); break ; case 'o' : offset = atof(argv[i]+2); break ; case 'd' : AES = AES_HIGH; if(argv[i][2] == 'l') AES = AES_LOW; /* SSI setup: Low*/ if(argv[i][2] == 'h') AES = AES_HIGH; break ; /* High */ case 'i' : INIT = 1; break; case 's' : SWAP = 1; break; case 'c' : switch(argv[i][2]) { case 'l' : CHANNEL = LEFT ; break ; case 'r' : CHANNEL = RIGHT ; break ; case 's' : CHANNEL = STEREO ; break ; case '+' : CHANNEL = SUM ; break ; default : usage(); } break ; default : usage(); } break ; default : file[j++] = argv[i]; if (j > 1) usage() ; break ; } if(j != 1) usage(); /////////////// Initialize Hardware, and read sound file ////////////////// k_err = SNDAcquire(SND_ACCESS_OUT|SND_ACCESS_DSP,0,0,0, NULL_NEGOTIATION_FUN,0,&dev_port,&owner_port); Error(k_err,"DSP Busy, or access to device denied\n"); k_err = SNDReset(SND_ACCESS_OUT|SND_ACCESS_DSP,dev_port,owner_port); Error(k_err,"Problem during reset\n"); k_err = snddriver_get_dsp_cmd_port(dev_port,owner_port,&cmd_port); // Here's an important setting. It appears that this size make the // whole thing work. others don't. I don't know why, but it has // something to do with the ramp created by the snd driver. k_err = snddriver_set_sndout_bufsize(dev_port,owner_port,2048); Error(k_err,"Buf Size "); k_err = SNDReadSoundfile(file[0], &sound); if(k_err) { char *defautSR,*getenv(); int defSR; mapfile = NXMapFile((const char*) file[0], NX_READONLY); sprintf(error_string,"Couldn't locate/read file \"%s\".", file[0]); Error((mapfile == 0), error_string); NXSeek(mapfile, 0, NX_FROMEND); length = NXTell(mapfile)/sizeof(short); NXSeek(mapfile, 0, NX_FROMSTART); defautSR = getenv("DEFAULT_SR"); if(defautSR != 0) defSR=atoi(defautSR); else defSR = 48000; S = ((S==0) ? defSR : S); k_err = SNDAlloc(&sound,0,SND_FORMAT_LINEAR_16,S,1,4); Error(k_err,"SND Allocation"); SNDGetDataPointer(sound,(char**)&location,&i,&i); sound->dataSize = length*sizeof(short); BINARYIN = 1; if(CHANNEL == STEREO) sound->channelCount = 2; else CHANNEL = MONO; } else { // Problem with SNDReadSoundfile, when sampling rate = 8000, // the returned soundfile has a 8012.821 sampling rate!!!!! // this straighten this up. i = open(file[0],O_RDONLY); read(i,&faa,sizeof(SNDSoundStruct)); k_err = SNDGetDataPointer(sound,(char**)&location,&length,&i); sound->samplingRate = faa.samplingRate; close(i); if(sound->channelCount == 1) CHANNEL = MONO; else if(CHANNEL == MONO) CHANNEL = STEREO; } Error(sound->dataFormat != SND_FORMAT_LINEAR_16, "Bad format: I need 16 bits linear"); if(S) sound->samplingRate = S; /////////////// Calculate Sampling rate conversion param. ////////////////// S_rateIn = sound->samplingRate; if(U == 0 && D == 0) S = ((AES == AES_LOW || AES == 0) ? 44100 : 48000); if(S!=0 && D==0 && U==0) { fract((float)S_rateIn/(float)S,rat,thresh); D = rat[0]; U = rat[1]; } if(U==0) U = 1; if(D==0) D = 1; if(U > 100 || D > 100) Error(1,"Unable to convert: values of U or D too large.\n"); if(U > max_order || D > max_order) printf("Warning! U and D should not be larger than %d.\n", max_order); S_rateOut = (int) floor((float) S_rateIn * (float)U / (float)D); ///////////// Examine sound file, set stereo conversion param. /////////////// if(CHANNEL != STEREO && sound->channelCount == 2) { inc = 2; start = CHANNEL; if(CHANNEL == SUM) start = 0; sound->channelCount = 1; sound->dataSize = sound->dataSize/2; length /= 2; } else {inc = 1 ; start = 0; } stereo = (sound->channelCount == 2); if(verbose) { printf("Playing\t\t%s\n",file[0]); if(AES) printf("Output:\t\tA/D64X AES/EBU, %skHz\n", ((AES == AES_LOW) ? "44.1" : "48")); else printf("Output:\t\t NeXT D/A converters\n"); printf("Sampling rate:\t%d\n", S_rateIn); switch(CHANNEL) { case STEREO : printf("Mode: \t\tStereo\n"); break ; case LEFT : printf("Mode: \t\tMono left\n"); break ; case RIGHT : printf("Mode: \t\tMono right\n"); break ; case SUM : printf("Mode:\t\tMono left + right\n"); break ; case MONO : printf("Mode:\t\tMono\n"); break ; } printf("Length: \t%d samples\n",length); if(SWAP) printf("Swapping Octets\n"); } /////////////// Calculate FIR Filter characteristics ////////////////// if(U == 1 && D == 1) K = 3; if(K == 0) // if(AES == 0) K = MIN((int)(40*(2-stereo)*U/D), MIN(MEMMAX/U,80)); // else K = MIN((int)(20*(2-stereo)*U/D), MIN(MEMMAX/U,40)); K = MIN((int)(55*(2-stereo)),MEMMAX/U); K = MIN(K,250); if(verbose) printf("Filter Length: %d, Up-factor %d, Down-factor %d\n",K,U,D); Filter_length = (((K*U) % 2)? K*U : K*U+1); para[0] = U; para[1] = D; para[2] = K-1; para[3] = Filter_length; /////////////// Initialize sound and dsp driver ////////////////// protocol = SNDDRIVER_DSP_PROTO_RAW; k_err = snddriver_stream_setup(dev_port, owner_port, SNDDRIVER_DMA_STREAM_TO_DSP, DMASIZE, 2, low_water, high_water, &protocol, &write_port); Error(k_err,"Stream "); k_err = snddriver_stream_setup(dev_port, owner_port, SNDDRIVER_STREAM_DSP_TO_SNDOUT_44, DMASIZE, 2, low_water, high_water, &protocol, &read_port); Error(k_err,"Stream "); k_err = snddriver_dsp_protocol(dev_port, owner_port, protocol); k_err = port_allocate(task_self(),&reply_port); /////////////// Get DSP program and boot DSP. ////////////////// dspStruct = (SNDSoundStruct *)getsectdata("__SND", "play.snd",&i); k_err = SNDBootDSP(dev_port, owner_port, dspStruct); Error(k_err,"Can't boot DSP!"); /////////////// Send stereo param and filter coeff to DSP. ////////////////// ster = stereo + 2 + 4*((AES != 0)) + 8*((AES == AES_HIGH)) + 16*(INIT); k_err = snddriver_dsp_write(cmd_port,&ster,1,sizeof(int), SNDDRIVER_LOW_PRIORITY); filtre = (int*) calloc(Filter_length,sizeof(int)); cut = 3.14159265 * (1/(float) MAX(U,D) - offset); if(cut <= 0) { printf("Problem with the filter's cut-off frequency because U or D \ are too large, \nyou might get aliasing\n"); cut = 3.14159265 * (1/(float) MAX(U,D)); } if(U == 1 && D == 1) { filtre[0] = filtre[2] = 0; filtre[1] = 8388607; } else cal_filtre(filtre, Filter_length, cut, U); k_err = snddriver_dsp_write(cmd_port,para,4,sizeof(int), SNDDRIVER_LOW_PRIORITY); k_err = snddriver_dsp_write(cmd_port,filtre, Filter_length,sizeof(int), SNDDRIVER_LOW_PRIORITY); /////////////// Allocate virtual memory for DMA_IN ////////////////// vm_allocate(task_self(),(vm_address_t *)(&foo),8*2*vm_page_size,TRUE); Error(k_err,"VM Allocation "); nb_iteration = length/vm_page_size/8; if(INIT) usleep(1500000); ////////////// Send DMA buffers, while doing stereo conversion //////////////// for(i=0,location = location + start ;i<nb_iteration;i++) { int max = 8*vm_page_size; if(BINARYIN) { k_err = NXRead(mapfile, (void*)foo, max*sizeof(short)); } else { if(CHANNEL == SUM) for(j=0;j<max;j++,location++) foo[j] = *(location) + *(++location); else for(j=0;j<max;j++,location += inc) foo[j] = *(location); } if(SWAP) swab(foo, foo, 8*vm_page_size*sizeof(short)); if(verbose) fprintf(stderr,"Buffer no %d\r",i); k_err = snddriver_stream_start_writing(write_port,(void *)foo, 8*vm_page_size,WRITE_TAG,0,0,0,0,0,0,0,0, reply_port); Error(k_err,"start writing "); if((i == 0 && AES == 0) || (i == 3 && AES != 0)) snddriver_dsp_host_cmd(cmd_port,20,SNDDRIVER_LOW_PRIORITY); } i = length - nb_iteration * 8 *vm_page_size; if(BINARYIN) { k_err = NXRead(mapfile, (void*)foo, i*sizeof(short)); } else { if(CHANNEL == SUM) for(j=0;j<i;j++,location++) foo[j] = *(location) + *(++location); else for(j=0;j<i;j++,location += inc) foo[j] = *(location); } if(SWAP) swab(foo, foo, 8*vm_page_size*sizeof(short)); for(j = i;j<8*vm_page_size;j++) foo[j] = 0; k_err = snddriver_stream_start_writing(write_port,(void *)foo, MIN((int)(i/DMASIZE + 2) * DMASIZE, 8*vm_page_size) ,WRITE_TAG,0,0,0,1,0,0,0,0, reply_port); snddriver_dsp_host_cmd(cmd_port,20,SNDDRIVER_LOW_PRIORITY); for(j = 0;j<8*vm_page_size;j++) foo[j] = 0; k_err = snddriver_stream_start_writing(write_port, (void *)foo,8*vm_page_size,WRITE_TAG,0,0,0,0,0,0,0,0, reply_port); Error(k_err,"start writing "); /////////////// Wait until calculations completed //////////////// reply_msg = (msg_header_t *)malloc(MSG_SIZE_MAX); done = 0; while (done != 1) { reply_msg->msg_size = MSG_SIZE_MAX; reply_msg->msg_local_port = reply_port; k_err = msg_receive(reply_msg, MSG_OPTION_NONE, 0); k_err = snddriver_reply_handler(reply_msg,&handlers); if(done == 2) { done = 1; usleep(250000); } } SNDRelease(SND_ACCESS_OUT|SND_ACCESS_DSP,dev_port,owner_port); vm_deallocate(task_self(),(pointer_t)foo, 8*2*vm_page_size); port_deallocate(task_self(), reply_port); free(filtre); exit(0); } // Calculates FIR low-pass filter by windowing a sinc with a hamming window, // and normalizing. void cal_filtre(filtre, order, freq_cut, P) int *filtre; int order; float freq_cut; int P; { int i; float scaler; float aux; for(i=1,scaler = 0;i<order/2;i++) { aux = (sin(freq_cut * i) / freq_cut / i * (0.54 + 0.46 * cos(6.28318530*i/(order-2)))); filtre[order/2+i] = filtre[order/2-i] = 8388607 * aux ; scaler += 2 * aux; } scaler = P * resize / (1 + scaler); filtre[order/2] = 8388607 * scaler; for(i=1;i<order/2;i++) filtre[order/2+i] = filtre[order/2-i] = filtre[order/2-i]*scaler; } /* fract(x,rat) returns in rat a ratio that best approximates the real x with an relative error less than thresh, making sure U and D are less than 100. Thresh is equal to 0.03, the auditory threshold for pitch sensation. */ void fract(x,rat,thresh) float x; int *rat; float thresh; { int i=0,j; float error = 1; float reste; int *A, *p, *q; int prev_p = 1, prev_q = 1; A = (int*) calloc(20,sizeof(int)); p = (int*) calloc(20,sizeof(int)); q = (int*) calloc(20,sizeof(int)); reste = x; while(error/x > thresh) { A[i] = floor(reste); reste = 1 / (reste - A[i]); for(j=i, p[i]=1, q[i]=A[i]; j>0; j--) { p[j-1] = q[j]; q[j-1] = A[j-1] * q[j] + p[j]; } error = fabs((float)q[0]/(float)p[0] - x); if(p[0] < max_order && q[0] < max_order) { prev_p = p[0]; prev_q = q[0]; } else { printf("Warning, difficult conversion: ideal factors U = %d, D = %d,\n", p[0],q[0]); p[0] = prev_p; q[0] = prev_q; error = fabs((float)q[0]/(float)p[0] - x); printf("Actual factors: U = %d, D = %d, \ Resulting Sampling rate error: %.2f %%\n",p[0],q[0],100*error/x); break; } i++; } rat[0] = q[0]; rat[1] = p[0]; free(A); free(p); free(q); } void usage() { printf("Usage: play \t-[Svscdi] sound \n\t\t\ -v:verbose: indicates conversion parameters, etc...\n\t\t\ -S:Input sampling rate\n\t\t\ -c:channels: s:stereo, l:left only, r:right only, +:sum \n\t\t\ -dh:Sends samples to digital output (A/D64X) at 48kHz \n\t\t\ -dl:Sends samples to digital output (A/D64X) at 44.1kHz \n\t\t\ -i:Initialize A/D64X for digital output at desired Frequ. \n\t\t\ -s: swaps octets of sound (for PCs and DECs)\n\t\t\ -h:get additional info on all the possible options\n\t\ Examples: \n\tTo play a binary file at 32kHz: \n\t\tplay -S32000 file\n\t\ To send a 32kHz binary file to A/D64X digital out at 48kHz:\n\t\tplay -S32000 file -dh\n\t\ To send a 32kHz binary file to A/D64X with initialization:\n\t\tplay -S32000 file -dh -i\n\n"); exit(0); } void usage2() { printf("Usage: play \t-[UDSKtovscrdi] sound\n\t\t\ -S:Output sampling rate, Input sampling rate if playing\n\t\t\ -U:Up-sampling factor\n\t\t\ -D:Down-sampling factor\n\t\t\ -K:filter-Order\n\t\t\ -dh:Sends samples to digital output (A/D64X) at 48kHz \n\t\t\ -dl:Sends samples to digital output (A/D64X) at 44.1kHz \n\t\t\ -i:Initialize AD64X for digital output at desired Frequ. \n\t\t\ -w:Scaling factor (default 0.9) \n\t\t\ -t:Sampling rate accuracy (default 0.003) \n\t\t\ -o:Cut-off frequency offset (default 0.005) \n\t\t\ -v:verbose: indicates conversion parameters\n\t\t\ -s: swaps octets on input (for PCs and DECs)\n\t\t\ -c:channels: s:stereo, l:left only, r:right only, +:sum \n\t\t\ -r:forces the header of the output sound to the sampling \n\t\t\ rate you specified instead of the approximated value\n"); exit(0); } void stopall() { // Attempts to handle ^C gracefully! release everything before exiting. snddriver_stream_control(write_port,0, SNDDRIVER_ABORT_STREAM); SNDRelease(SND_ACCESS_OUT|SND_ACCESS_DSP,dev_port,owner_port); port_deallocate(task_self(), reply_port); exit(0); }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.