7 All-c sample mixing routines, using a 32 bits mixing buffer
\r
10 All systems - all compilers
\r
20 #define FRACMASK ((1L<<FRACBITS)-1)
\r
22 #define TICKLSIZE 3600
\r
23 #define TICKWSIZE (TICKLSIZE*2)
\r
24 #define TICKBSIZE (TICKWSIZE*2)
\r
25 static SLONG VC_TICKBUF[TICKLSIZE];
\r
28 #define min(a,b) (((a)<(b)) ? (a) : (b))
\r
32 UBYTE kick; /* =1 -> sample has to be restarted */
\r
33 UBYTE active; /* =1 -> sample is playing */
\r
34 UWORD flags; /* 16/8 bits looping/one-shot */
\r
35 SWORD handle; /* identifies the sample */
\r
36 ULONG start; /* start index */
\r
37 ULONG size; /* samplesize */
\r
38 ULONG reppos; /* loop start */
\r
39 ULONG repend; /* loop end */
\r
40 ULONG frq; /* current frequency */
\r
41 UBYTE vol; /* current volume */
\r
42 UBYTE pan; /* current panning position */
\r
43 SLONG current; /* current index in the sample */
\r
44 SLONG increment; /* fixed-point increment value */
\r
46 UWORD lvolsel; /* left volume table selector */
\r
47 UWORD rvolsel; /* right volume table selector */
\r
49 SLONG lvolmul; /* left volume multiply */
\r
50 SLONG rvolmul; /* right volume multiply */
\r
55 static VINFO vinf[32];
\r
58 static UWORD samplesthatfit;
\r
59 static SLONG idxsize,idxlpos,idxlend,maxvol;
\r
62 static int ampshift;
\r
67 static SLONG voltab[65][256];
\r
68 static UWORD volsel[65];
\r
74 UWORD lvolsel,rvolsel;
\r
76 void AsmStereoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,ULONG todo);
\r
77 #pragma aux AsmStereoNormal \
\r
78 parm [esi] [edi] [ebx] [ecx] [edx] \
\r
82 void AsmMonoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,ULONG todo);
\r
83 #pragma aux AsmMonoNormal \
\r
84 parm [esi] [edi] [ebx] [ecx] [edx] \
\r
91 void freedescriptor(unsigned short selector);
\r
92 #pragma aux freedescriptor = \
\r
98 unsigned short getalias(void);
\r
99 #pragma aux getalias = \
\r
110 void setbase(unsigned short selector,unsigned long offset);
\r
111 #pragma aux setbase = \
\r
119 void VC_Sample32To16Copy(SLONG *srce,SWORD *dest,ULONG count,UBYTE shift);
\r
120 #pragma aux VC_Sample32To16Copy = \
\r
142 parm [esi] [edi] [edx] [cl] \
\r
146 void VC_Sample32To8Copy(SLONG *srce,SBYTE *dest,ULONG count,UBYTE shift);
\r
147 #pragma aux VC_Sample32To8Copy = \
\r
170 parm [esi] [edi] [edx] [cl] \
\r
177 static SLONG lvolmul,rvolmul;
\r
180 static void VC_Sample32To8Copy(SLONG *srce,SBYTE *dest,ULONG count,UBYTE shift)
\r
183 int shift=(24-ampshift);
\r
188 else if(c<-128) c=-128;
\r
195 static void VC_Sample32To16Copy(SLONG *srce,SWORD *dest,ULONG count,UBYTE shift)
\r
198 int shift=(16-ampshift);
\r
202 if(c>32767) c=32767;
\r
203 else if(c<-32768) c=-32768;
\r
212 static SLONG fraction2long(ULONG dividend,UWORD divisor)
\r
214 Converts the fraction 'dividend/divisor' into a fixed point longword.
\r
219 whole=dividend/divisor;
\r
220 part=((dividend%divisor)<<FRACBITS)/divisor;
\r
222 return((whole<<FRACBITS)|part);
\r
226 static UWORD samples2bytes(UWORD samples)
\r
228 if(md_mode & DMODE_16BITS) samples<<=1;
\r
229 if(md_mode & DMODE_STEREO) samples<<=1;
\r
234 static UWORD bytes2samples(UWORD bytes)
\r
236 if(md_mode & DMODE_16BITS) bytes>>=1;
\r
237 if(md_mode & DMODE_STEREO) bytes>>=1;
\r
242 /**************************************************
\r
243 ***************************************************
\r
244 ***************************************************
\r
245 **************************************************/
\r
248 static SBYTE *Samples[MAXSAMPLEHANDLES];
\r
251 BOOL LargeRead(SBYTE *buffer,ULONG size)
\r
257 /* how many bytes to load (in chunks of 8000) ? */
\r
259 todo=(size>8000)?8000:size;
\r
263 SL_Load(buffer,todo);
\r
264 /* and update pointers.. */
\r
274 SWORD VC_SampleLoad(FILE *fp,ULONG length,ULONG reppos,ULONG repend,UWORD flags)
\r
279 SL_Init(fp,flags,(flags|SF_SIGNED)&~SF_16BITS);
\r
281 /* Find empty slot to put sample address in */
\r
283 for(handle=0;handle<MAXSAMPLEHANDLES;handle++){
\r
284 if(Samples[handle]==NULL) break;
\r
287 if(handle==MAXSAMPLEHANDLES){
\r
288 myerr=ERROR_OUT_OF_HANDLES;
\r
292 if((Samples[handle]=(SBYTE *)malloc(length+16))==NULL){
\r
293 myerr=ERROR_SAMPLE_TOO_BIG;
\r
297 /* read sample into buffer. */
\r
298 LargeRead(Samples[handle],length);
\r
300 /* Unclick samples: */
\r
302 if(flags & SF_LOOP){
\r
303 if(flags & SF_BIDI)
\r
304 for(t=0;t<16;t++) Samples[handle][repend+t]=Samples[handle][(repend-t)-1];
\r
306 for(t=0;t<16;t++) Samples[handle][repend+t]=Samples[handle][t+reppos];
\r
309 for(t=0;t<16;t++) Samples[handle][t+length]=0;
\r
317 void VC_SampleUnload(SWORD handle)
\r
319 void *sampleadr=Samples[handle];
\r
322 Samples[handle]=NULL;
\r
326 /**************************************************
\r
327 ***************************************************
\r
328 ***************************************************
\r
329 **************************************************/
\r
332 #ifndef __WATCOMC__
\r
335 static void (*SampleMix)(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo);
\r
338 static void MixStereoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo)
\r
343 sample=srce[index>>FRACBITS];
\r
344 *(dest++)+=lvolmul*sample;
\r
345 *(dest++)+=rvolmul*sample;
\r
352 static void MixMonoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo)
\r
357 sample=srce[index>>FRACBITS];
\r
358 *(dest++)+=lvolmul*sample;
\r
365 static void MixStereoInterp(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo)
\r
370 a=srce[index>>FRACBITS];
\r
371 b=srce[1+(index>>FRACBITS)];
\r
372 sample=a+(((long)(b-a)*(index&FRACMASK))>>FRACBITS);
\r
374 *(dest++)+=lvolmul*sample;
\r
375 *(dest++)+=rvolmul*sample;
\r
382 static void MixMonoInterp(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo)
\r
387 a=srce[index>>FRACBITS];
\r
388 b=srce[1+(index>>FRACBITS)];
\r
389 sample=a+(((long)(b-a)*(index&FRACMASK))>>FRACBITS);
\r
391 *(dest++)+=lvolmul*sample;
\r
401 static UWORD NewPredict(SLONG index,SLONG end,SLONG increment,UWORD todo)
\r
403 This functions returns the number of resamplings we can do so that:
\r
405 - it never accesses indexes bigger than index 'end'
\r
406 - it doesn't do more than 'todo' resamplings
\r
411 di=(end-index)/increment;
\r
412 index+=(di*increment);
\r
426 return ((di<todo) ? di : todo);
\r
430 static void VC_AddChannel(SLONG *ptr,UWORD todo)
\r
432 Mixes 'todo' stereo or mono samples of the current channel to the tickbuffer.
\r
441 /* update the 'current' index so the sample loops, or
\r
442 stops playing if it reached the end of the sample */
\r
444 if(vnf->flags&SF_REVERSE){
\r
446 /* The sample is playing in reverse */
\r
448 if(vnf->flags&SF_LOOP){
\r
450 /* the sample is looping, so check if
\r
451 it reached the loopstart index */
\r
453 if(vnf->current<idxlpos){
\r
454 if(vnf->flags&SF_BIDI){
\r
456 /* sample is doing bidirectional loops, so 'bounce'
\r
457 the current index against the idxlpos */
\r
459 vnf->current=idxlpos+(idxlpos-vnf->current);
\r
460 vnf->flags&=~SF_REVERSE;
\r
461 vnf->increment=-vnf->increment;
\r
464 /* normal backwards looping, so set the
\r
465 current position to loopend index */
\r
467 vnf->current=idxlend-(idxlpos-vnf->current);
\r
472 /* the sample is not looping, so check
\r
473 if it reached index 0 */
\r
475 if(vnf->current<0){
\r
477 /* playing index reached 0, so stop
\r
478 playing this sample */
\r
488 /* The sample is playing forward */
\r
490 if(vnf->flags&SF_LOOP){
\r
492 /* the sample is looping, so check if
\r
493 it reached the loopend index */
\r
495 if(vnf->current>idxlend){
\r
496 if(vnf->flags&SF_BIDI){
\r
498 /* sample is doing bidirectional loops, so 'bounce'
\r
499 the current index against the idxlend */
\r
501 vnf->flags|=SF_REVERSE;
\r
502 vnf->increment=-vnf->increment;
\r
503 vnf->current=idxlend-(vnf->current-idxlend); /* ?? */
\r
506 /* normal backwards looping, so set the
\r
507 current position to loopend index */
\r
509 vnf->current=idxlpos+(vnf->current-idxlend);
\r
514 /* sample is not looping, so check
\r
515 if it reached the last position */
\r
517 if(vnf->current>idxsize){
\r
519 /* yes, so stop playing this sample */
\r
528 /* Vraag een far ptr op van het sampleadres
\r
529 op byte offset vnf->current, en hoeveel samples
\r
530 daarvan geldig zijn (VOORDAT segment overschrijding optreed) */
\r
532 if(!(s=Samples[vnf->handle])){
\r
538 if(vnf->flags & SF_REVERSE)
\r
539 end = (vnf->flags & SF_LOOP) ? idxlpos : 0;
\r
541 end = (vnf->flags & SF_LOOP) ? idxlend : idxsize;
\r
543 /* Als de sample simpelweg niet beschikbaar is, of als
\r
544 sample gestopt moet worden sample stilleggen en stoppen */
\r
547 done=NewPredict(vnf->current,end,vnf->increment,todo);
\r
550 /* printf("predict stopped it. current %ld, end %ld\n",vnf->current,end);
\r
555 /* optimisation: don't mix anything if volume is zero */
\r
559 if(md_mode & DMODE_STEREO)
\r
560 AsmStereoNormal(s,ptr,vnf->current,vnf->increment,done);
\r
562 AsmMonoNormal(s,ptr,vnf->current,vnf->increment,done);
\r
564 SampleMix(s,ptr,vnf->current,vnf->increment,done);
\r
567 vnf->current+=(vnf->increment*done);
\r
570 ptr+=(md_mode & DMODE_STEREO) ? (done<<1) : done;
\r
577 static void VC_FillTick(SBYTE *buf,UWORD todo)
\r
579 Mixes 'todo' samples to 'buf'.. The number of samples has
\r
580 to fit into the tickbuffer.
\r
585 /* clear the mixing buffer: */
\r
587 memset(VC_TICKBUF,0,(md_mode & DMODE_STEREO) ? todo<<3 : todo<<2);
\r
589 for(t=0;t<md_numchn;t++){
\r
593 idxsize=(vnf->size<<FRACBITS)-1;
\r
594 idxlpos=vnf->reppos<<FRACBITS;
\r
595 idxlend=(vnf->repend<<FRACBITS)-1;
\r
597 lvolsel=vnf->lvolsel;
\r
598 rvolsel=vnf->rvolsel;
\r
600 lvolmul=vnf->lvolmul;
\r
601 rvolmul=vnf->rvolmul;
\r
603 VC_AddChannel(VC_TICKBUF,todo);
\r
607 if(md_mode & DMODE_16BITS)
\r
608 VC_Sample32To16Copy(VC_TICKBUF,(SWORD *)buf,(md_mode & DMODE_STEREO) ? todo<<1 : todo,16-ampshift);
\r
610 VC_Sample32To8Copy(VC_TICKBUF,buf,(md_mode & DMODE_STEREO) ? todo<<1 : todo,24-ampshift);
\r
615 static void VC_WritePortion(SBYTE *buf,UWORD todo)
\r
617 Writes 'todo' mixed SAMPLES (!!) to 'buf'. When todo is bigger than the
\r
618 number of samples that fit into VC_TICKBUF, the mixing operation is split
\r
619 up into a number of smaller chunks.
\r
624 /* write 'part' samples to the buffer */
\r
627 part=min(todo,samplesthatfit);
\r
628 VC_FillTick(buf,part);
\r
629 buf+=samples2bytes(part);
\r
635 static UWORD TICKLEFT;
\r
638 void VC_WriteSamples(SBYTE *buf,UWORD todo)
\r
648 TICKLEFT=(125L*md_mixfreq)/(50L*md_bpm);
\r
650 /* compute volume, frequency counter & panning parameters for each channel. */
\r
652 for(t=0;t<md_numchn;t++){
\r
653 int pan,vol,lvol,rvol;
\r
656 vinf[t].current=(vinf[t].start << FRACBITS);
\r
661 if(vinf[t].frq==0) vinf[t].active=0;
\r
663 if(vinf[t].active){
\r
664 vinf[t].increment=fraction2long(vinf[t].frq,md_mixfreq);
\r
666 if(vinf[t].flags & SF_REVERSE) vinf[t].increment=-vinf[t].increment;
\r
672 if(md_mode & DMODE_STEREO){
\r
673 lvol= ( vol * (255-pan) ) / 255;
\r
674 rvol= ( vol * pan ) / 255;
\r
675 vinf[t].lvolsel=volsel[lvol];
\r
676 vinf[t].rvolsel=volsel[rvol];
\r
679 vinf[t].lvolsel=volsel[vol];
\r
682 if(md_mode & DMODE_STEREO){
\r
683 lvol= ( vol * (255-pan) ) / 255;
\r
684 rvol= ( vol * pan ) / 255;
\r
685 vinf[t].lvolmul=(maxvol*lvol)/64;
\r
686 vinf[t].rvolmul=(maxvol*rvol)/64;
\r
689 vinf[t].lvolmul=(maxvol*vol)/64;
\r
696 part=min(TICKLEFT,todo);
\r
698 VC_WritePortion(buf,part);
\r
703 buf+=samples2bytes(part);
\r
708 UWORD VC_WriteBytes(SBYTE *buf,UWORD todo)
\r
710 Writes 'todo' mixed SBYTES (!!) to 'buf'. It returns the number of
\r
711 SBYTES actually written to 'buf' (which is rounded to number of samples
\r
712 that fit into 'todo' bytes).
\r
715 todo=bytes2samples(todo);
\r
716 VC_WriteSamples(buf,todo);
\r
717 return samples2bytes(todo);
\r
721 void VC_SilenceBytes(SBYTE *buf,UWORD todo)
\r
723 Fill the buffer with 'todo' bytes of silence (it depends on the mixing
\r
724 mode how the buffer is filled)
\r
727 /* clear the buffer to zero (16 bits
\r
728 signed ) or 0x80 (8 bits unsigned) */
\r
730 if(md_mode & DMODE_16BITS)
\r
731 memset(buf,0,todo);
\r
733 memset(buf,0x80,todo);
\r
737 void VC_PlayStart(void)
\r
741 maxvol=16777216L / md_numchn;
\r
746 SLONG volmul=(maxvol*t)/64;
\r
747 for(c=-128;c<128;c++){
\r
748 voltab[t][(UBYTE)c]=(SLONG)c*volmul;
\r
753 /* instead of using a amplifying lookup table, I'm using a simple shift
\r
754 amplify now.. amplifying doubles with every extra 4 channels, and also
\r
755 doubles in stereo mode.. this seems to give similar volume levels
\r
756 across the channel range */
\r
758 ampshift=md_numchn/8;
\r
759 if(md_mode & DMODE_STEREO) ampshift++;
\r
761 #ifndef __WATCOMC__
\r
762 if(md_mode & DMODE_INTERP)
\r
763 SampleMix=(md_mode & DMODE_STEREO) ? MixStereoInterp : MixMonoInterp;
\r
765 SampleMix=(md_mode & DMODE_STEREO) ? MixStereoNormal : MixMonoNormal;
\r
768 samplesthatfit=TICKLSIZE;
\r
769 if(md_mode & DMODE_STEREO) samplesthatfit>>=1;
\r
774 void VC_PlayStop(void)
\r
790 vinf[t].pan=(t&1)?0:255;
\r
794 if(md_mode & DMODE_INTERP) md_mode&=~DMODE_INTERP;
\r
796 for(t=0;t<65;t++) volsel[t]=0;
\r
799 if(!(volsel[t]=getalias())) return 0;
\r
800 setbase(volsel[t],(ULONG)voltab[t]);
\r
813 if(volsel[t]) freedescriptor(volsel[t]);
\r
819 void VC_VoiceSetVolume(UBYTE voice,UBYTE vol)
\r
821 vinf[voice].vol=vol;
\r
825 void VC_VoiceSetFrequency(UBYTE voice,ULONG frq)
\r
827 vinf[voice].frq=frq;
\r
831 void VC_VoiceSetPanning(UBYTE voice,UBYTE pan)
\r
833 vinf[voice].pan=pan;
\r
837 void VC_VoicePlay(UBYTE voice,SWORD handle,ULONG start,ULONG size,ULONG reppos,ULONG repend,UWORD flags)
\r
839 if(start>=size) return;
\r
842 if(repend>size) repend=size; /* repend can't be bigger than size */
\r
845 vinf[voice].flags=flags;
\r
846 vinf[voice].handle=handle;
\r
847 vinf[voice].start=start;
\r
848 vinf[voice].size=size;
\r
849 vinf[voice].reppos=reppos;
\r
850 vinf[voice].repend=repend;
\r
851 vinf[voice].kick=1;
\r