~ubuntu-branches/ubuntu/maverick/ultrastar-ng/maverick

« back to all changes in this revision

Viewing changes to src/songs.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Miriam Ruiz, Miriam Ruiz, Mario Bonino, Jon Dowland, Ansgar Burchardt
  • Date: 2008-06-07 16:43:18 UTC
  • mfrom: (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080607164318-4cnzizck1tp8mrwp
Tags: 0.2.1-1
[ Miriam Ruiz ]
* New Upstream Release (Closes: #453132)
* Removed unneeded patches
* Added packages to build deps:
  + libtool
  + portaudio19-dev | portaudio-dev
  + libboost-dev, libboost-thread-dev, libboost-serialization-dev
  + libboost-program-options-dev, libboost-regex-dev
* Moved shared objects to private directory: /usr/lib/ultraster-ng
* Added rpath to binaries to search for shared objects in the private dir
* Uses ultrastar-ng-gstreamer as default, instead of ultrastar-ng-xine,
  since there are significantly less issues with GStreamer.
* Added patch to fix upstream desktop file
* Added -Wl,-as-needed to LDFLAGS
* Replaced fftw3-dev by libfftw3-dev in build dependencies.
* Standards-Version upgraded to 3.7.3

[ Mario Bonino ]
* Fixed data/Makefile.am to install .desktop file and icon

[ Jon Dowland ]
* add Homepage: control field to source stanza
* fix a bashism in debian/rules (Closes: #478634)

[ Ansgar Burchardt ]
* debian/control: Change XS-Vcs-* to Vcs-*
* Remove Homepage semi-field from description

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#include <boost/lexical_cast.hpp>
 
2
#include <boost/regex.hpp>
 
3
#include <glob.h>
1
4
#include <songs.h>
2
5
#include <screen.h>
3
 
 
4
 
bool compareSongs( CSong * left , CSong * right)
5
 
{
6
 
        if( left->orderType != right->orderType )
7
 
                fprintf(stderr,"Order mismatch\n");
8
 
        char * ordering1;
9
 
        char * ordering2;
10
 
        switch(left->orderType) {
11
 
                case 0: //edition
12
 
                        ordering1 = left->edition;
13
 
                        ordering2 = right->edition;
14
 
                        break;
15
 
                case 1: //genre
16
 
                        ordering1 = left->genre;
17
 
                        ordering2 = right->genre;
18
 
                        break;
19
 
                case 2: //title
20
 
                        ordering1 = left->title;
21
 
                        ordering2 = right->title;
22
 
                        break;
23
 
                case 3: //artist
24
 
                        ordering1 = left->artist;
25
 
                        ordering2 = right->artist;
26
 
                        break;
27
 
                default:
28
 
                        ordering1 = left->title;
29
 
                        ordering2 = right->title;
30
 
                        break;
31
 
        }
32
 
        // VERY IMPORTANT, if equal compareSongs MUST return false
33
 
        if(ordering1 == NULL && ordering2 == NULL)
34
 
                return (left->index < right->index);
35
 
        if(ordering1 == NULL)
36
 
                return true;
37
 
        if(ordering2 == NULL)
38
 
                return false;
39
 
        int cmp = strcmp(ordering1,ordering2);
40
 
        if( cmp < 0 )
41
 
                return true;
42
 
        else if( cmp > 0 )
43
 
                return false;
44
 
        else
45
 
                return (left->index < right->index);
46
 
}
47
 
 
48
 
void CSong::parseFile( void )
49
 
{
50
 
        char buff[256];
51
 
        snprintf(buff,256,"%s/%s",path,filename);
52
 
        int relativeShift = 0;
53
 
        maxScore = 0;
54
 
        FILE * fp = fopen(buff,"r");
55
 
        while(fgets(buff,256,fp)) {
56
 
                switch( buff[0] ) {
57
 
                        case '#' :
58
 
                                continue;
59
 
                        case 'E' :
60
 
                                break;
61
 
                        case 'F' :
62
 
                        case ':' :
63
 
                        case '*' : {
64
 
                                TNote * tmp = new TNote();
65
 
                                int shift;
66
 
                                int len = strlen(buff);
67
 
                                char * syllable = new char[16];
68
 
 
69
 
                                if (buff[0] == 'F')
70
 
                                    tmp->type = TYPE_NOTE_FREESTYLE;
71
 
                                if (buff[0] == '*')
72
 
                                    tmp->type = TYPE_NOTE_GOLDEN;
73
 
                                if (buff[0] == ':')
74
 
                                    tmp->type = TYPE_NOTE_NORMAL;
75
 
                                
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;
85
 
                                        delete tmp;
86
 
                                        break;
87
 
                                }
88
 
                                if( tmp->note <= noteMin )
89
 
                                        noteMin = tmp->note;
90
 
                                if( tmp->note >= noteMax )
91
 
                                        noteMax = tmp->note;
92
 
                                maxScore += tmp->length * tmp->type;
93
 
                                tmp->curMaxScore = maxScore;
94
 
                                notes.push_back(tmp);
95
 
                                break;
96
 
                        }
97
 
                        case '-' : {
98
 
                                TNote * tmp = new TNote();
99
 
                                int timestamp;
100
 
                                int sleep_end;
101
 
                                tmp->type = TYPE_NOTE_SLEEP;
102
 
                                int nbInt = sscanf(buff+1,"%d %d",&timestamp, &sleep_end);
103
 
                                tmp->timestamp = relativeShift + timestamp;
104
 
                                if( nbInt == 1 ) {
105
 
                                        tmp->length = 0;
106
 
                                } else {
107
 
                                        tmp->length = sleep_end - timestamp;
108
 
                                }
109
 
                                if(relative) {
110
 
                                        if( nbInt == 1 ) {
111
 
                                                relativeShift += timestamp;
112
 
                                        } else {
113
 
                                                relativeShift += sleep_end;
114
 
                                        }
115
 
                                }
116
 
                                tmp->curMaxScore = maxScore;
117
 
                                notes.push_back(tmp);
118
 
                                break;
119
 
                        }
120
 
                }
121
 
        }
122
 
        fclose(fp);
123
 
        // Adjust negativ notes
124
 
        if( noteMin <= 0 ) {
125
 
                unsigned int shift = (((noteMin*-1)%12)+1)*12;
126
 
                noteMin+= shift;
127
 
                noteMax+= shift;
128
 
                
129
 
                for( unsigned int i = 0 ; i < notes.size() ; i++ )
130
 
                        notes[i]->note+=shift;
131
 
        }
132
 
}
133
 
 
134
 
CSong::CSong()
135
 
{       
136
 
        path = NULL;
137
 
        filename = NULL;
138
 
        genre = NULL;
139
 
        edition = NULL;
140
 
        title = NULL;
141
 
        artist = NULL;
142
 
        text = NULL;
143
 
        creator = NULL;
144
 
        cover = NULL;
145
 
        mp3 = NULL;
146
 
        background = NULL;
147
 
        backgroundSurf = NULL;
148
 
        video = NULL;
149
 
        noteMin = 256;
150
 
        noteMax = -256;
151
 
        relative = false;
152
 
        videoGap=0;
153
 
        gap=0;
154
 
}
155
 
 
156
 
bool CSongs::parseFile( CSong * tmp )
157
 
{
158
 
        char buff[256];
159
 
        snprintf(buff,256,"%s/%s",tmp->path,tmp->filename);
160
 
        FILE * fp = fopen(buff,"r");
161
 
        if(!fp) {
162
 
                fprintf(stderr , "Cannot open \"%s\"\n",buff);
163
 
                return false;
164
 
        }
165
 
        while(fgets(buff,256,fp)) {
166
 
                if(buff[0] != '#' )
167
 
                        continue;
168
 
                if(!strncmp("#TITLE:",buff,7)) {
169
 
                        int len = strlen(buff);
170
 
                        char * title = new char[len - 7];
171
 
 
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);
175
 
                        tmp->title = title;
176
 
                } else if(!strncmp("#EDITION:",buff,9)) {
177
 
                        int len = strlen(buff);
178
 
                        char * edition = new char[len - 9];
179
 
 
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];
187
 
 
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];
195
 
 
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);
199
 
                        tmp->mp3 = mp3;
200
 
                } else if(!strncmp("#CREATOR:",buff,9)) {
201
 
                        int len = strlen(buff);
202
 
                        char * creator = new char[len - 9];
203
 
 
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)) {
211
 
                        TBpm bpm;
212
 
                        bpm.start = 0.0;
213
 
                        // We replace ',' by '.' for internationalization
214
 
                        char * comma = strchr(buff,',');
215
 
                        if( comma != NULL )
216
 
                                *comma = '.';
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];
222
 
 
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);
226
 
                        tmp->video = video;
