generic beat sync
This commit is contained in:
+207
-25
@@ -2,62 +2,244 @@
|
||||
#include "RageSoundManager.h"
|
||||
#include "RageSounds.h"
|
||||
#include "RageSound.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageUtil.h"
|
||||
#include "GameState.h"
|
||||
#include "TimingData.h"
|
||||
#include "MsdFile.h"
|
||||
#include "NotesLoaderSM.h"
|
||||
|
||||
RageSounds *SOUND = NULL;
|
||||
|
||||
/*
|
||||
* When playing music, automatically search for an SM file for timing data. If one is
|
||||
* found, automatically handle GAMESTATE->m_fSongBeat, etc.
|
||||
*
|
||||
* modf(GAMESTATE->m_fSongBeat) should always be continuously moving from 0 to 1. To do
|
||||
* this, wait before starting a sound until the fractional portion of the beat will be
|
||||
* the same.
|
||||
*
|
||||
* If PlayMusic(length_sec) is set, peek at the beat, and extend the length so we'll be
|
||||
* on an integral beat 0 when we loop. (XXX: should we increase fade_len, too?
|
||||
* That would cause the extra pad time to be silence.)
|
||||
*/
|
||||
static RageSound *g_Music;
|
||||
static TimingData g_Timing;
|
||||
static bool g_HasTiming;
|
||||
static bool g_UpdatingTimer;
|
||||
|
||||
RageSounds::RageSounds()
|
||||
{
|
||||
/* Init RageSoundMan first: */
|
||||
ASSERT( SOUNDMAN );
|
||||
music = new RageSound;
|
||||
|
||||
g_Music = new RageSound;
|
||||
g_Timing = TimingData();
|
||||
g_HasTiming = false;
|
||||
g_UpdatingTimer = false;
|
||||
}
|
||||
|
||||
RageSounds::~RageSounds()
|
||||
{
|
||||
delete music;
|
||||
delete g_Music;
|
||||
}
|
||||
|
||||
struct MusicToPlay
|
||||
{
|
||||
CString file;
|
||||
TimingData timing;
|
||||
bool HasTiming;
|
||||
CString timing_file;
|
||||
bool force_loop;
|
||||
float start_sec, length_sec, fade_len;
|
||||
};
|
||||
static MusicToPlay g_MusicToPlay;
|
||||
|
||||
|
||||
void StartQueuedMusic()
|
||||
{
|
||||
if( g_MusicToPlay.file.size() == 0 )
|
||||
return; /* nothing to do */
|
||||
|
||||
/* Go. */
|
||||
LOG->Trace("Start sound \"%s\"", g_MusicToPlay.file.c_str() );
|
||||
|
||||
g_HasTiming = g_MusicToPlay.HasTiming;
|
||||
g_Timing = g_MusicToPlay.timing;
|
||||
|
||||
g_Music->Load( g_MusicToPlay.file, false );
|
||||
|
||||
if( g_MusicToPlay.force_loop )
|
||||
g_Music->SetStopMode( RageSound::M_LOOP );
|
||||
|
||||
if( g_MusicToPlay.start_sec == -1 )
|
||||
g_Music->SetStartSeconds();
|
||||
else
|
||||
g_Music->SetStartSeconds( g_MusicToPlay.start_sec );
|
||||
|
||||
if( g_MusicToPlay.length_sec == -1 )
|
||||
g_Music->SetLengthSeconds();
|
||||
else
|
||||
g_Music->SetLengthSeconds( g_MusicToPlay.length_sec );
|
||||
|
||||
g_Music->SetFadeLength( g_MusicToPlay.fade_len );
|
||||
g_Music->SetPositionSeconds();
|
||||
g_Music->StartPlaying();
|
||||
|
||||
g_MusicToPlay.file = ""; /* done */
|
||||
}
|
||||
|
||||
|
||||
/* If we have a music file queued, try to start it. If we're doing a beat update,
|
||||
* fOldBeat is the beat before the update and fNewBeat is the beat after the update;
|
||||
* we'll only start the sound if the fractional part of the beat we'll be moving to
|
||||
* lies between them. */
|
||||
void TryToStartQueuedMusic( float fOldBeat, float fNewBeat )
|
||||
{
|
||||
if( g_MusicToPlay.file.size() == 0 )
|
||||
return; /* nothing to do */
|
||||
|
||||
if( g_UpdatingTimer )
|
||||
{
|
||||
float fOldBeatFraction = fmodfp( fOldBeat,1 );
|
||||
float fNewBeatFraction = fmodfp( fNewBeat,1 );
|
||||
|
||||
/* The beat that the new sound will start on. See if this lies within the beat
|
||||
* range of this update. */
|
||||
float fStartBeatFraction = fmodfp( g_MusicToPlay.timing.GetBeatFromElapsedTime(g_MusicToPlay.start_sec), 1 );
|
||||
if( fNewBeatFraction < fOldBeatFraction )
|
||||
fNewBeatFraction += 1.0f; /* unwrap */
|
||||
if( fStartBeatFraction < fOldBeatFraction )
|
||||
fStartBeatFraction += 1.0f; /* unwrap */
|
||||
|
||||
if( !( fOldBeatFraction <= fStartBeatFraction && fStartBeatFraction <= fNewBeatFraction ) )
|
||||
return;
|
||||
}
|
||||
|
||||
StartQueuedMusic();
|
||||
}
|
||||
|
||||
void RageSounds::Update( float fDeltaTime )
|
||||
{
|
||||
const float fOldBeat = GAMESTATE->m_fSongBeat;
|
||||
if( g_UpdatingTimer )
|
||||
{
|
||||
if( g_Music->IsPlaying() )
|
||||
GAMESTATE->UpdateSongPosition( g_Music->GetPositionSeconds(), g_Timing );
|
||||
else
|
||||
{
|
||||
/* There's no song playing. Fake it. */
|
||||
GAMESTATE->UpdateSongPosition( GAMESTATE->m_fMusicSeconds + fDeltaTime, g_Timing );
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have a sound queued to play, try to start it. */
|
||||
TryToStartQueuedMusic( fOldBeat, GAMESTATE->m_fSongBeat );
|
||||
}
|
||||
|
||||
|
||||
CString RageSounds::GetMusicPath() const
|
||||
{
|
||||
return music->GetLoadedFilePath();
|
||||
return g_Music->GetLoadedFilePath();
|
||||
}
|
||||
|
||||
void RageSounds::PlayMusic(CString file, bool force_loop, float start_sec, float length_sec, float fade_len)
|
||||
void RageSounds::PlayMusic( const CString &file, const CString &timing_file, bool force_loop, float start_sec, float length_sec, float fade_len )
|
||||
{
|
||||
// LOG->Trace("play '%s' (current '%s')", file.c_str(), music->GetLoadedFilePath().c_str());
|
||||
if(music->IsPlaying())
|
||||
// LOG->Trace("play '%s' (current '%s')", file.c_str(), g_Music->GetLoadedFilePath().c_str());
|
||||
if( g_Music->IsPlaying() )
|
||||
{
|
||||
if( !music->GetLoadedFilePath().CompareNoCase(file) )
|
||||
if( !g_Music->GetLoadedFilePath().CompareNoCase(file) )
|
||||
return; // do nothing
|
||||
|
||||
music->StopPlaying();
|
||||
g_Music->StopPlaying();
|
||||
}
|
||||
|
||||
g_Music->Unload();
|
||||
|
||||
g_MusicToPlay.file = file;
|
||||
g_MusicToPlay.timing_file = timing_file;
|
||||
g_MusicToPlay.force_loop = force_loop;
|
||||
g_MusicToPlay.start_sec = start_sec;
|
||||
g_MusicToPlay.length_sec = length_sec;
|
||||
g_MusicToPlay.fade_len = fade_len;
|
||||
|
||||
/* If file is blank, just stop. */
|
||||
if(file.empty())
|
||||
{
|
||||
music->Unload();
|
||||
if( file.empty() )
|
||||
return;
|
||||
|
||||
/* If no timing file was specified, look for one in the same place as the music file. */
|
||||
CString real_timing_file = timing_file;
|
||||
if( real_timing_file == "" )
|
||||
real_timing_file = SetExtension( file, "sm" );
|
||||
|
||||
/* See if we can find timing data. */
|
||||
g_MusicToPlay.HasTiming = false;
|
||||
g_MusicToPlay.timing = TimingData();
|
||||
|
||||
if( IsAFile(real_timing_file) )
|
||||
{
|
||||
LOG->Trace("Found '%s'", real_timing_file.c_str());
|
||||
MsdFile msd;
|
||||
bool bResult = msd.ReadFile( real_timing_file );
|
||||
if( !bResult )
|
||||
LOG->Warn( "Couldn't load %s", real_timing_file.c_str(), msd.GetError().c_str() );
|
||||
else
|
||||
{
|
||||
SMLoader::LoadTimingFromSMFile( msd, g_MusicToPlay.timing );
|
||||
g_MusicToPlay.HasTiming = true;
|
||||
}
|
||||
}
|
||||
|
||||
music->Load( file, false );
|
||||
if( force_loop )
|
||||
music->SetStopMode( RageSound::M_LOOP );
|
||||
if( g_MusicToPlay.HasTiming && force_loop )
|
||||
{
|
||||
float fStartBeat = g_MusicToPlay.timing.GetBeatFromElapsedTime( g_MusicToPlay.start_sec );
|
||||
float fEndSec = start_sec + length_sec;
|
||||
float fEndBeat = g_MusicToPlay.timing.GetBeatFromElapsedTime( fEndSec );
|
||||
|
||||
const float fStartBeatFraction = fmodfp( fStartBeat, 1 );
|
||||
const float fEndBeatFraction = fmodfp( fEndBeat, 1 );
|
||||
|
||||
if(start_sec == -1)
|
||||
music->SetStartSeconds();
|
||||
else
|
||||
music->SetStartSeconds(start_sec);
|
||||
float fBeatDifference = fStartBeatFraction - fEndBeatFraction;
|
||||
if( fBeatDifference < 0 )
|
||||
fBeatDifference += 1.0f; /* unwrap */
|
||||
|
||||
if(length_sec == -1)
|
||||
music->SetLengthSeconds();
|
||||
else
|
||||
music->SetLengthSeconds(length_sec);
|
||||
fEndBeat += fBeatDifference;
|
||||
|
||||
music->SetFadeLength(fade_len);
|
||||
music->SetPositionSeconds();
|
||||
music->StartPlaying();
|
||||
float fRealEndSec = g_MusicToPlay.timing.GetElapsedTimeFromBeat( fEndBeat );
|
||||
g_MusicToPlay.length_sec = fRealEndSec - g_MusicToPlay.start_sec;
|
||||
}
|
||||
|
||||
bool StartImmediately = false;
|
||||
if( !g_MusicToPlay.HasTiming )
|
||||
{
|
||||
/* This song has no real timing data. Fake m_fSongBeat as a 120 BPM song,
|
||||
* and don't do any forced-waiting for future songs. */
|
||||
g_MusicToPlay.timing.AddBPMSegment( BPMSegment(0,120) );
|
||||
|
||||
/* The offset is arbitrary. Change it so the beat will line up to where we are
|
||||
* now, so we don't have to delay. */
|
||||
float fDestBeat = fmodfp( GAMESTATE->m_fSongBeat, 1 );
|
||||
float fTime = g_MusicToPlay.timing.GetElapsedTimeFromBeat( fDestBeat );
|
||||
|
||||
g_MusicToPlay.timing.m_fBeat0OffsetInSeconds = fTime;
|
||||
|
||||
StartImmediately = true;
|
||||
}
|
||||
|
||||
/* If we have an active timer, try to start on the next update. Otherwise,
|
||||
* start now. */
|
||||
if( !g_HasTiming && !g_UpdatingTimer )
|
||||
StartImmediately = true;
|
||||
|
||||
if( StartImmediately )
|
||||
StartQueuedMusic();
|
||||
if( g_MusicToPlay.file != "" )
|
||||
LOG->Trace("Queued sound \"%s\"", g_MusicToPlay.file.c_str() );
|
||||
}
|
||||
|
||||
void RageSounds::HandleSongTimer( bool on )
|
||||
{
|
||||
g_UpdatingTimer = on;
|
||||
}
|
||||
|
||||
void RageSounds::PlayOnce( CString sPath )
|
||||
|
||||
@@ -3,16 +3,15 @@
|
||||
/* This contains all of the generally useful user-level SOUNDMAN calls, as well
|
||||
* as some other simple helper stuff. Don't include RageSoundManager for normal
|
||||
* use; it's only for drivers and sound code. This file should be very lightweight. */
|
||||
class RageSound;
|
||||
class RageSounds
|
||||
{
|
||||
RageSound *music;
|
||||
|
||||
public:
|
||||
RageSounds();
|
||||
~RageSounds();
|
||||
void Update( float fDeltaTime );
|
||||
|
||||
void PlayMusic(CString file, bool force_loop = false, float start_sec = -1, float length_sec = -1, float fade_len = 0);
|
||||
void PlayMusic( const CString &file, bool force_loop = false, float start_sec = -1, float length_sec = -1, float fade_len = 0 ) { PlayMusic( file, "", force_loop, start_sec, length_sec, fade_len ); }
|
||||
void PlayMusic( const CString &file, const CString &timing_file, bool force_loop = false, float start_sec = -1, float length_sec = -1, float fade_len = 0 );
|
||||
void StopMusic() { PlayMusic(""); }
|
||||
CString GetMusicPath() const;
|
||||
|
||||
@@ -20,6 +19,7 @@ public:
|
||||
void PlayOnceFromDir( CString sDir );
|
||||
|
||||
float GetPlayLatency() const;
|
||||
void HandleSongTimer( bool on=true );
|
||||
};
|
||||
|
||||
extern RageSounds *SOUND;
|
||||
|
||||
@@ -239,6 +239,9 @@ ScreenEdit::ScreenEdit( CString sName ) : Screen( sName )
|
||||
{
|
||||
LOG->Trace( "ScreenEdit::ScreenEdit()" );
|
||||
|
||||
/* We do this ourself. */
|
||||
SOUND->HandleSongTimer( false );
|
||||
|
||||
TICK_EARLY_SECONDS.Refresh();
|
||||
|
||||
// set both players to joined so the credit message doesn't show
|
||||
|
||||
@@ -97,6 +97,9 @@ ScreenGameplay::ScreenGameplay( CString sName, bool bDemonstration ) : Screen("S
|
||||
else
|
||||
LIGHTSMAN->SetLightMode( LIGHTMODE_GAMEPLAY );
|
||||
|
||||
/* We do this ourself. */
|
||||
SOUND->HandleSongTimer( false );
|
||||
|
||||
SECONDS_BETWEEN_COMMENTS.Refresh();
|
||||
G_TICK_EARLY_SECONDS.Refresh();
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "NoteFieldPositioning.h"
|
||||
#include "GameManager.h"
|
||||
#include "NotesLoaderSM.h"
|
||||
#include "RageSounds.h"
|
||||
|
||||
#define SECONDS_TO_SHOW THEME->GetMetricF("ScreenHowToPlay","SecondsToShow")
|
||||
#define STEPFILE THEME->GetMetric ("ScreenHowToPlay","Stepfile")
|
||||
@@ -78,6 +79,9 @@ static bool HaveAllCharAnimations()
|
||||
|
||||
ScreenHowToPlay::ScreenHowToPlay( CString sName ) : ScreenAttract( sName )
|
||||
{
|
||||
/* We do this ourself. */
|
||||
SOUND->HandleSongTimer( false );
|
||||
|
||||
m_iPerfects = 0;
|
||||
m_iNumPerfects = NUM_PERFECTS;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "GameState.h"
|
||||
#include "RageException.h"
|
||||
#include "RageTimer.h"
|
||||
#include "RageSounds.h"
|
||||
#include "ThemeManager.h"
|
||||
#include "RageDisplay.h"
|
||||
#include "Screen.h"
|
||||
@@ -472,6 +473,10 @@ void ScreenManager::Input( const DeviceInput& DeviceI, const InputEventType type
|
||||
|
||||
Screen* ScreenManager::MakeNewScreen( CString sClassName )
|
||||
{
|
||||
/* By default, RageSounds handles the song timer. When we change screens, reset this;
|
||||
* screens turn this off in their ctor if they handle timers themselves (gameplay, edit). */
|
||||
SOUND->HandleSongTimer( true );
|
||||
|
||||
Screen *ret = Screen::Create( sClassName );
|
||||
|
||||
/* Loading probably took a little while. Let's reset stats. This prevents us
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "PrefsManager.h"
|
||||
#include "RageLog.h"
|
||||
#include "InputMapper.h"
|
||||
#include "InputQueue.h"
|
||||
#include "AnnouncerManager.h"
|
||||
#include "InputMapper.h"
|
||||
#include "GameState.h"
|
||||
@@ -616,6 +615,7 @@ void ScreenSelectMusic::Update( float fDeltaTime )
|
||||
{
|
||||
SOUND->PlayMusic(
|
||||
m_sSampleMusicToPlay,
|
||||
m_sSampleMusicTimingData,
|
||||
true,
|
||||
m_fSampleStartSeconds,
|
||||
m_fSampleLengthSeconds,
|
||||
@@ -1096,7 +1096,7 @@ void ScreenSelectMusic::AfterMusicChange()
|
||||
|
||||
m_Banner.SetMovingFast( !!m_MusicWheel.IsMoving() );
|
||||
|
||||
CString SampleMusicToPlay;
|
||||
CString SampleMusicToPlay, SampleMusicTimingData;
|
||||
vector<CString> m_Artists, m_AltArtists;
|
||||
|
||||
m_MachineRank.SetText( "" );
|
||||
@@ -1135,6 +1135,7 @@ void ScreenSelectMusic::AfterMusicChange()
|
||||
case TYPE_SONG:
|
||||
{
|
||||
SampleMusicToPlay = pSong->GetMusicPath();
|
||||
SampleMusicTimingData = pSong->GetCacheFilePath();
|
||||
m_fSampleStartSeconds = pSong->m_fMusicSampleStartSeconds;
|
||||
m_fSampleLengthSeconds = pSong->m_fMusicSampleLengthSeconds;
|
||||
|
||||
@@ -1282,6 +1283,7 @@ void ScreenSelectMusic::AfterMusicChange()
|
||||
{
|
||||
SOUND->StopMusic();
|
||||
m_sSampleMusicToPlay = SampleMusicToPlay;
|
||||
m_sSampleMusicTimingData = SampleMusicTimingData;
|
||||
m_fPlaySampleCountdown = SAMPLE_MUSIC_DELAY;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ protected:
|
||||
bool m_bGoToOptions;
|
||||
Sprite m_sprOptionsMessage;
|
||||
float m_fPlaySampleCountdown;
|
||||
CString m_sSampleMusicToPlay;
|
||||
CString m_sSampleMusicToPlay, m_sSampleMusicTimingData;
|
||||
float m_fSampleStartSeconds, m_fSampleLengthSeconds;
|
||||
bool m_bAllowOptionsMenu, m_bAllowOptionsMenuRepeat;
|
||||
|
||||
|
||||
@@ -1262,6 +1262,7 @@ static void GameLoop()
|
||||
}
|
||||
|
||||
DISPLAY->Update( fDeltaTime );
|
||||
SOUND->Update( fDeltaTime );
|
||||
TEXTUREMAN->Update( fDeltaTime );
|
||||
GAMESTATE->Update( fDeltaTime );
|
||||
SCREENMAN->Update( fDeltaTime );
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
TimingData::TimingData()
|
||||
{
|
||||
m_fBeat0OffsetInSeconds = 0;
|
||||
|
||||
}
|
||||
|
||||
static int CompareBPMSegments(const BPMSegment &seg1, const BPMSegment &seg2)
|
||||
|
||||
Reference in New Issue
Block a user