|
From: Tapio V. <aa...@us...> - 2010-03-15 10:12:05
|
Module: performous
Branch: lrc
Commit: 15836db6c38af9b4280a764dce8166768bf492d5
Author: Tapio Vierros <tap...@gm...>
Date: Thu Dec 31 02:20:10 2009 +0200
WIP LRC lyrics parser.
---
game/notes.cc | 2 +-
game/notes.hh | 2 +-
game/songparser-lrc.cc | 101 ++++++++++++++++++++++++++++++++++++++++++++++++
game/songparser.hh | 13 +++++-
4 files changed, 114 insertions(+), 4 deletions(-)
diff --git a/game/notes.cc b/game/notes.cc
index 7719f04..6fc70bc 100644
--- a/game/notes.cc
+++ b/game/notes.cc
@@ -69,7 +69,7 @@ double Note::scoreMultiplier(double error) const {
case FREESTYLE: power += 1.0; return 1.0;
case NORMAL: case SLIDE: max = 1.0; break;
case GOLDEN: max = 2.0; break;
- case SLEEP: case TAP: case HOLDBEGIN: case HOLDEND: case ROLL: case MINE: case LIFT: break;
+ case SLEEP: case KARAOKE: case TAP: case HOLDBEGIN: case HOLDEND: case ROLL: case MINE: case LIFT: break;
}
double accuracy = clamp(1.5 - error, 0.0, 1.0);
power += accuracy;
diff --git a/game/notes.hh b/game/notes.hh
index e80e584..330e079 100644
--- a/game/notes.hh
+++ b/game/notes.hh
@@ -75,7 +75,7 @@ struct Note {
/// how well the note was sung [0,1] (used for drawing a star)
mutable float accuracy;
/// note type
- enum Type { FREESTYLE = 'F', NORMAL = ':', GOLDEN = '*', SLIDE = '+', SLEEP = '-',
+ enum Type { FREESTYLE = 'F', NORMAL = ':', GOLDEN = '*', SLIDE = '+', SLEEP = '-', KARAOKE = '[',
TAP = '1', HOLDBEGIN = '2', HOLDEND = '3', ROLL = '4', MINE = 'M', LIFT = 'L'} type;
int note; ///< MIDI pitch of the note (at the end for slide notes)
int notePrev; ///< MIDI pitch of the previous note (should be same as note for everything but SLIDE)
diff --git a/game/songparser-lrc.cc b/game/songparser-lrc.cc
new file mode 100644
index 0000000..ce2e7b5
--- /dev/null
+++ b/game/songparser-lrc.cc
@@ -0,0 +1,101 @@
+#include "songparser.hh"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <algorithm>
+#include <stdexcept>
+
+/// @file
+/// Functions used for parsing the UltraStar TXT song format
+
+namespace {
+ void assign(int& var, std::string const& str) {
+ try {
+ var = boost::lexical_cast<int>(str);
+ } catch (...) {
+ throw std::runtime_error("\"" + str + "\" is not valid integer value");
+ }
+ }
+ void assign(double& var, std::string str) {
+ std::replace(str.begin(), str.end(), ',', '.'); // Fix decimal separators
+ try {
+ var = boost::lexical_cast<double>(str);
+ } catch (...) {
+ throw std::runtime_error("\"" + str + "\" is not valid floating point value");
+ }
+ }
+ void assign(bool& var, std::string const& str) {
+ if (str == "YES" || str == "yes" || str == "1") var = true;
+ else if (str == "NO" || str == "no" || str == "0") var = false;
+ else throw std::runtime_error("Invalid boolean value: " + str);
+ }
+ double getSeconds(std::string const& timestamp) {
+ std::string::size_type pos = timestamp.find(':');
+ if (pos == std::string::npos) throw std::runtime_error("Invalid format, should be [mm:ss.xx] lyrics");
+ std::string mins_st = boost::trim_copy(timestamp.substr(1, pos - 1));
+ std::string secs_st = boost::trim_copy(timestamp.substr(pos + 1));
+ int mins = 0; assign(mins, mins_st);
+ double secs = 0; assign(secs, secs_st);
+ return 60.0 * mins + secs;
+ }
+}
+
+bool SongParser::lrcCheck(std::vector<char> const& data) {
+ return data[0] == '[';
+}
+
+void SongParser::lrcParse() {
+ Song& s = m_song;
+ std::string line;
+ while (getline(line) && lrcParseField(line)) {}
+ //if (s.title.empty() || s.artist.empty()) throw std::runtime_error("Required header fields missing");
+ if (s.title.empty()) s.title = "LRC";
+ if (s.artist.empty()) s.artist = "LRC";
+ if (m_bpm != 0.0) addBPM(0, m_bpm);
+ while (lrcParseNote(line) && getline(line)) {}
+}
+
+bool SongParser::lrcParseField(std::string const& line) {
+ if (line.empty()) return true;
+ if (line[0] != '[') return false;
+ std::string::size_type pos = line.find(':');
+ if (pos == std::string::npos) throw std::runtime_error("Invalid format, should be [key:value]");
+ std::string key = boost::trim_copy(line.substr(1, pos - 1));
+ std::string value = boost::trim_copy(line.substr(pos + 1));
+ value = value.substr(0, line.size()-1); // Strip trailing ']'
+ if (value.empty()) return true;
+ if (key == "ti") m_song.title = value.substr(value.find_first_not_of(" "));
+ else if (key == "ar") m_song.artist = value.substr(value.find_first_not_of(" "));
+ else if (key == "by") m_song.creator = value.substr(value.find_first_not_of(" "));
+ //else if (key == "MP3") m_song.music["background"] = m_song.path + value;
+ else if (key == "offset") { assign(m_gap, value.substr(value.find_first_not_of(" :+"))); m_gap *= 1e-3; }
+ return true;
+}
+
+bool SongParser::lrcParseNote(std::string line) {
+ if (line.empty() || line == "\r") return true;
+ if (line[line.size() - 1] == '\r') line.erase(line.size() - 1);
+ if (line[0] != '[') return false;
+ //std::istringstream iss(line);
+
+ if (!m_song.notes.empty()) {
+ Note n; n.type = Note::SLEEP;
+ n.begin = m_song.notes.back().end;
+ n.end = n.begin;
+ m_song.notes.push_back(n);
+ }
+
+ Note n;
+ n.type = Note::KARAOKE;
+ std::string::size_type pos = line.find(']');
+ if (pos == std::string::npos) throw std::runtime_error("Invalid format, should be [mm:ss.xx] lyrics");
+ n.begin = getSeconds(line.substr(1,pos-2));
+ n.end = n.begin + 2;
+ n.notePrev = n.note;
+ n.syllable = boost::trim_copy(line.substr(pos + 1));
+
+ m_song.notes.push_back(n);
+
+ return true;
+}
+
diff --git a/game/songparser.hh b/game/songparser.hh
index 1e05a05..d07a1e9 100644
--- a/game/songparser.hh
+++ b/game/songparser.hh
@@ -22,7 +22,7 @@ class SongParser {
m_relativeShift(),
m_maxScore()
{
- enum { NONE, TXT, INI, SM } type = NONE;
+ enum { NONE, TXT, INI, SM, LRC } type = NONE;
// Read the file, determine the type and do some initial validation checks
{
std::ifstream f((s.path + s.filename).c_str(), std::ios::binary);
@@ -36,6 +36,7 @@ class SongParser {
if (smCheck(data)) type = SM;
else if (txtCheck(data)) type = TXT;
else if (iniCheck(data)) type = INI;
+ //else if (lrcCheck(data)) type = LRC;
else throw SongParserException("Does not look like a song file (wrong header)", 1, true);
m_ss.write(&data[0], size);
}
@@ -44,15 +45,17 @@ class SongParser {
if (type == TXT) txtParse();
if (type == INI) iniParse();
if (type == SM) smParse();
+ //if (type == LRC) lrcParse();
} catch (std::runtime_error& e) {
throw SongParserException(e.what(), m_linenum);
}
// In case no images/videos were specified, try to guess them
- if (m_song.cover.empty() || (m_song.background.empty() && m_song.video.empty())) {
+ if (m_song.cover.empty() || (m_song.background.empty() && m_song.video.empty()) || m_song.notes.empty()) {
boost::regex coverfile("((cover|album|label|\\[co\\])\\.(png|jpeg|jpg|svg|bmp|gif))$", boost::regex_constants::icase);
boost::regex backgroundfile("((background|bg||\\[bg\\])\\.(png|jpeg|jpg|svg|bmp|gif))$", boost::regex_constants::icase);
boost::regex videofile("(.*\\.(avi|mpg|mpeg|flv|mov|mp4))$", boost::regex_constants::icase);
+ boost::regex lyricsfile("(.*\\.(lrc))$", boost::regex_constants::icase);
boost::cmatch match;
for (boost::filesystem::directory_iterator dirIt(s.path), dirEnd; dirIt != dirEnd; ++dirIt) {
@@ -64,6 +67,8 @@ class SongParser {
m_song.background = name;
} else if (m_song.background.empty() && m_song.video.empty() && regex_match(name.c_str(), match, videofile)) {
m_song.video = name;
+ } else if (m_song.notes.empty() && regex_match(name.c_str(), match, lyricsfile)) {
+ //TODO
}
}
}
@@ -101,6 +106,10 @@ class SongParser {
void smParse();
bool smParseField(std::string line);
Notes smParseNotes(std::string line);
+ bool lrcCheck(std::vector<char> const& data);
+ void lrcParse();
+ bool lrcParseField(std::string const& line);
+ bool lrcParseNote(std::string line);
double m_prevtime;
unsigned int m_prevts;
unsigned int m_relativeShift;
|