diff src/console/Music_Emu.cxx @ 316:fb513e10174e trunk

[svn] - merge libconsole-blargg into mainline libconsole: + obsoletes plugins-ugly:sapplug
author nenolod
date Thu, 30 Nov 2006 19:54:33 -0800
parents 3da1b8942b8b
children 986f098da058
line wrap: on
line diff
--- a/src/console/Music_Emu.cxx	Wed Nov 29 14:42:11 2006 -0800
+++ b/src/console/Music_Emu.cxx	Thu Nov 30 19:54:33 2006 -0800
@@ -1,8 +1,8 @@
-
-// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/
+// Game_Music_Emu 0.5.1. http://www.slack.net/~ant/
 
 #include "Music_Emu.h"
 
+#include "Multi_Buffer.h"
 #include <string.h>
 
 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
@@ -11,74 +11,400 @@
 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 */
+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 */
 
-#include BLARGG_SOURCE_BEGIN
+#include "blargg_source.h"
+
+int const stereo = 2; // number of channels for stereo
+int const silence_max = 6; // seconds
+int const silence_threshold = 0x10;
+long const fade_block_size = 512;
+int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift)
 
 Music_Emu::equalizer_t const Music_Emu::tv_eq = { -8.0, 180 };
 
+void Music_Emu::clear_track_vars()
+{
+	current_track_   = -1;
+	out_time         = 0;
+	emu_time         = 0;
+	emu_track_ended_ = true;
+	track_ended_     = true;
+	fade_start       = LONG_MAX / 2 + 1;
+	fade_step        = 1;
+	silence_time     = 0;
+	silence_count    = 0;
+	buf_remain       = 0;
+	warning(); // clear warning
+}
+
+void Music_Emu::unload()
+{
+	voice_count_ = 0;
+	clear_track_vars();
+	Gme_File::unload();
+}
+
 Music_Emu::Music_Emu()
 {
-	equalizer_.treble = -1.0;
-	equalizer_.bass = 60;
+	effects_buffer = 0;
+	
 	sample_rate_ = 0;
-	voice_count_ = 0;
-	mute_mask_ = 0;
-	track_count_ = 0;
-	error_count_ = 0;
-	track_ended_ = false;
+	mute_mask_   = 0;
+	tempo_       = 1.0;
+	gain_        = 1.0;
+	
+	// defaults
+	max_initial_silence = 2;
+	silence_lookahead   = 3;
+	ignore_silence_     = false;
+	equalizer_.treble   = -1.0;
+	equalizer_.bass     = 60;
+	
+	static const char* const names [] = {
+		"Voice 1", "Voice 2", "Voice 3", "Voice 4",
+		"Voice 5", "Voice 6", "Voice 7", "Voice 8"
+	};
+	set_voice_names( names );
+	Music_Emu::unload(); // non-virtual
+}
+
+Music_Emu::~Music_Emu() { delete effects_buffer; }
+
+blargg_err_t Music_Emu::set_sample_rate( long rate )
+{
+	require( !sample_rate() ); // sample rate can't be changed once set
+	RETURN_ERR( set_sample_rate_( rate ) );
+	RETURN_ERR( buf.resize( buf_size ) );
+	sample_rate_ = rate;
+	return 0;
+}
+
+void Music_Emu::pre_load()
+{
+	require( sample_rate() ); // set_sample_rate() must be called before loading a file
+	Gme_File::pre_load();
+}
+
+void Music_Emu::set_equalizer( equalizer_t const& eq )
+{
+	equalizer_ = eq;
+	set_equalizer_( eq );
+}
+
+void Music_Emu::mute_voice( int index, bool mute )
+{
+	require( (unsigned) index < (unsigned) voice_count() );
+	int bit = 1 << index;
+	int mask = mute_mask_ | bit;
+	if ( !mute )
+		mask ^= bit;
+	mute_voices( mask );
+}
+
+void Music_Emu::mute_voices( int mask )
+{
+	require( sample_rate() ); // sample rate must be set first
+	mute_mask_ = mask;
+	mute_voices_( mask );
+}
+
+void Music_Emu::set_tempo( double t )
+{
+	require( sample_rate() ); // sample rate must be set first
+	double const min = 0.02;
+	double const max = 4.00;
+	if ( t < min ) t = min;
+	if ( t > max ) t = max;
+	tempo_ = t;
+	set_tempo_( t );
+}
+
+void Music_Emu::post_load_()
+{
+	set_tempo( tempo_ );
+	remute_voices();
 }
 
