package jass.generators; import jass.engine.*; import jass.render.*; import jass.generators.*; import java.net.*; import java.awt.*; /** Provide a pitch shiftable object built of a set of recordings at particular frequencies. At any given freq. the sound is produced by pitch shifting nearby samples to this ranges and mixing them. If f is the disired freq. we find the two samples y1,2 with freq. f1 and f2 s.t. f1 < f < f2 . Both y1 and y2 are pitch shifted to f and then combined as y = (1-t)*y1_shifted + t*y2_shifted with 0<t<1 the obvious parameter. An optional overal multiplier of C(t) y can be provided which is of the form C = 1 + q*t*(1-t). Tweaking q for each sample interval can improve quality. The number of audio samples provided must be at least 2. @author Kees van den Doel (kvdoel@cs.ubc.ca) */ public class CrossfadeLoopBuffer extends Out { /** Array of buffers to loop */ protected float[][] loopBuffer; /** Buffer lengths */ protected int[] loopBufferLength; /** fGAin corrections (1 if audio files are ok) */ protected float[] gainCorrection; /** Playback volume. */ protected float volume = 1; /** Loop f (frequency) through buffer. */ protected float f = 1; protected float oldF = 1; // for in-buffer interpolation /** Natural loop frequencies of buffers */ protected float[] fb; /** Current fractional position [0 1] of pointer in buffer. */ protected float xLeft,xRight; // fractional offsets in frames /** Current integer position of pointer in buffer. */ protected int ixLeft,ixRight; // frame index (not sample index fro 2 channels) /** Current fractional f [0 1] of pointer in buffer per sample. */ protected float dxLeft,dxRight; /** Current integer f of pointer in buffer per sample. */ protected int dixLeft,dixRight; protected float aLeft; // gains of left contribution (right - 1-aLeft) //protected float[] sampleLevel; // |a| levels of buffers //protected float[] sampleCorrelation; // sampleCorrelation[i] = integral y[i]*y[i+1]/integral y[i] protected float levelCorrection; // corrects interpolated sum of sources for level difference protected float[] qFactor; // qFactor[i] adds a non-linear tweak to the crossfade between i and i+1 (see top for description) protected int iLeft=-1; // left frame index of currently active loopbuffer pair fb[] /** Sampling rate in Hertz of Out. */ public float srate; /** Sampling rate ratio, srateLoopBuffer/srate */ private float srateRatio = 1; /** Sampling rate in Hertz of loaded buffers (must all be the same). */ public float srateLoopBuffer; protected int nChannels; /** For derived classes @param bufferSize biffer size */ public CrossfadeLoopBuffer(int bufferSize) { super(bufferSize); // this is the internal buffer size } /** Construct buffers from named files. @param srate sampling rate in Hertz. @param bufferSize bufferSize of this Out @param fn Audio files names. @param natural frequencies of the audio files. */ public CrossfadeLoopBuffer(float srate,int bufferSize, String[] fn, float[] fb) { super(bufferSize); // this is the internal buffer size this.fb = fb; int nb = fb.length; gainCorrection = new float[nb]; qFactor = new float[nb]; AudioFileBuffer[] afBuffer = new AudioFileBuffer[nb]; loopBuffer = new float[nb][]; loopBufferLength = new int[nb]; for(int i=0;i<nb;i++) { afBuffer[i] = new AudioFileBuffer(fn[i]); loopBuffer[i] = afBuffer[i].buf; loopBufferLength[i] = loopBuffer[i].length; gainCorrection[i]=1; qFactor[i] = 1; } nChannels = afBuffer[0].nChannels; srateLoopBuffer = afBuffer[0].srate; this.srate = srate; srateRatio = srateLoopBuffer/srate; setF(f); } public float getQ(int k) { return qFactor[k]; } public void setQ(int k,float val) { qFactor[k] = val; } public int getNChannels() { return nChannels; } /** Set force magnitude. @param val Volume. */ public void setVolume(float val) { volume = val; } /** Set loopspeed. @param f Loop freq. */ public synchronized void setF(float f) { this.f = f; } // find index of left fb[] interval in which f lies private void findFInterval(float f) { int iLeftOld = iLeft; if(iLeft !=-1) { if(f>=fb[iLeft] && f<=fb[iLeft+1]) { return; } } int nb = fb.length; boolean notFound = true; if(f<fb[0]) { iLeft = 0; notFound = false; } else if(f>fb[nb-1]) { iLeft = nb-2; notFound = false; } // do binary search if (notFound) { iLeft = 0; } int iRight = nb-1; while(notFound) { int i = (iLeft+iRight)/2; if(f < fb[i]) { iRight = i; } else { iLeft = i; } if(iRight == iLeft+1) { notFound = false; } } if(iLeft==iLeftOld+1) { xLeft = xRight; ixLeft = ixRight; } else if(iLeft==iLeftOld-1) { xRight = xLeft; ixRight = ixLeft; } } protected void calcRates(float f) { findFInterval(f); float fLeft = fb[iLeft]; float fRight = fb[iLeft+1]; //float levelRatio = sampleLevel[iLeft+1]/sampleLevel[iLeft]; float rateLeft = (f/fLeft) * srateRatio; float rateRight = (f/fRight) * srateRatio; dixLeft = (int)rateLeft; dxLeft = rateLeft - dixLeft; dixRight = (int)rateRight; dxRight = rateRight - dixRight; aLeft = (fRight-f)/(fRight-fLeft); if(aLeft<0) { aLeft = 0; } else if(aLeft>1) { aLeft = 1; } levelCorrection = 1 + aLeft*(1-aLeft)*qFactor[iLeft]; } /** Get next sample value, interpolating in between sample points. */ int next_index_left = 0; int next_index_right = 0; protected float getNextSampleStereo(int k,int bufsz) { float val=0; float valLeft=0; float valRight=0; if (k % 2 == 0) { float f = (this.f - oldF) * (k + 1) / bufsz + oldF; calcRates(f); ixLeft += dixLeft; xLeft += dxLeft; if (xLeft > 1.f) { xLeft -= 1.f; ixLeft++; } while (ixLeft >= loopBufferLength[iLeft]/2) { ixLeft -= loopBufferLength[iLeft]/2; } if (ixLeft == loopBufferLength[iLeft]/2 - 1) { next_index_left = 0; } else { next_index_left = ixLeft + 1; } valLeft = (1.f - xLeft) * loopBuffer[iLeft][2*ixLeft] + xLeft * loopBuffer[iLeft][2*next_index_left]; ixRight += dixRight; xRight += dxRight; if (xRight > 1.f) { xRight -= 1.f; ixRight++; } while (ixRight >= loopBufferLength[iLeft + 1]/2) { ixRight -= loopBufferLength[iLeft + 1]/2; } if (ixRight == loopBufferLength[iLeft + 1]/2 - 1) { next_index_right = 0; } else { next_index_right = ixRight + 1; } valRight = (1.f - xRight) * loopBuffer[iLeft + 1][2*ixRight] + xRight * loopBuffer[iLeft + 1][2*next_index_right]; } else { try { valLeft = (1.f - xLeft) * loopBuffer[iLeft][2*ixLeft+1] + xLeft * loopBuffer[iLeft][2*next_index_left+1]; valRight = (1.f - xRight) * loopBuffer[iLeft + 1][2*ixRight+1] + xRight * loopBuffer[iLeft + 1][2*next_index_right+1]; } catch(ArrayIndexOutOfBoundsException e) { System.out.println("ixLeft="+ixLeft+" "+next_index_left+" "+ixRight+" "+next_index_right); } } val = (float) ((gainCorrection[iLeft] * valLeft * aLeft + gainCorrection[iLeft + 1] * valRight * (1 - aLeft)) * volume * levelCorrection); return val; } /** Get next sample value, interpolating in between sample points. */ protected float getNextSample(int k, int bufsz) { float f = (this.f - oldF) * (k + 1) / bufsz + oldF; calcRates(f); ixLeft += dixLeft; xLeft += dxLeft; if (xLeft > 1.f) { xLeft -= 1.f; ixLeft++; } while (ixLeft >= loopBufferLength[iLeft]) { ixLeft -= loopBufferLength[iLeft]; } int next_index_left; if (ixLeft == loopBufferLength[iLeft] - 1) { next_index_left = 0; } else { next_index_left = ixLeft + 1; } float valLeft = (1.f - xLeft) * loopBuffer[iLeft][ixLeft] + xLeft * loopBuffer[iLeft][next_index_left]; ixRight += dixRight; xRight += dxRight; if (xRight > 1.f) { xRight -= 1.f; ixRight++; } while (ixRight >= loopBufferLength[iLeft + 1]) { ixRight -= loopBufferLength[iLeft + 1]; } int next_index_right; if (ixRight == loopBufferLength[iLeft + 1] - 1) { next_index_right = 0; } else { next_index_right = ixRight + 1; } float valRight = (1.f - xRight) * loopBuffer[iLeft + 1][ixRight] + xRight * loopBuffer[iLeft + 1][next_index_right]; float val = (float) ((gainCorrection[iLeft] * valLeft * aLeft + gainCorrection[iLeft + 1] * valRight * (1 - aLeft)) * volume * levelCorrection); return val; } /** Compute the next buffer. */ public synchronized void computeBuffer() { int bufsz = getBufferSize(); for(int k=0;k<bufsz;k++) { if(nChannels==1) { buf[k] = getNextSample(k,bufsz); } else if(nChannels==2) { buf[k] = getNextSampleStereo(k,bufsz); } } oldF = this.f; } // to go away: public ControllerPanel getEditorPanel(String name) { return getControlPanel(); } public ControllerPanel getControlPanel() { final int nb = fb.length; double minV = -2; double maxV = 5; String[] names = new String[nb]; double[] val = new double[nb]; double[] min = new double[nb]; double[] max = new double[nb]; for(int i=0;i<nb-1;i++) { String f1 = String.format("%4.0f-",fb[i]); String f2 = String.format("%4.0f: ",fb[i+1]); names[i] = f1 + f2; val[i]=getQ(i); min[i]=minV; max[i]=maxV; } names[nb-1] = "RPM: "; val[nb-1] = fb[0]; max[nb-1] = fb[fb.length-1]; min[nb-1] = fb[0]; int nbuttons = 2; ControllerPanel controlPanel = new ControllerPanel(val.length, nbuttons) { public void onButton(int k) { switch (k) { case 0: { FileDialog fd = new FileDialog(new Frame(), "Save"); fd.setMode(FileDialog.SAVE); fd.setVisible(true); saveToFile(fd.getFile()); } break; case 1: { FileDialog fd = new FileDialog(new Frame(), "Load"); fd.setMode(FileDialog.LOAD); fd.setVisible(true); loadFromFile(fd.getFile()); } break; } } public void onSlider(int k) { float v = (float) this.val[k]; if(k<nb-1) { setQ(k, v); } else { setF(v); } } }; controlPanel.setSliders(val, min, max, names); controlPanel.setButtonNames(new String[]{"Save","Load"}); //controlPanel.setVisible(true); return controlPanel; } static final int MIXER_CH_CAB = 0; static final int MIXER_CH_ENG = 1; static final int MIXER_CH_INT = 2; static final int MIXER_CH_EXH = 3; static final int MIXER_CH_RFF = 4; static final int MIXER_CH_LEVEL = 5; static final int MIXER_CH_RPM = 6; public static void main(String args[]) throws Exception { float srate = 44100.f; int bufferSize = 256; int bufferSizeJavaSound = 1024 * 8; final SourcePlayer player; final Mixer mixer = new Mixer(bufferSize, MIXER_CH_RPM+1); final LevelMeter levelMeter = new LevelMeter(bufferSize,200f); levelMeter.addSource(mixer); final StickyControl stickyControlSpeed = new StickyControl(srate,bufferSize); stickyControlSpeed.setT(.1); //player = new SourcePlayer(bufferSize, bufferSizeJavaSound, srate,"Java Sound Audio Engine"); player = new SourcePlayer(bufferSize, bufferSizeJavaSound, srate); String[] fnCab = {"modelData/6430/stereo/cab800.wav","modelData/6430/stereo/cab1000.wav","modelData/6430/stereo/cab1200.wav","modelData/6430/stereo/cab1350.wav", "modelData/6430/stereo/cab1400.wav","modelData/6430/stereo/cab1600.wav", "modelData/6430/stereo/cab1800.wav","modelData/6430/stereo/cab2000.wav","modelData/6430/stereo/cab2200.wav","modelData/6430/stereo/cab2300.wav"}; String[] fnEng = {"modelData/6430/stereo/eng800.wav", "modelData/6430/stereo/eng1000.wav", "modelData/6430/stereo/eng1200.wav", "modelData/6430/stereo/eng1400.wav", "modelData/6430/stereo/eng1600.wav", "modelData/6430/stereo/eng1800.wav", "modelData/6430/stereo/eng2000.wav", "modelData/6430/stereo/eng2200.wav", "modelData/6430/stereo/eng2300.wav" }; String[] fnInt = {"modelData/6430/stereo/int800.wav", "modelData/6430/stereo/int1000.wav", "modelData/6430/stereo/int1200.wav", "modelData/6430/stereo/int1400.wav", "modelData/6430/stereo/int1600.wav", "modelData/6430/stereo/int1800.wav", "modelData/6430/stereo/int2000.wav", "modelData/6430/stereo/int2200.wav", "modelData/6430/stereo/int2300.wav" }; String[] fnExh = {"modelData/6430/stereo/exh850.wav", "modelData/6430/stereo/exh1000.wav", "modelData/6430/stereo/exh1200.wav", "modelData/6430/stereo/exh1400.wav", "modelData/6430/stereo/exh1600.wav", "modelData/6430/stereo/exh1800.wav", "modelData/6430/stereo/exh2000.wav", "modelData/6430/stereo/exh2200.wav", "modelData/6430/stereo/exh2300.wav" }; String[] fnRff = {"modelData/6430/stereo/rff800.wav", "modelData/6430/stereo/rff1000.wav", "modelData/6430/stereo/rff1200.wav", "modelData/6430/stereo/rff1400.wav", "modelData/6430/stereo/rff1600.wav", "modelData/6430/stereo/rff1800.wav", "modelData/6430/stereo/rff2000.wav", "modelData/6430/stereo/rff2200.wav", "modelData/6430/stereo/rff2300.wav" }; //String[] fn = {"sine440.wav","sine800.wav"}; float[] fbCab = {800,1000,1200,1350,1400,1600,1800,2000,2200,2300}; float[] fbEng = {800,1000,1200,1400,1600,1800,2000,2200,2300}; float[] fbInt = {800,1000,1200,1400,1600,1800,2000,2200,2300}; float[] fbExh = {850,1000,1200,1400,1600,1800,2000,2200,2300}; float[] fbRff = {800,1000,1200,1400,1600,1800,2000,2200,2300}; //float[] fb = {440,800}; final CrossfadeLoopBuffer cfCab = new CrossfadeLoopBuffer(srate,bufferSize,fnCab,fbCab); final CrossfadeLoopBuffer cfEng = new CrossfadeLoopBuffer(srate,bufferSize,fnEng,fbEng); final CrossfadeLoopBuffer cfInt = new CrossfadeLoopBuffer(srate,bufferSize,fnInt,fbInt); final CrossfadeLoopBuffer cfExh = new CrossfadeLoopBuffer(srate,bufferSize,fnExh,fbExh); final CrossfadeLoopBuffer cfRff = new CrossfadeLoopBuffer(srate,bufferSize,fnRff,fbRff); //final LoopBuffer lb = new LoopBuffer(srate,bufferSize,"sine440.wav"); mixer.addSource(cfCab); mixer.addSource(cfEng); mixer.addSource(cfInt); mixer.addSource(cfExh); mixer.addSource(cfRff); mixer.addSource(levelMeter); mixer.addSource(stickyControlSpeed); mixer.setGain(MIXER_CH_CAB, 0); mixer.setGain(MIXER_CH_ENG, 0); mixer.setGain(MIXER_CH_INT, 1); mixer.setGain(MIXER_CH_EXH, 0); mixer.setGain(MIXER_CH_RFF, 0); player.addSource(mixer); player.AGCOff(); player.setVolume(1f); player.setNChannels(cfCab.getNChannels()); System.out.println("ch="+cfCab.getNChannels()); // Add control panel double freq = 800; stickyControlSpeed.setX(freq); String[] names = {"rpm ","cab ","eng ","int ","exh ","rff "}; double[] val = {freq, 0, 0, 1, 0, 0}; double[] min = {800, 0, 0, 0, 0, 0}; double[] max = {2300, 1, 1, 1, 1, 1}; int nbuttons = 1; Controller a_controlPanel = new Controller(new java.awt.Frame("Demo"), false, val.length, nbuttons) { public void onButton(int k) { switch (k) { case 0: player.resetAGC(); break; } } public void onSlider(int k) { switch (k) { case 0: stickyControlSpeed.setXc(this.val[k]); break; case 1: mixer.setGain(MIXER_CH_CAB, (float) this.val[k]); break; case 2: mixer.setGain(MIXER_CH_ENG, (float) this.val[k]); break; case 3: mixer.setGain(MIXER_CH_INT, (float) this.val[k]); break; case 4: mixer.setGain(MIXER_CH_EXH, (float) this.val[k]); break; case 5: mixer.setGain(MIXER_CH_RFF, (float) this.val[k]); break; } } }; a_controlPanel.addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent e) { System.out.println("Close handler called"); player.stopPlaying(); try { Thread.sleep(500); } catch (Exception e3) { } System.exit(0); } }); a_controlPanel.setSliders(val, min, max, names); a_controlPanel.setButtonNames(new String[]{"Reset"}); a_controlPanel.setVisible(true); player.start(); new Thread() { public void run() { int i = 0; boolean shouldStop = false; int interval = 100; while (!shouldStop) { double v = stickyControlSpeed.getX(); cfCab.setF((float) stickyControlSpeed.getX()); cfEng.setF((float) stickyControlSpeed.getX()); cfInt.setF((float) stickyControlSpeed.getX()); cfExh.setF((float) stickyControlSpeed.getX()); cfRff.setF((float) stickyControlSpeed.getX()); //System.out.println(levelMeter.getDBLevel()); try { Thread.sleep(interval); } catch (Exception e) { } } //System.out.println("thread exit"); } }.start(); } }