composer-master/0000755000175000017500000000000013357046745012340 5ustar niknikcomposer-master/docs/0000755000175000017500000000000013357046745013270 5ustar niknikcomposer-master/docs/helpindex.html0000644000175000017500000001073213357046745016141 0ustar niknik

Help Browser

  1. Introduction
  2. Basic workflow
    1. Importing song and lyrics
    2. Note timing
    3. Fine tuning
    4. Song metadata
  3. Tips and tricks
  4. File formats

Introduction

This editor is designed to create notes for use in pitch analyzing karaoke games. We attempt to make the process as easy as possible by automating as much as we can. For example, the editor analyzes the song and attempts to automatically place the notes at the correct pitch.

Basic Workflow

In addition to the sections here, the intended workflow is documented as a handy dialog with clickable items - follow it step-by-step and in the end you'll have finished notes for the song. Access it through the main menubar: Help --> Getting started.

Importing song and lyrics

The first step is to import a music file for analyzation and lyrics text for generating notes. This can be done e.g. through the import menu. Supported song file formats vary depending on the platform, but at least mp3 and ogg should be ok. Lyrics assume that each phrase is on a line of its own - when the text is imported, a note is generated for each word and a sentence marker is placed at the beginning of each line.

You can also add an additional music file via the import menu. It will get analyzed and the results are displayed with different colors. The idea is that if you have a music file with both vocals and instruments, plus another karaoke version with just the instruments, you can use the analysis from the additional (karaoke) music to determine which tones belong to the background and are probably not singing.

Note timing

The easiest way to get the notes roughly timed is to switch to the Tools tab and start listening to the music. Each time you hear the phrase that is displayed in the tab, hit the Time phrase button (or its hotkey) and the start of the current phrase is placed to that position.

This way you'll be timing only the beginnings of the phrases - the rest of the notes are divided evenly to the extra space. Obviously this is not perfect, but it gives a very good starting point for fine tuning and timing each note by itself using this method would require very good reflexes and concentration.

Fine tuning

Once the notes are roughly timed, it is time to start manually tuning and fixing everything. Each time you correct a note, the neighbouring uncorrected ones adjust themselves accordingly, making your task easier.

At this phase, you can also break words into syllables in order to create more accurate notes. It is also possible to add or remove notes or batches of them.

Song metadata

The song title, artist and other metadata is automatically read from the music file if it happens to contain that information. If it is wrong or missing, you can fix it through the Song properties tab.

Tips and tricks

File formats

Various formats are supported to a reasonable extent:

Check the docs/FileFormats.txt for more detailed information about the level of support.