227
 
                } else if(!strncmp("#BACKGROUND:",buff,12)) {
228
 
                        int len = strlen(buff);
229
 
                        char * background = new char[len - 12];
230
 
 
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;
240
 
                        else
241
 
                                tmp->relative = false;
242
 
                } else if(!strncmp("#COVER:",buff,7)) {
243
 
                        int len = strlen(buff);
244
 
                        char * cover = new char[len - 7];
245
 
 
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);
249
 
                        tmp->cover = cover;
250
 
                }
251
 
        }
252
 
        fclose(fp);
253
 
        return true;
254
 
}
255
 
 
256
 
CSongs::CSongs()
257
 
{
258
 
        DIR * dir=NULL;
259
 
        struct dirent* dirEntry;
260
 
        struct stat    info;
261
 
        char buff[1024];
262
 
        char * songs_dir = CScreenManager::getSingletonPtr()->getSongsDirectory();
263
 
        dir = opendir(songs_dir);
264
 
        order = 2;
265
 
 
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");
269
 
        delete[] theme_path;
270
 
                                
271
 
 
 
6
// math.h needed for C99 stuff
 
7
#include <math.h>
 
8
#include <algorithm>
 
9
#include <cmath>
 
10
#include <cstdlib>
 
11
#include <fstream>
 
12
#include <iomanip>
 
13
#include <iostream>
 
14
#include <limits>
 
15
#include <stdexcept>
 
16
 
 
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";
 
25
        return oss.str();
 
26
}
 
