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.