composer-master/docs/FileFormats.txt0000644000175000017500000000302113357046745016240 0ustar niknikEditor file format support ========================== This document describes to what extent various file formats are supported by the editor. Own project files ----------------- Native save/load format. Preserves operation history (undo buffer). SingStar XML ------------ Import and export. Round-trip is not perfect: All XML elements except MELODY, TRACK, SENTENCE and NOTE are ignored as are all attributes of SENTENCE-element. Exporter writes some useful comments. UltraStar TXT ------------- Import and export. All tags are not preserved. Frets on Fire MIDI/INI ---------------------- Vocal track import and export. Timecoded LRC / Soramimi lyrics ------------------------------- LRC is a popular karaoke format, which has many variations. Soramimi is an old karaoke software, which uses a similar format. Composer can import the timing data but the format has no way of expressing pitch. We attempt to support reading the timecodes in many different syntaxes, including per-word timing. For maximum compatibility, exporting LRC will use the simple format, which has one time per sentence - thus import-export is a lossy operation. Some "ID Tags" are supported. Plain text lyrics ----------------- Raw lyrics without any timing data can be read from a file or copied from clipboard. A note is created for each word and a line break indicates sentence end. Music files ----------- The pitch analysis can be performed to any file that FFmpeg can decode. Playback and metadata reading support depends on Phonon media library back-end. composer-master/docs/Authors.txt0000644000175000017500000000024513357046745015457 0ustar niknikPrimary Authors: Lasse Kärkkäinen Tapio Vierros See Git repository history for full list of contributors. This applications uses code from the Performous game. composer-master/docs/Design.txt0000644000175000017500000000535113357046745015246 0ustar niknikThe implementation is based on Qt as planned earlier and we will continue with this approach if no serious issues are encountered. We also considered an implementation as a feature of Performous and decided to use this as a fallback in case Qt turns out to be problematic. We also reviewed Editor on Fire but determined that it is not suitable for our use because it is primarily designed for entering guitar notes and a lot of manual labor is required becaus of that. Also the implementation is in rather unmaintainable C code (instead of clean C++) and the user-interface is not very good. Instead of separate modes for creating new song or editing an existing song we aim to provide all tools in the main editor view. This allows to user to choose his workflow in a more flexible manner and one can also go back and redo parts of an existing song using any of the tools available. Lyrics are imported in the format commonly used on lyric sites (text with sentence per line). Next the user can give timing information (beginning time) for each word e.g. by tapping space while listening to the song. Not all words need to be timed and anything that is not timed will float freely and all floating words will be evenly divided into the time period they take (between words that have been already timed). This allows the user not to time each word but instead time only what needs to be timed (e.g. each sentence) and the rest will be done automatically. Pitch detection is used to find the pitch and then the exact timing is determined from the detected pitch. The user may adjust pitch and timing (beginning, end) manually to correct possible problems. Here again it was planned to have autodetected parameters float, i.e. have them change flexibly if anything in the song is changed, until locked down by user's manual adjustments. It is initially planned to display the flexibly created notes as soon as lyrics are available so that the user will technically be fixing a song rather than creating one. This should allow doing the minimal amount of work for getting the job done. As we won't be able to store all the required metadata about flexible parameters and such in SingStar XML nor other established formats, we will implement our own project file format that contains all the actions taken for creating the song (this also allows for implementing unlimited undo/redo). SingStar XML requires a constant BPM value which is not realistic for most songs because BPM usually varies. We considered implementing a beat detector for getting the actual beat timing but this would not work for all songs and we would have to use a bogus value for SingStar XML anyway. Because BPM is not relevant for singing, we plan to simply use a bogus value and not even try to detect the beats. composer-master/docs/License.txt0000644000175000017500000004325213357046745015421 0ustar niknikGNU GPLv2 or later. Some parts of the project may have more liberal licenses. GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. composer-master/icons/0000755000175000017500000000000013357046745013453 5ustar niknikcomposer-master/icons/media-playback-start.png0000644000175000017500000000270213357046745020160 0ustar niknikPNG  IHDRw=sBIT|dyIDATHMhU&!4 JfBPB MM("Tqݨɔq'NfRBHHrs=v1y9}>3111 1/WEލ#l)*$IHm.8N," KRb޽ÐU޿mr9\]MLLY˗/Qk=YZCCC477eaA)EV\.s} eY\z~$I&+yb}p˲Q֚4M) ݻw)vCC5˲>ә .tc]V[\edd^ qlAPw$ Q!>}bzz8ٳgφ NNN~o?c8WU" y}˲, ۶w</@D~qȑCCCZommك8q<߻wgϞ,}*o߾uƘiK)USJqQ|20 SSSܸqr "YA@.cRJn3uRsssɓpۉ8xGcc#===DQR괥*":::Hd8@ZekkrL\feek׮q̍mIgg'qZ)|>SyQ. MS\EkͣGXZZٳ fQ(HuFkbaa?ֆm(v|^ZhWEkss51;! Bq8vΝ4M1ƠfccAkcY,kee8I`ADrqyضMgg'.]ԩSu RiJ,//#"cJ;7oܤP($ dvHӔ'O2::Jkkk1aklFk=gigD$Ny* qgCSSLLLpZ[[T֕GQDZDQZYxgymE"򏙙6n{̙^ccoR^==l"\}9֮$4|%о}P9fggw(-oIENDB`composer-master/icons/help-contents.png0000644000175000017500000000343313357046745016747 0ustar niknikPNG  IHDRw=sBIT|dIDATHukPラ/{Y`a Qt 0%1iRqv$ckh;]NCc4X[E$ *wAPpeY`˻A*g·3Ϝy!XVuMӬY]U|/Ȋ MN3 bL`0Ht.]i2O,֝o0j0!ȸyMy[r*}j*wM1," 9?U'~^buYkL/ZPDip' fG\b;.y0:(R{ ".A⦽\Jt,c jH&{+p]k'Jך~3&jSr!I Zh &%XNWP o~!t"Kp3Ϝe=\^&)Ϡ_Th :>2Y3Y6h]G!p,n+}wB0 {K \3a Azk}쿁DS@GHCH[؁Pj'=v؊f 4BW]'nzV@ cɵFk:8n:!Z T!L@x0 G 6ۗ}48^/k`1˟dM&;ªMKlh _HAX1 V1e0auQ-9Kqc\x,rC?βBzbWSֆB?6LMd2?AU ˟8e FWl gu#} Ɨ3 !`(Qq}"XYr"a-ݵ,r*Y[^bDDΟJP :ts[Q 9 C NwEqsoo_ڼ~(с0C^@ CqRcK $IB`#y RI]Etvzi[F18Y{2|2옿 87D3cK!r Rƞs#Ek j`-}Ge6x:8 Uӑ6!ۊP03+O QAi&.z;yeSʻ:!RM*/_/rihĀwN YV )D˱M3T4d811XCUVC=K$r,>Uת%.m^+oRCsgWP iד`qW/ŰAPZz7[S<*\سg?\noVNEJe>R ISSSM >|ָXjx(eiYnlQ>g#+jWv27W2;?]؅ IF2O-opg9r9yc0=ER"NuCnC*Gq#xilx2lWe 3ܛEEE{6Z}}toD^,,-XY.,ۏi0p`m/x>H]\XU$I?i-'SEgnnrH p Rp p 2pbigkQiuխR*ctt@ןc@EEE>q[̲_;[~ [Cÿ&bOGRtO<?ւ_n'FqqUUU7SКuK\N'3pBݻߏb~FIENDB`composer-master/icons/edit-cut.png0000644000175000017500000000170713357046745015704 0ustar niknikPNG  IHDRw=sBIT|d~IDATH͕MH#gǟɇ Luǁ Ƌv[)BZ]<lPR–j4ŃTɒt ƃֆhMVci2$Hݍ}ǟKL&dllcEQ*^>bRmmGT5ؿ x|r:nAEd2 MMMA˲$U !kڏf3F}hz<eD0^4P)T*o,]]]_G===?SFj2qqq1d6V1EQqU  %6${xa; GK{{{YIXFgٖDQLU$D"oSe6Jzi0n (* Ä|> ґrž>qj4!Z2AJ  Tj?Y[[+ndddd:t㳳666|ewt:'HH\<}d0^^^~]Pݟ(J?j,Mӯ644T*eYξu~~V{atL_p}qQ2tvv~h4G$I9k=99yX]сT3 Q*sppxxw $9o)Yo%pݤ  333ϛ(A0W @gtcci}}UUbpp$rjssjA`vkeeѡp8|9LMMJF^_+${zwZq#ArZr 8q:<^SSs3ܛ߿]pD<IENDB`composer-master/icons/insert-text.png0000644000175000017500000000205213357046745016446 0ustar niknikPNG  IHDRw=sBIT|dIDATHTO[U=ݶn) AdH9^$&f?t%:/̂%a586p d[4f VՍەn/?b[/9&F($5VkJ%(82E޲V;3/< /$t~mṶq]),j_[q;`[4 '2q!nw4QqiEcw%mjgAh7\ TৃXL$K4O .|KVO{kYiH^ҾJ;i)2Q/KΡKu- (M2Pi﹘Xȸ(adFJ~?@@̓)[B n~Y?ے\zqqq*;311qP03s-fa\5!V^nwX(lyH$Jr:~r|||.ル s!P0|544 Bᎎonoo?566vnb---@cGGGۼt\ Jt]* B岪TOOR"8z@)2|P6%^iY˽?މ' L0ƟGx0lKRXӴR=L$DJP(|D ÝXUXW<1??X[[Zi Ïlll X1vo_WBH3 #N$I L&ECCX33{e .vIENDB`composer-master/icons/edit-undo.png0000644000175000017500000000256613357046745016062 0ustar niknikPNG  IHDRw=sBIT|d-IDATHU[lUE]{f9RyEkR( F_Ch5jb4$*Gh( --BA^>=g4"+{5{ل1Ǘu&*uhb谯\ee5N ҼR ;/餡{"I,efE]:'3 "ڱMXۗ'S `k&ݼgA`={uߣ{KgΚig|?Bm(aw n;C% -݂m iݗMZ3kf[ZIl$=ύq=mO)&#>M :A`oLTĚ_ 0':[@$>jp? 낄*4 5vc#g9[D4z~{uE<@@8T<qd0=јe M(,QWǵ^؏!5$$A-c>'ݏUWTj<}Ţ Ͳ;;g[t~;uhC1::qxn/DޓVB^·G= 6?ŪoE~R&y/uZMȫI.6Bܒ?B qw2$P0|`k|?8=dխvVT* &=3CHq@(sցf–sz۹ߝ~[4vx:M48JbzofuF!690_RĖ״Q,3fTk [j Jc`H%T6HIe4yi{YZb sł4E:xrlC8*d(|}%(4i HA[V eV]PD 8Ѻ=Af mĦX)mbEQ+Jnkt }`b-ͶҬTJ,R(wEZ ? N{"DQ,(HJףF9 : !I/"SI\xxtn[G['#wÓIENDB`composer-master/icons/media-playback-stop.png0000644000175000017500000000263413357046745020014 0ustar niknikPNG  IHDRw=sBIT|dSIDATHOhy?WՕt'vFFHLDe䴇^Vddr!i]If*&"郚aGc$]]~L&N}.}U `@Z{h7\VD;`x9==} pfffZ[ZI$ Zk\Wq ^:`O<)N8A(p]wA$I0 y-o&c:::vDsss?W\ZUg&R:;;}?MvuD$_;w_Jk=`-u&&&&Ad񎎎lcik+1i&)<\ttt+c1;*Zn$A)y I/{=NGDŽaȋ/p]c3/"IKKK\x\.R!&''dM<4%"&DQD>1 xi%&&&}qɓ;am4ve}}>oEwee'"4MgϞ1::+b8IlG* n" C<ϫ:sݻ.K]Vs2r Tk پfz7oޤR匈޽{pY啱뺿 PÇc@ommܹCA`\DQ.ɾm+JXk_kEQy g `{{5?Nr9\׭ȟD[6'~pv=oE?:``Zo@nx("p OR=IENDB`composer-master/icons/edit-delete.png0000644000175000017500000000257013357046745016352 0ustar niknikPNG  IHDRw=sBIT|d/IDATHKl'4MIRJJAB<, RT CPtCP *qoU*EE UI Mc$83=7-+՜39ltnn6۱o[uHAS4UuTE.,,,dbx<~$MRz-iX,jLfԩSOaѕ'ڒEAwN5Q&MF*T,m{nÆ ouuuimmk|~_/ 7'5jR;}ɓ||nĹ;IjhG"9sC|?r {ŶGŢ4<<otvi>=>KSiSV4H$T* q٬|`pp!۷o'355ܹ9,LTU$Iu] 3d67N> cdEn8|%z{{cCCCr9aii'Np1,6+IlX,211 !V5۶)JtvvL&$~|@QTUA"TU,ˢT*8B V! oi$ t]GO ;LEQVw X׹lN3s4Ms]aXڱc04033=WG<Ϙ3Igd}&篸Ag?aqH~+j:sY |6?6y+MɨvգY( sF!__IGJ!Ӽsw1"N0 }?wQMA@/ә袿VYAB"F)UK-~@M] j|acYٞo ;@yAhӞ@נ@K72,3~aƎk 6Hk2$$j dkx:tS$$6 H5M+ h< 6%4k׮eIf}}C꺴ie1,ݞumqalv̲Æa`AUb5ǚil:.k , 4,H6=]O=z4 JMMMWڶ\.%u9!0i]z Nعsu֞x<~],gGseA Hzqt[ԓA5x8범\/*JM0S%XdJ4JDDЂ d"+![śO͞M}(ДlcyQƧޓAJj kТZQ ż\;Md3[S,!Ȣ|=`\)uNf>T[- _$Wgi[劄!Dz!J!;sNb0OhIXM!aBaՠH)<طnSan.W!%LR\*$I ۽v=gS_,1\8lZ0w G#NFZL3MA1 jVC׃, ;vm/:ķ_my.!}|_A^MBXױ鐎R/WRB1BY-ƚ^3J)kN)bZZq2})ORKA@iJhcD"XxMMMxZ|2adllL6d_і,kA-4Bhii\.gϞ=588WqLzqBRVIRP(f+:1c YERJ1HiB0 sܧ.]:yСŕRJ{d5C|ql700P]mo BpL)X#,QkF|366vӎDDDT@EA ]Kwg;>tڅ-RƒΗn2{~.{f>%\%? j./5W}uͷz3҇`CD<+m*/=3/<9U$W͈⡆j^U&D6xT=PaxBxKUNXԡдཕg+ @USE0HqauX1(6g9E4zN8Ҏ0/U fI8 0VV= wr()^+FⰓGiT3`]$0ZfUŲvLh# ߭VWeFu C\vu 3ZP^5 I.<B}jV6L t}iԠ)n%0{% Y& S>Zj!O2L0LJ$BK{E/ܸڡQ5:r=@yzQ 'C$0]h9HT'sgr4A)OVQ70$M:/AL9o }(2mLDF=e?~D=e`uӂGnsft7A{9B[$L#zs1k9U^%E3[ h<(ޮ qOa`)?z~HHI"Q! SmvW%"Ks2W73E p351GoSx F8 6Q~ʤjt 66մ9GQYa`b`rk,Rfyc(c,B6&$1&&?%:{ !yA^RVit9 [;< p{|>basѨ6n[kIE0`b2m3,/Գ?ȮkzF> >pA6).tc,N+Ω@Xq3nY ҜwNJOEpgfY7c hvcKXd<6#\@=qentO"%f2X:As 7Q5?#I\3 ^hL0AG3n\.XpBr`E2dɒ&.xFfg'x1ɢ󼾴Mbd \DȒ|Gby psh  ȧOMtE9WZ98㹐$3wI x碟>b_a*VQ>(`1y%W]-Ag3%A sKa۶m=b_$ J|˺m]Ů;<K/oIENDB`composer-master/icons/zoom-original.png0000644000175000017500000000207713357046745016755 0ustar niknikPNG  IHDRw=sBIT|dIDATHk\Uw}dLim$1L&/6H6AܸDe@Mł qQ1T0'D$ab2y5̝{qQ  AzeEy]ߍ"kAz3O?!CHb&wI)f G"yQ^mO@\s]ήH4^KI%ь&aP1?sö^}(@\8|G1IĐ!8p$xq4w<0/ qێɜX88^/:~̔܅ o ӾF@=opp{Dyu`S+vuS1sDL*v 4"e =&C!ZUI  !XͮSYPQB#,BT<_0 "\M %a ۈT +`L`M"* U&jW[I_i6l ȑ/X%*Xa֘5QMad#J8^j M>=MuvQ5nXni2$BQ %&Fi~R0>>mc% hG" "ℵl6;o;TM-<m!۵ݝa].ߌtZ5MFTzazzz!N/\)0 *米3g:::nzzzsՆ522߿ 윟^bq7_Jc˲Bxh1^GVVVE&Ɍb-F_en?B 2LbHR%yq$`yy6 t:D*ztmmBPVU\.wyqq?Zw]릦T*:QUuphhh?42"d2W*PDBA$u=R.gff_MLL({7쌿gIENDB`composer-master/icons/zoom-out.png0000644000175000017500000000207513357046745015756 0ustar niknikPNG  IHDRw=sBIT|dIDATHk\us=o3I:diiJ?H6E܈эnn*\`Eiq(JII&}޹scBSq>Cxk~B&ܖۏ5]p?C`{M -#ĥRݻJl/O j 0:b@+zs08WuΟZ:dR` B~4=xO@|St- \(h84[JJ{aw,3!N;mDtQ  Zb_P/*r7hŚ<.l{\BDhf%pt. 3`؀JpX~2o]Dr3]%JJCmWf;Q鍂07"~kepkS3YY@(wA ҅5LeTQU0 s{Pھ i3*PX;A&+kr* s3&Ofgg%ۨLvuZaM"1˲?~N|>Gq1JMD" ïq###>' UT*9X(( BXLj6VVVJd2L"8F>/-d2}-۶ B}DuRcccAi12b #●r `0yznni,~XęBB IENDB`composer-master/icons/edit-copy.png0000644000175000017500000000150113357046745016053 0ustar niknikPNG  IHDRw=sBIT|dIDATH?O@Ɵ;;a"Tb!,F*HQۯoбt`P2tC*_j(T:!QVHBY:Ο'qUOܝ>.,1"pwffvaaa6>b۶ٽY}dHZ 5xoq~:IP`A<$\CX3p$H=M;) 2K<%"a"ѯ3;TWJAt@Dll$R}^C)TUdtpKA bqqNJl6 qR D]7DtA( D|;;Cf4qZPJsR0t'''q @Vt5Ku'Eb~ Fh6@.<2Q·-˲ !3999Hz7r055"B<vJ~y._YD}oV? fIyOv!I)hPRC*-˅W,#ZP$JTjJRJӵz剋|zzccz@БYʷ>&1?@:.BIENDB`composer-master/icons/document-save-as.png0000644000175000017500000000247613357046745017345 0ustar niknikPNG  IHDRw=sBIT|dIDATHmoogٙY7ɘ+B )U)Rߡ!H"E[UJ*bV@!!ݙ=.Ε4s96cTի*ZׯĄXkk-"v7nh DDF+r9О;wN%:2UiJ1&(Ð pl6ZKy+/dlͿ-`bxA0 KvzSSSVçKOkUU P}21DQD$$I@D:爢LsjJeͿM~s,';0I>ncLɶwW,pΑe)lkC6w[>윣jH.UL{HWlݍMPD+SE `>g1t_$v} hZ9 2 4Hл YoDdAD)H7wޭd9P(Lݼj*7OHH C--JIT*pGGGŎ;VM1tbe_Sw"BNt:QlBoݺu#""&?c4UQQQ.l324z 1@HirppX`N6or%K{{%?5NY5 1 D*^k ꚚR4L>=*...5 `FЧ-Z8(8hCl6hzϓrEҔ}D"S} ֝;wޙaX$aj-&˗/ݭ#>yŊK8(J/`VTw͜93nF9RM2gٲy8|x:BBBa2)ˎ'ɬ]]]G٢vˤҫf;uԹ aǼS\+? 2Y;4nH$?^ѣϟWVnuek׏l6;R)Cѩ c>Z]RYz@'*`xuZ,~$ͅ`he{ >wsN< ^g={5Ly0JIIm߾}&JNAQ|v/WiG5Xzu`FFaI۷[>4/fa]aN{n- +++<==ADm] i+dw|guL{{a=*kZ֭[#/^\~р0>2!##|ĉl!.l8p@q?㳅a4eH~WB%VWWk?t$!dhɒ%;p:!iC*ư.ELyrpxdʧ R$ô423*M ŶR>qi~P͋sHr H Q r+:Ć F9[TT-B@_tHts|)Fc7@-oǹsTVZnZ'σtjQnbxc,nĢ?xXg@la.'$$dsNKp GCy["6ws""`jIII}l6,YWOS_55f~hEF Y .V} bC? ʺ4hC׿V%nVc=/ww<˲eY/sp}{x8OYXTB ]Q* C9KJl9p YfM___?eO4`0q|QB288յ3ƪOWUU3`&,c WRiT)c;c["@8FTfw}u;챱nj1A q|!`0 0ys]SSSv3 J* [yp󕢔c >mΝ;w(Or?֯_I4TJo"b !޽ !@)4 ~|2QMMKMMM =MG!WJ/UV)ן(N?R Br]WJē B9Wޞ99;;\Fѕ+WSeee]4833sҥKp8Mi4M||s֭?cF98{A"xyjj*y'V沝tzJ4}ddO+lzzd2v}}}-۩%u1A8A022|l6k-Zk R+d2dY- ͛7h^;J) r9\EJ?c!c:::dhh9*Lz7nZkh4:=c||~,8!Ql6}-fffÐsu bѠP(prJ I9RkY!K.5V9ϟʕ+bgg%Jp13??@&h[<;cZBJk˗ikkKڏMcZ1akmQcBJZ% trK>'ϣJ R?!H}gee)%ƘUSNJ .p9<?~QΞ=K>'jϞ=cyyTYkUJxQRH)gpp0CImH#zNT(~'Xsy-u٥%$NGQDp]|󼊔ÇwB_VUY,#ɤZ봐m4j5666sd2#ͣG $Q8}_l6ӧOyfBH qk7Zcf8}t:`yy0 d28SB|%Ǐ/|ڵ~kퟭZ$=?xB!wB[o?:ߖπ(A1 ?TLIENDB`composer-master/icons/document-save.png0000644000175000017500000000171013357046745016732 0ustar niknikPNG  IHDRw=sBIT|dIDATHTOK;I}3N<(^; ,%:e1ht=c6οڃa2@Cu5^WEI\E`f4M"9Es " IH86 CU3g"iRքa0 1`f݊2ٰ<ARd)YӐoJͽm\_y /T eJAeX,|uv8T$  ȡIUEJlFԫ/d:]Z`Re9|hXQKaqϞ. ϗ f`Rδ6YZwo$u,CyXnB( dU ߓCCW,MIYTyE׿cjT\<σ*d]?zX8ںճ3c%VA@,/$yN `cy~uC]@!dUs/0_p,9$L&/999̞dq_]۶4MCJTU`ooBumuu)`,H4Ԩ`wwd2Aw5ݻwWwvvZb_j۶!=RJk׮}ݶi0N߉1^dRz.grM)eo:^oX=zZ*"ĪHfF)噁<ٸww5'w5KmÆ+>6i"Ԧ\8|h]n^|/]Jt~ @TTUA3X6J\9ҵT>PEu9+'nFFA 4H0AI,%905P $R[qf  RSr^c)ɳ%Œ˙ @ \qs X.JUπ c2lxd]v8󩂨 s^J2zeRF@ZEcfǪ\%9VHp$$ bA  (WkҎI<#36 >{'pUdl!nسZea'UB'\"c H:Jv[H}:=oowՁYx뫓ԈMm҆E\v"dOm]mGڮ.VC!@BT0j*&sz2Tv!nA%}Agczs[AP֌L&{Bo~OGً; cBPK2IioN墀!E`񮪁N˿T6svc.JG <"O\4d;V:=ȩE[kD fqm*NKBeP+L;80M/) c]7 V]haOUmйf |0sz^KEj]]IENDB`composer-master/icons/insert-object.png0000644000175000017500000000211413357046745016727 0ustar niknikPNG  IHDRw=sBIT|dIDATHahUe2uؼrn~oF!y F!E!~RHX>_BL6'iss徧sfpy}s}yPU tHVTDD'UeDE/9uVt4;֚;qT?yn޼8WxgZ{ioo>Q֭[WUucǾ @ }czVw+VݸAnI&&&hfb86(ѣ u}ƘoO-FD@X0l#G}zIΜ=q_~]%V^֮_`Z_شm|'o✋*L2Βdho@MT ֦m<fo|&AP+♶vr!.#mPmp>:dȸ Ɩ+Z*{HXT* l<1;}8ȕK\O[[2eUO9UKde0|_IENDB`composer-master/icons/help-hint.png0000644000175000017500000000235313357046745016054 0ustar niknikPNG  IHDRw=sBIT|dIDATHkUߙ333Ivvt7-iPj1m#^"x* oTb+h?Ln6 n7d79ٙ3gf/9{Ρ`uK䑱@ nV;?:i/ޛ>}}ABS{| lv Ԯ1smA:xef$~#m[y9N&~Ak/?Ԯiv5U[ W?הGR~Vs,Gm9J}cCIX+,Ck|\Wt[6Xy!J$zdn+W6sRGLjR~OD9'^d D["է7߸o-LcSlvQ]1 ]]c(y_ UkG?~ccö8LVCHx4K,2 =}Gr/m@kOe]ñP* 8ZRW(:MmX(nfٱ,Ex0 ~cD}帤uݔi$pk8QMH|V9.U%dH8Z\bs>fvAt  m5㽅' \rǚ0yFPu1d64yLvV_ij USF,vE{KEh ÐL^-Q!+j'RfuX&8<I**LrÎeZYaT*n6n&hqھElږ= BOo"-[D]"'3݅R=8m\|f}[uZ0v[=2RPI!m_n݄PP?~0#>riB/מ?P*y>$ (i u[>sDn6 xjqG#VN 0X)jp9 H/)ϲlP@!۶倮 I N#Wβ,0,!bBatbbVww(!ģiH$.廝G`Fjtx3g~284$K_.G/.V.IENDB`composer-master/icons/composer.svg0000644000175000017500000001567313357046745016037 0ustar niknik image/svg+xml composer-master/icons/edit-paste.png0000644000175000017500000000154113357046745016221 0ustar niknikPNG  IHDRw=sBIT|dIDATHAo[EϹw8#܊eBH;vKbXJ+PA 'Nxv~W7W3 q{ϳɃ #aFЈ/n1J{O0OhG_m%V!D%Iz4Sן|wn/@(UFiK`"GT?(bjz er = WXՈȝ2c.IU3rK=h%1i_|h* "BAiE\ĠRhDTSD!YD!Κ${WE3p`GT~ 0WJ9^r+_uZ &.4gp+ZU~~ Ψ>F:ֵxg!IENDB`composer-master/src/0000755000175000017500000000000013357046745013127 5ustar niknikcomposer-master/src/operation.cc0000644000175000017500000000036113357046745015436 0ustar niknik#include "operation.hh" QDataStream& operator<<(QDataStream& stream, const Operation& op) { stream << op.m_params; return stream; } QDataStream& operator>>(QDataStream& stream, Operation& op) { stream >> op.m_params; return stream; } composer-master/src/types.hh0000644000175000017500000000034713357046745014620 0ustar niknik#pragma once #ifdef WIN32 typedef signed char int8_t; typedef unsigned char uint8_t; typedef short int16_t; typedef unsigned short uint16_t; typedef int int32_t; typedef unsigned int uint32_t; #else #include #endif composer-master/src/songwriter-lrc.cc0000644000175000017500000000320513357046745016417 0ustar niknik#include "songwriter.hh" #include "config.hh" #include "util.hh" #include #include void LRCWriter::writeLRC(bool enhancedLRC) const { QFile f(path + "/song.lrc"); if (!f.open(QFile::WriteOnly | QFile::Truncate)) throw std::runtime_error("Couldn't open target file"); QTextStream out(&f); out.setCodec("UTF-8"); // Meta fields out << "[ti:" << (s.title.isEmpty() ? "Unknown" : s.title) << "]\n"; out << "[ar:" << (s.artist.isEmpty() ? "Unknown" : s.artist) << "]\n"; out << "[re:" << PACKAGE << "]\n"; out << "[ve:" << VERSION << "]\n"; if (!s.creator.isEmpty()) out << "[by:" << s.creator << "]\n"; // Loop through the notes const Notes& notes = s.getVocalTrack().notes; for (int i = 0; i < notes.size(); ++i) { const Note& n = notes[i]; if (n.type == Note::SLEEP) continue; // Put timestamp before new phrases if (i == 0 || n.lineBreak) out << '\n' << sec2timestamp(n.begin); if(enhancedLRC) //if using "enhanced LRC use <> timestamps! out << n.syllable << EnhancedLRCsec2timestamp(n.begin); else // Output the lyrics out << n.syllable; } out << '\n'; } QString LRCWriter::sec2timestamp(double sec) const { double modsec = std::fmod(sec, 60.0); return QString("[%1:%2.%3]") .arg(int(sec/60), 2, 10, QChar('0')) .arg(int(modsec), 2, 10, QChar('0')) .arg(int(100 * (modsec - int(modsec))), 2, 10, QChar('0')); } QString LRCWriter::EnhancedLRCsec2timestamp(double sec) const { double modsec = std::fmod(sec, 60.0); return QString("<%1:%2.%3>") .arg(int(sec/60), 2, 10, QChar('0')) .arg(int(modsec), 2, 10, QChar('0')) .arg(int(100 * (modsec - int(modsec))), 2, 10, QChar('0')); } composer-master/src/editorapp.cc0000644000175000017500000010646713357046745015443 0ustar niknik#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.hh" #include "editorapp.hh" #include "notelabel.hh" #include "songparser.hh" #include "songwriter.hh" #include "textcodecselector.hh" #include "gettingstarted.hh" #include "busydialog.hh" namespace { static const QString PROJECT_SAVE_FILE_EXTENSION = "songproject"; // FIXME: Nice extension here static const quint32 PROJECT_SAVE_FILE_MAGIC = 0x50455350; static const quint32 PROJECT_SAVE_FILE_VERSION = 101; // File format version 1.01 static const QDataStream::Version PROJECT_SAVE_FILE_STREAM_VERSION = QDataStream::Qt_4_7; // Helper function scans widget's children and sets their status tips to their tooltips void handleTips(QWidget *widget) { QObjectList objs = widget->children(); objs.push_back(widget); // Handle the parent too for (int i = 0; i < objs.size(); ++i) { QWidget *child = qobject_cast(objs[i]); if (!child) continue; if (!child->toolTip().isEmpty() && child->statusTip().isEmpty()) child->setStatusTip(child->toolTip()); } } } EditorApp::EditorApp(QWidget *parent) : QMainWindow(parent), gettingStarted(), noteGraph(), player(), synth(), statusbarProgress(), projectFileName(), latestPath(QDir::homePath()), currentBufferPlayer() { ui.setupUi(this); readSettings(); ui.helpDock->setVisible(false); // Some icons to make menus etc prettier setWindowIcon(QIcon::fromTheme("composer", QIcon(":/icons/composer.png"))); ui.actionNew->setIcon(QIcon::fromTheme("document-new", QIcon(":/icons/document-new.png"))); ui.actionOpen->setIcon(QIcon::fromTheme("document-open", QIcon(":/icons/document-open.png"))); ui.actionSave->setIcon(QIcon::fromTheme("document-save", QIcon(":/icons/document-save.png"))); ui.actionSaveAs->setIcon(QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png"))); ui.actionExit->setIcon(QIcon::fromTheme("application-exit", QIcon(":/icons/application-exit.png"))); ui.actionUndo->setIcon(QIcon::fromTheme("edit-undo", QIcon(":/icons/edit-undo.png"))); ui.actionRedo->setIcon(QIcon::fromTheme("edit-redo", QIcon(":/icons/edit-redo.png"))); ui.actionCut->setIcon(QIcon::fromTheme("edit-cut", QIcon(":/icons/edit-cut.png"))); ui.actionCopy->setIcon(QIcon::fromTheme("edit-copy", QIcon(":/icons/edit-copy.png"))); ui.actionPaste->setIcon(QIcon::fromTheme("edit-paste", QIcon(":/icons/edit-paste.png"))); ui.actionDelete->setIcon(QIcon::fromTheme("edit-delete", QIcon(":/icons/edit-delete.png"))); ui.actionSelectAll->setIcon(QIcon::fromTheme("edit-select-all", QIcon(":/icons/edit-select-all.png"))); ui.actionSelectAllAfter->setIcon(QIcon::fromTheme("edit-select-all", QIcon(":/icons/edit-select-all.png"))); ui.menuPreferences->setIcon(QIcon::fromTheme("preferences-other", QIcon(":/icons/preferences-other.png"))); ui.actionMusicFile->setIcon(QIcon::fromTheme("insert-object", QIcon(":/icons/insert-object.png"))); ui.actionAdditionalMusicFile->setIcon(QIcon::fromTheme("insert-object", QIcon(":/icons/insert-object.png"))); ui.actionLyricsFromFile->setIcon(QIcon::fromTheme("insert-text", QIcon(":/icons/insert-text.png"))); ui.actionLyricsFromClipboard->setIcon(QIcon::fromTheme("insert-text", QIcon(":/icons/insert-text.png"))); ui.actionLyricsFromLRCFile->setIcon(QIcon::fromTheme("insert-text", QIcon(":/icons/insert-text.png"))); ui.actionZoomIn->setIcon(QIcon::fromTheme("zoom-in", QIcon(":/icons/zoom-in.png"))); ui.actionZoomOut->setIcon(QIcon::fromTheme("zoom-out", QIcon(":/icons/zoom-out.png"))); ui.actionResetZoom->setIcon(QIcon::fromTheme("zoom-original", QIcon(":/icons/zoom-original.png"))); ui.actionHelp->setIcon(QIcon::fromTheme("help-contents", QIcon(":/icons/help-contents.png"))); ui.actionWhatsThis->setIcon(QIcon::fromTheme("help-hint", QIcon(":/icons/help-hint.png"))); ui.actionAbout->setIcon(QIcon::fromTheme("help-about", QIcon(":/icons/help-about.png"))); ui.cmdPlay->setIcon(QIcon::fromTheme("media-playback-start", QIcon(":/icons/media-playback-start.png"))); // Statusbar stuff statusbarButton = new QPushButton(NULL); ui.statusbar->addPermanentWidget(statusbarButton); statusbarButton->setText(tr("&Abort")); statusbarButton->hide(); statusbarProgress = new QProgressBar(NULL); ui.statusbar->addPermanentWidget(statusbarProgress); statusbarProgress->hide(); setWindowModified(false); updateMenuStates(); // Audio stuff player = new QMediaPlayer(this); player->setNotifyInterval(100); bufferPlayers[0] = new BufferPlayer(this); bufferPlayers[1] = new BufferPlayer(this); // Audio info QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); qDebug() << "QAudioDevice info"; qDebug() << "Supported codecs:"; foreach (const QString& codec, info.supportedCodecs()) qDebug() << codec; qDebug() << "Supported sample rates:"; foreach (const int& num, info.supportedSampleRates()) qDebug() << num; qDebug() << "Supported sample sizes:"; foreach (const int& num, info.supportedSampleSizes()) qDebug() << num; qDebug() << "Supported sample types:"; foreach (const int& num, info.supportedSampleTypes()) qDebug() << num; qDebug() << "Supported byte orders:"; foreach (const int& num, info.supportedByteOrders()) qDebug() << num; qDebug() << "Supported channel counts:"; foreach (const int& num, info.supportedChannelCounts()) qDebug() << num; // Audio signals connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(audioTick(qint64))); connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(playerStateChanged(QMediaPlayer::State))); connect(player, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(playerError(QMediaPlayer::Error))); connect(player, SIGNAL(metaDataChanged()), this, SLOT(metaDataChanged())); connect(player, SIGNAL(playbackRateChanged(qreal)), this, SLOT(playbackRateChanged(qreal))); // The piano keys piano = new Piano(ui.topFrame); QHBoxLayout *hl = new QHBoxLayout(ui.topFrame); hl->addWidget(piano); hl->addWidget(ui.noteGraphScroller); ui.topFrame->setLayout(hl); connect(ui.noteGraphScroller->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updatePiano(int))); // NoteGraph setup down here so that the objects we setup signals are already created setupNoteGraph(); updateNoteInfo(NULL); connect(ui.cmdMusicFile, SIGNAL(clicked()), this, SLOT(on_actionMusicFile_triggered())); piano->updatePixmap(noteGraph); song.reset(new Song); // Set status tips to tool tips handleTips(ui.tabGeneral); handleTips(ui.tabSong); QSettings settings; if (settings.value("showhelp", true).toBool()) on_actionGettingStarted_triggered(); } void EditorApp::setupNoteGraph() { noteGraph = new NoteGraphWidget(NULL); ui.noteGraphScroller->setWidget(noteGraph); // Deletes previous widget // Splitter sizes cannot be set through designer :( //QList ss; ss.push_back(700); ss.push_back(300); // Proportions, not pixels //ui.splitter->setSizes(ss); // Signals/slots connect(noteGraph, SIGNAL(operationDone(const Operation&)), this, SLOT(operationDone(const Operation&))); connect(noteGraph, SIGNAL(updateNoteInfo(NoteLabel*)), this, SLOT(updateNoteInfo(NoteLabel*))); connect(noteGraph, SIGNAL(statusBarMessage(QString)), this, SLOT(statusBarMessage(QString))); connect(statusbarButton, SIGNAL(clicked()), noteGraph, SLOT(abortPitch())); connect(ui.noteGraphScroller->horizontalScrollBar(), SIGNAL(valueChanged(int)), noteGraph, SLOT(updatePitch())); connect(ui.noteGraphScroller->verticalScrollBar(), SIGNAL(valueChanged(int)), noteGraph, SLOT(updatePitch())); connect(ui.actionCut, SIGNAL(triggered()), noteGraph, SLOT(cut())); connect(ui.actionCopy, SIGNAL(triggered()), noteGraph, SLOT(copy())); connect(ui.actionPaste, SIGNAL(triggered()), noteGraph, SLOT(paste())); connect(ui.cmdTimeSentence, SIGNAL(pressed()), noteGraph, SLOT(timeSentence())); connect(ui.cmdTimeNote,SIGNAL(pressed()),noteGraph, SLOT(timeSyllable())); connect(ui.cmdSkipSentence, SIGNAL(pressed()), noteGraph, SLOT(selectNextSentenceStart())); connect(ui.chkGrabSeekHandle, SIGNAL(toggled(bool)), noteGraph, SLOT(setSeekHandleWrapToViewport(bool))); noteGraph->setSeekHandleWrapToViewport(ui.chkGrabSeekHandle->isChecked()); connect(noteGraph, SIGNAL(analyzeProgress(int, int)), this, SLOT(analyzeProgress(int, int))); connect(noteGraph, SIGNAL(seeked(qint64)), player, SLOT(setPosition(qint64))); } void EditorApp::operationDone(const Operation &op) { //std::cout << "Push op: " << op.dump() << std::endl; setWindowModified(true); opStack.push(op); updateMenuStates(); redoStack.clear(); } void EditorApp::statusBarMessage(const QString& message) { statusBar()->showMessage(message); } void EditorApp::doOpStack() { BusyDialog busy(this, 20); noteGraph->clearNotes(); QString newMusic = ""; OperationStack::iterator opit = opStack.begin(); // Re-apply all operations in the stack while (opit != opStack.end()) { //std::cout << "Doing op: " << opit->dump() << std::endl; busy(); bool erased = false; try { if (opit->op() == "META") { // META ops are handled differently: // They are run once and then removed from the stack. // They are written to disk when saving though. QString metakey = opit->s(1), metavalue = opit->s(2); opit = opStack.erase(opit); erased = true; if (metakey == "MUSICFILE") { newMusic = metavalue; } else if (metakey == "TITLE") { song->title = metavalue; } else if (metakey == "ARTIST") { song->artist = metavalue; } else if (metakey == "GENRE") { song->genre = metavalue; } else if (metakey == "DATE") { song->year = metavalue; } else throw std::runtime_error("Unknown META key " + metakey.toStdString()); updateSongMeta(true); } else // Regular note operations noteGraph->doOperation(*opit, Operation::NO_EMIT | Operation::NO_UPDATE); } catch (std::exception& e) { std::cout << e.what() << std::endl; } if (!erased) { ++opit; erased = false; } } noteGraph->updateNotes(); noteGraph->startNotePixmapUpdates(); if (!newMusic.isEmpty()) setMusic(newMusic); updateMenuStates(); } void EditorApp::updateMenuStates() { // File menu ui.actionSave->setEnabled(isWindowModified()); // Edit menu ui.actionUndo->setEnabled(!opStack.isEmpty() && opStack.top().op() != "BLOCK"); ui.actionRedo->setEnabled(!redoStack.isEmpty()); bool hasSelectedNotes = (noteGraph && noteGraph->selectedNote()); ui.actionCut->setEnabled(hasSelectedNotes); ui.actionCopy->setEnabled(hasSelectedNotes); ui.actionDelete->setEnabled(hasSelectedNotes); bool hasNotes = (noteGraph && !noteGraph->noteLabels().isEmpty()); ui.actionSelectAll->setEnabled(hasNotes); ui.actionSelectAllAfter->setEnabled(hasSelectedNotes); bool zoom = (noteGraph && noteGraph->getZoomLevel() != 100); ui.actionResetZoom->setEnabled(zoom); // Window title updateTitle(); } void EditorApp::updateTitle() { // Project name QFileInfo finfo(projectFileName); QString proName = finfo.fileName().isEmpty() ? tr("Untitled") : finfo.fileName(); // Zoom level QString zoom = ""; if (noteGraph && noteGraph->getZoomLevel() != 100) zoom = " - " + QString::number(noteGraph->getZoomLevel()) + " %"; // Set the title setWindowTitle(QString(PACKAGE) + " - " + proName + "[*]" + zoom); } void EditorApp::updateNoteInfo(NoteLabel *note) { if (!note || !noteGraph || noteGraph->selectedNotes().size() > 1) { ui.cmdSplit->setEnabled(false); ui.cmdInsert->setEnabled(false); ui.lblCurrentSentence->setText(tr("Current phrase:") + " -"); // The next ones are available also for multi-note selections, so let's not disable them if (!note || !noteGraph) { ui.cmbNoteType->setEnabled(false); ui.chkFloating->setEnabled(false); ui.chkLineBreak->setEnabled(false); } } if (note && noteGraph) { if (noteGraph->selectedNotes().size() == 1) { // These are only available for single note selections ui.cmdSplit->setEnabled(true); ui.cmdInsert->setEnabled(true); ui.lblCurrentSentence->setText(tr("Current phrase: ") + "" + noteGraph->getPrevSentence() + " " + noteGraph->getCurrentSentence() + ""); } // The next ones are available also for multi-note selections ui.cmbNoteType->setEnabled(true); ui.cmbNoteType->setCurrentIndex(note->note().getTypeInt()); ui.chkFloating->setEnabled(true); ui.chkFloating->setChecked(note->isFloating()); ui.chkLineBreak->setEnabled(true); ui.chkLineBreak->setChecked(note->note().lineBreak); } updateMenuStates(); // Update piano if (piano && noteGraph) { piano->updatePixmap(noteGraph); } } void EditorApp::analyzeProgress(int value, int maximum) { if (statusbarProgress) { if (value == maximum) { statusbarProgress->hide(); statusbarButton->hide(); } else { statusbarProgress->setMaximum(maximum); statusbarProgress->setValue(value); statusbarProgress->show(); statusbarButton->show(); } } } // File menu void EditorApp::on_actionNew_triggered() { if (promptSaving()) { player->setMedia(NULL); song.reset(new Song); setupNoteGraph(); projectFileName = ""; opStack.clear(); redoStack.clear(); updateNoteInfo(NULL); statusbarProgress->hide(); statusbarButton->hide(); ui.txtTitle->clear(); ui.txtArtist->clear(); ui.txtGenre->clear(); ui.txtYear->clear(); ui.valMusicFile->clear(); setWindowModified(false); } updateMenuStates(); } void EditorApp::on_actionOpen_triggered() { if (!promptSaving()) return; QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), latestPath, tr("All supported formats") + "(*." + PROJECT_SAVE_FILE_EXTENSION + " *.xml *.mid *.ini *.txt);;" + tr("Project files") +" (*." + PROJECT_SAVE_FILE_EXTENSION + ") ;;" + tr("SingStar XML") + " (*.xml);;" + tr("Frets on Fire MIDI") + " (*.mid *.ini);;" + tr("UltraStar TXT") + " (*.txt);;" + tr("LRC") + " (*.lrc);;" + tr("All files") + " (*)"); if (!fileName.isNull()) openFile(fileName); } void EditorApp::openFile(QString fileName) { if (!fileName.isNull()) { QFileInfo finfo(fileName); latestPath = finfo.path(); try { if (finfo.suffix() == PROJECT_SAVE_FILE_EXTENSION) { // Project file loading QFile f(fileName); if (f.open(QFile::ReadOnly)) { opStack.clear(); QDataStream in(&f); quint32 magic; in >> magic; if (magic == PROJECT_SAVE_FILE_MAGIC) { quint32 version; in >> version; if (version == PROJECT_SAVE_FILE_VERSION) { in.setVersion(PROJECT_SAVE_FILE_STREAM_VERSION); while (!in.atEnd()) { Operation op; in >> op; //std::cout << "Loaded op: " << op.dump() << std::endl; opStack.push(op); } doOpStack(); projectFileName = fileName; setWindowModified(false); updateNoteInfo(NULL); // Title bar if (noteGraph) noteGraph->scrollToFirstNote(); } else QMessageBox::critical(this, tr("Error opening file!"), tr("Unsupported project file version in %1").arg(fileName)); } else QMessageBox::critical(this, tr("Error opening file!"), tr("File %1 doesn't look like a project file.").arg(fileName)); } else QMessageBox::critical(this, tr("Error opening file!"), tr("Couldn't open file %1 for reading.").arg(fileName)); } else { // Song import QString musicfile = song->music["EDITOR"]; // Preserve the music file song.reset(new Song(QString(finfo.path()+"/"), finfo.fileName())); song->music["EDITOR"] = musicfile; noteGraph->setLyrics(song->getVocalTrack()); updateSongMeta(true); } } catch (const std::exception& e) { QMessageBox::critical(this, tr("Error loading file!"), e.what()); } } } void EditorApp::on_actionSave_triggered() { if (projectFileName.isEmpty()) on_actionSaveAs_triggered(); else saveProject(projectFileName); } void EditorApp::on_actionSaveAs_triggered() { QString fileName = QFileDialog::getSaveFileName(this, tr("Save Project"), latestPath, tr("Project files ") + "(*." + PROJECT_SAVE_FILE_EXTENSION + ");;" + tr("All files") + " (*)"); if (!fileName.isNull()) { // Add the correct suffix if it is missing QFileInfo finfo(fileName); latestPath = finfo.path(); if (finfo.suffix() != PROJECT_SAVE_FILE_EXTENSION) fileName += "." + PROJECT_SAVE_FILE_EXTENSION; saveProject(fileName); } } bool EditorApp::promptSaving() { updateSongMeta(); // Make sure the stuff in textboxes is updated if (isWindowModified()) { QMessageBox::StandardButton b = QMessageBox::question(this, tr("Unsaved changes"), tr("There are unsaved changes, which would be lost. Save now?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); switch(b) { case QMessageBox::Yes: on_actionSave_triggered(); case QMessageBox::No: return true; default: return false; } } return true; } void EditorApp::saveProject(QString fileName) { QFile f(fileName); if (f.open(QFile::WriteOnly)) { QDataStream out(&f); out.setVersion(PROJECT_SAVE_FILE_STREAM_VERSION); out << PROJECT_SAVE_FILE_MAGIC << PROJECT_SAVE_FILE_VERSION; // Notes foreach (Operation op, opStack) out << op; // Song metadata out << Operation("META", "TITLE", song->title) << Operation("META", "ARTIST", song->artist) << Operation("META", "GENRE", song->genre) << Operation("META", "DATE", song->year) << Operation("META", "MUSICFILE", song->music["EDITOR"]); projectFileName = fileName; setWindowModified(false); } else QMessageBox::critical(this, tr("Error saving file!"), tr("Couldn't open file %1 for saving.").arg(fileName)); updateMenuStates(); } void EditorApp::exportSong(QString format, QString dialogTitle) { QString path = QFileDialog::getExistingDirectory(this, dialogTitle, latestPath); if (!path.isNull()) { latestPath = path; // Sync notes if (noteGraph) song->insertVocalTrack(TrackName::LEAD_VOCAL, noteGraph->getVocalTrack()); // Pick exporter try { if (format == "XML") SingStarXMLWriter(*song.data(), path); else if (format == "TXT") UltraStarTXTWriter(*song.data(), path); else if (format == "INI") FoFMIDIWriter(*song.data(), path); else if (format == "LRC") LRCWriter(*song.data(), path, false); else if (format == "ENHANCED LRC") LRCWriter(*song.data(), path, true); else if (format == "SMM") SMMWriter(*song.data(), path); } catch (const std::exception& e) { QMessageBox::critical(this, tr("Error exporting song!"), e.what()); } } } void EditorApp::on_actionSingStarXML_triggered() { exportSong("XML", tr("Export SingStar XML")); } void EditorApp::on_actionUltraStarTXT_triggered() { exportSong("TXT", tr("Export UltraStar TXT")); } void EditorApp::on_actionFoFMIDI_triggered() { exportSong("INI", tr("Export Frets on Fire MIDI")); } void EditorApp::on_actionLRC_triggered() { exportSong("LRC", tr("Export LRC")); } void EditorApp::on_actionEnhanced_LRC_triggered() { exportSong("ENHANCED LRC", tr("Export Enhanced LRC")); } void EditorApp::on_actionSoramimiTXT_triggered() { exportSong("SMM", tr("Export Soramimi TXT")); } void EditorApp::on_actionLyricsToFile_triggered() { QString fileName = QFileDialog::getSaveFileName(this, tr("Export to plain text lyrics"), latestPath); if (!fileName.isNull()) { QFile f(fileName); QFileInfo finfo(f); latestPath = finfo.path(); if (f.open(QFile::WriteOnly | QFile::Truncate)) { QTextStream out(&f); out << noteGraph->dumpLyrics(); } else QMessageBox::critical(this, tr("Error saving file!"), tr("Couldn't open file %1 for writing.").arg(fileName)); } } void EditorApp::on_actionLyricsToClipboard_triggered() { QApplication::clipboard()->setText(noteGraph->dumpLyrics()); } void EditorApp::on_actionExit_triggered() { close(); } // Edit menu void EditorApp::on_actionUndo_triggered() { if (opStack.isEmpty()) return; if (opStack.top().op() == "BLOCK") { updateMenuStates(); return; } else if (opStack.top().op() == "COMBINER") { // Special handling to add the ops in the right order try { int count = opStack.top().i(1); int start = opStack.size() - count - 1; for (int i = start; i < start + count; ++i) { redoStack.push(opStack.at(i)); } opStack.remove(start, count); } catch (std::runtime_error&) { QMessageBox::critical(this, tr("Error!"), tr("Corrupted undo stack.")); } } redoStack.push(opStack.top()); opStack.pop(); doOpStack(); } void EditorApp::on_actionRedo_triggered() { if (redoStack.isEmpty()) return; else if (redoStack.top().op() == "COMBINER") { // Special handling to add the ops in the right order try { int count = redoStack.top().i(1); int start = redoStack.size() - count - 1; for (int i = start; i < start + count; ++i) { opStack.push(redoStack.at(i)); } redoStack.remove(start, count); } catch (std::runtime_error&) { QMessageBox::critical(this, tr("Error!"), tr("Corrupted redo stack.")); } } opStack.push(redoStack.top()); redoStack.pop(); doOpStack(); } void EditorApp::on_actionDelete_triggered() { if (noteGraph) noteGraph->del(noteGraph->selectedNote()); } void EditorApp::on_actionSelectAll_triggered() { if (noteGraph) noteGraph->selectAll(); } void EditorApp::on_actionSelectAllAfter_triggered() { if (noteGraph) noteGraph->selectAllAfter(); } void EditorApp::on_actionAntiAliasing_toggled(bool checked) { QSettings settings; // Default QSettings parameters given in main() settings.setValue("anti-aliasing", checked); } // Insert menu void EditorApp::setMusic(QString filepath, bool primary) { ui.valMusicFile->setText(filepath); song->music[primary ? "EDITOR" : "ADDITIONAL"] = filepath; setWindowModified(true); updateMenuStates(); if (primary) { // Metadata is updated when it becomes available (signal) player->setMedia(QMediaContent(QUrl::fromLocalFile(filepath))); noteGraph->updateMusicPos(0, false); // Fire up analyzer noteGraph->analyzeMusic(filepath); } else noteGraph->analyzeMusic(filepath, 1); } void EditorApp::on_actionMusicFile_triggered() { QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), latestPath, tr("Music files") + " (*.mp3 *.ogg *.wav *.wma *.flac)"); if (!fileName.isNull()) { QFileInfo finfo(fileName); latestPath = finfo.path(); setMusic(fileName); } } void EditorApp::on_actionAdditionalMusicFile_triggered() { // FIXME: Duplication with on_actionMusicFile_triggered() QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), latestPath, tr("Music files") + " (*.mp3 *.ogg *.wav *.wma *.flac)"); if (!fileName.isNull()) { QFileInfo finfo(fileName); latestPath = finfo.path(); setMusic(fileName, false); } } void EditorApp::on_actionLyricsFromFile_triggered() { if ((noteGraph && noteGraph->noteLabels().empty()) || QMessageBox::question(this, tr("Replace lyrics"), tr("Loading lyrics from a file will replace the existing ones. Continue?"), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), latestPath, tr("Text files (*.txt)")); if (!fileName.isNull()) { QFile file(fileName); QFileInfo finfo(file); latestPath = finfo.path(); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; QString text = TextCodecSelector::readAllAndHandleEncoding(file, this); if (text != "") { if (SongParser::looksLikeSongFile(text) && QMessageBox::question(this, tr("Song file detected"), tr("The file you are opening doesn't look like plain text lyrics, but rather an actual song file. Would you like to reload it as such?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { openFile(fileName); } else noteGraph->setLyrics(text); } } } } void EditorApp::on_actionLyricsFromClipboard_triggered() { const QClipboard *clipboard = QApplication::clipboard(); const QMimeData *mimeData = clipboard->mimeData(); if (mimeData->hasText() && !mimeData->text().isEmpty()) { QString text = mimeData->text(); if ((noteGraph && noteGraph->noteLabels().empty()) || QMessageBox::question(this, tr("Replace lyrics"), tr("Pasting lyrics from clipboard will replace the existing ones. Continue?"), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { noteGraph->setLyrics(text); } } else { QMessageBox::warning(this, tr("No text to paste"), tr("No suitable data on the clipboard.")); } } void EditorApp::on_actionLyricsFromLRCFile_triggered() { QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), latestPath, tr("LRC/Soramimi timecodes") + " (*.lrc *.txt)"); openFile(fileName); } // View menu void EditorApp::on_actionZoomIn_triggered() { if (noteGraph) noteGraph->zoom(1.0); } void EditorApp::on_actionZoomOut_triggered() { if (noteGraph) noteGraph->zoom(-1.0); } void EditorApp::on_actionResetZoom_triggered() { if (noteGraph) noteGraph->zoom(getNaN()); } // Help menu void EditorApp::on_actionGettingStarted_triggered() { if (!gettingStarted) gettingStarted = new GettingStartedDialog(this); gettingStarted->show(); } void EditorApp::on_actionWhatsThis_triggered() { QWhatsThis::enterWhatsThisMode(); } void EditorApp::on_actionAboutQt_triggered() { QApplication::aboutQt(); } void EditorApp::on_actionAbout_triggered() { AboutDialog aboutDialog(this); aboutDialog.exec(); } // Misc stuff void EditorApp::updateSongMeta(bool readFromSongToUI) { if (!song) return; if (!readFromSongToUI) { if (ui.txtTitle->text() != song->title) { song->title = ui.txtTitle->text(); } if (ui.txtArtist->text() != song->artist) { song->artist = ui.txtArtist->text(); } if (ui.txtGenre->text() != song->genre) { song->genre = ui.txtGenre->text(); } if (ui.txtYear->text() != song->year) { song->year = ui.txtYear->text(); } } else { if (!song->title.isEmpty()) ui.txtTitle->setText(song->title); if (!song->artist.isEmpty()) ui.txtArtist->setText(song->artist); if (!song->genre.isEmpty()) ui.txtGenre->setText(song->genre); if (!song->year.isEmpty()) ui.txtYear->setText(song->year); } } void EditorApp::metaDataChanged() { if (player) { //foreach (const QString& str, player->availableMetaData()) // qDebug() << str; if (!player->metaData("Title").toString().isEmpty()) song->title = player->metaData("Title").toString(); if (!player->metaData("AlbumArtist").toString().isEmpty()) song->artist = player->metaData("AlbumArtist").toString(); if (!player->metaData("Genre").toString().isEmpty()) song->genre = player->metaData("Genre").toString(); if (!player->metaData("Year").toString().isEmpty()) song->year = player->metaData("Year").toString(); updateSongMeta(true); } } void EditorApp::playButton() { if (player && player->state() == QMediaPlayer::PlayingState) { ui.cmdPlay->setText(tr("Pause (P)")); ui.cmdPlay->setIcon(QIcon::fromTheme("media-playback-pause", QIcon(":/icons/media-playback-pause.png"))); ui.cmdPlay->setShortcut(QKeySequence("P")); if (ui.chkSynth->isChecked()) on_chkSynth_clicked(true); } else { ui.cmdPlay->setText(tr("Play (P)")); ui.cmdPlay->setIcon(QIcon::fromTheme("media-playback-start", QIcon(":/icons/media-playback-start.png"))); ui.cmdPlay->setShortcut(QKeySequence("P")); on_chkSynth_clicked(false); } } void EditorApp::on_chkSynth_clicked(bool checked) { if (checked && player && player->state() == QMediaPlayer::PlayingState) { synth.reset(new Synth); connect(synth.data(), SIGNAL(playBuffer(QByteArray)), this, SLOT(playBuffer(QByteArray))); if (player) player->setVolume(66); } else if (!checked) { synth.reset(); if (player) player->setVolume(100); } } void EditorApp::on_cmdPlay_clicked() { if (player) { // Can't use currentMedia().isNull() because it will always return false after first set if (player->currentMedia().canonicalUrl().isEmpty()) { on_actionMusicFile_triggered(); } else { if (player->state() == QMediaPlayer::PlayingState) player->pause(); else player->play(); } } } void EditorApp::audioTick(qint64 time) { if (noteGraph && player) noteGraph->updateMusicPos(time, (player->state() == QMediaPlayer::PlayingState ? true : false)); if (noteGraph && synth) { // Here we create some notes for the synthesizer to use. // We don't simply pass the whole list because then there // would be two threads working on the same list. SynthNotes notes; const NoteLabels &nls = noteGraph->noteLabels(); int numberOfNotesToPass = 12; for (NoteLabels::const_iterator it = nls.begin(); it != nls.end() && numberOfNotesToPass > 0; ++it) { if ((*it)->note().begin >= time / 1000.0) { notes.push_back(SynthNote((*it)->note())); --numberOfNotesToPass; } } synth->tick(time, player ? player->playbackRate() : 1.0, notes); } } void EditorApp::playerStateChanged(QMediaPlayer::State state) { playButton(); if (state != QMediaPlayer::PlayingState) { noteGraph->stopMusic(); } else if (!noteGraph->selectedNote() && !noteGraph->noteLabels().isEmpty()) { noteGraph->selectNote(noteGraph->noteLabels().front()); } } void EditorApp::playerError(QMediaPlayer::Error) { QString errst(tr("Error playing audio!")); if (player) errst += " " + player->errorString(); QMessageBox::critical(this, tr("Playback error"), errst); } void EditorApp::playbackRateChanged(qreal rate) { if (noteGraph) noteGraph->playbackRateChanged(rate); } void EditorApp::playBuffer(const QByteArray& buffer) { if (bufferPlayers[currentBufferPlayer]->play(buffer)) currentBufferPlayer = (currentBufferPlayer+1) % 2; } void EditorApp::on_txtTitle_editingFinished() { updateSongMeta(); } void EditorApp::on_txtArtist_editingFinished() { updateSongMeta(); } void EditorApp::on_txtGenre_editingFinished() { updateSongMeta(); } void EditorApp::on_txtYear_editingFinished() { updateSongMeta(); } void EditorApp::on_cmdSplit_clicked() { if (noteGraph) noteGraph->split(noteGraph->selectedNote()); } void EditorApp::on_cmdInsert_clicked() { if (noteGraph) noteGraph->createNote(noteGraph->selectedNote()->note().end); } void EditorApp::on_cmbNoteType_activated(int index) { if (noteGraph) noteGraph->setType(noteGraph->selectedNote(), index); } void EditorApp::on_chkFloating_clicked(bool checked) { if (noteGraph) noteGraph->setFloating(noteGraph->selectedNote(), checked); } void EditorApp::on_chkLineBreak_clicked(bool checked) { if (noteGraph) noteGraph->setLineBreak(noteGraph->selectedNote(), checked); } void EditorApp::highlightLabel(QString id) { const QString style = "background-color: #f00; font-weight: bold"; clearLabelHighlights(); // Set correct tab if (id == "SONG") ui.tabWidget->setCurrentIndex(1); else ui.tabWidget->setCurrentIndex(0); // Color the labels if (id == "TIMING") { ui.lblPlayback->setStyleSheet(style); ui.lblTiming->setStyleSheet(style); } else if (id == "TUNING") { ui.lblTools->setStyleSheet(style); ui.lblNoteProperties->setStyleSheet(style); } // Clear highlights after a while QTimer::singleShot(4000, this, SLOT(clearLabelHighlights())); } void EditorApp::clearLabelHighlights() { ui.lblPlayback->setStyleSheet(""); ui.lblTiming->setStyleSheet(""); ui.lblTools->setStyleSheet(""); ui.lblNoteProperties->setStyleSheet(""); } void EditorApp::closeEvent(QCloseEvent *event) { if (promptSaving()) event->accept(); else event->ignore(); writeSettings(); } void EditorApp::readSettings() { QSettings settings; // Default QSettings parameters given in main() // Read values QPoint pos = settings.value("pos", QPoint()).toPoint(); QSize size = settings.value("size", QSize(800, 600)).toSize(); bool maximized = settings.value("maximized", false).toBool(); bool aa = settings.value("anti-aliasing", true).toBool(); latestPath = settings.value("latestpath", QDir::homePath()).toString(); // Apply them if (!pos.isNull()) move(pos); resize(size); if (maximized) showMaximized(); ui.actionAntiAliasing->setChecked(aa); } void EditorApp::writeSettings() { QSettings settings; // Default QSettings parameters given in main() settings.setValue("pos", pos()); settings.setValue("size", size()); settings.setValue("maximized", isMaximized()); settings.setValue("anti-aliasing", ui.actionAntiAliasing->isChecked()); settings.setValue("latestpath", latestPath); } AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent) { setupUi(this); setWindowTitle(tr("About %1").arg(PACKAGE)); // Setup About tab lblName->setText(PACKAGE); lblVersion->setText(QString(tr("Version: ")) + VERSION); // Populate Authors tab { QFile f(":/docs/Authors.txt"); f.open(QIODevice::ReadOnly); QTextStream in(&f); in.setCodec("UTF-8"); txtAuthors->setPlainText(in.readAll()); } // Populate License text { QFile f(":/docs/License.txt"); f.open(QIODevice::ReadOnly); QTextStream in(&f); in.setCodec("UTF-8"); txtLicense->setPlainText(in.readAll()); } connect(cmdClose, SIGNAL(clicked()), this, SLOT(accept())); setModal(true); } namespace { bool selectionMatches(int n, NoteGraphWidget *ngw) { if (!ngw) return false; const NoteLabels& nls = ngw->selectedNotes(); for (int i = 0; i < nls.size(); ++i) if (nls[i]->note().note == n) return true; return false; } } void EditorApp::updatePiano(int y) { if (!piano) return; piano->move(piano->x(), ui.noteGraphScroller->y() - y); } Piano::Piano(QWidget *parent): QLabel(parent), m_player(new BufferPlayer(this)) { setMouseTracking(true); } void Piano::updatePixmap(NoteGraphWidget *ngw) { const int notes = 12 * 4; // Four octaves const QColor borderColor = QColor("#c0c0c0"); const QColor selectionColor = QColor("#a00"); const QColor mouseColor = QColor("#090"); int noteHeight = 16; int mousen = round((NoteGraphWidget::Height - mapFromGlobal(QCursor::pos()).y()) / 16.0); QImage image(50, notes * noteHeight, QImage::Format_ARGB32_Premultiplied); image.fill(qRgba(0, 0, 0, 0)); setFixedSize(image.width(), image.height()); { QPainter painter(&image); MusicalScale scale; QPen pen; pen.setWidth(2); int y; int w = image.width(); // Render only the white keys first for (int i = -1; i < notes; ++i) { if (scale.isSharp(i)) continue; int y2 = image.height() - i * noteHeight; // Note center y y2 -= (scale.isSharp(i + 1) ? 1.0 : 0.5) * noteHeight; // Key top y // Pick border color according to selection status if (i == mousen) pen.setColor(mouseColor); else if (selectionMatches(i, ngw)) pen.setColor(selectionColor); else pen.setColor(borderColor); painter.setPen(pen); // Skip the first key because y hasn't been calculated yet if (i > -1) { painter.fillRect(0, y2, w, y - y2, QColor("#ffffff")); painter.drawRect(0, y2 + 2, w-1, y - y2 - 2); } y = y2; // The next key bottom y } // Now render the black keys w *= 0.6; for (int i = 0; i < notes; ++i) { if (!scale.isSharp(i)) continue; y = image.height() - i*noteHeight - noteHeight / 2; // Pick border color according to selection status if (i == mousen) pen.setColor(mouseColor); else if (selectionMatches(i, ngw)) pen.setColor(selectionColor); else pen.setColor(borderColor); painter.setPen(pen); painter.fillRect(0, y, w, noteHeight, QColor("#000000")); painter.drawRect(0, y, w, noteHeight); } } setPixmap(QPixmap::fromImage(image)); } void Piano::mousePressEvent(QMouseEvent *event) { if (!m_player) return; QByteArray ba; int n = round((NoteGraphWidget::Height - event->pos().y()) / 16.0); Synth::createBuffer(ba, n % 12, 0.4); m_player->play(ba); } void Piano::mouseMoveEvent(QMouseEvent *event) { updatePixmap(NULL); } void EditorApp::on_sliderPlaybackRate_valueChanged(int value) { qreal playbackRate = value / 100.0; //qreal is actually a double player->setPlaybackRate(playbackRate); ui.labelPlayBackRate->setText(tr("Playback rate: ") + QString::number(playbackRate)); } composer-master/src/util.hh0000644000175000017500000000237213357046745014431 0ustar niknik#pragma once #include #include /** Implement C99 mathematical rounding (which C++ unfortunately currently lacks) **/ template T round(T val) { return int(val + (val >= 0 ? 0.5 : -0.5)); } /** Implement C99 remainder function (not precisely, but almost) **/ template T remainder(T val, T div) { return val - round(val/div) * div; } /** Limit val to range [min, max] **/ template T clamp(T val, T min = 0, T max = 1) { if (min > max) throw std::logic_error("min > max"); if (val < min) return min; if (val > max) return max; return val; } /** A convenient way for getting NaNs **/ static inline double getNaN() { return std::numeric_limits::quiet_NaN(); } /** A convenient way for getting infs **/ static inline double getInf() { return std::numeric_limits::infinity(); } static inline bool isPow2(unsigned int val) { if (val == 0) return false; if ((val & (val-1)) == 0) return true; // From Wikipedia: Power_of_two return false; } static inline unsigned int nextPow2(unsigned int val) { unsigned int ret = 1; while (ret < val) ret *= 2; return ret; } static inline unsigned int prevPow2(unsigned int val) { unsigned int ret = 1; while ((ret*2) < val) ret *= 2; return ret; } composer-master/src/songparser-ini.cc0000644000175000017500000001447613357046745016412 0ustar niknik#include "songparser.hh" #include "midifile.hh" #include #include /// @file /// Functions used for parsing the FoF INI/MID song format using namespace SongParserUtil; /// 'Magick' to check if this file looks like correct format bool SongParser::iniCheck(QString const& data) { return data.startsWith("[song]"); } /// 'Magick' to check if this file looks like correct format bool SongParser::midiCheck(QString const& data) { return data.startsWith("MThd"); } /// Parser void SongParser::iniParse() { // Some defaults m_song.music["background"] = m_song.path + "song.ogg"; m_song.music["vocals"] = m_song.path + "vocals.ogg"; m_song.music["guitar"] = m_song.path + "guitar.ogg"; m_song.music["rhythm"] = m_song.path + "rhythm.ogg"; m_song.music["drums"] = m_song.path + "drums.ogg"; // Parse the INI file QString line; if (!getline(line) || line != "[song]") throw std::runtime_error("INI should begin with [song]"); while (getline(line)) iniParseField(line); midParse(); } void SongParser::iniParseField(QString const& line) { if (line.trimmed().isEmpty()) return; int pos = line.indexOf('='); if (pos < 0) throw std::runtime_error("Invalid ini format, should be key=value"); QString key = line.left(pos).trimmed().toUpper(); QString value = line.mid(pos + 1).trimmed(); bool ok = true; if (key == "NAME") m_song.title = value; else if (key == "ARTIST") m_song.artist = value; else if (key == "EDITION") m_song.edition = value; else if (key == "GENRE") m_song.genre = value; else if (key == "CREATOR") m_song.creator = value; else if (key == "LANGUAGE") m_song.language= value; else if (key == "YEAR") m_song.year = value; else if (key == "COVER") m_song.cover = value; else if (key == "VIDEO") m_song.video = value; else if (key == "BACKGROUND") m_song.background = value; else if (key == "START") m_song.start = value.toDouble(&ok); else if (key == "DELAY") m_gap = 1e-3 * value.toDouble(&ok); else if (key == "VIDEO_START_TIME") m_song.videoGap = 1e-3 * value.toDouble(&ok); else if (key == "PREVIEW_START_TIME") m_song.preview_start = 1e-3 * value.toDouble(&ok); if (!ok) throw std::runtime_error(QString("Invalid value for %1: %2").arg(key).arg(value).toStdString()); return; } namespace { QString strConv(std::string const& str) { /* TODO: charset autodetection */ return QString::fromUtf8(str.data(), str.size()); } } void SongParser::midParse() { QByteArray name = (m_song.path + "notes.mid").toLocal8Bit(); midifile::Reader reader(std::string(name.data(), name.size()).c_str()); using midifile::Event; double tempo = 120.0; double division = reader.getDivision(); addBPM(0, tempo, division); unsigned track = 0; while (++track, reader.startTrack()) { VocalTrack vt(""); unsigned timecode = 0; bool newSentence = true; std::string trackName, lyric; for (Event ev; reader.parseEvent(ev); ) { timecode += ev.timecode; if (ev.type == Event::NOTE_ON && ev.arg2 == 0) ev.type = Event::NOTE_OFF; // Note on with velocity 0 actually means off. // Process any interesting events if (ev.type == Event::NOTE_ON) { if (ev.arg1 == 105) newSentence = true; // New sentence begins at next note if (ev.arg1 >= 100) continue; // Skip control signals vt.notes.push_back(Note(strConv(lyric))); lyric.clear(); Note& n = vt.notes.back(); n.begin = n.end = tsTime(timecode); n.note = ev.arg1; n.lineBreak = newSentence; newSentence = false; vt.noteMin = std::min(vt.noteMin, n.note); vt.noteMax = std::max(vt.noteMax, n.note); continue; } if (ev.type == Event::NOTE_OFF) { if (ev.arg1 >= 100) continue; // Skip control signals if (vt.notes.empty()) throw std::runtime_error("NOTE OFF before NOTE ON."); Note& n = vt.notes.back(); n.end = tsTime(timecode); continue; } if (ev.type == Event::SPECIAL) { Event::Meta meta = ev.getMeta(); if (meta == Event::META_LYRIC || (meta == Event::META_TEXT && *ev.begin != '[')) { lyric = ev.getDataStr(); continue; } if (meta == Event::META_TEXT) { // Non-lyric text event such as "[section Verse-1a]" continue; } if (meta == Event::META_SEQNAME) { trackName = ev.getDataStr(); continue; } if (meta == Event::META_TEMPO) { if (ev.end - ev.begin != 3) throw std::runtime_error("Invalid tempo change event"); unsigned microSecPerBeat = ev.begin[0] << 16 | ev.begin[1] << 8 | ev.begin[2]; tempo = 6e+7 / microSecPerBeat; addBPM(timecode, tempo, division); continue; } if (meta == Event::META_TIMESIGNATURE) { if (ev.end - ev.begin != 4) throw std::runtime_error("Invalid time signature event"); // TODO: Add processing if you find a file that contains these } if (meta == Event::META_KEYSIGNATURE) { if (ev.end - ev.begin != 2) throw std::runtime_error("Invalid key signature event"); // TODO: Add processing if you find a file that contains these } if (meta == Event::META_SEQUENCERSPECIFIC) { std::string data = ev.getDataStr(); // These are written by FoFLyricConverter if (m_song.title.isEmpty() && data.substr(0,6) == "Title=") m_song.title = strConv(data.substr(6)); else if (m_song.artist.isEmpty() && data.substr(0,7) == "Artist=") m_song.artist = strConv(data.substr(7)); continue; } if (meta == Event::META_ENDOFTRACK) break; // Not really necessary as the track would normally end after this anyway ev.print(); // Print any events that reach this far as we probably should add support for them... } } if (trackName == "PART VOCALS") m_song.insertVocalTrack("vocals", vt); }; } #if 0 // Text event handling const std::string sect_pfx = "[section "; if (!data.compare(0, sect_pfx.length(), sect_pfx)) {// [section verse_1] std::string sect_name = data.substr(sect_pfx.length(), data.length()-sect_pfx.length()-1); if (sect_name != "big_rock_ending") { bool space = true; for (std::string::const_iterator it = sect_name.begin(); it != sect_name.end(); ++it) { if (space) *it = toupper(*it); // start in uppercase if (*it == '_') {*it = ' '; space = true;} // underscores to spaces else space = false; } // replace gtr => guitar midisections.push_back(MidiSection(sect_name, get_seconds(miditime))); } else cmdevents.push_back(std::string(data)); // see songparser-ini.cc: we need to keep the BRE in cmdevents } else cmdevents.push_back(std::string(data)); #endif composer-master/src/songparser-lrc.cc0000644000175000017500000000702213357046745016400 0ustar niknik#include "songparser.hh" #include #include // LRC has many variations: http://en.wikipedia.org/wiki/LRC_(file_format) // We also support Soramimi format, which differs as follows: // * Per word timing, [] instead of <> (in contrast to "Enhanced LRC") // * Centisecond separator is ':' instead of '.' bool SongParser::lrcCheck(QString const& data) { return data[0] == '['; } void SongParser::lrcParse() { VocalTrack vocal(TrackName::LEAD_VOCAL); Notes& notes = vocal.notes; QString line; while (getline(line)) { // LRC header tags if (line.startsWith("[ar:", Qt::CaseInsensitive)) { m_song.artist = line.mid(4).trimmed().remove(QRegExp("\\]$")); } else if (line.startsWith("[ti:", Qt::CaseInsensitive)) { m_song.title = line.mid(4).trimmed().remove(QRegExp("\\]$")); } else if (line.startsWith("[by:", Qt::CaseInsensitive)) { m_song.creator = line.mid(4).trimmed().remove(QRegExp("\\]$")); // TODO: Gap, [offset: ? } else if (line.length() >= 2 && line[1].isLetter()) { // Skip unknown header tags continue; } else { // Note parsing // These replacements are compatibility between Soramimi and "enhanced" LRC line.replace("<", "[").replace(">", "]"); lrcNoteParse(line, 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::lrcNoteParse(QString line, VocalTrack& vocal) { if (line.isEmpty()) return false; if (line[0] != '[') throw std::runtime_error("Unexpected character at line start"); Notes& notes = vocal.notes; bool parsingTime = true, createNote = false; QString timeStr = "", lyric = ""; double time = 0, prevTime = 0; // Start traversing from 1 because we already know 0 is '[' for (int i = 1; i < line.length(); ++i) { // Two state parser: either parsing timestamp or lyrics if (parsingTime) { if (line[i].toLatin1() == ']') { // Timestamp end parsingTime = false; prevTime = time; time = convertLRCTimestampToDouble(timeStr); timeStr.clear(); if (!lyric.isEmpty()) { createNote = true; } else if (!notes.empty()) { // Adjust last sentence's end to fix possible overlaps if there is no line end timestamp notes.back().end = std::min(notes.back().end, time); } } else { // Accumulate timestamp string timeStr += line[i]; } } else { // Lyrics parsing mode if (line[i].toLatin1() == '[') parsingTime = true; // Lyric end else lyric += line[i]; // Accumulate lyric string } // Create the note if ((createNote || i == line.length() - 1) && !lyric.isEmpty()) { Note n(lyric); n.begin = prevTime; n.end = time; n.note = 33; notes.push_back(n); lyric.clear(); createNote = false; } } // Add SLEEP to line end Note n; n.type = Note::SLEEP; n.begin = time; n.end = time; n.note = 33; notes.push_back(n); return true; } double SongParser::convertLRCTimestampToDouble(QString timeStamp) { bool ok = false; // This replacing is also LRC <-> Soramimi compatibility 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("Invalid minutes in timestamp " + timeStamp.toStdString()); double sec = seconds.toDouble(&ok); if (!ok) throw std::runtime_error("Invalid seconds in timestamp " + timeStamp.toStdString()); return min * 60 + sec; } composer-master/src/notes.hh0000644000175000017500000000657513357046745014615 0ustar niknik#pragma once #include #include #include #include /// musical scale, defaults to C major class MusicalScale { private: double m_baseFreq; static const int m_baseId = 33; public: /// constructor MusicalScale(double baseFreq = 440.0): m_baseFreq(baseFreq) {} /// get name of note QString getNoteStr(double freq) const; /// get note number for id unsigned int getNoteNum(int id) const; /// true if sharp note bool isSharp(int id) const; /// get frequence for note id double getNoteFreq(int id) const; /// get note id for frequence int getNoteId(double freq) const; /// get note for frequence double getNote(double freq) const; /// get note offset for frequence double getNoteOffset(double freq) const; /// get octave number /// get base id static int getBaseId() { return m_baseId; } }; /// stores duration of a note struct Duration { double begin, ///< beginning timestamp in seconds end; ///< ending timestamp in seconds Duration(); /// create a new Duration object and initialize begin and end Duration(double b, double e): begin(b), end(e) {} /// compares begin timestamps of two Duration structs static bool ltBegin(Duration const& a, Duration const& b) { return a.begin < b.begin; } /// compares end timestamps of two Duration structs static bool ltEnd(Duration const& a, Duration const& b) { return a.end < b.end; } }; typedef std::vector Durations; typedef std::map NoteMap; /// note read from songfile struct Note { Note(QString lyric = ""); /// note type - NOTE! Keep the types array below in sync with the enum! enum Type { FREESTYLE = 'F', NORMAL = ':', GOLDEN = '*', SLIDE = '+', SLEEP = '-', TAP = '1', HOLDBEGIN = '2', HOLDEND = '3', ROLL = '4', MINE = 'M', LIFT = 'L'} type; static const Type types[]; int getTypeInt() const; //Duration duration; ///< note begin/end double begin; // FIXME: Should use duration but it is pain to change everywhere double end; double phase; ///< position within a measure, [0, 1) 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) QString syllable; ///< lyrics syllable for that note bool lineBreak; ///< is this note ending a syllable? /// move beginning without changing length void move(double newBegin) { double l = length(); begin = newBegin; end = newBegin + l; } /// note length double length() const { return end - begin; } /// difference of n from note double diff(double n) const { return diff(note, n); } /// difference of n from note, so that note + diff(note, n) is n (mod 12) static double diff(double note, double n); /// compares begin of two notes static bool ltBegin(Note const& a, Note const& b) { return a.begin < b.begin; } /// compares end of two notes static bool ltEnd(Note const& a, Note const& b) { return a.end < b.end; } /// human-readable description of note type QString typeString() const; }; typedef std::vector Notes; struct VocalTrack { VocalTrack(QString name); void reload(); QString name; Notes notes; int noteMin, noteMax; ///< lowest and highest note double beginTime, endTime; ///< the period where there are notes double m_scoreFactor; ///< normalization factor for the scoring system MusicalScale scale; ///< scale in which song is sung }; typedef std::map VocalTracks; composer-master/src/notelabel.cc0000644000175000017500000001532113357046745015405 0ustar niknik#include #include #include #include #include #include #include "notelabel.hh" #include "notegraphwidget.hh" namespace { static const int text_margin = 3; // Margin of the label texts } const int NoteLabel::render_delay = 300; // How many ms to wait before updating pixmap after some action const int NoteLabel::resize_margin = 5; // How many pixels is the resize area const double NoteLabel::default_length = 0.5; // The preferred size of notes const double NoteLabel::min_length = 0.05; // How many seconds minimum NoteLabel::NoteLabel(const Note ¬e, QWidget *parent, bool floating) : QLabel(parent), m_note(note), m_selected(false), m_floating(floating), m_resizing(0), m_hotspot() { updateLabel(); setMouseTracking(true); hide(); // We don't want to show the widget and create the pixmap as that is slow. // Since the undo-framework relies on rapidly creating and deleting NoteLabels, // this is a necessity to get adequete performance. NoteGraphWidget creates the // pixmaps later on once the final NoteLabels have been found. } void NoteLabel::updatePixmap() { if (isHidden()) return; QFont font; font.setStyleStrategy(QFont::ForceOutline); QFontMetrics metric(font); NoteGraphWidget *ngw = qobject_cast(parent()); QSize size(100, metric.size(Qt::TextSingleLine, lyric()).height() + 2 * text_margin); if (ngw) size.setWidth(ngw->s2px(m_note.length())); if (size.isEmpty()) return; QImage image(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied); image.fill(qRgba(0, 0, 0, 0)); QLinearGradient gradient(0, 0, 0, image.height()-1); float ff = m_floating ? 1.0f : 0.6f; int alpha = m_floating ? 160 : ( isSelected() ? 80 : 220 ); gradient.setColorAt(0.0, m_floating ? QColor(255, 255, 255, alpha) : QColor(50, 50, 50, alpha)); if (m_note.type == Note::NORMAL) { gradient.setColorAt(0.2, QColor(100 * ff, 100 * ff, 255 * ff, alpha)); gradient.setColorAt(0.8, QColor(100 * ff, 100 * ff, 255 * ff, alpha)); gradient.setColorAt(1.0, QColor(100 * ff, 100 * ff, 200 * ff, alpha)); } else if (m_note.type == Note::GOLDEN) { gradient.setColorAt(0.2, QColor(255 * ff, 255 * ff, 100 * ff, alpha)); gradient.setColorAt(0.8, QColor(255 * ff, 255 * ff, 100 * ff, alpha)); gradient.setColorAt(1.0, QColor(160 * ff, 160 * ff, 100 * ff, alpha)); } else if (m_note.type == Note::FREESTYLE) { gradient.setColorAt(0.2, QColor(100 * ff, 180 * ff, 100 * ff, alpha)); gradient.setColorAt(0.8, QColor(100 * ff, 180 * ff, 100 * ff, alpha)); gradient.setColorAt(1.0, QColor(100 * ff, 120 * ff, 100 * ff, alpha)); } { QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(isSelected() ? Qt::red : Qt::black); // Hilight selected note painter.setBrush(gradient); painter.drawRoundedRect(QRectF(0.5, 0.5, image.width()-1, image.height()-1), 8, 8); painter.setFont(font); painter.setPen(isSelected() ? Qt::red : Qt::white); painter.drawText(QRect(QPoint(text_margin, text_margin), QSize(size.width()-text_margin*2, size.height()-text_margin*2)), Qt::AlignCenter, lyric()); // Render sentence end indicator if (m_note.lineBreak) { painter.setPen(QPen(QBrush(QColor(255, 0, 0)), 4)); painter.drawLine(2, 0, 2, image.height()-1); } } setPixmap(QPixmap::fromImage(image)); updateTips(); show(); } void NoteLabel::setSelected(bool state) { if (m_selected != state) { m_selected = state; QTimer::singleShot(50, this, SLOT(updatePixmap())); // Quick update needed here for box selection if (!m_selected) { startResizing(0); // Reset startDragging(QPoint()); // Reset } } } void NoteLabel::resizeEvent(QResizeEvent *) { updatePixmap(); } void NoteLabel::moveEvent(QMoveEvent *) { updateTips(); } void NoteLabel::mouseMoveEvent(QMouseEvent *event) { NoteGraphWidget* ngw = qobject_cast(parent()); if (m_resizing != 0 && ngw) { // Resizing double diffsecs = ngw->px2s(event->pos().x()); if (m_resizing < 0) m_note.begin += diffsecs; // Left side else m_note.end += diffsecs - m_note.length(); // Right side // Enforce minimum size if (m_note.length() < min_length) { if (m_resizing < 0) m_note.begin = m_note.end - min_length; // Left side else m_note.end = m_note.begin + min_length; // Right side } updateLabel(); ngw->updateNotes(m_resizing > 0); } else if (!m_hotspot.isNull() && ngw) { // Moving QPoint newpos = pos() + event->pos() - m_hotspot; double ds = ngw->px2s(event->pos().x() - m_hotspot.x()); int dn = ngw->px2n(event->pos().y()) - ngw->px2n(m_hotspot.y()); NoteLabels& labels = ngw->selectedNotes(); for (int i = 0; i < labels.size(); ++i) { NoteLabel *nl = labels[i]; nl->note().begin += ds; nl->note().end += ds; nl->note().note += dn; nl->updateLabel(); } ngw->updateNotes((event->pos() - m_hotspot).x() < 0); // Check if we need a new hotspot, because the note was constrained if (pos().x() != newpos.x()) m_hotspot = event->pos(); } else { // Hover cursors if (event->pos().x() < NoteLabel::resize_margin || event->pos().x() > width() - NoteLabel::resize_margin) { setCursor(QCursor(Qt::SizeHorCursor)); } else { setCursor(QCursor(Qt::OpenHandCursor)); } } QToolTip::showText(event->globalPos(), toolTip(), this); event->ignore(); // Propagate event to parent } void NoteLabel::startResizing(int dir) { m_resizing = dir; m_hotspot = QPoint(); // Reset if (dir != 0) setCursor(QCursor(Qt::SizeHorCursor)); else setCursor(QCursor()); } void NoteLabel::startDragging(const QPoint& point) { m_hotspot = point; m_resizing = 0; if (!point.isNull()) setCursor(QCursor(Qt::ClosedHandCursor)); else setCursor(QCursor()); } void NoteLabel::updateLabel() { NoteGraphWidget* ngw = qobject_cast(parent()); if (ngw) { // Update label geometry resize(ngw->s2px(m_note.length()), height()); move(ngw->s2px(m_note.begin), ngw->n2px(m_note.note) - height() / 2); } } void NoteLabel::updateTips() { setToolTip(description(true)); setStatusTip(description(false)); setWhatsThis(description(true)); } QString NoteLabel::description(bool multiline) const { MusicalScale ms; return QString("Syllable: \"%2\"%1Type: %3%1Note: %4 (%5)%1%6 s - %7 s (= %8 s)") .arg(multiline ? "\n" : ", ") .arg(lyric()) .arg(m_note.typeString()) .arg(ms.getNoteStr(ms.getNoteFreq(m_note.note))) .arg(m_note.note) .arg(QString::number(m_note.begin, 'f', 4)) .arg(QString::number(m_note.end, 'f', 4)) .arg(QString::number(m_note.length(), 'f', 4) ); } NoteLabel::operator Operation() const { Operation op("NEW", -1); // -1 for id means auto-calculate based on position op << m_note.syllable << m_note.begin << m_note.end << m_note.note << m_floating << m_note.lineBreak << m_note.getTypeInt(); return op; } composer-master/src/songwriter-ini.cc0000644000175000017500000000740513357046745016424 0ustar niknik#include "songwriter.hh" #include "midifile.hh" #include "util.hh" #include void FoFMIDIWriter::writeMIDI() const { Notes const& notes = s.getVocalTrack().notes; if (notes.empty()) throw std::runtime_error("No notes"); double tempo = s.bpm > 0 ? s.bpm : 120.0; unsigned division = 256; // Allow for very precise timing unsigned endtc = round(tempo / 60.0 * division * notes.back().end); midifile::Writer writer(1, 2, division); using midifile::Event; writer.startTrack(); Event ev; ev.type = Event::SPECIAL; ev.channel = 0x0F; unsigned char buf[16]; ev.begin = ev.end = buf; // Write tempo info ev.arg1 = Event::META_TEMPO; ev.end = ev.begin + 3; unsigned val = 6e+7 / tempo; // Microseconds per beat buf[0] = val >> 16; buf[1] = val >> 8; buf[2] = val; writer.writeEvent(ev); // TODO: write Performous Composer and Title= & Artist= like FoF lyric converter does (note: it does on PART VOCALS but timing track seems more appropriate) // End the timing track ev.arg1 = Event::META_ENDOFTRACK; writer.writeEvent(ev); // Vocals track begins writer.startTrack(); // Write track name ev.arg1 = Event::META_SEQNAME; std::string partvocals = "PART VOCALS"; std::copy(partvocals.begin(), partvocals.end(), buf); ev.end = ev.begin + partvocals.size(); writer.writeEvent(ev); ev.begin = ev.end = NULL; // Write notes bool sentenceOn = false; // Sentence note 105 not playing unsigned timecode = 0; for (Notes::const_iterator it = notes.begin(), itend = notes.end(); it != itend; ++it) { ev.timecode = round(tempo / 60.0 * division * it->begin) - timecode; timecode += ev.timecode; if (it->lineBreak) { ev.type = Event::NOTE_ON; ev.channel = 0; ev.arg1 = 105; // Special note value for starting a new sentence // End the previous sentence (if not at the beginning) if (sentenceOn) { ev.arg2 = 0; writer.writeEvent(ev); // Note OFF ev.timecode = 0; // Reset time for the next event } sentenceOn = true; // Begin a new one ev.arg2 = 127; writer.writeEvent(ev); // Note ON ev.timecode = 0; // Reset time for the next event } // Write lyric QByteArray bytes = it->syllable.toUtf8(); ev.type = Event::SPECIAL; ev.channel = 0x0F; ev.arg1 = Event::META_LYRIC; ev.begin = reinterpret_cast(bytes.data()); ev.end = ev.begin + bytes.size(); writer.writeEvent(ev); ev.end = ev.begin = NULL; // Prepare for writing note on/off events ev.timecode = 0; // Same timecode as the lyric ev.type = Event::NOTE_ON; ev.channel = 0; // Note begin ev.arg1 = 36 + it->note; // FoF format uses offset for note values :( ev.arg2 = 127; // Note ON writer.writeEvent(ev); // Note end ev.timecode = round(tempo / 60.0 * division * it->end) - timecode; timecode += ev.timecode; ev.arg2 = 0; // Note OFF writer.writeEvent(ev); } ev.timecode = 0; // Terminate the sentence note if (sentenceOn) { ev.type = Event::NOTE_ON; ev.channel = 0; ev.arg1 = 105; ev.arg2 = 0; writer.writeEvent(ev); // Note OFF } // Terminate the vocal track ev.type = Event::SPECIAL; ev.channel = 0x0F; ev.arg1 = Event::META_ENDOFTRACK; writer.writeEvent(ev); // Write to file QByteArray name = (path + "/notes.mid").toLocal8Bit(); writer.save(std::string(name.data(), name.size()).c_str()); } void FoFMIDIWriter::writeINI() const { QFile f(path + "/song.ini"); if (!f.open(QFile::WriteOnly | QFile::Truncate)) throw std::runtime_error("Couldn't open target file"); QTextStream out(&f); out.setCodec("UTF-8"); out << "[song]\n"; out << "name = " << (s.title.isEmpty() ? "Unknown" : s.title) << '\n'; out << "artist = " << (s.artist.isEmpty() ? "Unknown" : s.artist) << '\n'; if (!s.genre.isEmpty()) out << "genre = " << s.genre << '\n'; if (!s.year.isEmpty()) out << "year = " << s.year << '\n'; } composer-master/src/operation.hh0000644000175000017500000000601213357046745015447 0ustar niknik#pragma once #include #include #include #include #include #include #include struct Operation { enum OperationFlags { NORMAL = 0, NO_EXEC = 1, NO_EMIT = 2, NO_UPDATE = 4, SELECT_NEW = 8 }; Operation() { } Operation(const QString &opString) { *this << opString; } Operation(const QString &opString, int id) { *this << opString << id; } Operation(const QString &opString, int id, bool state) { *this << opString << id << state; } Operation(const QString &opString, const QString &str1, const QString &str2) { *this << opString << str1 << str2; } // Functions to add parameters to Operation Operation& operator<<(const QString &str) { m_params.push_back(QVariant(str)); return *this; } Operation& operator<<(int i) { m_params.push_back(QVariant(i)); return *this; } Operation& operator<<(bool b) { m_params.push_back(QVariant(b)); return *this; } Operation& operator<<(float f) { m_params.push_back(QVariant(f)); return *this; } Operation& operator<<(double d) { m_params.push_back(QVariant(d)); return *this; } Operation& operator<<(QVariant q) { m_params.push_back(q); return *this; } /// Get the operation id QString op() const { return m_params.isEmpty() ? "" : m_params.front().toString(); } /// Get parameter count (excluding operation id) int paramCount() const { return m_params.size() - 1; } /// Overloaded template getter for param at certain position (1-based) template T param(int index) const { validate(index); m_params[index].value(); } // Get Operation parameter at certain index (1-based) QString s(int index) const { validate(index); return m_params[index].toString(); } char c(int index) const { validate(index); return m_params[index].toChar().toLatin1(); } int i(int index) const { validate(index); return m_params[index].toInt(); } unsigned u(int index) const { validate(index); return m_params[index].toUInt(); } bool b(int index) const { validate(index); return m_params[index].toBool(); } float f(int index) const { validate(index); return m_params[index].toFloat(); } double d(int index) const { validate(index); return m_params[index].toDouble(); } QVariant q(int index) const { validate(index); return m_params[index]; } /// Array access for modifying param QVariant& operator[](int index) { validate(index); return m_params[index]; } std::string dump() const { QString st; QTextStream ts(&st); foreach(QVariant qv, m_params) ts << qv.toString() << " "; return st.toStdString(); } friend QDataStream& operator<<(QDataStream&, const Operation&); friend QDataStream& operator>>(QDataStream& stream, Operation& op); private: void validate(int index) const { if (index < 0 || index >= m_params.size()) throw std::runtime_error("Invalid access to operation parameters"); } QList m_params; }; typedef QStack OperationStack; // Serialization operators QDataStream& operator<<(QDataStream& stream, const Operation& op); QDataStream& operator>>(QDataStream& stream, Operation& op); composer-master/src/songparser-xml.cc0000644000175000017500000000707713357046745016432 0ustar niknik#include "songparser.hh" #include #include #include #include int ts = 0; int sleepts = -1; /// 'Magick' to check if this file looks like correct format bool SongParser::xmlCheck(QString const& data) { return (data[0] == '<' && data[1] == '?' && data[2] == 'x' && data[3] == 'm' && data[4] == 'l') || (data[0] == '<' && data[1] == 'M' && data[2] == 'E' && data[3] == 'L'); } void SongParser::xmlParse() { // Build DOM tree from the xml file QDomDocument doc("MELODY"); if (!doc.setContent(m_stream.readAll())) { throw std::runtime_error(QT_TR_NOOP("XML parse error")); } VocalTrack vocal(TrackName::LEAD_VOCAL); Notes& notes = vocal.notes; // Parse meta QDomElement root = doc.documentElement(); m_song.bpm = root.attribute("Tempo").toDouble(); if (m_song.bpm == 0) throw std::runtime_error(QT_TR_NOOP("Invalid tempo")); if (root.attribute("Resolution") == QString("Demisemiquaver")) m_song.bpm *= 2; addBPM(0, m_song.bpm); m_song.genre = root.attribute("Genre"); m_song.year = root.attribute("Year"); bool track_found = false; // FIXME: HACK: We only parse the first track // Loop through the child elements QDomElement elem = root.firstChildElement(); while (!elem.isNull()) { if (elem.tagName() == "TRACK") { // Track found if (track_found) break; // FIXME: HACK: We only parse the first track track_found = true; m_song.artist = elem.attribute("Artist"); } else if (elem.tagName() == "SENTENCE") { // Sentence found // Loop through the notes in the sentence QDomElement noteElem = elem.firstChildElement(); while (!noteElem.isNull()) { // We are only interested in NOTE elements if (noteElem.tagName() == "NOTE") { // Note found int length = noteElem.attribute("Duration").toInt(); unsigned int ts = m_prevts; // See if it is an actual note and not sleep QString lyric = noteElem.attribute("Lyric").isEmpty() ? noteElem.attribute("Rap") : noteElem.attribute("Lyric"); if (noteElem.attribute("MidiNote") != "0" || !lyric.isEmpty()) { // TODO: Prettify lyric? (as ss_extract) Note n(lyric); if (noteElem.attribute("Bonus") == QString("Yes")) n.type = Note::GOLDEN; else if (noteElem.attribute("FreeStyle") == QString("Yes")) n.type = Note::FREESTYLE; else n.type = Note::NORMAL; n.note = noteElem.attribute("MidiNote").toInt(); n.notePrev = n.note; // No slide notes n.begin = tsTime(ts); n.end = tsTime(ts + length); // Track note meta vocal.noteMin = std::min(vocal.noteMin, n.note); vocal.noteMax = std::max(vocal.noteMax, n.note); // Save note notes.push_back(n); } // Update time m_prevts += length; m_prevtime = tsTime(ts + length); } noteElem = noteElem.nextSiblingElement(); } // Now add sentence end indicator Note n; n.type = Note::SLEEP; n.note = 0; n.begin = m_prevtime; n.end = n.begin; notes.push_back(n); } elem = elem.nextSiblingElement(); } 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")); } /* // Some extra formatting to make lyrics look better (hyphen removal & whitespace) if (lyric.size() > 0 && lyric[lyric.size() - 1] == '-') { if (lyric.size() > 1 && lyric[lyric.size() - 2] == ' ') lyric.erase(lyric.size() - 2); else lyric[lyric.size() - 1] = '~'; } else { lyric += ' '; } */ composer-master/src/gettingstarted.hh0000644000175000017500000000307213357046745016502 0ustar niknik#include #include "ui_gettingstarted.h" #include "editorapp.hh" #include class GettingStartedDialog: public QDialog, private Ui::GettingStarted { Q_OBJECT public: GettingStartedDialog(QWidget* parent) : QDialog(parent), m_editorApp(qobject_cast(parent)) { if (!m_editorApp) throw std::runtime_error("Couldn't open help dialog."); setupUi(this); QSettings settings; chkShowOnStartup->setChecked(settings.value("showhelp", true).toBool()); } public slots: void on_cmdClose_clicked(bool) { close(); } void on_chkShowOnStartup_stateChanged(int state) { QSettings settings; settings.setValue("showhelp", state != Qt::Unchecked); } // Command link buttons void on_cmdMusicFile_clicked(bool) { m_editorApp->on_actionMusicFile_triggered(); } void on_cmdLyricsFromFile_clicked(bool) { m_editorApp->on_actionLyricsFromFile_triggered(); } void on_cmdLyricsFromClipboard_clicked(bool) { m_editorApp->on_actionLyricsFromClipboard_triggered(); } void on_cmdTimeLyrics_clicked(bool) { m_editorApp->highlightLabel("TIMING"); } void on_cmdFineTuneLyrics_clicked(bool) { m_editorApp->highlightLabel("TUNING"); } void on_cmdMetadata_clicked(bool) { m_editorApp->highlightLabel("SONG"); } void on_cmdExport_clicked(bool) { m_editorApp->showExportMenu(); } void on_cmdLRC_clicked(bool) { m_editorApp->on_actionLyricsFromLRCFile_triggered(); } protected: void closeEvent(QCloseEvent*) { QSettings settings; settings.setValue("showhelp", chkShowOnStartup->isChecked()); } private: EditorApp *m_editorApp; }; composer-master/src/pitch.cc0000644000175000017500000001374613357046745014560 0ustar niknik#include "pitch.hh" #include "libda/fft.hpp" #include #include static const unsigned FFT_P = 12; // FFT size setting, will use 2^FFT_P sample FFT static const std::size_t FFT_N = 1 << FFT_P; // FFT size in samples static const std::size_t FFT_STEP = 512; // Step size in samples, should be <= 0.25 * FFT_N. Low values cause high CPU usage. // Limit the range to avoid noise and useless computation static const double FFT_MINFREQ = 45.0; static const double FFT_MAXFREQ = 3000.0; Tone::Tone(): freq(), level(), prev(), next() { for (std::size_t i = 0; i < MAXHARM; ++i) harmonics[i] = 0.0; } bool Tone::operator==(double f) const { return std::abs(freq / f - 1.0) < 0.06; // Half semitone } Analyzer::Analyzer(double rate, std::string id): m_rate(rate), m_id(id), m_window(FFT_N), m_fftLastPhase(FFT_N / 2), m_oldfreq(0.0) { // Hamming window for (size_t i=0; i < FFT_N; i++) { m_window[i] = 0.53836 - 0.46164 * std::cos(2.0 * M_PI * i / (FFT_N - 1)); } } unsigned Analyzer::processSize() const { return FFT_N; } unsigned Analyzer::processStep() const { return FFT_STEP; } void Analyzer::calcFFT(float* pcm) { m_fft = da::fft(pcm, m_window); } namespace { bool sqrLT(float a, float b) { return a * a < b * b; } bool matchFreq(double f1, double f2) { return std::abs(f1 / f2 - 1.0) < 0.06; } } void Combo::combine(Peak const& p) { freq += p.level * p.freq; // Multiplication for weighted average level += p.level; } bool Combo::match(double freqOther) const { return matchFreq(freq, freqOther); } void Analyzer::calcTones() { // Precalculated constants const double freqPerBin = m_rate / FFT_N; const double phaseStep = 2.0 * M_PI * FFT_STEP / FFT_N; const double normCoeff = 1.0 / FFT_N; // Limit frequency range of processing const size_t kMin = std::max(size_t(3), size_t(FFT_MINFREQ / freqPerBin)); const size_t kMax = std::min(FFT_N / 2, size_t(FFT_MAXFREQ / freqPerBin)); m_peaks.resize(kMax); // Process FFT into peaks for (size_t k = 1; k < kMax; ++k) { double level = normCoeff * std::abs(m_fft[k]); double phase = std::arg(m_fft[k]); // Use the reassignment method for calculating precise frequencies double delta = phase - m_fftLastPhase[k]; m_fftLastPhase[k] = phase; delta -= k * phaseStep; // Subtract the expected phase difference delta = remainder(delta, 2.0 * M_PI); // Map the delta phase into +/- M_PI interval delta /= phaseStep; // Calculate how much difference that makes during a step m_peaks[k].freqFFT = k * freqPerBin; // Calculate the simple FFT frequency m_peaks[k].freq = (k + delta) * freqPerBin; // Calculate the true frequency m_peaks[k].level = level; } // Filter peaks and combine adjacent peaks pointing at the same frequency into one typedef std::vector Combos; Combos combos; for (size_t k = kMin; k < kMax; ++k) { Peak const& p = m_peaks[k]; bool ok = p.level > 1e-3 && p.freq >= FFT_MINFREQ && p.freq <= FFT_MAXFREQ && std::abs(p.freqFFT - p.freq) < freqPerBin; if (!ok) continue; // Do we need to add a new Combo (rather than using the last one)? if (combos.empty() || !combos.back().match(p.freq)) combos.push_back(Combo()); combos.back().combine(p); } // Convert sum frequencies into averages for (Combos::iterator it = combos.begin(), itend = combos.end(); it != itend; ++it) { it->freq /= it->level; } // Only keep a reasonable amount of strongest combos std::sort(combos.begin(), combos.end(), Combo::cmpByLevel); if (combos.size() > 30) combos.resize(30); // The order may not be strictly correct, fix it... std::sort(combos.begin(), combos.end(), Combo::cmpByFreq); // Try to combine combos into tones (collections of harmonics) Tones tones; for (Combos::const_iterator it = combos.begin(), itend = combos.end(); it != itend; ++it) { for (int div = 1; div <= 3; ++div) { // Missing fundamental processing Tone tone; int plausibleHarmonics = 0; double basefreq = it->freq / div; if (basefreq < FFT_MINFREQ) break; // Do not try any lower frequencies for (Combos::const_iterator harm = it; harm != itend; ++harm) { double ratio = harm->freq / basefreq; unsigned n = round(ratio); if (n > Tone::MAXHARM) break; // No more harmonics can be found if (std::abs(ratio - n) > 0.03) continue; // Frequency doesn't match if (n == 0) throw std::logic_error("combos not correctly sorted"); if (n % div != 0) ++plausibleHarmonics; double l = harm->level; tone.harmonics[n - 1] += l; tone.level += l; tone.freq += l * harm->freq / n; // The sum of all harmonics' fundies (weighted by l) } if (div > 1 && plausibleHarmonics < 3) continue; // Not a proper missing fundamental tone.freq /= tone.level; // Average instead of sum tones.push_back(tone); } } // Clean harmonics misdetected as fundamental tones.sort(); for (Tones::iterator it = tones.begin(); it != tones.end(); ++it) { Tones::iterator it2 = it; ++it2; while (it2 != tones.end()) { double ratio = it2->freq / it->freq; double diff = std::abs(ratio - round(ratio)); bool erase = false; if (diff < 0.02 && it2->level < 2.0 * it->level) erase = true; // Precisely harmonic and not much stronger than fundamental // Perform the action if (erase) it2 = tones.erase(it2); else ++it2; } } temporalMerge(tones); } void Analyzer::temporalMerge(Tones& tones) { if (!m_moments.empty()) { Tones& old = m_moments.back().m_tones; Tones::iterator it = tones.begin(); // Iterate over old tones for (Tones::iterator oldit = old.begin(); oldit != old.end(); ++oldit) { // Try to find a matching new tone while (it != tones.end() && *it < *oldit) ++it; // If match found if (it != tones.end() && *it == *oldit) { // Link together the old and the new tones oldit->next = &*it; it->prev = &*oldit; } } } m_moments.push_back(Moment(m_moments.size() * processStep() / m_rate)); m_moments.back().stealTones(tones); // No pointers are invalidated } Moment::Moment(double t): m_time(t) {} void Moment::stealTones(Tones& tones) { m_tones.swap(tones); } composer-master/src/editorapp.hh0000644000175000017500000000776513357046745015456 0ustar niknik#pragma once #include "ui_editor.h" #include "ui_aboutdialog.h" #include "operation.hh" #include "song.hh" #include "synth.hh" #include "notegraphwidget.hh" #include class QProgressBar; class QPushButton; class QCloseEvent; class NoteLabel; class NoteGraphWidget; class GettingStartedDialog; class AboutDialog: public QDialog, private Ui::AboutDialog { Q_OBJECT public: AboutDialog(QWidget* parent = 0); }; class Piano: public QLabel { Q_OBJECT public: Piano(QWidget *parent = 0); public slots: void updatePixmap(NoteGraphWidget *ngw); protected: void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); private: BufferPlayer *m_player; }; class EditorApp: public QMainWindow { Q_OBJECT public: EditorApp(QWidget *parent = 0); void openFile(QString fileName); void updateSongMeta(bool readFromSongToUI = false); void updateMenuStates(); void updateTitle(); void highlightLabel(QString id); void showExportMenu() { ui.menuExport->exec(pos() + QPoint(0, ui.menubar->height())); } private: void setupNoteGraph(); void setMusic(QString filepath, bool primary = true); bool promptSaving(); void saveProject(QString fileName); void exportSong(QString format, QString dialogTitle); void doOpStack(); void playButton(); void readSettings(); void writeSettings(); public slots: void operationDone(const Operation &op); void updateNoteInfo(NoteLabel *note); void analyzeProgress(int value, int maximum); void metaDataChanged(); void audioTick(qint64 time); void playerStateChanged(QMediaPlayer::State state); void playerError(QMediaPlayer::Error error); void playbackRateChanged(qreal rate); void playBuffer(const QByteArray& buffer); void statusBarMessage(const QString& message); void updatePiano(int y); void clearLabelHighlights(); // Automatic slots void on_cmdPlay_clicked(); void on_chkSynth_clicked(bool checked); // File menu void on_actionNew_triggered(); void on_actionOpen_triggered(); void on_actionSave_triggered(); void on_actionSaveAs_triggered(); void on_actionSingStarXML_triggered(); void on_actionUltraStarTXT_triggered(); void on_actionFoFMIDI_triggered(); void on_actionLRC_triggered(); void on_actionEnhanced_LRC_triggered(); void on_actionSoramimiTXT_triggered(); void on_actionLyricsToFile_triggered(); void on_actionLyricsToClipboard_triggered(); void on_actionExit_triggered(); // Edit menu void on_actionUndo_triggered(); void on_actionRedo_triggered(); void on_actionDelete_triggered(); void on_actionSelectAll_triggered(); void on_actionSelectAllAfter_triggered(); void on_actionAntiAliasing_toggled(bool checked); // Insert menu void on_actionMusicFile_triggered(); void on_actionAdditionalMusicFile_triggered(); void on_actionLyricsFromFile_triggered(); void on_actionLyricsFromClipboard_triggered(); void on_actionLyricsFromLRCFile_triggered(); // View menu void on_actionZoomIn_triggered(); void on_actionZoomOut_triggered(); void on_actionResetZoom_triggered(); // Help menu void on_actionGettingStarted_triggered(); void on_actionWhatsThis_triggered(); void on_actionAbout_triggered(); void on_actionAboutQt_triggered(); // Note properties tab void on_txtTitle_editingFinished(); void on_txtArtist_editingFinished(); void on_txtGenre_editingFinished(); void on_txtYear_editingFinished(); void on_cmdSplit_clicked(); void on_cmdInsert_clicked(); void on_cmbNoteType_activated(int state); void on_chkFloating_clicked(bool checked); void on_chkLineBreak_clicked(bool checked); void on_sliderPlaybackRate_valueChanged(int value); protected: void closeEvent(QCloseEvent *event); private slots: private: Ui::EditorApp ui; GettingStartedDialog *gettingStarted; NoteGraphWidget *noteGraph; OperationStack opStack; OperationStack redoStack; QScopedPointer song; QMediaPlayer *player; BufferPlayer *bufferPlayers[2]; QScopedPointer synth; Piano *piano; QProgressBar *statusbarProgress; QPushButton *statusbarButton; QString projectFileName; QString latestPath; int currentBufferPlayer; }; composer-master/src/midifile.cc0000644000175000017500000001677213357046745015235 0ustar niknik#include "midifile.hh" #include #include #include #include #include using namespace midifile; Reader::Reader(char const* filename) { // Read the entire file into a buffer { std::ifstream file(filename, std::ios::binary); if (!file.is_open()) throw std::runtime_error("Unable to open " + std::string(filename)); file.exceptions(std::ios::failbit); file.seekg(0, std::ios::end); unsigned size = file.tellg(); m_data.resize(size + margin); file.seekg(0); file.read(reinterpret_cast(&m_data[0]), size); m_pos = &m_data[0]; m_fileEnd = m_pos + size; } parseMThd(); } bool Reader::startTrack() { m_pos = m_riffEnd; // Jump to the end of the current riff if (m_pos == m_fileEnd) return false; parseMTrk(); return true; } void Reader::parseRiff(char const* name) { if (!std::equal(name, name + 4, m_pos)) throw std::runtime_error("MIDI header " + std::string(name) + " not found, instead found " + std::string(m_pos, m_pos + 4)); m_pos += 4; unsigned size = read<4>(); if (m_fileEnd - m_pos < size) throw std::runtime_error("Unexpected end of file within MIDI header " + std::string(name)); m_riffEnd = m_pos + size; } void Reader::parseMThd() { parseRiff("MThd"); unsigned fmt = read<2>(); m_tracks = read<2>(); m_division = read<2>(); if (fmt == 2) throw std::runtime_error("MIDI format 2 not supported."); if (fmt == 0 && m_tracks == 1) return; // Old single track format if (fmt == 1 && m_tracks > 1) return; // New format (timing info on first track) throw std::runtime_error("MIDI format and track number combination is invalid."); m_runningStatus = 0; } void Reader::parseMTrk() { m_pos = m_riffEnd; // Skip any unread bytes of the previous parseRiff("MTrk"); } bool Reader::parseEvent(Event& ev) { if (m_pos == m_riffEnd) return false; ev = Event(); // Event header { ev.timecode = read_varlen(); unsigned event = read<1>(); if (event & 0x80) { if (event < 0xF0) m_runningStatus = event; // Stored for regular (0x00..0xEF) else if (event < 0xF8) m_runningStatus = 0; // Cleared for System Common Category (0xF0..0xF7) // No-op for RealTime Category messages (0xF8..0xFF) } else { --m_pos; // The "event" we read is actually part of the content so put it back if (m_runningStatus == 0) throw std::runtime_error("Invalid MIDI file (no stored running status available)"); event = m_runningStatus; } ev.type = static_cast(event & 0xF0); ev.channel = event & 0x0F; } if (ev.type != Event::SPECIAL || ev.channel >= 8) ev.arg1 = read<1>(); // Everything except System Common takes one argument unsigned tmp; switch (ev.type) { case Event::NOTE_ON: case Event::NOTE_OFF: case Event::NOTE_AFTERTOUCH: case Event::CONTROLLER: case Event::PITCH_BEND: ev.arg2 = read<1>(); break; case Event::PROGRAM_CHANGE: case Event::CHANNEL_AFTERTOUCH: break; // No arg2 for these case Event::SPECIAL: // Special category (system exclusive or meta event) tmp = read_varlen(); // data size ev.begin = m_pos; m_pos += tmp; ev.end = m_pos; break; } if (m_pos > m_riffEnd) throw std::runtime_error("MIDI event went past the end of Mtrk"); return true; } Writer::Writer(unsigned fmt, unsigned tracks, unsigned division) { beginRiff("MThd"); if (fmt == 0 && tracks != 1) throw std::logic_error("Format 0 MIDI must have exactly one track"); if (fmt == 1 && tracks < 2) throw std::logic_error("Format 1 MIDI must have a separate timing track"); if (division == 0) throw std::logic_error("Division must be set to a positive value"); write<2>(fmt); write<2>(tracks); write<2>(division); } void Writer::save(char const* filename) { endRiff(); std::ofstream f(filename, std::ios::binary); f.write(reinterpret_cast(&m_data[0]), m_data.size()); } void Writer::startTrack() { endRiff(); beginRiff("MTrk"); } void Writer::writeEvent(Event const& ev) { write_varlen(ev.timecode); if (ev.type & ~0xF0 || ev.type < 0x80) throw std::logic_error("Invalid MIDI event type"); if (ev.channel & ~0x0F) throw std::logic_error("Invalid MIDI channel number"); write<1>(ev.type | ev.channel); if (ev.type != Event::SPECIAL || ev.channel >= 8) write<1>(ev.arg1); // Everything except System Common takes one argument switch (ev.type) { case Event::NOTE_ON: case Event::NOTE_OFF: case Event::NOTE_AFTERTOUCH: case Event::CONTROLLER: case Event::PITCH_BEND: write<1>(ev.arg2); break; case Event::PROGRAM_CHANGE: case Event::CHANNEL_AFTERTOUCH: if (ev.arg2 != 0) throw std::logic_error("MIDI event with non-zero arg2 for event that only takes one arg"); break; // No arg2 for these case Event::SPECIAL: // Special category (system exclusive or meta event) write_varlen(ev.end - ev.begin); // data size std::copy(ev.begin, ev.end, std::back_inserter(m_data)); break; } } void Writer::beginRiff(char const* name) { m_riffBegin = m_data.size(); m_data.resize(m_riffBegin + 8); // Add space for header std::copy(name, name + 4, m_data.begin() + m_riffBegin); // Set RIFF name } void Writer::endRiff() { unsigned size = m_data.size() - m_riffBegin; if (size == 0) return; // Nothing to close size -= 8; m_data[m_riffBegin + 4] = size >> 24; m_data[m_riffBegin + 5] = size >> 16; m_data[m_riffBegin + 6] = size >> 8; m_data[m_riffBegin + 7] = size; m_riffBegin = m_data.size(); } // Debugging facilities follow namespace { std::string escapeString(const_iterator begin, const_iterator end) { std::string ret; while (begin != end) { value_type ch = *begin++; if (ch >= 0x20 && ch < 0x7F) { ret += ch; continue; } // Print escaped character std::ostringstream oss; oss << '<' << std::hex << std::setfill('0') << std::setw(2) << int(ch) << '>'; ret += oss.str(); } return ret; } } void Event::print() const { std::ostringstream oss; oss << "Midi event:" << std::setw(12) << timecode << " "; switch (type) { case NOTE_OFF: oss << "note off note=" << arg1 << " velocity=" << arg2; break; case NOTE_ON: oss << "note on note=" << arg1 << " velocity=" << arg2; break; case NOTE_AFTERTOUCH: oss << "aftertouch pitch=" << arg1 << " value=" << arg2; break; case CONTROLLER: oss << "controller num=" << arg1 << " value=" << arg2; break; case PROGRAM_CHANGE: oss << "program change num=" << arg1; break; case CHANNEL_AFTERTOUCH: oss << "channel aftertouch value =" << arg1; break; case PITCH_BEND: oss << "pitch bend value=" << (arg2 << 8 | arg1); break; case SPECIAL: if (channel == 0x0F) oss << "meta <" << metaName(static_cast(arg1)) << ">"; else if (channel >= 0x08) oss << "unknown realtime category message, arg1=" << arg1; else oss << "system common message"; break; } if (begin != end) oss << " data='" << escapeString(begin, end) << "'"; // TODO: filter non-printable oss << '\n'; std::cerr << oss.str(); } char const* Event::metaName(Meta meta) { switch (meta) { case META_SEQNUMBER: return "sequence number"; case META_TEXT: return "text"; case META_COPYRIGHT: return "copyright"; case META_SEQNAME: return "sequence or track name"; case META_INSTRNAME: return "instrument name"; case META_LYRIC: return "lyric"; case META_MARKERTEXT: return "marker text"; case META_CUEPOINT: return "cue point"; case META_CHPREFIX: return "channel prefix"; case META_ENDOFTRACK: return "end of track"; case META_TEMPO: return "tempo change"; case META_SMTPEOFFSET: return "SMTPE offset"; case META_TIMESIGNATURE: return "time signature"; case META_KEYSIGNATURE: return "key signature"; case META_SEQUENCERSPECIFIC: return "sequencer specific"; } return "unknown"; } composer-master/src/song.cc0000644000175000017500000000571213357046745014411 0ustar niknik#include "song.hh" #include "songparser.hh" #include "notes.hh" #include "util.hh" #include #include void Song::reload(bool errorIgnore) { loadStatus = NONE; vocalTracks.clear(); //instrumentTracks.clear(); //danceTracks.clear(); beats.clear(); midifilename.clear(); category.clear(); genre.clear(); edition.clear(); title.clear(); artist.clear(); collateByTitle.clear(); collateByTitleOnly.clear(); collateByArtist.clear(); collateByArtistOnly.clear(); text.clear(); creator.clear(); music.clear(); cover.clear(); background.clear(); video.clear(); videoGap = 0.0; start = 0.0; preview_start = getNaN(); bpm = 0.0; hasBRE = false; b0rkedTracks = false; if (!filename.isEmpty()) { try { SongParser(*this); } catch (...) { if (!errorIgnore) throw; } } collateUpdate(); } void Song::dropNotes() { // Singing if (!vocalTracks.empty()) { for (VocalTracks::iterator it = vocalTracks.begin(); it != vocalTracks.end(); ++it) it->second.notes.clear(); } /* // Instruments if (!instrumentTracks.empty()) { for (InstrumentTracks::iterator it = instrumentTracks.begin(); it != instrumentTracks.end(); ++it) it->second.nm.clear(); } // Dancing if (!danceTracks.empty()) { for (DanceTracks::iterator it = danceTracks.begin(); it != danceTracks.end(); ++it) it->second.clear(); } */ b0rkedTracks = false; loadStatus = HEADER; } void Song::collateUpdate() { collateByTitle = collate(title + artist) + '\0' + filename; collateByTitleOnly = collate(title); collateByArtist = collate(artist + title) + '\0' + filename; collateByArtistOnly = collate(artist); } QString Song::collate(QString const& str) { return str; //unicodeCollate(str); } namespace { // Cannot simply take double as its second argument because of a C++ defect bool noteEndLessThan(Note const& a, Note const& b) { return a.end < b.end; } } Song::Status Song::status(double time) { Note target; target.end = time; Notes::const_iterator it = std::lower_bound(getVocalTrack().notes.begin(), getVocalTrack().notes.end(), target, noteEndLessThan); if (it == getVocalTrack().notes.end()) return FINISHED; if (it->begin > time + 4.0) return INSTRUMENTAL_BREAK; return NORMAL; } bool Song::getNextSection(double pos, SongSection §ion) { if (songsections.empty()) return false; for (std::vector::iterator it= songsections.begin(); it != songsections.end(); ++it) { if (it->begin > pos) { section = *it; return true; } } // returning false here will jump forward 5s (see screen_sing.cc) return false; } bool Song::getPrevSection(double pos, SongSection §ion) { if (songsections.empty()) return false; for (std::vector::reverse_iterator it= songsections.rbegin(); it != songsections.rend(); it++) { // subtract 1 second so we can jump across a section if (it->begin < pos - 1.0) { section = *it; return true; } } // returning false here will jump backwards by 5s (see screen_sing.cc) return false; } composer-master/src/notegraphwidget.hh0000644000175000017500000001246213357046745016650 0ustar niknik#pragma once #include "pitchvis.hh" #include "notes.hh" #include "operation.hh" #include #include #include #include class QScrollArea; class NoteLabel; typedef QList NoteLabels; class SeekHandle: public QLabel { Q_OBJECT public: SeekHandle(QWidget *parent = 0); int curx() const { return x() + width() / 2; } bool wrapToViewport; protected: void mouseMoveEvent(QMouseEvent *event); void moveEvent(QMoveEvent *event); }; class NoteLabelManager: public QLabel { Q_OBJECT public: static const QString MimeType; NoteLabelManager(QWidget *parent = 0); virtual void updateNotes(bool leftToRight = true) {} virtual void startNotePixmapUpdates() {} virtual void forcedNotePixmapUpdate() {} void clearNotes(); void selectNote(NoteLabel *note, bool clearPrevious = true); void selectAll(); void selectAllAfter(); void shiftSelect(NoteLabel *note); void boxSelect(QPoint p1, QPoint p2); NoteLabel* selectedNote() const { return m_selectedNotes.isEmpty() ? NULL : m_selectedNotes.front(); } NoteLabels& selectedNotes() { return m_selectedNotes; } NoteLabels const& selectedNotes() const { return m_selectedNotes; } int getNoteLabelId(NoteLabel* note) const; int findIdForTime(double time) const; NoteLabels& noteLabels() { return m_notes; } void createNote(double time); void split(NoteLabel *note, float ratio = 0.5f); void del(NoteLabel *note); void move(NoteLabel *note, int value); void editLyric(NoteLabel *note); void setFloating(NoteLabel *note, bool state); void setLineBreak(NoteLabel *note, bool state); void setType(NoteLabel *note, int newtype); void doOperation(const Operation& op, int flags = Operation::NORMAL); virtual void zoom(float steps, double focalSecs = -1); int getZoomLevel() const; int s2px(double sec) const; double px2s(int px) const; int n2px(double note) const; double px2n(int px) const; signals: void updateNoteInfo(NoteLabel*); void operationDone(const Operation&); void statusBarMessage(QString); public slots: void selectNextSyllable(bool backwards = false, bool addToSelection = false); void selectNextSentenceStart(); void cut(); void copy(); void paste(); protected: QScrollArea* getScrollArea() const; void calcViewport(int &x1, int &y1, int &x2, int &y2) const; // Zoom settings static const double zoomStep; ///< Mouse wheel steps * zoomStep => double/half zoom factor static const int zoomMin = -12; ///< Number of steps to minimum zoom static const int zoomMax = 6; ///< Number of steps to maximum zoom static const double ppsNormal; ///< Pixels per second with default zoom double m_pixelsPerSecond; NoteLabels m_notes; NoteLabels m_selectedNotes; enum NoteAction { NONE, RESIZE, MOVE } m_selectedAction; int m_noteHalfHeight; double m_duration; }; const int MaxPitchVis = 2; class NoteGraphWidget: public NoteLabelManager { Q_OBJECT public: static const QString BGColor; static const int Height; NoteGraphWidget(QWidget *parent = 0); void setLyrics(QString lyrics); void setLyrics(const VocalTrack &track); void analyzeMusic(QString filepath, int visId = 0); void updateNotes(bool leftToRight = true); void updateMusicPos(qint64 time, bool smoothing = true); void stopMusic(); void seek(int x); void zoom(float steps, double focalSecs = -1); VocalTrack getVocalTrack() const; QString getCurrentSentence() const; QString getPrevSentence() const; QString dumpLyrics() const; public slots: void showContextMenu(const QPoint &pos); void timeSyllable(); void timeSentence(); void setSeekHandleWrapToViewport(bool state) { m_seekHandle.wrapToViewport = state; } void updatePixmap(const QImage &image, const QPoint &position, int visId); void updatePitch(); void abortPitch() { for (int i = 0; i < MaxPitchVis; ++i) if (m_pitch[i]) m_pitch[i]->cancel(); } void scrollToFirstNote(); void startNotePixmapUpdates(); ///< Starts creating pixmaps for NoteLabels void forcedNotePixmapUpdate(); void playbackRateChanged(qreal rate); signals: void analyzeProgress(int, int); void seeked(qint64 time); protected: void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); void wheelEvent(QWheelEvent *event); void mouseDoubleClickEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent * event); void keyPressEvent(QKeyEvent *event); void timerEvent(QTimerEvent *event); void paintEvent(QPaintEvent*); void resizeEvent(QResizeEvent *) { updatePitch(); } void dragEnterEvent(QDragEnterEvent *event); void dropEvent(QDropEvent *event); private: void finalizeNewLyrics(); void timeCurrent(); QPoint m_mouseHotSpot; bool m_seeking; bool m_actionHappened; QScopedPointer m_pitch[MaxPitchVis]; SeekHandle m_seekHandle; int m_nextNotePixmap; int m_notePixmapTimer; int m_analyzeTimer; int m_playbackTimer; QElapsedTimer m_playbackInterval; qint64 m_playbackPos; qreal m_playbackRate; QPixmap m_pixmap[MaxPitchVis]; QPoint m_pixmapPos[MaxPitchVis]; }; struct FloatingGap { FloatingGap(double startTime): begin(startTime), end(startTime), m_notesLength() {} void addNote(NoteLabel* n); bool isEmpty() const { return notes.empty(); } double length() const { return std::abs(end - begin); } double minLength() const; double notesLength() const { return m_notesLength; } double begin; double end; NoteLabels notes; private: double m_notesLength; }; composer-master/src/songwriter-smm.cc0000644000175000017500000000173213357046745016436 0ustar niknik#include "songwriter.hh" #include "config.hh" #include "util.hh" #include #include void SMMWriter::writeSMM() const { QFile f(path + "/" + s.artist + " - " + s.title + ".txt"); if (!f.open(QFile::WriteOnly | QFile::Truncate)) throw std::runtime_error("Couldn't open target file"); QTextStream out(&f); out.setCodec("UTF-8"); // Loop through the notes const Notes& notes = s.getVocalTrack().notes; for (int i = 0; i < notes.size(); ++i) { const Note& n = notes[i]; if (i == 0 || n.lineBreak) out << '\n' << sec2timestamp(n.begin); // Put timestamp between notes out << n.syllable << sec2timestamp(n.begin); if (n.type == Note::SLEEP) out << '\n'; } out << '\n'; } QString SMMWriter::sec2timestamp(double sec) const { double modsec = std::fmod(sec, 60.0); return QString("[%1:%2:%3]") .arg(int(sec/60), 2, 10, QChar('0')) .arg(int(modsec), 2, 10, QChar('0')) .arg(int(100 * (modsec - int(modsec))), 2, 10, QChar('0')); } composer-master/src/ffmpeg.cc0000644000175000017500000001607713357046745014715 0ustar niknik#include "ffmpeg.hh" #include "config.hh" #include "util.hh" #include #include #include // Somehow ffmpeg headers give errors that these are not defined... #define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 extern "C" { #include AVCODEC_INCLUDE #include AVFORMAT_INCLUDE #include SWSCALE_INCLUDE #include SWRESAMPLE_INCLUDE #include AVUTIL_INCLUDE #include AVUTIL_OPT_INCLUDE #include AVUTIL_MATH_INCLUDE } /// A custom allocator that uses av_malloc for aligned buffers template class AvMalloc: public std::allocator { public: /* pointer allocate(size_type count, allocator::const_pointer* = 0) { pointer ptr = static_cast(m(count * sizeof(T))); if (!ptr) throw std::bad_alloc(); return ptr; } void deallocate(pointer ptr, size_type) { f(ptr); }*/ private: void* m(size_t n) { return av_malloc(n); } void f(void* ptr) { av_free(ptr); } }; /*static*/ QMutex FFmpeg::s_avcodec_mutex; FFmpeg::FFmpeg(std::string const& _filename): m_filename(_filename), m_quit(), m_running(), m_eof(), pFormatCtx(), pAudioCodecCtx(), pAudioCodec(), m_rate(48000), audioStream(-1), m_position() { open(); // Throws on error m_running = true; start(); } FFmpeg::~FFmpeg() { m_quit = true; audioQueue.setEof(); audioQueue.reset(); wait(); // TODO: use RAII for freeing resources (to prevent memory leaks) QMutexLocker l(&s_avcodec_mutex); // avcodec_close is not thread-safe if (pAudioCodecCtx) avcodec_close(pAudioCodecCtx); if (pFormatCtx) avformat_close_input(&pFormatCtx); } double FFmpeg::duration() const { double d = m_running ? pFormatCtx->duration / double(AV_TIME_BASE) : getNaN(); return d >= 0.0 ? d : getInf(); } // FFMPEG has fluctuating API #if LIBAVCODEC_VERSION_INT < ((52<<16)+(64<<8)+0) #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO #define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO #endif void FFmpeg::open() { QMutexLocker l(&s_avcodec_mutex); av_register_all(); av_log_set_level(AV_LOG_ERROR); if (avformat_open_input(&pFormatCtx, m_filename.c_str(), NULL, NULL)) throw std::runtime_error("Cannot open input file"); if (avformat_find_stream_info(pFormatCtx, NULL) < 0) throw std::runtime_error("Cannot find stream information"); pFormatCtx->flags |= AVFMT_FLAG_GENPTS; audioStream = -1; // Take the first video/audio streams for (unsigned int i=0; inb_streams; i++) { AVCodecContext* cc = pFormatCtx->streams[i]->codec; cc->workaround_bugs = FF_BUG_AUTODETECT; if (audioStream == -1 && cc->codec_type==AVMEDIA_TYPE_AUDIO) audioStream = i; } if (audioStream == -1) throw std::runtime_error("No audio stream found"); AVCodecContext* cc = pFormatCtx->streams[audioStream]->codec; pAudioCodec = avcodec_find_decoder(cc->codec_id); audioQueue.setRateChannels(cc->sample_rate, cc->channels); if (!pAudioCodec) throw std::runtime_error("Cannot find audio codec"); if (avcodec_open2(cc, pAudioCodec, NULL) < 0) throw std::runtime_error("Cannot open audio codec"); pAudioCodecCtx = cc; m_resampleContext = swr_alloc(); av_opt_set_int(m_resampleContext, "in_channel_layout", pAudioCodecCtx->channel_layout ? pAudioCodecCtx->channel_layout : av_get_default_channel_layout(pAudioCodecCtx->channels), 0); av_opt_set_int(m_resampleContext, "out_channel_layout", av_get_default_channel_layout(2), 0); av_opt_set_int(m_resampleContext, "in_sample_rate", pAudioCodecCtx->sample_rate, 0); av_opt_set_int(m_resampleContext, "out_sample_rate", m_rate, 0); av_opt_set_int(m_resampleContext, "in_sample_fmt", pAudioCodecCtx->sample_fmt, 0); av_opt_set_int(m_resampleContext, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); swr_init(m_resampleContext); if (!m_resampleContext) throw std::runtime_error("Cannot create resampling context"); } void FFmpeg::run() { int errors = 0; while (!m_quit) { try { if (m_seekTarget == m_seekTarget) seek_internal(); decodeNextFrame(); m_eof = false; errors = 0; } catch (eof_error&) { audioQueue.setEof(); m_eof = true; msleep(100); } catch (std::exception& e) { std::cerr << "FFMPEG error: " << e.what() << std::endl; if (++errors > 2) { std::cerr << "FFMPEG terminating due to errors" << std::endl; m_quit = true; } } } audioQueue.setEof(); m_running = false; m_eof = true; } void FFmpeg::seek(double time, bool wait) { m_seekTarget = time; audioQueue.reset(); // Empty these to unblock the internals in case buffers were full if (wait) while (!m_quit && m_seekTarget == m_seekTarget) msleep(10); } void FFmpeg::seek_internal() { audioQueue.reset(); int flags = 0; // FIXME: if (m_seekTarget < position()) flags |= AVSEEK_FLAG_BACKWARD; int stream = -1; stream = audioStream; int64_t target = m_seekTarget * AV_TIME_BASE; // FIXME: av_rescale_q not declared on recent ffmpeg // Latest Performous code does not have these lines //const AVRational time_base_q = { 1, AV_TIME_BASE }; // AV_TIME_BASE_Q is the same thing with C99 struct literal (not supported by MSVC) //if (stream != -1) target = av_rescale_q(target, time_base_q, pFormatCtx->streams[stream]->time_base); av_seek_frame(pFormatCtx, stream, target, flags); m_seekTarget = getNaN(); // Signal that seeking is done } void FFmpeg::decodeNextFrame() { struct ReadFramePacket: public AVPacket { AVFormatContext* m_s; ReadFramePacket(AVFormatContext* s): m_s(s) { if (av_read_frame(s, this) < 0) throw eof_error(); } ~ReadFramePacket() { av_packet_unref(this); } double time() { return uint64_t(dts) == uint64_t(AV_NOPTS_VALUE) ? getNaN() : double(dts) * av_q2d(m_s->streams[stream_index]->time_base); } }; int frameFinished=0; while (!frameFinished) { ReadFramePacket packet(pFormatCtx); unsigned packetPos = 0; if (packet.stream_index==audioStream) { while (packetPos < packet.size) { if (m_quit || m_seekTarget == m_seekTarget) return; int bytesUsed; #if (LIBAVCODEC_VERSION_INT) > (AV_VERSION_INT(55,0,0)) AVFrame* m_frame = av_frame_alloc(); #else AVFrame* m_frame = avcodec_alloc_frame(); #endif int gotFramePointer = 0; bytesUsed = avcodec_decode_audio4(pAudioCodecCtx,m_frame,&gotFramePointer, &packet); if (bytesUsed < 0) throw std::runtime_error("cannot decode audio frame"); packetPos += bytesUsed; // Update position if timecode is available if (packet.time() == packet.time()) m_position = packet.time(); //resample here! int16_t * output; int out_linesize; int out_samples = swr_get_out_samples(m_resampleContext, m_frame->nb_samples); av_samples_alloc((uint8_t**)&output, &out_linesize, 2, out_samples,AV_SAMPLE_FMT_S16, 0); out_samples = swr_convert(m_resampleContext, (uint8_t**)&output, out_samples, (const uint8_t**)&m_frame->data[0], m_frame->nb_samples); std::vector m_output(output, output+out_samples*2); // Output samples int outsize = m_output.size(); /* Convert bytes into samples */ \ short* samples = reinterpret_cast(&m_output[0]);\ audioQueue.input(samples, samples + outsize, 1.0 / 32767.0);\ m_position += double(out_samples)/m_rate; // New position in case the next packet doesn't have packet.time() } // Audio frames are always finished frameFinished = 1; } } } composer-master/src/pitchvis.hh0000644000175000017500000000332313357046745015302 0ustar niknik#pragma once #include "notes.hh" #include "util.hh" #include #include #include #include #include #include #include #include struct PitchFragment { float time, note, level; // seconds, MIDI note, dB PitchFragment(float time, float note, float level): time(time), note(note), level(level) {} }; struct PitchPath { typedef std::vector Fragments; Fragments fragments; unsigned channel; PitchPath(unsigned channel): channel(channel) {} }; class NoteGraphWidget; class PitchVis: public QThread { Q_OBJECT public: typedef std::vector Paths; QMutex mutex; PitchVis(QString const& filename, QWidget *parent = NULL, int visId = 0); ~PitchVis() { stop(); wait(); } void stop(); void cancel(); void paint(int x1, int y1, int x2, int y2); bool newDataAvailable() const { return moreAvailable; } double getProgress() const { return position / duration; } double getDuration() const { return duration; } int guessNote(double begin, double end, int initial); signals: void renderedImage(const QImage &image, const QPoint &position, int visId); protected: void run(); // Thread runs here private: void renderer(); Paths const& getPaths() { moreAvailable = false; return paths; } MusicalScale scale; QString fileName; Paths paths; double position; ///< Position while analyzing double duration; ///< Song duration (or estimation while analyzing) bool moreAvailable; bool quit; ///< Quit at the frst chance bool cancelled; ///< Cancel analyzing, but use what was done so far bool restart; ///< Should we start the rendering again? QWaitCondition condition; int m_x1, m_y1, m_x2, m_y2; int m_visId; }; composer-master/src/notelabel.hh0000644000175000017500000000601713357046745015421 0ustar niknik#pragma once #include #include #include #include "notes.hh" #include "operation.hh" /** * @brief Widget representing a single note. * * Notes: * - Is rather useless without a parent NoteGraphWidget-object * - Widget is initially hidden and without a pixmap to allow quick creation * - Pixmap updates are generally delayed a little * - The idea is to allow some time to apply the base operation to every note * and then do the gfx updates asynchronously * - NoteLabel has its own mouse handling for moving, resizing, cursors, tooltips etc, * but requires the parent NoteGraphWidget to update some internal states * - Geometry & position is calculated from the underlying Note attributes (i.e. time and pitch) * - Setting size or pos manually will be overridden so the Note must be manipulated instead * - NoteLabel can be serialized to Operation-class */ class NoteLabel: public QLabel { Q_OBJECT public: static const int render_delay; static const int resize_margin; static const double default_length; static const double min_length; NoteLabel(const Note ¬e, QWidget *parent, bool floating = true); QString lyric() const { return m_note.syllable; } void setLyric(const QString &text) { m_note.syllable = text; QTimer::singleShot(render_delay, this, SLOT(updatePixmap())); } QString description(bool multiline) const; bool isSelected() const { return m_selected; } void setSelected(bool state = true); Note& note() { return m_note; } Note note() const { return m_note; } void updateLabel(); void updateTips(); bool isFloating() const { return m_floating; } void setFloating(bool state) { m_floating = state; QTimer::singleShot(render_delay, this, SLOT(updatePixmap())); } bool isLineBreak() const { return m_note.lineBreak; } void setLineBreak(bool state) { m_note.lineBreak = state; QTimer::singleShot(render_delay, this, SLOT(updatePixmap())); } void setType(int newtype) { m_note.type = Note::types[newtype]; QTimer::singleShot(render_delay, this, SLOT(updatePixmap())); } void startResizing(int dir); void startDragging(const QPoint& point); /// Create Operation from NoteLabel operator Operation() const; bool operator<(const NoteLabel &rhs) const { return m_note.begin < rhs.note().begin; } public slots: /// Shows the widget and creates the pixmap; if already visible, do nothing /// @return true if pixmap was actually created, false if the widget was already visible bool createPixmap() { if (isVisible()) return false; show(); updatePixmap(); return true; } /// Updates the pixmap but only if the widget is visible (i.e. createPixmap has been called) void updatePixmap(); protected: void resizeEvent(QResizeEvent *event); void moveEvent(QMoveEvent *event); void mouseMoveEvent(QMouseEvent *event); void closeEvent(QCloseEvent *event) { deleteLater(); event->accept(); } private: Note m_note; bool m_selected; bool m_floating; int m_resizing; QPoint m_hotspot; }; bool inline cmpNoteLabelPtr(const NoteLabel *lhs, const NoteLabel *rhs) { return (*lhs) < (*rhs); } composer-master/src/songwriter-txt.cc0000644000175000017500000000444713357046745016467 0ustar niknik#include "songwriter.hh" #include "config.hh" #include "util.hh" #include void UltraStarTXTWriter::writeTXT() const { QFile f(path + "/notes.txt"); if (!f.open(QFile::WriteOnly | QFile::Truncate)) throw std::runtime_error("Couldn't open target file"); // Figure out song filename QString mp3 = "NO_SONG"; if (!s.music["EDITOR"].isEmpty()) { QFileInfo finfo(s.music["EDITOR"]); mp3 = finfo.fileName(); } QTextStream out(&f); out.setCodec("UTF-8"); // Required fields out << "#TITLE:" << (s.title.isEmpty() ? "Unknown" : s.title) << '\n'; out << "#ARTIST:" << (s.artist.isEmpty() ? "Unknown" : s.artist) << '\n'; out << "#MP3:" << mp3 << '\n'; out << "#BPM:" << tempo << '\n'; out << "#GAP:" << "0" << '\n'; // Time to first lyric in milliseconds // Additional fields out << "#CREATOR:" << (s.creator.isEmpty() ? PACKAGE : s.creator) << '\n'; if (!s.genre.isEmpty()) out << "#GENRE:" << s.genre << '\n'; if (!s.year.isEmpty()) out << "#YEAR:" << s.year << '\n'; if (!s.language.isEmpty()) out << "#LANGUAGE:" << s.language << '\n'; if (!s.edition.isEmpty()) out << "#EDITION:" << s.edition << '\n'; if (!s.cover.isEmpty()) out << "#COVER:" << s.cover << '\n'; if (!s.background.isEmpty()) out << "#BACKGROUND:" << s.background << '\n'; if (!s.video.isEmpty()) out << "#VIDEO:" << s.video << '\n'; // The following are not useful, at least for now //if (s.videoGap != 0) out << "#VIDEOGAP:" << s.videoGap << '\n'; //if (start != 0) out << "#START:" << s.start << '\n'; //out << "#RELATIVE:" << "no" << '\n'; //out << "#PREVIEWSTART:" << s.preview_start << '\n'; //if (!s.music["vocals"].isEmpty()) out << "#VOCALS:" << s.music["vocals"] << '\n'; // FIXME: remove full path // Loop through the notes const Notes& notes = s.getVocalTrack().notes; for (int i = 0; i < notes.size(); ++i) { const Note& n = notes[i]; if (n.type == Note::SLEEP) continue; // Put sleeps between phrases if (i > 0 && n.lineBreak) { double ts = 0.5 * (notes[i-1].end + n.begin); out << "- " << sec2dur(ts) << '\n'; } // Output the note out << (char)n.type << ' '<< sec2dur(n.begin) << ' ' << sec2dur(n.length()) << ' ' << n.note << ' ' << n.syllable << '\n'; } out << "E"; // End indicator } int UltraStarTXTWriter::sec2dur(double sec) const { return round(tempo / 60.0 * sec * 4); } composer-master/src/notelabelmanager.cc0000644000175000017500000003736413357046745016753 0ustar niknik#include #include #include #include #include #include #include #include #include #include #include "notegraphwidget.hh" #include "notelabel.hh" #include "operation.hh" /*static*/ const QString NoteLabelManager::MimeType = "application/x-notelabels"; //to silence the MSVC compiler /*static*/ const double NoteLabelManager::zoomStep = 0.5; /*static*/ const double NoteLabelManager::ppsNormal = 200.0; NoteLabelManager::NoteLabelManager(QWidget *parent) : QLabel(parent), m_selectedAction(NONE), m_pixelsPerSecond(ppsNormal), m_duration(10.0) { // Determine NoteLabel height NoteLabel templabel(Note(" "), NULL); m_noteHalfHeight = templabel.height()/2; templabel.close(); } void NoteLabelManager::clearNotes() { selectNote(NULL); // Clear NoteLabels const QObjectList &childlist = children(); for (QObjectList::const_iterator it = childlist.begin(); it != childlist.end(); ++it) { NoteLabel *child = qobject_cast(*it); if (child) child->close(); } m_notes.clear(); } void NoteLabelManager::selectNote(NoteLabel* note, bool clearPrevious) { // Clear all previous selections? if (!note || clearPrevious) { // NULL means allways clear all for (int i = 0; i < m_selectedNotes.size(); ++i) m_selectedNotes[i]->setSelected(false); m_selectedNotes.clear(); } // Add at the beginning of the chain if (note && !note->isSelected()) { m_selectedNotes.push_front(note); note->setSelected(true); } else if (!note) m_selectedAction = NONE; // Signal UI about the change emit updateNoteInfo(selectedNote()); } void NoteLabelManager::selectAll() { selectNote(NULL); // Clear previous for (int i = m_notes.size()-1; i >= 0; --i) // Traverse in reverse order to get the first note first selectNote(m_notes[i], false); } void NoteLabelManager::selectAllAfter() { if (m_selectedNotes.empty()) return; NoteLabel* first = m_selectedNotes.back(); selectNote(NULL); // Clear previous for (int i = m_notes.size()-1; i >= 0; --i) { // Traverse in reverse order to get the first note first selectNote(m_notes[i], false); if (m_notes[i] == first) break; } } void NoteLabelManager::shiftSelect(NoteLabel* note) { if (!note || note == selectedNote()) return; if (!selectedNote()) { selectNote(note); return; } // Select all notes between the last selection and this int n = getNoteLabelId(selectedNote()), m = getNoteLabelId(note); selectNote(NULL); // Unselect everything if (m < n) std::swap(n, m); for (int i = n; i <= m; ++i) selectNote(m_notes[i], false); } void NoteLabelManager::boxSelect(QPoint p1, QPoint p2) { // Make sure the points are in right order (p1 = upper left, p2 = lower right) if (p1.x() > p2.x()) std::swap(p1.rx(), p2.rx()); if (p1.y() > p2.y()) std::swap(p1.ry(), p2.ry()); // Deselect all selectNote(NULL); // Loop through notes, select the ones inside rectangle for (int i = 0; i < m_notes.size(); ++i) { NoteLabel *nl = m_notes[i]; if (nl->x() > p2.x()) break; if (nl->x() + nl->width() > p1.x() && nl->y() + nl->height() > p1.y() && nl->y() < p2.y()) selectNote(nl, false); } } int NoteLabelManager::getNoteLabelId(NoteLabel* note) const { for (int i = 0; i < m_notes.size(); ++i) if (m_notes[i] == note) return i; return -1; } int NoteLabelManager::findIdForTime(double time) const { for (int i = 0; i < m_notes.size(); ++i) { if (m_notes[i]->note().begin >= time) return i; } return m_notes.size(); } void NoteLabelManager::selectNextSyllable(bool backwards, bool addToSelection) { int i = getNoteLabelId(selectedNote()); if (!backwards && i < m_notes.size()-1) selectNote(m_notes[i+1], !addToSelection); else if (backwards && i > 0) selectNote(m_notes[i-1], !addToSelection); } void NoteLabelManager::selectNextSentenceStart() { // Start looking for the sentance start from the next NoteLabel for (int i = getNoteLabelId(selectedNote()) + 1; i < m_notes.size(); ++i) { if (m_notes[i]->note().lineBreak) { selectNote(m_notes[i]); return; } } } QScrollArea* NoteLabelManager::getScrollArea() const { if (!parentWidget()) return NULL; return qobject_cast(parentWidget()->parent()); } void NoteLabelManager::calcViewport(int &x1, int &y1, int &x2, int &y2) const { QScrollArea *scrollArea = getScrollArea(); x1 = 0, x2 = 0, y1 = 0, y2 = 0; if (scrollArea) { if (scrollArea->horizontalScrollBar()) x1 = scrollArea->horizontalScrollBar()->value(); x2 = x1 + scrollArea->width(); if (scrollArea->verticalScrollBar()) y1 = scrollArea->verticalScrollBar()->value(); y2 = y1 + scrollArea->height(); } } void NoteLabelManager::createNote(double time) { // Spawn an input dialog bool ok; QString text = QInputDialog::getText(this, tr("New note"), tr("Enter one or more lyrics:"), QLineEdit::Normal, "-", &ok); if (ok && !text.isEmpty()) { // Find the correct place for this note int id = findIdForTime(time); int nlvl = (id > 0) ? m_notes[id-1]->note().note : 24; QTextStream ts(&text, QIODevice::ReadOnly); // Loop through all words while (!ts.atEnd()) { QString word; ts >> word; if (!word.isEmpty()) { // Create Operation for each word Operation op("NEW"); op << id << word << time // begin << time+1 // dummy end << nlvl // note << true // floating << false // linebreak << 0; // type doOperation(op); // Execute operation ++id; } } forcedNotePixmapUpdate(); } } void NoteLabelManager::split(NoteLabel *note, float ratio) { if (!note) return; if (selectedNote() == note) selectNote(NULL); // Cut the text int cutpos = int(std::ceil(note->lyric().length() * ratio)); QString firstst = note->lyric().left(cutpos); QString secondst = note->lyric().right(note->lyric().length() - cutpos); const Note& n = note->note(); // Create operations for adding the new labels and deleting the old one int id = getNoteLabelId(note); Operation new1("NEW"), new2("NEW"); new1 << id << firstst << n.begin << n.begin + n.length() * ratio << n.note << note->isFloating() << n.lineBreak << n.getTypeInt(); new2 << id+1 << secondst << n.begin + n.length() * ratio << n.end << n.note << note->isFloating() << false << 0; doOperation(new1, Operation::NO_UPDATE); doOperation(new2, Operation::NO_UPDATE); doOperation(Operation("DEL", id+2), Operation::NO_UPDATE); doOperation(Operation("COMBINER", 3)); // This will combine the previous ones to one undo action forcedNotePixmapUpdate(); } void NoteLabelManager::del(NoteLabel *note) { if (!note) return; // If delete is directed to a selected note, all selected notes will be deleted if (note->isSelected()) { int i = 0; // We need this after the loop for (; i < m_selectedNotes.size(); ++i) { Operation op("DEL"); op << getNoteLabelId(m_selectedNotes[i]); doOperation(op); } // Combine to one undo operation if (i > 1) { doOperation(Operation("COMBINER", i)); } // Clear all m_selectedNotes.clear(); } else { // Here we have non-selected note up for deletion doOperation(Operation("DEL", getNoteLabelId(note))); } } void NoteLabelManager::move(NoteLabel *note, int value) { if (!note) return; int i = 0; // We need this after the loop for (; i < m_selectedNotes.size(); ++i) { const Note &n = m_selectedNotes[i]->note(); Operation op("MOVE"); op << getNoteLabelId(m_selectedNotes[i]) << n.begin << n.end << n.note + value; doOperation(op, Operation::NO_UPDATE); } // Combine to one undo operation if (i > 1) { doOperation(Operation("COMBINER", i), Operation::NO_UPDATE); } updateNotes(); } void NoteLabelManager::setType(NoteLabel *note, int index) { if (!note) return; // Easy case: only one note if (m_selectedNotes.size() == 1 || !note->isSelected()) { if (note->note().getTypeInt() == index) return; Operation op("TYPE"); op << getNoteLabelId(note) << index; doOperation(op, Operation::NO_UPDATE); return; } // Multiple notes selected: apply to all int i = 0; for (; i < m_selectedNotes.size(); ++i) { Operation op("TYPE"); op << getNoteLabelId(m_selectedNotes[i]) << index; doOperation(op, Operation::NO_UPDATE); } doOperation(Operation("COMBINER", i), Operation::NO_UPDATE); } void NoteLabelManager::setFloating(NoteLabel *note, bool state) { if (!note) return; // Easy case: only one note if (m_selectedNotes.size() == 1 || !note->isSelected()) { if (note->isFloating() == state) return; doOperation(Operation("FLOATING", getNoteLabelId(note), state)); return; } // Multiple notes selected: apply to all int i = 0; for (; i < m_selectedNotes.size(); ++i) { doOperation(Operation("FLOATING", getNoteLabelId(m_selectedNotes[i]), state), Operation::NO_UPDATE); } doOperation(Operation("COMBINER", i)); } void NoteLabelManager::setLineBreak(NoteLabel *note, bool state) { if (!note) return; // Easy case: only one note if (m_selectedNotes.size() == 1 || !note->isSelected()) { if (note->isLineBreak() == state) return; doOperation(Operation("LINEBREAK", getNoteLabelId(note), state), Operation::NO_UPDATE); } // Multiple notes selected: apply to all int i = 0; for (; i < m_selectedNotes.size(); ++i) { doOperation(Operation("LINEBREAK", getNoteLabelId(m_selectedNotes[i]), state), Operation::NO_UPDATE); } doOperation(Operation("COMBINER", i), Operation::NO_UPDATE); } void NoteLabelManager::editLyric(NoteLabel *note) { if (!note) return; // Spawn an input dialog bool ok; QString text = QInputDialog::getText(this, tr("Edit lyric"), tr("Lyric:"), QLineEdit::Normal, note->lyric(), &ok); if (ok && !text.isEmpty()) { note->setLyric(text); // Create undo operation Operation op("LYRIC"); op << getNoteLabelId(note) << text; doOperation(op, Operation::NO_EXEC | Operation::NO_UPDATE); } } void NoteLabelManager::doOperation(const Operation& op, int flags) { if (!(flags & Operation::NO_EXEC)) { try { QString action = op.op(); if (action == "BLOCK" || action == "COMBINER") { ; // No op } else if (action == "CLEAR") { clearNotes(); } else if (action == "NEW") { Note newnote(op.s(2)); // lyric newnote.begin = op.d(3); // begin newnote.end = op.d(4); // end newnote.note = op.i(5); // note newnote.lineBreak = op.b(7); // lineBreak newnote.type = Note::types[op.i(8)]; // note type NoteLabel *newLabel = new NoteLabel( newnote, // Note(lyric) this, // parent op.b(6) // floating ); int id = op.i(1); if (id < 0) id = findIdForTime(op.d(3)); // -1 = auto-choose if (m_notes.isEmpty() || id > m_notes.size()) m_notes.push_back(newLabel); else m_notes.insert(id, newLabel); if (flags & Operation::SELECT_NEW) selectNote(newLabel, false); } else { NoteLabel *n = m_notes.at(op.i(1)); if (n) { if (action == "DEL") { n->close(); m_notes.removeAt(op.i(1)); } else if (action == "MOVE") { if(op.i(1) > 0) { NoteLabel *previous = m_notes.at(op.i(1) -1); if(previous && previous->note().end > op.d(2)) { previous->note().end = op.d(2); if(previous->note().begin >= previous->note().end) previous->note().begin = previous->note().end -0.01; } } n->note().begin = op.d(2); n->note().end = op.d(3); n->note().note = op.i(4); n->updateLabel(); n->setFloating(false); } else if (action == "FLOATING") { n->setFloating(op.b(2)); } else if (action == "LINEBREAK") { n->setLineBreak(op.b(2)); } else if (action == "LYRIC") { n->setLyric(op.s(2)); } else if (action == "TYPE") { n->setType(op.i(2)); } else { std::cerr << "Error: Unkown operation type " << action.toStdString() << std::endl; } } } } catch (std::runtime_error&) { std::cerr << "Error! Invalid operation: " << op.dump() << std::endl; } if (!(flags & Operation::NO_UPDATE)) updateNotes(); } if (!(flags & Operation::NO_EMIT)) { emit operationDone(op); emit updateNoteInfo(selectedNote()); } } void NoteLabelManager::zoom(float steps, double focalSecs) { QScrollArea *scrollArea = getScrollArea(); if (!scrollArea) return; // Default focal point is viewport center if (focalSecs < 0) focalSecs = px2s(scrollArea->horizontalScrollBar()->value() + scrollArea->width()/2); double focalFactor = (focalSecs - px2s(scrollArea->horizontalScrollBar()->value())) / px2s(scrollArea->width()); // Update m_pixelsPerSecond { double pps = m_pixelsPerSecond; // Update zoom factor, NaN means reset if (steps != steps) pps = ppsNormal; else { // A little trickier exponential adjustment to avoid accumulating rounding errors double current = std::log(pps / ppsNormal) / std::log(2.0) / zoomStep; // Find the steps for current level int level = clamp(int(round(current + steps)), zoomMin, zoomMax); // New level pps = ppsNormal * std::pow(2.0, level * zoomStep); // Calculate new zoom } if (pps == m_pixelsPerSecond) return; // Nothing changed m_pixelsPerSecond = pps; } setFixedSize(s2px(m_duration), height()); // Update scroll bar position scrollArea->horizontalScrollBar()->setValue(s2px(focalSecs) - focalFactor * scrollArea->width()); // Update notes for (int i = 0; i < m_notes.size(); ++i) m_notes[i]->updateLabel(); // Update pitch visualization update(); // Update window title emit updateNoteInfo(selectedNote()); } int NoteLabelManager::getZoomLevel() const { return int(m_pixelsPerSecond / ppsNormal * 100); } int NoteLabelManager::s2px(double sec) const { return sec * m_pixelsPerSecond; } double NoteLabelManager::px2s(int px) const { return px / m_pixelsPerSecond; } int NoteLabelManager::n2px(double note) const { return height() - 16.0 * note; } double NoteLabelManager::px2n(int px) const { return (height() - px) / 16.0; } void NoteLabelManager::cut() { if (m_selectedNotes.isEmpty()) return; // Copy copy(); // Delete del(selectedNote()); } void NoteLabelManager::copy() { if (m_selectedNotes.isEmpty()) return; // Sort notes (so that they get created in the right order in the other end) std::list sortedNotes = m_selectedNotes.toStdList(); sortedNotes.sort(cmpNoteLabelPtr); // Create operations of the notes and serialize to byte array QByteArray buf; QDataStream stream(&buf, QIODevice::WriteOnly); for (std::list::const_iterator it = sortedNotes.begin(); it != sortedNotes.end(); ++it) stream << (Operation)(*(*it)); QClipboard *clipboard = QApplication::clipboard(); if (!clipboard) return; // Put the data to clipboard QMimeData *mimeData = new QMimeData; mimeData->setData(MimeType, buf); clipboard->setMimeData(mimeData); } void NoteLabelManager::paste() { const QClipboard *clipboard = QApplication::clipboard(); const QMimeData *mimeData = clipboard->mimeData(); if (mimeData->hasFormat(MimeType) && !mimeData->data(MimeType).isEmpty()) { // Get data QByteArray buf = mimeData->data(MimeType); QDataStream stream(&buf, QIODevice::ReadOnly); // Deselect previous selectNote(NULL); double mouseTime = 0; int mouseNote = 0; bool first = true; int x1, y1, x2, y2; calcViewport(x1, y1, x2, y2); int mx = mapFromGlobal(QCursor::pos()).x(); int my = mapFromGlobal(QCursor::pos()).y(); // If mouse is outside, pick values inside viewport if (mx < x1 || mx > x2) mx = x1 + (x2 - x1) / 4.0; if (my < y1 || my > y2) my = (y1 + y2) / 2.0; // Read and execute all NoteLabel Operations from the clipboard while (!stream.atEnd()) { Operation op; stream >> op; if (first) { // Calculate mouse position compensators mouseTime = px2s(mx) - op.d(3); mouseNote = px2n(my) - op.i(5); first = false; } // Put position to mouse cursor op[3] = QVariant(op.d(3) + mouseTime); op[4] = QVariant(op.d(4) + mouseTime); op[5] = QVariant(op.i(5) + mouseNote); doOperation(op, Operation::SELECT_NEW); } } emit updateNoteInfo(selectedNote()); forcedNotePixmapUpdate(); } composer-master/src/midifile.hh0000644000175000017500000001017513357046745015236 0ustar niknik#pragma once #include "types.hh" #include #include #include namespace midifile { typedef uint8_t value_type; typedef uint8_t* iterator; typedef uint8_t const* const_iterator; struct Event { enum Type { NOTE_OFF = 0x80, NOTE_ON = 0x90, NOTE_AFTERTOUCH = 0xA0, CONTROLLER = 0xB0, PROGRAM_CHANGE = 0xC0, CHANNEL_AFTERTOUCH = 0xD0, PITCH_BEND = 0xE0, SPECIAL = 0xF0 }; enum Meta { META_SEQNUMBER = 0, META_TEXT, META_COPYRIGHT, META_SEQNAME, META_INSTRNAME, META_LYRIC, META_MARKERTEXT, META_CUEPOINT, META_CHPREFIX = 0x20, META_ENDOFTRACK = 0x2F, META_TEMPO = 0x51, META_SMTPEOFFSET = 0x54, META_TIMESIGNATURE = 0x58, META_KEYSIGNATURE = 0x59, META_SEQUENCERSPECIFIC = 0x7F }; static char const* metaName(Meta meta); unsigned timecode; // Relative to previous event Type type; unsigned channel; unsigned arg1; unsigned arg2; const_iterator begin, end; // Data belonging to the event (including any terminating 0x7F) Meta getMeta() const { return static_cast(arg1); } std::string getDataStr() const { return std::string(begin, end); } void print() const; ///< Prints to std::cout (for debugging only) Event(): timecode(), type(), channel(), arg1(), arg2(), begin(), end() {} }; class Reader { public: static const unsigned margin = 20; ///< The minimum number of extra bytes required after the end of the buffer for more efficient processing /// MIDI reader, read header. Reader(char const* filename); /// Get the number of tracks, including the timing track if used in the current format unsigned numTracks() const { return m_tracks; } /// Start reading a track (or jump to the next track), must be called before parsing any events /// @returns false if end of file reached (all tracks processed) bool startTrack(); /// Parse the next event of the current track, returns false if at the end of track bool parseEvent(Event& ev); /// Get the number of timecode units in a beat (1/4 note) unsigned getDivision() const { return m_division; } private: void parseMThd(); void parseMTrk(); void parseRiff(char const* name); template unsigned read() { unsigned value = 0; for (unsigned i = N - 1; i < N; --i) value |= *m_pos++ << (8 * i); return value; } unsigned read_varlen() { unsigned value = 0; unsigned len = 0; unsigned c = 0; do { if (++len > 4) throw std::runtime_error("MIDI parse error (too long varlen)"); c = *m_pos++; value = (value << 7) | (c & 0x7F); } while (c & 0x80); return value; } std::vector m_data; ///< Only used when reading a file, not used if the user provides a buffer const_iterator m_pos; const_iterator m_riffEnd; const_iterator m_fileEnd; unsigned m_tracks; unsigned m_division; unsigned m_runningStatus; }; class Writer { public: /// MIDI file writer (constructs the output in a memory buffer) /// @param fmt MIDI format (use 1 if in doubt) /// @param tracks The number of tracks (with fmt 1 this is one more than the actual tracks) /// @param division How many timecode units fit into a beat (1/4 note) Writer(unsigned fmt, unsigned tracks, unsigned division); /// Flush the output to a file (after writing everything) void save(char const* filename); /// Start a new track, must be called before each track. /// Ends any previous track but won't automatically add end of track event. void startTrack(); /// Writes an event to current track void writeEvent(Event const& ev); private: void beginRiff(char const* name); void endRiff(); template void write(unsigned value) { for (unsigned i = N - 1; i < N; --i) m_data.push_back(value >> (8 * i)); } void write_varlen(unsigned value) { if (value >= 0x10000000U) throw std::logic_error("Value cannot be MIDI varlen encoded"); if (value >= 0x200000U) m_data.push_back(0x80 | (value >> 21)); if (value >= 0x4000U) m_data.push_back(0x80 | (value >> 14)); if (value >= 0x80U) m_data.push_back(0x80 | (value >> 7)); m_data.push_back(value & 0x7F); } std::vector m_data; unsigned m_riffBegin; }; } composer-master/src/notegraphwidget.cc0000644000175000017500000006265313357046745016645 0ustar niknik#include #include #include #include #include #include #include #include #include #include #include #include #include "notelabel.hh" #include "notegraphwidget.hh" #include "song.hh" #include "util.hh" #include "busydialog.hh" namespace { static Operation opFromNote(const Note& note, int id, bool floating) { Operation op("NEW"); op << id << note.syllable << note.begin << note.end << note.note << floating << note.lineBreak << note.getTypeInt(); return op; } static const double endMarginSeconds = 5.0; } /*static*/ const int NoteGraphWidget::Height = 768; /*static*/ const QString NoteGraphWidget::BGColor = "#222"; NoteGraphWidget::NoteGraphWidget(QWidget *parent) : NoteLabelManager(parent), m_mouseHotSpot(), m_seeking(), m_actionHappened(), m_seekHandle(this), m_nextNotePixmap(), m_notePixmapTimer(), m_analyzeTimer(), m_playbackTimer(), m_playbackPos(), m_playbackRate(1.0), m_pixmap(), m_pixmapPos() { setProperty("darkBackground", true); setStyleSheet("QLabel[darkBackground=\"true\"] { background: " + BGColor + "; }"); // Initially expanding horizontally to fill the space QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Fixed); setSizePolicy(sp); setFixedHeight(Height); setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true); setMouseTracking(true); setWhatsThis(tr("Note graph that displays the song notes and allows you to manipulate them.")); // Context menu setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showContextMenu(const QPoint&))); qRegisterMetaType("QImage"); // Needed for queued connections from other threads updateNotes(); } void NoteGraphWidget::setLyrics(QString lyrics) { BusyDialog busy(this, 2); QTextStream ts(&lyrics, QIODevice::ReadOnly); doOperation(Operation("CLEAR")); bool firstNote = true; while (!ts.atEnd()) { busy(); // We want to loop one line at the time to insert line breaks bool sentenceStart = true; QString sentence = ts.readLine(); QTextStream ts2(&sentence, QIODevice::ReadOnly); while (!ts2.atEnd()) { QString word; ts2 >> word; if (!word.isEmpty()) { QStringList syllables = word.split('_'); syllables.last().append(" "); //add the space to the last part of the word for(int i = 0; i < syllables.size(); i++) { Note note(syllables[i]); note.end = NoteLabel::default_length; note.note = 24; if (sentenceStart) note.lineBreak = true; doOperation(opFromNote(note, m_notes.size(), !firstNote), Operation::NO_UPDATE); firstNote = false; sentenceStart = false; } } } } // Set duration m_duration = std::max(m_duration, NoteLabel::default_length * m_notes.size() * 1.1 + endMarginSeconds); finalizeNewLyrics(); } void NoteGraphWidget::setLyrics(const VocalTrack &track) { BusyDialog busy(this, 10); doOperation(Operation("CLEAR")); m_duration = std::max(m_duration, track.endTime + endMarginSeconds); const Notes ¬es = track.notes; for (Notes::const_iterator it = notes.begin(); it != notes.end(); ++it) { if (it->type == Note::SLEEP) continue; doOperation(opFromNote(*it, m_notes.size(), false), Operation::NO_UPDATE); busy(); } finalizeNewLyrics(); } void NoteGraphWidget::finalizeNewLyrics() { int ops = m_notes.size(); // Set the last note to non-floating and to the end of the song if (ops > 1 && m_notes[ops-1]->isFloating()) { doOperation(Operation("FLOATING", (int)m_notes.size()-1, false)); Operation moveop("MOVE"); moveop << (int)m_notes.size()-1 << m_duration - NoteLabel::default_length - endMarginSeconds << m_duration - endMarginSeconds << m_notes.back()->note().note; doOperation(moveop); ops += 2; // Amount of extra Operations added here } // Combine the import into one undo action doOperation(Operation("COMBINER", ops)); // Make sure there is enough room if (!m_notes.isEmpty()) setFixedWidth(std::max(width(), s2px(m_duration))); // Calculate floating note positions updateNotes(); // Scroll to show the first note scrollToFirstNote(); // Start creating pixmaps for notes startNotePixmapUpdates(); } void NoteGraphWidget::scrollToFirstNote() { QScrollArea* scrollArea = getScrollArea(); if (scrollArea && !m_notes.isEmpty()) { const Note& n = m_notes.front()->note(); int w = scrollArea->width(); scrollArea->ensureVisible(s2px(n.begin) + w/3, n2px(n.note), w/2, scrollArea->height()/2); } } void NoteGraphWidget::analyzeMusic(QString filepath, int visId) { m_pitch[visId].reset(new PitchVis(filepath, this, visId)); connect(m_pitch[visId].data(), SIGNAL(renderedImage(QImage,QPoint,int)), this, SLOT(updatePixmap(QImage,QPoint,int))); m_analyzeTimer = startTimer(100); } void NoteGraphWidget::timerEvent(QTimerEvent* event) { if (event->timerId() == m_playbackTimer) { // Playback position update m_playbackPos += m_playbackInterval.restart() * m_playbackRate; updateMusicPos(m_playbackPos); } else if (event->timerId() == m_analyzeTimer && m_pitch[0]) { // PitchVis stuff double progress = 1.0, duration; for (int i = 0; i < MaxPitchVis; ++i) { if (!m_pitch[i]) continue; QMutexLocker locker(&m_pitch[i]->mutex); progress = std::min(progress, m_pitch[i]->getProgress()); if (i == 0) duration = m_pitch[i]->getDuration(); } emit analyzeProgress(1000 * progress, 1000); // Update progress bar m_duration = std::max(m_duration, duration); // Analyzing has ended? if (progress == 1.0) { killTimer(m_analyzeTimer); updatePitch(); } } else if (event->timerId() == m_notePixmapTimer) { // Here we create a pixmap for a NoteLabel if (m_nextNotePixmap >= m_notes.size()) { killTimer(m_notePixmapTimer); m_notePixmapTimer = 0; m_nextNotePixmap = 0; return; } // Loop until a pixmap-to-create is found - we only create one at a time to not block the UI while (m_nextNotePixmap < m_notes.size() && !m_notes[m_nextNotePixmap]->createPixmap()) ++m_nextNotePixmap; ++m_nextNotePixmap; } } void NoteGraphWidget::startNotePixmapUpdates() { // With 0-delay, note pixmaps are created whenever there is not events to process // This means fast performance while keeping snappy interface if (!m_notePixmapTimer) m_notePixmapTimer = startTimer(1); m_nextNotePixmap = 0; } void NoteGraphWidget::forcedNotePixmapUpdate() { // Things like split and open often don't trigger the timer event, do a forced update of the pixmaps here m_nextNotePixmap = 0; while (m_nextNotePixmap < m_notes.size()) { m_notes[m_nextNotePixmap]->createPixmap(); ++m_nextNotePixmap; } } void NoteGraphWidget::playbackRateChanged(qreal rate) { m_playbackRate = rate; } void NoteGraphWidget::paintEvent(QPaintEvent*) { setFixedSize(s2px(m_duration), height()); // Find out the viewport int x1, y1, x2, y2; calcViewport(x1, y1, x2, y2); QPainter painter(this); // PitchVis pixmap for (int i = 0; i < MaxPitchVis; ++i) { if (!m_pixmap[i].isNull()) painter.drawPixmap(m_pixmapPos[i], m_pixmap[i]); } // Octave lines QPen pen; pen.setWidth(1); pen.setColor(QColor("#666")); painter.setPen(pen); for (int i = 1; i < 4; ++i) painter.drawLine(x1, n2px(i*12), x2, n2px(i*12)); // Selection box if (!m_mouseHotSpot.isNull()) { QPoint mousep = mapFromGlobal(QCursor::pos()); pen.setWidth(2); pen.setColor(QColor("#800")); painter.setPen(pen); painter.drawRect(QRect(m_mouseHotSpot, mousep)); } } void NoteGraphWidget::updatePixmap(const QImage &image, const QPoint &position, int visId) { // PitchVis sends its renderings here, let's save & draw them // This gets actually called in our own thread by our own event loop (queued connection) m_pixmap[visId] = QPixmap::fromImage(image); m_pixmapPos[visId] = position; update(); } void NoteGraphWidget::updatePitch() { // Called whenever pitch needs updating // Note that the scrollbar change signals are connected here, so no need to call this from everywhere if (!m_pitch[0]) return; // Find out the viewport int x1, y1, x2, y2; calcViewport(x1, y1, x2, y2); // Ask for a new render for (int i = 0; i < MaxPitchVis; ++i) if (m_pitch[i]) m_pitch[i]->paint(x1, 0, x2, height()); } void NoteGraphWidget::updateNotes(bool leftToRight) { // Here happens the magic that adjusts the floating // notes according to the fixed ones. FloatingGap gap(leftToRight ? 0 : m_duration); // Determine gaps between non-floating notes // Variable leftToRight controls the iteration direction. for (int i = (leftToRight ? 0 : m_notes.size()-1); i >= 0 && i < m_notes.size(); i += (leftToRight ? 1 : -1)) { NoteLabel *child = m_notes[i]; if (!child) continue; Note &n = m_notes[i]->note(); if (child->isFloating() && i != (leftToRight ? m_notes.size()-1 : 0)) { // Add floating note to gap gap.addNote(child); } else { // Fixed note encountered, handle the gap (divide notes evenly into it) gap.end = leftToRight ? n.begin : n.end; if (!gap.notes.isEmpty()) { // Calculate note length and space between notes double len = NoteLabel::min_length, step = 0; // Minimum values if (gap.length() > gap.minLength()) { // Is there space? len = gap.length() / double(gap.notes.size()) * 0.9; step = (gap.length() - len * gap.notes.size()) / double(gap.notes.size() + 1); } // Calculate starting time of the first note in gap double pos = gap.begin + (leftToRight ? step : (-step - len)); // Loop through all notes for (NoteLabels::iterator it2 = gap.notes.begin(); it2 != gap.notes.end(); ++it2) { Note &n2 = (*it2)->note(); // Set new note begin/end n2.begin = pos; n2.end = pos + len; // Try to find optimal pitch // TODO: Use info also from other pitchvis if (m_pitch[0]) n2.note = m_pitch[0]->guessNote(pos, pos + len + step, 24); // Calculate starting time for the next note pos += (len + step) * (leftToRight ? 1 : -1); // Update NoteLabel geometry (*it2)->updateLabel(); } } // Also move the fixed one (probably the one being moved by user) if there is no space double gapl = leftToRight ? (gap.end - gap.begin) : (gap.begin - gap.end); if (gapl < gap.minLength()) n.move(gap.begin + (leftToRight ? gap.minLength() : (-gap.minLength() - n.length()))); child->updateLabel(); // Start a new gap gap = FloatingGap(leftToRight ? n.end : n.begin); } } } void NoteGraphWidget::updateMusicPos(qint64 time, bool smoothing) { m_playbackPos = time; int x = s2px(m_playbackPos / 1000.0) - m_seekHandle.width() / 2; if (m_playbackTimer) killTimer(m_playbackTimer); m_seekHandle.move(x, 0); if (smoothing) m_playbackTimer = startTimer(20); // Hope for 50 fps else m_playbackTimer = 0; m_playbackInterval.restart(); } void NoteGraphWidget::stopMusic() { if (m_playbackTimer) killTimer(m_playbackTimer); m_playbackTimer = 0; } void NoteGraphWidget::seek(int x) { m_seekHandle.move(x - m_seekHandle.width()/2, 0); emit seeked(1000 * px2s(x)); } void NoteGraphWidget::zoom(float steps, double focalSecs) { NoteLabelManager::zoom(steps, focalSecs); // Update seek handle position int x = s2px(m_playbackPos / 1000.0) - m_seekHandle.width() / 2; m_seekHandle.move(x, 0); } void NoteGraphWidget::timeCurrent() { if (selectedNote()) { Operation op("MOVE"); double begin = px2s(m_seekHandle.curx()); double end = px2s(m_seekHandle.curx() + selectedNote()->width()); int n = selectedNote()->note().note; // TODO: Use info also from other pitchvis if (m_pitch[0]) n = m_pitch[0]->guessNote(begin, end, n); op << getNoteLabelId(selectedNote()) << begin << end << n; doOperation(op); } } void NoteGraphWidget::timeSyllable() { timeCurrent(); selectNextSyllable(); } void NoteGraphWidget::timeSentence() { timeCurrent(); selectNextSentenceStart(); } void NoteGraphWidget::mousePressEvent(QMouseEvent *event) { NoteLabel *child = qobject_cast(childAt(event->pos())); if (!child) { SeekHandle *seekh = qobject_cast(childAt(event->pos())); if (!seekh) { // Middle click or Alt + Left Click empty area = pan if (event->button() == Qt::MiddleButton || (event->button() == Qt::LeftButton && event->modifiers() & Qt::AltModifier)) { m_mouseHotSpot = event->pos(); // Left click empty area = dragbox selection } else if (event->button() == Qt::LeftButton) { m_mouseHotSpot = event->pos(); } } else { // Seeking m_seeking = true; setCursor(QCursor(Qt::SizeHorCursor)); } return; } if (child->isHidden()) return; QPoint hotSpot = event->pos() - child->pos(); // Left Click if (event->button() == Qt::LeftButton) { // Determine if it is drag or resize if (hotSpot.x() < NoteLabel::resize_margin || hotSpot.x() > child->width() - NoteLabel::resize_margin) { // Start a resize selectNote(child); // Resizing will deselect everything but one m_selectedAction = RESIZE; child->startResizing( (hotSpot.x() < NoteLabel::resize_margin) ? -1 : 1 ); } else { if (child->isSelected()) ; // No op // Ctrl and Shift allow selecting multiple notes for dragging else if (event->modifiers() & Qt::ShiftModifier) shiftSelect(child); else selectNote(child, !(event->modifiers() & Qt::ControlModifier)); m_selectedAction = MOVE; child->startDragging(hotSpot); } // Middle Click } else if (event->button() == Qt::MiddleButton) { split(child, float(hotSpot.x()) / child->width()); // Right Click } else if (event->button() == Qt::RightButton) { event->ignore(); } } void NoteGraphWidget::mouseReleaseEvent(QMouseEvent *event) { (void)*event; if (m_selectedAction != NONE) { if (selectedNote()) { int movecount = 0; for (int i = 0; i < m_selectedNotes.size(); ++i) { NoteLabel *nl = m_selectedNotes[i]; nl->startResizing(0); nl->startDragging(QPoint()); const Note& n = nl->note(); if (m_actionHappened) { // Operation for undo stack & saving Operation op("MOVE"); op << getNoteLabelId(nl) << n.begin << n.end << n.note; doOperation(op, Operation::NO_EXEC); ++movecount; } } // Combine to one undo operation if (movecount > 1) { Operation op("COMBINER"); op << movecount; doOperation(op); } // If we didn't move, select the note under cursor NoteLabel *child = qobject_cast(childAt(event->pos())); if (child && !m_actionHappened && !event->modifiers() && event->button() == Qt::LeftButton) selectNote(child); } m_selectedAction = NONE; } m_actionHappened = false; m_mouseHotSpot = QPoint(); m_seeking = false; setCursor(QCursor()); updateNotes(); update(); // Repaint to remove possible selection box } void NoteGraphWidget::wheelEvent(QWheelEvent *event) { // Wheel = Zoom if (event->orientation() == Qt::Vertical) { float numDegrees = event->delta() / 8; // Qt resolution is 8th of a degree float numSteps = numDegrees / 15; // Usually mice have 15 degree steps zoom(numSteps, px2s(event->pos().x())); QToolTip::showText(event->globalPos(), "Zoom: " + QString::number(getZoomLevel()) + " %", this); event->accept(); return; } event->ignore(); } void NoteGraphWidget::mouseDoubleClickEvent(QMouseEvent *event) { NoteLabel *child = qobject_cast(childAt(event->pos())); if (!child) { // Double click empty space = seek there seek(event->x()); selectNextSyllable(); return; } editLyric(child); } void NoteGraphWidget::mouseMoveEvent(QMouseEvent *event) { if (!m_actionHappened) { // Unfloat all selected notes, otherwise the move would be b0rked by auto-pitch if (m_selectedAction != NONE && selectedNote()) { m_actionHappened = true; // We have movement, so resize/move can be accepted // Undo op is handled later by the MOVE constructed at drop for (int i = 0; i < m_selectedNotes.size(); ++i) m_selectedNotes[i]->setFloating(false); } } // Seeking if (m_seeking) { seek(event->x()); // Box selection } else if (!m_mouseHotSpot.isNull() && (event->buttons() & Qt::LeftButton) && !(event->modifiers() & Qt::AltModifier)) { boxSelect(m_mouseHotSpot, event->pos()); update(); // Paint selection box // Pan } else if (!m_mouseHotSpot.isNull()) { setCursor(QCursor(Qt::ClosedHandCursor)); if (QScrollArea *scrollArea = getScrollArea()) { QPoint diff = event->pos() - m_mouseHotSpot; QScrollBar *scrollHor = scrollArea->horizontalScrollBar(); scrollHor->setValue(scrollHor->value() - diff.x()); QScrollBar *scrollVer = scrollArea->verticalScrollBar(); scrollVer->setValue(scrollVer->value() - diff.y()); m_mouseHotSpot = event->pos() - diff; } } MusicalScale ms; int note = round(px2n(event->y())); emit statusBarMessage(QString("Time: %1 s, note: %2 (%3)") .arg(px2s(event->x())) .arg(ms.getNoteStr(ms.getNoteFreq(note))) .arg(note)); emit updateNoteInfo(selectedNote()); } void NoteGraphWidget::keyPressEvent(QKeyEvent *event) { int k = event->key(), m = event->modifiers(); // NOTE: Putting shortcuts here makes them local to the NoteGraphWidget, // i.e. it must have focus for them to work. Concider using EditorApp. if (k == Qt::Key_Return) { // Edit lyric editLyric(selectedNote()); } else if (k == Qt::Key_Left) { // Select note on the left selectNextSyllable(true, (m & Qt::ControlModifier)); } else if (k == Qt::Key_Right) { // Select note on the right selectNextSyllable(false, (m & Qt::ControlModifier)); } else if (k == Qt::Key_Up) { // Move note up move(selectedNote(), 1); } else if (k == Qt::Key_Down) { // Move note down move(selectedNote(), -1); } else if (k == Qt::Key_Escape) { // Unselect all note(s) selectNote(NULL); } else { QWidget::keyPressEvent(event); } } void NoteGraphWidget::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat("text/plain")) event->acceptProposedAction(); } void NoteGraphWidget::dropEvent(QDropEvent *event) { QString lyrics = event->mimeData()->text(); if (m_notes.isEmpty() || QMessageBox::question(this, tr("Replace lyrics"), tr("Do you wish to replace the existing lyrics?"), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { setLyrics(lyrics); } event->acceptProposedAction(); } void NoteGraphWidget::showContextMenu(const QPoint &pos) { QPoint globalPos = mapToGlobal(pos); NoteLabel *child = qobject_cast(childAt(mapFromGlobal(globalPos))); if (child && !child->isSelected()) selectNote(child); QMenu menuContext(NULL); QMenu menuType(tr("Type"), NULL); QAction *actionNew = menuContext.addAction(tr("New note")); actionNew->setIcon(QIcon::fromTheme("insert-object", QIcon(":/icons/insert-object.png"))); actionNew->setEnabled(!child); // Only available when no notes under cursor QAction *actionSplit = menuContext.addAction(tr("Split")); actionSplit->setEnabled(m_selectedNotes.size() == 1); // Only available when exactly one note selected QAction *actionLyric = menuContext.addAction(tr("Edit lyric")); actionLyric->setEnabled(m_selectedNotes.size() == 1); // Only available when exactly one note selected menuContext.addSeparator(); QAction *actionFloating = menuContext.addAction(tr("Floating")); actionFloating->setCheckable(true); QAction *actionLineBreak = menuContext.addAction(tr("Line break")); actionLineBreak->setCheckable(true); menuContext.addAction(menuType.menuAction()); QAction *actionNormal = menuType.addAction(tr("Normal")); actionNormal->setCheckable(true); QAction *actionGolden = menuType.addAction(tr("Golden")); actionGolden->setCheckable(true); QAction *actionFreestyle = menuType.addAction(tr("Freestyle")); actionFreestyle->setCheckable(true); NoteLabel *nl = selectedNote(); if (!nl) { actionFloating->setEnabled(false); actionLineBreak->setEnabled(false); menuType.setEnabled(false); } else { actionFloating->setChecked(nl->isFloating()); actionLineBreak->setChecked(nl->isLineBreak()); actionNormal->setChecked(nl->note().type == Note::NORMAL); actionGolden->setChecked(nl->note().type == Note::GOLDEN); actionFreestyle->setChecked(nl->note().type == Note::FREESTYLE); } menuContext.addSeparator(); QAction *actionResetZoom = menuContext.addAction(tr("Reset zoom")); actionResetZoom->setIcon(QIcon::fromTheme("zoom-original", QIcon(":/icons/zoom-original.png"))); actionResetZoom->setEnabled(getZoomLevel() != 100); menuContext.addSeparator(); QAction *actionCut = menuContext.addAction(tr("Cut")); actionCut->setIcon(QIcon::fromTheme("edit-cut", QIcon(":/icons/edit-cut.png"))); actionCut->setEnabled(!m_selectedNotes.isEmpty()); QAction *actionCopy = menuContext.addAction(tr("Copy")); actionCopy->setIcon(QIcon::fromTheme("edit-copy", QIcon(":/icons/edit-copy.png"))); actionCopy->setEnabled(!m_selectedNotes.isEmpty()); QAction *actionPaste = menuContext.addAction(tr("Paste")); actionPaste->setIcon(QIcon::fromTheme("edit-paste", QIcon(":/icons/edit-paste.png"))); QAction *actionDelete = menuContext.addAction(tr("Delete")); actionDelete->setIcon(QIcon::fromTheme("edit-delete", QIcon(":/icons/edit-delete.png"))); actionDelete->setEnabled(!m_selectedNotes.isEmpty()); menuContext.addSeparator(); QAction *actionSelectAll = menuContext.addAction(tr("Select all")); actionSelectAll->setEnabled(!m_notes.isEmpty()); actionSelectAll->setIcon(QIcon::fromTheme("edit-select-all", QIcon(":/icons/edit-select-all.png"))); QAction *actionSelectAllAfter = menuContext.addAction(tr("Select all after")); actionSelectAllAfter->setEnabled(!m_selectedNotes.isEmpty()); actionSelectAllAfter->setIcon(QIcon::fromTheme("edit-select-all", QIcon(":/icons/edit-select-all.png"))); QAction *actionDeselect = menuContext.addAction(tr("Deselect")); actionDeselect->setEnabled(!m_selectedNotes.isEmpty()); QAction *sel = menuContext.exec(globalPos); if (sel) { if (sel == actionNew) createNote(px2s(mapFromGlobal(globalPos).x())); else if (sel == actionSplit) split(nl); else if (sel == actionLyric) editLyric(nl); else if (sel == actionFloating) setFloating(nl, !nl->isFloating()); else if (sel == actionLineBreak) setLineBreak(nl, !nl->isLineBreak()); else if (sel == actionNormal) setType(nl, 0); else if (sel == actionGolden) setType(nl, 1); else if (sel == actionFreestyle) setType(nl, 2); else if (sel == actionResetZoom) zoom(getNaN()); else if (sel == actionCut) cut(); else if (sel == actionCopy) copy(); else if (sel == actionPaste) paste(); else if (sel == actionDelete) del(nl); else if (sel == actionSelectAll) selectAll(); else if (sel == actionSelectAllAfter) selectAllAfter(); else if (sel == actionDeselect) selectNote(NULL); } menuType.clear(); menuContext.clear(); } VocalTrack NoteGraphWidget::getVocalTrack() const { VocalTrack track(TrackName::LEAD_VOCAL); Notes& notes = track.notes; if (!m_notes.isEmpty()) { for (int i = 0; i < m_notes.size(); ++i) { notes.push_back(m_notes[i]->note()); track.noteMin = std::min(notes.back().note, track.noteMin); track.noteMax = std::max(notes.back().note, track.noteMin); } track.beginTime = notes.front().begin; track.endTime = notes.back().end; } return track; } QString NoteGraphWidget::getCurrentSentence() const { QString lyrics; if (!m_notes.isEmpty() && selectedNote()) { int id = getNoteLabelId(selectedNote()); // Get to phrase beginning while (id >= 0 && !m_notes[id]->note().lineBreak) --id; if (id < 0) return ""; // Now loop the phrase for (int i = id; i < m_notes.size(); ++i) { if (i != id && m_notes[i]->note().lineBreak) break; // Selected notes are highlighted with different color if (m_notes[i]->isSelected()) lyrics += ""; lyrics += m_notes[i]->lyric(); if (m_notes[i]->isSelected()) lyrics += ""; lyrics += " "; } lyrics = lyrics.left(lyrics.size() - 1); // Remove trailing space } return lyrics; } QString NoteGraphWidget::getPrevSentence() const { QString lyrics; if (!m_notes.isEmpty() && selectedNote()) { // First find the previous start int id = getNoteLabelId(selectedNote()); while (id >= 0 && !m_notes[id]->note().lineBreak) --id; if (id < 0) return ""; --id; while (id >= 0 && !m_notes[id]->note().lineBreak) --id; if (id < 0) return ""; // Now get the sentence for (int i = id; i < m_notes.size(); ++i) { if (i != id && m_notes[i]->note().lineBreak) break; lyrics += m_notes[i]->lyric() + " "; } lyrics = lyrics.left(lyrics.size() - 1); // Remove trailing space } return lyrics; } QString NoteGraphWidget::dumpLyrics() const { QString lyrics; if (!m_notes.isEmpty()) { for (int i = 0; i < m_notes.size(); ++i) { if(!lyrics.isEmpty() && m_notes[i]->note().lineBreak) lyrics += '\n'; lyrics += m_notes[i]->lyric() + " "; } } return lyrics; } /// SeekHandle SeekHandle::SeekHandle(QWidget *parent) : QLabel(parent) { QImage image(8, NoteGraphWidget::Height, QImage::Format_ARGB32_Premultiplied); image.fill(qRgba(128, 128, 128, 128)); setPixmap(QPixmap::fromImage(image)); setMouseTracking(true); setStatusTip(tr("Seek by dragging")); } void SeekHandle::mouseMoveEvent(QMouseEvent *event) { setCursor(QCursor(Qt::SizeHorCursor)); event->ignore(); } void SeekHandle::moveEvent(QMoveEvent*) { // Make handle always visible in the ScrollArea if (wrapToViewport && parentWidget() && parentWidget()->parentWidget()) { QScrollArea *scrollArea = qobject_cast(parentWidget()->parentWidget()->parent()); if (scrollArea) { QScrollBar *scrollVer = scrollArea->verticalScrollBar(); int y = 0; if (scrollVer) y = scrollVer->value(); scrollArea->ensureVisible(x() + scrollArea->width()/3, y, scrollArea->width()/3, 0); } } } /// FloatingGap void FloatingGap::addNote(NoteLabel* n) { notes.push_back(n); end = n->note().end; m_notesLength += n->note().length(); } double FloatingGap::minLength() const { return notes.size() * NoteLabel::min_length; } composer-master/src/song.hh0000644000175000017500000001371613357046745014426 0ustar niknik#pragma once #include #include #include #include "notes.hh" #include #include /// parsing of songfile failed struct SongParserException: public std::runtime_error { /// constructor SongParserException(QString const& msg, unsigned int linenum, bool sil = false) : runtime_error(msg.toStdString()), m_linenum(linenum), m_silent(sil) {} unsigned int line() const { return m_linenum; } ///< line in which the error occured bool silent() const { return m_silent; } ///< if the error should not be printed to user (file skipped) private: unsigned int m_linenum; bool m_silent; }; class SongParser; namespace TrackName { const QString GUITAR = "Guitar"; const QString GUITAR_COOP = "Coop guitar"; const QString GUITAR_RHYTHM = "Rhythm guitar"; const QString BASS = "Bass"; const QString DRUMS = "Drums"; const QString LEAD_VOCAL = "Vocals"; const QString HARMONIC_1 = "Harmonic 1"; const QString HARMONIC_2 = "Harmonic 2"; const QString HARMONIC_3 = "Harmonic 3"; } /// class to load and parse songfiles class Song { Q_DISABLE_COPY(Song); friend class SongParser; VocalTracks vocalTracks; ///< notes for the sing part VocalTrack dummyVocal; ///< notes for the sing part public: /// constructors Song(): dummyVocal(TrackName::LEAD_VOCAL) { reload(true); } Song(QString const& path_, QString const& filename_): dummyVocal(TrackName::LEAD_VOCAL), path(path_), filename(filename_) { reload(false); } /// reload song void reload(bool errorIgnore = true); /// parse field bool parseField(QString const& line); /// drop notes (to conserve memory), but keep info about available tracks void dropNotes(); /** Get formatted song label. **/ QString str() const { return title + " by " + artist; } /** Get full song information (used by the search function). **/ QString strFull() const { return title + "\n" + artist + "\n" + genre + "\n" + edition + "\n" + path; } /// Is the song parsed from the file yet? enum LoadStatus { NONE, HEADER, FULL } loadStatus; /// status of song enum Status { NORMAL, INSTRUMENTAL_BREAK, FINISHED }; /** Get the song status at a given timestamp **/ Status status(double time); int randomIdx; ///< sorting index used for random order void insertVocalTrack(QString vocalTrack, VocalTrack track) { vocalTracks.erase(vocalTrack); vocalTracks.insert(std::make_pair(vocalTrack, track)); //remove type specification to remove compile error } // Get a selected track, or LEAD_VOCAL if not found or the first one if not found VocalTrack& getVocalTrack(QString vocalTrack = TrackName::LEAD_VOCAL) { if(vocalTracks.find(vocalTrack) != vocalTracks.end()) { return vocalTracks.find(vocalTrack)->second; } else { if(vocalTracks.find(TrackName::LEAD_VOCAL) != vocalTracks.end()) { return vocalTracks.find(TrackName::LEAD_VOCAL)->second; } else { if(!vocalTracks.empty()) { return vocalTracks.begin()->second; } else { return dummyVocal; } } } } VocalTrack getVocalTrack(QString vocalTrack = TrackName::LEAD_VOCAL) const { if(vocalTracks.find(vocalTrack) != vocalTracks.end()) { return vocalTracks.find(vocalTrack)->second; } else { if(vocalTracks.find(TrackName::LEAD_VOCAL) != vocalTracks.end()) { return vocalTracks.find(TrackName::LEAD_VOCAL)->second; } else { if(!vocalTracks.empty()) { return vocalTracks.begin()->second; } else { return dummyVocal; } } } } std::vector getVocalTrackNames() { std::vector result; for (VocalTracks::const_iterator it = vocalTracks.begin(); it != vocalTracks.end(); ++it) { result.push_back(it->first); } return result; } //InstrumentTracks instrumentTracks; ///< guitar etc. notes for this song //DanceTracks danceTracks; ///< dance tracks //bool hasDance() const { return !danceTracks.empty(); } //bool hasDrums() const { return instrumentTracks.find(TrackName::DRUMS) != instrumentTracks.end(); } //bool hasGuitars() const { return instrumentTracks.size() - hasDrums(); } bool hasVocals() const { return !vocalTracks.empty(); } QString path; ///< path of songfile QString filename; ///< name of songfile QString midifilename; ///< name of midi file in FoF format std::vector category; ///< category of song QString genre; ///< genre QString edition; ///< license QString title; ///< songtitle QString artist; ///< artist QString text; ///< songtext QString creator; ///< creator QString language; ///< language QString year; ///< year QMap music; ///< music files (background, guitar, rhythm/bass, drums, vocals) QString cover; ///< cd cover QString background; ///< background image QString video; ///< video double bpm; ///< used for more accurate import --> export cycle /// Variables used for comparisons (sorting) QString collateByTitle; QString collateByTitleOnly; /// Variables used for comparisons (sorting) QString collateByArtist; QString collateByArtistOnly; /** Rebuild collate variables from other strings **/ void collateUpdate(); /** Convert a string to its collate form **/ static QString collate(QString const& str); double videoGap; ///< gap with video double start; ///< start of song double preview_start; ///< starting time for the preview typedef std::vector > Stops; Stops stops; ///< related to dance typedef std::vector Beats; Beats beats; ///< related to instrument and dance bool hasBRE; ///< is there a Big Rock Ending? (used for drums only) bool b0rkedTracks; ///< are some tracks broken? (so that user can be notified) struct SongSection { QString name; double begin; SongSection(QString const& name, const double begin): name(name), begin(begin) {} }; typedef std::vector SongSections; SongSections songsections; ///< vector of song sections bool getNextSection(double pos, SongSection §ion); bool getPrevSection(double pos, SongSection §ion); }; static inline bool operator<(Song const& l, Song const& r) { return l.collateByArtist < r.collateByArtist; } composer-master/src/songparser-txt.cc0000644000175000017500000001304413357046745016440 0ustar niknik#include "songparser.hh" #include #include /// @file /// Functions used for parsing the UltraStar TXT song format using namespace SongParserUtil; /// 'Magick' to check if this file looks like correct format bool SongParser::txtCheck(QString const& data) { return data[0] == '#' && data[1] >= 'A' && data[1] <= 'Z'; } /// Parser void SongParser::txtParse() { QString line; // Parse header while (getline(line) && txtParseField(line)) {} if (m_song.title.isEmpty() || m_song.artist.isEmpty()) throw std::runtime_error("Required header fields missing"); if (m_song.bpm != 0.0) addBPM(0, m_song.bpm); // Parse notes VocalTrack vocal(TrackName::LEAD_VOCAL); while (txtParseNote(line, vocal) && getline(line)) {} // Workaround for the terminating : 1 0 0 line, written by some converters if (!vocal.notes.empty() && vocal.notes.back().type != Note::SLEEP && vocal.notes.back().begin == vocal.notes.back().end) vocal.notes.pop_back(); m_song.insertVocalTrack(TrackName::LEAD_VOCAL, vocal); } bool SongParser::txtParseField(QString const& line) { if (line.isEmpty()) return true; if (line[0] != '#') return false; int pos = line.indexOf(':'); if (pos < 0) throw std::runtime_error("Invalid txt format, should be #key:value"); QString key = line.left(pos).trimmed().mid(1); QString value = line.mid(pos + 1).trimmed(); bool ok = true; if (value.isEmpty()) return true; if (key == "TITLE") m_song.title = value; else if (key == "ARTIST") m_song.artist = value; else if (key == "EDITION") m_song.edition = value; else if (key == "GENRE") m_song.genre = value; else if (key == "CREATOR") m_song.creator = value; else if (key == "COVER") m_song.cover = value; else if (key == "MP3") m_song.music["background"] = m_song.path + value; else if (key == "VOCALS") m_song.music["vocals"] = m_song.path + value; else if (key == "VIDEO") m_song.video = value; else if (key == "BACKGROUND") m_song.background = value; else if (key == "START") m_song.start = value.replace(',','.').toDouble(&ok); else if (key == "VIDEOGAP") m_song.videoGap = value.replace(',','.').toDouble(&ok); else if (key == "PREVIEWSTART") m_song.preview_start = value.replace(',','.').toDouble(&ok); else if (key == "RELATIVE") assign(m_relative, value); else if (key == "GAP") { m_gap = value.replace(',','.').toDouble(&ok); m_gap *= 1e-3; } else if (key == "BPM") m_song.bpm = value.replace(',','.').toDouble(&ok); else if (key == "LANGUAGE") m_song.language = value; else if (key == "YEAR") m_song.year = value; if (!ok) throw std::runtime_error(QString("Invalid value for %1: %2").arg(key).arg(value).toStdString()); return true; } bool SongParser::txtParseNote(QString line, VocalTrack &vocal) { if (line.isEmpty() || line == "\r") return true; // Trim leading whitespace (after is preserved for word breaking purposes) while (line[0].isSpace()) { line = line.mid(1); if (line.isEmpty()) return true; } if (line[0] == '#') throw std::runtime_error("Key found in the middle of notes"); if (line[0] == 'E') return false; QTextStream iss(&line); if (line[0] == 'B') { unsigned int ts; double bpm; QChar ignore; iss >> ignore; iss >> ts >> bpm; if (iss.status() != QTextStream::Ok) throw std::runtime_error("Invalid BPM line format"); addBPM(ts, bpm); return true; } if (line[0] == 'P') return true; //We ignore player information for now (multiplayer hack) Note n; n.type = Note::Type(iss.read(1)[0].toLatin1()); unsigned int ts = m_prevts; switch (n.type) { case Note::NORMAL: case Note::FREESTYLE: case Note::GOLDEN: { unsigned int length = 0; iss >> ts >> length >> n.note; if (iss.status() != QTextStream::Ok) throw std::runtime_error("Invalid note line format"); n.notePrev = n.note; // No slide notes in TXT yet. if (m_relative) ts += m_relativeShift; if (iss.read(1)[0].toLatin1() == ' ') n.syllable = iss.readLine(); n.end = tsTime(ts + length); } break; case Note::SLEEP: { unsigned int end; iss >> ts >> end; if (iss.status() != QTextStream::Ok) end = ts; if (m_relative) { ts += m_relativeShift; end += m_relativeShift; m_relativeShift = end; } n.end = tsTime(end); } break; default: throw std::runtime_error("Unknown note type"); } n.begin = tsTime(ts); Notes& notes = vocal.notes; if (m_relative && notes.empty()) m_relativeShift = ts; m_prevts = ts; if (n.begin < m_prevtime) { // Oh no, overlapping notes (b0rked file) // Can't do this because too many songs are b0rked: throw std::runtime_error("Note overlaps with previous note"); if (notes.size() >= 1) { Note& p = notes.back(); // Workaround for songs that use semi-random timestamps for sleep if (p.type == Note::SLEEP) { p.end = p.begin; Notes::reverse_iterator it = notes.rbegin(); Note& p2 = *++it; if (p2.end < n.begin) p.begin = p.end = n.begin; } // Can we just make the previous note shorter? if (p.begin <= n.begin) p.end = n.begin; else { // Nothing to do, warn and skip std::cout << "Skipping overlapping note in " << m_song.path.toStdString() << m_song.filename.toStdString() << std::endl; return true; } } else throw std::runtime_error("The first note has negative timestamp"); } double prevtime = m_prevtime; m_prevtime = n.end; if (n.type != Note::SLEEP && n.end > n.begin) { vocal.noteMin = std::min(vocal.noteMin, n.note); vocal.noteMax = std::max(vocal.noteMax, n.note); } if (n.type == Note::SLEEP) { if (notes.empty()) return true; // Ignore sleeps at song beginning n.begin = n.end = prevtime; // Normalize sleep notes } notes.push_back(n); return true; } composer-master/src/songwriter.hh0000644000175000017500000000277413357046745015665 0ustar niknik#include "song.hh" #include #include struct SongWriter { SongWriter(const Song& s_, const QString& path_) : s(s_), path(path_) { QDir dir; dir.mkpath(path_); } const Song& s; QString path; }; struct SingStarXMLWriter: public SongWriter { SingStarXMLWriter(const Song& s_, const QString& path_) : SongWriter(s_, path_), tempo(s_.bpm > 0 ? s_.bpm : 180), res("Semiquaver") { writeXML(); } private: void writeXML(); int sec2dur(double sec) const; double dur2sec(int ts) const; int tempo; QString res; }; struct UltraStarTXTWriter: public SongWriter { UltraStarTXTWriter(const Song& s_, const QString& path_) : SongWriter(s_, path_), tempo(s_.bpm > 0 ? s_.bpm : 180) { writeTXT(); } private: void writeTXT() const; int sec2dur(double sec) const; int tempo; }; struct FoFMIDIWriter: public SongWriter { FoFMIDIWriter(const Song& s_, const QString& path_) : SongWriter(s_, path_) { writeINI(); writeMIDI(); } private: void writeINI() const; void writeMIDI() const; }; struct LRCWriter: public SongWriter { LRCWriter(const Song& s_, const QString& path_, bool enhanced = false) : SongWriter(s_, path_) { writeLRC(enhanced); } private: void writeLRC(bool enhancedLRC = false) const; QString sec2timestamp(double sec) const; QString EnhancedLRCsec2timestamp(double sec) const; }; struct SMMWriter: public SongWriter { SMMWriter(const Song& s_, const QString& path_) : SongWriter(s_,path_) { writeSMM(); } private: void writeSMM() const; QString sec2timestamp(double sec) const; }; composer-master/src/CMakeLists.txt0000644000175000017500000000302013357046745015662 0ustar niknikcmake_minimum_required(VERSION 2.6) cmake_policy(VERSION 2.6) set(EXENAME ${CMAKE_PROJECT_NAME}) set(CMAKE_AUTOMOC FALSE) if(UNIX) # On UNIX, binary name is lowercase with no spaces string(TOLOWER ${EXENAME} EXENAME) string(REPLACE " " "-" EXENAME ${EXENAME}) endif() # Headers that need MOC need to be defined separately file(GLOB MOC_HEADER_FILES editorapp.hh notelabel.hh notegraphwidget.hh textcodecselector.hh gettingstarted.hh pitchvis.hh synth.hh) file(GLOB SOURCE_FILES "*.cc") file(GLOB HEADER_FILES "*.hh") file(GLOB RESOURCE_FILES "../*.qrc") file(GLOB UI_FILES "../ui/*.ui") # Qt pre-processors QT5_ADD_RESOURCES(RESOURCE_SOURCES ${RESOURCE_FILES}) QT5_WRAP_UI(UI_SOURCES ${UI_FILES} ) QT5_WRAP_CPP(MOC_SOURCES ${MOC_HEADER_FILES}) # Generate config.hh configure_file(config.cmake.hh "${CMAKE_BINARY_DIR}/src/config.hh" @ONLY) include_directories(${CMAKE_BINARY_DIR}/src) include_directories(${CMAKE_SOURCE_DIR}/src) # Final binary add_executable(${EXENAME} ${HEADER_FILES} ${SOURCE_FILES} ${MOC_SOURCES} ${RESOURCE_SOURCES} ${UI_SOURCES}) target_link_libraries(${EXENAME} ${LIBS}) target_link_libraries(${EXENAME} swresample) #FIXME!! should be included in ${LIBS} # We don't currently have any assets, so on Windows, we just install to the root installation folder if(UNIX) install(TARGETS ${EXENAME} DESTINATION bin) install(FILES "../platform/composer.desktop" DESTINATION "share/applications/") install(FILES "../icons/composer.png" DESTINATION "share/pixmaps") else() install(TARGETS ${EXENAME} DESTINATION .) endif() composer-master/src/pitch.hh0000644000175000017500000000760713357046745014571 0ustar niknik#pragma once #include "util.hh" #include #include #include #include #include static inline double level2dB(double level) { return 20.0 * std::log10(level); } static inline double dB2level(double db) { return std::pow(10.0, db / 20.0); } /// A tone is a collection of a base frequency (freq) and all its harmonics struct Tone { static const std::size_t MAXHARM = 16; ///< The maximum number of harmonics tracked double freq; ///< Frequency (Hz) double level; ///< Level (linear) double harmonics[MAXHARM]; ///< Harmonics' levels Tone(); bool operator==(double f) const; ///< Compare for rough frequency match // Linked list of a continuous tone Tone* prev; Tone* next; static bool cmpByLevel(Tone const& a, Tone const& b) { return a.level > b.level; } }; static inline bool operator==(Tone const& lhs, Tone const& rhs) { return lhs == rhs.freq; } static inline bool operator!=(Tone const& lhs, Tone const& rhs) { return !(lhs == rhs); } static inline bool operator<=(Tone const& lhs, Tone const& rhs) { return lhs.freq < rhs.freq || lhs == rhs; } static inline bool operator>=(Tone const& lhs, Tone const& rhs) { return lhs.freq > rhs.freq || lhs == rhs; } static inline bool operator<(Tone const& lhs, Tone const& rhs) { return lhs.freq < rhs.freq && lhs != rhs; } static inline bool operator>(Tone const& lhs, Tone const& rhs) { return lhs.freq > rhs.freq && lhs != rhs; } struct Moment { typedef std::list Tones; Tones m_tones; double m_time; Moment(double t); double time() const { return m_time; } void stealTones(Tones& tones); }; /// A peak contains information about a single frequency struct Peak { double freqFFT; double freq; double level; Peak(): freqFFT(), freq(), level() {} }; /// A combo combines multiple FFT peaks that all display the same frequency into one struct Combo { double freq; double level; Combo(): freq(), level() {} void combine(Peak const& p); bool match(double freqOther) const; static bool cmpByFreq(Combo const& a, Combo const& b) { return a.freq < b.freq; } static bool cmpByLevel(Combo const& a, Combo const& b) { return a.level > b.level; } }; /// analyzer class /** class to analyze input audio and transform it into useable data */ class Analyzer { public: typedef std::vector > Fourier; ///< FFT vector (the first level of detection) typedef std::vector Peaks; ///< Peaks (the second level of detection) typedef std::list Tones; ///< Tones (the final level of detection) typedef std::list Moments; ///< Time-serie history of time and tones /// constructor Analyzer(double rate, std::string id); /** Get the fourier transform. **/ Fourier const& getFourier() const { return m_fft; } /** Get the peak frequencies. **/ Peaks const& getPeaks() const { return m_peaks; } /** Get a list of all tones detected. **/ Moments const& getMoments() const { return m_moments; } /** Find a tone within the singing range; prefers strong tones around 200-400 Hz. **/ //Tone const* findTone(double minfreq = 70.0, double maxfreq = 700.0) const; std::string const& getId() const { return m_id; } /// Process processSize() samples from RndIt input template void process(RndIt input) { std::vector pcm(input, input + processSize()); // Needs local modifyable copy for calculations calcFFT(&pcm[0]); calcTones(); } unsigned processSize() const; ///< The number of samples required by process() unsigned processStep() const; ///< The number of samples to increment the input position after each call to process() double getTime() const { return m_moments.empty() ? 0.0 : m_moments.back().time(); } private: double m_rate; std::string m_id; std::vector m_window; Fourier m_fft; std::vector m_fftLastPhase; Peaks m_peaks; Moments m_moments; mutable double m_oldfreq; void calcFFT(float* pcm); void calcTones(); void temporalMerge(Tones& tones); }; composer-master/src/config.cmake.hh0000644000175000017500000000133313357046745015774 0ustar niknik#pragma once // CMake uses config.cmake.hh to generate config.hh within the build folder. #ifndef EDITOR_CONFIG_HH #define EDITOR_CONFIG_HH #define PACKAGE "@CMAKE_PROJECT_NAME@" #define VERSION "@PROJECT_VERSION@" #cmakedefine STATIC_PLUGINS //#define SHARED_DATA_DIR "@SHARE_INSTALL@" // FFMPEG libraries use changing include file names... Get them from CMake. #define AVCODEC_INCLUDE <@AVCodec_INCLUDE@> #define AVFORMAT_INCLUDE <@AVFormat_INCLUDE@> #define SWRESAMPLE_INCLUDE <@SWResample_INCLUDE@> #define SWSCALE_INCLUDE <@SWScale_INCLUDE@> #define AVUTIL_INCLUDE <@AVUtil_INCLUDE@> #define AVUTIL_OPT_INCLUDE //HACK to get AVOption class! #define AVUTIL_MATH_INCLUDE #endif composer-master/src/notes.cc0000644000175000017500000000521213357046745014566 0ustar niknik#include "notes.hh" #include "util.hh" #include #include #include #include QString MusicalScale::getNoteStr(double freq) const { int id = getNoteId(freq); if (id == -1) return QString(); static const char * note[12] = {"C ","C#","D ","D#","E ","F ","F#","G ","G#","A ","A#","B "}; QString buf; QTextStream ts(&buf); // Acoustical Society of America Octave Designation System //int octave = 2 + id / 12; ts << note[id%12] << " " << int(round(freq)) << " Hz"; return ts.readAll(); } unsigned int MusicalScale::getNoteNum(int id) const { // C major scale int n = id % 12; return (n + (n > 4)) / 2; } bool MusicalScale::isSharp(int id) const { id %= 12; if (id < 0) id += 12; // Fix the modulus of a negative value // C major scale switch (id) { case 1: case 3: case 6: case 8: case 10: return true; } return false; } double MusicalScale::getNoteFreq(int id) const { if (id == -1) return 0.0; return m_baseFreq * std::pow(2.0, (id - m_baseId) / 12.0); } int MusicalScale::getNoteId(double freq) const { double note = getNote(freq); if (note >= 0.0 && note < 100.0) return int(note + 0.5); return -1; } double MusicalScale::getNote(double freq) const { if (freq < 1.0) return getNaN(); return m_baseId + 12.0 * std::log(freq / m_baseFreq) / std::log(2.0); } double MusicalScale::getNoteOffset(double freq) const { double frac = freq / getNoteFreq(getNoteId(freq)); return 12.0 * std::log(frac) / std::log(2.0); } Duration::Duration(): begin(getNaN()), end(getNaN()) {} const Note::Type Note::types[] = { NORMAL, GOLDEN, FREESTYLE, SLIDE, SLEEP, TAP, HOLDBEGIN, HOLDEND, ROLL, MINE, LIFT }; Note::Note(QString lyric): syllable(lyric), begin(), end(), phase(getNaN()), type(NORMAL), note(), notePrev(), lineBreak() {} double Note::diff(double note, double n) { return remainder(n - note, 12.0); } int Note::getTypeInt() const { switch (type) { case NORMAL: return 0; case GOLDEN: return 1; case FREESTYLE: return 2; case SLIDE: return 3; case SLEEP: return 4; default: return 255; } } QString Note::typeString() const { switch (type) { case NORMAL: return QT_TR_NOOP("Normal"); case GOLDEN: return QT_TR_NOOP("Bonus"); case FREESTYLE: return QT_TR_NOOP("Freestyle"); case SLIDE: return QT_TR_NOOP("Slide"); case SLEEP: return QT_TR_NOOP("Sleep"); default: return QT_TR_NOOP("Unknown"); } } VocalTrack::VocalTrack(QString name) : name(name) {reload();} void VocalTrack::reload() { notes.clear(); m_scoreFactor = 0.0; noteMin = std::numeric_limits::max(); noteMax = std::numeric_limits::min(); beginTime = endTime = getNaN(); } composer-master/src/pitchvis.cc0000644000175000017500000001620313357046745015271 0ustar niknik #include "notegraphwidget.hh" #include "pitchvis.hh" #include "pitch.hh" #include "ffmpeg.hh" #include #include #include #include #include #include #include PitchVis::PitchVis(QString const& filename, QWidget *parent, int visId) : QThread(parent), mutex(), fileName(filename), duration(), moreAvailable(), quit(), cancelled(), restart(), m_x1(), m_y1(), m_x2(), m_y2(), m_visId(visId), condition() { start(); // Launch the thread } void PitchVis::stop() { QMutexLocker locker(&mutex); quit = true; condition.wakeOne(); } void PitchVis::cancel() { QMutexLocker locker(&mutex); cancelled = true; } void PitchVis::run() { bool analyzingSuccess = false; try { // Initialize FFmpeg decoding std::string file(fileName.toLocal8Bit().data(), fileName.toLocal8Bit().size()); FFmpeg mpeg(file); { QMutexLocker locker(&mutex); paths.clear(); position = 0.0; duration = mpeg.duration(); // Estimation } unsigned rate = mpeg.audioQueue.getRate(); unsigned channels = mpeg.audioQueue.getChannels(); if (channels == 0) throw std::runtime_error("No audio channels found"); std::vector analyzers(channels, Analyzer(rate, "")); // Process the entire song std::vector data; data.reserve((duration + 1.0) * rate * channels); unsigned x = 0; while (mpeg.audioQueue.output(data)) { // Process as much as can be processed at this point while (data.size() / channels - x >= analyzers[0].processSize()) { // Pitch detection for (unsigned ch = 0; ch < channels; ++ch) { analyzers[ch].process(da::step_iterator(&data[x * channels + ch], channels)); } x += analyzers[0].processStep(); // Update progress and check for quit flag QMutexLocker locker(&mutex); if (quit) return; else if (cancelled) break; double t = analyzers[0].getTime(); position = t; duration = std::max(duration, t + 0.01); } } // DEBUG: std::ofstream("audio.raw", std::ios::binary).write(reinterpret_cast(&data[0]), data.size() * sizeof(float)); // Filter the analyzer output data into QPainterPaths. std::vector mit(channels), mend(channels); for (unsigned ch = 0; ch < channels; ++ch) { Analyzer::Moments const& moments = analyzers[ch].getMoments(); mit[ch] = moments.begin(); mend[ch] = moments.end(); } while (mit[0] != mend[0]) { for (unsigned ch = 0; ch < channels; ++mit[ch++]) { Moment::Tones const& tones = mit[ch]->m_tones; // Take tones then move forward the iterator for (Moment::Tones::const_iterator it2 = tones.begin(), it2end = tones.end(); it2 != it2end; ++it2) { if (it2->prev) continue; // The tone doesn't begin at this moment, skip // Copy the linked list into vector for easier access and calculate max level std::vector tones; for (Tone const* n = &*it2; n; n = n->next) { tones.push_back(n); } if (tones.size() < 3) continue; // Too short tone, ignored PitchPath path(ch); double score = 0.0; Analyzer::Moments::const_iterator momit = mit[ch]; // Store path used for rendering for (unsigned i = 0; i < tones.size(); ++i, ++momit) { float t = momit->m_time; float n = scale.getNote(tones[i]->freq); float level = level2dB(tones[i]->level); score += tones[i]->level; path.fragments.push_back(PitchFragment(t, n, level)); } QMutexLocker locker(&mutex); if (score > 1.0) paths.push_back(path); } } } analyzingSuccess = true; } catch (std::exception& e) { std::cerr << std::string("Error loading audio: ") + e.what() + '\n' << std::flush; } { QMutexLocker locker(&mutex); moreAvailable = true; position = duration; } // Start the renderer loop if (analyzingSuccess) renderer(); } void PitchVis::paint(int x1, int y1, int x2, int y2) { QMutexLocker locker(&mutex); m_x1 = x1; m_y1 = y1; m_x2 = x2; m_y2 = y2; // Wake the thread restart = true; condition.wakeOne(); } void PitchVis::renderer() { forever { int x1, x2, y1, y2; { QMutexLocker locker(&mutex); if (quit) return; x1 = m_x1, x2 = m_x2, y1 = m_y1, y2 = m_y2; } // Rendering // QImage allows drawing in non-main/non-GUI thread QImage image(x2-x1, y2-y1, QImage::Format_ARGB32_Premultiplied); NoteGraphWidget *widget = qobject_cast(parent()); if (!widget) continue; QSettings settings; // Default QSettings parameters given in main() bool aa = settings.value("anti-aliasing", true).toBool(); { QPainter painter(&image); painter.setCompositionMode(QPainter::CompositionMode_Source); if (aa) painter.setRenderHint(QPainter::Antialiasing); // Fill the background, otherwise the image will have all kinds of carbage painter.fillRect(image.rect(), QColor(0,0,0,0)); QPen pen; pen.setWidth(8); pen.setCapStyle(Qt::RoundCap); PitchVis::Paths const& paths = getPaths(); for (PitchVis::Paths::const_iterator it = paths.begin(), itend = paths.end(); it != itend; ++it) { PitchPath::Fragments const& fragments = it->fragments; int oldx, oldy; // Only render paths in view if (widget->s2px(fragments.back().time) < x1) continue; else if (widget->s2px(fragments.front().time) > x2) break; // Iterate through the path points for (PitchPath::Fragments::const_iterator it2 = fragments.begin(), it2end = fragments.end(); it2 != it2end; ++it2) { // TODO: Take y-size into account (change also the paint calls in NoteGraphWidget) int x = widget->s2px(it2->time) - x1; int y = widget->n2px(it2->note); if (m_visId == 0) pen.setColor(QColor(32 + 64 * it->channel, clamp(127 + it2->level, 32, 255), 32, 128)); else pen.setColor(QColor(clamp(127 + it2->level, 32, 255), 32, 32 + 32 * it->channel, 100)); painter.setPen(pen); if (it2 != fragments.begin()) painter.drawLine(oldx, oldy, x, y); oldx = x; oldy = y; } } } // Send the image // This is actually delivered by the reciever's event loop thread, and not called directly from here emit renderedImage(image, QPoint(x1, y1), m_visId); mutex.lock(); // If nothing to do, sleep here if (!restart) condition.wait(&mutex); restart = false; mutex.unlock(); } } int PitchVis::guessNote(double begin, double end, int note) { const unsigned scoreSz = 48; double score[scoreSz] = {}; if (note >= 0 || note < 48) score[note] = 10.0; // Slightly prefer the current note // Score against paths for (PitchVis::Paths::const_iterator it = paths.begin(), itend = paths.end(); it != itend; ++it) { PitchPath::Fragments const& fragments = it->fragments; // Discard paths completely outside the window if (fragments.back().time < begin) continue; if (fragments.front().time > end) break; for (PitchPath::Fragments::const_iterator it2 = fragments.begin(), it2end = fragments.end(); it2 != it2end; ++it2) { // Discard path points outside the window if (it2->time < begin) continue; if (it2->time > end) break; unsigned n = round(it2->note); if (n < scoreSz) score[n] += 100 + it2->level; } } // Return the idx with best score return std::max_element(score + 1, score + scoreSz) - score; } composer-master/src/libda/0000755000175000017500000000000013357046745014202 5ustar niknikcomposer-master/src/libda/portaudio.hpp0000644000175000017500000001054613357046745016727 0ustar niknik#pragma once /** * @file portaudio.hpp OOP / RAII wrappers & utilities for PortAudio library. */ #include #include #include #include #include #include "../unicode.hh" #define PORTAUDIO_CHECKED(func, args) portaudio::internal::check(func args, #func) namespace portaudio { class Error: public std::runtime_error { public: Error(PaError code_, char const* func_): runtime_error(std::string(func_) + " failed: " + Pa_GetErrorText(code_)), m_code(code_), m_func(func_) {} PaError code() const { return m_code; } std::string func() const { return m_func; } private: PaError m_code; char const* m_func; }; namespace internal { void inline check(PaError code, char const* func) { if (code != paNoError) throw Error(code, func); } } struct DeviceInfo { DeviceInfo(std::string n = "", int i = 0, int o = 0): name(n), in(i), out(o) {} std::string desc() { std::ostringstream oss; oss << name << " ("; if (in) oss << in << " in"; if (in && out) oss << ", "; if (out) oss << out << " out"; return oss.str() + ")"; } std::string name; int in, out; }; typedef std::vector DeviceInfos; struct AudioDevices { static int count() { return Pa_GetDeviceCount(); } /// Constructor gets the PA devices into a vector AudioDevices() { for (unsigned i = 0, end = Pa_GetDeviceCount(); i != end; ++i) { PaDeviceInfo const* info = Pa_GetDeviceInfo(i); if (!info) devices.push_back(DeviceInfo()); else devices.push_back(DeviceInfo(convertToUTF8(info->name), info->maxInputChannels, info->maxOutputChannels)); } } /// Get a printable dump of the devices std::string dump() { std::ostringstream oss; oss << "PortAudio devices:" << std::endl; for (unsigned i = 0; i < devices.size(); ++i) oss << " " << i << " " << devices[i].name << " (" << devices[i].in << " in, " << devices[i].out << " out)" << std::endl; oss << std::endl; return oss.str(); } DeviceInfos devices; }; struct Init { Init() { PORTAUDIO_CHECKED(Pa_Initialize, ()); } ~Init() { Pa_Terminate(); } }; struct Params { PaStreamParameters params; Params(PaStreamParameters const& init = PaStreamParameters()): params(init) { // Some useful defaults so that things just work channelCount(2).sampleFormat(paFloat32).suggestedLatency(0.05); } Params& channelCount(int val) { params.channelCount = val; return *this; } Params& device(PaDeviceIndex val) { params.device = val; return *this; } Params& sampleFormat(PaSampleFormat val) { params.sampleFormat = val; return *this; } Params& suggestedLatency(PaTime val) { params.suggestedLatency = val; return *this; } Params& hostApiSpecificStreamInfo(void* val) { params.hostApiSpecificStreamInfo = val; return *this; } operator PaStreamParameters const*() const { return ¶ms; } }; template int functorCallback(void const* input, void* output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData) { return (*static_cast(userData))(input, output, frameCount, timeInfo, statusFlags); } class Stream { PaStream* m_handle; public: Stream( PaStreamParameters const* input, PaStreamParameters const* output, double sampleRate, unsigned long framesPerBuffer = paFramesPerBufferUnspecified, PaStreamFlags flags = paNoFlag, PaStreamCallback* callback = NULL, void* userData = NULL) { PORTAUDIO_CHECKED(Pa_OpenStream, (&m_handle, input, output, sampleRate, framesPerBuffer, flags, callback, userData)); } template Stream( Functor& functor, PaStreamParameters const* input, PaStreamParameters const* output, double sampleRate, unsigned long framesPerBuffer = paFramesPerBufferUnspecified, PaStreamFlags flags = paNoFlag) { PORTAUDIO_CHECKED(Pa_OpenStream, (&m_handle, input, output, sampleRate, framesPerBuffer, flags, functorCallback, &functor)); } ~Stream() { // Give audio a little time to shutdown but then just quit boost::thread audiokiller(Pa_CloseStream, m_handle); if (!audiokiller.timed_join(boost::posix_time::milliseconds(5000))) { std::cout << "PortAudio BUG: Pa_CloseStream hung for more than five seconds. Exiting program." << std::endl; exit(1); } } operator PaStream*() { return m_handle; } }; } composer-master/src/libda/fft.hpp0000644000175000017500000000612613357046745015477 0ustar niknik#pragma once /** * @file fft.hpp FFT and related facilities. */ #include #include #include #ifndef M_PI #define M_PI 3.141592653589793 #endif namespace da { namespace math { /** Calculate the square of val. **/ static inline double sqr(double val) { return val * val; } template struct SinCosSeries { static double value() { return 1 - sqr(A * M_PI / B) / M / (M+1) * SinCosSeries::value(); } }; template struct SinCosSeries { static double value() { return 1.0; } }; template struct Sin { static double value() { return (A * M_PI / B) * SinCosSeries<2, 34, B, A>::value(); } }; template struct Cos { static double value() { return SinCosSeries<1, 33, B, A>::value(); } }; /** Calculate sin(2 pi A / B). **/ template double sin() { return Sin::value(); } /** Calculate cos(2 pi A / B). **/ template double cos() { return Cos::value(); } } namespace fourier { // Based on the description of Volodymyr Myrnyy in // http://www.dspdesignline.com/showArticle.jhtml?printableArticle=true&articleId=199903272 template struct DanielsonLanczos { static void apply(std::complex* data) { const std::size_t N = 1 << P; const std::size_t M = N / 2; // Compute even and odd halves DanielsonLanczos