27
 
 
28
unsigned int MusicalScale::getNoteNum(int id) const {
 
29
        // C major scale
 
30
        int n = id % 12;
 
31
        return (n + (n > 4)) / 2;
 
32
}
 
33
 
 
34
bool MusicalScale::isSharp(int id) const {
 
35
        if (id < 0) throw std::logic_error("MusicalScale::isSharp: Invalid note ID");
 
36
        // C major scale
 
37
        switch (id % 12) {
 
38
          case 1: case 3: case 6: case 8: case 10: return true;
 
39
        }
 
40
        return false;
 
41
}
 
42
 
 
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);
 
46
}
 
47
 
 
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);
 
51
        return -1;
 
52
}
 
53
 
 
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);
 
57
}
 
58
 
 
59
double MusicalScale::getNoteOffset(double freq) const {
 
60
        double frac = freq / getNoteFreq(getNoteId(freq));
 
61
        return 12.0 * log(frac) / log(2);
 
62
}
 
63
 
 
64
double Note::diff(double n) const {     return remainder(n - note, 12.0); }
 
65
double Note::maxScore() const { return scoreMultiplier(0.0) * (end - begin); }
 
66
 
 
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;
 
71
}
 
72
 
 
73
double Note::scoreMultiplier(double error) const {
 
74
        double max = 0.0;
 
75
        switch (type) {
 
76
          case FREESTYLE: return 1.0;
 
77
          case NORMAL: max = 1.0; break;
 
78
          case GOLDEN: max = 2.0; break;
 
79
          case SLEEP: break;
 
80
        }
 
81
        return std::min(1.0, std::max(0.0, 1.5 - error)) * max;
 
82
}
 
83
 
 
84
 
 
85
class SongParser {
 
86
  public:
 
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; }
 
91
          private:
 
92
                unsigned int m_linenum;
 
93
        };
 
94
        SongParser(Song& s):
 
95
          m_song(s),
 
96
          m_f((s.path + s.filename).c_str()),
 
97
          m_linenum(),
 
98
          m_relative(),
 
99
          m_gap(),
 
100
          m_bpm(),
 
101
          m_prevts(),
 
102
          m_relativeShift(),
 
103
          m_maxScore()
272
104
        {
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);
280
 
        }
281
 
 
282
 
        if( surface_nocover == NULL ) {
283
 
                printf("IMG_LoadPNG_RW: %s\n", IMG_GetError());
284
 
                return;
285
 
        }
