package audio.gme; // Band-limited sound synthesis buffer // http://www.slack.net/~ant/ /* Copyright (C) 2003-2007 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This module is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ final class BlipBuffer { static final boolean muchFaster = false; // speeds synthesis at a cost of quality BlipBuffer() { setVolume( 1.0 ); } // Sets sample rate of output and changes buffer length to msec public void setSampleRate( int rate, int msec ) { sampleRate = rate; buf = new int [(int) ((long) msec * rate / 1000) + 1024]; } // Sets input clock rate. Must be set after sample rate. public void setClockRate( int rate ) { clockRate_ = rate; factor = (int) (sampleRate / (float) clockRate_ * (1 << timeBits) + 0.5); } // Current clock rate public int clockRate() { return clockRate_; } // Removes all samples from buffer public void clear() { offset = 0; accum = 0; java.util.Arrays.fill( buf, 0, buf.length, 0 ); } // Sets overall volume, where 1.0 is normal public void setVolume( double v ) { final int shift = 15; final int round = 1 << (shift - 1); volume = (int) ((1 << shift) * v + 0.5) & ~1; if ( !muchFaster ) { // build new set of kernels int [] [] nk = new int [phaseCount + 1] []; for ( int i = nk.length; --i >= 0; ) nk [i] = new int [halfWidth]; // must be even since center kernel uses same half twice final int mul = volume; final int pc = phaseCount; for ( int p = 17; --p >= 0; ) { int remain = mul; for ( int i = 8; --i >= 0; ) { remain -= (nk [ p] [i] = (baseKernel [ p * halfWidth + i] * mul + round) >> shift); remain -= (nk [pc - p] [i] = (baseKernel [(pc - p) * halfWidth + i] * mul + round) >> shift); } nk [p] [7] += remain; // each pair of kernel halves must total mul } // replace kernel atomically kernel = nk; } } // Adds delta at given time public void addDelta( int time, int delta ) { final int [] buf = this.buf; final int phase = (time = time * factor + offset) >> (timeBits - phaseBits) & (phaseCount - 1); if ( muchFaster ) { final int right = ((delta *= volume) >> phaseBits) * phase; buf [time >>= timeBits] += delta - right; buf [time + 1 ] += right; } else { // TODO: use smaller kernel // left half int [] k = kernel [phase]; buf [time >>= timeBits] += k [0] * delta; buf [time + 1] += k [1] * delta; buf [time + 2] += k [2] * delta; buf [time + 3] += k [3] * delta; buf [time + 4] += k [4] * delta; buf [time + 5] += k [5] * delta; buf [time + 6] += k [6] * delta; buf [time + 7] += k [7] * delta; // right half (mirrored version of a left half) k = kernel [phaseCount - phase]; time += 8; buf [time ] += k [7] * delta; buf [time + 1] += k [6] * delta; buf [time + 2] += k [5] * delta; buf [time + 3] += k [4] * delta; buf [time + 4] += k [3] * delta; buf [time + 5] += k [2] * delta; buf [time + 6] += k [1] * delta; buf [time + 7] += k [0] * delta; } } // Number of samples that would be available at time public int countSamples( int time ) { int last_sample = (time * factor + offset) >> timeBits; int first_sample = offset >> timeBits; return last_sample - first_sample; } // Ends current time frame and makes samples available for reading public void endFrame( int time ) { offset += time * factor; assert samplesAvail() < buf.length; } // Number of samples available to be read public int samplesAvail() { return offset >> timeBits; } // Reads at most count samples into out at offset pos*2 (2 bytes per sample) // and returns number of samples actually read. public int readSamples( byte [] out, int pos, int count ) { final int avail = samplesAvail(); if ( count > avail ) count = avail; if ( count > 0 ) { // Integrate final int [] buf = this.buf; int accum = this.accum; pos <<= 1; int i = 0; do { int s = (accum += buf [i] - (accum >> 9)) >> 15; // clamp to 16 bits if ( (short) s != s ) s = (s >> 24) ^ 0x7FFF; // write as little-endian out [pos ] = (byte) (s >> 8); out [pos + 1] = (byte) s; pos += 2; } while ( ++i < count ); this.accum = accum; removeSamples( count ); } return count; } // internal static final int timeBits = 16; static final int phaseBits = (muchFaster ? 8 : 5); static final int phaseCount = 1 << phaseBits; static final int halfWidth = 8; static final int stepWidth = halfWidth * 2; int factor; int offset; int [] [] kernel; int accum; int [] buf; int sampleRate; int clockRate_; int volume; void removeSilence( int count ) { offset -= count << timeBits; assert samplesAvail() >= 0; } void removeSamples( int count ) { int remain = samplesAvail() - count + stepWidth; System.arraycopy( buf, count, buf, 0, remain ); java.util.Arrays.fill( buf, remain, remain + count, 0 ); removeSilence( count ); } // TODO: compute at run-time static final int [] baseKernel = { 10, -61, 284, -615, 1359,-1753, 5911,22498, 14, -71, 295, -616, 1314,-1615, 5259,22472, 17, -80, 304, -611, 1260,-1468, 4626,22402, 21, -88, 309, -603, 1200,-1313, 4015,22285, 23, -94, 313, -589, 1134,-1151, 3426,22122, 26, -100, 313, -572, 1063, -986, 2861,21915, 28, -104, 312, -550, 986, -818, 2322,21663, 30, -108, 308, -525, 906, -648, 1810,21369, 31, -110, 302, -497, 823, -478, 1326,21034, 33, -112, 295, -466, 737, -309, 871,20657, 34, -112, 285, -433, 649, -143, 446,20242, 34, -111, 274, -397, 561, 19, 51,19790, 34, -110, 261, -359, 472, 176, -313,19302, 35, -108, 247, -320, 383, 327, -646,18781, 34, -105, 232, -280, 296, 472, -948,18230, 34, -101, 216, -240, 210, 608,-1219,17651, 33, -97, 199, -199, 126, 736,-1459,17045, 32, -92, 182, -158, 45, 855,-1668,16413, 31, -86, 164, -117, -33, 964,-1847,15761, 30, -80, 145, -77, -107, 1063,-1996,15091, 28, -74, 127, -38, -177, 1151,-2117,14405, 26, -67, 108, 0, -243, 1228,-2211,13706, 24, -60, 90, 37, -304, 1294,-2277,12996, 22, -53, 72, 72, -360, 1349,-2318,12278, 20, -46, 54, 105, -410, 1392,-2334,11556, 18, -39, 37, 136, -455, 1425,-2327,10831, 15, -31, 21, 164, -495, 1446,-2298,10107, 13, -24, 5, 191, -529, 1456,-2249, 9385, 10, -17, -10, 215, -557, 1456,-2182, 8669, 8, -10, -24, 236, -580, 1446,-2096, 7962, 5, -3, -37, 255, -597, 1426,-1996, 7265, 3, 4, -50, 271, -608, 1397,-1881, 6580, 0, 10, -61, 284, -615, 1359,-1753, 5911, }; } // Stereo sound buffer with center channel final class StereoBuffer { private BlipBuffer [] bufs = new BlipBuffer [3]; // Same behavior as in BlipBuffer unless noted public StereoBuffer() { for ( int i = bufs.length; --i >= 0; ) bufs [i] = new BlipBuffer(); } public void setSampleRate( int rate, int msec ) { for ( int i = bufs.length; --i >= 0; ) bufs [i].setSampleRate( rate, msec ); } public void setClockRate( int rate ) { for ( int i = bufs.length; --i >= 0; ) bufs [i].setClockRate( rate ); } public int clockRate() { return bufs [0].clockRate(); } public int countSamples( int time ) { return bufs [0].countSamples( time ); } public void clear() { for ( int i = bufs.length; --i >= 0; ) bufs [i].clear(); } public void setVolume( double v ) { for ( int i = bufs.length; --i >= 0; ) bufs [i].setVolume( v ); } // The three channels that are mixed together // left output = left + center // right output = right + center public BlipBuffer center() { return bufs [2]; } public BlipBuffer left () { return bufs [0]; } public BlipBuffer right () { return bufs [1]; } public void endFrame( int time ) { for ( int i = bufs.length; --i >= 0; ) bufs [i].endFrame( time ); } public int samplesAvail() { return bufs [2].samplesAvail() << 1; } // Output is in stereo, so count must always be a multiple of 2 public int readSamples( byte [] out, int start, int count ) { assert (count & 1) == 0; final int avail = samplesAvail(); if ( count > avail ) count = avail; if ( (count >>= 1) > 0 ) { // TODO: optimize for mono case // calculate center in place final int [] mono = bufs [2].buf; { int accum = bufs [2].accum; int i = 0; do { mono [i] = (accum += mono [i] - (accum >> 9)); } while ( ++i < count ); bufs [2].accum = accum; } // calculate left and right for ( int ch = 2; --ch >= 0; ) { // add right and output final int [] buf = bufs [ch].buf; int accum = bufs [ch].accum; int pos = (start + ch) << 1; int i = 0; do { int s = ((accum += buf [i] - (accum >> 9)) + mono [i]) >> 15; // clamp to 16 bits if ( (short) s != s ) s = (s >> 24) ^ 0x7FFF; // write as big endian out [pos ] = (byte) (s >> 8); out [pos + 1] = (byte) s; pos += 4; } while ( ++i < count ); bufs [ch].accum = accum; } for ( int i = bufs.length; --i >= 0; ) bufs [i].removeSamples( count ); } return count << 1; } }