-Music_Emu::~Music_Emu()
+blargg_err_t Music_Emu::start_track( int track )
 {
+	clear_track_vars();
+	
+	int remapped = track;
+	RETURN_ERR( remap_track( &remapped ) );
+	current_track_ = track;
+	RETURN_ERR( start_track_( remapped ) );
+	
+	emu_track_ended_ = false;
+	track_ended_     = false;
+	
+	if ( !ignore_silence_ )
+	{
+		// play until non-silence or end of track
+		for ( long end = max_initial_silence * stereo * sample_rate(); emu_time < end; )
+		{
+			fill_buf();
+			if ( buf_remain | emu_track_ended_ )
+				break;
+		}
+		
+		emu_time      = buf_remain;
+		out_time      = 0;
+		silence_time  = 0;
+		silence_count = 0;
+	}
+	return track_ended() ? warning() : 0;
+}
+
+void Music_Emu::end_track_if_error( blargg_err_t err )
+{
+	if ( err )
+	{
+		emu_track_ended_ = true;
+		set_warning( err );
+	}
 }
 
-blargg_err_t Music_Emu::load_file( const char* path )
+// Tell/Seek
+
+blargg_long Music_Emu::msec_to_samples( blargg_long msec ) const
+{
+	blargg_long sec = msec / 1000;
+	msec -= sec * 1000;
+	return (sec * sample_rate() + msec * sample_rate() / 1000) * stereo;
+}
+
+long Music_Emu::tell() const
 {
-	Std_File_Reader in;
-	BLARGG_RETURN_ERR( in.open( path ) );
-	return load( in );
+	blargg_long rate = sample_rate() * stereo;
+	blargg_long sec = out_time / rate;
+	return sec * 1000 + (out_time - sec * rate) * 1000 / rate;
+}
+
+blargg_err_t Music_Emu::seek( long msec )
+{
+	blargg_long time = msec_to_samples( msec );
+	if ( time < out_time )
+		RETURN_ERR( start_track( current_track_ ) );
+	return skip( time - out_time );
 }
 
-void Music_Emu::skip( long count )
+blargg_err_t Music_Emu::skip( long count )
 {
-	const int buf_size = 1024;
-	sample_t buf [buf_size];
+	require( current_track() >= 0 ); // start_track() must have been called already
+	out_time += count;
 	
+	// remove from silence and buf first
+	{
+		long n = min( count, silence_count );
+		silence_count -= n;
+		count -= n;
+		
+		n = min( count, buf_remain );
+		buf_remain -= n;
+		count -= n;
+	}
+		
+	if ( count && !emu_track_ended_ )
+	{
+		emu_time += count;
+		end_track_if_error( skip_( count ) );
+	}
+	
+	if ( !(silence_count | buf_remain) ) // caught up to emulator, so update track ended
+		track_ended_ |= emu_track_ended_;
+	
+	return 0;
+}
+
+blargg_err_t Music_Emu::skip_( long count )
+{
+	// for long skip, mute sound
 	const long threshold = 30000;
 	if ( count > threshold )
 	{
 		int saved_mute = mute_mask_;
 		mute_voices( ~0 );
 		
-		while ( count > threshold / 2 )
+		while ( count > threshold / 2 && !emu_track_ended_ )
 		{
-			play( buf_size, buf );
+			RETURN_ERR( play_( buf_size, buf.begin() ) );
 			count -= buf_size;
 		}
 		
 		mute_voices( saved_mute );
 	}
 	
-	while ( count )
+	while ( count && !emu_track_ended_ )
 	{
-		int n = buf_size;
+		long n = buf_size;
 		if ( n > count )
 			n = count;
 		count -= n;
-		play( n, buf );
+		RETURN_ERR( play_( n, buf.begin() ) );
+	}
+	return 0;
+}
+
+// Fading
+
+void Music_Emu::set_fade( long start_msec, long length_msec )
+{
+	fade_step = sample_rate() * length_msec / (fade_block_size * fade_shift * 1000 / stereo);
+	fade_start = msec_to_samples( start_msec );
+}
+
+// unit / pow( 2.0, (double) x / step )
+static int int_log( blargg_long x, int step, int unit )
+{
+	int shift = x / step;
+	int fraction = (x - shift * step) * unit / step;
+	return ((unit - fraction) + (fraction >> 1)) >> shift;
+}
+
+void Music_Emu::handle_fade( long out_count, sample_t* out )
+{
+	for ( int i = 0; i < out_count; i += fade_block_size )
+	{
+		int const shift = 14;
+		int const unit = 1 << shift;
+		int gain = int_log( (out_time + i - fade_start) / fade_block_size,
+				fade_step, unit );
+		if ( gain < (unit >> fade_shift) )
+			track_ended_ = emu_track_ended_ = true;
+		
+		sample_t* io = &out [i];
+		for ( int count = min( fade_block_size, out_count - i ); count; --count )
+		{
+			*io = sample_t ((*io * gain) >> shift);
+			++io;
+		}
 	}
 }
 