286
 
 
287
 
        if( dir == NULL )
288
 
                return;
289
 
 
290
 
        while( (dirEntry = readdir(dir)) != NULL ) {
291
 
                if( dirEntry->d_name[0] == '.' )
292
 
                        continue;
293
 
                if( !strcmp(dirEntry->d_name,"CVS") )
294
 
                        continue;
295
 
                
296
 
                char * path = new char[1024];
297
 
                snprintf(path,1024,"%s%s",songs_dir,dirEntry->d_name);
298
 
                stat(path,&info);
299
 
                if ( !S_ISDIR(info.st_mode) ) {
300
 
                        delete[] path;
301
 
                        continue;
302
 
                }
303
 
 
304
 
                fprintf(stdout,"Song directory \"%s\" detected\n",dirEntry->d_name);
305
 
 
306
 
                CSong * tmp = new CSong();
307
 
 
308
 
                // Set default orderType to title
309
 
                tmp->orderType = 2;
310
 
                tmp->path = path;
311
 
                char * txt = new char[strlen(dirEntry->d_name)+4+1];
312
 
                sprintf(txt,"%s.txt",dirEntry->d_name); // safe sprintf
313
 
                tmp->filename = txt;
314
 
                if( !parseFile(tmp) ) {
315
 
                        delete[] path;
316
 
                        delete[] txt;
317
 
                        delete tmp;
318
 
                } else {
319
 
                        if(!tmp->cover) {
320
 
                            char * cover = new char[strlen(dirEntry->d_name)+4+1];
321
 
                            sprintf(cover,"%s.png",dirEntry->d_name); // safe sprintf
322
 
                            tmp->cover = cover;
323
 
                        }
324
 
 
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);
332
 
                        SDL_FreeRW(rwop);
333
 
                                   
334
 
                        if( coverSurface == NULL )
335
 
                            tmp->coverSurf = surface_nocover;
336
 
                        else {
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);
342
 
                        }
343
 
                        
344
 
                        
345
 
                        if (tmp->background) {
346
 
                            SDL_Surface * backgroundSurface = NULL;
347
 
 
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);
352
 
                                SDL_FreeRW(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);
357
 
                                SDL_FreeRW(rwop);
358
 
                            }
359
 
                        
360
 
                                                 
361
 
                            if( backgroundSurface == NULL )
362
 
                                tmp->backgroundSurf = NULL;
363
 
                            else {
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);
368
 
                            }
369
 
                        }
370
 
                        
371
 
                        tmp->parseFile();
372
 
                        tmp->index = songs.size();
373
 
                        songs.push_back(tmp);
374
 
                }
375
 
        }
376
 
        closedir(dir);
377
 
}
378
 
 
379
 
CSongs::~CSongs()
 
105
                if (!m_f.is_open()) throw Exception("Could not open TXT file", 0);
 
106
                std::string line;
 
107
                try {
 
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);
 
114
                }
 
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;
 
124
                }
 
125
                m_song.m_scoreFactor = 1.0 / m_maxScore;
 
126
        }
 
127
  private:
 
128
        Song& m_song;
 
129
        std::ifstream m_f;
 
130
        unsigned int m_linenum;
 
131
        bool getline(std::string& line) { ++m_linenum; return std::getline(m_f, line); }
 
132
        bool m_relative;
 
133
        double m_gap;
 
134
        double m_bpm;
 
135
        void assign(int& var, std::string const& str) {
 
136
                var = boost::lexical_cast<int>(str);
 
137
        }
 
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);
 
141
        }
 
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);
 
146
        }
 
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);
 
170
                return true;
 
171
        }
 
172
        unsigned int m_prevts;
 
173
        unsigned int m_relativeShift;
 
174
        double m_maxScore;
 
175
        struct BPM {
 
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
 
179
                unsigned int ts;
 
180
        };
 
181
        typedef std::vector<BPM> bpms_t;
 
182
        bpms_t m_bpms;
 
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));
 
187
        }
 
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') {
 
195
                        unsigned int ts;
 
196
                        double bpm;
 
197
                        iss.ignore();
 
198
                        if (!(iss >> ts >> bpm)) throw std::runtime_error("Invalid BPM line format");
 
