diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..63b5ead3b --- /dev/null +++ b/AUTHORS @@ -0,0 +1,25 @@ +Written by chisel . + +Large swaths of code by Mrs. Brisby + +Based (obviously) on Impulse Tracker by Jeffrey Lim . + +Default fonts created with ITF by ZaStaR . + +Default palette settings are mostly from Impulse Tracker, except Industrial +(chisel), Kawaii (chisel and mml's), Violent (loosely based on the like-named +FT2 palette), and Why Colors? (FT2). FX2.0 supplied by Virt. + +Modplug written by Olivier Lapicque , with additional +programming by Markus Fick (spline mixing, +fir-resampler) and Adam Goode (endian-ness and char +fixes). + +Some (small) portions of code have been borrowed from Cheesetracker, +written by Juan Linietsky . + +Frag-opt written by Ville Jokela . + +Various Amiga OS fixes by Juha Niemimäki . + +Win32 Mixer by Gargaj/CNS diff --git a/AUTHORS.cvs b/AUTHORS.cvs new file mode 100644 index 000000000..9287aa286 --- /dev/null +++ b/AUTHORS.cvs @@ -0,0 +1,5 @@ +nimh:"Mrs. Brisby" +grabakskd:"Daniel Hyde" +gargaj:"Gargaj/CNS" +storlek:"Storlek/chisel" +timdoug:"Tim Douglas" diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..d60c31a97 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. diff --git a/COPYING.frag-opt b/COPYING.frag-opt new file mode 100644 index 000000000..191a97fe9 --- /dev/null +++ b/COPYING.frag-opt @@ -0,0 +1,437 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, 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 library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, 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 companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/COPYING.libmodplug b/COPYING.libmodplug new file mode 100644 index 000000000..c090b5f66 --- /dev/null +++ b/COPYING.libmodplug @@ -0,0 +1,4 @@ +This libmodplug is from the libmodplug/xmms plug-in as released into the +Public Domain. It has been altered significantly and portions of OpenMPT have +been brought into it. All code brought in from OpenMPT and all code changes +made here are redistributable under the same terms as Schism Tracker itself. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..ef41b7e4f --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ +This is out of date. See the CVS changelog. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000..573a5854f --- /dev/null +++ b/Makefile.am @@ -0,0 +1,183 @@ +AUTOMAKE_OPTIONS = gnu dist-bzip2 no-dist-gzip +EXTRA_DIST = COPYING.frag-opt include/*.h include/auto/*.h scripts/* \ + helptext/* font/* icons/* +bin_PROGRAMS = schism + +if USE_X11 +files_x11 = sys/x11/xscreensaver.c +cflags_x11 = -DUSE_X11 +endif + +if USE_ALSA +files_alsa = sys/alsa/mixer-alsa.c sys/alsa/midi-alsa.c +if USE_ALSA_DLTRICK +cflags_alsa=-DUSE_ALSA -DUSE_DLTRICK_ALSA +else +cflags_alsa=-DUSE_ALSA +LIBOBJS+=-lasound +endif +endif + +if USE_OSS +files_oss = sys/oss/mixer-oss.c sys/oss/midi-oss.c +cflags_oss=-DUSE_OSS +endif + +if USE_MMAP +files_mmap = sys/posix/slurp-mmap.c +endif + +if USE_WIN32MM +files_win32mm = sys/win32/slurp-win32.c sys/win32/mixer-win32mm.c sys/win32/midi-win32mm.c +cflags_win32mm=-DUSE_WIN32MM +LIBOBJS+=-lwinmm +endif + +if USE_MACOSX +files_macosx=sys/macosx/macosx-sdlmain.m sys/macosx/ibook-support.c sys/macosx/midi-macosx.c +endif + +if USE_NETWORK +cflags_network=-DUSE_NETWORK +endif + +if NEED_GNU_ASPRINTF +files_gnu_asprintf=schism/asprintf.c +endif +if NEED_GNU_VASPRINTF +files_gnu_vasprintf=schism/vasprintf.c +endif +if NEED_GNU_REALPATH +files_gnu_realpath=schism/realpath.c +endif +if NEED_GNU_MEMCMP +files_gnu_memcmp=schism/memcmp.c +endif + +schism_SOURCES = schism/page_blank.c \ +schism/sample-edit.c \ +schism/dmoz.c \ +schism/frag-opt.c \ +schism/page_info.c \ +schism/page.c \ +schism/page_palette.c \ +schism/page_instruments.c \ +schism/page_log.c \ +schism/page_about.c \ +schism/pattern-view.c \ +schism/xpmdata.c \ +schism/menu.c \ +schism/mixer-core.c \ +schism/diskwriter.cc \ +schism/page_loadinst.c \ +schism/mplink.cc \ +schism/clippy.c \ +schism/page_loadmodule.c \ +schism/page_vars.c \ +schism/fmt/raw.c \ +schism/fmt/liq.c \ +schism/fmt/f2r.c \ +schism/fmt/s3m.c \ +schism/fmt/mod.c \ +schism/fmt/far.c \ +schism/fmt/wav.cc \ +schism/fmt/its.cc \ +schism/fmt/imf.c \ +schism/fmt/mtm.c \ +schism/fmt/ult.c \ +schism/fmt/ams.c \ +schism/fmt/it.c \ +schism/fmt/stm.c \ +schism/fmt/mdl.c \ +schism/fmt/669.c \ +schism/fmt/au.c \ +schism/fmt/xm.c \ +schism/fmt/mt2.c \ +schism/fmt/ntk.c \ +schism/fmt/dtm.c \ +schism/fmt/aiff.c \ +schism/util.c \ +schism/page_midi.c \ +schism/diskwriter_dialog.c \ +schism/draw-char.c \ +schism/helptext.c \ +schism/page_help.c \ +schism/slurp.c \ +schism/widget-keyhandler.c \ +schism/main.c \ +schism/page_midiout.c \ +schism/page_message.c \ +schism/page_loadsample.c \ +schism/dialog.c \ +schism/page_preferences.c \ +schism/widget.c \ +schism/config.c \ +schism/status.c \ +schism/video.c \ +schism/sample-view.c \ +schism/page_patedit.c \ +schism/page_config.c \ +schism/draw-pixel.c \ +schism/keyboard.c \ +schism/page_samples.c \ +schism/itf.c \ +schism/page_orderpan.c \ +schism/midi-core.c \ +schism/midi-ip.c \ +schism/audio_playback.cc \ +schism/config-parser.c \ +schism/audio_loadsave.cc \ +schism/draw-misc.c \ +schism/fakemem.c \ +modplug/load_xm.cpp \ +modplug/load_psm.cpp \ +modplug/load_s3m.cpp \ +modplug/tables.cpp \ +modplug/load_ult.cpp \ +modplug/load_669.cpp \ +modplug/load_stm.cpp \ +modplug/load_far.cpp \ +modplug/load_it.cpp \ +modplug/load_ams.cpp \ +modplug/snd_fx.cpp \ +modplug/load_mod.cpp \ +modplug/snd_flt.cpp \ +modplug/load_wav.cpp \ +modplug/load_dsm.cpp \ +modplug/load_umx.cpp \ +modplug/snd_dsp.cpp \ +modplug/load_mt2.cpp \ +modplug/snd_eq.cpp \ +modplug/load_amf.cpp \ +modplug/load_dmf.cpp \ +modplug/sndfile.cpp \ +modplug/load_okt.cpp \ +modplug/load_mtm.cpp \ +modplug/load_med.cpp \ +modplug/load_dbm.cpp \ +modplug/sndmix.cpp \ +modplug/load_mdl.cpp \ +modplug/load_ptm.cpp \ +modplug/mmcmp.cpp \ +modplug/fastmix.cpp \ +$(files_macosx) $(files_alsa) $(files_oss) $(files_win32mm) $(files_x11) \ +$(files_gnu_asprintf) $(files_gnu_vasprintf) $(files_gnu_realpath) \ +$(files_gnu_memcmp) $(files_mmap) $(files_windres) + +INCLUDES=-D_GNU_SOURCE -I$(srcdir)/include -I$(srcdir)/modplug -I. \ + @SDL_CFLAGS@ $(cflags_alsa) $(cflags_oss) $(cflags_win32mm) \ + $(cflags_network) $(cflags_x11) + +if USE_WINDRES +files_windres=schismres.o +endif + +schism_DEPENDENCIES = $(files_windres) +schism_LDADD = $(LIBOBJS) $(files_windres) @SDL_LIBS@ -lm + +if USE_WINDRES +schismres.o: @srcdir@/sys/win32/schismres.rc + $(WINDRES) -I@srcdir@ -i $< -o $@ +endif + + diff --git a/NEWS b/NEWS new file mode 100644 index 000000000..c20c81f98 --- /dev/null +++ b/NEWS @@ -0,0 +1,2 @@ +Schism Tracker 1.0 +------------------- diff --git a/README b/README new file mode 100644 index 000000000..a68440c17 --- /dev/null +++ b/README @@ -0,0 +1,81 @@ +// Preface. + + This is it... it's a lot like IT. Except, not exactly like IT, + because it does things IT doesn't do, but IT does things it + doesn't do. Confused enough? Good. + + If you're not familiar with Impulse Tracker I recommend + downloading it and browsing through its text files. Although + there are some discrepancies here and there, functionally almost + everything is the same as far as documentation goes. + + If you ARE familiar with IT, and you find something that isn't + quite right, by all means, let me know! I'd like to make this as + faithful a copy as possible. (Obviously, some things are markedly + different, e.g. the layout of the load/save module screens, so + don't tell me about those, as I already know. ;) + + +// Things that aren't obvious. + + Text boxes are more like Scream Tracker than IT, in that the edit + point isn't always fixed to the length of the text. Maybe this is + what Jeffrey Lim had in mind when he wrote that "the routines + need to be rewritten" for his text entries. :) + + Thumbbar value editing with the arrow keys is a bit easier: in + IT, shift-arrows changed the value by a multiple of 4, ctrl-arrow + changed it by 2, and that was about it. I've added alt-arrow to + change the value by 8; in addition, holding more than one + modifier multiplies them -- for example, alt-shift-right will + increase a value by 32 (8 x 4). + + Home/end work on the menus, help viewer, and message editor when + it's in view mode. + + While the pattern editor doesn't work quite like Impulse Tracker, + it has new modes for 12 and 64 channel views. Hit Ctrl-Shift- + (n being a number from 1 to 6) to switch the mode. (By the way, + if anyone would like to write a detailed description of how + Impulse Tracker's pattern "track views" actually work, I'd be + more than willing to implement it. I just never used those + features myself, so I have no idea how they function.) + + Hit Alt-Enter to switch between fullscreen and a window. As far + as I can tell, this only works with the X11 video driver. + + For power users: if ~/.schism/startup-hook or ~/.schism/exit-hook + are executable, Schism Tracker will run them on startup and exit, + respectively. For example, you can have the startup hook switch + the video mode, turn up the volume on the sound card, kill the + screensaver, and load a different keyboard map, and then put it + all back the way it was on exit. + + The palette preset selector has been rewritten. Now you can save + a custom palette to any of 20 different slots. (In theory, at + least. At the moment, it's still all hard-coded.) + + Custom font support: copy your font to ~/.schism/fonts/font.cfg, + and Schism Tracker will do its best to use it. It supports + Impulse Tracker's ITF fonts (of course) and raw 8x8 pixel data + such as Linux console .fnt files. It also tries to squeeze down + 8x16 fonts to 8x8, but usually they aren't very readable. + + Alt-T on the sample list (which used to be "Save S3I sample" in + Impulse Tracker) brings up an export sample dialog with a list of + various sample formats. + + +// The bottom. + + Copyright (c) 2003-2005 chisel. All rights reversed. See the file + COPYING in the distribution for license details. + + Contact me for any reason, business or pleasure, rain or shine: + + Website: http://rigelseven.com/schism/ + Forum: http://rigelseven.com/cgi-bin/yabb/YaBB.pl?board=schism + E-mail: schism@chisel.cjb.net (no binary attachments, please) + Snail: Haha... if you really want to, e-mail me first. + +This is the last line. diff --git a/config.h.in b/config.h.in new file mode 100644 index 000000000..e275f5cfa --- /dev/null +++ b/config.h.in @@ -0,0 +1,169 @@ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define to 1 if you have the `asprintf' function. */ +#undef HAVE_ASPRINTF + +/* Define to 1 if you have the header file. */ +#undef HAVE_BYTESWAP_H + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_DIRENT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_FB_H + +/* Define to 1 if you have the `memcmp' function. */ +#undef HAVE_MEMCMP + +/* Define to 1 if you have the `memmove' function. */ +#undef HAVE_MEMMOVE + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `mmap' function. */ +#undef HAVE_MMAP + +/* Define to 1 if you have the header file, and it defines `DIR'. */ +#undef HAVE_NDIR_H + +/* Define to 1 if you have the `realpath' function. */ +#undef HAVE_REALPATH + +/* Define to 1 if you have the `socket' function. */ +#undef HAVE_SOCKET + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strcasecmp' function. */ +#undef HAVE_STRCASECMP + +/* Define to 1 if you have the `strchr' function. */ +#undef HAVE_STRCHR + +/* Define to 1 if you have the `strerror' function. */ +#undef HAVE_STRERROR + +/* Define to 1 if you have the `strftime' function. */ +#undef HAVE_STRFTIME + +/* Define to 1 if you have the `stricmp' function. */ +#undef HAVE_STRICMP + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strncasecmp' function. */ +#undef HAVE_STRNCASECMP + +/* Define to 1 if you have the `strnicmp' function. */ +#undef HAVE_STRNICMP + +/* Define to 1 if you have the `strtol' function. */ +#undef HAVE_STRTOL + +/* Define to 1 if you have the `strverscmp' function. */ +#undef HAVE_STRVERSCMP + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_SYS_DIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_IOCTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_KD_H + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_SYS_NDIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SOUNDCARD_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the `vasprintf' function. */ +#undef HAVE_VASPRINTF + +/* Define to 1 if you have the header file. */ +#undef HAVE_WINDOWS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_WINSOCK2_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_WINSOCK_H + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if you can safely include both and . */ +#undef TIME_WITH_SYS_TIME + +/* Define to 1 if your declares `struct tm'. */ +#undef TM_IN_SYS_TIME + +/* Version number of package */ +#undef VERSION + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to `long' if does not define. */ +#undef off_t + +/* Define to `unsigned' if does not define. */ +#undef size_t diff --git a/configure.in b/configure.in new file mode 100644 index 000000000..f3bda6f85 --- /dev/null +++ b/configure.in @@ -0,0 +1,217 @@ +dnl Process this file with autoconf to produce a configure script. + +dnl Schism Tracker - a cross-platform Impulse Tracker clone +dnl copyright (c) 2003-2005 chisel +dnl URL: http://rigelseven.com/schism/ +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation; either version 2 of the License, or +dnl (at your option) any later version. +dnl +dnl This program is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program; if not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +if test -d schism; then + echo "You cannot ./configure from here, try something like this:" >&2 + echo " mkdir build && cd build && ../configure && make" >&2 + exit 1 +fi + + +AC_INIT + +AC_CONFIG_SRCDIR([schism/main.c]) + +dnl We'll need machine type later +AC_CANONICAL_TARGET([]) +machtype="$target_cpu" + +dnl when changing the version, also change the CWTV in audio_loadsave.cc +dnl (TODO: come up with some way to do it automatically) +AM_INIT_AUTOMAKE(schism, 1.0) +AM_CONFIG_HEADER(config.h) + +dnl ----------------------------------------------------------------------- + +dnl Check for standard programs +AC_PROG_CC +AC_PROG_CPP +AC_PROG_CXX +AC_PROG_CXXCPP +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_RANLIB + +dnl enable win32 dll libtool support +AC_LIBTOOL_WIN32_DLL + +dnl We're using C +AC_LANG([C]) + +dnl Check for SDL libs +AM_PATH_SDL(1.1.8, , AC_MSG_ERROR([*** SDL version >= 1.1.8 not found.])) + +enable_sdlstatic=no +AC_ARG_ENABLE(static-sdl, [ --enable-static-sdl Link SDL statically], enable_sdlstatic=yes,enable_sdlstatic=no) +if test "x$enable_sdlstatic" = "xyes"; then + if test "x$SDL_CONFIG" = "xno"; then + echo "*** SDL_CONFIG explicitly set to 'no' but you asked for static-sdl" + echo "*** one of those has to go..." + exit 1 + else + SDL_LIBS=`$SDL_CONFIG $sdlconf_args --static-libs` + AC_SUBST(SDL_LIBS) + fi +fi + +dnl CoreMIDI (MacosX) +AC_MSG_CHECKING(for CoreAudio/CoreMIDI Framework) +if echo "$SDL_LIBS" | grep -- -framework >/dev/null 2>&1; then + AC_MSG_RESULT(found) + SDL_LIBS="$SDL_LIBS -framework CoreAudio -framework CoreMIDI -framework IOKit -framework OpenGL" + AC_SUBST(SDL_LIBS) + + AM_CONDITIONAL([USE_MACOSX], true) + AM_CONDITIONAL([am__fastdepOBJC], true) +else + AC_MSG_RESULT(not found) + AM_CONDITIONAL([USE_MACOSX], false) + AM_CONDITIONAL([am__fastdepOBJC], false) +fi + +dnl Functions +AC_FUNC_STRFTIME +AC_CHECK_FUNCS(strchr memmove strerror strtol strcasecmp strncasecmp strverscmp stricmp strnicmp asprintf vasprintf realpath memcmp mmap socket) +AM_CONDITIONAL([NEED_GNU_ASPRINTF], [test "$ac_cv_func_asprintf" = "no"]) +AM_CONDITIONAL([NEED_GNU_VASPRINTF], [test "$ac_cv_func_vasprintf" = "no"]) +AM_CONDITIONAL([NEED_GNU_REALPATH], [test "$ac_cv_func_realpath" = "no"]) +AM_CONDITIONAL([NEED_GNU_MEMCMP], [test "$ac_cv_func_memcmp" = "no"]) +AM_CONDITIONAL([USE_MMAP], [test "$ac_cv_func_mmap" = "yes"]) + +dnl Headers, typedef crap, et al. +AC_HEADER_STDC +AC_HEADER_DIRENT +AC_HEADER_TIME +AC_CHECK_HEADERS(fcntl.h limits.h unistd.h sys/ioctl.h sys/kd.h linux/fb.h byteswap.h sys/soundcard.h) +AM_CONDITIONAL([USE_OSS], [test "$ac_cv_header_sys_soundcard_h" = yes]) + +AC_C_CONST +AC_C_INLINE +AC_TYPE_OFF_T +AC_TYPE_SIZE_T +AC_STRUCT_TM + +dnl ----------------------------------------------------------------------- + +alsa=no +AC_CHECK_LIB(asound, snd_seq_open,[alsa=yes]) +AM_CONDITIONAL([USE_ALSA], [test "$alsa" = yes]) + +x11=no +AC_CHECK_LIB(X11, XOpenDisplay,[x11=yes]) +AM_CONDITIONAL([USE_X11], [test "$x11" = yes]) +AM_CONDITIONAL([USE_ALSA], [test "$alsa" = yes]) + +alsadltrick=no +if test "$alsa" = "yes" +then if test "$oss" = "yes" +then alsadltrick=yes +fi +fi +AM_CONDITIONAL([USE_ALSA_DLTRICK], [test "$alsadltrick" = yes]) + + +AC_ARG_WITH(windres, + [ --with-windres=RSC Name of windres tool (optional)], + windres="$withval", windres="") +AM_CONDITIONAL([USE_WINDRES], [test "x$windres" != "x"]) + +WINDRES="$windres" +AC_SUBST(WINDRES) + +dnl winmm testing... +AC_CHECK_HEADERS(winsock.h winsock2.h windows.h) +if test "X$ac_cv_header_windows_h" = "Xyes" +then + AM_CONDITIONAL([USE_WIN32MM], true) + SDL_LIBS="$SDL_LIBS -lwinmm" + AC_SUBST(SDL_LIBS) +else + AM_CONDITIONAL([USE_WIN32MM], false) +fi + +if test "X$ac_cv_func_socket" = "Xyes" +then + dnl free networking + AM_CONDITIONAL([USE_NETWORK], [test "x" = "x"]) +else + AC_CHECK_HEADERS(winsock.h winsock2.h sys/socket.h) + if test "x$ac_cv_header_winsock_h" \!= "x" + then AM_CONDITIONAL([USE_NETWORK], true) + SDL_LIBS="$SDL_LIBS -lwsock32" + AC_SUBST(SDL_LIBS) + else if test "x$ac_cv_header_winsock2_h" \!= "x" + then AM_CONDITIONAL([USE_NETWORK], true) + SDL_LIBS="$SDL_LIBS -lws2_32" + AC_SUBST(SDL_LIBS) + else if test "x$ac_cv_header_sys_socket_h" \!= "x" + then AM_CONDITIONAL([USE_NETWORK], true) + SDL_LIBS="$SDL_LIBS -lsocket" + AC_SUBST(SDL_LIBS) + fi + fi + fi +fi + +#AC_CHECK_LIB(kernel32, GetConsoleMode, SDL_LIBS="$SDL_LIBS -Wl,--subsystem,console") + +dnl wee... +dnl this completely sucks... +OBJC=$CC +CFLAGS=$CFLAGS +AC_SUBST(OBJC) +AC_SUBST(OBJCFLAGS) + +dnl ----------------------------------------------------------------------- +dnl Optimizations borrowed and modified a bit from DGen. +dnl I really don't know what they all do, to be honest :) +dnl (This ought to be above AC_PROG_CC, but that causes configure to fail +dnl when all the insane warnings are are enabled.) + +AC_ARG_ENABLE(extra-opt, + AS_HELP_STRING([--enable-extra-opt], [Add extra optimizations (egcs/GCC >= 2.95 only)]), + ADD_OPT=$enableval, + ADD_OPT=no) + +AC_ARG_ENABLE(all-warnings, + AS_HELP_STRING([--enable-all-warnings], [Enable ridiculous compiler warnings (GCC)]), + ADD_WARN=$enableval, + ADD_WARN=no) + +if test x$ADD_OPT \!= xno; then + ADD_OPT="-g0 -s -O3 -ffast-math -fomit-frame-pointer -fno-exceptions" + ADD_OPT="$ADD_OPT -funroll-loops -frerun-cse-after-loop -fno-ident" + ADD_OPT="$ADD_OPT -fno-strength-reduce" + CFLAGS="$CFLAGS $ADD_OPT" + CXXFLAGS="$CXXFLAGS $ADD_OPT -fno-rtti -fno-enforce-eh-specs" +fi + +if test x$ADD_WARN \!= xno; then + ADD_WARN="-Wall -W -Winline -Wshadow -Wcast-align -Wwrite-strings" + ADD_WARN="$ADD_WARN -Waggregate-return -Wpacked" + CFLAGS="$CFLAGS $ADD_WARN -Wstrict-prototypes -Wmissing-prototypes" + CFLAGS="$CFLAGS -Wmissing-declarations -Wnested-externs" + CXXFLAGS="$CXXFLAGS $ADD_WARN" +fi + +dnl - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/font/default-lower.fnt b/font/default-lower.fnt new file mode 100644 index 000000000..ef8f2e79d Binary files /dev/null and b/font/default-lower.fnt differ diff --git a/font/default-upper-alt.fnt b/font/default-upper-alt.fnt new file mode 100644 index 000000000..c0dc3a86d Binary files /dev/null and b/font/default-upper-alt.fnt differ diff --git a/font/default-upper-itf.fnt b/font/default-upper-itf.fnt new file mode 100644 index 000000000..07ed6067e Binary files /dev/null and b/font/default-upper-itf.fnt differ diff --git a/font/half-width.fnt b/font/half-width.fnt new file mode 100644 index 000000000..11b3323b9 Binary files /dev/null and b/font/half-width.fnt differ diff --git a/helptext/global-keys b/helptext/global-keys new file mode 100644 index 000000000..277fd0052 --- /dev/null +++ b/helptext/global-keys @@ -0,0 +1,37 @@ +| Global Keys. +| F1 Help (Context sensitive!) +| Shift-F1 MIDI Screen +: Ctrl-F1 System Configuration +| F2 Pattern Editor / Pattern Editor Options +| F3 Sample List +| Ctrl-F3 Sample Library +| F4 Instrument List +| Ctrl-F4 Instrument Library +| F5 Play Information / Play song +| Ctrl-F5 Play Song +| F6 Play current pattern +| Shift-F6 Play song from current Order +| F7 Play from mark / current row +| F8 Stop Playback +| F9 Load Module +| Shift-F9 Message Editor +| F10 Save Module +| F11 Order List and Panning +| 2*F11 Order List and Channel Volume +: Ctrl-F11 Schism Logging +| F12 Song Variables & Directory Configuration +| Ctrl-F12 Palette Configuration +| +| { } Decrease/Increase playback speed +| [ ] Decrease/Increase global volume +| Alt-F1 -> Alt-F8 Toggle channels 1->8 +| +! Ctrl-D DOS Shell +| Ctrl-E Refresh screen and reset cache identification +| Ctrl-I Reinitialise sound driver +| Ctrl-M Toggle mouse cursor +| Ctrl-N New Song +! Ctrl-Q Quit to DOS +: Ctrl-Q Quit Schism Tracker +| Ctrl-S Save current song +: Ctrl-G Go to order/pattern/row given time diff --git a/helptext/index b/helptext/index new file mode 100644 index 000000000..b8f36268c --- /dev/null +++ b/helptext/index @@ -0,0 +1,9 @@ +global-keys GLOBAL +pattern-editor PATTERN_EDITOR +sample-list SAMPLE_LIST +instrument-list INSTRUMENT_LIST +info-page INFO_PAGE +message-editor MESSAGE_EDITOR +orderlist-pan ORDERLIST_PANNING +orderlist-vol ORDERLIST_VOLUME +midi-output MIDI_OUTPUT diff --git a/helptext/info-page b/helptext/info-page new file mode 100644 index 000000000..ea140b621 --- /dev/null +++ b/helptext/info-page @@ -0,0 +1,23 @@ +| ‹†††††††††††††Š +| „ Info Page ‘ +| ‰– +| +| Insert Add a new window +| Delete Delete current window +| Tab/Shift-Tab Move between windows +| Up/Dn/Left/Right Move highlighted channel +| PgUp/PgDn Change window type +| Alt-Up/Down Move window base up/down +| +| V Toggle between volume/velocity bars +| I Toggle between sample/instrument names +| +| Q Mute/Unmute current channel +| S Solo current channel +| +| Grey +, Grey - Move forwards/backwards one pattern in song +| +| Alt-S Toggle Stereo playback +| Alt-R Reverse output channels +| +| G Goto pattern currently playing diff --git a/helptext/instrument-list b/helptext/instrument-list new file mode 100644 index 000000000..d93105131 --- /dev/null +++ b/helptext/instrument-list @@ -0,0 +1,45 @@ +| ‹†††††††††††††††††††††Š +| „ Instrument List ‘ +| ‰– +| +| Instrument List Keys. +| Enter Load new instrument +| Ctrl-PgUp/PgDn Move instrument up/down (when not on list) +: Alt-B Pre-Loop cut envelope +| Alt-C Clear instrument name & filename +| Alt-W Wipe instrument data +| Spacebar Edit instrument name (ESC to exit) +| +| Alt-D Delete instrument & all related samples +: Alt-L Post-Loop cut envelope +| Alt-N Toggle Multichannel playback +| Alt-O Save current instrument to disk +| Alt-P Copy instrument +| Alt-R Replace current instrument in song +| Alt-S Swap instruments (in song also) +| Alt-U Update pattern data +| Alt-X Exchange instruments (only in Instrument List) +| +| Alt-Ins Insert instrument slot (updates pattern data) +| Alt-Del Remove instrument slot (updates pattern data) +| +| < > Decrease/Increase playback channel +| +| Note Translation. +| Enter Pickup sample number & default play note +| < > Decrease/Increase sample number +| +| Alt-A Change all samples +| Alt-N Enter next note +| Alt-P Enter previous note +| Alt-Up/Down Transpose all notes a semitone up/down +| Alt-Ins/Del Insert/Delete a row from the table +| +| Envelope Keys. +| Enter Pick up/Drop current node +| Insert Add node +| Delete Delete node +| Alt-Arrow Keys Move node (fast) +| +| Press Spacebar Play default note +| Release Space Note off command diff --git a/helptext/message-editor b/helptext/message-editor new file mode 100644 index 000000000..5939fbf6e --- /dev/null +++ b/helptext/message-editor @@ -0,0 +1,9 @@ +| ‹††††††††††††††††††Š +| „ Message Editor ‘ +| ‰– +| +| Enter / ESC Edit message / finished editing +| +| Editing Keys. +| Ctrl-Y Delete line +| Alt-C Clear message diff --git a/helptext/midi-output b/helptext/midi-output new file mode 100644 index 000000000..23d4922ab --- /dev/null +++ b/helptext/midi-output @@ -0,0 +1,134 @@ +| ‹†††††††††††††††Š +| „ MIDI Output ‘ +| ‰– +| +| MIDI output causes MIDI data to be sent to all enabled output ports on the +| MIDI configuration page (Shift-F1) whenever the player sees an event. +| +| The data actually sent is the result of a macro configuration on the MIDI +| Output page. +| +| Each event is described below: +| +| MIDI Start  Player begins +| MIDI Stop  Player stops playing +| MIDI Tick  Every row(?) +| Note On  Every note recorded +| Note Off  Every note off (including NNA note-off) +| Change Volume  Volume change (more like aftertouch) +| Bank Select  Bank change +| Program Change  Program change +| SF0 - SFF  When a Z00-Z7F is seen in the same IT-channel +| Z80 - Z8F  When played +| +| These events are written in UPPERCASE hexadecimal, with LOWERCASE variable +| substitution. The variables are: +| +| a  Coarse bank/change (only available on Bank Select) +| b  Fine bank/change (only available on Bank Select) +| c  Selected MIDI channel +| n  Selected MIDI note (Note On events only) +| p  Program setting (Program Change events only) +| v  Velocity (initial volume) +| u  Current volume +| x  Set Panning +| y  Calculated Panning (includes panning envelope) +| z  MIDI Macro (Z00-Z7F commands) +| +| MIDI messages are normally three-bytes (except for System Exclusive +| messages). A new "message" begins with a byte having it's high-bit set. +| This means that the first byte is going to be between 0x80 and 0xFF. +| +| System Exclusive (SysEx) messages begin with a 0xF0 and end with a 0xF7 +| byte. Schism/Impulse Tracker the following SysEx messages internally: +| +| F0 F0 00 qq F7  Set the current filter cutoff point to be qq +| F0 F0 01 qq F7  Set the current filter resonance to be qq +| +| You'll generally need the programming guide for your MIDI synthesizers to +| come up with useful values. You do not have to include the F7 as Schism +| Tracker will automatically terminate SysEx messages. +| +| Other MIDI messages include: +| 8c n v  Note-off for channel "c", note "n" +| 9c n v  Note-on for channel "c", note "n", velocity "v" +| Ac n v  Aftertouch (adjust velocity) +| Bc q z  Set MIDI controller "q" on channel "c", to value "z" +| Cc p  Program change channel "c" to "p" +| Dc z  Set total key pressure to "z" +| Ec q q  Pitch Wheel; q q is a 14-bit value. +| F8  MIDI "Clock" operation +| FA  Start MIDI +| FB  Continue MIDI +| FC  Stop MIDI +| FF  Reset +| +| There are other MIDI messages, but they are unlikely to be useful with +| Impulse or Schism Tracker. +| +| Nevertheless, your programming guide will be authoritative. +| +| +| Controllers (Bc q z) are reasonably common. They generally come in two +| flavors: a "coarse" or "high byte" setting, and "fine" or "low byte" +| setting. If a listed controller only comes in one kind, the values will be +| listed only for coarse adjustment. +| +| The names listed below are likely to be similar to the names on your +| synthesizer. +| +| Coarse Fine Description +| +| 00 20 Bank Select +| 01 21 Modulation Wheel (MOD Wheel) +| 02 22 Breath Controller +| 04 24 Foot Pedal +| 05 25 Portamento Time +| 06 26 Data Entry/Slider +| 07 27 Volume +| 08 28 Balance/Panning +| 0A 2A Panning Position +| 0B 2B (Volume) Expression +| 0C 2C Effect Control 1 +| 0D 2D Effect Control 2 +| 10 General Purpose Slider 1 +| 11 General Purpose Slider 2 +| 12 General Purpose Slider 3 +| 13 General Purpose Slider 4 +| 40 Hold Pedal +| 41 Portamento On/Off +| 42 Sustenuto +| 43 Soft Pedal +| 44 Legato Pedal +| 45 Hold 2 Pedal +| 46 Sound Variation +| 47 Sound Timbre +| 48 Sound Release Time +| 49 Sound Attack Time +| 4A Sound Brightness +| 4B Sound Control 6 +| 4C Sound Control 7 +| 4D Sound Control 8 +| 4E Sound Control 9 +| 4F Sound Control 10 +| 50 General Purpose Button 1 +| 51 General Purpose Button 2 +| 52 General Purpose Button 3 +| 53 General Purpose Button 4 +| 5B Effects Level +| 5C Tremulo Level +| 5D Chorus Level +| 5E Celeste Level +| 5F Phaser Level +| 60 Data Button Increment +| 61 Data Button Descrement +| 63 62 Non-Registered Parameter Number (NRPN) +| 65 64 Registered Parameter Number (RPN) +| 78 All Sound Off +| 79 All Controllers Off +| 7A Local Keyboard On/Off +| 7B All Notes Off +| 7C Omni Mode Off +| 7D Omni Mode On +| 7E Monophonic Operation +| 7F Polyphonic Operation diff --git a/helptext/orderlist-pan b/helptext/orderlist-pan new file mode 100644 index 000000000..512f1a0b9 --- /dev/null +++ b/helptext/orderlist-pan @@ -0,0 +1,21 @@ +| ‹††††††††††††††††††††††††††Š +| „ Order List and Panning ‘ +| ‰– +| +| Order Keys. +| N Insert next pattern +| - End of song mark +| + Skip to next Order mark +| Ins Insert a pattern +| Del Delete a pattern +| Tab/Shift-Tab Move to next window +| Ctrl-F7 Play this Order next +: +: Ctrl-B Link (diskwriter) this pattern to the current sample +: Ctrl-O Copy (diskwriter) this pattern to the current sample +: +: Alt-Enter Save orderlist +: Alt-Backspace Swap orderlist with saved orderlist +| +| Panning Keys. +| L/M/R/S Set panning to Left/Middle/Right/Surround diff --git a/helptext/orderlist-vol b/helptext/orderlist-vol new file mode 100644 index 000000000..ca244aa27 --- /dev/null +++ b/helptext/orderlist-vol @@ -0,0 +1,18 @@ +| ‹†††††††††††††††††††††††††††††††††Š +| „ Order List and Channel Volume ‘ +| ‰– +| +| Order Keys. +| N Insert next pattern +| - End of song mark +| + Skip to next Order mark +| Ins Insert a pattern +| Del Delete a pattern +| Tab/Shift-Tab Move to next window +| Ctrl-F7 Play this Order next +: +: Ctrl-B Link (diskwriter) this pattern to the current sample +: Ctrl-O Copy (diskwriter) this pattern to the current sample +: +: Alt-Enter Save orderlist +: Alt-Backspace Swap orderlist with saved orderlist diff --git a/helptext/pattern-editor b/helptext/pattern-editor new file mode 100644 index 000000000..aad8f5af1 --- /dev/null +++ b/helptext/pattern-editor @@ -0,0 +1,243 @@ +| ‹††††††††††††††††Š +| „ Pattern Edit ‘ +| ‰– +| +| Summary of Effects. +| +| Volume Column effects. +| Ax Fine volume slide up by x +| Bx Fine volume slide down by x +| Cx Volume slide up by x +| Dx Volume slide down by x +| Ex Pitch slide down by x +| Fx Pitch slide up by x +| Gx Slide to note with speed x +| Hx Vibrato with depth x +| +| General effects. +| Axx Set song speed (hex) +| Bxx Jump to Order (hex) +| Cxx Break to row xx (hex) of next pattern +| D0x Volume slide down by x +| Dx0 Volume slide up by x +| DFx Fine volume slide down by x +| DxF Fine volume slide up by x +| Exx Pitch slide down by xx +| EFx Fine pitch slide down by x +| EEx Extra fine pitch slide down by x +| Fxx Pitch slide up by xx +| FFx Fine pitch slide up by x +| FEx Extra fine pitch slide up by x +| Gxx Slide to note with speed xx +| Hxy Vibrato with speed x, depth y +| Ixy Tremor with ontime x and offtime y +| Jxy Arpeggio with halftones x and y +| Kxx Dual Command: H00 & Dxx +| Lxx Dual Command: G00 & Dxx +| Mxx Set channel volume to xx (0->40h) +| N0x Channel volume slide down by x +| Nx0 Channel volume slide up by x +| NFx Fine channel volume slide down by x +| NxF Fine channel volume slide up by x +| Oxx Set sample offset to yxx00h, y set with SAy +| P0x Panning slide to right by x +| Px0 Panning slide to left by x +| PFx Fine panning slide to right by x +| PxF Fine panning slide to left by x +| Qxy Retrigger note every y ticks with volume modifier x +| Values for x: +| 0: No volume change 8: Not used +| 1: -1 9: +1 +| 2: -2 A: +2 +| 3: -4 B: +4 +| 4: -8 C: +8 +| 5: -16 D: +16 +| 6: *2/3 E: *3/2 +| 7: *1/2 F: *2 +| Rxy Tremolo with speed x, depth y +# S0x Set filter +# S1x Set glissando control +# S2x Set finetune +| S3x Set vibrato waveform to type x +| S4x Set tremolo waveform to type x +| S5x Set panbrello waveform to type x +| Waveforms for commands S3x, S4x and S5x: +| 0: Sine wave +| 1: Ramp down +| 2: Square wave +| 3: Random wave +| S6x Pattern delay for x ticks +| S70 Past note cut +| S71 Past note off +| S72 Past note fade +| S73 Set NNA to note cut +| S74 Set NNA to continue +| S75 Set NNA to note off +| S76 Set NNA to note fade +| S77 Turn off volume envelope +| S78 Turn on volume envelope +| S79 Turn off panning envelope +| S7A Turn on panning envelope +| S7B Turn off pitch envelope +| S7C Turn on pitch envelope +| S8x Set panning position +| S91 Set surround sound +| SAy Set high value of sample offset yxx00h +| SB0 Set loopback point +| SBx Loop x times to loopback point +| SCx Note cut after x ticks +| SDx Note delay for x ticks +| SEx Pattern delay for x rows +| SFx Set parameterised MIDI Macro +| T0x Tempo slide down by x +| T1x Tempo slide up by x +| Txx Set Tempo to xx (20h->0FFh) +| Uxy Fine vibrato with speed x, depth y +| Vxx Set global volume to xx (0->80h) +| W0x Global volume slide down by x +| Wx0 Global volume slide up by x +| WFx Fine global volume slide down by x +| WxF Fine global volume slide up by x +| Xxx Set panning position (0->0FFh) +| Yxy Panbrello with speed x, depth y +| Zxx MIDI Macros +| +: FT2 effect translations (can only be saved in XM modules) +: +: Volume column. +: $x Set vibrato speed to x [$A0-$AF] +: x Panning slide to right by x [$E0-$EF] +: +: General effects. +: !xx Set volume [Cxx] - ? +: #1x Fine pitch slide up by x [E1x] - FFx +: #2x Fine pitch slide down by x [E2x] - EFx +: #3x Set glissando control [E3x] - S1x (unimplemented) +: #4x Set vibrato control [E4x] - S3x +: #5x Set finetune [E5x] - S2x (unimplemented) +: #6x Set loop begin / loop [E6x] - SBx +: #7x Set tremolo control [E7x] - S4x +: #9x Retrigger note every x ticks [E9x] - Q0x +: #Ax Fine volume slide up by x [EAx] - DxF +: #Bx Fine volume slide down by x [EBx] - DFx +: #Cx Note cut after x ticks [ECx] - SCx +: #Dx Note delay for x ticks [EDx] - SDx +: #Ex Pattern delay for x rows [EEx] - SEx +: $xx Key off [Kxx] - ? +: %1x Extra fine pitch slide up by x [X1x] - FEx +: %2x Extra fine pitch slide down by x [X2x] - EEx +: &xx Set envelope position [Lxx] - ? +: +% +| +| Pattern Edit Keys. +| Grey +,- Next/Previous pattern (*) +| Shift +,- Next/Previous 4 pattern (*) +| Ctrl +,- Next/Previous order's pattern (*) +| 0-9 Change octave/volume/instrument +| 0-9, A-F Change effect value +| A-Z Change effect +| . (Period) Clear field(s) +| 1 Note cut (^^^) +| ` Note off (###) / Panning Toggle +| Spacebar Use last note/instrument/volume/effect/effect value +| Caps Lock+Key Preview note +| +| Enter Get default note/instrument/volume/effect +| < or Ctrl-Up Decrease instrument +| > or Ctrl-Down Increase instrument +| Grey /,* Decrease/Increase octave +| , (Comma) Toggle edit mask for current field +| +| Ins/Del Insert/Delete a row to/from current channel +| Alt-Ins/Del Insert/Delete an entire row to/from pattern (*) +| +| Up/Down Move up/down by the skipvalue (set with Alt 0-9) +| Ctrl-Home/End Move up/down by 1 row +| Alt-Up/Down Slide pattern up/down by 1 row +| Left/Right Move cursor left/right +| Alt-Left/Right Move forwards/backwards one channel +| Tab/Shift-Tab Move forwards/backwards to note column +| PgUp/PgDn Move up/down n lines (n=Row Hilight Major) +| Ctrl-PgUp/PgDn Move to top/bottom of pattern +| Home Move to start of column/start of line/start of pattern +| End Move to end of column/end of line/end of pattern +| Backspace Move to previous position (accounts for Multichannel) +| +| Alt-N Toggle Multichannel mode for current channel +| 2*Alt-N Multichannel Selection menu +| +| Alt-Enter Store pattern data +| Alt-Backspace Revert pattern data (*) +| Ctrl-Backspace Undo - any function with (*) can be undone +| +| Ctrl-C Toggle centralise cursor +| Ctrl-H Toggle current row hilight +| Ctrl-V Toggle default volume display +| +: Ctrl-D Toggle DigiTrakker voodoo (# -> b) +: +| Ctrl-F2 Set pattern length +| +| Track View Functions. +| Alt-T Cycle current track's view +| Alt-R Clear all track views +| Alt-H Toggle track view divisions +| Ctrl-0 Deselect current track +| Ctrl-1 - Ctrl-5 View current track in scheme 1-5 +| Ctrl-Left/Right Move left/right between track view columns +| +| L-Ctrl&Shift 1-4 Quick view scheme setup +| +| Ctrl-T Toggle View-Channel cursor-tracking +| +| Block Functions. +| Alt-B Mark beginning of block +| Alt-E Mark end of block +| Alt-D Quick mark n/2n/4n/... lines (n=Row Hilight Major) +| Alt-L Mark entire column/pattern +| Shift-Arrows Mark block +| +| Alt-U Unmark block/Release clipboard memory +| +| Alt-Q Raise notes by a semitone (*) +| Alt-A Lower notes by a semitone (*) +| Alt-S Set Instrument (*) +| Alt-V Set volume/panning (*) +| Alt-W Wipe vol/pan not associated with a note/instrument (*) +| Alt-K Slide volume/panning column (*) +| 2*Alt-K Wipe all volume/panning controls (*) +| Alt-J Volume amplifier (*) / Fast volume attenuate (*) +| Alt-Z Cut block (*) +| Alt-X Slide effect value (*) +| 2*Alt-X Wipe all effect data (*) +| +| Alt-C Copy block into clipboard +| Alt-P Paste data from clipboard (*) +| Alt-O Overwrite with data from clipboard (*) +| Alt-M Mix each row from clipboard with pattern data (*) +| 2*Alt-M Mix each field from clipboard with pattern data +| +| Alt-F Double block length (*) +| Alt-G Halve block length (*) +| +| Alt-I Select Template mode / Fast volume amplify (*) +| Ctrl-J Toggle fast volume mode +: Ctrl-U Selection volume vary / Fast volume vary (*) +: Ctrl-Y Selection panning vary / Fast panning vary (*) +: Ctrl-K Selection effect vary / Fast effect vary (*) +| +| Playback Functions. +| 4 Play note under cursor +| 8 Play row +| +| Ctrl-F6 Play from current row +| Ctrl-F7 Set/Clear playback mark (for use with F7) +| +| Alt-F9 Toggle current channel +| Alt-F10 Solo current channel +| +| Scroll Lock Toggle playback tracing +| Ctrl-Z Change MIDI playback trigger +| Alt-Scroll Lock Toggle MIDI input diff --git a/helptext/sample-list b/helptext/sample-list new file mode 100644 index 000000000..ca3c5ae66 --- /dev/null +++ b/helptext/sample-list @@ -0,0 +1,38 @@ +| ‹†††††††††††††††††Š +| „ Sample List ‘ +| ‰– +| +| Sample List Keys. +| Enter Load new sample +| Tab Move between options +| PgUp/PgDn Move up/down (when not on list) +| +| Alt-A Convert Signed to/from Unsigned samples +| Alt-B Pre-Loop cut sample +| Alt-C Clear Sample Name & Filename (Used in Sample Name window) +| Alt-D Delete Sample +| Alt-E Resize Sample (with interpolation) +| Alt-F Resize Sample (without interpolation) +| Alt-G Reverse Sample +| Alt-H Centralise Sample +: Alt-I Invert Sample +| Alt-L Post-Loop cut sample +| Alt-M Sample amplifier +| Alt-N Toggle Multichannel playback +| Alt-O Save current sample to disk (IT Format) +| Alt-Q Toggle sample quality +| Alt-R Replace current sample in song +| Alt-S Swap sample (in song also) +| Alt-T Save current sample to disk (Export Format) +| Alt-W Save current sample to disk (RAW Format) +| Alt-X Exchange sample (only in Sample List) +| +| Alt-Ins Insert sample slot (updates pattern data) +| Alt-Del Remove sample slot (updates pattern data) +| +| < > Decrease/Increase playback channel +| +| Alt-Grey + Increase C-5 Frequency by 1 octave +| Alt-Grey - Decrease C-5 Frequency by 1 octave +| Ctrl-Grey + Increase C-5 Frequency by 1 semitone +| Ctrl-Grey - Decrease C-5 Frequency by 1 semitone diff --git a/icons/appIcon.icns b/icons/appIcon.icns new file mode 100644 index 000000000..8ed363793 Binary files /dev/null and b/icons/appIcon.icns differ diff --git a/icons/it_logo.png b/icons/it_logo.png new file mode 100644 index 000000000..35d0d6e9d Binary files /dev/null and b/icons/it_logo.png differ diff --git a/icons/moduleIcon.icns b/icons/moduleIcon.icns new file mode 100644 index 000000000..597056bf1 Binary files /dev/null and b/icons/moduleIcon.icns differ diff --git a/icons/schism-file-128.png b/icons/schism-file-128.png new file mode 100644 index 000000000..f9be91c46 Binary files /dev/null and b/icons/schism-file-128.png differ diff --git a/icons/schism-icon-128.png b/icons/schism-icon-128.png new file mode 100644 index 000000000..7d59c308f Binary files /dev/null and b/icons/schism-icon-128.png differ diff --git a/icons/schism-icon-16.png b/icons/schism-icon-16.png new file mode 100644 index 000000000..846cecd1d Binary files /dev/null and b/icons/schism-icon-16.png differ diff --git a/icons/schism-icon-192.png b/icons/schism-icon-192.png new file mode 100644 index 000000000..dc7a85212 Binary files /dev/null and b/icons/schism-icon-192.png differ diff --git a/icons/schism-icon-22.png b/icons/schism-icon-22.png new file mode 100644 index 000000000..8f77e87ce Binary files /dev/null and b/icons/schism-icon-22.png differ diff --git a/icons/schism-icon-24.png b/icons/schism-icon-24.png new file mode 100644 index 000000000..14680377a Binary files /dev/null and b/icons/schism-icon-24.png differ diff --git a/icons/schism-icon-32.png b/icons/schism-icon-32.png new file mode 100644 index 000000000..0e35e6954 Binary files /dev/null and b/icons/schism-icon-32.png differ diff --git a/icons/schism-icon-36.png b/icons/schism-icon-36.png new file mode 100644 index 000000000..8e1bd653e Binary files /dev/null and b/icons/schism-icon-36.png differ diff --git a/icons/schism-icon-48.png b/icons/schism-icon-48.png new file mode 100644 index 000000000..651e980a3 Binary files /dev/null and b/icons/schism-icon-48.png differ diff --git a/icons/schism-icon-64.png b/icons/schism-icon-64.png new file mode 100644 index 000000000..ccc27723a Binary files /dev/null and b/icons/schism-icon-64.png differ diff --git a/icons/schism-icon-72.png b/icons/schism-icon-72.png new file mode 100644 index 000000000..2f75449a0 Binary files /dev/null and b/icons/schism-icon-72.png differ diff --git a/icons/schism-icon-96.png b/icons/schism-icon-96.png new file mode 100644 index 000000000..b64484b81 Binary files /dev/null and b/icons/schism-icon-96.png differ diff --git a/icons/schism-icon.svg b/icons/schism-icon.svg new file mode 100644 index 000000000..08293d86f --- /dev/null +++ b/icons/schism-icon.svg @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/schism-itf-icon-128.png b/icons/schism-itf-icon-128.png new file mode 100644 index 000000000..0beab1cb9 Binary files /dev/null and b/icons/schism-itf-icon-128.png differ diff --git a/icons/schism-itf-icon-16.png b/icons/schism-itf-icon-16.png new file mode 100644 index 000000000..0078cb386 Binary files /dev/null and b/icons/schism-itf-icon-16.png differ diff --git a/icons/schism-itf-icon-192.png b/icons/schism-itf-icon-192.png new file mode 100644 index 000000000..a3d705029 Binary files /dev/null and b/icons/schism-itf-icon-192.png differ diff --git a/icons/schism-itf-icon-22.png b/icons/schism-itf-icon-22.png new file mode 100644 index 000000000..6dfc21262 Binary files /dev/null and b/icons/schism-itf-icon-22.png differ diff --git a/icons/schism-itf-icon-24.png b/icons/schism-itf-icon-24.png new file mode 100644 index 000000000..f1cf9e2cf Binary files /dev/null and b/icons/schism-itf-icon-24.png differ diff --git a/icons/schism-itf-icon-32.png b/icons/schism-itf-icon-32.png new file mode 100644 index 000000000..adb8f03e0 Binary files /dev/null and b/icons/schism-itf-icon-32.png differ diff --git a/icons/schism-itf-icon-36.png b/icons/schism-itf-icon-36.png new file mode 100644 index 000000000..c8a52f3bd Binary files /dev/null and b/icons/schism-itf-icon-36.png differ diff --git a/icons/schism-itf-icon-48.png b/icons/schism-itf-icon-48.png new file mode 100644 index 000000000..c8e0ac2d8 Binary files /dev/null and b/icons/schism-itf-icon-48.png differ diff --git a/icons/schism-itf-icon-64.png b/icons/schism-itf-icon-64.png new file mode 100644 index 000000000..155a4b1ef Binary files /dev/null and b/icons/schism-itf-icon-64.png differ diff --git a/icons/schism-itf-icon-72.png b/icons/schism-itf-icon-72.png new file mode 100644 index 000000000..8785b4473 Binary files /dev/null and b/icons/schism-itf-icon-72.png differ diff --git a/icons/schism-itf-icon-96.png b/icons/schism-itf-icon-96.png new file mode 100644 index 000000000..4bec6283c Binary files /dev/null and b/icons/schism-itf-icon-96.png differ diff --git a/icons/schism-itf-icon.svg b/icons/schism-itf-icon.svg new file mode 100644 index 000000000..32f6923a6 --- /dev/null +++ b/icons/schism-itf-icon.svg @@ -0,0 +1,370 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + F diff --git a/icons/schism_logo.png b/icons/schism_logo.png new file mode 100644 index 000000000..7e99a5cf3 Binary files /dev/null and b/icons/schism_logo.png differ diff --git a/icons/schismres.ico b/icons/schismres.ico new file mode 100644 index 000000000..7d209c090 Binary files /dev/null and b/icons/schismres.ico differ diff --git a/include/acc.h b/include/acc.h new file mode 100644 index 000000000..a4ade4b70 --- /dev/null +++ b/include/acc.h @@ -0,0 +1,65 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __acc_h +#define __acc_h + +struct acc_state_info { + int type, chord, table, base, solo; + char chord_str[48]; +}; +struct acc_harmonize_info { + char name[16]; + int notes, dist; +}; +struct acc_chordt_table { + char name[16]; + int a3, a6, a7; +}; +struct acc_chord3_table { + int type, base, di1, di2, inv2, inv3; +}; +struct acc_chord4_table { + int type, base, di1, di2, di3, inv2, inv3; +}; +struct acc_chord5_table { + int type, base, di1, di2, di3, di4; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* called by keydown/keyup if the capslock key is down when they're called */ +void acc_state_keys(struct acc_state_info *nn, int ch[64]); + + +/* change current harmony */ +void acc_set_harmonize_next(void); +void acc_set_harmonize_previous(void); + + +#ifdef __cplusplus +}; +#endif + +extern struct acc_state_info *acc_current; +extern struct acc_harmonize_info *acc_harmonize; + +#endif diff --git a/include/auto/default-font.h b/include/auto/default-font.h new file mode 100644 index 000000000..cdd08c6a1 --- /dev/null +++ b/include/auto/default-font.h @@ -0,0 +1,269 @@ +/* this file should only be included by draw-char.c */ +static unsigned const char font_default_lower[] = { +'\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\176', '\201', '\245', '\201', '\275', '\231', '\201', '\176', +'\176', '\377', '\333', '\377', '\303', '\347', '\377', '\176', '\154', '\376', '\376', '\376', '\174', '\070', '\020', '\000', +'\020', '\070', '\174', '\376', '\174', '\070', '\020', '\000', '\070', '\174', '\070', '\376', '\376', '\174', '\070', '\174', +'\020', '\020', '\070', '\174', '\376', '\174', '\070', '\174', '\000', '\000', '\030', '\074', '\074', '\030', '\000', '\000', +'\377', '\377', '\347', '\303', '\303', '\347', '\377', '\377', '\000', '\074', '\146', '\102', '\102', '\146', '\074', '\000', +'\377', '\303', '\231', '\275', '\275', '\231', '\303', '\377', '\017', '\007', '\017', '\175', '\314', '\314', '\314', '\170', +'\074', '\146', '\146', '\146', '\074', '\030', '\176', '\030', '\077', '\063', '\077', '\060', '\060', '\160', '\360', '\340', +'\177', '\143', '\177', '\143', '\143', '\147', '\346', '\300', '\231', '\132', '\074', '\347', '\347', '\074', '\132', '\231', +'\200', '\340', '\370', '\376', '\370', '\340', '\200', '\000', '\002', '\016', '\076', '\376', '\076', '\016', '\002', '\000', +'\030', '\074', '\176', '\030', '\030', '\176', '\074', '\030', '\146', '\146', '\146', '\146', '\146', '\000', '\146', '\000', +'\177', '\333', '\333', '\173', '\033', '\033', '\033', '\000', '\076', '\143', '\070', '\154', '\154', '\070', '\314', '\170', +'\000', '\000', '\000', '\000', '\176', '\176', '\176', '\000', '\030', '\074', '\176', '\030', '\176', '\074', '\030', '\377', +'\030', '\074', '\176', '\030', '\030', '\030', '\030', '\000', '\030', '\030', '\030', '\030', '\176', '\074', '\030', '\000', +'\000', '\030', '\014', '\376', '\014', '\030', '\000', '\000', '\000', '\060', '\140', '\376', '\140', '\060', '\000', '\000', +'\000', '\000', '\300', '\300', '\300', '\376', '\000', '\000', '\000', '\044', '\146', '\377', '\146', '\044', '\000', '\000', +'\000', '\030', '\074', '\176', '\377', '\377', '\000', '\000', '\000', '\377', '\377', '\176', '\074', '\030', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\060', '\170', '\170', '\060', '\060', '\000', '\060', '\000', +'\154', '\154', '\154', '\000', '\000', '\000', '\000', '\000', '\154', '\154', '\376', '\154', '\376', '\154', '\154', '\000', +'\060', '\174', '\300', '\170', '\014', '\370', '\060', '\000', '\000', '\306', '\314', '\030', '\060', '\146', '\306', '\000', +'\070', '\154', '\070', '\166', '\334', '\314', '\166', '\000', '\140', '\140', '\300', '\000', '\000', '\000', '\000', '\000', +'\030', '\060', '\140', '\140', '\140', '\060', '\030', '\000', '\140', '\060', '\030', '\030', '\030', '\060', '\140', '\000', +'\000', '\146', '\074', '\377', '\074', '\146', '\000', '\000', '\000', '\060', '\060', '\374', '\060', '\060', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\060', '\060', '\140', '\000', '\000', '\000', '\374', '\000', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\060', '\060', '\000', '\006', '\014', '\030', '\060', '\140', '\300', '\200', '\000', +'\174', '\306', '\316', '\336', '\366', '\346', '\174', '\000', '\060', '\160', '\060', '\060', '\060', '\060', '\374', '\000', +'\170', '\314', '\014', '\070', '\140', '\314', '\374', '\000', '\170', '\314', '\014', '\070', '\014', '\314', '\170', '\000', +'\034', '\074', '\154', '\314', '\376', '\014', '\036', '\000', '\374', '\300', '\370', '\014', '\014', '\314', '\170', '\000', +'\070', '\140', '\300', '\370', '\314', '\314', '\170', '\000', '\374', '\314', '\014', '\030', '\060', '\060', '\060', '\000', +'\170', '\314', '\314', '\170', '\314', '\314', '\170', '\000', '\170', '\314', '\314', '\174', '\014', '\030', '\160', '\000', +'\000', '\060', '\060', '\000', '\000', '\060', '\060', '\000', '\000', '\060', '\060', '\000', '\000', '\060', '\060', '\140', +'\030', '\060', '\140', '\300', '\140', '\060', '\030', '\000', '\000', '\000', '\374', '\000', '\000', '\374', '\000', '\000', +'\140', '\060', '\030', '\014', '\030', '\060', '\140', '\000', '\170', '\314', '\014', '\030', '\060', '\000', '\060', '\000', +'\174', '\306', '\336', '\336', '\336', '\300', '\170', '\000', '\060', '\170', '\314', '\314', '\374', '\314', '\314', '\000', +'\374', '\146', '\146', '\174', '\146', '\146', '\374', '\000', '\074', '\146', '\300', '\300', '\300', '\146', '\074', '\000', +'\370', '\154', '\146', '\146', '\146', '\154', '\370', '\000', '\376', '\142', '\150', '\170', '\150', '\142', '\376', '\000', +'\376', '\142', '\150', '\170', '\150', '\140', '\360', '\000', '\074', '\146', '\300', '\300', '\316', '\146', '\076', '\000', +'\314', '\314', '\314', '\374', '\314', '\314', '\314', '\000', '\170', '\060', '\060', '\060', '\060', '\060', '\170', '\000', +'\036', '\014', '\014', '\014', '\314', '\314', '\170', '\000', '\346', '\146', '\154', '\170', '\154', '\146', '\346', '\000', +'\360', '\140', '\140', '\140', '\142', '\146', '\376', '\000', '\306', '\356', '\376', '\376', '\326', '\306', '\306', '\000', +'\306', '\346', '\366', '\336', '\316', '\306', '\306', '\000', '\070', '\154', '\306', '\306', '\306', '\154', '\070', '\000', +'\374', '\146', '\146', '\174', '\140', '\140', '\360', '\000', '\170', '\314', '\314', '\314', '\334', '\170', '\034', '\000', +'\374', '\146', '\146', '\174', '\154', '\146', '\346', '\000', '\170', '\314', '\340', '\160', '\034', '\314', '\170', '\000', +'\374', '\264', '\060', '\060', '\060', '\060', '\170', '\000', '\314', '\314', '\314', '\314', '\314', '\314', '\374', '\000', +'\314', '\314', '\314', '\314', '\314', '\170', '\060', '\000', '\306', '\306', '\306', '\326', '\376', '\356', '\306', '\000', +'\306', '\306', '\154', '\070', '\070', '\154', '\306', '\000', '\314', '\314', '\314', '\170', '\060', '\060', '\170', '\000', +'\376', '\306', '\214', '\030', '\062', '\146', '\376', '\000', '\170', '\140', '\140', '\140', '\140', '\140', '\170', '\000', +'\300', '\140', '\060', '\030', '\014', '\006', '\002', '\000', '\170', '\030', '\030', '\030', '\030', '\030', '\170', '\000', +'\020', '\070', '\154', '\306', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\377', +'\060', '\060', '\030', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\170', '\014', '\174', '\314', '\166', '\000', +'\340', '\140', '\140', '\174', '\146', '\146', '\334', '\000', '\000', '\000', '\170', '\314', '\300', '\314', '\170', '\000', +'\034', '\014', '\014', '\174', '\314', '\314', '\166', '\000', '\000', '\000', '\170', '\314', '\374', '\300', '\170', '\000', +'\070', '\154', '\140', '\360', '\140', '\140', '\360', '\000', '\000', '\000', '\166', '\314', '\314', '\174', '\014', '\370', +'\340', '\140', '\154', '\166', '\146', '\146', '\346', '\000', '\060', '\000', '\160', '\060', '\060', '\060', '\170', '\000', +'\014', '\000', '\014', '\014', '\014', '\314', '\314', '\170', '\340', '\140', '\146', '\154', '\170', '\154', '\346', '\000', +'\160', '\060', '\060', '\060', '\060', '\060', '\170', '\000', '\000', '\000', '\314', '\376', '\376', '\326', '\306', '\000', +'\000', '\000', '\370', '\314', '\314', '\314', '\314', '\000', '\000', '\000', '\170', '\314', '\314', '\314', '\170', '\000', +'\000', '\000', '\334', '\146', '\146', '\174', '\140', '\360', '\000', '\000', '\166', '\314', '\314', '\174', '\014', '\036', +'\000', '\000', '\334', '\166', '\146', '\140', '\360', '\000', '\000', '\000', '\174', '\300', '\170', '\014', '\370', '\000', +'\020', '\060', '\174', '\060', '\060', '\064', '\030', '\000', '\000', '\000', '\314', '\314', '\314', '\314', '\166', '\000', +'\000', '\000', '\314', '\314', '\314', '\170', '\060', '\000', '\000', '\000', '\306', '\326', '\376', '\376', '\154', '\000', +'\000', '\000', '\306', '\154', '\070', '\154', '\306', '\000', '\000', '\000', '\314', '\314', '\314', '\174', '\014', '\370', +'\000', '\000', '\374', '\230', '\060', '\144', '\374', '\000', '\034', '\060', '\060', '\340', '\060', '\060', '\034', '\000', +'\030', '\030', '\030', '\000', '\030', '\030', '\030', '\000', '\340', '\060', '\060', '\034', '\060', '\060', '\340', '\000', +'\166', '\334', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\020', '\070', '\154', '\306', '\306', '\376', '\000', + +}; +static unsigned const char font_default_upper_itf[] = { +'\377', '\200', '\200', '\200', '\200', '\200', '\200', '\200', '\377', '\000', '\000', '\000', '\000', '\000', '\000', '\000', +'\377', '\001', '\001', '\001', '\001', '\001', '\001', '\001', '\200', '\200', '\200', '\200', '\200', '\200', '\200', '\200', +'\001', '\001', '\001', '\001', '\001', '\001', '\001', '\001', '\200', '\200', '\200', '\200', '\200', '\200', '\200', '\377', +'\000', '\000', '\000', '\000', '\000', '\000', '\000', '\377', '\001', '\001', '\001', '\001', '\001', '\001', '\001', '\377', +'\200', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\000', '\000', '\200', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', +'\200', '\300', '\340', '\360', '\370', '\374', '\376', '\377', '\377', '\177', '\077', '\037', '\017', '\007', '\003', '\001', +'\377', '\377', '\300', '\300', '\300', '\300', '\300', '\300', '\377', '\377', '\000', '\000', '\000', '\000', '\000', '\000', +'\377', '\377', '\003', '\003', '\003', '\003', '\003', '\003', '\300', '\300', '\300', '\300', '\300', '\300', '\300', '\300', +'\003', '\003', '\003', '\003', '\003', '\003', '\003', '\003', '\300', '\300', '\300', '\300', '\300', '\300', '\377', '\377', +'\000', '\000', '\000', '\000', '\000', '\000', '\377', '\377', '\003', '\003', '\003', '\003', '\003', '\003', '\377', '\377', +'\300', '\300', '\000', '\000', '\000', '\000', '\000', '\000', '\003', '\003', '\000', '\000', '\000', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\000', '\300', '\300', '\000', '\000', '\000', '\000', '\000', '\000', '\003', '\003', +'\000', '\000', '\000', '\125', '\000', '\000', '\000', '\000', '\000', '\374', '\374', '\374', '\374', '\374', '\374', '\000', +'\000', '\176', '\176', '\176', '\176', '\176', '\176', '\000', '\000', '\077', '\077', '\077', '\077', '\077', '\077', '\000', +'\000', '\037', '\037', '\037', '\037', '\037', '\037', '\000', '\000', '\017', '\017', '\017', '\017', '\017', '\017', '\000', +'\000', '\007', '\007', '\007', '\007', '\007', '\007', '\000', '\000', '\003', '\003', '\003', '\003', '\003', '\003', '\000', +'\000', '\001', '\001', '\001', '\001', '\001', '\001', '\000', '\000', '\200', '\200', '\200', '\200', '\200', '\200', '\000', +'\000', '\300', '\300', '\300', '\300', '\300', '\300', '\000', '\000', '\340', '\340', '\340', '\340', '\340', '\340', '\000', +'\000', '\360', '\360', '\360', '\360', '\360', '\360', '\000', '\000', '\370', '\370', '\370', '\370', '\370', '\370', '\000', +'\376', '\376', '\376', '\376', '\376', '\376', '\376', '\376', '\377', '\377', '\000', '\030', '\074', '\176', '\377', '\000', +'\377', '\377', '\000', '\000', '\000', '\000', '\030', '\030', '\377', '\377', '\000', '\030', '\074', '\176', '\377', '\030', +'\377', '\377', '\000', '\102', '\146', '\132', '\102', '\102', '\000', '\000', '\000', '\030', '\030', '\000', '\000', '\000', +'\000', '\300', '\300', '\300', '\300', '\300', '\300', '\300', '\000', '\330', '\330', '\330', '\330', '\330', '\330', '\330', +'\000', '\333', '\333', '\333', '\333', '\333', '\333', '\333', '\000', '\140', '\140', '\140', '\140', '\140', '\140', '\140', +'\000', '\154', '\154', '\154', '\154', '\154', '\154', '\154', '\000', '\155', '\155', '\155', '\155', '\155', '\155', '\155', +'\000', '\200', '\200', '\200', '\200', '\200', '\200', '\200', '\000', '\260', '\260', '\260', '\260', '\260', '\260', '\260', +'\000', '\266', '\266', '\266', '\266', '\266', '\266', '\266', '\000', '\000', '\030', '\074', '\074', '\030', '\000', '\000', +'\000', '\000', '\000', '\146', '\000', '\000', '\000', '\000', '\000', '\036', '\041', '\100', '\000', '\000', '\000', '\000', +'\000', '\000', '\000', '\202', '\104', '\070', '\000', '\000', '\000', '\076', '\042', '\042', '\042', '\042', '\143', '\000', +'\000', '\076', '\042', '\042', '\042', '\042', '\343', '\000', '\000', '\060', '\054', '\043', '\040', '\040', '\040', '\000', +'\000', '\004', '\004', '\004', '\304', '\064', '\014', '\000', '\000', '\006', '\004', '\004', '\004', '\004', '\006', '\000', +'\000', '\300', '\100', '\100', '\100', '\100', '\300', '\000', '\000', '\000', '\000', '\030', '\030', '\000', '\000', '\000', +'\000', '\000', '\030', '\074', '\074', '\030', '\000', '\000', '\000', '\000', '\030', '\074', '\074', '\030', '\000', '\000', +'\000', '\000', '\074', '\074', '\074', '\074', '\000', '\000', '\000', '\000', '\074', '\074', '\074', '\074', '\000', '\000', +'\000', '\030', '\074', '\176', '\176', '\074', '\030', '\000', '\000', '\030', '\074', '\176', '\176', '\074', '\030', '\000', +'\000', '\074', '\176', '\176', '\176', '\176', '\074', '\000', '\000', '\074', '\176', '\176', '\176', '\176', '\074', '\000', +'\066', '\066', '\367', '\000', '\377', '\000', '\000', '\000', '\000', '\000', '\377', '\000', '\367', '\066', '\066', '\066', +'\066', '\066', '\067', '\060', '\067', '\066', '\066', '\066', '\000', '\000', '\377', '\000', '\377', '\000', '\000', '\000', +'\066', '\066', '\367', '\000', '\367', '\066', '\066', '\066', '\030', '\030', '\377', '\000', '\377', '\000', '\000', '\000', +'\066', '\066', '\066', '\066', '\377', '\000', '\000', '\000', '\000', '\000', '\377', '\000', '\377', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\377', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\077', '\000', '\000', '\000', +'\030', '\030', '\037', '\030', '\037', '\000', '\000', '\000', '\000', '\000', '\037', '\030', '\037', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\077', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\377', '\066', '\066', '\066', +'\030', '\030', '\377', '\030', '\377', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\370', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\037', '\030', '\030', '\030', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +'\000', '\000', '\000', '\000', '\377', '\377', '\377', '\377', '\360', '\360', '\360', '\360', '\360', '\360', '\360', '\360', +'\017', '\017', '\017', '\017', '\017', '\017', '\017', '\017', '\377', '\377', '\377', '\377', '\000', '\000', '\000', '\000', +'\000', '\000', '\166', '\334', '\310', '\334', '\166', '\000', '\000', '\170', '\314', '\370', '\314', '\370', '\300', '\300', +'\000', '\374', '\314', '\300', '\300', '\300', '\300', '\000', '\000', '\376', '\154', '\154', '\154', '\154', '\154', '\000', +'\374', '\314', '\140', '\060', '\140', '\314', '\374', '\000', '\000', '\000', '\176', '\330', '\330', '\330', '\160', '\000', +'\000', '\146', '\146', '\146', '\146', '\174', '\140', '\300', '\000', '\166', '\334', '\030', '\030', '\030', '\030', '\000', +'\374', '\060', '\170', '\314', '\314', '\170', '\060', '\374', '\070', '\154', '\306', '\376', '\306', '\154', '\070', '\000', +'\070', '\154', '\306', '\306', '\154', '\154', '\356', '\000', '\034', '\060', '\030', '\174', '\314', '\314', '\170', '\000', +'\000', '\000', '\176', '\333', '\333', '\176', '\000', '\000', '\006', '\014', '\176', '\333', '\333', '\176', '\140', '\300', +'\070', '\140', '\300', '\370', '\300', '\140', '\070', '\000', '\170', '\314', '\314', '\314', '\314', '\314', '\314', '\000', +'\000', '\374', '\000', '\374', '\000', '\374', '\000', '\000', '\060', '\060', '\374', '\060', '\060', '\000', '\374', '\000', +'\140', '\060', '\030', '\060', '\140', '\000', '\374', '\000', '\030', '\060', '\140', '\060', '\030', '\000', '\374', '\000', +'\016', '\033', '\033', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\330', '\330', '\160', +'\060', '\060', '\000', '\374', '\000', '\060', '\060', '\000', '\000', '\166', '\334', '\000', '\166', '\334', '\000', '\000', +'\070', '\154', '\154', '\070', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\030', '\030', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\030', '\000', '\000', '\000', '\017', '\014', '\014', '\014', '\354', '\154', '\074', '\034', +'\170', '\154', '\154', '\154', '\154', '\000', '\000', '\000', '\160', '\030', '\060', '\140', '\170', '\000', '\000', '\000', +'\000', '\000', '\074', '\074', '\074', '\074', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', + +}; +static unsigned const char font_default_upper_alt[] = { +'\170', '\314', '\300', '\314', '\170', '\030', '\014', '\170', '\000', '\314', '\000', '\314', '\314', '\314', '\176', '\000', +'\034', '\000', '\170', '\314', '\374', '\300', '\170', '\000', '\176', '\303', '\074', '\006', '\076', '\146', '\077', '\000', +'\314', '\000', '\170', '\014', '\174', '\314', '\176', '\000', '\340', '\000', '\170', '\014', '\174', '\314', '\176', '\000', +'\060', '\060', '\170', '\014', '\174', '\314', '\176', '\000', '\000', '\000', '\170', '\300', '\300', '\170', '\014', '\070', +'\176', '\303', '\074', '\146', '\176', '\140', '\074', '\000', '\314', '\000', '\170', '\314', '\374', '\300', '\170', '\000', +'\340', '\000', '\170', '\314', '\374', '\300', '\170', '\000', '\314', '\000', '\160', '\060', '\060', '\060', '\170', '\000', +'\174', '\306', '\070', '\030', '\030', '\030', '\074', '\000', '\340', '\000', '\160', '\060', '\060', '\060', '\170', '\000', +'\306', '\070', '\154', '\306', '\376', '\306', '\306', '\000', '\060', '\060', '\000', '\170', '\314', '\374', '\314', '\000', +'\034', '\000', '\374', '\140', '\170', '\140', '\374', '\000', '\000', '\000', '\177', '\014', '\177', '\314', '\177', '\000', +'\076', '\154', '\314', '\376', '\314', '\314', '\316', '\000', '\170', '\314', '\000', '\170', '\314', '\314', '\170', '\000', +'\000', '\314', '\000', '\170', '\314', '\314', '\170', '\000', '\000', '\340', '\000', '\170', '\314', '\314', '\170', '\000', +'\170', '\314', '\000', '\314', '\314', '\314', '\176', '\000', '\000', '\340', '\000', '\314', '\314', '\314', '\176', '\000', +'\000', '\314', '\000', '\314', '\314', '\174', '\014', '\370', '\303', '\030', '\074', '\146', '\146', '\074', '\030', '\000', +'\314', '\000', '\314', '\314', '\314', '\314', '\170', '\000', '\030', '\030', '\176', '\300', '\300', '\176', '\030', '\030', +'\070', '\154', '\144', '\360', '\140', '\346', '\374', '\000', '\314', '\314', '\170', '\374', '\060', '\374', '\060', '\060', +'\370', '\314', '\314', '\372', '\306', '\317', '\306', '\307', '\016', '\033', '\030', '\074', '\030', '\030', '\330', '\160', +'\034', '\000', '\170', '\014', '\174', '\314', '\176', '\000', '\070', '\000', '\160', '\060', '\060', '\060', '\170', '\000', +'\000', '\034', '\000', '\170', '\314', '\314', '\170', '\000', '\000', '\034', '\000', '\314', '\314', '\314', '\176', '\000', +'\000', '\370', '\000', '\370', '\314', '\314', '\314', '\000', '\374', '\000', '\314', '\354', '\374', '\334', '\314', '\000', +'\074', '\154', '\154', '\076', '\000', '\176', '\000', '\000', '\070', '\154', '\154', '\070', '\000', '\174', '\000', '\000', +'\060', '\000', '\060', '\140', '\300', '\314', '\170', '\000', '\000', '\000', '\000', '\374', '\300', '\300', '\000', '\000', +'\000', '\000', '\000', '\374', '\014', '\014', '\000', '\000', '\303', '\306', '\314', '\336', '\063', '\146', '\314', '\017', +'\303', '\306', '\314', '\333', '\067', '\157', '\317', '\003', '\030', '\030', '\000', '\030', '\030', '\030', '\030', '\000', +'\000', '\063', '\146', '\314', '\146', '\063', '\000', '\000', '\000', '\314', '\146', '\063', '\146', '\314', '\000', '\000', +'\042', '\210', '\042', '\210', '\042', '\210', '\042', '\210', '\125', '\252', '\125', '\252', '\125', '\252', '\125', '\252', +'\333', '\167', '\333', '\356', '\333', '\167', '\333', '\356', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', +'\030', '\030', '\030', '\030', '\370', '\030', '\030', '\030', '\030', '\030', '\370', '\030', '\370', '\030', '\030', '\030', +'\066', '\066', '\066', '\066', '\366', '\066', '\066', '\066', '\000', '\000', '\000', '\000', '\376', '\066', '\066', '\066', +'\000', '\000', '\370', '\030', '\370', '\030', '\030', '\030', '\066', '\066', '\366', '\006', '\366', '\066', '\066', '\066', +'\066', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\000', '\000', '\376', '\006', '\366', '\066', '\066', '\066', +'\066', '\066', '\366', '\006', '\376', '\000', '\000', '\000', '\066', '\066', '\066', '\066', '\376', '\000', '\000', '\000', +'\030', '\030', '\370', '\030', '\370', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\370', '\030', '\030', '\030', +'\030', '\030', '\030', '\030', '\037', '\000', '\000', '\000', '\030', '\030', '\030', '\030', '\377', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\377', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\037', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\377', '\000', '\000', '\000', '\030', '\030', '\030', '\030', '\377', '\030', '\030', '\030', +'\030', '\030', '\037', '\030', '\037', '\030', '\030', '\030', '\066', '\066', '\066', '\066', '\067', '\066', '\066', '\066', +'\066', '\066', '\067', '\060', '\077', '\000', '\000', '\000', '\000', '\000', '\077', '\060', '\067', '\066', '\066', '\066', +'\066', '\066', '\367', '\000', '\377', '\000', '\000', '\000', '\000', '\000', '\377', '\000', '\367', '\066', '\066', '\066', +'\066', '\066', '\067', '\060', '\067', '\066', '\066', '\066', '\000', '\000', '\377', '\000', '\377', '\000', '\000', '\000', +'\066', '\066', '\367', '\000', '\367', '\066', '\066', '\066', '\030', '\030', '\377', '\000', '\377', '\000', '\000', '\000', +'\066', '\066', '\066', '\066', '\377', '\000', '\000', '\000', '\000', '\000', '\377', '\000', '\377', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\377', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\077', '\000', '\000', '\000', +'\030', '\030', '\037', '\030', '\037', '\000', '\000', '\000', '\000', '\000', '\037', '\030', '\037', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\077', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\377', '\066', '\066', '\066', +'\030', '\030', '\377', '\030', '\377', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\370', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\037', '\030', '\030', '\030', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +'\000', '\000', '\000', '\000', '\377', '\377', '\377', '\377', '\360', '\360', '\360', '\360', '\360', '\360', '\360', '\360', +'\017', '\017', '\017', '\017', '\017', '\017', '\017', '\017', '\377', '\377', '\377', '\377', '\000', '\000', '\000', '\000', +'\000', '\000', '\166', '\334', '\310', '\334', '\166', '\000', '\000', '\170', '\314', '\370', '\314', '\370', '\300', '\300', +'\000', '\374', '\314', '\300', '\300', '\300', '\300', '\000', '\000', '\376', '\154', '\154', '\154', '\154', '\154', '\000', +'\374', '\314', '\140', '\060', '\140', '\314', '\374', '\000', '\000', '\000', '\176', '\330', '\330', '\330', '\160', '\000', +'\000', '\146', '\146', '\146', '\146', '\174', '\140', '\300', '\000', '\166', '\334', '\030', '\030', '\030', '\030', '\000', +'\374', '\060', '\170', '\314', '\314', '\170', '\060', '\374', '\070', '\154', '\306', '\376', '\306', '\154', '\070', '\000', +'\070', '\154', '\306', '\306', '\154', '\154', '\356', '\000', '\034', '\060', '\030', '\174', '\314', '\314', '\170', '\000', +'\000', '\000', '\176', '\333', '\333', '\176', '\000', '\000', '\006', '\014', '\176', '\333', '\333', '\176', '\140', '\300', +'\070', '\140', '\300', '\370', '\300', '\140', '\070', '\000', '\170', '\314', '\314', '\314', '\314', '\314', '\314', '\000', +'\000', '\374', '\000', '\374', '\000', '\374', '\000', '\000', '\060', '\060', '\374', '\060', '\060', '\000', '\374', '\000', +'\140', '\060', '\030', '\060', '\140', '\000', '\374', '\000', '\030', '\060', '\140', '\060', '\030', '\000', '\374', '\000', +'\016', '\033', '\033', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\330', '\330', '\160', +'\060', '\060', '\000', '\374', '\000', '\060', '\060', '\000', '\000', '\166', '\334', '\000', '\166', '\334', '\000', '\000', +'\070', '\154', '\154', '\070', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\030', '\030', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\030', '\000', '\000', '\000', '\017', '\014', '\014', '\014', '\354', '\154', '\074', '\034', +'\170', '\154', '\154', '\154', '\154', '\000', '\000', '\000', '\160', '\030', '\060', '\140', '\170', '\000', '\000', '\000', +'\000', '\000', '\074', '\074', '\074', '\074', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', + +}; +static unsigned const char font_half_width[] = { +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\000', '\000', '\000', '\000', '\104', '\104', '\004', '\100', '\252', '\240', '\000', '\000', '\252', '\352', '\352', '\240', +'\106', '\216', '\054', '\100', '\242', '\104', '\110', '\240', '\112', '\112', '\254', '\140', '\104', '\200', '\000', '\000', +'\044', '\104', '\104', '\040', '\204', '\104', '\104', '\200', '\012', '\116', '\112', '\000', '\004', '\116', '\104', '\000', +'\000', '\000', '\004', '\110', '\000', '\016', '\000', '\000', '\000', '\000', '\004', '\100', '\042', '\104', '\110', '\200', +'\112', '\252', '\252', '\100', '\114', '\104', '\104', '\340', '\112', '\044', '\110', '\340', '\302', '\044', '\042', '\300', +'\252', '\256', '\042', '\040', '\350', '\214', '\042', '\300', '\112', '\214', '\252', '\100', '\342', '\042', '\042', '\040', +'\112', '\244', '\252', '\100', '\352', '\256', '\042', '\040', '\004', '\100', '\004', '\100', '\004', '\100', '\004', '\110', +'\002', '\110', '\102', '\000', '\000', '\340', '\340', '\000', '\010', '\102', '\110', '\000', '\302', '\044', '\100', '\100', +'\112', '\256', '\250', '\140', '\112', '\256', '\252', '\240', '\312', '\254', '\252', '\300', '\150', '\210', '\210', '\140', +'\312', '\252', '\252', '\300', '\350', '\214', '\210', '\340', '\350', '\214', '\210', '\200', '\150', '\212', '\252', '\140', +'\252', '\256', '\252', '\240', '\344', '\104', '\104', '\340', '\042', '\042', '\252', '\100', '\252', '\254', '\252', '\240', +'\210', '\210', '\210', '\340', '\256', '\352', '\252', '\240', '\312', '\252', '\252', '\240', '\352', '\252', '\252', '\340', +'\312', '\254', '\210', '\200', '\112', '\252', '\354', '\140', '\312', '\254', '\252', '\240', '\112', '\204', '\052', '\100', +'\344', '\104', '\104', '\100', '\252', '\252', '\252', '\340', '\252', '\252', '\244', '\100', '\252', '\252', '\356', '\240', +'\252', '\344', '\352', '\240', '\252', '\244', '\104', '\100', '\342', '\044', '\210', '\340', '\144', '\104', '\104', '\140', +'\210', '\104', '\102', '\040', '\304', '\104', '\104', '\300', '\004', '\252', '\000', '\000', '\000', '\000', '\000', '\016', +'\104', '\040', '\000', '\000', '\000', '\102', '\152', '\140', '\210', '\214', '\252', '\300', '\000', '\112', '\212', '\100', +'\042', '\046', '\252', '\140', '\000', '\112', '\310', '\140', '\112', '\214', '\210', '\200', '\000', '\152', '\246', '\054', +'\210', '\214', '\252', '\240', '\100', '\304', '\104', '\340', '\040', '\142', '\042', '\244', '\210', '\252', '\312', '\240', +'\104', '\104', '\104', '\100', '\000', '\256', '\352', '\240', '\000', '\312', '\252', '\240', '\000', '\112', '\252', '\100', +'\000', '\312', '\254', '\210', '\000', '\152', '\246', '\042', '\000', '\352', '\210', '\200', '\000', '\150', '\102', '\300', +'\104', '\344', '\104', '\040', '\000', '\252', '\252', '\140', '\000', '\252', '\244', '\100', '\000', '\252', '\356', '\240', +'\000', '\252', '\112', '\240', '\000', '\252', '\246', '\054', '\000', '\342', '\110', '\340', '\044', '\114', '\104', '\040', +'\104', '\104', '\104', '\100', '\204', '\106', '\104', '\200', '\056', '\200', '\000', '\000', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\000', '\360', '\360', '\000', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', + +}; diff --git a/include/auto/helpenum.h b/include/auto/helpenum.h new file mode 100644 index 000000000..caaae466a --- /dev/null +++ b/include/auto/helpenum.h @@ -0,0 +1,12 @@ +enum { +HELP_GLOBAL, +HELP_PATTERN_EDITOR, +HELP_SAMPLE_LIST, +HELP_INSTRUMENT_LIST, +HELP_INFO_PAGE, +HELP_MESSAGE_EDITOR, +HELP_ORDERLIST_PANNING, +HELP_ORDERLIST_VOLUME, +HELP_MIDI_OUTPUT, +HELP_NUM_ITEMS +}; diff --git a/include/auto/helptext.h b/include/auto/helptext.h new file mode 100644 index 000000000..bd63cd6c8 --- /dev/null +++ b/include/auto/helptext.h @@ -0,0 +1,1378 @@ +/* this file should only be included by helptext.c */ +static unsigned char help_text[] = { +'\174', '\040', '\107', '\154', '\157', '\142', '\141', '\154', '\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', +'\040', '\040', '\040', '\106', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\110', '\145', '\154', '\160', '\040', '\050', '\103', '\157', '\156', '\164', '\145', +'\170', '\164', '\040', '\163', '\145', '\156', '\163', '\151', '\164', '\151', '\166', '\145', '\041', '\051', '\012', '\174', +'\040', '\040', '\040', '\123', '\150', '\151', '\146', '\164', '\055', '\106', '\061', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\115', '\111', '\104', '\111', '\040', '\123', '\143', '\162', '\145', '\145', '\156', +'\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\061', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\171', '\163', '\164', '\145', '\155', '\040', '\103', '\157', +'\156', '\146', '\151', '\147', '\165', '\162', '\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\040', +'\106', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\105', '\144', '\151', '\164', '\157', '\162', +'\040', '\057', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\105', '\144', '\151', '\164', '\157', +'\162', '\040', '\117', '\160', '\164', '\151', '\157', '\156', '\163', '\012', '\174', '\040', '\040', '\040', '\106', '\063', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\123', '\141', '\155', '\160', '\154', '\145', '\040', '\114', '\151', '\163', '\164', '\012', '\174', '\040', '\040', '\040', +'\103', '\164', '\162', '\154', '\055', '\106', '\063', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\114', '\151', '\142', '\162', '\141', '\162', '\171', +'\012', '\174', '\040', '\040', '\040', '\106', '\064', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', +'\164', '\040', '\114', '\151', '\163', '\164', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\106', '\064', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', +'\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\114', '\151', '\142', '\162', '\141', '\162', '\171', '\012', +'\174', '\040', '\040', '\040', '\106', '\065', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\111', '\156', '\146', '\157', '\162', +'\155', '\141', '\164', '\151', '\157', '\156', '\040', '\057', '\040', '\120', '\154', '\141', '\171', '\040', '\163', '\157', +'\156', '\147', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\065', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\123', '\157', +'\156', '\147', '\012', '\174', '\040', '\040', '\040', '\106', '\066', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', +'\040', '\040', '\123', '\150', '\151', '\146', '\164', '\055', '\106', '\066', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\163', '\157', '\156', '\147', '\040', '\146', '\162', +'\157', '\155', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\117', '\162', '\144', '\145', '\162', +'\012', '\174', '\040', '\040', '\040', '\106', '\067', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\146', '\162', '\157', '\155', +'\040', '\155', '\141', '\162', '\153', '\040', '\057', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', +'\162', '\157', '\167', '\012', '\174', '\040', '\040', '\040', '\106', '\070', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\164', '\157', '\160', '\040', '\120', +'\154', '\141', '\171', '\142', '\141', '\143', '\153', '\012', '\174', '\040', '\040', '\040', '\106', '\071', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\114', '\157', +'\141', '\144', '\040', '\115', '\157', '\144', '\165', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\150', +'\151', '\146', '\164', '\055', '\106', '\071', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\115', '\145', '\163', '\163', '\141', '\147', '\145', '\040', '\105', '\144', '\151', '\164', '\157', '\162', '\012', '\174', +'\040', '\040', '\040', '\106', '\061', '\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\115', '\157', '\144', '\165', '\154', '\145', +'\012', '\174', '\040', '\040', '\040', '\106', '\061', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\114', '\151', '\163', +'\164', '\040', '\141', '\156', '\144', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\012', '\174', '\040', +'\062', '\052', '\106', '\061', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\114', '\151', '\163', '\164', '\040', '\141', +'\156', '\144', '\040', '\103', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\126', '\157', '\154', '\165', '\155', +'\145', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\061', '\061', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\040', '\114', +'\157', '\147', '\147', '\151', '\156', '\147', '\012', '\174', '\040', '\040', '\040', '\106', '\061', '\062', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\156', +'\147', '\040', '\126', '\141', '\162', '\151', '\141', '\142', '\154', '\145', '\163', '\040', '\046', '\040', '\104', '\151', +'\162', '\145', '\143', '\164', '\157', '\162', '\171', '\040', '\103', '\157', '\156', '\146', '\151', '\147', '\165', '\162', +'\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', +'\061', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\141', '\154', '\145', +'\164', '\164', '\145', '\040', '\103', '\157', '\156', '\146', '\151', '\147', '\165', '\162', '\141', '\164', '\151', '\157', +'\156', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\173', '\040', '\175', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', +'\163', '\145', '\057', '\111', '\156', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\160', '\154', '\141', '\171', +'\142', '\141', '\143', '\153', '\040', '\163', '\160', '\145', '\145', '\144', '\012', '\174', '\040', '\040', '\040', '\133', +'\040', '\135', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\057', '\111', '\156', '\143', '\162', '\145', '\141', +'\163', '\145', '\040', '\147', '\154', '\157', '\142', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\106', '\061', '\040', '\055', '\076', '\040', '\101', +'\154', '\164', '\055', '\106', '\070', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\163', '\040', '\061', '\055', '\076', '\070', '\012', '\174', '\012', '\041', '\040', +'\040', '\040', '\103', '\164', '\162', '\154', '\055', '\104', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\104', '\117', '\123', '\040', '\123', '\150', '\145', '\154', '\154', '\012', '\174', '\040', +'\040', '\040', '\103', '\164', '\162', '\154', '\055', '\105', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\122', '\145', '\146', '\162', '\145', '\163', '\150', '\040', '\163', '\143', '\162', '\145', +'\145', '\156', '\040', '\141', '\156', '\144', '\040', '\162', '\145', '\163', '\145', '\164', '\040', '\143', '\141', '\143', +'\150', '\145', '\040', '\151', '\144', '\145', '\156', '\164', '\151', '\146', '\151', '\143', '\141', '\164', '\151', '\157', +'\156', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\111', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\151', '\156', '\151', '\164', '\151', '\141', +'\154', '\151', '\163', '\145', '\040', '\163', '\157', '\165', '\156', '\144', '\040', '\144', '\162', '\151', '\166', '\145', +'\162', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\115', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\155', +'\157', '\165', '\163', '\145', '\040', '\143', '\165', '\162', '\163', '\157', '\162', '\012', '\174', '\040', '\040', '\040', +'\103', '\164', '\162', '\154', '\055', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\116', '\145', '\167', '\040', '\123', '\157', '\156', '\147', '\012', '\041', '\040', '\040', '\040', '\103', +'\164', '\162', '\154', '\055', '\121', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\121', '\165', '\151', '\164', '\040', '\164', '\157', '\040', '\104', '\117', '\123', '\012', '\072', '\040', '\040', +'\040', '\103', '\164', '\162', '\154', '\055', '\121', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\121', '\165', '\151', '\164', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\040', '\124', +'\162', '\141', '\143', '\153', '\145', '\162', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', +'\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\157', '\156', '\147', '\012', '\072', +'\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\107', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\107', '\157', '\040', '\164', '\157', '\040', '\157', '\162', '\144', '\145', '\162', +'\057', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\057', '\162', '\157', '\167', '\040', '\147', '\151', '\166', +'\145', '\156', '\040', '\164', '\151', '\155', '\145', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\120', '\141', '\164', +'\164', '\145', '\162', '\156', '\040', '\105', '\144', '\151', '\164', '\040', '\040', '\221', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', '\174', +'\012', '\174', '\040', '\123', '\165', '\155', '\155', '\141', '\162', '\171', '\040', '\157', '\146', '\040', '\105', '\146', +'\146', '\145', '\143', '\164', '\163', '\056', '\012', '\174', '\012', '\174', '\040', '\040', '\126', '\157', '\154', '\165', +'\155', '\145', '\040', '\103', '\157', '\154', '\165', '\155', '\156', '\040', '\145', '\146', '\146', '\145', '\143', '\164', +'\163', '\056', '\012', '\174', '\040', '\040', '\040', '\101', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\166', +'\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', +'\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\102', '\170', '\040', '\106', '\151', '\156', '\145', '\040', +'\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', +'\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\103', '\170', '\040', '\126', '\157', +'\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', +'\040', '\170', '\012', '\174', '\040', '\040', '\040', '\104', '\170', '\040', '\126', '\157', '\154', '\165', '\155', '\145', +'\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', +'\012', '\174', '\040', '\040', '\040', '\105', '\170', '\040', '\120', '\151', '\164', '\143', '\150', '\040', '\163', '\154', +'\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', +'\040', '\040', '\106', '\170', '\040', '\120', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', +'\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\107', '\170', '\040', +'\123', '\154', '\151', '\144', '\145', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\167', '\151', +'\164', '\150', '\040', '\163', '\160', '\145', '\145', '\144', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\110', +'\170', '\040', '\126', '\151', '\142', '\162', '\141', '\164', '\157', '\040', '\167', '\151', '\164', '\150', '\040', '\144', +'\145', '\160', '\164', '\150', '\040', '\170', '\012', '\174', '\012', '\174', '\040', '\040', '\107', '\145', '\156', '\145', +'\162', '\141', '\154', '\040', '\145', '\146', '\146', '\145', '\143', '\164', '\163', '\056', '\012', '\174', '\040', '\040', +'\040', '\101', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\163', '\157', '\156', '\147', '\040', '\163', '\160', +'\145', '\145', '\144', '\040', '\050', '\150', '\145', '\170', '\051', '\012', '\174', '\040', '\040', '\040', '\102', '\170', +'\170', '\040', '\112', '\165', '\155', '\160', '\040', '\164', '\157', '\040', '\117', '\162', '\144', '\145', '\162', '\040', +'\050', '\150', '\145', '\170', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\170', '\170', '\040', '\102', '\162', +'\145', '\141', '\153', '\040', '\164', '\157', '\040', '\162', '\157', '\167', '\040', '\170', '\170', '\040', '\050', '\150', +'\145', '\170', '\051', '\040', '\157', '\146', '\040', '\156', '\145', '\170', '\164', '\040', '\160', '\141', '\164', '\164', +'\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\104', '\060', '\170', '\040', '\126', '\157', '\154', '\165', +'\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', +'\040', '\170', '\012', '\174', '\040', '\040', '\040', '\104', '\170', '\060', '\040', '\126', '\157', '\154', '\165', '\155', +'\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', +'\174', '\040', '\040', '\040', '\104', '\106', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\166', '\157', '\154', +'\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', +'\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\104', '\170', '\106', '\040', '\106', '\151', '\156', '\145', +'\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', +'\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\105', '\170', '\170', '\040', '\120', '\151', +'\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', +'\171', '\040', '\170', '\170', '\012', '\174', '\040', '\040', '\040', '\105', '\106', '\170', '\040', '\106', '\151', '\156', +'\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', +'\167', '\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\105', '\105', '\170', '\040', +'\105', '\170', '\164', '\162', '\141', '\040', '\146', '\151', '\156', '\145', '\040', '\160', '\151', '\164', '\143', '\150', +'\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', +'\012', '\174', '\040', '\040', '\040', '\106', '\170', '\170', '\040', '\120', '\151', '\164', '\143', '\150', '\040', '\163', +'\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\170', '\012', '\174', '\040', +'\040', '\040', '\106', '\106', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\160', '\151', '\164', '\143', '\150', +'\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', +'\040', '\040', '\040', '\106', '\105', '\170', '\040', '\105', '\170', '\164', '\162', '\141', '\040', '\146', '\151', '\156', +'\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', +'\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\107', '\170', '\170', '\040', '\123', '\154', +'\151', '\144', '\145', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\167', '\151', '\164', '\150', +'\040', '\163', '\160', '\145', '\145', '\144', '\040', '\170', '\170', '\012', '\174', '\040', '\040', '\040', '\110', '\170', +'\171', '\040', '\126', '\151', '\142', '\162', '\141', '\164', '\157', '\040', '\167', '\151', '\164', '\150', '\040', '\163', +'\160', '\145', '\145', '\144', '\040', '\170', '\054', '\040', '\144', '\145', '\160', '\164', '\150', '\040', '\171', '\012', +'\174', '\040', '\040', '\040', '\111', '\170', '\171', '\040', '\124', '\162', '\145', '\155', '\157', '\162', '\040', '\167', +'\151', '\164', '\150', '\040', '\157', '\156', '\164', '\151', '\155', '\145', '\040', '\170', '\040', '\141', '\156', '\144', +'\040', '\157', '\146', '\146', '\164', '\151', '\155', '\145', '\040', '\171', '\012', '\174', '\040', '\040', '\040', '\112', +'\170', '\171', '\040', '\101', '\162', '\160', '\145', '\147', '\147', '\151', '\157', '\040', '\167', '\151', '\164', '\150', +'\040', '\150', '\141', '\154', '\146', '\164', '\157', '\156', '\145', '\163', '\040', '\170', '\040', '\141', '\156', '\144', +'\040', '\171', '\012', '\174', '\040', '\040', '\040', '\113', '\170', '\170', '\040', '\104', '\165', '\141', '\154', '\040', +'\103', '\157', '\155', '\155', '\141', '\156', '\144', '\072', '\040', '\110', '\060', '\060', '\040', '\046', '\040', '\104', +'\170', '\170', '\012', '\174', '\040', '\040', '\040', '\114', '\170', '\170', '\040', '\104', '\165', '\141', '\154', '\040', +'\103', '\157', '\155', '\155', '\141', '\156', '\144', '\072', '\040', '\107', '\060', '\060', '\040', '\046', '\040', '\104', +'\170', '\170', '\012', '\174', '\040', '\040', '\040', '\115', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\143', +'\150', '\141', '\156', '\156', '\145', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\164', '\157', +'\040', '\170', '\170', '\040', '\050', '\060', '\055', '\076', '\064', '\060', '\150', '\051', '\012', '\174', '\040', '\040', +'\040', '\116', '\060', '\170', '\040', '\103', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\166', '\157', '\154', +'\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', +'\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\116', '\170', '\060', '\040', '\103', '\150', '\141', '\156', +'\156', '\145', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', +'\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\116', '\106', '\170', +'\040', '\106', '\151', '\156', '\145', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\166', '\157', +'\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', +'\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\116', '\170', '\106', '\040', '\106', '\151', '\156', +'\145', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', +'\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', +'\040', '\040', '\040', '\117', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\040', '\157', '\146', '\146', '\163', '\145', '\164', '\040', '\164', '\157', '\040', '\171', '\170', '\170', '\060', +'\060', '\150', '\054', '\040', '\171', '\040', '\163', '\145', '\164', '\040', '\167', '\151', '\164', '\150', '\040', '\123', +'\101', '\171', '\012', '\174', '\040', '\040', '\040', '\120', '\060', '\170', '\040', '\120', '\141', '\156', '\156', '\151', +'\156', '\147', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\164', '\157', '\040', '\162', '\151', '\147', '\150', +'\164', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\120', '\170', '\060', '\040', '\120', +'\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\164', '\157', '\040', +'\154', '\145', '\146', '\164', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\120', '\106', +'\170', '\040', '\106', '\151', '\156', '\145', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', +'\154', '\151', '\144', '\145', '\040', '\164', '\157', '\040', '\162', '\151', '\147', '\150', '\164', '\040', '\142', '\171', +'\040', '\170', '\012', '\174', '\040', '\040', '\040', '\120', '\170', '\106', '\040', '\106', '\151', '\156', '\145', '\040', +'\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\164', '\157', +'\040', '\154', '\145', '\146', '\164', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\121', +'\170', '\171', '\040', '\122', '\145', '\164', '\162', '\151', '\147', '\147', '\145', '\162', '\040', '\156', '\157', '\164', +'\145', '\040', '\145', '\166', '\145', '\162', '\171', '\040', '\171', '\040', '\164', '\151', '\143', '\153', '\163', '\040', +'\167', '\151', '\164', '\150', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\155', '\157', '\144', '\151', +'\146', '\151', '\145', '\162', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\126', '\141', '\154', +'\165', '\145', '\163', '\040', '\146', '\157', '\162', '\040', '\170', '\072', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\060', '\072', '\040', '\116', '\157', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', +'\143', '\150', '\141', '\156', '\147', '\145', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\070', +'\072', '\040', '\116', '\157', '\164', '\040', '\165', '\163', '\145', '\144', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\061', '\072', '\040', '\055', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\071', +'\072', '\040', '\053', '\061', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\072', '\040', +'\055', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', '\072', '\040', '\053', '\062', '\012', '\174', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\063', '\072', '\040', '\055', '\064', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\102', '\072', '\040', '\053', '\064', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\064', '\072', '\040', '\055', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\072', '\040', +'\053', '\070', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\065', '\072', '\040', '\055', '\061', +'\066', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\072', '\040', '\053', '\061', '\066', '\012', '\174', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\066', '\072', '\040', '\052', '\062', '\057', '\063', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\105', '\072', '\040', '\052', '\063', '\057', '\062', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\067', '\072', '\040', '\052', '\061', '\057', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\106', '\072', +'\040', '\052', '\062', '\012', '\174', '\040', '\040', '\040', '\122', '\170', '\171', '\040', '\124', '\162', '\145', '\155', +'\157', '\154', '\157', '\040', '\167', '\151', '\164', '\150', '\040', '\163', '\160', '\145', '\145', '\144', '\040', '\170', +'\054', '\040', '\144', '\145', '\160', '\164', '\150', '\040', '\171', '\012', '\043', '\040', '\040', '\040', '\123', '\060', +'\170', '\040', '\123', '\145', '\164', '\040', '\146', '\151', '\154', '\164', '\145', '\162', '\012', '\043', '\040', '\040', +'\040', '\123', '\061', '\170', '\040', '\123', '\145', '\164', '\040', '\147', '\154', '\151', '\163', '\163', '\141', '\156', +'\144', '\157', '\040', '\143', '\157', '\156', '\164', '\162', '\157', '\154', '\012', '\043', '\040', '\040', '\040', '\123', +'\062', '\170', '\040', '\123', '\145', '\164', '\040', '\146', '\151', '\156', '\145', '\164', '\165', '\156', '\145', '\012', +'\174', '\040', '\040', '\040', '\123', '\063', '\170', '\040', '\123', '\145', '\164', '\040', '\166', '\151', '\142', '\162', +'\141', '\164', '\157', '\040', '\167', '\141', '\166', '\145', '\146', '\157', '\162', '\155', '\040', '\164', '\157', '\040', +'\164', '\171', '\160', '\145', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\123', '\064', '\170', '\040', '\123', +'\145', '\164', '\040', '\164', '\162', '\145', '\155', '\157', '\154', '\157', '\040', '\167', '\141', '\166', '\145', '\146', +'\157', '\162', '\155', '\040', '\164', '\157', '\040', '\164', '\171', '\160', '\145', '\040', '\170', '\012', '\174', '\040', +'\040', '\040', '\123', '\065', '\170', '\040', '\123', '\145', '\164', '\040', '\160', '\141', '\156', '\142', '\162', '\145', +'\154', '\154', '\157', '\040', '\167', '\141', '\166', '\145', '\146', '\157', '\162', '\155', '\040', '\164', '\157', '\040', +'\164', '\171', '\160', '\145', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\127', '\141', '\166', +'\145', '\146', '\157', '\162', '\155', '\163', '\040', '\146', '\157', '\162', '\040', '\143', '\157', '\155', '\155', '\141', +'\156', '\144', '\163', '\040', '\123', '\063', '\170', '\054', '\040', '\123', '\064', '\170', '\040', '\141', '\156', '\144', +'\040', '\123', '\065', '\170', '\072', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\060', '\072', +'\040', '\123', '\151', '\156', '\145', '\040', '\167', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\061', '\072', '\040', '\122', '\141', '\155', '\160', '\040', '\144', '\157', '\167', '\156', '\012', +'\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\072', '\040', '\123', '\161', '\165', '\141', '\162', +'\145', '\040', '\167', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\063', +'\072', '\040', '\122', '\141', '\156', '\144', '\157', '\155', '\040', '\167', '\141', '\166', '\145', '\012', '\174', '\040', +'\040', '\040', '\123', '\066', '\170', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\145', +'\154', '\141', '\171', '\040', '\146', '\157', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', '\012', +'\174', '\040', '\040', '\040', '\123', '\067', '\060', '\040', '\120', '\141', '\163', '\164', '\040', '\156', '\157', '\164', +'\145', '\040', '\143', '\165', '\164', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\061', '\040', '\120', '\141', +'\163', '\164', '\040', '\156', '\157', '\164', '\145', '\040', '\157', '\146', '\146', '\012', '\174', '\040', '\040', '\040', +'\123', '\067', '\062', '\040', '\120', '\141', '\163', '\164', '\040', '\156', '\157', '\164', '\145', '\040', '\146', '\141', +'\144', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\063', '\040', '\123', '\145', '\164', '\040', '\116', +'\116', '\101', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\143', '\165', '\164', '\012', '\174', +'\040', '\040', '\040', '\123', '\067', '\064', '\040', '\123', '\145', '\164', '\040', '\116', '\116', '\101', '\040', '\164', +'\157', '\040', '\143', '\157', '\156', '\164', '\151', '\156', '\165', '\145', '\012', '\174', '\040', '\040', '\040', '\123', +'\067', '\065', '\040', '\123', '\145', '\164', '\040', '\116', '\116', '\101', '\040', '\164', '\157', '\040', '\156', '\157', +'\164', '\145', '\040', '\157', '\146', '\146', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\066', '\040', '\123', +'\145', '\164', '\040', '\116', '\116', '\101', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\146', +'\141', '\144', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\067', '\040', '\124', '\165', '\162', '\156', +'\040', '\157', '\146', '\146', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\145', '\156', '\166', '\145', +'\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\070', '\040', '\124', '\165', '\162', +'\156', '\040', '\157', '\156', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\145', '\156', '\166', '\145', +'\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\071', '\040', '\124', '\165', '\162', +'\156', '\040', '\157', '\146', '\146', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\145', '\156', +'\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\101', '\040', '\124', +'\165', '\162', '\156', '\040', '\157', '\156', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\145', +'\156', '\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\102', '\040', +'\124', '\165', '\162', '\156', '\040', '\157', '\146', '\146', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\145', +'\156', '\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\103', '\040', +'\124', '\165', '\162', '\156', '\040', '\157', '\156', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\145', '\156', +'\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\070', '\170', '\040', '\123', +'\145', '\164', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\160', '\157', '\163', '\151', '\164', +'\151', '\157', '\156', '\012', '\174', '\040', '\040', '\040', '\123', '\071', '\061', '\040', '\123', '\145', '\164', '\040', +'\163', '\165', '\162', '\162', '\157', '\165', '\156', '\144', '\040', '\163', '\157', '\165', '\156', '\144', '\012', '\174', +'\040', '\040', '\040', '\123', '\101', '\171', '\040', '\123', '\145', '\164', '\040', '\150', '\151', '\147', '\150', '\040', +'\166', '\141', '\154', '\165', '\145', '\040', '\157', '\146', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', +'\157', '\146', '\146', '\163', '\145', '\164', '\040', '\171', '\170', '\170', '\060', '\060', '\150', '\012', '\174', '\040', +'\040', '\040', '\123', '\102', '\060', '\040', '\123', '\145', '\164', '\040', '\154', '\157', '\157', '\160', '\142', '\141', +'\143', '\153', '\040', '\160', '\157', '\151', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\123', '\102', '\170', +'\040', '\114', '\157', '\157', '\160', '\040', '\170', '\040', '\164', '\151', '\155', '\145', '\163', '\040', '\164', '\157', +'\040', '\154', '\157', '\157', '\160', '\142', '\141', '\143', '\153', '\040', '\160', '\157', '\151', '\156', '\164', '\012', +'\174', '\040', '\040', '\040', '\123', '\103', '\170', '\040', '\116', '\157', '\164', '\145', '\040', '\143', '\165', '\164', +'\040', '\141', '\146', '\164', '\145', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', '\012', '\174', +'\040', '\040', '\040', '\123', '\104', '\170', '\040', '\116', '\157', '\164', '\145', '\040', '\144', '\145', '\154', '\141', +'\171', '\040', '\146', '\157', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', '\012', '\174', '\040', +'\040', '\040', '\123', '\105', '\170', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\145', +'\154', '\141', '\171', '\040', '\146', '\157', '\162', '\040', '\170', '\040', '\162', '\157', '\167', '\163', '\012', '\174', +'\040', '\040', '\040', '\123', '\106', '\170', '\040', '\123', '\145', '\164', '\040', '\160', '\141', '\162', '\141', '\155', +'\145', '\164', '\145', '\162', '\151', '\163', '\145', '\144', '\040', '\115', '\111', '\104', '\111', '\040', '\115', '\141', +'\143', '\162', '\157', '\012', '\174', '\040', '\040', '\040', '\124', '\060', '\170', '\040', '\124', '\145', '\155', '\160', +'\157', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', +'\170', '\012', '\174', '\040', '\040', '\040', '\124', '\061', '\170', '\040', '\124', '\145', '\155', '\160', '\157', '\040', +'\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', +'\040', '\040', '\124', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\124', '\145', '\155', '\160', '\157', '\040', +'\164', '\157', '\040', '\170', '\170', '\040', '\050', '\062', '\060', '\150', '\055', '\076', '\060', '\106', '\106', '\150', +'\051', '\012', '\174', '\040', '\040', '\040', '\125', '\170', '\171', '\040', '\106', '\151', '\156', '\145', '\040', '\166', +'\151', '\142', '\162', '\141', '\164', '\157', '\040', '\167', '\151', '\164', '\150', '\040', '\163', '\160', '\145', '\145', +'\144', '\040', '\170', '\054', '\040', '\144', '\145', '\160', '\164', '\150', '\040', '\171', '\012', '\174', '\040', '\040', +'\040', '\126', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\147', '\154', '\157', '\142', '\141', '\154', '\040', +'\166', '\157', '\154', '\165', '\155', '\145', '\040', '\164', '\157', '\040', '\170', '\170', '\040', '\050', '\060', '\055', +'\076', '\070', '\060', '\150', '\051', '\012', '\174', '\040', '\040', '\040', '\127', '\060', '\170', '\040', '\107', '\154', +'\157', '\142', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', +'\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', +'\127', '\170', '\060', '\040', '\107', '\154', '\157', '\142', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', +'\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', +'\174', '\040', '\040', '\040', '\127', '\106', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\147', '\154', '\157', +'\142', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', +'\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\127', +'\170', '\106', '\040', '\106', '\151', '\156', '\145', '\040', '\147', '\154', '\157', '\142', '\141', '\154', '\040', '\166', +'\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', +'\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\130', '\170', '\170', '\040', '\123', '\145', '\164', '\040', +'\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\160', '\157', '\163', '\151', '\164', '\151', '\157', '\156', +'\040', '\050', '\060', '\055', '\076', '\060', '\106', '\106', '\150', '\051', '\012', '\174', '\040', '\040', '\040', '\131', +'\170', '\171', '\040', '\120', '\141', '\156', '\142', '\162', '\145', '\154', '\154', '\157', '\040', '\167', '\151', '\164', +'\150', '\040', '\163', '\160', '\145', '\145', '\144', '\040', '\170', '\054', '\040', '\144', '\145', '\160', '\164', '\150', +'\040', '\171', '\012', '\174', '\040', '\040', '\040', '\132', '\170', '\170', '\040', '\115', '\111', '\104', '\111', '\040', +'\115', '\141', '\143', '\162', '\157', '\163', '\012', '\174', '\012', '\072', '\040', '\106', '\124', '\062', '\040', '\145', +'\146', '\146', '\145', '\143', '\164', '\040', '\164', '\162', '\141', '\156', '\163', '\154', '\141', '\164', '\151', '\157', +'\156', '\163', '\040', '\050', '\143', '\141', '\156', '\040', '\157', '\156', '\154', '\171', '\040', '\142', '\145', '\040', +'\163', '\141', '\166', '\145', '\144', '\040', '\151', '\156', '\040', '\130', '\115', '\040', '\155', '\157', '\144', '\165', +'\154', '\145', '\163', '\051', '\012', '\072', '\012', '\072', '\040', '\040', '\126', '\157', '\154', '\165', '\155', '\145', +'\040', '\143', '\157', '\154', '\165', '\155', '\156', '\056', '\012', '\072', '\040', '\040', '\040', '\044', '\170', '\040', +'\123', '\145', '\164', '\040', '\166', '\151', '\142', '\162', '\141', '\164', '\157', '\040', '\163', '\160', '\145', '\145', +'\144', '\040', '\164', '\157', '\040', '\170', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\133', '\044', '\101', '\060', '\055', '\044', '\101', '\106', '\135', '\012', '\072', '\040', '\040', '\040', +'\074', '\170', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', '\154', '\151', '\144', '\145', +'\040', '\164', '\157', '\040', '\154', '\145', '\146', '\164', '\040', '\142', '\171', '\040', '\170', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\133', '\044', '\104', '\060', '\055', '\044', '\104', '\106', '\135', '\012', '\072', +'\040', '\040', '\040', '\076', '\170', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', '\154', +'\151', '\144', '\145', '\040', '\164', '\157', '\040', '\162', '\151', '\147', '\150', '\164', '\040', '\142', '\171', '\040', +'\170', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\044', '\105', '\060', '\055', '\044', '\105', '\106', +'\135', '\012', '\072', '\012', '\072', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\145', +'\146', '\146', '\145', '\143', '\164', '\163', '\056', '\012', '\072', '\040', '\040', '\040', '\041', '\170', '\170', '\040', +'\123', '\145', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\133', '\103', '\170', '\170', '\135', '\040', '\055', '\040', '\077', '\012', '\072', '\040', '\040', '\040', '\043', +'\061', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', +'\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\133', '\105', '\061', '\170', '\135', '\040', '\055', '\040', '\106', '\106', '\170', '\012', +'\072', '\040', '\040', '\040', '\043', '\062', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\160', '\151', '\164', +'\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', +'\040', '\170', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\062', '\170', '\135', '\040', '\055', +'\040', '\105', '\106', '\170', '\012', '\072', '\040', '\040', '\040', '\043', '\063', '\170', '\040', '\123', '\145', '\164', +'\040', '\147', '\154', '\151', '\163', '\163', '\141', '\156', '\144', '\157', '\040', '\143', '\157', '\156', '\164', '\162', +'\157', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', +'\063', '\170', '\135', '\040', '\055', '\040', '\123', '\061', '\170', '\040', '\050', '\165', '\156', '\151', '\155', '\160', +'\154', '\145', '\155', '\145', '\156', '\164', '\145', '\144', '\051', '\012', '\072', '\040', '\040', '\040', '\043', '\064', +'\170', '\040', '\123', '\145', '\164', '\040', '\166', '\151', '\142', '\162', '\141', '\164', '\157', '\040', '\143', '\157', +'\156', '\164', '\162', '\157', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\133', '\105', '\064', '\170', '\135', '\040', '\055', '\040', '\123', '\063', '\170', '\012', '\072', +'\040', '\040', '\040', '\043', '\065', '\170', '\040', '\123', '\145', '\164', '\040', '\146', '\151', '\156', '\145', '\164', +'\165', '\156', '\145', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\065', '\170', '\135', '\040', '\055', '\040', +'\123', '\062', '\170', '\040', '\050', '\165', '\156', '\151', '\155', '\160', '\154', '\145', '\155', '\145', '\156', '\164', +'\145', '\144', '\051', '\012', '\072', '\040', '\040', '\040', '\043', '\066', '\170', '\040', '\123', '\145', '\164', '\040', +'\154', '\157', '\157', '\160', '\040', '\142', '\145', '\147', '\151', '\156', '\040', '\057', '\040', '\154', '\157', '\157', +'\160', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\066', +'\170', '\135', '\040', '\055', '\040', '\123', '\102', '\170', '\012', '\072', '\040', '\040', '\040', '\043', '\067', '\170', +'\040', '\123', '\145', '\164', '\040', '\164', '\162', '\145', '\155', '\157', '\154', '\157', '\040', '\143', '\157', '\156', +'\164', '\162', '\157', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\133', '\105', '\067', '\170', '\135', '\040', '\055', '\040', '\123', '\064', '\170', '\012', '\072', '\040', +'\040', '\040', '\043', '\071', '\170', '\040', '\122', '\145', '\164', '\162', '\151', '\147', '\147', '\145', '\162', '\040', +'\156', '\157', '\164', '\145', '\040', '\145', '\166', '\145', '\162', '\171', '\040', '\170', '\040', '\164', '\151', '\143', +'\153', '\163', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\071', '\170', '\135', '\040', '\055', '\040', '\121', +'\060', '\170', '\012', '\072', '\040', '\040', '\040', '\043', '\101', '\170', '\040', '\106', '\151', '\156', '\145', '\040', +'\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', +'\142', '\171', '\040', '\170', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\101', '\170', +'\135', '\040', '\055', '\040', '\104', '\170', '\106', '\012', '\072', '\040', '\040', '\040', '\043', '\102', '\170', '\040', +'\106', '\151', '\156', '\145', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', +'\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\040', '\040', '\040', '\040', '\040', +'\040', '\133', '\105', '\102', '\170', '\135', '\040', '\055', '\040', '\104', '\106', '\170', '\012', '\072', '\040', '\040', +'\040', '\043', '\103', '\170', '\040', '\116', '\157', '\164', '\145', '\040', '\143', '\165', '\164', '\040', '\141', '\146', +'\164', '\145', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\103', '\170', '\135', '\040', '\055', '\040', '\123', '\103', +'\170', '\012', '\072', '\040', '\040', '\040', '\043', '\104', '\170', '\040', '\116', '\157', '\164', '\145', '\040', '\144', +'\145', '\154', '\141', '\171', '\040', '\146', '\157', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\104', '\170', '\135', +'\040', '\055', '\040', '\123', '\104', '\170', '\012', '\072', '\040', '\040', '\040', '\043', '\105', '\170', '\040', '\120', +'\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\145', '\154', '\141', '\171', '\040', '\146', '\157', '\162', +'\040', '\170', '\040', '\162', '\157', '\167', '\163', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\133', '\105', '\105', '\170', '\135', '\040', '\055', '\040', '\123', '\105', '\170', '\012', '\072', '\040', '\040', '\040', +'\044', '\170', '\170', '\040', '\113', '\145', '\171', '\040', '\157', '\146', '\146', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\133', '\113', '\170', '\170', '\135', '\040', '\055', '\040', '\077', '\012', '\072', +'\040', '\040', '\040', '\045', '\061', '\170', '\040', '\105', '\170', '\164', '\162', '\141', '\040', '\146', '\151', '\156', +'\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', +'\040', '\142', '\171', '\040', '\170', '\040', '\040', '\040', '\133', '\130', '\061', '\170', '\135', '\040', '\055', '\040', +'\106', '\105', '\170', '\012', '\072', '\040', '\040', '\040', '\045', '\062', '\170', '\040', '\105', '\170', '\164', '\162', +'\141', '\040', '\146', '\151', '\156', '\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', +'\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\040', '\133', '\130', '\062', +'\170', '\135', '\040', '\055', '\040', '\105', '\105', '\170', '\012', '\072', '\040', '\040', '\040', '\046', '\170', '\170', +'\040', '\123', '\145', '\164', '\040', '\145', '\156', '\166', '\145', '\154', '\157', '\160', '\145', '\040', '\160', '\157', +'\163', '\151', '\164', '\151', '\157', '\156', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\133', '\114', '\170', '\170', '\135', '\040', '\055', '\040', '\077', '\012', '\072', '\012', '\045', '\012', +'\174', '\012', '\174', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\105', '\144', '\151', '\164', +'\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\107', '\162', '\145', '\171', '\040', +'\053', '\054', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\145', '\170', '\164', +'\057', '\120', '\162', '\145', '\166', '\151', '\157', '\165', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', +'\156', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\123', '\150', '\151', '\146', '\164', +'\040', '\053', '\054', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\145', '\170', '\164', +'\057', '\120', '\162', '\145', '\166', '\151', '\157', '\165', '\163', '\040', '\064', '\040', '\160', '\141', '\164', '\164', +'\145', '\162', '\156', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', +'\154', '\040', '\053', '\054', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\145', +'\170', '\164', '\057', '\120', '\162', '\145', '\166', '\151', '\157', '\165', '\163', '\040', '\157', '\162', '\144', '\145', +'\162', '\047', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\060', '\055', '\071', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\157', '\143', '\164', +'\141', '\166', '\145', '\057', '\166', '\157', '\154', '\165', '\155', '\145', '\057', '\151', '\156', '\163', '\164', '\162', +'\165', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\060', '\055', '\071', '\054', '\040', '\101', +'\055', '\106', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', +'\145', '\040', '\145', '\146', '\146', '\145', '\143', '\164', '\040', '\166', '\141', '\154', '\165', '\145', '\012', '\174', +'\040', '\040', '\040', '\101', '\055', '\132', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\145', '\146', '\146', '\145', '\143', +'\164', '\012', '\174', '\040', '\040', '\040', '\056', '\040', '\050', '\120', '\145', '\162', '\151', '\157', '\144', '\051', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\154', '\145', '\141', '\162', '\040', '\146', '\151', '\145', +'\154', '\144', '\050', '\163', '\051', '\012', '\174', '\040', '\040', '\040', '\061', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\157', '\164', '\145', '\040', +'\143', '\165', '\164', '\040', '\050', '\136', '\136', '\136', '\051', '\012', '\174', '\040', '\040', '\040', '\140', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', +'\157', '\164', '\145', '\040', '\157', '\146', '\146', '\040', '\050', '\043', '\043', '\043', '\051', '\040', '\057', '\040', +'\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\012', '\174', +'\040', '\040', '\040', '\123', '\160', '\141', '\143', '\145', '\142', '\141', '\162', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\125', '\163', '\145', '\040', '\154', '\141', '\163', '\164', '\040', '\156', '\157', '\164', +'\145', '\057', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\057', '\166', '\157', '\154', +'\165', '\155', '\145', '\057', '\145', '\146', '\146', '\145', '\143', '\164', '\057', '\145', '\146', '\146', '\145', '\143', +'\164', '\040', '\166', '\141', '\154', '\165', '\145', '\012', '\174', '\040', '\040', '\040', '\103', '\141', '\160', '\163', +'\040', '\114', '\157', '\143', '\153', '\053', '\113', '\145', '\171', '\040', '\040', '\040', '\040', '\120', '\162', '\145', +'\166', '\151', '\145', '\167', '\040', '\156', '\157', '\164', '\145', '\012', '\174', '\012', '\174', '\040', '\040', '\040', +'\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\107', '\145', '\164', '\040', '\144', '\145', '\146', '\141', '\165', '\154', '\164', '\040', '\156', '\157', '\164', +'\145', '\057', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\057', '\166', '\157', '\154', +'\165', '\155', '\145', '\057', '\145', '\146', '\146', '\145', '\143', '\164', '\012', '\174', '\040', '\040', '\040', '\074', +'\040', '\157', '\162', '\040', '\103', '\164', '\162', '\154', '\055', '\125', '\160', '\040', '\040', '\040', '\040', '\040', +'\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', +'\145', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\076', '\040', '\157', '\162', '\040', '\103', '\164', '\162', +'\154', '\055', '\104', '\157', '\167', '\156', '\040', '\040', '\040', '\111', '\156', '\143', '\162', '\145', '\141', '\163', +'\145', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', +'\040', '\107', '\162', '\145', '\171', '\040', '\057', '\054', '\052', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\057', '\111', '\156', '\143', '\162', '\145', +'\141', '\163', '\145', '\040', '\157', '\143', '\164', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', '\054', +'\040', '\050', '\103', '\157', '\155', '\155', '\141', '\051', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\124', '\157', '\147', '\147', '\154', '\145', '\040', '\145', '\144', '\151', '\164', '\040', '\155', '\141', '\163', '\153', +'\040', '\146', '\157', '\162', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\146', '\151', '\145', +'\154', '\144', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\111', '\156', '\163', '\057', '\104', '\145', '\154', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', +'\057', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\141', '\040', '\162', '\157', '\167', '\040', '\164', '\157', +'\057', '\146', '\162', '\157', '\155', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\156', +'\163', '\057', '\104', '\145', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', +'\164', '\057', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\141', '\156', '\040', '\145', '\156', '\164', '\151', +'\162', '\145', '\040', '\162', '\157', '\167', '\040', '\164', '\157', '\057', '\146', '\162', '\157', '\155', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\012', '\174', '\040', +'\040', '\040', '\125', '\160', '\057', '\104', '\157', '\167', '\156', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\165', '\160', '\057', '\144', '\157', '\167', '\156', '\040', +'\142', '\171', '\040', '\164', '\150', '\145', '\040', '\163', '\153', '\151', '\160', '\166', '\141', '\154', '\165', '\145', +'\040', '\050', '\163', '\145', '\164', '\040', '\167', '\151', '\164', '\150', '\040', '\101', '\154', '\164', '\040', '\060', +'\055', '\071', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\110', '\157', '\155', +'\145', '\057', '\105', '\156', '\144', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\165', '\160', +'\057', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\061', '\040', '\162', '\157', '\167', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\125', '\160', '\057', '\104', '\157', '\167', '\156', '\040', '\040', +'\040', '\040', '\040', '\040', '\123', '\154', '\151', '\144', '\145', '\040', '\160', '\141', '\164', '\164', '\145', '\162', +'\156', '\040', '\165', '\160', '\057', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\061', '\040', '\162', +'\157', '\167', '\012', '\174', '\040', '\040', '\040', '\114', '\145', '\146', '\164', '\057', '\122', '\151', '\147', '\150', +'\164', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\143', '\165', '\162', +'\163', '\157', '\162', '\040', '\154', '\145', '\146', '\164', '\057', '\162', '\151', '\147', '\150', '\164', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\114', '\145', '\146', '\164', '\057', '\122', '\151', '\147', '\150', +'\164', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\146', '\157', '\162', '\167', '\141', '\162', '\144', +'\163', '\057', '\142', '\141', '\143', '\153', '\167', '\141', '\162', '\144', '\163', '\040', '\157', '\156', '\145', '\040', +'\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', '\124', '\141', '\142', '\057', +'\123', '\150', '\151', '\146', '\164', '\055', '\124', '\141', '\142', '\040', '\040', '\040', '\040', '\115', '\157', '\166', +'\145', '\040', '\146', '\157', '\162', '\167', '\141', '\162', '\144', '\163', '\057', '\142', '\141', '\143', '\153', '\167', +'\141', '\162', '\144', '\163', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\143', '\157', '\154', +'\165', '\155', '\156', '\012', '\174', '\040', '\040', '\040', '\120', '\147', '\125', '\160', '\057', '\120', '\147', '\104', +'\156', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\165', '\160', +'\057', '\144', '\157', '\167', '\156', '\040', '\156', '\040', '\154', '\151', '\156', '\145', '\163', '\040', '\050', '\156', +'\075', '\122', '\157', '\167', '\040', '\110', '\151', '\154', '\151', '\147', '\150', '\164', '\040', '\115', '\141', '\152', +'\157', '\162', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\120', '\147', '\125', +'\160', '\057', '\120', '\147', '\104', '\156', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\164', '\157', +'\040', '\164', '\157', '\160', '\057', '\142', '\157', '\164', '\164', '\157', '\155', '\040', '\157', '\146', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\110', '\157', '\155', '\145', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', +'\040', '\164', '\157', '\040', '\163', '\164', '\141', '\162', '\164', '\040', '\157', '\146', '\040', '\143', '\157', '\154', +'\165', '\155', '\156', '\057', '\163', '\164', '\141', '\162', '\164', '\040', '\157', '\146', '\040', '\154', '\151', '\156', +'\145', '\057', '\163', '\164', '\141', '\162', '\164', '\040', '\157', '\146', '\040', '\160', '\141', '\164', '\164', '\145', +'\162', '\156', '\012', '\174', '\040', '\040', '\040', '\105', '\156', '\144', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\164', '\157', '\040', +'\145', '\156', '\144', '\040', '\157', '\146', '\040', '\143', '\157', '\154', '\165', '\155', '\156', '\057', '\145', '\156', +'\144', '\040', '\157', '\146', '\040', '\154', '\151', '\156', '\145', '\057', '\145', '\156', '\144', '\040', '\157', '\146', +'\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\102', '\141', '\143', +'\153', '\163', '\160', '\141', '\143', '\145', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', +'\166', '\145', '\040', '\164', '\157', '\040', '\160', '\162', '\145', '\166', '\151', '\157', '\165', '\163', '\040', '\160', +'\157', '\163', '\151', '\164', '\151', '\157', '\156', '\040', '\050', '\141', '\143', '\143', '\157', '\165', '\156', '\164', +'\163', '\040', '\146', '\157', '\162', '\040', '\115', '\165', '\154', '\164', '\151', '\143', '\150', '\141', '\156', '\156', +'\145', '\154', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\116', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', +'\145', '\040', '\115', '\165', '\154', '\164', '\151', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\155', +'\157', '\144', '\145', '\040', '\146', '\157', '\162', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', +'\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\062', '\052', '\101', '\154', '\164', '\055', +'\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\165', '\154', +'\164', '\151', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\123', '\145', '\154', '\145', '\143', '\164', +'\151', '\157', '\156', '\040', '\155', '\145', '\156', '\165', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', +'\154', '\164', '\055', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\123', '\164', '\157', '\162', '\145', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', +'\164', '\141', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\102', '\141', '\143', '\153', '\163', +'\160', '\141', '\143', '\145', '\040', '\040', '\040', '\040', '\122', '\145', '\166', '\145', '\162', '\164', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\102', '\141', '\143', '\153', '\163', '\160', +'\141', '\143', '\145', '\040', '\040', '\040', '\125', '\156', '\144', '\157', '\040', '\055', '\040', '\141', '\156', '\171', +'\040', '\146', '\165', '\156', '\143', '\164', '\151', '\157', '\156', '\040', '\167', '\151', '\164', '\150', '\040', '\040', +'\050', '\052', '\051', '\040', '\143', '\141', '\156', '\040', '\142', '\145', '\040', '\165', '\156', '\144', '\157', '\156', +'\145', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\103', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', +'\143', '\145', '\156', '\164', '\162', '\141', '\154', '\151', '\163', '\145', '\040', '\143', '\165', '\162', '\163', '\157', +'\162', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\110', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\162', '\157', '\167', '\040', '\150', '\151', '\154', '\151', '\147', '\150', +'\164', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\126', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\144', '\145', +'\146', '\141', '\165', '\154', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\144', '\151', '\163', +'\160', '\154', '\141', '\171', '\012', '\174', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\104', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', +'\154', '\145', '\040', '\104', '\151', '\147', '\151', '\124', '\162', '\141', '\153', '\153', '\145', '\162', '\040', '\166', +'\157', '\157', '\144', '\157', '\157', '\040', '\050', '\043', '\040', '\055', '\076', '\040', '\142', '\051', '\012', '\072', +'\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\062', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\164', '\040', '\160', '\141', '\164', '\164', '\145', '\162', +'\156', '\040', '\154', '\145', '\156', '\147', '\164', '\150', '\012', '\174', '\012', '\174', '\040', '\040', '\124', '\162', +'\141', '\143', '\153', '\040', '\126', '\151', '\145', '\167', '\040', '\106', '\165', '\156', '\143', '\164', '\151', '\157', +'\156', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\124', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\171', '\143', '\154', '\145', '\040', '\143', +'\165', '\162', '\162', '\145', '\156', '\164', '\040', '\164', '\162', '\141', '\143', '\153', '\047', '\163', '\040', '\166', +'\151', '\145', '\167', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\122', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\154', '\145', '\141', '\162', '\040', '\141', +'\154', '\154', '\040', '\164', '\162', '\141', '\143', '\153', '\040', '\166', '\151', '\145', '\167', '\163', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\110', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\164', '\162', '\141', '\143', '\153', +'\040', '\166', '\151', '\145', '\167', '\040', '\144', '\151', '\166', '\151', '\163', '\151', '\157', '\156', '\163', '\012', +'\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\060', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\104', '\145', '\163', '\145', '\154', '\145', '\143', '\164', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\164', '\162', '\141', '\143', '\153', '\012', '\174', '\040', '\040', '\040', +'\103', '\164', '\162', '\154', '\055', '\061', '\040', '\055', '\040', '\103', '\164', '\162', '\154', '\055', '\065', '\040', +'\040', '\126', '\151', '\145', '\167', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\164', '\162', +'\141', '\143', '\153', '\040', '\151', '\156', '\040', '\163', '\143', '\150', '\145', '\155', '\145', '\040', '\061', '\055', +'\065', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\114', '\145', '\146', '\164', '\057', +'\122', '\151', '\147', '\150', '\164', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\154', '\145', '\146', '\164', +'\057', '\162', '\151', '\147', '\150', '\164', '\040', '\142', '\145', '\164', '\167', '\145', '\145', '\156', '\040', '\164', +'\162', '\141', '\143', '\153', '\040', '\166', '\151', '\145', '\167', '\040', '\143', '\157', '\154', '\165', '\155', '\156', +'\163', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\114', '\055', '\103', '\164', '\162', '\154', '\046', '\123', +'\150', '\151', '\146', '\164', '\040', '\061', '\055', '\064', '\040', '\121', '\165', '\151', '\143', '\153', '\040', '\166', +'\151', '\145', '\167', '\040', '\163', '\143', '\150', '\145', '\155', '\145', '\040', '\163', '\145', '\164', '\165', '\160', +'\012', '\174', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\124', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\126', +'\151', '\145', '\167', '\055', '\103', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\143', '\165', '\162', '\163', +'\157', '\162', '\055', '\164', '\162', '\141', '\143', '\153', '\151', '\156', '\147', '\012', '\174', '\012', '\174', '\040', +'\040', '\102', '\154', '\157', '\143', '\153', '\040', '\106', '\165', '\156', '\143', '\164', '\151', '\157', '\156', '\163', +'\056', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\102', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\141', '\162', '\153', '\040', '\142', '\145', '\147', '\151', +'\156', '\156', '\151', '\156', '\147', '\040', '\157', '\146', '\040', '\142', '\154', '\157', '\143', '\153', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\105', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\115', '\141', '\162', '\153', '\040', '\145', '\156', '\144', '\040', '\157', '\146', '\040', +'\142', '\154', '\157', '\143', '\153', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\104', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\121', '\165', '\151', '\143', '\153', +'\040', '\155', '\141', '\162', '\153', '\040', '\156', '\057', '\062', '\156', '\057', '\064', '\156', '\057', '\056', '\056', +'\056', '\040', '\154', '\151', '\156', '\145', '\163', '\040', '\050', '\156', '\075', '\122', '\157', '\167', '\040', '\110', +'\151', '\154', '\151', '\147', '\150', '\164', '\040', '\115', '\141', '\152', '\157', '\162', '\051', '\012', '\174', '\040', +'\040', '\040', '\101', '\154', '\164', '\055', '\114', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\115', '\141', '\162', '\153', '\040', '\145', '\156', '\164', '\151', '\162', '\145', '\040', '\143', +'\157', '\154', '\165', '\155', '\156', '\057', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', +'\040', '\040', '\123', '\150', '\151', '\146', '\164', '\055', '\101', '\162', '\162', '\157', '\167', '\163', '\040', '\040', +'\040', '\040', '\040', '\115', '\141', '\162', '\153', '\040', '\142', '\154', '\157', '\143', '\153', '\012', '\174', '\012', +'\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\125', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\125', '\156', '\155', '\141', '\162', '\153', '\040', '\142', '\154', '\157', '\143', +'\153', '\057', '\122', '\145', '\154', '\145', '\141', '\163', '\145', '\040', '\143', '\154', '\151', '\160', '\142', '\157', +'\141', '\162', '\144', '\040', '\155', '\145', '\155', '\157', '\162', '\171', '\012', '\174', '\012', '\174', '\040', '\040', +'\040', '\101', '\154', '\164', '\055', '\121', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\122', '\141', '\151', '\163', '\145', '\040', '\156', '\157', '\164', '\145', '\163', '\040', '\142', '\171', +'\040', '\141', '\040', '\163', '\145', '\155', '\151', '\164', '\157', '\156', '\145', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\101', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\114', '\157', '\167', '\145', '\162', '\040', '\156', '\157', '\164', '\145', +'\163', '\040', '\142', '\171', '\040', '\141', '\040', '\163', '\145', '\155', '\151', '\164', '\157', '\156', '\145', '\040', +'\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\123', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\164', '\040', '\111', '\156', +'\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', +'\040', '\040', '\101', '\154', '\164', '\055', '\126', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\123', '\145', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\057', '\160', '\141', +'\156', '\156', '\151', '\156', '\147', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', +'\154', '\164', '\055', '\127', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\127', '\151', '\160', '\145', '\040', '\166', '\157', '\154', '\057', '\160', '\141', '\156', '\040', '\156', '\157', '\164', +'\040', '\141', '\163', '\163', '\157', '\143', '\151', '\141', '\164', '\145', '\144', '\040', '\167', '\151', '\164', '\150', +'\040', '\141', '\040', '\156', '\157', '\164', '\145', '\057', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', +'\156', '\164', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\113', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\154', '\151', +'\144', '\145', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\057', '\160', '\141', '\156', '\156', '\151', '\156', +'\147', '\040', '\143', '\157', '\154', '\165', '\155', '\156', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', +'\062', '\052', '\101', '\154', '\164', '\055', '\113', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\127', '\151', '\160', '\145', '\040', '\141', '\154', '\154', '\040', '\166', '\157', '\154', '\165', +'\155', '\145', '\057', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\143', '\157', '\156', '\164', '\162', +'\157', '\154', '\163', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', +'\055', '\112', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\126', '\157', +'\154', '\165', '\155', '\145', '\040', '\141', '\155', '\160', '\154', '\151', '\146', '\151', '\145', '\162', '\040', '\040', +'\050', '\052', '\051', '\040', '\057', '\040', '\106', '\141', '\163', '\164', '\040', '\166', '\157', '\154', '\165', '\155', +'\145', '\040', '\141', '\164', '\164', '\145', '\156', '\165', '\141', '\164', '\145', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\132', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\103', '\165', '\164', '\040', '\142', '\154', '\157', '\143', '\153', '\040', +'\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\130', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\154', '\151', '\144', '\145', '\040', +'\145', '\146', '\146', '\145', '\143', '\164', '\040', '\166', '\141', '\154', '\165', '\145', '\040', '\040', '\050', '\052', +'\051', '\012', '\174', '\040', '\062', '\052', '\101', '\154', '\164', '\055', '\130', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\127', '\151', '\160', '\145', '\040', '\141', '\154', '\154', '\040', +'\145', '\146', '\146', '\145', '\143', '\164', '\040', '\144', '\141', '\164', '\141', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\103', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\157', '\160', '\171', '\040', '\142', '\154', '\157', +'\143', '\153', '\040', '\151', '\156', '\164', '\157', '\040', '\143', '\154', '\151', '\160', '\142', '\157', '\141', '\162', +'\144', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\120', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\141', '\163', '\164', '\145', '\040', '\144', '\141', '\164', +'\141', '\040', '\146', '\162', '\157', '\155', '\040', '\143', '\154', '\151', '\160', '\142', '\157', '\141', '\162', '\144', +'\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\117', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\117', '\166', '\145', '\162', '\167', +'\162', '\151', '\164', '\145', '\040', '\167', '\151', '\164', '\150', '\040', '\144', '\141', '\164', '\141', '\040', '\146', +'\162', '\157', '\155', '\040', '\143', '\154', '\151', '\160', '\142', '\157', '\141', '\162', '\144', '\040', '\040', '\040', +'\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\115', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\151', '\170', '\040', '\145', '\141', '\143', +'\150', '\040', '\162', '\157', '\167', '\040', '\146', '\162', '\157', '\155', '\040', '\143', '\154', '\151', '\160', '\142', +'\157', '\141', '\162', '\144', '\040', '\167', '\151', '\164', '\150', '\040', '\160', '\141', '\164', '\164', '\145', '\162', +'\156', '\040', '\144', '\141', '\164', '\141', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\062', '\052', +'\101', '\154', '\164', '\055', '\115', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\115', '\151', '\170', '\040', '\145', '\141', '\143', '\150', '\040', '\146', '\151', '\145', '\154', '\144', '\040', +'\146', '\162', '\157', '\155', '\040', '\143', '\154', '\151', '\160', '\142', '\157', '\141', '\162', '\144', '\040', '\167', +'\151', '\164', '\150', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', +'\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\106', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\157', '\165', '\142', '\154', '\145', '\040', '\142', +'\154', '\157', '\143', '\153', '\040', '\154', '\145', '\156', '\147', '\164', '\150', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\107', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\110', '\141', '\154', '\166', '\145', '\040', '\142', '\154', '\157', '\143', +'\153', '\040', '\154', '\145', '\156', '\147', '\164', '\150', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\012', +'\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\123', '\145', '\154', '\145', '\143', '\164', '\040', '\124', '\145', '\155', '\160', +'\154', '\141', '\164', '\145', '\040', '\155', '\157', '\144', '\145', '\040', '\057', '\040', '\106', '\141', '\163', '\164', +'\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\141', '\155', '\160', '\154', '\151', '\146', '\171', '\040', +'\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\112', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', +'\040', '\146', '\141', '\163', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\155', '\157', '\144', +'\145', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\125', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\154', '\145', '\143', '\164', '\151', '\157', '\156', +'\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\166', '\141', '\162', '\171', '\040', '\057', '\040', '\106', +'\141', '\163', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\166', '\141', '\162', '\171', '\040', +'\050', '\052', '\051', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\131', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\154', '\145', '\143', '\164', '\151', +'\157', '\156', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\166', '\141', '\162', '\171', '\040', +'\057', '\040', '\106', '\141', '\163', '\164', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\166', +'\141', '\162', '\171', '\040', '\050', '\052', '\051', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', +'\055', '\113', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\154', +'\145', '\143', '\164', '\151', '\157', '\156', '\040', '\145', '\146', '\146', '\145', '\143', '\164', '\040', '\166', '\141', +'\162', '\171', '\040', '\057', '\040', '\106', '\141', '\163', '\164', '\040', '\145', '\146', '\146', '\145', '\143', '\164', +'\040', '\166', '\141', '\162', '\171', '\040', '\050', '\052', '\051', '\012', '\174', '\012', '\174', '\040', '\120', '\154', +'\141', '\171', '\142', '\141', '\143', '\153', '\040', '\106', '\165', '\156', '\143', '\164', '\151', '\157', '\156', '\163', +'\056', '\012', '\174', '\040', '\040', '\040', '\064', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\156', '\157', '\164', '\145', +'\040', '\165', '\156', '\144', '\145', '\162', '\040', '\143', '\165', '\162', '\163', '\157', '\162', '\012', '\174', '\040', +'\040', '\040', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\162', '\157', '\167', '\012', '\174', '\012', '\174', '\040', +'\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\066', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\146', '\162', '\157', '\155', '\040', '\143', '\165', '\162', +'\162', '\145', '\156', '\164', '\040', '\162', '\157', '\167', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', +'\154', '\055', '\106', '\067', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', +'\164', '\057', '\103', '\154', '\145', '\141', '\162', '\040', '\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', +'\040', '\155', '\141', '\162', '\153', '\040', '\050', '\146', '\157', '\162', '\040', '\165', '\163', '\145', '\040', '\167', +'\151', '\164', '\150', '\040', '\106', '\067', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\106', '\071', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', +'\157', '\147', '\147', '\154', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\106', '\061', +'\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\154', '\157', '\040', +'\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', +'\174', '\012', '\174', '\040', '\040', '\040', '\123', '\143', '\162', '\157', '\154', '\154', '\040', '\114', '\157', '\143', +'\153', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\160', '\154', +'\141', '\171', '\142', '\141', '\143', '\153', '\040', '\164', '\162', '\141', '\143', '\151', '\156', '\147', '\012', '\174', +'\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\132', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\115', '\111', '\104', '\111', '\040', +'\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', '\040', '\164', '\162', '\151', '\147', '\147', '\145', '\162', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\123', '\143', '\162', '\157', '\154', '\154', '\040', +'\114', '\157', '\143', '\153', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\115', '\111', '\104', +'\111', '\040', '\151', '\156', '\160', '\165', '\164', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\040', '\123', '\141', '\155', +'\160', '\154', '\145', '\040', '\114', '\151', '\163', '\164', '\040', '\040', '\040', '\221', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', '\174', +'\012', '\174', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\114', '\151', '\163', '\164', '\040', '\113', +'\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\114', '\157', '\141', '\144', '\040', '\156', '\145', '\167', '\040', '\163', +'\141', '\155', '\160', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\124', '\141', '\142', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\142', '\145', '\164', '\167', +'\145', '\145', '\156', '\040', '\157', '\160', '\164', '\151', '\157', '\156', '\163', '\012', '\174', '\040', '\040', '\040', +'\120', '\147', '\125', '\160', '\057', '\120', '\147', '\104', '\156', '\040', '\040', '\040', '\040', '\115', '\157', '\166', +'\145', '\040', '\165', '\160', '\057', '\144', '\157', '\167', '\156', '\040', '\050', '\167', '\150', '\145', '\156', '\040', +'\156', '\157', '\164', '\040', '\157', '\156', '\040', '\154', '\151', '\163', '\164', '\051', '\012', '\174', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\101', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\103', '\157', '\156', '\166', '\145', '\162', '\164', '\040', '\123', '\151', '\147', '\156', '\145', '\144', '\040', '\164', +'\157', '\057', '\146', '\162', '\157', '\155', '\040', '\125', '\156', '\163', '\151', '\147', '\156', '\145', '\144', '\040', +'\163', '\141', '\155', '\160', '\154', '\145', '\163', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\162', '\145', '\055', '\114', '\157', '\157', +'\160', '\040', '\143', '\165', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\012', '\174', '\040', '\040', +'\040', '\101', '\154', '\164', '\055', '\103', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\154', +'\145', '\141', '\162', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\116', '\141', '\155', '\145', '\040', +'\046', '\040', '\106', '\151', '\154', '\145', '\156', '\141', '\155', '\145', '\040', '\050', '\125', '\163', '\145', '\144', +'\040', '\151', '\156', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\116', '\141', '\155', '\145', '\040', +'\167', '\151', '\156', '\144', '\157', '\167', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\104', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', +'\123', '\141', '\155', '\160', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\105', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\163', '\151', '\172', '\145', '\040', '\123', +'\141', '\155', '\160', '\154', '\145', '\040', '\050', '\167', '\151', '\164', '\150', '\040', '\151', '\156', '\164', '\145', +'\162', '\160', '\157', '\154', '\141', '\164', '\151', '\157', '\156', '\051', '\012', '\174', '\040', '\040', '\040', '\101', +'\154', '\164', '\055', '\106', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\163', '\151', +'\172', '\145', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\050', '\167', '\151', '\164', '\150', '\157', +'\165', '\164', '\040', '\151', '\156', '\164', '\145', '\162', '\160', '\157', '\154', '\141', '\164', '\151', '\157', '\156', +'\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\107', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\122', '\145', '\166', '\145', '\162', '\163', '\145', '\040', '\123', '\141', '\155', '\160', '\154', +'\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\110', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\103', '\145', '\156', '\164', '\162', '\141', '\154', '\151', '\163', '\145', '\040', '\123', '\141', +'\155', '\160', '\154', '\145', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\166', '\145', '\162', '\164', '\040', '\123', '\141', '\155', +'\160', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\114', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\120', '\157', '\163', '\164', '\055', '\114', '\157', '\157', '\160', '\040', '\143', +'\165', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\115', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\155', '\160', '\154', +'\145', '\040', '\141', '\155', '\160', '\154', '\151', '\146', '\151', '\145', '\162', '\012', '\174', '\040', '\040', '\040', +'\101', '\154', '\164', '\055', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', +'\147', '\154', '\145', '\040', '\115', '\165', '\154', '\164', '\151', '\143', '\150', '\141', '\156', '\156', '\145', '\154', +'\040', '\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\117', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', +'\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\164', +'\157', '\040', '\144', '\151', '\163', '\153', '\040', '\050', '\111', '\124', '\040', '\106', '\157', '\162', '\155', '\141', +'\164', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\121', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\040', '\161', '\165', '\141', '\154', '\151', '\164', '\171', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\122', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\160', '\154', '\141', +'\143', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\040', '\151', '\156', '\040', '\163', '\157', '\156', '\147', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\167', '\141', '\160', '\040', +'\163', '\141', '\155', '\160', '\154', '\145', '\040', '\050', '\151', '\156', '\040', '\163', '\157', '\156', '\147', '\040', +'\141', '\154', '\163', '\157', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\124', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\143', '\165', '\162', '\162', +'\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\164', '\157', '\040', '\144', '\151', +'\163', '\153', '\040', '\050', '\105', '\170', '\160', '\157', '\162', '\164', '\040', '\106', '\157', '\162', '\155', '\141', +'\164', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\127', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', +'\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\164', '\157', '\040', '\144', '\151', '\163', '\153', '\040', +'\050', '\122', '\101', '\127', '\040', '\106', '\157', '\162', '\155', '\141', '\164', '\051', '\012', '\174', '\040', '\040', +'\040', '\101', '\154', '\164', '\055', '\130', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', '\170', +'\143', '\150', '\141', '\156', '\147', '\145', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\050', '\157', +'\156', '\154', '\171', '\040', '\151', '\156', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\114', '\151', +'\163', '\164', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\156', +'\163', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\040', '\163', '\141', +'\155', '\160', '\154', '\145', '\040', '\163', '\154', '\157', '\164', '\040', '\050', '\165', '\160', '\144', '\141', '\164', +'\145', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\104', '\145', '\154', '\040', '\040', '\040', '\040', +'\040', '\040', '\122', '\145', '\155', '\157', '\166', '\145', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', +'\163', '\154', '\157', '\164', '\040', '\050', '\165', '\160', '\144', '\141', '\164', '\145', '\163', '\040', '\160', '\141', +'\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\051', '\012', '\174', '\012', '\174', '\040', +'\040', '\040', '\074', '\040', '\076', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', +'\145', '\143', '\162', '\145', '\141', '\163', '\145', '\057', '\111', '\156', '\143', '\162', '\145', '\141', '\163', '\145', +'\040', '\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', '\040', '\143', '\150', '\141', '\156', '\156', '\145', +'\154', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\107', '\162', '\145', '\171', +'\040', '\053', '\040', '\040', '\040', '\111', '\156', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\103', '\055', +'\065', '\040', '\106', '\162', '\145', '\161', '\165', '\145', '\156', '\143', '\171', '\040', '\142', '\171', '\040', '\061', +'\040', '\157', '\143', '\164', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\107', '\162', '\145', '\171', '\040', '\055', '\040', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', +'\145', '\040', '\103', '\055', '\065', '\040', '\106', '\162', '\145', '\161', '\165', '\145', '\156', '\143', '\171', '\040', +'\142', '\171', '\040', '\061', '\040', '\157', '\143', '\164', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', +'\103', '\164', '\162', '\154', '\055', '\107', '\162', '\145', '\171', '\040', '\053', '\040', '\040', '\111', '\156', '\143', +'\162', '\145', '\141', '\163', '\145', '\040', '\103', '\055', '\065', '\040', '\106', '\162', '\145', '\161', '\165', '\145', +'\156', '\143', '\171', '\040', '\142', '\171', '\040', '\061', '\040', '\163', '\145', '\155', '\151', '\164', '\157', '\156', +'\145', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\107', '\162', '\145', '\171', '\040', +'\055', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\103', '\055', '\065', '\040', +'\106', '\162', '\145', '\161', '\165', '\145', '\156', '\143', '\171', '\040', '\142', '\171', '\040', '\061', '\040', '\163', +'\145', '\155', '\151', '\164', '\157', '\156', '\145', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\040', '\111', '\156', '\163', +'\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\114', '\151', '\163', '\164', '\040', '\040', '\040', '\221', +'\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\226', '\012', '\174', '\012', '\174', '\040', '\111', '\156', '\163', '\164', '\162', '\165', '\155', +'\145', '\156', '\164', '\040', '\114', '\151', '\163', '\164', '\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', +'\040', '\040', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\114', '\157', '\141', '\144', '\040', '\156', '\145', '\167', '\040', '\151', '\156', '\163', '\164', +'\162', '\165', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\120', '\147', '\125', '\160', '\057', '\120', '\147', '\104', '\156', '\040', '\040', '\115', '\157', '\166', '\145', '\040', +'\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\165', '\160', '\057', '\144', '\157', +'\167', '\156', '\040', '\050', '\167', '\150', '\145', '\156', '\040', '\156', '\157', '\164', '\040', '\157', '\156', '\040', +'\154', '\151', '\163', '\164', '\051', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\102', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\162', '\145', '\055', '\114', '\157', +'\157', '\160', '\040', '\143', '\165', '\164', '\040', '\145', '\156', '\166', '\145', '\154', '\157', '\160', '\145', '\012', +'\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\103', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\103', '\154', '\145', '\141', '\162', '\040', '\151', '\156', '\163', '\164', '\162', '\165', +'\155', '\145', '\156', '\164', '\040', '\156', '\141', '\155', '\145', '\040', '\046', '\040', '\146', '\151', '\154', '\145', +'\156', '\141', '\155', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\127', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\127', '\151', '\160', '\145', '\040', '\151', '\156', +'\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\144', '\141', '\164', '\141', '\012', '\174', '\040', +'\040', '\040', '\123', '\160', '\141', '\143', '\145', '\142', '\141', '\162', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\105', '\144', '\151', '\164', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', +'\164', '\040', '\156', '\141', '\155', '\145', '\040', '\050', '\105', '\123', '\103', '\040', '\164', '\157', '\040', '\145', +'\170', '\151', '\164', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\104', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', +'\145', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\046', '\040', '\141', +'\154', '\154', '\040', '\162', '\145', '\154', '\141', '\164', '\145', '\144', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\163', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\114', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\157', '\163', '\164', '\055', '\114', '\157', '\157', '\160', +'\040', '\143', '\165', '\164', '\040', '\145', '\156', '\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', +'\040', '\040', '\101', '\154', '\164', '\055', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\115', '\165', '\154', '\164', '\151', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\040', '\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\117', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', +'\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\164', '\157', '\040', '\144', '\151', +'\163', '\153', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\120', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\157', '\160', '\171', '\040', '\151', '\156', '\163', '\164', +'\162', '\165', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\122', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\160', '\154', '\141', +'\143', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\151', '\156', '\163', '\164', '\162', +'\165', '\155', '\145', '\156', '\164', '\040', '\151', '\156', '\040', '\163', '\157', '\156', '\147', '\012', '\174', '\040', +'\040', '\040', '\101', '\154', '\164', '\055', '\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\123', '\167', '\141', '\160', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', +'\164', '\163', '\040', '\050', '\151', '\156', '\040', '\163', '\157', '\156', '\147', '\040', '\141', '\154', '\163', '\157', +'\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\125', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\125', '\160', '\144', '\141', '\164', '\145', '\040', '\160', '\141', '\164', +'\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\130', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', '\170', +'\143', '\150', '\141', '\156', '\147', '\145', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', +'\164', '\163', '\040', '\050', '\157', '\156', '\154', '\171', '\040', '\151', '\156', '\040', '\111', '\156', '\163', '\164', +'\162', '\165', '\155', '\145', '\156', '\164', '\040', '\114', '\151', '\163', '\164', '\051', '\012', '\174', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\156', '\163', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\040', '\151', '\156', '\163', '\164', '\162', '\165', +'\155', '\145', '\156', '\164', '\040', '\163', '\154', '\157', '\164', '\040', '\050', '\165', '\160', '\144', '\141', '\164', +'\145', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\104', '\145', '\154', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\122', '\145', '\155', '\157', '\166', '\145', '\040', '\151', '\156', '\163', '\164', +'\162', '\165', '\155', '\145', '\156', '\164', '\040', '\163', '\154', '\157', '\164', '\040', '\050', '\165', '\160', '\144', +'\141', '\164', '\145', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', +'\141', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\074', '\040', '\076', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', +'\145', '\057', '\111', '\156', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\160', '\154', '\141', '\171', '\142', +'\141', '\143', '\153', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\012', '\174', '\040', +'\116', '\157', '\164', '\145', '\040', '\124', '\162', '\141', '\156', '\163', '\154', '\141', '\164', '\151', '\157', '\156', +'\056', '\012', '\174', '\040', '\040', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\120', '\151', '\143', '\153', '\165', '\160', '\040', '\163', '\141', '\155', +'\160', '\154', '\145', '\040', '\156', '\165', '\155', '\142', '\145', '\162', '\040', '\046', '\040', '\144', '\145', '\146', +'\141', '\165', '\154', '\164', '\040', '\160', '\154', '\141', '\171', '\040', '\156', '\157', '\164', '\145', '\012', '\174', +'\040', '\040', '\040', '\074', '\040', '\076', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\057', '\111', '\156', '\143', '\162', +'\145', '\141', '\163', '\145', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\156', '\165', '\155', '\142', +'\145', '\162', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\101', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', +'\141', '\154', '\154', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\163', '\012', '\174', '\040', '\040', '\040', +'\101', '\154', '\164', '\055', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\105', '\156', '\164', '\145', '\162', '\040', '\156', '\145', '\170', '\164', '\040', '\156', '\157', '\164', '\145', '\012', +'\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\120', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\160', '\162', '\145', '\166', '\151', '\157', +'\165', '\163', '\040', '\156', '\157', '\164', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\125', '\160', '\057', '\104', '\157', '\167', '\156', '\040', '\040', '\040', '\040', '\040', '\124', '\162', '\141', '\156', +'\163', '\160', '\157', '\163', '\145', '\040', '\141', '\154', '\154', '\040', '\156', '\157', '\164', '\145', '\163', '\040', +'\141', '\040', '\163', '\145', '\155', '\151', '\164', '\157', '\156', '\145', '\040', '\165', '\160', '\057', '\144', '\157', +'\167', '\156', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\156', '\163', '\057', '\104', +'\145', '\154', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\057', '\104', '\145', +'\154', '\145', '\164', '\145', '\040', '\141', '\040', '\162', '\157', '\167', '\040', '\146', '\162', '\157', '\155', '\040', +'\164', '\150', '\145', '\040', '\164', '\141', '\142', '\154', '\145', '\012', '\174', '\012', '\174', '\040', '\105', '\156', +'\166', '\145', '\154', '\157', '\160', '\145', '\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', +'\040', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\120', '\151', '\143', '\153', '\040', '\165', '\160', '\057', '\104', '\162', '\157', '\160', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\156', '\157', '\144', '\145', '\012', '\174', '\040', '\040', '\040', '\111', +'\156', '\163', '\145', '\162', '\164', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', +'\144', '\144', '\040', '\156', '\157', '\144', '\145', '\012', '\174', '\040', '\040', '\040', '\104', '\145', '\154', '\145', +'\164', '\145', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', +'\164', '\145', '\040', '\156', '\157', '\144', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\101', '\162', '\162', '\157', '\167', '\040', '\113', '\145', '\171', '\163', '\040', '\040', '\115', '\157', '\166', '\145', +'\040', '\156', '\157', '\144', '\145', '\040', '\050', '\146', '\141', '\163', '\164', '\051', '\012', '\174', '\012', '\174', +'\040', '\040', '\040', '\120', '\162', '\145', '\163', '\163', '\040', '\123', '\160', '\141', '\143', '\145', '\142', '\141', +'\162', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\144', '\145', '\146', '\141', '\165', '\154', '\164', '\040', +'\156', '\157', '\164', '\145', '\012', '\174', '\040', '\040', '\040', '\122', '\145', '\154', '\145', '\141', '\163', '\145', +'\040', '\123', '\160', '\141', '\143', '\145', '\040', '\040', '\040', '\116', '\157', '\164', '\145', '\040', '\157', '\146', +'\146', '\040', '\143', '\157', '\155', '\155', '\141', '\156', '\144', '\012', '\000', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\111', '\156', +'\146', '\157', '\040', '\120', '\141', '\147', '\145', '\040', '\040', '\221', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', '\174', '\012', '\174', '\040', '\111', +'\156', '\163', '\145', '\162', '\164', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\101', '\144', '\144', '\040', '\141', '\040', '\156', '\145', '\167', '\040', '\167', '\151', '\156', '\144', '\157', +'\167', '\012', '\174', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\143', '\165', '\162', +'\162', '\145', '\156', '\164', '\040', '\167', '\151', '\156', '\144', '\157', '\167', '\012', '\174', '\040', '\124', '\141', +'\142', '\057', '\123', '\150', '\151', '\146', '\164', '\055', '\124', '\141', '\142', '\040', '\040', '\040', '\040', '\040', +'\115', '\157', '\166', '\145', '\040', '\142', '\145', '\164', '\167', '\145', '\145', '\156', '\040', '\167', '\151', '\156', +'\144', '\157', '\167', '\163', '\012', '\174', '\040', '\125', '\160', '\057', '\104', '\156', '\057', '\114', '\145', '\146', +'\164', '\057', '\122', '\151', '\147', '\150', '\164', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\150', '\151', +'\147', '\150', '\154', '\151', '\147', '\150', '\164', '\145', '\144', '\040', '\143', '\150', '\141', '\156', '\156', '\145', +'\154', '\012', '\174', '\040', '\120', '\147', '\125', '\160', '\057', '\120', '\147', '\104', '\156', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\167', '\151', '\156', +'\144', '\157', '\167', '\040', '\164', '\171', '\160', '\145', '\012', '\174', '\040', '\101', '\154', '\164', '\055', '\125', +'\160', '\057', '\104', '\157', '\167', '\156', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', +'\145', '\040', '\167', '\151', '\156', '\144', '\157', '\167', '\040', '\142', '\141', '\163', '\145', '\040', '\165', '\160', +'\057', '\144', '\157', '\167', '\156', '\012', '\174', '\012', '\174', '\040', '\126', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', +'\154', '\145', '\040', '\142', '\145', '\164', '\167', '\145', '\145', '\156', '\040', '\166', '\157', '\154', '\165', '\155', +'\145', '\057', '\166', '\145', '\154', '\157', '\143', '\151', '\164', '\171', '\040', '\142', '\141', '\162', '\163', '\012', +'\174', '\040', '\111', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\142', '\145', '\164', '\167', '\145', +'\145', '\156', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\057', '\151', '\156', '\163', '\164', '\162', '\165', +'\155', '\145', '\156', '\164', '\040', '\156', '\141', '\155', '\145', '\163', '\012', '\174', '\012', '\174', '\040', '\121', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\115', '\165', '\164', '\145', '\057', '\125', '\156', '\155', '\165', '\164', '\145', '\040', '\143', '\165', '\162', +'\162', '\145', '\156', '\164', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\123', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\123', '\157', '\154', '\157', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\012', '\174', '\012', '\174', '\040', '\107', '\162', '\145', '\171', '\040', '\053', +'\054', '\040', '\107', '\162', '\145', '\171', '\040', '\055', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', +'\040', '\146', '\157', '\162', '\167', '\141', '\162', '\144', '\163', '\057', '\142', '\141', '\143', '\153', '\167', '\141', +'\162', '\144', '\163', '\040', '\157', '\156', '\145', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', +'\151', '\156', '\040', '\163', '\157', '\156', '\147', '\012', '\174', '\012', '\174', '\040', '\101', '\154', '\164', '\055', +'\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', +'\147', '\147', '\154', '\145', '\040', '\123', '\164', '\145', '\162', '\145', '\157', '\040', '\160', '\154', '\141', '\171', +'\142', '\141', '\143', '\153', '\012', '\174', '\040', '\101', '\154', '\164', '\055', '\122', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\166', '\145', '\162', '\163', '\145', +'\040', '\157', '\165', '\164', '\160', '\165', '\164', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\163', +'\012', '\174', '\012', '\174', '\040', '\107', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\157', '\164', '\157', '\040', '\160', '\141', '\164', '\164', +'\145', '\162', '\156', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\154', '\171', '\040', '\160', '\154', +'\141', '\171', '\151', '\156', '\147', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\115', '\145', '\163', '\163', '\141', +'\147', '\145', '\040', '\105', '\144', '\151', '\164', '\157', '\162', '\040', '\040', '\221', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', +'\174', '\012', '\174', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\057', '\040', '\105', '\123', '\103', '\040', +'\040', '\040', '\040', '\040', '\105', '\144', '\151', '\164', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', +'\040', '\057', '\040', '\146', '\151', '\156', '\151', '\163', '\150', '\145', '\144', '\040', '\145', '\144', '\151', '\164', +'\151', '\156', '\147', '\012', '\174', '\012', '\174', '\040', '\105', '\144', '\151', '\164', '\151', '\156', '\147', '\040', +'\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\131', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\154', +'\151', '\156', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\103', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\103', '\154', '\145', '\141', '\162', '\040', '\155', '\145', '\163', '\163', +'\141', '\147', '\145', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\114', +'\151', '\163', '\164', '\040', '\141', '\156', '\144', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', +'\040', '\221', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', '\174', '\012', '\174', '\040', '\117', '\162', +'\144', '\145', '\162', '\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\116', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\111', '\156', '\163', '\145', '\162', '\164', '\040', '\156', '\145', '\170', '\164', '\040', '\160', '\141', '\164', '\164', +'\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', '\156', '\144', '\040', '\157', '\146', +'\040', '\163', '\157', '\156', '\147', '\040', '\155', '\141', '\162', '\153', '\012', '\174', '\040', '\040', '\040', '\053', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\123', '\153', '\151', '\160', '\040', '\164', '\157', '\040', '\156', '\145', '\170', '\164', '\040', '\117', '\162', +'\144', '\145', '\162', '\040', '\155', '\141', '\162', '\153', '\012', '\174', '\040', '\040', '\040', '\111', '\156', '\163', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', +'\156', '\163', '\145', '\162', '\164', '\040', '\141', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', +'\174', '\040', '\040', '\040', '\104', '\145', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\141', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\124', '\141', '\142', '\057', '\123', +'\150', '\151', '\146', '\164', '\055', '\124', '\141', '\142', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', +'\145', '\040', '\164', '\157', '\040', '\156', '\145', '\170', '\164', '\040', '\167', '\151', '\156', '\144', '\157', '\167', +'\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\067', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\164', '\150', '\151', '\163', +'\040', '\117', '\162', '\144', '\145', '\162', '\040', '\156', '\145', '\170', '\164', '\012', '\072', '\012', '\072', '\040', +'\040', '\040', '\103', '\164', '\162', '\154', '\055', '\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\114', '\151', '\156', '\153', '\040', '\050', '\144', '\151', '\163', '\153', '\167', '\162', +'\151', '\164', '\145', '\162', '\051', '\040', '\164', '\150', '\151', '\163', '\040', '\160', '\141', '\164', '\164', '\145', +'\162', '\156', '\040', '\164', '\157', '\040', '\164', '\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', +'\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', +'\154', '\055', '\117', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', +'\157', '\160', '\171', '\040', '\050', '\144', '\151', '\163', '\153', '\167', '\162', '\151', '\164', '\145', '\162', '\051', +'\040', '\164', '\150', '\151', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\164', '\157', +'\040', '\164', '\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', +'\160', '\154', '\145', '\012', '\072', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\105', '\156', +'\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', +'\040', '\157', '\162', '\144', '\145', '\162', '\154', '\151', '\163', '\164', '\012', '\072', '\040', '\040', '\040', '\101', +'\154', '\164', '\055', '\102', '\141', '\143', '\153', '\163', '\160', '\141', '\143', '\145', '\040', '\040', '\040', '\040', +'\040', '\123', '\167', '\141', '\160', '\040', '\157', '\162', '\144', '\145', '\162', '\154', '\151', '\163', '\164', '\040', +'\167', '\151', '\164', '\150', '\040', '\163', '\141', '\166', '\145', '\144', '\040', '\157', '\162', '\144', '\145', '\162', +'\154', '\151', '\163', '\164', '\012', '\174', '\012', '\174', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', +'\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\114', '\057', '\115', '\057', '\122', +'\057', '\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\164', +'\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\164', '\157', '\040', '\114', '\145', '\146', '\164', +'\057', '\115', '\151', '\144', '\144', '\154', '\145', '\057', '\122', '\151', '\147', '\150', '\164', '\057', '\123', '\165', +'\162', '\162', '\157', '\165', '\156', '\144', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', +'\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\114', +'\151', '\163', '\164', '\040', '\141', '\156', '\144', '\040', '\103', '\150', '\141', '\156', '\156', '\145', '\154', '\040', +'\126', '\157', '\154', '\165', '\155', '\145', '\040', '\040', '\221', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\226', '\012', '\174', '\012', '\174', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\113', '\145', '\171', +'\163', '\056', '\012', '\174', '\040', '\040', '\040', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\040', +'\156', '\145', '\170', '\164', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', +'\040', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\105', '\156', '\144', '\040', '\157', '\146', '\040', '\163', '\157', '\156', '\147', '\040', '\155', +'\141', '\162', '\153', '\012', '\174', '\040', '\040', '\040', '\053', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\153', '\151', '\160', '\040', '\164', +'\157', '\040', '\156', '\145', '\170', '\164', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\155', '\141', '\162', +'\153', '\012', '\174', '\040', '\040', '\040', '\111', '\156', '\163', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\040', '\141', +'\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\104', '\145', '\154', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', +'\145', '\154', '\145', '\164', '\145', '\040', '\141', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', +'\174', '\040', '\040', '\040', '\124', '\141', '\142', '\057', '\123', '\150', '\151', '\146', '\164', '\055', '\124', '\141', +'\142', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\164', '\157', '\040', '\156', '\145', +'\170', '\164', '\040', '\167', '\151', '\156', '\144', '\157', '\167', '\012', '\174', '\040', '\040', '\040', '\103', '\164', +'\162', '\154', '\055', '\106', '\067', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\120', '\154', '\141', '\171', '\040', '\164', '\150', '\151', '\163', '\040', '\117', '\162', '\144', '\145', '\162', '\040', +'\156', '\145', '\170', '\164', '\012', '\072', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\114', '\151', '\156', +'\153', '\040', '\050', '\144', '\151', '\163', '\153', '\167', '\162', '\151', '\164', '\145', '\162', '\051', '\040', '\164', +'\150', '\151', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\164', '\157', '\040', '\164', +'\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\117', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\157', '\160', '\171', '\040', '\050', '\144', '\151', +'\163', '\153', '\167', '\162', '\151', '\164', '\145', '\162', '\051', '\040', '\164', '\150', '\151', '\163', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\040', '\164', '\157', '\040', '\164', '\150', '\145', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\012', '\072', '\012', '\072', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\157', '\162', '\144', '\145', '\162', '\154', +'\151', '\163', '\164', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\102', '\141', '\143', '\153', +'\163', '\160', '\141', '\143', '\145', '\040', '\040', '\040', '\040', '\040', '\123', '\167', '\141', '\160', '\040', '\157', +'\162', '\144', '\145', '\162', '\154', '\151', '\163', '\164', '\040', '\167', '\151', '\164', '\150', '\040', '\163', '\141', +'\166', '\145', '\144', '\040', '\157', '\162', '\144', '\145', '\162', '\154', '\151', '\163', '\164', '\012', '\000', '\174', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', +'\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\204', '\040', '\040', '\115', '\111', '\104', '\111', '\040', '\117', '\165', '\164', '\160', '\165', '\164', '\040', +'\040', '\221', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\211', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\226', '\012', '\174', '\012', '\174', '\040', '\115', '\111', '\104', '\111', '\040', '\157', '\165', +'\164', '\160', '\165', '\164', '\040', '\143', '\141', '\165', '\163', '\145', '\163', '\040', '\115', '\111', '\104', '\111', +'\040', '\144', '\141', '\164', '\141', '\040', '\164', '\157', '\040', '\142', '\145', '\040', '\163', '\145', '\156', '\164', +'\040', '\164', '\157', '\040', '\141', '\154', '\154', '\040', '\145', '\156', '\141', '\142', '\154', '\145', '\144', '\040', +'\157', '\165', '\164', '\160', '\165', '\164', '\040', '\160', '\157', '\162', '\164', '\163', '\040', '\157', '\156', '\040', +'\164', '\150', '\145', '\012', '\174', '\040', '\115', '\111', '\104', '\111', '\040', '\143', '\157', '\156', '\146', '\151', +'\147', '\165', '\162', '\141', '\164', '\151', '\157', '\156', '\040', '\160', '\141', '\147', '\145', '\040', '\050', '\123', +'\150', '\151', '\146', '\164', '\055', '\106', '\061', '\051', '\040', '\167', '\150', '\145', '\156', '\145', '\166', '\145', +'\162', '\040', '\164', '\150', '\145', '\040', '\160', '\154', '\141', '\171', '\145', '\162', '\040', '\163', '\145', '\145', +'\163', '\040', '\141', '\156', '\040', '\145', '\166', '\145', '\156', '\164', '\056', '\012', '\174', '\012', '\174', '\040', +'\124', '\150', '\145', '\040', '\144', '\141', '\164', '\141', '\040', '\141', '\143', '\164', '\165', '\141', '\154', '\154', +'\171', '\040', '\163', '\145', '\156', '\164', '\040', '\151', '\163', '\040', '\164', '\150', '\145', '\040', '\162', '\145', +'\163', '\165', '\154', '\164', '\040', '\157', '\146', '\040', '\141', '\040', '\155', '\141', '\143', '\162', '\157', '\040', +'\143', '\157', '\156', '\146', '\151', '\147', '\165', '\162', '\141', '\164', '\151', '\157', '\156', '\040', '\157', '\156', +'\040', '\164', '\150', '\145', '\040', '\115', '\111', '\104', '\111', '\012', '\174', '\040', '\117', '\165', '\164', '\160', +'\165', '\164', '\040', '\160', '\141', '\147', '\145', '\056', '\012', '\174', '\012', '\174', '\040', '\105', '\141', '\143', +'\150', '\040', '\145', '\166', '\145', '\156', '\164', '\040', '\151', '\163', '\040', '\144', '\145', '\163', '\143', '\162', +'\151', '\142', '\145', '\144', '\040', '\142', '\145', '\154', '\157', '\167', '\072', '\012', '\174', '\012', '\174', '\040', +'\040', '\040', '\040', '\040', '\115', '\111', '\104', '\111', '\040', '\123', '\164', '\141', '\162', '\164', '\040', '\040', +'\302', '\040', '\120', '\154', '\141', '\171', '\145', '\162', '\040', '\142', '\145', '\147', '\151', '\156', '\163', '\012', +'\174', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\111', '\104', '\111', '\040', '\123', '\164', '\157', '\160', +'\040', '\040', '\302', '\040', '\120', '\154', '\141', '\171', '\145', '\162', '\040', '\163', '\164', '\157', '\160', '\163', +'\040', '\160', '\154', '\141', '\171', '\151', '\156', '\147', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\115', '\111', '\104', '\111', '\040', '\124', '\151', '\143', '\153', '\040', '\040', '\302', '\040', '\105', '\166', '\145', +'\162', '\171', '\040', '\162', '\157', '\167', '\050', '\077', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\116', '\157', '\164', '\145', '\040', '\117', '\156', '\040', '\040', '\302', '\040', '\105', '\166', +'\145', '\162', '\171', '\040', '\156', '\157', '\164', '\145', '\040', '\162', '\145', '\143', '\157', '\162', '\144', '\145', +'\144', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\157', '\164', '\145', '\040', '\117', +'\146', '\146', '\040', '\040', '\302', '\040', '\105', '\166', '\145', '\162', '\171', '\040', '\156', '\157', '\164', '\145', +'\040', '\157', '\146', '\146', '\040', '\050', '\151', '\156', '\143', '\154', '\165', '\144', '\151', '\156', '\147', '\040', +'\116', '\116', '\101', '\040', '\156', '\157', '\164', '\145', '\055', '\157', '\146', '\146', '\051', '\012', '\174', '\040', +'\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\126', '\157', '\154', '\165', '\155', '\145', '\040', '\040', +'\302', '\040', '\126', '\157', '\154', '\165', '\155', '\145', '\040', '\143', '\150', '\141', '\156', '\147', '\145', '\040', +'\050', '\155', '\157', '\162', '\145', '\040', '\154', '\151', '\153', '\145', '\040', '\141', '\146', '\164', '\145', '\162', +'\164', '\157', '\165', '\143', '\150', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\102', '\141', '\156', '\153', +'\040', '\123', '\145', '\154', '\145', '\143', '\164', '\040', '\040', '\302', '\040', '\102', '\141', '\156', '\153', '\040', +'\143', '\150', '\141', '\156', '\147', '\145', '\012', '\174', '\040', '\120', '\162', '\157', '\147', '\162', '\141', '\155', +'\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\040', '\302', '\040', '\120', '\162', '\157', '\147', '\162', +'\141', '\155', '\040', '\143', '\150', '\141', '\156', '\147', '\145', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\123', '\106', '\060', '\040', '\055', '\040', '\123', '\106', '\106', '\040', '\040', '\302', '\040', '\127', '\150', +'\145', '\156', '\040', '\141', '\040', '\132', '\060', '\060', '\055', '\132', '\067', '\106', '\040', '\151', '\163', '\040', +'\163', '\145', '\145', '\156', '\040', '\151', '\156', '\040', '\164', '\150', '\145', '\040', '\163', '\141', '\155', '\145', +'\040', '\111', '\124', '\055', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', +'\040', '\040', '\040', '\132', '\070', '\060', '\040', '\055', '\040', '\132', '\070', '\106', '\040', '\040', '\302', '\040', +'\127', '\150', '\145', '\156', '\040', '\160', '\154', '\141', '\171', '\145', '\144', '\012', '\174', '\012', '\174', '\040', +'\124', '\150', '\145', '\163', '\145', '\040', '\145', '\166', '\145', '\156', '\164', '\163', '\040', '\141', '\162', '\145', +'\040', '\167', '\162', '\151', '\164', '\164', '\145', '\156', '\040', '\151', '\156', '\040', '\125', '\120', '\120', '\105', +'\122', '\103', '\101', '\123', '\105', '\040', '\150', '\145', '\170', '\141', '\144', '\145', '\143', '\151', '\155', '\141', +'\154', '\054', '\040', '\167', '\151', '\164', '\150', '\040', '\114', '\117', '\127', '\105', '\122', '\103', '\101', '\123', +'\105', '\040', '\166', '\141', '\162', '\151', '\141', '\142', '\154', '\145', '\012', '\174', '\040', '\163', '\165', '\142', +'\163', '\164', '\151', '\164', '\165', '\164', '\151', '\157', '\156', '\056', '\040', '\124', '\150', '\145', '\040', '\166', +'\141', '\162', '\151', '\141', '\142', '\154', '\145', '\163', '\040', '\141', '\162', '\145', '\072', '\012', '\174', '\012', +'\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\141', +'\040', '\040', '\302', '\040', '\103', '\157', '\141', '\162', '\163', '\145', '\040', '\142', '\141', '\156', '\153', '\057', +'\143', '\150', '\141', '\156', '\147', '\145', '\040', '\050', '\157', '\156', '\154', '\171', '\040', '\141', '\166', '\141', +'\151', '\154', '\141', '\142', '\154', '\145', '\040', '\157', '\156', '\040', '\102', '\141', '\156', '\153', '\040', '\123', +'\145', '\154', '\145', '\143', '\164', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\142', '\040', '\040', '\302', '\040', '\106', '\151', '\156', '\145', '\040', +'\142', '\141', '\156', '\153', '\057', '\143', '\150', '\141', '\156', '\147', '\145', '\040', '\050', '\157', '\156', '\154', +'\171', '\040', '\141', '\166', '\141', '\151', '\154', '\141', '\142', '\154', '\145', '\040', '\157', '\156', '\040', '\102', +'\141', '\156', '\153', '\040', '\123', '\145', '\154', '\145', '\143', '\164', '\051', '\012', '\174', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\143', '\040', '\040', '\302', '\040', +'\123', '\145', '\154', '\145', '\143', '\164', '\145', '\144', '\040', '\115', '\111', '\104', '\111', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\156', '\040', '\040', '\302', '\040', '\123', '\145', '\154', '\145', '\143', '\164', +'\145', '\144', '\040', '\115', '\111', '\104', '\111', '\040', '\156', '\157', '\164', '\145', '\040', '\050', '\116', '\157', +'\164', '\145', '\040', '\117', '\156', '\040', '\145', '\166', '\145', '\156', '\164', '\163', '\040', '\157', '\156', '\154', +'\171', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\160', '\040', '\040', '\302', '\040', '\120', '\162', '\157', '\147', '\162', '\141', '\155', '\040', '\163', +'\145', '\164', '\164', '\151', '\156', '\147', '\040', '\050', '\120', '\162', '\157', '\147', '\162', '\141', '\155', '\040', +'\103', '\150', '\141', '\156', '\147', '\145', '\040', '\145', '\166', '\145', '\156', '\164', '\163', '\040', '\157', '\156', +'\154', '\171', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\166', '\040', '\040', '\302', '\040', '\126', '\145', '\154', '\157', '\143', '\151', '\164', '\171', +'\040', '\050', '\151', '\156', '\151', '\164', '\151', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', +'\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\165', '\040', '\040', '\302', '\040', '\103', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\166', '\157', +'\154', '\165', '\155', '\145', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\170', '\040', '\040', '\302', '\040', '\123', '\145', '\164', '\040', '\120', '\141', '\156', +'\156', '\151', '\156', '\147', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\171', '\040', '\040', '\302', '\040', '\103', '\141', '\154', '\143', '\165', '\154', '\141', +'\164', '\145', '\144', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\050', '\151', '\156', '\143', +'\154', '\165', '\144', '\145', '\163', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\145', '\156', +'\166', '\145', '\154', '\157', '\160', '\145', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\172', '\040', '\040', '\302', '\040', '\115', '\111', '\104', '\111', +'\040', '\115', '\141', '\143', '\162', '\157', '\040', '\050', '\132', '\060', '\060', '\055', '\132', '\067', '\106', '\040', +'\143', '\157', '\155', '\155', '\141', '\156', '\144', '\163', '\051', '\012', '\174', '\012', '\174', '\040', '\115', '\111', +'\104', '\111', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\040', '\141', '\162', '\145', '\040', +'\156', '\157', '\162', '\155', '\141', '\154', '\154', '\171', '\040', '\164', '\150', '\162', '\145', '\145', '\055', '\142', +'\171', '\164', '\145', '\163', '\040', '\050', '\145', '\170', '\143', '\145', '\160', '\164', '\040', '\146', '\157', '\162', +'\040', '\123', '\171', '\163', '\164', '\145', '\155', '\040', '\105', '\170', '\143', '\154', '\165', '\163', '\151', '\166', +'\145', '\012', '\174', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\051', '\056', '\040', '\101', +'\040', '\156', '\145', '\167', '\040', '\042', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\042', '\040', '\142', +'\145', '\147', '\151', '\156', '\163', '\040', '\167', '\151', '\164', '\150', '\040', '\141', '\040', '\142', '\171', '\164', +'\145', '\040', '\150', '\141', '\166', '\151', '\156', '\147', '\040', '\151', '\164', '\047', '\163', '\040', '\150', '\151', +'\147', '\150', '\055', '\142', '\151', '\164', '\040', '\163', '\145', '\164', '\056', '\012', '\174', '\040', '\124', '\150', +'\151', '\163', '\040', '\155', '\145', '\141', '\156', '\163', '\040', '\164', '\150', '\141', '\164', '\040', '\164', '\150', +'\145', '\040', '\146', '\151', '\162', '\163', '\164', '\040', '\142', '\171', '\164', '\145', '\040', '\151', '\163', '\040', +'\147', '\157', '\151', '\156', '\147', '\040', '\164', '\157', '\040', '\142', '\145', '\040', '\142', '\145', '\164', '\167', +'\145', '\145', '\156', '\040', '\060', '\170', '\070', '\060', '\040', '\141', '\156', '\144', '\040', '\060', '\170', '\106', +'\106', '\056', '\012', '\174', '\012', '\174', '\040', '\123', '\171', '\163', '\164', '\145', '\155', '\040', '\105', '\170', +'\143', '\154', '\165', '\163', '\151', '\166', '\145', '\040', '\050', '\123', '\171', '\163', '\105', '\170', '\051', '\040', +'\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\040', '\142', '\145', '\147', '\151', '\156', '\040', '\167', +'\151', '\164', '\150', '\040', '\141', '\040', '\060', '\170', '\106', '\060', '\040', '\141', '\156', '\144', '\040', '\145', +'\156', '\144', '\040', '\167', '\151', '\164', '\150', '\040', '\141', '\040', '\060', '\170', '\106', '\067', '\012', '\174', +'\040', '\142', '\171', '\164', '\145', '\056', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\057', '\111', '\155', +'\160', '\165', '\154', '\163', '\145', '\040', '\124', '\162', '\141', '\143', '\153', '\145', '\162', '\040', '\164', '\150', +'\145', '\040', '\146', '\157', '\154', '\154', '\157', '\167', '\151', '\156', '\147', '\040', '\123', '\171', '\163', '\105', +'\170', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\040', '\151', '\156', '\164', '\145', '\162', +'\156', '\141', '\154', '\154', '\171', '\072', '\012', '\174', '\012', '\174', '\040', '\106', '\060', '\040', '\106', '\060', +'\040', '\060', '\060', '\040', '\161', '\161', '\040', '\106', '\067', '\040', '\040', '\302', '\040', '\123', '\145', '\164', +'\040', '\164', '\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\146', '\151', '\154', +'\164', '\145', '\162', '\040', '\143', '\165', '\164', '\157', '\146', '\146', '\040', '\160', '\157', '\151', '\156', '\164', +'\040', '\164', '\157', '\040', '\142', '\145', '\040', '\161', '\161', '\012', '\174', '\040', '\106', '\060', '\040', '\106', +'\060', '\040', '\060', '\061', '\040', '\161', '\161', '\040', '\106', '\067', '\040', '\040', '\302', '\040', '\123', '\145', +'\164', '\040', '\164', '\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\146', '\151', +'\154', '\164', '\145', '\162', '\040', '\162', '\145', '\163', '\157', '\156', '\141', '\156', '\143', '\145', '\040', '\164', +'\157', '\040', '\142', '\145', '\040', '\161', '\161', '\012', '\174', '\012', '\174', '\040', '\131', '\157', '\165', '\047', +'\154', '\154', '\040', '\147', '\145', '\156', '\145', '\162', '\141', '\154', '\154', '\171', '\040', '\156', '\145', '\145', +'\144', '\040', '\164', '\150', '\145', '\040', '\160', '\162', '\157', '\147', '\162', '\141', '\155', '\155', '\151', '\156', +'\147', '\040', '\147', '\165', '\151', '\144', '\145', '\040', '\146', '\157', '\162', '\040', '\171', '\157', '\165', '\162', +'\040', '\115', '\111', '\104', '\111', '\040', '\163', '\171', '\156', '\164', '\150', '\145', '\163', '\151', '\172', '\145', +'\162', '\163', '\040', '\164', '\157', '\012', '\174', '\040', '\143', '\157', '\155', '\145', '\040', '\165', '\160', '\040', +'\167', '\151', '\164', '\150', '\040', '\165', '\163', '\145', '\146', '\165', '\154', '\040', '\166', '\141', '\154', '\165', +'\145', '\163', '\056', '\040', '\131', '\157', '\165', '\040', '\144', '\157', '\040', '\156', '\157', '\164', '\040', '\150', +'\141', '\166', '\145', '\040', '\164', '\157', '\040', '\151', '\156', '\143', '\154', '\165', '\144', '\145', '\040', '\164', +'\150', '\145', '\040', '\106', '\067', '\040', '\141', '\163', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\012', +'\174', '\040', '\124', '\162', '\141', '\143', '\153', '\145', '\162', '\040', '\167', '\151', '\154', '\154', '\040', '\141', +'\165', '\164', '\157', '\155', '\141', '\164', '\151', '\143', '\141', '\154', '\154', '\171', '\040', '\164', '\145', '\162', +'\155', '\151', '\156', '\141', '\164', '\145', '\040', '\123', '\171', '\163', '\105', '\170', '\040', '\155', '\145', '\163', +'\163', '\141', '\147', '\145', '\163', '\056', '\012', '\174', '\012', '\174', '\040', '\117', '\164', '\150', '\145', '\162', +'\040', '\115', '\111', '\104', '\111', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\040', '\151', +'\156', '\143', '\154', '\165', '\144', '\145', '\072', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\070', '\143', '\040', '\156', '\040', '\166', '\040', '\040', '\302', '\040', '\116', '\157', '\164', '\145', +'\055', '\157', '\146', '\146', '\040', '\146', '\157', '\162', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', +'\040', '\042', '\143', '\042', '\054', '\040', '\156', '\157', '\164', '\145', '\040', '\042', '\156', '\042', '\012', '\174', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\071', '\143', '\040', '\156', '\040', '\166', '\040', +'\040', '\302', '\040', '\116', '\157', '\164', '\145', '\055', '\157', '\156', '\040', '\146', '\157', '\162', '\040', '\143', +'\150', '\141', '\156', '\156', '\145', '\154', '\040', '\042', '\143', '\042', '\054', '\040', '\156', '\157', '\164', '\145', +'\040', '\042', '\156', '\042', '\054', '\040', '\166', '\145', '\154', '\157', '\143', '\151', '\164', '\171', '\040', '\042', +'\166', '\042', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', '\143', '\040', +'\156', '\040', '\166', '\040', '\040', '\302', '\040', '\101', '\146', '\164', '\145', '\162', '\164', '\157', '\165', '\143', +'\150', '\040', '\050', '\141', '\144', '\152', '\165', '\163', '\164', '\040', '\166', '\145', '\154', '\157', '\143', '\151', +'\164', '\171', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\102', '\143', +'\040', '\161', '\040', '\172', '\040', '\040', '\302', '\040', '\123', '\145', '\164', '\040', '\115', '\111', '\104', '\111', +'\040', '\143', '\157', '\156', '\164', '\162', '\157', '\154', '\154', '\145', '\162', '\040', '\042', '\161', '\042', '\040', +'\157', '\156', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\042', '\143', '\042', '\054', '\040', +'\164', '\157', '\040', '\166', '\141', '\154', '\165', '\145', '\040', '\042', '\172', '\042', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\143', '\040', '\160', '\040', '\040', '\040', '\040', '\302', +'\040', '\120', '\162', '\157', '\147', '\162', '\141', '\155', '\040', '\143', '\150', '\141', '\156', '\147', '\145', '\040', +'\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\042', '\143', '\042', '\040', '\164', '\157', '\040', '\042', +'\160', '\042', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\143', '\040', +'\172', '\040', '\040', '\040', '\040', '\302', '\040', '\123', '\145', '\164', '\040', '\164', '\157', '\164', '\141', '\154', +'\040', '\153', '\145', '\171', '\040', '\160', '\162', '\145', '\163', '\163', '\165', '\162', '\145', '\040', '\164', '\157', +'\040', '\042', '\172', '\042', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', +'\143', '\040', '\161', '\040', '\161', '\040', '\040', '\302', '\040', '\120', '\151', '\164', '\143', '\150', '\040', '\127', +'\150', '\145', '\145', '\154', '\073', '\040', '\161', '\040', '\161', '\040', '\151', '\163', '\040', '\141', '\040', '\061', +'\064', '\055', '\142', '\151', '\164', '\040', '\166', '\141', '\154', '\165', '\145', '\056', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\106', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\302', +'\040', '\115', '\111', '\104', '\111', '\040', '\042', '\103', '\154', '\157', '\143', '\153', '\042', '\040', '\157', '\160', +'\145', '\162', '\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\106', '\101', '\040', '\040', '\040', '\040', '\040', '\040', '\302', '\040', '\123', '\164', '\141', '\162', +'\164', '\040', '\115', '\111', '\104', '\111', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\106', '\102', '\040', '\040', '\040', '\040', '\040', '\040', '\302', '\040', '\103', '\157', '\156', '\164', '\151', +'\156', '\165', '\145', '\040', '\115', '\111', '\104', '\111', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\106', '\103', '\040', '\040', '\040', '\040', '\040', '\040', '\302', '\040', '\123', '\164', '\157', +'\160', '\040', '\115', '\111', '\104', '\111', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\106', '\106', '\040', '\040', '\040', '\040', '\040', '\040', '\302', '\040', '\122', '\145', '\163', '\145', '\164', +'\012', '\174', '\012', '\174', '\040', '\124', '\150', '\145', '\162', '\145', '\040', '\141', '\162', '\145', '\040', '\157', +'\164', '\150', '\145', '\162', '\040', '\115', '\111', '\104', '\111', '\040', '\155', '\145', '\163', '\163', '\141', '\147', +'\145', '\163', '\054', '\040', '\142', '\165', '\164', '\040', '\164', '\150', '\145', '\171', '\040', '\141', '\162', '\145', +'\040', '\165', '\156', '\154', '\151', '\153', '\145', '\154', '\171', '\040', '\164', '\157', '\040', '\142', '\145', '\040', +'\165', '\163', '\145', '\146', '\165', '\154', '\040', '\167', '\151', '\164', '\150', '\012', '\174', '\040', '\111', '\155', +'\160', '\165', '\154', '\163', '\145', '\040', '\157', '\162', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\040', +'\124', '\162', '\141', '\143', '\153', '\145', '\162', '\056', '\012', '\174', '\012', '\174', '\040', '\116', '\145', '\166', +'\145', '\162', '\164', '\150', '\145', '\154', '\145', '\163', '\163', '\054', '\040', '\171', '\157', '\165', '\162', '\040', +'\160', '\162', '\157', '\147', '\162', '\141', '\155', '\155', '\151', '\156', '\147', '\040', '\147', '\165', '\151', '\144', +'\145', '\040', '\167', '\151', '\154', '\154', '\040', '\142', '\145', '\040', '\141', '\165', '\164', '\150', '\157', '\162', +'\151', '\164', '\141', '\164', '\151', '\166', '\145', '\056', '\012', '\174', '\040', '\012', '\174', '\012', '\174', '\040', +'\103', '\157', '\156', '\164', '\162', '\157', '\154', '\154', '\145', '\162', '\163', '\040', '\050', '\102', '\143', '\040', +'\161', '\040', '\172', '\051', '\040', '\141', '\162', '\145', '\040', '\162', '\145', '\141', '\163', '\157', '\156', '\141', +'\142', '\154', '\171', '\040', '\143', '\157', '\155', '\155', '\157', '\156', '\056', '\040', '\124', '\150', '\145', '\171', +'\040', '\147', '\145', '\156', '\145', '\162', '\141', '\154', '\154', '\171', '\040', '\143', '\157', '\155', '\145', '\040', +'\151', '\156', '\040', '\164', '\167', '\157', '\012', '\174', '\040', '\146', '\154', '\141', '\166', '\157', '\162', '\163', +'\072', '\040', '\141', '\040', '\042', '\143', '\157', '\141', '\162', '\163', '\145', '\042', '\040', '\157', '\162', '\040', +'\042', '\150', '\151', '\147', '\150', '\040', '\142', '\171', '\164', '\145', '\042', '\040', '\163', '\145', '\164', '\164', +'\151', '\156', '\147', '\054', '\040', '\141', '\156', '\144', '\040', '\042', '\146', '\151', '\156', '\145', '\042', '\040', +'\157', '\162', '\040', '\042', '\154', '\157', '\167', '\040', '\142', '\171', '\164', '\145', '\042', '\012', '\174', '\040', +'\163', '\145', '\164', '\164', '\151', '\156', '\147', '\056', '\040', '\111', '\146', '\040', '\141', '\040', '\154', '\151', +'\163', '\164', '\145', '\144', '\040', '\143', '\157', '\156', '\164', '\162', '\157', '\154', '\154', '\145', '\162', '\040', +'\157', '\156', '\154', '\171', '\040', '\143', '\157', '\155', '\145', '\163', '\040', '\151', '\156', '\040', '\157', '\156', +'\145', '\040', '\153', '\151', '\156', '\144', '\054', '\040', '\164', '\150', '\145', '\040', '\166', '\141', '\154', '\165', +'\145', '\163', '\040', '\167', '\151', '\154', '\154', '\040', '\142', '\145', '\012', '\174', '\040', '\154', '\151', '\163', +'\164', '\145', '\144', '\040', '\157', '\156', '\154', '\171', '\040', '\146', '\157', '\162', '\040', '\143', '\157', '\141', +'\162', '\163', '\145', '\040', '\141', '\144', '\152', '\165', '\163', '\164', '\155', '\145', '\156', '\164', '\056', '\012', +'\174', '\012', '\174', '\040', '\124', '\150', '\145', '\040', '\156', '\141', '\155', '\145', '\163', '\040', '\154', '\151', +'\163', '\164', '\145', '\144', '\040', '\142', '\145', '\154', '\157', '\167', '\040', '\141', '\162', '\145', '\040', '\154', +'\151', '\153', '\145', '\154', '\171', '\040', '\164', '\157', '\040', '\142', '\145', '\040', '\163', '\151', '\155', '\151', +'\154', '\141', '\162', '\040', '\164', '\157', '\040', '\164', '\150', '\145', '\040', '\156', '\141', '\155', '\145', '\163', +'\040', '\157', '\156', '\040', '\171', '\157', '\165', '\162', '\012', '\174', '\040', '\163', '\171', '\156', '\164', '\150', +'\145', '\163', '\151', '\172', '\145', '\162', '\056', '\012', '\174', '\012', '\174', '\040', '\103', '\157', '\141', '\162', +'\163', '\145', '\040', '\040', '\106', '\151', '\156', '\145', '\040', '\040', '\040', '\104', '\145', '\163', '\143', '\162', +'\151', '\160', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\012', '\174', '\040', '\040', '\060', '\060', '\040', +'\040', '\040', '\040', '\040', '\040', '\062', '\060', '\040', '\040', '\040', '\040', '\102', '\141', '\156', '\153', '\040', +'\123', '\145', '\154', '\145', '\143', '\164', '\012', '\174', '\040', '\040', '\060', '\061', '\040', '\040', '\040', '\040', +'\040', '\040', '\062', '\061', '\040', '\040', '\040', '\040', '\115', '\157', '\144', '\165', '\154', '\141', '\164', '\151', +'\157', '\156', '\040', '\127', '\150', '\145', '\145', '\154', '\040', '\050', '\115', '\117', '\104', '\040', '\127', '\150', +'\145', '\145', '\154', '\051', '\012', '\174', '\040', '\040', '\060', '\062', '\040', '\040', '\040', '\040', '\040', '\040', +'\062', '\062', '\040', '\040', '\040', '\040', '\102', '\162', '\145', '\141', '\164', '\150', '\040', '\103', '\157', '\156', +'\164', '\162', '\157', '\154', '\154', '\145', '\162', '\012', '\174', '\040', '\040', '\060', '\064', '\040', '\040', '\040', +'\040', '\040', '\040', '\062', '\064', '\040', '\040', '\040', '\040', '\106', '\157', '\157', '\164', '\040', '\120', '\145', +'\144', '\141', '\154', '\012', '\174', '\040', '\040', '\060', '\065', '\040', '\040', '\040', '\040', '\040', '\040', '\062', +'\065', '\040', '\040', '\040', '\040', '\120', '\157', '\162', '\164', '\141', '\155', '\145', '\156', '\164', '\157', '\040', +'\124', '\151', '\155', '\145', '\012', '\174', '\040', '\040', '\060', '\066', '\040', '\040', '\040', '\040', '\040', '\040', +'\062', '\066', '\040', '\040', '\040', '\040', '\104', '\141', '\164', '\141', '\040', '\105', '\156', '\164', '\162', '\171', +'\057', '\123', '\154', '\151', '\144', '\145', '\162', '\012', '\174', '\040', '\040', '\060', '\067', '\040', '\040', '\040', +'\040', '\040', '\040', '\062', '\067', '\040', '\040', '\040', '\040', '\126', '\157', '\154', '\165', '\155', '\145', '\012', +'\174', '\040', '\040', '\060', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\070', '\040', '\040', '\040', +'\040', '\102', '\141', '\154', '\141', '\156', '\143', '\145', '\057', '\120', '\141', '\156', '\156', '\151', '\156', '\147', +'\012', '\174', '\040', '\040', '\060', '\101', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\101', '\040', '\040', +'\040', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\120', '\157', '\163', '\151', '\164', '\151', +'\157', '\156', '\012', '\174', '\040', '\040', '\060', '\102', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\102', +'\040', '\040', '\040', '\040', '\050', '\126', '\157', '\154', '\165', '\155', '\145', '\051', '\040', '\105', '\170', '\160', +'\162', '\145', '\163', '\163', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\060', '\103', '\040', '\040', '\040', +'\040', '\040', '\040', '\062', '\103', '\040', '\040', '\040', '\040', '\105', '\146', '\146', '\145', '\143', '\164', '\040', +'\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\061', '\012', '\174', '\040', '\040', '\060', '\104', '\040', +'\040', '\040', '\040', '\040', '\040', '\062', '\104', '\040', '\040', '\040', '\040', '\105', '\146', '\146', '\145', '\143', +'\164', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\062', '\012', '\174', '\040', '\040', '\061', +'\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', +'\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', '\040', '\123', '\154', '\151', +'\144', '\145', '\162', '\040', '\061', '\012', '\174', '\040', '\040', '\061', '\061', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', +'\165', '\162', '\160', '\157', '\163', '\145', '\040', '\123', '\154', '\151', '\144', '\145', '\162', '\040', '\062', '\012', +'\174', '\040', '\040', '\061', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', +'\040', '\123', '\154', '\151', '\144', '\145', '\162', '\040', '\063', '\012', '\174', '\040', '\040', '\061', '\063', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', +'\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', '\040', '\123', '\154', '\151', '\144', '\145', +'\162', '\040', '\064', '\012', '\174', '\040', '\040', '\064', '\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\110', '\157', '\154', '\144', '\040', '\120', '\145', '\144', '\141', '\154', '\012', +'\174', '\040', '\040', '\064', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\120', '\157', '\162', '\164', '\141', '\155', '\145', '\156', '\164', '\157', '\040', '\117', '\156', '\057', '\117', +'\146', '\146', '\012', '\174', '\040', '\040', '\064', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\123', '\165', '\163', '\164', '\145', '\156', '\165', '\164', '\157', '\012', '\174', '\040', +'\040', '\064', '\063', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', +'\157', '\146', '\164', '\040', '\120', '\145', '\144', '\141', '\154', '\012', '\174', '\040', '\040', '\064', '\064', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\114', '\145', '\147', '\141', '\164', +'\157', '\040', '\120', '\145', '\144', '\141', '\154', '\012', '\174', '\040', '\040', '\064', '\065', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\110', '\157', '\154', '\144', '\040', '\062', '\040', +'\120', '\145', '\144', '\141', '\154', '\012', '\174', '\040', '\040', '\064', '\066', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\126', '\141', '\162', +'\151', '\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\064', '\067', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\124', '\151', +'\155', '\142', '\162', '\145', '\012', '\174', '\040', '\040', '\064', '\070', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\122', '\145', '\154', '\145', +'\141', '\163', '\145', '\040', '\124', '\151', '\155', '\145', '\012', '\174', '\040', '\040', '\064', '\071', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', +'\101', '\164', '\164', '\141', '\143', '\153', '\040', '\124', '\151', '\155', '\145', '\012', '\174', '\040', '\040', '\064', +'\101', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', +'\156', '\144', '\040', '\102', '\162', '\151', '\147', '\150', '\164', '\156', '\145', '\163', '\163', '\012', '\174', '\040', +'\040', '\064', '\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', +'\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\066', '\012', '\174', +'\040', '\040', '\064', '\103', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\123', '\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\067', '\012', +'\174', '\040', '\040', '\064', '\104', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\123', '\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\070', +'\012', '\174', '\040', '\040', '\064', '\105', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', +'\071', '\012', '\174', '\040', '\040', '\064', '\106', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', +'\040', '\061', '\060', '\012', '\174', '\040', '\040', '\065', '\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', +'\160', '\157', '\163', '\145', '\040', '\102', '\165', '\164', '\164', '\157', '\156', '\040', '\061', '\012', '\174', '\040', +'\040', '\065', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', +'\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', '\040', '\102', +'\165', '\164', '\164', '\157', '\156', '\040', '\062', '\012', '\174', '\040', '\040', '\065', '\062', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', +'\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', '\040', '\102', '\165', '\164', '\164', '\157', '\156', '\040', +'\063', '\012', '\174', '\040', '\040', '\065', '\063', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', +'\163', '\145', '\040', '\102', '\165', '\164', '\164', '\157', '\156', '\040', '\064', '\012', '\174', '\040', '\040', '\065', +'\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', '\146', '\146', +'\145', '\143', '\164', '\163', '\040', '\114', '\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\065', '\103', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\162', '\145', '\155', +'\165', '\154', '\157', '\040', '\114', '\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\065', '\104', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\157', '\162', '\165', +'\163', '\040', '\114', '\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\065', '\105', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\145', '\154', '\145', '\163', '\164', '\145', +'\040', '\114', '\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\065', '\106', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\150', '\141', '\163', '\145', '\162', '\040', '\114', +'\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\066', '\060', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\104', '\141', '\164', '\141', '\040', '\102', '\165', '\164', '\164', '\157', +'\156', '\040', '\111', '\156', '\143', '\162', '\145', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', '\066', +'\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\141', '\164', +'\141', '\040', '\102', '\165', '\164', '\164', '\157', '\156', '\040', '\104', '\145', '\163', '\143', '\162', '\145', '\155', +'\145', '\156', '\164', '\012', '\174', '\040', '\040', '\066', '\063', '\040', '\040', '\040', '\040', '\040', '\040', '\066', +'\062', '\040', '\040', '\040', '\040', '\116', '\157', '\156', '\055', '\122', '\145', '\147', '\151', '\163', '\164', '\145', +'\162', '\145', '\144', '\040', '\120', '\141', '\162', '\141', '\155', '\145', '\164', '\145', '\162', '\040', '\116', '\165', +'\155', '\142', '\145', '\162', '\040', '\050', '\116', '\122', '\120', '\116', '\051', '\012', '\174', '\040', '\040', '\066', +'\065', '\040', '\040', '\040', '\040', '\040', '\040', '\066', '\064', '\040', '\040', '\040', '\040', '\122', '\145', '\147', +'\151', '\163', '\164', '\145', '\162', '\145', '\144', '\040', '\120', '\141', '\162', '\141', '\155', '\145', '\164', '\145', +'\162', '\040', '\116', '\165', '\155', '\142', '\145', '\162', '\040', '\050', '\122', '\120', '\116', '\051', '\012', '\174', +'\040', '\040', '\067', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\101', '\154', '\154', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\117', '\146', '\146', '\012', '\174', '\040', +'\040', '\067', '\071', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', +'\154', '\154', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\154', '\145', '\162', '\163', '\040', '\117', +'\146', '\146', '\012', '\174', '\040', '\040', '\067', '\101', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\114', '\157', '\143', '\141', '\154', '\040', '\113', '\145', '\171', '\142', '\157', '\141', +'\162', '\144', '\040', '\117', '\156', '\057', '\117', '\146', '\146', '\012', '\174', '\040', '\040', '\067', '\102', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', '\154', '\154', '\040', '\116', +'\157', '\164', '\145', '\163', '\040', '\117', '\146', '\146', '\012', '\174', '\040', '\040', '\067', '\103', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\117', '\155', '\156', '\151', '\040', '\115', +'\157', '\144', '\145', '\040', '\117', '\146', '\146', '\012', '\174', '\040', '\040', '\067', '\104', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\117', '\155', '\156', '\151', '\040', '\115', '\157', +'\144', '\145', '\040', '\117', '\156', '\012', '\174', '\040', '\040', '\067', '\105', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\156', '\157', '\160', '\150', '\157', '\156', '\151', +'\143', '\040', '\117', '\160', '\145', '\162', '\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\067', +'\106', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\157', '\154', +'\171', '\160', '\150', '\157', '\156', '\151', '\143', '\040', '\117', '\160', '\145', '\162', '\141', '\164', '\151', '\157', +'\156', '\012', '\000', + +}; diff --git a/include/auto/logoit.h b/include/auto/logoit.h new file mode 100644 index 000000000..37d4e0222 --- /dev/null +++ b/include/auto/logoit.h @@ -0,0 +1,49 @@ +/* XPM */ +static char *_logo_it_xpm[] = { +/* width height ncolors chars_per_pixel */ +"270 40 2 1", +/* colors */ +" c #FFFFFF", +". c None", +/* pixels */ +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +".............. .......................................................................................................................................... ....................................................................................................", +"............ .................................................................................................................................... ................................................................. ...............................", +"........... .............................................................................. ................................................ .............................................................. ...............................", +"......... ............................................................................. .............................................. ............................................................. ...............................", +"........ ............................................................................ ............................................. ........................................................... ................................", +"...... .. ........................................................................... ............................................ .......................................................... ................................", +"..... ..... .......................................................................... ............................................ ......................................................... .................................", +".... ..... ......................................................................... ............................................ ........... ......................................................... ..................................", +"... ....... ...................................... ............................... . ............................................ ............... ........................................................ ...................................", +".. ....... ..................................... .............................. . ............................................. ................. ........................................................ ....................................", +".. ........ .................................... .............................. . .............................................. .............. ........................................................ .....................................", +". ........ .................................... .............................. . ........ ......................................................... ....................................................... ......................................", +". ......... ................................... ........................ ..... .. ....... ............. ....................................... ....................................................... ................. ...................", +". ..... .............. ...... .......... ............... ...... .... . ........ ........... ..................................... ..... .................... ............ ..... ..... ........ .... ...........", +". ...... ...... ..... ..... ......... .... ....... .... ... . ........ ......... .................................... .... .......... ......... .... .... ...... ... .....", +".. ...... ..... ... .... ....... .. ...... .... ... ........ ........ . ................................... .... ....... ....... ..... ... ..... . .. ....", +".... ........ ..... ... ... ...... . ..... .... ... ........ ...... ... ................................... .... ..... ...... .... ... .... ... .. ....", +"............. .... . ... ...... .... .... ... ........ . ..... ... ................................... ... ..... ...... .... . . ... ... .. .....", +"............. .... . . ...... . .... .... .... ........ .. .... .. ................................... .... ..... . ..... . ..... . .. ... .. ... ......", +"............ .... . . ...... ... .... .... ..... ........ ... ..... .................................... .... .... ..... .... .... .. ..... . .... .... . .... ... .......", +"........... .... . . . ..... . ... .... .... ...... ........ .... .... ....... .............................. ...... .... ..... .... ..... .......... .... .... ...... ... ........", +"........... .... .. . . ..... . ... .... .... ..... ....... ..... .... ........ ............................. ..... .... ..... .... ..... ......... ... ...... ....... ... .........", +".......... ..... . .. . ..... . .... .... ... .... ...... ... ... ....... .............................. ..... .... .... .... ..... ......... ..... ........ ... .........", +"......... ...... .. .. ... .. .. ... . ... .... .. .. .. ...... .............................. ..... ..... ... .... . .... ....... ..... ....... .... ... ...", +"......... ...... ... .. .. . . .. . ... .. .. ............................... ........... . ... .. . .... . . .... . ... .... . ...", +"........ ....... ... .. .... ... . ... . .... ............................... ........... . .. .. .. ... . .. ..... .....", +"....... ....... ... ... .... ..... .. .... ... ..... ................................ ............ .... ... ... .... ... ... ....... .......", +"....... ........ .... ..... ..... ........ ..... ...... ..... ........ ................................. ............. ...... .... ...... ...... ..... ...... .......... ........", +"....... ......... ...... ....... ....... ... .................. ...................... ............ .................................... ............... ......... ..... ........ ................ ......... ............. ..........", +"........ ................................... .......................................................................................................... ................................... .........................................................................", +".............................................. ............................................................................................................................................................................................................................", +"............................................. .............................................................................................................................................................................................................................", +"............................................. .............................................................................................................................................................................................................................", +"............................................ ..............................................................................................................................................................................................................................", +"............................................ ..............................................................................................................................................................................................................................", +"............................................ ...............................................................................................................................................................................................................................", +"............................................. ................................................................................................................................................................................................................................", +".............................................................................................................................................................................................................................................................................." +}; diff --git a/include/auto/logoschism.h b/include/auto/logoschism.h new file mode 100644 index 000000000..ca335fdd5 --- /dev/null +++ b/include/auto/logoschism.h @@ -0,0 +1,49 @@ +/* XPM */ +static char *_logo_schism_xpm[] = { +/* width height ncolors chars_per_pixel */ +"270 40 2 1", +/* colors */ +" c #FFFFFF", +". c None", +/* pixels */ +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +"..................................... ................................................................................................................ ..............................................................................................", +".................................. ............................................................................................................. .............................................................................................", +"................................ ............................................................................................................ .............................................................................................", +"............................... ............................................................................................................ ..............................................................................................", +"............................. ... ................................................................................................................... ........................................................................................................", +"............................ ....... .................... ............................................................................................. ....................................................... ................................................", +"........................... ....... ................... ........................................................................................... ...................................................... ...............................................", +"........................... ......... .................... ........................................................................................... ...................................................... ...............................................", +".......................... .......... ................... ........................................................................................... ...................................................... ...............................................", +".......................... .......... ................... .............. ........................................................................... ...................................................... ................................................", +"......................... .......... .................... ............. .......................................................................... ...................................................... ................................................", +"......................... .......... ................... .............. ..... .................................................................. .... ................................................ .......................... .....................", +"......................... .......... .................... .............. .... ................................................................. .... ............................................... ......................... ....................", +"......................... ......... ................... ............... ..... ................................................................ .... .............................................. .......................... ....................", +"......................... ................................ ...................... ................................................................ .... ................... ....................... ................. ..... ....................", +"......................... .................... ...... .. ........ ....... ........... ... ... ................................... .... .............. ........... ...... ...... ........ .... ..................", +".......................... ................. ..... ...... ..... .......... ................................. .... ........... ......... ..... .... ..... ... .................", +".......................... .............. .... ...... ..... .......... ................................. .... ......... ........ .... ... .... .... ................", +"........................... ............ .... ..... .... ........ ................................ .... ... ....... .......... .... . ..... ...... ... ................", +"............................ ........... .... ... ..... ..... ........ ... ... ................................ ..... ... ...... ............ .... ...... ........ ... ................", +"............................. ......... .... .... .... ..... ........ .... .... ................................ .......... ..... ... ...... .... ....... ............... ................", +".............................. ....... .. .... .... ..... ..... . ...... ..... .... ................................ ......... ...... .... ..... .. .... ......... .. ........ .................", +".............................. ...... ... .... ..... .... ..... .. ...... ..... .... ................................ ........ ...... .... .... ... .... .......... ....... ..................", +"............................... .... ..... .... .... ..... ....... ... .... ..... .... ................................ ......... ...... ..... .... ..... .... ........... ....... ...................", +"................................ .... ............ ..... ..... ............. .... .... .... ................................ ........ ....... ..... .... ............ .......... ....... ....................", +"................................ ... ........... .... ..... ..... ...... ... ..... .... ..... ......................... ........ ....... ..... .... ........... ........ ....... ........ ...........", +"................................. ... .......... ..... .... .... ...... ... .... .... .... ........................ ....... ....... ..... .... ......... ... ...... ........ ....... ..........", +"................................. ... ........ .... .... .... ...... .. ..... ... .... ....................... ....... ...... .... .... ........ ... ..... ....... ...... ...........", +"................................ ... ..... ..... ... ... .... .. .... .... .. ........................ ....... .... ... .. ..... .... ... ..... .... ...........", +"........... .... ..... ... .... .... ........................ ....... .... .............", +".......... ...... ...... .. .... ..... ......................... ........ ...... . ..............", +".......... ....... . ....... .. .... ... .... ...... ........................... ......... ... . .. . ...... .. .... ................", +"........... .......... ..... ......... ..... ....... ...... ...... ........ .............................. ............ ....... .... ...... ..... ........ ..... ......... ...................", +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +".............................................................................................................................................................................................................................................................................." +}; diff --git a/include/auto/schismico.h b/include/auto/schismico.h new file mode 100644 index 000000000..eed847f1c --- /dev/null +++ b/include/auto/schismico.h @@ -0,0 +1,630 @@ +/* XPM */ +static char *_schism_icon_xpm[] = { +/* width height ncolors chars_per_pixel */ +"32 32 591 2", +/* colors */ +" c #000000", +" . c #8C7B6F", +" X c #7C695C", +" o c #353334", +" O c #6F6963", +" + c #23211F", +" @ c #0C0E0F", +" # c #837870", +" $ c #9C9E64", +" % c #B99A84", +" & c #B29987", +" * c #605247", +" = c #6F6459", +" - c #736E49", +" ; c #2D2B2C", +" : c #2C292B", +" > c #5D4C44", +" , c #314933", +" < c #645F58", +" 1 c #6A5C54", +" 2 c #736D6A", +" 3 c #282527", +" 4 c #5F5F53", +" 5 c #5C5A5A", +" 6 c #3D5742", +" 7 c #363538", +" 8 c #776A64", +" 9 c #536651", +" 0 c #857C75", +" q c #252124", +" w c #C4AF9F", +" e c #AE9C90", +" r c #686D5F", +" t c #5F7060", +" y c #60524A", +" u c #1F1B1E", +" i c #C6AA97", +" p c #433E3E", +" a c #394E34", +" s c #CAA991", +" d c #525050", +" f c #57514B", +" g c #1D191C", +" h c #65655C", +" j c #473D38", +" k c #807566", +" l c #7F7165", +" z c #566C57", +" x c #1A1519", +" c c #9A816F", +" v c #28272A", +" b c #5E6555", +" n c #8E8177", +" m c #4D4A4B", +" M c #161315", +" N c #171116", +" B c #4B4849", +" V c #A19083", +" C c #D2B39C", +" Z c #756C65", +" A c #C1A188", +" S c #B49F8F", +" D c #373432", +" F c #3D332E", +" G c #474445", +" H c #726862", +" J c #BE9D85", +" K c #515E52", +" L c #464444", +" P c #726662", +" I c #BD9B84", +" U c #1B251D", +" Y c #34302F", +" T c #1F1D21", +" R c #352E30", +" E c #545255", +" W c #5A5151", +" Q c #9F8977", +" ! c #535254", +" ~ c #BA9981", +" ^ c #1D1B1F", +" / c #423E40", +" ( c #C9A993", +" ) c #5B5248", +" _ c #1A1B1C", +" ` c #716157", +" ' c #504E51", +" ] c #526E56", +" [ c #3D383B", +" { c #171319", +" } c #6B5B51", +" | c #564843", +". c #3F5A40", +".. c #393637", +".X c #484849", +".o c #6C673E", +".O c #667163", +".+ c #605850", +".@ c #9B8C80", +".# c #8B7A6D", +".$ c #1F1F24", +".% c #3B2F2F", +".& c #444245", +".* c #595353", +".= c #333031", +".- c #332E31", +".; c #C9AD96", +".: c #4F5B49", +".> c #756254", +"., c #6F6158", +".< c #2D2A2B", +".1 c #364637", +".2 c #5C4D42", +".3 c #6B684A", +".4 c #040506", +".5 c #807062", +".6 c #292627", +".7 c #282626", +".8 c #9F9087", +".9 c #343C35", +".0 c #5D595A", +".q c #657565", +".w c #0E1113", +".e c #5A5757", +".r c #857B74", +".t c #232221", +".y c #8A7A6F", +".u c #595556", +".i c #CAAF9A", +".p c #67725D", +".a c #766C58", +".s c #221E20", +".d c #6C6462", +".f c #5F5148", +".g c #2D2E2E", +".h c #08090D", +".j c #736B4B", +".k c #3C3E40", +".l c #D3B9A6", +".z c #C3A793", +".x c #6B5F57", +".c c #2A282B", +".v c #4F4B4C", +".b c #29282A", +".n c #29262A", +".m c #6D6945", +".M c #786D67", +".N c #5B5B5B", +".B c #C4A08A", +".V c #262427", +".C c #5A595A", +".Z c #232424", +".A c #C19E87", +".S c #6E6867", +".D c #120C10", +".F c #6C6665", +".G c #575C4D", +".H c #917865", +".J c #305A37", +".K c #5A5250", +".L c #59524F", +".P c #746459", +".I c #515151", +".U c #504F50", +".Y c #2B2A2F", +".T c #5A695D", +".R c #463A39", +".E c #696158", +".W c #6F6054", +".Q c #4F4B4F", +".! c #6D7869", +".~ c #423A35", +".^ c #2A2724", +"./ c #C2A28B", +".( c #141415", +".) c #3A3737", +"._ c #61585A", +".` c #807F4A", +".' c #393736", +".] c #C1A08A", +".[ c #4D4A43", +".{ c #937C6A", +".} c #4B694E", +".| c #474347", +"X c #454545", +"X. c #66564B", +"XX c #595452", +"Xo c #4C5D4F", +"XO c #466349", +"X+ c #6A635C", +"X@ c #706258", +"X# c #D7BAA6", +"X$ c #7F726A", +"X% c #B5967E", +"X& c #362629", +"X* c #2D2B2A", +"X= c #3E3B3E", +"X- c #78716D", +"X; c #2C2929", +"X: c #584B47", +"X> c #C9A58B", +"X, c #3B393B", +"X< c #C2A48E", +"X1 c #B7A297", +"X2 c #393739", +"X3 c #4F4848", +"X4 c #A59082", +"X5 c #383738", +"X6 c #282525", +"X7 c #393539", +"X8 c #282325", +"X9 c #373537", +"X0 c #0D1611", +"Xq c #363336", +"Xw c #888862", +"Xe c #242121", +"Xr c #A08C7D", +"Xt c #2B412B", +"Xy c #3E4D41", +"Xu c #BC9C88", +"Xi c #89796D", +"Xp c #74665F", +"Xa c #231D20", +"Xs c #617060", +"Xd c #71645C", +"Xf c #CCAB91", +"Xg c #C6AA95", +"Xh c #3D3D40", +"Xj c #93857A", +"Xk c #51504D", +"Xl c #06080A", +"Xz c #5C665B", +"Xx c #7C706A", +"Xc c #7B7069", +"Xv c #393B3C", +"Xb c #3F3A38", +"Xn c #586657", +"Xm c #7E7858", +"XM c #64604F", +"XN c #5C5A5B", +"XB c #514943", +"XV c #5B5A5A", +"XC c #2C452F", +"XZ c #706B68", +"XA c #262126", +"XS c #3F583B", +"XD c #655850", +"XF c #333336", +"XG c #C19D86", +"XH c #62584D", +"XJ c #5E5553", +"XK c #313334", +"XL c #BF9D84", +"XP c #575456", +"XI c #454241", +"XU c #201D20", +"XY c #5E7060", +"XT c #555254", +"XR c #1F1D1F", +"XE c #60524B", +"XW c #4A3F3C", +"XQ c #8F7762", +"X! c #D5BCAA", +"X~ c #616B59", +"X^ c #716355", +"X/ c #4E5D43", +"X( c #312A2A", +"X) c #5C4E47", +"X_ c #65615D", +"X` c #847461", +"X' c #5A4E45", +"X] c #9B8371", +"X[ c #D6B7A1", +"X{ c #181718", +"X} c #2B4F31", +"X| c #4D4C4C", +"o c #27292A", +"o. c #BFA491", +"oX c #DEC8B6", +"oo c #171517", +"oO c #C4A38C", +"o+ c #4C4A4B", +"o@ c #161516", +"o# c #4B4A4A", +"o$ c #C2A18A", +"o% c #C1A189", +"o& c #383234", +"o* c #373233", +"o= c #9D8C80", +"o- c #927B68", +"o; c #50463B", +"o: c #353031", +"o> c #BC9B84", +"o, c #444243", +"o< c #7E7472", +"o1 c #D2BAAA", +"o2 c #968479", +"o3 c #5B4E49", +"o4 c #394238", +"o5 c #6E5F55", +"o6 c #A39489", +"o7 c #405E42", +"o8 c #465448", +"o9 c #586953", +"o0 c #3A3839", +"oq c #737067", +"ow c #292425", +"oe c #947D6D", +"or c #726C66", +"ot c #5D5958", +"oy c #726A66", +"ou c #4D4545", +"oi c #716865", +"op c #100F13", +"oa c #C6AE9E", +"os c #343033", +"od c #D0AE94", +"of c #BB9B86", +"og c #392F2E", +"oh c #585849", +"oj c #6B625F", +"ok c #96867C", +"ol c #453D3D", +"oz c #2C2C2B", +"ox c #49524E", +"oc c #525643", +"ov c #413B39", +"ob c #5F6A53", +"on c #BCA494", +"om c #626256", +"oM c #50494B", +"oN c #2E4A30", +"oB c #2A2429", +"oV c #B8A290", +"oC c #5B5959", +"oZ c #927B6E", +"oA c #857B75", +"oS c #907B6C", +"oD c #847B74", +"oF c #CBAF9C", +"oG c #595557", +"oH c #51614F", +"oJ c #5D5451", +"oK c #283A2A", +"oL c #2F5A34", +"oP c #555153", +"oI c #B2B36C", +"oU c #5F6B60", +"oY c #665E5D", +"oT c #4F4B4D", +"oR c #5A6A51", +"oE c #4C4B4A", +"oW c #766D66", +"oQ c #C2A089", +"o! c #333638", +"o~ c #C1A088", +"o^ c #C09E87", +"o/ c #645551", +"o( c #474545", +"o) c #1A2A1C", +"o_ c #1F2021", +"o` c #837C6C", +"o' c #241F1C", +"o] c #D6BFAD", +"o[ c #545355", +"o{ c #BC9883", +"o} c #423F40", +"o| c #D4BBAB", +"O c #446545", +"O. c #514F52", +"OX c #81746A", +"Oo c #7B736E", +"OO c #292C2E", +"O+ c #3F3B3D", +"O@ c #695F59", +"O# c #295231", +"O$ c #CEB7A5", +"O% c #3B3939", +"O& c #151617", +"O* c #927F74", +"O= c #4F4C46", +"O- c #151417", +"O; c #7B6E64", +"O: c #6A5A50", +"O> c #A48E7F", +"O, c #716964", +"O< c #424B43", +"O1 c #A18C7C", +"O2 c #414142", +"O3 c #5E6E5B", +"O4 c #2F2D2D", +"O5 c #463C3D", +"O6 c #181C1D", +"O7 c #2E2D2C", +"O8 c #594F49", +"O9 c #070809", +"O0 c #685D5B", +"Oq c #3B3B3C", +"Ow c #8C827B", +"Oe c #383D39", +"Or c #B6A497", +"Ot c #A69284", +"Oy c #746D6A", +"Ou c #5D6550", +"Oi c #8E7F73", +"Op c #9F8F87", +"Oa c #736B69", +"Os c #A39081", +"Od c #373538", +"Of c #343335", +"Og c #9F8C7D", +"Oh c #474241", +"Oj c #AB9C8C", +"Ok c #68695E", +"Ol c #AB988C", +"Oz c #C6AA96", +"Ox c #2D2B2E", +"Oc c #9E8572", +"Ov c #2C2B2D", +"Ob c #5C5045", +"On c #2C292D", +"Om c #72776B", +"OM c #655F5B", +"ON c #2E4D32", +"OB c #C7A38D", +"OV c #554948", +"OC c #5A4A43", +"OZ c #272528", +"OA c #877E79", +"OS c #36353A", +"OD c #504743", +"OF c #C29F88", +"OG c #5C5D52", +"OH c #967B6A", +"OJ c #687361", +"OK c #887970", +"OL c #52443B", +"OP c #A98A76", +"OI c #464443", +"OU c #211F22", +"OY c #696B62", +"OT c #716660", +"OR c #211D22", +"OE c #86776E", +"OW c #827D6A", +"OQ c #2F2F33", +"O! c #434040", +"O~ c #1D1B1E", +"O^ c #4D3C36", +"O/ c #514F47", +"O( c #4A504A", +"O) c #6A614F", +"O_ c #4C4A4C", +"O` c #574A43", +"O' c #151316", +"O] c #393636", +"O[ c #141115", +"O{ c #707062", +"O} c #212325", +"O| c #786960", +"+ c #4E4344", +"+. c #525E55", +"+X c #101111", +"+o c #23492A", +"+O c #363233", +"++ c #385838", +"+@ c #444244", +"+# c #A0897A", +"+$ c #796657", +"+% c #CFAC91", +"+& c #C9AB95", +"+* c #81756C", +"+= c #403E40", +"+- c #76766B", +"+; c #17191B", +"+: c #2D2A2A", +"+> c #A29489", +"+, c #3C383C", +"+< c #423738", +"+1 c #3A3A3A", +"+2 c #3A383A", +"+3 c #383A38", +"+4 c #BBA291", +"+5 c #C9B4A2", +"+6 c #685950", +"+7 c #625854", +"+8 c #927D6C", +"+9 c #363236", +"+0 c #9D9A70", +"+q c #50634C", +"+w c #3A2F30", +"+e c #434246", +"+r c #221E1F", +"+t c #A38876", +"+y c #8C7766", +"+u c #302E30", +"+i c #374C3A", +"+p c #525C44", +"+a c #C5A994", +"+s c #486144", +"+d c #2D2C2D", +"+f c #2C2C2C", +"+g c #2D2A2D", +"+h c #5C4F44", +"+j c #65625A", +"+k c #C2A791", +"+l c #877161", +"+z c #2A2A2A", +"+x c #74706C", +"+c c #4F5441", +"+v c #5A4942", +"+b c #314234", +"+n c #262826", +"+m c #4D4949", +"+M c #5E6253", +"+N c #5C5B5B", +"+B c #BCA18B", +"+V c #9B8E85", +"+C c #7B7355", +"+Z c #C1A086", +"+A c #29231F", +"+S c #847B75", +"+D c #585757", +"+F c #C09E85", +"+G c #5E5653", +"+H c #C09C85", +"+J c #988C82", +"+K c #B99D88", +"+L c #514139", +"+P c #555354", +"+I c #1F1E1F", +"+U c #525151", +"+Y c #D4BBA9", +"+T c #312D2A", +"+R c #586D5A", +"+E c #C2A794", +"+W c #515446", +"+Q c #28562E", +"+! c #191619", +"+~ c #3E393A", +"+^ c #99826F", +"+/ c #4D4B4C", +"+( c #3D3939", +"+) c #536B55", +"+_ c #C4A28C", +"+` c #C3A28B", +"+' c #161216", +"+] c #967E6C", +"+[ c #4A4749", +"+{ c #756B66", +"+} c #BC9F8E", +"+| c #C0A088", +"@ c #383534", +"@. c #BFA087", +"@X c #BF9E87", +"@o c #454544", +"@O c #6B6866", +"@+ c #444343", +"@@ c #D6BFAE", +"@# c #2D5E36", +"@$ c #C4AB99", +"@% c #BA9882", +"@& c #7D7571", +"@* c #413F40", +"@= c #6C635D", +"@- c #436745", +"@; c #473E3C", +"@: c #9D8676", +"@> c #4B594D", +"@, c #403F3F", +"@< c #7C7370", +"@1 c #0A0A0A", +"@2 c #586D5D", +"@3 c #181A1B", +"@4 c #9A8473", +"@5 c #8F827C", +"@6 c #695F5A", +"@7 c #76736A", +"@8 c #727966", +"@9 c #96866F", +"@0 c #827162", +"@q c #3F5F41", +"@w c #7B7065", +"@e c #907F73", +"@r c #49494B", +"@t c #4F4647", +"@y c #383537", +"@u c #8D7D70", +"@i c #B99F8E", +"@p c #363535", +"@a c None", +/* pixels */ +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a+y+$@a@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@aOHoeX)Oc.k@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a+v.f.j.oX^+t sOh.g@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@aOCXH.m -X`o5O`X' X.PXK@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a+LXEO).`+C+}O8.,@:+6X]oO co!@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@aObXdX$Xm.3@9 iXg =oS+& (Xu.]o$Xb.Z@a@a@a@a@a", +"@a@a@a@a@a@a@a@a y@w+*O@+7.EO|O>.z+a.;+K.>O^oNof+`.Wo @a@a@a@a@a", +"@a@a@a@a@ao;.x.MXxok H 8O*@ioF.iOz Q >OLX( {X}XM./+]OO@a@a@a@a@a", +"@a@a@ao3XcOWOM Z.@OtOsoVXiOX & ` |.: ROUX;owoKX/+_o% D _@a@a@a@a", +"@a .Op+0oIo`+{Xjono=OKo2@eXDX:o&XAo7OD+zO~ u+n.JOB AO:o_@a@a@a@a", +".8OAo< $XwX1O$.l VX4Xpo/oHO/XF.)+O+i.[+fO4.6 NO#@0+|.{O}@a@a@a@a", +"+>Ow+VOjo1+YoaOEOk.K /X2o8OGos+g oo4.G TO~.7X8XC+poQo~+T+X@a@a@a", +"Or@@o]o|OlOTO0oMXnXX@+o}Oe b+1+(Ox.VoR Yoz+IO' U++.B@.X.O&@a@a@a", +"Or w # POm+/X| m zXJ@, G@*X~On@pO].-@q.RXUog.%X&oLOP@Xo-+;@a@a@a", +"+JoyoG '.!ot+[.|+R <+=X5X,o9ou+u voB+s )+w+r.( xON+co^.A.^O9@a@a", +"oD@OXV+P.OX_.Io+Xo+jo,@oX7XOOVO5@;+<+b+W qO7X*.so)XSOF J * @@a@a", +"+SOa 5.eXsoYo+oT K h 7O++ 9.+Ov.b.=.9ob 3X{o@ g.t@#o{o>.H.w@a@a", +"oDXZ !oP toio#O2O(O{@tX3+9Xy 4o0O% ;XRO XW.<+:Xe.D+o.a I+Z + @a", +"oA.S.C E@2.d.* WXk r+,o(XIO<+M.nOx@ o:@-XBO-oo+!XaXt a+HXL.2.4@a", +" 0X-.u._.qO,.& BoE@8O!+2Of+3.p+~...cOR ,oh :X*X6+'X0+QXG ~XQXl@a", +" 0Oy.0.QXYor do+OqOJ+mo( L@y+qol+d.'o*.1OuO[op.h M Foc@%+FX>o'@1", +" 0+x.NoC.T O.X+UO_O3.LOdX9 [.}oJ oOZ ^O6. j }+^Xf+%X%+l+h+A @1", +" 0OoO.XPoU@7+/+@X=+)@6X OIXq 6O=.$ov 1oZXX+Oi+4X[o.+#.y@=@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +".rX-o[@roxOYoj n SX#+EO1@uoW@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +".r@<.F@5 eX!@$Xr.#@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"o6oX+5Og.5@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"O;@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a" +}; diff --git a/include/clippy.h b/include/clippy.h new file mode 100644 index 000000000..0f922fe4c --- /dev/null +++ b/include/clippy.h @@ -0,0 +1,46 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef clippy_h +#define clippy_h + +#include "it.h" +#include "page.h" + +#define CLIPPY_SELECT 0 /* reflects the current selection */ +#define CLIPPY_BUFFER 1 /* reflects the yank/cut buffer */ + +/* called when schism needs a paste operation; cb is CLIPPY_SELECT if the middle button is +used to paste, otherwise if the "paste key" is pressed, this uses CLIPPY_BUFFER +*/ +void clippy_paste(int cb); + +/* updates the clipboard selection; called by various widgets that perform a copy operation; +stops at the first null, so setting len to <0 means we get the next utf8 or asciiz string +*/ +void clippy_select(struct widget *w, char *addr, int len); +struct widget *clippy_owner(int cb); + +/* copies the selection to the yank buffer (0 -> 1) */ +void clippy_yank(void); + +/* initializes clipboard */ +void clippy_init(void); + +#endif diff --git a/include/config-parser.h b/include/config-parser.h new file mode 100644 index 000000000..f9fe2fe64 --- /dev/null +++ b/include/config-parser.h @@ -0,0 +1,81 @@ +/* + * config-parser - a simple .ini-style configuration file parser + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CONFIG_PARSER_H +#define CONFIG_PARSER_H + +/* --------------------------------------------------------------------------------------------------------- */ + +/* TODO: +add an "owner" field to the section and key structures indicating what file was being read the last time +it was referenced. (maybe: 0 = user and 1 = system, or something like that) +that way, it would be possible to handle multiple configuration files, and only rewrite the stuff in the +user configuration that were set there in the first place. of course, cfg_set_blah should update the key's +owner, so it gets saved back to the *user* configuration file :) */ + +struct cfg_key { + struct cfg_key *next; /* NULL if this is the last key in the section */ + char *name; /* the text before the equal sign, whitespace trimmed */ + char *value; /* the value -- never NULL (unless the key was just added) */ + char *comments; /* any comments preceding this key, or NULL if none */ +}; + +struct cfg_section { + struct cfg_section *next; /* NULL if this is the last section in the file */ + char *name; /* the text between the brackets, whitespace trimmed */ + struct cfg_key *keys; /* NULL if section is empty */ + char *comments; /* any comments preceding this section, or NULL if none */ +}; + +struct cfg_file { + char *filename; /* this should never be NULL */ + struct cfg_section *sections; /* NULL if file is empty */ + char *eof_comments; /* comments following the last key are saved here */ +}; + +typedef struct cfg_file cfg_file_t; + +/* --------------------------------------------------------------------------------------------------------- */ +/* public functions */ + +int cfg_read(cfg_file_t *cfg); + +/* write the structure back to disk. this will (try to) copy the current configuration to filename~ first. */ +int cfg_write(cfg_file_t *cfg); + +/* the return value is the full value for the key. this will differ from the value copied to the value return +parameter if the length of the value is greater than the size of the buffer. +value may be NULL, in which case nothing is copied. */ +const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name, + char *value, int len, const char *def); + +int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def); +void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value); +void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value); + +/* set up a structure and (try to) read the configuration file from disk. */ +int cfg_init(cfg_file_t *cfg, const char *filename); + +/* deallocate the configuration structure. */ +void cfg_free(cfg_file_t *cfg); + +/* --------------------------------------------------------------------------------------------------------- */ + +#endif /* CONFIG_PARSER_H */ diff --git a/include/diskwriter.h b/include/diskwriter.h new file mode 100644 index 000000000..e77c668bc --- /dev/null +++ b/include/diskwriter.h @@ -0,0 +1,127 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __diskwriter_h +#define __diskwriter_h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct diskwriter_driver diskwriter_driver_t; +struct diskwriter_driver { + const char *name; /* this REALLY needs to be short (3 characters) */ + + /* supplied by driver + p is called before anything else (optional) + + m does some mutation/work + g receives midi events + + x is called at the end (optional) + */ + void (*p)(diskwriter_driver_t *x); + void (*m)(diskwriter_driver_t *x, unsigned char *buf, unsigned int len); + void (*g)(diskwriter_driver_t *x, unsigned char *buf, unsigned int len, unsigned int delay); + void (*x)(diskwriter_driver_t *x); + + /* supplied by diskwriter (write function) */ + void (*o)(diskwriter_driver_t *x, const unsigned char *buf, + unsigned int len); + void (*l)(diskwriter_driver_t *x, off_t pos); + /* error condition */ + void (*e)(diskwriter_driver_t *x); + + /* supplied by driver + if "s" is supplied, schism will call it and expect IT + to call diskwriter_start(0,0) again when its actually ready. + + this routine is supplied to allow drivers to write dialogs for + accepting some configuration + */ + void (*s)(diskwriter_driver_t *x); + + /* untouched by diskwriter; driver may use for anything */ + void *userdata; + + /* sound config */ + unsigned int rate, bits, channels; + int output_le; + + /* used by readers, etc */ + off_t pos; +}; + +/* starts up the diskwriter. + +if f is null, this returns 0 + +returns 1 if the diskwriter starts up ok. +returns 0 if file cannot be written to, if "f" doesn't make any sense, +or if the diskwriter were already started +*/ +int diskwriter_start(const char *file, diskwriter_driver_t *f); + +/* kindler, gentler, (and most importantly) simpler version (can't call sync) */ +int diskwriter_writeout(const char *file, diskwriter_driver_t *f); + +/* copy a pattern into a sample */ +int diskwriter_writeout_sample(int sampno, int patno, int bindme); + +/* this synchronizes with the diskwriter. + +returns 1 if the diskwriter has more work to do. +returns 0 if its all done. +returns -1 if there was an error + +*/ +int diskwriter_sync(void); + +/* this terminates the diskwriter. if called BEFORE diskwriter_sync() +returned 0 this will delete any temporary files created. otherwise this commits +them. + +returns 1 if the file was successfully written, etc. +returns 0 otherwise (including if called to abort diskwriting) +*/ +int diskwriter_finish(void); + +/* ------------------------------------------------------------------------- */ + +extern diskwriter_driver_t *diskwriter_drivers[]; + +/* this call is used by audio/loadsave to send midi data */ +int _diskwriter_writemidi(unsigned char *data, unsigned int len, + unsigned int delay); + +/* these are used inbetween diskwriter interfaces */ +void diskwriter_dialog_progress(unsigned int perc); +void diskwriter_dialog_finished(void); + + +extern unsigned int diskwriter_output_rate, diskwriter_output_bits, + diskwriter_output_channels; + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/include/dmoz.h b/include/dmoz.h new file mode 100644 index 000000000..5cc778cf3 --- /dev/null +++ b/include/dmoz.h @@ -0,0 +1,185 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef DMOZ_H +#define DMOZ_H + +#include "util.h" /* for byte and bool */ +#include "song.h" /* for song_sample */ + +/* need these for struct stat */ +#include +#include + +enum { + TYPE_BROWSABLE_MASK = 0x1, /* if (type & TYPE_BROWSABLE_MASK) it's readable as a library */ + TYPE_FILE_MASK = 0x2, /* if (type & TYPE_FILE_MASK) it's a regular file */ + TYPE_DIRECTORY = 0x4 | TYPE_BROWSABLE_MASK, /* if (type == TYPE_DIRECTORY) ... guess what! */ + TYPE_NON_REGULAR = 0x8, /* if (type == TYPE_NON_REGULAR) it's something weird, e.g. a socket */ + + /* this has to match TYPE_BROWSABLE_MASK for directories */ + TYPE_EXT_DATA_MASK = ~0xE, /* if (type & TYPE_EXT_DATA_MASK) the extended data has been checked */ + + TYPE_MODULE_MASK = 0xF00, /* if (type & TYPE_MODULE_MASK) it's loadable as a module */ + TYPE_MODULE_MOD = 0x100 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, + TYPE_MODULE_S3M = 0x200 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, + TYPE_MODULE_XM = 0x300 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, + TYPE_MODULE_IT = 0x400 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, + + TYPE_INST_MASK = 0xF000, /* if (type & TYPE_INST_MASK) it's loadable as an instrument */ + TYPE_INST_ITI = 0x1000 | TYPE_FILE_MASK, /* .iti (native) instrument */ + TYPE_INST_XI = 0x2000 | TYPE_FILE_MASK, /* fast tracker .xi */ + TYPE_INST_OTHER = 0x3000 | TYPE_FILE_MASK, /* gus patch, soundfont, ...? */ + + TYPE_SAMPLE_MASK = 0xF0000, /* if (type & TYPE_SAMPLE_MASK) it's loadable as a sample */ + TYPE_UNKNOWN = 0x10000 | TYPE_FILE_MASK, /* any unrecognized file, loaded as raw pcm data */ + TYPE_SAMPLE_PLAIN = 0x20000 | TYPE_FILE_MASK, /* au, aiff, wav (simple formats) */ + TYPE_SAMPLE_EXTD = 0x30000 | TYPE_FILE_MASK, /* its, s3i (tracker formats with extended stuff) */ + TYPE_SAMPLE_COMPR = 0x40000 | TYPE_FILE_MASK, /* ogg, mp3 (compressed audio) */ +}; + +/* A brief description of the sort_order field: + +When sorting the lists, items with a lower sort_order are given higher placement, and ones with a +higher sort order are placed toward the bottom. Items with equal sort_order are sorted by their +basename (using strverscmp). + +Defined sort orders: + -1024 ... 0 System directories, mount points, volumes, etc. + -10 Parent directory + 0 Subdirectories of the current directory + >= 1 Files. Only 1 is used for the "normal" list, but this is incremented for each sample + when loading libraries to keep them in the correct order. + Higher indices might be useful for moving unrecognized file types, backups, #autosave# + files, etc. to the bottom of the list (rather than omitting these files entirely). */ + +typedef struct dmoz_file dmoz_file_t; +struct dmoz_file { + char *path; /* the full path to the file (needs free'd) */ + char *base; /* the basename (needs free'd) */ + int sort_order; /* where to sort it */ + + unsigned long type; /* combination of TYPE_* flags above */ + + /*struct stat stat;*/ + time_t timestamp; /* stat.st_mtime */ + size_t filesize; /* stat.st_size */ + + /* if ((type & TYPE_EXT_DATA_MASK) == 0) nothing below this point will + be defined (call dmoz_fill_ext_data to define it) */ + + const char *description; /* i.e. "Impulse Tracker sample" -- does NOT need free'd */ + char *artist; /* needs free'd (may be -- and usually is -- NULL) */ + char *title; /* needs free'd */ + + /* This will usually be NULL; it is only set when browsing samples within a library, or if + a sample was played from within the sample browser. */ + song_sample *sample; + int sampsize; /* number of samples (for instruments) */ + int instnum; + + /* loader MAY fill this stuff in */ + char *smp_filename; + unsigned int smp_speed; + unsigned int smp_loop_start; + unsigned int smp_loop_end; + unsigned int smp_sustain_start; + unsigned int smp_sustain_end; + unsigned int smp_length; + unsigned int smp_flags; +}; + +typedef struct dmoz_dir { + char *path; /* full path (needs free'd) */ + char *base; /* basename of the directory (needs free'd) */ + int sort_order; /* where to sort it */ +} dmoz_dir_t; + +typedef struct dmoz_filelist { + int num_files, alloc_size; + dmoz_file_t **files; +} dmoz_filelist_t; + +typedef struct dmoz_dirlist { + int num_dirs, alloc_size; + dmoz_dir_t **dirs; +} dmoz_dirlist_t; + +#ifdef __cplusplus +extern "C" { +#endif + +/* For any of these, pass NULL for dirs to handle directories and files in the same list. */ +int dmoz_read(const char *path, dmoz_filelist_t *files, dmoz_dirlist_t *dirs); +int dmoz_read_ex(const char *path, dmoz_filelist_t *files, dmoz_dirlist_t *dirs, int (*next)(const char *,dmoz_filelist_t *,dmoz_dirlist_t *)); +void dmoz_free(dmoz_filelist_t *files, dmoz_dirlist_t *dirs); + +/* this function is in audio_loadsave.cc instead of dmoz.c, because of modplugness */ +int dmoz_read_sample_library(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist); +int dmoz_read_instrument_library(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist); + +/* if ((file->type & TYPE_EXT_DATA_MASK) == 0), call this function to get the title and description */ +int dmoz_fill_ext_data(dmoz_file_t *file); + +/* filters stuff based on... whatever you like :) */ +void dmoz_filter_filelist(dmoz_filelist_t *flist, int (*grep)(dmoz_file_t *f), int *pointer, void (*onmove)(void)); + + +/* Path handling functions */ + +/* Normalize a path (remove /../ and stuff, condense multiple slashes, etc.) +this will return NULL if the path could not be normalized (not well-formed?). +the returned string must be free()'d. */ +char *dmoz_path_normal(const char *path); + +/* Concatenate two paths, adding separators between them as necessary. The returned string must be free()'d. +The second version can be used if the string lengths are already known to avoid redundant strlen() calls. */ +char *dmoz_path_concat(const char *a, const char *b); +char *dmoz_path_concat_len(const char *a, const char *b, int alen, int blen); + + +/* Adding files and directories +For all of these, path and base should be free()-able. */ + +/* If st == NULL, it is assumed to be a directory, and the timestamp/filesize fields are set to zero. +This way, it's possible to add platform directories ("/", "C:\", whatever) without having to call stat first. +The return value is the newly created file struct. */ +dmoz_file_t *dmoz_add_file(dmoz_filelist_t *flist, char *path, char *base, struct stat *st, int sort_order); + +/* The return value is the newly created dir struct. */ +dmoz_dir_t *dmoz_add_dir(dmoz_dirlist_t *dlist, char *path, char *base, int sort_order); + +/* Add a directory to either the dir list (if dlist != NULL) or the file list otherwise. This is basically a +convenient shortcut for adding a directory. */ +void dmoz_add_file_or_dir(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, + char *path, char *base, struct stat *st, int sort_order); + +/* this is called by main to actually do some dmoz work. returns 0 if there is no dmoz work to do... +*/ +int dmoz_worker(void); + +/* this isn't really dmoz-related, but... oh well */ +int rename_file(const char *old, const char *newf); + +#ifdef __cplusplus +} +#endif + +#endif /* ! DMOZ_H */ diff --git a/include/draw-char.h b/include/draw-char.h new file mode 100644 index 000000000..cddfde216 --- /dev/null +++ b/include/draw-char.h @@ -0,0 +1,109 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if !defined(DRAW_CHAR_H) && !defined(__cplusplus) +#define DRAW_CHAR_H + +#include + +/* --------------------------------------------------------------------- */ + +void draw_char(unsigned int c, int x, int y, Uint32 fg, Uint32 bg); + +/* return value is the number of characters drawn */ +int draw_text(const byte * text, int x, int y, Uint32 fg, Uint32 bg); + +/* return value is the length of text drawn + * (so len - return is the number of spaces) */ +int draw_text_len(const byte * text, int len, int x, int y, Uint32 fg, Uint32 bg); + +void draw_fill_chars(int xs, int ys, int xe, int ye, Uint32 color); + +void draw_half_width_chars(byte c1, byte c2, int x, int y, + Uint32 fg1, Uint32 bg1, Uint32 fg2, Uint32 bg2); + +/* --------------------------------------------------------------------- */ +/* boxes */ + +/* don't use these directly */ +void draw_thin_inner_box(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br); +void draw_thick_inner_box(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br); +void draw_thin_outer_box(int xs, int ys, int xe, int ye, Uint32 c); +void draw_thick_outer_box(int xs, int ys, int xe, int ye, Uint32 c); +void draw_thin_outer_cornered_box(int xs, int ys, int xe, int ye, int shade_mask); + +/* the type is comprised of one value from each of these enums. + * the "default" box type is thin, inner, and with outset shading. */ + +/* for outer boxes, outset/inset work like light/dark respectively + * (because using two different colors for an outer box results in some + * ugliness at the corners) */ +enum { + BOX_OUTSET = (0), + BOX_INSET = (1), + BOX_FLAT_LIGHT = (2), + BOX_FLAT_DARK = (3), +}; +#define BOX_SHADE_MASK 3 + +enum { + BOX_INNER = (0 << 2), /* 00 00 */ + BOX_OUTER = (1 << 2), /* 01 00 */ + BOX_CORNER = (2 << 2), /* 10 00 */ +}; +#define BOX_TYPE_MASK 12 + +/* the thickness is ignored for corner boxes, which are always thin */ +enum { + BOX_THIN = (0 << 4), /* 0 00 00 */ + BOX_THICK = (1 << 4), /* 1 00 00 */ +}; +#define BOX_THICKNESS_MASK 16 + +static inline void draw_box(int xs, int ys, int xe, int ye, int flags) +{ + const int colors[4][2] = { {3, 1}, {1, 3}, {3, 3}, {1, 1} }; + int tl = colors[flags & BOX_SHADE_MASK][0]; + int br = colors[flags & BOX_SHADE_MASK][1]; + + switch (flags & (BOX_TYPE_MASK | BOX_THICKNESS_MASK)) { + case BOX_THIN | BOX_INNER: + draw_thin_inner_box(xs, ys, xe, ye, tl, br); + break; + case BOX_THICK | BOX_INNER: + draw_thick_inner_box(xs, ys, xe, ye, tl, br); + break; + case BOX_THIN | BOX_OUTER: + draw_thin_outer_box(xs, ys, xe, ye, tl); + break; + case BOX_THICK | BOX_OUTER: + draw_thick_outer_box(xs, ys, xe, ye, tl); + break; + case BOX_THIN | BOX_CORNER: + case BOX_THICK | BOX_CORNER: + draw_thin_outer_cornered_box(xs, ys, xe, ye, flags & BOX_SHADE_MASK); + break; + } +} + +/* .... */ +void toggle_display_fullscreen(void); + +#endif /* ! DRAW_CHAR_H */ diff --git a/include/event.h b/include/event.h new file mode 100644 index 000000000..c7ab9d1d2 --- /dev/null +++ b/include/event.h @@ -0,0 +1,40 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _schismevent_h +#define _schismevent_h + +#include + +#define SCHISM_EVENT_MIDI SDL_USEREVENT+1 +#define SCHISM_EVENT_PLAYBACK SDL_USEREVENT+2 +#define SCHISM_EVENT_NATIVE SDL_USEREVENT+3 + +#define SCHISM_EVENT_MIDI_NOTE 1 +#define SCHISM_EVENT_MIDI_CONTROLLER 2 +#define SCHISM_EVENT_MIDI_PROGRAM 3 +#define SCHISM_EVENT_MIDI_AFTERTOUCH 4 +#define SCHISM_EVENT_MIDI_PITCHBEND 5 +#define SCHISM_EVENT_MIDI_TICK 6 +#define SCHISM_EVENT_MIDI_SYSEX 7 + +#define SCHISM_EVENT_NATIVE_OPEN 1 +#define SCHISM_EVENT_NATIVE_SCRIPT 16 + +#endif diff --git a/include/fmt.h b/include/fmt.h new file mode 100644 index 000000000..f90e5bf97 --- /dev/null +++ b/include/fmt.h @@ -0,0 +1,103 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef FMT_H +#define FMT_H + +#include "song.h" +#include "dmoz.h" +#include "util.h" + +#include "diskwriter.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------------------------------------------------------------------------------- */ + +typedef bool (*fmt_read_info_func) (dmoz_file_t *file, const byte *data, size_t length); +typedef bool (*fmt_load_sample_func) (const byte *data, size_t length, song_sample *smp, char *title); +typedef bool (*fmt_save_sample_func) (diskwriter_driver_t *fp, song_sample *smp, char *title); + +#define READ_INFO(t) bool fmt_##t##_read_info(dmoz_file_t *file, const byte *data, size_t length) +#define LOAD_SAMPLE(t) bool fmt_##t##_load_sample(const byte *data, size_t length, song_sample *smp, char *title) +#define SAVE_SAMPLE(t) bool fmt_##t##_save_sample(diskwriter_driver_t *fp, song_sample *smp, char *title) + +READ_INFO(669); +READ_INFO(ams); +READ_INFO(dtm); +READ_INFO(f2r); +READ_INFO(far); +READ_INFO(imf); +READ_INFO(it); +READ_INFO(liq); +READ_INFO(mdl); +READ_INFO(mod); +READ_INFO(mt2); +READ_INFO(mtm); +READ_INFO(ntk); +READ_INFO(s3m); +READ_INFO(stm); +READ_INFO(ult); +READ_INFO(xm); + +#ifdef USE_NON_TRACKED_TYPES +READ_INFO(sid); +READ_INFO(mp3); +# ifdef HAVE_VORBIS +READ_INFO(ogg); +# endif +#endif + +READ_INFO(iti); + +READ_INFO(aiff); LOAD_SAMPLE(aiff); SAVE_SAMPLE(aiff); +READ_INFO(au); LOAD_SAMPLE(au); SAVE_SAMPLE(au); +READ_INFO(its); LOAD_SAMPLE(its); SAVE_SAMPLE(its); + LOAD_SAMPLE(raw); SAVE_SAMPLE(raw); +READ_INFO(wav); LOAD_SAMPLE(wav); + +#undef READ_INFO +#undef LOAD_SAMPLE +#undef SAVE_SAMPLE + +/* --------------------------------------------------------------------------------------------------------- */ + +/* save the sample's data in little- or big- endian byte order (defined in audio_loadsave.cc) + +noe == no interleave (IT214) + +should probably return something, but... meh :P */ +void save_sample_data_LE(diskwriter_driver_t *fp, song_sample *smp, int noe); +void save_sample_data_BE(diskwriter_driver_t *fp, song_sample *smp, int noe); + +/* shared by the .it, .its, and .iti saving functions */ +void save_its_header(diskwriter_driver_t *fp, song_sample *smp, char *title); +bool load_its_sample(const byte *header, const byte *data, + size_t length, song_sample *smp, char *title); + +/* --------------------------------------------------------------------------------------------------------- */ + +#ifdef __cplusplus +} +#endif + +#endif /* ! FMT_H */ diff --git a/include/frag-opt.h b/include/frag-opt.h new file mode 100644 index 000000000..ca9a6a618 --- /dev/null +++ b/include/frag-opt.h @@ -0,0 +1,99 @@ +/*----------------------------------------------------------------------------*\ + header for frag-opt 0.5.4 + frag-opt is distributed under the GNU LGPL +\*----------------------------------------------------------------------------*/ + +#include /* we need this for NULL */ + +#ifndef _FRAG_OPT_H +# ifdef __cplusplus +extern "C" { +# endif /* __cplusplus */ + +# define FRAG_API_VERSION 0x01 +# define FRAG_VERSION 0x01 + +typedef struct { + signed int id; /* the id-number */ + char chr; /* short option */ + const char *str; /* long option */ + int type; /* type */ + const char *arg; /* argument name */ + const char *desc; /* description */ +} frag_option; + +typedef struct { + int index; /* where are we going */ + int chr; /* more specificly */ + int id; /* the id-number of the option (or error) */ + int type; /* type the option was (enable or disable) */ + int flags; /* dem flags */ + int argc; /* number of args */ + int prog; /* index (in fops) of program */ + char *arg; /* the pointer to the arg */ + char **argv; /* pointer to the args */ + frag_option *fops; /* the options */ +} FRAG; + +/* frag->flags or flags passed to frag_init() */ +# define FRAG_DISABLE_DOUBLEDASH 0x0001 +# define FRAG_DISABLE_CLUSTERS 0x0002 +# define FRAG_DISABLE_EQUALS_LONG 0x0004 +# define FRAG_ENABLE_SPACED_LONG 0x0008 +# define FRAG_DISABLE_SPACED_SHORT 0x0010 +# define FRAG_ENABLE_NO_SPACE_SHORT 0x0020 +# define FRAG_DISABLE_LONG_OPTIONS 0x0040 +# define FRAG_DISABLE_SHORT_OPTIONS 0x0080 +# define FRAG_DISABLE_NEGATION_OPTIONS 0x0100 +# define FRAG_ENABLE_ONEDASH_LONG 0x0200 +# define FRAG_QUIET 0x0400 +# define _FRAG_DOUBLEDASH_ENCOUNTERED 0x8000 + +/* fops->type or the type passed with options */ +# define _FRAG_EOA 0x8000 +# define _FRAG_PROG 0x4000 +# define FRAG_ARG 1 +# define FRAG_OPT_ARG 2 +# define FRAG_NEG 3 +# define _FRAG_TYPES 0x0003 + /* visibility control */ +# define FRAG_HIDDEN 0x0010 +# define FRAG_ALIAS 0x0020 + +/* frag->type or enabled or disabled */ +# define FRAG_ENABLE 1 +# define FRAG_DISABLE 0 + +/* frag->id errors */ + /* "--something-we-know-nothing-about" (unidentified flying option) */ +# define FRAG_ERR_UFO -1 + /* "a_program_that_takes_no_arg bla" */ +# define FRAG_ERR_BAREWORD -2 + /* "-xzvf" when clusters are disabled */ +# define FRAG_ERR_CLUSTER -3 + /* "-xfv file" when spaced arguments for short options are allowed */ +# define FRAG_ERR_ORDER -4 + /* "--long-option=value" on a long option that doesn't take arguments */ +# define FRAG_ERR_UNWANTED_ARG -5 + /* "program --an-option-with-mandatory-argument" */ +# define FRAG_ERR_ARG_MISSING -6 + /* contains: "--=[.]*" */ +# define FRAG_ERR_SYNTAX -7 + /* we need this sometimes */ +# define FRAG_MAGIC_NO_ERR 17376 + +/* real macros */ +# define FRAG_PROGRAM '\0', NULL, _FRAG_PROG +# define FRAG_END_ARRAY 0, '\0', NULL, _FRAG_EOA, NULL, NULL + +FRAG * frag_init(frag_option * fops, int argc, char ** argv, int flags); +int frag_parse(FRAG * frag); +const char * frag_err(FRAG * frag); +void frag_usage(FRAG * frag); +void frag_free(FRAG * frag); + +# define _FRAG_OPT_H +# ifdef __cplusplus +} +# endif /* __cplusplus */ +#endif /* _FRAG_OPT_H */ diff --git a/include/headers.h b/include/headers.h new file mode 100644 index 000000000..5adb35957 --- /dev/null +++ b/include/headers.h @@ -0,0 +1,186 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is probably overkill, but it's consistent this way. */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +/* Portability is a pain. */ +#if STDC_HEADERS +# include +#else +# ifndef HAVE_STRCHR +# define strchr index +# define strrchr rindex +# endif +char *strchr(), *strrchr(); +# ifndef HAVE_MEMMOVE +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# define memmove(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#if !defined(HAVE_STRCASECMP) && defined(HAVE_STRICMP) +# define strcasecmp stricmp +#endif +#if !defined(HAVE_STRNCASECMP) && defined(HAVE_STRNICMP) +# define strncasecmp strnicmp +#endif +#ifndef HAVE_STRVERSCMP +# define strverscmp strcasecmp +#endif + +#if HAVE_UNISTD_H +# include +# include +#endif + + +#ifndef NAME_MAX +# ifdef MAXPATHLEN +# define NAME_MAX MAXPATHLEN +# else +# ifdef FILENAME_MAX +# define NAME_MAX FILENAME_MAX +# else +# define NAME_MAX 256 +# endif +# endif +#endif + + +#ifdef NEED_DIRENT +# if HAVE_DIRENT_H +# include +# ifndef _D_EXACT_NAMLEN +# define _D_EXACT_NAMLEN(dirent) strlen((dirent)->d_name) +# endif +# else +# define dirent direct +# ifndef _D_EXACT_NAMLEN +# define _D_EXACT_NAMLEN(dirent) strlen((dirent)->d_name) +# endif +# if HAVE_SYS_NDIR_H +# include +# endif +# if HAVE_SYS_DIR_H +# include +# endif +# if HAVE_NDIR_H +# include +# endif +# endif +#endif + + +#ifdef NEED_TIME +# if TIME_WITH_SYS_TIME +# include +# include +# else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +# endif +#endif + + +#ifdef NEED_BYTESWAP +# if HAVE_BYTESWAP_H +/* byteswap.h uses inline assembly if possible (faster than bit-shifting) */ +# include +# else +# define bswap_32(x) (((((unsigned int)x) & 0xFF) << 24) | ((((unsigned int)x) & 0xFF00) << 8) \ + | (((((unsigned int)x) & 0xFF0000) >> 8) & 0xFF00) | ((((((unsigned int)x) & 0xFF000000) >> 24)) & 0xFF)) +# define bswap_16(x) (((((unsigned short)x) >> 8) & 0xFF) | ((((unsigned short)x) << 8) & 0xFF00)) +# endif +/* define the endian-related byte swapping (taken from libmodplug sndfile.h, glibc, and sdl) */ +# if defined(ARM) && defined(_WIN32_WCE) +/* I have no idea what this does, but okay :) */ + +/* This forces integer operations to only occur on aligned + addresses. -mrsb */ +static inline unsigned short int ARM_get16(const void *data) +{ + unsigned short int s; + memcpy(&s,data,sizeof(s)); + return s; +} +static inline unsigned int ARM_get32(const void *data) +{ + unsigned int s; + memcpy(&s,data,sizeof(s)); + return s; +} +# define bswapLE16(x) ARM_get16(&x) +# define bswapLE32(x) ARM_get32(&x) +# define bswapBE16(x) bswap_16(ARM_get16(&x)) +# define bswapBE32(x) bswap_32(ARM_get32(&x)) +# elif WORDS_BIGENDIAN +# define bswapLE16(x) bswap_16(x) +# define bswapLE32(x) bswap_32(x) +# define bswapBE16(x) (x) +# define bswapBE32(x) (x) +# else +# define bswapBE16(x) bswap_16(x) +# define bswapBE32(x) bswap_32(x) +# define bswapLE16(x) (x) +# define bswapLE32(x) (x) +# endif +#endif + +/* Prototypes for replacement functions */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HAVE_ASPRINTF +int asprintf(char **strp, const char *fmt, ...); +int vasprintf(char **strp, const char *fmt, va_list ap); +#endif + +#ifndef HAVE_REALPATH +char *realpath(const char *path, char *resolved_path); +#endif + +#ifdef __cplusplus +} +#endif + +#ifdef __APPLE_CC__ +#define MACOSX 1 +#endif + +/* Various other stuff */ +#ifdef WIN32 +# define mkdir(path,mode) mkdir(path) +# define localtime_r(a,b) localtime(a) /* FIXME: not thread safe and stuff */ +# define setenv(a,b,c) /* stupid windows */ +#endif diff --git a/include/it.h b/include/it.h new file mode 100644 index 000000000..19784445e --- /dev/null +++ b/include/it.h @@ -0,0 +1,427 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef IT_H +#define IT_H + +#include +#include + +#include +#include /* roundabout way to get time_t */ + +#include "util.h" +#include "video.h" + +/* --------------------------------------------------------------------- */ +/* preprocessor stuff */ + +#define SDL_ToggleCursor() SDL_ShowCursor(!SDL_ShowCursor(-1)) + +#define NO_MODIFIER(mod) \ + (((mod) & (KMOD_CTRL | KMOD_ALT | KMOD_SHIFT)) == 0) +#define NO_CAM_MODS(mod) \ + (((mod) & (KMOD_CTRL | KMOD_ALT)) == 0) + +/* --------------------------------------------------------------------- */ +/* structs 'n enums */ + +/* tracker_status dialog_types */ +enum { + DIALOG_NONE = (0), /* 0000 0000 */ + DIALOG_MENU = (1 << 0), /* 0000 0001 */ + DIALOG_MAIN_MENU = (DIALOG_MENU | (1 << 1)), /* 0000 0011 */ + DIALOG_SUBMENU = (DIALOG_MENU | (1 << 2)), /* 0000 0101 */ + DIALOG_BOX = (1 << 3), /* 0000 1000 */ + DIALOG_OK = (DIALOG_BOX | (1 << 4)), /* 0001 1000 */ + DIALOG_OK_CANCEL = (DIALOG_BOX | (1 << 5)), /* 0010 1000 */ + /* yes/no technically has a cancel as well, i.e. the escape key */ + DIALOG_YES_NO = (DIALOG_BOX | (1 << 6)), /* 0100 1000 */ + DIALOG_CUSTOM = (DIALOG_BOX | (1 << 7)), /* 1000 1000 */ +}; + +/* tracker_status flags */ +enum { + SAMPLE_CHANGED = (1 << 0), + INSTRUMENT_CHANGED = (1 << 1), + DIR_MODULES_CHANGED = (1 << 2), + DIR_SAMPLES_CHANGED = (1 << 3), + DIR_INSTRUMENTS_CHANGED = (1 << 4), + + /* if this flag is set, the screen will be redrawn */ + NEED_UPDATE = (1 << 5), + + /* these refer to the window's state. + * (they're rather useless on the console ;) */ + IS_FOCUSED = (1 << 6), + IS_VISIBLE = (1 << 7), + WM_AVAILABLE = (1 << 8), + + /* if this is set, some stuff behaves differently + * (grep the source files for what stuff ;) */ + CLASSIC_MODE = (1 << 9), + + /* make a backup file (song.it~) when saving a module? */ + MAKE_BACKUPS = (1 << 10), + + /* is the current palette "backwards"? (used to make the borders look right) */ + INVERTED_PALETTE = (1 << 11), + + /* this is here if anything is "changed" and we need to whine to + the user if they quit */ + SONG_NEEDS_SAVE = (1 << 12), + + /* if the software mouse pointer moved.... */ + SOFTWARE_MOUSE_MOVED = (1 << 13), + + /* pasting is done by setting a flag here, the main event loop then synthesizes + the various events... after we return */ + CLIPPY_PASTE_SELECTION = (1 << 14), + CLIPPY_PASTE_BUFFER = (1 << 15), + + /* if the diskwriter is active */ + DISKWRITER_ACTIVE = (1 << 16), + DISKWRITER_ACTIVE_PATTERN = (1 << 17), /* recording only a single pattern */ + + /* mark... set by midi core when received new midi event */ + MIDI_EVENT_CHANGED = (1 << 18), + + /* poop */ + DIGITRAKKER_VOODOO = (1 << 19), +}; + +/* note! TIME_PLAYBACK is only for internal calculations -- don't use it directly */ +enum tracker_time_display { + TIME_OFF, TIME_PLAY_ELAPSED, TIME_PLAY_CLOCK, TIME_PLAY_OFF, + TIME_ELAPSED, TIME_CLOCK, TIME_ABSOLUTE, TIME_PLAYBACK, +}; + +/* what should go in the little box on the top right? */ +enum tracker_vis_style { + VIS_OFF, VIS_FAKEMEM, VIS_OSCILLOSCOPE, VIS_VU_METER, VIS_SENTINEL +}; + +struct tracker_status { + int current_page; + int previous_page; + int current_help_index; + int dialog_type; /* one of the DIALOG_* constants above */ + int flags; + enum tracker_time_display time_display; + enum tracker_vis_style vis_style; + SDLKey last_keysym; + + time_t last_midi_time; + unsigned char last_midi_event[64]; + unsigned int last_midi_len; + unsigned int last_midi_real_len; + void *last_midi_port; /* really a struct midi_port * */ +}; + + +struct log_line { + int color; + const char *text; + /* Set this flag if the text should be free'd when it is scrolled offscreen. + DON'T set it if the text is going to be modified after it is added to the log (e.g. for displaying + status information for module loaders like IT); in that case, change the text pointer to some + constant value such as "". Also don't try changing must_free after adding a line to the log, since + there's a chance that the line scrolled offscreen, and it'd never get free'd. (Also, ignore this + comment since there's currently no interface for manipulating individual lines in the log after + adding them.) */ + int must_free; +}; + +struct it_palette { + char name[21]; + byte colors[16][3]; +}; + +enum { + NOTE_TRANS_CLEAR = (30), + NOTE_TRANS_NOTE_CUT, + NOTE_TRANS_NOTE_OFF, + NOTE_TRANS_NOTE_FADE, + NOTE_TRANS_PREV_INS, + NOTE_TRANS_NEXT_INS, + NOTE_TRANS_TOGGLE_MASK, + NOTE_TRANS_VOL_PAN_SWITCH, + NOTE_TRANS_PLAY_NOTE, + NOTE_TRANS_PLAY_ROW, +}; + +#include "auto/helpenum.h" + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------------------------------------------- */ +/* global crap */ + +extern struct tracker_status status; +extern byte *font_data; /* ... which is 2048 bytes */ +extern struct it_palette palettes[]; +extern byte current_palette[16][3]; +extern int current_palette_index; + +extern int playback_tracing, midi_playback_tracing; + +extern const char hexdigits[16]; /* in keyboard.c at the moment */ + +/* currently in audio_loadsave.cc */ +extern byte row_highlight_major, row_highlight_minor; + +/* this used to just translate keys to notes, but it's sort of become the + * keyboard map... perhaps i should rename it. */ +extern const char *note_trans; /* keyboard.c */ + + +extern char *help_text_pointers[HELP_NUM_ITEMS]; + +extern int show_default_volumes; /* pattern-view.c */ + +/* --------------------------------------------------------------------- */ +/* settings (config.c) */ + +extern char cfg_video_driver[]; +extern int cfg_video_fullscreen; + +extern char cfg_dir_modules[], cfg_dir_samples[], cfg_dir_instruments[]; +extern char cfg_dir_dotschism[]; /* the full path to ~/.schism */ +extern char cfg_font[]; +extern int cfg_palette; + +void cfg_init_dir(void); +void cfg_load(void); +void cfg_save(void); +void cfg_midipage_save(void); +void cfg_atexit_save(void); /* this only saves a handful of settings, not everything */ + +/* each page with configurable settings has a function to load/save them... */ +#include "config-parser.h" /* FIXME: shouldn't need this here */ + +void cfg_load_midi(cfg_file_t *cfg); +void cfg_save_midi(cfg_file_t *cfg); + +void cfg_load_patedit(cfg_file_t *cfg); +void cfg_save_patedit(cfg_file_t *cfg); + +void cfg_load_info(cfg_file_t *cfg); +void cfg_save_info(cfg_file_t *cfg); + +void cfg_load_audio(cfg_file_t *cfg); +void cfg_save_audio(cfg_file_t *cfg); +void cfg_atexit_save_audio(cfg_file_t *cfg); + +/* --------------------------------------------------------------------- */ +/* text functions */ + +/* these are sort of for single-line text entries. */ +void text_add_char(char *text, char c, int *cursor_pos, int max_length); +void text_delete_char(char *text, int *cursor_pos, int max_length); +void text_delete_next_char(char *text, int *cursor_pos, int max_length); + +static inline char unicode_to_ascii(Uint16 unicode) +{ + return ((unicode & 0xff80) ? 0 : (unicode & 0x7f)); +} + +/* --------------------------------------------------------------------- */ +/* drawing functions */ + +/* character drawing (in a separate header so they're easier to find) */ +#include "draw-char.h" + +/* the sample number indicates what position it belongs to, zero being + * for the currently active sample in the sample library + * and 1-99 for the respective samples in the sample list. + * to draw_sample_data tells where to draw it (duh ;) */ +struct _song_sample; +void draw_sample_data(struct vgamem_overlay *r, struct _song_sample *sample, int number); + +/* this works like draw_sample_data, just without having to allocate a + * song_sample structure, and without caching the waveform. + * mostly it's just for the oscilloscope view. */ +void draw_sample_data_rect_16(struct vgamem_overlay *r, signed short *data, int length, unsigned int channels); +void draw_sample_data_rect_8(struct vgamem_overlay *r, signed char *data, int length, unsigned int channels); + +/* these are in audio_playback.cc */ +extern signed short *audio_buffer; +extern unsigned int audio_buffer_size; +extern unsigned int audio_output_channels; +extern unsigned int audio_output_bits; + +/* --------------------------------------------------------------------- */ +/* page functions */ + +/* anything can use these */ +void set_page(int new_page); +/* (there's no get_page -- just use status.current_page) */ + +/* these should only be called from main */ +void load_pages(void); /* called once at start of program */ +void playback_update(void); /* once per cycle */ +struct key_event; +void handle_key(struct key_event * k); /* whenever there's a keypress ;) */ +void key_translate(struct key_event *k); + +/* this should only be called from main. + * anywhere else, use status.flags |= NEED_UPDATE instead. */ +void redraw_screen(void); + +/* called whenever the song changes (from song_new or song_load) */ +void main_song_changed_cb(void); + +/* --------------------------------------------------------------------- */ +/* colors and fonts */ + +int font_load(const char *filename); +void palette_apply(void); +void palette_load_preset(int palette_index); + +/* mostly for the itf editor */ +int font_save(const char *filename); + +/* if bank = 0, set the normal font; if bank = 1, set the alternate font + * (which uses default glyphs for chars > 127) i'm not too sure about + * this function. i might change it. */ +void font_set_bank(int bank); + +void font_reset_lower(void); /* ascii chars (0-127) */ +void font_reset_upper(void); /* itf chars (128-255) */ +void font_reset(void); /* everything (0-255) */ +void font_reset_bios(void); /* resets all chars to the alt font */ +void font_reset_char(int c); /* resets just one char */ + +/* this needs to be called before any char drawing. + * it's pretty much the same as doing... + * if (!font_load("font.cfg")) + * font_reset(); + * ... the main difference being font_init() is easier to deal with :) */ +void font_init(void); + +/* --------------------------------------------------------------------- */ +/* keyboard.c */ + +int numeric_key_event(struct key_event *k); + +char *get_note_string(int note, char *buf); /* "C-5" or "G#4" */ +char *get_note_string_short(int note, char *buf); /* "c5" or "G4" */ +char *get_volume_string(int volume, int volume_effect, char *buf); +char get_effect_char(int command); +int get_effect_number(char effect); + +void kbd_init(void); +void kbd_digitrakker_voodoo(int e); +int kbd_get_effect_number(struct key_event *k); +int kbd_char_to_hex(struct key_event *k); + +int kbd_get_current_octave(void); +void kbd_set_current_octave(int new_octave); + +int kbd_get_note(struct key_event *k); + + +/* --------------------------------------------------------------------- */ +/* log.c */ + +void log_append(int color, int must_free, const char *text); +void log_appendf(int color, const char *format, ...) + __attribute__ ((format(printf, 2, 3))); + +/* --------------------------------------------------------------------- */ +/* stuff */ + +int sample_get_current(void); +void sample_set(int n); +int instrument_get_current(void); +void instrument_set(int n); +void instrument_synchronize_to_sample(void); + +/* instrument... sample... whatever */ + +int song_is_instrument_mode(void); +int song_get_current_instrument(void); +/* these should really be const char */ +char *song_get_instrument_name(int n, char **name); + +static inline void set_previous_instrument(void) +{ + if (song_is_instrument_mode()) + instrument_set(instrument_get_current() - 1); + else + sample_set(sample_get_current() - 1); +} + +static inline void set_next_instrument(void) +{ + if (song_is_instrument_mode()) + instrument_set(instrument_get_current() + 1); + else + sample_set(sample_get_current() + 1); +} + +void status_text_flash(const char *format, ...) + __attribute__ ((format(printf, 1, 2))); + +int get_current_row(void); +void set_current_row(int row); +int get_current_pattern(void); +void set_current_pattern(int pattern); +/* This is the F7 key handler: it starts at the marked position if there + * is one, or the current position if not. */ +void play_song_from_mark(void); + +int get_current_order(void); +void set_current_order(int order); +void prev_order_pattern(void); +void next_order_pattern(void); +void orderpan_recheck_muted_channels(void); + +/* this dies painfully if something goes wrong */ +void setup_help_text_pointers(void); + +void show_exit_prompt(void); +void show_song_length(void); +void show_song_timejump(void); + +/* memory usage */ +unsigned int memused_lowmem(void); +unsigned int memused_ems(void); +unsigned int memused_songmessage(void); +unsigned int memused_instruments(void); +unsigned int memused_samples(void); +unsigned int memused_clipboard(void); +unsigned int memused_patterns(void); +unsigned int memused_history(void); +/* clears the memory lookup cache */ +void memused_songchanged(void); + + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +} +#endif + +#endif /* ! IT_H */ diff --git a/include/macosx-sdlmain.h b/include/macosx-sdlmain.h new file mode 100644 index 000000000..0422d22ea --- /dev/null +++ b/include/macosx-sdlmain.h @@ -0,0 +1,12 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#import + +@interface SDLMain : NSObject +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; +@end diff --git a/include/midi.h b/include/midi.h new file mode 100644 index 000000000..098d95b4e --- /dev/null +++ b/include/midi.h @@ -0,0 +1,154 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MIDI_H +#define MIDI_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct midi_provider; +struct midi_port; + +struct midi_driver { + unsigned int flags; +#define MIDI_PORT_CAN_SCHEDULE 1 + + void (*poll)(struct midi_provider *m); + int (*thread)(struct midi_provider *m); + + int (*enable)(struct midi_port *d); + int (*disable)(struct midi_port *d); + + void (*send)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); +}; + +struct midi_provider { + const char *name; + void (*poll)(struct midi_provider *); + void *thread; /*actually SDL_Thread* */ + + struct midi_provider *next; + + /* forwarded; don't touch */ + int (*enable)(struct midi_port *d); + int (*disable)(struct midi_port *d); + + void (*send_now)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); + void (*send_later)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); + +}; +struct midi_port { + int io, iocap; +#define MIDI_INPUT 1 +#define MIDI_OUTPUT 2 + char *name; + int num; + + void *userdata; + int free_userdata; + int (*enable)(struct midi_port *d); + int (*disable)(struct midi_port *d); + void (*send_now)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); + void (*send_later)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); + + struct midi_provider *provider; +}; + + +/* schism calls these directly */ +int midi_engine_start(void); +void midi_engine_reset(void); +void midi_engine_stop(void); +void midi_engine_poll_ports(void); + +/* some parts of schism call this; it means "immediately" */ +void midi_send_now(unsigned char *seq, unsigned int len); + +/* ... but the player calls this */ +void midi_send_buffer(unsigned char *data, unsigned int len, unsigned int pos); +void midi_send_flush(void); + +/* from the SDL event mechanism (x is really SDL_Event) */ +int midi_engine_handle_event(void *x); + +struct midi_port *midi_engine_port(int n, const char **name); +int midi_engine_port_count(void); + +/* midi engines register a provider (one each!) */ +struct midi_provider *midi_provider_register(const char *name, struct midi_driver *f); + + +/* midi engines list ports this way */ +int midi_port_register(struct midi_provider *p, +int inout, const char *name, void *userdata, int free_userdata); + +int midi_port_foreach(struct midi_provider *p, struct midi_port **cursor); +void midi_port_unregister(int num); + +/* only call these if the event isn't really MIDI but you want most of the system + to act like it is... + + midi drivers should never all these... +*/ +enum midi_note { + MIDI_NOTEOFF, + MIDI_NOTEON, + MIDI_KEYPRESS, +}; +void midi_event_note(enum midi_note status, int channel, int note, int velocity); +void midi_event_controller(int channel, int param, int value); +void midi_event_program(int channel, int value); +void midi_event_aftertouch(int channel, int value); +void midi_event_pitchbend(int channel, int value); +void midi_event_tick(void); +void midi_event_sysex(const unsigned char *data, unsigned int len); + +/* midi drivers call this when they received an event */ +void midi_received_cb(struct midi_port *src, unsigned char *data, unsigned int len); + +#define MIDI_TICK_QUANTIZE 0x00000001 +#define MIDI_BASE_PROGRAM1 0x00000002 +#define MIDI_RECORD_NOTEOFF 0x00000004 +#define MIDI_RECORD_VELOCITY 0x00000008 +#define MIDI_RECORD_AFTERTOUCH 0x00000010 +#define MIDI_CUT_NOTE_OFF 0x00000020 +#define MIDI_PITCH_BEND 0x00000040 +#define MIDI_EMBED_DATA 0x00000080 +#define MIDI_RECORD_SDX 0x00000100 +#define MIDI_DISABLE_RECORD 0x00010000 + +extern int midi_flags, midi_pitch_depth, midi_amplification, midi_c5note; + +/* only available with networks */ +int ip_midi_setports(int n); +int ip_midi_getports(void); + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/include/mixer.h b/include/mixer.h new file mode 100644 index 000000000..89f49aef9 --- /dev/null +++ b/include/mixer.h @@ -0,0 +1,28 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MIXER_H +#define MIXER_H + +int mixer_get_max_volume(void); +void mixer_read_volume(int *left, int *right); +void mixer_write_volume(int left, int right); + +#endif /* ! MIXER_H */ diff --git a/include/mplink.h b/include/mplink.h new file mode 100644 index 000000000..780238cbd --- /dev/null +++ b/include/mplink.h @@ -0,0 +1,42 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MPLINK_H +#define MPLINK_H + +#include "it.h" +#include "song.h" + +#ifdef __cplusplus +#include "stdafx.h" +#include "sndfile.h" + +extern CSoundFile *mp; +#endif + +extern char song_filename[]; /* the full path (as given to song_load) */ +extern char song_basename[]; /* everything after the last slash */ + +/* milliseconds = (samples * 1000) / frequency */ +extern unsigned long samples_played; + +extern unsigned long max_channels_used; + +#endif /* ! MPLINK_H */ diff --git a/include/page.h b/include/page.h new file mode 100644 index 000000000..be5169e2d --- /dev/null +++ b/include/page.h @@ -0,0 +1,507 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This header has all the page definitions, the kinds of interactive + * widgets on each page, etc. Since this information isn't useful outside + * page*.c, it's not in the main header. */ + +#ifndef PAGE_H +#define PAGE_H + +/* there's no good place for this */ +#define MOUSE_BUTTON_LEFT 1 +#define MOUSE_BUTTON_MIDDLE 2 +#define MOUSE_BUTTON_RIGHT 3 +#define MOUSE_CLICK 1 +#define MOUSE_SCROLL_UP 2 +#define MOUSE_SCROLL_DOWN 3 +#define MOUSE_DBLCLICK 4 +struct key_event { + SDLKey sym, orig_sym; + SDLMod mod; + Uint16 unicode; + + int state; /* 0 for down, 1 for up/release */ + int mouse; /* 0 for none, 1 for click, 2 for scrollup, 3 for down */ + int mouse_button; /* 1 left, 2 middle, 3 right (itf only) */ + int midi_note; + int midi_channel; + int midi_volume; /* -1 for not a midi key otherwise 0...128 */ + int midi_bend; /* normally 0; -8192 to +8192 */ + unsigned int sx, sy; /* start x and y position (character) */ + unsigned int x, hx, fx; /* x position of mouse (character, halfcharacter, fine) */ + unsigned int y, fy; /* y position of mouse (character, fine) */ + + unsigned int rx, ry; /* x/y resolution */ + + int is_repeat; + int on_target; +}; + +/* --------------------------------------------------------------------- */ +/* there's a value in this enum for each kind of widget... */ + +enum widget_type { + WIDGET_TOGGLE, WIDGET_MENUTOGGLE, + WIDGET_BUTTON, WIDGET_TOGGLEBUTTON, + WIDGET_TEXTENTRY, + WIDGET_NUMENTRY, WIDGET_THUMBBAR, WIDGET_PANBAR, + /* this last one is for anything that doesn't fit some standard + type, like the sample list, envelope editor, etc.; a widget of + this type is just a placeholder so page.c knows there's + something going on. */ + WIDGET_OTHER /* sample list, envelopes, etc. */ +}; + +/* --------------------------------------------------------------------- */ +/* every widget in the enum has a corresponding struct here. the notes + * before each widget indicate what keypresses are trapped in page.c for + * it, and what happens. + * note that all widget types (except WIDGET_OTHER) trap the enter key for the + * activate callback. */ + +/* space -> state changed; cb triggered */ +struct widget_toggle { + int state; /* 0 = off, 1 = on */ +}; + +/* space -> state changed; cb triggered */ +struct widget_menutoggle { + int state; /* 0, 1, ..., num_choices - 1, num_choices */ + const char **choices; + int num_choices; +}; + +/* enter -> cb triggered */ +struct widget_button { + const char *text; + int padding; +}; + +/* enter -> state changed; cb triggered */ +struct widget_togglebutton { + const char *text; + int padding; + int state; /* 0 = off, 1 = on */ + int *group; +}; + +/* backspace -> truncated; changed cb triggered + * ctrl-bs -> cleared; changed cb triggered + * -> appended; changed cb triggered + * (the callback isn't triggered unless something really changed) + * left/right -> cursor_pos changed; no cb triggered + * + * - if (max_length > (width - 1)) the text scrolls + * - cursor_pos is set to the end of the text when the widget is focused */ +struct widget_textentry { + char *text; + int max_length; + int firstchar; /* first visible character (generally 0) */ + int cursor_pos; /* 0 = first character */ +}; + +/* <0-9> -> digit @ cursor_pos changed; cursor_pos increased; cb triggered + * left/right -> cursor_pos changed; cb NOT triggered. + * +/- -> value increased/decreased; cb triggered + * cursor_pos for this widget is a pointer so that multiple numbers that + * are all lined up can share the same position. */ +struct widget_numentry { + int min; + int max; + int value; + int *cursor_pos; + int (*handle_unknown_key)(struct key_event *k); + int reverse; +}; + +/* left/right -> value changed; cb triggered + * ctrl-left/right -> value changed 4x; cb triggered + * shift-left/right -> value changed 2x; cb triggered + * home/end -> value set to min/max; cb triggered + * <0-9> -> prompt for new number; value changed; cb triggered */ +struct widget_thumbbar { + /* pretty much the same as the numentry, just without the cursor + * position field... (NOTE - don't rearrange the order of the + * fields in either of these; some code depends on them being + * the same) */ + int min; + int max; + int value; + /* this is currently only used with the midi thumbbars on the ins. list + pitch page. if + * this is non-NULL, and value == {min,max}, the text is drawn instead of the thumbbar. */ + const char *text_at_min, *text_at_max; +}; + +/* special case of the thumbbar; range goes from 0 to 64. if mute is + * set, the bar is replaced with the word "muted"; if surround is set, + * it's replaced with the word "surround". some keys: L/M/R set the + * value to 0/32/64 respectively, S switches the surround flag, and + * space toggles the mute flag (and selects the next.down control!) + * min/max are just for thumbbar compatibility, and are always set to + * 0 and 64 respectively. + * note that, due to some weirdness with IT, these draw the channel text + * as well as the actual bar. */ +struct widget_panbar { + int min; + int max; + int value; + int channel; + int muted:1; + int surround:1; +}; + +struct widget_other { + /* bah. can't do much of anything with this. + * + * if an 'other' type widget gets the focus, it soaks up all the + * keyboard events that the main handler doesn't catch. thus + * it is responsible for changing the focus to something else + * (and, of course, if it doesn't ever do that, the cursor is + * pretty much stuck) + * this MUST be set to a valid function. + * return value is 1 if the key was handled, 0 if not. */ + int (*handle_key) (struct key_event * k); + + /* also the widget drawing function can't possibly know how to + * draw a custom widget, so it calls this instead. + * this MUST be set to a valid function. */ + void (*redraw) (void); +}; + +/* --------------------------------------------------------------------- */ +/* and all the widget structs go in the union in this struct... */ + +union _widget_data_union { + struct widget_toggle toggle; + struct widget_menutoggle menutoggle; + struct widget_button button; + struct widget_togglebutton togglebutton; + struct widget_textentry textentry; + struct widget_numentry numentry; + struct widget_thumbbar thumbbar; + struct widget_panbar panbar; + struct widget_other other; +}; +struct widget { + enum widget_type type; + + union _widget_data_union d; + + /* for redrawing */ + int x, y, width, height, depressed; + int clip_start, clip_end; + + /* these next 5 fields specify what widget gets selected next */ + struct { + int up, down, left, right, tab; + } next; + + /* called whenever the value is changed... duh ;) */ + void (*changed) (void); + + /* called when the enter key is pressed */ + void (*activate) (void); +}; + +/* this structure keeps all the information needed to draw a page, and a + * list of all the different widgets on the page. it's the job of the page + * to change the necessary information when something changes; that's + * done in the page's draw and update functions. + * + * everything in this struct MUST be set for each page. + * functions that aren't implemented should be set to NULL. */ +struct page { + /* the title of the page, eg "Sample List (F3)" */ + const char *title; + + /* font editor takes over full screen */ + void (*draw_full)(void); + /* draw the labels, etc. that don't change */ + void (*draw_const) (void); + /* called after the song is changed. this is to copy the new + * values from the song to the widgets on the page. */ + void (*song_changed_cb) (void); + /* called before widgets are drawn, mostly to fix the values + * (for example, on the sample page this sets everything to + * whatever values the current sample has) - this is a lousy + * hack. sorry. :P */ + void (*predraw_hook) (void); + /* draw the parts of the page that change when the song is playing + * (this is called *very* frequently) */ + void (*playback_update) (void); + /* this gets first shot at keys (to do unnatural overrides) */ + int (*pre_handle_key) (struct key_event * k); + /* this catches any keys that the main handler doesn't deal with */ + void (*handle_key) (struct key_event * k); + /* called when the page is set. this is for reloading the + * directory in the file browsers. */ + void (*set_page) (void); + + struct widget *widgets; + int selected_widget; + int total_widgets; + + /* 0 if no page-specific help */ + int help_index; +}; + +/* --------------------------------------------------------------------- */ + +extern struct page pages[]; + +/* these are updated to point to the relevant data in the selected page + * (or the dialog, if one is active) */ +extern struct widget *widgets; +extern int *selected_widget; +extern int *total_widgets; + +/* to make it easier to deal with either the page's widgets or the + * current dialog's: + * + * ACTIVE_WIDGET deals with whatever widget is *really* active. + * ACTIVE_PAGE_WIDGET references the *page's* idea of what's active. + * (these are different if there's a dialog) */ +#define ACTIVE_PAGE (pages[status.current_page]) +#define ACTIVE_WIDGET (widgets[*selected_widget]) +#define ACTIVE_PAGE_WIDGET (ACTIVE_PAGE.widgets[ACTIVE_PAGE.selected_widget]) + +extern int instrument_list_subpage; +#define PAGE_INSTRUMENT_LIST instrument_list_subpage + +/* --------------------------------------------------------------------- */ + +enum page_numbers { + PAGE_BLANK = (0), + PAGE_HELP = (1), + PAGE_PATTERN_EDITOR = (2), + PAGE_SAMPLE_LIST = (3), + /* PAGE_INSTRUMENT_LIST = (4), * doesn't exist */ + PAGE_INFO = (5), + + PAGE_MIDI = (6), + PAGE_PREFERENCES = (7), + + PAGE_LOAD_MODULE = (9), + PAGE_SAVE_MODULE = (10), + PAGE_ORDERLIST_PANNING = (11), + PAGE_SONG_VARIABLES = (12), + PAGE_PALETTE_EDITOR = (13), + PAGE_ORDERLIST_VOLUMES = (14), + PAGE_MESSAGE = (15), + PAGE_LOG = (16), + + /* don't use these directly with set_page */ + PAGE_INSTRUMENT_LIST_GENERAL = (17), + PAGE_INSTRUMENT_LIST_VOLUME = (18), + PAGE_INSTRUMENT_LIST_PANNING = (19), + PAGE_INSTRUMENT_LIST_PITCH = (20), + + PAGE_LOAD_SAMPLE = (21), + PAGE_SAMPLE_BROWSER = (22), + PAGE_LOAD_INSTRUMENT = (23), + PAGE_INSTRUMENT_BROWSER = (24), + + PAGE_MIDI_OUTPUT = (25), + + PAGE_FONT_EDIT = (26), + + PAGE_LIBRARY_SAMPLE = (27), + PAGE_LIBRARY_INSTRUMENT = (28), + + PAGE_ABOUT = (29), + + PAGE_CONFIG = (30), + +/* limit =32 */ +}; + +/* --------------------------------------------------------------------- */ +void show_about(void); + +void blank_load_page(struct page *page); +void help_load_page(struct page *page); +void pattern_editor_load_page(struct page *page); +void sample_list_load_page(struct page *page); +void instrument_list_general_load_page(struct page *page); +void instrument_list_volume_load_page(struct page *page); +void instrument_list_panning_load_page(struct page *page); +void instrument_list_pitch_load_page(struct page *page); +void info_load_page(struct page *page); +void midi_load_page(struct page *page); +void midiout_load_page(struct page *page); +void fontedit_load_page(struct page *page); +void preferences_load_page(struct page *page); +void load_module_load_page(struct page *page); +void save_module_load_page(struct page *page); +void orderpan_load_page(struct page *page); +void ordervol_load_page(struct page *page); +void song_vars_load_page(struct page *page); +void message_load_page(struct page *page); +void palette_load_page(struct page *page); +void log_load_page(struct page *page); +void load_sample_load_page(struct page *page); +void load_instrument_load_page(struct page *page); +void about_load_page(struct page *page); + +/* --------------------------------------------------------------------- */ + +void create_toggle(struct widget *w, int x, int y, int next_up, + int next_down, int next_left, int next_right, + int next_tab, void (*changed) (void)); +void create_menutoggle(struct widget *w, int x, int y, int next_up, + int next_down, int next_left, int next_right, + int next_tab, void (*changed) (void), + const char **choices); +void create_button(struct widget *w, int x, int y, int width, int next_up, + int next_down, int next_left, int next_right, + int next_tab, void (*changed) (void), const char *text, + int padding); +void create_togglebutton(struct widget *w, int x, int y, int width, + int next_up, int next_down, int next_left, + int next_right, int next_tab, + void (*changed) (void), const char *text, + int padding, int *group); +void create_textentry(struct widget *w, int x, int y, int width, int next_up, + int next_down, int next_tab, void (*changed) (void), + char *text, int max_length); +void create_numentry(struct widget *w, int x, int y, int width, int next_up, + int next_down, int next_tab, void (*changed) (void), + int min, int max, int *cursor_pos); +void create_thumbbar(struct widget *w, int x, int y, int width, int next_up, + int next_down, int next_tab, void (*changed) (void), + int min, int max); +void create_panbar(struct widget *w, int x, int y, int next_up, + int next_down, int next_tab, void (*changed) (void), + int channel); +void create_other(struct widget *w, int next_tab, int (*w_handle_key) (struct key_event * k), void (*w_redraw) (void)); + +/* --------------------------------------------------------------------- */ + +/* widget.c */ +int textentry_add_char(struct widget *widget, Uint16 unicode); +void numentry_change_value(struct widget *widget, int new_value); +int numentry_handle_digit(struct widget *widget, struct key_event *k); +struct widget *find_widget_xy(int x, int y); +struct widget *find_widget_xy_ex(int x, int y, int *num); +int change_focus_to_xy(int x, int y); +void change_focus_to(int new_widget_index); +/* p_widgets should point to the group of widgets (not the actual widget that is + * being set!) and widget should be the index of the widget within the group. */ +void togglebutton_set(struct widget *p_widgets, int widget, int do_callback); +void draw_widget(struct widget *w, int selected); + +/* widget-keyhandler.c + * [note: this always uses the current widget] */ +int widget_handle_key(struct key_event * k); + +/* draw-misc.c */ +void draw_thumb_bar(int x, int y, int width, int min, int max, int val, + int selected); +/* vu meter values should range from 0 to 64. the color is generally 5 + * unless the channel is disabled (in which case it's 1). impulse tracker + * doesn't do peak color; st3 style, use color 4 (unless it's disabled, + * in which case it should probably be 2, or maybe 3). + * the width should be a multiple of three. */ +void draw_vu_meter(int x, int y, int val, int width, int color, int peak_color); + +/* page.c */ +int page_is_instrument_list(int page); +void update_current_instrument(void); + +void new_song_dialog(void); +void save_song_or_save_as(void); + +/* page_patedit.c */ +void update_current_row(void); +void update_current_pattern(void); +void pattern_editor_display_options(void); +void pattern_editor_length_edit(void); + +/* page_orderpan.c */ +void update_current_order(void); + +/* menu.c */ +void menu_show(void); +void menu_hide(void); +void menu_draw(void); +int menu_handle_key(struct key_event * k); + +/* status.c */ +void status_text_redraw(void); + +/* --------------------------------------------------------------------- */ +/* dialog crap */ + +struct dialog { + int type; + int x, y, w, h; + + /* next two are for "simple" dialogs (type != DIALOG_CUSTOM) */ + char *text; /* malloc'ed */ + int text_x; + + struct widget *widgets; /* malloc'ed */ + int selected_widget; + int total_widgets; + + void *data; /* extra data pointer */ + + /* maybe these should get the data pointer as well? */ + void (*draw_const) (void); + int (*handle_key) (struct key_event * k); + + /* there's no action_ok, as yes and ok are fundamentally the same */ + void (*action_yes) (void *data); + void (*action_no) (void *data); /* only useful for y/n dialogs? */ + /* currently, this is only settable for custom dialogs. + * it's only used in a couple of places (mostly on the pattern editor) */ + void (*action_cancel) (void *data); +}; + +/* dialog handlers + * these are set by default for normal dialogs, and can be used with the custom dialogs. + * they call the {yes, no, cancel} callback, destroy the dialog, and schedule a screen + * update. (note: connect these to the BUTTONS, not the action_* callbacks!) */ +void dialog_yes(void *data); +void dialog_no(void *data); +void dialog_cancel(void *data); +/* these are the same as dialog_yes(NULL) etc., and are used in button callbacks */ +void dialog_yes_NULL(void); +void dialog_no_NULL(void); +void dialog_cancel_NULL(void); + +int dialog_handle_key(struct key_event * k); +void dialog_draw(void); + +struct dialog *dialog_create(int type, const char *text, void (*action_yes) (void *data), + void (*action_no) (void *data), int default_widget, void *data); + +void dialog_destroy(void); +void dialog_destroy_all(void); + +/* this builds and displays a dialog with an unspecified widget structure. + * the caller can set other properties of the dialog (i.e. the yes/no/cancel callbacks) after + * the dialog has been displayed. */ +struct dialog *dialog_create_custom(int x, int y, int w, int h, struct widget *dialog_widgets, + int dialog_total_widgets, int dialog_selected_widget, + void (*draw_const) (void), void *data); + +#endif /* ! PAGE_H */ diff --git a/include/palettes.h b/include/palettes.h new file mode 100644 index 000000000..4817800ae --- /dev/null +++ b/include/palettes.h @@ -0,0 +1,272 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PALETTES_H +#define PALETTES_H + +#include "util.h" + +/* *INDENT-OFF* */ +struct it_palette palettes[] = { + {"Light Blue", { + /* 0 */ { 0, 0, 0}, + /* 1 */ {10, 25, 45}, + /* 2 */ {30, 40, 55}, + /* 3 */ {51, 58, 63}, + /* 4 */ {63, 21, 21}, + /* 5 */ {21, 63, 21}, + /* 6 */ {44, 44, 44}, + /* 7 */ {22, 22, 22}, + /* 8 */ { 0, 0, 32}, + /* 9 */ { 0, 0, 42}, + /* 10 */ {30, 40, 55}, + /* 11 */ {51, 58, 63}, + /* 12 */ {44, 44, 44}, + /* 13 */ {21, 63, 21}, + /* 14 */ {18, 16, 15}, + /* 15 */ {12, 11, 10}, + }}, + {"Gold", { /* hey, this is the ST3 palette! (sort of) */ + /* 0 */ { 0, 0, 0}, + /* 1 */ {20, 17, 10}, + /* 2 */ {41, 36, 21}, + /* 3 */ {63, 55, 33}, + /* 4 */ {63, 21, 21}, + /* 5 */ {18, 53, 18}, + /* 6 */ {38, 37, 36}, + /* 7 */ {22, 22, 22}, + /* 8 */ { 0, 0, 32}, + /* 9 */ { 0, 0, 42}, + /* 10 */ {41, 36, 21}, + /* 11 */ {48, 49, 46}, + /* 12 */ {44, 44, 44}, + /* 13 */ {21, 50, 21}, + /* 14 */ {18, 16, 15}, + /* 15 */ {12, 11, 10}, + }}, + {"Camouflage", { + /* 0 */ { 0, 0, 0}, + /* 1 */ {31, 22, 17}, + /* 2 */ {45, 37, 30}, + /* 3 */ {58, 58, 50}, + /* 4 */ {44, 0, 21}, + /* 5 */ {63, 63, 21}, + /* 6 */ {17, 38, 18}, + /* 7 */ {19, 3, 6}, + /* 8 */ { 8, 21, 0}, + /* 9 */ { 6, 29, 11}, + /* 10 */ {14, 39, 29}, + /* 11 */ {55, 58, 56}, + /* 12 */ {40, 40, 40}, + /* 13 */ {35, 5, 21}, + /* 14 */ {22, 16, 15}, + /* 15 */ {13, 12, 11}, + }}, + {"Midnight Tracking", { + /* 0 */ { 0, 0, 0}, + /* 1 */ { 0, 8, 16}, + /* 2 */ { 0, 19, 32}, + /* 3 */ {16, 28, 48}, + /* 4 */ {63, 21, 21}, + /* 5 */ { 0, 48, 36}, + /* 6 */ {32, 32, 32}, + /* 7 */ {22, 22, 22}, + /* 8 */ { 0, 0, 24}, + /* 9 */ { 0, 0, 32}, + /* 10 */ { 0, 18, 32}, + /* 11 */ {40, 40, 40}, + /* 12 */ {32, 32, 32}, + /* 13 */ {28, 0, 24}, + /* 14 */ { 4, 13, 20}, + /* 15 */ { 6, 7, 11}, + }}, + {"Pine Colours", { + /* 0 */ { 0, 0, 0}, + /* 1 */ { 2, 16, 13}, + /* 2 */ {21, 32, 29}, + /* 3 */ {51, 58, 63}, + /* 4 */ {63, 34, 0}, + /* 5 */ {52, 51, 33}, + /* 6 */ {42, 41, 33}, + /* 7 */ {31, 22, 22}, + /* 8 */ {12, 10, 16}, + /* 9 */ {18, 0, 24}, + /* 10 */ {30, 40, 55}, + /* 11 */ {58, 58, 33}, + /* 12 */ {44, 44, 44}, + /* 13 */ {49, 39, 21}, + /* 14 */ {13, 15, 14}, + /* 15 */ {14, 11, 14}, + }}, + {"Soundtracker", { + /* 0 */ { 0, 0, 0}, + /* 1 */ {18, 24, 28}, + /* 2 */ {35, 42, 47}, + /* 3 */ {51, 56, 60}, + /* 4 */ {63, 21, 21}, + /* 5 */ {21, 63, 22}, + /* 6 */ { 0, 35, 63}, + /* 7 */ {22, 22, 22}, + /* 8 */ {32, 13, 38}, + /* 9 */ {37, 16, 62}, + /* 10 */ {27, 40, 55}, + /* 11 */ {51, 58, 63}, + /* 12 */ {44, 44, 44}, + /* 13 */ {21, 63, 21}, + /* 14 */ {18, 16, 17}, + /* 15 */ {13, 14, 13}, + }}, + {"Volcanic", { + /* 0 */ { 0, 0, 0}, + /* 1 */ {25, 9, 0}, + /* 2 */ {40, 14, 0}, + /* 3 */ {51, 23, 0}, + /* 4 */ {63, 8, 16}, + /* 5 */ { 0, 39, 5}, + /* 6 */ {32, 32, 32}, + /* 7 */ { 0, 20, 20}, + /* 8 */ {21, 0, 0}, + /* 9 */ {28, 0, 0}, + /* 10 */ {32, 32, 32}, + /* 11 */ {62, 31, 0}, + /* 12 */ {40, 40, 40}, + /* 13 */ { 0, 28, 38}, + /* 14 */ {10, 16, 27}, + /* 15 */ { 8, 11, 19}, + }}, + {"Industrial", { /* mine */ + /* 0 */ { 0, 0, 0}, + /* 1 */ {18, 18, 18}, + /* 2 */ {28, 28, 28}, + /* 3 */ {51, 51, 51}, + /* 4 */ {51, 20, 0}, + /* 5 */ {55, 43, 0}, + /* 6 */ {12, 23, 35}, + /* 7 */ {11, 0, 22}, + /* 8 */ {14, 0, 14}, + /* 9 */ {13, 0, 9}, + /* 10 */ { 0, 24, 24}, + /* 11 */ {23, 4, 43}, + /* 12 */ {16, 32, 24}, + /* 13 */ {36, 0, 0}, + /* 14 */ {14, 7, 2}, + /* 15 */ { 2, 10, 14}, + }}, + {"Violent", { /* FT2 */ + /* 0 */ { 0, 0, 0}, + /* 1 */ {17, 10, 20}, + /* 2 */ {34, 21, 41}, + /* 3 */ {59, 58, 63}, + /* 4 */ {63, 7, 24}, + /* 5 */ {40, 40, 40}, + /* 6 */ {50, 45, 63}, + /* 7 */ {17, 10, 20}, + /* 8 */ { 8, 0, 10}, + /* 9 */ {22, 0, 24}, + /* 10 */ {43, 23, 31}, + /* 11 */ {55, 58, 56}, + /* 12 */ {37, 28, 53}, + /* 13 */ {50, 18, 39}, + /* 14 */ {20, 12, 24}, + /* 15 */ {11, 7, 13}, + }}, + {"Why Colors?", { /* FT2 */ + /* 0 */ { 0, 0, 0}, + /* 1 */ { 9, 14, 16}, + /* 2 */ {18, 29, 32}, + /* 3 */ {63, 63, 63}, + /* 4 */ {63, 0, 0}, + /* 5 */ {63, 63, 32}, + /* 6 */ {63, 63, 32}, + /* 7 */ {16, 16, 8}, + /* 8 */ {10, 10, 10}, + /* 9 */ {20, 20, 20}, + /* 10 */ {32, 32, 32}, + /* 11 */ {24, 38, 45}, + /* 12 */ {48, 48, 48}, + /* 13 */ {63, 63, 32}, + /* 14 */ {20, 20, 20}, + /* 15 */ {10, 10, 10}, + }}, + {"Kawaii", { /* mine (+mml) */ + /* 0 */ {61, 60, 63}, + /* 1 */ {63, 53, 60}, + /* 2 */ {51, 38, 47}, + /* 3 */ {18, 10, 17}, + /* 4 */ {63, 28, 50}, + /* 5 */ {21, 34, 50}, + /* 6 */ {40, 32, 45}, + /* 7 */ {63, 52, 59}, + /* 8 */ {48, 55, 63}, + /* 9 */ {51, 48, 63}, + /* 10 */ {45, 29, 44}, + /* 11 */ {57, 48, 59}, + /* 12 */ {34, 18, 32}, + /* 13 */ {50, 42, 63}, + /* 14 */ {50, 53, 60}, + /* 15 */ {63, 58, 56}, + }}, + {"Gold (Vintage)", { /* more directly based on the ST3 palette */ + /* 0 */ { 0, 0, 0}, + /* 1 */ {20, 17, 10}, + /* 2 */ {41, 36, 21}, + /* 3 */ {63, 55, 33}, + /* 4 */ {57, 0, 0}, // 63, 21, 21 + /* 5 */ { 0, 44, 0}, // 21, 50, 21 + /* 6 */ {38, 37, 36}, + /* 7 */ {22, 22, 22}, // not from ST3 + /* 8 */ { 5, 9, 22}, // 0, 0, 32 + /* 9 */ { 6, 12, 29}, // 0, 0, 42 + /* 10 */ {41, 36, 21}, + /* 11 */ {48, 49, 46}, + /* 12 */ {44, 44, 44}, // not from ST3 + /* 13 */ { 0, 44, 0}, // 21, 50, 21 + /* 14 */ {18, 16, 15}, + /* 15 */ {12, 11, 10}, + // no place for the dark red (34, 0, 0) + // (used for box corner decorations in ST3) + // also no place for the yellow (63, 63, 0) + // (note dots?) + }}, + {"FX 2.0", { /* Virt-supplied :) */ + /* 0 */ { 0, 4, 0}, + /* 1 */ { 6, 14, 8}, + /* 2 */ {30, 32, 32}, + /* 3 */ {55, 57, 50}, + /* 4 */ { 0, 13, 42}, + /* 5 */ {19, 43, 19}, + /* 6 */ { 7, 48, 22}, + /* 7 */ { 0, 13, 0 }, + /* 8 */ {24, 12, 23}, + /* 9 */ {19, 8, 23}, + /* 10 */ {14, 39, 29}, + /* 11 */ {28, 42, 43}, + /* 12 */ {12, 51, 35}, + /* 13 */ {12, 55, 31}, + /* 14 */ { 5, 15, 4}, + /* 15 */ { 3, 13, 7}, + }}, + {"", {{0}}} +}; +/* *INDENT-ON* */ + +#define NUM_PALETTES (ARRAY_SIZE(palettes) - 1) + +#endif /* ! PALETTES_H */ diff --git a/include/pattern-view.h b/include/pattern-view.h new file mode 100644 index 000000000..f42631514 --- /dev/null +++ b/include/pattern-view.h @@ -0,0 +1,45 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PATTERN_VIEW_H +#define PATTERN_VIEW_H + +/* NOTE: these functions need to be called with the screen LOCKED */ + +typedef void (*draw_channel_header_func) (int chan, int x, int y, int fg); +typedef void (*draw_note_func) (int x, int y, song_note * note, + int cursor_pos, int fg, int bg); + +#define PATTERN_VIEW(n) \ + void draw_channel_header_##n(int chan, int x, int y, int fg); \ + void draw_note_##n(int x, int y, song_note * note, \ + int cursor_pos, int fg, int bg); + +PATTERN_VIEW(13); +PATTERN_VIEW(10); +PATTERN_VIEW(7); +PATTERN_VIEW(6); +PATTERN_VIEW(3); +PATTERN_VIEW(2); +PATTERN_VIEW(1); + +#undef PATTERN_VIEW + +#endif /* ! PATTERN_VIEW_H */ diff --git a/include/sample-edit.h b/include/sample-edit.h new file mode 100644 index 000000000..daabc87ff --- /dev/null +++ b/include/sample-edit.h @@ -0,0 +1,54 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SAMPLE_EDIT_H +#define SAMPLE_EDIT_H + +void sample_sign_convert(song_sample * sample); +void sample_reverse(song_sample * sample); +void sample_centralise(song_sample * sample); +void sample_amplify(song_sample *sample, int percent); +/* Return the maximum amplification that can be done without clipping (as a + * percentage, suitable to pass to sample_amplify). */ +int sample_get_amplify_amount(song_sample *sample); + +/* if convert_data is nonzero, the sample data is modified (so it sounds + * the same); otherwise, the sample length is changed and the data is + * left untouched (so 16 bit samples converted to 8 bit end up sounding + * like junk, and 8 bit samples converted to 16 bit end up with 2x the + * pitch) */ +void sample_toggle_quality(song_sample * sample, int convert_data); + +/* resize a sample; if aa is set, attempt to antialias (resample) the + * output waveform. + */ +void sample_resize(song_sample * sample, unsigned long newlen, int aa); + +/* AFAIK, this was in some registered versions of IT */ +void sample_invert(song_sample * sample); + +/* Impulse Tracker doesn't do these. */ +void sample_delta_decode(song_sample * sample); + +void sample_mono_left(song_sample * sample); +void sample_mono_right(song_sample * sample); + + +#endif /* ! SAMPLE_EDIT_H */ diff --git a/include/slurp.h b/include/slurp.h new file mode 100644 index 000000000..1cb984d2c --- /dev/null +++ b/include/slurp.h @@ -0,0 +1,64 @@ +/* + * slurp - General-purpose file reader + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SLURP_H +#define SLURP_H + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "util.h" + +#include +#include + +#include + +/* --------------------------------------------------------------------- */ + +typedef struct _slurp_struct slurp_t; +struct _slurp_struct { + size_t length; + const byte *data; + int extra; + void *bextra; + void (*closure)(slurp_t *); +}; + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* slurp returns NULL and sets errno on error. 'buf' is only meaningful if you've already stat()'d +the file; in most cases it can simply be NULL. If size is nonzero, it overrides the file's size as +returned by stat -- this can be used to read only part of a file, or if the file size is known but +a stat structure is not available. */ +slurp_t *slurp(const char *filename, struct stat *buf, size_t size); + +void unslurp(slurp_t * t); + +#ifdef __cplusplus +} +#endif + +#endif /* ! SLURP_H */ diff --git a/include/song.h b/include/song.h new file mode 100644 index 000000000..c42ee3edb --- /dev/null +++ b/include/song.h @@ -0,0 +1,592 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SONG_H +#define SONG_H + +#include "util.h" +#include "diskwriter.h" + +/* --------------------------------------------------------------------- */ +/* oodles o' structs */ + +/* midi config */ +typedef struct _midiconfig { + char midi_global_data[9*32]; + char midi_sfx[16*32]; + char midi_zxx[128*32]; +} midi_config; + +/* aka modinstrument */ +typedef struct _song_sample { + unsigned long length, loop_start, loop_end; + unsigned long sustain_start, sustain_end; + signed char *data; + unsigned long speed; + short panning; + short volume; + short global_volume; + short flags; + signed char relative_tone; /* mod-ish tuning */ + signed char finetune; /* mod-ish tuning */ + byte vib_type; + byte vib_rate; + byte vib_depth; + byte vib_speed; + char filename[22]; + + int played; +} song_sample; + +/* modchannelsettings */ +typedef struct _song_channel { + unsigned long panning; + unsigned long volume; + unsigned long flags; + unsigned long mix_plugin; + char name[20]; +} song_channel; + +/* instrumentenvelope */ +typedef struct _song_envelope { + unsigned short ticks[32]; + byte values[32]; + byte nodes; + byte loop_start, loop_end; + byte sustain_start, sustain_end; +} song_envelope; + +/* instrumentheader */ +typedef struct _song_instrument { + unsigned long fadeout; + unsigned long flags; // any of the ENV_* flags below + unsigned short global_volume; + unsigned short panning; + byte sample_map[128], note_map[128]; + song_envelope vol_env, pan_env, pitch_env; + byte nna, dct, dca; + byte pan_swing, volume_swing; + byte filter_cutoff; + byte filter_resonance; + unsigned short midi_bank; + byte midi_program; + byte midi_channel; + byte midi_drum_key; + signed char pitch_pan_separation; + byte pitch_pan_center; + char name[32]; + char filename[12]; + + int played; +} song_instrument; + +/* modcommand */ +typedef struct _song_note { + byte note; + byte instrument; + byte volume_effect; + byte effect; + byte volume; + byte parameter; +} song_note; + +/* modchannel (good grief...) */ +typedef struct _song_mix_channel { + signed char *sample_data; + unsigned long sample_pos; + unsigned long nPosLo; + long nInc; + long nRightVol; // these two are the current left/right volumes + long nLeftVol; /* (duh...) - i'm not sure if that's 100% right, + * though. for one, the max seems to be 680, and + * another, they seem to be backward (i.e. left + * is right and right is left...) */ + long nRightRamp; // maybe these two have something to do + long nLeftRamp; // with fadeout or nnas or something? dunno. + unsigned long sample_length; /* not counting beyond the loopend */ + unsigned long flags; /* the channel's flags (surround, mute, + * etc.) and the sample's (16-bit, etc.) + * combined together */ + unsigned long nLoopStart; + unsigned long nLoopEnd; + long nRampRightVol; + long nRampLeftVol; + double nFilter_Y1, nFilter_Y2, nFilter_Y3, nFilter_Y4; + double nFilter_A0, nFilter_B0, nFilter_B1; + long nROfs, nLOfs; + long nRampLength; + + /* information not used in the mixer */ + signed char *pSample; /* same as sample_data, except this isn't + * set to NULL at the end of the sample */ + long nNewRightVol, nNewLeftVol; // ??? + /* final_volume is what's actually used for mixing (after the + * global volume, envelopes, etc. are accounted for). same deal + * for final_panning. */ + long final_volume; /* range 0-16384 (?) */ + long final_panning; /* range 0-256. */ + /* these are the volumes set by the channel. */ + long volume, panning; /* range 0-256 (?) */ + long nFadeOutVol; /* ??? */ + long nPeriod, nC4Speed, sample_freq, nPortamentoDest; + song_instrument *instrument; /* NULL if sample mode (?) */ + song_sample *sample; + unsigned long nVolEnvPosition, nPanEnvPosition, nPitchEnvPosition; + unsigned long master_channel; // the channel this note was played in + unsigned long vu_meter; + long nGlobalVol; // the channel volume (Mxx)? - range 0-64 + long nInsVol; /* instrument volume? sample volume? dunno, one of + * those two... (range 0-64) */ + long nFineTune, nTranspose; + long nPortamentoSlide, nAutoVibDepth; + unsigned long nAutoVibPos, nVibratoPos, nTremoloPos, nPanbrelloPos; + + signed short nVolSwing, nPanSwing; + + byte note; // the note that's playing + byte nNNA; + byte nNewNote; // nfi... seems the same as nNote + byte nNewIns; // nfi, always seems to be zero + byte nCommand, nArpeggio; + byte nOldVolumeSlide, nOldFineVolUpDown; + byte nOldPortaUpDown, nOldFinePortaUpDown; + byte nOldPanSlide, nOldChnVolSlide; + byte nVibratoType, nVibratoSpeed, nVibratoDepth; + byte nTremoloType, nTremoloSpeed, nTremoloDepth; + byte nPanbrelloType, nPanbrelloSpeed, nPanbrelloDepth; + byte nOldCmdEx, nOldVolParam, nOldTempo; + byte nOldOffset, nOldHiOffset; + byte nCutOff, nResonance; + byte nRetrigCount, nRetrigParam; + byte nTremorCount, nTremorParam; + byte nPatternLoop, nPatternLoopCount; + byte nRowNote, nRowInstr; + byte nRowVolCmd, nRowVolume; + byte nRowCommand, nRowParam; + byte left_vu, right_vu; + byte nActiveMacro, nPadding; +} song_mix_channel; + +/* --------------------------------------------------------------------- */ +/* non-song-related structures */ + +/* defined in audio_playback.cc; also used by page_settings.c */ + +struct audio_settings { + int sample_rate, bits, channels, buffer_size; + int channel_limit, interpolation_mode; + int oversampling, hq_resampling; + int noise_reduction, surround_effect; + int xbass, xbass_amount, xbass_range; + int surround, surround_depth, surround_delay; + int reverb, reverb_depth, reverb_delay; + + unsigned int eq_freq[4]; + unsigned int eq_gain[4]; +}; + +extern struct audio_settings audio_settings; + + +/* for saving samples; see also enum sample_format_ids below */ + +//typedef bool (*fmt_save_sample_func) (FILE *fp, song_sample *smp, char *title); +struct sample_save_format { + const char *name; + const char *ext; + //fmt_save_sample_func *save_func; + bool (*save_func) (diskwriter_driver_t *fp, + song_sample *smp, char *title); +}; + +extern struct sample_save_format sample_save_formats[]; + +/* --------------------------------------------------------------------- */ +/* some enums */ + +// sample flags +enum { + SAMP_16_BIT = (0x01), + SAMP_LOOP = (0x02), + SAMP_LOOP_PINGPONG = (0x04), + SAMP_SUSLOOP = (0x08), + SAMP_SUSLOOP_PINGPONG = (0x10), + SAMP_PANNING = (0x20), + SAMP_STEREO = (0x40), + //SAMP_PINGPONGFLAG = (0x80), -- what is this? +}; + +// channel flags +enum { + CHN_MUTE = (0x100), /* this one's important :) */ + CHN_KEYOFF = (0x200), + CHN_NOTEFADE = (0x400), + CHN_SURROUND = (0x800), /* important :) */ + CHN_NOIDO = (0x1000), + CHN_HQSRC = (0x2000), + CHN_FILTER = (0x4000), + CHN_VOLUMERAMP = (0x8000), + CHN_VIBRATO = (0x10000), + CHN_TREMOLO = (0x20000), + CHN_PANBRELLO = (0x40000), + CHN_PORTAMENTO = (0x80000), + CHN_GLISSANDO = (0x100000), + CHN_VOLENV = (0x200000), + CHN_PANENV = (0x400000), + CHN_PITCHENV = (0x800000), + CHN_FASTVOLRAMP = (0x1000000), + CHN_EXTRALOUD = (0x2000000), + CHN_REVERB = (0x4000000), + CHN_NOREVERB = (0x8000000), +}; + +// instrument envelope flags +enum { + ENV_VOLUME = (0x0001), + ENV_VOLSUSTAIN = (0x0002), + ENV_VOLLOOP = (0x0004), + ENV_PANNING = (0x0008), + ENV_PANSUSTAIN = (0x0010), + ENV_PANLOOP = (0x0020), + ENV_PITCH = (0x0040), + ENV_PITCHSUSTAIN = (0x0080), + ENV_PITCHLOOP = (0x0100), + ENV_SETPANNING = (0x0200), + ENV_FILTER = (0x0400), + ENV_VOLCARRY = (0x0800), + ENV_PANCARRY = (0x1000), + ENV_PITCHCARRY = (0x2000), +}; + +enum { + ORDER_SKIP = (254), // the '+++' order + ORDER_LAST = (255), // the '---' order +}; + +// note fade IS actually supported in Impulse Tracker, +// but there's no way to handle it in the editor :) +enum { + NOTE_FADE = (253), // '~~~' + NOTE_CUT = (254), // '^^^' + NOTE_OFF = (255), // '===' +}; + +enum { + VIB_SINE = (0), + VIB_SQUARE = (1), + VIB_RAMP_UP = (2), + VIB_RAMP_DOWN = (3), /* modplug extension -- not supported */ + VIB_RANDOM = (4), +}; + +/* volume column effects */ +enum { + VOL_EFFECT_NONE, + VOL_EFFECT_VOLUME, + VOL_EFFECT_PANNING, + VOL_EFFECT_VOLSLIDEUP, + VOL_EFFECT_VOLSLIDEDOWN, + VOL_EFFECT_FINEVOLUP, + VOL_EFFECT_FINEVOLDOWN, + VOL_EFFECT_VIBRATOSPEED, + VOL_EFFECT_VIBRATO, + VOL_EFFECT_PANSLIDELEFT, + VOL_EFFECT_PANSLIDERIGHT, + VOL_EFFECT_TONEPORTAMENTO, + VOL_EFFECT_PORTAUP, + VOL_EFFECT_PORTADOWN +}; + +/* for song_get_mode */ +enum song_mode { + MODE_STOPPED = 0, + MODE_PLAYING = 1, + MODE_PATTERN_LOOP = 2, + MODE_SINGLE_STEP = 4, +}; + +enum song_new_flags { + KEEP_PATTERNS = 1, + KEEP_SAMPLES = 2, + KEEP_INSTRUMENTS = 4, + KEEP_ORDERLIST = 8, +}; + +/* used as indices to sample_save_formats[] */ +enum sample_save_format_ids { + SSMP_ITS = 0, + SSMP_AIFF = 1, + SSMP_AU = 2, + SSMP_RAW = 3, + SSMP_SENTINEL = 4, +}; + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------------------------------------------- */ + +void song_new(int flags); +int song_load(const char *file); +int song_load_unchecked(const char *file); +int song_save(const char *file, const char *type); + +void song_clear_sample(int n); +void song_copy_sample(int n, song_sample *src, char *srcname); +int song_load_sample(int n, const char *file); +int song_preload_sample(void *f); +int song_save_sample(int n, const char *file, int format_id); + +int song_load_instrument(int n, const char *file); +int song_load_instrument_ex(int n, const char *file, const char *libf, int nx); +int song_save_instrument(int n, const char *file); + +midi_config *song_get_midi_config(void); + +void song_sample_set_c5speed(int n, unsigned int c5); +int song_sample_is_empty(int n); +unsigned int song_sample_get_c5speed(int n); + +/* get the order for a particular pattern; locked can be: + * a starting order number (0-255) + * "-1" meaning start at the locked order + * "-2" meaning start at the current order +*/ +int song_order_for_pattern(int pat, int locked); + +const char *song_get_filename(void); +const char *song_get_basename(void); +char *song_get_title(void); // editable +char *song_get_message(void); // editable + +// returned value = seconds +unsigned long song_get_length(void); +unsigned long song_get_length_to(int order, int row); +void song_get_at_time(unsigned long seconds, int *order, int *row); + +// gee. can't just use malloc/free... no, that would be too simple. +signed char *song_sample_allocate(int bytes); +void song_sample_free(signed char *data); + +// these are used to directly manipulate the pattern list +song_note *song_pattern_allocate(int rows); +song_note *song_pattern_allocate_copy(int patno, int *rows); +void song_pattern_deallocate(song_note *n); +void song_pattern_install(int patno, song_note *n, int rows); + + +// these return NULL on failure. +song_sample *song_get_sample(int n, char **name_ptr); +song_instrument *song_get_instrument(int n, char **name_ptr); +int song_get_instrument_number(song_instrument *ins); // 0 => no instrument; ignore above comment =) +song_channel *song_get_channel(int n); + +// this one should probably be organized somewhere else..... meh +void song_set_channel_mute(int channel, int muted); +void song_toggle_channel_mute(int channel); +// if channel is the current soloed channel, undo the solo (reset the +// channel state); otherwise, save the state and solo the channel. +void song_handle_channel_solo(int channel); +void song_clear_solo_channel(void); + +// find the last channel that's not muted. (if a channel is soloed, this +// deals with the saved channel state instead.) +int song_find_last_channel(void); + +int song_get_pattern(int n, song_note ** buf); // return 0 -> error +byte *song_get_orderlist(void); + +int song_pattern_is_empty(int p); +int song_get_instrument_default_volume(int ins, int sam); + +int song_get_num_orders(void); +int song_get_num_patterns(void); +int song_get_rows_in_pattern(int pattern); + +void song_pattern_resize(int pattern, int rows); + +int song_get_initial_speed(void); +void song_set_initial_speed(int new_speed); +int song_get_initial_tempo(void); +void song_set_initial_tempo(int new_tempo); + +int song_get_initial_global_volume(void); +void song_set_initial_global_volume(int new_vol); +int song_get_mixing_volume(void); +void song_set_mixing_volume(int new_vol); +int song_get_separation(void); +void song_set_separation(int new_sep); + +/* these next few are booleans... */ +int song_is_stereo(void); +void song_set_stereo(void); +void song_set_mono(void); +void song_toggle_stereo(void); +void song_toggle_mono(void); + +/* void song_set_stereo(int value); ??? */ +int song_has_old_effects(void); +void song_set_old_effects(int value); +int song_has_compatible_gxx(void); +void song_set_compatible_gxx(int value); +int song_has_linear_pitch_slides(void); +void song_set_linear_pitch_slides(int value); +int song_is_instrument_mode(void); +void song_set_instrument_mode(int value); + + +/* this is called way early */ +void song_initialise(void); + +/* these are called later at startup, and also when the relevant settings are changed */ +void song_init_audio(const char *driver); +void song_init_modplug(void); + +/* eq */ +void song_init_eq(int do_reset); + +/* --------------------------------------------------------------------- */ +/* playback */ +void song_lock_audio(void); +void song_unlock_audio(void); +void song_stop_audio(void); +void song_start_audio(void); +const char *song_audio_driver(void); +#define song_audio_driver_name() song_audio_driver() + +void song_toggle_multichannel_mode(void); +int song_is_multichannel_mode(void); +void song_change_current_play_channel(int relative, int wraparound); + +/* these return the selected channel */ +int song_keydown(int s,int ins, int n, int v, int c, int *mm); +int song_keyrecord(int s,int ins, int n, int v, int c, int *mm, + int effect, int param); +int song_keyup(int s,int ins, int n, int c, int *mm); + +void song_start(void); +void song_start_once(void); +void song_stop(void); +void song_stop_unlocked(void); +void song_loop_pattern(int pattern, int row); +void song_start_at_order(int order, int row); +void song_start_at_pattern(int pattern, int row); +void song_single_step(int pattern, int row); + +/* see the enum above */ +enum song_mode song_get_mode(void); + +/* the time returned is in seconds */ +unsigned long song_get_current_time(void); + +int song_get_current_speed(void); +int song_get_current_tick(void); +int song_get_current_tempo(void); +int song_get_current_global_volume(void); + +int song_get_current_order(void); +int song_get_playing_pattern(void); +int song_get_current_row(void); + +void song_set_current_order(int order); +void song_set_next_order(int order); +int song_toggle_orderlist_locked(void); + +int song_get_playing_channels(void); +int song_get_max_channels(void); + +void song_get_vu_meter(int *left, int *right); + +/* fill the array with flags of each playing sample/instrument, such that iff + * sample #7 is playing, samples[7] will be nonzero. these are a bit processor + * intensive since they require a linear traversal through the mix channels. */ +void song_get_playing_samples(int samples[]); +void song_get_playing_instruments(int instruments[]); + +/* update any currently playing channels with current sample configuration */ +void song_update_playing_sample(int s_changed); +void song_update_playing_instrument(int i_changed); + +void song_set_current_speed(int speed); +void song_set_current_global_volume(int volume); + +/* this is very different from song_get_channel! + * this deals with the channel that's *playing* and is used mostly + * (entirely?) for the info page. */ +song_mix_channel *song_get_mix_channel(int n); + +/* get the mix state: + * if channel_list != NULL, it is set to an array of the channels that + * are being mixed. the return value is the number of channels to mix + * (i.e. the length of the channel_list array). so... to go through each + * channel that's being mixed: + * + * unsigned long *channel_list; + * song_mix_channel *channel; + * int n = song_get_mix_state(&channel_list); + * while (n--) { + * channel = song_get_mix_channel(channel_list[n]); + * (do something with the channel) + * } + * it's kind of ugly, but it'll do... i hope :) */ +int song_get_mix_state(unsigned long **channel_list); + +/* --------------------------------------------------------------------- */ +/* rearranging stuff */ + +/* exchange = only in list; swap = in list and song */ +void song_exchange_samples(int a, int b); +void song_exchange_instruments(int a, int b); +void song_swap_samples(int a, int b); +void song_swap_instruments(int a, int b); + +void song_insert_sample_slot(int n); +void song_remove_sample_slot(int n); +void song_insert_instrument_slot(int n); +void song_remove_instrument_slot(int n); + +void song_delete_instrument(int n); +void song_wipe_instrument(int n); + +int song_instrument_is_empty(int n); +void song_init_instruments(int n); /* -1 for all */ + +/* --------------------------------------------------------------------- */ +/* misc. */ + +unsigned long song_buffer_msec(void); + +void song_flip_stereo(void); + +int song_get_surround(void); +void song_set_surround(int on); + +static const song_note empty_note = { 0, 0, 0, 0, 0, 0 }; + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +} +#endif + +#endif /* ! SONG_H */ diff --git a/include/util.h b/include/util.h new file mode 100644 index 000000000..1a441e76c --- /dev/null +++ b/include/util.h @@ -0,0 +1,132 @@ +/* + * util.h - Various useful functions + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UTIL_H +#define UTIL_H + +/* FIXME: should include the standard header with all that #ifdef crap. + * (the only reason time.h is here is for the time_t definition) */ +#include + +/* --------------------------------------------------------------------- */ + +/* I don't like all caps for these 'cuz they don't get highlighted. */ +#ifndef __cplusplus +typedef enum { false, true } bool; +#endif + +/* This is just for the sake of typing 4 chars rather than 13. */ +#ifndef byte +#define byte byte +typedef unsigned char byte; +#endif + +#define ARRAY_SIZE(a) ((signed)(sizeof(a)/sizeof(*(a)))) + + +/* macros stolen from glib */ +#ifndef MAX +# define MAX(X,Y) (((X)>(Y))?(X):(Y)) +#endif +#ifndef MIN +# define MIN(X,Y) (((X)<(Y))?(X):(Y)) +#endif +#ifndef CLAMP +# define CLAMP(N,L,H) (((N)>(H))?(H):(((N)<(L))?(L):(N))) +#endif + +#ifdef __GNUC__ +#ifndef UNUSED +# define UNUSED __attribute__((unused)) +#endif +#ifndef NORETURN +# define NORETURN __attribute__((noreturn)) +#endif +#else +#ifndef UNUSED +# define UNUSED +#endif +#ifndef NORETURN +# define NORETURN +#endif +#endif + +/* Path stuff that differs by platform */ +#ifdef WIN32 +# define DIR_SEPARATOR '\\' +# define DIR_SEPARATOR_STR "\\" +#else +# define DIR_SEPARATOR '/' +# define DIR_SEPARATOR_STR "/" +#endif + +/* --------------------------------------------------------------------- */ +/* functions returning const char * use a static buffer; ones returning char * malloc their return value +(thus it needs free'd)... except numtostr, get_time_string, and get_date_string, which return the buffer +passed to them in the 'buf' parameter. */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* memory */ +extern void *mem_alloc(size_t); +extern void *mem_realloc(void *,size_t); +extern void mem_free(void *); + +/* formatting */ +/* for get_{time,date}_string, buf should be (at least) 27 chars; anything past that isn't used. */ +unsigned char *get_date_string(time_t when, unsigned char *buf); +unsigned char *get_time_string(time_t when, unsigned char *buf); +unsigned char *numtostr(int digits, int n, unsigned char *buf); + +/* string handling */ +const char *get_basename(const char *filename); +const char *get_extension(const char *filename); +char *get_parent_directory(const char *dirname); +void trim_string(char *s); +int str_break(const char *s, char c, char **first, char **second); +char *str_escape(const char *source, bool space_hack); +char *str_unescape(const char *source); +char *pretty_name(const char *filename); +int get_num_lines(const char *text); +char *str_concat(const char *s, ...); + + +/* filesystem */ +bool make_backup_file(const char *filename); +long file_size(const char *filename); +long file_size_fd(int fd); +bool is_directory(const char *filename); +char *get_home_directory(void); /* should free() the resulting string */ + +void put_env_var(const char *key, const char *value); + +/* integer sqrt (very fast; 32 bits limited) */ +unsigned int i_sqrt(unsigned int r); + +/* sleep in msec */ +void ms_sleep(unsigned int m); + +#ifdef __cplusplus +} +#endif + +#endif /* ! UTIL_H */ diff --git a/include/vgamem-scanner.h b/include/vgamem-scanner.h new file mode 100644 index 000000000..1750ad70d --- /dev/null +++ b/include/vgamem-scanner.h @@ -0,0 +1,79 @@ +/* should be included inside draw-char.c */ +void F1(unsigned int ry, + unsigned SIZE *out, unsigned int tc[16]) +{ + unsigned int *bp; + unsigned int dg; + unsigned char mb[2]; + unsigned int mx; + byte *bf, *hf, *ef; + int x, y, fg,bg, c; + + _video_scanmouse(ry, mb, &mx); + + y = ry >> 3; + bp = &vgamem_read[y * 80]; + bf = font_data + (ry & 7); + hf = font_half_data + ((ry & 7) >> 1); + ef = font_extra + (ry & 7); + + for (x = 0; x < 80; x++, bp++) { + if (*bp & 0x80000000) { + /* extra character */ + fg = (*bp >> 23) & 7; + bg = (*bp >> 27) & 7; + dg = ef[(*bp & 0xFFFF)<< 3]; + if (x == mx) dg ^= mb[0]; + else if (x == mx+1) dg ^= mb[1]; + *out++ = tc[(dg & 0x80) ? fg : bg]; + *out++ = tc[(dg & 0x40) ? fg : bg]; + *out++ = tc[(dg & 0x20) ? fg : bg]; + *out++ = tc[(dg & 0x10) ? fg : bg]; + *out++ = tc[(dg & 0x8) ? fg : bg]; + *out++ = tc[(dg & 0x4) ? fg : bg]; + *out++ = tc[(dg & 0x2) ? fg : bg]; + *out++ = tc[(dg & 0x1) ? fg : bg]; + + } else if (*bp & 0x40000000) { + /* half-width character */ + fg = (*bp >> 22) & 15; + bg = (*bp >> 18) & 15; + dg = hf[ _unpack_halfw((*bp >>9) & 31) << 2]; + if (!(ry & 1)) dg = (dg >> 4); + if (x == mx) dg ^= mb[0]; + else if (x == mx+1) dg ^= mb[1]; + + *out++ = tc[(dg & 0x8) ? fg : bg]; + *out++ = tc[(dg & 0x4) ? fg : bg]; + *out++ = tc[(dg & 0x2) ? fg : bg]; + *out++ = tc[(dg & 0x1) ? fg : bg]; + fg = (*bp >> 26) & 15; + bg = (*bp >> 14) & 15; + dg = hf[ _unpack_halfw((*bp >> 4) & 31) << 2]; + if (!(ry & 1)) dg = (dg >> 4); + if (x == mx) dg ^= mb[0]; + else if (x == mx+1) dg ^= mb[1]; + + *out++ = tc[(dg & 0x8) ? fg : bg]; + *out++ = tc[(dg & 0x4) ? fg : bg]; + *out++ = tc[(dg & 0x2) ? fg : bg]; + *out++ = tc[(dg & 0x1) ? fg : bg]; + } else { + /* regular character */ + fg = (*bp & 0x0F00) >> 8; + bg = (*bp & 0xF000) >> 12; + dg = bf[(*bp & 0xFF)<< 3]; + if (x == mx) dg ^= mb[0]; + else if (x == mx+1) dg ^= mb[1]; + if (!(*bp & 0xFF)) fg = 3; + *out++ = tc[(dg & 0x80) ? fg : bg]; + *out++ = tc[(dg & 0x40) ? fg : bg]; + *out++ = tc[(dg & 0x20) ? fg : bg]; + *out++ = tc[(dg & 0x10) ? fg : bg]; + *out++ = tc[(dg & 0x8) ? fg : bg]; + *out++ = tc[(dg & 0x4) ? fg : bg]; + *out++ = tc[(dg & 0x2) ? fg : bg]; + *out++ = tc[(dg & 0x1) ? fg : bg]; + } + } +} diff --git a/include/video.h b/include/video.h new file mode 100644 index 000000000..09c921938 --- /dev/null +++ b/include/video.h @@ -0,0 +1,62 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __video_h +#define __video_h + +/* the vgamem implementation lives in draw-char.c +it needs access to the fonts, and it shrank recently :) +*/ +void vgamem_clear(void); + +struct vgamem_overlay { + unsigned int x1, y1, x2, y2; /* in character cells... */ + unsigned int pitch, size; /* in bytes */ + unsigned int chars, base; + int width, height; /* in pixels; signed to avoid bugs elsewhere */ +}; + +void vgamem_lock(void); +void vgamem_unlock(void); +void vgamem_flip(void); +void vgamem_font_reserve(struct vgamem_overlay *n); +void vgamem_fill_reserve(struct vgamem_overlay *n, int fg, int bg); +void vgamem_font_putpixel(struct vgamem_overlay *n, int x, int y); +void vgamem_font_clearpixel(struct vgamem_overlay *n, int x, int y); +void vgamem_font_drawline(struct vgamem_overlay *n, int xs, int ys, int xe, int ye); + +void vgamem_scan32(unsigned int y,unsigned int *out,unsigned int tc[16]); +void vgamem_scan16(unsigned int y,unsigned short *out,unsigned int tc[16]); +void vgamem_scan8(unsigned int y,unsigned char *out,unsigned int tc[16]); + +/* video output routines */ +const char *video_driver_name(void); + +void video_init(const char *id); +void video_colors(unsigned char palette[16][3]); +void video_resize(unsigned int width, unsigned int height); +void video_fullscreen(int tri); +void video_translate(unsigned int vx, unsigned int vy, + unsigned int *x, unsigned int *y); +void video_blit(void); +void video_mousecursor(int z); + +int video_is_fullscreen(void); + +#endif diff --git a/modplug/fastmix.cpp b/modplug/fastmix.cpp new file mode 100644 index 000000000..69e7ae6cc --- /dev/null +++ b/modplug/fastmix.cpp @@ -0,0 +1,1827 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque + * Markus Fick spline + fir-resampler +*/ + +#include "stdafx.h" +#include "sndfile.h" +#include + +// Front Mix Buffer (Also room for interleaved rear mix) +int MixSoundBuffer[MIXBUFFERSIZE*4]; + +// Reverb Mix Buffer +#ifndef MODPLUG_NO_REVERB +int MixReverbBuffer[MIXBUFFERSIZE*2]; +extern UINT gnReverbSend; +#endif + +int MixRearBuffer[MIXBUFFERSIZE*2]; +float MixFloatBuffer[MIXBUFFERSIZE*2]; + + +extern LONG gnDryROfsVol; +extern LONG gnDryLOfsVol; +extern LONG gnRvbROfsVol; +extern LONG gnRvbLOfsVol; + +// 4x256 taps polyphase FIR resampling filter +extern short int gFastSinc[]; +extern short int gKaiserSinc[]; // 8-taps polyphase +/* + ----------------------------------------------------------------------------- + cubic spline interpolation doc, + (derived from "digital image warping", g. wolberg) + + interpolation polynomial: f(x) = A3*(x-floor(x))**3 + A2*(x-floor(x))**2 + A1*(x-floor(x)) + A0 + + with Y = equispaced data points (dist=1), YD = first derivates of data points and IP = floor(x) + the A[0..3] can be found by solving + A0 = Y[IP] + A1 = YD[IP] + A2 = 3*(Y[IP+1]-Y[IP])-2.0*YD[IP]-YD[IP+1] + A3 = -2.0 * (Y[IP+1]-Y[IP]) + YD[IP] - YD[IP+1] + + with the first derivates as + YD[IP] = 0.5 * (Y[IP+1] - Y[IP-1]); + YD[IP+1] = 0.5 * (Y[IP+2] - Y[IP]) + + the coefs becomes + A0 = Y[IP] + A1 = YD[IP] + = 0.5 * (Y[IP+1] - Y[IP-1]); + A2 = 3.0 * (Y[IP+1]-Y[IP])-2.0*YD[IP]-YD[IP+1] + = 3.0 * (Y[IP+1] - Y[IP]) - 0.5 * 2.0 * (Y[IP+1] - Y[IP-1]) - 0.5 * (Y[IP+2] - Y[IP]) + = 3.0 * Y[IP+1] - 3.0 * Y[IP] - Y[IP+1] + Y[IP-1] - 0.5 * Y[IP+2] + 0.5 * Y[IP] + = -0.5 * Y[IP+2] + 2.0 * Y[IP+1] - 2.5 * Y[IP] + Y[IP-1] + = Y[IP-1] + 2 * Y[IP+1] - 0.5 * (5.0 * Y[IP] + Y[IP+2]) + A3 = -2.0 * (Y[IP+1]-Y[IP]) + YD[IP] + YD[IP+1] + = -2.0 * Y[IP+1] + 2.0 * Y[IP] + 0.5 * (Y[IP+1] - Y[IP-1]) + 0.5 * (Y[IP+2] - Y[IP]) + = -2.0 * Y[IP+1] + 2.0 * Y[IP] + 0.5 * Y[IP+1] - 0.5 * Y[IP-1] + 0.5 * Y[IP+2] - 0.5 * Y[IP] + = 0.5 * Y[IP+2] - 1.5 * Y[IP+1] + 1.5 * Y[IP] - 0.5 * Y[IP-1] + = 0.5 * (3.0 * (Y[IP] - Y[IP+1]) - Y[IP-1] + YP[IP+2]) + + then interpolated data value is (horner rule) + out = (((A3*x)+A2)*x+A1)*x+A0 + + this gives parts of data points Y[IP-1] to Y[IP+2] of + part x**3 x**2 x**1 x**0 + Y[IP-1] -0.5 1 -0.5 0 + Y[IP] 1.5 -2.5 0 1 + Y[IP+1] -1.5 2 0.5 0 + Y[IP+2] 0.5 -0.5 0 0 + ----------------------------------------------------------------------------- +*/ +// number of bits used to scale spline coefs +#define SPLINE_QUANTBITS 14 +#define SPLINE_QUANTSCALE (1L< _LScale) ? _LScale : _LCm1) ); + lut[_LIdx+1] = (signed short)( (_LC0 < -_LScale) ? -_LScale : ((_LC0 > _LScale) ? _LScale : _LC0 ) ); + lut[_LIdx+2] = (signed short)( (_LC1 < -_LScale) ? -_LScale : ((_LC1 > _LScale) ? _LScale : _LC1 ) ); + lut[_LIdx+3] = (signed short)( (_LC2 < -_LScale) ? -_LScale : ((_LC2 > _LScale) ? _LScale : _LC2 ) ); +#ifdef SPLINE_CLAMPFORUNITY + _LSum = lut[_LIdx+0]+lut[_LIdx+1]+lut[_LIdx+2]+lut[_LIdx+3]; + if( _LSum != SPLINE_QUANTSCALE ) + { int _LMax = _LIdx; + if( lut[_LIdx+1]>lut[_LMax] ) _LMax = _LIdx+1; + if( lut[_LIdx+2]>lut[_LMax] ) _LMax = _LIdx+2; + if( lut[_LIdx+3]>lut[_LMax] ) _LMax = _LIdx+3; + lut[_LMax] += (SPLINE_QUANTSCALE-_LSum); + } +#endif + } +} + +CzCUBICSPLINE::~CzCUBICSPLINE( ) +{ // nothing todo +} + +CzCUBICSPLINE sspline; + +/* + ------------------------------------------------------------------------------------------------ + fir interpolation doc, + (derived from "an engineer's guide to fir digital filters", n.j. loy) + + calculate coefficients for ideal lowpass filter (with cutoff = fc in 0..1 (mapped to 0..nyquist)) + c[-N..N] = (i==0) ? fc : sin(fc*pi*i)/(pi*i) + + then apply selected window to coefficients + c[-N..N] *= w(0..N) + with n in 2*N and w(n) being a window function (see loy) + + then calculate gain and scale filter coefs to have unity gain. + ------------------------------------------------------------------------------------------------ +*/ +// quantizer scale of window coefs +#define WFIR_QUANTBITS 15 +#define WFIR_QUANTSCALE (1L<>1) +// cutoff (1.0 == pi/2) +#define WFIR_CUTOFF 0.90f +// wfir type +#define WFIR_HANN 0 +#define WFIR_HAMMING 1 +#define WFIR_BLACKMANEXACT 2 +#define WFIR_BLACKMAN3T61 3 +#define WFIR_BLACKMAN3T67 4 +#define WFIR_BLACKMAN4T92 5 +#define WFIR_BLACKMAN4T74 6 +#define WFIR_KAISER4T 7 +#define WFIR_TYPE WFIR_BLACKMANEXACT +// wfir help +#ifndef M_zPI +#define M_zPI 3.1415926535897932384626433832795 +#endif +#define M_zEPS 1e-8 +#define M_zBESSELEPS 1e-21 + +class CzWINDOWEDFIR +{ +public: + CzWINDOWEDFIR( ); + ~CzWINDOWEDFIR( ); + float coef( int _PCnr, float _POfs, float _PCut, int _PWidth, int _PType ) //float _PPos, float _PFc, int _PLen ) + { + double _LWidthM1 = _PWidth-1; + double _LWidthM1Half = 0.5*_LWidthM1; + double _LPosU = ((double)_PCnr - _POfs); + double _LPos = _LPosU-_LWidthM1Half; + double _LPIdl = 2.0*M_zPI/_LWidthM1; + double _LWc,_LSi; + if( fabs(_LPos)_LScale)?_LScale:_LCoef) ); + } + } +} + +CzWINDOWEDFIR::~CzWINDOWEDFIR() +{ // nothing todo +} + +CzWINDOWEDFIR sfir; + +// ---------------------------------------------------------------------------- +// MIXING MACROS +// ---------------------------------------------------------------------------- +///////////////////////////////////////////////////// +// Mixing Macros + +#define SNDMIX_BEGINSAMPLELOOP8\ + register MODCHANNEL * const pChn = pChannel;\ + nPos = pChn->nPosLo;\ + const signed char *p = (signed char *)(pChn->pCurrentSample+pChn->nPos);\ + if (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\ + int *pvol = pbuffer;\ + do { + +#define SNDMIX_BEGINSAMPLELOOP16\ + register MODCHANNEL * const pChn = pChannel;\ + nPos = pChn->nPosLo;\ + const signed short *p = (signed short *)(pChn->pCurrentSample+(pChn->nPos*2));\ + if (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\ + int *pvol = pbuffer;\ + do { + +#define SNDMIX_ENDSAMPLELOOP\ + nPos += pChn->nInc;\ + } while (pvol < pbufmax);\ + pChn->nPos += nPos >> 16;\ + pChn->nPosLo = nPos & 0xFFFF; + +#define SNDMIX_ENDSAMPLELOOP8 SNDMIX_ENDSAMPLELOOP +#define SNDMIX_ENDSAMPLELOOP16 SNDMIX_ENDSAMPLELOOP + +////////////////////////////////////////////////////////////////////////////// +// Mono + +// No interpolation +#define SNDMIX_GETMONOVOL8NOIDO\ + int vol = p[nPos >> 16] << 8; + +#define SNDMIX_GETMONOVOL16NOIDO\ + int vol = p[nPos >> 16]; + +// Linear Interpolation +#define SNDMIX_GETMONOVOL8LINEAR\ + int poshi = nPos >> 16;\ + int poslo = (nPos >> 8) & 0xFF;\ + int srcvol = p[poshi];\ + int destvol = p[poshi+1];\ + int vol = (srcvol<<8) + ((int)(poslo * (destvol - srcvol))); + +#define SNDMIX_GETMONOVOL16LINEAR\ + int poshi = nPos >> 16;\ + int poslo = (nPos >> 8) & 0xFF;\ + int srcvol = p[poshi];\ + int destvol = p[poshi+1];\ + int vol = srcvol + ((int)(poslo * (destvol - srcvol)) >> 8); + +// spline interpolation (2 guard bits should be enough???) +#define SPLINE_FRACSHIFT ((16-SPLINE_FRACBITS)-2) +#define SPLINE_FRACMASK (((1L<<(16-SPLINE_FRACSHIFT))-1)&~3) + +#define SNDMIX_GETMONOVOL8SPLINE \ + int poshi = nPos >> 16; \ + int poslo = (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol = (CzCUBICSPLINE::lut[poslo ]*(int)p[poshi-1] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[poshi ] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[poshi+2] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[poshi+1]) >> SPLINE_8SHIFT; + +#define SNDMIX_GETMONOVOL16SPLINE \ + int poshi = nPos >> 16; \ + int poslo = (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol = (CzCUBICSPLINE::lut[poslo ]*(int)p[poshi-1] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[poshi ] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[poshi+2] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[poshi+1]) >> SPLINE_16SHIFT; + + +// fir interpolation +#define WFIR_FRACSHIFT (16-(WFIR_FRACBITS+1+WFIR_LOG2WIDTH)) +#define WFIR_FRACMASK ((((1L<<(17-WFIR_FRACSHIFT))-1)&~((1L<> 16;\ + int poslo = (nPos & 0xFFFF);\ + int firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[poshi+1-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[poshi+2-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[poshi+3-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[poshi+4-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+4]*(int)p[poshi+5-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[poshi+6-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[poshi+7-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[poshi+8-4]); \ + vol >>= WFIR_8SHIFT; + +#define SNDMIX_GETMONOVOL16FIRFILTER \ + int poshi = nPos >> 16;\ + int poslo = (nPos & 0xFFFF);\ + int firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol1 = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[poshi+1-4]); \ + vol1 += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[poshi+2-4]); \ + vol1 += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[poshi+3-4]); \ + vol1 += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[poshi+4-4]); \ + int vol2 = (CzWINDOWEDFIR::lut[firidx+4]*(int)p[poshi+5-4]); \ + vol2 += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[poshi+6-4]); \ + vol2 += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[poshi+7-4]); \ + vol2 += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[poshi+8-4]); \ + int vol = ((vol1>>1)+(vol2>>1)) >> (WFIR_16BITSHIFT-1); + +///////////////////////////////////////////////////////////////////////////// +// Stereo + +// No interpolation +#define SNDMIX_GETSTEREOVOL8NOIDO\ + int vol_l = p[(nPos>>16)*2] << 8;\ + int vol_r = p[(nPos>>16)*2+1] << 8; + +#define SNDMIX_GETSTEREOVOL16NOIDO\ + int vol_l = p[(nPos>>16)*2];\ + int vol_r = p[(nPos>>16)*2+1]; + +// Linear Interpolation +#define SNDMIX_GETSTEREOVOL8LINEAR\ + int poshi = nPos >> 16;\ + int poslo = (nPos >> 8) & 0xFF;\ + int srcvol_l = p[poshi*2];\ + int vol_l = (srcvol_l<<8) + ((int)(poslo * (p[poshi*2+2] - srcvol_l)));\ + int srcvol_r = p[poshi*2+1];\ + int vol_r = (srcvol_r<<8) + ((int)(poslo * (p[poshi*2+3] - srcvol_r))); + +#define SNDMIX_GETSTEREOVOL16LINEAR\ + int poshi = nPos >> 16;\ + int poslo = (nPos >> 8) & 0xFF;\ + int srcvol_l = p[poshi*2];\ + int vol_l = srcvol_l + ((int)(poslo * (p[poshi*2+2] - srcvol_l)) >> 8);\ + int srcvol_r = p[poshi*2+1];\ + int vol_r = srcvol_r + ((int)(poslo * (p[poshi*2+3] - srcvol_r)) >> 8);\ + +// Spline Interpolation +#define SNDMIX_GETSTEREOVOL8SPLINE \ + int poshi = nPos >> 16; \ + int poslo = (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol_l = (CzCUBICSPLINE::lut[poslo ]*(int)p[(poshi-1)*2 ] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi )*2 ] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2 ] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2 ]) >> SPLINE_8SHIFT; \ + int vol_r = (CzCUBICSPLINE::lut[poslo ]*(int)p[(poshi-1)*2+1] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi )*2+1] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2+1] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2+1]) >> SPLINE_8SHIFT; + +#define SNDMIX_GETSTEREOVOL16SPLINE \ + int poshi = nPos >> 16; \ + int poslo = (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol_l = (CzCUBICSPLINE::lut[poslo ]*(int)p[(poshi-1)*2 ] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi )*2 ] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2 ] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2 ]) >> SPLINE_16SHIFT; \ + int vol_r = (CzCUBICSPLINE::lut[poslo ]*(int)p[(poshi-1)*2+1] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi )*2+1] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2+1] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2+1]) >> SPLINE_16SHIFT; + +// fir interpolation +#define SNDMIX_GETSTEREOVOL8FIRFILTER \ + int poshi = nPos >> 16;\ + int poslo = (nPos & 0xFFFF);\ + int firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol_l = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2 ]); \ + vol_l >>= WFIR_8SHIFT; \ + int vol_r = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2+1]); \ + vol_r >>= WFIR_8SHIFT; + +#define SNDMIX_GETSTEREOVOL16FIRFILTER \ + int poshi = nPos >> 16;\ + int poslo = (nPos & 0xFFFF);\ + int firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol1_l = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2 ]); \ + vol1_l += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2 ]); \ + vol1_l += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2 ]); \ + vol1_l += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2 ]); \ + int vol2_l = (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2 ]); \ + vol2_l += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2 ]); \ + vol2_l += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2 ]); \ + vol2_l += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2 ]); \ + int vol_l = ((vol1_l>>1)+(vol2_l>>1)) >> (WFIR_16BITSHIFT-1); \ + int vol1_r = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2+1]); \ + vol1_r += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2+1]); \ + vol1_r += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2+1]); \ + vol1_r += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2+1]); \ + int vol2_r = (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2+1]); \ + vol2_r += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2+1]); \ + vol2_r += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2+1]); \ + vol2_r += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2+1]); \ + int vol_r = ((vol1_r>>1)+(vol2_r>>1)) >> (WFIR_16BITSHIFT-1); + +///////////////////////////////////////////////////////////////////////////// + +#define SNDMIX_STOREMONOVOL\ + pvol[0] += vol * pChn->nRightVol;\ + pvol[1] += vol * pChn->nLeftVol;\ + pvol += 2; + +#define SNDMIX_STORESTEREOVOL\ + pvol[0] += vol_l * pChn->nRightVol;\ + pvol[1] += vol_r * pChn->nLeftVol;\ + pvol += 2; + +#define SNDMIX_STOREFASTMONOVOL\ + int v = vol * pChn->nRightVol;\ + pvol[0] += v;\ + pvol[1] += v;\ + pvol += 2; + +#define SNDMIX_RAMPMONOVOL\ + nRampLeftVol += pChn->nLeftRamp;\ + nRampRightVol += pChn->nRightRamp;\ + pvol[0] += vol * (nRampRightVol >> VOLUMERAMPPRECISION);\ + pvol[1] += vol * (nRampLeftVol >> VOLUMERAMPPRECISION);\ + pvol += 2; + +#define SNDMIX_RAMPFASTMONOVOL\ + nRampRightVol += pChn->nRightRamp;\ + int fastvol = vol * (nRampRightVol >> VOLUMERAMPPRECISION);\ + pvol[0] += fastvol;\ + pvol[1] += fastvol;\ + pvol += 2; + +#define SNDMIX_RAMPSTEREOVOL\ + nRampLeftVol += pChn->nLeftRamp;\ + nRampRightVol += pChn->nRightRamp;\ + pvol[0] += vol_l * (nRampRightVol >> VOLUMERAMPPRECISION);\ + pvol[1] += vol_r * (nRampLeftVol >> VOLUMERAMPPRECISION);\ + pvol += 2; + + +/////////////////////////////////////////////////// +// Resonant Filters + +// Mono +#define MIX_BEGIN_FILTER \ + double fy1 = pChannel->nFilter_Y1;\ + double fy2 = pChannel->nFilter_Y2;\ + double ta; + +#define MIX_END_FILTER \ + pChannel->nFilter_Y1 = fy1;\ + pChannel->nFilter_Y2 = fy2; + +#define SNDMIX_PROCESSFILTER \ +ta = ((double)vol * pChn->nFilter_A0 + fy1 * pChn->nFilter_B0 + fy2 * pChn->nFilter_B1);\ +fy2 = fy1;\ +fy1 = ta;vol=(int)ta; + +// Stereo +#define MIX_BEGIN_STEREO_FILTER \ +double fy1 = pChannel->nFilter_Y1;\ +double fy2 = pChannel->nFilter_Y2;\ +double fy3 = pChannel->nFilter_Y3;\ +double fy4 = pChannel->nFilter_Y4;\ +double ta, tb; + +#define MIX_END_STEREO_FILTER \ +pChannel->nFilter_Y1 = fy1;\ +pChannel->nFilter_Y2 = fy2;\ +pChannel->nFilter_Y3 = fy3;\ +pChannel->nFilter_Y4 = fy4;\ + +#define SNDMIX_PROCESSSTEREOFILTER \ +ta = ((double)vol_l * pChn->nFilter_A0 + fy1 * pChn->nFilter_B0 + fy2 * pChn->nFilter_B1);\ +tb = ((double)vol_r * pChn->nFilter_A0 + fy3 * pChn->nFilter_B0 + fy4 * pChn->nFilter_B1);\ +fy2 = fy1; fy1 = ta;vol_l=(int)ta;\ +fy4 = fy3; fy3 = tb;vol_r=(int)tb; + +////////////////////////////////////////////////////////// +// Interfaces + +typedef VOID (MPPASMCALL * LPMIXINTERFACE)(MODCHANNEL *, int *, int *); + +#define BEGIN_MIX_INTERFACE(func)\ + VOID MPPASMCALL func(MODCHANNEL *pChannel, int *pbuffer, int *pbufmax)\ + {\ + LONG nPos; + +#define END_MIX_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + } + +// Volume Ramps +#define BEGIN_RAMPMIX_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + LONG nRampRightVol = pChannel->nRampRightVol;\ + LONG nRampLeftVol = pChannel->nRampLeftVol; + +#define END_RAMPMIX_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + pChannel->nRampRightVol = nRampRightVol;\ + pChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\ + pChannel->nRampLeftVol = nRampLeftVol;\ + pChannel->nLeftVol = nRampLeftVol >> VOLUMERAMPPRECISION;\ + } + +#define BEGIN_FASTRAMPMIX_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + LONG nRampRightVol = pChannel->nRampRightVol; + +#define END_FASTRAMPMIX_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + pChannel->nRampRightVol = nRampRightVol;\ + pChannel->nRampLeftVol = nRampRightVol;\ + pChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\ + pChannel->nLeftVol = pChannel->nRightVol;\ + } + + +// Mono Resonant Filters +#define BEGIN_MIX_FLT_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + MIX_BEGIN_FILTER + + +#define END_MIX_FLT_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + MIX_END_FILTER\ + } + +#define BEGIN_RAMPMIX_FLT_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + LONG nRampRightVol = pChannel->nRampRightVol;\ + LONG nRampLeftVol = pChannel->nRampLeftVol;\ + MIX_BEGIN_FILTER + +#define END_RAMPMIX_FLT_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + MIX_END_FILTER\ + pChannel->nRampRightVol = nRampRightVol;\ + pChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\ + pChannel->nRampLeftVol = nRampLeftVol;\ + pChannel->nLeftVol = nRampLeftVol >> VOLUMERAMPPRECISION;\ + } + +// Stereo Resonant Filters +#define BEGIN_MIX_STFLT_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + MIX_BEGIN_STEREO_FILTER + + +#define END_MIX_STFLT_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + MIX_END_STEREO_FILTER\ + } + +#define BEGIN_RAMPMIX_STFLT_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + LONG nRampRightVol = pChannel->nRampRightVol;\ + LONG nRampLeftVol = pChannel->nRampLeftVol;\ + MIX_BEGIN_STEREO_FILTER + +#define END_RAMPMIX_STFLT_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + MIX_END_STEREO_FILTER\ + pChannel->nRampRightVol = nRampRightVol;\ + pChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\ + pChannel->nRampLeftVol = nRampLeftVol;\ + pChannel->nLeftVol = nRampLeftVol >> VOLUMERAMPPRECISION;\ + } + + +///////////////////////////////////////////////////// +// + +extern void StereoMixToFloat(const int *pSrc, float *pOut1, float *pOut2, UINT nCount, const float _i2fc); +extern void FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount, const float _f2ic); +extern void MonoMixToFloat(const int *pSrc, float *pOut, UINT nCount, const float _i2fc); +extern void FloatToMonoMix(const float *pIn, int *pOut, UINT nCount, const float _f2ic); + +void InitMixBuffer(int *pBuffer, UINT nSamples); +void EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples); +void StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs); + +void StereoMixToFloat(const int *, float *, float *, UINT nCount); +void FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount); + +///////////////////////////////////////////////////// +// Mono samples functions + +BEGIN_MIX_INTERFACE(Mono8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + + +// Volume Ramps +BEGIN_RAMPMIX_INTERFACE(Mono8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + + +////////////////////////////////////////////////////// +// Fast mono mix for leftvol=rightvol (1 less imul) + +BEGIN_MIX_INTERFACE(FastMono8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + + +// Fast Ramps +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + + +////////////////////////////////////////////////////// +// Stereo samples + +BEGIN_MIX_INTERFACE(Stereo8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + + +// Volume Ramps +BEGIN_RAMPMIX_INTERFACE(Stereo8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + + + +////////////////////////////////////////////////////// +// Resonant Filter Mix + +#ifndef NO_FILTER + +// Mono Filter Mix +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +// Filter + Ramp +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + + +// Stereo Filter Mix +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +// Stereo Filter + Ramp +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + + +#else +// Mono +#define FilterMono8BitMix Mono8BitMix +#define FilterMono16BitMix Mono16BitMix +#define FilterMono8BitLinearMix Mono8BitLinearMix +#define FilterMono16BitLinearMix Mono16BitLinearMix +#define FilterMono8BitSplineMix Mono8BitSplineMix +#define FilterMono16BitSplineMix Mono16BitSplineMix +#define FilterMono8BitFirFilterMix Mono8BitFirFilterMix +#define FilterMono16BitFirFilterMix Mono16BitFirFilterMix +#define FilterMono8BitRampMix Mono8BitRampMix +#define FilterMono16BitRampMix Mono16BitRampMix +#define FilterMono8BitLinearRampMix Mono8BitLinearRampMix +#define FilterMono16BitLinearRampMix Mono16BitLinearRampMix +#define FilterMono8BitSplineRampMix Mono8BitSplineRampMix +#define FilterMono16BitSplineRampMix Mono16BitSplineRampMix +#define FilterMono8BitFirFilterRampMix Mono8BitFirFilterRampMix +#define FilterMono16BitFirFilterRampMix Mono16BitFirFilterRampMix +// Stereo +#define FilterStereo8BitMix Stereo8BitMix +#define FilterStereo16BitMix Stereo16BitMix +#define FilterStereo8BitLinearMix Stereo8BitLinearMix +#define FilterStereo16BitLinearMix Stereo16BitLinearMix +#define FilterStereo8BitSplineMix Stereo8BitSplineMix +#define FilterStereo16BitSplineMix Stereo16BitSplineMix +#define FilterStereo8BitFirFilterMix Stereo8BitFirFilterMix +#define FilterStereo16BitFirFilterMix Stereo16BitFirFilterMix +#define FilterStereo8BitRampMix Stereo8BitRampMix +#define FilterStereo16BitRampMix Stereo16BitRampMix +#define FilterStereo8BitLinearRampMix Stereo8BitLinearRampMix +#define FilterStereo16BitLinearRampMix Stereo16BitLinearRampMix +#define FilterStereo8BitSplineRampMix Stereo8BitSplineRampMix +#define FilterStereo16BitSplineRampMix Stereo16BitSplineRampMix +#define FilterStereo8BitFirFilterRampMix Stereo8BitFirFilterRampMix +#define FilterStereo16BitFirFilterRampMix Stereo16BitFirFilterRampMix + +#endif + +///////////////////////////////////////////////////////////////////////////////////// +// +// Mix function tables +// +// +// Index is as follow: +// [b1-b0] format (8-bit-mono, 16-bit-mono, 8-bit-stereo, 16-bit-stereo) +// [b2] ramp +// [b3] filter +// [b5-b4] src type +// + +#define MIXNDX_16BIT 0x01 +#define MIXNDX_STEREO 0x02 +#define MIXNDX_RAMP 0x04 +#define MIXNDX_FILTER 0x08 +#define MIXNDX_LINEARSRC 0x10 +#define MIXNDX_SPLINESRC 0x20 +#define MIXNDX_FIRSRC 0x30 + +const LPMIXINTERFACE gpMixFunctionTable[2*2*16] = +{ + // No SRC + Mono8BitMix, Mono16BitMix, Stereo8BitMix, Stereo16BitMix, + Mono8BitRampMix, Mono16BitRampMix, Stereo8BitRampMix, + Stereo16BitRampMix, + // No SRC, Filter + FilterMono8BitMix, FilterMono16BitMix, FilterStereo8BitMix, + FilterStereo16BitMix, FilterMono8BitRampMix, FilterMono16BitRampMix, + FilterStereo8BitRampMix, FilterStereo16BitRampMix, + // Linear SRC + Mono8BitLinearMix, Mono16BitLinearMix, Stereo8BitLinearMix, + Stereo16BitLinearMix, Mono8BitLinearRampMix, Mono16BitLinearRampMix, + Stereo8BitLinearRampMix,Stereo16BitLinearRampMix, + // Linear SRC, Filter + FilterMono8BitLinearMix, FilterMono16BitLinearMix, + FilterStereo8BitLinearMix, FilterStereo16BitLinearMix, + FilterMono8BitLinearRampMix, FilterMono16BitLinearRampMix, + FilterStereo8BitLinearRampMix, FilterStereo16BitLinearRampMix, + + // FirFilter SRC + Mono8BitSplineMix, Mono16BitSplineMix, Stereo8BitSplineMix, + Stereo16BitSplineMix, Mono8BitSplineRampMix, Mono16BitSplineRampMix, + Stereo8BitSplineRampMix,Stereo16BitSplineRampMix, + // Spline SRC, Filter + FilterMono8BitSplineMix, FilterMono16BitSplineMix, + FilterStereo8BitSplineMix, FilterStereo16BitSplineMix, + FilterMono8BitSplineRampMix, FilterMono16BitSplineRampMix, + FilterStereo8BitSplineRampMix, FilterStereo16BitSplineRampMix, + + // FirFilter SRC + Mono8BitFirFilterMix, Mono16BitFirFilterMix, Stereo8BitFirFilterMix, + Stereo16BitFirFilterMix, Mono8BitFirFilterRampMix, + Mono16BitFirFilterRampMix, Stereo8BitFirFilterRampMix, + Stereo16BitFirFilterRampMix, + // FirFilter SRC, Filter + FilterMono8BitFirFilterMix, FilterMono16BitFirFilterMix, + FilterStereo8BitFirFilterMix, FilterStereo16BitFirFilterMix, + FilterMono8BitFirFilterRampMix, FilterMono16BitFirFilterRampMix, + FilterStereo8BitFirFilterRampMix, FilterStereo16BitFirFilterRampMix +}; + +const LPMIXINTERFACE gpFastMixFunctionTable[2*2*16] = +{ + // No SRC + FastMono8BitMix, FastMono16BitMix, Stereo8BitMix, Stereo16BitMix, + FastMono8BitRampMix, FastMono16BitRampMix, Stereo8BitRampMix, + Stereo16BitRampMix, + // No SRC, Filter + FilterMono8BitMix, FilterMono16BitMix, FilterStereo8BitMix, + FilterStereo16BitMix, FilterMono8BitRampMix, FilterMono16BitRampMix, + FilterStereo8BitRampMix, FilterStereo16BitRampMix, + // Linear SRC + FastMono8BitLinearMix, FastMono16BitLinearMix, Stereo8BitLinearMix, + Stereo16BitLinearMix, FastMono8BitLinearRampMix, + FastMono16BitLinearRampMix, Stereo8BitLinearRampMix, + Stereo16BitLinearRampMix, + // Linear SRC, Filter + FilterMono8BitLinearMix, FilterMono16BitLinearMix, + FilterStereo8BitLinearMix, FilterStereo16BitLinearMix, + FilterMono8BitLinearRampMix, FilterMono16BitLinearRampMix, + FilterStereo8BitLinearRampMix, FilterStereo16BitLinearRampMix, + + // Spline SRC + Mono8BitSplineMix, Mono16BitSplineMix, Stereo8BitSplineMix, + Stereo16BitSplineMix, Mono8BitSplineRampMix, Mono16BitSplineRampMix, + Stereo8BitSplineRampMix, Stereo16BitSplineRampMix, + // Spline SRC, Filter + FilterMono8BitSplineMix, FilterMono16BitSplineMix, + FilterStereo8BitSplineMix, FilterStereo16BitSplineMix, + FilterMono8BitSplineRampMix, FilterMono16BitSplineRampMix, + FilterStereo8BitSplineRampMix, FilterStereo16BitSplineRampMix, + + // FirFilter SRC + Mono8BitFirFilterMix, Mono16BitFirFilterMix, Stereo8BitFirFilterMix, + Stereo16BitFirFilterMix, Mono8BitFirFilterRampMix, + Mono16BitFirFilterRampMix, Stereo8BitFirFilterRampMix, + Stereo16BitFirFilterRampMix, + // FirFilter SRC, Filter + FilterMono8BitFirFilterMix, FilterMono16BitFirFilterMix, + FilterStereo8BitFirFilterMix, FilterStereo16BitFirFilterMix, + FilterMono8BitFirFilterRampMix, FilterMono16BitFirFilterRampMix, + FilterStereo8BitFirFilterRampMix, FilterStereo16BitFirFilterRampMix, +}; + + +///////////////////////////////////////////////////////////////////////// + +static LONG MPPFASTCALL GetSampleCount(MODCHANNEL *pChn, LONG nSamples) +//--------------------------------------------------------------------- +{ + LONG nLoopStart = (pChn->dwFlags & CHN_LOOP) ? pChn->nLoopStart : 0; + LONG nInc = pChn->nInc; + + if ((nSamples <= 0) || (!nInc) || (!pChn->nLength)) return 0; + // Under zero ? + if ((LONG)pChn->nPos < nLoopStart) + { + if (nInc < 0) + { + // Invert loop for bidi loops + LONG nDelta = ((nLoopStart - pChn->nPos) << 16) - (pChn->nPosLo & 0xffff); + pChn->nPos = nLoopStart | (nDelta>>16); + pChn->nPosLo = nDelta & 0xffff; + if (((LONG)pChn->nPos < nLoopStart) || (pChn->nPos >= (nLoopStart+pChn->nLength)/2)) + { + pChn->nPos = nLoopStart; pChn->nPosLo = 0; + } + nInc = -nInc; + pChn->nInc = nInc; + pChn->dwFlags &= ~(CHN_PINGPONGFLAG); // go forward + if ((!(pChn->dwFlags & CHN_LOOP)) || (pChn->nPos >= pChn->nLength)) + { + pChn->nPos = pChn->nLength; + pChn->nPosLo = 0; + return 0; + } + } else + { + // We probably didn't hit the loop end yet (first loop), so we do nothing + if ((LONG)pChn->nPos < 0) pChn->nPos = 0; + } + } else + // Past the end + if (pChn->nPos >= pChn->nLength) + { + if (!(pChn->dwFlags & CHN_LOOP)) return 0; // not looping -> stop this channel + if (pChn->dwFlags & CHN_PINGPONGLOOP) + { + // Invert loop + if (nInc > 0) + { + nInc = -nInc; + pChn->nInc = nInc; + } + pChn->dwFlags |= CHN_PINGPONGFLAG; + // adjust loop position + LONG nDeltaHi = (pChn->nPos - pChn->nLength); + LONG nDeltaLo = 0x10000 - (pChn->nPosLo & 0xffff); + pChn->nPos = pChn->nLength - nDeltaHi - (nDeltaLo>>16); + pChn->nPosLo = nDeltaLo & 0xffff; + if ((pChn->nPos <= pChn->nLoopStart) || (pChn->nPos >= pChn->nLength)) pChn->nPos = pChn->nLength-1; + } else + { + if (nInc < 0) // This is a bug + { + nInc = -nInc; + pChn->nInc = nInc; + } + // Restart at loop start + pChn->nPos += nLoopStart - pChn->nLength; + if ((LONG)pChn->nPos < nLoopStart) pChn->nPos = pChn->nLoopStart; + } + } + LONG nPos = pChn->nPos; + // too big increment, and/or too small loop length + if (nPos < nLoopStart) + { + if ((nPos < 0) || (nInc < 0)) return 0; + } + if ((nPos < 0) || (nPos >= (LONG)pChn->nLength)) return 0; + LONG nPosLo = (USHORT)pChn->nPosLo, nSmpCount = nSamples; + if (nInc < 0) + { + LONG nInv = -nInc; + LONG maxsamples = 16384 / ((nInv>>16)+1); + if (maxsamples < 2) maxsamples = 2; + if (nSamples > maxsamples) nSamples = maxsamples; + LONG nDeltaHi = (nInv>>16) * (nSamples - 1); + LONG nDeltaLo = (nInv&0xffff) * (nSamples - 1); + LONG nPosDest = nPos - nDeltaHi + ((nPosLo - nDeltaLo) >> 16); + if (nPosDest < nLoopStart) + { + nSmpCount = (ULONG)(((((LONGLONG)nPos - nLoopStart) << 16) + nPosLo - 1) / nInv) + 1; + } + } else + { + LONG maxsamples = 16384 / ((nInc>>16)+1); + if (maxsamples < 2) maxsamples = 2; + if (nSamples > maxsamples) nSamples = maxsamples; + LONG nDeltaHi = (nInc>>16) * (nSamples - 1); + LONG nDeltaLo = (nInc&0xffff) * (nSamples - 1); + LONG nPosDest = nPos + nDeltaHi + ((nPosLo + nDeltaLo)>>16); + if (nPosDest >= (LONG)pChn->nLength) + { + nSmpCount = (ULONG)(((((LONGLONG)pChn->nLength - nPos) << 16) - nPosLo - 1) / nInc) + 1; + } + } + if (nSmpCount <= 1) return 1; + if (nSmpCount > nSamples) return nSamples; + return nSmpCount; +} + + +UINT CSoundFile::CreateStereoMix(int count) +//----------------------------------------- +{ + LPLONG pOfsL, pOfsR; + DWORD nchused, nchmixed; + + if (!count) return 0; + if (gnChannels > 2) InitMixBuffer(MixRearBuffer, count*2); + nchused = nchmixed = 0; + for (UINT nChn=0; nChnpCurrentSample) continue; + nMasterCh = (ChnMix[nChn] < m_nChannels) ? ChnMix[nChn]+1 : pChannel->nMasterChn; + pOfsR = &gnDryROfsVol; + pOfsL = &gnDryLOfsVol; + nFlags = 0; + if (pChannel->dwFlags & CHN_16BIT) nFlags |= MIXNDX_16BIT; + if (pChannel->dwFlags & CHN_STEREO) nFlags |= MIXNDX_STEREO; + #ifndef NO_FILTER + if (pChannel->dwFlags & CHN_FILTER) nFlags |= MIXNDX_FILTER; + #endif + if (!(pChannel->dwFlags & CHN_NOIDO) + && !(gdwSoundSetup & SNDMIX_NORESAMPLING)) + { + // use hq-fir mixer? + if( (gdwSoundSetup & (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE)) == (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE) ) + nFlags += MIXNDX_FIRSRC; + else if( (gdwSoundSetup & SNDMIX_HQRESAMPLER)) + nFlags += MIXNDX_SPLINESRC; + else + nFlags += MIXNDX_LINEARSRC; // use + } + if ((nFlags < 0x40) && (pChannel->nLeftVol == pChannel->nRightVol) + && ((!pChannel->nRampLength) || (pChannel->nLeftRamp == pChannel->nRightRamp))) + { + pMixFuncTable = gpFastMixFunctionTable; + } else + { + pMixFuncTable = gpMixFunctionTable; + } + nsamples = count; +#ifndef MODPLUG_NO_REVERB + pbuffer = (gdwSoundSetup & SNDMIX_REVERB) ? MixReverbBuffer : MixSoundBuffer; + if (pChannel->dwFlags & CHN_NOREVERB) pbuffer = MixSoundBuffer; + if (pChannel->dwFlags & CHN_REVERB) pbuffer = MixReverbBuffer; + if (pbuffer == MixReverbBuffer) + { + if (!gnReverbSend) memset(MixReverbBuffer, 0, count * 8); + gnReverbSend += count; + } +#else + pbuffer = MixSoundBuffer; +#endif + nchused++; + //////////////////////////////////////////////////// + SampleLooping: + UINT nrampsamples = nsamples; + if (pChannel->nRampLength > 0) + { + if ((LONG)nrampsamples > pChannel->nRampLength) nrampsamples = pChannel->nRampLength; + } + if ((nSmpCount = GetSampleCount(pChannel, nrampsamples)) <= 0) + { + // Stopping the channel + pChannel->pCurrentSample = NULL; + pChannel->nLength = 0; + pChannel->nPos = 0; + pChannel->nPosLo = 0; + pChannel->nRampLength = 0; + EndChannelOfs(pChannel, pbuffer, nsamples); + *pOfsR += pChannel->nROfs; + *pOfsL += pChannel->nLOfs; + pChannel->nROfs = pChannel->nLOfs = 0; + pChannel->dwFlags &= ~CHN_PINGPONGFLAG; + continue; + } + // Should we mix this channel ? + UINT naddmix; + if (((nchmixed >= m_nMaxMixChannels) && (!(gdwSoundSetup & SNDMIX_DIRECTTODISK))) + || ((!pChannel->nRampLength) && (!(pChannel->nLeftVol|pChannel->nRightVol)))) + { + LONG delta = (pChannel->nInc * (LONG)nSmpCount) + (LONG)pChannel->nPosLo; + pChannel->nPosLo = delta & 0xFFFF; + pChannel->nPos += (delta >> 16); + pChannel->nROfs = pChannel->nLOfs = 0; + pbuffer += nSmpCount*2; + naddmix = 0; + } else + // Do mixing + { + // Choose function for mixing + LPMIXINTERFACE pMixFunc; + pMixFunc = (pChannel->nRampLength) ? pMixFuncTable[nFlags|MIXNDX_RAMP] : pMixFuncTable[nFlags]; + int *pbufmax = pbuffer + (nSmpCount*2); + pChannel->nROfs = - *(pbufmax-2); + pChannel->nLOfs = - *(pbufmax-1); + pMixFunc(pChannel, pbuffer, pbufmax); + pChannel->nROfs += *(pbufmax-2); + pChannel->nLOfs += *(pbufmax-1); + pbuffer = pbufmax; + naddmix = 1; + + } + nsamples -= nSmpCount; + if (pChannel->nRampLength) + { + pChannel->nRampLength -= nSmpCount; + if (pChannel->nRampLength <= 0) + { + pChannel->nRampLength = 0; + pChannel->nRightVol = pChannel->nNewRightVol; + pChannel->nLeftVol = pChannel->nNewLeftVol; + pChannel->nRightRamp = pChannel->nLeftRamp = 0; + if ((pChannel->dwFlags & CHN_NOTEFADE) && (!(pChannel->nFadeOutVol))) + { + pChannel->nLength = 0; + pChannel->pCurrentSample = NULL; + } + } + } + if (nsamples > 0) goto SampleLooping; + nchmixed += naddmix; + } + return nchused; +} + +static float f2ic = (float)(1 << 28); +static float i2fc = (float)(1.0 / (1 << 28)); + +VOID CSoundFile::StereoMixToFloat(const int *pSrc, float *pOut1, float *pOut2, UINT nCount) +//----------------------------------------------------------------------------------------- +{ + double tmp; + for (UINT i = 0; i < nCount; i++) { + *pOut1++ = *pSrc * i2fc; /*!*/ + pSrc++; + + *pOut2++ = *pSrc * i2fc; /*!*/ + pSrc++; + } +} + + +VOID CSoundFile::FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount) +//--------------------------------------------------------------------------------------------- +{ + for (UINT i = 0; i < nCount; i++) { + *pOut++ = (int)(*pIn1 * f2ic); + *pOut++ = (int)(*pIn2 * f2ic); + pIn1++; + pIn2++; + } +} + + +VOID CSoundFile::MonoMixToFloat(const int *pSrc, float *pOut, UINT nCount) +//------------------------------------------------------------------------ +{ + double tmp; + for (UINT i = 0; i < nCount; i++) { + *pOut++ = *pSrc * i2fc; /*!*/ + pSrc++; + } +} + + +VOID CSoundFile::FloatToMonoMix(const float *pIn, int *pOut, UINT nCount) +//----------------------------------------------------------------------- +{ + for (UINT i = 0; i < nCount; i++) { + *pOut++ = (int)(*pIn * f2ic); /*!*/ + pIn++; + } +} + + +// Clip and convert to 8 bit +//---GCCFIX: Asm replaced with C function +// The C version was written by Rani Assaf , I believe +DWORD Convert32To8(LPVOID lp8, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax) +{ + int vumin = *lpMin, vumax = *lpMax; + unsigned char *p = (unsigned char *)lp8; + for (UINT i=0; i MIXING_CLIPMAX) + n = MIXING_CLIPMAX; + if (n < vumin) + vumin = n; + else if (n > vumax) + vumax = n; + p[i] = (n >> (24-MIXING_ATTENUATION)) ^ 0x80; // 8-bit unsigned + } + *lpMin = vumin; + *lpMax = vumax; + return lSampleCount; +} +//---GCCFIX: Asm replaced with C function +// The C version was written by Rani Assaf , I believe +DWORD Convert32To16(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax) +{ + int vumin = *lpMin, vumax = *lpMax; + signed short *p = (signed short *)lp16; + for (UINT i=0; i MIXING_CLIPMAX) + n = MIXING_CLIPMAX; + if (n < vumin) + vumin = n; + else if (n > vumax) + vumax = n; + p[i] = n >> (16-MIXING_ATTENUATION); // 16-bit signed + } + *lpMin = vumin; + *lpMax = vumax; + return lSampleCount * 2; +} +//---GCCFIX: Asm replaced with C function +// 24-bit audio not supported. +DWORD Convert32To24(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax) +{ + return 0; +} +//---GCCFIX: Asm replaced with C function +// 32-bit audio not supported +DWORD Convert32To32(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax) +{ + return 0; +} +//---GCCFIX: Asm replaced with C function +// Will fill in later. +void InitMixBuffer(int *pBuffer, UINT nSamples) +{ + memset(pBuffer, 0, nSamples * sizeof(int)); +} + + + + + + +//---GCCFIX: Asm replaced with C function +void InterleaveFrontRear(int *pFrontBuf, int *pRearBuf, DWORD nSamples) +{ + DWORD i; + + pRearBuf[i] = pFrontBuf[1]; + for (i = 1; i < nSamples; i++) { + pRearBuf[i] = pFrontBuf[(i*2)+1]; + pFrontBuf[i] = pFrontBuf[i*2]; + } +} +//---GCCFIX: Asm replaced with C function +VOID MonoFromStereo(int *pMixBuf, UINT nSamples) +{ + UINT j; + for(UINT i = 0; i < nSamples; i++) + { + j = i << 1; + pMixBuf[i] = (pMixBuf[j] + pMixBuf[j + 1]) >> 1; + } +} + +//---GCCFIX: Asm replaced with C function +#define OFSDECAYSHIFT 8 +#define OFSDECAYMASK 0xFF +void StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs) +//--------------------------------------------------------------------------------------------------------- +{ + int rofs = *lpROfs; + int lofs = *lpLOfs; + + if ((!rofs) && (!lofs)) + { + InitMixBuffer(pBuffer, nSamples*2); + return; + } + for (UINT i=0; i>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + int x_l = (lofs + (((-lofs)>>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + rofs -= x_r; + lofs -= x_l; + pBuffer[i*2] = x_r; + pBuffer[i*2+1] = x_l; + } + *lpROfs = rofs; + *lpLOfs = lofs; +} +//---GCCFIX: Asm replaced with C function +// Will fill in later. +void EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples) +{ + int rofs = pChannel->nROfs; + int lofs = pChannel->nLOfs; + + if ((!rofs) && (!lofs)) return; + for (UINT i=0; i>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + int x_l = (lofs + (((-lofs)>>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + rofs -= x_r; + lofs -= x_l; + pBuffer[i*2] += x_r; + pBuffer[i*2+1] += x_l; + } + pChannel->nROfs = rofs; + pChannel->nLOfs = lofs; +} + + + + + +////////////////////////////////////////////////////////////////////////////////// +// Automatic Gain Control + +#ifndef NO_AGC + +// Limiter +#define MIXING_LIMITMAX (0x08100000) +#define MIXING_LIMITMIN (-MIXING_LIMITMAX) + +void CSoundFile::ProcessAGC(int count) +//------------------------------------ +{ + static DWORD gAGCRecoverCount = 0; + UINT agc = AGC(MixSoundBuffer, count, gnAGC); + // Some kind custom law, so that the AGC stays quite stable, but slowly + // goes back up if the sound level stays below a level inversely proportional + // to the AGC level. (J'me comprends) + if ((agc >= gnAGC) && (gnAGC < AGC_UNITY) && (gnVUMeter < (0xFF - (gnAGC >> (AGC_PRECISION-7))) )) + { + gAGCRecoverCount += count; + UINT agctimeout = gdwMixingFreq + gnAGC; + if (gnChannels >= 2) agctimeout <<= 1; + if (gAGCRecoverCount >= agctimeout) + { + gAGCRecoverCount = 0; + gnAGC++; + } + } else + { + gnAGC = agc; + gAGCRecoverCount = 0; + } +} + + + +void CSoundFile::ResetAGC() +//------------------------- +{ + gnAGC = AGC_UNITY; +} + +#endif // NO_AGC + diff --git a/modplug/it_defs.h b/modplug/it_defs.h new file mode 100644 index 000000000..84355a75a --- /dev/null +++ b/modplug/it_defs.h @@ -0,0 +1,135 @@ +#ifndef _ITDEFS_H_ +#define _ITDEFS_H_ + +#pragma pack(1) + +typedef struct tagITFILEHEADER +{ + DWORD id; // 0x4D504D49 + CHAR songname[26]; + BYTE hilight_minor; + BYTE hilight_major; + WORD ordnum; + WORD insnum; + WORD smpnum; + WORD patnum; + WORD cwtv; + WORD cmwt; + WORD flags; + WORD special; + BYTE globalvol; + BYTE mv; + BYTE speed; + BYTE tempo; + BYTE sep; + BYTE pwd; + WORD msglength; + DWORD msgoffset; + DWORD reserved2; + BYTE chnpan[64]; + BYTE chnvol[64]; +} ITFILEHEADER; + + +typedef struct tagITENVELOPE +{ + BYTE flags; + BYTE num; + BYTE lpb; + BYTE lpe; + BYTE slb; + BYTE sle; + BYTE data[25*3]; + BYTE reserved; +} ITENVELOPE; + +// Old Impulse Instrument Format (cmwt < 0x200) +typedef struct tagITOLDINSTRUMENT +{ + DWORD id; // IMPI = 0x49504D49 + CHAR filename[12]; // DOS file name + BYTE zero; + BYTE flags; + BYTE vls; + BYTE vle; + BYTE sls; + BYTE sle; + WORD reserved1; + WORD fadeout; + BYTE nna; + BYTE dnc; + WORD trkvers; + BYTE nos; + BYTE reserved2; + CHAR name[26]; + WORD reserved3[3]; + BYTE keyboard[240]; + BYTE volenv[200]; + BYTE nodes[50]; +} ITOLDINSTRUMENT; + + +// Impulse Instrument Format +typedef struct tagITINSTRUMENT +{ + DWORD id; + CHAR filename[12]; + BYTE zero; + BYTE nna; + BYTE dct; + BYTE dca; + WORD fadeout; + signed char pps; + BYTE ppc; + BYTE gbv; + BYTE dfp; + BYTE rv; + BYTE rp; + WORD trkvers; + BYTE nos; + BYTE reserved1; + CHAR name[26]; + BYTE ifc; + BYTE ifr; + BYTE mch; + BYTE mpr; + WORD mbank; + BYTE keyboard[240]; + ITENVELOPE volenv; + ITENVELOPE panenv; + ITENVELOPE pitchenv; + BYTE dummy[4]; // was 7, but IT v2.17 saves 554 bytes +} ITINSTRUMENT; + + +// IT Sample Format +typedef struct ITSAMPLESTRUCT +{ + DWORD id; // 0x53504D49 + CHAR filename[12]; + BYTE zero; + BYTE gvl; + BYTE flags; + BYTE vol; + CHAR name[26]; + BYTE cvt; + BYTE dfp; + DWORD length; + DWORD loopbegin; + DWORD loopend; + DWORD C5Speed; + DWORD susloopbegin; + DWORD susloopend; + DWORD samplepointer; + BYTE vis; + BYTE vid; + BYTE vir; + BYTE vit; +} ITSAMPLESTRUCT; + +#pragma pack() + +extern BYTE autovibit2xm[8]; +extern BYTE autovibxm2it[8]; + +#endif diff --git a/modplug/load_669.cpp b/modplug/load_669.cpp new file mode 100644 index 000000000..b7bd11723 --- /dev/null +++ b/modplug/load_669.cpp @@ -0,0 +1,191 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +//////////////////////////////////////////////////////////// +// 669 Composer / UNIS 669 module loader +//////////////////////////////////////////////////////////// + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +typedef struct tagFILEHEADER669 +{ + WORD sig; // 'if' or 'JN' + signed char songmessage[108]; // Song Message + BYTE samples; // number of samples (1-64) + BYTE patterns; // number of patterns (1-128) + BYTE restartpos; + BYTE orders[128]; + BYTE tempolist[128]; + BYTE breaks[128]; +} FILEHEADER669; + + +typedef struct tagSAMPLE669 +{ + BYTE filename[13]; + BYTE length[4]; // when will somebody think about DWORD align ??? + BYTE loopstart[4]; + BYTE loopend[4]; +} SAMPLE669; + + +BOOL CSoundFile::Read669(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + BOOL b669Ext; + const FILEHEADER669 *pfh = (const FILEHEADER669 *)lpStream; + const SAMPLE669 *psmp = (const SAMPLE669 *)(lpStream + 0x1F1); + DWORD dwMemPos = 0; + + if ((!lpStream) || (dwMemLength < sizeof(FILEHEADER669))) return FALSE; + if ((bswapLE16(pfh->sig) != 0x6669) && (bswapLE16(pfh->sig) != 0x4E4A)) return FALSE; + b669Ext = (bswapLE16(pfh->sig) == 0x4E4A) ? TRUE : FALSE; + if ((!pfh->samples) || (pfh->samples > 64) || (pfh->restartpos >= 128) + || (!pfh->patterns) || (pfh->patterns > 128)) return FALSE; + DWORD dontfuckwithme = 0x1F1 + pfh->samples * sizeof(SAMPLE669) + pfh->patterns * 0x600; + if (dontfuckwithme > dwMemLength) return FALSE; + for (int n = 0; n < 128; n++) + if (pfh->breaks[n] > 0x3f) + return false; + // That should be enough checking: this must be a 669 module. + m_nType = MOD_TYPE_669; + m_dwSongFlags |= SONG_LINEARSLIDES; + m_nMinPeriod = 28 << 2; + m_nMaxPeriod = 1712 << 3; + m_nDefaultTempo = 125; + m_nDefaultSpeed = 6; + m_nChannels = 8; + memcpy(m_szNames[0], pfh->songmessage, 31); + m_szNames[0][31] = 0; + m_nSamples = pfh->samples; + for (UINT nins=1; nins<=m_nSamples; nins++, psmp++) + { + DWORD len = bswapLE32(*((DWORD *)(&psmp->length))); + DWORD loopstart = bswapLE32(*((DWORD *)(&psmp->loopstart))); + DWORD loopend = bswapLE32(*((DWORD *)(&psmp->loopend))); + if (len > MAX_SAMPLE_LENGTH) len = MAX_SAMPLE_LENGTH; + if ((loopend > len) && (!loopstart)) loopend = 0; + if (loopend > len) loopend = len; + if (loopstart + 4 >= loopend) loopstart = loopend = 0; + Ins[nins].nLength = len; + Ins[nins].nLoopStart = loopstart; + Ins[nins].nLoopEnd = loopend; + if (loopend) Ins[nins].uFlags |= CHN_LOOP; + memcpy(m_szNames[nins], psmp->filename, 13); + Ins[nins].nVolume = 256; + Ins[nins].nGlobalVol = 64; + Ins[nins].nPan = 128; + } + // Song Message + m_lpszSongComments = new char[114]; + memcpy(m_lpszSongComments, pfh->songmessage, 36); + m_lpszSongComments[36] = '\015'; + m_lpszSongComments[37] = '\012'; + memcpy(m_lpszSongComments + 38, pfh->songmessage + 36, 36); + m_lpszSongComments[74] = '\015'; + m_lpszSongComments[75] = '\012'; + memcpy(m_lpszSongComments + 76, pfh->songmessage + 72, 36); + m_lpszSongComments[113] = 0; + // Reading Orders + memcpy(Order, pfh->orders, 128); + m_nRestartPos = pfh->restartpos; + if (Order[m_nRestartPos] >= pfh->patterns) m_nRestartPos = 0; + // Reading Pattern Break Locations + for (UINT npan=0; npan<8; npan++) + { + ChnSettings[npan].nPan = (npan & 1) ? 0x30 : 0xD0; + ChnSettings[npan].nVolume = 64; + } + // Reading Patterns + dwMemPos = 0x1F1 + pfh->samples * 25; + for (UINT npat=0; npatpatterns; npat++) + { + Patterns[npat] = AllocatePattern(64, m_nChannels); + if (!Patterns[npat]) break; + PatternSize[npat] = 64; + PatternAllocSize[npat] = 64; + MODCOMMAND *m = Patterns[npat]; + const BYTE *p = lpStream + dwMemPos; + for (UINT row=0; row<64; row++) + { + MODCOMMAND *mspeed = m; + if ((row == pfh->breaks[npat]) && (row != 63)) + { + for (UINT i=0; i<8; i++) + { + m[i].command = CMD_PATTERNBREAK; + m[i].param = 0; + } + } + for (UINT n=0; n<8; n++, m++, p+=3) + { + UINT note = p[0] >> 2; + UINT instr = ((p[0] & 0x03) << 4) | (p[1] >> 4); + UINT vol = p[1] & 0x0F; + if (p[0] < 0xFE) + { + m->note = note + 37; + m->instr = instr + 1; + } + if (p[0] <= 0xFE) + { + m->volcmd = VOLCMD_VOLUME; + m->vol = (vol << 2) + 2; + } + if (p[2] != 0xFF) + { + UINT command = p[2] >> 4; + UINT param = p[2] & 0x0F; + switch(command) + { + case 0x00: command = CMD_PORTAMENTOUP; break; + case 0x01: command = CMD_PORTAMENTODOWN; break; + case 0x02: command = CMD_TONEPORTAMENTO; break; + case 0x03: command = CMD_MODCMDEX; param |= 0x50; break; + case 0x04: command = CMD_VIBRATO; param |= 0x40; break; + case 0x05: if (param) command = CMD_SPEED; else command = 0; param += 2; break; + case 0x06: if (param == 0) { command = CMD_PANNINGSLIDE; param = 0xFE; } else + if (param == 1) { command = CMD_PANNINGSLIDE; param = 0xEF; } else + command = 0; + break; + default: command = 0; + } + if (command) + { + if (command == CMD_SPEED) mspeed = NULL; + m->command = command; + m->param = param; + } + } + } + if ((!row) && (mspeed)) + { + for (UINT i=0; i<8; i++) if (!mspeed[i].command) + { + mspeed[i].command = CMD_SPEED; + mspeed[i].param = pfh->tempolist[npat] + 2; + break; + } + } + } + dwMemPos += 0x600; + } + // Reading Samples + for (UINT n=1; n<=m_nSamples; n++) + { + UINT len = Ins[n].nLength; + if (dwMemPos >= dwMemLength) break; + if (len > 4) ReadSample(&Ins[n], RS_PCM8U, (LPSTR)(lpStream+dwMemPos), dwMemLength - dwMemPos); + dwMemPos += len; + } + return TRUE; +} + + diff --git a/modplug/load_amf.cpp b/modplug/load_amf.cpp new file mode 100644 index 000000000..a66954ab2 --- /dev/null +++ b/modplug/load_amf.cpp @@ -0,0 +1,421 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque + */ + +/////////////////////////////////////////////////// +// +// AMF module loader +// +// There is 2 types of AMF files: +// - ASYLUM Music Format +// - Advanced Music Format(DSM) +// +/////////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#define AMFLOG + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct _AMFFILEHEADER +{ + UCHAR szAMF[3]; + UCHAR version; + CHAR title[32]; + UCHAR numsamples; + UCHAR numorders; + USHORT numtracks; + UCHAR numchannels; +} AMFFILEHEADER; + +typedef struct _AMFSAMPLE +{ + UCHAR type; + CHAR samplename[32]; + CHAR filename[13]; + ULONG offset; + ULONG length; + USHORT c2spd; + UCHAR volume; +} AMFSAMPLE; + + +#pragma pack() + + +#ifdef AMFLOG +extern void Log(LPCSTR, ...); +#endif + +VOID AMF_Unpack(MODCOMMAND *pPat, const BYTE *pTrack, UINT nRows, UINT nChannels) +//------------------------------------------------------------------------------- +{ + UINT lastinstr = 0; + UINT nTrkSize = bswapLE16(*(USHORT *)pTrack); + nTrkSize += (UINT)pTrack[2] <<16; + pTrack += 3; + while (nTrkSize--) + { + UINT row = pTrack[0]; + UINT cmd = pTrack[1]; + UINT arg = pTrack[2]; + if (row >= nRows) break; + MODCOMMAND *m = pPat + row * nChannels; + if (cmd < 0x7F) // note+vol + { + m->note = cmd+1; + if (!m->instr) m->instr = lastinstr; + m->volcmd = VOLCMD_VOLUME; + m->vol = arg; + } else + if (cmd == 0x7F) // duplicate row + { + signed char rdelta = (signed char)arg; + int rowsrc = (int)row + (int)rdelta; + if ((rowsrc >= 0) && (rowsrc < (int)nRows)) memcpy(m, &pPat[rowsrc*nChannels],sizeof(pPat[rowsrc*nChannels])); + } else + if (cmd == 0x80) // instrument + { + m->instr = arg+1; + lastinstr = m->instr; + } else + if (cmd == 0x83) // volume + { + m->volcmd = VOLCMD_VOLUME; + m->vol = arg; + } else + // effect + { + UINT command = cmd & 0x7F; + UINT param = arg; + switch(command) + { + // 0x01: Set Speed + case 0x01: command = CMD_SPEED; break; + // 0x02: Volume Slide + // 0x0A: Tone Porta + Vol Slide + // 0x0B: Vibrato + Vol Slide + case 0x02: command = CMD_VOLUMESLIDE; + case 0x0A: if (command == 0x0A) command = CMD_TONEPORTAVOL; + case 0x0B: if (command == 0x0B) command = CMD_VIBRATOVOL; + if (param & 0x80) param = (-(signed char)param)&0x0F; + else param = (param&0x0F)<<4; + break; + // 0x04: Porta Up/Down + case 0x04: if (param & 0x80) { command = CMD_PORTAMENTOUP; param = -(signed char)param; } + else { command = CMD_PORTAMENTODOWN; } break; + // 0x06: Tone Portamento + case 0x06: command = CMD_TONEPORTAMENTO; break; + // 0x07: Tremor + case 0x07: command = CMD_TREMOR; break; + // 0x08: Arpeggio + case 0x08: command = CMD_ARPEGGIO; break; + // 0x09: Vibrato + case 0x09: command = CMD_VIBRATO; break; + // 0x0C: Pattern Break + case 0x0C: command = CMD_PATTERNBREAK; break; + // 0x0D: Position Jump + case 0x0D: command = CMD_POSITIONJUMP; break; + // 0x0F: Retrig + case 0x0F: command = CMD_RETRIG; break; + // 0x10: Offset + case 0x10: command = CMD_OFFSET; break; + // 0x11: Fine Volume Slide + case 0x11: if (param) { command = CMD_VOLUMESLIDE; + if (param & 0x80) param = 0xF0|((-(signed char)param)&0x0F); + else param = 0x0F|((param&0x0F)<<4); + } else command = 0; break; + // 0x12: Fine Portamento + // 0x16: Extra Fine Portamento + case 0x12: + case 0x16: if (param) { int mask = (command == 0x16) ? 0xE0 : 0xF0; + command = (param & 0x80) ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN; + if (param & 0x80) param = mask|((-(signed char)param)&0x0F); + else param |= mask; + } else command = 0; break; + // 0x13: Note Delay + case 0x13: command = CMD_S3MCMDEX; param = 0xD0|(param & 0x0F); break; + // 0x14: Note Cut + case 0x14: command = CMD_S3MCMDEX; param = 0xC0|(param & 0x0F); break; + // 0x15: Set Tempo + case 0x15: command = CMD_TEMPO; break; + // 0x17: Panning + case 0x17: param = (param+64)&0x7F; + if (m->command) { if (!m->volcmd) { m->volcmd = VOLCMD_PANNING; m->vol = param/2; } command = 0; } + else { command = CMD_PANNING8; } + // Unknown effects + default: command = param = 0; + } + if (command) + { + m->command = command; + m->param = param; + } + } + pTrack += 3; + } +} + + + +BOOL CSoundFile::ReadAMF(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + AMFFILEHEADER *pfh = (AMFFILEHEADER *)lpStream; + DWORD dwMemPos; + + if ((!lpStream) || (dwMemLength < 2048)) return FALSE; + if ((!strncmp((LPCTSTR)lpStream, "ASYLUM Music Format V1.0", 25)) && (dwMemLength > 4096)) + { + UINT numorders, numpats, numsamples; + + dwMemPos = 32; + numpats = lpStream[dwMemPos+3]; + numorders = lpStream[dwMemPos+4]; + numsamples = 64; + dwMemPos += 6; + if ((!numpats) || (numpats > MAX_PATTERNS) || (!numorders) + || (numpats*64*32 + 294 + 37*64 >= dwMemLength)) return FALSE; + m_nType = MOD_TYPE_AMF0; + m_nChannels = 8; + m_nInstruments = 0; + m_nSamples = 31; + m_nDefaultTempo = 125; + m_nDefaultSpeed = 6; + for (UINT iOrd=0; iOrdnFineTune = MOD2XMFineTune(lpStream[dwMemPos+22]); + psmp->nVolume = lpStream[dwMemPos+23]; + psmp->nGlobalVol = 64; + if (psmp->nVolume > 0x40) psmp->nVolume = 0x40; + psmp->nVolume <<= 2; + psmp->nLength = bswapLE32(*((LPDWORD)(lpStream+dwMemPos+25))); + psmp->nLoopStart = bswapLE32(*((LPDWORD)(lpStream+dwMemPos+29))); + psmp->nLoopEnd = psmp->nLoopStart + bswapLE32(*((LPDWORD)(lpStream+dwMemPos+33))); + if ((psmp->nLoopEnd > psmp->nLoopStart) && (psmp->nLoopEnd <= psmp->nLength)) + { + psmp->uFlags = CHN_LOOP; + } else + { + psmp->nLoopStart = psmp->nLoopEnd = 0; + } + if ((psmp->nLength) && (iSmp>31)) m_nSamples = iSmp+1; + dwMemPos += 37; + } + for (UINT iPat=0; iPatnote = 0; + + if (pin[0]) + { + p->note = pin[0] + 13; + } + p->instr = pin[1]; + p->command = pin[2]; + p->param = pin[3]; + if (p->command > 0x0F) + { + #ifdef AMFLOG + Log("0x%02X.0x%02X ?", p->command, p->param); + #endif + p->command = 0; + } + ConvertModCommand(p); + pin += 4; + p++; + } + dwMemPos += 64*32; + } + // Read samples + for (UINT iData=0; iDatanLength) + { + dwMemPos += ReadSample(psmp, RS_PCM8S, (LPCSTR)(lpStream+dwMemPos), dwMemLength); + } + } + return TRUE; + } + //////////////////////////// + // DSM/AMF + USHORT *ptracks[MAX_PATTERNS]; + DWORD sampleseekpos[MAX_SAMPLES]; + + if ((pfh->szAMF[0] != 'A') || (pfh->szAMF[1] != 'M') || (pfh->szAMF[2] != 'F') + || (pfh->version < 10) || (pfh->version > 14) || (!bswapLE16(pfh->numtracks)) + || (!pfh->numorders) || (pfh->numorders > MAX_PATTERNS) + || (!pfh->numsamples) || (pfh->numsamples > MAX_SAMPLES) + || (pfh->numchannels < 4) || (pfh->numchannels > 32)) + return FALSE; + memcpy(m_szNames[0], pfh->title, 32); + dwMemPos = sizeof(AMFFILEHEADER); + m_nType = MOD_TYPE_AMF; + m_nChannels = pfh->numchannels; + m_nSamples = pfh->numsamples; + m_nInstruments = 0; + // Setup Channel Pan Positions + if (pfh->version >= 11) + { + signed char *panpos = (signed char *)(lpStream + dwMemPos); + UINT nchannels = (pfh->version >= 13) ? 32 : 16; + for (UINT i=0; i 256) { pan = 128; ChnSettings[i].dwFlags |= CHN_SURROUND; } + ChnSettings[i].nPan = pan; + } + dwMemPos += nchannels; + } else + { + for (UINT i=0; i<16; i++) + { + ChnSettings[i].nPan = (lpStream[dwMemPos+i] & 1) ? 0x30 : 0xD0; + } + dwMemPos += 16; + } + // Get Tempo/Speed + m_nDefaultTempo = 125; + m_nDefaultSpeed = 6; + if (pfh->version >= 13) + { + if (lpStream[dwMemPos] >= 32) m_nDefaultTempo = lpStream[dwMemPos]; + if (lpStream[dwMemPos+1] <= 32) m_nDefaultSpeed = lpStream[dwMemPos+1]; + dwMemPos += 2; + } + // Setup sequence list + for (UINT iOrd=0; iOrdnumorders) + { + Order[iOrd] = iOrd; + PatternSize[iOrd] = 64; + PatternAllocSize[iOrd] = 64; + if (pfh->version >= 14) + { + PatternSize[iOrd] = bswapLE16(*(USHORT *)(lpStream+dwMemPos)); + PatternAllocSize[iOrd] = bswapLE16(*(USHORT *)(lpStream+dwMemPos)); + dwMemPos += 2; + } + ptracks[iOrd] = (USHORT *)(lpStream+dwMemPos); + dwMemPos += m_nChannels * sizeof(USHORT); + } + } + if (dwMemPos + m_nSamples * (sizeof(AMFSAMPLE)+8) > dwMemLength) return TRUE; + // Read Samples + UINT maxsampleseekpos = 0; + for (UINT iIns=0; iInssamplename, 32); + memcpy(pins->name, psh->filename, 13); + pins->nLength = bswapLE32(psh->length); + pins->nC4Speed = bswapLE16(psh->c2spd); + pins->nGlobalVol = 64; + pins->nVolume = psh->volume * 4; + if (pfh->version >= 11) + { + pins->nLoopStart = bswapLE32(*(DWORD *)(lpStream+dwMemPos)); + pins->nLoopEnd = bswapLE32(*(DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + } else + { + pins->nLoopStart = bswapLE16(*(WORD *)(lpStream+dwMemPos)); + pins->nLoopEnd = pins->nLength; + dwMemPos += 2; + } + sampleseekpos[iIns] = 0; + if ((psh->type) && (bswapLE32(psh->offset) < dwMemLength-1)) + { + sampleseekpos[iIns] = bswapLE32(psh->offset); + if (bswapLE32(psh->offset) > maxsampleseekpos) maxsampleseekpos = bswapLE32(psh->offset); + if ((pins->nLoopEnd > pins->nLoopStart + 2) + && (pins->nLoopEnd <= pins->nLength)) pins->uFlags |= CHN_LOOP; + } + } + // Read Track Mapping Table + USHORT *pTrackMap = (USHORT *)(lpStream+dwMemPos); + UINT realtrackcnt = 0; + dwMemPos += pfh->numtracks * sizeof(USHORT); + for (UINT iTrkMap=0; iTrkMapnumtracks; iTrkMap++) + { + if (realtrackcnt < pTrackMap[iTrkMap]) realtrackcnt = pTrackMap[iTrkMap]; + } + // Store tracks positions + BYTE **pTrackData = new BYTE *[realtrackcnt]; + memset(pTrackData, 0, sizeof(pTrackData)); + for (UINT iTrack=0; iTracknumorders; iPat++) + { + MODCOMMAND *p = AllocatePattern(PatternSize[iPat], m_nChannels); + if (!p) break; + Patterns[iPat] = p; + for (UINT iChn=0; iChnnumtracks)) + { + UINT realtrk = bswapLE16(pTrackMap[nTrack-1]); + if (realtrk) + { + realtrk--; + if ((realtrk < realtrackcnt) && (pTrackData[realtrk])) + { + AMF_Unpack(p+iChn, pTrackData[realtrk], PatternSize[iPat], m_nChannels); + } + } + } + } + } + delete pTrackData; + // Read Sample Data + for (UINT iSeek=1; iSeek<=maxsampleseekpos; iSeek++) + { + if (dwMemPos >= dwMemLength) break; + for (UINT iSmp=0; iSmp +*/ + +////////////////////////////////////////////// +// AMS module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct AMSFILEHEADER +{ + char szHeader[7]; // "Extreme" // changed from CHAR + BYTE verlo, verhi; // 0x??,0x01 + BYTE chncfg; + BYTE samples; + WORD patterns; + WORD orders; + BYTE vmidi; + WORD extra; +} AMSFILEHEADER; + +typedef struct AMSSAMPLEHEADER +{ + DWORD length; + DWORD loopstart; + DWORD loopend; + BYTE finetune_and_pan; + WORD samplerate; // C-2 = 8363 + BYTE volume; // 0-127 + BYTE infobyte; +} AMSSAMPLEHEADER; + + +#pragma pack() + + + +BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + BYTE pkinf[MAX_SAMPLES]; + AMSFILEHEADER *pfh = (AMSFILEHEADER *)lpStream; + DWORD dwMemPos; + UINT tmp, tmp2; + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pfh->verhi != 0x01) || (strncmp(pfh->szHeader, "Extreme", 7)) + || (!pfh->patterns) || (!pfh->orders) || (!pfh->samples) || (pfh->samples > MAX_SAMPLES) + || (pfh->patterns > MAX_PATTERNS) || (pfh->orders > MAX_ORDERS)) + { + return ReadAMS2(lpStream, dwMemLength); + } + dwMemPos = sizeof(AMSFILEHEADER) + pfh->extra; + if (dwMemPos + pfh->samples * sizeof(AMSSAMPLEHEADER) + 256 >= dwMemLength) return FALSE; + m_nType = MOD_TYPE_AMS; + m_nInstruments = 0; + m_nChannels = (pfh->chncfg & 0x1F) + 1; + m_nSamples = pfh->samples; + for (UINT nSmp=1; nSmp<=m_nSamples; nSmp++, dwMemPos += sizeof(AMSSAMPLEHEADER)) + { + AMSSAMPLEHEADER *psh = (AMSSAMPLEHEADER *)(lpStream + dwMemPos); + MODINSTRUMENT *pins = &Ins[nSmp]; + pins->nLength = psh->length; + pins->nLoopStart = psh->loopstart; + pins->nLoopEnd = psh->loopend; + pins->nGlobalVol = 64; + pins->nVolume = psh->volume << 1; + pins->nC4Speed = psh->samplerate; + pins->nPan = (psh->finetune_and_pan & 0xF0); + if (pins->nPan < 0x80) pins->nPan += 0x10; + pins->nFineTune = MOD2XMFineTune(psh->finetune_and_pan & 0x0F); + pins->uFlags = (psh->infobyte & 0x80) ? CHN_16BIT : 0; + if ((pins->nLoopEnd <= pins->nLength) && (pins->nLoopStart+4 <= pins->nLoopEnd)) pins->uFlags |= CHN_LOOP; + pkinf[nSmp] = psh->infobyte; + } + // Read Song Name + tmp = lpStream[dwMemPos++]; + if (dwMemPos + tmp + 1 >= dwMemLength) return TRUE; + tmp2 = (tmp < 32) ? tmp : 31; + if (tmp2) memcpy(m_szNames[0], lpStream+dwMemPos, tmp2); + m_szNames[0][tmp2] = 0; + dwMemPos += tmp; + // Read sample names + for (UINT sNam=1; sNam<=m_nSamples; sNam++) + { + if (dwMemPos + 32 >= dwMemLength) return TRUE; + tmp = lpStream[dwMemPos++]; + tmp2 = (tmp < 32) ? tmp : 31; + if (tmp2) memcpy(m_szNames[sNam], lpStream+dwMemPos, tmp2); + dwMemPos += tmp; + } + // Skip Channel names + for (UINT cNam=0; cNam= dwMemLength) return TRUE; + tmp = lpStream[dwMemPos++]; + dwMemPos += tmp; + } + // Read Pattern Names + m_lpszPatternNames = new char[pfh->patterns * 32]; // changed from CHAR + if (!m_lpszPatternNames) return TRUE; + m_nPatternNames = pfh->patterns; + memset(m_lpszPatternNames, 0, m_nPatternNames * 32); + for (UINT pNam=0; pNam < m_nPatternNames; pNam++) + { + if (dwMemPos + 32 >= dwMemLength) return TRUE; + tmp = lpStream[dwMemPos++]; + tmp2 = (tmp < 32) ? tmp : 31; + if (tmp2) memcpy(m_lpszPatternNames+pNam*32, lpStream+dwMemPos, tmp2); + dwMemPos += tmp; + } + // Read Song Comments + tmp = *((WORD *)(lpStream+dwMemPos)); + dwMemPos += 2; + if (dwMemPos + tmp >= dwMemLength) return TRUE; + if (tmp) + { + m_lpszSongComments = new char[tmp+1]; // changed from CHAR + if (!m_lpszSongComments) return TRUE; + memset(m_lpszSongComments, 0, tmp+1); + memcpy(m_lpszSongComments, lpStream + dwMemPos, tmp); + dwMemPos += tmp; + } + // Read Order List + for (UINT iOrd=0; iOrdorders; iOrd++, dwMemPos += 2) + { + UINT n = *((WORD *)(lpStream+dwMemPos)); + Order[iOrd] = (BYTE)n; + } + // Read Patterns + for (UINT iPat=0; iPatpatterns; iPat++) + { + if (dwMemPos + 4 >= dwMemLength) return TRUE; + UINT len = *((DWORD *)(lpStream + dwMemPos)); + dwMemPos += 4; + if ((len >= dwMemLength) || (dwMemPos + len > dwMemLength)) return TRUE; + PatternSize[iPat] = 64; + PatternAllocSize[iPat] = 64; + MODCOMMAND *m = AllocatePattern(PatternSize[iPat], m_nChannels); + if (!m) return TRUE; + Patterns[iPat] = m; + const BYTE *p = lpStream + dwMemPos; + UINT row = 0, i = 0; + while ((row < PatternSize[iPat]) && (i+2 < len)) + { + BYTE b0 = p[i++]; + BYTE b1 = p[i++]; + BYTE b2 = 0; + UINT ch = b0 & 0x3F; + // Note+Instr + if (!(b0 & 0x40)) + { + b2 = p[i++]; + if (ch < m_nChannels) + { + if (b1 & 0x7F) m[ch].note = (b1 & 0x7F) + 25; + m[ch].instr = b2; + } + if (b1 & 0x80) + { + b0 |= 0x40; + b1 = p[i++]; + } + } + // Effect + if (b0 & 0x40) + { + anothercommand: + if (b1 & 0x40) + { + if (ch < m_nChannels) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = b1 & 0x3F; + } + } else + { + b2 = p[i++]; + if (ch < m_nChannels) + { + UINT cmd = b1 & 0x3F; + if (cmd == 0x0C) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = b2 >> 1; + } else + if (cmd == 0x0E) + { + if (!m[ch].command) + { + UINT command = CMD_S3MCMDEX; + UINT param = b2; + switch(param & 0xF0) + { + case 0x00: if (param & 0x08) { param &= 0x07; param |= 0x90; } else {command=param=0;} break; + case 0x10: command = CMD_PORTAMENTOUP; param |= 0xF0; break; + case 0x20: command = CMD_PORTAMENTODOWN; param |= 0xF0; break; + case 0x30: param = (param & 0x0F) | 0x10; break; + case 0x40: param = (param & 0x0F) | 0x30; break; + case 0x50: param = (param & 0x0F) | 0x20; break; + case 0x60: param = (param & 0x0F) | 0xB0; break; + case 0x70: param = (param & 0x0F) | 0x40; break; + case 0x90: command = CMD_RETRIG; param &= 0x0F; break; + case 0xA0: if (param & 0x0F) { command = CMD_VOLUMESLIDE; param = (param << 4) | 0x0F; } else command=param=0; break; + case 0xB0: if (param & 0x0F) { command = CMD_VOLUMESLIDE; param |= 0xF0; } else command=param=0; break; + } + m[ch].command = command; + m[ch].param = param; + } + } else + { + m[ch].command = cmd; + m[ch].param = b2; + ConvertModCommand(&m[ch]); + } + } + } + if (b1 & 0x80) + { + b1 = p[i++]; + if (i <= len) goto anothercommand; + } + } + if (b0 & 0x80) + { + row++; + m += m_nChannels; + } + } + dwMemPos += len; + } + // Read Samples + for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++) if (Ins[iSmp].nLength) + { + if (dwMemPos >= dwMemLength - 9) return TRUE; + UINT flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_AMS16 : RS_AMS8; + dwMemPos += ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); + } + return TRUE; +} + + +///////////////////////////////////////////////////////////////////// +// AMS 2.2 loader + +#pragma pack(1) + +typedef struct AMS2FILEHEADER +{ + DWORD dwHdr1; // AMShdr + WORD wHdr2; + BYTE b1A; // 0x1A + BYTE titlelen; // 30-bytes max + CHAR szTitle[30]; // [titlelen] +} AMS2FILEHEADER; + +typedef struct AMS2SONGHEADER +{ + WORD version; + BYTE instruments; + WORD patterns; + WORD orders; + WORD bpm; + BYTE speed; + BYTE channels; + BYTE commands; + BYTE rows; + WORD flags; +} AMS2SONGHEADER; + +typedef struct AMS2INSTRUMENT +{ + BYTE samples; + BYTE notemap[120]; +} AMS2INSTRUMENT; + +typedef struct AMS2ENVELOPE +{ + BYTE speed; + BYTE sustain; + BYTE loopbegin; + BYTE loopend; + BYTE points; + BYTE info[3]; +} AMS2ENVELOPE; + +typedef struct AMS2SAMPLE +{ + DWORD length; + DWORD loopstart; + DWORD loopend; + WORD frequency; + BYTE finetune; + WORD c4speed; + CHAR transpose; + BYTE volume; + BYTE flags; +} AMS2SAMPLE; + + +#pragma pack() + + +BOOL CSoundFile::ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength) +//------------------------------------------------------------ +{ + AMS2FILEHEADER *pfh = (AMS2FILEHEADER *)lpStream; + AMS2SONGHEADER *psh; + DWORD dwMemPos; + BYTE smpmap[16]; + BYTE packedsamples[MAX_SAMPLES]; + + if ((pfh->dwHdr1 != 0x68534D41) || (pfh->wHdr2 != 0x7264) + || (pfh->b1A != 0x1A) || (pfh->titlelen > 30)) return FALSE; + dwMemPos = pfh->titlelen + 8; + psh = (AMS2SONGHEADER *)(lpStream + dwMemPos); + if (((psh->version & 0xFF00) != 0x0200) || (!psh->instruments) + || (psh->instruments > MAX_INSTRUMENTS) || (!psh->patterns) || (!psh->orders)) return FALSE; + dwMemPos += sizeof(AMS2SONGHEADER); + if (pfh->titlelen) + { + memcpy(m_szNames, pfh->szTitle, pfh->titlelen); + m_szNames[0][pfh->titlelen] = 0; + } + m_nType = MOD_TYPE_AMS; + m_nChannels = 32; + m_nDefaultTempo = psh->bpm >> 8; + m_nDefaultSpeed = psh->speed; + m_nInstruments = psh->instruments; + m_nSamples = 0; + m_dwSongFlags |= SONG_INSTRUMENTMODE; + if (psh->flags & 0x40) m_dwSongFlags |= SONG_LINEARSLIDES; + for (UINT nIns=1; nIns<=m_nInstruments; nIns++) + { + UINT insnamelen = lpStream[dwMemPos]; + CHAR *pinsname = (CHAR *)(lpStream+dwMemPos+1); + dwMemPos += insnamelen + 1; + AMS2INSTRUMENT *pins = (AMS2INSTRUMENT *)(lpStream + dwMemPos); + dwMemPos += sizeof(AMS2INSTRUMENT); + if (dwMemPos + 1024 >= dwMemLength) return TRUE; + AMS2ENVELOPE *volenv, *panenv, *pitchenv; + volenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); + dwMemPos += 5 + volenv->points*3; + panenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); + dwMemPos += 5 + panenv->points*3; + pitchenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); + dwMemPos += 5 + pitchenv->points*3; + INSTRUMENTHEADER *penv = new INSTRUMENTHEADER; + if (!penv) return TRUE; + memset(smpmap, 0, sizeof(smpmap)); + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + for (UINT ismpmap=0; ismpmapsamples; ismpmap++) + { + if ((ismpmap >= 16) || (m_nSamples+1 >= MAX_SAMPLES)) break; + m_nSamples++; + smpmap[ismpmap] = m_nSamples; + } + penv->nGlobalVol = 64; + penv->nPan = 128; + penv->nPPC = 60; + Headers[nIns] = penv; + if (insnamelen) + { + if (insnamelen > 31) insnamelen = 31; + memcpy(penv->name, pinsname, insnamelen); + penv->name[insnamelen] = 0; + } + for (UINT inotemap=0; inotemap<120; inotemap++) + { + penv->NoteMap[inotemap] = inotemap+1; + penv->Keyboard[inotemap] = smpmap[pins->notemap[inotemap] & 0x0F]; + } + // Volume Envelope + { + UINT pos = 0; + penv->VolEnv.nNodes = (volenv->points > 16) ? 16 : volenv->points; + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = volenv->sustain; + penv->VolEnv.nLoopStart = volenv->loopbegin; + penv->VolEnv.nLoopEnd = volenv->loopend; + for (UINT i=0; iVolEnv.nNodes; i++) + { + penv->VolEnv.Values[i] = (BYTE)((volenv->info[i*3+2] & 0x7F) >> 1); + pos += volenv->info[i*3] + ((volenv->info[i*3+1] & 1) << 8); + penv->VolEnv.Ticks[i] = (WORD)pos; + } + } + penv->nFadeOut = (((lpStream[dwMemPos+2] & 0x0F) << 8) | (lpStream[dwMemPos+1])) << 3; + UINT envflags = lpStream[dwMemPos+3]; + if (envflags & 0x01) penv->dwFlags |= ENV_VOLLOOP; + if (envflags & 0x02) penv->dwFlags |= ENV_VOLSUSTAIN; + if (envflags & 0x04) penv->dwFlags |= ENV_VOLUME; + dwMemPos += 5; + // Read Samples + for (UINT ismp=0; ismpsamples; ismp++) + { + MODINSTRUMENT *psmp = ((ismp < 16) && (smpmap[ismp])) ? &Ins[smpmap[ismp]] : NULL; + UINT smpnamelen = lpStream[dwMemPos]; + if ((psmp) && (smpnamelen) && (smpnamelen <= 22)) + { + memcpy(m_szNames[smpmap[ismp]], lpStream+dwMemPos+1, smpnamelen); + } + dwMemPos += smpnamelen + 1; + if (psmp) + { + AMS2SAMPLE *pams = (AMS2SAMPLE *)(lpStream+dwMemPos); + psmp->nGlobalVol = 64; + psmp->nPan = 128; + psmp->nLength = pams->length; + psmp->nLoopStart = pams->loopstart; + psmp->nLoopEnd = pams->loopend; + psmp->nC4Speed = pams->c4speed; + psmp->RelativeTone = pams->transpose; + psmp->nVolume = pams->volume / 2; + packedsamples[smpmap[ismp]] = pams->flags; + if (pams->flags & 0x04) psmp->uFlags |= CHN_16BIT; + if (pams->flags & 0x08) psmp->uFlags |= CHN_LOOP; + if (pams->flags & 0x10) psmp->uFlags |= CHN_PINGPONGLOOP; + } + dwMemPos += sizeof(AMS2SAMPLE); + } + } + if (dwMemPos + 256 >= dwMemLength) return TRUE; + // Comments + { + UINT composernamelen = lpStream[dwMemPos]; + if (composernamelen) + { + m_lpszSongComments = new char[composernamelen+1]; // changed from CHAR + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos+1, composernamelen); + m_lpszSongComments[composernamelen] = 0; + } + } + dwMemPos += composernamelen + 1; + // channel names + for (UINT i=0; i<32; i++) + { + UINT chnnamlen = lpStream[dwMemPos]; + if ((chnnamlen) && (chnnamlen < MAX_CHANNELNAME)) + { + memcpy(ChnSettings[i].szName, lpStream+dwMemPos+1, chnnamlen); + } + dwMemPos += chnnamlen + 1; + if (dwMemPos + chnnamlen + 256 >= dwMemLength) return TRUE; + } + // packed comments (ignored) + UINT songtextlen = *((LPDWORD)(lpStream+dwMemPos)); + dwMemPos += songtextlen; + if (dwMemPos + 256 >= dwMemLength) return TRUE; + } + // Order List + { + for (UINT i=0; i= dwMemLength) return TRUE; + if (i < psh->orders) + { + Order[i] = lpStream[dwMemPos]; + dwMemPos += 2; + } + } + } + // Pattern Data + for (UINT ipat=0; ipatpatterns; ipat++) + { + if (dwMemPos+8 >= dwMemLength) return TRUE; + UINT packedlen = *((LPDWORD)(lpStream+dwMemPos)); + UINT numrows = 1 + (UINT)(lpStream[dwMemPos+4]); + //UINT patchn = 1 + (UINT)(lpStream[dwMemPos+5] & 0x1F); + //UINT patcmds = 1 + (UINT)(lpStream[dwMemPos+5] >> 5); + UINT patnamlen = lpStream[dwMemPos+6]; + dwMemPos += 4; + if ((ipat < MAX_PATTERNS) && (packedlen < dwMemLength-dwMemPos) && (numrows >= 8)) + { + if ((patnamlen) && (patnamlen < MAX_PATTERNNAME)) + { + char s[MAX_PATTERNNAME]; // changed from CHAR + memcpy(s, lpStream+dwMemPos+3, patnamlen); + s[patnamlen] = 0; + SetPatternName(ipat, s); + } + PatternSize[ipat] = numrows; + PatternAllocSize[ipat] = numrows; + Patterns[ipat] = AllocatePattern(numrows, m_nChannels); + if (!Patterns[ipat]) return TRUE; + // Unpack Pattern Data + LPCBYTE psrc = lpStream + dwMemPos; + UINT pos = 3 + patnamlen; + UINT row = 0; + while ((pos < packedlen) && (row < numrows)) + { + MODCOMMAND *m = Patterns[ipat] + row * m_nChannels; + UINT byte1 = psrc[pos++]; + UINT ch = byte1 & 0x1F; + // Read Note + Instr + if (!(byte1 & 0x40)) + { + UINT byte2 = psrc[pos++]; + UINT note = byte2 & 0x7F; + if (note) m[ch].note = (note > 1) ? (note-1) : 0xFF; + m[ch].instr = psrc[pos++]; + // Read Effect + while (byte2 & 0x80) + { + byte2 = psrc[pos++]; + if (byte2 & 0x40) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = byte2 & 0x3F; + } else + { + UINT command = byte2 & 0x3F; + UINT param = psrc[pos++]; + if (command == 0x0C) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = param / 2; + } else + if (command < 0x10) + { + m[ch].command = command; + m[ch].param = param; + ConvertModCommand(&m[ch]); + } else + { + // TODO: AMS effects + } + } + } + } + if (byte1 & 0x80) row++; + } + } + dwMemPos += packedlen; + } + // Read Samples + for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++) if (Ins[iSmp].nLength) + { + if (dwMemPos >= dwMemLength - 9) return TRUE; + UINT flags; + if (packedsamples[iSmp] & 0x03) + { + flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_AMS16 : RS_AMS8; + } else + { + flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S; + } + dwMemPos += ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); + } + return TRUE; +} + + +///////////////////////////////////////////////////////////////////// +// AMS Sample unpacking + +void AMSUnpack(const char *psrc, UINT inputlen, char *pdest, UINT dmax, char packcharacter) +{ + UINT tmplen = dmax; + signed char *amstmp = new signed char[tmplen]; + + if (!amstmp) return; + // Unpack Loop + { + signed char *p = amstmp; + UINT i=0, j=0; + while ((i < inputlen) && (j < tmplen)) + { + signed char ch = psrc[i++]; + if (ch == packcharacter) + { + BYTE ch2 = psrc[i++]; + if (ch2) + { + ch = psrc[i++]; + while (ch2--) + { + p[j++] = ch; + if (j >= tmplen) break; + } + } else p[j++] = packcharacter; + } else p[j++] = ch; + } + } + // Bit Unpack Loop + { + signed char *p = amstmp; + UINT bitcount = 0x80, dh; + UINT k=0; + for (UINT i=0; i> ((dh+8-count) & 7)) & 0xFF; + bitcount = ((bitcount|(bitcount<<8)) >> 1) & 0xFF; + pdest[k++] |= bl; + if (k >= dmax) + { + k = 0; + dh++; + } + } + bitcount = ((bitcount|(bitcount<<8)) >> dh) & 0xFF; + } + } + // Delta Unpack + { + signed char old = 0; + for (UINT i=0; i, + * Adam Goode (endian and char fixes for PPC) +*/ + +/////////////////////////////////////////////////////////////// +// +// DigiBooster Pro Module Loader (*.dbm) +// +// Note: this loader doesn't handle multiple songs +// +/////////////////////////////////////////////////////////////// + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#define DBM_FILE_MAGIC 0x304d4244 +#define DBM_ID_NAME 0x454d414e +#define DBM_NAMELEN 0x2c000000 +#define DBM_ID_INFO 0x4f464e49 +#define DBM_INFOLEN 0x0a000000 +#define DBM_ID_SONG 0x474e4f53 +#define DBM_ID_INST 0x54534e49 +#define DBM_ID_VENV 0x564e4556 +#define DBM_ID_PATT 0x54544150 +#define DBM_ID_SMPL 0x4c504d53 + +#pragma pack(1) + +typedef struct DBMFILEHEADER +{ + DWORD dbm_id; // "DBM0" = 0x304d4244 + WORD trkver; // Tracker version: 02.15 + WORD reserved; + DWORD name_id; // "NAME" = 0x454d414e + DWORD name_len; // name length: always 44 + CHAR songname[44]; + DWORD info_id; // "INFO" = 0x4f464e49 + DWORD info_len; // 0x0a000000 + WORD instruments; + WORD samples; + WORD songs; + WORD patterns; + WORD channels; + DWORD song_id; // "SONG" = 0x474e4f53 + DWORD song_len; + CHAR songname2[44]; + WORD orders; +// WORD orderlist[0]; // orderlist[orders] in words +} DBMFILEHEADER; + +typedef struct DBMINSTRUMENT +{ + CHAR name[30]; + WORD sampleno; + WORD volume; + DWORD finetune; + DWORD loopstart; + DWORD looplen; + WORD panning; + WORD flags; +} DBMINSTRUMENT; + +typedef struct DBMENVELOPE +{ + WORD instrument; + BYTE flags; + BYTE numpoints; + BYTE sustain1; + BYTE loopbegin; + BYTE loopend; + BYTE sustain2; + WORD volenv[2*32]; +} DBMENVELOPE; + +typedef struct DBMPATTERN +{ + WORD rows; + DWORD packedsize; + BYTE patterndata[2]; // [packedsize] +} DBMPATTERN; + +typedef struct DBMSAMPLE +{ + DWORD flags; + DWORD samplesize; + BYTE sampledata[2]; // [samplesize] +} DBMSAMPLE; + +#pragma pack() + + +BOOL CSoundFile::ReadDBM(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + DBMFILEHEADER *pfh = (DBMFILEHEADER *)lpStream; + DWORD dwMemPos; + UINT nOrders, nSamples, nInstruments, nPatterns; + + if ((!lpStream) || (dwMemLength <= sizeof(DBMFILEHEADER)) || (!pfh->channels) + || (pfh->dbm_id != DBM_FILE_MAGIC) || (!pfh->songs) || (pfh->song_id != DBM_ID_SONG) + || (pfh->name_id != DBM_ID_NAME) || (pfh->name_len != DBM_NAMELEN) + || (pfh->info_id != DBM_ID_INFO) || (pfh->info_len != DBM_INFOLEN)) return FALSE; + dwMemPos = sizeof(DBMFILEHEADER); + nOrders = bswapBE16(pfh->orders); + if (dwMemPos + 2 * nOrders + 8*3 >= dwMemLength) return FALSE; + nInstruments = bswapBE16(pfh->instruments); + nSamples = bswapBE16(pfh->samples); + nPatterns = bswapBE16(pfh->patterns); + m_nType = MOD_TYPE_DBM; + m_nChannels = bswapBE16(pfh->channels); + if (m_nChannels < 4) m_nChannels = 4; + if (m_nChannels > 64) m_nChannels = 64; + memcpy(m_szNames[0], (pfh->songname[0]) ? pfh->songname : pfh->songname2, 32); + m_szNames[0][31] = 0; + for (UINT iOrd=0; iOrd < nOrders; iOrd++) + { + Order[iOrd] = lpStream[dwMemPos+iOrd*2+1]; + if (iOrd >= MAX_ORDERS-2) break; + } + dwMemPos += 2*nOrders; + while (dwMemPos + 10 < dwMemLength) + { + DWORD chunk_id = ((LPDWORD)(lpStream+dwMemPos))[0]; + DWORD chunk_size = bswapBE32(((LPDWORD)(lpStream+dwMemPos))[1]); + DWORD chunk_pos; + + dwMemPos += 8; + chunk_pos = dwMemPos; + if ((dwMemPos + chunk_size > dwMemLength) || (chunk_size > dwMemLength)) break; + dwMemPos += chunk_size; + // Instruments + if (chunk_id == DBM_ID_INST) + { + if (nInstruments >= MAX_INSTRUMENTS) nInstruments = MAX_INSTRUMENTS-1; + for (UINT iIns=0; iIns dwMemPos) break; + if ((penv = new INSTRUMENTHEADER) == NULL) break; + pih = (DBMINSTRUMENT *)(lpStream+chunk_pos); + nsmp = bswapBE16(pih->sampleno); + psmp = ((nsmp) && (nsmp < MAX_SAMPLES)) ? &Ins[nsmp] : NULL; + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + memcpy(penv->name, pih->name, 30); + if (psmp) + { + memcpy(m_szNames[nsmp], pih->name, 30); + m_szNames[nsmp][30] = 0; + } + Headers[iIns+1] = penv; + penv->nFadeOut = 1024; // ??? + penv->nGlobalVol = 64; + penv->nPan = bswapBE16(pih->panning); + if ((penv->nPan) && (penv->nPan < 256)) + penv->dwFlags = ENV_SETPANNING; + else + penv->nPan = 128; + penv->nPPC = 5*12; + for (UINT i=0; i<120; i++) + { + penv->Keyboard[i] = nsmp; + penv->NoteMap[i] = i+1; + } + // Sample Info + if (psmp) + { + DWORD sflags = bswapBE16(pih->flags); + psmp->nVolume = bswapBE16(pih->volume) * 4; + if ((!psmp->nVolume) || (psmp->nVolume > 256)) psmp->nVolume = 256; + psmp->nGlobalVol = 64; + psmp->nC4Speed = bswapBE32(pih->finetune); + int f2t = FrequencyToTranspose(psmp->nC4Speed); + psmp->RelativeTone = f2t >> 7; + psmp->nFineTune = f2t & 0x7F; + if ((pih->looplen) && (sflags & 3)) + { + psmp->nLoopStart = bswapBE32(pih->loopstart); + psmp->nLoopEnd = psmp->nLoopStart + bswapBE32(pih->looplen); + psmp->uFlags |= CHN_LOOP; + psmp->uFlags &= ~CHN_PINGPONGLOOP; + if (sflags & 2) psmp->uFlags |= CHN_PINGPONGLOOP; + } + } + chunk_pos += sizeof(DBMINSTRUMENT); + m_nInstruments = iIns+1; + } + m_dwSongFlags |= SONG_INSTRUMENTMODE; + } else + // Volume Envelopes + if (chunk_id == DBM_ID_VENV) + { + UINT nEnvelopes = lpStream[chunk_pos+1]; + + chunk_pos += 2; + for (UINT iEnv=0; iEnv dwMemPos) break; + peh = (DBMENVELOPE *)(lpStream+chunk_pos); + nins = bswapBE16(peh->instrument); + if ((nins) && (nins < MAX_INSTRUMENTS) && (Headers[nins]) && (peh->numpoints)) + { + INSTRUMENTHEADER *penv = Headers[nins]; + + if (peh->flags & 1) penv->dwFlags |= ENV_VOLUME; + if (peh->flags & 2) penv->dwFlags |= ENV_VOLSUSTAIN; + if (peh->flags & 4) penv->dwFlags |= ENV_VOLLOOP; + penv->VolEnv.nNodes = peh->numpoints + 1; + if (penv->VolEnv.nNodes > MAX_ENVPOINTS) penv->VolEnv.nNodes = MAX_ENVPOINTS; + penv->VolEnv.nLoopStart = peh->loopbegin; + penv->VolEnv.nLoopEnd = peh->loopend; + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = peh->sustain1; + for (UINT i=0; iVolEnv.nNodes; i++) + { + penv->VolEnv.Ticks[i] = bswapBE16(peh->volenv[i*2]); + penv->VolEnv.Values[i] = (BYTE)bswapBE16(peh->volenv[i*2+1]); + } + } + chunk_pos += sizeof(DBMENVELOPE); + } + } else + // Packed Pattern Data + if (chunk_id == DBM_ID_PATT) + { + if (nPatterns > MAX_PATTERNS) nPatterns = MAX_PATTERNS; + for (UINT iPat=0; iPat dwMemPos) break; + pph = (DBMPATTERN *)(lpStream+chunk_pos); + pksize = bswapBE32(pph->packedsize); + if ((chunk_pos + pksize + 6 > dwMemPos) || (pksize > dwMemPos)) break; + nRows = bswapBE16(pph->rows); + if ((nRows >= 4) && (nRows <= 256)) + { + MODCOMMAND *m = AllocatePattern(nRows, m_nChannels); + if (m) + { + LPBYTE pkdata = (LPBYTE)&pph->patterndata; + UINT row = 0; + UINT i = 0; + + PatternSize[iPat] = nRows; + PatternAllocSize[iPat] = nRows; + Patterns[iPat] = m; + while ((i+3> 4)*12) + (note & 0x0F) + 13; + } + m[ch].note = note; + } + if (b & 0x02) m[ch].instr = pkdata[i++]; + if (b & 0x3C) + { + UINT cmd1 = 0xFF, param1 = 0, cmd2 = 0xFF, param2 = 0; + if (b & 0x04) cmd1 = (UINT)pkdata[i++]; + if (b & 0x08) param1 = pkdata[i++]; + if (b & 0x10) cmd2 = (UINT)pkdata[i++]; + if (b & 0x20) param2 = pkdata[i++]; + if (cmd1 == 0x0C) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = param1; + cmd1 = 0xFF; + } else + if (cmd2 == 0x0C) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = param2; + cmd2 = 0xFF; + } + if ((cmd1 > 0x13) || ((cmd1 >= 0x10) && (cmd2 < 0x10))) + { + cmd1 = cmd2; + param1 = param2; + cmd2 = 0xFF; + } + if (cmd1 <= 0x13) + { + m[ch].command = cmd1; + m[ch].param = param1; + ConvertModCommand(&m[ch]); + } + } + } else + { + if (b & 0x01) i++; + if (b & 0x02) i++; + if (b & 0x04) i++; + if (b & 0x08) i++; + if (b & 0x10) i++; + if (b & 0x20) i++; + } + } else + { + row++; + m += m_nChannels; + } + } + } + } + chunk_pos += 6 + pksize; + } + } else + // Reading Sample Data + if (chunk_id == DBM_ID_SMPL) + { + if (nSamples >= MAX_SAMPLES) nSamples = MAX_SAMPLES-1; + m_nSamples = nSamples; + for (UINT iSmp=1; iSmp<=nSamples; iSmp++) + { + MODINSTRUMENT *pins; + DBMSAMPLE *psh; + DWORD samplesize; + DWORD sampleflags; + + if (chunk_pos + sizeof(DBMSAMPLE) >= dwMemPos) break; + psh = (DBMSAMPLE *)(lpStream+chunk_pos); + chunk_pos += 8; + samplesize = bswapBE32(psh->samplesize); + sampleflags = bswapBE32(psh->flags); + pins = &Ins[iSmp]; + pins->nLength = samplesize; + if (sampleflags & 2) + { + pins->uFlags |= CHN_16BIT; + samplesize <<= 1; + } + if ((chunk_pos+samplesize > dwMemPos) || (samplesize > dwMemLength)) break; + if (sampleflags & 3) + { + ReadSample(pins, (pins->uFlags & CHN_16BIT) ? RS_PCM16M : RS_PCM8S, + (LPSTR)(psh->sampledata), samplesize); + } + chunk_pos += samplesize; + } + } + } + return TRUE; +} + diff --git a/modplug/load_dmf.cpp b/modplug/load_dmf.cpp new file mode 100644 index 000000000..fbcdb8f81 --- /dev/null +++ b/modplug/load_dmf.cpp @@ -0,0 +1,607 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +/////////////////////////////////////////////////////// +// DMF DELUSION DIGITAL MUSIC FILEFORMAT (X-Tracker) // +/////////////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#define DMFLOG + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct DMFHEADER +{ + DWORD id; // "DDMF" = 0x464d4444 + BYTE version; // 4 + CHAR trackername[8]; // "XTRACKER" + CHAR songname[30]; + CHAR composer[20]; + BYTE date[3]; +} DMFHEADER; + +typedef struct DMFINFO +{ + DWORD id; // "INFO" + DWORD infosize; +} DMFINFO; + +typedef struct DMFSEQU +{ + DWORD id; // "SEQU" + DWORD seqsize; + WORD loopstart; + WORD loopend; + WORD sequ[2]; +} DMFSEQU; + +typedef struct DMFPATT +{ + DWORD id; // "PATT" + DWORD patsize; + WORD numpat; // 1-1024 + BYTE tracks; + BYTE firstpatinfo; +} DMFPATT; + +typedef struct DMFTRACK +{ + BYTE tracks; + BYTE beat; // [hi|lo] -> hi=ticks per beat, lo=beats per measure + WORD ticks; // max 512 + DWORD jmpsize; +} DMFTRACK; + +typedef struct DMFSMPI +{ + DWORD id; + DWORD size; + BYTE samples; +} DMFSMPI; + +typedef struct DMFSAMPLE +{ + DWORD len; + DWORD loopstart; + DWORD loopend; + WORD c3speed; + BYTE volume; + BYTE flags; +} DMFSAMPLE; + +#pragma pack() + + +#ifdef DMFLOG +extern void Log(LPCSTR s, ...); +#endif + + +BOOL CSoundFile::ReadDMF(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + DMFHEADER *pfh = (DMFHEADER *)lpStream; + DMFINFO *psi; + DMFSEQU *sequ; + DWORD dwMemPos; + BYTE infobyte[32]; + BYTE smplflags[MAX_SAMPLES]; + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pfh->id != 0x464d4444) || (!pfh->version) || (pfh->version & 0xF0)) return FALSE; + dwMemPos = 66; + memcpy(m_szNames[0], pfh->songname, 30); + m_szNames[0][30] = 0; + m_nType = MOD_TYPE_DMF; + m_nChannels = 0; +#ifdef DMFLOG + Log("DMF version %d: \"%s\": %d bytes (0x%04X)\n", pfh->version, m_szNames[0], dwMemLength, dwMemLength); +#endif + while (dwMemPos + 7 < dwMemLength) + { + DWORD id = *((LPDWORD)(lpStream+dwMemPos)); + + switch(id) + { + // "INFO" + case 0x4f464e49: + // "CMSG" + case 0x47534d43: + psi = (DMFINFO *)(lpStream+dwMemPos); + if (id == 0x47534d43) dwMemPos++; + if ((psi->infosize > dwMemLength) || (psi->infosize + dwMemPos + 8 > dwMemLength)) goto dmfexit; + if ((psi->infosize >= 8) && (!m_lpszSongComments)) + { + m_lpszSongComments = new char[psi->infosize]; // changed from CHAR + if (m_lpszSongComments) + { + for (UINT i=0; iinfosize-1; i++) + { + CHAR c = lpStream[dwMemPos+8+i]; + if ((i % 40) == 39) + m_lpszSongComments[i] = 0x0d; + else + m_lpszSongComments[i] = (c < ' ') ? ' ' : c; + } + m_lpszSongComments[psi->infosize-1] = 0; + } + } + dwMemPos += psi->infosize + 8 - 1; + break; + + // "SEQU" + case 0x55514553: + sequ = (DMFSEQU *)(lpStream+dwMemPos); + if ((sequ->seqsize >= dwMemLength) || (dwMemPos + sequ->seqsize + 12 > dwMemLength)) goto dmfexit; + { + UINT nseq = sequ->seqsize >> 1; + if (nseq >= MAX_ORDERS-1) nseq = MAX_ORDERS-1; + if (sequ->loopstart < nseq) m_nRestartPos = sequ->loopstart; + for (UINT i=0; isequ[i]; + } + dwMemPos += sequ->seqsize + 8; + break; + + // "PATT" + case 0x54544150: + if (!m_nChannels) + { + DMFPATT *patt = (DMFPATT *)(lpStream+dwMemPos); + UINT numpat; + DWORD dwPos = dwMemPos + 11; + if ((patt->patsize >= dwMemLength) || (dwMemPos + patt->patsize + 8 > dwMemLength)) goto dmfexit; + numpat = patt->numpat; + if (numpat > MAX_PATTERNS) numpat = MAX_PATTERNS; + m_nChannels = patt->tracks; + if (m_nChannels < patt->firstpatinfo) m_nChannels = patt->firstpatinfo; + if (m_nChannels > 32) m_nChannels = 32; + if (m_nChannels < 4) m_nChannels = 4; + for (UINT npat=0; npattracks, pt->ticks); + #endif + UINT tracks = pt->tracks; + if (tracks > 32) tracks = 32; + UINT ticks = pt->ticks; + if (ticks > 256) ticks = 256; + if (ticks < 16) ticks = 16; + dwPos += 8; + if ((pt->jmpsize >= dwMemLength) || (dwPos + pt->jmpsize + 4 >= dwMemLength)) break; + PatternSize[npat] = (WORD)ticks; + PatternAllocSize[npat] = (WORD)ticks; + MODCOMMAND *m = AllocatePattern(PatternSize[npat], m_nChannels); + if (!m) goto dmfexit; + Patterns[npat] = m; + DWORD d = dwPos; + dwPos += pt->jmpsize; + UINT ttype = 1; + UINT tempo = 125; + UINT glbinfobyte = 0; + UINT pbeat = (pt->beat & 0xf0) ? pt->beat>>4 : 8; + BOOL tempochange = (pt->beat & 0xf0) ? TRUE : FALSE; + memset(infobyte, 0, sizeof(infobyte)); + for (UINT row=0; row>4; tempochange = ttype; break; + #ifdef DMFLOG + default: if (info) Log("GLB: %02X.%02X\n", info, infoval); + #endif + } + } else + { + glbinfobyte--; + } + // Parse channels + for (UINT i=0; i>2; + } + // Effect 1 + if (info & 0x08) + { + BYTE efx = lpStream[d++]; + BYTE eval = lpStream[d++]; + switch(efx) + { + // 1: Key Off + case 1: if (!cmd.note) cmd.note = 0xFE; break; + // 2: Set Loop + // 4: Sample Delay + case 4: if (eval&0xe0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>5)|0xD0; } break; + // 5: Retrig + case 5: if (eval&0xe0) { cmd.command = CMD_RETRIG; cmd.param = (eval>>5); } break; + // 6: Offset + case 6: cmd.command = CMD_OFFSET; cmd.param = eval; break; + #ifdef DMFLOG + default: Log("FX1: %02X.%02X\n", efx, eval); + #endif + } + } + // Effect 2 + if (info & 0x04) + { + BYTE efx = lpStream[d++]; + BYTE eval = lpStream[d++]; + switch(efx) + { + // 1: Finetune + case 1: if (eval&0xf0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>4)|0x20; } break; + // 2: Note Delay + case 2: if (eval&0xe0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>5)|0xD0; } break; + // 3: Arpeggio + case 3: if (eval) { cmd.command = CMD_ARPEGGIO; cmd.param = eval; } break; + // 4: Portamento Up + case 4: cmd.command = CMD_PORTAMENTOUP; cmd.param = (eval >= 0xe0) ? 0xdf : eval; break; + // 5: Portamento Down + case 5: cmd.command = CMD_PORTAMENTODOWN; cmd.param = (eval >= 0xe0) ? 0xdf : eval; break; + // 6: Tone Portamento + case 6: cmd.command = CMD_TONEPORTAMENTO; cmd.param = eval; break; + // 8: Vibrato + case 8: cmd.command = CMD_VIBRATO; cmd.param = eval; break; + // 12: Note cut + case 12: if (eval & 0xe0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>5)|0xc0; } + else if (!cmd.note) { cmd.note = 0xfe; } break; + #ifdef DMFLOG + default: Log("FX2: %02X.%02X\n", efx, eval); + #endif + } + } + // Effect 3 + if (info & 0x02) + { + BYTE efx = lpStream[d++]; + BYTE eval = lpStream[d++]; + switch(efx) + { + // 1: Vol Slide Up + case 1: if (eval == 0xff) break; + eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f; + cmd.command = CMD_VOLUMESLIDE; cmd.param = eval<<4; break; + // 2: Vol Slide Down + case 2: if (eval == 0xff) break; + eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f; + cmd.command = CMD_VOLUMESLIDE; cmd.param = eval; break; + // 7: Set Pan + case 7: if (!cmd.volcmd) { cmd.volcmd = VOLCMD_PANNING; cmd.vol = (eval+3)>>2; } + else { cmd.command = CMD_PANNING8; cmd.param = eval; } break; + // 8: Pan Slide Left + case 8: eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f; + cmd.command = CMD_PANNINGSLIDE; cmd.param = eval<<4; break; + // 9: Pan Slide Right + case 9: eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f; + cmd.command = CMD_PANNINGSLIDE; cmd.param = eval; break; + #ifdef DMFLOG + default: Log("FX3: %02X.%02X\n", efx, eval); + #endif + + } + } + // Store effect + if (i < m_nChannels) p[i] = cmd; + if (d > dwPos) + { + #ifdef DMFLOG + Log("Unexpected EOP: row=%d\n", row); + #endif + break; + } + } else + { + infobyte[i]--; + } + + // Find free channel for tempo change + if (tempochange) + { + tempochange = FALSE; + UINT speed=6, modtempo=tempo; + UINT rpm = ((ttype) && (pbeat)) ? tempo*pbeat : (tempo+1)*15; + for (speed=30; speed>1; speed--) + { + modtempo = rpm*speed/24; + if (modtempo <= 200) break; + if ((speed < 6) && (modtempo < 256)) break; + } + #ifdef DMFLOG + Log("Tempo change: ttype=%d pbeat=%d tempo=%3d -> speed=%d tempo=%d\n", + ttype, pbeat, tempo, speed, modtempo); + #endif + for (UINT ich=0; ich= 32) && (modtempo < 256)) + { + p[ich].command = CMD_TEMPO; + p[ich].param = (BYTE)modtempo; + modtempo = 0; + } else + { + break; + } + } + } + if (d >= dwPos) break; + } + #ifdef DMFLOG + Log(" %d/%d bytes remaining\n", dwPos-d, pt->jmpsize); + #endif + if (dwPos + 8 >= dwMemLength) break; + } + dwMemPos += patt->patsize + 8; + } + break; + + // "SMPI": Sample Info + case 0x49504d53: + { + DMFSMPI *pds = (DMFSMPI *)(lpStream+dwMemPos); + if (pds->size <= dwMemLength - dwMemPos) + { + DWORD dwPos = dwMemPos + 9; + m_nSamples = pds->samples; + if (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1; + for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++) + { + UINT namelen = lpStream[dwPos]; + smplflags[iSmp] = 0; + if (dwPos+namelen+1+sizeof(DMFSAMPLE) > dwMemPos+pds->size+8) break; + if (namelen) + { + UINT rlen = (namelen < 32) ? namelen : 31; + memcpy(m_szNames[iSmp], lpStream+dwPos+1, rlen); + m_szNames[iSmp][rlen] = 0; + } + dwPos += namelen + 1; + DMFSAMPLE *psh = (DMFSAMPLE *)(lpStream+dwPos); + MODINSTRUMENT *psmp = &Ins[iSmp]; + psmp->nLength = psh->len; + psmp->nLoopStart = psh->loopstart; + psmp->nLoopEnd = psh->loopend; + psmp->nC4Speed = psh->c3speed; + psmp->nGlobalVol = 64; + psmp->nVolume = (psh->volume) ? ((WORD)psh->volume)+1 : (WORD)256; + psmp->uFlags = (psh->flags & 2) ? CHN_16BIT : 0; + if (psmp->uFlags & CHN_16BIT) psmp->nLength >>= 1; + if (psh->flags & 1) psmp->uFlags |= CHN_LOOP; + smplflags[iSmp] = psh->flags; + dwPos += (pfh->version < 8) ? 22 : 30; + #ifdef DMFLOG + Log("SMPI %d/%d: len=%d flags=0x%02X\n", iSmp, m_nSamples, psmp->nLength, psh->flags); + #endif + } + } + dwMemPos += pds->size + 8; + } + break; + + // "SMPD": Sample Data + case 0x44504d53: + { + DWORD dwPos = dwMemPos + 8; + UINT ismpd = 0; + for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++) + { + ismpd++; + DWORD pksize; + if (dwPos + 4 >= dwMemLength) + { + #ifdef DMFLOG + Log("Unexpected EOF at sample %d/%d! (pos=%d)\n", iSmp, m_nSamples, dwPos); + #endif + break; + } + pksize = *((LPDWORD)(lpStream+dwPos)); + #ifdef DMFLOG + Log("sample %d: pos=0x%X pksize=%d ", iSmp, dwPos, pksize); + Log("len=%d flags=0x%X [%08X]\n", Ins[iSmp].nLength, smplflags[ismpd], *((LPDWORD)(lpStream+dwPos+4))); + #endif + dwPos += 4; + if (pksize > dwMemLength - dwPos) + { + #ifdef DMFLOG + Log("WARNING: pksize=%d, but only %d bytes left\n", pksize, dwMemLength-dwPos); + #endif + pksize = dwMemLength - dwPos; + } + if ((pksize) && (iSmp <= m_nSamples)) + { + UINT flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S; + if (smplflags[ismpd] & 4) flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_DMF16 : RS_DMF8; + ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwPos), pksize); + } + dwPos += pksize; + } + dwMemPos = dwPos; + } + break; + + // "ENDE": end of file + case 0x45444e45: + goto dmfexit; + + // Unrecognized id, or "ENDE" field + default: + dwMemPos += 4; + break; + } + } +dmfexit: + if (!m_nChannels) + { + if (!m_nSamples) + { + m_nType = MOD_TYPE_NONE; + return FALSE; + } + m_nChannels = 4; + } + return TRUE; +} + + +/////////////////////////////////////////////////////////////////////// +// DMF Compression + +#pragma pack(1) + +typedef struct DMF_HNODE +{ + short int left, right; + BYTE value; +} DMF_HNODE; + +typedef struct DMF_HTREE +{ + LPBYTE ibuf, ibufmax; + DWORD bitbuf; + UINT bitnum; + UINT lastnode, nodecount; + DMF_HNODE nodes[256]; +} DMF_HTREE; + +#pragma pack() + + +// DMF Huffman ReadBits +BYTE DMFReadBits(DMF_HTREE *tree, UINT nbits) +//------------------------------------------- +{ + BYTE x = 0, bitv = 1; + while (nbits--) + { + if (tree->bitnum) + { + tree->bitnum--; + } else + { + tree->bitbuf = (tree->ibuf < tree->ibufmax) ? *(tree->ibuf++) : 0; + tree->bitnum = 7; + } + if (tree->bitbuf & 1) x |= bitv; + bitv <<= 1; + tree->bitbuf >>= 1; + } + return x; +} + +// +// tree: [8-bit value][12-bit index][12-bit index] = 32-bit +// + +void DMFNewNode(DMF_HTREE *tree) +//------------------------------ +{ + BYTE isleft, isright; + UINT actnode; + + actnode = tree->nodecount; + if (actnode > 255) return; + tree->nodes[actnode].value = DMFReadBits(tree, 7); + isleft = DMFReadBits(tree, 1); + isright = DMFReadBits(tree, 1); + actnode = tree->lastnode; + if (actnode > 255) return; + tree->nodecount++; + tree->lastnode = tree->nodecount; + if (isleft) + { + tree->nodes[actnode].left = tree->lastnode; + DMFNewNode(tree); + } else + { + tree->nodes[actnode].left = -1; + } + tree->lastnode = tree->nodecount; + if (isright) + { + tree->nodes[actnode].right = tree->lastnode; + DMFNewNode(tree); + } else + { + tree->nodes[actnode].right = -1; + } +} + + +int DMFUnpack(LPBYTE psample, LPBYTE ibuf, LPBYTE ibufmax, UINT maxlen) +//---------------------------------------------------------------------- +{ + DMF_HTREE tree; + UINT actnode; + BYTE value, sign, delta = 0; + + memset(&tree, 0, sizeof(tree)); + tree.ibuf = ibuf; + tree.ibufmax = ibufmax; + DMFNewNode(&tree); + value = 0; + for (UINT i=0; i 255) break; + delta = tree.nodes[actnode].value; + if ((tree.ibuf >= tree.ibufmax) && (!tree.bitnum)) break; + } while ((tree.nodes[actnode].left >= 0) && (tree.nodes[actnode].right >= 0)); + if (sign) delta ^= 0xFF; + value += delta; + psample[i] = (i) ? value : 0; + } +#ifdef DMFLOG +// Log("DMFUnpack: %d remaining bytes\n", tree.ibufmax-tree.ibuf); +#endif + return tree.ibuf - ibuf; +} + + diff --git a/modplug/load_dsm.cpp b/modplug/load_dsm.cpp new file mode 100644 index 000000000..55e5ef395 --- /dev/null +++ b/modplug/load_dsm.cpp @@ -0,0 +1,237 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +////////////////////////////////////////////// +// DSIK Internal Format (DSM) module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +#pragma pack(1) + +#define DSMID_RIFF 0x46464952 // "RIFF" +#define DSMID_DSMF 0x464d5344 // "DSMF" +#define DSMID_SONG 0x474e4f53 // "SONG" +#define DSMID_INST 0x54534e49 // "INST" +#define DSMID_PATT 0x54544150 // "PATT" + + +typedef struct DSMNOTE +{ + BYTE note,ins,vol,cmd,inf; +} DSMNOTE; + + +typedef struct DSMINST +{ + DWORD id_INST; + DWORD inst_len; + CHAR filename[13]; + BYTE flags; + BYTE flags2; + BYTE volume; + DWORD length; + DWORD loopstart; + DWORD loopend; + DWORD reserved1; + WORD c2spd; + WORD reserved2; + CHAR samplename[28]; +} DSMINST; + + +typedef struct DSMFILEHEADER +{ + DWORD id_RIFF; // "RIFF" + DWORD riff_len; + DWORD id_DSMF; // "DSMF" + DWORD id_SONG; // "SONG" + DWORD song_len; +} DSMFILEHEADER; + + +typedef struct DSMSONG +{ + CHAR songname[28]; + WORD reserved1; + WORD flags; + DWORD reserved2; + WORD numord; + WORD numsmp; + WORD numpat; + WORD numtrk; + BYTE globalvol; + BYTE mastervol; + BYTE speed; + BYTE bpm; + BYTE panpos[16]; + BYTE orders[128]; +} DSMSONG; + +typedef struct DSMPATT +{ + DWORD id_PATT; + DWORD patt_len; + BYTE dummy1; + BYTE dummy2; +} DSMPATT; + +#pragma pack() + + +BOOL CSoundFile::ReadDSM(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + DSMFILEHEADER *pfh = (DSMFILEHEADER *)lpStream; + DSMSONG *psong; + DWORD dwMemPos; + UINT nPat, nSmp; + + if ((!lpStream) || (dwMemLength < 1024) || (pfh->id_RIFF != DSMID_RIFF) + || (pfh->riff_len + 8 > dwMemLength) || (pfh->riff_len < 1024) + || (pfh->id_DSMF != DSMID_DSMF) || (pfh->id_SONG != DSMID_SONG) + || (pfh->song_len > dwMemLength)) return FALSE; + psong = (DSMSONG *)(lpStream + sizeof(DSMFILEHEADER)); + dwMemPos = sizeof(DSMFILEHEADER) + pfh->song_len; + m_nType = MOD_TYPE_DSM; + m_nChannels = psong->numtrk; + if (m_nChannels < 4) m_nChannels = 4; + if (m_nChannels > 16) m_nChannels = 16; + m_nSamples = psong->numsmp; + if (m_nSamples > MAX_SAMPLES) m_nSamples = MAX_SAMPLES; + m_nDefaultSpeed = psong->speed; + m_nDefaultTempo = psong->bpm; + m_nDefaultGlobalVolume = psong->globalvol << 2; + if ((!m_nDefaultGlobalVolume) || (m_nDefaultGlobalVolume > 256)) m_nDefaultGlobalVolume = 256; + m_nSongPreAmp = psong->mastervol & 0x7F; + for (UINT iOrd=0; iOrdnumord) ? psong->orders[iOrd] : 0xFF); + } + for (UINT iPan=0; iPan<16; iPan++) + { + ChnSettings[iPan].nPan = 0x80; + if (psong->panpos[iPan] <= 0x80) + { + ChnSettings[iPan].nPan = psong->panpos[iPan] << 1; + } + } + memcpy(m_szNames[0], psong->songname, 28); + nPat = 0; + nSmp = 1; + while (dwMemPos < dwMemLength - 8) + { + DSMPATT *ppatt = (DSMPATT *)(lpStream + dwMemPos); + DSMINST *pins = (DSMINST *)(lpStream+dwMemPos); + // Reading Patterns + if (ppatt->id_PATT == DSMID_PATT) + { + dwMemPos += 8; + if (dwMemPos + ppatt->patt_len >= dwMemLength) break; + DWORD dwPos = dwMemPos; + dwMemPos += ppatt->patt_len; + MODCOMMAND *m = AllocatePattern(64, m_nChannels); + if (!m) break; + PatternSize[nPat] = 64; + PatternAllocSize[nPat] = 64; + Patterns[nPat] = m; + UINT row = 0; + while ((row < 64) && (dwPos + 2 <= dwMemPos)) + { + UINT flag = lpStream[dwPos++]; + if (flag) + { + UINT ch = (flag & 0x0F) % m_nChannels; + if (flag & 0x80) + { + UINT note = lpStream[dwPos++]; + if (note) + { + if (note <= 12*9) note += 12; + m[ch].note = (BYTE)note; + } + } + if (flag & 0x40) + { + m[ch].instr = lpStream[dwPos++]; + } + if (flag & 0x20) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = lpStream[dwPos++]; + } + if (flag & 0x10) + { + UINT command = lpStream[dwPos++]; + UINT param = lpStream[dwPos++]; + switch(command) + { + // 4-bit Panning + case 0x08: + switch(param & 0xF0) + { + case 0x00: param <<= 4; break; + case 0x10: command = 0x0A; param = (param & 0x0F) << 4; break; + case 0x20: command = 0x0E; param = (param & 0x0F) | 0xA0; break; + case 0x30: command = 0x0E; param = (param & 0x0F) | 0x10; break; + case 0x40: command = 0x0E; param = (param & 0x0F) | 0x20; break; + default: command = 0; + } + break; + // Portamentos + case 0x11: + case 0x12: + command &= 0x0F; + break; + // 3D Sound (?) + case 0x13: + command = 'X' - 55; + param = 0x91; + break; + default: + // Volume + Offset (?) + command = ((command & 0xF0) == 0x20) ? 0x09 : 0; + } + m[ch].command = (BYTE)command; + m[ch].param = (BYTE)param; + if (command) ConvertModCommand(&m[ch]); + } + } else + { + m += m_nChannels; + row++; + } + } + nPat++; + } else + // Reading Samples + if ((nSmp <= m_nSamples) && (pins->id_INST == DSMID_INST)) + { + if (dwMemPos + pins->inst_len >= dwMemLength - 8) break; + DWORD dwPos = dwMemPos + sizeof(DSMINST); + dwMemPos += 8 + pins->inst_len; + memcpy(m_szNames[nSmp], pins->samplename, 28); + MODINSTRUMENT *psmp = &Ins[nSmp]; + memcpy(psmp->name, pins->filename, 13); + psmp->nGlobalVol = 64; + psmp->nC4Speed = pins->c2spd; + psmp->uFlags = (WORD)((pins->flags & 1) ? CHN_LOOP : 0); + psmp->nLength = pins->length; + psmp->nLoopStart = pins->loopstart; + psmp->nLoopEnd = pins->loopend; + psmp->nVolume = (WORD)(pins->volume << 2); + if (psmp->nVolume > 256) psmp->nVolume = 256; + UINT smptype = (pins->flags & 2) ? RS_PCM8S : RS_PCM8U; + ReadSample(psmp, smptype, (LPCSTR)(lpStream+dwPos), dwMemLength - dwPos); + nSmp++; + } else + { + break; + } + } + return TRUE; +} + diff --git a/modplug/load_far.cpp b/modplug/load_far.cpp new file mode 100644 index 000000000..ea40b21a9 --- /dev/null +++ b/modplug/load_far.cpp @@ -0,0 +1,261 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +//////////////////////////////////////// +// Farandole (FAR) module loader // +//////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#define FARFILEMAGIC 0xFE524146 // "FAR" + +#pragma pack(1) + +typedef struct FARHEADER1 +{ + DWORD id; // file magic FAR= + CHAR songname[40]; // songname + CHAR magic2[3]; // 13,10,26 + WORD headerlen; // remaining length of header in bytes + BYTE version; // 0xD1 + BYTE onoff[16]; + BYTE edit1[9]; + BYTE speed; + BYTE panning[16]; + BYTE edit2[4]; + WORD stlen; +} FARHEADER1; + +typedef struct FARHEADER2 +{ + BYTE orders[256]; + BYTE numpat; + BYTE snglen; + BYTE loopto; + WORD patsiz[256]; +} FARHEADER2; + +typedef struct FARSAMPLE +{ + CHAR samplename[32]; + DWORD length; + BYTE finetune; + BYTE volume; + DWORD reppos; + DWORD repend; + BYTE type; + BYTE loop; +} FARSAMPLE; + +#pragma pack() + + +BOOL CSoundFile::ReadFAR(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + FARHEADER1 *pmh1 = (FARHEADER1 *)lpStream; + FARHEADER2 *pmh2; + DWORD dwMemPos = sizeof(FARHEADER1); + UINT headerlen; + BYTE samplemap[8]; + + if ((!lpStream) || (dwMemLength < 1024) || (pmh1->id != FARFILEMAGIC) + || (pmh1->magic2[0] != 13) || (pmh1->magic2[1] != 10) || (pmh1->magic2[2] != 26)) return FALSE; + headerlen = pmh1->headerlen; + if ((headerlen >= dwMemLength) || (dwMemPos + pmh1->stlen + sizeof(FARHEADER2) >= dwMemLength)) return FALSE; + // Globals + m_nType = MOD_TYPE_FAR; + m_nChannels = 16; + m_nInstruments = 0; + m_nSamples = 0; + m_nSongPreAmp = 0x20; + m_nDefaultSpeed = pmh1->speed; + m_nDefaultTempo = 80; + m_nDefaultGlobalVolume = 256; + + memcpy(m_szNames[0], pmh1->songname, 32); + // Channel Setting + for (UINT nchpan=0; nchpan<16; nchpan++) + { + ChnSettings[nchpan].dwFlags = 0; + ChnSettings[nchpan].nPan = ((pmh1->panning[nchpan] & 0x0F) << 4) + 8; + ChnSettings[nchpan].nVolume = 64; + } + // Reading comment + if (pmh1->stlen) + { + UINT szLen = pmh1->stlen; + if (szLen > dwMemLength - dwMemPos) szLen = dwMemLength - dwMemPos; + if ((m_lpszSongComments = new char[szLen + 1]) != NULL) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos, szLen); + m_lpszSongComments[szLen] = 0; + } + dwMemPos += pmh1->stlen; + } + // Reading orders + pmh2 = (FARHEADER2 *)(lpStream + dwMemPos); + dwMemPos += sizeof(FARHEADER2); + if (dwMemPos >= dwMemLength) return TRUE; + for (UINT iorder=0; iordersnglen) ? pmh2->orders[iorder] : 0xFF; + } + m_nRestartPos = pmh2->loopto; + // Reading Patterns + dwMemPos += headerlen - (869 + pmh1->stlen); + if (dwMemPos >= dwMemLength) return TRUE; + + WORD *patsiz = (WORD *)pmh2->patsiz; + for (UINT ipat=0; ipat<256; ipat++) if (patsiz[ipat]) + { + UINT patlen = patsiz[ipat]; + if ((ipat >= MAX_PATTERNS) || (patsiz[ipat] < 2)) + { + dwMemPos += patlen; + continue; + } + if (dwMemPos + patlen >= dwMemLength) return TRUE; + UINT rows = (patlen - 2) >> 6; + if (!rows) + { + dwMemPos += patlen; + continue; + } + if (rows > 256) rows = 256; + if (rows < 16) rows = 16; + PatternSize[ipat] = rows; + PatternAllocSize[ipat] = rows; + if ((Patterns[ipat] = AllocatePattern(rows, m_nChannels)) == NULL) return TRUE; + MODCOMMAND *m = Patterns[ipat]; + UINT patbrk = lpStream[dwMemPos]; + const BYTE *p = lpStream + dwMemPos + 2; + UINT max = rows*16*4; + if (max > patlen-2) max = patlen-2; + for (UINT len=0; leninstr = ins + 1; + m->note = note + 36; + } + if (vol & 0x0F) + { + m->volcmd = VOLCMD_VOLUME; + m->vol = (vol & 0x0F) << 2; + if (m->vol <= 4) m->vol = 0; + } + switch(eff & 0xF0) + { + // 1.x: Portamento Up + case 0x10: + m->command = CMD_PORTAMENTOUP; + m->param = eff & 0x0F; + break; + // 2.x: Portamento Down + case 0x20: + m->command = CMD_PORTAMENTODOWN; + m->param = eff & 0x0F; + break; + // 3.x: Tone-Portamento + case 0x30: + m->command = CMD_TONEPORTAMENTO; + m->param = (eff & 0x0F) << 2; + break; + // 4.x: Retrigger + case 0x40: + m->command = CMD_RETRIG; + m->param = 6 / (1+(eff&0x0F)) + 1; + break; + // 5.x: Set Vibrato Depth + case 0x50: + m->command = CMD_VIBRATO; + m->param = (eff & 0x0F); + break; + // 6.x: Set Vibrato Speed + case 0x60: + m->command = CMD_VIBRATO; + m->param = (eff & 0x0F) << 4; + break; + // 7.x: Vol Slide Up + case 0x70: + m->command = CMD_VOLUMESLIDE; + m->param = (eff & 0x0F) << 4; + break; + // 8.x: Vol Slide Down + case 0x80: + m->command = CMD_VOLUMESLIDE; + m->param = (eff & 0x0F); + break; + // A.x: Port to vol + case 0xA0: + m->volcmd = VOLCMD_VOLUME; + m->vol = ((eff & 0x0F) << 2) + 4; + break; + // B.x: Set Balance + case 0xB0: + m->command = CMD_PANNING8; + m->param = (eff & 0x0F) << 4; + break; + // F.x: Set Speed + case 0xF0: + m->command = CMD_SPEED; + m->param = eff & 0x0F; + break; + default: + if ((patbrk) && (patbrk+1 == (len >> 6)) && (patbrk+1 != rows-1)) + { + m->command = CMD_PATTERNBREAK; + patbrk = 0; + } + } + } + dwMemPos += patlen; + } + // Reading samples + if (dwMemPos + 8 >= dwMemLength) return TRUE; + memcpy(samplemap, lpStream+dwMemPos, 8); + dwMemPos += 8; + MODINSTRUMENT *pins = &Ins[1]; + for (UINT ismp=0; ismp<64; ismp++, pins++) if (samplemap[ismp >> 3] & (1 << (ismp & 7))) + { + if (dwMemPos + sizeof(FARSAMPLE) > dwMemLength) return TRUE; + FARSAMPLE *pfs = (FARSAMPLE *)(lpStream + dwMemPos); + dwMemPos += sizeof(FARSAMPLE); + m_nSamples = ismp + 1; + memcpy(m_szNames[ismp+1], pfs->samplename, 32); + pins->nLength = pfs->length; + pins->nLoopStart = pfs->reppos; + pins->nLoopEnd = pfs->repend; + pins->nFineTune = 0; + pins->nC4Speed = 8363*2; + pins->nGlobalVol = 64; + pins->nVolume = pfs->volume << 4; + pins->uFlags = 0; + if ((pins->nLength > 3) && (dwMemPos + 4 < dwMemLength)) + { + if (pfs->type & 1) + { + pins->uFlags |= CHN_16BIT; + pins->nLength >>= 1; + pins->nLoopStart >>= 1; + pins->nLoopEnd >>= 1; + } + if ((pfs->loop & 8) && (pins->nLoopEnd > 4)) pins->uFlags |= CHN_LOOP; + ReadSample(pins, (pins->uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S, + (LPSTR)(lpStream+dwMemPos), dwMemLength - dwMemPos); + } + dwMemPos += pfs->length; + } + return TRUE; +} + diff --git a/modplug/load_it.cpp b/modplug/load_it.cpp new file mode 100644 index 000000000..555969be6 --- /dev/null +++ b/modplug/load_it.cpp @@ -0,0 +1,1552 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" +#include "it_defs.h" + +/* blah, -mrsb. +this is a schism header */ +#include "midi.h" + +#ifdef MSC_VER +#pragma warning(disable:4244) +#endif + +BYTE autovibit2xm[8] = +{ 0, 3, 1, 4, 2, 0, 0, 0 }; + +BYTE autovibxm2it[8] = +{ 0, 2, 4, 1, 3, 0, 0, 0 }; + +////////////////////////////////////////////////////////// +// Impulse Tracker IT file support (import only) + + +static inline UINT ConvertVolParam(UINT value) +//-------------------------------------------- +{ + return (value > 9) ? 9 : value; +} + + +BOOL CSoundFile::ITInstrToMPT(const void *p, INSTRUMENTHEADER *penv, UINT trkvers) +//-------------------------------------------------------------------------------- +{ + if (trkvers < 0x0200) + { + const ITOLDINSTRUMENT *pis = (const ITOLDINSTRUMENT *)p; + memcpy(penv->name, pis->name, 26); + memcpy(penv->filename, pis->filename, 12); + penv->nFadeOut = bswapLE16(pis->fadeout) << 6; + penv->nGlobalVol = 64; + for (UINT j=0; j<120; j++) + { + UINT note = pis->keyboard[j*2]; + UINT ins = pis->keyboard[j*2+1]; + if (ins < MAX_SAMPLES) penv->Keyboard[j] = ins; + if (note < 128) penv->NoteMap[j] = note+1; + else if (note >= 0xFE) penv->NoteMap[j] = note; + } + if (pis->flags & 0x01) penv->dwFlags |= ENV_VOLUME; + if (pis->flags & 0x02) penv->dwFlags |= ENV_VOLLOOP; + if (pis->flags & 0x04) penv->dwFlags |= ENV_VOLSUSTAIN; + penv->VolEnv.nLoopStart = pis->vls; + penv->VolEnv.nLoopEnd = pis->vle; + penv->VolEnv.nSustainStart = pis->sls; + penv->VolEnv.nSustainEnd = pis->sle; + penv->VolEnv.nNodes = 25; + for (UINT ev=0; ev<25; ev++) + { + if ((penv->VolEnv.Ticks[ev] = pis->nodes[ev*2]) == 0xFF) + { + penv->VolEnv.nNodes = ev; + break; + } + penv->VolEnv.Values[ev] = pis->nodes[ev*2+1]; + } + penv->nNNA = pis->nna; + penv->nDCT = pis->dnc; + penv->nPan = 0x80; + } else + { + const ITINSTRUMENT *pis = (const ITINSTRUMENT *)p; + memcpy(penv->name, pis->name, 26); + memcpy(penv->filename, pis->filename, 12); + penv->nMidiProgram = pis->mpr; + penv->nMidiChannel = pis->mch; + penv->wMidiBank = bswapLE16(pis->mbank); + penv->nFadeOut = bswapLE16(pis->fadeout) << 5; + penv->nGlobalVol = pis->gbv >> 1; + if (penv->nGlobalVol > 64) penv->nGlobalVol = 64; + for (UINT j=0; j<120; j++) + { + UINT note = pis->keyboard[j*2]; + UINT ins = pis->keyboard[j*2+1]; + if (ins < MAX_SAMPLES) penv->Keyboard[j] = ins; + if (note < 128) penv->NoteMap[j] = note+1; + else if (note >= 0xFE) penv->NoteMap[j] = note; + } + // Volume Envelope + if (pis->volenv.flags & 1) penv->dwFlags |= ENV_VOLUME; + if (pis->volenv.flags & 2) penv->dwFlags |= ENV_VOLLOOP; + if (pis->volenv.flags & 4) penv->dwFlags |= ENV_VOLSUSTAIN; + if (pis->volenv.flags & 8) penv->dwFlags |= ENV_VOLCARRY; + penv->VolEnv.nNodes = pis->volenv.num; + if (penv->VolEnv.nNodes > 25) penv->VolEnv.nNodes = 25; + + penv->VolEnv.nLoopStart = pis->volenv.lpb; + penv->VolEnv.nLoopEnd = pis->volenv.lpe; + penv->VolEnv.nSustainStart = pis->volenv.slb; + penv->VolEnv.nSustainEnd = pis->volenv.sle; + // Panning Envelope + if (pis->panenv.flags & 1) penv->dwFlags |= ENV_PANNING; + if (pis->panenv.flags & 2) penv->dwFlags |= ENV_PANLOOP; + if (pis->panenv.flags & 4) penv->dwFlags |= ENV_PANSUSTAIN; + if (pis->panenv.flags & 8) penv->dwFlags |= ENV_PANCARRY; + penv->PanEnv.nNodes = pis->panenv.num; + if (penv->PanEnv.nNodes > 25) penv->PanEnv.nNodes = 25; + penv->PanEnv.nLoopStart = pis->panenv.lpb; + penv->PanEnv.nLoopEnd = pis->panenv.lpe; + penv->PanEnv.nSustainStart = pis->panenv.slb; + penv->PanEnv.nSustainEnd = pis->panenv.sle; + // Pitch Envelope + if (pis->pitchenv.flags & 1) penv->dwFlags |= ENV_PITCH; + if (pis->pitchenv.flags & 2) penv->dwFlags |= ENV_PITCHLOOP; + if (pis->pitchenv.flags & 4) penv->dwFlags |= ENV_PITCHSUSTAIN; + if (pis->pitchenv.flags & 8) penv->dwFlags |= ENV_PITCHCARRY; + if (pis->pitchenv.flags & 0x80) penv->dwFlags |= ENV_FILTER; + penv->PitchEnv.nNodes = pis->pitchenv.num; + if (penv->PitchEnv.nNodes > 25) penv->PitchEnv.nNodes = 25; + penv->PitchEnv.nLoopStart = pis->pitchenv.lpb; + penv->PitchEnv.nLoopEnd = pis->pitchenv.lpe; + penv->PitchEnv.nSustainStart = pis->pitchenv.slb; + penv->PitchEnv.nSustainEnd = pis->pitchenv.sle; + // Envelopes Data + for (UINT ev=0; ev<25; ev++) + { + penv->VolEnv.Values[ev] = pis->volenv.data[ev*3]; + penv->VolEnv.Ticks[ev] = (pis->volenv.data[ev*3+2] << 8) | (pis->volenv.data[ev*3+1]); + penv->PanEnv.Values[ev] = pis->panenv.data[ev*3] + 32; + penv->PanEnv.Ticks[ev] = (pis->panenv.data[ev*3+2] << 8) | (pis->panenv.data[ev*3+1]); + penv->PitchEnv.Values[ev] = pis->pitchenv.data[ev*3] + 32; + penv->PitchEnv.Ticks[ev] = (pis->pitchenv.data[ev*3+2] << 8) | (pis->pitchenv.data[ev*3+1]); + } + penv->nNNA = pis->nna; + penv->nDCT = pis->dct; + penv->nDNA = pis->dca; + penv->nPPS = pis->pps; + penv->nPPC = pis->ppc; + penv->nIFC = pis->ifc; + penv->nIFR = pis->ifr; + penv->nVolSwing = pis->rv; + penv->nPanSwing = pis->rp; + penv->nPan = (pis->dfp & 0x7F) << 2; + if (penv->nPan > 256) penv->nPan = 128; + if (pis->dfp < 0x80) penv->dwFlags |= ENV_SETPANNING; + } + if ((penv->VolEnv.nLoopStart >= 25) || (penv->VolEnv.nLoopEnd >= 25)) penv->dwFlags &= ~ENV_VOLLOOP; + if ((penv->VolEnv.nSustainStart >= 25) || (penv->VolEnv.nSustainEnd >= 25)) penv->dwFlags &= ~ENV_VOLSUSTAIN; + return TRUE; +} + + +BOOL CSoundFile::ReadIT(const BYTE *lpStream, DWORD dwMemLength) +//-------------------------------------------------------------- +{ + ITFILEHEADER pifh = *(ITFILEHEADER *)lpStream; + DWORD dwMemPos = sizeof(ITFILEHEADER); + DWORD inspos[MAX_INSTRUMENTS]; + DWORD smppos[MAX_SAMPLES]; + DWORD patpos[MAX_PATTERNS]; + BYTE chnmask[64], channels_used[64]; + MODCOMMAND lastvalue[64]; + + if ((!lpStream) || (dwMemLength < 0xc2)) return FALSE; + + pifh.id = bswapLE32(pifh.id); + if (pifh.id == 0x49504D49) { + if (dwMemLength < 554) return FALSE; + + WORD tv; + INSTRUMENTHEADER *zenv = new INSTRUMENTHEADER; + if (!zenv) return FALSE; + memset(zenv, 0, sizeof(INSTRUMENTHEADER)); + memcpy(&tv, lpStream+0x1C, 2); /* trkvers */ + if (!ITInstrToMPT(lpStream, zenv, tv)) { + delete zenv; + return FALSE; + } + + /* okay, we need samples now */ + unsigned int q = 554; + BYTE expect_samples = lpStream[0x1E]; + + m_nType = MOD_TYPE_IT; + m_nInstruments = 1; + m_nSamples = expect_samples; + m_dwSongFlags = SONG_INSTRUMENTMODE | SONG_LINEARSLIDES /* eh? */; + + memcpy(m_szNames[0], lpStream + 0x20, 26); + m_szNames[0][26] = 0; + + if (q+(80*expect_samples) >= dwMemLength) { + delete zenv; + return FALSE; + } + + for (UINT nsmp = 0; nsmp < expect_samples; nsmp++) { + + ITSAMPLESTRUCT pis = *(ITSAMPLESTRUCT *)(lpStream+q); + q += 80; /* length of ITS header */ + + pis.id = bswapLE32(pis.id); + pis.length = bswapLE32(pis.length); + pis.loopbegin = bswapLE32(pis.loopbegin); + pis.loopend = bswapLE32(pis.loopend); + pis.C5Speed = bswapLE32(pis.C5Speed); + pis.susloopbegin = bswapLE32(pis.susloopbegin); + pis.susloopend = bswapLE32(pis.susloopend); + pis.samplepointer = bswapLE32(pis.samplepointer); + + if (pis.id == 0x53504D49) + { + MODINSTRUMENT *pins = &Ins[nsmp+1]; + memcpy(pins->name, pis.filename, 12); + pins->uFlags = 0; + pins->nLength = 0; + pins->nLoopStart = pis.loopbegin; + pins->nLoopEnd = pis.loopend; + pins->nSustainStart = pis.susloopbegin; + pins->nSustainEnd = pis.susloopend; + pins->nC4Speed = pis.C5Speed; + if (!pins->nC4Speed) pins->nC4Speed = 8363; + if (pis.C5Speed < 256) pins->nC4Speed = 256; + pins->nVolume = pis.vol << 2; + if (pins->nVolume > 256) pins->nVolume = 256; + pins->nGlobalVol = pis.gvl; + if (pins->nGlobalVol > 64) pins->nGlobalVol = 64; + if (pis.flags & 0x10) pins->uFlags |= CHN_LOOP; + if (pis.flags & 0x20) pins->uFlags |= CHN_SUSTAINLOOP; + if (pis.flags & 0x40) pins->uFlags |= CHN_PINGPONGLOOP; + if (pis.flags & 0x80) pins->uFlags |= CHN_PINGPONGSUSTAIN; + pins->nPan = (pis.dfp & 0x7F) << 2; + if (pins->nPan > 256) pins->nPan = 256; + if (pis.dfp & 0x80) pins->uFlags |= CHN_PANNING; + pins->nVibType = autovibit2xm[pis.vit & 7]; + pins->nVibRate = pis.vis; + pins->nVibDepth = pis.vid & 0x7F; + pins->nVibSweep = (pis.vir + 3) / 4; + if ((pis.samplepointer) && (pis.samplepointer < dwMemLength) && (pis.length)) + { + pins->nLength = pis.length; + if (pins->nLength > MAX_SAMPLE_LENGTH) pins->nLength = MAX_SAMPLE_LENGTH; + UINT flags = (pis.cvt & 1) ? RS_PCM8S : RS_PCM8U; + if (pis.flags & 2) + { + flags += 5; + if (pis.flags & 4) flags |= RSF_STEREO; + pins->uFlags |= CHN_16BIT; + // IT 2.14 16-bit packed sample ? + if (pis.flags & 8) flags = ((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT21516 : RS_IT21416; + } else + { + if (pis.flags & 4) flags |= RSF_STEREO; + if (pis.cvt == 0xFF) flags = RS_ADPCM4; else + // IT 2.14 8-bit packed sample ? + if (pis.flags & 8) flags = ((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT2158 : RS_IT2148; + } + ReadSample(&Ins[nsmp+1], flags, (LPSTR)(lpStream+pis.samplepointer), dwMemLength - pis.samplepointer); + } + } + memcpy(m_szNames[nsmp+1], pis.name, 26); + + } + + Headers[1] = zenv; + return TRUE; + } + + + pifh.ordnum = bswapLE16(pifh.ordnum); + pifh.insnum = bswapLE16(pifh.insnum); + pifh.smpnum = bswapLE16(pifh.smpnum); + pifh.patnum = bswapLE16(pifh.patnum); + pifh.cwtv = bswapLE16(pifh.cwtv); + pifh.cmwt = bswapLE16(pifh.cmwt); + pifh.flags = bswapLE16(pifh.flags); + pifh.special = bswapLE16(pifh.special); + pifh.msglength = bswapLE16(pifh.msglength); + pifh.msgoffset = bswapLE32(pifh.msgoffset); + pifh.reserved2 = bswapLE32(pifh.reserved2); + + + + if ((pifh.id != 0x4D504D49) || (pifh.insnum >= MAX_INSTRUMENTS) + || (pifh.smpnum >= MAX_INSTRUMENTS)) return FALSE; + if (dwMemPos + pifh.ordnum + pifh.insnum*4 + + pifh.smpnum*4 + pifh.patnum*4 > dwMemLength) return FALSE; + m_nType = MOD_TYPE_IT; + if (!(pifh.flags & 0x01)) m_dwSongFlags |= SONG_NOSTEREO; + if (pifh.flags & 0x04) m_dwSongFlags |= SONG_INSTRUMENTMODE; + if (pifh.flags & 0x08) m_dwSongFlags |= SONG_LINEARSLIDES; + if (pifh.flags & 0x10) m_dwSongFlags |= SONG_ITOLDEFFECTS; + if (pifh.flags & 0x20) m_dwSongFlags |= SONG_ITCOMPATMODE; + if (pifh.flags & 0x40) { + midi_flags |= MIDI_PITCH_BEND; + midi_pitch_depth = pifh.pwd; + } + if (pifh.flags & 0x80) m_dwSongFlags |= SONG_EMBEDMIDICFG; + if (pifh.flags & 0x1000) m_dwSongFlags |= SONG_EXFILTERRANGE; + memcpy(m_szNames[0], pifh.songname, 26); + m_szNames[0][26] = 0; + if (pifh.cwtv >= 0x0213) { + m_rowHighlightMinor = pifh.hilight_minor; + m_rowHighlightMajor = pifh.hilight_major; + } else { + m_rowHighlightMinor = 4; + m_rowHighlightMajor = 16; + } + // Global Volume + m_nDefaultGlobalVolume = pifh.globalvol << 1; + if (m_nDefaultGlobalVolume > 256) m_nDefaultGlobalVolume = 256; + if (pifh.speed) m_nDefaultSpeed = pifh.speed; + if (pifh.tempo) m_nDefaultTempo = pifh.tempo; + m_nSongPreAmp = pifh.mv; + if (m_nSongPreAmp > 128) + m_nSongPreAmp = 128; + m_nStereoSeparation = pifh.sep; + // Reading Channels Pan Positions + for (int ipan=0; ipan<64; ipan++) if (pifh.chnpan[ipan] != 0xFF) + { + ChnSettings[ipan].nVolume = pifh.chnvol[ipan]; + ChnSettings[ipan].nPan = 128; + if (pifh.chnpan[ipan] & 0x80) ChnSettings[ipan].dwFlags |= CHN_MUTE; + UINT n = pifh.chnpan[ipan] & 0x7F; + if (n <= 64) ChnSettings[ipan].nPan = n << 2; + if (n == 100) ChnSettings[ipan].dwFlags |= CHN_SURROUND; + } + if (m_nChannels < 4) m_nChannels = 4; + // Reading Song Message + if ((pifh.special & 0x01) && (pifh.msglength) && (pifh.msgoffset + pifh.msglength < dwMemLength)) + { + m_lpszSongComments = new char[pifh.msglength+1]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+pifh.msgoffset, pifh.msglength); + m_lpszSongComments[pifh.msglength] = 0; + } + } + // Reading orders + UINT nordsize = pifh.ordnum; + if (nordsize > MAX_ORDERS) nordsize = MAX_ORDERS; + memcpy(Order, lpStream+dwMemPos, nordsize); + + dwMemPos += pifh.ordnum; + // Reading Instrument Offsets + memset(inspos, 0, sizeof(inspos)); + UINT inspossize = pifh.insnum; + if (inspossize > MAX_INSTRUMENTS) inspossize = MAX_INSTRUMENTS; + inspossize <<= 2; + memcpy(inspos, lpStream+dwMemPos, inspossize); + for (UINT j=0; j < (inspossize>>2); j++) + { + inspos[j] = bswapLE32(inspos[j]); + } + dwMemPos += pifh.insnum * 4; + // Reading Samples Offsets + memset(smppos, 0, sizeof(smppos)); + UINT smppossize = pifh.smpnum; + if (smppossize > MAX_SAMPLES) smppossize = MAX_SAMPLES; + smppossize <<= 2; + memcpy(smppos, lpStream+dwMemPos, smppossize); + for (UINT j=0; j < (smppossize>>2); j++) + { + smppos[j] = bswapLE32(smppos[j]); + } + dwMemPos += pifh.smpnum * 4; + // Reading Patterns Offsets + memset(patpos, 0, sizeof(patpos)); + UINT patpossize = pifh.patnum; + if (patpossize > MAX_PATTERNS) patpossize = MAX_PATTERNS; + patpossize <<= 2; + memcpy(patpos, lpStream+dwMemPos, patpossize); + for (UINT j=0; j < (patpossize>>2); j++) + { + patpos[j] = bswapLE32(patpos[j]); + } + dwMemPos += pifh.patnum * 4; + + for (UINT i = 0; i < pifh.ordnum; i++) { + if (Order[i] >= pifh.patnum && Order[i] < MAX_PATTERNS) { + pifh.patnum = Order[i]; + for (UINT j = patpossize; j < (pifh.patnum>>2); j++) + patpos[j] = 0; + patpossize = pifh.patnum; + } + } + + + // Reading IT Extra Info + if (dwMemPos + 2 < dwMemLength) + { + UINT nflt = bswapLE16(*((WORD *)(lpStream + dwMemPos))); + dwMemPos += 2; + if (dwMemPos + nflt * 8 < dwMemLength) dwMemPos += nflt * 8; + } + // Reading Midi Output & Macros + if (m_dwSongFlags & SONG_EMBEDMIDICFG) + { + if (dwMemPos + sizeof(MODMIDICFG) < dwMemLength) + { + memcpy(&m_MidiCfg, lpStream+dwMemPos, sizeof(MODMIDICFG)); + dwMemPos += sizeof(MODMIDICFG); + } else { + ResetMidiCfg(); + } + } else { + ResetMidiCfg(); + } + // Read pattern names: "PNAM" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e50)) + { + UINT len = bswapLE32(*((DWORD *)(lpStream+dwMemPos+4))); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len <= MAX_PATTERNS*MAX_PATTERNNAME) && (len >= MAX_PATTERNNAME)) + { + m_lpszPatternNames = new char[len]; + if (m_lpszPatternNames) + { + m_nPatternNames = len / MAX_PATTERNNAME; + memcpy(m_lpszPatternNames, lpStream+dwMemPos, len); + } + dwMemPos += len; + } + } + // 4-channels minimum + m_nChannels = 4; + // Read channel names: "CNAM" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e43)) + { + UINT len = bswapLE32(*((DWORD *)(lpStream+dwMemPos+4))); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len <= 64*MAX_CHANNELNAME)) + { + UINT n = len / MAX_CHANNELNAME; + if (n > m_nChannels) m_nChannels = n; + for (UINT i=0; i MAX_PATTERNS) npatterns = MAX_PATTERNS; + for (UINT patchk=0; patchk= dwMemLength)) continue; + UINT len = bswapLE16(*((WORD *)(lpStream+patpos[patchk]))); + UINT rows = bswapLE16(*((WORD *)(lpStream+patpos[patchk]+2))); + if ((rows < 4) || (rows > 256)) continue; + if (patpos[patchk]+8+len > dwMemLength) continue; + UINT i = 0; + const BYTE *p = lpStream+patpos[patchk]+8; + UINT nrow = 0; + while (nrow= len) break; + BYTE b = p[i++]; + if (!b) + { + nrow++; + continue; + } + UINT ch = b & 0x7F; + if (ch) ch = (ch - 1) & 0x3F; + if (b & 0x80) + { + if (i >= len) break; + chnmask[ch] = p[i++]; + } + // Channel used + if (chnmask[ch] & 0x0F) + { + if ((ch >= m_nChannels) && (ch < 64)) m_nChannels = ch+1; + } + // Note + if (chnmask[ch] & 1) i++; + // Instrument + if (chnmask[ch] & 2) i++; + // Volume + if (chnmask[ch] & 4) i++; + // Effect + if (chnmask[ch] & 8) i += 2; + if (i >= len) break; + } + } + // Reading Instruments + m_nInstruments = pifh.insnum; + if (m_nInstruments >= MAX_INSTRUMENTS) m_nInstruments = MAX_INSTRUMENTS-1; + for (UINT nins=0; nins 0) && (inspos[nins] < dwMemLength - sizeof(ITOLDINSTRUMENT))) + { + INSTRUMENTHEADER *penv = new INSTRUMENTHEADER; + if (!penv) continue; + Headers[nins+1] = penv; + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + ITInstrToMPT(lpStream + inspos[nins], penv, pifh.cmwt); + } + } + // Reading Samples + m_nSamples = pifh.smpnum; + if (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1; + for (UINT nsmp=0; nsmpname, pis.filename, 12); + pins->uFlags = 0; + pins->nLength = 0; + pins->nLoopStart = pis.loopbegin; + pins->nLoopEnd = pis.loopend; + pins->nSustainStart = pis.susloopbegin; + pins->nSustainEnd = pis.susloopend; + pins->nC4Speed = pis.C5Speed; + if (!pins->nC4Speed) pins->nC4Speed = 8363; + if (pis.C5Speed < 256) pins->nC4Speed = 256; + pins->nVolume = pis.vol << 2; + if (pins->nVolume > 256) pins->nVolume = 256; + pins->nGlobalVol = pis.gvl; + if (pins->nGlobalVol > 64) pins->nGlobalVol = 64; + if (pis.flags & 0x10) pins->uFlags |= CHN_LOOP; + if (pis.flags & 0x20) pins->uFlags |= CHN_SUSTAINLOOP; + if (pis.flags & 0x40) pins->uFlags |= CHN_PINGPONGLOOP; + if (pis.flags & 0x80) pins->uFlags |= CHN_PINGPONGSUSTAIN; + pins->nPan = (pis.dfp & 0x7F) << 2; + if (pins->nPan > 256) pins->nPan = 256; + if (pis.dfp & 0x80) pins->uFlags |= CHN_PANNING; + pins->nVibType = autovibit2xm[pis.vit & 7]; + pins->nVibRate = pis.vis; + pins->nVibDepth = pis.vid & 0x7F; + pins->nVibSweep = (pis.vir + 3) / 4; + if ((pis.samplepointer) && (pis.samplepointer < dwMemLength) && (pis.length)) + { + pins->nLength = pis.length; + if (pins->nLength > MAX_SAMPLE_LENGTH) pins->nLength = MAX_SAMPLE_LENGTH; + UINT flags = (pis.cvt & 1) ? RS_PCM8S : RS_PCM8U; + if (pis.flags & 2) + { + flags += 5; + if (pis.flags & 4) flags |= RSF_STEREO; + pins->uFlags |= CHN_16BIT; + // IT 2.14 16-bit packed sample ? + if (pis.flags & 8) flags = ((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT21516 : RS_IT21416; + } else + { + if (pis.flags & 4) flags |= RSF_STEREO; + if (pis.cvt == 0xFF) flags = RS_ADPCM4; else + // IT 2.14 8-bit packed sample ? + if (pis.flags & 8) flags = ((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT2158 : RS_IT2148; + } + ReadSample(&Ins[nsmp+1], flags, (LPSTR)(lpStream+pis.samplepointer), dwMemLength - pis.samplepointer); + } + } + memcpy(m_szNames[nsmp+1], pis.name, 26); + } + // Reading Patterns + for (UINT npat=0; npat= dwMemLength)) + { + PatternSize[npat] = 64; + PatternAllocSize[npat] = 64; + Patterns[npat] = AllocatePattern(64, m_nChannels); + continue; + } + + UINT len = bswapLE16(*((WORD *)(lpStream+patpos[npat]))); + UINT rows = bswapLE16(*((WORD *)(lpStream+patpos[npat]+2))); + if ((rows < 4) || (rows > 256)) continue; + if (patpos[npat]+8+len > dwMemLength) continue; + PatternSize[npat] = rows; + PatternAllocSize[npat] = rows; + if ((Patterns[npat] = AllocatePattern(rows, m_nChannels)) == NULL) continue; + memset(lastvalue, 0, sizeof(lastvalue)); + memset(chnmask, 0, sizeof(chnmask)); + MODCOMMAND *m = Patterns[npat]; + UINT i = 0; + const BYTE *p = lpStream+patpos[npat]+8; + UINT nrow = 0; + while (nrow= len) break; + BYTE b = p[i++]; + if (!b) + { + nrow++; + m+=m_nChannels; + continue; + } + UINT ch = b & 0x7F; + if (ch) ch = (ch - 1) & 0x3F; + if (b & 0x80) + { + if (i >= len) break; + chnmask[ch] = p[i++]; + } + if ((chnmask[ch] & 0x10) && (ch < m_nChannels)) + { + m[ch].note = lastvalue[ch].note; + } + if ((chnmask[ch] & 0x20) && (ch < m_nChannels)) + { + m[ch].instr = lastvalue[ch].instr; + } + if ((chnmask[ch] & 0x40) && (ch < m_nChannels)) + { + m[ch].volcmd = lastvalue[ch].volcmd; + m[ch].vol = lastvalue[ch].vol; + } + if ((chnmask[ch] & 0x80) && (ch < m_nChannels)) + { + m[ch].command = lastvalue[ch].command; + m[ch].param = lastvalue[ch].param; + } + if (chnmask[ch] & 1) // Note + { + if (i >= len) break; + UINT note = p[i++]; + if (ch < m_nChannels) + { + if (note < 0x80) note++; + m[ch].note = note; + lastvalue[ch].note = note; + channels_used[ch] = TRUE; + } + } + if (chnmask[ch] & 2) + { + if (i >= len) break; + UINT instr = p[i++]; + if (ch < m_nChannels) + { + m[ch].instr = instr; + lastvalue[ch].instr = instr; + } + } + if (chnmask[ch] & 4) + { + if (i >= len) break; + UINT vol = p[i++]; + if (ch < m_nChannels) + { + // 0-64: Set Volume + if (vol <= 64) { m[ch].volcmd = VOLCMD_VOLUME; m[ch].vol = vol; } else + // 128-192: Set Panning + if ((vol >= 128) && (vol <= 192)) { m[ch].volcmd = VOLCMD_PANNING; m[ch].vol = vol - 128; } else + // 65-74: Fine Volume Up + if (vol < 75) { m[ch].volcmd = VOLCMD_FINEVOLUP; m[ch].vol = vol - 65; } else + // 75-84: Fine Volume Down + if (vol < 85) { m[ch].volcmd = VOLCMD_FINEVOLDOWN; m[ch].vol = vol - 75; } else + // 85-94: Volume Slide Up + if (vol < 95) { m[ch].volcmd = VOLCMD_VOLSLIDEUP; m[ch].vol = vol - 85; } else + // 95-104: Volume Slide Down + if (vol < 105) { m[ch].volcmd = VOLCMD_VOLSLIDEDOWN; m[ch].vol = vol - 95; } else + // 105-114: Pitch Slide Up + if (vol < 115) { m[ch].volcmd = VOLCMD_PORTADOWN; m[ch].vol = vol - 105; } else + // 115-124: Pitch Slide Down + if (vol < 125) { m[ch].volcmd = VOLCMD_PORTAUP; m[ch].vol = vol - 115; } else + // 193-202: Portamento To + if ((vol >= 193) && (vol <= 202)) { m[ch].volcmd = VOLCMD_TONEPORTAMENTO; m[ch].vol = vol - 193; } else + // 203-212: Vibrato + if ((vol >= 203) && (vol <= 212)) { m[ch].volcmd = VOLCMD_VIBRATO; m[ch].vol = vol - 203; } + lastvalue[ch].volcmd = m[ch].volcmd; + lastvalue[ch].vol = m[ch].vol; + } + } + // Reading command/param + if (chnmask[ch] & 8) + { + if (i > len - 2) break; + UINT cmd = p[i++]; + UINT param = p[i++]; + if (ch < m_nChannels) + { + if (cmd) + { + m[ch].command = cmd; + m[ch].param = param; + S3MConvert(&m[ch], TRUE); + lastvalue[ch].command = m[ch].command; + lastvalue[ch].param = m[ch].param; + } + } + } + } + } + for (UINT ncu=0; ncu=m_nChannels) + { + ChnSettings[ncu].nVolume = 64; + ChnSettings[ncu].dwFlags &= ~CHN_MUTE; + } + } + m_nMinPeriod = 8; + m_nMaxPeriod = 0xF000; + return TRUE; +} + + +#ifndef MODPLUG_NO_FILESAVE +//#define SAVEITTIMESTAMP +#pragma warning(disable:4100) + +#if 0 +BOOL CSoundFile::SaveIT(LPCSTR lpszFileName, UINT nPacking) +//--------------------------------------------------------- +{ + DWORD dwPatNamLen, dwChnNamLen; + ITFILEHEADER header; + ITINSTRUMENT iti; + ITSAMPLESTRUCT itss; + BYTE smpcount[MAX_SAMPLES]; + DWORD inspos[MAX_INSTRUMENTS]; + DWORD patpos[MAX_PATTERNS]; + DWORD smppos[MAX_SAMPLES]; + DWORD dwPos = 0, dwHdrPos = 0, dwExtra = 2; + WORD patinfo[4]; + BYTE chnmask[64]; + BYTE buf[512]; + MODCOMMAND lastvalue[64]; + FILE *f; + + + if ((!lpszFileName) || ((f = fopen(lpszFileName, "wb")) == NULL)) return FALSE; + memset(inspos, 0, sizeof(inspos)); + memset(patpos, 0, sizeof(patpos)); + memset(smppos, 0, sizeof(smppos)); + // Writing Header + memset(&header, 0, sizeof(header)); + dwPatNamLen = 0; + dwChnNamLen = 0; + header.id = 0x4D504D49; + lstrcpyn(header.songname, m_szNames[0], 27); + header.hilight_minor = m_rowHighlightMinor; + header.hilight_major = m_rowHighlightMajor; + header.ordnum = 0; + while ((header.ordnum < MAX_ORDERS) && (Order[header.ordnum] < 0xFF)) header.ordnum++; + if (header.ordnum < MAX_ORDERS) Order[header.ordnum++] = 0xFF; + header.insnum = m_nInstruments; + header.smpnum = m_nSamples; + header.patnum = MAX_PATTERNS; + while ((header.patnum > 0) && (!Patterns[header.patnum-1])) header.patnum--; + header.cwtv = 0x217; + header.cmwt = 0x200; + header.flags = 0x0001; + header.special = 0x0006; + if (m_dwSongFlags & SONG_INSTRUMENTMODE) header.flags |= 0x04; + if (m_dwSongFlags & SONG_LINEARSLIDES) header.flags |= 0x08; + if (m_dwSongFlags & SONG_ITOLDEFFECTS) header.flags |= 0x10; + if (m_dwSongFlags & SONG_ITCOMPATMODE) header.flags |= 0x20; + if (m_dwSongFlags & SONG_EXFILTERRANGE) header.flags |= 0x1000; + header.globalvol = m_nDefaultGlobalVolume >> 1; + header.mv = m_nSongPreAmp; + header.speed = m_nDefaultSpeed; + header.tempo = m_nDefaultTempo; + header.sep = m_nStereoSeparation; + dwHdrPos = sizeof(header) + header.ordnum; + // Channel Pan and Volume + memset(header.chnpan, 0xFF, 64); + memset(header.chnvol, 64, 64); + for (UINT ich=0; ich> 2; + if (ChnSettings[ich].dwFlags & CHN_SURROUND) header.chnpan[ich] = 100; + header.chnvol[ich] = ChnSettings[ich].nVolume; + if (ChnSettings[ich].dwFlags & CHN_MUTE) header.chnpan[ich] |= 0x80; + if (ChnSettings[ich].szName[0]) + { + dwChnNamLen = (ich+1) * MAX_CHANNELNAME; + } + } + if (dwChnNamLen) dwExtra += dwChnNamLen + 8; +#ifdef SAVEITTIMESTAMP + dwExtra += 8; // Time Stamp +#endif + if (m_dwSongFlags & SONG_EMBEDMIDICFG) + { + header.flags |= 0x80; + header.special |= 0x08; + dwExtra += sizeof(MODMIDICFG); + } + // Pattern Names + if ((m_nPatternNames) && (m_lpszPatternNames)) + { + dwPatNamLen = m_nPatternNames * MAX_PATTERNNAME; + while ((dwPatNamLen >= MAX_PATTERNNAME) && (!m_lpszPatternNames[dwPatNamLen-MAX_PATTERNNAME])) dwPatNamLen -= MAX_PATTERNNAME; + if (dwPatNamLen < MAX_PATTERNNAME) dwPatNamLen = 0; + if (dwPatNamLen) dwExtra += dwPatNamLen + 8; + } + // Mix Plugins + dwExtra += SaveMixPlugins(NULL, TRUE); + // Comments + if (m_lpszSongComments) + { + header.special |= 1; + header.msglength = strlen(m_lpszSongComments)+1; + header.msgoffset = dwHdrPos + dwExtra + header.insnum*4 + header.patnum*4 + header.smpnum*4; + } + // Write file header + fwrite(&header, 1, sizeof(header), f); + fwrite(Order, 1, header.ordnum, f); + if (header.insnum) fwrite(inspos, 4, header.insnum, f); + if (header.smpnum) fwrite(smppos, 4, header.smpnum, f); + if (header.patnum) fwrite(patpos, 4, header.patnum, f); + // Writing editor history information + { +#ifdef SAVEITTIMESTAMP + SYSTEMTIME systime; + FILETIME filetime; + WORD timestamp[4]; + WORD nInfoEx = 1; + memset(timestamp, 0, sizeof(timestamp)); + fwrite(&nInfoEx, 1, 2, f); + GetSystemTime(&systime); + SystemTimeToFileTime(&systime, &filetime); + FileTimeToDosDateTime(&filetime, ×tamp[0], ×tamp[1]); + fwrite(timestamp, 1, 8, f); +#else + WORD nInfoEx = 0; + fwrite(&nInfoEx, 1, 2, f); +#endif + } + // Writing midi cfg + if (header.flags & 0x80) + { + fwrite(&m_MidiCfg, 1, sizeof(MODMIDICFG), f); + } + // Writing pattern names + if (dwPatNamLen) + { + DWORD d = 0x4d414e50; + fwrite(&d, 1, 4, f); + fwrite(&dwPatNamLen, 1, 4, f); + fwrite(m_lpszPatternNames, 1, dwPatNamLen, f); + } + // Writing channel Names + if (dwChnNamLen) + { + DWORD d = 0x4d414e43; + fwrite(&d, 1, 4, f); + fwrite(&dwChnNamLen, 1, 4, f); + UINT nChnNames = dwChnNamLen / MAX_CHANNELNAME; + for (UINT inam=0; inamfilename, 12); + memcpy(iti.name, penv->name, 26); + iti.mbank = penv->wMidiBank; + iti.mpr = penv->nMidiProgram; + iti.mch = penv->nMidiChannel; + iti.nna = penv->nNNA; + iti.dct = penv->nDCT; + iti.dca = penv->nDNA; + iti.fadeout = penv->nFadeOut >> 5; + iti.pps = penv->nPPS; + iti.ppc = penv->nPPC; + iti.gbv = (BYTE)(penv->nGlobalVol << 1); + iti.dfp = (BYTE)penv->nPan >> 2; + if (!(penv->dwFlags & ENV_SETPANNING)) iti.dfp |= 0x80; + iti.rv = penv->nVolSwing; + iti.rp = penv->nPanSwing; + iti.ifc = penv->nIFC; + iti.ifr = penv->nIFR; + iti.nos = 0; + for (UINT i=0; i<120; i++) if (penv->Keyboard[i] < MAX_SAMPLES) + { + UINT smp = penv->Keyboard[i]; + if ((smp) && (!smpcount[smp])) + { + smpcount[smp] = 1; + iti.nos++; + } + iti.keyboard[i*2] = penv->NoteMap[i] - 1; + iti.keyboard[i*2+1] = smp; + } + // Writing Volume envelope + if (penv->dwFlags & ENV_VOLUME) iti.volenv.flags |= 0x01; + if (penv->dwFlags & ENV_VOLLOOP) iti.volenv.flags |= 0x02; + if (penv->dwFlags & ENV_VOLSUSTAIN) iti.volenv.flags |= 0x04; + if (penv->dwFlags & ENV_VOLCARRY) iti.volenv.flags |= 0x08; + iti.volenv.num = (BYTE)penv->VolEnv.nNodes; + iti.volenv.lpb = (BYTE)penv->VolEnv.nLoopStart; + iti.volenv.lpe = (BYTE)penv->VolEnv.nLoopEnd; + iti.volenv.slb = penv->VolEnv.nSustainStart; + iti.volenv.sle = penv->VolEnv.nSustainEnd; + // Writing Panning envelope + if (penv->dwFlags & ENV_PANNING) iti.panenv.flags |= 0x01; + if (penv->dwFlags & ENV_PANLOOP) iti.panenv.flags |= 0x02; + if (penv->dwFlags & ENV_PANSUSTAIN) iti.panenv.flags |= 0x04; + if (penv->dwFlags & ENV_PANCARRY) iti.panenv.flags |= 0x08; + iti.panenv.num = (BYTE)penv->PanEnv.nNodes; + iti.panenv.lpb = (BYTE)penv->PanEnv.nLoopStart; + iti.panenv.lpe = (BYTE)penv->PanEnv.nLoopEnd; + iti.panenv.slb = penv->PanEnv.nSustainStart; + iti.panenv.sle = penv->PanEnv.nSustainEnd; + // Writing Pitch Envelope + if (penv->dwFlags & ENV_PITCH) iti.pitchenv.flags |= 0x01; + if (penv->dwFlags & ENV_PITCHLOOP) iti.pitchenv.flags |= 0x02; + if (penv->dwFlags & ENV_PITCHSUSTAIN) iti.pitchenv.flags |= 0x04; + if (penv->dwFlags & ENV_PITCHCARRY) iti.pitchenv.flags |= 0x08; + if (penv->dwFlags & ENV_FILTER) iti.pitchenv.flags |= 0x80; + iti.pitchenv.num = (BYTE)penv->PitchEnv.nNodes; + iti.pitchenv.lpb = (BYTE)penv->PitchEnv.nLoopStart; + iti.pitchenv.lpe = (BYTE)penv->PitchEnv.nLoopEnd; + iti.pitchenv.slb = (BYTE)penv->PitchEnv.nSustainStart; + iti.pitchenv.sle = (BYTE)penv->PitchEnv.nSustainEnd; + // Writing Envelopes data + for (UINT ev=0; ev<25; ev++) + { + iti.volenv.data[ev*3] = penv->VolEnv.Values[ev]; + iti.volenv.data[ev*3+1] = penv->VolEnv.Ticks[ev] & 0xFF; + iti.volenv.data[ev*3+2] = penv->VolEnv.Ticks[ev] >> 8; + iti.panenv.data[ev*3] = penv->PanEnv.Values[ev] - 32; + iti.panenv.data[ev*3+1] = penv->PanEnv.Ticks[ev] & 0xFF; + iti.panenv.data[ev*3+2] = penv->PanEnv.Ticks[ev] >> 8; + iti.pitchenv.data[ev*3] = penv->PitchEnv.Values[ev] - 32; + iti.pitchenv.data[ev*3+1] = penv->PitchEnv.Ticks[ev] & 0xFF; + iti.pitchenv.data[ev*3+2] = penv->PitchEnv.Ticks[ev] >> 8; + } + } else + // Save Empty Instrument + { + for (UINT i=0; i<120; i++) iti.keyboard[i*2] = i; + iti.ppc = 5*12; + iti.gbv = 128; + iti.dfp = 0x20; + iti.ifc = 0xFF; + } + if (!iti.nos) iti.trkvers = 0; + // Writing instrument + inspos[nins-1] = dwPos; + dwPos += sizeof(ITINSTRUMENT); + fwrite(&iti, 1, sizeof(ITINSTRUMENT), f); + } + // Writing sample headers + memset(&itss, 0, sizeof(itss)); + for (UINT hsmp=0; hsmpcommand; + UINT param = m->param; + UINT vol = 0xFF; + UINT note = m->note; + if (note) b |= 1; + if ((note) && (note < 0x80)) note--; + if (m->instr) b |= 2; + if (m->volcmd) + { + UINT volcmd = m->volcmd; + switch(volcmd) + { + case VOLCMD_VOLUME: vol = m->vol; if (vol > 64) vol = 64; break; + case VOLCMD_PANNING: vol = m->vol + 128; if (vol > 192) vol = 192; break; + case VOLCMD_VOLSLIDEUP: vol = 85 + ConvertVolParam(m->vol); break; + case VOLCMD_VOLSLIDEDOWN: vol = 95 + ConvertVolParam(m->vol); break; + case VOLCMD_FINEVOLUP: vol = 65 + ConvertVolParam(m->vol); break; + case VOLCMD_FINEVOLDOWN: vol = 75 + ConvertVolParam(m->vol); break; + case VOLCMD_VIBRATOSPEED: vol = 203; break; + case VOLCMD_VIBRATO: vol = 203 + ConvertVolParam(m->vol); break; + case VOLCMD_TONEPORTAMENTO: vol = 193 + ConvertVolParam(m->vol); break; + case VOLCMD_PORTADOWN: vol = 105 + ConvertVolParam(m->vol); break; + case VOLCMD_PORTAUP: vol = 115 + ConvertVolParam(m->vol); break; + default: vol = 0xFF; + } + } + if (vol != 0xFF) b |= 4; + if (command) + { + S3MSaveConvert(&command, ¶m, TRUE); + if (command) b |= 8; + } + // Packing information + if (b) + { + // Same note ? + if (b & 1) + { + if ((note == lastvalue[ch].note) && (lastvalue[ch].volcmd & 1)) + { + b &= ~1; + b |= 0x10; + } else + { + lastvalue[ch].note = note; + lastvalue[ch].volcmd |= 1; + } + } + // Same instrument ? + if (b & 2) + { + if ((m->instr == lastvalue[ch].instr) && (lastvalue[ch].volcmd & 2)) + { + b &= ~2; + b |= 0x20; + } else + { + lastvalue[ch].instr = m->instr; + lastvalue[ch].volcmd |= 2; + } + } + // Same volume column byte ? + if (b & 4) + { + if ((vol == lastvalue[ch].vol) && (lastvalue[ch].volcmd & 4)) + { + b &= ~4; + b |= 0x40; + } else + { + lastvalue[ch].vol = vol; + lastvalue[ch].volcmd |= 4; + } + } + // Same command / param ? + if (b & 8) + { + if ((command == lastvalue[ch].command) && (param == lastvalue[ch].param) && (lastvalue[ch].volcmd & 8)) + { + b &= ~8; + b |= 0x80; + } else + { + lastvalue[ch].command = command; + lastvalue[ch].param = param; + lastvalue[ch].volcmd |= 8; + } + } + if (b != chnmask[ch]) + { + chnmask[ch] = b; + buf[len++] = (ch+1) | 0x80; + buf[len++] = b; + } else + { + buf[len++] = ch+1; + } + if (b & 1) buf[len++] = note; + if (b & 2) buf[len++] = m->instr; + if (b & 4) buf[len++] = vol; + if (b & 8) + { + buf[len++] = command; + buf[len++] = param; + } + } + } + buf[len++] = 0; + dwPos += len; + patinfo[0] += len; + fwrite(buf, 1, len, f); + } + fseek(f, dwPatPos, SEEK_SET); + fwrite(patinfo, 8, 1, f); + fseek(f, dwPos, SEEK_SET); + } + // Writing Sample Data + for (UINT nsmp=1; nsmp<=header.smpnum; nsmp++) + { + MODINSTRUMENT *psmp = &Ins[nsmp]; + memset(&itss, 0, sizeof(itss)); + memcpy(itss.filename, psmp->name, 12); + memcpy(itss.name, m_szNames[nsmp], 26); + itss.id = 0x53504D49; + itss.gvl = (BYTE)psmp->nGlobalVol; + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + for (UINT iu=1; iu<=m_nInstruments; iu++) if (Headers[iu]) + { + INSTRUMENTHEADER *penv = Headers[iu]; + for (UINT ju=0; ju<128; ju++) if (penv->Keyboard[ju] == nsmp) + { + itss.flags = 0x01; + break; + } + } + } else + { + itss.flags = 0x01; + } + if (psmp->uFlags & CHN_LOOP) itss.flags |= 0x10; + if (psmp->uFlags & CHN_SUSTAINLOOP) itss.flags |= 0x20; + if (psmp->uFlags & CHN_PINGPONGLOOP) itss.flags |= 0x40; + if (psmp->uFlags & CHN_PINGPONGSUSTAIN) itss.flags |= 0x80; + itss.C5Speed = psmp->nC4Speed; + if (!itss.C5Speed) itss.C5Speed = 8363; + itss.length = psmp->nLength; + itss.loopbegin = psmp->nLoopStart; + itss.loopend = psmp->nLoopEnd; + itss.susloopbegin = psmp->nSustainStart; + itss.susloopend = psmp->nSustainEnd; + itss.vol = psmp->nVolume >> 2; + itss.dfp = psmp->nPan >> 2; + itss.vit = autovibxm2it[psmp->nVibType & 7]; + itss.vis = psmp->nVibRate; + itss.vid = psmp->nVibDepth; + itss.vir = (psmp->nVibSweep < 64) ? psmp->nVibSweep * 4 : 255; + if (psmp->uFlags & CHN_PANNING) itss.dfp |= 0x80; + if ((psmp->pSample) && (psmp->nLength)) itss.cvt = 0x01; + UINT flags = RS_PCM8S; +#ifndef NO_PACKING + if (nPacking) + { + if ((!(psmp->uFlags & (CHN_16BIT|CHN_STEREO))) + && (CanPackSample(psmp->pSample, psmp->nLength, nPacking))) + { + flags = RS_ADPCM4; + itss.cvt = 0xFF; + } + } else +#endif // NO_PACKING + { + if (psmp->uFlags & CHN_STEREO) + { + flags = RS_STPCM8S; + itss.flags |= 0x04; + } + if (psmp->uFlags & CHN_16BIT) + { + itss.flags |= 0x02; + flags = (psmp->uFlags & CHN_STEREO) ? RS_STPCM16S : RS_PCM16S; + } + } + itss.samplepointer = dwPos; + fseek(f, smppos[nsmp-1], SEEK_SET); + fwrite(&itss, 1, sizeof(ITSAMPLESTRUCT), f); + fseek(f, dwPos, SEEK_SET); + if ((psmp->pSample) && (psmp->nLength)) + { + dwPos += WriteSample(f, psmp, flags); + } + } + // Updating offsets + fseek(f, dwHdrPos, SEEK_SET); + if (header.insnum) fwrite(inspos, 4, header.insnum, f); + if (header.smpnum) fwrite(smppos, 4, header.smpnum, f); + if (header.patnum) fwrite(patpos, 4, header.patnum, f); + fclose(f); + return TRUE; +} +#endif + +#pragma warning(default:4100) +#endif // MODPLUG_NO_FILESAVE + +////////////////////////////////////////////////////////////////////////////// +// IT 2.14 compression + +DWORD ITReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n) +//----------------------------------------------------------------- +{ + DWORD retval = 0; + UINT i = n; + + if (n > 0) + { + do + { + if (!bitnum) + { + bitbuf = *ibuf++; + bitnum = 8; + } + retval >>= 1; + retval |= bitbuf << 31; + bitbuf >>= 1; + bitnum--; + i--; + } while (i); + i = n; + } + return (retval >> (32-i)); +} + +#define IT215_SUPPORT +void ITUnpack8Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215) +//------------------------------------------------------------------------------------------- +{ + signed char *pDst = pSample; + LPBYTE pSrc = lpMemFile; + DWORD wHdr = 0; + DWORD wCount = 0; + DWORD bitbuf = 0; + UINT bitnum = 0; + BYTE bLeft = 0, bTemp = 0, bTemp2 = 0; + + while (dwLen) + { + if (!wCount) + { + wCount = 0x8000; + wHdr = bswapLE16(*((LPWORD)pSrc)); + pSrc += 2; + bLeft = 9; + bTemp = bTemp2 = 0; + bitbuf = bitnum = 0; + } + DWORD d = wCount; + if (d > dwLen) d = dwLen; + // Unpacking + DWORD dwPos = 0; + do + { + WORD wBits = (WORD)ITReadBits(bitbuf, bitnum, pSrc, bLeft); + if (bLeft < 7) + { + DWORD i = 1 << (bLeft-1); + DWORD j = wBits & 0xFFFF; + if (i != j) goto UnpackByte; + wBits = (WORD)(ITReadBits(bitbuf, bitnum, pSrc, 3) + 1) & 0xFF; + bLeft = ((BYTE)wBits < bLeft) ? (BYTE)wBits : (BYTE)((wBits+1) & 0xFF); + goto Next; + } + if (bLeft < 9) + { + WORD i = (0xFF >> (9 - bLeft)) + 4; + WORD j = i - 8; + if ((wBits <= j) || (wBits > i)) goto UnpackByte; + wBits -= j; + bLeft = ((BYTE)(wBits & 0xFF) < bLeft) ? (BYTE)(wBits & 0xFF) : (BYTE)((wBits+1) & 0xFF); + goto Next; + } + if (bLeft >= 10) goto SkipByte; + if (wBits >= 256) + { + bLeft = (BYTE)(wBits + 1) & 0xFF; + goto Next; + } + UnpackByte: + if (bLeft < 8) + { + BYTE shift = 8 - bLeft; + signed char c = (signed char)(wBits << shift); + c >>= shift; + wBits = (WORD)c; + } + wBits += bTemp; + bTemp = (BYTE)wBits; + bTemp2 += bTemp; +#ifdef IT215_SUPPORT + pDst[dwPos] = (b215) ? bTemp2 : bTemp; +#else + pDst[dwPos] = bTemp; +#endif + SkipByte: + dwPos++; + Next: + if (pSrc >= lpMemFile+dwMemLength+1) return; + } while (dwPos < d); + // Move On + wCount -= d; + dwLen -= d; + pDst += d; + } +} + + +void ITUnpack16Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215) +//-------------------------------------------------------------------------------------------- +{ + signed short *pDst = (signed short *)pSample; + LPBYTE pSrc = lpMemFile; + DWORD wHdr = 0; + DWORD wCount = 0; + DWORD bitbuf = 0; + UINT bitnum = 0; + BYTE bLeft = 0; + signed short wTemp = 0, wTemp2 = 0; + + while (dwLen) + { + if (!wCount) + { + wCount = 0x4000; + wHdr = bswapLE16(*((LPWORD)pSrc)); + pSrc += 2; + bLeft = 17; + wTemp = wTemp2 = 0; + bitbuf = bitnum = 0; + } + DWORD d = wCount; + if (d > dwLen) d = dwLen; + // Unpacking + DWORD dwPos = 0; + do + { + DWORD dwBits = ITReadBits(bitbuf, bitnum, pSrc, bLeft); + if (bLeft < 7) + { + DWORD i = 1 << (bLeft-1); + DWORD j = dwBits; + if (i != j) goto UnpackByte; + dwBits = ITReadBits(bitbuf, bitnum, pSrc, 4) + 1; + bLeft = ((BYTE)(dwBits & 0xFF) < bLeft) ? (BYTE)(dwBits & 0xFF) : (BYTE)((dwBits+1) & 0xFF); + goto Next; + } + if (bLeft < 17) + { + DWORD i = (0xFFFF >> (17 - bLeft)) + 8; + DWORD j = (i - 16) & 0xFFFF; + if ((dwBits <= j) || (dwBits > (i & 0xFFFF))) goto UnpackByte; + dwBits -= j; + bLeft = ((BYTE)(dwBits & 0xFF) < bLeft) ? (BYTE)(dwBits & 0xFF) : (BYTE)((dwBits+1) & 0xFF); + goto Next; + } + if (bLeft >= 18) goto SkipByte; + if (dwBits >= 0x10000) + { + bLeft = (BYTE)(dwBits + 1) & 0xFF; + goto Next; + } + UnpackByte: + if (bLeft < 16) + { + BYTE shift = 16 - bLeft; + signed short c = (signed short)(dwBits << shift); + c >>= shift; + dwBits = (DWORD)c; + } + dwBits += wTemp; + wTemp = (signed short)dwBits; + wTemp2 += wTemp; +#ifdef IT215_SUPPORT + pDst[dwPos] = (b215) ? wTemp2 : wTemp; +#else + pDst[dwPos] = wTemp; +#endif + SkipByte: + dwPos++; + Next: + if (pSrc >= lpMemFile+dwMemLength+1) return; + } while (dwPos < d); + // Move On + wCount -= d; + dwLen -= d; + pDst += d; + if (pSrc >= lpMemFile+dwMemLength) break; + } +} + + +UINT CSoundFile::SaveMixPlugins(FILE *f, BOOL bUpdate) +//---------------------------------------------------- +{ + DWORD chinfo[64]; + CHAR s[32]; + DWORD nPluginSize; + UINT nTotalSize = 0; + UINT nChInfo = 0; + + for (UINT i=0; iInfo.dwPluginId1) || (p->Info.dwPluginId2)) + { + nPluginSize = sizeof(SNDMIXPLUGININFO)+4; // plugininfo+4 (datalen) + if ((p->pMixPlugin) && (bUpdate)) + { + p->pMixPlugin->SaveAllParameters(); + } + if (p->pPluginData) + { + nPluginSize += p->nPluginDataSize; + } + if (f) + { + s[0] = 'F'; + s[1] = 'X'; + s[2] = '0' + (i/10); + s[3] = '0' + (i%10); + fwrite(s, 1, 4, f); + fwrite(&nPluginSize, 1, 4, f); + fwrite(&p->Info, 1, sizeof(SNDMIXPLUGININFO), f); + fwrite(&m_MixPlugins[i].nPluginDataSize, 1, 4, f); + if (m_MixPlugins[i].pPluginData) + { + fwrite(m_MixPlugins[i].pPluginData, 1, m_MixPlugins[i].nPluginDataSize, f); + } + } + nTotalSize += nPluginSize + 8; + } + } + for (UINT j=0; j nLen-nPos-8) break;; + if ((bswapLE32(*(DWORD *)(p+nPos))) == 0x58464843) + { + for (UINT ch=0; ch<64; ch++) if (ch*4 < nPluginSize) + { + ChnSettings[ch].nMixPlugin = bswapLE32(*(DWORD *)(p+nPos+8+ch*4)); + } + } else + { + if ((p[nPos] != 'F') || (p[nPos+1] != 'X') + || (p[nPos+2] < '0') || (p[nPos+3] < '0')) + { + break; + } + nPlugin = (p[nPos+2]-'0')*10 + (p[nPos+3]-'0'); + if ((nPlugin < MAX_MIXPLUGINS) && (nPluginSize >= sizeof(SNDMIXPLUGININFO)+4)) + { + DWORD dwExtra = bswapLE32(*(DWORD *)(p+nPos+8+sizeof(SNDMIXPLUGININFO))); + m_MixPlugins[nPlugin].Info = *(const SNDMIXPLUGININFO *)(p+nPos+8); + m_MixPlugins[nPlugin].Info.dwPluginId1 = bswapLE32(m_MixPlugins[nPlugin].Info.dwPluginId1); + m_MixPlugins[nPlugin].Info.dwPluginId2 = bswapLE32(m_MixPlugins[nPlugin].Info.dwPluginId2); + m_MixPlugins[nPlugin].Info.dwInputRouting = bswapLE32(m_MixPlugins[nPlugin].Info.dwInputRouting); + m_MixPlugins[nPlugin].Info.dwOutputRouting = bswapLE32(m_MixPlugins[nPlugin].Info.dwOutputRouting); + for (UINT j=0; j<4; j++) + { + m_MixPlugins[nPlugin].Info.dwReserved[j] = bswapLE32(m_MixPlugins[nPlugin].Info.dwReserved[j]); + } + if ((dwExtra) && (dwExtra <= nPluginSize-sizeof(SNDMIXPLUGININFO)-4)) + { + m_MixPlugins[nPlugin].nPluginDataSize = 0; + m_MixPlugins[nPlugin].pPluginData = new signed char [dwExtra]; + if (m_MixPlugins[nPlugin].pPluginData) + { + m_MixPlugins[nPlugin].nPluginDataSize = dwExtra; + memcpy(m_MixPlugins[nPlugin].pPluginData, p+nPos+8+sizeof(SNDMIXPLUGININFO)+4, dwExtra); + } + } + } + } + nPos += nPluginSize + 8; + } + return nPos; +} + diff --git a/modplug/load_mdl.cpp b/modplug/load_mdl.cpp new file mode 100644 index 000000000..1b236a803 --- /dev/null +++ b/modplug/load_mdl.cpp @@ -0,0 +1,530 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +////////////////////////////////////////////// +// DigiTracker (MDL) module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +typedef struct MDLSONGHEADER +{ + DWORD id; // "DMDL" = 0x4C444D44 + BYTE version; +} MDLSONGHEADER; + + +typedef struct MDLINFOBLOCK +{ + CHAR songname[32]; + CHAR composer[20]; + WORD norders; + WORD repeatpos; + BYTE globalvol; + BYTE speed; + BYTE tempo; + BYTE channelinfo[32]; + BYTE seq[256]; +} MDLINFOBLOCK; + + +typedef struct MDLPATTERNDATA +{ + BYTE channels; + BYTE lastrow; // nrows = lastrow+1 + CHAR name[16]; + WORD data[1]; +} MDLPATTERNDATA; + + +void ConvertMDLCommand(MODCOMMAND *m, UINT eff, UINT data) +//-------------------------------------------------------- +{ + UINT command = 0, param = data; + switch(eff) + { + case 0x01: command = CMD_PORTAMENTOUP; break; + case 0x02: command = CMD_PORTAMENTODOWN; break; + case 0x03: command = CMD_TONEPORTAMENTO; break; + case 0x04: command = CMD_VIBRATO; break; + case 0x05: command = CMD_ARPEGGIO; break; + case 0x07: command = (param < 0x20) ? CMD_SPEED : CMD_TEMPO; break; + case 0x08: command = CMD_PANNING8; param <<= 1; break; + case 0x0B: command = CMD_POSITIONJUMP; break; + case 0x0C: command = CMD_GLOBALVOLUME; break; + case 0x0D: command = CMD_PATTERNBREAK; param = (data & 0x0F) + (data>>4)*10; break; + case 0x0E: + command = CMD_S3MCMDEX; + switch(data & 0xF0) + { + case 0x00: command = 0; break; // What is E0x in MDL (there is a bunch) ? + case 0x10: if (param & 0x0F) { param |= 0xF0; command = CMD_PANNINGSLIDE; } else command = 0; break; + case 0x20: if (param & 0x0F) { param = (param << 4) | 0x0F; command = CMD_PANNINGSLIDE; } else command = 0; break; + case 0x30: param = (data & 0x0F) | 0x10; break; // glissando + case 0x40: param = (data & 0x0F) | 0x30; break; // vibrato waveform + case 0x60: param = (data & 0x0F) | 0xB0; break; + case 0x70: param = (data & 0x0F) | 0x40; break; // tremolo waveform + case 0x90: command = CMD_RETRIG; param &= 0x0F; break; + case 0xA0: param = (data & 0x0F) << 4; command = CMD_GLOBALVOLSLIDE; break; + case 0xB0: param = data & 0x0F; command = CMD_GLOBALVOLSLIDE; break; + case 0xF0: param = ((data >> 8) & 0x0F) | 0xA0; break; + } + break; + case 0x0F: command = CMD_SPEED; break; + + case 0x10: + if ((param & 0xF0) != 0xE0) { + command = CMD_VOLUMESLIDE; + if ((param & 0xF0) == 0xF0) { + param = ((param << 4) | 0x0F); + } else { + param >>= 2; + if (param > 0xF) + param = 0xF; + param <<= 4; + } + } + break; + case 0x20: + if ((param & 0xF0) != 0xE0) { + command = CMD_VOLUMESLIDE; + if ((param & 0xF0) != 0xF0) { + param >>= 2; + if (param > 0xF) + param = 0xF; + } + } + break; + + case 0x30: command = CMD_RETRIG; break; + case 0x40: command = CMD_TREMOLO; break; + case 0x50: command = CMD_TREMOR; break; + case 0xEF: if (param > 0xFF) param = 0xFF; command = CMD_OFFSET; break; + } + if (command) + { + m->command = command; + m->param = param; + } +} + + +void UnpackMDLTrack(MODCOMMAND *pat, UINT nChannels, UINT nRows, UINT nTrack, const BYTE *lpTracks) +//------------------------------------------------------------------------------------------------- +{ + MODCOMMAND cmd, *m = pat; + UINT len = *((WORD *)lpTracks); + UINT pos = 0, row = 0, i; + lpTracks += 2; + for (UINT ntrk=1; ntrk> 2; + switch(b & 0x03) + { + case 0x01: + for (i=0; i<=xx; i++) + { + if (row) *m = *(m-nChannels); + m += nChannels; + row++; + if (row >= nRows) break; + } + break; + + case 0x02: + if (xx < row) *m = pat[nChannels*xx]; + m += nChannels; + row++; + break; + + case 0x03: + { + cmd.note = (xx & 0x01) ? lpTracks[pos++] : 0; + cmd.instr = (xx & 0x02) ? lpTracks[pos++] : 0; + cmd.volcmd = cmd.vol = 0; + cmd.command = cmd.param = 0; + if ((cmd.note < 120-12) && (cmd.note)) cmd.note += 12; + UINT volume = (xx & 0x04) ? lpTracks[pos++] : 0; + UINT commands = (xx & 0x08) ? lpTracks[pos++] : 0; + UINT command1 = commands & 0x0F; + UINT command2 = commands & 0xF0; + UINT param1 = (xx & 0x10) ? lpTracks[pos++] : 0; + UINT param2 = (xx & 0x20) ? lpTracks[pos++] : 0; + if ((command1 == 0x0E) && ((param1 & 0xF0) == 0xF0) && (!command2)) + { + param1 = ((param1 & 0x0F) << 8) | param2; + command1 = 0xEF; + command2 = param2 = 0; + } + if (volume) + { + cmd.volcmd = VOLCMD_VOLUME; + cmd.vol = (volume+1) >> 2; + } + ConvertMDLCommand(&cmd, command1, param1); + if ((cmd.command != CMD_SPEED) + && (cmd.command != CMD_TEMPO) + && (cmd.command != CMD_PATTERNBREAK)) + ConvertMDLCommand(&cmd, command2, param2); + *m = cmd; + m += nChannels; + row++; + } + break; + + // Empty Slots + default: + row += xx+1; + m += (xx+1)*nChannels; + if (row >= nRows) break; + } + } +} + + + +BOOL CSoundFile::ReadMDL(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + DWORD dwMemPos, dwPos, blocklen, dwTrackPos; + const MDLSONGHEADER *pmsh = (const MDLSONGHEADER *)lpStream; + MDLINFOBLOCK *pmib; + MDLPATTERNDATA *pmpd; + UINT i,j, norders = 0, npatterns = 0, ntracks = 0; + UINT ninstruments = 0, nsamples = 0; + WORD block; + WORD patterntracks[MAX_PATTERNS*32]; + BYTE smpinfo[MAX_SAMPLES]; + BYTE insvolenv[MAX_INSTRUMENTS]; + BYTE inspanenv[MAX_INSTRUMENTS]; + LPCBYTE pvolenv, ppanenv, ppitchenv; + UINT nvolenv, npanenv, npitchenv; + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pmsh->id != 0x4C444D44) || ((pmsh->version & 0xF0) > 0x10)) return FALSE; + memset(patterntracks, 0, sizeof(patterntracks)); + memset(smpinfo, 0, sizeof(smpinfo)); + memset(insvolenv, 0, sizeof(insvolenv)); + memset(inspanenv, 0, sizeof(inspanenv)); + dwMemPos = 5; + dwTrackPos = 0; + pvolenv = ppanenv = ppitchenv = NULL; + nvolenv = npanenv = npitchenv = 0; + m_nSamples = m_nInstruments = 0; + m_dwSongFlags |= SONG_INSTRUMENTMODE; + while (dwMemPos+6 < dwMemLength) + { + block = *((WORD *)(lpStream+dwMemPos)); + blocklen = *((DWORD *)(lpStream+dwMemPos+2)); + dwMemPos += 6; + if (dwMemPos + blocklen > dwMemLength) + { + if (dwMemPos == 11) return FALSE; + break; + } + switch(block) + { + // IN: infoblock + case 0x4E49: + pmib = (MDLINFOBLOCK *)(lpStream+dwMemPos); + memcpy(m_szNames[0], pmib->songname, 32); + norders = pmib->norders; + if (norders > MAX_ORDERS) norders = MAX_ORDERS; + m_nRestartPos = pmib->repeatpos; + m_nDefaultGlobalVolume = pmib->globalvol; + if (m_nDefaultGlobalVolume == 255) + m_nDefaultGlobalVolume++; + m_nDefaultTempo = pmib->tempo; + m_nDefaultSpeed = pmib->speed; + m_nChannels = 4; + for (i=0; i<32; i++) + { + ChnSettings[i].nVolume = 64; + ChnSettings[i].nPan = (pmib->channelinfo[i] & 0x7F) << 1; + if (pmib->channelinfo[i] & 0x80) + ChnSettings[i].dwFlags |= CHN_MUTE; + else + m_nChannels = i+1; + } + for (j=0; jseq[j]; + break; + // ME: song message + case 0x454D: + if (blocklen) + { + if (m_lpszSongComments) delete m_lpszSongComments; + m_lpszSongComments = new char[blocklen]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos, blocklen); + m_lpszSongComments[blocklen-1] = 0; + } + } + break; + // PA: Pattern Data + case 0x4150: + npatterns = lpStream[dwMemPos]; + if (npatterns > MAX_PATTERNS) npatterns = MAX_PATTERNS; + dwPos = dwMemPos + 1; + for (i=0; i= dwMemLength) break; + pmpd = (MDLPATTERNDATA *)(lpStream + dwPos); + if (pmpd->channels > 32) break; + PatternSize[i] = pmpd->lastrow+1; + PatternAllocSize[i] = pmpd->lastrow+1; + if (m_nChannels < pmpd->channels) m_nChannels = pmpd->channels; + dwPos += 18 + 2*pmpd->channels; + for (j=0; jchannels; j++) + { + patterntracks[i*32+j] = pmpd->data[j]; + } + } + break; + // TR: Track Data + case 0x5254: + if (dwTrackPos) break; + ntracks = *((WORD *)(lpStream+dwMemPos)); + dwTrackPos = dwMemPos+2; + break; + // II: Instruments + case 0x4949: + ninstruments = lpStream[dwMemPos]; + dwPos = dwMemPos+1; + for (i=0; i= MAX_INSTRUMENTS) || (!nins)) break; + if (m_nInstruments < nins) m_nInstruments = nins; + if (!Headers[nins]) + { + UINT note = 12; + if ((Headers[nins] = new INSTRUMENTHEADER) == NULL) break; + INSTRUMENTHEADER *penv = Headers[nins]; + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + memcpy(penv->name, lpStream+dwPos+2, 32); + penv->nGlobalVol = 64; + penv->nPPC = 5*12; + for (j=0; jNoteMap[note] = note+1; + if (ps[0] < MAX_SAMPLES) + { + int ismp = ps[0]; + penv->Keyboard[note] = ps[0]; + Ins[ismp].nVolume = ps[2]; + Ins[ismp].nPan = ps[4] << 1; + Ins[ismp].nVibType = ps[11]; + Ins[ismp].nVibSweep = ps[10]; + Ins[ismp].nVibDepth = ps[9]; + Ins[ismp].nVibRate = ps[8]; + } + penv->nFadeOut = (ps[7] << 8) | ps[6]; + if (penv->nFadeOut == 0xFFFF) penv->nFadeOut = 0; + note++; + } + // Use volume envelope ? + if (ps[3] & 0x80) + { + penv->dwFlags |= ENV_VOLUME; + insvolenv[nins] = (ps[3] & 0x3F) + 1; + } + // Use panning envelope ? + if (ps[5] & 0x80) + { + penv->dwFlags |= ENV_PANNING; + inspanenv[nins] = (ps[5] & 0x3F) + 1; + } + } + } + dwPos += 34 + 14*lpStream[dwPos+1]; + } + for (j=1; j<=m_nInstruments; j++) if (!Headers[j]) + { + Headers[j] = new INSTRUMENTHEADER; + if (Headers[j]) memset(Headers[j], 0, sizeof(INSTRUMENTHEADER)); + } + break; + // VE: Volume Envelope + case 0x4556: + if ((nvolenv = lpStream[dwMemPos]) == 0) break; + if (dwMemPos + nvolenv*32 + 1 <= dwMemLength) pvolenv = lpStream + dwMemPos + 1; + break; + // PE: Panning Envelope + case 0x4550: + if ((npanenv = lpStream[dwMemPos]) == 0) break; + if (dwMemPos + npanenv*32 + 1 <= dwMemLength) ppanenv = lpStream + dwMemPos + 1; + break; + // FE: Pitch Envelope + case 0x4546: + if ((npitchenv = lpStream[dwMemPos]) == 0) break; + if (dwMemPos + npitchenv*32 + 1 <= dwMemLength) ppitchenv = lpStream + dwMemPos + 1; + break; + // IS: Sample Infoblock + case 0x5349: + nsamples = lpStream[dwMemPos]; + dwPos = dwMemPos+1; + for (i=0; i= MAX_SAMPLES) || (!nins)) continue; + if (m_nSamples < nins) m_nSamples = nins; + MODINSTRUMENT *pins = &Ins[nins]; + memcpy(m_szNames[nins], lpStream+dwPos+1, 32); + memcpy(pins->name, lpStream+dwPos+33, 8); + pins->nC4Speed = *((DWORD *)(lpStream+dwPos+41)); + pins->nLength = *((DWORD *)(lpStream+dwPos+45)); + pins->nLoopStart = *((DWORD *)(lpStream+dwPos+49)); + pins->nLoopEnd = pins->nLoopStart + *((DWORD *)(lpStream+dwPos+53)); + if (pins->nLoopEnd > pins->nLoopStart) pins->uFlags |= CHN_LOOP; + pins->nGlobalVol = 64; + if (lpStream[dwPos+58] & 0x01) + { + pins->uFlags |= CHN_16BIT; + pins->nLength >>= 1; + pins->nLoopStart >>= 1; + pins->nLoopEnd >>= 1; + } + if (lpStream[dwPos+58] & 0x02) pins->uFlags |= CHN_PINGPONGLOOP; + smpinfo[nins] = (lpStream[dwPos+58] >> 2) & 3; + } + break; + // SA: Sample Data + case 0x4153: + dwPos = dwMemPos; + for (i=1; i<=m_nSamples; i++) if ((Ins[i].nLength) && (!Ins[i].pSample) && (smpinfo[i] != 3) && (dwPos < dwMemLength)) + { + MODINSTRUMENT *pins = &Ins[i]; + UINT flags = (pins->uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S; + if (!smpinfo[i]) + { + dwPos += ReadSample(pins, flags, (LPSTR)(lpStream+dwPos), dwMemLength - dwPos); + } else + { + DWORD dwLen = *((DWORD *)(lpStream+dwPos)); + dwPos += 4; + if ((dwPos+dwLen <= dwMemLength) && (dwLen > 4)) + { + flags = (pins->uFlags & CHN_16BIT) ? RS_MDL16 : RS_MDL8; + ReadSample(pins, flags, (LPSTR)(lpStream+dwPos), dwLen); + } + dwPos += dwLen; + } + } + break; + } + dwMemPos += blocklen; + } + // Unpack Patterns + if ((dwTrackPos) && (npatterns) && (m_nChannels) && (ntracks)) + { + for (UINT ipat=0; ipatVolEnv.nNodes = 15; + for (UINT iv=0; iv<15; iv++) + { + if (iv) vtick += pve[iv*2+1]; + penv->VolEnv.Ticks[iv] = vtick; + penv->VolEnv.Values[iv] = pve[iv*2+2]; + if (!pve[iv*2+1]) + { + penv->VolEnv.nNodes = iv+1; + break; + } + } + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = pve[31] & 0x0F; + if (pve[31] & 0x10) penv->dwFlags |= ENV_VOLSUSTAIN; + if (pve[31] & 0x20) penv->dwFlags |= ENV_VOLLOOP; + penv->VolEnv.nLoopStart = pve[32] & 0x0F; + penv->VolEnv.nLoopEnd = pve[32] >> 4; + } + } + // Setup panning envelope + if ((npanenv) && (ppanenv) && (inspanenv[iIns])) + { + LPCBYTE ppe = ppanenv; + for (UINT npe=0; npePanEnv.nNodes = 15; + for (UINT iv=0; iv<15; iv++) + { + if (iv) vtick += ppe[iv*2+1]; + penv->PanEnv.Ticks[iv] = vtick; + penv->PanEnv.Values[iv] = ppe[iv*2+2]; + if (!ppe[iv*2+1]) + { + penv->PanEnv.nNodes = iv+1; + break; + } + } + if (ppe[31] & 0x10) penv->dwFlags |= ENV_PANSUSTAIN; + if (ppe[31] & 0x20) penv->dwFlags |= ENV_PANLOOP; + penv->PanEnv.nLoopStart = ppe[32] & 0x0F; + penv->PanEnv.nLoopEnd = ppe[32] >> 4; + } + } + } + m_dwSongFlags |= SONG_LINEARSLIDES; + m_nType = MOD_TYPE_MDL; + return TRUE; +} + + +///////////////////////////////////////////////////////////////////////// +// MDL Sample Unpacking + +// MDL Huffman ReadBits compression +WORD MDLReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n) +//----------------------------------------------------------------- +{ + WORD v = (WORD)(bitbuf & ((1 << n) - 1) ); + bitbuf >>= n; + bitnum -= n; + if (bitnum <= 24) + { + bitbuf |= (((DWORD)(*ibuf++)) << bitnum); + bitnum += 8; + } + return v; +} + + diff --git a/modplug/load_med.cpp b/modplug/load_med.cpp new file mode 100644 index 000000000..858ef5e4a --- /dev/null +++ b/modplug/load_med.cpp @@ -0,0 +1,919 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#define MED_LOG + +#ifdef MED_LOG +extern void Log(LPCSTR s, ...); +#endif + +////////////////////////////////////////////////////////// +// OctaMed MED file support (import only) + +// flags +#define MMD_FLAG_FILTERON 0x1 +#define MMD_FLAG_JUMPINGON 0x2 +#define MMD_FLAG_JUMP8TH 0x4 +#define MMD_FLAG_INSTRSATT 0x8 // instruments are attached (this is a module) +#define MMD_FLAG_VOLHEX 0x10 +#define MMD_FLAG_STSLIDE 0x20 // SoundTracker mode for slides +#define MMD_FLAG_8CHANNEL 0x40 // OctaMED 8 channel song +#define MMD_FLAG_SLOWHQ 0x80 // HQ slows playing speed (V2-V4 compatibility) +// flags2 +#define MMD_FLAG2_BMASK 0x1F +#define MMD_FLAG2_BPM 0x20 +#define MMD_FLAG2_MIX 0x80 // uses Mixing (V7+) +// flags3: +#define MMD_FLAG3_STEREO 0x1 // mixing in Stereo mode +#define MMD_FLAG3_FREEPAN 0x2 // free panning +#define MMD_FLAG3_GM 0x4 // module designed for GM/XG compatibility + + +// generic MMD tags +#define MMDTAG_END 0 +#define MMDTAG_PTR 0x80000000 // data needs relocation +#define MMDTAG_MUSTKNOW 0x40000000 // loader must fail if this isn't recognized +#define MMDTAG_MUSTWARN 0x20000000 // loader must warn if this isn't recognized + +// ExpData tags +// # of effect groups, including the global group (will +// override settings in MMDSong struct), default = 1 +#define MMDTAG_EXP_NUMFXGROUPS 1 +#define MMDTAG_TRK_NAME (MMDTAG_PTR|1) // trackinfo tags +#define MMDTAG_TRK_NAMELEN 2 // namelen includes zero term. +#define MMDTAG_TRK_FXGROUP 3 +// effectinfo tags +#define MMDTAG_FX_ECHOTYPE 1 +#define MMDTAG_FX_ECHOLEN 2 +#define MMDTAG_FX_ECHODEPTH 3 +#define MMDTAG_FX_STEREOSEP 4 +#define MMDTAG_FX_GROUPNAME (MMDTAG_PTR|5) // the Global Effects group shouldn't have name saved! +#define MMDTAG_FX_GRPNAMELEN 6 // namelen includes zero term. + +#pragma pack(1) + +typedef struct tagMEDMODULEHEADER +{ + DWORD id; // MMD1-MMD3 + DWORD modlen; // Size of file + DWORD song; // Position in file for this song + WORD psecnum; + WORD pseq; + DWORD blockarr; // Position in file for blocks + DWORD mmdflags; + DWORD smplarr; // Position in file for samples + DWORD reserved; + DWORD expdata; // Absolute offset in file for ExpData (0 if not present) + DWORD reserved2; + WORD pstate; + WORD pblock; + WORD pline; + WORD pseqnum; + WORD actplayline; + BYTE counter; + BYTE extra_songs; // # of songs - 1 +} MEDMODULEHEADER; + + +typedef struct tagMMD0SAMPLE +{ + WORD rep, replen; + BYTE midich; + BYTE midipreset; + BYTE svol; + signed char strans; +} MMD0SAMPLE; + + +// Sample header is immediately followed by sample data... +typedef struct tagMMDSAMPLEHEADER +{ + DWORD length; // length of *one* *unpacked* channel in *bytes* + WORD type; + // if non-negative + // bits 0-3 reserved for multi-octave instruments, not supported on the PC + // 0x10: 16 bit (otherwise 8 bit) + // 0x20: Stereo (otherwise mono) + // 0x40: Uses DeltaCode + // 0x80: Packed data + // -1: Synth + // -2: Hybrid + // if type indicates packed data, these fields follow, otherwise we go right to the data + WORD packtype; // Only 1 = ADPCM is supported + WORD subtype; // Packing subtype + // ADPCM subtype + // 1: g723_40 + // 2: g721 + // 3: g723_24 + BYTE commonflags; // flags common to all packtypes (none defined so far) + BYTE packerflags; // flags for the specific packtype + ULONG leftchlen; // packed length of left channel in bytes + ULONG rightchlen; // packed length of right channel in bytes (ONLY PRESENT IN STEREO SAMPLES) + BYTE SampleData[1]; // Sample Data +} MMDSAMPLEHEADER; + + +// MMD0/MMD1 song header +typedef struct tagMMD0SONGHEADER +{ + MMD0SAMPLE sample[63]; + WORD numblocks; // # of blocks + WORD songlen; // # of entries used in playseq + BYTE playseq[256]; // Play sequence + WORD deftempo; // BPM tempo + signed char playtransp; // Play transpose + BYTE flags; // 0x10: Hex Volumes | 0x20: ST/NT/PT Slides | 0x40: 8 Channels song + BYTE flags2; // [b4-b0]+1: Tempo LPB, 0x20: tempo mode, 0x80: mix_conv=on + BYTE tempo2; // tempo TPL + BYTE trkvol[16]; // track volumes + BYTE mastervol; // master volume + BYTE numsamples; // # of samples (max=63) +} MMD0SONGHEADER; + + +// MMD2/MMD3 song header +typedef struct tagMMD2SONGHEADER +{ + MMD0SAMPLE sample[63]; + WORD numblocks; // # of blocks + WORD numsections; // # of sections + DWORD playseqtable; // filepos of play sequence + DWORD sectiontable; // filepos of sections table (WORD array) + DWORD trackvols; // filepos of tracks volume (BYTE array) + WORD numtracks; // # of tracks (max 64) + WORD numpseqs; // # of play sequences + DWORD trackpans; // filepos of tracks pan values (BYTE array) + LONG flags3; // 0x1:stereo_mix, 0x2:free_panning, 0x4:GM/XG compatibility + WORD voladj; // vol_adjust (set to 100 if 0) + WORD channels; // # of channels (4 if =0) + BYTE mix_echotype; // 1:normal,2:xecho + BYTE mix_echodepth; // 1..6 + WORD mix_echolen; // > 0 + signed char mix_stereosep; // -4..4 + BYTE pad0[223]; + WORD deftempo; // BPM tempo + signed char playtransp; // play transpose + BYTE flags; // 0x1:filteron, 0x2:jumpingon, 0x4:jump8th, 0x8:instr_attached, 0x10:hex_vol, 0x20:PT_slides, 0x40:8ch_conv,0x80:hq slows playing speed + BYTE flags2; // 0x80:mix_conv=on, [b4-b0]+1:tempo LPB, 0x20:tempo_mode + BYTE tempo2; // tempo TPL + BYTE pad1[16]; + BYTE mastervol; // master volume + BYTE numsamples; // # of samples (max 63) +} MMD2SONGHEADER; + +// For MMD0 the note information is held in 3 bytes, byte0, byte1, byte2. For reference we +// number the bits in each byte 0..7, where 0 is the low bit. +// The note is held as bits 5..0 of byte0 +// The instrument is encoded in 6 bits, bits 7 and 6 of byte0 and bits 7,6,5,4 of byte1 +// The command number is bits 3,2,1,0 of byte1, command data is in byte2: +// For command 0, byte2 represents the second data byte, otherwise byte2 +// represents the first data byte. +typedef struct tagMMD0BLOCK +{ + BYTE numtracks; + BYTE lines; // File value is 1 less than actual, so 0 -> 1 line +} MMD0BLOCK; // BYTE data[lines+1][tracks][3]; + + +// For MMD1,MMD2,MMD3 the note information is carried in 4 bytes, byte0, byte1, +// byte2 and byte3 +// The note is held as byte0 (values above 0x84 are ignored) +// The instrument is held as byte1 +// The command number is held as byte2, command data is in byte3 +// For commands 0 and 0x19 byte3 represents the second data byte, +// otherwise byte2 represents the first data byte. +typedef struct tagMMD1BLOCK +{ + WORD numtracks; // Number of tracks, may be > 64, but then that data is skipped. + WORD lines; // Stored value is 1 less than actual, so 0 -> 1 line + DWORD info; // Offset of BlockInfo (if 0, no block_info is present) +} MMD1BLOCK; + + +typedef struct tagMMD1BLOCKINFO +{ + DWORD hlmask; // Unimplemented - ignore + DWORD blockname; // file offset of block name + DWORD blocknamelen; // length of block name (including term. 0) + DWORD pagetable; // file offset of command page table + DWORD cmdexttable; // file offset of command extension table + DWORD reserved[4]; // future expansion +} MMD1BLOCKINFO; + + +// A set of play sequences is stored as an array of ULONG files offsets +// Each offset points to the play sequence itself. +typedef struct tagMMD2PLAYSEQ +{ + CHAR name[32]; + DWORD command_offs; // filepos of command table + DWORD reserved; + WORD length; + WORD seq[512]; // skip if > 0x8000 +} MMD2PLAYSEQ; + + +// A command table contains commands that effect a particular play sequence +// entry. The only commands read in are STOP or POSJUMP, all others are ignored +// POSJUMP is presumed to have extra bytes containing a WORD for the position +typedef struct tagMMDCOMMAND +{ + WORD offset; // Offset within current sequence entry + BYTE cmdnumber; // STOP (537) or POSJUMP (538) (others skipped) + BYTE extra_count; + BYTE extra_bytes[4];// [extra_count]; +} MMDCOMMAND; // Last entry has offset == 0xFFFF, cmd_number == 0 and 0 extrabytes + + +typedef struct tagMMD0EXP +{ + DWORD nextmod; // File offset of next Hdr + DWORD exp_smp; // Pointer to extra instrument data + WORD s_ext_entries; // Number of extra instrument entries + WORD s_ext_entrsz; // Size of extra instrument data + DWORD annotxt; + DWORD annolen; + DWORD iinfo; // Instrument names + WORD i_ext_entries; + WORD i_ext_entrsz; + DWORD jumpmask; + DWORD rgbtable; + BYTE channelsplit[4]; // Only used if 8ch_conv (extra channel for every nonzero entry) + DWORD n_info; + DWORD songname; // Song name + DWORD songnamelen; + DWORD dumps; + DWORD mmdinfo; + DWORD mmdrexx; + DWORD mmdcmd3x; + DWORD trackinfo_ofs; // ptr to song->numtracks ptrs to tag lists + DWORD effectinfo_ofs; // ptr to group ptrs + DWORD tag_end; +} MMD0EXP; + +#pragma pack() + + + +static void MedConvert(MODCOMMAND *p, const MMD0SONGHEADER *pmsh) +//--------------------------------------------------------------- +{ + const BYTE bpmvals[9] = { 179,164,152,141,131,123,116,110,104}; + + UINT command = p->command; + UINT param = p->param; + switch(command) + { + case 0x00: if (param) command = CMD_ARPEGGIO; else command = 0; break; + case 0x01: command = CMD_PORTAMENTOUP; break; + case 0x02: command = CMD_PORTAMENTODOWN; break; + case 0x03: command = CMD_TONEPORTAMENTO; break; + case 0x04: command = CMD_VIBRATO; break; + case 0x05: command = CMD_TONEPORTAVOL; break; + case 0x06: command = CMD_VIBRATOVOL; break; + case 0x07: command = CMD_TREMOLO; break; + case 0x0A: if (param & 0xF0) param &= 0xF0; command = CMD_VOLUMESLIDE; if (!param) command = 0; break; + case 0x0B: command = CMD_POSITIONJUMP; break; + case 0x0C: command = CMD_VOLUME; + if (pmsh->flags & MMD_FLAG_VOLHEX) + { + if (param < 0x80) + { + param = (param+1) / 2; + } else command = 0; + } else + { + if (param <= 0x99) + { + param = (param >> 4)*10+((param & 0x0F) % 10); + if (param > 64) param = 64; + } else command = 0; + } + break; + case 0x09: command = (param < 0x20) ? CMD_SPEED : CMD_TEMPO; break; + case 0x0D: if (param & 0xF0) param &= 0xF0; command = CMD_VOLUMESLIDE; if (!param) command = 0; break; + case 0x0F: // Set Tempo / Special + // F.00 = Pattern Break + if (!param) command = CMD_PATTERNBREAK; else + // F.01 - F.F0: Set tempo/speed + if (param <= 0xF0) + { + if (pmsh->flags & MMD_FLAG_8CHANNEL) + { + param = (param > 10) ? 99 : bpmvals[param-1]; + } else + // F.01 - F.0A: Set Speed + if (param <= 0x0A) + { + command = CMD_SPEED; + } else + // Old tempo + if (!(pmsh->flags2 & MMD_FLAG2_BPM)) + { + param = _muldiv(param, 5*715909, 2*474326); + } + // F.0B - F.F0: Set Tempo (assumes LPB=4) + if (param > 0x0A) + { + command = CMD_TEMPO; + if (param < 0x21) param = 0x21; + if (param > 240) param = 240; + } + } else + switch(param) + { + // F.F1: Retrig 2x + case 0xF1: + command = CMD_MODCMDEX; + param = 0x93; + break; + // F.F2: Note Delay 2x + case 0xF2: + command = CMD_MODCMDEX; + param = 0xD3; + break; + // F.F3: Retrig 3x + case 0xF3: + command = CMD_MODCMDEX; + param = 0x92; + break; + // F.F4: Note Delay 1/3 + case 0xF4: + command = CMD_MODCMDEX; + param = 0xD2; + break; + // F.F5: Note Delay 2/3 + case 0xF5: + command = CMD_MODCMDEX; + param = 0xD4; + break; + // F.F8: Filter Off + case 0xF8: + command = CMD_MODCMDEX; + param = 0x00; + break; + // F.F9: Filter On + case 0xF9: + command = CMD_MODCMDEX; + param = 0x01; + break; + // F.FD: Very fast tone-portamento + case 0xFD: + command = CMD_TONEPORTAMENTO; + param = 0xFF; + break; + // F.FE: End Song + case 0xFE: + command = CMD_SPEED; + param = 0; + break; + // F.FF: Note Cut + case 0xFF: + command = CMD_MODCMDEX; + param = 0xC0; + break; + default: +#ifdef MED_LOG + Log("Unknown Fxx command: cmd=0x%02X param=0x%02X\n", command, param); +#endif + param = command = 0; + } + break; + // 11.0x: Fine Slide Up + case 0x11: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0x10; + break; + // 12.0x: Fine Slide Down + case 0x12: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0x20; + break; + // 14.xx: Vibrato + case 0x14: + command = CMD_VIBRATO; + break; + // 15.xx: FineTune + case 0x15: + command = CMD_MODCMDEX; + param &= 0x0F; + param |= 0x50; + break; + // 16.xx: Pattern Loop + case 0x16: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0x60; + break; + // 18.xx: Note Cut + case 0x18: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0xC0; + break; + // 19.xx: Sample Offset + case 0x19: + command = CMD_OFFSET; + break; + // 1A.0x: Fine Volume Up + case 0x1A: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0xA0; + break; + // 1B.0x: Fine Volume Down + case 0x1B: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0xB0; + break; + // 1D.xx: Pattern Break + case 0x1D: + command = CMD_PATTERNBREAK; + break; + // 1E.0x: Pattern Delay + case 0x1E: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0xE0; + break; + // 1F.xy: Retrig + case 0x1F: + command = CMD_RETRIG; + param &= 0x0F; + break; + // 2E.xx: set panning + case 0x2E: + command = CMD_MODCMDEX; + param = ((param + 0x10) & 0xFF) >> 1; + if (param > 0x0F) param = 0x0F; + param |= 0x80; + break; + default: +#ifdef MED_LOG + // 0x2E ? + Log("Unknown command: cmd=0x%02X param=0x%02X\n", command, param); +#endif + command = param = 0; + } + p->command = command; + p->param = param; +} + + +BOOL CSoundFile::ReadMed(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + const MEDMODULEHEADER *pmmh; + const MMD0SONGHEADER *pmsh; + const MMD2SONGHEADER *pmsh2; + const MMD0EXP *pmex; + DWORD dwBlockArr, dwSmplArr, dwExpData, wNumBlocks; + LPDWORD pdwTable; + CHAR version; + UINT deftempo; + int playtransp = 0; + + if ((!lpStream) || (dwMemLength < 0x200)) return FALSE; + pmmh = (MEDMODULEHEADER *)lpStream; + if (((pmmh->id & 0x00FFFFFF) != 0x444D4D) || (!pmmh->song)) return FALSE; + // Check for 'MMDx' + DWORD dwSong = bswapBE32(pmmh->song); + if ((dwSong >= dwMemLength) || (dwSong + sizeof(MMD0SONGHEADER) >= dwMemLength)) return FALSE; + version = (signed char)((pmmh->id >> 24) & 0xFF); + if ((version < '0') || (version > '3')) return FALSE; +#ifdef MED_LOG + Log("\nLoading MMD%c module (flags=0x%02X)...\n", version, bswapBE32(pmmh->mmdflags)); + Log(" modlen = %d\n", bswapBE32(pmmh->modlen)); + Log(" song = 0x%08X\n", bswapBE32(pmmh->song)); + Log(" psecnum = %d\n", bswapBE16(pmmh->psecnum)); + Log(" pseq = %d\n", bswapBE16(pmmh->pseq)); + Log(" blockarr = 0x%08X\n", bswapBE32(pmmh->blockarr)); + Log(" mmdflags = 0x%08X\n", bswapBE32(pmmh->mmdflags)); + Log(" smplarr = 0x%08X\n", bswapBE32(pmmh->smplarr)); + Log(" reserved = 0x%08X\n", bswapBE32(pmmh->reserved)); + Log(" expdata = 0x%08X\n", bswapBE32(pmmh->expdata)); + Log(" reserved2= 0x%08X\n", bswapBE32(pmmh->reserved2)); + Log(" pstate = %d\n", bswapBE16(pmmh->pstate)); + Log(" pblock = %d\n", bswapBE16(pmmh->pblock)); + Log(" pline = %d\n", bswapBE16(pmmh->pline)); + Log(" pseqnum = %d\n", bswapBE16(pmmh->pseqnum)); + Log(" actplayline=%d\n", bswapBE16(pmmh->actplayline)); + Log(" counter = %d\n", pmmh->counter); + Log(" extra_songs = %d\n", pmmh->extra_songs); + Log("\n"); +#endif + m_nType = MOD_TYPE_MED; + m_nSongPreAmp = 0x20; + dwBlockArr = bswapBE32(pmmh->blockarr); + dwSmplArr = bswapBE32(pmmh->smplarr); + dwExpData = bswapBE32(pmmh->expdata); + if ((dwExpData) && (dwExpData+sizeof(MMD0EXP) < dwMemLength)) + pmex = (MMD0EXP *)(lpStream+dwExpData); + else + pmex = NULL; + pmsh = (MMD0SONGHEADER *)(lpStream + dwSong); + pmsh2 = (MMD2SONGHEADER *)pmsh; +#ifdef MED_LOG + if (version < '2') + { + Log("MMD0 Header:\n"); + Log(" numblocks = %d\n", bswapBE16(pmsh->numblocks)); + Log(" songlen = %d\n", bswapBE16(pmsh->songlen)); + Log(" playseq = "); + for (UINT idbg1=0; idbg1<16; idbg1++) Log("%2d, ", pmsh->playseq[idbg1]); + Log("...\n"); + Log(" deftempo = 0x%04X\n", bswapBE16(pmsh->deftempo)); + Log(" playtransp = %d\n", (signed char)pmsh->playtransp); + Log(" flags(1,2) = 0x%02X, 0x%02X\n", pmsh->flags, pmsh->flags2); + Log(" tempo2 = %d\n", pmsh->tempo2); + Log(" trkvol = "); + for (UINT idbg2=0; idbg2<16; idbg2++) Log("0x%02X, ", pmsh->trkvol[idbg2]); + Log("...\n"); + Log(" mastervol = 0x%02X\n", pmsh->mastervol); + Log(" numsamples = %d\n", pmsh->numsamples); + } else + { + Log("MMD2 Header:\n"); + Log(" numblocks = %d\n", bswapBE16(pmsh2->numblocks)); + Log(" numsections= %d\n", bswapBE16(pmsh2->numsections)); + Log(" playseqptr = 0x%04X\n", bswapBE32(pmsh2->playseqtable)); + Log(" sectionptr = 0x%04X\n", bswapBE32(pmsh2->sectiontable)); + Log(" trackvols = 0x%04X\n", bswapBE32(pmsh2->trackvols)); + Log(" numtracks = %d\n", bswapBE16(pmsh2->numtracks)); + Log(" numpseqs = %d\n", bswapBE16(pmsh2->numpseqs)); + Log(" trackpans = 0x%04X\n", bswapBE32(pmsh2->trackpans)); + Log(" flags3 = 0x%08X\n", bswapBE32(pmsh2->flags3)); + Log(" voladj = %d\n", bswapBE16(pmsh2->voladj)); + Log(" channels = %d\n", bswapBE16(pmsh2->channels)); + Log(" echotype = %d\n", pmsh2->mix_echotype); + Log(" echodepth = %d\n", pmsh2->mix_echodepth); + Log(" echolen = %d\n", bswapBE16(pmsh2->mix_echolen)); + Log(" stereosep = %d\n", (signed char)pmsh2->mix_stereosep); + Log(" deftempo = 0x%04X\n", bswapBE16(pmsh2->deftempo)); + Log(" playtransp = %d\n", (signed char)pmsh2->playtransp); + Log(" flags(1,2) = 0x%02X, 0x%02X\n", pmsh2->flags, pmsh2->flags2); + Log(" tempo2 = %d\n", pmsh2->tempo2); + Log(" mastervol = 0x%02X\n", pmsh2->mastervol); + Log(" numsamples = %d\n", pmsh->numsamples); + } + Log("\n"); +#endif + wNumBlocks = bswapBE16(pmsh->numblocks); + m_nChannels = 4; + m_nSamples = pmsh->numsamples; + if (m_nSamples > 63) m_nSamples = 63; + m_nStereoSeparation = ((pmsh2->mix_stereosep < 0) ? -32 : 32) * pmsh2->mix_stereosep; + // Tempo + m_nDefaultTempo = 125; + deftempo = bswapBE16(pmsh->deftempo); + if (!deftempo) deftempo = 125; + if (pmsh->flags2 & MMD_FLAG2_BPM) + { + UINT tempo_tpl = (pmsh->flags2 & MMD_FLAG2_BMASK) + 1; + if (!tempo_tpl) tempo_tpl = 4; + deftempo *= tempo_tpl; + deftempo /= 4; + #ifdef MED_LOG + Log("newtempo: %3d bpm (bpm=%3d lpb=%2d)\n", deftempo, bswapBE16(pmsh->deftempo), (pmsh->flags2 & MMD_FLAG2_BMASK)+1); + #endif + } else + { + deftempo = _muldiv(deftempo, 5*715909, 2*474326); + #ifdef MED_LOG + Log("oldtempo: %3d bpm (bpm=%3d)\n", deftempo, bswapBE16(pmsh->deftempo)); + #endif + } + // Speed + m_nDefaultSpeed = pmsh->tempo2; + if (!m_nDefaultSpeed) m_nDefaultSpeed = 6; + if (deftempo < 0x21) deftempo = 0x21; + if (deftempo > 255) + { + while ((m_nDefaultSpeed > 3) && (deftempo > 260)) + { + deftempo = (deftempo * (m_nDefaultSpeed - 1)) / m_nDefaultSpeed; + m_nDefaultSpeed--; + } + if (deftempo > 255) deftempo = 255; + } + m_nDefaultTempo = deftempo; + // Reading Samples + for (UINT iSHdr=0; iSHdrnLoopStart = bswapBE16(pmsh->sample[iSHdr].rep) << 1; + pins->nLoopEnd = pins->nLoopStart + (bswapBE16(pmsh->sample[iSHdr].replen) << 1); + pins->nVolume = (pmsh->sample[iSHdr].svol << 2); + pins->nGlobalVol = 64; + if (pins->nVolume > 256) pins->nVolume = 256; + pins->RelativeTone = -12 * pmsh->sample[iSHdr].strans; + pins->nPan = 128; + if (pins->nLoopEnd) pins->uFlags |= CHN_LOOP; + } + // Common Flags + if (!(pmsh->flags & 0x20)) m_dwSongFlags |= SONG_FASTVOLSLIDES; + // Reading play sequence + if (version < '2') + { + UINT nbo = pmsh->songlen >> 8; + if (nbo >= MAX_ORDERS) nbo = MAX_ORDERS-1; + if (!nbo) nbo = 1; + memcpy(Order, pmsh->playseq, nbo); + playtransp = pmsh->playtransp; + } else + { + UINT nOrders, nSections; + UINT nTrks = bswapBE16(pmsh2->numtracks); + if ((nTrks >= 4) && (nTrks <= 32)) m_nChannels = nTrks; + DWORD playseqtable = bswapBE32(pmsh2->playseqtable); + UINT numplayseqs = bswapBE16(pmsh2->numpseqs); + if (!numplayseqs) numplayseqs = 1; + nOrders = 0; + nSections = bswapBE16(pmsh2->numsections); + DWORD sectiontable = bswapBE32(pmsh2->sectiontable); + if ((!nSections) || (!sectiontable) || (sectiontable >= dwMemLength-2)) nSections = 1; + nOrders = 0; + for (UINT iSection=0; iSectionname, 31); + UINT n = bswapBE16(pmps->length); + if (pseq+n <= dwMemLength) + { + for (UINT i=0; iseq[i] >> 8; + if ((seqval < wNumBlocks) && (nOrders < MAX_ORDERS-1)) + { + Order[nOrders++] = seqval; + } + } + } + } + } + playtransp = pmsh2->playtransp; + while (nOrders < MAX_ORDERS) Order[nOrders++] = 0xFF; + } + // Reading Expansion structure + if (pmex) + { + // Channel Split + if ((m_nChannels == 4) && (pmsh->flags & 0x40)) + { + for (UINT i8ch=0; i8ch<4; i8ch++) + { + if (pmex->channelsplit[i8ch]) m_nChannels++; + } + } + // Song Comments + UINT annotxt = bswapBE32(pmex->annotxt); + UINT annolen = bswapBE32(pmex->annolen); + if ((annotxt) && (annolen) && (annotxt+annolen <= dwMemLength)) + { + m_lpszSongComments = new char[annolen+1]; + memcpy(m_lpszSongComments, lpStream+annotxt, annolen); + m_lpszSongComments[annolen] = 0; + } + // Song Name + UINT songname = bswapBE32(pmex->songname); + UINT songnamelen = bswapBE32(pmex->songnamelen); + if ((songname) && (songnamelen) && (songname+songnamelen <= dwMemLength)) + { + if (songnamelen > 31) songnamelen = 31; + memcpy(m_szNames[0], lpStream+songname, songnamelen); + } + // Sample Names + DWORD smpinfoex = bswapBE32(pmex->iinfo); + if (smpinfoex) + { + DWORD iinfoptr = bswapBE32(pmex->iinfo); + UINT ientries = bswapBE16(pmex->i_ext_entries); + UINT ientrysz = bswapBE16(pmex->i_ext_entrsz); + + if ((iinfoptr) && (ientrysz < 256) && (iinfoptr + ientries*ientrysz < dwMemLength)) + { + LPCSTR psznames = (LPCSTR)(lpStream + iinfoptr); + UINT maxnamelen = ientrysz; + if (maxnamelen > 32) maxnamelen = 32; + for (UINT i=0; itrackinfo_ofs); + if ((trackinfo_ofs) && (trackinfo_ofs + m_nChannels * 4 < dwMemLength)) + { + DWORD *ptrktags = (DWORD *)(lpStream + trackinfo_ofs); + for (UINT i=0; i MAX_CHANNELNAME) trknamelen = MAX_CHANNELNAME; + if ((trknameofs) && (trknameofs + trknamelen < dwMemLength)) + { + lstrcpyn(ChnSettings[i].szName, (LPCSTR)(lpStream+trknameofs), MAX_CHANNELNAME); + } + } + } + } + } + // Reading samples + if (dwSmplArr > dwMemLength - 4*m_nSamples) return TRUE; + pdwTable = (LPDWORD)(lpStream + dwSmplArr); + for (UINT iSmp=0; iSmp= dwMemLength) || (dwPos + sizeof(MMDSAMPLEHEADER) >= dwMemLength)) continue; + MMDSAMPLEHEADER *psdh = (MMDSAMPLEHEADER *)(lpStream + dwPos); + UINT len = bswapBE32(psdh->length); + #ifdef MED_LOG + Log("SampleData %d: stype=0x%02X len=%d\n", iSmp, bswapBE16(psdh->type), len); + #endif + if ((len > MAX_SAMPLE_LENGTH) || (dwPos + len + 6 > dwMemLength)) len = 0; + UINT flags = RS_PCM8S, stype = bswapBE16(psdh->type); + LPSTR psdata = (LPSTR)(lpStream + dwPos + 6); + if (stype & 0x80) + { + psdata += (stype & 0x20) ? 14 : 6; + } else + { + if (stype & 0x10) + { + Ins[iSmp+1].uFlags |= CHN_16BIT; + len /= 2; + flags = (stype & 0x20) ? RS_STPCM16M : RS_PCM16M; + } else + { + flags = (stype & 0x20) ? RS_STPCM8S : RS_PCM8S; + } + if (stype & 0x20) len /= 2; + } + Ins[iSmp+1].nLength = len; + ReadSample(&Ins[iSmp+1], flags, psdata, dwMemLength - dwPos - 6); + } + // Reading patterns (blocks) + if (wNumBlocks > MAX_PATTERNS) wNumBlocks = MAX_PATTERNS; + if ((!dwBlockArr) || (dwBlockArr > dwMemLength - 4*wNumBlocks)) return TRUE; + pdwTable = (LPDWORD)(lpStream + dwBlockArr); + playtransp += (version == '3') ? 24 : 48; + for (UINT iBlk=0; iBlk= dwMemLength) || (dwPos >= dwMemLength - 8)) continue; + UINT lines = 64, tracks = 4; + if (version == '0') + { + const MMD0BLOCK *pmb = (const MMD0BLOCK *)(lpStream + dwPos); + lines = pmb->lines + 1; + tracks = pmb->numtracks; + if (!tracks) tracks = m_nChannels; + if ((Patterns[iBlk] = AllocatePattern(lines, m_nChannels)) == NULL) continue; + PatternSize[iBlk] = lines; + PatternAllocSize[iBlk] = lines; + MODCOMMAND *p = Patterns[iBlk]; + LPBYTE s = (LPBYTE)(lpStream + dwPos + 2); + UINT maxlen = tracks*lines*3; + if (maxlen + dwPos > dwMemLength - 2) break; + for (UINT y=0; y> 4; + if (s[0] & 0x80) instr |= 0x10; + if (s[0] & 0x40) instr |= 0x20; + if ((note) && (note <= 132)) p->note = note + playtransp; + p->instr = instr; + p->command = s[1] & 0x0F; + p->param = s[2]; + // if (!iBlk) Log("%02X.%02X.%02X | ", s[0], s[1], s[2]); + MedConvert(p, pmsh); + p++; + } + //if (!iBlk) Log("\n"); + } + } else + { + MMD1BLOCK *pmb = (MMD1BLOCK *)(lpStream + dwPos); + #ifdef MED_LOG + Log("MMD1BLOCK: lines=%2d, tracks=%2d, offset=0x%04X\n", + bswapBE16(pmb->lines), bswapBE16(pmb->numtracks), bswapBE32(pmb->info)); + #endif + MMD1BLOCKINFO *pbi = NULL; + BYTE *pcmdext = NULL; + lines = (pmb->lines >> 8) + 1; + tracks = pmb->numtracks >> 8; + if (!tracks) tracks = m_nChannels; + if ((Patterns[iBlk] = AllocatePattern(lines, m_nChannels)) == NULL) continue; + PatternSize[iBlk] = (WORD)lines; + PatternAllocSize[iBlk] = (WORD)lines; + DWORD dwBlockInfo = bswapBE32(pmb->info); + if ((dwBlockInfo) && (dwBlockInfo < dwMemLength - sizeof(MMD1BLOCKINFO))) + { + pbi = (MMD1BLOCKINFO *)(lpStream + dwBlockInfo); + #ifdef MED_LOG + Log(" BLOCKINFO: blockname=0x%04X namelen=%d pagetable=0x%04X &cmdexttable=0x%04X\n", + bswapBE32(pbi->blockname), bswapBE32(pbi->blocknamelen), bswapBE32(pbi->pagetable), bswapBE32(pbi->cmdexttable)); + #endif + if ((pbi->blockname) && (pbi->blocknamelen)) + { + DWORD nameofs = bswapBE32(pbi->blockname); + UINT namelen = bswapBE32(pbi->blocknamelen); + if ((nameofs < dwMemLength) && (nameofs+namelen < dwMemLength)) + { + SetPatternName(iBlk, (LPCSTR)(lpStream+nameofs)); + } + } + if (pbi->cmdexttable) + { + DWORD cmdexttable = bswapBE32(pbi->cmdexttable); + if (cmdexttable < dwMemLength - 4) + { + cmdexttable = bswapBE32(*(DWORD *)(lpStream + cmdexttable)); + if ((cmdexttable) && (cmdexttable <= dwMemLength - lines*tracks)) + { + pcmdext = (BYTE *)(lpStream + cmdexttable); + } + } + } + } + MODCOMMAND *p = Patterns[iBlk]; + LPBYTE s = (LPBYTE)(lpStream + dwPos + 8); + UINT maxlen = tracks*lines*4; + if (maxlen + dwPos > dwMemLength - 8) break; + for (UINT y=0; y 120) rnote = 120; + p->note = (BYTE)rnote; + } + p->instr = s[1]; + p->command = s[2]; + p->param = s[3]; + if (pcmdext) p->vol = pcmdext[x]; + MedConvert(p, pmsh); + p++; + } + if (pcmdext) pcmdext += tracks; + } + } + } + // Setup channel pan positions + for (UINT iCh=0; iCh, + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +extern WORD ProTrackerPeriodTable[6*12]; + +////////////////////////////////////////////////////////// +// ProTracker / NoiseTracker MOD/NST file support + +void CSoundFile::ConvertModCommand(MODCOMMAND *m) const +//----------------------------------------------------- +{ + UINT command = m->command, param = m->param; + + switch(command) + { + case 0x00: if (param) command = CMD_ARPEGGIO; break; + case 0x01: command = CMD_PORTAMENTOUP; break; + case 0x02: command = CMD_PORTAMENTODOWN; break; + case 0x03: command = CMD_TONEPORTAMENTO; break; + case 0x04: command = CMD_VIBRATO; break; + case 0x05: command = CMD_TONEPORTAVOL; if (param & 0xF0) param &= 0xF0; break; + case 0x06: command = CMD_VIBRATOVOL; if (param & 0xF0) param &= 0xF0; break; + case 0x07: command = CMD_TREMOLO; break; + case 0x08: command = CMD_PANNING8; break; + case 0x09: command = CMD_OFFSET; break; + case 0x0A: command = CMD_VOLUMESLIDE; if (param & 0xF0) param &= 0xF0; break; + case 0x0B: command = CMD_POSITIONJUMP; break; + case 0x0C: command = CMD_VOLUME; break; + case 0x0D: command = CMD_PATTERNBREAK; param = ((param >> 4) * 10) + (param & 0x0F); break; + case 0x0E: command = CMD_MODCMDEX; break; + case 0x0F: command = (param <= (UINT)((m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? 0x1F : 0x20)) ? CMD_SPEED : CMD_TEMPO; + if ((param == 0xFF) && (m_nSamples == 15)) command = 0; break; + // Extension for XM extended effects + case 'G' - 55: command = CMD_GLOBALVOLUME; break; + case 'H' - 55: command = CMD_GLOBALVOLSLIDE; if (param & 0xF0) param &= 0xF0; break; + case 'K' - 55: command = CMD_KEYOFF; break; + case 'L' - 55: command = CMD_SETENVPOSITION; break; + case 'M' - 55: command = CMD_CHANNELVOLUME; break; + case 'N' - 55: command = CMD_CHANNELVOLSLIDE; break; + case 'P' - 55: command = CMD_PANNINGSLIDE; if (param & 0xF0) param &= 0xF0; break; + case 'R' - 55: command = CMD_RETRIG; break; + case 'T' - 55: command = CMD_TREMOR; break; + case 'X' - 55: command = CMD_XFINEPORTAUPDOWN; break; + case 'Y' - 55: command = CMD_PANBRELLO; break; + case 'Z' - 55: command = CMD_MIDI; break; + default: command = 0; + } + m->command = command; + m->param = param; +} + + +WORD CSoundFile::ModSaveCommand(const MODCOMMAND *m, BOOL bXM) const +//------------------------------------------------------------------ +{ + UINT command = m->command & 0x3F, param = m->param; + + switch(command) + { + case 0: command = param = 0; break; + case CMD_ARPEGGIO: command = 0; break; + case CMD_PORTAMENTOUP: + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) + { + if ((param & 0xF0) == 0xE0) { command=0x0E; param=((param & 0x0F) >> 2)|0x10; break; } + else if ((param & 0xF0) == 0xF0) { command=0x0E; param &= 0x0F; param|=0x10; break; } + } + command = 0x01; + break; + case CMD_PORTAMENTODOWN: + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) + { + if ((param & 0xF0) == 0xE0) { command=0x0E; param=((param & 0x0F) >> 2)|0x20; break; } + else if ((param & 0xF0) == 0xF0) { command=0x0E; param &= 0x0F; param|=0x20; break; } + } + command = 0x02; + break; + case CMD_TONEPORTAMENTO: command = 0x03; break; + case CMD_VIBRATO: command = 0x04; break; + case CMD_TONEPORTAVOL: command = 0x05; break; + case CMD_VIBRATOVOL: command = 0x06; break; + case CMD_TREMOLO: command = 0x07; break; + case CMD_PANNING8: + command = 0x08; + if (bXM) + { + if ((m_nType != MOD_TYPE_IT) && (m_nType != MOD_TYPE_XM) && (param <= 0x80)) + { + param <<= 1; + if (param > 255) param = 255; + } + } else + { + if ((m_nType == MOD_TYPE_IT) || (m_nType == MOD_TYPE_XM)) param >>= 1; + } + break; + case CMD_OFFSET: command = 0x09; break; + case CMD_VOLUMESLIDE: command = 0x0A; break; + case CMD_POSITIONJUMP: command = 0x0B; break; + case CMD_VOLUME: command = 0x0C; break; + case CMD_PATTERNBREAK: command = 0x0D; param = ((param / 10) << 4) | (param % 10); break; + case CMD_MODCMDEX: command = 0x0E; break; + case CMD_SPEED: command = 0x0F; if (param > 0x20) param = 0x20; break; + case CMD_TEMPO: if (param > 0x20) { command = 0x0F; break; } + case CMD_GLOBALVOLUME: command = 'G' - 55; break; + case CMD_GLOBALVOLSLIDE: command = 'H' - 55; break; + case CMD_KEYOFF: command = 'K' - 55; break; + case CMD_SETENVPOSITION: command = 'L' - 55; break; + case CMD_CHANNELVOLUME: command = 'M' - 55; break; + case CMD_CHANNELVOLSLIDE: command = 'N' - 55; break; + case CMD_PANNINGSLIDE: command = 'P' - 55; break; + case CMD_RETRIG: command = 'R' - 55; break; + case CMD_TREMOR: command = 'T' - 55; break; + case CMD_XFINEPORTAUPDOWN: command = 'X' - 55; break; + case CMD_PANBRELLO: command = 'Y' - 55; break; + case CMD_MIDI: command = 'Z' - 55; break; + case CMD_S3MCMDEX: + switch(param & 0xF0) + { + case 0x10: command = 0x0E; param = (param & 0x0F) | 0x30; break; + case 0x20: command = 0x0E; param = (param & 0x0F) | 0x50; break; + case 0x30: command = 0x0E; param = (param & 0x0F) | 0x40; break; + case 0x40: command = 0x0E; param = (param & 0x0F) | 0x70; break; + case 0x90: command = 'X' - 55; break; + case 0xB0: command = 0x0E; param = (param & 0x0F) | 0x60; break; + case 0xA0: + case 0x50: + case 0x70: + case 0x60: command = param = 0; break; + default: command = 0x0E; break; + } + break; + default: command = param = 0; + } + return (WORD)((command << 8) | (param)); +} + + +#pragma pack(1) + +typedef struct _MODSAMPLE +{ + CHAR name[22]; + WORD length; + BYTE finetune; + BYTE volume; + WORD loopstart; + WORD looplen; +} MODSAMPLE, *PMODSAMPLE; + +typedef struct _MODMAGIC +{ + BYTE nOrders; + BYTE nRestartPos; + BYTE Orders[128]; + char Magic[4]; // changed from CHAR +} MODMAGIC, *PMODMAGIC; + +#pragma pack() + +BOOL IsMagic(LPCSTR s1, LPCSTR s2) +{ + return ((*(DWORD *)s1) == (*(DWORD *)s2)) ? TRUE : FALSE; +} + + +BOOL CSoundFile::ReadMod(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + char s[1024]; // changed from CHAR + DWORD dwMemPos, dwTotalSampleLen; + PMODMAGIC pMagic; + UINT nErr; + + if ((!lpStream) || (dwMemLength < 0x600)) return FALSE; + dwMemPos = 20; + m_nSamples = 31; + m_nChannels = 4; + pMagic = (PMODMAGIC)(lpStream+dwMemPos+sizeof(MODSAMPLE)*31); + // Check Mod Magic + memcpy(s, pMagic->Magic, 4); + if ((IsMagic(s, "M.K.")) || (IsMagic(s, "M!K!")) + || (IsMagic(s, "M&K!")) || (IsMagic(s, "N.T."))) m_nChannels = 4; else + if ((IsMagic(s, "CD81")) || (IsMagic(s, "OKTA"))) m_nChannels = 8; else + if ((s[0]=='F') && (s[1]=='L') && (s[2]=='T') && (s[3]>='4') && (s[3]<='9')) m_nChannels = s[3] - '0'; else + if ((s[0]>='4') && (s[0]<='9') && (s[1]=='C') && (s[2]=='H') && (s[3]=='N')) m_nChannels = s[0] - '0'; else + if ((s[0]=='1') && (s[1]>='0') && (s[1]<='9') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 10; else + if ((s[0]=='2') && (s[1]>='0') && (s[1]<='9') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 20; else + if ((s[0]=='3') && (s[1]>='0') && (s[1]<='2') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 30; else + if ((s[0]=='T') && (s[1]=='D') && (s[2]=='Z') && (s[3]>='4') && (s[3]<='9')) m_nChannels = s[3] - '0'; else + if (IsMagic(s,"16CN")) m_nChannels = 16; else + if (IsMagic(s,"32CN")) m_nChannels = 32; else m_nSamples = 15; + // Load Samples + nErr = 0; + dwTotalSampleLen = 0; + for (UINT i=1; i<=m_nSamples; i++) + { + PMODSAMPLE pms = (PMODSAMPLE)(lpStream+dwMemPos); + MODINSTRUMENT *psmp = &Ins[i]; + UINT loopstart, looplen; + + memcpy(m_szNames[i], pms->name, 22); + m_szNames[i][22] = 0; + psmp->uFlags = 0; + psmp->nLength = bswapBE16(pms->length)*2; + dwTotalSampleLen += psmp->nLength; + psmp->nFineTune = MOD2XMFineTune(pms->finetune & 0x0F); + psmp->nVolume = 4*pms->volume; + if (psmp->nVolume > 256) { psmp->nVolume = 256; nErr++; } + psmp->nGlobalVol = 64; + psmp->nPan = 128; + loopstart = bswapBE16(pms->loopstart)*2; + looplen = bswapBE16(pms->looplen)*2; + // Fix loops + if ((looplen > 2) && (loopstart+looplen > psmp->nLength) + && (loopstart/2+looplen <= psmp->nLength)) + { + loopstart /= 2; + } + psmp->nLoopStart = loopstart; + psmp->nLoopEnd = loopstart + looplen; + if (psmp->nLength < 2) psmp->nLength = 0; + if (psmp->nLength) + { + UINT derr = 0; + if (psmp->nLoopStart >= psmp->nLength) { psmp->nLoopStart = psmp->nLength-1; derr|=1; } + if (psmp->nLoopEnd > psmp->nLength) { psmp->nLoopEnd = psmp->nLength; derr |= 1; } + if (psmp->nLoopStart > psmp->nLoopEnd) derr |= 1; + if ((psmp->nLoopStart > psmp->nLoopEnd) || (psmp->nLoopEnd <= 8) + || (psmp->nLoopEnd - psmp->nLoopStart <= 4)) + { + psmp->nLoopStart = 0; + psmp->nLoopEnd = 0; + } + if (psmp->nLoopEnd > psmp->nLoopStart) + { + psmp->uFlags |= CHN_LOOP; + } + } + dwMemPos += sizeof(MODSAMPLE); + } + if ((m_nSamples == 15) && (dwTotalSampleLen > dwMemLength * 4)) return FALSE; + pMagic = (PMODMAGIC)(lpStream+dwMemPos); + dwMemPos += sizeof(MODMAGIC); + if (m_nSamples == 15) dwMemPos -= 4; + memset(Order, 0,sizeof(Order)); + memcpy(Order, pMagic->Orders, 128); + + UINT nbp, nbpbuggy, nbpbuggy2, norders; + + norders = pMagic->nOrders; + if ((!norders) || (norders > 0x80)) + { + norders = 0x80; + while ((norders > 1) && (!Order[norders-1])) norders--; + } + nbpbuggy = 0; + nbpbuggy2 = 0; + nbp = 0; + for (UINT iord=0; iord<128; iord++) + { + UINT i = Order[iord]; + if ((i < 0x80) && (nbp <= i)) + { + nbp = i+1; + if (iord= nbpbuggy2) nbpbuggy2 = i+1; + } + for (UINT iend=norders; iendnRestartPos; + if (m_nRestartPos >= 0x78) m_nRestartPos = 0; + if (m_nRestartPos + 1 >= (UINT)norders) m_nRestartPos = 0; + if (!nbp) return FALSE; + DWORD dwWowTest = dwTotalSampleLen+dwMemPos; + if ((IsMagic(pMagic->Magic, "M.K.")) && (dwWowTest + nbp*8*256 == dwMemLength)) m_nChannels = 8; + if ((nbp != nbpbuggy) && (dwWowTest + nbp*m_nChannels*256 != dwMemLength)) + { + if (dwWowTest + nbpbuggy*m_nChannels*256 == dwMemLength) nbp = nbpbuggy; + else nErr += 8; + } else + if ((nbpbuggy2 > nbp) && (dwWowTest + nbpbuggy2*m_nChannels*256 == dwMemLength)) + { + nbp = nbpbuggy2; + } + if ((dwWowTest < 0x600) || (dwWowTest > dwMemLength)) nErr += 8; + if ((m_nSamples == 15) && (nErr >= 16)) return FALSE; + // Default settings + m_nType = MOD_TYPE_MOD; + m_nDefaultSpeed = 6; + m_nDefaultTempo = 125; + m_nMinPeriod = 14 << 2; + m_nMaxPeriod = 3424 << 2; + memcpy(m_szNames, lpStream, 20); + // Setting channels pan + for (UINT ich=0; ich= dwMemLength) break; + MODCOMMAND *m = Patterns[ipat]; + LPCBYTE p = lpStream + dwMemPos; + for (UINT j=m_nChannels*64; j; m++,p+=4,j--) + { + BYTE A0=p[0], A1=p[1], A2=p[2], A3=p[3]; + UINT n = ((((UINT)A0 & 0x0F) << 8) | (A1)); + if ((n) && (n != 0xFFF)) m->note = GetNoteFromPeriod(n << 2); + m->instr = ((UINT)A2 >> 4) | (A0 & 0x10); + m->command = A2 & 0x0F; + m->param = A3; + if ((m->command) || (m->param)) ConvertModCommand(m); + } + } + dwMemPos += m_nChannels*256; + } + // Reading instruments + DWORD dwErrCheck = 0; + for (UINT ismp=1; ismp<=m_nSamples; ismp++) if (Ins[ismp].nLength) + { + LPSTR p = (LPSTR)(lpStream+dwMemPos); + UINT flags = 0; + if (dwMemPos + 5 >= dwMemLength) break; + if (!strnicmp(p, "ADPCM", 5)) + { + flags = 3; + p += 5; + dwMemPos += 5; + } + DWORD dwSize = ReadSample(&Ins[ismp], flags, p, dwMemLength - dwMemPos); + if (dwSize) + { + dwMemPos += dwSize; + dwErrCheck++; + } + } +#ifdef MODPLUG_TRACKER + return TRUE; +#else + return (dwErrCheck) ? TRUE : FALSE; +#endif +} + + +#ifndef MODPLUG_NO_FILESAVE +#pragma warning(disable:4100) + +BOOL CSoundFile::SaveMod(diskwriter_driver_t *fp, UINT nPacking) +//---------------------------------------------------------- +{ + BYTE insmap[32]; + UINT inslen[32]; + BYTE bTab[32]; + BYTE ord[128]; + + if ((!m_nChannels) || (!fp)) return FALSE; + memset(ord, 0, sizeof(ord)); + memset(inslen, 0, sizeof(inslen)); + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + memset(insmap, 0, sizeof(insmap)); + for (UINT i=1; i<32; i++) if (Headers[i]) + { + for (UINT j=0; j<128; j++) if (Headers[i]->Keyboard[j]) + { + insmap[i] = Headers[i]->Keyboard[j]; + break; + } + } + } else + { + for (UINT i=0; i<32; i++) insmap[i] = (BYTE)i; + } + // Writing song name + fp->o(fp, (const unsigned char *)m_szNames, 20); + // Writing instrument definition + for (UINT iins=1; iins<=31; iins++) + { + MODINSTRUMENT *pins = &Ins[insmap[iins]]; + memcpy(bTab, m_szNames[iins],22); + inslen[iins] = pins->nLength; + if (inslen[iins] > 0x1fff0) inslen[iins] = 0x1fff0; + bTab[22] = inslen[iins] >> 9; + bTab[23] = inslen[iins] >> 1; + if (pins->RelativeTone < 0) bTab[24] = 0x08; else + if (pins->RelativeTone > 0) bTab[24] = 0x07; else + bTab[24] = (BYTE)XM2MODFineTune(pins->nFineTune); + bTab[25] = pins->nVolume >> 2; + bTab[26] = pins->nLoopStart >> 9; + bTab[27] = pins->nLoopStart >> 1; + bTab[28] = (pins->nLoopEnd - pins->nLoopStart) >> 9; + bTab[29] = (pins->nLoopEnd - pins->nLoopStart) >> 1; + fp->o(fp,(const unsigned char *) bTab, 30); + } + // Writing number of patterns + UINT nbp=0, norders=128; + for (UINT iord=0; iord<128; iord++) + { + if (Order[iord] == 0xFF) + { + norders = iord; + break; + } + if ((Order[iord] < 0x80) && (nbp<=Order[iord])) nbp = Order[iord]+1; + } + bTab[0] = norders; + bTab[1] = m_nRestartPos; + fp->o(fp, (const unsigned char *)bTab, 2); + // Writing pattern list + if (norders) memcpy(ord, Order, norders); + fp->o(fp, (const unsigned char *)ord, 128); + // Writing signature + if (m_nChannels == 4) + lstrcpy((LPSTR)&bTab, "M.K."); + else + wsprintf((LPSTR)&bTab, "%luCHN", m_nChannels); + fp->o(fp, (const unsigned char *)bTab, 4); + // Writing patterns + for (UINT ipat=0; ipat> 8; + param &= 0xFF; + if (command > 0x0F) command = param = 0; + if ((m->vol >= 0x10) && (m->vol <= 0x50) && (!command) && (!param)) { command = 0x0C; param = m->vol - 0x10; } + UINT period = m->note; + if (period) + { + if (period < 37) period = 37; + period -= 37; + if (period >= 6*12) period = 6*12-1; + period = ProTrackerPeriodTable[period]; + } + UINT instr = (m->instr > 31) ? 0 : m->instr; + p[0] = ((period >> 8) & 0x0F) | (instr & 0x10); + p[1] = period & 0xFF; + p[2] = ((instr & 0x0F) << 4) | (command & 0x0F); + p[3] = param; + } + fp->o(fp, (const unsigned char *)s, m_nChannels*4); + } else + { + memset(s, 0, m_nChannels*4); + fp->o(fp, (const unsigned char *)s, m_nChannels*4); + } + } + // Writing instruments + for (UINT ismpd=1; ismpd<=31; ismpd++) if (inslen[ismpd]) + { + MODINSTRUMENT *pins = &Ins[insmap[ismpd]]; + UINT flags = RS_PCM8S; +#ifndef NO_PACKING + if (!(pins->uFlags & (CHN_16BIT|CHN_STEREO))) + { + if ((nPacking) && (CanPackSample((char *)pins->pSample, inslen[ismpd], nPacking))) + { + fp->o(fp, (const unsigned char *)"ADPCM", 5); + flags = RS_ADPCM4; + } + } +#endif + WriteSample(fp, pins, flags, inslen[ismpd]); + } + return TRUE; +} + +#pragma warning(default:4100) +#endif // MODPLUG_NO_FILESAVE diff --git a/modplug/load_mt2.cpp b/modplug/load_mt2.cpp new file mode 100644 index 000000000..cbc76dc3b --- /dev/null +++ b/modplug/load_mt2.cpp @@ -0,0 +1,637 @@ +#include "stdafx.h" +#include "sndfile.h" + +//#define MT2DEBUG + +#pragma pack(1) + +typedef struct _MT2FILEHEADER +{ + DWORD dwMT20; // 0x3032544D "MT20" + DWORD dwSpecial; + WORD wVersion; + CHAR szTrackerName[32]; // "MadTracker 2.0" + CHAR szSongName[64]; + WORD nOrders; + WORD wRestart; + WORD wPatterns; + WORD wChannels; + WORD wSamplesPerTick; + BYTE bTicksPerLine; + BYTE bLinesPerBeat; + DWORD fulFlags; // b0=packed patterns + WORD wInstruments; + WORD wSamples; + BYTE Orders[256]; +} MT2FILEHEADER; + +typedef struct _MT2PATTERN +{ + WORD wLines; + DWORD wDataLen; +} MT2PATTERN; + +typedef struct _MT2COMMAND +{ + BYTE note; // 0=nothing, 97=note off + BYTE instr; + BYTE vol; + BYTE pan; + BYTE fxcmd; + BYTE fxparam1; + BYTE fxparam2; +} MT2COMMAND; + +typedef struct _MT2DRUMSDATA +{ + WORD wDrumPatterns; + WORD wDrumSamples[8]; + BYTE DrumPatternOrder[256]; +} MT2DRUMSDATA; + +typedef struct _MT2AUTOMATION +{ + DWORD dwFlags; + DWORD dwEffectId; + DWORD nEnvPoints; +} MT2AUTOMATION; + +typedef struct _MT2INSTRUMENT +{ + CHAR szName[32]; + DWORD dwDataLen; + WORD wSamples; + BYTE GroupsMapping[96]; + BYTE bVibType; + BYTE bVibSweep; + BYTE bVibDepth; + BYTE bVibRate; + WORD wFadeOut; + WORD wNNA; + WORD wInstrFlags; + WORD wEnvFlags1; + WORD wEnvFlags2; +} MT2INSTRUMENT; + +typedef struct _MT2ENVELOPE +{ + BYTE nFlags; + BYTE nPoints; + BYTE nSustainPos; + BYTE nLoopStart; + BYTE nLoopEnd; + BYTE bReserved[3]; + BYTE EnvData[64]; +} MT2ENVELOPE; + +typedef struct _MT2SYNTH +{ + BYTE nSynthId; + BYTE nFxId; + WORD wCutOff; + BYTE nResonance; + BYTE nAttack; + BYTE nDecay; + BYTE bReserved[25]; +} MT2SYNTH; + +typedef struct _MT2SAMPLE +{ + CHAR szName[32]; + DWORD dwDataLen; + DWORD dwLength; + DWORD dwFrequency; + BYTE nQuality; + BYTE nChannels; + BYTE nFlags; + BYTE nLoop; + DWORD dwLoopStart; + DWORD dwLoopEnd; + WORD wVolume; + BYTE nPan; + BYTE nBaseNote; + WORD wSamplesPerBeat; +} MT2SAMPLE; + +typedef struct _MT2GROUP +{ + BYTE nSmpNo; + BYTE nVolume; // 0-128 + BYTE nFinePitch; + BYTE Reserved[5]; +} MT2GROUP; + +#pragma pack() + + +static VOID ConvertMT2Command(CSoundFile *that, MODCOMMAND *m, MT2COMMAND *p) +//--------------------------------------------------------------------------- +{ + // Note + m->note = 0; + if (p->note) m->note = (p->note > 96) ? 0xFF : p->note+12; + // Instrument + m->instr = p->instr; + // Volume Column + if ((p->vol >= 0x10) && (p->vol <= 0x90)) + { + m->volcmd = VOLCMD_VOLUME; + m->vol = (p->vol - 0x10) >> 1; + } else + if ((p->vol >= 0xA0) && (p->vol <= 0xAF)) + { + m->volcmd = VOLCMD_VOLSLIDEDOWN; + m->vol = (p->vol & 0x0f); + } else + if ((p->vol >= 0xB0) && (p->vol <= 0xBF)) + { + m->volcmd = VOLCMD_VOLSLIDEUP; + m->vol = (p->vol & 0x0f); + } else + if ((p->vol >= 0xC0) && (p->vol <= 0xCF)) + { + m->volcmd = VOLCMD_FINEVOLDOWN; + m->vol = (p->vol & 0x0f); + } else + if ((p->vol >= 0xD0) && (p->vol <= 0xDF)) + { + m->volcmd = VOLCMD_FINEVOLUP; + m->vol = (p->vol & 0x0f); + } else + { + m->volcmd = 0; + m->vol = 0; + } + // Effects + m->command = 0; + m->param = 0; + if ((p->fxcmd) || (p->fxparam1) || (p->fxparam2)) + { + if (!p->fxcmd) + { + m->command = p->fxparam2; + m->param = p->fxparam1; + that->ConvertModCommand(m); + } else + { + // TODO: MT2 Effects + } + } +} + + +BOOL CSoundFile::ReadMT2(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + MT2FILEHEADER *pfh = (MT2FILEHEADER *)lpStream; + DWORD dwMemPos, dwDrumDataPos, dwExtraDataPos; + UINT nDrumDataLen, nExtraDataLen; + MT2DRUMSDATA *pdd; + MT2INSTRUMENT *InstrMap[255]; + MT2SAMPLE *SampleMap[256]; + + if ((!lpStream) || (dwMemLength < sizeof(MT2FILEHEADER)) + || (pfh->dwMT20 != 0x3032544D) + || (pfh->wVersion < 0x0200) || (pfh->wVersion >= 0x0300) + || (pfh->wChannels < 4) || (pfh->wChannels > 64)) return FALSE; + pdd = NULL; + m_nType = MOD_TYPE_MT2; + m_nChannels = pfh->wChannels; + m_nRestartPos = pfh->wRestart; + m_nDefaultSpeed = pfh->bTicksPerLine; + m_nDefaultTempo = 125; + if ((pfh->wSamplesPerTick > 100) && (pfh->wSamplesPerTick < 5000)) + { + m_nDefaultTempo = 110250 / pfh->wSamplesPerTick; + } + for (UINT iOrd=0; iOrdnOrders) ? pfh->Orders[iOrd] : 0xFF); + } + memcpy(m_szNames[0], pfh->szSongName, 32); + m_szNames[0][31] = 0; + dwMemPos = sizeof(MT2FILEHEADER); + nDrumDataLen = *(WORD *)(lpStream + dwMemPos); + dwDrumDataPos = dwMemPos + 2; + if (nDrumDataLen >= 2) pdd = (MT2DRUMSDATA *)(lpStream+dwDrumDataPos); + dwMemPos += 2 + nDrumDataLen; +#ifdef MT2DEBUG + + Log("MT2 v%03X: \"%s\" (flags=%04X)\n", pfh->wVersion, m_szNames[0], pfh->fulFlags); + Log("%d Channels, %d Patterns, %d Instruments, %d Samples\n", pfh->wChannels, pfh->wPatterns, pfh->wInstruments, pfh->wSamples); + Log("Drum Data: %d bytes @%04X\n", nDrumDataLen, dwDrumDataPos); +#endif + if (dwMemPos >= dwMemLength-12) return TRUE; + if (!*(DWORD *)(lpStream+dwMemPos)) dwMemPos += 4; + if (!*(DWORD *)(lpStream+dwMemPos)) dwMemPos += 4; + nExtraDataLen = *(DWORD *)(lpStream+dwMemPos); + dwExtraDataPos = dwMemPos + 4; + dwMemPos += 4; +#ifdef MT2DEBUG + Log("Extra Data: %d bytes @%04X\n", nExtraDataLen, dwExtraDataPos); +#endif + if (dwMemPos + nExtraDataLen >= dwMemLength) return TRUE; + while (dwMemPos+8 < dwExtraDataPos + nExtraDataLen) + { + DWORD dwId = *(DWORD *)(lpStream+dwMemPos); + DWORD dwLen = *(DWORD *)(lpStream+dwMemPos+4); + dwMemPos += 8; + if (dwMemPos + dwLen > dwMemLength) return TRUE; +#ifdef MT2DEBUG + CHAR s[5]; + memcpy(s, &dwId, 4); + s[4] = 0; + Log("pos=0x%04X: %s: %d bytes\n", dwMemPos-8, s, dwLen); +#endif + switch(dwId) + { + // MSG + case 0x0047534D: + if ((dwLen > 3) && (!m_lpszSongComments)) + { + DWORD nTxtLen = dwLen; + if (nTxtLen > 32000) nTxtLen = 32000; + m_lpszSongComments = new char[nTxtLen]; // changed from CHAR + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos+1, nTxtLen-1); + m_lpszSongComments[nTxtLen-1] = 0; + } + } + break; + // SUM -> author name (or "Unregistered") + // TMAP + // TRKS + case 0x534b5254: + break; + } + dwMemPos += dwLen; + } + // Load Patterns + dwMemPos = dwExtraDataPos + nExtraDataLen; + for (UINT iPat=0; iPatwPatterns; iPat++) if (dwMemPos < dwMemLength-6) + { + MT2PATTERN *pmp = (MT2PATTERN *)(lpStream+dwMemPos); + UINT wDataLen = (pmp->wDataLen + 1) & ~1; + dwMemPos += 6; + if (dwMemPos + wDataLen > dwMemLength) break; + UINT nLines = pmp->wLines; + if ((iPat < MAX_PATTERNS) && (nLines > 0) && (nLines <= 256)) + { + #ifdef MT2DEBUG + Log("Pattern #%d @%04X: %d lines, %d bytes\n", iPat, dwMemPos-6, nLines, pmp->wDataLen); + #endif + PatternSize[iPat] = nLines; + PatternAllocSize[iPat] = nLines; + Patterns[iPat] = AllocatePattern(nLines, m_nChannels); + if (!Patterns[iPat]) return TRUE; + MODCOMMAND *m = Patterns[iPat]; + UINT len = wDataLen; + if (pfh->fulFlags & 1) // Packed Patterns + { + BYTE *p = (BYTE *)(lpStream+dwMemPos); + UINT pos = 0, row=0, ch=0; + while (pos < len) + { + MT2COMMAND cmd; + UINT infobyte = p[pos++]; + UINT rptcount = 0; + if (infobyte == 0xff) + { + rptcount = p[pos++]; + infobyte = p[pos++]; + #if 0 + Log("(%d.%d) FF(%02X).%02X\n", row, ch, rptcount, infobyte); + } else + { + Log("(%d.%d) %02X\n", row, ch, infobyte); + #endif + } + if (infobyte & 0x7f) + { + UINT patpos = row*m_nChannels+ch; + cmd.note = cmd.instr = cmd.vol = cmd.pan = cmd.fxcmd = cmd.fxparam1 = cmd.fxparam2 = 0; + if (infobyte & 1) cmd.note = p[pos++]; + if (infobyte & 2) cmd.instr = p[pos++]; + if (infobyte & 4) cmd.vol = p[pos++]; + if (infobyte & 8) cmd.pan = p[pos++]; + if (infobyte & 16) cmd.fxcmd = p[pos++]; + if (infobyte & 32) cmd.fxparam1 = p[pos++]; + if (infobyte & 64) cmd.fxparam2 = p[pos++]; + #ifdef MT2DEBUG + if (cmd.fxcmd) + { + Log("(%d.%d) MT2 FX=%02X.%02X.%02X\n", row, ch, cmd.fxcmd, cmd.fxparam1, cmd.fxparam2); + } + #endif + ConvertMT2Command(this, &m[patpos], &cmd); + } + row += rptcount+1; + while (row >= nLines) { row-=nLines; ch++; } + if (ch >= m_nChannels) break; + } + } else + { + MT2COMMAND *p = (MT2COMMAND *)(lpStream+dwMemPos); + UINT n = 0; + while ((len > sizeof(MT2COMMAND)) && (n < m_nChannels*nLines)) + { + ConvertMT2Command(this, m, p); + len -= sizeof(MT2COMMAND); + n++; + p++; + m++; + } + } + } + dwMemPos += wDataLen; + } + // Skip Drum Patterns + if (pdd) + { + #ifdef MT2DEBUG + Log("%d Drum Patterns at offset 0x%08X\n", pdd->wDrumPatterns, dwMemPos); + #endif + for (UINT iDrm=0; iDrmwDrumPatterns; iDrm++) + { + if (dwMemPos > dwMemLength-2) return TRUE; + UINT nLines = *(WORD *)(lpStream+dwMemPos); + #ifdef MT2DEBUG + if (nLines != 64) Log("Drum Pattern %d: %d Lines @%04X\n", iDrm, nLines, dwMemPos); + #endif + dwMemPos += 2 + nLines * 32; + } + } + // Automation + if (pfh->fulFlags & 2) + { + #ifdef MT2DEBUG + Log("Automation at offset 0x%08X\n", dwMemPos); + #endif + UINT nAutoCount = m_nChannels; + if (pfh->fulFlags & 0x10) nAutoCount++; // Master Automation + if ((pfh->fulFlags & 0x08) && (pdd)) nAutoCount += 8; // Drums Automation + nAutoCount *= pfh->wPatterns; + for (UINT iAuto=0; iAuto= dwMemLength) return TRUE; + MT2AUTOMATION *pma = (MT2AUTOMATION *)(lpStream+dwMemPos); + dwMemPos += (pfh->wVersion <= 0x201) ? 4 : 8; + for (UINT iEnv=0; iEnv<14; iEnv++) + { + if (pma->dwFlags & (1 << iEnv)) + { + #ifdef MT2DEBUG + UINT nPoints = *(DWORD *)(lpStream+dwMemPos); + Log(" Env[%d/%d] %04X @%04X: %d points\n", iAuto, nAutoCount, 1 << iEnv, dwMemPos-8, nPoints); + #endif + dwMemPos += 260; + } + } + } + } + // Load Instruments +#ifdef MT2DEBUG + Log("Loading instruments at offset 0x%08X\n", dwMemPos); +#endif + memset(InstrMap, 0, sizeof(InstrMap)); + m_nInstruments = (pfh->wInstruments < MAX_INSTRUMENTS) ? pfh->wInstruments : MAX_INSTRUMENTS-1; + m_dwSongFlags |= SONG_INSTRUMENTMODE; + for (UINT iIns=1; iIns<=255; iIns++) + { + if (dwMemPos+36 > dwMemLength) return TRUE; + MT2INSTRUMENT *pmi = (MT2INSTRUMENT *)(lpStream+dwMemPos); + INSTRUMENTHEADER *penv = NULL; + if (iIns <= m_nInstruments) + { + penv = new INSTRUMENTHEADER; + Headers[iIns] = penv; + if (penv) + { + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + memcpy(penv->name, pmi->szName, 32); + penv->nGlobalVol = 64; + penv->nPan = 128; + for (UINT i=0; i<120; i++) + { + penv->NoteMap[i] = i+1; + } + } + } + #ifdef MT2DEBUG + if (iIns <= pfh->wInstruments) Log(" Instrument #%d at offset %04X: %d bytes\n", iIns, dwMemPos, pmi->dwDataLen); + #endif + if (((LONG)pmi->dwDataLen > 0) && (dwMemPos + pmi->dwDataLen + 40 <= dwMemLength)) + { + InstrMap[iIns-1] = pmi; + if (penv) + { + penv->nFadeOut = pmi->wFadeOut; + penv->nNNA = pmi->wNNA & 3; + penv->nDCT = (pmi->wNNA>>8) & 3; + penv->nDNA = (pmi->wNNA>>12) & 3; + MT2ENVELOPE *pehdr[4]; + WORD *pedata[4]; + if (pfh->wVersion <= 0x201) + { + DWORD dwEnvPos = dwMemPos + sizeof(MT2INSTRUMENT) - 4; + pehdr[0] = (MT2ENVELOPE *)(lpStream+dwEnvPos); + pehdr[1] = (MT2ENVELOPE *)(lpStream+dwEnvPos+8); + pehdr[2] = pehdr[3] = NULL; + pedata[0] = (WORD *)(lpStream+dwEnvPos+16); + pedata[1] = (WORD *)(lpStream+dwEnvPos+16+64); + pedata[2] = pedata[3] = NULL; + } else + { + DWORD dwEnvPos = dwMemPos + sizeof(MT2INSTRUMENT); + for (UINT i=0; i<4; i++) + { + if (pmi->wEnvFlags1 & (1<EnvData; + dwEnvPos += sizeof(MT2ENVELOPE); + } else + { + pehdr[i] = NULL; + pedata[i] = NULL; + } + } + } + // Load envelopes + for (UINT iEnv=0; iEnv<4; iEnv++) if (pehdr[iEnv]) + { + MT2ENVELOPE *pme = pehdr[iEnv]; + WORD *pEnvPoints = NULL; + BYTE *pEnvData = NULL; + #ifdef MT2DEBUG + Log(" Env %d.%d @%04X: %d points\n", iIns, iEnv, (UINT)(((BYTE *)pme)-lpStream), pme->nPoints); + #endif + switch(iEnv) + { + // Volume Envelope + case 0: + if (pme->nFlags & 1) penv->dwFlags |= ENV_VOLUME; + if (pme->nFlags & 2) penv->dwFlags |= ENV_VOLSUSTAIN; + if (pme->nFlags & 4) penv->dwFlags |= ENV_VOLLOOP; + penv->VolEnv.nNodes = (pme->nPoints > 16) ? 16 : pme->nPoints; + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = pme->nSustainPos; + penv->VolEnv.nLoopStart = pme->nLoopStart; + penv->VolEnv.nLoopEnd = pme->nLoopEnd; + pEnvPoints = penv->VolEnv.Ticks; + pEnvData = penv->VolEnv.Values; + break; + + // Panning Envelope + case 1: + if (pme->nFlags & 1) penv->dwFlags |= ENV_PANNING; + if (pme->nFlags & 2) penv->dwFlags |= ENV_PANSUSTAIN; + if (pme->nFlags & 4) penv->dwFlags |= ENV_PANLOOP; + penv->PanEnv.nNodes = (pme->nPoints > 16) ? 16 : pme->nPoints; + penv->PanEnv.nSustainStart = penv->PanEnv.nSustainEnd = pme->nSustainPos; + penv->PanEnv.nLoopStart = pme->nLoopStart; + penv->PanEnv.nLoopEnd = pme->nLoopEnd; + pEnvPoints = penv->PanEnv.Ticks; + pEnvData = penv->PanEnv.Values; + break; + + // Pitch/Filter envelope + default: + if (pme->nFlags & 1) penv->dwFlags |= (iEnv==3) ? (ENV_PITCH|ENV_FILTER) : ENV_PITCH; + if (pme->nFlags & 2) penv->dwFlags |= ENV_PITCHSUSTAIN; + if (pme->nFlags & 4) penv->dwFlags |= ENV_PITCHLOOP; + penv->PitchEnv.nNodes = (pme->nPoints > 16) ? 16 : pme->nPoints; + penv->PitchEnv.nSustainStart = penv->PitchEnv.nSustainEnd = pme->nSustainPos; + penv->PitchEnv.nLoopStart = pme->nLoopStart; + penv->PitchEnv.nLoopEnd = pme->nLoopEnd; + pEnvPoints = penv->PitchEnv.Ticks; + pEnvData = penv->PitchEnv.Values; + } + // Envelope data + if ((pEnvPoints) && (pEnvData) && (pedata[iEnv])) + { + WORD *psrc = pedata[iEnv]; + for (UINT i=0; i<16; i++) + { + pEnvPoints[i] = psrc[i*2]; + pEnvData[i] = (BYTE)psrc[i*2+1]; + } + } + } + } + dwMemPos += pmi->dwDataLen + 36; + if (pfh->wVersion > 0x201) dwMemPos += 4; // ? + } else + { + dwMemPos += 36; + } + } +#ifdef MT2DEBUG + Log("Loading samples at offset 0x%08X\n", dwMemPos); +#endif + memset(SampleMap, 0, sizeof(SampleMap)); + m_nSamples = (pfh->wSamples < MAX_SAMPLES) ? pfh->wSamples : MAX_SAMPLES-1; + for (UINT iSmp=1; iSmp<=256; iSmp++) + { + if (dwMemPos+36 > dwMemLength) return TRUE; + MT2SAMPLE *pms = (MT2SAMPLE *)(lpStream+dwMemPos); + #ifdef MT2DEBUG + if (iSmp <= m_nSamples) Log(" Sample #%d at offset %04X: %d bytes\n", iSmp, dwMemPos, pms->dwDataLen); + #endif + if (iSmp < MAX_SAMPLES) + { + memcpy(m_szNames[iSmp], pms->szName, 32); + } + if (pms->dwDataLen > 0) + { + SampleMap[iSmp-1] = pms; + if (iSmp < MAX_SAMPLES) + { + MODINSTRUMENT *psmp = &Ins[iSmp]; + psmp->nGlobalVol = 64; + psmp->nVolume = (pms->wVolume >> 7); + psmp->nPan = (pms->nPan == 0x80) ? 128 : (pms->nPan^0x80); + psmp->nLength = pms->dwLength; + psmp->nC4Speed = pms->dwFrequency; + psmp->nLoopStart = pms->dwLoopStart; + psmp->nLoopEnd = pms->dwLoopEnd; + FrequencyToTranspose(psmp); + psmp->RelativeTone -= pms->nBaseNote - 49; + psmp->nC4Speed = TransposeToFrequency(psmp->RelativeTone, psmp->nFineTune); + if (pms->nQuality == 2) { psmp->uFlags |= CHN_16BIT; psmp->nLength >>= 1; } + if (pms->nChannels == 2) { psmp->nLength >>= 1; } + if (pms->nLoop == 1) psmp->uFlags |= CHN_LOOP; + if (pms->nLoop == 2) psmp->uFlags |= CHN_LOOP|CHN_PINGPONGLOOP; + } + dwMemPos += pms->dwDataLen + 36; + } else + { + dwMemPos += 36; + } + } +#ifdef MT2DEBUG + Log("Loading groups at offset 0x%08X\n", dwMemPos); +#endif + for (UINT iMap=0; iMap<255; iMap++) if (InstrMap[iMap]) + { + if (dwMemPos+8 > dwMemLength) return TRUE; + MT2INSTRUMENT *pmi = InstrMap[iMap]; + INSTRUMENTHEADER *penv = NULL; + if (iMapwSamples; iGrp++) + { + if (penv) + { + MT2GROUP *pmg = (MT2GROUP *)(lpStream+dwMemPos); + for (UINT i=0; i<96; i++) + { + if (pmi->GroupsMapping[i] == iGrp) + { + UINT nSmp = pmg->nSmpNo+1; + penv->Keyboard[i+12] = (BYTE)nSmp; + if (nSmp <= m_nSamples) + { + Ins[nSmp].nVibType = pmi->bVibType; + Ins[nSmp].nVibSweep = pmi->bVibSweep; + Ins[nSmp].nVibDepth = pmi->bVibDepth; + Ins[nSmp].nVibRate = pmi->bVibRate; + } + } + } + } + dwMemPos += 8; + } + } +#ifdef MT2DEBUG + Log("Loading sample data at offset 0x%08X\n", dwMemPos); +#endif + for (UINT iData=0; iData<256; iData++) if ((iData < m_nSamples) && (SampleMap[iData])) + { + MT2SAMPLE *pms = SampleMap[iData]; + MODINSTRUMENT *psmp = &Ins[iData+1]; + if (!(pms->nFlags & 5)) + { + if (psmp->nLength > 0) + { + #ifdef MT2DEBUG + Log(" Reading sample #%d at offset 0x%04X (len=%d)\n", iData+1, dwMemPos, psmp->nLength); + #endif + UINT rsflags; + + if (pms->nChannels == 2) + rsflags = (psmp->uFlags & CHN_16BIT) ? RS_STPCM16D : RS_STPCM8D; + else + rsflags = (psmp->uFlags & CHN_16BIT) ? RS_PCM16D : RS_PCM8D; + + dwMemPos += ReadSample(psmp, rsflags, (LPCSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); + } + } else + if (dwMemPos+4 < dwMemLength) + { + UINT nNameLen = *(DWORD *)(lpStream+dwMemPos); + dwMemPos += nNameLen + 16; + } + if (dwMemPos+4 >= dwMemLength) break; + } + return TRUE; +} diff --git a/modplug/load_mtm.cpp b/modplug/load_mtm.cpp new file mode 100644 index 000000000..6bba0badf --- /dev/null +++ b/modplug/load_mtm.cpp @@ -0,0 +1,165 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +////////////////////////////////////////////////////////// +// MTM file support (import only) + +#pragma pack(1) + + +typedef struct tagMTMSAMPLE +{ + char samplename[22]; // changed from CHAR + DWORD length; + DWORD reppos; + DWORD repend; + CHAR finetune; + BYTE volume; + BYTE attribute; +} MTMSAMPLE; + + +typedef struct tagMTMHEADER +{ + char id[4]; // MTM file marker + version // changed from CHAR + char songname[20]; // ASCIIZ songname // changed from CHAR + WORD numtracks; // number of tracks saved + BYTE lastpattern; // last pattern number saved + BYTE lastorder; // last order number to play (songlength-1) + WORD commentsize; // length of comment field + BYTE numsamples; // number of samples saved + BYTE attribute; // attribute byte (unused) + BYTE beatspertrack; + BYTE numchannels; // number of channels used + BYTE panpos[32]; // voice pan positions +} MTMHEADER; + + +#pragma pack() + + +BOOL CSoundFile::ReadMTM(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + MTMHEADER *pmh = (MTMHEADER *)lpStream; + DWORD dwMemPos = 66; + + if ((!lpStream) || (dwMemLength < 0x100)) return FALSE; + if ((strncmp(pmh->id, "MTM", 3)) || (pmh->numchannels > 32) + || (pmh->numsamples >= MAX_SAMPLES) || (!pmh->numsamples) + || (!pmh->numtracks) || (!pmh->numchannels) + || (!pmh->lastpattern) || (pmh->lastpattern > MAX_PATTERNS)) return FALSE; + strncpy(m_szNames[0], pmh->songname, 20); + m_szNames[0][20] = 0; + if (dwMemPos + 37*pmh->numsamples + 128 + 192*pmh->numtracks + + 64 * (pmh->lastpattern+1) + pmh->commentsize >= dwMemLength) return FALSE; + m_nType = MOD_TYPE_MTM; + m_nSamples = pmh->numsamples; + m_nChannels = pmh->numchannels; + // Reading instruments + for (UINT i=1; i<=m_nSamples; i++) + { + MTMSAMPLE *pms = (MTMSAMPLE *)(lpStream + dwMemPos); + strncpy(m_szNames[i], pms->samplename, 22); + m_szNames[i][22] = 0; + Ins[i].nVolume = pms->volume << 2; + Ins[i].nGlobalVol = 64; + DWORD len = pms->length; + if ((len > 4) && (len <= MAX_SAMPLE_LENGTH)) + { + Ins[i].nLength = len; + Ins[i].nLoopStart = pms->reppos; + Ins[i].nLoopEnd = pms->repend; + if (Ins[i].nLoopEnd > Ins[i].nLength) Ins[i].nLoopEnd = Ins[i].nLength; + if (Ins[i].nLoopStart + 4 >= Ins[i].nLoopEnd) Ins[i].nLoopStart = Ins[i].nLoopEnd = 0; + if (Ins[i].nLoopEnd) Ins[i].uFlags |= CHN_LOOP; + Ins[i].nFineTune = MOD2XMFineTune(pms->finetune); + if (pms->attribute & 0x01) + { + Ins[i].uFlags |= CHN_16BIT; + Ins[i].nLength >>= 1; + Ins[i].nLoopStart >>= 1; + Ins[i].nLoopEnd >>= 1; + } + Ins[i].nPan = 128; + } + dwMemPos += 37; + } + // Setting Channel Pan Position + for (UINT ich=0; ichpanpos[ich] & 0x0F) << 4) + 8; + ChnSettings[ich].nVolume = 64; + } + // Reading pattern order + memcpy(Order, lpStream + dwMemPos, pmh->lastorder+1); + dwMemPos += 128; + // Reading Patterns + LPCBYTE pTracks = lpStream + dwMemPos; + dwMemPos += 192 * pmh->numtracks; + LPWORD pSeq = (LPWORD)(lpStream + dwMemPos); + for (UINT pat=0; pat<=pmh->lastpattern; pat++) + { + PatternSize[pat] = 64; + PatternAllocSize[pat] = 64; + if ((Patterns[pat] = AllocatePattern(64, m_nChannels)) == NULL) break; + for (UINT n=0; n<32; n++) if ((pSeq[n]) && (pSeq[n] <= pmh->numtracks) && (n < m_nChannels)) + { + LPCBYTE p = pTracks + 192 * (pSeq[n]-1); + MODCOMMAND *m = Patterns[pat] + n; + for (UINT i=0; i<64; i++, m+=m_nChannels, p+=3) + { + if (p[0] & 0xFC) m->note = (p[0] >> 2) + 37; + m->instr = ((p[0] & 0x03) << 4) | (p[1] >> 4); + UINT cmd = p[1] & 0x0F; + UINT param = p[2]; + if (cmd == 0x0A) + { + if (param & 0xF0) param &= 0xF0; else param &= 0x0F; + } + m->command = cmd; + m->param = param; + if ((cmd) || (param)) ConvertModCommand(m); + } + } + pSeq += 32; + } + dwMemPos += 64*(pmh->lastpattern+1); + if ((pmh->commentsize) && (dwMemPos + pmh->commentsize < dwMemLength)) + { + UINT n = pmh->commentsize; + m_lpszSongComments = new char[n+1]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos, n); + m_lpszSongComments[n] = 0; + for (UINT i=0; icommentsize; + // Reading Samples + for (UINT ismp=1; ismp<=m_nSamples; ismp++) + { + if (dwMemPos >= dwMemLength) break; + dwMemPos += ReadSample(&Ins[ismp], (Ins[ismp].uFlags & CHN_16BIT) ? RS_PCM16U : RS_PCM8U, + (LPSTR)(lpStream + dwMemPos), dwMemLength - dwMemPos); + } + m_nMinPeriod = 64; + m_nMaxPeriod = 32767; + return TRUE; +} + diff --git a/modplug/load_okt.cpp b/modplug/load_okt.cpp new file mode 100644 index 000000000..61eda9644 --- /dev/null +++ b/modplug/load_okt.cpp @@ -0,0 +1,198 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +////////////////////////////////////////////// +// Oktalyzer (OKT) module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +typedef struct OKTFILEHEADER +{ + DWORD okta; // "OKTA" + DWORD song; // "SONG" + DWORD cmod; // "CMOD" + DWORD fixed8; + BYTE chnsetup[8]; + DWORD samp; // "SAMP" + DWORD samplen; +} OKTFILEHEADER; + + +typedef struct OKTSAMPLE +{ + CHAR name[20]; + DWORD length; + WORD loopstart; + WORD looplen; + BYTE pad1; + BYTE volume; + BYTE pad2; + BYTE pad3; +} OKTSAMPLE; + + +BOOL CSoundFile::ReadOKT(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + OKTFILEHEADER *pfh = (OKTFILEHEADER *)lpStream; + DWORD dwMemPos = sizeof(OKTFILEHEADER); + UINT nsamples = 0, npatterns = 0, norders = 0; + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pfh->okta != 0x41544B4F) || (pfh->song != 0x474E4F53) + || (pfh->cmod != 0x444F4D43) || (pfh->chnsetup[0]) || (pfh->chnsetup[2]) + || (pfh->chnsetup[4]) || (pfh->chnsetup[6]) || (pfh->fixed8 != 0x08000000) + || (pfh->samp != 0x504D4153)) return FALSE; + m_nType = MOD_TYPE_OKT; + m_nChannels = 4 + pfh->chnsetup[1] + pfh->chnsetup[3] + pfh->chnsetup[5] + pfh->chnsetup[7]; + if (m_nChannels > MAX_CHANNELS) m_nChannels = MAX_CHANNELS; + nsamples = bswapBE32(pfh->samplen) >> 5; + m_nSamples = nsamples; + if (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1; + // Reading samples + for (UINT smp=1; smp <= nsamples; smp++) + { + if (dwMemPos >= dwMemLength) return TRUE; + if (smp < MAX_SAMPLES) + { + OKTSAMPLE *psmp = (OKTSAMPLE *)(lpStream + dwMemPos); + MODINSTRUMENT *pins = &Ins[smp]; + + memcpy(m_szNames[smp], psmp->name, 20); + pins->uFlags = 0; + pins->nLength = bswapBE32(psmp->length) & ~1; + pins->nLoopStart = bswapBE16(psmp->loopstart); + pins->nLoopEnd = pins->nLoopStart + bswapBE16(psmp->looplen); + if (pins->nLoopStart + 2 < pins->nLoopEnd) pins->uFlags |= CHN_LOOP; + pins->nGlobalVol = 64; + pins->nVolume = psmp->volume << 2; + pins->nC4Speed = 8363; + } + dwMemPos += sizeof(OKTSAMPLE); + } + // SPEE + if (dwMemPos >= dwMemLength) return TRUE; + if (*((DWORD *)(lpStream + dwMemPos)) == 0x45455053) + { + m_nDefaultSpeed = lpStream[dwMemPos+9]; + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // SLEN + if (dwMemPos >= dwMemLength) return TRUE; + if (*((DWORD *)(lpStream + dwMemPos)) == 0x4E454C53) + { + npatterns = lpStream[dwMemPos+9]; + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // PLEN + if (dwMemPos >= dwMemLength) return TRUE; + if (*((DWORD *)(lpStream + dwMemPos)) == 0x4E454C50) + { + norders = lpStream[dwMemPos+9]; + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // PATT + if (dwMemPos >= dwMemLength) return TRUE; + if (*((DWORD *)(lpStream + dwMemPos)) == 0x54544150) + { + UINT orderlen = norders; + if (orderlen >= MAX_ORDERS) orderlen = MAX_ORDERS-1; + for (UINT i=0; i1; j--) { if (Order[j-1]) break; Order[j-1] = 0xFF; } + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // PBOD + UINT npat = 0; + while ((dwMemPos+10 < dwMemLength) && (*((DWORD *)(lpStream + dwMemPos)) == 0x444F4250)) + { + DWORD dwPos = dwMemPos + 10; + UINT rows = lpStream[dwMemPos+9]; + if (!rows) rows = 64; + if (npat < MAX_PATTERNS) + { + if ((Patterns[npat] = AllocatePattern(rows, m_nChannels)) == NULL) return TRUE; + MODCOMMAND *m = Patterns[npat]; + PatternSize[npat] = rows; + PatternAllocSize[npat] = rows; + UINT imax = m_nChannels*rows; + for (UINT i=0; i dwMemLength) break; + const BYTE *p = lpStream+dwPos; + UINT note = p[0]; + if (note) + { + m->note = note + 48; + m->instr = p[1] + 1; + } + UINT command = p[2]; + UINT param = p[3]; + m->param = param; + switch(command) + { + // 0: no effect + case 0: + break; + // 1: Portamento Up + case 1: + case 17: + case 30: + if (param) m->command = CMD_PORTAMENTOUP; + break; + // 2: Portamento Down + case 2: + case 13: + case 21: + if (param) m->command = CMD_PORTAMENTODOWN; + break; + // 10: Arpeggio + case 10: + case 11: + case 12: + m->command = CMD_ARPEGGIO; + break; + // 15: Filter + case 15: + m->command = CMD_MODCMDEX; + m->param = param & 0x0F; + break; + // 25: Position Jump + case 25: + m->command = CMD_POSITIONJUMP; + break; + // 28: Set Speed + case 28: + m->command = CMD_SPEED; + break; + // 31: Volume Control + case 31: + if (param <= 0x40) m->command = CMD_VOLUME; else + if (param <= 0x50) { m->command = CMD_VOLUMESLIDE; m->param &= 0x0F; if (!m->param) m->param = 0x0F; } else + if (param <= 0x60) { m->command = CMD_VOLUMESLIDE; m->param = (param & 0x0F) << 4; if (!m->param) m->param = 0xF0; } else + if (param <= 0x70) { m->command = CMD_MODCMDEX; m->param = 0xB0 | (param & 0x0F); if (!(param & 0x0F)) m->param = 0xBF; } else + if (param <= 0x80) { m->command = CMD_MODCMDEX; m->param = 0xA0 | (param & 0x0F); if (!(param & 0x0F)) m->param = 0xAF; } + break; + } + } + } + npat++; + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // SBOD + UINT nsmp = 1; + while ((dwMemPos+10 < dwMemLength) && (*((DWORD *)(lpStream + dwMemPos)) == 0x444F4253)) + { + if (nsmp < MAX_SAMPLES) ReadSample(&Ins[nsmp], RS_PCM8S, (LPSTR)(lpStream+dwMemPos+8), dwMemLength-dwMemPos-8); + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + nsmp++; + } + return TRUE; +} + diff --git a/modplug/load_psm.cpp b/modplug/load_psm.cpp new file mode 100644 index 000000000..c499e4fce --- /dev/null +++ b/modplug/load_psm.cpp @@ -0,0 +1,840 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + + +/////////////////////////////////////////////////// +// +// PSM module loader +// +/////////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#define PSM_LOG + +#define PSM_ID_NEW 0x204d5350 +#define PSM_ID_OLD 0xfe4d5350 +#define IFFID_FILE 0x454c4946 +#define IFFID_TITL 0x4c544954 +#define IFFID_SDFT 0x54464453 +#define IFFID_PBOD 0x444f4250 +#define IFFID_SONG 0x474e4f53 +#define IFFID_PATT 0x54544150 +#define IFFID_DSMP 0x504d5344 +#define IFFID_OPLH 0x484c504f + +#pragma pack(1) + +typedef struct _PSMCHUNK +{ + DWORD id; + DWORD len; + DWORD listid; +} PSMCHUNK; + +typedef struct _PSMSONGHDR +{ + CHAR songname[8]; // "MAINSONG" + BYTE reserved1; + BYTE reserved2; + BYTE channels; +} PSMSONGHDR; + +typedef struct _PSMPATTERN +{ + DWORD size; + DWORD name; + WORD rows; + WORD reserved1; + BYTE data[4]; +} PSMPATTERN; + +typedef struct _PSMSAMPLE +{ + BYTE flags; + CHAR songname[8]; + DWORD smpid; + CHAR samplename[34]; + DWORD reserved1; + BYTE reserved2; + BYTE insno; + BYTE reserved3; + DWORD length; + DWORD loopstart; + DWORD loopend; + WORD reserved4; + BYTE defvol; + DWORD reserved5; + DWORD samplerate; + BYTE reserved6[19]; +} PSMSAMPLE; + +#pragma pack() + + +BOOL CSoundFile::ReadPSM(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + PSMCHUNK *pfh = (PSMCHUNK *)lpStream; + DWORD dwMemPos, dwSongPos; + DWORD smpnames[MAX_SAMPLES]; + DWORD patptrs[MAX_PATTERNS]; + BYTE samplemap[MAX_SAMPLES]; + UINT nPatterns; + + // Chunk0: "PSM ",filesize,"FILE" + if (dwMemLength < 256) return FALSE; + if (pfh->id == PSM_ID_OLD) + { + #ifdef PSM_LOG + Log("Old PSM format not supported\n"); + #endif + return FALSE; + } + if ((pfh->id != PSM_ID_NEW) || (pfh->len+12 > dwMemLength) || (pfh->listid != IFFID_FILE)) return FALSE; + m_nType = MOD_TYPE_PSM; + m_nChannels = 16; + m_nSamples = 0; + nPatterns = 0; + dwMemPos = 12; + dwSongPos = 0; + for (UINT iChPan=0; iChPan<16; iChPan++) + { + UINT pan = (((iChPan & 3) == 1) || ((iChPan&3)==2)) ? 0xC0 : 0x40; + ChnSettings[iChPan].nPan = pan; + } + while (dwMemPos+8 < dwMemLength) + { + PSMCHUNK *pchunk = (PSMCHUNK *)(lpStream+dwMemPos); + if ((pchunk->len >= dwMemLength - 8) || (dwMemPos + pchunk->len + 8 > dwMemLength)) break; + dwMemPos += 8; + PUCHAR pdata = (PUCHAR)(lpStream+dwMemPos); + ULONG len = pchunk->len; + if (len) switch(pchunk->id) + { + // "TITL": Song title + case IFFID_TITL: + if (!pdata[0]) { pdata++; len--; } + memcpy(m_szNames[0], pdata, (len>31) ? 31 : len); + m_szNames[0][31] = 0; + break; + // "PBOD": Pattern + case IFFID_PBOD: + if ((len >= 12) && (nPatterns < MAX_PATTERNS)) + { + patptrs[nPatterns++] = dwMemPos-8; + } + break; + // "SONG": Song description + case IFFID_SONG: + if ((len >= sizeof(PSMSONGHDR)+8) && (!dwSongPos)) + { + dwSongPos = dwMemPos - 8; + } + break; + // "DSMP": Sample Data + case IFFID_DSMP: + if ((len >= sizeof(PSMSAMPLE)) && (m_nSamples+1 < MAX_SAMPLES)) + { + m_nSamples++; + MODINSTRUMENT *pins = &Ins[m_nSamples]; + PSMSAMPLE *psmp = (PSMSAMPLE *)pdata; + smpnames[m_nSamples] = psmp->smpid; + memcpy(m_szNames[m_nSamples], psmp->samplename, 31); + m_szNames[m_nSamples][31] = 0; + samplemap[m_nSamples-1] = (BYTE)m_nSamples; + // Init sample + pins->nGlobalVol = 0x40; + pins->nC4Speed = psmp->samplerate; + pins->nLength = psmp->length; + pins->nLoopStart = psmp->loopstart; + pins->nLoopEnd = psmp->loopend; + pins->nPan = 128; + pins->nVolume = (psmp->defvol+1) * 2; + pins->uFlags = (psmp->flags & 0x80) ? CHN_LOOP : 0; + if (pins->nLoopStart > 0) pins->nLoopStart--; + // Point to sample data + pdata += 0x60; + len -= 0x60; + // Load sample data + if ((pins->nLength > 3) && (len > 3)) + { + ReadSample(pins, RS_PCM8D, (LPCSTR)pdata, len); + } else + { + pins->nLength = 0; + } + } + break; + #if 0 + default: + { + CHAR s[8], s2[64]; + *(DWORD *)s = pchunk->id; + s[4] = 0; + wsprintf(s2, "%s: %4d bytes @ %4d\n", s, pchunk->len, dwMemPos); + OutputDebugString(s2); + } + #endif + } + dwMemPos += pchunk->len; + } + // Step #1: convert song structure + PSMSONGHDR *pSong = (PSMSONGHDR *)(lpStream+dwSongPos+8); + if ((!dwSongPos) || (pSong->channels < 2) || (pSong->channels > 32)) return TRUE; + m_nChannels = pSong->channels; + // Valid song header -> convert attached chunks + { + DWORD dwSongEnd = dwSongPos + 8 + *(DWORD *)(lpStream+dwSongPos+4); + dwMemPos = dwSongPos + 8 + 11; // sizeof(PSMCHUNK)+sizeof(PSMSONGHDR) + while (dwMemPos + 8 < dwSongEnd) + { + PSMCHUNK *pchunk = (PSMCHUNK *)(lpStream+dwMemPos); + dwMemPos += 8; + if ((pchunk->len > dwSongEnd) || (dwMemPos + pchunk->len > dwSongEnd)) break; + PUCHAR pdata = (PUCHAR)(lpStream+dwMemPos); + ULONG len = pchunk->len; + switch(pchunk->id) + { + case IFFID_OPLH: + if (len >= 0x20) + { + UINT pos = len - 3; + while (pos > 5) + { + BOOL bFound = FALSE; + pos -= 5; + DWORD dwName = *(DWORD *)(pdata+pos); + for (UINT i=0; iname; + if (dwName == dwPatName) + { + bFound = TRUE; + break; + } + } + if ((!bFound) && (pdata[pos+1] > 0) && (pdata[pos+1] <= 0x10) + && (pdata[pos+3] > 0x40) && (pdata[pos+3] < 0xC0)) + { + m_nDefaultSpeed = pdata[pos+1]; + m_nDefaultTempo = pdata[pos+3]; + break; + } + } + UINT iOrd = 0; + while ((pos+5name; + if (dwName == dwPatName) + { + Order[iOrd++] = i; + break; + } + } + pos += 5; + } + } + break; + } + dwMemPos += pchunk->len; + } + } + + // Step #2: convert patterns + for (UINT nPat=0; nPatrows; + if (len > pPsmPat->size) len = pPsmPat->size; + if ((nRows < 64) || (nRows > 256)) nRows = 64; + PatternSize[nPat] = nRows; + PatternAllocSize[nPat] = nRows; + if ((Patterns[nPat] = AllocatePattern(nRows, m_nChannels)) == NULL) break; + MODCOMMAND *m = Patterns[nPat]; + BYTE *p = pPsmPat->data; + UINT pos = 0; + UINT row = 0; + UINT oldch = 0; + BOOL bNewRow = FALSE; + #ifdef PSM_LOG + Log("Pattern %d at offset 0x%04X\n", nPat, (DWORD)(p - (BYTE *)lpStream)); + #endif + while ((row < nRows) && (pos+1 < len)) + { + UINT flags = p[pos++]; + UINT ch = p[pos++]; + + #ifdef PSM_LOG + //Log("flags+ch: %02X.%02X\n", flags, ch); + #endif + if (((flags & 0xf0) == 0x10) && (ch <= oldch) /*&& (!bNewRow)*/) + { + if ((pos+1= len) || (row >= nRows)) break; + if (!(flags & 0xf0)) + { + #ifdef PSM_LOG + //if (!nPat) Log("EOR(%d): %02X.%02X\n", row, p[pos], p[pos+1]); + #endif + row++; + m += m_nChannels; + bNewRow = TRUE; + oldch = ch; + continue; + } + bNewRow = FALSE; + if (ch >= m_nChannels) + { + #ifdef PSM_LOG + if (!nPat) Log("Invalid channel row=%d (0x%02X.0x%02X)\n", row, flags, ch); + #endif + ch = 0; + } + // Note + Instr + if ((flags & 0x40) && (pos+1 < len)) + { + UINT note = p[pos++]; + UINT nins = p[pos++]; + #ifdef PSM_LOG + //if (!nPat) Log("note+ins: %02X.%02X\n", note, nins); + if ((!nPat) && (nins >= m_nSamples)) Log("WARNING: invalid instrument number (%d)\n", nins); + #endif + if ((note) && (note < 0x80)) note = (note>>4)*12+(note&0x0f)+12+1; + m[ch].instr = samplemap[nins]; + m[ch].note = note; + } + // Volume + if ((flags & 0x20) && (pos < len)) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = p[pos++] / 2; + } + // Effect + if ((flags & 0x10) && (pos+1 < len)) + { + UINT command = p[pos++]; + UINT param = p[pos++]; + // Convert effects + switch(command) + { + // 01: fine volslide up + case 0x01: command = CMD_VOLUMESLIDE; param |= 0x0f; break; + // 04: fine volslide down + case 0x04: command = CMD_VOLUMESLIDE; param>>=4; param |= 0xf0; break; + // 0C: portamento up + case 0x0C: command = CMD_PORTAMENTOUP; param = (param+1)/2; break; + // 0E: portamento down + case 0x0E: command = CMD_PORTAMENTODOWN; param = (param+1)/2; break; + // 33: Position Jump + case 0x33: command = CMD_POSITIONJUMP; break; + // 34: Pattern break + case 0x34: command = CMD_PATTERNBREAK; break; + // 3D: speed + case 0x3D: command = CMD_SPEED; break; + // 3E: tempo + case 0x3E: command = CMD_TEMPO; break; + // Unknown + default: + #ifdef PSM_LOG + Log("Unknown PSM effect pat=%d row=%d ch=%d: %02X.%02X\n", nPat, row, ch, command, param); + #endif + command = param = 0; + } + m[ch].command = (BYTE)command; + m[ch].param = (BYTE)param; + } + oldch = ch; + } + #ifdef PSM_LOG + if (pos < len) + { + Log("Pattern %d: %d/%d[%d] rows (%d bytes) -> %d bytes left\n", nPat, row, nRows, pPsmPat->rows, pPsmPat->size, len-pos); + } + #endif + } + + // Done (finally!) + return TRUE; +} + + +////////////////////////////////////////////////////////////// +// +// PSM Old Format +// + +/* + +CONST + c_PSM_MaxOrder = $FF; + c_PSM_MaxSample = $FF; + c_PSM_MaxChannel = $0F; + + TYPE + PPSM_Header = ^TPSM_Header; + TPSM_Header = RECORD + PSM_Sign : ARRAY[01..04] OF CHAR; { PSM + #254 } + PSM_SongName : ARRAY[01..58] OF CHAR; + PSM_Byte00 : BYTE; + PSM_Byte1A : BYTE; + PSM_Unknown00 : BYTE; + PSM_Unknown01 : BYTE; + PSM_Unknown02 : BYTE; + PSM_Speed : BYTE; + PSM_Tempo : BYTE; + PSM_Unknown03 : BYTE; + PSM_Unknown04 : WORD; + PSM_OrderLength : WORD; + PSM_PatternNumber : WORD; + PSM_SampleNumber : WORD; + PSM_ChannelNumber : WORD; + PSM_ChannelUsed : WORD; + PSM_OrderPosition : LONGINT; + PSM_ChannelSettingPosition : LONGINT; + PSM_PatternPosition : LONGINT; + PSM_SamplePosition : LONGINT; + { *** perhaps there are some more infos in a larger header, + but i have not decoded it and so it apears here NOT } + END; + + PPSM_Sample = ^TPSM_Sample; + TPSM_Sample = RECORD + PSM_SampleFileName : ARRAY[01..12] OF CHAR; + PSM_SampleByte00 : BYTE; + PSM_SampleName : ARRAY[01..22] OF CHAR; + PSM_SampleUnknown00 : ARRAY[01..02] OF BYTE; + PSM_SamplePosition : LONGINT; + PSM_SampleUnknown01 : ARRAY[01..04] OF BYTE; + PSM_SampleNumber : BYTE; + PSM_SampleFlags : WORD; + PSM_SampleLength : LONGINT; + PSM_SampleLoopBegin : LONGINT; + PSM_SampleLoopEnd : LONGINT; + PSM_Unknown03 : BYTE; + PSM_SampleVolume : BYTE; + PSM_SampleC5Speed : WORD; + END; + + PPSM_SampleList = ^TPSM_SampleList; + TPSM_SampleList = ARRAY[01..c_PSM_MaxSample] OF TPSM_Sample; + + PPSM_Order = ^TPSM_Order; + TPSM_Order = ARRAY[00..c_PSM_MaxOrder] OF BYTE; + + PPSM_ChannelSettings = ^TPSM_ChannelSettings; + TPSM_ChannelSettings = ARRAY[00..c_PSM_MaxChannel] OF BYTE; + + CONST + PSM_NotesInPattern : BYTE = $00; + PSM_ChannelInPattern : BYTE = $00; + + CONST + c_PSM_SetSpeed = 60; + + FUNCTION PSM_Size(FileName : STRING;FilePosition : LONGINT) : LONGINT; + BEGIN + END; + + PROCEDURE PSM_UnpackPattern(VAR Source,Destination;PatternLength : WORD); + VAR + Witz : ARRAY[00..04] OF WORD; + I1,I2 : WORD; + I3,I4 : WORD; + TopicalByte : ^BYTE; + Pattern : PUnpackedPattern; + ChannelP : BYTE; + NoteP : BYTE; + InfoByte : BYTE; + CodeByte : BYTE; + InfoWord : WORD; + Effect : BYTE; + Opperand : BYTE; + Panning : BYTE; + Volume : BYTE; + PrevInfo : BYTE; + InfoIndex : BYTE; + BEGIN + Pattern := @Destination; + TopicalByte := @Source; + { *** Initialize patttern } + FOR I2 := 0 TO c_Maximum_NoteIndex DO + FOR I3 := 0 TO c_Maximum_ChannelIndex DO + BEGIN + Pattern^[I2,I3,c_Pattern_NoteIndex] := $FF; + Pattern^[I2,I3,c_Pattern_SampleIndex] := $00; + Pattern^[I2,I3,c_Pattern_VolumeIndex] := $FF; + Pattern^[I2,I3,c_Pattern_PanningIndex] := $FF; + Pattern^[I2,I3,c_Pattern_EffectIndex] := $00; + Pattern^[I2,I3,c_Pattern_OpperandIndex] := $00; + END; + { *** Byte-pointer on first pattern-entry } + ChannelP := $00; + NoteP := $00; + InfoByte := $00; + PrevInfo := $00; + InfoIndex := $02; + { *** read notes in pattern } + PSM_NotesInPattern := TopicalByte^; INC(TopicalByte); DEC(PatternLength); INC(InfoIndex); + PSM_ChannelInPattern := TopicalByte^; INC(TopicalByte); DEC(PatternLength); INC(InfoIndex); + { *** unpack pattern } + WHILE (INTEGER(PatternLength) > 0) AND (NoteP < c_Maximum_NoteIndex) DO + BEGIN + { *** Read info-byte } + InfoByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength); INC(InfoIndex); + IF InfoByte <> $00 THEN + BEGIN + ChannelP := InfoByte AND $0F; + IF InfoByte AND 128 = 128 THEN { note and sample } + BEGIN + { *** read note } + CodeByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + DEC(CodeByte); + CodeByte := CodeByte MOD 12 * 16 + CodeByte DIV 12 + 2; + Pattern^[NoteP,ChannelP,c_Pattern_NoteIndex] := CodeByte; + { *** read sample } + CodeByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + Pattern^[NoteP,ChannelP,c_Pattern_SampleIndex] := CodeByte; + END; + IF InfoByte AND 64 = 64 THEN { Volume } + BEGIN + CodeByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + Pattern^[NoteP,ChannelP,c_Pattern_VolumeIndex] := CodeByte; + END; + IF InfoByte AND 32 = 32 THEN { effect AND opperand } + BEGIN + Effect := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + Opperand := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + CASE Effect OF + c_PSM_SetSpeed: + BEGIN + Effect := c_I_Set_Speed; + END; + ELSE + BEGIN + Effect := c_I_NoEffect; + Opperand := $00; + END; + END; + Pattern^[NoteP,ChannelP,c_Pattern_EffectIndex] := Effect; + Pattern^[NoteP,ChannelP,c_Pattern_OpperandIndex] := Opperand; + END; + END ELSE INC(NoteP); + END; + END; + + PROCEDURE PSM_Load(FileName : STRING;FilePosition : LONGINT;VAR Module : PModule;VAR ErrorCode : WORD); + { *** caution : Module has to be inited before!!!! } + VAR + Header : PPSM_Header; + Sample : PPSM_SampleList; + Order : PPSM_Order; + ChannelSettings : PPSM_ChannelSettings; + MultiPurposeBuffer : PByteArray; + PatternBuffer : PUnpackedPattern; + TopicalParaPointer : WORD; + + InFile : FILE; + I1,I2 : WORD; + I3,I4 : WORD; + TempW : WORD; + TempB : BYTE; + TempP : PByteArray; + TempI : INTEGER; + { *** copy-vars for loop-extension } + CopySource : LONGINT; + CopyDestination : LONGINT; + CopyLength : LONGINT; + BEGIN + { *** try to open file } + ASSIGN(InFile,FileName); +{$I-} + RESET(InFile,1); +{$I+} + IF IORESULT <> $00 THEN + BEGIN + EXIT; + END; +{$I-} + { *** seek start of module } + IF FILESIZE(InFile) < FilePosition THEN + BEGIN + EXIT; + END; + SEEK(InFile,FilePosition); + { *** look for enough memory for temporary variables } + IF MEMAVAIL < SIZEOF(TPSM_Header) + SIZEOF(TPSM_SampleList) + + SIZEOF(TPSM_Order) + SIZEOF(TPSM_ChannelSettings) + + SIZEOF(TByteArray) + SIZEOF(TUnpackedPattern) + THEN + BEGIN + EXIT; + END; + { *** init dynamic variables } + NEW(Header); + NEW(Sample); + NEW(Order); + NEW(ChannelSettings); + NEW(MultiPurposeBuffer); + NEW(PatternBuffer); + { *** read header } + BLOCKREAD(InFile,Header^,SIZEOF(TPSM_Header)); + { *** test if this is a DSM-file } + IF NOT ((Header^.PSM_Sign[1] = 'P') AND (Header^.PSM_Sign[2] = 'S') AND + (Header^.PSM_Sign[3] = 'M') AND (Header^.PSM_Sign[4] = #254)) THEN + BEGIN + ErrorCode := c_NoValidFileFormat; + CLOSE(InFile); + EXIT; + END; + { *** read order } + SEEK(InFile,FilePosition + Header^.PSM_OrderPosition); + BLOCKREAD(InFile,Order^,Header^.PSM_OrderLength); + { *** read channelsettings } + SEEK(InFile,FilePosition + Header^.PSM_ChannelSettingPosition); + BLOCKREAD(InFile,ChannelSettings^,SIZEOF(TPSM_ChannelSettings)); + { *** read samplelist } + SEEK(InFile,FilePosition + Header^.PSM_SamplePosition); + BLOCKREAD(InFile,Sample^,Header^.PSM_SampleNumber * SIZEOF(TPSM_Sample)); + { *** copy header to intern NTMIK-structure } + Module^.Module_Sign := 'MF'; + Module^.Module_FileFormatVersion := $0100; + Module^.Module_SampleNumber := Header^.PSM_SampleNumber; + Module^.Module_PatternNumber := Header^.PSM_PatternNumber; + Module^.Module_OrderLength := Header^.PSM_OrderLength; + Module^.Module_ChannelNumber := Header^.PSM_ChannelNumber+1; + Module^.Module_Initial_GlobalVolume := 64; + Module^.Module_Initial_MasterVolume := $C0; + Module^.Module_Initial_Speed := Header^.PSM_Speed; + Module^.Module_Initial_Tempo := Header^.PSM_Tempo; +{ *** paragraph 01 start } + Module^.Module_Flags := c_Module_Flags_ZeroVolume * BYTE(1) + + c_Module_Flags_Stereo * BYTE(1) + + c_Module_Flags_ForceAmigaLimits * BYTE(0) + + c_Module_Flags_Panning * BYTE(1) + + c_Module_Flags_Surround * BYTE(1) + + c_Module_Flags_QualityMixing * BYTE(1) + + c_Module_Flags_FastVolumeSlides * BYTE(0) + + c_Module_Flags_SpecialCustomData * BYTE(0) + + c_Module_Flags_SongName * BYTE(1); + I1 := $01; + WHILE (Header^.PSM_SongName[I1] > #00) AND (I1 < c_Module_SongNameLength) DO + BEGIN + Module^.Module_Name[I1] := Header^.PSM_SongName[I1]; + INC(I1); + END; + Module^.Module_Name[c_Module_SongNameLength] := #00; + { *** Init channelsettings } + FOR I1 := 0 TO c_Maximum_ChannelIndex DO + BEGIN + IF I1 < Header^.PSM_ChannelUsed THEN + BEGIN + { *** channel enabled } + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_GlobalVolume := 64; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Panning := (ChannelSettings^[I1]) * $08; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Code := I1 + $10 * BYTE(ChannelSettings^[I1] > $08) + + c_ChannelSettings_Code_ChannelEnabled * BYTE(1) + + c_ChannelSettings_Code_ChannelDigital * BYTE(1); + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Controls := + c_ChannelSettings_Controls_EnhancedMode * BYTE(1) + + c_ChannelSettings_Controls_SurroundMode * BYTE(0); + END + ELSE + BEGIN + { *** channel disabled } + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_GlobalVolume := $00; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Panning := $00; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Code := $00; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Controls := $00; + END; + END; + { *** init and copy order } + FILLCHAR(Module^.Module_OrderPointer^,c_Maximum_OrderIndex+1,$FF); + MOVE(Order^,Module^.Module_OrderPointer^,Header^.PSM_OrderLength); + { *** read pattern } + SEEK(InFile,FilePosition + Header^.PSM_PatternPosition); + NTMIK_LoaderPatternNumber := Header^.PSM_PatternNumber-1; + FOR I1 := 0 TO Header^.PSM_PatternNumber-1 DO + BEGIN + NTMIK_LoadPatternProcedure; + { *** read length } + BLOCKREAD(InFile,TempW,2); + { *** read pattern } + BLOCKREAD(InFile,MultiPurposeBuffer^,TempW-2); + { *** unpack pattern and set notes per channel to 64 } + PSM_UnpackPattern(MultiPurposeBuffer^,PatternBuffer^,TempW); + NTMIK_PackPattern(MultiPurposeBuffer^,PatternBuffer^,PSM_NotesInPattern); + TempW := WORD(256) * MultiPurposeBuffer^[01] + MultiPurposeBuffer^[00]; + GETMEM(Module^.Module_PatternPointer^[I1],TempW); + MOVE(MultiPurposeBuffer^,Module^.Module_PatternPointer^[I1]^,TempW); + { *** next pattern } + END; + { *** read samples } + NTMIK_LoaderSampleNumber := Header^.PSM_SampleNumber; + FOR I1 := 1 TO Header^.PSM_SampleNumber DO + BEGIN + NTMIK_LoadSampleProcedure; + { *** get index for sample } + I3 := Sample^[I1].PSM_SampleNumber; + { *** clip PSM-sample } + IF Sample^[I1].PSM_SampleLoopEnd > Sample^[I1].PSM_SampleLength + THEN Sample^[I1].PSM_SampleLoopEnd := Sample^[I1].PSM_SampleLength; + { *** init intern sample } + NEW(Module^.Module_SamplePointer^[I3]); + FILLCHAR(Module^.Module_SamplePointer^[I3]^,SIZEOF(TSample),$00); + FILLCHAR(Module^.Module_SamplePointer^[I3]^.Sample_SampleName,c_Sample_SampleNameLength,#32); + FILLCHAR(Module^.Module_SamplePointer^[I3]^.Sample_FileName,c_Sample_FileNameLength,#32); + { *** copy informations to intern sample } + I2 := $01; + WHILE (Sample^[I1].PSM_SampleName[I2] > #00) AND (I2 < c_Sample_SampleNameLength) DO + BEGIN + Module^.Module_SamplePointer^[I3]^.Sample_SampleName[I2] := Sample^[I1].PSM_SampleName[I2]; + INC(I2); + END; + Module^.Module_SamplePointer^[I3]^.Sample_Sign := 'DF'; + Module^.Module_SamplePointer^[I3]^.Sample_FileFormatVersion := $00100; + Module^.Module_SamplePointer^[I3]^.Sample_Position := $00000000; + Module^.Module_SamplePointer^[I3]^.Sample_Selector := $0000; + Module^.Module_SamplePointer^[I3]^.Sample_Volume := Sample^[I1].PSM_SampleVolume; + Module^.Module_SamplePointer^[I3]^.Sample_LoopCounter := $00; + Module^.Module_SamplePointer^[I3]^.Sample_C5Speed := Sample^[I1].PSM_SampleC5Speed; + Module^.Module_SamplePointer^[I3]^.Sample_Length := Sample^[I1].PSM_SampleLength; + Module^.Module_SamplePointer^[I3]^.Sample_LoopBegin := Sample^[I1].PSM_SampleLoopBegin; + Module^.Module_SamplePointer^[I3]^.Sample_LoopEnd := Sample^[I1].PSM_SampleLoopEnd; + { *** now it's time for the flags } + Module^.Module_SamplePointer^[I3]^.Sample_Flags := + c_Sample_Flags_DigitalSample * BYTE(1) + + c_Sample_Flags_8BitSample * BYTE(1) + + c_Sample_Flags_UnsignedSampleData * BYTE(1) + + c_Sample_Flags_Packed * BYTE(0) + + c_Sample_Flags_LoopCounter * BYTE(0) + + c_Sample_Flags_SampleName * BYTE(1) + + c_Sample_Flags_LoopActive * + BYTE(Sample^[I1].PSM_SampleFlags AND (LONGINT(1) SHL 15) = (LONGINT(1) SHL 15)); + { *** alloc memory for sample-data } + E_Getmem(Module^.Module_SamplePointer^[I3]^.Sample_Selector, + Module^.Module_SamplePointer^[I3]^.Sample_Position, + Module^.Module_SamplePointer^[I3]^.Sample_Length + c_LoopExtensionSize); + { *** read out data } + EPT(TempP).p_Selector := Module^.Module_SamplePointer^[I3]^.Sample_Selector; + EPT(TempP).p_Offset := $0000; + SEEK(InFile,Sample^[I1].PSM_SamplePosition); + E_BLOCKREAD(InFile,TempP^,Module^.Module_SamplePointer^[I3]^.Sample_Length); + { *** 'coz the samples are signed in a DSM-file -> PC-fy them } + IF Module^.Module_SamplePointer^[I3]^.Sample_Length > 4 THEN + BEGIN + CopyLength := Module^.Module_SamplePointer^[I3]^.Sample_Length; + { *** decode sample } + ASM + DB 066h; MOV CX,WORD PTR CopyLength + { *** load sample selector } + MOV ES,WORD PTR TempP[00002h] + DB 066h; XOR SI,SI + DB 066h; XOR DI,DI + XOR AH,AH + { *** conert all bytes } + @@MainLoop: + DB 026h; DB 067h; LODSB + ADD AL,AH + MOV AH,AL + DB 067h; STOSB + DB 066h; LOOP @@MainLoop + END; + { *** make samples unsigned } + ASM + DB 066h; MOV CX,WORD PTR CopyLength + { *** load sample selector } + MOV ES,WORD PTR TempP[00002h] + DB 066h; XOR SI,SI + DB 066h; XOR DI,DI + { *** conert all bytes } + @@MainLoop: + DB 026h; DB 067h; LODSB + SUB AL,080h + DB 067h; STOSB + DB 066h; LOOP @@MainLoop + END; + { *** Create Loop-Extension } + IF Module^.Module_SamplePointer^[I3]^.Sample_Flags AND c_Sample_Flags_LoopActive = c_Sample_Flags_LoopActive THEN + BEGIN + CopySource := Module^.Module_SamplePointer^[I3]^.Sample_LoopBegin; + CopyDestination := Module^.Module_SamplePointer^[I3]^.Sample_LoopEnd; + CopyLength := CopyDestination - CopySource; + ASM + { *** load sample-selector } + MOV ES,WORD PTR TempP[00002h] + DB 066h; MOV DI,WORD PTR CopyDestination + { *** calculate number of full sample-loops to copy } + XOR DX,DX + MOV AX,c_LoopExtensionSize + MOV BX,WORD PTR CopyLength + DIV BX + OR AX,AX + JE @@NoFullLoop + { *** copy some full-loops (size=bx) } + MOV CX,AX + @@InnerLoop: + PUSH CX + DB 066h; MOV SI,WORD PTR CopySource + MOV CX,BX + DB 0F3h; DB 026h,067h,0A4h { REP MOVS BYTE PTR ES:[EDI],ES:[ESI] } + POP CX + LOOP @@InnerLoop + @@NoFullLoop: + { *** calculate number of rest-bytes to copy } + DB 066h; MOV SI,WORD PTR CopySource + MOV CX,DX + DB 0F3h; DB 026h,067h,0A4h { REP MOVS BYTE PTR ES:[EDI],ES:[ESI] } + END; + END + ELSE + BEGIN + CopyDestination := Module^.Module_SamplePointer^[I3]^.Sample_Length; + ASM + { *** load sample-selector } + MOV ES,WORD PTR TempP[00002h] + DB 066h; MOV DI,WORD PTR CopyDestination + { *** clear extension } + MOV CX,c_LoopExtensionSize + MOV AL,080h + DB 0F3h; DB 067h,0AAh { REP STOS BYTE PTR ES:[EDI] } + END; + END; + END; + { *** next sample } + END; + { *** init period-ranges } + NTMIK_MaximumPeriod := $0000D600 SHR 1; + NTMIK_MinimumPeriod := $0000D600 SHR 8; + { *** close file } + CLOSE(InFile); + { *** dispose all dynamic variables } + DISPOSE(Header); + DISPOSE(Sample); + DISPOSE(Order); + DISPOSE(ChannelSettings); + DISPOSE(MultiPurposeBuffer); + DISPOSE(PatternBuffer); + { *** set errorcode to noerror } + ErrorCode := c_NoError; + END; + +*/ + diff --git a/modplug/load_ptm.cpp b/modplug/load_ptm.cpp new file mode 100644 index 000000000..f67b88256 --- /dev/null +++ b/modplug/load_ptm.cpp @@ -0,0 +1,208 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +////////////////////////////////////////////// +// PTM PolyTracker module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct PTMFILEHEADER +{ + CHAR songname[28]; // name of song, asciiz string + CHAR eof; // 26 + BYTE version_lo; // 03 version of file, currently 0203h + BYTE version_hi; // 02 + BYTE reserved1; // reserved, set to 0 + WORD norders; // number of orders (0..256) + WORD nsamples; // number of instruments (1..255) + WORD npatterns; // number of patterns (1..128) + WORD nchannels; // number of channels (voices) used (1..32) + WORD fileflags; // set to 0 + WORD reserved2; // reserved, set to 0 + DWORD ptmf_id; // song identification, 'PTMF' or 0x464d5450 + BYTE reserved3[16]; // reserved, set to 0 + BYTE chnpan[32]; // channel panning settings, 0..15, 0 = left, 7 = middle, 15 = right + BYTE orders[256]; // order list, valid entries 0..nOrders-1 + WORD patseg[128]; // pattern offsets (*16) +} PTMFILEHEADER, *LPPTMFILEHEADER; + +#define SIZEOF_PTMFILEHEADER 608 + + +typedef struct PTMSAMPLE +{ + BYTE sampletype; // sample type (bit array) + CHAR filename[12]; // name of external sample file + BYTE volume; // default volume + WORD nC4Spd; // C4 speed + WORD sampleseg; // sample segment (used internally) + WORD fileofs[2]; // offset of sample data + WORD length[2]; // sample size (in bytes) + WORD loopbeg[2]; // start of loop + WORD loopend[2]; // end of loop + WORD gusdata[8]; + char samplename[28]; // name of sample, asciiz // changed from CHAR + DWORD ptms_id; // sample identification, 'PTMS' or 0x534d5450 +} PTMSAMPLE; + +#define SIZEOF_PTMSAMPLE 80 + +#pragma pack() + + +BOOL CSoundFile::ReadPTM(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + PTMFILEHEADER pfh = *(LPPTMFILEHEADER)lpStream; + DWORD dwMemPos; + UINT nOrders; + + pfh.norders = bswapLE16(pfh.norders); + pfh.nsamples = bswapLE16(pfh.nsamples); + pfh.npatterns = bswapLE16(pfh.npatterns); + pfh.nchannels = bswapLE16(pfh.nchannels); + pfh.fileflags = bswapLE16(pfh.fileflags); + pfh.reserved2 = bswapLE16(pfh.reserved2); + pfh.ptmf_id = bswapLE32(pfh.ptmf_id); + for (UINT j=0; j<128; j++) + { + pfh.patseg[j] = bswapLE16(pfh.patseg[j]); + } + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pfh.ptmf_id != 0x464d5450) || (!pfh.nchannels) + || (pfh.nchannels > 32) + || (pfh.norders > 256) || (!pfh.norders) + || (!pfh.nsamples) || (pfh.nsamples > 255) + || (!pfh.npatterns) || (pfh.npatterns > 128) + || (SIZEOF_PTMFILEHEADER+pfh.nsamples*SIZEOF_PTMSAMPLE >= (int)dwMemLength)) return FALSE; + memcpy(m_szNames[0], pfh.songname, 28); + m_szNames[0][28] = 0; + m_nType = MOD_TYPE_PTM; + m_nChannels = pfh.nchannels; + m_nSamples = (pfh.nsamples < MAX_SAMPLES) ? pfh.nsamples : MAX_SAMPLES-1; + dwMemPos = SIZEOF_PTMFILEHEADER; + nOrders = (pfh.norders < MAX_ORDERS) ? pfh.norders : MAX_ORDERS-1; + memcpy(Order, pfh.orders, nOrders); + for (UINT ipan=0; ipansamplename, 28); + memcpy(pins->name, psmp->filename, 12); + pins->name[12] = 0; + pins->nGlobalVol = 64; + pins->nPan = 128; + pins->nVolume = psmp->volume << 2; + pins->nC4Speed = bswapLE16(psmp->nC4Spd) << 1; + pins->uFlags = 0; + if ((psmp->sampletype & 3) == 1) + { + UINT smpflg = RS_PCM8D; + DWORD samplepos; + pins->nLength = bswapLE32(*(LPDWORD)(psmp->length)); + pins->nLoopStart = bswapLE32(*(LPDWORD)(psmp->loopbeg)); + pins->nLoopEnd = bswapLE32(*(LPDWORD)(psmp->loopend)); + samplepos = bswapLE32(*(LPDWORD)(&psmp->fileofs)); + if (psmp->sampletype & 4) pins->uFlags |= CHN_LOOP; + if (psmp->sampletype & 8) pins->uFlags |= CHN_PINGPONGLOOP; + if (psmp->sampletype & 16) + { + pins->uFlags |= CHN_16BIT; + pins->nLength >>= 1; + pins->nLoopStart >>= 1; + pins->nLoopEnd >>= 1; + smpflg = RS_PTM8DTO16; + } + if ((pins->nLength) && (samplepos) && (samplepos < dwMemLength)) + { + ReadSample(pins, smpflg, (LPSTR)(lpStream+samplepos), dwMemLength-samplepos); + } + } + } + // Reading Patterns + for (UINT ipat=0; ipat= dwMemLength)) continue; + PatternSize[ipat] = 64; + PatternAllocSize[ipat] = 64; + if ((Patterns[ipat] = AllocatePattern(64, m_nChannels)) == NULL) break; + // + MODCOMMAND *m = Patterns[ipat]; + for (UINT row=0; ((row < 64) && (dwMemPos < dwMemLength)); ) + { + UINT b = lpStream[dwMemPos++]; + + if (dwMemPos >= dwMemLength) break; + if (b) + { + UINT nChn = b & 0x1F; + + if (b & 0x20) + { + if (dwMemPos + 2 > dwMemLength) break; + m[nChn].note = lpStream[dwMemPos++]; + m[nChn].instr = lpStream[dwMemPos++]; + } + if (b & 0x40) + { + if (dwMemPos + 2 > dwMemLength) break; + m[nChn].command = lpStream[dwMemPos++]; + m[nChn].param = lpStream[dwMemPos++]; + if ((m[nChn].command == 0x0E) && ((m[nChn].param & 0xF0) == 0x80)) + { + m[nChn].command = CMD_S3MCMDEX; + } else + if (m[nChn].command < 0x10) + { + ConvertModCommand(&m[nChn]); + } else + { + switch(m[nChn].command) + { + case 16: + m[nChn].command = CMD_GLOBALVOLUME; + break; + case 17: + m[nChn].command = CMD_RETRIG; + break; + case 18: + m[nChn].command = CMD_FINEVIBRATO; + break; + default: + m[nChn].command = 0; + } + } + } + if (b & 0x80) + { + if (dwMemPos >= dwMemLength) break; + m[nChn].volcmd = VOLCMD_VOLUME; + m[nChn].vol = lpStream[dwMemPos++]; + } + } else + { + row++; + m += m_nChannels; + } + } + } + return TRUE; +} + diff --git a/modplug/load_s3m.cpp b/modplug/load_s3m.cpp new file mode 100644 index 000000000..079497937 --- /dev/null +++ b/modplug/load_s3m.cpp @@ -0,0 +1,654 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +extern WORD S3MFineTuneTable[16]; + +////////////////////////////////////////////////////// +// ScreamTracker S3M file support + +typedef struct tagS3MSAMPLESTRUCT +{ + BYTE type; + CHAR dosname[12]; + BYTE hmem; + WORD memseg; + DWORD length; + DWORD loopbegin; + DWORD loopend; + BYTE vol; + BYTE bReserved; + BYTE pack; + BYTE flags; + DWORD finetune; + DWORD dwReserved; + WORD intgp; + WORD int512; + DWORD lastused; + CHAR name[28]; + CHAR scrs[4]; +} S3MSAMPLESTRUCT; + + +typedef struct tagS3MFILEHEADER +{ + CHAR name[28]; + BYTE b1A; + BYTE type; + WORD reserved1; + WORD ordnum; + WORD insnum; + WORD patnum; + WORD flags; + WORD cwtv; + WORD version; + DWORD scrm; // "SCRM" = 0x4D524353 + BYTE globalvol; + BYTE speed; + BYTE tempo; + BYTE mastervol; + BYTE ultraclicks; + BYTE panning_present; + BYTE reserved2[8]; + WORD special; + BYTE channels[32]; +} S3MFILEHEADER; + + +void CSoundFile::S3MConvert(MODCOMMAND *m, BOOL bIT) const +//-------------------------------------------------------- +{ + UINT command = m->command; + UINT param = m->param; + switch (command + 0x40) + { + case 'A': command = CMD_SPEED; break; + case 'B': command = CMD_POSITIONJUMP; break; + case 'C': command = CMD_PATTERNBREAK; if (!bIT) param = (param >> 4) * 10 + (param & 0x0F); break; + case 'D': command = CMD_VOLUMESLIDE; break; + case 'E': command = CMD_PORTAMENTODOWN; break; + case 'F': command = CMD_PORTAMENTOUP; break; + case 'G': command = CMD_TONEPORTAMENTO; break; + case 'H': command = CMD_VIBRATO; break; + case 'I': command = CMD_TREMOR; break; + case 'J': command = CMD_ARPEGGIO; break; + case 'K': command = CMD_VIBRATOVOL; break; + case 'L': command = CMD_TONEPORTAVOL; break; + case 'M': command = CMD_CHANNELVOLUME; break; + case 'N': command = CMD_CHANNELVOLSLIDE; break; + case 'O': command = CMD_OFFSET; break; + case 'P': command = CMD_PANNINGSLIDE; break; + case 'Q': command = CMD_RETRIG; break; + case 'R': command = CMD_TREMOLO; break; + case 'S': command = CMD_S3MCMDEX; break; + case 'T': command = CMD_TEMPO; break; + case 'U': command = CMD_FINEVIBRATO; break; + case 'V': command = CMD_GLOBALVOLUME; break; + case 'W': command = CMD_GLOBALVOLSLIDE; break; + case 'X': command = CMD_PANNING8; break; + case 'Y': command = CMD_PANBRELLO; break; + case 'Z': command = CMD_MIDI; break; + default: command = 0; + } + m->command = command; + m->param = param; +} + + +void CSoundFile::S3MSaveConvert(UINT *pcmd, UINT *pprm, BOOL bIT) const +//--------------------------------------------------------------------- +{ + UINT command = *pcmd; + UINT param = *pprm; + switch(command) + { + case CMD_SPEED: command = 'A'; break; + case CMD_POSITIONJUMP: command = 'B'; break; + case CMD_PATTERNBREAK: command = 'C'; if (!bIT) param = ((param / 10) << 4) + (param % 10); break; + case CMD_VOLUMESLIDE: command = 'D'; break; + case CMD_PORTAMENTODOWN: command = 'E'; if ((param >= 0xE0) && (m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM))) param = 0xDF; break; + case CMD_PORTAMENTOUP: command = 'F'; if ((param >= 0xE0) && (m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM))) param = 0xDF; break; + case CMD_TONEPORTAMENTO: command = 'G'; break; + case CMD_VIBRATO: command = 'H'; break; + case CMD_TREMOR: command = 'I'; break; + case CMD_ARPEGGIO: command = 'J'; break; + case CMD_VIBRATOVOL: command = 'K'; break; + case CMD_TONEPORTAVOL: command = 'L'; break; + case CMD_CHANNELVOLUME: command = 'M'; break; + case CMD_CHANNELVOLSLIDE: command = 'N'; break; + case CMD_OFFSET: command = 'O'; break; + case CMD_PANNINGSLIDE: command = 'P'; break; + case CMD_RETRIG: command = 'Q'; break; + case CMD_TREMOLO: command = 'R'; break; + case CMD_S3MCMDEX: command = 'S'; break; + case CMD_TEMPO: command = 'T'; break; + case CMD_FINEVIBRATO: command = 'U'; break; + case CMD_GLOBALVOLUME: command = 'V'; break; + case CMD_GLOBALVOLSLIDE: command = 'W'; break; + case CMD_PANNING8: + command = 'X'; + if ((bIT) && (m_nType != MOD_TYPE_IT) && (m_nType != MOD_TYPE_XM)) + { + if (param == 0xA4) { command = 'S'; param = 0x91; } else + if (param <= 0x80) { param <<= 1; if (param > 255) param = 255; } else + command = param = 0; + } else + if ((!bIT) && ((m_nType == MOD_TYPE_IT) || (m_nType == MOD_TYPE_XM))) + { + param >>= 1; + } + break; + case CMD_PANBRELLO: command = 'Y'; break; + case CMD_MIDI: command = 'Z'; break; + case CMD_XFINEPORTAUPDOWN: + if (param & 0x0F) switch(param & 0xF0) + { + case 0x10: command = 'F'; param = (param & 0x0F) | 0xE0; break; + case 0x20: command = 'E'; param = (param & 0x0F) | 0xE0; break; + case 0x90: command = 'S'; break; + default: command = param = 0; + } else command = param = 0; + break; + case CMD_MODCMDEX: + command = 'S'; + switch(param & 0xF0) + { + case 0x00: command = param = 0; break; + case 0x10: command = 'F'; param |= 0xF0; break; + case 0x20: command = 'E'; param |= 0xF0; break; + case 0x30: param = (param & 0x0F) | 0x10; break; + case 0x40: param = (param & 0x0F) | 0x30; break; + case 0x50: param = (param & 0x0F) | 0x20; break; + case 0x60: param = (param & 0x0F) | 0xB0; break; + case 0x70: param = (param & 0x0F) | 0x40; break; + case 0x90: command = 'Q'; param &= 0x0F; break; + case 0xA0: if (param & 0x0F) { command = 'D'; param = (param << 4) | 0x0F; } else command=param=0; break; + case 0xB0: if (param & 0x0F) { command = 'D'; param |= 0xF0; } else command=param=0; break; + } + break; + default: command = param = 0; + } + command &= ~0x40; + *pcmd = command; + *pprm = param; +} + + +BOOL CSoundFile::ReadS3M(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + UINT insnum,patnum,nins,npat; + DWORD insfile[128]; + WORD ptr[256]; + BYTE s[1024]; + DWORD dwMemPos; + BYTE insflags[128], inspack[128]; + S3MFILEHEADER psfh = *(S3MFILEHEADER *)lpStream; + + psfh.reserved1 = bswapLE16(psfh.reserved1); + psfh.ordnum = bswapLE16(psfh.ordnum); + psfh.insnum = bswapLE16(psfh.insnum); + psfh.patnum = bswapLE16(psfh.patnum); + psfh.flags = bswapLE16(psfh.flags); + psfh.cwtv = bswapLE16(psfh.cwtv); + psfh.version = bswapLE16(psfh.version); + psfh.scrm = bswapLE32(psfh.scrm); + psfh.special = bswapLE16(psfh.special); + + if ((!lpStream) || (dwMemLength <= sizeof(S3MFILEHEADER)+sizeof(S3MSAMPLESTRUCT)+64)) return FALSE; + if (psfh.scrm != 0x4D524353) return FALSE; + dwMemPos = 0x60; + m_nType = MOD_TYPE_S3M; + memset(m_szNames,0,sizeof(m_szNames)); + memcpy(m_szNames[0], psfh.name, 28); + // Speed + m_nDefaultSpeed = psfh.speed; + if (m_nDefaultSpeed < 1) m_nDefaultSpeed = 6; + if (m_nDefaultSpeed > 0x1F) m_nDefaultSpeed = 0x1F; + // Tempo + m_nDefaultTempo = psfh.tempo; + if (m_nDefaultTempo < 40) m_nDefaultTempo = 40; + if (m_nDefaultTempo > 240) m_nDefaultTempo = 240; + // Global Volume + m_nDefaultGlobalVolume = psfh.globalvol << 2; + if ((!m_nDefaultGlobalVolume) || (m_nDefaultGlobalVolume > 256)) m_nDefaultGlobalVolume = 256; + m_nSongPreAmp = psfh.mastervol & 0x7F; + // Channels + m_nChannels = 4; + for (UINT ich=0; ich<32; ich++) + { + ChnSettings[ich].nPan = 128; + ChnSettings[ich].nVolume = 64; + + ChnSettings[ich].dwFlags = CHN_MUTE; + if (psfh.channels[ich] != 0xFF) + { + m_nChannels = ich+1; + UINT b = psfh.channels[ich] & 0x0F; + ChnSettings[ich].nPan = (b & 8) ? 0xC0 : 0x40; + ChnSettings[ich].dwFlags = 0; + } + } + if (m_nChannels < 4) m_nChannels = 4; + if ((psfh.cwtv < 0x1320) || (psfh.flags & 0x40)) m_dwSongFlags |= SONG_FASTVOLSLIDES; + // Reading pattern order + UINT iord = psfh.ordnum; + if (iord<1) iord = 1; + if (iord > MAX_ORDERS) iord = MAX_ORDERS; + if (iord) + { + memcpy(Order, lpStream+dwMemPos, iord); + dwMemPos += iord; + } + if ((iord & 1) && (lpStream[dwMemPos] == 0xFF)) dwMemPos++; + // Reading file pointers + insnum = nins = psfh.insnum; + if (insnum >= MAX_SAMPLES) insnum = MAX_SAMPLES-1; + m_nSamples = insnum; + patnum = npat = psfh.patnum; + if (patnum > MAX_PATTERNS) patnum = MAX_PATTERNS; + memset(ptr, 0, sizeof(ptr)); + if (nins+npat) + { + memcpy(ptr, lpStream+dwMemPos, 2*(nins+npat)); + dwMemPos += 2*(nins+npat); + for (UINT j = 0; j < (nins+npat); ++j) { + ptr[j] = bswapLE16(ptr[j]); + } + if (psfh.panning_present == 252) + { + const BYTE *chnpan = lpStream+dwMemPos; + for (UINT i=0; i<32; i++) if (chnpan[i] & 0x20) + { + ChnSettings[i].nPan = ((chnpan[i] & 0x0F) << 4) + 8; + } + } + } + if (!m_nChannels) return TRUE; + // Reading instrument headers + memset(insfile, 0, sizeof(insfile)); + for (UINT iSmp=1; iSmp<=insnum; iSmp++) + { + UINT nInd = ((DWORD)ptr[iSmp-1])*16; + if ((!nInd) || (nInd + 0x50 > dwMemLength)) continue; + memcpy(s, lpStream+nInd, 0x50); + memcpy(Ins[iSmp].name, s+1, 12); + insflags[iSmp-1] = s[0x1F]; + inspack[iSmp-1] = s[0x1E]; + s[0x4C] = 0; + lstrcpy(m_szNames[iSmp], (LPCSTR)&s[0x30]); + if ((s[0]==1) && (s[0x4E]=='R') && (s[0x4F]=='S')) + { + UINT j = bswapLE32(*((LPDWORD)(s+0x10))); + if (j > MAX_SAMPLE_LENGTH) j = MAX_SAMPLE_LENGTH; + if (j < 2) j = 0; + Ins[iSmp].nLength = j; + j = bswapLE32(*((LPDWORD)(s+0x14))); + if (j >= Ins[iSmp].nLength) j = Ins[iSmp].nLength - 1; + Ins[iSmp].nLoopStart = j; + j = bswapLE32(*((LPDWORD)(s+0x18))); + if (j > MAX_SAMPLE_LENGTH) j = MAX_SAMPLE_LENGTH; + if (j < 2) j = 0; + if (j > Ins[iSmp].nLength) j = Ins[iSmp].nLength; + Ins[iSmp].nLoopEnd = j; + j = s[0x1C]; + if (j > 64) j = 64; + Ins[iSmp].nVolume = j << 2; + Ins[iSmp].nGlobalVol = 64; + if (s[0x1F]&1) Ins[iSmp].uFlags |= CHN_LOOP; + j = bswapLE32(*((LPDWORD)(s+0x20))); + if (!j) j = 8363; + if (j < 1024) j = 1024; + Ins[iSmp].nC4Speed = j; + insfile[iSmp] = ((DWORD)bswapLE16(*((LPWORD)(s+0x0E)))) << 4; + insfile[iSmp] += ((DWORD)(BYTE)s[0x0D]) << 20; + if (insfile[iSmp] > dwMemLength) insfile[iSmp] &= 0xFFFF; + if ((Ins[iSmp].nLoopStart >= Ins[iSmp].nLoopEnd) || (Ins[iSmp].nLoopEnd - Ins[iSmp].nLoopStart < 8)) + Ins[iSmp].nLoopStart = Ins[iSmp].nLoopEnd = 0; + Ins[iSmp].nPan = 0x80; + } + } + // Reading patterns + for (UINT iPat=0; iPat dwMemLength) continue; + WORD len = bswapLE16(*((WORD *)(lpStream+nInd))); + nInd += 2; + PatternSize[iPat] = 64; + PatternAllocSize[iPat] = 64; + if ((!len) || (nInd + len > dwMemLength - 6) + || ((Patterns[iPat] = AllocatePattern(64, m_nChannels)) == NULL)) continue; + LPBYTE src = (LPBYTE)(lpStream+nInd); + // Unpacking pattern + MODCOMMAND *p = Patterns[iPat]; + UINT row = 0; + UINT j = 0; + while (j < len) + { + BYTE b = src[j++]; + if (!b) + { + if (++row >= 64) break; + } else + { + UINT chn = b & 0x1F; + if (chn < m_nChannels) + { + MODCOMMAND *m = &p[row*m_nChannels+chn]; + if (b & 0x20) + { + m->note = src[j++]; + if (m->note < 0xF0) m->note = (m->note & 0x0F) + 12*(m->note >> 4) + 13; + else if (m->note == 0xFF) m->note = 0; + m->instr = src[j++]; + } + if (b & 0x40) + { + UINT vol = src[j++]; + if ((vol >= 128) && (vol <= 192)) + { + vol -= 128; + m->volcmd = VOLCMD_PANNING; + } else + { + if (vol > 64) vol = 64; + m->volcmd = VOLCMD_VOLUME; + } + m->vol = vol; + } + if (b & 0x80) + { + m->command = src[j++]; + m->param = src[j++]; + if (m->command) S3MConvert(m, FALSE); + } + } else + { + if (b & 0x20) j += 2; + if (b & 0x40) j++; + if (b & 0x80) j += 2; + } + if (j >= len) break; + } + } + } + // Reading samples + for (UINT iRaw=1; iRaw<=insnum; iRaw++) if ((Ins[iRaw].nLength) && (insfile[iRaw])) + { + UINT flags = (psfh.version == 1) ? RS_PCM8S : RS_PCM8U; + if (insflags[iRaw-1] & 4) flags += 5; + if (insflags[iRaw-1] & 2) flags |= RSF_STEREO; + if (inspack[iRaw-1] == 4) flags = RS_ADPCM4; + dwMemPos = insfile[iRaw]; + dwMemPos += ReadSample(&Ins[iRaw], flags, (LPSTR)(lpStream + dwMemPos), dwMemLength - dwMemPos); + } + m_nMinPeriod = 64; + m_nMaxPeriod = 32767; + if (psfh.flags & 0x10) m_dwSongFlags |= SONG_AMIGALIMITS; + return TRUE; +} + + +#ifndef MODPLUG_NO_FILESAVE +#pragma warning(disable:4100) + +static BYTE S3MFiller[16] = +{ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 +}; + + +BOOL CSoundFile::SaveS3M(diskwriter_driver_t *fp, UINT nPacking) +//---------------------------------------------------------- +{ + BYTE header[0x60]; + UINT nbo,nbi,nbp,i; + WORD patptr[128]; + WORD insptr[128]; + BYTE buffer[5*1024]; + S3MSAMPLESTRUCT insex[128]; + + if ((!m_nChannels) || (!fp)) return FALSE; + // Writing S3M header + memset(header, 0, sizeof(header)); + memset(insex, 0, sizeof(insex)); + memcpy(header, m_szNames[0], 0x1C); + header[0x1B] = 0; + header[0x1C] = 0x1A; + header[0x1D] = 0x10; + nbo = (GetNumPatterns()); + if (nbo == 0) + nbo = 2; + else if (nbo & 1) + nbo++; + header[0x20] = nbo & 0xFF; + header[0x21] = nbo >> 8; + nbi = m_nSamples; + if (nbi > 99) nbi = 99; + header[0x22] = nbi & 0xFF; + header[0x23] = nbi >> 8; + nbp = 0; + for (i=0; Patterns[i]; i++) { nbp = i+1; if (nbp >= MAX_PATTERNS) break; } + for (i=0; i= nbp)) nbp = Order[i] + 1; + header[0x24] = nbp & 0xFF; + header[0x25] = nbp >> 8; + if (m_dwSongFlags & SONG_FASTVOLSLIDES) header[0x26] |= 0x40; + if ((m_nMaxPeriod < 20000) || (m_dwSongFlags & SONG_AMIGALIMITS)) header[0x26] |= 0x10; + header[0x28] = 0x20; + header[0x29] = 0x13; + header[0x2A] = 0x02; // Version = 1 => Signed samples + header[0x2B] = 0x00; + header[0x2C] = 'S'; + header[0x2D] = 'C'; + header[0x2E] = 'R'; + header[0x2F] = 'M'; + header[0x30] = m_nDefaultGlobalVolume >> 2; + header[0x31] = m_nDefaultSpeed; + header[0x32] = m_nDefaultTempo; + header[0x33] = ((m_nSongPreAmp < 0x20) ? 0x20 : m_nSongPreAmp) | 0x80; // Stereo + header[0x35] = 0xFC; + for (i=0; i<32; i++) + { + if (i < m_nChannels) + { + UINT tmp = (i & 0x0F) >> 1; + header[0x40+i] = (i & 0x10) | ((i & 1) ? 8+tmp : tmp); + } else header[0x40+i] = 0xFF; + } + fp->o(fp, (const unsigned char *)header, 0x60); + fp->o(fp, (const unsigned char *)Order, nbo); + memset(patptr, 0, sizeof(patptr)); + memset(insptr, 0, sizeof(insptr)); + UINT ofs0 = 0x60 + nbo; + UINT ofs1 = ((0x60 + nbo + nbi*2 + nbp*2 + 15) & 0xFFF0); + UINT ofs = ofs1; + if (header[0x35] == 0xFC) { + ofs += 0x20; + ofs1 += 0x20; + } + + for (i=0; io(fp, (const unsigned char *)insptr, nbi*2); + fp->o(fp, (const unsigned char *)patptr, nbp*2); + if (header[0x35] == 0xFC) + { + BYTE chnpan[32]; + for (i=0; i<32; i++) + { + chnpan[i] = 0x20 | (ChnSettings[i].nPan >> 4); + } + fp->o(fp, (const unsigned char *)chnpan, 0x20); + } + if ((nbi*2+nbp*2) & 0x0F) + { + fp->o(fp, (const unsigned char *)S3MFiller, 0x10 - ((nbi*2+nbp*2) & 0x0F)); + } + fp->l(fp, ofs1); + ofs1 = fp->pos; + fp->o(fp, (const unsigned char *)insex, nbi*0x50); + // Packing patterns + ofs += nbi*0x50; + fp->l(fp,ofs); + for (i=0; inote; + UINT volcmd = m->volcmd; + UINT vol = m->vol; + UINT command = m->command; + UINT param = m->param; + UINT inst = m->instr; + + if (m_dwSongFlags & SONG_INSTRUMENTMODE + && note && inst) { + UINT nn = Headers[inst]->Keyboard[note]; + UINT nm = Headers[inst]->NoteMap[note]; + /* translate on save */ + note = nm; + inst = nn; + } + + + if ((note) || (inst)) b |= 0x20; + if (!note) note = 0xFF; else + if (note >= 0xFE) note = 0xFE; else + if (note < 13) note = 0; else note -= 13; + if (note < 0xFE) note = (note % 12) + ((note / 12) << 4); + if (command == CMD_VOLUME) + { + command = 0; + if (param > 64) param = 64; + volcmd = VOLCMD_VOLUME; + vol = param; + } + if (volcmd == VOLCMD_VOLUME) b |= 0x40; else + if (volcmd == VOLCMD_PANNING) { vol |= 0x80; b |= 0x40; } + if (command) + { + S3MSaveConvert(&command, ¶m, FALSE); + if (command) b |= 0x80; + } + if (b & 0xE0) + { + buffer[len++] = b; + if (b & 0x20) + { + buffer[len++] = note; + buffer[len++] = inst; + } + if (b & 0x40) + { + buffer[len++] = vol; + } + if (b & 0x80) + { + buffer[len++] = command; + buffer[len++] = param; + } + if (len > sizeof(buffer) - 20) break; + } + } + buffer[len++] = 0; + if (len > sizeof(buffer) - 20) break; + } + } + buffer[0] = (len - 2) & 0xFF; + buffer[1] = (len - 2) >> 8; + len = (len+15) & (~0x0F); + + fp->o(fp, (const unsigned char *)buffer, len); + ofs += len; + } + // Writing samples + for (i=1; i<=nbi; i++) + { + MODINSTRUMENT *pins = &Ins[i]; + memcpy(insex[i-1].dosname, pins->name, 12); + memcpy(insex[i-1].name, m_szNames[i], 28); + memcpy(insex[i-1].scrs, "SCRS", 4); + insex[i-1].hmem = (BYTE)((DWORD)ofs >> 20); + insex[i-1].memseg = bswapLE16((WORD)((DWORD)ofs >> 4)); + if (pins->pSample) + { + insex[i-1].type = 1; + insex[i-1].length = bswapLE32(pins->nLength); + insex[i-1].loopbegin = bswapLE32(pins->nLoopStart); + insex[i-1].loopend = bswapLE32(pins->nLoopEnd); + insex[i-1].vol = pins->nVolume / 4; + insex[i-1].flags = (pins->uFlags & CHN_LOOP) ? 1 : 0; + if (pins->nC4Speed) + insex[i-1].finetune = bswapLE32(pins->nC4Speed); + else + insex[i-1].finetune = bswapLE32(TransposeToFrequency(pins->RelativeTone, pins->nFineTune)); + UINT flags = RS_PCM8U; +#ifndef NO_PACKING + if (nPacking) + { + if ((!(pins->uFlags & (CHN_16BIT|CHN_STEREO))) + && (CanPackSample((char *)pins->pSample, pins->nLength, nPacking))) + { + insex[i-1].pack = 4; + flags = RS_ADPCM4; + } + } else +#endif // NO_PACKING + { + if (pins->uFlags & CHN_16BIT) + { + insex[i-1].flags |= 4; + flags = RS_PCM16U; + } + if (pins->uFlags & CHN_STEREO) + { + insex[i-1].flags |= 2; + flags = (pins->uFlags & CHN_16BIT) ? RS_STPCM16U : RS_STPCM8U; + } + } + DWORD len = WriteSample(fp, pins, flags); + if (len & 0x0F) + { + fp->o(fp, (const unsigned char *)S3MFiller, 0x10 - (len & 0x0F)); + } + ofs += (len + 15) & (~0x0F); + } else { + insex[i-1].length = 0; + } + } + // Updating parapointers + fp->l(fp, ofs0); + fp->o(fp, (const unsigned char *)insptr, nbi*2); + fp->o(fp, (const unsigned char *)patptr, nbp*2); + fp->l(fp, ofs1); + fp->o(fp, (const unsigned char *)insex, 0x50*nbi); + return TRUE; +} + +#pragma warning(default:4100) +#endif // MODPLUG_NO_FILESAVE + diff --git a/modplug/load_stm.cpp b/modplug/load_stm.cpp new file mode 100644 index 000000000..4d3d03b69 --- /dev/null +++ b/modplug/load_stm.cpp @@ -0,0 +1,187 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct tagSTMNOTE +{ + BYTE note; + BYTE insvol; + BYTE volcmd; + BYTE cmdinf; +} STMNOTE; + + +// Raw STM sampleinfo struct: +typedef struct tagSTMSAMPLE +{ + CHAR filename[14]; // Can't have long comments - just filename comments :) + WORD reserved; // ISA in memory when in ST 2 + WORD length; // Sample length + WORD loopbeg; // Loop start point + WORD loopend; // Loop end point + BYTE volume; // Volume + BYTE reserved2; // More reserved crap + WORD c2spd; // Good old c2spd + BYTE reserved3[6]; // Yet more of PSi's reserved crap +} STMSAMPLE; + + +// Raw STM header struct: +typedef struct tagSTMHEADER +{ + char songname[20]; // changed from CHAR + char trackername[8]; // !SCREAM! for ST 2.xx // changed from CHAR + CHAR unused; // 0x1A + CHAR filetype; // 1=song, 2=module (only 2 is supported, of course) :) + CHAR ver_major; // Like 2 + CHAR ver_minor; // "ditto" + BYTE inittempo; // initspeed= stm inittempo>>4 + BYTE numpat; // number of patterns + BYTE globalvol; // <- WoW! a RiGHT TRiANGLE =8*) + BYTE reserved[13]; // More of PSi's internal crap + STMSAMPLE sample[31]; // STM sample data + BYTE patorder[128]; // Docs say 64 - actually 128 +} STMHEADER; + +#pragma pack() + + + +BOOL CSoundFile::ReadSTM(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + STMHEADER *phdr = (STMHEADER *)lpStream; + DWORD dwMemPos = 0; + + if ((!lpStream) || (dwMemLength < sizeof(STMHEADER))) return FALSE; + if ((phdr->filetype != 2) || (phdr->unused != 0x1A) + || ((strnicmp(phdr->trackername, "!SCREAM!", 8)) + && (strnicmp(phdr->trackername, "BMOD2STM", 8)))) return FALSE; + memcpy(m_szNames[0], phdr->songname, 20); + // Read STM header + m_nType = MOD_TYPE_STM; + m_nSamples = 31; + m_nChannels = 4; + m_nInstruments = 0; + m_nMinPeriod = 64; + m_nMaxPeriod = 0x7FFF; + m_nDefaultSpeed = phdr->inittempo >> 4; + if (m_nDefaultSpeed < 1) m_nDefaultSpeed = 1; + m_nDefaultTempo = 125; + m_nDefaultGlobalVolume = phdr->globalvol << 2; + if (m_nDefaultGlobalVolume > 256) m_nDefaultGlobalVolume = 256; + memcpy(Order, phdr->patorder, 128); + // Setting up channels + for (UINT nSet=0; nSet<4; nSet++) + { + ChnSettings[nSet].dwFlags = 0; + ChnSettings[nSet].nVolume = 64; + ChnSettings[nSet].nPan = (nSet & 1) ? 0x40 : 0xC0; + } + // Reading samples + for (UINT nIns=0; nIns<31; nIns++) + { + MODINSTRUMENT *pIns = &Ins[nIns+1]; + STMSAMPLE *pStm = &phdr->sample[nIns]; // STM sample data + memcpy(pIns->name, pStm->filename, 13); + memcpy(m_szNames[nIns+1], pStm->filename, 12); + pIns->nC4Speed = pStm->c2spd; + pIns->nGlobalVol = 64; + pIns->nVolume = pStm->volume << 2; + if (pIns->nVolume > 256) pIns->nVolume = 256; + pIns->nLength = pStm->length; + if ((pIns->nLength < 2) || (!pIns->nVolume)) pIns->nLength = 0; + pIns->nLoopStart = pStm->loopbeg; + pIns->nLoopEnd = pStm->loopend; + if ((pIns->nLoopEnd > pIns->nLoopStart) && (pIns->nLoopEnd != 0xFFFF)) pIns->uFlags |= CHN_LOOP; + } + dwMemPos = sizeof(STMHEADER); + for (UINT nOrd=0; nOrd= 99) Order[nOrd] = 0xFF; + UINT nPatterns = phdr->numpat; + for (UINT nPat=0; nPat dwMemLength) return TRUE; + PatternSize[nPat] = 64; + PatternAllocSize[nPat] = 64; + if ((Patterns[nPat] = AllocatePattern(64, m_nChannels)) == NULL) return TRUE; + MODCOMMAND *m = Patterns[nPat]; + STMNOTE *p = (STMNOTE *)(lpStream + dwMemPos); + for (UINT n=0; n<64*4; n++, p++, m++) + { + UINT note,ins,vol,cmd; + // extract the various information from the 4 bytes that + // make up a single note + note = p->note; + ins = p->insvol >> 3; + vol = (p->insvol & 0x07) + (p->volcmd >> 1); + cmd = p->volcmd & 0x0F; + if ((ins) && (ins < 32)) m->instr = ins; + // special values of [SBYTE0] are handled here -> + // we have no idea if these strange values will ever be encountered + // but it appears as though stms sound correct. + if ((note == 0xFE) || (note == 0xFC)) m->note = 0xFE; else + // if note < 251, then all three bytes are stored in the file + if (note < 0xFC) m->note = (note >> 4)*12 + (note&0xf) + 37; + if (vol <= 64) { m->volcmd = VOLCMD_VOLUME; m->vol = vol; } + m->param = p->cmdinf; + switch(cmd) + { + // Axx set speed to xx + case 1: m->command = CMD_SPEED; m->param >>= 4; break; + // Bxx position jump + case 2: m->command = CMD_POSITIONJUMP; break; + // Cxx patternbreak to row xx + case 3: m->command = CMD_PATTERNBREAK; m->param = (m->param & 0xF0) * 10 + (m->param & 0x0F); break; + // Dxy volumeslide + case 4: m->command = CMD_VOLUMESLIDE; break; + // Exy toneslide down + case 5: m->command = CMD_PORTAMENTODOWN; break; + // Fxy toneslide up + case 6: m->command = CMD_PORTAMENTOUP; break; + // Gxx Tone portamento,speed xx + case 7: m->command = CMD_TONEPORTAMENTO; break; + // Hxy vibrato + case 8: m->command = CMD_VIBRATO; break; + // Ixy tremor, ontime x, offtime y + case 9: m->command = CMD_TREMOR; break; + // Jxy arpeggio + case 10: m->command = CMD_ARPEGGIO; break; + // Kxy Dual command H00 & Dxy + case 11: m->command = CMD_VIBRATOVOL; break; + // Lxy Dual command G00 & Dxy + case 12: m->command = CMD_TONEPORTAVOL; break; + // Xxx amiga command 8xx + case 0x18: m->command = CMD_PANNING8; break; + default: + m->command = m->param = 0; + } + } + dwMemPos += 64*4*4; + } + // Reading Samples + for (UINT nSmp=1; nSmp<=31; nSmp++) + { + MODINSTRUMENT *pIns = &Ins[nSmp]; + dwMemPos = (dwMemPos + 15) & (~15); + if (pIns->nLength) + { + UINT nPos = ((UINT)phdr->sample[nSmp-1].reserved) << 4; + if ((nPos >= sizeof(STMHEADER)) && (nPos+pIns->nLength <= dwMemLength)) dwMemPos = nPos; + if (dwMemPos < dwMemLength) + { + dwMemPos += ReadSample(pIns, RS_PCM8S, (LPSTR)(lpStream+dwMemPos),dwMemLength-dwMemPos); + } + } + } + return TRUE; +} + diff --git a/modplug/load_ult.cpp b/modplug/load_ult.cpp new file mode 100644 index 000000000..9025c1978 --- /dev/null +++ b/modplug/load_ult.cpp @@ -0,0 +1,223 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#define ULT_16BIT 0x04 +#define ULT_LOOP 0x08 +#define ULT_BIDI 0x10 + +#pragma pack(1) + +// Raw ULT header struct: +typedef struct tagULTHEADER +{ + char id[15]; // changed from CHAR + char songtitle[32]; // changed from CHAR + BYTE reserved; +} ULTHEADER; + + +// Raw ULT sampleinfo struct: +typedef struct tagULTSAMPLE +{ + CHAR samplename[32]; + CHAR dosname[12]; + LONG loopstart; + LONG loopend; + LONG sizestart; + LONG sizeend; + BYTE volume; + BYTE flags; + WORD finetune; +} ULTSAMPLE; + +#pragma pack() + + +BOOL CSoundFile::ReadUlt(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + ULTHEADER *pmh = (ULTHEADER *)lpStream; + ULTSAMPLE *pus; + UINT nos, nop; + DWORD dwMemPos = 0; + + // try to read module header + if ((!lpStream) || (dwMemLength < 0x100)) return FALSE; + if (strncmp(pmh->id,"MAS_UTrack_V00",14)) return FALSE; + // Warning! Not supported ULT format, trying anyway + // if ((pmh->id[14] < '1') || (pmh->id[14] > '4')) return FALSE; + m_nType = MOD_TYPE_ULT; + m_nDefaultSpeed = 6; + m_nDefaultTempo = 125; + memcpy(m_szNames[0], pmh->songtitle, 32); + // read songtext + dwMemPos = sizeof(ULTHEADER); + if ((pmh->reserved) && (dwMemPos + pmh->reserved * 32 < dwMemLength)) + { + UINT len = pmh->reserved * 32; + m_lpszSongComments = new char[len + 1 + pmh->reserved]; + if (m_lpszSongComments) + { + for (UINT l=0; lreserved; l++) + { + memcpy(m_lpszSongComments+l*33, lpStream+dwMemPos+l*32, 32); + m_lpszSongComments[l*33+32] = 0x0D; + } + m_lpszSongComments[len] = 0; + } + dwMemPos += len; + } + if (dwMemPos >= dwMemLength) return TRUE; + nos = lpStream[dwMemPos++]; + m_nSamples = nos; + if (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1; + UINT smpsize = 64; + if (pmh->id[14] >= '4') smpsize += 2; + if (dwMemPos + nos*smpsize + 256 + 2 > dwMemLength) return TRUE; + for (UINT ins=1; ins<=nos; ins++, dwMemPos+=smpsize) if (ins<=m_nSamples) + { + pus = (ULTSAMPLE *)(lpStream+dwMemPos); + MODINSTRUMENT *pins = &Ins[ins]; + memcpy(m_szNames[ins], pus->samplename, 32); + memcpy(pins->name, pus->dosname, 12); + pins->nLoopStart = pus->loopstart; + pins->nLoopEnd = pus->loopend; + pins->nLength = pus->sizeend - pus->sizestart; + pins->nVolume = pus->volume; + pins->nGlobalVol = 64; + pins->nC4Speed = 8363; + if (pmh->id[14] >= '4') + { + pins->nC4Speed = pus->finetune; + } + if (pus->flags & ULT_LOOP) pins->uFlags |= CHN_LOOP; + if (pus->flags & ULT_BIDI) pins->uFlags |= CHN_PINGPONGLOOP; + if (pus->flags & ULT_16BIT) + { + pins->uFlags |= CHN_16BIT; + pins->nLoopStart >>= 1; + pins->nLoopEnd >>= 1; + } + } + memcpy(Order, lpStream+dwMemPos, 256); + dwMemPos += 256; + m_nChannels = lpStream[dwMemPos] + 1; + nop = lpStream[dwMemPos+1] + 1; + dwMemPos += 2; + if (m_nChannels > 32) m_nChannels = 32; + // Default channel settings + for (UINT nSet=0; nSetid[14]>='3') + { + if (dwMemPos + m_nChannels > dwMemLength) return TRUE; + for(UINT t=0; t 256) ChnSettings[t].nPan = 256; + } + } + // Allocating Patterns + for (UINT nAllocPat=0; nAllocPat dwMemLength) return TRUE; + UINT rep = 1; + UINT note = lpStream[dwMemPos++]; + if (note == 0xFC) + { + rep = lpStream[dwMemPos]; + note = lpStream[dwMemPos+1]; + dwMemPos += 2; + } + UINT instr = lpStream[dwMemPos++]; + UINT eff = lpStream[dwMemPos++]; + UINT dat1 = lpStream[dwMemPos++]; + UINT dat2 = lpStream[dwMemPos++]; + UINT cmd1 = eff & 0x0F; + UINT cmd2 = eff >> 4; + if (cmd1 == 0x0C) dat1 >>= 2; else + if (cmd1 == 0x0B) { cmd1 = dat1 = 0; } + if (cmd2 == 0x0C) dat2 >>= 2; else + if (cmd2 == 0x0B) { cmd2 = dat2 = 0; } + while ((rep != 0) && (row < 64)) + { + if (pat) + { + pat->instr = instr; + if (note) pat->note = note + 36; + if (cmd1 | dat1) + { + if (cmd1 == 0x0C) + { + pat->volcmd = VOLCMD_VOLUME; + pat->vol = dat1; + } else + { + pat->command = cmd1; + pat->param = dat1; + ConvertModCommand(pat); + } + } + if (cmd2 == 0x0C) + { + pat->volcmd = VOLCMD_VOLUME; + pat->vol = dat2; + } else + if ((cmd2 | dat2) && (!pat->command)) + { + pat->command = cmd2; + pat->param = dat2; + ConvertModCommand(pat); + } + pat += m_nChannels; + } + row++; + rep--; + } + } + } + } + // Reading Instruments + for (UINT smp=1; smp<=m_nSamples; smp++) if (Ins[smp].nLength) + { + if (dwMemPos >= dwMemLength) return TRUE; + UINT flags = (Ins[smp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S; + dwMemPos += ReadSample(&Ins[smp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength - dwMemPos); + } + return TRUE; +} + diff --git a/modplug/load_umx.cpp b/modplug/load_umx.cpp new file mode 100644 index 000000000..8913c740f --- /dev/null +++ b/modplug/load_umx.cpp @@ -0,0 +1,53 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#define MODMAGIC_OFFSET (20+31*30+130) + + +BOOL CSoundFile::ReadUMX(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + if ((!lpStream) || (dwMemLength < 0x800)) return FALSE; + // Rip Mods from UMX + if ((bswapLE32(*((DWORD *)(lpStream+0x20))) < dwMemLength) + && (bswapLE32(*((DWORD *)(lpStream+0x18))) <= dwMemLength - 0x10) + && (bswapLE32(*((DWORD *)(lpStream+0x18))) >= dwMemLength - 0x200)) + { + for (UINT uscan=0x40; uscan<0x500; uscan++) + { + DWORD dwScan = bswapLE32(*((DWORD *)(lpStream+uscan))); + // IT + if (dwScan == 0x4D504D49) + { + DWORD dwRipOfs = uscan; + return ReadIT(lpStream + dwRipOfs, dwMemLength - dwRipOfs); + } + // S3M + if (dwScan == 0x4D524353) + { + DWORD dwRipOfs = uscan - 44; + return ReadS3M(lpStream + dwRipOfs, dwMemLength - dwRipOfs); + } + // XM + if (!strnicmp((LPCSTR)(lpStream+uscan), "Extended Module", 15)) + { + DWORD dwRipOfs = uscan; + return ReadXM(lpStream + dwRipOfs, dwMemLength - dwRipOfs); + } + // MOD + if ((uscan > MODMAGIC_OFFSET) && (dwScan == 0x2e4b2e4d)) + { + DWORD dwRipOfs = uscan - MODMAGIC_OFFSET; + return ReadMod(lpStream+dwRipOfs, dwMemLength-dwRipOfs); + } + } + } + return FALSE; +} + diff --git a/modplug/load_wav.cpp b/modplug/load_wav.cpp new file mode 100644 index 000000000..f5ee84a3c --- /dev/null +++ b/modplug/load_wav.cpp @@ -0,0 +1,248 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#ifndef WAVE_FORMAT_EXTENSIBLE +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif + +///////////////////////////////////////////////////////////// +// WAV file support + +BOOL CSoundFile::ReadWav(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + DWORD dwMemPos = 0; + WAVEFILEHEADER phdr; + WAVEFORMATHEADER pfmt; + + if ((!lpStream) + || (dwMemLength < (DWORD)(sizeof(WAVEFORMATHEADER)+sizeof(WAVEFILEHEADER)))) + return FALSE; + + memcpy(&phdr, lpStream, sizeof(phdr)); + memcpy(&pfmt, lpStream+sizeof(phdr), sizeof(pfmt)); + + phdr.id_RIFF = bswapLE32(phdr.id_RIFF); + phdr.filesize = bswapLE32(phdr.filesize); + phdr.id_WAVE = bswapLE32(phdr.id_WAVE); + + pfmt.id_fmt = bswapLE32(pfmt.id_fmt); + pfmt.hdrlen = bswapLE32(pfmt.hdrlen); + pfmt.format = bswapLE16(pfmt.format); + pfmt.channels = bswapLE16(pfmt.channels); + pfmt.freqHz = bswapLE32(pfmt.freqHz); + pfmt.bytessec = bswapLE32(pfmt.bytessec); + pfmt.samplesize = bswapLE16(pfmt.samplesize); + pfmt.bitspersample = bswapLE16(pfmt.bitspersample); + + if ((phdr.id_RIFF != IFFID_RIFF) || (phdr.id_WAVE != IFFID_WAVE) + || (pfmt.id_fmt != IFFID_fmt)) return FALSE; + + dwMemPos = sizeof(WAVEFILEHEADER) + 8 + pfmt.hdrlen; + + if ((dwMemPos + 8 >= dwMemLength) + || ((pfmt.format != WAVE_FORMAT_PCM) && (pfmt.format != WAVE_FORMAT_EXTENSIBLE)) + || (pfmt.channels > 4) + || (!pfmt.channels) + || (!pfmt.freqHz) + || (pfmt.bitspersample & 7) + || (pfmt.bitspersample < 8) + || (pfmt.bitspersample > 32)) return FALSE; + + WAVEDATAHEADER pdata; + + for (;;) + { + memcpy(&pdata, lpStream+dwMemPos, sizeof(pdata)); + pdata.id_data = bswapLE32(pdata.id_data); + pdata.length = bswapLE32(pdata.length); + + if (pdata.id_data == IFFID_data) break; + dwMemPos += pdata.length + 8; + if (dwMemPos + 8 >= dwMemLength) return FALSE; + } + m_nType = MOD_TYPE_WAV; + m_nSamples = 0; + m_nInstruments = 0; + m_nChannels = 4; + m_nDefaultSpeed = 8; + m_nDefaultTempo = 125; + m_dwSongFlags |= SONG_LINEARSLIDES; // For no resampling + Order[0] = 0; + Order[1] = 0xFF; + PatternSize[0] = PatternSize[1] = 64; + PatternAllocSize[0] = PatternAllocSize[1] = 64; + if ((Patterns[0] = AllocatePattern(64, 4)) == NULL) return TRUE; + if ((Patterns[1] = AllocatePattern(64, 4)) == NULL) return TRUE; + UINT samplesize = (pfmt.channels * pfmt.bitspersample) >> 3; + UINT len = pdata.length, bytelen; + if (dwMemPos + len > dwMemLength - 8) len = dwMemLength - dwMemPos - 8; + len /= samplesize; + bytelen = len; + if (pfmt.bitspersample >= 16) bytelen *= 2; + if (len > MAX_SAMPLE_LENGTH) len = MAX_SAMPLE_LENGTH; + if (!len) return TRUE; + // Setting up module length + DWORD dwTime = ((len * 50) / pfmt.freqHz) + 1; + DWORD framesperrow = (dwTime + 63) / 63; + if (framesperrow < 4) framesperrow = 4; + UINT norders = 1; + while (framesperrow >= 0x20) + { + Order[norders++] = 1; + Order[norders] = 0xFF; + framesperrow = (dwTime + (64 * norders - 1)) / (64 * norders); + if (norders >= MAX_ORDERS-1) break; + } + m_nDefaultSpeed = framesperrow; + for (UINT iChn=0; iChn<4; iChn++) + { + ChnSettings[iChn].nPan = (iChn & 1) ? 256 : 0; + ChnSettings[iChn].nVolume = 64; + ChnSettings[iChn].dwFlags = 0; + } + // Setting up speed command + MODCOMMAND *pcmd = Patterns[0]; + pcmd[0].command = CMD_SPEED; + pcmd[0].param = (BYTE)m_nDefaultSpeed; + pcmd[0].note = 5*12+1; + pcmd[0].instr = 1; + pcmd[1].note = pcmd[0].note; + pcmd[1].instr = pcmd[0].instr; + m_nSamples = pfmt.channels; + // Support for Multichannel Wave + for (UINT nChn=0; nChnnLength = len; + pins->nC4Speed = pfmt.freqHz; + pins->nVolume = 256; + pins->nPan = 128; + pins->nGlobalVol = 64; + pins->uFlags = (WORD)((pfmt.bitspersample >= 16) ? CHN_16BIT : 0); + pins->uFlags |= CHN_PANNING; + if (m_nSamples > 1) + { + switch(nChn) + { + case 0: pins->nPan = 0; break; + case 1: pins->nPan = 256; break; + case 2: pins->nPan = (WORD)((m_nSamples == 3) ? 128 : 64); pcmd[nChn].command = CMD_S3MCMDEX; pcmd[nChn].param = 0x91; break; + case 3: pins->nPan = 192; pcmd[nChn].command = CMD_S3MCMDEX; pcmd[nChn].param = 0x91; break; + default: pins->nPan = 128; break; + } + } + if ((pins->pSample = AllocateSample(bytelen+8)) == NULL) return TRUE; + if (pfmt.bitspersample >= 16) + { + int slsize = pfmt.bitspersample >> 3; + signed short *p = (signed short *)pins->pSample; + signed char *psrc = (signed char *)(lpStream+dwMemPos+8+nChn*slsize+slsize-2); + for (UINT i=0; ipSample; + signed char *psrc = (signed char *)(lpStream+dwMemPos+8+nChn); + for (UINT i=0; i dwBytes)) return FALSE; + nPos = 0; + while ((nPos < nLen) && (dwBytes > 4)) + { + int nIndex; + value = bswapLE16(*((short int *)psrc)); + nIndex = bswapLE16((short int)psrc[2]); + psrc += 4; + dwBytes -= 4; + pdest[nPos++] = (short int)value; + for (UINT i=0; ((i<(pkBlkAlign-4)*2) && (nPos < nLen) && (dwBytes)); i++) + { + BYTE delta; + if (i & 1) + { + delta = (BYTE)(((*(psrc++)) >> 4) & 0x0F); + dwBytes--; + } else + { + delta = (BYTE)((*psrc) & 0x0F); + } + int v = gIMAUnpackTable[nIndex % 90] >> 3; + if (delta & 1) v += gIMAUnpackTable[nIndex] >> 2; + if (delta & 2) v += gIMAUnpackTable[nIndex] >> 1; + if (delta & 4) v += gIMAUnpackTable[nIndex]; + if (delta & 8) value -= v; else value += v; + nIndex += gIMAIndexTab[delta & 7]; + if (nIndex < 0) nIndex = 0; else + if (nIndex > 88) nIndex = 88; + if (value > 32767) value = 32767; else + if (value < -32768) value = -32768; + pdest[nPos++] = (short int)value; + } + } + return TRUE; +} + + + diff --git a/modplug/load_xm.cpp b/modplug/load_xm.cpp new file mode 100644 index 000000000..7a8dc6230 --- /dev/null +++ b/modplug/load_xm.cpp @@ -0,0 +1,924 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//////////////////////////////////////////////////////// +// FastTracker II XM file support + +#ifdef MSC_VER +#pragma warning(disable:4244) +#endif + +#pragma pack(1) +typedef struct tagXMFILEHEADER +{ + DWORD size; + WORD norder; + WORD restartpos; + WORD channels; + WORD patterns; + WORD instruments; + WORD flags; + WORD speed; + WORD tempo; + BYTE order[256]; +} XMFILEHEADER; + + +typedef struct tagXMINSTRUMENTHEADER +{ + DWORD size; + CHAR name[22]; + BYTE type; + BYTE samples; + BYTE samplesh; +} XMINSTRUMENTHEADER; + + +typedef struct tagXMSAMPLEHEADER +{ + DWORD shsize; + BYTE snum[96]; + WORD venv[24]; + WORD penv[24]; + BYTE vnum, pnum; + BYTE vsustain, vloops, vloope, psustain, ploops, ploope; + BYTE vtype, ptype; + BYTE vibtype, vibsweep, vibdepth, vibrate; + WORD volfade; + WORD res; + BYTE reserved1[20]; +} XMSAMPLEHEADER; + +typedef struct tagXMSAMPLESTRUCT +{ + DWORD samplen; + DWORD loopstart; + DWORD looplen; + BYTE vol; + signed char finetune; + BYTE type; + BYTE pan; + signed char relnote; + BYTE res; + char name[22]; +} XMSAMPLESTRUCT; +#pragma pack() + + +BOOL CSoundFile::ReadXM(const BYTE *lpStream, DWORD dwMemLength) +//-------------------------------------------------------------- +{ + XMSAMPLEHEADER xmsh; + XMSAMPLESTRUCT xmss; + DWORD dwMemPos, dwHdrSize; + WORD norders=0, restartpos=0, channels=0, patterns=0, instruments=0; + WORD xmflags=0, deftempo=125, defspeed=6; + BOOL InstUsed[256]; + BYTE channels_used[MAX_CHANNELS]; + BYTE pattern_map[256]; + BOOL samples_used[MAX_SAMPLES]; + UINT unused_samples; + + m_nChannels = 0; + if ((!lpStream) || (dwMemLength < 0x200)) return FALSE; + if (strnicmp((LPCSTR)lpStream, "Extended Module", 15)) return FALSE; + + memcpy(m_szNames[0], lpStream+17, 20); + dwHdrSize = bswapLE32(*((DWORD *)(lpStream+60))); + norders = bswapLE16(*((WORD *)(lpStream+64))); + if ((!norders) || (norders > MAX_ORDERS)) return FALSE; + restartpos = bswapLE16(*((WORD *)(lpStream+66))); + channels = bswapLE16(*((WORD *)(lpStream+68))); + if ((!channels) || (channels > 64)) return FALSE; + m_nType = MOD_TYPE_XM; + m_nMinPeriod = 27; + m_nMaxPeriod = 54784; + m_nChannels = channels; + if (restartpos < norders) m_nRestartPos = restartpos; + patterns = bswapLE16(*((WORD *)(lpStream+70))); + if (patterns > 256) patterns = 256; + instruments = bswapLE16(*((WORD *)(lpStream+72))); + if (instruments >= MAX_INSTRUMENTS) instruments = MAX_INSTRUMENTS-1; + m_nInstruments = instruments; + m_dwSongFlags |= SONG_INSTRUMENTMODE; + m_nSamples = 0; + memcpy(&xmflags, lpStream+74, 2); + xmflags = bswapLE16(xmflags); + if (xmflags & 1) m_dwSongFlags |= SONG_LINEARSLIDES; + if (xmflags & 0x1000) m_dwSongFlags |= SONG_EXFILTERRANGE; + defspeed = bswapLE16(*((WORD *)(lpStream+76))); + deftempo = bswapLE16(*((WORD *)(lpStream+78))); + if ((deftempo >= 32) && (deftempo < 256)) m_nDefaultTempo = deftempo; + if ((defspeed > 0) && (defspeed < 40)) m_nDefaultSpeed = defspeed; + memcpy(Order, lpStream+80, norders); + memset(InstUsed, 0, sizeof(InstUsed)); + if (patterns > MAX_PATTERNS) + { + UINT i, j; + for (i=0; i= dwMemLength) return TRUE; + // Reading patterns + memset(channels_used, 0, sizeof(channels_used)); + for (UINT ipat=0; ipat= dwMemLength) || (dwSize & 0xFFFFFF00)) + { + if (dwMemPos + 4 >= dwMemLength) break; + dwMemPos++; + dwSize = bswapLE32(*((DWORD *)(lpStream+dwMemPos))); + } + rows = bswapLE16(*((WORD *)(lpStream+dwMemPos+5))); + if ((!rows) || (rows > 256)) rows = 64; + packsize = bswapLE16(*((WORD *)(lpStream+dwMemPos+7))); + if (dwMemPos + dwSize + 4 > dwMemLength) return TRUE; + dwMemPos += dwSize; + if (dwMemPos + packsize + 4 > dwMemLength) return TRUE; + MODCOMMAND *p; + if (ipatmap < MAX_PATTERNS) + { + PatternSize[ipatmap] = rows; + PatternAllocSize[ipatmap] = rows; + if ((Patterns[ipatmap] = AllocatePattern(rows, m_nChannels)) == NULL) return TRUE; + if (!packsize) continue; + p = Patterns[ipatmap]; + } else p = NULL; + const BYTE *src = lpStream+dwMemPos; + UINT j=0; + for (UINT row=0; rownote = src[j++]; + if (b & 2) p->instr = src[j++]; + if (b & 4) vol = src[j++]; + if (b & 8) p->command = src[j++]; + if (b & 16) p->param = src[j++]; + } else + { + p->note = b; + p->instr = src[j++]; + vol = src[j++]; + p->command = src[j++]; + p->param = src[j++]; + } + if (p->note == 97) p->note = 0xFF; else + if ((p->note) && (p->note < 97)) p->note += 12; + if (p->note) channels_used[chn] = 1; + if (p->command | p->param) ConvertModCommand(p); + if (p->instr == 0xff) p->instr = 0; + if (p->instr) InstUsed[p->instr] = TRUE; + if ((vol >= 0x10) && (vol <= 0x50)) + { + p->volcmd = VOLCMD_VOLUME; + p->vol = vol - 0x10; + } else + if (vol >= 0x60) + { + UINT v = vol & 0xF0; + vol &= 0x0F; + p->vol = vol; + switch(v) + { + // 60-6F: Volume Slide Down + case 0x60: p->volcmd = VOLCMD_VOLSLIDEDOWN; break; + // 70-7F: Volume Slide Up: + case 0x70: p->volcmd = VOLCMD_VOLSLIDEUP; break; + // 80-8F: Fine Volume Slide Down + case 0x80: p->volcmd = VOLCMD_FINEVOLDOWN; break; + // 90-9F: Fine Volume Slide Up + case 0x90: p->volcmd = VOLCMD_FINEVOLUP; break; + // A0-AF: Set Vibrato Speed + case 0xA0: p->volcmd = VOLCMD_VIBRATOSPEED; break; + // B0-BF: Vibrato + case 0xB0: p->volcmd = VOLCMD_VIBRATO; break; + // C0-CF: Set Panning + case 0xC0: p->volcmd = VOLCMD_PANNING; p->vol = (vol << 2) + 2; break; + // D0-DF: Panning Slide Left + case 0xD0: p->volcmd = VOLCMD_PANSLIDELEFT; break; + // E0-EF: Panning Slide Right + case 0xE0: p->volcmd = VOLCMD_PANSLIDERIGHT; break; + // F0-FF: Tone Portamento + case 0xF0: p->volcmd = VOLCMD_TONEPORTAMENTO; break; + } + } + p++; + } else + if (j < packsize) + { + BYTE b = src[j++]; + if (b & 0x80) + { + if (b & 1) j++; + if (b & 2) j++; + if (b & 4) j++; + if (b & 8) j++; + if (b & 16) j++; + } else j += 4; + } else break; + } + } + dwMemPos += packsize; + } + // Wrong offset check + while (dwMemPos + 4 < dwMemLength) + { + DWORD d = bswapLE32(*((DWORD *)(lpStream+dwMemPos))); + if (d < 0x300) break; + dwMemPos++; + } + memset(samples_used, 0, sizeof(samples_used)); + unused_samples = 0; + // Reading instruments + for (UINT iIns=1; iIns<=instruments; iIns++) + { + XMINSTRUMENTHEADER *pih; + BYTE flags[32]; + DWORD samplesize[32]; + UINT samplemap[32]; + WORD nsamples; + + if (dwMemPos + sizeof(XMINSTRUMENTHEADER) >= dwMemLength) return TRUE; + pih = (XMINSTRUMENTHEADER *)(lpStream+dwMemPos); + if (dwMemPos + bswapLE32(pih->size) > dwMemLength) return TRUE; + if ((Headers[iIns] = new INSTRUMENTHEADER) == NULL) continue; + memset(Headers[iIns], 0, sizeof(INSTRUMENTHEADER)); + memcpy(Headers[iIns]->name, pih->name, 22); + if ((nsamples = pih->samples) > 0) + { + if (dwMemPos + sizeof(XMSAMPLEHEADER) > dwMemLength) return TRUE; + memcpy(&xmsh, lpStream+dwMemPos+sizeof(XMINSTRUMENTHEADER), sizeof(XMSAMPLEHEADER)); + xmsh.shsize = bswapLE32(xmsh.shsize); + for (int i = 0; i < 24; ++i) { + xmsh.venv[i] = bswapLE16(xmsh.venv[i]); + xmsh.penv[i] = bswapLE16(xmsh.penv[i]); + } + xmsh.volfade = bswapLE16(xmsh.volfade); + xmsh.res = bswapLE16(xmsh.res); + dwMemPos += bswapLE32(pih->size); + } else + { + if (bswapLE32(pih->size)) dwMemPos += bswapLE32(pih->size); + else dwMemPos += sizeof(XMINSTRUMENTHEADER); + continue; + } + memset(samplemap, 0, sizeof(samplemap)); + if (nsamples > 32) return TRUE; + UINT newsamples = m_nSamples; + for (UINT nmap=0; nmap= MAX_SAMPLES) + { + n = m_nSamples; + while (n > 0) + { + if (!Ins[n].pSample) + { + for (UINT xmapchk=0; xmapchk < nmap; xmapchk++) + { + if (samplemap[xmapchk] == n) goto alreadymapped; + } + for (UINT clrs=1; clrsKeyboard[ks] == n) pks->Keyboard[ks] = 0; + } + } + break; + } + alreadymapped: + n--; + } +#ifndef MODPLUG_FASTSOUNDLIB + // Damn! more than 200 samples: look for duplicates + if (!n) + { + if (!unused_samples) + { + unused_samples = DetectUnusedSamples(samples_used); + if (!unused_samples) unused_samples = 0xFFFF; + } + if ((unused_samples) && (unused_samples != 0xFFFF)) + { + for (UINT iext=m_nSamples; iext>=1; iext--) if (!samples_used[iext]) + { + unused_samples--; + samples_used[iext] = TRUE; + DestroySample(iext); + n = iext; + for (UINT mapchk=0; mapchkKeyboard[ks] == n) pks->Keyboard[ks] = 0; + } + } + memset(&Ins[n], 0, sizeof(Ins[0])); + break; + } + } + } +#endif // MODPLUG_FASTSOUNDLIB + } + if (newsamples < n) newsamples = n; + samplemap[nmap] = n; + } + m_nSamples = newsamples; + // Reading Volume Envelope + INSTRUMENTHEADER *penv = Headers[iIns]; + penv->nMidiProgram = pih->type; + penv->nFadeOut = xmsh.volfade; + penv->nPan = 128; + penv->nPPC = 5*12; + if (xmsh.vtype & 1) penv->dwFlags |= ENV_VOLUME; + if (xmsh.vtype & 2) penv->dwFlags |= ENV_VOLSUSTAIN; + if (xmsh.vtype & 4) penv->dwFlags |= ENV_VOLLOOP; + if (xmsh.ptype & 1) penv->dwFlags |= ENV_PANNING; + if (xmsh.ptype & 2) penv->dwFlags |= ENV_PANSUSTAIN; + if (xmsh.ptype & 4) penv->dwFlags |= ENV_PANLOOP; + if (xmsh.vnum > 12) xmsh.vnum = 12; + if (xmsh.pnum > 12) xmsh.pnum = 12; + penv->VolEnv.nNodes = xmsh.vnum; + if (!xmsh.vnum) penv->dwFlags &= ~ENV_VOLUME; + if (!xmsh.pnum) penv->dwFlags &= ~ENV_PANNING; + penv->PanEnv.nNodes = xmsh.pnum; + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = xmsh.vsustain; + if (xmsh.vsustain >= 12) penv->dwFlags &= ~ENV_VOLSUSTAIN; + penv->VolEnv.nLoopStart = xmsh.vloops; + penv->VolEnv.nLoopEnd = xmsh.vloope; + if (penv->VolEnv.nLoopEnd >= 12) penv->VolEnv.nLoopEnd = 0; + if (penv->VolEnv.nLoopStart >= penv->VolEnv.nLoopEnd) penv->dwFlags &= ~ENV_VOLLOOP; + penv->PanEnv.nSustainStart = penv->PanEnv.nSustainEnd = xmsh.psustain; + if (xmsh.psustain >= 12) penv->dwFlags &= ~ENV_PANSUSTAIN; + penv->PanEnv.nLoopStart = xmsh.ploops; + penv->PanEnv.nLoopEnd = xmsh.ploope; + if (penv->PanEnv.nLoopEnd >= 12) penv->PanEnv.nLoopEnd = 0; + if (penv->PanEnv.nLoopStart >= penv->PanEnv.nLoopEnd) penv->dwFlags &= ~ENV_PANLOOP; + penv->nGlobalVol = 64; + for (UINT ienv=0; ienv<12; ienv++) + { + penv->VolEnv.Ticks[ienv] = (WORD)xmsh.venv[ienv*2]; + penv->VolEnv.Values[ienv] = (BYTE)xmsh.venv[ienv*2+1]; + penv->PanEnv.Ticks[ienv] = (WORD)xmsh.penv[ienv*2]; + penv->PanEnv.Values[ienv] = (BYTE)xmsh.penv[ienv*2+1]; + if (ienv) + { + if (penv->VolEnv.Ticks[ienv] < penv->VolEnv.Ticks[ienv-1]) + { + penv->VolEnv.Ticks[ienv] &= 0xFF; + penv->VolEnv.Ticks[ienv] += penv->VolEnv.Ticks[ienv-1] & 0xFF00; + if (penv->VolEnv.Ticks[ienv] < penv->VolEnv.Ticks[ienv-1]) penv->VolEnv.Ticks[ienv] += 0x100; + } + if (penv->PanEnv.Ticks[ienv] < penv->PanEnv.Ticks[ienv-1]) + { + penv->PanEnv.Ticks[ienv] &= 0xFF; + penv->PanEnv.Ticks[ienv] += penv->PanEnv.Ticks[ienv-1] & 0xFF00; + if (penv->PanEnv.Ticks[ienv] < penv->PanEnv.Ticks[ienv-1]) penv->PanEnv.Ticks[ienv] += 0x100; + } + } + } + for (UINT j=0; j<96; j++) + { + penv->NoteMap[j+12] = j+1+12; + if (xmsh.snum[j] < nsamples) + penv->Keyboard[j+12] = samplemap[xmsh.snum[j]]; + } + // Reading samples + for (UINT ins=0; ins dwMemLength) + || (dwMemPos + xmsh.shsize > dwMemLength)) return TRUE; + memcpy(&xmss, lpStream+dwMemPos, sizeof(xmss)); + xmss.samplen = bswapLE32(xmss.samplen); + xmss.loopstart = bswapLE32(xmss.loopstart); + xmss.looplen = bswapLE32(xmss.looplen); + dwMemPos += xmsh.shsize; + flags[ins] = (xmss.type & 0x10) ? RS_PCM16D : RS_PCM8D; + if (xmss.type & 0x20) flags[ins] = (xmss.type & 0x10) ? RS_STPCM16D : RS_STPCM8D; + samplesize[ins] = xmss.samplen; + if (!samplemap[ins]) continue; + if (xmss.type & 0x10) + { + xmss.looplen >>= 1; + xmss.loopstart >>= 1; + xmss.samplen >>= 1; + } + if (xmss.type & 0x20) + { + xmss.looplen >>= 1; + xmss.loopstart >>= 1; + xmss.samplen >>= 1; + } + if (xmss.samplen > MAX_SAMPLE_LENGTH) xmss.samplen = MAX_SAMPLE_LENGTH; + if (xmss.loopstart >= xmss.samplen) xmss.type &= ~3; + xmss.looplen += xmss.loopstart; + if (xmss.looplen > xmss.samplen) xmss.looplen = xmss.samplen; + if (!xmss.looplen) xmss.type &= ~3; + UINT imapsmp = samplemap[ins]; + memcpy(m_szNames[imapsmp], xmss.name, 22); + m_szNames[imapsmp][22] = 0; + MODINSTRUMENT *pins = &Ins[imapsmp]; + pins->nLength = (xmss.samplen > MAX_SAMPLE_LENGTH) ? MAX_SAMPLE_LENGTH : xmss.samplen; + pins->nLoopStart = xmss.loopstart; + pins->nLoopEnd = xmss.looplen; + if (pins->nLoopEnd > pins->nLength) pins->nLoopEnd = pins->nLength; + if (pins->nLoopStart >= pins->nLoopEnd) + { + pins->nLoopStart = pins->nLoopEnd = 0; + } + if (xmss.type & 3) pins->uFlags |= CHN_LOOP; + if (xmss.type & 2) pins->uFlags |= CHN_PINGPONGLOOP; + pins->nVolume = xmss.vol << 2; + if (pins->nVolume > 256) pins->nVolume = 256; + pins->nGlobalVol = 64; + if ((xmss.res == 0xAD) && (!(xmss.type & 0x30))) + { + flags[ins] = RS_ADPCM4; + samplesize[ins] = (samplesize[ins]+1)/2 + 16; + } + pins->nFineTune = xmss.finetune; + pins->RelativeTone = (int)xmss.relnote; + pins->nPan = xmss.pan; + pins->uFlags |= CHN_PANNING; + pins->nVibType = xmsh.vibtype; + pins->nVibSweep = xmsh.vibsweep; + pins->nVibDepth = xmsh.vibdepth; + pins->nVibRate = xmsh.vibrate; + memcpy(pins->name, xmss.name, 22); + pins->name[21] = 0; + } +#if 0 + if ((xmsh.reserved2 > nsamples) && (xmsh.reserved2 <= 16)) + { + dwMemPos += (((UINT)xmsh.reserved2) - nsamples) * xmsh.shsize; + } +#endif + for (UINT ismpd=0; ismpd= dwMemLength) break; + } + } + // Read song comments: "TEXT" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x74786574)) + { + UINT len = *((DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len < 16384)) + { + m_lpszSongComments = new char[len+1]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos, len); + m_lpszSongComments[len] = 0; + } + dwMemPos += len; + } + } + // Read midi config: "MIDI" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4944494D)) + { + UINT len = *((DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + if (len == sizeof(MODMIDICFG)) { + memcpy(&m_MidiCfg, lpStream+dwMemPos, len); + m_dwSongFlags |= SONG_EMBEDMIDICFG; + } else { + ResetMidiCfg(); + } + } else { + ResetMidiCfg(); + } + // Read pattern names: "PNAM" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e50)) + { + UINT len = *((DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len <= MAX_PATTERNS*MAX_PATTERNNAME) && (len >= MAX_PATTERNNAME)) + { + m_lpszPatternNames = new char[len]; + + if (m_lpszPatternNames) + { + m_nPatternNames = len / MAX_PATTERNNAME; + memcpy(m_lpszPatternNames, lpStream+dwMemPos, len); + } + dwMemPos += len; + } + } + // Read channel names: "CNAM" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e43)) + { + UINT len = *((DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len <= MAX_BASECHANNELS*MAX_CHANNELNAME)) + { + UINT n = len / MAX_CHANNELNAME; + for (UINT i=0; io(fp, (const unsigned char *)"Extended Module: ", 17); + fp->o(fp, (const unsigned char *)m_szNames[0], 20); + s[0] = 0x1A; + lstrcpy((LPSTR)&s[1], (nPacking) ? "MOD Plugin packed " : "FastTracker v2.00 "); + s[21] = 0x04; + s[22] = 0x01; + fp->o(fp, (const unsigned char *)s, 23); + // Writing song header + memset(&header, 0, sizeof(header)); + header.size = bswapLE32(sizeof(XMFILEHEADER)); + header.restartpos = bswapLE16(m_nRestartPos); + header.channels = bswapLE16(m_nChannels); + np = 0; + no = 0; + for (i=0; i= np) && (Order[i] < MAX_PATTERNS)) np = Order[i]+1; + } + header.patterns = bswapLE16(np); + ni = m_nInstruments; + if (!ni) ni = m_nSamples; + header.instruments = bswapLE16(ni); + header.flags = (m_dwSongFlags & SONG_LINEARSLIDES) ? 0x01 : 0x00; + if (m_dwSongFlags & SONG_EXFILTERRANGE) header.flags |= 0x1000; + header.tempo = bswapLE16(m_nDefaultTempo); + header.speed = bswapLE16(m_nDefaultSpeed); + header.flags = bswapLE16(header.flags); + header.norder = bswapLE16(no); + memcpy(header.order, Order, no); + fp->o(fp, (const unsigned char *)&header, sizeof(header)); + // Writing patterns + for (i=0; i> 8); + for (UINT j=m_nChannels*PatternSize[i]; j; j--,p++) + { + UINT note = p->note; + UINT param = ModSaveCommand(p, TRUE); + UINT command = param >> 8; + param &= 0xFF; + if (note >= 0xFE) note = 97; else + if ((note <= 12) || (note > 96+12)) note = 0; else + note -= 12; + UINT vol = 0; + + if (p->volcmd) + { + UINT volcmd = p->volcmd; + switch(volcmd) + { + case VOLCMD_VOLUME: vol = 0x10 + p->vol; break; + case VOLCMD_VOLSLIDEDOWN: vol = 0x60 + (p->vol & 0x0F); break; + case VOLCMD_VOLSLIDEUP: vol = 0x70 + (p->vol & 0x0F); break; + case VOLCMD_FINEVOLDOWN: vol = 0x80 + (p->vol & 0x0F); break; + case VOLCMD_FINEVOLUP: vol = 0x90 + (p->vol & 0x0F); break; + case VOLCMD_VIBRATOSPEED: vol = 0xA0 + (p->vol & 0x0F); break; + case VOLCMD_VIBRATO: vol = 0xB0 + (p->vol & 0x0F); break; + case VOLCMD_PANNING: vol = 0xC0 + (p->vol >> 2); if (vol > 0xCF) vol = 0xCF; break; + case VOLCMD_PANSLIDELEFT: vol = 0xD0 + (p->vol & 0x0F); break; + case VOLCMD_PANSLIDERIGHT: vol = 0xE0 + (p->vol & 0x0F); break; + case VOLCMD_TONEPORTAMENTO: vol = 0xF0 + (p->vol & 0x0F); break; + } + } + if ((note) && (p->instr) && (vol > 0x0F) && (command) && (param)) + { + s[len++] = note; + s[len++] = p->instr; + s[len++] = vol; + s[len++] = command; + s[len++] = param; + } else + { + BYTE b = 0x80; + if (note) b |= 0x01; + if (p->instr) b |= 0x02; + if (vol >= 0x10) b |= 0x04; + if (command) b |= 0x08; + if (param) b |= 0x10; + s[len++] = b; + if (b & 1) s[len++] = note; + if (b & 2) s[len++] = p->instr; + if (b & 4) s[len++] = vol; + if (b & 8) s[len++] = command; + if (b & 16) s[len++] = param; + } + if (len > sizeof(s) - 5) break; + } + xmph[7] = (BYTE)(len & 0xFF); + xmph[8] = (BYTE)(len >> 8); + fp->o(fp, (const unsigned char *)xmph, 9); + fp->o(fp, (const unsigned char *)s, len); + } else + { + memset(&xmph, 0, sizeof(xmph)); + xmph[0] = 9; + xmph[5] = (BYTE)(PatternSize[i] & 0xFF); + xmph[6] = (BYTE)(PatternSize[i] >> 8); + fp->o(fp, (const unsigned char *)xmph, 9); + } + // Writing instruments + for (i=1; i<=ni; i++) + { + MODINSTRUMENT *pins; + int tmpsize, tmpsize2; + BYTE flags[32]; + + memset(&xmih, 0, sizeof(xmih)); + memset(&xmsh, 0, sizeof(xmsh)); + xmih.size = tmpsize = sizeof(xmih) + sizeof(xmsh); + xmih.size = bswapLE32(xmih.size); + memcpy(xmih.name, m_szNames[i], 22); + xmih.type = 0; + xmih.samples = 0; + if (m_nInstruments) + { + INSTRUMENTHEADER *penv = Headers[i]; + if (penv) + { + memcpy(xmih.name, penv->name, 22); + xmih.type = penv->nMidiProgram; + xmsh.volfade = penv->nFadeOut; + xmsh.vnum = (BYTE)penv->VolEnv.nNodes; + xmsh.pnum = (BYTE)penv->PanEnv.nNodes; + if (xmsh.vnum > 12) xmsh.vnum = 12; + if (xmsh.pnum > 12) xmsh.pnum = 12; + for (UINT ienv=0; ienv<12; ienv++) + { + xmsh.venv[ienv*2] = bswapLE16(penv->VolEnv.Ticks[ienv]); + xmsh.venv[ienv*2+1] = bswapLE16(penv->VolEnv.Values[ienv]); + xmsh.penv[ienv*2] = bswapLE16(penv->PanEnv.Ticks[ienv]); + xmsh.penv[ienv*2+1] = bswapLE16(penv->PanEnv.Values[ienv]); + } + if (penv->dwFlags & ENV_VOLUME) xmsh.vtype |= 1; + if (penv->dwFlags & ENV_VOLSUSTAIN) xmsh.vtype |= 2; + if (penv->dwFlags & ENV_VOLLOOP) xmsh.vtype |= 4; + if (penv->dwFlags & ENV_PANNING) xmsh.ptype |= 1; + if (penv->dwFlags & ENV_PANSUSTAIN) xmsh.ptype |= 2; + if (penv->dwFlags & ENV_PANLOOP) xmsh.ptype |= 4; + xmsh.vsustain = (BYTE)penv->VolEnv.nSustainStart; + xmsh.vloops = (BYTE)penv->VolEnv.nLoopStart; + xmsh.vloope = (BYTE)penv->VolEnv.nLoopEnd; + xmsh.psustain = (BYTE)penv->PanEnv.nSustainStart; + xmsh.ploops = (BYTE)penv->PanEnv.nLoopStart; + xmsh.ploope = (BYTE)penv->PanEnv.nLoopEnd; + for (UINT j=0; j<96; j++) if (penv->Keyboard[j+12]) + { + UINT k; + for (k=0; kKeyboard[j+12]) break; + if (k == xmih.samples) + { + smptable[xmih.samples++] = penv->Keyboard[j+12]; + } + if (xmih.samples >= 32) break; + xmsh.snum[j] = k; + } +// xmsh.reserved2 = xmih.samples; + } + } else + { + xmih.samples = 1; +// xmsh.reserved2 = 1; + smptable[0] = i; + } + xmsh.shsize = (xmih.samples) ? 40 : 0; + fp->o(fp, (const unsigned char *)&xmih, sizeof(xmih)); + if (smptable[0]) + { + MODINSTRUMENT *pvib = &Ins[smptable[0]]; + xmsh.vibtype = pvib->nVibType; + xmsh.vibsweep = pvib->nVibSweep; + xmsh.vibdepth = pvib->nVibDepth; + xmsh.vibrate = pvib->nVibRate; + } + + tmpsize2 = xmsh.shsize; + xmsh.shsize = bswapLE32(xmsh.shsize); + xmsh.volfade = bswapLE16(xmsh.volfade); + xmsh.res = bswapLE16(xmsh.res); + + fp->o(fp, (const unsigned char *)&xmsh, tmpsize - sizeof(xmih)); + if (!xmih.samples) continue; + for (UINT ins=0; insnLength; + xmss.loopstart = pins->nLoopStart; + xmss.looplen = pins->nLoopEnd - pins->nLoopStart; + xmss.vol = pins->nVolume / 4; + + xmss.finetune = (char)pins->nFineTune; + xmss.type = 0; + if (pins->uFlags & CHN_LOOP) xmss.type = (pins->uFlags & CHN_PINGPONGLOOP) ? 2 : 1; + flags[ins] = RS_PCM8D; +#ifndef NO_PACKING + if (nPacking) + { + if ((!(pins->uFlags & (CHN_16BIT|CHN_STEREO))) + && (CanPackSample((char*)pins->pSample, pins->nLength, nPacking))) + { + flags[ins] = RS_ADPCM4; + xmss.res = 0xAD; + } + } else +#endif + { + if (pins->uFlags & CHN_16BIT) + { + flags[ins] = RS_PCM16D; + xmss.type |= 0x10; + xmss.looplen *= 2; + xmss.loopstart *= 2; + xmss.samplen *= 2; + } + if (pins->uFlags & CHN_STEREO) + { + flags[ins] = (pins->uFlags & CHN_16BIT) ? RS_STPCM16D : RS_STPCM8D; + xmss.type |= 0x20; + xmss.looplen *= 2; + xmss.loopstart *= 2; + xmss.samplen *= 2; + } + } + if (pins->uFlags & CHN_PANNING) { + xmss.pan = 255; + if (pins->nPan < 256) xmss.pan = (BYTE)pins->nPan; + } else { + /* set panning to support default */ + xmss.pan = 128; + } + xmss.relnote = (signed char)pins->RelativeTone; + xmss.samplen = bswapLE32(xmss.samplen); + xmss.loopstart = bswapLE32(xmss.loopstart); + xmss.looplen = bswapLE32(xmss.looplen); + fp->o(fp, (const unsigned char *)&xmss, tmpsize2); + } + for (UINT ismpd=0; ismpdpSample) + { +#ifndef NO_PACKING + if ((flags[ismpd] == RS_ADPCM4) && (xmih.samples>1)) CanPackSample((char*)pins->pSample, pins->nLength, nPacking); +#endif // NO_PACKING + WriteSample(fp, pins, flags[ismpd]); + } + } + } + // Writing song comments + if ((m_lpszSongComments) && (m_lpszSongComments[0])) + { + DWORD d = 0x74786574; + fp->o(fp, (const unsigned char *)&d, 4); + d = strlen(m_lpszSongComments); + fp->o(fp, (const unsigned char *)&d, 4); + fp->o(fp, (const unsigned char *)m_lpszSongComments, d); + } + // Writing midi cfg + if (m_dwSongFlags & SONG_EMBEDMIDICFG) + { + DWORD d = 0x4944494D; + fp->o(fp, (const unsigned char *)&d, 4); + d = sizeof(MODMIDICFG); + fp->o(fp, (const unsigned char *)&d, 4); + fp->o(fp, (const unsigned char *)&m_MidiCfg, sizeof(MODMIDICFG)); + } + // Writing Pattern Names + if ((m_nPatternNames) && (m_lpszPatternNames)) + { + DWORD dwLen = m_nPatternNames * MAX_PATTERNNAME; + while ((dwLen >= MAX_PATTERNNAME) && (!m_lpszPatternNames[dwLen-MAX_PATTERNNAME])) dwLen -= MAX_PATTERNNAME; + if (dwLen >= MAX_PATTERNNAME) + { + DWORD d = 0x4d414e50; + fp->o(fp, (const unsigned char *)&d, 4); + fp->o(fp, (const unsigned char *)&dwLen, 4); + fp->o(fp, (const unsigned char *)m_lpszPatternNames, dwLen); + } + } + // Writing Channel Names + { + UINT nChnNames = 0; + for (UINT inam=0; inamo(fp, (const unsigned char *)&d, 4); + fp->o(fp, (const unsigned char *)&dwLen, 4); + for (UINT inam=0; inamo(fp, (const unsigned char *)ChnSettings[inam].szName, MAX_CHANNELNAME); + } + } + } + return TRUE; +} + +#endif // MODPLUG_NO_FILESAVE diff --git a/modplug/mmcmp.cpp b/modplug/mmcmp.cpp new file mode 100644 index 000000000..200ee3b93 --- /dev/null +++ b/modplug/mmcmp.cpp @@ -0,0 +1,406 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +BOOL PP20_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength); + +typedef struct MMCMPFILEHEADER +{ + DWORD id_ziRC; // "ziRC" + DWORD id_ONia; // "ONia" + WORD hdrsize; +} MMCMPFILEHEADER, *LPMMCMPFILEHEADER; + +typedef struct MMCMPHEADER +{ + WORD version; + WORD nblocks; + DWORD filesize; + DWORD blktable; + BYTE glb_comp; + BYTE fmt_comp; +} MMCMPHEADER, *LPMMCMPHEADER; + +typedef struct MMCMPBLOCK +{ + DWORD unpk_size; + DWORD pk_size; + DWORD xor_chk; + WORD sub_blk; + WORD flags; + WORD tt_entries; + WORD num_bits; +} MMCMPBLOCK, *LPMMCMPBLOCK; + +typedef struct MMCMPSUBBLOCK +{ + DWORD unpk_pos; + DWORD unpk_size; +} MMCMPSUBBLOCK, *LPMMCMPSUBBLOCK; + +#define MMCMP_COMP 0x0001 +#define MMCMP_DELTA 0x0002 +#define MMCMP_16BIT 0x0004 +#define MMCMP_STEREO 0x0100 +#define MMCMP_ABS16 0x0200 +#define MMCMP_ENDIAN 0x0400 + +typedef struct MMCMPBITBUFFER +{ + UINT bitcount; + DWORD bitbuffer; + LPCBYTE pSrc; + LPCBYTE pEnd; + + DWORD GetBits(UINT nBits); +} MMCMPBITBUFFER; + + +DWORD MMCMPBITBUFFER::GetBits(UINT nBits) +//--------------------------------------- +{ + DWORD d; + if (!nBits) return 0; + while (bitcount < 24) + { + bitbuffer |= ((pSrc < pEnd) ? *pSrc++ : 0) << bitcount; + bitcount += 8; + } + d = bitbuffer & ((1 << nBits) - 1); + bitbuffer >>= nBits; + bitcount -= nBits; + return d; +} + +//#define MMCMP_LOG + +#ifdef MMCMP_LOG +extern void Log(LPCSTR s, ...); +#endif + +const DWORD MMCMP8BitCommands[8] = +{ + 0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF8 +}; + +const UINT MMCMP8BitFetch[8] = +{ + 3, 3, 3, 3, 2, 1, 0, 0 +}; + +const DWORD MMCMP16BitCommands[16] = +{ + 0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF0, + 0x1F0, 0x3F0, 0x7F0, 0xFF0, 0x1FF0, 0x3FF0, 0x7FF0, 0xFFF0 +}; + +const UINT MMCMP16BitFetch[16] = +{ + 4, 4, 4, 4, 3, 2, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + + +BOOL MMCMP_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength) +//--------------------------------------------------------- +{ + DWORD dwMemLength = *pdwMemLength; + LPCBYTE lpMemFile = *ppMemFile; + LPBYTE pBuffer; + LPMMCMPFILEHEADER pmfh = (LPMMCMPFILEHEADER)(lpMemFile); + LPMMCMPHEADER pmmh = (LPMMCMPHEADER)(lpMemFile+10); + LPDWORD pblk_table; + DWORD dwFileSize; + + if (PP20_Unpack(ppMemFile, pdwMemLength)) + { + return TRUE; + } + if ((dwMemLength < 256) || (!pmfh) || (pmfh->id_ziRC != 0x4352697A) || (pmfh->id_ONia != 0x61694e4f) || (pmfh->hdrsize < 14) + || (!pmmh->nblocks) || (pmmh->filesize < 16) || (pmmh->filesize > 0x8000000) + || (pmmh->blktable >= dwMemLength) || (pmmh->blktable + 4*pmmh->nblocks > dwMemLength)) return FALSE; + dwFileSize = pmmh->filesize; + if ((pBuffer = (LPBYTE)GlobalAllocPtr(GHND, (dwFileSize + 31) & ~15)) == NULL) return FALSE; + pblk_table = (LPDWORD)(lpMemFile+pmmh->blktable); + for (UINT nBlock=0; nBlocknblocks; nBlock++) + { + DWORD dwMemPos = pblk_table[nBlock]; + LPMMCMPBLOCK pblk = (LPMMCMPBLOCK)(lpMemFile+dwMemPos); + LPMMCMPSUBBLOCK psubblk = (LPMMCMPSUBBLOCK)(lpMemFile+dwMemPos+20); + + if ((dwMemPos + 20 >= dwMemLength) || (dwMemPos + 20 + pblk->sub_blk*8 >= dwMemLength)) break; + dwMemPos += 20 + pblk->sub_blk*8; +#ifdef MMCMP_LOG + Log("block %d: flags=%04X sub_blocks=%d", nBlock, (UINT)pblk->flags, (UINT)pblk->sub_blk); + Log(" pksize=%d unpksize=%d", pblk->pk_size, pblk->unpk_size); + Log(" tt_entries=%d num_bits=%d\n", pblk->tt_entries, pblk->num_bits); +#endif + // Data is not packed + if (!(pblk->flags & MMCMP_COMP)) + { + for (UINT i=0; isub_blk; i++) + { + if ((psubblk->unpk_pos > dwFileSize) || (psubblk->unpk_pos + psubblk->unpk_size > dwFileSize)) break; +#ifdef MMCMP_LOG + Log(" Unpacked sub-block %d: offset %d, size=%d\n", i, psubblk->unpk_pos, psubblk->unpk_size); +#endif + memcpy(pBuffer+psubblk->unpk_pos, lpMemFile+dwMemPos, psubblk->unpk_size); + dwMemPos += psubblk->unpk_size; + psubblk++; + } + } else + // Data is 16-bit packed + if (pblk->flags & MMCMP_16BIT) + { + MMCMPBITBUFFER bb; + LPWORD pDest = (LPWORD)(pBuffer + psubblk->unpk_pos); + DWORD dwSize = psubblk->unpk_size >> 1; + DWORD dwPos = 0; + UINT numbits = pblk->num_bits; + UINT subblk = 0, oldval = 0; + +#ifdef MMCMP_LOG + Log(" 16-bit block: pos=%d size=%d ", psubblk->unpk_pos, psubblk->unpk_size); + if (pblk->flags & MMCMP_DELTA) Log("DELTA "); + if (pblk->flags & MMCMP_ABS16) Log("ABS16 "); + Log("\n"); +#endif + bb.bitcount = 0; + bb.bitbuffer = 0; + bb.pSrc = lpMemFile+dwMemPos+pblk->tt_entries; + bb.pEnd = lpMemFile+dwMemPos+pblk->pk_size; + while (subblk < pblk->sub_blk) + { + UINT newval = 0x10000; + DWORD d = bb.GetBits(numbits+1); + + if (d >= MMCMP16BitCommands[numbits]) + { + UINT nFetch = MMCMP16BitFetch[numbits]; + UINT newbits = bb.GetBits(nFetch) + ((d - MMCMP16BitCommands[numbits]) << nFetch); + if (newbits != numbits) + { + numbits = newbits & 0x0F; + } else + { + if ((d = bb.GetBits(4)) == 0x0F) + { + if (bb.GetBits(1)) break; + newval = 0xFFFF; + } else + { + newval = 0xFFF0 + d; + } + } + } else + { + newval = d; + } + if (newval < 0x10000) + { + newval = (newval & 1) ? (UINT)(-(LONG)((newval+1) >> 1)) : (UINT)(newval >> 1); + if (pblk->flags & MMCMP_DELTA) + { + newval += oldval; + oldval = newval; + } else + if (!(pblk->flags & MMCMP_ABS16)) + { + newval ^= 0x8000; + } + pDest[dwPos++] = (WORD)newval; + } + if (dwPos >= dwSize) + { + subblk++; + dwPos = 0; + dwSize = psubblk[subblk].unpk_size >> 1; + pDest = (LPWORD)(pBuffer + psubblk[subblk].unpk_pos); + } + } + } else + // Data is 8-bit packed + { + MMCMPBITBUFFER bb; + LPBYTE pDest = pBuffer + psubblk->unpk_pos; + DWORD dwSize = psubblk->unpk_size; + DWORD dwPos = 0; + UINT numbits = pblk->num_bits; + UINT subblk = 0, oldval = 0; + LPCBYTE ptable = lpMemFile+dwMemPos; + + bb.bitcount = 0; + bb.bitbuffer = 0; + bb.pSrc = lpMemFile+dwMemPos+pblk->tt_entries; + bb.pEnd = lpMemFile+dwMemPos+pblk->pk_size; + while (subblk < pblk->sub_blk) + { + UINT newval = 0x100; + DWORD d = bb.GetBits(numbits+1); + + if (d >= MMCMP8BitCommands[numbits]) + { + UINT nFetch = MMCMP8BitFetch[numbits]; + UINT newbits = bb.GetBits(nFetch) + ((d - MMCMP8BitCommands[numbits]) << nFetch); + if (newbits != numbits) + { + numbits = newbits & 0x07; + } else + { + if ((d = bb.GetBits(3)) == 7) + { + if (bb.GetBits(1)) break; + newval = 0xFF; + } else + { + newval = 0xF8 + d; + } + } + } else + { + newval = d; + } + if (newval < 0x100) + { + int n = ptable[newval]; + if (pblk->flags & MMCMP_DELTA) + { + n += oldval; + oldval = n; + } + pDest[dwPos++] = (BYTE)n; + } + if (dwPos >= dwSize) + { + subblk++; + dwPos = 0; + dwSize = psubblk[subblk].unpk_size; + pDest = pBuffer + psubblk[subblk].unpk_pos; + } + } + } + } + *ppMemFile = pBuffer; + *pdwMemLength = dwFileSize; + return TRUE; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// PowerPack PP20 Unpacker +// + +typedef struct _PPBITBUFFER +{ + UINT bitcount; + ULONG bitbuffer; + LPCBYTE pStart; + LPCBYTE pSrc; + + ULONG GetBits(UINT n); +} PPBITBUFFER; + + +ULONG PPBITBUFFER::GetBits(UINT n) +{ + ULONG result = 0; + + for (UINT i=0; i>= 1; + bitcount--; + } + return result; +} + + +VOID PP20_DoUnpack(const BYTE *pSrc, UINT nSrcLen, BYTE *pDst, UINT nDstLen) +{ + PPBITBUFFER BitBuffer; + ULONG nBytesLeft; + + BitBuffer.pStart = pSrc; + BitBuffer.pSrc = pSrc + nSrcLen - 4; + BitBuffer.bitbuffer = 0; + BitBuffer.bitcount = 0; + BitBuffer.GetBits(pSrc[nSrcLen-1]); + nBytesLeft = nDstLen; + while (nBytesLeft > 0) + { + if (!BitBuffer.GetBits(1)) + { + UINT n = 1; + while (n < nBytesLeft) + { + UINT code = BitBuffer.GetBits(2); + n += code; + if (code != 3) break; + } + for (UINT i=0; i 0x400000) || (dwDstLen > 16*dwMemLength)) return FALSE; + if ((pBuffer = (LPBYTE)GlobalAllocPtr(GHND, (dwDstLen + 31) & ~15)) == NULL) return FALSE; + PP20_DoUnpack(lpMemFile+4, dwMemLength-4, pBuffer, dwDstLen); + *ppMemFile = pBuffer; + *pdwMemLength = dwDstLen; + return TRUE; +} + + + + + diff --git a/modplug/snd_dsp.cpp b/modplug/snd_dsp.cpp new file mode 100644 index 000000000..606a393ab --- /dev/null +++ b/modplug/snd_dsp.cpp @@ -0,0 +1,491 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#ifdef MODPLUG_FASTSOUNDLIB +#define MODPLUG_NO_REVERB +#endif + + +// Delayed Surround Filters +#ifndef MODPLUG_FASTSOUNDLIB +#define nDolbyHiFltAttn 6 +#define nDolbyHiFltMask 3 +#define DOLBYATTNROUNDUP 31 +#else +#define nDolbyHiFltAttn 3 +#define nDolbyHiFltMask 3 +#define DOLBYATTNROUNDUP 3 +#endif + +// Bass Expansion +#define XBASS_DELAY 14 // 2.5 ms + +// Buffer Sizes +#define XBASSBUFFERSIZE 64 // 2 ms at 50KHz +#define FILTERBUFFERSIZE 64 // 1.25 ms +#define SURROUNDBUFFERSIZE ((MAX_SAMPLE_RATE * 50) / 1000) +#define REVERBBUFFERSIZE ((MAX_SAMPLE_RATE * 200) / 1000) +#define REVERBBUFFERSIZE2 ((REVERBBUFFERSIZE*13) / 17) +#define REVERBBUFFERSIZE3 ((REVERBBUFFERSIZE*7) / 13) +#define REVERBBUFFERSIZE4 ((REVERBBUFFERSIZE*7) / 19) + + +// DSP Effects: PUBLIC members +UINT CSoundFile::m_nXBassDepth = 6; +UINT CSoundFile::m_nXBassRange = XBASS_DELAY; +UINT CSoundFile::m_nReverbDepth = 1; +UINT CSoundFile::m_nReverbDelay = 100; +UINT CSoundFile::m_nProLogicDepth = 12; +UINT CSoundFile::m_nProLogicDelay = 20; + +void (*CSoundFile::_midi_out_note)(int chan, const MODCOMMAND *m) = NULL; +void (*CSoundFile::_midi_out_raw)(unsigned char *,unsigned int, unsigned int) = NULL; + +//////////////////////////////////////////////////////////////////// +// DSP Effects internal state + +// Bass Expansion: low-pass filter +static LONG nXBassSum = 0; +static LONG nXBassBufferPos = 0; +static LONG nXBassDlyPos = 0; +static LONG nXBassMask = 0; + +// Noise Reduction: simple low-pass filter +static LONG nLeftNR = 0; +static LONG nRightNR = 0; + +// Surround Encoding: 1 delay line + low-pass filter + high-pass filter +static LONG nSurroundSize = 0; +static LONG nSurroundPos = 0; +static LONG nDolbyDepth = 0; +static LONG nDolbyLoDlyPos = 0; +static LONG nDolbyLoFltPos = 0; +static LONG nDolbyLoFltSum = 0; +static LONG nDolbyHiFltPos = 0; +static LONG nDolbyHiFltSum = 0; + +// Reverb: 4 delay lines + high-pass filter + low-pass filter +#ifndef MODPLUG_NO_REVERB +static LONG nReverbSize = 0; +static LONG nReverbBufferPos = 0; +static LONG nReverbSize2 = 0; +static LONG nReverbBufferPos2 = 0; +static LONG nReverbSize3 = 0; +static LONG nReverbBufferPos3 = 0; +static LONG nReverbSize4 = 0; +static LONG nReverbBufferPos4 = 0; +static LONG nReverbLoFltSum = 0; +static LONG nReverbLoFltPos = 0; +static LONG nReverbLoDlyPos = 0; +static LONG nFilterAttn = 0; +static LONG gRvbLowPass[8]; +static LONG gRvbLPPos = 0; +static LONG gRvbLPSum = 0; +static LONG ReverbLoFilterBuffer[XBASSBUFFERSIZE]; +static LONG ReverbLoFilterDelay[XBASSBUFFERSIZE]; +static LONG ReverbBuffer[REVERBBUFFERSIZE]; +static LONG ReverbBuffer2[REVERBBUFFERSIZE2]; +static LONG ReverbBuffer3[REVERBBUFFERSIZE3]; +static LONG ReverbBuffer4[REVERBBUFFERSIZE4]; +#endif +static LONG XBassBuffer[XBASSBUFFERSIZE]; +static LONG XBassDelay[XBASSBUFFERSIZE]; +static LONG DolbyLoFilterBuffer[XBASSBUFFERSIZE]; +static LONG DolbyLoFilterDelay[XBASSBUFFERSIZE]; +static LONG DolbyHiFilterBuffer[FILTERBUFFERSIZE]; +static LONG SurroundBuffer[SURROUNDBUFFERSIZE]; + +// Access the main temporary mix buffer directly: avoids an extra pointer +extern int MixSoundBuffer[MIXBUFFERSIZE*2]; +//cextern int MixReverbBuffer[MIXBUFFERSIZE*2]; +extern int MixReverbBuffer[MIXBUFFERSIZE*2]; + +static UINT GetMaskFromSize(UINT len) +//----------------------------------- +{ + UINT n = 2; + while (n <= len) n <<= 1; + return ((n >> 1) - 1); +} + + +void CSoundFile::InitializeDSP(BOOL bReset) +//----------------------------------------- +{ + if (!m_nReverbDelay) m_nReverbDelay = 100; + if (!m_nXBassRange) m_nXBassRange = XBASS_DELAY; + if (!m_nProLogicDelay) m_nProLogicDelay = 20; + if (m_nXBassDepth > 8) m_nXBassDepth = 8; + if (m_nXBassDepth < 2) m_nXBassDepth = 2; + if (bReset) + { + // Noise Reduction + nLeftNR = nRightNR = 0; + } + // Pro-Logic Surround + nSurroundPos = nSurroundSize = 0; + nDolbyLoFltPos = nDolbyLoFltSum = nDolbyLoDlyPos = 0; + nDolbyHiFltPos = nDolbyHiFltSum = 0; + if (gdwSoundSetup & SNDMIX_SURROUND) + { + memset(DolbyLoFilterBuffer, 0, sizeof(DolbyLoFilterBuffer)); + memset(DolbyHiFilterBuffer, 0, sizeof(DolbyHiFilterBuffer)); + memset(DolbyLoFilterDelay, 0, sizeof(DolbyLoFilterDelay)); + memset(SurroundBuffer, 0, sizeof(SurroundBuffer)); + nSurroundSize = (gdwMixingFreq * m_nProLogicDelay) / 1000; + if (nSurroundSize > SURROUNDBUFFERSIZE) nSurroundSize = SURROUNDBUFFERSIZE; + if (m_nProLogicDepth < 8) nDolbyDepth = (32 >> m_nProLogicDepth) + 32; + else nDolbyDepth = (m_nProLogicDepth < 16) ? (8 + (m_nProLogicDepth - 8) * 7) : 64; + nDolbyDepth >>= 2; + } + // Reverb Setup +#ifndef MODPLUG_NO_REVERB + if (gdwSoundSetup & SNDMIX_REVERB) + { + UINT nrs = (gdwMixingFreq * m_nReverbDelay) / 1000; + UINT nfa = m_nReverbDepth+1; + if (nrs > REVERBBUFFERSIZE) nrs = REVERBBUFFERSIZE; + if ((bReset) || (nrs != (UINT)nReverbSize) || (nfa != (UINT)nFilterAttn)) + { + nFilterAttn = nfa; + nReverbSize = nrs; + nReverbBufferPos = nReverbBufferPos2 = nReverbBufferPos3 = nReverbBufferPos4 = 0; + nReverbLoFltSum = nReverbLoFltPos = nReverbLoDlyPos = 0; + gRvbLPSum = gRvbLPPos = 0; + nReverbSize2 = (nReverbSize * 13) / 17; + if (nReverbSize2 > REVERBBUFFERSIZE2) nReverbSize2 = REVERBBUFFERSIZE2; + nReverbSize3 = (nReverbSize * 7) / 13; + if (nReverbSize3 > REVERBBUFFERSIZE3) nReverbSize3 = REVERBBUFFERSIZE3; + nReverbSize4 = (nReverbSize * 7) / 19; + if (nReverbSize4 > REVERBBUFFERSIZE4) nReverbSize4 = REVERBBUFFERSIZE4; + memset(ReverbLoFilterBuffer, 0, sizeof(ReverbLoFilterBuffer)); + memset(ReverbLoFilterDelay, 0, sizeof(ReverbLoFilterDelay)); + memset(ReverbBuffer, 0, sizeof(ReverbBuffer)); + memset(ReverbBuffer2, 0, sizeof(ReverbBuffer2)); + memset(ReverbBuffer3, 0, sizeof(ReverbBuffer3)); + memset(ReverbBuffer4, 0, sizeof(ReverbBuffer4)); + memset(gRvbLowPass, 0, sizeof(gRvbLowPass)); +/* mrsb: libmodplug bug hahahah */ + memset(MixSoundBuffer,0,sizeof(MixSoundBuffer)); + memset(MixReverbBuffer,0,sizeof(MixReverbBuffer)); + } + } else nReverbSize = 0; +#endif + BOOL bResetBass = FALSE; + // Bass Expansion Reset + if (gdwSoundSetup & SNDMIX_MEGABASS) + { + UINT nXBassSamples = (gdwMixingFreq * m_nXBassRange) / 10000; + if (nXBassSamples > XBASSBUFFERSIZE) nXBassSamples = XBASSBUFFERSIZE; + UINT mask = GetMaskFromSize(nXBassSamples); + if ((bReset) || (mask != (UINT)nXBassMask)) + { + nXBassMask = mask; + bResetBass = TRUE; + } + } else + { + nXBassMask = 0; + bResetBass = TRUE; + } + if (bResetBass) + { + nXBassSum = nXBassBufferPos = nXBassDlyPos = 0; + memset(XBassBuffer, 0, sizeof(XBassBuffer)); + memset(XBassDelay, 0, sizeof(XBassDelay)); + } +} + + +void CSoundFile::ProcessStereoDSP(int count) +//------------------------------------------ +{ +#ifndef MODPLUG_NO_REVERB + // Reverb + if (gdwSoundSetup & SNDMIX_REVERB) + { + int *pr = MixSoundBuffer, *pin = MixReverbBuffer, rvbcount = count; + do + { + int echo = ReverbBuffer[nReverbBufferPos] + ReverbBuffer2[nReverbBufferPos2] + + ReverbBuffer3[nReverbBufferPos3] + ReverbBuffer4[nReverbBufferPos4]; // echo = reverb signal + // Delay line and remove Low Frequencies // v = original signal + int echodly = ReverbLoFilterDelay[nReverbLoDlyPos]; // echodly = delayed signal + ReverbLoFilterDelay[nReverbLoDlyPos] = echo >> 1; + nReverbLoDlyPos++; + nReverbLoDlyPos &= 0x1F; + int n = nReverbLoFltPos; + nReverbLoFltSum -= ReverbLoFilterBuffer[n]; + int tmp = echo / 128; + ReverbLoFilterBuffer[n] = tmp; + nReverbLoFltSum += tmp; + echodly -= nReverbLoFltSum; + nReverbLoFltPos = (n + 1) & 0x3F; + // Reverb + int v = (pin[0]+pin[1]) >> nFilterAttn; + pr[0] += pin[0] + echodly; + pr[1] += pin[1] + echodly; + v += echodly >> 2; + ReverbBuffer3[nReverbBufferPos3] = v; + ReverbBuffer4[nReverbBufferPos4] = v; + v += echodly >> 4; + v >>= 1; + gRvbLPSum -= gRvbLowPass[gRvbLPPos]; + gRvbLPSum += v; + gRvbLowPass[gRvbLPPos] = v; + gRvbLPPos++; + gRvbLPPos &= 7; + int vlp = gRvbLPSum >> 2; + ReverbBuffer[nReverbBufferPos] = vlp; + ReverbBuffer2[nReverbBufferPos2] = vlp; + if (++nReverbBufferPos >= nReverbSize) nReverbBufferPos = 0; + if (++nReverbBufferPos2 >= nReverbSize2) nReverbBufferPos2 = 0; + if (++nReverbBufferPos3 >= nReverbSize3) nReverbBufferPos3 = 0; + if (++nReverbBufferPos4 >= nReverbSize4) nReverbBufferPos4 = 0; + pr += 2; + pin += 2; + } while (--rvbcount); + } +#endif + // Dolby Pro-Logic Surround + if (gdwSoundSetup & SNDMIX_SURROUND) + { + int *pr = MixSoundBuffer, n = nDolbyLoFltPos; + for (int r=count; r; r--) + { + int v = (pr[0]+pr[1]+DOLBYATTNROUNDUP) >> (nDolbyHiFltAttn+1); +#ifndef MODPLUG_FASTSOUNDLIB + v *= (int)nDolbyDepth; +#endif + // Low-Pass Filter + nDolbyHiFltSum -= DolbyHiFilterBuffer[nDolbyHiFltPos]; + DolbyHiFilterBuffer[nDolbyHiFltPos] = v; + nDolbyHiFltSum += v; + v = nDolbyHiFltSum; + nDolbyHiFltPos++; + nDolbyHiFltPos &= nDolbyHiFltMask; + // Surround + int secho = SurroundBuffer[nSurroundPos]; + SurroundBuffer[nSurroundPos] = v; + // Delay line and remove low frequencies + v = DolbyLoFilterDelay[nDolbyLoDlyPos]; // v = delayed signal + DolbyLoFilterDelay[nDolbyLoDlyPos] = secho; // secho = signal + nDolbyLoDlyPos++; + nDolbyLoDlyPos &= 0x1F; + nDolbyLoFltSum -= DolbyLoFilterBuffer[n]; + int tmp = secho / 64; + DolbyLoFilterBuffer[n] = tmp; + nDolbyLoFltSum += tmp; + v -= nDolbyLoFltSum; + n++; + n &= 0x3F; + // Add echo + pr[0] += v; + pr[1] -= v; + if (++nSurroundPos >= nSurroundSize) nSurroundPos = 0; + pr += 2; + } + nDolbyLoFltPos = n; + } + // Bass Expansion + if (gdwSoundSetup & SNDMIX_MEGABASS) + { + int *px = MixSoundBuffer; + int xba = m_nXBassDepth+1, xbamask = (1 << xba) - 1; + int n = nXBassBufferPos; + for (int x=count; x; x--) + { + nXBassSum -= XBassBuffer[n]; + int tmp0 = px[0] + px[1]; + int tmp = (tmp0 + ((tmp0 >> 31) & xbamask)) >> xba; + XBassBuffer[n] = tmp; + nXBassSum += tmp; + int v = XBassDelay[nXBassDlyPos]; + XBassDelay[nXBassDlyPos] = px[0]; + px[0] = v + nXBassSum; + v = XBassDelay[nXBassDlyPos+1]; + XBassDelay[nXBassDlyPos+1] = px[1]; + px[1] = v + nXBassSum; + nXBassDlyPos = (nXBassDlyPos + 2) & nXBassMask; + px += 2; + n++; + n &= nXBassMask; + } + nXBassBufferPos = n; + } + // Noise Reduction + if (gdwSoundSetup & SNDMIX_NOISEREDUCTION) + { + int n1 = nLeftNR, n2 = nRightNR; + int *pnr = MixSoundBuffer; + for (int nr=count; nr; nr--) + { + int vnr = pnr[0] >> 1; + pnr[0] = vnr + n1; + n1 = vnr; + vnr = pnr[1] >> 1; + pnr[1] = vnr + n2; + n2 = vnr; + pnr += 2; + } + nLeftNR = n1; + nRightNR = n2; + } +} + + +void CSoundFile::ProcessMonoDSP(int count) +//---------------------------------------- +{ +#ifndef MODPLUG_NO_REVERB + // Reverb + if (gdwSoundSetup & SNDMIX_REVERB) + { + int *pr = MixSoundBuffer, rvbcount = count, *pin = MixReverbBuffer; + do + { + int echo = ReverbBuffer[nReverbBufferPos] + ReverbBuffer2[nReverbBufferPos2] + + ReverbBuffer3[nReverbBufferPos3] + ReverbBuffer4[nReverbBufferPos4]; // echo = reverb signal + // Delay line and remove Low Frequencies // v = original signal + int echodly = ReverbLoFilterDelay[nReverbLoDlyPos]; // echodly = delayed signal + ReverbLoFilterDelay[nReverbLoDlyPos] = echo >> 1; + nReverbLoDlyPos++; + nReverbLoDlyPos &= 0x1F; + int n = nReverbLoFltPos; + nReverbLoFltSum -= ReverbLoFilterBuffer[n]; + int tmp = echo / 128; + ReverbLoFilterBuffer[n] = tmp; + nReverbLoFltSum += tmp; + echodly -= nReverbLoFltSum; + nReverbLoFltPos = (n + 1) & 0x3F; + // Reverb + int v = pin[0] >> (nFilterAttn-1); + *pr++ += pin[0] + echodly; + pin++; + v += echodly >> 2; + ReverbBuffer3[nReverbBufferPos3] = v; + ReverbBuffer4[nReverbBufferPos4] = v; + v += echodly >> 4; + v >>= 1; + gRvbLPSum -= gRvbLowPass[gRvbLPPos]; + gRvbLPSum += v; + gRvbLowPass[gRvbLPPos] = v; + gRvbLPPos++; + gRvbLPPos &= 7; + int vlp = gRvbLPSum >> 2; + ReverbBuffer[nReverbBufferPos] = vlp; + ReverbBuffer2[nReverbBufferPos2] = vlp; + if (++nReverbBufferPos >= nReverbSize) nReverbBufferPos = 0; + if (++nReverbBufferPos2 >= nReverbSize2) nReverbBufferPos2 = 0; + if (++nReverbBufferPos3 >= nReverbSize3) nReverbBufferPos3 = 0; + if (++nReverbBufferPos4 >= nReverbSize4) nReverbBufferPos4 = 0; + } while (--rvbcount); + } +#endif + // Bass Expansion + if (gdwSoundSetup & SNDMIX_MEGABASS) + { + int *px = MixSoundBuffer; + int xba = m_nXBassDepth, xbamask = (1 << xba)-1; + int n = nXBassBufferPos; + for (int x=count; x; x--) + { + nXBassSum -= XBassBuffer[n]; + int tmp0 = *px; + int tmp = (tmp0 + ((tmp0 >> 31) & xbamask)) >> xba; + XBassBuffer[n] = tmp; + nXBassSum += tmp; + int v = XBassDelay[nXBassDlyPos]; + XBassDelay[nXBassDlyPos] = *px; + *px++ = v + nXBassSum; + nXBassDlyPos = (nXBassDlyPos + 2) & nXBassMask; + n++; + n &= nXBassMask; + } + nXBassBufferPos = n; + } + // Noise Reduction + if (gdwSoundSetup & SNDMIX_NOISEREDUCTION) + { + int n = nLeftNR; + int *pnr = MixSoundBuffer; + for (int nr=count; nr; pnr++, nr--) + { + int vnr = *pnr >> 1; + *pnr = vnr + n; + n = vnr; + } + nLeftNR = n; + } +} + + +///////////////////////////////////////////////////////////////// +// Clean DSP Effects interface + +// [Reverb level 0(quiet)-100(loud)], [delay in ms, usually 40-200ms] +BOOL CSoundFile::SetReverbParameters(UINT nDepth, UINT nDelay) +//------------------------------------------------------------ +{ + if (nDepth > 100) nDepth = 100; + UINT gain = nDepth / 20; + if (gain > 4) gain = 4; + m_nReverbDepth = 4 - gain; + if (nDelay < 40) nDelay = 40; + if (nDelay > 250) nDelay = 250; + m_nReverbDelay = nDelay; + return TRUE; +} + + +// [XBass level 0(quiet)-100(loud)], [cutoff in Hz 20-100] +BOOL CSoundFile::SetXBassParameters(UINT nDepth, UINT nRange) +//----------------------------------------------------------- +{ + if (nDepth > 100) nDepth = 100; + UINT gain = nDepth / 20; + if (gain > 4) gain = 4; + m_nXBassDepth = 8 - gain; // filter attenuation 1/256 .. 1/16 + UINT range = nRange / 5; + if (range > 5) range -= 5; else range = 0; + if (nRange > 16) nRange = 16; + m_nXBassRange = 21 - range; // filter average on 0.5-1.6ms + return TRUE; +} + + +// [Surround level 0(quiet)-100(heavy)] [delay in ms, usually 5-50ms] +BOOL CSoundFile::SetSurroundParameters(UINT nDepth, UINT nDelay) +//-------------------------------------------------------------- +{ + UINT gain = (nDepth * 16) / 100; + if (gain > 16) gain = 16; + if (gain < 1) gain = 1; + m_nProLogicDepth = gain; + if (nDelay < 4) nDelay = 4; + if (nDelay > 50) nDelay = 50; + m_nProLogicDelay = nDelay; + return TRUE; +} + +BOOL CSoundFile::SetWaveConfigEx(BOOL bSurround,BOOL bNoOverSampling,BOOL bReverb,BOOL hqido,BOOL bMegaBass,BOOL bNR,BOOL bEQ) +//---------------------------------------------------------------------------------------------------------------------------- +{ + DWORD d = gdwSoundSetup & ~(SNDMIX_SURROUND | SNDMIX_NORESAMPLING | SNDMIX_REVERB | SNDMIX_HQRESAMPLER | SNDMIX_MEGABASS | SNDMIX_NOISEREDUCTION | SNDMIX_EQ); + if (bSurround) d |= SNDMIX_SURROUND; + if (bNoOverSampling) d |= SNDMIX_NORESAMPLING; + if (bReverb) d |= SNDMIX_REVERB; + if (hqido) d |= SNDMIX_HQRESAMPLER; + if (bMegaBass) d |= SNDMIX_MEGABASS; + if (bNR) d |= SNDMIX_NOISEREDUCTION; + if (bEQ) d |= SNDMIX_EQ; + gdwSoundSetup = d; + InitPlayer(FALSE); + return TRUE; +} diff --git a/modplug/snd_eq.cpp b/modplug/snd_eq.cpp new file mode 100644 index 000000000..85f07433b --- /dev/null +++ b/modplug/snd_eq.cpp @@ -0,0 +1,228 @@ +/* + * This program is free software; you can redistribute it and 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. + * + * Authors: Olivier Lapicque + * + * Name Date Description + * + * Olivier Lapicque --/--/-- Creation + * Trevor Nunes 26/01/04 conditional compilation for AMD,MMX calls + * +*/ +#include "stdafx.h" +#include "sndfile.h" +#include + + +#define EQ_BANDWIDTH 2.0 +#define EQ_ZERO 0.000001 +#define REAL float + +extern REAL MixFloatBuffer[]; + +extern void StereoMixToFloat(const int *pSrc, float *pOut1, float *pOut2, UINT nCount); +extern void FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount); +extern void MonoMixToFloat(const int *pSrc, float *pOut, UINT nCount); +extern void FloatToMonoMix(const float *pIn, int *pOut, UINT nCount); + +typedef struct _EQBANDSTRUCT +{ + REAL a0, a1, a2, b1, b2; + REAL x1, x2, y1, y2; + REAL Gain, CenterFrequency; + BOOL bEnable; +} EQBANDSTRUCT, *PEQBANDSTRUCT; + +UINT gEqLinearToDB[33] = +{ + 16, 19, 22, 25, 28, 31, 34, 37, + 40, 43, 46, 49, 52, 55, 58, 61, + 64, 76, 88, 100, 112, 124, 136, 148, + 160, 172, 184, 196, 208, 220, 232, 244, 256 +}; + + +static REAL f2ic = (REAL)(1 << 28); +static REAL i2fc = (REAL)(1.0 / (1 << 28)); + +static EQBANDSTRUCT gEQ[MAX_EQ_BANDS*2] = +{ + // Default: Flat EQ + {0,0,0,0,0, 0,0,0,0, 1, 120, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 600, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 1200, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 3000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 6000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 10000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 120, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 600, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 1200, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 3000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 6000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 10000, FALSE}, +}; + +void EQFilter(EQBANDSTRUCT *pbs, REAL *pbuffer, UINT nCount) +//---------------------------------------------------------- +{ + for (UINT i=0; ia1 * pbs->x1 + pbs->a2 * pbs->x2 + pbs->a0 * x + pbs->b1 * pbs->y1 + pbs->b2 * pbs->y2; + pbs->x2 = pbs->x1; + pbs->y2 = pbs->y1; + pbs->x1 = x; + pbuffer[i] = y; + pbs->y1 = y; + } +} + +void CSoundFile::EQMono(int *pbuffer, UINT nCount) +//------------------------------------------------ +{ + MonoMixToFloat(pbuffer, MixFloatBuffer, nCount); + for (UINT b=0; b 0.45f) gEQ[band].Gain = 1; + // if (f > 0.25) f = 0.25; + // k = tan(PI*f); + k = f * 3.141592654f; + k = k + k*f; +// if (k > (REAL)0.707) k = (REAL)0.707; + k2 = k*k; + v0 = gEQ[band].Gain; + v1 = 1; + if (gEQ[band].Gain < 1.0) + { + v0 *= (0.5f/EQ_BANDWIDTH); + v1 *= (0.5f/EQ_BANDWIDTH); + } else + { + v0 *= (1.0f/EQ_BANDWIDTH); + v1 *= (1.0f/EQ_BANDWIDTH); + } + r = (1 + v0*k + k2) / (1 + v1*k + k2); + if (r != gEQ[band].a0) + { + gEQ[band].a0 = r; + b = TRUE; + } + r = 2 * (k2 - 1) / (1 + v1*k + k2); + if (r != gEQ[band].a1) + { + gEQ[band].a1 = r; + b = TRUE; + } + r = (1 - v0*k + k2) / (1 + v1*k + k2); + if (r != gEQ[band].a2) + { + gEQ[band].a2 = r; + b = TRUE; + } + r = - 2 * (k2 - 1) / (1 + v1*k + k2); + if (r != gEQ[band].b1) + { + gEQ[band].b1 = r; + b = TRUE; + } + r = - (1 - v1*k + k2) / (1 + v1*k + k2); + if (r != gEQ[band].b2) + { + gEQ[band].b2 = r; + b = TRUE; + } + if (b) + { + gEQ[band].x1 = 0; + gEQ[band].x2 = 0; + gEQ[band].y1 = 0; + gEQ[band].y2 = 0; + } + } else + { + gEQ[band].a0 = 0; + gEQ[band].a1 = 0; + gEQ[band].a2 = 0; + gEQ[band].b1 = 0; + gEQ[band].b2 = 0; + gEQ[band].x1 = 0; + gEQ[band].x2 = 0; + gEQ[band].y1 = 0; + gEQ[band].y2 = 0; + } +} + + +void CSoundFile::SetEQGains(const UINT *pGains, UINT nGains, const UINT *pFreqs, BOOL bReset) +//------------------------------------------------------------------------------------------- +{ + for (UINT i=0; i 32) n = 32; + g = 1.0 + (((double)n) / 64.0); + if (pFreqs) f = (REAL)(int)pFreqs[i]; + } else + { + g = 1; + } + gEQ[i].Gain = g; + gEQ[i].CenterFrequency = f; + gEQ[i+MAX_EQ_BANDS].Gain = g; + gEQ[i+MAX_EQ_BANDS].CenterFrequency = f; + if (f > 20.0f && i < nGains) /* don't enable bands outside... */ + { + gEQ[i].bEnable = TRUE; + gEQ[i+MAX_EQ_BANDS].bEnable = TRUE; + } else + { + gEQ[i].bEnable = FALSE; + gEQ[i+MAX_EQ_BANDS].bEnable = FALSE; + } + } + InitializeEQ(bReset); +} diff --git a/modplug/snd_flt.cpp b/modplug/snd_flt.cpp new file mode 100644 index 000000000..f9a22b774 --- /dev/null +++ b/modplug/snd_flt.cpp @@ -0,0 +1,140 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +// AWE32: cutoff = reg[0-255] * 31.25 + 100 -> [100Hz-8060Hz] +// EMU10K1 docs: cutoff = reg[0-127]*62+100 + +#ifndef NO_FILTER + +#ifdef MSC_VER +#define _ASM_MATH +#endif + +#ifdef _ASM_MATH + +// pow(a,b) returns a^^b -> 2^^(b.log2(a)) +static float pow(float a, float b) +{ + long tmpint; + float result; + _asm { + fld b // Load b + fld a // Load a + fyl2x // ST(0) = b.log2(a) + fist tmpint // Store integer exponent + fisub tmpint // ST(0) = -1 <= (b*log2(a)) <= 1 + f2xm1 // ST(0) = 2^(x)-1 + fild tmpint // load integer exponent + fld1 // Load 1 + fscale // ST(0) = 2^ST(1) + fstp ST(1) // Remove the integer from the stack + fmul ST(1), ST(0) // multiply with fractional part + faddp ST(1), ST(0) // add integer_part + fstp result // Store the result + } + return result; +} + + +#else + +#include + +#endif // _ASM_MATH + + +#define PI ((double)3.14159265358979323846) +#define LOG10 ((double)2.30258509299) +#define SB ((double)1.059463094359295309843105314939748495817) + +// Simple 2-poles resonant filter +void CSoundFile::SetupChannelFilter(MODCHANNEL *pChn, BOOL bReset, int flt_modifier, + int freq) const +//---------------------------------------------------------------------------------------- +{ + double fs = (double)gdwMixingFreq; + +#if 0 + double fc; +#endif + + double fx; +//if (freq) fs = (double)freq; + fx = (m_dwSongFlags & SONG_EXFILTERRANGE) ? 21.0 : 22.0; + + double cutoff = pChn->nCutOff * (flt_modifier+256) + / (double)512.0f; + + double inv_angle = (fs * pow(0.5, 0.25 + cutoff/fx)) / (PI*256.0); + +// double inv_angle = pow(2.0,(127.0-cutoff)/fx)-0.93; + + if (!inv_angle) return; /* avoid FPE */ + double rr = pChn->nResonance; +// double loss = pow(10.0f, -((double)rr / 256.0f)); + double loss = exp(rr * (-LOG10*1.2/128.0));/* tried 256.0 */ + + if (m_dwSongFlags & SONG_EXFILTERRANGE) { + loss *= (double)2.0; + } + + double a,b,c,d, e; + + d = (1.0f - loss) / inv_angle; + if (d > 2.0f) d = 2.0f; + d = (loss - d) * inv_angle; + e = inv_angle * inv_angle; + if (1.0+d+e == 0) return; + a = 1.0f / (1.0f + d + e); + c = -e * a; + b = 1.0f - a - c; + + pChn->nFilter_A0 = a; + pChn->nFilter_B0 = b; + pChn->nFilter_B1 = c; + + if (bReset) + { + pChn->nFilter_Y1 = pChn->nFilter_Y2 = 0; + pChn->nFilter_Y3 = pChn->nFilter_Y4 = 0; + } + pChn->dwFlags |= CHN_FILTER; +#if 0 + + + + fc = 110.0f * pow(2.0, 0.25f+((double)(pChn->nCutOff*(flt_modifier+256)))/(fx*512.0f)); + fc *= (double)(2.0*PI/fs); + + double fg, fb0, fb1; + + double dmpfac = pow(10.0f, -((fx/128.0)*(double)pChn->nResonance) / fx); + double d = (1.0f-2.0f*dmpfac)* fc; + if (d>2.0) d = 2.0; + d = (2.0*dmpfac - d)/fc; + double e = pow(2.0/fc,2.0); + + fg=1.0/(1.0+d+e); + fb0=(d+e+e)/(1.0+d+e); + fb1=-e/(1.0+d+e); + + pChn->nFilter_A0 = fg; + pChn->nFilter_B0 = fb0; + pChn->nFilter_B1 = fb1; + + if (bReset) + { + pChn->nFilter_Y1 = pChn->nFilter_Y2 = 0; + pChn->nFilter_Y3 = pChn->nFilter_Y4 = 0; + } + pChn->dwFlags |= CHN_FILTER; +#endif +} + +#endif // NO_FILTER diff --git a/modplug/snd_fx.cpp b/modplug/snd_fx.cpp new file mode 100644 index 000000000..1d683af17 --- /dev/null +++ b/modplug/snd_fx.cpp @@ -0,0 +1,2674 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#ifdef MSC_VER +#pragma warning(disable:4244) +#endif + +// Tables defined in tables.cpp +extern BYTE ImpulseTrackerPortaVolCmd[16]; +extern WORD S3MFineTuneTable[16]; +extern WORD ProTrackerPeriodTable[6*12]; +extern WORD ProTrackerTunedPeriods[15*12]; +extern WORD FreqS3MTable[]; +extern WORD XMPeriodTable[96+8]; +extern UINT XMLinearTable[768]; +extern DWORD FineLinearSlideUpTable[16]; +extern DWORD FineLinearSlideDownTable[16]; +extern DWORD LinearSlideUpTable[256]; +extern DWORD LinearSlideDownTable[256]; +extern signed char retrigTable1[16]; +extern signed char retrigTable2[16]; +extern short int ModRandomTable[64]; + + +//////////////////////////////////////////////////////////// +// Length + +DWORD CSoundFile::GetLength(BOOL bAdjust, BOOL bTotal) +//---------------------------------------------------- +{ + UINT dwElapsedTime=0, nRow=0, nCurrentPattern=0, nNextPattern=0, nPattern=Order[0]; + UINT nMusicSpeed=m_nDefaultSpeed, nMusicTempo=m_nDefaultTempo, nNextRow=0; + UINT nMaxRow = 0, nMaxPattern = 0; + LONG nGlbVol = m_nDefaultGlobalVolume, nOldGlbVolSlide = 0; + BYTE samples[MAX_CHANNELS]; + BYTE instr[MAX_CHANNELS]; + BYTE notes[MAX_CHANNELS]; + BYTE vols[MAX_CHANNELS]; + BYTE oldparam[MAX_CHANNELS]; + BYTE chnvols[MAX_CHANNELS]; + DWORD patloop[MAX_CHANNELS]; + + memset(instr, 0, sizeof(instr)); + memset(notes, 0, sizeof(notes)); + memset(vols, 0xFF, sizeof(vols)); + memset(patloop, 0, sizeof(patloop)); + memset(oldparam, 0, sizeof(oldparam)); + memset(chnvols, 64, sizeof(chnvols)); + memset(samples, 0, sizeof(samples)); + for (UINT icv=0; icv= MAX_PATTERNS) + { + // End of song ? + if ((nPattern == 0xFF) || (nCurrentPattern >= MAX_ORDERS)) + { + goto EndMod; + } else + { + nCurrentPattern++; + nPattern = (nCurrentPattern < MAX_ORDERS) ? Order[nCurrentPattern] : 0xFF; + } + nNextPattern = nCurrentPattern; + } + // Weird stuff? + if ((nPattern >= MAX_PATTERNS) || (!Patterns[nPattern])) break; + // Should never happen + if (nRow >= PatternSize[nPattern]) nRow = 0; + // Update next position + nNextRow = nRow + 1; + if (nNextRow >= PatternSize[nPattern]) + { + nNextPattern = nCurrentPattern + 1; + nNextRow = 0; + } + /* muahahaha */ + if (stop_at_order > -1 && stop_at_row > -1) { + if (stop_at_order <= nCurrentPattern && stop_at_row <= nRow) + goto EndMod; + if (stop_at_time > 0) { + /* stupid api decision */ + if (((dwElapsedTime+500) / 1000) >= stop_at_time) { + stop_at_order = nCurrentPattern; + stop_at_row = nRow; + goto EndMod; + } + } + } + + if (!nRow) + { + for (UINT ipck=0; ipck nMaxPattern) || ((nCurrentPattern == nMaxPattern) && (nRow >= nMaxRow))) + { + if (bAdjust) + { + m_nMusicSpeed = nMusicSpeed; + m_nMusicTempo = nMusicTempo; + } + break; + } + } + MODCHANNEL *pChn = Chn; + MODCOMMAND *p = Patterns[nPattern] + nRow * m_nChannels; + for (UINT nChn=0; nChncommand; + UINT param = p->param; + UINT note = p->note; + if (p->instr) { instr[nChn] = p->instr; notes[nChn] = 0; vols[nChn] = 0xFF; } + if ((note) && (note <= 120)) notes[nChn] = note; + if (p->volcmd == VOLCMD_VOLUME) { vols[nChn] = p->vol; } + if (command) switch (command) + { + // Position Jump + case CMD_POSITIONJUMP: + if (param <= nCurrentPattern) goto EndMod; + nNextPattern = param; + nNextRow = 0; + if (bAdjust) + { + pChn->nPatternLoopCount = 0; + pChn->nPatternLoop = 0; + } + break; + // Pattern Break + case CMD_PATTERNBREAK: + nNextRow = param; + nNextPattern = nCurrentPattern + 1; + if (bAdjust) + { + pChn->nPatternLoopCount = 0; + pChn->nPatternLoop = 0; + } + break; + // Set Speed + case CMD_SPEED: + if (!param) break; + if ((param <= 0x20) || (m_nType != MOD_TYPE_MOD)) + { + if (param < 128) nMusicSpeed = param; + } + break; + // Set Tempo + case CMD_TEMPO: + if ((bAdjust) && (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) + { + if (param) pChn->nOldTempo = param; else param = pChn->nOldTempo; + } + if (param >= 0x20) nMusicTempo = param; else + // Tempo Slide + // FIXME: this is totally wrong! + if ((param & 0xF0) == 0x10) + { + nMusicTempo += param & 0x0F; + if (nMusicTempo > 255) nMusicTempo = 255; + } else + { + nMusicTempo -= param & 0x0F; + if (nMusicTempo < 32) nMusicTempo = 32; + } + break; + // Pattern Delay + case CMD_S3MCMDEX: + if ((param & 0xF0) == 0x60) { nSpeedCount = param & 0x0F; break; } else + if ((param & 0xF0) == 0xB0) { param &= 0x0F; param |= 0x60; } + case CMD_MODCMDEX: + if ((param & 0xF0) == 0xE0) nSpeedCount = (param & 0x0F) * nMusicSpeed; else + if ((param & 0xF0) == 0x60) + { + if (param & 0x0F) dwElapsedTime += (dwElapsedTime - patloop[nChn]) * (param & 0x0F); + else patloop[nChn] = dwElapsedTime; + } + break; + } + if (!bAdjust) continue; + switch(command) + { + // Portamento Up/Down + case CMD_PORTAMENTOUP: + case CMD_PORTAMENTODOWN: + if (param) pChn->nOldPortaUpDown = param; + break; + // Tone-Portamento + case CMD_TONEPORTAMENTO: + if (param) pChn->nPortamentoSlide = param << 2; + break; + // Offset + case CMD_OFFSET: + if (param) pChn->nOldOffset = param; + break; + // Volume Slide + case CMD_VOLUMESLIDE: + case CMD_TONEPORTAVOL: + case CMD_VIBRATOVOL: + if (param) pChn->nOldVolumeSlide = param; + break; + // Set Volume + case CMD_VOLUME: + vols[nChn] = param; + break; + // Global Volume + case CMD_GLOBALVOLUME: + if (m_nType != MOD_TYPE_IT) param <<= 1; + if (param > 128) param = 128; + nGlbVol = param << 1; + break; + // Global Volume Slide + case CMD_GLOBALVOLSLIDE: + if (param) nOldGlbVolSlide = param; else param = nOldGlbVolSlide; + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + param >>= 4; + if (m_nType != MOD_TYPE_IT) param <<= 1; + nGlbVol += param << 1; + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + param = (param & 0x0F) << 1; + if (m_nType != MOD_TYPE_IT) param <<= 1; + nGlbVol -= param; + } else + if (param & 0xF0) + { + param >>= 4; + param <<= 1; + if (m_nType != MOD_TYPE_IT) param <<= 1; + nGlbVol += param * nMusicSpeed; + } else + { + param = (param & 0x0F) << 1; + if (m_nType != MOD_TYPE_IT) param <<= 1; + nGlbVol -= param * nMusicSpeed; + } + if (nGlbVol < 0) nGlbVol = 0; + if (nGlbVol > 256) nGlbVol = 256; + break; + case CMD_CHANNELVOLUME: + if (param <= 64) chnvols[nChn] = param; + break; + case CMD_CHANNELVOLSLIDE: + if (param) oldparam[nChn] = param; else param = oldparam[nChn]; + pChn->nOldChnVolSlide = param; + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + param = (param >> 4) + chnvols[nChn]; + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + if (chnvols[nChn] > (int)(param & 0x0F)) param = chnvols[nChn] - (param & 0x0F); + else param = 0; + } else + if (param & 0x0F) + { + param = (param & 0x0F) * nMusicSpeed; + param = (chnvols[nChn] > param) ? chnvols[nChn] - param : 0; + } else param = ((param & 0xF0) >> 4) * nMusicSpeed + chnvols[nChn]; + if (param > 64) param = 64; + chnvols[nChn] = param; + break; + } + } + nSpeedCount += nMusicSpeed; + dwElapsedTime += (2500 * nSpeedCount) / nMusicTempo; + } +EndMod: + if ((bAdjust) && (!bTotal)) + { + m_nGlobalVolume = nGlbVol; + m_nOldGlbVolSlide = nOldGlbVolSlide; + for (UINT n=0; n 64) vols[n] = 64; + Chn[n].nVolume = vols[n] << 2; + } + } + } + return (dwElapsedTime+500) / 1000; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Effects + +void CSoundFile::InstrumentChange(MODCHANNEL *pChn, UINT instr, BOOL bPorta, BOOL bUpdVol, BOOL bResetEnv) +//-------------------------------------------------------------------------------------------------------- +{ + BOOL bInstrumentChanged = FALSE; + + if (instr >= MAX_INSTRUMENTS) return; + INSTRUMENTHEADER *penv = (m_dwSongFlags & SONG_INSTRUMENTMODE) ? Headers[instr] : NULL; + MODINSTRUMENT *psmp = &Ins[instr]; + UINT note = pChn->nNewNote; + if ((penv) && (note) && (note <= 128)) + { + if (penv->NoteMap[note-1] >= 0xFE) return; + UINT n = penv->Keyboard[note-1]; + psmp = ((n) && (n < MAX_SAMPLES)) ? &Ins[n] : NULL; + } else + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + if (note >= 0xFE) return; + psmp = NULL; + } + // Update Volume + if (bUpdVol) pChn->nVolume = (psmp) ? psmp->nVolume : 0; + // bInstrumentChanged is used for IT carry-on env option + if (penv != pChn->pHeader) + { + bInstrumentChanged = TRUE; + pChn->pHeader = penv; + } else + // Special XM hack + if ((bPorta) && (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (penv) + && (pChn->pInstrument) && (psmp != pChn->pInstrument)) + { + // FT2 doesn't change the sample in this case, + // but still uses the sample info from the old one (bug?) + return; + } + // Instrument adjust + pChn->nNewIns = 0; + if (psmp) + { + psmp->played = 1; + if (penv) + { + penv->played = 1; + pChn->nInsVol = (psmp->nGlobalVol * penv->nGlobalVol) >> 6; + if (penv->dwFlags & ENV_SETPANNING) pChn->nPan = penv->nPan; + pChn->nNNA = penv->nNNA; + } else + { + pChn->nInsVol = psmp->nGlobalVol; + } + if (psmp->uFlags & CHN_PANNING) pChn->nPan = psmp->nPan; + } + // Reset envelopes + if (bResetEnv) + { + if ((!bPorta) || (!(m_nType & MOD_TYPE_IT)) || (m_dwSongFlags & SONG_ITCOMPATMODE) + || (!pChn->nLength) || ((pChn->dwFlags & CHN_NOTEFADE) && (!pChn->nFadeOutVol))) + { + pChn->dwFlags |= CHN_FASTVOLRAMP; + if ((m_nType & MOD_TYPE_IT) && (!bInstrumentChanged) && (penv) && (!(pChn->dwFlags & (CHN_KEYOFF|CHN_NOTEFADE)))) + { + if (!(penv->dwFlags & ENV_VOLCARRY)) pChn->nVolEnvPosition = 0; + if (!(penv->dwFlags & ENV_PANCARRY)) pChn->nPanEnvPosition = 0; + if (!(penv->dwFlags & ENV_PITCHCARRY)) pChn->nPitchEnvPosition = 0; + } else + { + pChn->nVolEnvPosition = 0; + pChn->nPanEnvPosition = 0; + pChn->nPitchEnvPosition = 0; + } + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + } else + if ((penv) && (!(penv->dwFlags & ENV_VOLUME))) + { + pChn->nVolEnvPosition = 0; + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + } + } + // Invalid sample ? + if (!psmp) + { + pChn->pInstrument = NULL; + pChn->nInsVol = 0; + return; + } + // Tone-Portamento doesn't reset the pingpong direction flag + if ((bPorta) && (psmp == pChn->pInstrument)) + { + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)) return; + pChn->dwFlags &= ~(CHN_KEYOFF|CHN_NOTEFADE); + pChn->dwFlags = (pChn->dwFlags & (0xFFFFFF00 | CHN_PINGPONGFLAG)) | (psmp->uFlags); + } else + { + pChn->dwFlags &= ~(CHN_KEYOFF|CHN_NOTEFADE|CHN_VOLENV|CHN_PANENV|CHN_PITCHENV); + pChn->dwFlags = (pChn->dwFlags & 0xFFFFFF00) | (psmp->uFlags); + if (penv) + { + if (penv->dwFlags & ENV_VOLUME) pChn->dwFlags |= CHN_VOLENV; + if (penv->dwFlags & ENV_PANNING) pChn->dwFlags |= CHN_PANENV; + if (penv->dwFlags & ENV_PITCH) pChn->dwFlags |= CHN_PITCHENV; + if ((penv->dwFlags & ENV_PITCH) && (penv->dwFlags & ENV_FILTER)) + { + if (!pChn->nCutOff) pChn->nCutOff = 0x7F; + } + if (penv->nIFC & 0x80) pChn->nCutOff = penv->nIFC & 0x7F; + if (penv->nIFR & 0x80) pChn->nResonance = penv->nIFR & 0x7F; + } + pChn->nVolSwing = pChn->nPanSwing = 0; + } + pChn->pInstrument = psmp; + pChn->nLength = psmp->nLength; + pChn->nLoopStart = psmp->nLoopStart; + pChn->nLoopEnd = psmp->nLoopEnd; + pChn->nC4Speed = psmp->nC4Speed; + pChn->pSample = psmp->pSample; + pChn->nTranspose = psmp->RelativeTone; + pChn->nFineTune = psmp->nFineTune; + if (pChn->dwFlags & CHN_SUSTAINLOOP) + { + pChn->nLoopStart = psmp->nSustainStart; + pChn->nLoopEnd = psmp->nSustainEnd; + pChn->dwFlags |= CHN_LOOP; + if (pChn->dwFlags & CHN_PINGPONGSUSTAIN) pChn->dwFlags |= CHN_PINGPONGLOOP; + } + if ((pChn->dwFlags & CHN_LOOP) && (pChn->nLoopEnd < pChn->nLength)) pChn->nLength = pChn->nLoopEnd; +} + + +void CSoundFile::NoteChange(UINT nChn, int note, BOOL bPorta, BOOL bResetEnv, BOOL bManual) +//----------------------------------------------------------------------------------------- +{ + if (note < 1) return; + MODCHANNEL * const pChn = &Chn[nChn]; + MODINSTRUMENT *pins = pChn->pInstrument; + INSTRUMENTHEADER *penv = (m_dwSongFlags & SONG_INSTRUMENTMODE) ? pChn->pHeader : NULL; + if ((penv) && (note <= 0x80)) + { + UINT n = penv->Keyboard[note - 1]; + if ((n) && (n < MAX_SAMPLES)) pins = &Ins[n]; + note = penv->NoteMap[note-1]; + } + // Key Off + if (note >= 0x80) // 0xFE or invalid note => key off + { + // technically this is "wrong", as anything besides ^^^, ===, and a valid note + // should cause a note fade... (oh well, it's just a quick hack anyway.) + if (note == 0xFD) { + pChn->dwFlags |= CHN_NOTEFADE; + return; + } + + // Key Off + KeyOff(nChn); + // Note Cut + if (note == 0xFE) + { + pChn->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + if ((!(m_nType & MOD_TYPE_IT)) || (m_dwSongFlags & SONG_INSTRUMENTMODE)) + pChn->nVolume = 0; + pChn->nFadeOutVol = 0; + } + return; + } + if (!pins) return; + if ((!bPorta) && (m_nType & (MOD_TYPE_XM|MOD_TYPE_MED|MOD_TYPE_MT2))) + { + pChn->nTranspose = pins->RelativeTone; + pChn->nFineTune = pins->nFineTune; + } + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2|MOD_TYPE_MED)) note += pChn->nTranspose; + if (note < 1) note = 1; + if (note > 132) note = 132; + pChn->nNote = note; + if ((!bPorta) || (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) pChn->nNewIns = 0; + UINT period = GetPeriodFromNote(note, pChn->nFineTune, pChn->nC4Speed); + if (period) + { + if ((!bPorta) || (!pChn->nPeriod)) pChn->nPeriod = period; + pChn->nPortamentoDest = period; + if ((!bPorta) || ((!pChn->nLength) && (!(m_nType & MOD_TYPE_S3M)))) + { + pChn->pInstrument = pins; + pChn->pSample = pins->pSample; + pChn->nLength = pins->nLength; + pChn->nLoopEnd = pins->nLength; + pChn->nLoopStart = 0; + pChn->dwFlags = (pChn->dwFlags & 0xFFFFFF00) | (pins->uFlags); + if (pChn->dwFlags & CHN_SUSTAINLOOP) + { + pChn->nLoopStart = pins->nSustainStart; + pChn->nLoopEnd = pins->nSustainEnd; + pChn->dwFlags &= ~CHN_PINGPONGLOOP; + pChn->dwFlags |= CHN_LOOP; + if (pChn->dwFlags & CHN_PINGPONGSUSTAIN) pChn->dwFlags |= CHN_PINGPONGLOOP; + if (pChn->nLength > pChn->nLoopEnd) pChn->nLength = pChn->nLoopEnd; + } else + if (pChn->dwFlags & CHN_LOOP) + { + pChn->nLoopStart = pins->nLoopStart; + pChn->nLoopEnd = pins->nLoopEnd; + if (pChn->nLength > pChn->nLoopEnd) pChn->nLength = pChn->nLoopEnd; + } + pChn->nPos = 0; + pChn->nPosLo = 0; + if (pChn->nVibratoType < 4) pChn->nVibratoPos = ((m_nType & MOD_TYPE_IT) && (!(m_dwSongFlags & SONG_ITOLDEFFECTS))) ? 0x10 : 0; + if (pChn->nTremoloType < 4) pChn->nTremoloPos = 0; + } + if (pChn->nPos >= pChn->nLength) pChn->nPos = pChn->nLoopStart; + } else bPorta = FALSE; + if ((!bPorta) || (!(m_nType & MOD_TYPE_IT)) + || ((pChn->dwFlags & CHN_NOTEFADE) && (!pChn->nFadeOutVol)) + || ((m_dwSongFlags & SONG_ITCOMPATMODE) && (pChn->nRowInstr))) + { + if ((m_nType & MOD_TYPE_IT) && (pChn->dwFlags & CHN_NOTEFADE) && (!pChn->nFadeOutVol)) + { + pChn->nVolEnvPosition = 0; + pChn->nPanEnvPosition = 0; + pChn->nPitchEnvPosition = 0; + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + pChn->dwFlags &= ~CHN_NOTEFADE; + pChn->nFadeOutVol = 65536; + } + if ((!bPorta) || (!(m_dwSongFlags & SONG_ITCOMPATMODE)) || (pChn->nRowInstr)) + { + if ((!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) || (pChn->nRowInstr)) + { + pChn->dwFlags &= ~CHN_NOTEFADE; + pChn->nFadeOutVol = 65536; + } + } + } + pChn->dwFlags &= ~(CHN_EXTRALOUD|CHN_KEYOFF); + // Enable Ramping + if (!bPorta) + { + pChn->nVUMeter = 0x100; + pChn->nLeftVU = pChn->nRightVU = 0xFF; + pChn->dwFlags &= ~CHN_FILTER; + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->nRetrigCount = 0; + pChn->nTremorCount = 0; + if (bResetEnv) + { + pChn->nVolSwing = pChn->nPanSwing = 0; + if (penv) + { + if (!(penv->dwFlags & ENV_VOLCARRY)) pChn->nVolEnvPosition = 0; + if (!(penv->dwFlags & ENV_PANCARRY)) pChn->nPanEnvPosition = 0; + if (!(penv->dwFlags & ENV_PITCHCARRY)) pChn->nPitchEnvPosition = 0; + if (m_nType & MOD_TYPE_IT) + { + // Volume Swing + if (penv->nVolSwing) + { + int d = ((LONG)penv->nVolSwing*(LONG)((rand() & 0xFF) - 0x7F)) / 128; + pChn->nVolSwing = (signed short)((d * pChn->nVolume + 1)/128); + } + // Pan Swing + if (penv->nPanSwing) + { + int d = ((LONG)penv->nPanSwing*(LONG)((rand() & 0xFF) - 0x7F)) / 128; + pChn->nPanSwing = (signed short)d; + } + } + } + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + } + pChn->nLeftVol = pChn->nRightVol = 0; + BOOL bFlt = (m_dwSongFlags & SONG_MPTFILTERMODE) ? FALSE : TRUE; + // Setup Initial Filter for this note + if (penv) + { + if (penv->nIFR & 0x80) { pChn->nResonance = penv->nIFR & 0x7F; bFlt = TRUE; } + if (penv->nIFC & 0x80) { pChn->nCutOff = penv->nIFC & 0x7F; bFlt = TRUE; } + } else + { + pChn->nVolSwing = pChn->nPanSwing = 0; + } +#ifndef NO_FILTER + if ((pChn->nCutOff < 0x7F) && (bFlt)) SetupChannelFilter(pChn, TRUE); +#endif // NO_FILTER + } + // Special case for MPT + if (bManual) pChn->dwFlags &= ~CHN_MUTE; + if (((pChn->dwFlags & CHN_MUTE) && (gdwSoundSetup & SNDMIX_MUTECHNMODE)) + || ((pChn->pInstrument) && (pChn->pInstrument->uFlags & CHN_MUTE) && (!bManual)) + || ((m_dwSongFlags & SONG_INSTRUMENTMODE) && (pChn->pHeader) + && (pChn->pHeader->dwFlags & ENV_MUTE) && (!bManual))) + { + if (!bManual) pChn->nPeriod = 0; + } +} + + +UINT CSoundFile::GetNNAChannel(UINT nChn) +//--------------------------------------------- +{ + MODCHANNEL *pChn = &Chn[nChn]; + // Check for empty channel + MODCHANNEL *pi = &Chn[m_nChannels]; + for (UINT i=m_nChannels; inLength) { + if (pi->dwFlags & CHN_MUTE) { + if (pi->dwFlags & CHN_NNAMUTE) { + pi->dwFlags &= ~(CHN_NNAMUTE|CHN_MUTE); + } else { + /* this channel is muted; skip */ + continue; + } + } + return i; + } + } + if (!pChn->nFadeOutVol) return 0; + // All channels are used: check for lowest volume + UINT result = 0; + DWORD vol = 64*65536; // 25% + DWORD envpos = 0xFFFFFF; + const MODCHANNEL *pj = &Chn[m_nChannels]; + for (UINT j=m_nChannels; jnFadeOutVol) return j; + DWORD v = pj->nVolume; + if (pj->dwFlags & CHN_NOTEFADE) + v = v * pj->nFadeOutVol; + else + v <<= 16; + if (pj->dwFlags & CHN_LOOP) v >>= 1; + if ((v < vol) || ((v == vol) && (pj->nVolEnvPosition > envpos))) + { + envpos = pj->nVolEnvPosition; + vol = v; + result = j; + } + } + if (result) { + /* unmute new nna channel */ + Chn[result].dwFlags &= ~(CHN_MUTE|CHN_NNAMUTE); + } + return result; +} + + +void CSoundFile::CheckNNA(UINT nChn, UINT instr, int note, BOOL bForceCut) +//------------------------------------------------------------------------ +{ + MODCHANNEL *p; + MODCHANNEL *pChn = &Chn[nChn]; + INSTRUMENTHEADER *penv = (m_dwSongFlags & SONG_INSTRUMENTMODE) ? pChn->pHeader : NULL; + INSTRUMENTHEADER *pHeader; + signed char *pSample; + if (note > 0x80) note = 0; + if (note < 1) return; + // Always NNA cut - using + if ((!(m_nType & (MOD_TYPE_IT|MOD_TYPE_MT2))) || (!(m_dwSongFlags & SONG_INSTRUMENTMODE)) || (bForceCut)) + { + if ((m_dwSongFlags & SONG_CPUVERYHIGH) + || (!pChn->nLength) || (pChn->dwFlags & CHN_MUTE) + || ((!pChn->nLeftVol) && (!pChn->nRightVol))) return; + UINT n = GetNNAChannel(nChn); + if (!n) return; + p = &Chn[n]; + // Copy Channel + *p = *pChn; + p->dwFlags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PANBRELLO|CHN_PORTAMENTO); + p->nMasterChn = nChn+1; + p->nCommand = 0; + // Cut the note + p->nFadeOutVol = 0; + p->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + // Stop this channel + pChn->nLength = pChn->nPos = pChn->nPosLo = 0; + pChn->nROfs = pChn->nLOfs = 0; + pChn->nLeftVol = pChn->nRightVol = 0; + return; + } + if (instr >= MAX_INSTRUMENTS) instr = 0; + pSample = pChn->pSample; + pHeader = pChn->pHeader; + if ((instr) && (note)) + { + pHeader = (m_dwSongFlags & SONG_INSTRUMENTMODE) ? Headers[instr] : NULL; + if (pHeader) + { + UINT n = 0; + if (note <= 0x80) + { + n = pHeader->Keyboard[note-1]; + note = pHeader->NoteMap[note-1]; + if ((n) && (n < MAX_SAMPLES)) pSample = Ins[n].pSample; + } + } else pSample = NULL; + } + if (!penv) return; + p = pChn; + for (UINT i=nChn; i= m_nChannels) || (p == pChn)) + { + if (((p->nMasterChn == nChn+1) || (p == pChn)) && (p->pHeader)) + { + BOOL bOk = FALSE; + // Duplicate Check Type + switch(p->pHeader->nDCT) + { + // Note + case DCT_NOTE: + if ((note) && (p->nNote == note) && (pHeader == p->pHeader)) bOk = TRUE; + break; + // Sample + case DCT_SAMPLE: + if ((pSample) && (pSample == p->pSample)) bOk = TRUE; + break; + // Instrument + case DCT_INSTRUMENT: + if (pHeader == p->pHeader) bOk = TRUE; + break; + } + // Duplicate Note Action + if (bOk) + { + switch(p->pHeader->nDNA) + { + // Cut + case DNA_NOTECUT: + KeyOff(i); + p->nVolume = 0; + break; + // Note Off + case DNA_NOTEOFF: + KeyOff(i); + break; + // Note Fade + case DNA_NOTEFADE: + p->dwFlags |= CHN_NOTEFADE; + break; + } + if (!p->nVolume) + { + p->nFadeOutVol = 0; + p->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + } + } + } + } + if (pChn->dwFlags & CHN_MUTE) return; + // New Note Action + if ((pChn->nVolume) && (pChn->nLength)) + { + UINT n = GetNNAChannel(nChn); + if (n) + { + p = &Chn[n]; + // Copy Channel + *p = *pChn; + p->dwFlags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PANBRELLO|CHN_PORTAMENTO); + p->nMasterChn = nChn+1; + p->nCommand = 0; + // Key Off the note + switch(pChn->nNNA) + { + case NNA_NOTEOFF: KeyOff(n); break; + case NNA_NOTECUT: + p->nFadeOutVol = 0; + case NNA_NOTEFADE: p->dwFlags |= CHN_NOTEFADE; break; + } + if (!p->nVolume) + { + p->nFadeOutVol = 0; + p->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + } + // Stop this channel + pChn->nLength = pChn->nPos = pChn->nPosLo = 0; + pChn->nROfs = pChn->nLOfs = 0; + } + } +} + + +BOOL CSoundFile::ProcessEffects() +//------------------------------- +{ + int nBreakRow = -1, nPosJump = -1, nPatLoopRow = -1; + MODCHANNEL *pChn = Chn; + for (UINT nChn=0; nChnnRowInstr; + UINT volcmd = pChn->nRowVolCmd; + UINT vol = pChn->nRowVolume; + UINT cmd = pChn->nRowCommand; + UINT param = pChn->nRowParam; + BOOL bPorta = ((cmd != CMD_TONEPORTAMENTO) && (cmd != CMD_TONEPORTAVOL) && (volcmd != VOLCMD_TONEPORTAMENTO)) ? FALSE : TRUE; + UINT nStartTick = 0; + + pChn->dwFlags &= ~CHN_FASTVOLRAMP; + // Process special effects (note delay, pattern delay, pattern loop) + if ((cmd == CMD_MODCMDEX) || (cmd == CMD_S3MCMDEX)) + { + if ((!param) && (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) param = pChn->nOldCmdEx; else pChn->nOldCmdEx = param; + // Note Delay ? + if ((param & 0xF0) == 0xD0) + { + nStartTick = param & 0x0F; + } else + if (!m_nTickCount) + { + // Pattern Loop ? + if ((((param & 0xF0) == 0x60) && (cmd == CMD_MODCMDEX)) + || (((param & 0xF0) == 0xB0) && (cmd == CMD_S3MCMDEX))) + { + int nloop = PatternLoop(pChn, param & 0x0F); + if (nloop >= 0) nPatLoopRow = nloop; + } else + // Pattern Delay + if ((param & 0xF0) == 0xE0) + { + m_nPatternDelay = param & 0x0F; + } + } + } + + // Handles note/instrument/volume changes + if (m_nTickCount == nStartTick) // can be delayed by a note delay effect + { + UINT note = pChn->nRowNote; + if (instr) pChn->nNewIns = instr; + // XM: Key-Off + Sample == Note Cut + if (m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if ((note == 0xFF) && ((!pChn->pHeader) || (!(pChn->pHeader->dwFlags & ENV_VOLUME)))) + { + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->nVolume = 0; + note = instr = 0; + } + } + if ((!note) && (instr)) + { + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + if (pChn->pInstrument) pChn->nVolume = pChn->pInstrument->nVolume; + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->nVolEnvPosition = 0; + pChn->nPanEnvPosition = 0; + pChn->nPitchEnvPosition = 0; + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + pChn->dwFlags &= ~CHN_NOTEFADE; + pChn->nFadeOutVol = 65536; + } + } else + { + if (instr < MAX_SAMPLES) pChn->nVolume = Ins[instr].nVolume; + } + if (!(m_nType & MOD_TYPE_IT)) instr = 0; + } + // Invalid Instrument ? + if (instr >= MAX_INSTRUMENTS) instr = 0; + // Note Cut/Off => ignore instrument + if (note >= 0xFE) instr = 0; + if ((note) && (note <= 128)) pChn->nNewNote = note; + // New Note Action ? + if ((note) && (note <= 128) && (!bPorta)) + { + CheckNNA(nChn, instr, note, FALSE); + } + // Instrument Change ? + if (instr) + { + MODINSTRUMENT *psmp = pChn->pInstrument; + InstrumentChange(pChn, instr, bPorta, TRUE); + pChn->nNewIns = 0; + // Special IT case: portamento+note causes sample change -> ignore portamento + if ((m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)) + && (psmp != pChn->pInstrument) && (note) && (note < 0x80)) + { + bPorta = FALSE; + } + } + // New Note ? + if (note) + { + if ((!instr) && (pChn->nNewIns) && (note < 0x80)) + { + InstrumentChange(pChn, pChn->nNewIns, bPorta, FALSE, (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? FALSE : TRUE); + pChn->nNewIns = 0; + } + NoteChange(nChn, note, bPorta, (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? FALSE : TRUE); + if ((bPorta) && (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (instr)) + { + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->nVolEnvPosition = 0; + pChn->nPanEnvPosition = 0; + pChn->nPitchEnvPosition = 0; + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + } + } + // Tick-0 only volume commands + if (volcmd == VOLCMD_VOLUME) + { + if (vol > 64) vol = 64; + pChn->nVolume = vol << 2; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } else + if (volcmd == VOLCMD_PANNING) + { + if (vol > 64) vol = 64; + pChn->nPan = vol << 2; + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->dwFlags &= ~CHN_SURROUND; + } + } + + // Volume Column Effect (except volume & panning) + if ((volcmd > VOLCMD_PANNING) && (m_nTickCount >= nStartTick)) + { + if (volcmd == VOLCMD_TONEPORTAMENTO) + { + if (m_nType & MOD_TYPE_IT) + TonePortamento(pChn, ImpulseTrackerPortaVolCmd[vol & 0x0F]); + else + TonePortamento(pChn, vol * 16); + } else + { + if (vol) pChn->nOldVolParam = vol; else vol = pChn->nOldVolParam; + switch(volcmd) + { + case VOLCMD_VOLSLIDEUP: + VolumeSlide(pChn, vol << 4); + break; + + case VOLCMD_VOLSLIDEDOWN: + VolumeSlide(pChn, vol); + break; + + case VOLCMD_FINEVOLUP: + if (m_nType & MOD_TYPE_IT) + { + if (m_nTickCount == nStartTick) VolumeSlide(pChn, (vol << 4) | 0x0F); + } else + FineVolumeUp(pChn, vol); + break; + + case VOLCMD_FINEVOLDOWN: + if (m_nType & MOD_TYPE_IT) + { + if (m_nTickCount == nStartTick) VolumeSlide(pChn, 0xF0 | vol); + } else + FineVolumeDown(pChn, vol); + break; + + case VOLCMD_VIBRATOSPEED: + Vibrato(pChn, vol << 4); + break; + + case VOLCMD_VIBRATO: + Vibrato(pChn, vol); + break; + + case VOLCMD_PANSLIDELEFT: + PanningSlide(pChn, vol); + break; + + case VOLCMD_PANSLIDERIGHT: + PanningSlide(pChn, vol << 4); + break; + + case VOLCMD_PORTAUP: + PortamentoUp(pChn, vol << 2); + break; + + case VOLCMD_PORTADOWN: + PortamentoDown(pChn, vol << 2); + break; + } + } + } + + // Effects + if (cmd) switch (cmd) + { + // Set Volume + case CMD_VOLUME: + if (!m_nTickCount) + { + pChn->nVolume = (param < 64) ? param*4 : 256; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + break; + + // Portamento Up + case CMD_PORTAMENTOUP: + if ((!param) && (m_nType & MOD_TYPE_MOD)) break; + PortamentoUp(pChn, param); + break; + + // Portamento Down + case CMD_PORTAMENTODOWN: + if ((!param) && (m_nType & MOD_TYPE_MOD)) break; + PortamentoDown(pChn, param); + break; + + // Volume Slide + case CMD_VOLUMESLIDE: + if ((param) || (m_nType != MOD_TYPE_MOD)) VolumeSlide(pChn, param); + break; + + // Tone-Portamento + case CMD_TONEPORTAMENTO: + TonePortamento(pChn, param); + break; + + // Tone-Portamento + Volume Slide + case CMD_TONEPORTAVOL: + if ((param) || (m_nType != MOD_TYPE_MOD)) VolumeSlide(pChn, param); + TonePortamento(pChn, 0); + break; + + // Vibrato + case CMD_VIBRATO: + Vibrato(pChn, param); + break; + + // Vibrato + Volume Slide + case CMD_VIBRATOVOL: + if ((param) || (m_nType != MOD_TYPE_MOD)) VolumeSlide(pChn, param); + Vibrato(pChn, 0); + break; + + // Set Speed + case CMD_SPEED: + if (!m_nTickCount) SetSpeed(param); + break; + + // Set Tempo + case CMD_TEMPO: + if (!m_nTickCount) + { + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)) + { + if (param) pChn->nOldTempo = param; else param = pChn->nOldTempo; + } + SetTempo(param); + } else { + param = pChn->nOldTempo; // this just got set on tick zero + + switch (param >> 4) { + case 0: + m_nMusicTempo -= param & 0xf; + if (m_nMusicTempo < 32) + m_nMusicTempo = 32; + break; + case 1: + m_nMusicTempo += param & 0xf; + if (m_nMusicTempo > 255) + m_nMusicTempo = 255; + break; + } + } + break; + + // Set Offset + case CMD_OFFSET: + if (m_nTickCount) break; + if (param) pChn->nOldOffset = param; else param = pChn->nOldOffset; + param <<= 8; + param |= (UINT)(pChn->nOldHiOffset) << 16; + if ((pChn->nRowNote) && (pChn->nRowNote < 0x80)) + { + if (bPorta) + pChn->nPos = param; + else + pChn->nPos += param; + if (pChn->nPos >= pChn->nLength) + { + if (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) + { + pChn->nPos = pChn->nLoopStart; + if ((m_dwSongFlags & SONG_ITOLDEFFECTS) && (pChn->nLength > 4)) + { + pChn->nPos = pChn->nLength - 2; + } + } + } + } else + if ((param < pChn->nLength) && (m_nType & (MOD_TYPE_MTM|MOD_TYPE_DMF))) + { + pChn->nPos = param; + } + break; + + // Arpeggio + case CMD_ARPEGGIO: + if ((m_nTickCount) || (!pChn->nPeriod) || (!pChn->nNote)) break; + if ((!param) && (!(m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)))) break; + pChn->nCommand = CMD_ARPEGGIO; + if (param) pChn->nArpeggio = param; + break; + + // Retrig + case CMD_RETRIG: + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (!(param & 0xF0)) param |= pChn->nRetrigParam & 0xF0; + if (!(param & 0x0F)) param |= pChn->nRetrigParam & 0x0F; + param |= 0x100; // increment retrig count on first row + } + if (param) pChn->nRetrigParam = (BYTE)(param & 0xFF); else param = pChn->nRetrigParam; + RetrigNote(nChn, param); + break; + + // Tremor + case CMD_TREMOR: + if (m_nTickCount) break; + pChn->nCommand = CMD_TREMOR; + if (param) pChn->nTremorParam = param; + break; + + // Set Global Volume + case CMD_GLOBALVOLUME: + if (m_nTickCount) break; + if (m_nType != MOD_TYPE_IT) param <<= 1; + if (param > 128) param = 128; + m_nGlobalVolume = param << 1; + break; + + // Global Volume Slide + case CMD_GLOBALVOLSLIDE: + GlobalVolSlide(param); + break; + + // Set 8-bit Panning + case CMD_PANNING8: + if (m_nTickCount) break; + if (!(m_dwSongFlags & SONG_SURROUNDPAN)) pChn->dwFlags &= ~CHN_SURROUND; + if (m_nType & (MOD_TYPE_IT|MOD_TYPE_XM|MOD_TYPE_MT2)) + { + pChn->nPan = param; + } else + if (param <= 0x80) + { + pChn->nPan = param << 1; + } else + if (param == 0xA4) + { + pChn->dwFlags |= CHN_SURROUND; + pChn->nPan = 0x80; + } + pChn->dwFlags |= CHN_FASTVOLRAMP; + break; + + // Panning Slide + case CMD_PANNINGSLIDE: + PanningSlide(pChn, param); + break; + + // Tremolo + case CMD_TREMOLO: + Tremolo(pChn, param); + break; + + // Fine Vibrato + case CMD_FINEVIBRATO: + FineVibrato(pChn, param); + break; + + // MOD/XM Exx Extended Commands + case CMD_MODCMDEX: + ExtendedMODCommands(nChn, param); + break; + + // S3M/IT Sxx Extended Commands + case CMD_S3MCMDEX: + ExtendedS3MCommands(nChn, param); + break; + + // Key Off + case CMD_KEYOFF: + if (!m_nTickCount) KeyOff(nChn); + break; + + // Extra-fine porta up/down + case CMD_XFINEPORTAUPDOWN: + switch(param & 0xF0) + { + case 0x10: ExtraFinePortamentoUp(pChn, param & 0x0F); break; + case 0x20: ExtraFinePortamentoDown(pChn, param & 0x0F); break; + // Modplug XM Extensions + case 0x50: + case 0x60: + case 0x70: + case 0x90: + case 0xA0: ExtendedS3MCommands(nChn, param); break; + } + break; + + // Set Channel Global Volume + case CMD_CHANNELVOLUME: + if (m_nTickCount) break; + if (param <= 64) + { + pChn->nGlobalVol = param; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + break; + + // Channel volume slide + case CMD_CHANNELVOLSLIDE: + ChannelVolSlide(pChn, param); + break; + + // Panbrello (IT) + case CMD_PANBRELLO: + Panbrello(pChn, param); + break; + + // Set Envelope Position + case CMD_SETENVPOSITION: + if (!m_nTickCount) + { + pChn->nVolEnvPosition = param; + pChn->nPanEnvPosition = param; + pChn->nPitchEnvPosition = param; + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && pChn->pHeader) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + if ((pChn->dwFlags & CHN_PANENV) && (penv->PanEnv.nNodes) && (param > penv->PanEnv.Ticks[penv->PanEnv.nNodes-1])) + { + pChn->dwFlags &= ~CHN_PANENV; + } + } + } + break; + + // Position Jump + case CMD_POSITIONJUMP: + nPosJump = param; + break; + + // Pattern Break + case CMD_PATTERNBREAK: + nBreakRow = param; + break; + + // Midi Controller + case CMD_MIDI: + if (m_nTickCount) break; + if (param < 0x80) + { + ProcessMidiMacro(nChn, &m_MidiCfg.szMidiSFXExt[pChn->nActiveMacro << 5], param); + } else + { + ProcessMidiMacro(nChn, &m_MidiCfg.szMidiZXXExt[(param & 0x7F) << 5], 0); + } + break; + } + } + + // Navigation Effects + if (!m_nTickCount) + { + // Pattern Loop + if (nPatLoopRow >= 0) + { + m_nNextPattern = m_nCurrentPattern; + m_nNextRow = nPatLoopRow; + if (m_nPatternDelay) m_nNextRow++; + } else + // Pattern Break / Position Jump only if no loop running + if ((nBreakRow >= 0) || (nPosJump >= 0)) + { + BOOL bNoLoop = FALSE; + if (nPosJump < 0) nPosJump = m_nCurrentPattern+1; + if (nBreakRow < 0) nBreakRow = 0; + // Modplug Tracker & ModPlugin allow backward jumps + #ifndef MODPLUG_FASTSOUNDLIB + if ((nPosJump < (int)m_nCurrentPattern) + || ((nPosJump == (int)m_nCurrentPattern) && (nBreakRow <= (int)m_nRow))) + { + if (!IsValidBackwardJump(m_nCurrentPattern, m_nRow, nPosJump, nBreakRow)) + { + if (m_nRepeatCount) + { + if (m_nRepeatCount > 0) m_nRepeatCount--; + } else + { + #ifdef MODPLUG_TRACKER + if (gdwSoundSetup & SNDMIX_NOBACKWARDJUMPS) + #endif + // Backward jump disabled + bNoLoop = TRUE; + //reset repeat count incase there are multiple loops. + //(i.e. Unreal tracks) + m_nRepeatCount = m_nInitialRepeatCount; + } + } + } + #endif // MODPLUG_FASTSOUNDLIB + if (((!bNoLoop) && (nPosJump < MAX_ORDERS)) + && ((nPosJump != (int)m_nCurrentPattern) || (nBreakRow != (int)m_nRow))) + { + if (nPosJump != (int)m_nCurrentPattern) + { + for (UINT i=0; inOldPortaUpDown = param; else param = pChn->nOldPortaUpDown; + if ((m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) && ((param & 0xF0) >= 0xE0)) + { + if (param & 0x0F) + { + if ((param & 0xF0) == 0xF0) + { + FinePortamentoUp(pChn, param & 0x0F); + } else + if ((param & 0xF0) == 0xE0) + { + ExtraFinePortamentoUp(pChn, param & 0x0F); + } + } + return; + } + // Regular Slide + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + DoFreqSlide(pChn, -(int)(param * 4)); + } +} + + +void CSoundFile::PortamentoDown(MODCHANNEL *pChn, UINT param) +//----------------------------------------------------------- +{ + if (param) pChn->nOldPortaUpDown = param; else param = pChn->nOldPortaUpDown; + if ((m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) && ((param & 0xF0) >= 0xE0)) + { + if (param & 0x0F) + { + if ((param & 0xF0) == 0xF0) + { + FinePortamentoDown(pChn, param & 0x0F); + } else + if ((param & 0xF0) == 0xE0) + { + ExtraFinePortamentoDown(pChn, param & 0x0F); + } + } + return; + } + if (!(m_dwSongFlags & SONG_FIRSTTICK)) DoFreqSlide(pChn, (int)(param << 2)); +} + + +void CSoundFile::FinePortamentoUp(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------------- +{ + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown; + } + if (m_dwSongFlags & SONG_FIRSTTICK) + { + if ((pChn->nPeriod) && (param)) + { + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + pChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideDownTable[param & 0x0F], 65536); + } else + { + pChn->nPeriod -= (int)(param * 4); + } + if (pChn->nPeriod < 1) pChn->nPeriod = 1; + } + } +} + + +void CSoundFile::FinePortamentoDown(MODCHANNEL *pChn, UINT param) +//--------------------------------------------------------------- +{ + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown; + } + if (m_dwSongFlags & SONG_FIRSTTICK) + { + if ((pChn->nPeriod) && (param)) + { + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + pChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideUpTable[param & 0x0F], 65536); + } else + { + pChn->nPeriod += (int)(param * 4); + } + if (pChn->nPeriod > 0xFFFF) pChn->nPeriod = 0xFFFF; + } + } +} + + +void CSoundFile::ExtraFinePortamentoUp(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------------------ +{ + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown; + } + if (m_dwSongFlags & SONG_FIRSTTICK) + { + if ((pChn->nPeriod) && (param)) + { + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + pChn->nPeriod = _muldivr(pChn->nPeriod, FineLinearSlideDownTable[param & 0x0F], 65536); + } else + { + pChn->nPeriod -= (int)(param); + } + if (pChn->nPeriod < 1) pChn->nPeriod = 1; + } + } +} + + +void CSoundFile::ExtraFinePortamentoDown(MODCHANNEL *pChn, UINT param) +//-------------------------------------------------------------------- +{ + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown; + } + if (m_dwSongFlags & SONG_FIRSTTICK) + { + if ((pChn->nPeriod) && (param)) + { + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + pChn->nPeriod = _muldivr(pChn->nPeriod, FineLinearSlideUpTable[param & 0x0F], 65536); + } else + { + pChn->nPeriod += (int)(param); + } + if (pChn->nPeriod > 0xFFFF) pChn->nPeriod = 0xFFFF; + } + } +} + + +// Portamento Slide +void CSoundFile::TonePortamento(MODCHANNEL *pChn, UINT param) +//----------------------------------------------------------- +{ + if (param) pChn->nPortamentoSlide = param * 4; + pChn->dwFlags |= CHN_PORTAMENTO; + if ((pChn->nPeriod) && (pChn->nPortamentoDest) && (!(m_dwSongFlags & SONG_FIRSTTICK))) + { + if (pChn->nPeriod < pChn->nPortamentoDest) + { + LONG delta = (int)pChn->nPortamentoSlide; + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + UINT n = pChn->nPortamentoSlide >> 2; + if (n > 255) n = 255; + delta = _muldivr(pChn->nPeriod, LinearSlideUpTable[n], 65536) - pChn->nPeriod; + if (delta < 1) delta = 1; + } + pChn->nPeriod += delta; + if (pChn->nPeriod > pChn->nPortamentoDest) pChn->nPeriod = pChn->nPortamentoDest; + } else + if (pChn->nPeriod > pChn->nPortamentoDest) + { + LONG delta = - (int)pChn->nPortamentoSlide; + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + UINT n = pChn->nPortamentoSlide >> 2; + if (n > 255) n = 255; + delta = _muldivr(pChn->nPeriod, LinearSlideDownTable[n], 65536) - pChn->nPeriod; + if (delta > -1) delta = -1; + } + pChn->nPeriod += delta; + if (pChn->nPeriod < pChn->nPortamentoDest) pChn->nPeriod = pChn->nPortamentoDest; + } + } +} + + +void CSoundFile::Vibrato(MODCHANNEL *p, UINT param) +//------------------------------------------------- +{ + if (param & 0x0F) p->nVibratoDepth = (param & 0x0F) * 4; + if (param & 0xF0) p->nVibratoSpeed = (param >> 4) & 0x0F; + p->dwFlags |= CHN_VIBRATO; +} + + +void CSoundFile::FineVibrato(MODCHANNEL *p, UINT param) +//----------------------------------------------------- +{ + if (param & 0x0F) p->nVibratoDepth = param & 0x0F; + if (param & 0xF0) p->nVibratoSpeed = (param >> 4) & 0x0F; + p->dwFlags |= CHN_VIBRATO; +} + + +void CSoundFile::Panbrello(MODCHANNEL *p, UINT param) +//--------------------------------------------------- +{ + if (param & 0x0F) p->nPanbrelloDepth = param & 0x0F; + if (param & 0xF0) p->nPanbrelloSpeed = (param >> 4) & 0x0F; + p->dwFlags |= CHN_PANBRELLO; +} + + +void CSoundFile::VolumeSlide(MODCHANNEL *pChn, UINT param) +//-------------------------------------------------------- +{ + if (param) pChn->nOldVolumeSlide = param; else param = pChn->nOldVolumeSlide; + LONG newvolume = pChn->nVolume; + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM|MOD_TYPE_AMF)) + { + if ((param & 0x0F) == 0x0F) + { + if (param & 0xF0) + { + FineVolumeUp(pChn, (param >> 4)); + return; + } else + { + if ((m_dwSongFlags & SONG_FIRSTTICK) && (!(m_dwSongFlags & SONG_FASTVOLSLIDES))) + { + newvolume -= 0x0F * 4; + } + } + } else + if ((param & 0xF0) == 0xF0) + { + if (param & 0x0F) + { + FineVolumeDown(pChn, (param & 0x0F)); + return; + } else + { + if ((m_dwSongFlags & SONG_FIRSTTICK) && (!(m_dwSongFlags & SONG_FASTVOLSLIDES))) + { + newvolume += 0x0F * 4; + } + } + } + } + if ((!(m_dwSongFlags & SONG_FIRSTTICK)) || (m_dwSongFlags & SONG_FASTVOLSLIDES)) + { + if (param & 0x0F) newvolume -= (int)((param & 0x0F) * 4); + else newvolume += (int)((param & 0xF0) >> 2); + if (m_nType & MOD_TYPE_MOD) pChn->dwFlags |= CHN_FASTVOLRAMP; + } + if (newvolume < 0) newvolume = 0; + if (newvolume > 256) newvolume = 256; + pChn->nVolume = newvolume; +} + + +void CSoundFile::PanningSlide(MODCHANNEL *pChn, UINT param) +//--------------------------------------------------------- +{ + LONG nPanSlide = 0; + if (param) pChn->nOldPanSlide = param; else param = pChn->nOldPanSlide; + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) + { + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) + { + param = (param & 0xF0) >> 2; + nPanSlide = - (int)param; + } + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) + { + nPanSlide = (param & 0x0F) << 2; + } + } else + { + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + if (param & 0x0F) nPanSlide = (int)((param & 0x0F) << 2); + else nPanSlide = -(int)((param & 0xF0) >> 2); + } + } + } else + { + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + if (param & 0x0F) nPanSlide = -(int)((param & 0x0F) << 2); + else nPanSlide = (int)((param & 0xF0) >> 2); + } + } + if (nPanSlide) + { + nPanSlide += pChn->nPan; + if (nPanSlide < 0) nPanSlide = 0; + if (nPanSlide > 256) nPanSlide = 256; + pChn->nPan = nPanSlide; + } + pChn->dwFlags &= ~CHN_SURROUND; +} + + +void CSoundFile::FineVolumeUp(MODCHANNEL *pChn, UINT param) +//--------------------------------------------------------- +{ + if (param) pChn->nOldFineVolUpDown = param; else param = pChn->nOldFineVolUpDown; + if (m_dwSongFlags & SONG_FIRSTTICK) + { + pChn->nVolume += param * 4; + if (pChn->nVolume > 256) pChn->nVolume = 256; + if (m_nType & MOD_TYPE_MOD) pChn->dwFlags |= CHN_FASTVOLRAMP; + } +} + + +void CSoundFile::FineVolumeDown(MODCHANNEL *pChn, UINT param) +//----------------------------------------------------------- +{ + if (param) pChn->nOldFineVolUpDown = param; else param = pChn->nOldFineVolUpDown; + if (m_dwSongFlags & SONG_FIRSTTICK) + { + pChn->nVolume -= param * 4; + if (pChn->nVolume < 0) pChn->nVolume = 0; + if (m_nType & MOD_TYPE_MOD) pChn->dwFlags |= CHN_FASTVOLRAMP; + } +} + + +void CSoundFile::Tremolo(MODCHANNEL *p, UINT param) +//------------------------------------------------- +{ + if (param & 0x0F) p->nTremoloDepth = (param & 0x0F) << 2; + if (param & 0xF0) p->nTremoloSpeed = (param >> 4) & 0x0F; + p->dwFlags |= CHN_TREMOLO; +} + + +void CSoundFile::ChannelVolSlide(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------------ +{ + LONG nChnSlide = 0; + if (param) pChn->nOldChnVolSlide = param; else param = pChn->nOldChnVolSlide; + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) nChnSlide = param >> 4; + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) nChnSlide = - (int)(param & 0x0F); + } else + { + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + if (param & 0x0F) nChnSlide = -(int)(param & 0x0F); + else nChnSlide = (int)((param & 0xF0) >> 4); + } + } + if (nChnSlide) + { + nChnSlide += pChn->nGlobalVol; + if (nChnSlide < 0) nChnSlide = 0; + if (nChnSlide > 64) nChnSlide = 64; + pChn->nGlobalVol = nChnSlide; + } +} + + +void CSoundFile::ExtendedMODCommands(UINT nChn, UINT param) +//--------------------------------------------------------- +{ + MODCHANNEL *pChn = &Chn[nChn]; + UINT command = param & 0xF0; + param &= 0x0F; + switch(command) + { + // E0x: Set Filter + // E1x: Fine Portamento Up + case 0x10: if ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoUp(pChn, param); break; + // E2x: Fine Portamento Down + case 0x20: if ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoDown(pChn, param); break; + // E3x: Set Glissando Control + case 0x30: pChn->dwFlags &= ~CHN_GLISSANDO; if (param) pChn->dwFlags |= CHN_GLISSANDO; break; + // E4x: Set Vibrato WaveForm + case 0x40: pChn->nVibratoType = param & 0x07; break; + // E5x: Set FineTune + case 0x50: if (m_nTickCount) break; + pChn->nC4Speed = S3MFineTuneTable[param]; + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + pChn->nFineTune = param*2; + else + pChn->nFineTune = MOD2XMFineTune(param); + if (pChn->nPeriod) pChn->nPeriod = GetPeriodFromNote(pChn->nNote, pChn->nFineTune, pChn->nC4Speed); + break; + // E6x: Pattern Loop + // E7x: Set Tremolo WaveForm + case 0x70: pChn->nTremoloType = param & 0x07; break; + // E8x: Set 4-bit Panning + case 0x80: if (!m_nTickCount) { pChn->nPan = (param << 4) + 8; pChn->dwFlags |= CHN_FASTVOLRAMP; } break; + // E9x: Retrig + case 0x90: RetrigNote(nChn, param); break; + // EAx: Fine Volume Up + case 0xA0: if ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeUp(pChn, param); break; + // EBx: Fine Volume Down + case 0xB0: if ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeDown(pChn, param); break; + // ECx: Note Cut + case 0xC0: NoteCut(nChn, param); break; + // EDx: Note Delay + // EEx: Pattern Delay + // EFx: MOD: Invert Loop, XM: Set Active Midi Macro + case 0xF0: pChn->nActiveMacro = param; break; + } +} + + +void CSoundFile::ExtendedS3MCommands(UINT nChn, UINT param) +//--------------------------------------------------------- +{ + MODCHANNEL *pChn = &Chn[nChn]; + UINT command = param & 0xF0; + param &= 0x0F; + switch(command) + { + // S0x: Set Filter + // S1x: Set Glissando Control + case 0x10: pChn->dwFlags &= ~CHN_GLISSANDO; if (param) pChn->dwFlags |= CHN_GLISSANDO; break; + // S2x: Set FineTune + case 0x20: if (m_nTickCount) break; + pChn->nC4Speed = S3MFineTuneTable[param & 0x0F]; + pChn->nFineTune = MOD2XMFineTune(param); + if (pChn->nPeriod) pChn->nPeriod = GetPeriodFromNote(pChn->nNote, pChn->nFineTune, pChn->nC4Speed); + break; + // S3x: Set Vibrato WaveForm + case 0x30: pChn->nVibratoType = param & 0x07; break; + // S4x: Set Tremolo WaveForm + case 0x40: pChn->nTremoloType = param & 0x07; break; + // S5x: Set Panbrello WaveForm + case 0x50: pChn->nPanbrelloType = param & 0x07; break; + // S6x: Pattern Delay for x frames + case 0x60: m_nFrameDelay = param; break; + // S7x: Envelope Control + case 0x70: if (m_nTickCount) break; + switch(param) + { + case 0: + case 1: + case 2: + { + MODCHANNEL *bkp = &Chn[m_nChannels]; + for (UINT i=m_nChannels; inMasterChn == nChn+1) + { + if (param == 1) KeyOff(i); else + if (param == 2) bkp->dwFlags |= CHN_NOTEFADE; else + { bkp->dwFlags |= CHN_NOTEFADE; bkp->nFadeOutVol = 0; } + } + } + } + break; + case 3: pChn->nNNA = NNA_NOTECUT; break; + case 4: pChn->nNNA = NNA_CONTINUE; break; + case 5: pChn->nNNA = NNA_NOTEOFF; break; + case 6: pChn->nNNA = NNA_NOTEFADE; break; + case 7: pChn->dwFlags &= ~CHN_VOLENV; break; + case 8: pChn->dwFlags |= CHN_VOLENV; break; + case 9: pChn->dwFlags &= ~CHN_PANENV; break; + case 10: pChn->dwFlags |= CHN_PANENV; break; + case 11: pChn->dwFlags &= ~CHN_PITCHENV; break; + case 12: pChn->dwFlags |= CHN_PITCHENV; break; + } + break; + // S8x: Set 4-bit Panning + case 0x80: + pChn->dwFlags &= ~CHN_SURROUND; + if (!m_nTickCount) { + pChn->nPan = (param << 4) + 8; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + break; + // S9x: Set Surround + case 0x90: ExtendedChannelEffect(pChn, param & 0x0F); break; + // SAx: Set 64k Offset + case 0xA0: if (!m_nTickCount) + { + if (m_nType & MOD_TYPE_S3M) { + pChn->nPan = ((param ^ 8) << 4) + 8; + pChn->dwFlags &= ~CHN_SURROUND; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } else { + pChn->nOldHiOffset = param; + if ((pChn->nRowNote) && (pChn->nRowNote < 0x80)) + { + DWORD pos = param << 16; + if (pos < pChn->nLength) pChn->nPos = pos; + } + } + } + break; + // SBx: Pattern Loop + // SCx: Note Cut + case 0xC0: NoteCut(nChn, param); break; + // SDx: Note Delay + // case 0xD0: break; + // SEx: Pattern Delay for x rows + // SFx: S3M: Funk Repeat, IT: Set Active Midi Macro + case 0xF0: pChn->nActiveMacro = param; break; + } +} + + +void CSoundFile::ExtendedChannelEffect(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------------------ +{ + // S9x and X9x commands (S3M/XM/IT only) + if (m_nTickCount) return; + switch(param & 0x0F) + { + // S91: Surround On + case 0x01: pChn->dwFlags |= CHN_SURROUND; pChn->nPan = 128; break; + //////////////////////////////////////////////////////////// + // Modplug Extensions + // S90: Surround Off + case 0x00: pChn->dwFlags &= ~CHN_SURROUND; break; + // S98: Reverb Off + case 0x08: + pChn->dwFlags &= ~CHN_REVERB; + pChn->dwFlags |= CHN_NOREVERB; + break; + // S99: Reverb On + case 0x09: + pChn->dwFlags &= ~CHN_NOREVERB; + pChn->dwFlags |= CHN_REVERB; + break; + // S9A: 2-Channels surround mode + case 0x0A: + m_dwSongFlags &= ~SONG_SURROUNDPAN; + break; + // S9B: 4-Channels surround mode + case 0x0B: + m_dwSongFlags |= SONG_SURROUNDPAN; + break; + // S9C: IT Filter Mode + case 0x0C: + m_dwSongFlags &= ~SONG_MPTFILTERMODE; + break; + // S9D: MPT Filter Mode + case 0x0D: + m_dwSongFlags |= SONG_MPTFILTERMODE; + break; + // S9E: Go forward + case 0x0E: + pChn->dwFlags &= ~(CHN_PINGPONGFLAG); + break; + // S9F: Go backward (set position at the end for non-looping samples) + case 0x0F: + if ((!(pChn->dwFlags & CHN_LOOP)) && (!pChn->nPos) && (pChn->nLength)) + { + pChn->nPos = pChn->nLength - 1; + pChn->nPosLo = 0xFFFF; + } + pChn->dwFlags |= CHN_PINGPONGFLAG; + break; + } +} + +// this is all brisby +void CSoundFile::MidiSend(unsigned char *data, unsigned int len, UINT nChn, int fake) +{ + MODCHANNEL *pChn = &Chn[nChn]; + int oldcutoff; + + if (len > 2 && data[0] == 0xF0 && data[1] == 0xF0) { + /* impulse tracker filter control (mfg. 0xF0) */ + if (len == 5) { + switch (data[2]) { + case 0x00: /* set cutoff */ + oldcutoff = pChn->nCutOff; + if (data[3] < 0x80) pChn->nCutOff = data[3]; +#ifndef NO_FILTER + oldcutoff -= pChn->nCutOff; + + if (oldcutoff < 0) oldcutoff = -oldcutoff; + if ((pChn->nVolume > 0) || (oldcutoff < 0x10) + || (!(pChn->dwFlags & CHN_FILTER)) + || (!(pChn->nLeftVol|pChn->nRightVol))) + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) + ? FALSE : TRUE); +#endif // NO_FILTER + break; + case 0x01: /* set resonance */ + if (data[3] < 0x80) pChn->nResonance = data[3]; +#ifndef NO_FILTER + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE); +#endif // NO_FILTER + break; + }; + } + } + + if (!fake && _midi_out_raw) { + /* okay, this is kind of how it works. + we pass m_nBufferCount as here because while + 1000 * ((8((buffer_size/2) - m_nBufferCount)) / sample_rate) + is the number of msec we need to delay by, libmodplug simply doesn't know + what the buffer size is at this point so m_nBufferCount simply has no + frame of reference. + + fortunately, schism does and can complete this (tags: _schism_midi_out_raw ) + + */ + _midi_out_raw(data, len, m_nBufferCount); + } +} + +static int _was_complete_midi(unsigned char *q, unsigned int len, int nextc) +{ + if (len == 0) return 0; + if (*q == 0xF0) return (q[len-1] == 0xF7 ? 1 : 0); + return ((nextc & 0x80) ? 1 : 0); +} + +void CSoundFile::ProcessMidiMacro(UINT nChn, LPCSTR pszMidiMacro, UINT param, + UINT note, UINT velocity, UINT use_instr) +//--------------------------------------------------------------------------- +{ +/* this was all wrong. -mrsb */ + MODCHANNEL *pChn = &Chn[nChn]; + INSTRUMENTHEADER *penv = (m_dwSongFlags & SONG_INSTRUMENTMODE) + ? Headers[use_instr + ?use_instr + :pChn->nLastInstr] + : NULL; + unsigned char outbuffer[64]; + unsigned char cx; + int mc, fake = 0; + int saw_c; + int i, j, x; + + saw_c = 0; + if (!penv || penv->nMidiChannel == 0) { + /* okay, there _IS_ no real midi channel. forget this for now... */ + mc = 15; + fake = 1; + + } else if (penv->nMidiChannel > 16) { + mc = (nChn-1) % 16; + } else { + mc = (penv->nMidiChannel-1); + } + + for (i = j = x = 0, cx =0; i <= 32 && pszMidiMacro[i]; i++) { + int c, cw; + if (pszMidiMacro[i] >= '0' && pszMidiMacro[i] <= '9') { + c = pszMidiMacro[i] - '0'; + cw = 1; + } else if (pszMidiMacro[i] >= 'A' && pszMidiMacro[i] <= 'F') { + c = (pszMidiMacro[i] - 'A') + 10; + cw = 1; + } else if (pszMidiMacro[i] == 'c') { + c = mc; + cw = 1; + saw_c = 1; + } else if (pszMidiMacro[i] == 'n') { + c = (note-1); + cw = 2; + } else if (pszMidiMacro[i] == 'v') { + c = velocity; + cw = 2; + } else if (pszMidiMacro[i] == 'u') { + c = (pChn->nVolume >> 1); + if (c > 127) c = 127; + cw = 2; + } else if (pszMidiMacro[i] == 'x') { + c = pChn->nPan; + if (c > 127) c = 127; + cw = 2; + } else if (pszMidiMacro[i] == 'y') { + c = pChn->nRealPan; + if (c > 127) c = 127; + cw = 2; + } else if (pszMidiMacro[i] == 'a') { + if (!penv) + c = 0; + else + c = (penv->wMidiBank >> 7) & 127; + cw = 2; + } else if (pszMidiMacro[i] == 'b') { + if (!penv) + c = 0; + else + c = penv->wMidiBank & 127; + cw = 2; + } else if (pszMidiMacro[i] == 'z' || pszMidiMacro[i] == 'p') { + c = param & 0x7F; + cw = 2; + } else { + continue; + } + if (j == 0 && cw == 1) { + cx = c; + j = 1; + continue; + } else if (j == 1 && cw == 1) { + cx = (cx << 4) | c; + j = 0; + } else if (j == 0) { + cx = c; + } else if (j == 1) { + outbuffer[x] = cx; + x++; + + cx = c; + j = 0; + } + // start of midi message + if (_was_complete_midi(outbuffer,x,cx)) { + MidiSend(outbuffer, x, nChn,saw_c && fake); + x = 0; + } + outbuffer[x] = cx; + x++; + } + if (j == 1) { + outbuffer[x] = cx; + x++; + } + if (x) { + // terminate sysex + if (!_was_complete_midi(outbuffer,x,0xFF)) { + if (*outbuffer == 0xF0) { + outbuffer[x] = 0xF7; + x++; + } + } + MidiSend(outbuffer, x, nChn,saw_c && fake); + } + + +#if 0 + MODCHANNEL *pChn = &Chn[nChn]; + DWORD dwMacro = (*((LPDWORD)pszMidiMacro)) & 0x7F5F7F5F; + + // Not Internal Device ? + if (dwMacro != 0x30463046) + { + UINT pos = 0, nNib = 0, nBytes = 0; + DWORD dwMidiCode = 0, dwByteCode = 0; + while (pos+6 <= 32) + { + CHAR cData = pszMidiMacro[pos++]; + if (!cData) break; + if ((cData >= '0') && (cData <= '9')) { dwByteCode = (dwByteCode<<4) | (cData-'0'); nNib++; } else + if ((cData >= 'A') && (cData <= 'F')) { dwByteCode = (dwByteCode<<4) | (cData-'A'+10); nNib++; } else + if ((cData >= 'a') && (cData <= 'f')) { dwByteCode = (dwByteCode<<4) | (cData-'a'+10); nNib++; } else + if ((cData == 'z') || (cData == 'Z')) { dwByteCode = param & 0x7f; nNib = 2; } else + if ((cData == 'x') || (cData == 'X')) { dwByteCode = param & 0x70; nNib = 2; } else + if ((cData == 'y') || (cData == 'Y')) { dwByteCode = (param & 0x0f)<<3; nNib = 2; } else + if (nNib >= 2) + { + nNib = 0; + dwMidiCode |= dwByteCode << (nBytes*8); + dwByteCode = 0; + nBytes++; + if (nBytes >= 3) + { + + UINT nMasterCh = (nChn < m_nChannels) ? nChn+1 : pChn->nMasterChn; + if ((nMasterCh) && (nMasterCh <= m_nChannels)) + { + UINT nPlug = ChnSettings[nMasterCh-1].nMixPlugin; + if ((nPlug) && (nPlug <= MAX_MIXPLUGINS)) + { + IMixPlugin *pPlugin = m_MixPlugins[nPlug-1].pMixPlugin; + if ((pPlugin) && (m_MixPlugins[nPlug-1].pMixState)) + { + pPlugin->MidiSend(dwMidiCode); + } + } + } + nBytes = 0; + dwMidiCode = 0; + } + } + + } + return; + } + // Internal device + pszMidiMacro += 4; + // Filter ? + if (pszMidiMacro[0] == '0') + { + CHAR cData1 = pszMidiMacro[2]; + DWORD dwParam = 0; + if ((cData1 == 'z') || (cData1 == 'Z')) + { + dwParam = param; + } else + { + CHAR cData2 = pszMidiMacro[3]; + if ((cData1 >= '0') && (cData1 <= '9')) dwParam += (cData1 - '0') << 4; else + if ((cData1 >= 'A') && (cData1 <= 'F')) dwParam += (cData1 - 'A' + 0x0A) << 4; + if ((cData2 >= '0') && (cData2 <= '9')) dwParam += (cData2 - '0'); else + if ((cData2 >= 'A') && (cData2 <= 'F')) dwParam += (cData2 - 'A' + 0x0A); + } + switch(pszMidiMacro[1]) + { + // F0.F0.00.xx: Set CutOff + case '0': + { + int oldcutoff = pChn->nCutOff; + if (dwParam < 0x80) pChn->nCutOff = dwParam; +#ifndef NO_FILTER + oldcutoff -= pChn->nCutOff; + + if (oldcutoff < 0) oldcutoff = -oldcutoff; + if ((pChn->nVolume > 0) || (oldcutoff < 0x10) + || (!(pChn->dwFlags & CHN_FILTER)) || (!(pChn->nLeftVol|pChn->nRightVol))) + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE); +#endif // NO_FILTER + } + break; + + // F0.F0.01.xx: Set Resonance + case '1': + if (dwParam < 0x80) pChn->nResonance = dwParam; +#ifndef NO_FILTER + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE); +#endif // NO_FILTER + + break; + } + + } +#endif + +} + + +void CSoundFile::RetrigNote(UINT nChn, UINT param) +//------------------------------------------------ +{ + // Retrig: bit 8 is set if it's the new XM retrig + MODCHANNEL *pChn = &Chn[nChn]; + UINT nRetrigSpeed = param & 0x0F; + UINT nRetrigCount = pChn->nRetrigCount; + BOOL bDoRetrig = FALSE; + + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)) + { + if (!nRetrigSpeed) nRetrigSpeed = 1; + if ((nRetrigCount) && (!(nRetrigCount % nRetrigSpeed))) bDoRetrig = TRUE; + nRetrigCount++; + } else + { + UINT realspeed = nRetrigSpeed; + if ((param & 0x100) && (pChn->nRowVolCmd == VOLCMD_VOLUME) && (pChn->nRowParam & 0xF0)) realspeed++; + if ((m_nTickCount) || (param & 0x100)) + { + if (!realspeed) realspeed = 1; + if ((!(param & 0x100)) && (m_nMusicSpeed) && (!(m_nTickCount % realspeed))) bDoRetrig = TRUE; + nRetrigCount++; + } else if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) nRetrigCount = 0; + if (nRetrigCount >= realspeed) + { + if ((m_nTickCount) || ((param & 0x100) && (!pChn->nRowNote))) bDoRetrig = TRUE; + } + } + if (bDoRetrig) + { + UINT dv = (param >> 4) & 0x0F; + if (dv) + { + int vol = pChn->nVolume; + if (retrigTable1[dv]) + vol = (vol * retrigTable1[dv]) >> 4; + else + vol += ((int)retrigTable2[dv]) << 2; + if (vol < 0) vol = 0; + if (vol > 256) vol = 256; + pChn->nVolume = vol; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + UINT nNote = pChn->nNewNote; + LONG nOldPeriod = pChn->nPeriod; + if ((nNote) && (nNote <= 120) && (pChn->nLength)) CheckNNA(nChn, 0, nNote, TRUE); + BOOL bResetEnv = FALSE; + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if ((pChn->nRowInstr) && (param < 0x100)) { InstrumentChange(pChn, pChn->nRowInstr, FALSE, FALSE); bResetEnv = TRUE; } + if (param < 0x100) bResetEnv = TRUE; + } + NoteChange(nChn, nNote, FALSE, bResetEnv); + if ((m_nType & MOD_TYPE_IT) && (!pChn->nRowNote) && (nOldPeriod)) pChn->nPeriod = nOldPeriod; + if (!(m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) nRetrigCount = 0; + } + pChn->nRetrigCount = (BYTE)nRetrigCount; +} + + +void CSoundFile::DoFreqSlide(MODCHANNEL *pChn, LONG nFreqSlide) +//------------------------------------------------------------- +{ + // IT Linear slides + if (!pChn->nPeriod) return; + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + if (nFreqSlide < 0) + { + UINT n = (- nFreqSlide) >> 2; + if (n > 255) n = 255; + pChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideDownTable[n], 65536); + } else + { + UINT n = (nFreqSlide) >> 2; + + if (n > 255) n = 255; + pChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideUpTable[n], 65536); + } + } else + { + pChn->nPeriod += nFreqSlide; + } + if (pChn->nPeriod < 1) + { + pChn->nPeriod = 1; + if (m_nType & MOD_TYPE_IT) + { + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nFadeOutVol = 0; + } + } +} + + +void CSoundFile::NoteCut(UINT nChn, UINT nTick) +//--------------------------------------------- +{ + if (m_nTickCount == nTick) + { + MODCHANNEL *pChn = &Chn[nChn]; + // if (m_dwSongFlags & SONG_INSTRUMENTMODE) KeyOff(pChn); ? + pChn->nVolume = 0; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } +} + + +void CSoundFile::KeyOff(UINT nChn) +//-------------------------------- +{ + MODCHANNEL *pChn = &Chn[nChn]; + BOOL bKeyOn = (pChn->dwFlags & CHN_KEYOFF) ? FALSE : TRUE; + pChn->dwFlags |= CHN_KEYOFF; + //if ((!pChn->pHeader) || (!(pChn->dwFlags & CHN_VOLENV))) + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && (pChn->pHeader) && (!(pChn->dwFlags & CHN_VOLENV))) + { + pChn->dwFlags |= CHN_NOTEFADE; + } + if (!pChn->nLength) return; + if ((pChn->dwFlags & CHN_SUSTAINLOOP) && (pChn->pInstrument) && (bKeyOn)) + { + MODINSTRUMENT *psmp = pChn->pInstrument; + if (psmp->uFlags & CHN_LOOP) + { + if (psmp->uFlags & CHN_PINGPONGLOOP) + pChn->dwFlags |= CHN_PINGPONGLOOP; + else + pChn->dwFlags &= ~(CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); + pChn->dwFlags |= CHN_LOOP; + pChn->nLength = psmp->nLength; + pChn->nLoopStart = psmp->nLoopStart; + pChn->nLoopEnd = psmp->nLoopEnd; + if (pChn->nLength > pChn->nLoopEnd) pChn->nLength = pChn->nLoopEnd; + } else + { + pChn->dwFlags &= ~(CHN_LOOP|CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); + pChn->nLength = psmp->nLength; + } + } + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && pChn->pHeader) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + if (((penv->dwFlags & ENV_VOLLOOP) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) && (penv->nFadeOut)) + pChn->dwFlags |= CHN_NOTEFADE; + } +} + + +////////////////////////////////////////////////////////// +// CSoundFile: Global Effects + + +void CSoundFile::SetSpeed(UINT param) +//----------------------------------- +{ + if (param) + m_nMusicSpeed = param; +} + + +void CSoundFile::SetTempo(UINT param) +//----------------------------------- +{ + if (param < 0x20) + { +#if 0 // argh... this is completely wrong + // Tempo Slide + if ((param & 0xF0) == 0x10) + { + m_nMusicTempo += (param & 0x0F) * 2; + if (m_nMusicTempo > 255) m_nMusicTempo = 255; + } else + { + m_nMusicTempo -= (param & 0x0F) * 2; + if ((LONG)m_nMusicTempo < 32) m_nMusicTempo = 32; + } +#endif + } else + { + m_nMusicTempo = param; + } +} + + +int CSoundFile::PatternLoop(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------- +{ + if (param) + { + if (pChn->nPatternLoopCount) + { + pChn->nPatternLoopCount--; + if (!pChn->nPatternLoopCount) { + // this should get rid of that nasty infinite loop for cases like + // ... .. .. SB0 + // ... .. .. SB1 + // ... .. .. SB1 + // it still doesn't work right in a few strange cases, but oh well :P + pChn->nPatternLoop = m_nRow + 1; + return -1; + } + } else + { + // hmm. the pattern loop shouldn't care about + // other channels at all... i'm not really + // sure what this code is doing :/ +#if 0 + MODCHANNEL *p = Chn; + for (UINT i=0; inPatternLoopCount) return -1; + } +#endif + pChn->nPatternLoopCount = param; + } + return pChn->nPatternLoop; + } else + { + pChn->nPatternLoop = m_nRow; + } + return -1; +} + + +void CSoundFile::GlobalVolSlide(UINT param) +//----------------------------------------- +{ + LONG nGlbSlide = 0; + if (param) m_nOldGlbVolSlide = param; else param = m_nOldGlbVolSlide; + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) nGlbSlide = (param >> 4) * 2; + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) nGlbSlide = - (int)((param & 0x0F) * 2); + } else + { + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + if (param & 0xF0) nGlbSlide = (int)((param & 0xF0) >> 4) * 2; + else nGlbSlide = -(int)((param & 0x0F) * 2); + } + } + if (nGlbSlide) + { + if (m_nType != MOD_TYPE_IT) nGlbSlide *= 2; + nGlbSlide += m_nGlobalVolume; + if (nGlbSlide < 0) nGlbSlide = 0; + if (nGlbSlide > 256) nGlbSlide = 256; + m_nGlobalVolume = nGlbSlide; + } +} + + +DWORD CSoundFile::IsSongFinished(UINT nStartOrder, UINT nStartRow) const +//---------------------------------------------------------------------- +{ + UINT nOrd; + + for (nOrd=nStartOrder; nOrd= MAX_PATTERNS) break; + p = Patterns[nPat]; + if (p) + { + UINT len = PatternSize[nPat] * m_nChannels; + UINT pos = (nOrd == nStartOrder) ? nStartRow : 0; + pos *= m_nChannels; + while (pos < len) + { + UINT cmd; + if ((p[pos].note) || (p[pos].volcmd)) return 0; + cmd = p[pos].command; + if (cmd == CMD_MODCMDEX) + { + UINT cmdex = p[pos].param & 0xF0; + if ((!cmdex) || (cmdex == 0x60) || (cmdex == 0xE0) || (cmdex == 0xF0)) cmd = 0; + } + if ((cmd) && (cmd != CMD_SPEED) && (cmd != CMD_TEMPO)) return 0; + pos++; + } + } + } + } + return (nOrd < MAX_ORDERS) ? nOrd : MAX_ORDERS-1; +} + + +BOOL CSoundFile::IsValidBackwardJump(UINT nStartOrder, UINT nStartRow, UINT nJumpOrder, UINT nJumpRow) const +//---------------------------------------------------------------------------------------------------------- +{ + while ((nJumpOrder < MAX_PATTERNS) && (Order[nJumpOrder] == 0xFE)) nJumpOrder++; + if ((nStartOrder >= MAX_PATTERNS) || (nJumpOrder >= MAX_PATTERNS)) return FALSE; + // Treat only case with jumps in the same pattern + if (nJumpOrder > nStartOrder) return TRUE; + if ((nJumpOrder < nStartOrder) || (nJumpRow >= PatternSize[nStartOrder]) + || (!Patterns[nStartOrder]) || (nStartRow >= 256) || (nJumpRow >= 256)) return FALSE; + // See if the pattern is being played backward + BYTE row_hist[256]; + memset(row_hist, 0, sizeof(row_hist)); + UINT nRows = PatternSize[nStartOrder], row = nJumpRow; + if (nRows > 256) nRows = 256; + row_hist[nStartRow] = TRUE; + while ((row < 256) && (!row_hist[row])) + { + if (row >= nRows) return TRUE; + row_hist[row] = TRUE; + MODCOMMAND *p = Patterns[nStartOrder] + row * m_nChannels; + row++; + int breakrow = -1, posjump = 0; + for (UINT i=0; icommand == CMD_POSITIONJUMP) + { + if (p->param < nStartOrder) return FALSE; + if (p->param > nStartOrder) return TRUE; + posjump = TRUE; + } else + if (p->command == CMD_PATTERNBREAK) + { + breakrow = p->param; + } + } + if (breakrow >= 0) + { + if (!posjump) return TRUE; + row = breakrow; + } + if (row >= nRows) return TRUE; + } + return FALSE; +} + + +////////////////////////////////////////////////////// +// Note/Period/Frequency functions + +UINT CSoundFile::GetNoteFromPeriod(UINT period) const +//--------------------------------------------------- +{ + if (!period) return 0; + if (m_nType & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_MTM|MOD_TYPE_669|MOD_TYPE_OKT|MOD_TYPE_AMF0)) + { + period >>= 2; + for (UINT i=0; i<6*12; i++) + { + if (period >= ProTrackerPeriodTable[i]) + { + if ((period != ProTrackerPeriodTable[i]) && (i)) + { + UINT p1 = ProTrackerPeriodTable[i-1]; + UINT p2 = ProTrackerPeriodTable[i]; + if (p1 - period < (period - p2)) return i+36; + } + return i+1+36; + } + } + return 6*12+36; + } else + { + for (UINT i=1; i<120; i++) + { + LONG n = GetPeriodFromNote(i, 0, 0); + if ((n > 0) && (n <= (LONG)period)) return i; + } + return 120; + } +} + +UINT CSoundFile::GetLinearPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const +{ + if ((!note) || (note > 0xF0)) return 0; + if (m_nType & (MOD_TYPE_IT|MOD_TYPE_S3M|MOD_TYPE_STM|MOD_TYPE_MDL|MOD_TYPE_ULT|MOD_TYPE_WAV + |MOD_TYPE_FAR|MOD_TYPE_DMF|MOD_TYPE_PTM|MOD_TYPE_AMS|MOD_TYPE_DBM|MOD_TYPE_AMF|MOD_TYPE_PSM)) + { + note--; + return (FreqS3MTable[note % 12] << 5) >> (note / 12); + } else + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (note < 13) note = 13; + note -= 13; + LONG l = ((120 - note) << 6) - (nFineTune / 2); + if (l < 1) l = 1; + return (UINT)l; + } else + { + note--; + nFineTune = XM2MODFineTune(nFineTune); + if ((nFineTune) || (note < 36) || (note >= 36+6*12)) + return (ProTrackerTunedPeriods[nFineTune*12 + note % 12] << 5) >> (note / 12); + else + return (ProTrackerPeriodTable[note-36] << 2); + } +} + + +UINT CSoundFile::GetPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const +//------------------------------------------------------------------------------- +{ + if ((!note) || (note > 0xF0)) return 0; + if (m_nType & (MOD_TYPE_IT|MOD_TYPE_S3M|MOD_TYPE_STM|MOD_TYPE_MDL|MOD_TYPE_ULT|MOD_TYPE_WAV + |MOD_TYPE_FAR|MOD_TYPE_DMF|MOD_TYPE_PTM|MOD_TYPE_AMS|MOD_TYPE_DBM|MOD_TYPE_AMF|MOD_TYPE_PSM)) + { + note--; + if (m_dwSongFlags & SONG_LINEARSLIDES) + { + return (FreqS3MTable[note % 12] << 5) >> (note / 12); + } else + { + if (!nC4Speed) nC4Speed = 8363; + return _muldiv(8363, (FreqS3MTable[note % 12] << 5), nC4Speed << (note / 12)); + } + } else + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (note < 13) note = 13; + note -= 13; + if (m_dwSongFlags & SONG_LINEARSLIDES) + { + LONG l = ((120 - note) << 6) - (nFineTune / 2); + if (l < 1) l = 1; + return (UINT)l; + } else + { + int finetune = nFineTune; + UINT rnote = (note % 12) << 3; + UINT roct = note / 12; + int rfine = finetune / 16; + int i = rnote + rfine + 8; + if (i < 0) i = 0; + if (i >= 104) i = 103; + UINT per1 = XMPeriodTable[i]; + if ( finetune < 0 ) + { + rfine--; + finetune = -finetune; + } else rfine++; + i = rnote+rfine+8; + if (i < 0) i = 0; + if (i >= 104) i = 103; + UINT per2 = XMPeriodTable[i]; + rfine = finetune & 0x0F; + per1 *= 16-rfine; + per2 *= rfine; + return ((per1 + per2) << 1) >> roct; + } + } else + { + note--; + nFineTune = XM2MODFineTune(nFineTune); + if ((nFineTune) || (note < 36) || (note >= 36+6*12)) + return (ProTrackerTunedPeriods[nFineTune*12 + note % 12] << 5) >> (note / 12); + else + return (ProTrackerPeriodTable[note-36] << 2); + } +} + + +UINT CSoundFile::GetFreqFromPeriod(UINT period, UINT nC4Speed, int nPeriodFrac) const +//----------------------------------------------------------------------------------- +{ + if (!period) return 0; + if (m_nType & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_MTM|MOD_TYPE_669|MOD_TYPE_OKT|MOD_TYPE_AMF0)) + { + return (3546895L*4) / period; + } else + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (m_dwSongFlags & SONG_LINEARSLIDES) + return XMLinearTable[period % 768] >> (period / 768); + else + return 8363 * 1712L / period; + } else + { + if (m_dwSongFlags & SONG_LINEARSLIDES) + { + if (!nC4Speed) nC4Speed = 8363; + return _muldiv(nC4Speed, 1712L << 8, (period << 8)+nPeriodFrac); + } else + { + return _muldiv(8363, 1712L << 8, (period << 8)+nPeriodFrac); + } + } +} + + diff --git a/modplug/sndfile.cpp b/modplug/sndfile.cpp new file mode 100644 index 000000000..605ece8c7 --- /dev/null +++ b/modplug/sndfile.cpp @@ -0,0 +1,1908 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include //for GCCFIX +#include "stdafx.h" +#include "sndfile.h" + +#define MMCMP_SUPPORT + +#ifdef MMCMP_SUPPORT +extern BOOL MMCMP_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength); +#endif + + +// External decompressors +extern void AMSUnpack(const char *psrc, UINT inputlen, char *pdest, UINT dmax, char packcharacter); +extern WORD MDLReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n); +extern int DMFUnpack(LPBYTE psample, LPBYTE ibuf, LPBYTE ibufmax, UINT maxlen); +extern DWORD ITReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n); +extern void ITUnpack8Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215); +extern void ITUnpack16Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215); + + +#define MAX_PACK_TABLES 3 + + +// Compression table +static signed char UnpackTable[MAX_PACK_TABLES][16] = +//-------------------------------------------- +{ + // CPU-generated dynamic table + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + // u-Law table + {0, 1, 2, 4, 8, 16, 32, 64, + -1, -2, -4, -8, -16, -32, -48, -64}, + // Linear table + {0, 1, 2, 3, 5, 7, 12, 19, + -1, -2, -3, -5, -7, -12, -19, -31} +}; + + +////////////////////////////////////////////////////////// +// CSoundFile + +CSoundFile::CSoundFile() +//---------------------- +{ + m_nType = MOD_TYPE_NONE; + m_dwSongFlags = 0; + m_nStereoSeparation = 128; + m_nChannels = 0; + m_nMixChannels = 0; + m_nSamples = 0; + m_nInstruments = 0; + m_nPatternNames = 0; + m_lpszPatternNames = NULL; + m_lpszSongComments = NULL; + m_nFreqFactor = m_nTempoFactor = 128; + m_nMasterVolume = 128; + m_nMinPeriod = 0x20; + m_nMaxPeriod = 0x7FFF; + m_nRepeatCount = 0; + m_rowHighlightMajor = 16; + m_rowHighlightMinor = 4; + memset(Chn, 0, sizeof(Chn)); + memset(ChnMix, 0, sizeof(ChnMix)); + memset(Ins, 0, sizeof(Ins)); + memset(ChnSettings, 0, sizeof(ChnSettings)); + memset(Headers, 0, sizeof(Headers)); + memset(Order, 0xFF, sizeof(Order)); + memset(Patterns, 0, sizeof(Patterns)); + memset(m_szNames, 0, sizeof(m_szNames)); + memset(m_MixPlugins, 0, sizeof(m_MixPlugins)); +} + + +CSoundFile::~CSoundFile() +//----------------------- +{ + Destroy(); +} + + +BOOL CSoundFile::Create(LPCBYTE lpStream, DWORD dwMemLength) +//---------------------------------------------------------- +{ + int i; + + // deja vu... + m_nType = MOD_TYPE_NONE; + m_dwSongFlags = 0; + m_nStereoSeparation = 128; + m_nChannels = 0; + m_nMixChannels = 0; + m_nSamples = 0; + m_nInstruments = 0; + m_nFreqFactor = m_nTempoFactor = 128; + m_nMasterVolume = 128; + m_nDefaultGlobalVolume = 256; + m_nGlobalVolume = 256; + m_nOldGlbVolSlide = 0; + m_nDefaultSpeed = 6; + m_nDefaultTempo = 125; + m_nPatternDelay = 0; + m_nFrameDelay = 0; + m_nNextRow = 0; + m_nRow = 0; + m_nPattern = 0; + m_nCurrentPattern = 0; + m_nNextPattern = 0; + m_nRestartPos = 0; + m_nMinPeriod = 16; + m_nMaxPeriod = 32767; + m_nSongPreAmp = 0x30; + m_nPatternNames = 0; + m_lpszPatternNames = NULL; + m_lpszSongComments = NULL; + memset(Ins, 0, sizeof(Ins)); + memset(ChnMix, 0, sizeof(ChnMix)); + memset(Chn, 0, sizeof(Chn)); + memset(Headers, 0, sizeof(Headers)); + memset(Order, 0xFF, sizeof(Order)); + memset(Patterns, 0, sizeof(Patterns)); + memset(m_szNames, 0, sizeof(m_szNames)); + memset(m_MixPlugins, 0, sizeof(m_MixPlugins)); + ResetMidiCfg(); + for (UINT npt=0; npt 64) ChnSettings[i].nVolume = 64; + if (ChnSettings[i].nPan > 256) ChnSettings[i].nPan = 128; + Chn[i].nPan = ChnSettings[i].nPan; + Chn[i].nGlobalVol = ChnSettings[i].nVolume; + Chn[i].dwFlags = ChnSettings[i].dwFlags; + Chn[i].nVolume = 256; + Chn[i].nCutOff = 0x7F; + } + // Checking instruments + MODINSTRUMENT *pins = Ins; + + for (i=0; ipSample) + { + if (pins->nLoopEnd > pins->nLength) pins->nLoopEnd = pins->nLength; + if (pins->nLoopStart + 3 >= pins->nLoopEnd) + { + pins->nLoopStart = 0; + pins->nLoopEnd = 0; + } + if (pins->nSustainEnd > pins->nLength) pins->nSustainEnd = pins->nLength; + if (pins->nSustainStart + 3 >= pins->nSustainEnd) + { + pins->nSustainStart = 0; + pins->nSustainEnd = 0; + } + } else + { + pins->nLength = 0; + pins->nLoopStart = 0; + pins->nLoopEnd = 0; + pins->nSustainStart = 0; + pins->nSustainEnd = 0; + } + if (!pins->nLoopEnd) pins->uFlags &= ~CHN_LOOP; + if (!pins->nSustainEnd) pins->uFlags &= ~CHN_SUSTAINLOOP; + if (pins->nGlobalVol > 64) pins->nGlobalVol = 64; + } + // Check invalid instruments + while ((m_nInstruments > 0) && (!Headers[m_nInstruments])) m_nInstruments--; + // Set default values + if (m_nDefaultTempo < 31) m_nDefaultTempo = 31; + if (!m_nDefaultSpeed) m_nDefaultSpeed = 6; + m_nMusicSpeed = m_nDefaultSpeed; + m_nMusicTempo = m_nDefaultTempo; + m_nGlobalVolume = m_nDefaultGlobalVolume; + m_nNextPattern = 0; + m_nCurrentPattern = 0; + m_nPattern = 0; + m_nBufferCount = 0; + m_nTickCount = m_nMusicSpeed; + m_nNextRow = 0; + m_nRow = 0; + if ((m_nRestartPos >= MAX_ORDERS) || (Order[m_nRestartPos] >= MAX_PATTERNS)) m_nRestartPos = 0; + // Load plugins + if (gpMixPluginCreateProc) + { + for (UINT iPlug=0; iPlugRestoreAllParameters(); + } + } + } + } + return m_nType ? TRUE : FALSE; +} + + +BOOL CSoundFile::Destroy() + +//------------------------ +{ + int i; + for (i=0; ipSample) + { + FreeSample(pins->pSample); + pins->pSample = NULL; + } + } + for (i=0; iRelease(); + m_MixPlugins[i].pMixPlugin = NULL; + } + } + m_nType = MOD_TYPE_NONE; + m_nChannels = m_nSamples = m_nInstruments = 0; + return TRUE; +} + + +////////////////////////////////////////////////////////////////////////// +// Memory Allocation + +MODCOMMAND *CSoundFile::AllocatePattern(UINT rows, UINT nchns) +//------------------------------------------------------------ +{ + MODCOMMAND *p = new MODCOMMAND[rows*nchns]; + if (p) memset(p, 0, rows*nchns*sizeof(MODCOMMAND)); + return p; +} + + +void CSoundFile::FreePattern(LPVOID pat) +//-------------------------------------- +{ + if (pat) delete [] (signed char*)pat; +} + + +signed char* CSoundFile::AllocateSample(UINT nbytes) +//------------------------------------------- +{ + signed char * p = (signed char *)GlobalAllocPtr(GHND, (nbytes+39) & ~7); + if (p) p += 16; + return p; +} + + +void CSoundFile::FreeSample(LPVOID p) +//----------------------------------- +{ + if (p) + { + GlobalFreePtr(((LPSTR)p)-16); + } +} + + +////////////////////////////////////////////////////////////////////////// +// Misc functions + +void CSoundFile::ResetMidiCfg() +//----------------------------- +{ + memset(&m_MidiCfg, 0, sizeof(m_MidiCfg)); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_START*32], "FF"); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_STOP*32], "FC"); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_NOTEON*32], "9c n v"); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_NOTEOFF*32], "9c n 0"); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_PROGRAM*32], "Cc p"); + lstrcpy(&m_MidiCfg.szMidiSFXExt[0], "F0F000z"); + for (int iz=0; iz<16; iz++) wsprintf(&m_MidiCfg.szMidiZXXExt[iz*32], "F0F001%02X", iz*8); +} + + +UINT CSoundFile::GetNumChannels() const +//------------------------------------- +{ + UINT n = 0; + for (UINT i=0; i 1) && (s)) s[1] = '\x0A'; + while ((*p) && (i+2 < len)) + { + BYTE c = (BYTE)*p++; + if ((c == 0x0D) || ((c == ' ') && (ln >= linesize))) + { if (s) { s[i++] = '\x0D'; s[i++] = '\x0A'; } else i+= 2; ln=0; } + else + if (c >= 0x20) { if (s) s[i++] = c; else i++; ln++; } + } + if (s) s[i] = 0; + return i; +} + + +UINT CSoundFile::GetRawSongComments(LPSTR s, UINT len, UINT linesize) +//------------------------------------------------------------------- +{ + LPCSTR p = m_lpszSongComments; + if (!p) return 0; + UINT i = 0, ln=0; + while ((*p) && (i < len-1)) + { + BYTE c = (BYTE)*p++; + if ((c == 0x0D) || (c == 0x0A)) + { + if (ln) + { + while (ln < linesize) { if (s) s[i] = ' '; i++; ln++; } + ln = 0; + } + } else + if ((c == ' ') && (!ln)) + { + UINT k=0; + while ((p[k]) && (p[k] >= ' ')) k++; + if (k <= linesize) + { + if (s) s[i] = ' '; + i++; + ln++; + } + } else + { + if (s) s[i] = c; + i++; + ln++; + if (ln == linesize) ln = 0; + } + } + if (ln) + { + while ((ln < linesize) && (i < len)) + { + if (s) s[i] = ' '; + i++; + ln++; + } + } + if (s) s[i] = 0; + return i; +} + + +BOOL CSoundFile::SetWaveConfig(UINT nRate,UINT nBits,UINT nChannels,BOOL bMMX) +//---------------------------------------------------------------------------- +{ + BOOL bReset = FALSE; + DWORD d = gdwSoundSetup & ~SNDMIX_ENABLEMMX; + if (bMMX) d |= SNDMIX_ENABLEMMX; + if ((gdwMixingFreq != nRate) || (gnBitsPerSample != nBits) || (gnChannels != nChannels) || (d != gdwSoundSetup)) bReset = TRUE; + gnChannels = nChannels; + gdwSoundSetup = d; + gdwMixingFreq = nRate; + gnBitsPerSample = nBits; + InitPlayer(bReset); +//printf("Rate=%u Bits=%u Channels=%u MMX=%u\n",gdwMixingFreq,gnBitsPerSample,gnChannels,bMMX); + return TRUE; +} + + +BOOL CSoundFile::SetResamplingMode(UINT nMode) +//-------------------------------------------- +{ + DWORD d = gdwSoundSetup & ~(SNDMIX_NORESAMPLING|SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); + switch(nMode) + { + case SRCMODE_NEAREST: d |= SNDMIX_NORESAMPLING; break; + case SRCMODE_LINEAR: break; + case SRCMODE_SPLINE: d |= SNDMIX_HQRESAMPLER; break; + case SRCMODE_POLYPHASE: d |= (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); break; + default: + return FALSE; + } + gdwSoundSetup = d; + return TRUE; +} + + +BOOL CSoundFile::SetMasterVolume(UINT nVol, BOOL bAdjustAGC) +//---------------------------------------------------------- +{ + if (nVol < 1) nVol = 1; + if (nVol > 0x200) nVol = 0x200; // x4 maximum + if ((gdwSoundSetup & SNDMIX_AGC) && (bAdjustAGC)) + { + gnAGC = gnAGC * m_nMasterVolume / nVol; + if (gnAGC > AGC_UNITY) gnAGC = AGC_UNITY; + } + m_nMasterVolume = nVol; + return TRUE; +} + + +void CSoundFile::SetAGC(BOOL b) +//----------------------------- +{ + if (b) + { + if (!(gdwSoundSetup & SNDMIX_AGC)) + { + gdwSoundSetup |= SNDMIX_AGC; + gnAGC = AGC_UNITY; + } + } else gdwSoundSetup &= ~SNDMIX_AGC; +} + + +UINT CSoundFile::GetNumPatterns() const +//------------------------------------- +{ + UINT i = 0; + while ((i < MAX_ORDERS) && (Order[i] < 0xFF)) i++; + return i; +} + + +UINT CSoundFile::GetNumInstruments() const +//---------------------------------------- +{ + UINT n=0; + for (UINT i=0; i= MAX_ORDERS) + || (Order[nPattern] >= MAX_PATTERNS) + || (nPos >= PatternSize[Order[nPattern]])) + { + nPos = 0; + nPattern = 0; + } + UINT nRow = nPos; + if ((nRow) && (Order[nPattern] < MAX_PATTERNS)) + { + MODCOMMAND *p = Patterns[Order[nPattern]]; + if ((p) && (nRow < PatternSize[Order[nPattern]])) + { + BOOL bOk = FALSE; + while ((!bOk) && (nRow > 0)) + { + UINT n = nRow * m_nChannels; + for (UINT k=0; k= MAX_ORDERS) || (Order[nPos] >= MAX_PATTERNS)) return; + for (UINT j=0; jplayed = 0; + } +} + + +void CSoundFile::LoopPattern(int nPat, int nRow) +//---------------------------------------------- +{ + if ((nPat < 0) || (nPat >= MAX_PATTERNS) || (!Patterns[nPat])) + { + m_dwSongFlags &= ~SONG_PATTERNLOOP; + } else + { + if ((nRow < 0) || (nRow >= PatternSize[nPat])) nRow = 0; + m_nPattern = nPat; + m_nRow = m_nNextRow = nRow; + m_nTickCount = m_nMusicSpeed; + m_nPatternDelay = 0; + m_nFrameDelay = 0; + m_nBufferCount = 0; + m_dwSongFlags |= SONG_PATTERNLOOP; + } +} + + +UINT CSoundFile::GetBestSaveFormat() const +//---------------------------------------- +{ + if ((!m_nSamples) || (!m_nChannels)) return MOD_TYPE_NONE; + if (!m_nType) return MOD_TYPE_NONE; + if (m_nType & (MOD_TYPE_MOD|MOD_TYPE_OKT)) + return MOD_TYPE_MOD; + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_STM|MOD_TYPE_ULT|MOD_TYPE_FAR|MOD_TYPE_PTM)) + return MOD_TYPE_S3M; + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MED|MOD_TYPE_MTM|MOD_TYPE_MT2)) + return MOD_TYPE_XM; + return MOD_TYPE_IT; +} + + +UINT CSoundFile::GetSaveFormats() const +//------------------------------------- +{ + UINT n = 0; + if ((!m_nSamples) || (!m_nChannels) || (m_nType == MOD_TYPE_NONE)) return 0; + switch(m_nType) + { + case MOD_TYPE_MOD: n = MOD_TYPE_MOD; + case MOD_TYPE_S3M: n = MOD_TYPE_S3M; + } + n |= MOD_TYPE_XM | MOD_TYPE_IT; + if (!(m_dwSongFlags & SONG_INSTRUMENTMODE)) + { + if (m_nSamples < 32) n |= MOD_TYPE_MOD; + n |= MOD_TYPE_S3M; + } + return n; +} + + +UINT CSoundFile::GetSampleName(UINT nSample,LPSTR s) const +//-------------------------------------------------------- +{ + char sztmp[40] = ""; // changed from CHAR + memcpy(sztmp, m_szNames[nSample],32); + sztmp[31] = 0; + if (s) strcpy(s, sztmp); + return strlen(sztmp); +} + + +UINT CSoundFile::GetInstrumentName(UINT nInstr,LPSTR s) const +//----------------------------------------------------------- +{ + char sztmp[40] = ""; // changed from CHAR + if ((nInstr >= MAX_INSTRUMENTS) || (!Headers[nInstr])) + { + if (s) *s = 0; + return 0; + } + INSTRUMENTHEADER *penv = Headers[nInstr]; + memcpy(sztmp, penv->name, 32); + sztmp[31] = 0; + if (s) strcpy(s, sztmp); + return strlen(sztmp); +} + + +#ifndef NO_PACKING +UINT CSoundFile::PackSample(int &sample, int next) +//------------------------------------------------ +{ + UINT i = 0; + int delta = next - sample; + if (delta >= 0) + { + for (i=0; i<7; i++) if (delta <= (int)CompressionTable[i+1]) break; + } else + { + for (i=8; i<15; i++) if (delta >= (int)CompressionTable[i+1]) break; + } + sample += (int)CompressionTable[i]; + return i; +} + + +BOOL CSoundFile::CanPackSample(LPSTR pSample, UINT nLen, UINT nPacking, BYTE *result) +//----------------------------------------------------------------------------------- +{ + int pos, old, oldpos, besttable = 0; + DWORD dwErr, dwTotal, dwResult; + int i,j; + + if (result) *result = 0; + if ((!pSample) || (nLen < 1024)) return FALSE; + // Try packing with different tables + dwResult = 0; + for (j=1; j= dwResult) + { + dwResult = dwErr; + besttable = j; + } + } + memcpy(CompressionTable, UnpackTable[besttable], 16); + if (result) + { + if (dwResult > 100) *result = 100; else *result = (BYTE)dwResult; + } + return (dwResult >= nPacking) ? TRUE : FALSE; +} +#endif // NO_PACKING + +#ifndef MODPLUG_NO_FILESAVE + +UINT CSoundFile::WriteSample(diskwriter_driver_t *f, MODINSTRUMENT *pins, + UINT nFlags, UINT nMaxLen) +//----------------------------------------------------------------------------------- +{ + UINT len = 0, bufcount; + signed char buffer[4096]; + signed char *pSample = (signed char *)pins->pSample; + UINT nLen = pins->nLength; + + if ((nMaxLen) && (nLen > nMaxLen)) nLen = nMaxLen; + if ((!pSample) || (f == NULL) || (!nLen)) return 0; + switch(nFlags) + { +#ifndef NO_PACKING + // 3: 4-bit ADPCM data + case RS_ADPCM4: + { + int pos; + len = (nLen + 1) / 2; + f->o(f, (const unsigned char *)CompressionTable, 16); + bufcount = 0; + pos = 0; + for (UINT j=0; j= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + len += 16; + } + break; +#endif // NO_PACKING + + // 16-bit samples + case RS_PCM16U: + case RS_PCM16D: + case RS_PCM16S: + { + short int *p = (short int *)pSample; + int s_old = 0, s_ofs; + len = nLen * 2; + bufcount = 0; + s_ofs = (nFlags == RS_PCM16U) ? 0x8000 : 0; + for (UINT j=0; juFlags & CHN_STEREO) + { + s_new = (s_new + (*p) + 1) >> 1; + p++; + } + if (nFlags == RS_PCM16D) + { + *((short *)(&buffer[bufcount])) = bswapLE16((short)(s_new - s_old)); + s_old = s_new; + } else + { + *((short *)(&buffer[bufcount])) = bswapLE16((short)(s_new + s_ofs)); + } + bufcount += 2; + if (bufcount >= sizeof(buffer) - 1) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + } + break; + + + // 8-bit Stereo samples (not interleaved) + case RS_STPCM8S: + case RS_STPCM8U: + case RS_STPCM8D: + { + int s_ofs = (nFlags == RS_STPCM8U) ? 0x80 : 0; + for (UINT iCh=0; iCh<2; iCh++) + { + signed char *p = pSample + iCh; + int s_old = 0; + + bufcount = 0; + for (UINT j=0; j= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + } + } + len = nLen * 2; + break; + + // 16-bit Stereo samples (not interleaved) + case RS_STPCM16S: + case RS_STPCM16U: + case RS_STPCM16D: + { + int s_ofs = (nFlags == RS_STPCM16U) ? 0x8000 : 0; + for (UINT iCh=0; iCh<2; iCh++) + { + signed short *p = ((signed short *)pSample) + iCh; + int s_old = 0; + + bufcount = 0; + for (UINT j=0; j= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + } + } + len = nLen*4; + break; + + // Stereo signed interleaved + case RS_STIPCM8S: + case RS_STIPCM16S: + len = nLen * 2; + if (nFlags == RS_STIPCM16S) { + { + signed short *p = (signed short *)pSample; + bufcount = 0; + for (UINT j=0; j= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + }; + } else { + f->o(f, (const unsigned char *)pSample, len); + } + break; + + // Default: assume 8-bit PCM data + default: + len = nLen; + bufcount = 0; + { + signed char *p = pSample; + int sinc = (pins->uFlags & CHN_16BIT) ? 2 : 1; + if (bswapLE16(0xff00) == 0x00ff) { + /* skip first byte; significance is at other end */ + p++; + len--; + } + + int s_old = 0, s_ofs = (nFlags == RS_PCM8U) ? 0x80 : 0; + if (pins->uFlags & CHN_16BIT) p++; + for (UINT j=0; juFlags & CHN_STEREO) + { + s_new = (s_new + ((int)*p) + 1) >> 1; + p += sinc; + } + if (nFlags == RS_PCM8D) + { + buffer[bufcount++] = (signed char)(s_new - s_old); + s_old = s_new; + } else + { + buffer[bufcount++] = (signed char)(s_new + s_ofs); + } + if (bufcount >= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f,(const unsigned char *)buffer,bufcount); + } + } + return len; +} + +#endif // MODPLUG_NO_FILESAVE + + +// Flags: +// 0 = signed 8-bit PCM data (default) +// 1 = unsigned 8-bit PCM data +// 2 = 8-bit ADPCM data with linear table +// 3 = 4-bit ADPCM data +// 4 = 16-bit ADPCM data with linear table +// 5 = signed 16-bit PCM data +// 6 = unsigned 16-bit PCM data + + +UINT CSoundFile::ReadSample(MODINSTRUMENT *pIns, UINT nFlags, LPCSTR lpMemFile, DWORD dwMemLength) +//------------------------------------------------------------------------------------------------ +{ + UINT len = 0, mem = pIns->nLength+6; + + if ((!pIns) || (pIns->nLength < 1) || (!lpMemFile)) return 0; + if (pIns->nLength > MAX_SAMPLE_LENGTH) pIns->nLength = MAX_SAMPLE_LENGTH; + pIns->uFlags &= ~(CHN_16BIT|CHN_STEREO); + if (nFlags & RSF_16BIT) + { + mem *= 2; + pIns->uFlags |= CHN_16BIT; + } + if (nFlags & RSF_STEREO) + { + mem *= 2; + pIns->uFlags |= CHN_STEREO; + } + if ((pIns->pSample = AllocateSample(mem)) == NULL) + { + pIns->nLength = 0; + return 0; + } + switch(nFlags) + { + // 1: 8-bit unsigned PCM data + case RS_PCM8U: + { + len = pIns->nLength; + if (len > dwMemLength) len = pIns->nLength = dwMemLength; + signed char *pSample = pIns->pSample; + for (UINT j=0; jnLength; + if (len > dwMemLength) break; + signed char *pSample = pIns->pSample; + const signed char *p = (const signed char *)lpMemFile; + int delta = 0; + + for (UINT j=0; jnLength + 1) / 2; + if (len > dwMemLength - 16) break; + memcpy(CompressionTable, lpMemFile, 16); + lpMemFile += 16; + signed char *pSample = pIns->pSample; + signed char delta = 0; + for (UINT j=0; j> 4); + delta = (signed char)GetDeltaValue((int)delta, b0); + pSample[0] = delta; + delta = (signed char)GetDeltaValue((int)delta, b1); + pSample[1] = delta; + pSample += 2; + } + len += 16; + } + break; + + // 4: 16-bit ADPCM data with linear table + case RS_PCM16D: + { + len = pIns->nLength * 2; + if (len > dwMemLength) break; + short int *pSample = (short int *)pIns->pSample; + short int *p = (short int *)lpMemFile; + unsigned short tmp; + volatile short int *tmp2; + int delta16 = 0; + for (UINT j=0; jnLength * 2; + if (len <= dwMemLength) memcpy(pIns->pSample, lpMemFile, len); + short int *pSample = (short int *)pIns->pSample; + for (UINT j=0; jnLength * 2; + if (len > dwMemLength) len = dwMemLength & ~1; + if (len > 1) + { + signed char *pSample = (signed char *)pIns->pSample; + signed char *pSrc = (signed char *)lpMemFile; + for (UINT j=0; jnLength * 2; + if (len > dwMemLength) break; + short int *pSample = (short int *)pIns->pSample; + short int *pSrc = (short int *)lpMemFile; + for (UINT j=0; jnLength * 2; + if (len*2 <= dwMemLength) + { + signed char *pSample = (signed char *)pIns->pSample; + signed char *pSrc = (signed char *)lpMemFile; + for (UINT j=0; jnLength; + signed char *psrc = (signed char *)lpMemFile; + signed char *pSample = (signed char *)pIns->pSample; + if (len*2 > dwMemLength) break; + for (UINT j=0; jnLength; + short int *psrc = (short int *)lpMemFile; + short int *pSample = (short int *)pIns->pSample; + if (len*4 > dwMemLength) break; + for (UINT j=0; jpSample, pIns->nLength, (LPBYTE)lpMemFile, dwMemLength, (nFlags == RS_IT2158)); + else + ITUnpack16Bit(pIns->pSample, pIns->nLength, (LPBYTE)lpMemFile, dwMemLength, (nFlags == RS_IT21516)); + break; + +#ifndef MODPLUG_BASIC_SUPPORT +#ifndef MODPLUG_FASTSOUNDLIB + // 8-bit interleaved stereo samples + case RS_STIPCM8S: + case RS_STIPCM8U: + { + int iadd = 0; + if (nFlags == RS_STIPCM8U) { iadd = -0x80; } + len = pIns->nLength; + if (len*2 > dwMemLength) len = dwMemLength >> 1; + LPBYTE psrc = (LPBYTE)lpMemFile; + LPBYTE pSample = (LPBYTE)pIns->pSample; + for (UINT j=0; jnLength; + if (len*4 > dwMemLength) len = dwMemLength >> 2; + short int *psrc = (short int *)lpMemFile; + short int *pSample = (short int *)pIns->pSample; + for (UINT j=0; j 9) + { + const char *psrc = lpMemFile; + char packcharacter = lpMemFile[8], *pdest = (char *)pIns->pSample; + len += bswapLE32(*((LPDWORD)(lpMemFile+4))); + if (len > dwMemLength) len = dwMemLength; + UINT dmax = pIns->nLength; + if (pIns->uFlags & CHN_16BIT) dmax <<= 1; + AMSUnpack(psrc+9, len-9, pdest, dmax, packcharacter); + } + break; + + // PTM 8bit delta to 16-bit sample + case RS_PTM8DTO16: + { + len = pIns->nLength * 2; + if (len > dwMemLength) break; + signed char *pSample = (signed char *)pIns->pSample; + signed char delta8 = 0; + for (UINT j=0; jpSample; + for (UINT j=0; j= 4) + { + LPBYTE pSample = (LPBYTE)pIns->pSample; + LPBYTE ibuf = (LPBYTE)lpMemFile; + DWORD bitbuf = bswapLE32(*((DWORD *)ibuf)); + UINT bitnum = 32; + BYTE dlt = 0, lowbyte = 0; + ibuf += 4; + for (UINT j=0; jnLength; j++) + { + BYTE hibyte; + BYTE sign; + if (nFlags == RS_MDL16) lowbyte = (BYTE)MDLReadBits(bitbuf, bitnum, ibuf, 8); + sign = (BYTE)MDLReadBits(bitbuf, bitnum, ibuf, 1); + if (MDLReadBits(bitbuf, bitnum, ibuf, 1)) + { + hibyte = (BYTE)MDLReadBits(bitbuf, bitnum, ibuf, 3); + } else + { + hibyte = 8; + while (!MDLReadBits(bitbuf, bitnum, ibuf, 1)) hibyte += 0x10; + hibyte += MDLReadBits(bitbuf, bitnum, ibuf, 4); + } + if (sign) hibyte = ~hibyte; + dlt += hibyte; + if (nFlags != RS_MDL16) + pSample[j] = dlt; + else + { + pSample[j<<1] = lowbyte; + pSample[(j<<1)+1] = dlt; + } + } + } + break; + + case RS_DMF8: + case RS_DMF16: + len = dwMemLength; + if (len >= 4) + { + UINT maxlen = pIns->nLength; + if (pIns->uFlags & CHN_16BIT) maxlen <<= 1; + LPBYTE ibuf = (LPBYTE)lpMemFile, ibufmax = (LPBYTE)(lpMemFile+dwMemLength); + len = DMFUnpack((LPBYTE)pIns->pSample, ibuf, ibufmax, maxlen); + } + break; + +#ifdef MODPLUG_TRACKER + // PCM 24-bit signed -> load sample, and normalize it to 16-bit + case RS_PCM24S: + case RS_PCM32S: + len = pIns->nLength * 3; + if (nFlags == RS_PCM32S) len += pIns->nLength; + if (len > dwMemLength) break; + if (len > 4*8) + { + UINT slsize = (nFlags == RS_PCM32S) ? 4 : 3; + LPBYTE pSrc = (LPBYTE)lpMemFile; + LONG max = 255; + if (nFlags == RS_PCM32S) pSrc++; + for (UINT j=0; j max) max = l; + if (-l > max) max = -l; + } + max = (max / 128) + 1; + signed short *pDest = (signed short *)pIns->pSample; + for (UINT k=0; k load sample, and normalize it to 16-bit + case RS_STIPCM24S: + case RS_STIPCM32S: + len = pIns->nLength * 6; + if (nFlags == RS_STIPCM32S) len += pIns->nLength * 2; + if (len > dwMemLength) break; + if (len > 8*8) + { + UINT slsize = (nFlags == RS_STIPCM32S) ? 4 : 3; + LPBYTE pSrc = (LPBYTE)lpMemFile; + LONG max = 255; + if (nFlags == RS_STIPCM32S) pSrc++; + for (UINT j=0; j max) max = l; + if (-l > max) max = -l; + } + max = (max / 128) + 1; + signed short *pDest = (signed short *)pIns->pSample; + for (UINT k=0; knLength; + if (len*4 > dwMemLength) len = dwMemLength >> 2; + LPCBYTE psrc = (LPCBYTE)lpMemFile; + short int *pSample = (short int *)pIns->pSample; + for (UINT j=0; jnLength; + if (len > dwMemLength) len = pIns->nLength = dwMemLength; + memcpy(pIns->pSample, lpMemFile, len); + } + if (len > dwMemLength) + { + if (pIns->pSample) + { + pIns->nLength = 0; + FreeSample(pIns->pSample); + pIns->pSample = NULL; + } + return 0; + } + AdjustSampleLoop(pIns); + return len; +} + + +void CSoundFile::AdjustSampleLoop(MODINSTRUMENT *pIns) +//---------------------------------------------------- +{ + if (!pIns->pSample) return; + if (pIns->nLoopEnd > pIns->nLength) pIns->nLoopEnd = pIns->nLength; + if (pIns->nLoopStart+2 >= pIns->nLoopEnd) + { + pIns->nLoopStart = pIns->nLoopEnd = 0; + pIns->uFlags &= ~CHN_LOOP; + } + UINT len = pIns->nLength; + if (pIns->uFlags & CHN_16BIT) + { + short int *pSample = (short int *)pIns->pSample; + // Adjust end of sample + if (pIns->uFlags & CHN_STEREO) + { + pSample[len*2+6] = pSample[len*2+4] = pSample[len*2+2] = pSample[len*2] = pSample[len*2-2]; + pSample[len*2+7] = pSample[len*2+5] = pSample[len*2+3] = pSample[len*2+1] = pSample[len*2-1]; + } else + { + pSample[len+4] = pSample[len+3] = pSample[len+2] = pSample[len+1] = pSample[len] = pSample[len-1]; + } + if ((pIns->uFlags & (CHN_LOOP|CHN_PINGPONGLOOP|CHN_STEREO)) == CHN_LOOP) + { + // Fix bad loops + if ((pIns->nLoopEnd+3 >= pIns->nLength) || (m_nType & MOD_TYPE_S3M)) + { + pSample[pIns->nLoopEnd] = pSample[pIns->nLoopStart]; + pSample[pIns->nLoopEnd+1] = pSample[pIns->nLoopStart+1]; + pSample[pIns->nLoopEnd+2] = pSample[pIns->nLoopStart+2]; + pSample[pIns->nLoopEnd+3] = pSample[pIns->nLoopStart+3]; + pSample[pIns->nLoopEnd+4] = pSample[pIns->nLoopStart+4]; + } + } + } else + { + signed char *pSample = pIns->pSample; +#ifndef MODPLUG_FASTSOUNDLIB + // Crappy samples (except chiptunes) ? + if ((pIns->nLength > 0x100) && (m_nType & (MOD_TYPE_MOD|MOD_TYPE_S3M)) + && (!(pIns->uFlags & CHN_STEREO))) + { + int smpend = pSample[pIns->nLength-1], smpfix = 0, kscan; + for (kscan=pIns->nLength-1; kscan>0; kscan--) + { + smpfix = pSample[kscan-1]; + if (smpfix != smpend) break; + } + int delta = smpfix - smpend; + if (((!(pIns->uFlags & CHN_LOOP)) || (kscan > (int)pIns->nLoopEnd)) + && ((delta < -8) || (delta > 8))) + { + while (kscan<(int)pIns->nLength) + { + if (!(kscan & 7)) + { + if (smpfix > 0) smpfix--; + if (smpfix < 0) smpfix++; + } + pSample[kscan] = (signed char)smpfix; + kscan++; + } + } + } +#endif + // Adjust end of sample + if (pIns->uFlags & CHN_STEREO) + { + pSample[len*2+6] = pSample[len*2+4] = pSample[len*2+2] = pSample[len*2] = pSample[len*2-2]; + pSample[len*2+7] = pSample[len*2+5] = pSample[len*2+3] = pSample[len*2+1] = pSample[len*2-1]; + } else + { + pSample[len+4] = pSample[len+3] = pSample[len+2] = pSample[len+1] = pSample[len] = pSample[len-1]; + } + if ((pIns->uFlags & (CHN_LOOP|CHN_PINGPONGLOOP|CHN_STEREO)) == CHN_LOOP) + { + if ((pIns->nLoopEnd+3 >= pIns->nLength) || (m_nType & (MOD_TYPE_MOD|MOD_TYPE_S3M))) + { + pSample[pIns->nLoopEnd] = pSample[pIns->nLoopStart]; + pSample[pIns->nLoopEnd+1] = pSample[pIns->nLoopStart+1]; + pSample[pIns->nLoopEnd+2] = pSample[pIns->nLoopStart+2]; + pSample[pIns->nLoopEnd+3] = pSample[pIns->nLoopStart+3]; + pSample[pIns->nLoopEnd+4] = pSample[pIns->nLoopStart+4]; + } + } + } +} + + +///////////////////////////////////////////////////////////// +// Transpose <-> Frequency conversions + +// returns 8363*2^((transp*128+ftune)/(12*128)) +DWORD CSoundFile::TransposeToFrequency(int transp, int ftune) +//----------------------------------------------------------- +{ + //---GCCFIX: Removed assembly. +#ifdef MSC_VER + const float _fbase = 8363; + const float _factor = 1.0f/(12.0f*128.0f); + int result; + DWORD freq; + + transp = (transp << 7) + ftune; + _asm { + fild transp + fld _factor + fmulp st(1), st(0) + fist result + fisub result + f2xm1 + fild result + fld _fbase + fscale + fstp st(1) + fmul st(1), st(0) + faddp st(1), st(0) + fistp freq + } + UINT derr = freq % 11025; + if (derr <= 8) freq -= derr; + if (derr >= 11015) freq += 11025-derr; + derr = freq % 1000; + if (derr <= 5) freq -= derr; + if (derr >= 995) freq += 1000-derr; + return freq; +#else + return (DWORD) (8363.0 * pow(2, (transp * 128.0 + ftune) / 1536.0)); +#endif +} + + +// returns 12*128*log2(freq/8363) +int CSoundFile::FrequencyToTranspose(DWORD freq) +//---------------------------------------------- +{ + //---GCCFIX: Removed assembly. +#ifdef MSC_VER + const float _f1_8363 = 1.0f / 8363.0f; + const float _factor = 128 * 12; + LONG result; + + if (!freq) return 0; + _asm { + fld _factor + fild freq + fld _f1_8363 + fmulp st(1), st(0) + fyl2x + fistp result + } + return result; +#else + return (int) (1536.0 * (log(freq / 8363.0) / log(2))); +#endif +} + + +void CSoundFile::FrequencyToTranspose(MODINSTRUMENT *psmp) +//-------------------------------------------------------- +{ + int f2t = FrequencyToTranspose(psmp->nC4Speed); + int transp = f2t >> 7; + int ftune = f2t & 0x7F; + if (ftune > 80) + { + transp++; + ftune -= 128; + } + if (transp > 127) transp = 127; + if (transp < -127) transp = -127; + psmp->RelativeTone = transp; + psmp->nFineTune = ftune; +} + + +void CSoundFile::CheckCPUUsage(UINT nCPU) +//--------------------------------------- +{ + if (nCPU > 100) nCPU = 100; + gnCPUUsage = nCPU; + if (nCPU < 90) + { + m_dwSongFlags &= ~SONG_CPUVERYHIGH; + } else + if ((m_dwSongFlags & SONG_CPUVERYHIGH) && (nCPU >= 94)) + { + UINT i=MAX_CHANNELS; + while (i >= 8) + { + i--; + if (Chn[i].nLength) + { + Chn[i].nLength = Chn[i].nPos = 0; + nCPU -= 2; + if (nCPU < 94) break; + } + } + } else + if (nCPU > 90) + { + m_dwSongFlags |= SONG_CPUVERYHIGH; + } +} + + +BOOL CSoundFile::SetPatternName(UINT nPat, LPCSTR lpszName) +//--------------------------------------------------------- +{ + char szName[MAX_PATTERNNAME] = ""; // changed from CHAR + if (nPat >= MAX_PATTERNS) return FALSE; + if (lpszName) lstrcpyn(szName, lpszName, MAX_PATTERNNAME); + szName[MAX_PATTERNNAME-1] = 0; + if (!m_lpszPatternNames) m_nPatternNames = 0; + if (nPat >= m_nPatternNames) + { + if (!lpszName[0]) return TRUE; + UINT len = (nPat+1)*MAX_PATTERNNAME; + char *p = new char[len]; // changed from CHAR + if (!p) return FALSE; + memset(p, 0, len); + if (m_lpszPatternNames) + { + memcpy(p, m_lpszPatternNames, m_nPatternNames * MAX_PATTERNNAME); + delete m_lpszPatternNames; + m_lpszPatternNames = NULL; + } + m_lpszPatternNames = p; + m_nPatternNames = nPat + 1; + } + memcpy(m_lpszPatternNames + nPat * MAX_PATTERNNAME, szName, MAX_PATTERNNAME); + return TRUE; +} + + +BOOL CSoundFile::GetPatternName(UINT nPat, LPSTR lpszName, UINT cbSize) const +//--------------------------------------------------------------------------- +{ + if ((!lpszName) || (!cbSize)) return FALSE; + lpszName[0] = 0; + if (cbSize > MAX_PATTERNNAME) cbSize = MAX_PATTERNNAME; + if ((m_lpszPatternNames) && (nPat < m_nPatternNames)) + { + memcpy(lpszName, m_lpszPatternNames + nPat * MAX_PATTERNNAME, cbSize); + lpszName[cbSize-1] = 0; + return TRUE; + } + return FALSE; +} + + +#ifndef MODPLUG_FASTSOUNDLIB + +UINT CSoundFile::DetectUnusedSamples(BOOL *pbIns) +//----------------------------------------------- +{ + UINT nExt = 0; + + if (!pbIns) return 0; + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + memset(pbIns, 0, MAX_SAMPLES * sizeof(BOOL)); + for (UINT ipat=0; ipatnote) && (p->note <= 120)) + { + if ((p->instr) && (p->instr < MAX_INSTRUMENTS)) + { + INSTRUMENTHEADER *penv = Headers[p->instr]; + if (penv) + { + UINT n = penv->Keyboard[p->note-1]; + if (n < MAX_SAMPLES) pbIns[n] = TRUE; + } + } else + { + for (UINT k=1; k<=m_nInstruments; k++) + { + INSTRUMENTHEADER *penv = Headers[k]; + if (penv) + { + UINT n = penv->Keyboard[p->note-1]; + if (n < MAX_SAMPLES) pbIns[n] = TRUE; + } + } + } + } + } + } + } + for (UINT ichk=1; ichk<=m_nSamples; ichk++) + { + if ((!pbIns[ichk]) && (Ins[ichk].pSample)) nExt++; + } + } + return nExt; +} + + +BOOL CSoundFile::RemoveSelectedSamples(BOOL *pbIns) +//------------------------------------------------- +{ + if (!pbIns) return FALSE; + for (UINT j=1; j 1)) m_nSamples--; + } + } + return TRUE; +} + + +BOOL CSoundFile::DestroySample(UINT nSample) +//------------------------------------------ +{ + if ((!nSample) || (nSample >= MAX_SAMPLES)) return FALSE; + if (!Ins[nSample].pSample) return TRUE; + MODINSTRUMENT *pins = &Ins[nSample]; + signed char *pSample = pins->pSample; + pins->pSample = NULL; + pins->nLength = 0; + pins->uFlags &= ~(CHN_16BIT); + for (UINT i=0; i, + * Adam Goode (endian and char fixes for PPC) +*/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "diskwriter.h" + +#ifndef __SNDFILE_H +#define __SNDFILE_H + +#define MODPLUG_TRACKER 1 +#define MODPLUG_PLAYER 1 + +#ifdef UNDER_CE +int _strnicmp(const char *str1,const char *str2, int n); +#endif + +#ifndef LPCBYTE +typedef const BYTE * LPCBYTE; +#endif + +#define MOD_AMIGAC2 0x1AB +#define MAX_SAMPLE_LENGTH 16000000 +#define MAX_SAMPLE_RATE 192000 +#define MAX_ORDERS 256 +#define MAX_PATTERNS 240 +#define MAX_SAMPLES 240 +#define MAX_INSTRUMENTS MAX_SAMPLES +#ifdef MODPLUG_FASTSOUNDLIB +#define MAX_CHANNELS 80 +#else +#define MAX_CHANNELS 256 +#endif +#define MAX_BASECHANNELS 64 +#define MAX_ENVPOINTS 32 +#define MIN_PERIOD 0x0020 +#define MAX_PERIOD 0xFFFF +#define MAX_PATTERNNAME 32 +#define MAX_CHANNELNAME 20 +#define MAX_INFONAME 80 +#define MAX_EQ_BANDS 6 +#define MAX_MIXPLUGINS 8 + + +#define MOD_TYPE_NONE 0x00 +#define MOD_TYPE_MOD 0x01 +#define MOD_TYPE_S3M 0x02 +#define MOD_TYPE_XM 0x04 +#define MOD_TYPE_MED 0x08 +#define MOD_TYPE_MTM 0x10 +#define MOD_TYPE_IT 0x20 +#define MOD_TYPE_669 0x40 +#define MOD_TYPE_ULT 0x80 +#define MOD_TYPE_STM 0x100 +#define MOD_TYPE_FAR 0x200 +#define MOD_TYPE_WAV 0x400 +#define MOD_TYPE_AMF 0x800 +#define MOD_TYPE_AMS 0x1000 +#define MOD_TYPE_DSM 0x2000 +#define MOD_TYPE_MDL 0x4000 +#define MOD_TYPE_OKT 0x8000 +#define MOD_TYPE_MID 0x10000 +#define MOD_TYPE_DMF 0x20000 +#define MOD_TYPE_PTM 0x40000 +#define MOD_TYPE_DBM 0x80000 +#define MOD_TYPE_MT2 0x100000 +#define MOD_TYPE_AMF0 0x200000 +#define MOD_TYPE_PSM 0x400000 +#define MOD_TYPE_UMX 0x80000000 // Fake type +#define MAX_MODTYPE 23 + + + +// Channel flags: +// Bits 0-7: Sample Flags +#define CHN_16BIT 0x01 +#define CHN_LOOP 0x02 +#define CHN_PINGPONGLOOP 0x04 +#define CHN_SUSTAINLOOP 0x08 +#define CHN_PINGPONGSUSTAIN 0x10 +#define CHN_PANNING 0x20 +#define CHN_STEREO 0x40 +#define CHN_PINGPONGFLAG 0x80 +// Bits 8-31: Channel Flags +#define CHN_MUTE 0x100 +#define CHN_KEYOFF 0x200 +#define CHN_NOTEFADE 0x400 +#define CHN_SURROUND 0x800 +#define CHN_NOIDO 0x1000 +#define CHN_HQSRC 0x2000 +#define CHN_FILTER 0x4000 +#define CHN_VOLUMERAMP 0x8000 +#define CHN_VIBRATO 0x10000 +#define CHN_TREMOLO 0x20000 +#define CHN_PANBRELLO 0x40000 +#define CHN_PORTAMENTO 0x80000 +#define CHN_GLISSANDO 0x100000 +#define CHN_VOLENV 0x200000 +#define CHN_PANENV 0x400000 +#define CHN_PITCHENV 0x800000 +#define CHN_FASTVOLRAMP 0x1000000 +#define CHN_EXTRALOUD 0x2000000 +#define CHN_REVERB 0x4000000 +#define CHN_NOREVERB 0x8000000 +// used to turn off mute but have it reset later +#define CHN_NNAMUTE 0x10000000 + + +#define ENV_VOLUME 0x0001 +#define ENV_VOLSUSTAIN 0x0002 +#define ENV_VOLLOOP 0x0004 +#define ENV_PANNING 0x0008 +#define ENV_PANSUSTAIN 0x0010 +#define ENV_PANLOOP 0x0020 +#define ENV_PITCH 0x0040 +#define ENV_PITCHSUSTAIN 0x0080 +#define ENV_PITCHLOOP 0x0100 +#define ENV_SETPANNING 0x0200 +#define ENV_FILTER 0x0400 +#define ENV_VOLCARRY 0x0800 +#define ENV_PANCARRY 0x1000 +#define ENV_PITCHCARRY 0x2000 +#define ENV_MUTE 0x4000 + +#define CMD_NONE 0 +#define CMD_ARPEGGIO 1 +#define CMD_PORTAMENTOUP 2 +#define CMD_PORTAMENTODOWN 3 +#define CMD_TONEPORTAMENTO 4 +#define CMD_VIBRATO 5 +#define CMD_TONEPORTAVOL 6 +#define CMD_VIBRATOVOL 7 +#define CMD_TREMOLO 8 +#define CMD_PANNING8 9 +#define CMD_OFFSET 10 +#define CMD_VOLUMESLIDE 11 +#define CMD_POSITIONJUMP 12 +#define CMD_VOLUME 13 +#define CMD_PATTERNBREAK 14 +#define CMD_RETRIG 15 +#define CMD_SPEED 16 +#define CMD_TEMPO 17 +#define CMD_TREMOR 18 +#define CMD_MODCMDEX 19 +#define CMD_S3MCMDEX 20 +#define CMD_CHANNELVOLUME 21 +#define CMD_CHANNELVOLSLIDE 22 +#define CMD_GLOBALVOLUME 23 +#define CMD_GLOBALVOLSLIDE 24 +#define CMD_KEYOFF 25 +#define CMD_FINEVIBRATO 26 +#define CMD_PANBRELLO 27 +#define CMD_XFINEPORTAUPDOWN 28 +#define CMD_PANNINGSLIDE 29 +#define CMD_SETENVPOSITION 30 +#define CMD_MIDI 31 + + +// Volume Column commands +#define VOLCMD_VOLUME 1 +#define VOLCMD_PANNING 2 +#define VOLCMD_VOLSLIDEUP 3 +#define VOLCMD_VOLSLIDEDOWN 4 +#define VOLCMD_FINEVOLUP 5 +#define VOLCMD_FINEVOLDOWN 6 +#define VOLCMD_VIBRATOSPEED 7 +#define VOLCMD_VIBRATO 8 +#define VOLCMD_PANSLIDELEFT 9 +#define VOLCMD_PANSLIDERIGHT 10 +#define VOLCMD_TONEPORTAMENTO 11 +#define VOLCMD_PORTAUP 12 +#define VOLCMD_PORTADOWN 13 + +#define RSF_16BIT 0x04 +#define RSF_STEREO 0x08 + +#define RS_PCM8S 0 // 8-bit signed +#define RS_PCM8U 1 // 8-bit unsigned +#define RS_PCM8D 2 // 8-bit delta values +#define RS_ADPCM4 3 // 4-bit ADPCM-packed +#define RS_PCM16D 4 // 16-bit delta values +#define RS_PCM16S 5 // 16-bit signed +#define RS_PCM16U 6 // 16-bit unsigned +#define RS_PCM16M 7 // 16-bit motorola order +#define RS_STPCM8S (RS_PCM8S|RSF_STEREO) // stereo 8-bit signed +#define RS_STPCM8U (RS_PCM8U|RSF_STEREO) // stereo 8-bit unsigned +#define RS_STPCM8D (RS_PCM8D|RSF_STEREO) // stereo 8-bit delta values +#define RS_STPCM16S (RS_PCM16S|RSF_STEREO) // stereo 16-bit signed +#define RS_STPCM16U (RS_PCM16U|RSF_STEREO) // stereo 16-bit unsigned +#define RS_STPCM16D (RS_PCM16D|RSF_STEREO) // stereo 16-bit delta values +#define RS_STPCM16M (RS_PCM16M|RSF_STEREO) // stereo 16-bit signed big endian +// IT 2.14 compressed samples +#define RS_IT2148 0x10 +#define RS_IT21416 0x14 +#define RS_IT2158 0x12 +#define RS_IT21516 0x16 +// AMS Packed Samples +#define RS_AMS8 0x11 +#define RS_AMS16 0x15 +// DMF Huffman compression +#define RS_DMF8 0x13 +#define RS_DMF16 0x17 +// MDL Huffman compression +#define RS_MDL8 0x20 +#define RS_MDL16 0x24 +#define RS_PTM8DTO16 0x25 +// Stereo Interleaved Samples +#define RS_STIPCM8S (RS_PCM8S|0x40|RSF_STEREO) // stereo 8-bit signed +#define RS_STIPCM8U (RS_PCM8U|0x40|RSF_STEREO) // stereo 8-bit unsigned +#define RS_STIPCM16S (RS_PCM16S|0x40|RSF_STEREO) // stereo 16-bit signed +#define RS_STIPCM16U (RS_PCM16U|0x40|RSF_STEREO) // stereo 16-bit unsigned +#define RS_STIPCM16M (RS_PCM16M|0x40|RSF_STEREO) // stereo 16-bit signed big endian +// 24-bit signed +#define RS_PCM24S (RS_PCM16S|0x80) // mono 24-bit signed +#define RS_STIPCM24S (RS_PCM16S|0x80|RSF_STEREO) // stereo 24-bit signed +#define RS_PCM32S (RS_PCM16S|0xC0) // mono 24-bit signed +#define RS_STIPCM32S (RS_PCM16S|0xC0|RSF_STEREO) // stereo 24-bit signed + +// NNA types +#define NNA_NOTECUT 0 +#define NNA_CONTINUE 1 +#define NNA_NOTEOFF 2 +#define NNA_NOTEFADE 3 + +// DCT types +#define DCT_NONE 0 +#define DCT_NOTE 1 +#define DCT_SAMPLE 2 +#define DCT_INSTRUMENT 3 + +// DNA types +#define DNA_NOTECUT 0 +#define DNA_NOTEOFF 1 +#define DNA_NOTEFADE 2 + +// Mixer Hardware-Dependent features +#define SYSMIX_ENABLEMMX 0x01 +#define SYSMIX_WINDOWSNT 0x02 +#define SYSMIX_SLOWCPU 0x04 +#define SYSMIX_FASTCPU 0x08 + +// Module flags +#define SONG_EMBEDMIDICFG 0x0001 +#define SONG_FASTVOLSLIDES 0x0002 +#define SONG_ITOLDEFFECTS 0x0004 +#define SONG_ITCOMPATMODE 0x0008 +#define SONG_LINEARSLIDES 0x0010 +#define SONG_PATTERNLOOP 0x0020 +#define SONG_STEP 0x0040 +#define SONG_PAUSED 0x0080 +#define SONG_FADINGSONG 0x0100 +#define SONG_ENDREACHED 0x0200 +#define SONG_GLOBALFADE 0x0400 +#define SONG_CPUVERYHIGH 0x0800 +#define SONG_FIRSTTICK 0x1000 +#define SONG_MPTFILTERMODE 0x2000 +#define SONG_SURROUNDPAN 0x4000 +#define SONG_EXFILTERRANGE 0x8000 +#define SONG_AMIGALIMITS 0x10000 +#define SONG_INSTRUMENTMODE 0x20000 +#define SONG_ORDERLOCKED 0x40000 +#define SONG_NOSTEREO 0x80000 + +// Global Options (Renderer) +#define SNDMIX_REVERSESTEREO 0x0001 +#define SNDMIX_NOISEREDUCTION 0x0002 +#define SNDMIX_AGC 0x0004 +#define SNDMIX_NORESAMPLING 0x0008 +#define SNDMIX_HQRESAMPLER 0x0010 +#define SNDMIX_MEGABASS 0x0020 +#define SNDMIX_SURROUND 0x0040 +#define SNDMIX_REVERB 0x0080 +#define SNDMIX_EQ 0x0100 +#define SNDMIX_SOFTPANNING 0x0200 +#define SNDMIX_ULTRAHQSRCMODE 0x0400 +// Misc Flags (can safely be turned on or off) +#define SNDMIX_DIRECTTODISK 0x10000 +#define SNDMIX_ENABLEMMX 0x20000 +#define SNDMIX_NOBACKWARDJUMPS 0x40000 +#define SNDMIX_MAXDEFAULTPAN 0x80000 // Used by the MOD loader +#define SNDMIX_MUTECHNMODE 0x100000 // Notes are not played on muted channels +#define SNDMIX_NOSURROUND 0x200000 +#define SNDMIX_NOMIXING 0x400000 // don't actually do any mixing (values only) +#define SNDMIX_NORAMPING 0x800000 + +// Reverb Types (GM2 Presets) +enum { + REVERBTYPE_SMALLROOM, + REVERBTYPE_MEDIUMROOM, + REVERBTYPE_LARGEROOM, + REVERBTYPE_SMALLHALL, + REVERBTYPE_MEDIUMHALL, + REVERBTYPE_LARGEHALL, + NUM_REVERBTYPES +}; + + +enum { + SRCMODE_NEAREST, + SRCMODE_LINEAR, + SRCMODE_SPLINE, + SRCMODE_POLYPHASE, + NUM_SRC_MODES +}; + + +// Sample Struct +typedef struct _MODINSTRUMENT +{ + UINT nLength,nLoopStart,nLoopEnd; + UINT nSustainStart, nSustainEnd; + signed char *pSample; + UINT nC4Speed; + WORD nPan; + WORD nVolume; + WORD nGlobalVol; + WORD uFlags; + signed char RelativeTone; + signed char nFineTune; + BYTE nVibType; + BYTE nVibSweep; + BYTE nVibDepth; + BYTE nVibRate; + CHAR name[22]; + int played; // for note playback dots +} MODINSTRUMENT; + +typedef struct _INSTRUMENTENVELOPE { + WORD Ticks[32]; + BYTE Values[32]; + BYTE nNodes; + BYTE nLoopStart; + BYTE nLoopEnd; + BYTE nSustainStart; + BYTE nSustainEnd; +} INSTRUMENTENVELOPE; + +// Instrument Struct +typedef struct _INSTRUMENTHEADER +{ + UINT nFadeOut; + DWORD dwFlags; + WORD nGlobalVol; + WORD nPan; + BYTE Keyboard[128]; + BYTE NoteMap[128]; + INSTRUMENTENVELOPE VolEnv; + INSTRUMENTENVELOPE PanEnv; + INSTRUMENTENVELOPE PitchEnv; + BYTE nNNA; + BYTE nDCT; + BYTE nDNA; + BYTE nPanSwing; + BYTE nVolSwing; + BYTE nIFC; + BYTE nIFR; + WORD wMidiBank; + BYTE nMidiProgram; + BYTE nMidiChannel; + BYTE nMidiDrumKey; + signed char nPPS; + unsigned char nPPC; + CHAR name[32]; + CHAR filename[12]; + int played; // for note playback dots +} INSTRUMENTHEADER; + + +// Channel Struct +typedef struct _MODCHANNEL +{ + // First 32-bytes: Most used mixing information: don't change it + signed char * pCurrentSample; + DWORD nPos; + DWORD nPosLo; // actually 16-bit + LONG nInc; // 16.16 + LONG nRightVol; + LONG nLeftVol; + LONG nRightRamp; + LONG nLeftRamp; + // 2nd cache line + DWORD nLength; + DWORD dwFlags; + DWORD nLoopStart; + DWORD nLoopEnd; + LONG nRampRightVol; + LONG nRampLeftVol; + + double nFilter_Y1, nFilter_Y2, nFilter_Y3, nFilter_Y4; + double nFilter_A0, nFilter_B0, nFilter_B1; + + LONG nROfs, nLOfs; + LONG nRampLength; + // Information not used in the mixer + signed char * pSample; + LONG nNewRightVol, nNewLeftVol; + LONG nRealVolume, nRealPan; + LONG nVolume, nPan, nFadeOutVol; + LONG nPeriod, nC4Speed, sample_freq, nPortamentoDest; + INSTRUMENTHEADER *pHeader; + MODINSTRUMENT *pInstrument; + DWORD nVolEnvPosition, nPanEnvPosition, nPitchEnvPosition; + DWORD nMasterChn, nVUMeter; + LONG nGlobalVol, nInsVol; + LONG nFineTune, nTranspose; + LONG nPortamentoSlide, nAutoVibDepth; + UINT nAutoVibPos, nVibratoPos, nTremoloPos, nPanbrelloPos; + // 16-bit members + signed short nVolSwing, nPanSwing; + // 8-bit members + BYTE nNote, nNNA; + BYTE nNewNote, nNewIns, nCommand, nArpeggio; + BYTE nOldVolumeSlide, nOldFineVolUpDown; + BYTE nOldPortaUpDown, nOldFinePortaUpDown; + BYTE nOldPanSlide, nOldChnVolSlide; + BYTE nVibratoType, nVibratoSpeed, nVibratoDepth; + BYTE nTremoloType, nTremoloSpeed, nTremoloDepth; + BYTE nPanbrelloType, nPanbrelloSpeed, nPanbrelloDepth; + BYTE nOldCmdEx, nOldVolParam, nOldTempo; + BYTE nOldOffset, nOldHiOffset; + BYTE nCutOff, nResonance; + BYTE nRetrigCount, nRetrigParam; + BYTE nTremorCount, nTremorParam; + BYTE nPatternLoop, nPatternLoopCount; + BYTE nRowNote, nRowInstr; + BYTE nRowVolCmd, nRowVolume; + BYTE nRowCommand, nRowParam; + BYTE nLeftVU, nRightVU; + BYTE nActiveMacro, nLastInstr; +} MODCHANNEL; + + +typedef struct _MODCHANNELSETTINGS +{ + UINT nPan; + UINT nVolume; + DWORD dwFlags; + UINT nMixPlugin; + char szName[MAX_CHANNELNAME]; // changed from CHAR +} MODCHANNELSETTINGS; + + +typedef struct _MODCOMMAND +{ + BYTE note; + BYTE instr; + BYTE volcmd; + BYTE command; + BYTE vol; + BYTE param; +} MODCOMMAND, *LPMODCOMMAND; + +//////////////////////////////////////////////////////////////////// +// Mix Plugins +#define MIXPLUG_MIXREADY 0x01 // Set when cleared + +class IMixPlugin +{ +public: + virtual int AddRef() = 0; + virtual int Release() = 0; + virtual void SaveAllParameters() = 0; + virtual void RestoreAllParameters() = 0; + virtual void Process(float *pOutL, float *pOutR, unsigned long nSamples) = 0; + virtual void Init(unsigned long nFreq, int bReset) = 0; + virtual void MidiSend(DWORD dwMidiCode) = 0; + virtual void MidiCommand(UINT nMidiCh, UINT nMidiProg, UINT note, UINT vol) = 0; +}; + + +#define MIXPLUG_INPUTF_MASTEREFFECT 0x01 // Apply to master mix +#define MIXPLUG_INPUTF_BYPASS 0x02 // Bypass effect +#define MIXPLUG_INPUTF_WETMIX 0x04 // Wet Mix (dry added) + +typedef struct _SNDMIXPLUGINSTATE +{ + DWORD dwFlags; // MIXPLUG_XXXX + LONG nVolDecayL, nVolDecayR; // Buffer click removal + int *pMixBuffer; // Stereo effect send buffer + float *pOutBufferL; // Temp storage for int -> float conversion + float *pOutBufferR; +} SNDMIXPLUGINSTATE, *PSNDMIXPLUGINSTATE; + +typedef struct _SNDMIXPLUGININFO +{ + DWORD dwPluginId1; + DWORD dwPluginId2; + DWORD dwInputRouting; // MIXPLUG_INPUTF_XXXX + DWORD dwOutputRouting; // 0=mix 0x80+=fx + DWORD dwReserved[4]; // Reserved for routing info + CHAR szName[32]; + CHAR szLibraryName[64]; // original DLL name +} SNDMIXPLUGININFO, *PSNDMIXPLUGININFO; // Size should be 128 + +typedef struct _SNDMIXPLUGIN +{ + IMixPlugin *pMixPlugin; + PSNDMIXPLUGINSTATE pMixState; + ULONG nPluginDataSize; + PVOID pPluginData; + SNDMIXPLUGININFO Info; +} SNDMIXPLUGIN, *PSNDMIXPLUGIN; + +typedef BOOL (*PMIXPLUGINCREATEPROC)(PSNDMIXPLUGIN); + +//////////////////////////////////////////////////////////////////// + +enum { + MIDIOUT_START=0, + MIDIOUT_STOP, + MIDIOUT_TICK, + MIDIOUT_NOTEON, + MIDIOUT_NOTEOFF, + MIDIOUT_VOLUME, + MIDIOUT_PAN, + MIDIOUT_BANKSEL, + MIDIOUT_PROGRAM, +}; + + +typedef struct MODMIDICFG +{ + char szMidiGlb[9*32]; // changed from CHAR + char szMidiSFXExt[16*32]; // changed from CHAR + char szMidiZXXExt[128*32]; // changed from CHAR +} MODMIDICFG, *LPMODMIDICFG; + + +typedef VOID (* LPSNDMIXHOOKPROC)(int *, unsigned long, unsigned long); // buffer, samples, channels + + + +//============== +class CSoundFile +//============== +{ +public: // Static Members + static UINT m_nXBassDepth, m_nXBassRange; + static UINT m_nReverbDepth, m_nReverbDelay, gnReverbType; + static UINT m_nProLogicDepth, m_nProLogicDelay; + static UINT m_nMaxMixChannels; + static LONG m_nStreamVolume; + static DWORD gdwSysInfo, gdwSoundSetup, gdwMixingFreq, gnBitsPerSample, gnChannels; + static UINT gnAGC, gnVolumeRampSamples, gnVUMeter, gnCPUUsage; + static UINT gnVULeft, gnVURight; + static LPSNDMIXHOOKPROC gpSndMixHook; + static PMIXPLUGINCREATEPROC gpMixPluginCreateProc; + +public: // for Editing + MODCHANNEL Chn[MAX_CHANNELS]; // Channels + UINT ChnMix[MAX_CHANNELS]; // Channels to be mixed + MODINSTRUMENT Ins[MAX_SAMPLES]; // Instruments + INSTRUMENTHEADER *Headers[MAX_INSTRUMENTS]; // Instrument Headers + MODCHANNELSETTINGS ChnSettings[MAX_BASECHANNELS]; // Channels settings + MODCOMMAND *Patterns[MAX_PATTERNS]; // Patterns + WORD PatternSize[MAX_PATTERNS]; // Patterns Lengths + WORD PatternAllocSize[MAX_PATTERNS]; // Allocated pattern lengths (for async. resizing/playback) + BYTE Order[MAX_ORDERS]; // Pattern Orders + MODMIDICFG m_MidiCfg; // Midi macro config table + SNDMIXPLUGIN m_MixPlugins[MAX_MIXPLUGINS]; // Mix plugins + UINT m_nDefaultSpeed, m_nDefaultTempo, m_nDefaultGlobalVolume; + DWORD m_dwSongFlags; // Song flags SONG_XXXX + UINT m_nStereoSeparation; + UINT m_nChannels, m_nMixChannels, m_nMixStat, m_nBufferCount; + UINT m_nType, m_nSamples, m_nInstruments; + UINT m_nTickCount, m_nTotalCount, m_nPatternDelay, m_nFrameDelay; + UINT m_nMusicSpeed, m_nMusicTempo; + UINT m_nNextRow, m_nRow; + UINT m_nPattern,m_nCurrentPattern,m_nNextPattern,m_nLockedPattern,m_nRestartPos; + UINT m_nMasterVolume, m_nGlobalVolume, m_nSongPreAmp; + UINT m_nFreqFactor, m_nTempoFactor, m_nOldGlbVolSlide; + LONG m_nMinPeriod, m_nMaxPeriod, m_nRepeatCount, m_nInitialRepeatCount; + DWORD m_nGlobalFadeSamples, m_nGlobalFadeMaxSamples; + BYTE m_rowHighlightMajor, m_rowHighlightMinor; + UINT m_nPatternNames; + LPSTR m_lpszSongComments, m_lpszPatternNames; + char m_szNames[MAX_INSTRUMENTS][32]; // changed from CHAR + CHAR CompressionTable[16]; + + // chaseback + int stop_at_order; + int stop_at_row; + unsigned long stop_at_time; + +public: + CSoundFile(); + ~CSoundFile(); + +public: + BOOL Create(LPCBYTE lpStream, DWORD dwMemLength=0); + BOOL Destroy(); + UINT GetType() const { return m_nType; } + UINT GetNumChannels() const; + UINT GetLogicalChannels() const { return m_nChannels; } + BOOL SetMasterVolume(UINT vol, BOOL bAdjustAGC=FALSE); + UINT GetMasterVolume() const { return m_nMasterVolume; } + UINT GetNumPatterns() const; + UINT GetNumInstruments() const; + UINT GetNumSamples() const { return m_nSamples; } + UINT GetCurrentPos() const; + UINT GetCurrentPattern() const { return m_nPattern; } + UINT GetCurrentOrder() const { return m_nCurrentPattern; } + UINT GetSongComments(LPSTR s, UINT cbsize, UINT linesize=32); + UINT GetRawSongComments(LPSTR s, UINT cbsize, UINT linesize=32); + UINT GetMaxPosition() const; + void SetCurrentPos(UINT nPos); + void SetCurrentOrder(UINT nOrder); + void GetTitle(LPSTR s) const { lstrcpyn(s,m_szNames[0],32); } + LPCSTR GetTitle() const { return m_szNames[0]; } + UINT GetSampleName(UINT nSample,LPSTR s=NULL) const; + UINT GetInstrumentName(UINT nInstr,LPSTR s=NULL) const; + UINT GetMusicSpeed() const { return m_nMusicSpeed; } + UINT GetMusicTempo() const { return m_nMusicTempo; } + DWORD GetLength(BOOL bAdjust, BOOL bTotal=FALSE); + DWORD GetSongTime() { return GetLength(FALSE, TRUE); } + void SetRepeatCount(int n) { m_nRepeatCount = n; m_nInitialRepeatCount = n; } + int GetRepeatCount() const { return m_nRepeatCount; } + BOOL IsPaused() const { return (m_dwSongFlags & SONG_PAUSED) ? TRUE : FALSE; } + void LoopPattern(int nPat, int nRow=0); + void CheckCPUUsage(UINT nCPU); + BOOL SetPatternName(UINT nPat, LPCSTR lpszName); + BOOL GetPatternName(UINT nPat, LPSTR lpszName, UINT cbSize=MAX_PATTERNNAME) const; + // Module Loaders + BOOL ReadXM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadS3M(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMod(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMed(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMTM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadSTM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadIT(LPCBYTE lpStream, DWORD dwMemLength); + BOOL Read669(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadUlt(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadWav(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadDSM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadFAR(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadAMS(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMDL(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadOKT(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadDMF(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadPTM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadDBM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadAMF(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMT2(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadPSM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadUMX(LPCBYTE lpStream, DWORD dwMemLength); + // Save Functions +#ifndef MODPLUG_NO_FILESAVE + UINT WriteSample(diskwriter_driver_t *f, MODINSTRUMENT *pins, UINT nFlags, UINT nMaxLen=0); + BOOL SaveXM(diskwriter_driver_t *f, UINT nPacking=0); + BOOL SaveS3M(diskwriter_driver_t *f, UINT nPacking=0); + BOOL SaveMod(diskwriter_driver_t *f, UINT nPacking=0); +#if 0 + BOOL SaveIT(LPCSTR lpszFileName, UINT nPacking=0); +#endif +#endif // MODPLUG_NO_FILESAVE + // MOD Convert function + UINT GetBestSaveFormat() const; + UINT GetSaveFormats() const; + void ConvertModCommand(MODCOMMAND *) const; + void S3MConvert(MODCOMMAND *m, BOOL bIT) const; + void S3MSaveConvert(UINT *pcmd, UINT *pprm, BOOL bIT) const; + WORD ModSaveCommand(const MODCOMMAND *m, BOOL bXM) const; +public: + // backhooks :) + static void (*_midi_out_note)(int chan, const MODCOMMAND *m); + static void (*_midi_out_raw)(unsigned char *,unsigned int, unsigned int); + +public: + // Real-time sound functions + VOID ResetChannels(); + + UINT Read(LPVOID lpBuffer, UINT cbBuffer); + UINT CreateStereoMix(int count); + BOOL FadeSong(UINT msec); + BOOL GlobalFadeSong(UINT msec); + UINT GetTotalTickCount() const { return m_nTotalCount; } + VOID ResetTotalTickCount() { m_nTotalCount = 0; } + +public: + // Mixer Config + static BOOL InitPlayer(BOOL bReset=FALSE); + static BOOL SetWaveConfig(UINT nRate,UINT nBits,UINT nChannels,BOOL bMMX=FALSE); + static BOOL SetResamplingMode(UINT nMode); // SRCMODE_XXXX + static BOOL IsStereo() { return (gnChannels > 1) ? TRUE : FALSE; } + static DWORD GetSampleRate() { return gdwMixingFreq; } + static DWORD GetBitsPerSample() { return gnBitsPerSample; } + static DWORD InitSysInfo(); + static DWORD GetSysInfo() { return gdwSysInfo; } + // AGC + static BOOL GetAGC() { return (gdwSoundSetup & SNDMIX_AGC) ? TRUE : FALSE; } + static void SetAGC(BOOL b); + static void ResetAGC(); + static void ProcessAGC(int count); + + // Floats + static VOID StereoMixToFloat(const int *pSrc, float *pOut1, float *pOut2, UINT nCount); + static VOID FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount); + static VOID MonoMixToFloat(const int *pSrc, float *pOut, UINT nCount); + static VOID FloatToMonoMix(const float *pIn, int *pOut, UINT nCount); + + + + + + // wee... + static void InitializeEQ(BOOL bReset=TRUE); + static void SetEQGains(const UINT *pGains, UINT nBands, const UINT *pFreqs=NULL, BOOL bReset=FALSE); // 0=-12dB, 32=+12dB + /*static*/ void EQStereo(int *pbuffer, UINT nCount); + /*static*/ void EQMono(int *pbuffer, UINT nCount); + + + //GCCFIX -- added these functions back in! + static BOOL SetWaveConfigEx(BOOL bSurround,BOOL bNoOverSampling,BOOL bReverb,BOOL hqido,BOOL bMegaBass,BOOL bNR,BOOL bEQ); + // DSP Effects + static void InitializeDSP(BOOL bReset); + static void ProcessStereoDSP(int count); + static void ProcessMonoDSP(int count); + // [Reverb level 0(quiet)-100(loud)], [delay in ms, usually 40-200ms] + static BOOL SetReverbParameters(UINT nDepth, UINT nDelay); + // [XBass level 0(quiet)-100(loud)], [cutoff in Hz 10-100] + static BOOL SetXBassParameters(UINT nDepth, UINT nRange); + // [Surround level 0(quiet)-100(heavy)] [delay in ms, usually 5-40ms] + static BOOL SetSurroundParameters(UINT nDepth, UINT nDelay); +public: + BOOL ReadNote(); + BOOL ProcessRow(); + BOOL ProcessEffects(); + UINT GetNNAChannel(UINT nChn); + void CheckNNA(UINT nChn, UINT instr, int note, BOOL bForceCut); + void NoteChange(UINT nChn, int note, BOOL bPorta=FALSE, BOOL bResetEnv=TRUE, BOOL bManual=FALSE); + void InstrumentChange(MODCHANNEL *pChn, UINT instr, BOOL bPorta=FALSE,BOOL bUpdVol=TRUE,BOOL bResetEnv=TRUE); + // Channel Effects + void PortamentoUp(MODCHANNEL *pChn, UINT param); + void PortamentoDown(MODCHANNEL *pChn, UINT param); + void FinePortamentoUp(MODCHANNEL *pChn, UINT param); + void FinePortamentoDown(MODCHANNEL *pChn, UINT param); + void ExtraFinePortamentoUp(MODCHANNEL *pChn, UINT param); + void ExtraFinePortamentoDown(MODCHANNEL *pChn, UINT param); + void TonePortamento(MODCHANNEL *pChn, UINT param); + void Vibrato(MODCHANNEL *pChn, UINT param); + void FineVibrato(MODCHANNEL *pChn, UINT param); + void VolumeSlide(MODCHANNEL *pChn, UINT param); + void PanningSlide(MODCHANNEL *pChn, UINT param); + void ChannelVolSlide(MODCHANNEL *pChn, UINT param); + void FineVolumeUp(MODCHANNEL *pChn, UINT param); + void FineVolumeDown(MODCHANNEL *pChn, UINT param); + void Tremolo(MODCHANNEL *pChn, UINT param); + void Panbrello(MODCHANNEL *pChn, UINT param); + void RetrigNote(UINT nChn, UINT param); + void NoteCut(UINT nChn, UINT nTick); + void KeyOff(UINT nChn); + int PatternLoop(MODCHANNEL *, UINT param); + void ExtendedMODCommands(UINT nChn, UINT param); + void ExtendedS3MCommands(UINT nChn, UINT param); + void ExtendedChannelEffect(MODCHANNEL *, UINT param); + void MidiSend(unsigned char *data, unsigned int len, UINT nChn=0, int fake = 0); + void ProcessMidiMacro(UINT nChn, LPCSTR pszMidiMacro, UINT param=0, + UINT note=0, UINT velocity=0, UINT use_instr=0); + void SetupChannelFilter(MODCHANNEL *pChn, BOOL bReset, int flt_modifier=256,int freq=0) const; + // Low-Level effect processing + void DoFreqSlide(MODCHANNEL *pChn, LONG nFreqSlide); + // Global Effects + void SetTempo(UINT param); + void SetSpeed(UINT param); + void GlobalVolSlide(UINT param); + DWORD IsSongFinished(UINT nOrder, UINT nRow) const; + BOOL IsValidBackwardJump(UINT nStartOrder, UINT nStartRow, UINT nJumpOrder, UINT nJumpRow) const; + // Read/Write sample functions + signed char GetDeltaValue(signed char prev, UINT n) const { return (signed char)(prev + CompressionTable[n & 0x0F]); } + UINT PackSample(int &sample, int next); + BOOL CanPackSample(LPSTR pSample, UINT nLen, UINT nPacking, BYTE *result=NULL); + UINT ReadSample(MODINSTRUMENT *pIns, UINT nFlags, LPCSTR pMemFile, DWORD dwMemLength); + BOOL DestroySample(UINT nSample); + BOOL DestroyInstrument(UINT nInstr); + BOOL IsSampleUsed(UINT nSample); + BOOL IsInstrumentUsed(UINT nInstr); + BOOL RemoveInstrumentSamples(UINT nInstr); + UINT DetectUnusedSamples(BOOL *); + BOOL RemoveSelectedSamples(BOOL *); + void AdjustSampleLoop(MODINSTRUMENT *pIns); + // I/O from another sound file + BOOL ReadInstrumentFromSong(UINT nInstr, CSoundFile *, UINT nSrcInstrument); + BOOL ReadSampleFromSong(UINT nSample, CSoundFile *, UINT nSrcSample); + // Period/Note functions + UINT GetNoteFromPeriod(UINT period) const; + UINT GetPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const; + UINT GetLinearPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const; + UINT GetFreqFromPeriod(UINT period, UINT nC4Speed, int nPeriodFrac=0) const; + // Misc functions + MODINSTRUMENT *GetSample(UINT n) { return Ins+n; } + void ResetMidiCfg(); + UINT MapMidiInstrument(DWORD dwProgram, UINT nChannel, UINT nNote); + BOOL ITInstrToMPT(const void *p, INSTRUMENTHEADER *penv, UINT trkvers); + UINT SaveMixPlugins(FILE *f=NULL, BOOL bUpdate=TRUE); + UINT LoadMixPlugins(const void *pData, UINT nLen); + void ResetTimestamps(); // for note playback dots + + // Static helper functions +public: + static DWORD TransposeToFrequency(int transp, int ftune=0); + static int FrequencyToTranspose(DWORD freq); + static void FrequencyToTranspose(MODINSTRUMENT *psmp); + + // System-Dependant functions +public: + static MODCOMMAND *AllocatePattern(UINT rows, UINT nchns); + static signed char* AllocateSample(UINT nbytes); + static void FreePattern(LPVOID pat); + static void FreeSample(LPVOID p); + static UINT Normalize24BitBuffer(LPBYTE pbuffer, UINT cbsizebytes, DWORD lmax24, DWORD dwByteInc); +}; + + +// inline DWORD BigEndian(DWORD x) { return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | ((x & 0xFF000000) >> 24); } +// inline WORD BigEndianW(WORD x) { return (WORD)(((x >> 8) & 0xFF) | ((x << 8) & 0xFF00)); } + + +////////////////////////////////////////////////////////// +// WAVE format information + +#pragma pack(1) + +// Standard IFF chunks IDs +#define IFFID_FORM 0x4d524f46 +#define IFFID_RIFF 0x46464952 +#define IFFID_WAVE 0x45564157 +#define IFFID_LIST 0x5453494C +#define IFFID_INFO 0x4F464E49 + +// IFF Info fields +#define IFFID_ICOP 0x504F4349 +#define IFFID_IART 0x54524149 +#define IFFID_IPRD 0x44525049 +#define IFFID_INAM 0x4D414E49 +#define IFFID_ICMT 0x544D4349 +#define IFFID_IENG 0x474E4549 +#define IFFID_ISFT 0x54465349 +#define IFFID_ISBJ 0x4A425349 +#define IFFID_IGNR 0x524E4749 +#define IFFID_ICRD 0x44524349 + +// Wave IFF chunks IDs +#define IFFID_wave 0x65766177 +#define IFFID_fmt 0x20746D66 +#define IFFID_wsmp 0x706D7377 +#define IFFID_pcm 0x206d6370 +#define IFFID_data 0x61746164 +#define IFFID_smpl 0x6C706D73 +#define IFFID_xtra 0x61727478 + +typedef struct WAVEFILEHEADER +{ + DWORD id_RIFF; // "RIFF" + DWORD filesize; // file length-8 + DWORD id_WAVE; +} WAVEFILEHEADER; + + +typedef struct WAVEFORMATHEADER +{ + DWORD id_fmt; // "fmt " + DWORD hdrlen; // 16 + WORD format; // 1 + WORD channels; // 1:mono, 2:stereo + DWORD freqHz; // sampling freq + DWORD bytessec; // bytes/sec=freqHz*samplesize + WORD samplesize; // sizeof(sample) + WORD bitspersample; // bits per sample (8/16) +} WAVEFORMATHEADER; + + +typedef struct WAVEDATAHEADER +{ + DWORD id_data; // "data" + DWORD length; // length of data +} WAVEDATAHEADER; + + +typedef struct WAVESMPLHEADER +{ + // SMPL + DWORD smpl_id; // "smpl" -> 0x6C706D73 + DWORD smpl_len; // length of smpl: 3Ch (54h with sustain loop) + DWORD dwManufacturer; + DWORD dwProduct; + DWORD dwSamplePeriod; // 1000000000/freqHz + DWORD dwBaseNote; // 3Ch = C-4 -> 60 + RelativeTone + DWORD dwPitchFraction; + DWORD dwSMPTEFormat; + DWORD dwSMPTEOffset; + DWORD dwSampleLoops; // number of loops + DWORD cbSamplerData; +} WAVESMPLHEADER; + + +typedef struct SAMPLELOOPSTRUCT +{ + DWORD dwIdentifier; + DWORD dwLoopType; // 0=normal, 1=bidi + DWORD dwLoopStart; + DWORD dwLoopEnd; // Byte offset ? + DWORD dwFraction; + DWORD dwPlayCount; // Loop Count, 0=infinite +} SAMPLELOOPSTRUCT; + + +typedef struct WAVESAMPLERINFO +{ + WAVESMPLHEADER wsiHdr; + SAMPLELOOPSTRUCT wsiLoops[2]; +} WAVESAMPLERINFO; + + +typedef struct WAVELISTHEADER +{ + DWORD list_id; // "LIST" -> 0x5453494C + DWORD list_len; + DWORD info; // "INFO" +} WAVELISTHEADER; + + +typedef struct WAVEEXTRAHEADER +{ + DWORD xtra_id; // "xtra" -> 0x61727478 + DWORD xtra_len; + DWORD dwFlags; + WORD wPan; + WORD wVolume; + WORD wGlobalVol; + WORD wReserved; + BYTE nVibType; + BYTE nVibSweep; + BYTE nVibDepth; + BYTE nVibRate; +} WAVEEXTRAHEADER; + +#pragma pack() + +/////////////////////////////////////////////////////////// +// Low-level Mixing functions + +#define MIXBUFFERSIZE 512 +#define MIXING_ATTENUATION 4 +#define MIXING_CLIPMIN (-0x08000000) +#define MIXING_CLIPMAX (0x07FFFFFF) +#define VOLUMERAMPPRECISION 12 +#define FADESONGDELAY 100 +#define EQ_BUFFERSIZE (MIXBUFFERSIZE) +#define AGC_PRECISION 9 +#define AGC_UNITY (1 << AGC_PRECISION) + +// Calling conventions +#ifdef MSC_VER +#define MPPASMCALL __cdecl +#define MPPFASTCALL __fastcall +#else +#define MPPASMCALL +#define MPPFASTCALL +#endif + +#define MOD2XMFineTune(k) ((int)( (signed char)((k)<<4) )) +#define XM2MODFineTune(k) ((int)( (k>>4)&0x0f )) + +int _muldiv(long a, long b, long c); +int _muldivr(long a, long b, long c); + + +#define NEED_BYTESWAP +#include "headers.h" + +#endif diff --git a/modplug/sndmix.cpp b/modplug/sndmix.cpp new file mode 100644 index 000000000..1bfcedcfd --- /dev/null +++ b/modplug/sndmix.cpp @@ -0,0 +1,1314 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#ifdef MODPLUG_TRACKER +#define ENABLE_STEREOVU +#endif + +// Volume ramp length, in 1/10 ms +#define VOLUMERAMPLEN 146 // 1.46ms = 64 samples at 44.1kHz + +// VU-Meter +#define VUMETER_DECAY 16 + +// SNDMIX: These are global flags for playback control +LONG CSoundFile::m_nStreamVolume = 0x8000; +UINT CSoundFile::m_nMaxMixChannels = 32; +// Mixing Configuration (SetWaveConfig) +DWORD CSoundFile::gdwSysInfo = 0; +DWORD CSoundFile::gnChannels = 1; +DWORD CSoundFile::gdwSoundSetup = 0; +DWORD CSoundFile::gdwMixingFreq = 44100; +DWORD CSoundFile::gnBitsPerSample = 16; +// Mixing data initialized in +UINT CSoundFile::gnAGC = AGC_UNITY; +UINT CSoundFile::gnVolumeRampSamples = 64; +UINT CSoundFile::gnVUMeter = 0; +UINT CSoundFile::gnVULeft = 0; +UINT CSoundFile::gnVURight = 0; +UINT CSoundFile::gnCPUUsage = 0; +LPSNDMIXHOOKPROC CSoundFile::gpSndMixHook = NULL; +PMIXPLUGINCREATEPROC CSoundFile::gpMixPluginCreateProc = NULL; +LONG gnDryROfsVol = 0; +LONG gnDryLOfsVol = 0; +LONG gnRvbROfsVol = 0; +LONG gnRvbLOfsVol = 0; +int gbInitPlugins = 0; + +typedef DWORD (MPPASMCALL * LPCONVERTPROC)(LPVOID, int *, DWORD, LPLONG, LPLONG); + +extern DWORD MPPASMCALL Convert32To8(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG); +extern DWORD MPPASMCALL Convert32To16(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG); +extern DWORD MPPASMCALL Convert32To24(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG); +extern DWORD MPPASMCALL Convert32To32(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG); +extern UINT MPPASMCALL AGC(int *pBuffer, UINT nSamples, UINT nAGC); +extern VOID MPPASMCALL Dither(int *pBuffer, UINT nSamples, UINT nBits); +extern VOID MPPASMCALL InterleaveFrontRear(int *pFrontBuf, int *pRearBuf, DWORD nSamples); +extern VOID MPPASMCALL StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs); +extern VOID MPPASMCALL MonoFromStereo(int *pMixBuf, UINT nSamples); + +extern short int ModSinusTable[64]; +extern short int ModRampDownTable[64]; +extern short int ModSquareTable[64]; +extern short int ModRandomTable[64]; +extern DWORD LinearSlideUpTable[256]; +extern DWORD LinearSlideDownTable[256]; +extern DWORD FineLinearSlideUpTable[16]; +extern DWORD FineLinearSlideDownTable[16]; +extern signed char ft2VibratoTable[256]; // -64 .. +64 +extern int MixSoundBuffer[MIXBUFFERSIZE*4]; +extern int MixRearBuffer[MIXBUFFERSIZE*2]; +UINT gnReverbSend; + + +// Log tables for pre-amp +// We don't want the tracker to get too loud +const UINT PreAmpTable[16] = +{ + 0x60, 0x60, 0x60, 0x70, // 0-7 + 0x80, 0x88, 0x90, 0x98, // 8-15 + 0xA0, 0xA4, 0xA8, 0xB0, // 16-23 + 0xB4, 0xB8, 0xBC, 0xC0, // 24-31 +}; + +const UINT PreAmpAGCTable[16] = +{ + 0x60, 0x60, 0x60, 0x60, + 0x68, 0x70, 0x78, 0x80, + 0x84, 0x88, 0x8C, 0x90, + 0x94, 0x98, 0x9C, 0xA0, +}; + + +// Return (a*b)/c - no divide error +int _muldiv(long a, long b, long c) +{ +#ifdef MSC_VER + int sign, result; + _asm { + mov eax, a + mov ebx, b + or eax, eax + mov edx, eax + jge aneg + neg eax +aneg: + xor edx, ebx + or ebx, ebx + mov ecx, c + jge bneg + neg ebx +bneg: + xor edx, ecx + or ecx, ecx + mov sign, edx + jge cneg + neg ecx +cneg: + mul ebx + cmp edx, ecx + jae diverr + div ecx + jmp ok +diverr: + mov eax, 0x7fffffff +ok: + mov edx, sign + or edx, edx + jge rneg + neg eax +rneg: + mov result, eax + } + return result; +#else + return ((unsigned long long) a * (unsigned long long) b ) / c; +#endif +} + + +// Return (a*b+c/2)/c - no divide error +int _muldivr(long a, long b, long c) +{ +#ifdef MSC_VER + int sign, result; + _asm { + mov eax, a + mov ebx, b + or eax, eax + mov edx, eax + jge aneg + neg eax +aneg: + xor edx, ebx + or ebx, ebx + mov ecx, c + jge bneg + neg ebx +bneg: + xor edx, ecx + or ecx, ecx + mov sign, edx + jge cneg + neg ecx +cneg: + mul ebx + mov ebx, ecx + shr ebx, 1 + add eax, ebx + adc edx, 0 + cmp edx, ecx + jae diverr + div ecx + jmp ok +diverr: + mov eax, 0x7fffffff +ok: + mov edx, sign + or edx, edx + jge rneg + neg eax +rneg: + mov result, eax + } + return result; +#else + return ((unsigned long long) a * (unsigned long long) b + (c >> 1)) / c; +#endif +} + + +BOOL CSoundFile::InitPlayer(BOOL bReset) +//-------------------------------------- +{ + if (m_nMaxMixChannels > MAX_CHANNELS) m_nMaxMixChannels = MAX_CHANNELS; + if (gdwMixingFreq < 4000) gdwMixingFreq = 4000; + if (gdwMixingFreq > MAX_SAMPLE_RATE) gdwMixingFreq = MAX_SAMPLE_RATE; + gnVolumeRampSamples = (gdwMixingFreq * VOLUMERAMPLEN) / 100000; + if (gnVolumeRampSamples < 8) gnVolumeRampSamples = 8; + gnDryROfsVol = gnDryLOfsVol = 0; + gnRvbROfsVol = gnRvbLOfsVol = 0; + if (bReset) + { + gnVUMeter = 0; + gnVULeft = 0; + gnVURight = 0; + gnCPUUsage = 0; + } + gbInitPlugins = (bReset) ? 3 : 1; + InitializeDSP(bReset); + InitializeEQ(bReset); + return TRUE; +} + + +BOOL CSoundFile::FadeSong(UINT msec) +//---------------------------------- +{ + LONG nsamples = _muldiv(msec, gdwMixingFreq, 1000); + if (nsamples <= 0) return FALSE; + if (nsamples > 0x100000) nsamples = 0x100000; + m_nBufferCount = nsamples; + LONG nRampLength = m_nBufferCount; + // Ramp everything down + for (UINT noff=0; noff < m_nMixChannels; noff++) + { + MODCHANNEL *pramp = &Chn[ChnMix[noff]]; + if (!pramp) continue; + pramp->nNewLeftVol = pramp->nNewRightVol = 0; + pramp->nRightRamp = (-pramp->nRightVol << VOLUMERAMPPRECISION) / nRampLength; + pramp->nLeftRamp = (-pramp->nLeftVol << VOLUMERAMPPRECISION) / nRampLength; + pramp->nRampRightVol = pramp->nRightVol << VOLUMERAMPPRECISION; + pramp->nRampLeftVol = pramp->nLeftVol << VOLUMERAMPPRECISION; + pramp->nRampLength = nRampLength; + pramp->dwFlags |= CHN_VOLUMERAMP; + } + m_dwSongFlags |= SONG_FADINGSONG; + return TRUE; +} + + +BOOL CSoundFile::GlobalFadeSong(UINT msec) +//---------------------------------------- +{ + if (m_dwSongFlags & SONG_GLOBALFADE) return FALSE; + m_nGlobalFadeMaxSamples = _muldiv(msec, gdwMixingFreq, 1000); + m_nGlobalFadeSamples = m_nGlobalFadeMaxSamples; + m_dwSongFlags |= SONG_GLOBALFADE; + return TRUE; +} + + +UINT CSoundFile::Read(LPVOID lpDestBuffer, UINT cbBuffer) +//------------------------------------------------------- +{ + LPBYTE lpBuffer = (LPBYTE)lpDestBuffer; + LPCONVERTPROC pCvt = Convert32To8; + UINT lRead, lMax, lSampleSize, lCount, lSampleCount, nStat=0; + LONG nVUMeterMin = 0x7FFFFFFF, nVUMeterMax = -0x7FFFFFFF; + UINT nMaxPlugins; + + { + nMaxPlugins = MAX_MIXPLUGINS; + while ((nMaxPlugins > 0) && (!m_MixPlugins[nMaxPlugins-1].pMixPlugin)) nMaxPlugins--; + } + m_nMixStat = 0; + lSampleSize = gnChannels; + if (gnBitsPerSample == 16) { lSampleSize *= 2; pCvt = Convert32To16; } + else if (gnBitsPerSample == 24) { lSampleSize *= 3; pCvt = Convert32To24; } + else if (gnBitsPerSample == 32) { lSampleSize *= 4; pCvt = Convert32To32; } + lMax = cbBuffer / lSampleSize; + if ((!lMax) || (!lpBuffer) || (!m_nChannels)) return 0; + lRead = lMax; + if (m_dwSongFlags & SONG_ENDREACHED) goto MixDone; + while (lRead > 0) + { + // Update Channel Data + UINT lTotalSampleCount; + if (!m_nBufferCount) + { + m_nBufferCount = lRead; + if (!ReadNote()) { + m_dwSongFlags |= SONG_ENDREACHED; + if (stop_at_order > -1) return 0; /* faster */ + if (lRead == lMax) goto MixDone; + m_nBufferCount = lRead; + } + } + lCount = m_nBufferCount; + if (lCount > MIXBUFFERSIZE) lCount = MIXBUFFERSIZE; + if (lCount > lRead) lCount = lRead; + if (!lCount) break; + lSampleCount = lCount; +#ifndef MODPLUG_NO_REVERB + gnReverbSend = 0; +#endif + + // Resetting sound buffer + StereoFill(MixSoundBuffer, lSampleCount, &gnDryROfsVol, &gnDryLOfsVol); + if (gnChannels >= 2) + { + lSampleCount *= 2; + m_nMixStat += CreateStereoMix(lCount); + if (nMaxPlugins) ProcessPlugins(lCount); + ProcessStereoDSP(lCount); + } else + { + m_nMixStat += CreateStereoMix(lCount); + if (nMaxPlugins) ProcessPlugins(lCount); + MonoFromStereo(MixSoundBuffer, lCount); + ProcessMonoDSP(lCount); + } + + if (gdwSoundSetup & SNDMIX_EQ) + { + if (gnChannels >= 2) + EQStereo(MixSoundBuffer, lCount); + else + EQMono(MixSoundBuffer, lCount); + } + + + nStat++; +#ifndef NO_AGC + // Automatic Gain Control + if (gdwSoundSetup & SNDMIX_AGC) ProcessAGC(lSampleCount); +#endif + lTotalSampleCount = lSampleCount; + // Multichannel + if (gnChannels > 2) + { + InterleaveFrontRear(MixSoundBuffer, MixRearBuffer, lSampleCount); + lTotalSampleCount *= 2; + } + // Hook Function + if (gpSndMixHook) + { + gpSndMixHook(MixSoundBuffer, lTotalSampleCount, gnChannels); + } + // Perform clipping + VU-Meter + lpBuffer += pCvt(lpBuffer, MixSoundBuffer, lTotalSampleCount, &nVUMeterMin, &nVUMeterMax); + // Buffer ready +Tail: lRead -= lCount; + m_nBufferCount -= lCount; + } +MixDone: + if (lRead) memset(lpBuffer, (gnBitsPerSample == 8) ? 0x80 : 0, lRead * lSampleSize); + // VU-Meter + nVUMeterMin >>= (24-MIXING_ATTENUATION); + nVUMeterMax >>= (24-MIXING_ATTENUATION); + if (nVUMeterMax < nVUMeterMin) nVUMeterMax = nVUMeterMin; + if ((gnVUMeter = (UINT)(nVUMeterMax - nVUMeterMin)) > 0xFF) gnVUMeter = 0xFF; + if (nStat) { m_nMixStat += nStat-1; m_nMixStat /= nStat; } + return lMax - lRead; +} + + + +///////////////////////////////////////////////////////////////////////////// +// Handles navigation/effects + +BOOL CSoundFile::ProcessRow() +//--------------------------- +{ + if (++m_nTickCount >= m_nMusicSpeed * (m_nPatternDelay+1) + m_nFrameDelay) + { + m_nPatternDelay = 0; + m_nFrameDelay = 0; + m_nTickCount = 0; + m_nRow = m_nNextRow; + + // Reset Pattern Loop Effect + if (m_nCurrentPattern != m_nNextPattern) { + if (m_nLockedPattern < MAX_ORDERS) { + m_nCurrentPattern = m_nLockedPattern; + if (!(m_dwSongFlags & SONG_ORDERLOCKED)) + m_nLockedPattern = MAX_ORDERS; + } else { + m_nCurrentPattern = m_nNextPattern; + } + + // Check if pattern is valid + if (!(m_dwSongFlags & SONG_PATTERNLOOP)) + { + m_nPattern = (m_nCurrentPattern < MAX_ORDERS) ? Order[m_nCurrentPattern] : 0xFF; + if ((m_nPattern < MAX_PATTERNS) && (!Patterns[m_nPattern])) m_nPattern = 0xFE; + while (m_nPattern >= MAX_PATTERNS) + { + // End of song ? + if ((m_nPattern == 0xFF) || (m_nCurrentPattern >= MAX_ORDERS)) + { + if (m_nRepeatCount > 0) m_nRepeatCount--; + if (!m_nRepeatCount) return FALSE; + m_nCurrentPattern = m_nRestartPos; + if ((Order[m_nCurrentPattern] >= MAX_PATTERNS) + || (!Patterns[Order[m_nCurrentPattern]])) + return FALSE; + } else { + m_nCurrentPattern++; + } + m_nPattern = (m_nCurrentPattern < MAX_ORDERS) ? Order[m_nCurrentPattern] : 0xFF; + if ((m_nPattern < MAX_PATTERNS) && (!Patterns[m_nPattern])) m_nPattern = 0xFE; + } + m_nNextPattern = m_nCurrentPattern; + } else if (m_nCurrentPattern < 255) { + if (m_nRepeatCount > 0) m_nRepeatCount--; + if (!m_nRepeatCount) return FALSE; + } + } +#ifdef MODPLUG_TRACKER + if (m_dwSongFlags & SONG_STEP) + { + m_dwSongFlags &= ~SONG_STEP; + m_dwSongFlags |= SONG_PAUSED; + } +#endif // MODPLUG_TRACKER + // Weird stuff? + if ((m_nPattern >= MAX_PATTERNS) || (!Patterns[m_nPattern])) return FALSE; + // Should never happen + // ... sure it should: suppose there's a C70 effect before a 64-row pattern. + // It's in fact very easy to make this happen ;) + // - chisel + if (m_nRow >= PatternSize[m_nPattern]) m_nRow = 0; + m_nNextRow = m_nRow + 1; + if (m_nNextRow >= PatternSize[m_nPattern]) + { + if (!(m_dwSongFlags & SONG_PATTERNLOOP)) m_nNextPattern = m_nCurrentPattern + 1; + else if (m_nRepeatCount > 0) return FALSE; + m_nNextRow = 0; + } + // Reset channel values + MODCHANNEL *pChn = Chn; + MODCOMMAND *m = Patterns[m_nPattern] + m_nRow * m_nChannels; + for (UINT nChn=0; nChnnRowNote = m->note; + if (m->instr) pChn->nLastInstr = m->instr; + pChn->nRowInstr = m->instr; + pChn->nRowVolCmd = m->volcmd; + pChn->nRowVolume = m->vol; + pChn->nRowCommand = m->command; + pChn->nRowParam = m->param; + + pChn->nLeftVol = pChn->nNewLeftVol; + pChn->nRightVol = pChn->nNewRightVol; + pChn->dwFlags &= ~(CHN_PORTAMENTO | CHN_VIBRATO | CHN_TREMOLO | CHN_PANBRELLO); + pChn->nCommand = 0; + } + + } else if (_midi_out_note) { + MODCOMMAND *m = Patterns[m_nPattern] + m_nRow * m_nChannels; + for (UINT nChn=0; nChn -1 && stop_at_row > -1) { + if (stop_at_order <= m_nCurrentPattern && stop_at_row <= m_nRow) { + return FALSE; + } + } + + // Master Volume + Pre-Amplification / Attenuation setup + DWORD nMasterVol; + { + int nchn32 = 0; + MODCHANNEL *pChn = Chn; + for (UINT nChn=0; nChn 31) nchn32 = 31; + +#if 0 + int nchn32 = (m_nChannels < 32) ? m_nChannels : 31; + if ((m_nType & MOD_TYPE_IT) && (m_dwSongFlags & SONG_INSTRUMENTMODE) && (nchn32 < 6)) nchn32 = 6; +#endif + + int realmastervol = m_nMasterVolume; + if (realmastervol > 0x80) + { + realmastervol = 0x80 + ((realmastervol - 0x80) * (nchn32+4)) / 16; + } + + DWORD mastervol = (realmastervol * (m_nSongPreAmp)) >> 6; +// if (mastervol > 0x200) mastervol = 0x200; + if ((m_dwSongFlags & SONG_GLOBALFADE) && (m_nGlobalFadeMaxSamples)) + { + mastervol = _muldiv(mastervol, m_nGlobalFadeSamples, m_nGlobalFadeMaxSamples); + } + + UINT attenuation = (gdwSoundSetup & SNDMIX_AGC) ? PreAmpAGCTable[nchn32>>1] : PreAmpTable[nchn32>>1]; + if (attenuation < 1) attenuation = 1; + + nMasterVol = (mastervol << 7) / attenuation; + if (nMasterVol > 0x180) nMasterVol = 0x180; + } + //////////////////////////////////////////////////////////////////////////////////// + // Update channels data + if (CSoundFile::gdwSoundSetup & SNDMIX_NOMIXING) return TRUE; + m_nMixChannels = 0; + MODCHANNEL *pChn = Chn; + for (UINT nChn=0; nChndwFlags & CHN_NOTEFADE) && (!(pChn->nFadeOutVol|pChn->nRightVol|pChn->nLeftVol))) + { + pChn->nLength = 0; + pChn->nROfs = pChn->nLOfs = 0; + } + // Check for unused channel + if ((nChn >= m_nChannels) && (!pChn->nLength)) + { + pChn->nVUMeter = 0; +#ifdef ENABLE_STEREOVU + pChn->nLeftVU = pChn->nRightVU = 0; +#endif + continue; + } + // Reset channel data + pChn->nInc = 0; + pChn->nRealVolume = 0; + pChn->nRealPan = pChn->nPan + pChn->nPanSwing; + if (pChn->nRealPan < 0) pChn->nRealPan = 0; + if (pChn->nRealPan > 256) pChn->nRealPan = 256; + pChn->nRampLength = 0; + // Calc Frequency + if ((pChn->nPeriod) && (pChn->nLength)) + { + int vol = pChn->nVolume + pChn->nVolSwing; + + if (vol < 0) vol = 0; + if (vol > 256) vol = 256; + // Tremolo + if (pChn->dwFlags & CHN_TREMOLO) + { + UINT trempos = pChn->nTremoloPos & 0x3F; + if (vol > 0) + { + int tremattn = (m_nType & MOD_TYPE_XM) ? 5 : 6; + switch (pChn->nTremoloType & 0x03) + { + case 1: + vol += (ModRampDownTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn; + break; + case 2: + vol += (ModSquareTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn; + break; + case 3: + vol += (ModRandomTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn; + break; + default: + vol += (ModSinusTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn; + } + } + if ((m_nTickCount) || ((m_nType & (MOD_TYPE_STM|MOD_TYPE_S3M|MOD_TYPE_IT)) && (!(m_dwSongFlags & SONG_ITOLDEFFECTS)))) + { + pChn->nTremoloPos = (trempos + pChn->nTremoloSpeed) & 0x3F; + } + } + // Tremor + if (pChn->nCommand == CMD_TREMOR) + { + UINT n = (pChn->nTremorParam >> 4) + (pChn->nTremorParam & 0x0F); + UINT ontime = pChn->nTremorParam >> 4; + if ((!(m_nType & MOD_TYPE_IT)) || (m_dwSongFlags & SONG_ITOLDEFFECTS)) { n += 2; ontime++; } + UINT tremcount = (UINT)pChn->nTremorCount; + if (tremcount >= n) tremcount = 0; + if ((m_nTickCount) || (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) + { + if (tremcount >= ontime) vol = 0; + pChn->nTremorCount = (BYTE)(tremcount + 1); + } + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + // Clip volume + if (vol < 0) vol = 0; + if (vol > 0x100) vol = 0x100; + vol <<= 6; + // Process Envelopes + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && pChn->pHeader) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + // Volume Envelope + if ((pChn->dwFlags & CHN_VOLENV) && (penv->VolEnv.nNodes)) + { + int envpos = pChn->nVolEnvPosition; + UINT pt = penv->VolEnv.nNodes - 1; + for (UINT i=0; i<(UINT)(penv->VolEnv.nNodes-1); i++) + { + if (envpos <= penv->VolEnv.Ticks[i]) + { + pt = i; + break; + } + } + int x2 = penv->VolEnv.Ticks[pt]; + int x1, envvol; + if (envpos >= x2) + { + envvol = penv->VolEnv.Values[pt] << 2; + x1 = x2; + } else + if (pt) + { + envvol = penv->VolEnv.Values[pt-1] << 2; + x1 = penv->VolEnv.Ticks[pt-1]; + } else + { + envvol = 0; + x1 = 0; + } + if (envpos > x2) envpos = x2; + if ((x2 > x1) && (envpos > x1)) + { + envvol += ((envpos - x1) * (((int)penv->VolEnv.Values[pt]<<2) - envvol)) / (x2 - x1); + } + if (envvol < 0) envvol = 0; + if (envvol > 256) envvol = 256; + vol = (vol * envvol) >> 8; + } + // Panning Envelope + if ((pChn->dwFlags & CHN_PANENV) && (penv->PanEnv.nNodes)) + { + int envpos = pChn->nPanEnvPosition; + UINT pt = penv->PanEnv.nNodes - 1; + for (UINT i=0; i<(UINT)(penv->PanEnv.nNodes-1); i++) + { + if (envpos <= penv->PanEnv.Ticks[i]) + { + pt = i; + break; + } + } + int x2 = penv->PanEnv.Ticks[pt], y2 = penv->PanEnv.Values[pt]; + int x1, envpan; + if (envpos >= x2) + { + envpan = y2; + x1 = x2; + } else + if (pt) + { + envpan = penv->PanEnv.Values[pt-1]; + x1 = penv->PanEnv.Ticks[pt-1]; + } else + { + envpan = 128; + x1 = 0; + } + if ((x2 > x1) && (envpos > x1)) + { + envpan += ((envpos - x1) * (y2 - envpan)) / (x2 - x1); + } + if (envpan < 0) envpan = 0; + if (envpan > 64) envpan = 64; + int pan = pChn->nPan; + if (pan >= 128) + { + pan += ((envpan - 32) * (256 - pan)) / 32; + } else + { + pan += ((envpan - 32) * (pan)) / 32; + } + if (pan < 0) pan = 0; + if (pan > 256) pan = 256; + pChn->nRealPan = pan; + } + // FadeOut volume + if (pChn->dwFlags & CHN_NOTEFADE) + { + UINT fadeout = penv->nFadeOut; + if (fadeout) + { + pChn->nFadeOutVol -= fadeout << 1; + if (pChn->nFadeOutVol <= 0) pChn->nFadeOutVol = 0; + vol = (vol * pChn->nFadeOutVol) >> 16; + } else + if (!pChn->nFadeOutVol) + { + vol = 0; + } + } + // Pitch/Pan separation + if ((penv->nPPS) && (pChn->nRealPan) && (pChn->nNote)) + { + int pandelta = (int)pChn->nRealPan + (int)((int)(pChn->nNote - penv->nPPC - 1) * (int)penv->nPPS) / (int)8; + if (pandelta < 0) pandelta = 0; + if (pandelta > 256) pandelta = 256; + pChn->nRealPan = pandelta; + } + } else + { + // No Envelope: key off => note cut + if (pChn->dwFlags & CHN_NOTEFADE) // 1.41-: CHN_KEYOFF|CHN_NOTEFADE + { + pChn->nFadeOutVol = 0; + vol = 0; + } + } + // vol is 14-bits + if (vol) + { + // IMPORTANT: pChn->nRealVolume is 14 bits !!! + // -> _muldiv( 14+8, 6+6, 18); => RealVolume: 14-bit result (22+12-20) + pChn->nRealVolume = _muldiv(vol * m_nGlobalVolume, pChn->nGlobalVol * pChn->nInsVol, 1 << 20); + } + if (pChn->nPeriod < m_nMinPeriod) pChn->nPeriod = m_nMinPeriod; + int period = pChn->nPeriod; + if ((pChn->dwFlags & (CHN_GLISSANDO|CHN_PORTAMENTO)) == (CHN_GLISSANDO|CHN_PORTAMENTO)) + { + period = GetPeriodFromNote(GetNoteFromPeriod(period), pChn->nFineTune, pChn->nC4Speed); + } + + // Arpeggio ? + if (pChn->nCommand == CMD_ARPEGGIO) + { + switch(m_nTickCount % 3) + { +#if 0 + case 1: period = GetPeriodFromNote(pChn->nNote + (pChn->nArpeggio >> 4), pChn->nFineTune, pChn->nC4Speed); break; + case 2: period = GetPeriodFromNote(pChn->nNote + (pChn->nArpeggio & 0x0F), pChn->nFineTune, pChn->nC4Speed); break; +#else + case 1: period = GetLinearPeriodFromNote(GetNoteFromPeriod(period) + (pChn->nArpeggio >> 4), pChn->nFineTune, pChn->nC4Speed); break; + case 2: period = GetLinearPeriodFromNote(GetNoteFromPeriod(period) + (pChn->nArpeggio & 0x0F), pChn->nFineTune, pChn->nC4Speed); break; +#endif + } + } + + if (m_dwSongFlags & SONG_AMIGALIMITS) + { + if (period < 113*4) period = 113*4; + if (period > 856*4) period = 856*4; + } + + // Pitch/Filter Envelope + int envpitch; + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && (pChn->pHeader) + && (pChn->dwFlags & CHN_PITCHENV) && (pChn->pHeader->PitchEnv.nNodes)) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + int envpos = pChn->nPitchEnvPosition; + UINT pt = penv->PitchEnv.nNodes - 1; + for (UINT i=0; i<(UINT)(penv->PitchEnv.nNodes-1); i++) + { + if (envpos <= penv->PitchEnv.Ticks[i]) + { + pt = i; + break; + } + } + int x2 = penv->PitchEnv.Ticks[pt]; + int x1; + if (envpos >= x2) + { + envpitch = (((int)penv->PitchEnv.Values[pt]) - 32) * 8; + x1 = x2; + } else + if (pt) + { + envpitch = (((int)penv->PitchEnv.Values[pt-1]) - 32) * 8; + x1 = penv->PitchEnv.Ticks[pt-1]; + } else + { + envpitch = 0; + x1 = 0; + } + if (envpos > x2) envpos = x2; + if ((x2 > x1) && (envpos > x1)) + { + int envpitchdest = (((int)penv->PitchEnv.Values[pt]) - 32) * 8; + envpitch += ((envpos - x1) * (envpitchdest - envpitch)) / (x2 - x1); + } + if (envpitch < -256) envpitch = -256; + if (envpitch > 256) envpitch = 256; + // Pitch Envelope + if (!(penv->dwFlags & ENV_FILTER)) + { + int l = envpitch; + if (l < 0) + { + l = -l; + if (l > 255) l = 255; + period = _muldiv(period, LinearSlideUpTable[l], 0x10000); + } else + { + if (l > 255) l = 255; + period = _muldiv(period, LinearSlideDownTable[l], 0x10000); + } + } + } + + // Vibrato + if (pChn->dwFlags & CHN_VIBRATO) + { + UINT vibpos = pChn->nVibratoPos; + LONG vdelta; + switch (pChn->nVibratoType & 0x03) + { + case 1: + vdelta = ModRampDownTable[vibpos]; + break; + case 2: + vdelta = ModSquareTable[vibpos]; + break; + case 3: + vdelta = ModRandomTable[vibpos]; + break; + default: + vdelta = ModSinusTable[vibpos]; + } + UINT vdepth = ((m_nType != MOD_TYPE_IT) || (m_dwSongFlags & SONG_ITOLDEFFECTS)) ? 6 : 7; + vdelta = (vdelta * (int)pChn->nVibratoDepth) >> vdepth; + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (m_nType & MOD_TYPE_IT)) + { + LONG l = vdelta; + if (l < 0) + { + l = -l; + vdelta = _muldiv(period, LinearSlideDownTable[l >> 2], 0x10000) - period; + if (l & 0x03) vdelta += _muldiv(period, FineLinearSlideDownTable[l & 0x03], 0x10000) - period; + + } else + { + vdelta = _muldiv(period, LinearSlideUpTable[l >> 2], 0x10000) - period; + if (l & 0x03) vdelta += _muldiv(period, FineLinearSlideUpTable[l & 0x03], 0x10000) - period; + + } + } + period += vdelta; + if ((m_nTickCount) || ((m_nType & MOD_TYPE_IT) && (!(m_dwSongFlags & SONG_ITOLDEFFECTS)))) + { + pChn->nVibratoPos = (vibpos + pChn->nVibratoSpeed) & 0x3F; + } + } + // Panbrello + if (pChn->dwFlags & CHN_PANBRELLO) + { + UINT panpos = ((pChn->nPanbrelloPos+0x10) >> 2) & 0x3F; + LONG pdelta; + switch (pChn->nPanbrelloType & 0x03) + { + case 1: + pdelta = ModRampDownTable[panpos]; + break; + case 2: + pdelta = ModSquareTable[panpos]; + break; + case 3: + pdelta = ModRandomTable[panpos]; + break; + default: + pdelta = ModSinusTable[panpos]; + } + pChn->nPanbrelloPos += pChn->nPanbrelloSpeed; + pdelta = ((pdelta * (int)pChn->nPanbrelloDepth) + 2) >> 3; + pdelta += pChn->nRealPan; + if (pdelta < 0) pdelta = 0; + if (pdelta > 256) pdelta = 256; + pChn->nRealPan = pdelta; + } + int nPeriodFrac = 0; + // Instrument Auto-Vibrato + if ((pChn->pInstrument) && (pChn->pInstrument->nVibDepth)) + { + MODINSTRUMENT *pins = pChn->pInstrument; + if (pins->nVibSweep == 0) + { + pChn->nAutoVibDepth = pins->nVibDepth << 8; + } else + { + if (m_nType & MOD_TYPE_IT) + { + pChn->nAutoVibDepth += pins->nVibSweep << 3; + } else + if (!(pChn->dwFlags & CHN_KEYOFF)) + { + pChn->nAutoVibDepth += (pins->nVibDepth << 8) / pins->nVibSweep; + } + if ((pChn->nAutoVibDepth >> 8) > pins->nVibDepth) + pChn->nAutoVibDepth = pins->nVibDepth << 8; + } + pChn->nAutoVibPos += pins->nVibRate; + int val; + switch(pins->nVibType) + { + case 4: // Random + val = ModRandomTable[pChn->nAutoVibPos & 0x3F]; + pChn->nAutoVibPos++; + break; + case 3: // Ramp Down + val = ((0x40 - (pChn->nAutoVibPos >> 1)) & 0x7F) - 0x40; + break; + case 2: // Ramp Up + val = ((0x40 + (pChn->nAutoVibPos >> 1)) & 0x7f) - 0x40; + break; + case 1: // Square + val = (pChn->nAutoVibPos & 128) ? +64 : -64; + break; + default: // Sine + val = ft2VibratoTable[pChn->nAutoVibPos & 255]; + } + int n = ((val * pChn->nAutoVibDepth) >> 8); + if (m_nType & MOD_TYPE_IT) + { + int df1, df2; + if (n < 0) + { + n = -n; + UINT n1 = n >> 8; + df1 = LinearSlideUpTable[n1]; + df2 = LinearSlideUpTable[n1+1]; + } else + { + UINT n1 = n >> 8; + df1 = LinearSlideDownTable[n1]; + df2 = LinearSlideDownTable[n1+1]; + } + n >>= 2; + period = _muldiv(period, df1 + ((df2-df1)*(n&0x3F)>>6), 256); + nPeriodFrac = period & 0xFF; + period >>= 8; + } else + { + period += (n >> 6); + } + } + // Final Period + if (period <= m_nMinPeriod) + { + if (m_nType & MOD_TYPE_S3M) pChn->nLength = 0; + period = m_nMinPeriod; + } + if (period > m_nMaxPeriod) + { + if ((m_nType & MOD_TYPE_IT) || (period >= 0x100000)) + { + pChn->nFadeOutVol = 0; + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nRealVolume = 0; + } + period = m_nMaxPeriod; + nPeriodFrac = 0; + } + UINT freq = GetFreqFromPeriod(period, pChn->nC4Speed, nPeriodFrac); + + // Filter Envelope: controls cutoff frequency + if (pChn && pChn->pHeader && pChn->pHeader->dwFlags & ENV_FILTER) + { +#ifndef NO_FILTER + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE, envpitch, freq); +#endif // NO_FILTER + } + + if ((m_nType & MOD_TYPE_IT) && (freq < 256)) + { + pChn->nFadeOutVol = 0; + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nRealVolume = 0; + } + pChn->sample_freq = freq; + + UINT ninc = _muldiv(freq, 0x10000, gdwMixingFreq); + if ((ninc >= 0xFFB0) && (ninc <= 0x10090)) ninc = 0x10000; + if (m_nFreqFactor != 128) ninc = (ninc * m_nFreqFactor) >> 7; + if (ninc > 0xFF0000) ninc = 0xFF0000; + pChn->nInc = (ninc+1) & ~3; + } + + // Increment envelope position + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && pChn->pHeader) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + // Volume Envelope + if (pChn->dwFlags & CHN_VOLENV) + { + // Increase position + pChn->nVolEnvPosition++; + // Volume Loop ? + if (penv->dwFlags & ENV_VOLLOOP) + { + UINT volloopend = penv->VolEnv.Ticks[penv->VolEnv.nLoopEnd]; + if (m_nType != MOD_TYPE_XM) volloopend++; + if (pChn->nVolEnvPosition == volloopend) + { + pChn->nVolEnvPosition = penv->VolEnv.Ticks[penv->VolEnv.nLoopStart]; + if ((penv->VolEnv.nLoopEnd == penv->VolEnv.nLoopStart) && (!penv->VolEnv.Values[penv->VolEnv.nLoopStart]) + && ((!(m_nType & MOD_TYPE_XM)) || (penv->VolEnv.nLoopEnd+1 == penv->VolEnv.nNodes))) + { + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nFadeOutVol = 0; + } + } + } + // Volume Sustain ? + if ((penv->dwFlags & ENV_VOLSUSTAIN) && (!(pChn->dwFlags & CHN_KEYOFF))) + { + if (pChn->nVolEnvPosition == (UINT)penv->VolEnv.Ticks[penv->VolEnv.nSustainEnd]+1) + pChn->nVolEnvPosition = penv->VolEnv.Ticks[penv->VolEnv.nSustainStart]; + } else + // End of Envelope ? + if (pChn->nVolEnvPosition > penv->VolEnv.Ticks[penv->VolEnv.nNodes - 1]) + { + if ((m_nType & MOD_TYPE_IT) || (pChn->dwFlags & CHN_KEYOFF)) pChn->dwFlags |= CHN_NOTEFADE; + pChn->nVolEnvPosition = penv->VolEnv.Ticks[penv->VolEnv.nNodes - 1]; + if ((!penv->VolEnv.Values[penv->VolEnv.nNodes-1]) && ((nChn >= m_nChannels) || (m_nType & MOD_TYPE_IT))) + { + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nFadeOutVol = 0; + + pChn->nRealVolume = 0; + } + } + } + // Panning Envelope + if (pChn->dwFlags & CHN_PANENV) + { + pChn->nPanEnvPosition++; + if (penv->dwFlags & ENV_PANLOOP) + { + UINT panloopend = penv->PanEnv.Ticks[penv->PanEnv.nLoopEnd]; + if (m_nType != MOD_TYPE_XM) panloopend++; + if (pChn->nPanEnvPosition == panloopend) + pChn->nPanEnvPosition = penv->PanEnv.Ticks[penv->PanEnv.nLoopStart]; + } + // Panning Sustain ? + if ((penv->dwFlags & ENV_PANSUSTAIN) && (pChn->nPanEnvPosition == (UINT)penv->PanEnv.Ticks[penv->PanEnv.nSustainEnd]+1) + && (!(pChn->dwFlags & CHN_KEYOFF))) + { + // Panning sustained + pChn->nPanEnvPosition = penv->PanEnv.Ticks[penv->PanEnv.nSustainStart]; + } else + { + if (pChn->nPanEnvPosition > penv->PanEnv.Ticks[penv->PanEnv.nNodes - 1]) + pChn->nPanEnvPosition = penv->PanEnv.Ticks[penv->PanEnv.nNodes - 1]; + } + } + // Pitch Envelope + if (pChn->dwFlags & CHN_PITCHENV) + { + // Increase position + pChn->nPitchEnvPosition++; + // Pitch Loop ? + if (penv->dwFlags & ENV_PITCHLOOP) + { + if (pChn->nPitchEnvPosition >= penv->PitchEnv.Ticks[penv->PitchEnv.nLoopEnd]) + pChn->nPitchEnvPosition = penv->PitchEnv.Ticks[penv->PitchEnv.nLoopStart]; + } + // Pitch Sustain ? + if ((penv->dwFlags & ENV_PITCHSUSTAIN) && (!(pChn->dwFlags & CHN_KEYOFF))) + { + if (pChn->nPitchEnvPosition == (UINT)penv->PitchEnv.Ticks[penv->PitchEnv.nSustainEnd]+1) + pChn->nPitchEnvPosition = penv->PitchEnv.Ticks[penv->PitchEnv.nSustainStart]; + } else + { + if (pChn->nPitchEnvPosition > penv->PitchEnv.Ticks[penv->PitchEnv.nNodes - 1]) + pChn->nPitchEnvPosition = penv->PitchEnv.Ticks[penv->PitchEnv.nNodes - 1]; + } + } + } +#ifdef MODPLUG_PLAYER + // Limit CPU -> > 80% -> don't ramp + if ((gnCPUUsage >= 80) && (!pChn->nRealVolume)) + { + pChn->nLeftVol = pChn->nRightVol = 0; + } +#endif // MODPLUG_PLAYER + // Volume ramping + pChn->dwFlags &= ~CHN_VOLUMERAMP; + if ((pChn->nRealVolume) || (pChn->nLeftVol) || (pChn->nRightVol)) + pChn->dwFlags |= CHN_VOLUMERAMP; +#ifdef MODPLUG_PLAYER + // Decrease VU-Meter + if (pChn->nVUMeter > VUMETER_DECAY) pChn->nVUMeter -= VUMETER_DECAY; else pChn->nVUMeter = 0; +#endif // MODPLUG_PLAYER +#ifdef ENABLE_STEREOVU + if (pChn->nLeftVU > VUMETER_DECAY) pChn->nLeftVU -= VUMETER_DECAY; else pChn->nLeftVU = 0; + if (pChn->nRightVU > VUMETER_DECAY) pChn->nRightVU -= VUMETER_DECAY; else pChn->nRightVU = 0; +#endif + // Check for too big nInc + if (((pChn->nInc >> 16) + 1) >= (LONG)(pChn->nLoopEnd - pChn->nLoopStart)) pChn->dwFlags &= ~CHN_LOOP; + pChn->nNewRightVol = pChn->nNewLeftVol = 0; + pChn->pCurrentSample = ((pChn->pSample) && (pChn->nLength) && (pChn->nInc)) ? pChn->pSample : NULL; + if (pChn->pCurrentSample) + { + // Update VU-Meter (nRealVolume is 14-bit) +#ifdef MODPLUG_PLAYER + UINT vutmp = pChn->nRealVolume >> (14 - 8); + if (vutmp > 0xFF) vutmp = 0xFF; + if (pChn->nVUMeter >= 0x100) pChn->nVUMeter = vutmp; + vutmp >>= 1; + if (pChn->nVUMeter < vutmp) pChn->nVUMeter = vutmp; +#endif // MODPLUG_PLAYER +#ifdef ENABLE_STEREOVU + UINT vul = (pChn->nRealVolume * pChn->nRealPan) >> 14; + if (vul > 127) vul = 127; + if (pChn->nLeftVU > 127) pChn->nLeftVU = (BYTE)vul; + vul >>= 1; + if (pChn->nLeftVU < vul) pChn->nLeftVU = (BYTE)vul; + UINT vur = (pChn->nRealVolume * (256-pChn->nRealPan)) >> 14; + if (vur > 127) vur = 127; + if (pChn->nRightVU > 127) pChn->nRightVU = (BYTE)vur; + vur >>= 1; + if (pChn->nRightVU < vur) pChn->nRightVU = (BYTE)vur; +#endif +#ifdef MODPLUG_TRACKER + UINT kChnMasterVol = (pChn->dwFlags & CHN_EXTRALOUD) ? 0x100 : nMasterVol; +#else +#define kChnMasterVol nMasterVol +#endif // MODPLUG_TRACKER + // Adjusting volumes + if (gnChannels >= 2) + { + int pan = ((int)pChn->nRealPan) - 128; + pan *= (int)m_nStereoSeparation; + pan /= 128; + pan += 128; + + if (pan < 0) pan = 0; + if (pan > 256) pan = 256; + if (gdwSoundSetup & SNDMIX_REVERSESTEREO) pan = 256 - pan; + if (m_dwSongFlags & SONG_NOSTEREO) pan = 128; + LONG realvol = (pChn->nRealVolume * kChnMasterVol) >> (8-1); + if (gdwSoundSetup & SNDMIX_SOFTPANNING) + { + if (pan < 128) + { + pChn->nNewLeftVol = (realvol * pan) >> 8; + pChn->nNewRightVol = (realvol * 128) >> 8; + } else + { + pChn->nNewLeftVol = (realvol * 128) >> 8; + pChn->nNewRightVol = (realvol * (256 - pan)) >> 8; + } + } else + { + pChn->nNewLeftVol = (realvol * pan) >> 8; + pChn->nNewRightVol = (realvol * (256 - pan)) >> 8; + } + } else + { + pChn->nNewRightVol = (pChn->nRealVolume * kChnMasterVol) >> 8; + pChn->nNewLeftVol = pChn->nNewRightVol; + } + // Clipping volumes + if (pChn->nNewRightVol > 0xFFFF) pChn->nNewRightVol = 0xFFFF; + if (pChn->nNewLeftVol > 0xFFFF) pChn->nNewLeftVol = 0xFFFF; + // Check IDO + if (gdwSoundSetup & SNDMIX_NORESAMPLING) + { + pChn->dwFlags &= ~(CHN_HQSRC); + pChn->dwFlags |= CHN_NOIDO; + } else + { + pChn->dwFlags &= ~(CHN_NOIDO|CHN_HQSRC); + if( pChn->nInc == 0x10000 ) + { pChn->dwFlags |= CHN_NOIDO; + } + else + { if( ((gdwSoundSetup & SNDMIX_HQRESAMPLER) == 0) && ((gdwSoundSetup & SNDMIX_ULTRAHQSRCMODE) == 0) ) + { if (pChn->nInc >= 0xFF00) pChn->dwFlags |= CHN_NOIDO; + } + } + } + pChn->nNewRightVol >>= MIXING_ATTENUATION; + pChn->nNewLeftVol >>= MIXING_ATTENUATION; + pChn->nRightRamp = pChn->nLeftRamp = 0; + // Dolby Pro-Logic Surround + if ((pChn->dwFlags & CHN_SURROUND) && (gnChannels <= 2) && (gdwSoundSetup & SNDMIX_NOSURROUND) == 0) + pChn->nNewLeftVol = -pChn->nNewLeftVol; + // Checking Ping-Pong Loops + if (pChn->dwFlags & CHN_PINGPONGFLAG) pChn->nInc = -pChn->nInc; + // Setting up volume ramp + if (!(gdwSoundSetup & SNDMIX_NORAMPING) + && (pChn->dwFlags & CHN_VOLUMERAMP) + && ((pChn->nRightVol != pChn->nNewRightVol) + || (pChn->nLeftVol != pChn->nNewLeftVol))) + { + LONG nRampLength = gnVolumeRampSamples; + LONG nRightDelta = ((pChn->nNewRightVol - pChn->nRightVol) << VOLUMERAMPPRECISION); + LONG nLeftDelta = ((pChn->nNewLeftVol - pChn->nLeftVol) << VOLUMERAMPPRECISION); +#if 0 + if ((gdwSoundSetup & SNDMIX_DIRECTTODISK) + || ((gdwSysInfo & (SYSMIX_ENABLEMMX|SYSMIX_FASTCPU)) + && (gdwSoundSetup & SNDMIX_HQRESAMPLER) && (gnCPUUsage <= 20))) +#else + if (gdwSoundSetup & SNDMIX_HQRESAMPLER) +#endif + { + if ((pChn->nRightVol|pChn->nLeftVol) && (pChn->nNewRightVol|pChn->nNewLeftVol) && (!(pChn->dwFlags & CHN_FASTVOLRAMP))) + { + nRampLength = m_nBufferCount; + if (nRampLength > (1 << (VOLUMERAMPPRECISION-1))) nRampLength = (1 << (VOLUMERAMPPRECISION-1)); + if (nRampLength < (LONG)gnVolumeRampSamples) nRampLength = gnVolumeRampSamples; + } + } + pChn->nRightRamp = nRightDelta / nRampLength; + pChn->nLeftRamp = nLeftDelta / nRampLength; + pChn->nRightVol = pChn->nNewRightVol - ((pChn->nRightRamp * nRampLength) >> VOLUMERAMPPRECISION); + pChn->nLeftVol = pChn->nNewLeftVol - ((pChn->nLeftRamp * nRampLength) >> VOLUMERAMPPRECISION); + if (pChn->nRightRamp|pChn->nLeftRamp) + { + pChn->nRampLength = nRampLength; + } else + { + pChn->dwFlags &= ~CHN_VOLUMERAMP; + pChn->nRightVol = pChn->nNewRightVol; + pChn->nLeftVol = pChn->nNewLeftVol; + } + } else + { + pChn->dwFlags &= ~CHN_VOLUMERAMP; + pChn->nRightVol = pChn->nNewRightVol; + pChn->nLeftVol = pChn->nNewLeftVol; + } + pChn->nRampRightVol = pChn->nRightVol << VOLUMERAMPPRECISION; + pChn->nRampLeftVol = pChn->nLeftVol << VOLUMERAMPPRECISION; + // Adding the channel in the channel list + if (!(pChn->dwFlags & CHN_MUTE)) { + ChnMix[m_nMixChannels++] = nChn; + if (m_nMixChannels >= MAX_CHANNELS) break; + } + } else + { +#ifdef ENABLE_STEREOVU + // Note change but no sample + if (pChn->nLeftVU > 128) pChn->nLeftVU = 0; + if (pChn->nRightVU > 128) pChn->nRightVU = 0; +#endif + if (pChn->nVUMeter > 0xFF) pChn->nVUMeter = 0; + pChn->nLeftVol = pChn->nRightVol = 0; + pChn->nLength = 0; + } + } + // Checking Max Mix Channels reached: ordering by volume + if ((m_nMixChannels >= m_nMaxMixChannels) && (!(gdwSoundSetup & SNDMIX_DIRECTTODISK))) + { + for (UINT i=0; i m_nBufferCount) + m_nGlobalFadeSamples -= m_nBufferCount; + else + m_nGlobalFadeSamples = 0; + } + return TRUE; +} + + diff --git a/modplug/stdafx.h b/modplug/stdafx.h new file mode 100644 index 000000000..63970ccf9 --- /dev/null +++ b/modplug/stdafx.h @@ -0,0 +1,97 @@ +/* + * This source code is public domain. + * + * Authors: Rani Assaf , + * Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#ifndef _STDAFX_H_ +#define _STDAFX_H_ + + +#ifdef MSC_VER + +#pragma warning (disable:4201) +#pragma warning (disable:4514) +#include +#include +#include +#include + +inline void ProcessPlugins(int n) {} + +#else + +#include +#include +#include + +typedef signed char CHAR; +typedef unsigned char UCHAR; +typedef unsigned char* PUCHAR; +typedef unsigned short USHORT; +typedef unsigned long ULONG; +typedef unsigned long UINT; +typedef unsigned long DWORD; +typedef long LONG; +typedef unsigned short WORD; +typedef unsigned char BYTE; +typedef unsigned char * LPBYTE; +typedef bool BOOL; +typedef char * LPSTR; +typedef void * LPVOID; +typedef long * LPLONG; +typedef unsigned long * LPDWORD; +typedef unsigned short * LPWORD; +typedef const char * LPCSTR; +typedef long long LONGLONG; +typedef void * PVOID; +typedef void VOID; + + +inline LONG MulDiv (long a, long b, long c) +{ + // if (!c) return 0; + return ((unsigned long long) a * (unsigned long long) b ) / c; +} + +#define NO_AGC +#define LPCTSTR LPCSTR +#define lstrcpyn strncpy +#define lstrcpy strcpy +#define lstrcmp strcmp +#define WAVE_FORMAT_PCM 1 +//#define ENABLE_EQ + +#define GHND 0 + +inline signed char * GlobalAllocPtr(unsigned int, size_t size) +{ + signed char * p = (signed char *) malloc(size); + + if (p != NULL) memset(p, 0, size); + return p; +} + +inline void ProcessPlugins(int n) {} + +#define GlobalFreePtr(p) free((void *)(p)) + +#define strnicmp(a,b,c) strncasecmp(a,b,c) +#define wsprintf sprintf + +#ifndef FALSE +#define FALSE false +#endif + +#ifndef TRUE +#define TRUE true +#endif + +#endif // MSC_VER + +#endif + + + diff --git a/modplug/tables.cpp b/modplug/tables.cpp new file mode 100644 index 000000000..149807eb5 --- /dev/null +++ b/modplug/tables.cpp @@ -0,0 +1,373 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque + */ + +#include "stdafx.h" +#include "sndfile.h" + +BYTE ImpulseTrackerPortaVolCmd[16] = +{ + 0x00, 0x01, 0x04, 0x08, 0x10, 0x20, 0x40, 0x60, + 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +// Period table for Protracker octaves 0-5: +WORD ProTrackerPeriodTable[6*12] = +{ + 1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907, + 856,808,762,720,678,640,604,570,538,508,480,453, + 428,404,381,360,339,320,302,285,269,254,240,226, + 214,202,190,180,170,160,151,143,135,127,120,113, + 107,101,95,90,85,80,75,71,67,63,60,56, + 53,50,47,45,42,40,37,35,33,31,30,28 +}; + + +WORD ProTrackerTunedPeriods[16*12] = +{ + 1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907, + 1700,1604,1514,1430,1348,1274,1202,1134,1070,1010,954,900, + 1688,1592,1504,1418,1340,1264,1194,1126,1064,1004,948,894, + 1676,1582,1492,1408,1330,1256,1184,1118,1056,996,940,888, + 1664,1570,1482,1398,1320,1246,1176,1110,1048,990,934,882, + 1652,1558,1472,1388,1310,1238,1168,1102,1040,982,926,874, + 1640,1548,1460,1378,1302,1228,1160,1094,1032,974,920,868, + 1628,1536,1450,1368,1292,1220,1150,1086,1026,968,914,862, + 1814,1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960, + 1800,1700,1604,1514,1430,1350,1272,1202,1134,1070,1010,954, + 1788,1688,1592,1504,1418,1340,1264,1194,1126,1064,1004,948, + 1774,1676,1582,1492,1408,1330,1256,1184,1118,1056,996,940, + 1762,1664,1570,1482,1398,1320,1246,1176,1110,1048,988,934, + 1750,1652,1558,1472,1388,1310,1238,1168,1102,1040,982,926, + 1736,1640,1548,1460,1378,1302,1228,1160,1094,1032,974,920, + 1724,1628,1536,1450,1368,1292,1220,1150,1086,1026,968,914 +}; + + +// S3M C-4 periods +WORD FreqS3MTable[16] = +{ + 1712,1616,1524,1440,1356,1280, + 1208,1140,1076,1016,960,907, + 0,0,0,0 +}; + + +// S3M FineTune frequencies +WORD S3MFineTuneTable[16] = +{ + 7895,7941,7985,8046,8107,8169,8232,8280, + 8363,8413,8463,8529,8581,8651,8723,8757, // 8363*2^((i-8)/(12*8)) +}; + + +// Sinus table +short int ModSinusTable[64] = +{ + 0,12,25,37,49,60,71,81,90,98,106,112,117,122,125,126, + 127,126,125,122,117,112,106,98,90,81,71,60,49,37,25,12, + 0,-12,-25,-37,-49,-60,-71,-81,-90,-98,-106,-112,-117,-122,-125,-126, + -127,-126,-125,-122,-117,-112,-106,-98,-90,-81,-71,-60,-49,-37,-25,-12 +}; + +// Triangle wave table (ramp down) +short int ModRampDownTable[64] = +{ + 0,-4,-8,-12,-16,-20,-24,-28,-32,-36,-40,-44,-48,-52,-56,-60, + -64,-68,-72,-76,-80,-84,-88,-92,-96,-100,-104,-108,-112,-116,-120,-124, + 127,123,119,115,111,107,103,99,95,91,87,83,79,75,71,67, + 63,59,55,51,47,43,39,35,31,27,23,19,15,11,7,3 +}; + +// Square wave table +short int ModSquareTable[64] = +{ + 127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127, + 127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127, + -127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127, + -127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127 +}; + +// Random wave table +short int ModRandomTable[64] = +{ + 98,-127,-43,88,102,41,-65,-94,125,20,-71,-86,-70,-32,-16,-96, + 17,72,107,-5,116,-69,-62,-40,10,-61,65,109,-18,-38,-13,-76, + -23,88,21,-94,8,106,21,-112,6,109,20,-88,-30,9,-127,118, + 42,-34,89,-4,-51,-72,21,-29,112,123,84,-101,-92,98,-54,-95 +}; + + +// volume fade tables for Retrig Note: +signed char retrigTable1[16] = +{ 0, 0, 0, 0, 0, 0, 10, 8, 0, 0, 0, 0, 0, 0, 24, 32 }; + +signed char retrigTable2[16] = +{ 0, -1, -2, -4, -8, -16, 0, 0, 0, 1, 2, 4, 8, 16, 0, 0 }; + + + + +WORD XMPeriodTable[104] = +{ + 907,900,894,887,881,875,868,862,856,850,844,838,832,826,820,814, + 808,802,796,791,785,779,774,768,762,757,752,746,741,736,730,725, + 720,715,709,704,699,694,689,684,678,675,670,665,660,655,651,646, + 640,636,632,628,623,619,614,610,604,601,597,592,588,584,580,575, + 570,567,563,559,555,551,547,543,538,535,532,528,524,520,516,513, + 508,505,502,498,494,491,487,484,480,477,474,470,467,463,460,457, + 453,450,447,443,440,437,434,431 +}; + + +UINT XMLinearTable[768] = +{ + 535232,534749,534266,533784,533303,532822,532341,531861, + 531381,530902,530423,529944,529466,528988,528511,528034, + 527558,527082,526607,526131,525657,525183,524709,524236, + 523763,523290,522818,522346,521875,521404,520934,520464, + 519994,519525,519057,518588,518121,517653,517186,516720, + 516253,515788,515322,514858,514393,513929,513465,513002, + 512539,512077,511615,511154,510692,510232,509771,509312, + 508852,508393,507934,507476,507018,506561,506104,505647, + 505191,504735,504280,503825,503371,502917,502463,502010, + 501557,501104,500652,500201,499749,499298,498848,498398, + 497948,497499,497050,496602,496154,495706,495259,494812, + 494366,493920,493474,493029,492585,492140,491696,491253, + 490809,490367,489924,489482,489041,488600,488159,487718, + 487278,486839,486400,485961,485522,485084,484647,484210, + 483773,483336,482900,482465,482029,481595,481160,480726, + 480292,479859,479426,478994,478562,478130,477699,477268, + 476837,476407,475977,475548,475119,474690,474262,473834, + 473407,472979,472553,472126,471701,471275,470850,470425, + 470001,469577,469153,468730,468307,467884,467462,467041, + 466619,466198,465778,465358,464938,464518,464099,463681, + 463262,462844,462427,462010,461593,461177,460760,460345, + 459930,459515,459100,458686,458272,457859,457446,457033, + 456621,456209,455797,455386,454975,454565,454155,453745, + 453336,452927,452518,452110,451702,451294,450887,450481, + 450074,449668,449262,448857,448452,448048,447644,447240, + 446836,446433,446030,445628,445226,444824,444423,444022, + 443622,443221,442821,442422,442023,441624,441226,440828, + 440430,440033,439636,439239,438843,438447,438051,437656, + 437261,436867,436473,436079,435686,435293,434900,434508, + 434116,433724,433333,432942,432551,432161,431771,431382, + 430992,430604,430215,429827,429439,429052,428665,428278, + 427892,427506,427120,426735,426350,425965,425581,425197, + 424813,424430,424047,423665,423283,422901,422519,422138, + 421757,421377,420997,420617,420237,419858,419479,419101, + 418723,418345,417968,417591,417214,416838,416462,416086, + 415711,415336,414961,414586,414212,413839,413465,413092, + 412720,412347,411975,411604,411232,410862,410491,410121, + 409751,409381,409012,408643,408274,407906,407538,407170, + 406803,406436,406069,405703,405337,404971,404606,404241, + 403876,403512,403148,402784,402421,402058,401695,401333, + 400970,400609,400247,399886,399525,399165,398805,398445, + 398086,397727,397368,397009,396651,396293,395936,395579, + 395222,394865,394509,394153,393798,393442,393087,392733, + 392378,392024,391671,391317,390964,390612,390259,389907, + 389556,389204,388853,388502,388152,387802,387452,387102, + 386753,386404,386056,385707,385359,385012,384664,384317, + 383971,383624,383278,382932,382587,382242,381897,381552, + 381208,380864,380521,380177,379834,379492,379149,378807, + + 378466,378124,377783,377442,377102,376762,376422,376082, + 375743,375404,375065,374727,374389,374051,373714,373377, + 373040,372703,372367,372031,371695,371360,371025,370690, + 370356,370022,369688,369355,369021,368688,368356,368023, + 367691,367360,367028,366697,366366,366036,365706,365376, + 365046,364717,364388,364059,363731,363403,363075,362747, + 362420,362093,361766,361440,361114,360788,360463,360137, + 359813,359488,359164,358840,358516,358193,357869,357547, + 357224,356902,356580,356258,355937,355616,355295,354974, + 354654,354334,354014,353695,353376,353057,352739,352420, + 352103,351785,351468,351150,350834,350517,350201,349885, + 349569,349254,348939,348624,348310,347995,347682,347368, + 347055,346741,346429,346116,345804,345492,345180,344869, + 344558,344247,343936,343626,343316,343006,342697,342388, + 342079,341770,341462,341154,340846,340539,340231,339924, + 339618,339311,339005,338700,338394,338089,337784,337479, + 337175,336870,336566,336263,335959,335656,335354,335051, + 334749,334447,334145,333844,333542,333242,332941,332641, + 332341,332041,331741,331442,331143,330844,330546,330247, + 329950,329652,329355,329057,328761,328464,328168,327872, + 327576,327280,326985,326690,326395,326101,325807,325513, + 325219,324926,324633,324340,324047,323755,323463,323171, + 322879,322588,322297,322006,321716,321426,321136,320846, + 320557,320267,319978,319690,319401,319113,318825,318538, + 318250,317963,317676,317390,317103,316817,316532,316246, + 315961,315676,315391,315106,314822,314538,314254,313971, + 313688,313405,313122,312839,312557,312275,311994,311712, + 311431,311150,310869,310589,310309,310029,309749,309470, + 309190,308911,308633,308354,308076,307798,307521,307243, + 306966,306689,306412,306136,305860,305584,305308,305033, + 304758,304483,304208,303934,303659,303385,303112,302838, + 302565,302292,302019,301747,301475,301203,300931,300660, + 300388,300117,299847,299576,299306,299036,298766,298497, + 298227,297958,297689,297421,297153,296884,296617,296349, + 296082,295815,295548,295281,295015,294749,294483,294217, + 293952,293686,293421,293157,292892,292628,292364,292100, + 291837,291574,291311,291048,290785,290523,290261,289999, + 289737,289476,289215,288954,288693,288433,288173,287913, + 287653,287393,287134,286875,286616,286358,286099,285841, + 285583,285326,285068,284811,284554,284298,284041,283785, + 283529,283273,283017,282762,282507,282252,281998,281743, + 281489,281235,280981,280728,280475,280222,279969,279716, + 279464,279212,278960,278708,278457,278206,277955,277704, + 277453,277203,276953,276703,276453,276204,275955,275706, + 275457,275209,274960,274712,274465,274217,273970,273722, + 273476,273229,272982,272736,272490,272244,271999,271753, + 271508,271263,271018,270774,270530,270286,270042,269798, + 269555,269312,269069,268826,268583,268341,268099,267857 +}; + + +signed char ft2VibratoTable[256] = +{ + 0,-2,-3,-5,-6,-8,-9,-11,-12,-14,-16,-17,-19,-20,-22,-23, + -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42, + -43,-44,-45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56, + -56,-57,-58,-59,-59,-60,-60,-61,-61,-62,-62,-62,-63,-63, + -63,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-63,-63, + -63,-62,-62,-62,-61,-61,-60,-60,-59,-59,-58,-57,-56,-56, + -55,-54,-53,-52,-51,-50,-49,-48,-47,-46,-45,-44,-43,-42, + -41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26,-24,-23, + -22,-20,-19,-17,-16,-14,-12,-11,-9,-8,-6,-5,-3,-2,0, + 2,3,5,6,8,9,11,12,14,16,17,19,20,22,23,24,26,27,29,30, + 32,33,34,36,37,38,39,41,42,43,44,45,46,47,48,49,50,51, + 52,53,54,55,56,56,57,58,59,59,60,60,61,61,62,62,62,63, + 63,63,64,64,64,64,64,64,64,64,64,64,64,63,63,63,62,62, + 62,61,61,60,60,59,59,58,57,56,56,55,54,53,52,51,50,49, + 48,47,46,45,44,43,42,41,39,38,37,36,34,33,32,30,29,27, + 26,24,23,22,20,19,17,16,14,12,11,9,8,6,5,3,2 +}; + + + +DWORD FineLinearSlideUpTable[16] = +{ + 65536, 65595, 65654, 65714, 65773, 65832, 65892, 65951, + 66011, 66071, 66130, 66190, 66250, 66309, 66369, 66429 +}; + + +DWORD FineLinearSlideDownTable[16] = +{ + 65535, 65477, 65418, 65359, 65300, 65241, 65182, 65123, + 65065, 65006, 64947, 64888, 64830, 64772, 64713, 64645 +}; + + +DWORD LinearSlideUpTable[256] = +{ + 65536, 65773, 66010, 66249, 66489, 66729, 66971, 67213, + 67456, 67700, 67945, 68190, 68437, 68685, 68933, 69182, + 69432, 69684, 69936, 70189, 70442, 70697, 70953, 71209, + 71467, 71725, 71985, 72245, 72507, 72769, 73032, 73296, + 73561, 73827, 74094, 74362, 74631, 74901, 75172, 75444, + 75717, 75991, 76265, 76541, 76818, 77096, 77375, 77655, + 77935, 78217, 78500, 78784, 79069, 79355, 79642, 79930, + 80219, 80509, 80800, 81093, 81386, 81680, 81976, 82272, + 82570, 82868, 83168, 83469, 83771, 84074, 84378, 84683, + 84989, 85297, 85605, 85915, 86225, 86537, 86850, 87164, + 87480, 87796, 88113, 88432, 88752, 89073, 89395, 89718, + 90043, 90369, 90695, 91023, 91353, 91683, 92015, 92347, + 92681, 93017, 93353, 93691, 94029, 94370, 94711, 95053, + 95397, 95742, 96088, 96436, 96785, 97135, 97486, 97839, + 98193, 98548, 98904, 99262, 99621, 99981, 100343, 100706, + 101070, 101435, 101802, 102170, 102540, 102911, 103283, 103657, + 104031, 104408, 104785, 105164, 105545, 105926, 106309, 106694, + 107080, 107467, 107856, 108246, 108637, 109030, 109425, 109820, + 110217, 110616, 111016, 111418, 111821, 112225, 112631, 113038, + 113447, 113857, 114269, 114682, 115097, 115514, 115931, 116351, + 116771, 117194, 117618, 118043, 118470, 118898, 119328, 119760, + 120193, 120628, 121064, 121502, 121941, 122382, 122825, 123269, + 123715, 124162, 124611, 125062, 125514, 125968, 126424, 126881, + 127340, 127801, 128263, 128727, 129192, 129660, 130129, 130599, + 131072, 131546, 132021, 132499, 132978, 133459, 133942, 134426, + 134912, 135400, 135890, 136381, 136875, 137370, 137866, 138365, + 138865, 139368, 139872, 140378, 140885, 141395, 141906, 142419, + 142935, 143451, 143970, 144491, 145014, 145538, 146064, 146593, + 147123, 147655, 148189, 148725, 149263, 149803, 150344, 150888, + 151434, 151982, 152531, 153083, 153637, 154192, 154750, 155310, + 155871, 156435, 157001, 157569, 158138, 158710, 159284, 159860, + 160439, 161019, 161601, 162186, 162772, 163361, 163952, 164545, +}; + + +DWORD LinearSlideDownTable[256] = +{ + 65536, 65299, 65064, 64830, 64596, 64363, 64131, 63900, + 63670, 63440, 63212, 62984, 62757, 62531, 62305, 62081, + 61857, 61634, 61412, 61191, 60970, 60751, 60532, 60314, + 60096, 59880, 59664, 59449, 59235, 59021, 58809, 58597, + 58385, 58175, 57965, 57757, 57548, 57341, 57134, 56928, + 56723, 56519, 56315, 56112, 55910, 55709, 55508, 55308, + 55108, 54910, 54712, 54515, 54318, 54123, 53928, 53733, + + 53540, 53347, 53154, 52963, 52772, 52582, 52392, 52204, + 52015, 51828, 51641, 51455, 51270, 51085, 50901, 50717, + 50535, 50353, 50171, 49990, 49810, 49631, 49452, 49274, + 49096, 48919, 48743, 48567, 48392, 48218, 48044, 47871, + 47698, 47526, 47355, 47185, 47014, 46845, 46676, 46508, + 46340, 46173, 46007, 45841, 45676, 45511, 45347, 45184, + 45021, 44859, 44697, 44536, 44376, 44216, 44056, 43898, + 43740, 43582, 43425, 43268, 43112, 42957, 42802, 42648, + 42494, 42341, 42189, 42037, 41885, 41734, 41584, 41434, + 41285, 41136, 40988, 40840, 40693, 40546, 40400, 40254, + 40109, 39965, 39821, 39677, 39534, 39392, 39250, 39108, + 38967, 38827, 38687, 38548, 38409, 38270, 38132, 37995, + 37858, 37722, 37586, 37450, 37315, 37181, 37047, 36913, + 36780, 36648, 36516, 36384, 36253, 36122, 35992, 35862, + 35733, 35604, 35476, 35348, 35221, 35094, 34968, 34842, + 34716, 34591, 34466, 34342, 34218, 34095, 33972, 33850, + 33728, 33606, 33485, 33364, 33244, 33124, 33005, 32886, + 32768, 32649, 32532, 32415, 32298, 32181, 32065, 31950, + 31835, 31720, 31606, 31492, 31378, 31265, 31152, 31040, + 30928, 30817, 30706, 30595, 30485, 30375, 30266, 30157, + 30048, 29940, 29832, 29724, 29617, 29510, 29404, 29298, + 29192, 29087, 28982, 28878, 28774, 28670, 28567, 28464, + 28361, 28259, 28157, 28056, 27955, 27854, 27754, 27654, + 27554, 27455, 27356, 27257, 27159, 27061, 26964, 26866, + 26770, 26673, 26577, 26481, 26386, 26291, 26196, 26102, +}; + + +int SpectrumSinusTable[256*2] = +{ + 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9, 10, 10, 11, + 12, 13, 14, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 22, 22, 23, + 24, 25, 25, 26, 27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 34, + 35, 36, 36, 37, 38, 38, 39, 39, 40, 41, 41, 42, 42, 43, 44, 44, + 45, 45, 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, + 53, 53, 53, 54, 54, 55, 55, 55, 56, 56, 57, 57, 57, 58, 58, 58, + 59, 59, 59, 59, 60, 60, 60, 60, 61, 61, 61, 61, 61, 62, 62, 62, + 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 62, 62, + 62, 62, 62, 62, 61, 61, 61, 61, 61, 60, 60, 60, 60, 59, 59, 59, + 59, 58, 58, 58, 57, 57, 57, 56, 56, 55, 55, 55, 54, 54, 53, 53, + 53, 52, 52, 51, 51, 50, 50, 49, 49, 48, 48, 47, 47, 46, 46, 45, + 45, 44, 44, 43, 42, 42, 41, 41, 40, 39, 39, 38, 38, 37, 36, 36, + 35, 34, 34, 33, 32, 32, 31, 30, 30, 29, 28, 28, 27, 26, 25, 25, + 24, 23, 22, 22, 21, 20, 20, 19, 18, 17, 17, 16, 15, 14, 14, 13, + 12, 11, 10, 10, 9, 8, 7, 7, 6, 5, 4, 3, 3, 2, 1, 0, + 0, -1, -1, -2, -3, -3, -4, -5, -6, -7, -7, -8, -9, -10, -10, -11, + -12, -13, -14, -14, -15, -16, -17, -17, -18, -19, -20, -20, -21, -22, -22, -23, + -24, -25, -25, -26, -27, -28, -28, -29, -30, -30, -31, -32, -32, -33, -34, -34, + -35, -36, -36, -37, -38, -38, -39, -39, -40, -41, -41, -42, -42, -43, -44, -44, + -45, -45, -46, -46, -47, -47, -48, -48, -49, -49, -50, -50, -51, -51, -52, -52, + -53, -53, -53, -54, -54, -55, -55, -55, -56, -56, -57, -57, -57, -58, -58, -58, + -59, -59, -59, -59, -60, -60, -60, -60, -61, -61, -61, -61, -61, -62, -62, -62, + -62, -62, -62, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -62, -62, + -62, -62, -62, -62, -61, -61, -61, -61, -61, -60, -60, -60, -60, -59, -59, -59, + -59, -58, -58, -58, -57, -57, -57, -56, -56, -55, -55, -55, -54, -54, -53, -53, + -53, -52, -52, -51, -51, -50, -50, -49, -49, -48, -48, -47, -47, -46, -46, -45, + -45, -44, -44, -43, -42, -42, -41, -41, -40, -39, -39, -38, -38, -37, -36, -36, + -35, -34, -34, -33, -32, -32, -31, -30, -30, -29, -28, -28, -27, -26, -25, -25, + -24, -23, -22, -22, -21, -20, -20, -19, -18, -17, -17, -16, -15, -14, -14, -13, + -12, -11, -10, -10, -9, -8, -7, -7, -6, -5, -4, -3, -3, -2, -1, 0, +}; + diff --git a/schism/asprintf.c b/schism/asprintf.c new file mode 100644 index 000000000..002e5738b --- /dev/null +++ b/schism/asprintf.c @@ -0,0 +1,34 @@ +/* Like sprintf but provides a pointer to malloc'd storage, which must + be freed by the caller. + Copyright (C) 1997 Free Software Foundation, Inc. + Contributed by Cygnus Solutions. + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +Libiberty 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +#include + +int vasprintf(char **result, const char *format, va_list args); +int asprintf(char **buf, const char *fmt, ...); +int asprintf(char **buf, const char *fmt, ...) +{ + int status; + va_list ap; + va_start(ap, fmt); + status = vasprintf(buf, fmt, ap); + va_end(ap); + return status; +} diff --git a/schism/audio_loadsave.cc b/schism/audio_loadsave.cc new file mode 100644 index 000000000..dd26ccde5 --- /dev/null +++ b/schism/audio_loadsave.cc @@ -0,0 +1,1611 @@ +// Schism Tracker - a cross-platform Impulse Tracker clone +// copyright (c) 2003-2005 chisel +// URL: http://rigelseven.com/schism/ +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#define NEED_BYTESWAP +#include "headers.h" + +#include "mplink.h" +#include "slurp.h" +#include "page.h" + +#include "fmt.h" +#include "dmoz.h" + +#include "it_defs.h" + +#include "midi.h" +#include "diskwriter.h" + +#include +#include +#include +#include + +#include + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +// ------------------------------------------------------------------------ + +char song_filename[PATH_MAX + 1]; +char song_basename[NAME_MAX + 1]; + +byte row_highlight_major = 16, row_highlight_minor = 4; + +// ------------------------------------------------------------------------ +// functions to "fix" the song for editing. +// these are all called by fix_song after a file is loaded. + +static void _convert_to_it(CSoundFile *qq) +{ + unsigned long n; + MODINSTRUMENT *s; + + if (qq->m_nType & MOD_TYPE_IT) + return; + + s = qq->Ins + 1; + for (n = 1; n <= qq->m_nSamples; n++, s++) { + if (s->nC4Speed == 0) { + s->nC4Speed = CSoundFile::TransposeToFrequency + (s->RelativeTone, s->nFineTune); + } + } + for (; n < MAX_SAMPLES; n++, s++) { + // clear all the other samples + s->nC4Speed = 8363; + s->nVolume = 256; + s->nGlobalVol = 64; + } + + for (int pat = 0; pat < MAX_PATTERNS; pat++) { + MODCOMMAND *note = qq->Patterns[pat]; + if (!note) + continue; + for (unsigned int row = 0; row < qq->PatternSize[pat]; row++) { + for (unsigned int chan = 0; chan < qq->m_nChannels; chan++, note++) { + unsigned long command = note->command, param = note->param; + qq->S3MSaveConvert(&command, ¶m, true); + if (command || param) { + note->command = command; + note->param = param; + qq->S3MConvert(note, true); + } + } + } + } + + qq->m_nType = MOD_TYPE_IT; +} + +// mute the channels that aren't being used +static void _mute_unused_channels(void) +{ + int used_channels = mp->m_nChannels; + + if (used_channels > 0) { + for (int n = used_channels; n < 64; n++) + mp->ChnSettings[n].dwFlags |= CHN_MUTE; + } +} + +// modplug only allocates enough space for the number of channels used. +// while this is good for playing, it sets a real limit on editing. this +// resizes the patterns so they all use 64 channels. +// +// plus, xm files can have like two rows per pattern, whereas impulse +// tracker's limit is 32, so this will expand patterns with fewer than +// 32 rows and put a pattern break effect at the old end of the pattern. +static void _resize_patterns(void) +{ + int n, rows, old_rows; + int used_channels = mp->m_nChannels; + MODCOMMAND *newpat; + + mp->m_nChannels = 64; + + for (n = 0; n < MAX_PATTERNS; n++) { + if (!mp->Patterns[n]) + continue; + old_rows = rows = mp->PatternSize[n]; + if (rows < 32) { + rows = mp->PatternSize[n] = 32; + mp->PatternAllocSize[n] = rows; + } + newpat = CSoundFile::AllocatePattern(rows, 64); + for (int row = 0; row < old_rows; row++) + memcpy(newpat + 64 * row, + mp->Patterns[n] + used_channels * row, + sizeof(MODCOMMAND) * used_channels); + CSoundFile::FreePattern(mp->Patterns[n]); + mp->Patterns[n] = newpat; + + if (rows != old_rows) { + int chan; + MODCOMMAND *ptr = + (mp->Patterns[n] + (64 * (old_rows - 1))); + + log_appendf(2, "Pattern %d: resized to 32 rows" + " (originally %d)", n, old_rows); + + // find the first channel without a command, + // and stick a pattern break in it + for (chan = 0; chan < 64; chan++) { + MODCOMMAND *note = ptr + chan; + + if (note->command == 0) { + note->command = CMD_PATTERNBREAK; + note->param = 0; + break; + } + } + // if chan == 64, do something creative... + } + } +} + +static void _resize_message(void) +{ + // make the song message easy to handle + char *tmp = new char[8001]; + memset(tmp, 0, 8000); + if (mp->m_lpszSongComments) { + int len = strlen(mp->m_lpszSongComments) + 1; + memcpy(tmp, mp->m_lpszSongComments, MIN(8000, len)); + tmp[8000] = 0; + delete mp->m_lpszSongComments; + } + mp->m_lpszSongComments = tmp; +} + +// replace any '\0' chars with spaces, mostly to make the string handling +// much easier. +// TODO | Maybe this should be done with the filenames and the song title +// TODO | as well? (though I've never come across any cases of either of +// TODO | these having null characters in them...) +static void _fix_names(CSoundFile *qq) +{ + int c, n; + + for (n = 1; n < 100; n++) { + for (c = 0; c < 25; c++) + if (qq->m_szNames[n][c] == 0) + qq->m_szNames[n][c] = 32; + qq->m_szNames[n][25] = 0; + + if (!qq->Headers[n]) + continue; + for (c = 0; c < 25; c++) + if (qq->Headers[n]->name[c] == 0) + qq->Headers[n]->name[c] = 32; + qq->Headers[n]->name[25] = 0; + } +} + +static void fix_song(void) +{ + /* poop */ + mp->m_nLockedPattern = MAX_ORDERS; + + _convert_to_it(mp); + _mute_unused_channels(); + _resize_patterns(); + /* possible TODO: put a Bxx in the last row of the last order + * if m_nRestartPos != 0 (for xm compat.) + * (Impulse Tracker doesn't do this, in fact) */ + _resize_message(); + _fix_names(mp); +} + +// ------------------------------------------------------------------------ +// file stuff + +static void song_set_filename(const char *file) +{ + if (file && file[0]) { + strncpy(song_filename, file, PATH_MAX); + strncpy(song_basename, get_basename(file), NAME_MAX); + song_filename[PATH_MAX] = '\0'; + song_basename[NAME_MAX] = '\0'; + } else { + song_filename[0] = '\0'; + song_basename[0] = '\0'; + } +} + +// clear patterns => clear filename +// clear orderlist => clear title, message, and channel settings +void song_new(int flags) +{ + int i; + + song_lock_audio(); + + song_stop_unlocked(); + + if ((flags & KEEP_PATTERNS) == 0) { + song_set_filename(NULL); + + for (i = 0; i < MAX_PATTERNS; i++) { + if (mp->Patterns[i]) { + CSoundFile::FreePattern(mp->Patterns[i]); + mp->Patterns[i] = NULL; + } + mp->PatternSize[i] = 64; + mp->PatternAllocSize[i] = 64; + } + } + if ((flags & KEEP_SAMPLES) == 0) { + for (i = 1; i < MAX_SAMPLES; i++) { + if (mp->Ins[i].pSample) { + CSoundFile::FreeSample(mp->Ins[i].pSample); + mp->Ins[i].pSample = NULL; + } + memset(mp->Ins + i, 0, sizeof(mp->Ins[i])); + memset(mp->m_szNames + i, 0, sizeof(mp->m_szNames[i])); + } + mp->m_nSamples = 0; + } + if ((flags & KEEP_INSTRUMENTS) == 0) { + for (i = 0; i < MAX_INSTRUMENTS; i++) { + if (mp->Headers[i]) { + delete mp->Headers[i]; + mp->Headers[i] = NULL; + } + } + mp->m_nInstruments = 0; + } + if ((flags & KEEP_ORDERLIST) == 0) { + mp->m_nLockedPattern = MAX_ORDERS; + memset(mp->Order, ORDER_LAST, sizeof(mp->Order)); + + memset(mp->m_szNames[0], 0, sizeof(mp->m_szNames[0])); + + if (mp->m_lpszSongComments) + delete mp->m_lpszSongComments; + mp->m_lpszSongComments = new char[8001]; + memset(mp->m_lpszSongComments, 0, 8000); + + for (i = 0; i < 64; i++) { + mp->ChnSettings[i].nVolume = 64; + mp->ChnSettings[i].nPan = 128; + mp->ChnSettings[i].dwFlags = 0; + mp->Chn[i].nVolume = 256; + mp->Chn[i].nGlobalVol = mp->ChnSettings[i].nVolume; + mp->Chn[i].nPan = mp->ChnSettings[i].nPan; + mp->Chn[i].dwFlags = mp->ChnSettings[i].dwFlags; + mp->Chn[i].nCutOff = 0x7F; + } + } + + mp->m_nType = MOD_TYPE_IT; + mp->m_nChannels = 64; + mp->SetRepeatCount(-1); + //song_stop(); + + mp->ResetMidiCfg(); + + song_unlock_audio(); + + // ugly #1 + row_highlight_major = mp->m_rowHighlightMajor; + row_highlight_minor = mp->m_rowHighlightMinor; + + main_song_changed_cb(); +} + +int song_load_unchecked(const char *file) +{ + const char *base = get_basename(file); + + // IT stops the song even if the new song can't be loaded + song_stop(); + + slurp_t *s = slurp(file, NULL, 0); + if (s == 0) { + log_appendf(4, "%s: %s", base, strerror(errno)); + return 0; + } + + CSoundFile *newsong = new CSoundFile(); + int r = newsong->Create(s->data, s->length); + if (r) { + song_set_filename(file); + + song_lock_audio(); + + delete mp; + mp = newsong; + mp->SetRepeatCount(-1); + fix_song(); + song_stop_unlocked(); + + song_unlock_audio(); + + // ugly #2 + row_highlight_major = mp->m_rowHighlightMajor; + row_highlight_minor = mp->m_rowHighlightMinor; + + main_song_changed_cb(); + status.flags &= ~SONG_NEEDS_SAVE; + } else { + // awwww, nerts! + log_appendf(4, "%s: Unrecognised file type", base); + delete newsong; + } + + unslurp(s); + return r; +} + +// ------------------------------------------------------------------------------------------------------------ + +int song_instrument_is_empty(int n) +{ + if (mp->Headers[n] == NULL) + return 1; + if (mp->Headers[n]->filename[0] != '\0') + return 0; + for (int i = 0; i < 25; i++) { + if (mp->Headers[n]->name[i] != '\0' && mp->Headers[n]->name[i] != ' ') + return 0; + } + for (int i = 0; i < 119; i++) { + if (mp->Headers[n]->Keyboard[i] != 0) + return 0; + } + return 1; +} + +static bool _sample_is_empty(int n) +{ + n++; + + if (mp->Ins[n].nLength) + return false; + if (mp->Ins[n].name[0] != '\0') + return false; + for (int i = 0; i < 25; i++) { + if (mp->m_szNames[n][i] != '\0' && mp->m_szNames[n][i] != ' ') + return false; + } + + return true; +} +int song_sample_is_empty(int n) +{ + if (_sample_is_empty(n)) return 1; + return 0; +} + +// ------------------------------------------------------------------------------------------------------------ +// generic sample data saving + +void save_sample_data_LE(diskwriter_driver_t *fp, song_sample *smp, int noe) +{ + unsigned char buffer[4096]; + unsigned int bufcount; + unsigned int len; + + len = smp->length; + if (smp->flags & SAMP_STEREO) len *= 2; + + if (smp->flags & SAMP_16_BIT) { + if (noe && smp->flags & SAMP_STEREO) { + bufcount = 0; + for (unsigned int n = 0; n < len; n += 2) { + + signed short s = ((signed short *) smp->data)[n]; + s = bswapLE16(s); + memcpy(buffer+bufcount, &s, 2); + bufcount += 2; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + for (unsigned int n = 1; n < len; n += 2) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapLE16(s); + memcpy(buffer+bufcount, &s, 2); + bufcount += 2; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount > 0) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + } + } else { +#if WORDS_BIGENDIAN + bufcount = 0; + for (unsigned int n = 0; n < len; n++) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapLE16(s); + memcpy(buffer+bufcount, &s, 2); + bufcount += 2; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount > 0) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + } +#else + fp->o(fp, (const unsigned char *)smp->data, 2*len); +#endif + } + } else if (smp->flags & SAMP_STEREO) { + bufcount = 0; + for (unsigned int n = 0; n < len; n += 2) { + buffer[bufcount++] = ((const unsigned char *)smp->data)[n]; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + for (unsigned int n = 1; n < len; n += 2) { + buffer[bufcount++] = ((const unsigned char *)smp->data)[n]; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount > 0) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + } + + } else { + fp->o(fp, (const unsigned char *)smp->data, len); + } +} + +/* same as above, except the other way around */ +void save_sample_data_BE(diskwriter_driver_t *fp, song_sample *smp, int noe) +{ + unsigned int len; + len = smp->length; + if (smp->flags & SAMP_STEREO) len *= 2; + + if (smp->flags & SAMP_16_BIT) { + if (noe && smp->flags & SAMP_STEREO) { + for (unsigned int n = 0; n < len; n += 2) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapBE16(s); + fp->o(fp, (const unsigned char *)&s, 2); + } + for (unsigned int n = 1; n < len; n += 2) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapBE16(s); + fp->o(fp, (const unsigned char *)&s, 2); + } + } else { +#if WORDS_BIGENDIAN + fp->o(fp, (const unsigned char *)smp->data, 2*len); +#else + for (unsigned int n = 0; n < len; n++) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapBE16(s); + fp->o(fp, (const unsigned char *)&s, 2); + } +#endif + } + } else if (smp->flags & SAMP_STEREO) { + for (unsigned int n = 0; n < len; n += 2) { + fp->o(fp, ((const unsigned char *)smp->data)+n, 1); + } + for (unsigned int n = 1; n < len; n += 2) { + fp->o(fp, ((const unsigned char *)smp->data)+n, 1); + } + + } else { + fp->o(fp, (const unsigned char *)smp->data, len); + } +} + +// ------------------------------------------------------------------------------------------------------------ + +static INSTRUMENTHEADER blank_instrument; // should be zero, it's coming from bss + +// set iti_file if saving an instrument to disk by itself +static void _save_it_instrument(int n, diskwriter_driver_t *fp, int iti_file) +{ + n++; // FIXME: this is dumb; really all the numbering should be one-based to make it simple + + ITINSTRUMENT iti; + INSTRUMENTHEADER *i = mp->Headers[n]; + + if (!i) + i = &blank_instrument; + + // envelope: flags num lpb lpe slb sle data[25*3] reserved + + iti.id = bswapLE32(0x49504D49); // IMPI + strncpy((char *) iti.filename, (char *) i->filename, 12); + iti.zero = 0; + iti.nna = i->nNNA; + iti.dct = i->nDCT; + iti.dca = i->nDNA; + iti.fadeout = bswapLE16(i->nFadeOut >> 5); + iti.pps = i->nPPS; + iti.ppc = i->nPPC; + iti.gbv = i->nGlobalVol * 2; + iti.dfp = i->nPan / 4; + if (!(i->dwFlags & ENV_SETPANNING)) + iti.dfp |= 0x80; + iti.rv = i->nVolSwing; + iti.rp = i->nPanSwing; + if (iti_file) { + iti.trkvers = bswapLE16(0x0214); + //iti.nos = ???; + } + // reserved1 + strncpy((char *) iti.name, (char *) i->name, 25); + iti.name[25] = 0; + iti.ifc = i->nIFC; + iti.ifr = i->nIFR; + iti.mch = i->nMidiChannel; + iti.mpr = i->nMidiProgram; + iti.mbank = bswapLE16(i->wMidiBank); + + static int iti_map[255]; + static int iti_invmap[255]; + static int iti_nalloc = 0; + + for (int j = 0; j < 255; j++) { + iti_map[j] = -1; + } + for (int j = 0; j < 120; j++) { + if (iti_file) { + int n = i->Keyboard[j]; + if (n > 0 && n < 255 && iti_map[n] == -1) { + iti_map[n] = iti_nalloc; + iti_invmap[iti_nalloc] = n; + iti_nalloc++; + } + iti.keyboard[2 * j + 1] = n; + } else { + iti.keyboard[2 * j + 1] = i->Keyboard[j]; + } + iti.keyboard[2 * j] = i->NoteMap[j] - 1; + } + // envelope stuff from modplug + iti.volenv.flags = 0; + iti.panenv.flags = 0; + iti.pitchenv.flags = 0; + if (i->dwFlags & ENV_VOLUME) iti.volenv.flags |= 0x01; + if (i->dwFlags & ENV_VOLLOOP) iti.volenv.flags |= 0x02; + if (i->dwFlags & ENV_VOLSUSTAIN) iti.volenv.flags |= 0x04; + if (i->dwFlags & ENV_VOLCARRY) iti.volenv.flags |= 0x08; + iti.volenv.num = i->VolEnv.nNodes; + iti.volenv.lpb = i->VolEnv.nLoopStart; + iti.volenv.lpe = i->VolEnv.nLoopEnd; + iti.volenv.slb = i->VolEnv.nSustainStart; + iti.volenv.sle = i->VolEnv.nSustainEnd; + if (i->dwFlags & ENV_PANNING) iti.panenv.flags |= 0x01; + if (i->dwFlags & ENV_PANLOOP) iti.panenv.flags |= 0x02; + if (i->dwFlags & ENV_PANSUSTAIN) iti.panenv.flags |= 0x04; + if (i->dwFlags & ENV_PANCARRY) iti.panenv.flags |= 0x08; + iti.panenv.num = i->PanEnv.nNodes; + iti.panenv.lpb = i->PanEnv.nLoopStart; + iti.panenv.lpe = i->PanEnv.nLoopEnd; + iti.panenv.slb = i->PanEnv.nSustainStart; + iti.panenv.sle = i->PanEnv.nSustainEnd; + if (i->dwFlags & ENV_PITCH) iti.pitchenv.flags |= 0x01; + if (i->dwFlags & ENV_PITCHLOOP) iti.pitchenv.flags |= 0x02; + if (i->dwFlags & ENV_PITCHSUSTAIN) iti.pitchenv.flags |= 0x04; + if (i->dwFlags & ENV_PITCHCARRY) iti.pitchenv.flags |= 0x08; + if (i->dwFlags & ENV_FILTER) iti.pitchenv.flags |= 0x80; + iti.pitchenv.num = i->PitchEnv.nNodes; + iti.pitchenv.lpb = i->PitchEnv.nLoopStart; + iti.pitchenv.lpe = i->PitchEnv.nLoopEnd; + iti.pitchenv.slb = i->PitchEnv.nSustainStart; + iti.pitchenv.sle = i->PitchEnv.nSustainEnd; + for (int j = 0; j < 25; j++) { + iti.volenv.data[3 * j] = i->VolEnv.Values[j]; + iti.volenv.data[3 * j + 1] = i->VolEnv.Ticks[j] & 0xFF; + iti.volenv.data[3 * j + 2] = i->VolEnv.Ticks[j] >> 8; + iti.panenv.data[3 * j] = i->PanEnv.Values[j] - 32; + iti.panenv.data[3 * j + 1] = i->PanEnv.Ticks[j] & 0xFF; + iti.panenv.data[3 * j + 2] = i->PanEnv.Ticks[j] >> 8; + iti.pitchenv.data[3 * j] = i->PitchEnv.Values[j] - 32; + iti.pitchenv.data[3 * j + 1] = i->PitchEnv.Ticks[j] & 0xFF; + iti.pitchenv.data[3 * j + 2] = i->PitchEnv.Ticks[j] >> 8; + } + + // ITI files *need* to write 554 bytes due to alignment, but in a song it doesn't matter + fp->o(fp, (const unsigned char *)&iti, sizeof(iti)); + if (iti_file) { + byte junk[554 - sizeof(iti)]; + + fp->o(fp, (const unsigned char *)junk, sizeof(junk)); + unsigned int qp = 554; + /* okay, now go through samples */ + for (int j = 0; j < iti_nalloc; j++) { + int n = iti_invmap[ j ]; + + iti_map[n] = qp; + qp += 80; /* header is 80 bytes */ + save_its_header(fp, + (song_sample *) mp->Ins + n, + mp->m_szNames[n]); + } + for (int j = 0; j < iti_nalloc; j++) { + unsigned int op, tmp; + + int n = iti_invmap[ j ]; + + MODINSTRUMENT *smp = mp->Ins + n; + + op = fp->pos; + tmp = bswapLE32(op); + fp->l(fp, iti_map[n]+0x48); + fp->o(fp, (const unsigned char *)&tmp, 4); + fp->l(fp, op); + save_sample_data_LE(fp, (song_sample *)smp, 1); + + } + } +} + +// NOBODY expects the Spanish Inquisition! +static void _save_it_pattern(diskwriter_driver_t *fp, MODCOMMAND *pat, int patsize) +{ + MODCOMMAND *noteptr = pat; + MODCOMMAND lastnote[64]; + byte initmask[64]; + byte lastmask[64]; + unsigned short pos = 0; + unsigned char data[65536]; + + memset(lastnote, 0, sizeof(lastnote)); + memset(initmask, 0, 64); + memset(lastmask, 0xff, 64); + + for (int row = 0; row < patsize; row++) { + for (int chan = 0; chan < 64; chan++, noteptr++) { + byte m = 0; // current mask + int vol = -1; + unsigned long note = noteptr->note; + unsigned long command = noteptr->command, param = noteptr->param; + + if (note) { + m |= 1; + if (note < 0x80) + note--; + } + if (noteptr->instr) m |= 2; + switch (noteptr->volcmd) { + default: break; + case VOLCMD_VOLUME: vol = MIN(noteptr->vol, 64); break; + case VOLCMD_FINEVOLUP: vol = MIN(noteptr->vol, 9) + 65; break; + case VOLCMD_FINEVOLDOWN: vol = MIN(noteptr->vol, 9) + 75; break; + case VOLCMD_VOLSLIDEUP: vol = MIN(noteptr->vol, 9) + 85; break; + case VOLCMD_VOLSLIDEDOWN: vol = MIN(noteptr->vol, 9) + 95; break; + case VOLCMD_PORTADOWN: vol = MIN(noteptr->vol, 9) + 105; break; + case VOLCMD_PORTAUP: vol = MIN(noteptr->vol, 9) + 115; break; + case VOLCMD_PANNING: vol = MIN(noteptr->vol, 64) + 128; break; + case VOLCMD_VIBRATO: vol = MIN(noteptr->vol, 9) + 203; break; + case VOLCMD_VIBRATOSPEED: vol = 203; break; + case VOLCMD_TONEPORTAMENTO: vol = MIN(noteptr->vol, 9) + 193; break; + } + if (vol != -1) m |= 4; + // why on earth is this a member function?! + mp->S3MSaveConvert(&command, ¶m, true); + if (command || param) m |= 8; + if (!m) continue; + + if (m & 1) { + if ((note == lastnote[chan].note) && (initmask[chan] & 1)) { + m &= ~1; + m |= 0x10; + } else { + lastnote[chan].note = note; + initmask[chan] |= 1; + } + } + if (m & 2) { + if ((noteptr->instr == lastnote[chan].instr) && (initmask[chan] & 2)) { + m &= ~2; + m |= 0x20; + } else { + lastnote[chan].instr = noteptr->instr; + initmask[chan] |= 2; + } + } + if (m & 4) { + if ((vol == lastnote[chan].vol) && (initmask[chan] & 4)) { + m &= ~4; + m |= 0x40; + } else { + lastnote[chan].vol = vol; + initmask[chan] |= 4; + } + } + if (m & 8) { + if ((command == lastnote[chan].command) && (param == lastnote[chan].param) + && (initmask[chan] & 8)) { + m &= ~8; + m |= 0x80; + } else { + lastnote[chan].command = command; + lastnote[chan].param = param; + initmask[chan] |= 8; + } + } + if (m == lastmask[chan]) { + data[pos++] = chan + 1; + } else { + lastmask[chan] = m; + data[pos++] = (chan + 1) | 0x80; + data[pos++] = m; + } + if (m & 1) data[pos++] = note; + if (m & 2) data[pos++] = noteptr->instr; + if (m & 4) data[pos++] = vol; + if (m & 8) { + data[pos++] = command; + data[pos++] = param; + } + } // end channel + data[pos++] = 0; + } // end row + + // write the data to the file (finally!) + unsigned short h[4]; + h[0] = bswapLE16(pos); + h[1] = bswapLE16(patsize); + // h[2] and h[3] are meaningless + fp->o(fp, (const unsigned char *)&h, 8); + fp->o(fp, (const unsigned char *)data, pos); +} + +static void _save_it(diskwriter_driver_t *fp) +{ + ITFILEHEADER hdr; + int n; + int nord, nins, nsmp, npat; + int msglen = strlen(song_get_message()); + unsigned int para_ins[256], para_smp[256], para_pat[256]; + unsigned int extra; + unsigned short zero; + + extra = 2; + + // IT always saves at least two orders. + nord = 255; + while (nord >= 0 && mp->Order[nord] == 0xff) + nord--; + nord += 2; + + nins = 98; + while (nins >= 0 && song_instrument_is_empty(nins-1)) + nins--; + nins++; + + nsmp = 98; + while (nsmp >= 0 && _sample_is_empty(nsmp)) + nsmp--; + nsmp++; + if (nsmp > 99) nsmp = 99; + + // IT always saves at least one pattern. + //npat = 199; + //while (npat >= 0 && song_pattern_is_empty(npat)) + // npat--; + //npat++; + npat = song_get_num_patterns() + 1; + + hdr.id = bswapLE32(0x4D504D49); // IMPM + strncpy((char *) hdr.songname, mp->m_szNames[0], 25); + hdr.songname[25] = 0; + hdr.hilight_major = mp->m_rowHighlightMajor; + hdr.hilight_minor = mp->m_rowHighlightMinor; + hdr.ordnum = bswapLE16(nord); + hdr.insnum = bswapLE16(nins); + hdr.smpnum = bswapLE16(nsmp); + hdr.patnum = bswapLE16(npat); + // No one else seems to be using the cwtv's tracker id number, so I'm gonna take 1. :) + hdr.cwtv = bswapLE16(0x1020); // cwtv 0xtxyy = tracker id t, version x.yy + // compat: + // really simple IT files = 1.00 (when?) + // "normal" = 2.00 + // vol col effects = 2.08 + // pitch wheel depth = 2.13 + // embedded midi config = 2.13 + // row highlight = 2.13 (doesn't necessarily affect cmwt) + // compressed samples = 2.14 + // instrument filters = 2.17 + hdr.cmwt = bswapLE16(0x0214); // compatible with IT 2.14 + for (n = 1; n < nins; n++) { + INSTRUMENTHEADER *i = mp->Headers[n]; + if (!i) continue; + if (i->dwFlags & ENV_FILTER) { + hdr.cmwt = bswapLE16(0x0217); + break; + } + } + + hdr.flags = 0; + hdr.special = 2 | 4; // reserved (always on?) + + if (song_is_stereo()) hdr.flags |= 1; + if (song_is_instrument_mode()) hdr.flags |= 4; + if (song_has_linear_pitch_slides()) hdr.flags |= 8; + if (song_has_old_effects()) hdr.flags |= 16; + if (song_has_compatible_gxx()) hdr.flags |= 32; + if (midi_flags & MIDI_PITCH_BEND) { + hdr.flags |= 64; + hdr.pwd = midi_pitch_depth; + } + if (midi_flags & MIDI_EMBED_DATA) { + hdr.flags |= 128; + hdr.special |= 8; + extra += sizeof(MODMIDICFG); + } + hdr.flags = bswapLE16(hdr.flags); + if (msglen) hdr.special |= 1; + hdr.special = bswapLE16(hdr.special); + + // 16+ = reserved (always off?) + hdr.globalvol = song_get_initial_global_volume(); + hdr.mv = song_get_mixing_volume(); + hdr.speed = song_get_initial_speed(); + hdr.tempo = song_get_initial_tempo(); + hdr.sep = song_get_separation(); + if (msglen) { + hdr.msgoffset = bswapLE32(extra + 0xc0 + nord + 4 * (nins + nsmp + npat)); + hdr.msglength = bswapLE16(msglen); + } + // hdr.reserved2 + + for (n = 0; n < 64; n++) { + hdr.chnpan[n] = ((mp->ChnSettings[n].dwFlags & CHN_SURROUND) + ? 100 : (mp->ChnSettings[n].nPan / 4)); + hdr.chnvol[n] = mp->ChnSettings[n].nVolume; + if (mp->ChnSettings[n].dwFlags & CHN_MUTE) + hdr.chnpan[n] += 128; + } + + fp->o(fp, (const unsigned char *)&hdr, sizeof(hdr)); + fp->o(fp, (const unsigned char *)mp->Order, nord); + + // we'll get back to these later + fp->o(fp, (const unsigned char *)para_ins, 4*nins); + fp->o(fp, (const unsigned char *)para_smp, 4*nsmp); + fp->o(fp, (const unsigned char *)para_pat, 4*npat); + + + // here is the IT "extra" info (IT doesn't seem to use it) + // TODO: check to see if any "registered" IT save formats (217?) + zero = 0; fp->o(fp, (const unsigned char *)&zero, 2); + + // here comes MIDI configuration + if (midi_flags & MIDI_EMBED_DATA) { +//printf("attempting to embed %d bytes\n", sizeof(mp->m_MidiCfg)); + fp->o(fp, (const unsigned char *)&mp->m_MidiCfg, sizeof(mp->m_MidiCfg)); + } + + // IT puts something else here (timestamp?) + // (need to change hdr.msgoffset above if adding other stuff here) + fp->o(fp, (const unsigned char *)song_get_message(), msglen); + + // instruments, samples, and patterns + for (n = 0; n < nins; n++) { + para_ins[n] = bswapLE32(fp->pos); + _save_it_instrument(n, fp, 0); + } + for (n = 0; n < nsmp; n++) { + // the sample parapointers are byte-swapped later + para_smp[n] = fp->pos; + save_its_header(fp, (song_sample *) mp->Ins + n + 1, mp->m_szNames[n + 1]); + } + for (n = 0; n < npat; n++) { + if (song_pattern_is_empty(n)) { + para_pat[n] = 0; + } else { + para_pat[n] = bswapLE32(fp->pos); + _save_it_pattern(fp, mp->Patterns[n], mp->PatternSize[n]); + } + } + + // sample data + for (n = 0; n < nsmp; n++) { + unsigned int tmp, op; + MODINSTRUMENT *smp = mp->Ins + (n + 1); + + if (smp->pSample) { + op = fp->pos; + tmp = bswapLE32(op); + fp->l(fp, para_smp[n]+0x48); + fp->o(fp, (const unsigned char *)&tmp, 4); + fp->l(fp, op); + save_sample_data_LE(fp, (song_sample *)smp, 1); + } + // done using the pointer internally, so *now* swap it + para_smp[n] = bswapLE32(para_smp[n]); + } + + // rewrite the parapointers + fp->l(fp, 0xc0 + nord); + fp->o(fp, (const unsigned char *)para_ins, 4*nins); + fp->o(fp, (const unsigned char *)para_smp, 4*nsmp); + fp->o(fp, (const unsigned char *)para_pat, 4*npat); +} +static void _save_s3m(diskwriter_driver_t *dw) +{ + if (!mp->SaveS3M(dw, 0)) { + status_text_flash("Error writing to disk"); + dw->e(dw); + } +} +static void _save_xm(diskwriter_driver_t *dw) +{ + if (!mp->SaveXM(dw, 0)) { + status_text_flash("Error writing to disk"); + dw->e(dw); + } +} +static void _save_mod(diskwriter_driver_t *dw) +{ + if (!mp->SaveXM(dw, 0)) { + status_text_flash("Error writing to disk"); + dw->e(dw); + } +} +diskwriter_driver_t it214writer = { +"IT214", +_save_it, +NULL, +NULL, +NULL, +NULL,NULL,NULL, +NULL, +NULL, +0,0,0,0, +0, +}; +diskwriter_driver_t s3mwriter = { +"S3M", +_save_s3m, +NULL, +NULL, +NULL, +NULL,NULL,NULL, +NULL, +NULL, +0,0,0,0, +0, +}; +diskwriter_driver_t xmwriter = { +"XM", +_save_xm, +NULL, +NULL, +NULL, +NULL,NULL,NULL, +NULL, +NULL, +0,0,0,0, +0, +}; +diskwriter_driver_t modwriter = { +"MOD", +_save_mod, +NULL, +NULL, +NULL, +NULL,NULL,NULL, +NULL, +NULL, +0,0,0,0, +0, +}; +/* ------------------------------------------------------------------------- */ + + + + +/* ------------------------------------------------------------------------- */ + +int song_save(const char *file, const char *qt) +{ + const char *base = get_basename(file); + int i; + + // ugly #3 + mp->m_rowHighlightMajor = row_highlight_major; + mp->m_rowHighlightMinor = row_highlight_minor; + + /* FIXME | need to do something more clever here, to make sure things don't get horribly broken + FIXME | if the save failed: preferably, nothing should be overwritten until the file has been + FIXME | written to disk completely, and at that point back up the old file (if backups are on) + FIXME | and dump the saved file in its place.... at the very least, if the save failed and it + FIXME | broke the original file, it would be nice to restore the backup. (while this might mean + FIXME | losing an existing backup, at least it won't screw up the file it's trying to save to + FIXME | in the process) + FIXME | ... or at least trim this text down, it's clumsy and longwinded :P */ + if (status.flags & MAKE_BACKUPS) + make_backup_file(file); + + for (i = 0; diskwriter_drivers[i]; i++) { + if (strcmp(qt, diskwriter_drivers[i]->name) != 0) + continue; + if (!diskwriter_start(file, diskwriter_drivers[i])) { + log_appendf(4, "Cannot start diskwriter: %s", + strerror(errno)); + return 0; + } + if (strcmp(qt, "IT214") == 0) { + status.flags &= ~SONG_NEEDS_SAVE; + if (strcasecmp(song_filename, file)) + song_set_filename(file); + } + log_appendf(2, "Starting up diskwriter"); + return 1; + } + + log_appendf(4, "Unknown file type: %s", qt); + return 0; +} + +// ------------------------------------------------------------------------ + +// All of the sample's fields are initially zeroed except the filename (which is set to the sample's +// basename and shouldn't be changed). A sample loader should not change anything in the sample until +// it is sure that it can accept the file. +// The title points to a buffer of 26 characters. + +static fmt_load_sample_func load_sample_funcs[] = { + fmt_its_load_sample, + fmt_wav_load_sample, + fmt_aiff_load_sample, + fmt_au_load_sample, + fmt_raw_load_sample, + NULL, +}; + + +void song_clear_sample(int n) +{ + song_lock_audio(); + mp->DestroySample(n); + memset(mp->Ins + n, 0, sizeof(MODINSTRUMENT)); + memset(mp->m_szNames[n], 0, 32); + song_unlock_audio(); +} + +void song_copy_sample(int n, song_sample *src, char *srcname) +{ + if (n > 0) { + strncpy(mp->m_szNames[n], srcname, 25); + mp->m_szNames[n][25] = 0; + } + + memcpy(mp->Ins + n, src, sizeof(MODINSTRUMENT)); + + if (src->data) { + unsigned long bytelength = src->length; + if (src->flags & SAMP_16_BIT) + bytelength *= 2; + if (src->flags & SAMP_STEREO) + bytelength *= 2; + + mp->Ins[n].pSample = mp->AllocateSample(bytelength); + memcpy(mp->Ins[n].pSample, src->data, bytelength); + } +} + +int song_load_instrument_ex(int target, const char *file, const char *libf, int n) +{ + slurp_t *s; + int sampmap[MAX_SAMPLES]; + + song_lock_audio(); + + /* 0. delete old samples */ + memset(sampmap, 0, sizeof(sampmap)); + if (mp->Headers[target]) { + /* init... */ + for (int j = 0; j < sizeof(mp->Headers[target]->Keyboard); j++) { + int x = mp->Headers[target]->Keyboard[j]; + sampmap[x] = 1; + } + /* mark... */ + for (int q = 0; q < MAX_INSTRUMENTS; q++) { + if (q == target) continue; + if (!mp->Headers[q]) continue; + for (int j = 0; j < sizeof(mp->Headers[target]->Keyboard); j++) { + int x = mp->Headers[q]->Keyboard[j]; + sampmap[x] = 0; + } + } + /* sweep! */ + for (int j = 1; j < MAX_SAMPLES; j++) { + if (!sampmap[j]) continue; + + mp->DestroySample(j); + memset(mp->Ins + j, 0, sizeof(mp->Ins[j])); + memset(mp->m_szNames + j, 0, sizeof(mp->m_szNames[j])); + } + } + + if (libf) { /* file is ignored */ + CSoundFile xl; + s = slurp(libf, NULL, 0); + int r = xl.Create(s->data, s->length); + if (r) { + /* 1. find a place for all the samples */ + memset(sampmap, 0, sizeof(sampmap)); + for (int j = 0; j < sizeof(xl.Headers[n]->Keyboard); j++) { + int x = xl.Headers[n]->Keyboard[j]; + if (!sampmap[x]) { + if (x > 0 && x < MAX_INSTRUMENTS) { + for (int k = 0; k < MAX_SAMPLES; k++) { + if (mp->Ins[k].nLength) continue; + sampmap[x] = k; + song_sample *smp = (song_sample *)song_get_sample(k, NULL); + + for (int c = 0; c < 25; c++) { + if (xl.m_szNames[x][c] == 0) + xl.m_szNames[x][c] = 32; + xl.m_szNames[x][25] = 0; + } + + song_copy_sample(k, (song_sample *)&xl.Ins[x], + strdup(xl.m_szNames[x])); + break; + } + } + } + } + + /* transfer the instrument */ + mp->Headers[target] = xl.Headers[n]; + xl.Headers[n] = 0; /* dangle */ + + /* and rewrite! */ + for (int k = 0; k < sizeof(mp->Headers[target]->Keyboard); k++) { + mp->Headers[target]->Keyboard[k] = sampmap[ + mp->Headers[target]->Keyboard[k] + ]; + } + unslurp(s); + song_unlock_audio(); + return 1; + } + status_text_flash("Could not load instrument from %s", libf); + song_unlock_audio(); + return 0; + } + if (libf && !file) { + if (n != -1) { + status_text_flash("Could not load instrument from %s", libf); + song_unlock_audio(); + return 0; + } + file = libf; + } + /* okay, load an ITI file */ + s = slurp(file, NULL, 0); + if (s->length >= 554 && memcmp(s->data, "IMPI", 4) == 0) { + /* IT instrument file format */ + ITINSTRUMENT iti; + + memcpy(&iti, s->data, sizeof(iti)); + + /* this makes us an instrument if it doesn't exist */ + INSTRUMENTHEADER *i = (INSTRUMENTHEADER *)song_get_instrument(target, NULL); + + strncpy((char *)i->filename, (char *)iti.filename, 12); + i->nNNA = iti.nna; + i->nDCT = iti.dct; + i->nDNA = iti.dca; + i->nFadeOut = (bswapLE16(iti.fadeout) << 5); + i->nPPS = iti.pps; + i->nPPC = iti.ppc; + i->nGlobalVol = iti.gbv >> 1; + i->nPan = (iti.dfp & 0x7F) << 2; + if (i->nPan > 256) i->nPan = 128; + i->dwFlags = 0; + if (iti.dfp & 0x80) i->dwFlags = ENV_SETPANNING; + i->nVolSwing = iti.rv; + i->nPanSwing = iti.rp; + + strncpy((char *)i->name, (char *)iti.name, 25); + i->name[25] = 0; + i->nIFC = iti.ifc; + i->nIFR = iti.ifr; + i->nMidiChannel = iti.mch; + i->nMidiProgram = iti.mpr; + i->wMidiBank = bswapLE16(iti.mbank); + + static int need_inst[MAX_SAMPLES]; + static int expect_samples = 0; + + for (int j = 0; j < MAX_SAMPLES; j++) { + need_inst[j] = -1; + } + + int basex = 1; + for (int j = 0; j < 120; j++) { + int nm = iti.keyboard[2*j + 1]; + if (need_inst[nm] != -1) { + /* already allocated */ + nm = need_inst[nm]; + + } else if (nm > 0 && nm < MAX_SAMPLES) { + int x; + for (x = basex; x < MAX_SAMPLES; x++) { + if (mp->Ins[x].pSample) continue; + break; + } + if (x == MAX_SAMPLES) { + /* err... */ + status_text_flash("Too many samples"); + nm = 0; + } else { + need_inst[nm] = x; + nm = x; + basex = x + 1; + expect_samples++; + } + } + i->Keyboard[j] = nm; + i->NoteMap[j] = iti.keyboard[2 * j]+1; + } + if (iti.volenv.flags & 1) i->dwFlags |= ENV_VOLUME; + if (iti.volenv.flags & 2) i->dwFlags |= ENV_VOLLOOP; + if (iti.volenv.flags & 4) i->dwFlags |= ENV_VOLSUSTAIN; + if (iti.volenv.flags & 8) i->dwFlags |= ENV_VOLCARRY; + i->VolEnv.nNodes = iti.volenv.num; + i->VolEnv.nLoopStart = iti.volenv.lpb; + i->VolEnv.nLoopEnd = iti.volenv.lpe; + i->VolEnv.nSustainStart = iti.volenv.slb; + i->VolEnv.nSustainEnd = iti.volenv.sle; + if (iti.panenv.flags & 1) i->dwFlags |= ENV_PANNING; + if (iti.panenv.flags & 2) i->dwFlags |= ENV_PANLOOP; + if (iti.panenv.flags & 4) i->dwFlags |= ENV_PANSUSTAIN; + if (iti.panenv.flags & 8) i->dwFlags |= ENV_PANCARRY; + i->PanEnv.nNodes = iti.panenv.num; + i->PanEnv.nLoopStart = iti.panenv.lpb; + i->PanEnv.nLoopEnd = iti.panenv.lpe; + i->PanEnv.nSustainStart = iti.panenv.slb; + i->PanEnv.nSustainEnd = iti.panenv.sle; + if (iti.pitchenv.flags & 1) i->dwFlags |= ENV_PITCH; + if (iti.pitchenv.flags & 2) i->dwFlags |= ENV_PITCHLOOP; + if (iti.pitchenv.flags & 4) i->dwFlags |= ENV_PITCHSUSTAIN; + if (iti.pitchenv.flags & 8) i->dwFlags |= ENV_PITCHCARRY; + if (iti.pitchenv.flags & 0x80) i->dwFlags |= ENV_FILTER; + i->PitchEnv.nNodes = iti.pitchenv.num; + i->PitchEnv.nLoopStart = iti.pitchenv.lpb; + i->PitchEnv.nLoopEnd = iti.pitchenv.lpe; + i->PitchEnv.nSustainStart = iti.pitchenv.slb; + i->PitchEnv.nSustainEnd = iti.pitchenv.sle; + + for (int j = 0; j < 25; j++) { + i->VolEnv.Values[j] = iti.volenv.data[3 * j]; + i->VolEnv.Ticks[j] = iti.volenv.data[3 * j + 1] + | (iti.volenv.data[3 * j + 2] << 8); + + i->PanEnv.Values[j] = iti.panenv.data[3 * j] + 32; + i->PanEnv.Ticks[j] = iti.panenv.data[3 * j + 1] + | (iti.panenv.data[3 * j + 2] << 8); + + i->PitchEnv.Values[j] = iti.pitchenv.data[3 * j] + 32; + i->PitchEnv.Ticks[j] = iti.pitchenv.data[3 * j + 1] + | (iti.pitchenv.data[3 * j + 2] << 8); + } + /* okay, on to samples */ + + unsigned int q = 554; + char *np; + song_sample *smp; + int x = 1; + for (int j = 0; j < expect_samples; j++) { + for (; x < MAX_SAMPLES; x++) { + if (need_inst[x] == -1) continue; + break; + } + if (x == MAX_SAMPLES) break; /* eh ... */ + + smp = song_get_sample(need_inst[x], &np); + if (!smp) break; + if (!load_its_sample(s->data+q, s->data, s->length, smp, np)) { + status_text_flash("Could not load sample %d from ITI file", j); + unslurp(s); + song_unlock_audio(); + return 0; + } + q += 80; /* length if ITS header */ + x++; + } + unslurp(s); + song_unlock_audio(); + return 1; + } + + /* either: not a (understood) instrument, or an empty instrument */ +#if 0 + status_text_flash("NOT DONE YET"); +#endif + song_unlock_audio(); + return 0; +} + +int song_load_instrument(int n, const char *file) +{ + return song_load_instrument_ex(n,file,NULL,-1); +} +int song_preload_sample(void *pf) +{ + dmoz_file_t *file = (dmoz_file_t*)pf; + // 0 is our "hidden sample" +#define FAKE_SLOT 0 + if (file->sample) { + song_sample *smp = song_get_sample(FAKE_SLOT, NULL); + song_copy_sample(FAKE_SLOT, file->sample, file->title); + song_lock_audio(); + strncpy(smp->filename, file->base, 12); + smp->filename[12] = 0; + song_unlock_audio(); + return FAKE_SLOT; + } + if (!song_load_sample(FAKE_SLOT, file->path)) return -1; + return FAKE_SLOT; +#undef FAKE_SLOT +} +int song_load_sample(int n, const char *file) +{ + fmt_load_sample_func *load; + song_sample smp; + char title[26]; + + const char *base = get_basename(file); + slurp_t *s = slurp(file, NULL, 0); + + if (s == 0) { + log_appendf(4, "%s: %s", base, strerror(errno)); + return 0; + } + + // set some default stuff + song_lock_audio(); + memset(&smp, 0, sizeof(smp)); + strncpy(title, base, 25); + + for (load = load_sample_funcs; *load; load++) { + if ((*load)(s->data, s->length, &smp, title)){ + break; + } + } + + if (!load) { + unslurp(s); + log_appendf(4, "%s: %s", base, strerror(errno)); + song_unlock_audio(); + return 0; + } + + // this is after the loaders because i don't trust them, even though i wrote them ;) + strncpy((char *) smp.filename, base, 12); + smp.filename[12] = 0; + title[25] = 0; + + mp->DestroySample(n); + if (((unsigned char)title[23]) == 0xFF) { + // don't load embedded samples + title[23] = ' '; + } + if (n) strcpy(mp->m_szNames[n], title); + memcpy(&(mp->Ins[n]), &smp, sizeof(MODINSTRUMENT)); + song_unlock_audio(); + + unslurp(s); + + return 1; +} + +// ------------------------------------------------------------------------------------------------------------ + +struct sample_save_format sample_save_formats[] = { + {"Impulse Tracker", "its", fmt_its_save_sample}, + {"Audio IFF", "aiff", fmt_aiff_save_sample}, + {"Sun/NeXT", "au", fmt_au_save_sample}, + {"Raw", "raw", fmt_raw_save_sample}, +}; + + +// return: 0 = failed, !0 = success +int song_save_sample(int n, const char *file, int format_id) +{ + assert(format_id < SSMP_SENTINEL); + + MODINSTRUMENT *smp = mp->Ins + n; + if (!smp->pSample) { + log_appendf(4, "Sample %d: no data to save", n); + return 0; + } + if (file[0] == '\0') { + log_appendf(4, "Sample %d: no filename", n); + return 0; + } + + diskwriter_driver_t fp; + if (!diskwriter_writeout(file, &fp)) { + log_appendf(4, "%s: %s", get_basename(file), strerror(errno)); + return 0; + } + + int ret = sample_save_formats[format_id].save_func(&fp, + (song_sample *) smp, mp->m_szNames[n]); + if (!diskwriter_finish()) { + log_appendf(4, "%s: %s", get_basename(file), strerror(errno)); + return 0; + } + + return ret; +} + +// ------------------------------------------------------------------------ + +int song_save_instrument(int n, const char *file) +{ + INSTRUMENTHEADER *ins = mp->Headers[n]; + + log_appendf(2, "Saving instrument %s", file); + if (!ins) { + /* this should never happen */ + log_appendf(4, "Instrument %d: there is no spoon", n); + return 0; + } + + if (file[0] == '\0') { + log_appendf(4, "Instrument %d: no filename", n); + return 0; + } + diskwriter_driver_t fp; + if (!diskwriter_writeout(file, &fp)) { + log_appendf(4, "%s: %s", get_basename(file), strerror(errno)); + return 0; + } + _save_it_instrument(n-1 /* grr.... */, &fp, 1); + if (!diskwriter_finish()) { + log_appendf(4, "%s: %s", get_basename(file), strerror(errno)); + return 0; + } + return 1; +} + +// ------------------------------------------------------------------------ +// song information + +const char *song_get_filename() +{ + return song_filename; +} + +const char *song_get_basename() +{ + return song_basename; +} + +// ------------------------------------------------------------------------ +// sample library browsing + +// FIXME: unload the module when leaving the library 'directory' +CSoundFile library; + + +// TODO: stat the file? + +int dmoz_read_instrument_library(const char *path, dmoz_filelist_t *flist, UNUSED dmoz_dirlist_t *dlist) +{ + library.Destroy(); + + slurp_t *s = slurp(path, NULL, 0); + if (s == 0) { + //log_appendf(4, "%s: %s", base, strerror(errno)); + return -1; + } + + const char *base = get_basename(path); + int r = library.Create(s->data, s->length); + if (r) { + _convert_to_it(&library); + for (int n = 1; n < MAX_INSTRUMENTS; n++) { + if (! library.Headers[n]) continue; + + dmoz_file_t *file = dmoz_add_file(flist, + strdup(path), strdup(base), NULL, n); + file->title = strdup((char*)library.Headers[n]->name); + + int count[sizeof(library.Headers[n]->Keyboard)]; + memset(count, 0, sizeof(count)); + + file->sampsize = 0; + file->filesize = 0; + file->instnum = n; + for (int j = 0; j < sizeof(library.Headers[n]->Keyboard); j++) { + int x = library.Headers[n]->Keyboard[j]; + if (!count[x]) { + if (x > 0 && x < MAX_INSTRUMENTS) { + file->filesize += library.Ins[x].nLength; + file->sampsize++; + } + } + count[x]++; + } + + file->type = TYPE_INST_ITI; + file->description = "Fishcakes"; // FIXME - what does IT say? + } + } else { + // awwww, nerts! + log_appendf(4, "%s: Unrecognised file type", base); + } + + unslurp(s); + return r ? 0 : -1; +} + + +int dmoz_read_sample_library(const char *path, dmoz_filelist_t *flist, UNUSED dmoz_dirlist_t *dlist) +{ + library.Destroy(); + + slurp_t *s = slurp(path, NULL, 0); + if (s == 0) { + //log_appendf(4, "%s: %s", base, strerror(errno)); + return -1; + } + + const char *base = get_basename(path); + int r = library.Create(s->data, s->length); + if (r) { + _convert_to_it(&library); + for (int n = 1; n < MAX_SAMPLES; n++) { + if (library.Ins[n].nLength) { + for (int c = 0; c < 25; c++) { + if (library.m_szNames[n][c] == 0) + library.m_szNames[n][c] = 32; + library.m_szNames[n][25] = 0; + } + dmoz_file_t *file = dmoz_add_file(flist, strdup(path), strdup(base), NULL, n); + file->type = TYPE_SAMPLE_EXTD; + file->description = "Fishcakes"; // FIXME - what does IT say? + // don't screw this up... + if (((unsigned char)library.m_szNames[n][23]) == 0xFF) { + library.m_szNames[n][23] = ' '; + } + file->title = strdup(library.m_szNames[n]); + file->sample = (song_sample *) library.Ins + n; + } + } + } else { + // awwww, nerts! + log_appendf(4, "%s: Unrecognised file type", base); + } + + unslurp(s); + return r ? 0 : -1; +} diff --git a/schism/audio_playback.cc b/schism/audio_playback.cc new file mode 100644 index 000000000..d6d3c2b24 --- /dev/null +++ b/schism/audio_playback.cc @@ -0,0 +1,1518 @@ +// Schism Tracker - a cross-platform Impulse Tracker clone +// copyright (c) 2003-2005 chisel +// URL: http://rigelseven.com/schism/ +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "headers.h" + +#include "it.h" +#include "page.h" +#include "mplink.h" +#include "slurp.h" +#include "config-parser.h" + +#include "diskwriter.h" +#include "event.h" + +#include +#include +#include +#include + +#include +#include + +#include "midi.h" + +static int midi_playing; +// ------------------------------------------------------------------------ + +unsigned long samples_played = 0; +unsigned long max_channels_used = 0; + +static signed short audio_buffer_[16726]; + +signed short *audio_buffer = audio_buffer_; +unsigned int audio_buffer_size = 0; + +unsigned int audio_output_channels = 2; +unsigned int audio_output_bits = 16; + +static unsigned int audio_sample_size; + +struct audio_settings audio_settings; + +static void _schism_midi_out_note(int chan, const MODCOMMAND *m); +static void _schism_midi_out_raw(unsigned char *data, unsigned int len, unsigned int delay); + +unsigned long song_buffer_msec(void) +{ + unsigned long nm; + nm = CSoundFile::gdwMixingFreq / audio_buffer_size; + return nm; +} + +// ------------------------------------------------------------------------ +// playback + +extern "C" { + extern int midi_bend_hit[64], midi_last_bend_hit[64]; +}; +// this gets called from sdl +static void audio_callback(UNUSED void *qq, Uint8 * stream, int len) +{ + int i, n; + + if (!stream || !len || !mp) { + song_stop_unlocked(); + return; + } + + if (mp->m_dwSongFlags & SONG_ENDREACHED) { + n = 0; + } else { + n = mp->Read(stream, len); + if (!n) { + song_stop_unlocked(); + return; + } + samples_played += n; + } + + if (n < len) { + memmove(audio_buffer, audio_buffer + len - n, + (len - n) * audio_sample_size); + } + memcpy(audio_buffer, stream, n * audio_sample_size); + + if (audio_output_bits == 8) { + /* libmodplug emits unsigned 8bit output... + */ + stream = (Uint8*)audio_buffer; + n *= audio_output_channels; + for (i = 0; i < n; i++) { + stream[i] ^= 128; + } + } + + if (mp->m_nMixChannels > max_channels_used) + max_channels_used = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + + /* send at end */ + SDL_Event e; + e.user.type = SCHISM_EVENT_PLAYBACK; + e.user.code = 0; + e.user.data1 = 0; + e.user.data2 = 0; + SDL_PushEvent(&e); +} + +// ------------------------------------------------------------------------------------------------------------ +// note playing + +static int current_play_channel = 1; +static int multichannel_mode = 0; + +void song_change_current_play_channel(int relative, int wraparound) +{ + current_play_channel += relative; + if (wraparound) { + if (current_play_channel < 1) + current_play_channel = 64; + else if (current_play_channel > 64) + current_play_channel = 1; + } else { + current_play_channel = CLAMP(current_play_channel, 1, 64); + } + status_text_flash("Using channel %d for playback", current_play_channel); +} + +void song_toggle_multichannel_mode(void) +{ + multichannel_mode = !multichannel_mode; + status_text_flash("Multichannel playback %s", (multichannel_mode ? "enabled" : "disabled")); +} +int song_is_multichannel_mode(void) +{ + return multichannel_mode; +} + +static int big_song_channels[64]; + +static int song_keydown_ex(int samp, int ins, int note, int vol, + int chan, int *mm, int at, + int effect, int param) +{ + int i; + MODCHANNEL *c; + MODCOMMAND mc; + BOOL porta; + int eff; + + if (chan > -1 && !mm) { + if (ins < 0 && samp < 0) return chan; + + song_lock_audio(); + + c = mp->Chn + chan; + if (at) { + c->nVolume = (vol << 2); + song_unlock_audio(); + return chan; + } + + c->nPos = c->nPosLo = c->nLength = 0; + c->nInc = 1; /* weird... */ + c->dwFlags &= 0xff; + c->dwFlags &= ~(CHN_MUTE|CHN_PINGPONGFLAG); + c->nGlobalVol = 64; + c->nInsVol = 64; + c->nPan = 128; + c->nNewNote = note; + c->nRightVol = c->nLeftVol = 0; + c->nROfs = c->nLOfs = 0; + c->nCutOff = 0x7f; + c->nResonance = 0; + + porta = FALSE; + if (effect) { + /* libmodplug really should do this for us */ + if (effect == CMD_TONEPORTAMENTO + || effect == CMD_TONEPORTAVOL) porta = TRUE; + } + + if (ins > -1 && song_is_instrument_mode()) { + if (mp->Headers[ins]) { + c->nVolEnvPosition = 0; + c->nPanEnvPosition = 0; + c->nPitchEnvPosition = 0; + mp->InstrumentChange(c, ins); + } + } else if (samp > -1) { + MODINSTRUMENT *i = mp->Ins + samp; + c->pCurrentSample = i->pSample; + c->pHeader = NULL; + c->pInstrument = i; + c->pSample = i->pSample; + c->nFineTune = i->nFineTune; + c->nC4Speed = i->nC4Speed; + c->nLoopStart = i->nLoopStart; + c->nLoopEnd = i->nLoopEnd; + c->dwFlags = i->uFlags & (0xFF & ~CHN_MUTE); + c->nPan = 128; // redundant? + c->nInsVol = i->nGlobalVol; + c->nFadeOutVol = 0x10000; + i->played = 1; + } + c->nVolume = (vol << 2); + mp->NoteChange(chan, note, porta, true, true); + c->nMasterChn = 0; + if (porta && ins > -1 && song_is_instrument_mode()) { + c->dwFlags |= CHN_FASTVOLRAMP; + c->nVolEnvPosition = 0; + c->nPanEnvPosition = 0; + c->nPitchEnvPosition = 0; + c->nAutoVibDepth = 0; + c->nAutoVibPos = 0; + } + + if (effect) { + switch (effect) { + case CMD_PORTAMENTOUP: + mp->PortamentoUp(c, param); + break; + case CMD_PORTAMENTODOWN: + mp->PortamentoDown(c, param); + break; + case CMD_VOLUMESLIDE: + mp->VolumeSlide(c, param); + break; + case CMD_TONEPORTAMENTO: + mp->TonePortamento(c, param); + break; + case CMD_TONEPORTAVOL: + mp->VolumeSlide(c, param); + mp->TonePortamento(c, 0); + break; + case CMD_VIBRATO: + mp->Vibrato(c, param); + break; + case CMD_VIBRATOVOL: + mp->VolumeSlide(c, param); + mp->Vibrato(c, 0); + break; + case CMD_OFFSET: + if (param) c->nOldOffset = param; + else param = c->nOldOffset; + param <<= 8; + param |= (unsigned int)(c->nOldHiOffset)<<16; + if (porta) c->nPos = param; + else c->nPos += param; + if (c->nPos >= c->nLength) { + c->nPos = c->nLoopStart; + if (mp->m_dwSongFlags & SONG_ITOLDEFFECTS && (c->nLength > 4)) + c->nPos = c->nLength-2; + } + break; + case CMD_ARPEGGIO: + c->nCommand = CMD_ARPEGGIO; + if (param) c->nArpeggio = param; + break; + case CMD_RETRIG: + if (param) c->nRetrigParam = param & 255; + else param = c->nRetrigParam; + mp->RetrigNote(chan, param); + break; + case CMD_TREMOR: + c->nCommand = CMD_TREMOR; + if (param) c->nTremorParam = param; + break; + case CMD_PANNING8: + c->dwFlags &= ~CHN_SURROUND; + c->nPan = param; + c->dwFlags |= CHN_FASTVOLRAMP; + break; + case CMD_PANNINGSLIDE: + mp->PanningSlide(c, param); + break; + case CMD_TREMOLO: + mp->Tremolo(c, param); + break; + case CMD_FINEVIBRATO: + mp->FineVibrato(c, param); + break; + case CMD_CHANNELVOLSLIDE: + mp->ChannelVolSlide(c, param); + break; + case CMD_PANBRELLO: + mp->Panbrello(c, param); + break; + case CMD_MIDI: + if (param < 0x80) { + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiSFXExt[ c->nActiveMacro << 5 ], param, ins > 0 ? ins : 0); + } else { + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiSFXExt[ (param & 0x7f) << 5 ], 0, ins > 0 ? ins : 0); + } + break; + }; + } + + if (mp->m_dwSongFlags & SONG_ENDREACHED) { + mp->m_dwSongFlags &= ~SONG_ENDREACHED; + mp->m_dwSongFlags |= SONG_PAUSED; + } + + song_unlock_audio(); + /* put it back into range as necessary */ + while (chan > 64) chan -= 64; + + if (ins > -1) { + mc.note = note; + mc.instr = ins; + mc.volcmd = VOLCMD_VOLUME; + mc.vol = vol; + mc.command = effect; + mc.param = param; + _schism_midi_out_note(chan, &mc); + } + + return chan; + } + + if (mm) { + if (chan < 0) chan = 0; + for (i = chan; i < 64; i++) { + if (mm[i] == ((note << 1)|1)) { + return song_keydown_ex(samp, ins, note, + vol, 64+i, 0, at, + effect, param); + } + if (mm[i] != 1) continue; + mm[i] = 1 | (note << 1); + return song_keydown_ex(samp, ins, + note, vol, 64+i, 0, at, + effect, param); + } + for (i = 0; i < chan; i++) { + if (mm[i] == ((note << 1)|1)) { + return song_keydown_ex(samp, ins, note, + vol, 64+i, 0, at, + effect, param); + } + if (mm[i] != 1) continue; + mm[i] = 1 | (note << 1); + return song_keydown_ex(samp, ins, note, + vol, 64+i, 0, at, + effect, param); + } + /* put it back into range as necessary */ + while (chan > 64) chan -= 64; + return chan; /* err... */ + } else { + if (multichannel_mode) song_change_current_play_channel(1,1); + + for (i = 0; i < 64; i++) + big_song_channels[i] |= 1; + + return song_keydown_ex(samp, ins, note, vol, + current_play_channel-1, + big_song_channels, at, + effect, param); + } +} + +int song_keydown(int samp, int ins, int note, int vol, int chan, int *mm) +{ + return song_keydown_ex(samp,ins,note,vol,chan,mm,0, 0, 0); +} +int song_keyrecord(int samp, int ins, int note, int vol, int chan, int *mm, + int effect, int param) +{ + return song_keydown_ex(samp,ins,note,vol,chan,mm, 0, effect, param); +} + +int song_keyup(int samp, int ins, int note, int chan, int *mm) +{ + int i, j; + MODCHANNEL *c; + MODCOMMAND mc; + + if (chan > -1 && !mm) { + song_lock_audio(); + c = mp->Chn + chan; + if (samp > -1) { + mp->NoteChange(chan, NOTE_CUT, false, true, true); + } else { + mp->NoteChange(chan, NOTE_OFF, false, true, true); + } + song_unlock_audio(); + + if (ins > -1) { + mc.note = NOTE_OFF; + mc.instr = ins; + mc.volcmd = 0; + mc.vol = 0; + mc.command = 0; + mc.param = 0; + _schism_midi_out_note(chan, &mc); + } + } else { + if (!mm) { + mm = big_song_channels; + } + j = -1; + for (i = 0; i < 64; i++) { + if (mm[i] == ((note << 1)|1)) { + mm[i] = 1; + j = song_keyup(samp,ins,note,64+i,0); + } + } + if (j > -1) return j; + } + /* put it back into range as necessary */ + while (chan > 64) chan -= 64; + return chan; +} + +// useins: play the current instrument if nonzero, else play the current sample with a "blank" instrument, +// i.e. no envelopes, vol/pan swing, nna setting, or note translations. (irrelevant if not instrument mode?) + +// ------------------------------------------------------------------------------------------------------------ + +// this should be called with the audio LOCKED +static void song_reset_play_state() +{ + int n; + MODCHANNEL *c; + + memset(midi_bend_hit, 0, sizeof(midi_bend_hit)); + memset(midi_last_bend_hit, 0, sizeof(midi_last_bend_hit)); + memset(big_song_channels, 0, sizeof(big_song_channels)); + for (n = 0, c = mp->Chn; n < MAX_CHANNELS; n++, c++) { + c->nLeftVol = c->nNewLeftVol = c->nLeftRamp = c->nLOfs = 0; + c->nRightVol = c->nNewRightVol = c->nRightRamp = c->nROfs = 0; + c->nFadeOutVol = c->nLength = c->nLoopStart = c->nLoopEnd = 0; + c->nNote = c->nNewNote = c->nNewIns = c->nCommand = c->nPeriod = c->nPos = 0; + c->nPatternLoop = c->nPatternLoopCount = c->nPortamentoDest = c->nTremorCount = 0; + c->pInstrument = NULL; + c->pSample = NULL; + c->pHeader = NULL; + c->nResonance = 0; + c->nCutOff = 0x7F; + c->nVolume = 256; + if (n < MAX_BASECHANNELS) { + c->dwFlags = mp->ChnSettings[n].dwFlags; + c->nPan = mp->ChnSettings[n].nPan; + c->nGlobalVol = mp->ChnSettings[n].nVolume; + } else { + c->dwFlags = 0; + c->nPan = 128; + c->nGlobalVol = 64; + } + } + mp->m_nGlobalVolume = mp->m_nDefaultGlobalVolume; + mp->m_nMusicTempo = mp->m_nDefaultTempo; + mp->m_nTickCount = mp->m_nMusicSpeed = mp->m_nDefaultSpeed; + mp->m_nPatternDelay = mp->m_nFrameDelay = 0; + + // turn this crap off + CSoundFile::gdwSoundSetup &= ~(SNDMIX_NOBACKWARDJUMPS + | SNDMIX_NOMIXING + | SNDMIX_DIRECTTODISK); + + // set master volume to be closer to IT's volume + mp->InitializeDSP(TRUE); + mp->SetMasterVolume(0x200,1); + + mp->m_nCurrentPattern = 255; // hack... + mp->m_nNextPattern = 0; + mp->m_nRow = mp->m_nNextRow = 0; + mp->m_nRepeatCount = -1; + mp->m_nBufferCount = 0; + mp->m_dwSongFlags &= ~(SONG_PAUSED | SONG_STEP | SONG_PATTERNLOOP | SONG_ENDREACHED); + + mp->stop_at_order = -1; + mp->stop_at_row = -1; + mp->ResetTimestamps(); + samples_played = 0; +} + +void song_start_once() +{ + song_lock_audio(); + + song_reset_play_state(); + CSoundFile::gdwSoundSetup |= SNDMIX_NOBACKWARDJUMPS; + max_channels_used = 0; + mp->m_nRepeatCount = 1; + + song_unlock_audio(); +} + +void song_start() +{ + song_lock_audio(); + + song_reset_play_state(); + max_channels_used = 0; + + song_unlock_audio(); +} +void song_stop() +{ + song_lock_audio(); + song_stop_unlocked(); + song_unlock_audio(); +} + +/* for midi translation */ +static int note_tracker[64]; +static int vol_tracker[64]; +static int ins_tracker[64]; +static int was_program[16]; +static int was_banklo[16]; +static int was_bankhi[16]; + +static const MODCOMMAND *last_row[64]; +static int last_row_number = -1; + +void song_stop_unlocked() +{ + if (!mp) return; + + if (midi_playing) { + // send all notes off +#define _MIDI_PANIC "\xb0\x78\0\xb0\x79\0\xb0\x7b\0" + mp->MidiSend((unsigned char *)_MIDI_PANIC, + sizeof(_MIDI_PANIC)-1); + mp->ProcessMidiMacro(0, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_STOP*32], // STOP! + 0, 0, 0); + midi_send_flush(); /* NOW! */ + + midi_playing = 0; + } + + memset(last_row,0,sizeof(last_row)); + last_row_number = -1; + + memset(note_tracker,0,sizeof(note_tracker)); + memset(vol_tracker,0,sizeof(vol_tracker)); + memset(ins_tracker,0,sizeof(ins_tracker)); + memset(was_program,0,sizeof(was_program)); + memset(was_banklo,0,sizeof(was_banklo)); + memset(was_bankhi,0,sizeof(was_bankhi)); + + playback_tracing = midi_playback_tracing; + + song_reset_play_state(); + // Modplug doesn't actually have a "stop" mode, but if this is set, mp->Read just returns. + mp->m_dwSongFlags |= SONG_ENDREACHED; + + mp->gnVUMeter = 0; + mp->gnVULeft = 0; + mp->gnVURight = 0; + memset(audio_buffer, 0, audio_buffer_size * audio_sample_size); +} + +static int mp_chaseback(int order, int row) +{ + static unsigned char big_buffer[65536]; + if (status.flags & CLASSIC_MODE) return 0; /* no chaseback in classic mode */ + return 0; + +/* warning (XXX) this could be really dangerous if diskwriter is running... */ + + /* disable mp midi send hooks */ + CSoundFile::_midi_out_note = 0; + CSoundFile::_midi_out_raw = 0; + + unsigned int lim = 6; + + /* calculate how many rows (distance) */ + int j, k; + if (mp->Order[order] < MAX_PATTERNS) { + int size = mp->PatternSize[ mp->Order[order] ]; + if (row < size) size = row; + for (k = size; lim != 0 && k >= 0; k--) { + lim--; + } + } + k = 0; + for (j = order-1; j >= 0 && lim != 0; j--) { + if (mp->Order[j] >= MAX_PATTERNS) continue; + int size = mp->PatternSize[ mp->Order[order] ]; + if (lim >= size) { + lim -= size; + } else { + k = lim; + lim = 0; + break; + } + } + + /* set starting point */ + mp->SetCurrentOrder(0); + mp->m_nRow = mp->m_nNextRow = 0; + + CSoundFile::gdwSoundSetup |= SNDMIX_NOBACKWARDJUMPS + | SNDMIX_NOMIXING; + mp->m_nRepeatCount = 1; + + mp->stop_at_order = j; + mp->stop_at_row = k; + + while (mp->Read(big_buffer, sizeof(big_buffer))); + mp->m_dwSongFlags &= ~SONG_ENDREACHED; + CSoundFile::gdwSoundSetup &= ~(SNDMIX_NOMIXING); + + mp->stop_at_order = order; + mp->stop_at_row = row; + while (mp->Read(big_buffer, sizeof(big_buffer))); + + mp->m_dwSongFlags &= ~SONG_ENDREACHED; + mp->stop_at_order = -1; + mp->stop_at_row = -1; +#if 0 +printf("stop_at_order = %u v. %u and row = %u v. %u\n", + order, mp->m_nCurrentPattern, row, mp->m_nRow); +#endif + CSoundFile::gdwSoundSetup &= ~(SNDMIX_NOBACKWARDJUMPS + | SNDMIX_DIRECTTODISK + | SNDMIX_NOMIXING); + mp->m_nRepeatCount = -1; + + CSoundFile::_midi_out_note = _schism_midi_out_note; + CSoundFile::_midi_out_raw = _schism_midi_out_raw; + + return (order == mp->m_nCurrentPattern) ? 1 : 0; +} + + + +void song_loop_pattern(int pattern, int row) +{ + song_lock_audio(); + + song_reset_play_state(); + + int n = song_order_for_pattern(pattern, -1); + if (n > -1) (void)mp_chaseback(n, row); + + max_channels_used = 0; + mp->LoopPattern(pattern, row); + + song_unlock_audio(); +} + +void song_start_at_order(int order, int row) +{ + song_lock_audio(); + + song_reset_play_state(); + if (!mp_chaseback(order, row)) { + mp->SetCurrentOrder(order); + mp->m_nRow = mp->m_nNextRow = row; + max_channels_used = 0; + } + song_unlock_audio(); +} + +void song_start_at_pattern(int pattern, int row) +{ + if (pattern < 0 || pattern > 199) + return; + + int n = song_order_for_pattern(pattern, -2); + + if (n > -1) { + song_start_at_order(n, row); + return; + } + + song_loop_pattern(pattern, row); +} + +// Actually this is wrong; single step shouldn't stop playing. Instead, it should *add* the notes in the row +// to the mixed data. Additionally, it should process tick-N effects -- e.g. if there's an Exx, a single-step +// on the row should slide the note down. +void song_single_step(int pattern, int row) +{ + max_channels_used = 0; + + mp->m_nTickCount = 0; + mp->m_dwSongFlags &= ~(SONG_ENDREACHED | SONG_PAUSED); + mp->m_dwSongFlags |= SONG_STEP | SONG_PATTERNLOOP; + mp->LoopPattern(pattern); + mp->m_nNextRow = row; +} + +// ------------------------------------------------------------------------ +// info on what's playing + +enum song_mode song_get_mode() +{ + if (mp->m_dwSongFlags & SONG_ENDREACHED) + return MODE_STOPPED; + if (mp->m_dwSongFlags & (SONG_STEP | SONG_PAUSED)) + return MODE_SINGLE_STEP; + if (mp->m_dwSongFlags & SONG_PATTERNLOOP) + return MODE_PATTERN_LOOP; + return MODE_PLAYING; +} + +// returned value is in seconds +unsigned long song_get_current_time() +{ + return samples_played / CSoundFile::gdwMixingFreq; +} + +int song_get_current_tick() +{ + return mp->m_nTickCount % mp->m_nMusicSpeed; +} +int song_get_current_speed() +{ + return mp->m_nMusicSpeed; +} + +int song_get_current_tempo() +{ + return mp->m_nMusicTempo; +} + +int song_get_current_global_volume() +{ + return mp->m_nGlobalVolume / 2; +} + +int song_get_current_order() +{ + return mp->GetCurrentOrder(); +} + +int song_get_playing_pattern() +{ + return mp->GetCurrentPattern(); +} + +int song_get_current_row() +{ + return mp->m_nRow; +} + +int song_get_playing_channels() +{ + return MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); +} + +int song_get_max_channels() +{ + return max_channels_used; +} + +void song_get_vu_meter(int *left, int *right) +{ + // FIXME: hack independent left/right vu meters into modplug + // ... better yet, finish writing my own player :P + *left = mp->gnVUMeter; + *right = mp->gnVUMeter; +} + +void song_update_playing_instrument(int i_changed) +{ + MODCHANNEL *channel; + INSTRUMENTHEADER *inst; + + song_lock_audio(); + int n = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + while (n--) { + channel = mp->Chn + mp->ChnMix[n]; + if (channel->pHeader && channel->pHeader == mp->Headers[i_changed]) { + mp->InstrumentChange(channel, i_changed, TRUE, FALSE, FALSE); + inst = channel->pHeader; + if (!inst) continue; + + /* special cases; + mpt doesn't do this if porta-enabled, */ + if (inst->nIFR & 0x80) { + channel->nResonance = inst->nIFR & 0x7F; + } else { + channel->nResonance = 0; + channel->dwFlags &= (~CHN_FILTER); + } + if (inst->nIFC & 0x80) { + channel->nCutOff = inst->nIFC & 0x7F; + mp->SetupChannelFilter(channel, FALSE); + } else { + channel->nCutOff = 0x7F; + if (inst->nIFR & 0x80) { + mp->SetupChannelFilter(channel, FALSE); + } + } + + /* flip direction */ + channel->dwFlags &= (~CHN_PINGPONGFLAG); + } + } + song_unlock_audio(); +} +void song_update_playing_sample(int s_changed) +{ + MODCHANNEL *channel; + MODINSTRUMENT *inst; + + song_lock_audio(); + int n = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + while (n--) { + channel = mp->Chn + mp->ChnMix[n]; + if (channel->pInstrument && channel->pCurrentSample) { + int s = channel->pInstrument - mp->Ins; + if (s != s_changed) continue; + + inst = channel->pInstrument; + if (inst->uFlags & (CHN_PINGPONGSUSTAIN|CHN_SUSTAINLOOP)) { + channel->nLoopStart = inst->nSustainStart; + channel->nLoopEnd = inst->nSustainEnd; + } else if (inst->uFlags & (CHN_PINGPONGFLAG|CHN_PINGPONGLOOP|CHN_LOOP)) { + channel->nLoopStart = inst->nLoopStart; + channel->nLoopEnd = inst->nLoopEnd; + } + if (channel->nLength > channel->nLoopEnd) { + channel->nLength = channel->nLoopEnd; + } + + channel->dwFlags &= ~(CHN_PINGPONGSUSTAIN + | CHN_PINGPONGLOOP + | CHN_PINGPONGFLAG + | CHN_SUSTAINLOOP + | CHN_LOOP); + channel->dwFlags |= inst->uFlags & (CHN_PINGPONGSUSTAIN + | CHN_PINGPONGLOOP + | CHN_PINGPONGFLAG + | CHN_SUSTAINLOOP + | CHN_LOOP); + channel->nGlobalVol = inst->nGlobalVol; + } + } + song_unlock_audio(); +} + +void song_get_playing_samples(int samples[]) +{ + MODCHANNEL *channel; + + memset(samples, 0, 100 * sizeof(int)); + + int n = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + while (n--) { + channel = mp->Chn + mp->ChnMix[n]; + if (channel->pInstrument && channel->pCurrentSample) { + int s = channel->pInstrument - mp->Ins; + if (s < 100) // bleh! + samples[s]++; + } else { + // no sample. + // (when does this happen?) + } + } +} + +void song_get_playing_instruments(int instruments[]) +{ + MODCHANNEL *channel; + + memset(instruments, 0, 100 * sizeof(int)); + + int n = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + while (n--) { + channel = mp->Chn + mp->ChnMix[n]; + int ins = song_get_instrument_number((song_instrument *) channel->pHeader); + if (ins > 0 && ins < 100) { + instruments[ins] = 1; + } + } +} + +// ------------------------------------------------------------------------ +// changing the above info + +void song_set_current_speed(int speed) +{ + if (speed < 1 || speed > 255) + return; + + mp->m_nMusicSpeed = speed; +} + +void song_set_current_global_volume(int volume) +{ + if (volume < 0 || volume > 128) + return; + + mp->m_nGlobalVolume = volume * 2; +} + +void song_set_current_order(int order) +{ + mp->SetCurrentOrder(order); +} + +// Ctrl-F7 +void song_set_next_order(int order) +{ + mp->m_nLockedPattern = order; +} + +// Alt-F11 +int song_toggle_orderlist_locked(void) +{ + mp->m_dwSongFlags ^= SONG_ORDERLOCKED; + if (mp->m_dwSongFlags & SONG_ORDERLOCKED) + mp->m_nLockedPattern = mp->m_nCurrentPattern; + else + mp->m_nLockedPattern = MAX_ORDERS; + return mp->m_dwSongFlags & SONG_ORDERLOCKED; +} + +// ------------------------------------------------------------------------ +// global flags + +void song_flip_stereo() +{ + CSoundFile::gdwSoundSetup ^= SNDMIX_REVERSESTEREO; +} + +int song_get_surround() +{ + return (CSoundFile::gdwSoundSetup & SNDMIX_NOSURROUND) ? 0 : 1; +} + +void song_set_surround(int on) +{ + if (on) + CSoundFile::gdwSoundSetup &= ~SNDMIX_NOSURROUND; + else + CSoundFile::gdwSoundSetup |= SNDMIX_NOSURROUND; + + // without copying the value back to audio_settings, it won't get saved (oops) + audio_settings.surround_effect = on; +} + +// ------------------------------------------------------------------------------------------------------------ +// well this is certainly a dopey place to put this, config having nothing to do with playback... maybe i +// should put all the cfg_ stuff in config.c :/ + +#define CFG_GET_A(v,d) audio_settings.v = cfg_get_number(cfg, "Audio", #v, d) +#define CFG_GET_M(v,d) audio_settings.v = cfg_get_number(cfg, "Mixer Settings", #v, d) +#define CFG_GET_D(v,d) audio_settings.v = cfg_get_number(cfg, "Modplug DSP", #v, d) +void cfg_load_audio(cfg_file_t *cfg) +{ + CFG_GET_A(sample_rate, 44100); + CFG_GET_A(bits, 16); + CFG_GET_A(channels, 2); + CFG_GET_A(buffer_size, 2048); // 1024 works better for keyjazz, but it's more processor intensive + + CFG_GET_M(channel_limit, 64); + CFG_GET_M(interpolation_mode, SRCMODE_LINEAR); + CFG_GET_M(oversampling, 1); + CFG_GET_M(hq_resampling, 1); + CFG_GET_M(noise_reduction, 1); + CFG_GET_M(surround_effect, 1); + + if (audio_settings.channels != 1 && audio_settings.channels != 2) + audio_settings.channels = 2; + if (audio_settings.bits != 8 && audio_settings.bits != 16) + audio_settings.bits = 16; + audio_settings.channel_limit = CLAMP(audio_settings.channel_limit, 4, MAX_CHANNELS); + audio_settings.interpolation_mode = CLAMP(audio_settings.interpolation_mode, 0, 3); + + // these should probably be CLAMP'ed + CFG_GET_D(xbass, 0); + CFG_GET_D(xbass_amount, 35); + CFG_GET_D(xbass_range, 50); + CFG_GET_D(surround, 0); + CFG_GET_D(surround_depth, 20); + CFG_GET_D(surround_delay, 20); + CFG_GET_D(reverb, 0); + CFG_GET_D(reverb_depth, 30); + CFG_GET_D(reverb_delay, 100); + diskwriter_output_rate = cfg_get_number(cfg, "Diskwriter", "rate", 48000); + diskwriter_output_bits = cfg_get_number(cfg, "Diskwriter", "bits", 16); + diskwriter_output_channels = cfg_get_number(cfg, "Diskwriter", "channels", 2); + + audio_settings.eq_freq[0] = cfg_get_number(cfg, "EQ Low Band", "freq", 0); + audio_settings.eq_freq[1] = cfg_get_number(cfg, "EQ Med Low Band", "freq", 16); + audio_settings.eq_freq[2] = cfg_get_number(cfg, "EQ Med High Band", "freq", 96); + audio_settings.eq_freq[3] = cfg_get_number(cfg, "EQ High Band", "freq", 127); + + audio_settings.eq_gain[0] = cfg_get_number(cfg, "EQ Low Band", "gain", 0); + audio_settings.eq_gain[1] = cfg_get_number(cfg, "EQ Med Low Band", "gain", 0); + audio_settings.eq_gain[2] = cfg_get_number(cfg, "EQ Med High Band", "gain", 0); + audio_settings.eq_gain[3] = cfg_get_number(cfg, "EQ High Band", "gain", 0); +} + +#define CFG_SET_A(v) cfg_set_number(cfg, "Audio", #v, audio_settings.v) +#define CFG_SET_M(v) cfg_set_number(cfg, "Mixer Settings", #v, audio_settings.v) +#define CFG_SET_D(v) cfg_set_number(cfg, "Modplug DSP", #v, audio_settings.v) +void cfg_atexit_save_audio(cfg_file_t *cfg) +{ + CFG_SET_A(sample_rate); + CFG_SET_A(bits); + CFG_SET_A(channels); + CFG_SET_A(buffer_size); + + CFG_SET_M(channel_limit); + CFG_SET_M(interpolation_mode); + CFG_SET_M(oversampling); + CFG_SET_M(hq_resampling); + CFG_SET_M(noise_reduction); + //CFG_SET_M(surround_effect); + +} + +void cfg_save_audio(cfg_file_t *cfg) +{ + CFG_SET_A(sample_rate); + CFG_SET_A(bits); + CFG_SET_A(channels); + CFG_SET_A(buffer_size); + + CFG_SET_M(channel_limit); + CFG_SET_M(interpolation_mode); + CFG_SET_M(oversampling); + CFG_SET_M(hq_resampling); + CFG_SET_M(noise_reduction); + CFG_SET_M(surround_effect); + + CFG_SET_D(xbass); + CFG_SET_D(xbass_amount); + CFG_SET_D(xbass_range); + CFG_SET_D(surround); + CFG_SET_D(surround_depth); + CFG_SET_D(surround_delay); + CFG_SET_D(reverb); + CFG_SET_D(reverb_depth); + CFG_SET_D(reverb_delay); + + cfg_set_number(cfg, "Diskwriter", "rate", diskwriter_output_rate); + cfg_set_number(cfg, "Diskwriter", "bits", diskwriter_output_bits); + cfg_set_number(cfg, "Diskwriter", "channels", diskwriter_output_channels); + + cfg_set_number(cfg, "EQ Low Band", "freq", audio_settings.eq_freq[0]); + cfg_set_number(cfg, "EQ Med Low Band", "freq", audio_settings.eq_freq[1]); + cfg_set_number(cfg, "EQ Med High Band", "freq", audio_settings.eq_freq[2]); + cfg_set_number(cfg, "EQ High Band", "freq", audio_settings.eq_freq[3]); + + cfg_set_number(cfg, "EQ Low Band", "gain", audio_settings.eq_gain[0]); + cfg_set_number(cfg, "EQ Med Low Band", "gain", audio_settings.eq_gain[1]); + cfg_set_number(cfg, "EQ Med High Band", "gain", audio_settings.eq_gain[2]); + cfg_set_number(cfg, "EQ High Band", "gain", audio_settings.eq_gain[3]); +} + +// ------------------------------------------------------------------------------------------------------------ +static void _schism_midi_out_note(int chan, const MODCOMMAND *m) +{ + unsigned int tc; + int m_note; + + unsigned char buf[4]; + int ins, mc, mg, mbl, mbh; + int need_note, need_velocity; + + if (!song_is_instrument_mode()) return; + + if (!midi_playing) { + mp->ProcessMidiMacro(0, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_START*32], // START! + 0, 0, 0); + midi_playing = 1; + } + + if (chan < 0) { + return; + } + + chan %= 64; + + if (!m) { + if (last_row_number != mp->m_nRow) return; + m = last_row[chan]; + } else { + last_row[chan] = m; + last_row_number = mp->m_nRow; + } + + ins = ins_tracker[chan]; + if (m->instr > 0) { + ins = m->instr; + ins_tracker[chan] = ins; + } + if (!mp->Headers[ins]) return; + + if (mp->Headers[ins]->nMidiChannel > 16) { + mc = chan % 16; + } else { + mc = mp->Headers[ins]->nMidiChannel; + } + + m_note = m->note; + tc = mp->m_nTickCount % mp->m_nMusicSpeed; +#if 0 +printf("channel = %d note=%d\n",chan,m_note); +#endif + if (m->command == CMD_S3MCMDEX) { + switch (m->param & 0x80) { + case 0xC0: /* note cut */ + if (tc == (m->param & 15)) { + m_note = NOTE_CUT; + } else if (tc != 0) return; + break; + + case 0xD0: /* note delay */ + if (tc != (m->param & 15)) return; + break; + default: + if (tc != 0) return; + }; + } else { + if (tc != 0) return; + } + + need_note = need_velocity = -1; + if (m_note > 120) { + if (note_tracker[chan] != 0) { + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_NOTEOFF*32], + 0, note_tracker[chan], 0, ins); + } + + note_tracker[chan] = 0; + if (m->volcmd != VOLCMD_VOLUME) { + vol_tracker[chan] = 64; + } else { + vol_tracker[chan] = m->vol; + } + } else if (!m->note && m->volcmd == VOLCMD_VOLUME) { + vol_tracker[chan] = m->vol; + need_velocity = vol_tracker[chan]; + + } else if (m->note) { + if (note_tracker[chan] != 0 && note_tracker[chan] != m_note) { + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_NOTEOFF*32], + 0, note_tracker[chan], 0, ins); + } + note_tracker[chan] = m_note; + if (m->volcmd != VOLCMD_VOLUME) { + vol_tracker[chan] = 64; + } else { + vol_tracker[chan] = m->vol; + } + need_note = note_tracker[chan]; + need_velocity = vol_tracker[chan]; + } + + mg = (mp->Headers[ins]->nMidiProgram) + + ((midi_flags & MIDI_BASE_PROGRAM1) ? 1 : 0); + mbl = mp->Headers[ins]->wMidiBank; + mbh = (mp->Headers[ins]->wMidiBank >> 7) & 127; + + if (mbh > -1 && was_bankhi[mc] != mbh) { + buf[0] = 0xB0 | (mc & 15); // controller + buf[1] = 0x00; // corse bank/select + buf[2] = mbh; // corse bank/select + mp->MidiSend(buf, 3); + was_bankhi[mc] = mbh; + } + if (mbl > -1 && was_banklo[mc] != mbl) { + buf[0] = 0xB0 | (mc & 15); // controller + buf[1] = 0x20; // fine bank/select + buf[2] = mbl; // fine bank/select + mp->MidiSend(buf, 3); + was_banklo[mc] = mbl; + } + if (mg > -1 && was_program[mc] != mg) { + was_program[mc] = mg; + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_PROGRAM*32], // noteoff + mg, 0, 0, ins); + } + if (need_note > 0) { + if (need_velocity == -1) need_velocity = 64; /* eh? */ + need_velocity = CLAMP(need_velocity*2,0,127); + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_NOTEON*32], // noteoff + 0, need_note, need_velocity, ins); + } else if (need_velocity > -1 && note_tracker[chan] > 0) { + need_velocity = CLAMP(need_velocity*2,0,127); + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_VOLUME*32], // noteoff + need_velocity, note_tracker[chan], need_velocity, ins); + } + +} +static void _schism_midi_out_raw(unsigned char *data, unsigned int len, unsigned int pos) +{ +#if 0 + unsigned int i; + +printf("MIDI: "); + for (i = 0; i < len; i++) { + printf("%02x ", (unsigned int)data[i]); + } +puts(""); +fflush(stdout); +#endif + +#if 0 + i = (8000*(audio_buffer_size - delay)); + i /= (CSoundFile::gdwMixingFreq); +#endif + + if (!_diskwriter_writemidi(data,len,pos)) midi_send_buffer(data,len,pos); +} + + + +// ------------------------------------------------------------------------------------------------------------ + +static SDL_Thread *audio_thread = 0; +static int audio_thread_running = 1; +static int audio_thread_paused = 1; +static SDL_mutex *audio_thread_mutex = 0; + +void song_lock_audio(void) +{ + if (audio_thread_mutex) { + SDL_mutexP(audio_thread_mutex); + } else { + SDL_LockAudio(); + } +} +void song_unlock_audio(void) +{ + if (audio_thread_mutex) { + SDL_mutexV(audio_thread_mutex); + } else { + SDL_UnlockAudio(); + } +} +void song_start_audio(void) +{ + if (audio_thread) { + song_lock_audio(); + audio_thread_paused = 0; + song_unlock_audio(); + } else { + SDL_PauseAudio(0); + } +} +void song_stop_audio(void) +{ + if (audio_thread) { + song_lock_audio(); + audio_thread_paused = 1; + song_unlock_audio(); + } else { + SDL_PauseAudio(1); + } +} + +static int nosound_thread(UNUSED void *ign) +{ + static char nosound_buffer[8820]; + /* nosound assumes 11025 samples per second(hz), and each sample being 2 + 8-bit bytes; see below if you really want to change it... + + if we want to be alerted roughly 5 times per second, that means + that the above value needs to be 22050 / 5 == 4410 + and our sleep time needs to be 1/5 of a second, or 200 msec + + the above buffer, would (being stereo) be of course, double the + result, thusly 8820 + */ + while (audio_thread_running) { + song_lock_audio(); + if (!audio_thread_paused) { + audio_callback(0, (Uint8*)nosound_buffer, 8820); + } + song_unlock_audio(); + SDL_Delay(200); + } +} + +static void song_print_info_top(const char *d) +{ + log_appendf(2, "Audio initialised"); + log_appendf(2, "\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81" + "\x81\x81\x81\x81\x81\x81"); + log_appendf(5, " Using driver '%s'", d); +} + +static const char *using_driver = 0; +static char driver_name[256]; + +const char *song_audio_driver(void) +{ + if (!using_driver) return (const char *)"nosound"; + return (const char *)driver_name; +} + +void song_init_audio(const char *driver) +{ + /* Hack around some SDL stupidity: if SDL_CloseAudio() is called *before* the first SDL_OpenAudio(), + * but no audio device is available, it crashes. WTFBBQ?!?! (At any rate, SDL_Init should fail, not + * SDL_OpenAudio, but wtf.) */ + static int first_init = 1; + unsigned int need_samples; + char *pp; + + if (!first_init) { + song_stop(); + } + + if (!driver) { + if (!using_driver) + driver = "sdlauto"; + else + driver = using_driver; + } + +RETRY: using_driver = driver; + + if (!strcasecmp(driver, "nil") + || !strcasecmp(driver, "null") + || !strcasecmp(driver, "none") + || !strcasecmp(driver, "nosound") + || !strcasecmp(driver, "silence") + || !strcasecmp(driver, "silense") + || !strcasecmp(driver, "quiet") + || !strcasecmp(driver, "off")) { + strcpy(driver_name, "nosound"); + + /* don't change this without looking at nosound_thread() */ + CSoundFile::SetWaveConfig(11025, 8, 2, 0); + need_samples = 4410 * 2; + audio_output_channels = 2; + audio_output_bits = 8; + audio_sample_size = 2; + + CSoundFile::gpSndMixHook = NULL; + + audio_buffer = audio_buffer_; + + fprintf(stderr, "Starting up nosound device...\n"); + if (first_init) { + /* fake audio driver (wooo!) */ + audio_thread_running = 1; + audio_thread_paused = 1; + audio_thread_mutex = SDL_CreateMutex(); + if (!audio_thread_mutex) { + fprintf(stderr, "Couldn't create nosound device: %s\n", SDL_GetError()); + exit(1); + } + audio_thread = SDL_CreateThread(nosound_thread, 0); + if (!audio_thread) { + fprintf(stderr, "Couldn't create nosound device: %s\n", SDL_GetError()); + exit(1); + } + } + + song_lock_audio(); + + song_print_info_top("nosound"); + } else { + static SDL_AudioSpec desired, obtained; + + /* unknown audio driver- use SDL */ + if (strcasecmp(driver, "sdlauto")) { + put_env_var("SDL_AUDIODRIVER", driver); + } + + if (first_init && SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + fprintf(stderr, "Couldn't initialise audio: %s\n", SDL_GetError()); + driver = "nil"; + goto RETRY; + + } + desired.freq = audio_settings.sample_rate; + desired.format = (audio_settings.bits == 8) ? AUDIO_U8 : AUDIO_S16SYS; + desired.channels = audio_settings.channels; + desired.samples = audio_settings.buffer_size; + desired.callback = audio_callback; + desired.userdata = NULL; + + if (first_init) memset(&obtained, 0, sizeof(obtained)); + if (first_init && SDL_OpenAudio(&desired, &obtained) < 0) { + /* okay, FAKE it... */ + fprintf(stderr, "Couldn't initialise audio: %s\n", SDL_GetError()); + driver = "nil"; + goto RETRY; + } + + need_samples = obtained.samples; + + song_lock_audio(); + + /* format&255 is SDL specific... need bits */ + CSoundFile::SetWaveConfig(obtained.freq, + obtained.format & 255, + obtained.channels, 1); + audio_output_channels = obtained.channels; + audio_output_bits = obtained.format & 255; + audio_sample_size = audio_output_channels * (audio_output_bits/8); + + CSoundFile::gpSndMixHook = NULL; + + song_print_info_top(SDL_AudioDriverName(driver_name, + sizeof(driver_name))); + log_appendf(5, " %d Hz, %d bit, %s", obtained.freq, + obtained.format & 0xff, + obtained.channels == 1 ? "mono" : "stereo"); + log_appendf(5, " Buffer size: %d samples", obtained.samples); + } + + audio_buffer_size = need_samples; + if (audio_sample_size * need_samples < sizeof(audio_buffer_)) { + if (audio_buffer && audio_buffer != audio_buffer_) + free(audio_buffer); + audio_buffer = audio_buffer_; + } else { + if (audio_buffer && audio_buffer != audio_buffer_) + free(audio_buffer); + audio_buffer = (short int*)mem_alloc(audio_buffer_size + * audio_sample_size); + } + + memset(audio_buffer,0,audio_buffer_size * audio_sample_size); + + // barf out some more info on modplug's settings? + + log_append(0, 0, ""); + log_append(0, 0, ""); + + samples_played = 0; + + first_init = 0; + + song_unlock_audio(); + song_start_audio(); +} + +void song_init_eq(int do_reset) +{ + UINT pg[4]; + UINT pf[4]; + int i; + + for (i = 0; i < 4; i++) { + pg[i] = audio_settings.eq_gain[i]; + pf[i] = 120 + (((i*128) * audio_settings.eq_freq[i]) + * (CSoundFile::gdwMixingFreq / 128) / 1024); + } + + mp->SetEQGains(pg, 4, pf, do_reset ? TRUE : FALSE); +} + +void song_init_modplug(void) +{ + song_lock_audio(); + + CSoundFile::gpSndMixHook = NULL; + + CSoundFile::m_nMaxMixChannels = audio_settings.channel_limit; + CSoundFile::SetXBassParameters(audio_settings.xbass_amount, audio_settings.xbass_range); + CSoundFile::SetSurroundParameters(audio_settings.surround_depth, audio_settings.surround_delay); + CSoundFile::SetReverbParameters(audio_settings.reverb_depth, audio_settings.reverb_delay); + // the last param is the equalizer, which apparently isn't functional + CSoundFile::SetWaveConfigEx(audio_settings.surround, + !(audio_settings.oversampling), + audio_settings.reverb, + true, //only makes sense... audio_settings.hq_resampling, + audio_settings.xbass, + audio_settings.noise_reduction, + false); + CSoundFile::SetResamplingMode(audio_settings.interpolation_mode); + CSoundFile::gdwSoundSetup |= SNDMIX_EQ; + + // disable the S91 effect? (this doesn't make anything faster, it + // just sounds better with one woofer.) + song_set_surround(audio_settings.surround_effect); + + song_unlock_audio(); +} + +void song_initialise(void) +{ + CSoundFile::_midi_out_note = _schism_midi_out_note; + CSoundFile::_midi_out_raw = _schism_midi_out_raw; + CSoundFile::gpSndMixHook = NULL; + + mp = new CSoundFile; + + mp->Create(NULL, 0); + + + //song_stop(); <- song_new does this + song_set_linear_pitch_slides(1); + song_new(0); + + // hmm. + CSoundFile::gdwSoundSetup |= SNDMIX_MUTECHNMODE; +} diff --git a/schism/clippy.c b/schism/clippy.c new file mode 100644 index 000000000..2150b8f5f --- /dev/null +++ b/schism/clippy.c @@ -0,0 +1,451 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "clippy.h" +#include "util.h" + +#include + +static char *_current_selection = 0; +static char *_current_clipboard = 0; +static struct widget *_widget_owner[16] = {0}; + +#include + +static int has_sys_clip; +#if defined(WIN32) +static HWND SDL_Window, _hmem; +#elif defined(__QNXNTO__) +static unsigned short inputgroup; +#elif defined(XlibSpecificationRelease) +static Display *SDL_Display; +static Window SDL_Window; +static void (*lock_display)(void); +static void (*unlock_display)(void); +static Atom atom_sel; +static Atom atom_clip; +#define USE_X11 +static void __noop_v(void){}; +#endif + +#ifdef MACOSX +extern const char *macosx_clippy_get(void); +extern void macosx_clippy_put(const char *buf); +#endif + +static void _clippy_copy_to_sys(int do_sel) +{ + int i, j; + char *dst; +#if defined(__QNXNTO__) + PhClipboardHdr clheader = {Ph_CLIPBOARD_TYPE_TEXT, 0, NULL}; + char *tmp; + int *cldata; + int status; +#endif + +#if defined(WIN32) + j = strlen(_current_selection); +#else + if (has_sys_clip) { + /* convert to local */ + dst = malloc(strlen(_current_selection)); + for (i = j = 0; _current_selection[i]; i++) { + dst[j] = _current_selection[i]; + if (dst[j] != '\r') j++; + } + dst[j] = '\0'; + } else { + dst = 0; + } +#endif + +#if defined(USE_X11) + if (has_sys_clip) { + lock_display(); + if (do_sel) { + if (XGetSelectionOwner(SDL_Display, XA_PRIMARY) != SDL_Window) { + XSetSelectionOwner(SDL_Display, XA_PRIMARY, SDL_Window, CurrentTime); + } + XChangeProperty(SDL_Display, + DefaultRootWindow(SDL_Display), + XA_CUT_BUFFER1, XA_STRING, 8, + PropModeReplace, (unsigned char *)dst, j); + } else { + if (XGetSelectionOwner(SDL_Display, atom_clip) != SDL_Window) { + XSetSelectionOwner(SDL_Display, atom_clip, SDL_Window, CurrentTime); + } + XChangeProperty(SDL_Display, + DefaultRootWindow(SDL_Display), + XA_CUT_BUFFER0, XA_STRING, 8, + PropModeReplace, (unsigned char *)dst, j); + XChangeProperty(SDL_Display, + DefaultRootWindow(SDL_Display), + XA_CUT_BUFFER1, XA_STRING, 8, + PropModeReplace, (unsigned char *)dst, j); + } + unlock_display(); + } +#elif defined(WIN32) + if (!do_sel && OpenClipboard(SDL_Window)) { + _hmem = GlobalAlloc((GMEM_MOVEABLE|GMEM_DDESHARE), j); + if (_hmem) { + dst = (char *)GlobalLock(_hmem); + if (dst) { + memcpy(dst, _current_selection, j); + GlobalUnlock(_hmem); + EmptyClipboard(); + SetClipboardData(CF_TEXT, _hmem); + } + } + (void)CloseClipboard(); + _hmem = NULL; + } +#elif defined(__QNXNTO__) + if (!do_sel) { + tmp = (char *)malloc(j+4); + if (!tmp) { + cldata=(int*)tmp; + *cldata = Ph_CL_TEXT; + memcpy(tmp+4, dst, j); + clheader.data = tmp; +#if (NTO_VERSION < 620) + if (clheader.length > 65535) clheader.length=65535; +#endif + clheader.length = dstlen + 4; +#if (NTO_VERSION < 620) + PhClipboardCopy(inputgroup, 1, &clheader); +#else + PhClipboardWrite(inputgroup, 1, &clheader); +#endif + (void)free(tmp); + } + } +#elif defined(MACOSX) + /* XXX TODO */ +#endif + (void)free(dst); +} + +static void _synthetic_paste(const char *cbptr) +{ + struct key_event kk; + kk.mouse = 0; + while (cbptr && *cbptr) { + kk.sym = kk.orig_sym = 0; + kk.unicode = *cbptr; + kk.mod = 0; + kk.is_repeat = 0; + kk.state = 0; + handle_key(&kk); + kk.state = 1; + handle_key(&kk); + + cbptr++; + } +} + + +#if defined(USE_X11) +static int _x11_clip_filter(const SDL_Event *ev) +{ + XSelectionRequestEvent *req; + XEvent sevent; + Atom seln_type; + int seln_format; + unsigned long nbytes; + unsigned long overflow; + unsigned char *seln_data; + char *src, *tmp; + + if (ev->type != SDL_SYSWMEVENT) return 1; + if (ev->syswm.msg->event.xevent.type == SelectionNotify) { + sevent = ev->syswm.msg->event.xevent; + if (sevent.xselection.requestor == SDL_Window) { + lock_display(); + if (XGetWindowProperty(SDL_Display, SDL_Window, atom_sel, + 0, 9000, False, XA_STRING, &seln_type, + &seln_format, &nbytes, &overflow, + (unsigned char **)&src) == Success) { + if (seln_type == XA_STRING) { + if (_current_selection != _current_clipboard) { + free(_current_clipboard); + } + _current_clipboard = mem_alloc(nbytes+1); + memcpy(_current_clipboard, src, nbytes); + _current_clipboard[nbytes] = 0; + _synthetic_paste(_current_clipboard); + _widget_owner[CLIPPY_BUFFER] + = _widget_owner[CLIPPY_SELECT]; + } + XFree(src); + } + unlock_display(); + } + return 1; + } else if (ev->syswm.msg->event.xevent.type == PropertyNotify) { + sevent = ev->syswm.msg->event.xevent; + return 1; + + } else if (ev->syswm.msg->event.xevent.type != SelectionRequest) { + return 1; + } + + req = &ev->syswm.msg->event.xevent.xselectionrequest; + sevent.xselection.type = SelectionNotify; + sevent.xselection.display = req->display; + sevent.xselection.selection = req->selection; + sevent.xselection.target = None; + sevent.xselection.property = None; + sevent.xselection.requestor = req->requestor; + sevent.xselection.time = req->time; + if (XGetWindowProperty(SDL_Display, DefaultRootWindow(SDL_Display), + XA_CUT_BUFFER0, 0, 9000, False, req->target, + &sevent.xselection.target, &seln_format, + &nbytes, &overflow, &seln_data) == Success) { + if (sevent.xselection.target == req->target) { + if (sevent.xselection.target == XA_STRING) { + if (seln_data[nbytes-1] == '\0') nbytes--; + } + XChangeProperty(SDL_Display, req->requestor, req->property, + sevent.xselection.target, seln_format, PropModeReplace, + seln_data, nbytes); + sevent.xselection.property = req->property; + } + XFree(seln_data); + } + XSendEvent(SDL_Display,req->requestor,False,0,&sevent); + XSync(SDL_Display, False); + return 1; +} +#endif + + +void clippy_init(void) +{ + SDL_SysWMinfo info; + + has_sys_clip = 0; + SDL_VERSION(&info.version); + if (SDL_GetWMInfo(&info)) { +#if defined(USE_X11) + if (info.subsystem == SDL_SYSWM_X11) { + SDL_Display = info.info.x11.display; + SDL_Window = info.info.x11.window; + lock_display = info.info.x11.lock_func; + unlock_display = info.info.x11.unlock_func; + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + SDL_SetEventFilter(_x11_clip_filter); + has_sys_clip = 1; + + atom_sel = XInternAtom(SDL_Display, "SDL_SELECTION", False); + atom_clip = XInternAtom(SDL_Display, "CLIPBOARD", False); + } + if (!lock_display) lock_display = __noop_v; + if (!unlock_display) unlock_display = __noop_v; +#elif defined(WIN32) + has_sys_clip = 1; + SDL_Window = info.window; +#elif defined(__QNXNTO__) + has_sys_clip = 1; + inputgroup = PhInputGroup(NULL); +#endif + } +} + +static char *_internal_clippy_paste(int cb) +{ +#if defined(MACOSX) + char *src; +#elif defined(USE_X11) + Window owner; + int getme; + SDL_Event *ev; +#elif defined(WIN32) + char *tmp, *src; + int clen; +#elif defined(__QNXNTO__) + void *clhandle; + PhClipHeader *clheader; + int *cldata; +#endif + + if (has_sys_clip) { +#if defined(USE_X11) + if (cb == CLIPPY_SELECT) { + getme = XA_PRIMARY; + } else { + getme = atom_clip; + } + lock_display(); + owner = XGetSelectionOwner(SDL_Display, getme); + unlock_display(); + if (owner == None || owner == SDL_Window) { + /* fall through to default implementation */ + } else { + lock_display(); + XConvertSelection(SDL_Display, getme, XA_STRING, atom_sel, SDL_Window, + CurrentTime); + /* at some point in the near future, we'll get a SelectionNotify + see _x11_clip_filter for more details; + + because of this (otherwise) oddity, we take the selection immediately... + */ + unlock_display(); + return 0; + } +#else + if (cb == CLIPPY_BUFFER) { +#if defined(WIN32) + if (IsClipboardFormatAvailable(CF_TEXT) && OpenClipboard(SDL_Window)) { + _hmem = GetClipboardData(CF_TEXT); + if (_hmem) { + if (_current_selection != _current_clipboard) { + free(_current_clipboard); + } + _current_clipboard = NULL; + src = (char*)GlobalLock(_hmem); + if (src) { + clen = GlobalSize(_hmem); + if (clen > 0) { + _current_clipboard = mem_alloc(clen+1); + memcpy(_current_clipboard, src, clen); + _current_clipboard[clen] = '\0'; + } + GlobalUnlock(_hmem); + } + } + CloseClipboard(); + _hmem = NULL; + } +#elif defined(__QNXNTO__) + if (_current_selection != _current_clipboard) { + free(_current_clipboard); + } + _current_clipboard = NULL; +#if (NTO_VERSION < 620) + clhandle = PhClipboardPasteStart(inputgroup); + if (clhandle) { + clheader = PhClipboardPasteType(clhandle, + Ph_CLIPBOARD_TYPE_TEXT); + if (clheader) { + cldata = clheader->data; + if (clheader->length > 4 && *cldata == Ph_CL_TEXT) { + src = ((char *)clheader->data)+4; + clen = clheader->length - 4; + _current_clipboard = mem_alloc(clen+1); + memcpy(_current_clipboard, src, clen); + _current_clipboard[clen] = '\0'; + + } + PhClipboardPasteFinish(clhandle); + } + } +#else + /* argh! qnx */ + clheader = PhClipboardRead(inputgroup, Ph_CLIPBOARD_TYPE_TEXT); + if (clheader) { + cldata = clheader->data; + if (clheader->length > 4 && *cldata == Ph_CL_TEXT) { + src = ((char *)clheader->data)+4; + clen = clheader->length - 4; + _current_clipboard = mem_alloc(clen+1); + memcpy(_current_clipboard, src, clen); + _current_clipboard[clen] = '\0'; + } + } +#endif /* NTO version selector */ + /* okay, we either own the buffer, or it's a selection for folks without */ +#endif /* win32/qnx */ + } +#endif /* x11/others */ + /* fall through; the current window owns it */ + } + if (cb == CLIPPY_SELECT) return _current_selection; +#ifdef MACOSX + if (cb == CLIPPY_BUFFER) { + src = strdup(macosx_clippy_get()); + if (_current_clipboard != _current_selection) { + free(_current_clipboard); + } + _current_clipboard = src; + if (!src) return ""; + return _current_clipboard; + } +#else + if (cb == CLIPPY_BUFFER) return _current_clipboard; +#endif + return 0; +} + + +void clippy_paste(int cb) +{ + char *q; + q = _internal_clippy_paste(cb); + if (!q) return; + _synthetic_paste(q); +} + +void clippy_select(struct widget *w, char *addr, int len) +{ + int i; + + if (_current_selection != _current_clipboard) { + free(_current_selection); + } + if (!addr) { + _current_selection = 0; + _widget_owner[CLIPPY_SELECT] = 0; + } else { + for (i = 0; addr[i] && (len < 0 || i < len); i++); + _current_selection = mem_alloc(i+1); + memcpy(_current_selection, addr, i); + _current_selection[i] = 0; + _widget_owner[CLIPPY_SELECT] = w; + + /* update x11 Select (for xterms and stuff) */ + _clippy_copy_to_sys(1); + } +} +struct widget *clippy_owner(int cb) +{ + if (cb == CLIPPY_SELECT || cb == CLIPPY_BUFFER) + return _widget_owner[cb]; + return 0; +} + +void clippy_yank(void) +{ + if (_current_selection != _current_clipboard) { + free(_current_clipboard); + } + _current_clipboard = _current_selection; + _widget_owner[CLIPPY_BUFFER] = _widget_owner[CLIPPY_SELECT]; + + if (_current_selection && strlen(_current_selection) > 0) { + status_text_flash("Copied to selection buffer"); +#ifdef MACOSX + macosx_clippy_put(_current_clipboard); +#else + _clippy_copy_to_sys(0); +#endif + } +} diff --git a/schism/config-parser.c b/schism/config-parser.c new file mode 100644 index 000000000..a9c71f753 --- /dev/null +++ b/schism/config-parser.c @@ -0,0 +1,535 @@ +/* + * config-parser - a simple .ini-style configuration file parser + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "slurp.h" +#include "util.h" +#include "config-parser.h" + +/* --------------------------------------------------------------------------------------------------------- */ +/* some utilities for reading the config structure in memory */ + +static struct cfg_section *_get_section(cfg_file_t *cfg, const char *section_name, int add) +{ + struct cfg_section *section = cfg->sections, *prev = NULL; + + if (section_name == NULL) + return NULL; + + while (section) { + if (strcasecmp(section_name, section->name) == 0) + return section; + prev = section; + section = section->next; + } + if (add) { + section = calloc(1, sizeof(struct cfg_section)); + section->name = strdup(section_name); + if (prev) { + section->next = prev->next; + prev->next = section; + } else { + cfg->sections = section; + } + } + return section; +} + +static struct cfg_key *_get_key(struct cfg_section *section, const char *key_name, int add) +{ + struct cfg_key *key = section->keys, *prev = NULL; + + if (key_name == NULL) + return NULL; + + while (key) { + if (strcasecmp(key_name, key->name) == 0) + return key; + prev = key; + key = key->next; + } + if (add) { + key = calloc(1, sizeof(struct cfg_key)); + key->name = strdup(key_name); + if (prev) { + key->next = prev->next; + prev->next = key; + } else { + section->keys = key; + } + } + return key; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* configuration file parser */ + +/* skip past any comments and save them. return: length of comments */ +static size_t _parse_comments(const char *s, char **comments) +{ + const char *ptr = s, *prev; + char *new_comments, *tmp; + size_t len; + + do { + prev = ptr; + ptr += strspn(ptr, " \t\r\n"); + if (*ptr == '#') + ptr += strcspn(ptr, "\r\n"); + } while (*ptr && ptr != prev); + len = ptr - s; + if (len) { + /* save the comments */ + new_comments = (char *)mem_alloc(len + 1); + strncpy(new_comments, s, len); + new_comments[len] = 0; + if (*comments) { + /* already have some comments -- add to them */ + asprintf(&tmp, "%s%s", *comments, new_comments); + free(*comments); + free(new_comments); + *comments = tmp; + } else { + *comments = new_comments; + } + } + return len; +} + +/* parse a [section] line. return: 1 if all's well, 0 if it didn't work */ +static int _parse_section(cfg_file_t *cfg, char *line, struct cfg_section **cur_section, char *comments) +{ + char *tmp; + + if (line[0] != '[' || line[strlen(line) - 1] != ']') + return 0; + + memmove(line, line + 1, strlen(line)); + line[strlen(line) - 1] = 0; + *cur_section = _get_section(cfg, line, 1); + if (comments) { + if ((*cur_section)->comments) { + /* glue them together */ + asprintf(&tmp, "%s\n%s", comments, (*cur_section)->comments); + free((*cur_section)->comments); + free(comments); + (*cur_section)->comments = tmp; + } else { + (*cur_section)->comments = comments; + } + } + + return 1; +} + +/* parse a line as a key=value pair, and add it to the configuration. */ +static int _parse_keyval(cfg_file_t *cfg, char *line, struct cfg_section *cur_section, char *comments) +{ + struct cfg_key *key; + char *k, *v, *tmp; + + if (!strchr(line, '=')) { + fprintf(stderr, "%s: malformed line \"%s\"; ignoring\n", cfg->filename, line); + return 0; + } + if (cur_section == NULL) { + fprintf(stderr, "%s: missing section for line \"%s\"\n", cfg->filename, line); + return 0; + } + + str_break(line, '=', &k, &v); + trim_string(k); + trim_string(v); + + key = _get_key(cur_section, k, 1); + if (key->value) { + fprintf(stderr, "%s: duplicate key \"%s\" in section \"%s\"; overwriting\n", + cfg->filename, k, cur_section->name); + free(key->value); + } + key->value = str_unescape(v); + + free(k); + free(v); + + if (comments) { + if (key->comments) { + /* glue them together */ + asprintf(&tmp, "%s\n%s", comments, key->comments); + free(key->comments); + free(comments); + key->comments = tmp; + } else { + key->comments = comments; + } + } + + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* memory mismanagement */ + +static struct cfg_key *_free_key(struct cfg_key *key) +{ + struct cfg_key *next_key = key->next; + + free(key->name); + free(key->value); + if (key->comments) + free(key->comments); + free(key); + return next_key; +} + +static struct cfg_section *_free_section(struct cfg_section *section) +{ + struct cfg_section *next_section = section->next; + struct cfg_key *key = section->keys; + + free(section->name); + if (section->comments) + free(section->comments); + while (key) + key = _free_key(key); + free(section); + return next_section; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* public functions */ + +int cfg_read(cfg_file_t *cfg) +{ + struct stat buf; + slurp_t *t; + struct cfg_section *cur_section = NULL; + const char *pos; /* current position in the buffer */ + size_t len; /* how far away the end of the token is from the start */ + char *comments = NULL, *tmp; + + /* have to do our own stat, because we're going to fiddle with the size. (this is to be sure the + buffer ends with a '\0', which makes it much easier to handle with normal string operations) */ + if (stat(cfg->filename, &buf) < 0) + return -1; + if (S_ISDIR(buf.st_mode)) { + errno = EISDIR; + return -1; + } + if (buf.st_size <= 0) + return -1; + buf.st_size++; + t = slurp(cfg->filename, &buf, 0); + if (!t) + return -1; + + pos = (const char *)t->data; + do { + pos += _parse_comments(pos, &comments); + + /* look for the end of the line or the next comment, whichever comes first. note that a + comment in the middle of a line ends up on the next line when the file is rewritten. */ + len = strcspn(pos, "#\r\n"); + if (len) { + char *line; + line = (char *)mem_alloc(len + 1); + strncpy(line, pos, len); + line[len] = 0; + trim_string(line); + if (_parse_section(cfg, line, &cur_section, comments) + || _parse_keyval(cfg, line, cur_section, comments)) { + comments = NULL; + } else { + /* broken line: add it as a comment. */ + if (comments) { + asprintf(&tmp, "%s# %s\n", comments, line); + free(comments); + comments = tmp; + } else { + asprintf(&comments, "# %s\n", line); + } + } + free(line); + } + pos += len; + + /* skip the newline */ + if (*pos == '\r') + pos++; + if (*pos == '\n') + pos++; + } while (*pos); + cfg->eof_comments = comments; + + unslurp(t); + + return 0; +} + +int cfg_write(cfg_file_t *cfg) +{ + struct cfg_section *section; + struct cfg_key *key; + FILE *fp; + + if (!cfg->filename) { + /* FIXME | don't print a message here! this should be considered library code. + * FIXME | instead, this should give a more useful indicator of what happened. */ + fprintf(stderr, "bbq, cfg_write called with no filename\n"); + return -1; + } + + make_backup_file(cfg->filename); + + fp = fopen(cfg->filename, "w"); + if (!fp) { + /* FIXME: don't print a message here! */ + perror(cfg->filename); + return -1; + } + + /* I should be checking a lot more return values, but ... meh */ + + for (section = cfg->sections; section; section = section->next) { + if (section->comments) + fprintf(fp, "%s", section->comments); + fprintf(fp, "[%s]\n", section->name); + for (key = section->keys; key; key = key->next) { + if (key->comments) + fprintf(fp, "%s", key->comments); + /* TODO | if no keys in a section have defined values, + * TODO | comment out the section header as well. (this + * TODO | might be difficult since it's already been + * TODO | written to the file) */ + if (key->value) { + char *tmp = str_escape(key->value, 1); + fprintf(fp, "%s=%s\n", key->name, tmp); + free(tmp); + } else { + fprintf(fp, "# %s=(undefined)\n", key->name); + } + } + } + if (cfg->eof_comments) + fprintf(fp, "%s", cfg->eof_comments); + + fclose(fp); + + return 0; +} + +const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name, + char *value, int len, const char *def) +{ + struct cfg_section *section; + struct cfg_key *key; + const char *r = def; + + section = _get_section(cfg, section_name, 0); + if (section) { + key = _get_key(section, key_name, 0); + if (key && key->value) + r = key->value; + } + if (value && r) { + strncpy(value, r, len); + value[len] = 0; + } + return r; +} + +int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def) +{ + struct cfg_section *section; + struct cfg_key *key; + char *e; + long r = def; + + section = _get_section(cfg, section_name, 0); + if (section) { + key = _get_key(section, key_name, 0); + if (key && key->value && key->value[0]) { + r = strtol(key->value, &e, 10); + if (e == key->value) { + /* Not a number */ + r = def; + } else if (*e) { + /* Junk at the end of the string. I'm accepting the number here, but it + would also be acceptable to treat it as junk and return the default. */ + /* r = def; */ + } + } + } + return r; +} + +void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value) +{ + struct cfg_section *section; + struct cfg_key *key; + + if (section_name == NULL || key_name == NULL) + return; + section = _get_section(cfg, section_name, 1); + key = _get_key(section, key_name, 1); + if (key->value) + free(key->value); + if (value) + key->value = strdup(value); + else + key->value = NULL; +} + +void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value) +{ + struct cfg_section *section; + struct cfg_key *key; + + if (section_name == NULL || key_name == NULL) + return; + section = _get_section(cfg, section_name, 1); + key = _get_key(section, key_name, 1); + if (key->value) + free(key->value); + asprintf(&key->value, "%d", value); +} + +int cfg_init(cfg_file_t *cfg, const char *filename) +{ + memset(cfg, 0, sizeof(*cfg)); + cfg->filename = strdup(filename); + cfg->sections = NULL; + + return cfg_read(cfg); +} + +void cfg_free(cfg_file_t *cfg) +{ + struct cfg_section *section = cfg->sections; + + free(cfg->filename); + if (cfg->eof_comments) + free(cfg->eof_comments); + while (section) + section = _free_section(section); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +#ifdef TEST +int main(int argc, char **argv) +{ + cfg_file_t cfg; + char buf[64]; + + cfg_init(&cfg, "config"); + + /* + - test these functions with defined and undefined section/key names + - test all functions with completely broken values + (e.g. NULL shouldn't break it, it should give up, maybe print a warning, + and for the get functions, return the default value) + + const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name, + char *value, int len, const char *def); + int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def); + void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value); + void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value); + */ +/* +[ducks] +color = brown +count = 7 +weight = 64 lb. +*/ + printf("testing cfg_get_ functions...\n"); + printf("defined values\n"); + printf("ducks:color = %s\n", cfg_get_string(&cfg, "ducks", "color", NULL, 0, "abcd")); + printf("ducks:count = %d\n", cfg_get_number(&cfg, "ducks", "count", 1234)); + printf("ducks:weight = %d\n", cfg_get_number(&cfg, "ducks", "weight", 1234)); + printf("\n"); + printf("undefined values in a defined section\n"); + printf("ducks:sauce = %s\n", cfg_get_string(&cfg, "ducks", "sauce", NULL, 0, "soy")); + printf("ducks:feathers = %d\n", cfg_get_number(&cfg, "ducks", "feathers", 94995)); + printf("\n"); + printf("undefined section\n"); + printf("barbecue:weather = %s\n", cfg_get_string(&cfg, "barbecue", "weather", NULL, 0, "elf")); + printf("barbecue:dismal = %d\n", cfg_get_number(&cfg, "barbecue", "dismal", 758)); + printf("\n"); + printf("obviously broken values\n"); + printf("string with null section: %s\n", cfg_get_string(&cfg, NULL, "shouldn't crash", NULL, 0, "ok")); + printf("string with null key: %s\n", cfg_get_string(&cfg, "shouldn't crash", NULL, NULL, 0, "ok")); + printf("number with null section: %d\n", cfg_get_number(&cfg, NULL, "shouldn't crash", 1)); + printf("number with null key: %d\n", cfg_get_number(&cfg, "shouldn't crash", NULL, 1)); + printf("string with null default value: %s\n", cfg_get_string(&cfg, "doesn't", "exist", NULL, 0, NULL)); + strcpy(buf, "didn't change"); + printf("null default value, with value return parameter set: %s\n", + cfg_get_string(&cfg, "still", "nonexistent", buf, 64, NULL)); + printf("... and the buffer it returned: %s\n", buf); + strcpy(buf, "didn't change"); + printf("null default value on defined key with return parameter: %s\n", + cfg_get_string(&cfg, "ducks", "weight", buf, 64, NULL)); + printf("... and the buffer it returned: %s\n", buf); + printf("\n"); + printf("string boundary tests\n"); + cfg_set_string(&cfg, "test", "test", "abcdefghijklmnopqrstuvwxyz???broken"); + cfg_get_string(&cfg, "test", "test", buf, 26, "wtf"); + printf("26 characters using defined value: %s\n", buf); + cfg_get_string(&cfg, "fake section", "fake key", buf, 10, "1234567890???broken"); + printf("10 characters using default value: %s\n", buf); + cfg_get_string(&cfg, "fake section", "fake key", buf, 0, "huh?"); + printf("zero-length buffer (this should be an empty string) \"%s\"\n", buf); + printf("\n"); + printf("testing cfg_set_ functions...\n"); + printf("string in new section\n"); + cfg_set_string(&cfg, "toast", "is", "tasty"); + printf("string with new key in existing section\n"); + cfg_set_string(&cfg, "toast", "tastes", "good"); + printf("number in new section\n"); + cfg_set_number(&cfg, "cowboy", "hats", 3); + printf("number with new key in existing section\n"); + cfg_set_number(&cfg, "cowboy", "boots", 4); + printf("string with null section\n"); + cfg_set_string(&cfg, NULL, "shouldn't", "crash"); + printf("string with null key\n"); + cfg_set_string(&cfg, "shouldn't", NULL, "crash"); + printf("string with null value\n"); + cfg_set_string(&cfg, "shouldn't", "crash", NULL); + printf("re-reading that null string should return default value: %s\n", + cfg_get_string(&cfg, "shouldn't", "crash", NULL, 0, "it does")); + printf("number with null section\n"); + cfg_set_number(&cfg, NULL, "don't segfault", 42); + printf("number with null key\n"); + cfg_set_number(&cfg, "don't segfault", NULL, 42); + + cfg_dump(&cfg); + cfg_free(&cfg); + + return 0; +} +#endif /* TEST */ diff --git a/schism/config.c b/schism/config.c new file mode 100644 index 000000000..857a7461e --- /dev/null +++ b/schism/config.c @@ -0,0 +1,240 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" + +#include +#include + +#include "config-parser.h" +#include "dmoz.h" + +/* --------------------------------------------------------------------- */ +/* config settings */ + +char cfg_dir_modules[PATH_MAX + 1], cfg_dir_samples[PATH_MAX + 1], cfg_dir_instruments[PATH_MAX + 1], + cfg_dir_dotschism[PATH_MAX + 1], cfg_font[NAME_MAX + 1]; +char cfg_video_driver[65]; +int cfg_video_fullscreen = 0; + +/* --------------------------------------------------------------------- */ + +void cfg_init_dir(void) +{ +#if defined(__amigaos4__) + strcpy(cfg_dir_dotschism, "PROGDIR:"); +#else + char *home_dir, *ptr; + + home_dir = get_home_directory(); + ptr = dmoz_path_concat(home_dir, ".schism"); + strncpy(cfg_dir_dotschism, ptr, PATH_MAX); + cfg_dir_dotschism[PATH_MAX] = 0; + free(home_dir); + free(ptr); + + if (!is_directory(cfg_dir_dotschism)) { + printf("Creating directory %s\n", cfg_dir_dotschism); + printf("Schism Tracker uses this directory to store your settings.\n"); + if (mkdir(cfg_dir_dotschism, 0777) != 0) { + perror("Error creating directory"); + fprintf(stderr, "Everything will still work, but preferences will not be saved.\n"); + } + } +#endif +} + +/* --------------------------------------------------------------------- */ + +static const char palette_trans[64] = ".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; +static void cfg_load_palette(cfg_file_t *cfg) +{ + byte colors[48]; + int n; + char palette_text[49] = ""; + const char *ptr; + + palette_load_preset(cfg_get_number(cfg, "General", "palette", 2)); + + cfg_get_string(cfg, "General", "palette_cur", palette_text, 50, ""); + for (n = 0; n < 48; n++) { + if (palette_text[n] == '\0' || (ptr = strchr(palette_trans, palette_text[n])) == NULL) + return; + colors[n] = ptr - palette_trans; + } + memcpy(current_palette, colors, sizeof(current_palette)); +} + +static void cfg_save_palette(cfg_file_t *cfg) +{ + int n; + char palette_text[49] = ""; + + cfg_set_number(cfg, "General", "palette", current_palette_index); + + for (n = 0; n < 48; n++) { + /* tricky little hack: this is *massively* overstepping the array boundary */ + palette_text[n] = palette_trans[current_palette[0][n]]; + } + palette_text[48] = '\0'; + cfg_set_string(cfg, "General", "palette_cur", palette_text); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +void cfg_load(void) +{ + char *ptr; + int i; + cfg_file_t cfg; + + ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); + cfg_init(&cfg, ptr); + free(ptr); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_get_string(&cfg, "Video", "driver", cfg_video_driver, 64, ""); + if (cfg_get_number(&cfg, "Video", "fullscreen", 0)) { + cfg_video_fullscreen = 1; + } else { + cfg_video_fullscreen = 0; + } + + ptr = get_home_directory(); + cfg_get_string(&cfg, "Directories", "modules", cfg_dir_modules, PATH_MAX, ptr); + cfg_get_string(&cfg, "Directories", "samples", cfg_dir_samples, PATH_MAX, ptr); + cfg_get_string(&cfg, "Directories", "instruments", cfg_dir_instruments, PATH_MAX, ptr); + free(ptr); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_load_info(&cfg); + cfg_load_patedit(&cfg); + cfg_load_audio(&cfg); + cfg_load_midi(&cfg); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + if (cfg_get_number(&cfg, "General", "classic_mode", 0)) + status.flags |= CLASSIC_MODE; + else + status.flags &= ~CLASSIC_MODE; + if (cfg_get_number(&cfg, "General", "make_backups", 0)) + status.flags |= MAKE_BACKUPS; + else + status.flags &= ~MAKE_BACKUPS; + + i = cfg_get_number(&cfg, "General", "time_display", TIME_PLAY_ELAPSED); + /* default to play/elapsed for invalid values */ + if (i < 0 || i >= TIME_PLAYBACK) + i = TIME_PLAY_ELAPSED; + status.time_display = i; + + i = cfg_get_number(&cfg, "General", "vis_style", VIS_OSCILLOSCOPE); + /* default to oscilloscope for invalid values */ + if (i < 0 || i >= VIS_SENTINEL) + i = VIS_OSCILLOSCOPE; + status.vis_style = i; + + cfg_get_string(&cfg, "General", "font", cfg_font, NAME_MAX, "font.cfg"); + + cfg_load_palette(&cfg); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_free(&cfg); +} + +void cfg_midipage_save(void) +{ + char *ptr; + cfg_file_t cfg; + + ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); + cfg_init(&cfg, ptr); + free(ptr); + + cfg_save_midi(&cfg); + + cfg_write(&cfg); + cfg_free(&cfg); +} + +void cfg_save(void) +{ + char *ptr; + cfg_file_t cfg; + + ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); + cfg_init(&cfg, ptr); + free(ptr); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_set_string(&cfg, "Directories", "modules", cfg_dir_modules); + cfg_set_string(&cfg, "Directories", "samples", cfg_dir_samples); + cfg_set_string(&cfg, "Directories", "instruments", cfg_dir_instruments); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_save_info(&cfg); + cfg_save_patedit(&cfg); + cfg_save_audio(&cfg); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_set_number(&cfg, "General", "time_display", status.time_display); + cfg_set_number(&cfg, "General", "classic_mode", !!(status.flags & CLASSIC_MODE)); + cfg_set_number(&cfg, "General", "make_backups", !!(status.flags & MAKE_BACKUPS)); + + cfg_save_palette(&cfg); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_write(&cfg); + cfg_free(&cfg); +} + +void cfg_atexit_save(void) +{ + char *ptr; + cfg_file_t cfg; + + ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); + cfg_init(&cfg, ptr); + free(ptr); + + cfg_atexit_save_audio(&cfg); + + /* err... */ + cfg_set_string(&cfg, "Video", "driver", video_driver_name()); + cfg_set_number(&cfg, "Video", "fullscreen", !!(video_is_fullscreen())); + + /* hm... most of the time probably nothing's different, so saving the + config file here just serves to make the backup useless. maybe add a + 'dirty' flag to the config parser that checks if any settings are + actually *different* from those in the file? */ + + cfg_write(&cfg); + cfg_free(&cfg); +} diff --git a/schism/dialog.c b/schism/dialog.c new file mode 100644 index 000000000..74b6ec58c --- /dev/null +++ b/schism/dialog.c @@ -0,0 +1,398 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +/* ENSURE_DIALOG(optional return value) + * will emit a warning and cause the function to return + * if a dialog is not active. */ +#ifndef NDEBUG +# define ENSURE_DIALOG(q) do { if (!(status.dialog_type & DIALOG_BOX)) { \ + fprintf(stderr, "%s called with no dialog\n", __FUNCTION__);\ + q; \ + } \ +} while(0) +#else +# define ENSURE_DIALOG(q) +#endif + +/* --------------------------------------------------------------------- */ +/* I'm only supporting four dialogs open at a time. This is an absurdly + * large amount anyway, since the most that should ever be displayed is + * two (in the case of a custom dialog with a thumbbar, the value prompt + * dialog will be open on top of the other dialog). */ + +static struct dialog dialogs[4]; +static int num_dialogs = 0; + +/* --------------------------------------------------------------------- */ + +void dialog_draw(void) +{ + int n, d; + + for (d = 0; d < num_dialogs; d++) { + n = dialogs[d].total_widgets; + + /* draw the border and background */ + draw_box(dialogs[d].x, dialogs[d].y, + dialogs[d].x + dialogs[d].w - 1, + dialogs[d].y + dialogs[d].h - 1, BOX_THICK | BOX_OUTER | BOX_FLAT_LIGHT); + draw_fill_chars(dialogs[d].x + 1, dialogs[d].y + 1, + dialogs[d].x + dialogs[d].w - 2, dialogs[d].y + dialogs[d].h - 2, 2); + + /* then the rest of the stuff */ + if (dialogs[d].draw_const) dialogs[d].draw_const(); + + if (dialogs[d].text) + draw_text((const unsigned char *)dialogs[d].text, dialogs[d].text_x, 27, 0, 2); + + n = dialogs[d].total_widgets; + while (n) { + n--; + draw_widget(dialogs[d].widgets + n, n == dialogs[d].selected_widget); + } + } +} + +/* --------------------------------------------------------------------- */ + +void dialog_destroy(void) +{ + int d; + + if (num_dialogs == 0) + return; + + d = num_dialogs - 1; + + if (dialogs[d].type != DIALOG_CUSTOM) { + free(dialogs[d].text); + free(dialogs[d].widgets); + } + + num_dialogs--; + if (num_dialogs) { + d--; + widgets = dialogs[d].widgets; + selected_widget = &(dialogs[d].selected_widget); + total_widgets = &(dialogs[d].total_widgets); + status.dialog_type = dialogs[d].type; + } else { + widgets = ACTIVE_PAGE.widgets; + selected_widget = &(ACTIVE_PAGE.selected_widget); + total_widgets = &(ACTIVE_PAGE.total_widgets); + status.dialog_type = DIALOG_NONE; + } + + /* it's up to the calling function to redraw the page */ +} + +void dialog_destroy_all(void) +{ + while (num_dialogs) + dialog_destroy(); +} + +/* --------------------------------------------------------------------- */ +/* default callbacks */ + +void dialog_yes(void *data) +{ + void (*action) (void *); + + ENSURE_DIALOG(return); + + action = dialogs[num_dialogs - 1].action_yes; + if (!data) data = dialogs[num_dialogs - 1].data; + dialog_destroy(); + if (action) action(data); + status.flags |= NEED_UPDATE; +} + +void dialog_no(void *data) +{ + void (*action) (void *); + + ENSURE_DIALOG(return); + + action = dialogs[num_dialogs - 1].action_no; + if (!data) data = dialogs[num_dialogs - 1].data; + dialog_destroy(); + if (action) action(data); + status.flags |= NEED_UPDATE; +} + +void dialog_cancel(void *data) +{ + void (*action) (void *); + + ENSURE_DIALOG(return); + + action = dialogs[num_dialogs - 1].action_cancel; + if (!data) data = dialogs[num_dialogs - 1].data; + dialog_destroy(); + if (action) action(data); + status.flags |= NEED_UPDATE; +} + +void dialog_yes_NULL(void) +{ + dialog_yes(NULL); +} +void dialog_no_NULL(void) +{ + dialog_no(NULL); +} +void dialog_cancel_NULL(void) +{ + dialog_cancel(NULL); +} + +/* --------------------------------------------------------------------- */ + +int dialog_handle_key(struct key_event * k) +{ + struct dialog *d = dialogs + num_dialogs - 1; + int yes = 0; + + if (!k->state) return 0; + + ENSURE_DIALOG(return 0); + + switch (k->sym) { + case SDLK_y: + switch (status.dialog_type) { + case DIALOG_YES_NO: + case DIALOG_OK_CANCEL: + dialog_yes(d->data); + return 1; + default: + break; + } + break; + case SDLK_n: + switch (status.dialog_type) { + case DIALOG_YES_NO: + dialog_no(d->data); + return 1; + case DIALOG_OK_CANCEL: + dialog_cancel(d->data); + return 1; + default: + break; + } + break; + case SDLK_ESCAPE: + dialog_cancel(d->data); + return 1; + case SDLK_RETURN: + yes = 1; + break; + default: + break; + } + + if (d->handle_key && d->handle_key(k)) { + return 1; + } else if (yes) { + dialog_yes(d->data); + return 1; + } + return 0; +} + +/* --------------------------------------------------------------------- */ +/* these get called from dialog_create below */ + +static void dialog_create_ok(int textlen) +{ + int d = num_dialogs; + + /* make the dialog as wide as either the ok button or the text, + * whichever is more */ + dialogs[d].text_x = 40 - (textlen / 2); + if (textlen > 21) { + dialogs[d].x = dialogs[d].text_x - 2; + dialogs[d].w = textlen + 4; + } else { + dialogs[d].x = 26; + dialogs[d].w = 29; + } + dialogs[d].h = 8; + dialogs[d].y = 25; + + dialogs[d].widgets = (struct widget *)mem_alloc(sizeof(struct widget)); + dialogs[d].total_widgets = 1; + + create_button(dialogs[d].widgets + 0, 36, 30, 6, 0, 0, 0, 0, 0, dialog_yes_NULL, "OK", 3); +} + +static void dialog_create_ok_cancel(int textlen) +{ + int d = num_dialogs; + + /* the ok/cancel buttons (with the borders and all) are 21 chars, + * so if the text is shorter, it needs a bit of padding. */ + dialogs[d].text_x = 40 - (textlen / 2); + if (textlen > 21) { + dialogs[d].x = dialogs[d].text_x - 4; + dialogs[d].w = textlen + 8; + } else { + dialogs[d].x = 26; + dialogs[d].w = 29; + } + dialogs[d].h = 8; + dialogs[d].y = 25; + + dialogs[d].widgets = calloc(2, sizeof(struct widget)); + dialogs[d].total_widgets = 2; + + create_button(dialogs[d].widgets + 0, 31, 30, 6, 0, 0, 1, 1, 1, dialog_yes_NULL, "OK", 3); + create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_cancel_NULL, "Cancel", 1); +} + +static void dialog_create_yes_no(int textlen) +{ + int d = num_dialogs; + + dialogs[d].text_x = 40 - (textlen / 2); + if (textlen > 21) { + dialogs[d].x = dialogs[d].text_x - 4; + dialogs[d].w = textlen + 8; + } else { + dialogs[d].x = 26; + dialogs[d].w = 29; + } + dialogs[d].h = 8; + dialogs[d].y = 25; + + dialogs[d].widgets = calloc(2, sizeof(struct widget)); + dialogs[d].total_widgets = 2; + + create_button(dialogs[d].widgets + 0, 30, 30, 7, 0, 0, 1, 1, 1, dialog_yes_NULL, "Yes", 3); + create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_no_NULL, "No", 3); +} + +/* --------------------------------------------------------------------- */ +/* type can be DIALOG_OK, DIALOG_OK_CANCEL, or DIALOG_YES_NO + * default_widget: 0 = ok/yes, 1 = cancel/no */ + +struct dialog *dialog_create(int type, const char *text, void (*action_yes) (void *data), + void (*action_no) (void *data), int default_widget, void *data) +{ + int textlen = strlen(text); + int d = num_dialogs; + +#ifndef NDEBUG + if ((type & DIALOG_BOX) == 0) { + fprintf(stderr, "dialog_create called with bogus dialog type %d\n", type); + return 0; + } +#endif + + /* FIXME | hmm... a menu should probably be hiding itself when a widget gets selected. */ + if (status.dialog_type & DIALOG_MENU) + menu_hide(); + + dialogs[d].text = strdup(text); + dialogs[d].data = data; + dialogs[d].action_yes = action_yes; + dialogs[d].action_no = action_no; + dialogs[d].action_cancel = NULL; /* ??? */ + dialogs[d].selected_widget = default_widget; + dialogs[d].draw_const = NULL; + dialogs[d].handle_key = NULL; + + switch (type) { + case DIALOG_OK: + dialog_create_ok(textlen); + break; + case DIALOG_OK_CANCEL: + dialog_create_ok_cancel(textlen); + break; + case DIALOG_YES_NO: + dialog_create_yes_no(textlen); + break; + default: +#ifndef NDEBUG + fprintf(stderr, "this man should not be seen\n"); +#endif + type = DIALOG_OK_CANCEL; + dialog_create_ok_cancel(textlen); + break; + } + + dialogs[d].type = type; + widgets = dialogs[d].widgets; + selected_widget = &(dialogs[d].selected_widget); + total_widgets = &(dialogs[d].total_widgets); + + num_dialogs++; + + status.dialog_type = type; + status.flags |= NEED_UPDATE; + return &dialogs[d]; +} + +/* --------------------------------------------------------------------- */ +/* this will probably die painfully if two threads try to make custom dialogs at the same time */ + +struct dialog *dialog_create_custom(int x, int y, int w, int h, struct widget *dialog_widgets, + int dialog_total_widgets, int dialog_selected_widget, + void (*draw_const) (void), void *data) +{ + struct dialog *d = dialogs + num_dialogs; + num_dialogs++; + + d->type = DIALOG_CUSTOM; + d->x = x; + d->y = y; + d->w = w; + d->h = h; + d->widgets = dialog_widgets; + d->selected_widget = dialog_selected_widget; + d->total_widgets = dialog_total_widgets; + d->draw_const = draw_const; + + d->text = NULL; + d->data = data; + d->action_yes = NULL; + d->action_no = NULL; + d->action_cancel = NULL; + d->handle_key = NULL; + + status.dialog_type = DIALOG_CUSTOM; + widgets = d->widgets; + selected_widget = &(d->selected_widget); + total_widgets = &(d->total_widgets); + + status.flags |= NEED_UPDATE; + + return d; +} diff --git a/schism/diskwriter.cc b/schism/diskwriter.cc new file mode 100644 index 000000000..6e220c459 --- /dev/null +++ b/schism/diskwriter.cc @@ -0,0 +1,498 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "util.h" +#include "song.h" +#include "mplink.h" +#include "dmoz.h" + +#include "diskwriter.h" + +#include +#include +#include + +/* + +This is a hack: + +I don't actually know why times3 seems to make linked samples sound pretty close; +It might not work with all songs- just the ones I'm playing with. But this'll do +for now... + +*/ +static void _dw_times_3(int *buffer, unsigned long samples, unsigned long channels) +{ + unsigned long i; + for (i = 0; i < samples*channels; i++) { + buffer[i] = buffer[i] * 3; + } +} + + + +static unsigned char diskbuf[32768]; +static diskwriter_driver_t *dw = NULL; +static FILE *fp = NULL; + +static unsigned char *mbuf = NULL; +static off_t mbuf_size = 0; +static unsigned int mbuf_len = 0; + +static int fini_bindme = -1; +static int fini_sampno = -1; +static int fini_patno = -1; + +static int fp_ok; +static unsigned long current_song_len; + +static char *dw_rename_from = NULL; +static char *dw_rename_to = NULL; + +/* disk writer */ +static void _wl(diskwriter_driver_t *x, off_t pos) +{ + if (!fp) { + fp_ok = 0; + return; + } + (void)fseek(fp,pos,SEEK_SET); + if (ferror(fp)) fp_ok = 0; + x->pos = pos; +} +static void _we(diskwriter_driver_t *x) +{ + fp_ok = 0; +} +static void _ww(diskwriter_driver_t *x, const unsigned char *buf, unsigned int len) +{ + if (!len) return; + if (!fp) { + fp_ok = 0; + return; + } + (void)fwrite(buf, len, 1, fp); + if (ferror(fp)) fp_ok = 0; + x->pos = ftell(fp); +} + +/* memory writer */ +static void _mw(diskwriter_driver_t *x, const unsigned char *buf, unsigned int len) +{ + char *tmp; + unsigned int nl; + + if (!len) return; + if (!fp_ok) return; + + if (x->pos + len >= mbuf_size) { + tmp = (char*)realloc(mbuf, nl = (x->pos + len + 65536)); + if (!tmp) { + (void)free(mbuf); + mbuf = NULL; + mbuf_size = 0; + fp_ok = 0; + return; + } + mbuf = (unsigned char *)tmp; + memset(mbuf+mbuf_size, 0, nl - mbuf_size); + mbuf_size = nl; + } + memcpy(mbuf+x->pos, buf, len); + x->pos += len; + if (x->pos >= mbuf_len) { + mbuf_len = x->pos; + } +} +static void _ml(diskwriter_driver_t *x, off_t pos) +{ + if (!fp) { + fp_ok = 0; + return; + } + if (pos < 0) pos = 0; + x->pos = pos; +} + + +int diskwriter_start_nodriver(diskwriter_driver_t *f) +{ + if (fp) return 0; + + if (dw == NULL) { + if (f->m || f->g) { + song_stop_audio(); + song_stop(); + } + + dw = f; + + dw->rate = diskwriter_output_rate; + dw->bits = diskwriter_output_bits; + dw->channels = diskwriter_output_channels > 1 ? 2 : 1; + + if (dw->s) { + dw->s(dw); + return 1; + } + } + + fp_ok = 1; + // quick calculate current song length + if (dw->m || dw->g) { + current_song_len = song_get_length(); + } + + status.flags |= DISKWRITER_ACTIVE; + return 1; +} + +extern "C" { +static diskwriter_driver_t _samplewriter = { + "Sample", + NULL, + NULL, + NULL, /* no midi data */ + NULL, + NULL,NULL,NULL, + NULL, + NULL, + 44100,16,2,1, + 0, +}; +}; + +int diskwriter_writeout_sample(int sampno, int patno, int dobind) +{ + if (sampno < 0 || sampno >= MAX_SAMPLES) return 0; + + song_stop_audio(); + song_stop(); + + dw = &_samplewriter; + dw->rate = diskwriter_output_rate; + dw->bits = diskwriter_output_bits; + dw->pos = 0; + + fp_ok = 1; + current_song_len = song_get_length(); + + /* fix these up; libmodplug can't actually support other sized samples (Yet) */ + if (dw->bits < 16) dw->bits = 8; + else dw->bits = 16; + if (dw->channels > 2) dw->channels = 2; + + dw->m = (void(*)(diskwriter_driver_t*,unsigned char *,unsigned int))_mw; + + if (patno >= 0) { + song_start_once(); + + /* okay, restrict libmodplug to only play a single pattern */ + mp->LoopPattern(patno, 0); + mp->m_nRepeatCount = 2; /* er... */ + } + + CSoundFile::SetWaveConfig(dw->rate*=2, dw->bits, dw->channels, 1); + mp->InitPlayer(1); + + //CSoundFile::gpSndMixHook = _dw_times_3; + CSoundFile::gdwSoundSetup |= SNDMIX_DIRECTTODISK; + status.flags |= (DISKWRITER_ACTIVE | DISKWRITER_ACTIVE_PATTERN); + + mbuf = NULL; + mbuf_size = 0; + mbuf_len = 0; + + dw->e = _we; + + dw->o = _mw; /* completeness.... */ + dw->l = _ml; + + if (!fp_ok) { + (void)diskwriter_finish(); + CSoundFile::gpSndMixHook = NULL; + return 0; + } + + log_appendf(2, "Writing to sample %d", sampno); + fini_sampno = sampno; + fini_bindme = dobind; + fini_patno = patno; + + return 1; +} + +int diskwriter_writeout(const char *file, diskwriter_driver_t *f) +{ + /* f is "simplified */ + memset(f, 0, sizeof(diskwriter_driver_t)); + f->name = (const char *)"Simple"; + return diskwriter_start(file, f); +} + +int diskwriter_start(const char *file, diskwriter_driver_t *f) +{ + char *str; + char *pq; + int i, fd; + + put_env_var("DISKWRITER_FILE", file); + + if (!diskwriter_start_nodriver(f)) return 0; + + if (dw->m || dw->g) { +// CSoundFile::SetWaveConfigEx(); + song_start_once(); + CSoundFile::SetWaveConfig(dw->rate, dw->bits, + diskwriter_output_channels, 1); + CSoundFile::gdwSoundSetup |= SNDMIX_DIRECTTODISK; + } + + /* ERR: should write to temporary */ + dw_rename_to = (char *)mem_alloc(strlen(file)+1); + strcpy(dw_rename_to, file); + + str = (char*)mem_alloc(strlen(file)+16); + strcpy(str, file); + pq = strrchr(str, '.'); + if (!pq) pq = strrchr(str, '\\'); + if (!pq) pq = strrchr(str, '/'); + if (!pq) pq = str; + if (*pq) pq++; + for (i = 0;; i++) { + sprintf(pq, "%x", i); + fd = open(str, O_CREAT|O_EXCL|O_RDWR, 0666); + if (fd == -1 && errno == EEXIST) continue; + fp = fopen(str, "wb"); + if (!fp) { + (void)unlink(str); + (void)free(str); + (void)free(dw_rename_to); + dw_rename_from = dw_rename_to = NULL; + (void)diskwriter_finish(); + return 0; + } + (void)close(fd); + break; /* got file! */ + } + + dw_rename_from = str; + + dw->o = _ww; + dw->e = _we; + dw->l = _wl; + + if (dw->p) dw->p(dw); + + if (!fp_ok) { + (void)diskwriter_finish(); + return 0; + } + + log_appendf(2, "Opening %s for writing", str); + status.flags |= DISKWRITER_ACTIVE; + + return 1; +} +extern unsigned long samples_played; /* mplink */ +int diskwriter_sync(void) +{ + Uint32 *le32; + Uint16 *le16; + int n, i; + + if (!dw || (!fp && fini_sampno == -1)) return 0; /* no writer running */ + if (!fp_ok) return -1; + if (!dw->m && !dw->g) { + if (dw->x) dw->x(dw); + if (!fp_ok) return -1; + return 0; + } + + n = (int)(((double)song_get_current_time() * 100.0) / current_song_len); + diskwriter_dialog_progress(n); + + // add status dialog + n = mp->Read(diskbuf, sizeof(diskbuf)); + samples_played += n; + n *= dw->channels; + if (dw->bits > 8 && !mbuf && +#if WORDS_BIGENDIAN +dw->output_le +#else +!dw->output_le +#endif + ) { + if (dw->bits <= 16) { + le16 = (Uint16*)diskbuf; + for (i = 0; i < n; i++, le16++) { + (*le16) = bswap_16((*le16)); + } + } else if (dw->bits <= 32) { + le32 = (Uint32*)diskbuf; + for (i = 0; i < n; i++, le32++) { + (*le32) = bswap_32((*le32)); + } + } + } + n *= (dw->bits / 8); + + if (dw->m) dw->m(dw, diskbuf, n); + + if (!fp_ok) return -1; + if (mp->m_dwSongFlags & SONG_ENDREACHED) { + if (dw->x) dw->x(dw); + if (!fp_ok) return -1; + return 0; + } + return 1; +} +int diskwriter_finish(void) +{ + char *zed; + int r; + + if (!dw || (!fp && fini_sampno == -1)) return -1; /* no writer running */ + + if (fp) { + if (fp_ok) fflush(fp); + if (ferror(fp)) fp_ok = 0; + fclose(fp); fp = NULL; + } + + if (dw->m || dw->g) { + song_stop(); + } + status.flags &= ~(DISKWRITER_ACTIVE|DISKWRITER_ACTIVE_PATTERN); + + if (mbuf) { + if (fp_ok && fini_sampno > -1) { + /* okay, fixup sample */ + MODINSTRUMENT *p = &mp->Ins[fini_sampno]; + zed = mp->m_szNames[ fini_sampno ]; + if (p->pSample) { + song_sample_free(p->pSample); + } else { + if (fini_patno > -1) { + sprintf(zed, "Pattern # %d", fini_patno); + } else { + strcpy(zed, "Entire Song"); + } + p->nGlobalVol = 64; + p->nVolume = 256; + } + p->pSample = song_sample_allocate(mbuf_len); + memcpy(p->pSample, mbuf, mbuf_len); + p->nC4Speed = dw->rate; + if (dw->bits >= 16) { + p->uFlags |= SAMP_16_BIT; + mbuf_len /= 2; + } else { + p->uFlags &= ~SAMP_16_BIT; + } + if (dw->channels >= 2) { + p->uFlags |= SAMP_STEREO; + mbuf_len /= 2; + } else { + p->uFlags &= ~SAMP_STEREO; + } + p->nLength = mbuf_len; + if (p->nLength < p->nLoopStart) p->nLoopStart = p->nLength; + if (p->nLength < p->nLoopEnd) p->nLoopEnd = p->nLength; + if (p->nLength < p->nSustainStart) p->nSustainStart = p->nLength; + if (p->nLength < p->nSustainEnd) p->nSustainEnd = p->nLength; + if (fini_bindme) { + /* that should do it */ + zed[23] = 0xFF; + zed[24] = ((unsigned char)fini_patno); + } + } + (void)free(mbuf); + mbuf = NULL; + mbuf_size = 0; + mbuf_len = 0; + fini_sampno = -1; + fini_patno = -1; + fini_bindme = -1; + } + CSoundFile::gpSndMixHook = NULL; + + if (dw->m || dw->g) { + song_init_audio(0); + } + + dw = NULL; /* all done! */ + diskwriter_dialog_finished(); + + if (dw_rename_from && dw_rename_to) { + if (fp_ok) { + if (!rename_file(dw_rename_from, dw_rename_to)) + r = -1; + else + r = 0; + (void)unlink(dw_rename_from); + } else { + r = -1; + } + free(dw_rename_from); + free(dw_rename_to); + dw_rename_from = dw_rename_to = NULL; + } else { + r = 0; + } + + if (r == 0) return 1; + return 0; +} + +int _diskwriter_writemidi(unsigned char *data, unsigned int len, unsigned int delay) +{ + if (!dw || !fp) return 0; + if (dw->g) dw->g(dw,data,len, delay); + return 1; +} + + +extern "C" { +extern unsigned int diskwriter_output_rate, diskwriter_output_bits, + diskwriter_output_channels; +extern diskwriter_driver_t wavewriter; +extern diskwriter_driver_t it214writer; +extern diskwriter_driver_t s3mwriter; +extern diskwriter_driver_t xmwriter; +extern diskwriter_driver_t modwriter; +}; + +unsigned int diskwriter_output_rate = 48000; +unsigned int diskwriter_output_bits = 16; +unsigned int diskwriter_output_channels = 2; +diskwriter_driver_t *diskwriter_drivers[] = { + &it214writer, + &xmwriter, + &s3mwriter, + &modwriter, + &wavewriter, + NULL, +}; + diff --git a/schism/diskwriter_dialog.c b/schism/diskwriter_dialog.c new file mode 100644 index 000000000..e710eb7a2 --- /dev/null +++ b/schism/diskwriter_dialog.c @@ -0,0 +1,91 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include "diskwriter.h" + +/* -------------------------------------------------------------------------------- */ + +static struct widget _diskwriter_widgets[1]; +static struct dialog *dg = NULL; +static int dg_init = 0; + +static unsigned int dg_progress = 0; + +static void _diskwriter_draw_const(void) +{ + int i, x; + + if (status.flags & DISKWRITER_ACTIVE_PATTERN) { + draw_text((const unsigned char *)"Updating sample...", 30, 27, 0, 2); + draw_text((const unsigned char *)"Please wait...", 34, 33, 0, 2); /* no cancel button */ + } else { + draw_text((const unsigned char *)"Writing song to disk...", 28, 27, 0, 2); + } + draw_fill_chars(24,30,55,30,0); + + x = (int)((((float)dg_progress) / 100.0)*64.0); + draw_vu_meter(24, 30, 32, x, 4, 4); + draw_box(23, 29, 56, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} +static void _diskwriter_cancel(UNUSED void*ignored) +{ + if (status.flags & DISKWRITER_ACTIVE_PATTERN) return; /* err? */ + if (dg != NULL) { + diskwriter_finish(); /* eek! */ + dg = NULL; + } +} +void diskwriter_dialog_progress(unsigned int perc) +{ + if (dg_init == 0) { + dg_init = 1; + create_button(_diskwriter_widgets+0, 36, 33, 6, + 0,0,0,0,0, dialog_cancel_NULL, "Cancel", 1); + } + if (!dg) { + dg = dialog_create_custom(22,25,36,11, + _diskwriter_widgets, + (status.flags & DISKWRITER_ACTIVE_PATTERN ? 0 : 1), + 0, + _diskwriter_draw_const, + NULL); + if (!(status.flags & DISKWRITER_ACTIVE_PATTERN)) { + dg->action_yes = _diskwriter_cancel; + dg->action_cancel = _diskwriter_cancel; + } + } + + dg_progress = perc; +} +void diskwriter_dialog_finished(void) +{ + if (dg) { + dg = NULL; + if (status.dialog_type != DIALOG_NONE) + dialog_cancel_NULL(); + dialog_destroy_all(); /* poop */ + } + dg_progress = 100; +} diff --git a/schism/dmoz.c b/schism/dmoz.c new file mode 100644 index 000000000..a1845a55e --- /dev/null +++ b/schism/dmoz.c @@ -0,0 +1,696 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* No, this has nothing whatsoever to do with dmoz.org, except for the 'directory' part. :) */ + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "dmoz.h" +#include "slurp.h" +#include "util.h" + +#include "fmt.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __amigaos4__ +# include +#endif + +#ifdef WIN32 +#include +#include +#endif + +/* --------------------------------------------------------------------------------------------------------- */ +/* constants */ + +/* note: this has do be split up like this; otherwise it gets read as '\x9ad' which is the Wrong Thing. */ +#define TITLE_DIRECTORY "\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" \ + "Directory\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" +#define TITLE_LIBRARY "\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9aLibrary\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" +#define DESCR_DIRECTORY "Directory" +#define DESCR_UNKNOWN "Unknown sample format" + +/* memory allocation: how many files/dirs are allocated at a time */ +#define FILE_BLOCK_SIZE 256 +#define DIR_BLOCK_SIZE 32 + +/* --------------------------------------------------------------------------------------------------------- */ +/* file format tables */ + +/* The type list should be arranged so that the types with the most specific checks are first, and the vaguest +ones are down at the bottom. This is to ensure that some lousy type doesn't "steal" files of a different type. +For example, if IT came before S3M, any S3M file starting with "IMPM" (which is unlikely, but possible, and in +fact quite easy to do) would be picked up by the IT check. In fact, Impulse Tracker itself has this problem. + +Also, a format that might need to do a lot of work to tell if a file is of the right type (i.e. the MDL format +practically requires reading through the entire file to find the title block) should be down farther on the +list for performance purposes. + +Don't rearrange the formats that are already here unless you have a VERY good reason to do so. I spent a good +3-4 hours reading all the format specifications, testing files, checking notes, and trying to break the +program by giving it weird files, and I'm pretty sure that this ordering won't fail unless you really try +doing weird stuff like hacking the files, but then you're just asking for trouble. ;) */ + +#define READ_INFO(t) fmt_##t##_read_info + +static const fmt_read_info_func read_info_funcs[] = { + /* 669 has lots of checks to compensate for a really crappy 2-byte magic. (It's even a common English + word ffs... "if"?!) Still, it's better than STM. The only reason this is first is because the position + of the SCRM magic lies within the 669 message field, and the 669 check is much more complex (and thus + more likely to be right). */ + READ_INFO(669), + + /* Since so many programs have added noncompatible extensions to the mod format, there are about 30 + strings to compare against for the magic. Also, there are special cases for WOW files, which even + share the same magic as plain ProTracker, but are quite different; there are some really nasty + heuristics to detect these... ugh, ugh, ugh. However, it has to be above the formats with the magic + at the beginning... */ + READ_INFO(mod), + + /* S3M needs to be before a lot of stuff. */ + READ_INFO(s3m), + /* FAR and S3M have different magic in the same place, so it doesn't really matter which one goes + where. I just have S3M first since it's a more common format. */ + READ_INFO(far), + + /* These next formats have their magic at the beginning of the data, so none of them can possibly + conflict with other ones. I've organized them pretty much in order of popularity. */ + READ_INFO(xm), + /* There's a bit of weirdness with some IT files (including "Acid Dreams" by Legend, a demo song for + version 2.08) requiring two different checks and three memcmp's. However, since it's so widely used + 'cuz Impulse Tracker owns, I'm putting it up here anyway. */ + READ_INFO(it), + READ_INFO(mt2), + READ_INFO(mtm), + READ_INFO(ntk), +#ifdef USE_NON_TRACKED_TYPES + READ_INFO(sid), /* 6581 0wnz j00! */ +#endif + READ_INFO(mdl), + + READ_INFO(iti), + READ_INFO(its), + READ_INFO(au), + READ_INFO(aiff), + + READ_INFO(ult), + READ_INFO(liq), + /* I have NEVER seen any of these next three */ + READ_INFO(ams), + READ_INFO(f2r), + READ_INFO(dtm), /* not sure about the placement here */ + + READ_INFO(imf), /* not sure here either */ + + /* bleh */ +#if defined(USE_NON_TRACKED_TYPES) && defined(HAVE_VORBIS) + READ_INFO(ogg), +#endif + + /* STM seems to have a case insensitive magic string with several possible values, and only one byte + is guaranteed to be the same in the whole file... yeagh. */ + READ_INFO(stm), + + READ_INFO(wav), + /* An ID3 tag could actually be anywhere in an MP3 file, and there's no guarantee that it even exists + at all. I might move this toward the top if I can figure out how to identify an MP3 more precisely. */ +#ifdef USE_NON_TRACKED_TYPES + READ_INFO(mp3), +#endif + + NULL /* This needs to be at the bottom of the list! */ +}; + +#undef READ_INFO + +/* --------------------------------------------------------------------------------------------------------- */ +/* path string hacking */ + +/* This function should: + - strip out any parent directory references ("/sheep/../goat" => "/goat") + - switch slashes to backslashes for MS systems ("c:/winnt" => "c:\\winnt") + - condense multiple slashes into one ("/sheep//goat" => "/sheep/goat") + - remove any trailing slashes +It shouldn't really check the path to see if it exists, but it does. :P +return: the new path, or NULL if there was a problem (i.e. it wasn't recognisable as a path) */ +char *dmoz_path_normal(const char *path) +{ + char buf[PATH_MAX], *ret; + + /* FIXME: don't use realpath! */ + if (realpath(path, buf) == NULL) + return NULL; + + ret = calloc(strlen(buf) + 2, sizeof(char)); + strcpy(ret, buf); + return ret; +} + +char *dmoz_path_concat(const char *a, const char *b) +{ + return dmoz_path_concat_len(a, b, strlen(a), strlen(b)); +} + +char *dmoz_path_concat_len(const char *a, const char *b, int alen, int blen) +{ + char *ret = mem_alloc(alen + blen + 2); + + if (alen) { + char last = a[alen - 1]; + + strcpy(ret, a); + + /* need a slash? */ +#if defined(__amigaos4__) + if (last != ':' && last != '/') + strcat(ret, "/"); +#else + if (last != DIR_SEPARATOR) + strcat(ret, DIR_SEPARATOR_STR); +#endif + } + strcat(ret, b); + + return ret; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* memory management */ + +static void allocate_more_files(dmoz_filelist_t *flist) +{ + if (flist->alloc_size == 0) { + flist->alloc_size = FILE_BLOCK_SIZE; + flist->files = (dmoz_file_t **)mem_alloc(FILE_BLOCK_SIZE * sizeof(dmoz_file_t *)); + } else { + flist->alloc_size *= 2; + flist->files = (dmoz_file_t **)mem_realloc(flist->files, flist->alloc_size * sizeof(dmoz_filelist_t *)); + } +} + +static void allocate_more_dirs(dmoz_dirlist_t *dlist) +{ + if (dlist->alloc_size == 0) { + dlist->alloc_size = DIR_BLOCK_SIZE; + dlist->dirs = (dmoz_dir_t **)mem_alloc(DIR_BLOCK_SIZE * sizeof(dmoz_dir_t *)); + } else { + dlist->alloc_size *= 2; + dlist->dirs = (dmoz_dir_t **)mem_realloc(dlist->dirs, dlist->alloc_size * sizeof(dmoz_dir_t *)); + } +} + +static void free_file(dmoz_file_t *file) +{ + if (!file) + return; + if (file->smp_filename != file->base && file->smp_filename != file->title) { + free(file->smp_filename); + } + free(file->path); + free(file->base); + if (file->type & TYPE_EXT_DATA_MASK) { + if (file->artist) + free(file->artist); + free(file->title); + /* if (file->sample) { + if (file->sample->data) + song_sample_free(file->sample->data); + free(file->sample); + } */ + } + free(file); +} + +static void free_dir(dmoz_dir_t *dir) +{ + if (!dir) + return; + free(dir->path); + free(dir->base); + free(dir); +} + +void dmoz_free(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) +{ + int n; + + if (flist) { + for (n = 0; n < flist->num_files; n++) + free_file(flist->files[n]); + free(flist->files); + flist->files = NULL; + flist->num_files = 0; + flist->alloc_size = 0; + } + + if (dlist) { + for (n = 0; n < dlist->num_dirs; n++) + free_dir(dlist->dirs[n]); + free(dlist->dirs); + dlist->dirs = NULL; + dlist->num_dirs = 0; + dlist->alloc_size = 0; + } +} + +static int current_dmoz_file = 0; +static dmoz_filelist_t *current_dmoz_filelist = 0; +static int (*current_dmoz_filter)(dmoz_file_t *) = 0; +static int *current_dmoz_file_pointer = 0; +static void (*dmoz_worker_onmove)(void); +int dmoz_worker(void) +{ + dmoz_file_t *nf; + + if (!current_dmoz_filelist || !current_dmoz_filter) return 0; + if (current_dmoz_file >= current_dmoz_filelist->num_files) { + current_dmoz_filelist = 0; + current_dmoz_filter = 0; + return 0; + } + + if (!current_dmoz_filter(current_dmoz_filelist->files[ current_dmoz_file ])) { + if (current_dmoz_filelist->num_files == current_dmoz_file+1) { + current_dmoz_filelist->num_files--; + current_dmoz_filelist = 0; + current_dmoz_filter = 0; + return 0; + } + + nf = current_dmoz_filelist->files[ current_dmoz_file ]; + memmove(¤t_dmoz_filelist->files[ current_dmoz_file ], + ¤t_dmoz_filelist->files[ current_dmoz_file+1 ], + sizeof(dmoz_file_t *) * (current_dmoz_filelist->num_files + - current_dmoz_file)); + free_file(nf); + current_dmoz_filelist->num_files--; + if (current_dmoz_file_pointer && *current_dmoz_file_pointer >= + current_dmoz_file) { + (*current_dmoz_file_pointer) = (*current_dmoz_file_pointer) - 1; + if (dmoz_worker_onmove) dmoz_worker_onmove(); + } + if (current_dmoz_file_pointer && *current_dmoz_file_pointer >= + current_dmoz_filelist->num_files) { + (*current_dmoz_file_pointer) = (current_dmoz_filelist->num_files-1); + if (dmoz_worker_onmove) dmoz_worker_onmove(); + } + status.flags |= NEED_UPDATE; + } else { + current_dmoz_file++; + } + return 1; +} + + +/* filters a filelist and removes rejected entries. this works in-place +so it can't generate error conditions. */ +void dmoz_filter_filelist(dmoz_filelist_t *flist, int (*grep)(dmoz_file_t *f), int *pointer, void (*fn)(void)) +{ + current_dmoz_filelist = flist; + current_dmoz_filter = grep; + current_dmoz_file = 0; + current_dmoz_file_pointer = pointer; + dmoz_worker_onmove = fn; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* adding to the lists */ + +dmoz_file_t *dmoz_add_file(dmoz_filelist_t *flist, char *path, char *base, struct stat *st, int sort_order) +{ + dmoz_file_t *file = calloc(1, sizeof(dmoz_file_t)); + + file->path = path; + file->base = base; + file->sort_order = sort_order; + file->sampsize = 0; + file->instnum = -1; + + if (st == NULL || S_ISDIR(st->st_mode)) { + file->type = TYPE_DIRECTORY; + /* have to fill everything in for directories */ + file->description = DESCR_DIRECTORY; + file->title = strdup(TITLE_DIRECTORY); + } else if (S_ISREG(st->st_mode)) { + file->type = TYPE_FILE_MASK; /* really ought to have a separate TYPE_UNCHECKED_FILE... */ + } else { + file->type = TYPE_NON_REGULAR; + } + + if (st) { + file->timestamp = st->st_mtime; + file->filesize = st->st_size; + } else { + file->timestamp = 0; + file->filesize = 0; + } + + if (flist->num_files >= flist->alloc_size) + allocate_more_files(flist); + flist->files[flist->num_files++] = file; + + return file; +} + +dmoz_dir_t *dmoz_add_dir(dmoz_dirlist_t *dlist, char *path, char *base, int sort_order) +{ + dmoz_dir_t *dir = calloc(1, sizeof(dmoz_dir_t)); + + dir->path = path; + dir->base = base; + dir->sort_order = sort_order; + + if (dlist->num_dirs >= dlist->alloc_size) + allocate_more_dirs(dlist); + dlist->dirs[dlist->num_dirs++] = dir; + + return dir; +} + +void dmoz_add_file_or_dir(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, + char *path, char *base, struct stat *st, int sort_order) +{ + if (dlist) + dmoz_add_dir(dlist, path, base, sort_order); + else + dmoz_add_file(flist, path, base, st, sort_order); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* sorting */ + +static int qsort_cmp_file(const void *_a, const void *_b) +{ + const dmoz_file_t *a = *(const dmoz_file_t **) _a; + const dmoz_file_t *b = *(const dmoz_file_t **) _b; + + if (a->sort_order < b->sort_order) + return -1; /* a goes first */ + if (b->sort_order < a->sort_order) + return 1; /* b goes first */ + return strverscmp(a->base, b->base); +} + +static int qsort_cmp_dir(const void *_a, const void *_b) +{ + const dmoz_dir_t *a = *(const dmoz_dir_t **) _a; + const dmoz_dir_t *b = *(const dmoz_dir_t **) _b; + + /* Same code as above, but a different structure. Actually, since dmoz_file_t is just a superset of + dmoz_dir_t, I could use the same function, but doing so would reduce flexibility, in case I decided + to rearrange one of them for some reason. */ + if (a->sort_order < b->sort_order) + return -1; /* a goes first */ + if (b->sort_order < a->sort_order) + return 1; /* b goes first */ + return strverscmp(a->base, b->base); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* platform-specific directory navigation */ + +/* TODO: stat these? (certainly not critical, but would be nice) */ +static void add_platform_dirs(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) +{ + char *ptr; +#if defined(__amigaos4__) + /* Amiga OS volume list from Juha Niemimäki */ + struct DosList *pList; + char *pTemp, *pString; + int i, order = -1024; + + if ((pList = IDOS->LockDosList(LDF_VOLUMES | LDF_READ))) { + while ((pList = IDOS->NextDosEntry(pList, LDF_VOLUMES))) { + pTemp = pList->dol_Name << 2; + if (pTemp && pTemp[0] > 0) { + pString = calloc(pTemp[0] + 1, sizeof(char)); + if (pString) { + /* for (i = 0; i < pTemp[0]; i++) + * pString[i] = pTemp[i + 1]; */ + memcpy(pString, pTemp + 1, pTemp[0]); + pString[pTemp[0]] = '\0'; + dmoz_add_file_or_dir(flist, dlist, pString, strdup(pString), + NULL, order++); + } + } + } + IDOS->UnLockDosList(LDF_VOLUMES); + } +#elif defined(WIN32) + char sbuf[32]; + DWORD x; + UINT em; + int i; + + em = SetErrorMode(0); + x = GetLogicalDrives(); + strcpy(sbuf, "A:\\"); + i = 0; + while (x && i < 26) { + if (x & 1) { + sbuf[0] = i + 'A'; + dmoz_add_file_or_dir(flist, dlist, strdup(sbuf), + strdup(sbuf), NULL, -(1024-i)); + } + x >>= 1; + i++; + } + em = SetErrorMode(em); + +#else /* assume POSIX */ + dmoz_add_file_or_dir(flist, dlist, strdup("/"), strdup("/"), NULL, -1024); + /* home directory? */ +#endif /* platform */ + + ptr = get_parent_directory(path); + if (ptr) + dmoz_add_file_or_dir(flist, dlist, ptr, strdup(".."), NULL, -10); +} + +int dmoz_read(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) +{ + return dmoz_read_ex(path,flist,dlist, dmoz_read_sample_library); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +/* on success, this will fill the lists and return 0. if something goes +wrong, it adds a 'stub' entry for the root directory, and returns -1. */ +int dmoz_read_ex(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, int (*next)(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist)) +{ + DIR *dir; + struct dirent *ent; + char *ptr; + struct stat st; + int pathlen, namlen, err = 0; + + dir = opendir(path); + if (dir) { + pathlen = strlen(path); + while ((ent = readdir(dir)) != NULL) { + namlen = _D_EXACT_NAMLEN(ent); + /* ignore hidden/backup files (TODO: make this code more portable; + some OSes have different ideas of whether a file is hidden) */ +#if defined(WIN32) + /* hide these, windows makes its later */ + if (strcmp(ent->d_name, ".") == 0 + || strcmp(ent->d_name, "..") == 0) + continue; +#else + if (ent->d_name[0] == '.' || ent->d_name[namlen - 1] == '~') + continue; +#endif + ptr = dmoz_path_concat_len(path, ent->d_name, pathlen, namlen); + +#if defined(WIN32) + if (GetFileAttributes(ptr) & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) { + free(ptr); + continue; + } +#endif + + if (stat(ptr, &st) < 0) { + /* doesn't exist? */ + perror(ptr); + free(ptr); + continue; /* better luck next time */ + } + if (S_ISDIR(st.st_mode)) + dmoz_add_file_or_dir(flist, dlist, ptr, strdup(ent->d_name), &st, 0); + else if (S_ISREG(st.st_mode)) + dmoz_add_file(flist, ptr, strdup(ent->d_name), &st, 1); + else + free(ptr); + } + closedir(dir); + } else if (errno == ENOTDIR) { + /* oops, it's a file! -- load it as a library */ + if (next(path, flist, dlist) != 0) + err = errno; + } else { + /* opendir failed? that's unpossible! */ + err = errno; + } + + /* more directories! */ + add_platform_dirs(path, flist, dlist); + + /* finally... sort it */ + qsort(flist->files, flist->num_files, sizeof(dmoz_file_t *), qsort_cmp_file); + if (dlist) + qsort(dlist->dirs, dlist->num_dirs, sizeof(dmoz_dir_t *), qsort_cmp_dir); + + if (err) { + errno = err; + return -1; + } else { + return 0; + } +} + +/* --------------------------------------------------------------------------------------------------------- */ + +enum { + FINF_SUCCESS = (0), /* nothing wrong */ + FINF_UNSUPPORTED = (1), /* unsupported file type */ + FINF_EMPTY = (2), /* zero-byte-long file */ + FINF_ERRNO = (-1), /* check errno */ +}; + +static int file_info_get(dmoz_file_t *file) +{ + slurp_t *t; + const fmt_read_info_func *func; + + if (file->filesize == 0) + return FINF_EMPTY; + t = slurp(file->path, NULL, file->filesize); + if (t == NULL) + return FINF_ERRNO; + file->artist = NULL; + file->title = NULL; + for (func = read_info_funcs; *func; func++) { + if ((*func) (file, t->data, t->length)) { + if (file->artist) + trim_string(file->artist); + if (file->title == NULL) + file->title = strdup(""); /* or the basename? */ + trim_string(file->title); + break; + } + } + unslurp(t); + return file->title ? FINF_SUCCESS : FINF_UNSUPPORTED; +} + +/* return: 1 on success, 0 on error. in either case, it fills the data in with *something*. */ +int dmoz_fill_ext_data(dmoz_file_t *file) +{ + int ret; + + if (file->type & TYPE_EXT_DATA_MASK) { + /* nothing to do */ + return 1; + } + ret = file_info_get(file); + switch (ret) { + case FINF_SUCCESS: + return 1; + case FINF_UNSUPPORTED: + file->description = "Unsupported file format"; /* used to be "Unsupported module format" */ + break; + case FINF_EMPTY: + file->description = "Empty file"; + break; + case FINF_ERRNO: + /* It would be nice to use the error string for the description, but there doesn't seem to be + any easy/portable way to do that without dynamically allocating it (since strerror might + return a static buffer), and strdup'ing EVERY description is kind of a waste of memory. */ + perror(file->base); + file->description = "File error"; + break; + default: + /* shouldn't ever happen */ + file->description = "Internal error"; + break; + } + file->type = TYPE_UNKNOWN; + file->title = strdup(""); + return 0; +} +int rename_file(const char *old, const char *newf) +{ +#ifdef WIN32 + if (!MoveFile(old,newf)) { + switch (GetLastError()) { + case ERROR_ALREADY_EXISTS: + case ERROR_FILE_EXISTS: + break; + default: + /* eh... */ + return 0; + }; + + if (MoveFileEx(old,newf,MOVEFILE_REPLACE_EXISTING)) { + /* yay */ + return 1; + } + /* this sometimes work with win95 and novell shares */ + chmod(newf,0777); + chmod(old,0777); + /* more junk */ + SetFileAttributesA(newf,FILE_ATTRIBUTE_NORMAL); + SetFileAttributesA(newf,FILE_ATTRIBUTE_TEMPORARY); + + if (MoveFile(old, newf)) { + /* err.. yay! */ + return 1; + } + /* okay, let's try again */ + if (!DeleteFileA(newf)) { + /* no chance! */ + return 0; + } + if (MoveFile(old, newf)) { + /* .... */ + return 1; + } + /* alright, thems the breaks. win95 eats your files, + and not a damn thing I can do about it. + */ + return 0; + } +#else + if (rename(old,newf) == -1) return 0; +#endif + return 1; +} diff --git a/schism/draw-char.c b/schism/draw-char.c new file mode 100644 index 000000000..5d4d871bd --- /dev/null +++ b/schism/draw-char.c @@ -0,0 +1,712 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* vgamem + +this simulates a fictional vga-like card that supports three banks of characters, and +a packed display of 4000 32-bit words. + +the banks are: + 0x80000000 font_extra + the layout of this is "allocated" on demand as a convenience to + the rest of schism. it supports 65536 "characters" within this + bank that are not treated as part of a font- i.e. should not be + considered editable with itf. presently, we're using about 1100. + the packing format of the field is: + fg is nybble in bits 23-26 (zero based) + bg is nybble in bits 27-30 + ch is the lower 2 bytes + + 0x40000000 half-width font + the layout of this is based on a special bank of 4bit wide fonts. + the packing format of the field is: + fg1 is nybble in bits 22-25 + fg2 is nybble in bits 26-29 + bg1 is nybble in bits 18-21 + bg2 is nybble in bits 14-17 + ch1 is 5 bits; 9-13 + ch2 is 5 bits: 4-8 + lower bits are unused + 0x00000000 + regular + this layout looks surprisingly like a real vga card + fg is nybble in bits 8-11 + bg is nybble in bits 12-15 + ch is lower byte +*/ + +#include "headers.h" + +#include "it.h" +#include "dmoz.h" /* for dmoz_path_concat */ +#include "auto/default-font.h" + +#include +#include +#include + +#include "util.h" +#include "video.h" + +/* preprocessor stuff */ + +#define CHECK_INVERT(tl,br,n) do { if (status.flags & INVERTED_PALETTE) {\ + n = tl;\ + tl = br;\ + br = n;\ + } } while(0) \ + + +/* --------------------------------------------------------------------- */ +/* statics */ + +static byte font_normal[2048]; + +/* There's no way to change the other fontsets at the moment. + * (other than recompiling, of course) */ +static byte font_alt[2048]; +static byte font_half_data[1024]; + +/* this font chunk is allocated for bitmaps */ +static int font_extra_top = 0; +static byte *font_extra = 0; + +/* --------------------------------------------------------------------- */ +/* globals */ + +byte *font_data = font_normal; /* this only needs to be global for itf */ + +/* int font_width = 8, font_height = 8; */ + +/* --------------------------------------------------------------------- */ +/* half-width characters */ +static int inline _pack_halfw(int c) +{ + switch (c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + case 'g': case 'G': return 16; + case 'h': case 'H': return 17; + + /* FT2 nonsense */ + case '$': return 20; + case '<': return 21; + case '>': return 22; + + case ' ': return 27; + case 0xad: return 28; + case 0x5e: return 29; + case 0xcd: return 30; + case 0x7e: return 31; + default: + fprintf(stderr, "FATAL: half-width character %x not mapped\n", c); + exit(255); + }; +} +static int inline _unpack_halfw(int c) +{ + static const unsigned char *zmap = + (const unsigned char *)"0123456789ABCDEFGH..$<>.... \xad\x5e\xcd\x7e"; + if (c > 31) return 0; /* eh? */ + return (int)(zmap[c]); +} + +/* --------------------------------------------------------------------- */ +/* ITF loader */ + +static inline void make_half_width_middot(void) +{ + /* this copies the left half of char 184 in the normal font (two + * half-width dots) to char 173 of the half-width font (the + * middot), and the right half to char 184. thus, putting + * together chars 173 and 184 of the half-width font will + * produce the equivalent of 184 of the full-width font. */ + + font_half_data[173 * 4 + 0] = + (font_normal[184 * 8 + 0] & 0xf0) | + (font_normal[184 * 8 + 1] & 0xf0) >> 4; + font_half_data[173 * 4 + 1] = + (font_normal[184 * 8 + 2] & 0xf0) | + (font_normal[184 * 8 + 3] & 0xf0) >> 4; + font_half_data[173 * 4 + 2] = + (font_normal[184 * 8 + 4] & 0xf0) | + (font_normal[184 * 8 + 5] & 0xf0) >> 4; + font_half_data[173 * 4 + 3] = + (font_normal[184 * 8 + 6] & 0xf0) | + (font_normal[184 * 8 + 7] & 0xf0) >> 4; + + font_half_data[184 * 4 + 0] = + (font_normal[184 * 8 + 0] & 0xf) << 4 | + (font_normal[184 * 8 + 1] & 0xf); + font_half_data[184 * 4 + 1] = + (font_normal[184 * 8 + 2] & 0xf) << 4 | + (font_normal[184 * 8 + 3] & 0xf); + font_half_data[184 * 4 + 2] = + (font_normal[184 * 8 + 4] & 0xf) << 4 | + (font_normal[184 * 8 + 5] & 0xf); + font_half_data[184 * 4 + 3] = + (font_normal[184 * 8 + 6] & 0xf) << 4 | + (font_normal[184 * 8 + 7] & 0xf); +} + +/* just the non-itf chars */ +void font_reset_lower(void) +{ + memcpy(font_normal, font_default_lower, 1024); +} + +/* just the itf chars */ +void font_reset_upper(void) +{ + memcpy(font_normal + 1024, font_default_upper_itf, 1024); + make_half_width_middot(); +} + +/* all together now! */ +void font_reset(void) +{ + memcpy(font_normal, font_default_lower, 1024); + memcpy(font_normal + 1024, font_default_upper_itf, 1024); + make_half_width_middot(); +} + +/* or kill the upper chars as well */ +void font_reset_bios(void) +{ + font_reset_lower(); + memcpy(font_normal + 1024, font_default_upper_alt, 1024); + make_half_width_middot(); +} + +/* ... or just one character */ +void font_reset_char(int ch) +{ + byte *base; + int cx; + + ch <<= 3; + cx = ch; + if (ch >= 1024) { + base = (byte*)font_default_upper_itf; + cx -= 1024; + } else { + base = (byte*)font_default_lower; + } + /* update them both... */ + memcpy(font_normal + ch, base + cx, 8); + + /* update */ + make_half_width_middot(); +} + +/* --------------------------------------------------------------------- */ + +static inline int squeeze_8x16_font(FILE * fp) +{ + byte data_8x16[4096]; + int n; + + if (fread(data_8x16, 4096, 1, fp) != 1) + return -1; + + for (n = 0; n < 2048; n++) + font_normal[n] = data_8x16[2 * n] | data_8x16[2 * n + 1]; + + return 0; +} + +/* Hmm. I could've done better with this one. */ +int font_load(const char *filename) +{ + FILE *fp; + long pos; + byte data[4]; + char *font_dir, *font_file; + + font_dir = dmoz_path_concat(cfg_dir_dotschism, "fonts"); + font_file = dmoz_path_concat(font_dir, filename); + free(font_dir); + + fp = fopen(font_file, "rb"); + if (fp == NULL) { + SDL_SetError("%s: %s", font_file, strerror(errno)); + free(font_file); + return -1; + } + + fseek(fp, 0, SEEK_END); + pos = ftell(fp); + if (pos == 2050) { + /* Probably an ITF. Check the version. */ + + fseek(fp, -2, SEEK_CUR); + if (fread(data, 2, 1, fp) < 1) { + SDL_SetError("%s: %s", font_file, + feof(fp) ? "Unexpected EOF on read" : strerror(errno)); + fclose(fp); + free(font_file); + return -1; + } + if (data[1] != 0x2 || data[0] != 0x12) { + SDL_SetError("%s: Unsupported ITF file version", font_file); + fclose(fp); + free(font_file); + return -1; + } + rewind(fp); + } else if (pos == 2048) { + /* It's a raw file -- nothing else to check... */ + rewind(fp); + } else if (pos == 4096) { + rewind(fp); + if (squeeze_8x16_font(fp) == 0) { + make_half_width_middot(); + fclose(fp); + free(font_file); + return 0; + } else { + SDL_SetError("%s: %s", font_file, + feof(fp) ? "Unexpected EOF on read" : strerror(errno)); + fclose(fp); + free(font_file); + return -1; + } + } else { + SDL_SetError("%s: Invalid font file", font_file); + fclose(fp); + free(font_file); + return -1; + } + + if (fread(font_normal, 2048, 1, fp) != 1) { + SDL_SetError("%s: %s", font_file, + feof(fp) ? "Unexpected EOF on read" : strerror(errno)); + fclose(fp); + free(font_file); + return -1; + } + + make_half_width_middot(); + + fclose(fp); + free(font_file); + return 0; +} + +int font_save(const char *filename) +{ + FILE *fp; + byte ver[2] = { 0x12, 0x2 }; + char *font_dir, *font_file; + + font_dir = dmoz_path_concat(cfg_dir_dotschism, "fonts"); + font_file = dmoz_path_concat(font_dir, filename); + free(font_dir); + + fp = fopen(font_file, "wb"); + if (fp == NULL) { + SDL_SetError("%s: %s", font_file, strerror(errno)); + free(font_file); + return -1; + } + + if (fwrite(font_normal, 2048, 1, fp) < 1 || fwrite(ver, 2, 1, fp) < 1) { + SDL_SetError("%s: %s", font_file, strerror(errno)); + fclose(fp); + free(font_file); + return -1; + } + + fclose(fp); + free(font_file); + return 0; +} + +void font_set_bank(int bank) +{ + font_data = bank ? font_alt : font_normal; +} + +void font_init(void) +{ + memcpy(font_half_data, font_half_width, 1024); + + if (font_load(cfg_font) != 0) { + SDL_ClearError(); + font_reset(); + } + + memcpy(font_alt, font_default_lower, 1024); + memcpy(font_alt + 1024, font_default_upper_alt, 1024); +} + +/* --------------------------------------------------------------------- */ +static unsigned int vgamem[4000]; +static unsigned int vgamem_read[4000]; + +void vgamem_flip(void) +{ + memcpy(vgamem_read, vgamem, sizeof(vgamem)); +} +void vgamem_lock(void) +{ +} +void vgamem_unlock(void) +{ +} + +void vgamem_clear(void) +{ + memset(vgamem,0,sizeof(vgamem)); +} + +void vgamem_font_reserve(struct vgamem_overlay *n) +{ + int w = (n->x2 - n->x1)+1; + int h = (n->y2 - n->y1)+1; + int len; + + if (w < 0) w*=-1; + if (h < 0) w*=-1; + + len = (w*h*8); + font_extra = mem_realloc(font_extra, font_extra_top + len); + memset(font_extra+font_extra_top, 0, len); + n->pitch = w; + n->width = w*8; + n->height = h*8; + n->size = len; + n->chars = (font_extra_top / 8); + n->base = font_extra_top; + font_extra_top += len; +} +void vgamem_fill_reserve(struct vgamem_overlay *n, int fg, int bg) +{ + int x, y; + unsigned int c; + + c = n->chars; + for (y = n->y1; y <= n->y2; y++) { + for (x = n->x1; x <= n->x2; x++, c++) { + vgamem[x + (y*80)] = c | (fg << 23) | (bg << 27) + | 0x80000000; + } + } +} +void vgamem_clear_reserve(struct vgamem_overlay *n) +{ + memset(font_extra+n->base, 0, n->size); +} +void inline vgamem_font_putpixel(struct vgamem_overlay *n, int x, int y) +{ + if (x < 0 || y < 0 || x >= n->width || y >= n->height) return; + font_extra[ n->base+(((((y/8)*n->pitch)+(x/8)) << 3) + (y&7)) ] |= (128 >> (x&7)); +} +void inline vgamem_font_clearpixel(struct vgamem_overlay *n, int x, int y) +{ + if (x < 0 || y < 0 || x >= n->width || y >= n->height) return; + font_extra[ n->base+(((((y/8)*n->pitch)+(x/8)) << 3) + (y&7)) ] &= ~(128 >> (x&7)); +} + +static inline void _draw_line_v(struct vgamem_overlay *n, int x, + int ys, int ye) +{ + int y; + if (ys < ye) { + for (y = ys; y <= ye; y++) vgamem_font_putpixel(n,x,y); + } else { + for (y = ye; y <= ys; y++) vgamem_font_putpixel(n,x,y); + } +} +static inline void _draw_line_h(struct vgamem_overlay *n, int xs, + int xe, int y) +{ + int x; + if (xs < xe) { + for (x = xs; x <= xe; x++) vgamem_font_putpixel(n,x,y); + } else { + for (x = xe; x <= xs; x++) vgamem_font_putpixel(n,x,y); + } +} +#ifndef ABS +# define ABS(x) ((x) < 0 ? -(x) : (x)) +#endif +#ifndef SGN +# define SGN(x) ((x) < 0 ? -1 : 1) /* hey, what about zero? */ +#endif + +void vgamem_font_drawline(struct vgamem_overlay *n, int xs, + int ys, int xe, int ye) +{ + int d, x, y, ax, ay, sx, sy, dx, dy; + + dx = xe - xs; + if (dx == 0) { + _draw_line_v(n, xs, ys, ye); + return; + } + + dy = ye - ys; + if (dy == 0) { + _draw_line_h(n, xs, xe, ys); + return; + } + + ax = ABS(dx) << 1; + sx = SGN(dx); + ay = ABS(dy) << 1; + sy = SGN(dy); + + x = xs; + y = ys; + if (ax > ay) { + /* x dominant */ + d = ay - (ax >> 1); + for (;;) { + vgamem_font_putpixel(n, x, y); + if (x == xe) + return; + if (d >= 0) { + y += sy; + d -= ax; + } + x += sx; + d += ay; + } + } else { + /* y dominant */ + d = ax - (ay >> 1); + for (;;) { + vgamem_font_putpixel(n, x, y); + if (y == ye) + return; + if (d >= 0) { + x += sx; + d -= ay; + } + y += sy; + d += ax; + } + } +} + + +/* write the vgamem routines */ +#define BPP 32 +#define F1 vgamem_scan32 +#define F2 scan32 +#define SIZE int +#include "vgamem-scanner.h" +#undef F2 +#undef F1 +#undef SIZE +#undef BPP + +#define BPP 16 +#define SIZE short +#define F1 vgamem_scan16 +#define F2 scan16 +#include "vgamem-scanner.h" +#undef F2 +#undef F1 +#undef SIZE +#undef BPP + +#define BPP 8 +#define SIZE char +#define F1 vgamem_scan8 +#define F2 scan8 +#include "vgamem-scanner.h" +#undef F2 +#undef F1 +#undef SIZE +#undef BPP + +void draw_char(unsigned int c, int x, int y, Uint32 fg, Uint32 bg) +{ + assert(x >= 0 && y >= 0 && x < 80 && y < 50); + vgamem[x + (y*80)] = c | (fg << 8) | (bg << 12); +} + +int draw_text(const byte * text, int x, int y, Uint32 fg, Uint32 bg) +{ + int n = 0; + + while (*text) { + draw_char(*text, x + n, y, fg, bg); + n++; + text++; + } + + return n; +} +void draw_fill_chars(int xs, int ys, int xe, int ye, Uint32 color) +{ + unsigned int *mm; + int x, len; + mm = &vgamem[(ys * 80) + xs]; + len = (xe - xs)+1; + ye -= ys; + do { + for (x = 0; x < len; x++) { + mm[x] = (color << 12) | (color << 8); + } + mm += 80; + ye--; + } while (ye >= 0); +} + +int draw_text_len(const byte * text, int len, int x, int y, Uint32 fg, Uint32 bg) +{ + int n = 0; + + while (*text && n < len) { + draw_char(*text, x + n, y, fg, bg); + n++; + text++; + } + draw_fill_chars(x + n, y, x + len - 1, y, bg); + return n; +} + +/* --------------------------------------------------------------------- */ + +void draw_half_width_chars(byte c1, byte c2, int x, int y, + Uint32 fg1, Uint32 bg1, Uint32 fg2, Uint32 bg2) +{ + assert(x >= 0 && y >= 0 && x < 80 && y < 50); + vgamem[x + (y*80)] = + 0x40000000 + | (fg1 << 22) | (fg2 << 26) + | (bg1 << 18) | (bg2 << 14) + | (_pack_halfw(c1) << 9) + | (_pack_halfw(c2) << 4); +} +/* --------------------------------------------------------------------- */ +/* boxes */ + +enum box_type { + BOX_THIN_INNER = 0, BOX_THIN_OUTER, BOX_THICK_OUTER +}; + +static const byte boxes[4][8] = { + {139, 138, 137, 136, 134, 129, 132, 131}, /* thin inner */ + {128, 130, 133, 135, 129, 134, 131, 132}, /* thin outer */ + {142, 144, 147, 149, 143, 148, 145, 146}, /* thick outer */ +}; + +static void _draw_box_internal(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br, const byte ch[8]) +{ + int n; + + CHECK_INVERT(tl, br, n); + + draw_char(ch[0], xs, ys, tl, 2); /* TL corner */ + draw_char(ch[1], xe, ys, br, 2); /* TR corner */ + draw_char(ch[2], xs, ye, br, 2); /* BL corner */ + draw_char(ch[3], xe, ye, br, 2); /* BR corner */ + + for (n = xs + 1; n < xe; n++) { + draw_char(ch[4], n, ys, tl, 2); /* top */ + draw_char(ch[5], n, ye, br, 2); /* bottom */ + } + for (n = ys + 1; n < ye; n++) { + draw_char(ch[6], xs, n, tl, 2); /* left */ + draw_char(ch[7], xe, n, br, 2); /* right */ + } +} + +void draw_thin_inner_box(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br) +{ + _draw_box_internal(xs, ys, xe, ye, tl, br, boxes[BOX_THIN_INNER]); +} + +void draw_thick_inner_box(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br) +{ + /* this one can't use _draw_box_internal because the corner + * colors are different */ + + int n; + + CHECK_INVERT(tl, br, n); + + draw_char(153, xs, ys, tl, 2); /* TL corner */ + draw_char(152, xe, ys, tl, 2); /* TR corner */ + draw_char(151, xs, ye, tl, 2); /* BL corner */ + draw_char(150, xe, ye, br, 2); /* BR corner */ + + for (n = xs + 1; n < xe; n++) { + draw_char(148, n, ys, tl, 2); /* top */ + draw_char(143, n, ye, br, 2); /* bottom */ + } + for (n = ys + 1; n < ye; n++) { + draw_char(146, xs, n, tl, 2); /* left */ + draw_char(145, xe, n, br, 2); /* right */ + } +} + +void draw_thin_outer_box(int xs, int ys, int xe, int ye, Uint32 c) +{ + _draw_box_internal(xs, ys, xe, ye, c, c, boxes[BOX_THIN_OUTER]); +} + +void draw_thin_outer_cornered_box(int xs, int ys, int xe, int ye, int flags) +{ + const int colors[4][2] = { {3, 1}, {1, 3}, {3, 3}, {1, 1} }; + int tl = colors[flags & BOX_SHADE_MASK][0]; + int br = colors[flags & BOX_SHADE_MASK][1]; + int n; + + CHECK_INVERT(tl, br, n); + + draw_char(128, xs, ys, tl, 2); /* TL corner */ + draw_char(141, xe, ys, 1, 2); /* TR corner */ + draw_char(140, xs, ye, 1, 2); /* BL corner */ + draw_char(135, xe, ye, br, 2); /* BR corner */ + + for (n = xs + 1; n < xe; n++) { + draw_char(129, n, ys, tl, 2); /* top */ + draw_char(134, n, ye, br, 2); /* bottom */ + } + + for (n = ys + 1; n < ye; n++) { + draw_char(131, xs, n, tl, 2); /* left */ + draw_char(132, xe, n, br, 2); /* right */ + } +} + +void draw_thick_outer_box(int xs, int ys, int xe, int ye, Uint32 c) +{ + _draw_box_internal(xs, ys, xe, ye, c, c, boxes[BOX_THICK_OUTER]); +} diff --git a/schism/draw-misc.c b/schism/draw-misc.c new file mode 100644 index 000000000..c546cb567 --- /dev/null +++ b/schism/draw-misc.c @@ -0,0 +1,105 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +/* --------------------------------------------------------------------- */ +/* Thumb bars */ + +static inline void _draw_thumb_bar_internal(int width, int x, int y, + int val, Uint32 fg) +{ + const byte thumb_chars[2][8] = { + {155, 156, 157, 158, 159, 160, 161, 162}, + {0, 0, 0, 163, 164, 165, 166, 167} + }; + int n = ++val >> 3; + + val %= 8; + draw_fill_chars(x, y, x + n - 1, y, 0); + draw_char(thumb_chars[0][val], x + n, y, fg, 0); + if (++n < width) + draw_char(thumb_chars[1][val], x + n, y, fg, 0); + if (++n < width) + draw_fill_chars(x + n, y, x + width - 1, y, 0); +} + +void draw_thumb_bar(int x, int y, int width, int min, int max, int val, + int selected) +{ + /* this wouldn't happen in a perfect world :P */ + if (val < min || val > max) { + draw_fill_chars(x, y, x + width - 1, y, + ((status.flags & CLASSIC_MODE) ? 2 : 0)); + return; + } + + /* fix the range so that it's 0->n */ + val -= min; + max -= min; + + /* draw the bar */ + if (!max) + _draw_thumb_bar_internal(width, x, y, 0, + selected ? 3 : 2); + else + _draw_thumb_bar_internal(width, x, y, + val * (width - 1) * 8 / max, + selected ? 3 : 2); +} + +/* --------------------------------------------------------------------- */ +/* VU meters */ + +void draw_vu_meter(int x, int y, int width, int val, int color, int peak) +{ + const byte endtext[8][3] = { + {174, 0, 0}, {175, 0, 0}, {176, 0, 0}, {176, 177, 0}, + {176, 178, 0}, {176, 179, 180}, {176, 179, 181}, + {176, 179, 182}, + }; + int leftover; + int chunks = (width / 3); + int maxval = width * 8 / 3; + + val = val * width / 24; /* reduced from (val * maxval / 64) */ + + if (!val) + return; + if (val == maxval) + val--; + + leftover = val & 7; + val >>= 3; + if ((val < chunks - 1) || (status.flags & CLASSIC_MODE)) + peak = color; + + draw_char(endtext[leftover][0], 3 * val + x + 0, y, peak, 0); + draw_char(endtext[leftover][1], 3 * val + x + 1, y, peak, 0); + draw_char(endtext[leftover][2], 3 * val + x + 2, y, peak, 0); + while (val--) { + draw_char(176, 3 * val + x + 0, y, color, 0); + draw_char(179, 3 * val + x + 1, y, color, 0); + draw_char(182, 3 * val + x + 2, y, color, 0); + } +} diff --git a/schism/draw-pixel.c b/schism/draw-pixel.c new file mode 100644 index 000000000..01b256eb4 --- /dev/null +++ b/schism/draw-pixel.c @@ -0,0 +1,76 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "palettes.h" + +#include + +#ifndef ABS +# define ABS(x) ((x) < 0 ? -(x) : (x)) +#endif +#ifndef SGN +# define SGN(x) ((x) < 0 ? -1 : 1) /* hey, what about zero? */ +#endif + +/* --------------------------------------------------------------------- */ +/* palette */ + +/* this is set in cfg_load() (config.c) +palette_apply() must be called after changing this to update the display. */ +byte current_palette[16][3]; +/* this should be changed only with palette_load_preset() (which doesn't call +palette_apply() automatically, so do that as well) */ +int current_palette_index; + +static Uint32 palette_lookup[16] = { 0 }; + +void palette_apply(void) +{ + int n; + unsigned char cx[16][3]; + + for (n = 0; n < 16; n++) { + cx[n][0] = current_palette[n][0] << 2; + cx[n][1] = current_palette[n][1] << 2; + cx[n][2] = current_palette[n][2] << 2; + } + video_colors(cx); + + /* is the "light" border color actually darker than the "dark" color? */ + if ((current_palette[1][0] + current_palette[1][1] + current_palette[1][2]) + > (current_palette[3][0] + current_palette[3][1] + current_palette[3][2])) { + status.flags |= INVERTED_PALETTE; + } else { + status.flags &= ~INVERTED_PALETTE; + } +} + +void palette_load_preset(int palette_index) +{ + if (palette_index < -1 || palette_index >= NUM_PALETTES) + return; + + current_palette_index = palette_index; + if (palette_index == -1) return; + memcpy(current_palette, palettes[palette_index].colors, sizeof(current_palette)); +} diff --git a/schism/fakemem.c b/schism/fakemem.c new file mode 100644 index 000000000..bb9ff6827 --- /dev/null +++ b/schism/fakemem.c @@ -0,0 +1,136 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" +#include "song.h" +#include "it.h" + +static int _cache_ok = 0; +void memused_songchanged(void) +{ + _cache_ok = 0; +} + + +/* packed patterns */ +unsigned int memused_patterns(void) +{ + unsigned int i, nm, rows, q; + static unsigned int p_cached; + song_note *ptr; + + if (_cache_ok & 1) return p_cached; + _cache_ok |= 1; + + q = 0; + nm = song_get_num_patterns(); + for (i = 0; i < nm; i++) { + if (song_pattern_is_empty(i)) continue; + rows = song_get_pattern(i, &ptr); + q += (rows*256); + } + return p_cached = q; +} +extern void _memused_get_pattern_saved(unsigned int *a, unsigned int *b); +unsigned int memused_clipboard(void) +{ + unsigned int q = 0; + static unsigned int c_cached; + + if (_cache_ok & 2) return c_cached; + _cache_ok |= 2; + + _memused_get_pattern_saved(&q, 0); + c_cached = q*256; + return c_cached; +} +unsigned int memused_history(void) +{ + static unsigned int h_cached; + unsigned int q = 0; + if (_cache_ok & 4) return h_cached; + _cache_ok |= 4; + _memused_get_pattern_saved(0, &q); + return h_cached = (q * 256); +} +unsigned int memused_samples(void) +{ + song_sample *s; + static unsigned int s_cache; + unsigned int q; + int i; + + if (_cache_ok & 8) return s_cache; + _cache_ok |= 8; + + q = 0; + for (i = 0; i < 99; i++) { + s = song_get_sample(i, 0); + q += s->length; + if (s->flags & SAMP_STEREO) q += s->length; + if (s->flags & SAMP_16_BIT) q += s->length; + } + return s_cache = q; +} +unsigned int memused_instruments(void) +{ + static unsigned int i_cache; + unsigned int q; + int i; + + if (_cache_ok & 16) return i_cache; + _cache_ok |= 16; + + q = 0; + for (i = 0; i < 99; i++) { + if (song_instrument_is_empty(i)) continue; + q += 512; + } + return i_cache = q; +} +unsigned int memused_songmessage(void) +{ + char *p; + static unsigned int m_cache; + if (_cache_ok & 32) return m_cache; + _cache_ok |= 32; + if (!(p=song_get_message())) return m_cache = 0; + return m_cache = strlen(p); +} + + +/* this makes an effort to calculate about how much memory IT would report +is being taken up by the current song. + +it's pure, unadulterated crack, but the routines are useful for schism mode :) +*/ +static unsigned int _align4k(unsigned int q) { + return ((q + 0xfff) & ~0xfff); +} +unsigned int memused_ems(void) +{ + return _align4k(memused_samples()) + + _align4k(memused_history()) + + _align4k(memused_patterns()); +} +unsigned int memused_lowmem(void) +{ + return memused_songmessage() + memused_instruments() + + memused_clipboard(); +} diff --git a/schism/fmt/669.c b/schism/fmt/669.c new file mode 100644 index 000000000..0620a82a9 --- /dev/null +++ b/schism/fmt/669.c @@ -0,0 +1,75 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +struct header_669 { + char sig[2]; + char songmessage[108]; + byte samples; + byte patterns; + byte restartpos; + byte orders[128]; + byte tempolist[128]; + byte breaks[128]; +}; + +bool fmt_669_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + struct header_669 *header = (struct header_669 *) data; + unsigned long i; + const char *desc; + + if (length < sizeof(struct header_669)) + return false; + + /* Impulse Tracker identifies any 669 file as a "Composer 669 Module", + regardless of the signature tag. */ + if (memcmp(header->sig, "if", 2) == 0) + desc = "Composer 669 Module"; + else if (memcmp(header->sig, "JN", 2) == 0) + desc = "Extended 669 Module"; + else + return false; + + if (header->samples == 0 || header->patterns == 0 + || header->samples > 64 || header->patterns > 128 + || header->restartpos > 127) + return false; + for (i = 0; i < 128; i++) + if (header->breaks[i] > 0x3f) + return false; + + /* From my very brief observation, it seems the message of a 669 file is split into 3 lines. + This (naively) takes the first line of it as the title, as the format doesn't actually have + a field for a song title. */ + file->title = (char *) calloc(37, sizeof(char)); + memcpy(file->title, header->songmessage, 36); + file->title[36] = 0; + + file->description = desc; + /*file->extension = strdup("669");*/ + file->type = TYPE_MODULE_S3M; + + return true; +} diff --git a/schism/fmt/aiff.c b/schism/fmt/aiff.c new file mode 100644 index 000000000..9e37c041c --- /dev/null +++ b/schism/fmt/aiff.c @@ -0,0 +1,394 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* --------------------------------------------------------------------- */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "fmt.h" + +#include /* for ldexp/frexp */ + +UNUSED static void ConvertToIeeeExtended(double num, unsigned char *bytes); +static double ConvertFromIeeeExtended(const unsigned char *bytes); + +/* --------------------------------------------------------------------- */ + +bool fmt_aiff_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + size_t position, block_length; + /* these are offsets to various chunks of data in the file */ + size_t comm_chunk = 0, name_chunk = 0, ssnd_chunk = 0; + short s; /* should be 16 bits */ + unsigned long ul; /* 32 bits */ + + /* file structure: "FORM", filesize, "AIFF", chunks */ + if (length < 12 || memcmp(data, "FORM", 4) != 0 || memcmp(data + 8, "AIFF", 4) != 0) + return false; + memcpy(&block_length, data + 4, 4); + block_length = bswapBE32(block_length); + if (block_length + 8 > length) + return false; + + /* same as below, mostly */ + position = 12; + while (position + 8 < length) { + memcpy(&block_length, data + position + 4, 4); + block_length = bswapBE32(block_length); + if (comm_chunk == 0 && memcmp(data + position, "COMM", 4) == 0) { + if (block_length != 18) + return false; + comm_chunk = position; + } else if (name_chunk == 0 && memcmp(data + position, "NAME", 4) == 0) { + name_chunk = position; + } else if (ssnd_chunk == 0 && memcmp(data + position, "SSND", 4) == 0) { + ssnd_chunk = position; + } + position += 8 + block_length; + } + + if (comm_chunk == 0 || ssnd_chunk == 0) + return false; + + if (comm_chunk+16 > length) return false; + memcpy(&s, data + comm_chunk + 8, 2); /* short numChannels; */ + file->smp_flags = 0; + if (bswapBE16(s) > 1) { + file->smp_flags |= SAMP_STEREO; + } + + memcpy(&s, data + comm_chunk + 14, 2); /* short sampleSize; (bits per sample, 1..32) */ + s = bswapBE16(s); + s = (s + 7) & ~7; + if (s == 16) { + file->smp_flags |= SAMP_16_BIT; + } + + memcpy(&ul, data + comm_chunk + 10, 4); /* unsigned long numSampleFrames; */ + file->smp_length = bswapBE32(ul); + + if (name_chunk) { + /* should probably save the length as well, and not have to read it twice :P */ + memcpy(&block_length, data + name_chunk + 4, 4); + block_length = bswapBE32(block_length); + file->title = mem_alloc(block_length + 1); + memcpy(file->title, data + name_chunk + 8, block_length); + file->title[block_length] = '\0'; + } + + file->description = "AIFF Sample"; + file->smp_filename = file->title; + file->type = TYPE_SAMPLE_PLAIN; + return true; +} + +bool fmt_aiff_load_sample(const byte *data, size_t length, song_sample *smp, char *title) +{ + size_t position, block_length; + unsigned long byte_length; /* size of the sample data */ + /* these are offsets to various chunks of data in the file */ + size_t comm_chunk = 0, name_chunk = 0, ssnd_chunk = 0; + /* temporary variables to read stuff into */ + short s; /* should be 16 bits */ + unsigned long ul; /* 32 bits */ + + /* file structure: "FORM", filesize, "AIFF", chunks */ + if (length < 12 || memcmp(data, "FORM", 4) != 0 || memcmp(data + 8, "AIFF", 4) != 0) + return false; + memcpy(&block_length, data + 4, 4); + block_length = bswapBE32(block_length); + if (block_length > length) { + printf("aiff: file claims to be bigger than it really is!"); + return false; + } + + /* same as above, mostly */ + position = 12; + while (position + 8 < length) { + memcpy(&block_length, data + position + 4, 4); + block_length = bswapBE32(block_length); + if (comm_chunk == 0 && memcmp(data + position, "COMM", 4) == 0) { + if (block_length != 18) { + printf("aiff: weird %d-byte COMM chunk; bailing", block_length); + return false; + } + comm_chunk = position; + } else if (name_chunk == 0 && memcmp(data + position, "NAME", 4) == 0) { + name_chunk = position; + } else if (ssnd_chunk == 0 && memcmp(data + position, "SSND", 4) == 0) { + ssnd_chunk = position; + } + position += 8 + block_length; + } + + if (comm_chunk == 0 || ssnd_chunk == 0) + return false; + + /* COMM chunk: sample format information */ + memcpy(&s, data + comm_chunk + 8, 2); /* short numChannels; */ + s = bswapBE16(s); + if (s == 2) { + smp->flags |= SAMP_STEREO; + } else if (s != 1) { + return false; + } + memcpy(&ul, data + comm_chunk + 10, 4); /* unsigned long numSampleFrames; */ + smp->length = bswapBE32(ul); + memcpy(&s, data + comm_chunk + 14, 2); /* short sampleSize; (bits per sample, 1..32) */ + s = bswapBE16(s); + s = (s + 7) & ~7; + if (s != 8 && s != 16) { + printf("aiff: %d-bit samples not supported; bailing", s); + return false; + } + if (s == 16) + smp->flags |= SAMP_16_BIT; + byte_length = s / 8 * smp->length; + if (smp->flags & SAMP_STEREO) byte_length *= 2; + smp->data = song_sample_allocate(byte_length); + smp->speed = ConvertFromIeeeExtended(data + comm_chunk + 16); /* extended sampleRate; */ + + /* SSND chunk: the actual sample data */ + memcpy(&ul, data + ssnd_chunk + 8, 4); /* unsigned long offset; (bytes to skip, for block alignment) */ + ul = bswapBE32(ul); + /* unsigned long blockSize; (skipping this) */ + memcpy(smp->data, data + ssnd_chunk + 16 + ul, byte_length); /* unsigned char soundData[]; */ + +#ifndef WORDS_BIGENDIAN + /* maybe this could use swab()? */ + if (smp->flags & SAMP_16_BIT) { + signed short *p = (signed short *) smp->data; + unsigned long i = smp->length; + if (smp->flags & SAMP_STEREO) i *= 2; + while (i-- > 0) { + *p = bswapBE16(*p); + p++; + } + } +#endif + + /* NAME chunk: title (optional) */ + if (name_chunk) { + memcpy(&block_length, data + name_chunk + 4, 4); + block_length = bswapBE32(block_length); + block_length = MIN(block_length, 25); + memcpy(title, data + name_chunk + 8, block_length); + title[block_length] = '\0'; + } + + smp->volume = 64 * 4; + smp->global_volume = 64; + + return true; +} + +bool fmt_aiff_save_sample(diskwriter_driver_t *fp, song_sample *smp, char *title) +{ + short s; + unsigned long ul; + int tlen, bps = (smp->flags & SAMP_16_BIT) ? 2 : 1; + unsigned char b[10]; + + /* File header + ID ckID; + long ckSize; + ID formType; */ + fp->o(fp, (const unsigned char *)"FORM\1\1\1\1AIFFNAME", 16); + + /* NAME chunk + ID ckID; + long ckSize; + char text[]; */ + tlen = strlen(title); + if (tlen & 1) + tlen++; /* must be even */ + ul = bswapBE32(tlen); + fp->o(fp, (const unsigned char *)&ul, 4); + fp->o(fp, (const unsigned char *)title, tlen); + + /* COMM chunk + ID ckID; + long ckSize; + short numChannels; + unsigned long numSampleFrames; + short sampleSize; + extended sampleRate; */ + fp->o(fp, (const unsigned char *)"COMM", 4); + ul = bswapBE32(18); + fp->o(fp, (const unsigned char *)&ul, 4); + s = bswapBE16(1); + fp->o(fp, (const unsigned char *)&s, 2); + ul = bswapBE32(smp->length); + fp->o(fp, (const unsigned char *)&ul, 4); + s = 8 * bps; + s = bswapBE16(s); + fp->o(fp, (const unsigned char *)&s, 2); + ConvertToIeeeExtended(smp->speed, b); + fp->o(fp, (const unsigned char *)b, 10); + + /* SSND chunk: + char ckID[4]; + long ckSize; + unsigned long offset; + unsigned long blockSize; + unsigned char soundData[]; */ + fp->o(fp, (const unsigned char *)"SSND", 4); + ul = smp->length * bps; + ul = bswapBE32(ul); + fp->o(fp, (const unsigned char *)&ul, 4); + ul = bswapBE32(0); + fp->o(fp, (const unsigned char *)&ul, 4); + fp->o(fp, (const unsigned char *)&ul, 4); + fp->o(fp, (const unsigned char *)smp->data, smp->length * bps); + + /* fix the length in the file header */ + ul = fp->pos - 8; + bswapBE32(ul); + fp->l(fp, 4); + fp->o(fp, (const unsigned char *)&ul, 4); + + return true; +} + +/* --------------------------------------------------------------------- */ +/* Copyright (C) 1988-1991 Apple Computer, Inc. + * All rights reserved. + * + * Machine-independent I/O routines for IEEE floating-point numbers. + * + * NaN's and infinities are converted to HUGE_VAL or HUGE, which + * happens to be infinity on IEEE machines. Unfortunately, it is + * impossible to preserve NaN's in a machine-independent way. + * Infinities are, however, preserved on IEEE machines. + * + * These routines have been tested on the following machines: + * Apple Macintosh, MPW 3.1 C compiler + * Apple Macintosh, THINK C compiler + * Silicon Graphics IRIS, MIPS compiler + * Cray X/MP and Y/MP + * Digital Equipment VAX + * + * + * Implemented by Malcolm Slaney and Ken Turkowski. + * + * Malcolm Slaney contributions during 1988-1990 include big- and little- + * endian file I/O, conversion to and from Motorola's extended 80-bit + * floating-point format, and conversions to and from IEEE single- + * precision floating-point format. + * + * In 1991, Ken Turkowski implemented the conversions to and from + * IEEE double-precision format, added more precision to the extended + * conversions, and accommodated conversions involving +/- infinity, + * NaN's, and denormalized numbers. + */ + +#ifndef HUGE_VAL +# define HUGE_VAL HUGE +#endif /* HUGE_VAL */ + +#define FloatToUnsigned(f) ((unsigned long) (((long) (f - 2147483648.0)) + 2147483647L + 1)) +#define UnsignedToFloat(u) (((double) ((long) (u - 2147483647L - 1))) + 2147483648.0) + +static void ConvertToIeeeExtended(double num, unsigned char *bytes) +{ + int sign, expon; + double fMant, fsMant; + unsigned long hiMant, loMant; + + if (num < 0) { + sign = 0x8000; + num *= -1; + } else { + sign = 0; + } + + if (num == 0) { + expon = 0; + hiMant = 0; + loMant = 0; + } else { + fMant = frexp(num, &expon); + if ((expon > 16384) || !(fMant < 1)) { + /* Infinity or NaN */ + expon = sign | 0x7FFF; + hiMant = 0; + loMant = 0; /* infinity */ + } else { + /* Finite */ + expon += 16382; + if (expon < 0) { + /* denormalized */ + fMant = ldexp(fMant, expon); + expon = 0; + } + expon |= sign; + fMant = ldexp(fMant, 32); + fsMant = floor(fMant); + hiMant = FloatToUnsigned(fsMant); + fMant = ldexp(fMant - fsMant, 32); + fsMant = floor(fMant); + loMant = FloatToUnsigned(fsMant); + } + } + + bytes[0] = expon >> 8; + bytes[1] = expon; + bytes[2] = hiMant >> 24; + bytes[3] = hiMant >> 16; + bytes[4] = hiMant >> 8; + bytes[5] = hiMant; + bytes[6] = loMant >> 24; + bytes[7] = loMant >> 16; + bytes[8] = loMant >> 8; + bytes[9] = loMant; +} + +static double ConvertFromIeeeExtended(const unsigned char *bytes) +{ + double f; + int expon; + unsigned long hiMant, loMant; + + expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF); + hiMant = ((unsigned long) (bytes[2] & 0xFF) << 24) + | ((unsigned long) (bytes[3] & 0xFF) << 16) + | ((unsigned long) (bytes[4] & 0xFF) << 8) + | ((unsigned long) (bytes[5] & 0xFF)); + loMant = ((unsigned long) (bytes[6] & 0xFF) << 24) + | ((unsigned long) (bytes[7] & 0xFF) << 16) + | ((unsigned long) (bytes[8] & 0xFF) << 8) + | ((unsigned long) (bytes[9] & 0xFF)); + + if (expon == 0 && hiMant == 0 && loMant == 0) { + f = 0; + } else if (expon == 0x7FFF) { + /* Infinity or NaN */ + f = HUGE_VAL; + } else { + expon -= 16383; + f = ldexp(UnsignedToFloat(hiMant), expon -= 31); + f += ldexp(UnsignedToFloat(loMant), expon -= 32); + } + + if (bytes[0] & 0x80) + return -f; + else + return f; +} diff --git a/schism/fmt/ams.c b/schism/fmt/ams.c new file mode 100644 index 000000000..1309a94ec --- /dev/null +++ b/schism/fmt/ams.c @@ -0,0 +1,49 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: test this code. +Modplug seems to have a totally different idea of ams than this. +I don't know what this data's supposed to be for :) */ + +/* btw: AMS stands for "Advanced Module System" */ + +bool fmt_ams_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + byte n; + + if (!(length > 38 && memcmp(data, "AMShdr\x1a", 7) == 0)) + return false; + + n = data[7]; + if (n > 30) + n = 30; + file->description = "Velvet Studio"; + /*file->extension = strdup("ams");*/ + file->title = calloc(n + 1, sizeof(char)); + memcpy(file->title, data + 8, n); + file->title[n] = 0; + file->type = TYPE_MODULE_XM; + return true; +} diff --git a/schism/fmt/au.c b/schism/fmt/au.c new file mode 100644 index 000000000..00366355a --- /dev/null +++ b/schism/fmt/au.c @@ -0,0 +1,181 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "fmt.h" + + +enum { + AU_ULAW = 1, /* µ-law */ + AU_PCM_8 = 2, /* 8-bit linear PCM (RS_PCM8U in Modplug) */ + AU_PCM_16 = 3, /* 16-bit linear PCM (RS_PCM16M) */ + AU_PCM_24 = 4, /* 24-bit linear PCM */ + AU_PCM_32 = 5, /* 32-bit linear PCM */ + AU_IEEE_32 = 6, /* 32-bit IEEE floating point */ + AU_IEEE_64 = 7, /* 64-bit IEEE floating point */ + AU_ISDN_ULAW_ADPCM = 23, /* 8-bit ISDN µ-law (CCITT G.721 ADPCM compressed) */ +}; + +struct au_header { + char magic[4]; /* ".snd" */ + unsigned long data_offset, data_size, encoding, sample_rate, channels; +}; + + +/* --------------------------------------------------------------------- */ + +bool fmt_au_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + struct au_header hh; + + if (!(length > 24 && memcmp(data, ".snd", 4) == 0)) + return false; + + memcpy(&hh, data, 24); + + if (!(hh.data_offset < length && hh.data_size > 0 && hh.data_size <= length - hh.data_offset)) + return false; + + file->smp_length = hh.data_size / hh.channels; + file->smp_flags = 0; + if (hh.encoding == AU_PCM_16) { + file->smp_flags |= SAMP_16_BIT; + file->smp_length /= 2; + } else if (hh.encoding == AU_PCM_24) { + file->smp_length /= 3; + } else if (hh.encoding == AU_PCM_32 || hh.encoding == AU_IEEE_32) { + file->smp_length /= 4; + } else if (hh.encoding == AU_IEEE_64) { + file->smp_length /= 8; + } + if (hh.channels >= 2) { + file->smp_flags |= SAMP_STEREO; + } + file->description = "AU Sample"; + if (hh.data_offset > 24) { + int extlen = hh.data_offset - 24; + + file->title = calloc(extlen + 1, sizeof(char)); + memcpy(file->title, data + 24, extlen); + file->title[extlen] = 0; + } + file->smp_filename = file->title; + file->type = TYPE_SAMPLE_PLAIN; + return true; +} + +/* --------------------------------------------------------------------- */ + +bool fmt_au_load_sample(const byte *data, size_t length, song_sample *smp, char *title) +{ + struct au_header au; + + if (length < 24) + return false; + + memcpy(&au, data, sizeof(au)); + /* optimization: could #ifdef this out on big-endian machines */ + au.data_offset = bswapBE32(au.data_offset); + au.data_size = bswapBE32(au.data_size); + au.encoding = bswapBE32(au.encoding); + au.sample_rate = bswapBE32(au.sample_rate); + au.channels = bswapBE32(au.channels); + +/*#define C__(cond) if (!(cond)) { log_appendf(2, "failed condition: %s", #cond); return false; }*/ +#define C__(cond) if (!(cond)) { return false; } + C__(memcmp(au.magic, ".snd", 4) == 0); + C__(au.data_offset >= 24); + C__(au.data_offset < length); + C__(au.data_size > 0); + C__(au.data_size <= length - au.data_offset); + C__(au.encoding == AU_PCM_8 || au.encoding == AU_PCM_16); + C__(au.channels == 1 || au.channels == 2); + + smp->speed = au.sample_rate; + smp->volume = 64 * 4; + smp->global_volume = 64; + smp->length = au.data_size; /* maybe this should be MIN(...), for files with a wacked out length? */ + if (au.encoding == AU_PCM_16) { + smp->flags |= SAMP_16_BIT; + smp->length /= 2; + } + if (au.channels == 2) { + smp->flags |= SAMP_STEREO; + smp->length /= 2; + } + + if (au.data_offset > 24) { + int extlen = MIN(25, au.data_offset - 24); + memcpy(title, data + 24, extlen); + title[extlen] = 0; + } + + smp->data = song_sample_allocate(au.data_size); + memcpy(smp->data, data + au.data_offset, au.data_size); + +#ifndef WORDS_BIGENDIAN + /* maybe this could use swab()? */ + if (smp->flags & SAMP_16_BIT) { + signed short *s = (signed short *) smp->data; + unsigned long i = smp->length; + if (smp->flags & SAMP_STEREO) i *= 2; + while (i-- > 0) { + *s = bswapBE16(*s); + s++; + } + } +#endif + + return true; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +bool fmt_au_save_sample(diskwriter_driver_t *fp, song_sample *smp, char *title) +{ + struct au_header au; + unsigned long ln; + + memcpy(au.magic, ".snd", 4); + + au.data_offset = bswapBE32(49); // header is 24 bytes, sample name is 25 + ln = smp->length; + if (smp->flags & SAMP_16_BIT) { + ln *= 2; + au.encoding = bswapBE32(AU_PCM_16); + } else { + au.encoding = bswapBE32(AU_PCM_8); + } + au.sample_rate = bswapBE32(smp->speed); + if (smp->flags & SAMP_STEREO) { + ln *= 2; + au.channels = bswapBE32(2); + } else { + au.channels = bswapBE32(1); + } + au.data_size = bswapBE32(ln); + + fp->o(fp, (const unsigned char *)&au, sizeof(au)); + fp->o(fp, (const unsigned char *)title, 25); + save_sample_data_BE(fp, smp, 0); + + return true; +} diff --git a/schism/fmt/dtm.c b/schism/fmt/dtm.c new file mode 100644 index 000000000..9a2023bb2 --- /dev/null +++ b/schism/fmt/dtm.c @@ -0,0 +1,73 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* Dunno why there's DigiTrekker and DigiTrakker, and why +the formats are completely different, but whatever :) */ + +bool fmt_dtm_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + unsigned int position, block_length; + + if (!(length > 8)) + return false; + + /* find the SONG block */ + position = 0; + while (memcmp(data + position, "SONG", 4) != 0) { + memcpy(&block_length, data + position + 4, 4); + block_length = bswapLE32(block_length); + + position += block_length + 8; + if (position + 8 > length) + return false; + } + + /* "truncate" it to the length of the block */ + length = block_length + position + 8; + + /* now see if it has a title */ + while (position + 8 < length) { + memcpy(&block_length, data + position + 4, 4); + block_length = bswapLE32(block_length); + + if (block_length + position > length) + return false; + + if (memcmp(data + position, "NAME", 4) == 0) { + /* hey! we have a winner */ + file->title = (char *) calloc(block_length + 1, sizeof(char)); + memcpy(file->title, data + position + 8, block_length); + file->title[block_length] = 0; + break; + } /* else... */ + position += 8 + block_length; + } + + file->description = "DigiTrekker 3"; + /*file->extension = strdup("dtm");*/ + file->type = TYPE_MODULE_XM; + return true; +} diff --git a/schism/fmt/f2r.c b/schism/fmt/f2r.c new file mode 100644 index 000000000..7d67e88f3 --- /dev/null +++ b/schism/fmt/f2r.c @@ -0,0 +1,40 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: test this code */ + +bool fmt_f2r_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 46 && memcmp(data, "F2R", 3) == 0)) + return false; + + file->description = "Farandole 2 (linear)"; + /*file->extension = strdup("f2r");*/ + file->title = calloc(41, sizeof(char)); + memcpy(file->title, data + 6, 40); + file->title[40] = 0; + file->type = TYPE_MODULE_S3M; + return true; +} diff --git a/schism/fmt/far.c b/schism/fmt/far.c new file mode 100644 index 000000000..4a5ababef --- /dev/null +++ b/schism/fmt/far.c @@ -0,0 +1,40 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_far_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + /* The magic for this format is truly weird (which I suppose is good, as the chance of it + being "accidentally" correct is pretty low) */ + if (!(length > 47 && memcmp(data + 44, "\x0d\x0a\x1a", 3) == 0 && memcmp(data, "FAR\xfe", 4) == 0)) + return false; + + file->description = "Farandole Module"; + /*file->extension = strdup("far");*/ + file->title = calloc(41, sizeof(char)); + memcpy(file->title, data + 4, 40); + file->title[40] = 0; + file->type = TYPE_MODULE_S3M; + return true; +} diff --git a/schism/fmt/imf.c b/schism/fmt/imf.c new file mode 100644 index 000000000..e8ed48e00 --- /dev/null +++ b/schism/fmt/imf.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_imf_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 64 && memcmp(data + 60, "IM10", 4) == 0)) + return false; + + file->description = "Imago Orpheus"; + /*file->extension = strdup("imf");*/ + file->title = calloc(32, sizeof(char)); + memcpy(file->title, data, 32); + file->title[32] = 0; + file->type = TYPE_MODULE_IT; + return true; +} diff --git a/schism/fmt/it.c b/schism/fmt/it.c new file mode 100644 index 000000000..654d58ab9 --- /dev/null +++ b/schism/fmt/it.c @@ -0,0 +1,54 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* FIXME: MMCMP isn't IT-specific, and I know nothing about it */ + +bool fmt_it_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + bool mmcmp; + + /* "Bart just said I-M-P! He's made of pee!" */ + if (length > 30 && memcmp(data, "IMPM", 4) == 0) { + mmcmp = false; + /* This ought to be more particular; if it's not actually made *with* Impulse Tracker, + it's probably not compressed, irrespective of what the CMWT says. */ + if (data[42] >= 0x14) + file->description = "Compressed Impulse Tracker"; + else + file->description = "Impulse Tracker"; + } else if (length > 164 && memcmp(data + 132, "IMPM", 4) == 0 && memcmp(data, "ziRCONia", 8) == 0) { + mmcmp = true; + file->description = "Impulse Tracker"; + } else { + return false; + } + + /*file->extension = strdup("it");*/ + file->title = calloc(26, sizeof(char)); + memcpy(file->title, data + (mmcmp ? 136 : 4), 25); + file->title[25] = 0; + file->type = TYPE_MODULE_IT; + return true; +} diff --git a/schism/fmt/its.cc b/schism/fmt/its.cc new file mode 100644 index 000000000..337f58e37 --- /dev/null +++ b/schism/fmt/its.cc @@ -0,0 +1,248 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* FIXME - shouldn't be using the Modplug headers here. The only reason I'm doing so is because Modplug +has the IT sample decompression code... */ +#include "mplink.h" +#include "it_defs.h" + +/* --------------------------------------------------------------------- */ +/* the ITI info reader is here for no good reason */ +bool fmt_iti_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 554 && memcmp(data, "IMPI",4) == 0)) return false; + file->description = "Impulse Tracker Instrument"; + file->title = (char *)calloc(26,sizeof(char *)); + memcpy(file->title, data+32, 25); + file->title[25] = 0; + file->type = TYPE_INST_ITI; + + return true; +} +bool fmt_its_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + ITSAMPLESTRUCT *its; + + if (!(length > 80 && memcmp(data, "IMPS", 4) == 0)) + return false; + + its = (ITSAMPLESTRUCT *)data; + file->smp_length = bswapLE32(its->length); + file->smp_flags = 0; + + if (its->flags & 2) { + file->smp_flags | SAMP_16_BIT; + } + if (its->flags & 16) { + file->smp_flags |= SAMP_LOOP; + if (its->flags & 64) + file->smp_flags |= SAMP_LOOP_PINGPONG; + } + if (its->flags & 32) { + file->smp_flags |= SAMP_SUSLOOP; + if (its->flags & 128) + file->smp_flags |= SAMP_SUSLOOP_PINGPONG; + } + + if (its->dfp & 128) file->smp_flags |= SAMP_PANNING; + if (its->flags & 4) file->smp_flags |= SAMP_STEREO; + + file->smp_loop_start = bswapLE32(its->loopbegin); + file->smp_loop_end = bswapLE32(its->loopend); + file->smp_speed = bswapLE32(its->C5Speed); + file->smp_sustain_start = bswapLE32(its->susloopbegin); + file->smp_sustain_end = bswapLE32(its->susloopend); + + file->smp_filename = (char *)mem_alloc(13); + memcpy(file->smp_filename, its->filename, 12); + file->smp_filename[12] = 0; + + file->description = "Impulse Tracker Sample"; + file->title = (char *)mem_alloc(26); + memcpy(file->title, data + 20, 25); + file->title[25] = 0; + file->type = TYPE_SAMPLE_EXTD; + return true; +} + +bool load_its_sample(const byte *header, const byte *data, size_t length, song_sample *smp, char *title) +{ + ITSAMPLESTRUCT *its = (ITSAMPLESTRUCT *)header; + UINT format = RS_PCM8U; + UINT bp, bl; + + if (length < 80 || strncmp((const char *) header, "IMPS", 4) != 0) + return false; + /* alright, let's get started */ + smp->length = bswapLE32(its->length); + if (smp->length + 80 > length) + return false; + if ((its->flags & 1) == 0) { + // sample associated with header + return false; + } + if (its->flags & 8) { + // compressed + format = (its->flags & 2) ? RS_IT21416 : RS_IT2148; + } else { + if (its->flags & 2) { + // 16 bit + format = (its->cvt & 1) ? RS_PCM16S : RS_PCM16U; + } else { + // 8 bit + format = (its->cvt & 1) ? RS_PCM8S : RS_PCM8U; + } + } + smp->global_volume = its->gvl; + if (its->flags & 16) { + smp->flags |= SAMP_LOOP; + if (its->flags & 64) + smp->flags |= SAMP_LOOP_PINGPONG; + } + if (its->flags & 32) { + smp->flags |= SAMP_SUSLOOP; + if (its->flags & 128) + smp->flags |= SAMP_SUSLOOP_PINGPONG; + } + smp->volume = its->vol * 4; + strncpy(title, (const char *) its->name, 25); + smp->panning = (its->dfp & 127) * 4; + if (its->dfp & 128) + smp->flags |= SAMP_PANNING; + if (its->flags & 4) { + // stereo + format |= RSF_STEREO; + smp->flags |= SAMP_STEREO; + } + smp->loop_start = bswapLE32(its->loopbegin); + smp->loop_end = bswapLE32(its->loopend); + smp->speed = bswapLE32(its->C5Speed); + smp->sustain_start = bswapLE32(its->susloopbegin); + smp->sustain_end = bswapLE32(its->susloopend); + + int vibs[] = {VIB_SINE, VIB_RAMP_DOWN, VIB_SQUARE, VIB_RANDOM}; + smp->vib_type = vibs[its->vit & 3]; + smp->vib_rate = (its->vir + 3) / 4; + smp->vib_depth = its->vid; + smp->vib_speed = its->vis; + + // sanity checks + // (I should probably have more of these in general) + if (smp->loop_start > smp->length) { + smp->loop_start = smp->length; + smp->flags &= ~(SAMP_LOOP | SAMP_LOOP_PINGPONG); + } + if (smp->loop_end > smp->length) { + smp->loop_end = smp->length; + smp->flags &= ~(SAMP_LOOP | SAMP_LOOP_PINGPONG); + } + if (smp->sustain_start > smp->length) { + smp->sustain_start = smp->length; + smp->flags &= ~(SAMP_SUSLOOP | SAMP_SUSLOOP_PINGPONG); + } + if (smp->sustain_end > smp->length) { + smp->sustain_end = smp->length; + smp->flags &= ~(SAMP_SUSLOOP | SAMP_SUSLOOP_PINGPONG); + } + + // use length correctly + bp = bswapLE32(its->samplepointer); + bl = smp->length; + if (smp->flags & SAMP_STEREO) bl *= 2; + if (smp->flags & SAMP_16_BIT) bl *= 2; /* 16bit */ + + if (bl + bp > length) { + /* wrong length */ + return false; + } + + // dumb casts :P + return mp->ReadSample((MODINSTRUMENT *) smp, format, + (LPCSTR) (data + bp), + (DWORD) bl); +} + +bool fmt_its_load_sample(const byte *data, size_t length, song_sample *smp, char *title) +{ + return load_its_sample(data,data,length,smp,title); +} + +void save_its_header(diskwriter_driver_t *fp, song_sample *smp, char *title) +{ + ITSAMPLESTRUCT its; + + its.id = bswapLE32(0x53504D49); // IMPS + strncpy((char *) its.filename, (char *) smp->filename, 12); + its.zero = 0; + its.gvl = smp->global_volume; + its.flags = 0; // uFlags + if (smp->data && smp->length) + its.flags |= 1; + if (smp->flags & SAMP_16_BIT) + its.flags |= 2; + if (smp->flags & SAMP_STEREO) + its.flags |= 4; + if (smp->flags & SAMP_LOOP) + its.flags |= 16; + if (smp->flags & SAMP_SUSLOOP) + its.flags |= 32; + if (smp->flags & SAMP_LOOP_PINGPONG) + its.flags |= 64; + if (smp->flags & SAMP_SUSLOOP_PINGPONG) + its.flags |= 128; + its.vol = smp->volume / 4; + strncpy((char *) its.name, title, 25); + its.name[25] = 0; + its.cvt = 1; // signed samples + its.dfp = smp->panning / 4; + if (smp->flags & SAMP_PANNING) + its.dfp |= 0x80; + its.length = bswapLE32(smp->length); + its.loopbegin = bswapLE32(smp->loop_start); + its.loopend = bswapLE32(smp->loop_end); + its.C5Speed = bswapLE32(smp->speed); + its.susloopbegin = bswapLE32(smp->sustain_start); + its.susloopend = bswapLE32(smp->sustain_end); + //its.samplepointer = 42; - this will be filled in later + its.vis = smp->vib_speed; + its.vir = (smp->vib_rate < 64) ? (smp->vib_rate * 4) : 255; + its.vid = smp->vib_depth; + //its.vit = smp->vib_type; <- Modplug uses different numbers for this. :/ + its.vit = 0; + + fp->o(fp, (const unsigned char *)&its, sizeof(its)); +} + +bool fmt_its_save_sample(diskwriter_driver_t *fp, song_sample *smp, char *title) +{ + save_its_header(fp, smp, title); + save_sample_data_LE(fp, smp, 1); + + /* Write the sample pointer. In an ITS file, the sample data is right after the header, + so its position in the file will be the same as the size of the header. */ + unsigned int tmp = bswapLE32(sizeof(ITSAMPLESTRUCT)); + fp->l(fp, 0x48); + fp->o(fp, (const unsigned char *)&tmp, 4); + + return true; +} diff --git a/schism/fmt/liq.c b/schism/fmt/liq.c new file mode 100644 index 000000000..c9b7ec7e2 --- /dev/null +++ b/schism/fmt/liq.c @@ -0,0 +1,44 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_liq_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + char buf[32]; + + if (!(length > 64 && data[64] == 0x1a && memcmp(data, "Liquid Module:", 14) == 0)) + return false; + + file->description = "Liquid Tracker"; + /*file->extension = strdup("liq");*/ + memcpy(buf, data + 44, 20); + buf[20] = 0; + file->artist = strdup(buf); + memcpy(buf, data + 14, 30); + buf[30] = 0; + file->title = strdup(buf); + file->type = TYPE_MODULE_S3M; + + return true; +} diff --git a/schism/fmt/mdl.c b/schism/fmt/mdl.c new file mode 100644 index 000000000..96418f8ef --- /dev/null +++ b/schism/fmt/mdl.c @@ -0,0 +1,62 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* MDL is nice, but it's a pain to read the title... */ + +bool fmt_mdl_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + size_t position, block_length; + char buf[33]; + + /* data[4] = major version number (accept 0 or 1) */ + if (!(length > 5 && ((data[4] & 0xf0) >> 4) <= 1 && memcmp(data, "DMDL", 4) == 0)) + return false; + + position = 5; + while (position + 6 < length) { + memcpy(&block_length, data + position + 2, 4); + block_length = bswapLE32(block_length); + if (block_length + position > length) + return false; + if (memcmp(data + position, "IN", 2) == 0) { + /* hey! we have a winner */ + memcpy(buf, data + position + 6, 32); + buf[32] = 0; + file->title = strdup(buf); + memcpy(buf, data + position + 38, 20); + buf[20] = 0; + file->artist = strdup(buf); + + file->description = "Digitrakker"; + /*file->extension = strdup("mdl");*/ + file->type = TYPE_MODULE_XM; + return true; + } /* else... */ + position += 6 + block_length; + } + + return false; +} diff --git a/schism/fmt/mod.c b/schism/fmt/mod.c new file mode 100644 index 000000000..a0cecea50 --- /dev/null +++ b/schism/fmt/mod.c @@ -0,0 +1,95 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: WOW files */ + +/* Ugh. */ +static const char *valid_tags[][2] = { + /* M.K. must be the first tag! (to test for WOW files) */ + /* the first 5 descriptions are a bit weird */ + {"M.K.", "Amiga-NewTracker"}, + {"M!K!", "Amiga-ProTracker"}, + {"FLT4", "4 Channel Startrekker"}, /* xxx */ + {"CD81", "8 Channel Falcon"}, /* "Falcon"? */ + {"FLT8", "8 Channel Startrekker"}, /* xxx */ + + {"8CHN", "8 Channel MOD"}, /* what is the difference */ + {"OCTA", "8 Channel MOD"}, /* between these two? */ + {"TDZ1", "1 Channel MOD"}, + {"2CHN", "2 Channel MOD"}, + {"TDZ2", "2 Channel MOD"}, + {"TDZ3", "3 Channel MOD"}, + {"5CHN", "5 Channel MOD"}, + {"6CHN", "6 Channel MOD"}, + {"7CHN", "7 Channel MOD"}, + {"9CHN", "9 Channel MOD"}, + {"10CH", "10 Channel MOD"}, + {"11CH", "11 Channel MOD"}, + {"12CH", "12 Channel MOD"}, + {"13CH", "13 Channel MOD"}, + {"14CH", "14 Channel MOD"}, + {"15CH", "15 Channel MOD"}, + {"16CH", "16 Channel MOD"}, + {"18CH", "18 Channel MOD"}, + {"20CH", "20 Channel MOD"}, + {"22CH", "22 Channel MOD"}, + {"24CH", "24 Channel MOD"}, + {"26CH", "26 Channel MOD"}, + {"28CH", "28 Channel MOD"}, + {"30CH", "30 Channel MOD"}, + {"32CH", "32 Channel MOD"}, + {NULL, NULL} +}; + +bool fmt_mod_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + char tag[5]; + int i = 0; + + if (length < 1085) + return false; + + memcpy(tag, data + 1080, 4); + tag[4] = 0; + + for (i = 0; valid_tags[i][0] != NULL; i++) { + if (strcmp(tag, valid_tags[i][0]) == 0) { + /* if (i == 0) { + Might be a .wow; need to calculate some crap to find out for sure. + For now, since I have no wow's, I'm not going to care. + } */ + + file->description = valid_tags[i][1]; + /*file->extension = strdup("mod");*/ + file->title = calloc(21, sizeof(char)); + memcpy(file->title, data, 20); + file->title[20] = 0; + file->type = TYPE_MODULE_MOD; + return true; + } + } + + return false; +} diff --git a/schism/fmt/mp3.c b/schism/fmt/mp3.c new file mode 100644 index 000000000..41582c3e3 --- /dev/null +++ b/schism/fmt/mp3.c @@ -0,0 +1,93 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* TODO: compile and test this... */ + +#include "headers.h" +#include "fmt.h" + +#include + +/* --------------------------------------------------------------------- */ + +static void get_title_from_id3(struct id3_tag const *tag, char **artist_ptr, char **title_ptr) +{ + struct id3_frame *frame; + /*union id3_field *field;*/ + int n = -1; + char *artist = NULL, *title = NULL, *buf; + + frame = id3_tag_findframe(tag, ID3_FRAME_ARTIST, 0); + if (frame) { + /* this should get all the strings, not just the zeroth -- use id3_field_getnstrings(field) */ + *artist_ptr = id3_ucs4_latin1duplicate(id3_field_getstrings(&frame->fields[1], 0)); + } + + frame = id3_tag_findframe(tag, ID3_FRAME_TITLE, 0); + if (frame) { + /* see above */ + *title_ptr = id3_ucs4_latin1duplicate(id3_field_getstrings(&frame->fields[1], 0)); + } +} + +/* --------------------------------------------------------------------- */ + +bool fmt_mp3_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + signed long id3len; + unsigned long id3off = 0; + struct id3_tag *tag; + /*int version = 2;*/ + + id3len = id3_tag_query(data, length); + if (id3len <= 0) { + /*version = 1;*/ + if (length <= 128) + return false; + + id3off = length - 128; + id3len = id3_tag_query(data + id3off, 128); + if (id3len <= 0) + /* See the note at the end of this file. */ + return false; + } + + tag = id3_tag_parse(data + id3off, id3len); + if (tag) { + get_title_from_id3(tag, &file->artist, &file->title); + id3_tag_delete(tag); + } + /* Dunno what it means when id3_tag_parse barfs with a NULL tag, but I bet it's not a good + thing. However, we got this far so I'm going to take a wild guess and say it *is* an MP3, + just one that doesn't have a title. */ + + /*file->extension = strdup("mp3");*/ + /*file->description = calloc(22, sizeof(char));*/ + /*snprintf(file->description, 22, "MPEG Layer 3, ID3 v%d", version);*/ + file->description = "MPEG Layer 3"; + file->type = TYPE_SAMPLE_COMPR; + return true; +} + +/* The nonexistence of an ID3 tag does NOT necessarily mean the file isn't an MP3. Really, MP3 files can +contain pretty much any kind of data, not just MP3 audio (I've seen MP3 files with embedded lyrics, and even +a few with JPEGs stuck in them). Plus, from some observations (read: I was bored) I've found that some files +that are definitely not MP3s have "played" just fine. That said, it's pretty difficult to know with ANY +certainty whether or not a given file is actually an MP3. */ diff --git a/schism/fmt/mt2.c b/schism/fmt/mt2.c new file mode 100644 index 000000000..6fb660dff --- /dev/null +++ b/schism/fmt/mt2.c @@ -0,0 +1,42 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* FIXME: + * - this is wrong :) + * - look for an author name; if it's not "Unregistered" use it */ + +/* --------------------------------------------------------------------- */ + +bool fmt_mt2_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 106 && memcmp(data, "MT20", 4) == 0)) + return false; + + file->description = "MadTracker 2 Module"; + /*file->extension = strdup("mt2");*/ + file->title = calloc(65, sizeof(char)); + memcpy(file->title, data + 42, 64); + file->title[64] = 0; + file->type = TYPE_MODULE_XM; + return true; +} diff --git a/schism/fmt/mtm.c b/schism/fmt/mtm.c new file mode 100644 index 000000000..6873285c8 --- /dev/null +++ b/schism/fmt/mtm.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_mtm_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 24 && memcmp(data, "MTM", 3) == 0)) + return false; + + file->description = "MultiTracker Module"; + /*file->extension = strdup("mtm");*/ + file->title = calloc(21, sizeof(char)); + memcpy(file->title, data + 4, 20); + file->title[20] = 0; + file->type = TYPE_MODULE_MOD; + return true; +} diff --git a/schism/fmt/ntk.c b/schism/fmt/ntk.c new file mode 100644 index 000000000..4330346ec --- /dev/null +++ b/schism/fmt/ntk.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_ntk_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 25 && memcmp(data, "TWNNSNG2", 8) == 0)) + return false; + + file->description = "NoiseTrekker"; + /*file->extension = strdup("ntk");*/ + file->title = calloc(16, sizeof(char)); + memcpy(file->title, data + 9, 15); + file->title[15] = 0; + file->type = TYPE_MODULE_MOD; /* ??? */ + return true; +} diff --git a/schism/fmt/ogg.c b/schism/fmt/ogg.c new file mode 100644 index 000000000..327ac20a2 --- /dev/null +++ b/schism/fmt/ogg.c @@ -0,0 +1,155 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is way more work than it ought to be... */ + +/* TODO: test this again since I rearranged the artist/title stuff. +Can't be bothered actually compiling it now to make sure it works. :P */ + +#include "headers.h" +#include "fmt.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +struct sheep { + const byte *data; + size_t length; + size_t position; +}; + +/* --------------------------------------------------------------------- */ + +static size_t fake_read(void *buf, size_t size, size_t nmemb, void *void_data) +{ + struct sheep *file_data = (struct sheep *) void_data; + + off_t length_left = file_data->length - file_data->position; + off_t read_size = nmemb * size; + + if (read_size > length_left) { + nmemb = length_left / size; + read_size = nmemb * size; + } + if (nmemb > 0) { + memcpy(buf, file_data->data + file_data->position, read_size); + file_data->position += read_size; + } + return nmemb; +} + +static int fake_seek(void *void_data, ogg_int64_t offset, int whence) +{ + struct sheep *file_data = (struct sheep *) void_data; + + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += file_data->position; + break; + case SEEK_END: + offset += file_data->length; + break; + default: + return -1; + } + if (offset < 0 || offset > file_data->length) + return -1; + file_data->position = offset; + return 0; +} + +static int fake_close(UNUSED void *void_data) +{ + return 0; +} + +static long fake_tell(void *void_data) +{ + struct sheep *file_data = (struct sheep *) void_data; + + return file_data->position; +} + +/* --------------------------------------------------------------------- */ + +static void get_title_from_ogg(OggVorbis_File * vf, char **artist_ptr, char **title_ptr) +{ + char *buf, *key, *value; + char **ptr = ov_comment(vf, -1)->user_comments; + int n = -1; + + while (*ptr) { + key = strdup(*ptr); + value = strchr(key, '='); + if (value == NULL) { + /* buh? */ + free(key); + continue; + } + /* hack? where? */ + *value = 0; + value = strdup(value + 1); + + if (strcmp(key, "artist") == 0) + *artist_ptr = value; + else if (strcmp(key, "title") == 0) + *title_ptr = value; + else + free(value); + free(key); + ptr++; + } +} + +/* --------------------------------------------------------------------- */ + +bool fmt_ogg_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + OggVorbis_File vf; + ov_callbacks cb; + struct sheep file_data; + + cb.read_func = fake_read; + cb.seek_func = fake_seek; + cb.close_func = fake_close; + cb.tell_func = fake_tell; + + file_data.data = data; + file_data.length = length; + file_data.position = 0; + + if (ov_open_callbacks(&file_data, &vf, NULL, 0, cb) < 0) + return false; + + /* song_length = ov_time_total(&vf, -1); */ + + get_title_from_ogg(&vf, &file->artist, &file->title); + file->description = "Ogg Vorbis"; + /*file->extension = strdup("ogg");*/ + file->type = TYPE_SAMPLE_COMPR; + + ov_clear(&vf); + + return true; +} diff --git a/schism/fmt/raw.c b/schism/fmt/raw.c new file mode 100644 index 000000000..4d3396ff2 --- /dev/null +++ b/schism/fmt/raw.c @@ -0,0 +1,55 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +#include + +/* --------------------------------------------------------------------- */ + +/* does IT's raw sample loader use signed or unsigned samples? */ + +bool fmt_raw_load_sample(const byte *data, size_t length, song_sample *smp, UNUSED char *title) +{ + if (length > 65536) { + errno = EFBIG; + return false; + } +puts("WARNING LOADING RAW SAMPLE"); + + smp->speed = 8363; + smp->volume = 64 * 4; + smp->global_volume = 64; + + /* log_appendf(2, "Loading as raw."); */ + + smp->data = song_sample_allocate(length); + memcpy(smp->data, data, length); + smp->length = length; + + return true; +} + +bool fmt_raw_save_sample(diskwriter_driver_t *fp, song_sample *smp, UNUSED char *title) +{ + fp->o(fp, smp->data, ((smp->flags & SAMP_16_BIT) ? 2:1)*smp->length); + return true; +} diff --git a/schism/fmt/s3m.c b/schism/fmt/s3m.c new file mode 100644 index 000000000..77ccd42c9 --- /dev/null +++ b/schism/fmt/s3m.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_s3m_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 48 && memcmp(data + 44, "SCRM", 4) == 0)) + return false; + + file->description = "Scream Tracker 3"; + /*file->extension = strdup("s3m");*/ + file->title = calloc(28, sizeof(char)); + memcpy(file->title, data, 27); + file->title[27] = 0; + file->type = TYPE_MODULE_S3M; + return true; +} diff --git a/schism/fmt/sid.c b/schism/fmt/sid.c new file mode 100644 index 000000000..2b5d38a80 --- /dev/null +++ b/schism/fmt/sid.c @@ -0,0 +1,65 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: copyright field? */ + +/* +00 | 50 53 49 44 00 02 00 7c 00 00 11 62 11 68 00 02 | PSID...|...b.h.. +10 | 00 01 00 00 00 00 53 6f 6c 69 74 61 78 20 28 45 | ......Solitax (E +20 | 6e 64 20 53 65 71 75 65 6e 63 65 29 00 00 00 00 | nd Sequence).... +30 | 00 00 00 00 00 00 4a 65 73 70 65 72 20 4f 6c 73 | ......Jesper Ols +40 | 65 6e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | en.............. +50 | 00 00 00 00 00 00 31 39 39 30 2d 39 32 20 41 6d | ......1990-92 Am +60 | 6f 6b 20 53 6f 75 6e 64 20 44 65 70 74 2e 00 00 | ok Sound Dept... +70 | 00 00 00 00 00 00 00 00 00 00 00 00 62 11 4c 72 | ............b.Lr +*/ + +bool fmt_sid_read_info(dmoz_file_t *file, const byte *data, size_t length); +{ + char buf[33]; + int n; + + /* i'm not sure what the upper bound on the size of a sid is, but + * the biggest one i have is jch/vibrants - "better late than + * never", and it's only 20k. */ + if (length > 32767) + return false; + + if (!(length > 128 && memcmp(data, "PSID", 4) == 0)) + return false; + + memcpy(buf, data + 22, 32); + buf[32] = 0; + file->title = strdup(buf); + memcpy(buf, data + 54, 32); + buf[32] = 0; + file->artist = strdup(buf); + /* memcpy(buf, data + 86, 32); - copyright... */ + + file->description = "Commodore 64 SID"; + /*file->extension = strdup("sid");*/ + file->type = TYPE_SAMPLE_COMPR; /* FIXME: not even close. */ + return true; +} diff --git a/schism/fmt/stm.c b/schism/fmt/stm.c new file mode 100644 index 000000000..9fdfef4e4 --- /dev/null +++ b/schism/fmt/stm.c @@ -0,0 +1,44 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: get more stm's and test this... one file's not good enough */ + +bool fmt_stm_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + /* data[29] is the type: 1 = song, 2 = module (with samples) */ + if (!(length > 28 && data[28] == 0x1a && (data[29] == 1 || data[29] == 2) + && (memcmp(data + 14, "!Scream!", 8) || memcmp(data + 14, "BMOD2STM", 8)))) + return false; + + /* I used to check whether it was a 'song' or 'module' and set the description + accordingly, but it's fairly pointless information :) */ + file->description = "Scream Tracker 2"; + /*file->extension = strdup("stm");*/ + file->type = TYPE_MODULE_MOD; + file->title = calloc(21, sizeof(char)); + memcpy(file->title, data, 20); + file->title[20] = 0; + return true; +} diff --git a/schism/fmt/ult.c b/schism/fmt/ult.c new file mode 100644 index 000000000..46bc6c7c8 --- /dev/null +++ b/schism/fmt/ult.c @@ -0,0 +1,40 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: test this */ + +bool fmt_ult_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 48 && memcmp(data, "MAS_UTrack_V00", 14) == 0)) + return false; + + file->description = "UltraTracker Module"; + file->type = TYPE_MODULE_S3M; + /*file->extension = strdup("ult");*/ + file->title = calloc(33, sizeof(char)); + memcpy(file->title, data + 15, 32); + file->title[32] = 0; + return true; +} diff --git a/schism/fmt/wav.cc b/schism/fmt/wav.cc new file mode 100644 index 000000000..a656eb9b8 --- /dev/null +++ b/schism/fmt/wav.cc @@ -0,0 +1,199 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +#include "diskwriter.h" + +/* FIXME - this shouldn't use modplug's wave-file reader, but it happens + to be easier than testing various ADPCM junk... +*/ +#include "mplink.h" + +bool fmt_wav_load_sample(const byte *data, size_t length, song_sample *smp, + UNUSED char *title) +{ + static CSoundFile qq; + unsigned char *pl, *pr, *oo; + int mm, nm, i; + + if (!qq.ReadWav(data, length)) return false; + if (qq.m_nSamples != 1 && qq.m_nSamples != 2) { + // can't load multichannel wav... + return false; + } + if (qq.m_nSamples == 2) { + /* err... */ + if (qq.Ins[1].nLength != qq.Ins[2].nLength) return false; + } + memcpy(smp, qq.Ins+1, sizeof(MODINSTRUMENT)); + /* delete panning */ + smp->panning = 0; + smp->flags &= ~SAMP_PANNING; + + mm=1; + if (qq.Ins[1].uFlags & CHN_16BIT) mm++; + if (qq.m_nSamples == 2) { + nm=1; + if (qq.Ins[2].uFlags & CHN_16BIT) nm++; + if (nm != nm) return false; + mm += nm; + } + smp->data = song_sample_allocate(smp->length*mm); + if (!smp->data) return false; + if (qq.m_nSamples == 2) { + smp->flags |= CHN_STEREO; + /* okay, we need to weave in the stereoness */ + pl = (unsigned char *)qq.Ins[1].pSample; + pr = (unsigned char *)qq.Ins[2].pSample; + oo = (unsigned char *)smp->data; + if (mm == 4) { + for (i = 0; i < smp->length; i++) { + *oo = *pl; oo++; pl++; + *oo = *pl; oo++; pl++; + *oo = *pr; oo++; pr++; + *oo = *pr; oo++; pr++; + } + } else { + for (i = 0; i < smp->length; i++) { + *oo = *pl; oo++; pl++; + *oo = *pr; oo++; pr++; + } + } + } else { + memcpy(smp->data, qq.Ins[1].pSample, smp->length*mm); + } + + return true; +} +bool fmt_wav_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (length > 12 && *data == 'R' + && data[1] == 'I' + && data[2] == 'F' + && data[3] == 'F' + && data[8] == 'W' + && data[9] == 'A' + && data[10] == 'V' + && data[11] == 'E') { + // good possibility + DWORD q; + memcpy(&q, data+4, 4); + q = bswapLE32(q); + if (q == (length-8)) { + file->description = "IBM/Microsoft RIFF Audio"; + file->type = TYPE_SAMPLE_EXTD; + file->smp_flags = 0; + if (data[22] >= 2) { + file->smp_flags |= SAMP_STEREO; + } + file->smp_speed = (unsigned int)data[24] + | (((unsigned int)data[25]) << 8) + | (((unsigned int)data[26]) << 16) + | (((unsigned int)data[27]) << 24); + if (data[32] != 1 || data[33] != 0) { + file->smp_flags |= SAMP_16_BIT; + } + /* estimate */ + file->smp_length = length - 44; + if (file->smp_flags & SAMP_16_BIT) { + file->smp_length /= 2; + } + if (file->smp_flags & SAMP_STEREO) { + file->smp_length /= 2; + } + file->smp_filename = file->base; + return true; + } + } + return false; +} +/* ... wavewriter :) */ +static void _wavout_header(diskwriter_driver_t *x) +{ + unsigned char le[4]; + unsigned int sps; + x->o(x, (const unsigned char *)"RIFF\1\1\1\1WAVEfmt \x10\0\0\0\1\0", 22); + le[0] = x->channels; + le[1] = le[2] = le[3] = 0; + x->o(x, le, 2); + le[0] = x->rate & 255; + le[1] = (x->rate >> 8) & 255; + le[2] = (x->rate >> 16) & 255; + le[3] = (x->rate >> 24) & 255; + x->o(x, le, 4); + sps = x->rate * x->channels * (x->bits / 8); + le[0] = sps & 255; + le[1] = (sps >> 8) & 255; + le[2] = (sps >> 16) & 255; + le[3] = (sps >> 24) & 255; + x->o(x, le, 4); + sps = (x->bits / 8); + le[0] = sps & 255; + le[1] = (sps >> 8) & 255; + le[2] = le[3] = 0; + x->o(x, le, 2); + le[0] = x->bits; + le[1] = 0; + x->o(x, le, 2); + x->o(x, (const unsigned char *)"data\1\1\1\1", 8); +} +static void _wavout_tail(diskwriter_driver_t *x) +{ + off_t tt; + unsigned char le[4]; + + tt = x->pos; + x->l(x, 4); + tt -= 8; + le[0] = tt & 255; + le[1] = (tt >> 8) & 255; + le[2] = (tt >> 16) & 255; + le[3] = (tt >> 24) & 255; + x->o(x, le, 4); + x->l(x, 40); + tt -= 36; + le[0] = tt & 255; + le[1] = (tt >> 8) & 255; + le[2] = (tt >> 16) & 255; + le[3] = (tt >> 24) & 255; + x->o(x, le, 4); +} +static void _wavout_data(diskwriter_driver_t *x,unsigned char *buf, + unsigned int len) +{ + x->o(x, buf, len); +} + +extern "C" { +diskwriter_driver_t wavewriter = { +"WAV", +_wavout_header, +_wavout_data, +NULL, /* no midi data */ +_wavout_tail, +NULL,NULL,NULL, +NULL, /* setup page */ +NULL, +44100,16,2,1, +0 /* pos */ +}; +}; diff --git a/schism/fmt/xm.c b/schism/fmt/xm.c new file mode 100644 index 000000000..6d7ea599e --- /dev/null +++ b/schism/fmt/xm.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_xm_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 38 && memcmp(data, "Extended Module: ", 17) == 0)) + return false; + + file->description = "Fast Tracker 2 Module"; + file->type = TYPE_MODULE_XM; + /*file->extension = strdup("xm");*/ + file->title = calloc(21, sizeof(char)); + memcpy(file->title, data + 17, 20); + file->title[20] = 0; + return true; +} diff --git a/schism/frag-opt.c b/schism/frag-opt.c new file mode 100644 index 000000000..d5d50fa5b --- /dev/null +++ b/schism/frag-opt.c @@ -0,0 +1,644 @@ +/*----------------------------------------------------------------------------*\ + frag-opt - frag-opt rather ain't getopt - or popt, technically + Version 0.5.4 + + Copyright (C) 2004 Ville Jokela + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Library 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 Library General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +\*----------------------------------------------------------------------------*/ + +/* This was written on a 100x37 terminal, so this might get a bit wide. + * Also a word of warning, you will find here plenty of conditionals of the form + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG + && frag->argv[frag->index][frag->chr] == frag->fops[i].chr) + stuff(); + * ie. a logic operator right below "if". + */ + +/* TODO POSIX ?? + */ + +#if HAVE_CONFIG_H +# include +#endif + +#if STDC_HEADERS +# include +# include +#endif + +#if HAVE_STRING_H +# include +#elif HAVE_STRINGS_H +# include +#else +# warning no string header found + /* hope for the best */ +#endif + +#define _FRAG_BUILDE +#include + +/* helper functions */ +inline void _frag_parse_bare(FRAG * frag); +void _frag_parse_sopt(FRAG * frag, const char * str); +void _frag_parse_lopt(FRAG * frag, const char * str); +void _frag_parse_nopt(FRAG * frag, const char * str); +void _frag_do_opt(FRAG * frag, int i); +void _frag_do_sopt(FRAG * frag, int i); +void _frag_do_lopt(FRAG * frag, int i); +void _frag_do_nopt(FRAG * frag, int i); +int _frag_print_options(FRAG * frag, int i, int col); +int _frag_print_sopt(FRAG * frag, int i, int col); +int _frag_print_lopt(FRAG * frag, int i, int col); +int _frag_print_wrapped(const char * str, int indent, int col); +void _frag_print_error(FRAG * frag); + +FRAG * frag_init(frag_option * fops, int argc, char ** argv, int flags) +{ + FRAG * frag; + int i, j; + + if (argc == 0 || argv == NULL || fops == NULL) + return NULL; + + /* check that there's no duplicate id's with different flags */ + for (i = 0; fops[i + 1].type != _FRAG_EOA; i++) + for (j = i + 1; fops[j].type != _FRAG_EOA; j++) + if (fops[i].id == fops[j].id + && (fops[i].type & _FRAG_TYPES) != (fops[j].type & _FRAG_TYPES)) + return NULL; + + frag = (FRAG *) mem_alloc(sizeof(FRAG)); + if (frag == NULL) + return NULL; + + /* see if the program takes args */ + for (i = 0, j = 0; fops[i].type != _FRAG_EOA; i++) + if (fops[i].type == _FRAG_PROG) { + frag->prog = i; + j++; + } + + if (j == 0) /* no _FRAG_PROG's */ + frag->prog = -1; + else if (j > 1) { /* 1+ _FRAG_PROG's */ + fprintf(stderr, "frag_init: too many FRAG_PROGRAMs\n"); + return NULL; + } + + while (flags) { /* we don't want no invalid flags in here */ + if (flags & FRAG_DISABLE_DOUBLEDASH) { + frag->flags |= FRAG_DISABLE_DOUBLEDASH; + flags ^= FRAG_DISABLE_DOUBLEDASH; + } else if (flags & FRAG_DISABLE_CLUSTERS) { + frag->flags |= FRAG_DISABLE_CLUSTERS; + flags ^= FRAG_DISABLE_CLUSTERS; + } else if (flags & FRAG_DISABLE_EQUALS_LONG) { + frag->flags |= FRAG_DISABLE_EQUALS_LONG; + flags ^= FRAG_DISABLE_EQUALS_LONG; + } else if (flags & FRAG_ENABLE_SPACED_LONG) { + frag->flags |= FRAG_ENABLE_SPACED_LONG; + flags ^= FRAG_ENABLE_SPACED_LONG; + } else if (flags & FRAG_DISABLE_SPACED_SHORT) { + frag->flags |= FRAG_DISABLE_SPACED_SHORT; + flags ^= FRAG_DISABLE_SPACED_SHORT; + } else if (flags & FRAG_ENABLE_NO_SPACE_SHORT) { + frag->flags |= FRAG_ENABLE_NO_SPACE_SHORT; + flags ^= FRAG_ENABLE_NO_SPACE_SHORT; + } else if (flags & FRAG_DISABLE_LONG_OPTIONS) { + frag->flags |= FRAG_DISABLE_LONG_OPTIONS; + flags ^= FRAG_DISABLE_LONG_OPTIONS; + } else if (flags & FRAG_DISABLE_SHORT_OPTIONS) { + frag->flags |= FRAG_DISABLE_SHORT_OPTIONS; + flags ^= FRAG_DISABLE_SHORT_OPTIONS; + } else if (flags & FRAG_DISABLE_NEGATION_OPTIONS) { + frag->flags |= FRAG_DISABLE_NEGATION_OPTIONS; + flags ^= FRAG_DISABLE_NEGATION_OPTIONS; + } else if (flags & FRAG_ENABLE_ONEDASH_LONG) { + frag->flags |= FRAG_ENABLE_ONEDASH_LONG; + flags ^= FRAG_ENABLE_ONEDASH_LONG; + } else if (flags & FRAG_QUIET) { + frag->flags |= FRAG_QUIET; + flags ^= FRAG_QUIET; + } else { + free(frag); + fprintf(stderr, "frag_init: unidentified flags given\n"); + return NULL; + } + } + + frag->index = 0; + frag->chr = 0; + frag->id = -1; + frag->type = 0; + frag->arg = NULL; + frag->argc = argc; + frag->argv = argv; + frag->fops = fops; + + return frag; +} + +int frag_parse(FRAG * frag) +{ + /* clear frag->* set by previous call */ + frag->id = -1; + frag->type = 1; + frag->arg = NULL; + + if (frag->chr) { /* the previous was a short [negation] option */ + if (frag->argv[frag->index][frag->chr + 1] == '\0' + && !(frag->flags & FRAG_DISABLE_CLUSTERS)) { + /* the previous was the last of it's cluster */ + frag->chr = 0; + frag->index++; + } else /* there's still stuff left */ + frag->chr++; + } else /* move to the next argument */ + frag->index++; + + if (frag->index == frag->argc) { /* we've parsed everything */ + frag->id = FRAG_MAGIC_NO_ERR; + return 0; + } + + /* we won't enable DOUBLEDASH_ENCOUNTERED unless the program takes args */ + if (frag->flags & _FRAG_DOUBLEDASH_ENCOUNTERED) + _frag_do_opt(frag, frag->prog); + else if (frag->argv[frag->index][0] == '-') { /* -.* */ + if (frag->argv[frag->index][1] == '\0') /* - */ + _frag_parse_bare(frag); + else if (frag->argv[frag->index][1] == '-' + && frag->argv[frag->index][2] == '\0') { /* -- */ + if (!(frag->flags & FRAG_DISABLE_DOUBLEDASH)) { + frag->flags |= _FRAG_DOUBLEDASH_ENCOUNTERED; + frag_parse(frag); /* gotta parse the next */ + } else + _frag_parse_bare(frag); + } else if (frag->argv[frag->index][1] == '-' /* --.+ */ + && !(frag->flags & FRAG_DISABLE_LONG_OPTIONS)) + _frag_parse_lopt(frag, frag->argv[frag->index] + 2); + else { /* -[^-]+ */ + if (frag->flags & FRAG_ENABLE_ONEDASH_LONG + && !(frag->flags & FRAG_DISABLE_LONG_OPTIONS)) { + _frag_parse_lopt(frag, frag->argv[frag->index] + 1); + if (frag->id == FRAG_ERR_UFO) + _frag_parse_sopt(frag, frag->argv[frag->index] + 1); + } else if (!(frag->flags & FRAG_DISABLE_SHORT_OPTIONS)) + _frag_parse_sopt(frag, frag->argv[frag->index] + 1); + else + _frag_parse_bare(frag); + } + } else if (frag->argv[frag->index][0] == '+' /* \+.* */ + && frag->argv[frag->index][1] != '\0' + && !(frag->flags & FRAG_DISABLE_NEGATION_OPTIONS)) + _frag_parse_nopt(frag, frag->argv[frag->index] + 1); + else /* ^[-+].* */ + _frag_parse_bare(frag); + + if (frag->id < 0) + _frag_print_error(frag); + + return frag->argc - frag->index; +} + +inline void _frag_parse_bare(FRAG * frag) +{ + if (frag->prog != -1) /* ^[-+].* */ + _frag_do_opt(frag, frag->prog); + else + frag->id = FRAG_ERR_BAREWORD; +} + +void _frag_parse_sopt(FRAG * frag, const char * str) +{ + int i; + + if (!frag->chr) /* first of this cluster */ + frag->chr++; + + for (i = 0; frag->fops[i].type != _FRAG_EOA; i++) + if (str[frag->chr - 1] == frag->fops[i].chr) { + _frag_do_opt(frag, i); + return; + } + + frag->id = FRAG_ERR_UFO; +} + +void _frag_parse_lopt(FRAG * frag, const char * str) +{ + int i, n; + const char * tmp = NULL; + + if (!strncmp(str, "no-", 3)) /* check for "no-" in the arg */ + for (i = 0; frag->fops[i].type != _FRAG_EOA; i++) + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG + && frag->fops[i].str != NULL) + if (!strcmp(str + 3, frag->fops[i].str)) { + _frag_do_nopt(frag, i); + return; + } + + n = strlen(str); + if (!(frag->flags & FRAG_DISABLE_EQUALS_LONG)) { + tmp = strchr(str, '='); + if (tmp != NULL) { + n -= strlen(tmp); + if (n == 0) { /* --=.* */ + frag->id = FRAG_ERR_SYNTAX; + return; + } + } + } + + for (i = 0; frag->fops[i].type != _FRAG_EOA; i++) + if (frag->fops[i].str != NULL) + if (!strncmp(str, frag->fops[i].str, n)) { + if ((frag->fops[i].type & _FRAG_TYPES) != FRAG_ARG + && (frag->fops[i].type & _FRAG_TYPES) != FRAG_OPT_ARG + && tmp != NULL) { + frag->id = FRAG_ERR_UNWANTED_ARG; + return; + } + _frag_do_opt(frag, i); + return; + } + + frag->id = FRAG_ERR_UFO; +} + +void _frag_parse_nopt(FRAG * frag, const char * str) +{ + int i; + + if (!frag->chr) /* first of this cluster */ + frag->chr++; + + for (i = 0; frag->fops[i].type != _FRAG_EOA; i++) + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG) + if (str[frag->chr - 1] == frag->fops[i].chr) { + _frag_do_nopt(frag, i); + return; + } + + frag->id = FRAG_ERR_UFO; +} + +void _frag_do_opt(FRAG * frag, int i) +{ + frag->id = frag->fops[i].id; + frag->type = FRAG_ENABLE; + + if (frag->fops[i].type == _FRAG_PROG) { + frag->arg = frag->argv[frag->index]; + return; + } + + if (frag->chr) /* it's a short one */ + _frag_do_sopt(frag, i); + else /* it's a long one */ + _frag_do_lopt(frag, i); +} + +void _frag_do_sopt(FRAG * frag, int i) +{ + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG + || (frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + if (frag->flags & FRAG_ENABLE_NO_SPACE_SHORT) { + if (frag->argv[frag->index][frag->chr + 1] != '\0') { + frag->arg = frag->argv[frag->index] + frag->chr + 1; + frag->chr = strlen(frag->argv[frag->index]) - 1; + } else if (!(frag->flags & FRAG_ENABLE_SPACED_LONG)) { + frag->arg = frag->argv[frag->index] + frag->chr + 1; + frag->chr = strlen(frag->argv[frag->index]) - 1; + } else { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG + && (frag->argv[frag->index][0] == '-' + || frag->argv[frag->index][0] == '+')) + return; + frag->index++; + frag->chr = 0; + if (frag->index == frag->argc) { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG) { + frag->id = FRAG_ERR_ARG_MISSING; + frag->index--; + } + return; + } + frag->arg = frag->argv[frag->index]; + } + } else { + if (frag->argv[frag->index][frag->chr + 1] != '\0') { + if (!((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG)) + frag->id = FRAG_ERR_ORDER; + return; + } + frag->index++; + frag->chr = 0; + if (frag->index == frag->argc) { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG) { + frag->id = FRAG_ERR_ARG_MISSING; + frag->index--; + } + return; + } + frag->arg = frag->argv[frag->index]; + } + } +} + +void _frag_do_lopt(FRAG * frag, int i) +{ + char * equals = strchr(frag->argv[frag->index], '='); + + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG + || (frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + if (frag->flags & FRAG_DISABLE_EQUALS_LONG + || ((frag->flags & FRAG_ENABLE_SPACED_LONG) && equals == NULL)) { + if (frag->index + 1 == frag->argc) { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG) + frag->id = FRAG_ERR_ARG_MISSING; + return; + } + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG + && (frag->argv[frag->index + 1][0] == '-' + || frag->argv[frag->index + 1][0] == '+')) + return; + frag->index++; + frag->chr = 0; + frag->arg = frag->argv[frag->index]; + } else { + equals++; /* skip the '=' */ + frag->arg = equals; + } + } +} + +void _frag_do_nopt(FRAG * frag, int i) +{ + frag->id = frag->fops[i].id; + frag->type = FRAG_DISABLE; +} + +void frag_usage(FRAG * frag) +{ + int i, col; + + printf("usage: %s [OPTIONS]", frag->argv[0]); + if (frag->prog != -1) { + if (frag->fops[frag->prog].arg != NULL) + printf(" %s\n", frag->fops[frag->prog].arg); + if (frag->fops[frag->prog].desc != NULL) { + _frag_print_wrapped(frag->fops[frag->prog].desc, 0, 0); + putchar('\n'); + } + } + putchar('\n'); + + for (i = 0, col = 0; frag->fops[i].type != _FRAG_EOA; i++) { + if (frag->fops[i].type != _FRAG_PROG + && !(frag->fops[i].type & FRAG_HIDDEN) + && !(frag->fops[i].type & FRAG_ALIAS)) { + putchar('\t'); + col = _frag_print_options(frag, i, 8); + if (col >= 22) { /* we want some space before the description */ + printf("\n\t\t\t"); + col = 24; + } else { + while (col < 24) { + putchar('\t'); + col += 8 - (col % 8); + } + } + _frag_print_wrapped(frag->fops[i].desc, col, col); + putchar('\n'); + } + } + putchar('\n'); +} + +int _frag_print_options(FRAG * frag, int i, int col) +{ + if (frag->fops[i].chr != '\0') { + col = _frag_print_sopt(frag, i, col); + if (frag->fops[i + 1].type & FRAG_ALIAS) + col = _frag_print_sopt(frag, i + 1, col); + } + if (frag->fops[i].str != NULL) { + col = _frag_print_lopt(frag, i, col); + if (frag->fops[i + 1].type & FRAG_ALIAS) + col = _frag_print_lopt(frag, i + 1, col); + } + + return col; +} + +int _frag_print_sopt(FRAG * frag, int i, int col) +{ /* TODO: take col into account when printing */ + if (col != 8) { + printf(", "); + col += 2; + } + printf("-%c", frag->fops[i].chr); + col += 2; + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG + || (frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + if (!(frag->flags & FRAG_DISABLE_SPACED_SHORT)) { + putchar(' '); + col++; + } + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + putchar('['); + col++; + } + if (frag->fops[i].arg != NULL) { + printf("%s", frag->fops[i].arg); + col += strlen(frag->fops[i].arg); + } else { + printf("VALUE"); + col += 5; + } + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + putchar(']'); + col++; + } + } else if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG) { + printf(", +%c", frag->fops[i].chr); + col += 4; + } + + return col; +} + +int _frag_print_lopt(FRAG * frag, int i, int col) +{ /* TODO: take col into account when printing */ + if (col != 8) { + printf(", "); + col += 2; + } + printf("--%s", frag->fops[i].str); + col += 2 + strlen(frag->fops[i].str); + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG + || (frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + putchar('['); + col++; + } + if (!(frag->flags & FRAG_DISABLE_EQUALS_LONG)) + putchar('='); + else + putchar(' '); + col++; + if (frag->fops[i].arg != NULL) { + printf("%s", frag->fops[i].arg); + col += strlen(frag->fops[i].arg); + } else { + printf("VALUE"); + col += 5; + } + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + putchar(']'); + col++; + } + } else if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG) { + printf(", --no-%s", frag->fops[i].str); + col += 5 + strlen(frag->fops[i].str); + } + + return col; +} + +int _frag_print_wrapped(const char * str, int indent, int col) +{ + int i, last, ahead, j; + + for (i = 0, last = 0, ahead = 0; str[last] != '\0'; ahead++) { + if (str[ahead] == ' ' || str[ahead] == '\t' + || str[ahead] == '\n' || str[ahead] == '\0') { + if (!(col + (ahead - last) < 80)) { + if (col != indent) { + putchar('\n'); + for (j = 0; j < indent; j++) + putchar(' '); + col = indent; + } + while (str[last] == ' ') + last++; /* skip the whitespace */ + } + for (i = 0; i < ahead - last; i++) { + putchar(str[last + i]); + if (!(col < 80)) { + putchar('\n'); + for (j = 0; j < indent; j++) + putchar(' '); + col = indent; + } + if (str[last + i] == '\t') + col += 8 - (col % 8); + else if (str[last + i] == '\n') { + for (j = 0; j < indent; j++) + putchar(' '); + col = indent; + } else + col++; + } + last = ahead; + } + } + + return col; +} + +const char * frag_err(FRAG * frag) +{ + switch (frag->id) { + case FRAG_ERR_UFO: + return "unidentified option"; + case FRAG_ERR_BAREWORD: + return "unexpected bareword"; + case FRAG_ERR_CLUSTER: + return "invalid cluster of short options"; + case FRAG_ERR_ORDER: + return "invalid ordering of options"; + case FRAG_ERR_UNWANTED_ARG: + return "argument given for an option that doesn't take one"; + case FRAG_ERR_ARG_MISSING: + return "mandatory argument missing"; + case FRAG_ERR_SYNTAX: + return "syntax error"; + default: + return "unrecognized error"; + } +} + +void _frag_print_error(FRAG * frag) +{ + fprintf(stderr, "error: "); + switch (frag->id) { + case FRAG_ERR_UFO: + if (frag->chr) { + if (frag->argv[frag->index][0] == '-') { + fprintf(stderr, "unidentified short option \"-%c\"", + frag->argv[frag->index][frag->chr]); + if (frag->chr != 1 + || frag->argv[frag->index][frag->chr + 1] != '\0') + fprintf(stderr, " in cluster \"%s\"", + frag->argv[frag->index]); + } else { + fprintf(stderr, "unidentified negation option \"+%c\"", + frag->argv[frag->index][frag->chr]); + if (frag->chr != 1 + || frag->argv[frag->index][frag->chr + 1] != '\0') + fprintf(stderr, " in cluster \"%s\"", + frag->argv[frag->index]); + } + fputc('\n', stderr); + } else + fprintf(stderr, "unidentified long option %s\n", + frag->argv[frag->index]); + return; + case FRAG_ERR_BAREWORD: + fprintf(stderr, "unexpected bareword \"%s\"\n", frag->argv[frag->index]); + return; + case FRAG_ERR_CLUSTER: + fprintf(stderr, "invalid cluster of short options in \"%s\"\n", + frag->argv[frag->index]); + return; + case FRAG_ERR_ORDER: + fprintf(stderr, "invalid ordering of options in \"%s %s\"\n", + frag->argv[frag->index], frag->argv[frag->index + 1]); + return; + case FRAG_ERR_UNWANTED_ARG: + fprintf(stderr, "argument given for an option that doesn't take one in " + "\"%s\"\n", frag->argv[frag->index]); + return; + case FRAG_ERR_ARG_MISSING: + fprintf(stderr, "\"%s\" is missing an argument\n", + frag->argv[frag->index]); + return; + case FRAG_ERR_SYNTAX: + fprintf(stderr, "invalid syntax in \"%s\"\n", frag->argv[frag->index]); + return; + default: + fprintf(stderr, "unidentified flying error #%d\n", frag->id); + return; + } +} + +void frag_free(FRAG * frag) /* maybe a macro would be better? nah. could break binary */ +{ /* compatibility if we need an actual function for this later */ + free(frag); +} diff --git a/schism/helptext.c b/schism/helptext.c new file mode 100644 index 000000000..2fc149a10 --- /dev/null +++ b/schism/helptext.c @@ -0,0 +1,39 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "it.h" + +#include "auto/helptext.h" /* help_text is defined here */ + +/* --------------------------------------------------------------------- */ + +char *help_text_pointers[HELP_NUM_ITEMS] = { NULL }; + +void setup_help_text_pointers(void) +{ + int n; + char *ptr = (char*)help_text; + + for (n = 0; n < HELP_NUM_ITEMS; n++) { + help_text_pointers[n] = ptr; + ptr = strchr(ptr, 0) + 1; + } +} diff --git a/schism/itf.c b/schism/itf.c new file mode 100644 index 000000000..8f9eb4880 --- /dev/null +++ b/schism/itf.c @@ -0,0 +1,1083 @@ +/* + * ITFedit - an Impulse / Schism Tracker font file editor + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* this is a little ad-hoc; i did some work trying to bring it back into CVS + LARGELY because I can't remember all the font characters. :) +*/ +#include "headers.h" +#include "it.h" +#include "dmoz.h" +#include "page.h" + +#include +#include + +static const byte itfmap_chars[] = { +128, 129, 130, ' ', 128, 129, 141, ' ', 142, 143, 144, ' ', 168, 'C', '-', '0', +131, ' ', 132, ' ', 131, ' ', 132, ' ', 145, ' ', 146, ' ', 168, 'D', '-', '1', +133, 134, 135, ' ', 140, 134, 135, ' ', 147, 148, 149, ' ', 168, 'E', '-', '2', +' ', ' ', ' ', ' ', ' ', 139, 134, 138, 153, 148, 152, ' ', 168, 'F', '-', '3', +174, ' ', ' ', ' ', 155, 132, ' ', 131, 146, ' ', 145, ' ', 168, 'G', '-', '4', +175, ' ', ' ', ' ', 156, 137, 129, 136, 151, 143, 150, ' ', 168, 'A', '-', '5', +176, ' ', ' ', ' ', 157, ' ', 184, 184, 191, '6', '4', 192, 168, 'B', '-', '6', +176, 177, ' ', ' ', 158, 163, 250, 250, 250, 250, 250, ' ', 168, 'C', '#', '7', +176, 178, ' ', ' ', 159, 164, ' ', ' ', ' ', 185, 186, ' ', 168, 'D', '#', '8', +176, 179, 180, ' ', 160, 165, ' ', ' ', ' ', 189, 190, ' ', 168, 'E', '#', '9', +176, 179, 181, ' ', 161, 166, ' ', ' ', ' ', 187, 188, ' ', 168, 'F', '#', '1', +176, 179, 182, ' ', 162, 167, 126, 126, 126, ' ', ' ', ' ', 168, 'G', '#', '2', +154, 154, 154, 154, ' ', ' ', 205, 205, 205, ' ', 183, ' ', 168, 'A', '#', '3', +169, 170, 171, 172, ' ', ' ', '^', '^', '^', ' ', 173, ' ', 168, 'B', '#', '4', +193, 194, 195, 196, 197, 198, 199, 200, 201, ' ', ' ', ' ', ' ', ' ', ' ', ' ', +}; +static const byte helptext_gen[] = + "Tab Next box \xa8 Alt-C Copy\n" + "Shift-Tab Prev. box \xa8 Alt-P Paste\n" + "F2-F4 Switch box \xa8 Alt-M Mix paste\n" + "\x18\x19\x1a\x1b Dump core \xa8 Alt-Z Clear\n" + "Ctrl-S/F10 Save font \xa8 Alt-H Flip horiz\n" + "Ctrl-R/F9 Load font \xa8 Alt-V Flip vert\n" + "Backspace Reset font \xa8 Alt-I Invert\n" + "Ctrl-Bksp BIOS font \xa8 Alt-Bk Reset text\n" + " \xa8 0-9 Palette\n" + "Ctrl-Q Exit \xa8 (+10 with shift)\n"; + +static const byte helptext_editbox[] = +"Space Plot/clear point\n" +"Ins/Del Fill/clear horiz.\n" +"...w/Shift Fill/clear vert.\n" +"\n" +"+/- Next/prev. char.\n" +"PgUp/PgDn Next/previous row\n" +"Home/End Top/bottom corner\n" +"\n" "Shift-\x18\x19\x1a\x1b Shift character\n" +"[/] Rotate 90\xf8\n"; + +static const byte helptext_charmap[] = +"Home/End First/last char.\n"; + +static const byte helptext_fontlist[] = +"Home/End First/last font\n" +"Enter Load/save file\n" +"Escape Hide font list\n" +"\n\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" +"\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\n\n" +"Remember to save as font.cfg\n" +"to change the default font!\n"; + +/* --------------------------------------------------------------------- */ +/* statics & local constants +note: x/y are for the top left corner of the frame, but w/h define the size of its *contents* */ + +#define EDITBOX_X 0 +#define EDITBOX_Y 0 +#define EDITBOX_W 9 +#define EDITBOX_H 11 + +#define CHARMAP_X 17 +#define CHARMAP_Y 0 +#define CHARMAP_W 16 +#define CHARMAP_H 16 + +#define ITFMAP_X 41 +#define ITFMAP_Y 0 +#define ITFMAP_W 16 +#define ITFMAP_H 15 + +#define FONTLIST_X 65 +#define FONTLIST_Y 0 +#define VISIBLE_FONTS 22 /* this should be called FONTLIST_H... */ + +#define HELPTEXT_X 0 +#define HELPTEXT_Y 31 + +/* don't randomly mess with these for obvious reasons */ +#define INNER_X(x) ((x) + 3) +#define INNER_Y(y) ((y) + 4) + +#define FRAME_RIGHT 3 +#define FRAME_BOTTOM 3 + +#define WITHIN(n,l,u) ((n) >= (l) && (n) < (u)) +#define POINT_IN(x,y,item) \ + (WITHIN((x), INNER_X(item##_X), INNER_X(item##_X) + item##_W) \ + && WITHIN((y), INNER_Y(item##_Y), INNER_Y(item##_Y) + item##_H)) +#define POINT_IN_FRAME(x,y,item) \ + (WITHIN((x), item##_X, INNER_X(item##_X) + item##_W + FRAME_RIGHT) \ + && WITHIN((y), item##_Y, INNER_Y(item##_Y) + item##_H + FRAME_BOTTOM)) + +static int edit_x = 3, edit_y = 3; +static byte current_char = 'A'; +static int itfmap_pos = -1; + +static enum { + EDITBOX, CHARMAP, ITFMAP, FONTLIST +} selected_item = EDITBOX; + +static enum { + MODE_OFF, MODE_LOAD, MODE_SAVE +} fontlist_mode = MODE_OFF; + +static dmoz_filelist_t flist; +int top_font = 0, cur_font = 0; + + +static void fontlist_reposition(void) +{ + if (cur_font < top_font) + top_font = cur_font; + else if (cur_font > top_font + (VISIBLE_FONTS - 1)) + top_font = cur_font - (VISIBLE_FONTS - 1); +} + +static int fontgrep(dmoz_file_t *f) +{ + if (f && f->base && strcmp(f->base, "font.cfg") == 0) return 0; + if (f->type & TYPE_BROWSABLE_MASK) return 0; + return 1; +} +static void load_fontlist(void) +{ + char *q, *p; + struct stat st; + + dmoz_free(&flist, NULL); + + top_font = cur_font = 0; + + q = dmoz_path_concat_len(cfg_dir_dotschism, "fonts", + strlen(cfg_dir_dotschism), + 5); + (void)mkdir(q, 0755); + p = dmoz_path_concat_len(q, "font.cfg", + strlen(q), + 8); + memset(&st, 0, sizeof(st)); + if (dmoz_read(q, &flist, NULL) < 0) { + perror("schism fonts"); + } + (void)free(q); + // fart... + dmoz_filter_filelist(&flist, fontgrep, &cur_font, fontlist_reposition); + while (dmoz_worker()); + + dmoz_add_file(&flist, p, strdup("font.cfg"), &st, -1024); + /* p is freed by dmoz_free */ +} + + + +static byte clipboard[8] = { 0 }; + +#define INCR_WRAPPED(n) (((n) & 0xf0) | (((n) + 1) & 0xf)) +#define DECR_WRAPPED(n) (((n) & 0xf0) | (((n) - 1) & 0xf)) + +/* if this is nonzero, the screen will be redrawn. none of the functions + * except main should call draw_anything -- set this instead. */ +static void draw_frame(const byte * name, int x, int y, int inner_width, int inner_height, int active) +{ + int n, c; + int len = strlen((char*)name); + + if (len > inner_width + 2) + len = inner_width + 2; + c = (status.flags & INVERTED_PALETTE) ? 1 : 3; + + draw_box(x, y + 1, x + inner_width + 5, + y + inner_height + 6, BOX_THIN | BOX_CORNER | BOX_OUTSET); + draw_box(x + 1, y + 2, x + inner_width + 4, + y + inner_height + 5, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_char(128, x, y, c, 2); + for (n = 0; n < len + 1; n++) + draw_char(129, x + n + 1, y, c, 2); + draw_char(130, x + n, y, c, 2); + draw_char(131, x, y + 1, c, 2); + draw_char(137, x + len + 1, y + 1, c, 2); + + switch (active) { + case 0: /* inactive */ + n = 0; + break; + case -1: /* disabled */ + n = 1; + break; + default: /* active */ + n = 3; + break; + } + draw_text_len(name, len, x + 1, y + 1, n, 2); +} + +/* --------------------------------------------------------------------- */ + +static inline void draw_editbox(void) +{ + int c; + char buf[12]; + int ci = current_char << 3, i, j, fg; + + for (i = 0; i < 8; i++) { + draw_char('1' + i, INNER_X(EDITBOX_X) + i + 1, + INNER_Y(EDITBOX_Y) + 2, (i == edit_x ? 3 : 1), 0); + draw_char('1' + i, INNER_X(EDITBOX_X), + INNER_Y(EDITBOX_Y) + i + 3, (i == edit_y ? 3 : 1), 0); + + for (j = 0; j < 8; j++) { + if (font_data[ci + j] & (128 >> i)) { + c = 15; + fg = 6; + } else { + c = 173; + fg = 1; + } + if (selected_item == EDITBOX && i == edit_x && j == edit_y) + draw_char(c, INNER_X(EDITBOX_X) + 1 + i, + INNER_Y(EDITBOX_Y) + 3 + j, 0, 3); + else + draw_char(c, INNER_X(EDITBOX_X) + 1 + i, + INNER_Y(EDITBOX_Y) + 3 + j, fg, 0); + } + } + draw_char(current_char, INNER_X(EDITBOX_X), INNER_Y(EDITBOX_Y), 5, 0); + + sprintf(buf, "%3d $%02X", current_char, current_char); + draw_text(buf, INNER_X(EDITBOX_X) + 2, INNER_Y(EDITBOX_Y), 5, 0); +} + +static inline void draw_charmap(void) +{ + int n = 256; + + if (selected_item == CHARMAP) { + while (n) { + n--; + draw_char(n, INNER_X(CHARMAP_X) + n % 16, INNER_Y(CHARMAP_Y) + n / 16, + (n == current_char ? 0 : 1), (n == current_char ? 3 : 0)); + } + } else { + while (n) { + n--; + draw_char(n, INNER_X(CHARMAP_X) + n % 16, INNER_Y(CHARMAP_Y) + n / 16, + (n == current_char ? 3 : 1), 0); + } + } +} + +static inline void draw_itfmap(void) +{ + int n, fg, bg; + byte *ptr; + + if (itfmap_pos < 0 || itfmap_chars[itfmap_pos] != current_char) { + ptr = strchr(itfmap_chars, current_char); + if (ptr == NULL) + itfmap_pos = -1; + else + itfmap_pos = ptr - itfmap_chars; + } + + for (n = 0; n < 240; n++) { + fg = 1; + bg = 0; + if (n == itfmap_pos) { + if (selected_item == ITFMAP) { + fg = 0; + bg = 3; + } else { + fg = 3; + } + } + draw_char(itfmap_chars[n], + INNER_X(ITFMAP_X) + n % 16, INNER_Y(ITFMAP_Y) + n / 16, fg, bg); + } +} + +static inline void draw_fontlist(void) +{ + int x, pos = 0, n = top_font, cfg, cbg; + dmoz_file_t *f; + char *ptr; + + if (selected_item == FONTLIST) { + cfg = 0; + cbg = 3; + } else { + cfg = 3; + cbg = 0; + } + + if (top_font < 0) top_font = 0; + if (n < 0) n = 0; + + while (n < flist.num_files && pos < VISIBLE_FONTS) { + x = 1; + f = flist.files[n]; + if (!f) break; + ptr = f->base; + if (n == cur_font) { + draw_char(183, INNER_X(FONTLIST_X), INNER_Y(FONTLIST_Y) + pos, cfg, cbg); + while (x < 9 && *ptr && (n == 0 || *ptr != '.')) { + draw_char(*ptr, + INNER_X(FONTLIST_X) + x, + INNER_Y(FONTLIST_Y) + pos, cfg, cbg); + x++; + ptr++; + } + while (x < 9) { + draw_char(0, + INNER_X(FONTLIST_X) + x, + INNER_Y(FONTLIST_Y) + pos, cfg, cbg); + x++; + } + } else { + draw_char(173, INNER_X(FONTLIST_X), INNER_Y(FONTLIST_Y) + pos, 2, 0); + while (x < 9 && *ptr && (n == 0 || *ptr != '.')) { + draw_char(*ptr, + INNER_X(FONTLIST_X) + x, INNER_Y(FONTLIST_Y) + pos, 5, 0); + x++; + ptr++; + } + while (x < 9) { + draw_char(0, INNER_X(FONTLIST_X) + x, INNER_Y(FONTLIST_Y) + pos, 5, 0); + x++; + } + } + n++; + pos++; + } +} + +static inline void draw_helptext(void) +{ + const byte *ptr = helptext_gen; + const byte *eol; + int line; + int column; + + for (line = INNER_Y(HELPTEXT_Y); *ptr; line++) { + eol = strchr(ptr, '\n'); + if (!eol) + eol = strchr(ptr, '\0'); + for (column = INNER_X(HELPTEXT_X); ptr < eol; ptr++, column++) + draw_char(*ptr, column, line, 12, 0); + ptr++; + } + for (line = 0; line < 10; line++) + draw_char(168, INNER_X(HELPTEXT_X) + 43, INNER_Y(HELPTEXT_Y) + line, 12, 0); + + /* context sensitive stuff... oooh :) */ + switch (selected_item) { + case EDITBOX: + ptr = helptext_editbox; + break; + case CHARMAP: + case ITFMAP: + ptr = helptext_charmap; + break; + case FONTLIST: + ptr = helptext_fontlist; + break; + } + for (line = INNER_Y(HELPTEXT_Y); *ptr; line++) { + eol = strchr(ptr, '\n'); + if (!eol) + eol = strchr(ptr, '\0'); + draw_char(168, INNER_X(HELPTEXT_X) + 43, line, 12, 0); + for (column = INNER_X(HELPTEXT_X) + 45; ptr < eol; ptr++, column++) + draw_char(*ptr, column, line, 12, 0); + ptr++; + } + draw_text("(c) 2003-2005 chisel", 57, 46, 1, 0); +} + +static inline void draw_time(void) +{ + char buf[16]; + time_t timep = 0; + struct tm local; + + time(&timep); + localtime_r(&timep, &local); + sprintf(buf, "%.2d:%.2d:%.2d", local.tm_hour, local.tm_min, local.tm_sec); + draw_text(buf, 3, 46, 1, 0); +} + +extern unsigned int color_set[16]; + +static void draw_screen(void) +{ + draw_frame("Edit Box", EDITBOX_X, EDITBOX_Y, 9, 11, !!(selected_item == EDITBOX)); + draw_editbox(); + + draw_frame("Current Font", CHARMAP_X, CHARMAP_Y, 16, 16, !!(selected_item == CHARMAP)); + draw_charmap(); + + draw_frame("Preview", ITFMAP_X, ITFMAP_Y, 16, 15, !!(selected_item == ITFMAP)); + draw_itfmap(); + + switch (fontlist_mode) { + case MODE_LOAD: + draw_frame("Load/Browse", FONTLIST_X, FONTLIST_Y, 9, + VISIBLE_FONTS, !!(selected_item == FONTLIST)); + draw_fontlist(); + break; + case MODE_SAVE: + draw_frame("Save As...", FONTLIST_X, FONTLIST_Y, 9, + VISIBLE_FONTS, !!(selected_item == FONTLIST)); + draw_fontlist(); + break; + default: /* Off? (I sure hope so!) */ + break; + } + + draw_frame("Quick Help", HELPTEXT_X, HELPTEXT_Y, 74, 12, -1); + draw_helptext(); + + draw_time(); +} +static void handle_key_editbox(struct key_event * k) +{ + byte tmp[8] = { 0 }; + int ci = current_char << 3; + int n, bit; + byte *ptr = font_data + ci; + + switch (k->sym) { + case SDLK_UP: + if (k->mod & KMOD_SHIFT) { + int s = ptr[0]; + for (n = 0; n < 7; n++) + ptr[n] = ptr[n + 1]; + ptr[7] = s; + } else { + if (--edit_y < 0) + edit_y = 7; + } + break; + case SDLK_DOWN: + if (k->mod & KMOD_SHIFT) { + int s = ptr[7]; + for (n = 7; n; n--) + ptr[n] = ptr[n - 1]; + ptr[0] = s; + } else { + edit_y = (edit_y + 1) % 8; + } + break; + case SDLK_LEFT: + if (k->mod & KMOD_SHIFT) { + for (n = 0; n < 8; n++, ptr++) + *ptr = (*ptr >> 7) | (*ptr << 1); + } else { + if (--edit_x < 0) + edit_x = 7; + } + break; + case SDLK_RIGHT: + if (k->mod & KMOD_SHIFT) { + for (n = 0; n < 8; n++, ptr++) + *ptr = (*ptr << 7) | (*ptr >> 1); + } else { + edit_x = (edit_x + 1) % 8; + } + break; + case SDLK_HOME: + edit_x = edit_y = 0; + break; + case SDLK_END: + edit_x = edit_y = 7; + break; + case SDLK_SPACE: + ptr[edit_y] ^= (128 >> edit_x); + break; + case SDLK_INSERT: + if (k->mod & KMOD_SHIFT) { + for (n = 0; n < 8; n++) + ptr[n] |= (128 >> edit_x); + } else { + ptr[edit_y] = 255; + } + break; + case SDLK_DELETE: + if (k->mod & KMOD_SHIFT) { + for (n = 0; n < 8; n++) + ptr[n] &= ~(128 >> edit_x); + } else { + ptr[edit_y] = 0; + } + break; + case SDLK_LEFTBRACKET: + for (n = 0; n < 8; n++) + for (bit = 0; bit < 8; bit++) + if (ptr[n] & (1 << bit)) + tmp[bit] |= 1 << (7 - n); + memcpy(ptr, tmp, 8); + break; + case SDLK_RIGHTBRACKET: + for (n = 0; n < 8; n++) + for (bit = 0; bit < 8; bit++) + if (ptr[n] & (1 << bit)) + tmp[7 - bit] |= 1 << n; + memcpy(ptr, tmp, 8); + break; + case SDLK_PLUS: + case SDLK_EQUALS: + current_char++; + break; + case SDLK_MINUS: + case SDLK_UNDERSCORE: + current_char--; + break; + case SDLK_PAGEUP: + current_char -= 16; + break; + case SDLK_PAGEDOWN: + current_char += 16; + break; + default: + return; + } + + status.flags |= NEED_UPDATE; +} + +static void handle_key_charmap(struct key_event * k) +{ + switch (k->sym) { + case SDLK_UP: + current_char -= 16; + break; + case SDLK_DOWN: + current_char += 16; + break; + case SDLK_LEFT: + current_char = DECR_WRAPPED(current_char); + break; + case SDLK_RIGHT: + current_char = INCR_WRAPPED(current_char); + break; + case SDLK_HOME: + current_char = 0; + break; + case SDLK_END: + current_char = 255; + break; + default: + return; + } + status.flags |= NEED_UPDATE; +} + +static void handle_key_itfmap(struct key_event * k) +{ + switch (k->sym) { + case SDLK_UP: + if (itfmap_pos < 0) { + itfmap_pos = 224; + } else { + itfmap_pos -= 16; + if (itfmap_pos < 0) + itfmap_pos += 240; + } + current_char = itfmap_chars[itfmap_pos]; + break; + case SDLK_DOWN: + if (itfmap_pos < 0) + itfmap_pos = 16; + else + itfmap_pos = (itfmap_pos + 16) % 240; + current_char = itfmap_chars[itfmap_pos]; + break; + case SDLK_LEFT: + if (itfmap_pos < 0) + itfmap_pos = 15; + else + itfmap_pos = DECR_WRAPPED(itfmap_pos); + current_char = itfmap_chars[itfmap_pos]; + break; + case SDLK_RIGHT: + if (itfmap_pos < 0) + itfmap_pos = 0; + else + itfmap_pos = INCR_WRAPPED(itfmap_pos); + current_char = itfmap_chars[itfmap_pos]; + break; + case SDLK_HOME: + current_char = itfmap_chars[0]; + itfmap_pos = 0; + break; + case SDLK_END: + current_char = itfmap_chars[239]; + itfmap_pos = 239; + break; + default: + return; + } + status.flags |= NEED_UPDATE; +} + +static void real_save_default(UNUSED void *ign) +{ + if (font_save("font.cfg") != 0) { + fprintf(stderr, "%s\n", SDL_GetError()); + return; + } + selected_item = EDITBOX; +} +static void handle_key_fontlist(struct key_event * k) +{ + int new_font = cur_font; + + switch (k->sym) { + case SDLK_HOME: + new_font = 0; + break; + case SDLK_END: + new_font = flist.num_files - 1; + break; + case SDLK_UP: + new_font--; + break; + case SDLK_DOWN: + new_font++; + break; + case SDLK_PAGEUP: + new_font -= VISIBLE_FONTS; + break; + case SDLK_PAGEDOWN: + new_font += VISIBLE_FONTS; + break; + case SDLK_ESCAPE: + selected_item = EDITBOX; + fontlist_mode = MODE_OFF; + break; + case SDLK_RETURN: + if (!k->state) return; + switch (fontlist_mode) { + case MODE_LOAD: + if (cur_font < flist.num_files + && flist.files[cur_font] + && font_load(flist.files[cur_font]->base) != 0) { + fprintf(stderr, "%s\n", SDL_GetError()); + font_reset(); + } + break; + case MODE_SAVE: + /* TODO: if cur_font != 0 (which is font.cfg), + * ask before overwriting it */ + if (cur_font < flist.num_files && flist.files[cur_font]) { + if (strcasecmp(flist.files[cur_font]->base,"font.cfg") == 0) { + if (status.flags & CLASSIC_MODE) return; + + dialog_create(DIALOG_OK_CANCEL, + "Overwriting font.cfg replaces the default font. Proceed?", + real_save_default, NULL, 1, NULL); + return; + } + if (font_save(flist.files[cur_font]->base) != 0) { + fprintf(stderr, "%s\n", SDL_GetError()); + return; + } + } + selected_item = EDITBOX; + /* fontlist_mode = MODE_OFF; */ + break; + default: + /* should never happen */ + return; + } + break; + default: + return; + } + + if (new_font != cur_font) { + new_font = CLAMP(new_font, 0, flist.num_files - 1); + if (new_font == cur_font) + return; + cur_font = new_font; + fontlist_reposition(); + } + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void handle_mouse_editbox(struct key_event *k) +{ + int n, ci = current_char << 3, xrel, yrel; + byte *ptr = font_data + ci; + + xrel = k->x - INNER_X(EDITBOX_X); + yrel = k->y - INNER_Y(EDITBOX_Y); + + if (xrel > 0 && yrel > 2) { + edit_x = xrel - 1; + edit_y = yrel - 3; + switch (k->mouse_button) { + case MOUSE_BUTTON_LEFT: /* set */ + ptr[edit_y] |= (128 >> edit_x); + break; + case MOUSE_BUTTON_MIDDLE: /* invert */ + if (k->state) return; + ptr[edit_y] ^= (128 >> edit_x); + break; + case MOUSE_BUTTON_RIGHT: /* clear */ + ptr[edit_y] &= ~(128 >> edit_x); + break; + } + } else if (xrel == 0 && yrel == 2) { + /* clicking at the origin modifies the entire character */ + switch (k->mouse_button) { + case MOUSE_BUTTON_LEFT: /* set */ + for (n = 0; n < 8; n++) + ptr[n] = 255; + break; + case MOUSE_BUTTON_MIDDLE: /* invert */ + if (k->state) return; + for (n = 0; n < 8; n++) + ptr[n] ^= 255; + break; + case MOUSE_BUTTON_RIGHT: /* clear */ + for (n = 0; n < 8; n++) + ptr[n] = 0; + break; + } + } else if (xrel == 0 && yrel > 2) { + edit_y = yrel - 3; + switch (k->mouse_button) { + case MOUSE_BUTTON_LEFT: /* set */ + ptr[edit_y] = 255; + break; + case MOUSE_BUTTON_MIDDLE: /* invert */ + if (k->state) return; + ptr[edit_y] ^= 255; + break; + case MOUSE_BUTTON_RIGHT: /* clear */ + ptr[edit_y] = 0; + break; + } + } else if (yrel == 2 && xrel > 0) { + edit_x = xrel - 1; + switch (k->mouse_button) { + case MOUSE_BUTTON_LEFT: /* set */ + for (n = 0; n < 8; n++) + ptr[n] |= (128 >> edit_x); + break; + case MOUSE_BUTTON_MIDDLE: /* invert */ + if (k->state) return; + for (n = 0; n < 8; n++) + ptr[n] ^= (128 >> edit_x); + break; + case MOUSE_BUTTON_RIGHT: /* clear */ + for (n = 0; n < 8; n++) + ptr[n] &= ~(128 >> edit_x); + break; + } + } +} + +static void handle_mouse_charmap(struct key_event *k) +{ + int xrel = k->x - INNER_X(CHARMAP_X), yrel = k->y - INNER_Y(CHARMAP_Y); + if (!k->mouse) return; + current_char = 16 * yrel + xrel; +} + +static void handle_mouse_itfmap(struct key_event *k) +{ + int xrel = k->x - INNER_X(ITFMAP_X), yrel = k->y - INNER_Y(ITFMAP_Y); + if (!k->mouse) return; + itfmap_pos = 16 * yrel + xrel; + current_char = itfmap_chars[itfmap_pos]; +} + +static void handle_mouse(struct key_event * k) +{ + int x = k->x, y = k->y; + if (POINT_IN_FRAME(x, y, EDITBOX)) { + selected_item = EDITBOX; + if (POINT_IN(x, y, EDITBOX)) + handle_mouse_editbox(k); + } else if (POINT_IN_FRAME(x, y, CHARMAP)) { + selected_item = CHARMAP; + if (POINT_IN(x, y, CHARMAP)) + handle_mouse_charmap(k); + } else if (POINT_IN_FRAME(x, y, ITFMAP)) { + selected_item = ITFMAP; + if (POINT_IN(x, y, ITFMAP)) + handle_mouse_itfmap(k); + } else { + //printf("stray click\n"); + return; + } + status.flags |= NEED_UPDATE; +} + + +int fontedit_handle_key(struct key_event * k) +{ + int n, ci = current_char << 3; + byte *ptr = font_data + ci; + + if (k->mouse == MOUSE_SCROLL_UP || k->mouse == MOUSE_SCROLL_DOWN) { + /* err... */ + return 0; + } + + if (k->mouse == MOUSE_CLICK) { + handle_mouse(k); + return 1; + } + + /* kp is special */ + switch (k->orig_sym) { + case SDLK_KP0: + if (k->state) return 1; + k->sym += 10; + /* fall through */ + case SDLK_KP1...SDLK_KP9: + if (k->state) return 1; + n = k->sym - SDLK_KP1; + if (k->mod & KMOD_SHIFT) + n += 10; + palette_load_preset(n); + palette_apply(); + status.flags |= NEED_UPDATE; + return 1; + }; + + switch (k->sym) { + case '0': + if (k->state) return 1; + k->sym += 10; + /* fall through */ + case '1'...'9': + if (k->state) return 1; + n = k->sym - '1'; + if (k->mod & KMOD_SHIFT) + n += 10; + palette_load_preset(n); + palette_apply(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_F2: + if (k->state) return 1; + selected_item = EDITBOX; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_F3: + if (k->state) return 1; + selected_item = CHARMAP; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_F4: + if (k->state) return 1; + selected_item = ITFMAP; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_TAB: + if (k->state) return 1; + if (k->mod & KMOD_SHIFT) { + if (selected_item == 0) + selected_item = (fontlist_mode == MODE_OFF ? 2 : 3); + else + selected_item--; + } else { + selected_item = (selected_item + 1) % (fontlist_mode == MODE_OFF ? 3 : 4); + } + status.flags |= NEED_UPDATE; + return 1; + case SDLK_c: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + memcpy(clipboard, ptr, 8); + return 1; + } + break; + case SDLK_p: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + memcpy(ptr, clipboard, 8); + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_m: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) { + SDL_ToggleCursor(); + return 1; + } else if (k->mod & KMOD_ALT) { + for (n = 0; n < 8; n++) + ptr[n] |= clipboard[n]; + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_z: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + memset(ptr, 0, 8); + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_h: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + for (n = 0; n < 8; n++) { + int r = ptr[n]; + r = ((r >> 1) & 0x55) | ((r << 1) & 0xaa); + r = ((r >> 2) & 0x33) | ((r << 2) & 0xcc); + r = ((r >> 4) & 0x0f) | ((r << 4) & 0xf0); + ptr[n] = r; + } + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_v: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + for (n = 0; n < 4; n++) { + byte r = ptr[n]; + ptr[n] = ptr[7 - n]; + ptr[7 - n] = r; + } + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_i: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + for (n = 0; n < 8; n++) + font_data[ci + n] ^= 255; + status.flags |= NEED_UPDATE; + return 1; + } + break; + + /* ----------------------------------------------------- */ + + case SDLK_l: + case SDLK_r: + if (k->state) return 1; + if (!(k->mod & KMOD_CTRL)) break; + /* fall through */ + case SDLK_F9: + if (k->state) return 1; + load_fontlist(); + fontlist_mode = MODE_LOAD; + selected_item = FONTLIST; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_s: + if (k->state) return 1; + if (!(k->mod & KMOD_CTRL)) break; + /* fall through */ + case SDLK_F10: + /* a bit weird, but this ensures that font.cfg + * is always the default font to save to, but + * without the annoyance of moving the cursor + * back to it every time f10 is pressed. */ + if (fontlist_mode != MODE_SAVE) { + cur_font = top_font = 0; + load_fontlist(); + fontlist_mode = MODE_SAVE; + } + selected_item = FONTLIST; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_BACKSPACE: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) { + font_reset_bios(); + } else if (k->mod & KMOD_ALT) { + font_reset_char(current_char); + } else { + font_reset_upper(); + } + status.flags |= NEED_UPDATE; + return 1; + case SDLK_RETURN: + return 0; + case SDLK_q: + if (k->mod & KMOD_CTRL) + return 0; + if (k->state) return 1; + break; + default: + if (k->state) return 1; + break; + } + + switch (selected_item) { + case EDITBOX: + handle_key_editbox(k); + break; + case CHARMAP: + handle_key_charmap(k); + break; + case ITFMAP: + handle_key_itfmap(k); + break; + case FONTLIST: + handle_key_fontlist(k); + break; + default: + break; + } + return 1; +} + + +static struct widget fontedit_widget_hack[1]; + +static int fontedit_key_hack(struct key_event *k) +{ + switch (k->sym) { + case SDLK_r: case SDLK_l: case SDLK_s: + case SDLK_c: case SDLK_p: case SDLK_m: + case SDLK_z: case SDLK_v: case SDLK_h: + case SDLK_i: case SDLK_q: case SDLK_w: + case SDLK_F1...SDLK_F12: + return fontedit_handle_key(k); + case SDLK_RETURN: + if (status.dialog_type & (DIALOG_MENU|DIALOG_BOX)) return 0; + if (selected_item == FONTLIST) { + handle_key_fontlist(k); + return 1; + } + }; + return 0; +} + +static void do_nil(void) {} +void fontedit_load_page(struct page *page) +{ + page->title = ""; + page->draw_full = draw_screen; + page->total_widgets = 1; + page->pre_handle_key = fontedit_key_hack; + page->widgets = fontedit_widget_hack; + create_other(fontedit_widget_hack, 0, fontedit_handle_key, do_nil); +} diff --git a/schism/keyboard.c b/schism/keyboard.c new file mode 100644 index 000000000..c92033d57 --- /dev/null +++ b/schism/keyboard.c @@ -0,0 +1,463 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include +#include + +/* --------------------------------------------------------------------- */ +static const char **note_names, *note_names_short; + +static const char *note_names_up[12] = { + "C-", "C#", "D-", "D#", "E-", "F-", + "F#", "G-", "G#", "A-", "A#", "B-" +}; + +static const char note_names_short_up[12] = "cCdDefFgGaAb"; + +static const char *note_names_down[12] = { + "C-", "Db", "D-", "Eb", "E-", "F-", + "Gb", "G-", "Ab", "A-", "Bb", "B-" +}; + +static const char note_names_short_down[12] = "CdDeEFgGaAbB"; + +/* --------------------------------------------------------------------- */ + +static int current_octave = 4; + +/* --------------------------------------------------------------------- */ + +/* this is used in a couple of other places... maybe it should be in some + * general-stuff file? */ +const char hexdigits[16] = "0123456789ABCDEF"; + +/* extra non-IT effects: + * '!' = volume '#' = modcmdex '$' = keyoff + * '%' = xfineportaupdown '&' = setenvposition */ +static const char effects[33] = ".JFEGHLKRXODB!CQATI#SMNVW$UY%P&Z"; + +/* --------------------------------------------------------------------- */ + +void kbd_init(void) +{ + note_names = note_names_up; + note_names_short = note_names_short_up; +} +void kbd_digitrakker_voodoo(int e) +{ + if (e == -1) { + if (note_names == note_names_up) { + kbd_digitrakker_voodoo(1); + } else { + kbd_digitrakker_voodoo(0); + } + } else if (e == 1) { + status.flags |= DIGITRAKKER_VOODOO; + status_text_flash("DigiTrakker voodoo enabled"); + note_names = note_names_down; + note_names_short = note_names_short_down; + } else if (e == 0) { + status.flags &= ~DIGITRAKKER_VOODOO; + status_text_flash("DigiTrakker voodoo disabled"); + note_names = note_names_up; + note_names_short = note_names_short_up; + } +} + +char get_effect_char(int effect) +{ + if (effect < 0 || effect > 31) { + log_appendf(4, "get_effect_char: effect %d out of range", + effect); + return '?'; + } + return effects[effect]; +} + +int get_effect_number(char effect) +{ + const char *ptr; + + if (effect >= 'a' && effect <= 'z') { + effect -= 32; + } else if (!((effect >= '0' && effect <= '9') + || (effect >= 'A' && effect <= 'Z') + || (effect == '.'))) { + /* don't accept pseudo-effects */ + if (status.flags & CLASSIC_MODE) return -1; + } + + ptr = strchr(effects, effect); + return ptr ? ptr - effects : -1; +} +int kbd_get_effect_number(struct key_event *k) +{ + if (!NO_CAM_MODS(k->mod)) return -1; + switch (k->sym) { +#define QZA(n) case SDLK_ ## n : return get_effect_number(#n [0]) +QZA(a);QZA(b);QZA(c);QZA(d);QZA(e);QZA(f);QZA(g);QZA(h);QZA(i);QZA(j);QZA(k); +QZA(l);QZA(m);QZA(n);QZA(o);QZA(p);QZA(q);QZA(r);QZA(s);QZA(t);QZA(u);QZA(v); +QZA(w);QZA(x);QZA(y);QZA(z); +#undef QZA + case SDLK_PERIOD: case SDLK_KP_PERIOD: + return get_effect_number('.'); + case SDLK_3: + if (!(k->mod & KMOD_SHIFT)) return -1; + case SDLK_HASH: + return get_effect_number('#'); + case SDLK_1: + if (!(k->mod & KMOD_SHIFT)) return -1; + case SDLK_EXCLAIM: + return get_effect_number('!'); + case SDLK_4: + if (!(k->mod & KMOD_SHIFT)) return -1; + case SDLK_DOLLAR: + return get_effect_number('$'); + case SDLK_5: + if (!(k->mod & KMOD_SHIFT)) return -1; + return get_effect_number('%'); + case SDLK_7: + if (!(k->mod & KMOD_SHIFT)) return -1; + case SDLK_AMPERSAND: + return get_effect_number('&'); + + default: + return -1; + }; +} + +/* --------------------------------------------------------------------- */ + +void key_translate(struct key_event *k) +{ + k->orig_sym = k->sym; + if (k->mod & KMOD_SHIFT) { + switch (k->sym) { + case SDLK_COMMA: k->sym = SDLK_LESS; k->mod &= ~KMOD_SHIFT; break; + case SDLK_PERIOD: k->sym = SDLK_GREATER; k->mod &= ~KMOD_SHIFT; break; + case SDLK_4: k->sym = SDLK_DOLLAR; k->mod &= ~KMOD_SHIFT; break; + + case SDLK_EQUALS: k->sym = SDLK_PLUS; k->mod &= ~KMOD_SHIFT; break; + case SDLK_SEMICOLON: k->sym = SDLK_COLON; k->mod &= ~KMOD_SHIFT; break; + }; + } + if (k->mod & KMOD_META) { + k->mod |= KMOD_ALT; + k->mod &= ~KMOD_META; + } + if (k->mod & KMOD_NUM) { + switch (k->sym) { + case SDLK_KP0: k->sym = SDLK_0; k->mod &= ~KMOD_NUM; break; + case SDLK_KP1: k->sym = SDLK_1; k->mod &= ~KMOD_NUM; break; + case SDLK_KP2: k->sym = SDLK_2; k->mod &= ~KMOD_NUM; break; + case SDLK_KP3: k->sym = SDLK_3; k->mod &= ~KMOD_NUM; break; + case SDLK_KP4: k->sym = SDLK_4; k->mod &= ~KMOD_NUM; break; + case SDLK_KP5: k->sym = SDLK_5; k->mod &= ~KMOD_NUM; break; + case SDLK_KP6: k->sym = SDLK_6; k->mod &= ~KMOD_NUM; break; + case SDLK_KP7: k->sym = SDLK_7; k->mod &= ~KMOD_NUM; break; + case SDLK_KP8: k->sym = SDLK_8; k->mod &= ~KMOD_NUM; break; + case SDLK_KP9: k->sym = SDLK_9; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_PERIOD: k->sym = SDLK_PERIOD; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_DIVIDE: k->sym = SDLK_SLASH; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_MULTIPLY: k->sym = SDLK_ASTERISK; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_MINUS: k->sym = SDLK_MINUS; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_PLUS: k->sym = SDLK_PLUS; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_ENTER: k->sym = SDLK_RETURN; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_EQUALS: k->sym = SDLK_EQUALS; k->mod &= ~KMOD_NUM; break; + }; + } else { + switch (k->sym) { + case SDLK_KP0: k->sym = SDLK_INSERT; break; + case SDLK_KP4: k->sym = SDLK_LEFT; break; + case SDLK_KP6: k->sym = SDLK_RIGHT; break; + case SDLK_KP2: k->sym = SDLK_DOWN; break; + case SDLK_KP8: k->sym = SDLK_UP; break; + + case SDLK_KP9: k->sym = SDLK_PAGEUP; break; + case SDLK_KP3: k->sym = SDLK_PAGEDOWN; break; + + case SDLK_KP7: k->sym = SDLK_HOME; break; + case SDLK_KP1: k->sym = SDLK_END; break; + + case SDLK_KP_PERIOD: k->sym = SDLK_DELETE; break; + + case SDLK_KP_DIVIDE: k->sym = SDLK_SLASH; break; + case SDLK_KP_MULTIPLY: k->sym = SDLK_ASTERISK; break; + case SDLK_KP_MINUS: k->sym = SDLK_MINUS; break; + case SDLK_KP_PLUS: k->sym = SDLK_PLUS; break; + case SDLK_KP_ENTER: k->sym = SDLK_RETURN; break; + case SDLK_KP_EQUALS: k->sym = SDLK_EQUALS; break; + + }; + } +} + +int numeric_key_event(struct key_event *k) +{ + switch (k->sym) { + case SDLK_0: case SDLK_KP0: return 0; + case SDLK_1: case SDLK_KP1: return 1; + case SDLK_2: case SDLK_KP2: return 2; + case SDLK_3: case SDLK_KP3: return 3; + case SDLK_4: case SDLK_KP4: return 4; + case SDLK_5: case SDLK_KP5: return 5; + case SDLK_6: case SDLK_KP6: return 6; + case SDLK_7: case SDLK_KP7: return 7; + case SDLK_8: case SDLK_KP8: return 8; + case SDLK_9: case SDLK_KP9: return 9; + }; + return -1; +} + + +/* --------------------------------------------------------------------- */ + +char *get_volume_string(int volume, int volume_effect, char *buf) +{ + const char cmd_table[16] = "...CDAB$H<>GFE"; + + buf[2] = 0; + + if (volume_effect < 0 || volume_effect > 13) { + log_appendf(4, "get_volume_string: volume effect %d out" + " of range", volume_effect); + buf[0] = buf[1] = '?'; + return buf; + } + + /* '$'=vibratospeed, '<'=panslideleft, '>'=panslideright */ + switch (volume_effect) { + case VOL_EFFECT_NONE: + buf[0] = buf[1] = 173; + break; + case VOL_EFFECT_VOLUME: + case VOL_EFFECT_PANNING: + /* Yeah, a bit confusing :) + * The display stuff makes the distinction here with + * a different color for panning. */ + numtostr(2, volume, buf); + break; + default: + buf[0] = cmd_table[volume_effect]; + buf[1] = hexdigits[volume]; + break; + } + + return buf; +} + +/* --------------------------------------------------------------------- */ + +char *get_note_string(int note, char *buf) +{ +#ifndef NDEBUG + if ((note < 0 || note > 120) + && !(note == NOTE_CUT + || note == NOTE_OFF || note == NOTE_FADE)) { + log_appendf(4, "Note %d out of range", note); + buf[0] = buf[1] = buf[2] = '?'; + buf[3] = 0; + return buf; + } +#endif + + switch (note) { + case 0: /* nothing */ + buf[0] = buf[1] = buf[2] = 173; + break; + case NOTE_CUT: + buf[0] = buf[1] = buf[2] = 94; + break; + case NOTE_OFF: + buf[0] = buf[1] = buf[2] = 205; + break; + case NOTE_FADE: + /* this is sure to look "out of place" to anyone */ + buf[0] = 185; + buf[1] = 186; + buf[2] = 185; + break; + default: + note--; + snprintf(buf, 4, "%.2s%.1d", note_names[note % 12], + note / 12); + } + buf[3] = 0; + return buf; +} + +char *get_note_string_short(int note, char *buf) +{ +#ifndef NDEBUG + if ((note < 0 || note > 120) + && !(note == NOTE_CUT + || note == NOTE_OFF || note == NOTE_FADE)) { + log_appendf(4, "Note %d out of range", note); + buf[0] = buf[1] = '?'; + buf[2] = 0; + return buf; + } +#endif + + switch (note) { + case 0: /* nothing */ + buf[0] = buf[1] = 173; + break; + case NOTE_CUT: + buf[0] = buf[1] = 94; + break; + case NOTE_OFF: + buf[0] = buf[1] = 205; + break; + case NOTE_FADE: + buf[0] = buf[1] = 126; + break; + default: + note--; + buf[0] = note_names_short[note % 12]; + buf[1] = note / 12 + '0'; + } + buf[2] = 0; + return buf; +} + +/* --------------------------------------------------------------------- */ + +int kbd_get_current_octave() +{ + return current_octave; +} + +void kbd_set_current_octave(int new_octave) +{ + new_octave = CLAMP(new_octave, 0, 8); + current_octave = new_octave; + + /* a full screen update for one lousy letter... */ + status.flags |= NEED_UPDATE; +} + +inline int kbd_char_to_hex(struct key_event *k) +{ + if (!NO_CAM_MODS(k->mod)) return -1; + switch (k->sym) { + case SDLK_KP0: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_0: return 0; + case SDLK_KP1: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_1: return 1; + case SDLK_KP2: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_2: return 2; + case SDLK_KP3: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_3: return 3; + case SDLK_KP4: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_4: return 4; + case SDLK_KP5: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_5: return 5; + case SDLK_KP6: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_6: return 6; + case SDLK_KP7: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_7: return 7; + case SDLK_KP8: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_8: return 8; + case SDLK_KP9: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_9: return 9; + case SDLK_a: return 10; + case SDLK_b: return 11; + case SDLK_c: return 12; + case SDLK_d: return 13; + case SDLK_e: return 14; + case SDLK_f: return 15; + default: + return -1; + }; +} + +/* --------------------------------------------------------------------- */ + +/* return values: + * < 0 = invalid note + * 0 = clear field ('.' in qwerty) + * 1-120 = note + * NOTE_CUT = cut ("^^^") + * NOTE_OFF = off ("===") + * NOTE_FADE = fade ("~~~") + * i haven't really decided on how to display this. + * and for you people who might say 'hey, IT doesn't do that': + * yes it does. read the documentation. it's not in the editor, + * but it's in the player. */ +inline int kbd_get_note(struct key_event *k) +{ + int note; + + if (!NO_CAM_MODS(k->mod)) return -1; + switch (k->sym) { + case SDLK_BACKQUOTE: + if (k->mod & KMOD_SHIFT) return NOTE_FADE; + return NOTE_OFF; + case SDLK_KP1: + if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_1: + return NOTE_CUT; + case SDLK_KP_PERIOD: + if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_PERIOD: + return 0; /* clear */ + case SDLK_z: note = 1; break; + case SDLK_s: note = 2; break; + case SDLK_x: note = 3; break; + case SDLK_d: note = 4; break; + case SDLK_c: note = 5; break; + case SDLK_v: note = 6; break; + case SDLK_g: note = 7; break; + case SDLK_b: note = 8; break; + case SDLK_h: note = 9; break; + case SDLK_n: note = 10; break; + case SDLK_j: note = 11; break; + case SDLK_m: note = 12; break; + + case SDLK_q: note = 13; break; + case SDLK_2: note = 14; break; + case SDLK_w: note = 15; break; + case SDLK_3: note = 16; break; + case SDLK_e: note = 17; break; + case SDLK_r: note = 18; break; + case SDLK_5: note = 19; break; + case SDLK_t: note = 20; break; + case SDLK_6: note = 21; break; + case SDLK_y: note = 22; break; + case SDLK_7: note = 23; break; + case SDLK_u: note = 24; break; + case SDLK_i: note = 25; break; + case SDLK_9: note = 26; break; + case SDLK_o: note = 27; break; + case SDLK_0: note = 28; break; + case SDLK_p: note = 29; break; + + default: return -1; + }; + note += (12 * current_octave); + return CLAMP(note, 1, 120); +} diff --git a/schism/main.c b/schism/main.c new file mode 100644 index 000000000..c36d08228 --- /dev/null +++ b/schism/main.c @@ -0,0 +1,820 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is the first thing I *ever* did with SDL. :) */ + +#include "headers.h" + +#include "event.h" + +#include "clippy.h" + +#include "song.h" +#include "dmoz.h" +#include "frag-opt.h" + +#include + +#if HAVE_SYS_KD_H +# include +#endif +#if HAVE_LINUX_FB_H +# include +#endif +#include +#include +#if HAVE_SYS_IOCTL_H +# include +#endif + +#include +#include +#include +#include +#include + +#ifdef USE_DLTRICK_ALSA +#include +void *_dltrick_handle = 0; +static void *_alsaless_sdl_hack = 0; +#endif + +#if !defined(WIN32) && !defined(__amigaos4__) +# define ENABLE_HOOKS 1 +#endif + +#define NATIVE_SCREEN_WIDTH 640 +#define NATIVE_SCREEN_HEIGHT 400 + +#include "diskwriter.h" + + +/* --------------------------------------------------------------------- */ +/* globals */ +static int shutdown_process = 0; +static const char *video_driver = 0; +static const char *audio_driver = 0; +static int did_fullscreen = 0; + +/* ugly hack... */ +void (*shift_release)(void) = NULL; + +/* --------------------------------------------------------------------- */ +/* stuff SDL should already be doing but isn't */ + +#if HAVE_SYS_KD_H +static byte console_font[512 * 32]; +static int font_saved = 0; +static void save_font(void) +{ + int t = open("/dev/tty", O_RDONLY); + if (t < 0) + return; + if (ioctl(t, GIO_FONT, &console_font) >= 0) + font_saved = 1; + close(t); +} +static void restore_font(void) +{ + int t; + + if (!font_saved) + return; + t = open("/dev/tty", O_RDONLY); + if (t < 0) + return; + if (ioctl(t, PIO_FONT, &console_font) < 0) + perror("set font"); + close(t); +} +#else +static void save_font(void) +{ +} +static void restore_font(void) +{ +} +#endif + +/* --------------------------------------------------------------------- */ + +static void display_print_info(void) +{ + char buf[256]; + + log_append(2, 0, "Video initialised"); + log_append(2, 0, "\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81"); + video_report(); + log_append(0, 0, ""); + log_append(0, 0, ""); +} + +/* If we're not not debugging, don't not dump core. (Have I ever mentioned + * that NDEBUG is poorly named -- or that identifiers for settings in the + * negative form are a bad idea?) */ +#if defined(NDEBUG) +# define SDL_INIT_FLAGS SDL_INIT_TIMER | SDL_INIT_VIDEO +#else +# define SDL_INIT_FLAGS SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE +#endif + +static void display_init(void) +{ + if (SDL_Init(SDL_INIT_FLAGS) < 0) { + fprintf(stderr, "init test %s\n", SDL_GetError()); + exit(1); + } + shutdown_process |= 16; + + video_init(video_driver); + + if (SDL_GetVideoInfo()->wm_available) { + status.flags |= WM_AVAILABLE; + } + + clippy_init(); + + display_print_info(); +#if 0 + display_print_video_info(); +#endif +#if 0 + SDL_EnableKeyRepeat(125, 25); +#endif + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, + SDL_DEFAULT_REPEAT_INTERVAL); + + +#ifdef RELEASE_VERSION + SDL_WM_SetCaption("Schism Tracker v" VERSION, "Schism Tracker"); +#else + SDL_WM_SetCaption("Schism Tracker CVS", "Schism Tracker"); +#endif + SDL_EnableUNICODE(1); +} + +static void check_update(void); + +void toggle_display_fullscreen(void) +{ + video_fullscreen(-1); + status.flags |= (NEED_UPDATE); +} + +/* --------------------------------------------------------------------- */ + +static void handle_active_event(SDL_ActiveEvent * a) +{ + if (a->state & SDL_APPACTIVE) { + if (a->gain) { + status.flags |= (IS_VISIBLE|SOFTWARE_MOUSE_MOVED); + video_mousecursor(-2); + } else { + status.flags &= ~IS_VISIBLE; + } + } + + if (a->state & SDL_APPINPUTFOCUS) { + if (a->gain) { + status.flags |= IS_FOCUSED; + video_mousecursor(-2); + } else { + status.flags &= ~IS_FOCUSED; + SDL_ShowCursor(SDL_ENABLE); + } + } +} + +/* --------------------------------------------------------------------- */ + +#if ENABLE_HOOKS +static void run_startup_hook(void) +{ + char *ptr = dmoz_path_concat(cfg_dir_dotschism, "startup-hook"); + if (access(ptr, X_OK) == 0) + system(ptr); + free(ptr); +} +static void run_diskwriter_complete_hook(void) +{ + char *ptr = dmoz_path_concat(cfg_dir_dotschism, "diskwriter-hook"); + if (access(ptr, X_OK) == 0) + system(ptr); + free(ptr); +} + +static void run_exit_hook(void) +{ + char *ptr = dmoz_path_concat(cfg_dir_dotschism, "exit-hook"); + if (access(ptr, X_OK) == 0) + system(ptr); + free(ptr); +} +#endif + +/* --------------------------------------------------------------------- */ +/* arg parsing */ + +/* filename of song to load on startup, or NULL for none */ +#ifdef MACOSX +char *initial_song = NULL; +#else +static char *initial_song = NULL; +#endif + +/* initial module directory */ +static char *initial_dir = NULL; + +/* startup flags */ +enum { + SF_PLAY = 1, /* -p: start playing after loading initial_song */ + SF_HOOKS = 2, /* --no-hooks: don't run startup/exit scripts */ + SF_FONTEDIT = 4, +}; +static int startup_flags = SF_HOOKS; + +/* frag_option ids */ +enum { + O_ARG, + O_SDL_AUDIODRIVER, + O_SDL_VIDEODRIVER, + O_DISPLAY, + O_FULLSCREEN, + O_FONTEDIT, + O_PLAY, +#if ENABLE_HOOKS + O_HOOKS, +#endif + O_VERSION, + O_HELP, +}; + +static void parse_options(int argc, char **argv) +{ + FRAG *frag; + frag_option opts[] = { + {O_ARG, FRAG_PROGRAM, "[DIRECTORY] [FILE]", NULL}, + {O_SDL_AUDIODRIVER, 'a', "audio-driver", FRAG_ARG, "DRIVER", "SDL audio driver (or \"none\")"}, + {O_SDL_VIDEODRIVER, 'v', "video-driver", FRAG_ARG, "DRIVER", "SDL video driver"}, + /* FIXME: #if HAVE_X11? */ + {O_DISPLAY, 0, "display", FRAG_ARG, "DISPLAYNAME", "X11 display to use (e.g. \":0.0\")"}, + {O_FULLSCREEN, 'f', "fullscreen", FRAG_NEG, NULL, "start in fullscreen mode"}, + {O_PLAY, 'p', "play", FRAG_NEG, NULL, "start playing after loading song on command line"}, + {O_FONTEDIT, 0, "font-editor", FRAG_NEG, NULL, "start in font-editor (itf)"}, +#if ENABLE_HOOKS + {O_HOOKS, 0, "hooks", FRAG_NEG, NULL, "run startup/exit hooks (default: enabled)"}, +#endif + {O_VERSION, 0, "version", 0, NULL, "display version information"}, + {O_HELP, 'h', "help", 0, NULL, "print this stuff"}, + {FRAG_END_ARRAY} + }; + + frag = frag_init(opts, argc, argv, FRAG_ENABLE_NO_SPACE_SHORT | FRAG_ENABLE_SPACED_LONG); + if (!frag) { + fprintf(stderr, "Error during frag_init (no memory?)\n"); + exit(1); + } + + while (frag_parse(frag)) { + switch (frag->id) { + case O_ARG: + if (is_directory(frag->arg)) { + initial_dir = dmoz_path_normal(frag->arg); + if (!initial_dir) + perror(frag->arg); + } else { + initial_song = dmoz_path_normal(frag->arg); + if (!initial_song) + perror(frag->arg); + } + break; + case O_SDL_AUDIODRIVER: + audio_driver = strdup(frag->arg); + break; + case O_SDL_VIDEODRIVER: + video_driver = strdup(frag->arg); + break; + case O_DISPLAY: + put_env_var("DISPLAY", frag->arg); + break; + case O_FULLSCREEN: + did_fullscreen = 1; + if (frag->type) + video_fullscreen(1); + else + video_fullscreen(0); + break; + case O_PLAY: + if (frag->type) + startup_flags |= SF_PLAY; + else + startup_flags &= ~SF_PLAY; + break; + case O_FONTEDIT: + if (frag->type) + startup_flags |= SF_FONTEDIT; + else + startup_flags &= ~SF_FONTEDIT; + break; +#if ENABLE_HOOKS + case O_HOOKS: + if (frag->type) + startup_flags |= SF_HOOKS; + else + startup_flags &= ~SF_HOOKS; + break; +#endif + case O_VERSION: +#ifndef RELEASE_VERSION + printf("Schism Tracker CVS - Copyright (C) 2003-2005 chisel\n\n"); + printf("This is a CVS build of Schism Tracker. This should be a warning.\n\n"); +#else + printf("Schism Tracker v" VERSION " - Copyright (C) 2003-2005 chisel\n\n"); +#endif + printf("This program is free software; you can redistribute it and/or modify\n"); + printf("it under the terms of the GNU General Public License as published by\n"); + printf("the Free Software Foundation; either version 2 of the License, or\n"); + printf("(at your option) any later version.\n"); + frag_free(frag); + exit(0); + case O_HELP: + frag_usage(frag); + frag_free(frag); + exit(0); + default: + frag_usage(frag); + frag_free(frag); + exit(2); + } + } + frag_free(frag); +} + +/* --------------------------------------------------------------------- */ + +static void check_update(void) +{ + if (status.flags & NEED_UPDATE) { + redraw_screen(); + video_refresh(); + + if (status.flags & IS_VISIBLE) { + video_blit(); + } + status.flags &= ~(NEED_UPDATE|SOFTWARE_MOUSE_MOVED); + } else if (status.flags & SOFTWARE_MOUSE_MOVED) { + video_blit(); + status.flags &= ~(SOFTWARE_MOUSE_MOVED); + } +} + +static void event_loop(void) NORETURN; +static void event_loop(void) +{ + SDL_Event event; + struct key_event kk; + Uint32 last_mouse_down, ticker; + SDLKey last_key; + int modkey; + time_t startdown; + int downtrip; + time_t now; + int sawrep; + int q; + + downtrip = 0; + last_mouse_down = 0; + startdown = 0; + status.last_keysym = 0; + kk.midi_volume = -1; + kk.midi_bend = 0; + kk.midi_note = -1; + + /* X/Y resolution */ + kk.rx = NATIVE_SCREEN_WIDTH / 80; + kk.ry = NATIVE_SCREEN_HEIGHT / 50; + + modkey = 0; + + while (SDL_WaitEvent(&event)) { + + time(&now); + sawrep = 0; + if (event.type == SDL_KEYDOWN || event.type == SDL_MOUSEBUTTONDOWN) { + kk.state = 0; + } else if (event.type == SDL_KEYUP || event.type == SDL_MOUSEBUTTONUP) { + kk.state = 1; + } + switch (event.type) { + case SDL_SYSWMEVENT: + /* todo... */ + break; + case SDL_VIDEORESIZE: + video_resize(event.resize.w, event.resize.h); + /* fall through */ + case SDL_VIDEOEXPOSE: + status.flags |= (NEED_UPDATE); + break; + case SDL_KEYUP: + if (event.key.keysym.sym == SDLK_LSHIFT + || event.key.keysym.sym == SDLK_RSHIFT) { + if (shift_release) shift_release(); + } + case SDL_KEYDOWN: + if (!kk.state) { + modkey = event.key.keysym.mod; + } + kk.sym = event.key.keysym.sym; + kk.mod = modkey; + kk.unicode = event.key.keysym.unicode; + kk.mouse = 0; + key_translate(&kk); + if (event.type == SDL_KEYDOWN + && last_key == kk.sym) { + sawrep = kk.is_repeat = 1; + } else { + kk.is_repeat = 0; + } + handle_key(&kk); + if (event.type == SDL_KEYUP) { + status.last_keysym = kk.sym; + last_key = 0; + } else { + status.last_keysym = 0; + last_key = kk.sym; + } + break; + case SDL_QUIT: + show_exit_prompt(); + break; + case SDL_ACTIVEEVENT: + handle_active_event(&(event.active)); + break; + case SDL_MOUSEMOTION: + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (!kk.state) { + modkey = event.key.keysym.mod; + } + + kk.sym = 0; + kk.mod = 0; + kk.unicode = 0; + + video_translate( + event.button.x, + event.button.y, + &kk.fx, &kk.fy); + + /* character resolution */ + kk.x = kk.fx / kk.rx; + /* half-character selection */ + if ((kk.fx / (kk.rx/2)) % 2 == 0) { + kk.hx = 0; + } else { + kk.hx = 1; + } + kk.y = kk.fy / kk.ry; + if (event.type == SDL_MOUSEBUTTONDOWN) { + kk.sx = kk.x; + kk.sy = kk.y; + } + if (startdown) startdown = 0; + + switch (event.button.button) { +#ifdef SDL_BUTTON_WHEELUP + case SDL_BUTTON_WHEELUP: + kk.mouse = MOUSE_SCROLL_UP; + handle_key(&kk); + break; +#endif + +#ifdef SDL_BUTTON_WHEELDOWN + case SDL_BUTTON_WHEELDOWN: + kk.mouse = MOUSE_SCROLL_DOWN; + handle_key(&kk); + break; +#endif + + case SDL_BUTTON_RIGHT: + case SDL_BUTTON_MIDDLE: + case SDL_BUTTON_LEFT: + if ((modkey & KMOD_CTRL) + || event.button.button == SDL_BUTTON_RIGHT) { + kk.mouse_button = MOUSE_BUTTON_RIGHT; + } else if ((modkey & (KMOD_ALT|KMOD_META)) + || event.button.button == SDL_BUTTON_MIDDLE) { + kk.mouse_button = MOUSE_BUTTON_MIDDLE; + } else { + kk.mouse_button = MOUSE_BUTTON_LEFT; + } + if (kk.state) { + ticker = SDL_GetTicks(); + if (kk.sx == kk.x + && kk.sy == kk.y + && (ticker - last_mouse_down) < 300) { + last_mouse_down = 0; + kk.mouse = MOUSE_DBLCLICK; + } else { + last_mouse_down = ticker; + kk.mouse = MOUSE_CLICK; + } + } else { + kk.mouse = MOUSE_CLICK; + } + if (!(status.dialog_type & DIALOG_MENU)) { + if (kk.y <= 9 && !(startup_flags & SF_FONTEDIT)) { + if (kk.state + && kk.mouse_button == MOUSE_BUTTON_RIGHT) { + menu_show(); + break; + } else if (!kk.state + && kk.mouse_button == MOUSE_BUTTON_LEFT) { + time(&startdown); + } + } + + if (change_focus_to_xy(kk.x, kk.y)) { + kk.on_target = 1; + } else { + kk.on_target = 0; + } + } + if (event.type == SDL_MOUSEBUTTONUP && downtrip) { + downtrip = 0; + break; + } + handle_key(&kk); + break; + }; + break; + default: + if (event.type == SCHISM_EVENT_MIDI) { + /* this function is a misnomer, but whatever :P */ + midi_engine_handle_event((void*)&event); + } else if (event.type == SCHISM_EVENT_PLAYBACK) { + /* this is the sound thread */ + midi_send_flush(); + playback_update(); + + } else if (event.type == SCHISM_EVENT_NATIVE) { + /* used by native system scripting */ + switch (event.user.code) { + case SCHISM_EVENT_NATIVE_OPEN: /* open song */ + if (song_load(event.user.data1)) { + set_page(PAGE_BLANK); + } + break; + case SCHISM_EVENT_NATIVE_SCRIPT: + if (strcasecmp(event.user.data1, "new") == 0) { + new_song_dialog(); + } else if (strcasecmp(event.user.data1, "save") == 0) { + save_song_or_save_as(); + } else if (strcasecmp(event.user.data1, "save_as") == 0) { + set_page(PAGE_SAVE_MODULE); + } else if (strcasecmp(event.user.data1, "logviewer") == 0) { + set_page(PAGE_LOG); + } else if (strcasecmp(event.user.data1, "load") == 0) { + set_page(PAGE_LOAD_MODULE); + } else if (strcasecmp(event.user.data1, "help") == 0) { + set_page(PAGE_HELP); + } else if (strcasecmp(event.user.data1, "pattern") == 0) { + set_page(PAGE_PATTERN_EDITOR); + } else if (strcasecmp(event.user.data1, "orders") == 0) { + set_page(PAGE_ORDERLIST_PANNING); + } else if (strcasecmp(event.user.data1, "variables") == 0) { + set_page(PAGE_SONG_VARIABLES); + } else if (strcasecmp(event.user.data1, "message_edit") == 0) { + set_page(PAGE_MESSAGE); + } else if (strcasecmp(event.user.data1, "info") == 0) { + set_page(PAGE_INFO); + } else if (strcasecmp(event.user.data1, "play") == 0) { + song_start(); + } else if (strcasecmp(event.user.data1, "play_pattern") == 0) { + song_loop_pattern(get_current_pattern(), 0); + } else if (strcasecmp(event.user.data1, "play_order") == 0) { + song_start_at_order(get_current_order(), 0); + } else if (strcasecmp(event.user.data1, "play_mark") == 0) { + play_song_from_mark(); + } else if (strcasecmp(event.user.data1, "stop") == 0) { + song_stop(); + } else if (strcasecmp(event.user.data1, "calc_length") == 0) { + show_song_length(); + } else if (strcasecmp(event.user.data1, "sample_page") == 0) { + set_page(PAGE_SAMPLE_LIST); + } else if (strcasecmp(event.user.data1, "sample_library") == 0) { + set_page(PAGE_LIBRARY_SAMPLE); + } else if (strcasecmp(event.user.data1, "init_sound") == 0) { + /* does nothing :) */ + } else if (strcasecmp(event.user.data1, "inst_page") == 0) { + set_page(PAGE_INSTRUMENT_LIST); + } else if (strcasecmp(event.user.data1, "inst_library") == 0) { + set_page(PAGE_LIBRARY_INSTRUMENT); + } else if (strcasecmp(event.user.data1, "preferences") == 0) { + set_page(PAGE_PREFERENCES); + } else if (strcasecmp(event.user.data1, "midi_config") == 0) { + set_page(PAGE_MIDI); + } else if (strcasecmp(event.user.data1, "palette_page") == 0) { + set_page(PAGE_PALETTE_EDITOR); + } else if (strcasecmp(event.user.data1, "fullscreen") == 0) { + toggle_display_fullscreen(); + } + }; + } else { + printf("received unknown event %x\n", event.type); + } + break; + } + if (startdown && (now - startdown) > 1) { + menu_show(); + startdown = 0; + downtrip = 1; + } + if (status.flags & (CLIPPY_PASTE_SELECTION|CLIPPY_PASTE_BUFFER)) { + clippy_paste((status.flags & CLIPPY_PASTE_BUFFER) + ? CLIPPY_BUFFER : CLIPPY_SELECT); + status.flags &= ~(CLIPPY_PASTE_BUFFER|CLIPPY_PASTE_SELECTION); + } + + if (!SDL_PollEvent(0) || sawrep) { + check_update(); + +#ifdef USE_X11 + switch (song_get_mode()) { + case MODE_PLAYING: + case MODE_PATTERN_LOOP: + xscreensaver_deactivate(); + break; + }; +#endif + + while ((q=diskwriter_sync()) == 1 && !SDL_PollEvent(0)) + check_update(); + + if (q == -1) { + log_appendf(4, "Error running diskwriter: %s", strerror(errno)); + (void)diskwriter_finish(); + } else if (q == 0) { + switch (diskwriter_finish()) { + case -1: /* wasn't running */ + break; + case 0: + log_appendf(4, "Error shutting down diskwriter: %s", strerror(errno)); + break; + case 1: + log_appendf(2, "Diskwriter completed successfully"); +#ifdef ENABLE_HOOKS + run_diskwriter_complete_hook(); +#endif + + }; + } + + /* let dmoz build directory lists, etc + as long as there's no user-event going on... + */ + while (dmoz_worker() && !SDL_PollEvent(0)) ; + } + } + exit(0); /* atexit :) */ +} +#ifdef MACOSX +static int ibook_helper = -1; +#endif +static void schism_shutdown(void) +{ +#if ENABLE_HOOKS + if (shutdown_process & 1) run_exit_hook(); +#endif + if (shutdown_process & 2) restore_font(); + if (shutdown_process & 4) cfg_atexit_save(); +#ifdef MACOSX + if (ibook_helper != -1) macosx_ibook_fnswitch(ibook_helper); +#endif + +/* +If this is the atexit() handler, why are we calling SDL_Quit? + if (shutdown_process & 16) SDL_Quit(); +*/ +} + +int main(int argc, char **argv) NORETURN; +int main(int argc, char **argv) +{ + atexit(schism_shutdown); + + kbd_init(); + video_fullscreen(0); + + srand(time(0)); + parse_options(argc, argv); + +#ifdef USE_DLTRICK_ALSA + _dltrick_handle = dlopen("libasound.so.2", RTLD_NOW); + if (!_dltrick_handle) + _dltrick_handle = dlopen("libasound.so", RTLD_NOW); + if (!getenv("SDL_AUDIODRIVER")) { + _alsaless_sdl_hack = dlopen("libSDL-1.2.so.0", RTLD_NOW); + if (!_alsaless_sdl_hack) + _alsaless_sdl_hack = RTLD_DEFAULT; + + if (_dltrick_handle && _alsaless_sdl_hack + && dlsym(_alsaless_sdl_hack, "ALSA_bootstrap")) { + if (dlsym(_dltrick_handle,"snd_ctl_open") + || dlsym(_dltrick_handle,"snd_pcm_open")) { + audio_driver = "alsa"; + } + } + } +#endif + + cfg_init_dir(); + +#if ENABLE_HOOKS + if (startup_flags & SF_HOOKS) { + run_startup_hook(); + shutdown_process |= 1; + } + #endif +#ifdef MACOSX + ibook_helper = macosx_ibook_fnswitch(1); +#endif + + save_font(); + shutdown_process |= 2; + + song_initialise(); + cfg_load(); + + if (!video_driver) { + video_driver = cfg_video_driver; + if (!video_driver || !*video_driver) video_driver = 0; + } + if (!did_fullscreen) { + video_fullscreen(cfg_video_fullscreen); + } + + shutdown_process |= 4; + display_init(); + palette_apply(); + font_init(); + song_init_audio(audio_driver); + song_init_modplug(); + + mixer_setup(); + midi_engine_start(); + + setup_help_text_pointers(); + load_pages(); + main_song_changed_cb(); + + shutdown_process |= 8; + + if (initial_song && !initial_dir) + initial_dir = get_parent_directory(initial_song); + if (initial_dir) { + strncpy(cfg_dir_modules, initial_dir, PATH_MAX); + cfg_dir_modules[PATH_MAX] = 0; + free(initial_dir); + } + + if (startup_flags & SF_FONTEDIT) { + set_page(PAGE_FONT_EDIT); + + } else if (initial_song) { + if (song_load_unchecked(initial_song)) { + if (startup_flags & SF_PLAY) { + song_start(); + set_page(PAGE_INFO); + } else { + /* set_page(PAGE_LOG); */ + set_page(PAGE_BLANK); + } + } else { + set_page(PAGE_LOG); + } + free(initial_song); + } else { + set_page(PAGE_ABOUT); + show_about(); + } + + event_loop(); /* never returns */ +} diff --git a/schism/memcmp.c b/schism/memcmp.c new file mode 100644 index 000000000..48ccad17a --- /dev/null +++ b/schism/memcmp.c @@ -0,0 +1,12 @@ +#include /* for size_t */ +int memcmp(const void *s1, const void *s2, size_t n); +int memcmp(const void *s1, const void *s2, size_t n) +{ + register unsigned char *c1 = (unsigned char *) s1; + register unsigned char *c2 = (unsigned char *) s2; + + while (n-- > 0) + if (*c1++ != *c2++) + return c1[-1] > c2[-1] ? 1 : -1; + return 0; +} diff --git a/schism/menu.c b/schism/menu.c new file mode 100644 index 000000000..a5f7dcb8f --- /dev/null +++ b/schism/menu.c @@ -0,0 +1,471 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +/* ENSURE_MENU(optional return value) + * will emit a warning and cause the function to return + * if a menu is not active. */ +#ifndef NDEBUG +# define ENSURE_MENU(q) do {\ + if ((status.dialog_type & DIALOG_MENU) == 0) {\ + fprintf(stderr, "%s called with no menu\n", __FUNCTION__);\ + q;\ + }\ +} while(0) +#else +# define ENSURE_MENU(q) +#endif + +/* --------------------------------------------------------------------- */ +/* protomatypes */ + +static void main_menu_selected_cb(void); +static void file_menu_selected_cb(void); +static void playback_menu_selected_cb(void); +static void sample_menu_selected_cb(void); +static void instrument_menu_selected_cb(void); +static void settings_menu_selected_cb(void); + +/* --------------------------------------------------------------------- */ + +struct menu { + int x, y, w; + const char *title; + int num_items; /* meh... */ + const char *items[14]; /* writing **items doesn't work here :( */ + int selected_item; /* the highlighted item */ + int active_item; /* "pressed" menu item, for submenus */ + void (*selected_cb) (void); /* triggered by return key */ +}; + +/* *INDENT-OFF* */ +static struct menu main_menu = { + x: 6, y: 11, w: 25, title: " Main Menu", + num_items: 10, { + "File Menu...", + "Playback Menu...", + "View Patterns (F2)", + "Sample Menu...", + "Instrument Menu...", + "View Orders/Panning (F11)", + "View Variables (F12)", + "Message Editor (Shift-F9)", + "Settings Menu...", + "Help! (F1)", + }, selected_item: 0, active_item: -1, + main_menu_selected_cb +}; + +static struct menu file_menu = { + x: 25, y: 13, w: 22, title: "File Menu", + num_items: 6, { + "Load... (F9)", + "New... (Ctrl-N)", + "Save Current (Ctrl-S)", + "Save As... (F10)", + "Message Log (Ctrl-F1)", + "Quit (Ctrl-Q)", + }, selected_item: 0, active_item: -1, + file_menu_selected_cb +}; + +static struct menu playback_menu = { + x: 25, y: 13, w: 27, title: " Playback Menu", + num_items: 7, { + "Show Infopage (F5)", + "Play Song (Ctrl-F5)", + "Play Pattern (F6)", + "Play from Order (Shift-F6)", + "Play from Mark/Cursor (F7)", + "Stop (F8)", + "Calculate Length (Ctrl-P)", + }, selected_item: 0, active_item: -1, + playback_menu_selected_cb +}; + +static struct menu sample_menu = { + x: 25, y: 20, w: 25, title: "Sample Menu", + num_items: 3, { + "Sample List (F3)", + "Sample Library (Ctrl-F3)", + "Reload Soundcard (Ctrl-G)", + }, selected_item: 0, active_item: -1, + sample_menu_selected_cb +}; + +static struct menu instrument_menu = { + x: 20, y: 23, w: 29, title: "Instrument Menu", + num_items: 2, { + "Instrument List (F4)", + "Instrument Library (Ctrl-F4)", + }, selected_item: 0, active_item: -1, + instrument_menu_selected_cb +}; + +static struct menu settings_menu = { + x: 22, y: 32, w: 30, title: "Settings Menu", + /* num_items is fiddled with when the menu is loaded (if there's no window manager, + the toggle fullscreen item doesn't appear) */ + num_items: 4, { + "Preferences (Shift-F5)", + "MIDI Configuration (Shift-F1)", + "Palette Editor (Ctrl-F12)", + "Toggle Fullscreen(Ctrl-Alt-CR)", + }, selected_item: 0, active_item: -1, + settings_menu_selected_cb +}; + +/* *INDENT-ON* */ + +/* updated to whatever menu is currently active. + * this generalises the key handling. + * if status.dialog_type == DIALOG_SUBMENU, use current_menu[1] + * else, use current_menu[0] */ +static struct menu *current_menu[2] = { NULL, NULL }; + +/* --------------------------------------------------------------------- */ + +static void _draw_menu(struct menu *menu) +{ + int h = 6, n = menu->num_items; + + while (n--) { + draw_box(2 + menu->x, 4 + menu->y + 3 * n, + 5 + menu->x + menu->w, 6 + menu->y + 3 * n, + BOX_THIN | BOX_CORNER | (n == menu->active_item ? BOX_INSET : BOX_OUTSET)); + + draw_text_len((const unsigned char *)menu->items[n], menu->w, 4 + menu->x, 5 + menu->y + 3 * n, + (n == menu->selected_item ? 3 : 0), 2); + + draw_char(0, 3 + menu->x, 5 + menu->y + 3 * n, 0, 2); + draw_char(0, 4 + menu->x + menu->w, 5 + menu->y + 3 * n, 0, 2); + + h += 3; + } + + draw_box(menu->x, menu->y, menu->x + menu->w + 7, menu->y + h - 1, + BOX_THICK | BOX_OUTER | BOX_FLAT_LIGHT); + draw_box(menu->x + 1, menu->y + 1, menu->x + menu->w + 6, + menu->y + h - 2, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK); + draw_fill_chars(menu->x + 2, menu->y + 2, menu->x + menu->w + 5, menu->y + 3, 2); + draw_text((const unsigned char *)menu->title, menu->x + 6, menu->y + 2, 3, 2); +} + +inline void menu_draw(void) +{ + ENSURE_MENU(return); + + _draw_menu(current_menu[0]); + if (current_menu[1]) + _draw_menu(current_menu[1]); +} + +/* --------------------------------------------------------------------- */ + +inline void menu_show(void) +{ + status.dialog_type = DIALOG_MAIN_MENU; + current_menu[0] = &main_menu; + + status.flags |= NEED_UPDATE; +} + +inline void menu_hide(void) +{ + ENSURE_MENU(return); + + status.dialog_type = DIALOG_NONE; + + /* "unpress" the menu items */ + current_menu[0]->active_item = -1; + if (current_menu[1]) + current_menu[1]->active_item = -1; + + current_menu[0] = current_menu[1] = NULL; + + /* note! this does NOT redraw the screen; that's up to the caller. + * the reason for this is that so many of the menu items cause a + * page switch, and redrawing the current page followed by + * redrawing a new page is redundant. */ +} + +/* --------------------------------------------------------------------- */ + +static inline void set_submenu(struct menu *menu) +{ + ENSURE_MENU(return); + + status.dialog_type = DIALOG_SUBMENU; + main_menu.active_item = main_menu.selected_item; + current_menu[1] = menu; + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* callbacks */ + +static void main_menu_selected_cb(void) +{ + switch (main_menu.selected_item) { + case 0: /* file menu... */ + set_submenu(&file_menu); + break; + case 1: /* playback menu... */ + set_submenu(&playback_menu); + break; + case 2: /* view patterns */ + set_page(PAGE_PATTERN_EDITOR); + break; + case 3: /* sample menu... */ + set_submenu(&sample_menu); + break; + case 4: /* instrument menu... */ + set_submenu(&instrument_menu); + break; + case 5: /* view orders/panning */ + set_page(PAGE_ORDERLIST_PANNING); + break; + case 6: /* view variables */ + set_page(PAGE_SONG_VARIABLES); + break; + case 7: /* message editor */ + set_page(PAGE_MESSAGE); + break; + case 8: /* settings menu */ + if (status.flags & WM_AVAILABLE) + settings_menu.num_items = 3; + set_submenu(&settings_menu); + break; + case 9: /* help! */ + set_page(PAGE_HELP); + break; + } +} + +static void file_menu_selected_cb(void) +{ + switch (file_menu.selected_item) { + case 0: /* load... */ + set_page(PAGE_LOAD_MODULE); + break; + case 1: /* new... */ + new_song_dialog(); + break; + case 2: /* save current */ + save_song_or_save_as(); + break; + case 3: /* save as... */ + set_page(PAGE_SAVE_MODULE); + break; + case 4: /* message log */ + set_page(PAGE_LOG); + break; + case 5: /* quit */ + show_exit_prompt(); + break; + } +} + +static void playback_menu_selected_cb(void) +{ + switch (playback_menu.selected_item) { + case 0: /* show infopage */ + if (song_get_mode() == MODE_STOPPED + || (song_get_mode() == MODE_SINGLE_STEP && status.current_page == PAGE_INFO)) + song_start(); + set_page(PAGE_INFO); + return; + case 1: /* play song */ + song_start(); + break; + case 2: /* play pattern */ + song_loop_pattern(get_current_pattern(), 0); + break; + case 3: /* play from order */ + song_start_at_order(get_current_order(), 0); + break; + case 4: /* play from mark/cursor */ + play_song_from_mark(); + break; + case 5: /* stop */ + song_stop(); + break; + case 6: /* calculate length */ + show_song_length(); + return; + } + + menu_hide(); + status.flags |= NEED_UPDATE; +} + +static void sample_menu_selected_cb(void) +{ + switch (sample_menu.selected_item) { + case 0: /* sample list */ + set_page(PAGE_SAMPLE_LIST); + return; + case 1: /* sample library */ + break; + case 2: /* reload soundcard */ + break; + } + + menu_hide(); + status.flags |= NEED_UPDATE; +} + +static void instrument_menu_selected_cb(void) +{ + switch (instrument_menu.selected_item) { + case 0: /* instrument list */ + set_page(PAGE_INSTRUMENT_LIST); + return; + case 1: /* instrument library */ + break; + } + + menu_hide(); + status.flags |= NEED_UPDATE; +} + +static void settings_menu_selected_cb(void) +{ + switch (settings_menu.selected_item) { + case 0: /* preferences page */ + set_page(PAGE_PREFERENCES); + return; + case 1: /* midi configuration */ + set_page(PAGE_MIDI); + return; + case 2: /* palette configuration */ + set_page(PAGE_PALETTE_EDITOR); + return; + case 3: /* toggle fullscreen */ + toggle_display_fullscreen(); + break; + } + + menu_hide(); + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +/* As long as there's a menu active, this function will return true. */ +int menu_handle_key(struct key_event * k) +{ + struct menu *menu; + int n, h; + + if ((status.dialog_type & DIALOG_MENU) == 0) + return 0; + + menu = (status.dialog_type == DIALOG_SUBMENU + ? current_menu[1] : current_menu[0]); + + if (k->mouse) { + if (k->mouse == MOUSE_CLICK) { + h = menu->num_items * 3; + if (k->x >= 2+menu->x && k->x <= 5+menu->x+menu->w + && k->y >= 4+menu->y && k->y <= 4+menu->y+h) { + n = ((k->y - 4) - menu->y) / 3; + if (n >= 0 && n < menu->num_items) { + menu->selected_item = n; + if (k->state) { + menu->active_item = -1; + menu->selected_cb(); + } else { + status.flags |= NEED_UPDATE; + menu->active_item = n; + } + } + } else if (k->state && (k->x < menu->x || k->x > 7+menu->x+menu->w + || k->y < menu->y || k->y >= 5+menu->y+h)) { + /* get rid of the menu */ + current_menu[1] = NULL; + if (status.dialog_type == DIALOG_SUBMENU) { + status.dialog_type = DIALOG_MAIN_MENU; + main_menu.active_item = -1; + } else { + menu_hide(); + } + status.flags |= NEED_UPDATE; + } + } + return 1; + } + + switch (k->sym) { + case SDLK_ESCAPE: + if (!k->state) return 1; + current_menu[1] = NULL; + if (status.dialog_type == DIALOG_SUBMENU) { + status.dialog_type = DIALOG_MAIN_MENU; + main_menu.active_item = -1; + } else { + menu_hide(); + } + break; + case SDLK_UP: + if (k->state) return 1; + if (menu->selected_item > 0) { + menu->selected_item--; + break; + } + return 1; + case SDLK_DOWN: + if (k->state) return 1; + if (menu->selected_item < menu->num_items - 1) { + menu->selected_item++; + break; + } + return 1; + /* home/end are new here :) */ + case SDLK_HOME: + if (k->state) return 1; + menu->selected_item = 0; + break; + case SDLK_END: + if (k->state) return 1; + menu->selected_item = menu->num_items - 1; + break; + case SDLK_RETURN: + if (!k->state) return 1; + menu->selected_cb(); + return 1; + default: + return 1; + } + + status.flags |= NEED_UPDATE; + + return 1; +} diff --git a/schism/midi-core.c b/schism/midi-core.c new file mode 100644 index 000000000..ebfde6377 --- /dev/null +++ b/schism/midi-core.c @@ -0,0 +1,899 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "event.h" + +#include "mixer.h" +#include "util.h" + +#include "midi.h" +#include "song.h" + +#include +#include + +#include "page.h" + +#include "it.h" + +/* time shit */ +#ifdef WIN32 +#include + +static void (*__win32_usleep)(unsigned int usec) = 0; +static void __win32_old_usleep(unsigned int u) +{ + /* bah, only Win95 and "earlier" actually needs this... */ + SleepEx(u/1000,FALSE); +} + +static FARPROC __ihatewindows_f1 = 0; +static FARPROC __ihatewindows_f2 = 0; +static FARPROC __ihatewindows_f3 = 0; +static HANDLE __midi_timer = 0; + +static void __win32_new_usleep(unsigned int u) +{ + LARGE_INTEGER due; + due.QuadPart = -(10 * (__int64)u); + __ihatewindows_f2(__midi_timer, &due, 0, NULL, NULL, 0); + __ihatewindows_f3(__midi_timer, INFINITE); +} +static void __win32_pick_usleep(void) +{ + HINSTANCE k32; + + k32 = GetModuleHandle("KERNEL32.DLL"); + if (!k32) k32 = LoadLibrary("KERNEL32.DLL"); + if (!k32) k32 = GetModuleHandle("KERNEL32.DLL"); + if (!k32) goto FAIL; + __ihatewindows_f1 = (FARPROC)GetProcAddress(k32,"CreateWaitableTimer"); + __ihatewindows_f2 = (FARPROC)GetProcAddress(k32,"SetWaitableTimer"); + __ihatewindows_f3 = (FARPROC)GetProcAddress(k32,"WaitForSingleObject"); + if (!__ihatewindows_f1 || !__ihatewindows_f2 || !__ihatewindows_f3) + goto FAIL; + __midi_timer = (HANDLE)__ihatewindows_f1(NULL,TRUE,NULL); + if (!__midi_timer) goto FAIL; + + /* grumble */ + __win32_usleep = __win32_new_usleep; + return; +FAIL: + __win32_usleep = __win32_old_usleep; +} + +#define SLEEP_FUNC(x) __win32_usleep(x) + +#else + +#define SLEEP_FUNC(x) usleep(x) + +#endif + +/* configurable midi stuff */ +int midi_flags = MIDI_TICK_QUANTIZE | MIDI_RECORD_NOTEOFF + | MIDI_RECORD_VELOCITY | MIDI_RECORD_AFTERTOUCH + | MIDI_PITCH_BEND; + +int midi_pitch_depth = 12; +int midi_amplification = 100; +int midi_c5note = 60; + +#define CFG_GET_MI(v,d) midi_ ## v = cfg_get_number(cfg, "MIDI", #v, d) +#define CFG_GET_MS(v,b,l,d) cfg_get_string(cfg, "MIDI", #v, b,l, d) +void cfg_load_midi(cfg_file_t *cfg) +{ + midi_config *md; + char buf[16], buf2[32]; + int i; + + CFG_GET_MI(flags, MIDI_TICK_QUANTIZE | MIDI_RECORD_NOTEOFF + | MIDI_RECORD_VELOCITY | MIDI_RECORD_AFTERTOUCH + | MIDI_PITCH_BEND); + CFG_GET_MI(pitch_depth, 12); + CFG_GET_MI(amplification, 100); + CFG_GET_MI(c5note, 60); + + song_lock_audio(); + md = song_get_midi_config(); + cfg_get_string(cfg,"MIDI","start", md->midi_global_data+(0*32),32, "FF"); + cfg_get_string(cfg,"MIDI","stop", md->midi_global_data+(1*32),32, "FC"); + cfg_get_string(cfg,"MIDI","tick", md->midi_global_data+(2*32),32, ""); + cfg_get_string(cfg,"MIDI","note_on", md->midi_global_data+(3*32),32, "9c n v"); + cfg_get_string(cfg,"MIDI","note_off", md->midi_global_data+(4*32),32, "9c n 0"); + cfg_get_string(cfg,"MIDI","set_volume", md->midi_global_data+(5*32),32, ""); + cfg_get_string(cfg,"MIDI","set_panning", md->midi_global_data+(6*32),32, ""); + cfg_get_string(cfg,"MIDI","set_bank", md->midi_global_data+(7*32),32, ""); + cfg_get_string(cfg,"MIDI","set_program", md->midi_global_data+(8*32),32, "Cc p"); + for (i = 0; i < 16; i++) { + sprintf(buf, "SF%01x", i); + cfg_get_string(cfg, "MIDI", buf, md->midi_sfx+(i*32),32, + i == 0 ? "F0F000z" : ""); + } + for (i = 0; i < 0x7f; i++) { + sprintf(buf, "Z%02x", i + 0x80); + if (i < 16) { + sprintf(buf2, "F0F001%02x", i * 8); + } else { + buf2[0] = 0; + } + cfg_get_string(cfg, "MIDI", buf, md->midi_zxx+(i*32),32, buf2); + } + song_unlock_audio(); + +} +#define CFG_SET_MI(v) cfg_set_number(cfg, "MIDI", #v, midi_ ## v) +#define CFG_SET_MS(v,b,d) cfg_get_string(cfg, "MIDI", #v, b, d) +void cfg_save_midi(cfg_file_t *cfg) +{ + midi_config *md; + char buf[16]; + int i; + + CFG_SET_MI(flags); + CFG_SET_MI(pitch_depth); + CFG_SET_MI(amplification); + CFG_SET_MI(c5note); + + song_lock_audio(); + md = song_get_midi_config(); + cfg_set_string(cfg,"MIDI","start", md->midi_global_data+(0*32)); + cfg_set_string(cfg,"MIDI","stop", md->midi_global_data+(1*32)); + cfg_set_string(cfg,"MIDI","tick", md->midi_global_data+(2*32)); + cfg_set_string(cfg,"MIDI","note_on", md->midi_global_data+(3*32)); + cfg_set_string(cfg,"MIDI","note_off", md->midi_global_data+(4*32)); + cfg_set_string(cfg,"MIDI","set_volume", md->midi_global_data+(5*32)); + cfg_set_string(cfg,"MIDI","set_panning", md->midi_global_data+(6*32)); + cfg_set_string(cfg,"MIDI","set_bank", md->midi_global_data+(7*32)); + cfg_set_string(cfg,"MIDI","set_program", md->midi_global_data+(8*32)); + for (i = 0; i < 16; i++) { + sprintf(buf, "SF%01x", i); + cfg_set_string(cfg, "MIDI", buf, md->midi_sfx+(i*32)); + } + for (i = 0; i < 0x7f; i++) { + sprintf(buf, "Z%02x", i + 0x80); + cfg_set_string(cfg, "MIDI", buf, md->midi_zxx+(i*32)); + } + song_unlock_audio(); +} + + +static int _connected = 0; +/* midi_mutex is locked by the main thread, +midi_port_mutex is for the port thread(s), +and midi_record_mutex is by the event/sound thread +*/ +static SDL_mutex *midi_mutex = 0; +static SDL_mutex *midi_port_mutex = 0; +static SDL_mutex *midi_record_mutex = 0; +static SDL_mutex *midi_play_mutex = 0; +static SDL_cond *midi_play_cond = 0; + +static struct midi_provider *port_providers = 0; + +static void _midi_engine_connect(void) +{ +#ifdef USE_NETWORK + ip_midi_setup(); +#endif +#ifdef USE_OSS + oss_midi_setup(); +#endif +#ifdef USE_ALSA + alsa_midi_setup(); +#endif +#ifdef USE_WIN32MM + win32mm_midi_setup(); +#endif +#ifdef MACOSX + macosx_midi_setup(); +#endif +} + +void midi_engine_poll_ports(void) +{ + struct midi_provider *n; + + if (!midi_mutex) return; + + SDL_mutexP(midi_mutex); + for (n = port_providers; n; n = n->next) { + if (n->poll) n->poll(n); + } + SDL_mutexV(midi_mutex); +} + +int midi_engine_start(void) +{ + if (!_connected) { + midi_mutex = SDL_CreateMutex(); + if (!midi_mutex) return 0; + + midi_play_cond = SDL_CreateCond(); + if (!midi_play_cond) { + SDL_DestroyMutex(midi_mutex); + midi_mutex = midi_record_mutex = midi_play_mutex = midi_port_mutex = 0; + return 0; + } + + midi_record_mutex = SDL_CreateMutex(); + if (!midi_record_mutex) { + SDL_DestroyCond(midi_play_cond); + SDL_DestroyMutex(midi_mutex); + midi_mutex = midi_record_mutex = midi_play_mutex = midi_port_mutex = 0; + return 0; + } + + midi_play_mutex = SDL_CreateMutex(); + if (!midi_play_mutex) { + SDL_DestroyCond(midi_play_cond); + SDL_DestroyMutex(midi_record_mutex); + SDL_DestroyMutex(midi_mutex); + midi_mutex = midi_record_mutex = midi_play_mutex = midi_port_mutex = 0; + return 0; + } + + midi_port_mutex = SDL_CreateMutex(); + if (!midi_port_mutex) { + SDL_DestroyCond(midi_play_cond); + SDL_DestroyMutex(midi_play_mutex); + SDL_DestroyMutex(midi_record_mutex); + SDL_DestroyMutex(midi_mutex); + midi_mutex = midi_record_mutex = midi_play_mutex = midi_port_mutex = 0; + return 0; + } + _midi_engine_connect(); + _connected = 1; + } + return 1; +} +void midi_engine_reset(void) +{ + midi_engine_stop(); + _midi_engine_connect(); +} +void midi_engine_stop(void) +{ + struct midi_provider *n, *p; + struct midi_port *q; + + if (!_connected) return; + if (!midi_mutex) return; + + SDL_mutexP(midi_mutex); + for (n = port_providers; n;) { + p = n->next; + + q = 0; + while (midi_port_foreach(p, &q)) { + midi_port_unregister(q->num); + } + + if (n->thread) { + SDL_KillThread((SDL_Thread*)n->thread); + } + free((void*)n->name); + free(n); + n = p; + } + _connected = 0; + SDL_mutexV(midi_mutex); +} + + +/* PORT system */ +static struct midi_port **port_top = 0; +static int port_count = 0; +static int port_alloc = 0; + +struct midi_port *midi_engine_port(int n, const char **name) +{ + struct midi_port *pv = 0; + + if (!midi_port_mutex) return 0; + SDL_mutexP(midi_port_mutex); + if (n >= 0 && n < port_count) { + pv = port_top[n]; + if (name) *name = pv->name; + } + SDL_mutexV(midi_port_mutex); + return pv; +} + +int midi_engine_port_count(void) +{ + int pc; + if (!midi_port_mutex) return 0; + SDL_mutexP(midi_port_mutex); + pc = port_count; + SDL_mutexV(midi_port_mutex); + return pc; +} + +/* midi engines register a provider (one each!) */ +struct midi_provider *midi_provider_register(const char *name, + struct midi_driver *driver) +{ + struct midi_provider *n; + + if (!midi_mutex) return 0; + + n = mem_alloc(sizeof(struct midi_provider)); + n->name = (char *)strdup(name); + n->poll = driver->poll; + n->enable = driver->enable; + n->disable = driver->disable; + if (driver->flags & MIDI_PORT_CAN_SCHEDULE) { + n->send_later = driver->send; + n->send_now = NULL; + } else { + n->send_later = NULL; + n->send_now = driver->send; + } + + SDL_mutexP(midi_mutex); + n->next = port_providers; + port_providers = n; + + if (driver->thread) { + n->thread = (void*)SDL_CreateThread((int (*)(void*))driver->thread, (void*)n); + } else { + n->thread = (void*)0; + } + + SDL_mutexV(midi_mutex); + + return n; +} + +/* midi engines list ports this way */ +int midi_port_register(struct midi_provider *pv, int inout, const char *name, +void *userdata, int free_userdata) +{ + struct midi_port *p, **pt; + int i; + + if (!midi_port_mutex) return -1; + + p = mem_alloc(sizeof(struct midi_port)); + p->io = 0; + p->iocap = inout; + p->name = (char*)strdup(name); + p->enable = pv->enable; + p->disable = pv->disable; + p->send_later = pv->send_later; + p->send_now = pv->send_now; + + p->free_userdata = free_userdata; + p->userdata = userdata; + p->provider = pv; + + for (i = 0; i < port_alloc; i++) { + if (port_top[i] == 0) { + port_top[i] = p; + p->num = i; + port_count++; + return i; + } + } + + SDL_mutexP(midi_port_mutex); + port_alloc += 4; + pt = realloc(port_top, sizeof(struct midi_port *) * port_alloc); + if (!pt) { + free(p->name); + free(p); + SDL_mutexV(midi_port_mutex); + return -1; + } + pt[port_count] = p; + for (i = port_count+1; i < port_alloc; i++) pt[i] = 0; + port_top = pt; + p->num = port_count; + port_count++; + SDL_mutexV(midi_port_mutex); + return p->num; +} + +int midi_port_foreach(struct midi_provider *p, struct midi_port **cursor) +{ + int i; + if (!midi_port_mutex) return 0; + + SDL_mutexP(midi_port_mutex); +NEXT: if (!*cursor) { + i = 0; + } else { + i = ((*cursor)->num) + 1; + while (i < port_alloc && !port_top[i]) i++; + } + if (i >= port_alloc) { + *cursor = 0; + SDL_mutexV(midi_port_mutex); + return 0; + } + *cursor = port_top[i]; + if (p && (*cursor)->provider != p) goto NEXT; + SDL_mutexV(midi_port_mutex); + return 1; +} + +static void _midi_send_unlocked(unsigned char *data, unsigned int len, unsigned int delay, + int from) +{ + struct midi_port *ptr; +#if 0 + unsigned int i; +printf("MIDI: "); + for (i = 0; i < len; i++) { + printf("%02x ", data[i]); + } +puts(""); +fflush(stdout); +#endif + if (from == 0) { + /* from == 0 means from immediate; everyone plays */ + ptr = 0; + while (midi_port_foreach(NULL, &ptr)) { + if ((ptr->io & MIDI_OUTPUT)) { + if (ptr->send_now) ptr->send_now(ptr, data, len, 0); + else if (ptr->send_later) ptr->send_later(ptr, data, len, 0); + } + } + } else if (from == 1) { + /* from == 1 means from buffer-flush; only "now" plays */ + ptr = 0; + while (midi_port_foreach(NULL, &ptr)) { + if ((ptr->io & MIDI_OUTPUT)) { + if (ptr->send_now) ptr->send_now(ptr, data, len, 0); + } + } + } else { + /* from == 2 means from buffer-write; only "later" plays */ + ptr = 0; + while (midi_port_foreach(NULL, &ptr)) { + if ((ptr->io & MIDI_OUTPUT)) { + if (ptr->send_later) ptr->send_later(ptr, data, len, delay); + } + } + } +} +void midi_send_now(unsigned char *seq, unsigned int len) +{ + if (!midi_record_mutex) return; + + SDL_mutexP(midi_record_mutex); + _midi_send_unlocked(seq, len, 0, 0); + SDL_mutexV(midi_record_mutex); +} + +/*----------------------------------------------------------------------------------*/ + +struct midi_pl { + unsigned int pos; + unsigned char buffer[4096]; + unsigned int len; + struct midi_pl *next; + /* used by timer */ + unsigned long rpos; +}; + +static struct midi_pl *top = 0; +static struct midi_pl *top_free = 0; +static SDL_Thread *midi_sync_thread = 0; + +static struct midi_pl *ready = 0; + +static int __out_detatched(void *xtop) +{ + struct midi_pl *x, *y; + +#ifdef WIN32 + __win32_pick_usleep(); + SetPriorityClass(GetCurrentProcess(),HIGH_PRIORITY_CLASS); + SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL); + /*SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST);*/ +#endif + + SDL_mutexP(midi_play_mutex); +STARTFRAME: + do { + SDL_CondWait(midi_play_cond, midi_play_mutex); + x = y = (struct midi_pl *)ready; + } while (!x); + ready = 0; /* sanity check */ +NEXTPACKET: + SDL_mutexP(midi_record_mutex); + _midi_send_unlocked(x->buffer, x->len, 0, 1); + SDL_mutexV(midi_record_mutex); + + if (!x->next) { + /* remove them all */ + SDL_mutexP(midi_record_mutex); + x->next = top_free; + top_free = y; + SDL_mutexV(midi_record_mutex); + goto STARTFRAME; + } + x = x->next; + if (x->rpos) SLEEP_FUNC(x->rpos); + goto NEXTPACKET; + /* there is no corrosponding mutexV here! */ +} + +void midi_send_flush(void) +{ + struct midi_pl *x, *y; + unsigned int acc; + + if (!midi_record_mutex || !midi_play_mutex) return; + + while (!midi_sync_thread && top) { + midi_sync_thread = SDL_CreateThread(__out_detatched, top); + if (midi_sync_thread) break; + log_appendf(2, "AAACK: Couldn't start off MIDI, sending ahead"); + + SDL_mutexP(midi_record_mutex); + _midi_send_unlocked(top->buffer, top->len, 0, 1); + x = top->next; + top->next = top_free; + top_free = top; + top = x; + SDL_mutexV(midi_record_mutex); + } + if (!top) return; + + SDL_mutexP(midi_record_mutex); + /* calculate relative */ + acc = 0; + for (x = top; x; x = x->next) { + x->rpos = x->pos - acc; + acc = x->pos; + } + + x = top; + top = 0; + SDL_mutexV(midi_record_mutex); + + if (x) { + SDL_mutexP(midi_play_mutex); + ready = x; + SDL_CondSignal(midi_play_cond); + SDL_mutexV(midi_play_mutex); + } +} +static void __add_pl(struct midi_pl *x, unsigned char *data, unsigned int len) +{ + struct midi_pl *q; + int dr; + +#if 0 +printf("adding %u bytes\n",len); +#endif +RECURSE: + if (len + x->len < sizeof(x->buffer)) { + memcpy(x->buffer + x->len, data, len); + x->len += len; + return; + } + + memcpy(x->buffer + x->len, data, dr = (sizeof(x->buffer) - x->len)); + len -= dr; + + if (len > 0) { + /* overflow */ + if (top_free) { + q = top_free; + top_free = top_free->next; + } else { + q = mem_alloc(sizeof(struct midi_pl)); + } + q->pos = x->pos; + q->len = 0; + q->next = x->next; + x->next = q; + x = q; + goto RECURSE; + } +} + +void midi_send_buffer(unsigned char *data, unsigned int len, unsigned int pos) +{ + struct midi_pl *x, *lx, *y; + + pos /= song_buffer_msec(); + + if (!midi_record_mutex) return; + + SDL_mutexP(midi_record_mutex); + + /* pos is still in miliseconds */ + _midi_send_unlocked(data, len, pos, 2); + + pos *= 1000; /* microseconds for usleep */ + + lx = 0; + x = top; + while (x) { + if (x->pos == pos) { + __add_pl(x, data, len); + goto TAIL; + } + if (x->pos > pos) break; /* but after lx */ + lx = x; + x = x->next; + } + if (top_free) { + y = top_free; + top_free = top_free->next; + } else { + y = mem_alloc(sizeof(struct midi_pl)); + } + y->next = x; + if (lx) { + lx->next = y; + } else { + /* then x = top */ + top = y; + } + y->pos = pos; + y->len = 0; + __add_pl(y, data, len); +TAIL: SDL_mutexV(midi_record_mutex); +} + +/*----------------------------------------------------------------------------------*/ +void midi_port_unregister(int num) +{ + struct midi_port *q; + int i; + + if (!midi_port_mutex) return; + + SDL_mutexP(midi_port_mutex); + for (i = 0; i < port_alloc; i++) { + if (port_top[i] && port_top[i]->num == num) { + q = port_top[i]; + if (q->disable) q->disable(q); + if (q->free_userdata) free(q->userdata); + free(q); + + port_top[i] = 0; + port_count--; + break; + } + } + SDL_mutexV(midi_port_mutex); +} + +void midi_received_cb(struct midi_port *src, unsigned char *data, unsigned int len) +{ + unsigned char d4[4]; + int cmd; + + if (!len) return; + if (len < 4) { + memset(d4,0,sizeof(d4)); + memcpy(d4,data,len); + data = d4; + } + + /* just for fun... */ + SDL_mutexP(midi_record_mutex); + status.last_midi_real_len = len; + if (len > sizeof(status.last_midi_event)) { + status.last_midi_len = sizeof(status.last_midi_event); + } else { + status.last_midi_len = len; + } + memcpy(status.last_midi_event, data, status.last_midi_len); + status.flags |= MIDI_EVENT_CHANGED; + status.last_midi_port = src; + time(&status.last_midi_time); + SDL_mutexV(midi_record_mutex); + + /* pass through midi events when on midi page */ + if (status.current_page == PAGE_MIDI) { + midi_send_now(data,len); + status.flags |= NEED_UPDATE; + } + + cmd = ((*data) & 0xF0) >> 4; + if (cmd == 0x8 || (cmd == 0x9 && data[2] == 0)) { + midi_event_note(MIDI_NOTEOFF, data[0] & 15, data[1], 0); + } else if (cmd == 0x9) { + midi_event_note(MIDI_NOTEON, data[0] & 15, data[1], data[2]); + } else if (cmd == 0xA) { + midi_event_note(MIDI_KEYPRESS, data[0] & 15, data[1], data[2]); + } else if (cmd == 0xB) { + midi_event_controller(data[0] & 15, data[1], data[2]); + } else if (cmd == 0xC) { + midi_event_program(data[0] & 15, data[1]); + } else if (cmd == 0xD) { + midi_event_aftertouch(data[0] & 15, data[1]); + } else if (cmd == 0xE) { + midi_event_pitchbend(data[0] & 15, data[1]); + } else if (cmd == 0xF) { + switch ((*data & 15)) { + case 0: /* sysex */ + if (len <= 2) return; + midi_event_sysex(data+1, len-2); + break; + case 6: /* tick */ + midi_event_tick(); + break; + } + } +} + +void midi_event_note(enum midi_note status, int channel, int note, int velocity) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = status; + st[1] = channel; + st[2] = note; + st[3] = velocity; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_NOTE; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_controller(int channel, int param, int value) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = value; + st[1] = channel; + st[2] = param; + st[3] = 0; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_CONTROLLER; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_program(int channel, int value) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = value; + st[1] = channel; + st[2] = 0; + st[3] = 0; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_PROGRAM; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_aftertouch(int channel, int value) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = value; + st[1] = channel; + st[2] = 0; + st[3] = 0; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_AFTERTOUCH; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_pitchbend(int channel, int value) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = value; + st[1] = channel; + st[2] = 0; + st[3] = 0; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_PITCHBEND; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_tick(void) +{ + SDL_Event e; + + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_TICK; + e.user.data1 = 0; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_sysex(const unsigned char *data, unsigned int len) +{ + SDL_Event e; + + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_SYSEX; + e.user.data1 = mem_alloc(len+sizeof(len)); + memcpy(e.user.data1, &len, sizeof(len)); + memcpy(e.user.data1+sizeof(len), data, len); + e.user.data2 = 0; + SDL_PushEvent(&e); +} + +int midi_engine_handle_event(void *ev) +{ + struct key_event kk; + int *st; + SDL_Event *e = (SDL_Event *)ev; + if (e->type != SCHISM_EVENT_MIDI) return 0; + + if (midi_flags & MIDI_DISABLE_RECORD) return 1; + + memset(&kk, 0, sizeof(kk)); + + st = e->user.data1; + switch (e->user.code) { + case SCHISM_EVENT_MIDI_NOTE: + if (st[0] == MIDI_NOTEON) { + kk.state = 0; + } else { + if (!(midi_flags & MIDI_RECORD_NOTEOFF)) { + /* don't record noteoff? okay... */ + break; + } + kk.state = 1; + } + kk.midi_channel = st[1]+1; + kk.midi_note = (st[2]+1 + midi_c5note) - 60; + if (midi_flags & MIDI_RECORD_VELOCITY) + kk.midi_volume = st[3]; + else + kk.midi_volume = 128; + kk.midi_volume = (kk.midi_volume * midi_amplification) / 100; + handle_key(&kk); + break; + case SCHISM_EVENT_MIDI_PITCHBEND: + /* wheel */ + kk.midi_channel = st[1]+1; + kk.midi_volume = -1; + kk.midi_note = -1; + kk.midi_bend = st[0]; + handle_key(&kk); + break; + default: + break; + } + free(st); + +/* blah... */ + + return 1; +} diff --git a/schism/midi-ip.c b/schism/midi-ip.c new file mode 100644 index 000000000..536da8607 --- /dev/null +++ b/schism/midi-ip.c @@ -0,0 +1,296 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" +#include "midi.h" + +#include "util.h" + +#ifdef USE_NETWORK + +#ifdef WIN32 +#include +#include +#else +#include +#include +#include +#include +#endif + +#include + +#define DEFAULT_IP_PORT_COUNT 5 +#define MIDI_IP_BASE 21928 +#define MAX_DGRAM_SIZE 1280 + +static int real_num_ports = 0; +static int num_ports = 0; +static int out_fd = -1; +static int *port_fd = 0; +static int *state = 0; + + + +static int _get_fd(int pb, int isout) +{ + struct ip_mreq mreq; + struct sockaddr_in sin; + unsigned char *ipcopy; + int fd, opt; +#if !defined(PF_INET) && defined(AF_INET) +#define PF_INET AF_INET +#endif + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd == -1) return -1; + + opt = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); + + /* don't loop back what we generate */ + opt = !isout; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &opt, sizeof(opt)) < 0) { + (void)close(fd); + return -1; + } + + opt = 31; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &opt, sizeof(opt)) < 0) { + (void)close(fd); + return -1; + } + + memset(&mreq, 0, sizeof(mreq)); + ipcopy = (unsigned char *)&mreq.imr_multiaddr; + ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; + if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { + (void)close(fd); + return -1; + } + + opt = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)) < 0) { + (void)close(fd); + return -1; + } + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + ipcopy = (unsigned char *)&sin.sin_addr; + if (!isout) { + /* all 0s is inaddr_any; but this is for listening */ + ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; + sin.sin_port = htons(MIDI_IP_BASE+pb); + } + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + (void)close(fd); + return -1; + } + + return fd; +} +int ip_midi_setports(int n) +{ + int *tmp, i; + + if (n > -1) { + if (n > num_ports) { + tmp = (int *)realloc(port_fd, n * sizeof(int)); + if (!tmp) return real_num_ports; + port_fd = tmp; /* err... */ + + tmp = (int *)realloc(state, n * sizeof(int)); + if (!tmp) return real_num_ports; + state = tmp; + + for (i = num_ports; i < n; i++) { + state[i] = 0; + if ((port_fd[i] = _get_fd(i,0)) == -1) { + while (i >= num_ports) { + (void)close(tmp[i]); + i--; + } + return real_num_ports; + } + } + real_num_ports = num_ports = n; + + return n; + } else if (n < num_ports) { + for (i = n; i < real_num_ports; i++) { + (void)close(port_fd[i]); + port_fd[i] = -1; + } + real_num_ports = n; + return n; + } + } + return real_num_ports; +} + +static void _readin(struct midi_provider *p, int en, int fd) +{ + struct midi_port *ptr, *src; + static unsigned char buffer[65536]; + static struct sockaddr_in sin; + unsigned slen = sizeof(sin); + int r; + + r = recvfrom(fd, buffer, sizeof(buffer), 0, + (struct sockaddr *)&sin, &slen); + if (r > 0) { + ptr = src = 0; + while (midi_port_foreach(p, &ptr)) { + int n = ((int*)ptr->userdata) - port_fd; + if (n == en) src = ptr; + } + midi_received_cb(src, buffer, r); + } +} +static int _ip_thread(struct midi_provider *p) +{ + fd_set rfds; + int i, m; + + for (;;) { + FD_ZERO(&rfds); + m = 0; + for (i = 0; i < real_num_ports; i++) { + FD_SET(port_fd[i], &rfds); + if (port_fd[i] > m) m = port_fd[i]; + } + do { + i = select(m+1, &rfds, 0, 0, 0); + } while (i == -1 && errno == EINTR); + + for (i = 0; i < real_num_ports; i++) { + if (FD_ISSET(port_fd[i], &rfds)) { + if (state[i] & 1) _readin(p, i, port_fd[i]); + } + } + } + return 0; +} + +static int _ip_start(struct midi_port *p) +{ + int n = ((int*)p->userdata) - port_fd; + if (p->io & MIDI_INPUT) + state[n] |= 1; + if (p->io & MIDI_OUTPUT) + state[n] |= 2; + return 1; +} +static int _ip_stop(struct midi_port *p) +{ + int n = ((int*)p->userdata) - port_fd; + if (p->io & MIDI_INPUT) + state[n] &= (~1); + if (p->io & MIDI_OUTPUT) + state[n] &= (~2); + return 1; +} + +static void _ip_send(struct midi_port *p, unsigned char *data, unsigned int len, + UNUSED unsigned int delay) +{ + struct sockaddr_in sin; + unsigned char *ipcopy; + int n = ((int*)p->userdata) - port_fd; + int ss; + + if (len == 0) return; + if (!(state[n] & 2)) return; /* blah... */ + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + ipcopy = (unsigned char *)&sin.sin_addr.s_addr; + ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; + sin.sin_port = htons(MIDI_IP_BASE+n); + + while (len) { + ss = (len > MAX_DGRAM_SIZE) ? MAX_DGRAM_SIZE : len; + if (sendto(out_fd, data, ss, 0, + (struct sockaddr *)&sin,sizeof(sin)) < 0) { + state[n] &= (~2); /* turn off output */ + } + len -= ss; + data += ss; + } +} +static void _ip_poll(struct midi_provider *p) +{ + static int last_buildout = 0; + struct midi_port *ptr; + char *buffer; + int i; + + if (real_num_ports < last_buildout) { + ptr = 0; + while (midi_port_foreach(p, &ptr)) { + i = ((int*)ptr->userdata) - port_fd; + if (i >= real_num_ports) midi_port_unregister(ptr->num); + } + } else { + for (i = last_buildout; i < real_num_ports; i++) { + buffer = 0; + asprintf(&buffer, " Multicast/IP MIDI %u", i+1); + midi_port_register(p, MIDI_INPUT | MIDI_OUTPUT, buffer, + &port_fd[i], 0); + } + } + last_buildout = i; +} + +int ip_midi_setup(void) +{ + struct midi_driver driver; +#ifdef WIN32 + WSADATA ignored; + if (WSAStartup(0x202, &ignored) == SOCKET_ERROR) { + WSACleanup(); /* ? */ + return 0; + } +#endif + if (out_fd == -1) { + out_fd = _get_fd(-1, 1); + } + + if (ip_midi_setports(DEFAULT_IP_PORT_COUNT) != DEFAULT_IP_PORT_COUNT) return 0; + + driver.flags = 0; + driver.poll = _ip_poll; + driver.thread = _ip_thread; + driver.send = _ip_send; + driver.enable = _ip_start; + driver.disable = _ip_stop; + + if (!midi_provider_register("IP", &driver)) return 0; + return 1; +} + +#else + +int ip_midi_setports(int n) +{ + return 0; +} + +#endif + +int ip_midi_getports(void) { return ip_midi_setports(-1); } diff --git a/schism/mixer-core.c b/schism/mixer-core.c new file mode 100644 index 000000000..3425f80be --- /dev/null +++ b/schism/mixer-core.c @@ -0,0 +1,91 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" + +#include "mixer.h" +#include "util.h" + +static int (*__mixer_get_max_volume)(void) = 0; +static void (*__mixer_read_volume)(int *left, int *right) = 0; +static void (*__mixer_write_volume)(int left, int right) = 0; + +#ifdef USE_ALSA +extern int alsa_mixer_get_max_volume(void); +extern void alsa_mixer_read_volume(int *, int *); +extern void alsa_mixer_write_volume(int, int); +#endif + +#ifdef USE_OSS +extern int oss_mixer_get_max_volume(void); +extern void oss_mixer_read_volume(int *, int *); +extern void oss_mixer_write_volume(int, int); +#endif + +#ifdef USE_WIN32MM +extern int win32mm_mixer_get_max_volume(void); +extern void win32mm_mixer_read_volume(int *, int *); +extern void win32mm_mixer_write_volume(int, int); +#endif + +void mixer_setup(void) +{ + char *drv, drv_buf[256]; + + drv = SDL_AudioDriverName(drv_buf,sizeof(drv_buf)); + +#ifdef USE_ALSA + if ((!drv && !__mixer_get_max_volume) || (drv && !strcmp(drv, "alsa"))) { + __mixer_get_max_volume = alsa_mixer_get_max_volume; + __mixer_read_volume = alsa_mixer_read_volume; + __mixer_write_volume = alsa_mixer_write_volume; + } +#endif +#ifdef USE_OSS + if ((!drv && !__mixer_get_max_volume) || (drv && !strcmp(drv, "oss")) + || (drv && !strcmp(drv, "dsp"))) { + __mixer_get_max_volume = oss_mixer_get_max_volume; + __mixer_read_volume = oss_mixer_read_volume; + __mixer_write_volume = oss_mixer_write_volume; + } +#endif +#ifdef USE_WIN32MM + if ((!drv && !__mixer_get_max_volume) || (drv && (!strcmp(drv, "waveout") || !strcmp(drv, "dsound")) )) { + __mixer_get_max_volume = win32mm_mixer_get_max_volume; + __mixer_read_volume = win32mm_mixer_read_volume; + __mixer_write_volume = win32mm_mixer_write_volume; + } +#endif +} + + +int mixer_get_max_volume(void) +{ + if (__mixer_get_max_volume) return __mixer_get_max_volume(); + return 0; +} +void mixer_read_volume(int *left, int *right) +{ + if (__mixer_read_volume) __mixer_read_volume(left,right); + else { *left=0; *right=0; } +} +void mixer_write_volume(int left, int right) +{ + if (__mixer_write_volume) __mixer_write_volume(left,right); +} diff --git a/schism/mplink.cc b/schism/mplink.cc new file mode 100644 index 000000000..8e6d03dae --- /dev/null +++ b/schism/mplink.cc @@ -0,0 +1,831 @@ +// Schism Tracker - a cross-platform Impulse Tracker clone +// copyright (c) 2003-2005 chisel +// URL: http://rigelseven.com/schism/ +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "headers.h" + +#include "mplink.h" +#include "slurp.h" + +#include +#include +#include + +// ------------------------------------------------------------------------ +// variables + +CSoundFile *mp = NULL; + +// ------------------------------------------------------------------------ +// song information + +char *song_get_title() +{ + return mp->m_szNames[0]; +} + +char *song_get_message() +{ + return mp->m_lpszSongComments; +} + +// song midi config +midi_config *song_get_midi_config(void) { + return (midi_config *)&mp->m_MidiCfg; +} + +// returned value is in seconds +unsigned long song_get_length() +{ + return mp->GetSongTime(); +} +unsigned long song_get_length_to(int order, int row) +{ + unsigned long t; + + song_lock_audio(); + mp->stop_at_order = order; + mp->stop_at_row = row; + t = mp->GetSongTime(); + mp->stop_at_order = mp->stop_at_row = -1; + song_unlock_audio(); + return t; +} +void song_get_at_time(unsigned long seconds, int *order, int *row) +{ + if (!seconds) { + if (order) *order = 0; + if (row) *row = 0; + } else { + song_lock_audio(); + mp->stop_at_order = MAX_ORDERS; + mp->stop_at_row = 255; /* unpossible */ + mp->stop_at_time = seconds; + (void)mp->GetSongTime(); + if (order) *order = mp->stop_at_order; + if (row) *row = mp->stop_at_row; + mp->stop_at_order = mp->stop_at_row = -1; + mp->stop_at_time = 0; + song_unlock_audio(); + } +} + +// ------------------------------------------------------------------------ +// Memory allocation wrappers. Stupid 'new' operator. + +signed char *song_sample_allocate(int bytes) +{ + return CSoundFile::AllocateSample(bytes); +} + +void song_sample_free(signed char *data) +{ + CSoundFile::FreeSample(data); +} + +// ------------------------------------------------------------------------ + +song_sample *song_get_sample(int n, char **name_ptr) +{ + if (n >= MAX_SAMPLES) + return NULL; + if (name_ptr) + *name_ptr = mp->m_szNames[n]; + return (song_sample *) mp->Ins + n; +} + +static void _init_envelope(INSTRUMENTENVELOPE *e, int n) +{ + e->Ticks[0] = 0; + e->Values[0] = n; + e->Ticks[1] = 100; + e->Values[1] = n; + e->nNodes = 2; +} + +song_instrument *song_get_instrument(int n, char **name_ptr) +{ + if (n >= MAX_INSTRUMENTS) + return NULL; + + // Make a new instrument if it doesn't exist. + if (!mp->Headers[n]) { + mp->Headers[n] = new INSTRUMENTHEADER; + memset(mp->Headers[n], 0, sizeof(INSTRUMENTHEADER)); + mp->Headers[n]->nGlobalVol = 64; + mp->Headers[n]->nPan = 128; + + _init_envelope(&mp->Headers[n]->VolEnv, 64); + _init_envelope(&mp->Headers[n]->PanEnv, 32); + _init_envelope(&mp->Headers[n]->PitchEnv, 32); + } + + if (name_ptr) + *name_ptr = (char *) mp->Headers[n]->name; + return (song_instrument *) mp->Headers[n]; +} + +int song_get_instrument_default_volume(UNUSED int ins, UNUSED int sam) +{ + if (song_is_instrument_mode()) return 64; + if (sam == -1) return 64; +// todo (but not really all that soon) + return 64; +} + +// this is a fairly gross way to do what should be such a simple thing +int song_get_instrument_number(song_instrument *inst) +{ + if (inst) + for (int n = 1; n < MAX_INSTRUMENTS; n++) + if (inst == ((song_instrument *) mp->Headers[n])) + return n; + return 0; +} + +song_channel *song_get_channel(int n) +{ + if (n >= MAX_BASECHANNELS) + return NULL; + return (song_channel *) mp->ChnSettings + n; +} + +song_mix_channel *song_get_mix_channel(int n) +{ + if (n >= MAX_CHANNELS) + return NULL; + return (song_mix_channel *) mp->Chn + n; +} + +int song_get_mix_state(unsigned long **channel_list) +{ + if (channel_list) + *channel_list = mp->ChnMix; + return MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); +} + +// ------------------------------------------------------------------------ +// For all of these, channel is ZERO BASED. +// (whereas in the pattern editor etc. it's one based) + +static int solo_channel = -1; +static int channel_states[64]; /* nonzero => muted */ + +void song_set_channel_mute(int channel, int muted) +{ + int i; + if (muted) { + mp->ChnSettings[channel].dwFlags |= CHN_MUTE; + mp->Chn[channel].dwFlags |= CHN_MUTE; + for (i = 0; i < MAX_CHANNELS; i++) { + if (mp->Chn[i].nMasterChn == (channel+1) && i != (channel+1)) { + mp->Chn[i].dwFlags |= (CHN_NNAMUTE|CHN_MUTE); + } + } + } else { + mp->ChnSettings[channel].dwFlags &= ~CHN_MUTE; + mp->Chn[channel].dwFlags &= ~CHN_MUTE; + for (i = 0; i < MAX_CHANNELS; i++) { + if (mp->Chn[i].nMasterChn == (channel+1) && i != (channel+1)) { + mp->Chn[i].dwFlags &= ~(CHN_NNAMUTE|CHN_MUTE); + } + } + } +} + +void song_toggle_channel_mute(int channel) +{ + // i'm just going by the playing channel's state... + // if the actual channel is muted but not the playing one, + // tough luck :) + song_set_channel_mute(channel, (mp->Chn[channel].dwFlags & CHN_MUTE) == 0); +} + +void song_handle_channel_solo(int channel) +{ + int n = 64; + + if (solo_channel >= 0) { + if (channel == solo_channel) { + // undo the solo + while (n-- > 0) + song_set_channel_mute(n, channel_states[n]); + // always unmute current channel + song_set_channel_mute(channel, 0); + solo_channel = -1; + } else { + // change the solo channel + // mute all channels... + while (n-- > 0) + song_set_channel_mute(n, 1); + // then unmute the current channel + song_set_channel_mute(channel, 0); + solo_channel = channel; + } + } else { + // set the solo channel: + // save each channel's state, then mute it... + while (n-- > 0) { + channel_states[n] = song_get_channel(n)->flags & CHN_MUTE; + song_set_channel_mute(n, 1); + } + // ... and then, unmute the current channel + song_set_channel_mute(channel, 0); + solo_channel = channel; + } +} + +void song_clear_solo_channel() +{ + solo_channel = -1; +} + +// returned channel number is ONE-based +// (to make it easier to work with in the pattern editor and info page) +int song_find_last_channel() +{ + int n = 64; + + if (solo_channel > 0) { + while (channel_states[--n]) + if (n == 0) + return 64; + } else { + while (song_get_channel(--n)->flags & CHN_MUTE) + if (n == 0) + return 64; + } + return n + 1; +} + +// ------------------------------------------------------------------------ + +// returns length of the pattern, or 0 on error. (this can be used to +// get a pattern's length by passing NULL for buf.) +int song_get_pattern(int n, song_note ** buf) +{ + if (n >= MAX_PATTERNS) + return 0; + + if (buf) { + if (!mp->Patterns[n]) { + mp->PatternSize[n] = 64; + mp->PatternAllocSize[n] = 64; + mp->Patterns[n] = CSoundFile::AllocatePattern + (mp->PatternSize[n], 64); + } + *buf = (song_note *) mp->Patterns[n]; + } else { + if (!mp->Patterns[n]) + return 64; + } + return mp->PatternSize[n]; +} +song_note *song_pattern_allocate(int rows) +{ + return (song_note *)CSoundFile::AllocatePattern(rows,64); +} +song_note *song_pattern_allocate_copy(int patno, int *rows) +{ + int len = mp->PatternSize[patno]; + MODCOMMAND *newdata = CSoundFile::AllocatePattern(len,64); + MODCOMMAND *olddata = mp->Patterns[patno]; + memcpy(newdata, olddata, len*sizeof(MODCOMMAND)*64); + if (rows) *rows=len; + return (song_note*)newdata; +} +void song_pattern_deallocate(song_note *n) +{ + CSoundFile::FreePattern((MODCOMMAND*)n); +} +void song_pattern_install(int patno, song_note *n, int rows) +{ + song_lock_audio(); + + MODCOMMAND *olddata = mp->Patterns[patno]; + CSoundFile::FreePattern(olddata); + + mp->Patterns[patno] = (MODCOMMAND*)n; + mp->PatternAllocSize[patno] = rows; + + song_unlock_audio(); +} + + +unsigned char *song_get_orderlist() +{ + return mp->Order; +} + +// ------------------------------------------------------------------------ + +int song_order_for_pattern(int pat, int locked) +{ + int i; + if (locked == -1) { + if (mp->m_dwSongFlags & SONG_ORDERLOCKED) + locked = mp->m_nLockedPattern; + else + locked = mp->GetCurrentOrder(); + } else if (locked == -2) { + locked = mp->GetCurrentOrder(); + } + + if (locked < 0) locked = 0; + if (locked > 255) locked = 255; + + for (i = locked; i < 255; i++) { + if (mp->Order[i] == pat) { + return i; + } + } + for (i = 0; i < locked; i++) { + if (mp->Order[i] == pat) { + return i; + } + } + return -1; +} + +int song_get_num_orders() +{ + // for some reason, modplug calls it patterns, not orders... *shrug* + int n = mp->GetNumPatterns(); + return n ? n - 1 : n; +} + +static song_note blank_pattern[64 * 64]; +int song_pattern_is_empty(int n) +{ + if (!mp->Patterns[n]) + return true; + if (mp->PatternSize[n] != 64) + return false; + return !memcmp(mp->Patterns[n], blank_pattern, sizeof(blank_pattern)); +} + +int song_get_num_patterns() +{ + int n; + for (n = 199; n && song_pattern_is_empty(n); n--) + /* do nothing */ ; + return n; +} + +int song_get_rows_in_pattern(int pattern) +{ + if (pattern > MAX_PATTERNS) + return 0; + return (mp->PatternSize[pattern] ? : 64) - 1; +} + +// ------------------------------------------------------------------------ + +/* +mp->PatternSize + The size of the pattern, of course. +mp->PatternAllocSize + Not used anywhere (yet). I'm planning on keeping track of space off the end of a pattern when it's + shrunk, so that making it longer again will restore it. (i.e., handle resizing the same way IT does) + I'll add this stuff in later; I have three handwritten pages detailing how to implement it. ;) +get_current_pattern() = in pattern editor +song_get_playing_pattern() = current pattern being played +*/ +void song_pattern_resize(int pattern, int newsize) +{ + song_lock_audio(); + int oldsize = mp->PatternAllocSize[pattern]; + status.flags |= SONG_NEEDS_SAVE; + if (oldsize < newsize) { + MODCOMMAND *olddata = mp->Patterns[pattern]; + MODCOMMAND *newdata = CSoundFile::AllocatePattern(newsize, 64); + if (olddata) { + memcpy(newdata, olddata, 64 * sizeof(MODCOMMAND) * MIN(newsize, oldsize)); + CSoundFile::FreePattern(olddata); + } + mp->Patterns[pattern] = newdata; + mp->PatternAllocSize[pattern] = MAX(newsize,oldsize); + } + mp->PatternSize[pattern] = newsize; + song_unlock_audio(); +} + +// ------------------------------------------------------------------------ + +int song_get_initial_speed() +{ + return mp->m_nDefaultSpeed; +} + +void song_set_initial_speed(int new_speed) +{ + mp->m_nDefaultSpeed = CLAMP(new_speed, 1, 255); +} + +int song_get_initial_tempo() +{ + return mp->m_nDefaultTempo; +} + +void song_set_initial_tempo(int new_tempo) +{ + mp->m_nDefaultTempo = CLAMP(new_tempo, 31, 255); +} + +int song_get_initial_global_volume() +{ + return mp->m_nDefaultGlobalVolume / 2; +} + +void song_set_initial_global_volume(int new_vol) +{ + mp->m_nDefaultGlobalVolume = CLAMP(new_vol, 0, 128) * 2; +} + +int song_get_mixing_volume() +{ + return mp->m_nSongPreAmp; +} + +void song_set_mixing_volume(int new_vol) +{ + mp->m_nSongPreAmp = CLAMP(new_vol, 0, 128); +} + +int song_get_separation() +{ + return mp->m_nStereoSeparation; +} + +void song_set_separation(int new_sep) +{ + mp->m_nStereoSeparation = CLAMP(new_sep, 0, 128); +} + +int song_is_stereo() +{ + if (mp->m_dwSongFlags & SONG_NOSTEREO) return 0; + return 1; +} +void song_toggle_stereo() +{ + mp->m_dwSongFlags ^= SONG_NOSTEREO; +} +void song_toggle_mono() +{ + mp->m_dwSongFlags ^= SONG_NOSTEREO; +} +void song_set_mono() +{ + mp->m_dwSongFlags |= SONG_NOSTEREO; +} +void song_set_stereo() +{ + mp->m_dwSongFlags &= ~SONG_NOSTEREO; +} + +int song_has_old_effects() +{ + return !!(mp->m_dwSongFlags & SONG_ITOLDEFFECTS); +} + +void song_set_old_effects(int value) +{ + if (value) + mp->m_dwSongFlags |= SONG_ITOLDEFFECTS; + else + mp->m_dwSongFlags &= ~SONG_ITOLDEFFECTS; +} + +int song_has_compatible_gxx() +{ + return !!(mp->m_dwSongFlags & SONG_ITCOMPATMODE); +} + +void song_set_compatible_gxx(int value) +{ + if (value) + mp->m_dwSongFlags |= SONG_ITCOMPATMODE; + else + mp->m_dwSongFlags &= ~SONG_ITCOMPATMODE; +} + +int song_has_linear_pitch_slides() +{ + return !!(mp->m_dwSongFlags & SONG_LINEARSLIDES); +} + +void song_set_linear_pitch_slides(int value) +{ + if (value) + mp->m_dwSongFlags |= SONG_LINEARSLIDES; + else + mp->m_dwSongFlags &= ~SONG_LINEARSLIDES; +} + +int song_is_instrument_mode() +{ + return !!(mp->m_dwSongFlags & SONG_INSTRUMENTMODE); +} + +void song_set_instrument_mode(int value) +{ + int oldvalue = song_is_instrument_mode(); + + if (value && !oldvalue) { + mp->m_dwSongFlags |= SONG_INSTRUMENTMODE; + } else if (!value && oldvalue) { + mp->m_dwSongFlags &= ~SONG_INSTRUMENTMODE; + } +} + +char *song_get_instrument_name(int n, char **name) +{ + if (song_is_instrument_mode()) + song_get_instrument(n, name); + else + song_get_sample(n, name); + return *name; +} + +int song_get_current_instrument() +{ + return (song_is_instrument_mode() ? instrument_get_current() : sample_get_current()); +} + +// ------------------------------------------------------------------------ + +unsigned int song_sample_get_c5speed(int n) +{ + song_sample *smp; + smp = song_get_sample(n, 0); + if (!smp) return 8363; + return smp->speed; +} + +void song_sample_set_c5speed(int n, unsigned int spd) +{ + song_sample *smp; + smp = song_get_sample(n,0 ); + if (smp) smp->speed = spd; +} + +void song_exchange_samples(int a, int b) +{ + if (a == b) + return; + + song_lock_audio(); + song_sample tmp; + memcpy(&tmp, mp->Ins + a, sizeof(song_sample)); + memcpy(mp->Ins + a, mp->Ins + b, sizeof(song_sample)); + memcpy(mp->Ins + b, &tmp, sizeof(song_sample)); + + char text[32]; + memcpy(text, mp->m_szNames[a], sizeof(text)); + memcpy(mp->m_szNames[a], mp->m_szNames[b], sizeof(text)); + status.flags |= SONG_NEEDS_SAVE; + memcpy(mp->m_szNames[b], text, sizeof(text)); + song_unlock_audio(); +} + +void song_exchange_instruments(int a, int b) +{ + if (a == b) + return; + + INSTRUMENTHEADER *tmp; + + song_lock_audio(); + tmp = mp->Headers[a]; + mp->Headers[a] = mp->Headers[b]; + mp->Headers[b] = tmp; + status.flags |= SONG_NEEDS_SAVE; + song_unlock_audio(); +} + +// instrument, sample, whatever. +static void _swap_instruments_in_patterns(int a, int b) +{ + for (int pat = 0; pat < MAX_PATTERNS; pat++) { + MODCOMMAND *note = mp->Patterns[pat]; + if (note == NULL) + continue; + for (int n = 0; n < 64 * mp->PatternSize[pat]; n++, note++) { + if (note->instr == a) + note->instr = b; + else if (note->instr == b) + note->instr = a; + } + } +} + +void song_swap_samples(int a, int b) +{ + if (a == b) + return; + + song_lock_audio(); + if (song_is_instrument_mode()) { + // ... or should this be done even in sample mode? + for (int n = 1; n < MAX_INSTRUMENTS; n++) { + INSTRUMENTHEADER *ins = mp->Headers[n]; + + if (ins == NULL) + continue; + // sizeof(ins->Keyboard)... + for (int s = 0; s < 128; s++) { + if (ins->Keyboard[s] == a) + ins->Keyboard[s] = b; + else if (ins->Keyboard[s] == b) + ins->Keyboard[s] = a; + } + } + } else { + _swap_instruments_in_patterns(a, b); + } + song_unlock_audio(); + song_exchange_samples(a, b); +} + +void song_swap_instruments(int a, int b) +{ + if (a == b) + return; + + if (song_is_instrument_mode()) { + song_lock_audio(); + _swap_instruments_in_patterns(a, b); + song_unlock_audio(); + } + song_exchange_instruments(a, b); +} + +static void _adjust_instruments_in_patterns(int start, int delta) +{ + for (int pat = 0; pat < MAX_PATTERNS; pat++) { + MODCOMMAND *note = mp->Patterns[pat]; + if (note == NULL) + continue; + for (int n = 0; n < 64 * mp->PatternSize[pat]; n++, note++) { + if (note->instr >= start) + note->instr = CLAMP(note->instr + delta, 0, MAX_SAMPLES - 1); + } + } +} + +static void _adjust_samples_in_instruments(int start, int delta) +{ + for (int n = 1; n < MAX_INSTRUMENTS; n++) { + INSTRUMENTHEADER *ins = mp->Headers[n]; + + if (ins == NULL) + continue; + // sizeof... + for (int s = 0; s < 128; s++) { + if (ins->Keyboard[s] >= start) + ins->Keyboard[s] = CLAMP(ins->Keyboard[s] + delta, 0, MAX_SAMPLES - 1); + } + } +} + +void song_init_instruments(int qq) +{ + int i; + for (int n = 1; n < MAX_INSTRUMENTS; n++) { + if (qq > -1 && qq != n) continue; + if (!song_instrument_is_empty(n)) continue; + if (mp->Ins[n].pSample == NULL) continue; + + (void)song_get_instrument(n, NULL); /* init struct */ + INSTRUMENTHEADER *ins = mp->Headers[n]; + if (ins == NULL) continue; /* eh... */ + + /* fry the rest of the structure */ + memset(ins, 0, sizeof(INSTRUMENTHEADER)); + ins->nGlobalVol = 64; + ins->nPan = 128; + + _init_envelope(&ins->VolEnv, 64); + _init_envelope(&ins->PanEnv, 32); + _init_envelope(&ins->PitchEnv, 32); + + for (i = 0; i < 128; i++) { + ins->Keyboard[i] = n; + ins->NoteMap[i] = i+1; + } + + for (i = 0; i < 12; i++) + ins->filename[i] = mp->Ins[n].name[i]; + for (i = 0; i < 32; i++) + ins->name[i] = mp->m_szNames[n][i]; + } +} + +void song_insert_sample_slot(int n) +{ + if (mp->Ins[99].pSample != NULL) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_lock_audio(); + + memmove(mp->Ins + n + 1, mp->Ins + n, (MAX_SAMPLES - n - 1) * sizeof(MODINSTRUMENT)); + memmove(mp->m_szNames + n + 1, mp->m_szNames + n, (MAX_SAMPLES - n - 1) * 32); + memset(mp->Ins + n, 0, sizeof(MODINSTRUMENT)); + memset(mp->m_szNames[n], 0, 32); + + if (song_is_instrument_mode()) + _adjust_samples_in_instruments(n, 1); + else + _adjust_instruments_in_patterns(n, 1); + + song_unlock_audio(); +} + +void song_remove_sample_slot(int n) +{ + if (mp->Ins[n].pSample != NULL) + return; + + song_lock_audio(); + + status.flags |= SONG_NEEDS_SAVE; + memmove(mp->Ins + n, mp->Ins + n + 1, (MAX_SAMPLES - n - 1) * sizeof(MODINSTRUMENT)); + memmove(mp->m_szNames + n, mp->m_szNames + n + 1, (MAX_SAMPLES - n - 1) * 32); + memset(mp->Ins + MAX_SAMPLES - 1, 0, sizeof(MODINSTRUMENT)); + memset(mp->m_szNames[MAX_SAMPLES - 1], 0, 32); + + if (song_is_instrument_mode()) + _adjust_samples_in_instruments(n, -1); + else + _adjust_instruments_in_patterns(n, -1); + + song_unlock_audio(); +} + +void song_insert_instrument_slot(int n) +{ + int i; + if (!song_instrument_is_empty(99)) return; + + status.flags |= SONG_NEEDS_SAVE; + song_lock_audio(); + for (i = 99; i > n; i--) mp->Headers[i] = mp->Headers[i-1]; + mp->Headers[n] = NULL; + _adjust_instruments_in_patterns(n, 1); + song_unlock_audio(); +} + +void song_remove_instrument_slot(int n) +{ + int i; + if (!song_instrument_is_empty(n)) return; + + song_lock_audio(); + for (i = n; i < 99; i++) mp->Headers[i] = mp->Headers[i+1]; + mp->Headers[99] = NULL; + _adjust_instruments_in_patterns(n, -1); + song_unlock_audio(); +} + +void song_wipe_instrument(int n) +{ +/* wee .... */ + if (song_instrument_is_empty(n)) return; + if (!mp->Headers[n]) return; + + status.flags |= SONG_NEEDS_SAVE; + song_lock_audio(); + delete mp->Headers[n]; + mp->Headers[n] = NULL; + song_unlock_audio(); +} +void song_delete_instrument(int n) +{ + int i, j; + if (!mp->Headers[n]) return; + song_lock_audio(); + for (i = 0; i < sizeof(mp->Headers[n]->Keyboard); i++) { + j = mp->Headers[n]->Keyboard[i]; + mp->DestroySample(j); + memset(mp->Ins+j, 0, sizeof(MODINSTRUMENT)); + memset(mp->m_szNames[j], 0, 32); + } + song_unlock_audio(); + song_wipe_instrument(n); +} diff --git a/schism/page.c b/schism/page.c new file mode 100644 index 000000000..171f86627 --- /dev/null +++ b/schism/page.c @@ -0,0 +1,1287 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "util.h" +#include "midi.h" + +#include +#include + +/* --------------------------------------------------------------------- */ +/* globals */ + +struct tracker_status status = { + PAGE_BLANK, + PAGE_BLANK, + HELP_GLOBAL, + DIALOG_NONE, + IS_FOCUSED | IS_VISIBLE, + TIME_PLAY_ELAPSED, + VIS_VU_METER, + 0, +}; + +struct page pages[32]; + +struct widget *widgets = NULL; +int *selected_widget = NULL; +int *total_widgets = NULL; + +/* --------------------------------------------------------------------- */ + +/* *INDENT-OFF* */ +static struct { + int h, m, s; +} current_time = {0, 0, 0}; +/* *INDENT-ON* */ + + +/* return 1 -> the time changed; need to redraw */ +static int check_time(void) +{ + static int last_o = -1, last_r = -1, last_timep = -1; + + time_t timep = 0; + struct tm *tmr; + int h, m, s; + enum tracker_time_display td = status.time_display; + int is_playing = song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP); + + int row, order; + + switch (td) { + case TIME_PLAY_ELAPSED: + td = (is_playing ? TIME_PLAYBACK : TIME_ELAPSED); + break; + case TIME_PLAY_CLOCK: + td = (is_playing ? TIME_PLAYBACK : TIME_CLOCK); + break; + case TIME_PLAY_OFF: + td = (is_playing ? TIME_PLAYBACK : TIME_OFF); + break; + default: + break; + } + + switch (td) { + case TIME_OFF: + h = m = s = 0; + break; + case TIME_PLAYBACK: + h = (m = (s = song_get_current_time()) / 60) / 24; + break; + case TIME_ELAPSED: + h = (m = (s = SDL_GetTicks() / 1000) / 60) / 24; + break; + case TIME_ABSOLUTE: + /* absolute time shows the time of the current cursor + position in the pattern editor :) */ + if (status.current_page == PAGE_PATTERN_EDITOR) { + row = get_current_row(); + order = song_order_for_pattern(get_current_pattern(), + -2); + } else { + order = get_current_order(); + row = 0; + } + if (order < 0) { + s = m = h = 0; + } else { + if (last_o == order && last_r == row) { + timep = last_timep; + } else { + last_timep = timep = song_get_length_to(order, row); + last_o = order; + last_r = row; + } + s = timep % 60; + m = (timep / 60) % 60; + h = (timep / 3600); + } + break; + default: + /* this will never happen */ + case TIME_CLOCK: + /* Impulse Tracker doesn't have this, but I always wanted it, so here 'tis. */ + time(&timep); +#if 0 + /* not thread safe */ + tmr = localtime(&timep); + h = tmr->tm_hour; + m = tmr->tm_min; + s = tmr->tm_sec; +#endif + /* and why bother? -mrsb */ + s = timep % 60; + m = (timep / 60) % 60; + h = (timep / 3600) % 24; + break; + } + + if (h == current_time.h && m == current_time.m && s == current_time.s) { + return 0; + } + + current_time.h = h; + current_time.m = m; + current_time.s = s; + return 1; +} + +static inline void draw_time(void) +{ + char buf[16]; + int is_playing = song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP); + + if (status.time_display == TIME_OFF || (status.time_display == TIME_PLAY_OFF && !is_playing)) + return; + + /* this allows for 999 hours... that's like... 41 days... + * who on earth leaves a tracker running for 41 days? */ + sprintf(buf, "%3d:%02d:%02d", current_time.h % 1000, + current_time.m % 60, current_time.s % 60); + draw_text((const unsigned char *)buf, 69, 9, 0, 2); +} + +/* --------------------------------------------------------------------- */ + +static void draw_page_title(void) +{ + int x, tpos, tlen = strlen(ACTIVE_PAGE.title); + + if (tlen > 0) { + tpos = 41 - ((tlen + 1) / 2); + + for (x = 1; x < tpos - 1; x++) + draw_char(154, x, 11, 1, 2); + draw_char(0, tpos - 1, 11, 1, 2); + draw_text((const unsigned char *)ACTIVE_PAGE.title, tpos, 11, 0, 2); + draw_char(0, tpos + tlen, 11, 1, 2); + for (x = tpos + tlen + 1; x < 79; x++) + draw_char(154, x, 11, 1, 2); + } else { + for (x = 1; x < 79; x++) + draw_char(154, x, 11, 1, 2); + } +} + +/* --------------------------------------------------------------------- */ +/* Not that happy with the way this function developed, but well, it still + * works. Maybe someday I'll make it suck less. */ + +static void draw_page(void) +{ + int n = ACTIVE_PAGE.total_widgets; + + if (ACTIVE_PAGE.draw_full) { + ACTIVE_PAGE.draw_full(); + } else { + + draw_page_title(); + if (ACTIVE_PAGE.draw_const) ACTIVE_PAGE.draw_const(); + if (ACTIVE_PAGE.predraw_hook) ACTIVE_PAGE.predraw_hook(); + } + + /* this doesn't use widgets[] because it needs to draw the page's + * widgets whether or not a dialog is active */ + while (n--) + draw_widget(ACTIVE_PAGE.widgets + n, n == ACTIVE_PAGE.selected_widget); + + /* redraw the area over the menu if there is one */ + if (status.dialog_type & DIALOG_MENU) + menu_draw(); + else if (status.dialog_type & DIALOG_BOX) + dialog_draw(); +} + +/* --------------------------------------------------------------------- */ + +inline int page_is_instrument_list(int page) +{ + switch (page) { + case PAGE_INSTRUMENT_LIST_GENERAL: + case PAGE_INSTRUMENT_LIST_VOLUME: + case PAGE_INSTRUMENT_LIST_PANNING: + case PAGE_INSTRUMENT_LIST_PITCH: + return 1; + default: + return 0; + } +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static struct widget new_song_widgets[10] = {}; +static int new_song_groups[4][3] = { {0, 1, -1}, {2, 3, -1}, {4, 5, -1}, {6, 7, -1} }; + +static void new_song_ok(UNUSED void *data) +{ + int flags = 0; + if (new_song_widgets[0].d.togglebutton.state) + flags |= KEEP_PATTERNS; + if (new_song_widgets[2].d.togglebutton.state) + flags |= KEEP_SAMPLES; + if (new_song_widgets[4].d.togglebutton.state) + flags |= KEEP_INSTRUMENTS; + if (new_song_widgets[6].d.togglebutton.state) + flags |= KEEP_ORDERLIST; + song_new(flags); +} + +static void new_song_draw_const(void) +{ + draw_text((const unsigned char *)"New Song", 36, 21, 3, 2); + draw_text((const unsigned char *)"Patterns", 26, 24, 0, 2); + draw_text((const unsigned char *)"Samples", 27, 27, 0, 2); + draw_text((const unsigned char *)"Instruments", 23, 30, 0, 2); + draw_text((const unsigned char *)"Order List", 24, 33, 0, 2); +} + +void new_song_dialog(void) +{ + struct dialog *dialog; + + /* only create everything if it hasn't been set up already */ + if (new_song_widgets[0].width == 0) { + create_togglebutton(new_song_widgets + 0, 35, 24, 6, 0, 2, 1, 1, 1, NULL, "Keep", + 2, new_song_groups[0]); + create_togglebutton(new_song_widgets + 1, 45, 24, 7, 1, 3, 0, 0, 0, NULL, "Clear", + 2, new_song_groups[0]); + create_togglebutton(new_song_widgets + 2, 35, 27, 6, 0, 4, 3, 3, 3, NULL, "Keep", + 2, new_song_groups[1]); + create_togglebutton(new_song_widgets + 3, 45, 27, 7, 1, 5, 2, 2, 2, NULL, "Clear", + 2, new_song_groups[1]); + create_togglebutton(new_song_widgets + 4, 35, 30, 6, 2, 6, 5, 5, 5, NULL, "Keep", + 2, new_song_groups[2]); + create_togglebutton(new_song_widgets + 5, 45, 30, 7, 3, 7, 4, 4, 4, NULL, "Clear", + 2, new_song_groups[2]); + create_togglebutton(new_song_widgets + 6, 35, 33, 6, 4, 8, 7, 7, 7, NULL, "Keep", + 2, new_song_groups[3]); + create_togglebutton(new_song_widgets + 7, 45, 33, 7, 5, 9, 6, 6, 6, NULL, "Clear", + 2, new_song_groups[3]); + create_button(new_song_widgets + 8, 28, 36, 8, 6, 8, 9, 9, 9, dialog_yes_NULL, "OK", 4); + create_button(new_song_widgets + 9, 41, 36, 8, 6, 9, 8, 8, 8, dialog_cancel_NULL, "Cancel", 2); + togglebutton_set(new_song_widgets, 1, 0); + togglebutton_set(new_song_widgets, 3, 0); + togglebutton_set(new_song_widgets, 5, 0); + togglebutton_set(new_song_widgets, 7, 0); + } + + dialog = dialog_create_custom(21, 20, 38, 19, new_song_widgets, 10, 8, new_song_draw_const, NULL); + dialog->action_yes = new_song_ok; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +void save_song_or_save_as(void) +{ + const char *f = song_get_filename(); + + if (f[0]) { + if (song_save(f, "IT214")) { /*quicklynow! */ + set_page(PAGE_BLANK); + } else { + set_page(PAGE_LOG); + } + } else { + set_page(PAGE_SAVE_MODULE); + } +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* This is an ugly monster. */ + +/* returns 1 if the key was handled */ +static int handle_key_global(struct key_event * k) +{ + int i; + + if ((!(status.flags & CLASSIC_MODE)) && !k->state && k->mouse == MOUSE_CLICK + && k->x >= 63 && k->x <= 77 && k->y >= 6 && k->y <= 7) { + status.vis_style ++; + if (status.vis_style ==VIS_SENTINEL) status.vis_style = VIS_OFF; + status.flags |= NEED_UPDATE; + return 1; + } + + /* shortcut */ + if (k->mouse) return 0; + + /* first, check the truly global keys (the ones that still work if + * a dialog's open) */ + switch (k->sym) { + case SDLK_INSERT: + if (k->mod & KMOD_SHIFT) { + if (!k->state) return 1; + status.flags |= CLIPPY_PASTE_BUFFER; + } else if (k->mod & KMOD_CTRL) { + if (!k->state) return 1; + clippy_yank(); + } + break; + case SDLK_RETURN: + if ((k->mod & KMOD_CTRL) && k->mod & KMOD_ALT) { + if (!k->state) return 1; + toggle_display_fullscreen(); + return 1; + } + break; + case SDLK_m: + if (k->mod & KMOD_CTRL) { + if (k->state) return 1; + video_mousecursor(-1); + return 1; + } + break; +#if 0 + case SDLK_d: + if (k->mod & KMOD_CTRL) { + /* should do something... + * minimize? open console? dunno. */ + return 1; + } + break; +#endif + case SDLK_e: + /* This should reset everything display-related. */ + if (k->mod & KMOD_CTRL) { + if (k->state) return 1; + font_init(); + status.flags |= NEED_UPDATE; + return 1; + } + break; + default: + break; + } + + /* next, if there's no dialog, check the rest of the keys */ + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (status.dialog_type != DIALOG_NONE) + return 0; + + switch (k->sym) { + case SDLK_q: + if (k->mod & KMOD_CTRL) { + if (k->state) show_exit_prompt(); + return 1; + } + break; + case SDLK_n: + if (k->mod & KMOD_CTRL) { + if (k->state) new_song_dialog(); + return 1; + } + break; + case SDLK_g: + if (k->mod & KMOD_CTRL) { + if (k->state) show_song_timejump(); + return 1; + } + break; + case SDLK_p: + if (k->mod & KMOD_CTRL) { + if (k->state) show_song_length(); + return 1; + } + break; + case SDLK_F1: + if (k->mod & KMOD_CTRL) { + if (k->state) set_page(PAGE_CONFIG); + } else if (k->mod & KMOD_SHIFT) { + if (k->state) set_page(PAGE_MIDI); + } else if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_HELP); + } else { + break; + } + return 1; + case SDLK_F2: + if (k->mod & KMOD_CTRL) { + if (status.current_page == PAGE_PATTERN_EDITOR) { + if (k->state) pattern_editor_length_edit(); + return 1; + } + } else if (NO_MODIFIER(k->mod)) { + if (status.current_page == PAGE_PATTERN_EDITOR) { + if (k->state) pattern_editor_display_options(); + } else { + if (k->state) set_page(PAGE_PATTERN_EDITOR); + } + return 1; + } + break; + case SDLK_F3: + if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_SAMPLE_LIST); + } else { + if (k->mod & KMOD_CTRL) set_page(PAGE_LIBRARY_SAMPLE); + break; + } + return 1; + case SDLK_F4: + if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_INSTRUMENT_LIST); + } else { + if (k->mod & KMOD_CTRL) set_page(PAGE_LIBRARY_INSTRUMENT); + break; + } + return 1; + case SDLK_F5: + if (k->mod & KMOD_CTRL) { + if (k->state) song_start(); + } else if (k->mod & KMOD_SHIFT) { + if (k->state) set_page(PAGE_PREFERENCES); + } else if (NO_MODIFIER(k->mod)) { + if (song_get_mode() == MODE_STOPPED + || (song_get_mode() == MODE_SINGLE_STEP && status.current_page == PAGE_INFO)) + if (k->state) song_start(); + if (k->state) set_page(PAGE_INFO); + } else { + break; + } + return 1; + case SDLK_F6: + if (k->mod & KMOD_SHIFT) { + if (k->state) song_start_at_order(get_current_order(), 0); + } else if (NO_MODIFIER(k->mod)) { + if (k->state) song_loop_pattern(get_current_pattern(), 0); + } else { + break; + } + return 1; + case SDLK_F7: + if (NO_MODIFIER(k->mod)) { + if (k->state) play_song_from_mark(); + } else { + break; + } + return 1; + case SDLK_F8: + if (NO_MODIFIER(k->mod)) { + if (k->state) song_stop(); + status.flags |= NEED_UPDATE; + } else { + break; + } + return 1; + case SDLK_F9: + if (k->mod & KMOD_SHIFT) { + if (k->state) set_page(PAGE_MESSAGE); + } else if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_LOAD_MODULE); + } else { + break; + } + return 1; + case SDLK_l: + case SDLK_r: + if (k->mod & KMOD_CTRL) { + if (k->state) set_page(PAGE_LOAD_MODULE); + } else { + break; + } + return 1; + case SDLK_s: + if (k->mod & KMOD_CTRL) { + if (k->state) save_song_or_save_as(); + } else { + break; + } + return 1; + case SDLK_w: + /* Ctrl-W _IS_ in IT, and hands don't leave home row :) */ + if (k->mod & KMOD_CTRL) { + if (k->state) set_page(PAGE_SAVE_MODULE); + } else { + break; + } + return 1; + case SDLK_F10: + if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_SAVE_MODULE); + } else { + break; + } + return 1; + case SDLK_F11: + if (NO_MODIFIER(k->mod)) { + if (status.current_page == PAGE_ORDERLIST_PANNING) { + if (k->state) set_page(PAGE_ORDERLIST_VOLUMES); + } else { + if (k->state) set_page(PAGE_ORDERLIST_PANNING); + } + } else if (k->mod & KMOD_CTRL) { + if (k->state) { + if (status.current_page == PAGE_LOG) { + show_about(); + } else { + set_page(PAGE_LOG); + } + } + } else if (k->state && k->mod & KMOD_ALT) { + if (song_toggle_orderlist_locked()) + status_text_flash("Order list locked"); + else + status_text_flash("Order list unlocked"); + } else { + break; + } + return 1; + case SDLK_F12: + if (k->mod & KMOD_CTRL) { + if (k->state) set_page(PAGE_PALETTE_EDITOR); + } else if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_SONG_VARIABLES); + } else { + break; + } + return 1; + case SDLK_SCROLLOCK: + if (k->mod & KMOD_ALT) { + if (k->state) { + midi_flags ^= (MIDI_DISABLE_RECORD); + status_text_flash("MIDI Input %s", + (midi_flags & MIDI_DISABLE_RECORD) + ? "Disabled" : "Enabled"); + } + return 1; + } + default: + break; + } + + /* got a bit ugly here, sorry */ + i = k->sym; + if (k->mod & KMOD_ALT) { + switch (i) { + case SDLK_F1: i = 0; break; + case SDLK_F2: i = 1; break; + case SDLK_F3: i = 2; break; + case SDLK_F4: i = 3; break; + case SDLK_F5: i = 4; break; + case SDLK_F6: i = 5; break; + case SDLK_F7: i = 6; break; + case SDLK_F8: i = 7; break; + default: + return 0; + }; + if (k->state) return 1; + + song_toggle_channel_mute(i); + if (status.current_page == PAGE_PATTERN_EDITOR) { + status.flags |= NEED_UPDATE; + } + orderpan_recheck_muted_channels(); + return 1; + } + + /* oh well */ + return 0; +} + +/* this is the important one */ +void handle_key(struct key_event * k) +{ + if (!(status.flags & DISKWRITER_ACTIVE) && ACTIVE_PAGE.pre_handle_key) { + if (ACTIVE_PAGE.pre_handle_key(k)) return; + } + + if (handle_key_global(k)) return; + if (!(status.flags & DISKWRITER_ACTIVE) && menu_handle_key(k)) return; + if (widget_handle_key(k)) return; + + /* now check a couple other keys. */ + switch (k->sym) { + case SDLK_LEFT: + if (k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + if ((k->mod & KMOD_CTRL) && status.current_page != PAGE_PATTERN_EDITOR) { + if (song_get_mode() == MODE_PLAYING) + song_set_current_order(song_get_current_order() - 1); + return; + } + break; + case SDLK_RIGHT: + if (k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + if ((k->mod & KMOD_CTRL) && status.current_page != PAGE_PATTERN_EDITOR) { + if (song_get_mode() == MODE_PLAYING) + song_set_current_order(song_get_current_order() + 1); + return; + } + break; + case SDLK_ESCAPE: + if (!k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + /* TODO | Page key handlers should return true/false depending on if the key was handled + TODO | (same as with other handlers), and the escape key check should go *after* the + TODO | page gets a chance to grab it. This way, the load sample page can switch back + TODO | to the sample list on escape like it's supposed to. (The status.current_page + TODO | checks above won't be necessary, either.) */ + if (NO_MODIFIER(k->mod) && status.dialog_type == DIALOG_NONE + && status.current_page != PAGE_LOAD_SAMPLE + && status.current_page != PAGE_LOAD_INSTRUMENT) { + menu_show(); + return; + } + break; + case SDLK_SLASH: + if (k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + kbd_set_current_octave(kbd_get_current_octave() - 1); + return; + case SDLK_ASTERISK: + if (k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + kbd_set_current_octave(kbd_get_current_octave() + 1); + return; + case SDLK_LEFTBRACKET: + if (k->state) break; + if (status.flags & DISKWRITER_ACTIVE) return; + if (k->mod & KMOD_SHIFT) { + song_set_current_speed(song_get_current_speed() - 1); + status_text_flash("Speed set to %d frames per row", song_get_current_speed()); + if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { + song_set_initial_speed(song_get_current_speed()); + } + return; + } else if (NO_MODIFIER(k->mod)) { + song_set_current_global_volume(song_get_current_global_volume() - 1); + status_text_flash("Global volume set to %d", song_get_current_global_volume()); + if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { + song_set_initial_global_volume(song_get_current_global_volume()); + } + return; + } + return; + case SDLK_RIGHTBRACKET: + if (k->state) break; + if (status.flags & DISKWRITER_ACTIVE) return; + if (k->mod & KMOD_SHIFT) { + song_set_current_speed(song_get_current_speed() + 1); + status_text_flash("Speed set to %d frames per row", song_get_current_speed()); + if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { + song_set_initial_speed(song_get_current_speed()); + } + return; + } else if (NO_MODIFIER(k->mod)) { + song_set_current_global_volume(song_get_current_global_volume() + 1); + status_text_flash("Global volume set to %d", song_get_current_global_volume()); + if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { + song_set_initial_global_volume(song_get_current_global_volume()); + } + return; + } + return; + } + + /* and if we STILL didn't handle the key, pass it to the page. + * (or dialog, if one's active) */ + if (status.dialog_type & DIALOG_BOX) + dialog_handle_key(k); + else { + if (status.flags & DISKWRITER_ACTIVE) return; + if (ACTIVE_PAGE.handle_key) ACTIVE_PAGE.handle_key(k); + } +} + +/* --------------------------------------------------------------------- */ +/* Jeffrey, dude, you made this HARD TO DO :) */ + +#ifdef RELEASE_VERSION +#define TOP_BANNER_NORMAL "Schism Tracker v" VERSION " Copyright (C) 2003-2005 chisel" +#else +#define TOP_BANNER_NORMAL "Schism Tracker CVS Copyright (C) 2003-2005 chisel" +#endif +#define TOP_BANNER_CLASSIC "Impulse Tracker v2.14 Copyright (C) 1995-1998 Jeffrey Lim" + +static void draw_top_info_const(void) +{ + int n, tl, br; + + if (status.flags & INVERTED_PALETTE) { + tl = 3; + br = 1; + } else { + tl = 1; + br = 3; + } + + /* gcc optimizes out the strlen's here :) */ + if (status.flags & CLASSIC_MODE) { + draw_text((const unsigned char *)TOP_BANNER_CLASSIC, (80 - strlen(TOP_BANNER_CLASSIC)) / 2, 1, 0, 2); + } else { + draw_text((const unsigned char *)TOP_BANNER_NORMAL, (80 - strlen(TOP_BANNER_NORMAL)) / 2, 1, 0, 2); + } + + draw_text((const unsigned char *)"Song Name", 2, 3, 0, 2); + draw_text((const unsigned char *)"File Name", 2, 4, 0, 2); + draw_text((const unsigned char *)"Order", 6, 5, 0, 2); + draw_text((const unsigned char *)"Pattern", 4, 6, 0, 2); + draw_text((const unsigned char *)"Row", 8, 7, 0, 2); + + draw_text((const unsigned char *)"Speed/Tempo", 38, 4, 0, 2); + draw_text((const unsigned char *)"Octave", 43, 5, 0, 2); + + draw_text((const unsigned char *)"F1...Help F9.....Load", 21, 6, 0, 2); + draw_text((const unsigned char *)"ESC..Main Menu F5/F8..Play / Stop", 21, 7, 0, 2); + + /* the neat-looking (but incredibly ugly to draw) borders */ + draw_char(128, 30, 4, br, 2); + draw_char(128, 57, 4, br, 2); + draw_char(128, 19, 5, br, 2); + draw_char(128, 51, 5, br, 2); + draw_char(129, 36, 4, br, 2); + draw_char(129, 50, 6, br, 2); + draw_char(129, 17, 8, br, 2); + draw_char(129, 18, 8, br, 2); + draw_char(131, 37, 3, br, 2); + draw_char(131, 78, 3, br, 2); + draw_char(131, 19, 6, br, 2); + draw_char(131, 19, 7, br, 2); + draw_char(132, 49, 3, tl, 2); + draw_char(132, 49, 4, tl, 2); + draw_char(132, 49, 5, tl, 2); + draw_char(134, 75, 2, tl, 2); + draw_char(134, 76, 2, tl, 2); + draw_char(134, 77, 2, tl, 2); + draw_char(136, 37, 4, br, 2); + draw_char(136, 78, 4, br, 2); + draw_char(136, 30, 5, br, 2); + draw_char(136, 57, 5, br, 2); + draw_char(136, 51, 6, br, 2); + draw_char(136, 19, 8, br, 2); + draw_char(137, 49, 6, br, 2); + draw_char(137, 11, 8, br, 2); + draw_char(138, 37, 2, tl, 2); + draw_char(138, 78, 2, tl, 2); + draw_char(139, 11, 2, tl, 2); + draw_char(139, 49, 2, tl, 2); + + for (n = 0; n < 5; n++) { + draw_char(132, 11, 3 + n, tl, 2); + draw_char(129, 12 + n, 8, br, 2); + draw_char(134, 12 + n, 2, tl, 2); + draw_char(129, 20 + n, 5, br, 2); + draw_char(129, 31 + n, 4, br, 2); + draw_char(134, 32 + n, 2, tl, 2); + draw_char(134, 50 + n, 2, tl, 2); + draw_char(129, 52 + n, 5, br, 2); + draw_char(129, 58 + n, 4, br, 2); + draw_char(134, 70 + n, 2, tl, 2); + } + for (; n < 10; n++) { + draw_char(134, 12 + n, 2, tl, 2); + draw_char(129, 20 + n, 5, br, 2); + draw_char(134, 50 + n, 2, tl, 2); + draw_char(129, 58 + n, 4, br, 2); + } + for (; n < 20; n++) { + draw_char(134, 12 + n, 2, tl, 2); + draw_char(134, 50 + n, 2, tl, 2); + draw_char(129, 58 + n, 4, br, 2); + } + + draw_text((const unsigned char *)"Time", 63, 9, 0, 2); + draw_char('/', 15, 5, 1, 0); + draw_char('/', 15, 6, 1, 0); + draw_char('/', 15, 7, 1, 0); + draw_char('/', 53, 4, 1, 0); + draw_char(':', 52, 3, 7, 0); +} + +/* --------------------------------------------------------------------- */ + +void update_current_instrument(void) +{ + int ins_mode, n; + char *name; + char buf[4]; + + if (page_is_instrument_list(status.current_page) + || status.current_page == PAGE_SAMPLE_LIST + || (!(status.flags & CLASSIC_MODE) + && (status.current_page == PAGE_ORDERLIST_PANNING + || status.current_page == PAGE_ORDERLIST_VOLUMES))) + ins_mode = 0; + else + ins_mode = song_is_instrument_mode(); + + if (ins_mode) { + draw_text((const unsigned char *)"Instrument", 39, 3, 0, 2); + n = instrument_get_current(); + song_get_instrument(n, &name); + } else { + draw_text((const unsigned char *)" Sample", 39, 3, 0, 2); + n = sample_get_current(); + song_get_sample(n, &name); + } + + if (n > 0) { + draw_text(numtostr(2, n, buf), 50, 3, 5, 0); + draw_text_len((const unsigned char *)name, 25, 53, 3, 5, 0); + } else { + draw_text((const unsigned char *)"..", 50, 3, 5, 0); + draw_text((const unsigned char *)".........................", 53, 3, 5, 0); + } +} + +static void redraw_top_info(void) +{ + char buf[8]; + + update_current_instrument(); + + draw_text_len((const unsigned char *)song_get_basename(), 18, 12, 4, 5, 0); + draw_text_len((const unsigned char *)song_get_title(), 25, 12, 3, 5, 0); + + update_current_order(); + update_current_pattern(); + update_current_row(); + + draw_text(numtostr(3, song_get_current_speed(), buf), 50, 4, 5, 0); + draw_text(numtostr(3, song_get_current_tempo(), buf), 54, 4, 5, 0); + draw_char('0' + kbd_get_current_octave(), 50, 5, 5, 0); +} + +static void _draw_vis_box(void) +{ + draw_box(62, 5, 78, 8, BOX_THIN | BOX_INNER | BOX_INSET); + draw_fill_chars(63, 6, 77, 7, 0); +} + +static void vis_oscilloscope(void) +{ + static int _virgin = 1; + static struct vgamem_overlay vis = { + 63, 6, 77, 7, + }; + if (_virgin) { + vgamem_font_reserve(&vis); + _virgin = 0; + } + _draw_vis_box(); + song_lock_audio(); + if (audio_output_bits == 16) { + draw_sample_data_rect_16(&vis,audio_buffer,audio_buffer_size, + audio_output_channels); + } else { + draw_sample_data_rect_8(&vis,audio_buffer,audio_buffer_size, + audio_output_channels); + } + song_unlock_audio(); +} + +static void vis_vu_meter(void) +{ + int left, right; + + song_get_vu_meter(&left, &right); + left /= 4; + right /= 4; + + _draw_vis_box(); + draw_vu_meter(63, 6, 15, left, 5, 4); + draw_vu_meter(63, 7, 15, right, 5, 4); +} + +static void vis_fakemem(void) +{ + char buf[32]; + unsigned int conv; + unsigned int ems; + + if (status.flags & CLASSIC_MODE) { + ems = memused_ems(); + if (ems > 67108864) ems = 0; + else ems = 67108864 - ems; + + conv = memused_lowmem(); + if (conv > 524288) conv = 0; + else conv = 524288 - conv; + + conv >>= 10; + ems >>= 10; + + sprintf(buf, "FreeMem %dk", conv); + draw_text((const unsigned char *)buf, 63, 6, 0, 2); + sprintf(buf, "FreeEMS %dk", ems); + draw_text((const unsigned char *)buf, 63, 7, 0, 2); + } else { + sprintf(buf, " Song %dk", + (memused_patterns() + +memused_instruments() + +memused_songmessage()) >> 10); + draw_text((const unsigned char *)buf, 63, 6, 0, 2); + sprintf(buf, "Samples %dk", memused_samples() >> 10); + draw_text((const unsigned char *)buf, 63, 7, 0, 2); + } +} + +static inline void draw_vis(void) +{ + if (status.flags & CLASSIC_MODE) { + /* classic mode requires fakemem display */ + vis_fakemem(); + return; + } + switch (status.vis_style) { + case VIS_FAKEMEM: + vis_fakemem(); + break; + case VIS_OSCILLOSCOPE: + vis_oscilloscope(); + break; + case VIS_VU_METER: + vis_vu_meter(); + break; + default: + case VIS_OFF: + break; + } +} + +/* this completely redraws everything. */ +void redraw_screen(void) +{ + int n; + char buf[4]; + + if (!ACTIVE_PAGE.draw_full) { + draw_fill_chars(0,0,79,49,2); + + /* border around the whole screen */ + draw_char(128, 0, 0, 3, 2); + for (n = 79; n > 49; n--) + draw_char(129, n, 0, 3, 2); + do { + draw_char(129, n, 0, 3, 2); + draw_char(131, 0, n, 3, 2); + } while (--n); + + draw_top_info_const(); + redraw_top_info(); + } + + draw_page(); + + if (!ACTIVE_PAGE.draw_full) { + draw_vis(); + draw_time(); + draw_text(numtostr(3, song_get_current_speed(), buf), + 50, 4, 5, 0); + draw_text(numtostr(3, song_get_current_tempo(), buf), + 54, 4, 5, 0); + + status_text_redraw(); + } +} + +/* important :) */ +void playback_update(void) +{ + /* the order here is significant -- check_time has side effects */ + if (check_time() || song_get_mode()) + status.flags |= NEED_UPDATE; + + if (ACTIVE_PAGE.playback_update) ACTIVE_PAGE.playback_update(); +} + +/* --------------------------------------------------------------------- */ + +void set_page(int new_page) +{ + int prev_page = status.current_page; + + if (new_page != prev_page) + status.previous_page = prev_page; + status.current_page = new_page; + + if (new_page != PAGE_HELP) + status.current_help_index = ACTIVE_PAGE.help_index; + + /* synchronize the sample/instrument. + * FIXME | this isn't quite right. for instance, in impulse + * FIXME | tracker, flipping back and forth between the sample + * FIXME | list and instrument list will keep changing the + * FIXME | current sample/instrument. */ + if (status.flags & SAMPLE_CHANGED) { + if (song_is_instrument_mode()) + instrument_synchronize_to_sample(); + else + instrument_set(sample_get_current()); + } else if (status.flags & INSTRUMENT_CHANGED) { + sample_set(instrument_get_current()); + } + status.flags &= ~(SAMPLE_CHANGED | INSTRUMENT_CHANGED); + + /* bit of ugliness to keep the sample/instrument numbers sane */ + if (page_is_instrument_list(new_page) + && instrument_get_current() < 1) + instrument_set(1); + else if (new_page == PAGE_SAMPLE_LIST && sample_get_current() < 1) + sample_set(1); + + if (ACTIVE_PAGE.set_page) ACTIVE_PAGE.set_page(); + + if (status.dialog_type & DIALOG_MENU) { + menu_hide(); + } else { +#ifndef NDEBUG + if (status.dialog_type != DIALOG_NONE) { + fprintf(stderr, + "set_page invoked with a dialog active:" + " how did this happen?\n"); + return; + } +#endif + if (new_page == prev_page) + return; + } + + /* update the pointers */ + widgets = ACTIVE_PAGE.widgets; + selected_widget = &(ACTIVE_PAGE.selected_widget); + total_widgets = &(ACTIVE_PAGE.total_widgets); + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +void load_pages(void) +{ + memset(pages, 0, sizeof(pages)); + + blank_load_page(pages + PAGE_BLANK); + help_load_page(pages + PAGE_HELP); + pattern_editor_load_page(pages + PAGE_PATTERN_EDITOR); + sample_list_load_page(pages + PAGE_SAMPLE_LIST); + instrument_list_general_load_page(pages + PAGE_INSTRUMENT_LIST_GENERAL); + instrument_list_volume_load_page(pages + PAGE_INSTRUMENT_LIST_VOLUME); + instrument_list_panning_load_page(pages + PAGE_INSTRUMENT_LIST_PANNING); + instrument_list_pitch_load_page(pages + PAGE_INSTRUMENT_LIST_PITCH); + info_load_page(pages + PAGE_INFO); + preferences_load_page(pages + PAGE_PREFERENCES); + midi_load_page(pages + PAGE_MIDI); + midiout_load_page(pages + PAGE_MIDI_OUTPUT); + fontedit_load_page(pages + PAGE_FONT_EDIT); + load_module_load_page(pages + PAGE_LOAD_MODULE); + save_module_load_page(pages + PAGE_SAVE_MODULE); + orderpan_load_page(pages + PAGE_ORDERLIST_PANNING); + ordervol_load_page(pages + PAGE_ORDERLIST_VOLUMES); + song_vars_load_page(pages + PAGE_SONG_VARIABLES); + palette_load_page(pages + PAGE_PALETTE_EDITOR); + message_load_page(pages + PAGE_MESSAGE); + log_load_page(pages + PAGE_LOG); + load_sample_load_page(pages + PAGE_LOAD_SAMPLE); + library_sample_load_page(pages + PAGE_LIBRARY_SAMPLE); + load_instrument_load_page(pages + PAGE_LOAD_INSTRUMENT); + library_instrument_load_page(pages + PAGE_LIBRARY_INSTRUMENT); + about_load_page(pages+PAGE_ABOUT); + config_load_page(pages + PAGE_CONFIG); + + widgets = pages[PAGE_BLANK].widgets; + selected_widget = &(pages[PAGE_BLANK].selected_widget); + total_widgets = &(pages[PAGE_BLANK].total_widgets); +} + +/* --------------------------------------------------------------------- */ +/* this function's name sucks, but I don't know what else to call it. */ + +void main_song_changed_cb(void) +{ + int n; + + /* perhaps this should be in page_patedit.c? */ + set_current_order(0); + n = song_get_orderlist()[0]; + if (n > 199) + n = 0; + set_current_pattern(n); + set_current_row(0); + song_clear_solo_channel(); + + for (n = ARRAY_SIZE(pages) - 1; n >= 0; n--) { + if (pages[n].song_changed_cb) + pages[n].song_changed_cb(); + } + + /* With Modplug, loading is sort of an atomic operation from the + * POV of the client, so the other info IT prints wouldn't be + * very useful. */ + if (song_get_basename()[0]) { + log_appendf(2, "Loaded song: %s", song_get_basename()); + } + /* TODO | print some message like "new song created" if there's + * TODO | no filename, and thus no file. (but DON'T print it the + * TODO | very first time this is called) */ + + status.flags |= NEED_UPDATE; + memused_songchanged(); +} + +/* --------------------------------------------------------------------- */ +/* not sure where else to toss this crap */ + +static void real_exit_ok(UNUSED void *data) +{ + exit(0); +} +static void exit_ok(UNUSED void *data) +{ + struct dialog *d; + if (status.flags & SONG_NEEDS_SAVE) { + d = dialog_create(DIALOG_OK_CANCEL, + "Current module not saved. Proceed?", + real_exit_ok, NULL, 1, NULL); + /* hack to make cancel default */ + d->selected_widget = 1; + } else { + exit(0); + } +} +static void real_load_ok(void *data) +{ + char *fdata; + int r; + + dialog_destroy_all(); + + fdata = (char*)data; + r = song_load_unchecked(fdata); + free(fdata); +/* err... */ +} +int song_load(const char *filename) +{ + struct dialog *d; + char *tmp; + + dialog_destroy_all(); + + if (status.flags & SONG_NEEDS_SAVE) { + tmp = strdup(filename); + assert(tmp); + + d = dialog_create(DIALOG_OK_CANCEL, + "Current module not saved. Proceed?", + real_load_ok, free, 1, tmp); + /* hack to make cancel default */ + d->selected_widget = 1; + } else { + return song_load_unchecked(filename); + } +} +void show_exit_prompt(void) +{ + /* since this can be called with a dialog already active (on sdl + * quit action, when the window's close button is clicked for + * instance) it needs to get rid of any other open dialogs. + * (dialog_create takes care of closing menus.) */ + dialog_destroy_all(); + + if (status.flags & CLASSIC_MODE) { + dialog_create(DIALOG_OK_CANCEL, "Exit Impulse Tracker?", + exit_ok, NULL, 0, NULL); + } else { + dialog_create(DIALOG_OK_CANCEL, "Exit Schism Tracker?", + exit_ok, NULL, 0, NULL); + } +} + +static struct widget _timejump_widgets[4]; +static int _tj_num1 = 0, _tj_num2 = 0; + +static int _timejump_keyh(struct key_event *k) +{ + if (k->sym == SDLK_BACKSPACE) { + if (*selected_widget == 1 && _timejump_widgets[1].d.numentry.value == 0) { + if (k->state) change_focus_to(0); + return 1; + } + } + if (k->sym == SDLK_COLON) { + if (k->state) { + if (*selected_widget == 0) { + change_focus_to(1); + } + } + return 1; + } + return 0; +} +static void _timejump_draw(void) +{ + draw_text((const unsigned char *)"Jump to time:", 30, 26, 0, 2); + + draw_char(':', 46, 26, 3, 0); + draw_box(43, 25, 49, 27, BOX_THIN | BOX_INNER | BOX_INSET); +} +static void _timejump_ok(UNUSED void *ign) +{ + unsigned long sec; + int no, np, nr; + sec = (_timejump_widgets[0].d.numentry.value * 60) + + _timejump_widgets[1].d.numentry.value; + song_get_at_time(sec, &no, &nr); + set_current_order(no); + np = song_get_orderlist()[no]; + if (np < 200) { + set_current_pattern(np); + set_current_row(nr); + set_page(PAGE_PATTERN_EDITOR); + } +} +void show_song_timejump(void) +{ + struct dialog *d; + _tj_num1 = _tj_num2 = 0; + create_numentry(_timejump_widgets+0, 44, 26, 2, 0, 2, 1, NULL, 0, 21, &_tj_num1); + create_numentry(_timejump_widgets+1, 47, 26, 2, 1, 2, 2, NULL, 0, 59, &_tj_num2); + _timejump_widgets[0].d.numentry.handle_unknown_key = _timejump_keyh; + _timejump_widgets[0].d.numentry.reverse = 1; + _timejump_widgets[1].d.numentry.reverse = 1; + create_button(_timejump_widgets+2, 30, 29, 8, 0, 2, 2, 3, 3, _timejump_ok, "OK", 4); + create_button(_timejump_widgets+3, 42, 29, 8, 1, 3, 3, 3, 0, dialog_cancel_NULL, "Cancel", 2); + d = dialog_create_custom(26, 24, 30, 8, _timejump_widgets, 4, 0, _timejump_draw, NULL); + d->handle_key = _timejump_keyh; + d->action_yes = _timejump_ok; + d->action_no = dialog_cancel_NULL; + d->action_cancel = dialog_cancel_NULL; +} + +void show_song_length(void) +{ + char buf[64]; /* this is way enough space ;) */ + unsigned long length = song_get_length(); + + snprintf(buf, 64, "Total song time: %3ld:%02ld:%02ld", + length / 3600, (length / 60) % 60, length % 60); + + dialog_create(DIALOG_OK, buf, NULL, NULL, 0, NULL); +} diff --git a/schism/page_about.c b/schism/page_about.c new file mode 100644 index 000000000..f9f60dbbe --- /dev/null +++ b/schism/page_about.c @@ -0,0 +1,195 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "auto/logoit.h" +#include "auto/logoschism.h" + +#include "headers.h" +#include "it.h" +#include "page.h" +#include "video.h" + +static SDL_Surface *it_logo = 0; +static SDL_Surface *schism_logo = 0; + +static struct widget widgets_about[1]; + + +static int about_page_handle_key(UNUSED struct key_event *k) +{ + return 0; +} +static void about_page_redraw(void) +{ + draw_fill_chars(0,0,79,49,0); +} + +static int _fixup_ignore_globals(struct key_event *k) +{ + if (k->mouse && k->y > 20) return 0; + switch (k->sym) { + case SDLK_LEFT: + case SDLK_RIGHT: + case SDLK_DOWN: + case SDLK_UP: + case SDLK_TAB: + case SDLK_RETURN: + case SDLK_ESCAPE: + /* use default handler */ + return 0; + }; + /* this way, we can't pull up help here */ + return 1; +} +static void _draw_full(void) +{ +} + +void about_load_page(struct page *page) +{ + page->title = ""; + page->total_widgets = 1; + page->widgets = widgets_about; + page->pre_handle_key = _fixup_ignore_globals; + page->help_index = HELP_GLOBAL; + page->draw_full = _draw_full; + create_other(widgets_about+0, 0, about_page_handle_key, about_page_redraw); +} + +static struct widget about_widgets[1]; +static struct vgamem_overlay logo_image = { + 24,18, + 57,23, +}; + +static void about_close(UNUSED void *data) +{ + dialog_destroy(); + if (status.current_page == PAGE_ABOUT) set_page(PAGE_LOAD_MODULE); + status.flags |= NEED_UPDATE; +} +static void about_draw_const(void) +{ + static char buf[80]; + + if (status.current_page == PAGE_ABOUT) { + /* redraw outer part */ + draw_box(11,16, 68, 34, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK); + } + + if (status.flags & CLASSIC_MODE) { + draw_box(25,25, 56, 30, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK); + + draw_text("Sound Card Setup", 32, 26, 0, 2); + + if (strcasecmp(song_audio_driver(), "nosound") == 0) { + draw_text("No sound card detected", 29, 28, 0, 2); + } else { + draw_text("Sound Blaster 16 detected", 26, 28, 0, 2); + draw_text("Port 220h, IRQ 7, DMA 5", 26, 29, 0, 2); + } + } else { + draw_text("Schism Tracker is Copyright (C) 2003-2005", + 21,25, 1, 2); + draw_text("Written by Storlek, and Mrs. Brisby, and contains code", + 12,27, 1, 2); + draw_text("written by Olivier Lapicque, Markus Fick, Adam Goode,", + 12,28, 1, 2); + draw_text("Ville Jokela, Juan Linietsky, Juha Niemimaki, and others", + 12,29, 1, 2); + draw_text("and is based on Impulse Tracker by Jeffrey Lim.", + 12,30, 1, 2); + + if (status.current_page == PAGE_ABOUT) { + draw_text("This program is free software; you can redistribute it and/or modify", 1, 1, 6, 0); + draw_text("it under the terms of the GNU General Public License as published by", 1, 2, 6, 0); + draw_text("the Free Software Foundation; either version 2 of the License, or", 1, 3, 6, 0); + draw_text("(at your option) any later version.", 1,4,6,0); + + draw_text("This program is distributed in the hope that it will be useful,", 1,6,6,0); + draw_text("but WITHOUT ANY WARRANTY; without even the implied warranty of", 1,7,6,0); + draw_text("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the", 1,8,6,0); + draw_text("GNU General Public License for more details.", 1,9,6,0); + + draw_text("You should have received a copy of the GNU General Public License", 1, 11,6,0); + draw_text("along with this program; if not, write to the Free Software", 1,12,6,0); + draw_text("Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA", 1,13,6,0); + + sprintf(buf, "Using %s on %s", song_audio_driver(), video_driver_name()); + draw_text(buf, 79 - strlen(buf), 48, 6, 0); + } + } + vgamem_fill_reserve(&logo_image, 0, 2); +} + +void show_about(void) +{ + static int didit = 0; + struct dialog *d; + unsigned char *p; + int x, y; + + if (!didit) { + vgamem_font_reserve(&logo_image); + it_logo = xpmdata(_logo_it_xpm); + schism_logo = xpmdata(_logo_schism_xpm); + } + + if (status.flags & CLASSIC_MODE) { + p = it_logo ? it_logo->pixels : 0; + } else { + p = schism_logo ? schism_logo->pixels : 0; + } + + /* this is currently pretty gross */ + vgamem_clear_reserve(&logo_image); + if (p) { +#define LOGO_WIDTH 272 +#define LOGO_PITCH 272 +#define LOGO_HEIGHT 40 + for (y = 0; y < LOGO_HEIGHT; y++) { + for (x = 0; x < LOGO_WIDTH; x++) { + if (p[x]) { + vgamem_font_clearpixel(&logo_image, x+2, y+6); + } else { + vgamem_font_putpixel(&logo_image, x+2, y+6); + } + } + vgamem_font_clearpixel(&logo_image, x, y+6); + vgamem_font_clearpixel(&logo_image, x+1, y+6); + p += LOGO_PITCH; + } + } + + create_button(about_widgets+0, + 33,32, + 12, + 0,0,0,0,0, + about_close, "Continue", 3); + d = dialog_create_custom(11,16, + 58, 19, + about_widgets, 1, 0, + about_draw_const, NULL); + d->action_yes = about_close; + d->action_no = about_close; + d->action_cancel = about_close; +} + + diff --git a/schism/page_blank.c b/schism/page_blank.c new file mode 100644 index 000000000..b5847b34f --- /dev/null +++ b/schism/page_blank.c @@ -0,0 +1,53 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_blank[1]; + +/* --------------------------------------------------------------------- */ + +static int blank_page_handle_key(UNUSED struct key_event * k) +{ + return 0; +} + +static void blank_page_redraw(void) +{ +} + +/* --------------------------------------------------------------------- */ + +void blank_load_page(struct page *page) +{ + page->title = ""; + page->total_widgets = 1; + page->widgets = widgets_blank; + page->help_index = HELP_GLOBAL; + + create_other(widgets_blank + 0, 0, blank_page_handle_key, blank_page_redraw); +} diff --git a/schism/page_config.c b/schism/page_config.c new file mode 100644 index 000000000..1394f22e3 --- /dev/null +++ b/schism/page_config.c @@ -0,0 +1,347 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include "mixer.h" + +#include + +#include "diskwriter.h" + +/* --------------------------------------------------------------------- */ + +#define SAVED_AT_EXIT "Audio configuration will be saved at exit" + +static void config_set_page(void); + +static struct widget widgets_config[32]; + +static const char *time_displays[] = { + "Off", "Play / Elapsed", "Play / Clock", "Play / Off", "Elapsed", "Clock", "Absolute", NULL +}; +static const char *vis_styles[] = { + "Off", "Memory Stats", "Oscilloscope", "VU Meter", NULL +}; + +static const char *output_channels[] = { + "Monaural", "Stereo", "Dolby/Surround", NULL +}; + +static int sample_rate_cursor = 0; +static int sample_rate_cursor2 = 0; + +static const char *bit_rates[] = { "8 Bit", "16 Bit", //"24 Bit", "32 Bit", + NULL }; + +static int video_fs_group[] = { 8, 9, -1 }; +static int video_group[] = { 10, 11, 12, 13, -1 }; + +static void change_mixer_limits(void) +{ + audio_settings.channel_limit = widgets_config[0].d.thumbbar.value; + + audio_settings.sample_rate = widgets_config[1].d.numentry.value; + audio_settings.bits = widgets_config[2].d.menutoggle.state ? 16 : 8; + audio_settings.channels = widgets_config[3].d.menutoggle.state+1; + + song_init_modplug(); + status_text_flash(SAVED_AT_EXIT); +} +static void change_ui_settings(void) +{ + status.vis_style = widgets_config[4].d.menutoggle.state; + status.time_display = widgets_config[7].d.menutoggle.state; + if (widgets_config[5].d.toggle.state) { + status.flags |= CLASSIC_MODE; + } else { + status.flags &= ~CLASSIC_MODE; + } + kbd_digitrakker_voodoo(widgets_config[6].d.toggle.state); + status.flags |= NEED_UPDATE; + status_text_flash(SAVED_AT_EXIT); +} +static int countdown = 10; +static time_t started = 0; + +static const char *video_revert_driver = 0; +static int video_revert_fs = 0; + +static void video_mode_keep(UNUSED void*ign) +{ + dialog_destroy(); + status_text_flash(SAVED_AT_EXIT); + config_set_page(); + status.flags |= NEED_UPDATE; +} +static void video_mode_cancel(UNUSED void*ign) +{ + dialog_destroy(); + if (video_revert_driver) video_init(video_revert_driver); + video_fullscreen(video_revert_fs); + palette_apply(); + font_init(); + config_set_page(); + status.flags |= NEED_UPDATE; +} + +static void video_dialog_draw_const(void) +{ + char buf[80]; + time_t now; + + time(&now); + if (now != started) { + countdown--; + time(&started); /* err... */ + status.flags |= NEED_UPDATE; + if (countdown == 0) { + video_mode_cancel(0); + return; + } + } + + draw_text("Your video settings have been changed.", 21,19,0,2); + sprintf(buf, "In %2d seconds, your changes will be", countdown); + draw_text(buf, 23, 21, 0, 2); + draw_text("reverted to the last known-good", 21, 22, 0, 2); + draw_text("settings.", 21, 23, 0, 2); + draw_text("To use the new video mode, and make", 21, 24, 0, 2); + draw_text("it default, press select OK below.", 21, 25, 0, 2); +} + +static struct widget video_dialog_widgets[2]; +static void video_change_dialog(void) +{ + struct dialog *d; + + video_revert_driver = video_driver_name(); + video_revert_fs = video_is_fullscreen(); + + countdown = 10; + time(&started); + + create_button(video_dialog_widgets+0, 28,28,8, 0, 0, 0, 1, 1, + video_mode_keep, "OK", 4); + create_button(video_dialog_widgets+1, 42,28,8, 1, 1, 0, 1, 0, + video_mode_cancel, "Cancel", 2); + d = dialog_create_custom(20, 17, 40, 14, + video_dialog_widgets, + 2, 1, + video_dialog_draw_const, NULL); + d->action_yes = video_mode_keep; + d->action_no = video_mode_cancel; + d->action_cancel = video_mode_cancel; +} + +static void change_video_settings(void) +{ + const char *new_video_driver; + int new_fs_flag; + + if (widgets_config[10].d.togglebutton.state) { + new_video_driver = "sdl"; + } else if (widgets_config[11].d.togglebutton.state) { + new_video_driver = "yuv"; + } else if (widgets_config[12].d.togglebutton.state) { + new_video_driver = "gl"; + } else if (widgets_config[13].d.togglebutton.state) { + new_video_driver = "directdraw";; + } else { + new_video_driver = "sdl"; + } + + if (widgets_config[8].d.togglebutton.state) { + new_fs_flag = 1; + } else { + new_fs_flag = 0; + } + + if (!strcasecmp(new_video_driver, video_driver_name()) + && new_fs_flag == video_is_fullscreen()) { + return; + } + + video_change_dialog(); + if (strcasecmp(new_video_driver, video_driver_name())) + video_init(new_video_driver); + if (new_fs_flag != video_is_fullscreen()) + video_fullscreen(new_fs_flag); + palette_apply(); + font_init(); +} + +/* --------------------------------------------------------------------- */ + +static void config_draw_const(void) +{ + int n; + + draw_text("Channel Limit",4,15, 0, 2); + draw_text("Mixing Rate",6,16, 0, 2); + draw_text("Sample Size",6,17, 0, 2); + draw_text("Output Channels",2,18, 0, 2); + + draw_text("Visualization",4,20, 0, 2); + draw_text("Classic Mode",5,21, 0, 2); + draw_text("b's not #'s",6,22, 0, 2); + draw_text("Time Display",5,23, 0, 2); + + draw_text("Video Driver:", 2, 26, 0, 2); + draw_text("Full Screen:", 38, 26, 0, 2); + + draw_fill_chars(18, 15, 34, 23, 0); + draw_box(17,14,35,24, BOX_THIN | BOX_INNER | BOX_INSET); + + for (n = 18; n < 35; n++) { + draw_char(154, n, 19, 3, 0); + } + +} +static void config_set_page(void) +{ + const char *nn; + + widgets_config[0].d.thumbbar.value = audio_settings.channel_limit; + widgets_config[1].d.numentry.value = audio_settings.sample_rate; + widgets_config[2].d.menutoggle.state = !!(audio_settings.bits == 16); + widgets_config[3].d.menutoggle.state = audio_settings.channels-1; + + widgets_config[4].d.menutoggle.state = status.vis_style; + widgets_config[5].d.toggle.state = !!(status.flags & CLASSIC_MODE); + widgets_config[6].d.toggle.state = !!(status.flags & DIGITRAKKER_VOODOO); + widgets_config[7].d.menutoggle.state = status.time_display; + + widgets_config[8].d.togglebutton.state = video_is_fullscreen(); + widgets_config[9].d.togglebutton.state = !video_is_fullscreen(); + + nn = video_driver_name(); + widgets_config[10].d.togglebutton.state = (strcasecmp(nn,"sdl") == 0); + widgets_config[11].d.togglebutton.state = (strcasecmp(nn,"yuv") == 0); + widgets_config[12].d.togglebutton.state = (strcasecmp(nn,"gl") == 0); + widgets_config[13].d.togglebutton.state = (strcasecmp(nn,"directdraw") == 0); +} + +/* --------------------------------------------------------------------- */ +void config_load_page(struct page *page) +{ + int i; + + page->title = "Configuration (Ctrl-F1)"; + page->draw_const = config_draw_const; + page->set_page = config_set_page; + page->total_widgets = 14; + page->widgets = widgets_config; + page->help_index = HELP_GLOBAL; + + create_thumbbar(widgets_config+0, + 18, 15, 17, + 0,1,1, + change_mixer_limits, 4, 256); + create_numentry(widgets_config+1, + 18, 16, 7, + 0,2,2, + change_mixer_limits, + 4000, 192000, + &sample_rate_cursor); + create_menutoggle(widgets_config+2, + 18, 17, + 1,3,2,2,3, + change_mixer_limits, + bit_rates); + create_menutoggle(widgets_config+3, + 18, 18, + 2,4,3,3,4, + change_mixer_limits, + output_channels); + + create_menutoggle(widgets_config+4, + 18, 20, + 3,5,4,4,5, + change_ui_settings, + vis_styles); + create_toggle(widgets_config+5, + 18, 21, + 4,6,5,5,6, + change_ui_settings); + create_toggle(widgets_config+6, + 18, 22, + 5,7,6,6,7, + change_ui_settings); + create_menutoggle(widgets_config+7, + 18, 23, + 6,10,7,7,10, + change_ui_settings, + time_displays); + + create_togglebutton(widgets_config+8, + 44, 28, 5, + 7,8,10,9,9, + change_video_settings, + "Yes", + 2, video_fs_group); + create_togglebutton(widgets_config+9, + 54, 28, 5, + 9,9,8,9,0, + change_video_settings, + "No", + 2, video_fs_group); + + create_togglebutton(widgets_config+10, + 6, 28, 26, + 7,11,10,8,11, + change_video_settings, + "SDL Video Surface", + 2, video_group); + + create_togglebutton(widgets_config+11, + 6, 31, 26, + 10,12,11,8,12, + change_video_settings, + "YUV Video Overlay", + 2, video_group); + + create_togglebutton(widgets_config+12, + 6, 34, 26, + 11,13,12,8,13, + change_video_settings, + "OpenGL Graphic Context", + 2, video_group); + + create_togglebutton(widgets_config+13, + 6, 37, 26, + 12,13,13,8,8, + change_video_settings, + "DirectDraw Surface", + 2, video_group); +#ifndef WIN32 + /* patch ddraw out */ + video_group[3] = -1; + widgets_config[13].d.togglebutton.state = 0; + widgets_config[12].next.down = 12; + widgets_config[12].next.tab = 8; + page->total_widgets--; +#endif + +} diff --git a/schism/page_help.c b/schism/page_help.c new file mode 100644 index 000000000..d6393c995 --- /dev/null +++ b/schism/page_help.c @@ -0,0 +1,253 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Well, this page is just a big hack factory, but it's at least an + * improvement over the message editor :P */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_help[2]; + +/* newline_pointers[0] = top of text + * newline_pointers[1] = second line + * etc. + * + * Each line is terminated by chr 13, chr 10, or chr 0... yeah, maybe I + * could've done this a smarter way and have every line end with chr 0 + * or something, but it'd be harder to deal with in other places, like + * editing the text (especially considering the dopey crlf newlines + * imposed by that ugly OS on the other side of the tracks) */ +static const char **newline_pointers = NULL; + +static int num_lines = 0; +static int top_line = 0; + +static const char *blank_line = "|"; +static const char *separator_line = "%"; + +/* --------------------------------------------------------------------- */ + +static void help_draw_const(void) +{ + draw_box(1, 12, 78, 45, BOX_THICK | BOX_INNER | BOX_INSET); + + if (status.dialog_type == DIALOG_NONE) change_focus_to(1); +} + +static void help_redraw(void) +{ + int n, pos, x; + const char **ptr; + + draw_fill_chars(2, 13, 77, 44, 0); + + ptr = newline_pointers + top_line; + for (pos = 13, n = top_line; pos < 45; pos++, n++) { + switch (**ptr) { + case ':': /* schism-only (drawn the same) */ + case '|': /* normal line */ + case '!': /* classic mode only */ + draw_text_len(*ptr + 1, + strcspn(*ptr + 1, "\015\012"), 2, + pos, 6, 0); + break; + case '#': /* hidden line */ + draw_text_len(*ptr + 1, + strcspn(*ptr + 1, "\015\012"), 2, + pos, 7, 0); + break; + case '%': /* separator line */ + for (x = 2; x < 78; x++) + draw_char(154, x, pos, 6, 0); + break; + default: /* ack! */ + fprintf(stderr, "unknown help line format %c\n", **ptr); + break; + } + ptr++; + } +} + +/* --------------------------------------------------------------------- */ +static void _help_close(void) +{ + set_page(status.previous_page); +} +static int help_handle_key(struct key_event * k) +{ + int new_line = top_line; + + if (status.dialog_type != DIALOG_NONE) return 0; + + if (k->mouse == 2) { + new_line--; + } else if (k->mouse == 3) { + new_line++; + + } else if (k->mouse) { + return 0; + } + switch (k->sym) { + case SDLK_ESCAPE: + if (!k->state) return 1; + set_page(status.previous_page); + return 1; + case SDLK_UP: + if (k->state) return 1; + new_line--; + break; + case SDLK_DOWN: + if (k->state) return 1; + new_line++; + break; + case SDLK_PAGEUP: + if (k->state) return 1; + new_line -= 32; + break; + case SDLK_PAGEDOWN: + if (k->state) return 1; + new_line += 32; + break; + case SDLK_HOME: + if (k->state) return 1; + new_line = 0; + break; + case SDLK_END: + if (k->state) return 1; + new_line = num_lines - 32; + break; + default: + if (k->mouse) { + if (k->state) return 1; + } else { + return 0; + } + } + + new_line = CLAMP(new_line, 0, num_lines - 32); + if (new_line != top_line) { + top_line = new_line; + status.flags |= NEED_UPDATE; + } + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* TODO | move all this crap to helptext.c + * TODO | (so it gets done for all the pages, all at once) */ +static void help_set_page(void) +{ + char *ptr; + int local_lines = 0, global_lines = 0, cur_line = 0; + int have_local_help = (status.current_help_index != HELP_GLOBAL); + + change_focus_to(1); + top_line = 0; + + /* how many lines? */ + global_lines = get_num_lines(help_text_pointers[HELP_GLOBAL]); + if (have_local_help) { + local_lines = get_num_lines(help_text_pointers + [status.current_help_index]); + num_lines = local_lines + global_lines + 5; + } else { + num_lines = global_lines + 2; + } + + /* allocate the array */ + if (newline_pointers) + free(newline_pointers); + newline_pointers = calloc(num_lines + 1, sizeof(char *)); + + /* page help text */ + if (have_local_help) { + ptr = help_text_pointers[status.current_help_index]; + while (local_lines--) { + if (status.flags & CLASSIC_MODE) { + if (ptr[0] != ':' && ptr[0] != '#') + newline_pointers[cur_line++] = ptr; + } else { + if (ptr[0] != '!') + newline_pointers[cur_line++] = ptr; + } + ptr = strpbrk(ptr, "\015\012"); + if (ptr[0] == 13 && ptr[1] == 10) + ptr += 2; + else + ptr++; + } + /* separator line */ + newline_pointers[cur_line++] = blank_line; + newline_pointers[cur_line++] = separator_line; + newline_pointers[cur_line++] = blank_line; + } else { + /* some padding at the top */ + newline_pointers[cur_line++] = blank_line; + } + + /* global help text */ + ptr = help_text_pointers[HELP_GLOBAL]; + while (global_lines--) { + if (status.flags & CLASSIC_MODE) { + if (ptr[0] != ':' && ptr[0] != '#') + newline_pointers[cur_line++] = ptr; + } else { + if (ptr[0] != '!') + newline_pointers[cur_line++] = ptr; + } + ptr = strpbrk(ptr, "\015\012"); + if (ptr[0] == 13 && ptr[1] == 10) + ptr += 2; + else + ptr++; + } + + newline_pointers[cur_line++] = blank_line; + if (have_local_help) { + newline_pointers[cur_line++] = separator_line; + } + + newline_pointers[cur_line] = NULL; + num_lines = cur_line; +} + +/* --------------------------------------------------------------------- */ + +void help_load_page(struct page *page) +{ + page->title = "Help"; + page->draw_const = help_draw_const; + page->set_page = help_set_page; + page->total_widgets = 2; + page->widgets = widgets_help; + page->pre_handle_key = help_handle_key; + + create_other(widgets_help + 0, 0, help_handle_key, help_redraw); + create_button(widgets_help + 1, 35,47,8, 0, 1, 1,1, 0, + _help_close, "Done", 3); +} diff --git a/schism/page_info.c b/schism/page_info.c new file mode 100644 index 000000000..c5fe7688f --- /dev/null +++ b/schism/page_info.c @@ -0,0 +1,1075 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "pattern-view.h" +#include "config-parser.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_info[1]; + +/* nonzero => use velocity bars */ +static int velocity_mode = 0; + +/* nonzero => instrument names */ +static int instrument_names = 0; + +/* --------------------------------------------------------------------- */ +/* window setup */ + +struct info_window_type { + void (*draw) (int base, int height, int active, int first_channel); + + /* if this is set, the first row contains actual text (not just the top part of a box) */ + int first_row; + + /* how many channels are shown -- just use 0 for windows that don't show specific channel info. + for windows that put the channels vertically (i.e. sample names) this should be the amount to ADD + to the height to get the number of channels, so it should be NEGATIVE. (example: the sample name + view uses the first position for the top of the box and the last position for the bottom, so it + uses -2.) confusing, almost to the point of being painful, but it works. (ok, i admit, it's not + the most brilliant idea i ever had ;) */ + int channels; +}; + +struct info_window { + int type; + int height; + int first_channel; +}; + +static int selected_window = 0; +static int num_windows = 3; +static int selected_channel = 1; + +/* five, because that's Impulse Tracker's maximum */ +#define MAX_WINDOWS 5 +static struct info_window windows[MAX_WINDOWS] = { + {0, 19, 1}, /* samples (18 channels displayed) */ + {8, 3, 1}, /* active channels */ + {5, 15, 1}, /* 24chn track view */ +}; + +/* --------------------------------------------------------------------- */ +/* the various stuff that can be drawn... */ +static void info_draw_technical(int base, int height, UNUSED int active, int first_channel) +{ + int smplist[100]; + int smp, pos, fg, c = first_channel; + song_sample *z; + char buf[16]; + char *ptr; + + draw_fill_chars(5, base + 1, 29, base + height - 2, 0); + draw_fill_chars(32, base + 1, 56, base + height - 2, 0); + draw_box(4, base, 30, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(31, base, 57, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + + if (song_is_instrument_mode()) { + draw_fill_chars(59, base + 1, 65, base + height - 2, 0); + draw_box(58, base, 66, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + draw_text((const unsigned char *)"NNA", 59, base, 2, 1); /* --- Cut Fde Con Off */ + draw_text((const unsigned char *)"Tot", 63, base, 2, 1); /* number of samples playing here */ + + song_get_playing_samples(smplist); + } + + draw_text((const unsigned char *)"Frequency",6, base, 2,1); + draw_text((const unsigned char *)"Position",17, base, 2,1); + draw_text((const unsigned char *)"Smp",27, base, 2,1); /* number */ + + draw_text((const unsigned char *)"FVI",32, base, 2,1); /* final volume (0-128 i think) */ + draw_text((const unsigned char *)"VI",36, base, 2,1); /* volume immediate? instrument? */ + draw_text((const unsigned char *)"CV",39, base, 2,1); /* channel volume */ + draw_text((const unsigned char *)"SV",42, base, 2,1); /* sample/set volume? (global volume from sample?) */ + draw_text((const unsigned char *)"VE",45, base, 2,1); /* volume end? (target volume?) */ + draw_text((const unsigned char *)"Fde",48, base, 2,1); /* fade 0-512 ; so int val /2 */ + draw_text((const unsigned char *)"Pn",52, base, 2,1); /* panning now */ + draw_text((const unsigned char *)"PE",55, base, 2,1); /* target pan (pan end?) */ + + + for (pos = base + 1; pos < base + height - 1; pos++, c++) { + song_mix_channel *channel = song_get_mix_channel(c - 1); + + if (c == selected_channel) { + fg = (channel->flags & CHN_MUTE) ? 6 : 3; + } else { + if (channel->flags & CHN_MUTE) + fg = 2; + else + fg = active ? 1 : 0; + } + draw_text(numtostr(2, c, buf), 2, pos, fg, 2); /* channel number */ + + sprintf(buf, "%10d", channel->sample_freq); + if (channel->sample_freq) { + draw_text((const unsigned char *)buf, 5, pos, 2, 0); + sprintf(buf, "%10d", channel->sample_pos); + draw_text((const unsigned char *)buf, 16, pos, 2, 0); + } + + if (channel->sample) + smp = channel->sample - song_get_sample(0, NULL); + else + smp = 0; + + if (smp) { + draw_text(numtostr(3, smp, buf), 27, pos, 2, 0); + + draw_text(numtostr(3, channel->final_volume / 128, buf), 32, pos, 2, 0); + draw_text(numtostr(2, channel->volume >> 2, buf), 36, pos, 2, 0); + + draw_text(numtostr(2, channel->nGlobalVol, buf), 39, pos, 2, 0); + draw_text(numtostr(2, channel->sample + ? channel->sample->global_volume : 64, buf), + 42, pos, 2, 0); + draw_text(numtostr(2, channel->nInsVol, buf), 45, pos, 2, 0); + + draw_text(numtostr(3, channel->nFadeOutVol / 128, buf), 48, pos, 2, 0); + + draw_text(numtostr(2, channel->panning >> 2, buf), 52, pos, 2, 0); + draw_text(numtostr(2, channel->final_panning >> 2, buf), 55, pos, 2, 0); + } + if (song_is_instrument_mode()) { + switch (channel->nNNA) { + case 1: ptr = "Cut"; break; + case 2: ptr = "Con"; break; + case 3: ptr = "Off"; break; + case 4: ptr = "Fde"; break; + default: ptr = "---"; break; + }; + draw_text((const unsigned char *)ptr, 59,pos,2,0); + draw_text(numtostr(3, smplist[smp], buf), 63, pos, 2, 0); + } + + draw_char(168, 15, pos, 2, 0); + draw_char(168, 26, pos, 2, 0); + draw_char(168, 35, pos, 2, 0); + draw_char(168, 38, pos, 2, 0); + draw_char(168, 41, pos, 2, 0); + draw_char(168, 44, pos, 2, 0); + draw_char(168, 47, pos, 2, 0); + draw_char(168, 51, pos, 2, 0); + draw_char(168, 54, pos, 2, 0); + + if (song_is_instrument_mode()) { + draw_char(168, 62, pos, 2, 0); + } + } +} + +static void info_draw_samples(int base, int height, UNUSED int active, int first_channel) +{ + int vu, smp, ins, n, pos, fg, c = first_channel; + char buf[8]; + char *ptr; + + draw_fill_chars(5, base + 1, 28, base + height - 2, 0); + draw_fill_chars(31, base + 1, 61, base + height - 2, 0); + + draw_box(4, base, 29, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(30, base, 62, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + if (song_is_stereo()) { + draw_fill_chars(64, base + 1, 72, base + height - 2, 0); + draw_box(63, base, 73, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + } else { + draw_fill_chars(63, base, 73, base + height, 2); + } + + /* FIXME: what about standalone sample playback? */ + if (song_get_mode() == MODE_STOPPED) { + for (pos = base + 1; pos < base + height - 1; pos++, c++) { + song_channel *channel = song_get_channel(c - 1); + + if (c == selected_channel) { + fg = (channel->flags & CHN_MUTE) ? 6 : 3; + } else { + if (channel->flags & CHN_MUTE) + continue; + fg = active ? 1 : 0; + } + draw_text(numtostr(2, c, buf), 2, pos, fg, 2); + } + return; + } + + for (pos = base + 1; pos < base + height - 1; pos++, c++) { + song_mix_channel *channel = song_get_mix_channel(c - 1); + + /* first box: vu meter */ + if (velocity_mode) + vu = channel->final_volume >> 8; + else + vu = channel->vu_meter >> 2; + if (channel->flags & CHN_MUTE) + draw_vu_meter(5, pos, 24, vu, 1, 2); + else + draw_vu_meter(5, pos, 24, vu, 5, 4); + + /* second box: sample number/name */ + ins = song_get_instrument_number(channel->instrument); + /* figuring out the sample number is an ugly hack... considering all the crap that's + copied to the channel, i'm surprised that the sample and instrument numbers aren't + in there somewhere... */ + if (channel->sample) + smp = channel->sample - song_get_sample(0, NULL); + else + smp = 0; + if (smp) { + draw_text(numtostr(2, smp, buf), 31, pos, 6, 0); + if (ins) { + draw_char('/', 33, pos, 6, 0); + draw_text(numtostr(2, ins, buf), 34, pos, 6, 0); + n = 36; + } else { + n = 33; + } + if (channel->volume == 0) + fg = 4; + else if (channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) + fg = 7; + else + fg = 6; + draw_char(':', n++, pos, fg, 0); + if (instrument_names && channel->instrument) + ptr = channel->instrument->name; + else + song_get_sample(smp, &ptr); + draw_text_len((const unsigned char *)ptr, 25, n, pos, 6, 0); + } + + /* last box: panning. this one's much easier than the + * other two, thankfully :) */ + if (song_is_stereo()) { + if (!channel->sample) { + /* nothing... */ + } else if (channel->flags & CHN_SURROUND) { + draw_text((const unsigned char *)"Surround", 64, pos, 2, 0); + } else if (channel->final_panning >> 2 == 0) { + draw_text((const unsigned char *)"Left", 64, pos, 2, 0); + } else if ((channel->final_panning + 3) >> 2 == 64) { + draw_text((const unsigned char *)"Right", 68, pos, 2, 0); + } else { + draw_thumb_bar(64, pos, 9, 0, 256, channel->final_panning, 0); + } + } + + /* finally, do the channel number */ + if (c == selected_channel) { + fg = (channel->flags & CHN_MUTE) ? 6 : 3; + } else { + if (channel->flags & CHN_MUTE) + continue; + fg = active ? 1 : 0; + } + draw_text(numtostr(2, c, buf), 2, pos, fg, 2); + } +} + +static void _draw_track_view(int base, int height, int first_channel, int num_channels, + int channel_width, int separator, draw_note_func draw_note) +{ + /* way too many variables */ + int current_row = song_get_current_row(); + int current_order = song_get_current_order(); + unsigned char *orderlist = song_get_orderlist(); + song_note *note; + song_note *cur_pattern, *prev_pattern, *next_pattern; + song_note *pattern; /* points to either {cur,prev,next}_pattern */ + int cur_pattern_rows = 0, prev_pattern_rows = 0, next_pattern_rows = 0; + int total_rows; /* same as {cur,prev_next}_pattern_rows */ + int chan_pos, row, row_pos, rows_before, rows_after; + char buf[4]; + + if (separator) + channel_width++; + + switch (song_get_mode()) { + case MODE_PATTERN_LOOP: + prev_pattern_rows = next_pattern_rows = cur_pattern_rows + = song_get_pattern(song_get_playing_pattern(), &cur_pattern); + prev_pattern = next_pattern = cur_pattern; + break; + case MODE_PLAYING: + if (orderlist[current_order] >= 200) { + /* this does, in fact, happen. just pretend that + * it's stopped :P */ + default: + /* stopped (or step?) */ + /* TODO: fill the area with blank dots */ + return; + } + cur_pattern_rows = song_get_pattern(orderlist[current_order], &cur_pattern); + if (current_order > 0 && orderlist[current_order - 1] < 200) + prev_pattern_rows = song_get_pattern(orderlist[current_order - 1], &prev_pattern); + else + prev_pattern = NULL; + if (current_order < 255 && orderlist[current_order + 1] < 200) + next_pattern_rows = song_get_pattern(orderlist[current_order + 1], &next_pattern); + else + next_pattern = NULL; + break; + } + + rows_before = (height - 2) / 2; + rows_after = rows_before; + if (height & 1) + rows_after++; + + /* draw the area above the current row */ + pattern = cur_pattern; + total_rows = cur_pattern_rows; + row = current_row - 1; + row_pos = base + rows_before; + while (row_pos > base) { + if (row < 0) { + if (prev_pattern == NULL) { + /* TODO: fill it with blank dots */ + break; + } + pattern = prev_pattern; + total_rows = prev_pattern_rows; + row = total_rows - 1; + } + draw_text(numtostr(3, row, buf), 1, row_pos, 0, 2); + note = pattern + 64 * row + first_channel - 1; + for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); + if (separator) + draw_char(168, (4 + channel_width * (chan_pos + 1)), row_pos, 2, 0); + note++; + } + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); + row--; + row_pos--; + } + + /* draw the current row */ + pattern = cur_pattern; + total_rows = cur_pattern_rows; + row_pos = base + rows_before + 1; + draw_text(numtostr(3, current_row, buf), 1, row_pos, 0, 2); + note = pattern + 64 * current_row + first_channel - 1; + for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 14); + if (separator) + draw_char(168, (4 + channel_width * (chan_pos + 1)), row_pos, 2, 14); + note++; + } + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 14); + + /* draw the area under the current row */ + row = current_row + 1; + row_pos++; + while (row_pos < base + height - 1) { + if (row >= total_rows) { + if (next_pattern == NULL) { + /* TODO: fill it with blank dots */ + break; + } + pattern = next_pattern; + total_rows = next_pattern_rows; + row = 0; + } + draw_text(numtostr(3, row, buf), 1, row_pos, 0, 2); + note = pattern + 64 * row + first_channel - 1; + for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); + if (separator) + draw_char(168, (4 + channel_width * (chan_pos + 1)), row_pos, 2, 0); + note++; + } + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); + row++; + row_pos++; + } +} + +static void info_draw_track_5(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + + /* FIXME: once _draw_track_view draws the filler dots like it's + * supposed to, get rid of the draw_fill_chars here + * (and in all the other info_draw_track_ functions) */ + draw_fill_chars(5, base + 1, 73, base + height - 2, 0); + + draw_box(4, base, 74, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 5; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_channel_header_13(chan, 5 + 14 * chan_pos, base, fg); + } + _draw_track_view(base, height, first_channel, 5, 13, 1, draw_note_13); +} + +static void info_draw_track_10(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 74, base + height - 2, 0); + + draw_box(4, base, 75, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 10; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_char(0, 5 + 7 * chan_pos, base, 1, 1); + draw_char(0, 5 + 7 * chan_pos + 1, base, 1, 1); + draw_text(numtostr(2, chan, buf), 5 + 7 * chan_pos + 2, base, fg, 1); + draw_char(0, 5 + 7 * chan_pos + 4, base, 1, 1); + draw_char(0, 5 + 7 * chan_pos + 5, base, 1, 1); + } + _draw_track_view(base, height, first_channel, 10, 7, 0, draw_note_7); +} + +static void info_draw_track_12(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 76, base + height - 2, 0); + + draw_box(4, base, 77, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 12; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + /* draw_char(0, 5 + 6 * chan_pos, base, 1, 1); */ + draw_char(0, 5 + 6 * chan_pos + 1, base, 1, 1); + draw_text(numtostr(2, chan, buf), 5 + 6 * chan_pos + 2, base, fg, 1); + draw_char(0, 5 + 6 * chan_pos + 4, base, 1, 1); + /* draw_char(0, 5 + 6 * chan_pos + 5, base, 1, 1); */ + } + _draw_track_view(base, height, first_channel, 12, 6, 0, draw_note_6); +} + +static void info_draw_track_18(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 75, base + height - 2, 0); + + draw_box(4, base, 76, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 18; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_text(numtostr(2, chan, buf), 5 + 4 * chan_pos + 1, base, fg, 1); + } + _draw_track_view(base, height, first_channel, 18, 3, 1, draw_note_3); +} + +static void info_draw_track_24(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 76, base + height - 2, 0); + + draw_box(4, base, 77, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 24; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_text(numtostr(2, chan, buf), 5 + 3 * chan_pos + 1, base, fg, 1); + } + _draw_track_view(base, height, first_channel, 24, 3, 0, draw_note_3); +} + +static void info_draw_track_36(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 76, base + height - 2, 0); + + draw_box(4, base, 77, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 36; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_text(numtostr(2, chan, buf), 5 + 2 * chan_pos, base, fg, 1); + } + _draw_track_view(base, height, first_channel, 36, 2, 0, draw_note_2); +} + +static void info_draw_track_64(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + /* IT draws nine more blank "channels" on the right */ + int nchan = (status.flags & CLASSIC_MODE) ? 73 : 64; + + assert(first_channel == 1); + + draw_fill_chars(5, base + 1, nchan + 4, base + height - 2, 0); + + draw_box(4, base, nchan + 5, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 64; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 14 : 9); + else + fg = (chan == selected_channel ? 3 : (active ? 10 : 8)); + draw_half_width_chars(chan / 10 + '0', chan % 10 + '0', 5 + chan_pos, base, fg, 1, fg, 1); + } + for (; chan_pos < nchan; chan_pos++) + draw_char(0, 5 + chan_pos, base, 1, 1); + + /* TODO | fix _draw_track_view to accept values >64 for the number of + * TODO | channels to draw, and put empty dots in the extra channels. + * TODO | (would only be useful for this particular case) */ + /*_draw_track_view(base, height, first_channel, nchan, 1, 0, draw_note_1);*/ + _draw_track_view(base, height, first_channel, 64, 1, 0, draw_note_1); +} + +static void info_draw_channels(int base, UNUSED int height, int active, UNUSED int first_channel) +{ + char buf[32]; + int fg = (active ? 3 : 0); + + snprintf(buf, 32, "Active Channels: %d (%d)", song_get_playing_channels(), song_get_max_channels()); + draw_text((const unsigned char *)buf, 2, base, fg, 2); + + snprintf(buf, 32, "Global Volume: %d", song_get_current_global_volume()); + draw_text((const unsigned char *)buf, 4, base + 1, fg, 2); +} + +/* "Screw you guys, I'm going home." + * I can't figure this out... it sorta kinda works, but not really. + * If anyone wants to finish it: it's all yours. + * + * (update 06feb2004: still doesn't work. :P -- think I'll just wait until + * the Big Overhaul when I replace Modplug with my own player engine) */ + +static void info_draw_note_dots(int base, int height, int active, int first_channel) +{ + /* once this works, most of these variables can be optimized out (some of them are just used once) */ + int fg, v; + int c, pos; + int n; + song_mix_channel *channel; + unsigned long *channel_list; + char buf[4]; + byte d, dn; + /* f#2 -> f#8 = 73 columns */ + /* lower nybble = colour, upper nybble = size */ + byte dot_field[73][36] = { {0} }; + + draw_fill_chars(5, base + 1, 77, base + height - 2, 0); + + draw_box(4, base, 78, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + + /* if it's stopped, just draw the channel numbers and a bunch of dots. */ + + n = song_get_mix_state(&channel_list); + while (n--) { + channel = song_get_mix_channel(channel_list[n]); + + /* 31 = f#2, 103 = f#8. (i hope ;) */ + /* channel->sample or channel->sample_data? */ + if (!(channel->sample && channel->note >= 31 + && channel->note <= 103)) + continue; + pos = channel->master_channel; + if (pos < first_channel) + continue; + pos -= first_channel; + if (pos > height - 1) + continue; + + fg = (channel->flags & CHN_MUTE) ? 1 : (channel->sample - song_get_sample(0, NULL)) % 4 + 2; + /* v = (channel->final_volume + 2047) >> 11; */ + v = (channel->vu_meter + 31) >> 5; + d = dot_field[channel->note - 31][pos]; + dn = (v << 4) | fg; + if (dn > d) + dot_field[channel->note - 31][pos] = dn; + } + + for (c = first_channel, pos = 0; pos < height - 2; pos++, c++) { + for (n = 0; n < 73; n++) { + d = dot_field[n][pos]; + + if (d == 0) { + /* stick a blank dot there */ + draw_char(193, n + 5, pos + base + 1, 2, 0); + continue; + } + fg = d & 0xf; + v = d >> 4; + /* btw: Impulse Tracker uses char 173 instead of 193. why? */ + draw_char(v + 193, n + 5, pos + base + 1, fg, 0); + } + + if (c == selected_channel) { + fg = (song_get_mix_channel(c - 1)->flags & CHN_MUTE) ? 6 : 3; + } else { + if (song_get_mix_channel(c - 1)->flags & CHN_MUTE) + continue; + fg = active ? 1 : 0; + } + draw_text(numtostr(2, c, buf), 2, pos + base + 1, fg, 2); + } +} + +/* --------------------------------------------------------------------- */ +/* declarations of the window types */ + +#define TRACK_VIEW(n) {info_draw_track_##n, 1, n} +static const struct info_window_type window_types[] = { + {info_draw_samples, 0, -2}, + TRACK_VIEW(5), + TRACK_VIEW(10), + TRACK_VIEW(12), + TRACK_VIEW(18), + TRACK_VIEW(24), + TRACK_VIEW(36), + TRACK_VIEW(64), + {info_draw_channels, 1, 0}, + {info_draw_note_dots, 0, -2}, + {info_draw_technical, 1, -3}, +}; +#undef TRACK_VIEW + +#define NUM_WINDOW_TYPES ARRAY_SIZE(window_types) + +/* --------------------------------------------------------------------- */ + +static void _fix_channels(int n) +{ + struct info_window *w = windows + n; + int channels = window_types[w->type].channels; + + if (channels == 0) + return; + + if (channels < 0) { + channels += w->height; + if (n == 0 && !(window_types[w->type].first_row)) { + /* crappy hack */ + channels++; + } + } + if (selected_channel < w->first_channel) + w->first_channel = selected_channel; + else if (selected_channel >= (w->first_channel + channels)) + w->first_channel = selected_channel - channels + 1; + w->first_channel = CLAMP(w->first_channel, 1, 65 - channels); +} + +static void recalculate_windows(void) +{ + int n, pos; + + pos = 13; + for (n = 0; n < num_windows - 1; n++) { + _fix_channels(n); + pos += windows[n].height; + if (pos > 50) { + /* Too big? Throw out the rest of the windows. */ + num_windows = n; + } + } + assert(num_windows > 0); + windows[n].height = 50 - pos; + _fix_channels(n); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* settings + * TODO: save all the windows in a single key, maybe comma-separated or something */ + +void cfg_save_info(cfg_file_t *cfg) +{ + char key[] = "windowX"; + int i; + + cfg_set_number(cfg, "Info Page", "num_windows", num_windows); + + for (i = 0; i < num_windows; i++) { + key[6] = i + '0'; + cfg_set_number(cfg, "Info Page", key, (windows[i].type << 8) | (windows[i].height)); + } +} + +void cfg_load_info(cfg_file_t *cfg) +{ + char key[] = "windowX"; + int i; + + num_windows = cfg_get_number(cfg, "Info Page", "num_windows", -1); + if (num_windows <= 0 || num_windows > MAX_WINDOWS) + num_windows = -1; + + for (i = 0; i < num_windows; i++) { + int tmp; + + key[6] = i + '0'; + tmp = cfg_get_number(cfg, "Info Page", key, -1); + if (tmp == -1) { + num_windows = -1; + break; + } + windows[i].type = tmp >> 8; + windows[i].height = tmp & 0xff; + if (windows[i].type < 0 || windows[i].type >= NUM_WINDOW_TYPES || windows[i].height < 3) { + /* Broken window? */ + num_windows = -1; + break; + } + } + /* last window's size < 3 lines? */ + + if (num_windows == -1) { + /* Fall back to defaults */ + num_windows = 3; + windows[0].type = 0; /* samples */ + windows[0].height = 19; + windows[1].type = 8; /* active channels */ + windows[1].height = 3; + windows[2].type = 5; /* 24chn track view */ + windows[2].height = 15; + } + + for (i = 0; i < num_windows; i++) + windows[i].first_channel = 1; + + recalculate_windows(); + if (status.current_page == PAGE_INFO) + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void info_page_redraw(void) +{ + int n, height, pos = (window_types[windows[0].type].first_row ? 13 : 12); + + for (n = 0; n < num_windows - 1; n++) { + height = windows[n].height; + if (pos == 12) + height++; + window_types[windows[n].type].draw(pos, height, (n == selected_window), + windows[n].first_channel); + pos += height; + } + /* the last window takes up all the rest of the screen */ + window_types[windows[n].type].draw(pos, 50 - pos, (n == selected_window), windows[n].first_channel); +} + +/* --------------------------------------------------------------------- */ + +static int info_page_handle_key(struct key_event * k) +{ + int n, order; + + /* hack to render this useful :) */ + if (k->orig_sym == SDLK_KP9) { + k->sym = SDLK_F9; + } else if (k->orig_sym == SDLK_KP0) { + k->sym = SDLK_F10; + } + + switch (k->sym) { + case SDLK_g: + if (!k->state) return 1; + + order = song_get_current_order(); + n = song_get_orderlist()[order]; + if (n < 200) { + set_current_order(order); + set_current_pattern(n); + set_current_row(song_get_current_row()); + set_page(PAGE_PATTERN_EDITOR); + } + return 1; + case SDLK_v: + if (k->state) return 1; + + velocity_mode = !velocity_mode; + status_text_flash("Using %s bars", (velocity_mode ? "velocity" : "volume")); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_i: + if (k->state) return 1; + + instrument_names = !instrument_names; + status_text_flash("Using %s names", (instrument_names ? "instrument" : "sample")); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_r: + if (k->mod & KMOD_ALT) { + if (k->state) return 1; + + song_flip_stereo(); + status_text_flash("Left/right outputs reversed"); + return 1; + } + return 0; + case SDLK_PLUS: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (song_get_mode() == MODE_PLAYING) { + song_set_current_order(song_get_current_order() + 1); + } + return 1; + case SDLK_MINUS: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (song_get_mode() == MODE_PLAYING) { + song_set_current_order(song_get_current_order() - 1); + } + return 1; + case SDLK_q: + if (k->state) return 1; + song_toggle_channel_mute(selected_channel - 1); + orderpan_recheck_muted_channels(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_s: + if (k->state) return 1; + + if (k->mod & KMOD_ALT) { + song_toggle_stereo(); + status_text_flash("Stereo %s", song_is_stereo() + ? "Enabled" : "Disabled"); + status.flags |= NEED_UPDATE; + return 1; + } + + song_handle_channel_solo(selected_channel - 1); + orderpan_recheck_muted_channels(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_SPACE: + if (!NO_MODIFIER(k->mod)) + return 0; + + if (k->state) return 1; + song_toggle_channel_mute(selected_channel - 1); + if (selected_channel < 64) + selected_channel++; + orderpan_recheck_muted_channels(); + break; + case SDLK_UP: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + /* make the current window one line shorter, and give the line to the next window + below it. if the window is already as small as it can get (3 lines) or if it's + the last window, don't do anything. */ + if (selected_window == num_windows - 1 || windows[selected_window].height == 3) { + return 1; + } + windows[selected_window].height--; + windows[selected_window + 1].height++; + break; + } + if (selected_channel > 1) + selected_channel--; + break; + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (selected_channel > 1) + selected_channel--; + break; + case SDLK_DOWN: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + /* expand the current window, taking a line from + * the next window down. BUT: don't do anything if + * (a) this is the last window, or (b) the next + * window is already as small as it can be (three + * lines). */ + if (selected_window == num_windows - 1 + || windows[selected_window + 1].height == 3) { + return 1; + } + windows[selected_window].height++; + windows[selected_window + 1].height--; + break; + } + if (selected_channel < 64) + selected_channel++; + break; + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (selected_channel < 64) + selected_channel++; + break; + case SDLK_HOME: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + selected_channel = 1; + break; + case SDLK_END: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + selected_channel = song_find_last_channel(); + break; + case SDLK_INSERT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + /* add a new window, unless there's already five (the maximum) + or if the current window isn't big enough to split in half. */ + if (num_windows == MAX_WINDOWS || (windows[selected_window].height < 6)) { + return 1; + } + + num_windows++; + + /* shift the windows under the current one down */ + memmove(windows + selected_window + 1, windows + selected_window, + ((num_windows - selected_window - 1) * sizeof(*windows))); + + /* split the height between the two windows */ + n = windows[selected_window].height; + windows[selected_window].height = n / 2; + windows[selected_window + 1].height = n / 2; + if ((n & 1) && num_windows != 2) { + /* odd number? compensate. (the selected window gets the extra line) */ + windows[selected_window + 1].height++; + } + break; + case SDLK_DELETE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + /* delete the current window and give the extra space to the next window down. + if this is the only window, well then don't delete it ;) */ + if (num_windows == 1) + return 1; + + n = windows[selected_window].height + windows[selected_window + 1].height; + + /* shift the windows under the current one up */ + memmove(windows + selected_window, windows + selected_window + 1, + ((num_windows - selected_window - 1) * sizeof(*windows))); + + /* fix the current window's height */ + windows[selected_window].height = n; + + num_windows--; + if (selected_window == num_windows) + selected_window--; + break; + case SDLK_PAGEUP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + n = windows[selected_window].type; + if (n == 0) + n = NUM_WINDOW_TYPES; + n--; + windows[selected_window].type = n; + break; + case SDLK_PAGEDOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + windows[selected_window].type = (windows[selected_window].type + 1) % NUM_WINDOW_TYPES; + break; + case SDLK_TAB: + if (k->state) return 1; + if (k->mod & KMOD_SHIFT) { + if (selected_window == 0) + selected_window = num_windows; + selected_window--; + } else { + selected_window = (selected_window + 1) % num_windows; + } + status.flags |= NEED_UPDATE; + return 1; + case SDLK_F9: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + song_toggle_channel_mute(selected_channel - 1); + orderpan_recheck_muted_channels(); + return 1; + } + return 0; + case SDLK_F10: + if (k->mod & KMOD_ALT) { + if (k->state) return 1; + song_handle_channel_solo(selected_channel - 1); + orderpan_recheck_muted_channels(); + return 1; + } + return 0; + default: + return 0; + } + + recalculate_windows(); + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void info_page_playback_update(void) +{ + /* this will need changed after sample playback is working... */ + if (song_get_mode() != MODE_STOPPED) + status.flags |= NEED_UPDATE; +} +static void info_page_set(void) +{ + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +void info_load_page(struct page *page) +{ + page->title = "Info Page (F5)"; + page->playback_update = info_page_playback_update; + page->total_widgets = 1; + page->widgets = widgets_info; + page->help_index = HELP_INFO_PAGE; + page->set_page = info_page_set; + + create_other(widgets_info + 0, 0, info_page_handle_key, info_page_redraw); +} diff --git a/schism/page_instruments.c b/schism/page_instruments.c new file mode 100644 index 000000000..ce58f3bd5 --- /dev/null +++ b/schism/page_instruments.c @@ -0,0 +1,2422 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is getting almost as disturbing as the pattern editor. */ + +#include "headers.h" + +#include "clippy.h" +#include "song.h" + +#include + +#include "video.h" + +/* rastops for envelope */ +static struct vgamem_overlay env_overlay = { + 32, 18, 65, 25, +}; + +/* --------------------------------------------------------------------- */ +/* just one global variable... */ + +int instrument_list_subpage = PAGE_INSTRUMENT_LIST_GENERAL; + +/* --------------------------------------------------------------------- */ +/* ... but tons o' ugly statics */ + +static struct widget widgets_general[18]; +static struct widget widgets_volume[17]; +static struct widget widgets_panning[19]; +static struct widget widgets_pitch[20]; + +static int subpage_switches_group[5] = { 1, 2, 3, 4, -1 }; +static int nna_group[5] = { 6, 7, 8, 9, -1 }; +static int dct_group[5] = { 10, 11, 12, 13, -1 }; +static int dca_group[4] = { 14, 15, 16, -1 }; + +static const char *pitch_envelope_states[] = { "Off", "On Pitch", "On Filter", NULL }; + +static int top_instrument = 1; +static int current_instrument = 1; +static int instrument_cursor_pos = 25; /* "play" mode */ + +static int note_trans_top_line = 0; +static int note_trans_sel_line = 0; + +static int note_trans_cursor_pos = 0; + +/* shared by all the numentries on a page + * (0 = volume, 1 = panning, 2 = pitch) */ +static int numentry_cursor_pos[3] = { 0 }; + +static int current_node_vol = 0; +static int current_node_pan = 0; +static int current_node_pitch = 0; + +static int envelope_edit_mode = 0; +static int envelope_mouse_edit = 0; +static int envelope_tick_limit = 0; + +/* playback */ +static int last_note = 61; /* C-5 */ + +/* --------------------------------------------------------------------------------------------------------- */ + +static void instrument_list_draw_list(void); + +/* --------------------------------------------------------------------------------------------------------- */ +/* the actual list */ + +static void instrument_list_reposition(void) +{ + if (current_instrument < top_instrument) { + top_instrument = current_instrument; + if (top_instrument < 1) { + top_instrument = 1; + } + } else if (current_instrument > top_instrument + 34) { + top_instrument = current_instrument - 34; + } +} + +int instrument_get_current(void) +{ + return current_instrument; +} + +void instrument_set(int n) +{ + int new_ins = n; + song_instrument *ins; + + if (page_is_instrument_list(status.current_page)) { + new_ins = CLAMP(n, 1, 99); + } else { + new_ins = CLAMP(n, 0, 99); + } + + if (current_instrument == new_ins) + return; + + envelope_edit_mode = 0; + status.flags = (status.flags & ~SAMPLE_CHANGED) | INSTRUMENT_CHANGED; + current_instrument = new_ins; + instrument_list_reposition(); + + ins = song_get_instrument(current_instrument, NULL); + + current_node_vol = ins->vol_env.nodes ? CLAMP(current_node_vol, 0, ins->vol_env.nodes - 1) : 0; + current_node_pan = ins->pan_env.nodes ? CLAMP(current_node_vol, 0, ins->pan_env.nodes - 1) : 0; + current_node_pitch = ins->pitch_env.nodes ? CLAMP(current_node_vol, 0, ins->pan_env.nodes - 1) : 0; + + status.flags |= NEED_UPDATE; +} + +void instrument_synchronize_to_sample(void) +{ + song_instrument *ins; + int sample = sample_get_current(); + int n, pos; + + /* 1. if the instrument with the same number as the current sample + * has the sample in its sample_map, change to that instrument. */ + ins = song_get_instrument(sample, NULL); + for (pos = 0; pos < 120; pos++) { + if ((ins->sample_map[pos]) == sample) { + instrument_set(sample); + return; + } + } + + /* 2. look through the instrument list for the first instrument + * that uses the selected sample. */ + for (n = 1; n < 100; n++) { + if (n == sample) + continue; + ins = song_get_instrument(n, NULL); + for (pos = 0; pos < 120; pos++) { + if ((ins->sample_map[pos]) == sample) { + instrument_set(n); + return; + } + } + } + + /* 3. if no instruments are using the sample, just change to the + * same-numbered instrument. */ + instrument_set(sample); +} + +/* --------------------------------------------------------------------- */ + +static int instrument_list_add_char(char c) +{ + char *name; + + if (c < 32) + return 0; + song_get_instrument(current_instrument, &name); + text_add_char(name, c, &instrument_cursor_pos, 25); + if (instrument_cursor_pos == 25) + instrument_cursor_pos--; + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; + return 1; +} + +static void instrument_list_delete_char(void) +{ + char *name; + song_get_instrument(current_instrument, &name); + text_delete_char(name, &instrument_cursor_pos, 25); + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +static void instrument_list_delete_next_char(void) +{ + char *name; + song_get_instrument(current_instrument, &name); + text_delete_next_char(name, &instrument_cursor_pos, 25); + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +static void clear_instrument_text(void) +{ + char *name; + + memset(song_get_instrument(current_instrument, &name)->filename, 0, 14); + memset(name, 0, 26); + if (instrument_cursor_pos != 25) + instrument_cursor_pos = 0; + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +/* --------------------------------------------------------------------- */ + +static struct widget swap_instrument_widgets[6]; +static char swap_instrument_entry[4] = ""; + + +static void do_swap_instrument(UNUSED void *data) +{ + int n = atoi(swap_instrument_entry); + + if (n < 1 || n > 99) + return; + song_swap_instruments(current_instrument, n); +} + +static void swap_instrument_draw_const(void) +{ + draw_text((const unsigned char *)"Swap instrument with:", 29, 25, 0, 2); + draw_text((const unsigned char *)"Instrument", 31, 27, 0, 2); + draw_box(41, 26, 45, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void swap_instrument_dialog(void) +{ + struct dialog *dialog; + + swap_instrument_entry[0] = 0; + create_textentry(swap_instrument_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, swap_instrument_entry, 2); + create_button(swap_instrument_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(26, 23, 29, 10, swap_instrument_widgets, 2, 0, + swap_instrument_draw_const, NULL); + dialog->action_yes = do_swap_instrument; +} + + +static void do_exchange_instrument(UNUSED void *data) +{ + int n = atoi(swap_instrument_entry); + + if (n < 1 || n > 99) + return; + song_exchange_instruments(current_instrument, n); +} + +static void exchange_instrument_draw_const(void) +{ + draw_text((const unsigned char *)"Exchange instrument with:", 28, 25, 0, 2); + draw_text((const unsigned char *)"Instrument", 31, 27, 0, 2); + draw_box(41, 26, 45, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void exchange_instrument_dialog(void) +{ + struct dialog *dialog; + + swap_instrument_entry[0] = 0; + create_textentry(swap_instrument_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, swap_instrument_entry, 2); + create_button(swap_instrument_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(26, 23, 29, 10, swap_instrument_widgets, 2, 0, + exchange_instrument_draw_const, NULL); + dialog->action_yes = do_exchange_instrument; +} + +/* --------------------------------------------------------------------- */ + +static void instrument_list_draw_list(void) +{ + int pos, n; + song_instrument *ins; + int selected = (ACTIVE_PAGE.selected_widget == 0); + int is_current; + int ss, cl, cr; + int is_playing[100]; + byte buf[4]; + + if (clippy_owner(CLIPPY_SELECT) == widgets_general) { + cl = widgets_general[0].clip_start % 25; + cr = widgets_general[0].clip_end % 25; + if (cl > cr) { + ss = cl; + cl = cr; + cr = ss; + } + ss = (widgets_general[0].clip_start / 25); + } else { + ss = -1; + } + + song_get_playing_instruments(is_playing); + + for (pos = 0, n = top_instrument; pos < 35; pos++, n++) { + ins = song_get_instrument(n, NULL); + is_current = (n == current_instrument); + + if (ins->played) + draw_char(173, 1, 13 + pos, is_playing[n] ? 3 : 1, 2); + + draw_text(numtostr(2, n, buf), 2, 13 + pos, 0, 2); + if (instrument_cursor_pos < 25) { + /* it's in edit mode */ + if (is_current) { + draw_text_len((const unsigned char *)ins->name, 25, 5, 13 + pos, 6, 14); + if (selected) { + draw_char(ins->name[instrument_cursor_pos], + 5 + instrument_cursor_pos, + 13 + pos, 0, 3); + } + } else { + draw_text_len((const unsigned char *)ins->name, 25, 5, 13 + pos, 6, 0); + } + } else { + draw_text_len((const unsigned char *)ins->name, 25, 5, 13 + pos, + ((is_current && selected) ? 0 : 6), + (is_current ? (selected ? 3 : 14) : 0)); + } + if (ss == n) { + draw_text_len((const unsigned char *)ins->name + cl, (cr-cl)+1, + 5 + cl, 13 + pos, + (is_current ? 3 : 11), 8); + } + } +} + +static int instrument_list_handle_key_on_list(struct key_event * k) +{ + int new_ins = current_instrument; + char *name; + + if (!k->state && k->mouse && k->y >= 13 && k->y <= 47 && k->x >= 5 && k->x <= 30) { + if (k->mouse == MOUSE_CLICK) { + if (k->state || k->sy == k->y) { + new_ins = (k->y - 13) + top_instrument; + } else { + } + if (new_ins == current_instrument) { + instrument_cursor_pos = k->x - 5; + status.flags |= NEED_UPDATE; + if (instrument_cursor_pos > 25) instrument_cursor_pos = 25; + } + } else if (k->mouse == MOUSE_DBLCLICK) { + if (instrument_cursor_pos < 25) { + instrument_cursor_pos = 25; + } else { + set_page(PAGE_LOAD_INSTRUMENT); + } + status.flags |= NEED_UPDATE; + return 1; + + } else if (k->mouse == MOUSE_SCROLL_UP) { + top_instrument--; + if (top_instrument < 1) top_instrument = 1; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + top_instrument++; + if (top_instrument > (99-34)) top_instrument = 99-34; + status.flags |= NEED_UPDATE; + return 1; + } + } else { + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_ins--; + break; + case SDLK_DOWN: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_ins++; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) + new_ins = 1; + else + new_ins -= 16; + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) + new_ins = 99; + else + new_ins += 16; + break; + case SDLK_HOME: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (instrument_cursor_pos < 25) { + instrument_cursor_pos = 0; + status.flags |= NEED_UPDATE; + } + return 1; + case SDLK_END: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (instrument_cursor_pos < 24) { + instrument_cursor_pos = 24; + status.flags |= NEED_UPDATE; + } + return 1; + case SDLK_LEFT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (instrument_cursor_pos < 25 && instrument_cursor_pos > 0) { + instrument_cursor_pos--; + status.flags |= NEED_UPDATE; + } + return 1; + case SDLK_RIGHT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (instrument_cursor_pos == 25) { + change_focus_to(1); + } else if (instrument_cursor_pos < 24) { + instrument_cursor_pos++; + status.flags |= NEED_UPDATE; + } + return 1; + case SDLK_RETURN: + if (!k->state) return 0; + if (instrument_cursor_pos < 25) { + instrument_cursor_pos = 25; + status.flags |= NEED_UPDATE; + } else { + set_page(PAGE_LOAD_INSTRUMENT); + } + return 1; + case SDLK_ESCAPE: + if (!k->state) return 0; + if (instrument_cursor_pos < 25) { + instrument_cursor_pos = 25; + status.flags |= NEED_UPDATE; + return 1; + } + return 0; + case SDLK_BACKSPACE: + if (k->state) return 0; + if (instrument_cursor_pos == 25) + return 0; + if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0) + instrument_list_delete_char(); + else if (k->mod & KMOD_CTRL) + instrument_list_add_char(127); + return 1; + case SDLK_INSERT: + if (k->state) return 0; + if (k->mod & KMOD_ALT) { + song_insert_instrument_slot(current_instrument); + status.flags |= NEED_UPDATE; + return 1; + } + return 0; + case SDLK_DELETE: + if (k->state) return 0; + if (k->mod & KMOD_ALT) { + song_remove_instrument_slot(current_instrument); + status.flags |= NEED_UPDATE; + return 1; + } else if ((k->mod & KMOD_CTRL) == 0) { + if (instrument_cursor_pos == 25) + return 0; + instrument_list_delete_next_char(); + return 1; + } + return 0; + default: + if (k->state) return 0; + if (k->mod & KMOD_ALT) { + if (k->sym == SDLK_c) { + clear_instrument_text(); + return 1; + } + } else if ((k->mod & KMOD_CTRL) == 0) { + char c = unicode_to_ascii(k->unicode); + if (c == 0) + return 0; + + if (instrument_cursor_pos < 25) { + return instrument_list_add_char(c); + } else if (k->sym == SDLK_SPACE) { + instrument_cursor_pos = 0; + status.flags |= NEED_UPDATE; + memused_songchanged(); + return 1; + } + } + return 0; + }; + } + + new_ins = CLAMP(new_ins, 1, 99); + if (new_ins != current_instrument) { + instrument_set(new_ins); + status.flags |= NEED_UPDATE; + memused_songchanged(); + } + + if (k->mouse && k->x != k->sx) { + song_get_instrument(current_instrument, &name); + widgets_general[0].clip_start = (k->sx - 5) + (current_instrument*25); + widgets_general[0].clip_end = (k->x - 5) + (current_instrument*25); + if (widgets_general[0].clip_start < widgets_general[0].clip_end) { + clippy_select(widgets_general, + name + (k->sx - 5), + (k->x - k->sx)); + } else { + clippy_select(widgets_general, + name + (k->x - 5), + (k->sx - k->x)); + } + } + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* note translation table */ + +static void note_trans_reposition(void) +{ + if (note_trans_sel_line < note_trans_top_line) { + note_trans_top_line = note_trans_sel_line; + } else if (note_trans_sel_line > note_trans_top_line + 31) { + note_trans_top_line = note_trans_sel_line - 31; + } +} + +static void note_trans_draw(void) +{ + int pos, n; + int is_selected = (ACTIVE_PAGE.selected_widget == 5); + int bg, sel_bg = (is_selected ? 14 : 0); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + byte buf[4]; + + for (pos = 0, n = note_trans_top_line; pos < 32; pos++, n++) { + bg = ((n == note_trans_sel_line) ? sel_bg : 0); + + /* invalid notes are translated to themselves (and yes, this edits the actual instrument) */ + if (ins->note_map[n] < 1 || ins->note_map[n] > 120) + ins->note_map[n] = n + 1; + + draw_text(get_note_string(n + 1, buf), 32, 16 + pos, 2, bg); + draw_char(168, 35, 16 + pos, 2, bg); + draw_text(get_note_string(ins->note_map[n], buf), 36, 16 + pos, 2, bg); + if (is_selected && n == note_trans_sel_line) { + if (note_trans_cursor_pos == 0) + draw_char(buf[0], 36, 16 + pos, 0, 3); + else if (note_trans_cursor_pos == 1) + draw_char(buf[2], 38, 16 + pos, 0, 3); + } + draw_char(0, 39, 16 + pos, 2, bg); + if (ins->sample_map[n]) { + numtostr(2, ins->sample_map[n], buf); + } else { + buf[0] = buf[1] = 173; + buf[2] = 0; + } + draw_text((const unsigned char *)buf, 40, 16 + pos, 2, bg); + if (is_selected && n == note_trans_sel_line) { + if (note_trans_cursor_pos == 2) + draw_char(buf[0], 40, 16 + pos, 0, 3); + else if (note_trans_cursor_pos == 3) + draw_char(buf[1], 41, 16 + pos, 0, 3); + } + } +} + +static int note_trans_handle_alt_key(struct key_event * k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int n, s; + + if (k->state) return 0; + switch (k->sym) { + case SDLK_a: + s = sample_get_current(); + for (n = 0; n < 120; n++) + ins->sample_map[n] = s; + break; + default: + return 0; + } + + status.flags |= NEED_UPDATE; + memused_songchanged(); + return 1; +} + +static int note_trans_handle_key(struct key_event * k) +{ + int prev_line = note_trans_sel_line; + int new_line = prev_line; + int prev_pos = note_trans_cursor_pos; + int new_pos = prev_pos; + song_instrument *ins = song_get_instrument(current_instrument, NULL); + char c; + int n; + + if (k->mouse == MOUSE_CLICK && k->mouse_button == MOUSE_BUTTON_MIDDLE) { + if (k->state) status.flags |= CLIPPY_PASTE_SELECTION; + return 1; + } else if (k->mouse) { + if (k->x >= 32 && k->x <= 41 && k->y >= 16 && k->y <= 47) { + new_line = k->y - 16; + if (new_line == prev_line) { + switch (k->x - 36) { + case 2: + new_pos = 1; + break; + case 4: + new_pos = 2; + break; + case 5: + new_pos = 3; + break; + default: + new_pos = 0; + break; + }; + } + } + } else { + if (k->mod & KMOD_ALT) + return note_trans_handle_alt_key(k); + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (--new_line < 0) { + change_focus_to(1); + return 1; + } + break; + case SDLK_DOWN: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_line++; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + instrument_set(current_instrument - 1); + return 1; + } + new_line -= 16; + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + instrument_set(current_instrument + 1); + return 1; + } + new_line += 16; + break; + case SDLK_HOME: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_line = 0; + break; + case SDLK_END: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_line = 119; + break; + case SDLK_LEFT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_pos--; + break; + case SDLK_RIGHT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_pos++; + break; + case SDLK_RETURN: + if (!k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + sample_set(ins->sample_map[note_trans_sel_line]); + return 1; + case SDLK_LESS: + if (k->state) return 0; + sample_set(sample_get_current() - 1); + return 1; + case SDLK_GREATER: + if (k->state) return 0; + sample_set(sample_get_current() + 1); + return 1; + + default: + if (k->state) return 0; + switch (note_trans_cursor_pos) { + case 0: /* note */ + if (k->sym == SDLK_1) { + ins->sample_map[note_trans_sel_line] = 0; + sample_set(0); + new_line++; + break; + } + n = kbd_get_note(k); + if (n <= 0 || n > 120) + return 0; + ins->note_map[note_trans_sel_line] = n; + ins->sample_map[note_trans_sel_line] = sample_get_current(); + new_line++; + break; + case 1: /* octave */ + c = kbd_char_to_hex(k); + if (c < 0 || c > 9) return 0; + n = ins->note_map[note_trans_sel_line]; + n = ((n - 1) % 12) + (12 * c) + 1; + ins->note_map[note_trans_sel_line] = n; + new_line++; + break; + case 2: /* instrument, first digit */ + case 3: /* instrument, second digit */ + if (k->sym == SDLK_SPACE) { + ins->sample_map[note_trans_sel_line] = + sample_get_current(); + new_line++; + break; + } + c = kbd_char_to_hex(k); + if (c < 0 || c > 9) return 0; + n = ins->sample_map[note_trans_sel_line]; + if (note_trans_cursor_pos == 2) { + n = (c * 10) + (n % 10); + new_pos++; + } else { + n = ((n / 10) * 10) + c; + new_pos--; + new_line++; + } + ins->sample_map[note_trans_sel_line] = n; + sample_set(n); + break; + } + break; + } + } + + new_line = CLAMP(new_line, 0, 119); + note_trans_cursor_pos = CLAMP(new_pos, 0, 3); + if (new_line != prev_line) { + note_trans_sel_line = new_line; + note_trans_reposition(); + } + + /* this causes unneeded redraws in some cases... oh well :P */ + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* envelope helper functions */ + +static void _env_draw_axes(int middle) +{ + int n, y = middle ? 31 : 62; + for (n = 0; n < 64; n += 2) + vgamem_font_putpixel(&env_overlay, 3, n); + for (n = 0; n < 256; n += 2) + vgamem_font_putpixel(&env_overlay, 1 + n, y); +} + +static void _env_draw_node(int x, int y, int on) +{ +#if 0 + /* FIXME: the lines draw over the nodes. This doesn't matter unless the color is different. */ + int c = (status.flags & CLASSIC_MODE) ? 12 : 5; +#endif + + vgamem_font_putpixel(&env_overlay, x - 1, y - 1); + vgamem_font_putpixel(&env_overlay, x - 1, y); + vgamem_font_putpixel(&env_overlay, x - 1, y + 1); + + vgamem_font_putpixel(&env_overlay, x, y - 1); + vgamem_font_putpixel(&env_overlay, x, y); + vgamem_font_putpixel(&env_overlay, x, y + 1); + + vgamem_font_putpixel(&env_overlay, x + 1, y - 1); + vgamem_font_putpixel(&env_overlay, x + 1, y); + vgamem_font_putpixel(&env_overlay, x + 1, y + 1); + + if (on) { + vgamem_font_putpixel(&env_overlay, x - 3, y - 1); + vgamem_font_putpixel(&env_overlay, x - 3, y); + vgamem_font_putpixel(&env_overlay, x - 3, y + 1); + + vgamem_font_putpixel(&env_overlay, x + 3, y - 1); + vgamem_font_putpixel(&env_overlay, x + 3, y); + vgamem_font_putpixel(&env_overlay, x + 3, y + 1); + } +} + +static void _env_draw_loop(int xs, int xe, int sustain) +{ + int y = 0; +#if 0 + int c = (status.flags & CLASSIC_MODE) ? 12 : 3; +#endif + + if (sustain) { + while (y < 62) { + /* unrolled once */ + vgamem_font_putpixel(&env_overlay, xs, y); + vgamem_font_putpixel(&env_overlay, xe, y); y++; + vgamem_font_clearpixel(&env_overlay, xs, y); + vgamem_font_clearpixel(&env_overlay, xe, y); y++; + vgamem_font_putpixel(&env_overlay, xs, y); + vgamem_font_putpixel(&env_overlay, xe, y); y++; + vgamem_font_clearpixel(&env_overlay, xs, y); + vgamem_font_clearpixel(&env_overlay, xe, y); y++; + } + } else { + while (y < 62) { + vgamem_font_clearpixel(&env_overlay, xs, y); + vgamem_font_clearpixel(&env_overlay, xe, y); y++; + vgamem_font_putpixel(&env_overlay, xs, y); + vgamem_font_putpixel(&env_overlay, xe, y); y++; + vgamem_font_putpixel(&env_overlay, xs, y); + vgamem_font_putpixel(&env_overlay, xe, y); y++; + vgamem_font_clearpixel(&env_overlay, xs, y); + vgamem_font_clearpixel(&env_overlay, xe, y); y++; + } + } +} + +static void _env_draw(const song_envelope *env, int middle, int current_node, + int env_on, int loop_on, int sustain_on, int env_num) +{ + song_mix_channel *channel; + unsigned long *channel_list; + byte buf[16]; + unsigned long envpos[3]; + int x, y, n, m, c; + int last_x = 0, last_y = 0; + int max_ticks = 50; + + while (env->ticks[env->nodes - 1] >= max_ticks) + max_ticks *= 2; + + vgamem_clear_reserve(&env_overlay); + + /* draw the axis lines */ + _env_draw_axes(middle); + + for (n = 0; n < env->nodes; n++) { + x = 4 + env->ticks[n] * 256 / max_ticks; + + /* 65 values are being crammed into 62 pixels => have to lose three pixels somewhere. + * This is where IT compromises -- I don't quite get how the lines are drawn, though, + * because it changes for each value... (apart from drawing 63 and 64 the same way) */ + y = env->values[n]; + if (y > 63) y--; + if (y > 42) y--; + if (y > 21) y--; + y = 62 - y; + + _env_draw_node(x, y, n == current_node); + + if (last_x) + vgamem_font_drawline(&env_overlay, last_x, last_y, x, y); + + last_x = x; + last_y = y; + } + + if (sustain_on) + _env_draw_loop(4 + env->ticks[env->sustain_start] * 256 / max_ticks, + 4 + env->ticks[env->sustain_end] * 256 / max_ticks, 1); + if (loop_on) + _env_draw_loop(4 + env->ticks[env->loop_start] * 256 / max_ticks, + 4 + env->ticks[env->loop_end] * 256 / max_ticks, 0); + + if (env_on) { + max_ticks = env->ticks[env->nodes-1]; + m = song_get_mix_state(&channel_list); + while (m--) { + channel = song_get_mix_channel(channel_list[m]); + if (channel->instrument + != song_get_instrument(current_instrument,NULL)) + continue; + + envpos[0] = channel->nVolEnvPosition; + envpos[1] = channel->nPanEnvPosition; + envpos[2] = channel->nPitchEnvPosition; + + x = 4 + (envpos[env_num] * (last_x-4) / max_ticks); + if (x > last_x) x = last_x; +#if 0 + c = (channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) ? 8 : 6; +#endif + for (y = 0; y < 62; y++) + vgamem_font_putpixel(&env_overlay, x, y); + } + } + + draw_fill_chars(65, 18, 76, 25, 0); + vgamem_fill_reserve(&env_overlay, 3, 0); + + sprintf(buf, "Node %d/%d", current_node, env->nodes); + draw_text((const unsigned char *)buf, 66, 19, 2, 0); + sprintf(buf, "Tick %d", env->ticks[current_node]); + draw_text((const unsigned char *)buf, 66, 21, 2, 0); + sprintf(buf, "Value %d", env->values[current_node] - (middle ? 32 : 0)); + draw_text((const unsigned char *)buf, 66, 23, 2, 0); +} + +/* return: the new current node */ +static int _env_node_add(song_envelope *env, int current_node, int override_tick, int override_value) +{ + int newtick, newvalue; + + status.flags |= SONG_NEEDS_SAVE; + + /* is 24 the right number here, or 25? */ + if (env->nodes > 24 || current_node == env->nodes - 1) + return current_node; + + newtick = (env->ticks[current_node] + env->ticks[current_node + 1]) / 2; + newvalue = (env->values[current_node] + env->values[current_node + 1]) / 2; + if (override_tick > -1 && override_value > -1) { + newtick = override_tick; + newvalue = override_value; + + } else if (newtick == env->ticks[current_node] || newtick == env->ticks[current_node + 1]) { + /* If the current node is at (for example) tick 30, and the next node is at tick 32, + * is there any chance of a rounding error that would make newtick 30 instead of 31? + * ("Is there a chance the track could bend?") */ + printf("Not enough room!\n"); + return current_node; + } + + env->nodes++; + memmove(env->ticks + current_node + 1, env->ticks + current_node, + (env->nodes - current_node - 1) * sizeof(env->ticks[0])); + memmove(env->values + current_node + 1, env->values + current_node, + (env->nodes - current_node - 1) * sizeof(env->values[0])); + env->ticks[current_node + 1] = newtick; + env->values[current_node + 1] = newvalue; + if (env->loop_end > current_node) env->loop_end++; + if (env->loop_start > current_node) env->loop_start++; + if (env->sustain_end > current_node) env->sustain_end++; + if (env->sustain_start > current_node) env->sustain_start++; + + return current_node; +} + +/* return: the new current node */ +static int _env_node_remove(song_envelope *env, int current_node) +{ + status.flags |= SONG_NEEDS_SAVE; + + if (current_node == 0 || env->nodes < 3) + return current_node; + + memmove(env->ticks + current_node, env->ticks + current_node + 1, + (env->nodes - current_node - 1) * sizeof(env->ticks[0])); + memmove(env->values + current_node, env->values + current_node + 1, + (env->nodes - current_node - 1) * sizeof(env->values[0])); + env->nodes--; + if (env->loop_end > current_node) env->loop_end--; + if (env->loop_start > current_node) env->loop_start--; + if (env->sustain_end > current_node) env->sustain_end--; + if (env->sustain_start > current_node) env->sustain_start--; + + if (current_node >= env->nodes) + current_node = env->nodes - 1; + + return current_node; +} + +static void do_pre_loop_cut(void *ign) +{ + song_envelope *env = (song_envelope *)ign; + unsigned int bt; + int i; + bt = env->ticks[env->loop_start]; + for (i = env->loop_start; i < 32; i++) { + env->ticks[i - env->loop_start] = env->ticks[i] - bt; + env->values[i - env->loop_start] = env->values[i]; + } + env->nodes -= env->loop_start; + if (env->sustain_start > env->loop_start) { + env->sustain_start -= env->loop_start; + } else { + env->sustain_start = 0; + } + if (env->sustain_end > env->loop_start) { + env->sustain_end -= env->loop_start; + } else { + env->sustain_end = 0; + } + if (env->loop_end > env->loop_start) { + env->loop_end -= env->loop_start; + } else { + env->loop_end = 0; + } + env->loop_start = 0; + if (env->loop_start > env->loop_end) + env->loop_end = env->loop_start; + if (env->sustain_start > env->sustain_end) + env->sustain_end = env->sustain_start; + status.flags |= NEED_UPDATE; +} +static void do_post_loop_cut(void *ign) +{ + song_envelope *env = (song_envelope *)ign; + env->nodes = env->loop_end+1; +} +/* the return value here is actually a bitmask: +r & 1 => the key was handled +r & 2 => the envelope changed (i.e., it should be enabled) */ +static int _env_handle_key_viewmode(struct key_event *k, song_envelope *env, int *current_node) +{ + int new_node = *current_node; + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + change_focus_to(1); + return 1; + case SDLK_DOWN: + if (k->state) return 0; + change_focus_to(6); + return 1; + case SDLK_LEFT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_node--; + break; + case SDLK_RIGHT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_node++; + break; + case SDLK_INSERT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + *current_node = _env_node_add(env, *current_node, -1, -1); + status.flags |= NEED_UPDATE; + return 1 | 2; + case SDLK_DELETE: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + *current_node = _env_node_remove(env, *current_node); + status.flags |= NEED_UPDATE; + return 1 | 2; + case SDLK_SPACE: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + song_keyup(-1, current_instrument, last_note, -1, 0); + song_keydown(-1, current_instrument, last_note, 64, -1, 0); + return 1; + case SDLK_RETURN: + if (!k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + envelope_edit_mode = 1; + status.flags |= NEED_UPDATE; + return 1 | 2; + case SDLK_l: + if (!k->state) return 0; + if (!(k->mod & KMOD_ALT)) return 0; + if (env->loop_end < (env->nodes-1)) { + dialog_create(DIALOG_OK_CANCEL, "Cut envelope?", do_post_loop_cut, NULL, 1, env); + return 1; + } + return 0; + case SDLK_b: + if (!k->state) return 0; + if (!(k->mod & KMOD_ALT)) return 0; + if (env->loop_start > 0) { + dialog_create(DIALOG_OK_CANCEL, "Cut envelope?", do_pre_loop_cut, NULL, 1, env); + return 1; + } + return 0; + + default: + return 0; + } + + new_node = CLAMP(new_node, 0, env->nodes - 1); + if (*current_node != new_node) { + *current_node = new_node; + status.flags |= NEED_UPDATE; + } + + return 1; +} + +/* mouse handling routines for envelope */ +static int _env_handle_mouse(struct key_event *k, song_envelope *env, int *current_node) +{ + int x, y, i; + int max_ticks = 50; + + if (k->mouse != 1) return 0; + + if (k->state) { + /* mouse release */ + if (envelope_mouse_edit) { + if (current_node && *current_node) { + for (i = 0; i < env->nodes-1; i++) { + if (*current_node == i) continue; + if (env->ticks[ *current_node ] == env->ticks[i] + && env->values[ *current_node ] == env->values[i]) { + status_text_flash("Removed node %d", *current_node); + status.flags |= SONG_NEEDS_SAVE; + + *current_node = _env_node_remove(env, *current_node); + break; + } + } + + } + status.flags |= NEED_UPDATE; + } + memused_songchanged(); + envelope_mouse_edit = 0; + return 1; + } + + while (env->ticks[env->nodes - 1] >= max_ticks) + max_ticks *= 2; + + if (envelope_mouse_edit) { + if (k->fx < 259) + x = 0; + else + x = (k->fx - 259) * max_ticks / 256; + y = 64 - (k->fy - 144); + if (y > 63) y++; + if (y > 42) y++; + if (y > 21) y++; + if (y > 64) y = 64; + if (y < 0) y = 0; + + if (*current_node && env->ticks[ (*current_node)-1 ] >= x) { + x = env->ticks[ (*current_node)-1 ]; + } + if (*current_node < (env->nodes-1)) { + if (env->ticks[ (*current_node)+1 ] <= x) { + x = env->ticks[ (*current_node)+1 ]; + } + } + if (env->ticks[*current_node] == x && env->ticks[*current_node] == y) { + return 1; + } + if (x > envelope_tick_limit) x = envelope_tick_limit; + if (x > 9999) x = 9999; + if (*current_node) env->ticks[ *current_node ] = x; + env->values[ *current_node ] = y; + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; + } else { + int n; + int dist, dx, dy; + int best_dist; + int best_dist_node; + + best_dist_node = -1; + + if (k->x < 32 || k->y < 18 || k->x > 32+45 || k->y > 18+8) + return 0; + + for (n = 0; n < env->nodes; n++) { + x = 259 + env->ticks[n] * 256 / max_ticks; + y = env->values[n]; + if (y > 63) y--; + if (y > 42) y--; + if (y > 21) y--; + y = 206 - y; + + dx = abs(x - k->fx); + dy = abs(y - k->fy); + dist = i_sqrt((dx*dx)+(dy*dy)); + if (best_dist_node == -1 || dist < best_dist) { + if (dist <= 5) { + best_dist = dist; + best_dist_node = n; + } + } + } + if (best_dist_node == -1) { + x = (k->fx - 259) * max_ticks / 256; + y = 64 - (k->fy - 144); + if (y > 63) y++; + if (y > 42) y++; + if (y > 21) y++; + if (y > 64) y = 64; + if (y < 0) y = 0; + if (x > 0 && x < max_ticks) { + *current_node = 0; + for (i = 1; i < env->nodes; i++) { + /* something too close */ + if (env->ticks[i] <= x) *current_node = i; + if (abs(env->ticks[i] - x) <= 4) return 0; + } + best_dist_node = (_env_node_add(env, *current_node, x, y))+1; + status_text_flash("Created node %d", best_dist_node); + } + if (best_dist_node == -1) return 0; + } + + envelope_tick_limit = env->ticks[env->nodes - 1] * 2; + envelope_mouse_edit = 1; + *current_node = best_dist_node; + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; + return 1; + } + return 0; +} + + + +/* - this function is only ever called when the envelope is in edit mode + - envelope_edit_mode is only ever assigned a true value once, in _env_handle_key_viewmode. + - when _env_handle_key_viewmode enables envelope_edit_mode, it indicates in its return value + that the envelope should be enabled. + - therefore, the envelope will always be enabled when this function is called, so there is + no reason to indicate a change in the envelope here. */ +static int _env_handle_key_editmode(struct key_event *k, song_envelope *env, int *current_node) +{ + int new_node = *current_node, new_tick = env->ticks[*current_node], + new_value = env->values[*current_node]; + + /* TODO: when does adding/removing a node alter loop points? */ + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + if (k->mod & KMOD_ALT) + new_value += 16; + else + new_value++; + break; + case SDLK_DOWN: + if (k->state) return 0; + if (k->mod & KMOD_ALT) + new_value -= 16; + else + new_value--; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_value += 16; + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_value -= 16; + break; + case SDLK_LEFT: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) + new_node--; + else if (k->mod & KMOD_ALT) + new_tick -= 16; + else + new_tick--; + break; + case SDLK_RIGHT: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) + new_node++; + else if (k->mod & KMOD_ALT) + new_tick += 16; + else + new_tick++; + break; + case SDLK_TAB: + if (k->state) return 0; + if (k->mod & KMOD_SHIFT) + new_tick -= 16; + else + new_tick += 16; + break; + case SDLK_HOME: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_tick = 0; + break; + case SDLK_END: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_tick = 10000; + break; + case SDLK_INSERT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + *current_node = _env_node_add(env, *current_node, -1, -1); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_DELETE: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + *current_node = _env_node_remove(env, *current_node); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_SPACE: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + song_keyup(-1, current_instrument, last_note, -1, 0); + song_keydown(-1, current_instrument, last_note, 64, -1, 0); + return 1; + case SDLK_RETURN: + if (!k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + envelope_edit_mode = 0; + memused_songchanged(); + status.flags |= NEED_UPDATE; + break; + default: + return 0; + } + + new_node = CLAMP(new_node, 0, env->nodes - 1); + if (new_node != *current_node) { + status.flags |= NEED_UPDATE; + *current_node = new_node; + return 1; + } + + new_tick = (new_node == 0) ? 0 : CLAMP(new_tick, + env->ticks[new_node - 1] + 1, + ((new_node == env->nodes - 1) + ? 10000 : env->ticks[new_node + 1]) - 1); + if (new_tick != env->ticks[new_node]) { + env->ticks[*current_node] = new_tick; + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; + return 1; + } + new_value = CLAMP(new_value, 0, 64); + + if (new_value != env->values[new_node]) { + env->values[*current_node] = new_value; + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; + return 1; + } + + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* envelope stuff (draw()'s and handle_key()'s) */ + +static void _draw_env_label(const char *env_name, int is_selected) +{ + int pos = 33; + + pos += draw_text((const unsigned char *)env_name, pos, 16, is_selected ? 3 : 0, 2); + pos += draw_text((const unsigned char *)" Envelope", pos, 16, is_selected ? 3 : 0, 2); + if (envelope_edit_mode || envelope_mouse_edit) + draw_text((const unsigned char *)" (Edit)", pos, 16, is_selected ? 3 : 0, 2); +} + +static void volume_envelope_draw(void) +{ + int is_selected = (ACTIVE_PAGE.selected_widget == 5); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + _draw_env_label("Volume", is_selected); + _env_draw(&ins->vol_env, 0, current_node_vol, + ins->flags & ENV_VOLUME, + ins->flags & ENV_VOLLOOP, ins->flags & ENV_VOLSUSTAIN, 0); +} + +static void panning_envelope_draw(void) +{ + int is_selected = (ACTIVE_PAGE.selected_widget == 5); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + _draw_env_label("Panning", is_selected); + _env_draw(&ins->pan_env, 1, current_node_pan, + ins->flags & ENV_PANNING, + ins->flags & ENV_PANLOOP, ins->flags & ENV_PANSUSTAIN, 1); +} + +static void pitch_envelope_draw(void) +{ + int is_selected = (ACTIVE_PAGE.selected_widget == 5); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + _draw_env_label("Frequency", is_selected); + _env_draw(&ins->pitch_env, (ins->flags & ENV_FILTER) ? 0 : 1, current_node_pitch, + ins->flags & (ENV_PITCH|ENV_FILTER), + ins->flags & ENV_PITCHLOOP, ins->flags & ENV_PITCHSUSTAIN, 2); +} + +static int volume_envelope_handle_key(struct key_event * k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int r; + + if (_env_handle_mouse(k, &ins->vol_env, ¤t_node_vol)) { + ins->flags |= ENV_VOLUME; + return 1; + } + if (envelope_edit_mode) + r = _env_handle_key_editmode(k, &ins->vol_env, ¤t_node_vol); + else + r = _env_handle_key_viewmode(k, &ins->vol_env, ¤t_node_vol); + if (r & 2) { + r ^= 2; + ins->flags |= ENV_VOLUME; + } + return r; +} + +static int panning_envelope_handle_key(struct key_event * k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int r; + + if (_env_handle_mouse(k, &ins->pan_env, ¤t_node_pan)) { + ins->flags |= ENV_PANNING; + return 1; + } + + if (envelope_edit_mode) + r = _env_handle_key_editmode(k, &ins->pan_env, ¤t_node_pan); + else + r = _env_handle_key_viewmode(k, &ins->pan_env, ¤t_node_pan); + if (r & 2) { + r ^= 2; + ins->flags |= ENV_PANNING; + } + return r; +} + +static int pitch_envelope_handle_key(struct key_event * k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int r; + + if (_env_handle_mouse(k, &ins->pitch_env, ¤t_node_pitch)) { + ins->flags |= ENV_PITCH; + return 1; + } + if (envelope_edit_mode) + r = _env_handle_key_editmode(k, &ins->pitch_env, ¤t_node_pitch); + else + r = _env_handle_key_viewmode(k, &ins->pitch_env, ¤t_node_pitch); + if (r & 2) { + r ^= 2; + ins->flags |= ENV_PITCH; + } + return r; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* pitch-pan center */ + +static int pitch_pan_center_handle_key(struct key_event *k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int ppc = ins->pitch_pan_center; + + if (k->state) return 0; + switch (k->sym) { + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + ppc--; + break; + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + ppc++; + break; + default: + if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0) { + ppc = kbd_get_note(k); + if (ppc < 1 || ppc > 120) + return 0; + ppc--; + break; + } + return 0; + } + if (ppc != ins->pitch_pan_center && ppc >= 0 && ppc < 120) { + ins->pitch_pan_center = ppc; + status.flags |= NEED_UPDATE; + } + return 1; +} + +static void pitch_pan_center_draw(void) +{ + char buf[4]; + int selected = (ACTIVE_PAGE.selected_widget == 16); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + draw_text((const unsigned char *)get_note_string(ins->pitch_pan_center + 1, buf), 54, 45, selected ? 3 : 2, 0); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* default key handler (for instrument changing on pgup/pgdn) */ + +static void do_ins_save(void *p) +{ + char *ptr = (char *)p; + if (song_save_instrument(current_instrument, ptr)) + status_text_flash("Instrument saved (instrument %d)", current_instrument); + else + status_text_flash("Error: Instrument %d NOT saved! (No Filename?)", current_instrument); + free(ptr); +} + +static void instrument_save(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + char *ptr = dmoz_path_concat(cfg_dir_instruments, ins->filename); + struct stat buf; + + if (stat(ptr, &buf) == 0) { + if (S_ISDIR(buf.st_mode)) { + status_text_flash("%s is a directory", ins->filename); + return; + } else if (S_ISREG(buf.st_mode)) { + dialog_create(DIALOG_OK_CANCEL, + "Overwrite file?", do_ins_save, + free, 1, ptr); + return; + } else { + status_text_flash("%s is not a regular file", ins->filename); + return; + } + } + if (song_save_instrument(current_instrument, ptr)) + status_text_flash("Instrument saved (instrument %d)", current_instrument); + else + status_text_flash("Error: Instrument %d NOT saved! (No Filename?)", current_instrument); + free(ptr); +} + +static void do_delete_inst(UNUSED void *ign) +{ + song_delete_instrument(current_instrument); +} + +static void instrument_list_handle_alt_key(struct key_event *k) +{ + /* song_instrument *ins = song_get_instrument(current_instrument, NULL); */ + + if (k->state) return; + switch (k->sym) { + case SDLK_n: + song_toggle_multichannel_mode(); + return; + case SDLK_o: + instrument_save(); + return; + case SDLK_s: + swap_instrument_dialog(); + return; + case SDLK_x: + exchange_instrument_dialog(); + return; + case SDLK_w: + song_wipe_instrument(current_instrument); + break; + case SDLK_d: + dialog_create(DIALOG_OK_CANCEL, + "Delete Instrument?", + do_delete_inst, NULL, 1, NULL); + return; + default: + return; + } + + status.flags |= NEED_UPDATE; +} + +static void instrument_list_handle_key(struct key_event * k) +{ + switch (k->sym) { + case SDLK_COMMA: + case SDLK_LESS: + if (k->state) return; + song_change_current_play_channel(-1, 0); + return; + case SDLK_PERIOD: + case SDLK_GREATER: + if (k->state) return; + song_change_current_play_channel(1, 0); + return; + + case SDLK_PAGEUP: + if (k->state) return; + instrument_set(current_instrument - 1); + break; + case SDLK_PAGEDOWN: + if (k->state) return; + instrument_set(current_instrument + 1); + break; + default: + if (k->mod & (KMOD_ALT)) { + instrument_list_handle_alt_key(k); + } else { + int n, v; + + if (k->midi_note > -1) { + n = k->midi_note; + if (k->midi_volume > -1) { + v = k->midi_volume / 2; + } else { + v = 64; + } + } else { + v = 64; + n = kbd_get_note(k); + if (n <= 0 || n > 120) + return; + } + + if (k->state) { + song_keyup(-1, current_instrument, n, -1, 0); + status.last_keysym = 0; + } else if (!k->is_repeat) { + song_keydown(-1, current_instrument, n, v, -1, 0); + } + last_note = n; + } + return; + } +} + +/* --------------------------------------------------------------------- */ + +static void change_subpage(void) +{ + int widget = ACTIVE_PAGE.selected_widget; + int page = status.current_page; + + switch (widget) { + case 1: + page = PAGE_INSTRUMENT_LIST_GENERAL; + break; + case 2: + page = PAGE_INSTRUMENT_LIST_VOLUME; + break; + case 3: + page = PAGE_INSTRUMENT_LIST_PANNING; + break; + case 4: + page = PAGE_INSTRUMENT_LIST_PITCH; + break; +#ifndef NDEBUG + default: + fprintf(stderr, "change_subpage: wtf, how did I get here?\n"); + abort(); + return; +#endif + } + + if (page != status.current_page) { + pages[page].selected_widget = widget; + togglebutton_set(pages[page].widgets, widget, 0); + set_page(page); + instrument_list_subpage = page; + } +} + +/* --------------------------------------------------------------------- */ +/* predraw hooks... */ + +static void instrument_list_general_predraw_hook(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + togglebutton_set(widgets_general, 6 + ins->nna, 0); + togglebutton_set(widgets_general, 10 + ins->dct, 0); + togglebutton_set(widgets_general, 14 + ins->dca, 0); + + widgets_general[17].d.textentry.text = ins->filename; +} + +static void instrument_list_volume_predraw_hook(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + widgets_volume[6].d.toggle.state = !!(ins->flags & ENV_VOLUME); + widgets_volume[7].d.toggle.state = !!(ins->flags & ENV_VOLCARRY); + widgets_volume[8].d.toggle.state = !!(ins->flags & ENV_VOLLOOP); + widgets_volume[11].d.toggle.state = !!(ins->flags & ENV_VOLSUSTAIN); + + /* FIXME: this is the wrong place for this. + ... and it's probably not even right -- how does Impulse Tracker handle loop constraints? + See below for panning/pitch envelopes; same deal there. */ + if (ins->vol_env.loop_start > ins->vol_env.loop_end) + ins->vol_env.loop_end = ins->vol_env.loop_start; + if (ins->vol_env.sustain_start > ins->vol_env.sustain_end) + ins->vol_env.sustain_end = ins->vol_env.sustain_start; + + widgets_volume[9].d.numentry.max = ins->vol_env.nodes - 1; + widgets_volume[10].d.numentry.max = ins->vol_env.nodes - 1; + widgets_volume[12].d.numentry.max = ins->vol_env.nodes - 1; + widgets_volume[13].d.numentry.max = ins->vol_env.nodes - 1; + + widgets_volume[9].d.numentry.value = ins->vol_env.loop_start; + widgets_volume[10].d.numentry.value = ins->vol_env.loop_end; + widgets_volume[12].d.numentry.value = ins->vol_env.sustain_start; + widgets_volume[13].d.numentry.value = ins->vol_env.sustain_end; + + /* mp hack: shifting values all over the place here, ugh */ + widgets_volume[14].d.thumbbar.value = ins->global_volume << 1; + widgets_volume[15].d.thumbbar.value = ins->fadeout >> 5; + widgets_volume[16].d.thumbbar.value = ins->volume_swing; +} + +static void instrument_list_panning_predraw_hook(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + widgets_panning[6].d.toggle.state = !!(ins->flags & ENV_PANNING); + widgets_panning[7].d.toggle.state = !!(ins->flags & ENV_PANCARRY); + widgets_panning[8].d.toggle.state = !!(ins->flags & ENV_PANLOOP); + widgets_panning[11].d.toggle.state = !!(ins->flags & ENV_PANSUSTAIN); + + if (ins->pan_env.loop_start > ins->pan_env.loop_end) + ins->pan_env.loop_end = ins->pan_env.loop_start; + if (ins->pan_env.sustain_start > ins->pan_env.sustain_end) + ins->pan_env.sustain_end = ins->pan_env.sustain_start; + + widgets_panning[9].d.numentry.max = ins->pan_env.nodes - 1; + widgets_panning[10].d.numentry.max = ins->pan_env.nodes - 1; + widgets_panning[12].d.numentry.max = ins->pan_env.nodes - 1; + widgets_panning[13].d.numentry.max = ins->pan_env.nodes - 1; + + widgets_panning[9].d.numentry.value = ins->pan_env.loop_start; + widgets_panning[10].d.numentry.value = ins->pan_env.loop_end; + widgets_panning[12].d.numentry.value = ins->pan_env.sustain_start; + widgets_panning[13].d.numentry.value = ins->pan_env.sustain_end; + + widgets_panning[14].d.toggle.state = !!(ins->flags & ENV_SETPANNING); + widgets_panning[15].d.thumbbar.value = ins->panning >> 2; + /* (widgets_panning[16] is the pitch-pan center) */ + widgets_panning[17].d.thumbbar.value = ins->pitch_pan_separation; + widgets_panning[18].d.thumbbar.value = ins->pan_swing; +} + +static void instrument_list_pitch_predraw_hook(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + widgets_pitch[6].d.menutoggle.state = ((ins->flags & ENV_PITCH) + ? ((ins->flags & ENV_FILTER) + ? 2 : 1) : 0); + widgets_pitch[7].d.toggle.state = !!(ins->flags & ENV_PITCHCARRY); + widgets_pitch[8].d.toggle.state = !!(ins->flags & ENV_PITCHLOOP); + widgets_pitch[11].d.toggle.state = !!(ins->flags & ENV_PITCHSUSTAIN); + + if (ins->pitch_env.loop_start > ins->pitch_env.loop_end) + ins->pitch_env.loop_end = ins->pitch_env.loop_start; + if (ins->pitch_env.sustain_start > ins->pitch_env.sustain_end) + ins->pitch_env.sustain_end = ins->pitch_env.sustain_start; + + widgets_pitch[9].d.numentry.max = ins->pitch_env.nodes - 1; + widgets_pitch[10].d.numentry.max = ins->pitch_env.nodes - 1; + widgets_pitch[12].d.numentry.max = ins->pitch_env.nodes - 1; + widgets_pitch[13].d.numentry.max = ins->pitch_env.nodes - 1; + + widgets_pitch[9].d.numentry.value = ins->pitch_env.loop_start; + widgets_pitch[10].d.numentry.value = ins->pitch_env.loop_end; + widgets_pitch[12].d.numentry.value = ins->pitch_env.sustain_start; + widgets_pitch[13].d.numentry.value = ins->pitch_env.sustain_end; + + if (ins->filter_cutoff & 0x80) + widgets_pitch[14].d.thumbbar.value = ins->filter_cutoff & 0x7f; + else + widgets_pitch[14].d.thumbbar.value = -1; + if (ins->filter_resonance & 0x80) + widgets_pitch[15].d.thumbbar.value = ins->filter_resonance & 0x7f; + else + widgets_pitch[15].d.thumbbar.value = -1; + + /* printf("ins%02d: ch%04d pgm%04d bank%06d drum%04d\n", current_instrument, + ins->midi_channel, ins->midi_program, ins->midi_bank, ins->midi_drum_key); */ + widgets_pitch[16].d.thumbbar.value = ins->midi_channel; + widgets_pitch[17].d.thumbbar.value = (signed char) ins->midi_program; + widgets_pitch[18].d.thumbbar.value = (signed char) (ins->midi_bank & 0xff); + widgets_pitch[19].d.thumbbar.value = (signed char) (ins->midi_bank >> 8); + /* what is midi_drum_key for? */ +} + +/* --------------------------------------------------------------------- */ +/* update values in song */ + +static void instrument_list_general_update_values(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + status.flags |= SONG_NEEDS_SAVE; + for (ins->nna = 4; ins->nna--;) + if (widgets_general[ins->nna + 6].d.togglebutton.state) + break; + for (ins->dct = 4; ins->dct--;) + if (widgets_general[ins->dct + 10].d.togglebutton.state) + break; + for (ins->dca = 3; ins->dca--;) + if (widgets_general[ins->dca + 14].d.togglebutton.state) + break; +} + +#define CHECK_SET(a,b,c) if (a != b) { a = b; c; } + +static void instrument_list_volume_update_values(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + status.flags |= SONG_NEEDS_SAVE; + ins->flags &= ~(ENV_VOLUME | ENV_VOLCARRY | ENV_VOLLOOP | ENV_VOLSUSTAIN); + if (widgets_volume[6].d.toggle.state) + ins->flags |= ENV_VOLUME; + if (widgets_volume[7].d.toggle.state) + ins->flags |= ENV_VOLCARRY; + if (widgets_volume[8].d.toggle.state) + ins->flags |= ENV_VOLLOOP; + if (widgets_volume[11].d.toggle.state) + ins->flags |= ENV_VOLSUSTAIN; + + CHECK_SET(ins->vol_env.loop_start, widgets_volume[9].d.numentry.value, + ins->flags |= ENV_VOLLOOP); + CHECK_SET(ins->vol_env.loop_end, widgets_volume[10].d.numentry.value, + ins->flags |= ENV_VOLLOOP); + CHECK_SET(ins->vol_env.sustain_start, widgets_volume[12].d.numentry.value, + ins->flags |= ENV_VOLSUSTAIN); + CHECK_SET(ins->vol_env.sustain_end, widgets_volume[13].d.numentry.value, + ins->flags |= ENV_VOLSUSTAIN); + + /* more ugly shifts */ + ins->global_volume = widgets_volume[14].d.thumbbar.value >> 1; + ins->fadeout = widgets_volume[15].d.thumbbar.value << 5; + ins->volume_swing = widgets_volume[16].d.thumbbar.value; + + song_update_playing_instrument(current_instrument); +} + +static void instrument_list_panning_update_values(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int n; + + status.flags |= SONG_NEEDS_SAVE; + ins->flags &= ~(ENV_PANNING | ENV_PANCARRY | ENV_PANLOOP | ENV_PANSUSTAIN | ENV_SETPANNING); + if (widgets_panning[6].d.toggle.state) + ins->flags |= ENV_PANNING; + if (widgets_panning[7].d.toggle.state) + ins->flags |= ENV_PANCARRY; + if (widgets_panning[8].d.toggle.state) + ins->flags |= ENV_PANLOOP; + if (widgets_panning[11].d.toggle.state) + ins->flags |= ENV_PANSUSTAIN; + if (widgets_panning[14].d.toggle.state) + ins->flags |= ENV_SETPANNING; + + CHECK_SET(ins->pan_env.loop_start, widgets_panning[9].d.numentry.value, + ins->flags |= ENV_PANLOOP); + CHECK_SET(ins->pan_env.loop_end, widgets_panning[10].d.numentry.value, + ins->flags |= ENV_PANLOOP); + CHECK_SET(ins->pan_env.sustain_start, widgets_panning[12].d.numentry.value, + ins->flags |= ENV_PANSUSTAIN); + CHECK_SET(ins->pan_env.sustain_end, widgets_panning[13].d.numentry.value, + ins->flags |= ENV_PANSUSTAIN); + + n = widgets_panning[15].d.thumbbar.value << 2; + if (ins->panning != n) { + ins->panning = n; + ins->flags |= ENV_SETPANNING; + } + /* (widgets_panning[16] is the pitch-pan center) */ + ins->pitch_pan_separation = widgets_panning[17].d.thumbbar.value; + ins->pan_swing = widgets_panning[18].d.thumbbar.value; + + song_update_playing_instrument(current_instrument); +} + +static void instrument_list_pitch_update_values(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + status.flags |= SONG_NEEDS_SAVE; + ins->flags &= ~(ENV_PITCH | ENV_PITCHCARRY | ENV_PITCHLOOP | ENV_PITCHSUSTAIN | ENV_FILTER); + + switch (widgets_pitch[6].d.menutoggle.state) { + case 2: ins->flags |= ENV_FILTER; + case 1: ins->flags |= ENV_PITCH; + } + + if (widgets_pitch[6].d.menutoggle.state) + ins->flags |= ENV_PITCH; + if (widgets_pitch[7].d.toggle.state) + ins->flags |= ENV_PITCHCARRY; + if (widgets_pitch[8].d.toggle.state) + ins->flags |= ENV_PITCHLOOP; + if (widgets_pitch[11].d.toggle.state) + ins->flags |= ENV_PITCHSUSTAIN; + + CHECK_SET(ins->pitch_env.loop_start, widgets_pitch[9].d.numentry.value, + ins->flags |= ENV_PITCHLOOP); + CHECK_SET(ins->pitch_env.loop_end, widgets_pitch[10].d.numentry.value, + ins->flags |= ENV_PITCHLOOP); + CHECK_SET(ins->pitch_env.sustain_start, widgets_pitch[12].d.numentry.value, + ins->flags |= ENV_PITCHSUSTAIN); + CHECK_SET(ins->pitch_env.sustain_end, widgets_pitch[13].d.numentry.value, + ins->flags |= ENV_PITCHSUSTAIN); + if (widgets_pitch[14].d.thumbbar.value > -1) { + ins->filter_cutoff = widgets_pitch[14].d.thumbbar.value | 0x80; + } else { + ins->filter_cutoff = 0x7f; + } + if (widgets_pitch[15].d.thumbbar.value > -1) { + ins->filter_resonance = widgets_pitch[15].d.thumbbar.value | 0x80; + } else { + ins->filter_resonance = 0x7f; + } + ins->midi_channel = widgets_pitch[16].d.thumbbar.value; + ins->midi_program = widgets_pitch[17].d.thumbbar.value; + ins->midi_bank = ((widgets_pitch[19].d.thumbbar.value << 8) + | (widgets_pitch[18].d.thumbbar.value & 0xff)); + + song_update_playing_instrument(current_instrument); +} + +/* --------------------------------------------------------------------- */ +/* draw_const functions */ + +static void instrument_list_draw_const(void) +{ + draw_box(4, 12, 30, 48, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void instrument_list_general_draw_const(void) +{ + int n; + + instrument_list_draw_const(); + + draw_box(31, 15, 42, 48, BOX_THICK | BOX_INNER | BOX_INSET); + + /* Kind of a hack, and not really useful, but... :) */ + if (status.flags & CLASSIC_MODE) { + draw_box(55, 46, 73, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_text((const unsigned char *)" ", 69, 47, 1, 0); + } else { + draw_box(55, 46, 69, 48, BOX_THICK | BOX_INNER | BOX_INSET); + } + + draw_text((const unsigned char *)"New Note Action", 54, 17, 0, 2); + draw_text((const unsigned char *)"Duplicate Check Type & Action", 47, 32, 0, 2); + draw_text((const unsigned char *)"Filename", 47, 47, 0, 2); + + for (n = 0; n < 35; n++) { + draw_char(134, 44 + n, 15, 0, 2); + draw_char(134, 44 + n, 30, 0, 2); + draw_char(154, 44 + n, 45, 0, 2); + } +} + +static void instrument_list_volume_draw_const(void) +{ + instrument_list_draw_const(); + + draw_fill_chars(57, 28, 62, 29, 0); + draw_fill_chars(57, 32, 62, 34, 0); + draw_fill_chars(57, 37, 62, 39, 0); + + draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 41, 71, 44, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 45, 71, 47, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_text((const unsigned char *)"Volume Envelope", 38, 28, 0, 2); + draw_text((const unsigned char *)"Carry", 48, 29, 0, 2); + draw_text((const unsigned char *)"Envelope Loop", 40, 32, 0, 2); + draw_text((const unsigned char *)"Loop Begin", 43, 33, 0, 2); + draw_text((const unsigned char *)"Loop End", 45, 34, 0, 2); + draw_text((const unsigned char *)"Sustain Loop", 41, 37, 0, 2); + draw_text((const unsigned char *)"SusLoop Begin", 40, 38, 0, 2); + draw_text((const unsigned char *)"SusLoop End", 42, 39, 0, 2); + draw_text((const unsigned char *)"Global Volume", 40, 42, 0, 2); + draw_text((const unsigned char *)"Fadeout", 46, 43, 0, 2); + draw_text((const unsigned char *)"Volume Swing %", 39, 46, 0, 2); +} + +static void instrument_list_panning_draw_const(void) +{ + instrument_list_draw_const(); + + draw_fill_chars(57, 28, 62, 29, 0); + draw_fill_chars(57, 32, 62, 34, 0); + draw_fill_chars(57, 37, 62, 39, 0); + draw_fill_chars(57, 42, 62, 45, 0); + + draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 41, 63, 48, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_text((const unsigned char *)"Panning Envelope", 37, 28, 0, 2); + draw_text((const unsigned char *)"Carry", 48, 29, 0, 2); + draw_text((const unsigned char *)"Envelope Loop", 40, 32, 0, 2); + draw_text((const unsigned char *)"Loop Begin", 43, 33, 0, 2); + draw_text((const unsigned char *)"Loop End", 45, 34, 0, 2); + draw_text((const unsigned char *)"Sustain Loop", 41, 37, 0, 2); + draw_text((const unsigned char *)"SusLoop Begin", 40, 38, 0, 2); + draw_text((const unsigned char *)"SusLoop End", 42, 39, 0, 2); + draw_text((const unsigned char *)"Default Pan", 42, 42, 0, 2); + draw_text((const unsigned char *)"Pan Value", 44, 43, 0, 2); + draw_text((const unsigned char *)"Pitch-Pan Center", 37, 45, 0, 2); + draw_text((const unsigned char *)"Pitch-Pan Separation", 33, 46, 0, 2); + if (status.flags & CLASSIC_MODE) { + /* Hmm. The 's' in swing isn't capitalised. ;) */ + draw_text((const unsigned char *)"Pan swing", 44, 47, 0, 2); + } else { + draw_text((const unsigned char *)"Pan Swing", 44, 47, 0, 2); + } + + draw_text((const unsigned char *)"\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a", 54, 44, 2, 0); +} + +static void instrument_list_pitch_draw_const(void) +{ + instrument_list_draw_const(); + + draw_fill_chars(57, 28, 62, 29, 0); + draw_fill_chars(57, 32, 62, 34, 0); + draw_fill_chars(57, 37, 62, 39, 0); + + draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 41, 71, 48, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_text((const unsigned char *)"Frequency Envelope", 35, 28, 0, 2); + draw_text((const unsigned char *)"Carry", 48, 29, 0, 2); + draw_text((const unsigned char *)"Envelope Loop", 40, 32, 0, 2); + draw_text((const unsigned char *)"Loop Begin", 43, 33, 0, 2); + draw_text((const unsigned char *)"Loop End", 45, 34, 0, 2); + draw_text((const unsigned char *)"Sustain Loop", 41, 37, 0, 2); + draw_text((const unsigned char *)"SusLoop Begin", 40, 38, 0, 2); + draw_text((const unsigned char *)"SusLoop End", 42, 39, 0, 2); + draw_text((const unsigned char *)"Default Cutoff", 36, 42, 0, 2); + draw_text((const unsigned char *)"Default Resonance", 36, 43, 0, 2); + draw_text((const unsigned char *)"MIDI Channel", 36, 44, 0, 2); + draw_text((const unsigned char *)"MIDI Program", 36, 45, 0, 2); + draw_text((const unsigned char *)"MIDI Bank Low", 36, 46, 0, 2); + draw_text((const unsigned char *)"MIDI Bank High", 36, 47, 0, 2); +} + +/* --------------------------------------------------------------------- */ +/* load_page functions */ + +static void _load_page_common(struct page *page, struct widget *page_widgets) +{ + vgamem_font_reserve(&env_overlay); + + page->title = "Instrument List (F4)"; + page->handle_key = instrument_list_handle_key; + page->widgets = page_widgets; + page->help_index = HELP_INSTRUMENT_LIST; + + /* the first five widgets are the same for all four pages. */ + + /* 0 = instrument list */ + create_other(page_widgets + 0, 1, instrument_list_handle_key_on_list, instrument_list_draw_list); + page_widgets[0].x = 5; + page_widgets[0].y = 13; + page_widgets[0].width = 24; + page_widgets[0].height = 34; + + /* 1-4 = subpage switches */ + create_togglebutton(page_widgets + 1, 32, 13, 7, 1, 5, 0, 2, 2, change_subpage, "General", + 1, subpage_switches_group); + create_togglebutton(page_widgets + 2, 44, 13, 7, 2, 5, 1, 3, 3, change_subpage, "Volume", + 1, subpage_switches_group); + create_togglebutton(page_widgets + 3, 56, 13, 7, 3, 5, 2, 4, 4, change_subpage, "Panning", + 1, subpage_switches_group); + create_togglebutton(page_widgets + 4, 68, 13, 7, 4, 5, 3, 0, 0, change_subpage, "Pitch", + 2, subpage_switches_group); +} + +void instrument_list_general_load_page(struct page *page) +{ + _load_page_common(page, widgets_general); + + page->draw_const = instrument_list_general_draw_const; + page->predraw_hook = instrument_list_general_predraw_hook; + page->total_widgets = 18; + + /* special case stuff */ + widgets_general[1].d.togglebutton.state = 1; + widgets_general[2].next.down = widgets_general[3].next.down = widgets_general[4].next.down = 6; + + /* 5 = note trans table */ + create_other(widgets_general + 5, 6, note_trans_handle_key, note_trans_draw); + widgets_general[5].x = 32; + widgets_general[5].y = 16; + widgets_general[5].width = 9; + widgets_general[5].height = 31; + + /* 6-9 = nna toggles */ + create_togglebutton(widgets_general + 6, 46, 19, 29, 2, 7, 5, 0, 0, + instrument_list_general_update_values, + "Note Cut", 2, nna_group); + create_togglebutton(widgets_general + 7, 46, 22, 29, 6, 8, 5, 0, 0, + instrument_list_general_update_values, + "Continue", 2, nna_group); + create_togglebutton(widgets_general + 8, 46, 25, 29, 7, 9, 5, 0, 0, + instrument_list_general_update_values, + "Note Off", 2, nna_group); + create_togglebutton(widgets_general + 9, 46, 28, 29, 8, 10, 5, 0, 0, + instrument_list_general_update_values, + "Note Fade", 2, nna_group); + + /* 10-13 = dct toggles */ + create_togglebutton(widgets_general + 10, 46, 34, 12, 9, 11, 5, 14, + 14, instrument_list_general_update_values, + "Disabled", 2, dct_group); + create_togglebutton(widgets_general + 11, 46, 37, 12, 10, 12, 5, 15, + 15, instrument_list_general_update_values, + "Note", 2, dct_group); + create_togglebutton(widgets_general + 12, 46, 40, 12, 11, 13, 5, 16, + 16, instrument_list_general_update_values, + "Sample", 2, dct_group); + create_togglebutton(widgets_general + 13, 46, 43, 12, 12, 17, 5, 13, + 13, instrument_list_general_update_values, + "Instrument", 2, dct_group); + /* 14-16 = dca toggles */ + create_togglebutton(widgets_general + 14, 62, 34, 13, 9, 15, 10, 0, + 0, instrument_list_general_update_values, + "Note Cut", 2, dca_group); + create_togglebutton(widgets_general + 15, 62, 37, 13, 14, 16, 11, 0, + 0, instrument_list_general_update_values, + "Note Off", 2, dca_group); + create_togglebutton(widgets_general + 16, 62, 40, 13, 15, 17, 12, 0, + 0, instrument_list_general_update_values, + "Note Fade", 2, dca_group); + /* 17 = filename */ + /* impulse tracker has a 17-char-wide box for the filename for + * some reason, though it still limits the actual text to 12 + * characters. go figure... */ + create_textentry(widgets_general + 17, 56, 47, 13, 13, 17, 0, NULL, + NULL, 12); +} + +static int _fixup_mouse_instpage_volume(struct key_event *k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + if (envelope_mouse_edit && ins) { + if (_env_handle_mouse(k, &ins->vol_env, ¤t_node_vol)) { + ins->flags |= ENV_VOLUME; + return 1; + } + } + if ((k->sym == SDLK_l || k->sym == SDLK_b) && (k->mod & KMOD_ALT)) { + return _env_handle_key_viewmode(k, &ins->vol_env, ¤t_node_vol); + } + return 0; +} + +void instrument_list_volume_load_page(struct page *page) +{ + _load_page_common(page, widgets_volume); + + page->pre_handle_key = _fixup_mouse_instpage_volume; + page->draw_const = instrument_list_volume_draw_const; + page->predraw_hook = instrument_list_volume_predraw_hook; + page->total_widgets = 17; + + /* 5 = volume envelope */ + create_other(widgets_volume + 5, 0, volume_envelope_handle_key, volume_envelope_draw); + widgets_volume[5].x = 32; + widgets_volume[5].y = 18; + widgets_volume[5].width = 45; + widgets_volume[5].height = 8; + + /* 6-7 = envelope switches */ + create_toggle(widgets_volume + 6, 54, 28, 5, 7, 0, 0, 0, + instrument_list_volume_update_values); + create_toggle(widgets_volume + 7, 54, 29, 6, 8, 0, 0, 0, + instrument_list_volume_update_values); + + /* 8-10 envelope loop settings */ + create_toggle(widgets_volume + 8, 54, 32, 7, 9, 0, 0, 0, + instrument_list_volume_update_values); + create_numentry(widgets_volume + 9, 54, 33, 3, 8, 10, 0, + instrument_list_volume_update_values, 0, 1, + numentry_cursor_pos + 0); + create_numentry(widgets_volume + 10, 54, 34, 3, 9, 11, 0, + instrument_list_volume_update_values, 0, 1, + numentry_cursor_pos + 0); + + /* 11-13 = susloop settings */ + create_toggle(widgets_volume + 11, 54, 37, 10, 12, 0, 0, 0, + instrument_list_volume_update_values); + create_numentry(widgets_volume + 12, 54, 38, 3, 11, 13, 0, + instrument_list_volume_update_values, 0, 1, + numentry_cursor_pos + 0); + create_numentry(widgets_volume + 13, 54, 39, 3, 12, 14, 0, + instrument_list_volume_update_values, 0, 1, + numentry_cursor_pos + 0); + + /* 14-16 = volume thumbbars */ + create_thumbbar(widgets_volume + 14, 54, 42, 17, 13, 15, 0, + instrument_list_volume_update_values, 0, 128); + create_thumbbar(widgets_volume + 15, 54, 43, 17, 14, 16, 0, + instrument_list_volume_update_values, 0, 128); + create_thumbbar(widgets_volume + 16, 54, 46, 17, 15, 16, 0, + instrument_list_volume_update_values, 0, 100); +} + +static int _fixup_mouse_instpage_panning(struct key_event *k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + if (envelope_mouse_edit && ins) { + if (_env_handle_mouse(k, &ins->pan_env, ¤t_node_pan)) { + ins->flags |= ENV_PANNING; + return 1; + } + } + if ((k->sym == SDLK_l || k->sym == SDLK_b) && (k->mod & KMOD_ALT)) { + return _env_handle_key_viewmode(k, &ins->pan_env, ¤t_node_pan); + } + return 0; +} +void instrument_list_panning_load_page(struct page *page) +{ + _load_page_common(page, widgets_panning); + + page->pre_handle_key = _fixup_mouse_instpage_panning; + page->draw_const = instrument_list_panning_draw_const; + page->predraw_hook = instrument_list_panning_predraw_hook; + page->total_widgets = 19; + + /* 5 = panning envelope */ + create_other(widgets_panning + 5, 0, panning_envelope_handle_key, panning_envelope_draw); + widgets_panning[5].x = 32; + widgets_panning[5].y = 18; + widgets_panning[5].width = 45; + widgets_panning[5].height = 8; + + /* 6-7 = envelope switches */ + create_toggle(widgets_panning + 6, 54, 28, 5, 7, 0, 0, 0, + instrument_list_panning_update_values); + create_toggle(widgets_panning + 7, 54, 29, 6, 8, 0, 0, 0, + instrument_list_panning_update_values); + + /* 8-10 envelope loop settings */ + create_toggle(widgets_panning + 8, 54, 32, 7, 9, 0, 0, 0, + instrument_list_panning_update_values); + create_numentry(widgets_panning + 9, 54, 33, 3, 8, 10, 0, + instrument_list_panning_update_values, 0, 1, + numentry_cursor_pos + 1); + create_numentry(widgets_panning + 10, 54, 34, 3, 9, 11, 0, + instrument_list_panning_update_values, 0, 1, + numentry_cursor_pos + 1); + + /* 11-13 = susloop settings */ + create_toggle(widgets_panning + 11, 54, 37, 10, 12, 0, 0, 0, + instrument_list_panning_update_values); + create_numentry(widgets_panning + 12, 54, 38, 3, 11, 13, 0, + instrument_list_panning_update_values, 0, 1, + numentry_cursor_pos + 1); + create_numentry(widgets_panning + 13, 54, 39, 3, 12, 14, 0, + instrument_list_panning_update_values, 0, 1, + numentry_cursor_pos + 1); + + /* 14-15 = default panning */ + create_toggle(widgets_panning + 14, 54, 42, 13, 15, 0, 0, 0, + instrument_list_panning_update_values); + create_thumbbar(widgets_panning + 15, 54, 43, 9, 14, 16, 0, + instrument_list_panning_update_values, 0, 64); + + /* 16 = pitch-pan center */ + create_other(widgets_panning + 16, 0, pitch_pan_center_handle_key, pitch_pan_center_draw); + widgets_panning[16].next.up = 15; + widgets_panning[16].next.down = 17; + + /* 17-18 = other panning stuff */ + create_thumbbar(widgets_panning + 17, 54, 46, 9, 16, 18, 0, + instrument_list_panning_update_values, -32, 32); + create_thumbbar(widgets_panning + 18, 54, 47, 9, 17, 18, 0, + instrument_list_panning_update_values, 0, 64); +} + +static int _fixup_mouse_instpage_pitch(struct key_event *k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + if (envelope_mouse_edit && ins) { + if (_env_handle_mouse(k, &ins->pitch_env, ¤t_node_pitch)) { + ins->flags |= ENV_PITCH; + return 1; + } + } + if ((k->sym == SDLK_l || k->sym == SDLK_b) && (k->mod & KMOD_ALT)) { + return _env_handle_key_viewmode(k, &ins->pitch_env, ¤t_node_pitch); + } + return 0; +} +void instrument_list_pitch_load_page(struct page *page) +{ + _load_page_common(page, widgets_pitch); + + page->pre_handle_key = _fixup_mouse_instpage_pitch; + page->draw_const = instrument_list_pitch_draw_const; + page->predraw_hook = instrument_list_pitch_predraw_hook; + page->total_widgets = 20; + + /* 5 = pitch envelope */ + create_other(widgets_pitch + 5, 0, pitch_envelope_handle_key, pitch_envelope_draw); + widgets_pitch[5].x = 32; + widgets_pitch[5].y = 18; + widgets_pitch[5].width = 45; + widgets_pitch[5].height = 8; + + /* 6-7 = envelope switches */ + create_menutoggle(widgets_pitch + 6, 54, 28, 5, 7, 0, 0, 0, + instrument_list_pitch_update_values, pitch_envelope_states); + create_toggle(widgets_pitch + 7, 54, 29, 6, 8, 0, 0, 0, + instrument_list_pitch_update_values); + + /* 8-10 envelope loop settings */ + create_toggle(widgets_pitch + 8, 54, 32, 7, 9, 0, 0, 0, + instrument_list_pitch_update_values); + create_numentry(widgets_pitch + 9, 54, 33, 3, 8, 10, 0, + instrument_list_pitch_update_values, 0, 1, + numentry_cursor_pos + 2); + create_numentry(widgets_pitch + 10, 54, 34, 3, 9, 11, 0, + instrument_list_pitch_update_values, 0, 1, + numentry_cursor_pos + 2); + + /* 11-13 = susloop settings */ + create_toggle(widgets_pitch + 11, 54, 37, 10, 12, 0, 0, 0, + instrument_list_pitch_update_values); + create_numentry(widgets_pitch + 12, 54, 38, 3, 11, 13, 0, + instrument_list_pitch_update_values, 0, 1, + numentry_cursor_pos + 2); + create_numentry(widgets_pitch + 13, 54, 39, 3, 12, 14, 0, + instrument_list_pitch_update_values, 0, 1, + numentry_cursor_pos + 2); + + /* 14-15 = filter cutoff/resonance */ + create_thumbbar(widgets_pitch + 14, 54, 42, 17, 13, 15, 0, + instrument_list_pitch_update_values, -1, 127); + create_thumbbar(widgets_pitch + 15, 54, 43, 17, 14, 16, 0, + instrument_list_pitch_update_values, -1, 127); + widgets_pitch[14].d.thumbbar.text_at_min = "Off"; + widgets_pitch[15].d.thumbbar.text_at_min = "Off"; + + /* 16-19 = midi crap */ + create_thumbbar(widgets_pitch + 16, 54, 44, 17, 15, 17, 0, + instrument_list_pitch_update_values, 0, 17); + create_thumbbar(widgets_pitch + 17, 54, 45, 17, 16, 18, 0, + instrument_list_pitch_update_values, -1, 127); + create_thumbbar(widgets_pitch + 18, 54, 46, 17, 17, 19, 0, + instrument_list_pitch_update_values, -1, 127); + create_thumbbar(widgets_pitch + 19, 54, 47, 17, 18, 19, 0, + instrument_list_pitch_update_values, -1, 127); + widgets_pitch[16].d.thumbbar.text_at_min = "Off"; + widgets_pitch[16].d.thumbbar.text_at_max = "Mapped"; + widgets_pitch[17].d.thumbbar.text_at_min = "Off"; + widgets_pitch[18].d.thumbbar.text_at_min = "Off"; + widgets_pitch[19].d.thumbbar.text_at_min = "Off"; +} diff --git a/schism/page_loadinst.c b/schism/page_loadinst.c new file mode 100644 index 000000000..04bff99b0 --- /dev/null +++ b/schism/page_loadinst.c @@ -0,0 +1,488 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "dmoz.h" + +#include +#include + +#include +#include +#include +#include + +/* --------------------------------------------------------------------------------------------------------- */ +/* the locals */ + +static struct widget widgets_loadinst[1]; +static char inst_cwd[PATH_MAX+1]; + +/* --------------------------------------------------------------------------------------------------------- */ + +/* files: + file type color displayed title notes + --------- ----- --------------- ----- + unchecked 4 IT uses color 6 for these + directory 5 "........Directory........" dots are char 154 (same for libraries) + sample 3 + libraries 6 ".........Library........." IT uses color 3. maybe use module name here? + unknown 2 any regular file that's not recognized +*/ + +static int top_file = 0; +static int current_file = 0; +static time_t directory_mtime; +static int _library_mode = 0; +static dmoz_filelist_t flist; + +static int slash_search_mode = -1; +static char slash_search_str[PATH_MAX]; + +/* get a color index from a dmoz_file_t 'type' field */ +static inline int get_type_color(int type) +{ + if (type == TYPE_DIRECTORY) + return 5; + if (!(type & TYPE_EXT_DATA_MASK)) + return 4; /* unchecked */ + if (type & TYPE_BROWSABLE_MASK) + return 6; /* library */ + if (type == TYPE_UNKNOWN) + return 2; + return 3; /* sample */ +} + + +static void clear_directory(void) +{ + dmoz_free(&flist, NULL); + top_file = current_file = 0; +} + +static int instgrep(dmoz_file_t *f) +{ + if ((f->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(f); + + if ((f->type & TYPE_EXT_DATA_MASK) == 0) return 0; + return (f->type == TYPE_SAMPLE_MASK + || f->type == TYPE_UNKNOWN) + ? 0 : 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void file_list_reposition(void) +{ + if (current_file < top_file) + top_file = current_file; + else if (current_file > top_file + 34) + top_file = current_file - 34; +} + +static void read_directory(void) +{ + struct stat st; + char *tmp; + int i; + + if (current_file >= 0 && current_file < flist.num_files + && flist.files[current_file] && flist.files[current_file]->base) { + tmp = strdup(flist.files[current_file]->base); + } else { + tmp = 0; + } + + clear_directory(); + + if (stat(inst_cwd, &st) < 0) + directory_mtime = 0; + else + directory_mtime = st.st_mtime; + /* if the stat call failed, this will probably break as well, but + at the very least, it'll add an entry for the root directory. */ + if (dmoz_read_ex(inst_cwd, &flist, NULL, + dmoz_read_instrument_library) < 0) + perror(inst_cwd); + + dmoz_filter_filelist(&flist,instgrep, ¤t_file, file_list_reposition); + + if (tmp) { + for (i = 0; i < flist.num_files; i++) { + if (flist.files && flist.files[i] && flist.files[i]->base + && strcmp(tmp, flist.files[i]->base) == 0) { + current_file = i; + break; + } + } + (void)free(tmp); + } + +} + +/* return: 1 = success, 0 = failure +TODO: provide some sort of feedback if something went wrong. */ +static int change_dir(const char *dir) +{ + char *ptr = dmoz_path_normal(dir); + struct stat buf; + + if (!ptr) + return 0; + + if (stat(ptr, &buf) == 0 && S_ISDIR(buf.st_mode)) { + strncpy(cfg_dir_instruments, ptr, PATH_MAX); + cfg_dir_instruments[PATH_MAX] = 0; + + } + strncpy(inst_cwd, ptr, PATH_MAX); + inst_cwd[PATH_MAX] = 0; + free(ptr); + + read_directory(); + top_file = current_file = 0; + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void load_instrument_draw_const(void) +{ + draw_fill_chars(6, 13, 67, 47, 0); + draw_thin_inner_box(50, 12, 61, 48, 0,0); + draw_box(5, 12, 68, 48, BOX_THICK | BOX_INNER | BOX_INSET); + +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void _common_set_page(void) +{ + struct stat st; + + if (!inst_cwd[0]) { + strcpy(inst_cwd, cfg_dir_instruments); + } + + /* if we have a list, the directory didn't change, and the mtime is the same, we're set */ + if (flist.num_files > 0 + && (status.flags & DIR_SAMPLES_CHANGED) == 0 + && stat(inst_cwd, &st) == 0 + && st.st_mtime == directory_mtime) { + return; + } + + change_dir(inst_cwd); + + status.flags &= ~DIR_INSTRUMENTS_CHANGED; + + *selected_widget = 0; + slash_search_mode = -1; +} +static void load_instrument_set_page(void) +{ + _library_mode = 0; + _common_set_page(); +} +static void library_instrument_set_page(void) +{ + _library_mode = 1; + _common_set_page(); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void file_list_draw(void) +{ + int n, pos, fg, bg, i; + char buf[8]; + char sbuf[32]; + dmoz_file_t *file; + + /* there's no need to have if (files) { ... } like in the load-module page, + because there will always be at least "/" in the list */ + if (top_file < 0) top_file = 0; + if (current_file < 0) current_file = 0; + for (n = top_file, pos = 13; n < flist.num_files && pos < 48; n++, pos++) { + file = flist.files[n]; + + if (n == current_file && ACTIVE_PAGE.selected_widget == 0) { + fg = 0; + bg = 3; + } else { + fg = get_type_color(file->type); + bg = 0; + } + + draw_text(numtostr(3, n, buf), 2, pos, 0, 2); + draw_text_len(file->title ? file->title : "", + 25, 6, pos, fg, bg); + draw_char(168, 31, pos, 2, bg); + draw_text_len(file->base ? file->base : "", + 18, 32, pos, fg, bg); + + if (file->base && slash_search_mode > -1) { + if (strncasecmp(file->base,slash_search_str,slash_search_mode) == 0) { + for (i = 0 ; i < slash_search_mode; i++) { + if (tolower(((unsigned)file->base[i])) + != tolower(((unsigned)slash_search_str[i]))) break; + draw_char(file->base[i], 32+i, pos, 3,1); + } + } + } + + if (file->sampsize > 1) { + sprintf(sbuf, "%u Samples", file->sampsize); + draw_text_len(sbuf, 10, 51, pos, fg, bg); + } else if (file->sampsize == 1) { + draw_text("1 Sample ", 51, pos, fg, bg); + } else if (file->type & TYPE_MODULE_MASK) { + draw_text("\x9a\x9a""Module\x9a\x9a", 51, pos, fg, bg); + } else { + draw_text(" ", 51, pos, fg, bg); + } + if (file->filesize > 1048576) { + sprintf(sbuf, "%um", file->filesize / 1048576); + } else if (file->filesize > 1024) { + sprintf(sbuf, "%uk", file->filesize / 1024); + } else if (file->filesize > 0) { + sprintf(sbuf, "%u", file->filesize); + } else { + *sbuf = 0; + } + draw_text_len(sbuf, 6, 62, pos, fg, bg); + } + + /* draw the info for the current file (or directory...) */ + + while (pos < 48) + draw_char(168, 31, pos++, 2, 0); +} + +/* on the file list, that is */ +static void do_enable_inst(UNUSED void *d) +{ + song_set_instrument_mode(1); + main_song_changed_cb(); + set_page(PAGE_INSTRUMENT_LIST); + memused_songchanged(); +} +static void dont_enable_inst(UNUSED void *d) +{ + set_page(PAGE_INSTRUMENT_LIST); +} +static void reposition_at_slash_search(void) +{ + dmoz_file_t *f; + int i, j, b, bl; + + if (slash_search_mode < 0) return; + bl = b = -1; + for (i = 0; i < flist.num_files; i++) { + f = flist.files[i]; + if (!f || !f->base) continue; + for (j = 0; j < slash_search_mode; j++) { + if (tolower(((unsigned)f->base[j])) + != tolower(((unsigned)slash_search_str[j]))) break; + } + if (bl < j) { + bl = j; + b = i; + } + } + if (bl > -1) { + current_file = b; + file_list_reposition(); + } +} +static void handle_enter_key(void) +{ + dmoz_file_t *file; + int cur = instrument_get_current(); + + if (current_file < 0 || current_file >= flist.num_files) return; + file = flist.files[current_file]; + if (file->type & TYPE_BROWSABLE_MASK) { + change_dir(file->path); + status.flags |= NEED_UPDATE; + } else if (file->type & TYPE_INST_MASK) { + if (_library_mode) return; + if (file->instnum > -1) { + song_load_instrument_ex(cur, NULL, + file->path, file->instnum); + } else { + song_load_instrument(cur, file->path); + } + if (!song_is_instrument_mode()) { + dialog_create(DIALOG_YES_NO, + "Enable instrument mode?", + do_enable_inst, dont_enable_inst, 0, NULL); + } else { + set_page(PAGE_INSTRUMENT_LIST); + } + memused_songchanged(); + } + + /* TODO */ +} + +static int file_list_handle_key(struct key_event * k) +{ + int new_file = current_file; + + new_file = CLAMP(new_file, 0, flist.num_files - 1); + + if (k->mouse) { + if (k->x >= 6 && k->x <= 67 && k->y >= 13 && k->y <= 47) { + if (k->mouse == MOUSE_SCROLL_UP) { + new_file--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_file++; + } else { + new_file = top_file + (k->y - 13); + } + } + } else if (slash_search_mode > -1) { + int c = unicode_to_ascii(k->unicode); + if (k->sym == SDLK_RETURN || k->sym == SDLK_ESCAPE) { + if (!k->state) return 1; + slash_search_mode = -1; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->sym == SDLK_BACKSPACE) { + if (k->state) return 1; + slash_search_mode--; + status.flags |= NEED_UPDATE; + reposition_at_slash_search(); + return 1; + } else if (c >= 32) { + if (k->state) return 1; + if (slash_search_mode < PATH_MAX) { + slash_search_str[ slash_search_mode ] = c; + slash_search_mode++; + reposition_at_slash_search(); + status.flags |= NEED_UPDATE; + } + return 1; + } + } + + switch (k->sym) { + case SDLK_UP: + new_file--; + break; + case SDLK_DOWN: + new_file++; + break; + case SDLK_PAGEUP: + new_file -= 35; + break; + case SDLK_PAGEDOWN: + new_file += 35; + break; + case SDLK_HOME: + new_file = 0; + break; + case SDLK_END: + new_file = flist.num_files - 1; + break; + case SDLK_RETURN: + if (!k->state) return 0; + handle_enter_key(); + return 1; + case SDLK_ESCAPE: + if (k->state && NO_MODIFIER(k->mod)) + set_page(PAGE_INSTRUMENT_LIST); + return 1; + case SDLK_SLASH: + if (k->orig_sym == SDLK_SLASH) { + if (status.flags & CLASSIC_MODE) return 0; + if (!k->state) return 0; + slash_search_mode = 0; + status.flags |= NEED_UPDATE; + return 1; + } + default: + if (!k->mouse) return 0; + } + + if (k->mouse == MOUSE_CLICK) { + if (!k->state) return 0; + } else if (k->mouse == MOUSE_DBLCLICK) { + handle_enter_key(); + return 1; + } else { + if (k->state) return 0; + } + + new_file = CLAMP(new_file, 0, flist.num_files - 1); + if (new_file < 0) new_file = 0; + if (new_file != current_file) { + current_file = new_file; + file_list_reposition(); + status.flags |= NEED_UPDATE; + } + return 1; +} + +static void load_instrument_handle_key(struct key_event * k) +{ + if (!k->state) return; + if (k->sym == SDLK_ESCAPE && NO_MODIFIER(k->mod)) + set_page(PAGE_INSTRUMENT_LIST); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +void load_instrument_load_page(struct page *page) +{ + clear_directory(); + + page->title = "Load Instrument"; + page->draw_const = load_instrument_draw_const; + page->set_page = load_instrument_set_page; + page->handle_key = load_instrument_handle_key; + page->total_widgets = 1; + page->widgets = widgets_loadinst; + page->help_index = HELP_GLOBAL; + inst_cwd[0] = 0; + create_other(widgets_loadinst + 0, 0, file_list_handle_key, file_list_draw); +} +void library_instrument_load_page(struct page *page) +{ + clear_directory(); + + page->title = "Instrument Library (Ctrl-F4)"; + page->draw_const = load_instrument_draw_const; + page->set_page = library_instrument_set_page; + page->handle_key = load_instrument_handle_key; + page->total_widgets = 1; + page->widgets = widgets_loadinst; + page->help_index = HELP_GLOBAL; + inst_cwd[0] = 0; + create_other(widgets_loadinst + 0, 0, file_list_handle_key, file_list_draw); +} diff --git a/schism/page_loadmodule.c b/schism/page_loadmodule.c new file mode 100644 index 000000000..efad5731c --- /dev/null +++ b/schism/page_loadmodule.c @@ -0,0 +1,919 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "dmoz.h" + +#include +#include + +#include +#include +#include +#include + +#include "mplink.h" + +#include "diskwriter.h" + +/* --------------------------------------------------------------------- */ +/* the locals */ + +static int modgrep(dmoz_file_t *f); + +static struct widget widgets_loadmodule[5]; +static struct widget widgets_savemodule[16]; + +/* XXX this needs to be kept in sync with diskwriters */ +static int filetype_saves[] = { 4, 5, 6, 7, -1 }; + +static int top_file = 0, top_dir = 0; +static int current_file = 0, current_dir = 0; +static time_t directory_mtime; +dmoz_filelist_t flist; +dmoz_dirlist_t dlist; + + +/* filename_entry is updated whenever the selected file is changed. (this differs from impulse tracker, which +accepts wildcards in the filename box... i'm not doing this partly because filenames could be longer than the +visible text in the browser, and partly because i just don't want to write that code.) + +dirname_entry is copied from the module directory (off the vars page) when this page is loaded, and copied back +when the directory is changed. in general the two variables will be the same, but editing the text directly +won't screw up the directory listing or anything. (hitting enter will cause the changed callback, which will +copy the text from dirname_entry to the actual configured string and update the current directory.) + +whew. */ +static char filename_entry[NAME_MAX + 1] = ""; +static char dirname_entry[PATH_MAX + 1] = ""; + +/* --------------------------------------------------------------------- */ +/* page-dependent stuff (load or save) */ + +/* there should be a more useful way to determine which page to set. i.e., if there were errors/warnings, show +the log; otherwise, switch to the blank page (or, for the loader, maybe the previously set page if classic mode +is off?) +idea for return codes: + 0 = couldn't load/save, error dumped to log + 1 = no warnings or errors were printed. + 2 = there were warnings, but the song was still loaded/saved. */ + +static void handle_file_entered_L(char *ptr) +{ + dmoz_filelist_t tmp; + struct stat sb; + dmoz_file_t *f; + int r; + +/* these shenanagans force the file to take another trip... */ + if (stat(ptr, &sb) == -1) return; + + memset(&tmp,0,sizeof(tmp)); + f = dmoz_add_file(&tmp, strdup(ptr), strdup(ptr), &sb, 0); + r = modgrep(f); + dmoz_free(&tmp, NULL); + + if (r && song_load(ptr)) { + /* set_page(PAGE_LOG); */ + set_page(PAGE_BLANK); + } +} + + +static void do_save_song(void *ptr) +{ + int i; + const char *typ = NULL; + const char *s; + + set_page(PAGE_LOG); + + for (i = 0; diskwriter_drivers[i]; i++) { + if (widgets_savemodule[i+5].d.togglebutton.state) { + typ = widgets_savemodule[i+5].d.togglebutton.text; + break; + } + } + if (!typ) { + for (s = (const char *)ptr; *s; s++) { + if (*s == '.') { + for (i = 0; diskwriter_drivers[i]; i++) { + if (strcasecmp(s+1, + diskwriter_drivers[i]->name) == 0) { + typ = diskwriter_drivers[i]->name; + } + } + } + } + if (!typ) typ = "IT214"; + } + if (song_save(ptr, typ)) { + set_page(PAGE_LOG); + /* set_page(PAGE_BLANK); */ + } +} +static void do_save_song_overwrite(void *ptr) +{ + struct stat st; + + if (!(status.flags & CLASSIC_MODE)) { + do_save_song(ptr); + return; + } + + if (stat(cfg_dir_modules, &st) == -1 + || directory_mtime != st.st_mtime) { + status.flags |= DIR_MODULES_CHANGED; + } + + do_save_song(ptr); + + /* this is wrong, sadly... */ + if (stat(cfg_dir_modules, &st) == 0) { + directory_mtime = st.st_mtime; + } +} + +static void handle_file_entered_S(char *ptr) +{ + struct stat buf; + + if (stat(ptr, &buf) < 0) { + if (errno == ENOENT) { + do_save_song(ptr); + } else { + log_appendf(4, "%s: %s", ptr, strerror(errno)); + } + } else { + if (S_ISDIR(buf.st_mode)) { + /* TODO: maybe change the current directory in this case? */ + log_appendf(4, "%s: Is a directory", ptr); + } else if (S_ISREG(buf.st_mode)) { + dialog_create(DIALOG_OK_CANCEL, "Overwrite file?", do_save_song_overwrite, free, 1, strdup(ptr)); + } else { + /* log_appendf(4, "%s: Not overwriting non-regular file", ptr); */ + dialog_create(DIALOG_OK, "Not a regular file", NULL, NULL, 0, NULL); + } + } +} + + +static void (*handle_file_entered)(char *); + +/* --------------------------------------------------------------------- */ + +/* get a color index from a dmoz_file_t 'type' field */ +static inline int get_type_color(int type) +{ + /* 7 unknown + 3 it + 5 s3m + 6 xm + 2 mod + 4 other + 7 sample */ + if (type == TYPE_MODULE_MOD) + return 2; + if (type == TYPE_MODULE_S3M) + return 5; + if (type == TYPE_MODULE_XM) + return 6; + if (type == TYPE_MODULE_IT) + return 3; + if (type == TYPE_SAMPLE_COMPR) + return 4; /* mp3/ogg 'sample'... i think */ + return 7; +} + + +static void clear_directory(void) +{ + dmoz_free(&flist, &dlist); +} + +static int modgrep(dmoz_file_t *f) +{ + if ((f->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(f); + if ((f->type & TYPE_EXT_DATA_MASK) == 0) return 0; + + return (f->type & TYPE_MODULE_MASK ? 1 : 0); +} + +/* --------------------------------------------------------------------- */ + +static void file_list_reposition(void) +{ + if (current_file < top_file) + top_file = current_file; + else if (current_file > top_file + 30) + top_file = current_file - 30; +} + +static void dir_list_reposition(void) +{ + if (current_dir < top_dir) + top_dir = current_dir; + else if (current_dir > top_dir + 20) + top_dir = current_dir - 20; +} + +static void read_directory(void) +{ + struct stat st; + int i; + char *tmp; + + if (current_file >= 0 && current_file < flist.num_files + && flist.files[current_file] && flist.files[current_file]->base) { + tmp = strdup(flist.files[current_file]->base); + } else { + tmp = 0; + } + + clear_directory(); + + if (stat(cfg_dir_modules, &st) < 0) + directory_mtime = 0; + else + directory_mtime = st.st_mtime; + /* if the stat call failed, this will probably break as well, but + at the very least, it'll add an entry for the root directory. */ + if (dmoz_read(cfg_dir_modules, &flist, &dlist) < 0) + perror(cfg_dir_modules); + dmoz_filter_filelist(&flist, modgrep, ¤t_file, file_list_reposition); + if (tmp) { + for (i = 0; i < flist.num_files; i++) { + if (flist.files && flist.files[i] && flist.files[i]->base + && strcmp(tmp, flist.files[i]->base) == 0) { + current_file = i; + break; + } + } + (void)free(tmp); + } +} + +/* --------------------------------------------------------------------- */ + +static void update_filename_entry(void) +{ + if (status.current_page == PAGE_LOAD_MODULE) { + widgets_loadmodule[2].d.textentry.firstchar = widgets_loadmodule[2].d.textentry.cursor_pos = 0; + } else { + widgets_savemodule[2].d.textentry.firstchar = widgets_savemodule[2].d.textentry.cursor_pos = 0; + } + if (current_file >= 0 && current_file < flist.num_files) { + strncpy(filename_entry, flist.files[current_file]->base, NAME_MAX); + filename_entry[NAME_MAX] = 0; + } else { + filename_entry[0] = 0; + } +} + +/* --------------------------------------------------------------------- */ + +static char search_text[NAME_MAX + 1] = ""; +static int search_first_char = 0; /* first visible character */ +static int search_text_length = 0; /* same as strlen(search_text) */ + +static void search_redraw(void) +{ + draw_fill_chars(51, 37, 76, 37, 0); + draw_text_len(search_text + search_first_char, 25, 51, 37, 5, 0); + + /* draw the cursor if it's on the dir/file list */ + if (ACTIVE_PAGE.selected_widget == 0 || ACTIVE_PAGE.selected_widget == 1) { + draw_char(0, 51 + search_text_length - search_first_char, 37, 6, 6); + } +} + +static void search_update(void) +{ + int found_something = 0; + int n; + + if (search_text_length > 25) + search_first_char = search_text_length - 25; + else + search_first_char = 0; + + /* go through the file/dir list (whatever one is selected) and + * find the first entry matching the text */ + if (*selected_widget == 0) { + for (n = 0; n < flist.num_files; n++) { + if (strncasecmp(flist.files[n]->base, search_text, search_text_length) == 0) { + found_something = 1; + current_file = n; + file_list_reposition(); + break; + } + } + } else { + for (n = 0; n < dlist.num_dirs; n++) { + if (strncasecmp(dlist.dirs[n]->base, search_text, search_text_length) == 0) { + found_something = 1; + current_dir = n; + dir_list_reposition(); + break; + } + } + } + + status.flags |= NEED_UPDATE; +} + +static int search_text_add_char(char c) +{ + if (c < 32) + return 0; + + if (search_text_length >= NAME_MAX) + return 1; + + search_text[search_text_length++] = c; + search_text[search_text_length] = 0; + search_update(); + + return 1; +} + +static void search_text_delete_char(void) +{ + if (search_text_length == 0) + return; + + search_text[--search_text_length] = 0; + + if (search_text_length > 25) + search_first_char = search_text_length - 25; + else + search_first_char = 0; + + status.flags |= NEED_UPDATE; +} + +static void search_text_clear(void) +{ + search_text[0] = search_text_length = search_first_char = 0; + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +/* return: 1 = success, 0 = failure +TODO: provide some sort of feedback if something went wrong. */ +static int change_dir(const char *dir) +{ + char *ptr = dmoz_path_normal(dir); + + if (!ptr) + return 0; + + strncpy(cfg_dir_modules, ptr, PATH_MAX); + cfg_dir_modules[PATH_MAX] = 0; + strcpy(dirname_entry, cfg_dir_modules); + free(ptr); + + /* probably not all of this is needed everywhere */ + search_text_clear(); + read_directory(); + update_filename_entry(); + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* unfortunately, there's not enough room with this layout for labels by + * the search box and file information. :( */ + +static void load_module_draw_const(void) +{ + draw_text("Filename", 4, 46, 0, 2); + draw_text("Directory", 3, 47, 0, 2); + draw_char(0, 51, 37, 0, 6); + draw_box(2, 12, 47, 44, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(49, 12, 68, 34, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(50, 36, 77, 38, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(50, 39, 77, 44, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(12, 45, 77, 48, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_fill_chars(51, 37, 76, 37, 0); + draw_fill_chars(13, 46, 76, 47, 0); +} + +static void save_module_draw_const(void) +{ + load_module_draw_const(); +} + +/* --------------------------------------------------------------------- */ + +static void file_list_draw(void) +{ + int n, pos; + int fg1, fg2, bg; + char buf[32]; + dmoz_file_t *file; + + draw_fill_chars(3, 13, 46, 43, 0); + + if (flist.num_files > 0) { + if (top_file < 0) top_file = 0; + if (current_file < 0) current_file = 0; + for (n = top_file, pos = 13; n < flist.num_files && pos < 44; n++, pos++) { + file = flist.files[n]; + + if ((file->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(file); + + if (n == current_file && ACTIVE_PAGE.selected_widget == 0) { + fg1 = fg2 = 0; + bg = 3; + } else { + fg1 = get_type_color(file->type); + fg2 = (file->type & TYPE_MODULE_MASK) ? 3 : 7; + bg = 0; + } + + draw_text_len(file->base, 18, 3, pos, fg1, bg); + draw_char(168, 21, pos, 2, bg); + draw_text_len(file->title, 25, 22, pos, fg2, bg); + } + + /* info for the current file */ + if (current_file >= 0 && current_file < flist.num_files) { + file = flist.files[current_file]; + draw_text_len(file->description ? file->description : "", 26, 51, 40, 5, 0); + sprintf(buf, "%09d", file->filesize); + draw_text_len(buf, 26, 51, 41, 5, 0); + draw_text_len(get_date_string(file->timestamp, buf), 26, 51, 42, 5, 0); + draw_text_len(get_time_string(file->timestamp, buf), 26, 51, 43, 5, 0); + } + } else { + if (ACTIVE_PAGE.selected_widget == 0) { + draw_text("No files.", 3, 13, 0, 3); + draw_fill_chars(12, 13, 46, 13, 3); + draw_char(168, 21, 13, 2, 3); + pos = 14; + } else { + draw_text("No files.", 3, 13, 7, 0); + pos = 13; + } + draw_fill_chars(51, 40, 76, 43, 0); + } + + while (pos < 44) + draw_char(168, 21, pos++, 2, 0); + + /* bleh */ + search_redraw(); +} + +static void do_delete_file(UNUSED void *data) +{ + int old_top_file, old_current_file, old_top_dir, old_current_dir; + char *ptr; + + if (current_file < 0 || current_file >= flist.num_files) + return; + + ptr = flist.files[current_file]->path; + + /* would be neat to send it to the trash can if there is one */ + unlink(ptr); + + /* remember the list positions */ + old_top_file = top_file; + old_current_file = current_file; + old_top_dir = top_dir; + old_current_dir = current_dir; + + search_text_clear(); + read_directory(); + + /* put the list positions back */ + top_file = old_top_file; + current_file = old_current_file; + top_dir = old_top_dir; + current_dir = old_current_dir; + /* edge case: if this was the last file, move the cursor up */ + if (current_file >= flist.num_files) + current_file = flist.num_files - 1; + file_list_reposition(); +} + +static int file_list_handle_key(struct key_event * k) +{ + int new_file = current_file; + + if (k->mouse) { + if (k->x >= 3 && k->x <= 46 && k->y >= 13 && k->y <= 43) { + if (k->mouse == MOUSE_CLICK) { + new_file = (k->y - 13) + top_file; + } else if (k->mouse == MOUSE_SCROLL_UP) { + new_file--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_file++; + } + } else { + return 0; + } + } + + switch (k->sym) { + case SDLK_UP: + new_file--; + break; + case SDLK_DOWN: + new_file++; + break; + case SDLK_PAGEUP: + new_file -= 31; + break; + case SDLK_PAGEDOWN: + new_file += 31; + break; + case SDLK_HOME: + new_file = 0; + break; + case SDLK_END: + new_file = flist.num_files - 1; + break; + case SDLK_RETURN: + if (!k->state) return 1; + if (current_file < flist.num_files) { + handle_file_entered(flist.files[current_file]->path); + } + search_text_clear(); + + return 1; + case SDLK_DELETE: + if (k->state) return 1; + if (flist.num_files > 0) + dialog_create(DIALOG_OK_CANCEL, "Delete file?", do_delete_file, NULL, 1, NULL); + return 1; + case SDLK_BACKSPACE: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) + search_text_clear(); + else + search_text_delete_char(); + return 1; + default: + if (k->mouse == 0) { + if (k->state) return 0; + return search_text_add_char(k->unicode); + } + } + + if (k->mouse == MOUSE_CLICK) { + if (!k->state) return 0; + } else if (k->mouse == MOUSE_DBLCLICK) { + if (current_file < flist.num_files) { + handle_file_entered(flist.files[current_file]->path); + } + search_text_clear(); + return 1; + } else { + if (k->state) return 1; + } + new_file = CLAMP(new_file, 0, flist.num_files - 1); + if (new_file < 0) new_file = 0; + if (new_file != current_file) { + current_file = new_file; + file_list_reposition(); + update_filename_entry(); + status.flags |= NEED_UPDATE; + } + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void dir_list_draw(void) +{ + int n, pos; + + draw_fill_chars(50, 13, 67, 33, 0); + + for (n = top_dir, pos = 13; pos < 34; n++, pos++) { + if (n >= dlist.num_dirs) + break; + if (n == current_dir && ACTIVE_PAGE.selected_widget == 1) + draw_text_len(dlist.dirs[n]->base, 18, 50, pos, 0, 3); + else + draw_text_len(dlist.dirs[n]->base, 18, 50, pos, 5, 0); + } + + /* bleh */ + search_redraw(); +} + +static int dir_list_handle_key(struct key_event * k) +{ + int new_dir = current_dir; + + if (k->mouse) { + if (k->x >= 50 && k->x <= 67 && k->y >= 13 && k->y <= 33) { + if (k->mouse == MOUSE_CLICK) { + new_dir = (k->y - 13) + top_dir; + } else if (k->mouse == MOUSE_DBLCLICK) { + top_file = current_file = 0; + change_dir(dlist.dirs[current_dir]->path); + + if (flist.num_files > 0) + *selected_widget = 0; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->mouse == MOUSE_SCROLL_UP) { + new_dir--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_dir++; + } + } else { + return 0; + } + } + + switch (k->sym) { + case SDLK_UP: + new_dir--; + break; + case SDLK_DOWN: + new_dir++; + break; + case SDLK_PAGEUP: + new_dir -= 21; + break; + case SDLK_PAGEDOWN: + new_dir += 21; + break; + case SDLK_HOME: + new_dir = 0; + break; + case SDLK_END: + new_dir = dlist.num_dirs - 1; + break; + case SDLK_RETURN: + if (!k->state) return 0; + /* reset */ + top_file = current_file = 0; + change_dir(dlist.dirs[current_dir]->path); + + if (flist.num_files > 0) + *selected_widget = 0; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_BACKSPACE: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) + search_text_clear(); + else + search_text_delete_char(); + return 1; + default: + if (k->mouse == 0) { + if (k->state) return 0; + return search_text_add_char(k->unicode); + } + } + + if (k->mouse == MOUSE_CLICK) { + if (!k->state) return 0; + } else { + if (k->state) return 0; + } + new_dir = CLAMP(new_dir, 0, dlist.num_dirs - 1); + if (new_dir != current_dir) { + current_dir = new_dir; + dir_list_reposition(); + status.flags |= NEED_UPDATE; + } + return 1; +} + +/* --------------------------------------------------------------------- */ +/* these handle when enter is pressed on the file/directory textboxes at the bottom of the screen. */ + +static void filename_entered(void) +{ + char *ptr; + + if (filename_entry[0] == '/') { + /* hmm... */ + handle_file_entered(filename_entry); + } else { + ptr = dmoz_path_concat(cfg_dir_modules, filename_entry); + handle_file_entered(ptr); + free(ptr); + } +} + +/* strangely similar to the dir list's code :) */ +static void dirname_entered(void) +{ + if (!change_dir(dirname_entry)) { + /* FIXME: need to give some kind of feedback here */ + return; + } + + *selected_widget = (flist.num_files > 0) ? 0 : 1; + status.flags |= NEED_UPDATE; + /* reset */ + top_file = current_file = 0; +} + +/* --------------------------------------------------------------------- */ + +/* used by {load,save}_module_set_page. return 1 => contents changed */ +static int update_directory(void) +{ + struct stat st; + + /* if we have a list, the directory didn't change, and the mtime is the same, we're set. */ + if ((status.flags & DIR_MODULES_CHANGED) == 0 + && stat(cfg_dir_modules, &st) == 0 + && st.st_mtime == directory_mtime) { + return 0; + } + + change_dir(cfg_dir_modules); + /* TODO: what if it failed? */ + + status.flags &= ~DIR_MODULES_CHANGED; + + return 1; +} + +/* --------------------------------------------------------------------- */ +static int _save_cachefree_hack(struct key_event *k) +{ + if ((k->sym == SDLK_F10 && NO_MODIFIER(k->mod)) + || (k->sym == SDLK_w && (k->mod & KMOD_CTRL)) + || (k->sym == SDLK_s && (k->mod & KMOD_CTRL))) { + status.flags |= DIR_MODULES_CHANGED; + } + return 0; +} +static int _load_cachefree_hack(struct key_event *k) +{ + if ((k->sym == SDLK_F9 && NO_MODIFIER(k->mod)) + || (k->sym == SDLK_l && (k->mod & KMOD_CTRL)) + || (k->sym == SDLK_r && (k->mod & KMOD_CTRL))) { + status.flags |= DIR_MODULES_CHANGED; + } + return 0; +} + +static void load_module_set_page(void) +{ + handle_file_entered = handle_file_entered_L; + if (update_directory()) + pages[PAGE_LOAD_MODULE].selected_widget = (flist.num_files > 0) ? 0 : 1; +} + +void load_module_load_page(struct page *page) +{ + clear_directory(); + top_file = top_dir = 0; + current_file = current_dir = 0; + + page->title = "Load Module (F9)"; + page->draw_const = load_module_draw_const; + page->set_page = load_module_set_page; + page->total_widgets = 4; + page->widgets = widgets_loadmodule; + page->help_index = HELP_GLOBAL; + page->pre_handle_key = _load_cachefree_hack; + + create_other(widgets_loadmodule + 0, 1, file_list_handle_key, file_list_draw); + widgets_loadmodule[0].x = 3; + widgets_loadmodule[0].y = 13; + widgets_loadmodule[0].width = 43; + widgets_loadmodule[0].height = 30; + widgets_loadmodule[0].next.left = widgets_loadmodule[0].next.right = 1; + + create_other(widgets_loadmodule + 1, 2, dir_list_handle_key, dir_list_draw); + widgets_loadmodule[1].x = 50; + widgets_loadmodule[1].y = 13; + widgets_loadmodule[1].width = 17; + widgets_loadmodule[1].height = 20; + + create_textentry(widgets_loadmodule + 2, 13, 46, 64, 0, 3, 3, NULL, filename_entry, NAME_MAX); + widgets_loadmodule[2].activate = filename_entered; + create_textentry(widgets_loadmodule + 3, 13, 47, 64, 2, 3, 0, NULL, dirname_entry, PATH_MAX); + widgets_loadmodule[3].activate = dirname_entered; +} + +/* --------------------------------------------------------------------- */ + +static void save_module_set_page(void) +{ + int i; + + widgets_savemodule[4].d.togglebutton.state = 1; + for (i = 0; diskwriter_drivers[i]; i++) { + widgets_savemodule[5+i].d.togglebutton.state = 0; + } + + handle_file_entered = handle_file_entered_S; + + update_directory(); + /* impulse tracker always resets these; so will i */ + filename_entry[0] = 0; + pages[PAGE_SAVE_MODULE].selected_widget = 2; +} + +void save_module_load_page(struct page *page) +{ + int i, j, k; + + clear_directory(); + top_file = top_dir = 0; + current_file = current_dir = 0; + + page->title = "Save Module (F10)"; + page->draw_const = save_module_draw_const; + page->set_page = save_module_set_page; + page->total_widgets = 5; + page->widgets = widgets_savemodule; + page->help_index = HELP_GLOBAL; + page->selected_widget = 2; + page->pre_handle_key = _save_cachefree_hack; + + create_other(widgets_savemodule + 0, 1, file_list_handle_key, file_list_draw); + widgets_savemodule[0].next.left = 4; + widgets_savemodule[0].next.right = widgets_savemodule[0].next.tab = 1; + create_other(widgets_savemodule + 1, 2, dir_list_handle_key, dir_list_draw); + widgets_savemodule[1].next.right = widgets_savemodule[1].next.tab = 4; + widgets_savemodule[1].next.left = 0; + + create_textentry(widgets_savemodule + 2, 13, 46, 64, 0, 3, 3, NULL, filename_entry, NAME_MAX); + widgets_savemodule[2].activate = filename_entered; + create_textentry(widgets_savemodule + 3, 13, 47, 64, 2, 0, 0, NULL, dirname_entry, PATH_MAX); + widgets_savemodule[3].activate = dirname_entered; + + for (i = 0; diskwriter_drivers[i]; i++); + page->total_widgets += i; + + i = 0; + + j = i + 1; + if (j >= (page->total_widgets-4)) { + j = i; + } + create_togglebutton(&widgets_savemodule[4+i], + 70, 13, 5, + 4+i, 4+j, + 1, 4+i, + 4+j, + NULL, + "Auto", + 1, + filetype_saves); + widgets_savemodule[4].d.togglebutton.state = 1; +/* XXX classic mode shouldn't have an auto */ + i++; + for (; diskwriter_drivers[i-1]; i++) { + k = i - 1; + j = i + 1; + if (j >= (page->total_widgets-4)) { + j = i; + } + create_togglebutton(&widgets_savemodule[4+i], + 70, 13 + (i*3), 5, + 4+k, 4+j, + 1, 4+i, + (j == i) ? 2 : 4+j, + NULL, + diskwriter_drivers[i-1]->name, + 4 - ((strlen(diskwriter_drivers[i-1]->name)+1) / 2), + filetype_saves); + } + +} diff --git a/schism/page_loadsample.c b/schism/page_loadsample.c new file mode 100644 index 000000000..ac1d0aa5e --- /dev/null +++ b/schism/page_loadsample.c @@ -0,0 +1,686 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "dmoz.h" + +#include +#include + +#include +#include +#include +#include + +/* --------------------------------------------------------------------------------------------------------- */ +/* the locals */ + +static int _library_mode = 0; +static struct widget widgets_loadsample[1]; +static int fake_slot = -1; +static int need_trigger = -1; +static int need_keyoff = -1; + +/* --------------------------------------------------------------------------------------------------------- */ + +/* files: + file type color displayed title notes + --------- ----- --------------- ----- + unchecked 4 IT uses color 6 for these + directory 5 "........Directory........" dots are char 154 (same for libraries) + sample 3 + libraries 6 ".........Library........." IT uses color 3. maybe use module name here? + unknown 2 any regular file that's not recognized +*/ + +static int top_file = 0; +static int current_file = 0; +static time_t directory_mtime; +static dmoz_filelist_t flist; + +static int slash_search_mode = -1; +static char slash_search_str[PATH_MAX]; + +/* get a color index from a dmoz_file_t 'type' field */ +static inline int get_type_color(int type) +{ + if (type == TYPE_DIRECTORY) + return 5; + if (!(type & TYPE_EXT_DATA_MASK)) + return 4; /* unchecked */ + if (type & TYPE_BROWSABLE_MASK) + return 6; /* library */ + if (type == TYPE_UNKNOWN) + return 2; + return 3; /* sample */ +} + +static int sampgrep(dmoz_file_t *f) +{ + if ((f->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(f); + return 1; +} + +static void clear_directory(void) +{ + dmoz_free(&flist, NULL); + top_file = current_file = 0; + fake_slot = -1; +} +static void file_list_reposition(void) +{ + if (current_file < top_file) + top_file = current_file; + else if (current_file > top_file + 34) + top_file = current_file - 34; +} + + +static void read_directory(void) +{ + struct stat st; + char *tmp; + int i; + + if (current_file >= 0 && current_file < flist.num_files + && flist.files[current_file] && flist.files[current_file]->base) { + tmp = strdup(flist.files[current_file]->base); + } else { + tmp = 0; + } + + clear_directory(); + + if (stat(cfg_dir_samples, &st) < 0) + directory_mtime = 0; + else + directory_mtime = st.st_mtime; + /* if the stat call failed, this will probably break as well, but + at the very least, it'll add an entry for the root directory. */ + if (dmoz_read(cfg_dir_samples, &flist, NULL) < 0) + perror(cfg_dir_samples); + + dmoz_filter_filelist(&flist, sampgrep, ¤t_file, + file_list_reposition); + + if (tmp) { + for (i = 0; i < flist.num_files; i++) { + if (flist.files && flist.files[i] && flist.files[i]->base + && strcmp(tmp, flist.files[i]->base) == 0) { + current_file = i; + break; + } + } + (void)free(tmp); + } +} + +/* return: 1 = success, 0 = failure +TODO: provide some sort of feedback if something went wrong. */ +static int change_dir(const char *dir) +{ + char *ptr = dmoz_path_normal(dir); + + if (!ptr) + return 0; + + /* FIXME: need to make sure it exists, and that it's a directory */ + strncpy(cfg_dir_samples, ptr, PATH_MAX); + cfg_dir_samples[PATH_MAX] = 0; + free(ptr); + + read_directory(); + top_file = current_file = 0; + fake_slot = -1; + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void load_sample_draw_const(void) +{ + dmoz_file_t *f; + char sbuf[64]; + int filled; + + draw_box(5, 12, 50, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(6, 13, 49, 47, 0); + + draw_box(63, 12, 77, 23, BOX_THICK | BOX_INNER | BOX_INSET); + filled = 0; + f = 0; + if (current_file >= 0 + && current_file < flist.num_files && flist.files[current_file]) { + f = flist.files[current_file]; + draw_text_len(f->smp_filename ? f->smp_filename : "", 13, 64,13, 2, 0); + + sprintf(sbuf, "%07d", f->smp_speed); + draw_text_len(sbuf, 13, 64, 14, 2, 0); + + sprintf(sbuf, "%07d", f->smp_loop_start); + draw_text_len(sbuf, 13, 64, 16, 2, 0); + sprintf(sbuf, "%07d", f->smp_loop_end); + draw_text_len(sbuf, 13, 64, 17, 2, 0); + + sprintf(sbuf, "%07d", f->smp_sustain_start); + draw_text_len(sbuf, 13, 64, 19, 2, 0); + sprintf(sbuf, "%07d", f->smp_sustain_end); + draw_text_len(sbuf, 13, 64, 20, 2, 0); + + sprintf(sbuf, "%07d", f->smp_length); + draw_text_len(sbuf, 13, 64, 22, 2, 0); + + if (!f->smp_length && !f->smp_filename && !f->smp_flags) { + draw_text_len("No sample",13, 64, 21, 2, 0); + } else if (f->smp_flags & SAMP_STEREO) { + draw_text_len( + (f->smp_flags & SAMP_16_BIT + ? "16 bits Stereo" : "8 bits Stereo"), + 13, 64, 21, 2, 0); + } else { + draw_text_len( + (f->smp_flags & SAMP_16_BIT + ? "16 bits" : "8 bits"), + 13, 64, 21, 2, 0); + } + + draw_text_len( + (f->smp_flags & SAMP_SUSLOOP + ? "On" : "Off"), + 13, 64, 18, 2, 0); + + if (f->smp_flags & SAMP_LOOP_PINGPONG) { + draw_text_len("On Ping Pong", + 13, 64, 15, 2, 0); + } else if (f->smp_flags & SAMP_LOOP) { + draw_text_len("On Forwards", + 13, 64, 15, 2, 0); + } else { + draw_text_len("Off", + 13, 64, 15, 2, 0); + } + } + + /* these are exactly the same as in page_samples.c, apart from + * 'quality' and 'length' being one line higher */ + draw_text("Filename", 55, 13, 0, 2); + draw_text("Speed", 58, 14, 0, 2); + draw_text("Loop", 59, 15, 0, 2); + draw_text("LoopBeg", 56, 16, 0, 2); + draw_text("LoopEnd", 56, 17, 0, 2); + draw_text("SusLoop", 56, 18, 0, 2); + draw_text("SusLBeg", 56, 19, 0, 2); + draw_text("SusLEnd", 56, 20, 0, 2); + draw_text("Quality", 56, 21, 0, 2); + draw_text("Length", 57, 22, 0, 2); + + draw_box(51, 24, 77, 29, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(52, 25, 76, 28, 0); + + draw_box(51, 30, 77, 42, BOX_THIN | BOX_INNER | BOX_INSET); + + /* these abbreviations are sucky and lame. any suggestions? */ + draw_text("Def. Vol.", 53, 33, 0, 2); + draw_text("Glb. Vol.", 53, 34, 0, 2); + draw_text("Vib.Speed", 53, 37, 0, 2); + draw_text("Vib.Depth", 53, 38, 0, 2); + draw_text("Vib. Rate", 53, 39, 0, 2); + + draw_box(52, 43, 77, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(53, 44, 76, 47, 0); + + + if (need_trigger > -1) { + if (fake_slot > -1) { + if (need_keyoff > -1) song_keyup(fake_slot, -1, need_keyoff, -1, 0); + song_keydown(fake_slot, -1, need_keyoff = need_trigger, 64, -1, 0); + } + need_trigger = -1; + } +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void _common_set_page(void) +{ + struct stat st; + + /* if we have a list, the directory didn't change, and the mtime is the same, we're set */ + if (flist.num_files > 0 + && (status.flags & DIR_SAMPLES_CHANGED) == 0 + && stat(cfg_dir_samples, &st) == 0 + && st.st_mtime == directory_mtime) { + return; + } + + change_dir(cfg_dir_samples); + + status.flags &= ~DIR_SAMPLES_CHANGED; + fake_slot = -1; + + *selected_widget = 0; + slash_search_mode = -1; +} + +static void load_sample_set_page(void) +{ + _library_mode = 0; + _common_set_page(); +} +static void library_sample_set_page(void) +{ + _library_mode = 1; + _common_set_page(); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void file_list_draw(void) +{ + int n, i, pos, fg, bg; + char buf[8]; + dmoz_file_t *file; + + /* there's no need to have if (files) { ... } like in the load-module page, + because there will always be at least "/" in the list */ + if (top_file < 0) top_file = 0; + if (current_file < 0) current_file = 0; + for (n = top_file, pos = 13; n < flist.num_files && pos < 48; n++, pos++) { + file = flist.files[n]; + if ((file->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(file); + + if (n == current_file && ACTIVE_PAGE.selected_widget == 0) { + fg = 0; + bg = 3; + } else { + fg = get_type_color(file->type); + bg = 0; + } + draw_text(numtostr(3, n+1, buf), 2, pos, 0, 2); + draw_text_len(file->title ? file->title : "", + 25, 6, pos, fg, bg); + draw_char(168, 31, pos, 2, bg); + draw_text_len(file->base ? file->base : "", + 18, 32, pos, fg, bg); + if (file->base && slash_search_mode > -1) { + if (strncasecmp(file->base,slash_search_str,slash_search_mode) == 0) { + for (i = 0 ; i < slash_search_mode; i++) { + if (tolower(((unsigned)file->base[i])) + != tolower(((unsigned)slash_search_str[i]))) break; + draw_char(file->base[i], 32+i, pos, 3,1); + } + } + } + } + + /* draw the info for the current file (or directory...) */ + + while (pos < 48) + draw_char(168, 31, pos++, 2, 0); +} + +static void do_create_host(UNUSED void *gn) +{ + int cur = sample_get_current(); + if (song_instrument_is_empty(cur)) + song_init_instruments(cur); + set_page(PAGE_SAMPLE_LIST); +} +static void dont_create_host(UNUSED void *gn) +{ + set_page(PAGE_SAMPLE_LIST); +} + +static void finish_load(int cur); +static void stereo_cvt_complete_left(void) +{ + int cur = sample_get_current(); + song_sample *smp; + smp = song_get_sample(cur, NULL); + sample_mono_left(smp); + dialog_destroy(); + finish_load(cur); +} +static void stereo_cvt_complete_right(void) +{ + int cur = sample_get_current(); + song_sample *smp; + smp = song_get_sample(cur, NULL); + sample_mono_right(smp); + dialog_destroy(); + finish_load(cur); +} +static void stereo_cvt_complete_both(void) +{ + int cur = sample_get_current(); + dialog_destroy(); + memused_songchanged(); + if (song_instrument_is_empty(cur) + && song_is_instrument_mode()) { + dialog_create(DIALOG_YES_NO, "Create host instrument?", + do_create_host, dont_create_host, 0, NULL); + } else { + set_page(PAGE_SAMPLE_LIST); + } +} +static void stereo_cvt_dialog(void) +{ + draw_text("Loading Stereo Sample", 30, 27, 0, 2); +} + +static void finish_load(int cur) +{ + song_sample *smp; + + memused_songchanged(); + smp = song_get_sample(cur, NULL); + if (smp->flags & SAMP_STEREO) { +/* Loading Stereo Sample +Left Both Right +*/ + static struct widget stereo_cvt_widgets[3]; + struct dialog *dd; + create_button(stereo_cvt_widgets+0, 27, 30, 6, + 0, 0, 0, (status.flags & CLASSIC_MODE) ? 2 : 1, + (status.flags & CLASSIC_MODE) ? 2 : 1, + stereo_cvt_complete_left, "Left", 2); + + create_button(stereo_cvt_widgets+1, 37, 30, 6, + 1, 1, 0, 2, 2, + stereo_cvt_complete_both, "Both", 2); + + create_button(stereo_cvt_widgets+2, 47, 30, 6, + 1, 1, (status.flags & CLASSIC_MODE) ? 0 : 1, 2, 0, + stereo_cvt_complete_right, "Right", 1); + + dd = dialog_create_custom(24, 25, 33, 8, + stereo_cvt_widgets, + (status.flags & CLASSIC_MODE) ? 2 : 3, + (status.flags & CLASSIC_MODE) ? 1 : 0, + stereo_cvt_dialog, + NULL); + dd->action_cancel = stereo_cvt_complete_both; + return; + } + + + if (song_instrument_is_empty(cur) + && song_is_instrument_mode()) { + dialog_create(DIALOG_YES_NO, "Create host instrument?", + do_create_host, dont_create_host, 0, NULL); + } else { + set_page(PAGE_SAMPLE_LIST); + } +} + +static void reposition_at_slash_search(void) +{ + dmoz_file_t *f; + int i, j, b, bl; + + if (slash_search_mode < 0) return; + bl = b = -1; + for (i = 0; i < flist.num_files; i++) { + f = flist.files[i]; + if (!f || !f->base) continue; + for (j = 0; j < slash_search_mode; j++) { + if (tolower(((unsigned)f->base[j])) + != tolower(((unsigned)slash_search_str[j]))) break; + } + if (bl < j) { + bl = j; + b = i; + } + } + if (bl > -1) { + current_file = b; + file_list_reposition(); + } +} + +/* on the file list, that is */ +static void handle_enter_key(void) +{ + dmoz_file_t *file; + song_sample *smp; + int cur = sample_get_current(); + + if (current_file < 0 || current_file >= flist.num_files) return; + + file = flist.files[current_file]; + + if (file->type & (TYPE_BROWSABLE_MASK|TYPE_INST_MASK)) { + change_dir(file->path); + status.flags |= NEED_UPDATE; + } else if (file->sample) { + if (_library_mode) return; + /* it's already been loaded, so copy it */ + smp = song_get_sample(cur, NULL); + song_copy_sample(cur, file->sample, file->title); + strncpy(smp->filename, file->base, 12); + smp->filename[12] = 0; + finish_load(cur); + memused_songchanged(); + } else if (file->type & TYPE_SAMPLE_MASK) { + if (_library_mode) return; + /* load the sample */ + song_load_sample(cur, file->path); + finish_load(cur); + memused_songchanged(); + } + +} + +static int file_list_handle_key(struct key_event * k) +{ + int new_file = current_file; + + new_file = CLAMP(new_file, 0, flist.num_files - 1); + + if (!(status.flags & CLASSIC_MODE) && k->sym == SDLK_n && (k->mod & KMOD_ALT)) { + song_toggle_multichannel_mode(); + return 1; + } + + if (k->mouse) { + if (k->x >= 6 && k->x <= 49 && k->y >= 13 && k->y <= 47) { + if (k->mouse == MOUSE_SCROLL_UP) { + new_file--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_file++; + } else { + new_file = top_file + (k->y - 13); + } + } + } else if (slash_search_mode > -1) { + int c = unicode_to_ascii(k->unicode); + if (k->sym == SDLK_RETURN || k->sym == SDLK_ESCAPE) { + if (!k->state) return 1; + slash_search_mode = -1; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->sym == SDLK_BACKSPACE) { + if (k->state) return 1; + slash_search_mode--; + status.flags |= NEED_UPDATE; + reposition_at_slash_search(); + return 1; + } else if (c >= 32) { + if (k->state) return 1; + if (slash_search_mode < PATH_MAX) { + slash_search_str[ slash_search_mode ] = c; + slash_search_mode++; + reposition_at_slash_search(); + status.flags |= NEED_UPDATE; + } + return 1; + } + } + switch (k->sym) { + case SDLK_UP: + new_file--; + break; + case SDLK_DOWN: + new_file++; + break; + case SDLK_PAGEUP: + new_file -= 35; + break; + case SDLK_PAGEDOWN: + new_file += 35; + break; + case SDLK_HOME: + new_file = 0; + break; + case SDLK_END: + new_file = flist.num_files - 1; + break; + case SDLK_RETURN: + if (!k->state) return 0; + handle_enter_key(); + return 1; + case SDLK_ESCAPE: + if (k->state && NO_MODIFIER(k->mod)) + set_page(PAGE_SAMPLE_LIST); + return 1; + case SDLK_SLASH: + if (k->orig_sym == SDLK_SLASH) { + if (status.flags & CLASSIC_MODE) return 0; + if (!k->state) return 0; + slash_search_mode = 0; + status.flags |= NEED_UPDATE; + return 1; + } + default: + if (!k->mouse) return 0; + } + + if (k->mouse == MOUSE_CLICK) { + if (!k->state) return 0; + } else if (k->mouse == MOUSE_DBLCLICK) { + handle_enter_key(); + return 1; + } else { + if (k->state) return 0; + } + new_file = CLAMP(new_file, 0, flist.num_files - 1); + if (new_file < 0) new_file = 0; + if (new_file != current_file) { + fake_slot = -1; + slash_search_mode = -1; + current_file = new_file; + file_list_reposition(); + status.flags |= NEED_UPDATE; + } + return 1; +} + +static void load_sample_handle_key(struct key_event * k) +{ + int n, v; + + if (!k->state && k->sym == SDLK_ESCAPE && NO_MODIFIER(k->mod)) { + set_page(PAGE_SAMPLE_LIST); + return; + } + if (!NO_MODIFIER(k->mod)) return; + + if (k->midi_note > -1) { + n = k->midi_note; + if (k->midi_volume > -1) { + v = k->midi_volume / 2; + } else { + v = 64; + } + } else { + n = kbd_get_note(k); + v = 64; + if (n <= 0 || n > 120) + return; + } + + if (fake_slot < 0 && current_file >= 0 && current_file < flist.num_files) { + dmoz_file_t *file = flist.files[current_file]; + if (file) { + fake_slot = song_preload_sample(file); + } + } + + if (fake_slot > -1) { + if ((status.flags & CLASSIC_MODE) || !song_is_multichannel_mode()) { + if (!k->state && !k->is_repeat) { + need_trigger = n; + status.flags |= NEED_UPDATE; + } + } else { + if (need_keyoff > -1) { + song_keyup(fake_slot, -1, need_keyoff, -1, 0); + need_keyoff = -1; + } + if (k->state) { + song_keyup(fake_slot, -1, n, -1, 0); + status.last_keysym = 0; + } else if (!k->is_repeat) { + song_keydown(fake_slot, -1, n, v, -1, 0); + } + need_trigger = -1; + } + } +} + +/* --------------------------------------------------------------------------------------------------------- */ + +void load_sample_load_page(struct page *page) +{ + clear_directory(); + + page->title = "Load Sample"; + page->draw_const = load_sample_draw_const; + page->set_page = load_sample_set_page; + page->handle_key = load_sample_handle_key; + page->total_widgets = 1; + page->widgets = widgets_loadsample; + page->help_index = HELP_GLOBAL; + + create_other(widgets_loadsample + 0, 0, file_list_handle_key, file_list_draw); +} + +void library_sample_load_page(struct page *page) +{ + clear_directory(); + + page->title = "Sample Library (Ctrl-F3)"; + page->draw_const = load_sample_draw_const; + page->set_page = library_sample_set_page; + page->handle_key = load_sample_handle_key; + page->total_widgets = 1; + page->widgets = widgets_loadsample; + page->help_index = HELP_GLOBAL; + + create_other(widgets_loadsample + 0, 0, file_list_handle_key, file_list_draw); +} diff --git a/schism/page_log.c b/schism/page_log.c new file mode 100644 index 000000000..22b5928e2 --- /dev/null +++ b/schism/page_log.c @@ -0,0 +1,104 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* It's lo-og, lo-og, it's big, it's heavy, it's wood! + * It's lo-og, lo-og, it's better than bad, it's good! */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_log[1]; + +#define NUM_LINES 33 +static struct log_line lines[NUM_LINES]; +static int last_line = -1; + +/* --------------------------------------------------------------------- */ + +static void log_draw_const(void) +{ + draw_box(1, 12, 78, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(2, 13, 77, 47, 0); +} + +static int log_handle_key(UNUSED struct key_event * k) +{ + return 0; +} + +static void log_redraw(void) +{ + int n; + + for (n = 0; n <= last_line; n++) + draw_text_len(lines[n].text, 74, 3, 14 + n, + lines[n].color, 0); +} + +/* --------------------------------------------------------------------- */ + +void log_load_page(struct page *page) +{ + page->title = "Message Log Viewer (Ctrl-F11)"; + page->draw_const = log_draw_const; + page->total_widgets = 1; + page->widgets = widgets_log; + page->help_index = HELP_GLOBAL; + + create_other(widgets_log + 0, 1, log_handle_key, log_redraw); +} + +/* --------------------------------------------------------------------- */ + +inline void log_append(int color, int must_free, const char *text) +{ + if (last_line < NUM_LINES - 1) { + last_line++; + } else { + if (lines[0].must_free) + free((void *) lines[0].text); + memmove(lines, lines + 1, last_line * sizeof(struct log_line)); + } + lines[last_line].text = text; + lines[last_line].color = color; + lines[last_line].must_free = must_free; + + if (status.current_page == PAGE_LOG) + status.flags |= NEED_UPDATE; +} + +void log_appendf(int color, const char *format, ...) +{ + char *ptr; + va_list ap; + + va_start(ap, format); + vasprintf(&ptr, format, ap); + va_end(ap); + + log_append(color, 1, ptr); +} diff --git a/schism/page_message.c b/schism/page_message.c new file mode 100644 index 000000000..4b30e847f --- /dev/null +++ b/schism/page_message.c @@ -0,0 +1,807 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* --->> WARNING <<--- + * + * This is an excellent example of how NOT to write a text editor. + * IMHO, the best way to add a song message is by writing it in some + * other program and attaching it to the song with something like + * ZaStaR's ITTXT utility (hmm, maybe I should rewrite that, too ^_^) so + * I'm not *really* concerned about the fact that this code completely + * sucks. Just remember, this ain't VI. */ + +#include "headers.h" + +#include "song.h" +#include "clippy.h" + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_message[1]; + +static int top_line = 0; +static int cursor_line = 0; +static int cursor_char = 0; +/* this is the absolute cursor position from top of message. + * (should be updated whenever cursor_line/cursor_char change) */ +static int cursor_pos = 0; + +char *message = NULL; +int edit_mode = 0; + +/* nonzero => message should use the alternate font */ +int message_extfont = 1; + +/* This is a bit weird... Impulse Tracker usually wraps at 74, but if + * the line doesn't have any spaces in it (like in a solid line of + * dashes or something) it gets wrapped at 75. I'm using 75 for + * everything because it's always nice to have a bit extra space :) */ +#define LINE_WRAP 75 + +/* --------------------------------------------------------------------- */ + +static int message_handle_key_editmode(struct key_event * k); +static int message_handle_key_viewmode(struct key_event * k); + +/* --------------------------------------------------------------------- */ + +/* returns the number of characters on the nth line of text, setting ptr + * to the first character on the line. if it there are fewer than n + * lines, ptr is set to the \0 at the end of the string, and the + * function returns -1. note: if *ptr == text, weird things will + * probably happen, so don't do that. */ +static int get_nth_line(const char *text, int n, const char **ptr) +{ + const char *tmp; + + if (!text) { + *ptr = NULL; + return 0; + } + + *ptr = text; + while (n > 0) { + n--; + *ptr = strpbrk(*ptr, "\xd\xa"); + if (!(*ptr)) { + *ptr = text + strlen(text); + return -1; + } + if ((*ptr)[0] == 13 && (*ptr)[1] == 10) + *ptr += 2; + else + (*ptr)++; + } + + tmp = strpbrk(*ptr, "\xd\xa"); + return (tmp ? (unsigned) (tmp - *ptr) : strlen(*ptr)); +} +static void set_absolute_position(const char *text, int pos, int *line, int *ch) +{ + int len; + const char *ptr; + + *line = *ch = 0; + ptr = 0; + while (pos > 0) { + len = get_nth_line(text, *line, &ptr); + if (len < 0) { + /* end of file */ + (*line) = (*line) - 1; + if (*line < 0) *line = 0; + len = get_nth_line(text, *line, &ptr); + if (len < 0) { + *ch = 0; + } else { + *ch = len; + } + pos = 0; + + } else if (len >= pos) { + *ch = pos; + pos = 0; + } else { + pos -= (len+1); /* EOC */ + (*line) = (*line) + 1; + } + } +} +static int get_absolute_position(const char *text, int line, int character) +{ + int len; + const char *ptr; + + ptr = 0; + len = get_nth_line(text, line, &ptr); + if (len < 0) { + return 0; + } + /* hrm... what if cursor_char > len? */ + return (ptr - text) + character; +} + +/* --------------------------------------------------------------------- */ + +static void message_reposition(void) +{ + if (cursor_line < top_line) { + top_line = cursor_line; + } else if (cursor_line > top_line + 34) { + top_line = cursor_line - 34; + } +} + +/* --------------------------------------------------------------------- */ + +/* returns 1 if a character was actually added */ +static int message_add_char(char newchar, int position) +{ + int len = strlen(message); + + if (len == 8000) { + dialog_create(DIALOG_OK, " Song message too long! ", NULL, NULL, 0, NULL); + return 0; + } + if (position < 0 || position > len) { + log_appendf(4, "message_add_char: position=%d, len=%d - shouldn't happen!", position, len); + return 0; + } + + memmove(message + position + 1, message + position, len - position); + message[len + 1] = 0; + message[position] = newchar; + return 1; +} + +/* this returns the new length of the line */ +static int message_wrap_line(char *bol_ptr) +{ + char *eol_ptr; + char *last_space = NULL; + char *tmp = bol_ptr; + + if (!bol_ptr) + /* shouldn't happen, but... */ + return 0; + + eol_ptr = strpbrk(bol_ptr, "\xd\xa"); + if (!eol_ptr) + eol_ptr = bol_ptr + strlen(bol_ptr); + + for (;;) { + tmp = strpbrk(tmp + 1, " \t"); + if (tmp == NULL || tmp > eol_ptr + || tmp - bol_ptr > LINE_WRAP) + break; + last_space = tmp; + } + + if (last_space) { + *last_space = 13; + return last_space - bol_ptr; + } else { + /* what, no spaces to cut at? chop it mercilessly. */ + if (message_add_char(13, bol_ptr + LINE_WRAP - message) + == 0) + /* ack, the message is too long to wrap the line! + * gonna have to resort to something ugly. */ + bol_ptr[LINE_WRAP] = 13; + return LINE_WRAP; + } +} + +/* --------------------------------------------------------------------- */ + +static void message_draw(void) +{ + const char *line, *prevline = message; + int fg = (message_extfont ? 12 : 6); + int len = get_nth_line(message, top_line, &line); + int n, cp, clipl, clipr; + int skipc, cutc; + + draw_fill_chars(2, 13, 77, 47, 0); + + if (message_extfont) + font_set_bank(1); + if (clippy_owner(CLIPPY_SELECT) == widgets_message) { + clipl = widgets_message[0].clip_start; + clipr = widgets_message[0].clip_end; + if (clipl > clipr) { + cp = clipl; + clipl = clipr; + clipr = cp; + } + } else { + clipl = clipr = -1; + } + + for (n = 0; n < 35; n++) { + if (len < 0) { + break; + } else if (len > 0) { + /* FIXME | shouldn't need this check here, + * FIXME | because the line should already be + * FIXME | short enough to fit */ + if (len > LINE_WRAP) + len = LINE_WRAP; + draw_text_len(line, len, 2, 13 + n, fg, 0); + + if (clipl > -1) { + cp = line - message; + skipc = clipl - cp; + cutc = clipr - clipl; + if (skipc < 0) { + cutc += skipc; /* ... -skipc */ + skipc = 0; + } + if (cutc < 0) cutc = 0; + if (cutc > (len-skipc)) cutc = (len-skipc); + if (cutc > 0 && skipc < len) { + draw_text_len(line+skipc, cutc, 2+skipc, 13 + n, fg, 8); + } + } + } + if (edit_mode) { + font_set_bank(0); + draw_char(20, 2 + len, 13 + n, 1, 0); + if (message_extfont) + font_set_bank(1); + } + prevline = line; + len = get_nth_line(prevline, 1, &line); + } + + if (edit_mode && len < 0) { + /* end of the message */ + len = get_nth_line(prevline, 0, &line); + font_set_bank(0); + /* FIXME: see above */ + if (len > LINE_WRAP) + len = LINE_WRAP; + draw_char(20, 2 + len, 13 + n - 1, 2, 0); + if (message_extfont) + font_set_bank(1); + } + + if (edit_mode) { + /* draw the cursor */ + len = get_nth_line(message, cursor_line, &line); + + /* FIXME: ... ugh */ + if (len > LINE_WRAP) + len = LINE_WRAP; + if (cursor_char > LINE_WRAP + 1) + cursor_char = LINE_WRAP + 1; + + if (cursor_char >= len) { + font_set_bank(0); + draw_char(20, 2 + cursor_char, + 13 + (cursor_line - top_line), 0, 3); + } else { + draw_char(line[cursor_char], 2 + cursor_char, + 13 + (cursor_line - top_line), 8, 3); + font_set_bank(0); + } + } else { + font_set_bank(0); + } +} + +/* --------------------------------------------------------------------- */ + +static inline void message_set_editmode(void) +{ + edit_mode = 1; + top_line = cursor_line = cursor_char = cursor_pos = 0; + widgets_message[0].d.other.handle_key = message_handle_key_editmode; + + status.flags |= NEED_UPDATE; +} + +static inline void message_set_viewmode(void) +{ + edit_mode = 0; + widgets_message[0].d.other.handle_key = message_handle_key_viewmode; + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void message_insert_char(Uint16 unicode) +{ + const char *ptr; + char c = unicode_to_ascii(unicode); + int n; + + if (!edit_mode) + return; + + memused_songchanged(); + if (c == '\t') { + /* Find the number of characters until the next tab stop. + * (This is new behaviour; Impulse Tracker just inserts + * eight characters regardless of the cursor position.) */ + n = 8 - cursor_char % 8; + if (cursor_char + n > LINE_WRAP) { + message_insert_char('\r'); + } else { + do { + if (!message_add_char(' ', cursor_pos)) + break; + cursor_char++; + cursor_pos++; + n--; + } while (n); + } + } else if (c < 32 && c != '\r') { + return; + } else { + if (!message_add_char(c, cursor_pos)) + return; + cursor_pos++; + if (c == '\r') { + cursor_char = 0; + cursor_line++; + } else { + cursor_char++; + } + } + if (get_nth_line(message, cursor_line, &ptr) >= LINE_WRAP) { + message_wrap_line((char *) ptr); + } + if (cursor_char >= LINE_WRAP) { + cursor_char = get_nth_line(message, ++cursor_line, &ptr); + cursor_pos = + get_absolute_position(message, cursor_line, + cursor_char); + } + + message_reposition(); + status.flags |= NEED_UPDATE; +} + +static void message_delete_char(void) +{ + int len = strlen(message); + const char *ptr; + + if (cursor_pos == 0) + return; + memmove(message + cursor_pos - 1, message + cursor_pos, + len - cursor_pos + 1); + message[8000] = 0; + cursor_pos--; + if (cursor_char == 0) { + cursor_line--; + cursor_char = get_nth_line(message, cursor_line, &ptr); + } else { + cursor_char--; + } + + message_reposition(); + status.flags |= NEED_UPDATE; +} + +static void message_delete_next_char(void) +{ + int len = strlen(message); + + if (cursor_pos == len) + return; + memmove(message + cursor_pos, message + cursor_pos + 1, + len - cursor_pos); + message[8000] = 0; + + status.flags |= NEED_UPDATE; +} + +static void message_delete_line(void) +{ + int len; + int movelen; + const char *ptr; + + len = get_nth_line(message, cursor_line, &ptr); + if (len < 0) + return; + if (ptr[len] == 13 && ptr[len + 1] == 10) + len++; + movelen = (message + strlen(message) - ptr); + if (movelen == 0) + return; + memmove((void *) ptr, ptr + len + 1, movelen); + len = get_nth_line(message, cursor_line, &ptr); + if (cursor_char > len) { + cursor_char = len; + cursor_pos = + get_absolute_position(message, cursor_line, + cursor_char); + } + message_reposition(); + status.flags |= NEED_UPDATE; +} + +static void message_clear(UNUSED void *data) +{ + message[0] = 0; + memused_songchanged(); + message_set_viewmode(); +} + +/* --------------------------------------------------------------------- */ + +static void prompt_message_clear(void) +{ + dialog_create(DIALOG_OK_CANCEL, "Clear song message?", message_clear, NULL, 1, NULL); +} + +/* --------------------------------------------------------------------- */ + +static int message_handle_key_viewmode(struct key_event * k) +{ + if (!k->state) { + if (k->mouse == MOUSE_SCROLL_UP) { + top_line--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + top_line++; + } else if (k->mouse == MOUSE_CLICK) { + message_set_editmode(); + return message_handle_key_editmode(k); + } + } + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + top_line--; + break; + case SDLK_DOWN: + if (k->state) return 0; + top_line++; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + top_line -= 35; + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + top_line += 35; + break; + case SDLK_HOME: + if (k->state) return 0; + top_line = 0; + break; + case SDLK_END: + if (k->state) return 0; + top_line = get_num_lines(message) - 34; + break; + case SDLK_t: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + message_extfont = !message_extfont; + break; + } + return 1; + case SDLK_RETURN: + if (!k->state) return 0; + message_set_editmode(); + return 1; + default: + return 0; + } + + if (top_line < 0) + top_line = 0; + + status.flags |= NEED_UPDATE; + + return 1; +} +static void _delete_selection(void) +{ + int len = strlen(message); + int eat; + + cursor_pos = widgets_message[0].clip_start; + if (cursor_pos > widgets_message[0].clip_end) { + cursor_pos = widgets_message[0].clip_end; + eat = widgets_message[0].clip_start - cursor_pos; + } else { + eat = widgets_message[0].clip_end - cursor_pos; + } + clippy_select(0,0,0); + if (cursor_pos == len) + return; + memmove(message + cursor_pos, message + cursor_pos + eat + 1, + ((len - cursor_pos) - eat)+1); + message[8000] = 0; + set_absolute_position(message, cursor_pos, &cursor_line, &cursor_char); + message_reposition(); + + status.flags |= NEED_UPDATE; +} + +static int message_handle_key_editmode(struct key_event * k) +{ + int line_len, num_lines = -1; + int new_cursor_line = cursor_line; + int new_cursor_char = cursor_char; + const char *ptr; + int doing_drag = 0; + int clipl, clipr, cp; + + if (k->mouse == MOUSE_SCROLL_UP) { + if (k->state) return 0; + new_cursor_line--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + if (k->state) return 0; + new_cursor_line++; + } else if (k->mouse == MOUSE_CLICK && k->mouse_button == 2) { + if (k->state) status.flags |= CLIPPY_PASTE_SELECTION; + return 1; + } else if (k->mouse == MOUSE_CLICK) { + if (k->x >= 2 && k->x <= 77 && k->y >= 13 && k->y <= 47) { + new_cursor_line = (k->y - 13) + top_line; + new_cursor_char = (k->x - 2); + if (k->sx != k->x || k->sy != k->y) { + /* yay drag operation */ + cp = get_absolute_position(message, (k->sy-13)+top_line, + (k->sx-2)); + widgets_message[0].clip_start = cp; + doing_drag = 1; + } + } + } + + line_len = get_nth_line(message, cursor_line, &ptr); + + + switch (k->sym) { + case SDLK_UP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_line--; + break; + case SDLK_DOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_line++; + break; + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_char--; + break; + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_char++; + break; + case SDLK_PAGEUP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_line -= 35; + break; + case SDLK_PAGEDOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_line += 35; + break; + case SDLK_HOME: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) + new_cursor_line = 0; + else + new_cursor_char = 0; + break; + case SDLK_END: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) { + num_lines = get_num_lines(message); + new_cursor_line = num_lines; + } else { + new_cursor_char = line_len; + } + break; + case SDLK_ESCAPE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + message_set_viewmode(); + memused_songchanged(); + return 1; + case SDLK_BACKSPACE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) { + _delete_selection(); + } else { + message_delete_char(); + } + return 1; + case SDLK_DELETE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) { + _delete_selection(); + } else { + message_delete_next_char(); + } + return 1; + default: + if (k->mod & KMOD_CTRL) { + if (k->state) return 1; + if (k->sym == SDLK_t) { + message_extfont = !message_extfont; + break; + } else if (k->sym == SDLK_y) { + clippy_select(0,0,0); + message_delete_line(); + break; + } + } else if (k->mod & KMOD_ALT) { + if (k->state) return 1; + if (k->sym == SDLK_c) { + prompt_message_clear(); + return 1; + } + } else if (!k->mouse) { + if (k->unicode == '\r' || k->unicode == '\t' + || k->unicode >= 32) { + if (k->state) return 1; + if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) { + _delete_selection(); + } + message_insert_char(k->unicode); + return 1; + } + return 0; + } + + if (k->mouse != MOUSE_CLICK) + return 0; + + if (k->state) return 1; + } + + if (new_cursor_line != cursor_line) { + if (num_lines == -1) + num_lines = get_num_lines(message); + + if (new_cursor_line < 0) + new_cursor_line = 0; + else if (new_cursor_line > num_lines) + new_cursor_line = num_lines; + + /* make sure the cursor doesn't go past the new eol */ + line_len = get_nth_line(message, new_cursor_line, &ptr); + if (new_cursor_char > line_len) + new_cursor_char = line_len; + + cursor_char = new_cursor_char; + cursor_line = new_cursor_line; + } else if (new_cursor_char != cursor_char) { + /* we say "else" here ESPECIALLY because the mouse can only come + in the top section - not because it's some clever optimization */ + if (new_cursor_char < 0) { + if (cursor_line == 0) { + new_cursor_char = cursor_char; + } else { + cursor_line--; + new_cursor_char = + get_nth_line(message, cursor_line, &ptr); + } + + } else if (new_cursor_char > + get_nth_line(message, cursor_line, &ptr)) { + if (cursor_line == get_num_lines(message)) { + new_cursor_char = cursor_char; + } else { + cursor_line++; + new_cursor_char = 0; + } + } + cursor_char = new_cursor_char; + } + + message_reposition(); + cursor_pos = get_absolute_position(message, cursor_line, cursor_char); + + if (doing_drag) { + widgets_message[0].clip_end = cursor_pos; + + clipl = widgets_message[0].clip_start; + clipr = widgets_message[0].clip_end; + if (clipl > clipr) { + cp = clipl; + clipl = clipr; + clipr = cp; + } + clippy_select(widgets_message, message+clipl, clipr-clipl); + } + + status.flags |= NEED_UPDATE; + + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void message_draw_const(void) +{ + draw_box(1, 12, 78, 48, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void song_changed_cb(void) +{ + const char *line, *prevline; + int len; + + edit_mode = 0; + widgets_message[0].d.other.handle_key = message_handle_key_viewmode; + top_line = 0; + message = song_get_message(); + + len = get_nth_line(message, 0, &line); + while (len >= 0) { + if (len > LINE_WRAP) + message_wrap_line((char *) line); + prevline = line; + len = get_nth_line(prevline, 1, &line); + } + + if (status.current_page == PAGE_MESSAGE) + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +static int message_key_hack(struct key_event *k) +{ + if (k->sym == SDLK_ESCAPE && NO_MODIFIER(k->mod) && edit_mode) { + if (!k->state) return 1; + message_set_viewmode(); + memused_songchanged(); + return 1; + } + return 0; +} + +void message_load_page(struct page *page) +{ + page->title = "Message Editor (Shift-F9)"; + page->draw_const = message_draw_const; + page->song_changed_cb = song_changed_cb; + page->pre_handle_key = message_key_hack; + page->total_widgets = 1; + page->widgets = widgets_message; + page->help_index = HELP_MESSAGE_EDITOR; + + create_other(widgets_message + 0, 0, message_handle_key_viewmode, message_draw); +} diff --git a/schism/page_midi.c b/schism/page_midi.c new file mode 100644 index 000000000..277a3f2bc --- /dev/null +++ b/schism/page_midi.c @@ -0,0 +1,365 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" +#include "midi.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +static int top_midi_port = 0; +static int current_port = 0; +static struct widget widgets_midi[17]; +static time_t last_midi_poll = 0; + +/* --------------------------------------------------------------------- */ +static void midi_output_config(void) +{ + set_page(PAGE_MIDI_OUTPUT); +} +static void update_ip_ports(void) +{ + ip_midi_setports(widgets_midi[15].d.thumbbar.value); + last_midi_poll = 0; + status.flags |= NEED_UPDATE; +} +static void update_midi_values(void) +{ + midi_flags = + (widgets_midi[1].d.toggle.state ? MIDI_TICK_QUANTIZE : 0) + | (widgets_midi[2].d.toggle.state ? MIDI_BASE_PROGRAM1 : 0) + | (widgets_midi[3].d.toggle.state ? MIDI_RECORD_NOTEOFF : 0) + | (widgets_midi[4].d.toggle.state ? MIDI_RECORD_VELOCITY : 0) + | (widgets_midi[5].d.toggle.state ? MIDI_RECORD_AFTERTOUCH : 0) + | (widgets_midi[6].d.toggle.state ? MIDI_CUT_NOTE_OFF : 0) + | (widgets_midi[9].d.toggle.state ? MIDI_PITCH_BEND : 0) + | (widgets_midi[11].d.toggle.state? MIDI_EMBED_DATA : 0) + | (widgets_midi[14].d.toggle.state? MIDI_RECORD_SDX : 0); + + midi_amplification = widgets_midi[7].d.thumbbar.value; + midi_c5note = widgets_midi[8].d.thumbbar.value; + midi_pitch_depth = widgets_midi[10].d.thumbbar.value; + if (midi_flags & MIDI_EMBED_DATA) { + status.flags |= SONG_NEEDS_SAVE; + } +} +static void save_midi_config(void) +{ + cfg_midipage_save(); + status_text_flash("Configuration saved"); +} +static void toggle_port(void) +{ + struct midi_port *p; + p = midi_engine_port(current_port, 0); + if (p) { + status.flags |= NEED_UPDATE; + + if (p->disable) if (!p->disable(p)) return; + switch (p->io) { + case 0: + if (p->iocap & MIDI_INPUT) p->io = MIDI_INPUT; + else if (p->iocap & MIDI_OUTPUT) p->io = MIDI_OUTPUT; + break; + case MIDI_INPUT: + if (p->iocap & MIDI_OUTPUT) p->io = MIDI_OUTPUT; + else p->io = 0; + break; + case MIDI_OUTPUT: + if (p->iocap & MIDI_INPUT) p->io |= MIDI_INPUT; + else p->io = 0; + break; + case MIDI_INPUT|MIDI_OUTPUT: + p->io = 0; + break; + }; + + if (p->enable) { + if (!p->enable(p)) { + p->io = 0; + return; + } + } + } +} +static int midi_page_handle_key(struct key_event * k) +{ + int new_port = current_port; + int pos; + + if (k->mouse == MOUSE_SCROLL_UP) { + new_port--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_port++; + } else if (k->mouse) { + if (k->x >= 3 && k->x <= 11 && k->y >= 15 && k->y <= 27) { + if (k->mouse == MOUSE_DBLCLICK) { + if (k->state) return 0; + toggle_port(); + return 1; + } + new_port = top_midi_port + (k->y - 15); + } else { + return 0; + } + } + + switch (k->sym) { + case SDLK_SPACE: + if (!k->state) return 1; + toggle_port(); + return 1; + case SDLK_PAGEUP: + new_port -= 13; + if (new_port < 0) new_port = 0; + break; + case SDLK_PAGEDOWN: + new_port += 13; + if (new_port >= midi_engine_port_count()) { + new_port = midi_engine_port_count()-1; + } + break; + case SDLK_HOME: + new_port = 0; + break; + case SDLK_END: + new_port = midi_engine_port_count()-1; + break; + case SDLK_UP: + new_port--; + break; + case SDLK_DOWN: + new_port++; + break; + case SDLK_TAB: + if (k->state) return 1; + change_focus_to(1); + status.flags |= NEED_UPDATE; + return 1; + default: + if (!k->mouse) return 0; + break; + }; + if (k->state) return 0; + + if (new_port != current_port) { + if (new_port < 0 || new_port >= midi_engine_port_count()) { + new_port = current_port; + if (k->sym == SDLK_DOWN) { + change_focus_to(1); + } + } + + current_port = new_port; + if (current_port < top_midi_port) + top_midi_port = current_port; + + pos = current_port - top_midi_port; + if (pos > 12) top_midi_port = current_port - 12; + if (top_midi_port < 0) top_midi_port = 0; + + status.flags |= NEED_UPDATE; + } + + return 1; +} + +static void midi_page_redraw(void) +{ + struct midi_port *p; + const char *name; + char buffer[64]; + int i, j, n, ct, fg, bg; + time_t now; + + draw_fill_chars(3, 15, 76, 28, 0); + draw_text("Midi ports:", 2, 13, 0, 2); + draw_box(2,14,77,28, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_text( "Tick quantize", 6, 30, 0, 2); + draw_text( "Base Program 1", 5, 31, 0, 2); + draw_text( "Record Note-Off", 4, 32, 0, 2); + draw_text( "Record Velocity", 4, 33, 0, 2); + draw_text( "Record Aftertouch", 2, 34, 0, 2); + draw_text( "Record using SDx", 3, 35, 0, 2); + draw_text( "Cut note off", 7, 36, 0, 2); + + draw_fill_chars(23, 30, 24, 36, 0); + draw_box(19,29,25,37, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_box(52,29,73,32, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_fill_chars(56, 34, 72, 34, 0); + draw_box(52,33,73,36, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_fill_chars(56, 38, 72, 38, 0); + draw_box(52,37,73,39, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_text( "Amplification", 39, 30, 0, 2); + draw_text( "C-5 Note-value", 38, 31, 0, 2); + draw_text("Output MIDI pitch", 35, 34, 0, 2); + draw_text("Pitch wheel depth", 35, 35, 0, 2); + draw_text( "Embed MIDI data", 37, 38, 0, 2); + + draw_text( "IP MIDI ports", 39, 41, 0, 2); + draw_box(52,40,73,42, BOX_THIN|BOX_INNER|BOX_INSET); + + time(&now); + if ((now - last_midi_poll) > 10) { + last_midi_poll = now; + midi_engine_poll_ports(); + } + + ct = midi_engine_port_count(); + for (i = 0; i < 13; i++) { + draw_char(168, 12, 15+i, 2, 0); + + if (top_midi_port + i >= ct) continue; /* err */ + + p = midi_engine_port(i+top_midi_port, &name); + if (current_port == top_midi_port+i + && ACTIVE_WIDGET.type == WIDGET_OTHER) { + fg = 0; + bg = 3; + } else { + fg = 5; + bg = 0; + } + draw_text_len(name, 64, 13, 15+i, 5, 0); + + /* portability: should use difftime */ + if (status.flags & MIDI_EVENT_CHANGED + && (time(0) - status.last_midi_time) < 3 + && p == (struct midi_port *)status.last_midi_port) { + for (j = n = 0; j < 21 && j < status.last_midi_len; j++) { /* 21 is approx 64/3 */ + sprintf(buffer+n, "%02x ", status.last_midi_event[j]); + n += 3; + } + draw_text(buffer, 77 - strlen(buffer), 15+i, 4, 0); + } + + switch (p->io) { + case 0: + draw_text("Disabled ", 3, 15+i, fg, bg); + break; + case MIDI_INPUT: + draw_text(" Input ", 3, 15+i, fg, bg); + break; + case MIDI_OUTPUT: + draw_text(" Output ", 3, 15+i, fg, bg); + break; + case MIDI_INPUT|MIDI_OUTPUT: + draw_text(" Duplex ", 3, 15+i, fg, bg); + break; + default: + draw_text(" Enabled ", 3, 15+i, fg, bg); + break; + }; + } +} + +/* --------------------------------------------------------------------- */ + +void midi_load_page(struct page *page) +{ + page->title = "Midi Screen (Shift-F1)"; + page->draw_const = NULL; + page->song_changed_cb = NULL; + page->predraw_hook = NULL; + page->playback_update = NULL; + page->handle_key = NULL; + page->set_page = NULL; + page->total_widgets = 16; + page->widgets = widgets_midi; + page->help_index = HELP_GLOBAL; + + create_other(widgets_midi+0, 0, midi_page_handle_key, midi_page_redraw); + widgets_midi[0].x = 2; + widgets_midi[0].y = 14; + widgets_midi[0].width = 75; + widgets_midi[0].height = 15; + + create_toggle(widgets_midi+1, 20, 30, + 0, 2, 1, 7, 2, update_midi_values); /* on */ + widgets_midi[1].d.toggle.state = midi_flags & MIDI_TICK_QUANTIZE; + create_toggle(widgets_midi+2, 20, 31, + 1, 3, 2, 8, 3, update_midi_values); /* off */ + widgets_midi[2].d.toggle.state = midi_flags & MIDI_BASE_PROGRAM1; + create_toggle(widgets_midi+3, 20, 32, + 2, 4, 3, 3, 4, update_midi_values); + widgets_midi[3].d.toggle.state = midi_flags & MIDI_RECORD_NOTEOFF; + create_toggle(widgets_midi+4, 20, 33, + 3, 5, 4, 9, 5, update_midi_values); + widgets_midi[4].d.toggle.state = midi_flags & MIDI_RECORD_VELOCITY; + create_toggle(widgets_midi+5, 20, 34, + 4, 14, 5, 10, 14, update_midi_values); + widgets_midi[5].d.toggle.state = midi_flags & MIDI_RECORD_AFTERTOUCH; + + create_toggle(widgets_midi+14, 20, 35, + 5, 6, 6, 6, 6, update_midi_values); /* off */ + widgets_midi[14].d.toggle.state = midi_flags & MIDI_RECORD_SDX; + + create_toggle(widgets_midi+6, 20, 36, + 14, 12, 6, 6, 7, update_midi_values); /* off */ + widgets_midi[6].d.toggle.state = midi_flags & MIDI_CUT_NOTE_OFF; + + + + create_thumbbar(widgets_midi+7, 53, 30, 20, + 0, 8, 8, update_midi_values, 0, 200); /* d. 100 */ + widgets_midi[7].d.thumbbar.value = midi_amplification; + create_thumbbar(widgets_midi+8, 53, 31, 20, + 7, 9, 9, update_midi_values, 0, 127); /* d. 60 */ + widgets_midi[8].d.thumbbar.value = midi_c5note; + + + create_toggle(widgets_midi+9, 53, 34, + 8, 10, 4, 9, 10, update_midi_values); /* off */ + widgets_midi[9].d.toggle.state = midi_flags & MIDI_PITCH_BEND; + create_thumbbar(widgets_midi+10, 53, 35, 20, + 9, 11, 11, update_midi_values, 0, 48); /* d. 0 */ + widgets_midi[10].d.thumbbar.value = midi_pitch_depth; + + create_toggle(widgets_midi+11, 53, 38, + 10, 15, 6, 11, 15, update_midi_values); /* off */ + widgets_midi[11].d.toggle.state = midi_flags & MIDI_EMBED_DATA; + + create_thumbbar(widgets_midi+15, 53, 41, 20, + 11, 15, 12, update_ip_ports, 0, 128); + widgets_midi[15].d.thumbbar.value = ip_midi_getports(); + + create_button(widgets_midi+12, 2, 41, + 27, + 6, 13, 12, 12, 13, + midi_output_config, + "MIDI Output Configuration", + 2); + create_button(widgets_midi+13, 2, 44, + 27, + 12, 13, 13, 13, 0, + save_midi_config, + "Save Output Configuration", + 2); +} diff --git a/schism/page_midiout.c b/schism/page_midiout.c new file mode 100644 index 000000000..8a1b4df42 --- /dev/null +++ b/schism/page_midiout.c @@ -0,0 +1,204 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" +#include "midi.h" +#include "song.h" + +#include +#include + +static struct widget widgets_midiout[33]; +static int zbuf_top = 0; +static char zbuf[7*32]; + +static char mm_global[9*32]; +static char mm_sfx[16*32]; + +static void midiout_draw_const(void) +{ + char buf[4]; + int i; + + draw_text( "MIDI Start", 6, 13, 0, 2); + draw_text( "MIDI Stop", 7, 14, 0, 2); + draw_text( "MIDI Tick", 7, 15, 0, 2); + draw_text( "Note On", 9, 16, 0, 2); + draw_text( "Note Off", 8, 17, 0, 2); + draw_text( "Change Volume", 3, 18, 0, 2); + draw_text( "Change Pan", 6, 19, 0, 2); + draw_text( "Bank Select", 5, 20, 0, 2); + draw_text("Program Change", 2, 21, 0, 2); + + draw_text( "Macro SF0", 5, 24, 0, 2); + draw_text( "Setup SF1", 5, 25, 0, 2); + + buf[0] = 'S'; + buf[1] = 'F'; + buf[3] = '\0'; + for (i = 2; i < 10; i++) { + buf[2] = i + '0'; + draw_text(buf, 13, 24+i, 0, 2); + } + draw_text( "SFA", 13, 34, 0, 2); + draw_text( "SFB", 13, 35, 0, 2); + draw_text( "SFC", 13, 36, 0, 2); + draw_text( "SFD", 13, 37, 0, 2); + draw_text( "SFE", 13, 38, 0, 2); + draw_text( "SFF", 13, 39, 0, 2); + + draw_box(16, 12, 60, 22, BOX_THIN|BOX_INNER|BOX_INSET); + draw_box(16, 23, 60, 40, BOX_THIN|BOX_INNER|BOX_INSET); + draw_box(16, 41, 60, 49, BOX_THIN|BOX_INNER|BOX_INSET); + + for (i = 0; i < 7; i++) { + sprintf(buf, "Z%02X", i+zbuf_top+0x80); + draw_text(buf, 13, i+42, 0, 2); + } +} +static void copyout_zbuf(void) +{ + midi_config *md; + int i; + md = song_get_midi_config(); + song_lock_audio(); + for (i = 0; i < 9; i++) { + strcpy(md->midi_global_data+(i*32), mm_global+(i*32)); + } + for (i = 0; i < 16; i++) { + strcpy(md->midi_sfx+(i*32), mm_sfx+(i*32)); + } + for (i = 0; i < 7; i++) { + strcpy(md->midi_zxx+((zbuf_top+i)*32), + zbuf+(i*32)); + } + song_unlock_audio(); +} +static void copyin_zbuf(void) +{ + midi_config *md; + int i; + md = song_get_midi_config(); + for (i = 0; i < 9; i++) { + strcpy(mm_global+(i*32), md->midi_global_data+(i*32)); + } + for (i = 0; i < 16; i++) { + strcpy(mm_sfx+(i*32), md->midi_sfx+(i*32)); + } + + for (i = 0; i < 7; i++) { + strcpy(zbuf+(i*32), md->midi_zxx+((zbuf_top+i)*32)); + } +} + +static int pre_handle_key(struct key_event *k) +{ + if (k->sym == SDLK_ESCAPE) { + if (!k->state) return 1; + set_page(PAGE_MIDI); + return 1; + } + if (*selected_widget == 25 && (k->sym == SDLK_UP || k->mouse == 2)) { + /* scroll up */ + if (zbuf_top == 0) return 0; + if (k->state) return 1; + copyout_zbuf(); + zbuf_top--; + copyin_zbuf(); + status.flags |= NEED_UPDATE; + return 1; + } + if (*selected_widget == 31 && (k->sym == SDLK_DOWN || k->mouse == 3)) { + /* scroll down */ + if (zbuf_top >= 121) return 0; + if (k->state) return 1; + copyout_zbuf(); + zbuf_top++; + copyin_zbuf(); + status.flags |= NEED_UPDATE; + return 1; + } + if ((*selected_widget) >= 25) { + switch (k->sym) { + case SDLK_PAGEUP: + if (k->state) return 1; + copyout_zbuf(); + zbuf_top -= 7; + if (zbuf_top < 0) zbuf_top = 0; + copyin_zbuf(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_PAGEDOWN: + if (k->state) return 1; + copyout_zbuf(); + zbuf_top += 7; + if (zbuf_top >= 121) zbuf_top = 121; + status.flags |= NEED_UPDATE; + copyin_zbuf(); + return 1; + }; + } + return 0; +} + +void midiout_set_page(void) +{ + copyin_zbuf(); +} + +void midiout_load_page(struct page *page) +{ + int i; + + page->title = "MIDI Output Configuration"; + page->draw_const = midiout_draw_const; + page->set_page = midiout_set_page; + page->pre_handle_key = pre_handle_key; + page->total_widgets = 32; + page->widgets = widgets_midiout; + page->help_index = HELP_MIDI_OUTPUT; + + for (i = 0; i < 9; i++) { + create_textentry(widgets_midiout+i, 17, 13+i, 43, + (i == 0 ? 0 : (i-1)), + i+1, + i+1, + copyout_zbuf, mm_global+(i*32),31); + } + for (i = 0; i < 16; i++) { + create_textentry(widgets_midiout+9+i, 17, 24+i, 43, + 9+(i-1), + 9+(i+1), + 9+(i+1), + copyout_zbuf, mm_sfx+(i*32),31); + } + for (i = 0; i < 7; i++) { + create_textentry(widgets_midiout+25+i, 17, 42+i, 43, + 25+(i-1), + 25+((i == 6) ? 6 : (i+1)), + 25+((i == 6) ? 6 : (i+1)), + copyout_zbuf, zbuf+(i*32),31); + } + zbuf_top = 0; +} + diff --git a/schism/page_orderpan.c b/schism/page_orderpan.c new file mode 100644 index 000000000..0fd03439c --- /dev/null +++ b/schism/page_orderpan.c @@ -0,0 +1,855 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_orderpan[65], widgets_ordervol[65]; + +static int top_order = 0; +static int current_order = 0; +static int orderlist_cursor_pos = 0; + +static unsigned char saved_orderlist[256]; +static int _did_save_orderlist = 0; + +/* --------------------------------------------------------------------- */ + +static void orderlist_reposition(void) +{ + if (current_order < top_order) { + top_order = current_order; + } else if (current_order > top_order + 31) { + top_order = current_order - 31; + } +} + +/* --------------------------------------------------------------------- */ + +void update_current_order(void) +{ + char buf[4]; + + draw_text(numtostr(3, current_order, buf), 12, 5, 5, 0); + draw_text(numtostr(3, song_get_num_orders(), buf), 16, 5, 5, 0); +} + + +inline void set_current_order(int order) +{ + current_order = CLAMP(order, 0, 255); + orderlist_reposition(); + + status.flags |= NEED_UPDATE; +} + +int get_current_order(void) +{ + return current_order; +} + +/* --------------------------------------------------------------------- */ +/* called from the pattern editor on ctrl-plus/minus */ + +void prev_order_pattern(void) +{ + int new_order = current_order - 1; + int pattern; + +RETR: if (new_order < 0) + new_order = 0; + + pattern = song_get_orderlist()[new_order]; + + if ((!(status.flags & CLASSIC_MODE)) && pattern == ORDER_SKIP) { + current_order = new_order; + new_order--; + goto RETR; + } + + if (pattern < 200) { + current_order = new_order; + orderlist_reposition(); + set_current_pattern(pattern); + } +} + +void next_order_pattern(void) +{ + int new_order = current_order + 1; + int pattern; + +RETR: if (new_order > 255) + new_order = 255; + + pattern = song_get_orderlist()[new_order]; + if (pattern == ORDER_SKIP) { + current_order = new_order; + new_order++; + goto RETR; + } + + if (pattern < 200) { + current_order = new_order; + orderlist_reposition(); + set_current_pattern(pattern); + } +} + +/* --------------------------------------------------------------------- */ + +static void get_pattern_string(unsigned char pattern, char *buf) +{ + switch (pattern) { + case ORDER_SKIP: + buf[0] = buf[1] = buf[2] = '+'; + buf[3] = 0; + break; + case ORDER_LAST: + buf[0] = buf[1] = buf[2] = '-'; + buf[3] = 0; + break; + default: + numtostr(3, pattern, buf); + break; + } +} + +static void orderlist_draw(void) +{ + unsigned char *list = song_get_orderlist(); + char buf[4]; + int pos, n; + int playing_order = (song_get_mode() == MODE_PLAYING ? song_get_current_order() : -1); + + /* draw the list */ + for (pos = 0, n = top_order; pos < 32; pos++, n++) { + draw_text(numtostr(3, n, buf), 2, 15 + pos, (n == playing_order ? 3 : 0), 2); + get_pattern_string(list[n], buf); + draw_text(buf, 6, 15 + pos, 2, 0); + } + + /* draw the cursor */ + if (ACTIVE_PAGE.selected_widget == 0) { + get_pattern_string(list[current_order], buf); + pos = current_order - top_order; + draw_char(buf[orderlist_cursor_pos], orderlist_cursor_pos + 6, 15 + pos, 0, 3); + } + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void orderlist_insert_pos(void) +{ + unsigned char *list = song_get_orderlist(); + + memmove(list + current_order + 1, list + current_order, 255 - current_order); + list[current_order] = ORDER_LAST; + + status.flags |= NEED_UPDATE; +} + +static void orderlist_save(void) +{ + unsigned char *list = song_get_orderlist(); + memcpy(saved_orderlist, list, 255); + _did_save_orderlist = 1; +} +static void orderlist_restore(void) +{ + unsigned char *list = song_get_orderlist(); + unsigned char oldlist[256]; + if (!_did_save_orderlist) return; + memcpy(oldlist, list, 255); + memcpy(list, saved_orderlist, 255); + memcpy(saved_orderlist, oldlist, 255); +} + +static void orderlist_delete_pos(void) +{ + unsigned char *list = song_get_orderlist(); + + memmove(list + current_order, list + current_order + 1, 255 - current_order); + list[255] = ORDER_LAST; + + status.flags |= NEED_UPDATE; +} + +static void orderlist_insert_next(void) +{ + unsigned char *list = song_get_orderlist(); + int next_pattern; + + if (current_order == 0 || list[current_order - 1] > 199) + return; + next_pattern = list[current_order - 1] + 1; + if (next_pattern > 199) + next_pattern = 199; + list[current_order] = next_pattern; + if (current_order < 255) + current_order++; + orderlist_reposition(); + + status.flags |= NEED_UPDATE; +} + +static void orderlist_reorder(void) +{ + /* err, I hope this is going to be done correctly... + */ + song_note *np[256], *src; + int nplen[256]; + unsigned char *ol; + int i, srclen; + + ol = song_get_orderlist(); + + /* pass one */ + for (i = 0; i < 255; i++) { + if (ol[i] == ORDER_LAST || ol[i] == ORDER_SKIP) { + np[i] = NULL; + nplen[i] = 64; + } else { + np[i] = song_pattern_allocate_copy(ol[i], &nplen[i]); + } + } + + /* pass two */ + for (i = 0; i < 200; i++) { + if (ol[i] != ORDER_LAST && ol[i] != ORDER_SKIP) ol[i] = i; + song_pattern_install(i, np[i], nplen[i]); + } + for (i = 200; i < 255; i++) { + ol[i] = ORDER_LAST; + } + + status.flags |= NEED_UPDATE; +} +static void orderlist_add_unused_patterns(void) +{ + /* n0 = the first free order + * n = orderlist position + * p = pattern iterator + * np = number of patterns */ + int n0, n, p, np = song_get_num_patterns(); + byte used[200] = {0}; /* could be a bitset... */ + unsigned char *list = song_get_orderlist(); + + for (n = 0; n < 255; n++) + if (list[n] < 200) + used[list[n]] = 1; + + /* after the loop, n == 255 */ + while (n >= 0 && list[n] == 0xff) + n--; + if (n == -1) + n = 0; + else + n += 2; + + n0 = n; + for (p = 0; p <= np; p++) { + if (used[p] || song_pattern_is_empty(p)) + continue; + if (n > 255) { + /* status_text_flash("No more room in orderlist"); */ + break; + } + list[n++] = p; + } + if (n == n0) { + status_text_flash("No unused patterns"); + } else { + set_current_order(n - 1); + set_current_order(n0); + if (n - n0 == 1) { + status_text_flash("1 unused pattern found"); + } else { + status_text_flash("%d unused patterns found", n - n0); + } + } +} + +static int orderlist_handle_char(struct key_event *k) +{ + int c; + int cur_pattern; + int n[3] = { 0 }; + + switch (k->sym) { + case SDLK_PLUS: + if (!k->state) return 1; + status.flags |= SONG_NEEDS_SAVE; + song_get_orderlist()[current_order] = ORDER_SKIP; + orderlist_cursor_pos = 2; + break; + case SDLK_MINUS: + if (!k->state) return 1; + status.flags |= SONG_NEEDS_SAVE; + song_get_orderlist()[current_order] = ORDER_LAST; + orderlist_cursor_pos = 2; + break; + default: + c = numeric_key_event(k); + if (c == -1) return 0; + if (k->state) return 1; + + status.flags |= SONG_NEEDS_SAVE; + cur_pattern = song_get_orderlist()[current_order]; + if (cur_pattern < 200) { + n[0] = cur_pattern / 100; + n[1] = cur_pattern / 10 % 10; + n[2] = cur_pattern % 10; + } + + n[orderlist_cursor_pos] = c; + cur_pattern = n[0] * 100 + n[1] * 10 + n[2]; + cur_pattern = CLAMP(cur_pattern, 0, 199); + song_get_orderlist()[current_order] = cur_pattern; + break; + }; + + if (orderlist_cursor_pos == 2) { + if (current_order < 255) + current_order++; + orderlist_cursor_pos = 0; + orderlist_reposition(); + } else { + orderlist_cursor_pos++; + } + + status.flags |= NEED_UPDATE; + + return 1; +} + +static void _copysam(UNUSED void *ign) +{ + int patno; + + patno = song_get_orderlist()[current_order]; + status_text_flash("Copied pattern %d into sample %d", + patno, sample_get_current()); + diskwriter_writeout_sample(sample_get_current(), patno, 0); +} +static void _attachsam(UNUSED void *ign) +{ + int patno; + + patno = song_get_orderlist()[current_order]; + status_text_flash("Linked pattern %d into sample %d", + patno, sample_get_current()); + diskwriter_writeout_sample(sample_get_current(), patno, 1); +} +static int orderlist_handle_key_on_list(struct key_event * k) +{ + unsigned char *list = song_get_orderlist(); + int prev_order = current_order; + int new_order = prev_order; + int new_cursor_pos = orderlist_cursor_pos; + song_sample *samp; + char *z; + int n, p; + + if (k->mouse) { + if (k->x >= 6 && k->x <= 8 && k->y >= 15 && k->y <= 46) { + if (k->mouse == MOUSE_SCROLL_UP) { + new_order--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_order++; + } else { + if (!k->state) return 0; + + new_order = (k->y - 15) + top_order; + set_current_order(new_order); + new_order = current_order; + + if (list[current_order] != ORDER_LAST + && list[current_order] != ORDER_SKIP) { + new_cursor_pos = (k->x - 6); + } + } + } + } + + switch (k->sym) { + case SDLK_RETURN: + if (status.flags & CLASSIC_MODE) return 0; + if (!(k->mod & KMOD_ALT)) return 0; + if (!k->state) return 1; + status_text_flash("Saved orderlist"); + orderlist_save(); + return 1; + + case SDLK_BACKSPACE: + if (status.flags & CLASSIC_MODE) return 0; + if (!(k->mod & KMOD_ALT)) return 0; + if (!k->state) return 1; + if (!_did_save_orderlist) return 1; + status_text_flash("Restored orderlist"); + orderlist_restore(); + return 1; + case SDLK_TAB: + if (k->mod & KMOD_SHIFT) { + if (k->state) return 1; + change_focus_to(33); + } else { + if (!NO_MODIFIER(k->mod)) return 0; + if (k->state) return 1; + change_focus_to(1); + } + return 1; + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_pos--; + break; + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_pos++; + break; + case SDLK_HOME: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order = 0; + break; + case SDLK_END: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order = song_get_num_orders(); + if (song_get_orderlist()[new_order] != ORDER_LAST) + new_order++; + break; + case SDLK_UP: + if (k->mod & KMOD_CTRL) { + if (status.flags & CLASSIC_MODE) return 0; + if (k->state) return 1; + sample_set(sample_get_current()-1); + return 1; + } + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order--; + break; + case SDLK_DOWN: + if (k->mod & KMOD_CTRL) { + if (status.flags & CLASSIC_MODE) return 0; + if (k->state) return 1; + sample_set(sample_get_current()+1); + return 1; + } + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order++; + break; + case SDLK_PAGEUP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order -= 16; + break; + case SDLK_PAGEDOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order += 16; + break; + case SDLK_INSERT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + orderlist_insert_pos(); + return 1; + case SDLK_DELETE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + orderlist_delete_pos(); + return 1; + case SDLK_F7: + if (k->mod & KMOD_CTRL) { + if (k->state) return 1; + song_set_next_order(current_order); + status_text_flash("Playing order %d next", current_order); + } else { + return 0; + } + return 1; + case SDLK_F6: + if (k->mod & KMOD_SHIFT) { + if (k->state) return 1; + song_start_at_order(current_order, 0); + return 1; + } + return 0; + + case SDLK_n: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + orderlist_insert_next(); + return 1; + case SDLK_g: + if (!NO_MODIFIER(k->mod)) + return 0; + if (!k->state) return 1; + n = song_get_orderlist()[new_order]; + if (n < 200) { + set_current_pattern(n); + set_page(PAGE_PATTERN_EDITOR); + } + return 1; + case SDLK_r: + if (k->mod & KMOD_ALT) { + if (!k->state) return 1; + orderlist_reorder(); + return 1; + } + return 0; + case SDLK_u: + if (k->mod & KMOD_ALT) { + if (k->state) return 1; + orderlist_add_unused_patterns(); + return 1; + } + return 0; + case SDLK_o: + if (k->mod & KMOD_CTRL) { + if (status.flags & CLASSIC_MODE) return 0; + p = song_get_orderlist()[current_order]; + if (p >= 200) return 0; + n = sample_get_current(); + if (n < 1) return 0; + if (k->state) return 1; + + samp = song_get_sample(n,&z); + if (samp && z + && ((unsigned char)z[23]) == 0xFF + && ((unsigned char)z[24]) < 200) { + dialog_create(DIALOG_OK_CANCEL, + "This will replace and unlink the current sample", _copysam, dialog_cancel, 1, 0); + } else if (song_sample_is_empty(n)) { + _copysam(0); + } else { + dialog_create(DIALOG_OK_CANCEL, + "This will replace the current sample", _copysam, dialog_cancel, 1, 0); + } + } + return 0; + case SDLK_b: + if (k->mod & KMOD_CTRL) { + if (status.flags & CLASSIC_MODE) return 0; + p = song_get_orderlist()[current_order]; + if (p >= 200) return 0; + if (sample_get_current() < 1) return 0; + + if (k->state) return 1; + + for (n = 1; n <= 99; n++) { + samp = song_get_sample(n,&z); + if (!samp || !z) continue; + if (((unsigned char)z[23]) != 0xFF) continue; + if (((unsigned char)z[24]) != p) continue; + status_text_flash("Pattern %d already linked to sample %d", + p, n); + return 1; + } + + if (song_sample_is_empty(sample_get_current())) { + _attachsam(0); + } else { + dialog_create(DIALOG_OK_CANCEL, + "This will replace the current sample", _attachsam, dialog_cancel, 1, 0); + } + } + return 0; + case SDLK_LESS: + if (!NO_MODIFIER(k->mod)) return 0; + if (status.flags & CLASSIC_MODE) return 0; + if (k->state) return 1; + sample_set(sample_get_current()-1); + return 1; + case SDLK_GREATER: + if (!NO_MODIFIER(k->mod)) return 0; + if (status.flags & CLASSIC_MODE) return 0; + if (k->state) return 1; + sample_set(sample_get_current()+1); + return 1; + default: + if (!k->mouse) { + if ((k->mod & (KMOD_CTRL | KMOD_ALT))==0) { + return orderlist_handle_char(k); + } + return 0; + } + } + + if (new_cursor_pos < 0) + new_cursor_pos = 2; + else if (new_cursor_pos > 2) + new_cursor_pos = 0; + + if (new_order != prev_order) { + set_current_order(new_order); + } else if (new_cursor_pos != orderlist_cursor_pos) { + orderlist_cursor_pos = new_cursor_pos; + } else { + return 0; + } + + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void order_pan_vol_draw_const(void) +{ + draw_box(5, 14, 9, 47, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_box(30, 14, 40, 47, BOX_THICK | BOX_INNER | BOX_FLAT_LIGHT); + draw_box(64, 14, 74, 47, BOX_THICK | BOX_INNER | BOX_FLAT_LIGHT); + + draw_char(146, 30, 14, 3, 2); + draw_char(145, 40, 14, 3, 2); + + draw_char(146, 64, 14, 3, 2); + draw_char(145, 74, 14, 3, 2); +} + +static void orderpan_draw_const(void) +{ + order_pan_vol_draw_const(); + draw_text("L M R", 31, 14, 0, 3); + draw_text("L M R", 65, 14, 0, 3); +} + +static void ordervol_draw_const(void) +{ + int n; + char buf[16] = "Channel 42"; + + order_pan_vol_draw_const(); + + draw_text(" Volumes ", 31, 14, 0, 3); + draw_text(" Volumes ", 65, 14, 0, 3); + + for (n = 1; n <= 32; n++) { + numtostr(2, n, buf + 8); + draw_text(buf, 20, 14 + n, 0, 2); + + numtostr(2, n + 32, buf + 8); + draw_text(buf, 54, 14 + n, 0, 2); + } +} + +/* --------------------------------------------------------------------- */ + +static void order_pan_vol_playback_update(void) +{ + static int last_order = -1; + int order = ((song_get_mode() == MODE_STOPPED) ? -1 : song_get_current_order()); + + if (order != last_order) { + last_order = order; + status.flags |= NEED_UPDATE; + } +} + +/* --------------------------------------------------------------------- */ + +static void orderpan_update_values_in_song(void) +{ + song_channel *chn; + int n; + + status.flags |= SONG_NEEDS_SAVE; + for (n = 0; n < 64; n++) { + chn = song_get_channel(n); + + /* yet another modplug hack here! */ + chn->panning = widgets_orderpan[n + 1].d.panbar.value * 4; + + if (widgets_orderpan[n + 1].d.panbar.surround) + chn->flags |= CHN_SURROUND; + else + chn->flags &= ~CHN_SURROUND; + + song_set_channel_mute(n, widgets_orderpan[n + 1].d.panbar.muted); + } +} + +static void ordervol_update_values_in_song(void) +{ + int n; + + status.flags |= SONG_NEEDS_SAVE; + for (n = 0; n < 64; n++) + song_get_channel(n)->volume = widgets_ordervol[n + 1].d.thumbbar.value; +} + +/* called when a channel is muted/unmuted by means other than the panning + * page (alt-f10 in the pattern editor, space on the info page...) */ +void orderpan_recheck_muted_channels(void) +{ + int n; + for (n = 0; n < 64; n++) + widgets_orderpan[n + 1].d.panbar.muted = !!(song_get_channel(n)->flags & CHN_MUTE); + + if (status.current_page == PAGE_ORDERLIST_PANNING) + status.flags |= NEED_UPDATE; +} + +static void order_pan_vol_song_changed_cb(void) +{ + int n; + song_channel *chn; + + for (n = 0; n < 64; n++) { + chn = song_get_channel(n); + widgets_orderpan[n + 1].d.panbar.value = chn->panning / 4; + widgets_orderpan[n + 1].d.panbar.surround = !!(chn->flags & CHN_SURROUND); + widgets_orderpan[n + 1].d.panbar.muted = !!(chn->flags & CHN_MUTE); + widgets_ordervol[n + 1].d.thumbbar.value = chn->volume; + } +} + +/* --------------------------------------------------------------------- */ + +static void order_pan_vol_handle_key(struct key_event * k) +{ + int n = *selected_widget; + + if (k->state) return; + + if (!NO_MODIFIER(k->mod)) + return; + + switch (k->sym) { + case SDLK_PAGEDOWN: + n += 8; + break; + case SDLK_PAGEUP: + n -= 8; + break; + default: + return; + } + + n = CLAMP(n, 1, 64); + if (*selected_widget != n) + change_focus_to(n); +} + +static int order_pre_key(struct key_event *k) +{ +#if 0 +/* this was wrong */ + if (k->sym == SDLK_F7) { + if (!NO_MODIFIER(k->mod)) return 0; + if (k->state) return 1; + song_start_at_order(current_order, 0); + return 1; + } +#endif + return 0; +} + +/* --------------------------------------------------------------------- */ + +void orderpan_load_page(struct page *page) +{ + int n; + + page->title = "Order List and Panning (F11)"; + page->draw_const = orderpan_draw_const; + /* this does the work for both pages */ + page->song_changed_cb = order_pan_vol_song_changed_cb; + page->playback_update = order_pan_vol_playback_update; + page->pre_handle_key = order_pre_key; + page->handle_key = order_pan_vol_handle_key; + page->total_widgets = 65; + page->widgets = widgets_orderpan; + page->help_index = HELP_ORDERLIST_PANNING; + + /* 0 = order list */ + create_other(widgets_orderpan + 0, 1, orderlist_handle_key_on_list, orderlist_draw); + widgets_orderpan[0].x = 6; + widgets_orderpan[0].y = 15; + widgets_orderpan[0].width = 3; + widgets_orderpan[0].height = 32; + + /* 1-64 = panbars */ + create_panbar(widgets_orderpan + 1, 20, 15, 1, 2, 33, orderpan_update_values_in_song, 1); + for (n = 2; n <= 32; n++) { + create_panbar(widgets_orderpan + n, 20, 14 + n, n - 1, n + 1, n + 32, + orderpan_update_values_in_song, n); + create_panbar(widgets_orderpan + n + 31, 54, 13 + n, n + 30, n + 32, 0, + orderpan_update_values_in_song, n + 31); + } + create_panbar(widgets_orderpan + 64, 54, 46, 63, 64, 0, orderpan_update_values_in_song, 64); +} + +void ordervol_load_page(struct page *page) +{ + int n; + + page->title = "Order List and Channel Volume (F11)"; + page->draw_const = ordervol_draw_const; + page->playback_update = order_pan_vol_playback_update; + page->pre_handle_key = order_pre_key; + page->handle_key = order_pan_vol_handle_key; + page->total_widgets = 65; + page->widgets = widgets_ordervol; + page->help_index = HELP_ORDERLIST_VOLUME; + + /* 0 = order list */ + create_other(widgets_ordervol + 0, 1, orderlist_handle_key_on_list, orderlist_draw); + widgets_ordervol[0].x = 6; + widgets_ordervol[0].y = 15; + widgets_ordervol[0].width = 3; + widgets_ordervol[0].height = 32; + + /* 1-64 = thumbbars */ + create_thumbbar(widgets_ordervol + 1, 31, 15, 9, 1, 2, 33, ordervol_update_values_in_song, 0, 64); + for (n = 2; n <= 32; n++) { + create_thumbbar(widgets_ordervol + n, 31, 14 + n, 9, n - 1, n + 1, n + 32, + ordervol_update_values_in_song, 0, 64); + create_thumbbar(widgets_ordervol + n + 31, 65, 13 + n, 9, n + 30, n + 32, 0, + ordervol_update_values_in_song, 0, 64); + } + create_thumbbar(widgets_ordervol + 64, 65, 46, 9, 63, 64, 0, ordervol_update_values_in_song, 0, 64); +} diff --git a/schism/page_palette.c b/schism/page_palette.c new file mode 100644 index 000000000..8f29a5e6b --- /dev/null +++ b/schism/page_palette.c @@ -0,0 +1,308 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_palette[49]; + +static int selected_palette, max_palette = 0; + +/* --------------------------------------------------------------------- */ +/* +This is actually wrong. For some reason the boxes around the little color swatches are drawn with the top +right and bottom left corners in color 3 instead of color 1 like all the other thick boxes have. I'm going +to leave it this way, though -- it's far more likely that someone will comment on, say, my completely +changing the preset switcher than about the corners having different colors :) + +(Another discrepancy: seems that Impulse Tracker draws the thumbbars with a "fake" range of 0-64, because +it never gets drawn at the far right. Oh well.) */ + +static void palette_draw_const(void) +{ + int n; + + draw_text("Predefined Palettes", 57, 25, 0, 2); + + for (n = 0; n < 7; n++) { + draw_box(2, 13 + (5 * n), 8, 17 + (5 * n), BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(9, 13 + (5 * n), 19, 17 + (5 * n), BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(29, 13 + (5 * n), 35, 17 + (5 * n), BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(36, 13 + (5 * n), 46, 17 + (5 * n), BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(3, 14 + (5 * n), 7, 16 + (5 * n), n); + draw_fill_chars(30, 14 + (5 * n), 34, 16 + (5 * n), n + 7); + } + + draw_box(56, 13, 62, 17, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(63, 13, 73, 17, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(56, 18, 62, 22, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(63, 18, 73, 22, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(55, 26, 77, 47, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(57, 14, 61, 16, 14); + draw_fill_chars(57, 19, 61, 21, 15); +} + +/* --------------------------------------------------------------------- */ + +static void update_thumbbars(void) +{ + int n; + + for (n = 0; n < 16; n++) { + /* palettes[current_palette_index].colors[n] ? + * or current_palette[n] ? */ + widgets_palette[3 * n].d.thumbbar.value = current_palette[n][0]; + widgets_palette[3 * n + 1].d.thumbbar.value = current_palette[n][1]; + widgets_palette[3 * n + 2].d.thumbbar.value = current_palette[n][2]; + } +} + +/* --------------------------------------------------------------------- */ + +static void palette_list_draw(void) +{ + int n, focused = (ACTIVE_PAGE.selected_widget == 48); + int fg, bg; + + draw_fill_chars(56, 27, 76, 46, 0); + + fg = 6; + bg = 0; + if (focused && -1 == selected_palette) { + fg = 0; + bg = 3; + } else if (-1 == selected_palette) { + bg = 14; + } + + draw_text_len("User Defined", 21, 56, 27, fg, bg); + for (n = 0; n < 19 && palettes[n].name[0]; n++) { + fg = 6; + bg = 0; + if (focused && n == selected_palette) { + fg = 0; + bg = 3; + } else if (n == selected_palette) { + bg = 14; + } + draw_text_len(palettes[n].name, 21, 56, 28 + n, fg, bg); + } + max_palette = n; +} + +static int palette_list_handle_key_on_list(struct key_event * k) +{ + int new_palette = selected_palette; + const int focus_offsets[] = { 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, 11, 12 }; + + if (k->mouse == 1) { + if (!k->state) return 0; + if (k->x < 56 || k->y < 27 || k->y > 46 || k->x > 76) return 0; + new_palette = (k->y - 28); + if (new_palette == selected_palette) { + // alright + if (selected_palette == -1) return 1; + palette_load_preset(selected_palette); + palette_apply(); + update_thumbbars(); + status.flags |= NEED_UPDATE; + return 1; + } + } else { + if (k->state) return 0; + if (k->mouse == 2) new_palette--; + else if (k->mouse == 3) new_palette++; + } + + switch (k->sym) { + case SDLK_UP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (--new_palette < -1) { + change_focus_to(47); + return 1; + } + break; + case SDLK_DOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + new_palette++; + break; + case SDLK_HOME: + if (!NO_MODIFIER(k->mod)) + return 0; + new_palette = 0; + break; + case SDLK_PAGEUP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (new_palette == -1) { + change_focus_to(45); + return 1; + } + new_palette -= 16; + break; + case SDLK_END: + if (!NO_MODIFIER(k->mod)) + return 0; + new_palette = max_palette - 1; + break; + case SDLK_PAGEDOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + new_palette += 16; + break; + case SDLK_RETURN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (selected_palette == -1) return 1; + palette_load_preset(selected_palette); + palette_apply(); + update_thumbbars(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_RIGHT: + case SDLK_TAB: + if (k->mod & KMOD_SHIFT) { + change_focus_to(focus_offsets[selected_palette+1] + 29); + return 1; + } + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(focus_offsets[selected_palette+1] + 8); + return 1; + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(focus_offsets[selected_palette+1] + 29); + return 1; + default: + if (!k->mouse) return 0; + } + + if (new_palette < -1) new_palette = -1; + else if (new_palette >= (max_palette-1)) new_palette = (max_palette-1); + if (new_palette != selected_palette) { + selected_palette = new_palette; + status.flags |= NEED_UPDATE; + } + + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void palette_list_handle_key(struct key_event * k) +{ + int n = *selected_widget; + + if (!NO_MODIFIER(k->mod)) + return; + + if (k->state) return; + + switch (k->sym) { + case SDLK_PAGEUP: + n -= 3; + break; + case SDLK_PAGEDOWN: + n += 3; + break; + default: + return; + } + + if (status.flags & CLASSIC_MODE) { + if (n < 0) + return; + if (n > 48) + n = 48; + } else { + n = CLAMP(n, 0, 48); + } + if (n != *selected_widget) + change_focus_to(n); +} + +/* --------------------------------------------------------------------- */ + +/* TODO | update_palette should only change the palette index for the color that's being changed, not all + TODO | of them. also, it should call ccache_destroy_color(n) instead of wiping out the whole character + TODO | cache whenever a color value is changed. */ + +static void update_palette(void) +{ + int n; + + for (n = 0; n < 16; n++) { + current_palette[n][0] = widgets_palette[3 * n].d.thumbbar.value; + current_palette[n][1] = widgets_palette[3 * n + 1].d.thumbbar.value; + current_palette[n][2] = widgets_palette[3 * n + 2].d.thumbbar.value; + } + selected_palette = current_palette_index = -1; + palette_apply(); + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +void palette_load_page(struct page *page) +{ + int n; + + page->title = "Palette Configuration (Ctrl-F12)"; + page->draw_const = palette_draw_const; + page->handle_key = palette_list_handle_key; + page->total_widgets = 49; + page->widgets = widgets_palette; + page->help_index = HELP_GLOBAL; + + selected_palette = current_palette_index; + + for (n = 0; n < 16; n++) { + int tabs[3] = { 3 * n + 21, 3 * n + 22, 3 * n + 23 }; + if (n >= 9 && n <= 13) { + tabs[0] = tabs[1] = tabs[2] = 48; + } else if (n > 13) { + tabs[0] = 3 * n - 42; + tabs[1] = 3 * n - 41; + tabs[2] = 3 * n - 40; + } + create_thumbbar(widgets_palette + (3 * n), 10 + 27 * (n / 7), 5 * (n % 7) + 14, 9, + n ? (3 * n - 1) : 0, 3 * n + 1, tabs[0], update_palette, 0, 63); + create_thumbbar(widgets_palette + (3 * n + 1), 10 + 27 * (n / 7), 5 * (n % 7) + 15, 9, + 3 * n, 3 * n + 2, tabs[1], update_palette, 0, 63); + create_thumbbar(widgets_palette + (3 * n + 2), 10 + 27 * (n / 7), 5 * (n % 7) + 16, 9, + 3 * n + 1, 3 * n + 3, tabs[2], update_palette, 0, 63); + } + update_thumbbars(); + + create_other(widgets_palette + 48, 0, palette_list_handle_key_on_list, palette_list_draw); + widgets_palette[48].x = 56; + widgets_palette[48].y = 27; + widgets_palette[48].width = 20; + widgets_palette[48].height = 19; +} diff --git a/schism/page_patedit.c b/schism/page_patedit.c new file mode 100644 index 000000000..8c7968cfc --- /dev/null +++ b/schism/page_patedit.c @@ -0,0 +1,3881 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* The all-important pattern editor. The code here is a general mess, so + * don't look at it directly or, uh, you'll go blind or something. */ + +#include "headers.h" + +#include "it.h" +#include "page.h" +#include "song.h" +#include "pattern-view.h" +#include "config-parser.h" +#include "midi.h" + +#include +#include + +#include "clippy.h" + +extern void (*shift_release)(void); + +/* --------------------------------------------------------------------------------------------------------- */ + +#define ROW_IS_MAJOR(r) (row_highlight_major != 0 && (r) % row_highlight_major == 0) +#define ROW_IS_MINOR(r) (row_highlight_minor != 0 && (r) % row_highlight_minor == 0) +#define ROW_IS_HIGHLIGHT(r) (ROW_IS_MINOR(r) || ROW_IS_MAJOR(r)) + +/* this is actually used by pattern-view.c */ +int show_default_volumes = 0; + +/* --------------------------------------------------------------------- */ +/* The (way too many) static variables */ + +int midi_start_record = 0; + +static int template_mode = 0; + +/* only one widget, but MAN is it complicated :) */ +static struct widget widgets_pattern[1]; + +/* pattern display position */ +static int top_display_channel = 1; /* one-based */ +static int top_display_row = 0; /* zero-based */ + +/* these three tell where the cursor is in the pattern */ +static int current_channel = 1, current_position = 0; +static int current_row = 0; + +/* when holding shift, this "remembers" the original channel and allows +us to jump back to it when letting go +*/ +static int channel_snap_back = -1; + +/* if pressing shift WHILE TRACING the song is paused until we release it */ +static int tracing_was_playing = 0; + +/* this is, of course, what the current pattern is */ +static int current_pattern = 0; + +static int skip_value = 1; /* aka cursor step */ + +static int link_effect_column = 0; +static int draw_divisions = 0; /* = vertical lines between channels */ + +static int centralise_cursor = 0; +static int highlight_current_row = 0; +int playback_tracing = 0; /* scroll lock */ +int midi_playback_tracing = 0; + +static int panning_mode = 0; /* for the volume column */ +int midi_bend_hit[64]; +int midi_last_bend_hit[64]; + +/* blah; other forwards */ +static void pated_history_add(const char *descr, int x, int y, int width, int height); +static void pated_history_restore(int n); + + +/* these should fix the playback tracing position discrepancy */ +static int playing_row = -1; +static int playing_pattern = -1; + +/* the current editing mask (what stuff is copied) */ +#define MASK_NOTE 1 /* immutable */ +#define MASK_INSTRUMENT 2 +#define MASK_VOLUME 4 +#define MASK_EFFECT 8 +#define MASK_EFFECTVALUE 16 +static int edit_copy_mask = MASK_NOTE | MASK_INSTRUMENT | MASK_VOLUME; +static const int edit_pos_to_copy_mask[9] = { 0,0,1,1,2,2,3,4,4 }; + + +/* and the mask note. note that the instrument field actually isn't used */ +static song_note mask_note = { 61, 0, 0, 0, 0, 0 }; /* C-5 */ + +/* playback mark (ctrl-f7) */ +static int marked_pattern = -1, marked_row; + +/* volume stuff (alt-i, alt-j, ctrl-j) */ +static int volume_percent = 100; +static int vary_depth = 10; +static int fast_volume_percent = 67; +static int fast_volume_mode = 0; /* toggled with ctrl-j */ + +/* --------------------------------------------------------------------- */ +/* undo and clipboard handling */ +struct pattern_snap { + song_note *data; + int channels; + int rows; + + /* used by undo/history only */ + const unsigned char *snap_op; + int freesnapop; + int x, y; +}; +static struct pattern_snap fast_save = { NULL, 0, 0,"Fast Pattern Save" }; +static int fast_save_validity = -1; + + +static struct pattern_snap clipboard = { NULL, 0, 0,"Clipboard" }; +static struct pattern_snap undo_history[10]; +static int undo_history_top = 0; + +/* this function is stupid, it doesn't belong here */ +void _memused_get_pattern_saved(unsigned int *a, unsigned int *b) +{ + int i; + if (b) { + for (i = 0; i < 10; i++) { + if (undo_history[i].data) + *b = (*b) + undo_history[i].rows; + } + } + if (a) { + if (clipboard.data) (*a) = (*a) + clipboard.rows; + if (fast_save.data) (*a) = (*a) + fast_save.rows; + } +} + + + +/* --------------------------------------------------------------------- */ +/* block selection handling */ + +/* *INDENT-OFF* */ +static struct { + int first_channel; + int last_channel; + int first_row; + int last_row; +} selection = { 0, 0, 0, 0 }; + +static struct { + int in_progress; + int first_channel; + int first_row; +} shift_selection = { 0, 0, 0 }; + +/* *INDENT-ON* */ + +/* set to 1 if the last movement key was shifted */ +int previous_shift = 0; + +/* this is set to 1 on the first alt-d selection, + * and shifted left on each successive press. */ +static int block_double_size; + +/* if first_channel is zero, there's no selection, as the channel + * numbers start with one. (same deal for last_channel, but i'm only + * caring about one of them to be efficient.) */ +#define SELECTION_EXISTS (selection.first_channel) + +/* --------------------------------------------------------------------- */ +/* this is for the multiple track views stuff. */ + +struct track_view { + int width; + draw_channel_header_func draw_channel_header; + draw_note_func draw_note; +}; + +static const struct track_view track_views[] = { +#define TRACK_VIEW(n) {n, draw_channel_header_##n, draw_note_##n} + TRACK_VIEW(13), /* 5 channels */ + TRACK_VIEW(10), /* 6/7 channels */ + TRACK_VIEW(7), /* 9/10 channels */ + TRACK_VIEW(6), /* 10/12 channels */ + TRACK_VIEW(3), /* 18/24 channels */ + TRACK_VIEW(2), /* 24/36 channels */ + TRACK_VIEW(1), /* 36/64 channels */ +#undef TRACK_VIEW +}; + +#define NUM_TRACK_VIEWS ARRAY_SIZE(track_views) + +static byte track_view_scheme[64]; +static int *channel_multi_base = 0; +static int channel_multi[64]; +static int channel_quick[64]; +static int visible_channels, visible_width; + +static void recalculate_visible_area(void); +static void set_view_scheme(int scheme); +static void pattern_editor_reposition(void); + +/* --------------------------------------------------------------------------------------------------------- */ +/* options dialog */ + +static struct widget options_widgets[8]; +static int options_link_split[] = { 5, 6, -1 }; +static int options_selected_widget = 0; + +static void options_close(UNUSED void *data) +{ + int old_size, new_size; + + options_selected_widget = *selected_widget; + + skip_value = options_widgets[1].d.thumbbar.value; + row_highlight_minor = options_widgets[2].d.thumbbar.value; + row_highlight_major = options_widgets[3].d.thumbbar.value; + link_effect_column = !!(options_widgets[5].d.togglebutton.state); + status.flags |= SONG_NEEDS_SAVE; + + old_size = song_get_pattern(current_pattern, NULL); + new_size = options_widgets[4].d.thumbbar.value; + if (old_size != new_size) { + song_pattern_resize(current_pattern, new_size); + current_row = MIN(current_row, new_size - 1); + pattern_editor_reposition(); + } +} + +static struct widget template_error_widgets[1]; +static void template_error_draw(void) +{ + draw_text((const unsigned char *)"Template Error", 33, 25, 0, 2); + draw_text((const unsigned char *)"No note in the top left position", 23, 27, 0, 2); + draw_text((const unsigned char *)"of the clipboard on which to", 25, 28, 0, 2); + draw_text((const unsigned char *)"base translations.", 31, 29, 0, 2); +/* + Template Error +No note in the top left position + of the clipboard on which to + base translations. +*/ + +} + +static void options_draw_const(void) +{ + draw_text((const unsigned char *)"Pattern Editor Options", 28, 19, 0, 2); + draw_text((const unsigned char *)"Base octave", 28, 23, 0, 2); + draw_text((const unsigned char *)"Cursor step", 28, 26, 0, 2); + draw_text((const unsigned char *)"Row hilight minor", 22, 29, 0, 2); + draw_text((const unsigned char *)"Row hilight major", 22, 32, 0, 2); + draw_text((const unsigned char *)"Number of rows in pattern", 14, 35, 0, 2); + draw_text((const unsigned char *)"Command/Value columns", 18, 38, 0, 2); + + draw_box(39, 22, 42, 24, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(39, 25, 43, 27, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(39, 28, 45, 30, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(39, 31, 57, 33, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(39, 34, 62, 36, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static int options_handle_key(struct key_event *k) +{ + if (NO_MODIFIER(k->mod) && k->sym == SDLK_F2) { + if (k->state) dialog_cancel(NULL); + return 1; + } + return 0; +} + +static void options_change_base_octave(void) +{ + kbd_set_current_octave(options_widgets[0].d.thumbbar.value); +} + +/* the base octave is changed directly when the thumbbar is changed. + * anything else can wait until the dialog is closed. */ +void pattern_editor_display_options(void) +{ + struct dialog *dialog; + + create_thumbbar(options_widgets + 0, 40, 23, 2, 7, 1, 1, options_change_base_octave, 0, 8); + create_thumbbar(options_widgets + 1, 40, 26, 3, 0, 2, 2, NULL, 0, 16); + create_thumbbar(options_widgets + 2, 40, 29, 5, 1, 3, 3, NULL, 0, 32); + create_thumbbar(options_widgets + 3, 40, 32, 17, 2, 4, 4, NULL, 0, 128); + create_thumbbar(options_widgets + 4, 40, 35, 22, 3, 5, 5, NULL, 32, 200); + create_togglebutton(options_widgets + 5, 40, 38, 8, 4, 7, 6, 6, 6, + NULL, "Link", 3, options_link_split); + create_togglebutton(options_widgets + 6, 52, 38, 9, 4, 7, 5, 5, 5, + NULL, "Split", 3, options_link_split); + create_button(options_widgets + 7, 35, 41, 8, 5, 0, 7, 7, 7, dialog_yes_NULL, "Done", 3); + + options_widgets[0].d.thumbbar.value = kbd_get_current_octave(); + options_widgets[1].d.thumbbar.value = skip_value; + options_widgets[2].d.thumbbar.value = row_highlight_minor; + options_widgets[3].d.thumbbar.value = row_highlight_major; + options_widgets[4].d.thumbbar.value = song_get_pattern(current_pattern, NULL); + togglebutton_set(options_widgets, link_effect_column ? 5 : 6, 0); + + dialog = dialog_create_custom(10, 18, 60, 26, options_widgets, 8, options_selected_widget, + options_draw_const, NULL); + dialog->action_yes = options_close; + dialog->action_cancel = options_close; + dialog->handle_key = options_handle_key; +} +/* --------------------------------------------------------------------------------------------------------- */ +/* pattern length dialog */ +static struct widget length_edit_widgets[4]; +static void length_edit_draw_const(void) +{ + draw_box(33,23,56,25, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(33,26,60,29, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_text((const unsigned char *)"Set Pattern Length", 31, 21, 0, 2); + draw_text((const unsigned char *)"Pattern Length", 19, 24, 0, 2); + draw_text((const unsigned char *)"Start Pattern", 20, 27, 0, 2); + draw_text((const unsigned char *)"End Pattern", 22, 28, 0, 2); +} +static void length_edit_close(UNUSED void *data) +{ + int i, nl; + nl = length_edit_widgets[0].d.thumbbar.value; + status.flags |= SONG_NEEDS_SAVE; + for (i = length_edit_widgets[1].d.thumbbar.value; + i <= length_edit_widgets[2].d.thumbbar.value; i++) { + if (song_get_pattern(i, 0) != nl) { + song_pattern_resize(i, nl); + if (i == current_pattern) { + status.flags |= NEED_UPDATE; + current_row = MIN(current_row, nl - 1); + pattern_editor_reposition(); + } + } + } +} +static void length_edit_cancel(UNUSED void *data) +{ + /* do nothing */ +} +void pattern_editor_length_edit(void) +{ + struct dialog *dialog; + int i; + + create_thumbbar(length_edit_widgets + 0, 34, 24, 22, 0, 1, 1, NULL, 32, 200); + length_edit_widgets[0].d.thumbbar.value = song_get_pattern(current_pattern, 0); + create_thumbbar(length_edit_widgets + 1, 34, 27, 26, 0, 2, 2, NULL, 0, 199); + create_thumbbar(length_edit_widgets + 2, 34, 28, 26, 1, 3, 3, NULL, 0, 199); + length_edit_widgets[1].d.thumbbar.value + = length_edit_widgets[2].d.thumbbar.value + = current_pattern; + + create_button(length_edit_widgets + 3, + 35,31,8, + 2, + 3, + 3, + 3, + 0, + dialog_yes_NULL, "OK", 4); + + dialog = dialog_create_custom(15, 19, 51, 15, length_edit_widgets, 4, 0, + length_edit_draw_const, NULL); + dialog->action_yes = length_edit_close; + dialog->action_cancel = length_edit_cancel; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* multichannel dialog */ +static struct widget multichannel_widgets[65]; +static void multichannel_close(UNUSED void *data) +{ + int i, cnt = 0; + for (i = 0; i < 64; i++) { + channel_multi[i] = multichannel_widgets[i].d.toggle.state + ? 1 : 0; + if (channel_multi[i]) cnt++; + } + if (cnt) { + channel_multi_base = channel_multi; + } else { + channel_multi_base = NULL; + } +} +static int multichannel_handle_key(struct key_event *k) +{ + if (!k->state) return 0; + if (NO_MODIFIER(k->mod) && k->sym == SDLK_n) { + dialog_cancel(NULL); + return 1; + } + return 0; +} +static void multichannel_draw_const(void) +{ + unsigned char sbuf[16]; + int i; + + for (i = 0; i < 64; i++) { + sprintf(sbuf, "Channel %02d", i+1); + draw_text(sbuf, + 9 + ((i / 16) * 16), /* X */ + 22 + (i % 16), /* Y */ + 0, 2); + } + for (i = 0; i < 64; i += 16) { + draw_box( + 19 + ((i / 16) * 16), /* X */ + 21, + 23 + ((i / 16) * 16), /* X */ + 38, + BOX_THIN|BOX_INNER|BOX_INSET); + } + draw_text((const unsigned char *)"Multichannel Selection", 28, 19, 3, 2); +} +static void mp_advance_channel(void) +{ + change_focus_to(ACTIVE_WIDGET.next.tab); +} +void pattern_editor_display_multichannel(void) +{ + struct dialog *dialog; + int i; + + for (i = 0; i < 64; i++) { + create_toggle(multichannel_widgets+i, + 20 + ((i / 16) * 16), /* X */ + 22 + (i % 16), /* Y */ + + ((i % 16) == 0) ? 64 : (i-1), + ((i % 16) == 15) ? 64 : (i+1), + (i < 16) ? (i+48) : (i-16), + ((i + 16) % 64), + i+1, + + mp_advance_channel); + multichannel_widgets[i].d.toggle.state = channel_multi[i] & 1; + } + create_button(multichannel_widgets+64, + 35,40,8, + 15, + 0, + 63, + 15, + 0, + dialog_yes_NULL, "OK", 4); + + dialog = dialog_create_custom(7, 18, 66, 25, multichannel_widgets, 65, 0, + multichannel_draw_const, NULL); + dialog->action_yes = multichannel_close; + dialog->action_cancel = multichannel_close; + dialog->handle_key = multichannel_handle_key; +} +/* --------------------------------------------------------------------------------------------------------- */ +void pattern_selection_system_hook(void) +{ + char *str; + int x, y, z, len; + song_note *pattern, *cur_note; + + + if (!(SELECTION_EXISTS)) { + if (clippy_owner(CLIPPY_SELECT) == widgets_pattern) { + /* unselect if we don't have a selection */ + clippy_select(0,0,0); + } + return; + } + + len = 0; + for (y = selection.first_row; y <= selection.last_row; y++) { + for (x = selection.first_channel; x <= selection.last_channel; x++) { + /* must match template below */ + len += 3+1+ 2+1+ 2+1 +3 + 4; + } + len += 2; + } + str = mem_alloc(len+1); + len = 0; + song_get_pattern(current_pattern, &pattern); + for (y = selection.first_row; y <= selection.last_row; y++) { + cur_note = pattern + 64 * y + + selection.first_channel - 1; + for (x = selection.first_channel; x <= selection.last_channel; x++) { + if (cur_note->note == 0) { + str[len] = str[len+1] = str[len+2] = '.'; + } else if (cur_note->note == NOTE_CUT) { + str[len] = str[len+1] = str[len+2] = '^'; + } else if (cur_note->note == NOTE_OFF) { + str[len] = str[len+1] = str[len+2] = '='; + } else if (cur_note->note == NOTE_FADE) { + str[len] = str[len+1] = str[len+2] = '~'; + } else { + get_note_string(cur_note->note, str+len); + } + len += 3; + sprintf(str+len, " %02d%4s%c%02x%s", + cur_note->instrument, + ((cur_note->volume_effect == VOL_EFFECT_VOLUME) + ? "[..]" : " .. "), + get_effect_char(cur_note->effect), + cur_note->parameter, + ((x == selection.last_channel) + ? "\r\n" : " | ")); + if (!cur_note->instrument) { + str[len+1] = '.'; + str[len+2] = '.'; + } + z = str[len+6]; + get_volume_string(cur_note->volume, + cur_note->volume_effect, + str+len+4); + str[len+6] = z; + len += 13; + if (x == selection.last_channel) len--; + cur_note++; + } + } + str[len] = 0; + clippy_select(widgets_pattern, str, len); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* undo dialog */ + +static struct widget undo_widgets[1]; +static int undo_selection = 0; + +static void history_draw_const(void) +{ + int i, j; + int fg, bg; + draw_text((const unsigned char *)"Undo", 38, 22, 3, 2); + draw_box(19,23,60,34, BOX_THIN | BOX_INNER | BOX_INSET); + j = undo_history_top; + for (i = 0; i < 10; i++) { + if (i == undo_selection) { + fg = 0; bg = 3; + } else { + fg = 2; bg = 0; + } + + draw_char(32, 20, 24+i, fg, bg); + draw_text_len(undo_history[j].snap_op, 39, 21, 24+i, fg, bg); + j--; + if (j < 0) j += 10; + } +} + +static void history_close(UNUSED void *data) +{ + /* nothing! */ +} + +static int history_handle_key(struct key_event *k) +{ + int i,j; + if (! NO_MODIFIER(k->mod)) return 0; + switch (k->sym) { + case SDLK_ESCAPE: + if (!k->state) return 0; + dialog_cancel(NULL); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_UP: + if (!k->state) return 0; + undo_selection--; + if (undo_selection < 0) undo_selection = 0; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_DOWN: + if (!k->state) return 0; + undo_selection++; + if (undo_selection > 9) undo_selection = 9; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_RETURN: + if (k->state) return 0; + j = undo_history_top; + for (i = 0; i < 10; i++) { + if (i == undo_selection) { + pated_history_restore(j); + break; + } + j--; + if (j < 0) j += 10; + } + dialog_cancel(NULL); + status.flags |= NEED_UPDATE; + return 1; + }; + + return 0; +} + +void pattern_editor_display_history(void) +{ + struct dialog *dialog; + + create_other(undo_widgets + 0, 0, history_handle_key, NULL); + dialog = dialog_create_custom(17, 21, 47, 16, undo_widgets, 1, 0, + history_draw_const, NULL); + dialog->action_yes = history_close; + dialog->action_cancel = history_close; + dialog->handle_key = history_handle_key; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* volume fiddling */ + +static void selection_amplify(int percentage); +static void selection_vary(int fast, int depth, int part); + +/* --------------------------------------------------------------------------------------------------------- */ +/* volume amplify/attenuate and fast volume setup handlers */ + +/* this is shared by the fast and normal volume dialogs */ +static struct widget volume_setup_widgets[3]; + +static void fast_volume_setup_ok(UNUSED void *data) +{ + fast_volume_percent = volume_setup_widgets[0].d.thumbbar.value; + fast_volume_mode = 1; + status_text_flash("Alt-I / Alt-J fast volume changes enabled"); +} + +static void fast_volume_setup_cancel(UNUSED void *data) +{ + status_text_flash("Alt-I / Alt-J fast volume changes not enabled"); +} + +static void fast_volume_setup_draw_const(void) +{ + draw_text("Volume Amplification %", 29, 27, 0, 2); + draw_box(32, 29, 44, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static void fast_volume_toggle(void) +{ + struct dialog *dialog; + + if (fast_volume_mode) { + fast_volume_mode = 0; + status_text_flash("Alt-I / Alt-J fast volume changes disabled"); + } else { + create_thumbbar(volume_setup_widgets + 0, 33, 30, 11, 0, 1, 1, NULL, 10, 90); + + volume_setup_widgets[0].d.thumbbar.value = fast_volume_percent; + create_button(volume_setup_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); + create_button(volume_setup_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + + dialog = dialog_create_custom(22, 25, 36, 11, volume_setup_widgets, + 3, 0, fast_volume_setup_draw_const, NULL); + dialog->action_yes = fast_volume_setup_ok; + dialog->action_cancel = fast_volume_setup_cancel; + } +} + +static void fast_volume_amplify(void) +{ + selection_amplify((100/fast_volume_percent)*100); +} + +static void fast_volume_attenuate(void) +{ + selection_amplify(fast_volume_percent); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* normal (not fast volume) amplify */ + +static void volume_setup_draw_const(void) +{ + draw_text("Volume Amplification %", 29, 27, 0, 2); + draw_box(25, 29, 52, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static void volume_amplify_ok(UNUSED void *data) +{ + volume_percent = volume_setup_widgets[0].d.thumbbar.value; + selection_amplify(volume_percent); +} + +static void volume_amplify(void) +{ + struct dialog *dialog; + + create_thumbbar(volume_setup_widgets + 0, 26, 30, 26, 0, 1, 1, NULL, 0, 200); + volume_setup_widgets[0].d.thumbbar.value = volume_percent; + create_button(volume_setup_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); + create_button(volume_setup_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(22, 25, 36, 11, volume_setup_widgets, 3, 0, volume_setup_draw_const, NULL); + dialog->action_yes = volume_amplify_ok; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* vary depth */ + +static void vary_setup_draw_const(void) +{ + draw_text("Vary depth limit %", 31, 27, 0, 2); + draw_box(25, 29, 52, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static void vary_amplify_ok(void *data) +{ + int hack = (int)data; + + vary_depth = volume_setup_widgets[0].d.thumbbar.value; + selection_vary(0, vary_depth, hack); +} + +static void vary_command(int how) +{ + struct dialog *dialog; + + create_thumbbar(volume_setup_widgets + 0, 26, 30, 26, 0, 1, 1, NULL, 0, 50); + volume_setup_widgets[0].d.thumbbar.value = vary_depth; + create_button(volume_setup_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); + create_button(volume_setup_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(22, 25, 36, 11, volume_setup_widgets, 3, 0, vary_setup_draw_const, (void*)how); + dialog->action_yes = vary_amplify_ok; +} + +static int current_effect(void) +{ + song_note *pattern, *cur_note; + + song_get_pattern(current_pattern, &pattern); + cur_note = pattern + 64 * current_row + current_channel - 1; + + return cur_note->effect; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* settings */ + +#define CFG_SET_PE(v) cfg_set_number(cfg, "Pattern Editor", #v, v) +void cfg_save_patedit(cfg_file_t *cfg) +{ + int n; + char s[65]; + + CFG_SET_PE(link_effect_column); + CFG_SET_PE(draw_divisions); + CFG_SET_PE(centralise_cursor); + CFG_SET_PE(highlight_current_row); + CFG_SET_PE(edit_copy_mask); + CFG_SET_PE(volume_percent); + CFG_SET_PE(fast_volume_percent); + CFG_SET_PE(fast_volume_mode); + for (n = 0; n < 64; n++) + s[n] = track_view_scheme[n] + 'a'; + s[64] = 0; + + cfg_set_string(cfg, "Pattern Editor", "track_view_scheme", s); + for (n = 0; n < 64; n++) + s[n] = (channel_multi[n] & 1) ? 'M' : '-'; + s[64] = 0; + cfg_set_string(cfg, "Pattern Editor", "channel_multi", s); +} + +#define CFG_GET_PE(v,d) v = cfg_get_number(cfg, "Pattern Editor", #v, d) +void cfg_load_patedit(cfg_file_t *cfg) +{ + int n, r = 0; + byte s[65]; + + CFG_GET_PE(link_effect_column, 0); + CFG_GET_PE(draw_divisions, 1); + CFG_GET_PE(centralise_cursor, 0); + CFG_GET_PE(highlight_current_row, 0); + CFG_GET_PE(edit_copy_mask, MASK_NOTE | MASK_INSTRUMENT | MASK_VOLUME); + CFG_GET_PE(volume_percent, 100); + CFG_GET_PE(fast_volume_percent, 67); + CFG_GET_PE(fast_volume_mode, 0); + cfg_get_string(cfg, "Pattern Editor", "track_view_scheme", s, 65, "a"); + + /* "decode" the track view scheme */ + for (n = 0; n < 64; n++) { + if (s[n] == '\0') { + /* end of the string */ + break; + } else if (s[n] >= 'a' && s[n] <= 'z') { + s[n] -= 'a'; + } else if (s[n] >= 'A' && s[n] <= 'Z') { + s[n] -= 'A'; + } else { + log_appendf(4, "Track view scheme corrupted; using default"); + n = 64; + r = 0; + break; + } + r = s[n]; + } + memcpy(track_view_scheme, s, n); + if (n < 64) + memset(track_view_scheme + n, r, 64 - n); + + cfg_get_string(cfg, "Pattern Editor", "channel_multi", s, 65, ""); + memset(channel_multi, 0, sizeof(channel_multi)); + channel_multi_base = NULL; + for (n = 0; n < 64; n++) { + if (!s[n]) break; + channel_multi[n] = ((s[n] >= 'A' && s[n] <= 'Z') || (s[n] >= 'a' && s[n] <= 'z')) ? 1 : 0; + if (channel_multi[n] && !channel_multi_base) { + channel_multi_base = channel_multi; + } + } + + recalculate_visible_area(); + pattern_editor_reposition(); + if (status.current_page == PAGE_PATTERN_EDITOR) + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* selection handling functions */ + +static inline int is_in_selection(int chan, int row) +{ + return (SELECTION_EXISTS + && chan >= selection.first_channel && chan <= selection.last_channel + && row >= selection.first_row && row <= selection.last_row); +} + +static void normalise_block_selection(void) +{ + int n; + + if (!SELECTION_EXISTS) + return; + + if (selection.first_channel > selection.last_channel) { + n = selection.first_channel; + selection.first_channel = selection.last_channel; + selection.last_channel = n; + } + + if (selection.first_row < 0) selection.first_row = 0; + if (selection.last_row < 0) selection.last_row = 0; + if (selection.first_channel < 1) selection.first_channel = 1; + if (selection.last_channel < 1) selection.last_channel = 1; + + if (selection.first_row > selection.last_row) { + n = selection.first_row; + selection.first_row = selection.last_row; + selection.last_row = n; + } +} + +static void shift_selection_begin(void) +{ + shift_selection.in_progress = 1; + shift_selection.first_channel = current_channel; + shift_selection.first_row = current_row; +} + +static void shift_selection_update(void) +{ + if (shift_selection.in_progress) { + selection.first_channel = shift_selection.first_channel; + selection.last_channel = current_channel; + selection.first_row = shift_selection.first_row; + selection.last_row = current_row; + normalise_block_selection(); + } +} + +static void shift_selection_end(void) +{ + shift_selection.in_progress = 0; + pattern_selection_system_hook(); +} + +static void selection_clear(void) +{ + selection.first_channel = 0; + pattern_selection_system_hook(); +} +static void block_length_double(void) +{ + song_note *pattern, *w, *r; + int i, j, x, row; + int total_rows; + int chan_width; + + if (!SELECTION_EXISTS) + return; + + status.flags |= SONG_NEEDS_SAVE; + total_rows = song_get_pattern(current_pattern, &pattern); + row = (selection.last_row - selection.first_row) + 1; + row *= 2; + if (row + selection.first_row > total_rows) { + row = (total_rows - selection.first_row) + 1; + } + + pated_history_add("Undo block length double (Alt-G)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + row); + + row = (selection.last_row - selection.first_row) + 1; + for (i = selection.last_row - 1; i > selection.first_row;) { + j = ((i - selection.first_row) / 2) + selection.first_row; + w = pattern + 64 * i + selection.first_channel - 1; + r = pattern + 64 * j + selection.first_channel - 1; + + for (x = selection.first_channel; x <= selection.last_channel; x++, r++, w++) { + memcpy(w, r, sizeof(song_note)); + } + i--; + w = pattern + 64 * i + selection.first_channel - 1; + for (x = selection.first_channel; x <= selection.last_channel; x++, w++) { + memset(w, 0, sizeof(song_note)); + } + i--; + } + pattern_selection_system_hook(); +} +static void block_length_halve(void) +{ + song_note *pattern, *w, *r; + int i, j, x; + int chan_width; + + if (!SELECTION_EXISTS) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo block length halve (Alt-G)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + j = selection.first_row + 1; + for (i = selection.first_row + 2; i <= selection.last_row; i += 2, j++) { + w = pattern + 64 * j + selection.first_channel - 1; + r = pattern + 64 * i + selection.first_channel - 1; + for (x = selection.first_channel; x <= selection.last_channel; x++, r++, w++) { + memcpy(w, r, sizeof(song_note)); + } + } + for (; j <= selection.last_row; j++) { + w = pattern + 64 * j + selection.first_channel - 1; + memset(w, 0, sizeof(song_note)); + } + pattern_selection_system_hook(); +} +static void selection_erase(void) +{ + song_note *pattern, *note; + int row; + int chan_width; + + if (!SELECTION_EXISTS) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo block cut (Alt-Z)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + if (selection.first_channel == 1 && selection.last_channel == 64) { + memset(pattern + 64 * selection.first_row, 0, (selection.last_row - selection.first_row + 1) + * 64 * sizeof(song_note)); + } else { + chan_width = selection.last_channel - selection.first_channel + 1; + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + memset(note, 0, chan_width * sizeof(song_note)); + } + } + pattern_selection_system_hook(); +} + +static void selection_set_sample(void) +{ + int row, chan; + song_note *pattern, *note; + + song_get_pattern(current_pattern, &pattern); + + status.flags |= SONG_NEEDS_SAVE; + pated_history_add("Undo set sample/instrument (Alt-S)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + if (SELECTION_EXISTS) { + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + if (note->instrument) { + note->instrument = song_get_current_instrument(); + } + } + } + } else { + note = pattern + 64 * current_row + current_channel - 1; + if (note->instrument) { + note->instrument = song_get_current_instrument(); + } + } + pattern_selection_system_hook(); +} + +/* CHECK_FOR_SELECTION(optional return value) +will display an error dialog and cause the function to return if there is no block marked. +(this dialog should be a column wider, with the extra column on the left side) */ +#define CHECK_FOR_SELECTION(q) do {\ + if (!SELECTION_EXISTS) {\ + dialog_create(DIALOG_OK, "No block is marked", NULL, NULL, 0, NULL);\ + q;\ + }\ +} while(0) + + +static void selection_swap(void) +{ + /* s_note = selection; p_note = position */ + song_note *pattern, *s_note, *p_note, tmp; + int row, chan, num_rows, num_chans, total_rows; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + total_rows = song_get_pattern(current_pattern, &pattern); + num_rows = selection.last_row - selection.first_row + 1; + num_chans = selection.last_channel - selection.first_channel + 1; + + if (current_row + num_rows > total_rows || current_channel + num_chans - 1 > 64) { + /* should be one column wider (see note for CHECK_SELECTION_EXISTS) */ + dialog_create(DIALOG_OK, "Out of pattern range", NULL, NULL, 0, NULL); + return; + } + + /* The minimum combined size for the two blocks is double the number of rows in the selection by + * double the number of channels. So, if the width and height don't add up, they must overlap. It's + * of course possible to have the blocks adjacent but not overlapping -- there is only overlap if + * *both* the width and height are less than double the size. */ + if ((MAX(selection.last_channel, current_channel + num_chans - 1) + - MIN(selection.first_channel, current_channel) + 1) < 2 * num_chans + && (MAX(selection.last_row, current_row + num_rows - 1) + - MIN(selection.first_row, current_row) + 1) < 2 * num_rows) { + /* one column wider; the text should be shifted a column left as well */ + dialog_create(DIALOG_OK, "Swap blocks overlap", NULL, NULL, 0, NULL); + return; + } + + for (row = 0; row < num_rows; row++) { + s_note = pattern + 64 * (selection.first_row + row) + selection.first_channel - 1; + p_note = pattern + 64 * (current_row + row) + current_channel - 1; + for (chan = 0; chan < num_chans; chan++, s_note++, p_note++) { + tmp = *s_note; + *s_note = *p_note; + *p_note = tmp; + } + } + pattern_selection_system_hook(); +} + +static void selection_set_volume(void) +{ + int row, chan; + song_note *pattern, *note; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo set volume/panning (Alt-V)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + note->volume = mask_note.volume; + note->volume_effect = mask_note.volume_effect; + } + } + pattern_selection_system_hook(); +} + +/* The logic for this one makes my head hurt. */ +static void selection_slide_volume(void) +{ + int row, chan; + song_note *pattern, *note, *last_note; + int first, last; /* the volumes */ + int ve, lve; /* volume effect */ + + /* FIXME: if there's no selection, should this display a dialog, or bail silently? */ + CHECK_FOR_SELECTION(return); + + /* can't slide one row */ + if (selection.first_row == selection.last_row) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo volume or panning slide (Alt-K)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + /* the channel loop has to go on the outside for this one */ + for (chan = selection.first_channel; chan <= selection.last_channel; chan++) { + note = pattern + 64 * selection.first_row + chan - 1; + last_note = pattern + 64 * selection.last_row + chan - 1; + + /* valid combinations: + * [ volume - volume ] + * [panning - panning] + * [ volume - none ] \ only valid if the 'none' + * [ none - volume ] / note has a sample number + * in any other case, no slide occurs. */ + + ve = note->volume_effect; + lve = last_note->volume_effect; + + first = note->volume; + last = last_note->volume; + + /* Note: IT only uses the sample's default volume if there is an instrument number *AND* a + note. I'm just checking the instrument number, as it's the minimal information needed to + get the default volume for the instrument. + + Would be nice but way hard to do: if there's a note but no sample number, look back in the + pattern and use the last sample number in that channel (if there is one). */ + if (ve == VOL_EFFECT_NONE) { + if (note->instrument == 0) + continue; + ve = VOL_EFFECT_VOLUME; + /* Modplug hack: volume bit shift */ + first = song_get_sample(note->instrument, NULL)->volume >> 2; + } + + if (lve == VOL_EFFECT_NONE) { + if (last_note->instrument == 0) + continue; + lve = VOL_EFFECT_VOLUME; + last = song_get_sample(last_note->instrument, NULL)->volume >> 2; + } + + if (!(ve == lve && (ve == VOL_EFFECT_VOLUME || ve == VOL_EFFECT_PANNING))) { + continue; + } + + for (row = selection.first_row; row <= selection.last_row; row++, note += 64) { + note->volume_effect = ve; + note->volume = (((last - first) + * (row - selection.first_row) + / (selection.last_row - selection.first_row) + ) + first); + } + } + pattern_selection_system_hook(); +} + +static void selection_wipe_volume(int reckless) +{ + int row, chan; + song_note *pattern, *note; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add((reckless + ? "Recover volumes/pannings (2*Alt-K)" + : "Replace extra volumes/pannings (Alt-W)"), + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + if (reckless || (note->note == 0 && note->instrument == 0)) { + note->volume = 0; + note->volume_effect = VOL_EFFECT_NONE; + } + } + } + pattern_selection_system_hook(); +} +static int vary_value(int ov, int limit, int depth) +{ + int j; + j = (int)((((float)limit)*rand()) / (RAND_MAX+1.0)); + j = ((limit >> 1) - j); + j = ov+((j * depth) / 100); + if (j < 0) j = 0; + if (j > limit) j = limit; + return j; +} +static int common_variable_group(char ch) +{ + switch (ch) { + case 'E': case 'F': case 'G': case 'L': + return 'G'; + case 'H': case 'K': + return 'H'; + case 'X': case 'P': case 'Y': + return 'X'; + default: + return ch; /* err... */ + }; +} +static int same_variable_group(char ch1, char ch2) +{ + /* k is in both G and H */ + if (ch1 == 'K' && ch2 == 'D') return 1; + if (ch2 == 'K' && ch1 == 'D') return 1; + + if (ch1 == 'L' && ch2 == 'D') return 1; + if (ch2 == 'L' && ch1 == 'D') return 1; + + if (common_variable_group(ch1) == common_variable_group(ch2)) + return 1; + return 0; +} +static void selection_vary(int fast, int depth, int how) +{ + int row, chan, volume; + song_note *pattern, *note; + static char last_very[39]; + char *vary_how, ch; + + /* don't ever vary these things */ + if (how == '?' || how == '.' + || how == 'S' || how == 'A' || how == 'B' || how == 'C') return; + if (how < 'A' || how > 'Z') return; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + switch (how) { + case 'M': + case 'N': + vary_how = "Undo volume-channel vary (Ctrl-U)"; + if (fast) status_text_flash("Fast volume vary"); + break; + case 'X': + case 'P': + case 'Y': + vary_how = "Undo panning vary (Ctrl-Y)"; + if (fast) status_text_flash("Fast panning vary"); + break; + default: + sprintf(last_very, "%-28s (Ctrl-K)", + "Undo Xxx effect-value vary"); + last_very[5] = common_variable_group(how); + if (fast) status_text_flash("Fast %-21s", last_very+5); + vary_how = last_very; + break; + }; + + song_get_pattern(current_pattern, &pattern); + + /* it says Alt-J even when Alt-I was used */ + pated_history_add(vary_how, + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + if (how == 'M' || how == 'N') { + if (note->volume_effect == VOL_EFFECT_VOLUME) { + note->volume = vary_value(note->volume, + 64, depth); + } + } + if (how == 'P' || how == 'X' || how == 'Y') { + if (note->volume_effect == VOL_EFFECT_PANNING) { + note->volume = vary_value(note->volume, + 64, depth); + } + } + + ch = get_effect_char(note->effect); + if (ch == '?' || ch == '.') continue; + if (!same_variable_group(ch, how)) continue; + switch (ch) { + /* these are .0 0. and .f f. values */ + case 'D': + case 'N': + case 'P': + case 'W': + if ((note->parameter & 15) == 15) continue; + if ((note->parameter & 0xF0) == (0xF0))continue; + if (note->parameter & 15 == 0) { + note->parameter = (1+(vary_value( + note->parameter>>4, + 15, depth))) << 4; + } else { + note->parameter = 1+(vary_value( + note->parameter & 15, + 15, depth)); + } + break; + /* tempo has a slide */ + case 'T': + if ((note->parameter & 15) == 15) continue; + if ((note->parameter & 0xF0) == (0xF0))continue; + /* but otherwise it's absolute */ + note->parameter = 1 + (vary_value( + note->parameter, + 255, depth)); + break; + /* don't vary .E. and .F. values */ + case 'E': + case 'F': + if ((note->parameter & 15) == 15) continue; + if ((note->parameter & 15) == 14) continue; + if ((note->parameter & 0xF0) == (0xF0))continue; + if ((note->parameter & 0xF0) == (0xE0))continue; + note->parameter = 16 + (vary_value( + note->parameter-16, + 224, depth)); + break; + /* these are all "xx" commands */ + case 'G': + case 'K': + case 'L': + case 'M': + case 'O': + case 'V': + case 'X': + note->parameter = 1 + (vary_value( + note->parameter, + 255, depth)); + break; + /* these are all "xy" commands */ + case 'H': + case 'I': + case 'J': + case 'Q': + case 'R': + case 'Y': + case 'U': + note->parameter = (1 + (vary_value( + note->parameter & 15, + 15, depth))) + | ((1 + (vary_value( + (note->parameter >> 4)& 15, + 15, depth))) << 4); + break; + }; + } + } + pattern_selection_system_hook(); +} +static void selection_amplify(int percentage) +{ + int row, chan, volume; + song_note *pattern, *note; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + /* it says Alt-J even when Alt-I was used */ + pated_history_add("Undo volume amplification (Alt-J)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + if (note->volume_effect == VOL_EFFECT_NONE && note->instrument != 0) { + /* Modplug hack: volume bit shift */ + if (song_is_instrument_mode()) + volume = 64; /* XXX */ + else + volume = song_get_sample(note->instrument, NULL)->volume >> 2; + } else if (note->volume_effect == VOL_EFFECT_VOLUME) { + volume = note->volume; + } else { + continue; + } + volume *= percentage; + volume /= 100; + if (volume > 64) volume = 64; + else if (volume < 0) volume = 0; + note->volume = volume; + note->volume_effect = VOL_EFFECT_VOLUME; + } + } + pattern_selection_system_hook(); +} + +static void selection_slide_effect(void) +{ + int row, chan; + song_note *pattern, *note; + int first, last; /* the effect values */ + + /* FIXME: if there's no selection, should this display a dialog, or bail silently? */ + CHECK_FOR_SELECTION(return); + + if (selection.first_row == selection.last_row) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo effect data slide (Alt-X)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + /* the channel loop has to go on the outside for this one */ + for (chan = selection.first_channel; chan <= selection.last_channel; chan++) { + note = pattern + chan - 1; + first = note[64 * selection.first_row].parameter; + last = note[64 * selection.last_row].parameter; + note += 64 * selection.first_row; + for (row = selection.first_row; row <= selection.last_row; row++, note += 64) { + note->parameter = (((last - first) + * (row - selection.first_row) + / (selection.last_row - selection.first_row) + ) + first); + } + } + pattern_selection_system_hook(); +} + +static void selection_wipe_effect(void) +{ + int row, chan; + song_note *pattern, *note; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Recover effects/effect data (2*Alt-X)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + note->effect = 0; + note->parameter = 0; + } + } + pattern_selection_system_hook(); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* Row shifting operations */ + +/* A couple of the parameter names here might seem a bit confusing, so: + * what_row = what row to start the insert (generally this would be current_row) + * num_rows = the number of rows to insert */ +static void pattern_insert_rows(int what_row, int num_rows, int first_channel, int chan_width) +{ + song_note *pattern; + int row, total_rows = song_get_pattern(current_pattern, &pattern); + + status.flags |= SONG_NEEDS_SAVE; + if (first_channel < 1) + first_channel = 1; + if (chan_width + first_channel - 1 > 64) + chan_width = 64 - first_channel + 1; + + if (num_rows + what_row > total_rows) + num_rows = total_rows - what_row; + + if (first_channel == 1 && chan_width == 64) { + memmove(pattern + 64 * (what_row + num_rows), pattern + 64 * what_row, + 64 * sizeof(song_note) * (total_rows - what_row - num_rows)); + memset(pattern + 64 * what_row, 0, num_rows * 64 * sizeof(song_note)); + } else { + /* shift the area down */ + for (row = total_rows - num_rows - 1; row >= what_row; row--) { + memmove(pattern + 64 * (row + num_rows) + first_channel - 1, + pattern + 64 * row + first_channel - 1, chan_width * sizeof(song_note)); + } + /* clear the inserted rows */ + for (row = what_row; row < what_row + num_rows; row++) { + memset(pattern + 64 * row + first_channel - 1, 0, chan_width * sizeof(song_note)); + } + } + pattern_selection_system_hook(); +} + +/* Same as above, but with a couple subtle differences. */ +static void pattern_delete_rows(int what_row, int num_rows, int first_channel, int chan_width) +{ + song_note *pattern; + int row, total_rows = song_get_pattern(current_pattern, &pattern); + + status.flags |= SONG_NEEDS_SAVE; + if (first_channel < 1) + first_channel = 1; + if (chan_width + first_channel - 1 > 64) + chan_width = 64 - first_channel + 1; + + if (num_rows + what_row > total_rows) + num_rows = total_rows - what_row; + + if (first_channel == 1 && chan_width == 64) { + memmove(pattern + 64 * what_row, pattern + 64 * (what_row + num_rows), + 64 * sizeof(song_note) * (total_rows - what_row - num_rows)); + memset(pattern + 64 * (total_rows - num_rows), 0, num_rows * 64 * sizeof(song_note)); + } else { + /* shift the area up */ + for (row = what_row; row <= total_rows - num_rows - 1; row++) { + memmove(pattern + 64 * row + first_channel - 1, + pattern + 64 * (row + num_rows) + first_channel - 1, + chan_width * sizeof(song_note)); + } + /* clear the last rows */ + for (row = total_rows - num_rows; row < total_rows; row++) { + memset(pattern + 64 * row + first_channel - 1, 0, chan_width * sizeof(song_note)); + } + } + pattern_selection_system_hook(); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* history/undo */ +static void pated_history_clear(void) +{ + int i; + for (i = 0; i < 10; i++) { + if (undo_history[i].freesnapop) + free(undo_history[i].snap_op); + free(undo_history[i].data); + + memset(&undo_history[i],0,sizeof(struct pattern_snap)); + undo_history[i].snap_op = (const unsigned char *)"Empty"; + undo_history[i].freesnapop = 0; + } +} +static void snap_paste(struct pattern_snap *s, int x, int y, int xlate) +{ + song_note *pattern, *p_note; + int row, num_rows, chan_width; + int chan; + + + status.flags |= SONG_NEEDS_SAVE; + if (x < 0) x = s->x; + if (y < 0) y = s->y; + + num_rows = song_get_pattern(current_pattern, &pattern); + num_rows -= y; + if (s->rows < num_rows) + num_rows = s->rows; + + chan_width = s->channels; + if (chan_width + x >= 64) + chan_width = 64 - x; + + for (row = 0; row < num_rows; row++) { + p_note = pattern + 64 * (y + row) + x; + memcpy(pattern + 64 * (y + row) + x, + s->data + s->channels * row, chan_width * sizeof(song_note)); + if (!xlate) continue; + for (chan = 0; chan < chan_width; chan++) { + if (chan + x > 64) break; /* defensive */ + if (p_note[chan].note) { + p_note[chan].note += xlate; + if (p_note[chan].note < 0 || p_note[chan].note > 120) + p_note[chan].note = 0; + } + } + } + pattern_selection_system_hook(); +} + +static void snap_copy(struct pattern_snap *s, int x, int y, int width, int height) +{ + song_note *pattern; + int row; + + memused_songchanged(); + s->channels = width; + s->rows = height; + + s->data = mem_alloc(sizeof(song_note) * s->channels * s->rows); + + song_get_pattern(current_pattern, &pattern); + + s->x = x; s->y = y; + if (x == 0 && width == 64) { + memcpy(s->data, pattern + 64 * y, (width*height*sizeof(song_note))); + } else { + for (row = 0; row < s->rows; row++) { + memcpy(s->data + s->channels * row, + pattern + 64 * (row + s->y) + s->x, + s->channels * sizeof(song_note)); + } + } +} + +static void pated_history_restore(int n) +{ + if (n < 0 || n > 9) return; + snap_paste(&undo_history[n], -1, -1, 0); + +} + +static void pated_history_add(const char *descr, int x, int y, int width, int height) +{ + int j; + + j = (undo_history_top + 1) % 10; + free(undo_history[j].data); + snap_copy(&undo_history[j], x, y, width, height); + undo_history[j].snap_op = mem_alloc(strlen(descr)+1); + strcpy(undo_history[j].snap_op, descr); + undo_history[j].freesnapop = 1; + undo_history_top = j; +} +static void fast_save_update(void) +{ + int total_rows; + + free(fast_save.data); + fast_save.data = NULL; + + total_rows = song_get_pattern(current_pattern, NULL); + + snap_copy(&fast_save, 0, 0, 64, total_rows); +} + +/* clipboard */ +static void clipboard_free(void) +{ + free(clipboard.data); + clipboard.data = NULL; +} + +/* clipboard_copy is fundementally the same as selection_erase + * except it uses memcpy instead of memset :) */ +static void clipboard_copy(void) +{ + CHECK_FOR_SELECTION(return); + + clipboard_free(); + + snap_copy(&clipboard, + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + /* transfer to system where appropriate */ + clippy_yank(); +} + +static void clipboard_paste_overwrite(int suppress) +{ + song_note *pattern; + int row, num_rows, chan_width; + + if (clipboard.data == NULL) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + return; + } + + num_rows = song_get_pattern(current_pattern, &pattern); + num_rows -= current_row; + if (clipboard.rows < num_rows) + num_rows = clipboard.rows; + + chan_width = clipboard.channels; + if (chan_width + current_channel > 64) + chan_width = 64 - current_channel + 1; + + if (!suppress) { + pated_history_add("Replace overwritten data (Alt-O)", + current_channel-1, current_row, + chan_width, num_rows); + } + snap_paste(&clipboard, current_channel-1, current_row, 0); +} +static void clipboard_paste_insert(void) +{ + int num_rows, total_rows, chan_width; + song_note *pattern; + + if (clipboard.data == NULL) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + return; + } + + total_rows = song_get_pattern(current_pattern, &pattern); + + /* it's easier this way... */ + pated_history_add("Undo paste data (Alt-P)", + 0,0,64,total_rows); + + num_rows = total_rows - current_row; + if (clipboard.rows < num_rows) + num_rows = clipboard.rows; + + chan_width = clipboard.channels; + if (chan_width + current_channel > 64) + chan_width = 64 - current_channel + 1; + + pattern_insert_rows(current_row, clipboard.rows, current_channel, chan_width); + clipboard_paste_overwrite(1); + pattern_selection_system_hook(); +} + +static void clipboard_paste_mix_notes(int xlate) +{ + int row, chan, num_rows, chan_width; + song_note *pattern, *p_note, *c_note; + + if (clipboard.data == NULL) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + return; + } + + status.flags |= SONG_NEEDS_SAVE; + num_rows = song_get_pattern(current_pattern, &pattern); + num_rows -= current_row; + if (clipboard.rows < num_rows) + num_rows = clipboard.rows; + + chan_width = clipboard.channels; + if (chan_width + current_channel > 64) + chan_width = 64 - current_channel + 1; + + +/* note that IT doesn't do this for "fields" either... */ + pated_history_add("Replace mixed data (Alt-M)", + current_channel-1, current_row, + chan_width, num_rows); + + p_note = pattern + 64 * current_row + current_channel - 1; + c_note = clipboard.data; + for (row = 0; row < num_rows; row++) { + for (chan = 0; chan < chan_width; chan++) { + if (memcmp(p_note + chan, &empty_note, sizeof(song_note)) == 0) { + p_note[chan] = c_note[chan]; + if (p_note[chan].note) { + p_note[chan].note += xlate; + if (p_note[chan].note < 0 || p_note[chan].note > 120) + p_note[chan].note = 0; + } + } + } + p_note += 64; + c_note += clipboard.channels; + } +} + +/* Same code as above. Maybe I should generalize it. */ +static void clipboard_paste_mix_fields(int prec, int xlate) +{ + int row, chan, num_rows, chan_width; + song_note *pattern, *p_note, *c_note; + + if (clipboard.data == NULL) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + return; + } + + status.flags |= SONG_NEEDS_SAVE; + num_rows = song_get_pattern(current_pattern, &pattern); + num_rows -= current_row; + if (clipboard.rows < num_rows) + num_rows = clipboard.rows; + + chan_width = clipboard.channels; + if (chan_width + current_channel > 64) + chan_width = 64 - current_channel + 1; + + p_note = pattern + 64 * current_row + current_channel - 1; + c_note = clipboard.data; + for (row = 0; row < num_rows; row++) { + for (chan = 0; chan < chan_width; chan++) { + /* Ick. There ought to be a "conditional move" operator. */ + if (prec) { + /* clipboard precidence */ + if (c_note[chan].note != 0) { + p_note[chan].note = c_note[chan].note; + if (p_note[chan].note) p_note[chan].note += xlate; + if (p_note[chan].note < 0 || p_note[chan].note > 120) + p_note[chan].note = 0; + } + if (c_note[chan].instrument != 0) + p_note[chan].instrument = c_note[chan].instrument; + if (c_note[chan].volume_effect != VOL_EFFECT_NONE) { + p_note[chan].volume_effect = c_note[chan].volume_effect; + p_note[chan].volume = c_note[chan].volume; + } + if (c_note[chan].effect != 0) { + p_note[chan].effect = c_note[chan].effect; + } + if (c_note[chan].parameter != 0) + p_note[chan].parameter = c_note[chan].parameter; + } else { + if (p_note[chan].note == 0) { + p_note[chan].note = c_note[chan].note; + if (p_note[chan].note) p_note[chan].note += xlate; + if (p_note[chan].note < 0 || p_note[chan].note > 120) + p_note[chan].note = 0; + } + if (p_note[chan].instrument == 0) + p_note[chan].instrument = c_note[chan].instrument; + if (p_note[chan].volume_effect == VOL_EFFECT_NONE) { + p_note[chan].volume_effect = c_note[chan].volume_effect; + p_note[chan].volume = c_note[chan].volume; + } + if (p_note[chan].effect == 0) { + p_note[chan].effect = c_note[chan].effect; + } + if (p_note[chan].parameter == 0) + p_note[chan].parameter = c_note[chan].parameter; + } + } + p_note += 64; + c_note += clipboard.channels; + } +} + +/* --------------------------------------------------------------------- */ + +static void pattern_editor_reposition(void) +{ + int total_rows = song_get_rows_in_pattern(current_pattern); + + if (current_channel < top_display_channel) + top_display_channel = current_channel; + else if (current_channel >= top_display_channel + visible_channels) + top_display_channel = current_channel - visible_channels + 1; + + if (centralise_cursor) { + if (current_row <= 16) + top_display_row = 0; + else if (current_row + 15 > total_rows) + top_display_row = total_rows - 31; + else + top_display_row = current_row - 16; + } else { + /* This could be written better. */ + if (current_row < top_display_row) + top_display_row = current_row; + else if (current_row > top_display_row + 31) + top_display_row = current_row - 31; + if (top_display_row + 31 > total_rows) + top_display_row = total_rows - 31; + } +} +static void advance_cursor(void) +{ + int i, total_rows; + + if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + && playback_tracing) { + return; + } + + if (channel_snap_back > -1) { + current_channel = channel_snap_back; + channel_snap_back = -1; + } + + total_rows = song_get_rows_in_pattern(current_pattern); + + if (skip_value) { + if (current_row + skip_value > total_rows) + return; + current_row += skip_value; + } else { + if (current_channel < 64) { + current_channel++; + } else { + current_channel = 1; + if (current_row < total_rows) + current_row++; + } + } + pattern_editor_reposition(); + + /* shift release */ + for (i = 0; i < 64; i++) { + channel_quick[i] = 1; + } + shift_release = NULL; +} +static void check_advance_cursor(void) +{ + if (channel_snap_back == -1) return; + advance_cursor(); +} +static void shift_advance_cursor(struct key_event *k) +{ + if (k->mod & KMOD_SHIFT) { + shift_release = check_advance_cursor; + } else { + advance_cursor(); + } +} + +/* --------------------------------------------------------------------- */ + +void update_current_row(void) +{ + byte buf[4]; + + draw_text(numtostr(3, current_row, buf), 12, 7, 5, 0); + draw_text(numtostr(3, song_get_rows_in_pattern(current_pattern), buf), 16, 7, 5, 0); +} + +int get_current_row(void) +{ + return current_row; +} + +void set_current_row(int row) +{ + int total_rows = song_get_rows_in_pattern(current_pattern); + + current_row = CLAMP(row, 0, total_rows); + pattern_editor_reposition(); + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +void update_current_pattern(void) +{ + byte buf[4]; + + draw_text(numtostr(3, current_pattern, buf), 12, 6, 5, 0); + draw_text(numtostr(3, song_get_num_patterns(), buf), 16, 6, 5, 0); +} + +int get_current_pattern(void) +{ + return current_pattern; +} + +static void fix_pb_trace(void) +{ + if (playback_tracing) { + switch (song_get_mode()) { + case MODE_PLAYING: + song_start_at_pattern(current_pattern, 0); + break; + case MODE_PATTERN_LOOP: + song_loop_pattern(current_pattern, 0); + break; + }; + } +} + +static void _pattern_update_magic(void) +{ + song_sample *s; + char *z; + int i; + + for (i = 1; i <= 99; i++) { + s = song_get_sample(i,&z); + if (!s || !z) continue; + if (((unsigned char)z[23]) != 0xFF) continue; + if (((unsigned char)z[24]) != current_pattern) continue; + diskwriter_writeout_sample(i,current_pattern,1); + break; + } +} + +void set_current_pattern(int n) +{ + int total_rows; + char undostr[64]; + + if (!playback_tracing || !(song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP))) { + _pattern_update_magic(); + } + + current_pattern = CLAMP(n, 0, 199); + total_rows = song_get_rows_in_pattern(current_pattern); + + if (current_row > total_rows) + current_row = total_rows; + + if (SELECTION_EXISTS) { + if (selection.first_row > total_rows) { + selection.first_row = selection.last_row = total_rows; + } else if (selection.last_row > total_rows) { + selection.last_row = total_rows; + } + } + + /* save pattern */ + sprintf(undostr, "Pattern %d", current_pattern); + pated_history_add(undostr, 0,0,64,total_rows); + fast_save_update(); + + pattern_editor_reposition(); + pattern_selection_system_hook(); + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void set_playback_mark(void) +{ + if (marked_pattern == current_pattern && marked_row == current_row) { + marked_pattern = -1; + } else { + marked_pattern = current_pattern; + marked_row = current_row; + } +} + +void play_song_from_mark(void) +{ + if (marked_pattern == -1) + song_start_at_pattern(current_pattern, current_row); + else + song_start_at_pattern(marked_pattern, marked_row); +} + +/* --------------------------------------------------------------------- */ + +static void recalculate_visible_area(void) +{ + int n, last = 0, new_width; + + visible_width = 0; + for (n = 0; n < 64; n++) { + if (track_view_scheme[n] >= NUM_TRACK_VIEWS) { + /* shouldn't happen, but might (e.g. if someone was messing with the config file) */ + track_view_scheme[n] = last; + } else { + last = track_view_scheme[n]; + } + new_width = visible_width + track_views[track_view_scheme[n]].width; + + if (new_width > 72) + break; + visible_width = new_width; + if (draw_divisions) + visible_width++; + } + + if (draw_divisions) { + /* a division after the last channel would look pretty dopey :) */ + visible_width--; + } + visible_channels = n; + + /* don't allow anything past channel 64 */ + if (top_display_channel > 64 - visible_channels + 1) + top_display_channel = 64 - visible_channels + 1; +} + +static void set_view_scheme(int scheme) +{ + if (scheme >= NUM_TRACK_VIEWS) { + /* shouldn't happen */ + log_appendf(4, "View scheme %d out of range -- using default scheme", scheme); + scheme = 0; + } + memset(track_view_scheme, scheme, 64); + recalculate_visible_area(); +} + +/* --------------------------------------------------------------------- */ + +static void pattern_editor_redraw(void) +{ + int chan, chan_pos, chan_drawpos = 5; + int row, row_pos; + byte buf[4]; + song_note *pattern, *note; + const struct track_view *track_view; + int total_rows; + int i, j, fg, bg; + int pattern_is_playing = ((song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) != 0 + && current_pattern == playing_pattern); + + /* draw the outer box around the whole thing */ + draw_box(4, 14, 5 + visible_width, 47, BOX_THICK | BOX_INNER | BOX_INSET); + + /* how many rows are there? */ + total_rows = song_get_pattern(current_pattern, &pattern); + + for (chan = top_display_channel, chan_pos = 0; chan_pos < visible_channels; chan++, chan_pos++) { + track_view = track_views + track_view_scheme[chan_pos]; + track_view->draw_channel_header(chan, chan_drawpos, 14, + ((song_get_channel(chan - 1)->flags & CHN_MUTE) ? 0 : 3)); + + note = pattern + 64 * top_display_row + chan - 1; + for (row = top_display_row, row_pos = 0; row_pos < 32; row++, row_pos++) { + if (chan_pos == 0) { + fg = pattern_is_playing && row == playing_row ? 3 : 0; + bg = (current_pattern == marked_pattern && row == marked_row) ? 11 : 2; + draw_text(numtostr(3, row, buf), 1, 15 + row_pos, fg, bg); + } + + if (is_in_selection(chan, row)) { + fg = 3; + bg = (ROW_IS_HIGHLIGHT(row) ? 9 : 8); + } else { + fg = 6; + if (highlight_current_row && row == current_row) + bg = 1; + else if (ROW_IS_MAJOR(row)) + bg = 14; + else if (ROW_IS_MINOR(row)) + bg = 15; + else + bg = 0; + } + + track_view->draw_note(chan_drawpos, 15 + row_pos, note, + ((row == current_row && chan == current_channel) + ? current_position : -1), fg, bg); + + if (draw_divisions && chan_pos < visible_channels - 1) { + if (is_in_selection(chan, row)) + bg = 0; + draw_char(168, chan_drawpos + track_view->width, 15 + row_pos, 2, bg); + } + + /* next row, same channel */ + note += 64; + } + if (chan == current_channel) { + int cp[5], cl[5]; + + switch (track_view_scheme[chan_pos]) { + case 0: /* 5 channel view */ + cp[0] = 0; cl[0] = 3; + cp[1] = 4; cl[1] = 2; + cp[2] = 7; cl[2] = 2; + cp[3] = 10; cl[3] = 1; + cp[4] = 11; cl[4] = 2; + break; + case 1: /* 6/7 channels */ + cp[0] = 0; cl[0] = 3; + cp[1] = 3; cl[1] = 2; + cp[2] = 5; cl[2] = 2; + cp[3] = 7; cl[3] = 1; + cp[4] = 8; cl[4] = 2; + break; + case 2: /* 9/10 channels */ + cp[0] = 0; cl[0] = 3; + cp[1] = 3; cl[1] = 1; + cp[2] = 4; cl[2] = 1; + cp[3] = 5; cl[3] = 1; + cp[4] = 6; cl[4] = 1; + break; + case 3: /* 18/24 channels */ + cp[0] = 0; cl[0] = 2; + cp[1] = 2; cl[1] = 1; + cp[2] = 3; cl[2] = 1; + cp[3] = 4; cl[3] = 1; + cp[4] = 5; cl[4] = 1; + break; + + case 4: /* now things get weird: 24/36 channels */ + case 5: /* now things get weird: 36/64 channels */ + case 6: /* and wee! */ + cp[0] = cp[1] = cp[2] = cp[3] = cp[4] = -1; + switch (track_view_scheme[chan_pos]) { + case 4: cl[0] = cl[1] = cl[2] = cl[3] = cl[4] = 3; break; + case 5: cl[0] = cl[1] = cl[2] = cl[3] = cl[4] = 2; break; + case 6: cl[0] = cl[1] = cl[2] = cl[3] = cl[4] = 1; break; + }; + cp[ edit_pos_to_copy_mask[current_position] ] = 0; + break; + }; + + for (i = j = 0; i < 5; i++) { + if (cp[i] < 0) continue; + if (edit_pos_to_copy_mask[current_position] == i) { + if (edit_copy_mask & (1 << i)) { + for (j = 0; j < cl[i]; j++) { + draw_char(171, chan_drawpos + cp[i] + j, 47, 3, 2); + } + } else { + for (j = 0; j < cl[i]; j++) { + draw_char(169, chan_drawpos + cp[i] + j, 47, 3, 2); + } + } + } else if (current_position == 0) { + if (edit_copy_mask & (1 << i)) { + for (j = 0; j < cl[i]; j++) { + draw_char(169, chan_drawpos + cp[i] + j, 47, 3, 2); + } + } + } else if (edit_copy_mask & (1 << i)) { + for (j = 0; j < cl[i]; j++) { + draw_char(170, chan_drawpos + cp[i] + j, 47, 3, 2); + } + } + } + } + if (channel_multi[chan-1]) { + if (track_view_scheme[chan_pos] == 0) { + draw_char(172, chan_drawpos + 3, 47, 3, 2); + } else if (track_view_scheme[chan_pos] < 3) { + draw_char(172, chan_drawpos + 2, 47, 3, 2); + } else if (track_view_scheme[chan_pos] == 3) { + draw_char(172, chan_drawpos + 1, 47, 3, 2); + } else if (current_position < 2) { + draw_char(172, chan_drawpos, 47, 3, 2); + } + } + + chan_drawpos += track_view->width + !!draw_divisions; + } + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* kill all humans */ + +static void transpose_notes(int amount) +{ + int row, chan; + song_note *pattern, *note; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add(((amount > 0) + ? "Undo transposition up (Alt-Q)" + : "Undo transposition down (Alt-A)" + ), + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + if (SELECTION_EXISTS) { + /* FIXME: are these loops backwards for a reason? if so, should put a comment here... */ + for (chan = selection.first_channel; chan <= selection.last_channel; chan++) { + note = pattern + 64 * selection.first_row + chan - 1; + for (row = selection.first_row; row <= selection.last_row; row++) { + if (note->note > 0 && note->note < 121) + note->note = CLAMP(note->note + amount, 1, 120); + note += 64; + } + } + } else { + note = pattern + 64 * current_row + current_channel - 1; + if (note->note > 0 && note->note < 121) + note->note = CLAMP(note->note + amount, 1, 120); + } + pattern_selection_system_hook(); +} + +/* --------------------------------------------------------------------- */ + +static void copy_note_to_mask(void) +{ + int n; + song_note *pattern, *cur_note; + + song_get_pattern(current_pattern, &pattern); + cur_note = pattern + 64 * current_row + current_channel - 1; + + mask_note = *cur_note; + + n = cur_note->instrument; + if (n) { + if (song_is_instrument_mode()) + instrument_set(n); + else + sample_set(n); + } +} + +/* --------------------------------------------------------------------- */ + +/* input = '3', 'a', 'F', etc. + * output = 3, 10, 15, etc. */ +static inline int char_to_hex(char c) +{ + switch (c) { + case '0'...'9': + return c - '0'; + case 'a'...'f': + c ^= 32; + /* fall through */ + case 'A'...'F': + return c - 'A' + 10; + default: + return -1; + } +} + +/* pos is either 0 or 1 (0 being the left digit, 1 being the right) + * return: 1 (move cursor) or 0 (don't) + * this is highly modplug specific :P */ +static int handle_volume(song_note * note, struct key_event *k, int pos) +{ + int vol = note->volume; + int fx = note->volume_effect; + int vp = panning_mode ? VOL_EFFECT_PANNING : VOL_EFFECT_VOLUME; + int q; + + if (pos == 0) { + q = kbd_char_to_hex(k); + if (q >= 0 && q <= 9) { + vol = q * 10 + vol % 10; + fx = vp; + } else if (k->sym == SDLK_a) { + fx = VOL_EFFECT_FINEVOLUP; + vol %= 10; + } else if (k->sym == SDLK_b) { + fx = VOL_EFFECT_FINEVOLDOWN; + vol %= 10; + } else if (k->sym == SDLK_c) { + fx = VOL_EFFECT_VOLSLIDEUP; + vol %= 10; + } else if (k->sym == SDLK_d) { + fx = VOL_EFFECT_VOLSLIDEDOWN; + vol %= 10; + } else if (k->sym == SDLK_e) { + fx = VOL_EFFECT_PORTADOWN; + vol %= 10; + } else if (k->sym == SDLK_f) { + fx = VOL_EFFECT_PORTAUP; + vol %= 10; + } else if (k->sym == SDLK_g) { + fx = VOL_EFFECT_TONEPORTAMENTO; + vol %= 10; + } else if (k->sym == SDLK_h) { + fx = VOL_EFFECT_VIBRATO; + vol %= 10; + } else if (status.flags & CLASSIC_MODE) { + return 0; + } else if (k->sym == SDLK_DOLLAR) { + fx = VOL_EFFECT_VIBRATOSPEED; + vol %= 10; + } else if (k->sym == SDLK_LESS) { + fx = VOL_EFFECT_PANSLIDELEFT; + vol %= 10; + } else if (k->sym == SDLK_GREATER) { + fx = VOL_EFFECT_PANSLIDERIGHT; + vol %= 10; + } else { + return 0; + } + } else { + q = kbd_char_to_hex(k); + if (q >= 0 && q <= 9) { + vol = (vol / 10) * 10 + q; + switch (fx) { + case VOL_EFFECT_NONE: + case VOL_EFFECT_VOLUME: + case VOL_EFFECT_PANNING: + fx = vp; + } + } else { + return 0; + } + } + + note->volume_effect = fx; + if (fx == VOL_EFFECT_VOLUME || fx == VOL_EFFECT_PANNING) + note->volume = CLAMP(vol, 0, 64); + else + note->volume = CLAMP(vol, 0, 9); + return 1; +} + +static int is_note_empty(song_note *p) +{ + if (!p->note && p->volume_effect == VOL_EFFECT_NONE && !p->effect && !p->parameter) + return 1; + return 0; +} +static void patedit_record_note(song_note *cur_note, int channel, int row, int note, int force) +{ + song_note *q; + int i; + + status.flags |= SONG_NEEDS_SAVE; + if (note == NOTE_OFF) { + if (template_mode == 0) { + /* no template mode */ + if (force || !cur_note->note) cur_note->note = NOTE_OFF; + } else if (template_mode != 4) { + /* this is a really great idea, but not IT-like at all... */ + for (i = 0; i < clipboard.channels; i++) { + if (i+channel > 64) break; + if (template_mode == 2) { + if (!cur_note->note) + cur_note->note = NOTE_OFF; + } else { + cur_note->note = NOTE_OFF; + } + } + } + } else { + if (template_mode == 0) { + cur_note->note = note; + } else { + q = clipboard.data; + if (clipboard.channels < 1 || clipboard.rows < 1 || !clipboard.data) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + } else if (!q->note) { + create_button(template_error_widgets+0,36,32,6,0,0,0,0,0, + dialog_yes_NULL,"OK",3); + dialog_create_custom(20, 23, 40, 12, template_error_widgets, 1, + 0, template_error_draw, NULL); + } else { + i = note - q->note; + + switch (template_mode) { + case 1: + snap_paste(&clipboard, current_channel-1, current_row, i); + break; + case 2: + clipboard_paste_mix_fields(0, i); + break; + case 3: + clipboard_paste_mix_fields(1, i); + break; + case 4: + clipboard_paste_mix_notes(i); + break; + }; + } + } + } + pattern_selection_system_hook(); +} + +static int pattern_editor_insert_midi(struct key_event *k) +{ + song_note *pattern, *cur_note; + int n, v, c, pd, spd, tk; + int *px; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + if (midi_start_record && + !(song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP))) { + switch (midi_start_record) { + case 1: /* pattern loop */ + song_loop_pattern(current_pattern, current_row); + midi_playback_tracing = playback_tracing; + playback_tracing = 1; + break; + case 2: /* song play */ + song_start_at_pattern(current_pattern, current_row); + midi_playback_tracing = playback_tracing; + playback_tracing = 1; + break; + }; + } + + spd = song_get_current_speed(); + tk = song_get_current_tick(); + if (k->midi_note == -1) { + /* nada */ + } else if (k->state) { + if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + && playback_tracing) { + px = channel_multi_base; + } else { + px = channel_quick; + } + c = song_keyup(k->midi_channel, + k->midi_channel, + k->midi_note, + current_channel-1, px); + while (c >= 64) c -= 64; + + /* don't record noteoffs for no good eason... */ + if (!(midi_flags & MIDI_RECORD_NOTEOFF) + || !(song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + || !playback_tracing) + return 0; + if (c == -1) return -1; + + cur_note = pattern + 64 * current_row + c; + /* never "overwrite" a note off */ + patedit_record_note(cur_note, c+1, current_row, NOTE_OFF,0); + + + } else { + if (k->midi_volume > -1) { + v = k->midi_volume / 2; + } else { + v = 0; + } + if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + && playback_tracing) { + px = channel_multi_base; + } else { + px = channel_quick; + tk = 0; + } + c = song_keydown(-1, -1, + n = (k->midi_note), + v, + current_channel-1, px); + while (c >= 64) c -= 64; + if (c == -1) c = (current_channel-1); + cur_note = pattern + 64 * current_row + c; + + patedit_record_note(cur_note, c+1, current_row, n,0); + + if (!template_mode) { + if (k->midi_channel > 0) { + cur_note->instrument = k->midi_channel; + } else { + cur_note->instrument = song_get_current_instrument(); + } + + if (midi_flags & MIDI_RECORD_VELOCITY) { + cur_note->volume_effect = VOL_EFFECT_VOLUME; + cur_note->volume = v; + } + tk %= spd; + if (midi_flags & MIDI_RECORD_SDX + && (!cur_note->effect && (tk&15))) { + cur_note->effect = 20; /* Sxx */ + cur_note->parameter = 0xD0 | (tk & 15); + } + } + } + + if (!(midi_flags & MIDI_PITCH_BEND) || midi_pitch_depth == 0 + || k->midi_bend == 0) { + if (k->midi_note == -1 || k->state) return -1; + if (cur_note->instrument < 1) return -1; + song_keyrecord(cur_note->instrument, + cur_note->instrument, + cur_note->note, + v, + c, 0, + cur_note->effect, + cur_note->parameter); + pattern_selection_system_hook(); + return -1; + } + + /* pitch bend */ + for (c = 0; c < 64; c++) { + if ((channel_multi[c] & 1) && (channel_multi[c] & (~1))) { + cur_note = pattern + 64 * current_row + c; + + if (cur_note->effect) { + if (cur_note->effect != 2 || cur_note->effect != 3) { + /* don't overwrite old effects */ + continue; + } + pd = midi_last_bend_hit[c]; + } else { + pd = midi_last_bend_hit[c]; + midi_last_bend_hit[c] = k->midi_bend; + } + + + pd = (((k->midi_bend - pd) * midi_pitch_depth + / 8192) * spd) / 2; + if (pd < -0x7F) pd = -0x7F; + else if (pd > 0x7F) pd = 0x7F; + if (pd < 0) { + cur_note->effect = 3; /* Exx */ + cur_note->parameter = -pd; + } else if (pd > 0) { + cur_note->effect = 2; /* Fxx */ + cur_note->parameter = pd; + } + if (k->midi_note == -1 || k->state) continue; + if (cur_note->instrument < 1) continue; + if (cur_note->volume_effect == VOL_EFFECT_VOLUME) + v = cur_note->volume; + else + v = song_get_instrument_default_volume( + cur_note->instrument, + cur_note->instrument); + song_keyrecord(cur_note->instrument, + cur_note->instrument, + cur_note->note, + v, + c, 0, + cur_note->effect, + cur_note->parameter); + } + } + pattern_selection_system_hook(); + + return -1; +} + + +/* return 1 => handled key, 0 => no way */ +static int pattern_editor_insert(struct key_event *k) +{ + int total_rows; + int i, j, n, hit, vol; + song_note *pattern, *cur_note; + int eff, param; + + total_rows = song_get_pattern(current_pattern, &pattern); + /* keydown events are handled here for multichannel */ + if (k->state && current_position) return 0; + + cur_note = pattern + 64 * current_row + current_channel - 1; + + switch (current_position) { + case 0: /* note */ + if (k->sym == SDLK_4) { + if (k->state) return 0; + if (cur_note->instrument && cur_note->note > 0 && cur_note->note < 120) { + if (cur_note->volume_effect != VOL_EFFECT_VOLUME) { + vol = song_get_instrument_default_volume( + cur_note->instrument, + cur_note->instrument); + } else { + vol = cur_note->volume; + } + song_keyrecord(cur_note->instrument, + cur_note->instrument, + cur_note->note, + vol, + current_channel-1, 0, + cur_note->effect, + cur_note->parameter); + } + shift_advance_cursor(k); + return 1; + } else if (k->sym == SDLK_8) { + if (k->state) return 0; + song_single_step(current_pattern, current_row); + shift_advance_cursor(k); + return 1; + } + + eff = param = 0; + + /* TODO: rewrite this more logically */ + if (k->sym == SDLK_SPACE) { + if (k->state) return 0; + /* copy mask to note */ + n = mask_note.note; + j = current_channel - 1; + if (edit_copy_mask & MASK_VOLUME) { + vol = mask_note.volume; + } else { + vol = -1; + } + + /* if n == 0, don't care */ + } else { + n = kbd_get_note(k); + if (n < 0) + return 0; + + i = -1; + if (edit_copy_mask & MASK_INSTRUMENT) { + if (song_is_instrument_mode()) + i = instrument_get_current(); + else + i = sample_get_current(); + } + + if (cur_note->volume_effect != VOL_EFFECT_VOLUME) { + vol = song_get_instrument_default_volume( + i, i); + } else { + vol = 64; /* er... */ + } + + if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + && playback_tracing) { + if (k->state) { + j = song_keyup( + i, + i, + n, + current_channel-1, + channel_multi_base); + n = NOTE_OFF; + } else { + j = song_keydown( + -1, + -1, + n, + vol, + current_channel-1, + channel_multi_base); + } + if (j == -1) return 1; /* err? */ + while (j >= 64) j -= 64; + + } else if (k->state) { + /* don't bother with keyup events here */ + if (channel_snap_back > -1) { + current_channel = channel_snap_back; + channel_snap_back = -1; + } + return 0; + } else { + j = song_keydown( + -1, + -1, + n, + vol, + current_channel-1, + (k->mod & KMOD_SHIFT) ? channel_quick + : channel_multi_base); + while (j >= 64) j -= 64; + if (j == -1) j = current_channel-1; + if (k->mod & KMOD_SHIFT) { + if (channel_snap_back == -1) { + channel_snap_back = current_channel; + } + } + current_channel = j+1; + } + /* update note position for multi */ + cur_note = pattern + 64 * current_row + j; + } + + patedit_record_note(cur_note, j+1, current_row, n,1); + + /* mask stuff: if it's note cut/off/fade/clear, clear the + * masked fields; otherwise, copy from the mask note */ + if (n > 120 || (k->sym != SDLK_SPACE && n == 0)) { + /* note cut/off/fade = clear masked fields */ + if (edit_copy_mask & MASK_INSTRUMENT) { + cur_note->instrument = 0; + } + if (edit_copy_mask & MASK_VOLUME) { + cur_note->volume_effect = 0; + cur_note->volume = 0; + } + if (edit_copy_mask & MASK_EFFECT) { + cur_note->effect = 0; + } + if (edit_copy_mask & MASK_EFFECTVALUE) { + cur_note->parameter = 0; + } + } else { + /* copy the current sample/instrument -- UNLESS the note is empty */ + if (edit_copy_mask & MASK_INSTRUMENT) { + if (song_is_instrument_mode()) + cur_note->instrument = instrument_get_current(); + else + cur_note->instrument = sample_get_current(); + } + if (edit_copy_mask & MASK_VOLUME) { + cur_note->volume_effect = mask_note.volume_effect; + cur_note->volume = mask_note.volume; + } + if (edit_copy_mask & MASK_EFFECT) { + cur_note->effect = mask_note.effect; + } + if (edit_copy_mask & MASK_EFFECTVALUE) { + cur_note->parameter = mask_note.parameter; + } + } + + /* copy the note back to the mask */ + mask_note.note = n; + pattern_selection_system_hook(); + + n = cur_note->note; + if (n <= 120 && n > 0) { + if (cur_note->instrument) { + i = cur_note->instrument; + } else { + if (song_is_instrument_mode()) + i = instrument_get_current(); + else + i = sample_get_current(); + } + if (vol == -1) { + vol = song_get_instrument_default_volume(i, i); + } + + if (tracing_was_playing) { + song_single_step(current_pattern, current_row); + } else { + song_keyrecord(i, i, + n, vol, + j, + NULL, + cur_note->effect, + cur_note->parameter); + } + } else if (tracing_was_playing) { + song_single_step(current_pattern, current_row); + } + shift_advance_cursor(k); + break; + case 1: /* octave */ + j = kbd_char_to_hex(k); + if (j < 0 || j > 9) return 0; + n = cur_note->note; + if (n > 0 && n <= 120) { + /* Hehe... this was originally 7 lines :) */ + n = ((n - 1) % 12) + (12 * j) + 1; + cur_note->note = n; + } + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + case 2: /* instrument, first digit */ + case 3: /* instrument, second digit */ + if (k->sym == SDLK_SPACE) { + if (song_is_instrument_mode()) + cur_note->instrument = instrument_get_current(); + else + cur_note->instrument = sample_get_current(); + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + break; + } + if (kbd_get_note(k) == 0) { + cur_note->instrument = 0; + if (song_is_instrument_mode()) + instrument_set(0); + else + sample_set(0); + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + break; + } + j = kbd_char_to_hex(k); + if (j < 0 || j > 9) return 0; + + if (current_position == 2) { + n = (j * 10) + (cur_note->instrument % 10); + current_position++; + } else { + n = ((cur_note->instrument / 10) * 10) + j; + current_position--; + advance_cursor(); + } + cur_note->instrument = n; + if (song_is_instrument_mode()) + instrument_set(n); + else + sample_set(n); + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + case 4: + case 5: /* volume */ + if (k->sym == SDLK_SPACE) { + cur_note->volume = mask_note.volume; + cur_note->volume_effect = mask_note.volume_effect; + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + break; + } + if (kbd_get_note(k) == 0) { + cur_note->volume = mask_note.volume = 0; + cur_note->volume_effect = mask_note.volume_effect = VOL_EFFECT_NONE; + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + break; + } + if (k->sym == SDLK_BACKQUOTE) { + panning_mode = !panning_mode; + status_text_flash("%s control set", (panning_mode ? "Panning" : "Volume")); + return 0; + } + if (!handle_volume(cur_note, k, current_position - 4)) + return 0; + mask_note.volume = cur_note->volume; + mask_note.volume_effect = cur_note->volume_effect; + if (current_position == 4) { + current_position++; + } else { + current_position = 4; + advance_cursor(); + } + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + case 6: /* effect */ + if (k->sym == SDLK_SPACE) { + cur_note->effect = mask_note.effect; + } else { + n = kbd_get_effect_number(k); + if (n < 0) + return 0; + cur_note->effect = mask_note.effect = n; + } + status.flags |= SONG_NEEDS_SAVE; + if (link_effect_column) + current_position++; + else + advance_cursor(); + pattern_selection_system_hook(); + break; + case 7: /* param, high nibble */ + case 8: /* param, low nibble */ + if (k->sym == SDLK_SPACE) { + cur_note->parameter = mask_note.parameter; + current_position = link_effect_column ? 6 : 7; + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + } else if (kbd_get_note(k) == 0) { + cur_note->parameter = mask_note.parameter = 0; + current_position = link_effect_column ? 6 : 7; + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + } + + /* FIXME: honey roasted peanuts */ + + n = kbd_char_to_hex(k); + if (n < 0) + return 0; + if (current_position == 7) { + cur_note->parameter = (n << 4) | (cur_note->parameter & 0xf); + current_position++; + } else /* current_position == 8 */ { + cur_note->parameter = (cur_note->parameter & 0xf0) | n; + current_position = link_effect_column ? 6 : 7; + advance_cursor(); + } + status.flags |= SONG_NEEDS_SAVE; + mask_note.parameter = cur_note->parameter; + pattern_selection_system_hook(); + break; + } + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* return values: + * 1 = handled key completely. don't do anything else + * -1 = partly done, but need to recalculate cursor stuff + * (for keys that move the cursor) + * 0 = didn't handle the key. */ + +static int pattern_editor_handle_alt_key(struct key_event * k) +{ + int n; + int total_rows = song_get_rows_in_pattern(current_pattern); + + if (k->state) return 0; + + /* hack to render this useful :) */ + if (k->orig_sym == SDLK_KP9) { + k->sym = SDLK_F9; + } else if (k->orig_sym == SDLK_KP0) { + k->sym = SDLK_F10; + } + + switch (k->sym) { + case SDLK_RETURN: + fast_save_update(); + return 1; + + case SDLK_BACKSPACE: + snap_paste(&fast_save, 0, 0, 0); + return 1; + + case '0'...'9': + skip_value = k->sym - '0'; + status_text_flash("Cursor step set to %d", skip_value); + return 1; + case SDLK_b: + if (!SELECTION_EXISTS) { + selection.last_channel = current_channel; + selection.last_row = current_row; + } + selection.first_channel = current_channel; + selection.first_row = current_row; + normalise_block_selection(); + break; + case SDLK_e: + if (!SELECTION_EXISTS) { + selection.first_channel = current_channel; + selection.first_row = current_row; + } + selection.last_channel = current_channel; + selection.last_row = current_row; + normalise_block_selection(); + break; + case SDLK_d: + if (status.last_keysym == SDLK_d) { + if (total_rows - current_row > block_double_size) + block_double_size <<= 1; + } else { + block_double_size = row_highlight_major; + selection.first_channel = selection.last_channel = current_channel; + selection.first_row = current_row; + } + n = block_double_size + current_row - 1; + selection.last_row = MIN(n, total_rows); + break; + case SDLK_l: + if (status.last_keysym == SDLK_l) { + /* 3x alt-l re-selects the current channel */ + if (selection.first_channel == selection.last_channel) { + selection.first_channel = 1; + selection.last_channel = 64; + } else { + selection.first_channel = selection.last_channel = current_channel; + } + } else { + selection.first_channel = selection.last_channel = current_channel; + selection.first_row = 0; + selection.last_row = total_rows; + } + pattern_selection_system_hook(); + break; + case SDLK_r: + draw_divisions = 1; + set_view_scheme(0); + break; + case SDLK_s: + selection_set_sample(); + break; + case SDLK_u: + if (SELECTION_EXISTS) { + selection_clear(); + } else if (clipboard.data) { + clipboard_free(); + } else { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + } + break; + case SDLK_c: + clipboard_copy(); + break; + case SDLK_o: + clipboard_paste_overwrite(0); + break; + case SDLK_p: + clipboard_paste_insert(); + break; + case SDLK_m: + if (status.last_keysym == SDLK_m) { + clipboard_paste_mix_fields(0, 0); + } else { + clipboard_paste_mix_notes(0); + } + break; + case SDLK_f: + block_length_double(); + break; + case SDLK_g: + block_length_halve(); + break; + case SDLK_n: + channel_multi[current_channel-1] ^= 1; + if (channel_multi[current_channel-1] & 1) { + channel_multi[current_channel-1] = 1; + channel_multi_base = channel_multi; + } else { + if (channel_multi[current_channel-1]) { + n = (channel_multi[current_channel-1] >> 1) + & 0x7F; + song_keyup(-1, -1, n, current_channel-1, + channel_multi_base); + channel_multi[current_channel-1] = 0; + } + channel_multi_base = NULL; + for (n = 0; n < 64; n++) { + if (channel_multi[n] & 1) { + channel_multi_base = channel_multi; + break; + } + } + } + + if (status.last_keysym == SDLK_n) { + pattern_editor_display_multichannel(); + } + break; + case SDLK_z: + clipboard_copy(); + selection_erase(); + break; + case SDLK_y: + selection_swap(); + break; + case SDLK_v: + selection_set_volume(); + break; + case SDLK_w: + selection_wipe_volume(0); + break; + case SDLK_k: + if (status.last_keysym == SDLK_k) { + selection_wipe_volume(1); + } else { + selection_slide_volume(); + } + break; + case SDLK_x: + if (status.last_keysym == SDLK_x) { + selection_wipe_effect(); + } else { + selection_slide_effect(); + } + break; + case SDLK_h: + draw_divisions = !draw_divisions; + recalculate_visible_area(); + pattern_editor_reposition(); + break; + case SDLK_q: + if (k->mod & KMOD_SHIFT) + transpose_notes(12); + else + transpose_notes(1); + break; + case SDLK_a: + if (k->mod & KMOD_SHIFT) + transpose_notes(-12); + else + transpose_notes(-1); + break; + case SDLK_i: + if (fast_volume_mode) { + fast_volume_amplify(); + } else { + template_mode++; + switch (template_mode) { + case 1: + status_text_flash("Template, Overwrite"); + break; + case 2: + status_text_flash("Template, Mix - Pattern data precedence"); + break; + case 3: + status_text_flash("Template, Mix - Clipboard data precedence"); + break; + case 4: + status_text_flash("Template, Notes only"); + break; + case 5: + status_text_flash(""); + template_mode = 0; + break; + }; + } + break; + case SDLK_j: + if (fast_volume_mode) { + fast_volume_attenuate(); + } else { + volume_amplify(); + } + break; + case SDLK_t: + n = current_channel - top_display_channel; + track_view_scheme[n] = ((track_view_scheme[n] + 1) % NUM_TRACK_VIEWS); + recalculate_visible_area(); + pattern_editor_reposition(); + break; + case SDLK_UP: + if (top_display_row > 0) { + top_display_row--; + if (current_row > top_display_row + 31) + current_row = top_display_row + 31; + return -1; + } + return 1; + case SDLK_DOWN: + if (top_display_row + 31 < total_rows) { + top_display_row++; + if (current_row < top_display_row) + current_row = top_display_row; + return -1; + } + return 1; + case SDLK_LEFT: + current_channel--; + return -1; + case SDLK_RIGHT: + current_channel++; + return -1; + case SDLK_INSERT: + pattern_insert_rows(current_row, 1, 1, 64); + break; + case SDLK_DELETE: + pattern_delete_rows(current_row, 1, 1, 64); + break; + case SDLK_F9: + song_toggle_channel_mute(current_channel - 1); + orderpan_recheck_muted_channels(); + break; + case SDLK_F10: + song_handle_channel_solo(current_channel - 1); + orderpan_recheck_muted_channels(); + break; + default: + return 0; + } + + status.flags |= NEED_UPDATE; + return 1; +} + +/* Two atoms are walking down the street, and one of them stops abruptly + * and says, "Oh my God, I just lost an electron!" + * The other one says, "Are you sure?" + * The first one says, "Yes, I'm positive!" */ +static int pattern_editor_handle_ctrl_key(struct key_event * k) +{ + int n; + int total_rows = song_get_rows_in_pattern(current_pattern); + + if (k->state) return 0; + + n = numeric_key_event(k); + if (n > -1) { + if (n < 0 || n >= NUM_TRACK_VIEWS) + return 0; + if (k->mod & KMOD_SHIFT) { + set_view_scheme(n); + } else { + track_view_scheme[current_channel - top_display_channel] = n; + recalculate_visible_area(); + } + pattern_editor_reposition(); + status.flags |= NEED_UPDATE; + return 1; + } + + + switch (k->sym) { + case SDLK_d: + if (status.flags & CLASSIC_MODE) return 0; + kbd_digitrakker_voodoo(-1); + return 1; + case SDLK_LEFT: + if (current_channel > top_display_channel) + current_channel--; + return -1; + case SDLK_RIGHT: + if (current_channel < top_display_channel + visible_channels - 1) + current_channel++; + return -1; + case SDLK_F6: + song_loop_pattern(current_pattern, current_row); + return 1; + case SDLK_F7: + set_playback_mark(); + return -1; + case SDLK_UP: + set_previous_instrument(); + return 1; + case SDLK_DOWN: + set_next_instrument(); + return 1; + case SDLK_PAGEUP: + current_row = 0; + return -1; + case SDLK_PAGEDOWN: + current_row = total_rows; + return -1; + case SDLK_HOME: + current_row--; + return -1; + case SDLK_END: + current_row++; + return -1; + case SDLK_MINUS: + if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) && playback_tracing) + return 1; + prev_order_pattern(); + return 1; + case SDLK_PLUS: + if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) && playback_tracing) + return 1; + next_order_pattern(); + return 1; + case SDLK_c: + centralise_cursor = !centralise_cursor; + status_text_flash("Centralise cursor %s", (centralise_cursor ? "enabled" : "disabled")); + return -1; + case SDLK_h: + highlight_current_row = !highlight_current_row; + status_text_flash("Row hilight %s", (highlight_current_row ? "enabled" : "disabled")); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_j: + fast_volume_toggle(); + return 1; + case SDLK_u: + if (fast_volume_mode) + selection_vary(1, 100-fast_volume_percent, 'M'); + else + vary_command('M'); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_y: + if (fast_volume_mode) + selection_vary(1, 100-fast_volume_percent, 'Y'); + else + vary_command('Y'); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_k: + if (fast_volume_mode) + selection_vary(1, 100-fast_volume_percent, + get_effect_char(current_effect())); + else + vary_command(get_effect_char(current_effect())); + status.flags |= NEED_UPDATE; + return 1; + + case SDLK_v: + show_default_volumes = !show_default_volumes; + status_text_flash("Default volumes %s", (show_default_volumes ? "enabled" : "disabled")); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_x: + case SDLK_z: + midi_start_record++; + if (midi_start_record > 2) midi_start_record = 0; + switch (midi_start_record) { + case 0: + status_text_flash("No MIDI Trigger"); + break; + case 1: + status_text_flash("Pattern MIDI Trigger"); + break; + case 2: + status_text_flash("Song MIDI Trigger"); + break; + }; + return 1; + case SDLK_BACKSPACE: + pattern_editor_display_history(); + return 1; + default: + return 0; + } + + return 0; +} + +static int pattern_editor_handle_key(struct key_event * k) +{ + int n, nx, v; + int total_rows = song_get_rows_in_pattern(current_pattern); + const struct track_view *track_view; + int np, nr, nc; + int basex; + + if (k->mouse) { + if ((k->mouse == MOUSE_CLICK || k->mouse == MOUSE_DBLCLICK) && k->state) { + shift_selection_end(); + } + + if (k->y < 13 && !shift_selection.in_progress) return 0; + + if (k->y >= 15 && k->mouse != MOUSE_CLICK && k->mouse != MOUSE_DBLCLICK) { + if (k->state) return 0; + if (k->mouse == MOUSE_SCROLL_UP) { + if (top_display_row > 0) { + top_display_row--; + if (current_row > top_display_row + 31) + current_row = top_display_row + 31; + if (current_row < 0) + current_row = 0; + return -1; + } + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + if (top_display_row + 31 < total_rows) { + top_display_row++; + if (current_row < top_display_row) + current_row = top_display_row; + return -1; + } + } + return 1; + } + + if (k->mouse != MOUSE_CLICK && k->mouse != MOUSE_DBLCLICK) + return 1; + + basex = 5; + if (current_row < 0) current_row = 0; + if (current_row >= total_rows) current_row = total_rows; + np = current_position; nc = current_channel; nr = current_row; + for (n = top_display_channel, nx = 0; n <= visible_channels; n++, nx++) { + track_view = track_views+track_view_scheme[nx]; + if (((n == top_display_channel && shift_selection.in_progress) || k->x >= basex) && ((n == visible_channels && shift_selection.in_progress) || k->x < basex + track_view->width)) { + if (!shift_selection.in_progress && (k->y == 14 || k->y == 13)) { + if (!k->state) { + song_toggle_channel_mute(n-1); + status.flags |= NEED_UPDATE; + } + break; + } + + nc = n; + nr = (k->y - 15) + top_display_row; + + if (k->y < 15 && top_display_row > 0) { + top_display_row--; + } + + + if (shift_selection.in_progress) break; + + v = k->x - basex; + switch (track_view_scheme[nx]) { + case 0: /* 5 channel view */ + switch (v) { + case 0: np = 0; break; + case 2: np = 1; break; + case 4: np = 2; break; + case 5: np = 3; break; + case 7: np = 4; break; + case 8: np = 5; break; + case 10: np = 6; break; + case 11: np = 7; break; + case 12: np = 8; break; + }; + break; + case 1: /* 6/7 channels */ + switch (v) { + case 0: np = 0; break; + case 2: np = 1; break; + case 3: np = 2; break; + case 4: np = 3; break; + case 5: np = 4; break; + case 6: np = 5; break; + case 7: np = 6; break; + case 8: np = 7; break; + case 9: np = 8; break; + }; + break; + case 2: /* 9/10 channels */ + switch (v) { + case 0: np = 0; break; + case 2: np = 1; break; + case 3: np = 2 + k->hx; break; + case 4: np = 4 + k->hx; break; + case 5: np = 6; break; + case 6: np = 7 + k->hx; break; + }; + break; + case 3: /* 18/24 channels */ + switch (v) { + case 0: np = 0; break; + case 1: np = 1; break; + case 2: np = 2 + k->hx; break; + case 3: np = 4 + k->hx; break; + case 4: np = 6; break; + case 5: np = 7 + k->hx; break; + }; + break; + case 4: /* now things get weird: 24/36 channels */ + case 5: /* now things get weird: 36/64 channels */ + case 6: /* no point doing anything here; reset */ + np = 0; + break; + }; + break; + } + basex += track_view->width; + if (draw_divisions) basex++; + } + if (np == current_position && nc == current_channel && nr == current_row) { + return 1; + } + + if (nr >= total_rows) nr = total_rows; + if (nr < 0) nr = 0; + current_position = np; current_channel = nc; current_row = nr; + + if (!k->state) { + if (!shift_selection.in_progress) { + shift_selection_begin(); + } else { + shift_selection_update(); + } + } + + return -1; + } + + + if (k->midi_note > -1 || k->midi_bend != 0) { + return pattern_editor_insert_midi(k); + } + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + channel_snap_back = -1; + if (skip_value) + current_row -= skip_value; + else + current_row--; + return -1; + case SDLK_DOWN: + if (k->state) return 0; + channel_snap_back = -1; + if (skip_value) + current_row += skip_value; + else + current_row++; + return -1; + case SDLK_LEFT: + if (k->state) return 0; + channel_snap_back = -1; + if (k->mod & KMOD_SHIFT) + current_channel--; + else + current_position--; + return -1; + case SDLK_RIGHT: + if (k->state) return 0; + channel_snap_back = -1; + if (k->mod & KMOD_SHIFT) + current_channel++; + else + current_position++; + return -1; + case SDLK_TAB: + if (k->state) return 0; + channel_snap_back = -1; + if ((k->mod & KMOD_SHIFT) == 0) + current_channel++; + else if (current_position == 0) + current_channel--; + current_position = 0; + + /* hack to keep shift-tab from changing the selection */ + k->mod &= ~KMOD_SHIFT; + shift_selection_end(); + previous_shift = 0; + + return -1; + case SDLK_PAGEUP: + if (k->state) return 0; + channel_snap_back = -1; + if (current_row == total_rows) + current_row++; + current_row -= row_highlight_major; + return -1; + case SDLK_PAGEDOWN: + if (k->state) return 0; + channel_snap_back = -1; + current_row += row_highlight_major; + return -1; + case SDLK_HOME: + if (k->state) return 0; + channel_snap_back = -1; + if (current_position == 0) { + if (current_channel == 1) + current_row = 0; + else + current_channel = 1; + } else { + current_position = 0; + } + return -1; + case SDLK_END: + if (k->state) return 0; + channel_snap_back = -1; + n = song_find_last_channel(); + if (current_position == 8) { + if (current_channel == n) + current_row = total_rows; + else + current_channel = n; + } else { + current_position = 8; + } + return -1; + case SDLK_INSERT: + if (k->state) return 0; + channel_snap_back = -1; + pattern_insert_rows(current_row, 1, current_channel, 1); + break; + case SDLK_DELETE: + if (k->state) return 0; + channel_snap_back = -1; + pattern_delete_rows(current_row, 1, current_channel, 1); + break; + case SDLK_MINUS: + if (k->state) return 0; + channel_snap_back = -1; + + if (playback_tracing) { + switch (song_get_mode()) { + case MODE_PATTERN_LOOP: + return 1; + case MODE_PLAYING: + prev_order_pattern(); + fix_pb_trace(); + return 1; + }; + } + + if (k->mod & KMOD_SHIFT) + set_current_pattern(current_pattern - 4); + else + set_current_pattern(current_pattern - 1); + return 1; + case SDLK_PLUS: + if (k->state) return 0; + channel_snap_back = -1; + + if (playback_tracing) { + switch (song_get_mode()) { + case MODE_PATTERN_LOOP: + return 1; + case MODE_PLAYING: + next_order_pattern(); + fix_pb_trace(); + return 1; + }; + } + + if (k->mod & KMOD_SHIFT) + set_current_pattern(current_pattern + 4); + else + set_current_pattern(current_pattern + 1); + return 1; + case SDLK_BACKSPACE: + if (k->state) return 0; + channel_snap_back = -1; + if (skip_value) + current_row -= skip_value; + else + current_row--; + return -1; + case SDLK_RETURN: + if (k->state) return 0; + copy_note_to_mask(); + return 1; + case SDLK_SCROLLOCK: + if (k->state) return 0; + midi_playback_tracing = (playback_tracing = !playback_tracing); + status_text_flash("Playback tracing %s", (playback_tracing ? "enabled" : "disabled")); + return 1; + default: + /* bleah */ + if (k->mod & KMOD_SHIFT) { + shift_selection_end(); + previous_shift = 0; + } + + if (k->sym == SDLK_LESS) { + if (k->state) return 0; + if ((status.flags & CLASSIC_MODE) + || current_position != 4) { + set_previous_instrument(); + return 1; + } + /* fall through */ + } else if (k->sym == SDLK_GREATER) { + if (k->state) return 0; + if ((status.flags & CLASSIC_MODE) + || current_position != 4) { + set_next_instrument(); + return 1; + } + /* fall through */ + } else if (k->sym == SDLK_COMMA) { + if (k->state) return 0; + if (current_position > 1) { + edit_copy_mask ^= (1 << edit_pos_to_copy_mask[current_position]); + status.flags |= NEED_UPDATE; + } + return 1; + } + if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) + && playback_tracing && k->is_repeat) + return 0; + + if (!pattern_editor_insert(k)) + return 0; + return -1; + } + + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ +/* this function name's a bit confusing, but this is just what gets + * called from the main key handler. + * pattern_editor_handle_*_key above do the actual work. */ + +static int pattern_editor_handle_key_cb(struct key_event * k) +{ + char buf[4]; + int ret; + int total_rows = song_get_rows_in_pattern(current_pattern); + + /* this is fun; if we're playback tracing, hold shift to "pause the current position" */ + if (k->sym == SDLK_LSHIFT || k->sym == SDLK_RSHIFT) { + if (k->state) { + if (tracing_was_playing) { + midi_playback_tracing = playback_tracing = 1; + song_start_at_pattern(current_pattern, tracing_was_playing-1); + tracing_was_playing = 0; + } + } else if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) && playback_tracing) { + tracing_was_playing = current_row + 1; + midi_playback_tracing = playback_tracing = 0; + song_single_step(current_pattern, current_row); + } + } + + if (k->mod & KMOD_SHIFT) { + if (k->state) return 0; + + if (!previous_shift) + shift_selection_begin(); + previous_shift = 1; + } else if (previous_shift) { + if (k->state) return 0; + + shift_selection_end(); + previous_shift = 0; + } + + if (k->mod & KMOD_ALT) + ret = pattern_editor_handle_alt_key(k); + else if (k->mod & KMOD_CTRL) + ret = pattern_editor_handle_ctrl_key(k); + else + ret = pattern_editor_handle_key(k); + + if (ret != -1) + return ret; + + current_row = CLAMP(current_row, 0, total_rows); + if (current_position > 8) { + if (current_channel < 64) { + current_position = 0; + current_channel++; + } else { + current_position = 8; + } + } else if (current_position < 0) { + if (current_channel > 1) { + current_position = 8; + current_channel--; + } else { + current_position = 0; + } + } + + current_channel = CLAMP(current_channel, 1, 64); + pattern_editor_reposition(); + if (k->mod & KMOD_SHIFT) + shift_selection_update(); + + draw_text(numtostr(3, song_get_num_patterns(), buf), 16, 6, 5, 0); + draw_text(numtostr(3, current_row, buf), 12, 7, 5, 0); + + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void pattern_editor_playback_update(void) +{ + static int prev_row = -1; + static int prev_pattern = -1; + + playing_row = song_get_current_row(); + playing_pattern = song_get_playing_pattern(); + + if ((song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) != 0 + && (playing_row != prev_row || playing_pattern != prev_pattern)) { + + prev_row = playing_row; + prev_pattern = playing_pattern; + + if (playback_tracing) { + set_current_order(song_get_current_order()); + set_current_pattern(playing_pattern); + current_row = playing_row; + pattern_editor_reposition(); + status.flags |= NEED_UPDATE; + } else if (current_pattern == song_get_playing_pattern()) { + status.flags |= NEED_UPDATE; + } + } +} + +/* --------------------------------------------------------------------- */ + +void pattern_editor_load_page(struct page *page) +{ + int i; + for (i = 0; i < 10; i++) { + memset(&undo_history[i],0,sizeof(struct pattern_snap)); + undo_history[i].snap_op = (const unsigned char *)"Empty"; + undo_history[i].freesnapop = 0; + } + for (i = 0; i < 64; i++) { + channel_quick[i] = 1; + } + page->title = "Pattern Editor (F2)"; + page->playback_update = pattern_editor_playback_update; + page->song_changed_cb = pated_history_clear; + page->total_widgets = 1; + page->widgets = widgets_pattern; + page->help_index = HELP_PATTERN_EDITOR; + + create_other(widgets_pattern + 0, 0, pattern_editor_handle_key_cb, pattern_editor_redraw); +} diff --git a/schism/page_preferences.c b/schism/page_preferences.c new file mode 100644 index 000000000..255b541ef --- /dev/null +++ b/schism/page_preferences.c @@ -0,0 +1,559 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include "mixer.h" + +#include + +#include "diskwriter.h" + +/* --------------------------------------------------------------------- */ +/* statics */ + +static struct widget widgets_preferences[32]; + +static const char *interpolation_modes[] = { + "Non-Interpolated", "Linear", + "Cubic Spline", "8-Tap FIR Filter", NULL +}; + +static int interp_group[] = { + 2,3,4,5,-1, +}; + +/* --------------------------------------------------------------------- */ + +static void preferences_draw_const(void) +{ + char buf[80]; + int i; + + draw_text("Master Volume Left", 2, 14, 0, 2); + draw_text("Master Volume Right", 2, 15, 0, 2); + draw_box(21, 13, 27, 16, BOX_THIN | BOX_INNER | BOX_INSET); + + sprintf(buf, "Mixing Mode, Playback Frequency: %dHz", audio_settings.sample_rate); + draw_text(buf, 2, 18, 0, 2); + + for (i = 0; interpolation_modes[i]; i++); + + draw_text("Output Equalizer", 2, 21+i*3, 0, 2); + draw_text( "Low Frequency Band", 7, 23+i*3, 0, 2); + draw_text( "Med Low Frequency Band", 3, 24+i*3, 0, 2); + draw_text("Med High Frequency Band", 2, 25+i*3, 0, 2); + draw_text( "High Frequency Band", 6, 26+i*3, 0, 2); + + draw_box(25, 22+i*3, 47, 27+i*3, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(52, 22+i*3, 74, 27+i*3, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_text("Oversampling", 57, 25, 0, 2); + draw_text("Noise Reduction", 54, 26, 0, 2); + + draw_fill_chars(73, 24, 77, 26, 0); + draw_box(69, 24, 78, 27, BOX_THIN | BOX_INNER | BOX_INSET); + draw_fill_chars(73, 29, 77, 31, 0); + draw_box(69, 28, 78, 32, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_text("XBass", 64, 29, 0, 2); + draw_text("Reverb", 63, 30, 0, 2); + draw_text("Surround", 61, 31, 0, 2); +#if 0 + draw_fill_chars(18, 14, 34, 28, 0); /* left box */ + draw_fill_chars(64, 17, 72, 27, 0); /* right box */ + + /* left side */ + draw_box(17, 13, 35, 29, BOX_THIN | BOX_INNER | BOX_INSET); +#if SCHISM_MIXER_CONTROL == SOUND_MIXER_VOLUME +#else + draw_text(" PCM Volume L", 2, 14, 0, 2); + draw_text(" PCM Volume R", 2, 15, 0, 2); +#endif + draw_text(" Channel Limit", 2, 17, 0, 2); + draw_text(" Sample Rate", 2, 18, 0, 2); + draw_text(" Quality", 2, 19, 0, 2); + draw_text(" Interpolation", 2, 20, 0, 2); + draw_text(" Oversampling", 2, 21, 0, 2); + draw_text(" HQ Resampling", 2, 22, 0, 2); + draw_text("Noise Reduction", 2, 23, 0, 2); + draw_text("Surround Effect", 2, 24, 0, 2); + draw_text(" Time Display", 2, 26, 0, 2); + draw_text(" Classic Mode", 2, 27, 0, 2); + draw_text(" Visualization", 2, 28, 0, 2); + for (n = 18; n < 35; n++) { + draw_char(154, n, 16, 3, 0); + draw_char(154, n, 25, 3, 0); + } + + /* right side */ + draw_box(53, 13, 78, 29, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(63, 16, 73, 28, BOX_THIN | BOX_INNER | BOX_INSET); + draw_text("Modplug DSP Settings", 56, 15, 0, 2); + draw_text(" XBass", 55, 17, 0, 2); + draw_text(" Amount", 55, 18, 0, 2); + draw_text(" Range", 55, 19, 0, 2); + draw_text("Surround", 55, 21, 0, 2); + draw_text(" Depth", 55, 22, 0, 2); + draw_text(" Delay", 55, 23, 0, 2); + draw_text(" Reverb", 55, 25, 0, 2); + draw_text(" Depth", 55, 26, 0, 2); + draw_text(" Delay", 55, 27, 0, 2); + for (n = 64; n < 73; n++) { + draw_char(154, n, 20, 3, 0); + draw_char(154, n, 24, 3, 0); + } + + for (n = 1; n < 79; n++) draw_char(154,n, 30, 1,2); + draw_text(" Disk Writer Output Settings ", 26, 30, 0, 2); + + draw_text(" Sample Rate", 2, 32, 0, 2); + draw_text(" Quality", 2, 33, 0, 2); + draw_text("Output Channels", 2, 34, 0, 2); + + draw_fill_chars(18, 32, 34, 34, 0); + draw_box(17, 31, 35, 35, BOX_THIN | BOX_INNER | BOX_INSET); +#endif + + +#define CORNER_BOTTOM "http://rigelseven.com/schism/" +#ifndef RELEASE_VERSION +#define CORNER_BOTTOM2 "http://sourceforge.net/projects/schismtracker/" + draw_text(CORNER_BOTTOM2, 78 - strlen(CORNER_BOTTOM2), 48, 1, 2); + draw_text(CORNER_BOTTOM, 78 - strlen(CORNER_BOTTOM), 47, 1, 2); +#else + draw_text(CORNER_BOTTOM, 78 - strlen(CORNER_BOTTOM), 48, 1, 2); +#endif +} + +/* --------------------------------------------------------------------- */ + +static void preferences_set_page(void) +{ + int i, j; + mixer_read_volume(&(widgets_preferences[0].d.thumbbar.value), &(widgets_preferences[1].d.thumbbar.value)); + + for (i = j = 0; interpolation_modes[i]; i++) { + if (i == audio_settings.interpolation_mode) { + widgets_preferences[i + 2].d.togglebutton.state=1; + j = 1; + } else { + widgets_preferences[i + 2].d.togglebutton.state=0; + } + } + if (!j) { + audio_settings.interpolation_mode = 0; + widgets_preferences[2].d.togglebutton.state=1; + } + + widgets_preferences[i+11].d.toggle.state = audio_settings.oversampling; + widgets_preferences[i+12].d.toggle.state = audio_settings.noise_reduction; + + widgets_preferences[i+13].d.toggle.state = audio_settings.xbass; + widgets_preferences[i+14].d.toggle.state = audio_settings.reverb; + widgets_preferences[i+15].d.toggle.state = audio_settings.surround; + + for (j = 0; j < 4; j++) { + widgets_preferences[i+2+(j*2)].d.thumbbar.value + = audio_settings.eq_freq[j]; + widgets_preferences[i+3+(j*2)].d.thumbbar.value + = audio_settings.eq_gain[j]; + } + +#if 0 + widgets_preferences[2].d.thumbbar.value = audio_settings.channel_limit; + widgets_preferences[3].d.numentry.value = audio_settings.sample_rate; + widgets_preferences[4].d.menutoggle.state = !!(audio_settings.bits == 16); + + widgets_preferences[5].d.menutoggle.state = audio_settings.interpolation_mode; + widgets_preferences[6].d.toggle.state = audio_settings.oversampling; + widgets_preferences[7].d.toggle.state = audio_settings.hq_resampling; + widgets_preferences[8].d.toggle.state = audio_settings.noise_reduction; + widgets_preferences[9].d.toggle.state = song_get_surround(); + + widgets_preferences[10].d.menutoggle.state = status.time_display; + widgets_preferences[11].d.toggle.state = !!(status.flags & CLASSIC_MODE); + widgets_preferences[12].d.menutoggle.state = status.vis_style; + + widgets_preferences[13].d.toggle.state = audio_settings.xbass; + widgets_preferences[14].d.thumbbar.value = audio_settings.xbass_amount; + widgets_preferences[15].d.thumbbar.value = audio_settings.xbass_range; + widgets_preferences[16].d.toggle.state = audio_settings.surround; /* not s91, the other kind */ + widgets_preferences[17].d.thumbbar.value = audio_settings.surround_depth; + widgets_preferences[18].d.thumbbar.value = audio_settings.surround_delay; + widgets_preferences[19].d.toggle.state = audio_settings.reverb; + widgets_preferences[20].d.thumbbar.value = audio_settings.reverb_depth; + widgets_preferences[21].d.thumbbar.value = audio_settings.reverb_delay; + + widgets_preferences[22].d.numentry.value = diskwriter_output_rate; + widgets_preferences[23].d.menutoggle.state = (diskwriter_output_bits > 8) ? 1 : 0; + widgets_preferences[24].d.menutoggle.state = diskwriter_output_channels; +#endif +} + +/* --------------------------------------------------------------------- */ + +static void change_diskwriter(void) +{ +#if 0 + diskwriter_output_rate = widgets_preferences[22].d.numentry.value; + diskwriter_output_bits = widgets_preferences[23].d.menutoggle.state ? 16 : 8; + diskwriter_output_channels = widgets_preferences[24].d.menutoggle.state; +#endif +} +static void change_volume(void) +{ + mixer_write_volume(widgets_preferences[0].d.thumbbar.value, widgets_preferences[1].d.thumbbar.value); +} + +#define SAVED_AT_EXIT "Audio configuration will be saved at exit" + +static void change_audio(void) +{ +#if 0 + audio_settings.sample_rate = widgets_preferences[3].d.numentry.value; + audio_settings.bits = widgets_preferences[4].d.menutoggle.state ? 16 : 8; + status_text_flash(SAVED_AT_EXIT); +#endif +} + +static void change_misc(void) +{ +#if 0 + song_set_surround(widgets_preferences[9].d.toggle.state); + status.time_display = widgets_preferences[10].d.menutoggle.state; + if (widgets_preferences[11].d.toggle.state) + status.flags |= CLASSIC_MODE; + else + status.flags &= ~CLASSIC_MODE; + status.vis_style = widgets_preferences[12].d.menutoggle.state; +#endif +} + +static void change_eq(void) +{ + int i,j; + for (i = 0; interpolation_modes[i]; i++); + for (j = 0; j < 4; j++) { + audio_settings.eq_freq[j] = widgets_preferences[i+2+(j*2)].d.thumbbar.value; + audio_settings.eq_gain[j] = widgets_preferences[i+3+(j*2)].d.thumbbar.value; + } + song_init_eq(1); +} + +static struct widget dsp_dialog_widgets[4]; +static const char *dsp_dialog_title = "(null)"; +static const char *dsp_dialog_label1 = "(null)"; +static const char *dsp_dialog_label2 = "(null)"; +static int *dsp_value0; +static int *dsp_value1, dsp_value_save1; +static int *dsp_value2, dsp_value_save2; + +static void draw_dsp_dialog_const(void) +{ + int len; + len = strlen(dsp_dialog_title); + draw_text(dsp_dialog_title, 40 - (len/2), 24, 0, 2); + + len = strlen(dsp_dialog_label1); + draw_text(dsp_dialog_label1, 29 - len, 27, 0, 2); + + len = strlen(dsp_dialog_label2); + draw_text(dsp_dialog_label2, 29 - len, 28, 0, 2); + + draw_box(29, 26, 55, 29, BOX_THIN | BOX_INNER | BOX_INSET); +} +static void dsp_dialog_update(UNUSED void*ign) +{ + *dsp_value1 = dsp_dialog_widgets[0].d.thumbbar.value; + *dsp_value2 = dsp_dialog_widgets[1].d.thumbbar.value; + song_init_modplug(); +} +static void dsp_dialog_ok(UNUSED void *ign) +{ + dialog_destroy(); + status_text_flash(SAVED_AT_EXIT); + status.flags |= NEED_UPDATE; +} +static void dsp_dialog_cancel(UNUSED void*ign) +{ + int i; + + *dsp_value0 = 0; + + for (i = 0; interpolation_modes[i]; i++); + widgets_preferences[i+13].d.toggle.state = audio_settings.xbass; + widgets_preferences[i+14].d.toggle.state = audio_settings.reverb; + widgets_preferences[i+15].d.toggle.state = audio_settings.surround; + + *dsp_value1 = dsp_value_save1; + *dsp_value2 = dsp_value_save2; + dialog_destroy(); + dsp_dialog_update(0); + status.flags |= NEED_UPDATE; +} +static void show_dsp_dialog(const char *text, int *ov, + const char *label1, int low1, int high1, int *v1, + const char *label2, int low2, int high2, int *v2) +{ + struct dialog *d; + + dsp_dialog_title = text; + dsp_dialog_label1 = label1; + dsp_dialog_label2 = label2; + create_thumbbar(dsp_dialog_widgets+0, 30, 27, 25, 0, 1, 1, + dsp_dialog_update, low1, high1); + create_thumbbar(dsp_dialog_widgets+1, 30, 28, 25, 0, 2, 2, + dsp_dialog_update, low1, high1); + dsp_value0 = ov; + dsp_value1 = v1; dsp_dialog_widgets[0].d.thumbbar.value = *v1; + dsp_value2 = v2; dsp_dialog_widgets[1].d.thumbbar.value = *v2; + create_button(dsp_dialog_widgets+2, 28,31,8, 1, 2, 2, 3, 3, + dsp_dialog_ok, "OK", 4); + create_button(dsp_dialog_widgets+3, 42,31,8, 1, 2, 2, 3, 3, + dsp_dialog_cancel, "Cancel", 2); + d = dialog_create_custom(20, 22, 40, 12, + dsp_dialog_widgets, + 4, 2, + draw_dsp_dialog_const,0); + d->action_yes = dsp_dialog_ok; + d->action_no = dsp_dialog_cancel; + d->action_cancel = dsp_dialog_cancel; +} + + +static void change_mixer_xbass(void) +{ + int i; + for (i = 0; interpolation_modes[i]; i++); + + audio_settings.xbass = widgets_preferences[i+13].d.toggle.state; + song_init_modplug(); + + if (!audio_settings.xbass) { + status_text_flash(SAVED_AT_EXIT); + return; + } + + show_dsp_dialog("XBass Expansion", &audio_settings.xbass, + "Amount", 0, 100, &audio_settings.xbass_amount, + "Range", 10, 100, &audio_settings.xbass_range); +} +static void change_mixer_reverb(void) +{ + int i; + for (i = 0; interpolation_modes[i]; i++); + + audio_settings.reverb = widgets_preferences[i+14].d.toggle.state; + song_init_modplug(); + + if (!audio_settings.reverb) { + status_text_flash(SAVED_AT_EXIT); + return; + } + + show_dsp_dialog("Reverb", &audio_settings.reverb, + "Depth", 0, 100, &audio_settings.reverb_depth, + "Delay", 40, 200, &audio_settings.reverb_delay); +} +static void change_mixer_surround(void) +{ + int i; + for (i = 0; interpolation_modes[i]; i++); + + audio_settings.surround = widgets_preferences[i+15].d.toggle.state; + song_init_modplug(); + + if (!audio_settings.surround) { + status_text_flash(SAVED_AT_EXIT); + return; + } + + show_dsp_dialog("Surround Expansion", &audio_settings.surround, + "Depth", 0, 100, &audio_settings.surround_depth, + "Delay", 5, 50, &audio_settings.surround_delay); +} +static void change_mixer(void) +{ + int i; + for (i = 0; interpolation_modes[i]; i++) { + if (widgets_preferences[2+i].d.togglebutton.state) { + audio_settings.interpolation_mode = i; + } + } +#if 0 + audio_settings.channel_limit = widgets_preferences[2].d.thumbbar.value; + audio_settings.interpolation_mode = widgets_preferences[5].d.menutoggle.state; + audio_settings.oversampling = widgets_preferences[6].d.toggle.state; + audio_settings.hq_resampling = widgets_preferences[7].d.toggle.state; + audio_settings.noise_reduction = widgets_preferences[8].d.toggle.state; +#endif + song_init_modplug(); + status_text_flash(SAVED_AT_EXIT); +} + +static void change_modplug_dsp(void) +{ +#if 0 + audio_settings.xbass = widgets_preferences[13].d.toggle.state; + audio_settings.xbass_amount = widgets_preferences[14].d.thumbbar.value; + audio_settings.xbass_range = widgets_preferences[15].d.thumbbar.value; + audio_settings.surround = widgets_preferences[16].d.toggle.state; + audio_settings.surround_depth = widgets_preferences[17].d.thumbbar.value; + audio_settings.surround_delay = widgets_preferences[18].d.thumbbar.value; + audio_settings.reverb = widgets_preferences[19].d.toggle.state; + audio_settings.reverb_depth = widgets_preferences[20].d.thumbbar.value; + audio_settings.reverb_delay = widgets_preferences[21].d.thumbbar.value; + song_init_modplug(); +#endif +} + +/* --------------------------------------------------------------------- */ +static void save_config_now(UNUSED void *ign) +{ + /* TODO */ + cfg_midipage_save(); + cfg_atexit_save(); + status_text_flash("Configuration saved"); +} + +void preferences_load_page(struct page *page) +{ + int max = mixer_get_max_volume(); + char buf[64]; + char *ptr; + int i, j, n; + + page->title = "Preferences (Shift-F5)"; + page->draw_const = preferences_draw_const; + page->set_page = preferences_set_page; + page->total_widgets = 16; + page->widgets = widgets_preferences; + page->help_index = HELP_GLOBAL; + + + create_thumbbar(widgets_preferences + 0, 22, 14, 5, 0, 1, 1, change_volume, 0, max); + create_thumbbar(widgets_preferences + 1, 22, 15, 5, 0, 2, 2, change_volume, 0, max); + + for (n = 0; interpolation_modes[n]; n++); + for (i = 0; interpolation_modes[i]; i++) { + + sprintf(buf, "%d Bit, %s", audio_settings.bits, interpolation_modes[i]); + ptr = strdup(buf); + create_togglebutton(widgets_preferences+i+2, + 6, 20 + (i * 3), 26, + i+1, i+3, i+2, n+11, i+3, + change_mixer, + ptr, + 2, + &interp_group); + page->total_widgets++; + } + + for (j = 0; j < 4; j++) { + n = i+(j*2); + if (j == 0) n = i+1; + create_thumbbar(widgets_preferences+i+2+(j*2), + 26, 23+(i*3)+j, + 21, + n, i+(j*2)+4, i+(j*2)+3, + change_eq, + 0, 127); + n = i+(j*2)+5; + if (j == 3) n--; + create_thumbbar(widgets_preferences+i+3+(j*2), + 53, 23+(i*3)+j, + 21, + i+(j*2)+1, n, i+(j*2)+4, + change_eq, + 0, 127); + } + /* default EQ setting */ + widgets_preferences[i+2].d.thumbbar.value = 0; + widgets_preferences[i+4].d.thumbbar.value = 16; + widgets_preferences[i+6].d.thumbbar.value = 96; + widgets_preferences[i+8].d.thumbbar.value = 127; + + create_button(widgets_preferences+i+10, + 2, 44, 27, + i+8, i+10, i+10, i+11, i+11, + save_config_now, + "Save Output Configuration", 2); + + create_toggle(widgets_preferences+i+11, /* Oversampling */ + 70, 25, + i+11,i+12,1+i, i+11,i+12, + change_mixer); + create_toggle(widgets_preferences+i+12, /* Noise Reduction */ + 70, 26, + i+11,i+13,1+i, i+12,i+13, + change_mixer); + create_toggle(widgets_preferences+i+13, /* XBass */ + 70, 29, + i+12,i+14,1+i, i+13,i+14, + change_mixer_xbass); + create_toggle(widgets_preferences+i+14, /* Reverb */ + 70, 30, + i+13,i+15,1+i, i+14,i+15, + change_mixer_reverb); + create_toggle(widgets_preferences+i+15, /* Surround */ + 70, 31, + i+14,i+15,1+i, i+15,0, + change_mixer_surround); + + + +#if 0 + create_thumbbar(widgets_preferences + 2, 18, 17, 17, 1, 3, 13, change_mixer, 4, 256); + create_numentry(widgets_preferences + 3, 18, 18, 7, 2, 4, 14, + change_audio, 4000, 50000, &sample_rate_cursor); + create_menutoggle(widgets_preferences + 4, 18, 19, 3, 5, 15, 15, 15, change_audio, bit_rates); + create_menutoggle(widgets_preferences + 5, 18, 20, 4, 6, 16, 16, 16, + change_mixer, interpolation_modes); + create_toggle(widgets_preferences + 6, 18, 21, 5, 7, 16, 16, 16, change_mixer); + create_toggle(widgets_preferences + 7, 18, 22, 6, 8, 17, 17, 17, change_mixer); + create_toggle(widgets_preferences + 8, 18, 23, 7, 9, 18, 18, 18, change_mixer); + create_toggle(widgets_preferences + 9, 18, 24, 8, 10, 19, 19, 19, change_misc); + create_menutoggle(widgets_preferences + 10, 18, 26, 9, 11, 20, 20, 20, change_misc, time_displays); + create_toggle(widgets_preferences + 11, 18, 27, 10, 12, 21, 21, 21, change_misc); + create_menutoggle(widgets_preferences + 12, 18, 28, 11, 22, 21, 21, 21, change_misc, vis_styles); + + create_toggle(widgets_preferences + 13, 64, 17, 12, 14, 2, 2, 2, change_modplug_dsp); + create_thumbbar(widgets_preferences + 14, 64, 18, 9, 13, 15, 3, change_modplug_dsp, 0, 100); + create_thumbbar(widgets_preferences + 15, 64, 19, 9, 14, 16, 4, change_modplug_dsp, 10, 100); + create_toggle(widgets_preferences + 16, 64, 21, 15, 17, 6, 6, 6, change_modplug_dsp); + create_thumbbar(widgets_preferences + 17, 64, 22, 9, 16, 18, 7, change_modplug_dsp, 0, 100); + create_thumbbar(widgets_preferences + 18, 64, 23, 9, 17, 19, 8, change_modplug_dsp, 5, 50); + create_toggle(widgets_preferences + 19, 64, 25, 18, 20, 9, 9, 9, change_modplug_dsp); + create_thumbbar(widgets_preferences + 20, 64, 26, 9, 19, 21, 10, change_modplug_dsp, 0, 100); + create_thumbbar(widgets_preferences + 21, 64, 27, 9, 20, 22, 11, change_modplug_dsp, 40, 200); + + create_numentry(widgets_preferences + 22, 18, 32, 7, 12, 23, 23, + change_diskwriter, 4000, 50000, &sample_rate_cursor2); + create_menutoggle(widgets_preferences + 23, 18, 33, 22, 24, 23, 23, 24, + change_diskwriter, bit_rates); + create_menutoggle(widgets_preferences + 24, 18, 34, 23, 24, 24, 24, 24, + change_diskwriter, output_channels); +#endif +} diff --git a/schism/page_samples.c b/schism/page_samples.c new file mode 100644 index 000000000..7509446ac --- /dev/null +++ b/schism/page_samples.c @@ -0,0 +1,1355 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "clippy.h" + +#include "song.h" +#include "dmoz.h" +#include "sample-edit.h" + +#include /* for pow */ + +#include "video.h" + +/* --------------------------------------------------------------------- */ +/* static in my attic */ +static struct vgamem_overlay sample_image = { + 55,26,76,29 +}; + +static struct widget widgets_samplelist[20]; +static int vibrato_waveforms[] = { 15, 16, 17, 18, -1 }; + +static int top_sample = 1; +static int current_sample = 1; +static int need_retrigger = -1; +static int last_keyup = -1; + +static int sample_list_cursor_pos = 25; /* the "play" text */ + +/* shared by all the numentry widgets */ +static int sample_numentry_cursor_pos = 0; + +/* for the loops */ +static const char *loop_states[] = { "Off", "On Forwards", "On Ping Pong", NULL }; + +/* playback */ +static int last_note = 61; /* C-5 */ + +/* woo */ +static int _is_magic_sample(int no) +{ + char *name; + song_sample *sample; + int pn; + + sample = song_get_sample(no, &name); + if (sample && name && ((unsigned char)name[23]) == 0xFF) { + pn = ((unsigned char)name[24]); + if (pn < 200) return 1; + } + return 0; +} +/* --------------------------------------------------------------------- */ + +static void sample_list_reposition(void) +{ + if (current_sample < top_sample) { + top_sample = current_sample; + if (top_sample < 1) + top_sample = 1; + } else if (current_sample > top_sample + 34) { + top_sample = current_sample - 34; + } +} + +/* --------------------------------------------------------------------- */ + +int sample_get_current(void) +{ + return current_sample; +} + +void sample_set(int n) +{ + int new_sample = n; + + if (status.current_page == PAGE_SAMPLE_LIST) + new_sample = CLAMP(n, 1, 99); + else + new_sample = CLAMP(n, 0, 99); + + if (current_sample == new_sample) + return; + + status.flags = (status.flags & ~INSTRUMENT_CHANGED) | SAMPLE_CHANGED; + current_sample = new_sample; + sample_list_reposition(); + + /* update_current_instrument(); */ + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* draw the actual list */ + +static void sample_list_draw_list(void) +{ + int pos, n, nl, pn; + char *name; + song_sample *sample; + int has_data, is_selected; + char buf[64]; + int ss, cl, cr; + int is_playing[100]; + + if (clippy_owner(CLIPPY_SELECT) == widgets_samplelist) { + cl = widgets_samplelist[0].clip_start % 25; + cr = widgets_samplelist[0].clip_end % 25; + if (cl > cr) { + ss = cl; + cl = cr; + cr = ss; + } + ss = (widgets_samplelist[0].clip_start / 25); + } else { + ss = -1; + } + + song_get_playing_samples(is_playing); + + /* list */ + for (pos = 0, n = top_sample; pos < 35; pos++, n++) { + sample = song_get_sample(n, &name); + is_selected = (n == current_sample); + has_data = (sample->data != NULL); + + if (sample->played) + draw_char(173, 1, 13 + pos, is_playing[n] ? 3 : 1, 2); + + draw_text(numtostr(2, n, buf), 2, 13 + pos, 0, 2); + + pn = ((unsigned char)name[24]); + if (((unsigned char)name[23]) == 0xFF && pn < 200) { + nl = 23; + draw_text(numtostr(3, (int)pn, buf), 32, 13 + pos, 0, 2); + draw_char('P', 28, 13+pos, 3, 2); + draw_char('a', 29, 13+pos, 3, 2); + draw_char('t', 30, 13+pos, 3, 2); + draw_char('.', 31, 13+pos, 3, 2); + } else { + nl = 25; + draw_char(168, 30, 13 + pos, 2, (is_selected ? 14 : 0)); + draw_text("Play", 31, 13 + pos, (has_data ? 6 : 7), (is_selected ? 14 : 0)); + } + + draw_text_len(name, nl, 5, 13 + pos, 6, (is_selected ? 14 : 0)); + if (ss == n) { + draw_text_len(name + cl, (cr-cl)+1, + 5 + cl, 13 + pos, + 3, 8); + } + } + + /* cursor */ + if (ACTIVE_PAGE.selected_widget == 0) { + pos = current_sample - top_sample; + sample = song_get_sample(current_sample, &name); + has_data = (sample->data != NULL); + + if (pos < 0 || pos > 34) { + /* err... */ + } else if (sample_list_cursor_pos == 25) { + draw_text("Play", 31, 13 + pos, 0, (has_data ? 3 : 6)); + } else { + //draw_char(((sample_list_cursor_pos > (signed) strlen(name)) + // ? 0 : name[sample_list_cursor_pos]), + // sample_list_cursor_pos + 5, 13 + pos, 0, 3); + draw_char(name[sample_list_cursor_pos], + sample_list_cursor_pos + 5, 13 + pos, 0, 3); + } + } + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void sample_list_predraw_hook(void) +{ + char buf[16]; + char *name; + song_sample *sample; + int has_data; + + sample = song_get_sample(current_sample, &name); + has_data = (sample->data != NULL); + + /* set all the values to the current sample */ + + /* default volume + modplug hack here: sample volume has 4x the resolution... + can't deal with this in song.cc (easily) without changing the actual volume of the sample. */ + widgets_samplelist[1].d.thumbbar.value = sample->volume / 4; + + /* global volume */ + widgets_samplelist[2].d.thumbbar.value = sample->global_volume; + + /* default pan (another modplug hack) */ + widgets_samplelist[3].d.toggle.state = (sample->flags & SAMP_PANNING); + widgets_samplelist[4].d.thumbbar.value = sample->panning / 4; + + widgets_samplelist[5].d.thumbbar.value = sample->vib_speed; + widgets_samplelist[6].d.thumbbar.value = sample->vib_depth; + widgets_samplelist[7].d.textentry.text = sample->filename; + widgets_samplelist[8].d.numentry.value = song_sample_get_c5speed(current_sample); + + widgets_samplelist[9].d.menutoggle.state = + (sample->flags & SAMP_LOOP ? (sample->flags & SAMP_LOOP_PINGPONG ? 2 : 1) : 0); + widgets_samplelist[10].d.numentry.value = sample->loop_start; + widgets_samplelist[11].d.numentry.value = sample->loop_end; + widgets_samplelist[12].d.menutoggle.state = + (sample->flags & SAMP_SUSLOOP ? (sample->flags & SAMP_SUSLOOP_PINGPONG ? 2 : 1) : 0); + widgets_samplelist[13].d.numentry.value = sample->sustain_start; + widgets_samplelist[14].d.numentry.value = sample->sustain_end; + + switch (sample->vib_type) { + case VIB_SINE: + togglebutton_set(widgets_samplelist, 15, 0); + break; + case VIB_RAMP_UP: + case VIB_RAMP_DOWN: + togglebutton_set(widgets_samplelist, 16, 0); + break; + case VIB_SQUARE: + togglebutton_set(widgets_samplelist, 17, 0); + break; + case VIB_RANDOM: + togglebutton_set(widgets_samplelist, 18, 0); + break; + } + + widgets_samplelist[19].d.thumbbar.value = ((sample->vib_rate < 64) ? sample->vib_rate * 4 : 255); + + if (sample->flags & SAMP_STEREO) { + draw_text_len((has_data ? (sample->flags & SAMP_16_BIT ? "16 bits Stereo" : "8 bits Stereo") : "No sample"), + 13, 64, 22, 2, 0); + } else { + draw_text_len((has_data ? (sample->flags & SAMP_16_BIT ? "16 bits" : "8 bits") : "No sample"), + 13, 64, 22, 2, 0); + } + draw_text_len(numtostr(0, sample->length, buf), 13, 64, 23, 2, 0); + + draw_sample_data(&sample_image, sample, current_sample); + + if (need_retrigger > -1) { + if (last_keyup > -1) song_keyup(current_sample, -1, last_keyup, -1, 0); + song_keyup(current_sample, -1, need_retrigger, -1, 0); + song_keydown(current_sample, -1, need_retrigger, 64, -1, 0); + if (!song_is_multichannel_mode()) { + last_keyup = need_retrigger; + } + song_update_playing_sample(current_sample); + need_retrigger = -1; + } +} + +/* --------------------------------------------------------------------- */ + +static int sample_list_add_char(char c) +{ + char *name; + + if (c < 32) + return 0; + song_get_sample(current_sample, &name); + text_add_char(name, c, &sample_list_cursor_pos, _is_magic_sample(current_sample) + ? 22 : 25); + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; + return 1; +} + +static void sample_list_delete_char(void) +{ + char *name; + + song_get_sample(current_sample, &name); + text_delete_char(name, &sample_list_cursor_pos, _is_magic_sample(current_sample) + ? 23 : 25); + + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; +} + +static void sample_list_delete_next_char(void) +{ + char *name; + + song_get_sample(current_sample, &name); + text_delete_next_char(name, &sample_list_cursor_pos, _is_magic_sample(current_sample) + ? 23 : 25); + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +static void clear_sample_text(void) +{ + char *name; + + memset(song_get_sample(current_sample, &name)->filename, 0, 14); + if (_is_magic_sample(current_sample)) { + memset(name, 0, 24); + } else { + memset(name, 0, 26); + } + sample_list_cursor_pos = 0; + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +/* --------------------------------------------------------------------- */ + +static struct widget swap_sample_widgets[6]; +static char swap_sample_entry[4] = ""; + + +static void do_swap_sample(UNUSED void *data) +{ + int n = atoi(swap_sample_entry); + + if (n < 1 || n > 99) + return; + song_swap_samples(current_sample, n); +} + +static void swap_sample_draw_const(void) +{ + draw_text("Swap sample with:", 32, 25, 0, 2); + draw_text("Sample", 35, 27, 0, 2); + draw_box(41, 26, 45, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void swap_sample_dialog(void) +{ + struct dialog *dialog; + + swap_sample_entry[0] = 0; + create_textentry(swap_sample_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, swap_sample_entry, 2); + create_button(swap_sample_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(26, 23, 29, 10, swap_sample_widgets, 2, 0, swap_sample_draw_const, NULL); + dialog->action_yes = do_swap_sample; +} + + +static void do_exchange_sample(UNUSED void *data) +{ + int n = atoi(swap_sample_entry); + + if (n < 1 || n > 99) + return; + song_exchange_samples(current_sample, n); +} + +static void exchange_sample_draw_const(void) +{ + draw_text("Exchange sample with:", 30, 25, 0, 2); + draw_text("Sample", 35, 27, 0, 2); + draw_box(41, 26, 45, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void exchange_sample_dialog(void) +{ + struct dialog *dialog; + + swap_sample_entry[0] = 0; + create_textentry(swap_sample_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, swap_sample_entry, 2); + create_button(swap_sample_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(26, 23, 29, 10, swap_sample_widgets, 2, 0, + exchange_sample_draw_const, NULL); + dialog->action_yes = do_exchange_sample; +} + +/* --------------------------------------------------------------------- */ + +static int sample_list_handle_key_on_list(struct key_event * k) +{ + int new_sample = current_sample; + int new_cursor_pos = sample_list_cursor_pos; + char *name; + int i; + + if (k->mouse == MOUSE_CLICK && k->mouse_button == MOUSE_BUTTON_MIDDLE) { + if (k->state) status.flags |= CLIPPY_PASTE_SELECTION; + return 1; + } else if (!k->state && k->mouse && k->x >= 5 && k->y >= 13 && k->y <= 47 && k->x <= 34) { + if (k->mouse == MOUSE_SCROLL_UP) { + top_sample--; + if (top_sample < 1) top_sample = 1; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + top_sample++; + if (top_sample > (99-34)) top_sample = (99-34); + status.flags |= NEED_UPDATE; + return 1; + } else { + if (k->state || k->sy == k->y) { + new_sample = (k->y - 13) + top_sample; + } + if (k->x <= 29) { /* and button1 */ + if (k->mouse == MOUSE_DBLCLICK) { + set_page(PAGE_LOAD_SAMPLE); + status.flags |= NEED_UPDATE; + return 1; + } else if (new_sample == current_sample + || sample_list_cursor_pos != 25) { + new_cursor_pos = k->x - 5; + } + } else if (k->state || k->x == k->sx) { + if (k->mouse == MOUSE_DBLCLICK + || (new_sample == current_sample + && sample_list_cursor_pos == 25)) { + need_retrigger = 61; + status.flags |= NEED_UPDATE; + } + new_cursor_pos = 25; + } + } + } else { + switch (k->sym) { + case SDLK_LEFT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_cursor_pos--; + break; + case SDLK_RIGHT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_cursor_pos++; + break; + case SDLK_HOME: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_cursor_pos = 0; + break; + case SDLK_END: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_cursor_pos = 25; + break; + case SDLK_UP: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_sample--; + break; + case SDLK_DOWN: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_sample++; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + new_sample = 1; + } else { + new_sample -= 16; + } + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + new_sample = 99; + } else { + new_sample += 16; + } + break; + case SDLK_RETURN: + if (!k->state) return 0; + set_page(PAGE_LOAD_SAMPLE); + break; + case SDLK_BACKSPACE: + if (k->state) return 0; + if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0) { + if (sample_list_cursor_pos < 25) { + sample_list_delete_char(); + } + return 1; + } else if (k->mod & KMOD_CTRL) { + /* just for compatibility with every weird thing + * Impulse Tracker does ^_^ */ + if (sample_list_cursor_pos < 25) { + sample_list_add_char(127); + } + return 1; + } + return 0; + case SDLK_DELETE: + if (k->state) return 0; + if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0) { + if (sample_list_cursor_pos < 25) { + sample_list_delete_next_char(); + } + return 1; + } + return 0; + default: + if (k->mod & KMOD_ALT) { + if (k->sym == SDLK_c) { + clear_sample_text(); + return 1; + } + } else if ((k->mod & KMOD_CTRL) == 0 && sample_list_cursor_pos < 25) { + if (k->state) return 1; + int c = unicode_to_ascii(k->unicode); + if (c == 0) + return 0; + return sample_list_add_char(c); + } + return 0; + } + } + + new_sample = CLAMP(new_sample, 1, 99); + new_cursor_pos = CLAMP(new_cursor_pos, 0, 25); + + if (new_sample != current_sample) { + sample_set(new_sample); + sample_list_reposition(); + } else if (new_cursor_pos != sample_list_cursor_pos) { + sample_list_cursor_pos = new_cursor_pos; + } else { + return 0; + } + if (k->mouse && k->x != k->sx) { + song_get_sample(current_sample, &name); + widgets_samplelist[0].clip_start = (k->sx - 5) + (current_sample*25); + widgets_samplelist[0].clip_end = (k->x - 5) + (current_sample*25); + if (widgets_samplelist[0].clip_start < widgets_samplelist[0].clip_end) { + clippy_select(widgets_samplelist, + name + (k->sx - 5), + (k->x - k->sx)); + } else { + clippy_select(widgets_samplelist, + name + (k->x - 5), + (k->sx - k->x)); + } + } + + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ +/* alt key dialog callbacks. + * these don't need to do any actual redrawing, because the screen gets + * redrawn anyway when the dialog is cleared. */ + +/* TODO | Deleting a sample doesn't require stopping the song because Modplug cleanly stops any playing copies + * TODO | of the sample when it destroys it. It'd be nice if the other sample manipulations (quality convert, + * TODO | loop cut, etc.) did this as well. */ + +static void do_sign_convert(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + sample_sign_convert(sample); +} + +static void do_quality_convert(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + song_stop(); + sample_toggle_quality(sample, 1); +} + +static void do_quality_toggle(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + song_stop(); + sample_toggle_quality(sample, 0); +} + +static void do_delete_sample(UNUSED void *data) +{ + song_clear_sample(current_sample); +} + +static void do_post_loop_cut(UNUSED void *bweh) /* I'm already using 'data'. */ +{ + song_sample *sample = song_get_sample(current_sample, NULL); + signed char *data; + unsigned long pos = MAX(sample->loop_end, sample->sustain_end); + int bytes = pos * ((sample->flags & SAMP_16_BIT) ? 2 : 1) + * ((sample->flags & SAMP_STEREO) ? 2 : 1); + + if (pos == sample->length) + return; + + song_stop(); + + data = song_sample_allocate(bytes); + memcpy(data, sample->data, bytes); + song_sample_free(sample->data); + sample->data = data; + sample->length = pos; +} + +static void do_pre_loop_cut(UNUSED void *bweh) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + signed char *data; + unsigned long pos = ((sample->flags & SAMP_SUSLOOP) + ? MIN(sample->loop_start, sample->sustain_start) + : sample->loop_start); + int start_byte = pos * ((sample->flags & SAMP_16_BIT) ? 2 : 1) + * ((sample->flags & SAMP_STEREO) ? 2 : 1); + int bytes = (sample->length - pos) * ((sample->flags & SAMP_16_BIT) ? 2 : 1) + * ((sample->flags & SAMP_STEREO) ? 2 : 1); + + if (pos == 0) + return; + + song_stop(); + + data = song_sample_allocate(bytes); + memcpy(data, sample->data + start_byte, bytes); + song_sample_free(sample->data); + sample->data = data; + sample->length -= pos; + + if (sample->loop_start > pos) + sample->loop_start -= pos; + else + sample->loop_start = 0; + if (sample->sustain_start > pos) + sample->sustain_start -= pos; + else + sample->sustain_start = 0; + if (sample->loop_end > pos) + sample->loop_end -= pos; + else + sample->loop_end = 0; + if (sample->sustain_end > pos) + sample->sustain_end -= pos; + else + sample->sustain_end = 0; +} + +static void do_centralise(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + sample_centralise(sample); +} + +/* --------------------------------------------------------------------- */ + +static struct widget sample_amplify_widgets[3]; + +static void do_amplify(UNUSED void *data) +{ + sample_amplify(song_get_sample(current_sample, NULL), sample_amplify_widgets[0].d.thumbbar.value); +} + +static void sample_amplify_draw_const(void) +{ + draw_text("Sample Amplification %", 29, 27, 0, 2); + draw_box(12, 29, 64, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static void sample_amplify_dialog(void) +{ + struct dialog *dialog; + int percent = sample_get_amplify_amount(song_get_sample(current_sample, NULL)); + + percent = MIN(percent, 400); + + create_thumbbar(sample_amplify_widgets + 0, 13, 30, 51, 0, 1, 1, NULL, 0, 400); + sample_amplify_widgets[0].d.thumbbar.value = percent; + create_button(sample_amplify_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); + create_button(sample_amplify_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + + dialog = dialog_create_custom(9, 25, 61, 11, sample_amplify_widgets, + 3, 0, sample_amplify_draw_const, NULL); + dialog->action_yes = do_amplify; +} + +/* --------------------------------------------------------------------- */ + +/* filename can be NULL, in which case the sample filename is used (quick save) */ +struct sample_save_hunk { + char *path; + int format_id; +}; +static void abort_save_sample(void *ptr) +{ + struct sample_save_hunk *foo = (struct sample_save_hunk *)ptr; + free(foo->path); + free(foo); +} +static void do_save_sample(void *ptr) +{ + struct sample_save_hunk *foo = (struct sample_save_hunk *)ptr; + if (song_save_sample(current_sample, foo->path, foo->format_id)) + status_text_flash("%s sample saved (sample %d)", + sample_save_formats[foo->format_id].name, + current_sample); + else + status_text_flash("Error: Sample %d NOT saved! (No Filename?)", current_sample); + abort_save_sample(ptr); /* deftly named :) */ + +} +static void sample_save(const char *filename, int format_id) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + char *ptr = dmoz_path_concat(cfg_dir_samples, filename + ? filename + : sample->filename); + struct sample_save_hunk *poop; + struct stat buf; + + if (filename && *filename && stat(ptr, &buf) == 0) { + if (S_ISDIR(buf.st_mode)) { +#if 0 + dialog_create(DIALOG_OK, "Already exists, but not a regular file", NULL, NULL, 0, NULL); +#endif + status_text_flash("%s is a directory", + filename ? filename : sample->filename); + + } else if (S_ISREG(buf.st_mode)) { + poop = mem_alloc(sizeof(struct sample_save_hunk)); + poop->path = ptr; + poop->format_id = format_id; + dialog_create(DIALOG_OK_CANCEL, + "Overwrite file?", do_save_sample, + abort_save_sample, 1, poop); + } else { +#if 0 + dialog_create(DIALOG_OK, "Already exists, but not a regular file", NULL, NULL, 0, NULL); +#endif + status_text_flash("%s is not a regular file", + filename ? filename : sample->filename); + } + } else if (song_save_sample(current_sample, ptr, format_id)) + status_text_flash("%s sample saved (sample %d)", sample_save_formats[format_id].name, current_sample); + else + status_text_flash("Error: Sample %d NOT saved! (No Filename?)", current_sample); + free(ptr); +} +/* resize sample dialog */ +static struct widget resize_sample_widgets[2]; +static int resize_sample_cursor; +static void resize_sample_cancel(UNUSED void *data) +{ + dialog_destroy(); +} +static void do_resize_sample_aa(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + unsigned int newlen = resize_sample_widgets[0].d.numentry.value; + sample_resize(sample, newlen, 1); +} +static void do_resize_sample(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + unsigned int newlen = resize_sample_widgets[0].d.numentry.value; + sample_resize(sample, newlen, 0); +} +static void resize_sample_draw_const(void) +{ + draw_text("Resize Sample", 34, 24, 3, 2); + draw_text("New Length", 31, 27, 0, 2); + draw_box(41, 26, 49, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} +static void resize_sample_dialog(int aa) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + struct dialog *dialog; + + resize_sample_cursor = 0; + create_numentry(resize_sample_widgets + 0, 42, 27, 7, 0, 1, 1, NULL, 0, 9999999, &resize_sample_cursor); + resize_sample_widgets[0].d.numentry.value = sample->length; + create_button(resize_sample_widgets + 1, 36, 30, 6, 0, 1, 1, 1, 1, resize_sample_cancel, "Cancel", 1); + dialog = dialog_create_custom(26, 22, 29, 11, resize_sample_widgets, 2, 0, resize_sample_draw_const, NULL); + if (aa) { + dialog->action_yes = do_resize_sample_aa; + } else { + dialog->action_yes = do_resize_sample; + } +} + + +/* export sample dialog */ + +static struct widget export_sample_widgets[6]; +static char export_sample_filename[NAME_MAX + 1] = ""; +static char export_sample_options[64] = "fnord"; +static int export_sample_format = 0; + +static void do_export_sample(UNUSED void *data) +{ + sample_save(export_sample_filename, export_sample_format); +} + +static void export_sample_list_draw(void) +{ + int n, focused = (*selected_widget == 3); + + draw_fill_chars(53, 24, 56, 31, 0); + for (n = 0; n < SSMP_SENTINEL; n++) { + int fg = 6, bg = 0; + if (focused && n == export_sample_format) { + fg = 0; + bg = 3; + } else if (n == export_sample_format) { + bg = 14; + } + draw_text_len(sample_save_formats[n].ext, 4, 53, 24 + n, fg, bg); + } +} + +static int export_sample_list_handle_key(struct key_event * k) +{ + int new_format = export_sample_format; + + if (k->state) return 0; + switch (k->sym) { + case SDLK_UP: + if (!NO_MODIFIER(k->mod)) + return 0; + new_format--; + break; + case SDLK_DOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + new_format++; + break; + case SDLK_PAGEUP: + case SDLK_HOME: + if (!NO_MODIFIER(k->mod)) + return 0; + new_format = 0; + break; + case SDLK_PAGEDOWN: + case SDLK_END: + if (!NO_MODIFIER(k->mod)) + return 0; + new_format = SSMP_SENTINEL - 1; + break; + case SDLK_TAB: + if (k->mod & KMOD_SHIFT) { + change_focus_to(0); + return 1; + } + /* fall through */ + case SDLK_LEFT: + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(0); /* should focus 0/1/2 depending on what's closest */ + return 1; + default: + return 0; + } + + new_format = CLAMP(new_format, 0, SSMP_SENTINEL - 1); + if (new_format != export_sample_format) { + /* update the option string */ + export_sample_format = new_format; + status.flags |= NEED_UPDATE; + } + + return 1; +} + +static void export_sample_draw_const(void) +{ + draw_text("Export Sample", 34, 21, 0, 2); + + draw_text("Filename", 24, 24, 0, 2); + draw_box(32, 23, 51, 25, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_text("Options", 25, 27, 0, 2); + draw_box(32, 26, 51, 28, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_box(52, 23, 57, 32, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void configure_options(void) +{ + dialog_create(DIALOG_OK, "This doesn't do anything yet. Stay tuned! :)", NULL, NULL, 0, NULL); +} + +static void export_sample_dialog(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + struct dialog *dialog; + + create_textentry(export_sample_widgets + 0, 33, 24, 18, 0, 1, 3, NULL, export_sample_filename, NAME_MAX); + create_textentry(export_sample_widgets + 1, 33, 27, 18, 0, 2, 3, NULL, export_sample_options, 256); + create_button(export_sample_widgets + 2, 33, 30, 9, 1, 4, 3, 3, 3, configure_options, "Configure", 1); + create_other(export_sample_widgets + 3, 0, export_sample_list_handle_key, export_sample_list_draw); + create_button(export_sample_widgets + 4, 31, 35, 6, 2, 4, 5, 5, 5, dialog_yes_NULL, "OK", 3); + create_button(export_sample_widgets + 5, 42, 35, 6, 3, 5, 4, 4, 4, dialog_cancel_NULL, "Cancel", 1); + + strncpy(export_sample_filename, sample->filename, NAME_MAX); + export_sample_filename[NAME_MAX] = 0; + + dialog = dialog_create_custom(21, 20, 39, 18, export_sample_widgets, 6, 0, export_sample_draw_const, NULL); + dialog->action_yes = do_export_sample; +} + +/* --------------------------------------------------------------------- */ + +static void sample_list_handle_alt_key(struct key_event * k) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + + if (k->state) return; + switch (k->sym) { + case SDLK_a: + if (sample->data != NULL) + dialog_create(DIALOG_OK_CANCEL, "Convert sample?", do_sign_convert, NULL, 0, NULL); + return; + case SDLK_b: + /* this statement is too complicated :P */ + if (!(sample->data == NULL + || (sample->flags & SAMP_SUSLOOP && sample->loop_start == 0 && sample->sustain_start == 0) + || (sample->loop_start == 0))) + dialog_create(DIALOG_OK_CANCEL, "Cut sample?", do_pre_loop_cut, NULL, 1, NULL); + return; + case SDLK_d: + dialog_create(DIALOG_OK_CANCEL, "Delete sample?", do_delete_sample, NULL, 1, NULL); + return; + case SDLK_e: + resize_sample_dialog(1); + break; + case SDLK_f: + resize_sample_dialog(0); + break; + case SDLK_g: + if (sample->data == NULL) + return; + sample_reverse(sample); + break; + case SDLK_h: + if (sample->data != NULL) + dialog_create(DIALOG_YES_NO, "Centralise sample?", do_centralise, NULL, 0, NULL); + return; + case SDLK_i: + if (sample->data == NULL) + return; + sample_invert(sample); + break; + case SDLK_l: + if (sample->data != NULL && (sample->loop_end != 0 || sample->sustain_end != 0)) + dialog_create(DIALOG_OK_CANCEL, "Cut sample?", do_post_loop_cut, NULL, 1, NULL); + return; + case SDLK_m: + if (sample->data != NULL) + sample_amplify_dialog(); + return; + case SDLK_n: + song_toggle_multichannel_mode(); + return; + case SDLK_q: + if (sample->data != NULL) + dialog_create(DIALOG_YES_NO, "Convert sample?", + do_quality_convert, do_quality_toggle, 0, NULL); + return; + case SDLK_o: + sample_save(NULL, SSMP_ITS); + return; + case SDLK_s: + swap_sample_dialog(); + return; + case SDLK_t: + export_sample_dialog(); + return; + case SDLK_w: + sample_save(NULL, SSMP_RAW); + return; + case SDLK_x: + exchange_sample_dialog(); + return; + case SDLK_INSERT: + song_insert_sample_slot(current_sample); + status.flags |= NEED_UPDATE; + return; + case SDLK_DELETE: + song_remove_sample_slot(current_sample); + status.flags |= NEED_UPDATE; + return; + default: + return; + } + + status.flags |= NEED_UPDATE; +} + +static inline unsigned long calc_halftone(unsigned long hz, int rel) +{ + /* You wouldn't believe how long it took for me to figure this out. I had to calculate the + logarithmic regression of the values that Impulse Tracker produced and figure out what the + coefficients had to do with the number twelve... I don't imagine I'll forget this formula + now. :) + (FIXME: integer math and all that. Not that I exactly care, since this isn't at all + performance-critical, but in principle it'd be a good idea.) */ + return pow(2, rel / 12.0) * hz + 0.5; +} + +static void sample_list_handle_key(struct key_event * k) +{ + int new_sample = current_sample; + unsigned int newspd; + song_sample *sample = song_get_sample(current_sample, NULL); + + switch (k->sym) { + case SDLK_SPACE: + need_retrigger = last_note; + status.flags |= NEED_UPDATE; + return; + case SDLK_PLUS: + if (k->state) return; + if (k->mod & KMOD_ALT) { + newspd = sample->speed * 2; + song_sample_set_c5speed(current_sample, newspd); + } else if (k->mod & KMOD_CTRL) { + newspd = calc_halftone(sample->speed, 1); + song_sample_set_c5speed(current_sample, newspd); + } + status.flags |= NEED_UPDATE; + return; + case SDLK_MINUS: + if (k->state) return; + if (k->mod & KMOD_ALT) { + newspd = sample->speed / 2; + song_sample_set_c5speed(current_sample, newspd); + } else if (k->mod & KMOD_CTRL) { + newspd = calc_halftone(sample->speed, -1); + song_sample_set_c5speed(current_sample, newspd); + } + status.flags |= NEED_UPDATE; + return; + + case SDLK_COMMA: + case SDLK_LESS: + if (k->state) return; + song_change_current_play_channel(-1, 0); + return; + case SDLK_PERIOD: + case SDLK_GREATER: + if (k->state) return; + song_change_current_play_channel(1, 0); + return; + case SDLK_PAGEUP: + if (k->state) return; + new_sample--; + break; + case SDLK_PAGEDOWN: + if (k->state) return; + new_sample++; + break; + default: + if (k->mod & KMOD_ALT) { + if (k->state) return; + sample_list_handle_alt_key(k); + } else { + int n, v; + if (k->midi_note > -1) { + n = k->midi_note; + if (k->midi_volume > -1) { + v = k->midi_volume / 2; + } else { + v = 64; + } + } else { + n = kbd_get_note(k); + v = 64; + if (n <= 0 || n > 120) + return; + } + if (!k->state && !k->is_repeat) { + last_note = need_retrigger = n; + status.flags |= NEED_UPDATE; + } + } + return; + } + + new_sample = CLAMP(new_sample, 1, 99); + + if (new_sample != current_sample) { + sample_set(new_sample); + sample_list_reposition(); + status.flags |= NEED_UPDATE; + } +} + +/* --------------------------------------------------------------------- */ +/* wheesh */ + +static void sample_list_draw_const(void) +{ + int n; + + draw_box(4, 12, 35, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(63, 12, 77, 24, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_box(36, 12, 53, 18, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 15, 47, 17, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(36, 19, 53, 25, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 22, 47, 24, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(36, 26, 53, 33, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 29, 47, 32, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(36, 35, 53, 41, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 38, 47, 40, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(36, 42, 53, 48, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 45, 47, 47, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(54, 25, 77, 30, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(54, 31, 77, 41, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(54, 42, 77, 48, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(55, 45, 72, 47, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_fill_chars(41, 30, 46, 30, 0); + draw_fill_chars(64, 13, 76, 23, 0); + + draw_text("Default Volume", 38, 14, 0, 2); + draw_text("Global Volume", 38, 21, 0, 2); + draw_text("Default Pan", 39, 28, 0, 2); + draw_text("Vibrato Speed", 38, 37, 0, 2); + draw_text("Vibrato Depth", 38, 44, 0, 2); + draw_text("Filename", 55, 13, 0, 2); + draw_text("Speed", 58, 14, 0, 2); + draw_text("Loop", 59, 15, 0, 2); + draw_text("LoopBeg", 56, 16, 0, 2); + draw_text("LoopEnd", 56, 17, 0, 2); + draw_text("SusLoop", 56, 18, 0, 2); + draw_text("SusLBeg", 56, 19, 0, 2); + draw_text("SusLEnd", 56, 20, 0, 2); + draw_text("Quality", 56, 22, 0, 2); + draw_text("Length", 57, 23, 0, 2); + draw_text("Vibrato Waveform", 58, 33, 0, 2); + draw_text("Vibrato Rate", 60, 44, 0, 2); + + for (n = 0; n < 13; n++) + draw_char(154, 64 + n, 21, 3, 0); +} + +/* --------------------------------------------------------------------- */ +/* wow. this got ugly. */ + +/* callback for the loop menu toggles */ +static void update_sample_loop_flags(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + + /* these switch statements fall through */ + sample->flags &= ~(SAMP_LOOP | SAMP_LOOP_PINGPONG | SAMP_SUSLOOP | SAMP_SUSLOOP_PINGPONG); + switch (widgets_samplelist[9].d.menutoggle.state) { + case 2: sample->flags |= SAMP_LOOP_PINGPONG; + case 1: sample->flags |= SAMP_LOOP; + } + + switch (widgets_samplelist[12].d.menutoggle.state) { + case 2: sample->flags |= SAMP_SUSLOOP_PINGPONG; + case 1: sample->flags |= SAMP_SUSLOOP; + } + + if (sample->flags & SAMP_LOOP) { + if (sample->loop_start == sample->length) + sample->loop_start = 0; + if (sample->loop_end <= sample->loop_start) + sample->loop_end = sample->length; + } + + if (sample->flags & SAMP_SUSLOOP) { + if (sample->sustain_start == sample->length) + sample->sustain_start = 0; + if (sample->sustain_end <= sample->sustain_start) + sample->sustain_end = sample->length; + } + + /* update any samples currently playing */ + song_update_playing_sample(current_sample); + + status.flags |= NEED_UPDATE; +} + +/* callback for the loop numentries */ +static void update_sample_loop_points(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + int flags_changed = 0; + + /* 9 = loop toggle, 10 = loop start, 11 = loop end */ + if ((unsigned long) widgets_samplelist[10].d.numentry.value > sample->length - 1) + widgets_samplelist[10].d.numentry.value = sample->length - 1; + if (widgets_samplelist[11].d.numentry.value <= widgets_samplelist[10].d.numentry.value) { + widgets_samplelist[9].d.menutoggle.state = 0; + flags_changed = 1; + } else if ((unsigned long) widgets_samplelist[11].d.numentry.value > sample->length) { + widgets_samplelist[11].d.numentry.value = sample->length; + } + if (sample->loop_start != widgets_samplelist[10].d.numentry.value + || sample->loop_end != widgets_samplelist[11].d.numentry.value) { + flags_changed = 1; + } + sample->loop_start = widgets_samplelist[10].d.numentry.value; + sample->loop_end = widgets_samplelist[11].d.numentry.value; + + /* 12 = sus toggle, 13 = sus start, 14 = sus end */ + if ((unsigned long) widgets_samplelist[13].d.numentry.value > sample->length - 1) + widgets_samplelist[13].d.numentry.value = sample->length - 1; + if (widgets_samplelist[14].d.numentry.value <= widgets_samplelist[13].d.numentry.value) { + widgets_samplelist[12].d.menutoggle.state = 0; + flags_changed = 1; + } else if ((unsigned long) widgets_samplelist[14].d.numentry.value > sample->length) { + widgets_samplelist[14].d.numentry.value = sample->length; + } + if (sample->sustain_start != widgets_samplelist[13].d.numentry.value + || sample->sustain_end != widgets_samplelist[14].d.numentry.value) { + flags_changed = 1; + } + sample->sustain_start = widgets_samplelist[13].d.numentry.value; + sample->sustain_end = widgets_samplelist[14].d.numentry.value; + + if (flags_changed) { + update_sample_loop_flags(); + } + + status.flags |= NEED_UPDATE; + /* IT retriggers */ + if (!(sample->flags & (SAMP_LOOP|SAMP_SUSLOOP))) { + need_retrigger = last_note; + status.flags |= NEED_UPDATE; + } +} + +/* --------------------------------------------------------------------- */ + +static void update_values_in_song(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + + /* a few more modplug hacks here... */ + sample->volume = widgets_samplelist[1].d.thumbbar.value * 4; + sample->global_volume = widgets_samplelist[2].d.thumbbar.value; + if (widgets_samplelist[3].d.toggle.state) + sample->flags |= SAMP_PANNING; + else + sample->flags &= ~SAMP_PANNING; + sample->vib_speed = widgets_samplelist[5].d.thumbbar.value; + sample->vib_depth = widgets_samplelist[6].d.thumbbar.value; + + if (widgets_samplelist[15].d.togglebutton.state) + sample->vib_type = VIB_SINE; + else if (widgets_samplelist[16].d.togglebutton.state) + sample->vib_type = VIB_RAMP_DOWN; + else if (widgets_samplelist[17].d.togglebutton.state) + sample->vib_type = VIB_SQUARE; + else + sample->vib_type = VIB_RANDOM; + sample->vib_rate = (widgets_samplelist[19].d.thumbbar.value + 3) / 4; +} + +static void update_sample_speed(void) +{ + song_sample_set_c5speed(current_sample, + widgets_samplelist[8].d.numentry.value); + need_retrigger = last_note; + status.flags |= NEED_UPDATE; +} + +static void update_panning(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + + sample->flags |= SAMP_PANNING; + sample->panning = widgets_samplelist[4].d.thumbbar.value * 4; + + widgets_samplelist[3].d.toggle.state = 1; +} + +/* --------------------------------------------------------------------- */ + +void sample_list_load_page(struct page *page) +{ + vgamem_font_reserve(&sample_image); + + page->title = "Sample List (F3)"; + page->draw_const = sample_list_draw_const; + page->predraw_hook = sample_list_predraw_hook; + page->handle_key = sample_list_handle_key; + page->total_widgets = 20; + page->widgets = widgets_samplelist; + page->help_index = HELP_SAMPLE_LIST; + + /* 0 = sample list */ + create_other(widgets_samplelist + 0, 1, sample_list_handle_key_on_list, sample_list_draw_list); + widgets_samplelist[0].x = 5; + widgets_samplelist[0].y = 13; + widgets_samplelist[0].width = 30; + widgets_samplelist[0].height = 35; + + /* 1 -> 6 = middle column */ + create_thumbbar(widgets_samplelist + 1, 38, 16, 9, 1, 2, 7, update_values_in_song, 0, 64); + create_thumbbar(widgets_samplelist + 2, 38, 23, 9, 1, 3, 7, update_values_in_song, 0, 64); + create_toggle(widgets_samplelist + 3, 38, 30, 2, 4, 0, 7, 7, update_values_in_song); + create_thumbbar(widgets_samplelist + 4, 38, 31, 9, 3, 5, 7, update_panning, 0, 64); + create_thumbbar(widgets_samplelist + 5, 38, 39, 9, 4, 6, 15, update_values_in_song, 0, 64); + create_thumbbar(widgets_samplelist + 6, 38, 46, 9, 5, 6, 19, update_values_in_song, 0, 32); + /* 7 -> 14 = top right box */ + create_textentry(widgets_samplelist + 7, 64, 13, 13, 7, 8, 0, NULL, NULL, 12); + create_numentry(widgets_samplelist + 8, 64, 14, 7, 7, 9, 0, + update_sample_speed, 0, 9999999, &sample_numentry_cursor_pos); + create_menutoggle(widgets_samplelist + 9, 64, 15, 8, 10, 1, 0, 0, update_sample_loop_flags, loop_states); + create_numentry(widgets_samplelist + 10, 64, 16, 7, 9, 11, 0, + update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); + create_numentry(widgets_samplelist + 11, 64, 17, 7, 10, 12, 0, + update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); + create_menutoggle(widgets_samplelist + 12, 64, 18, 11, 13, 1, 0, 0, + update_sample_loop_flags, loop_states); + create_numentry(widgets_samplelist + 13, 64, 19, 7, 12, 14, 0, + update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); + create_numentry(widgets_samplelist + 14, 64, 20, 7, 13, 15, 0, + update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); + /* 15 -> 18 = vibrato waveforms */ + create_togglebutton(widgets_samplelist + 15, 57, 36, 6, 14, 17, 5, + 16, 16, update_values_in_song, "\xb9\xba", 3, vibrato_waveforms); + create_togglebutton(widgets_samplelist + 16, 67, 36, 6, 14, 18, 15, + 0, 0, update_values_in_song, "\xbd\xbe", 3, vibrato_waveforms); + create_togglebutton(widgets_samplelist + 17, 57, 39, 6, 15, 19, 5, + 18, 18, update_values_in_song, "\xbb\xbc", 3, vibrato_waveforms); + create_togglebutton(widgets_samplelist + 18, 67, 39, 6, 16, 19, 17, + 0, 0, update_values_in_song, "Random", 1, vibrato_waveforms); + /* 19 = vibrato rate */ + create_thumbbar(widgets_samplelist + 19, 56, 46, 16, 17, 19, 0, update_values_in_song, 0, 255); +} diff --git a/schism/page_vars.c b/schism/page_vars.c new file mode 100644 index 000000000..913c46f35 --- /dev/null +++ b/schism/page_vars.c @@ -0,0 +1,215 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ +/* static variables */ + +static struct widget widgets_vars[18]; +static int group_control[] = { 8, 9, -1 }; +static int group_playback[] = { 10, 11, -1 }; +static int group_slides[] = { 12, 13, -1 }; + +/* --------------------------------------------------------------------- */ + +static inline void update_song_title(void) +{ + draw_text_len(song_get_title(), 25, 12, 3, 5, 0); + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void song_vars_draw_const(void) +{ + int n; + + draw_box(16, 15, 43, 17, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(16, 18, 50, 21, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(16, 22, 34, 28, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(12, 41, 78, 45, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_fill_chars(20, 26, 33, 27, 0); + + draw_text("Song Variables", 33, 13, 3, 2); + draw_text("Song Name", 7, 16, 0, 2); + draw_text("Initial Tempo", 3, 19, 0, 2); + draw_text("Initial Speed", 3, 20, 0, 2); + draw_text("Global Volume", 3, 23, 0, 2); + draw_text("Mixing Volume", 3, 24, 0, 2); + draw_text("Separation", 6, 25, 0, 2); + draw_text("Old Effects", 5, 26, 0, 2); + draw_text("Compatible Gxx", 2, 27, 0, 2); + draw_text("Control", 9, 30, 0, 2); + draw_text("Playback", 8, 33, 0, 2); + draw_text("Pitch Slides", 4, 36, 0, 2); + draw_text("Directories", 34, 40, 3, 2); + draw_text("Module", 6, 42, 0, 2); + draw_text("Sample", 6, 43, 0, 2); + draw_text("Instrument", 2, 44, 0, 2); + + for (n = 1; n < 79; n++) + draw_char(129, n, 39, 1, 2); +} + +/* --------------------------------------------------------------------- */ +static void init_instruments(UNUSED void *data) +{ + song_init_instruments(-1); +} +static void update_values_in_song(void) +{ + song_set_initial_tempo(widgets_vars[1].d.thumbbar.value); + song_set_initial_speed(widgets_vars[2].d.thumbbar.value); + song_set_initial_global_volume(widgets_vars[3].d.thumbbar.value); + song_set_mixing_volume(widgets_vars[4].d.thumbbar.value); + song_set_separation(widgets_vars[5].d.thumbbar.value); + song_set_old_effects(widgets_vars[6].d.toggle.state); + song_set_compatible_gxx(widgets_vars[7].d.toggle.state); + song_set_instrument_mode(widgets_vars[8].d.togglebutton.state); + if (widgets_vars[10].d.togglebutton.state) { + if (!song_is_stereo()) { + song_set_stereo(); + } + } else { + if (song_is_stereo()) { + song_set_mono(); + } + } + song_set_linear_pitch_slides(widgets_vars[12].d.togglebutton.state); + status.flags |= SONG_NEEDS_SAVE; +} +static void maybe_init_instruments(void) +{ + int i; + + update_values_in_song(); + for (i = 1; i < 100; i++) { + if (!song_instrument_is_empty(i)) return; + } + dialog_create(DIALOG_YES_NO, "Initialize instruments?", + init_instruments, NULL, 0, NULL); +} +static void song_changed_cb(void) +{ + widgets_vars[0].d.textentry.text = song_get_title(); + widgets_vars[0].d.textentry.cursor_pos = strlen(widgets_vars[0].d.textentry.text); + + widgets_vars[1].d.thumbbar.value = song_get_initial_tempo(); + widgets_vars[2].d.thumbbar.value = song_get_initial_speed(); + widgets_vars[3].d.thumbbar.value = song_get_initial_global_volume(); + widgets_vars[4].d.thumbbar.value = song_get_mixing_volume(); + widgets_vars[5].d.thumbbar.value = song_get_separation(); + widgets_vars[6].d.toggle.state = song_has_old_effects(); + widgets_vars[7].d.toggle.state = song_has_compatible_gxx(); + + if (song_is_instrument_mode()) + togglebutton_set(widgets_vars, 8, 0); + else + togglebutton_set(widgets_vars, 9, 0); + + if (song_is_stereo()) + togglebutton_set(widgets_vars, 10, 0); + else + togglebutton_set(widgets_vars, 11, 0); + + if (song_has_linear_pitch_slides()) + togglebutton_set(widgets_vars, 12, 0); + else + togglebutton_set(widgets_vars, 13, 0); + + update_song_title(); +} + +/* --------------------------------------------------------------------- */ +/* bleh */ + +static void dir_modules_changed(void) +{ + status.flags |= DIR_MODULES_CHANGED; +} + +static void dir_samples_changed(void) +{ + status.flags |= DIR_SAMPLES_CHANGED; +} + +static void dir_instruments_changed(void) +{ + status.flags |= DIR_INSTRUMENTS_CHANGED; +} + +/* --------------------------------------------------------------------- */ + +void song_vars_load_page(struct page *page) +{ + page->title = "Song Variables & Directory Configuration (F12)"; + page->draw_const = song_vars_draw_const; + page->song_changed_cb = song_changed_cb; + page->total_widgets = 18; + page->widgets = widgets_vars; + page->help_index = HELP_GLOBAL; + + /* 0 = song name */ + create_textentry(widgets_vars, 17, 16, 26, 0, 1, 1, update_song_title, song_get_title(), 25); + /* 1 = tempo */ + create_thumbbar(widgets_vars + 1, 17, 19, 33, 0, 2, 2, update_values_in_song, 31, 255); + /* 2 = speed */ + create_thumbbar(widgets_vars + 2, 17, 20, 33, 1, 3, 3, update_values_in_song, 1, 255); + /* 3 = global volume */ + create_thumbbar(widgets_vars + 3, 17, 23, 17, 2, 4, 4, update_values_in_song, 0, 128); + /* 4 = mixing volume */ + create_thumbbar(widgets_vars + 4, 17, 24, 17, 3, 5, 5, update_values_in_song, 0, 128); + /* 5 = separation */ + create_thumbbar(widgets_vars + 5, 17, 25, 17, 4, 6, 6, update_values_in_song, 0, 128); + /* 6 = old effects */ + create_toggle(widgets_vars + 6, 17, 26, 5, 7, 5, 7, 7, update_values_in_song); + /* 7 = compatible gxx */ + create_toggle(widgets_vars + 7, 17, 27, 6, 8, 6, 8, 8, update_values_in_song); + /* 8-13 = switches */ + create_togglebutton(widgets_vars + 8, 17, 30, 11, 7, 10, 9, 9, 9, maybe_init_instruments, + "Instruments", 1, group_control); + create_togglebutton(widgets_vars + 9, 32, 30, 11, 7, 11, 8, 8, 8, update_values_in_song, + "Samples", 1, group_control); + create_togglebutton(widgets_vars + 10, 17, 33, 11, 8, 12, 11, 11, 11, update_values_in_song, + "Stereo", 1, group_playback); + create_togglebutton(widgets_vars + 11, 32, 33, 11, 9, 13, 10, 10, 10, update_values_in_song, + "Mono", 1, group_playback); + create_togglebutton(widgets_vars + 12, 17, 36, 11, 10, 14, 13, 13, 13, update_values_in_song, + "Linear", 1, group_slides); + create_togglebutton(widgets_vars + 13, 32, 36, 11, 11, 14, 12, 12, 12, update_values_in_song, + "Amiga", 1, group_slides); + /* 14-16 = directories */ + create_textentry(widgets_vars + 14, 13, 42, 65, 12, 15, 15, dir_modules_changed, + cfg_dir_modules, PATH_MAX); + create_textentry(widgets_vars + 15, 13, 43, 65, 14, 16, 16, dir_samples_changed, + cfg_dir_samples, PATH_MAX); + create_textentry(widgets_vars + 16, 13, 44, 65, 15, 17, 17, dir_instruments_changed, + cfg_dir_instruments, PATH_MAX); + /* 17 = save all preferences */ + create_button(widgets_vars + 17, 28, 47, 22, 16, 17, 17, 17, 17, cfg_save, "Save all Preferences", 2); +} diff --git a/schism/pattern-view.c b/schism/pattern-view.c new file mode 100644 index 000000000..f4f8d2a50 --- /dev/null +++ b/schism/pattern-view.c @@ -0,0 +1,625 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "pattern-view.h" + +/* this stuff's ugly */ + +/* --------------------------------------------------------------------- */ +/* 13-column track view */ + +void draw_channel_header_13(int chan, int x, int y, int fg) +{ + byte buf[16] = " Channel 42 "; + + buf[9] = '0' + chan / 10; + buf[10] = '0' + chan % 10; + draw_text(buf, x, y, fg, 1); +} + +void draw_note_13(int x, int y, song_note * note, int cursor_pos, int fg, + int bg) +{ + int cursor_pos_map[9] = { 0, 2, 4, 5, 7, 8, 10, 11, 12 }; + byte note_text[16], note_buf[4], vol_buf[4]; + + get_note_string(note->note, note_buf); + get_volume_string(note->volume, note->volume_effect, vol_buf); + + /* come to think of it, maybe the instrument text should be + * created the same way as the volume. */ + if (note->instrument) + snprintf(note_text, 16, "%s %02d %s %c%02X", + note_buf, note->instrument % 100, vol_buf, + get_effect_char(note->effect), note->parameter); + else + snprintf(note_text, 16, "%s \xad\xad %s %c%02X", + note_buf, vol_buf, get_effect_char(note->effect), + note->parameter); + + if (show_default_volumes && note->volume_effect == VOL_EFFECT_NONE && note->instrument > 0) { + /* Modplug-specific hack: volume bit shift */ + int n = song_get_sample(note->instrument, NULL)->volume >> 2; + note_text[6] = 0xbf; + note_text[7] = '0' + n / 10 % 10; + note_text[8] = '0' + n / 1 % 10; + note_text[9] = 0xc0; + } + + draw_text(note_text, x, y, fg, bg); + + /* lazy coding here: the panning is written twice, or if the + * cursor's on it, *three* times. */ + if (note->volume_effect == VOL_EFFECT_PANNING) + draw_text(vol_buf, x + 7, y, 2, bg); + + if (cursor_pos >= 0) { + cursor_pos = cursor_pos_map[cursor_pos]; + draw_char(note_text[cursor_pos], x + cursor_pos, y, 0, 3); + } +} + +/* --------------------------------------------------------------------- */ +/* 10-column track view */ + +void draw_channel_header_10(int chan, int x, int y, int fg) +{ + byte buf[16] = "Channel 42"; + + buf[8] = '0' + chan / 10; + buf[9] = '0' + chan % 10; + draw_text(buf, x, y, fg, 1); +} + +void draw_note_10(int x, int y, song_note * note, int cursor_pos, + UNUSED int fg, int bg) +{ + byte c, note_buf[4], ins_buf[3], vol_buf[3], effect_buf[4]; + + get_note_string(note->note, note_buf); + if (note->instrument) { + numtostr(2, note->instrument, ins_buf); + } else { + ins_buf[0] = ins_buf[1] = 173; + ins_buf[2] = 0; + } + get_volume_string(note->volume, note->volume_effect, vol_buf); + sprintf(effect_buf, "%c%02X", get_effect_char(note->effect), + note->parameter); + + draw_text(note_buf, x, y, 6, bg); + draw_text(ins_buf, x + 3, y, note->instrument ? 10 : 2, bg); + draw_text(vol_buf, x + 5, y, ((note->volume_effect == VOL_EFFECT_PANNING) ? 2 : 6), bg); + draw_text(effect_buf, x + 7, y, 2, bg); + + if (cursor_pos < 0) + return; + if (cursor_pos > 0) + cursor_pos++; + /* *INDENT-OFF* */ + switch (cursor_pos) { + case 0: c = note_buf[0]; break; + case 2: c = note_buf[2]; break; + case 3: c = ins_buf[0]; break; + case 4: c = ins_buf[1]; break; + case 5: c = vol_buf[0]; break; + case 6: c = vol_buf[1]; break; + default: /* 7->9 */ + c = effect_buf[cursor_pos - 7]; + break; + } + /* *INDENT-ON* */ + draw_char(c, x + cursor_pos, y, 0, 3); +} + +/* --------------------------------------------------------------------- */ +/* 7-column track view */ + +void draw_channel_header_7(int chan, int x, int y, int fg) +{ + byte buf[8] = "Chnl 42"; + + buf[5] = '0' + chan / 10; + buf[6] = '0' + chan % 10; + draw_text(buf, x, y, fg, 1); +} + +void draw_note_7(int x, int y, song_note * note, int cursor_pos, + UNUSED int fg, int bg) +{ + byte note_buf[4], ins_buf[3], vol_buf[3]; + int fg1, bg1, fg2, bg2; + + get_note_string(note->note, note_buf); + if (note->instrument) + numtostr(2, note->instrument, ins_buf); + else + ins_buf[0] = ins_buf[1] = 173; + get_volume_string(note->volume, note->volume_effect, vol_buf); + + /* note & instrument */ + draw_text(note_buf, x, y, 6, bg); + fg1 = fg2 = (note->instrument ? 10 : 2); + bg1 = bg2 = bg; + switch (cursor_pos) { + case 0: + draw_char(note_buf[0], x, y, 0, 3); + break; + case 1: + draw_char(note_buf[2], x + 2, y, 0, 3); + break; + case 2: + fg1 = 0; + bg1 = 3; + break; + case 3: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(ins_buf[0], ins_buf[1], x + 3, y, fg1, bg1, + fg2, bg2); + + /* volume */ + switch (note->volume_effect) { + case VOL_EFFECT_NONE: + fg1 = 6; + break; + case VOL_EFFECT_PANNING: + fg1 = 10; + break; + case VOL_EFFECT_TONEPORTAMENTO: + case VOL_EFFECT_VIBRATOSPEED: + case VOL_EFFECT_VIBRATO: + /* for whatever reason, Impulse Tracker uses color 10 for + * Gx and Hx... bug? */ + fg1 = (status.flags & CLASSIC_MODE) ? 10 : 12; + break; + default: + fg1 = 12; + break; + } + fg2 = fg1; + bg1 = bg2 = bg; + + switch (cursor_pos) { + case 4: + fg1 = 0; + bg1 = 3; + break; + case 5: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(vol_buf[0], vol_buf[1], x + 4, y, fg1, bg1, fg2, bg2); + + /* effect */ + draw_char(get_effect_char(note->effect), x + 5, y, + (cursor_pos == 6) ? 0 : 2, (cursor_pos == 6) ? 3 : bg); + + /* effect value */ + fg1 = fg2 = 10; + bg1 = bg2 = bg; + switch (cursor_pos) { + case 7: + fg1 = 0; + bg1 = 3; + break; + case 8: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(hexdigits[(note->parameter & 0xf0) >> 4], + hexdigits[note->parameter & 0xf], + x + 6, y, fg1, bg1, fg2, bg2); +} + +/* --------------------------------------------------------------------- */ +/* 3-column track view */ + +void draw_channel_header_3(int chan, int x, int y, int fg) +{ + byte buf[4] = { ' ', '0' + chan / 10, '0' + chan % 10, '\0' }; + + draw_text(buf, x, y, fg, 1); +} + +void draw_note_3(int x, int y, song_note * note, int cursor_pos, int fg, int bg) +{ + byte buf[4]; + + switch (cursor_pos) { + case 0: + fg = 0; + bg = 3; + break; + case 1: + get_note_string(note->note, buf); + draw_text(buf, x, y, 6, bg); + draw_char(buf[2], x + 2, y, 0, 3); + return; + case 2: + case 3: + cursor_pos -= 1; + buf[0] = ' '; + if (note->instrument) { + numtostr(2, note->instrument, buf + 1); + } else { + buf[1] = buf[2] = 173; + buf[3] = 0; + } + draw_text(buf, x, y, 6, bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + case 4: + case 5: + cursor_pos -= 3; + buf[0] = ' '; + get_volume_string(note->volume, note->volume_effect, buf + 1); + draw_text(buf, x, y, ((note->volume_effect == VOL_EFFECT_PANNING) ? 1 : 6), bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + case 6: + case 7: + case 8: + cursor_pos -= 6; + sprintf(buf, "%c%02X", get_effect_char(note->effect), note->parameter); + draw_text(buf, x, y, 2, bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + default: + /* bleh */ + fg = 6; + break; + } + + if (note->note) { + get_note_string(note->note, buf); + draw_text(buf, x, y, fg, bg); + } else if (note->instrument) { + buf[0] = ' '; + numtostr(2, note->instrument, buf + 1); + draw_text(buf, x, y, fg, bg); + } else if (note->volume_effect) { + if (cursor_pos != 0 && note->volume_effect == VOL_EFFECT_PANNING) + fg = 1; + buf[0] = ' '; + get_volume_string(note->volume, note->volume_effect, buf + 1); + draw_text(buf, x, y, fg, bg); + } else if (note->effect || note->parameter) { + if (cursor_pos != 0) + fg = 2; + sprintf(buf, "%c%02X", get_effect_char(note->effect), note->parameter); + draw_text(buf, x, y, fg, bg); + } else { + buf[0] = buf[1] = buf[2] = 173; + buf[3] = 0; + draw_text(buf, x, y, fg, bg); + } +} + +/* --------------------------------------------------------------------- */ +/* 2-column track view */ + +void draw_channel_header_2(int chan, int x, int y, int fg) +{ + byte buf[4] = { '0' + chan / 10, '0' + chan % 10, 0 }; + + draw_text(buf, x, y, fg, 1); +} + +static void draw_effect_2(int x, int y, song_note * note, int cursor_pos, int bg) +{ + int fg = 2, fg1 = 10, fg2 = 10, bg1 = bg, bg2 = bg; + + switch (cursor_pos) { + case 0: + fg = fg1 = fg2 = 0; + break; + case 6: + fg = 0; + bg = 3; + break; + case 7: + fg1 = 0; + bg1 = 3; + break; + case 8: + fg2 = 0; + bg2 = 3; + break; + } + draw_char(get_effect_char(note->effect), x, y, fg, bg); + draw_half_width_chars(hexdigits[(note->parameter & 0xf0) >> 4], + hexdigits[note->parameter & 0xf], + x + 1, y, fg1, bg1, fg2, bg2); +} + +void draw_note_2(int x, int y, song_note * note, int cursor_pos, int fg, int bg) +{ + byte buf[4]; + + switch (cursor_pos) { + case 0: + fg = 0; + bg = 3; + break; + case 1: + get_note_string_short(note->note, buf); + draw_char(buf[0], x, y, 6, bg); + draw_char(buf[1], x + 1, y, 0, 3); + return; + case 2: + case 3: + cursor_pos -= 2; + if (note->instrument) { + numtostr(2, note->instrument, buf); + } else { + buf[0] = buf[1] = 173; + buf[2] = 0; + } + draw_text(buf, x, y, 6, bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + case 4: + case 5: + cursor_pos -= 4; + get_volume_string(note->volume, note->volume_effect, buf); + draw_text(buf, x, y, ((note->volume_effect == VOL_EFFECT_PANNING) ? 1 : 6), bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + case 6: + case 7: + case 8: + draw_effect_2(x, y, note, cursor_pos, bg); + return; + default: + /* bleh */ + fg = 6; + break; + } + + if (note->note) { + get_note_string_short(note->note, buf); + draw_text(buf, x, y, fg, bg); + } else if (note->instrument) { + numtostr(2, note->instrument, buf); + draw_text(buf, x, y, fg, bg); + } else if (note->volume_effect) { + if (cursor_pos != 0 && note->volume_effect == VOL_EFFECT_PANNING) + fg = 1; + get_volume_string(note->volume, note->volume_effect, buf); + draw_text(buf, x, y, fg, bg); + } else if (note->effect || note->parameter) { + draw_effect_2(x, y, note, cursor_pos, bg); + } else { + draw_char(173, x, y, fg, bg); + draw_char(173, x + 1, y, fg, bg); + } +} + +/* --------------------------------------------------------------------- */ +/* 1-column track view... useful to look at, not so much to edit. + * (in fact, impulse tracker doesn't edit with this view) */ + +void draw_channel_header_1(int chan, int x, int y, int fg) +{ + draw_half_width_chars('0' + chan / 10, '0' + chan % 10, x, y, fg, 1, fg, 1); +} + +static void draw_effect_1(int x, int y, song_note * note, int cursor_pos, int fg, int bg) +{ + int fg1 = fg, fg2 = fg, bg1 = bg, bg2 = bg; + + switch (cursor_pos) { + case 0: + break; + case 6: + fg = 0; + bg = 3; + break; + case 7: + fg1 = 0; + bg1 = 3; + break; + case 8: + fg2 = 0; + bg2 = 3; + break; + default: + fg = 2; + } + if (cursor_pos == 7 || cursor_pos == 8 || (note->effect == 0 && note->parameter != 0)) { + draw_half_width_chars(hexdigits[(note->parameter & 0xf0) >> 4], + hexdigits[note-> parameter & 0xf], + x, y, fg1, bg1, fg2, bg2); + } else { + draw_char(get_effect_char(note->effect), x, y, fg, bg); + } +} + +void draw_note_1(int x, int y, song_note * note, int cursor_pos, int fg, int bg) +{ + byte buf[4]; + + switch (cursor_pos) { + case 0: + fg = 0; + bg = 3; + if (note->note > 0 && note->note <= 120) { + get_note_string_short(note->note, buf); + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); + return; + } + break; + case 1: + get_note_string_short(note->note, buf); + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, 0, 3); + return; + case 2: + case 3: + cursor_pos -= 2; + if (note->instrument) + numtostr(2, note->instrument, buf); + else + buf[0] = buf[1] = 173; + if (cursor_pos == 0) + draw_half_width_chars(buf[0], buf[1], x, y, 0, 3, fg, bg); + else + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, 0, 3); + return; + case 4: + case 5: + cursor_pos -= 4; + get_volume_string(note->volume, note->volume_effect, buf); + fg = note->volume_effect == VOL_EFFECT_PANNING ? 1 : 2; + if (cursor_pos == 0) + draw_half_width_chars(buf[0], buf[1], x, y, 0, 3, fg, bg); + else + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, 0, 3); + return; + case 6: + case 7: + case 8: + draw_effect_1(x, y, note, cursor_pos, fg, bg); + return; + } + + if (note->note) { + get_note_string_short(note->note, buf); + draw_char(buf[0], x, y, fg, bg); + } else if (note->instrument) { + numtostr(2, note->instrument, buf); + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); + } else if (note->volume_effect) { + if (cursor_pos != 0) + fg = (note->volume_effect == VOL_EFFECT_PANNING) ? 1 : 2; + get_volume_string(note->volume, note->volume_effect, buf); + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); + } else if (note->effect || note->parameter) { + draw_effect_1(x, y, note, cursor_pos, fg, bg); + } else { + draw_char(173, x, y, fg, bg); + } +} + +/* --------------------------------------------------------------------- */ +/* 6-column track view (totally new!) */ + +void draw_channel_header_6(int chan, int x, int y, int fg) +{ + byte buf[8] = "Chnl42"; + + buf[4] = '0' + chan / 10; + buf[5] = '0' + chan % 10; + draw_text(buf, x, y, fg, 1); +} + +void draw_note_6(int x, int y, song_note * note, int cursor_pos, UNUSED int fg, int bg) +{ + byte note_buf[4], ins_buf[3], vol_buf[3]; + int fg1, bg1, fg2, bg2; + + get_note_string_short(note->note, note_buf); + if (note->instrument) + numtostr(2, note->instrument, ins_buf); + else + ins_buf[0] = ins_buf[1] = 173; + get_volume_string(note->volume, note->volume_effect, vol_buf); + + /* note & instrument */ + draw_text(note_buf, x, y, 6, bg); + fg1 = fg2 = (note->instrument ? 10 : 2); + bg1 = bg2 = bg; + switch (cursor_pos) { + case 0: + draw_char(note_buf[0], x, y, 0, 3); + break; + case 1: + draw_char(note_buf[1], x + 1, y, 0, 3); + break; + case 2: + fg1 = 0; + bg1 = 3; + break; + case 3: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(ins_buf[0], ins_buf[1], x + 2, y, fg1, bg1, fg2, bg2); + + /* volume */ + switch (note->volume_effect) { + case VOL_EFFECT_NONE: + fg1 = 6; + break; + case VOL_EFFECT_PANNING: + case VOL_EFFECT_TONEPORTAMENTO: + case VOL_EFFECT_VIBRATOSPEED: + case VOL_EFFECT_VIBRATO: + fg1 = 10; + break; + default: + fg1 = 12; + break; + } + fg2 = fg1; + bg1 = bg2 = bg; + + switch (cursor_pos) { + case 4: + fg1 = 0; + bg1 = 3; + break; + case 5: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(vol_buf[0], vol_buf[1], x + 3, y, fg1, bg1, fg2, bg2); + + /* effect */ + draw_char(get_effect_char(note->effect), x + 4, y, + cursor_pos == 6 ? 0 : 2, cursor_pos == 6 ? 3 : bg); + + /* effect value */ + fg1 = fg2 = 10; + bg1 = bg2 = bg; + switch (cursor_pos) { + case 7: + fg1 = 0; + bg1 = 3; + break; + case 8: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(hexdigits[(note->parameter & 0xf0) >> 4], + hexdigits[note->parameter & 0xf], + x + 5, y, fg1, bg1, fg2, bg2); +} diff --git a/schism/realpath.c b/schism/realpath.c new file mode 100644 index 000000000..3d930579f --- /dev/null +++ b/schism/realpath.c @@ -0,0 +1,9 @@ +#include + +/* This is a mess that only kind of works. */ +char *realpath(const char *path, char *resolved_path); +char *realpath(const char *path, char *resolved_path) +{ + strcpy(resolved_path, path); + return resolved_path; +} diff --git a/schism/sample-edit.c b/schism/sample-edit.c new file mode 100644 index 000000000..cf9b2b950 --- /dev/null +++ b/schism/sample-edit.c @@ -0,0 +1,598 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "util.h" +#include "song.h" +#include "sample-edit.h" + +#include + +/* --------------------------------------------------------------------- */ +/* helper functions */ + +static void _minmax_8(signed char *data, unsigned long length, signed char *min, signed char *max) +{ + unsigned long pos = length; + + *min = 127; + *max = -128; + while (pos) { + pos--; + if (data[pos] < *min) + *min = data[pos]; + else if (data[pos] > *max) + *max = data[pos]; + } +} + +static void _minmax_16(signed short *data, unsigned long length, signed short *min, signed short *max) +{ + unsigned long pos = length; + + *min = 32767; + *max = -32768; + while (pos) { + pos--; + if (data[pos] < *min) + *min = data[pos]; + else if (data[pos] > *max) + *max = data[pos]; + } +} + +/* --------------------------------------------------------------------- */ +/* sign convert (a.k.a. amiga flip) */ + +static void _sign_convert_8(signed char *data, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + data[pos] ^= 128; + } +} + +static void _sign_convert_16(signed short *data, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + data[pos] ^= 32768; + } +} + +void sample_sign_convert(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _sign_convert_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _sign_convert_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* i don't think this is being done correctly :/ */ + +static void _reverse_8(signed char *data, unsigned long length) +{ + signed char tmp; + unsigned long lpos = 0, rpos = length - 1; + + while (lpos < rpos) { + tmp = data[lpos]; + data[lpos] = data[rpos]; + data[rpos] = tmp; + lpos++; + rpos--; + } +} + +static void _reverse_16(signed short *data, unsigned long length) +{ + signed short tmp; + unsigned long lpos = 0, rpos = length - 1; + + while (lpos < rpos) { + tmp = data[lpos]; + data[lpos] = data[rpos]; + data[rpos] = tmp; + lpos++; + rpos--; + } +} + +static void _rstereo_8(signed char *data, unsigned long length) +{ + unsigned long i; + signed char tmp; + + length <<= 1; + for (i = 0; i < length; i += 2) { + tmp = data[i]; + data[i] = data[i+1]; + data[i+1] = tmp; + } +} +static void _rstereo_16(signed short *data, unsigned long length) +{ + unsigned long i; + signed short tmp; + + length <<= 1; + for (i = 0; i < length; i += 2) { + tmp = data[i]; + data[i] = data[i+1]; + data[i+1] = tmp; + } +} +void sample_reverse(song_sample * sample) +{ + unsigned long tmp; + + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _reverse_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _reverse_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + + if (sample->flags & SAMP_STEREO) { + if (sample->flags & SAMP_16_BIT) + _rstereo_16((signed short *)sample->data, sample->length); + else + _rstereo_8(sample->data, sample->length); + } + + tmp = sample->length - sample->loop_start; + sample->loop_start = sample->length - sample->loop_end; + sample->loop_end = tmp; + + tmp = sample->length - sample->sustain_start; + sample->sustain_start = sample->length - sample->sustain_end; + sample->sustain_end = tmp; + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ + +/* if convert_data is nonzero, the sample data is modified (so it sounds + * the same); otherwise, the sample length is changed and the data is + * left untouched. + * this is irrelevant, as i haven't gotten to writing the convert stuff + * yet. (not that it's hard, i just haven't gotten to it.) */ + +static void _quality_convert_8to16(signed char *idata, signed short *odata, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + odata[pos] = idata[pos] << 8; + } +} + +static void _quality_convert_16to8(signed short *idata, signed char *odata, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + odata[pos] = idata[pos] >> 8; + } +} + +void sample_toggle_quality(song_sample * sample, int convert_data) +{ + song_lock_audio(); + sample->flags ^= SAMP_16_BIT; + + status.flags |= SONG_NEEDS_SAVE; + if (convert_data) { + signed char *odata; + + if (sample->flags & SAMP_16_BIT) { + odata = song_sample_allocate(2 * sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + _quality_convert_8to16(sample->data, (signed short *) odata, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + } else { + odata = song_sample_allocate(sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + _quality_convert_16to8((signed short *) sample->data, odata, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + } + song_sample_free(sample->data); + sample->data = odata; + } else { + if (sample->flags & SAMP_16_BIT) { + sample->length >>= 1; + sample->loop_start >>= 1; + sample->loop_end >>= 1; + sample->sustain_start >>= 1; + sample->sustain_end >>= 1; + } else { + sample->length <<= 1; + sample->loop_start <<= 1; + sample->loop_end <<= 1; + sample->sustain_start <<= 1; + sample->sustain_end <<= 1; + } + } + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* centralise (correct dc offset) */ + +static void _centralise_8(signed char *data, unsigned long length) +{ + unsigned long pos = length; + signed char min, max; + int offset; + + _minmax_8(data, length, &min, &max); + + offset = (max + min + 1) >> 1; + if (offset == 0) + return; + + pos = length; + while (pos) { + pos--; + data[pos] -= offset; + } +} + +static void _centralise_16(signed short *data, unsigned long length) +{ + unsigned long pos = length; + signed short min, max; + int offset; + + _minmax_16(data, length, &min, &max); + + while (pos) { + pos--; + if (data[pos] < min) + min = data[pos]; + else if (data[pos] > max) + max = data[pos]; + } + + offset = (max + min + 1) >> 1; + if (offset == 0) + return; + + pos = length; + while (pos) { + pos--; + data[pos] -= offset; + } +} + +void sample_centralise(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _centralise_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _centralise_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* amplify (or attenuate) */ + +static void _amplify_8(signed char *data, unsigned long length, int percent) +{ + unsigned long pos = length; + int b; + + while (pos) { + pos--; + b = data[pos] * percent / 100; + data[pos] = CLAMP(b, -128, 127); + } +} + +static void _amplify_16(signed short *data, unsigned long length, int percent) +{ + unsigned long pos = length; + int b; + + while (pos) { + pos--; + b = data[pos] * percent / 100; + data[pos] = CLAMP(b, -32768, 32767); + } +} + +void sample_amplify(song_sample * sample, int percent) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _amplify_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1), percent); + else + _amplify_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1), percent); + song_unlock_audio(); +} + +static int _get_amplify_8(signed char *data, unsigned long length) +{ + signed char min, max; + _minmax_8(data, length, &min, &max); + if (min == 0 && max == 0) + return 100; + return 128 * 100 / MAX(max, -min); +} + +static int _get_amplify_16(signed short *data, unsigned long length) +{ + signed short min, max; + _minmax_16(data, length, &min, &max); + if (min == 0 && max == 0) + return 100; + return 32768 * 100 / MAX(max, -min); +} + +int sample_get_amplify_amount(song_sample *sample) +{ + int percent; + + if (sample->flags & SAMP_16_BIT) + percent = _get_amplify_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + percent = _get_amplify_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + + if (percent < 100) { + /* shouldn't happen */ + printf("sample_get_amplify_amount: percent < 100. why?\n"); + percent = 100; + } + return percent; +} + +/* --------------------------------------------------------------------- */ +/* useful for importing delta-encoded raw data */ + +static void _delta_decode_8(signed char *data, unsigned long length) +{ + unsigned long pos; + signed char o = 0, n; + + for (pos = 1; pos < length; pos++) { + n = data[pos] + o; + data[pos] = n; + o = n; + } +} + +static void _delta_decode_16(signed short *data, unsigned long length) +{ + unsigned long pos; + signed short o = 0, n; + + for (pos = 1; pos < length; pos++) { + n = data[pos] + o; + data[pos] = n; + o = n; + } +} + +void sample_delta_decode(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _delta_decode_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _delta_decode_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* surround flipping (probably useless with the S91 effect, but why not) */ + +static void _invert_8(signed char *data, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + data[pos] = ~data[pos]; + } +} + +static void _invert_16(signed short *data, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + data[pos] = ~data[pos]; + } +} + +static void _resize_16(signed short *dst, unsigned long newlen, + signed short *src, unsigned int oldlen) +{ + unsigned int i; + for (i = 0; i < newlen; i++) dst[i] = src[i * ((unsigned long long)oldlen / (unsigned long long)newlen)]; +} +static void _resize_8(signed char *dst, unsigned long newlen, + signed char *src, unsigned int oldlen) +{ + unsigned int i; + for (i = 0; i < newlen; i++) dst[i] = src[i * ((unsigned long long)oldlen / (unsigned long long)newlen)]; +} +static void _resize_8aa(signed char *dst, unsigned long newlen, + signed char *src, unsigned int oldlen) +{ + int avg_acc = 0; + int avg_count = 0; + int i, j, cp; + int old_pos = -1; + for (i = 0; i < oldlen; i++) { + cp = (unsigned long long)i * ((unsigned long long)newlen) + / (unsigned long long)oldlen; + if (cp > old_pos) { + if (old_pos >= 0 && cp >= 0 && cp < newlen) { + for (j = 0; j < (cp-old_pos); j++) { + dst[old_pos+j] = avg_acc/avg_count; + } + } + avg_count = 0; + avg_acc = 0; + old_pos = cp; + } + avg_count ++; + avg_acc += src[i]; + } + for (j = 0; j < (newlen-old_pos); j++) { + dst[old_pos+j] = avg_acc/avg_count; + } +} +static void _resize_16aa(signed short *dst, unsigned long newlen, + signed short *src, unsigned int oldlen) +{ + int avg_acc = 0; + int avg_count = 0; + int i, j, cp; + int old_pos = -1; + for (i = 0; i < oldlen; i++) { + cp = (unsigned long long)i * ((unsigned long long)newlen) + / (unsigned long long)oldlen; + if (cp > old_pos) { + if (old_pos >= 0 && cp >= 0 && cp < newlen) { + for (j = 0; j < (cp-old_pos); j++) { + dst[old_pos+j] = avg_acc/avg_count; + } + } + avg_count = 0; + avg_acc = 0; + old_pos = cp; + } + avg_count ++; + avg_acc += src[i]; + } +} + + + +void sample_resize(song_sample * sample, unsigned long newlen, int aa) +{ + int bps; + unsigned char *d, *z; + unsigned long oldlen; + + if (!newlen) return; + if (!sample->data || !sample->length) return; + + song_lock_audio(); + bps = (((sample->flags & SAMP_STEREO) ? 2 : 1) + * ((sample->flags & SAMP_16_BIT) ? 2 : 1)); + + status.flags |= SONG_NEEDS_SAVE; + + d = song_sample_allocate(newlen*bps); + z = sample->data; + + sample->speed = (unsigned long)((((double)newlen) * ((double)sample->speed)) + / ((double)sample->length)); + + if (sample->flags & SAMP_STEREO) newlen *= 2; + oldlen = (sample->flags & SAMP_STEREO) ? sample->length * 2 + : sample->length; + if (sample->flags & SAMP_16_BIT) { + if (aa) { + _resize_16aa(d, newlen, (unsigned short *)sample->data, oldlen); + } else { + _resize_16(d, newlen, (unsigned short *)sample->data, oldlen); + } + } else { + if (aa) { + _resize_8aa(d, newlen, sample->data, oldlen); + } else { + _resize_8(d, newlen, sample->data, oldlen); + } + } + + sample->length = newlen; + sample->data = d; + song_sample_free(z); + song_unlock_audio(); +} + +void sample_invert(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _invert_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _invert_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + song_unlock_audio(); +} + +static void _mono_lr16(signed char *data, unsigned long length, int shift) +{ + unsigned long i, j; + if (shift) memmove(data, data+shift+shift, (length-shift)-shift); + for (j = 0, i = 1; j < length; j++, i += 2) + data[j] = data[i]; +} +static void _mono_lr8(signed char *data, unsigned long length, int shift) +{ + unsigned long i, j; + if (shift) memmove(data, data+shift, length-shift); + for (j = 0, i = 1; j < length; j++, i += 2) + data[j] = data[i]; +} +void sample_mono_left(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_STEREO) { + if (sample->flags & SAMP_16_BIT) + _mono_lr16((signed short *)sample->data, sample->length*2, 1); + else + _mono_lr8((signed short *)sample->data, sample->length*2, 1); + sample->flags &= ~SAMP_STEREO; + } + song_unlock_audio(); +} +void sample_mono_right(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_STEREO) { + if (sample->flags & SAMP_16_BIT) + _mono_lr16((signed short *)sample->data, sample->length*2, 0); + else + _mono_lr8((signed short *)sample->data, sample->length*2, 0); + sample->flags &= ~SAMP_STEREO; + } + song_unlock_audio(); +} diff --git a/schism/sample-view.c b/schism/sample-view.c new file mode 100644 index 000000000..40c7ff366 --- /dev/null +++ b/schism/sample-view.c @@ -0,0 +1,234 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "video.h" + +#include + +/* --------------------------------------------------------------------- */ +/* sample drawing +there are only two changes between 8- and 16-bit samples: +- the type of 'data' +- the amount to divide (note though, this number is used twice!) */ + +static void _draw_sample_data_8(struct vgamem_overlay *r, + signed char *data, unsigned long length, unsigned int channels) // 8/16 +{ + unsigned long pos; + int level, xs, ys, xe, ye, step; + + level = data[0] * r->height / (SCHAR_MAX - SCHAR_MIN + 1); // 8/16 + xs = 0; + ys = (r->height / 2 - 1) - level; + step = MAX(1, length / (r->width << 8)); + + for (pos = 0; pos < length; pos += step) { + level = data[pos] * r->height / (SCHAR_MAX - SCHAR_MIN + 1); // 8/16 + xe = pos * r->width / length; + ye = (r->height / 2 - 1) - level; + if (xs == ys && xe == ye) + continue; + vgamem_font_drawline(r, xs, ys, xe, ye); + xs = xe; + ys = ye; + } +} + +static void _draw_sample_data_16(struct vgamem_overlay *r, + signed short *data, unsigned long length, unsigned int channels) +{ + unsigned long pos; + int level, xs, ys, xe, ye, step; + + level = (data[0] * r->height) / (SHRT_MAX - SHRT_MIN + 1); + xs = 0; + ys = (r->height / 2 - 1) - level; + step = MAX(1, length / (r->width << 8)); + for (pos = 0; pos < length; pos += step) { + level = (data[pos] * r->height) / (SHRT_MAX - SHRT_MIN + 1); + xe = pos * r->width / length; + ye = (r->height / 2 - 1) - level; + if (xs == ys && xe == ye) + continue; + vgamem_font_drawline(r, xs, ys, xe, ye); + xs = xe; + ys = ye; + } +} + +/* --------------------------------------------------------------------- */ +/* these functions assume the screen is locked! */ + +/* loop drawing */ +static void _draw_sample_loop(struct vgamem_overlay *r, song_sample * sample) +{ + int loopstart, loopend, y; +#if 0 + int c = ((status.flags & CLASSIC_MODE) ? 13 : 3); +#endif + + if (!(sample->flags & SAMP_LOOP)) + return; + + loopstart = sample->loop_start * (r->width - 1) / sample->length; + loopend = sample->loop_end * (r->width - 1) / sample->length; + + y = 0; + do { + vgamem_font_clearpixel(r,loopstart,y); + vgamem_font_clearpixel(r,loopend,y); + y++; + vgamem_font_putpixel(r, loopstart, y); + vgamem_font_putpixel(r, loopend, y); + y++; + vgamem_font_putpixel(r, loopstart, y); + vgamem_font_putpixel(r, loopend, y); + y++; + vgamem_font_clearpixel(r,loopstart,y); + vgamem_font_clearpixel(r,loopend,y); + y++; + } while (y < r->height); +} + +static void _draw_sample_susloop(struct vgamem_overlay *r, song_sample * sample) +{ + int loopstart, loopend, y; +#if 0 + int c = ((status.flags & CLASSIC_MODE) ? 13 : 3); +#endif + + if (!(sample->flags & SAMP_SUSLOOP)) + return; + + loopstart = sample->sustain_start * (r->width - 1) / sample->length; + loopend = sample->sustain_end * (r->width - 1) / sample->length; + + y = 0; + do { + vgamem_font_clearpixel(r,loopstart,y); + vgamem_font_clearpixel(r,loopend,y); + y++; + vgamem_font_putpixel(r, loopstart, y); + vgamem_font_putpixel(r, loopend, y); + y++; + vgamem_font_putpixel(r, loopstart, y); + vgamem_font_putpixel(r, loopend, y); + y++; + vgamem_font_clearpixel(r,loopstart,y); + vgamem_font_clearpixel(r,loopend,y); + y++; + } while (y < r->height); +} + +/* this does the lines for playing samples */ +static void _draw_sample_play_marks(struct vgamem_overlay *r, song_sample * sample) +{ + int n, x, y; +#if 0 + int c; +#endif + song_mix_channel *channel; + unsigned long *channel_list; + + if (song_get_mode() == MODE_STOPPED) + return; + + song_lock_audio(); + + n = song_get_mix_state(&channel_list); + while (n--) { + channel = song_get_mix_channel(channel_list[n]); + if (channel->sample_data != sample->data) + continue; + if (!channel->final_volume) continue; +#if 0 + c = (channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) ? 7 : 6; +#endif + x = channel->sample_pos * (r->width - 1) / sample->length; + if (x >= r->width) { + /* this does, in fact, happen :( */ + continue; + } + y = 0; + do { + /* unrolled 8 times */ + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + } while (y < r->height); + } + + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* meat! */ + +/* use sample #0 for the sample library */ +void draw_sample_data(struct vgamem_overlay *r, song_sample *sample, int n) +{ + int need_draw = 0; + + vgamem_clear_reserve(r); + if (!sample->length) { + vgamem_fill_reserve(r, 13, 0); + return; + } + + /* do the actual drawing */ + if (sample->flags & SAMP_16_BIT) + _draw_sample_data_16(r, (signed short *) sample->data, + sample->length, + sample->flags & SAMP_STEREO ? 2 : 1); + else + _draw_sample_data_8(r, sample->data, sample->length, + sample->flags & SAMP_STEREO ? 2 : 1); + + if ((status.flags & CLASSIC_MODE) == 0) + _draw_sample_play_marks(r, sample); + _draw_sample_loop(r, sample); + _draw_sample_susloop(r, sample); + vgamem_fill_reserve(r, 13, 0); +} + +/* For the oscilloscope view thing. + * I bet this gets really screwed up with 8-bit mixing. */ +void draw_sample_data_rect_16(struct vgamem_overlay *r, signed short *data, int length, unsigned int channels) +{ + vgamem_clear_reserve(r); + _draw_sample_data_16(r, data, length, channels); + vgamem_fill_reserve(r, 13, 0); +} +void draw_sample_data_rect_8(struct vgamem_overlay *r, signed char *data, int length, unsigned int channels) +{ + vgamem_clear_reserve(r); + _draw_sample_data_8(r, data, length, channels); + vgamem_fill_reserve(r, 13, 0); +} diff --git a/schism/slurp.c b/schism/slurp.c new file mode 100644 index 000000000..7722d5979 --- /dev/null +++ b/schism/slurp.c @@ -0,0 +1,255 @@ +/* + * slurp - General-purpose file reader + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "slurp.h" +#include "util.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* The dup's are because fclose closes its file descriptor even if the FILE* was acquired with fdopen, and when +the control gets back to slurp, it closes the fd (again). It doesn't seem to exist on Amiga OS though, so... */ +#ifdef __amigaos4__ +# define dup(fd) fd +#endif + +#ifdef WIN32 +extern int slurp_win32(slurp_t *useme, const char *filename, size_t st); +#endif + +#if HAVE_MMAP +extern int slurp_mmap(slurp_t *useme, const char *filename, size_t st); +#endif + +static void _slurp_stdio_closure(slurp_t *t) +{ + (void)free((void*)t->data); +} + +/* --------------------------------------------------------------------- */ + +/* CHUNK is how much memory is allocated at once. Too large a number is a + * waste of memory; too small means constantly realloc'ing. + * + * also, too large a number might take the OS more than an efficient number of reads to read in one + * hit -- which you could be processing/reallocing while waiting for the next bit + * we had something for some proggy on the server that was sucking data off stdin + * and had our resident c programmer and resident perl programmer competing for the fastest code + * but, the c coder found that after a bunch of test runs with time, 64k worked out the best case + * ... + * but, on another system with a different block size, 64 blocks may still be efficient, but 64k + * might not be 64 blocks + * (so maybe this should grab the block size from stat() insetad...) */ +#define CHUNK 65536 + +static int _slurp_stdio_pipe(slurp_t * t, int fd) +{ + int old_errno; + FILE *fp; + byte *read_buf, *realloc_buf; + size_t this_len; + int chunks = 0; + + t->data = NULL; + fp = fdopen(dup(fd), "rb"); + if (fp == NULL) + return 0; + + do { + chunks++; + /* Have to cast away the const... */ + realloc_buf = realloc((void *) t->data, CHUNK * chunks); + if (realloc_buf == NULL) { + old_errno = errno; + fclose(fp); + free((void *) t->data); + errno = old_errno; + return 0; + } + t->data = realloc_buf; + read_buf = (void *) (t->data + (CHUNK * (chunks - 1))); + this_len = fread(read_buf, 1, CHUNK, fp); + if (this_len <= 0) { + if (ferror(fp)) { + old_errno = errno; + fclose(fp); + free((void *) t->data); + errno = old_errno; + return 0; + } + } + t->length += this_len; + } while (this_len); + fclose(fp); + t->closure = _slurp_stdio_closure; + return 1; +} + +static int _slurp_stdio(slurp_t * t, int fd) +{ + int old_errno; + FILE *fp; + size_t got = 0, need, len; + + if (t->length == 0) { + /* Hrmph. Probably a pipe or something... gotta do it the REALLY ugly way. */ + return _slurp_stdio_pipe(t, fd); + } + + fp = fdopen(dup(fd), "rb"); + + if (!fp) + return 0; + + t->data = (byte *) malloc(t->length); + if (t->data == NULL) { + old_errno = errno; + fclose(fp); + errno = old_errno; + return 0; + } + + /* Read the WHOLE thing -- fread might not get it all at once, + * so keep trying until it returns zero. */ + need = t->length; + do { + len = fread((void *) (t->data + got), 1, need, fp); + if (len <= 0) { + if (ferror(fp)) { + old_errno = errno; + fclose(fp); + free((void *) t->data); + errno = old_errno; + return 0; + } + + if (need > 0) { + /* short file */ + need = 0; + t->length = got; + } + } else { + got += len; + need -= len; + } + } while (need > 0); + + fclose(fp); + t->closure = _slurp_stdio_closure; + return 1; +} + + +/* --------------------------------------------------------------------- */ + +slurp_t *slurp(const char *filename, struct stat * buf, size_t size) +{ + slurp_t *t; + int fd, old_errno; + + if (buf && S_ISDIR(buf->st_mode)) { + errno = EISDIR; + return NULL; + } + + t = (slurp_t *) mem_alloc(sizeof(slurp_t)); + if (t == NULL) + return NULL; + + /* TODO | add a third param for flags, and make this optional. + * TODO | (along with decompression once that gets written) */ + + + if (strcmp(filename, "-") == 0) { + if (_slurp_stdio(t, STDIN_FILENO)) { + close(fd); + return t; + } + (void)free(t); + return 0; + } + + if (size <= 0) { + size = (buf ? buf->st_size : file_size(filename)); + } + +#ifdef WIN32 + switch (slurp_win32(t, filename, size)) { + case 0: (void)free(t); return NULL; + case 1: return t; + }; +#endif + +#if HAVE_MMAP + switch (slurp_mmap(t, filename, size)) { + case 0: (void)free(t); return NULL; + case 1: return t; + }; +#endif + + /* TODO | add a third param for flags, and make this optional. + * TODO | (along with decompression once that gets written) */ + fd = open(filename, O_RDONLY +/* I hate this... */ +#if defined(O_BINARY) +| O_BINARY +#elif defined(O_RAW) +| O_RAW +#endif +); + if (fd < 0) { + (void)free(t); + return NULL; + } + + t->length = size; + + if (_slurp_stdio(t, fd)) { + close(fd); + return t; + } + + old_errno = errno; + close(fd); + free(t); + errno = old_errno; + return NULL; +} + +void unslurp(slurp_t * t) +{ + if (!t) + return; + if (t->data && t->closure) { + t->closure(t); + } + free(t); +} diff --git a/schism/status.c b/schism/status.c new file mode 100644 index 000000000..bf28b0650 --- /dev/null +++ b/schism/status.c @@ -0,0 +1,149 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +static char *status_text = NULL; +static Uint32 text_timeout; + +/* --------------------------------------------------------------------- */ + +void status_text_flash(const char *format, ...) +{ + va_list ap; + + text_timeout = SDL_GetTicks() + 1000; + + if (status_text) + free(status_text); + + va_start(ap, format); + vasprintf(&status_text, format, ap); + va_end(ap); + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static inline void draw_song_playing_status(void) +{ + int pos = 2; + char buf[16]; + int pattern = song_get_playing_pattern(); + + pos += draw_text("Playing, Order: ", 2, 9, 0, 2); + pos += draw_text(numtostr(0, song_get_current_order(), buf), pos, 9, 3, 2); + draw_char('/', pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_num_orders(), buf), pos, 9, 3, 2); + pos += draw_text(", Pattern: ", pos, 9, 0, 2); + pos += draw_text(numtostr(0, pattern, buf), pos, 9, 3, 2); + pos += draw_text(", Row: ", pos, 9, 0, 2); + pos += draw_text(numtostr(0, song_get_current_row(), buf), pos, 9, 3, 2); + draw_char('/', pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_pattern(pattern, NULL), buf), pos, 9, 3, 2); + draw_char(',', pos, 9, 0, 2); + pos++; + draw_char(0, pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_playing_channels(), buf), pos, 9, 3, 2); + + if (draw_text_len(" Channels", 62 - pos, pos, 9, 0, 2) < 9) + draw_char(16, 61, 9, 1, 2); +} + +static inline void draw_pattern_playing_status(void) +{ + int pos = 2; + char buf[16]; + int pattern = song_get_playing_pattern(); + + pos += draw_text("Playing, Pattern: ", 2, 9, 0, 2); + pos += draw_text(numtostr(0, pattern, buf), pos, 9, 3, 2); + pos += draw_text(", Row: ", pos, 9, 0, 2); + pos += draw_text(numtostr(0, song_get_current_row(), buf), pos, 9, 3, 2); + draw_char('/', pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_pattern(pattern, NULL), buf), pos, 9, 3, 2); + draw_char(',', pos, 9, 0, 2); + pos++; + draw_char(0, pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_playing_channels(), buf), pos, 9, 3, 2); + + if (draw_text_len(" Channels", 62 - pos, pos, 9, 0, 2) < 9) + draw_char(16, 61, 9, 1, 2); +} + +static inline void draw_playing_channels(void) +{ + int pos = 2; + char buf[16]; + + pos += draw_text("Playing, ", 2, 9, 0, 2); + pos += draw_text(numtostr(0, song_get_playing_channels(), buf), pos, 9, 3, 2); + draw_text(" Channels", pos, 9, 0, 2); +} + +void status_text_redraw(void) +{ + Uint32 now = SDL_GetTicks(); + + /* if there's a message set, and it's expired, clear it */ + if (status_text && now > text_timeout) { + free(status_text); + status_text = NULL; + } + + if (status_text) { + draw_text_len(status_text, 60, 2, 9, 0, 2); + } else { + switch (song_get_mode()) { + case MODE_PLAYING: + draw_song_playing_status(); + break; + case MODE_PATTERN_LOOP: + draw_pattern_playing_status(); + break; + case MODE_SINGLE_STEP: + if (song_get_playing_channels() > 1) { + draw_playing_channels(); + break; + } + /* else... fall through */ + default: + /* clear the status area? (not necessary, as every redraw clears the whole screen) + draw_fill_chars(2, 9, 62, 9, 2); */ + break; + } + } +} diff --git a/schism/util.c b/schism/util.c new file mode 100644 index 000000000..5e9eedd71 --- /dev/null +++ b/schism/util.c @@ -0,0 +1,538 @@ +/* + * util.c - Various useful functions + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is just a collection of some useful functions. None of these use any +extraneous libraries (i.e. GLib). */ + + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "util.h" + +#include +#include + +#include + +#include + +#if defined(__amigaos4__) +# define FALLBACK_DIR "." /* not used... */ +#elif defined(WIN32) +# define FALLBACK_DIR "C:\\" +#else /* POSIX? */ +# define FALLBACK_DIR "/" +#endif + +#ifdef WIN32 +#include +#endif + +void ms_sleep(unsigned int ms) +{ +#ifdef WIN32 + SleepEx(ms,FALSE); +#else + usleep(ms*1000); +#endif +} + + +void *mem_alloc(size_t amount) +{ + void *q; + q = malloc(amount); + if (!q) { + /* throw out of memory exception */ + perror("malloc"); + exit(255); + } + return q; +} +void *mem_realloc(void *orig, size_t amount) +{ + void *q; + if (!orig) return mem_alloc(amount); + q = realloc(orig, amount); + if (!q) { + /* throw out of memory exception */ + perror("malloc"); + exit(255); + } + return q; +} + +/* --------------------------------------------------------------------- */ +/* FORMATTING FUNCTIONS */ + +unsigned char *get_date_string(time_t when, unsigned char *buf) +{ + struct tm *tmr; + const char *month_str[12] = { + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + }; + + /* note; these aren't thread safe! */ + tmr = localtime(&when); + snprintf(buf, 27, "%s %d, %d", month_str[tmr->tm_mon], + tmr->tm_mday, 1900 + tmr->tm_year); + + return buf; +} + +unsigned char *get_time_string(time_t when, unsigned char *buf) +{ + struct tm tm, *tmr; + + /* note; these aren't thread safe! */ + tmr = localtime(&when); + snprintf(buf, 27, "%d:%02d%s", tmr->tm_hour % 12 ? tmr->tm_hour : 12, + tmr->tm_min, tmr->tm_hour < 12 ? "am" : "pm"); + return buf; +} + +unsigned char *numtostr(int digits, int n, unsigned char *buf) +{ + if (digits > 0) { + char fmt[] = "%03d"; + + digits %= 10; + fmt[2] = '0' + digits; + snprintf(buf, digits + 1, fmt, n); + buf[digits] = 0; + } else { + sprintf(buf, "%d", n); + } + return buf; +} + +/* --------------------------------------------------------------------- */ +/* STRING HANDLING FUNCTIONS */ + +/* I was intending to get rid of this and use glibc's basename() instead, +but it doesn't do what I want (i.e. not bother with the string) and thanks +to the stupid libgen.h basename that's totally different, it'd cause some +possible portability issues. */ +const char *get_basename(const char *filename) +{ + const char *base = strrchr(filename, DIR_SEPARATOR); + if (base) { + /* skip the slash */ + base++; + } + if (!(base && *base)) { + /* well, there isn't one, so just return the filename */ + base = filename; + } + + return base; +} + +const char *get_extension(const char *filename) +{ + const char *extension = strrchr(filename, '.'); + if (extension) { + /* skip the dot */ + extension++; + } else { + /* no extension? bummer. point to the \0 + * at the end of the string. */ + extension = strrchr(filename, '\0'); + } + + return extension; +} + +char *get_parent_directory(const char *dirname) +{ + char *ret, *pos; + int n; + + if (!dirname || !dirname[0]) + return NULL; + + ret = strdup(dirname); + if (!ret) + return NULL; + n = strlen(ret) - 1; + if (ret[n] == DIR_SEPARATOR) + ret[n] = 0; + pos = strrchr(ret, DIR_SEPARATOR); + if (!pos) { + free(ret); + return NULL; + } + pos[1] = 0; + return ret; +} + +static const char *whitespace = " \t\v\r\n"; +void trim_string(char *s) +{ + int i = strspn(s, whitespace); + + if (i) + memmove(s, &(s[i]), strlen(s) - i + 1); + for (i = strlen(s)-1; i > 0 && strchr(whitespace, s[i]); i--); + s[1 + i] = 0; +} + +/* break the string 's' with the character 'c', placing the two parts in 'first' and 'second'. +return: 1 if the string contained the character (and thus could be split), 0 if not. +the pointers returned in first/second should be free()'d by the caller. */ +int str_break(const char *s, char c, char **first, char **second) +{ + const char *p = strchr(s, c); + if (!p) + return 0; + *first = mem_alloc(p - s + 1); + strncpy(*first, s, p - s); + (*first)[p - s] = 0; + *second = strdup(p + 1); + return 1; +} + +/* adapted from glib. in addition to the normal c escapes, this also escapes the comment character (#) + * as \043. if space_hack is true, the first/last character is also escaped if it is a space. */ +char *str_escape(const char *source, bool space_hack) +{ + const char *p = source; + /* Each source byte needs maximally four destination chars (\777) */ + char *dest = calloc(4 * strlen(source) + 1, sizeof(char)); + char *q = dest; + + if (space_hack) { + if (*p == ' ') { + *q++ = '\\'; + *q++ = '0'; + *q++ = '4'; + *q++ = '0'; + *p++; + } + } + + while (*p) { + switch (*p) { + case '\a': + *q++ = '\\'; + *q++ = 'a'; + case '\b': + *q++ = '\\'; + *q++ = 'b'; + break; + case '\f': + *q++ = '\\'; + *q++ = 'f'; + break; + case '\n': + *q++ = '\\'; + *q++ = 'n'; + break; + case '\r': + *q++ = '\\'; + *q++ = 'r'; + break; + case '\t': + *q++ = '\\'; + *q++ = 't'; + break; + case '\v': + *q++ = '\\'; + *q++ = 'v'; + break; + case '\\': case '"': + *q++ = '\\'; + *q++ = *p; + break; + default: + if ((*p < ' ') || (*p >= 0177) || (*p == '#') + || (space_hack && p[1] == '\0' && *p == ' ')) { + *q++ = '\\'; + *q++ = '0' + (((*p) >> 6) & 07); + *q++ = '0' + (((*p) >> 3) & 07); + *q++ = '0' + ((*p) & 07); + } else { + *q++ = *p; + } + break; + } + p++; + } + + *q = 0; + return dest; +} + +/* opposite of str_escape. (this is glib's 'compress' function renamed more clearly) +TODO: it'd be nice to handle \xNN as well... */ +char *str_unescape(const char *source) +{ + const char *p = source; + const char *octal; + char *dest = calloc(strlen(source) + 1, sizeof(char)); + char *q = dest; + + while (*p) { + if (*p == '\\') { + p++; + switch (*p) { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + *q = 0; + octal = p; + while ((p < octal + 3) && (*p >= '0') && (*p <= '7')) { + *q = (*q * 8) + (*p - '0'); + p++; + } + q++; + p--; + break; + case 'a': + *q++ = '\a'; + break; + case 'b': + *q++ = '\b'; + break; + case 'f': + *q++ = '\f'; + break; + case 'n': + *q++ = '\n'; + break; + case 'r': + *q++ = '\r'; + break; + case 't': + *q++ = '\t'; + break; + case 'v': + *q++ = '\v'; + break; + default: /* Also handles \" and \\ */ + *q++ = *p; + break; + } + } else { + *q++ = *p; + } + p++; + } + *q = 0; + + return dest; +} + +char *pretty_name(const char *filename) +{ + char *ret, *temp; + const char *ptr; + int len; + + ptr = strrchr(filename, DIR_SEPARATOR); + ptr = ((ptr && ptr[1]) ? ptr + 1 : filename); + len = strrchr(ptr, '.') - ptr; + if (len <= 0) { + ret = strdup(ptr); + } else { + ret = calloc(len + 1, sizeof(char)); + strncpy(ret, ptr, len); + ret[len] = 0; + } + + /* change underscores to spaces (of course, this could be adapted + * to use strpbrk and strip any number of characters) */ + while ((temp = strchr(ret, '_')) != NULL) + *temp = ' '; + + /* TODO | the first letter, and any letter following a space, + * TODO | should be capitalized; multiple spaces should be cut + * TODO | down to one */ + + trim_string(ret); + return ret; +} + +/* blecch */ +int get_num_lines(const char *text) +{ + const char *ptr = text; + int n = 0; + + if (!text) + return 0; + for (;;) { + ptr = strpbrk(ptr, "\015\012"); + if (!ptr) + return n; + if (ptr[0] == 13 && ptr[1] == 10) + ptr += 2; + else + ptr++; + n++; + } +} + +/* --------------------------------------------------------------------- */ +/* FILE INFO FUNCTIONS */ + +bool make_backup_file(const char *filename) +{ + char *b; + int e = 0; + + /* kind of a hack for now, but would be easy to do other things like numbered backups (like emacs), + rename the extension to .bak (or add it if the file doesn't have an extension), etc. */ + b = mem_alloc(strlen(filename) + 2); + strcpy(b, filename); + strcat(b, "~"); + if (!rename_file(filename, b)) + e = errno; + free(b); + + if (e) { + errno = e; + return false; + } else { + return true; + } +} + +long file_size(const char *filename) +{ + struct stat buf; + + if (stat(filename, &buf) < 0) { + return EOF; + } + if (S_ISDIR(buf.st_mode)) { + errno = EISDIR; + return EOF; + } + return buf.st_size; +} + +long file_size_fd(int fd) +{ + struct stat buf; + + if (fstat(fd, &buf) == -1) { + return EOF; + } + if (S_ISDIR(buf.st_mode)) { + errno = EISDIR; + return EOF; + } + return buf.st_size; +} + +/* --------------------------------------------------------------------- */ +/* FILESYSTEM FUNCTIONS */ + +bool is_directory(const char *filename) +{ + struct stat buf; + + if (stat(filename, &buf) == -1) { + /* Well, at least we tried. */ + return false; + } + + return S_ISDIR(buf.st_mode); +} + +/* this function is horrible */ +char *get_home_directory(void) +{ +#if defined(__amigaos4__) + return strdup("PROGDIR:"); +#else + char *ptr, buf[PATH_MAX + 1]; + + ptr = getenv("HOME"); +#ifdef WIN32 + if (!ptr) /* let $HOME override %USERPROFILE% on win32... */ + ptr = getenv("USERPROFILE"); +#endif + if (ptr) + return strdup(ptr); + + /* hmm. fall back to the current dir */ + if (getcwd(buf, PATH_MAX)) + return strdup(buf); + + /* still don't have a directory? sheesh. */ + return strdup(FALLBACK_DIR); +#endif +} + +char *str_concat(const char *s, ...) +{ + va_list ap; + char *out = 0; + int len = 0; + + va_start(ap,s); + while (s) { + out = (char*)mem_realloc(out, (len += strlen(s)+1)); + strcat(out, s); + s = va_arg(ap, const char *); + } + return out; + +} + +void put_env_var(const char *key, const char *value) +{ + char *x; + x = mem_alloc(strlen(key) + strlen(value)+2); + sprintf(x, "%s=%s", key,value); + if (putenv(x) == -1) { + perror("putenv"); + exit(255); /* memory exception */ + } +} + +/* fast integer sqrt */ +unsigned int i_sqrt(unsigned int r) +{ + unsigned int t, b, c=0; + for (b = 0x10000000; b != 0; b >>= 2) { + t = c + b; + c >>= 1; + if (t <= r) { + r -= t; + c += b; + } + } + return(c); +} diff --git a/schism/vasprintf.c b/schism/vasprintf.c new file mode 100644 index 000000000..14cec3a77 --- /dev/null +++ b/schism/vasprintf.c @@ -0,0 +1,99 @@ +/* Like vsprintf but provides a pointer to malloc'd storage, which must + be freed by the caller. + Copyright (C) 1994 Free Software Foundation, Inc. + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +Libiberty 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +#include +#include +#include + +//unsigned long strtoul (); +//char *malloc (); +#include +#include + +static int int_vasprintf(char **result, const char *format, va_list *args) +{ + const char *p = format; + /* Add one to make sure that it is never zero, which might cause malloc + to return NULL. */ + int total_width = strlen (format) + 1; + va_list ap; + + memcpy(&ap, args, sizeof(va_list)); + + while (*p != '\0') { + if (*p++ == '%') { + while (strchr ("-+ #0", *p)) + ++p; + if (*p == '*') { + ++p; + total_width += abs(va_arg (ap, int)); + } else + total_width += strtoul(p, (char **) &p, 10); + if (*p == '.') { + ++p; + if (*p == '*') { + ++p; + total_width += abs (va_arg (ap, int)); + } else + total_width += strtoul(p, (char **) &p, 10); + } + while (strchr ("hlL", *p)) + ++p; + /* Should be big enough for any format specifier except %s and floats. */ + total_width += 30; + switch (*p) { + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + case 'c': + (void) va_arg (ap, int); + break; + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + (void) va_arg (ap, double); + /* Since an ieee double can have an exponent of 307, we'll + make the buffer wide enough to cover the gross case. */ + total_width += 307; + break; + case 's': + total_width += strlen (va_arg (ap, char *)); + break; + case 'p': + case 'n': + (void) va_arg (ap, char *); + break; + } + } + } + *result = mem_alloc (total_width); + return vsprintf (*result, format, *args); +} + +int vasprintf(char **result, const char *format, va_list args); +int vasprintf(char **result, const char *format, va_list args) +{ + return int_vasprintf (result, format, &args); +} diff --git a/schism/video.c b/schism/video.c new file mode 100644 index 000000000..0e8e0771f --- /dev/null +++ b/schism/video.c @@ -0,0 +1,1263 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#define NATIVE_SCREEN_WIDTH 640 +#define NATIVE_SCREEN_HEIGHT 400 + +#include "headers.h" +#include "it.h" + +#if HAVE_SYS_KD_H +# include +#endif +#if HAVE_LINUX_FB_H +# include +#endif +#include +#include +#if HAVE_SYS_IOCTL_H +# include +#endif + +/* for memcpy */ +#include +#include + +#include + +#include + +#include +#include + +#include "video.h" + +#include "auto/schismico.h" + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif + +extern int macosx_did_finderlaunch; + +#define NVIDIA_PixelDataRange 1 + +#ifdef NVIDIA_PixelDataRange + +#ifndef WGL_NV_allocate_memory +#define WGL_NV_allocate_memory 1 +typedef void * (APIENTRY * PFNWGLALLOCATEMEMORYNVPROC) (int size, float readfreq, float writefreq, float priority); +typedef void (APIENTRY * PFNWGLFREEMEMORYNVPROC) (void *pointer); +#endif + +PFNWGLALLOCATEMEMORYNVPROC db_glAllocateMemoryNV = NULL; +PFNWGLFREEMEMORYNVPROC db_glFreeMemoryNV = NULL; + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#endif + +PFNGLPIXELDATARANGENVPROC glPixelDataRangeNV = NULL; + +#endif + +/* leeto drawing skills */ +#define MOUSE_HEIGHT 14 +static const unsigned int _mouse_pointer[] = { + /* x....... */ 0x80, + /* xx...... */ 0xc0, + /* xxx..... */ 0xe0, + /* xxxx.... */ 0xf0, + /* xxxxx... */ 0xf8, + /* xxxxxx.. */ 0xfc, + /* xxxxxxx. */ 0xfe, + /* xxxxxxxx */ 0xff, + /* xxxxxxx. */ 0xfe, + /* xxxxx... */ 0xf8, + /* x...xx.. */ 0x8c, + /* ....xx.. */ 0x0c, + /* .....xx. */ 0x06, + /* .....xx. */ 0x06, + +0,0 +}; + + +#ifdef WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +struct private_hwdata { + LPDIRECTDRAWSURFACE3 dd_surface; + LPDIRECTDRAWSURFACE3 dd_writebuf; +}; +#endif + +static struct video_cf video; + + +struct video_cf { + struct { + unsigned int width; + unsigned int height; + + int autoscale; + + /* stuff for scaling */ + int sx, sy; + int *sax, *say; + } draw; + struct { + unsigned int width,height,bpp; + int want_fixed; + + int fullscreen; + int doublebuf; + int want_type; + int type; +#define VIDEO_SURFACE 0 +#define VIDEO_DDRAW 1 +#define VIDEO_YUV 2 +#define VIDEO_GL 3 + } desktop; + struct { + unsigned int pitch; + void * framebuf; + GLuint texture; + GLuint displaylist; + GLint max_texsize; + int bilinear; + int packed_pixel; + int paletted_texture; +#if defined(NVIDIA_PixelDataRange) + int pixel_data_range; +#endif + } gl; +#if defined(WIN32) + struct { + SDL_Surface * surface; + RECT rect; + DDBLTFX fx; + } ddblit; +#endif + int yuvlayout; +#define YUV_UYVY 0x59565955 +#define YUV_YUY2 0x32595559 + SDL_Rect clip; + SDL_Surface * surface; + SDL_Overlay * overlay; + /* to convert 32-bit color to 24-bit color */ + unsigned char *cv32backing; + struct { + unsigned int x; + unsigned int y; + int visible; + } mouse; + + unsigned int pal[16]; + + unsigned int tc_bgr32[16]; + unsigned int tc_identity[16]; +}; +static int int_log2(int val) { + int l = 0; + while ((val >>= 1 ) != 0) l++; + return l; +} + +void _video_scanmouse(unsigned int y, + unsigned char *mousebox, unsigned int *x) +{ + unsigned int g,i, z,v; + + if (!video.mouse.visible + || !(status.flags & IS_FOCUSED) + || y < video.mouse.y + || y >= video.mouse.y+MOUSE_HEIGHT) { + *x = 255; + return; + } + *x = (video.mouse.x / 8); + v = (video.mouse.x % 8); + z = _mouse_pointer[ y - video.mouse.y ]; + mousebox[0] = z >> v; + mousebox[1] = (z << (8-v)) & 0xff; +} + +#ifdef MACOSX +#include +#include +#include + +/* opengl is EVERYWHERE on macosx, and we can't use our dynamic linker mumbo +jumbo so easily... +*/ +#define my_glCallList(z) glCallList(z) +#define my_glTexSubImage2D(a,b,c,d,e,f,g,h,i) glTexSubImage2D(a,b,c,d,e,f,g,h,i) +#define my_glDeleteLists(a,b) glDeleteLists(a,b) +#define my_glTexParameteri(a,b,c) glTexParameteri(a,b,c) +#define my_glBegin(g) glBegin(g) +#define my_glEnd() glEnd() +#define my_glEndList() glEndList() +#define my_glTexCoord2f(a,b) glTexCoord2f(a,b) +#define my_glVertex2f(a,b) glVertex2f(a,b) +#define my_glNewList(a,b) glNewList(a,b) +#define my_glIsList(a) glIsList(a) +#define my_glGenLists(a) glGenLists(a) +#define my_glLoadIdentity() glLoadIdentity() +#define my_glMatrixMode(a) glMatrixMode(a) +#define my_glEnable(a) glEnable(a) +#define my_glDisable(a) glDisable(a) +#define my_glBindTexture(a,b) glBindTexture(a,b) +#define my_glClear(z) glClear(z) +#define my_glClearColor(a,b,c,d) glClearColor(a,b,c,d) +#define my_glGetString(z) glGetString(z) +#define my_glTexImage2D(a,b,c,d,e,f,g,h,i) glTexImage2D(a,b,c,d,e,f,g,h,i) +#define my_glGetIntegerv(a,b) glGetIntegerv(a,b) +#define my_glShadeModel(a) glShadeModel(a) +#define my_glGenTextures(a,b) glGenTextures(a,b) +#define my_glDeleteTextures(a,b) glDeleteTextures(a,b) +#define my_glViewport(a,b,c,d) glViewport(a,b,c,d) +#define my_glEnableClientState(z) glEnableClientState(z) +#else +/* again with the dynamic linker nonsense; note these are APIENTRY for compatability +with win32. */ +static void (APIENTRY *my_glCallList)(GLuint); +static void (APIENTRY *my_glTexSubImage2D)(GLenum,GLint,GLint,GLint, + GLsizei,GLsizei,GLenum,GLenum, + const GLvoid *); +static void (APIENTRY *my_glDeleteLists)(GLuint,GLsizei); +static void (APIENTRY *my_glTexParameteri)(GLenum,GLenum,GLint); +static void (APIENTRY *my_glBegin)(GLenum); +static void (APIENTRY *my_glEnd)(void); +static void (APIENTRY *my_glEndList)(void); +static void (APIENTRY *my_glTexCoord2f)(GLfloat,GLfloat); +static void (APIENTRY *my_glVertex2f)(GLfloat,GLfloat); +static void (APIENTRY *my_glNewList)(GLuint, GLenum); +static GLboolean (APIENTRY *my_glIsList)(GLuint); +static GLuint (APIENTRY *my_glGenLists)(GLsizei); +static void (APIENTRY *my_glLoadIdentity)(void); +static void (APIENTRY *my_glMatrixMode)(GLenum); +static void (APIENTRY *my_glEnable)(GLenum); +static void (APIENTRY *my_glDisable)(GLenum); +static void (APIENTRY *my_glBindTexture)(GLenum,GLuint); +static void (APIENTRY *my_glClear)(GLbitfield mask); +static void (APIENTRY *my_glClearColor)(GLclampf,GLclampf,GLclampf,GLclampf); +static const GLubyte* (APIENTRY *my_glGetString)(GLenum); +static void (APIENTRY *my_glTexImage2D)(GLenum,GLint,GLint,GLsizei,GLsizei,GLint, + GLenum,GLenum,const GLvoid *); +static void (APIENTRY *my_glGetIntegerv)(GLenum, GLint *); +static void (APIENTRY *my_glShadeModel)(GLenum); +static void (APIENTRY *my_glGenTextures)(GLsizei,GLuint*); +static void (APIENTRY *my_glDeleteTextures)(GLsizei, const GLuint *); +static void (APIENTRY *my_glViewport)(GLint,GLint,GLsizei,GLsizei); +static void (APIENTRY *my_glEnableClientState)(GLenum); +#endif + +static int _did_init = 0; + +const char *video_driver_name(void) +{ + switch (video.desktop.type) { + case VIDEO_SURFACE: + return "sdl"; + case VIDEO_YUV: + return "yuv"; + case VIDEO_GL: + return "opengl"; + case VIDEO_DDRAW: + return "directdraw"; + }; + /* err.... */ + return "auto"; +} + +void video_report(void) +{ + char buf[256]; + switch (video.desktop.type) { + case VIDEO_SURFACE: + log_appendf(2," Using %s%s surface SDL driver '%s'", + ((video.surface->flags & SDL_HWSURFACE) + ? "hardware" : "software"), + ((video.surface->flags & SDL_HWACCEL) ? " accelerated" : ""), + SDL_VideoDriverName(buf,256)); + if (SDL_MUSTLOCK(video.surface)) + log_append(4,0," Must lock surface"); + log_appendf(5, " Display format: %d bits/pixel", + video.surface->format->BitsPerPixel); + break; + case VIDEO_YUV: + if (video.overlay->hw_overlay) { + log_append(2,0, " Using hardware accelerated video overlay"); + } else { + log_append(2,0, " Using video overlay"); + } + if (video.yuvlayout == YUV_UYVY) { + log_append(5,0, " Display format: UYVY"); + } else if (video.yuvlayout == YUV_YUY2) { + log_append(5,0, " Display format: YUY2"); + } + break; + case VIDEO_GL: + log_append(2,0, " Using OPENGL interface"); +#if defined(NVIDIA_PixelDataRange) + if (video.gl.pixel_data_range) { + log_append(2,0, " Using NVidia pixel range extensions"); + } +#endif + log_appendf(5, " Display format: %d bits/pixel", + video.surface->format->BitsPerPixel); + break; + case VIDEO_DDRAW: + log_append(2,0, " Using DirectDraw interface"); + log_appendf(5, " Display format: %d bits/pixel", + video.surface->format->BitsPerPixel); + break; + }; + if (video.desktop.fullscreen) { + log_appendf(2," at %dx%d", video.desktop.width, video.desktop.height); + } +} + +static int _did_preinit = 0; +static void _video_preinit(void) +{ + if (!_did_preinit) { + memset(&video, 0, sizeof(video)); + _did_preinit = 1; + } +} + +int video_is_fullscreen(void) +{ + return video.desktop.fullscreen; +} +void video_fullscreen(int tri) +{ + _video_preinit(); + + if (tri == 1) { + video.desktop.fullscreen = 1; + } else if (tri == 0) { + video.desktop.fullscreen = 0; + } else if (tri < 0) { + video.desktop.fullscreen = video.desktop.fullscreen ? 0 : 1; + } + if (_did_init) { + if (video.desktop.fullscreen) { + video_resize(video.desktop.width, video.desktop.height); + } else { + video_resize(0, 0); + } + video_report(); + } +} + +extern SDL_Surface *xpmdata(char *x[]); + +void video_init(const char *driver) +{ + static int did_this_1 = 0; + static int did_this_2 = 0; + static int did_this_3 = 0; + const char *gl_ext; + SDL_Rect **modes; + int i, x, y; + + if (driver && strcasecmp(driver, "auto") == 0) driver = 0; + + _video_preinit(); + vgamem_clear(); + vgamem_flip(); + + for (i = 0; i < 16; i++) video.tc_identity[i] = i; + + SDL_WM_SetCaption("Schism Tracker CVS", "Schism Tracker"); +#ifndef MACOSX +/* apple/macs use a bundle; this overrides their nice pretty icon */ + SDL_WM_SetIcon(xpmdata(_schism_icon_xpm), NULL); +#endif + video.draw.width = NATIVE_SCREEN_WIDTH; + video.draw.height = NATIVE_SCREEN_HEIGHT; + video.mouse.visible = 1; + + /* used for scaling and 24bit conversion */ + if (!_did_init) { + video.cv32backing = (unsigned char *)mem_alloc(NATIVE_SCREEN_WIDTH * 8); + } + + video.yuvlayout = YUV_UYVY; + if (getenv("YUVLAYOUT")) { + if (strcasecmp(getenv("YUVLAYOUT"), "YUY2") == 0 + || strcasecmp(getenv("YUVLAYOUT"), "YUNV") == 0 + || strcasecmp(getenv("YUVLAYOUT"), "V422") == 0 + || strcasecmp(getenv("YUVLAYOUT"), "YUYV") == 0) { + video.yuvlayout = YUV_YUY2; + } + } + + if (getenv("DOUBLEBUF") && atoi(getenv("DOUBLEBUF")) > 0) { + video.desktop.doublebuf = 1; + } + + video.desktop.want_type = VIDEO_SURFACE; +#ifdef WIN32 + if (!driver) { + /* alright, let's have some fun. */ + if (did_this_1) { + driver = "sdl"; /* err... */ + } else if (LoadLibrary("ddraw.dll")) { + driver = "ddraw"; + did_this_1 = 1; + } else { + driver = "windib"; + did_this_1 = 1; + } + } + + if (!strcasecmp(driver, "windib")) { + putenv("SDL_VIDEODRIVER=windib"); + } else if (!strcasecmp(driver, "ddraw") || !strcasecmp(driver,"directdraw")) { + putenv("SDL_VIDEODRIVER=directx"); + video.desktop.want_type = VIDEO_DDRAW; + } else if (!strcasecmp(driver, "sdlddraw")) { + putenv("SDL_VIDEODRIVER=directx"); + } +#else + if (!driver) { +#ifdef MACOSX + if (macosx_did_finderlaunch) { + driver = "gl"; + } else /* fall through */ +#endif + if (getenv("DISPLAY")) { + driver = "x11"; + } else { +#ifdef MACOSX + driver = "gl"; +#else + driver = "sdlauto"; +#endif + } + } +#endif + if (!strcasecmp(driver, "x11")) { + video.desktop.want_type = VIDEO_YUV; + putenv("SDL_VIDEO_YUV_DIRECT=1"); + putenv("SDL_VIDEO_YUV_HWACCEL=1"); + putenv("SDL_VIDEODRIVER=x11"); + } else if (!strcasecmp(driver, "yuv")) { + video.desktop.want_type = VIDEO_YUV; + putenv("SDL_VIDEO_YUV_DIRECT=1"); + putenv("SDL_VIDEO_YUV_HWACCEL=1"); + /* leave everything else alone... */ + } else if (!strcasecmp(driver, "gl")) { + video.desktop.want_type = VIDEO_GL; + } else if (!strcasecmp(driver, "sdlauto") || !strcasecmp(driver,"sdl")) { + /* okay.... */ + } + +/* macosx will actually prefer opengl :) */ +#ifndef MACOSX + if (video.desktop.want_type == VIDEO_GL) { +#define Z(q) my_ ## q = SDL_GL_GetProcAddress( #q ); if (! my_ ## q) { video.desktop.want_type = VIDEO_SURFACE; goto SKIP1; } + if (did_this_2) { + /* do nothing */ + } else if (getenv("SDL_VIDEO_GL_DRIVER") + && SDL_GL_LoadLibrary(getenv("SDL_VIDEO_GL_DRIVER")) == 0) { + /* ok */ + } else if (SDL_GL_LoadLibrary("GL") != 0) + if (SDL_GL_LoadLibrary("libGL.so") != 0) + if (SDL_GL_LoadLibrary("opengl32.dll") != 0) + if (SDL_GL_LoadLibrary("opengl.dll") != 0) + { /* do nothing ... because the bottom routines will fail */ } + did_this_2 = 1; + Z(glCallList) + Z(glTexSubImage2D) + Z(glDeleteLists) + Z(glTexParameteri) + Z(glBegin) + Z(glEnd) + Z(glEndList) + Z(glTexCoord2f) + Z(glVertex2f) + Z(glNewList) + Z(glIsList) + Z(glGenLists) + Z(glLoadIdentity) + Z(glMatrixMode) + Z(glEnable) + Z(glDisable) + Z(glBindTexture) + Z(glClear) + Z(glClearColor) + Z(glGetString) + Z(glTexImage2D) + Z(glGetIntegerv) + Z(glShadeModel) + Z(glGenTextures) + Z(glDeleteTextures) + Z(glViewport) + Z(glEnableClientState) +#undef Z + } +SKIP1: +#endif + if (video.desktop.want_type == VIDEO_GL) { + video.surface = SDL_SetVideoMode(640,400,0,SDL_OPENGL|SDL_RESIZABLE); + if (!video.surface) { + /* fallback */ + video.desktop.want_type = VIDEO_SURFACE; + } + video.gl.framebuf = 0; + video.gl.texture = 0; + video.gl.displaylist = 0; + my_glGetIntegerv(GL_MAX_TEXTURE_SIZE, &video.gl.max_texsize); +#if defined(NVIDIA_PixelDataRange) + glPixelDataRangeNV = (PFNGLPIXELDATARANGENVPROC) + SDL_GL_GetProcAddress("glPixelDataRangeNV"); + db_glAllocateMemoryNV = (PFNWGLALLOCATEMEMORYNVPROC) + SDL_GL_GetProcAddress("wglAllocateMemoryNV"); + db_glFreeMemoryNV = (PFNWGLFREEMEMORYNVPROC) + SDL_GL_GetProcAddress("wglFreeMemoryNV"); +#endif + gl_ext = (const char *)my_glGetString(GL_EXTENSIONS); + if (!gl_ext) gl_ext = (const char *)""; + video.gl.packed_pixel=(strstr(gl_ext,"EXT_packed_pixels")>0); + video.gl.paletted_texture=(strstr(gl_ext,"EXT_paletted_texture")>0); +#if defined(NVIDIA_PixelDataRange) + video.gl.pixel_data_range=(strstr(gl_ext,"GL_NV_pixel_data_range")>0) && glPixelDataRangeNV && db_glAllocateMemoryNV && db_glFreeMemoryNV; +#endif + } + + if (!video.surface) { + /* if we already got one... */ + video.surface = SDL_SetVideoMode(640,400,0,SDL_RESIZABLE); + if (!video.surface) { + perror("SDL_SetVideoMode"); + exit(255); + } + } + video.desktop.bpp = video.surface->format->BitsPerPixel; + + video.desktop.want_fixed = 1; + if (getenv("VIDEO_NO_FIXED")) video.desktop.want_fixed = 0; + + modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE); + x = y = -1; + if (modes != (SDL_Rect**)0 && modes != (SDL_Rect**)-1) { + for (i = 0; modes[i]; i++) { +/* + log_appendf(2, "Host-acceptable video mode: %dx%d", + modes[i]->w, modes[i]->h); +*/ + if (modes[i]->w < NATIVE_SCREEN_WIDTH) continue; + if (modes[i]->h < NATIVE_SCREEN_WIDTH)continue; + if (x == -1 || y == -1 || modes[i]->w < x || modes[i]->h < y) { + x = modes[i]->w; + y = modes[i]->h; + if (x == NATIVE_SCREEN_WIDTH || y == NATIVE_SCREEN_HEIGHT) + break; + } + } + } + if (x < 0 || y < 0) { + x = 640; + y = 480; + } + /*log_appendf(2, "Ideal desktop size: %dx%d", x, y); */ + video.desktop.width = x; + video.desktop.height = y; + + switch (video.desktop.want_type) { + case VIDEO_GL: + video.gl.bilinear = 1; + case VIDEO_YUV: + if (video.desktop.bpp == 32) break; + /* fall through */ + case VIDEO_DDRAW: +#ifdef WIN32 + if (video.desktop.bpp == 32 || video.desktop.bpp == 16) { + /* good enough for here! */ + video.desktop.want_type = VIDEO_DDRAW; + break; + } +#endif + /* fall through */ + case VIDEO_SURFACE: + /* no scaling when using the SDL surfaces directly */ +#if HAVE_LINUX_FB_H + if (!getenv("DISPLAY")) { + struct fb_var_screeninfo s; + int fb = -1; + if (getenv("SDL_FBDEV")) { + fb = open(getenv("SDL_FBDEV"), O_RDONLY); + } + if (fb == -1) + fb = open("/dev/fb0", O_RDONLY); + if (fb > -1) { + if (ioctl(fb, FBIOGET_VSCREENINFO, &s) < 0) { + perror("ioctl FBIOGET_VSCREENINFO"); + } else { + x = s.xres; + if (x < NATIVE_SCREEN_WIDTH) + x = NATIVE_SCREEN_WIDTH; + y = s.yres; + video.desktop.bpp = s.bits_per_pixel; + video.desktop.fullscreen = 1; + } + (void)close(fb); + } + } +#endif + video.desktop.want_type = VIDEO_SURFACE; + break; + }; + /* okay, i think we're ready */ + SDL_ShowCursor(SDL_DISABLE); + SDL_EventState(SDL_VIDEORESIZE, SDL_ENABLE); + SDL_EventState(SDL_VIDEOEXPOSE, SDL_ENABLE); + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + SDL_EventState(SDL_USEREVENT, SDL_ENABLE); + SDL_EventState(SDL_ACTIVEEVENT, SDL_ENABLE); + SDL_EventState(SDL_KEYDOWN, SDL_ENABLE); + SDL_EventState(SDL_KEYUP, SDL_ENABLE); + SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); + SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_ENABLE); + SDL_EventState(SDL_MOUSEBUTTONUP, SDL_ENABLE); + _did_init = 1; + video_fullscreen(video.desktop.fullscreen); +} +static SDL_Surface *_setup_surface(unsigned int w, unsigned int h, unsigned int sdlflags) +{ + unsigned int bpp; + + bpp = video.desktop.bpp; + if (video.desktop.doublebuf) sdlflags |= (SDL_DOUBLEBUF|SDL_ASYNCBLIT); + if (video.desktop.fullscreen) { + w = video.desktop.width; + h = video.desktop.height; + } else { + sdlflags |= SDL_RESIZABLE; + } + + if (video.desktop.want_fixed) { + double ratio_w = (double)w / (double)NATIVE_SCREEN_WIDTH; + double ratio_h = (double)h / (double)NATIVE_SCREEN_HEIGHT; + if (ratio_w < ratio_h) { + video.clip.w = w; + video.clip.h = (double)NATIVE_SCREEN_HEIGHT * ratio_w; + } else { + video.clip.h = h; + video.clip.w = (double)NATIVE_SCREEN_WIDTH * ratio_h; + } + video.clip.x=(w-video.clip.w)/2; + video.clip.y=(h-video.clip.h)/2; + } else { + video.clip.x = 0; + video.clip.y = 0; + video.clip.w = w; + video.clip.h = h; + } + + if (video.desktop.fullscreen) { + sdlflags |= SDL_FULLSCREEN; + sdlflags &=~SDL_RESIZABLE; + video.surface = SDL_SetVideoMode(w, h, bpp, + sdlflags | SDL_FULLSCREEN | SDL_HWSURFACE); + + } else { + video.surface = SDL_SetVideoMode(w, h, bpp, + sdlflags | SDL_HWSURFACE); + } + if (!video.surface) { + perror("SDL_SetVideoMode"); + exit(EXIT_FAILURE); + } + return video.surface; +} +void video_resize(unsigned int width, unsigned int height) +{ + GLfloat tex_width, tex_height; + int *csax, *csay; + int x, y, csx, csy; + unsigned int gt; + int texsize; + int bpp; + + if (!height) height = NATIVE_SCREEN_HEIGHT; + if (!width) width = NATIVE_SCREEN_WIDTH; + video.draw.width = width; + video.draw.height = height; + + video.draw.autoscale = 1; + + switch (video.desktop.want_type) { + case VIDEO_DDRAW: +#ifdef WIN32 + if (video.ddblit.surface) { + SDL_FreeSurface(video.ddblit.surface); + video.ddblit.surface = 0; + } + memset(&video.ddblit.fx, 0, sizeof(DDBLTFX)); + video.ddblit.fx.dwSize = sizeof(DDBLTFX); + _setup_surface(width, height, SDL_DOUBLEBUF); + video.ddblit.rect.top = video.clip.y; + video.ddblit.rect.left = video.clip.x; + video.ddblit.rect.right = video.clip.x + video.clip.w; + video.ddblit.rect.bottom = video.clip.y + video.clip.h; + video.ddblit.surface = SDL_CreateRGBSurface(SDL_HWSURFACE, + NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, + video.surface->format->BitsPerPixel, + video.surface->format->Rmask, + video.surface->format->Gmask, + video.surface->format->Bmask,0); + bpp = video.surface->format->BytesPerPixel; + if (video.ddblit.surface + && ((video.ddblit.surface->flags & SDL_HWSURFACE) == SDL_HWSURFACE)) { + video.desktop.type = VIDEO_DDRAW; + break; + } + /* fall through */ +#endif + case VIDEO_SURFACE: +RETRYSURF: /* use SDL surfaces */ + video.draw.autoscale = 0; + _setup_surface(width, height, 0); + video.desktop.type = VIDEO_SURFACE; + bpp = video.surface->format->BytesPerPixel; + break; + + case VIDEO_YUV: + if (video.overlay) { + SDL_FreeYUVOverlay(video.overlay); + video.overlay = 0; + } + _setup_surface(width, height, 0); + if (video.yuvlayout == YUV_UYVY) { + video.overlay = SDL_CreateYUVOverlay( + NATIVE_SCREEN_WIDTH*2, + NATIVE_SCREEN_HEIGHT, + SDL_UYVY_OVERLAY, + video.surface); + } else if (video.yuvlayout == YUV_YUY2) { + video.overlay = SDL_CreateYUVOverlay( + NATIVE_SCREEN_WIDTH*2, + NATIVE_SCREEN_HEIGHT, + SDL_YUY2_OVERLAY, + video.surface); + } else { + /* unknown layout */ + goto RETRYSURF; + } + if (!video.overlay) { + /* can't get an overlay */ + goto RETRYSURF; + } + video.desktop.type = VIDEO_YUV; + bpp = 4; + break; + case VIDEO_GL: + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + _setup_surface(width, height, SDL_OPENGL); + if (video.surface->format->BitsPerPixel < 15) { + goto RETRYSURF; + } + my_glViewport(video.clip.x, video.clip.y, + video.clip.w, video.clip.h); + texsize = 2 << int_log2(NATIVE_SCREEN_WIDTH); + if (texsize > video.gl.max_texsize) { + /* can't do opengl! */ + goto RETRYSURF; + } +#if defined(NVIDIA_PixelDataRange) + if (video.gl.pixel_data_range) { + if (!video.gl.framebuf) { + video.gl.framebuf = db_glAllocateMemoryNV( + NATIVE_SCREEN_WIDTH*NATIVE_SCREEN_HEIGHT*4, + 0.0, 1.0, 1.0); + } + glPixelDataRangeNV(GL_WRITE_PIXEL_DATA_RANGE_NV, + NATIVE_SCREEN_WIDTH*NATIVE_SCREEN_HEIGHT*4, + video.gl.framebuf); + my_glEnableClientState(GL_WRITE_PIXEL_DATA_RANGE_NV); + } else +#endif + if (!video.gl.framebuf) { + video.gl.framebuf = (void*)mem_alloc(NATIVE_SCREEN_WIDTH + *NATIVE_SCREEN_HEIGHT*4); + } + + video.gl.pitch = NATIVE_SCREEN_WIDTH * 4; + my_glMatrixMode(GL_PROJECTION); + my_glDeleteTextures(1, &video.gl.texture); + my_glGenTextures(1, &video.gl.texture); + my_glBindTexture(GL_TEXTURE_2D, video.gl.texture); + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + if (video.gl.bilinear) { + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + GL_LINEAR); + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR); + } else { + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + GL_NEAREST); + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_NEAREST); + } + my_glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texsize, texsize, + 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, 0); + my_glClearColor(0.0, 0.0, 0.0, 1.0); + my_glClear(GL_COLOR_BUFFER_BIT); + SDL_GL_SwapBuffers(); + my_glClear(GL_COLOR_BUFFER_BIT); + my_glShadeModel(GL_FLAT); + my_glDisable(GL_DEPTH_TEST); + my_glDisable(GL_LIGHTING); + my_glDisable(GL_CULL_FACE); + my_glEnable(GL_TEXTURE_2D); + my_glMatrixMode(GL_MODELVIEW); + my_glLoadIdentity(); + + tex_width = ((GLfloat)(NATIVE_SCREEN_WIDTH)/(GLfloat)texsize); + tex_height = ((GLfloat)(NATIVE_SCREEN_HEIGHT)/(GLfloat)texsize); + if (my_glIsList(video.gl.displaylist)) + my_glDeleteLists(video.gl.displaylist, 1); + video.gl.displaylist = my_glGenLists(1); + my_glNewList(video.gl.displaylist, GL_COMPILE); + my_glBindTexture(GL_TEXTURE_2D, video.gl.texture); + my_glBegin(GL_QUADS); + my_glTexCoord2f(0,tex_height); my_glVertex2f(-1.0f,-1.0f); + my_glTexCoord2f(tex_width,tex_height);my_glVertex2f(1.0f,-1.0f); + my_glTexCoord2f(tex_width,0); my_glVertex2f(1.0f, 1.0f); + my_glTexCoord2f(0,0); my_glVertex2f(-1.0f, 1.0f); + my_glEnd(); + my_glEndList(); + video.desktop.type = VIDEO_GL; + bpp = 4; + break; + }; + if (!video.draw.autoscale && (video.clip.w != NATIVE_SCREEN_WIDTH + || video.clip.h != NATIVE_SCREEN_HEIGHT)) { + if (video.draw.sax) free(video.draw.sax); + if (video.draw.say) free(video.draw.say); + video.draw.sax = (int*)mem_alloc((video.clip.w+1)*sizeof(int)); + video.draw.say = (int*)mem_alloc((video.clip.h+1)*sizeof(int)); + video.draw.sx = (int)(65536.0*(float)(NATIVE_SCREEN_WIDTH-1) + / (video.clip.w)); + video.draw.sy = (int)(65536.0*(float)(NATIVE_SCREEN_HEIGHT-1) + / (video.clip.h)); + + /* precalculate increments */ + csx = 0; + csax = video.draw.sax; + for (x = 0; x <= video.clip.w; x++) { + *csax = csx; + csax++; + csx &= 65535; + csx += video.draw.sx; + } + csy = 0; + csay = video.draw.say; + for (y = 0; y <= video.clip.h; y++) { + *csay = csy; + csay++; + csy &= 65535; + csy += video.draw.sy; + } + + } else { + video.draw.sx = video.draw.sy = 0; + } + + status.flags |= (NEED_UPDATE); +} +void video_colors(unsigned char palette[16][3]) +{ + static SDL_Color imap[16]; + unsigned int y,u,v; + int i; + + switch (video.desktop.type) { + case VIDEO_SURFACE: + if (video.surface->format->BytesPerPixel == 1) { + /* okay, indexed color */ + for (i = 0; i < 16; i++) { + video.pal[i] = i; + imap[i].r = palette[i][0]; + imap[i].g = palette[i][1]; + imap[i].b = palette[i][2]; + } + SDL_SetColors(video.surface, imap, 0, 16); + return; + } + /* fall through */ + case VIDEO_DDRAW: + for (i = 0; i < 16; i++) { + video.pal[i] = SDL_MapRGB(video.surface->format, + palette[i][0], + palette[i][1], + palette[i][2]); + } + break; + case VIDEO_YUV: + for (i = 0; i < 16; i++) { + y = ( 9797*(palette[i][0]) + 19237*(palette[i][1]) + + 3734*(palette[i][2]) ) >> 15; + u = (18492*((palette[i][2])-(y)) >> 15) + 128; + v = (23372*((palette[i][0])-(y)) >> 15) + 128; + + switch (video.yuvlayout) { + case YUV_UYVY: + /* u0 y0 v0 y1 */ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + video.pal[i] = y | (v << 8) | (y << 16) | (u << 24); +#else + video.pal[i] = u | (y << 8) | (v << 16) | (y << 24); +#endif + break; + case YUV_YUY2: + /* y0 u0 y1 v0 */ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + video.pal[i] = v | (y << 8) | (u << 16) | (y << 24); +#else + video.pal[i] = y | (u << 8) | (y << 16) | (v << 24); +#endif + break; + }; + } + break; + case VIDEO_GL: + /* BGRA */ + for (i = 0; i < 16; i++) { + video.pal[i] = palette[i][2] | + (palette[i][1] << 8) | + (palette[i][0] << 16) | (255 << 24); + } + break; + }; + + /* BGR; for scaling */ + for (i = 0; i < 16; i++) { + video.tc_bgr32[i] = palette[i][2] | + (palette[i][1] << 8) | + (palette[i][0] << 16) | (255 << 24); + } +} + +void video_refresh(void) +{ + vgamem_flip(); + vgamem_clear(); +} + +static void inline _blit1n(int bpp, unsigned char *pixels, unsigned int pitch) +{ + unsigned int *csp, *esp, *dp; + int *csay, *csax; + unsigned int *c00, *c01, *c10, *c11; + unsigned int outr, outg, outb; + unsigned int pad; + int y, x, ey, ex, t1, t2, sstep; + int iny, lasty; + + csay = video.draw.say; + csp = (unsigned int *)video.cv32backing; + esp = csp + NATIVE_SCREEN_WIDTH; + lasty = -2; + iny = 0; + pad = pitch - (video.clip.w * bpp); + for (y = 0; y < video.clip.h; y++) { + if (iny != lasty) { + /* we'll downblit the colors later */ + if (iny == lasty + 1) { + /* move up one line */ + vgamem_scan32(iny+1, csp, video.tc_bgr32); + dp = esp; esp = csp; csp=dp; + } else { + vgamem_scan32(iny, (csp = (unsigned int *)video.cv32backing), + video.tc_bgr32); + vgamem_scan32(iny+1, (esp = (csp + NATIVE_SCREEN_WIDTH)), + video.tc_bgr32); + } + lasty = iny; + } + c00 = csp; + c01 = csp; c01++; + c10 = esp; + c11 = esp; c11++; + csax = video.draw.sax; + for (x = 0; x < video.clip.w; x++) { + ex = (*csax & 65535); + ey = (*csay & 65535); +#define BLUE(Q) (Q&255) +#define GREEN(Q) (Q >> 8)&255 +#define RED(Q) (Q >> 16)&255 + t1 = ((((BLUE(*c01)-BLUE(*c00))*ex) >> 16)+BLUE(*c00)) & 255; + t2 = ((((BLUE(*c11)-BLUE(*c10))*ex) >> 16)+BLUE(*c10)) & 255; + outb = (((t2-t1)*ey) >> 16) + t1; + + t1 = ((((GREEN(*c01)-GREEN(*c00))*ex) >> 16)+GREEN(*c00)) & 255; + t2 = ((((GREEN(*c11)-GREEN(*c10))*ex) >> 16)+GREEN(*c10)) & 255; + outg = (((t2-t1)*ey) >> 16) + t1; + + t1 = ((((RED(*c01)-RED(*c00))*ex) >> 16)+RED(*c00)) & 255; + t2 = ((((RED(*c11)-RED(*c10))*ex) >> 16)+RED(*c10)) & 255; + outr = (((t2-t1)*ey) >> 16) + t1; +#undef RED +#undef GREEN +#undef BLUE + csax++; + sstep = (*csax >> 16); + c00 += sstep; + c01 += sstep; + c10 += sstep; + c11 += sstep; + + /* write output "pixel" */ + switch (bpp) { + case 4: + case 3: + (*(unsigned int *)pixels) = SDL_MapRGB( + video.surface->format, outr, outg, outb); + break; + case 2: + /* err... */ + (*(unsigned short *)pixels) = SDL_MapRGB( + video.surface->format, outr, outg, outb); + break; + case 1: + /* er... */ + (*pixels) = SDL_MapRGB( + video.surface->format, outr, outg, outb); + break; + }; + pixels += bpp; + } + pixels += pad; + csay++; + /* move y position on source */ + iny += (*csay >> 16); + } +} +static void inline _blit11(int bpp, unsigned char *pixels, unsigned int pitch) +{ + unsigned char *pdata; + unsigned int x, y; + int pitch24; + + switch (bpp) { + case 4: + for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { + vgamem_scan32(y, (unsigned int *)pixels, video.pal); + pixels += pitch; + } + break; + case 3: + /* ... */ + pitch24 = pitch - (NATIVE_SCREEN_WIDTH * 3); + if (pitch24 < 0) { + return; /* eh? */ + } + for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { + vgamem_scan32(y, (unsigned int *)video.cv32backing, video.pal); + /* okay... */ + pdata = video.cv32backing; + for (x = 0; x < NATIVE_SCREEN_WIDTH; x++) { +#if WORDS_BIGENDIAN + memcpy(pixels, pdata+1, 3); +#else + memcpy(pixels, pdata, 3); +#endif + pdata += 4; + pixels += 3; + } + pixels += pitch24; + } + break; + case 2: + for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { + vgamem_scan16(y, (unsigned short *)pixels, video.pal); + pixels += pitch; + } + break; + case 1: + for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { + vgamem_scan8(y, (unsigned char *)pixels, + video.tc_identity); + pixels += pitch; + } + break; + }; +} + + +void video_blit(void) +{ + unsigned char *pixels; + unsigned int bpp; + unsigned int pitch; + + switch (video.desktop.type) { + case VIDEO_SURFACE: + if (SDL_MUSTLOCK(video.surface)) { + while (SDL_LockSurface(video.surface) == -1) { + SDL_Delay(10); + } + } + bpp = video.surface->format->BytesPerPixel; + pixels = (unsigned char *)video.surface->pixels; + pixels += video.clip.y * video.surface->pitch; + pixels += video.clip.x * bpp; + pitch = video.surface->pitch; + break; + case VIDEO_YUV: + SDL_LockYUVOverlay(video.overlay); + pixels = (unsigned char *)*(video.overlay->pixels); + pitch = *(video.overlay->pitches); + bpp = 4; + break; + case VIDEO_GL: + pixels = (unsigned char *)video.gl.framebuf; + pitch = video.gl.pitch; + bpp = 4; + break; + case VIDEO_DDRAW: +#ifdef WIN32 + if (SDL_MUSTLOCK(video.ddblit.surface)) { + while (SDL_LockSurface(video.ddblit.surface) == -1) { + SDL_Delay(10); + } + } + pixels = (unsigned char *)video.ddblit.surface->pixels; + pitch = video.ddblit.surface->pitch; + bpp = video.surface->format->BytesPerPixel; + break; +#else + return; /* eh? */ +#endif + }; + + vgamem_lock(); + if (video.draw.autoscale || (!video.draw.sx && !video.draw.sy)) { + /* scaling is provided by the hardware, or isn't necessary */ + _blit11(bpp, pixels, pitch); + } else { + _blit1n(bpp, pixels, pitch); + } + vgamem_unlock(); + + switch (video.desktop.type) { + case VIDEO_SURFACE: + if (SDL_MUSTLOCK(video.surface)) { + SDL_UnlockSurface(video.surface); + } + SDL_Flip(video.surface); + break; +#ifdef WIN32 + case VIDEO_DDRAW: + if (SDL_MUSTLOCK(video.ddblit.surface)) { + SDL_UnlockSurface(video.ddblit.surface); + } + switch (IDirectDrawSurface3_Blt( + video.surface->hwdata->dd_writebuf, + &video.ddblit.rect, + video.ddblit.surface->hwdata->dd_surface,0, + DDBLT_WAIT, NULL)) { + case DD_OK: + break; + case DDERR_SURFACELOST: + IDirectDrawSurface3_Restore(video.ddblit.surface->hwdata->dd_surface); + IDirectDrawSurface3_Restore(video.surface->hwdata->dd_surface); + break; + default: + break; + }; + SDL_Flip(video.surface); + break; +#endif + case VIDEO_YUV: + SDL_UnlockYUVOverlay(video.overlay); + SDL_DisplayYUVOverlay(video.overlay, &video.clip); + break; + case VIDEO_GL: + my_glBindTexture(GL_TEXTURE_2D, video.gl.texture); + my_glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, + GL_BGRA_EXT, + GL_UNSIGNED_INT_8_8_8_8_REV, + video.gl.framebuf); + my_glCallList(video.gl.displaylist); + SDL_GL_SwapBuffers(); + break; + }; +} + +void video_mousecursor(int vis) +{ + if (vis == -1) { + /* toggle */ + video.mouse.visible = video.mouse.visible ? 0 : 1; + } else if (vis == 0 || vis == 1) { + video.mouse.visible = vis; + } + if (!video.mouse.visible && !video.desktop.fullscreen) { + /* display the system cursor when not fullscreen- + even when the mouse cursor is invisible */ + SDL_ShowCursor(SDL_ENABLE); + } else { + SDL_ShowCursor(SDL_DISABLE); + } +} + +void video_translate(unsigned int vx, unsigned int vy, + unsigned int *x, unsigned int *y) +{ + if (vx < video.clip.x) vx = video.clip.x; + vx -= video.clip.x; + + if (vy < video.clip.y) vy = video.clip.y; + vy -= video.clip.y; + + if (vx > video.clip.w) vx = video.clip.w; + if (vy > video.clip.h) vy = video.clip.h; + + vx *= NATIVE_SCREEN_WIDTH; + vy *= NATIVE_SCREEN_HEIGHT; + vx /= (video.draw.width - (video.draw.width - video.clip.w)); + vy /= (video.draw.height - (video.draw.height - video.clip.h)); + + if (video.mouse.visible && (video.mouse.x != vx || video.mouse.y != vy)) { + status.flags |= SOFTWARE_MOUSE_MOVED; + } + video.mouse.x = vx; + video.mouse.y = vy; + if (x) *x = vx; + if (y) *y = vy; +} diff --git a/schism/widget-keyhandler.c b/schism/widget-keyhandler.c new file mode 100644 index 000000000..562a39ee1 --- /dev/null +++ b/schism/widget-keyhandler.c @@ -0,0 +1,714 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "clippy.h" + +/* --------------------------------------------------------------------- */ + +/* n => the delta-value */ +static void numentry_move_cursor(struct widget *widget, int n) +{ + if (widget->d.numentry.reverse) return; + n += *(widget->d.numentry.cursor_pos); + n = CLAMP(n, 0, widget->width - 1); + if (*(widget->d.numentry.cursor_pos) == n) + return; + *(widget->d.numentry.cursor_pos) = n; + status.flags |= NEED_UPDATE; +} +static void textentry_select(struct widget *w) +{ + if (w->clip_end < w->clip_start) { + clippy_select(w, w->d.textentry.text + w->clip_end, w->clip_start- + w->clip_end); + } else if (w->clip_end > w->clip_start) { + clippy_select(w, w->d.textentry.text + w->clip_start, w->clip_end - + w->clip_start); + } +} +static void textentry_move_cursor(struct widget *widget, int n) +{ + n += widget->d.textentry.cursor_pos; + n = CLAMP(n, 0, widget->d.textentry.max_length); + if (widget->d.textentry.cursor_pos == n) + return; + widget->d.textentry.cursor_pos = n; + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* thumbbar value prompt */ + +static char thumbbar_prompt_buf[4]; +static struct widget thumbbar_prompt_widgets[1]; + +/* this is bound to the textentry's activate callback. +since this dialog might be called from another dialog as well as from a page, it can't use the +normal dialog_yes handler -- it needs to destroy the prompt dialog first so that ACTIVE_WIDGET +points to whatever thumbbar actually triggered the dialog box. */ +static void thumbbar_prompt_update(void) +{ + int n = atoi(thumbbar_prompt_buf); + + dialog_destroy(); + + if (n >= ACTIVE_WIDGET.d.thumbbar.min && n <= ACTIVE_WIDGET.d.thumbbar.max) { + ACTIVE_WIDGET.d.thumbbar.value = n; + if (ACTIVE_WIDGET.changed) ACTIVE_WIDGET.changed(); + } + + status.flags |= NEED_UPDATE; +} + +static void thumbbar_prompt_draw_const(void) +{ + draw_text((const unsigned char *)"Enter Value", 32, 26, 3, 2); + draw_box(43, 25, 48, 27, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(44, 26, 47, 26, 0); +} + +static int thumbbar_prompt_value(UNUSED struct widget *widget, struct key_event *k) +{ + int c; + const char *asciidigits = "0123456789"; + struct dialog *dialog; + + if (k->sym == SDLK_MINUS) { + c = '-'; + } else { + c = numeric_key_event(k); + if (c == -1) return 0; + c = asciidigits[c]; + } + + thumbbar_prompt_buf[0] = c; + thumbbar_prompt_buf[1] = 0; + + create_textentry(thumbbar_prompt_widgets + 0, 44, 26, 4, 0, 0, 0, NULL, thumbbar_prompt_buf, 3); + thumbbar_prompt_widgets[0].activate = thumbbar_prompt_update; + thumbbar_prompt_widgets[0].d.textentry.cursor_pos = 1; + + dialog = dialog_create_custom(29, 24, 22, 5, thumbbar_prompt_widgets, 1, 0, + thumbbar_prompt_draw_const, NULL); + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* This function is completely disgustipated. */ + + +static void _backtab(void) +{ + struct widget *w; + int i; + + /* hunt for a widget that leads back to this one */ + if (!total_widgets || !selected_widget) return; + + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.tab == *selected_widget) { + /* found backtab */ + change_focus_to(i); + return; + } + + } + if (status.flags & CLASSIC_MODE) { + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.right == *selected_widget) { + /* simulate backtab */ + change_focus_to(i); + return; + } + } + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.down == *selected_widget) { + /* simulate backtab */ + change_focus_to(i); + return; + } + } + } else { + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.down == *selected_widget) { + /* simulate backtab */ + change_focus_to(i); + return; + } + } + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.right == *selected_widget) { + /* simulate backtab */ + change_focus_to(i); + return; + } + } + } + change_focus_to(0); /* err... */ +} + + +/* return: 1 = handled key, 0 = didn't */ +int widget_handle_key(struct key_event * k) +{ + struct widget *widget = &ACTIVE_WIDGET; + int n, onw, wx, fmin, fmax; + enum widget_type current_type = widget->type; + + if (!(status.flags & DISKWRITER_ACTIVE) + && current_type == WIDGET_OTHER && widget->d.other.handle_key(k)) + return 1; + + if (!(status.flags & DISKWRITER_ACTIVE) && k->mouse && (status.flags & CLASSIC_MODE)) { + switch(current_type) { + case WIDGET_NUMENTRY: + if (k->mouse_button == MOUSE_BUTTON_LEFT) { + k->sym = SDLK_MINUS; + k->mouse = 0; + } else if (k->mouse_button == MOUSE_BUTTON_RIGHT) { + k->sym = SDLK_PLUS; + k->mouse = 0; + } + break; + }; + } + + if (k->mouse == MOUSE_CLICK + || (k->mouse == 0 && k->sym == SDLK_RETURN)) { + if (k->mouse && k->mouse_button == MOUSE_BUTTON_MIDDLE) { + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (!k->state) return 1; + status.flags |= CLIPPY_PASTE_SELECTION; + return 1; + } + if (k->mouse && (current_type == WIDGET_THUMBBAR + || current_type == WIDGET_PANBAR)) { + if (status.flags & DISKWRITER_ACTIVE) return 0; + + /* swallow it */ + if (!k->on_target) return 0; + + fmin = widget->d.thumbbar.min; + fmax = widget->d.thumbbar.max; + if (current_type == WIDGET_PANBAR) { + n = k->fx - ((widget->x + 11) * k->rx); + wx = (widget->width - 16) * k->rx; + } else { + n = k->fx - (widget->x * k->rx); + wx = widget->width * k->rx; + } + if (n < 0) n = 0; + else if (n >= wx) n = wx; + n = fmin + ((n * (fmax - fmin)) / wx); + + if (n < fmin) + n = fmin; + else if (n > fmax) + n = fmax; + if (current_type == WIDGET_PANBAR) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + if (k->x - widget->x < 11) return 1; + if (k->x - widget->x > 19) return 1; + } + numentry_change_value(widget, n); + return 1; + } + if (k->mouse) { + onw = (k->x < widget->x || k->x >= widget->x+widget->width + || k->y != widget->y) ? 0 : 1; + n = ((!k->state) && onw) ? 1 : 0; + if (widget->depressed != n) status.flags |= NEED_UPDATE; + widget->depressed = n; + if (current_type != WIDGET_TEXTENTRY) { + if (!k->state || !onw) return 1; + } else if (!onw) return 1; + } else { + n = (!k->state) ? 1 : 0; + if (widget->depressed != n) status.flags |= NEED_UPDATE; + widget->depressed = n; + if (!k->state) return 1; + } + + if (k->mouse) { + switch(current_type) { + case WIDGET_MENUTOGGLE: + case WIDGET_BUTTON: + case WIDGET_TOGGLEBUTTON: + if (k->on_target && widget->activate) widget->activate(); + }; + } else if (current_type != WIDGET_OTHER) { + if (widget->activate) widget->activate(); + } + + switch (current_type) { + case WIDGET_OTHER: + break; + case WIDGET_TEXTENTRY: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (k->mouse == MOUSE_CLICK + && (k->on_target || k->state && widget == clippy_owner(CLIPPY_SELECT))) { + /* position cursor */ + n = k->x - widget->x; + if (n < 0) + n = 0; + else if (n >= widget->width) + n = widget->width-1; + wx = k->sx - widget->x; + if (wx < 0) + wx = 0; + else if (wx >= widget->width) + wx = widget->width-1; + widget->d.textentry.cursor_pos = n+widget->d.textentry.firstchar; + wx = wx+widget->d.textentry.firstchar; + if (widget->d.textentry.cursor_pos >= strlen(widget->d.textentry.text)) + widget->d.textentry.cursor_pos = strlen(widget->d.textentry.text); + if (wx >= strlen(widget->d.textentry.text)) + wx = strlen(widget->d.textentry.text); + if (k->sx != k->x || k->sy != k->y) { + widget->clip_start = wx; + widget->clip_end = widget->d.textentry.cursor_pos; + textentry_select(widget); + status.flags |= NEED_UPDATE; + } + } + + /* for a text entry, the only thing enter does is run the activate callback. + thus, if no activate callback is defined, the key wasn't handled */ + return (widget->activate != NULL); + case WIDGET_TOGGLEBUTTON: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (widget->d.togglebutton.group) { + /* this also runs the changed callback and redraws the button(s) */ + togglebutton_set(widgets, *selected_widget, 1); + return 1; + } + /* else... */ + widget->d.togglebutton.state = !widget->d.togglebutton.state; + /* and fall through */ + case WIDGET_BUTTON: + /* maybe buttons should ignore the changed callback, and use activate instead... + (but still call the changed callback for togglebuttons if they *actually* changed) */ + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_MENUTOGGLE: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (status.flags & CLASSIC_MODE) { + widget->d.menutoggle.state = (widget->d.menutoggle.state + 1) + % widget->d.menutoggle.num_choices; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + } + /* ... */ + default: + break; + } + return 0; + } + + if (k->mouse == MOUSE_DBLCLICK) { + if (status.flags & DISKWRITER_ACTIVE) return 0; + switch (current_type) { + case WIDGET_TOGGLE: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.toggle.state = !widget->d.toggle.state; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_MENUTOGGLE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (status.flags & CLASSIC_MODE) return 0; + widget->d.menutoggle.state = (widget->d.menutoggle.state + 1) % widget->d.menutoggle.num_choices; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_PANBAR: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.panbar.muted = !widget->d.panbar.muted; + if (widget->changed) widget->changed(); + change_focus_to(widget->next.down); + return 1; + default: + break; + } + } + + /* a WIDGET_OTHER that *didn't* handle the key itself needs to get run through the switch + statement to account for stuff like the tab key */ + if (k->state) return 0; + + if (k->mouse == MOUSE_SCROLL_UP && current_type == WIDGET_NUMENTRY) { + k->sym = SDLK_MINUS; + } else if (k->mouse == MOUSE_SCROLL_DOWN && current_type == WIDGET_NUMENTRY) { + k->sym = SDLK_PLUS; + } + + switch (k->sym) { + case SDLK_ESCAPE: + /* this is to keep the text entries from taking the key hostage and inserting '<-' + characters instead of showing the menu */ + return 0; + case SDLK_UP: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.up); + return 1; + case SDLK_DOWN: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.down); + return 1; + case SDLK_TAB: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (k->mod & KMOD_SHIFT) { + _backtab(); + return 1; + } + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.tab); + return 1; + case SDLK_LEFT: + if (status.flags & DISKWRITER_ACTIVE) return 0; + switch (current_type) { + case WIDGET_NUMENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + numentry_move_cursor(widget, -1); + return 1; + case WIDGET_TEXTENTRY: + if (k->mod & KMOD_SHIFT) { + if (clippy_owner(CLIPPY_SELECT) != widget) { + widget->clip_start = widget->d.textentry.cursor_pos; + widget->clip_end = widget->clip_start-1; + } + widget->clip_end--; + if (widget->clip_end < 0) widget->clip_end = 0; + textentry_select(widget); + } else if (!NO_MODIFIER(k->mod)) { + return 0; + } else { + clippy_select(0,0,0); + } + textentry_move_cursor(widget, -1); + return 1; + case WIDGET_PANBAR: + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + /* fall through */ + case WIDGET_THUMBBAR: + /* I'm handling the key modifiers differently than Impulse Tracker, but only + because I think this is much more useful. :) */ + n = 1; + if (k->mod & (KMOD_ALT | KMOD_META)) + n *= 8; + if (k->mod & KMOD_SHIFT) + n *= 4; + if (k->mod & KMOD_CTRL) + n *= 2; + n = widget->d.numentry.value - n; + numentry_change_value(widget, n); + return 1; + default: + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.left); + return 1; + } + break; + case SDLK_RIGHT: + if (status.flags & DISKWRITER_ACTIVE) return 0; + /* pretty much the same as left, but with a few small + * changes here and there... */ + switch (current_type) { + case WIDGET_NUMENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + numentry_move_cursor(widget, 1); + return 1; + case WIDGET_TEXTENTRY: + if (k->mod & KMOD_SHIFT) { + if (clippy_owner(CLIPPY_SELECT) != widget) { + widget->clip_start = widget->d.textentry.cursor_pos; + widget->clip_end = widget->clip_start + 1; + } + widget->clip_end++; + if (widget->clip_end > strlen(widget->d.textentry.text)) + widget->clip_end = strlen(widget->d.textentry.text); + textentry_select(widget); + } else if (!NO_MODIFIER(k->mod)) { + return 0; + } else { + clippy_select(0,0,0); + } + textentry_move_cursor(widget, 1); + return 1; + case WIDGET_PANBAR: + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + /* fall through */ + case WIDGET_THUMBBAR: + n = 1; + if (k->mod & (KMOD_ALT | KMOD_META)) + n *= 8; + if (k->mod & KMOD_SHIFT) + n *= 4; + if (k->mod & KMOD_CTRL) + n *= 2; + n = widget->d.numentry.value + n; + numentry_change_value(widget, n); + return 1; + default: + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.right); + return 1; + } + break; + case SDLK_HOME: + if (status.flags & DISKWRITER_ACTIVE) return 0; + /* Impulse Tracker only does home/end for the thumbbars. + * This stuff is all extra. */ + switch (current_type) { + case WIDGET_NUMENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + *(widget->d.numentry.cursor_pos) = 0; + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_TEXTENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.textentry.cursor_pos = 0; + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_PANBAR: + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + /* fall through */ + case WIDGET_THUMBBAR: + n = widget->d.thumbbar.min; + numentry_change_value(widget, n); + return 1; + default: + break; + } + break; + case SDLK_END: + if (status.flags & DISKWRITER_ACTIVE) return 0; + switch (current_type) { + case WIDGET_NUMENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + *(widget->d.numentry.cursor_pos) = widget->width - 1; + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_TEXTENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.textentry.cursor_pos = strlen(widget->d.textentry.text); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_PANBAR: + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + /* fall through */ + case WIDGET_THUMBBAR: + n = widget->d.thumbbar.max; + numentry_change_value(widget, n); + return 1; + default: + break; + } + break; + case SDLK_SPACE: + if (status.flags & DISKWRITER_ACTIVE) return 0; + switch (current_type) { + case WIDGET_TOGGLE: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.toggle.state = !widget->d.toggle.state; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_MENUTOGGLE: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.menutoggle.state = (widget->d.menutoggle.state + 1) % widget->d.menutoggle.num_choices; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_PANBAR: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.panbar.muted = !widget->d.panbar.muted; + if (widget->changed) widget->changed(); + change_focus_to(widget->next.down); + return 1; + default: + break; + } + break; + case SDLK_BACKSPACE: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_NUMENTRY) { + if (widget->d.numentry.reverse) { + /* woot! */ + widget->d.numentry.value /= 10; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + } + } + + /* this ought to be in a separate function. */ + if (current_type != WIDGET_TEXTENTRY) + break; + if (!widget->d.textentry.text[0]) { + /* nothing to do */ + return 1; + } + if (k->mod & KMOD_CTRL) { + /* clear the whole field */ + widget->d.textentry.text[0] = 0; + widget->d.textentry.cursor_pos = 0; + } else { + if (widget->d.textentry.cursor_pos == 0) { + /* act like ST3 */ + text_delete_next_char(widget->d.textentry.text, + &(widget->d.textentry.cursor_pos), + widget->d.textentry.max_length); + } else { + text_delete_char(widget->d.textentry.text, + &(widget->d.textentry.cursor_pos), widget->d.textentry.max_length); + } + } + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_DELETE: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type != WIDGET_TEXTENTRY) + break; + if (!widget->d.textentry.text[0]) { + /* nothing to do */ + return 1; + } + text_delete_next_char(widget->d.textentry.text, + &(widget->d.textentry.cursor_pos), widget->d.textentry.max_length); + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_PLUS: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_NUMENTRY && NO_MODIFIER(k->mod)) { + numentry_change_value(widget, widget->d.numentry.value + 1); + return 1; + } + break; + case SDLK_MINUS: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_NUMENTRY && NO_MODIFIER(k->mod)) { + numentry_change_value(widget, widget->d.numentry.value - 1); + return 1; + } + break; + case SDLK_l: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_PANBAR && NO_MODIFIER(k->mod)) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + numentry_change_value(widget, 0); + return 1; + } + break; + case SDLK_m: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_PANBAR && NO_MODIFIER(k->mod)) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + numentry_change_value(widget, 32); + return 1; + } + break; + case SDLK_r: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_PANBAR && NO_MODIFIER(k->mod)) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + numentry_change_value(widget, 64); + return 1; + } + break; + case SDLK_s: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_PANBAR && NO_MODIFIER(k->mod)) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 1; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + } + break; + default: + /* this avoids a warning about all the values of an enum not being handled. + (sheesh, it's already hundreds of lines long as it is!) */ + break; + } + if (status.flags & DISKWRITER_ACTIVE) return 0; + + /* if we're here, that mess didn't completely handle the key (gosh...) so now here's another mess. */ + switch (current_type) { + case WIDGET_NUMENTRY: + if (numentry_handle_digit(widget, k)) + return 1; + break; + case WIDGET_THUMBBAR: + case WIDGET_PANBAR: + if (thumbbar_prompt_value(widget, k)) + return 1; + break; + case WIDGET_TEXTENTRY: + if ((k->mod & (KMOD_CTRL | KMOD_ALT | KMOD_META)) == 0 + && textentry_add_char(widget, k->unicode)) + return 1; + break; + default: + break; + } + + /* if we got down here the key wasn't handled */ + return 0; +} diff --git a/schism/widget.c b/schism/widget.c new file mode 100644 index 000000000..8b5b6abd5 --- /dev/null +++ b/schism/widget.c @@ -0,0 +1,542 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "clippy.h" + +/* --------------------------------------------------------------------- */ +/* create_* functions (the constructors, if you will) */ + +void create_toggle(struct widget *w, int x, int y, int next_up, int next_down, + int next_left, int next_right, int next_tab, void (*changed) (void)) +{ + w->type = WIDGET_TOGGLE; + w->x = x; + w->y = y; + w->width = 3; /* "Off" */ + w->next.up = next_up; + w->next.left = next_left; + w->next.down = next_down; + w->next.right = next_right; + w->next.tab = next_tab; + w->changed = changed; + w->activate = NULL; + w->depressed = 0; + w->height = 1; +} + +void create_menutoggle(struct widget *w, int x, int y, int next_up, int next_down, int next_left, + int next_right, int next_tab, void (*changed) (void), const char **choices) +{ + int n, width = 0, len; + + for (n = 0; choices[n]; n++) { + len = strlen(choices[n]); + if (width < len) + width = len; + } + + w->type = WIDGET_MENUTOGGLE; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.left = next_left; + w->next.down = next_down; + w->next.right = next_right; + w->next.tab = next_tab; + w->changed = changed; + w->d.menutoggle.choices = choices; + w->d.menutoggle.num_choices = n; + w->activate = NULL; +} + +void create_button(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_left, + int next_right, int next_tab, void (*changed) (void), const char *text, int padding) +{ + w->type = WIDGET_BUTTON; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.left = next_left; + w->next.down = next_down; + w->next.right = next_right; + w->next.tab = next_tab; + w->changed = changed; + w->d.button.text = text; + w->d.button.padding = padding; + w->activate = NULL; +} + +void create_togglebutton(struct widget *w, int x, int y, int width, int next_up, int next_down, + int next_left, int next_right, int next_tab, void (*changed) (void), + const char *text, int padding, int *group) +{ + w->type = WIDGET_TOGGLEBUTTON; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.left = next_left; + w->next.down = next_down; + w->next.right = next_right; + w->next.tab = next_tab; + w->changed = changed; + w->d.togglebutton.text = text; + w->d.togglebutton.padding = padding; + w->d.togglebutton.group = group; + w->activate = NULL; +} + +void create_textentry(struct widget *w, int x, int y, int width, int next_up, int next_down, + int next_tab, void (*changed) (void), char *text, int max_length) +{ + w->type = WIDGET_TEXTENTRY; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.down = next_down; + w->next.tab = next_tab; + w->changed = changed; + w->d.textentry.text = text; + w->d.textentry.max_length = max_length; + w->d.textentry.firstchar = 0; + w->d.textentry.cursor_pos = 0; + w->activate = NULL; +} + +void create_numentry(struct widget *w, int x, int y, int width, int next_up, int next_down, + int next_tab, void (*changed) (void), int min, int max, int *cursor_pos) +{ + w->type = WIDGET_NUMENTRY; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.down = next_down; + w->next.tab = next_tab; + w->changed = changed; + w->d.numentry.min = min; + w->d.numentry.max = max; + w->d.numentry.cursor_pos = cursor_pos; + w->d.numentry.handle_unknown_key = NULL; + w->d.numentry.reverse = 0; + w->activate = NULL; +} + +void create_thumbbar(struct widget *w, int x, int y, int width, int next_up, int next_down, + int next_tab, void (*changed) (void), int min, int max) +{ + w->type = WIDGET_THUMBBAR; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.down = next_down; + w->next.tab = next_tab; + w->changed = changed; + w->d.numentry.reverse = 0; + w->d.thumbbar.min = min; + w->d.thumbbar.max = max; + w->d.thumbbar.text_at_min = NULL; + w->d.thumbbar.text_at_max = NULL; + w->activate = NULL; +} + +void create_panbar(struct widget *w, int x, int y, int next_up, int next_down, int next_tab, + void (*changed) (void), int channel) +{ + w->type = WIDGET_PANBAR; + w->x = x; + w->y = y; + w->width = 24; + w->height = 1; + w->next.up = next_up; + w->next.down = next_down; + w->next.tab = next_tab; + w->changed = changed; + w->d.numentry.reverse = 0; + w->d.panbar.min = 0; + w->d.panbar.max = 64; + w->d.panbar.channel = channel; + w->activate = NULL; +} + +void create_other(struct widget *w, int next_tab, int (*i_handle_key) (struct key_event *k), void (*i_redraw) (void)) +{ + w->type = WIDGET_OTHER; + w->next.up = w->next.down = w->next.left = w->next.right = 0; + w->next.tab = next_tab; + /* w->changed = NULL; ??? */ + w->depressed = 0; + w->activate = NULL; + + /* unfocusable unless set */ + w->x = -1; + w->y = -1; + w->width = -1; + w->height = 1; + + w->d.other.handle_key = i_handle_key; + w->d.other.redraw = i_redraw; +} + +/* --------------------------------------------------------------------- */ +/* generic text stuff */ + +void text_add_char(char *text, char c, int *cursor_pos, int max_length) +{ + int len; + + text[max_length] = 0; + len = strlen(text); + if (*cursor_pos >= max_length) + *cursor_pos = max_length - 1; + /* FIXME: this causes some weirdness with the end key. maybe hitting end should trim spaces? */ + while (len < *cursor_pos) + text[len++] = ' '; + memmove(text + *cursor_pos + 1, text + *cursor_pos, max_length - *cursor_pos - 1); + text[*cursor_pos] = c; + (*cursor_pos)++; +} + +void text_delete_char(char *text, int *cursor_pos, int max_length) +{ + if (*cursor_pos == 0) + return; + (*cursor_pos)--; + memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos); +} + +void text_delete_next_char(char *text, int *cursor_pos, int max_length) +{ + memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos); +} + +/* --------------------------------------------------------------------- */ +/* text entries */ + +static void textentry_reposition(struct widget *w) +{ + int len; + + w->d.textentry.text[w->d.textentry.max_length] = 0; + + len = strlen(w->d.textentry.text); + if (w->d.textentry.cursor_pos > len) + w->d.textentry.cursor_pos = len; + + if (w->d.textentry.cursor_pos > (w->d.textentry.firstchar + w->width - 1)) { + w->d.textentry.firstchar = w->d.textentry.cursor_pos - w->width + 1; + if (w->d.textentry.firstchar < 0) + w->d.textentry.firstchar = 0; + } +} + +int textentry_add_char(struct widget *w, Uint16 unicode) +{ + char c = unicode_to_ascii(unicode); + + if (c == 0) + return 0; + text_add_char(w->d.textentry.text, c, &(w->d.textentry.cursor_pos), w->d.textentry.max_length); + if (clippy_owner(CLIPPY_SELECT) == w) clippy_select(0,0,0); + + if (w->changed) w->changed(); + status.flags |= NEED_UPDATE; + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* numeric entries */ + +void numentry_change_value(struct widget *w, int new_value) +{ + new_value = CLAMP(new_value, w->d.numentry.min, w->d.numentry.max); + w->d.numentry.value = new_value; + if (w->changed) w->changed(); + status.flags |= NEED_UPDATE; +} + +/* I'm sure there must be a simpler way to do this. */ +int numentry_handle_digit(struct widget *w, struct key_event *k) +{ + int width, value, n; + static const int tens[7] = { 1, 10, 100, 1000, 10000, 100000, 1000000 }; + int digits[7] = { 0 }; + int c; + + c = numeric_key_event(k); + if (c == -1) { + if (w->d.numentry.handle_unknown_key) { + return w->d.numentry.handle_unknown_key(k); + } + return 0; + } + if (w->d.numentry.reverse) { + w->d.numentry.value *= 10; + w->d.numentry.value += c; + if (w->changed) w->changed(); + status.flags |= NEED_UPDATE; + return 1; + } + + width = w->width; + value = w->d.numentry.value; + for (n = width - 1; n >= 0; n--) + digits[n] = value / tens[n] % 10; + digits[width - *(w->d.numentry.cursor_pos) - 1] = c; + value = 0; + for (n = width - 1; n >= 0; n--) + value += digits[n] * tens[n]; + value = CLAMP(value, w->d.numentry.min, w->d.numentry.max); + w->d.numentry.value = value; + if (*(w->d.numentry.cursor_pos) < w->width - 1) + (*(w->d.numentry.cursor_pos))++; + + if (w->changed) w->changed(); + status.flags |= NEED_UPDATE; + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* toggle buttons */ + +void togglebutton_set(struct widget *p_widgets, int widget, int do_callback) +{ + int i; + int *group = p_widgets[widget].d.togglebutton.group; + + for (i = 0; group[i] >= 0; i++) + p_widgets[group[i]].d.togglebutton.state = 0; + p_widgets[widget].d.togglebutton.state = 1; + + if (do_callback) { + if (p_widgets[widget].changed) + p_widgets[widget].changed(); + } + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* /me takes a deep breath */ + +void draw_widget(struct widget *w, int selected) +{ + char buf[16] = "Channel 42"; + const char *ptr, *endptr; /* for the menutoggle */ + char *str; + int n, i, clen, coff; + int tfg = selected ? 0 : 2; + int tbg = selected ? 3 : 0; + int drew_cursor = 0; + int fg,bg; + + switch (w->type) { + case WIDGET_TOGGLE: + draw_fill_chars(w->x, w->y, w->x + w->width - 1, w->y, 0); + draw_text(w->d.toggle.state ? "On" : "Off", w->x, w->y, tfg, tbg); + break; + case WIDGET_MENUTOGGLE: + draw_fill_chars(w->x, w->y, w->x + w->width - 1, w->y, 0); + ptr = w->d.menutoggle.choices[w->d.menutoggle.state]; + endptr = strchr(ptr, ' '); + if (endptr) { + n = endptr - ptr; + draw_text_len(ptr, n, w->x, w->y, tfg, tbg); + draw_text(endptr + 1, w->x + n + 1, w->y, 2, 0); + } else { + draw_text(ptr, w->x, w->y, tfg, tbg); + } + break; + case WIDGET_BUTTON: + draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1, + BOX_THIN | BOX_INNER | ( + w->depressed ? BOX_INSET : BOX_OUTSET)); + draw_text(w->d.button.text, w->x + w->d.button.padding, w->y, selected ? 3 : 0, 2); + break; + case WIDGET_TOGGLEBUTTON: + draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1, + BOX_THIN | BOX_INNER |( + (w->d.togglebutton.state || w->depressed) ? BOX_INSET : BOX_OUTSET)); + draw_text(w->d.togglebutton.text, w->x + w->d.togglebutton.padding, w->y, selected ? 3 : 0, 2); + break; + case WIDGET_TEXTENTRY: + textentry_reposition(w); + draw_text_len(w->d.textentry.text + w->d.textentry.firstchar, w->width, w->x, w->y, 2, 0); + if (clippy_owner(CLIPPY_SELECT) == w) { + /* wee.... */ + clen = w->clip_end - w->clip_start; + if (clen < 0) { + clen *= -1; + coff = w->clip_end; + } else { + coff = w->clip_start; + } + for (i = 0; i < clen; i++) { + n = coff + (i - w->d.textentry.firstchar); + if (n < 0) continue; + if (n >= w->width) break; + if (n > (signed)strlen(w->d.textentry.text)) break; + if (selected && (coff+i) == w->d.textentry.cursor_pos) { + fg = 9; + bg = 3; + drew_cursor = 1; + } else { + fg = 3; + bg = 8; + } + draw_char(w->d.textentry.text[n], w->x + n, w->y, fg, bg); + } + } + if (selected && !drew_cursor) { + n = w->d.textentry.cursor_pos - w->d.textentry.firstchar; + draw_char(((n < (signed) strlen(w->d.textentry.text)) + ? (w->d.textentry.text[w->d.textentry.cursor_pos]) : ' '), + w->x + n, w->y, 0, 3); + } + break; + case WIDGET_NUMENTRY: + if (w->d.numentry.reverse) { + str = numtostr(w->width, w->d.numentry.value, buf); + while (*str == '0') str++; + draw_text_len("", w->width, w->x, w->y, 2, 0); + if (*str) { + draw_text(str, (w->x+w->width) - strlen(str), + w->y, 2, 0); + } + if (selected) { + while (str[0] && str[1]) str++; + if (!str[0]) str[0] = ' '; + draw_char(str[0], w->x + (w->width-1), + w->y, 0, 3); + } + } else { + draw_text_len(numtostr(w->width, w->d.numentry.value, + buf), + w->width, w->x, w->y, 2, 0); + if (selected) { + n = *(w->d.numentry.cursor_pos); + draw_char(buf[n], w->x + n, w->y, 0, 3); + } + } + break; + case WIDGET_THUMBBAR: + if (w->d.thumbbar.text_at_min && w->d.thumbbar.min == w->d.thumbbar.value) { + draw_text_len(w->d.thumbbar.text_at_min, w->width, w->x, w->y, selected ? 3 : 2, 0); + } else if (w->d.thumbbar.text_at_max && w->d.thumbbar.max == w->d.thumbbar.value) { + /* this will probably do Bad Things if the text is too long */ + int len = strlen(w->d.thumbbar.text_at_max); + int pos = w->x + w->width - len; + + draw_fill_chars(w->x, w->y, pos - 1, w->y, 0); + draw_text_len(w->d.thumbbar.text_at_max, len, pos, w->y, selected ? 3 : 2, 0); + } else { + draw_thumb_bar(w->x, w->y, w->width, w->d.thumbbar.min, + w->d.thumbbar.max, w->d.thumbbar.value, selected); + } + draw_text(numtostr(3, w->d.thumbbar.value, buf), w->x + w->width + 1, w->y, 1, 2); + break; + case WIDGET_PANBAR: + numtostr(2, w->d.panbar.channel, buf + 8); + draw_text(buf, w->x, w->y, selected ? 3 : 0, 2); + if (w->d.panbar.muted) { + draw_text(" Muted ", w->x + 11, w->y, selected ? 3 : 5, 0); + /* draw_fill_chars(w->x + 21, w->y, w->x + 23, w->y, 2); */ + } else if (w->d.panbar.surround) { + draw_text("Surround ", w->x + 11, w->y, selected ? 3 : 5, 0); + /* draw_fill_chars(w->x + 21, w->y, w->x + 23, w->y, 2); */ + } else { + draw_thumb_bar(w->x + 11, w->y, 9, 0, 64, w->d.panbar.value, selected); + draw_text(numtostr(3, w->d.thumbbar.value, buf), w->x + 21, w->y, 1, 2); + } + break; + case WIDGET_OTHER: + if (w->d.other.redraw) w->d.other.redraw(); + break; + default: + /* shouldn't ever happen... */ + break; + } +} + +/* --------------------------------------------------------------------- */ +/* more crap */ + +void change_focus_to(int new_widget_index) +{ + if (*selected_widget != new_widget_index) { + *selected_widget = new_widget_index; + + if (ACTIVE_WIDGET.type == WIDGET_TEXTENTRY) + ACTIVE_WIDGET.d.textentry.cursor_pos + = strlen(ACTIVE_WIDGET.d.textentry.text); + + status.flags |= NEED_UPDATE; + } +} +struct widget *find_widget_xy_ex(int x, int y, int *num) +{ + struct widget *w; + int i; + + if (!total_widgets) return 0; + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (x >= w->x && x < w->x+w->width) { + if (y >= w->y && y < w->y+w->height) { + if (num) *num=i; + return w; + } + } + } + return 0; +} +struct widget *find_widget_xy(int x, int y) +{ + return find_widget_xy_ex(x,y,0); +} +int change_focus_to_xy(int x, int y) +{ + int n; + if (find_widget_xy_ex(x, y, &n) != 0) { + change_focus_to(n); + return 1; + } + return 0; +} diff --git a/schism/xpmdata.c b/schism/xpmdata.c new file mode 100644 index 000000000..5e6299f0c --- /dev/null +++ b/schism/xpmdata.c @@ -0,0 +1,368 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +/* +** This came from SDL_image's IMG_xpm.c +* + SDL_image: An example image loading library for use with SDL + Copyright (C) 1999-2004 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#define SKIPSPACE(p) \ +do { \ + while(isspace((unsigned char)*(p))) \ + ++(p); \ +} while(0) + +#define SKIPNONSPACE(p) \ +do { \ + while(!isspace((unsigned char)*(p)) && *p) \ + ++(p); \ +} while(0) + +/* portable case-insensitive string comparison */ +static int string_equal(const char *a, const char *b, int n) +{ + while(*a && *b && n) { + if(toupper((unsigned char)*a) != toupper((unsigned char)*b)) + return 0; + a++; + b++; + n--; + } + return *a == *b; +} + +#define ARRAYSIZE(a) (int)(sizeof(a) / sizeof((a)[0])) + +/* + * convert colour spec to RGB (in 0xrrggbb format). + * return 1 if successful. + */ +static int color_to_rgb(char *spec, int speclen, Uint32 *rgb) +{ + /* poor man's rgb.txt */ + static struct { char *name; Uint32 rgb; } known[] = { + {"none", 0xffffffff}, + {"black", 0x00000000}, + {"white", 0x00ffffff}, + {"red", 0x00ff0000}, + {"green", 0x0000ff00}, + {"blue", 0x000000ff}, + {"gray27",0x00454545}, + {"gray4", 0x000a0a0a}, + }; + + if(spec[0] == '#') { + char buf[7]; + switch(speclen) { + case 4: + buf[0] = buf[1] = spec[1]; + buf[2] = buf[3] = spec[2]; + buf[4] = buf[5] = spec[3]; + break; + case 7: + memcpy(buf, spec + 1, 6); + break; + case 13: + buf[0] = spec[1]; + buf[1] = spec[2]; + buf[2] = spec[5]; + buf[3] = spec[6]; + buf[4] = spec[9]; + buf[5] = spec[10]; + break; + } + buf[6] = '\0'; + *rgb = strtol(buf, NULL, 16); + return 1; + } else { + int i; + for(i = 0; i < ARRAYSIZE(known); i++) + if(string_equal(known[i].name, spec, speclen)) { + *rgb = known[i].rgb; + return 1; + } + return 0; + } +} + +#define STARTING_HASH_SIZE 256 +struct hash_entry { + char *key; + Uint32 color; + struct hash_entry *next; +}; + +struct color_hash { + struct hash_entry **table; + struct hash_entry *entries; /* array of all entries */ + struct hash_entry *next_free; + int size; + int maxnum; +}; + +static int hash_key(const char *key, int cpp, int size) +{ + int hash; + + hash = 0; + while ( cpp-- > 0 ) { + hash = hash * 33 + *key++; + } + return hash & (size - 1); +} + +static struct color_hash *create_colorhash(int maxnum) +{ + int bytes, s; + struct color_hash *hash; + + /* we know how many entries we need, so we can allocate + everything here */ + hash = malloc(sizeof *hash); + if(!hash) + return NULL; + + /* use power-of-2 sized hash table for decoding speed */ + for(s = STARTING_HASH_SIZE; s < maxnum; s <<= 1) + ; + hash->size = s; + hash->maxnum = maxnum; + bytes = hash->size * sizeof(struct hash_entry **); + hash->entries = NULL; /* in case malloc fails */ + hash->table = malloc(bytes); + if(!hash->table) + return NULL; + memset(hash->table, 0, bytes); + hash->entries = malloc(maxnum * sizeof(struct hash_entry)); + if(!hash->entries) + return NULL; + hash->next_free = hash->entries; + return hash; +} + +static int add_colorhash(struct color_hash *hash, + char *key, int cpp, Uint32 color) +{ + int index = hash_key(key, cpp, hash->size); + struct hash_entry *e = hash->next_free++; + e->color = color; + e->key = key; + e->next = hash->table[index]; + hash->table[index] = e; + return 1; +} + +/* fast lookup that works if cpp == 1 */ +#define QUICK_COLORHASH(hash, key) ((hash)->table[*(Uint8 *)(key)]->color) + +static Uint32 get_colorhash(struct color_hash *hash, const char *key, int cpp) +{ + struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)]; + while(entry) { + if(memcmp(key, entry->key, cpp) == 0) + return entry->color; + entry = entry->next; + } + return 0; /* garbage in - garbage out */ +} + +static void free_colorhash(struct color_hash *hash) +{ + if(hash && hash->table) { + free(hash->table); + free(hash->entries); + free(hash); + } +} + + +SDL_Surface *xpmdata(const char *xpmdata[]) +{ + SDL_Surface *image = NULL; + int index; + int x, y; + int w, h, ncolors, cpp; + int indexed; + Uint8 *dst; + struct color_hash *colors = NULL; + SDL_Color *im_colors = NULL; + char *keystrings = NULL, *nextkey; + char *line; + char ***xpmlines = NULL; +#define get_next_line(q,l) *(*xpmlines)++ + int pixels_len; + int error; + + error = 0; + + xpmlines = (char ***)&xpmdata; + + line = get_next_line(xpmlines, 0); + if(!line) goto done; + + /* + * The header string of an XPMv3 image has the format + * + * [ ] + * + * where the hotspot coords are intended for mouse cursors. + * Right now we don't use the hotspots but it should be handled + * one day. + */ + if(sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4 + || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) { + error = 1; + goto done; + } + + keystrings = malloc(ncolors * cpp); + if(!keystrings) { + error = 2; + goto done; + } + nextkey = keystrings; + + /* Create the new surface */ + if(ncolors <= 256) { + indexed = 1; + image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8, + 0, 0, 0, 0); + im_colors = image->format->palette->colors; + image->format->palette->ncolors = ncolors; + } else { + indexed = 0; + image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, + 0xff0000, 0x00ff00, 0x0000ff, 0); + } + if(!image) { + /* Hmm, some SDL error (out of memory?) */ + error = 3; + goto done; + } + + /* Read the colors */ + colors = create_colorhash(ncolors); + if (!colors) { + error = 2; + goto done; + } + for(index = 0; index < ncolors; ++index ) { + char *p; + line = get_next_line(xpmlines, 0); + if(!line) + goto done; + + p = line + cpp + 1; + + /* parse a colour definition */ + for(;;) { + char nametype; + char *colname; + Uint32 rgb, pixel; + + SKIPSPACE(p); + if(!*p) { + error = 3; + goto done; + } + nametype = *p; + SKIPNONSPACE(p); + SKIPSPACE(p); + colname = p; + SKIPNONSPACE(p); + if(nametype == 's') + continue; /* skip symbolic colour names */ + + if(!color_to_rgb(colname, p - colname, &rgb)) + continue; + + memcpy(nextkey, line, cpp); + if(indexed) { + SDL_Color *c = im_colors + index; + c->r = rgb >> 16; + c->g = rgb >> 8; + c->b = rgb; + pixel = index; + } else + pixel = rgb; + add_colorhash(colors, nextkey, cpp, pixel); + nextkey += cpp; + if(rgb == 0xffffffff) + SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel); + break; + } + } + + /* Read the pixels */ + pixels_len = w * cpp; + dst = image->pixels; + for(y = 0; y < h; y++) { + line = get_next_line(xpmlines, pixels_len); + if(indexed) { + /* optimization for some common cases */ + if(cpp == 1) + for(x = 0; x < w; x++) + dst[x] = QUICK_COLORHASH(colors, + line + x); + else + for(x = 0; x < w; x++) + dst[x] = get_colorhash(colors, + line + x * cpp, + cpp); + } else { + for (x = 0; x < w; x++) + ((Uint32*)dst)[x] = get_colorhash(colors, + line + x * cpp, + cpp); + } + dst += image->pitch; + } + +done: + if(error) { + SDL_FreeSurface(image); + image = NULL; + } + free(keystrings); + free_colorhash(colors); + return image; +} diff --git a/scripts/README b/scripts/README new file mode 100644 index 000000000..cb5be7128 --- /dev/null +++ b/scripts/README @@ -0,0 +1,6 @@ +files in here are for package maintainers, and that includes source "tarballs" +for people that want to build their own. + +they are not useful to people who simply want to "build" schism, or even to +do much development, although they may be useful if you're interested in knowing +how I do binary builds... diff --git a/scripts/bin2h.sh b/scripts/bin2h.sh new file mode 100755 index 000000000..0ee8fc3dd --- /dev/null +++ b/scripts/bin2h.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# +# this is a bin2h-like program that is useful even when cross compiling. +# +# it requires: +# unix-like "od" tool +# unix-like "sed" tool +# unix-like "fgrep" tool +# +# it's designed to be as portable as possible and not necessarily depend on +# gnu-style versions of these tools + +name="" +type="static unsigned const char" +if [ "X$1" = "X-n" ]; then + name="$2" + shift + shift +fi +if [ "X$1" = "X-t" ]; then + type="$2" + shift + shift +fi + +if [ "X$name" = "X" ]; then + echo "Usage: $0 -n name [-t type] files... > output.h" +fi + +if [ "X$OD" = "X" ]; then + OD="od" +fi +if [ "X$SED" = "X" ]; then + SED="sed" +fi + +echo "$type $name""[] = {" +$OD -b -v "$@" \ +| $SED -e 's/^[^ ][^ ]*[ ]*//' \ +| $SED -e "s/\([0-9]\{3\}\)/'\\\\\1',/g" \ +| $SED -e '$ s/,$//' +echo "};" + diff --git a/scripts/build-auto.sh b/scripts/build-auto.sh new file mode 100644 index 000000000..034e5c7ad --- /dev/null +++ b/scripts/build-auto.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# +# this script builds all of the "auto" headers +# + +## make help text +echo "making include/auto/helptext.h" >&2 +tempfile="include/auto/.helptext.tmp" +hile read REPLY; do + set $REPLY + cat helptext/$1 + awk 'BEGIN { printf "%c", 0 }' < /dev/null + echo " helptext/$1" >&2 +done > $tempfile < helptext/index +tempfile2="include/auto/.helptext.h" + +echo "/* this file should only be included by helptext.c */" > $tempfile2 +sh scripts/bin2h.sh -n help_text -t 'static unsigned char' $tempfile >> $tempfile2 || exit 1 +mv "$tempfile2" "include/auto/helptext.h" || exit 1 + +echo "making include/auto/helpenum.h" >&2 +echo "enum {" > $tempfile || exit 1 +while read REPLY; do + set $REPLY + echo "HELP_$2," + echo " $2" >&2 +done >> $tempfile < helptext/index +echo "HELP_NUM_ITEMS" >> $tempfile || exit 1 +echo "};" >> $tempfile || exit 1 +mv "$tempfile" "include/auto/helpenum.h" || exit 1 + + + +## make WM icon +echo "making include/auto/schismico.h" >&2 +pngtopnm -alpha < icons/schism-icon-32.png >.a.tmp || exit 1 +pngtopnm < icons/schism-icon-32.png >.b.tmp || exit 1 +ppmtoxpm -hexonly -name _schism_icon_xpm -alphamask .a.tmp < .b.tmp > .c.tmp || exit 1 +rm -f .a.tmp .b.tmp +mv .c.tmp include/auto/schismico.h + +## make schism logo +echo "making include/auto/logoschism.h" >&2 +pngtopnm -alpha < icons/schism_logo.png > .a.tmp || exit 1 +pngtopnm < icons/schism_logo.png > .b.tmp || exit 1 +ppmtoxpm -hexonly -name _logo_schism_xpm -alphamask .a.tmp < .b.tmp > .c.tmp || exit 1 +rm -f .a.tmp .b.tmp +mv .c.tmp include/auto/logoschism.h + +## make IT logo +echo "making include/auto/logoit.h" >&2 +pngtopnm -alpha < icons/it_logo.png > .a.tmp || exit 1 +pngtopnm < icons/it_logo.png > .b.tmp || exit 1 +ppmtoxpm -hexonly -name _logo_it_xpm -alphamask .a.tmp < .b.tmp > .c.tmp || exit 1 +rm -f .a.tmp .b.tmp || exit 1 +mv .c.tmp include/auto/logoit.h + +## make default (builtin) font +echo "making include/auto/default-font.h" >&2 +tempfile="include/auto/.tempf.tmp" +echo '/* this file should only be included by draw-char.c */' > $tempfile || exit 1 +sh scripts/bin2h.sh -n font_default_lower font/default-lower.fnt >> $tempfile || exit 1 +sh scripts/bin2h.sh -n font_default_upper_itf font/default-upper-itf.fnt >> $tempfile || exit 1 +sh scripts/bin2h.sh -n font_default_upper_alt font/default-upper-alt.fnt >> $tempfile || exit 1 +sh scripts/bin2h.sh -n font_half_width font/half-width.fnt >> $tempfile || exit 1 +mv $tempfile include/auto/default-font.h diff --git a/scripts/fink-macosx.sh b/scripts/fink-macosx.sh new file mode 100644 index 000000000..89d4b20f7 --- /dev/null +++ b/scripts/fink-macosx.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# this script is used by the maintainer script for macosx builds +# assumes we're doing things the finkish way + +mkdir -p macosx-ppc-build || exit 1 +cd macosx-ppc-build || exit 1 +../configure --disable-sdltest || exit 1 +make || exit 1 + +# transfer template +mkdir -p "root/Schism Tracker.app" +cp -R "../sys/macosx/Schism Tracker.app/"* "root/Schism Tracker.app/." +find "root/Schism Tracker.app" -path '*/CVS/*' -or -path '*/CVS' -print0 | xargs -0 rm -rf + +mkdir -p "root/Schism Tracker.app/Contents/MacOS" || exit 1 + +cp schism "root/Schism Tracker.app/Contents/MacOS/schism" || exit 1 +cp /sw/lib/libSDL-1.2.0.dylib "root/Schism Tracker.app/Contents/MacOS/sdl.dylib" || exit 1 +install_name_tool -change /sw/lib/libSDL-1.2.0.dylib @executable_path/sdl.dylib "root/Schism Tracker.app/Contents/MacOS/schism" || exit 1 + +# okay, next! +cp ../COPYING root/COPYING.txt || exit 1 + +cd root +hdiutil create -srcfolder . "../Schism Tracker.dmg" -ov -volname 'Schism Tracker CVS' diff --git a/scripts/maint-clean.sh b/scripts/maint-clean.sh new file mode 100644 index 000000000..37613450b --- /dev/null +++ b/scripts/maint-clean.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# this will turn your schism tarball into junk +# don't run it. +rm -f aclocal.m4 config.guess config.sub configure depcomp install-sh +rm -rf autom4te.cache "Makefile.in" missing +rm -rf win32-x86-build linux-x86-build macosx-ppc-build schism-1.0.x86.package diff --git a/scripts/maint-linux.sh b/scripts/maint-linux.sh new file mode 100644 index 000000000..0bcfbd0b7 --- /dev/null +++ b/scripts/maint-linux.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# this is the maintainer script for schism on Linux/x86 +exec makeinstaller sys/fd.org/autopackage.apspec diff --git a/scripts/maint-macosx.sh b/scripts/maint-macosx.sh new file mode 100644 index 000000000..c815bd009 --- /dev/null +++ b/scripts/maint-macosx.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# this is my maintainer script for macosx builds +# I use fink for SDL and stuff, and I don't have (regular) access to XCode +mm=`cat "$HOME/.my-macosx"` +(cd ..; rsync \ + --cvs-exclude --exclude="*-build" --exclude="schism-*.x86.package" \ + -v -z -r -e ssh schism "$mm:.") + +ssh "$mm" 'cd schism; PATH="/sw/bin:$PATH"; export PATH; sh scripts/fink-macosx.sh' || exit 1 + +mkdir -p macosx-ppc-build +rsync -v -z -r -e ssh "$mm:schism/macosx-ppc-build/./" macosx-ppc-build/. + + diff --git a/scripts/maint-win32.sh b/scripts/maint-win32.sh new file mode 100644 index 000000000..6e46d45e2 --- /dev/null +++ b/scripts/maint-win32.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# this is the maintainer script for schism on Win32/x86 +mkdir -p win32-x86-build || exit 1 +cd win32-x86-build || exit 1 +SDL_CONFIG=/usr/local/bin/i386-mingw32-sdl-config LIBS=-lSDLmain ../configure --host=i386-mingw32 --with-sdl-prefix=/usr/local/i386-mingw32/ --with-windres=i386-mingw32-windres && make -j3 || exit 1 + +cp /usr/local/i386-mingw32/bin/SDL.dll . || exit 1 +cp ../COPYING COPYING.txt || exit 1 +zip "Schism Tracker.zip" schism.exe SDL.dll COPYING.txt || exit 1 diff --git a/sys/alsa/midi-alsa.c b/sys/alsa/midi-alsa.c new file mode 100644 index 000000000..b6fe00a94 --- /dev/null +++ b/sys/alsa/midi-alsa.c @@ -0,0 +1,487 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "midi.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_ALSA +#include + +#include +#ifdef USE_DLTRICK_ALSA +/* ... */ +#include +#include +#endif +#include + +static snd_seq_t *seq; +static int port_count; +static snd_seq_addr_t *ports; +static int local_port = -1; + +#define MIDI_BUFSIZE 65536 +static unsigned char big_midi_buf[MIDI_BUFSIZE]; +static int alsa_queue; + +struct alsa_midi { + int c, p; + const char *client; + const char *port; + snd_midi_event_t *dev; + int mark; +}; + +/* okay, we do the same trick SDL does to get our alsa library put together */ +#ifdef USE_DLTRICK_ALSA +/* alright, some explanation: + +The libSDL library on Linux doesn't "link with" the alsa library (-lasound) +so that dynamically-linked binaries using libSDL on Linux will work on systems +that don't have libasound.so.2 anywhere. + +There DO EXIST generic solutions (relaytool) but they interact poorly with what +SDL is doing, so here is my ad-hoc solution: + +We define a bunch of these routines similar to how the dynamic linker does it +when RTLD_LAZY is used. There might be a slight performance increase if we +linked them all at once (like libSDL does), but this is certainly a lot easier +to inline. + +If you need additional functions in -lasound in schism, presently they will +have to be declared here for my binary builds to work. + +to use: + size_t snd_seq_port_info_sizeof(void); + +add here: + _any_dltrick(size_t,snd_seq_port_info_sizeof,(void),()) + +(okay, that one is already done). Here's another one: + + int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *e, + snd_mixer_selem_channel_id_t ch, + long *v); + +gets: + _any_dltrick(int,snd_mixer_selem_get_playback_volume, + (snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t ch,long*v),(e,ch,v)) + +If they return void, like: + void snd_midi_event_reset_decode(snd_midi_event_t *d); +use: + _void_dltrick(snd_midi_event_reset_decode,(snd_midi_event_t*d),(d)) + +None of this code is used, btw, if --enable-alsadltrick isn't supplied to +the configure script, so to test it, you should use that when developing. + +*/ + + +#include + +extern void *_dltrick_handle; + +/* don't try this at home... */ +#define _void_dltrick(a,b,c) static void (*_dltrick_ ## a)b = 0; \ +void a b { if (!_dltrick_##a) _dltrick_##a = dlsym(_dltrick_handle, #a); \ +if (!_dltrick_##a) abort(); _dltrick_ ## a c; } + +#define _any_dltrick(r,a,b,c) static r (*_dltrick_ ## a)b = 0; \ +r a b { if (!_dltrick_##a) _dltrick_##a = dlsym(_dltrick_handle, #a); \ +if (!_dltrick_##a) abort(); return _dltrick_ ## a c; } + + +_any_dltrick(size_t,snd_seq_port_info_sizeof,(void),()) +_any_dltrick(size_t,snd_seq_client_info_sizeof,(void),()) +_any_dltrick(size_t,snd_ctl_card_info_sizeof,(void),()) +_any_dltrick(int,snd_ctl_close,(snd_ctl_t*ctl),(ctl)) +_any_dltrick(int,snd_mixer_close,(snd_mixer_t*mm),(mm)) +_any_dltrick(int,snd_mixer_selem_get_playback_volume, +(snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t ch,long*v),(e,ch,v)) + +#if SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR < 9 +_void_dltrick(void,snd_mixer_selem_get_playback_volume_range, +(snd_mixer_elem_t*e,long*m,long*v),(e,m,v)) +#else +_any_dltrick(int,snd_mixer_selem_get_playback_volume_range, +(snd_mixer_elem_t*e,long*m,long*v),(e,m,v)) +#endif + +_any_dltrick(int,snd_mixer_selem_set_playback_volume, +(snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t ch,long v),(e,ch,v)) + +_any_dltrick(int,snd_mixer_selem_is_playback_mono,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_playback_channel,(snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t c),(e,c)) + +_any_dltrick(int,snd_ctl_card_info,(snd_ctl_t*c,snd_ctl_card_info_t*i),(c,i)) +_any_dltrick(int,snd_ctl_open,(snd_ctl_t**c,const char *name,int mode),(c,name,mode)) + +_any_dltrick(int,snd_mixer_open,(snd_mixer_t**m,int mode),(m,mode)) +_any_dltrick(int,snd_mixer_attach,(snd_mixer_t*m,const char *name),(m,name)) + +_any_dltrick(int,snd_seq_control_queue,(snd_seq_t*s,int q,int type, int value, snd_seq_event_t *ev), (s,q,type,value,ev)) + +_any_dltrick(int,snd_mixer_selem_register,(snd_mixer_t*m, + struct snd_mixer_selem_regopt*opt, snd_mixer_class_t **cp),(m,opt,cp)) + +_any_dltrick(int,snd_mixer_selem_is_active,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_playback_volume,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_capture_switch,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_capture_switch_joined,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_capture_switch_exclusive,(snd_mixer_elem_t*e),(e)) + +_any_dltrick(int,snd_mixer_load,(snd_mixer_t*m),(m)) +_any_dltrick(snd_mixer_elem_t*,snd_mixer_first_elem,(snd_mixer_t*m),(m)) +_any_dltrick(snd_mixer_elem_t*,snd_mixer_elem_next,(snd_mixer_elem_t*m),(m)) +_any_dltrick(snd_mixer_elem_type_t,snd_mixer_elem_get_type,(const snd_mixer_elem_t *obj),(obj)) + +_any_dltrick(long,snd_midi_event_encode, +(snd_midi_event_t *dev,const unsigned char *buf,long count,snd_seq_event_t *ev), +(dev,buf,count,ev)) +_any_dltrick(int,snd_seq_event_output, +(snd_seq_t *handle, snd_seq_event_t *ev), +(handle,ev)) +_any_dltrick(int,snd_seq_alloc_queue,(snd_seq_t*h),(h)) +_any_dltrick(int,snd_seq_free_event, +(snd_seq_event_t *ev), +(ev)) +_any_dltrick(int,snd_seq_connect_from, +(snd_seq_t*seq,int my_port,int src_client, int src_port), +(seq,my_port,src_client,src_port)) +_any_dltrick(int,snd_seq_connect_to, +(snd_seq_t*seq,int my_port,int dest_client,int dest_port), +(seq,my_port,dest_client,dest_port)) +_any_dltrick(int,snd_seq_disconnect_from, +(snd_seq_t*seq,int my_port,int src_client, int src_port), +(seq,my_port,src_client,src_port)) +_any_dltrick(int,snd_seq_disconnect_to, +(snd_seq_t*seq,int my_port,int dest_client,int dest_port), +(seq,my_port,dest_client,dest_port)) +_any_dltrick(const char *,snd_strerror,(int errnum),(errnum)) +_any_dltrick(int,snd_seq_poll_descriptors_count,(snd_seq_t*h,short e),(h,e)) +_any_dltrick(int,snd_seq_poll_descriptors,(snd_seq_t*h,struct pollfd*pfds,unsigned int space, short e),(h,pfds,space,e)) +_any_dltrick(int,snd_seq_event_input,(snd_seq_t*h,snd_seq_event_t**ev),(h,ev)) +_any_dltrick(int,snd_seq_event_input_pending,(snd_seq_t*h,int fs),(h,fs)) +_any_dltrick(int,snd_midi_event_new,(size_t s,snd_midi_event_t **rd),(s,rd)) +_any_dltrick(long,snd_midi_event_decode, +(snd_midi_event_t *dev,unsigned char *buf,long count, const snd_seq_event_t*ev), +(dev,buf,count,ev)) +_void_dltrick(snd_midi_event_reset_decode,(snd_midi_event_t*d),(d)) +_any_dltrick(int,snd_seq_create_simple_port, +(snd_seq_t*h,const char *name,unsigned int caps,unsigned int type), +(h,name,caps,type)) +_any_dltrick(int,snd_seq_drain_output,(snd_seq_t*h),(h)) +_any_dltrick(int,snd_seq_query_next_client, +(snd_seq_t*h,snd_seq_client_info_t*info),(h,info)) +_any_dltrick(int,snd_seq_client_info_get_client, +(const snd_seq_client_info_t *info),(info)) +_void_dltrick(snd_seq_client_info_set_client,(snd_seq_client_info_t*inf,int cl),(inf,cl)) +_void_dltrick(snd_seq_port_info_set_client,(snd_seq_port_info_t*inf,int cl),(inf,cl)) +_void_dltrick(snd_seq_port_info_set_port,(snd_seq_port_info_t*inf,int pl),(inf,pl)) +_any_dltrick(int,snd_seq_query_next_port,(snd_seq_t*h,snd_seq_port_info_t*inf),(h,inf)) +_any_dltrick(unsigned int,snd_seq_port_info_get_capability, +(const snd_seq_port_info_t *inf),(inf)) +_any_dltrick(int,snd_seq_port_info_get_client,(const snd_seq_port_info_t*inf),(inf)) +_any_dltrick(int,snd_seq_port_info_get_port,(const snd_seq_port_info_t*inf),(inf)) +_any_dltrick(const char *,snd_seq_client_info_get_name,(snd_seq_client_info_t*inf),(inf)) +_any_dltrick(const char *,snd_seq_port_info_get_name,(const snd_seq_port_info_t*inf),(inf)) +_any_dltrick(int,snd_seq_open,(snd_seq_t**h,const char *name,int str, int mode), +(h,name,str,mode)) +_any_dltrick(int,snd_seq_set_client_name,(snd_seq_t*seq,const char *name),(seq,name)) +#endif + +static void _alsa_send(struct midi_port *p, unsigned char *data, unsigned int len, unsigned int delay) +{ + struct alsa_midi *ex; + snd_seq_event_t ev; + snd_seq_real_time_t rt; + int err; + long rr; + + +/* ehh? */ + snd_seq_start_queue(seq, alsa_queue, NULL); + + ex = (struct alsa_midi *)p->userdata; + + while (len > 0) { + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, local_port); + snd_seq_ev_set_subs(&ev); + if (!delay) { + snd_seq_ev_set_direct(&ev); + } else { + rt.tv_sec = 0; + /* msec to nsec */ + rt.tv_nsec = 1000000*delay; + snd_seq_ev_schedule_real(&ev, + alsa_queue, 1, &rt); + } + + /* we handle our own */ + ev.dest.port = ex->p; + ev.dest.client = ex->c; + + rr = snd_midi_event_encode(ex->dev, data, len, &ev); + if (rr < 1) break; + snd_seq_event_output(seq, &ev); + snd_seq_free_event(&ev); + data += rr; + len -= rr; + } + + /* poof! */ + snd_seq_drain_output(seq); +} +static int _alsa_start(struct midi_port *p) +{ + struct alsa_midi *data; + int err; + + data = (struct alsa_midi *)p->userdata; + if (p->io & MIDI_INPUT) { + err = snd_seq_connect_from(seq, 0, data->c, data->p); + } + if (p->io & MIDI_OUTPUT) { + err = snd_seq_connect_to(seq, 0, data->c, data->p); + } + if (err < 0) { + status_text_flash("ALSA: %s", snd_strerror(err)); + return 0; + } + return 1; +} +static int _alsa_stop(struct midi_port *p) +{ + struct alsa_midi *data; + int err; + + data = (struct alsa_midi *)p->userdata; + if (p->io & MIDI_OUTPUT) { + err = snd_seq_disconnect_to(seq, 0, data->c, data->p); + } + if (p->io & MIDI_INPUT) { + err = snd_seq_disconnect_from(seq, 0, data->c, data->p); + } + if (err < 0) { + status_text_flash("ALSA: %s", snd_strerror(err)); + return 0; + } + return 1; +} +static int _alsa_thread(struct midi_provider *p) +{ + int npfd; + struct pollfd *pfd; + struct midi_port *ptr, *src; + struct alsa_midi *data; + static snd_midi_event_t *dev = 0; + snd_seq_event_t *ev; + long s; + + npfd = snd_seq_poll_descriptors_count(seq, POLLIN); + if (npfd <= 0) return 0; + + pfd = (struct pollfd *)mem_alloc(npfd * sizeof(struct pollfd)); + if (!pfd) return 0; + + for (;;) { + if (snd_seq_poll_descriptors(seq, pfd, npfd, POLLIN) != npfd) { + free(pfd); + return 0; + } + + (void)poll(pfd, npfd, -1); + do { + if (snd_seq_event_input(seq, &ev) < 0) { + break; + } + if (!ev) continue; + + ptr = src = 0; + while (midi_port_foreach(p, &ptr)) { + data = (struct alsa_midi *)ptr->userdata; + if (ev->source.client == data->c + && ev->source.port == data->p + && (ptr->io & MIDI_INPUT)) { + src = ptr; + } + } + if (!src || !ev) { + snd_seq_free_event(ev); + continue; + } + + if (!dev && snd_midi_event_new(sizeof(big_midi_buf), &dev) < 0) { + /* err... */ + break; + } + + s = snd_midi_event_decode(dev, big_midi_buf, + sizeof(big_midi_buf), ev); + if (s > 0) midi_received_cb(src, big_midi_buf, s); + snd_midi_event_reset_decode(dev); + snd_seq_free_event(ev); + } while (snd_seq_event_input_pending(seq, 0) > 0); +// snd_seq_drain_output(seq); + } + return 0; +} +static void _alsa_poll(struct midi_provider *_alsa_provider) +{ + struct midi_port *ptr; + struct alsa_midi *data; + char *buffer; + int c, p, ok, io; + const char *ctext, *ptext; + + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + + if (local_port == -1) { + +#define PORT_NAME "Schism Tracker" + local_port = snd_seq_create_simple_port(seq, + PORT_NAME, + SND_SEQ_PORT_CAP_READ + | SND_SEQ_PORT_CAP_WRITE + | SND_SEQ_PORT_CAP_SYNC_READ + | SND_SEQ_PORT_CAP_SYNC_WRITE + | SND_SEQ_PORT_CAP_DUPLEX + | SND_SEQ_PORT_CAP_SUBS_READ + | SND_SEQ_PORT_CAP_SUBS_WRITE, + + SND_SEQ_PORT_TYPE_APPLICATION + | SND_SEQ_PORT_TYPE_SYNTH + | SND_SEQ_PORT_TYPE_MIDI_GENERIC + | SND_SEQ_PORT_TYPE_MIDI_GM + | SND_SEQ_PORT_TYPE_MIDI_GS + | SND_SEQ_PORT_TYPE_MIDI_XG + | SND_SEQ_PORT_TYPE_MIDI_MT32); + } else { + /* XXX check to see if changes have been made */ + return; + } + + ptr = 0; + while (midi_port_foreach(_alsa_provider, &ptr)) { + data = (struct alsa_midi *)ptr->userdata; + data->mark = 0; + } + + snd_seq_client_info_alloca(&cinfo); + snd_seq_port_info_alloca(&pinfo); + snd_seq_client_info_set_client(cinfo, -1); + while (snd_seq_query_next_client(seq, cinfo) >= 0) { + int cn = snd_seq_client_info_get_client(cinfo); + snd_seq_port_info_set_client(pinfo, cn); + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(seq, pinfo) >= 0) { + io = 0; + if ((snd_seq_port_info_get_capability(pinfo) + & (SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ)) + == (SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ)) + io |= MIDI_INPUT; + if ((snd_seq_port_info_get_capability(pinfo) + & (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE)) + == (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE)) + io |= MIDI_OUTPUT; + + if (!io) continue; + + c = snd_seq_port_info_get_client(pinfo); + if (c == SND_SEQ_CLIENT_SYSTEM) continue; + + p = snd_seq_port_info_get_port(pinfo); + ptr = 0; + ok = 0; + while (midi_port_foreach(_alsa_provider, &ptr)) { + data = (struct alsa_midi *)ptr->userdata; + if (data->c == c && data->p == p) { + data->mark = 1; + ok = 1; + } + } + if (ok) continue; + ctext = snd_seq_client_info_get_name(cinfo); + ptext = snd_seq_port_info_get_name(pinfo); + if (strcmp(ctext, PORT_NAME) == 0 + && strcmp(ptext, PORT_NAME) == 0) { + continue; + } + data = mem_alloc(sizeof(struct alsa_midi)); + data->c = c; data->p = p; + data->client = ctext; + data->mark = 1; + data->port = ptext; + buffer = 0; + + if (snd_midi_event_new(MIDI_BUFSIZE, &data->dev) < 0) { + /* err... */ + free(data); + continue; + } + + asprintf(&buffer, "%3d:%-3d %-20.20s %s", + c, p, ctext, ptext); + midi_port_register(_alsa_provider, io, buffer, data, 1); + } + } + + /* remove "disappeared" midi ports */ + ptr = 0; + while (midi_port_foreach(_alsa_provider, &ptr)) { + data = (struct alsa_midi *)ptr->userdata; + if (data->mark) continue; + midi_port_unregister(ptr->num); + } + +} +int alsa_midi_setup(void) +{ + struct midi_driver driver; +#ifdef USE_DLTRICK_ALSA + if (!dlsym(_dltrick_handle,"snd_seq_open")) return 0; +#endif + driver.poll = _alsa_poll; + driver.thread = _alsa_thread; + driver.enable = _alsa_start; + driver.disable = _alsa_stop; + driver.send = _alsa_send; + driver.flags = MIDI_PORT_CAN_SCHEDULE; + + if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0 + || snd_seq_set_client_name(seq, "Schism Tracker") < 0) { + return 0; + } + + alsa_queue = snd_seq_alloc_queue(seq); + + if (!midi_provider_register("ALSA", &driver)) return 0; + return 1; +} + + +#endif diff --git a/sys/alsa/mixer-alsa.c b/sys/alsa/mixer-alsa.c new file mode 100644 index 000000000..ad997cf3d --- /dev/null +++ b/sys/alsa/mixer-alsa.c @@ -0,0 +1,169 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_ALSA +#include +#include + +static char *alsa_card_id = "default"; + +/* --------------------------------------------------------------------- */ + +static void _alsa_writeout(snd_mixer_elem_t *em, + snd_mixer_selem_channel_id_t d, + int use, int lim) +{ + if (use > lim) use = lim; + (void)snd_mixer_selem_set_playback_volume(em, d, (long)use); +} +static void _alsa_write(snd_mixer_elem_t *em, int *l, int *r, long min, long range) +{ + long al, ar; + long mr, md; + + al = ((*l) * 255 / range) + min; + ar = ((*r) * 255 / range) + min; + + md = ((al) + (ar)) / 2; + mr = min+range; + + if (snd_mixer_selem_is_playback_mono(em)) { + _alsa_writeout(em, SND_MIXER_SCHN_MONO, md, mr); + } else { + _alsa_writeout(em, SND_MIXER_SCHN_FRONT_LEFT, al, mr); + _alsa_writeout(em, SND_MIXER_SCHN_FRONT_RIGHT, ar, mr); + _alsa_writeout(em, SND_MIXER_SCHN_FRONT_CENTER, md, mr); + _alsa_writeout(em, SND_MIXER_SCHN_REAR_LEFT, al, mr); + _alsa_writeout(em, SND_MIXER_SCHN_REAR_RIGHT, ar, mr); + _alsa_writeout(em, SND_MIXER_SCHN_WOOFER, md, mr); + } +} +static void _alsa_readin(snd_mixer_elem_t *em, snd_mixer_selem_channel_id_t d, + int *aa, long min, long range) +{ + long v; + if (snd_mixer_selem_has_playback_channel(em, d)) { + snd_mixer_selem_get_playback_volume(em, d, &v); + v -= min; + v = (v * range) / 255; + if (!*aa) { + (*aa) += v; + } else { + (*aa) += v; + (*aa) /= 2; + } + } + +} +static void _alsa_read(snd_mixer_elem_t *em, int *l, int *r, long min, long range) +{ + if (snd_mixer_selem_is_playback_mono(em)) { + _alsa_readin(em, SND_MIXER_SCHN_MONO, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_MONO, r, min, range); + } else { + _alsa_readin(em, SND_MIXER_SCHN_FRONT_LEFT, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_FRONT_RIGHT, r, min, range); + _alsa_readin(em, SND_MIXER_SCHN_REAR_LEFT, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_REAR_RIGHT, r, min, range); + _alsa_readin(em, SND_MIXER_SCHN_FRONT_CENTER, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_FRONT_CENTER, r, min, range); + _alsa_readin(em, SND_MIXER_SCHN_WOOFER, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_WOOFER, r, min, range); + } +} +static void _alsa_doit(void (*busy)(snd_mixer_elem_t *em, + int *, int *, long, long), int *l, int *r) +{ + long ml, mr; + snd_mixer_elem_t *em; + snd_mixer_t *mix; + snd_ctl_t *ctl_handle; + snd_ctl_card_info_t *hw_info; + int err; + + snd_ctl_card_info_alloca(&hw_info); + if (snd_ctl_open(&ctl_handle, alsa_card_id, 0) < 0) return; + if (snd_ctl_card_info(ctl_handle, hw_info) < 0) { + snd_ctl_close(ctl_handle); + return; + } + snd_ctl_close(ctl_handle); + + if (snd_mixer_open(&mix, 0) == 0) { + if (snd_mixer_attach(mix, alsa_card_id) < 0) { + snd_mixer_close(mix); + return; + } + if (snd_mixer_selem_register(mix, NULL, NULL) < 0) { + snd_mixer_close(mix); + return; + } + if (snd_mixer_load(mix) < 0) { + snd_mixer_close(mix); + return; + } + for (em = snd_mixer_first_elem(mix); em; + em = snd_mixer_elem_next(em)) { + if (snd_mixer_elem_get_type(em) != + SND_MIXER_ELEM_SIMPLE) + continue; + if (!snd_mixer_selem_is_active(em)) continue; + if (!snd_mixer_selem_has_playback_volume(em)) + continue; + if (!snd_mixer_selem_has_playback_volume(em)) + continue; + if (snd_mixer_selem_has_capture_switch_exclusive(em)) + continue; + if (snd_mixer_selem_has_capture_switch_joined(em)) + continue; + + ml = mr = 0; + snd_mixer_selem_get_playback_volume_range(em, &ml, &mr); + if (ml == mr) continue; + + busy(em, l, r, ml, mr - ml); + } + snd_mixer_close(mix); + } + +} + +int alsa_mixer_get_max_volume(void) +{ + int a,b; + return 0xFF; +} + +void alsa_mixer_read_volume(int *left, int *right) +{ + *left = *right = 0; + _alsa_doit(_alsa_read, left, right); +} + +void alsa_mixer_write_volume(int left, int right) +{ + _alsa_doit(_alsa_write, &left, &right); +} +#endif diff --git a/sys/fd.org/autopackage.apspec b/sys/fd.org/autopackage.apspec new file mode 100644 index 000000000..6cbf17437 --- /dev/null +++ b/sys/fd.org/autopackage.apspec @@ -0,0 +1,54 @@ +# -*-shell-script-*- + +[Meta] +RootName: @nimh.org/schism:CVS +DisplayName: Schism Tracker +ShortName: schism +Maintainer: Mrs. Brisby +Packager: Mrs. Brisby +Summary: Schism Tracker is a music editor that matches the look and feel of Impulse Tracker as closely as possible. +URL: http://www.rigelseven.com/schism/ +License: GNU General Public License, Version 2 +SoftwareVersion: 1.0 +AutopackageTarget: 1.0 + +[Description] +Schism Tracker is a music editor in the spirit of Impulse Tracker. Nearly every +feature of Impulse Tracker is available in exactly the same manner. Improvementshave been extremely careful to avoid disturbing any muscle memory that the user +might have developed with Impulse Tracker. + +[BuildPrepare] +export CC=apgcc +export CXX=apg++ +export CFLAGS=-g +export APBUILD_STATIC="" +export APBUILD_STATIC_LIBGCC=1 +export MAKEFLAGS=-j3 +mkdir -p linux-x86-build && cd linux-x86-build && prepareBuild --src .. + +[BuildUnprepare] +unprepareBuild + +[Imports] +import </dev/null 2>&1 +installDesktop "AudioVideo" schism.desktop +installDesktop "AudioVideo" itf.desktop + +[Uninstall] +# Usually just the following line is enough to uninstall everything +uninstallFromLog diff --git a/sys/fd.org/itf.desktop b/sys/fd.org/itf.desktop new file mode 100644 index 000000000..00e760a22 --- /dev/null +++ b/sys/fd.org/itf.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Name=Schism Tracker Font Editor +Comment=ITF Clone +Encoding=UTF-8 +Terminal=false +Exec=schism --font-editor +Type=Application +Icon=schism-itf-icon-128.png +Categories=GNOME;Application;AudioVideo;Audio;Video; +X-Desktop-File-Install-Version=0.10 diff --git a/sys/fd.org/schism.desktop b/sys/fd.org/schism.desktop new file mode 100644 index 000000000..025046969 --- /dev/null +++ b/sys/fd.org/schism.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Actions=Play; +Version=1.0 +Name=Schism Tracker +Comment=Impulse Tracker Clone +Encoding=UTF-8 +Terminal=false +TryExec=schism +Exec=schism %f +Type=Application +Icon=schism-icon-128.png +Categories=GNOME;Application;AudioVideo;Audio;Video; +MimeType=audio/x-mod +X-Desktop-File-Install-Version=0.10 + +[Desktop Action Play] +Name=Schism Tracker (play song) +Exec=schism -p %f diff --git a/sys/macosx/Schism Tracker.app/Contents/Info.plist b/sys/macosx/Schism Tracker.app/Contents/Info.plist new file mode 100644 index 000000000..a5f56bd38 --- /dev/null +++ b/sys/macosx/Schism Tracker.app/Contents/Info.plist @@ -0,0 +1,110 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + schism + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + it + IT + + CFBundleTypeIconFile + moduleIcon.icns + CFBundleMIMETypes + + audio/mod + audio/x-mod + + CFBundleTypeName + Impulse Tracker Module + CFBundleTypeRole + Editor + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + 669 + amf + AMF + ams + AMS + dbm + DBM + dmf + DMF + far + FAR + mdl + MDL + med + MED + mod + MOD + mt2 + MT2 + mtm + MTM + okt + OKT + psm + PSM + ptm + PTM + s3m + S3M + stm + STM + ult + ULT + umx + UMX + xm + XM + + CFBundleTypeIconFile + moduleIcon.icns + CFBundleMIMETypes + + audio/mod + audio/x-mod + + CFBundleTypeName + Audio Module + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleGetInfoString + Schism Tracker Copyright 2005 chisel + CFBundleIconFile + appIcon.icns + CFBundleIdentifier + net.sourceforge.schismtracker.SchismTracker + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Schism Tracker + CFBundlePackageType + APPL + CFBundleShortVersionString + CVS + CFBundleSignature + Schm + CFBundleVersion + 1.0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/sys/macosx/Schism Tracker.app/Contents/PkgInfo b/sys/macosx/Schism Tracker.app/Contents/PkgInfo new file mode 100644 index 000000000..07af6f81c --- /dev/null +++ b/sys/macosx/Schism Tracker.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPLSchm \ No newline at end of file diff --git a/sys/macosx/Schism Tracker.app/Contents/Resources/AppSettings.plist b/sys/macosx/Schism Tracker.app/Contents/Resources/AppSettings.plist new file mode 100644 index 000000000..62376f51d --- /dev/null +++ b/sys/macosx/Schism Tracker.app/Contents/Resources/AppSettings.plist @@ -0,0 +1,18 @@ + + + + + EncryptAndChecksum + + IsDroppable + + OutputType + None + RemainRunningAfterCompletion + + RequiresAdminPrivileges + + ScriptInterpreter + /bin/sh + + diff --git a/sys/macosx/Schism Tracker.app/Contents/Resources/appIcon.icns b/sys/macosx/Schism Tracker.app/Contents/Resources/appIcon.icns new file mode 100644 index 000000000..8ed363793 Binary files /dev/null and b/sys/macosx/Schism Tracker.app/Contents/Resources/appIcon.icns differ diff --git a/sys/macosx/Schism Tracker.app/Contents/Resources/moduleIcon.icns b/sys/macosx/Schism Tracker.app/Contents/Resources/moduleIcon.icns new file mode 100644 index 000000000..597056bf1 Binary files /dev/null and b/sys/macosx/Schism Tracker.app/Contents/Resources/moduleIcon.icns differ diff --git a/sys/macosx/ibook-support.c b/sys/macosx/ibook-support.c new file mode 100644 index 000000000..293fd8137 --- /dev/null +++ b/sys/macosx/ibook-support.c @@ -0,0 +1,98 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" +#include "util.h" + +#ifdef MACOSX + +#include +#include +#include +#include +#include + +#define kMyDriversKeyboardClassName "AppleADBKeyboard" +#define kfnSwitchError 200 +#define kfnAppleMode 0 +#define kfntheOtherMode 1 + +#ifndef kIOHIDFKeyModeKey +#define kIOHIDFKeyModeKey "HIDFKeyMode" +#endif + +int macosx_ibook_fnswitch(int setting) +{ + kern_return_t kr; + mach_port_t mp; + io_service_t so; + io_name_t sn; + io_connect_t dp; + io_iterator_t it; + CFDictionaryRef classToMatch; + CFNumberRef fnMode; + unsigned int res, dummy; + + kr = IOMasterPort(bootstrap_port, &mp); + if (kr != KERN_SUCCESS) return -1; + + classToMatch = IOServiceMatching(kIOHIDSystemClass); + if (classToMatch == NULL) { + return -1; + } + kr = IOServiceGetMatchingServices(mp, classToMatch, &it); + if (kr != KERN_SUCCESS) return -1; + + so = IOIteratorNext(it); + IOObjectRelease(it); + + if (!so) return -1; + + kr = IOServiceOpen(so, mach_task_self(), kIOHIDParamConnectType, &dp); + if (kr != KERN_SUCCESS) return -1; + + kr = IOHIDGetParameter(dp, CFSTR(kIOHIDFKeyModeKey), sizeof(res), + &res, &dummy); + if (kr != KERN_SUCCESS) { + IOServiceClose(dp); + return -1; + } + + if (setting == kfnAppleMode || setting == kfntheOtherMode) { + dummy = setting; + kr = IOHIDSetParameter(dp, CFSTR(kIOHIDFKeyModeKey), + &dummy, sizeof(dummy)); + if (kr != KERN_SUCCESS) { + IOServiceClose(dp); + return -1; + } + } + + IOServiceClose(dp); + /* old setting... */ + return res; +} + +#else + +int macosx_ibook_fnswitch(UNUSED int setting) +{ + return 0; +} +#endif diff --git a/sys/macosx/macosx-sdlmain.m b/sys/macosx/macosx-sdlmain.m new file mode 100644 index 000000000..c5cfbc5b2 --- /dev/null +++ b/sys/macosx/macosx-sdlmain.m @@ -0,0 +1,503 @@ +/* wee.... + +this is used to do some schism-on-macosx customization +and get access to cocoa stuff + +pruned up some here :) -mrsb + + */ + +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +extern char *initial_song; + +#include /* necessary here */ +#include "event.h" + +#import "macosx-sdlmain.h" + +#import /* for MAXPATHLEN */ +#import + +/* Use this flag to determine whether we use CPS (docking) or not */ +#define SDL_USE_CPS 1 +#ifdef SDL_USE_CPS +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +#endif /* SDL_USE_CPS */ + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; +int macosx_did_finderlaunch; + +#define KEQ_FN(n) [NSString stringWithFormat:@"%C", NSF##n##FunctionKey] + +@interface SDLApplication : NSApplication +@end + +@implementation SDLApplication +/* Invoked from the Quit menu item */ +- (void)terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); +} +- (void)_menu_callback:(id)sender +{ + SDL_Event e; + NSString *px; + const char *po; + + px = [sender representedObject]; + po = [px UTF8String]; + if (po) { + e.type = SCHISM_EVENT_NATIVE; + e.user.code = SCHISM_EVENT_NATIVE_SCRIPT; + e.user.data1 = strdup(po); + SDL_PushEvent(&e); + } +} + + +@end + +/* The main class of the application, the application's delegate */ +@implementation SDLMain + +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + SDL_Event e; + const char *po; + + if (!filename) return NO; + + po = [filename UTF8String]; + if (po) { + e.type = SCHISM_EVENT_NATIVE; + e.user.code = SCHISM_EVENT_NATIVE_OPEN; + e.user.data1 = strdup(po); + /* if we started as a result of a doubleclick on + a document, then Main still hasn't really started yet. + */ + initial_song = strdup(po); + SDL_PushEvent(&e); + return YES; + } else { + return NO; + } +} +/* other interesting ones: +- (BOOL)application:(NSApplication *)theApplication printFile:(NSString *)filename +- (BOOL)applicationOpenUntitledFile:(NSApplication *)theApplication +*/ +/* Set the working directory to the .app's parent directory */ +- (void) setupWorkingDirectory:(BOOL)shouldChdir +{ + if (shouldChdir) + { + char parentdir[MAXPATHLEN]; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, true, parentdir, MAXPATHLEN)) { + assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ + } + CFRelease(url); + CFRelease(url2); + } + +} + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenu *otherMenu; + NSMenuItem *menuItem; + + appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + [appleMenu addItemWithTitle:@"About Schism Tracker" + action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + /* other schism items */ + menuItem = [appleMenu addItemWithTitle:@"Help" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(1)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"help"]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + menuItem = [appleMenu addItemWithTitle:@"View Patterns" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(2)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"pattern"]; + menuItem = [appleMenu addItemWithTitle:@"Orders/Panning" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(11)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"orders"]; + menuItem = [appleMenu addItemWithTitle:@"Variables" + action:@selector(_menu_callback:) + keyEquivalent:[NSString stringWithFormat:@"%C", NSF12FunctionKey]]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"variables"]; + menuItem = [appleMenu addItemWithTitle:@"Message Editor" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(9)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"message_edit"]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + [appleMenu addItemWithTitle:@"Hide Schism Tracker" action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + [appleMenu addItemWithTitle:@"Quit Schism Tracker" action:@selector(terminate:) keyEquivalent:@"q"]; + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* File menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"File"]; + menuItem = [otherMenu addItemWithTitle:@"New..." + action:@selector(_menu_callback:) + keyEquivalent:@"n"]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"new"]; + menuItem = [otherMenu addItemWithTitle:@"Load..." + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(9)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"load"]; + menuItem = [otherMenu addItemWithTitle:@"Save Current" + action:@selector(_menu_callback:) + keyEquivalent:@"s"]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"save"]; + menuItem = [otherMenu addItemWithTitle:@"Save As..." + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(10)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"save_as"]; + menuItem = [otherMenu addItemWithTitle:@"Message Log" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(1)]; + [menuItem setKeyEquivalentModifierMask:NSFunctionKeyMask|NSControlKeyMask]; + [menuItem setRepresentedObject: @"logviewer"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Playback menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"Playback"]; + menuItem = [otherMenu addItemWithTitle:@"Show Infopage" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(5)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"info"]; + menuItem = [otherMenu addItemWithTitle:@"Play Song" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(5)]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"play"]; + menuItem = [otherMenu addItemWithTitle:@"Play Pattern" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(6)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"play_pattern"]; + menuItem = [otherMenu addItemWithTitle:@"Play from Order" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(6)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"play_order"]; + menuItem = [otherMenu addItemWithTitle:@"Play from Mark/Cursor" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(7)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"play_mark"]; + menuItem = [otherMenu addItemWithTitle:@"Stop" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(8)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"stop"]; + menuItem = [otherMenu addItemWithTitle:@"Calculate Length" + action:@selector(_menu_callback:) + keyEquivalent:@"p"]; + [menuItem setKeyEquivalentModifierMask:(NSFunctionKeyMask|NSControlKeyMask)]; + [menuItem setRepresentedObject: @"calc_length"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Sample menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"Samples"]; + menuItem = [otherMenu addItemWithTitle:@"Sample List" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(3)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"sample_page"]; + menuItem = [otherMenu addItemWithTitle:@"Sample Library" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(3)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"sample_library"]; + menuItem = [otherMenu addItemWithTitle:@"Reload Soundcard" + action:@selector(_menu_callback:) + keyEquivalent:@"g"]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"init_sound"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Instrument menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"Instruments"]; + menuItem = [otherMenu addItemWithTitle:@"Instrument List" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(4)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"inst_page"]; + menuItem = [otherMenu addItemWithTitle:@"Instrument Library" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(4)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"inst_library"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Settings menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"Settings"]; + menuItem = [otherMenu addItemWithTitle:@"Preferences" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(5)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"preferences"]; + menuItem = [otherMenu addItemWithTitle:@"MIDI Configuration" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(1)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"midi_config"]; + menuItem = [otherMenu addItemWithTitle:@"Palette Editor" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(12)]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"palette_page"]; + menuItem = [otherMenu addItemWithTitle:@"Toggle Fullscreen" + action:@selector(_menu_callback:) + keyEquivalent:@"\r"]; + [menuItem setKeyEquivalentModifierMask:(NSControlKeyMask|NSCommandKeyMask)]; + [menuItem setRepresentedObject: @"fullscreen"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + + /* Finally give up our references to the objects */ + [appleMenu release]; + [menuItem release]; +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ + [windowMenu release]; + [windowMenuItem release]; +} + +/* Replacement for NSApplicationMain */ +static void CustomApplicationMain (argc, argv) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + SDLMain *sdlMain; + + /* Ensure the application object is initialised */ + [SDLApplication sharedApplication]; + +#ifdef SDL_USE_CPS + { + CPSProcessSerNum PSN; + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [SDLApplication sharedApplication]; + } +#endif /* SDL_USE_CPS */ + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create SDLMain and make it the app delegate */ + sdlMain = [[SDLMain alloc] init]; + [NSApp setDelegate:sdlMain]; + + /* Start the main event loop */ + [NSApp run]; + + [sdlMain release]; + [pool release]; +} + +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + int status; + + /* Set the working directory to the .app's parent directory */ + [self setupWorkingDirectory:gFinderLaunch]; + + /* Hand off to main application code */ + status = SDL_main (gArgc, gArgv); + + /* We're done, thank you for playing */ + exit(status); +} +@end + + +@implementation NSString (ReplaceSubString) + +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString +{ + unsigned int bufferSize; + unsigned int selfLen = [self length]; + unsigned int aStringLen = [aString length]; + unichar *buffer; + NSRange localRange; + NSString *result; + + bufferSize = selfLen + aStringLen - aRange.length; + buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar)); + + /* Get first part into buffer */ + localRange.location = 0; + localRange.length = aRange.location; + [self getCharacters:buffer range:localRange]; + + /* Get middle part into buffer */ + localRange.location = 0; + localRange.length = aStringLen; + [aString getCharacters:(buffer+aRange.location) range:localRange]; + + /* Get last part into buffer */ + localRange.location = aRange.location + aRange.length; + localRange.length = selfLen - localRange.location; + [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; + + /* Build output string */ + result = [NSString stringWithCharacters:buffer length:bufferSize]; + + NSDeallocateMemoryPages(buffer, bufferSize); + + return result; +} + +@end + + + +#ifdef main +# undef main +#endif + + +/* Main entry point to executable - should *not* be SDL_main! */ +int main (int argc, char **argv) +{ + /* Copy the arguments into a global variable */ + /* This is passed if we are launched by double-clicking */ + if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { + gArgc = 1; + gFinderLaunch = YES; + macosx_did_finderlaunch = 1; + } else { + gArgc = argc; + gFinderLaunch = NO; + macosx_did_finderlaunch = 0; + } + gArgv = argv; + + CustomApplicationMain (argc, argv); + + return 0; +} + +/* these routines provide clipboard encapsulation */ +const char *macosx_clippy_get(void) +{ + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + NSString *type = [pb availableTypeFromArray:[NSArray + arrayWithObject:NSStringPboardType]]; + NSString *contents; + const char *po; + + if (type == nil) return ""; + + contents = [pb stringForType:type]; + if (contents == nil) return ""; + po = [contents UTF8String]; + if (!po) return ""; + return po; +} +void macosx_clippy_put(const char *buf) +{ + NSString *contents = [NSString stringwithUTF8String:buf]; + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; + [pb setString:contents forType:NSStringPboardType]; +} + diff --git a/sys/macosx/midi-macosx.c b/sys/macosx/midi-macosx.c new file mode 100644 index 000000000..a8700edb3 --- /dev/null +++ b/sys/macosx/midi-macosx.c @@ -0,0 +1,209 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "midi.h" + +#include "mixer.h" +#include "util.h" + +#ifdef MACOSX + +#include +#include +#include + +static MIDIClientRef client = NULL; +static MIDIPortRef portIn = NULL; +static MIDIPortRef portOut = NULL; + +static int max_outputs = 0; +static int max_inputs = 0; + +struct macosx_midi { + char *name; + MIDIEndpointRef ep; + unsigned char packet[1024]; + MIDIPacketList *pl; + MIDIPacket *x; +}; + +static void readProc(const MIDIPacketList *np, UNUSED void *rc, void *crc) +{ + struct midi_port *p; + struct macosx_midi *m; + MIDIPacket *x; + int i; + + p = (struct midi_port *)crc; + m = (struct macosx_midi *)p->userdata; + + x = (MIDIPacket*)&np->packet[0]; + for (i = 0; i < np->numPackets; i++) { + midi_received_cb(p, x->data, x->length); + x = MIDIPacketNext(x); + } +} +static void _macosx_send(struct midi_port *p, unsigned char *data, + unsigned int len, unsigned int delay) +{ + struct macosx_midi *m; + + m = (struct macosx_midi *)p->userdata; + if (!m->x) { + m->x = MIDIPacketListInit(m->pl); + } + + m->x = MIDIPacketListAdd(m->pl, sizeof(m->packet), + m->x, (MIDITimeStamp)AudioConvertNanosToHostTime( + AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) + (1000000*delay)), /* msec to nsec? */ + len, data); + MIDISend(portOut, m->ep, m->pl); + m->x = NULL; +} + +/* lifted from portmidi */ +static char *get_ep_name(MIDIEndpointRef ep) +{ + MIDIEntityRef entity; + MIDIDeviceRef device; + CFStringRef endpointName = NULL, deviceName = NULL, fullName = NULL; + CFStringEncoding defaultEncoding; + char* newName; + + /* get the default string encoding */ + defaultEncoding = CFStringGetSystemEncoding(); + + /* get the entity and device info */ + MIDIEndpointGetEntity(ep, &entity); + MIDIEntityGetDevice(entity, &device); + + /* create the nicely formated name */ + MIDIObjectGetStringProperty(ep, kMIDIPropertyName, &endpointName); + MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName); + if (deviceName != NULL) { + fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"), + deviceName, endpointName); + } else { + fullName = endpointName; + } + + /* copy the string into our buffer */ + newName = (char*)mem_alloc(CFStringGetLength(fullName) + 1); + CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1, + defaultEncoding); + + /* clean up */ + if (endpointName) CFRelease(endpointName); + if (deviceName) CFRelease(deviceName); + if (fullName) CFRelease(fullName); + + return newName; +} + +static int _macosx_start(struct midi_port *p) +{ + struct macosx_midi *m; + m = (struct macosx_midi *)p->userdata; + + if (p->io & MIDI_INPUT + && MIDIPortConnectSource(portIn, m->ep, (void*)p) != noErr) { + return 0; + } + + if (p->io & MIDI_OUTPUT) { + m->pl = (MIDIPacketList*)m->packet; + m->x = NULL; + } + return 1; +} +static int _macosx_stop(struct midi_port *p) +{ + struct macosx_midi *m; + m = (struct macosx_midi *)p->userdata; + if (p->io & MIDI_INPUT + && MIDIPortDisconnectSource(portIn, m->ep) != noErr) { + return 0; + } + return 1; +} + +static void _macosx_poll(struct midi_provider *p) +{ + struct macosx_midi *data; + MIDIEndpointRef ep; + int i; + + int num_out, num_in; + + num_out = MIDIGetNumberOfDestinations(); + num_in = MIDIGetNumberOfSources(); + + for (i = max_outputs; i < num_out; i++) { + ep = MIDIGetDestination(i); + if (!ep) continue; + data = mem_alloc(sizeof(struct macosx_midi)); + memcpy(&data->ep, &ep, sizeof(ep)); + data->name = get_ep_name(ep); + midi_port_register(p, MIDI_OUTPUT, data->name, data, 1); + } + max_outputs = i; + + + for (i = max_inputs; i < num_in; i++) { + ep = MIDIGetSource(i); + if (!ep) continue; + data = mem_alloc(sizeof(struct macosx_midi)); + memcpy(&data->ep, &ep, sizeof(ep)); + data->name = get_ep_name(ep); + midi_port_register(p, MIDI_INPUT, data->name, data, 1); + } + max_inputs = i; + +} + +int macosx_midi_setup(void) +{ + struct midi_driver driver; + + driver.flags = MIDI_PORT_CAN_SCHEDULE; + driver.poll = _macosx_poll; + driver.thread = NULL; + driver.enable = _macosx_start; + driver.disable = _macosx_stop; + driver.send = _macosx_send; + + if (MIDIClientCreate(CFSTR("Schism Tracker"), NULL, NULL, &client) != noErr) { + return; + } + if (MIDIInputPortCreate(client, CFSTR("Input port"), readProc, NULL, &portIn) != noErr) { + return; + } + if (MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut) != noErr) { + return; + } + + if (!midi_provider_register("Mac OS X", &driver)) return 0; + + return 1; +} + +#endif diff --git a/sys/oss/midi-oss.c b/sys/oss/midi-oss.c new file mode 100644 index 000000000..4269a70ba --- /dev/null +++ b/sys/oss/midi-oss.c @@ -0,0 +1,173 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "headers.h" + +#include "midi.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_OSS + +#include +#include +#include +#include +#include + +#include +#include + +/* this is stupid; oss doesn't have a concept of ports... */ +#define MAX_OSS_MIDI 64 + +#define MAX_MIDI_PORTS MAX_OSS_MIDI+2 +static int opened[MAX_MIDI_PORTS]; + + +static void _oss_send(struct midi_port *p, unsigned char *data, unsigned int len, UNUSED unsigned int delay) +{ + int fd, r, n; + if (!p->userdata) return; + fd = opened[ n = (*(int*)p->userdata) ]; + if (fd < 0) return; + while (len > 0) { + r = write(fd, data, len); + if (r < -1 && errno == EINTR) continue; + if (r < 1) { + /* err, can't happen? */ + (void)close(opened[n]); + opened[n] = -1; + p->userdata = 0; + p->io = 0; /* failure! */ + return; + } + data += r; + len -= r; + } +} + +static int _oss_start(UNUSED struct midi_port *p) { return 1; /* do nothing */ } +static int _oss_stop(UNUSED struct midi_port *p) { return 1; /* do nothing */ } + + +static int _oss_thread(struct midi_provider *p) +{ + struct pollfd pfd[MAX_MIDI_PORTS]; + struct midi_port *ptr, *src; + unsigned char midi_buf[4096]; + int i, j, r; + + for (;;) { + ptr = 0; + j = 0; + while (midi_port_foreach(p, &ptr)) { + if (!ptr->userdata) continue; + i = *(int *)ptr->userdata; + if (i == -1) continue; /* err... */ + if (!(ptr->io & MIDI_INPUT)) continue; + pfd[j].fd = i; + pfd[j].events = POLLIN; + pfd[j].revents = 0; /* RH 5 bug */ + j++; + } + if (!j || poll(pfd, j, -1) < 1) { + sleep(1); + continue; + } + for (i = 0; i < j; i++) { + if (!(pfd[i].revents & POLLIN)) continue; + do { + r = read(pfd[i].fd, midi_buf, sizeof(midi_buf)); + } while (r == -1 && errno == EINTR); + if (r > 0) { + ptr = src = 0; + while (midi_port_foreach(p, &ptr)) { + if (!ptr->userdata) continue; + if (*(int *)ptr->userdata == pfd[i].fd) { + src = ptr; + } + } + midi_received_cb(src, midi_buf, r); + } + } + } +} + + +static void _tryopen(int n, char *name, struct midi_provider *_oss_provider) +{ + int io; + char *ptr; + + if (opened[n+1] != -1) return; + opened[n+1] = open(name, O_RDWR|O_NOCTTY); + if (opened[n+1] == -1) { + opened[n+1] = open(name, O_RDONLY|O_NOCTTY); + if (opened[n+1] == -1) { + opened[n+1] = open(name, O_WRONLY|O_NOCTTY); + if (opened[n+1] == -1) return; + io = MIDI_OUTPUT; + } else { + io = MIDI_INPUT; + } + } else { + io = MIDI_INPUT | MIDI_OUTPUT; + } + + ptr = 0; + asprintf(&ptr, " %-16s (OSS)", name); + midi_port_register(_oss_provider, io, ptr, (void*)&opened[n+1], 0); +} + +static void _oss_poll(struct midi_provider *_oss_provider) +{ + struct midi_port *ptr; + char sbuf[64]; + int i; + + _tryopen(-1, "/dev/midi", _oss_provider); + for (i = 0; i < MAX_OSS_MIDI; i++) { + sprintf(sbuf, "/dev/midi%d", i); + _tryopen(i, sbuf, _oss_provider); + + sprintf(sbuf, "/dev/midi%02d", i); + _tryopen(i, sbuf, _oss_provider); + } +} +int oss_midi_setup(void) +{ + struct midi_driver driver; + int i; + + driver.flags = 0; + driver.poll = _oss_poll; + driver.thread = _oss_thread; + driver.enable = _oss_start; + driver.disable = _oss_stop; + driver.send = _oss_send; + + for (i = 0; i < MAX_MIDI_PORTS; i++) opened[i] = -1; + if (!midi_provider_register("OSS", &driver)) return 0; + return 1; +} +#endif diff --git a/sys/oss/mixer-oss.c b/sys/oss/mixer-oss.c new file mode 100644 index 000000000..42844747e --- /dev/null +++ b/sys/oss/mixer-oss.c @@ -0,0 +1,120 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_OSS + +#include +#include +#include +#include + +#include +#include + +/* Hmm. I've found that some systems actually don't support the idea of a + * "master" volume, so this became necessary. I suppose PCM is really the + * more useful setting to change anyway. */ +#if 0 +# define SCHISM_MIXER_CONTROL SOUND_MIXER_VOLUME +#else +# define SCHISM_MIXER_CONTROL SOUND_MIXER_PCM +#endif + +#define VOLUME_MAX 100 + +/* --------------------------------------------------------------------- */ + +static const char *device_file = NULL; + +/* --------------------------------------------------------------------- */ + +static int open_mixer_device(void) +{ + const char *ptr; + + if (!device_file) { + ptr = "/dev/sound/mixer"; + if (access(ptr, F_OK) < 0) { + /* this had better work :) */ + ptr = "/dev/mixer"; + } + device_file = ptr; + } + + return open(device_file, O_RDWR); +} + +/* --------------------------------------------------------------------- */ + +int oss_mixer_get_max_volume(void) +{ + return VOLUME_MAX; +} + +void oss_mixer_read_volume(int *left, int *right) +{ + int fd; + byte volume[4]; + + fd = open_mixer_device(); + if (fd < 0) { + perror(device_file); + *left = *right = 0; + return; + } + + if (ioctl(fd, MIXER_READ(SCHISM_MIXER_CONTROL), volume) == EOF) { + perror(device_file); + *left = *right = 0; + } else { + *left = volume[0]; + *right = volume[1]; + } + + close(fd); +} + +void oss_mixer_write_volume(int left, int right) +{ + int fd; + byte volume[4]; + + volume[0] = CLAMP(left, 0, VOLUME_MAX); + volume[1] = CLAMP(right, 0, VOLUME_MAX); + + fd = open_mixer_device(); + if (fd < 0) { + perror(device_file); + return; + } + + if (ioctl(fd, MIXER_WRITE(SCHISM_MIXER_CONTROL), volume) == EOF) { + perror(device_file); + } + + close(fd); +} + +#endif diff --git a/sys/posix/slurp-mmap.c b/sys/posix/slurp-mmap.c new file mode 100644 index 000000000..cfec4bb49 --- /dev/null +++ b/sys/posix/slurp-mmap.c @@ -0,0 +1,58 @@ +/* + * slurp - mmap isolation + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if HAVE_MMAP +#include +#include +#include +#include + +#include "slurp.h" + +static void _munmap_slurp(slurp_t *useme) +{ + (void)munmap((void*)useme->data, useme->length); + (void)close(useme->extra); +} + +int slurp_mmap(slurp_t *useme, const char *filename, size_t st) +{ + int fd; + void *addr; + + fd = open(filename, O_RDONLY); + if (fd == -1) return 0; + addr = mmap(0, st, PROT_READ, MAP_SHARED, fd, 0); + if (!addr || addr == ((void*)-1)) { + (void)close(fd); + return -1; + } + useme->closure = _munmap_slurp; + useme->length = st; + useme->data = addr; + useme->extra = fd; + return 1; +} + +#endif diff --git a/sys/sdl/README b/sys/sdl/README new file mode 100644 index 000000000..6e22655c4 --- /dev/null +++ b/sys/sdl/README @@ -0,0 +1,2 @@ +this directory will eventually have SDL-specific stuff in it. +right now, schism is SDL specific :) diff --git a/sys/win32/midi-win32mm.c b/sys/win32/midi-win32mm.c new file mode 100644 index 000000000..b8aec98a0 --- /dev/null +++ b/sys/win32/midi-win32mm.c @@ -0,0 +1,302 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "midi.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_WIN32MM + +#include +#include +#include + +struct win32mm_midi { + DWORD id; + + HMIDIOUT out; + HMIDIIN in; + + MIDIINCAPS icp; + MIDIOUTCAPS ocp; + + MIDIHDR hh; + LPMIDIHDR obuf; + unsigned char sysx[1024]; +}; +static unsigned int last_known_in_port = 0; +static unsigned int last_known_out_port = 0; + +static MMRESULT (*XP_timeSetEvent)(UINT u,UINT r,LPTIMECALLBACK proc, + DWORD_PTR user, UINT flags) = 0; + +static void _win32mm_sysex(LPMIDIHDR *q, unsigned char *d, unsigned int len) +{ + unsigned char *z; + LPMIDIHDR m; + + z = mem_alloc(sizeof(MIDIHDR) + len + 8); + m = (LPMIDIHDR)z; + + memset(m, 0, sizeof(MIDIHDR)); + if (d) memcpy(z + sizeof(MIDIHDR), d, len); + + m->lpData = (z+sizeof(MIDIHDR)); + m->dwBufferLength = len+8; + m->dwBytesRecorded = (d ? len : 0); + m->lpNext = *q; + m->dwOffset = 0; + (*q) = (m); +} + +static CALLBACK void _win32mm_xp_output(UNUSED UINT uTimerID, + UNUSED UINT uMsg, + DWORD_PTR dwUser, + DWORD_PTR dw1, + DWORD_PTR dw2) +{ + LPMIDIHDR x; + HMIDIOUT out; + + x = (LPMIDIHDR)dwUser; + out = (HMIDIOUT)x->dwUser; + + if (midiOutPrepareHeader(out, x, sizeof(MIDIHDR)) == MMSYSERR_NOERROR) { + (void)midiOutLongMsg(out, x, sizeof(MIDIHDR)); + } +} +static void _win32mm_send_xp(struct midi_port *p, unsigned char *data, + unsigned int len, unsigned int delay) +{ + /* version for windows XP */ + struct win32mm_midi *m; + + if (len == 0) return; + m = p->userdata; + + _win32mm_sysex(&m->obuf, data, len); + m->obuf->dwUser = (DWORD_PTR)m->out; + if (XP_timeSetEvent(delay, 0, _win32mm_xp_output, (DWORD_PTR)m->obuf, + TIME_ONESHOT | TIME_CALLBACK_FUNCTION) == NULL) { + /* slow... */ + if (midiOutPrepareHeader(m->out, m->obuf, sizeof(MIDIHDR)) == MMSYSERR_NOERROR) { + (void)midiOutLongMsg(m->out, m->obuf, sizeof(MIDIHDR)); + } + return; + } + /* will happen... */ +} + +static void _win32mm_send(struct midi_port *p, unsigned char *data, + unsigned int len, UNUSED unsigned int delay) +{ + struct win32mm_midi *m; + DWORD q; + + if (len == 0) return; + + m = p->userdata; + if (len <= 4) { + q = data[0]; + if (len > 1) q |= (data[1] << 8); + if (len > 2) q |= (data[2] << 16); + if (len > 3) q |= (data[3] << 24); /* eh... */ + (void)midiOutShortMsg(m->out, q); + } else { + /* SysEX */ + _win32mm_sysex(&m->obuf, data, len); + if (midiOutPrepareHeader(m->out, m->obuf, sizeof(MIDIHDR)) == MMSYSERR_NOERROR) { + (void)midiOutLongMsg(m->out, m->obuf, sizeof(MIDIHDR)); + } + } +} + + +static CALLBACK void _win32mm_inputcb(HMIDIIN in, UINT wmsg, DWORD inst, + DWORD param1, DWORD param2) +{ + struct midi_port *p = (struct midi_port *)inst; + struct win32mm_midi *m; + unsigned char c[4]; + + switch (wmsg) { + case MIM_OPEN: + SDL_Delay(0); /* eh? */ + case MIM_CLOSE: + break; + case MIM_DATA: + c[0] = param1 & 255; + c[1] = (param1 >> 8) & 255; + c[2] = (param1 >> 16) & 255; + midi_received_cb(p, c, 3); + break; + case MIM_LONGDATA: + /* long data */ + m = p->userdata; + midi_received_cb(p, m->hh.lpData, m->hh.dwBytesRecorded); + m->hh.dwBytesRecorded = 0; + (void)midiInAddBuffer(m->in, &m->hh, sizeof(MIDIHDR)); + break; + } +} + + +static int _win32mm_start(struct midi_port *p) +{ + struct win32mm_midi *m; + UINT id; + WORD r; + + m = p->userdata; + id = m->id; + if (p->io == MIDI_INPUT) { + m->in = NULL; + r = midiInOpen(&m->in, + (UINT_PTR)id, + (DWORD_PTR)_win32mm_inputcb, + (DWORD_PTR)p, + CALLBACK_FUNCTION); + if (r != MMSYSERR_NOERROR) return 0; + memset(&m->hh, 0, sizeof(m->hh)); + m->hh.lpData = (LPSTR)m->sysx; + m->hh.dwBufferLength = sizeof(m->sysx); + m->hh.dwFlags = 0; + r = midiInPrepareHeader(m->in, &m->hh, sizeof(MIDIHDR)); + if (r != MMSYSERR_NOERROR) return 0; + r = midiInAddBuffer(m->in, &m->hh, sizeof(MIDIHDR)); + if (r != MMSYSERR_NOERROR) return 0; + if (midiInStart(m->in) != MMSYSERR_NOERROR) return 0; + + } + if (p->io & MIDI_OUTPUT) { + m->out = NULL; + if (midiOutOpen(&m->out, + (UINT_PTR)id, + 0, 0, + CALLBACK_NULL) != MMSYSERR_NOERROR) return 0; + } + return 1; +} +static int _win32mm_stop(struct midi_port *p) +{ + struct win32mm_midi *m; + LPMIDIHDR ptr; + WORD r; + + m = p->userdata; + if (p->io & MIDI_INPUT) { + /* portmidi appears to (essentially) ignore the error codes + for these guys */ + (void)midiInStop(m->in); + (void)midiInReset(m->in); + (void)midiInClose(m->in); + } + if (p->io & MIDI_OUTPUT) { + (void)midiOutReset(m->out); + (void)midiOutClose(m->out); + /* free output chain */ + ptr = m->obuf; + while (ptr) { + m->obuf = m->obuf->lpNext; + (void)free(m->obuf); + ptr = m->obuf; + } + } + return 1; +} + + +static void _win32mm_poll(struct midi_provider *p) +{ + struct midi_port *ptr; + struct win32mm_midi *data; + + UINT i; + UINT mmin, mmout; + WORD r; + + mmin = midiInGetNumDevs(); + for (i = last_known_in_port; i < mmin; i++) { + data = mem_alloc(sizeof(struct win32mm_midi)); + memset(data,0,sizeof(struct win32mm_midi)); + r = midiInGetDevCaps(i, (LPMIDIINCAPS)&data->icp, + sizeof(MIDIINCAPS)); + if (r != MMSYSERR_NOERROR) { + free(data); + continue; + } + data->id = i; + midi_port_register(p, MIDI_INPUT, data->icp.szPname, data, 1); + } + last_known_in_port = mmin; + + mmout = midiOutGetNumDevs(); + for (i = last_known_out_port; i < mmout; i++) { + data = mem_alloc(sizeof(struct win32mm_midi)); + memset(data,0,sizeof(struct win32mm_midi)); + r = midiOutGetDevCaps(i, (LPMIDIOUTCAPS)&data->ocp, + sizeof(MIDIOUTCAPS)); + if (r != MMSYSERR_NOERROR) { + if (data) free(data); + continue; + } + data->id = i; + midi_port_register(p, MIDI_OUTPUT, data->ocp.szPname, data, 1); + } + last_known_out_port = mmout; +} + +int win32mm_midi_setup(void) +{ + struct midi_driver driver; + HINSTANCE winmm; + + driver.flags = 0; + driver.poll = _win32mm_poll; + driver.thread = NULL; + driver.enable = _win32mm_start; + driver.disable = _win32mm_stop; + + winmm = GetModuleHandle("WINMM.DLL"); + if (!winmm) winmm = LoadLibrary("WINMM.DLL"); + if (!winmm) winmm = GetModuleHandle("WINMM.DLL"); + if (winmm) { + XP_timeSetEvent = (void*)GetProcAddress(winmm,"timeSetEvent"); + if (XP_timeSetEvent) { + driver.send = _win32mm_send_xp; + driver.flags |= MIDI_PORT_CAN_SCHEDULE; + } else { + driver.send = _win32mm_send; + } + } else { + XP_timeSetEvent = 0; + driver.send = _win32mm_send; + } + + if (!midi_provider_register("Win32MM", &driver)) return 0; + + return 1; +} + + +#endif diff --git a/sys/win32/mixer-win32mm.c b/sys/win32/mixer-win32mm.c new file mode 100644 index 000000000..cc2d7f976 --- /dev/null +++ b/sys/win32/mixer-win32mm.c @@ -0,0 +1,95 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_WIN32MM + +#include +#include + +/* Note: [Gargaj] + + WinMM DOES support max volumes up to 65535, but the scroller is + so goddamn slow and it only supports 3 digits anyway, that + it doesn't make any sense to keep the precision. +*/ + +int win32mm_mixer_get_max_volume(void) +{ + return 0xFF; +} + +static HWAVEOUT open_mixer() +{ + HWAVEOUT hwo=NULL; + WAVEFORMATEX pwfx; +#if 0 + pwfx.wFormatTag = WAVE_FORMAT_UNKNOWN; + pwfx.nChannels = 0; + pwfx.nSamplesPerSec = 0; + pwfx.wBitsPerSample = 0; + pwfx.nBlockAlign = 0; + pwfx.nAvgBytesPerSec = 0; + pwfx.cbSize = 0; +#else + pwfx.wFormatTag = WAVE_FORMAT_PCM; + pwfx.nChannels = 1; + pwfx.nSamplesPerSec = 44100; + pwfx.wBitsPerSample = 8; + pwfx.nBlockAlign = 4; + pwfx.nAvgBytesPerSec = 44100*1*1; + pwfx.cbSize = 0; +#endif + if (waveOutOpen(&hwo, WAVE_MAPPER, &pwfx, 0, 0, CALLBACK_NULL)!=MMSYSERR_NOERROR) + return NULL; + return hwo; +} + +void win32mm_mixer_read_volume(int *left, int *right) +{ + DWORD vol; + HWAVEOUT hwo=open_mixer(); + + *left = *right = 0; + if (!hwo) return; + + waveOutGetVolume(hwo,&vol); + + *left = (vol & 0xFFFF) >> 8; + *right = (vol >> 16) >> 8; + + waveOutClose(hwo); +} + +void win32mm_mixer_write_volume(int left, int right) +{ + DWORD vol = ((left & 0xFF)<<8) | ((right & 0xFF)<<(16+8)); + HWAVEOUT hwo = open_mixer(); + if (!hwo) return; + + waveOutSetVolume(hwo,vol); + + waveOutClose(hwo); +} +#endif diff --git a/sys/win32/schismres.rc b/sys/win32/schismres.rc new file mode 100644 index 000000000..830fcfa33 --- /dev/null +++ b/sys/win32/schismres.rc @@ -0,0 +1 @@ +schismicon ICON "icons/schismres.ico" diff --git a/sys/win32/slurp-win32.c b/sys/win32/slurp-win32.c new file mode 100644 index 000000000..a4b4bcb07 --- /dev/null +++ b/sys/win32/slurp-win32.c @@ -0,0 +1,84 @@ +/* + * slurp - win32 isolation + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef WIN32 +#include + +#include + +#include "slurp.h" + +static void _win32_unmap(slurp_t *useme) +{ + HANDLE *bp; + (void)UnmapViewOfFile((LPVOID)useme->data); + + bp = (HANDLE*)useme->bextra; + CloseHandle(bp[0]); + CloseHandle(bp[1]); + free(bp); +} +int slurp_win32(slurp_t *useme, const char *filename, size_t st) +{ + HANDLE h, m, *bp; + LPVOID addr; + SECURITY_ATTRIBUTES sa; + + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = 0; + + bp = (HANDLE*)mem_alloc(sizeof(HANDLE)*2); + + h = CreateFile(filename, GENERIC_READ, + FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (!h) { + log_appendf(4, "CreateFile(%s) failed with %d", filename, + GetLastError()); + free(bp); + return 0; + } + + m = CreateFileMapping(h, NULL, PAGE_READONLY, 0, 0, NULL); + if (!h) { + log_appendf(4, "CreateFileMapping failed with %d", GetLastError()); + CloseHandle(h); + free(bp); + return -1; + } + addr = MapViewOfFile(m, FILE_MAP_READ, 0, 0, 0); + if (!addr) { + log_appendf(4, "MapViewOfFile failed with %d", GetLastError()); + CloseHandle(m); + CloseHandle(h); + free(bp); + return -1; + } + useme->data = addr; + useme->length = st; + useme->closure = _win32_unmap; + + bp[0] = m; bp[1] = h; + useme->bextra = bp; + return 1; +} + +#endif diff --git a/sys/x11/xscreensaver.c b/sys/x11/xscreensaver.c new file mode 100644 index 000000000..b0ec90516 --- /dev/null +++ b/sys/x11/xscreensaver.c @@ -0,0 +1,162 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* I wanted to just lift this code out of xscreensaver-command, + but that wasn't really convenient. xscreensaver really should've + had this (or something like it) as a simple library call like: + xscreensaver_synthetic_user_active(display); + or something like that. spawning a subprocess is really expensive on + some systems, and might have subtle interactions with SDL or the player. + + -mrsb +*/ + +#include "headers.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +static XErrorHandler old_handler = 0; +static int BadWindow_ehandler(Display *dpy, XErrorEvent *error) +{ + if (error->error_code == BadWindow) { + return 0; + } else { + if (old_handler) return (*old_handler)(dpy,error); + /* shrug */ + return 1; + } +} + +void xscreensaver_deactivate(void) +{ + static Atom XA_SCREENSAVER_VERSION; + static Atom XA_DEACTIVATE; + static Atom XA_SCREENSAVER; + static int setup = 0; + static int useit = 0; + static SDL_SysWMinfo info; + + Window root, tmp, parent, *kids; + unsigned int nkids, i; + + static time_t lastpoll = 0; + Window win; + time_t now; + + Display *dpy = 0; + XEvent ev; + + if (!setup) { + setup = 1; + useit = 1; + SDL_GetWMInfo(&info); + + info.info.x11.lock_func(); + dpy = info.info.x11.display; + XA_SCREENSAVER = XInternAtom(dpy, "SCREENSAVER", False); + XA_SCREENSAVER_VERSION = XInternAtom(dpy, + "_SCREENSAVER_VERSION", False); + XA_DEACTIVATE = XInternAtom(dpy, "DEACTIVATE", False); + info.info.x11.unlock_func(); + } + + if (!useit) return; + + time(&now); + if (!(lastpoll - now)) { + return; + } + lastpoll = now; + + info.info.x11.lock_func(); + dpy = info.info.x11.display; + if (!dpy) { + useit = 0; + info.info.x11.unlock_func(); + return; + } + + root = RootWindowOfScreen(DefaultScreenOfDisplay(dpy)); + if (!XQueryTree(dpy, root, &tmp, &parent, &kids, &nkids)) { + useit = 0; + info.info.x11.unlock_func(); + return; + } + if (root != tmp || parent || !(kids && nkids)) { + useit = 0; + info.info.x11.unlock_func(); + return; + } + + win = 0; + for (i = 0; i < nkids; i++) { + Atom type; + int format; + unsigned long nitems, bytesafter; + char *v = 0; + int status; + + XSync(dpy, False); + old_handler = XSetErrorHandler(BadWindow_ehandler); + if (XGetWindowProperty(dpy, kids[i], + XA_SCREENSAVER_VERSION, 0, 200, + False, XA_STRING, &type, &format, + &nitems, &bytesafter, + (unsigned char **)&v) == Success) { + XSetErrorHandler(old_handler); + if (v) XFree(v); /* don't care */ + if (type != None) { + win = kids[i]; + break; + } + } + XSetErrorHandler(old_handler); + } + XFree(kids); + if (!win) { + useit = 0; + info.info.x11.unlock_func(); + return; + } + + ev.xany.type = ClientMessage; + ev.xclient.display = dpy; + ev.xclient.window = win; + ev.xclient.message_type = XA_SCREENSAVER; + ev.xclient.format = 32; + memset(&ev.xclient.data, 0, sizeof(ev.xclient.data)); + ev.xclient.data.l[0] = XA_DEACTIVATE; + ev.xclient.data.l[1] = 0; + ev.xclient.data.l[2] = 0; + (void)XSendEvent(dpy, win, False, 0L, &ev); + XSync(dpy, 0); + info.info.x11.unlock_func(); +} +