().apply(data); DanielsonLanczos

().apply(data + M); // Combine the results using math::sqr; using math::sin; const std::complex wp(-2.0 * sqr(sin<1, N>()), -sin<2, N>()); std::complex w(1.0); for (std::size_t i = 0; i < M; ++i) { std::complex temp = data[i + M] * w; data[M + i] = data[i] - temp; data[i] += temp; w += w * wp; } } }; template struct DanielsonLanczos<0, T> { static void apply(std::complex*) {} }; } /** Perform FFT on data. **/ template void fft(std::complex* data) { // Perform bit-reversal sorting of sample data. const std::size_t N = 1 << P; std::size_t j = 0; for (std::size_t i = 0; i < N; ++i) { if (i < j) std::swap(data[i], data[j]); std::size_t m = N / 2; while (m > 1 && m <= j) { j -= m; m >>= 1; } j += m; } // Do the actual calculation fourier::DanielsonLanczos::apply(data); } /** Perform FFT on data from floating point iterator, windowing the input. **/ template std::vector > fft(InIt begin, Window window) { std::vector > data(1 << P); // Perform bit-reversal sorting of sample data. const std::size_t N = 1 << P; std::size_t j = 0; for (std::size_t i = 0; i < N; ++i) { data[j] = *begin++ * window[i]; std::size_t m = N / 2; while (m > 1 && m <= j) { j -= m; m >>= 1; } j += m; } // Do the actual calculation fourier::DanielsonLanczos::apply(&data[0]); return data; } } composer-master/src/libda/sample.hpp0000644000175000017500000000652313357046745016202 0ustar niknik#pragma once /** * @file sample.hpp Sample format definition and format conversions. */ namespace da { // Implement mathematical rounding (which C++ unfortunately currently lacks) template T round(T val) { return static_cast(static_cast(val + (val >= 0 ? 0.5 : -0.5))); } // WARNING: changing this breaks binary compatibility on the library! typedef float sample_t; // A helper function for clamping a value to a certain range template T clamp(T val, T min, T max) { if (val < min) val = min; if (val > max) val = max; return val; } const sample_t max_s16 = 32767.0f, min_s16 = -max_s16 - 1.0f; const sample_t max_s24 = 8388607.0f, min_s24 = -max_s24 - 1.0f; const sample_t max_s32 = 2147483647.0f, min_s32= -max_s32 - 1.0f; // The following conversions provide lossless conversions between floats // and integers. Be sure to use only these conversions or otherwise the // conversions may not be lossless, due to different scaling factors being // used by different libraries. // The negative minimum integer value produces sample_t value slightly // more negative than -1.0 but this is necessary in order to prevent // clipping in the float-to-int conversions. Now amplitude 1.0 in floating // point produces -32767 .. 32767 symmetrical non-clipping range in s16. static inline sample_t conv_from_s16(int s) { return s / max_s16; } static inline sample_t conv_from_s24(int s) { return s / max_s24; } static inline sample_t conv_from_s32(int s) { return s / max_s32; } // The rounding is strictly not necessary, but it greatly improves // the error tolerance if any floating point calculations are done. // The ugly static_casts are required to avoid warnings in MSVC. static inline int conv_to_s16(sample_t s) { return clamp(static_cast(round(s * max_s16)), static_cast(min_s16), static_cast(max_s16)); } static inline int conv_to_s24(sample_t s) { return clamp(static_cast(round(s * max_s24)), static_cast(min_s24), static_cast(max_s24)); } static inline int conv_to_s32(sample_t s) { return static_cast(clamp(round(s * max_s32), min_s32, max_s32 )); } // Non-rounding non-clamping versions are provided for very low end devices (still lossless) static inline int conv_to_s16_fast(sample_t s) { return static_cast(s * max_s16); } static inline int conv_to_s24_fast(sample_t s) { return static_cast(s * max_s24); } static inline int conv_to_s32_fast(sample_t s) { return static_cast(s * max_s32); } template class step_iterator: public std::iterator { ValueType* m_pos; std::ptrdiff_t m_step; public: step_iterator(ValueType* pos, std::ptrdiff_t step): m_pos(pos), m_step(step) {} ValueType& operator*() { return *m_pos; } step_iterator operator+(std::ptrdiff_t rhs) { return step_iterator(m_pos + m_step * rhs, m_step); } step_iterator& operator++() { m_pos += m_step; return *this; } step_iterator operator++(int) { step_iterator ret = *this; ++*this; return ret; } bool operator!=(step_iterator const& rhs) const { return m_pos != rhs.m_pos; } std::ptrdiff_t operator-(step_iterator const& rhs) const { return (m_pos - rhs.m_pos) / m_step; } // TODO: more operators }; typedef step_iterator sample_iterator; typedef step_iterator sample_const_iterator; } composer-master/src/songwriter-xml.cc0000644000175000017500000001070513357046745016442 0ustar niknik#include "songwriter.hh" #include "util.hh" #include #include #include void SingStarXMLWriter::writeXML() { if (tempo > 300) { tempo /= 2; res = "Demisemiquaver"; // Demisemiquaver = 2x tempo of Semiquaver } QDomDocument doc; QDomProcessingInstruction xmlheader = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.appendChild(xmlheader); QDomElement root = doc.createElement("MELODY"); root.setAttribute("xmlns", "http://www.singstargame.com"); root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); root.setAttribute("Version", "1"); root.setAttribute("Tempo", QString::number(tempo)); root.setAttribute("FixedTempo", "Yes"); root.setAttribute("Resolution", res); root.setAttribute("Genre", s.genre); root.setAttribute("Year", s.year); root.setAttribute("xsi:schemaLocation", "http://www.singstargame.com http://15GMS-SINGSQL/xml_schema/melody.xsd"); root.setAttribute("m2xVersion", "060110"); //? root.setAttribute("audioVersion", "2"); //? doc.appendChild(root); // Some helpful comments QDomComment artistComment = doc.createComment(QString("Artist: ") + s.artist); QDomComment titleComment = doc.createComment(QString("Title: ") + s.title); root.appendChild(artistComment); root.appendChild(titleComment); // Track element int tracknum = 1; QDomElement trackElem = doc.createElement("TRACK"); trackElem.setAttribute("Name", "Player1"); trackElem.setAttribute("Artist", s.artist); root.appendChild(trackElem); // Create first sentence int sentencenum = 1; QDomElement sentenceElem = doc.createElement("SENTENCE"); // FIXME: Should there be Singer and Part attributes? QDomComment sentenceComment = doc.createComment(QString("Track %1, Sentence %2").arg(tracknum).arg(sentencenum)); sentenceElem.appendChild(sentenceComment); // First sentence also needs a starting SLEEP Notes const& notes = s.getVocalTrack().notes; int ts = sec2dur(notes.front().begin); QDomElement firstNoteElem = doc.createElement("NOTE"); firstNoteElem.setAttribute("MidiNote", "0"); firstNoteElem.setAttribute("Duration", QString::number(ts)); firstNoteElem.setAttribute("Lyric", ""); sentenceElem.appendChild(firstNoteElem); bool firstNote = true; // Iterate all notes for (unsigned int i = 0; i < notes.size(); ++i) { Note const& n = notes[i]; if (n.type == Note::SLEEP) continue; // Skip SLEEPs // New sentence if (n.lineBreak && !firstNote) { root.appendChild(sentenceElem); ++sentencenum; sentenceElem = doc.createElement("SENTENCE"); sentenceComment = doc.createComment(QString("Track %1, Sentence %2").arg(tracknum).arg(sentencenum)); sentenceElem.appendChild(sentenceComment); } firstNote = false; // Construct a regular note element int l = sec2dur(n.length()); ts += l; QDomElement noteElem = doc.createElement("NOTE"); noteElem.setAttribute("MidiNote", QString::number(n.note)); noteElem.setAttribute("Duration", QString::number(l)); noteElem.setAttribute("Lyric", n.syllable); if (n.type == Note::GOLDEN) noteElem.setAttribute("Bonus", "Yes"); if (n.type == Note::FREESTYLE) noteElem.setAttribute("FreeStyle", "Yes"); sentenceElem.appendChild(noteElem); // Construct a note element, indicationg the pause before next note // This is only done if the pause has duration // We also take the overall position into consideration (counter rounding errors) int pauseLen = 0; double end = dur2sec(ts); for (int j = i + 1; j < notes.size(); ++j) { // Find the next non-SLEEP note if (notes[j].type != Note::SLEEP) { pauseLen = sec2dur(notes[j].begin - end); // Difference to next note break; } } if (pauseLen > 0) { QDomElement pauseElem = doc.createElement("NOTE"); pauseElem.setAttribute("MidiNote", "0"); pauseElem.setAttribute("Duration", QString::number(pauseLen)); pauseElem.setAttribute("Lyric", ""); sentenceElem.appendChild(pauseElem); ts += pauseLen; } } root.appendChild(sentenceElem); // Get the xml data QString xml = doc.toString(4); // Write to file QFile f(path + "/notes.xml"); if (f.open(QFile::WriteOnly)) { QTextStream out(&f); out.setCodec("UTF-8"); out << xml; } else throw std::runtime_error("Couldn't open target file notes.xml"); } int SingStarXMLWriter::sec2dur(double sec) const { return round(tempo / 60.0 * sec * (res == "Demisemiquaver" ? 8 : 4)); } double SingStarXMLWriter::dur2sec(int ts) const { return ts * 60.0 / (tempo * (res == "Demisemiquaver" ? 8 : 4)); } composer-master/src/busydialog.hh0000644000175000017500000000167313357046745015621 0ustar niknik#pragma once #include #include #include #include #include #include class BusyDialog: public QDialog { public: BusyDialog(QWidget *parent = NULL, int eventsInterval = 10): QDialog(parent), timer(), interval(eventsInterval), count() { QProgressBar *progress = new QProgressBar(this); progress->setRange(0,0); setWindowTitle(tr("Working...")); QVBoxLayout *vb = new QVBoxLayout(this); vb->addWidget(progress); setLayout(vb); timer.start(); } void operator()() { // Only show the dialog after certainamount of time if (isHidden() && timer.elapsed() > 3000) open(); if (isVisible()) { if (count == 0) // Let's not process events all the time QApplication::processEvents(); count = (count + 1) % interval; } } protected: void closeEvent(QCloseEvent* event) { event->ignore(); } private: QElapsedTimer timer; int interval; int count; }; composer-master/src/songparser.hh0000644000175000017500000000577313357046745015647 0ustar niknik#pragma once #include "song.hh" #include namespace SongParserUtil { /// Parse a boolean from string and assign it to a variable void assign(bool& var, QString const& str); } /// parses songfiles class SongParser { public: /// constructor SongParser(Song& s); static bool looksLikeSongFile(QString const& data) { return txtCheck(data) || xmlCheck(data) || iniCheck(data) || smCheck(data) || lrcCheck(data); } private: void finalize(); Song& m_song; QTextStream m_stream; unsigned int m_linenum; bool getline(QString& line); bool m_relative; double m_gap; // UltraStar TXT static bool txtCheck(QString const& data); void txtParse(); bool txtParseField(QString const& line); bool txtParseNote(QString line, VocalTrack &vocal); // SingStar XML static bool xmlCheck(QString const& data); void xmlParse(); // Frets on Fire MIDI static bool iniCheck(QString const& data); static bool midiCheck(QString const& data); void iniParse(); void iniParseField(QString const& line); void midParse(); // LRC / Soramimi static bool lrcCheck(QString const& data); void lrcParse(); bool lrcNoteParse(QString line, VocalTrack &vocal); double convertLRCTimestampToDouble(QString timeStamp); // FIXME: Dummy funcs static bool smCheck(QString const& data) { (void)data; return false; } void smParse() { } bool smParseField(std::string line) { (void)line; return false; } Notes smParseNotes(std::string line) { (void)line; return Notes(); } double m_prevtime; unsigned int m_prevts; unsigned int m_relativeShift; double m_maxScore; struct BPM { BPM(double _begin, double _ts, double _bpm, double division): begin(_begin), step(60.0 / _bpm / division), ts(_ts) {} double begin; // Time in seconds double step; // Seconds per quarter note double ts; }; typedef std::vector bpms_t; bpms_t m_bpms; unsigned m_tsPerBeat; ///< The ts increment per beat unsigned m_tsEnd; ///< The ending ts of the song void addBPM(double ts, double bpm, double division = 4.0) { if (!(bpm >= 1.0 && bpm < 1e12)) throw std::runtime_error("Invalid BPM value"); if (!m_bpms.empty()) { double diff = ts - m_bpms.back().ts; if (diff == 0.0) m_bpms.pop_back(); // Avoid zero-length BPM definitions else if (!(diff > 0.0)) throw std::runtime_error("Invalid BPM timestamp"); } m_bpms.push_back(BPM(tsTime(ts), ts, bpm, division)); } /// Convert a timestamp (beats) into time (seconds) double tsTime(double ts) const { if (m_bpms.empty()) { if (ts != 0) throw std::runtime_error("BPM data missing"); return m_gap; } for (std::vector::const_reverse_iterator it = m_bpms.rbegin(); it != m_bpms.rend(); ++it) { if (it->ts <= ts) return it->begin + (ts - it->ts) * it->step; } throw std::logic_error("INTERNAL ERROR: BPM data invalid"); } /// Stops stored in format Song::Stops m_stops; /// Convert a stop into (as stored in the song) std::pair stopConvert(std::pair s) { s.first = tsTime(s.first); return s; } }; composer-master/src/textcodecselector.hh0000644000175000017500000000642413357046745017201 0ustar niknik/**************************************************************************** ** ** Copyright (C) 2000-2008 TROLLTECH ASA. All rights reserved. ** ** This file is part of the Opensource Edition of the Qtopia Toolkit. ** ** This software is licensed under the terms of the GNU General Public ** License (GPL) version 2. ** ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** Code has been modified from the original. ****************************************************************************/ #include #include #include #include #include #include #include #include #include class TextCodecSelector : public QDialog { Q_OBJECT public: TextCodecSelector(QWidget* parent = 0) : QDialog(parent) { setWindowTitle(tr("Unknown encoding")); list = new QListWidget(this); connect(list, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(accept())); connect(list, SIGNAL(itemPressed(QListWidgetItem*)), this, SLOT(accept())); codecs = QTextCodec::availableCodecs(); qSort(codecs); list->addItem(tr("Automatic")); foreach (QByteArray n, codecs) { list->addItem(n); } QLabel *label = new QLabel(tr("Choose the encoding for this file:")); label->setWordWrap(true); QVBoxLayout *vb = new QVBoxLayout(this); vb->addWidget(label); vb->addWidget(list); setLayout(vb); } QTextCodec *selection(QByteArray ba) const { int n = list->currentRow(); if (n < 0) { return 0; } else if (n == 0) { // Automatic... just try them all and pick the one that can convert // in and out without losing anything with the smallest intermediate length. QTextCodec *best = 0; int shortest = INT_MAX; foreach (QByteArray name, codecs) { QTextCodec* c = QTextCodec::codecForName(name); QString enc = c->toUnicode(ba); if (c->fromUnicode(enc) == ba) { std::cout << QString(c->name()).toStdString() << std::endl; if (enc.length() < shortest) { best = c; shortest = enc.length(); } } } return best; } else { return QTextCodec::codecForName(codecs.at(n-1)); } } static QTextCodec* codecForContent(QByteArray ba, QWidget *parent = 0) { TextCodecSelector tcs(parent); tcs.setModal(true); tcs.exec(); if (tcs.result()) return tcs.selection(ba); return 0; } static QString readAllAndHandleEncoding(QFile &file, QWidget *parent = 0) { QString data = ""; QByteArray ba = file.readAll(); if (!ba.isEmpty()) { data = QString::fromUtf8(ba, ba.size()); if (data.toUtf8().size() != ba.size()) { // Not UTF-8 :( data = QString::fromLatin1(ba, ba.size()); if (data.toLatin1().size() != ba.size()) { // Not Latin1 :( data = QString::fromLocal8Bit(ba, ba.size()); if (data.toLocal8Bit().size() != ba.size()) { // Not Local 8-bit :( QTextCodec* codec = codecForContent(ba, parent); if (codec) data = codec->toUnicode(ba); } } } } return data; } private: QListWidget *list; QList codecs; }; composer-master/src/main.cc0000644000175000017500000000362113357046745014364 0ustar niknik#include #include #include #include "config.hh" #include "editorapp.hh" #ifdef STATIC_PLUGINS #include Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) #endif int main(int argc, char *argv[]) { Q_INIT_RESOURCE(editor); QApplication app(argc, argv); // These values are used by e.g. Phonon and QSettings app.setApplicationName(PACKAGE); app.setApplicationVersion(VERSION); app.setOrganizationName("Performous Team"); app.setOrganizationDomain("performous.org"); // Command line parsing // Unfortunately Qt doesn't include proper interface for this, // even though it does it internally (and handles some args). // This here is not very elegant, but didn't want to introduce // additional dependency for a couple of very simple options. QStringList args = QApplication::arguments(); QString openpath = ""; for (int i = 1; i < args.size(); ++i) { // FIXME: On Windows arg0 might or might not be the program name if (args[i] == "--version" || args[i] == "-v") { std::cout << VERSION << std::endl; exit(EXIT_SUCCESS); } else if (args[i] == "--help" || args[i] == "-h") { std::cout << PACKAGE << " " << VERSION << std::endl << std::endl << "-h [ --help ] you are viewing it" << std::endl << "-v [ --version ] display version number" << std::endl << "argument without a switch is interpreted as a song file to open" << std::endl ; exit(EXIT_SUCCESS); } else if (!args[i].startsWith("-")) openpath = args[i]; // No switch else { std::cout << "Unknown option: " << args[i].toStdString() << std::endl; exit(EXIT_FAILURE); } } // Localization QString locale = QLocale::system().name(); QTranslator translator; translator.load(locale); app.installTranslator(&translator); EditorApp window; window.show(); if (!openpath.isEmpty()) window.openFile(openpath); // Load song if given in command line return app.exec(); } composer-master/src/ffmpeg.hh0000644000175000017500000000640513357046745014721 0ustar niknik#pragma once #include "util.hh" #include "libda/sample.hpp" #include #include #include #include #include #include #include class AudioQueue { public: void reset() { QMutexLocker lock(&m_mutex); m_size = 0; m_needData.wakeOne(); m_needSpace.wakeOne(); } template void input(Iterator begin, Iterator end, double scale) { QMutexLocker lock(&m_mutex); unsigned count = end - begin; unsigned capacity = m_ring.size(); if (capacity < count) throw std::logic_error("AudioQueue input chunk is bigger than capacity"); while (capacity - m_size < count) m_needSpace.wait(&m_mutex); for (unsigned i = 0; i < count; ++i) { m_ring[m_position + m_size++] = *begin++ * scale; } m_needData.wakeOne(); } void setEof(bool eof = true) { QMutexLocker lock(&m_mutex); m_eof = eof; m_needData.wakeOne(); } bool output(std::vector& out) { QMutexLocker lock(&m_mutex); while (m_size == 0) { if (m_eof) return false; m_needData.wait(&m_mutex); } std::size_t outsz = out.size(); out.resize(outsz + m_size); da::sample_t* outptr = &out[outsz]; Ring::iterator b = m_ring.begin() + m_position, e = m_ring.begin() + (m_position + m_size) % m_ring.size(); if (e <= b) { // Copy the first part std::copy(b, m_ring.end(), outptr); outptr += m_ring.end() - b; b = m_ring.begin(); } // Copy the only/last part std::copy(b, e, outptr); m_size = 0; m_needSpace.wakeOne(); return true; } unsigned samplesPerSecond() const { return m_channels * m_rate; } void setRateChannels(unsigned rate, unsigned channels) { m_rate = rate; m_channels = channels; } unsigned getRate() { return m_rate; } unsigned getChannels() { return m_channels; } AudioQueue(unsigned capacity = 32768): m_ring(capacity), m_channels(), m_position(), m_size(), m_eof() {} private: QMutex m_mutex; QWaitCondition m_needData, m_needSpace; typedef std::vector Ring; Ring m_ring; unsigned m_rate; unsigned m_channels; unsigned m_position; unsigned m_size; bool m_eof; }; // ffmpeg forward declarations // ffmpeg forward declarations extern "C" { struct AVCodec; struct AVCodecContext; struct AVFormatContext; struct AVFrame; struct SwrContext; struct SwsContext; } /// ffmpeg class class FFmpeg: public QThread { public: /// constructor FFmpeg(std::string const& file); ~FFmpeg(); /// Thread runs here, don't call directly void run(); /// Queue for audio AudioQueue audioQueue; /** Seek to the chosen time. Will block until the seek is done, if wait is true. **/ void seek(double time, bool wait = true); /// Duration double duration() const; bool terminating() const { return m_quit; } private: class eof_error: public std::exception {}; void seek_internal(); void open(); void decodeNextFrame(); std::string m_filename; unsigned int m_rate; volatile bool m_quit; volatile bool m_running; volatile bool m_eof; volatile double m_seekTarget; AVFormatContext* pFormatCtx; SwrContext* m_resampleContext; AVCodecContext* pAudioCodecCtx; AVCodec* pAudioCodec; int audioStream; double m_position; static QMutex s_avcodec_mutex; // Used for avcodec_open/close (which use some static crap and are thus not thread-safe) }; composer-master/src/synth.cc0000644000175000017500000001350113357046745014603 0ustar niknik#include #include #include #include #include #include #include "synth.hh" #ifndef M_PI #define M_PI 3.141592653589793 #endif void Synth::tick(qint64 pos, qreal playbackRate, const SynthNotes& notes) { QMutexLocker locker(&m_mutex); m_pos = pos / 1000.0; m_rate = playbackRate; m_notes = notes; if (m_notes.isEmpty()) m_quit = true; if (isRunning()) m_condition.wakeOne(); else start(); } void Synth::stop() { QMutexLocker locker(&m_mutex); m_quit = true; m_condition.wakeOne(); } void Synth::createBuffer(QByteArray &buffer, int note, double length) { // This is simple beep, so we use mono and lowish sample rate // --> quick to create and small memory footprint // Going to 8 bits seems to create weird samples on Windows though //std::string header = writeWavHeader(16, 1, SampleRate, length * SampleRate); //buffer = QByteArray(header.c_str(), header.size()); const int bytesPerSample = 2; const quint64 samples = length * SampleRate; buffer.clear(); buffer.reserve(samples * bytesPerSample); double d = (note + 1) / 13.0; double freq = MusicalScale().getNoteFreq(note + 12); double phase = 0; // Synthesize tones for (size_t i = 0; i < samples; ++i) { float fvalue = d * 0.2 * std::sin(phase) + 0.2 * std::sin(2 * phase) + (1.0 - d) * 0.2 * std::sin(4 * phase); phase += 2.0 * M_PI * freq / SampleRate; // Convert float to 16-bit integer and push to buffer qint16 svalue = fvalue * 32768; char* value = reinterpret_cast(&svalue); buffer.push_back(value[0]); buffer.push_back(value[1]); } //std::ofstream of("/tmp/wavdump.wav"); //of.write(buf.data(), buf.size()); } void Synth::run() { calcNext(); while (!m_quit) { m_mutex.lock(); // Wait here until wake up or time out if (m_condition.wait(&m_mutex, m_delay * 1000)) { // We were woken up, so let's see if an update is in order m_mutex.unlock(); if (m_quit) break; calcNext(); } else { // Time-out: time to play the music m_mutex.unlock(); if (m_quit) break; emit playBuffer(m_soundData[m_curBuffer]); m_curBuffer = (m_curBuffer+1) % 2; // Slightly hacky stuff follows: // We advance the time a bit to make sure we are over the note beginning. // Then cache the next note, but put longer delay (which will be corrected // with the next tick) so that we don't accidentally play wrong note( // in case we advanced the time too much). m_pos += 0.2; calcNext(); m_delay = std::max(m_delay, 1.0); } } } void Synth::calcNext() { QElapsedTimer timer; timer.start(); SynthNote n; { QMutexLocker locker(&m_mutex); SynthNotes::const_iterator it = m_notes.begin(); while (it != m_notes.end() && it->begin < m_pos) ++it; if (it == m_notes.end()) { m_delay = 1000.0; return; } n = *it; } m_delay = n.begin - m_pos; if (n.begin != m_noteBegin) { // Need to create a new buffer m_noteBegin = n.begin; createBuffer(m_soundData[m_curBuffer], n.note % 12, n.length / m_rate); } // Compensate for the time spent in this function m_delay -= timer.elapsed() / 1000.0; if (m_delay <= 0.001) m_delay = 0.001; } std::string Synth::writeWavHeader(unsigned bits, unsigned ch, unsigned sr, unsigned samples) { std::ostringstream out; unsigned bps = ch * bits / 8; // Bytes per sample unsigned datasize = bps * samples; unsigned size = datasize + 0x2C; out.write("RIFF" ,4); // RIFF chunk { unsigned int tmp=size-0x8 ; out.write((char*)(&tmp),4); } // RIFF chunk size out.write("WAVEfmt ",8); // WAVEfmt header { int tmp=0x00000010 ; out.write((char*)(&tmp),4); } // Always 0x10 { short tmp=0x0001 ; out.write((char*)(&tmp),2); } // Always 1 { short tmp = ch; out.write((char*)(&tmp),2); } // Number of channels { int tmp = sr; out.write((char*)(&tmp),4); } // Sample rate { int tmp = bps * sr; out.write((char*)(&tmp),4); } // Bytes per second { short tmp = bps; out.write((char*)(&tmp),2); } // Bytes per frame { short tmp = bits; out.write((char*)(&tmp),2); } // Bits per sample out.write("data",4); // data chunk { int tmp = datasize; out.write((char*)(&tmp),4); } return out.str(); } // BufferPlayer BufferPlayer::BufferPlayer(QObject *parent) : QObject(parent) { m_buffer = new QBuffer(this); QAudioFormat format; format.setChannelCount(1); format.setSampleRate(Synth::SampleRate); format.setSampleSize(16); format.setSampleType(QAudioFormat::SignedInt); format.setByteOrder(QAudioFormat::LittleEndian); format.setCodec("audio/pcm"); QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); if (!info.isFormatSupported(format)) { qWarning() << "Synth output format not supported, trying nearest"; format = info.nearestFormat(format); } m_player = new QAudioOutput(format, this); m_player->setVolume(1.0f); //m_player->setNotifyInterval(100); //connect(m_player, SIGNAL(notify()), this, SLOT(debugDumpStats())); connect(m_player, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State))); } bool BufferPlayer::play(const QByteArray& ba) { if (m_player->state() != QAudio::ActiveState) { m_player->stop(); m_buffer->close(); m_buffer->setData(ba); m_buffer->open(QIODevice::ReadOnly); m_player->setBufferSize(ba.size()); m_player->start(m_buffer); return true; } return false; } void BufferPlayer::handleStateChanged(QAudio::State newState) { //qDebug() << "Synth" << newState; switch (newState) { case QAudio::IdleState: // Finished playing (no more data) m_player->stop(); break; case QAudio::StoppedState: // Stopped for other reasons if (m_player->error() != QAudio::NoError) { qWarning() << "Synth audio error code " << m_player->error(); } break; default: break; } } void BufferPlayer::debugDumpStats() { qWarning() << "bytesFree =" << m_player->bytesFree() << "- elapsedUSecs =" << m_player->elapsedUSecs() << "- processedUSecs =" << m_player->processedUSecs(); } composer-master/src/songparser.cc0000644000175000017500000001277513357046745015635 0ustar niknik#include "songparser.hh" #include "textcodecselector.hh" #include #include #include namespace SongParserUtil { void assign(bool& var, QString 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.toStdString()); } } /// constructor SongParser::SongParser(Song& s): m_song(s), m_stream(), m_linenum(), m_relative(), m_gap(), m_prevtime(), m_prevts(), m_relativeShift(), m_maxScore(), m_tsPerBeat(), m_tsEnd() { enum { NONE, TXT, XML, INI, MIDI, SM, LRC } type = NONE; // Read the file, determine the type and do some initial validation checks QFile file(m_song.path + m_song.filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) throw SongParserException(QT_TR_NOOP("Could not open song file"), 0); QFileInfo finfo(file); if (finfo.size() < 10 || finfo.size() > 100000) throw SongParserException("Does not look like a song file (wrong size)", 1, true); // Determine encoding QString data = TextCodecSelector::readAllAndHandleEncoding(file); file.close(); // Add a newline to the end to make sure our parsing doesn't skip the last line data += "\n"; if (smCheck(data)) type = SM; else if (txtCheck(data)) type = TXT; else if (xmlCheck(data)) type = XML; else if (iniCheck(data)) type = INI; else if (midiCheck(data)) type = MIDI; else if (lrcCheck(data)) type = LRC; else throw SongParserException("Does not look like a song file (wrong header)", 1, true); m_stream.setString(&data); // Parse try { if (type == TXT) txtParse(); else if (type == XML) xmlParse(); else if (type == INI) iniParse(); else if (type == MIDI) midParse(); else if (type == SM) smParse(); else if (type == LRC) lrcParse(); } catch (std::runtime_error& e) { throw SongParserException(e.what(), m_linenum); } // Remove bogus entries if (!QFileInfo(m_song.path + m_song.cover).exists()) m_song.cover = ""; if (!QFileInfo(m_song.path + m_song.background).exists()) m_song.background = ""; if (!QFileInfo(m_song.path + m_song.video).exists()) m_song.video = ""; // TODO: Should we try to guess images and stuff? finalize(); // Do some adjusting to the notes s.loadStatus = Song::FULL; } bool SongParser::getline(QString &line) { ++m_linenum; line = m_stream.readLine(); return !m_stream.atEnd(); } namespace { /// Return the amount of shift (in notes) required for note to make put it in the nearest octave of the target note int nearestOctave(int note, int target) { return (1200 + 6 + target - note) / 12 * 12 - 1200; // 1200 for always positive, 6 for mathematical rounding } void normalize(Notes& notes, int limLow, int limHigh) { // Analyze the entire song first int defaultShift = 0; int shiftFS = 0; { std::vector fsNotes, regNotes; for (Notes::iterator it = notes.begin(); it != notes.end(); ++it) { (it->type == Note::FREESTYLE ? fsNotes : regNotes).push_back(it->note); } if (!regNotes.empty()) { std::sort(regNotes.begin(), regNotes.end()); // Find a good starting value for default shift defaultShift = nearestOctave(regNotes[regNotes.size() / 2], 0.5 * (limLow + limHigh)); // Find the additional correction required for freestyle notes if (!fsNotes.empty()) { std::sort(fsNotes.begin(), fsNotes.end()); shiftFS = nearestOctave(fsNotes[fsNotes.size() / 2], regNotes[regNotes.size() / 2]); } } } // Process sentence by sentence for (Notes::iterator it = notes.begin(), itnext = it; it != notes.end();) { int low, high; low = high = it->note; // Analyze the sentence and find the end of it while (++itnext != notes.end() && !itnext->lineBreak) { if (itnext->type == Note::SLEEP) continue; int n = itnext->note; if (itnext->type == Note::FREESTYLE) n += shiftFS; low = std::min(low, n); high = std::max(high, n); } // Per-sentence shift int shift = nearestOctave(0.5 * (low + high), 0.5 * (limLow + limHigh)); if (std::abs(defaultShift - shift) <= 12) shift = defaultShift; // Shift the notes into position for (; it != itnext; ++it) { if (it->type == Note::SLEEP) continue; int s = shift; if (it->type == Note::FREESTYLE) s += shiftFS; // The last resort if everything else fails (per-note shifting) while (it->note + s < limLow) s += 12; while (it->note + s > limHigh) s -= 12; it->note += s; it->notePrev += s; } } } } void SongParser::finalize() { std::vector tracks = m_song.getVocalTrackNames(); for(std::vector::const_iterator it = tracks.begin() ; it != tracks.end() ; ++it) { VocalTrack& vocal = m_song.getVocalTrack(*it); vocal.m_scoreFactor = 1.0 / m_maxScore; if (vocal.notes.empty()) continue; // Set begin/end times vocal.beginTime = vocal.notes.front().begin, vocal.endTime = vocal.notes.back().end; // Setup sentence start indicators and remove sleep notes bool sentenceStart = true; for (Notes::iterator it = vocal.notes.begin(); it != vocal.notes.end();) { if (sentenceStart) it->lineBreak = true; sentenceStart = false; if (it->type == Note::SLEEP) { sentenceStart = true; it = vocal.notes.erase(it); } else ++it; } // Note normalization const int limLow = 1, limHigh = 47; if (vocal.noteMin < limLow || vocal.noteMax > limHigh) normalize(vocal.notes, limLow, limHigh); } if (m_tsPerBeat) { // Add song beat markers for (unsigned ts = 0; ts < m_tsEnd; ts += m_tsPerBeat) m_song.beats.push_back(tsTime(ts)); } } composer-master/src/synth.hh0000644000175000017500000000472713357046745014627 0ustar niknik#pragma once #include #include #include #include #include #include #include #include #include "notes.hh" struct SynthNote { SynthNote(): note(24), begin(), length() {} SynthNote(const Note& n): note(n.note), begin(n.begin), length(n.length()) {} bool operator<(const SynthNote& rhs) { return begin < rhs.begin; } int note; double begin; double length; }; typedef QList SynthNotes; /** * @brief Threaded WAV buffer creator. * * Synthesizes and schedules notes in a thread and sends them to the main thread when its time to play them. */ class Synth: public QThread { Q_OBJECT public: static const int SampleRate = 22050; ///< Sample rate Synth(QObject *parent = NULL) : QThread(parent), m_delay(), m_pos(), m_rate(1.0), m_noteBegin(), m_curBuffer(), m_quit() { qRegisterMetaType("QByteArray"); // Register type for use with queued connections } ~Synth() { stop(); wait(); } /// Updates the synth void tick(qint64 pos, qreal playbackRate, const SynthNotes& notes); /// Stop synthesizing void stop(); /// Creates the sound static void createBuffer(QByteArray &buffer, int note, double length); signals: void playBuffer(const QByteArray&); protected: /// Thread runs here void run(); private: /// Calculates the next values void calcNext(); /// WAV header writer static std::string writeWavHeader(unsigned bits, unsigned ch, unsigned sr, unsigned samples); SynthNotes m_notes; ///< Notes to synthesize double m_delay; ///< How many seconds until the next sound must be played double m_pos; ///< Position where we are now double m_rate; ///< Music playback speed multiplier double m_noteBegin; ///< Position of the next note QByteArray m_soundData[2]; ///< The WAV buffers int m_curBuffer; ///< Which buffer we are currently using bool m_quit; ///< Flag to signal the thread should quit QMutex m_mutex; ///< Mutex for protecting resource access QWaitCondition m_condition; ///< For signaling the thread }; /** * @brief Class for playing a WAV buffer from memory. * * Designed to be reused, but won't play the buffer if the previous hasn't finished. */ class BufferPlayer: public QObject { Q_OBJECT Q_DISABLE_COPY(BufferPlayer) public: BufferPlayer(QObject *parent); bool play(const QByteArray& ba); public slots: void handleStateChanged(QAudio::State newState); void debugDumpStats(); private: QBuffer *m_buffer; QAudioOutput *m_player; }; composer-master/ui/0000755000175000017500000000000013357046745012755 5ustar niknikcomposer-master/ui/editor.ui0000644000175000017500000005643513357046745014617 0ustar niknik EditorApp 0 0 800 600 0 0 600 500 Editor 0 0 QFrame::NoFrame QFrame::Plain 140 70 221 91 true 0 0 219 89 16777215 300 0 &General General This tab contains playback, timing and note manipulation tools. Play/pause the music file Play (P) P Set selected note start to cursor position and move to next phrase start Time phrase (N) N Split selected note into two new ones &Split note Current phrase: Make sure the playback cursor is always visible Grab playback true Skip to next phrase start without timing Skip phrase (M) M Floating notes automatically adjust with regards to neighbours Qt::RightToLeft Floating note Insert new notes after the selected one I&nsert after Synthesize and play notes during playback Synthesizer Playback Timing Note type Choose what kind of note the selection should be. false Normal Golden Freestyle 0 0 Playback rate: 1 Qt::AlignCenter 0 0 50 150 100 Qt::Horizontal Tools Qt::LeftToRight Note properties Qt::Vertical 20 40 Check to start a new phrase with selected note Qt::RightToLeft Phrase beginning Time note Song &properties Song properties This tab lets you to configure the song metadata Title Genre Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Artist Year Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Music Path to the music file No music Qt::Vertical 20 40 Change music file... Load music... 0 0 800 25 &File Expo&rt &Edit P&references &Help &Insert &View true 2 0 0 qrc:/docs/helpindex.html true &About... &New Ctrl+N &Open... Ctrl+O &Save Ctrl+S S&ave as... E&xit Ctrl+Q false &Undo Ctrl+Z false &Redo Ctrl+Shift+Z &SingStar XML... Lyrics to fi&le... Lyrics to &clipboard &Music file... Lyrics from fi&le... Lyrics from &clipboard &UltraStar TXT... &FoF MIDI... &What's this? true &Anti-aliasing Use anti-aliasing for the pitch visualization &Getting started Select &all Ctrl+A &Copy Ctrl+C Cu&t Ctrl+X &Paste Ctrl+V &Delete Del Zoom &in Ctrl++ Zoom &out Ctrl+- &Reset zoom Ctrl+0 &LRC... &Contents F1 About Qt... &Additional music file... Timed lyrics from LRC/Soramimi file... false Select all a&fter Sora&mimi TXT... &Enhanced LRC... actionHelp triggered() helpDock show() -1 -1 751 301 composer-master/ui/aboutdialog.ui0000644000175000017500000001065313357046745015613 0ustar niknik AboutDialog 0 0 531 320 Dialog &About 32 75 true ApplicationName Qt::AlignCenter 16 Version: XY Qt::AlignCenter Qt::Vertical 20 40 Website: <a href="http://performous.org/composer">http://performous.org/composer</a> Qt::RichText Qt::AlignCenter true Qt::Vertical 20 40 This is an application for creating notes for karaoke games that score the performance based on pitch accuracy. true A&uthors QPlainTextEdit::NoWrap true &License QPlainTextEdit::NoWrap true &Close composer-master/ui/gettingstarted.ui0000644000175000017500000001452613357046745016354 0ustar niknik GettingStarted 0 0 766 350 Getting Started &Show this window on application start true &Close true QFrame::StyledPanel QFrame::Raised or Insert lyrics from clipboard Insert a music file 20 2. Insert lyrics from a file 20 3. 20 1. Qt::Vertical 20 40 20 4. 20 5. Qt::Horizontal 40 20 or Time the lyrics roughly while listening to the song Fine tune and fix wrongly guessed note positions 20 6. Export to your preferred format Enter or correct song metadata LRC is a popular karaoke format with timing information, but no pitch. Soramimi is an old abandoned karaoke software using a similar format. Insert LRC/Soramimi timecodes composer-master/editor.qrc0000644000175000017500000000240313357046745014334 0ustar niknik icons/application-exit.png icons/composer.png icons/document-new.png icons/document-save-as.png icons/document-save.png icons/edit-redo.png icons/edit-undo.png icons/edit-copy.png icons/edit-cut.png icons/edit-delete.png icons/edit-paste.png icons/edit-select-all.png icons/document-open.png icons/help-about.png icons/help-contents.png icons/help-hint.png icons/insert-object.png icons/insert-text.png icons/media-playback-pause.png icons/media-playback-start.png icons/media-playback-stop.png icons/preferences-other.png icons/zoom-in.png icons/zoom-original.png icons/zoom-out.png docs/helpindex.html docs/Authors.txt docs/License.txt composer-master/licence.txt0000644000175000017500000004372313357046745014514 0ustar niknikPerformous is Copyright (C) 2006-2016 The Performous Team Performous is GNU GPLv2 or later. Some parts of the project may have more liberal licenses, see docs/license for details. See docs/Authors.txt for information on copyright holders and licenses of specific parts. GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. composer-master/platform/0000755000175000017500000000000013357046745014164 5ustar niknikcomposer-master/platform/composer.desktop0000644000175000017500000000032113357046745017402 0ustar niknik[Desktop Entry] Encoding=UTF-8 Name=Composer Comment=Note creator for pitch-detecting karaoke games Exec=composer Icon=composer.png Terminal=false Type=Application Categories=Application;AudioVideo;Music;Qt; composer-master/platform/mingw-cross-env/0000755000175000017500000000000013357046745017222 5ustar niknikcomposer-master/platform/mingw-cross-env/makeinstaller.py0000755000175000017500000000723413357046745022440 0ustar niknik#!/usr/bin/env python # This file has been modified from: # NSIS script generator for Performous. # Copyright (C) 2010 John Stumpo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import subprocess import sys try: makensis = subprocess.Popen([os.environ['MAKENSIS'], '-'], stdin=subprocess.PIPE) except KeyError: makensis = subprocess.Popen(['makensis', '-'], stdin=subprocess.PIPE) if not os.path.isdir('dist'): os.mkdir('dist') os.chdir('stage') app = 'Composer' version = '2.0' makensis.stdin.write(r'''!include "MUI2.nsh" !define APP "%(app)s" !define VERSION "%(version)s" Name "${APP} ${VERSION}" OutFile "dist\${APP}-${VERSION}.exe" SetCompressor /SOLID lzma ShowInstDetails show ShowUninstDetails show InstallDir "$PROGRAMFILES\${APP}" InstallDirRegKey HKLM "Software\${APP}" "" RequestExecutionLevel admin !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_WELCOME !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_FINISH !insertmacro MUI_LANGUAGE "English" Section ''' % {'app': app, 'version': version}) for root, dirs, files in os.walk('.'): makensis.stdin.write(' SetOutPath "$INSTDIR\\%s"\n' % root.replace('/', '\\')) for file in files: makensis.stdin.write(' File "%s"\n' % os.path.join('stage', root, file).replace('/', '\\')) makensis.stdin.write(r''' WriteRegStr HKLM "Software\${APP}" "" "$INSTDIR" WriteUninstaller "$INSTDIR\uninst.exe" SetShellVarContext all CreateDirectory "$SMPROGRAMS\${APP}" CreateShortcut "$SMPROGRAMS\${APP}\${APP}.lnk" "$INSTDIR\${APP}.exe" CreateShortcut "$SMPROGRAMS\${APP}\Uninstall.lnk" "$INSTDIR\uninst.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP}" "DisplayName" "${APP}" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP}" "UninstallString" "$\"$INSTDIR\uninst.exe$\"" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP}" "DisplayIcon" "$INSTDIR\${APP}.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP}" "DisplayVersion" "${VERSION}" SectionEnd Section Uninstall ''') for root, dirs, files in os.walk('.', topdown=False): for dir in dirs: makensis.stdin.write(' RmDir "$INSTDIR\\%s"\n' % os.path.join(root, dir).replace('/', '\\')) for file in files: makensis.stdin.write(' Delete "$INSTDIR\\%s"\n' % os.path.join(root, file).replace('/', '\\')) makensis.stdin.write(' RmDir "$INSTDIR\\%s"\n' % root.replace('/', '\\')) makensis.stdin.write(r''' Delete "$INSTDIR\uninst.exe" RmDir "$INSTDIR" SetShellVarContext all Delete "$SMPROGRAMS\${APP}\${APP}.lnk" Delete "$SMPROGRAMS\${APP}\Uninstall.lnk" RmDir "$SMPROGRAMS\${APP}" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP}" DeleteRegKey /ifempty HKLM "Software\${APP}" SectionEnd ''') makensis.stdin.close() if makensis.wait() != 0: print >>sys.stderr, 'Installer compilation failed.' sys.exit(1) else: print 'Installer ready.' composer-master/platform/mingw-cross-env/makebuilddir.sh0000755000175000017500000000116313357046745022216 0ustar niknik#!/bin/bash # Creates a build dir and does the required CMake mangling. source pathconfig.sh mkdir -p "$BUILD_DIR" cd "$BUILD_DIR" cat > Toolchain.cmake << EOF set(CMAKE_SYSTEM_NAME Windows) set(CMAKE_C_COMPILER ${CROSS_ID}-gcc) set(CMAKE_CXX_COMPILER ${CROSS_ID}-g++) set(CMAKE_FIND_ROOT_PATH ${CROSS_PREFIX}/${CROSS_ID}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(WINDRES ${CROSS_ID}-windres) EOF PATH="${CROSS_PREFIX}/bin:$PATH" cmake -DCMAKE_TOOLCHAIN_FILE=Toolchain.cmake -DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" "$SOURCE_DIR" composer-master/platform/mingw-cross-env/README0000644000175000017500000000205613357046745020105 0ustar niknikHow to cross-compile from Linux for Windows =========================================== 1. Download and extract mingw-cross-env from http://mingw-cross-env.nongnu.org/ to your desired location. 2. Read through http://mingw-cross-env.nongnu.org/#usage to learn how to utilize all your processor cores for speedier compilation. 3. Compile GCC and its dependencies: make gcc 4. Replace ffmpeg.mk and qt.mk files in the src-subfolder of your cross-env installation with those provided in the mk-directory here. This enables Phonon and uses shared libraries instead of static ones (static libs cause trouble in linking and apparantly Phonon cannot be even built that way). 5. Compile ffmpeg and qt: make ffmpeg qt 6. Add the cross-compiler's bin directory (e.g. $HOME/mingw/usr/bin) to your PATH environment variable. 7. Run makebuilddir.sh 8. Compile editor as usual: cd build && make 9. Run makezip.sh if you want to create a simple zip package. 10. To create an executable installer, run makeinstaller.py from the build dir (requires makezip.sh to be run first) composer-master/platform/mingw-cross-env/makezip.sh0000755000175000017500000000116713357046745021226 0ustar niknik#!/bin/bash -e # Creates a simple zip package. source pathconfig.sh ARCHIVE=editor.7z ARCHIVER=`which 7z` if [ $? -ne 0 ]; then echo "Couldn't find 7z, cannot create archive." exit 1 fi cd "$BUILD_DIR" rm -rf "$INSTALL_DIR" mkdir -p "$INSTALL_DIR" # No install target yet cp *.exe "$INSTALL_DIR" # Copy DLLs ../copydlls.py "$CROSS_PREFIX/$CROSS_ID/bin" "$INSTALL_DIR" # These ones are not detected by copydlls.py cp "$CROSS_PREFIX/$CROSS_ID/bin/QtSvg4.dll" "$INSTALL_DIR" cp -r "$CROSS_PREFIX/$CROSS_ID/plugins/phonon_backend" "$INSTALL_DIR" # Create archive cd "$INSTALL_DIR" $ARCHIVER a -bd -r "$BUILD_DIR/$ARCHIVE" * composer-master/platform/mingw-cross-env/mk/0000755000175000017500000000000013357046745017631 5ustar niknikcomposer-master/platform/mingw-cross-env/mk/qt.mk0000644000175000017500000000544013357046745020611 0ustar niknik# This file is part of mingw-cross-env. # See doc/index.html for further information. # Qt PKG := qt $(PKG)_IGNORE := $(PKG)_VERSION := 4.7.1 $(PKG)_CHECKSUM := fcf764d39d982c7f84703821582bd10c3192e341 $(PKG)_SUBDIR := $(PKG)-everywhere-opensource-src-$($(PKG)_VERSION) $(PKG)_FILE := $(PKG)-everywhere-opensource-src-$($(PKG)_VERSION).tar.gz $(PKG)_WEBSITE := http://qt.nokia.com/ $(PKG)_URL := http://get.qt.nokia.com/qt/source/$($(PKG)_FILE) $(PKG)_DEPS := gcc libodbc++ postgresql freetds openssl libgcrypt zlib libpng jpeg libmng tiff sqlite libiconv dbus define $(PKG)_UPDATE wget -q -O- 'http://qt.gitorious.org/qt/qt/commits' | \ grep '

  • ]*\)\.tar.*,\1,p' | \ head -1 endef define $(PKG)_BUILD cd '$(1)' && ./configure \ --cross-prefix='$(TARGET)'- \ --enable-cross-compile \ --arch=i686 \ --target-os=mingw32 \ --prefix='$(PREFIX)/$(TARGET)' \ --enable-shared \ --disable-static \ --disable-debug \ --disable-doc \ --enable-memalign-hack \ --enable-gpl \ --enable-version3 \ --disable-nonfree \ --enable-postproc \ --enable-libspeex \ --enable-libtheora \ --enable-libvorbis \ --enable-libmp3lame \ --enable-libxvid \ --enable-libfaad \ --disable-libfaac \ --enable-libopencore-amrnb \ --enable-libopencore-amrwb \ --enable-libx264 \ --enable-libvpx $(MAKE) -C '$(1)' -j '$(JOBS)' $(MAKE) -C '$(1)' -j 1 install endef composer-master/platform/mingw-cross-env/copydlls.py0000755000175000017500000001016213357046745021430 0ustar niknik#!/usr/bin/env python # DLL dependency resolution and copying script. # Copyright (C) 2010 John Stumpo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import shutil import struct import sys if len(sys.argv) != 3: sys.stderr.write('''Usage: %s [source] [destination] Copies DLLs in source needed by PE executables in destination to destination. Both source and destination should be directories. ''' % sys.argv[0]) sys.exit(1) def is_pe_file(file): f = open(file, 'rb') if f.read(2) != 'MZ': return False # DOS magic number not present f.seek(60) peoffset = struct.unpack('