package audio.gme; // Music emulator interface // 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 */ class MusicEmu { // enables performance-intensive assertions protected static final boolean debug = false; public MusicEmu() { trackCount_ = 0; trackEnded_ = true; currentTrack_ = 0; } // Requests change of sample rate and returns sample rate used, which might be different public final int setSampleRate( int rate ) { return sampleRate_ = setSampleRate_( rate ); } public final int sampleRate() { return sampleRate_; } // Loads music file into emulator. Might keep reference to data. public void loadFile( byte [] data ) { trackEnded_ = true; currentTrack_ = 0; currentTime_ = 0; trackCount_ = loadFile_( data ); } // Number of tracks public final int trackCount() { return trackCount_; } // Starts track, where 0 is first track public void startTrack( int track ) { if ( track < 0 || track > trackCount_ ) error( "Invalid track" ); trackEnded_ = false; currentTrack_ = track; currentTime_ = 0; fadeStart = 0x40000000; // far into the future fadeStep = 1; } // Currently started track public final int currentTrack() { return currentTrack_; } // Generates at most count samples into out and returns // number of samples written. If track has ended, fills // buffer with silence. public final int play( byte [] out, int count ) { if ( !trackEnded_ ) { count = play_( out, count ); if ( (currentTime_ += count >> 1) > fadeStart ) applyFade( out, count ); } else { java.util.Arrays.fill( out, 0, count * 2, (byte) 0 ); } return count; } // Sets fade start and length, in seconds. Must be set after call to startTrack(). public final void setFade( int start, int length ) { fadeStart = sampleRate_ * start; fadeStep = sampleRate_ * length / (fadeBlockSize * fadeShift); if ( fadeStep < 1 ) fadeStep = 1; } // Number of seconds current track has been played public final int currentTime() { return currentTime_ / sampleRate_; } // True if track has reached end or setFade()'s fade has finished public final boolean trackEnded() { return trackEnded_; } // protected // must be defined in derived class protected int setSampleRate_( int rate ) { return rate; } protected int loadFile_( byte [] in ) { return 0; } protected int play_( byte [] out, int count ) { return 0; } // Reports error string as exception protected void error( String str ) { throw new Error( str ); } // Sets end of track flag and stops emulating file protected void setTrackEnded() { trackEnded_ = true; } // Stops track and notes emulation error protected void logError() { if ( !trackEnded_ ) { trackEnded_ = true; System.out.println( "emulation error" ); } } // Reads 16 bit little endian int starting at in [pos] protected static int getLE16( byte [] in, int pos ) { return (in [pos ] & 0xFF) | (in [pos + 1] & 0xFF) << 8; } // Reads 32 bit little endian int starting at in [pos] protected static int getLE32( byte [] in, int pos ) { return (in [pos ] & 0xFF) | (in [pos + 1] & 0xFF) << 8 | (in [pos + 2] & 0xFF) << 16 | (in [pos + 3] & 0xFF) << 24; } // True if first bytes of file match expected string protected static boolean isHeader( byte [] header, String expected ) { for ( int i = expected.length(); --i >= 0; ) if ( (byte) expected.charAt( i ) != header [i] ) return false; return true; } // private int sampleRate_; int trackCount_; int currentTrack_; int currentTime_; int fadeStart; int fadeStep; boolean trackEnded_; static final int fadeBlockSize = 512; static final int fadeShift = 8; // fade ends with gain at 1.0 / (1 << fadeShift) // unit / pow( 2.0, (double) x / step ) static int int_log( int x, int step, int unit ) { int shift = x / step; int fraction = (x - shift * step) * unit / step; return ((unit - fraction) + (fraction >> 1)) >> shift; } static final int gainShift = 14; static final int gainUnit = 1 << gainShift; // Scales count big-endian 16-bit samples from io [pos*2] by gain/gainUnit static void scaleSamples( byte [] io, int pos, int count, int gain ) { pos <<= 1; count = (count << 1) + pos; do { int s; io [pos + 1] = (byte) (s = ((io [pos] << 8 | (io [pos + 1] & 0xFF)) * gain) >> gainShift); io [pos ] = (byte) (s >> 8); } while ( (pos += 2) < count ); } private void applyFade( byte [] io, int count ) { // Apply successively smaller gains based on time since fade start for ( int i = 0; i < count; i += fadeBlockSize ) { // logarithmic progression int gain = int_log( (currentTime_ + i - fadeStart) / fadeBlockSize, fadeStep, gainUnit ); if ( gain < (gainUnit >> fadeShift) ) setTrackEnded(); int n = count - i; if ( n > fadeBlockSize ) n = fadeBlockSize; scaleSamples( io, i, n, gain ); } } }