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 #if __WATCOMC__ >= 1200 /* OpenWatcom 1.0+ */
\r
77 #define AsmStereoNormal _AsmStereoNormal
\r
78 #define AsmMonoNormal _AsmMonoNormal
\r
81 void AsmStereoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,ULONG todo);
\r
82 #pragma aux AsmStereoNormal \
\r
83 parm [esi] [edi] [ebx] [ecx] [edx] \
\r
87 void AsmMonoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,ULONG todo);
\r
88 #pragma aux AsmMonoNormal \
\r
89 parm [esi] [edi] [ebx] [ecx] [edx] \
\r
96 void freedescriptor(unsigned short selector);
\r
97 #pragma aux freedescriptor = \
\r
103 unsigned short getalias(void);
\r
104 #pragma aux getalias = \
\r
115 void setbase(unsigned short selector,unsigned long offset);
\r
116 #pragma aux setbase = \
\r
124 void VC_Sample32To16Copy(SLONG *srce,SWORD *dest,ULONG count,UBYTE shift);
\r
125 #pragma aux VC_Sample32To16Copy = \
\r
147 parm [esi] [edi] [edx] [cl] \
\r
151 void VC_Sample32To8Copy(SLONG *srce,SBYTE *dest,ULONG count,UBYTE shift);
\r
152 #pragma aux VC_Sample32To8Copy = \
\r
175 parm [esi] [edi] [edx] [cl] \
\r
182 static SLONG lvolmul,rvolmul;
\r
185 static void VC_Sample32To8Copy(SLONG *srce,SBYTE *dest,ULONG count,UBYTE shift)
\r
188 int shift=(24-ampshift);
\r
193 else if(c<-128) c=-128;
\r
200 static void VC_Sample32To16Copy(SLONG *srce,SWORD *dest,ULONG count,UBYTE shift)
\r
203 int shift=(16-ampshift);
\r
207 if(c>32767) c=32767;
\r
208 else if(c<-32768) c=-32768;
\r
217 static SLONG fraction2long(ULONG dividend,UWORD divisor)
\r
219 Converts the fraction 'dividend/divisor' into a fixed point longword.
\r
224 whole=dividend/divisor;
\r
225 part=((dividend%divisor)<<FRACBITS)/divisor;
\r
227 return((whole<<FRACBITS)|part);
\r
231 static UWORD samples2bytes(UWORD samples)
\r
233 if(md_mode & DMODE_16BITS) samples<<=1;
\r
234 if(md_mode & DMODE_STEREO) samples<<=1;
\r
239 static UWORD bytes2samples(UWORD bytes)
\r
241 if(md_mode & DMODE_16BITS) bytes>>=1;
\r
242 if(md_mode & DMODE_STEREO) bytes>>=1;
\r
247 /**************************************************
\r
248 ***************************************************
\r
249 ***************************************************
\r
250 **************************************************/
\r
253 static SBYTE *Samples[MAXSAMPLEHANDLES];
\r
256 BOOL LargeRead(SBYTE *buffer,ULONG size)
\r
262 /* how many bytes to load (in chunks of 8000) ? */
\r
264 todo=(size>8000)?8000:size;
\r
268 SL_Load(buffer,todo);
\r
269 /* and update pointers.. */
\r
279 SWORD VC_SampleLoad(FILE *fp,ULONG length,ULONG reppos,ULONG repend,UWORD flags)
\r
284 SL_Init(fp,flags,(flags|SF_SIGNED)&~SF_16BITS);
\r
286 /* Find empty slot to put sample address in */
\r
288 for(handle=0;handle<MAXSAMPLEHANDLES;handle++){
\r
289 if(Samples[handle]==NULL) break;
\r
292 if(handle==MAXSAMPLEHANDLES){
\r
293 myerr=ERROR_OUT_OF_HANDLES;
\r
297 if((Samples[handle]=(SBYTE *)malloc(length+16))==NULL){
\r
298 myerr=ERROR_SAMPLE_TOO_BIG;
\r
302 /* read sample into buffer. */
\r
303 LargeRead(Samples[handle],length);
\r
305 /* Unclick samples: */
\r
307 if(flags & SF_LOOP){
\r
308 if(flags & SF_BIDI)
\r
309 for(t=0;t<16;t++) Samples[handle][repend+t]=Samples[handle][(repend-t)-1];
\r
311 for(t=0;t<16;t++) Samples[handle][repend+t]=Samples[handle][t+reppos];
\r
314 for(t=0;t<16;t++) Samples[handle][t+length]=0;
\r
322 void VC_SampleUnload(SWORD handle)
\r
324 void *sampleadr=Samples[handle];
\r
327 Samples[handle]=NULL;
\r
331 /**************************************************
\r
332 ***************************************************
\r
333 ***************************************************
\r
334 **************************************************/
\r
337 #ifndef __WATCOMC__
\r
340 static void (*SampleMix)(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo);
\r
343 static void MixStereoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo)
\r
348 sample=srce[index>>FRACBITS];
\r
349 *(dest++)+=lvolmul*sample;
\r
350 *(dest++)+=rvolmul*sample;
\r
357 static void MixMonoNormal(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo)
\r
362 sample=srce[index>>FRACBITS];
\r
363 *(dest++)+=lvolmul*sample;
\r
370 static void MixStereoInterp(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo)
\r
375 a=srce[index>>FRACBITS];
\r
376 b=srce[1+(index>>FRACBITS)];
\r
377 sample=a+(((long)(b-a)*(index&FRACMASK))>>FRACBITS);
\r
379 *(dest++)+=lvolmul*sample;
\r
380 *(dest++)+=rvolmul*sample;
\r
387 static void MixMonoInterp(SBYTE *srce,SLONG *dest,SLONG index,SLONG increment,UWORD todo)
\r
392 a=srce[index>>FRACBITS];
\r
393 b=srce[1+(index>>FRACBITS)];
\r
394 sample=a+(((long)(b-a)*(index&FRACMASK))>>FRACBITS);
\r
396 *(dest++)+=lvolmul*sample;
\r
406 static UWORD NewPredict(SLONG index,SLONG end,SLONG increment,UWORD todo)
\r
408 This functions returns the number of resamplings we can do so that:
\r
410 - it never accesses indexes bigger than index 'end'
\r
411 - it doesn't do more than 'todo' resamplings
\r
416 di=(end-index)/increment;
\r
417 index+=(di*increment);
\r
431 return ((di<todo) ? di : todo);
\r
435 static void VC_AddChannel(SLONG *ptr,UWORD todo)
\r
437 Mixes 'todo' stereo or mono samples of the current channel to the tickbuffer.
\r
446 /* update the 'current' index so the sample loops, or
\r
447 stops playing if it reached the end of the sample */
\r
449 if(vnf->flags&SF_REVERSE){
\r
451 /* The sample is playing in reverse */
\r
453 if(vnf->flags&SF_LOOP){
\r
455 /* the sample is looping, so check if
\r
456 it reached the loopstart index */
\r
458 if(vnf->current<idxlpos){
\r
459 if(vnf->flags&SF_BIDI){
\r
461 /* sample is doing bidirectional loops, so 'bounce'
\r
462 the current index against the idxlpos */
\r
464 vnf->current=idxlpos+(idxlpos-vnf->current);
\r
465 vnf->flags&=~SF_REVERSE;
\r
466 vnf->increment=-vnf->increment;
\r
469 /* normal backwards looping, so set the
\r
470 current position to loopend index */
\r
472 vnf->current=idxlend-(idxlpos-vnf->current);
\r
477 /* the sample is not looping, so check
\r
478 if it reached index 0 */
\r
480 if(vnf->current<0){
\r
482 /* playing index reached 0, so stop
\r
483 playing this sample */
\r
493 /* The sample is playing forward */
\r
495 if(vnf->flags&SF_LOOP){
\r
497 /* the sample is looping, so check if
\r
498 it reached the loopend index */
\r
500 if(vnf->current>idxlend){
\r
501 if(vnf->flags&SF_BIDI){
\r
503 /* sample is doing bidirectional loops, so 'bounce'
\r
504 the current index against the idxlend */
\r
506 vnf->flags|=SF_REVERSE;
\r
507 vnf->increment=-vnf->increment;
\r
508 vnf->current=idxlend-(vnf->current-idxlend); /* ?? */
\r
511 /* normal backwards looping, so set the
\r
512 current position to loopend index */
\r
514 vnf->current=idxlpos+(vnf->current-idxlend);
\r
519 /* sample is not looping, so check
\r
520 if it reached the last position */
\r
522 if(vnf->current>idxsize){
\r
524 /* yes, so stop playing this sample */
\r
533 /* Vraag een far ptr op van het sampleadres
\r
534 op byte offset vnf->current, en hoeveel samples
\r
535 daarvan geldig zijn (VOORDAT segment overschrijding optreed) */
\r
537 if(!(s=Samples[vnf->handle])){
\r
543 if(vnf->flags & SF_REVERSE)
\r
544 end = (vnf->flags & SF_LOOP) ? idxlpos : 0;
\r
546 end = (vnf->flags & SF_LOOP) ? idxlend : idxsize;
\r
548 /* Als de sample simpelweg niet beschikbaar is, of als
\r
549 sample gestopt moet worden sample stilleggen en stoppen */
\r
552 done=NewPredict(vnf->current,end,vnf->increment,todo);
\r
555 /* printf("predict stopped it. current %ld, end %ld\n",vnf->current,end);
\r
560 /* optimisation: don't mix anything if volume is zero */
\r
564 if(md_mode & DMODE_STEREO)
\r
565 AsmStereoNormal(s,ptr,vnf->current,vnf->increment,done);
\r
567 AsmMonoNormal(s,ptr,vnf->current,vnf->increment,done);
\r
569 SampleMix(s,ptr,vnf->current,vnf->increment,done);
\r
572 vnf->current+=(vnf->increment*done);
\r
575 ptr+=(md_mode & DMODE_STEREO) ? (done<<1) : done;
\r
582 static void VC_FillTick(SBYTE *buf,UWORD todo)
\r
584 Mixes 'todo' samples to 'buf'.. The number of samples has
\r
585 to fit into the tickbuffer.
\r
590 /* clear the mixing buffer: */
\r
592 memset(VC_TICKBUF,0,(md_mode & DMODE_STEREO) ? todo<<3 : todo<<2);
\r
594 for(t=0;t<md_numchn;t++){
\r
598 idxsize=(vnf->size<<FRACBITS)-1;
\r
599 idxlpos=vnf->reppos<<FRACBITS;
\r
600 idxlend=(vnf->repend<<FRACBITS)-1;
\r
602 lvolsel=vnf->lvolsel;
\r
603 rvolsel=vnf->rvolsel;
\r
605 lvolmul=vnf->lvolmul;
\r
606 rvolmul=vnf->rvolmul;
\r
608 VC_AddChannel(VC_TICKBUF,todo);
\r
612 if(md_mode & DMODE_16BITS)
\r
613 VC_Sample32To16Copy(VC_TICKBUF,(SWORD *)buf,(md_mode & DMODE_STEREO) ? todo<<1 : todo,16-ampshift);
\r
615 VC_Sample32To8Copy(VC_TICKBUF,buf,(md_mode & DMODE_STEREO) ? todo<<1 : todo,24-ampshift);
\r
620 static void VC_WritePortion(SBYTE *buf,UWORD todo)
\r
622 Writes 'todo' mixed SAMPLES (!!) to 'buf'. When todo is bigger than the
\r
623 number of samples that fit into VC_TICKBUF, the mixing operation is split
\r
624 up into a number of smaller chunks.
\r
629 /* write 'part' samples to the buffer */
\r
632 part=min(todo,samplesthatfit);
\r
633 VC_FillTick(buf,part);
\r
634 buf+=samples2bytes(part);
\r
640 static UWORD TICKLEFT;
\r
643 void VC_WriteSamples(SBYTE *buf,UWORD todo)
\r
653 TICKLEFT=(125L*md_mixfreq)/(50L*md_bpm);
\r
655 /* compute volume, frequency counter & panning parameters for each channel. */
\r
657 for(t=0;t<md_numchn;t++){
\r
658 int pan,vol,lvol,rvol;
\r
661 vinf[t].current=(vinf[t].start << FRACBITS);
\r
666 if(vinf[t].frq==0) vinf[t].active=0;
\r
668 if(vinf[t].active){
\r
669 vinf[t].increment=fraction2long(vinf[t].frq,md_mixfreq);
\r
671 if(vinf[t].flags & SF_REVERSE) vinf[t].increment=-vinf[t].increment;
\r
677 if(md_mode & DMODE_STEREO){
\r
678 lvol= ( vol * (255-pan) ) / 255;
\r
679 rvol= ( vol * pan ) / 255;
\r
680 vinf[t].lvolsel=volsel[lvol];
\r
681 vinf[t].rvolsel=volsel[rvol];
\r
684 vinf[t].lvolsel=volsel[vol];
\r
687 if(md_mode & DMODE_STEREO){
\r
688 lvol= ( vol * (255-pan) ) / 255;
\r
689 rvol= ( vol * pan ) / 255;
\r
690 vinf[t].lvolmul=(maxvol*lvol)/64;
\r
691 vinf[t].rvolmul=(maxvol*rvol)/64;
\r
694 vinf[t].lvolmul=(maxvol*vol)/64;
\r
701 part=min(TICKLEFT,todo);
\r
703 VC_WritePortion(buf,part);
\r
708 buf+=samples2bytes(part);
\r
713 UWORD VC_WriteBytes(SBYTE *buf,UWORD todo)
\r
715 Writes 'todo' mixed SBYTES (!!) to 'buf'. It returns the number of
\r
716 SBYTES actually written to 'buf' (which is rounded to number of samples
\r
717 that fit into 'todo' bytes).
\r
720 todo=bytes2samples(todo);
\r
721 VC_WriteSamples(buf,todo);
\r
722 return samples2bytes(todo);
\r
726 void VC_SilenceBytes(SBYTE *buf,UWORD todo)
\r
728 Fill the buffer with 'todo' bytes of silence (it depends on the mixing
\r
729 mode how the buffer is filled)
\r
732 /* clear the buffer to zero (16 bits
\r
733 signed ) or 0x80 (8 bits unsigned) */
\r
735 if(md_mode & DMODE_16BITS)
\r
736 memset(buf,0,todo);
\r
738 memset(buf,0x80,todo);
\r
742 void VC_PlayStart(void)
\r
746 maxvol=16777216L / md_numchn;
\r
751 SLONG volmul=(maxvol*t)/64;
\r
752 for(c=-128;c<128;c++){
\r
753 voltab[t][(UBYTE)c]=(SLONG)c*volmul;
\r
758 /* instead of using a amplifying lookup table, I'm using a simple shift
\r
759 amplify now.. amplifying doubles with every extra 4 channels, and also
\r
760 doubles in stereo mode.. this seems to give similar volume levels
\r
761 across the channel range */
\r
763 ampshift=md_numchn/8;
\r
764 if(md_mode & DMODE_STEREO) ampshift++;
\r
766 #ifndef __WATCOMC__
\r
767 if(md_mode & DMODE_INTERP)
\r
768 SampleMix=(md_mode & DMODE_STEREO) ? MixStereoInterp : MixMonoInterp;
\r
770 SampleMix=(md_mode & DMODE_STEREO) ? MixStereoNormal : MixMonoNormal;
\r
773 samplesthatfit=TICKLSIZE;
\r
774 if(md_mode & DMODE_STEREO) samplesthatfit>>=1;
\r
779 void VC_PlayStop(void)
\r
795 vinf[t].pan=(t&1)?0:255;
\r
799 if(md_mode & DMODE_INTERP) md_mode&=~DMODE_INTERP;
\r
801 for(t=0;t<65;t++) volsel[t]=0;
\r
804 if(!(volsel[t]=getalias())) return 0;
\r
805 setbase(volsel[t],(ULONG)voltab[t]);
\r
818 if(volsel[t]) freedescriptor(volsel[t]);
\r
824 void VC_VoiceSetVolume(UBYTE voice,UBYTE vol)
\r
826 vinf[voice].vol=vol;
\r
830 void VC_VoiceSetFrequency(UBYTE voice,ULONG frq)
\r
832 vinf[voice].frq=frq;
\r
836 void VC_VoiceSetPanning(UBYTE voice,UBYTE pan)
\r
838 vinf[voice].pan=pan;
\r
842 void VC_VoicePlay(UBYTE voice,SWORD handle,ULONG start,ULONG size,ULONG reppos,ULONG repend,UWORD flags)
\r
844 if(start>=size) return;
\r
847 if(repend>size) repend=size; /* repend can't be bigger than size */
\r
850 vinf[voice].flags=flags;
\r
851 vinf[voice].handle=handle;
\r
852 vinf[voice].start=start;
\r
853 vinf[voice].size=size;
\r
854 vinf[voice].reppos=reppos;
\r
855 vinf[voice].repend=repend;
\r
856 vinf[voice].kick=1;
\r