|
From: Felix B. <fbe...@us...> - 2010-07-04 21:44:47
|
Module: performous
Branch: master
Commit: c783bf544100b9501830fd34c256ac9e30324b8a
Author: Felix Bertram <fl...@be...>
Date: Sun Jul 4 14:43:37 2010 -0700
Slowdown play
- uses pre-created time-stretched audio files
- minimal changes to code, uses warped time-base
---
game/audio.cc | 44 +++++++++++++++++++++++++++++++++++---------
game/audio.hh | 7 +++++--
game/guitargraph.cc | 4 +++-
game/main.cc | 2 ++
game/screen_sing.cc | 8 ++++++--
game/screen_sing.hh | 1 +
6 files changed, 52 insertions(+), 14 deletions(-)
diff --git a/game/audio.cc b/game/audio.cc
index ae4acfb..18824d8 100644
--- a/game/audio.cc
+++ b/game/audio.cc
@@ -5,6 +5,7 @@
#include <libda/fft.hpp> // For M_PI
#include <cmath>
#include <iostream>
+#include <boost/filesystem.hpp>
struct SampleStream {
SampleStream(boost::shared_ptr<FFmpeg> const& mpeg): m_mpeg(mpeg) {}
@@ -75,19 +76,39 @@ void Audio::play(Sample const& s, std::string const& volumeSetting) {
m_mixer.add(da::shared_ref(acc));
}
-void Audio::playMusic(std::map<std::string,std::string> const& filenames, bool preview, double fadeTime, double startPos) {
+void Audio::playMusic(std::map<std::string,std::string> const& filenames, bool preview, double fadeTime, double startPos, int speed) {
if (!isOpen()) return;
da::lock_holder l = m_mixer.lock();
fadeout(fadeTime);
boost::shared_ptr<da::chain> ch(new da::chain());
for(std::map<std::string,std::string>::const_iterator it = filenames.begin() ; it != filenames.end() ; ++it ) {
+ std::string f;
try {
- boost::shared_ptr<Stream> s(new Stream(it->second, m_rs.rate()));
+ f = it->second;
+ if (speed != 100) {
+ // instead of real-time time-stretching we use previously created files
+ // best effects have been achieved with SBSMS library
+ // as used by Audacity's Sliding Time Scale/ Pitch Shift plugin
+ std::string f2 = "-" + boost::lexical_cast<std::string>(speed) + ".ogg";
+ f.replace(f.find(".ogg"), 4, f2);
+ // std::cout << "loading time-stretched audio (" << f << ")" << std::endl;
+
+ if (!boost::filesystem::exists(f)) throw std::runtime_error("time-stretched file \"" + f +"\" not found");
+ }
+
+ boost::shared_ptr<Stream> s(new Stream(f, m_rs.rate()));
m_streams[it->first] = s;
ch->add(da::shared_ref(s));
s->seek(startPos);
} catch (std::runtime_error& e) {
- std::cerr << "Error loading " << it->second << " (" << e.what() << ")" << std::endl;
+ if (speed != 100) {
+ // in case time-stretching failed let's retry at normal speed
+ // TODO: we could also auto-create time-stretched files here
+ playMusic(filenames, preview, fadeTime, startPos, 100);
+ return;
+ }
+ // in case we are at normal speed we skip this file and try to continue
+ std::cerr << "Error loading " << f << " (" << e.what() << ")" << std::endl;
continue;
}
}
@@ -96,12 +117,14 @@ void Audio::playMusic(std::map<std::string,std::string> const& filenames, bool p
ch->add(boost::ref(m_volume));
m_mixer.fadein(da::shared_ref(ch), fadeTime, startPos);
if (!preview) pause(false);
+
+ m_speed = speed;
}
-void Audio::playMusic(std::string const& filename, bool preview, double fadeTime, double startPos) {
+void Audio::playMusic(std::string const& filename, bool preview, double fadeTime, double startPos, int speed) {
std::map<std::string,std::string> tmp;
tmp["unidentified"] = filename;
- playMusic(tmp, preview, fadeTime, startPos);
+ playMusic(tmp, preview, fadeTime, startPos, speed);
}
void Audio::stopMusic() {
@@ -120,12 +143,13 @@ void Audio::fadeout(double fadeTime) {
double Audio::getPosition() const {
da::lock_holder l = m_mixer.lock();
- return m_streams.empty() ? getNaN() : m_streams.begin()->second->pos();
+ // only the audio slows down; the game still uses original time base
+ return m_streams.empty() ? getNaN() : m_streams.begin()->second->pos() * m_speed/100.0;
}
double Audio::getLength() const {
da::lock_holder l = m_mixer.lock();
- return m_streams.empty() ? getNaN() : m_streams.begin()->second->duration();
+ return m_streams.empty() ? getNaN() : m_streams.begin()->second->duration() * m_speed/100.0;
}
bool Audio::isPlaying() const {
@@ -133,14 +157,16 @@ bool Audio::isPlaying() const {
return m_streams.empty() ? false : !m_streams.begin()->second->eof();
}
-void Audio::seek(double offset) {
+void Audio::seek(double offset2) {
+ double offset = offset2 * 100.0/m_speed;
da::lock_holder l = m_mixer.lock();
for(std::map<std::string,boost::shared_ptr<Stream> >::iterator it = m_streams.begin() ; it != m_streams.end() ; ++it)
it->second->seek(clamp(it->second->pos() + offset, 0.0, it->second->duration()));
pause(false);
}
-void Audio::seekPos(double pos) {
+void Audio::seekPos(double pos2) {
+ double pos = pos2 * 100.0/m_speed;
da::lock_holder l = m_mixer.lock();
for(std::map<std::string,boost::shared_ptr<Stream> >::iterator it = m_streams.begin() ; it != m_streams.end() ; ++it)
it->second->seek(pos);
diff --git a/game/audio.hh b/game/audio.hh
index e4f701f..d441cb1 100644
--- a/game/audio.hh
+++ b/game/audio.hh
@@ -94,9 +94,9 @@ class Audio {
* @param fadeTime time to fade
* @param startPos starting position
*/
- void playMusic(std::string const& filename, bool preview = false, double fadeTime = 0.5, double startPos = -0.2);
+ void playMusic(std::string const& filename, bool preview = false, double fadeTime = 0.5, double startPos = -0.2, int speed = 100);
/// plays a list of songs
- void playMusic(std::map<std::string,std::string> const& filenames, bool preview = false, double fadeTime = 0.5, double startPos = -0.2);
+ void playMusic(std::map<std::string,std::string> const& filenames, bool preview = false, double fadeTime = 0.5, double startPos = -0.2, int speed = 100);
/// plays a sample
void play(Sample const& s, std::string const& volumeSetting);
/// get pause status
@@ -107,6 +107,8 @@ class Audio {
void fadeout(double time = 1.0);
/** Get the length of the currently playing song, in seconds. **/
double getLength() const;
+ /// return current speed
+ int getSpeed() {return m_speed;}
/**
* This methods seek forward in the stream (backwards if
* argument is negative), and continues playing.
@@ -137,5 +139,6 @@ class Audio {
std::string m_volumeSetting;
da::mixer m_mixer;
std::map<std::string,boost::shared_ptr<Stream> > m_streams;
+ int m_speed;
};
diff --git a/game/guitargraph.cc b/game/guitargraph.cc
index 0be7a41..0fdf6aa 100644
--- a/game/guitargraph.cc
+++ b/game/guitargraph.cc
@@ -185,7 +185,9 @@ bool GuitarGraph::difficulty(Difficulty level) {
/// Core engine
void GuitarGraph::engine() {
double time = m_audio.getPosition();
- time -= config["audio/controller_delay"].f();
+ // need to compensate for speed as well. When playing at half speed,
+ // 1 second from getPosition is actually two seconds in real time
+ time -= config["audio/controller_delay"].f() * m_audio.getSpeed()/100.0;
// Handle key markers
if (!m_drums) {
for (int i = 0; i < m_pads; ++i) {
diff --git a/game/main.cc b/game/main.cc
index 91aed77..03da057 100644
--- a/game/main.cc
+++ b/game/main.cc
@@ -145,6 +145,8 @@ void audioSetup(Capture& capture, Audio& audio) {
if (channels != 2) throw std::runtime_error("Only stereo playback is supported, error in pdev=" + *it);
try {
audio.open(devstr, rate, frames);
+ // when we get here, we have successfully opened a device. let's use it!
+ break;
} catch (std::exception const& e) {
std::cerr << "Playback device pdev=" << *it << " failed and will be ignored:\n " << e.what() << std::endl;
}
diff --git a/game/screen_sing.cc b/game/screen_sing.cc
index dd822f3..0b52e85 100644
--- a/game/screen_sing.cc
+++ b/game/screen_sing.cc
@@ -31,6 +31,7 @@ namespace {
void ScreenSing::enter() {
//m_practmode = true; // un-comment this line to play with practice mode. temporary, of course!
+ m_speed = 75; // 75% slow-down; falls back to 100% if time-stretch files are not found. temporary, of course!
ScreenManager* sm = ScreenManager::getSingletonPtr();
sm->flashMessage(_("Loading song..."), 0.0, 1.0, 0.5);
sm->drawFlashMessage(); sm->window().swap(); // Make loading message show
@@ -85,6 +86,7 @@ void ScreenSing::enter() {
theme->timer.dimensions.screenTop(0.5 * m_progress->dimensions.h());
boost::ptr_vector<Analyzer>& analyzers = m_capture.analyzers();
m_layout_singer.reset(new LayoutSinger(m_song->vocals, m_database, theme));
+
// Load instrument and dance tracks
{
int type = 0; // 0 for dance, 1 for guitars, 2 for drums
@@ -108,7 +110,7 @@ void ScreenSing::enter() {
}
// Startup delay for instruments is longer than for singing only
double setup_delay = (m_instruments.empty() && m_dancers.empty() ? -1.0 : -8.0);
- m_audio.playMusic(m_song->music, false, 0.0, setup_delay);
+ m_audio.playMusic(m_song->music, false, 0.0, setup_delay, m_speed);
m_engine.reset(new Engine(m_audio, m_song->vocals, analyzers.begin(), analyzers.end(), m_database));
}
@@ -317,7 +319,9 @@ void ScreenSing::draw() {
// Get the time in the song
double length = m_audio.getLength();
double time = m_audio.getPosition();
- time -= config["audio/video_delay"].f();
+ // need to compensate for speed as well. When playing at half speed,
+ // 1 second from getPosition is actually two seconds in real time
+ time -= config["audio/video_delay"].f() * m_audio.getSpeed()/100.0;
double songPercent = clamp(time / length);
// Rendering starts
diff --git a/game/screen_sing.hh b/game/screen_sing.hh
index c743877..16cff8e 100644
--- a/game/screen_sing.hh
+++ b/game/screen_sing.hh
@@ -93,5 +93,6 @@ class ScreenSing: public Screen {
AnimValue m_quitTimer;
bool m_only_singers_alive;
bool m_practmode;
+ int m_speed; // speed in percent, 100 for normal, 50 for half
};
|