performous / composer

Song editor for Performous and other singing games
Other
39 stars 23 forks source link

Soramimi txt import support #3

Closed nieknooijens closed 11 years ago

nieknooijens commented 11 years ago

hi there

I'm from a dutch Japanese student society and we do karaoke every Friday. we use a program called soramimi (performous-like without pitch recognition) unfortunately the program is very buggy and the original developer has ceased development. the code is now in the hands of two idiots who can't program at all and they won't release the source-code for it (cause then I would've continue development under GPL3). the result is that the program hasn't been updated since 2001. as a solution we want to switch to performous! but all our songs are still in soramimi format:

(link only contains lyrics textfiles no copyrighted material!) http://www.maxworks.nl/soramimi/timecodedlyrics/SMMLyricsTimeCoded.htm

it would be great if we could insert the timecodes into the composer and let the composer do the pitch recognition, this way we will be able to convert our collection in no time!

I'm already busy on some txtparser for the composer and i've got qtcreator all set up but since I'm still an ICT student I can't do this on my own. I'll need some help at creating a good parser and inject it into the program.

all help is greatly appreciated niek nooijens

tapio commented 11 years ago

Where do you need help exactly? How about forking this repository, hacking away the best you can and then asking for help on specific problems you encounter, pointing to the code you are working on.

A word of warning - the automatic pitch detection will not magically give you good results with regular mp3s where there is both vocals and instruments. It'll help, but manual adjustments will be needed. That being said, you can also ignore the pitch and put Performous into karaoke mode where the pitch doesn't matter.

nieknooijens commented 11 years ago