-const char** Music_Emu::voice_names() const
+// Silence detection
+
+void Music_Emu::emu_play( long count, sample_t* out )
+{
+	check( current_track_ >= 0 );
+	emu_time += count;
+	if ( current_track_ >= 0 && !emu_track_ended_ )
+		end_track_if_error( play_( count, out ) );
+	else
+		memset( out, 0, count * sizeof *out );
+}
+
+// number of consecutive silent samples at end
+static long count_silence( Music_Emu::sample_t* begin, long size )
 {
-	static const char* names [] = {
-		"Voice 1", "Voice 2", "Voice 3", "Voice 4",
-		"Voice 5", "Voice 6", "Voice 7", "Voice 8"
-	};
-	return names;
+	Music_Emu::sample_t first = *begin;
+	*begin = silence_threshold; // sentinel
+	Music_Emu::sample_t* p = begin + size;
+	while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { }
+	*begin = first;
+	return size - (p - begin);
+}
+
+// fill internal buffer and check it for silence
+void Music_Emu::fill_buf()
+{
+	assert( !buf_remain );
+	if ( !emu_track_ended_ )
+	{
+		emu_play( buf_size, buf.begin() );
+		long silence = count_silence( buf.begin(), buf_size );
+		if ( silence < buf_size )
+		{
+			silence_time = emu_time - silence;
+			buf_remain   = buf_size;
+			return;
+		}
+	}
+	silence_count += buf_size;
 }
 
+blargg_err_t Music_Emu::play( long out_count, sample_t* out )
+{
+	if ( track_ended_ )
+	{
+		memset( out, 0, out_count * sizeof *out );
+	}
+	else
+	{
+		require( current_track() >= 0 );
+		require( out_count % stereo == 0 );
+		
+		assert( emu_time >= out_time );
+		
+		// prints nifty graph of how far ahead we are when searching for silence
+		//dprintf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" );
+		
+		long pos = 0;
+		if ( silence_count )
+		{
+			// during a run of silence, run emulator at >=2x speed so it gets ahead
+			long ahead_time = silence_lookahead * (out_time + out_count - silence_time) + silence_time;
+			while ( emu_time < ahead_time && !(buf_remain | emu_track_ended_) )
+				fill_buf();
+			
+			// fill with silence
+			pos = min( silence_count, out_count );
+			memset( out, 0, pos * sizeof *out );
+			silence_count -= pos;
+			
+			if ( emu_time - silence_time > silence_max * stereo * sample_rate() )
+			{
+				track_ended_  = emu_track_ended_ = true;
+				silence_count = 0;
+				buf_remain    = 0;
+			}
+		}
+		
+		if ( buf_remain )
+		{
+			// empty silence buf
+			long n = min( buf_remain, out_count - pos );
+			memcpy( &out [pos], buf.begin() + (buf_size - buf_remain), n * sizeof *out );
+			buf_remain -= n;
+			pos += n;
+		}
+		
+		// generate remaining samples normally
+		long remain = out_count - pos;
+		if ( remain )
+		{
+			emu_play( remain, out + pos );
+			track_ended_ |= emu_track_ended_;
+			
+			if ( !ignore_silence_ || out_time > fade_start )
+			{
+				// check end for a new run of silence
+				long silence = count_silence( out + pos, remain );
+				if ( silence < remain )
+					silence_time = emu_time - silence;
+				
+				if ( emu_time - silence_time >= buf_size )
+					fill_buf(); // cause silence detection on next play()
+			}
+		}
+		
+		if ( out_time > fade_start )
+			handle_fade( out_count, out );
+	}
+	out_time += out_count;
+	return 0;
+}
+
+// Gme_Info_
+
+blargg_err_t Gme_Info_::set_sample_rate_( long )            { return 0; }
+void         Gme_Info_::pre_load()                          { Gme_File::pre_load(); } // skip Music_Emu
+void         Gme_Info_::post_load_()                        { Gme_File::post_load_(); } // skip Music_Emu
+void         Gme_Info_::set_equalizer_( equalizer_t const& ){ check( false ); }
+void         Gme_Info_::mute_voices_( int )                 { check( false ); }
+void         Gme_Info_::set_tempo_( double )                { }
+blargg_err_t Gme_Info_::start_track_( int )                 { return "Use full emulator for playback"; }
+blargg_err_t Gme_Info_::play_( long, sample_t* )            { return "Use full emulator for playback"; }