199
                        addBPM(ts, bpm);
 
200
                        return true;
 
201
                }
 
202
                Note n;
 
203
                n.type = Note::Type(iss.get());
 
204
                unsigned int ts = m_prevts;
 
205
                switch (n.type) {
 
206
                  case Note::NORMAL:
 
207
                  case Note::FREESTYLE:
 
208
                  case Note::GOLDEN:
 
209
                        {
 
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);
 
215
                        }
 
216
                        break;
 
217
                  case Note::SLEEP:
 
218
                        {
 
219
                                unsigned int end;
 
220
                                if (!(iss >> ts >> end)) end = ts;
 
221
                                if (m_relative) {
 
222
                                        ts += m_relativeShift;
 
223
                                        end += m_relativeShift;
 
224
                                        m_relativeShift = end;
 
225
                                }
 
226
                                n.end = tsTime(end);
 
227
                        }
 
228
                        break;
 
229
                  default: throw std::runtime_error("Unknown note type");
 
230
                }
 
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");
 
235
                m_prevts = ts;
 
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) {
 
244
                                        p.end = p.begin;
 
245
                                        Note& p2 = notes[notes.size() - 2];
 
246
                                        if (p2.end < n.begin) p.begin = p.end = n.begin;
 
247
                                }
 
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");
 
252
                }
 
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();
 
257
                }
 
258
                notes.push_back(n);
 
259
                return true;
 
260
        }
 
261
        double tsTime(unsigned int ts) const {
 
262
                if (m_bpms.empty()) {
 
263
                        if (ts != 0) throw std::runtime_error("BPM data missing");
 
264
                        return m_gap;
 
265
                }
 
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;
 
268
                }
 
269
                throw std::logic_error("INTERNAL ERROR: BPM data invalid");
 
270
        }
 
271
};
 
272
 
 
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()),
 
276
  path(_path),
 
277
  filename(_filename),
 
278
  videoGap(),
 
279
  start(),
 
280
  m_coverSurf(),
 
281
  m_backgroundSurf(),
 
282
  m_scoreFactor(),
 
283
  m_score(),
 
284
  m_scoreTime()
380
285
{
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];
395
 
                }
396
 
                if( surface_nocover != songs[i]->coverSurf )
397
 
                        SDL_FreeSurface(songs[i]->coverSurf);
398
 
                if( songs[i]->backgroundSurf )
399
 
                        SDL_FreeSurface(songs[i]->backgroundSurf);
400
 
                delete songs[i];
401
 
        }
 
286
        SongParser(*this);
 
287
}
 
288
 
 
289
void Song::reset() {
 
290
        m_score = 0.0;
 
291
        m_scoreTime = 0.0;
 
292
        m_scoreIt = notes.begin();
 
293
}
 
294
 
 
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;
 
300
                ++m_scoreIt;
 
301
        }
 
302
        m_scoreTime = time;
 
303
        m_score = std::min(1.0, std::max(0.0, m_score));
 
304
}
 
305
 
 
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;
 
320
        else {
 
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);
 
326
        }
 
327
        // Prevent trying to reload the same cover
 
328
        if (!m_coverSurf) cover.clear();
 
329
}
 
330
 
 
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;
 
345
        else {
 
346
                m_backgroundSurf = zoomSurface(surf, width / surf->w, height / surf->h, 1);
 
347
                SDL_FreeSurface(surf);
 
348
        }
 
349
        // Prevent trying to reload the same cover
 
350
        if (!m_backgroundSurf) background.clear();
 
351
}
 
352
 
 
353
void Song::unloadCover() {
 
354
        if (m_coverSurf) SDL_FreeSurface(m_coverSurf);
 
355
        m_coverSurf = NULL;
 
356
}
 
357
 
 
358
void Song::unloadBackground() {
 
359
        if (m_backgroundSurf) SDL_FreeSurface(m_backgroundSurf);
 
360
        m_backgroundSurf = NULL;
 
361
}
 
362
 
 
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.
 
368
}
 
369
 
 
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);
 
381
        reload();
 
382
}
 
383
 
 
384
Songs::~Songs() {
402
385
        SDL_FreeSurface(surface_nocover);
403
 
 
404
 
}
405
 
 
406
 
CSong * CSongs::getSong( unsigned int i )
407
 
