1
#include <boost/lexical_cast.hpp>
2
#include <boost/regex.hpp>
4
bool compareSongs( CSong * left , CSong * right)
6
if( left->orderType != right->orderType )
7
fprintf(stderr,"Order mismatch\n");
10
switch(left->orderType) {
12
ordering1 = left->edition;
13
ordering2 = right->edition;
16
ordering1 = left->genre;
17
ordering2 = right->genre;
20
ordering1 = left->title;
21
ordering2 = right->title;
24
ordering1 = left->artist;
25
ordering2 = right->artist;
28
ordering1 = left->title;
29
ordering2 = right->title;
32
// VERY IMPORTANT, if equal compareSongs MUST return false
33
if(ordering1 == NULL && ordering2 == NULL)
34
return (left->index < right->index);
39
int cmp = strcmp(ordering1,ordering2);
45
return (left->index < right->index);
48
void CSong::parseFile( void )
51
snprintf(buff,256,"%s/%s",path,filename);
52
int relativeShift = 0;
54
FILE * fp = fopen(buff,"r");
55
while(fgets(buff,256,fp)) {
64
TNote * tmp = new TNote();
66
int len = strlen(buff);
67
char * syllable = new char[16];
70
tmp->type = TYPE_NOTE_FREESTYLE;
72
tmp->type = TYPE_NOTE_GOLDEN;
74
tmp->type = TYPE_NOTE_NORMAL;
76
if (buff[len-2] == '\r') len--;
77
buff[len-1] = '\0'; // Replace the \n or \r with a \0
78
sscanf(buff+1,"%d %d %d%n",&tmp->timestamp, &tmp->length , &tmp->note , &shift);
79
tmp->timestamp += relativeShift;
80
snprintf(syllable,16,"%s",buff+shift+2);
81
tmp->syllable = syllable;
82
// Avoid ":1 0 0" to mess noteMin
83
if( tmp->length == 0 ) {
84
delete[] tmp->syllable;
88
if( tmp->note <= noteMin )
90
if( tmp->note >= noteMax )
92
maxScore += tmp->length * tmp->type;
93
tmp->curMaxScore = maxScore;
98
TNote * tmp = new TNote();
101
tmp->type = TYPE_NOTE_SLEEP;
102
int nbInt = sscanf(buff+1,"%d %d",×tamp, &sleep_end);
103
tmp->timestamp = relativeShift + timestamp;
107
tmp->length = sleep_end - timestamp;
111
relativeShift += timestamp;
113
relativeShift += sleep_end;
116
tmp->curMaxScore = maxScore;
117
notes.push_back(tmp);
123
// Adjust negativ notes
125
unsigned int shift = (((noteMin*-1)%12)+1)*12;
129
for( unsigned int i = 0 ; i < notes.size() ; i++ )
130
notes[i]->note+=shift;
147
backgroundSurf = NULL;
156
bool CSongs::parseFile( CSong * tmp )
159
snprintf(buff,256,"%s/%s",tmp->path,tmp->filename);
160
FILE * fp = fopen(buff,"r");
162
fprintf(stderr , "Cannot open \"%s\"\n",buff);
165
while(fgets(buff,256,fp)) {
168
if(!strncmp("#TITLE:",buff,7)) {
169
int len = strlen(buff);
170
char * title = new char[len - 7];
172
if (buff[len-2] == '\r') len--;
173
buff[len-1]='\0'; // Replace the \n or \r with a \0
174
memcpy(title,buff+7,len - 7);
176
} else if(!strncmp("#EDITION:",buff,9)) {
177
int len = strlen(buff);
178
char * edition = new char[len - 9];
180
if (buff[len-2] == '\r') len--;
181
buff[len-1]='\0'; // Replace the \n or \r with a \0
182
memcpy(edition,buff+9,len - 9);
183
tmp->edition = edition;
184
} else if(!strncmp("#ARTIST:",buff,8)) {
185
int len = strlen(buff);
186
char * artist = new char[len - 8];
188
if (buff[len-2] == '\r') len--;
189
buff[len-1]='\0'; // Replace the \n or \r with a \0
190
memcpy(artist,buff+8,len - 8);
191
tmp->artist = artist;
192
} else if(!strncmp("#MP3:",buff,5)) {
193
int len = strlen(buff);
194
char * mp3 = new char[len - 5];
196
if (buff[len-2] == '\r') len--;
197
buff[len-1]='\0'; // Replace the \n or \r with a \0
198
memcpy(mp3,buff+5,len - 5);
200
} else if(!strncmp("#CREATOR:",buff,9)) {
201
int len = strlen(buff);
202
char * creator = new char[len - 9];
204
if (buff[len-2] == '\r') len--;
205
buff[len-1]='\0'; // Replace the \n or \r with a \0
206
memcpy(creator,buff+9,len - 9);
207
tmp->creator = creator;
208
} else if(!strncmp("#GAP:",buff,5)) {
209
sscanf(buff+5,"%f",&tmp->gap);
210
} else if(!strncmp("#BPM:",buff,5)) {
213
// We replace ',' by '.' for internationalization
214
char * comma = strchr(buff,',');
217
sscanf(buff+5,"%f",&bpm.bpm);
218
tmp->bpm.push_back(bpm);
219
} else if(!strncmp("#VIDEO:",buff,7)) {
220
int len = strlen(buff);
221
char * video = new char[len - 7];
223
if (buff[len-2] == '\r') len--;
224
buff[len-1]='\0'; // Replace the \n or \r with a \0
225
memcpy(video,buff+7,len - 7);
227
} else if(!strncmp("#BACKGROUND:",buff,12)) {
228
int len = strlen(buff);
229
char * background = new char[len - 12];
231
if (buff[len-2] == '\r') len--;
232
buff[len-1]='\0'; // Replace the \n or \r with a \0
233
memcpy(background,buff+12,len - 12);
234
tmp->background = background;
235
} else if(!strncmp("#VIDEOGAP:",buff,10)) {
236
sscanf(buff+10,"%f",&tmp->videoGap);
237
} else if(!strncmp("#RELATIVE:",buff,10)) {
238
if( buff[10] == 'y' || buff[10] == 'Y' )
239
tmp->relative = true;
241
tmp->relative = false;
242
} else if(!strncmp("#COVER:",buff,7)) {
243
int len = strlen(buff);
244
char * cover = new char[len - 7];
246
if (buff[len-2] == '\r') len--;
247
buff[len-1]='\0'; // Replace the \n or \r with a \0
248
memcpy(cover,buff+7,len - 7);
259
struct dirent* dirEntry;
262
char * songs_dir = CScreenManager::getSingletonPtr()->getSongsDirectory();
263
dir = opendir(songs_dir);
266
char * theme_path = new char[1024];
267
CScreenManager::getSingletonPtr()->getThemePathFile(theme_path,"no_cover.png");
268
SDL_RWops *rwop_nocover = SDL_RWFromFile(theme_path, "rb");
6
// math.h needed for C99 stuff
17
std::string MusicalScale::getNoteStr(double freq) const {
18
int id = getNoteId(freq);
19
if (id == -1) return std::string();
20
static const char * note[12] = {"C ","C#","D ","D#","E ","F ","F#","G ","G#","A ","A#","B "};
21
std::ostringstream oss;
22
// Acoustical Society of America Octave Designation System
23
//int octave = 2 + id / 12;
24
oss << note[id%12] << " " << int(round(freq)) << " Hz";
28
unsigned int MusicalScale::getNoteNum(int id) const {
31
return (n + (n > 4)) / 2;
34
bool MusicalScale::isSharp(int id) const {
35
if (id < 0) throw std::logic_error("MusicalScale::isSharp: Invalid note ID");
38
case 1: case 3: case 6: case 8: case 10: return true;
43
double MusicalScale::getNoteFreq(int id) const {
44
if (id == -1) return 0.0;
45
return m_baseFreq * pow(2.0, (id - m_baseId) / 12.0);
48
int MusicalScale::getNoteId(double freq) const {
49
double note = getNote(freq);
50
if (note >= 0.0 && note < 100.0) return int(note + 0.5);
54
double MusicalScale::getNote(double freq) const {
55
if (freq < 1.0) return std::numeric_limits<double>::quiet_NaN();
56
return m_baseId + 12.0 * log(freq / m_baseFreq) / log(2);
59
double MusicalScale::getNoteOffset(double freq) const {
60
double frac = freq / getNoteFreq(getNoteId(freq));
61
return 12.0 * log(frac) / log(2);
64
double Note::diff(double n) const { return remainder(n - note, 12.0); }
65
double Note::maxScore() const { return scoreMultiplier(0.0) * (end - begin); }
67
double Note::score(double n, double b, double e) const {
68
double len = std::min(e, end) - std::max(b, begin);
69
if (len <= 0.0 || !(n > 0.0)) return 0.0;
70
return scoreMultiplier(std::abs(diff(n))) * len;
73
double Note::scoreMultiplier(double error) const {
76
case FREESTYLE: return 1.0;
77
case NORMAL: max = 1.0; break;
78
case GOLDEN: max = 2.0; break;
81
return std::min(1.0, std::max(0.0, 1.5 - error)) * max;
87
struct Exception: public std::runtime_error {
88
Exception(std::string const& msg, unsigned int linenum):
89
runtime_error(msg), m_linenum(linenum) {}
90
unsigned int line() const { return m_linenum; }
92
unsigned int m_linenum;
96
m_f((s.path + s.filename).c_str()),
273
SDL_Surface * surface_nocover_tmp = NULL;
274
surface_nocover_tmp = IMG_LoadPNG_RW(rwop_nocover);
275
int w = CScreenManager::getSingletonPtr()->getWidth()*256/800;
276
int h = CScreenManager::getSingletonPtr()->getHeight()*256/600;
277
surface_nocover = zoomSurface(surface_nocover_tmp,(double)w/surface_nocover_tmp->w,(double)h/surface_nocover_tmp->h,1);
278
SDL_FreeSurface(surface_nocover_tmp);
279
SDL_FreeRW(rwop_nocover);
282
if( surface_nocover == NULL ) {
283
printf("IMG_LoadPNG_RW: %s\n", IMG_GetError());
290
while( (dirEntry = readdir(dir)) != NULL ) {
291
if( dirEntry->d_name[0] == '.' )
293
if( !strcmp(dirEntry->d_name,"CVS") )
296
char * path = new char[1024];
297
snprintf(path,1024,"%s%s",songs_dir,dirEntry->d_name);
299
if ( !S_ISDIR(info.st_mode) ) {
304
fprintf(stdout,"Song directory \"%s\" detected\n",dirEntry->d_name);
306
CSong * tmp = new CSong();
308
// Set default orderType to title
311
char * txt = new char[strlen(dirEntry->d_name)+4+1];
312
sprintf(txt,"%s.txt",dirEntry->d_name); // safe sprintf
314
if( !parseFile(tmp) ) {
320
char * cover = new char[strlen(dirEntry->d_name)+4+1];
321
sprintf(cover,"%s.png",dirEntry->d_name); // safe sprintf
325
snprintf(buff,1024,"%s/%s/%s",songs_dir,dirEntry->d_name,tmp->cover);
326
SDL_RWops *rwop = SDL_RWFromFile(buff, "rb");
327
SDL_Surface * coverSurface = NULL;
328
if(strstr(tmp->cover, ".png"))
329
coverSurface = IMG_LoadPNG_RW(rwop);
330
else if(strstr(tmp->cover, ".jpg"))
331
coverSurface = IMG_LoadJPG_RW(rwop);
334
if( coverSurface == NULL )
335
tmp->coverSurf = surface_nocover;
337
// Here we want to have cover of 256x256 in 800x600 and scale it if the resolution is different
338
int w = CScreenManager::getSingletonPtr()->getWidth()*256/800;
339
int h = CScreenManager::getSingletonPtr()->getHeight()*256/600;
340
tmp->coverSurf = zoomSurface(coverSurface,(double) w/coverSurface->w,(double) h/coverSurface->h,1);
341
SDL_FreeSurface(coverSurface);
345
if (tmp->background) {
346
SDL_Surface * backgroundSurface = NULL;
348
if(strstr(tmp->background, ".png")) {
349
snprintf(buff,1024,"%s/%s/%s",songs_dir,dirEntry->d_name,tmp->background);
350
rwop = SDL_RWFromFile(buff, "rb");
351
backgroundSurface = IMG_LoadPNG_RW(rwop);
353
} else if(strstr(tmp->background, ".jpg")) {
354
snprintf(buff,1024,"%s/%s/%s",songs_dir,dirEntry->d_name,tmp->background);
355
rwop = SDL_RWFromFile(buff, "rb");
356
backgroundSurface = IMG_LoadJPG_RW(rwop);
361
if( backgroundSurface == NULL )
362
tmp->backgroundSurf = NULL;
364
int w = CScreenManager::getSingletonPtr()->getWidth();
365
int h = CScreenManager::getSingletonPtr()->getHeight();
366
tmp->backgroundSurf = zoomSurface(backgroundSurface,(double)w/backgroundSurface->w,(double)h/backgroundSurface->h,1);
367
SDL_FreeSurface(backgroundSurface);
372
tmp->index = songs.size();
373
songs.push_back(tmp);
105
if (!m_f.is_open()) throw Exception("Could not open TXT file", 0);
108
while (getline(line) && parseField(line));
109
if (s.title.empty() || s.artist.empty()) throw std::runtime_error("Required header fields missing");
110
if (m_bpm != 0.0) addBPM(0, m_bpm);
111
while (parseNote(line) && getline(line));
112
} catch (std::runtime_error& e) {
113
throw Exception(e.what(), m_linenum);
115
if (s.notes.empty()) throw Exception("No notes", m_linenum);
116
// Workaround for the terminating : 1 0 0 line, written by some converters
117
if (s.notes.back().type != Note::SLEEP && s.notes.back().begin == s.notes.back().end) s.notes.pop_back();
118
// Adjust negative notes
119
if (m_song.noteMin <= 0) {
120
unsigned int shift = (1 - m_song.noteMin / 12) * 12;
121
m_song.noteMin += shift;
122
m_song.noteMax += shift;
123
for (Song::notes_t::iterator it = s.notes.begin(); it != s.notes.end(); ++it) it->note += shift;
125
m_song.m_scoreFactor = 1.0 / m_maxScore;
130
unsigned int m_linenum;
131
bool getline(std::string& line) { ++m_linenum; return std::getline(m_f, line); }
135
void assign(int& var, std::string const& str) {
136
var = boost::lexical_cast<int>(str);
138
void assign(double& var, std::string str) {
139
std::replace(str.begin(), str.end(), ',', '.'); // Fix decimal separators
140
var = boost::lexical_cast<double>(str);
142
void assign(bool& var, std::string const& str) {
143
if (str == "YES" || str == "yes" || str == "1") var = true;
144
else if (str == "NO" || str == "no" || str == "0") var = false;
145
else throw std::logic_error("Invalid boolean value: " + str);
147
bool parseField(std::string const& line) {
148
if (line.empty()) return true;
149
if (line[0] != '#') return false;
150
std::string::size_type pos = line.find(':');
151
if (pos == std::string::npos) throw std::runtime_error("Invalid format, should be #key=value");
152
std::string key = line.substr(1, pos - 1);
153
std::string::size_type pos2 = line.find_last_not_of(" \t\r");
154
std::string value = line.substr(pos + 1, pos2 - pos);
155
if (value.empty()) throw std::runtime_error("Value missing from key " + key);
156
if (key == "TITLE") m_song.title = value.substr(value.find_first_not_of(" :"));
157
else if (key == "ARTIST") m_song.artist = value.substr(value.find_first_not_of(" "));
158
else if (key == "EDITION") m_song.edition = value.substr(value.find_first_not_of(" "));
159
else if (key == "GENRE") m_song.genre = value.substr(value.find_first_not_of(" "));
160
else if (key == "CREATOR") m_song.creator = value.substr(value.find_first_not_of(" "));
161
else if (key == "COVER") m_song.cover = value;
162
else if (key == "MP3") m_song.mp3 = value;
163
else if (key == "VIDEO") m_song.video = value;
164
else if (key == "BACKGROUND") m_song.background = value;
165
else if (key == "START") assign(m_song.start, value);
166
else if (key == "VIDEOGAP") assign(m_song.videoGap, value);
167
else if (key == "RELATIVE") assign(m_relative, value);
168
else if (key == "GAP") { assign(m_gap, value); m_gap *= 1e-3; }
169
else if (key == "BPM") assign(m_bpm, value);
172
unsigned int m_prevts;
173
unsigned int m_relativeShift;
176
BPM(double _begin, unsigned int _ts, double bpm): begin(_begin), step(0.25 * 60.0 / bpm), ts(_ts) {}
177
double begin; // Time in seconds
178
double step; // Seconds per quarter note
181
typedef std::vector<BPM> bpms_t;
183
void addBPM(unsigned int ts, double bpm) {
184
if (!m_bpms.empty() && m_bpms.back().ts >= ts) throw std::runtime_error("Invalid BPM timestamp");
185
if (!(bpm >= 1.0 && bpm < 1e12)) throw std::runtime_error("Invalid BPM value");
186
m_bpms.push_back(BPM(tsTime(ts), ts, bpm));
188
bool parseNote(std::string line) {
189
if (line.empty() || line == "\r") return true;
190
if (line[0] == '#') throw std::runtime_error("Key found in the middle of notes");
191
if (line[line.size() - 1] == '\r') line.erase(line.size() - 1);
192
if (line[0] == 'E') return false;
193
std::istringstream iss(line);
194
if (line[0] == 'B') {
198
if (!(iss >> ts >> bpm)) throw std::runtime_error("Invalid BPM line format");
203
n.type = Note::Type(iss.get());
204
unsigned int ts = m_prevts;
207
case Note::FREESTYLE:
210
unsigned int length = 0;
211
if (!(iss >> ts >> length >> n.note)) throw std::runtime_error("Invalid note line format");
212
if (m_relative) ts += m_relativeShift;
213
if (iss.get() == ' ') std::getline(iss, n.syllable);
214
n.end = tsTime(ts + length);
220
if (!(iss >> ts >> end)) end = ts;
222
ts += m_relativeShift;
223
end += m_relativeShift;
224
m_relativeShift = end;
229
default: throw std::runtime_error("Unknown note type");
231
n.begin = tsTime(ts);
232
Song::notes_t& notes = m_song.notes;
233
if (m_relative && m_song.notes.empty()) m_relativeShift = ts;
234
if (notes.empty() && n.type == Note::SLEEP) throw std::runtime_error("Song cannot begin with sleep");
236
double prevtime = notes.empty() ? 0.0 : notes.back().end;
237
if (n.begin < prevtime) {
238
// Oh no, overlapping notes (b0rked file)
239
// Can't do this because too many songs are b0rked: throw std::runtime_error("Note overlaps with previous note");
240
if (notes.size() >= 1) {
241
Note& p = notes.back();
242
// Workaround for songs that use semi-random timestamps for sleep
243
if (p.type == Note::SLEEP) {
245
Note& p2 = notes[notes.size() - 2];
246
if (p2.end < n.begin) p.begin = p.end = n.begin;
248
// Can we just make the previous note shorter?
249
if (p.begin <= n.begin) p.end = n.begin;
250
else throw std::runtime_error("Note overlaps with earlier notes");
251
} else throw std::runtime_error("The first note has negative timestamp");
253
if (n.type != Note::SLEEP && n.end > n.begin) {
254
m_song.noteMin = std::min(m_song.noteMin, n.note);
255
m_song.noteMax = std::max(m_song.noteMax, n.note);
256
m_maxScore += n.maxScore();
261
double tsTime(unsigned int ts) const {
262
if (m_bpms.empty()) {
263
if (ts != 0) throw std::runtime_error("BPM data missing");
266
for (std::vector<BPM>::const_reverse_iterator it = m_bpms.rbegin(); it != m_bpms.rend(); ++it) {
267
if (it->ts <= ts) return it->begin + (ts - it->ts) * it->step;
269
throw std::logic_error("INTERNAL ERROR: BPM data invalid");
273
Song::Song(std::string const& _path, std::string const& _filename):
274
noteMin(std::numeric_limits<int>::max()),
275
noteMax(std::numeric_limits<int>::min()),
381
for(unsigned int i = 0; i < songs.size(); i++) {
382
delete[] songs[i]->mp3;
383
delete[] songs[i]->path;
384
delete[] songs[i]->filename;
385
delete[] songs[i]->cover;
386
delete[] songs[i]->title;
387
delete[] songs[i]->artist;
388
delete[] songs[i]->edition;
389
delete[] songs[i]->creator;
390
delete[] songs[i]->video;
391
delete[] songs[i]->background;
392
for(unsigned int j = 0; j < songs[i]->notes.size(); j++) {
393
delete[] songs[i]->notes[j]->syllable;
394
delete songs[i]->notes[j];
396
if( surface_nocover != songs[i]->coverSurf )
397
SDL_FreeSurface(songs[i]->coverSurf);
398
if( songs[i]->backgroundSurf )
399
SDL_FreeSurface(songs[i]->backgroundSurf);
292
m_scoreIt = notes.begin();
295
void Song::update(double time, double freq) {
296
if (time <= m_scoreTime) return;
297
while (m_scoreIt != notes.end()) {
298
if (freq > 0.0) m_score += m_scoreFactor * m_scoreIt->score(scale.getNote(freq), m_scoreTime, time);
299
if (time < m_scoreIt->end) break;
303
m_score = std::min(1.0, std::max(0.0, m_score));
306
void Song::loadCover() {
307
if (m_coverSurf || cover.empty()) return;
308
double width = CScreenManager::getSingletonPtr()->getWidth();
309
double height = CScreenManager::getSingletonPtr()->getHeight();
310
std::string file = path + cover;
311
std::string::size_type extpos = file.rfind('.');
312
std::string ext = (extpos == std::string::npos) ? "" : file.substr(extpos);
313
SDL_RWops* rwop = SDL_RWFromFile(file.c_str(), "rb");
314
SDL_Surface* surf = NULL;
315
if (ext == ".jpg" || ext == ".JPG" || ext == ".jpeg") surf = IMG_LoadJPG_RW(rwop);
316
else if (ext == ".png" || ext == ".PNG") surf = IMG_LoadPNG_RW(rwop);
317
else std::cout << "Cover image file " << file << " has unknown extension" << std::endl;
318
if (rwop) SDL_RWclose(rwop);
319
if (surf == NULL) m_coverSurf = NULL;
321
// Here we want to have cover of 256x256 in 800x600 and scale it if the resolution is different
322
double w = width * 256.0 / 800.0;
323
double h = height * 256.0 / 600.0;
324
m_coverSurf = zoomSurface(surf, w / surf->w, h / surf->h, 1);
325
SDL_FreeSurface(surf);
327
// Prevent trying to reload the same cover
328
if (!m_coverSurf) cover.clear();
331
void Song::loadBackground() {
332
if (m_backgroundSurf || background.empty()) return;
333
double width = CScreenManager::getSingletonPtr()->getWidth();
334
double height = CScreenManager::getSingletonPtr()->getHeight();
335
std::string file = path + background;
336
std::string::size_type extpos = file.rfind('.');
337
std::string ext = (extpos == std::string::npos) ? "" : file.substr(extpos);
338
SDL_RWops* rwop = SDL_RWFromFile(file.c_str(), "rb");
339
SDL_Surface* surf = NULL;
340
if (ext == ".jpg" || ext == ".JPG" || ext == ".jpeg") surf = IMG_LoadJPG_RW(rwop);
341
else if (ext == ".png" || ext == ".PNG") surf = IMG_LoadPNG_RW(rwop);
342
else std::cout << "Background image file " << file << " has unknown extension" << std::endl;
343
if (rwop) SDL_RWclose(rwop);
344
if (!surf) m_backgroundSurf = NULL;
346
m_backgroundSurf = zoomSurface(surf, width / surf->w, height / surf->h, 1);
347
SDL_FreeSurface(surf);
349
// Prevent trying to reload the same cover
350
if (!m_backgroundSurf) background.clear();
353
void Song::unloadCover() {
354
if (m_coverSurf) SDL_FreeSurface(m_coverSurf);
358
void Song::unloadBackground() {
359
if (m_backgroundSurf) SDL_FreeSurface(m_backgroundSurf);
360
m_backgroundSurf = NULL;
363
bool operator<(Song const& l, Song const& r) {
364
if (l.artist != r.artist) return l.artist < r.artist;
365
if (l.title != r.title) return l.title < r.title;
366
return l.filename < r.filename;
367
// If filenames are identical, too, the songs are considered the same.
370
Songs::Songs(std::set<std::string> const& songdirs): m_songdirs(songdirs), m_current(), m_order() {
371
std::string file = CScreenManager::getSingletonPtr()->getThemePathFile("no_cover.png");
372
SDL_RWops* rwop_nocover = SDL_RWFromFile(file.c_str(), "rb");
373
SDL_Surface* tmp = IMG_LoadPNG_RW(rwop_nocover);
374
if (!tmp) throw std::runtime_error("Could not load " + file);
375
double w = CScreenManager::getSingletonPtr()->getWidth() * 256.0 / 800.0;
376
double h = CScreenManager::getSingletonPtr()->getHeight() * 256.0 / 600.0;
377
surface_nocover = zoomSurface(tmp, w / tmp->w, h / tmp->h, 1);
378
SDL_FreeSurface(tmp);
379
if (rwop_nocover) SDL_RWclose(rwop_nocover);
380
if (surface_nocover == NULL) throw std::runtime_error("Cannot load " + file);
402
385
SDL_FreeSurface(surface_nocover);
406
CSong * CSongs::getSong( unsigned int i )
408
if( i >= songs.size())
414
void CSongs::sortByEdition( void )
417
for(unsigned int i = 0; i < songs.size(); i++)
418
songs[i]->orderType = 0;
419
sort(songs.begin(), songs.end(),compareSongs);
421
void CSongs::sortByGenre( void )
424
for(unsigned int i = 0; i < songs.size(); i++)
425
songs[i]->orderType = 1;
426
sort(songs.begin(), songs.end(),compareSongs);
428
void CSongs::sortByTitle( void )
431
for(unsigned int i = 0; i < songs.size(); i++)
432
songs[i]->orderType = 2;
433
sort(songs.begin(), songs.end(),compareSongs);
435
void CSongs::sortByArtist( void )
438
for(unsigned int i = 0; i < songs.size(); i++)
439
songs[i]->orderType = 3;
440
sort(songs.begin(), songs.end(),compareSongs);
388
void Songs::reload() {
390
for (std::set<std::string>::const_iterator it = m_songdirs.begin(); it != m_songdirs.end(); ++it) {
392
std::string pattern = *it + "*/*.[tT][xX][tT]";
393
std::cout << ">>> Scanning " << *it << ": " << std::flush;
394
glob (pattern.c_str(), GLOB_NOSORT, NULL, &_glob);
395
std::cout << _glob.gl_pathc << " song files found." << std::endl;
396
for (unsigned int i = 0 ; i < _glob.gl_pathc ; i++) {
397
char* txtfilename = strrchr(_glob.gl_pathv[i],'/'); txtfilename[0] = '\0'; txtfilename++;
398
std::string path = _glob.gl_pathv[i];
399
std::string::size_type pos = path.rfind('/');
400
if (pos < path.size() - 1) pos += 1; else pos = 0;
401
std::cout << "\r " << std::setiosflags(std::ios::left) << std::setw(70) << path.substr(pos, 70) << "\x1B[K" << std::flush;
403
songs.insert(new Song(path + "/", txtfilename));
404
} catch (SongParser::Exception& e) {
405
std::cout << "FAIL\n " << txtfilename;
406
if (e.line()) std::cout << " line " << e.line();
407
std::cout << ": " << e.what() << std::endl;
410
std::cout << "\r\x1B[K" << std::flush;
417
class Songs::RestoreSel {
421
RestoreSel(Songs& s): m_s(s), m_sel(s.empty() ? NULL : &s.current()) {}
425
filtered_t& f = m_s.m_filtered;
426
filtered_t::iterator it = std::find(f.begin(), f.end(), m_sel);
427
if (it != f.end()) m_s.m_current = it - f.begin();
431
void Songs::random() {
432
m_current = empty() ? 0 : std::rand() % m_filtered.size();
435
void Songs::setFilter(std::string const& val) {
436
RestoreSel restore(*this);
439
for (songlist_t::iterator it = m_songs.begin(); it != m_songs.end(); ++it) {
440
if (regex_search(it->str(), boost::regex(val))) filtered.push_back(&*it);
444
for (songlist_t::iterator it = m_songs.begin(); it != m_songs.end(); ++it) {
445
filtered.push_back(&*it);
448
m_filtered.swap(filtered);
453
std::string Song::* m_field;
455
CmpByField(std::string Song::* field): m_field(field) {}
456
bool operator()(Song const& left , Song const& right) {
457
if (left.*m_field == right.*m_field) return left < right;
458
return left.*m_field < right.*m_field;
460
bool operator()(Song const* left , Song const* right) {
461
return operator()(*left, *right);
465
static char const* order[] = {
473
static const int orders = sizeof(order) / sizeof(*order);
475
std::string Songs::sortDesc() const {
476
std::string str = order[m_order];
478
if (m_order == 2) str += " (" + current().edition + ")";
479
if (m_order == 3) str += " (" + current().genre + ")";
480
if (m_order == 4) str += " (" + current().path + ")";
485
void Songs::sortChange(int diff) {
486
m_order = (m_order + diff) % orders;
487
if (m_order < 0) m_order += orders;
488
RestoreSel restore(*this);
492
void Songs::sort_internal() {
494
case 0: std::sort(m_filtered.begin(), m_filtered.end(), CmpByField(&Song::title)); break;
495
case 1: std::sort(m_filtered.begin(), m_filtered.end(), CmpByField(&Song::artist)); break;
496
case 2: std::sort(m_filtered.begin(), m_filtered.end(), CmpByField(&Song::edition)); break;
497
case 3: std::sort(m_filtered.begin(), m_filtered.end(), CmpByField(&Song::genre)); break;
498
case 4: std::sort(m_filtered.begin(), m_filtered.end(), CmpByField(&Song::path)); break;
499
default: throw std::logic_error("Internal error: unknown sort order in Songs::sortChange");