/* Name: VIRTCH.C Description: All-c sample mixing routines, using a 32 bits mixing buffer Portability: All systems - all compilers */ package audio.jmikmod.MikMod.Virtch; import persist.SimulatedRandomAccessFile; import audio.jmikmod.MikMod.VINFO; import audio.jmikmod.MikMod.clMain; public class clVirtch extends Object { public clMain m_; public static final int TICKLSIZE = 3600; public static final int TICKWSIZE = (TICKLSIZE*2); public static final int TICKBSIZE = (TICKWSIZE*2); /* max. number of handles a driver has to provide. (not strict) */ public static final int MAXSAMPLEHANDLES = 128; protected int VC_TICKBUF[]; //[TICKLSIZE]; protected VINFO vinf[]; //[32]; protected VINFO vnf; protected short samplesthatfit; protected int idxsize,idxlpos,idxlend,maxvol; protected int per256; protected int ampshift; protected int lvolmul,rvolmul; protected byte [] Samples[]; //[MAXSAMPLEHANDLES]; protected int iWhichSampleMixFunc; protected int TICKLEFT; protected static final int FRACBITS = 11; protected static final int FRACMASK = ((1<<FRACBITS)-1); public clVirtch(clMain theMain) { int i; VC_TICKBUF = new int [TICKLSIZE]; vinf = new VINFO[32]; for(i=0;i<32;i++) vinf[i] = new VINFO(); Samples = new byte [MAXSAMPLEHANDLES][]; m_ = theMain; //memset(VC_TICKBUF, 0, sizeof(VC_TICKBUF)); for(i=0;i<TICKLSIZE;i++) VC_TICKBUF[i] = 0; //memset(vinf, 0, sizeof(vinf)); for(i=0;i<32;i++) { vinf[i].flags = vinf[i].start = vinf[i].size = vinf[i].reppos = vinf[i].repend = vinf[i].frq = vinf[i].current = vinf[i].increment = vinf[i].lvolmul = vinf[i].rvolmul = 0; vinf[i].handle = vinf[i].vol = vinf[i].pan = (short)0; vinf[i].kick = vinf[i].active = false; } vnf = null; samplesthatfit = 0; idxsize = idxlpos = idxlend = maxvol = 0; per256 = 0; ampshift = 0; lvolmul = rvolmul = 0; //memset(Samples, 0, sizeof(Samples)); for(i=0;i<MAXSAMPLEHANDLES;i++) Samples[i] = null; TICKLEFT = 0; } protected void VC_Sample32To8Copy(int srce[],byte dest[],int dest_offset, int count,short shift) { int c; int shifti=(24-ampshift); int src_idx=0, dest_idx=dest_offset; while((count--) != 0){ c=srce[src_idx] >> shifti; if(c>127) c=(byte)127; else if(c<-128) c=-128; dest[dest_idx++]=(byte)(c+128); src_idx++; } } protected void VC_Sample32To16Copy(int srce[],byte dest[],int dest_offset, int count,short shift) { int c; int shifti=(16-ampshift); int src_idx=0, dest_idx=dest_offset; while((count--) != 0){ c=srce[src_idx] >> shifti; if(c>32767) c=32767; else if(c<-32768) c=-32768; //#ifdef MM_BIG_ENDIAN // dest[dest_idx++]=(c>>8)&0xFF; // dest[dest_idx++]=c&0xFF; //#else dest[dest_idx++]=(byte)(c&0xFF); dest[dest_idx++]=(byte)((c>>8)&0xFF); //#endif src_idx++; } } protected static int fraction2long(int dividend,int divisor) /* Converts the fraction 'dividend/divisor' into a fixed point longword. */ { int whole,part; whole=dividend/divisor; part=((dividend%divisor)<<FRACBITS)/divisor; return((whole<<FRACBITS)|part); } protected int samples2bytes(int samples) { if((m_.MDriver.md_mode & m_.DMODE_16BITS) != 0) samples<<=1; if((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0) samples<<=1; return samples; } protected int bytes2samples(int bytes) { if((m_.MDriver.md_mode & m_.DMODE_16BITS) != 0) bytes>>=1; if((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0) bytes>>=1; return bytes; } /************************************************** *************************************************** *************************************************** **************************************************/ public int LargeRead(byte buffer[],int size) { int t; int todo; int buf_offset=0; while(size != 0){ /* how many bytes to load (in chunks of 8000) ? */ todo=(size>8000)?8000:size; /* read data */ m_.MDriver.SL_Load(buffer,buf_offset,todo); /* and update pointers.. */ size-=todo; buf_offset+=todo; } return 1; } public short VC_SampleLoad(SimulatedRandomAccessFile fp,int length,int reppos,int repend,int flags) { int handle; int t; m_.MDriver.SL_Init(fp,(short)flags,(short)((flags|(m_.MDriver.SF_SIGNED))&~(m_.MDriver.SF_16BITS))); /* Find empty slot to put sample address in */ for(handle=0;handle<MAXSAMPLEHANDLES;handle++){ if(Samples[handle]==null) break; } if(handle==MAXSAMPLEHANDLES){ m_.mmIO.myerr=m_.ERROR_OUT_OF_HANDLES; return -1; } /*if((Samples[handle]=(char *)malloc(length+17))==NULL){ /* using 17 here appears to solve linux libc-5.4.7 paranoia probs */ /* m_.mmIO.myerr=m_.ERROR_SAMPLE_TOO_BIG; return -1; }*/ Samples[handle] = new byte [length+17]; /* read sample into buffer. */ LargeRead(Samples[handle],length); /* Unclick samples: */ if((flags & (m_.MDriver.SF_LOOP)) != 0){ if((flags & (m_.MDriver.SF_BIDI)) != 0) for(t=0;t<16;t++) Samples[handle][repend+t]=Samples[handle][(repend-t)-1]; else for(t=0;t<16;t++) Samples[handle][repend+t]=Samples[handle][t+reppos]; } else{ for(t=0;t<16;t++) Samples[handle][t+length]=0; } return (short)handle; } public void VC_SampleUnload(short handle) { //delete [] Samples[handle]; Samples[handle]=null; } /************************************************** *************************************************** *************************************************** **************************************************/ protected void MixStereoNormal(byte srce[],int dest[],int dest_offset, int index,int increment,short todo) { byte sample; int dest_idx=dest_offset; while(todo>0){ sample=srce[index>>FRACBITS]; dest[dest_idx++]+=lvolmul*sample; dest[dest_idx++]+=rvolmul*sample; index+=increment; todo--; } } protected void MixMonoNormal(byte srce[],int dest[],int dest_offset, int index,int increment,short todo) { byte sample; int dest_idx=dest_offset; while(todo>0){ sample=srce[index>>FRACBITS]; dest[dest_idx++]+=lvolmul*sample; index+=increment; todo--; } } protected void MixStereoInterp(byte srce[],int dest[],int dest_offset, int index,int increment,short todo) { short sample,a,b; int dest_idx=dest_offset; while(todo>0){ a=srce[index>>FRACBITS]; b=srce[1+(index>>FRACBITS)]; sample=(short)(a+(((int)(b-a)*(index&FRACMASK))>>FRACBITS)); dest[dest_idx++]+=lvolmul*sample; dest[dest_idx++]+=rvolmul*sample; index+=increment; todo--; } } protected void MixMonoInterp(byte srce[],int dest[],int dest_offset, int index,int increment,short todo) { short sample,a,b; int dest_idx=dest_offset; while(todo>0){ a=srce[index>>FRACBITS]; b=srce[1+(index>>FRACBITS)]; sample=(short)(a+(((int)(b-a)*(index&FRACMASK))>>FRACBITS)); dest[dest_idx++]+=lvolmul*sample; index+=increment; todo--; } } static int NewPredict(int index,int end,int increment,int todo) /* This functions returns the number of resamplings we can do so that: - it never accesses indexes bigger than index 'end' - it doesn't do more than 'todo' resamplings */ { int di; di=(end-index)/increment; index+=(di*increment); if(increment<0){ while(index>=end){ index+=increment; di++; } } else{ while(index<=end){ index+=increment; di++; } } return ((di<todo) ? di : todo); } protected void VC_AddChannel(int ptr[],int todo) /* Mixes 'todo' stereo or mono samples of the current channel to the tickbuffer. */ { int end; int done; int needs; byte s[]; int ptr_idx=0; while(todo>0){ /* update the 'current' index so the sample loops, or stops playing if it reached the end of the sample */ if((vnf.flags&(m_.MDriver.SF_REVERSE)) != 0){ /* The sample is playing in reverse */ if((vnf.flags&(m_.MDriver.SF_LOOP)) != 0){ /* the sample is looping, so check if it reached the loopstart index */ if(vnf.current<idxlpos){ if((vnf.flags&(m_.MDriver.SF_BIDI)) != 0){ /* sample is doing bidirectional loops, so 'bounce' the current index against the idxlpos */ vnf.current=idxlpos+(idxlpos-vnf.current); vnf.flags&=~(m_.MDriver.SF_REVERSE); vnf.increment=-vnf.increment; } else /* normal backwards looping, so set the current position to loopend index */ vnf.current=idxlend-(idxlpos-vnf.current); } } else{ /* the sample is not looping, so check if it reached index 0 */ if(vnf.current<0){ /* playing index reached 0, so stop playing this sample */ vnf.current=0; vnf.active=false; break; } } } else{ /* The sample is playing forward */ if((vnf.flags&(m_.MDriver.SF_LOOP)) != 0){ /* the sample is looping, so check if it reached the loopend index */ if(vnf.current>idxlend){ if((vnf.flags&(m_.MDriver.SF_BIDI)) != 0){ /* sample is doing bidirectional loops, so 'bounce' the current index against the idxlend */ vnf.flags|=(m_.MDriver.SF_REVERSE); vnf.increment=-vnf.increment; vnf.current=idxlend-(vnf.current-idxlend); /* ?? */ } else /* normal backwards looping, so set the current position to loopend index */ vnf.current=idxlpos+(vnf.current-idxlend); } } else{ /* sample is not looping, so check if it reached the last position */ if(vnf.current>idxsize){ /* yes, so stop playing this sample */ vnf.current=0; vnf.active=false; break; } } } /* Vraag een far ptr op van het sampleadres op byte offset vnf.current, en hoeveel samples daarvan geldig zijn (VOORDAT segment overschrijding optreed) */ if(Samples[vnf.handle] == null){ vnf.current=0; vnf.active=false; break; } if((vnf.flags & (m_.MDriver.SF_REVERSE)) != 0) end = ((vnf.flags & (m_.MDriver.SF_LOOP)) != 0) ? idxlpos : 0; else end = ((vnf.flags & (m_.MDriver.SF_LOOP)) != 0) ? idxlend : idxsize; /* Als de sample simpelweg niet beschikbaar is, of als sample gestopt moet worden sample stilleggen en stoppen */ /* mix 'em: */ done=NewPredict(vnf.current,end,vnf.increment,todo); if(done==0){ /* printf("predict stopped it. current %ld, end %ld\n",vnf.current,end); */ vnf.active=false; break; } /* optimisation: don't mix anything if volume is zero */ if(vnf.vol != 0){ SampleMix(Samples[vnf.handle],ptr,ptr_idx,vnf.current,vnf.increment,(short)done); } vnf.current+=(vnf.increment*done); todo-=done; ptr_idx+=((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0) ? (done<<1) : done; } } protected void VC_FillTick(byte buf[],int buf_offset,short todo) /* Mixes 'todo' samples to 'buf'.. The number of samples has to fit into the tickbuffer. */ { int t; /* clear the mixing buffer: */ //memset(VC_TICKBUF,0,(m_.MDriver.md_mode & m_.DMODE_STEREO) ? todo<<3 : todo<<2); for(t=0; t < (((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0) ? (todo<<1) : (todo)) ; t++) VC_TICKBUF[t] = 0; for(t=0;t<m_.MDriver.md_numchn;t++){ vnf=vinf[t]; if(vnf.active){ idxsize=(vnf.size<<FRACBITS)-1; idxlpos=vnf.reppos<<FRACBITS; idxlend=(vnf.repend<<FRACBITS)-1; lvolmul=vnf.lvolmul; rvolmul=vnf.rvolmul; VC_AddChannel(VC_TICKBUF,todo); } } if((m_.MDriver.md_mode & m_.DMODE_16BITS) != 0) //VC_Sample32To16Copy(VC_TICKBUF,(short *)buf,(buf_offset>>1),(m_.MDriver.md_mode & m_.DMODE_STEREO) ? todo<<1 : todo,16-ampshift); VC_Sample32To16Copy(VC_TICKBUF,buf,buf_offset,((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0) ? todo<<1 : todo,(short)(16-ampshift)); else VC_Sample32To8Copy(VC_TICKBUF,buf,buf_offset,((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0) ? todo<<1 : todo,(short)(24-ampshift)); } protected void VC_WritePortion(byte buf[],int buf_offset, short todo) /* Writes 'todo' mixed SAMPLES (!!) to 'buf'. When todo is bigger than the number of samples that fit into VC_TICKBUF, the mixing operation is split up into a number of smaller chunks. */ { short part; int buf_ptr=buf_offset; /* write 'part' samples to the buffer */ while(todo!=0){ part=(todo<samplesthatfit)?todo:samplesthatfit; VC_FillTick(buf,buf_ptr,part); buf_ptr+=samples2bytes(part); todo-=part; } } public void VC_WriteSamples(byte buf[],int todo) { int t; short part; int buf_ptr=0; while(todo>0){ if(TICKLEFT==0){ m_.MDriver.tickhandler(); TICKLEFT=(125 * m_.MDriver.md_mixfreq) / (50 * m_.MDriver.md_bpm); /* compute volume, frequency counter & panning parameters for each channel. */ for(t=0;t<m_.MDriver.md_numchn;t++){ int pan,vol,lvol,rvol; if(vinf[t].kick){ vinf[t].current=(vinf[t].start << FRACBITS); vinf[t].active=true; vinf[t].kick=false; } if(vinf[t].frq==0) vinf[t].active=false; if(vinf[t].active){ vinf[t].increment=fraction2long(vinf[t].frq,m_.MDriver.md_mixfreq); if((vinf[t].flags & (m_.MDriver.SF_REVERSE)) != 0) vinf[t].increment=-vinf[t].increment; vol=vinf[t].vol; pan=vinf[t].pan; if((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0){ lvol=(vol*((pan<128) ? 128 : (255-pan))) / 128; rvol=(vol*((pan>128) ? 128 : pan)) / 128; vinf[t].lvolmul=(maxvol*lvol)/64; vinf[t].rvolmul=(maxvol*rvol)/64; } else{ vinf[t].lvolmul=(maxvol*vol)/64; } } } } part=(short)((TICKLEFT<todo) ? TICKLEFT : todo); VC_WritePortion(buf,buf_ptr,part); TICKLEFT-=part; todo-=part; buf_ptr+=samples2bytes(part); } } public int VC_WriteBytes(byte buf[],int todo) /* Writes 'todo' mixed chars (!!) to 'buf'. It returns the number of chars actually written to 'buf' (which is rounded to number of samples that fit into 'todo' bytes). */ { todo=bytes2samples(todo); VC_WriteSamples(buf,todo); return samples2bytes(todo); } public void VC_SilenceBytes(byte buf[],short todo) /* Fill the buffer with 'todo' bytes of silence (it depends on the mixing mode how the buffer is filled) */ { int i; /* clear the buffer to zero (16 bits signed ) or 0x80 (8 bits unsigned) */ if((m_.MDriver.md_mode & m_.DMODE_16BITS) != 0) { //memset(buf,0,todo); for(i=0;i<todo;i++) buf[i] = 0; } else { //memset(buf,0x80,todo); for(i=0;i<todo;i++) buf[i] = (byte)0x80; } } public void VC_PlayStart() { int t; for(t=0;t<32;t++){ vinf[t].current=0; vinf[t].flags=0; vinf[t].handle=0; vinf[t].kick=false; vinf[t].active=false; vinf[t].frq=10000; vinf[t].vol=0; vinf[t].pan=((t&1) != 0)?((short)0):((short)255); } if(m_.MDriver.md_numchn>0) /* sanity check - avoid core dump! */ maxvol=16777216 / (m_.MDriver.md_numchn); else maxvol=16777216; /* instead of using a amplifying lookup table, I'm using a simple shift amplify now.. amplifying doubles with every extra 4 channels, and also doubles in stereo mode.. this seems to give similar volume levels across the channel range */ ampshift=m_.MDriver.md_numchn/8; /* if(md_mode & m_.DMODE_STEREO) ampshift++;*/ if((m_.MDriver.md_mode & m_.DMODE_INTERP) != 0) iWhichSampleMixFunc = ((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0) ? 3 : 2; else iWhichSampleMixFunc = ((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0) ? 1 : 0; /* if(md_mode & m_.DMODE_INTERP) SampleMix=(md_mode & m_.DMODE_STEREO) ? MixStereoInterp : MixMonoInterp; else SampleMix=(md_mode & m_.DMODE_STEREO) ? MixStereoNormal : MixMonoNormal; */ samplesthatfit=(short)TICKLSIZE; if((m_.MDriver.md_mode & m_.DMODE_STEREO) != 0) samplesthatfit>>=1; TICKLEFT=0; } protected void SampleMix(byte srce[],int dest[],int dest_offset, int index,int increment,short todo) { if (iWhichSampleMixFunc >= 2) { if (iWhichSampleMixFunc == 3) { MixStereoInterp(srce,dest,dest_offset,index,increment,todo); } else { MixMonoInterp(srce,dest,dest_offset,index,increment,todo); } } else { if (iWhichSampleMixFunc == 1) { MixStereoNormal(srce,dest,dest_offset,index,increment,todo); } else { MixMonoNormal(srce,dest,dest_offset,index,increment,todo); } } } public void VC_PlayStop() { } public boolean VC_Init() { return true; } public void VC_Exit() { } public void VC_VoiceSetVolume(short voice,short vol) { vinf[voice].vol=vol; } public void VC_VoiceSetFrequency(short voice,int frq) { vinf[voice].frq=frq; } public void VC_VoiceSetPanning(short voice,short pan) { vinf[voice].pan=pan; } public void VC_VoicePlay(short voice,short handle,int start,int size,int reppos,int repend,int flags) { if(start>=size) return; if((flags&(m_.MDriver.SF_LOOP)) != 0){ if(repend>size) repend=size; /* repend can't be bigger than size */ } vinf[voice].flags=flags; vinf[voice].handle=handle; vinf[voice].start=start; vinf[voice].size=size; vinf[voice].reppos=reppos; vinf[voice].repend=repend; vinf[voice].kick=true; } }