{
408
 
        if( i >= songs.size())
409
 
                return NULL;
410
 
        else
411
 
                return songs[i];
412
 
}
413
 
 
414
 
void CSongs::sortByEdition( void )
415
 
{
416
 
        order = 0;
417
 
        for(unsigned int i = 0; i < songs.size(); i++)
418
 
                songs[i]->orderType = 0;
419
 
        sort(songs.begin(), songs.end(),compareSongs);
420
 
}
421
 
void CSongs::sortByGenre( void )
422
 
{
423
 
        order = 1;
424
 
        for(unsigned int i = 0; i < songs.size(); i++)
425
 
                songs[i]->orderType = 1;
426
 
        sort(songs.begin(), songs.end(),compareSongs);
427
 
}
428
 
void CSongs::sortByTitle( void )
429
 
{
430
 
        order = 2;
431
 
        for(unsigned int i = 0; i < songs.size(); i++)
432
 
                songs[i]->orderType = 2;
433
 
        sort(songs.begin(), songs.end(),compareSongs);
434
 
}
435
 
void CSongs::sortByArtist( void )
436
 
{
437
 
        order = 3;
438
 
        for(unsigned int i = 0; i < songs.size(); i++)
439
 
                songs[i]->orderType = 3;
440
 
        sort(songs.begin(), songs.end(),compareSongs);
441
 
}
 
386
}
 
387
 
 
388
void Songs::reload() {
 
389
        songlist_t songs;
 
390
        for (std::set<std::string>::const_iterator it = m_songdirs.begin(); it != m_songdirs.end(); ++it) {
 
391
                glob_t _glob;
 
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;
 
402
                        try {
 
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;
 
408
                        }
 
409
                }
 
410
                std::cout << "\r\x1B[K" << std::flush;
 
411
                globfree(&_glob);
 
412
        }
 
413
        m_songs.swap(songs);
 
414
        setFilter("");
 
415
}
 
416
 
 
417
class Songs::RestoreSel {
 
418
        Songs& m_s;
 
419
        Song* m_sel;
 
420
  public:
 
421
        RestoreSel(Songs& s): m_s(s), m_sel(s.empty() ? NULL : &s.current()) {}
 
422
        ~RestoreSel() {
 
423
                m_s.random();
 
424
                if (!m_sel) return;
 
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();
 
428
        }
 
429
};
 
430
 
 
431
void Songs::random() {
 
432
        m_current = empty() ? 0 : std::rand() % m_filtered.size();
 
433
}
 
434
 
 
435
void Songs::setFilter(std::string const& val) {
 
436
        RestoreSel restore(*this);
 
437
        filtered_t filtered;
 
438
        try {
 
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);
 
441
                }
 
442
        } catch (...) {
 
443
                filtered.clear();
 
444
                for (songlist_t::iterator it = m_songs.begin(); it != m_songs.end(); ++it) {
 
445
                        filtered.push_back(&*it);
 
446
                }
 
447
        }
 
448
        m_filtered.swap(filtered);
 
449
        sort_internal();
 
450
}
 
451
 
 
452
class CmpByField {
 
453
        std::string Song::* m_field;
 
454
  public:
 
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;
 
459
        }
 
460
        bool operator()(Song const* left , Song const* right) {
 
461
                return operator()(*left, *right);
 
462
        }
 
463
};
 
464
 
 
465
static char const* order[] = {
 
466
        "by song",
 
467
        "by artist",
 
468
        "by edition",
 
469
        "by genre",
 
470
        "by path"
 
471
};
 
472
 
 
473
static const int orders = sizeof(order) / sizeof(*order);
 
474
 
 
475
std::string Songs::sortDesc() const {
 
476
        std::string str = order[m_order];
 
477
        if (!empty()) {
 
478
                if (m_order == 2) str += " (" + current().edition + ")";
 
479
                if (m_order == 3) str += " (" + current().genre + ")";
 
480
                if (m_order == 4) str += " (" + current().path + ")";
 
481
        }
 
482
        return str;
 
483
}
 
484
 
 
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);
 
489
        sort_internal();
 
490
}
 
491
 
 
492
void Songs::sort_internal() {
 
493
        switch (m_order) {
 
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");
 
500
        }
 
501
}
 
502