some problems I've encountered so far: 1 the way [notes] are handled is very confusing, you' ve got a class note and a list which is called [notes]?? how does this whole note-class work and which fields do I need to store my info in? 2 If I add code into the txtparcer.cc file I immediately have the problem that soramimi doesn' t provide song-name and artist information it only contains text and their timings so the program throws a runtime-error because those fields are empty. it needs an popup where the user can fill in this information, but since I do ICT&technology (programming microcontrollers) i' ve got no experience with GUI-programming (and if i had chosen to do ICT& software I would only be working with microsoft C# bleh!) 3 I'm afraid of breaking the program (which is my own problem)

tapio commented 11 years ago
  1. What do you mean by [notes]? You should probably take a peek at songparser-xml.cc as it is simpler than the txt parser. In short, you create Note objects, give them the lyric, begin and end (in seconds) and push them to VocalTrack.notes. Since you don't have pitch information, just leave the Note.note property alone. After each sentence (I guess a line in those soramimi files) you add a note of type SLEEP like here: https://github.com/performous/composer/blob/master/songparser-xml.cc#L98
  2. You should not touch songparser-txt.cc as it is UltraStar format parser. Create a completely new songparser-smm.cc, which can use code from others as inspiration, but as the Soramimi format is very simple and only contains time codes and lyrics, it should be easiest to code a new one from scratch. The exception is probably a feature of the UltraStar parser, just don't duplicate it in your parser and leave the fields empty or put Unknown into them and the user can change them from the Song properties GUI tab after the import.
  3. Do not be afraid. You can hack away in your own repository without affecting the main code at all. We will also review any code you submit before merging it (but of course we also expect some testing to be done).
nieknooijens commented 11 years ago

thanks i'll be working on it next weekend and if I'm stuck again I'll post it here

nieknooijens commented 11 years ago

ok something's wrong with my parser, it keeps telling me there are no notes when I load my file it does compile though!

the code probabely sucks cause I'm still a student :+1: here's the contents of my file (songparser smmtxt.cc):

#include "songparser.hh"
#include <stdexcept>
#include <iostream>

const int bpm = 6000; //this way we'll have 100 bpm every second, so 1bpm every milisecond!
VocalTrack vocal(TrackName::LEAD_VOCAL);
Notes& notes = vocal.notes;

void SongParser::smmParse()
{
    QString line;
    while (getline(line) && smmNoteParse(line)) {}
    m_song.bpm = bpm;
    m_song.insertVocalTrack(TrackName::LEAD_VOCAL, vocal);

    if (!notes.empty()) {
        vocal.beginTime = notes.front().begin;
        vocal.endTime = notes.back().end;
        // Insert notes
        m_song.insertVocalTrack(TrackName::LEAD_VOCAL, vocal);
    } else throw std::runtime_error(QT_TR_NOOP("Couldn't find any notes"));
}

bool SongParser::smmNoteParse(QString line)
{
    int i = 0;
    int j = 0;
    int sizeofLine = sizeof(line);
    char nextChar;
    Note n;
    if (line.isEmpty()) return true;
    if (line[0] != '[')
    {
        throw std::runtime_error("not a soramimi file!");
        return false;
    }
    else
    {
        QString noteTimeBegin = "";
        QString noteTimeEnd = "";
        QString noteText;
        n.type =  Note::NORMAL;
        while(i<sizeofLine)
        {
            if(line[i] == '[')
            {
                i++;
                nextChar = line.at(i).toLatin1();
                while(nextChar != ']')
                {
                    noteTimeBegin+=nextChar;
                    i++;
                    nextChar = line.at(i).toLatin1();
                    //now we've got the starttime as string seperated by :
                }
            }
             else if(line[i] == ']') //confirm end of time indication
            {
                    i++;
                    while(nextChar != '[')
                    {
                        noteText+=nextChar;
                        i++;
                        nextChar = line.at(i).toLatin1();
                        //now we've got the note text as string;
                        n.syllable = noteText;
                    }
                    j = i;
                    j++;
                    nextChar = line.at(j).toLatin1();
                    while(nextChar != ']')
                    {
                    noteTimeEnd+=nextChar;
                    i++;
                    nextChar = line.at(j).toLatin1();
                                /*now we've got the endtime as string seperated by :
                                but we start a different counter because the end time
                                of one note is the start time of the following exept for the last note in the line*/
                   }
                    n.begin = convertTimeToBpm(bpm,noteTimeBegin);
                    n.end = convertTimeToBpm(bpm, noteTimeEnd);
                    notes.push_back(n);

                    if(line.at(j+1) == '\0') //see if we' re at the end of the line
                    {

                        Note e;
                        e.type = Note::SLEEP;
                        e.note = 0;
                        e.begin = convertTimeToBpm(bpm,noteTimeEnd);
                        e.end = e.begin;
                        notes.push_back(e);
                        return true;
                    }

            }
         }

        return true;

   }

}

int SongParser::convertTimeToBpm(int bpm, QString time)
{
    int beats = 0;
    char Minutes [2];
    Minutes [0] = time.at(0).toLatin1();
    Minutes [1] = time.at(1).toLatin1();
    int minutes = atoi(Minutes);
    beats += (bpm * minutes);
    char Seconds [2];
    Seconds [0] = time.at(3).toLatin1();
    Seconds [1] = time.at(4).toLatin1();
    int seconds = atoi(Seconds);
    beats += ((bpm/60)*seconds);
    char MiliSeconds [2];
    MiliSeconds [0] = time.at(6).toLatin1();
    MiliSeconds [1] = time.at(7).toLatin1();
    int miliseconds = atoi(MiliSeconds);
    beats += ((bpm/6000)*miliseconds);
    return beats;
}
tapio commented 11 years ago

Are you sure this soramimi parser gets invoked? I don't see smmCheck() function, so have you modified songparser.cc to actually launch the parser?

Btw, BPM 6000 is a bad idea. It has no connection to reality as usually songs have something like BPM 120. In fact, the whole conversion to BPMs is unnecessary: Both Composer and Performous actually work with straight time stamps in (floating point) seconds. The reason you see BPM stuff is that most other formats derive timestamps from BPM so Composer/Performous needs to convert those values to real time. As Soramimi uses also regular time, forget about BPMs and just convert to seconds.

You should really fork our repo and do commits in your copy and then link to those commits (paste urls to github commits). That way I could look at syntax highlighted code and add comments inside the diffs etc.

nieknooijens commented 11 years ago

I did modify songparser.cc i added smmtxt to the enum that determines the file format but i linked the parser in a very dirty way i'm not very proud of :-) i haven't written a check yet. but i did add some buttons to the gui to load soramimi-text files as a stepin the getting-sarted dialog. i'll uploadit tomorrow on an emptygit repo:-)

nieknooijens commented 11 years ago

I uploaded my changes here: https://github.com/nieknooijens/composer-smmtxt unfortunately I can't find a "donate" page on performous.org (i think you deserve a good cup of coffee helping me out with this)

nieknooijens commented 11 years ago

there's one thing i need to know about the bpm, in which format is note::begin? it's an unsigned int, but is it in bpm, miliseconds or something else? I need to know this before I can move on debugging my stuff.

tapio commented 11 years ago

What do you mean note::begin is unsigned int? It's double and seconds: https://github.com/performous/composer/blob/master/notes.hh#L64

Regarding your code drop: you should have done it by pressing the Fork button in our repo's interface. That way the codebases retain link between themselves, which allows us to do lots of useful stuff with the GitHub user interface.

nieknooijens commented 11 years ago

i admit i'm just plain stupid i should search harder....

nieknooijens commented 11 years ago

hi there, today I got succes! the parser works almost flawlessly! there are just a few issues I need help with: 1: I have to remove all the empty lines from the soramimi textfile, otherwise the program just freezes (I don't know why) 2: the last phrase is skipped for some reason (so it parses everything exept the last phrase) edit: fixed these two, used true instead of false so it kept looping for eternity

the last issue is that the parser gets called in a very dirty way, but i'll fix that as soon as possible

https://github.com/nieknooijens/composer-smmtxt/blob/master/songparser-smmtxt.cc

if we fix those issues it's ready for the main tree!

nieknooijens commented 11 years ago

ok in my opinion i'm done, the parser works flawlessly and the dirty hacks have been cleaned out, would you pease review my code and [if everything meets your code standards] merge the code into the main repository?

i got problems with make packages and with using the mingc compiler, I want to create a .deb package and cross-compile it fgor windows....

tapio commented 11 years ago

I'll look into it after Christmas. One thing that caught the eye earlier is that the timestamp conversion code looks long, hacky and uses C-library (atoi).

Another issue is the linecount variable - global state is evil. Note that you are not forced to follow the function naming and uses of the other parses - e.g. in the TXT parser it was convenient to parse one note per function call because there was only one note per line.

nieknooijens notifications@github.com kirjoitti:

ok in my opinion i'm done, the parser works flawlessly and the dirty hacks have been cleaned out, would you pease review my code and [if everything meets your code standards] merge the code into the main repository?


Reply to this email directly or view it on GitHub: https://github.com/performous/composer/issues/3#issuecomment-11635774

tapio commented 11 years ago

Pushed your stuff to a branch and added some tweaks: https://github.com/performous/composer/commits/soramimi

You should especially pay attention to adhering to surrounding coding style, e.g. using tabs for indentation.

nieknooijens commented 11 years ago

I'm still a student and I'm trying to learn from this opportunity so therefore i'll read all of them. if you need any help with the project i'll be happy to help though please mail me.

about the timestamp conversion how do i replace characters in a qstring and convert a string to a double in that case i'll be able to drop the atoi() function which is indeed pretty ugly....

nieknooijens commented 11 years ago

dear tapio

i refactored the timestamp-conversion method to:

double SongParser::convertSMMTimestampToDouble(QString timeStamp) { bool ok = false; timeStamp.replace(QString(":"), QString(".")); QString minutes = timeStamp.mid(0,2); QString seconds = timeStamp.mid(3,5); double Min = minutes.toDouble(&ok); if(!ok) { throw std::runtime_error("double conversion went wrong"); } double Sec = seconds.toDouble(&ok); if(!ok) { throw std::runtime_error("double conversion went wrong"); } double append = Min*60; append +=Sec; return append; }

hope this meets your code standards

niek

nieknooijens commented 11 years ago

I've got trouble compiling it for windows, the given mingw32-scripts do not work and i've got no succes modifying them on windows itself I have been busy for over 7 hours getting the toolchain set up which is a pain, and even now it doesn't work... how can I cross-compile the composer for windows?

tapio commented 11 years ago

I've pushed your change to the official branch. I'm not entirely comfortable promoting the obscure soramimi format to the Getting Started screen. However, it seems the format is very close to LRC: http://en.wikipedia.org/wiki/LRC_(file_format). Since LRC is very common karaoke format, I think we should rename this to LRC and make a couple of compatibility patches. Since you replace the : with periods, the timecode format difference is already handled.

Regarding mingw-cross-env scripts, they are probably outdated - e.g. the compiler project has changed its name to mxe. I haven't tried myself for a while, but I'd start by following instructions on http://mxe.cc/ for compiling a cross-compiler, Qt and ffmpeg. On Windows itself, you should be able to compile Composer by installing Qt SDK (4.8) and compile ffmpeg libs.

nieknooijens commented 11 years ago

don't tell me I acidentally supported a file format which wasn't inteded to.... :+1: but as we say at school: "it's not a bug, it's a feature!" I agree renaming it to LRC but we should rename it to LRC/soramimi implying that it's compatible with soramimi as well. the button at the getting started screen was just a shortcut for me since I need to batch-convert those files, it was placed there on logical grounds since imported LRC/soramimi timecodes aren't actually finished songs yet since the piches aren't correct. there's another shortcut in the insert-menu so feel free to remove the button from the getting-started-screen. last but not least: is it ok if I delete my "temporarily fork"?

niek

tapio commented 11 years ago

I did the rebranding to LRC and merged soramimi branch to master. I preserved the GUI references to Soramimi.

You can delete your repo - if you have further commits please use the Fork button in the upper right corner of our repo page to create yourself a new linked repository.

tapio commented 11 years ago

With regards to Windows build, it seems my old toolchain still manages to compile Composer after updating the git URL. A build from today (not tested): http://aave.performous.org/ci/results/editor-win32/20130114175320/out The CI system: http://aave.performous.org/ci/

tapio commented 11 years ago

I shortened the parser and it can now also import LRC exported from Composer (although that simple version is less useful than Soramimi's files). I did a little testing and Soramimi seemed to still work, but I'd appreciate if you'd check if it still works as you intended. I'm thinking about releasing v1.1 soon, so I'd also appreciate if you'd tell if everything you need is already implemented.

nieknooijens commented 11 years ago

I tested it with a few files and with some files it works as expected, but I found a bug! if you insert this file: http://www.maxworks.nl/soramimi/timecodedlyrics/Gundam_00Daybreak%27s_Bell.txt it will create very large empty notes and the timing is way-off! when I use my own version it works just fine.

the build system actually didn't compile it, when I use the installer from the link 2 posts upwards it only installs phonon....

tapio commented 11 years ago

I fixed the installer creation: http://aave.performous.org/ci/results/editor-win32/20130115131422/out/

I assumed all Soramimi files had a timestamp at the end of the line, which I think causes the errors. What would be the correct end time for last notes on a line that are not followed by a timestamp?

nieknooijens commented 11 years ago

they have a timestamp at the end of the line, but some of them contain empty lines and some of them contain lines with only a timestamp and no text, the empty lines can be ignored, i fixed the "one-timestamp-lines" issue by marking those as sleepnotes. which worked perfectly...

tapio commented 11 years ago

Ok, thanks for the clarification. I'll fix those "one timestamp no text lines" in the evening. I'd imagine just ignoring them would also be fine instead of adding sleeps with no meaning.

tapio commented 11 years ago

Should be fixed now by 205c728632243dd0.

nieknooijens commented 11 years ago

it fixed it indeed, I found another strange bug, when you open up a textfile with a text-editor and remove all the empty lines, it will fail to parse the last line.. 95% of all soramimi files have empty lines so it shouldn't be a huge problem,

tapio commented 11 years ago

Excellent catch. I'm pretty sure this was a bug in other parsers too. Should be fixed by 9e3017f17a8e4857b82808d633fe2b6c15.

nieknooijens commented 11 years ago

hi there sorry for the late response, we had to finish some lame project at school which uses controllers attached to routers to drive a racing game (don't ask me why! the teachers made that up for us). therefore last weeks were a little busy. I tested the latest composer firmly and I can't find any more bugs (you might have guessed I’ve got some experience in software testing, i was beta tester for desura and steam) so if there aren't any more wishes for the next composer it's ready for release. my next project will probably be playlist support for performous (I've read somewhere that's a very wanted feature). thanks for all the support making the soramimi-import feature a success!