This is Play.m in view mode; [Download] [Up]
// Play, a sound player Object by J. Laroche. June 1992 // Version 2.0 #import "Play.h" #define Error(A,B) if((A)) { if (delegate && [delegate \ respondsTo:@selector(error:)]) \ { strcpy(errorMessage,B); \ [delegate error:errorMessage];} \ fprintf(stderr,"%s\n", B); \ SNDRelease(SND_ACCESS_OUT|SND_ACCESS_DSP,dev_port,owner_port); \ return -1;} //#define Error(A,B) if((A)) { printf("%s \n",B); return self;} #define MAX(A,B) ((A) < (B) ? (B) : (A)) #define MIN(A,B) ((A) > (B) ? (B) : (A)) #define DMASIZE 2048 #define MEMMAX 4000 #define WRITE_TAG 0 #define READ_BUF_SIZE (vm_page_size / BYTES_PER_16BIT) static void HandleDSPMessage(msg_header_t *msg, void *userData) { [(Play *)userData playCompleted:0]; } static void PlayMonitor(DPSTimedEntry tag, double now, char *userData) { [(Play *)userData playMonitor]; } @implementation Play:Object //------------ Create a new Play object. + new { self = [super new]; stereo = 0; verbose = 0; CHANNEL = MONO; AES = 0; INIT = 0; ster = 0; U = 0; D = 0; K = 0; Filter_length = 0; KDef = 0; S = 0; low_water = 48*1024; high_water = 512*1024; thresh = 0.003; offset=0.005; max_order = (int) (MEMMAX / 150); status = IDLE; S_rateIn = 16000; errorMessage = calloc(1024,sizeof(char)); WAIT = NO; gain = 0.9; return self; } - setDelegate:anObject { delegate = anObject; return self; } - setSamplingRate:(int)aSamplingRate { S_rateIn = aSamplingRate; return self; } ////////// - setOutput:(int)anOutput. Sets output to desired output: //// AES_HIGH : ssi port, to A/D64X converters, 48kHz, digital. //// AES_LOW : ssi port, to A/D64X converters, 44.1kHz, digital. //// 0 : NeXT Dacs. - setOutput:(int)anOutput { AES = anOutput; return self; } ////////////////////////// - setMonoMode:(int)aMode. Sets sound playing mode. ///// MONO or STEREO. - setMonoMode:(int)aMode { CHANNEL = aMode; stereo = (CHANNEL == STEREO); return self; } ////////////// - playSound:(SNDSoundStruct*)aSound:(int)begin:(int)length ///// Starts playing length samples of sound, starting from begin. ///// When finished, sends the delegate a didPlay message. - (int) playSound:(SNDSoundStruct*)aSound:(int)begin:(int)length { int i,j; static int AESStat = 0; short *beginSound, *endSound; int beginPage, endPage, pageNumber; Sound *DSPprogram; if(status != IDLE) return -1; if(aSound == 0) Error(1,"The sound file is null!"); length = MIN(length*(1+stereo),aSound->dataSize/sizeof(short) - begin); if(aSound->dataSize/sizeof(short) < begin) Error(1,"Sound not long enough for selected duration"); first_samp = begin; size_samp = length; if(!AES) INIT = 0; else if(AES != AESStat) {INIT = 1; AESStat = AES;} else INIT = 0; k_err = SNDAcquire(SND_ACCESS_OUT|SND_ACCESS_DSP,0,0,0, NULL_NEGOTIATION_FUN,0,&dev_port,&owner_port); Error(k_err,"DSP or DACs Busy, or access to devices denied\n Are you using another machine's DSP?"); k_err = SNDReset(SND_ACCESS_OUT|SND_ACCESS_DSP,dev_port,owner_port); Error(k_err,"Error during initialization."); 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,512); k_err = snddriver_set_sndout_bufcount(dev_port,owner_port,2); Error(k_err,"Error during initialization."); // From now on, we consider we're playing, even if it's not true. /////////////// Calculate Sampling rate conversion param. ////////////////// S = ((AES == AES_LOW || AES == 0)? 44100 : 48000); [self calculateRatio:(float)S_rateIn/(float)S:rat:thresh]; D = rat[0]; U = rat[1]; if(D == 0 || U == 0) Error(1,"Weird sampling rate, can't play sound."); ///////////// Examine sound file, set stereo conversion param. /////////////// 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 MONO : printf("Mode:\t\tMono\n"); break ; } } /////////////// Calculate FIR Filter characteristics ////////////////// if(U == 1 && D == 1) K = 3; else if(KDef == 0) K = MIN((int)(55*(2-stereo)),MEMMAX/U); else K = KDef; // 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(K,250); if(verbose) fprintf("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,"Error during Stream Set-up"); 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,"Error during Stream Set-up"); 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); Error(!dspStruct,"Cannot find DSP program!\nSomething's wrong"); // k_err = SNDReadDSPfile("play.snd", &dspStruct,(char*)&i); // Error(k_err,"Cannot find DSP program!\nSomething's wrong"); k_err = SNDBootDSP(dev_port, owner_port, dspStruct); Error(k_err,"Cannot boot DSP! Something's wrong"); /////////////// 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 [self calculateFilter: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),vm_page_size,TRUE); Error(k_err,"VM Allocation Error "); vm_allocate(task_self(),(vm_address_t *)(&faa),4*DMASIZE,TRUE); Error(k_err,"VM Allocation Error "); beginSound = (short*)aSound + aSound->dataLocation/ sizeof(short) + begin; endSound = (short*)aSound+aSound->dataLocation/sizeof(short)+begin+length; beginPage = (((int)beginSound) % (vm_page_size)) / sizeof(short); endPage = (((int)endSound) % (vm_page_size)) / sizeof(short); pageNumber = (((int)endSound - (int)beginSound)/sizeof(short) - endPage - (vm_page_size/2 - beginPage)); if(pageNumber > 0) pageNumber /= (vm_page_size/2); else pageNumber = 0; if(INIT) usleep(1500000); for(i=0;i<length && beginPage<vm_page_size/2;i++, beginPage++) foo[beginPage] = *(beginSound++); if(i == length) pageNumber = -1; // No need to send anything else. k_err = snddriver_stream_start_writing(write_port,(void *)foo, vm_page_size/2,WRITE_TAG,0,0,0,0,0,0,0,0, reply_port); if(pageNumber > 0) { k_err = snddriver_stream_start_writing(write_port,(void *)beginSound, pageNumber * vm_page_size/2,WRITE_TAG,0,0,0,0,0,0,0,0, reply_port); Error(k_err,"Error when starting playing"); } if(pageNumber >= 0) { for(j=0, endSound -= endPage;j<endPage;j++) foo[j] = *(endSound++); for(;j<vm_page_size/2;j++) foo[j] = 0; k_err = snddriver_stream_start_writing(write_port,(void *)foo, vm_page_size/2,WRITE_TAG,0,0,0,0,0,0,0,0, reply_port); Error(k_err,"Error when starting playing"); } k_err = snddriver_dsp_host_cmd(cmd_port,20,SNDDRIVER_LOW_PRIORITY); k_err = snddriver_stream_start_writing(write_port,(void *)faa, 2*DMASIZE,WRITE_TAG,0,0,1,0,0,0,0,0, reply_port); Error(k_err,"Error when finishing playing"); if(WAIT) { reply_msg = (msg_header_t *)malloc(MSG_SIZE_MAX); reply_msg->msg_size = MSG_SIZE_MAX; reply_msg->msg_local_port = reply_port; k_err = msg_receive(reply_msg, MSG_OPTION_NONE, 0); [self playCompleted:self]; return 0; } DPSAddPort(reply_port, HandleDSPMessage, /* function to call */ MSG_SIZE_MAX, self, /* first arg to HandleDSPMessage */ NX_RUNMODALTHRESHOLD /* priority */ ); play_mon = DPSAddTimedEntry(0.05, PlayMonitor, self, NX_MODALRESPTHRESHOLD); status = PLAYING; return 0; } // Calculates FIR low-pass filter by windowing a sinc with a hamming window, // and normalizing. - calculateFilter:(int*)afiltre:(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)))); afiltre[order/2+i] = afiltre[order/2-i] = 8388607 * aux ; scaler += 2 * aux; } scaler = P * gain / (1 + scaler); afiltre[order/2] = 8388607 * scaler; for(i=1;i<order/2;i++) afiltre[order/2+i] = afiltre[order/2-i] = afiltre[order/2-i]*scaler; return self; } /* calculateRatio 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. */ - calculateRatio:(float)x:(int*)arat:(float)athresh { 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 > athresh) { 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++; } arat[0] = q[0]; arat[1] = p[0]; return self; } - stop { if(status != IDLE) { DPSRemovePort(reply_port); if(play_mon) DPSRemoveTimedEntry(play_mon); play_mon = 0; SNDRelease(SND_ACCESS_OUT|SND_ACCESS_DSP,dev_port,owner_port); vm_deallocate(task_self(),(pointer_t)foo, vm_page_size); vm_deallocate(task_self(),(pointer_t)faa, 4*DMASIZE); k_err = port_deallocate(task_self(),reply_port); free(filtre); status = IDLE; if (delegate && [delegate respondsTo:@selector(didPlay:)]) [delegate perform:@selector(didPlay:) with:self]; if (delegate && [delegate respondsTo:@selector(soundAnimate:)]) [delegate soundAnimate:20]; return self; } return self; } -(int) pause { if(AES != 0 || status == IDLE) return 0; switch(status) { case PLAYING : k_err = snddriver_dsp_host_cmd(cmd_port,21,SNDDRIVER_LOW_PRIORITY); Error(k_err,"Couldn't pause"); if(play_mon) DPSRemoveTimedEntry(play_mon); play_mon = 0; status = PAUSED; break; case PAUSED : k_err = snddriver_dsp_host_cmd(cmd_port,20,SNDDRIVER_LOW_PRIORITY); Error(k_err,"Couldn't resume"); play_mon = DPSAddTimedEntry(0.05, PlayMonitor, self, NX_MODALRESPTHRESHOLD); status = PLAYING; break; default : return 0; } return 1; } - pause:sender { [self pause]; return self; } - playCompleted:tag { usleep(100000); [self stop]; if (delegate && [delegate respondsTo:@selector(didPlay:)]) [delegate perform:@selector(didPlay:) with:self]; return self; } - playMonitor { int samples; int isStereo = (stereo+1); int del = (delegate && [delegate respondsTo:@selector(soundAnimate:)]); k_err = snddriver_stream_nsamples(write_port, &samples); samples /= sizeof(short); // samples originally contains nb of bytes! if (del) { if(samples/isStereo > size_samp) [delegate soundAnimate:0]; else [delegate soundAnimate:((first_samp+samples)/isStereo)]; } return self; } - setWait:(BOOL)wait { WAIT = wait; return self; } - setGain:(float)aGain { gain = aGain; return self; } - setOffset:(float)anOffset { offset = anOffset; return self; } - setFilterLength:(int)aFilterLength { KDef = aFilterLength; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.