pax_global_header00006660000000000000000000000064132374147630014524gustar00rootroot0000000000000052 comment=417b7b9442d95dd1ef20165764421a22d65006e4 schismtracker-20180209/000077500000000000000000000000001323741476300146355ustar00rootroot00000000000000schismtracker-20180209/.gitignore000066400000000000000000000002311323741476300166210ustar00rootroot00000000000000*.swp Makefile.in aclocal.m4 autom4te.cache/ build/ buildx86/ buildx64/ compile config.guess config.h.in config.sub configure depcomp install-sh missing schismtracker-20180209/.travis.yml000066400000000000000000000040301323741476300167430ustar00rootroot00000000000000language: c os: - linux - osx env: - ARTIFACT_FILE="$TRAVIS_BUILD_DIR/build/schismtracker-$TRAVIS_TAG-$TRAVIS_OS_NAME.tar.gz" matrix: include: - os: linux - os: linux dist: trusty - os: osx osx_image: beta-xcode6.1 - os: osx osx_image: beta-xcode6.2 - os: osx osx_image: beta-xcode6.3 - os: osx osx_image: xcode6.4 - os: osx osx_image: xcode7 - os: osx osx_image: xcode7.1 - os: osx osx_image: xcode7.2 - os: osx osx_image: xcode7.3 - os: osx osx_image: xcode8 addons: apt: packages: - libsdl1.2-dev before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install sdl; fi script: - autoreconf -i - mkdir -p build - cd build - sh ../configure - make - grep '^Schism Tracker' <(./schismtracker --version) after_success: - cd .. - mkdir -p schismtracker - | cp build/schismtracker README.md COPYING docs/configuration.md \ sys/posix/schismtracker.1 schismtracker/ - | if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then echo "TODO: macosx app" else cp sys/fd.org/schism.desktop schismtracker/ fi - tar -cvzf "$ARTIFACT_FILE" schismtracker notifications: email: on_success: change on_failure: change deploy: provider: releases api_key: secure: K4vJ1+jbWBRSFcHOAiDNgJ2T0pKbF3ZaEIa7qPXGznE1dYnORndTVz29FBo8/TBhiZEyEPwdOik/CbOp8HhSa1QmLvsRCBTsN/JHquV4eN3Yyvu2Rmj+M7Krj/4zoPA6j00D7uzfrknnAAPpacN3OjhSZNxr9hF3FFcjbP4E7Sa2ixHSuKWAzqm7SayxE6rAi9siDf+QyXnQEgyYZQvVfZ29/7YjYef0o+RGuau9V8ygVx8Ul109dESU0PyLqr785hJA5tIUCwMKvXKQPmRXGNuL7DgYN/MvbVS/GS5gStO8VjEGosABO1f+rVrI6PToS9ffYyG2Mc/KP4orWe6DSGcvhY8u1M6XTznLZOROyPEF9swywkcHIQfKa8smvwW/w/XDgjYBU6ixK+sP9pP/fXGZsQ5mYW8O6IaqeL9zS9qFqjBNiCJEavki5u9FNafEf+tx+oyC2vF0M6kDTL1uWX1fHeg7BayFFoboQ4PFQ1EEx4hf2TY8Pcdbz4BwRI6CJO/Z4QGET6/Dml9ca4PTFxkh5nFkEo7JS9/t+eokPz5Rn4UphjE6MI8YPaNV5D/Ouh9JmXsJ9KjDeoaj7gUId14OJIW4qef5h0FrUJ0CIXej2vOTkGiZLqf/m3t/EKuZxpDV4vL0n/DgctnHpjcyIRJC2/pHkMAnHM0BVbfxu6E= file: "$ARTIFACT_FILE" skip_cleanup: true on: tags: true schismtracker-20180209/AUTHORS000066400000000000000000000055061323741476300157130ustar00rootroot00000000000000Written by Storlek . 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. - "Atlantic" is from an ooooold version of IT. - "Purple Motion" was roughly copied from an Imago Orpheus palette. - "Why Colors?" is loosely based on the like-named FT2 palette. - "Industrial" was created by Storlek. - "Kawaii" was co-designed by Storlek and mml. - "FX 2.0" was supplied by Virt. Tim Douglas maintained the original Mac OS X packages, and supplied patches fixing a handful of endianness issues. The Schism Tracker logo was designed by delt. The player engine was originally based on Modplug, which was written by Olivier Lapicque , with additional programming by Markus Fick (spline mixing, fir-resampler) and Adam Goode (endian-ness and char fixes). Further changes to the player code were back-ported from the OpenMPT project . Ben de Graaff rewrote a large portion of libmodplug in C. Some file-loading mechanisms, in particular large portions of the IMF loader, as well as many of the tracker-identifying heuristics, have been adapted from xmp by Claudio Matsuoka and Hipolito Carraro Jr . Tremor logic, and possibly some other effect handling, stolen from DUMB by Ben Davis, Robert J Ohannessian and Julien Cugniere . IMF note slide effect processing taken from Imago Orpheus by Lutz Röder (and then rewritten in C). Other portions of file loading and playing code has been liberally adapted from Mikmod by Raphael Assenat and Miodrag Vallat. Joel Yliluoma (Bisqwit) implemented Adlib support and Scream Tracker sample loading, and also contributed some MIDI-related code. fmopl (OPL2-emulator used in Dosbox and MAME) was written by Jarek Burczynski. Predefined Adlib MIDI patches taken from dro2midi, originally extracted from Creative Labs' MIDI player (PLAY.EXE). Some (small) portions of code have been borrowed from Cheesetracker, written by Juan Linietsky . Various Amiga OS fixes by Juha Niemimäki . Win32 Mixer by Gargaj/CNS . Michael Chen improved the software scaler quite a bit. Some little fixes by ToastyX . Wii ISFS filesystem support code from ftpii by Joseph Jordan . Wii Homebrew Browser icon by pbsds. Path manipulation code taken from bash. See https://github.com/schismtracker/schismtracker/graphs/contributors for a list of additional contributors. schismtracker-20180209/COPYING000066400000000000000000000431101323741476300156670ustar00rootroot00000000000000 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. schismtracker-20180209/INSTALL000066400000000000000000000012451323741476300156700ustar00rootroot00000000000000This is a fairly typical autotools package. In a nutshell, if you're using a roughly Unix-ish environment: 0.) autoreconf -i You only need this if you've fetched this package from the repo and lack ./configure in the top-level source directory. 1.) ./configure Want plenty more options than you need? ./configure --help 2.) make This should create a ./schismtracker binary, which you can move around to wherever it makes you happiest. 3.) make install If you want it installed! You likely want sudo for this. 4.) happy tracking! For more detailed instructions, peruse the files in the 'docs' directory. schismtracker-20180209/Makefile.am000066400000000000000000000212411323741476300166710ustar00rootroot00000000000000## vi:set bd=syn\ make: for elvis AUTOMAKE_OPTIONS = foreign dist-bzip2 no-dist-gzip ## NOTE: helptexts should be in the same order as the enum in page.h helptexts = \ helptext/global-keys \ helptext/copyright \ helptext/info-page \ helptext/instrument-list \ helptext/message-editor \ helptext/midi-output \ helptext/orderlist-pan \ helptext/orderlist-vol \ helptext/pattern-editor \ helptext/adlib-sample \ helptext/sample-list fonts = \ font/default-lower.fnt \ font/default-upper-alt.fnt \ font/default-upper-itf.fnt \ font/half-width.fnt icons = \ icons/appIcon.icns \ icons/moduleIcon.icns \ icons/schism-file-128.png \ icons/schism-icon-128.png \ icons/schism-icon-16.png \ icons/schism-icon-192.png \ icons/schism-icon-22.png \ icons/schism-icon-24.png \ icons/schism-icon-32.png \ icons/schism-icon-36.png \ icons/schism-icon-48.png \ icons/schism-icon-64.png \ icons/schism-icon-72.png \ icons/schism-icon-96.png \ icons/schism-icon.svg \ icons/schism-itf-icon-128.png \ icons/schism-itf-icon-16.png \ icons/schism-itf-icon-192.png \ icons/schism-itf-icon-22.png \ icons/schism-itf-icon-24.png \ icons/schism-itf-icon-32.png \ icons/schism-itf-icon-36.png \ icons/schism-itf-icon-48.png \ icons/schism-itf-icon-64.png \ icons/schism-itf-icon-72.png \ icons/schism-itf-icon-96.png \ icons/schism-itf-icon.svg \ icons/it_logo.png \ icons/schism_logo.png \ icons/schismres.ico sysfiles = \ sys/fd.org/autopackage.apspec \ sys/fd.org/itf.desktop \ sys/fd.org/schism.desktop \ sys/macosx/Schism_Tracker.app/Contents/Info.plist \ sys/macosx/Schism_Tracker.app/Contents/PkgInfo \ sys/macosx/Schism_Tracker.app/Contents/Resources/AppSettings.plist \ sys/macosx/Schism_Tracker.app/Contents/Resources/appIcon.icns \ sys/macosx/Schism_Tracker.app/Contents/Resources/moduleIcon.icns \ sys/win32/schism.nsis \ sys/wii/schismtracker/icon.png \ sys/wii/schismtracker/meta.xml \ sys/sdl/README scripts = \ scripts/bin2h.sh \ scripts/build-font.sh \ scripts/genhelp.py \ scripts/itf2half.py \ scripts/half2itf.py \ scripts/itcfg.py \ scripts/itmidicfg.py \ scripts/lutgen.c \ scripts/palette.py EXTRA_DIST = \ include/auto/README \ $(helptexts) \ $(fonts) \ $(icons) \ $(sysfiles) \ $(scripts) bin_PROGRAMS = schismtracker noinst_HEADERS = \ include/auto/logoit.h \ include/auto/logoschism.h \ include/auto/schismico.h \ include/charset.h \ include/clippy.h \ include/cmixer.h \ include/config-parser.h \ include/disko.h \ include/dmoz.h \ include/draw-char.h \ include/event.h \ include/fmopl2.h \ include/fmopl3.h \ include/fmt.h \ include/fmt-types.h \ include/headers.h \ include/it_defs.h \ include/it.h \ include/log.h \ include/midi.h \ include/osdefs.h \ include/page.h \ include/pattern-view.h \ include/precomp_lut.h \ include/sample-edit.h \ include/sdlmain.h \ include/slurp.h \ include/sndfile.h \ include/snd_fm.h \ include/snd_gm.h \ include/song.h \ include/tables.h \ include/tree.h \ include/util.h \ include/version.h \ include/vgamem-scanner.h \ include/video.h \ sys/wii/isfs.h \ sys/wii/certs_bin.h \ sys/wii/su_tik_bin.h \ sys/wii/su_tmd_bin.h \ sys/win32/wine-ddraw.h dist_man_MANS = sys/posix/schismtracker.1 desktopdir = $(datadir)/applications desktop_DATA = sys/fd.org/schism.desktop appicondir = $(datadir)/pixmaps appicon_DATA= icons/schism-icon-128.png noinst_SCRIPTS = $(scripts) nodist_schismtracker_SOURCES = \ auto/default-font.c \ auto/helptext.c CLEANFILES = \ auto/default-font.c \ auto/helptext.c auto/default-font.c: Makefile.am scripts/bin2h.sh scripts/build-font.sh $(fonts) sh $(srcdir)/scripts/build-font.sh $(srcdir) $(fonts) >$@ auto/helptext.c: Makefile.am scripts/genhelp.py $(helptexts) $(PYTHON) $(srcdir)/scripts/genhelp.py $(srcdir) $(helptexts) >$@ if USE_X11 files_x11 = sys/x11/xscreensaver.c sys/x11/xkb.c cflags_x11 = -DUSE_X11 if USE_XV files_x11 += sys/x11/xv.c cflags_x11 += -DUSE_XV endif endif if USE_ALSA files_alsa = sys/alsa/init.c sys/alsa/volume-alsa.c sys/alsa/midi-alsa.c if USE_ALSA_DLTRICK cflags_alsa=-DUSE_ALSA -DUSE_DLTRICK_ALSA else cflags_alsa=-DUSE_ALSA lib_asound=-lasound endif endif if USE_OSS files_oss = sys/oss/volume-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_WIN32 files_win32 = \ sys/win32/osdefs.c \ sys/win32/slurp-win32.c \ sys/win32/volume-win32mm.c \ sys/win32/midi-win32mm.c \ sys/win32/filetype.c \ sys/win32/localtime_r.c cflags_win32=-I$(srcdir)/sys/win32 lib_win32=-lwinmm endif if HAVE_WINDRES ## use today's date if we didn't get a commit date from git if HAVE_GIT wrcflags_version = -DWRC_VERSION=0,`echo '$(PACKAGE_VERSION)' | sed 's/\(....\)\(..\)\(..\).*/\1,\2,\3/'` else wrcflags_version = -DWRC_VERSION=0,`date +%Y%m%d | sed 's/\(....\)\(..\)\(..\).*/\1,\2,\3/'` endif ## --use-temp-file is needed to work around stupid bugs WRCFLAGS = --use-temp-file -I. -I$(srcdir) $(cflags_version) $(wrcflags_version) .rc.$(OBJEXT): $(WINDRES) $(WRCFLAGS) -i $< -o $@ files_windres=sys/win32/schismres.rc sys/win32/schismres.$(OBJEXT): icons/schismres.ico config.h Makefile.am endif HAVE_WINDRES if USE_WII files_wii=sys/wii/isfs.c sys/wii/osdefs.c cflags_wii=-I$(srcdir)/sys/wii endif if USE_MACOSX files_macosx = \ sys/macosx/macosx-sdlmain.m \ sys/macosx/ibook-support.c \ sys/macosx/midi-macosx.c \ sys/macosx/volume-macosx.c \ sys/macosx/osdefs.c endif if USE_NETWORK cflags_network=-DUSE_NETWORK endif ## Replacement functions for crappy systems files_stdlib = if NEED_ASPRINTF files_stdlib += sys/stdlib/asprintf.c endif if NEED_VASPRINTF files_stdlib += sys/stdlib/vasprintf.c endif if NEED_MEMCMP files_stdlib += sys/stdlib/memcmp.c endif if NEED_STRPTIME files_stdlib += sys/stdlib/strptime.c endif if NEED_MKSTEMP files_stdlib += sys/stdlib/mkstemp.c endif ## aaaaaaaaahhhhhhhhhhhhhhhhhhh!!!!!!!1 schismtracker_SOURCES = \ schism/page_blank.c \ schism/charset.c \ schism/sample-edit.c \ schism/dmoz.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/volume-core.c \ schism/disko.c \ schism/page_loadinst.c \ schism/mplink.c \ schism/clippy.c \ schism/page_loadmodule.c \ schism/page_vars.c \ schism/palettes.c \ fmt/compression.c \ fmt/generic.c \ fmt/mmcmp.c \ fmt/669.c \ fmt/aiff.c \ fmt/ams.c \ fmt/au.c \ fmt/f2r.c \ fmt/far.c \ fmt/imf.c \ fmt/it.c \ fmt/iti.c \ fmt/its.c \ fmt/liq.c \ fmt/mdl.c \ fmt/med.c \ fmt/mf.c \ fmt/mid.c \ fmt/mod.c \ fmt/mt2.c \ fmt/mtm.c \ fmt/mus.c \ fmt/ntk.c \ fmt/okt.c \ fmt/pat.c \ fmt/raw.c \ fmt/s3i.c \ fmt/s3m.c \ fmt/sfx.c \ fmt/stm.c \ fmt/ult.c \ fmt/wav.c \ fmt/xi.c \ fmt/xm.c \ schism/util.c \ schism/page_midi.c \ schism/draw-char.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/keyboard.c \ schism/page_samples.c \ schism/itf.c \ schism/page_orderpan.c \ schism/page_waterfall.c \ schism/midi-core.c \ schism/midi-ip.c \ schism/audio_playback.c \ schism/config-parser.c \ schism/audio_loadsave.c \ schism/draw-misc.c \ schism/fakemem.c \ schism/version.c \ player/csndfile.c \ player/mixutil.c \ player/equalizer.c \ player/mixer.c \ player/filters.c \ player/fmopl2.c \ player/fmopl3.c \ player/fmpatches.c \ player/sndmix.c \ player/opl-util.c \ player/snd_fm.c \ player/effects.c \ player/snd_gm.c \ player/tables.c \ $(files_macosx) \ $(files_alsa) \ $(files_oss) \ $(files_win32) \ $(files_x11) \ $(files_stdlib) \ $(files_mmap) \ $(files_wii) \ $(files_windres) # have version.o rely on all files schism/version.$(OBJEXT): $(filter-out schism/version.$(OBJEXT),$(schismtracker_OBJECTS)) $(HEADERS) cflags_fmopl=-DHAS_YM3812=1 -DHAS_Y8950=0 -DHAS_YM3526=0 AM_CPPFLAGS = -D_USE_AUTOCONF -D_GNU_SOURCE -I$(srcdir)/include -I. AM_CFLAGS = $(SDL_CFLAGS) $(cflags_alsa) $(cflags_oss) \ $(cflags_network) $(cflags_x11) $(cflags_fmopl) \ $(cflags_version) $(cflags_win32) $(cflags_wii) AM_OBJCFLAGS = $(AM_CFLAGS) schismtracker_DEPENDENCIES = $(files_windres) schismtracker_LDADD = $(lib_asound) $(lib_win32) $(SDL_LIBS) $(LIBM) schismtracker-20180209/NEWS000066400000000000000000000332621323741476300153420ustar00rootroot0000000000000020120105 - Happy New Year again! Not much new. (Perhaps that'll change soon?) Miscellaneous: - Added "invert_home_end" option on the pattern editor to make the keys act more like FT2. There is no UI switch for this option. - Fixed a couple quirky segfaults, most of which were unlikely to happen unless you were looking for them. - Fixed a bug that cropped up that broke the mouse in dialogs. - Small fixes to the IMF and S3M loaders. - Removing the last envelope node adjusts the loop points (as it should!) - A dialog now warns you if you are about to export an empty file (i.e., nothing in the orderlist) - Added a separate glob pattern, listing only WAV and AIFF files by default - Export shows file size and duration when it finishes ============================================================================== 20110101 - Happy New Year! This is mostly for Windows users, as there was a huge file-corruption problem which I hadn't noticed, plus a couple other small changes. Aside from that, nothing major since last month. Windows-related: - Fixed a MASSIVE problem with file saving code that destroyed any files it wrote! (because of the stupid distinction between binary and text files, argh!!) - Fixed another tangentially related bug which stuffed increasing numbers of newlines in the config file when saving. As a side effect the config file will appear trashed in Notepad; just use something more competent (such as Wordpad) and it'll be fine. - Default directory if you haven't saved your config is now "My Documents" rather than "Application Data". The config file / fonts / etc. are still in the same place. - Packages are now bundled with a different sdl.dll, which should fix some strange bugs, such as producing "ding" noises when alt-keys are pressed Miscellaneous: - The --diskwrite flag wasn't mentioned anywhere in the manpage. Now it is! - Fixed "stacking up" on Ctrl-F2 dialog with multiple keypresses ============================================================================== 20101128 - Forgot the manpage with the previous build. Whoops! ============================================================================== 20101127 - Another massive update with lots of big changes! As it turns out, the 20100202 build was sort of broken, and I was well into tearing apart various huge chunks of code by the time the first bug reports surfaced that indicated that something wasn't right. So, I just reverted the download links to point back to the 20100101 build with the expectation that I'd be able to pull everything together fairly soon... but then Stuff Happened and then Schism Tracker sort of got shoved to the back burner for a while, and now it's almost December. Oops. - As you might have noticed, the website is changing significantly. Pardon the dust, and please do report any troubles. :) Miscellaneous: - MAJOR structural changes to the code: all C++ removed, many variables and functions renamed, and directory layout rearranged. Apologies to anyone maintaining separate patch sets! :) - Manpage added for 'nix systems (mostly for Debian's sake) - Command line option parsing overhauled; the old +f and +p options have been replaced with -F and -P respectively. - New command-line batch rendering mode: schismtracker file.it --diskwrite file.wav This works with both the .wav and .aiff writers (based on file extension) and will also render multichannel if %c is in the output filename. - Song length calculation improved somewhat - Sharp/flat display setting is now honored in preferences (previously, it was only "half applied" which led to the option potentially displaying the opposite of the actual setting at startup) File loading: - Due to the restructuring, a handful of obscure file formats are no longer supported. If you're missing support for a format, post several test files on /scdev/ and I'll write a new loader. (From what I can tell, most of the remaining loaders that I hadn't rewritten were fairly broken anyway) - Stereo sample flag ignored when loading old IT files (it was meaningless) - Fixed dumb bug in the AIFF/8SVX sample loader that caused a crash when reading a truncated or corrupted file - MOD 8xx effect import fixed, it was screwing up the values in some inexplicable way. (thanks Saga Musix) - Slight hack added to S3M loader to discard SC0 and SD0 effects - Subtle old-effects tremolo quirk implemented (you won't notice) - MPT detect is slightly more lenient - Raw sample loader truncates large samples, instead of loading nothing - Fixed potential crash bug in the IT sample decompression code when loading truncated or otherwise corrupt files - Fixed a potential crash bug in the MTM loader - Several improvements to XI loading, and instrument loading in general - XM instruments with undefined loop type 3 are treated as ping-pong (fixes some apparently malformed files which play properly in FT2) - Fixed a subtle bug when importing pitch slide effects into the volume column. This mostly affected XM files with both volume and effect column data. - "Embed MIDI data" switch is now always enabled / disabled as appropriate when loading a song, and MIDI output settings are no longer reset when a file does not provide its own settings. - MIDI output settings are blanked when loading old IT files, to prevent stray Zxx effects from interfering with playback, thus making unnecessary the former destructive workaround of deleting the effects themselves. File saving / rendering: - All low-level file output code rewritten (both file save and export) - New S3M writer which actually assigns Adlib channels correctly and shows warnings for unsupported features (like it always SHOULD have done). - XM/MOD save support removed, sorry. If you want them, get Milky Tracker. (Or alternately, describe to me why an IT clone should provide support for those formats, and why they would be of benefit...) - File saving doesn't fail if the file already existed (was broken on Wii, and maybe also Windows) - Overwriting an existing file will *attempt* to preserve permissions - File output disallows overwriting a file without write permission - Complete overhaul to multi-channel export: select MWAV or MAIFF format to split by channel (entering a directory name won't work anymore) - Completely blank channels are no longer saved when diskwriting per channel - Multi-channel export replaces %c in the filename with the channel number - Pattern-to-sample doesn't stop playback (note: this royally confuses Adlib instruments, so don't try it :) - Ctrl-Shift-O key added to write each channel to a separate sample - Ctrl-O, Ctrl-Shift-O, and Ctrl-B added to pattern editor - All (?) changes should now mark the file as modified - Edit history (timestamp data) is now saved along with IT files. This was written by Impulse Tracker, and is also supported by recent versions of OpenMPT. Although it isn't yet viewable from within the tracker, there is a script in the repository to output this data. Player: - SAy effect was incorrectly applying offset to new notes - Channel panning should "stay put" after panbrello effects - Channel volume (Mxx/Nxx) fixed to NOT affect background notes (!) - Sample vibrato code rewritten for better accuracy - Vibrato waveform resets on new notes - Oxx with out-of-range offsets and Old Effects off fixed to play like Impulse Tracker (ignore effect and play from the start of the sample) - Various strange bugs related to Gxx and multisampled instruments fixed - Incoming MIDI start/continue messages (FA and FC) are now handled User interface: - A "+" indicator is displayed next to the filename when the song has unsaved changes - Status line now shows how many times a song has looped - Minor changes to the menu - Infopage technical view was showing incorrect data sometimes - Tab key behavior made much more logical on midi input/output screens - Shift-F1 on the midi input screen will switch to midi output - Reverted some dialog behavior that partially broke text input in the previous build. (Sorry!) - In the pattern editor, the '8' key on number pad now plays the row as expected. (This has been inexplicably broken since 2006!) - 'D' properly sends keyjazz note-offs (most obvious on instrument list) - Fixed bug preventing midi pitch bend from ever being input in the pattern editor (maybe!) - Fixed horribly broken help text, which was sometimes cutting off the text or even crashing in various circumstances - Caught potential crash caused by attempting to draw playback marks on broken instrument envelopes (such as those written by MPT) - Right click only pops up main menu when a dialog is not active ============================================================================== 20100202 (Groundhog Day) - BIG update -- lots of stuff added this month! File loading/saving: - MMCMP decompression fixed for big-endian systems - Fixed a crash bug and some problems with XM and AIFF on 64-bit - Replaced loaders: FAR, MDL, ULT, OKT - New loader: MUS (Doom music) - Some fixes to XM and IMF envelope behavior; XM envelopes should now load just like IT - XM loader was leaking a ton of memory by allocating space for each sample twice, whoops - Added title scanner for MoonFish files, because why not :) - Relative paths on the command line (e.g. schismtracker ./file.it) are changed to absolute paths. This fixes a long-standing bug with the file browsers getting "stuck" inside the current directory. - Font path defined in the config file can be an absolute path residing outside of ~/.schism/fonts now, so it's possible, for example, to share the same font.cfg between Schism Tracker and Impulse Tracker. - Path normalizing code rewritten, and now actually works correctly - Module browser remembers cursor position (this got broken when filename pattern matching was added) - Changed the formatting for printing information when loading songs: less "cramped", more IT-like Pattern editor: - More template changes to better match IT behavior - Enter key turns template mode off (except with Notes Only templates) - Multi-cursor only shows when shift is not being held down (for block selections) - Note cut/off/fade and clear now correctly wipe all masked fields in all affected channels - Some behavior was erroneously dependent on the specific template mode - PgUp from the last row of a pattern will place the cursor on the previous major highlight, even if the highlight doesn't line up with the pattern size. - Multichannel dialog layout and cursor alignment changed to match Impulse Tracker - Block swap (Alt-Y) wasn't recording the pattern state for undo. Sample list: - New feature: press Alt-Shift-Z on the sample list to select from 128 built-in Adlib MIDI patches! - Host instrument dialog defaults to No when an instrument containing the sample already exists (not QUITE the same as Impulse Tracker, but maybe this is better?) - When creating a host instrument, the number of the instrument used is displayed in the status line - Anything that "generates" a sample (Alt-P, Alt-Y, Alt-Z, Alt-Shift-Z) will prompt for a host instrument if instrument mode is enabled. - Sample modification keys (reverse, sign flip, amplify, etc.) are disabled with Adlib samples. - Quality convert fixed to show the confirmation dialog with stereo samples, although changing quality without converting the data is unimplemented (and probably would not be very useful, for that matter) - Sample-loading page fixed to always show the current sample number/name at the top of the screen (was showing the current instrument if instrument mode was enabled) - I was wrong: in Impulse Tracker, F8 never clears the dots that show what samples have been played; *starting* playback does that. Fixed. - Fixed some edge-case bugs with Alt-Ins/Del on the sample list Player: - Modplug's extensions to S9x removed (they were mostly broken anyway, and no one seemed to notice) - Fixed a player bug with the handling of S6x and SDx effects on the same row - Envelope carry behavior changed to reset the envelope regardless of carry if a new note is played after a note-off - Adlib mixing volume amplified due to request from Manwe. (Sorry this took so long... it was *really* easy after I found the right place to do it, haha) - Changed internal handling of arpeggio slightly Miscellaneous: - Copyright text moved off of the log page to a new help screen, accessible by pressing F1 at the startup dialog (or on Ctrl-F11) - Several previously undocumented keys added to help - Some other internal changes with help text; should be easier to keep it up-to-date in the future - Alt-P and Alt-N keys on instrument list note translation table fixed - Upper value limit fixed for entering sample numbers on note translation table - Dialog changes: 'c' and 'o' keys to select OK/Cancel - Global keys that open dialogs (Ctrl-N, Ctrl-P, etc.) show the dialog on key-down, instead of key-up - In classic mode, Schism Tracker will make the clicking sound that IT produced when initializing the sound card. - Audio device setup code rewritten - New config file parameter: [Audio] driver=alsa:hw:2 (format is driver:device) - Mouse behavior is (hopefully) more consistent and expected ============================================================================== For older stuff, see http://schismtracker.org/hg/file/20100202/NEWS schismtracker-20180209/README.md000066400000000000000000000034071323741476300161200ustar00rootroot00000000000000# Schism Tracker Schism Tracker is a free and open-source reimplementation of [Impulse Tracker](https://github.com/schismtracker/schismtracker/wiki/Impulse-Tracker), a program used to create high quality music without the requirements of specialized, expensive equipment, and with a unique "finger feel" that is difficult to replicate in part. The player is based on a highly modified version of the [Modplug](https://openmpt.org/legacy_software) engine, with a number of bugfixes and changes to [improve IT playback](https://github.com/schismtracker/schismtracker/wiki/Player-abuse-tests). Where Impulse Tracker was limited to i386-based systems running MS-DOS, Schism Tracker runs on almost any platform that [SDL](http://www.libsdl.org/) supports, and has been successfully built for Linux, Mac OS X, Windows, FreeBSD, AmigaOS, BeOS, and even [the Wii](http://www.wiibrew.org/wiki/Schism_Tracker). Schism will most likely build on *any* architecture supported by GCC4 (e.g. alpha, m68k, arm, etc.) but it will probably not be as well-optimized on many systems. See [the wiki](https://github.com/schismtracker/schismtracker/wiki) for more information. ![screenshot](http://schismtracker.org/screenie.png) ## Download The latest stable builds for Windows, macOS, and Linux are available from [the releases page](https://github.com/schismtracker/schismtracker/releases). Builds can also be installed from Homebrew on macOS and from some distro repositories on Linux, but these versions may not have the latest bug fixes and enhancements. Older builds for other platforms can be found on [the wiki](https://github.com/schismtracker/schismtracker/wiki). ## Compilation See the [docs/](https://github.com/schismtracker/schismtracker/tree/master/docs) folder for platform-specific instructions. schismtracker-20180209/appveyor.yml000066400000000000000000000125231323741476300172300ustar00rootroot00000000000000environment: matrix: - arch: x86_64 bits: 64 - arch: i686 bits: 32 install: - ps: | [Environment]::SetEnvironmentVariable("MSYS2_PATH_TYPE", "inherit", "Machine") $arch = $env:Arch $bits = $env:Bits $msys = "msys$bits" $mingw = "mingw$bits" $mingwUpper = $mingw.ToUpper() $buildFolder = $env:APPVEYOR_BUILD_FOLDER $tag = $env:APPVEYOR_REPO_TAG_NAME $posixBuildFolder = $buildFolder -Replace '\\', '/' $env:PATH="C:\$msys\$mingw\bin;C:\$msys\usr\bin;$env:PATH" $artifact = "schismtracker-$tag-win$bits.zip" function bash($command, $dieOnError = $true) { "" Write-Host $command & "C:\$msys\usr\bin\sh.exe" --login -c "MSYSTEM=$mingwUpper . /etc/profile && cd $posixBuildFolder && $command" if ($LASTEXITCODE -eq 0) { Write-Host "'$command' succeeded. While its output might be red, it exited with '0'." -ForegroundColor Green } else { Write-Host "'$command' failed with exit code $LASTEXITCODE! " -ForegroundColor Red -NoNewline if ($dieOnError) { Write-Host "Exiting." -ForegroundColor Red exit $LASTEXITCODE } else { "Continuing." } } } # 32-bit MSYS2 is not offered on AppVeyor yet, so we need to install it. if ($arch -eq "i686") { Write-Host "Installing 32-bit MSYS2..." -ForegroundColor Cyan # download installer $zipPath = "$($env:USERPROFILE)\msys2-i686-latest.tar.xz" $tarPath = "$($env:USERPROFILE)\msys2-i686-latest.tar" "Downloading MSYS2 installation package..." (New-Object Net.WebClient).DownloadFile('http://repo.msys2.org/distrib/msys2-i686-latest.tar.xz', $zipPath) Write-Host "Unzipping $zipPath..." 7z x $zipPath -y -o"$env:USERPROFILE" | Out-Null Write-Host "Untaring $tarPath to C:\msys32\..." 7z x $tarPath -y -oC:\ | Out-Null del $zipPath del $tarPath Write-Host "32-bit MSYS2 installed" -ForegroundColor Green bash "pacman --sync --noconfirm --needed pacman pacman-mirrors" bash "pacman --sync --noconfirm --needed VCS" bash "pacman --sync --noconfirm --needed base-devel" bash "pacman --sync --noconfirm --needed msys2-devel" bash "pacman --sync --noconfirm --needed mingw-w64-$arch-toolchain" } bash "pacman --sync --noconfirm --needed mingw-w64-$arch-SDL" build_script: - ps: | bash "autoreconf --install --include=/$mingw/share/aclocal/" bash "./configure" bash "make" xcopy C:\$msys\$mingw\bin\SDL.dll .\ if ($arch -eq "i686") { xcopy C:\$msys\$mingw\bin\libgcc_s_dw2-1.dll .\ xcopy C:\$msys\$mingw\bin\libwinpthread-1.dll .\ } test_script: - ps: | $exe = "$buildFolder\schismtracker.exe" if (Test-Path $exe) { Write-Host "'$exe' exists. Continuing." -ForegroundColor Green } else { Write-Host "'$exe' does not exist. Exiting." -ForegroundColor Red exit 1 } Start-Process -Wait -FilePath $exe -ArgumentList '--version' $result = Get-Content "stdout.txt" if ($result -Match 'Schism Tracker') { Write-Host "'$exe --version' output 'Schism Tracker'. Success!" -ForegroundColor Green } elseif ([string]::IsNullOrWhiteSpace($result)) { Write-Host "'$exe --version' did not output anything. It might be damaged." -ForegroundColor Red exit 1 } else { Write-Host "'$exe --version' did not output 'Schism Tracker' as expected. The result was:" -ForegroundColor Red Write-Host $result -ForegroundColor Red exit 1 } before_deploy: - ps: | if (Test-Path "schismtracker.exe") { 7z a -tzip "$artifact" "schismtracker.exe" } if (Test-Path "SDL.dll") { 7z a -tzip "$artifact" "SDL.dll" } if (Test-Path "libgcc_s_dw2-1.dll") { 7z a -tzip "$artifact" "libgcc_s_dw2-1.dll" } if (Test-Path "libwinpthread-1.dll") { 7z a -tzip "$artifact" "libwinpthread-1.dll" } if (Test-Path $artifact) { appveyor PushArtifact "$artifact" } on_failure: - dir /s /b > dir.txt - if exist "stdout.txt" 7z a -tzip schismtracker_debug_logs.zip "stdout.txt" > nul - if exist "stderr.txt" 7z a -tzip schismtracker_debug_logs.zip "stderr.txt" > nul - if exist "dir.txt" 7z a -tzip schismtracker_debug_logs.zip "dir.txt" > nul - if exist "config.log" 7z a -tzip schismtracker_debug_logs.zip "config.log" > nul - if exist "configure.ac" 7z a -tzip schismtracker_debug_logs.zip "configure.ac" > nul - if exist "configure" 7z a -tzip schismtracker_debug_logs.zip "configure" > nul - if exist "C:\msys%bits%\mingw%bits%\bin\sdl-config" 7z a -tzip schismtracker_debug_logs.zip "C:\msys%bits%\mingw%bits%\bin\sdl-config" > nul on_finish: - ps: if (Test-Path "schismtracker_debug_logs.zip") { appveyor PushArtifact "schismtracker_debug_logs.zip" } artifacts: - path: '*.zip' notifications: - provider: Email on_build_success: false on_build_failure: false on_build_status_changed: true deploy: release: $(APPVEYOR_REPO_TAG_NAME) description: $(APPVEYOR_REPO_COMMIT_MESSAGE) provider: GitHub auth_token: secure: 3Yck1BxvSwZXduwRGW7QO7jm8D5ozdZwHlqTkCOFdfdWgKIQZXcVAmc/3gmwQ1vs artifact: /.*\.zip/ draft: false prerelease: false on: branch: master appveyor_repo_tag: true schismtracker-20180209/configure.ac000066400000000000000000000270021323741476300171240ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script. dnl Schism Tracker - a cross-platform Impulse Tracker clone dnl copyright (c) 2003-2005 Storlek dnl copyright (c) 2005-2008 Mrs. Brisby dnl copyright (c) 2009 Storlek & Mrs. Brisby dnl copyright (c) 2010-2012 Storlek dnl URL: http://schismtracker.org/ 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 dnl PACKAGE_VERSION will be either "" if not using git, or date of the last git commit in the form YYYYMMDD m4_define([last_git_commit], patsubst(m4_esyscmd([git log -n 1 --date=short --format=format:%cd]), [[^0-9]])) AC_INIT([schismtracker], [last_git_commit]) AC_CONFIG_SRCDIR([schism/main.c]) AM_INIT_AUTOMAKE([-Wall subdir-objects]) AC_CONFIG_HEADERS([config.h]) dnl ----------------------------------------------------------------------- dnl Check for standard programs AC_PROG_CC AM_PROG_CC_C_O AC_PROG_CPP AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_CC_C99 dnl do we have Git AC_CHECK_TOOL([GIT], [git]) AC_SUBST(GIT) AM_CONDITIONAL([HAVE_GIT], [test "x$GIT" != "x"]) dnl Windows poop AC_CHECK_TOOL([WINDRES], [windres]) AC_SUBST(WINDRES) AM_CONDITIONAL([HAVE_WINDRES], [test "x$WINDRES" != "x"]) dnl Necessary for building the internal help. AM_PATH_PYTHON([2.4]) dnl We're using C AC_LANG([C]) dnl check endianness AC_C_BIGENDIAN dnl Check for SDL libs AM_PATH_SDL(1.2.10, , AC_MSG_ERROR([*** SDL version >= 1.2.10 not found.])) dnl Libs AC_CHECK_LIB([dl], [dlopen]) dnl Functions AC_CHECK_FUNCS(strchr memmove strerror strtol strcasecmp strncasecmp strverscmp stricmp strnicmp strcasestr strptime asprintf vasprintf memcmp mmap nice unsetenv dup fnmatch mkstemp) AM_CONDITIONAL([NEED_ASPRINTF], [test "$ac_cv_func_asprintf" = "no"]) AM_CONDITIONAL([NEED_VASPRINTF], [test "$ac_cv_func_vasprintf" = "no"]) AM_CONDITIONAL([NEED_MEMCMP], [test "$ac_cv_func_memcmp" = "no"]) AM_CONDITIONAL([NEED_STRPTIME], [test "$ac_cv_func_strptime" = "no"]) AM_CONDITIONAL([NEED_MKSTEMP], [test "$ac_cv_func_mkstemp" = "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(inttypes.h fcntl.h limits.h signal.h unistd.h sys/param.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 AC_SEARCH_LIBS([powf], [m]) dnl ----------------------------------------------------------------------- dnl Always use x11 by default unless overriden later per-platform dnl (this is stupid, and only needed because the x11 check is dysfunctional) defaultx11=yes dnl CoreMIDI (Mac OS X) AC_MSG_CHECKING(for CoreAudio/CoreMIDI Framework) if echo "$SDL_LIBS" | grep -- -framework >/dev/null 2>&1; then AC_MSG_RESULT(found) dnl frameworks aren't part of sdl... should be a separate variable 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) defaultx11=no else AC_MSG_RESULT(not found) AM_CONDITIONAL([USE_MACOSX], false) AM_CONDITIONAL([am__fastdepOBJC], false) fi 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_WIN32], true) SDL_LIBS="$SDL_LIBS -lwinmm" AC_SUBST(SDL_LIBS) defaultx11=no else AM_CONDITIONAL([USE_WIN32], false) fi dnl Wii crap AC_CHECK_LIB(ogc, IOS_ReloadIOS, libogc_found=yes, libogc_found=no, [-mrvl -L${DEVKITPRO}/libogc/lib/wii]) if test x"$libogc_found" = "xyes"; then AM_CONDITIONAL([USE_WII], true) wii_machdep="-DGEKKO -mrvl -mcpu=750 -meabi -mhard-float" CFLAGS="$CFLAGS $wii_machdep -I${DEVKITPRO}/libogc/include" LIBS="$LIBS $wii_machdep -L${DEVKITPRO}/libogc/lib/wii" SDL_CFLAGS="$SDL_CFLAGS -I${DEVKITPRO}/libogc/include/SDL" SDL_LIBS="$SDL_LIBS -lSDL -lfat -lwiiuse -lbte -logc -lm -lwiikeyboard" AC_SUBST(CFLAGS) AC_SUBST(LIBS) AC_SUBST(SDL_CFLAGS) AC_SUBST(SDL_LIBS) EXEEXT=.elf AC_SUBST(EXEEXT) defaultx11=no else AM_CONDITIONAL([USE_WII], false) fi dnl should probably have some AC_MSG_CHECKING here, but meh if test "x$defaultx11" = "xno"; then if test "x$with_x" = "x"; then AC_MSG_NOTICE([X11 disabled by default; use --with-x if you really want it]) dnl (you don't want it, because it's probably broken anyway) with_x=no fi fi dnl OpenGL crud - it doesn't always exist saved_cppflags=$CPPFLAGS CPPFLAGS="$CPPFLAGS $SDL_CFLAGS" AC_CHECK_HEADER([SDL_opengl.h], [AC_DEFINE([USE_OPENGL], 1, [Define to 1 if SDL's OpenGL works])]) CPPFLAGS=$saved_cppflags dnl ----------------------------------------------------------------------- saved_libs=$LIBS alsa=no AC_CHECK_LIB(asound, snd_seq_open,[alsa=yes]) alsadltrick=no if test "$alsa" = "yes"; then if test "$ac_cv_header_sys_soundcard_h" = "yes"; then alsadltrick=yes LIBS=$saved_libs fi fi AM_CONDITIONAL([USE_ALSA], [test "$alsa" = yes]) AM_CONDITIONAL([USE_ALSA_DLTRICK], [test "$alsadltrick" = yes]) AC_PATH_XTRA() if test "$no_x" = "yes"; then dnl no X11 AM_CONDITIONAL([USE_X11], [false]) AM_CONDITIONAL([USE_XV], [false]) else AM_CONDITIONAL([USE_X11], [true]) SDL_LIBS="$SDL_LIBS -lX11 -lXext" AC_SUBST(SDL_LIBS) LIBS="$LIBS $X_LIBS" AC_SUBST(LIBS) dnl this previously checked X11/extensions/XKB.h, but nothing dnl ever used it (xkb.c still included X11/XKBlib.h even when dnl autoconf reported it as nonexistent -- weird!) AC_CHECK_HEADERS(X11/Xlib.h X11/XKBlib.h) AC_CHECK_LIB(Xv, XvQueryExtension,,,-lX11 -lXext) if test "$ac_cv_lib_Xv_XvQueryExtension" = "yes"; then AC_CHECK_HEADERS(X11/extensions/Xvlib.h,,,[[ #include ]]) if test "$ac_cv_header_X11_extensions_xvlib_h" = "yes"; then SDL_LIBS="$SDL_LIBS -lXv -lXext" AC_SUBST(SDL_LIBS) AM_CONDITIONAL([USE_XV], [true]) elif test "$ac_cv_header_X11_extensions_Xvlib_h" = "yes"; then SDL_LIBS="$SDL_LIBS -lXv -lXext" AC_SUBST(SDL_LIBS) AM_CONDITIONAL([USE_XV], [true]) else AM_CONDITIONAL([USE_XV], [false]) fi else AM_CONDITIONAL([USE_XV], [false]) fi fi dnl asdjklfjklasdfaskdfjklads saved_LIBS="$LIBS" AC_SEARCH_LIBS(socket, socket network) LIBS="$saved_LIBS" if test "x$ac_cv_search_socket" = "xno"; then dnl Windows sucks (I don't even know what this is about, but it apparently works) AC_CHECK_HEADERS(winsock.h winsock2.h) if test "x$ac_cv_header_winsock_h" = "xyes"; then socketlib="-lwsock32" elif test "x$ac_cv_header_winsock2_h" = "xyes"; then socketlib="-lws2_32" fi if test "x$socketlib" = "x"; then echo "*** No sockets for you!" AM_CONDITIONAL([USE_NETWORK], false) else AM_CONDITIONAL([USE_NETWORK], true) SDL_LIBS="$SDL_LIBS $socketlib" AC_SUBST(SDL_LIBS) fi elif test "x$ac_cv_search_socket" = "xnone required"; then dnl free networking AM_CONDITIONAL([USE_NETWORK], true) else SDL_LIBS="$SDL_LIBS $ac_cv_search_socket" AC_SUBST(SDL_LIBS) AM_CONDITIONAL([USE_NETWORK], true) fi #AC_CHECK_LIB(kernel32, GetConsoleMode, SDL_LIBS="$SDL_LIBS -Wl,--subsystem,console") dnl wee... dnl this completely sucks... OBJC=$CC OBJCFLAGS=$CFLAGS AC_SUBST(OBJC) AC_SUBST(OBJCFLAGS) dnl ----------------------------------------------------------------------- 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, system-dependent optimizations (do not use this together with debug or even profiling)]), ADD_OPT=$enableval, ADD_OPT=no) AC_ARG_ENABLE(all-warnings, AS_HELP_STRING([--enable-all-warnings], [Enable ridiculous compiler warnings]), ADD_WARN=$enableval, ADD_WARN=no) AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [Enable debug flags]), ADD_DEBUG=$enableval, ADD_DEBUG=no) AC_ARG_ENABLE(profiling, AS_HELP_STRING([--enable-profiling], [Enable profiling flags (slows things down), debugging (--enable-debug) is also enabled by this]), ADD_PROFILING=$enableval, ADD_PROFILING=no) AC_ARG_ENABLE(ludicrous-mode, AS_HELP_STRING([--enable-ludicrous-mode], [Enable all warnings, and treat as errors]), ADD_LUDICROUS=$enableval, ADD_LUDICROUS=no) AC_ARG_ENABLE(fortify-source, AS_HELP_STRING([--enable-fortify-source], [Build with FORTIFY_SOURCE]), ADD_FORTIFY=$enableval, ADD_FORTIFY=no) dnl fortify needs -O; do this early so ADD_OPT can override with higher -O level if test x$ADD_FORTIFY \!= xno; then CFLAGS="$CFLAGS -O -D_FORTIFY_SOURCE=2" fi dnl place extra optimizations after existing cflags so that they can override dnl override whatever flags might exist by default (-g -O2 usually) if test x$ADD_OPT \!= xno; then if test x$ADD_DEBUG \!= xno || test x$ADD_PROFILING \!= xno; then AC_MSG_NOTICE([You aren't supposed to use --enable-debug or --enable-profiling together with --enable-extra-opt!!]) fi ADD_OPT="-Ofast -g0 -s -fno-exceptions -march=native" CFLAGS="$CFLAGS $ADD_OPT" fi if test x$ADD_LUDICROUS \!= xno; then ADD_WARN=yes CFLAGS="$CFLAGS -Werror" fi dnl ... but put the warnings first, to make it possible to quiet certain dnl warnings if necessary, while still providing most of the benefit if test x$ADD_WARN \!= xno; then ADD_WARN="-Wall -Wextra -Winline -Wshadow -Wwrite-strings -Waggregate-return -Wpacked" ADD_WARN="$ADD_WARN -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wnested-externs" CFLAGS="$ADD_WARN $CFLAGS" fi if test x$ADD_PROFILING \!= xno || test x$ADD_DEBUG \!= xno; then CFLAGS="$CFLAGS -g -O0" OBJCFLAGS="$OBJCFLAGS -g -O0" SDL_LIBS="$SDL_LIBS -g -O0" AC_SUBST(SDL_LIBS) fi if test x$ADD_PROFILING \!= xno; then CFLAGS="$CFLAGS -pg" OBJCFLAGS="$OBJCFLAGS -pg" SDL_LIBS="$SDL_LIBS -pg" AC_SUBST(SDL_LIBS) fi dnl - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - AC_CONFIG_FILES([Makefile]) AC_OUTPUT schismtracker-20180209/docs/000077500000000000000000000000001323741476300155655ustar00rootroot00000000000000schismtracker-20180209/docs/building_on_linux.md000066400000000000000000000105351323741476300216230ustar00rootroot00000000000000# Building on Linux Since Linux is the primary development platform for Schism Tracker, it's probably the easiest to compile on, and those familiar with automake-based projects will find few surprises here. ## Prerequisites You'll need [autoconf](http://www.gnu.org/software/autoconf/), [automake](http://www.gnu.org/software/automake/), [gcc](http://gcc.gnu.org/), [make](http://www.gnu.org/software/make/), [Python](https://www.python.org/) and [LibSDL](http://www.libsdl.org/) *at a minimum*. Additionally, [Git](https://git-scm.com/) is strongly recommended. If all you're planning on doing is building it once, you can just as easily grab the source tarball from the repository and build from that, but having Git installed makes it easier to keep up-to-date, help with debugging, and (if you're so inclined) development. See below for distro-specific instructions on how to get everything installed in order to build Schism Tracker. To get the source: git clone https://github.com/schismtracker/schismtracker.git cd schismtracker && autoreconf -i You can then update your schismtracker source directory by going to this schismtracker directory and running: git pull ## Building Schism Tracker To build Schism Tracker, you should set up a build-directory and compile from there. From the schismtracker directory: mkdir -p build && cd build && ../configure && make The resulting binary `schismtracker` is completely self-contained and can be copied anywhere you like on the filesystem. You can specify custom compiler flags, e.g. to optimize schismtracker stronly, system-dependently: make clean # recompiling needed after changing compiler setting ../configure --enable-extra-opt make -j $(nproc || sysctl -n hw.ncpu || echo 2) The -j flag passed to make enables compilation on multiple threads. For debugging, and other settings, see `../configure --help`. ## Packaging Schism Tracker for Linux systems The `icons/` directory contains icons that you may find suitable for your desktop environment. The `sys/fd.org/schism.desktop` can be used to launch Schism Tracker from a desktop environment, and `sys/fd.org/itf.desktop` can be used to launch the built-in font-editor. ## ALSA problems The configure script should autodetect everything on your system, but if you don't have the ALSA development libraries installed, Schism Tracker won't be built with ALSA MIDI support, even if your SDL libraries include ALSA digital output. See below for information on what packages you should install for your distribution in order to build a full-featured Schism Tracker binary. ## Cross-compiling Win32 Schism Tracker can be built using the mingw32 cross-compiler on a Linux host. You will also need the [SDL MINGW32 development library](http://libsdl.org/download-1.2.php). If you unpacked it into `/usr/i586-mingw32/`, you could use the following to cross-compile Schism Tracker for Win32: mkdir win32-build cd build env SDL_CONFIG=/usr/i586-mingw32/sdl-config \ ../configure --{host,target}=i586-mingw32 --without-x make If you want to build an installer using the [nullsoft scriptable install system](http://nsis.sourceforge.net/), copy some files into your build directory: cd build cp /usr/i586-mingw32/bin/SDL.dll . cp ../COPYING COPYING.txt cp ../README README.txt cp ../NEWS NEWS.txt cp ../sys/win32/schism.nsis . cp ../icons/schismres.ico schism.ico and run the makensis application: makensis schism.nsis ## Distribution-specific instructions Getting the prerequisites covered is fairly straightforward in most Linux distributions. #### Ubuntu / Debian apt-get install build-essential automake autoconf autoconf-archive \ libx11-dev libxext-dev libxv-dev libxxf86misc-dev \ libxxf86vm-dev libsdl1.2-dev libasound2-dev mercurial \ libtool Additionally, for cross-compiling win32 binaries: apt-get install mingw32 mingw32-binutils mingw32-runtime nsis #### Arch Linux pacman -S base-devel mercurial sdl alsa-lib libxv libxxf86vm For cross-compiling win32 binaries: pacman -S mingw-w64-gcc yaourt -S mingw-w64-sdl nsis Note: yaourt isn't strictly necessary, but since `mingw-w64-sdl` and `nsis` are AUR packages, you'll have to build them by hand otherwise or use a different [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers). schismtracker-20180209/docs/building_on_osx.md000066400000000000000000000111211323741476300212650ustar00rootroot00000000000000# Building on OS X Start by installing [Homebrew](http://brew.sh/). Open up the Terminal and paste in the following command. ``` /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ``` After Homebrew has been successfully installed, you need to install `automake`, `autoconf`, `SDL` and `git`. ``` brew install automake autoconf sdl git ``` Now clone the GitHub repo: ``` git clone https://github.com/schismtracker/schismtracker.git ``` Enter the Schismtracker folder and run `autoreconf -i` ``` cd schismtracker autoreconf -i ``` Now you will need to create the `build` -folder, enter it and start `../configure` ``` mkdir -p build cd build ../configure && make ``` Test Schismtracker from the commandline by typing ``` ./schismtracker ``` If it worked, you are ready to start the updating of the **Schism Tracker.app** ## Baking Schism Tracker into an App ready to be put in /Applications If you are in the `build` -folder, discover the `Schism_Tracker.app` subfolder `Contents` and, after creating the `MacOS` -folder, copy the newly built `schismtracker` there - then test the `Schism_Tracker.app` by clicking on it in Finder. Here are the instructions on how to do it (this will open a Finder window showing the `sys/macosx` -folder, where-in you will see the app itself. ``` cd ../sys/macosx/Schism_Tracker.app/Contents/ mkdir MacOS cd MacOS cp ../../../../../build/schismtracker . cd ../../../ open . ``` If this newly baked version of `Schism_Tracker.app` worked, just copy it to your `/Applications` -folder. Enjoy. ## Building for distribution However, if you want to build an application bundle *for distribution*, there's a few potential snags. In particular, SDK versions are backward-incompatible, so you need to make note of what version you're building with; and if you also want to support the dwindling population of PowerPC users, you'll have to build a Universal binary. Plus, since SDL is not normally present on OS X, you'll need to bundle it in with the application. There used to be somewhat lengthy instructions here elaborating on various nuances of installing Fink and managing multiple SDKs, but these have become rather outdated and probably less than thoroughly useful. If anyone has current and first-hand experience with building on OS X which might be helpful to others, please feel free to share. ## Cross-compiling on a Linux host [This page](http://devs.openttd.org/~truebrain/compile-farm/apple-darwin9.txt) has some notes that might be of use in building a cross-compilation toolchain. Building a cross-compiler is *not* an easy process, and will probably take the better part of a day; however, it is definitely possible, and in fact is what I use for compiling the "official" OS X packages. The build process is rather messy, but it goes something like: mkdir -p osx/{x86,ppc} cd osx/x86 env PATH=/usr/i686-apple-darwin9/bin:${PATH} \ {C,CXX,OBJC}FLAGS='-g0 -O2' LDFLAGS=-s \ ../../configure --with-sdl-prefix=/usr/i686-apple-darwin9 \ --{target,host}=i686-apple-darwin9 \ --build-i686-linux env PATH=/usr/i686-apple-darwin9/bin:${PATH} make cd ../ppc env PATH=/usr/powerpc-apple-darwin9/bin:${PATH} \ {C,CXX,OBJC}FLAGS='-g0 -O2' LDFLAGS=-s \ ../../configure --with-sdl-prefix=/usr/powerpc-apple-darwin9 \ --{target,host}=powerpc-apple-darwin9 \ --build-i686-linux env PATH=/usr/powerpc-apple-darwin9/bin:${PATH} make cd .. /usr/i686-apple-darwin9/bin/lipo -create -o {.,x86,ppc}/schismtracker /usr/i686-apple-darwin9/bin/install_name_tool -change \ '@executable_path/../Frameworks/SDL.framework/Versions/A/SDL' \ '@executable_path/sdl.dylib' schismtracker Then copy the `lipo`'ed `schismtracker` and `sdl.dylib` both into the `MacOS/` folder in the bundle, and it Should Work. The versions I have installed are GCC 4.0.1 (SVN v5493), ODCCTools SVN v280, and XCode SDK 3.1.3. This SDK version advertises compatibility with 10.4 at minimum, although people have reported success in running Schism Tracker on 10.3.9. Note: because parts of the debugging information are conspicuously absent, cross-compiling with debugging symbols simply isn't possible. This isn't very likely to be useful anyway; if you can run the program in a debugger, then you're *probably* running OS X, in which case you might as well just build natively and eliminate the hassle. schismtracker-20180209/docs/building_on_windows.md000066400000000000000000000121111323741476300221460ustar00rootroot00000000000000# Building on Windows _This page was based originally on the COMPILE-WIN32.txt file traditionally provided with Schism Tracker sources. It has been rewriten with instructions that use newer tools._ ## Software needed To compile on Windows, the following things are needed: * mingw-gcc * Python * SDL headers and libs * An environment in which to run them, like msys. If you want proper version information, you'll need git installed and in your path too ## Installing MSYS2 and mingw These instructions describe how to install msys2, which includes all of the required packages. ### Get MSYS2 and install it Go to the URL http://msys2.github.io/ and download either the 32bit installer or the 64bit installer. The 32bit download can run on 32bit windows and the 64bit one requires a 64bit Windows. 64bit executables can be created with either of them. Once installed, follow these instructions to get up-to-date files. This process is also described in their web page, so in case of conflict, you might opt to follow their instructions. Run the MSYS2 shell (a start menu shortcut should have been created) and update the pacman package manager: pacman -Sy pacman Close the MSYS2 window. Run it again from the Start menu and update the system: pacman -Syu Close the MSYS2 window. Run it again from the Start menu _(note: the update process can, in some cases, break the start menu shortcut - in which case you may need to look in C:\msys64 (or wherever you installed msys) and run msys2\_shell.cmd)_ and update the rest with: pacman -Su ### Install the toolchains Once you have the shell environment ready, it's time to get the compilers. Execute the following command: pacman -S mingw-w64-i686-toolchain libtool autoconf automake make Also, you need the following specific dependency: pacman -S mingw-w64-i686-SDL If you also want to build for 64bits: pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-SDL You can search for packages with pacman -Ss package descripption ## Compilation MSYS2 installs three shortcut icons, one to run the msys shell, and two more that setup the environment to use either the 32bit compiler or the 64bit compiler. You can also start the 32bit compiler with msys2_shell.cmd -mingw32 and the 64bit compiler with msys2_shell.cmd -mingw64 ### Configure schismtracker to build Open the 32bit or 64bit shell depending on which version you want to build. The steps here only need to be done once, or when configure.ac or the installed package versions change. Go to schismtracker sources root (drive letters are mapped to /x , example C:/ is /c/, D:/ is /d/ ...) Reconfigure it: autoreconf -i _(note: if you get a "possibly undefined macro: AM\_PATH\_SDL" error, you're probably using the standard msys2 shell - either use the mingw start menu shortcuts, or start msys2_shell.cmd with either -mingw32 or -mingw64 as mentioned above)_ If you're planning to build both 32- and 64-bit binaries, you may wish to create the subdirs build32 and build64: mkdir build32 mkdir build64 and then follow the rest of the instructions twice, once with the -mingw32 shell in the build32 subdir, and once with the -mingw64 shell in the build64 subdir. Otherwise just build will do: mkdir build Now move into the build subdir and run the configure script: cd build # or build32 or build64 as appropriate ../configure ### Build and rebuild In order to build and run it, from the appropriate build subdir, run these: make ../schismtracker & ### Compilation problems The configure script should give hints on what is missing to compile. If you've followed the steps, everything should already be in the place, but in case it doesn't, see the config.log file, which reports a detailed output (more than what is seen on the screen) that could help identify what is missing, or which option is not working. ### Debugging When installing the toolchains, the gdb debugger is also installed. Run it from the win32 shell to debug the 32bit exe, or run it from the Win64 shell to debug the 64bit one. ## Prepare the distribution file To distribute the application, it is important to bundle the correct version of the SDL.dll file with the executable. For a 32bit build, the file is located in /msys2_path/mingw32/bin/SDL.dll For a 64bit build, the file is located in /msys2_path/mingw64/bin/SDL.dll The 32bit build also requires the files /msys2_path/mingw64/bin/libgcc_s_dw2-1.dll and /msys2_path/mingw64/bin/libwinpthread-1.dll If you want to reduce the exe size (removing the debugging information), use the following command: _(note: you MUST do this from the same shell than you used to build the executable, as the strip tool is architecture-dependent)_ strip -g schismtracker.exe ## SDL2 notes The current version of schismtracker uses SDL1, but a fork with SDL2 has been made here https://github.com/davvid/schismtracker/tree/laptop-octave In order to build that branch, installing the SDL2 packages AND pkg-config is needed pacman -S mingw-w64-i686-SDL2 mingw-w64-i686-SDL2_gfx mingw-w64-i686-pkg-config mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_gfx mingw-w64-x86_64-pkg-config schismtracker-20180209/docs/configuration.md000066400000000000000000000167511323741476300207700ustar00rootroot00000000000000# Configuration Schism Tracker saves its configuration in a plain-text, [INI-style](http://en.wikipedia.org/wiki/INI_file) file named `config`. Under normal circumstances it should theoretically not be necessary to deal with this file directly. However, there are some options that are not configurable from within Schism Tracker for one reason or another. In these cases, any plain text editor should suffice. The location of this file is dependent on the OS: - **Windows** - `%APPDATA%\Schism Tracker` - **Mac OS X** - `~/Library/Application Support/Schism Tracker` - **AmigaOS 4** - `PROGDIR:` - **Linux/Unix** - `$HOME/.schism/` - **Wii** - Same directory as `boot.elf` (e.g. `sd:/apps/schismtracker`) Aside from `config`, you may also add a `fonts` subdirectory for custom font files. By default, `font.cfg` is automatically loaded on startup, and other `*.itf` files are listed by the built-in font editor (Shift-F12). See [the links page](https://github.com/schismtracker/schismtracker/wiki/Links) for some resources on getting fonts for Schism Tracker. ## Potentially useful "hidden" config options To enable any of these, find the `[section]` in the config file, look for the `key=`, and change the value. If the key doesn't exist, simply add it. #### Video [Video] lazy_redraw=1 width=640 height=400 want_fixed=0 gl_bilinear=1 `lazy_redraw` slows down the framerate when the program isn't focused. This used to be kind of useful when the GUI rendering sucked, and maybe it still is if you're stuck with a painfully slow video card and software rendering, and you also want to have a huge window that isn't active. `width` and `height` are the initial dimensions to use for the window, and the dimensions to return to when toggling fullscreen off. If you are having problems with fullscreen aspect ratio, you can try setting `want_fixed` to 1. This will attempt to "correct" the fullscreen resolution used by Schism. If `gl_bilinear` is zero, OpenGL will not use bilinear filtering when scaling the screen. This has no effect when running at the native resolution of 640x400. #### Backups [General] make_backups=1 numbered_backups=1 When overwriting a `filename.it`, copy the existing file to `filename.it~`. With numbered_backups, write to `filename.it.1~`, `filename.it.2~`, etc. #### Key repeat [General] key_repeat_delay=125 key_repeat_rate=25 Alter the key repeat. "Delay" is how long before keys begin to repeat, "rate" is how long between repeated keystrokes. (Both are in milliseconds.) Above are [Storlek](https://github.com/schismtracker/schismtracker/wiki/Storlek)'s settings, which are very fast but convenient for speed tracking. The *default* repeat delay and rate come from your operating system, so you only need to set this if you like having a different rate for Schism Tracker than you do for the rest of your system. #### Alternate font [General] font=notch.itf Load some other font besides `font.cfg` at startup. This option doesn't really have much of a point, because the file listed is limited to those within the `fonts` directory, and it's just easier to open the font editor, browse fonts, and save to `font.cfg`. #### DJ mode [General] stop_on_load=0 If zero, loading a song when another one is playing will start playing the new song after it is loaded. #### File browser [Directories] module_pattern=*.it\073 *.xm\073 *.s3m\073 *.mtm\073 *.669\073 *.mod Changes what files are presented in the load/save module lists. Use * for all files. For annoying compatibility reasons, semicolons are rewritten as `\x3b` or `\073` when saving. This was formerly named `filename_pattern`; Schism Tracker ignores the old value and comments it out when saving to work around bugs in older versions. sort_with=strcasecmp Alter the sort order. Possible values are `strcmp` (case-sensitive), `strcasecmp` (case-insensitive), and `strverscmp` (case-sensitive, but handles numbers smartly e.g. `5.it` will be listed above `10.it`). #### Keyjazz [Pattern Editor] keyjazz_noteoff=1 keyjazz_write_noteoff=0 keyjazz_repeat=0 If `keyjazz_noteoff` is 1, letting go of a key in the pattern editor will cause a note-off. If using this, you might also want to consider setting `keyjazz_repeat` to 0 in order to avoid inserting multiple notes when holding down keys. If `keyjazz_write_noteoff` is 1, letting go of a key in the pattern editor will also write a note off *if* playback tracing (Ctrl+F) is enabled. #### Pattern editor behavior tweaks [Pattern Editor] mask_copy_search_mode=1 invert_home_end=1 When `mask_copy_search_mode` is set to 1, pressing Enter on a row with no instrument number will search backward in the channel for an instrument and switch to that one. `invert_home_end` changes the order of the Home and End keys to make the cursor move to the first or last row within the channel before moving to the first or last channel. FT2 users might want to enable this. #### Key modifiers [General] meta_is_ctrl=1 altgr_is_alt=1 These alter how modifier keys are interpreted. Mac OS X users in particular might appreciate `meta_is_ctrl`, which allows using the Command/Apple key as the Ctrl modifier within Schism Tracker. `altgr_is_alt` works similarly. #### Audio output [Audio] buffer_size=256 driver=dsp:/dev/dsp1 These settings define the audio buffer size, and which audio device Schism Tracker uses. (The other settings in the `[Audio]` section are configurable from Shift-F1.) `buffer_size` should be a power of two and defines the number of samples in the mixing buffer. Smaller values result in less audio latency but could cause buffer underruns and skipping. `driver` is parsed identically to the `--audio-driver` switch on the command line. If you're using Alsa on Linux and want to use you can set `driver=alsa:dmix` to get Schism Tracker to play with other programs. (However, Alsa completely ignores the latency with dmix so it might cause massive delays between pressing a note and hearing it, which is why Schism Tracker requests a "real" device by default.) [Diskwriter] rate=96000 bits=16 channels=2 This defines the sample format used by the disk writer – for exporting to .wav/.aiff *and* internal pattern-to-sample rendering. ## Hook functions Schism Tracker can run custom scripts on startup, exit, and upon completion of the disk writer. These are stored in the configuration directory, and are named `startup-hook`, `exit-hook`, and `diskwriter-hook` respectively. (On Windows, append `.bat` to the filenames.) Hooks are useful for making various adjustments to the system – adjusting the system volume, remapping the keyboard, etc. The disk writer hook can be used to do additional post-processing, converting, etc. (Note: on the Wii, hooks are not processed since there is no underlying OS or command interpreter to run them.) #### Example For users with non-US keyboards, some keys may not work properly. This can be worked around by switching temporarily to a US keyboard layout on startup, and resetting the keyboard on exit. To define hooks to accomplish this: cat >~/.schism/startup-hook <~/.schism/exit-hook < * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "sndfile.h" /* --------------------------------------------------------------------- */ struct header_669 { char sig[2]; char songmessage[108]; uint8_t samples; uint8_t patterns; uint8_t restartpos; uint8_t orders[128]; uint8_t tempolist[128]; uint8_t breaks[128]; }; int fmt_669_read_info(dmoz_file_t *file, const uint8_t *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 0; /* 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 0; if (header->samples == 0 || header->patterns == 0 || header->samples > 64 || header->patterns > 128 || header->restartpos > 127) return 0; for (i = 0; i < 128; i++) if (header->breaks[i] > 0x3f) return 0; file->title = strn_dup(header->songmessage, 36); file->description = desc; /*file->extension = str_dup("669");*/ file->type = TYPE_MODULE_S3M; return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* This is better than IT's and MPT's 669 loaders */ int fmt_669_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t b[16]; uint16_t npat, nsmp; int n, pat, chan, smp, row; song_note_t *note; uint16_t tmp; uint32_t tmplong; uint8_t patspeed[128], breakpos[128]; uint8_t restartpos; const char *tid; char titletmp[37]; slurp_read(fp, &tmp, 2); switch (bswapLE16(tmp)) { case 0x6669: // 'if' tid = "Composer 669"; break; case 0x4e4a: // 'JN' tid = "UNIS 669"; break; default: return LOAD_UNSUPPORTED; } /* The message is 108 bytes, split onto 3 lines of 36 bytes each. Also copy the first part of the message into the title, because 669 doesn't actually have a dedicated title field... */ read_lined_message(song->message, fp, 108, 36); strncpy(titletmp, song->message, 36); titletmp[36] = '\0'; titletmp[strcspn(titletmp, "\r\n")] = '\0'; trim_string(titletmp); titletmp[25] = '\0'; strcpy(song->title, titletmp); nsmp = slurp_getc(fp); npat = slurp_getc(fp); restartpos = slurp_getc(fp); if (nsmp > 64 || npat > 128 || restartpos > 127) return LOAD_UNSUPPORTED; strcpy(song->tracker_id, tid); /* orderlist */ slurp_read(fp, song->orderlist, 128); /* stupid crap */ slurp_read(fp, patspeed, 128); slurp_read(fp, breakpos, 128); /* samples */ for (smp = 1; smp <= nsmp; smp++) { slurp_read(fp, b, 13); b[13] = 0; /* the spec says it's supposed to be ASCIIZ, but some 669's use all 13 chars */ strcpy(song->samples[smp].name, (char *) b); b[12] = 0; /* ... filename field only has room for 12 chars though */ strcpy(song->samples[smp].filename, (char *) b); slurp_read(fp, &tmplong, 4); song->samples[smp].length = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); song->samples[smp].loop_start = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); tmplong = bswapLE32(tmplong); if (tmplong > song->samples[smp].length) tmplong = 0; else song->samples[smp].flags |= CHN_LOOP; song->samples[smp].loop_end = tmplong; song->samples[smp].c5speed = 8363; song->samples[smp].volume = 60; /* ickypoo */ song->samples[smp].volume *= 4; //mphack song->samples[smp].global_volume = 64; /* ickypoo */ song->samples[smp].vib_type = 0; song->samples[smp].vib_rate = 0; song->samples[smp].vib_depth = 0; song->samples[smp].vib_speed = 0; } /* patterns */ for (pat = 0; pat < npat; pat++) { uint8_t effect[8] = { 255, 255, 255, 255, 255, 255, 255, 255 }; uint8_t rows = breakpos[pat] + 1; if (rows > 64) { return LOAD_UNSUPPORTED; } note = song->patterns[pat] = csf_allocate_pattern(CLAMP(rows, 32, 64)); song->pattern_size[pat] = song->pattern_alloc_size[pat] = CLAMP(rows, 32, 64); for (row = 0; row < rows; row++, note += 56) { for (chan = 0; chan < 8; chan++, note++) { slurp_read(fp, b, 3); switch (b[0]) { case 0xfe: /* no note, only volume */ note->voleffect = VOLFX_VOLUME; note->volparam = (b[1] & 0xf) << 2; break; case 0xff: /* no note or volume */ break; default: note->note = (b[0] >> 2) + 36 + 1; note->instrument = ((b[0] & 3) << 4 | (b[1] >> 4)) + 1; note->voleffect = VOLFX_VOLUME; note->volparam = (b[1] & 0xf) << 2; break; } /* (sloppily) import the stupid effect */ if (b[2] != 0xff) effect[chan] = b[2]; if (effect[chan] == 0xff) continue; note->param = effect[chan] & 0xf; switch (effect[chan] >> 4) { default: /* oops. never mind. */ //printf("ignoring effect %X\n", effect[chan]); note->param = 0; break; case 0: /* A - portamento up */ note->effect = FX_PORTAMENTOUP; break; case 1: /* B - portamento down */ note->effect = FX_PORTAMENTODOWN; break; case 2: /* C - port to note */ note->effect = FX_TONEPORTAMENTO; break; case 3: /* D - frequency adjust (??) */ note->effect = FX_PORTAMENTOUP; if (note->param) note->param |= 0xf0; else note->param = 0xf1; effect[chan] = 0xff; break; case 4: /* E - frequency vibrato */ note->effect = FX_VIBRATO; note->param |= 0x80; break; case 5: /* F - set tempo */ /* TODO: param 0 is a "super fast tempo" in extended mode (?) */ if (note->param) note->effect = FX_SPEED; effect[chan] = 0xff; break; case 6: /* G - subcommands (extended) */ switch (note->param) { case 0: /* balance fine slide left */ //TODO("test pan slide effect (P%dR%dC%d)", pat, row, chan); note->effect = FX_PANNINGSLIDE; note->param = 0x8F; break; case 1: /* balance fine slide right */ //TODO("test pan slide effect (P%dR%dC%d)", pat, row, chan); note->effect = FX_PANNINGSLIDE; note->param = 0xF8; break; default: /* oops, nothing again */ note->param = 0; } break; case 7: /* H - slot retrig */ //TODO("test slot retrig (P%dR%dC%d)", pat, row, chan); note->effect = FX_RETRIG; break; } } } if (rows < 64) { /* skip the rest of the rows beyond the break position */ slurp_seek(fp, 3 * 8 * (64 - rows), SEEK_CUR); } /* handle the stupid pattern speed */ note = song->patterns[pat]; for (chan = 0; chan < 9; chan++, note++) { if (note->effect == FX_SPEED) { break; } else if (!note->effect) { note->effect = FX_SPEED; note->param = patspeed[pat]; break; } } /* handle the break position */ if (rows < 32) { //printf("adding pattern break for pattern %d\n", pat); note = song->patterns[pat] + MAX_CHANNELS * (rows - 1); for (chan = 0; chan < 9; chan++, note++) { if (!note->effect) { note->effect = FX_PATTERNBREAK; note->param = 0; break; } } } } csf_insert_restart_pos(song, restartpos); /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (smp = 1; smp <= nsmp; smp++) { uint32_t ssize; if (song->samples[smp].length == 0) continue; ssize = csf_read_sample(song->samples + smp, SF_LE | SF_M | SF_PCMU | SF_8, fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, ssize, SEEK_CUR); } } /* set the rest of the stuff */ song->initial_speed = 4; song->initial_tempo = 78; song->flags = SONG_ITOLDEFFECTS | SONG_LINEARSLIDES; song->pan_separation = 64; for (n = 0; n < 8; n++) song->channels[n].panning = (n & 1) ? 256 : 0; //mphack for (n = 8; n < 64; n++) song->channels[n].flags = CHN_MUTE; // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } /* done! */ return LOAD_SUCCESS; } schismtracker-20180209/fmt/aiff.c000066400000000000000000000400561323741476300165010ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "log.h" #include "fmt.h" #include #include /* swab */ #include /* for ldexp/frexp */ #ifdef WIN32 # define swab(a,b,c) swab((const char*)(a),(char*)(b),(size_t)(c)) #endif static void ConvertToIeeeExtended(double num, unsigned char *bytes); static double ConvertFromIeeeExtended(const unsigned char *bytes); /* --------------------------------------------------------------------- */ #pragma pack(push, 1) typedef union chunkdata { struct { uint32_t filetype; // AIFF, 8SVX, etc. uint8_t data[0]; // rest of file is encapsulated in here, also chunked } FORM; // 8SVX struct { uint32_t smp_highoct_1shot; uint32_t smp_highoct_repeat; uint32_t smp_cycle_highoct; uint16_t smp_per_sec; uint8_t num_octaves; uint8_t compression; // 0 = none, 1 = fibonacci-delta uint32_t volume; // fixed point, 65536 = 1.0 } VHDR; // AIFF struct { uint16_t num_channels; uint32_t num_frames; uint16_t sample_size; uint8_t sample_rate[80]; // IEEE-extended } COMM; uint8_t bytes[0]; } chunkdata_t; #pragma pack(pop) typedef struct chunk { uint32_t id; uint32_t size; const chunkdata_t *data; } chunk_t; // other chunks that might exist: "NAME", "AUTH", "ANNO", "(c) " // 'chunk' is filled in with the chunk header // return: 0 if chunk overflows EOF, 1 if it was successfully read // pos is updated to point to the beginning of the next chunk static int iff_chunk_read(chunk_t *chunk, const uint8_t *data, size_t length, size_t *pos) { if (*pos + 8 > length) return 0; memcpy(&chunk->id, data + *pos, 4); memcpy(&chunk->size, data + *pos + 4, 4); chunk->id = bswapBE32(chunk->id); chunk->size = bswapBE32(chunk->size); chunk->data = (chunkdata_t *) (data + *pos + 8); *pos += 8 + chunk->size; return (*pos <= length); } // Wish I could do this: //#define ID(x) ((0[#x] << 24) | (1[#x] << 16) | (2[#x] << 8) | (3[#x])) // It works great, but gcc doesn't think it's a constant, so it can't be used in a switch. // (although with -O, it definitely does optimize it into a constant...) #define ID_FORM 0x464F524D #define ID_8SVX 0x38535658 #define ID_VHDR 0x56484452 #define ID_BODY 0x424F4459 #define ID_NAME 0x4E414D45 #define ID_AUTH 0x41555448 #define ID_ANNO 0x414E4E4F #define ID__C__ 0x28632920 /* "(c) " */ #define ID_AIFF 0x41494646 #define ID_COMM 0x434F4D4D #define ID_SSND 0x53534E44 /* --------------------------------------------------------------------- */ #define ZEROIZE(x) memset(&(x), 0, sizeof(x)) static int _read_iff(dmoz_file_t *file, song_sample_t *smp, const uint8_t *data, size_t length) { chunk_t chunk; size_t pos = 0; chunk_t vhdr, body, name, comm, auth, anno, ssnd; // butt if (!iff_chunk_read(&chunk, data, length, &pos)) return 0; if (chunk.id != ID_FORM) return 0; // jump "into" the FORM chunk // if (pos < length), there's more data after the FORM chunk -- but I don't care about this scenario pos = 0; length = MIN(length, chunk.size); data = chunk.data->FORM.data; /* the header is already byteswapped, but anything in 'chunk' will need to be swapped as needed because the structure is a const pointing into the data itself */ switch (bswapBE32(chunk.data->FORM.filetype)) { case ID_8SVX: // shut up, gcc ZEROIZE(vhdr); ZEROIZE(body); ZEROIZE(name); ZEROIZE(auth); ZEROIZE(anno); while (iff_chunk_read(&chunk, data, length, &pos)) { switch (chunk.id) { case ID_VHDR: vhdr = chunk; break; case ID_BODY: body = chunk; break; case ID_NAME: name = chunk; break; case ID_AUTH: auth = chunk; break; case ID_ANNO: anno = chunk; break; default: break; } } if (!(vhdr.id && body.id)) return 0; if (vhdr.data->VHDR.compression) { log_appendf(4, "error: compressed 8SVX files are unsupported"); return 0; } if (vhdr.data->VHDR.num_octaves != 1) { log_appendf(4, "warning: 8SVX file contains %d octaves", vhdr.data->VHDR.num_octaves); } if (file) { file->description = "8SVX sample"; file->type = TYPE_SAMPLE_PLAIN; } if (!name.id) name = auth; if (!name.id) name = anno; if (name.id) { if (file) file->title = strn_dup((const char *)name.data->bytes, name.size); if (smp) { int len = MIN(25, name.size); memcpy(smp->name, name.data->bytes, len); smp->name[len] = 0; } } if (smp) { smp->c5speed = bswapBE16(vhdr.data->VHDR.smp_per_sec); smp->length = body.size; csf_read_sample(smp, SF_BE | SF_PCMS | SF_8 | SF_M, body.data->bytes, body.size); smp->volume = 64*4; smp->global_volume = 64; // this is done kinda weird smp->loop_end = bswapBE32(vhdr.data->VHDR.smp_highoct_repeat); if (smp->loop_end) { smp->loop_start = bswapBE32(vhdr.data->VHDR.smp_highoct_1shot); smp->loop_end += smp->loop_start; if (smp->loop_start > smp->length) smp->loop_start = 0; if (smp->loop_end > smp->length) smp->loop_end = smp->length; if (smp->loop_start + 2 < smp->loop_end) smp->flags |= CHN_LOOP; } // TODO vhdr.data->VHDR.volume ? } return 1; case ID_AIFF: ZEROIZE(comm); ZEROIZE(ssnd); ZEROIZE(name); ZEROIZE(auth); ZEROIZE(anno); while (iff_chunk_read(&chunk, data, length, &pos)) { switch (chunk.id) { case ID_COMM: comm = chunk; break; case ID_SSND: ssnd = chunk; break; case ID_NAME: name = chunk; break; default: break; } } if (!(comm.id && ssnd.id)) return 0; if (file) { file->description = "Audio IFF sample"; file->type = TYPE_SAMPLE_PLAIN; } if (!name.id) name = auth; if (!name.id) name = anno; if (name.id) { if (file) file->title = strn_dup((const char *)name.data->bytes, name.size); if (smp) { int len = MIN(25, name.size); memcpy(smp->name, name.data->bytes, len); smp->name[len] = 0; } } /* TODO loop points */ if (smp) { uint32_t flags = SF_BE | SF_PCMS; switch (bswapBE16(comm.data->COMM.num_channels)) { default: log_appendf(4, "warning: multichannel AIFF is unsupported"); case 1: flags |= SF_M; break; case 2: flags |= SF_SI; break; } switch ((bswapBE16(comm.data->COMM.sample_size) + 7) & ~7) { default: log_appendf(4, "warning: AIFF has unsupported bit-width"); case 8: flags |= SF_8; break; case 16: flags |= SF_16; break; } // TODO: data checking; make sure sample count and byte size agree // (and if not, cut to shorter of the two) smp->c5speed = ConvertFromIeeeExtended(comm.data->COMM.sample_rate); smp->length = bswapBE32(comm.data->COMM.num_frames); smp->volume = 64*4; smp->global_volume = 64; // the audio data starts 8 bytes into the chunk // (don't care about the block alignment stuff) csf_read_sample(smp, flags, ssnd.data->bytes + 8, ssnd.size - 8); } return 1; } return 0; } /* --------------------------------------------------------------------- */ int fmt_aiff_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { return _read_iff(file, NULL, data, length); } int fmt_aiff_load_sample(const uint8_t *data, size_t length, song_sample_t *smp) { return _read_iff(NULL, smp, data, length); } /* --------------------------------------------------------------------- */ struct aiff_writedata { long comm_frames, ssnd_size; // seek positions for writing header data size_t numbytes; // how many bytes have been written int bps; // bytes per sample int swap; // should be byteswapped? }; static int aiff_header(disko_t *fp, int bits, int channels, int rate, const char *name, size_t length, struct aiff_writedata *awd /* out */) { int16_t s; uint32_t ul; int tlen, bps = 1; uint8_t b[10]; bps *= ((bits + 7) / 8); /* note: channel multiply is done below -- need single-channel value for the COMM chunk */ /* write a very large size for now */ disko_write(fp, "FORM\377\377\377\377AIFF", 12); if (name && *name) { disko_write(fp, "NAME", 4); tlen = strlen(name); ul = (tlen + 1) & ~1; /* must be even */ ul = bswapBE32(ul); disko_write(fp, &ul, 4); disko_write(fp, name, tlen); if (tlen & 1) disko_putc(fp, '\0'); } /* Common Chunk The Common Chunk describes fundamental parameters of the sampled sound. typedef struct { ID ckID; // 'COMM' long ckSize; // 18 short numChannels; unsigned long numSampleFrames; short sampleSize; extended sampleRate; } CommonChunk; */ disko_write(fp, "COMM", 4); ul = bswapBE32(18); /* chunk size -- won't change */ disko_write(fp, &ul, 4); s = bswapBE16(channels); disko_write(fp, &s, 2); if (awd) awd->comm_frames = disko_tell(fp); ul = bswapBE32(length); /* num sample frames */ disko_write(fp, &ul, 4); s = bswapBE16(bits); disko_write(fp, &s, 2); ConvertToIeeeExtended(rate, b); disko_write(fp, b, 10); /* NOW do this (sample size in AIFF is indicated per channel, not per frame) */ bps *= channels; /* == number of bytes per (stereo) sample */ /* Sound Data Chunk The Sound Data Chunk contains the actual sample frames. typedef struct { ID ckID; // 'SSND' long ckSize; // data size in bytes, *PLUS EIGHT* (for offset and blockSize) unsigned long offset; // just set this to 0... unsigned long blockSize; // likewise unsigned char soundData[]; } SoundDataChunk; */ disko_write(fp, "SSND", 4); if (awd) awd->ssnd_size = disko_tell(fp); ul = bswapBE32(length * bps + 8); disko_write(fp, &ul, 4); ul = bswapBE32(0); disko_write(fp, &ul, 4); disko_write(fp, &ul, 4); return bps; } /* --------------------------------------------------------------------- */ int fmt_aiff_save_sample(disko_t *fp, song_sample_t *smp) { int bps; uint32_t ul; uint32_t flags = SF_BE | SF_PCMS; flags |= (smp->flags & CHN_16BIT) ? SF_16 : SF_8; flags |= (smp->flags & CHN_STEREO) ? SF_SI : SF_M; bps = aiff_header(fp, (smp->flags & CHN_16BIT) ? 16 : 8, (smp->flags & CHN_STEREO) ? 2 : 1, smp->c5speed, smp->name, smp->length, NULL); if (csf_write_sample(fp, smp, flags) != smp->length * bps) { log_appendf(4, "AIFF: unexpected data size written"); return SAVE_INTERNAL_ERROR; } /* TODO: loop data */ /* fix the length in the file header */ ul = disko_tell(fp) - 8; ul = bswapBE32(ul); disko_seek(fp, 4, SEEK_SET); disko_write(fp, &ul, 4); return SAVE_SUCCESS; } int fmt_aiff_export_head(disko_t *fp, int bits, int channels, int rate) { struct aiff_writedata *awd = malloc(sizeof(struct aiff_writedata)); if (!awd) return DW_ERROR; fp->userdata = awd; awd->bps = aiff_header(fp, bits, channels, rate, NULL, ~0, awd); awd->numbytes = 0; #if WORDS_BIGENDIAN awd->swap = 0; #else awd->swap = (bits > 8); #endif return DW_OK; } int fmt_aiff_export_body(disko_t *fp, const uint8_t *data, size_t length) { struct aiff_writedata *awd = fp->userdata; if (length % awd->bps) { log_appendf(4, "AIFF export: received uneven length"); return DW_ERROR; } awd->numbytes += length; if (awd->swap) { const int16_t *ptr = (const int16_t *) data; uint16_t v; length /= 2; while (length--) { v = *ptr; v = bswapBE16(v); disko_write(fp, &v, 2); ptr++; } } else { disko_write(fp, data, length); } return DW_OK; } int fmt_aiff_export_silence(disko_t *fp, long bytes) { disko_seek(fp, bytes, SEEK_CUR); return DW_OK; } int fmt_aiff_export_tail(disko_t *fp) { struct aiff_writedata *awd = fp->userdata; uint32_t ul; /* fix the length in the file header */ ul = disko_tell(fp) - 8; ul = bswapBE32(ul); disko_seek(fp, 4, SEEK_SET); disko_write(fp, &ul, 4); /* write the other lengths */ disko_seek(fp, awd->comm_frames, SEEK_SET); ul = bswapBE32(awd->numbytes / awd->bps); disko_write(fp, &ul, 4); disko_seek(fp, awd->ssnd_size, SEEK_SET); ul = bswapBE32(awd->numbytes + 8); disko_write(fp, &ul, 4); free(awd); return DW_OK; } /* --------------------------------------------------------------------- */ /* 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) ((uint32_t) (((int32_t) (f - 2147483648.0)) + 2147483647L + 1)) #define UnsignedToFloat(u) (((double) ((int32_t) (u - 2147483647L - 1))) + 2147483648.0) static void ConvertToIeeeExtended(double num, unsigned char *bytes) { int sign, expon; double fMant, fsMant; uint32_t 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; uint32_t hiMant, loMant; expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF); hiMant = ((uint32_t) (bytes[2] & 0xFF) << 24) | ((uint32_t) (bytes[3] & 0xFF) << 16) | ((uint32_t) (bytes[4] & 0xFF) << 8) | ((uint32_t) (bytes[5] & 0xFF)); loMant = ((uint32_t) (bytes[6] & 0xFF) << 24) | ((uint32_t) (bytes[7] & 0xFF) << 16) | ((uint32_t) (bytes[8] & 0xFF) << 8) | ((uint32_t) (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; } schismtracker-20180209/fmt/ams.c000066400000000000000000000032501323741476300163470ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" */ int fmt_ams_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { uint8_t n; if (!(length > 38 && memcmp(data, "AMShdr\x1a", 7) == 0)) return 0; n = data[7]; if (n > 30) n = 30; file->description = "Velvet Studio"; /*file->extension = str_dup("ams");*/ file->title = strn_dup((const char *)data + 8, n); file->type = TYPE_MODULE_XM; return 1; } schismtracker-20180209/fmt/au.c000066400000000000000000000124521323741476300162000ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" */ uint32_t data_offset, data_size, encoding, sample_rate, channels; }; /* --------------------------------------------------------------------- */ int fmt_au_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { struct au_header au; if (!(length > 24 && memcmp(data, ".snd", 4) == 0)) return 0; memcpy(&au, data, 24); 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); if (!(au.data_offset < length && au.data_size > 0 && au.data_size <= length - au.data_offset)) return 0; file->smp_length = au.data_size / au.channels; file->smp_flags = 0; if (au.encoding == AU_PCM_16) { file->smp_flags |= CHN_16BIT; file->smp_length /= 2; } else if (au.encoding == AU_PCM_24) { file->smp_length /= 3; } else if (au.encoding == AU_PCM_32 || au.encoding == AU_IEEE_32) { file->smp_length /= 4; } else if (au.encoding == AU_IEEE_64) { file->smp_length /= 8; } if (au.channels >= 2) { file->smp_flags |= CHN_STEREO; } file->description = "AU Sample"; if (au.data_offset > 24) { int extlen = au.data_offset - 24; file->title = strn_dup((const char *)data + 24, extlen); } file->smp_filename = file->title; file->type = TYPE_SAMPLE_PLAIN; return 1; } /* --------------------------------------------------------------------- */ int fmt_au_load_sample(const uint8_t *data, size_t length, song_sample_t *smp) { struct au_header au; uint32_t sflags = SF_BE | SF_PCMS; if (length < 24) return 0; 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 0; }*/ #define C__(cond) if (!(cond)) { return 0; } 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->c5speed = au.sample_rate; smp->volume = 64 * 4; smp->global_volume = 64; smp->length = au.data_size; if (au.encoding == AU_PCM_16) { sflags |= SF_16; smp->length /= 2; } else { sflags |= SF_8; } if (au.channels == 2) { sflags |= SF_SI; smp->length /= 2; } else { sflags |= SF_M; } if (au.data_offset > 24) { int extlen = MIN(25, au.data_offset - 24); memcpy(smp->name, data + 24, extlen); smp->name[extlen] = 0; } csf_read_sample(smp, sflags, data + au.data_offset, length - au.data_offset); return 1; } /* --------------------------------------------------------------------------------------------------------- */ int fmt_au_save_sample(disko_t *fp, song_sample_t *smp) { struct au_header au; uint32_t 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 & CHN_16BIT) { ln *= 2; au.encoding = bswapBE32(AU_PCM_16); } else { au.encoding = bswapBE32(AU_PCM_8); } au.sample_rate = bswapBE32(smp->c5speed); if (smp->flags & CHN_STEREO) { ln *= 2; au.channels = bswapBE32(2); } else { au.channels = bswapBE32(1); } au.data_size = bswapBE32(ln); disko_write(fp, &au, sizeof(au)); disko_write(fp, smp->name, 25); csf_write_sample(fp, smp, SF_BE | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SI : SF_M)); return SAVE_SUCCESS; } schismtracker-20180209/fmt/compression.c000066400000000000000000000176051323741476300201410ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" // ------------------------------------------------------------------------------------------------------------ // IT decompression code from itsex.c (Cubic Player) and load_it.cpp (Modplug) // (I suppose this could be considered a merge between the two.) static uint32_t it_readbits(int8_t n, uint32_t *bitbuf, uint32_t *bitnum, const uint8_t **ibuf) { uint32_t value = 0; uint32_t i = n; // this could be better while (i--) { if (!*bitnum) { *bitbuf = *(*ibuf)++; *bitnum = 8; } value >>= 1; value |= (*bitbuf) << 31; (*bitbuf) >>= 1; (*bitnum)--; } return value >> (32 - n); } uint32_t it_decompress8(void *dest, uint32_t len, const void *file, uint32_t filelen, int it215, int channels) { const uint8_t *filebuf; // source buffer containing compressed sample data const uint8_t *srcbuf; // current position in source buffer int8_t *destpos; // position in destination buffer which will be returned uint16_t blklen; // length of compressed data block in samples uint16_t blkpos; // position in block uint8_t width; // actual "bit width" uint16_t value; // value read from file to be processed int8_t d1, d2; // integrator buffers (d2 for it2.15) int8_t v; // sample value uint32_t bitbuf, bitnum; // state for it_readbits filebuf = srcbuf = (const uint8_t *) file; destpos = (int8_t *) dest; // now unpack data till the dest buffer is full while (len) { // read a new block of compressed data and reset variables // block layout: word size, bytes data if (srcbuf + 2 > filebuf + filelen || srcbuf + 2 + (srcbuf[0] | (srcbuf[1] << 8)) > filebuf + filelen) { // truncated! return srcbuf - filebuf; } srcbuf += 2; bitbuf = bitnum = 0; blklen = MIN(0x8000, len); blkpos = 0; width = 9; // start with width of 9 bits d1 = d2 = 0; // reset integrator buffers // now uncompress the data block while (blkpos < blklen) { if (width > 9) { // illegal width, abort printf("Illegal bit width %d for 8-bit sample\n", width); return srcbuf - filebuf; } value = it_readbits(width, &bitbuf, &bitnum, &srcbuf); if (width < 7) { // method 1 (1-6 bits) // check for "100..." if (value == 1 << (width - 1)) { // yes! value = it_readbits(3, &bitbuf, &bitnum, &srcbuf) + 1; // read new width width = (value < width) ? value : value + 1; // and expand it continue; // ... next value } } else if (width < 9) { // method 2 (7-8 bits) uint8_t border = (0xFF >> (9 - width)) - 4; // lower border for width chg if (value > border && value <= (border + 8)) { value -= border; // convert width to 1-8 width = (value < width) ? value : value + 1; // and expand it continue; // ... next value } } else { // method 3 (9 bits) // bit 8 set? if (value & 0x100) { width = (value + 1) & 0xff; // new width... continue; // ... and next value } } // now expand value to signed byte if (width < 8) { uint8_t shift = 8 - width; v = (value << shift); v >>= shift; } else { v = (int8_t) value; } // integrate upon the sample values d1 += v; d2 += d1; // .. and store it into the buffer *destpos = it215 ? d2 : d1; destpos += channels; blkpos++; } // now subtract block length from total length and go on len -= blklen; } return srcbuf - filebuf; } // Mostly the same as above. uint32_t it_decompress16(void *dest, uint32_t len, const void *file, uint32_t filelen, int it215, int channels) { const uint8_t *filebuf; // source buffer containing compressed sample data const uint8_t *srcbuf; // current position in source buffer int16_t *destpos; // position in destination buffer which will be returned uint16_t blklen; // length of compressed data block in samples uint16_t blkpos; // position in block uint8_t width; // actual "bit width" uint32_t value; // value read from file to be processed int16_t d1, d2; // integrator buffers (d2 for it2.15) int16_t v; // sample value uint32_t bitbuf, bitnum; // state for it_readbits filebuf = srcbuf = (const uint8_t *) file; destpos = (int16_t *) dest; // now unpack data till the dest buffer is full while (len) { // read a new block of compressed data and reset variables // block layout: word size, bytes data if (srcbuf + 2 > filebuf + filelen || srcbuf + 2 + (srcbuf[0] | (srcbuf[1] << 8)) > filebuf + filelen) { // truncated! return srcbuf - filebuf; } srcbuf += 2; bitbuf = bitnum = 0; blklen = MIN(0x4000, len); // 0x4000 samples => 0x8000 bytes again blkpos = 0; width = 17; // start with width of 17 bits d1 = d2 = 0; // reset integrator buffers // now uncompress the data block while (blkpos < blklen) { if (width > 17) { // illegal width, abort printf("Illegal bit width %d for 16-bit sample\n", width); return srcbuf - filebuf; } value = it_readbits(width, &bitbuf, &bitnum, &srcbuf); if (width < 7) { // method 1 (1-6 bits) // check for "100..." if (value == (uint32_t) 1 << (width - 1)) { // yes! value = it_readbits(4, &bitbuf, &bitnum, &srcbuf) + 1; // read new width width = (value < width) ? value : value + 1; // and expand it continue; // ... next value } } else if (width < 17) { // method 2 (7-16 bits) uint16_t border = (0xFFFF >> (17 - width)) - 8; // lower border for width chg if (value > border && value <= (uint32_t) (border + 16)) { value -= border; // convert width to 1-8 width = (value < width) ? value : value + 1; // and expand it continue; // ... next value } } else { // method 3 (17 bits) // bit 16 set? if (value & 0x10000) { width = (value + 1) & 0xff; // new width... continue; // ... and next value } } // now expand value to signed word if (width < 16) { uint8_t shift = 16 - width; v = (value << shift); v >>= shift; } else { v = (int16_t) value; } // integrate upon the sample values d1 += v; d2 += d1; // .. and store it into the buffer *destpos = it215 ? d2 : d1; destpos += channels; blkpos++; } // now subtract block length from total length and go on len -= blklen; } return srcbuf - filebuf; } // ------------------------------------------------------------------------------------------------------------ // MDL sample decompression uint16_t mdl_read_bits(uint32_t *bitbuf, uint32_t *bitnum, uint8_t **ibuf, int8_t n) { uint16_t v = (uint16_t)((*bitbuf) & ((1 << n) - 1) ); (*bitbuf) >>= n; (*bitnum) -= n; if ((*bitnum) <= 24) { (*bitbuf) |= (((uint32_t)(*(*ibuf)++)) << (*bitnum)); (*bitnum) += 8; } return v; } schismtracker-20180209/fmt/f2r.c000066400000000000000000000027161323741476300162660ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ int fmt_f2r_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 46 && memcmp(data, "F2R", 3) == 0)) return 0; file->description = "Farandole 2 (linear)"; /*file->extension = str_dup("f2r");*/ file->title = strn_dup((const char *)data + 6, 40); file->type = TYPE_MODULE_S3M; return 1; } schismtracker-20180209/fmt/far.c000066400000000000000000000167071323741476300163520ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "sndfile.h" /* --------------------------------------------------------------------- */ int fmt_far_read_info(dmoz_file_t *file, const uint8_t *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 0; file->description = "Farandole Module"; /*file->extension = str_dup("far");*/ file->title = strn_dup((const char *)data + 4, 40); file->type = TYPE_MODULE_S3M; return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* This loader sucks. Mostly it was implemented based on what Modplug does, which is kind of counterproductive, but I can't get Farandole to run in Dosbox to test stuff */ #pragma pack(push, 1) struct far_header { uint8_t magic[4]; char title[40]; uint8_t eof[3]; uint16_t header_len; uint8_t version; uint8_t onoff[16]; uint8_t editing_state[9]; // stuff we don't care about uint8_t default_speed; uint8_t chn_panning[16]; uint8_t pattern_state[4]; // more stuff we don't care about uint16_t message_len; }; struct far_sample { char name[32]; uint32_t length; uint8_t finetune; uint8_t volume; uint32_t loopstart; uint32_t loopend; uint8_t type; uint8_t loop; }; #pragma pack(pop) static uint8_t far_effects[] = { FX_NONE, FX_PORTAMENTOUP, FX_PORTAMENTODOWN, FX_TONEPORTAMENTO, FX_RETRIG, FX_VIBRATO, // depth FX_VIBRATO, // speed FX_VOLUMESLIDE, // up FX_VOLUMESLIDE, // down FX_VIBRATO, // sustained (?) FX_NONE, // actually slide-to-volume FX_PANNING, FX_SPECIAL, // note offset => note delay? FX_NONE, // fine tempo down FX_NONE, // fine tempo up FX_SPEED, }; static void far_import_note(song_note_t *note, const uint8_t data[4]) { if (data[0] > 0 && data[0] < 85) { note->note = data[0] + 36; note->instrument = data[1] + 1; } if (data[2] & 0x0F) { note->voleffect = VOLFX_VOLUME; note->volparam = (data[2] & 0x0F) << 2; // askjdfjasdkfjasdf } note->param = data[3] & 0xf; switch (data[3] >> 4) { case 3: // porta to note note->param <<= 2; break; case 4: // retrig note->param = 6 / (1 + (note->param & 0xf)) + 1; // ugh? break; case 6: // vibrato speed case 7: // volume slide up case 0xb: // panning note->param <<= 4; break; case 0xa: // volume-portamento (what!) note->voleffect = VOLFX_VOLUME; note->volparam = (note->param << 2) + 4; break; case 0xc: // note offset note->param = 6 / (1 + (note->param & 0xf)) + 1; note->param |= 0xd; } note->effect = far_effects[data[3] >> 4]; } int fmt_far_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { struct far_header fhdr; struct far_sample fsmp; song_sample_t *smp; int nord, restartpos; int n, pat, row, chn; uint8_t orderlist[256]; uint16_t pattern_size[256]; uint8_t data[8]; slurp_read(fp, &fhdr, sizeof(fhdr)); if (memcmp(fhdr.magic, "FAR\xfe", 4) != 0 || memcmp(fhdr.eof, "\x0d\x0a\x1a", 3) != 0) return LOAD_UNSUPPORTED; fhdr.title[25] = '\0'; strcpy(song->title, fhdr.title); fhdr.header_len = bswapLE16(fhdr.header_len); fhdr.message_len = bswapLE16(fhdr.message_len); for (n = 0; n < 16; n++) { /* WHAT A GREAT WAY TO STORE THIS INFORMATION */ song->channels[n].panning = SHORT_PANNING(fhdr.chn_panning[n] & 0xf); song->channels[n].panning *= 4; //mphack if (!fhdr.onoff[n]) song->channels[n].flags |= CHN_MUTE; } for (; n < 64; n++) song->channels[n].flags |= CHN_MUTE; song->initial_speed = fhdr.default_speed; song->initial_tempo = 80; // to my knowledge, no other program is insane enough to save in this format strcpy(song->tracker_id, "Farandole Composer"); /* Farandole's song message doesn't have line breaks, and the tracker runs in some screwy ultra-wide text mode, so this displays more or less like crap. */ read_lined_message(song->message, fp, fhdr.message_len, 132); slurp_seek(fp, sizeof(fhdr) + fhdr.message_len, SEEK_SET); if ((lflags & (LOAD_NOSAMPLES | LOAD_NOPATTERNS)) == (LOAD_NOSAMPLES | LOAD_NOPATTERNS)) return LOAD_SUCCESS; slurp_read(fp, orderlist, 256); slurp_getc(fp); // supposed to be "number of patterns stored in the file"; apparently that's wrong nord = slurp_getc(fp); restartpos = slurp_getc(fp); nord = MIN(nord, MAX_ORDERS); memcpy(song->orderlist, orderlist, nord); memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord); slurp_read(fp, pattern_size, 256 * 2); // byteswapped later slurp_seek(fp, fhdr.header_len - (869 + fhdr.message_len), SEEK_CUR); for (pat = 0; pat < 256; pat++) { int breakpos, rows; song_note_t *note; pattern_size[pat] = bswapLE16(pattern_size[pat]); if (pat >= MAX_PATTERNS || pattern_size[pat] < (2 + 16 * 4)) { slurp_seek(fp, pattern_size[pat], SEEK_CUR); continue; } breakpos = slurp_getc(fp); slurp_getc(fp); // apparently, this value is *not* used anymore!!! I will not support it!! rows = (pattern_size[pat] - 2) / (16 * 4); if (!rows) continue; note = song->patterns[pat] = csf_allocate_pattern(rows); song->pattern_size[pat] = song->pattern_alloc_size[pat] = rows; breakpos = breakpos && breakpos < rows - 2 ? breakpos + 1 : -1; for (row = 0; row < rows; row++, note += 48) { for (chn = 0; chn < 16; chn++, note++) { slurp_read(fp, data, 4); far_import_note(note, data); } if (row == breakpos) note->effect = FX_PATTERNBREAK; } } csf_insert_restart_pos(song, restartpos); if (lflags & LOAD_NOSAMPLES) return LOAD_SUCCESS; slurp_read(fp, data, 8); smp = song->samples + 1; for (n = 0; n < 64; n++, smp++) { if (!(data[n / 8] & (1 << (n % 8)))) /* LOLWHAT */ continue; slurp_read(fp, &fsmp, sizeof(fsmp)); fsmp.name[25] = '\0'; strcpy(smp->name, fsmp.name); smp->length = fsmp.length = bswapLE32(fsmp.length); smp->loop_start = bswapLE32(fsmp.loopstart); smp->loop_end = bswapLE32(fsmp.loopend); smp->volume = fsmp.volume << 4; // "not supported", but seems to exist anyway if (fsmp.type & 1) { smp->length >>= 1; smp->loop_start >>= 1; smp->loop_end >>= 1; } if (smp->loop_end > smp->loop_start && (fsmp.loop & 8)) smp->flags |= CHN_LOOP; smp->c5speed = 16726; smp->global_volume = 64; csf_read_sample(smp, SF_LE | SF_M | SF_PCMS | ((fsmp.type & 1) ? SF_16 : SF_8), fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, fsmp.length, SEEK_CUR); } return LOAD_SUCCESS; } schismtracker-20180209/fmt/generic.c000066400000000000000000000141341323741476300172060ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" /* --------------------------------------------------------------------------------------------------------- */ static int _mod_period_to_note(int period) { int n; if (period) for (n = 0; n <= NOTE_LAST; n++) if (period >= (32 * period_table[n % 12] >> (n / 12 + 2))) return n + 1; return NOTE_NONE; } void mod_import_note(const uint8_t p[4], song_note_t *note) { note->note = _mod_period_to_note(((p[0] & 0xf) << 8) + p[1]); note->instrument = (p[0] & 0xf0) + (p[2] >> 4); note->voleffect = VOLFX_NONE; note->volparam = 0; note->effect = p[2] & 0xf; note->param = p[3]; } /* --------------------------------------------------------------------------------------------------------- */ const uint8_t effect_weight[FX_MAX] = { [FX_PATTERNBREAK] = 248, [FX_POSITIONJUMP] = 240, [FX_SPEED] = 232, [FX_TEMPO] = 224, [FX_GLOBALVOLUME] = 216, [FX_GLOBALVOLSLIDE] = 208, [FX_CHANNELVOLUME] = 200, [FX_CHANNELVOLSLIDE] = 192, [FX_TONEPORTAVOL] = 184, [FX_TONEPORTAMENTO] = 176, [FX_ARPEGGIO] = 168, [FX_RETRIG] = 160, [FX_TREMOR] = 152, [FX_OFFSET] = 144, [FX_VOLUME] = 136, [FX_VIBRATOVOL] = 128, [FX_VOLUMESLIDE] = 120, [FX_PORTAMENTODOWN] = 112, [FX_PORTAMENTOUP] = 104, [FX_NOTESLIDEDOWN] = 96, // IMF Hxy [FX_NOTESLIDEUP] = 88, // IMF Gxy [FX_PANNING] = 80, [FX_PANNINGSLIDE] = 72, [FX_MIDI] = 64, [FX_SPECIAL] = 56, [FX_PANBRELLO] = 48, [FX_VIBRATO] = 40, [FX_FINEVIBRATO] = 32, [FX_TREMOLO] = 24, [FX_KEYOFF] = 16, [FX_SETENVPOSITION] = 8, [FX_NONE] = 0, }; void swap_effects(song_note_t *note) { song_note_t tmp = { .note = note->note, .instrument = note->instrument, .voleffect = note->effect, .volparam = note->param, .effect = note->voleffect, .param = note->volparam, }; *note = tmp; } int convert_voleffect(uint8_t *e, uint8_t *p, int force) { switch (*e) { case FX_NONE: return 1; case FX_VOLUME: *e = VOLFX_VOLUME; *p = MIN(*p, 64); break; case FX_PORTAMENTOUP: /* if not force, reject when dividing causes loss of data in LSB, or if the final value is too large to fit. (volume column Ex/Fx are four times stronger than effect column) */ if (!force && ((*p & 3) || *p > 9 * 4 + 3)) return 0; *p = MIN(*p / 4, 9); *e = VOLFX_PORTAUP; break; case FX_PORTAMENTODOWN: if (!force && ((*p & 3) || *p > 9 * 4 + 3)) return 0; *p = MIN(*p / 4, 9); *e = VOLFX_PORTADOWN; break; case FX_TONEPORTAMENTO: if (*p >= 0xf0) { // hack for people who can't type F twice :) *e = VOLFX_TONEPORTAMENTO; *p = 0x9; return 1; } for (int n = 0; n < 10; n++) { if (force ? (*p <= vc_portamento_table[n]) : (*p == vc_portamento_table[n])) { *e = VOLFX_TONEPORTAMENTO; *p = n; return 1; } } return 0; case FX_VIBRATO: if (force) *p = MIN(*p, 9); else if (*p > 9) return 0; *e = VOLFX_VIBRATODEPTH; break; case FX_FINEVIBRATO: if (force) *p = 0; else if (*p) return 0; *e = VOLFX_VIBRATODEPTH; break; case FX_PANNING: *p = MIN(64, *p * 64 / 255); *e = VOLFX_PANNING; break; case FX_VOLUMESLIDE: // ugh // (IT doesn't even attempt to do this, presumably since it'd screw up the effect memory) if (*p == 0) return 0; if ((*p & 0xf) == 0) { // Dx0 / Cx if (force) *p = MIN(*p >> 4, 9); else if ((*p >> 4) > 9) return 0; else *p >>= 4; *e = VOLFX_VOLSLIDEUP; } else if ((*p & 0xf0) == 0) { // D0x / Dx if (force) *p = MIN(*p, 9); else if (*p > 9) return 0; *e = VOLFX_VOLSLIDEDOWN; } else if ((*p & 0xf) == 0xf) { // DxF / Ax if (force) *p = MIN(*p >> 4, 9); else if ((*p >> 4) > 9) return 0; else *p >>= 4; *e = VOLFX_FINEVOLUP; } else if ((*p & 0xf0) == 0xf0) { // DFx / Bx if (force) *p = MIN(*p, 9); else if ((*p & 0xf) > 9) return 0; else *p &= 0xf; *e = VOLFX_FINEVOLDOWN; } else { // ??? return 0; } break; case FX_SPECIAL: switch (*p >> 4) { case 8: /* Impulse Tracker imports XM volume-column panning very weirdly: XM = P0 P1 P2 P3 P4 P5 P6 P7 P8 P9 PA PB PC PD PE PF IT = 00 05 10 15 20 21 30 31 40 45 42 47 60 61 62 63 I'll be um, not duplicating that behavior. :) */ *e = VOLFX_PANNING; *p = SHORT_PANNING(*p & 0xf); return 1; case 0: case 1: case 2: case 0xf: if (force) { *e = *p = 0; return 1; } break; default: break; } return 0; default: return 0; } return 1; } void read_lined_message(char *msg, slurp_t *fp, int len, int linelen) { int msgsize = 0, linesize; while (len) { linesize = MIN(len, linelen); if (msgsize + linesize + 1 >= MAX_MESSAGE) { /* Skip the rest */ slurp_seek(fp, len, SEEK_CUR); break; } slurp_read(fp, msg, linesize); len -= linesize; msg[linesize] = '\0'; linesize = rtrim_string(msg); msgsize += linesize + 1; msg += linesize; *msg++ = '\n'; } *msg = '\0'; } schismtracker-20180209/fmt/imf.c000066400000000000000000000443241323741476300163510ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "log.h" #include "fmt.h" #include "sndfile.h" /* --------------------------------------------------------------------- */ int fmt_imf_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 64 && memcmp(data + 60, "IM10", 4) == 0)) return 0; file->description = "Imago Orpheus"; /*file->extension = str_dup("imf");*/ file->title = strn_dup((const char *)data, 32); file->type = TYPE_MODULE_IT; return 1; } /* --------------------------------------------------------------------------------------------------------- */ #pragma pack(push, 1) struct imf_channel { char name[12]; /* Channelname (ASCIIZ-String, max 11 chars) */ uint8_t chorus; /* Default chorus */ uint8_t reverb; /* Default reverb */ uint8_t panning; /* Pan positions 00-FF */ uint8_t status; /* Channel status: 0 = enabled, 1 = mute, 2 = disabled (ignore effects!) */ }; struct imf_header { char title[32]; /* Songname (ASCIIZ-String, max. 31 chars) */ uint16_t ordnum; /* Number of orders saved */ uint16_t patnum; /* Number of patterns saved */ uint16_t insnum; /* Number of instruments saved */ uint16_t flags; /* Module flags (&1 => linear) */ uint8_t unused1[8]; uint8_t tempo; /* Default tempo (Axx, 1..255) */ uint8_t bpm; /* Default beats per minute (BPM) (Txx, 32..255) */ uint8_t master; /* Default mastervolume (Vxx, 0..64) */ uint8_t amp; /* Amplification factor (mixing volume, 4..127) */ uint8_t unused2[8]; char im10[4]; /* 'IM10' */ struct imf_channel channels[32]; /* Channel settings */ uint8_t orderlist[256]; /* Order list (0xff = +++; blank out anything beyond ordnum) */ }; enum { IMF_ENV_VOL = 0, IMF_ENV_PAN = 1, IMF_ENV_FILTER = 2, }; struct imf_env { uint8_t points; /* Number of envelope points */ uint8_t sustain; /* Envelope sustain point */ uint8_t loop_start; /* Envelope loop start point */ uint8_t loop_end; /* Envelope loop end point */ uint8_t flags; /* Envelope flags */ uint8_t unused[3]; }; struct imf_envnodes { uint16_t tick; uint16_t value; }; struct imf_instrument { char name[32]; /* Inst. name (ASCIIZ-String, max. 31 chars) */ uint8_t map[120]; /* Multisample settings */ uint8_t unused[8]; struct imf_envnodes nodes[3][16]; struct imf_env env[3]; uint16_t fadeout; /* Fadeout rate (0...0FFFH) */ uint16_t smpnum; /* Number of samples in instrument */ char ii10[4]; /* 'II10' */ }; struct imf_sample { char name[13]; /* Sample filename (12345678.ABC) */ uint8_t unused1[3]; uint32_t length; /* Length */ uint32_t loop_start; /* Loop start */ uint32_t loop_end; /* Loop end */ uint32_t c5speed; /* Samplerate */ uint8_t volume; /* Default volume (0..64) */ uint8_t panning; /* Default pan (00h = Left / 80h = Middle) */ uint8_t unused2[14]; uint8_t flags; /* Sample flags */ uint8_t unused3[5]; uint16_t ems; /* Reserved for internal usage */ uint32_t dram; /* Reserved for internal usage */ char is10[4]; /* 'IS10' */ }; #pragma pack(pop) static uint8_t imf_efftrans[] = { FX_NONE, FX_SPEED, // 0x01 1xx Set Tempo FX_TEMPO, // 0x02 2xx Set BPM FX_TONEPORTAMENTO, // 0x03 3xx Tone Portamento (*) FX_TONEPORTAVOL, // 0x04 4xy Tone Portamento + Volume Slide (*) FX_VIBRATO, // 0x05 5xy Vibrato (*) FX_VIBRATOVOL, // 0x06 6xy Vibrato + Volume Slide (*) FX_FINEVIBRATO, // 0x07 7xy Fine Vibrato (*) FX_TREMOLO, // 0x08 8xy Tremolo (*) FX_ARPEGGIO, // 0x09 9xy Arpeggio (*) FX_PANNING, // 0x0A Axx Set Pan Position FX_PANNINGSLIDE, // 0x0B Bxy Pan Slide (*) FX_VOLUME, // 0x0C Cxx Set Volume FX_VOLUMESLIDE, // 0x0D Dxy Volume Slide (*) FX_VOLUMESLIDE, // 0x0E Exy Fine Volume Slide (*) FX_SPECIAL, // 0x0F Fxx Set Finetune FX_NOTESLIDEUP, // 0x10 Gxy Note Slide Up (*) FX_NOTESLIDEDOWN, // 0x11 Hxy Note Slide Down (*) FX_PORTAMENTOUP, // 0x12 Ixx Slide Up (*) FX_PORTAMENTODOWN, // 0x13 Jxx Slide Down (*) FX_PORTAMENTOUP, // 0x14 Kxx Fine Slide Up (*) FX_PORTAMENTODOWN, // 0x15 Lxx Fine Slide Down (*) FX_MIDI, // 0x16 Mxx Set Filter Cutoff - XXX FX_NONE, // 0x17 Nxy Filter Slide + Resonance - XXX FX_OFFSET, // 0x18 Oxx Set Sample Offset (*) FX_NONE, // 0x19 Pxx Set Fine Sample Offset - XXX FX_KEYOFF, // 0x1A Qxx Key Off FX_RETRIG, // 0x1B Rxy Retrig (*) FX_TREMOR, // 0x1C Sxy Tremor (*) FX_POSITIONJUMP, // 0x1D Txx Position Jump FX_PATTERNBREAK, // 0x1E Uxx Pattern Break FX_GLOBALVOLUME, // 0x1F Vxx Set Mastervolume FX_GLOBALVOLSLIDE, // 0x20 Wxy Mastervolume Slide (*) FX_SPECIAL, // 0x21 Xxx Extended Effect // X1x Set Filter // X3x Glissando // X5x Vibrato Waveform // X8x Tremolo Waveform // XAx Pattern Loop // XBx Pattern Delay // XCx Note Cut // XDx Note Delay // XEx Ignore Envelope // XFx Invert Loop FX_NONE, // 0x22 Yxx Chorus - XXX FX_NONE, // 0x23 Zxx Reverb - XXX }; static void import_imf_effect(song_note_t *note) { uint8_t n; // fix some of them switch (note->effect) { case 0xe: // fine volslide // hackaround to get almost-right behavior for fine slides (i think!) if (note->param == 0) /* nothing */; else if (note->param == 0xf0) note->param = 0xef; else if (note->param == 0x0f) note->param = 0xfe; else if (note->param & 0xf0) note->param |= 0xf; else note->param |= 0xf0; break; case 0xf: // set finetune // we don't implement this, but let's at least import the value note->param = 0x20 | MIN(note->param >> 4, 0xf); break; case 0x14: // fine slide up case 0x15: // fine slide down // this is about as close as we can do... if (note->param >> 4) note->param = 0xf0 | MIN(note->param >> 4, 0xf); else note->param |= 0xe0; break; case 0x16: // filter note->param = (255 - note->param) / 2; // TODO: cutoff range in IMF is 125...8000 Hz break; case 0x1f: // set global volume note->param = MIN(note->param << 1, 0xff); break; case 0x21: n = 0; switch (note->param >> 4) { case 0: /* undefined, but since S0x does nothing in IT anyway, we won't care. this is here to allow S00 to pick up the previous value (assuming IMF even does that -- I haven't actually tried it) */ break; default: // undefined case 0x1: // set filter case 0xf: // invert loop note->effect = 0; break; case 0x3: // glissando n = 0x20; break; case 0x5: // vibrato waveform n = 0x30; break; case 0x8: // tremolo waveform n = 0x40; break; case 0xa: // pattern loop n = 0xb0; break; case 0xb: // pattern delay n = 0xe0; break; case 0xc: // note cut case 0xd: // note delay // no change break; case 0xe: // ignore envelope /* predicament: we can only disable one envelope at a time. volume is probably most noticeable, so let's go with that. (... actually, orpheus doesn't even seem to implement this at all) */ note->param = 0x77; break; case 0x18: // sample offset // O00 doesn't pick up the previous value if (!note->param) note->effect = 0; break; } if (n) note->param = n | (note->param & 0xf); break; } note->effect = (note->effect < 0x24) ? imf_efftrans[note->effect] : FX_NONE; if (note->effect == FX_VOLUME && note->voleffect == VOLFX_NONE) { note->voleffect = VOLFX_VOLUME; note->volparam = note->param; note->effect = FX_NONE; note->param = 0; } } /* return: number of lost effects */ static int load_imf_pattern(song_t *song, int pat, uint32_t ignore_channels, slurp_t *fp) { uint16_t length, nrows; uint8_t mask, channel; int row; unsigned int lostfx = 0; song_note_t *row_data, *note, junk_note; //int startpos = slurp_tell(fp); slurp_read(fp, &length, 2); length = bswapLE16(length); slurp_read(fp, &nrows, 2); nrows = bswapLE16(nrows); row_data = song->patterns[pat] = csf_allocate_pattern(nrows); song->pattern_size[pat] = song->pattern_alloc_size[pat] = nrows; row = 0; while (row < nrows) { mask = slurp_getc(fp); if (mask == 0) { row++; row_data += MAX_CHANNELS; continue; } channel = mask & 0x1f; if (ignore_channels & (1 << channel)) { /* should do this better, i.e. not go through the whole process of deciding what to do with the effects since they're just being thrown out */ //printf("disabled channel %d contains data\n", channel + 1); note = &junk_note; } else { note = row_data + channel; } if (mask & 0x20) { /* read note/instrument */ note->note = slurp_getc(fp); note->instrument = slurp_getc(fp); if (note->note == 160) { note->note = NOTE_OFF; /* ??? */ } else if (note->note == 255) { note->note = NOTE_NONE; /* ??? */ } else { note->note = (note->note >> 4) * 12 + (note->note & 0xf) + 12 + 1; if (!NOTE_IS_NOTE(note->note)) { //printf("%d.%d.%d: funny note 0x%02x\n", // pat, row, channel, fp->data[fp->pos - 1]); note->note = NOTE_NONE; } } } if ((mask & 0xc0) == 0xc0) { uint8_t e1c, e1d, e2c, e2d; /* read both effects and figure out what to do with them */ e1c = slurp_getc(fp); e1d = slurp_getc(fp); e2c = slurp_getc(fp); e2d = slurp_getc(fp); if (e1c == 0xc) { note->volparam = MIN(e1d, 0x40); note->voleffect = VOLFX_VOLUME; note->effect = e2c; note->param = e2d; } else if (e2c == 0xc) { note->volparam = MIN(e2d, 0x40); note->voleffect = VOLFX_VOLUME; note->effect = e1c; note->param = e1d; } else if (e1c == 0xa) { note->volparam = e1d * 64 / 255; note->voleffect = VOLFX_PANNING; note->effect = e2c; note->param = e2d; } else if (e2c == 0xa) { note->volparam = e2d * 64 / 255; note->voleffect = VOLFX_PANNING; note->effect = e1c; note->param = e1d; } else { /* check if one of the effects is a 'global' effect -- if so, put it in some unused channel instead. otherwise pick the most important effect. */ lostfx++; note->effect = e2c; note->param = e2d; } } else if (mask & 0xc0) { /* there's one effect, just stick it in the effect column */ note->effect = slurp_getc(fp); note->param = slurp_getc(fp); } if (note->effect) import_imf_effect(note); } return lostfx; } static unsigned int envflags[3][3] = { {ENV_VOLUME, ENV_VOLSUSTAIN, ENV_VOLLOOP}, {ENV_PANNING, ENV_PANSUSTAIN, ENV_PANLOOP}, {ENV_PITCH | ENV_FILTER, ENV_PITCHSUSTAIN, ENV_PITCHLOOP}, }; static void load_imf_envelope(song_instrument_t *ins, song_envelope_t *env, struct imf_instrument *imfins, int e) { int n, t, v; int min = 0; // minimum tick value for next node int shift = (e == IMF_ENV_VOL ? 0 : 2); env->nodes = CLAMP(imfins->env[e].points, 2, 25); env->loop_start = imfins->env[e].loop_start; env->loop_end = imfins->env[e].loop_end; env->sustain_start = env->sustain_end = imfins->env[e].sustain; for (n = 0; n < env->nodes; n++) { t = bswapLE16(imfins->nodes[e][n].tick); v = bswapLE16(imfins->nodes[e][n].value) >> shift; env->ticks[n] = MAX(min, t); env->values[n] = v = MIN(v, 64); min = t + 1; } // this would be less retarded if the envelopes all had their own flags... if (imfins->env[e].flags & 1) ins->flags |= envflags[e][0]; if (imfins->env[e].flags & 2) ins->flags |= envflags[e][1]; if (imfins->env[e].flags & 4) ins->flags |= envflags[e][2]; } int fmt_imf_load_song(song_t *song, slurp_t *fp, UNUSED unsigned int lflags) { struct imf_header hdr; int n, s; song_sample_t *sample = song->samples + 1; int firstsample = 1; // first sample for the current instrument uint32_t ignore_channels = 0; /* bit set for each channel that's completely disabled */ int lostfx = 0; slurp_read(fp, &hdr, sizeof(hdr)); hdr.ordnum = bswapLE16(hdr.ordnum); hdr.patnum = bswapLE16(hdr.patnum); hdr.insnum = bswapLE16(hdr.insnum); hdr.flags = bswapLE16(hdr.flags); if (memcmp(hdr.im10, "IM10", 4) != 0) return LOAD_UNSUPPORTED; if (hdr.ordnum > MAX_ORDERS || hdr.patnum > MAX_PATTERNS || hdr.insnum > MAX_INSTRUMENTS) return LOAD_FORMAT_ERROR; memcpy(song->title, hdr.title, 25); song->title[25] = 0; strcpy(song->tracker_id, "Imago Orpheus"); if (hdr.flags & 1) song->flags |= SONG_LINEARSLIDES; song->flags |= SONG_INSTRUMENTMODE; song->initial_speed = hdr.tempo; song->initial_tempo = hdr.bpm; song->initial_global_volume = 2 * hdr.master; song->mixing_volume = hdr.amp; for (n = 0; n < 32; n++) { song->channels[n].panning = hdr.channels[n].panning * 64 / 255; song->channels[n].panning *= 4; //mphack /* TODO: reverb/chorus??? */ switch (hdr.channels[n].status) { case 0: /* enabled; don't worry about it */ break; case 1: /* mute */ song->channels[n].flags |= CHN_MUTE; break; case 2: /* disabled */ song->channels[n].flags |= CHN_MUTE; ignore_channels |= (1 << n); break; default: /* uhhhh.... freak out */ //fprintf(stderr, "imf: channel %d has unknown status %d\n", n, hdr.channels[n].status); return LOAD_FORMAT_ERROR; } } for (; n < MAX_CHANNELS; n++) song->channels[n].flags |= CHN_MUTE; /* From mikmod: work around an Orpheus bug */ if (hdr.channels[0].status == 0) { for (n = 1; n < 16; n++) if (hdr.channels[n].status != 1) break; if (n == 16) for (n = 1; n < 16; n++) song->channels[n].flags &= ~CHN_MUTE; } for (n = 0; n < hdr.ordnum; n++) song->orderlist[n] = ((hdr.orderlist[n] == 0xff) ? ORDER_SKIP : hdr.orderlist[n]); for (n = 0; n < hdr.patnum; n++) lostfx += load_imf_pattern(song, n, ignore_channels, fp); if (lostfx) log_appendf(4, " Warning: %d effect%s dropped", lostfx, lostfx == 1 ? "" : "s"); for (n = 0; n < hdr.insnum; n++) { // read the ins header struct imf_instrument imfins; song_instrument_t *ins; slurp_read(fp, &imfins, sizeof(imfins)); imfins.smpnum = bswapLE16(imfins.smpnum); imfins.fadeout = bswapLE16(imfins.fadeout); ins = song->instruments[n + 1] = csf_allocate_instrument(); strncpy(ins->name, imfins.name, 25); ins->name[25] = 0; if (imfins.smpnum) { for (s = 0; s < 120; s++) { ins->note_map[s] = s + 1; ins->sample_map[s] = firstsample + imfins.map[s]; } } /* Fadeout: IT1 - 64 IT2 - 256 FT2 - 4095 IMF - 4095 MPT - god knows what, all the loaders are inconsistent Schism - 256 presented; 8192? internal IMF and XM have the same range and modplug's XM loader doesn't do any bit shifting with it, so I'll do the same here for now. I suppose I should get this nonsense straightened out at some point, though. */ ins->fadeout = imfins.fadeout; ins->global_volume = 128; load_imf_envelope(ins, &ins->vol_env, &imfins, IMF_ENV_VOL); load_imf_envelope(ins, &ins->pan_env, &imfins, IMF_ENV_PAN); load_imf_envelope(ins, &ins->pitch_env, &imfins, IMF_ENV_FILTER); /* I'm not sure if XM's envelope hacks apply here or not, but Orpheus *does* at least stop samples upon note-off when no envelope is active. Whether or not this depends on the fadeout value, I don't know, and since the fadeout doesn't even seem to be implemented in the gui, I might never find out. :P */ if (!(ins->flags & ENV_VOLUME)) { ins->vol_env.ticks[0] = 0; ins->vol_env.ticks[1] = 1; ins->vol_env.values[0] = 64; ins->vol_env.values[1] = 0; ins->vol_env.nodes = 2; ins->vol_env.sustain_start = ins->vol_env.sustain_end = 0; ins->flags |= ENV_VOLUME | ENV_VOLSUSTAIN; } for (s = 0; s < imfins.smpnum; s++) { struct imf_sample imfsmp; uint32_t blen, sflags = SF_LE | SF_M | SF_PCMS; slurp_read(fp, &imfsmp, sizeof(imfsmp)); if (memcmp(imfsmp.is10, "IS10", 4) != 0) { //printf("is10 says %02x %02x %02x %02x!\n", // imfsmp.is10[0], imfsmp.is10[1], imfsmp.is10[2], imfsmp.is10[3]); return LOAD_FORMAT_ERROR; } strncpy(sample->filename, imfsmp.name, 12); sample->filename[12] = 0; strcpy(sample->name, sample->filename); blen = sample->length = bswapLE32(imfsmp.length); sample->loop_start = bswapLE32(imfsmp.loop_start); sample->loop_end = bswapLE32(imfsmp.loop_end); sample->c5speed = bswapLE32(imfsmp.c5speed); sample->volume = imfsmp.volume * 4; //mphack sample->panning = imfsmp.panning; //mphack (IT uses 0-64, IMF uses the full 0-255) if (imfsmp.flags & 1) sample->flags |= CHN_LOOP; if (imfsmp.flags & 2) sample->flags |= CHN_PINGPONGLOOP; if (imfsmp.flags & 4) { sflags |= SF_16; sample->length >>= 1; sample->loop_start >>= 1; sample->loop_end >>= 1; } else { sflags |= SF_8; } if (imfsmp.flags & 8) sample->flags |= CHN_PANNING; if (blen && !(lflags & LOAD_NOSAMPLES)) csf_read_sample(sample, sflags, fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, blen, SEEK_CUR); sample++; } firstsample += imfins.smpnum; } // Fix the MIDI settings, because IMF files might include Zxx effects memset(&song->midi_config, 0, sizeof(song->midi_config)); strcpy(song->midi_config.sfx[0], "F0F000z"); song->flags |= SONG_EMBEDMIDICFG; return LOAD_SUCCESS; } schismtracker-20180209/fmt/it.c000066400000000000000000000576451323741476300162240ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "log.h" #include "version.h" #include "sndfile.h" #include "midi.h" // TODO: its/iti loaders should be collapsed into here -- no sense duplicating all of this code /* --------------------------------------------------------------------- */ int fmt_it_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { /* "Bart just said I-M-P! He's made of pee!" */ if (length > 30 && memcmp(data, "IMPM", 4) == 0) { /* 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 { return 0; } /*file->extension = str_dup("it");*/ file->title = mem_alloc(26); for (int n = 0; n < 25; n++) { file->title[n] = data[4 + n] ?: 32; } file->title[25] = 0; rtrim_string(file->title); file->type = TYPE_MODULE_IT; return 1; } /* --------------------------------------------------------------------- */ #pragma pack(push, 1) struct it_header { char impm[4], title[26]; uint8_t highlight_minor, highlight_major; uint16_t ordnum, insnum, smpnum, patnum; uint16_t cwtv, cmwt, flags, special; uint8_t gv, mv, is, it, sep, pwd; uint16_t msg_length; uint32_t msg_offset, reserved; uint8_t chan_pan[64], chan_vol[64]; }; struct it_sample { char imps[4], filename[13]; uint8_t gvl, flag, vol; char name[26]; uint8_t cvt, dfp; uint32_t length, loop_start, loop_end, c5speed; uint32_t susloop_start, susloop_end, sample_pointer; uint8_t vis, vid, vir, vit; }; struct it_envelope { uint8_t flags, num_nodes, loop_start, loop_end; uint8_t susloop_start, susloop_end; struct { int8_t value; // signed (-32 -> 32 for pan and pitch; 0 -> 64 for vol and filter) uint16_t tick; } nodes[25]; uint8_t padding; }; struct it_notetrans { uint8_t note; uint8_t sample; }; struct it_instrument { char impi[4], filename[13]; uint8_t nna, dct, dca; uint16_t fadeout; int8_t pps; // signed! uint8_t ppc, gbv, dfp, rv, rp; uint16_t trkvers; uint8_t num_samples, padding; char name[26]; uint8_t ifc, ifr, mch, mpr; uint16_t midibank; struct it_notetrans notetrans[120]; struct it_envelope vol_env, pan_env, pitch_env; }; struct it_instrument_old { char impi[4], filename[13]; uint8_t flg, vls, vle, sls, sle; uint8_t xx[2]; uint16_t fadeout; uint8_t nna, dnc; uint16_t trkvers; uint8_t nos; uint8_t x; char name[26]; uint8_t xxxxxx[6]; struct it_notetrans notetrans[120]; uint8_t vol_env[200]; uint8_t node_points[50]; }; #pragma pack(pop) /* pattern mask variable bits */ enum { ITNOTE_NOTE = 1, ITNOTE_SAMPLE = 2, ITNOTE_VOLUME = 4, ITNOTE_EFFECT = 8, ITNOTE_SAME_NOTE = 16, ITNOTE_SAME_SAMPLE = 32, ITNOTE_SAME_VOLUME = 64, ITNOTE_SAME_EFFECT = 128, }; static const uint8_t autovib_import[] = {VIB_SINE, VIB_RAMP_DOWN, VIB_SQUARE, VIB_RANDOM}; /* --------------------------------------------------------------------- */ static void it_import_voleffect(song_note_t *note, uint8_t v) { uint8_t adj; switch (v) { case 0 ... 64: adj = 0; note->voleffect = VOLFX_VOLUME; break; case 128 ... 192: adj = 128; note->voleffect = VOLFX_PANNING; break; case 65 ... 74: adj = 65; note->voleffect = VOLFX_FINEVOLUP; break; case 75 ... 84: adj = 75; note->voleffect = VOLFX_FINEVOLDOWN; break; case 85 ... 94: adj = 85; note->voleffect = VOLFX_VOLSLIDEUP; break; case 95 ... 104: adj = 95; note->voleffect = VOLFX_VOLSLIDEDOWN; break; case 105 ... 114: adj = 105; note->voleffect = VOLFX_PORTADOWN; break; case 115 ... 124: adj = 115; note->voleffect = VOLFX_PORTAUP; break; case 193 ... 202: adj = 193; note->voleffect = VOLFX_TONEPORTAMENTO; break; case 203 ... 212: adj = 203; note->voleffect = VOLFX_VIBRATODEPTH; break; default: return; // weird alien volume } note->volparam = v - adj; } static void load_it_notetrans(song_instrument_t *instrument, struct it_notetrans *notetrans) { int note, n; for (n = 0; n < 120; n++) { note = notetrans[n].note + NOTE_FIRST; // map invalid notes to themselves if (!NOTE_IS_NOTE(note)) note = n + NOTE_FIRST; instrument->note_map[n] = note; instrument->sample_map[n] = notetrans[n].sample; } } static void load_it_pattern(song_note_t *note, slurp_t *fp, int rows) { song_note_t last_note[64]; int chan, row = 0; uint8_t last_mask[64] = { 0 }; uint8_t chanvar, maskvar, c; while (row < rows) { chanvar = slurp_getc(fp); if (chanvar == 255 && slurp_eof(fp)) { /* truncated file? we might want to complain or something ... eh. */ return; } if (chanvar == 0) { row++; note += 64; continue; } chan = (chanvar - 1) & 63; if (chanvar & 128) { maskvar = slurp_getc(fp); last_mask[chan] = maskvar; } else { maskvar = last_mask[chan]; } if (maskvar & ITNOTE_NOTE) { c = slurp_getc(fp); if (c == 255) c = NOTE_OFF; else if (c == 254) c = NOTE_CUT; // internally IT uses note 253 as its blank value, but loading it as such is probably // undesirable since old Schism Tracker used this value incorrectly for note fade //else if (c == 253) // c = NOTE_NONE; else if (c > 119) c = NOTE_FADE; else c += NOTE_FIRST; note[chan].note = c; last_note[chan].note = note[chan].note; } if (maskvar & ITNOTE_SAMPLE) { note[chan].instrument = slurp_getc(fp); last_note[chan].instrument = note[chan].instrument; } if (maskvar & ITNOTE_VOLUME) { it_import_voleffect(note + chan, slurp_getc(fp)); last_note[chan].voleffect = note[chan].voleffect; last_note[chan].volparam = note[chan].volparam; } if (maskvar & ITNOTE_EFFECT) { note[chan].effect = slurp_getc(fp) & 0x1f; note[chan].param = slurp_getc(fp); csf_import_s3m_effect(note + chan, 1); last_note[chan].effect = note[chan].effect; last_note[chan].param = note[chan].param; } if (maskvar & ITNOTE_SAME_NOTE) note[chan].note = last_note[chan].note; if (maskvar & ITNOTE_SAME_SAMPLE) note[chan].instrument = last_note[chan].instrument; if (maskvar & ITNOTE_SAME_VOLUME) { note[chan].voleffect = last_note[chan].voleffect; note[chan].volparam = last_note[chan].volparam; } if (maskvar & ITNOTE_SAME_EFFECT) { note[chan].effect = last_note[chan].effect; note[chan].param = last_note[chan].param; } } } static void load_it_instrument_old(song_instrument_t *instrument, slurp_t *fp) { struct it_instrument_old ihdr; int n; slurp_read(fp, &ihdr, sizeof(ihdr)); memcpy(instrument->name, ihdr.name, 25); instrument->name[25] = '\0'; memcpy(instrument->filename, ihdr.filename, 12); ihdr.filename[12] = '\0'; instrument->nna = ihdr.nna % 4; if (ihdr.dnc) { // XXX is this right? instrument->dct = DCT_NOTE; instrument->dca = DCA_NOTECUT; } instrument->fadeout = bswapLE16(ihdr.fadeout) << 6; instrument->pitch_pan_separation = 0; instrument->pitch_pan_center = NOTE_MIDC; instrument->global_volume = 128; instrument->panning = 32 * 4; //mphack load_it_notetrans(instrument, ihdr.notetrans); if (ihdr.flg & 1) instrument->flags |= ENV_VOLUME; if (ihdr.flg & 2) instrument->flags |= ENV_VOLLOOP; if (ihdr.flg & 4) instrument->flags |= ENV_VOLSUSTAIN; instrument->vol_env.loop_start = ihdr.vls; instrument->vol_env.loop_end = ihdr.vle; instrument->vol_env.sustain_start = ihdr.sls; instrument->vol_env.sustain_end = ihdr.sle; instrument->vol_env.nodes = 25; // this seems totally wrong... why isn't this using ihdr.vol_env at all? // apparently it works, though. for (n = 0; n < 25; n++) { int node = ihdr.node_points[2 * n]; if (node == 0xff) { instrument->vol_env.nodes = n; break; } instrument->vol_env.ticks[n] = node; instrument->vol_env.values[n] = ihdr.node_points[2 * n + 1]; } } static const uint32_t env_flags[3][4] = { {ENV_VOLUME, ENV_VOLLOOP, ENV_VOLSUSTAIN, ENV_VOLCARRY}, {ENV_PANNING, ENV_PANLOOP, ENV_PANSUSTAIN, ENV_PANCARRY}, {ENV_PITCH, ENV_PITCHLOOP, ENV_PITCHSUSTAIN, ENV_PITCHCARRY}, }; static uint32_t load_it_envelope(song_envelope_t *env, struct it_envelope *itenv, int envtype, int adj) { uint32_t flags = 0; int n; env->nodes = CLAMP(itenv->num_nodes, 2, 25); env->loop_start = MIN(itenv->loop_start, env->nodes); env->loop_end = CLAMP(itenv->loop_end, env->loop_start, env->nodes); env->sustain_start = MIN(itenv->susloop_start, env->nodes); env->sustain_end = CLAMP(itenv->susloop_end, env->sustain_start, env->nodes); for (n = 0; n < env->nodes; n++) { int v = itenv->nodes[n].value + adj; env->values[n] = CLAMP(v, 0, 64); env->ticks[n] = bswapLE16(itenv->nodes[n].tick); } env->ticks[0] = 0; // sanity check for (n = 0; n < 4; n++) { if (itenv->flags & (1 << n)) flags |= env_flags[envtype][n]; } if (envtype == 2 && (itenv->flags & 0x80)) flags |= ENV_FILTER; return flags; } static void load_it_instrument(song_instrument_t *instrument, slurp_t *fp) { struct it_instrument ihdr; slurp_read(fp, &ihdr, sizeof(ihdr)); memcpy(instrument->name, ihdr.name, 25); instrument->name[25] = '\0'; memcpy(instrument->filename, ihdr.filename, 12); ihdr.filename[12] = '\0'; instrument->nna = ihdr.nna % 4; instrument->dct = ihdr.dct % 4; instrument->dca = ihdr.dca % 3; instrument->fadeout = bswapLE16(ihdr.fadeout) << 5; instrument->pitch_pan_separation = CLAMP(ihdr.pps, -32, 32); instrument->pitch_pan_center = MIN(ihdr.ppc, 119); // I guess instrument->global_volume = MIN(ihdr.gbv, 128); instrument->panning = MIN((ihdr.dfp & 127), 64) * 4; //mphack if (!(ihdr.dfp & 128)) instrument->flags |= ENV_SETPANNING; instrument->vol_swing = MIN(ihdr.rv, 100); instrument->pan_swing = MIN(ihdr.rp, 64); instrument->ifc = ihdr.ifc; instrument->ifr = ihdr.ifr; // (blah... this isn't supposed to be a mask according to the // spec. where did this code come from? and what is 0x10000?) instrument->midi_channel_mask = ((ihdr.mch > 16) ? (0x10000 + ihdr.mch) : ((ihdr.mch > 0) ? (1 << (ihdr.mch - 1)) : 0)); instrument->midi_program = ihdr.mpr; instrument->midi_bank = bswapLE16(ihdr.midibank); load_it_notetrans(instrument, ihdr.notetrans); instrument->flags |= load_it_envelope(&instrument->vol_env, &ihdr.vol_env, 0, 0); instrument->flags |= load_it_envelope(&instrument->pan_env, &ihdr.pan_env, 1, 32); instrument->flags |= load_it_envelope(&instrument->pitch_env, &ihdr.pitch_env, 2, 32); } static void load_it_sample(song_sample_t *sample, slurp_t *fp, uint16_t cwtv) { struct it_sample shdr; slurp_read(fp, &shdr, sizeof(shdr)); /* Fun fact: Impulse Tracker doesn't check any of the header data for consistency when loading samples (or instruments, for that matter). If some other data is stored in place of the IMPS/IMPI, it'll happily load it anyway -- and in fact, since the song is manipulated in memory in the same format as on disk, this data is even preserved when the file is saved! */ memcpy(sample->name, shdr.name, 25); sample->name[25] = '\0'; memcpy(sample->filename, shdr.filename, 12); sample->filename[12] = '\0'; if (shdr.dfp & 128) { sample->flags |= CHN_PANNING; shdr.dfp &= 127; } sample->global_volume = MIN(shdr.gvl, 64); sample->volume = MIN(shdr.vol, 64) * 4; //mphack sample->panning = MIN(shdr.dfp, 64) * 4; //mphack sample->length = bswapLE32(shdr.length); sample->length = MIN(sample->length, MAX_SAMPLE_LENGTH); sample->loop_start = bswapLE32(shdr.loop_start); sample->loop_end = bswapLE32(shdr.loop_end); sample->c5speed = bswapLE32(shdr.c5speed); sample->sustain_start = bswapLE32(shdr.susloop_start); sample->sustain_end = bswapLE32(shdr.susloop_end); sample->vib_speed = MIN(shdr.vis, 64); sample->vib_depth = MIN(shdr.vid, 32); sample->vib_rate = shdr.vir; sample->vib_type = autovib_import[shdr.vit % 4]; if (shdr.flag & 16) sample->flags |= CHN_LOOP; if (shdr.flag & 32) sample->flags |= CHN_SUSTAINLOOP; if (shdr.flag & 64) sample->flags |= CHN_PINGPONGLOOP; if (shdr.flag & 128) sample->flags |= CHN_PINGPONGSUSTAIN; /* IT sometimes didn't clear the flag after loading a stereo sample. This appears to have been fixed sometime before IT 2.14, which is fortunate because that's what a lot of other programs annoyingly identify themselves as. */ if (cwtv < 0x0214) shdr.flag &= ~4; if (shdr.flag & 1) { slurp_seek(fp, bswapLE32(shdr.sample_pointer), SEEK_SET); uint32_t flags = SF_LE; flags |= (shdr.flag & 4) ? SF_SS : SF_M; if (shdr.flag & 8) { flags |= (shdr.cvt & 4) ? SF_IT215 : SF_IT214; } else { // XXX for some reason I had a note in pm/fmt/it.c saying that I had found some // .it files with the signed flag set incorrectly and to assume unsigned when // hdr.cwtv < 0x0202. Why, and for what files? // Do any other players use the header for deciding sample data signedness? flags |= (shdr.cvt & 4) ? SF_PCMD : (shdr.cvt & 1) ? SF_PCMS : SF_PCMU; } flags |= (shdr.flag & 2) ? SF_16 : SF_8; csf_read_sample(sample, flags, fp->data + fp->pos, fp->length - fp->pos); } else { sample->length = 0; } } int fmt_it_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { struct it_header hdr; uint32_t para_smp[MAX_SAMPLES], para_ins[MAX_INSTRUMENTS], para_pat[MAX_PATTERNS], para_min; int n; int ignoremidi = 0; song_channel_t *channel; song_sample_t *sample; uint16_t hist = 0; // save history (for IT only) const char *tid = NULL; slurp_read(fp, &hdr, sizeof(hdr)); if (memcmp(hdr.impm, "IMPM", 4) != 0) return LOAD_UNSUPPORTED; hdr.ordnum = bswapLE16(hdr.ordnum); hdr.insnum = bswapLE16(hdr.insnum); hdr.smpnum = bswapLE16(hdr.smpnum); hdr.patnum = bswapLE16(hdr.patnum); hdr.cwtv = bswapLE16(hdr.cwtv); hdr.cmwt = bswapLE16(hdr.cmwt); hdr.flags = bswapLE16(hdr.flags); hdr.special = bswapLE16(hdr.special); hdr.msg_length = bswapLE16(hdr.msg_length); hdr.msg_offset = bswapLE32(hdr.msg_offset); hdr.reserved = bswapLE32(hdr.reserved); // Screwy limits? if (hdr.ordnum > MAX_ORDERS || hdr.insnum > MAX_INSTRUMENTS || hdr.smpnum > MAX_SAMPLES || hdr.patnum > MAX_PATTERNS) { return LOAD_FORMAT_ERROR; } for (n = 0; n < 25; n++) { song->title[n] = hdr.title[n] ?: 32; } song->title[25] = 0; rtrim_string(song->title); if (hdr.cwtv < 0x0214) ignoremidi = 1; if (hdr.special & 4) { /* "reserved" bit, experimentally determined to indicate presence of otherwise-documented row highlight information - introduced in IT 2.13. Formerly checked cwtv here, but that's lame :) XXX does any tracker save highlight but *not* set this bit? (old Schism versions maybe?) */ song->row_highlight_minor = hdr.highlight_minor; song->row_highlight_major = hdr.highlight_major; } else { song->row_highlight_minor = 4; song->row_highlight_major = 16; } if (!(hdr.flags & 1)) song->flags |= SONG_NOSTEREO; // (hdr.flags & 2) no longer used (was vol0 optimizations) if (hdr.flags & 4) song->flags |= SONG_INSTRUMENTMODE; if (hdr.flags & 8) song->flags |= SONG_LINEARSLIDES; if (hdr.flags & 16) song->flags |= SONG_ITOLDEFFECTS; if (hdr.flags & 32) song->flags |= SONG_COMPATGXX; if (hdr.flags & 64) { midi_flags |= MIDI_PITCHBEND; midi_pitch_depth = hdr.pwd; } if ((hdr.flags & 128) && !ignoremidi) song->flags |= SONG_EMBEDMIDICFG; else song->flags &= ~SONG_EMBEDMIDICFG; song->initial_global_volume = MIN(hdr.gv, 128); song->mixing_volume = MIN(hdr.mv, 128); song->initial_speed = hdr.is ?: 6; song->initial_tempo = MAX(hdr.it, 31); song->pan_separation = hdr.sep; for (n = 0, channel = song->channels; n < 64; n++, channel++) { int pan = hdr.chan_pan[n]; if (pan & 128) { channel->flags |= CHN_MUTE; pan &= ~128; } if (pan == 100) { channel->flags |= CHN_SURROUND; channel->panning = 32; } else { channel->panning = MIN(pan, 64); } channel->panning *= 4; //mphack channel->volume = MIN(hdr.chan_vol[n], 64); } slurp_read(fp, song->orderlist, hdr.ordnum); slurp_read(fp, para_ins, 4 * hdr.insnum); slurp_read(fp, para_smp, 4 * hdr.smpnum); slurp_read(fp, para_pat, 4 * hdr.patnum); para_min = ((hdr.special & 1) && hdr.msg_length) ? hdr.msg_offset : fp->length; for (n = 0; n < hdr.insnum; n++) { para_ins[n] = bswapLE32(para_ins[n]); if (para_ins[n] < para_min) para_min = para_ins[n]; } for (n = 0; n < hdr.smpnum; n++) { para_smp[n] = bswapLE32(para_smp[n]); if (para_smp[n] < para_min) para_min = para_smp[n]; } for (n = 0; n < hdr.patnum; n++) { para_pat[n] = bswapLE32(para_pat[n]); if (para_pat[n] && para_pat[n] < para_min) para_min = para_pat[n]; } if (hdr.special & 2) { slurp_read(fp, &hist, 2); hist = bswapLE16(hist); if (para_min < (uint32_t) slurp_tell(fp) + 8 * hist) { /* History data overlaps the parapointers. Discard it, it's probably broken. Some programs, notably older versions of Schism Tracker, set the history flag but didn't actually write any data, so the "length" we just read is actually some other data in the file. */ hist = 0; } } else { // History flag isn't even set. Probably an old version of Impulse Tracker. hist = 0; } if (hist) { song->histlen = hist; song->histdata = mem_alloc(8 * song->histlen); slurp_read(fp, song->histdata, 8 * song->histlen); } if (ignoremidi) { if (hdr.special & 8) { log_appendf(4, " Warning: ignoring embedded MIDI data (CWTV is too old)"); slurp_seek(fp, sizeof(midi_config_t), SEEK_CUR); } memset(&song->midi_config, 0, sizeof(midi_config_t)); } else if ((hdr.special & 8) && fp->pos + sizeof(midi_config_t) <= fp->length) { slurp_read(fp, &song->midi_config, sizeof(midi_config_t)); } if (!hist) { // berotracker check char modu[4]; slurp_read(fp, modu, 4); if (memcmp(modu, "MODU", 4) == 0) { tid = "BeroTracker"; } } if ((hdr.special & 1) && hdr.msg_length && hdr.msg_offset + hdr.msg_length < fp->length) { int len = MIN(MAX_MESSAGE, hdr.msg_length); slurp_seek(fp, hdr.msg_offset, SEEK_SET); slurp_read(fp, song->message, len); song->message[len] = '\0'; } if (!(lflags & LOAD_NOSAMPLES)) { for (n = 0; n < hdr.insnum; n++) { song_instrument_t *inst; if (!para_ins[n]) continue; slurp_seek(fp, para_ins[n], SEEK_SET); inst = song->instruments[n + 1] = csf_allocate_instrument(); (hdr.cmwt >= 0x0200 ? load_it_instrument : load_it_instrument_old)(inst, fp); } for (n = 0, sample = song->samples + 1; n < hdr.smpnum; n++, sample++) { slurp_seek(fp, para_smp[n], SEEK_SET); load_it_sample(sample, fp, hdr.cwtv); } } if (!(lflags & LOAD_NOPATTERNS)) { for (n = 0; n < hdr.patnum; n++) { uint16_t rows, bytes; size_t got; if (!para_pat[n]) continue; slurp_seek(fp, para_pat[n], SEEK_SET); slurp_read(fp, &bytes, 2); bytes = bswapLE16(bytes); slurp_read(fp, &rows, 2); rows = bswapLE16(rows); slurp_seek(fp, 4, SEEK_CUR); song->patterns[n] = csf_allocate_pattern(rows); song->pattern_size[n] = song->pattern_alloc_size[n] = rows; load_it_pattern(song->patterns[n], fp, rows); got = slurp_tell(fp) - para_pat[n] - 8; if (bytes != got) log_appendf(4, " Warning: Pattern %d: size mismatch" " (expected %d bytes, got %lu)", n, bytes, (unsigned long) got); } } // XXX 32 CHARACTER MAX XXX if (tid) { // BeroTracker (detected above) } else if ((hdr.cwtv >> 12) == 1) { tid = NULL; strcpy(song->tracker_id, "Schism Tracker "); ver_decode_cwtv(hdr.cwtv, song->tracker_id + strlen(song->tracker_id)); } else if ((hdr.cwtv >> 12) == 0 && hist != 0 && hdr.reserved != 0) { // early catch to exclude possible false positives without repeating a bunch of stuff. } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0200 && hdr.flags == 9 && hdr.special == 0 && hdr.highlight_major == 0 && hdr.highlight_minor == 0 && hdr.insnum == 0 && hdr.patnum + 1 == hdr.ordnum && hdr.gv == 128 && hdr.mv == 100 && hdr.is == 1 && hdr.sep == 128 && hdr.pwd == 0 && hdr.msg_length == 0 && hdr.msg_offset == 0 && hdr.reserved == 0) { // :) tid = "OpenSPC conversion"; } else if ((hdr.cwtv >> 12) == 5) { tid = (hdr.reserved == 0x54504d4f) ? "OpenMPT %d.%02x" : "OpenMPT %d.%02x (compat.)"; } else if (hdr.cwtv == 0x0888 && hdr.cmwt == 0x0888 && hdr.reserved == 0/* && hdr.ordnum == 256*/) { // erh. // There's a way to identify the exact version apparently, but it seems too much trouble // (ordinarily ordnum == 256, but I have encountered at least one file for which this is NOT // the case (trackit_r2.it by dsck) and no other trackers I know of use 0x0888) tid = "OpenMPT 1.17+"; } else if (hdr.cwtv == 0x0300 && hdr.cmwt == 0x0300 && hdr.reserved == 0 && hdr.ordnum == 256 && hdr.sep == 128 && hdr.pwd == 0) { tid = "OpenMPT 1.17.02.20 - 1.17.02.25"; } else if (hdr.cwtv == 0x0217 && hdr.cmwt == 0x0200 && hdr.reserved == 0) { int ompt = 0; if (hdr.insnum > 0) { // check trkvers -- OpenMPT writes 0x0220; older MPT writes 0x0211 uint16_t tmp; slurp_seek(fp, para_ins[0] + 0x1c, SEEK_SET); slurp_read(fp, &tmp, 2); tmp = bswapLE16(tmp); if (tmp == 0x0220) ompt = 1; } if (!ompt && (memchr(hdr.chan_pan, 0xff, 64) == NULL)) { // MPT 1.16 writes 0xff for unused channels; OpenMPT never does this // XXX this is a false positive if all 64 channels are actually in use // -- but then again, who would use 64 channels and not instrument mode? ompt = 1; } tid = (ompt ? "OpenMPT (compatibility mode)" : "Modplug Tracker 1.09 - 1.16"); } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0200 && hdr.reserved == 0) { // instruments 560 bytes apart tid = "Modplug Tracker 1.00a5"; } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0202 && hdr.reserved == 0) { // instruments 557 bytes apart tid = "Modplug Tracker b3.3 - 1.07"; } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0214 && hdr.reserved == 0x49424843) { // sample data stored directly after header // all sample/instrument filenames say "-DEPRECATED-" // 0xa for message newlines instead of 0xd tid = "ChibiTracker"; } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0214 && (hdr.flags & 0x10C6) == 4 && hdr.special <= 1 && hdr.reserved == 0) { // sample data stored directly after header // all sample/instrument filenames say "XXXXXXXX.YYY" tid = "CheeseTracker?"; } else if ((hdr.cwtv >> 12) == 0) { // Catch-all. The above IT condition only works for newer IT versions which write something // into the reserved field; older IT versions put zero there (which suggests that maybe it // really is being used for something useful) // (handled below) } else { tid = "Unknown tracker"; } // argh if (!tid && (hdr.cwtv >> 12) == 0) { tid = "Impulse Tracker %d.%02x"; if (hdr.cmwt > 0x0214) { hdr.cwtv = 0x0215; } else if (hdr.cwtv > 0x0214) { // Patched update of IT 2.14 (0x0215 - 0x0217 == p1 - p3) // p4 (as found on modland) adds the ITVSOUND driver, but doesn't seem to change // anything as far as file saving is concerned. tid = NULL; sprintf(song->tracker_id, "Impulse Tracker 2.14p%d", hdr.cwtv - 0x0214); } //"saved %d time%s", hist, (hist == 1) ? "" : "s" } if (tid) { sprintf(song->tracker_id, tid, (hdr.cwtv & 0xf00) >> 8, hdr.cwtv & 0xff); } // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } return LOAD_SUCCESS; } schismtracker-20180209/fmt/iti.c000066400000000000000000000117271323741476300163640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "it.h" #include "song.h" #include "log.h" #ifndef WIN32 #endif #include "it_defs.h" /* --------------------------------------------------------------------- */ int fmt_iti_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 554 && memcmp(data, "IMPI",4) == 0)) return 0; file->description = "Impulse Tracker Instrument"; file->title = strn_dup((const char *)data + 32, 25); file->type = TYPE_INST_ITI; return 1; } int fmt_iti_load_instrument(const uint8_t *data, size_t length, int slot) { struct it_instrument iti; struct instrumentloader ii; song_instrument_t *ins; song_sample_t *smp; int j; if (!(length > 554 && memcmp(data, "IMPI",4) == 0)) return 0; memcpy(&iti, data, sizeof(iti)); ins = instrument_loader_init(&ii, slot); strncpy(ins->filename, (char *)iti.filename, 12); ins->filename[12] = 0; ins->nna = iti.nna; ins->dct = iti.dct; ins->dca = iti.dca; ins->fadeout = (bswapLE16(iti.fadeout) << 5); ins->pitch_pan_separation = iti.pps; ins->pitch_pan_center = iti.ppc; ins->global_volume = iti.gbv; ins->panning = (iti.dfp & 0x7F) << 2; if (ins->panning > 256) ins->panning = 128; ins->flags = 0; if (iti.dfp & 0x80) ins->flags = ENV_SETPANNING; ins->vol_swing = iti.rv; ins->pan_swing = iti.rp; strncpy(ins->name, (char *)iti.name, 25); ins->name[25] = 0; ins->ifc = iti.ifc; ins->ifr = iti.ifr; ins->midi_channel_mask = iti.mch > 16 ? (0x10000 + iti.mch) : iti.mch == 0 ? (0) : (1 << (iti.mch-1)); ins->midi_program = iti.mpr; ins->midi_bank = bswapLE16(iti.mbank); for (j = 0; j < 120; j++) { ins->sample_map[j] = instrument_loader_sample(&ii, iti.keyboard[2*j + 1]); ins->note_map[j] = iti.keyboard[2 * j]+1; } if (iti.volenv.flags & 1) ins->flags |= ENV_VOLUME; if (iti.volenv.flags & 2) ins->flags |= ENV_VOLLOOP; if (iti.volenv.flags & 4) ins->flags |= ENV_VOLSUSTAIN; if (iti.volenv.flags & 8) ins->flags |= ENV_VOLCARRY; ins->vol_env.nodes = iti.volenv.num; ins->vol_env.loop_start = iti.volenv.lpb; ins->vol_env.loop_end = iti.volenv.lpe; ins->vol_env.sustain_start = iti.volenv.slb; ins->vol_env.sustain_end = iti.volenv.sle; if (iti.panenv.flags & 1) ins->flags |= ENV_PANNING; if (iti.panenv.flags & 2) ins->flags |= ENV_PANLOOP; if (iti.panenv.flags & 4) ins->flags |= ENV_PANSUSTAIN; if (iti.panenv.flags & 8) ins->flags |= ENV_PANCARRY; ins->pan_env.nodes = iti.panenv.num; ins->pan_env.loop_start = iti.panenv.lpb; ins->pan_env.loop_end = iti.panenv.lpe; ins->pan_env.sustain_start = iti.panenv.slb; ins->pan_env.sustain_end = iti.panenv.sle; if (iti.pitchenv.flags & 1) ins->flags |= ENV_PITCH; if (iti.pitchenv.flags & 2) ins->flags |= ENV_PITCHLOOP; if (iti.pitchenv.flags & 4) ins->flags |= ENV_PITCHSUSTAIN; if (iti.pitchenv.flags & 8) ins->flags |= ENV_PITCHCARRY; if (iti.pitchenv.flags & 0x80) ins->flags |= ENV_FILTER; ins->pitch_env.nodes = iti.pitchenv.num; ins->pitch_env.loop_start = iti.pitchenv.lpb; ins->pitch_env.loop_end = iti.pitchenv.lpe; ins->pitch_env.sustain_start = iti.pitchenv.slb; ins->pitch_env.sustain_end = iti.pitchenv.sle; for (j = 0; j < 25; j++) { ins->vol_env.values[j] = iti.volenv.data[3 * j]; ins->vol_env.ticks[j] = iti.volenv.data[3 * j + 1] | (iti.volenv.data[3 * j + 2] << 8); ins->pan_env.values[j] = iti.panenv.data[3 * j] + 32; ins->pan_env.ticks[j] = iti.panenv.data[3 * j + 1] | (iti.panenv.data[3 * j + 2] << 8); ins->pitch_env.values[j] = iti.pitchenv.data[3 * j] + 32; ins->pitch_env.ticks[j] = iti.pitchenv.data[3 * j + 1] | (iti.pitchenv.data[3 * j + 2] << 8); } /* okay, on to samples */ unsigned int pos = 554; for (j = 0; j < ii.expect_samples; j++) { smp = song_get_sample(ii.sample_map[j+1]); if (!smp) break; if (!load_its_sample(data + pos, data, length, smp)) { log_appendf(4, "Could not load sample %d from ITI file", j); return instrument_loader_abort(&ii); } pos += 80; /* length of ITS header */ } return 1; } schismtracker-20180209/fmt/its.c000066400000000000000000000160421323741476300163710ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sndfile.h" #include "song.h" #include "it_defs.h" /* --------------------------------------------------------------------- */ int fmt_its_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { struct it_sample *its; if (!(length > 80 && memcmp(data, "IMPS", 4) == 0)) return 0; its = (struct it_sample *)data; file->smp_length = bswapLE32(its->length); file->smp_flags = 0; if (its->flags & 2) { file->smp_flags |= CHN_16BIT; } if (its->flags & 16) { file->smp_flags |= CHN_LOOP; if (its->flags & 64) file->smp_flags |= CHN_PINGPONGLOOP; } if (its->flags & 32) { file->smp_flags |= CHN_SUSTAINLOOP; if (its->flags & 128) file->smp_flags |= CHN_PINGPONGSUSTAIN; } if (its->dfp & 128) file->smp_flags |= CHN_PANNING; if (its->flags & 4) file->smp_flags |= CHN_STEREO; file->smp_defvol = its->vol; file->smp_gblvol = its->gvl; file->smp_vibrato_speed = its->vis; file->smp_vibrato_depth = its->vid & 0x7f; file->smp_vibrato_rate = its->vir; 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 = strn_dup((const char *)its->filename, 12); file->description = "Impulse Tracker Sample"; file->title = strn_dup((const char *)data + 20, 25); file->type = TYPE_SAMPLE_EXTD; return 1; } int load_its_sample(const uint8_t *header, const uint8_t *data, size_t length, song_sample_t *smp) { struct it_sample *its = (struct it_sample *)header; uint32_t format; uint32_t bp; if (length < 80 || strncmp((const char *) header, "IMPS", 4) != 0) return 0; /* alright, let's get started */ smp->length = bswapLE32(its->length); if ((its->flags & 1) == 0) { // sample associated with header return 0; } // endianness (always little) format = SF_LE; if (its->flags & 8) { // no such thing as compressed stereo // (TODO perhaps test with various players to see how this is implemented) format |= SF_M; // compression algorithm format |= (its->cvt & 4) ? SF_IT215 : SF_IT214; } else { // channels format |= (its->flags & 4) ? SF_SS : SF_M; // signedness (or delta?) format |= (its->cvt & 4) ? SF_PCMD : (its->cvt & 1) ? SF_PCMS : SF_PCMU; } // bit width format |= (its->flags & 2) ? SF_16 : SF_8; smp->global_volume = its->gvl; if (its->flags & 16) { smp->flags |= CHN_LOOP; if (its->flags & 64) smp->flags |= CHN_PINGPONGLOOP; } if (its->flags & 32) { smp->flags |= CHN_SUSTAINLOOP; if (its->flags & 128) smp->flags |= CHN_PINGPONGSUSTAIN; } smp->volume = its->vol * 4; strncpy(smp->name, (const char *) its->name, 25); smp->panning = (its->dfp & 127) * 4; if (its->dfp & 128) smp->flags |= CHN_PANNING; smp->loop_start = bswapLE32(its->loopbegin); smp->loop_end = bswapLE32(its->loopend); smp->c5speed = 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; 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 &= ~(CHN_LOOP | CHN_PINGPONGLOOP); } if (smp->loop_end > smp->length) { smp->loop_end = smp->length; smp->flags &= ~(CHN_LOOP | CHN_PINGPONGLOOP); } if (smp->sustain_start > smp->length) { smp->sustain_start = smp->length; smp->flags &= ~(CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); } if (smp->sustain_end > smp->length) { smp->sustain_end = smp->length; smp->flags &= ~(CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); } bp = bswapLE32(its->samplepointer); // dumb casts :P return csf_read_sample((song_sample_t *) smp, format, (const char *) (data + bp), (uint32_t) (length - bp)); } int fmt_its_load_sample(const uint8_t *data, size_t length, song_sample_t *smp) { return load_its_sample(data, data, length, smp); } void save_its_header(disko_t *fp, song_sample_t *smp) { struct it_sample its = {}; its.id = bswapLE32(0x53504D49); // IMPS strncpy((char *) its.filename, smp->filename, 12); its.gvl = smp->global_volume; if (smp->data && smp->length) its.flags |= 1; if (smp->flags & CHN_16BIT) its.flags |= 2; if (smp->flags & CHN_STEREO) its.flags |= 4; if (smp->flags & CHN_LOOP) its.flags |= 16; if (smp->flags & CHN_SUSTAINLOOP) its.flags |= 32; if (smp->flags & CHN_PINGPONGLOOP) its.flags |= 64; if (smp->flags & CHN_PINGPONGSUSTAIN) its.flags |= 128; its.vol = smp->volume / 4; strncpy((char *) its.name, smp->name, 25); its.name[25] = 0; its.cvt = 1; // signed samples its.dfp = smp->panning / 4; if (smp->flags & CHN_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->c5speed); 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; its.vid = smp->vib_depth; switch (smp->vib_type) { case VIB_RANDOM: its.vit = 3; break; case VIB_SQUARE: its.vit = 2; break; case VIB_RAMP_DOWN: its.vit = 1; break; default: case VIB_SINE: its.vit = 0; break; } disko_write(fp, &its, sizeof(its)); } int fmt_its_save_sample(disko_t *fp, song_sample_t *smp) { save_its_header(fp, smp); csf_write_sample(fp, smp, SF_LE | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M)); /* 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(struct it_sample)); disko_seek(fp, 0x48, SEEK_SET); disko_write(fp, &tmp, 4); return SAVE_SUCCESS; } schismtracker-20180209/fmt/liq.c000066400000000000000000000030051323741476300163520ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" /* --------------------------------------------------------------------- */ int fmt_liq_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 64 && data[64] == 0x1a && memcmp(data, "Liquid Module:", 14) == 0)) return 0; file->description = "Liquid Tracker"; /*file->extension = str_dup("liq");*/ file->artist = strn_dup((const char *)data + 44, 20); file->title = strn_dup((const char *)data + 14, 30); file->type = TYPE_MODULE_S3M; return 1; } schismtracker-20180209/fmt/mdl.c000066400000000000000000000763001323741476300163510ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "log.h" #include "sndfile.h" /* --------------------------------------------------------------------- */ /* MDL is nice, but it's a pain to read the title... */ int fmt_mdl_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { uint32_t position, block_length; /* data[4] = major version number (accept 0 or 1) */ if (!(length > 5 && ((data[4] & 0xf0) >> 4) <= 1 && memcmp(data, "DMDL", 4) == 0)) return 0; position = 5; while (position + 6 < length) { memcpy(&block_length, data + position + 2, 4); block_length = bswapLE32(block_length); if (block_length + position > length) return 0; if (memcmp(data + position, "IN", 2) == 0) { /* hey! we have a winner */ file->title = strn_dup((const char *)data + position + 6, 32); file->artist = strn_dup((const char *)data + position + 38, 20); file->description = "Digitrakker"; /*file->extension = str_dup("mdl");*/ file->type = TYPE_MODULE_XM; return 1; } /* else... */ position += 6 + block_length; } return 0; } /* --------------------------------------------------------------------------------------------------------- */ /* Structs and stuff for the loader */ #define MDL_BLOCK(a,b) (((a) << 8) | (b)) #define MDL_BLK_INFO MDL_BLOCK('I','N') #define MDL_BLK_MESSAGE MDL_BLOCK('M','E') #define MDL_BLK_PATTERNS MDL_BLOCK('P','A') #define MDL_BLK_PATTERNNAMES MDL_BLOCK('P','N') #define MDL_BLK_TRACKS MDL_BLOCK('T','R') #define MDL_BLK_INSTRUMENTS MDL_BLOCK('I','I') #define MDL_BLK_VOLENVS MDL_BLOCK('V','E') #define MDL_BLK_PANENVS MDL_BLOCK('P','E') #define MDL_BLK_FREQENVS MDL_BLOCK('F','E') #define MDL_BLK_SAMPLEINFO MDL_BLOCK('I','S') #define MDL_BLK_SAMPLEDATA MDL_BLOCK('S','A') #define MDL_FADE_CUT 0xffff enum { MDLNOTE_NOTE = 1 << 0, MDLNOTE_SAMPLE = 1 << 1, MDLNOTE_VOLUME = 1 << 2, MDLNOTE_EFFECTS = 1 << 3, MDLNOTE_PARAM1 = 1 << 4, MDLNOTE_PARAM2 = 1 << 5, }; #pragma pack(push,1) struct mdl_infoblock { char title[32]; char composer[20]; uint16_t numorders; uint16_t repeatpos; uint8_t globalvol; uint8_t speed; uint8_t tempo; uint8_t chanpan[32]; }; /* This is actually a part of the instrument (II) block */ struct mdl_samplehdr { uint8_t smpnum; uint8_t lastnote; uint8_t volume; uint8_t volenv_flags; // 6 bits env #, 2 bits flags uint8_t panning; uint8_t panenv_flags; uint16_t fadeout; uint8_t vibspeed; uint8_t vibdepth; uint8_t vibsweep; uint8_t vibtype; uint8_t reserved; // zero uint8_t freqenv_flags; }; struct mdl_sampleinfo { uint8_t smpnum; char name[32]; char filename[8]; uint32_t c4speed; // c4, c5, whatever uint32_t length; uint32_t loopstart; uint32_t looplen; uint8_t unused; // was volume in v0.0, why it was changed I have no idea uint8_t flags; }; struct mdl_sampleinfo_v0 { uint8_t smpnum; char name[32]; char filename[8]; uint16_t c4speed; uint32_t length; uint32_t loopstart; uint32_t looplen; uint8_t volume; uint8_t flags; }; struct mdl_envelope { uint8_t envnum; struct { uint8_t x; // delta-value from last point, 0 means no more points defined uint8_t y; // 0-63 } nodes[15]; uint8_t flags; uint8_t loop; // lower 4 bits = start, upper 4 bits = end }; #pragma pack(pop) /* --------------------------------------------------------------------------------------------------------- */ /* Internal definitions */ struct mdlpat { int track; // which track to put here int rows; // 1-256 song_note_t *note; // first note -- add 64 for next note, etc. struct mdlpat *next; }; struct mdlenv { uint32_t flags; song_envelope_t data; }; enum { MDL_HAS_INFO = 1 << 0, MDL_HAS_MESSAGE = 1 << 1, MDL_HAS_PATTERNS = 1 << 2, MDL_HAS_TRACKS = 1 << 3, MDL_HAS_INSTRUMENTS = 1 << 4, MDL_HAS_VOLENVS = 1 << 5, MDL_HAS_PANENVS = 1 << 6, MDL_HAS_FREQENVS = 1 << 7, MDL_HAS_SAMPLEINFO = 1 << 8, MDL_HAS_SAMPLEDATA = 1 << 9, }; static const uint8_t mdl_efftrans[] = { /* 0 */ FX_NONE, /* 1st column only */ /* 1 */ FX_PORTAMENTOUP, /* 2 */ FX_PORTAMENTODOWN, /* 3 */ FX_TONEPORTAMENTO, /* 4 */ FX_VIBRATO, /* 5 */ FX_ARPEGGIO, /* 6 */ FX_NONE, /* Either column */ /* 7 */ FX_TEMPO, /* 8 */ FX_PANNING, /* 9 */ FX_SETENVPOSITION, /* A */ FX_NONE, /* B */ FX_POSITIONJUMP, /* C */ FX_GLOBALVOLUME, /* D */ FX_PATTERNBREAK, /* E */ FX_SPECIAL, /* F */ FX_SPEED, /* 2nd column only */ /* G */ FX_VOLUMESLIDE, // up /* H */ FX_VOLUMESLIDE, // down /* I */ FX_RETRIG, /* J */ FX_TREMOLO, /* K */ FX_TREMOR, /* L */ FX_NONE, }; static const uint8_t autovib_import[] = {VIB_SINE, VIB_RAMP_DOWN, VIB_SQUARE, VIB_SINE}; /* --------------------------------------------------------------------------------------------------------- */ /* a highly overcomplicated mess to import effects */ // receive an MDL effect, give back a 'normal' one. static void translate_fx(uint8_t *pe, uint8_t *pp) { uint8_t e = *pe; uint8_t p = *pp; if (e > 21) e = 0; // (shouldn't ever happen) *pe = mdl_efftrans[e]; switch (e) { case 7: // tempo // MDL supports any nonzero tempo value, but we don't p = MAX(p, 0x20); break; case 8: // panning p = MIN(p << 1, 0xff); break; case 0xd: // pattern break // convert from stupid decimal-hex p = 10 * (p >> 4) + (p & 0xf); break; case 0xe: // special switch (p >> 4) { case 0: // unused case 3: // unused case 5: // set finetune case 8: // set samplestatus (what?) *pe = FX_NONE; break; case 1: // pan slide left *pe = FX_PANNINGSLIDE; p = (MAX(p & 0xf, 0xe) << 4) | 0xf; break; case 2: // pan slide right *pe = FX_PANNINGSLIDE; p = 0xf0 | MAX(p & 0xf, 0xe); break; case 4: // vibrato waveform p = 0x30 | (p & 0xf); break; case 6: // pattern loop p = 0xb0 | (p & 0xf); break; case 7: // tremolo waveform p = 0x40 | (p & 0xf); break; case 9: // retrig *pe = FX_RETRIG; p &= 0xf; break; case 0xa: // global vol slide up *pe = FX_GLOBALVOLSLIDE; p = 0xf0 & (((p & 0xf) + 1) << 3); break; case 0xb: // global vol slide down *pe = FX_GLOBALVOLSLIDE; p = ((p & 0xf) + 1) >> 1; break; case 0xc: // note cut case 0xd: // note delay case 0xe: // pattern delay // nothing to change here break; case 0xf: // offset -- further mangled later. *pe = FX_OFFSET; break; } break; case 0x10: // volslide up if (p < 0xE0) { // Gxy -> Dz0 (z=(xy>>2)) p >>= 2; if (p > 0x0F) p = 0x0F; p <<= 4; } else if (p < 0xF0) { // GEy -> DzF (z=(y>>2)) p = (((p & 0x0F) << 2) | 0x0F); } else { // GFy -> DyF p = ((p << 4) | 0x0F); } break; case 0x11: // volslide down if (p < 0xE0) { // Hxy -> D0z (z=(xy>>2)) p >>= 2; if(p > 0x0F) p = 0x0F; } else if (p < 0xF0) { // HEy -> DFz (z=(y>>2)) p = (((p & 0x0F) >> 2) | 0xF0); } else { // HFy -> DFy // Nothing to do } break; } *pp = p; } // return: 1 if an effect was lost, 0 if not. static int cram_mdl_effects(song_note_t *note, uint8_t vol, uint8_t e1, uint8_t e2, uint8_t p1, uint8_t p2) { int lostfx = 0; int n; uint8_t tmp; // map second effect values 1-6 to effects G-L if (e2 >= 1 && e2 <= 6) e2 += 15; translate_fx(&e1, &p1); translate_fx(&e2, &p2); /* From the Digitrakker documentation: * EFx -xx - Set Sample Offset This is a double-command. It starts the sample at adress xxx*256. Example: C-5 01 -- EF1 -23 ->starts sample 01 at address 12300 (in hex). Kind of screwy, but I guess it's better than the mess required to do it with IT (which effectively requires 3 rows in order to set the offset past 0xff00). If we had access to the entire track, we *might* be able to shove the high offset SAy into surrounding rows, but it wouldn't always be possible, it'd make the loader a lot uglier, and generally would be more trouble than it'd be worth to implement. What's more is, if there's another effect in the second column, it's ALSO processed in addition to the offset, and the second data byte is shared between the two effects. And: an offset effect without a note will retrigger the previous note, but I'm not even going to try to handle that behavior. */ if (e1 == FX_OFFSET) { // EFy -xx => offset yxx00 p1 = (p1 & 0xf) ? 0xff : p2; if (e2 == FX_OFFSET) e2 = FX_NONE; } else if (e2 == FX_OFFSET) { // --- EFy => offset y0000 (best we can do without doing a ton of extra work is 0xff00) p2 = (p2 & 0xf) ? 0xff : 0; } if (vol) { note->voleffect = VOLFX_VOLUME; note->volparam = (vol + 2) >> 2; } /* If we have Dxx + G00, or Dxx + H00, combine them into Lxx/Kxx. (Since pitch effects only "fit" in the first effect column, and volume effects only work in the second column, we don't have to check every combination here.) */ if (e2 == FX_VOLUMESLIDE && p1 == 0) { if (e1 == FX_TONEPORTAMENTO) { e1 = FX_NONE; e2 = FX_TONEPORTAVOL; } else if (e1 == FX_VIBRATO) { e1 = FX_NONE; e2 = FX_VIBRATOVOL; } } /* Try to fit the "best" effect into e2. */ if (e1 == FX_NONE) { // easy } else if (e2 == FX_NONE) { // almost as easy e2 = e1; p2 = p1; e1 = FX_NONE; } else if (e1 == e2 && e1 != FX_SPECIAL) { /* Digitrakker processes the effects left-to-right, so if both effects are the same, the second essentially overrides the first. */ e1 = FX_NONE; } else if (!vol) { // The volume column is free, so try to shove one of them into there. // See also xm.c. // (Just because I'm using the same sort of code twice doesn't make it any less of a hack) for (n = 0; n < 4; n++) { if (convert_voleffect(&e1, &p1, n >> 1)) { note->voleffect = e1; note->volparam = p1; e1 = FX_NONE; break; } else { // swap them tmp = e2; e2 = e1; e1 = tmp; tmp = p2; p2 = p1; p1 = tmp; } } } // If we still have two effects, pick the 'best' one if (e1 != FX_NONE && e2 != FX_NONE) { lostfx++; if (effect_weight[e1] < effect_weight[e2]) { e2 = e1; p2 = p1; } } note->effect = e2; note->param = p2; return lostfx; } /* --------------------------------------------------------------------------------------------------------- */ /* block reading */ // return: repeat position. static int mdl_read_info(song_t *song, slurp_t *fp) { struct mdl_infoblock info; int n, songlen; uint8_t b; slurp_read(fp, &info, sizeof(info)); info.numorders = bswapLE16(info.numorders); info.repeatpos = bswapLE16(info.repeatpos); // title is space-padded info.title[31] = '\0'; trim_string(info.title); strncpy(song->title, info.title, 25); song->title[25] = '\0'; song->initial_global_volume = (info.globalvol + 1) >> 1; song->initial_speed = info.speed ?: 1; song->initial_tempo = MAX(info.tempo, 31); // MDL tempo range is actually 4-255 // channel pannings for (n = 0; n < 32; n++) { song->channels[n].panning = (info.chanpan[n] & 127) << 1; // ugh if (info.chanpan[n] & 128) song->channels[n].flags |= CHN_MUTE; } for (; n < 64; n++) { song->channels[n].panning = 128; song->channels[n].flags |= CHN_MUTE; } songlen = MIN(info.numorders, MAX_ORDERS - 1); for (n = 0; n < songlen; n++) { b = slurp_getc(fp); song->orderlist[n] = (b < MAX_PATTERNS) ? b : ORDER_SKIP; } return info.repeatpos; } static void mdl_read_message(song_t *song, slurp_t *fp, uint32_t blklen) { char *ptr = song->message; blklen = MIN(blklen, MAX_MESSAGE); slurp_read(fp, ptr, blklen); ptr[blklen] = '\0'; while ((ptr = strchr(ptr, '\r')) != NULL) *ptr = '\n'; } static struct mdlpat *mdl_read_patterns(song_t *song, slurp_t *fp) { struct mdlpat pat_head = { .next = NULL }; // only exists for .next struct mdlpat *patptr = &pat_head; song_note_t *note; int npat, nchn, rows, pat, chn; uint16_t trknum; npat = slurp_getc(fp); npat = MIN(npat, MAX_PATTERNS); for (pat = 0; pat < npat; pat++) { nchn = slurp_getc(fp); rows = slurp_getc(fp) + 1; slurp_seek(fp, 16, SEEK_CUR); // skip the name note = song->patterns[pat] = csf_allocate_pattern(rows); song->pattern_size[pat] = song->pattern_alloc_size[pat] = rows; for (chn = 0; chn < nchn; chn++, note++) { slurp_read(fp, &trknum, 2); trknum = bswapLE16(trknum); if (!trknum) continue; patptr->next = mem_alloc(sizeof(struct mdlpat)); patptr = patptr->next; patptr->track = trknum; patptr->rows = rows; patptr->note = note; patptr->next = NULL; } } return pat_head.next; } // mostly the same as above static struct mdlpat *mdl_read_patterns_v0(song_t *song, slurp_t *fp) { struct mdlpat pat_head = { .next = NULL }; struct mdlpat *patptr = &pat_head; song_note_t *note; int npat, pat, chn; uint16_t trknum; npat = slurp_getc(fp); npat = MIN(npat, MAX_PATTERNS); for (pat = 0; pat < npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (chn = 0; chn < 32; chn++, note++) { slurp_read(fp, &trknum, 2); trknum = bswapLE16(trknum); if (!trknum) continue; patptr->next = mem_alloc(sizeof(struct mdlpat)); patptr = patptr->next; patptr->track = trknum; patptr->rows = 64; patptr->note = note; patptr->next = NULL; } } return pat_head.next; } static song_note_t **mdl_read_tracks(slurp_t *fp) { song_note_t **tracks = mem_calloc(65536, sizeof(song_note_t *)); int ntrks, trk, row, lostfx = 0; uint16_t h; uint8_t b, x, y; uint8_t vol, e1, e2, p1, p2; size_t bytesleft, reallen = fp->length; slurp_read(fp, &h, 2); ntrks = bswapLE16(h); // track 0 is always blank for (trk = 1; trk <= ntrks; trk++) { slurp_read(fp, &h, 2); bytesleft = bswapLE16(h); fp->length = MIN(fp->length, fp->pos + bytesleft); // narrow tracks[trk] = mem_calloc(256, sizeof(song_note_t)); row = 0; while (row < 256 && !slurp_eof(fp)) { b = slurp_getc(fp); x = b >> 2; y = b & 3; switch (y) { case 0: // (x+1) empty notes follow row += x + 1; break; case 1: // Repeat previous note (x+1) times if (row > 0) { do { tracks[trk][row] = tracks[trk][row - 1]; } while (++row < 256 && x--); } break; case 2: // Copy note from row x if (row > x) tracks[trk][row] = tracks[trk][x]; row++; break; case 3: // New note data if (x & MDLNOTE_NOTE) { b = slurp_getc(fp); // convenient! :) // (I don't know what DT does for out of range notes, might be worth // checking some time) tracks[trk][row].note = (b > 120) ? NOTE_OFF : b; } if (x & MDLNOTE_SAMPLE) { b = slurp_getc(fp); if (b >= MAX_INSTRUMENTS) b = 0; tracks[trk][row].instrument = b; } vol = (x & MDLNOTE_VOLUME) ? slurp_getc(fp) : 0; if (x & MDLNOTE_EFFECTS) { b = slurp_getc(fp); e1 = b & 0xf; e2 = b >> 4; } else { e1 = e2 = 0; } p1 = (x & MDLNOTE_PARAM1) ? slurp_getc(fp) : 0; p2 = (x & MDLNOTE_PARAM2) ? slurp_getc(fp) : 0; lostfx += cram_mdl_effects(&tracks[trk][row], vol, e1, e2, p1, p2); row++; break; } } fp->length = reallen; // widen } if (lostfx) log_appendf(4, " Warning: %d effect%s dropped", lostfx, lostfx == 1 ? "" : "s"); return tracks; } /* This is actually somewhat horrible. Digitrakker's envelopes are actually properties of the *sample*, not the instrument -- that is, the only thing an instrument is actually doing is providing a keyboard split and grouping a bunch of samples together. This is handled here by importing the instrument names and note/sample mapping into a "master" instrument, but NOT writing the envelope data there -- instead, that stuff is placed into whatever instrument matches up with the sample number. Then, when building the tracks into patterns, we'll actually *remap* all the numbers and rewrite each instrument's sample map as a 1:1 mapping with the sample. In the end, the song will play back correctly (well, at least hopefully it will ;) though the instrument names won't always line up. */ static void mdl_read_instruments(song_t *song, slurp_t *fp) { struct mdl_samplehdr shdr; // Etaoin shrdlu song_instrument_t *ins; // 'master' instrument song_instrument_t *sins; // other instruments created to track each sample's individual envelopes song_sample_t *smp; int nins, nsmp; int insnum; int firstnote, note; nins = slurp_getc(fp); while (nins--) { insnum = slurp_getc(fp); firstnote = 0; nsmp = slurp_getc(fp); // if it's out of range, or if the same instrument was already loaded (weird), don't read it if (insnum == 0 || insnum > MAX_INSTRUMENTS) { // skip it (32 bytes name, plus 14 bytes per sample) slurp_seek(fp, 32 + 14 * nsmp, SEEK_SET); continue; } // ok, make an instrument if (!song->instruments[insnum]) song->instruments[insnum] = csf_allocate_instrument(); ins = song->instruments[insnum]; slurp_read(fp, ins->name, 25); slurp_seek(fp, 7, SEEK_CUR); // throw away the rest ins->name[25] = '\0'; while (nsmp--) { // read a sample slurp_read(fp, &shdr, sizeof(shdr)); shdr.fadeout = bswapLE16(shdr.fadeout); if (shdr.smpnum == 0 || shdr.smpnum > MAX_SAMPLES) { continue; } if (!song->instruments[shdr.smpnum]) song->instruments[shdr.smpnum] = csf_allocate_instrument(); sins = song->instruments[shdr.smpnum]; smp = song->samples + shdr.smpnum; // Write this sample's instrument mapping // (note: test "jazz 2 jazz.mdl", it uses a multisampled piano) shdr.lastnote = MIN(shdr.lastnote, 119); for (note = firstnote; note <= shdr.lastnote; note++) ins->sample_map[note] = shdr.smpnum; firstnote = shdr.lastnote + 1; // get ready for the next sample // temporarily hijack the envelope "nodes" field to write the envelope number sins->vol_env.nodes = shdr.volenv_flags & 63; sins->pan_env.nodes = shdr.panenv_flags & 63; sins->pitch_env.nodes = shdr.freqenv_flags & 63; if (shdr.volenv_flags & 128) sins->flags |= ENV_VOLUME; if (shdr.panenv_flags & 128) sins->flags |= ENV_PANNING; if (shdr.freqenv_flags & 128) sins->flags |= ENV_PITCH; // DT fadeout = 0000-1fff, or 0xffff for "cut" // assuming DT uses 'cut' behavior for anything past 0x1fff, too lazy to bother // hex-editing a file at the moment to find out :P sins->fadeout = (shdr.fadeout < 0x2000) ? (shdr.fadeout + 1) >> 1 // this seems about right : MDL_FADE_CUT; // temporary // for the volume envelope / flags: // "bit 6 -> flags, if volume is used" // ... huh? what happens if the volume isn't used? smp->volume = shdr.volume; //mphack (range 0-255, s/b 0-64) smp->panning = ((MIN(shdr.panning, 127) + 1) >> 1) * 4; //mphack if (shdr.panenv_flags & 64) smp->flags |= CHN_PANNING; smp->vib_speed = shdr.vibspeed; // XXX bother checking ranges for vibrato smp->vib_depth = shdr.vibdepth; smp->vib_rate = shdr.vibsweep; smp->vib_type = autovib_import[shdr.vibtype & 3]; } } } static void mdl_read_sampleinfo(song_t *song, slurp_t *fp, uint8_t *packtype) { struct mdl_sampleinfo sinfo; song_sample_t *smp; int nsmp; nsmp = slurp_getc(fp); while (nsmp--) { slurp_read(fp, &sinfo, sizeof(sinfo)); if (sinfo.smpnum == 0 || sinfo.smpnum > MAX_SAMPLES) { continue; } smp = song->samples + sinfo.smpnum; strncpy(smp->name, sinfo.name, 25); smp->name[25] = '\0'; strncpy(smp->filename, sinfo.filename, 8); smp->filename[8] = '\0'; // MDL has ten octaves like IT, but they're not the *same* ten octaves -- dropping // perfectly good note data is stupid so I'm adjusting the sample tunings instead smp->c5speed = bswapLE32(sinfo.c4speed) * 2; smp->length = bswapLE32(sinfo.length); smp->loop_start = bswapLE32(sinfo.loopstart); smp->loop_end = bswapLE32(sinfo.looplen); if (smp->loop_end) { smp->loop_end += smp->loop_start; smp->flags |= CHN_LOOP; } if (sinfo.flags & 1) { smp->flags |= CHN_16BIT; smp->length >>= 1; smp->loop_start >>= 1; smp->loop_end >>= 1; } if (sinfo.flags & 2) smp->flags |= CHN_PINGPONGLOOP; packtype[sinfo.smpnum] = ((sinfo.flags >> 2) & 3); smp->global_volume = 64; } } // (ughh) static void mdl_read_sampleinfo_v0(song_t *song, slurp_t *fp, uint8_t *packtype) { struct mdl_sampleinfo_v0 sinfo; song_sample_t *smp; int nsmp; nsmp = slurp_getc(fp); while (nsmp--) { slurp_read(fp, &sinfo, sizeof(sinfo)); if (sinfo.smpnum == 0 || sinfo.smpnum > MAX_SAMPLES) { continue; } smp = song->samples + sinfo.smpnum; strncpy(smp->name, sinfo.name, 25); smp->name[25] = '\0'; strncpy(smp->filename, sinfo.filename, 8); smp->filename[8] = '\0'; smp->c5speed = bswapLE16(sinfo.c4speed) * 2; smp->length = bswapLE32(sinfo.length); smp->loop_start = bswapLE32(sinfo.loopstart); smp->loop_end = bswapLE32(sinfo.looplen); smp->volume = sinfo.volume; //mphack (range 0-255, I think?) if (smp->loop_end) { smp->loop_end += smp->loop_start; smp->flags |= CHN_LOOP; } if (sinfo.flags & 1) { smp->flags |= CHN_16BIT; smp->length >>= 1; smp->loop_start >>= 1; smp->loop_end >>= 1; } if (sinfo.flags & 2) smp->flags |= CHN_PINGPONGLOOP; packtype[sinfo.smpnum] = ((sinfo.flags >> 2) & 3); smp->global_volume = 64; } } static void mdl_read_envelopes(slurp_t *fp, struct mdlenv **envs, uint32_t flags) { struct mdl_envelope ehdr; song_envelope_t *env; uint8_t nenv; int n, tick; nenv = slurp_getc(fp); while (nenv--) { slurp_read(fp, &ehdr, sizeof(ehdr)); if (ehdr.envnum > 63) continue; if (!envs[ehdr.envnum]) envs[ehdr.envnum] = mem_calloc(1, sizeof(struct mdlenv)); env = &envs[ehdr.envnum]->data; env->nodes = 15; tick = -ehdr.nodes[0].x; // adjust so it starts at zero for (n = 0; n < 15; n++) { if (!ehdr.nodes[n].x) { env->nodes = MAX(n, 2); break; } tick += ehdr.nodes[n].x; env->ticks[n] = tick; env->values[n] = MIN(ehdr.nodes[n].y, 64); // actually 0-63 } env->loop_start = ehdr.loop & 0xf; env->loop_end = ehdr.loop >> 4; env->sustain_start = env->sustain_end = ehdr.flags & 0xf; envs[ehdr.envnum]->flags = 0; if (ehdr.flags & 16) envs[ehdr.envnum]->flags |= (flags & (ENV_VOLSUSTAIN | ENV_PANSUSTAIN | ENV_PITCHSUSTAIN)); if (ehdr.flags & 32) envs[ehdr.envnum]->flags |= (flags & (ENV_VOLLOOP | ENV_PANLOOP | ENV_PITCHLOOP)); } } /* --------------------------------------------------------------------------------------------------------- */ static void copy_envelope(song_instrument_t *ins, song_envelope_t *ienv, struct mdlenv **envs, uint32_t enable) { // nodes temporarily indicates which envelope to load struct mdlenv *env = envs[ienv->nodes]; if (env) { ins->flags |= env->flags; memcpy(ienv, &env->data, sizeof(song_envelope_t)); } else { ins->flags &= ~enable; ienv->nodes = 2; } } int fmt_mdl_load_song(song_t *song, slurp_t *fp, UNUSED unsigned int lflags) { struct mdlpat *pat, *patptr = NULL; struct mdlenv *volenvs[64] = {NULL}, *panenvs[64] = {NULL}, *freqenvs[64] = {NULL}; uint8_t packtype[MAX_SAMPLES] = {0}; song_note_t **tracks = NULL; long datapos = 0; // where to seek for the sample data int restartpos = -1; int trk, n; uint32_t readflags = 0; uint8_t tag[4]; uint8_t fmtver; // file format version, e.g. 0x11 = v1.1 slurp_read(fp, tag, 4); if (memcmp(tag, "DMDL", 4) != 0) return LOAD_UNSUPPORTED; fmtver = slurp_getc(fp); // Read the next block while (!slurp_eof(fp)) { uint32_t blklen; // length of this block size_t nextpos; // ... and start of next one slurp_read(fp, tag, 2); slurp_read(fp, &blklen, 4); blklen = bswapLE32(blklen); nextpos = slurp_tell(fp) + blklen; switch (MDL_BLOCK(tag[0], tag[1])) { case MDL_BLK_INFO: if (!(readflags & MDL_HAS_INFO)) { readflags |= MDL_HAS_INFO; restartpos = mdl_read_info(song, fp); } break; case MDL_BLK_MESSAGE: if (!(readflags & MDL_HAS_MESSAGE)) { readflags |= MDL_HAS_MESSAGE; mdl_read_message(song, fp, blklen); } break; case MDL_BLK_PATTERNS: if (!(readflags & MDL_HAS_PATTERNS)) { readflags |= MDL_HAS_PATTERNS; patptr = ((fmtver >> 4) ? mdl_read_patterns : mdl_read_patterns_v0)(song, fp); } break; case MDL_BLK_TRACKS: if (!(readflags & MDL_HAS_TRACKS)) { readflags |= MDL_HAS_TRACKS; tracks = mdl_read_tracks(fp); } break; case MDL_BLK_INSTRUMENTS: if (!(readflags & MDL_HAS_INSTRUMENTS)) { readflags |= MDL_HAS_INSTRUMENTS; mdl_read_instruments(song, fp); } break; case MDL_BLK_VOLENVS: if (!(readflags & MDL_HAS_VOLENVS)) { readflags |= MDL_HAS_VOLENVS; mdl_read_envelopes(fp, volenvs, ENV_VOLLOOP | ENV_VOLSUSTAIN); } break; case MDL_BLK_PANENVS: if (!(readflags & MDL_HAS_PANENVS)) { readflags |= MDL_HAS_PANENVS; mdl_read_envelopes(fp, panenvs, ENV_PANLOOP | ENV_PANSUSTAIN); } break; case MDL_BLK_FREQENVS: if (!(readflags & MDL_HAS_FREQENVS)) { readflags |= MDL_HAS_FREQENVS; mdl_read_envelopes(fp, freqenvs, ENV_PITCHLOOP | ENV_PITCHSUSTAIN); } break; case MDL_BLK_SAMPLEINFO: if (!(readflags & MDL_HAS_SAMPLEINFO)) { readflags |= MDL_HAS_SAMPLEINFO; ((fmtver >> 4) ? mdl_read_sampleinfo : mdl_read_sampleinfo_v0) (song, fp, packtype); } break; case MDL_BLK_SAMPLEDATA: // Can't do anything until we have the sample info block loaded, since the sample // lengths and packing information is stored there. // Best we can do at the moment is to remember where this block was so we can jump // back to it later. if (!(readflags & MDL_HAS_SAMPLEDATA)) { readflags |= MDL_HAS_SAMPLEDATA; datapos = slurp_tell(fp); } break; case MDL_BLK_PATTERNNAMES: // don't care break; default: //log_appendf(4, " Warning: Unknown block of type '%c%c' (0x%04X) at %ld", // tag[0], tag[1], MDL_BLOCK(tag[0], tag[1]), slurp_tell(fp)); break; } if (slurp_seek(fp, nextpos, SEEK_SET) != 0) { log_appendf(4, " Warning: Failed to seek (file truncated?)"); break; } } if (!(readflags & MDL_HAS_INSTRUMENTS)) { // Probably a v0 file, fake an instrument for (n = 1; n < MAX_SAMPLES; n++) { if (song->samples[n].length) { song->instruments[n] = csf_allocate_instrument(); strcpy(song->instruments[n]->name, song->samples[n].name); } } } if (readflags & MDL_HAS_SAMPLEINFO) { // Sample headers loaded! // if the sample data was encountered, load it now // otherwise, clear out the sample lengths so Bad Things don't happen later if (datapos) { slurp_seek(fp, datapos, SEEK_SET); for (n = 1; n < MAX_SAMPLES; n++) { if (!packtype[n] && !song->samples[n].length) continue; uint32_t smpsize, flags; if (packtype[n] > 2) { log_appendf(4, " Warning: Sample %d: unknown packing type %d", n, packtype[n]); packtype[n] = 0; // ? } else if (packtype[n] == ((song->samples[n].flags & CHN_16BIT) ? 1 : 2)) { log_appendf(4, " Warning: Sample %d: bit width / pack type mismatch", n); } flags = SF_LE | SF_M; flags |= packtype[n] ? SF_MDL : SF_PCMS; flags |= (song->samples[n].flags & CHN_16BIT) ? SF_16 : SF_8; smpsize = csf_read_sample(song->samples + n, flags, fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, smpsize, SEEK_CUR); } } else { for (n = 1; n < MAX_SAMPLES; n++) song->samples[n].length = 0; } } if (readflags & MDL_HAS_TRACKS) { song_note_t *patnote, *trknote; // first off, fix all the instrument numbers to compensate // for the screwy envelope craziness if (fmtver >> 4) { for (trk = 1; trk < 65536 && tracks[trk]; trk++) { uint8_t cnote = NOTE_FIRST; // current/last used data for (n = 0, trknote = tracks[trk]; n < 256; n++, trknote++) { if (NOTE_IS_NOTE(trknote->note)) { cnote = trknote->note; } if (trknote->instrument) { // translate it trknote->instrument = song->instruments[trknote->instrument] ? (song->instruments[trknote->instrument] ->sample_map[cnote - 1]) : 0; } } } } // "paste" the tracks into the channels for (pat = patptr; pat; pat = pat->next) { trknote = tracks[pat->track]; if (!trknote) continue; patnote = pat->note; for (n = 0; n < pat->rows; n++, trknote++, patnote += 64) { *patnote = *trknote; } } // and clean up for (trk = 1; trk < 65536 && tracks[trk]; trk++) free(tracks[trk]); free(tracks); } while (patptr) { pat = patptr; patptr = patptr->next; free(pat); } // Finish fixing up the instruments for (n = 1; n < MAX_INSTRUMENTS; n++) { song_instrument_t *ins = song->instruments[n]; if (ins) { copy_envelope(ins, &ins->vol_env, volenvs, ENV_VOLUME); copy_envelope(ins, &ins->pan_env, panenvs, ENV_PANNING); copy_envelope(ins, &ins->pitch_env, freqenvs, ENV_PITCH); if (ins->flags & ENV_VOLUME) { // fix note-fade if (!(ins->flags & ENV_VOLLOOP)) ins->vol_env.loop_start = ins->vol_env.loop_end = ins->vol_env.nodes - 1; if (!(ins->flags & ENV_VOLSUSTAIN)) ins->vol_env.sustain_start = ins->vol_env.sustain_end = ins->vol_env.nodes - 1; ins->flags |= ENV_VOLLOOP | ENV_VOLSUSTAIN; } if (ins->fadeout == MDL_FADE_CUT) { // fix note-off if (!(ins->flags & ENV_VOLUME)) { ins->vol_env.ticks[0] = 0; ins->vol_env.values[0] = 64; ins->vol_env.sustain_start = ins->vol_env.sustain_end = 0; ins->flags |= ENV_VOLUME | ENV_VOLSUSTAIN; // (the rest is set below) } int se = ins->vol_env.sustain_end; ins->vol_env.nodes = se + 2; ins->vol_env.ticks[se + 1] = ins->vol_env.ticks[se] + 1; ins->vol_env.values[se + 1] = 0; ins->fadeout = 0; } // set a 1:1 map for each instrument with a corresponding sample, // and a blank map for each one that doesn't. int note, smp = song->samples[n].data ? n : 0; for (note = 0; note < 120; note++) { ins->sample_map[note] = smp; ins->note_map[note] = note + 1; } } } if (readflags & MDL_HAS_VOLENVS) { for (n = 0; n < 64; n++) free(volenvs[n]); } if (readflags & MDL_HAS_PANENVS) { for (n = 0; n < 64; n++) free(panenvs[n]); } if (readflags & MDL_HAS_FREQENVS) { for (n = 0; n < 64; n++) free(freqenvs[n]); } if (restartpos > 0) csf_insert_restart_pos(song, restartpos); song->flags |= SONG_ITOLDEFFECTS | SONG_COMPATGXX | SONG_INSTRUMENTMODE | SONG_LINEARSLIDES; sprintf(song->tracker_id, "Digitrakker %s", (fmtver == 0x11) ? "3" // really could be 2.99b -- but close enough for me : (fmtver == 0x10) ? "2.3" : (fmtver == 0x00) ? "2.0 - 2.2b" // there was no 1.x release : "v?.?"); return LOAD_SUCCESS; } schismtracker-20180209/fmt/med.c000066400000000000000000000026061323741476300163400ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" /* --------------------------------------------------------------------- */ int fmt_med_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 32 && memcmp(data, "MMD0", 3) == 0)) return 0; file->description = "OctaMed"; file->title = strdup(""); // TODO actually read the title file->type = TYPE_MODULE_MOD; return 1; } schismtracker-20180209/fmt/mf.c000066400000000000000000000027101323741476300161710ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" /* --------------------------------------------------------------------- */ int fmt_mf_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 290 && memcmp(data, "MOONFISH", 8) == 0)) return 0; file->description = "MoonFish"; /*file->extension = str_dup("mf");*/ file->title = strn_dup((const char *)data + 33, MIN(32, data[32])); file->type = TYPE_MODULE_MOD; /* ??? */ return 1; } schismtracker-20180209/fmt/mid.c000066400000000000000000000363401323741476300163460ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "log.h" #include "sndfile.h" /* some thoughts... really, we don't even need to adhere to the same channel numbering -- the notes could go pretty much anywhere, as long as note-off events are handled properly. but it'd be nice to have the channels at least sort of resemble the midi file, whether it's arranged by track or by midi channel. - try to allocate channels that aren't in use when possible, to avoid stomping on playing notes - set instrument NNA to continue with dupe cut (or note off?) */ /* --------------------------------------------------------------------------------------------------------- */ // structs, local defines, etc. #define MID_ROWS_PER_PATTERN 200 // Pulse/row calculations are done in fixed point for better accuracy #define FRACBITS 12 #define FRACMASK ((1 << FRACBITS) - 1) #pragma pack(push, 1) struct mthd { //char tag[4]; // MThd uint32_t header_length; uint16_t format; // 0 = single-track, 1 = multi-track, 2 = multi-song uint16_t num_tracks; // number of track chunks uint16_t division; // delta timing value: positive = units/beat; negative = smpte compatible units (?) }; struct mtrk { char tag[4]; // MTrk uint32_t length; // number of bytes of track data following }; #pragma pack(pop) struct event { unsigned int pulse; // the PPQN-tick, counting from zero, when this midi-event happens uint8_t chan; // target channel (0-based!) song_note_t note; // the note data (new data will overwrite old data in same channel+row) struct event *next; }; static struct event *alloc_event(unsigned int pulse, uint8_t chan, const song_note_t *note, struct event *next) { struct event *ev = malloc(sizeof(struct event)); if (!ev) { perror("malloc"); return NULL; } ev->pulse = pulse; ev->chan = chan; ev->note = *note; ev->next = next; return ev; } /* --------------------------------------------------------------------------------------------------------- */ // support functions static unsigned int read_varlen(slurp_t *fp) { int b; unsigned int v = 0; // This will fail tremendously if a value overflows. I don't care. do { b = slurp_getc(fp); if (b == EOF) return 0; // truncated?! v <<= 7; v |= b & 0x7f; } while (b & 0x80); return v; } /* --------------------------------------------------------------------------------------------------------- */ // info (this is ultra lame) int fmt_mid_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { slurp_t fp = {.length = length, .data = (uint8_t *) data, .pos = 0}; song_t *tmpsong = csf_allocate(); if (!tmpsong) return 0; // wahhhh if (fmt_mid_load_song(tmpsong, &fp, LOAD_NOSAMPLES | LOAD_NOPATTERNS) == LOAD_SUCCESS) { file->description = "Standard MIDI File"; file->title = strdup(tmpsong->title); file->type = TYPE_MODULE_MOD; csf_free(tmpsong); return 1; } csf_free(tmpsong); return 0; } /* --------------------------------------------------------------------------------------------------------- */ // load int fmt_mid_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { struct mthd mthd; struct mtrk mtrk; unsigned char buf[32]; song_note_t note; struct event *event_queue, *cur, *prev, *new; struct { uint8_t fg_note; uint8_t bg_note; // really just used as a boolean... uint8_t instrument; } midich[16] = {{NOTE_NONE, NOTE_NONE, 0}}; char *message_cur = song->message; unsigned int message_left = MAX_MESSAGE; unsigned int pulse = 0; // cumulative time from start of track uint8_t patch_samples[128] = {0}; uint8_t nsmp = 1; // Next free sample slurp_read(fp, buf, 4); if (memcmp(buf, "RIFF", 4) == 0) { // Stupid MS crap. slurp_seek(fp, 16, SEEK_CUR); slurp_read(fp, buf, 4); } if (memcmp(buf, "MThd", 4) != 0 || slurp_read(fp, &mthd, sizeof(mthd)) != sizeof(mthd)) { return LOAD_UNSUPPORTED; } mthd.header_length = bswapBE32(mthd.header_length); // don't care about format, either there's one track or more than one track. whoop de doo. // (format 2 MIDs will probably be hilariously broken, but I don't have any and also don't care) mthd.format = bswapBE16(mthd.format); mthd.num_tracks = bswapBE16(mthd.num_tracks); mthd.division = bswapBE16(mthd.division); slurp_seek(fp, mthd.header_length - 6, SEEK_CUR); // account for potential weirdness song->title[0] = '\0'; // should be already, but to be sure... /* We'll count by "pulses" here, which are basically MIDI-speak for ticks, except that there are a heck of a lot more of them. (480 pulses/quarter is fairly common, that's like A78, if the tempo could be adjusted high enough to make practical use of that speed) Also, we'll use a 32-bit value and hopefully not overflow -- which is unlikely anyway, as it'd either require PPQN to be very ridiculously high, or a file that's several *hours* long. Stuff a useless event at the start of the event queue. */ note = (song_note_t) {.note = NOTE_NONE}; event_queue = alloc_event(0, 0, ¬e, NULL); for (int trknum = 0; trknum < mthd.num_tracks; trknum++) { unsigned int delta; // time since last event (read from file) unsigned int vlen; // some other generic varlen number int rs = 0; // running status byte int status; // THIS status byte (as opposed to rs) unsigned char hi, lo, cn, x, y; unsigned int bpm; // stupid int found_end = 0; long nextpos; cur = event_queue->next; prev = event_queue; pulse = 0; if (slurp_read(fp, &mtrk, sizeof(mtrk)) != sizeof(mtrk)) { log_appendf(4, " Warning: Short read on track header (truncated?)"); break; } if (memcmp(mtrk.tag, "MTrk", 4) != 0) { log_appendf(4, " Warning: Invalid track header (corrupt file?)"); break; } mtrk.length = bswapBE32(mtrk.length); nextpos = slurp_tell(fp) + mtrk.length; // where this track is supposed to end while (!found_end && slurp_tell(fp) < nextpos) { delta = read_varlen(fp); // delta-time pulse += delta; // 'real' pulse count // get status byte, if there is one if (fp->data[fp->pos] & 0x80) { status = slurp_getc(fp); } else if (rs & 0x80) { status = rs; } else { // garbage? continue; } note = (song_note_t) {.note = NOTE_NONE}; hi = status >> 4; lo = status & 0xf; cn = lo; //or: trknum * CHANNELS_PER_TRACK + lo % CHANNELS_PER_TRACK; switch (hi) { case 0x8: // note off - x, y rs = status; x = slurp_getc(fp); // note y = slurp_getc(fp); // release velocity x = CLAMP(x + NOTE_FIRST, NOTE_FIRST, NOTE_LAST); // clamp is wrong, but whatever // if the last note in the channel is the same as this note, just write === // otherwise, if there is a note playing, assume our note got backgrounded // and write S71 (past note off) if (midich[cn].fg_note == x) { note = (song_note_t) {.note = NOTE_OFF}; midich[cn].fg_note = NOTE_NONE; } else { // S71, past note off note = (song_note_t) {.effect = FX_SPECIAL, .param = 0x71}; midich[cn].bg_note = NOTE_NONE; } break; case 0x9: // note on - x, y (velocity zero = note off) rs = status; x = slurp_getc(fp); // note y = slurp_getc(fp); // attack velocity x = CLAMP(x + NOTE_FIRST, NOTE_FIRST, NOTE_LAST); // see note off above. if (lo == 9) { // ignore percussion for now } else if (y == 0) { // this is actually another note-off, see above // (maybe that stuff should be split into a function or blahblah) if (midich[cn].fg_note == x) { note = (song_note_t) {.note = NOTE_OFF}; midich[cn].fg_note = NOTE_NONE; } else { // S71, past note off note = (song_note_t) {.effect = FX_SPECIAL, .param = 0x71}; midich[cn].bg_note = NOTE_NONE; } } else { if (nsmp == 1 && !(lflags & LOAD_NOSAMPLES)) { // no samples defined yet - fake a program change patch_samples[0] = 1; adlib_patch_apply(song->samples + 1, 0); nsmp++; } note = (song_note_t) { .note = x, .instrument = patch_samples[midich[cn].instrument], .voleffect = VOLFX_VOLUME, .volparam = (y & 0x7f) * 64 / 127, }; midich[cn].fg_note = x; midich[cn].bg_note = midich[cn].fg_note; } break; case 0xa: // polyphonic key pressure (aftertouch) - x, y rs = status; x = slurp_getc(fp); y = slurp_getc(fp); // TODO polyphonic aftertouch channel=lo note=x pressure=y continue; case 0xb: // controller OR channel mode - x, y rs = status; // controller if first data byte 0-119 // channel mode if first data byte 120-127 x = slurp_getc(fp); y = slurp_getc(fp); // TODO controller change channel=lo controller=x value=y continue; case 0xc: // program change - x (instrument/voice selection) rs = status; x = slurp_getc(fp); midich[cn].instrument = x; // look familiar? this was copied from the .mus loader if (!patch_samples[x] && !(lflags & LOAD_NOSAMPLES)) { if (nsmp < MAX_SAMPLES) { // New sample! patch_samples[x] = nsmp; adlib_patch_apply(song->samples + nsmp, x); nsmp++; } else { log_appendf(4, " Warning: Too many samples"); } } note = (song_note_t) {.instrument = patch_samples[x]}; break; case 0xd: // channel pressure (aftertouch) - x rs = status; x = slurp_getc(fp); // TODO channel aftertouch channel=lo pressure=x continue; case 0xe: // pitch bend - x, y rs = status; x = slurp_getc(fp); y = slurp_getc(fp); // TODO pitch bend channel=lo lsb=x msb=y continue; case 0xf: // system messages switch (lo) { case 0xf: // meta-event (text and stuff) x = slurp_getc(fp); // type vlen = read_varlen(fp); // value length switch (x) { case 0x1: // text case 0x2: // copyright case 0x3: // track name case 0x4: // instrument name case 0x5: // lyric case 0x6: // marker case 0x7: // cue point y = MIN(vlen, message_left - 1); slurp_read(fp, message_cur, y); if (x == 3 && y && !song->title[0]) { strncpy(song->title, message_cur, MIN(y, 25)); song->title[25] = '\0'; } message_cur += y; message_left -= y; if (y && message_cur[-1] != '\n') { *message_cur++ = '\n'; message_left--; } vlen -= y; if (vlen) slurp_seek(fp, vlen, SEEK_CUR); continue; case 0x20: // MIDI channel (FF 20 len* cc) // specifies which midi-channel sysexes are assigned to case 0x21: // MIDI port (FF 21 len* pp) // specifies which port/bus this track's events are routed to continue; case 0x2f: found_end = 1; break; case 0x51: // set tempo // read another stupid kind of variable length number // hopefully this fits into 4 bytes - if not, too bad! // (what is this? friggin' magic?) memset(buf, 0, 4); y = MIN(vlen, 4); slurp_read(fp, buf + (4 - y), y); bpm = buf[0] << 24 | (buf[1] << 16) | (buf[2] << 8) | buf[3]; bpm = CLAMP(60000000 / (bpm ?: 1), 0x20, 0xff); note = (song_note_t) {.effect = FX_TEMPO, .param = bpm}; vlen -= y; break; case 0x54: // SMPTE offset (what time in the song this track starts) // (what?!) continue; case 0x58: // time signature (FF 58 len* nn dd cc bb) case 0x59: // key signature (FF 59 len* sf mi) // TODO care? don't care? continue; case 0x7f: // some proprietary crap continue; default: // some mystery crap log_appendf(2, " Unknown meta-event FF %02X", x); continue; } slurp_seek(fp, vlen, SEEK_CUR); break; case 0x0: // sysex case 0x1 ... 0x7: // syscommon rs = 0; // clear running status case 0x8 ... 0xe: // sysrt // 0xf0 - sysex // 0xf1-0xf7 - common // 0xf8-0xff - sysrt // sysex and common cancel running status // TODO handle these, or at least skip them coherently continue; } } // skip past any events with a lower pulse count (from other channels) while (cur && pulse > cur->pulse) { prev = cur; cur = cur->next; } // and now, cur is either NULL or has a higher timestamp, so insert before it new = alloc_event(pulse, cn, ¬e, cur); prev->next = new; prev = prev->next; } if (slurp_tell(fp) != nextpos) { log_appendf(2, " Track %d ended %ld bytes from boundary", trknum, slurp_tell(fp) - nextpos); slurp_seek(fp, nextpos, SEEK_SET); } } song->initial_speed = 3; song->initial_tempo = 120; prev = NULL; cur = event_queue; if (lflags & LOAD_NOPATTERNS) { while (cur) { prev = cur; cur = cur->next; free(prev); } return LOAD_SUCCESS; } // okey doke! now let's write this crap out to the patterns song_note_t *pattern = NULL, *rowdata; int row = MID_ROWS_PER_PATTERN; // what row of the pattern rowdata is pointing to (fixed point) int rowfrac = 0; // how much is left over int pat = 0; // next pattern number to create pulse = 0; // PREVIOUS event pulse. while (cur) { /* calculate pulse delta from previous event * calculate row count from the pulse count using ppqn (assuming 1 row = 1/32nd note? 1/64?) * advance the row as required it'd be nice to aim for the "middle" of ticks instead of the start of them, that way if an event is just barely off, it won't end up shifted way ahead. */ unsigned int delta = cur->pulse - pulse; if (delta) { // Increment position row <<= FRACBITS; row += 8 * (delta << FRACBITS) / mthd.division; // times 8 -> 32nd notes row += rowfrac; rowfrac = row & FRACMASK; row >>= FRACBITS; } pulse = cur->pulse; while (row >= MID_ROWS_PER_PATTERN) { // New pattern time! pattern = song->patterns[pat] = csf_allocate_pattern(MID_ROWS_PER_PATTERN); song->pattern_size[pat] = song->pattern_alloc_size[pat] = MID_ROWS_PER_PATTERN; song->orderlist[pat] = pat; pat++; row -= MID_ROWS_PER_PATTERN; } rowdata = pattern + 64 * row; if (cur->note.note) { rowdata[cur->chan].note = cur->note.note; rowdata[cur->chan].instrument = cur->note.instrument; } if (cur->note.voleffect) { rowdata[cur->chan].voleffect = cur->note.voleffect; rowdata[cur->chan].volparam = cur->note.volparam; } if (cur->note.effect) { rowdata[cur->chan].effect = cur->note.effect; rowdata[cur->chan].param = cur->note.param; } prev = cur; cur = cur->next; free(prev); } return LOAD_SUCCESS; } schismtracker-20180209/fmt/mmcmp.c000066400000000000000000000156141323741476300167070ustar00rootroot00000000000000/* * 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. * * Author: Olivier Lapicque * Modified by Claudio Matsuoka for xmp * Modified again by Storlek to de-xmp-ify it. */ #define NEED_BYTESWAP #include "headers.h" #include "slurp.h" // for definition of mmcmp_unpack #pragma pack(push, 1) typedef struct mm_header { char zirconia[8]; // "ziRCONia" uint16_t hdrsize; uint16_t version; uint16_t blocks; uint32_t filesize; uint32_t blktable; uint8_t glb_comp; uint8_t fmt_comp; } mm_header_t; typedef struct mm_block { uint32_t unpk_size; uint32_t pk_size; uint32_t xor_chk; uint16_t sub_blk; uint16_t flags; uint16_t tt_entries; uint16_t num_bits; } mm_block_t; typedef struct mm_subblock { uint32_t unpk_pos; uint32_t unpk_size; } mm_subblock_t; #pragma pack(pop) // only used internally typedef struct mm_bit_buffer { uint32_t bits, buffer; uint8_t *src, *end; } mm_bit_buffer_t; enum { MM_COMP = 0x0001, MM_DELTA = 0x0002, MM_16BIT = 0x0004, MM_STEREO = 0x0100, // unused? MM_ABS16 = 0x0200, MM_ENDIAN = 0x0400, // unused? }; static const uint32_t mm_8bit_commands[8] = { 0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF8 }; static const uint32_t mm_8bit_fetch[8] = { 3, 3, 3, 3, 2, 1, 0, 0 }; static const uint32_t mm_16bit_commands[16] = { 0x0001, 0x0003, 0x0007, 0x000F, 0x001E, 0x003C, 0x0078, 0x00F0, 0x01F0, 0x03F0, 0x07F0, 0x0FF0, 0x1FF0, 0x3FF0, 0x7FF0, 0xFFF0, }; static const uint32_t mm_16bit_fetch[16] = { 4, 4, 4, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static uint32_t get_bits(mm_bit_buffer_t *bb, uint32_t bits) { uint32_t d; if (!bits) return 0; while (bb->bits < 24) { bb->buffer |= ((bb->src < bb->end) ? *bb->src++ : 0) << bb->bits; bb->bits += 8; } d = bb->buffer & ((1 << bits) - 1); bb->buffer >>= bits; bb->bits -= bits; return d; } int mmcmp_unpack(uint8_t **data, size_t *length) { size_t memlength; uint8_t *memfile; uint8_t *buffer; mm_header_t hdr; uint32_t *pblk_table; size_t filesize; uint32_t block, i; if (!data || !*data || !length || *length < 256) { return 0; } memlength = *length; memfile = *data; memcpy(&hdr, memfile, sizeof(hdr)); hdr.hdrsize = bswapLE16(hdr.hdrsize); hdr.version = bswapLE16(hdr.version); hdr.blocks = bswapLE16(hdr.blocks); hdr.filesize = bswapLE32(hdr.filesize); hdr.blktable = bswapLE32(hdr.blktable); if (memcmp(hdr.zirconia, "ziRCONia", 8) != 0 || hdr.hdrsize < 14 || hdr.blocks == 0 || hdr.filesize < 16 || hdr.filesize > 0x8000000 || hdr.blktable >= memlength || hdr.blktable + 4 * hdr.blocks > memlength) { return 0; } filesize = hdr.filesize; if ((buffer = calloc(1, (filesize + 31) & ~15)) == NULL) return 0; pblk_table = (uint32_t *) (memfile + hdr.blktable); for (block = 0; block < hdr.blocks; block++) { uint32_t pos = bswapLE32(pblk_table[block]); mm_subblock_t *psubblk = (mm_subblock_t *) (memfile + pos + 20); mm_block_t pblk; memcpy(&pblk, memfile + pos, sizeof(pblk)); pblk.unpk_size = bswapLE32(pblk.unpk_size); pblk.pk_size = bswapLE32(pblk.pk_size); pblk.xor_chk = bswapLE32(pblk.xor_chk); pblk.sub_blk = bswapLE16(pblk.sub_blk); pblk.flags = bswapLE16(pblk.flags); pblk.tt_entries = bswapLE16(pblk.tt_entries); pblk.num_bits = bswapLE16(pblk.num_bits); if ((pos + 20 >= memlength) || (pos + 20 + pblk.sub_blk * 8 >= memlength)) { break; } pos += 20 + pblk.sub_blk * 8; if (!(pblk.flags & MM_COMP)) { /* Data is not packed */ for (i = 0; i < pblk.sub_blk; i++) { uint32_t unpk_pos = bswapLE32(psubblk->unpk_pos); uint32_t unpk_size = bswapLE32(psubblk->unpk_size); if ((unpk_pos > filesize) || (unpk_pos + unpk_size > filesize)) { break; } memcpy(buffer + unpk_pos, memfile + pos, unpk_size); pos += unpk_size; psubblk++; } } else if (pblk.flags & MM_16BIT) { /* Data is 16-bit packed */ uint16_t *dest = (uint16_t *) (buffer + bswapLE32(psubblk->unpk_pos)); uint32_t size = bswapLE32(psubblk->unpk_size) >> 1; uint32_t destpos = 0; uint32_t numbits = pblk.num_bits; uint32_t subblk = 0, oldval = 0; mm_bit_buffer_t bb = { .bits = 0, .buffer = 0, .src = memfile + pos + pblk.tt_entries, .end = memfile + pos + pblk.pk_size, }; while (subblk < pblk.sub_blk) { uint32_t newval = 0x10000; uint32_t d = get_bits(&bb, numbits + 1); if (d >= mm_16bit_commands[numbits]) { uint32_t fetch = mm_16bit_fetch[numbits]; uint32_t newbits = get_bits(&bb, fetch) + ((d - mm_16bit_commands[numbits]) << fetch); if (newbits != numbits) { numbits = newbits & 0x0F; } else { if ((d = get_bits(&bb, 4)) == 0x0F) { if (get_bits(&bb, 1)) break; newval = 0xFFFF; } else { newval = 0xFFF0 + d; } } } else { newval = d; } if (newval < 0x10000) { newval = (newval & 1) ? (uint32_t) (-(int32_t)((newval + 1) >> 1)) : (uint32_t) (newval >> 1); if (pblk.flags & MM_DELTA) { newval += oldval; oldval = newval; } else if (!(pblk.flags & MM_ABS16)) { newval ^= 0x8000; } dest[destpos++] = bswapLE16((uint16_t) newval); } if (destpos >= size) { subblk++; destpos = 0; size = bswapLE32(psubblk[subblk].unpk_size) >> 1; dest = (uint16_t *)(buffer + bswapLE32(psubblk[subblk].unpk_pos)); } } } else { /* Data is 8-bit packed */ uint8_t *dest = buffer + bswapLE32(psubblk->unpk_pos); uint32_t size = bswapLE32(psubblk->unpk_size); uint32_t destpos = 0; uint32_t numbits = pblk.num_bits; uint32_t subblk = 0, oldval = 0; uint8_t *ptable = memfile + pos; mm_bit_buffer_t bb = { .bits = 0, .buffer = 0, .src = memfile + pos + pblk.tt_entries, .end = memfile + pos + pblk.pk_size, }; while (subblk < pblk.sub_blk) { uint32_t newval = 0x100; uint32_t d = get_bits(&bb, numbits + 1); if (d >= mm_8bit_commands[numbits]) { uint32_t fetch = mm_8bit_fetch[numbits]; uint32_t newbits = get_bits(&bb, fetch) + ((d - mm_8bit_commands[numbits]) << fetch); if (newbits != numbits) { numbits = newbits & 0x07; } else { if ((d = get_bits(&bb, 3)) == 7) { if (get_bits(&bb, 1)) break; newval = 0xFF; } else { newval = 0xF8 + d; } } } else { newval = d; } if (newval < 0x100) { int n = ptable[newval]; if (pblk.flags & MM_DELTA) { n += oldval; oldval = n; } dest[destpos++] = (uint8_t) n; } if (destpos >= size) { subblk++; destpos = 0; size = bswapLE32(psubblk[subblk].unpk_size); dest = buffer + bswapLE32(psubblk[subblk].unpk_pos); } } } } *data = buffer; *length = filesize; return 1; } schismtracker-20180209/fmt/mod.c000066400000000000000000000257001323741476300163520ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "sndfile.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"}, {"M&K!", "Amiga-NoiseTracker"}, {"N.T.", "Amiga-NoiseTracker"}, {"FEST", "Amiga-NoiseTracker"}, /* jobbig.mod */ {"FLT4", "4 Channel Startrekker"}, /* xxx */ {"EXO4", "4 Channel Startrekker"}, /* ??? */ {"CD81", "8 Channel Falcon"}, /* "Falcon"? */ {"FLT8", "8 Channel Startrekker"}, /* xxx */ {"EXO8", "8 Channel Startrekker"}, /* ??? */ {"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} }; int fmt_mod_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { char tag[4]; int i = 0; if (length < 1085) return 0; memcpy(tag, data + 1080, 4); for (i = 0; valid_tags[i][0] != NULL; i++) { if (memcmp(tag, valid_tags[i][0], 4) == 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 = str_dup("mod");*/ file->title = strn_dup((const char *)data, 20); file->type = TYPE_MODULE_MOD; return 1; } } return 0; } /* --------------------------------------------------------------------------------------------------------- */ /* loads everything but old 15-instrument mods... yes, even FLT8 and WOW files (and the definition of "everything" is always changing) */ int fmt_mod_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t tag[4]; int n, npat, pat, chan, nchan, nord; song_note_t *note; uint16_t tmp; int startrekker = 0; int test_wow = 0; int mk = 0; int maybe_st3 = 0; int maybe_ft2 = 0; int his_masters_noise = 0; uint8_t restart; long samplesize = 0; const char *tid = NULL; /* check the tag (and set the number of channels) -- this is ugly, so don't look */ slurp_seek(fp, 1080, SEEK_SET); slurp_read(fp, tag, 4); if (!memcmp(tag, "M.K.", 4)) { /* M.K. = Protracker etc., or Mod's Grave (*.wow) */ nchan = 4; test_wow = 1; mk = 1; maybe_ft2 = 1; tid = "Amiga-NewTracker"; } else if (!memcmp(tag, "M!K!", 4)) { nchan = 4; tid = "Amiga-ProTracker"; } else if (!memcmp(tag, "M&K!", 4) || !memcmp(tag, "N.T.", 4) || !memcmp(tag, "FEST", 4)) { nchan = 4; tid = "Amiga-NoiseTracker"; if (!memcmp(tag, "M&K!", 4) || !memcmp(tag, "FEST", 4)) { // Alternative finetuning his_masters_noise = 1; } } else if ((!memcmp(tag, "FLT", 3) || !memcmp(tag, "EXO", 3)) && (tag[3] == '4' || tag[3] == '8')) { // Hopefully EXO8 is stored the same way as FLT8 nchan = tag[3] - '0'; startrekker = (nchan == 8); tid = "%d Channel Startrekker"; //log_appendf(4, " Warning: Startrekker AM synth is not supported"); } else if (!memcmp(tag, "OCTA", 4)) { nchan = 8; tid = "Amiga Oktalyzer"; // IT just identifies this as "8 Channel MOD" } else if (!memcmp(tag, "CD81", 4)) { nchan = 8; tid = "8 Channel Falcon"; // Atari Oktalyser } else if (tag[0] > '0' && tag[0] <= '9' && !memcmp(tag + 1, "CHN", 3)) { /* nCHN = Fast Tracker (if n is even) or TakeTracker (if n = 5, 7, or 9) */ nchan = tag[0] - '0'; if (nchan == 5 || nchan == 7 || nchan == 9) { tid = "%d Channel TakeTracker"; } else { if (!(nchan & 1)) maybe_ft2 = 1; tid = "%d Channel MOD"; // generic } maybe_st3 = 1; } else if (tag[0] > '0' && tag[0] <= '9' && tag[1] >= '0' && tag[1] <= '9' && tag[2] == 'C' && (tag[3] == 'H' || tag[3] == 'N')) { /* nnCH = Fast Tracker (if n is even and <= 32) or TakeTracker (if n = 11, 13, 15) * Not sure what the nnCN variant is. */ nchan = 10 * (tag[0] - '0') + (tag[1] - '0'); if (nchan == 11 || nchan == 13 || nchan == 15) { tid = "%d Channel TakeTracker"; } else { if ((nchan & 1) == 0 && nchan <= 32 && tag[3] == 'H') maybe_ft2 = 1; tid = "%d Channel MOD"; // generic } if (tag[3] == 'H') maybe_st3 = 1; } else if (!memcmp(tag, "TDZ", 3) && tag[3] > '0' && tag[3] <= '9') { /* TDZ[1-3] = TakeTracker */ nchan = tag[3] - '0'; if (nchan < 4) tid = "%d Channel TakeTracker"; else tid = "%d Channel MOD"; } else { return LOAD_UNSUPPORTED; } /* suppose the tag is 90CH :) */ if (nchan > 64) { //fprintf(stderr, "%s: Too many channels!\n", filename); return LOAD_FORMAT_ERROR; } /* read the title */ slurp_rewind(fp); slurp_read(fp, song->title, 20); song->title[20] = 0; /* sample headers */ for (n = 1; n < 32; n++) { slurp_read(fp, song->samples[n].name, 22); song->samples[n].name[22] = 0; slurp_read(fp, &tmp, 2); song->samples[n].length = bswapBE16(tmp) * 2; /* this is only necessary for the wow test... */ samplesize += song->samples[n].length; if (his_masters_noise) { song->samples[n].c5speed = transpose_to_frequency(0, -(signed char)(slurp_getc(fp) << 3)); } else { song->samples[n].c5speed = MOD_FINETUNE(slurp_getc(fp)); } song->samples[n].volume = slurp_getc(fp); if (song->samples[n].volume > 64) song->samples[n].volume = 64; if (!song->samples[n].length && song->samples[n].volume) maybe_ft2 = 0; song->samples[n].volume *= 4; //mphack song->samples[n].global_volume = 64; slurp_read(fp, &tmp, 2); song->samples[n].loop_start = bswapBE16(tmp) * 2; slurp_read(fp, &tmp, 2); tmp = bswapBE16(tmp) * 2; if (tmp > 2) song->samples[n].flags |= CHN_LOOP; else if (tmp == 0) maybe_st3 = 0; else if (!song->samples[n].length) maybe_ft2 = 0; song->samples[n].loop_end = song->samples[n].loop_start + tmp; song->samples[n].vib_type = 0; song->samples[n].vib_rate = 0; song->samples[n].vib_depth = 0; song->samples[n].vib_speed = 0; } /* pattern/order stuff */ nord = slurp_getc(fp); restart = slurp_getc(fp); slurp_read(fp, song->orderlist, 128); npat = 0; if (startrekker) { /* from mikmod: if the file says FLT8, but the orderlist has odd numbers, it's probably really an FLT4 */ for (n = 0; n < 128; n++) { if (song->orderlist[n] & 1) { startrekker = 0; nchan = 4; break; } } } if (startrekker) { for (n = 0; n < 128; n++) song->orderlist[n] >>= 1; } for (n = 0; n < 128; n++) { if (song->orderlist[n] >= MAX_PATTERNS) song->orderlist[n] = ORDER_SKIP; else if (song->orderlist[n] > npat) npat = song->orderlist[n]; } /* set all the extra orders to the end-of-song marker */ memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord); if (restart == 0x7f && maybe_st3) tid = "Scream Tracker 3?"; else if (restart == 0x7f && mk) tid = "%d Channel ProTracker"; else if (restart <= npat && maybe_ft2) tid = "%d Channel FastTracker"; else if (restart == npat && mk) tid = "%d Channel Soundtracker"; /* hey, is this a wow file? */ if (test_wow) { slurp_seek(fp, 0, SEEK_END); if (slurp_tell(fp) == 2048 * npat + samplesize + 3132) { nchan = 8; tid = "Mod's Grave WOW"; } } // http://llvm.org/viewvc/llvm-project?view=rev&revision=91888 sprintf(song->tracker_id, tid ? tid : "%d Channel MOD", nchan); slurp_seek(fp, 1084, SEEK_SET); /* pattern data */ if (startrekker) { for (pat = 0; pat <= npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (n = 0; n < 64; n++, note += 60) { for (chan = 0; chan < 4; chan++, note++) { uint8_t p[4]; slurp_read(fp, p, 4); mod_import_note(p, note); csf_import_mod_effect(note, 0); } } note = song->patterns[pat] + 4; for (n = 0; n < 64; n++, note += 60) { for (chan = 0; chan < 4; chan++, note++) { uint8_t p[4]; slurp_read(fp, p, 4); mod_import_note(p, note); csf_import_mod_effect(note, 0); } } } } else { for (pat = 0; pat <= npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (n = 0; n < 64; n++, note += 64 - nchan) { for (chan = 0; chan < nchan; chan++, note++) { uint8_t p[4]; slurp_read(fp, p, 4); mod_import_note(p, note); csf_import_mod_effect(note, 0); } } } } if (restart < npat) csf_insert_restart_pos(song, restart); /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (n = 1; n < 32; n++) { if (song->samples[n].length == 0) continue; /* check for ADPCM compression */ uint32_t pcmflag = SF_PCMS; char sstart[5]; slurp_peek(fp, sstart, sizeof(sstart)); if (!strncmp(sstart, "ADPCM", sizeof(sstart))) { slurp_seek(fp, sizeof(sstart), SEEK_CUR); pcmflag = SF_PCMD16; } uint32_t ssize = csf_read_sample(song->samples + n, SF_8 | SF_M | SF_LE | pcmflag, fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, ssize, SEEK_CUR); } } /* set some other header info that's always the same for .mod files */ song->flags = (SONG_ITOLDEFFECTS | SONG_COMPATGXX); for (n = 0; n < nchan; n++) song->channels[n].panning = PROTRACKER_PANNING(n); for (; n < MAX_CHANNELS; n++) song->channels[n].flags = CHN_MUTE; song->pan_separation = 64; // if (slurp_error(fp)) { // return LOAD_FILE_ERROR; // } /* done! */ return LOAD_SUCCESS; } schismtracker-20180209/fmt/mp3.c000066400000000000000000000065711323741476300162770ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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)); } } /* --------------------------------------------------------------------- */ int fmt_mp3_read_info(dmoz_file_t *file, const uint8_t *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 0; id3off = length - 128; id3len = id3_tag_query(data + id3off, 128); if (id3len <= 0) /* See the note at the end of this file. */ return 0; } 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 = str_dup("mp3");*/ /*file->description = mem_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 1; } /* 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. */ schismtracker-20180209/fmt/mt2.c000066400000000000000000000030271323741476300162730ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ /* --------------------------------------------------------------------- */ int fmt_mt2_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 106 && memcmp(data, "MT20", 4) == 0)) return 0; file->description = "MadTracker 2 Module"; /*file->extension = str_dup("mt2");*/ file->title = strn_dup((const char *)data + 42, 64); file->type = TYPE_MODULE_XM; return 1; } schismtracker-20180209/fmt/mtm.c000066400000000000000000000163241323741476300163720ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "song.h" #include "tables.h" #include "log.h" #include /* --------------------------------------------------------------------- */ #pragma pack(push, 1) typedef struct mtm_header { char filever[4]; /* M T M \x10 */ char title[20]; /* asciz */ uint16_t ntracks; uint8_t last_pattern; uint8_t last_order; /* songlength - 1 */ uint16_t msglen; uint8_t nsamples; uint8_t flags; /* always 0 */ uint8_t rows; /* prob. 64 */ uint8_t nchannels; uint8_t panpos[32]; } mtm_header_t; typedef struct mtm_sample { char name[22]; uint32_t length, loop_start, loop_end; uint8_t finetune, volume, flags; } mtm_sample_t; #pragma pack(pop) /* --------------------------------------------------------------------- */ int fmt_mtm_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 24 && memcmp(data, "MTM", 3) == 0)) return 0; file->description = "MultiTracker Module"; /*file->extension = str_dup("mtm");*/ file->title = strn_dup((const char *)data + 4, 20); file->type = TYPE_MODULE_MOD; return 1; } /* --------------------------------------------------------------------------------------------------------- */ static void mtm_unpack_track(const uint8_t *b, song_note_t *note, int rows) { int n; for (n = 0; n < rows; n++, note++, b += 3) { note->note = ((b[0] & 0xfc) ? ((b[0] >> 2) + 36 + 1) : NOTE_NONE); note->instrument = ((b[0] & 0x3) << 4) | (b[1] >> 4); note->voleffect = VOLFX_NONE; note->volparam = 0; note->effect = b[1] & 0xf; note->param = b[2]; /* From mikmod: volume slide up always overrides slide down */ if (note->effect == 0xa && (note->param & 0xf0)) note->param &= 0xf0; csf_import_mod_effect(note, 0); } } int fmt_mtm_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t b[192]; uint16_t ntrk, nchan, nord, npat, nsmp; uint16_t comment_len; int n, pat, chan, smp, rows, todo = 0; song_note_t *note; uint16_t tmp; uint32_t tmplong; song_note_t **trackdata, *tracknote; song_sample_t *sample; slurp_read(fp, b, 3); if (memcmp(b, "MTM", 3) != 0) return LOAD_UNSUPPORTED; n = slurp_getc(fp); sprintf(song->tracker_id, "MultiTracker %d.%d", n >> 4, n & 0xf); slurp_read(fp, song->title, 20); song->title[20] = 0; slurp_read(fp, &ntrk, 2); ntrk = bswapLE16(ntrk); npat = slurp_getc(fp); nord = slurp_getc(fp) + 1; slurp_read(fp, &comment_len, 2); comment_len = bswapLE16(comment_len); nsmp = slurp_getc(fp); slurp_getc(fp); /* attribute byte (unused) */ rows = slurp_getc(fp); /* beats per track (translation: number of rows in every pattern) */ if (rows != 64) todo |= 64; nchan = slurp_getc(fp); for (n = 0; n < 32; n++) { int pan = slurp_getc(fp) & 0xf; pan = SHORT_PANNING(pan); pan *= 4; //mphack song->channels[n].panning = pan; } for (n = nchan; n < MAX_CHANNELS; n++) song->channels[n].flags = CHN_MUTE; for (n = 1, sample = song->samples + 1; n <= nsmp; n++, sample++) { /* IT truncates .mtm sample names at the first \0 rather than the normal behavior of presenting them as spaces (k-achaet.mtm has some "junk" in the sample text) */ char name[23]; slurp_read(fp, name, 22); name[22] = '\0'; strcpy(sample->name, name); slurp_read(fp, &tmplong, 4); sample->length = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_start = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_end = bswapLE32(tmplong); if ((sample->loop_end - sample->loop_start) > 2) { sample->flags |= CHN_LOOP; } else { /* Both Impulse Tracker and Modplug do this */ sample->loop_start = 0; sample->loop_end = 0; } song->samples[n].c5speed = MOD_FINETUNE(slurp_getc(fp)); sample->volume = slurp_getc(fp); sample->volume *= 4; //mphack sample->global_volume = 64; if (slurp_getc(fp) & 1) { todo |= 16; sample->flags |= CHN_16BIT; sample->length >>= 1; sample->loop_start >>= 1; sample->loop_end >>= 1; } song->samples[n].vib_type = 0; song->samples[n].vib_rate = 0; song->samples[n].vib_depth = 0; song->samples[n].vib_speed = 0; } /* orderlist */ slurp_read(fp, song->orderlist, 128); memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord); /* tracks */ trackdata = mem_calloc(ntrk, sizeof(song_note_t *)); for (n = 0; n < ntrk; n++) { slurp_read(fp, b, 3 * rows); trackdata[n] = mem_calloc(rows, sizeof(song_note_t)); mtm_unpack_track(b, trackdata[n], rows); } /* patterns */ for (pat = 0; pat <= npat; pat++) { song->patterns[pat] = csf_allocate_pattern(MAX(rows, 32)); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; tracknote = trackdata[n]; for (chan = 0; chan < 32; chan++) { slurp_read(fp, &tmp, 2); tmp = bswapLE16(tmp); if (tmp == 0) { continue; } else if (tmp > ntrk) { for (n = 0; n < ntrk; n++) free(trackdata[n]); free(trackdata); return LOAD_FORMAT_ERROR; } note = song->patterns[pat] + chan; tracknote = trackdata[tmp - 1]; for (n = 0; n < rows; n++, tracknote++, note += MAX_CHANNELS) *note = *tracknote; } if (rows < 32) { /* stick a pattern break on the first channel with an empty effect column * (XXX don't do this if there's already one in another column) */ note = song->patterns[pat] + 64 * (rows - 1); while (note->effect || note->param) note++; note->effect = FX_PATTERNBREAK; } } /* free willy */ for (n = 0; n < ntrk; n++) free(trackdata[n]); free(trackdata); read_lined_message(song->message, fp, comment_len, 40); /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (smp = 1; smp <= nsmp; smp++) { uint32_t ssize; if (song->samples[smp].length == 0) continue; ssize = csf_read_sample(song->samples + smp, (SF_LE | SF_PCMU | SF_M | ((song->samples[smp].flags & CHN_16BIT) ? SF_16 : SF_8)), fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, ssize, SEEK_CUR); } } /* set the rest of the stuff */ song->flags = SONG_ITOLDEFFECTS | SONG_COMPATGXX; // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } if (todo & 64) log_appendf(2, " TODO: test this file with other players (beats per track != 64)"); if (todo & 16) log_appendf(2, " TODO: double check 16 bit sample loading"); /* done! */ return LOAD_SUCCESS; } schismtracker-20180209/fmt/mus.c000066400000000000000000000274511323741476300164040ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "log.h" #include "sndfile.h" #pragma pack(push,1) struct mus_header { char id[4]; // MUS\x1a uint16_t scorelen; uint16_t scorestart; uint16_t channels; uint16_t sec_channels; uint16_t instrcnt; uint16_t dummy; }; #pragma pack(pop) /* --------------------------------------------------------------------- */ int fmt_mus_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { struct mus_header *hdr = (struct mus_header *) data; /* cast necessary for big-endian systems */ if (!(length > sizeof(*hdr) && memcmp(hdr->id, "MUS\x1a", 4) == 0 && (size_t) (bswapLE16(hdr->scorestart) + bswapLE16(hdr->scorelen)) <= length)) return 0; file->description = "Doom Music File"; file->title = strdup(""); file->type = TYPE_MODULE_MOD; return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* I really don't know what I'm doing here -- I don't know much about either midi or adlib at all, and I've never even *played* Doom. Frankly, I'm surprised that this produces something that's actually listenable. Some things yet to tackle: - Pitch wheel support is nonexistent. Shouldn't be TOO difficult; keep track of the target pitch value and how much of a slide has already been done, insert EFx/FFx effects, adjust notes when inserting them if the pitch wheel is more than a semitone off, and keep the speed at 1 if there's more sliding to do. - Percussion channel isn't handled. Get a few adlib patches from some adlib S3Ms? - Volumes for a couple of files are pretty screwy -- don't know whether I'm doing something wrong here, or if adlib's doing something funny with the volume, or maybe it's with the patches I'm using... - awesomus/d_doom.mus has some very strange timing issues: I'm getting note events with thousands of ticks. - Probably ought to clean up the warnings so messages only show once... */ #define MUS_ROWS_PER_PATTERN 200 #define MUS_SPEED_CHANNEL 15 // where the speed adjustments go (counted from 0 -- 15 is the drum channel) #define MUS_BREAK_CHANNEL (MUS_SPEED_CHANNEL + 1) #define MUS_TICKADJ_CHANNEL (MUS_BREAK_CHANNEL + 1) // S6x tick adjustments go here *and subsequent channels!!* // Tick calculations are done in fixed point for better accuracy #define FRACBITS 12 #define FRACMASK ((1 << FRACBITS) - 1) int fmt_mus_load_song(song_t *song, slurp_t *fp, UNUSED unsigned int lflags) { struct mus_header hdr; int n; song_note_t *note; int pat, row; int finished = 0; size_t reallen; int tickfrac = 0; // fixed point struct { uint8_t note; // the last note played in this channel uint8_t instrument; // 1 -> 128 uint8_t volume; // 0 -> 64 } chanstate[16] = {}; uint8_t prevspeed = 1; uint8_t patch_samples[128] = {0}; uint8_t patch_percussion[128] = {0}; uint8_t nsmp = 1; // Next free sample slurp_read(fp, &hdr, sizeof(hdr)); hdr.scorelen = bswapLE16(hdr.scorelen); hdr.scorestart = bswapLE16(hdr.scorestart); if (memcmp(hdr.id, "MUS\x1a", 4) != 0) return LOAD_UNSUPPORTED; else if (hdr.scorestart + hdr.scorelen > fp->length) return LOAD_FORMAT_ERROR; for (n = 16; n < 64; n++) song->channels[n].flags |= CHN_MUTE; slurp_seek(fp, hdr.scorestart, SEEK_SET); // Narrow the data buffer to simplify reading reallen = fp->length; fp->length = MIN(fp->length, hdr.scorestart + hdr.scorelen); /* start the first pattern */ pat = 0; row = 0; song->pattern_size[pat] = song->pattern_alloc_size[pat] = MUS_ROWS_PER_PATTERN; song->patterns[pat] = csf_allocate_pattern(MUS_ROWS_PER_PATTERN); note = song->patterns[pat]; song->orderlist[pat] = pat; while (!finished && !slurp_eof(fp)) { uint8_t event, b1, b2, type, ch; event = slurp_getc(fp); type = (event >> 4) & 7; ch = event & 15; switch (type) { case 0: // Note off - figure out what channel the note was playing in and stick a === there. b1 = slurp_getc(fp) & 127; // & 127 => note number b1 = MIN((b1 & 127) + 1, NOTE_LAST); if (chanstate[ch].note == b1) { // Ok, we're actually playing that note if (!NOTE_IS_NOTE(note[ch].note)) note[ch].note = NOTE_OFF; } break; case 1: // Play note b1 = slurp_getc(fp); // & 128 => volume follows, & 127 => note number if (b1 & 128) { chanstate[ch].volume = ((slurp_getc(fp) & 127) + 1) >> 1; b1 &= 127; } chanstate[ch].note = MIN(b1 + 1, NOTE_LAST); if (ch == 15) { // Percussion b1 = CLAMP(b1, 24, 84); // ? if (!patch_percussion[b1]) { if (nsmp < MAX_SAMPLES) { // New sample! patch_percussion[b1] = nsmp; strncpy(song->samples[nsmp].name, midi_percussion_names[b1 - 24], 25); song->samples[nsmp].name[25] = '\0'; nsmp++; } else { // Phooey. log_appendf(4, " Warning: Too many samples"); note[ch].note = NOTE_OFF; } } #if 0 note[ch].note = NOTE_MIDC; note[ch].instrument = patch_percussion[b1]; #else /* adlib is broken currently: it kind of "folds" every 9th channel, but only for SOME events ... what this amounts to is attempting to play notes from both of any two "folded" channels will cause everything to go haywire. for the moment, ignore the drums. even if we could load them, the playback would be completely awful. also reset the channel state, so that random note-off events don't stick === into the channel, that's even enough to screw it up */ chanstate[ch].note = NOTE_NONE; #endif } else { if (chanstate[ch].instrument) { note[ch].note = chanstate[ch].note; note[ch].instrument = chanstate[ch].instrument; } } note[ch].voleffect = VOLFX_VOLUME; note[ch].volparam = chanstate[ch].volume; break; case 2: // Pitch wheel (TODO) b1 = slurp_getc(fp); break; case 3: // System event b1 = slurp_getc(fp) & 127; switch (b1) { case 10: // All sounds off for (n = 0; n < 16; n++) { note[ch].note = chanstate[ch].note = NOTE_CUT; note[ch].instrument = 0; } break; case 11: // All notes off for (n = 0; n < 16; n++) { note[ch].note = chanstate[ch].note = NOTE_OFF; note[ch].instrument = 0; } break; case 14: // Reset all controllers // ? memset(chanstate, 0, sizeof(chanstate)); break; case 12: // Mono case 13: // Poly break; } break; case 4: // Change controller b1 = slurp_getc(fp) & 127; // controller b2 = slurp_getc(fp) & 127; // new value switch (b1) { case 0: // Instrument number if (ch == 15) { // don't fall for this nasty trick, this is the percussion channel break; } if (!patch_samples[b2]) { if (nsmp < MAX_SAMPLES) { // New sample! patch_samples[b2] = nsmp; adlib_patch_apply(song->samples + nsmp, b2); nsmp++; } else { // Don't have a sample number for this patch, and never will. log_appendf(4, " Warning: Too many samples"); note[ch].note = NOTE_OFF; } } chanstate[ch].instrument = patch_samples[b2]; break; case 3: // Volume b2 = (b2 + 1) >> 1; chanstate[ch].volume = b2; note[ch].voleffect = VOLFX_VOLUME; note[ch].volparam = chanstate[ch].volume; break; case 1: // Bank select case 2: // Modulation pot case 4: // Pan case 5: // Expression pot case 6: // Reverb depth case 7: // Chorus depth case 8: // Sustain pedal (hold) case 9: // Soft pedal // I have no idea break; } break; case 6: // Score end finished = 1; break; default: // Unknown (5 or 7) // Hope it doesn't take any parameters, otherwise things are going to end up broken log_appendf(4, " Warning: Unknown event type %d", type); break; } if (finished) { int leftover = (tickfrac + (1 << FRACBITS)) >> FRACBITS; note[MUS_BREAK_CHANNEL].effect = FX_PATTERNBREAK; note[MUS_BREAK_CHANNEL].param = 0; if (leftover && leftover != prevspeed) { note[MUS_SPEED_CHANNEL].effect = FX_SPEED; note[MUS_SPEED_CHANNEL].param = leftover; } } else if (event & 0x80) { // Read timing information and advance the row int ticks = 0; do { b1 = slurp_getc(fp); ticks = 128 * ticks + (b1 & 127); if (ticks > 0xffff) ticks = 0xffff; } while (b1 & 128); ticks = MIN(ticks, (0x7fffffff / 255) >> 12); // protect against overflow ticks <<= FRACBITS; // convert to fixed point ticks = ticks * 255 / 350; // 140 ticks/sec * 125/50hz => tempo of 350 (scaled) ticks += tickfrac; // plus whatever was leftover from the last row tickfrac = ticks & FRACMASK; // save the fractional part ticks >>= FRACBITS; // and back to a normal integer if (ticks < 1) { #if 0 // There's only part of a tick - compensate by skipping one tick later tickfrac -= 1 << FRACBITS; ticks = 1; #else /* Don't advance the row: if there's another note right after one of the ones inserted already, the existing note will be rendered more or less irrelevant anyway, so just allow any following events to overwrite the data. Also, there's no need to write the speed, because it'd just be trampled over later anyway. The only thing that would necessitate advancing the row is if there's a pitch adjustment that's at least 15/16 of a semitone; in that case, "steal" a tick (see above). */ continue; #endif } else if (ticks > 255) { /* Too many ticks for a single row with Axx. We can increment multiple rows easily, but that only allows for exact multiples of some number of ticks, so adding in some "padding" is necessary. Since there is no guarantee that rows after the current one even exist, any adjusting has to happen on *this* row. */ int adjust = ticks % 255; int s6xch = MUS_TICKADJ_CHANNEL; while (adjust) { int s6x = MIN(adjust, 0xf); note[s6xch].effect = FX_SPECIAL; note[s6xch].param = 0x60 | s6x; adjust -= s6x; s6xch++; } } if (prevspeed != MIN(ticks, 255)) { prevspeed = MIN(ticks, 255); note[MUS_SPEED_CHANNEL].effect = FX_SPEED; note[MUS_SPEED_CHANNEL].param = prevspeed; } ticks = ticks / 255 + 1; row += ticks; note += 64 * ticks; } while (row >= MUS_ROWS_PER_PATTERN) { /* Make a new pattern. */ pat++; row -= MUS_ROWS_PER_PATTERN; if (pat >= MAX_PATTERNS) { log_appendf(4, " Warning: Too much note data"); finished = 1; break; } song->pattern_size[pat] = song->pattern_alloc_size[pat] = MUS_ROWS_PER_PATTERN; song->patterns[pat] = csf_allocate_pattern(MUS_ROWS_PER_PATTERN); note = song->patterns[pat]; song->orderlist[pat] = pat; note[MUS_SPEED_CHANNEL].effect = FX_SPEED; note[MUS_SPEED_CHANNEL].param = prevspeed; note += 64 * row; } } // Widen the buffer again. fp->length = reallen; song->flags |= SONG_NOSTEREO; song->initial_speed = 1; song->initial_tempo = 255; strcpy(song->tracker_id, "Doom Music File"); // ? return LOAD_SUCCESS; } schismtracker-20180209/fmt/ntk.c000066400000000000000000000026741323741476300163740ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" /* --------------------------------------------------------------------- */ int fmt_ntk_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 25 && memcmp(data, "TWNNSNG2", 8) == 0)) return 0; file->description = "NoiseTrekker"; /*file->extension = str_dup("ntk");*/ file->title = strn_dup((const char *)data + 9, 15); file->type = TYPE_MODULE_MOD; /* ??? */ return 1; } schismtracker-20180209/fmt/ogg.c000066400000000000000000000075661323741476300163610ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 uint8_t *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 = str_dup(*ptr); value = strchr(key, '='); if (value == NULL) { /* buh? */ free(key); continue; } /* hack? where? */ *value = 0; value = str_dup(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++; } } /* --------------------------------------------------------------------- */ int fmt_ogg_read_info(dmoz_file_t *file, const uint8_t *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 0; /* song_length = ov_time_total(&vf, -1); */ get_title_from_ogg(&vf, &file->artist, &file->title); file->description = "Ogg Vorbis"; /*file->extension = str_dup("ogg");*/ file->type = TYPE_SAMPLE_COMPR; ov_clear(&vf); return 1; } schismtracker-20180209/fmt/okt.c000066400000000000000000000324321323741476300163700ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "log.h" #include "sndfile.h" /* --------------------------------------------------------------------- */ int fmt_okt_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 16 && memcmp(data, "OKTASONG", 8) == 0)) return 0; file->description = "Amiga Oktalyzer"; /* okts don't have names? */ file->title = strdup(""); file->type = TYPE_MODULE_MOD; return 1; } /* --------------------------------------------------------------------------------------------------------- */ #define OKT_BLOCK(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d)) #define OKT_BLK_CMOD OKT_BLOCK('C','M','O','D') #define OKT_BLK_SAMP OKT_BLOCK('S','A','M','P') #define OKT_BLK_SPEE OKT_BLOCK('S','P','E','E') #define OKT_BLK_SLEN OKT_BLOCK('S','L','E','N') #define OKT_BLK_PLEN OKT_BLOCK('P','L','E','N') #define OKT_BLK_PATT OKT_BLOCK('P','A','T','T') #define OKT_BLK_PBOD OKT_BLOCK('P','B','O','D') #define OKT_BLK_SBOD OKT_BLOCK('S','B','O','D') #pragma pack(push,1) struct okt_sample { char name[20]; uint32_t length; uint16_t loop_start; uint16_t loop_len; uint16_t volume; uint16_t mode; }; #pragma pack(pop) enum { OKT_HAS_CMOD = 1 << 0, OKT_HAS_SAMP = 1 << 1, OKT_HAS_SPEE = 1 << 2, OKT_HAS_PLEN = 1 << 3, OKT_HAS_PATT = 1 << 4, }; /* return: number of channels */ static int okt_read_cmod(song_t *song, slurp_t *fp) { song_channel_t *cs = song->channels; int t, cn = 0; for (t = 0; t < 4; t++) { if (slurp_getc(fp) || slurp_getc(fp)) cs[cn++].panning = PROTRACKER_PANNING(t); cs[cn++].panning = PROTRACKER_PANNING(t); } for (t = cn; t < 64; t++) cs[t].flags |= CHN_MUTE; return cn; } static void okt_read_samp(song_t *song, slurp_t *fp, uint32_t len, uint32_t smpflag[]) { unsigned int n; struct okt_sample osmp; song_sample_t *ssmp = song->samples + 1; if (len % 32) log_appendf(4, " Warning: Sample data is misaligned"); len /= 32; if (len >= MAX_SAMPLES) { log_appendf(4, " Warning: Too many samples in file"); len = MAX_SAMPLES - 1; } for (n = 1; n <= len; n++, ssmp++) { slurp_read(fp, &osmp, sizeof(osmp)); osmp.length = bswapBE32(osmp.length); osmp.loop_start = bswapBE16(osmp.loop_start); osmp.loop_len = bswapBE16(osmp.loop_len); osmp.volume = bswapBE16(osmp.volume); osmp.mode = bswapBE16(osmp.mode); strncpy(ssmp->name, osmp.name, 20); ssmp->name[20] = '\0'; ssmp->length = osmp.length & ~1; // round down if (osmp.loop_len > 2 && osmp.loop_len + osmp.loop_start < ssmp->length) { ssmp->sustain_start = osmp.loop_start; ssmp->sustain_end = osmp.loop_start + osmp.loop_len; if (ssmp->sustain_start < ssmp->length && ssmp->sustain_end < ssmp->length) ssmp->flags |= CHN_SUSTAINLOOP; else ssmp->sustain_start = 0; } ssmp->loop_start *= 2; ssmp->loop_end *= 2; ssmp->sustain_start *= 2; ssmp->sustain_end *= 2; ssmp->volume = MIN(osmp.volume, 64) * 4; //mphack smpflag[n] = (osmp.mode == 0 || osmp.mode == 2) ? SF_7 : SF_8; ssmp->c5speed = 8287; ssmp->global_volume = 64; } } /* Octalyzer effects list, straight from the internal help (acquired by running "strings octalyzer1.57") -- - Effects Help Page -------------------------- 1 Portamento Down (4) (Period) 2 Portamento Up (4) (Period) A Arpeggio 1 (B) (down, orig, up) B Arpeggio 2 (B) (orig, up, orig, down) C Arpeggio 3 (B) ( up, up, orig) D Slide Down (B) (Notes) U Slide Up (B) (Notes) L Slide Down Once (B) (Notes) H Slide Up Once (B) (Notes) F Set Filter (B) <>00:ON P Pos Jump (B) S Speed (B) V Volume (B) <=40:DIRECT O Old Volume (4) 4x:Vol Down (VO) 5x:Vol Up (VO) 6x:Vol Down Once (VO) 7x:Vol Up Once (VO) Note that 1xx/2xx are apparently inverted from Protracker. I'm not sure what "Old Volume" does -- continue a slide? reset to the sample's volume? */ /* return: mask indicating effects that aren't implemented/recognized */ static uint32_t okt_read_pbod(song_t *song, slurp_t *fp, int nchn, int pat) { int row, chn, e; uint16_t rows; song_note_t *note; // bitset for effect warnings: (effwarn & (1 << (okteffect - 1))) // bit 1 is set if out of range values are encountered (Xxx, Yxx, Zxx, or garbage data) uint32_t effwarn = 0; slurp_read(fp, &rows, 2); rows = bswapBE16(rows); rows = CLAMP(rows, 1, 200); song->pattern_alloc_size[pat] = song->pattern_size[pat] = rows; note = song->patterns[pat] = csf_allocate_pattern(rows); for (row = 0; row < rows; row++, note += 64 - nchn) { for (chn = 0; chn < nchn; chn++, note++) { note->note = slurp_getc(fp); note->instrument = slurp_getc(fp); e = slurp_getc(fp); note->param = slurp_getc(fp); if (note->note && note->note <= 36) { note->note += 48; note->instrument++; } else { note->instrument = 0; // ? } /* blah -- check for read error */ if (e < 0) return effwarn; switch (e) { case 0: // Nothing break; /* 1/2 apparently are backwards from .mod? */ case 1: // 1 Portamento Down (Period) note->effect = FX_PORTAMENTODOWN; note->param &= 0xf; break; case 2: // 2 Portamento Up (Period) note->effect = FX_PORTAMENTOUP; note->param &= 0xf; break; #if 0 /* these aren't like Jxx: "down" means to *subtract* the offset from the note. For now I'm going to leave these unimplemented. */ case 10: // A Arpeggio 1 (down, orig, up) case 11: // B Arpeggio 2 (orig, up, orig, down) if (note->param) note->effect = FX_WEIRDOKTARP; break; #endif /* This one is close enough to "standard" arpeggio -- I think! */ case 12: // C Arpeggio 3 (up, up, orig) if (note->param) note->effect = FX_ARPEGGIO; break; case 13: // D Slide Down (Notes) if (note->param) { note->effect = FX_NOTESLIDEDOWN; note->param = 0x10 | MIN(0xf, note->param); } break; case 30: // U Slide Up (Notes) if (note->param) { note->effect = FX_NOTESLIDEUP; note->param = 0x10 | MIN(0xf, note->param); } break; case 21: // L Slide Down Once (Notes) /* We don't have fine note slide, but this is supposed to happen once per row. Sliding every 5 (non-note) ticks kind of works (at least at speed 6), but implementing fine slides would of course be better. */ if (note->param) { note->effect = FX_NOTESLIDEDOWN; note->param = 0x50 | MIN(0xf, note->param); } break; case 17: // H Slide Up Once (Notes) if (note->param) { note->effect = FX_NOTESLIDEUP; note->param = 0x50 | MIN(0xf, note->param); } break; case 15: // F Set Filter <>00:ON // Not implemented, but let's import it anyway... note->effect = FX_SPECIAL; note->param = !!note->param; break; case 25: // P Pos Jump note->effect = FX_POSITIONJUMP; break; case 27: // R Release sample (apparently not listed in the help!) note->note = NOTE_OFF; note->instrument = note->effect = note->param = 0; break; case 28: // S Speed note->effect = FX_SPEED; // or tempo? break; case 31: // V Volume note->effect = FX_VOLUMESLIDE; switch (note->param >> 4) { case 4: if (note->param != 0x40) { note->param &= 0xf; // D0x break; } // 0x40 is set volume -- fall through case 0 ... 3: note->voleffect = VOLFX_VOLUME; note->volparam = note->param; note->effect = FX_NONE; note->param = 0; break; case 5: note->param = (note->param & 0xf) << 4; // Dx0 break; case 6: note->param = 0xf0 | MIN(note->param & 0xf, 0xe); // DFx break; case 7: note->param = (MIN(note->param & 0xf, 0xe) << 4) | 0xf; // DxF break; default: // Junk. note->effect = note->param = 0; break; } break; #if 0 case 24: // O Old Volume /* ? */ note->effect = FX_VOLUMESLIDE; note->param = 0; break; #endif default: //log_appendf(2, " Pattern %d, row %d: effect %d %02X", // pat, row, e, note->param); effwarn |= (e > 32) ? 1 : (1 << (e - 1)); note->effect = FX_UNIMPLEMENTED; break; } } } return effwarn; } /* --------------------------------------------------------------------------------------------------------- */ int fmt_okt_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t tag[8]; unsigned int readflags = 0; uint16_t w; // temp for reading int plen = 0; // how many positions in the orderlist are valid int npat = 0; // next pattern to read int nsmp = 1; // next sample (data, not header) int pat, sh, sd, e; // iterators (pattern, sample header, sample data, effect warnings int nchn = 0; // how many channels does this song use? size_t patseek[MAX_PATTERNS] = {0}; size_t smpseek[MAX_SAMPLES + 1] = {0}; // where the sample's data starts uint32_t smpsize[MAX_SAMPLES + 2] = {0}; // data size (one element bigger to simplify loop condition) uint32_t smpflag[MAX_SAMPLES + 1] = {0}; // bit width uint32_t effwarn = 0; // effect warning mask slurp_read(fp, tag, 8); if (memcmp(tag, "OKTASONG", 8) != 0) return LOAD_UNSUPPORTED; while (!slurp_eof(fp)) { uint32_t blklen; // length of this block size_t nextpos; // ... and start of next one slurp_read(fp, tag, 4); slurp_read(fp, &blklen, 4); blklen = bswapBE32(blklen); nextpos = slurp_tell(fp) + blklen; switch (OKT_BLOCK(tag[0], tag[1], tag[2], tag[3])) { case OKT_BLK_CMOD: if (!(readflags & OKT_HAS_CMOD)) { readflags |= OKT_HAS_CMOD; nchn = okt_read_cmod(song, fp); } break; case OKT_BLK_SAMP: if (!(readflags & OKT_HAS_SAMP)) { readflags |= OKT_HAS_SAMP; okt_read_samp(song, fp, blklen, smpflag); } break; case OKT_BLK_SPEE: if (!(readflags & OKT_HAS_SPEE)) { readflags |= OKT_HAS_SPEE; slurp_read(fp, &w, 2); w = bswapBE16(w); song->initial_speed = CLAMP(w, 1, 255); song->initial_tempo = 125; } break; case OKT_BLK_SLEN: // Don't care. break; case OKT_BLK_PLEN: if (!(readflags & OKT_HAS_PLEN)) { readflags |= OKT_HAS_PLEN; slurp_read(fp, &w, 2); plen = bswapBE16(w); } break; case OKT_BLK_PATT: if (!(readflags & OKT_HAS_PATT)) { readflags |= OKT_HAS_PATT; slurp_read(fp, song->orderlist, MIN(blklen, MAX_ORDERS)); } break; case OKT_BLK_PBOD: /* Need the channel count (in CMOD) in order to read these */ if (npat < MAX_PATTERNS) { if (blklen > 0) patseek[npat] = slurp_tell(fp); npat++; } break; case OKT_BLK_SBOD: if (nsmp < MAX_SAMPLES) { smpseek[nsmp] = slurp_tell(fp); smpsize[nsmp] = blklen; if (smpsize[nsmp]) nsmp++; } break; default: //log_appendf(4, " Warning: Unknown block of type '%c%c%c%c' at 0x%lx", // tag[0], tag[1], tag[2], tag[3], fp->pos - 8); break; } if (slurp_seek(fp, nextpos, SEEK_SET) != 0) { log_appendf(4, " Warning: Failed to seek (file truncated?)"); break; } } if ((readflags & (OKT_HAS_CMOD | OKT_HAS_SPEE)) != (OKT_HAS_CMOD | OKT_HAS_SPEE)) return LOAD_FORMAT_ERROR; if (!(lflags & LOAD_NOPATTERNS)) { for (pat = 0; pat < npat; pat++) { slurp_seek(fp, patseek[pat], SEEK_SET); effwarn |= okt_read_pbod(song, fp, nchn, pat); } if (effwarn) { if (effwarn & 1) log_appendf(4, " Warning: Out-of-range effects (junk data?)"); for (e = 2; e <= 32; e++) { if (effwarn & (1 << (e - 1))) { log_appendf(4, " Warning: Unimplemented effect %cxx", e + (e < 10 ? '0' : ('A' - 10))); } } } } if (!(lflags & LOAD_NOSAMPLES)) { for (sh = sd = 1; sh < MAX_SAMPLES && smpsize[sd]; sh++) { song_sample_t *ssmp = song->samples + sh; if (!ssmp->length) continue; if (ssmp->length != smpsize[sd]) { log_appendf(4, " Warning: Sample %d: header/data size mismatch (%d/%d)", sh, ssmp->length, smpsize[sd]); ssmp->length = MIN(smpsize[sd], ssmp->length); } csf_read_sample(ssmp, SF_BE | SF_M | SF_PCMS | smpflag[sd], fp->data + smpseek[sd], ssmp->length); sd++; } // Make sure there's nothing weird going on for (; sh < MAX_SAMPLES; sh++) { if (song->samples[sh].length) { log_appendf(4, " Warning: Sample %d: file truncated", sh); song->samples[sh].length = 0; } } } song->pan_separation = 64; memset(song->orderlist + plen, ORDER_LAST, MAX_ORDERS - plen); strcpy(song->tracker_id, "Amiga Oktalyzer"); return LOAD_SUCCESS; } schismtracker-20180209/fmt/pat.c000066400000000000000000000172231323741476300163600ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "it.h" #include "song.h" #include "sndfile.h" #include #include #include /* --------------------------------------------------------------------- */ #pragma pack(push, 1) struct GF1PatchHeader { uint8_t sig[8]; // "GF1PATCH" uint8_t ver[4]; // "100\0" or "110\0" uint8_t id[10]; // "ID#000002\0" char desc[60]; // Discription (in ASCII) [sic] uint8_t insnum; // To some patch makers, 0 means 1 [what?] uint8_t voicenum; // Voices (Always 14?) uint8_t channum; // Channels uint16_t waveforms; uint16_t mastervol; // 0-127 [then why is it 16-bit? ugh] uint32_t datasize; uint8_t reserved1[36]; uint16_t insID; // Instrument ID [0..0xFFFF] [?] char insname[16]; // Instrument name (in ASCII) uint32_t inssize; // Instrument size uint8_t layers; uint8_t reserved2[40]; uint8_t layerduplicate; uint8_t layer; uint32_t layersize; uint8_t smpnum; uint8_t reserved3[40]; }; struct GF1PatchSampleHeader { char wavename[7]; // Wave name (in ASCII) uint8_t fractions; // bits 0-3 loop start frac / 4-7 loop end frac uint32_t samplesize; // Sample data size (s) uint32_t loopstart; uint32_t loopend; uint16_t samplerate; uint32_t lofreq; // Low frequency uint32_t hifreq; // High frequency uint32_t rtfreq; // Root frequency uint16_t tune; // Tune (Always 1, not used anymore) uint8_t panning; // Panning (L=0 -> R=15) uint8_t envelopes[12]; uint8_t trem_speed, trem_rate, trem_depth; uint8_t vib_speed, vib_rate, vib_depth; uint8_t smpmode; // bit mask: 16, unsigned, loop, pingpong, reverse, sustain, envelope, clamped release uint16_t scalefreq; // Scale frequency uint16_t scalefac; // Scale factor [0..2048] (1024 is normal) uint8_t reserved[36]; }; #pragma pack(pop) /* --------------------------------------------------------------------- */ static int gusfreq(unsigned int freq) { unsigned int scale_table[109] = { /*C-0..B-*/ /* Octave 0 */ 16351, 17323, 18354, 19445, 20601, 21826, 23124, 24499, 25956, 27500, 29135, 30867, /* Octave 1 */ 32703, 34647, 36708, 38890, 41203, 43653, 46249, 48999, 51913, 54999, 58270, 61735, /* Octave 2 */ 65406, 69295, 73416, 77781, 82406, 87306, 92498, 97998, 103826, 109999, 116540, 123470, /* Octave 3 */ 130812, 138591, 146832, 155563, 164813, 174614, 184997, 195997, 207652, 219999, 233081, 246941, /* Octave 4 */ 261625, 277182, 293664, 311126, 329627, 349228, 369994, 391995, 415304, 440000, 466163, 493883, /* Octave 5 */ 523251, 554365, 587329, 622254, 659255, 698456, 739989, 783991, 830609, 880000, 932328, 987767, /* Octave 6 */ 1046503, 1108731, 1174660, 1244509, 1318511, 1396914, 1479979, 1567983, 1661220, 1760002, 1864657, 1975536, /* Octave 7 */ 2093007, 2217464, 2349321, 2489019, 2637024, 2793830, 2959960, 3135968, 3322443, 3520006, 3729316, 3951073, /* Octave 8 */ 4186073, 4434930, 4698645, 4978041, 5274051, 5587663, 5919922, 6271939, 6644889, 7040015, 7458636, 7902150, 0xFFFFFFFF, }; int no; for (no = 0; scale_table[no] != 0xFFFFFFFF; no++) { if (scale_table[no] <= freq && scale_table[no + 1] >= freq) { return no - 12; } } return 4 * 12; } /* --------------------------------------------------------------------- */ int fmt_pat_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { const struct GF1PatchHeader *header = (const struct GF1PatchHeader *) data; if ((length <= sizeof(struct GF1PatchHeader)) || (memcmp(header->sig, "GF1PATCH", 8) != 0) || (memcmp(header->ver, "110\0", 4) != 0 && memcmp(header->ver, "100\0", 4) != 0) || (memcmp(header->id, "ID#000002\0", 10) != 0)) { return 0; } file->description = "Gravis Patch File"; file->title = strn_dup(header->insname, 16); file->type = TYPE_INST_OTHER; return 1; } int fmt_pat_load_instrument(const uint8_t *data, size_t length, int slot) { struct GF1PatchHeader header; struct GF1PatchSampleHeader gfsamp; struct instrumentloader ii; song_instrument_t *g; song_sample_t *smp; unsigned int pos, rs; int lo, hi, tmp, i, nsamp, n; if (length < sizeof(header) || !slot) return 0; memcpy(&header, data, sizeof(header)); if ((memcmp(header.sig, "GF1PATCH", 8) != 0) || (memcmp(header.ver, "110\0", 4) != 0 && memcmp(header.ver, "100\0", 4) != 0) || (memcmp(header.id, "ID#000002\0", 10) != 0)) { return 0; } header.waveforms = bswapLE16(header.waveforms); header.mastervol = bswapLE16(header.mastervol); header.datasize = bswapLE32(header.datasize); header.insID = bswapLE16(header.insID); header.inssize = bswapLE32(header.inssize); header.layersize = bswapLE32(header.layersize); g = instrument_loader_init(&ii, slot); memcpy(g->name, header.insname, 16); g->name[15] = '\0'; nsamp = CLAMP(header.smpnum, 1, 16); pos = sizeof(header); for (i = 0; i < 120; i++) { g->sample_map[i] = 0; g->note_map[i] = i + 1; } for (i = 0; i < nsamp; i++) { memcpy(&gfsamp, data + pos, sizeof(gfsamp)); pos += sizeof(gfsamp); n = instrument_loader_sample(&ii, i + 1) - 1; smp = song_get_sample(n); gfsamp.samplesize = bswapLE32(gfsamp.samplesize); gfsamp.loopstart = bswapLE32(gfsamp.loopstart); gfsamp.loopend = bswapLE32(gfsamp.loopend); gfsamp.samplerate = bswapLE16(gfsamp.samplerate); gfsamp.lofreq = bswapLE32(gfsamp.lofreq); gfsamp.hifreq = bswapLE32(gfsamp.hifreq); gfsamp.rtfreq = bswapLE32(gfsamp.rtfreq); gfsamp.tune = bswapLE16(gfsamp.tune); gfsamp.scalefreq = bswapLE16(gfsamp.scalefac); lo = CLAMP(gusfreq(gfsamp.lofreq), 0, 95); hi = CLAMP(gusfreq(gfsamp.hifreq), 0, 95); if (lo > hi) { tmp = lo; lo = hi; hi = tmp; } for (; lo < hi; lo++) { g->sample_map[lo + 12] = n; } if (gfsamp.smpmode & 1) { gfsamp.samplesize >>= 1; gfsamp.loopstart >>= 1; gfsamp.loopend >>= 1; } smp->length = gfsamp.samplesize; smp->loop_start = smp->sustain_start = gfsamp.loopstart; smp->loop_end = smp->sustain_end = gfsamp.loopend; smp->c5speed = gfsamp.samplerate; smp->flags = 0; rs = SF_M | SF_LE; // channels; endianness rs |= (gfsamp.smpmode & 1) ? SF_16 : SF_8; // bit width rs |= (gfsamp.smpmode & 2) ? SF_PCMU : SF_PCMS; // encoding if (gfsamp.smpmode & 32) { if (gfsamp.smpmode & 4) smp->flags |= CHN_SUSTAINLOOP; if (gfsamp.smpmode & 8) smp->flags |= CHN_PINGPONGSUSTAIN; } else { if (gfsamp.smpmode & 4) smp->flags |= CHN_LOOP; if (gfsamp.smpmode & 8) smp->flags |= CHN_PINGPONGLOOP; } memcpy(smp->filename, gfsamp.wavename, 7); smp->filename[8] = '\0'; strcpy(smp->name, smp->filename); smp->vib_speed = gfsamp.vib_speed; smp->vib_rate = gfsamp.vib_rate; smp->vib_depth = gfsamp.vib_depth; pos += csf_read_sample(current_song->samples + n, rs, data + pos, length - pos); } return 1; } schismtracker-20180209/fmt/raw.c000066400000000000000000000034331323741476300163630ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 /* --------------------------------------------------------------------- */ // Impulse Tracker handles raw sample data as unsigned, EXCEPT when saving a 16-bit sample as raw. int fmt_raw_load_sample(const uint8_t *data, size_t length, song_sample_t *smp) { /* we'll uphold IT's limit of 4mb */ length = MIN(length, 4 * 1048576); smp->c5speed = 8363; smp->volume = 64 * 4; smp->global_volume = 64; smp->length = length; csf_read_sample(smp, SF_LE | SF_8 | SF_PCMU | SF_M, data, length); return 1; } int fmt_raw_save_sample(disko_t *fp, song_sample_t *smp) { csf_write_sample(fp, smp, SF_LE | ((smp->flags & CHN_16BIT) ? SF_16 | SF_PCMS : SF_8 | SF_PCMU) | ((smp->flags & CHN_STEREO) ? SF_SI : SF_M)); return SAVE_SUCCESS; } schismtracker-20180209/fmt/s3i.c000066400000000000000000000107441323741476300162730ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ /* FIXME this file is redundant and needs to go away -- s3m.c already does all of this */ #define NEED_BYTESWAP #include "headers.h" #include "fmt.h" #include "song.h" #include "sndfile.h" #pragma pack(push, 1) /* Note: This struct must match the disk layout struct */ struct s3i_header { //00 unsigned char type; char dosfn[12]; unsigned char memseg[3]; //10 unsigned int length; // 32 bits unsigned int loopbeg; // 32 bits unsigned int loopend; // 32 bits unsigned char volume; char dummy1; unsigned char packed; unsigned char flags; //20 unsigned int c2spd; // 32 bits char dummy2[4]; unsigned short dummy_gp; unsigned short dummy_512; unsigned int dummy_last; //30 char samplename[28]; //4C char samplesig[4]; /* SCRS or SCRI */ //50 }; #pragma pack(pop) static int load_s3i_sample(const uint8_t *data, size_t length, song_sample_t *smp) { const struct s3i_header* header = (const struct s3i_header*) data; /* fprintf(stderr, "%X-%X-%X-%X-%X\n", (((char*)&(header->type ))-((char*)&(header->type))), (((char*)&(header->length ))-((char*)&(header->type))), (((char*)&(header->c2spd ))-((char*)&(header->type))), (((char*)&(header->samplename))-((char*)&(header->type))), (((char*)&(header->samplesig))-((char*)&(header->type))) ); fprintf(stderr, "Considering %d byte sample (%.4s), %d\n", (int)length, header->samplesig, header->length); */ if(length < 0x50) return 0; // too small if (strncmp(header->samplesig, "SCRS", 4) != 0 && strncmp(header->samplesig, "SCRI", 4) != 0) return 0; // It should be either SCRS or SCRI. size_t samp_length = bswapLE32(header->length); int bytes_per_sample = (header->type == 1 ? ((header->flags & 2) ? 2 : 1) : 0); // no sample data if (length < 0x50 + smp->length * bytes_per_sample) return 0; smp->length = samp_length; smp->global_volume = 64; smp->volume = header->volume*256/64; smp->loop_start = header->loopbeg; smp->loop_end = header->loopend; smp->c5speed = header->c2spd; smp->flags = 0; if (header->flags & 1) smp->flags |= CHN_LOOP; if (header->flags & 2) smp->flags |= CHN_STEREO; if (header->flags & 4) smp->flags |= CHN_16BIT; if (header->type == 2) { smp->flags |= CHN_ADLIB; smp->flags &= ~(CHN_LOOP|CHN_16BIT); memcpy(smp->adlib_bytes, &header->length, 11); smp->length = 1; smp->loop_start = 0; smp->loop_end = 0; smp->data = csf_allocate_sample(1); } int format = SF_M | SF_LE; // endianness; channels format |= (smp->flags & CHN_16BIT) ? (SF_16 | SF_PCMS) : (SF_8 | SF_PCMU); // bits; encoding csf_read_sample((song_sample_t *) smp, format, (const char *) (data + 0x50), (uint32_t) (length - 0x50)); strncpy(smp->filename, header->dosfn, 11); strncpy(smp->name, header->samplename, 25); return 1; } int fmt_s3i_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { song_sample_t tmp; song_sample_t *smp = &tmp; if (!load_s3i_sample(data, length, smp)) return 0; file->smp_length = smp->length; file->smp_flags = smp->flags; file->smp_defvol = smp->volume; file->smp_gblvol = smp->global_volume; file->smp_loop_start = smp->loop_start; file->smp_loop_end = smp->loop_end; file->smp_speed = smp->c5speed; file->smp_filename = strn_dup(smp->filename, 12); file->description = "Scream Tracker Sample"; file->title = strn_dup(smp->name, 25); file->type = TYPE_SAMPLE_EXTD | TYPE_INST_OTHER; return 1; } int fmt_s3i_load_sample(const uint8_t *data, size_t length, song_sample_t *smp) { // what the crap? return load_s3i_sample(data, length, smp); } schismtracker-20180209/fmt/s3m.c000066400000000000000000000711241323741476300162760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "version.h" #include "sndfile.h" #include "disko.h" #include "log.h" /* --------------------------------------------------------------------- */ int fmt_s3m_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 48 && memcmp(data + 44, "SCRM", 4) == 0)) return 0; file->description = "Scream Tracker 3"; /*file->extension = str_dup("s3m");*/ file->title = strn_dup((const char *)data, 27); file->type = TYPE_MODULE_S3M; return 1; } /* --------------------------------------------------------------------------------------------------------- */ enum { S3I_TYPE_NONE = 0, S3I_TYPE_PCM = 1, S3I_TYPE_ADMEL = 2, S3I_TYPE_CONTROL = 0xff, // only internally used for saving }; /* misc flags for loader (internal) */ #define S3M_UNSIGNED 1 #define S3M_CHANPAN 2 // the FC byte int fmt_s3m_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint16_t nsmp, nord, npat; int misc = S3M_UNSIGNED | S3M_CHANPAN; // temporary flags, these are both generally true int n; song_note_t *note; /* junk variables for reading stuff into */ uint16_t tmp; uint8_t c; uint32_t tmplong; uint8_t b[4]; /* parapointers */ uint16_t para_smp[MAX_SAMPLES]; uint16_t para_pat[MAX_PATTERNS]; uint32_t para_sdata[MAX_SAMPLES] = { 0 }; uint32_t smp_flags[MAX_SAMPLES] = { 0 }; song_sample_t *sample; uint16_t trkvers; uint16_t flags; uint16_t special; uint32_t adlib = 0; // bitset int uc; const char *tid = NULL; /* check the tag */ slurp_seek(fp, 44, SEEK_SET); slurp_read(fp, b, 4); if (memcmp(b, "SCRM", 4) != 0) return LOAD_UNSUPPORTED; /* read the title */ slurp_rewind(fp); slurp_read(fp, song->title, 25); song->title[25] = 0; /* skip the last three bytes of the title, the supposed-to-be-0x1a byte, the tracker ID, and the two useless reserved bytes */ slurp_seek(fp, 7, SEEK_CUR); slurp_read(fp, &nord, 2); slurp_read(fp, &nsmp, 2); slurp_read(fp, &npat, 2); nord = bswapLE16(nord); nsmp = bswapLE16(nsmp); npat = bswapLE16(npat); if (nord > MAX_ORDERS || nsmp > MAX_SAMPLES || npat > MAX_PATTERNS) return LOAD_FORMAT_ERROR; song->flags = SONG_ITOLDEFFECTS; slurp_read(fp, &flags, 2); /* flags (don't really care) */ flags = bswapLE16(flags); slurp_read(fp, &trkvers, 2); trkvers = bswapLE16(trkvers); slurp_read(fp, &tmp, 2); /* file format info */ if (tmp == bswapLE16(1)) misc &= ~S3M_UNSIGNED; /* signed samples (ancient s3m) */ slurp_seek(fp, 4, SEEK_CUR); /* skip the tag */ song->initial_global_volume = slurp_getc(fp) << 1; // In the case of invalid data, ST3 uses the speed/tempo value that's set in the player prior to // loading the song, but that's just crazy. song->initial_speed = slurp_getc(fp) ?: 6; song->initial_tempo = slurp_getc(fp); if (song->initial_tempo <= 32) { // (Yes, 32 is ignored by Scream Tracker.) song->initial_tempo = 125; } song->mixing_volume = slurp_getc(fp); if (song->mixing_volume & 0x80) { song->mixing_volume ^= 0x80; } else { song->flags |= SONG_NOSTEREO; } uc = slurp_getc(fp); /* ultraclick removal (useless) */ if (slurp_getc(fp) != 0xfc) misc &= ~S3M_CHANPAN; /* stored pan values */ /* Interesting: Impulse Tracker appears to leave some junk data in this unused section, and what's more, it always seems to follow the same general pattern. So it's actually possible to identify whether a song was saved in IT, then loaded and re-saved in ST3. */ slurp_seek(fp, 8, SEEK_CUR); slurp_read(fp, &special, 2); // field not used by st3 special = bswapLE16(special); /* channel settings */ for (n = 0; n < 32; n++) { /* Channel 'type': 0xFF is a disabled channel, which shows up as (--) in ST3. Any channel with the high bit set is muted. 00-07 are L1-L8, 08-0F are R1-R8, 10-18 are adlib channels A1-A9. Hacking at a file with a hex editor shows some perhaps partially-implemented stuff: types 19-1D show up in ST3 as AB, AS, AT, AC, and AH; 20-2D are the same as 10-1D except with 'B' insted of 'A'. None of these appear to produce any sound output, apart from 19 which plays adlib instruments briefly before cutting them. (Weird!) Also, 1E/1F and 2E/2F display as "??"; and pressing 'A' on a disabled (--) channel will change its type to 1F. Values past 2F seem to display bits of the UI like the copyright and help, strange! These out-of-range channel types will almost certainly hang or crash ST3 or produce other strange behavior. Simply put, don't do it. :) */ c = slurp_getc(fp); if (c & 0x80) { song->channels[n].flags |= CHN_MUTE; // ST3 doesn't even play effects in muted channels -- throw them out? c &= ~0x80; } if (c < 0x08) { // L1-L8 (panned to 3 in ST3) song->channels[n].panning = 14; } else if (c < 0x10) { // R1-R8 (panned to C in ST3) song->channels[n].panning = 50; } else if (c < 0x19) { // A1-A9 song->channels[n].panning = 32; adlib |= 1 << n; } else { // Disabled 0xff/0x7f, or broken song->channels[n].panning = 32; song->channels[n].flags |= CHN_MUTE; } song->channels[n].volume = 64; } for (; n < 64; n++) { song->channels[n].panning = 32; song->channels[n].volume = 64; song->channels[n].flags = CHN_MUTE; } /* orderlist */ slurp_read(fp, song->orderlist, nord); memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord); /* load the parapointers */ slurp_read(fp, para_smp, 2 * nsmp); slurp_read(fp, para_pat, 2 * npat); /* default pannings */ if (misc & S3M_CHANPAN) { for (n = 0; n < 32; n++) { c = slurp_getc(fp); if (c & 0x20) song->channels[n].panning = ((c & 0xf) << 2) + 2; } } //mphack - fix the pannings for (n = 0; n < 64; n++) song->channels[n].panning *= 4; /* samples */ for (n = 0, sample = song->samples + 1; n < nsmp; n++, sample++) { uint8_t type; slurp_seek(fp, (para_smp[n]) << 4, SEEK_SET); type = slurp_getc(fp); slurp_read(fp, sample->filename, 12); sample->filename[12] = 0; slurp_read(fp, b, 3); // data pointer for pcm, irrelevant otherwise switch (type) { case S3I_TYPE_PCM: para_sdata[n] = b[1] | (b[2] << 8) | (b[0] << 16); slurp_read(fp, &tmplong, 4); sample->length = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_start = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_end = bswapLE32(tmplong); sample->volume = slurp_getc(fp) * 4; //mphack slurp_getc(fp); /* unused byte */ slurp_getc(fp); /* packing info (never used) */ c = slurp_getc(fp); /* flags */ if (c & 1) sample->flags |= CHN_LOOP; smp_flags[n] = (SF_LE | ((misc & S3M_UNSIGNED) ? SF_PCMU : SF_PCMS) | ((c & 4) ? SF_16 : SF_8) | ((c & 2) ? SF_SS : SF_M)); break; default: //printf("s3m: mystery-meat sample type %d\n", type); case S3I_TYPE_NONE: slurp_seek(fp, 12, SEEK_CUR); sample->volume = slurp_getc(fp) * 4; //mphack slurp_seek(fp, 3, SEEK_CUR); break; case S3I_TYPE_ADMEL: slurp_read(fp, sample->adlib_bytes, 12); sample->volume = slurp_getc(fp) * 4; //mphack // next byte is "dsk", what is that? slurp_seek(fp, 3, SEEK_CUR); sample->flags |= CHN_ADLIB; // dumb hackaround that ought to some day be fixed: sample->length = 1; sample->data = csf_allocate_sample(1); break; } slurp_read(fp, &tmplong, 4); sample->c5speed = bswapLE32(tmplong); if (type == S3I_TYPE_ADMEL) { if (sample->c5speed < 1000 || sample->c5speed > 0xFFFF) { sample->c5speed = 8363; } } slurp_seek(fp, 12, SEEK_CUR); /* wasted space */ slurp_read(fp, sample->name, 25); sample->name[25] = 0; sample->vib_type = 0; sample->vib_rate = 0; sample->vib_depth = 0; sample->vib_speed = 0; sample->global_volume = 64; } /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (n = 0, sample = song->samples + 1; n < nsmp; n++, sample++) { if (!sample->length || (sample->flags & CHN_ADLIB)) continue; slurp_seek(fp, para_sdata[n] << 4, SEEK_SET); csf_read_sample(sample, smp_flags[n], fp->data + fp->pos, fp->length - fp->pos); } } if (!(lflags & LOAD_NOPATTERNS)) { for (n = 0; n < npat; n++) { int row = 0; long end; para_pat[n] = bswapLE16(para_pat[n]); if (!para_pat[n]) continue; slurp_seek(fp, para_pat[n] << 4, SEEK_SET); slurp_read(fp, &tmp, 2); end = (para_pat[n] << 4) + bswapLE16(tmp) + 2; song->patterns[n] = csf_allocate_pattern(64); while (row < 64 && slurp_tell(fp) < end) { int mask = slurp_getc(fp); uint8_t chn = (mask & 31); if (mask == EOF) { log_appendf(4, " Warning: Pattern %d: file truncated", n); break; } if (!mask) { /* done with the row */ row++; continue; } note = song->patterns[n] + 64 * row + chn; if (mask & 32) { /* note/instrument */ note->note = slurp_getc(fp); note->instrument = slurp_getc(fp); //if (note->instrument > 99) // note->instrument = 0; switch (note->note) { default: // Note; hi=oct, lo=note note->note = (note->note >> 4) * 12 + (note->note & 0xf) + 13; break; case 255: note->note = NOTE_NONE; break; case 254: note->note = (adlib & (1 << chn)) ? NOTE_OFF : NOTE_CUT; break; } } if (mask & 64) { /* volume */ note->voleffect = VOLFX_VOLUME; note->volparam = slurp_getc(fp); if (note->volparam == 255) { note->voleffect = VOLFX_NONE; note->volparam = 0; } else if (note->volparam > 64) { // some weirdly saved s3m? note->volparam = 64; } } if (mask & 128) { note->effect = slurp_getc(fp); note->param = slurp_getc(fp); csf_import_s3m_effect(note, 0); if (note->effect == FX_SPECIAL) { // mimic ST3's SD0/SC0 behavior if (note->param == 0xd0) { note->note = NOTE_NONE; note->instrument = 0; note->voleffect = VOLFX_NONE; note->volparam = 0; note->effect = FX_NONE; note->param = 0; } else if (note->param == 0xc0) { note->effect = FX_NONE; note->param = 0; } } } /* ... next note, same row */ } } } /* MPT identifies as ST3.20 in the trkvers field, but it puts zeroes for the 'special' field, only ever * sets flags 0x10 and 0x40, writes multiples of 16 orders, always saves channel pannings, and writes * zero into the ultraclick removal field. (ST3 always puts either 8, 12, or 16 there). * Velvet Studio also pretends to be ST3, but writes zeroes for 'special'. ultraclick, and flags, and * does NOT save channel pannings. Also, it writes a fairly recognizable LRRL pattern for the channels, * but I'm not checking that. (yet?) */ if (trkvers == 0x1320) { if (special == 0 && uc == 0 && (flags & ~0x50) == 0 && misc == (S3M_UNSIGNED | S3M_CHANPAN) && (nord % 16) == 0) { tid = "Modplug Tracker"; } else if (special == 0 && uc == 0 && flags == 0 && misc == (S3M_UNSIGNED)) { tid = "Velvet Studio"; } else if (uc != 8 && uc != 12 && uc != 16) { // sure isn't scream tracker tid = "Unknown tracker"; } } if (!tid) { switch (trkvers >> 12) { case 1: tid = "Scream Tracker %d.%02x"; break; case 2: tid = "Imago Orpheus %d.%02x"; break; case 3: if (trkvers <= 0x3214) { tid = "Impulse Tracker %d.%02x"; } else { tid = NULL; sprintf(song->tracker_id, "Impulse Tracker 2.14p%d", trkvers - 0x3214); } break; case 4: tid = NULL; strcpy(song->tracker_id, "Schism Tracker "); ver_decode_cwtv(trkvers, song->tracker_id + strlen(song->tracker_id)); break; case 5: tid = "OpenMPT %d.%02x"; break; } } if (tid) sprintf(song->tracker_id, tid, (trkvers & 0xf00) >> 8, trkvers & 0xff); // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } /* done! */ return LOAD_SUCCESS; } /* --------------------------------------------------------------------------------------------------------- */ /* IT displays some of these slightly differently most notably "Only 100 patterns supported" which doesn't follow the general pattern, and the channel limits (IT entirely refuses to save data in channels > 16 at all). Also, the Adlib and sample count warnings of course do not exist in IT at all. */ enum { WARN_MAXPATTERNS, WARN_CHANNELVOL, WARN_LINEARSLIDES, WARN_SAMPLEVOL, WARN_LOOPS, WARN_SAMPLEVIB, WARN_INSTRUMENTS, WARN_PATTERNLEN, WARN_MAXCHANNELS, WARN_MAXPCM, WARN_MAXADLIB, WARN_PCMADLIBMIX, WARN_MUTED, WARN_NOTERANGE, WARN_VOLEFFECTS, WARN_MAXSAMPLES, MAX_WARN }; static const char *s3m_warnings[] = { [WARN_MAXPATTERNS] = "Over 100 patterns", [WARN_CHANNELVOL] = "Channel volumes", [WARN_LINEARSLIDES] = "Linear slides", [WARN_SAMPLEVOL] = "Sample volumes", [WARN_LOOPS] = "Sustain and Ping Pong loops", [WARN_SAMPLEVIB] = "Sample vibrato", [WARN_INSTRUMENTS] = "Instrument functions", [WARN_PATTERNLEN] = "Pattern lengths other than 64 rows", [WARN_MAXCHANNELS] = "Data outside 32 channels", [WARN_MAXPCM] = "Over 16 PCM channels", [WARN_MAXADLIB] = "Over 9 Adlib channels", [WARN_PCMADLIBMIX] = "Adlib and PCM in the same channel", [WARN_MUTED] = "Data in muted channels", [WARN_NOTERANGE] = "Notes outside the range C-1 to B-8", [WARN_VOLEFFECTS] = "Extended volume column effects", [WARN_MAXSAMPLES] = "Over 99 samples", [MAX_WARN] = NULL }; #pragma pack(push, 1) struct s3m_header { char title[28]; char eof; // 0x1a char type; // 16 uint8_t x[2]; // junk uint16_t ordnum, smpnum, patnum; // ordnum should be even uint16_t flags, cwtv, ffi; // 0, 0x4nnn, 2 for unsigned char scrm[4]; // "SCRM" uint8_t gv, is, it, mv, uc, dp; // gv is half range of IT, uc should be 8/12/16, dp is 252 uint8_t junk[10]; // last 2 bytes are "special", which means "more junk" }; struct s3i_header { uint8_t type; char filename[12]; union { struct { uint8_t memseg[3]; uint32_t length; uint32_t loop_start; uint32_t loop_end; } pcm; struct { uint8_t zero[3]; uint8_t data[12]; } admel; }; uint8_t vol; uint8_t x; // "dsk" for adlib uint8_t pack; // 0 uint8_t flags; // 1=loop 2=stereo 4=16-bit / zero for adlib uint32_t c5speed; uint8_t junk[12]; char name[28]; char tag[4]; // SCRS/SCRI/whatever }; #pragma pack(pop) #define SEEK_ALIGN(fp) disko_seek((fp), (16 - (disko_tell(fp) & 15)) & 15, SEEK_CUR) static void write_s3i_header(disko_t *fp, song_sample_t *smp, uint32_t sdata) { struct s3i_header hdr = {}; int n; if (smp->flags & CHN_ADLIB) { hdr.type = S3I_TYPE_ADMEL; memcpy(hdr.admel.data, smp->adlib_bytes, 11); memcpy(hdr.tag, "SCRI", 4); } else if (smp->data != NULL) { hdr.type = S3I_TYPE_PCM; hdr.pcm.memseg[0] = (sdata >> 20) & 0xff; hdr.pcm.memseg[1] = (sdata >> 4) & 0xff; hdr.pcm.memseg[2] = (sdata >> 12) & 0xff; hdr.pcm.length = bswapLE32(smp->length); hdr.pcm.loop_start = bswapLE32(smp->loop_start); hdr.pcm.loop_end = bswapLE32(smp->loop_end); hdr.flags = ((smp->flags & CHN_LOOP) ? 1 : 0) | ((smp->flags & CHN_STEREO) ? 2 : 0) | ((smp->flags & CHN_16BIT) ? 4 : 0); memcpy(hdr.tag, "SCRS", 4); } else { hdr.type = S3I_TYPE_NONE; } memcpy(hdr.filename, smp->filename, 12); hdr.vol = smp->volume / 4; //mphack hdr.c5speed = bswapLE32(smp->c5speed); for (n = 25; n >= 0; n--) if ((smp->name[n] ?: 32) != 32) break; for (; n >= 0; n--) hdr.name[n] = smp->name[n] ?: 32; disko_write(fp, &hdr, sizeof(hdr)); } static int write_s3m_pattern(disko_t *fp, song_t *song, int pat, uint8_t *chantypes, uint16_t *para_pat) { long start, end; uint8_t b, type; uint16_t w; int row, rows, chan; song_note_t out, *note; int warn = 0; if (csf_pattern_is_empty(song, pat)) { // easy! para_pat[pat] = 0; return 0; } if (song->pattern_size[pat] != 64) { warn |= 1 << WARN_PATTERNLEN; } rows = MIN(64, song->pattern_size[pat]); SEEK_ALIGN(fp); start = disko_tell(fp); para_pat[pat] = bswapLE16(start >> 4); // write a bogus length for now... disko_putc(fp, 0); disko_putc(fp, 0); note = song->patterns[pat]; for (row = 0; row < rows; row++) { for (chan = 0; chan < 32; chan++, note++) { out = *note; b = 0; if (song->channels[chan].flags & CHN_MUTE) { if (out.instrument || out.effect) { /* most players do in fact play data on muted channels, but that's wrong since ST3 doesn't. to eschew the problem, we'll just drop the data when writing (and complain) */ warn |= 1 << WARN_MUTED; continue; } } else if ((song->flags & SONG_INSTRUMENTMODE) && out.instrument && NOTE_IS_NOTE(out.note)) { song_instrument_t *ins = song->instruments[out.instrument]; if (ins) { out.instrument = ins->sample_map[out.note - 1]; out.note = ins->note_map[out.note - 1]; } } switch (out.note) { case 1 ... 12: case 109 ... 120: // Octave 0/9 (or higher?) warn |= 1 << WARN_NOTERANGE; out.note = 255; break; case 13 ... 108: // C-1 through B-8 out.note -= 13; out.note = (out.note % 12) + ((out.note / 12) << 4); b |= 32; break; case NOTE_CUT: case NOTE_OFF: // IT translates === to ^^^ when writing S3M files // (and more importantly, we load ^^^ as === in adlib-channels) out.note = 254; b |= 32; break; default: // Nothing (or garbage values) out.note = 255; break; } if (out.instrument != 0) { if (song->samples[out.instrument].flags & CHN_ADLIB) type = S3I_TYPE_ADMEL; else if (song->samples[out.instrument].data != NULL) type = S3I_TYPE_PCM; else type = S3I_TYPE_NONE; if (type != S3I_TYPE_NONE) { if (chantypes[chan] == S3I_TYPE_NONE || chantypes[chan] == S3I_TYPE_CONTROL) { chantypes[chan] = type; } else if (chantypes[chan] != type) { warn |= 1 << WARN_PCMADLIBMIX; } } b |= 32; } switch (out.voleffect) { case VOLFX_NONE: break; case VOLFX_VOLUME: b |= 64; break; default: warn |= 1 << WARN_VOLEFFECTS; break; } csf_export_s3m_effect(&out.effect, &out.param, 0); if (out.effect || out.param) { b |= 128; } // If there's an effect, don't allow the channel to be muted in the S3M file. // S3I_TYPE_CONTROL is an internal value indicating that the channel should get a // "junk" value (such as B1) that doesn't actually play. if (chantypes[chan] == S3I_TYPE_NONE && out.effect) { chantypes[chan] = S3I_TYPE_CONTROL; } if (!b) continue; b |= chan; // write it! disko_putc(fp, b); if (b & 32) { disko_putc(fp, out.note); disko_putc(fp, out.instrument); } if (b & 64) { disko_putc(fp, out.volparam); } if (b & 128) { disko_putc(fp, out.effect); disko_putc(fp, out.param); } } if (!(warn & (1 << WARN_MAXCHANNELS))) { /* if the flag is already set, there's no point in continuing to search for stuff */ for (; chan < MAX_CHANNELS; chan++, note++) { if (!csf_note_is_empty(note)) { warn |= 1 << WARN_MAXCHANNELS; break; } } } note += MAX_CHANNELS - chan; disko_putc(fp, 0); /* end of row */ } /* if the pattern was < 64 rows, pad it */ for (; row < 64; row++) { disko_putc(fp, 0); } /* hop back and write the real length */ end = disko_tell(fp); disko_seek(fp, start, SEEK_SET); w = bswapLE16(end - start); disko_write(fp, &w, 2); disko_seek(fp, end, SEEK_SET); return warn; } static int fixup_chantypes(song_channel_t *channels, uint8_t *chantypes) { int warn = 0; int npcm = 0, nadmel = 0, nctrl = 0; int pcm = 0, admel = 0x10, junk = 0x20; int n; /* Value Label Value Label (20-2F => 10-1F with B instead of A) 00 L1 10 A1 01 L2 11 A2 02 L3 12 A3 03 L4 13 A4 04 L5 14 A5 05 L6 15 A6 06 L7 16 A7 07 L8 17 A8 08 R1 18 A9 09 R2 19 AB 0A R3 1A AS 0B R4 1B AT 0C R5 1C AC 0D R6 1D AH 0E R7 1E ?? 0F R8 1F ?? For the L1 R1 L2 R2 pattern: ((n << 3) | (n >> 1)) & 0xf PCM * 16 = 00-0F Adlib * 9 = 10-18 Remaining = 20-2F (nothing will be played, but effects are still processed) Try to make as many of the "control" channels PCM as possible. */ for (n = 0; n < 32; n++) { switch (chantypes[n]) { case S3I_TYPE_PCM: npcm++; break; case S3I_TYPE_ADMEL: nadmel++; break; case S3I_TYPE_CONTROL: nctrl++; break; } } if (npcm > 16) { npcm = 16; warn |= 1 << WARN_MAXPCM; } if (nadmel > 9) { nadmel = 9; warn |= 1 << WARN_MAXADLIB; } for (n = 0; n < 32; n++) { switch (chantypes[n]) { case S3I_TYPE_PCM: if (pcm <= 0x0f) chantypes[n] = pcm++; else chantypes[n] = junk++; break; case S3I_TYPE_ADMEL: if (admel <= 0x18) chantypes[n] = admel++; else chantypes[n] = junk++; break; case S3I_TYPE_NONE: if (channels[n].flags & CHN_MUTE) { chantypes[n] = 255; // (--) break; } // else fall through - attempt to honor unmuted channels. default: if (npcm < 16) { chantypes[n] = ((pcm << 3) | (pcm >> 1)) & 0xf; pcm++; npcm++; } else if (nadmel < 9) { chantypes[n] = admel++; nadmel++; } else if (chantypes[n] == S3I_TYPE_NONE) { chantypes[n] = 255; // (--) } else { chantypes[n] = junk++; // give up } break; } if (junk > 0x2f) junk = 0x19; // "overflow" to the adlib drums } return warn; } int fmt_s3m_save_song(disko_t *fp, song_t *song) { struct s3m_header hdr = {}; int nord, nsmp, npat; int n; song_sample_t *smp; long smphead_pos; /* where to write the sample headers */ long patptr_pos; /* where to write pattern pointers */ long pos; /* temp */ uint16_t w; uint16_t para_pat[MAX_PATTERNS]; uint32_t para_sdata[MAX_SAMPLES]; uint8_t chantypes[32]; unsigned int warn = 0; if (song->flags & SONG_INSTRUMENTMODE) warn |= 1 << WARN_INSTRUMENTS; if (song->flags & SONG_LINEARSLIDES) warn |= 1 << WARN_LINEARSLIDES; nord = csf_get_num_orders(song) + 1; // TECH.DOC says orders should be even. In practice it doesn't appear to matter (in fact IT doesn't // make the number even), but if the spec says... if (nord & 1) nord++; // see note in IT writer -- shouldn't clamp here, but can't save more than we're willing to load nord = CLAMP(nord, 2, MAX_ORDERS); nsmp = csf_get_num_samples(song) ?: 1; // ST3 always saves one sample if (nsmp > 99) { nsmp = 99; warn |= 1 << WARN_MAXSAMPLES; } npat = csf_get_num_patterns(song) ?: 1; // ST3 always saves one pattern if (npat > 100) { npat = 100; warn |= 1 << WARN_MAXPATTERNS; } log_appendf(5, " %d orders, %d samples, %d patterns", nord, nsmp, npat); /* this is used to identify what kinds of samples (pcm or adlib) are used on which channels, since it actually matters to st3 */ memset(chantypes, S3I_TYPE_NONE, 32); memcpy(hdr.title, song->title, 25); hdr.eof = 0x1a; hdr.type = 16; // ST3 module (what else is there?!) hdr.ordnum = bswapLE16(nord); hdr.smpnum = bswapLE16(nsmp); hdr.patnum = bswapLE16(npat); hdr.flags = 0; hdr.cwtv = bswapLE16(0x4000 | ver_cwtv); hdr.ffi = bswapLE16(2); // format version; 1 = signed samples, 2 = unsigned memcpy(hdr.scrm, "SCRM", 4); hdr.gv = song->initial_global_volume / 2; hdr.is = song->initial_speed; hdr.it = song->initial_tempo; hdr.mv = song->mixing_volume; if (!(song->flags & SONG_NOSTEREO)) hdr.mv |= 128; hdr.uc = 16; // ultraclick (the "Waste GUS channels" option) hdr.dp = 252; /* The sample data parapointers are 24+4 bits, whereas pattern data and sample headers are only 16+4 bits -- so while the sample data can be written up to 268 MB within the file (starting at 0xffffff0), the pattern data and sample headers are restricted to the first 1 MB (starting at 0xffff0). In effect, this practically requires the sample data to be written last in the file, as it is entirely possible (and quite easy, even) to write more than 1 MB of sample data in a file. The "practical standard order" listed in TECH.DOC is sample headers, patterns, then sample data. Thus: File header Channel settings Orderlist Sample header pointers Pattern pointers Default pannings Sample headers Pattern data Sample data */ disko_write(fp, &hdr, sizeof(hdr)); // header disko_seek(fp, 32, SEEK_CUR); // channel settings (skipped for now) disko_write(fp, song->orderlist, nord); // orderlist /* sample header pointers because the sample headers are fixed-size, it's possible to determine where they will be written right now: the first sample will be at the start of the next 16-byte block after all the header stuff, and each subsequent sample starts 0x50 bytes after the previous one. */ pos = smphead_pos = (0x60 + nord + 2 * (nsmp + npat) + 32 + 15) & ~15; for (n = 0; n < nsmp; n++) { w = bswapLE16(pos >> 4); disko_write(fp, &w, 2); pos += 0x50; } /* pattern pointers can't figure these out ahead of time since the patterns are variable length, but do make a note of where to seek later in order to write the values... */ patptr_pos = disko_tell(fp); disko_seek(fp, 2 * npat, SEEK_CUR); /* channel pannings ... also not yet! */ disko_seek(fp, 32, SEEK_CUR); /* skip ahead past the sample headers as well (what a pain) */ disko_seek(fp, 0x50 * nsmp, SEEK_CUR); /* patterns -- finally omg we can write some data */ for (n = 0; n < npat; n++) warn |= write_s3m_pattern(fp, song, n, chantypes, para_pat); /* sample data */ for (n = 0, smp = song->samples + 1; n < nsmp; n++, smp++) { if ((smp->flags & CHN_ADLIB) || smp->data == NULL) { para_sdata[n] = 0; continue; } SEEK_ALIGN(fp); para_sdata[n] = disko_tell(fp); csf_write_sample(fp, smp, SF_LE | SF_PCMU | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M)); } /* now that we're done adding stuff to the end of the file, go back and rewrite everything we skipped earlier.... */ // channel types warn |= fixup_chantypes(song->channels, chantypes); disko_seek(fp, 0x40, SEEK_SET); disko_write(fp, chantypes, 32); // pattern pointers disko_seek(fp, patptr_pos, SEEK_SET); disko_write(fp, para_pat, 2 * npat); /* channel panning settings come after the pattern pointers... This produces somewhat left-biased panning values, but this is what IT does, and more importantly it's stable across repeated load/saves. (Hopefully.) Technically it is possible to squeeze out two "extra" values for hard-left and hard-right panning by writing a "disabled" pan value (omit the 0x20 bit, so it's presented as a dot in ST3) -- but some trackers, including MPT and older Schism Tracker versions, load such values as 16/48 rather than 0/64, so this would result in potentially inconsistent behavior and is therefore undesirable. */ for (n = 0; n < 32; n++) { song_channel_t *ch = song->channels + n; uint8_t b; if (ch->volume != 64) warn |= 1 << WARN_CHANNELVOL; //mphack: channel panning range b = ((chantypes[n] & 0x7f) < 0x20) ? (0x20 | (((MAX((ch->panning / 4), 2) - 2) >> 2) & 0xf)) : 0; disko_putc(fp, b); } /* sample headers */ disko_seek(fp, smphead_pos, SEEK_SET); for (n = 0, smp = song->samples + 1; n < nsmp; n++, smp++) { if (smp->global_volume != 64) { warn |= 1 << WARN_SAMPLEVOL; } if ((smp->flags & (CHN_LOOP | CHN_PINGPONGLOOP)) == (CHN_LOOP | CHN_PINGPONGLOOP) || (smp->flags & CHN_SUSTAINLOOP)) { warn |= 1 << WARN_LOOPS; } if (smp->vib_depth != 0) { warn |= 1 << WARN_SAMPLEVIB; } write_s3i_header(fp, smp, para_sdata[n]); } /* announce all the things we broke */ for (n = 0; n < MAX_WARN; n++) { if (warn & (1 << n)) log_appendf(4, " Warning: %s unsupported in S3M format", s3m_warnings[n]); } return SAVE_SUCCESS; } schismtracker-20180209/fmt/sfx.c000066400000000000000000000212511323741476300163700ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "log.h" #include "sndfile.h" /* --------------------------------------------------------------------------------------------------------- */ /* None of the sfx files on Modland are of the 31-instrument type that xmp recognizes. However, there are a number of 31-instrument files with a different tag, under "SoundFX 2". */ static struct sfxfmt { size_t tagpos; const char tag[4]; int nsmp; int dunno; const char *id; } sfxfmts[] = { {124, "SO31", 31, 4, "SoundFX 2"}, {124, "SONG", 31, 0, "SoundFX 2 (?)"}, { 60, "SONG", 15, 0, "SoundFX"}, { 0, "" , 0, 0, NULL}, }; int fmt_sfx_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { int n; for (n = 0; sfxfmts[n].nsmp; n++) { if (length >= sfxfmts[n].tagpos + 4 && memcmp(data + sfxfmts[n].tagpos, sfxfmts[n].tag, 4) == 0) { file->description = sfxfmts[n].id; /*file->extension = str_dup("sfx");*/ file->title = strdup(""); // whatever file->type = TYPE_MODULE_MOD; return 1; } } return 0; } /* --------------------------------------------------------------------------------------------------------- */ /* Loader taken mostly from XMP. Why did I write a loader for such an obscure format? That is, besides the fact that neither Modplug nor Mikmod support SFX (and for good reason; it's a particularly dumb format) */ int fmt_sfx_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t tag[4]; int n, nord, npat, pat, chan, restart, nsmp = 0; uint32_t smpsize[31]; uint16_t tmp; song_note_t *note; song_sample_t *sample; unsigned int effwarn = 0; struct sfxfmt *fmt = sfxfmts; do { slurp_seek(fp, fmt->tagpos, SEEK_SET); slurp_read(fp, tag, 4); if (memcmp(tag, fmt->tag, 4) == 0) { nsmp = fmt->nsmp; break; } fmt++; } while (fmt->nsmp); if (!nsmp) return LOAD_UNSUPPORTED; slurp_rewind(fp); slurp_read(fp, smpsize, 4 * nsmp); slurp_seek(fp, 4, SEEK_CUR); /* the tag again */ slurp_read(fp, &tmp, 2); if (!tmp) return LOAD_UNSUPPORTED; // erf tmp = 14565 * 122 / bswapBE16(tmp); song->initial_tempo = CLAMP(tmp, 31, 255); slurp_seek(fp, 14, SEEK_CUR); /* unknown bytes (reserved?) - see below */ if (lflags & LOAD_NOSAMPLES) { slurp_seek(fp, 30 * nsmp, SEEK_CUR); } else { for (n = 0, sample = song->samples + 1; n < nsmp; n++, sample++) { slurp_read(fp, sample->name, 22); sample->name[22] = 0; slurp_read(fp, &tmp, 2); /* seems to be half the sample size, minus two bytes? */ tmp = bswapBE16(tmp); sample->length = bswapBE32(smpsize[n]); song->samples[n].c5speed = MOD_FINETUNE(slurp_getc(fp)); // ? sample->volume = slurp_getc(fp); if (sample->volume > 64) sample->volume = 64; sample->volume *= 4; //mphack sample->global_volume = 64; slurp_read(fp, &tmp, 2); sample->loop_start = bswapBE16(tmp); slurp_read(fp, &tmp, 2); tmp = bswapBE16(tmp) * 2; /* loop length */ if (tmp > 2) { sample->loop_end = sample->loop_start + tmp; sample->flags |= CHN_LOOP; } else { sample->loop_start = sample->loop_end = 0; } } } /* pattern/order stuff */ nord = slurp_getc(fp); nord = MIN(nord, 127); restart = slurp_getc(fp); slurp_read(fp, song->orderlist, nord); slurp_seek(fp, 128 - nord, SEEK_CUR); npat = 0; for (n = 0; n < nord; n++) { if (song->orderlist[n] > npat) npat = song->orderlist[n]; } npat++; /* Not sure what this is about, but skipping a few bytes here seems to make SO31's load right. (they all seem to have zero here) */ slurp_seek(fp, fmt->dunno, SEEK_CUR); if (lflags & LOAD_NOPATTERNS) { slurp_seek(fp, npat * 1024, SEEK_CUR); } else { for (pat = 0; pat < npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (n = 0; n < 64; n++, note += 60) { for (chan = 0; chan < 4; chan++, note++) { uint8_t p[4]; slurp_read(fp, p, 4); mod_import_note(p, note); /* Note events starting with FF all seem to be special in some way: bytes apparent use example file on modland ----- ------------ ----------------------- FF FE note cut 1st intro.sfx FF FD unknown! another world (intro).sfx FF FC pattern break orbit wanderer.sfx2 */ if (p[0] == 0xff) { switch (p[1]) { case 0xfc: note->note = NOTE_NONE; note->instrument = 0; // stuff a C00 in channel 5 note[4 - chan].effect = FX_PATTERNBREAK; break; case 0xfe: note->note = NOTE_CUT; note->instrument = 0; break; } } switch (note->effect) { case 0: break; case 1: /* arpeggio */ note->effect = FX_ARPEGGIO; break; case 2: /* pitch bend */ if (note->param >> 4) { note->effect = FX_PORTAMENTODOWN; note->param >>= 4; } else if (note->param & 0xf) { note->effect = FX_PORTAMENTOUP; note->param &= 0xf; } else { note->effect = 0; } break; case 5: /* volume up */ note->effect = FX_VOLUMESLIDE; note->param = (note->param & 0xf) << 4; break; case 6: /* set volume */ if (note->param > 64) note->param = 64; note->voleffect = VOLFX_VOLUME; note->volparam = 64 - note->param; note->effect = 0; note->param = 0; break; case 3: /* LED on (wtf!) */ case 4: /* LED off (ditto) */ case 7: /* set step up */ case 8: /* set step down */ default: effwarn |= (1 << note->effect); note->effect = FX_UNIMPLEMENTED; break; } } } } for (n = 0; n < 16; n++) { if (effwarn & (1 << n)) log_appendf(4, " Warning: Unimplemented effect %Xxx", n); } if (restart < npat) csf_insert_restart_pos(song, restart); } /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (n = 0, sample = song->samples + 1; n < fmt->nsmp; n++, sample++) { uint32_t ssize; if (sample->length <= 2) continue; ssize = csf_read_sample(sample, SF_8 | SF_LE | SF_PCMS | SF_M, fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, ssize, SEEK_CUR); } } /* more header info */ song->flags = SONG_ITOLDEFFECTS | SONG_COMPATGXX; for (n = 0; n < 4; n++) song->channels[n].panning = PROTRACKER_PANNING(n); /* ??? */ for (; n < MAX_CHANNELS; n++) song->channels[n].flags = CHN_MUTE; strcpy(song->tracker_id, fmt->id); song->pan_separation = 64; // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } /* done! */ return LOAD_SUCCESS; } /* most of modland's sfx files have all zeroes for those 14 "unknown" bytes, with the following exceptions: 64 00 00 00 00 00 00 00 00 00 00 00 00 00 d............. - unknown/antitrax.sfx 74 63 68 33 00 00 00 00 00 00 00 00 00 00 tch3.......... - unknown/axel f.sfx 61 6c 6b 00 00 00 00 00 00 00 00 00 00 00 alk........... Andreas Hommel/cyberblast-intro.sfx 21 00 00 00 00 00 00 00 00 00 00 00 00 00 !............. - unknown/dugger.sfx 00 00 00 00 00 0d 00 00 00 00 00 00 00 00 .............. Jean Baudlot/future wars - time travellers - dugger (title).sfx 00 00 00 00 00 00 00 00 0d 00 00 00 00 00 .............. Jean Baudlot/future wars - time travellers - escalator.sfx 6d 65 31 34 00 00 00 00 00 00 00 00 00 00 me14.......... - unknown/melodious.sfx 0d 0d 0d 53 46 58 56 31 2e 38 00 00 00 00 ...SFXV1.8.... AM-FM/sunday morning.sfx 61 6c 6b 00 00 00 00 00 00 00 00 00 00 00 alk........... - unknown/sunday morning.sfx 6f 67 00 00 00 00 00 00 00 00 00 00 00 00 og............ Philip Jespersen/supaplex.sfx 6e 74 20 73 6f 6e 67 00 00 00 00 00 00 00 nt song....... - unknown/sweety.sfx 61 6c 6b 00 00 00 00 00 00 00 00 00 00 00 alk........... - unknown/thrust.sfx */ schismtracker-20180209/fmt/stm.c000066400000000000000000000174421323741476300164020ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "sndfile.h" /* --------------------------------------------------------------------- */ /* TODO: get more stm's and test this... one file's not good enough */ int fmt_stm_read_info(dmoz_file_t *file, const uint8_t *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 0; /* 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 = str_dup("stm");*/ file->type = TYPE_MODULE_MOD; file->title = strn_dup((const char *)data, 20); return 1; } /* --------------------------------------------------------------------- */ #pragma pack(push, 1) struct stm_sample { char name[12]; uint8_t zero; uint8_t inst_disk; // lol disks uint16_t reserved; uint16_t length, loop_start, loop_end; uint8_t volume; uint8_t reserved2; uint16_t c5speed; uint32_t morejunk; uint16_t paragraphs; // what? }; #pragma pack(pop) static uint8_t stm_effects[16] = { FX_NONE, // . FX_SPEED, // A FX_POSITIONJUMP, // B FX_PATTERNBREAK, // C FX_VOLUMESLIDE, // D FX_PORTAMENTODOWN, // E FX_PORTAMENTOUP, // F FX_TONEPORTAMENTO, // G FX_VIBRATO, // H FX_TREMOR, // I FX_ARPEGGIO, // J // KLMNO can be entered in the editor but don't do anything }; /* ST2 says at startup: "Remark: the user ID is encoded in over ten places around the file!" I wonder if this is interesting at all. */ static void load_stm_pattern(song_note_t *note, slurp_t *fp) { int row, chan; uint8_t v[4]; for (row = 0; row < 64; row++, note += 64 - 4) { for (chan = 0; chan < 4; chan++, note++) { slurp_read(fp, v, 4); // mostly copied from modplug... if (v[0] < 251) note->note = (v[0] >> 4) * 12 + (v[0] & 0xf) + 37; note->instrument = v[1] >> 3; if (note->instrument > 31) note->instrument = 0; // oops never mind, that was crap note->volparam = (v[1] & 0x7) + ((v[2] & 0xf0) >> 1); if (note->volparam <= 64) note->voleffect = VOLFX_VOLUME; else note->volparam = 0; note->param = v[3]; // easy! note->effect = stm_effects[v[2] & 0xf]; // patch a couple effects up switch (note->effect) { case FX_SPEED: // I don't know how Axx really works, but I do know that this // isn't it. It does all sorts of mindbogglingly screwy things: // 01 - very fast, // 0F - very slow. // 10 - fast again! // I don't get it. note->param >>= 4; break; case FX_PATTERNBREAK: note->param = (note->param & 0xf0) * 10 + (note->param & 0xf); break; case FX_POSITIONJUMP: // This effect is also very weird. // Bxx doesn't appear to cause an immediate break -- it merely // sets the next order for when the pattern ends (either by // playing it all the way through, or via Cxx effect) // I guess I'll "fix" it later... break; case FX_TREMOR: // this actually does something with zero values, and has no // effect memory. which makes SENSE for old-effects tremor, // but ST3 went and screwed it all up by adding an effect // memory and IT followed that, and those are much more popular // than STM so we kind of have to live with this effect being // broken... oh well. not a big loss. break; default: // Anything not listed above is a no-op if there's no value. // (ST2 doesn't have effect memory) if (!note->param) note->effect = FX_NONE; break; } } } } int fmt_stm_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { char id[8]; uint8_t tmp[4]; int npat, n; slurp_seek(fp, 20, SEEK_SET); slurp_read(fp, id, 8); slurp_read(fp, tmp, 4); if (!( // this byte is guaranteed to be 0x1a, always... tmp[0] == 0x1a // from the doc: // 1 - song (contains no samples) // 2 - module (contains samples) // I'm not going to care about "songs". && tmp[1] == 2 // and check the file tag -- but god knows why, it's case insensitive && (strncasecmp(id, "!Scream!", 8) == 0 || strncasecmp(id, "BMOD2STM", 8) == 0) )) { return LOAD_UNSUPPORTED; } // and the next two bytes are the tracker version. // (XXX should this care about BMOD2STM? what is that anyway?) sprintf(song->tracker_id, "Scream Tracker %d.%02x", tmp[2], tmp[3]); slurp_seek(fp, 0, SEEK_SET); slurp_read(fp, song->title, 20); song->title[20] = '\0'; slurp_seek(fp, 12, SEEK_CUR); // skip the tag and stuff song->initial_speed = (slurp_getc(fp) >> 4) ?: 1; npat = slurp_getc(fp); song->initial_global_volume = 2 * slurp_getc(fp); slurp_seek(fp, 13, SEEK_CUR); // junk if (npat > 64) return LOAD_FORMAT_ERROR; for (n = 1; n <= 31; n++) { struct stm_sample stmsmp; uint16_t blen; song_sample_t *sample = song->samples + n; slurp_read(fp, &stmsmp, sizeof(stmsmp)); // the strncpy here is intentional -- ST2 doesn't show the '3' after the \0 bytes in the first // sample of pm_fract.stm, for example strncpy(sample->filename, stmsmp.name, 12); memcpy(sample->name, sample->filename, 12); blen = sample->length = bswapLE16(stmsmp.length); sample->loop_start = bswapLE16(stmsmp.loop_start); sample->loop_end = bswapLE16(stmsmp.loop_end); sample->c5speed = bswapLE16(stmsmp.c5speed); sample->volume = stmsmp.volume * 4; //mphack if (sample->loop_start < blen && sample->loop_end != 0xffff && sample->loop_start < sample->loop_end) { sample->flags |= CHN_LOOP; sample->loop_end = CLAMP(sample->loop_end, sample->loop_start, blen); } } slurp_read(fp, song->orderlist, 128); for (n = 0; n < 128; n++) { if (song->orderlist[n] >= 64) song->orderlist[n] = ORDER_LAST; } if (lflags & LOAD_NOPATTERNS) { slurp_seek(fp, npat * 64 * 4 * 4, SEEK_CUR); } else { for (n = 0; n < npat; n++) { song->patterns[n] = csf_allocate_pattern(64); song->pattern_size[n] = song->pattern_alloc_size[n] = 64; load_stm_pattern(song->patterns[n], fp); } } if (!(lflags & LOAD_NOSAMPLES)) { for (n = 1; n <= 31; n++) { song_sample_t *sample = song->samples + n; int align = (sample->length + 15) & ~15; if (sample->length < 3) { // Garbage? sample->length = 0; } else { csf_read_sample(sample, SF_LE | SF_PCMS | SF_8 | SF_M, (const char *) (fp->data + fp->pos), sample->length); } slurp_seek(fp, align, SEEK_CUR); } } for (n = 0; n < 4; n++) song->channels[n].panning = ((n & 1) ? 64 : 0) * 4; //mphack for (; n < 64; n++) song->channels[n].flags |= CHN_MUTE; song->pan_separation = 64; song->flags = SONG_ITOLDEFFECTS | SONG_COMPATGXX; return LOAD_SUCCESS; } schismtracker-20180209/fmt/ult.c000066400000000000000000000241521323741476300163770ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "it.h" /* for get_effect_char */ #include "log.h" #include "sndfile.h" #include /* for pow */ /* --------------------------------------------------------------------- */ int fmt_ult_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 48 && memcmp(data, "MAS_UTrack_V00", 14) == 0)) return 0; file->description = "UltraTracker Module"; file->type = TYPE_MODULE_S3M; /*file->extension = str_dup("ult");*/ file->title = strn_dup((const char *)data + 15, 32); return 1; } /* --------------------------------------------------------------------------------------------------------- */ enum { ULT_16BIT = 4, ULT_LOOP = 8, ULT_PINGPONGLOOP = 16, }; #pragma pack(push, 1) struct ult_sample { char name[32]; char filename[12]; uint32_t loop_start; uint32_t loop_end; uint32_t size_start; uint32_t size_end; uint8_t volume; // 0-255, apparently prior to 1.4 this was logarithmic? uint8_t flags; // above uint16_t speed; // only exists for 1.4+ int16_t finetune; }; #pragma pack(pop) /* Unhandled effects: 5x1 - do not loop sample (x is unused) 5x2 - play sample backwards 5xC - end loop and finish sample 9xx - set sample offset to xx * 1024 with 9yy: set sample offset to xxyy * 4 E0x - set vibrato strength (2 is normal) F00 - reset speed/tempo to 6/125 Apparently 3xx will CONTINUE to slide until it reaches its destination, or until a 300 effect is encountered. I'm not attempting to handle this (yet). The logarithmic volume scale used in older format versions here, or pretty much anywhere for that matter. I don't even think Ultra Tracker tries to convert them. */ static const uint8_t ult_efftrans[] = { FX_ARPEGGIO, FX_PORTAMENTOUP, FX_PORTAMENTODOWN, FX_TONEPORTAMENTO, FX_VIBRATO, FX_NONE, FX_NONE, FX_TREMOLO, FX_NONE, FX_OFFSET, FX_VOLUMESLIDE, FX_PANNING, FX_VOLUME, FX_PATTERNBREAK, FX_NONE, // extended effects, processed separately FX_SPEED, }; static void translate_fx(uint8_t *pe, uint8_t *pp) { uint8_t e = *pe & 0xf; uint8_t p = *pp; *pe = ult_efftrans[e]; switch (e) { case 0: if (!p) *pe = FX_NONE; break; case 3: // 300 apparently stops sliding, which is totally weird if (!p) p = 1; // close enough? break; case 0xa: // blah, this sucks if (p & 0xf0) p &= 0xf0; break; case 0xb: // mikmod does this wrong, resulting in values 0-225 instead of 0-255 p = (p & 0xf) * 0x11; break; case 0xc: // volume p >>= 2; break; case 0xd: // pattern break p = 10 * (p >> 4) + (p & 0xf); break; case 0xe: // special switch (p >> 4) { case 1: *pe = FX_PORTAMENTOUP; p = 0xf0 | (p & 0xf); break; case 2: *pe = FX_PORTAMENTODOWN; p = 0xf0 | (p & 0xf); break; case 8: *pe = FX_SPECIAL; p = 0x60 | (p & 0xf); break; case 9: *pe = FX_RETRIG; p &= 0xf; break; case 0xa: *pe = FX_VOLUMESLIDE; p = ((p & 0xf) << 4) | 0xf; break; case 0xb: *pe = FX_VOLUMESLIDE; p = 0xf0 | (p & 0xf); break; case 0xc: case 0xd: *pe = FX_SPECIAL; break; } break; case 0xf: if (p > 0x2f) *pe = FX_TEMPO; break; } *pp = p; } static int read_ult_event(slurp_t *fp, song_note_t *note, int *lostfx) { uint8_t b, repeat = 1; uint32_t off; int n; b = slurp_getc(fp); if (b == 0xfc) { repeat = slurp_getc(fp); b = slurp_getc(fp); } note->note = (b > 0 && b < 61) ? b + 36 : NOTE_NONE; note->instrument = slurp_getc(fp); b = slurp_getc(fp); note->voleffect = b & 0xf; note->effect = b >> 4; note->volparam = slurp_getc(fp); note->param = slurp_getc(fp); translate_fx(¬e->voleffect, ¬e->volparam); translate_fx(¬e->effect, ¬e->param); // sample offset -- this is even more special than digitrakker's if (note->voleffect == FX_OFFSET && note->effect == FX_OFFSET) { off = ((note->volparam << 8) | note->param) >> 6; note->voleffect = FX_NONE; note->param = MIN(off, 0xff); } else if (note->voleffect == FX_OFFSET) { off = note->volparam * 4; note->volparam = MIN(off, 0xff); } else if (note->effect == FX_OFFSET) { off = note->param * 4; note->param = MIN(off, 0xff); } else if (note->voleffect == note->effect) { /* don't try to figure out how ultratracker does this, it's quite random */ note->effect = FX_NONE; } if (note->effect == FX_VOLUME || (note->effect == FX_NONE && note->voleffect != FX_VOLUME)) { swap_effects(note); } // Do that dance. // Maybe I should quit rewriting this everywhere and make a generic version :P for (n = 0; n < 4; n++) { if (convert_voleffect_of(note, n >> 1)) { n = 5; break; } swap_effects(note); } if (n < 5) { if (effect_weight[note->voleffect] > effect_weight[note->effect]) swap_effects(note); (*lostfx)++; //log_appendf(4, "Effect dropped: %c%02X < %c%02X", get_effect_char(note->voleffect), // note->volparam, get_effect_char(note->effect), note->param); note->voleffect = 0; } if (!note->voleffect) note->volparam = 0; if (!note->effect) note->param = 0; return repeat; } /* --------------------------------------------------------------------------------------------------------- */ int fmt_ult_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { char buf[34]; uint8_t ver; int nmsg, nsmp, nchn, npat; int n, chn, pat, row; int lostfx = 0, gxx = 0; struct ult_sample usmp; song_sample_t *smp; const char *verstr[] = {"<1.4", "1.4", "1.5", "1.6"}; slurp_read(fp, buf, 14); if (memcmp(buf, "MAS_UTrack_V00", 14) != 0) return LOAD_UNSUPPORTED; ver = slurp_getc(fp); if (ver < '1' || ver > '4') return LOAD_FORMAT_ERROR; ver -= '0'; slurp_read(fp, buf, 32); buf[25] = '\0'; strcpy(song->title, buf); sprintf(song->tracker_id, "Ultra Tracker %s", verstr[ver - 1]); song->flags |= SONG_COMPATGXX | SONG_ITOLDEFFECTS; nmsg = slurp_getc(fp); read_lined_message(song->message, fp, nmsg * 32, 32); nsmp = slurp_getc(fp); for (n = 0, smp = song->samples + 1; n < nsmp; n++, smp++) { // annoying: v4 added a field before the end of the struct if (ver >= 4) { slurp_read(fp, &usmp, sizeof(usmp)); usmp.speed = bswapLE16(usmp.speed); } else { slurp_read(fp, &usmp, 64); usmp.finetune = usmp.speed; usmp.speed = 8363; } usmp.finetune = bswapLE16(usmp.finetune); usmp.loop_start = bswapLE32(usmp.loop_start); usmp.loop_end = bswapLE32(usmp.loop_end); usmp.size_start = bswapLE32(usmp.size_start); usmp.size_end = bswapLE32(usmp.size_end); strncpy(smp->name, usmp.name, 25); smp->name[25] = '\0'; strncpy(smp->filename, usmp.filename, 12); smp->filename[12] = '\0'; if (usmp.size_end <= usmp.size_start) continue; smp->length = usmp.size_end - usmp.size_start; smp->loop_start = usmp.loop_start; smp->loop_end = MIN(usmp.loop_end, smp->length); smp->volume = usmp.volume; //mphack - should be 0-64 not 0-256 smp->global_volume = 64; /* mikmod does some weird integer math here, but it didn't really work for me */ smp->c5speed = usmp.speed; if (usmp.finetune) smp->c5speed *= pow(2, (usmp.finetune / (12.0 * 32768))); if (usmp.flags & ULT_LOOP) smp->flags |= CHN_LOOP; if (usmp.flags & ULT_PINGPONGLOOP) smp->flags |= CHN_PINGPONGLOOP; if (usmp.flags & ULT_16BIT) { smp->flags |= CHN_16BIT; smp->loop_start >>= 1; smp->loop_end >>= 1; } } // ult just so happens to use 255 for its end mark, so there's no need to fiddle with this slurp_read(fp, song->orderlist, 256); nchn = slurp_getc(fp) + 1; npat = slurp_getc(fp) + 1; if (nchn > 32 || npat > MAX_PATTERNS) return LOAD_FORMAT_ERROR; if (ver >= 3) { for (n = 0; n < nchn; n++) song->channels[n].panning = ((slurp_getc(fp) & 0xf) << 2) + 2; } else { for (n = 0; n < nchn; n++) song->channels[n].panning = (n & 1) ? 48 : 16; } for (; n < 64; n++) { song->channels[n].panning = 32; song->channels[n].flags = CHN_MUTE; } //mphack - fix the pannings for (n = 0; n < 64; n++) song->channels[n].panning *= 4; if ((lflags & (LOAD_NOSAMPLES | LOAD_NOPATTERNS)) == (LOAD_NOSAMPLES | LOAD_NOPATTERNS)) return LOAD_SUCCESS; for (pat = 0; pat < npat; pat++) { song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; song->patterns[pat] = csf_allocate_pattern(64); } for (chn = 0; chn < nchn; chn++) { song_note_t evnote; song_note_t *note; int repeat; for (pat = 0; pat < npat; pat++) { note = song->patterns[pat] + chn; row = 0; while (row < 64) { repeat = read_ult_event(fp, &evnote, &lostfx); if (evnote.effect == FX_TONEPORTAMENTO || evnote.voleffect == VOLFX_TONEPORTAMENTO) { gxx |= 1; } if (repeat + row > 64) repeat = 64 - row; while (repeat--) { *note = evnote; note += 64; row++; } } } } if (gxx) log_appendf(4, " Warning: Gxx effects may not be suitably imported"); if (lostfx) log_appendf(4, " Warning: %d effect%s dropped", lostfx, lostfx == 1 ? "" : "s"); if (!(lflags & LOAD_NOSAMPLES)) { for (n = 0, smp = song->samples + 1; n < nsmp; n++, smp++) { uint32_t ssize = csf_read_sample(smp, SF_LE | SF_M | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8), fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, ssize, SEEK_CUR); } } return LOAD_SUCCESS; } schismtracker-20180209/fmt/wav.c000066400000000000000000000247411323741476300163740ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "it.h" #include "disko.h" #include "sndfile.h" #include "log.h" #include #define WAVE_FORMAT_PCM 0x0001 #define WAVE_FORMAT_IEEE_FLOAT 0x0003 // IEEE float #define WAVE_FORMAT_ALAW 0x0006 // 8-bit ITU-T G.711 A-law #define WAVE_FORMAT_MULAW 0x0007 // 8-bit ITU-T G.711 µ-law #define WAVE_FORMAT_EXTENSIBLE 0xFFFE // 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 #pragma pack(push, 1) typedef struct { uint32_t id_RIFF; // "RIFF" uint32_t filesize; // file length-8 uint32_t id_WAVE; } wave_file_header_t; typedef struct { uint16_t format; // 1 uint16_t channels; // 1:mono, 2:stereo uint32_t freqHz; // sampling freq uint32_t bytessec; // bytes/sec=freqHz*samplesize uint16_t samplesize; // sizeof(sample) uint16_t bitspersample; // bits per sample (8/16) } wave_format_t; typedef struct { uint32_t id; uint32_t length; } wave_chunk_prefix_t; typedef struct { wave_format_t fmt; // Format wave_chunk_prefix_t data; // Data header uint8_t *buf; } wave_file_t; #pragma pack(pop) /* --------------------------------------------------------------------------------------------------------- */ static int wav_load(wave_file_t *f, const uint8_t *data, size_t len) { wave_file_header_t phdr; size_t offset; int have_format = 0; if (len < sizeof(wave_file_header_t)) { return 0; } memcpy(&phdr, data, sizeof(wave_file_header_t)); #if WORDS_BIGENDIAN phdr.id_RIFF = bswapLE32(phdr.id_RIFF); phdr.filesize = bswapLE32(phdr.filesize); phdr.id_WAVE = bswapLE32(phdr.id_WAVE); #endif if (phdr.id_RIFF != IFFID_RIFF || phdr.id_WAVE != IFFID_WAVE) { return 0; } offset = sizeof(wave_file_header_t); while (1) { wave_chunk_prefix_t c; memcpy(&c, data + offset, sizeof(wave_chunk_prefix_t)); #if WORDS_BIGENDIAN c.id = bswapLE32(c.id); c.length = bswapLE32(c.length); #endif offset += sizeof(wave_chunk_prefix_t); if (offset + c.length > len) { log_appendf(4, "Corrupt WAV file. Chunk points outside of WAV file [%lu + %u > %lu]\n", (unsigned long) offset, c.length, (unsigned long) len); return 0; } switch (c.id) { case IFFID_fmt: { if (have_format) { log_appendf(4, "Corrupt WAV file. Found multiple format headers.\n"); return 0; } have_format = 1; memcpy(&f->fmt, data + offset, sizeof(wave_format_t)); #if WORDS_BIGENDIAN f->fmt.format = bswapLE16(f->fmt.format); f->fmt.channels = bswapLE16(f->fmt.channels); f->fmt.freqHz = bswapLE32(f->fmt.freqHz); f->fmt.bytessec = bswapLE32(f->fmt.bytessec); f->fmt.samplesize = bswapLE16(f->fmt.samplesize); f->fmt.bitspersample = bswapLE16(f->fmt.bitspersample); #endif break; } case IFFID_data: if (!have_format) { log_appendf(4, "WAV file did not specify format before data\n"); return 0; } memcpy(&f->data, &c, sizeof(wave_chunk_prefix_t)); f->buf = (uint8_t *)(data + offset); return 1; } offset += c.length; if (offset == len) break; } return 1; } /* --------------------------------------------------------------------------------------------------------- */ int fmt_wav_load_sample(const uint8_t *data, size_t len, song_sample_t *smp) { wave_file_t f; uint32_t flags; if (!wav_load(&f, data, len)) return 0; if (f.fmt.format != WAVE_FORMAT_PCM || !f.fmt.freqHz || (f.fmt.channels != 1 && f.fmt.channels != 2)) return 0; smp->flags = 0; // flags are set by csf_read_sample flags = 0; // endianness flags = SF_LE; // channels flags |= (f.fmt.channels == 2) ? SF_SI : SF_M; // interleaved stereo // bit width switch (f.fmt.bitspersample) { case 8: flags |= SF_8; break; case 16: flags |= SF_16; break; case 24: flags |= SF_24; break; case 32: flags |= SF_32; break; default: return 0; // unsupported } // encoding (8-bit wav is unsigned, everything else is signed -- yeah, it's stupid) flags |= (f.fmt.bitspersample == 8) ? SF_PCMU : SF_PCMS; smp->volume = 64 * 4; smp->global_volume = 64; smp->c5speed = f.fmt.freqHz; smp->length = f.data.length / ((f.fmt.bitspersample / 8) * f.fmt.channels); return csf_read_sample((song_sample_t *)smp, flags, (const char *) f.buf, f.data.length); } int fmt_wav_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { wave_file_t f; if (!wav_load(&f, data, length)) return 0; else if (f.fmt.format != WAVE_FORMAT_PCM || !f.fmt.freqHz || (f.fmt.channels != 1 && f.fmt.channels != 2) || (f.fmt.bitspersample != 8 && f.fmt.bitspersample != 16 && f.fmt.bitspersample != 24 && f.fmt.bitspersample != 32)) return 0; file->smp_flags = 0; if (f.fmt.channels == 2) file->smp_flags |= CHN_STEREO; if (f.fmt.bitspersample == 16) file->smp_flags |= CHN_16BIT; file->smp_speed = f.fmt.freqHz; file->smp_length = f.data.length / ((f.fmt.bitspersample / 8) * f.fmt.channels); file->description = "IBM/Microsoft RIFF Audio"; file->type = TYPE_SAMPLE_PLAIN; file->smp_filename = file->base; return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* wav is like aiff's retarded cousin */ struct wav_writedata { long data_size; // seek position for writing data size (in bytes) size_t numbytes; // how many bytes have been written int bps; // bytes per sample int swap; // should be byteswapped? }; static int wav_header(disko_t *fp, int bits, int channels, int rate, size_t length, struct wav_writedata *wwd /* out */) { int16_t s; uint32_t ul; int bps = 1; bps *= ((bits + 7) / 8) * channels; /* write a very large size for now */ disko_write(fp, "RIFF\377\377\377\377WAVEfmt ", 16); ul = bswapLE32(16); // fmt chunk size disko_write(fp, &ul, 4); s = bswapLE16(1); // linear pcm disko_write(fp, &s, 2); s = bswapLE16(channels); // number of channels disko_write(fp, &s, 2); ul = bswapLE32(rate); // sample rate disko_write(fp, &ul, 4); ul = bswapLE32(bps * rate); // "byte rate" (why?! I have no idea) disko_write(fp, &ul, 4); s = bswapLE16(bps); // (oh, come on! the format already stores everything needed to calculate this!) disko_write(fp, &s, 2); s = bswapLE16(bits); // bits per sample disko_write(fp, &s, 2); disko_write(fp, "data", 4); if (wwd) wwd->data_size = disko_tell(fp); ul = bswapLE32(bps * length); disko_write(fp, &ul, 4); return bps; } int fmt_wav_save_sample(disko_t *fp, song_sample_t *smp) { int bps; uint32_t ul; uint32_t flags = SF_LE; flags |= (smp->flags & CHN_16BIT) ? (SF_16 | SF_PCMS) : (SF_8 | SF_PCMU); flags |= (smp->flags & CHN_STEREO) ? SF_SI : SF_M; bps = wav_header(fp, (smp->flags & CHN_16BIT) ? 16 : 8, (smp->flags & CHN_STEREO) ? 2 : 1, smp->c5speed, smp->length, NULL); if (csf_write_sample(fp, smp, flags) != smp->length * bps) { log_appendf(4, "WAV: unexpected data size written"); return SAVE_INTERNAL_ERROR; } /* fix the length in the file header */ ul = disko_tell(fp) - 8; ul = bswapLE32(ul); disko_seek(fp, 4, SEEK_SET); disko_write(fp, &ul, 4); return SAVE_SUCCESS; } int fmt_wav_export_head(disko_t *fp, int bits, int channels, int rate) { struct wav_writedata *wwd = malloc(sizeof(struct wav_writedata)); if (!wwd) return DW_ERROR; fp->userdata = wwd; wwd->bps = wav_header(fp, bits, channels, rate, ~0, wwd); wwd->numbytes = 0; #if WORDS_BIGENDIAN wwd->swap = (bits > 8); #else wwd->swap = 0; #endif return DW_OK; } int fmt_wav_export_body(disko_t *fp, const uint8_t *data, size_t length) { struct wav_writedata *wwd = fp->userdata; if (length % wwd->bps) { log_appendf(4, "WAV export: received uneven length"); return DW_ERROR; } wwd->numbytes += length; if (wwd->swap) { const int16_t *ptr = (const int16_t *) data; uint16_t v; length /= 2; while (length--) { v = *ptr; v = bswapLE16(v); disko_write(fp, &v, 2); ptr++; } } else { disko_write(fp, data, length); } return DW_OK; } int fmt_wav_export_silence(disko_t *fp, long bytes) { struct wav_writedata *wwd = fp->userdata; wwd->numbytes += bytes; disko_seek(fp, bytes, SEEK_CUR); return DW_OK; } int fmt_wav_export_tail(disko_t *fp) { struct wav_writedata *wwd = fp->userdata; uint32_t ul; /* fix the length in the file header */ ul = disko_tell(fp) - 8; ul= bswapLE32(ul); disko_seek(fp, 4, SEEK_SET); disko_write(fp, &ul, 4); /* write the other lengths */ disko_seek(fp, wwd->data_size, SEEK_SET); ul = bswapLE32(wwd->numbytes); disko_write(fp, &ul, 4); free(wwd); return DW_OK; } schismtracker-20180209/fmt/xi.c000066400000000000000000000162451323741476300162170ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "it.h" #include "song.h" #include "sndfile.h" #include #include /* --------------------------------------------------------------------- */ #pragma pack(push, 1) struct xm_point { uint16_t ticks; // Time in tracker ticks uint16_t val; // Value from 0x00 to 0x40. }; struct xm_sample_header { uint32_t samplen; uint32_t loopstart; uint32_t looplen; uint8_t vol; signed char finetune; uint8_t type; uint8_t pan; signed char relnote; uint8_t res; char name[22]; }; struct xi_sample_header { uint8_t snum[96]; union { uint16_t env[48]; // Occupies same mem as venv,penv struct { struct xm_point venv[12], penv[12]; }; }; uint8_t vnum, pnum; uint8_t vsustain, vloops, vloope, psustain, ploops, ploope; uint8_t vtype, ptype; uint8_t vibtype, vibsweep, vibdepth, vibrate; uint16_t volfade; uint8_t reserved1[0x16]; uint16_t nsamples; }; struct xi_file_header { int8_t header[0x15]; // "Extended Instrument: " int8_t name[0x16]; // Name of instrument uint8_t magic; // 0x1a, DOS EOF char so you can 'type file.xi' int8_t tracker[0x14]; // Name of tracker uint16_t version; // big-endian 0x0102 struct xi_sample_header xish; struct xm_sample_header sheader[]; }; #pragma pack(pop) static int validate_xi(const struct xi_file_header *xi, size_t length) { if (length <= sizeof(struct xi_file_header)) return 0; if (memcmp(xi->header, "Extended Instrument: ", 21) != 0) return 0; if (xi->magic != 0x1a) return 0; if (bswapLE16(xi->version) != 0x0102) return(0); return(1); } /* --------------------------------------------------------------------- */ int fmt_xi_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { struct xi_file_header *xi = (struct xi_file_header *) data; if (!validate_xi(xi, length)) return 0; file->description = "FastTracker Instrument"; file->title = strn_dup((const char *)xi->name, 22); file->type = TYPE_INST_XI; return 1; } int fmt_xi_load_instrument(const uint8_t *data, size_t length, int slot) { const struct xi_file_header *xi = (const struct xi_file_header *) data; struct xi_sample_header xmsh; struct instrumentloader ii; song_instrument_t *g; const uint8_t *sampledata, *end; int k, prevtick; if (!slot) return 0; if (!validate_xi(xi, length)) return 0; end = data + length; song_delete_instrument(slot); g = instrument_loader_init(&ii, slot); memcpy(g->name, xi->name, 22); g->name[22] = '\0'; xmsh = xi->xish; for (k = 0; k < 96; k++) { if (xmsh.snum[k] > 15) xmsh.snum[k] = 15; xmsh.snum[k] = instrument_loader_sample(&ii, xmsh.snum[k] + 1); g->note_map[k + 12] = k + 1 + 12; if (xmsh.snum[k]) g->sample_map[k + 12] = xmsh.snum[k]; } for (k = 0; k < 12; k++) { g->note_map[k] = 0; g->sample_map[k] = 0; g->note_map[k + 108] = 0; g->sample_map[k + 108] = 0; } // bswap all volume and panning envelope points for (k = 0; k < 48; k++) xmsh.env[k] = bswapLE16(xmsh.env[k]); // Set up envelope types in instrument if (xmsh.vtype & 0x01) g->flags |= ENV_VOLUME; if (xmsh.vtype & 0x02) g->flags |= ENV_VOLSUSTAIN; if (xmsh.vtype & 0x04) g->flags |= ENV_VOLLOOP; if (xmsh.ptype & 0x01) g->flags |= ENV_PANNING; if (xmsh.ptype & 0x02) g->flags |= ENV_PANSUSTAIN; if (xmsh.ptype & 0x04) g->flags |= ENV_PANLOOP; prevtick = -1; // Copy envelopes into instrument for (k = 0; k < xmsh.vnum; k++) { if (xmsh.venv[k].ticks < prevtick) prevtick++; else prevtick = xmsh.venv[k].ticks; g->vol_env.ticks[k] = prevtick; g->vol_env.values[k] = xmsh.venv[k].val; } prevtick = -1; for (k = 0; k < xmsh.pnum; k++) { if (xmsh.penv[k].ticks < prevtick) prevtick++; else prevtick = xmsh.penv[k].ticks; g->pan_env.ticks[k] = prevtick; g->pan_env.values[k] = xmsh.penv[k].val; } g->vol_env.loop_start = xmsh.vloops; g->vol_env.loop_end = xmsh.vloope; g->vol_env.sustain_start = xmsh.vsustain; g->vol_env.nodes = xmsh.vnum; g->pan_env.loop_start = xmsh.ploops; g->pan_env.loop_end = xmsh.ploope; g->pan_env.sustain_start = xmsh.psustain; g->pan_env.nodes = xmsh.pnum; xmsh.volfade = bswapLE16(xmsh.volfade); xmsh.nsamples = bswapLE16(xmsh.nsamples); // Sample data begins at the end of the sample headers sampledata = (const uint8_t *) (xi->sheader + xmsh.nsamples); for (k = 0; k < xmsh.nsamples; k++) { struct xm_sample_header xmss = xi->sheader[k]; song_sample_t *smp; unsigned int rs, samplesize, n; xmss.samplen = bswapLE32(xmss.samplen); xmss.loopstart = bswapLE32(xmss.loopstart); xmss.looplen = bswapLE32(xmss.looplen); rs = SF_LE | SF_PCMD; // endianness; encoding rs |= (xmss.type & 0x20) ? SF_SS : SF_M; // channels rs |= (xmss.type & 0x10) ? SF_16 : SF_8; // bits 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.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; n = instrument_loader_sample(&ii, k + 1); smp = song_get_sample(n); smp->flags = 0; memcpy(smp->name, xmss.name, 22); smp->name[21] = '\0'; samplesize = xmss.samplen; smp->length = samplesize; smp->loop_start = xmss.loopstart; smp->loop_end = xmss.looplen; if (smp->loop_end < smp->loop_start) smp->loop_end = smp->length; if (smp->loop_start >= smp->loop_end) smp->loop_start = smp->loop_end = 0; switch (xmss.type & 3) { case 3: case 2: smp->flags |= CHN_PINGPONGLOOP; case 1: smp->flags |= CHN_LOOP; } smp->volume = xmss.vol << 2; if (smp->volume > 256) smp->volume = 256; smp->global_volume = 64; smp->panning = xmss.pan; smp->flags |= CHN_PANNING; smp->vib_type = xmsh.vibtype; smp->vib_speed = xmsh.vibsweep; smp->vib_depth = xmsh.vibdepth; smp->vib_rate = xmsh.vibrate / 4; // XXX xm.c does not divide here, which is wrong? smp->c5speed = transpose_to_frequency(xmss.relnote, xmss.finetune); sampledata += csf_read_sample(current_song->samples + n, rs, sampledata, (end-sampledata)); } return 1; } schismtracker-20180209/fmt/xm.c000066400000000000000000000614141323741476300162210ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "fmt.h" #include "it.h" // needed for get_effect_char (purely informational) #include "log.h" #include "sndfile.h" #include "tables.h" /* --------------------------------------------------------------------- */ int fmt_xm_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { if (!(length > 38 && memcmp(data, "Extended Module: ", 17) == 0)) return 0; file->description = "Fast Tracker 2 Module"; file->type = TYPE_MODULE_XM; /*file->extension = str_dup("xm");*/ file->title = strn_dup((const char *)data + 17, 20); return 1; } /* --------------------------------------------------------------------------------------------------------- */ // gloriously stolen from xmp struct xm_file_header { uint8_t id[17]; // ID text: "Extended module: " uint8_t name[20]; // Module name, padded with zeroes uint8_t doseof; // 0x1a uint8_t tracker[20]; // Tracker name uint16_t version; // Version number, minor-major uint32_t headersz; // Header size uint16_t songlen; // Song length (in patten order table) uint16_t restart; // Restart position uint16_t channels; // Number of channels (2,4,6,8,10,...,32) uint16_t patterns; // Number of patterns (max 256) uint16_t instruments; // Number of instruments (max 128) uint16_t flags; // bit 0: 0=Amiga freq table, 1=Linear uint16_t tempo; // Default tempo uint16_t bpm; // Default BPM }; static uint8_t autovib_import[8] = { VIB_SINE, VIB_SQUARE, VIB_RAMP_DOWN, // actually ramp up VIB_RAMP_DOWN, VIB_RANDOM, // default to sine VIB_SINE, VIB_SINE, VIB_SINE, }; static void load_xm_patterns(song_t *song, struct xm_file_header *hdr, slurp_t *fp) { int pat, row, chan; uint32_t patlen; uint8_t b; uint16_t rows; uint16_t bytes; size_t end; // should be same data type as slurp_t's length song_note_t *note; unsigned int lostpat = 0; unsigned int lostfx = 0; for (pat = 0; pat < hdr->patterns; pat++) { slurp_read(fp, &patlen, 4); // = 8/9 patlen = bswapLE32(patlen); b = slurp_getc(fp); // = 0 if (hdr->version == 0x0102) { rows = slurp_getc(fp) + 1; patlen++; // fake it so that alignment works properly. } else { slurp_read(fp, &rows, 2); rows = bswapLE16(rows); } slurp_read(fp, &bytes, 2); bytes = bswapLE16(bytes); // if 0, pattern is empty slurp_seek(fp, patlen - 9, SEEK_CUR); // probably a no-op if (!rows) continue; if (pat >= MAX_PATTERNS) { if (bytes) lostpat++; slurp_seek(fp, bytes, SEEK_CUR); continue; } note = song->patterns[pat] = csf_allocate_pattern(rows); song->pattern_size[pat] = song->pattern_alloc_size[pat] = rows; if (!bytes) continue; // hack to avoid having to count bytes when reading end = slurp_tell(fp) + bytes; end = MIN(end, fp->length); for (row = 0; row < rows; row++, note += MAX_CHANNELS - hdr->channels) { for (chan = 0; fp->pos < end && chan < hdr->channels; chan++, note++) { b = slurp_getc(fp); if (b & 128) { if (b & 1) note->note = slurp_getc(fp); if (b & 2) note->instrument = slurp_getc(fp); if (b & 4) note->volparam = slurp_getc(fp); if (b & 8) note->effect = slurp_getc(fp); if (b & 16) note->param = slurp_getc(fp); } else { note->note = b; note->instrument = slurp_getc(fp); note->volparam = slurp_getc(fp); note->effect = slurp_getc(fp); note->param = slurp_getc(fp); } // translate everything if (note->note > 0 && note->note < 97) note->note += 12; else if (note->note == 97) note->note = NOTE_OFF; else note->note = NOTE_NONE; if (note->effect || note->param) csf_import_mod_effect(note, 1); if (note->instrument == 0xff) note->instrument = 0; // now that the mundane stuff is over with... NOW IT'S TIME TO HAVE SOME FUN! // the volume column is initially imported as "normal" effects, juggled around // in order to make it more IT-like, and then converted into volume-effects /* IT puts all volume column effects into the effect column if there's not an effect there already; in the case of two set-volume effects, the one in the effect column takes precedence. set volume with values > 64 are clipped to 64 pannings are imported as S8x, unless there's an effect in which case it's translated to a volume-column panning value. volume and panning slides with zero value (+0, -0, etc.) still translate to an effect -- even though volslides don't have effect memory in FT2. */ switch (note->volparam >> 4) { case 5: // 0x50 = volume 64, 51-5F = nothing if (note->volparam == 0x50) { case 1 ... 4: // Set volume Value-$10 note->voleffect = FX_VOLUME; note->volparam -= 0x10; break; } // NOTE: falls through from case 5 when vol != 0x50 case 0: // Do nothing note->voleffect = FX_NONE; note->volparam = 0; break; case 6: // Volume slide down note->volparam &= 0xf; if (note->volparam) note->voleffect = FX_VOLUMESLIDE; break; case 7: // Volume slide up note->volparam = (note->volparam & 0xf) << 4; if (note->volparam) note->voleffect = FX_VOLUMESLIDE; break; case 8: // Fine volume slide down note->volparam &= 0xf; if (note->volparam) { if (note->volparam == 0xf) note->volparam = 0xe; // DFF is fine slide up... note->volparam |= 0xf0; note->voleffect = FX_VOLUMESLIDE; } break; case 9: // Fine volume slide up note->volparam = (note->volparam & 0xf) << 4; if (note->volparam) { note->volparam |= 0xf; note->voleffect = FX_VOLUMESLIDE; } break; case 10: // Set vibrato speed /* ARGH. this doesn't actually CAUSE vibrato - it only sets the value! i don't think there's a way to handle this correctly and sanely, so i'll just do what impulse tracker and mpt do... (probably should write a warning saying the song might not be played correctly) */ note->volparam = (note->volparam & 0xf) << 4; note->voleffect = FX_VIBRATO; break; case 11: // Vibrato note->volparam &= 0xf; note->voleffect = FX_VIBRATO; break; case 12: // Set panning note->voleffect = FX_SPECIAL; note->volparam = 0x80 | (note->volparam & 0xf); break; case 13: // Panning slide left // in FT2, <0 sets the panning to far left on the SECOND tick // this is "close enough" (except at speed 1) note->volparam &= 0xf; if (note->volparam) { note->volparam <<= 4; note->voleffect = FX_PANNINGSLIDE; } else { note->volparam = 0x80; note->voleffect = FX_SPECIAL; } break; case 14: // Panning slide right note->volparam &= 0xf; if (note->volparam) note->voleffect = FX_PANNINGSLIDE; break; case 15: // Tone porta note->volparam = (note->volparam & 0xf) << 4; note->voleffect = FX_TONEPORTAMENTO; break; } if (note->effect == FX_KEYOFF && note->param == 0) { // FT2 ignores both K00 and its note entirely (but still plays // previous notes and processes the volume column!) note->note = NOTE_NONE; note->instrument = 0; note->effect = FX_NONE; } else if (note->note == NOTE_OFF && note->effect == FX_SPECIAL && (note->param >> 4) == 0xd) { // note off with a delay ignores the note off, and also // ignores set-panning (but not other effects!) // (actually the other vol. column effects happen on the // first tick with ft2, but this is "close enough" i think) note->note = NOTE_NONE; note->instrument = 0; // note: haven't fixed up volumes yet if (note->voleffect == FX_PANNING) { note->voleffect = FX_NONE; note->volparam = 0; note->effect = FX_NONE; note->param = 0; } } if (note->effect == FX_NONE && note->voleffect != FX_NONE) { // put the lotion in the basket swap_effects(note); } else if (note->effect == note->voleffect) { // two of the same kind of effect => ignore the volume column // (note that ft2 behaves VERY strangely with Mx + 3xx combined -- // but i'll ignore that nonsense and just go by xm.txt here because // it's easier :) note->voleffect = note->volparam = 0; } if (note->effect == FX_VOLUME) { // try to move set-volume into the volume column swap_effects(note); } // now try to rewrite the volume column, if it's not possible then see if we // can do so after swapping them. // this is a terrible hack -- don't write code like this, kids :) int n; for (n = 0; n < 4; n++) { // (n >> 1) will be 0/1, indicating our desire to j... j... jam it in if (convert_voleffect_of(note, n >> 1)) { n = 5; // it'd be nice if c had a for...else like python break; } // nope that didn't work, switch them around swap_effects(note); } if (n < 5) { // Need to throw one out. if (effect_weight[note->voleffect] > effect_weight[note->effect]) { note->effect = note->voleffect; note->param = note->volparam; } //log_appendf(4, "Warning: pat%u row%u chn%u: lost effect %c%02X", // pat, row, chan + 1, get_effect_char(note->voleffect), note->volparam); note->voleffect = note->volparam = 0; lostfx++; } /* some XM effects that schism probably won't handle decently: 0xy / Jxy - this one is *totally* screwy, see milkytracker source for details :) (NOT documented -- in fact, all the documentation claims that it should simply play note -> note+x -> note+y -> note like any other tracker, but that sure isn't what FT2 does...) Axy / Dxy - it's probably not such a good idea to move these between the volume and effect column, since there's a chance it might screw stuff up since the volslides don't share memory (in either .it or .xm) -- e.g. ... .. .. DF0 ... .. .. D04 ... .. .. D00 is quite different from ... .. .. DF0 ... .. D4 .00 ... .. .. D00 But oh well. Works "enough" for now. [Note: IT doesn't even try putting volslide into the volume column.] E6x / SBx - ridiculously broken; it screws up the pattern break row if E60 isn't at the start of the pattern -- this is fairly well known by FT2 users, but curiously absent from its "known bugs" list E9x / Q0x - actually E9x isn't like Q0x at all... it's really stupid, I give up. hope no one wants to listen to XM files with retrig. ECx / SCx - doesn't actually CUT the note, it just sets volume to zero at tick x (this is documented) */ } } } if (lostfx) log_appendf(4, " Warning: %d effect%s dropped", lostfx, lostfx == 1 ? "" : "s"); if (lostpat) log_appendf(4, " Warning: Too many patterns in song (%d skipped)", lostpat); } static void load_xm_samples(song_sample_t *first, int total, slurp_t *fp) { song_sample_t *smp = first; size_t smpsize; int ns; // dontyou: 20 samples starting at 26122 // trnsmix: 31 samples starting at 61946 for (ns = 0; ns < total; ns++, smp++) { smpsize = smp->length; if (!smpsize) continue; if (smp->flags & CHN_16BIT) { smp->length >>= 1; smp->loop_start >>= 1; smp->loop_end >>= 1; } // modplug's sample-reading function is complicated and retarded csf_read_sample(smp, SF_LE | SF_M | SF_PCMD | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8), fp->data + fp->pos, fp->length - fp->pos); slurp_seek(fp, smpsize, SEEK_CUR); } } enum { ID_CONFIRMED = 1, // confirmed with inst/sample header sizes ID_FT2GENERIC = 2, // "FastTracker v2.00", but fasttracker has NOT been ruled out ID_OLDMODPLUG = 4, // "FastTracker v 2.00" ID_OTHER = 8, // something we don't know, testing for digitrakker. ID_FT2CLONE = 16, // NOT FT2: itype changed between instruments, or \0 found in song title ID_MAYBEMODPLUG = 32, // some FT2-ish thing, possibly MPT. ID_DIGITRAK = 64, // probably digitrakker ID_UNKNOWN = 128 | ID_CONFIRMED, // ????? }; // TODO: try to identify packers (boobiesqueezer?) // this also does some tracker detection // return value is the number of samples that need to be loaded later (for old xm files) static int load_xm_instruments(song_t *song, struct xm_file_header *hdr, slurp_t *fp) { int n, ni, ns; int abssamp = 1; // "real" sample int32_t ihdr, shdr; // instrument/sample header size (yes these should be signed) uint8_t b; uint16_t w; uint32_t d; int detected; int itype = -1; uint8_t srsvd_or = 0; // bitwise-or of all sample reserved bytes if (strncmp(song->tracker_id, "FastTracker ", 12) == 0) { if (hdr->headersz == 276 && strncmp(song->tracker_id + 12, "v2.00 ", 8) == 0) { // TODO: is it at all possible to tell the precise FT2 version? that'd be a neat trick. // (Answer: unlikely. After some testing, I can't identify any differences between 2.04 // and 2.09. Doesn't mean for certain that they're identical, but I would be surprised // if anything did change.) detected = ID_FT2GENERIC | ID_MAYBEMODPLUG; // replace the "v2.00" with just a 2, since it's probably not actually v2.00 strcpy(song->tracker_id + 12, "2"); } else if (strncmp(song->tracker_id + 12, "v 2.00 ", 8) == 0) { // Old MPT: // - 1.00a5 (ihdr=245) // - beta 3.3 (ihdr=263) strcpy(song->tracker_id, "Modplug Tracker 1.0"); detected = ID_OLDMODPLUG; } else { // definitely NOT FastTracker, so let's clear up that misconception detected = ID_UNKNOWN; } } else if (strncmp(song->tracker_id, "*Converted ", 11) == 0 || strspn(song->tracker_id, " ") == 20) { // this doesn't catch any cases where someone typed something into the field :( detected = ID_OTHER | ID_DIGITRAK; } else { detected = ID_OTHER; } // FT2 pads the song title with spaces, some other trackers don't if (detected & ID_FT2GENERIC && memchr(song->title, '\0', 20) != NULL) detected = ID_FT2CLONE | ID_MAYBEMODPLUG; for (ni = 1; ni <= hdr->instruments; ni++) { int vtype, vsweep, vdepth, vrate; song_instrument_t *ins; uint16_t nsmp; slurp_read(fp, &ihdr, 4); ihdr = bswapLE32(ihdr); if (ni >= MAX_INSTRUMENTS) { // TODO: try harder log_appendf(4, " Warning: Too many instruments in file"); break; } song->instruments[ni] = ins = csf_allocate_instrument(); slurp_read(fp, ins->name, 22); ins->name[22] = '\0'; if ((detected & ID_DIGITRAK) && memchr(ins->name, '\0', 22) != NULL) detected &= ~ID_DIGITRAK; b = slurp_getc(fp); if (itype == -1) { itype = b; } else if (itype != b && (detected & ID_FT2GENERIC)) { // FT2 writes some random junk for the instrument type field, // but it's always the SAME junk for every instrument saved. detected = (detected & ~ID_FT2GENERIC) | ID_FT2CLONE | ID_MAYBEMODPLUG; } slurp_read(fp, &nsmp, 2); nsmp = bswapLE16(nsmp); slurp_read(fp, &shdr, 4); shdr = bswapLE32(shdr); if (detected == ID_OLDMODPLUG) { detected = ID_CONFIRMED; if (ihdr == 245) { strcat(song->tracker_id, " alpha"); } else if (ihdr == 263) { strcat(song->tracker_id, " beta"); } else { // WEIRD!! detected = ID_UNKNOWN; } } if (!nsmp) { // lucky day! it's pretty easy to identify tracker if there's a blank instrument if (!(detected & ID_CONFIRMED)) { if ((detected & ID_MAYBEMODPLUG) && ihdr == 263 && shdr == 0) { detected = ID_CONFIRMED; strcpy(song->tracker_id, "Modplug Tracker"); } else if ((detected & ID_DIGITRAK) && ihdr != 29) { detected &= ~ID_DIGITRAK; } else if ((detected & (ID_FT2CLONE | ID_FT2GENERIC)) && ihdr != 33) { // Sure isn't FT2. // note: FT2 NORMALLY writes shdr=40 for all samples, but sometimes it // just happens to write random garbage there instead. surprise! detected = ID_UNKNOWN; } } // some adjustment hack from xmp. slurp_seek(fp, ihdr - 33, SEEK_CUR); continue; } for (n = 0; n < 12; n++) ins->note_map[n] = n + 1; for (; n < 96 + 12; n++) { ins->note_map[n] = n + 1; ins->sample_map[n] = slurp_getc(fp) + abssamp; } for (; n < 120; n++) ins->note_map[n] = n + 1; // envelopes // (god, xm stores this in such a retarded format, why isn't all the volume stuff // together and THEN the panning, so that this could at least not be so redundant) int prevtick = -1; for (n = 0; n < 12; n++) { slurp_read(fp, &w, 2); // tick w = bswapLE16(w); if (w < prevtick) { // TODO: mikmod source indicates files exist with broken envelope values, // and it does some complicated stuff to adjust them. investigate? w = prevtick + 1; } ins->vol_env.ticks[n] = prevtick = w; slurp_read(fp, &w, 2); // value w = bswapLE16(w); ins->vol_env.values[n] = MIN(w, 64); } // same thing again prevtick = -1; for (n = 0; n < 12; n++) { slurp_read(fp, &w, 2); // tick w = bswapLE16(w); if (w < prevtick) { w = prevtick + 1; } ins->pan_env.ticks[n] = prevtick = w; slurp_read(fp, &w, 2); // value w = bswapLE16(w); ins->pan_env.values[n] = MIN(w, 64); } b = slurp_getc(fp); ins->vol_env.nodes = CLAMP(b, 2, 12); b = slurp_getc(fp); ins->pan_env.nodes = CLAMP(b, 2, 12); ins->vol_env.sustain_start = ins->vol_env.sustain_end = slurp_getc(fp); ins->vol_env.loop_start = slurp_getc(fp); ins->vol_env.loop_end = slurp_getc(fp); ins->pan_env.sustain_start = ins->pan_env.sustain_end = slurp_getc(fp); ins->pan_env.loop_start = slurp_getc(fp); ins->pan_env.loop_end = slurp_getc(fp); b = slurp_getc(fp); if ((b & 1) && ins->vol_env.nodes > 0) ins->flags |= ENV_VOLUME; if (b & 2) ins->flags |= ENV_VOLSUSTAIN; if (b & 4) ins->flags |= ENV_VOLLOOP; b = slurp_getc(fp); if ((b & 1) && ins->pan_env.nodes > 0) ins->flags |= ENV_PANNING; if (b & 2) ins->flags |= ENV_PANSUSTAIN; if (b & 4) ins->flags |= ENV_PANLOOP; vtype = autovib_import[slurp_getc(fp) & 0x7]; vsweep = slurp_getc(fp); vdepth = slurp_getc(fp); vrate = slurp_getc(fp) / 4; slurp_read(fp, &w, 2); ins->fadeout = bswapLE16(w); if (ins->flags & ENV_VOLUME) { // fix note-fade if (!(ins->flags & ENV_VOLLOOP)) ins->vol_env.loop_start = ins->vol_env.loop_end = ins->vol_env.nodes - 1; if (!(ins->flags & ENV_VOLSUSTAIN)) ins->vol_env.sustain_start = ins->vol_env.sustain_end = ins->vol_env.nodes - 1; ins->flags |= ENV_VOLLOOP | ENV_VOLSUSTAIN; } else { // fix note-off ins->vol_env.ticks[0] = 0; ins->vol_env.ticks[1] = 1; ins->vol_env.values[0] = 64; ins->vol_env.values[1] = 0; ins->vol_env.nodes = 2; ins->vol_env.sustain_start = ins->vol_env.sustain_end = 0; ins->flags |= ENV_VOLUME | ENV_VOLSUSTAIN; } // some other things... ins->panning = 128; ins->global_volume = 128; ins->pitch_pan_center = 60; // C-5? /* here we're looking at what the ft2 spec SAYS are two reserved bytes. most programs blindly follow ft2's saving and add 22 zero bytes at the end (making the instrument header size 263 bytes), but ft2 is really writing the midi settings there, at least in the first 7 bytes. (as far as i can tell, the rest of the bytes are always zero) */ int midi_enabled = slurp_getc(fp); // instrument midi enable = 0/1 b = slurp_getc(fp); // midi transmit channel = 0-15 ins->midi_channel_mask = (midi_enabled == 1) ? 1 << MIN(b, 15) : 0; slurp_read(fp, &w, 2); // midi program = 0-127 w = bswapLE16(w); ins->midi_program = MIN(w, 127); slurp_read(fp, &w, 2); // bender range (halftones) = 0-36 if (slurp_getc(fp) == 1) ins->global_volume = 0; // mute computer = 0/1 slurp_seek(fp, ihdr - 248, SEEK_CUR); for (ns = 0; ns < nsmp; ns++) { int8_t relnote, finetune; song_sample_t *smp; if (abssamp + ns >= MAX_SAMPLES) { // TODO: try harder (fill unused sample slots) log_appendf(4, " Warning: Too many samples in file"); break; } smp = song->samples + abssamp + ns; slurp_read(fp, &d, 4); smp->length = bswapLE32(d); slurp_read(fp, &d, 4); smp->loop_start = bswapLE32(d); slurp_read(fp, &d, 4); smp->loop_end = bswapLE32(d) + smp->loop_start; smp->volume = slurp_getc(fp); smp->volume = MIN(64, smp->volume); smp->volume *= 4; //mphack smp->global_volume = 64; smp->flags = CHN_PANNING; finetune = slurp_getc(fp); b = slurp_getc(fp); // flags if (smp->loop_start >= smp->loop_end) b &= ~3; // that loop sucks, turn it off switch (b & 3) { /* NOTE: all cases fall through here. In FT2, type 3 is played as pingpong, but the GUI doesn't show any selected loop type. Apparently old MPT versions wrote 3 for pingpong loops, but that doesn't seem to be reliable enough to declare "THIS WAS MPT" because it seems FT2 would also SAVE that broken data after loading an instrument with loop type 3 was set. I have no idea. */ case 3: case 2: smp->flags |= CHN_PINGPONGLOOP; case 1: smp->flags |= CHN_LOOP; } if (b & 0x10) { smp->flags |= CHN_16BIT; // NOTE length and loop start/end are adjusted later } smp->panning = slurp_getc(fp); //mphack, should be adjusted to 0-64 relnote = slurp_getc(fp); smp->c5speed = transpose_to_frequency(relnote, finetune); srsvd_or |= slurp_getc(fp); slurp_read(fp, smp->name, 22); smp->name[22] = '\0'; if (detected & ID_DIGITRAK && memchr(smp->name, '\0', 22) != NULL) detected &= ~ID_DIGITRAK; smp->vib_type = vtype; smp->vib_rate = vsweep; smp->vib_depth = vdepth; smp->vib_speed = vrate; } if (hdr->version == 0x0104) load_xm_samples(song->samples + abssamp, ns, fp); abssamp += ns; // if we ran out of samples, stop trying to load instruments // (note this will break things with xm format ver < 0x0104!) if (ns != nsmp) break; } if (detected & ID_FT2CLONE) { if (srsvd_or == 0) { strcpy(song->tracker_id, "Modplug Tracker"); } else { // PlayerPro: itype and smp rsvd are both always zero // no idea how to identify it elsewise. strcpy(song->tracker_id, "FastTracker clone"); } } else if ((detected & ID_DIGITRAK) && srsvd_or == 0 && (itype ?: -1) == -1) { strcpy(song->tracker_id, "Digitrakker"); } else if (detected == ID_UNKNOWN) { strcpy(song->tracker_id, "Unknown tracker"); } return (hdr->version < 0x0104) ? abssamp : 0; } int fmt_xm_load_song(song_t *song, slurp_t *fp, UNUSED unsigned int lflags) { struct xm_file_header hdr; int n; uint8_t b; slurp_read(fp, &hdr, sizeof(hdr)); hdr.version = bswapLE16(hdr.version); hdr.headersz = bswapLE32(hdr.headersz); hdr.songlen = bswapLE16(hdr.songlen); hdr.restart = bswapLE16(hdr.restart); hdr.channels = bswapLE16(hdr.channels); hdr.patterns = bswapLE16(hdr.patterns); hdr.instruments = bswapLE16(hdr.instruments); hdr.flags = bswapLE16(hdr.flags); hdr.tempo = bswapLE16(hdr.tempo); hdr.bpm = bswapLE16(hdr.bpm); if (memcmp(hdr.id, "Extended Module: ", 17) != 0 || hdr.doseof != 0x1a || hdr.channels > MAX_CHANNELS) return LOAD_UNSUPPORTED; memcpy(song->title, hdr.name, 20); song->title[20] = '\0'; memcpy(song->tracker_id, hdr.tracker, 20); song->tracker_id[20] = '\0'; if (hdr.flags & 1) song->flags |= SONG_LINEARSLIDES; song->flags |= SONG_ITOLDEFFECTS | SONG_COMPATGXX | SONG_INSTRUMENTMODE; song->initial_speed = MIN(hdr.tempo, 255) ?: 255; song->initial_tempo = CLAMP(hdr.bpm, 31, 255); song->initial_global_volume = 128; song->mixing_volume = 48; for (n = 0; n < hdr.channels; n++) song->channels[n].panning = 32 * 4; //mphack for (; n < MAX_CHANNELS; n++) song->channels[n].flags |= CHN_MUTE; hdr.songlen = MIN(MAX_ORDERS, hdr.songlen); for (n = 0; n < hdr.songlen; n++) { b = slurp_getc(fp); song->orderlist[n] = (b >= MAX_PATTERNS) ? ORDER_SKIP : b; } slurp_seek(fp, 60 + hdr.headersz, SEEK_SET); if (hdr.version == 0x0104) { load_xm_patterns(song, &hdr, fp); load_xm_instruments(song, &hdr, fp); } else { int nsamp = load_xm_instruments(song, &hdr, fp); load_xm_patterns(song, &hdr, fp); load_xm_samples(song->samples + 1, nsamp, fp); } csf_insert_restart_pos(song, hdr.restart); return LOAD_SUCCESS; } schismtracker-20180209/font/000077500000000000000000000000001323741476300156035ustar00rootroot00000000000000schismtracker-20180209/font/default-lower.fnt000066400000000000000000000020001323741476300210560ustar00rootroot00000000000000~~~~l|88||88|8|8|8||8|<<><~~<ffffff{>c8ll8x~~~<~~<<~~< 0``0$ff$<~~<0xx000llllllll0|x 00f8l8vv``0```0`00`f<x00000x xflxlf```bf8ll8ff|``xxff|lfxpx0000xx0l88lx00xƌ2fx`````x`0 xx8l00x |v``|ffxx |vxx8l```v| `lvff0p000x x`flxlp00000xxxff|`v| vf`|x 0|004vx0ll8l| 0d00000000v8lschismtracker-20180209/font/default-upper-alt.fnt000066400000000000000000000020001323741476300216370ustar00rootroot00000000000000xx x~xx~<>f?x |~x |~00x |~xx 8~lxxxxxxxx~~| ~8ll8|00`x 3f7o3ff3f3f""""UUUUww66666666666666666666666666666666666676666670??0766666666667076666666666666666666??6666666666vvxlllll`0`~pffff|`v0xx08ll88lll0|x~~ ~~`8``8x0000`00`0`0p0000vv8ll8 l<xllllp0`x<<<<schismtracker-20180209/font/default-upper-itf.fnt000066400000000000000000000020001323741476300216410ustar00rootroot00000000000000?U~~~~~~??????<~<~BfZBB```````lllllllmmmmmmm<<f!@D8>""""c>""""0,# 4 @@@@<<<<<<<<<<<<<~~<<~~<<~~~~<<~~~~<66666667076666666666666666666??6666666666vvxlllll`0`~pffff|`v0xx08ll88lll0|x~~ ~~`8``8x0000`00`0`0p0000vv8ll8 l<xllllp0`x<<<<schismtracker-20180209/font/half-width.fnt000066400000000000000000000020001323741476300203330ustar00rootroot00000000000000DD@F,@DHJJ`D$DD DD NJNDH@"DHJ@LDDJ$H$"" "J@"" J@" @@@HHBBH$@@J`Jʬh`ʪ茈茈h`DD""@ꪠʪꪪʬJ`ʬJ*@DD@પ@꠪D@$dDD`DB DDD Bj`J@"&`J`Jj,@D b"ʠDDD@ʪJ@ʬj"ꈀhBDD `@J,H$LD DDD@FD.schismtracker-20180209/helptext/000077500000000000000000000000001323741476300164725ustar00rootroot00000000000000schismtracker-20180209/helptext/adlib-sample000066400000000000000000000117231323741476300207530ustar00rootroot00000000000000= 7888888888888888889 = 4 AdLib Samples 6 = 1222222222222222223 | | AdLib synthesis is a method of music creation originally provided by | Yamaha's YM3812 chip, that used to exist in most soundcards, especially | Sound Blaster compatibles, in the 1990s. In Schism Tracker, AdLib support | is provided through emulation. | | AdLib allows for producing impressive music while using very little | storage, because all instruments are synthesized by the hardware, instead | of being stored as PCM data like other tracker samples are. The synthesis | method used in AdLib is frequency modulation, hence it is also called FM | synthesis. | | AdLib consists of 9 melodic channels, each of which has the following | properties, most of which can be changed on the sample editing screen: | | Frequency, Octave, Note On/off | These properties are automatically managed | by Schism Tracker when the music plays, and | are not part of the sample editor. | | Synthesis mode (Additive synthesis) | Whether the sample is synthesized using FM, or whether | the two operators' outputs are simply summed together. | | Feedback level | This property allows the modulator to modulate its own | output. High levels of feedback are usually used to | create percussive instruments. | | Two oscillators (called "operators"), one called "Modulator" and | the other called "Carrier", each having the following properties: | | Frequency multiplier | Specifies the value by which the frequency produced by | the individual oscillator is multiplied. Though the | values displayed in the sample editor are indicated | as 0..15, the actual values they correspond to are | 1/2, 1..10, 10, 12, 12, 15 and 15. | | Waveform | Four possible waveforms exist for each channel: | 0 = sinewave _ _ | / \ / \ | \_/ \_/ | 1 = half sinewave _ _ | / \___/ \___ | | 2 = polarized sinewave _ _ _ _ | / \/ \/ \/ \ | | 3 = half polarized sinewave . . . . | /|_/|_/|_/|_ | Volume(0-63) | The volume level of this operator. The volume levels | are logarithmic, so a reduction of 8 in volume | corresponds to approximately half quieter signal. | | Attack, decay, sustain, release | These properties control the volume envelope of the | instrument. | | Attack = how quickly the volume reaches the maximum level | when the instrument is triggered | Decay = at which rate the volume decays immediately | after the attack phase | Sustain = at which rate the volume decays while it is | still keyed on (this number only affects if | the Sustain sound flag is set) | Release = at which rate the volume decays after the | instrument is no longer keyed on | | Volume Vibrato (Tremolo) | Automatic volume vibrato for the instrument on/off | | Pitch Vibrato | Automatic pitch vibrato for the instrument on/off | | Envelope scaling per key | If this property is set, then the envelope rates are | scaled according to the key that is played: lower pitches | decay slower, and higher pitches decay faster. This is | useful for e.g. piano type instruments. | | Volume scaling per key | Similarly as envelope scaling, this property controls | the rate on which the volume level is scaled by the | pitch that is played. | | The combined outcome of all these settings for both the Operator | and the Carrier determines how the sample will sound. | | If you want to create a new AdLib sample from scratch, use an empty | sample slot, and press Alt-Z. Set attack=15, decay=2, sustain=7 for | both operators, and experiment from there changing each property | as you like. | | IMPORTANT NOTE: AdLib instruments can currently only be | loaded from / saved to S3M files! They are not supported | by any other format supported by Schism Tracker, and thus, | if you save your AdLib module in e.g. IT format, the AdLib | instruments will be lost. | | References: | - Wikipedia article at http://en.wikipedia.org/wiki/Yamaha_YM3812 | - OPL2 emulator by Tatsuyuki Satoh and Jarek Burczynski schismtracker-20180209/helptext/copyright000066400000000000000000000020131323741476300204210ustar00rootroot00000000000000= 78888888888888888888888888889 = 4 Copyright and Credits 6 = 12222222222222222222222222223 | + Copyright (c) 2003-2017 Storlek + Mrs. Brisby + and others... + + Based on Impulse Tracker which is copyright (c) 1995-1998 Jeffrey Lim. + Contains code by Olivier Lapicque, Markus Fick, Adam Goode, Ville Jokela, + Juan Linietsky, Juha Niemimäki, and others. See the file AUTHORS in + the distribution for details. + + 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. + + 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 schismtracker-20180209/helptext/global-keys000066400000000000000000000055601323741476300206340ustar00rootroot00000000000000| 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 : Shift-F8 Pause / Resume Playback | F8 Stop Playback ! F9 Load Module : F9 Load Module (also Ctrl-L) | Shift-F9 Message Editor ! F10 Save Module : F10 Save Module (also Ctrl-W) : Shift-F10 Export Module (to WAV, AIFF) | 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 : Shift-F12 Font Editor | | { } Decrease/Increase playback speed : Ctrl-[ ] Decrease/Increase playback tempo | [ ] Decrease/Increase global volume | Alt-F1 -> Alt-F8 Toggle channels 1->8 | ! Ctrl-D DOS Shell : Ctrl-D Toggle mouse/keyboard grab | Ctrl-E Refresh screen and reset cache identification : Ctrl-G Go to order/pattern/row given time | Ctrl-I Reinitialise sound driver | Ctrl-M Toggle mouse cursor | Ctrl-N New Song : Ctrl-P Calculate approximate song length ! Ctrl-Q Quit to DOS : Ctrl-Q Quit Schism Tracker : Ctrl-Shift-Q Quit without confirmation | Ctrl-S Save current song | | Ctrl-Alt-Enter Toggle Fullscreen : : On text widgets. : Ctrl, Ctrl Digraph entry: ; C, -> Ç i> -> î y: -> ÿ n? -> ñ pi -> π ; u: -> ü i! -> ì O: -> Ö N? -> Ñ My -> µ ; e' -> é A: -> Ä U: -> Ü -a -> ª o/ -> φ ; a> -> â AA -> Å Ct -> ¢ -o -> º O/ -> φ ; a: -> ä E' -> É Pd -> £ ?I -> ¿ +- -> ± ; a! -> à ae -> æ Ye -> ¥ NO -> ¬ -: -> ÷ ; aa -> å AE -> Æ Pt -> ₧ 12 -> ½ DG -> ° ; c, -> ç o> -> ô ff -> ƒ 14 -> « .M -> ∙ ; e> -> ê o: -> ö a' -> á !I -> » 2S -> ² ; e: -> ë o! -> ò i' -> í << -> ░ nS -> ⁿ ; e! -> è u> -> û o' -> ó >> -> ▒ PI -> ¶ ; i: -> ï u! -> ù u' -> ú ss -> ß SE -> § schismtracker-20180209/helptext/info-page000066400000000000000000000014531323741476300202650ustar00rootroot00000000000000= 788888888888889 = 4 Info Page 6 = 122222222222223 | | 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 schismtracker-20180209/helptext/instrument-list000066400000000000000000000036501323741476300216020ustar00rootroot00000000000000= 78888888888888888888889 = 4 Instrument List 6 = 12222222222222222222223 | | Instrument List Keys. | Enter Load new instrument | Ctrl-PgUp/PgDn Move instrument up/down (when not on list) | 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 : , (Comma) Toggle edit mask for current field | | Envelope Keys. | Enter Pick up/Drop current node | Insert Add node | Delete Delete node | Alt-Arrow Keys Move node (fast) : Alt-B Pre-Loop cut envelope : Alt-F Double envelope length : Alt-G Halve envelope length : Alt-E Resize envelope : Alt-Z Generate envelope from ADSR values | | Press Spacebar Play default note | Release Space Note off command schismtracker-20180209/helptext/message-editor000066400000000000000000000005121323741476300213230ustar00rootroot00000000000000= 78888888888888888889 = 4 Message Editor 6 = 12222222222222222223 | | Enter / ESC Edit message / finished editing : Ctrl-T Toggle extended ASCII font | | Editing Keys. | Ctrl-Y Delete line | Alt-C Clear message schismtracker-20180209/helptext/midi-output000066400000000000000000000121621323741476300206770ustar00rootroot00000000000000= 78888888888888889 = 4 MIDI Output 6 = 12222222222222223 | | 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 tick | 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 its 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 use the following SysEx messages internally: | | F0 F0 00 x F7 - Set the current filter cutoff point to be "x" | F0 F0 01 x F7 - Set the current filter resonance to be "x" | | 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 schismtracker-20180209/helptext/orderlist-pan000066400000000000000000000024521323741476300212030ustar00rootroot00000000000000= 7888888888888888888888888889 = 4 Order List and Panning 6 = 1222222222222222222222222223 | | Order Keys. | N Insert next pattern : Shift-N Copy current pattern to new pattern, and insert order | - 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 | | Alt-F11 Lock/unlock order list | Alt-R Sort order list | Alt-U Search for unused patterns : : Ctrl-B Link (diskwriter) this pattern to the current sample : Ctrl-O Copy (diskwriter) this pattern to the current sample : : C Continue to next position of current pattern : : Alt-Enter Save order list : Alt-Backspace Swap order list with saved order list | | Panning Keys. | L/M/R/S Set panning to Left/Middle/Right/Surround : Alt-L/M/R Pan all unmuted channels Left/Middle/Right : Alt-S/A Pan all unmuted channels Stereo/Amiga Stereo : Alt-Backslash Linear panning (left to right) : Alt-Slash Linear panning (right to left) schismtracker-20180209/helptext/orderlist-vol000066400000000000000000000017601323741476300212260ustar00rootroot00000000000000= 78888888888888888888888888888888889 = 4 Order List and Channel Volume 6 = 12222222222222222222222222222222223 | | Order Keys. | N Insert next pattern : Shift-N Copy current pattern to new pattern, and insert order | - 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 | | Alt-F11 Lock/unlock order list | Alt-R Sort order list | Alt-U Search for unused patterns : : Ctrl-B Link (diskwriter) this pattern to the current sample : Ctrl-O Copy (diskwriter) this pattern to the current sample : : C Continue to next position of current pattern : : Alt-Enter Save order list : Alt-Backspace Swap order list with saved order list schismtracker-20180209/helptext/pattern-editor000066400000000000000000000217601323741476300213640ustar00rootroot00000000000000= 788888888888888889 = 4 Pattern Edit 6 = 122222222222222223 | | 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 # S99 Toggle duck modulator (not implemented) | 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] : $xx Key off [Kxx] : &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 : Shift-` Note fade (~~~) | 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 : Grey / Decrease octave (also Alt-Home) : Grey * Increase octave (also Alt-End) | , (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) : Shift-A/F Move to previous/next note/instrument/volume/effect | | 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-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-1 - Ctrl-6 View current track in scheme 1-6 | Ctrl-Left/Right Move left/right between track view columns | ! L-Ctrl&Shift 1-4 Quick view scheme setup : Ctrl-Shift 1-6 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-Shift-Q Raise notes by an octave (*) | Alt-A Lower notes by a semitone (*) | Alt-Shift-A Lower notes by an octave (*) | 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-Y Swap block (*) | Alt-X Slide effect value (*) | 2*Alt-X Wipe all effect data (*) | | Ctrl-Ins/Del Roll block down/up | | Alt-C Copy block into clipboard : Shift-L Copy block to clipboard honoring current mute-settings | Alt-P Paste data from clipboard (*) | Alt-O Overwrite with data from clipboard (*) : 2*Alt-O Grow pattern to clipboard length | 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 : Scroll Lock Toggle playback tracing (also Ctrl-F) ! Ctrl-Z Change MIDI playback trigger ! Ctrl-Z Change MIDI playback trigger (also Ctrl-X) | Alt-Scroll Lock Toggle MIDI input schismtracker-20180209/helptext/sample-list000066400000000000000000000036001323741476300206460ustar00rootroot00000000000000= 7888888888888888889 = 4 Sample List 6 = 1222222222222222223 | | 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-Shift-D Downmix stereo sample to mono | 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-P Copy sample | 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-Y Text to sample data : Alt-Z Edit/create AdLib (FM) sample : Alt-Shift-Z Load predefined AdLib sample by MIDI patch number | | Alt-Ins Insert sample slot (updates pattern data) | Alt-Del Remove sample slot (updates pattern data) : Alt-Up/Down Swap sample with previous/next : : Alt-F9 Toggle current sample : Alt-F10 Solo current sample | | < > 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 schismtracker-20180209/icons/000077500000000000000000000000001323741476300157505ustar00rootroot00000000000000schismtracker-20180209/icons/appIcon.icns000066400000000000000000001465061323741476300202330ustar00rootroot00000000000000icnsFics#Hx?x?ics8]W]]]WW]]WW]]W2V]WVW]WWis32Au Z{*Ub}E aspuYi|twuO( \w\F%(-38TH/8.%4_gVL=E576'9i _OOBF>6: 2 ;*`TQGC=.;)$ &VaUNE>?&9(@emwo:aTID>X]{}a%ciontȘt` Opke*GVqpq}<VfbfnM}Vjky_L%_sj}OJ"00>.t{LM-A,(:&L]PS;M3=5#Agm WLU?K:8=>F#WNUCI>0B5' /CXPSDCA'B#>[XaZ.YOO DAWQnrtjT%Y`iu}nf_Q/=QUV*AIOi]_h5N[YYu}^?bI _oa^wtiU>"E_a{nI>$((+l)ioIC/7*$-c?VPJB834./jQNLA@9-6( l7QOKA<:'5$4IJPJ'QNH A\D4+1O+#@3 )^rHBGeHD:T?20+K*%'#59x#]tNONc>;9RB*,.L6,H  VU]xLEFe@73LM2-%A<%'!A/Ua:]zLDAbC>8GL*./?E2ie@\zJGEdA66EV/'/SQzwV8 \{JA@cJ;26Q5SymQ9)\zNG@\A;TzlYH7[wCCXsnf eu?QnWYurw_$$ 8Ezczv)& 3AQjz]LQw;!GMX\Sf]rW"*MWUwa|s!GV]gZF]nmLc4M`]lndj_9LQ:Yhz`V~xqNX%6vpUgx{}j=&Q6)f.if}qQMI!%BDRN mwR/)=W#! 8S>xno|U:VM.,2[+%&Z% .l' ofeS76RT-00[-$$ T, VI awG>\[75M\-,+Z9 QEI{j UfF@X^:6D`1.%OE**:N -m"WhGBWa89Ab/00LO! 2Z&]E ViEATd65>i:.'=Z'!Z- I~iViF?Ni?<8e;0-2[%#$!Q7 ;pUkHGRj::9d>(*0`1EM _DTmHBMn<51_J0,$W8#%1S'Eh|M:UoJAGm?<5YK(-+UD3iioR5UoHDIp=42VX-&@TDfydJ2TpG?CpE:0FV/Ift_K9-SpKEBm=7KpsfYLM?SmA@R}wuw [g6H[GUc^cQ&$ 8;[aQec)& 3?BJYXLADc6!>GGOanOIWN_rJ" GNNYRhzSi|t`!GNU]RBP^r}{s[>Grn.MXVbqbY^n~|qQ3-iuE'V_`WQp~ruohtbCB$ Yu]Nngj]lzxqlk\9&8/Frl(_|cfdndK>=#%!.62ksA bvueL0+3D%#"*=#]sZeoP;EE0-.E+%!?$Lrk# ee|`VM98CI/0-C,$%8(6mq= YoHANR97AM/-,B4 !79)aqW NaGBLS;7;O3/':<*) (:OpiObGDKU:O112;A!'>3#$I#9":Time>NhJCBZ@<7FC*--=:*OYjsn\E-NhHEE[=54EL.'0D>XnxrgUA/LiHAA[D;39H0C[q}zsfWH<0'MhKF@W=8Hb{|tjbYQM?LfCBOp|~zxw| S\|z6l8mk}@6է VIyڈ9 YL |; [O*}RTWoțe2ѰU( ٻnD ʥX5໐fB%٫zN,՜d6it32w$+];Hz>'+*(&%$E[`Q@@Wze-*(%$$&,Pb^K?FeU8SmE?!,*(&##)=XeZFAQroI:<>63Fn=%*(&$",I_eUCD^]B;@?:>A>;HmlF44>!*(&$"1 @\i^IEYzfG>AB=>WY<49S|=#(&$! (LciXGJeWB@CA>HgvL66Dfd)&$"$*1WjfQHSsN@DE@@V}3;S|(&$#  B`nbMI`ǃHCAIkf<"&$! !Ogm[KQls/{¸ RVmb&$! $4ZnjUK[{l:+\?4o%$!  FdrdPOgsQjK;B^: $! &Rkr_NWuhPNKMs b$! $7]rnYPb^OPRRMrmZ k #! HgwhTSntWPSTSRROc]GBQwi^L=7l :! (UovcR[yhTRTUTTSRRSqRDH]mfZJ>=F=E a# $8awr[TheSURTSTTSRRP\JFPntkbSFIRG52)2 " "JjzlWYwuD}SR[zjRTSRQML`~ni\MHQQ?$,+${ 9  )YszfVc_48kdifQTPMUrsmeWJOSI0+,&V`!$=c{v_XpL0H|yXQPPczpl`PNSP;!$,)7 %Loo[_~q@3ZhZfVNOXu~cZOSTE+ 03/8'+\v~iZj]8+V{PUPSfg {kqzfOWN7  *96(c`$=@gzc^wL3.-2üM[x[HFtocODEK( +1,(+'<TPrs`fjA100@kenQHTtyri]RSJL;yK(30% ,)+7V/_zm_rT9/6Rso\Rc_JKdtodWVZT?@Jx"(  &2<=5'262~:%074) *)!c S|hjjk eXeAznd`e_J3'#7BA{+ !$/:@:,!,7/s_#/76, &)&; 4Rrho{jk e|tjaecU<+%&'%5B,9A>2%(63Vw48/# %351]*Qggjjb cfdg]G2( ''$1B>t[@8)!$'>CJ,!+/71#pPqgfrjfukcR:-)* )(&&*8KHga!  "-89A=50 ",2.")($E2 PĺĹ zoa\[c0**,,+*)(+3?E?F@Sq! ",8>:.!06/xV#-30% ")'/ \$ OM~skhk]UJvi(-,,+*+2>GE:-(=@Iz#! #,8?=2%+70]n#.53'  )( z PÁzoiliZB4GFm(,+,11+)(''&$1B>s]A<1%5;:|[*! $6>4  OÁ{gpm`I945543211CTYF4-+;**)('&&*8LJhj#  &0=D8ik -ADFD0b 1 OÁÿxlebj=655665327AKPUN=*,+**('+5?FAGASt &'2;;0072I /DTVM10*#:[  OÀ ù}tptjaVa3876557?JQL@?LEK),*),3?GF?H}# ) '3<=4(!538&2EVWM8" (%( 0NwqrthQCQPf4767>JRPE:2/:LFT)++-4?HH@3)%29A=0!)4=?7+ 450}@4HXXM8" (&p0  N|trvo\F=:=QQ~j4=HQTK>62"07JFz])3>HKC7,'&'&&$4A;z@ *5?A9-" 15,qe !6KZYL8! '&"DZ  *?N¾|rwufOA==><=PQzvLVOD:54433214HFuuBMG;/))((''&&$2A:r[=;>UZ|E=75665433106NQp:2,+*)(''%2HGhs-& ';BKVL7! !04*w.  Mjgm@A ?>>DNT]Yv2776K5325=ILTP`*+,,+**)'&',7ACJESu (>GPPD= !*-74)RZ *Llkm?CB@@DMWZRFPQm4776546=GOPF9FIU/+,,+)(-8BGA6->>F# +@S_]E>8,x]$.2/&('#4 Llkm>ADLV\WMA<:LRe67=HPRI=50/BKN4+>/9CIC9-&$$:@<3 !-BV`^J5!-4-Xr)130& '&!.   LlknFT\[RF?= ;JT_9KG@)1:DJE;/(%%&%$5@;zA#/EXa]J6"*51@#-440% '&bY ,Llkr[WKB@??>>=;HT^RUQF;64433210=KCVCKG=1+'(&&%#3@9sU2H[b^J5$#422/'076/$ &  KlkoCBAK@?>=<>N^eM=7556543320/=QOb=4-*)*)(''&&%#-?@FPWW\\?576854225=GMVM|_&+,+**)(''%$#)6OWbF5%7=;ht(" ),%nX XLlks?CA@AFQYYQEGUUE6766547=IPND=ICvv&,,+**)'%%+9Nb]VOL#)39=B:I %660C iMlku>CGPY\TI@<:EVRK5658?IQOF;3/2HEi(,,+)('-;Qch^J5=>=;DVOT6@KRRI>521102HG[++*)0>Seh_J5(""5@:yC  &1:?=5* 22-~J&/20) %%z W MlkxVQFA@@?>>=;CVMgPTL@843A2101FHT.1@Vgj^J5)$$%$#3@8s\#*4>A>4*22*mi#+330' &%P Mlkv?BA8@?>=<?DNUW\Sd376+54331//6J][bI5+())(''&&%$"*ADU~A>2( (2091,675,"   &$ U Llkz?CA@AEOWZRGASOj47653119H]k_^NM'++**)(''%%'-7=L- %7::S0) &#` SMlk{>CGPX[TJA<;>RQn376544;J_mm^IAJDV(,+**('&*0;CGE:BA97&/5BA6xf )--:*QMlk|MZ]VMC>=>=<>RP|w255>Maon^J80-:JEyc',*))-5>GIE:0'"6?9yF!+3<=8-74-Sy !'352)U2Llk}SOFA@@?>>=<=QOy;Pdqo^I92001/8ICu|%,09BJJE:/($5"3@7tc &19?=7,#*20=  '..,"$(%n kLlk}?BBAA@?>>=;;PTzkp]J:3223211/4GDg9FMKD9.(&%&%%$"1@8]v$-6?@=4)'2105 '/1/)$$A),Llk?CBAA@?=<;M}'3;CA<1'!11+|] %,32/%  #$"+TLlk?CBA@>>GXjuk`Wj656+542214NQa:6$5448?IQRLAJJK8*+**)(''&%$&+4:KLDB".11D3761&   %#Ol'Klk?M]nwp^K@;::NS]=557?B9zI2>@@=<:LTZA=HQVTK@720.>JBO(+)*-6?FHE<2)#3?6tj&/799?90S  &4^(KlkZLB?@@?>>=<9IV[ZVTK@8428110.;K #+4:L@B?6,#'20,I  ":gIFW,*)(''&&%$#$(.CJF~^.) #20(re 7^BqJ/  KlkVa[RHA>=>=<:EVKc257=EMSRMC92.0FHR1*)('&%%(-7?FDCG>=<:DVKk=JRVSJ@820#/0FHK;)**('(-5=DHE>3*2=7Yy1NusN4 JlkAABAA@?>>=;9DWSzTRI?7324110//EIDG'*-2=DJHD90'#!->;K!,FrsP7 JlkBAA@?==>DLTa\{9754332110/.@IAT7CJKH@6-'$$#!'<<>0)BjuQ9 !%#!JlkCBB@@?BISY[WOWP~1605433211/.-?NLjIF;2+'%%&%%$##!%;=6>&?bvT<%"$(,+)&$" JlkBABGNW]\VLB<=RO{36,54321028@GNWO}r++)((''&&%%$##!"9=4uM<;;>QOx46(4337>HNSOGDLCt&**)(''&&%%$#".8CbxWA,+18<;8541/-*)' IlkX\RIB?>=<;=QOt5'8=FMSRND;3-7IDc)**)(''&&$# #5MryXC0/8@DC?=<:64405=IlkFBA@A@?>>=<;=QPk?CMSVRI@720//-4GFV,)*)('%"!$2Kn{YE45@GKKGECA?=987IlkEBBAA@?>>=;:==>CIQ^`eH:6544332110/.1FHI:$/E_|]J;>PW^[WSSQPLIHL0JlkGBB@@?CIRX\YQVVZA46654332110/--BD?d}^K?CVahfa\[YXVUSU-IlkGACIPX]]XNF>;NSWI466543321/,*.>Zx}_MBH_looifeba^ZXZ*IlkOW]_\TKB><;;:MTUR366542/-/=>=<;:JTQX35301:QkbQGNk}}xwurqknf"Ill}JBA@A@?>>=<;:HTOZ/9NfcQJRr~{xtpmHll|JBBAA@?>>=<:8EPIrcRJU{}{uGll|KABAA@?>=98;H`dTKU}Hll{KABA@=; F[r¶fSJTHkjvOVo¿øgTIUƷ Fp|ĸhTGOݿEŹgSAFBƁƺgP;:ȿCǻgO4(rɹCɾiL,*CiM% ж$L(ڿ$L11[X%$@gw-,*('Ow8%0,+*(4]T#0-+*(CkZ$1v"0-++(#*Q{uG R6*-+*(&6Ld42cS!-+*(&$#@OK=}xu5Ftu!/+*(%#!2GRI:5135/+9Zv3&*(&$",=QWJ;YzUv 1#&$! DY_PCH]x|f,{ùuGIk~[oP&$! $-N`]KCPixyl;*y rM7.\s'$!  Zh\KJ`z|dNGIJJIHEVzoP?mJJQj\HJJIGDCTo{dTMD96;;/(WWMi 0 &Ndl[OYtU25^Y\yZGJHEKboZQJ@8;=6% RWSXN!$!5XliVQdrE/BmiNHGHVraUPG=;>;-CXVJq $DapdTVod<1Q}[PZMGGNefID<>?5# 3\aVl.'(Qhq_T_{}T5*NkHLHJZvzZ|xbP]hU=B<+ 2fcU^ M$Q8[qmZVkpG2-,1twEQimO@?plZSJKZ\u1/0<_}Zot_H?IcuaWPG@?]u`i?'/-" ;XULu -V*TlucXf}S:/5LrfboSJWqTCDWtjZTLCBEA.@bY_X$.0(  %WVLe M$L6bvq^\qеtY|^RRQMOgdKBLfs`YQHBFE:))_`Ug#.2, PUSO pOGs]bzqYRURQ[tvWFGYv}h]UMFEGA0!![aOt' +30% AZ]O| -L&W̢fVUVSTehNFOiVSJFIF8(Lb[jI1*  >ggSfL;9gʌRWVT[rz[JI]z|i\ykHIJ@/"MpgfX #--0\XQUpI-xg[jʐRVdmRIRlrc\TYfhZ5( '25EgbXb$-1*FVTI+Lxc^a^Jx͑nBqKL`}|i`XPKMwxg\ %197-']aPv #.3." 8WUImKJpb_bba^RtίBplrd]TMMNF7`k]e! $/9;3&VaUk1"-41&  "UTL]oKpacb a]Wa6{iaYQNPL>/%"Pm[j(#.8=7* HbWeO!-54* MTUI+Jiagpb a]6rd]UPQPF5)$%&"Jlbl7*7>;/$=a^\b15-! CadRr K*Jt|^_a`Z}TSQSL=.''& !AlfjQ=6( 2io[v( 'Ae^Nb oIg`u|_iqgYdiZQD4+( '&%%(?sshT  +5=jiUn*  ).+!SSRM * I yjcZUp{xV-*:)('&)2HNI7) ATRGr m eFwneabbZHCrw{[3556rxy^3=<;9=tzsC<64543211/.9rzqv91*)('& $#"?qphb,% =ejZyA9* >_aSc&  F~cad?@?=<Hppba"/Fstbm3 &A@>>AKTXPDkyp}3665425;EMMD9esir-*)' ,7@D?30cjZl"$2?GE>dbThM #+.,#"QSQFk Ewedd=@CISYUK@:8dzm56;ENOG<3.-Zudz2*).7@EA7+%!$ZkXs.'5AIF9+M_Y[]%./-# ?TRFk%   EwedeFSZYPD=;8_{m}7;ENQK@6100/-Sua;)/8BGC9-'$5#!Nkal9(7DIG:,C`^Ou!)01-" -TQI[F  hEwed~jYUIA>==<<;8Z|p~OSND9422110/,Puc~N@IE;0)%%&&%$# HlahI*9FLH:,5_]Op)%-43,! RQPF k  EwedgB@? >=<;:;_zI<6432110.,NzvwY;2+('('&%$#=ifceCLH;-!%[]UjN-53+"  DRPEq $ -Ewedh>A@??=<>DNThs<4655432003;DY~usT')*)(('&$#"%:r|li8,! "^hge`'! 6VYO` E =Fwedi>A?>@EOVWOAW}mA4655325:7T~kF4547=HNME91-7lnjr(**)'&&*4BNQK<1_iUt0!+3993&F`]Nv# ',,-VYTDy # kFwec}jKW[ULA=;<;8S~jM5?IPOG;30//.5kqhr*)((,6DPRK=/%! Pj`k;%/6<:2(-]]Sj>  $,.-' =RQDfD Fwec|nUOD?>>=<<;8O}j_NQI?6210/.2irgs-.8FQTL>/'#" IkaiN!(2;=:1'!"[]SdV !(//-$ )SPLPi Fwec}l?@@??>=<;::Qva>84P32110/..asd}KSUL>0'$%&%$##Ajb_b(6>?<1'Q^[Th&-20,! OPPD" -Ewec}n>A@??=<=BKR]u}Z265543211/-.2_nS>1*&''&%$#"4jpcj=;0%$E^\Ly,)242)  BRQCjC -Ewec}o>A?>@DMTWODFutz^3655431/05?MVjj}E&))(('&$#%+4Bsx`v*RsjtL'*)((&%&).9AEB;gkUt1#+?mm`jT  VY]G!=Ewec{pLW[TKA=;<;9@rwxi145:DRZZN@4/)JskrW&*(''+3=EFC9/%!Si_k= *18942_^ZYa  *Zb`IpB>Dwec{rRND?>==<<;9?qwvu8GT][OA50/./,Bqkpj$*/7@GGB8.&""!Kj`iS$.6;:4*!K\\My $,,)DWQE_ h kCvec|q>@@??>=<<;8A@??>=;::@L~z?944332110/.,8oymvGA7-(%%&&%$##"7gg[h'08@>9.%2]\RjL #*//+" NOPBx BDvec{w>A@?><=CN[b\z}n56554320 39CLxmu0)('('&-%$##!-fo]{A>7-$$Z[V\\!(01/(  ?QPCd gDvec{z>@??FP^c_RDFNOJ@ksb4()(('& %#"$*2=pwau; T\_Rw/43.$   .QOJNCvecz{>HS_e`TE=9i{l:4459BKPOI?6/,^s^?')(('% (.8?EB<_m_k?Qkm\t6    QPN=},ADvecy~]f`TF?<;<;:8d{m>50.,Tsa{G')',4=DGC:0' Ki_hY$,35QlfXlE    ;ABU}gCvecy~QG@>=<<;97^~rVTQH>620//.+PsisM&+0:AHFB7/&"! Fjb\d",2984* 0\[SbX   #B_u>Cvecz=@@??>=;;:>CfxQ=74332110//.+Lrjq`:FJG?5-'$#"!:hgZk!)18;82(Y[[Rp   :]s_FBvecy>A@?>==BIQWUgmL255432110/.-,LyvswB=4+'%%&%$##"!.dhT})(18=;7-%O][Lx!   5Yn>~qY,Cvecx?@?@FMUZYQG;96U}iU3553226=FMOLABmpdr*('&%$#"!"&/eulnQ-'7][RfS -Mh?sY;&  BvecxU^YPF@<;<;:7S}iY246;CKPOJ@70+4jqev.('&&%$#&,5=<<;:7R}k}_=HOTPH>60. ,2hr`7'('&%',2;BFB<2&IiaZe/Viuu\@+ Bvecw??@??>=<<;96O~w|nRPF>6202//.-/er]@&(,1:AGEB7.&" >hfYo';^tv\A. 2BvecwA@@??><;;=BH^}m864332110//.-,[r`yL5@GHE=4+%"!!1egS*%9Yrw_B0 "%#!KBuecvA@@>>=AGPVXSV|xys155432110//-,+Uwqu_FD91)&%$$##"!!*bgRr6"6Rpx`E3$"%),+)&$" ,Bvecv@?AFMUZZSJA9Cuwv~35543210/.16>Dbxud*)'&%$##"!!%^h\gB5LmyaF6'&-1430-+*(&##-BvecvHRY]ZRI@<:98Aswt45542126?>=<<;:9>qxn=AKQSOG>60.--*=<<;98=r}nURND<400/.-+6kqcy/''$"$-@Xw}gN?6:IPUSPLJIFDB?<5AvedtD@@??><;;Ss~hPA9>QY^[WSSQPLIHL@vedsE@@>&AGPUYVPv~k?45432110//.-+/gnVsYniPD?>=<<;:97^}iS/8H\zmWIHRu~{xtpm?vedqG@@??>=<<;:95YxaxftoXJIU~}{u@vedpH@@??>=<;869Cb|qXJIW@vedoH@@?><9;CUkqYLIWAvedoH>==CTgr[LJV@vdblKQes[LGUƷ ?thqu\KDOݿ=w\JAF<x\H;>ȿ;x\G4,ɹ;z]F,?={]E% ж E%ڿ?)*Kj{I%$6Vrwutc-,*(!Bcwwtsr/%0,+*(,OmzvtuwpirvF$0-+*(9ZuzvuvwkK!*rta 1-++(#$Egzzwwxvb= Ersq.+-+*(&.Bq~zwyylT.,SnvrrvE"-+*(&$6DB7j|{weK9FA406ew\C7522AYdT>78BYpyyt_gwnbruD!-*(%$$& 9GG:26KbbO<7Uf]F;8AWn{{yiN5,,-(&2Klbqrp,(*(&$",6FLB67G_fVA9ZeW8(]suC+(&$"$.$=LK>6:BSm||nU;/.1/,4KecK2&'-Zss_,(&$"10EPI;8E[i^F=BSihN+8ALHBU}~}}||}x>A[qkN^zwwvvxwtsr qqptC&$! $)DUSE?I]hy\FB5&#FWp~~}}|{qpaD1*Ozwwvvuutsr qqpr^'$!  5LYPDBQfiXBcaKOGHgz~~}}|~f9/2D_txwwvvuutsr qqpqo*"$! ?SYMBG[kfQB@?acGLr{~~~}}| ~f:Rly|zxwwvvuvtsutr qqpptB$! $-IXWHCOdnbKBBDC?Xf?]lRF{~~}}|{yu}|yxwwvvwvkXC``gsrrqqppr]%! ! 9P]TFEWlm[GCDEDDC@MybqaG:7?y~~}}|{{|zzyxwyxsaN>6.12Ltrrqqppqn)!! #DW^QFK_ymSEDFFEEDDCCqoWA8:G^u~~~}}|{zzyyz{wjVD;4.+8A;7qrrqqpsA# $-K]\MGTlzPEFDEDDCAH=;ASl}~~}}|{zz{zs_K@92.381/4-/hsrqqpq]# !Kax~~}}|{{|}yhTE>81288,//)Uurqqpn(  !GZbUJQh{rO24UQSkQCEBADWo~~}}|}|q^KB<538;4$-/+@srqqp osA!$"/Ob`QL[r|gC0?b}xy]HCBCNd{~~}nRE@;57;9,&/-0nrqqp oq] %>Xf]OPdzw\;3LptTJRFBBHZr~}ptX<97<<2"  361Xuqqp opn'%J_fXNXo~pO6-I|`CFBERg~jOk#yfSCQXJ9>9*';8-Htqqp oos@$=4SfdUQbx|eE40/2ijAJ]vx`H;:b~o\LD>9=>c8" %+%*.-3qp ooq\SB\k`SWk{uY=322:=BIC?/.;5LN#,-& ..(Otqpor>$W3YkiZWh{zi]_dZKUuoWLMLHJ\urYE?F[rvcRJD?=CB8(!9:>Z!+/) ,.+6qqpop\JAhX]quc]ce`]j}|fRMOMLShiOBBQh}lZNHB?BE?0"8:5c%*1.$  $141frpom&\$Ou|p`afge`qt]POPNO]tv^HAI^vzJFBBFC7(1;8YB/)*;;.Rtponr?86^~`chhefcgyMRPOTglSDDTk~kZQi]CFG>.#4F@SM!*+%30*>rpo nq[*lv`VycigKRhcgyMQ\rzbKDL`yubTNHFIUS5)! &/45@:AU"*-('.,.mqppo nol%Gn^YZY}dheAMhdf~d~dFGWnl[RKHGFQONS"" %/74+ 9:6e "+/+  !/-'Xsppo nnr<Eh\Z]\[X~ehgCLe\iBdb{tcUPJGJLD5AEEZ#""  $.670%5:4[.!*1.% .-'Grppo nnpZEi[]\]]\[X~figCLj}C~l\SMJJNJ=/'$:GA](!!#,5:4) /;4TF *10' )-,1opponk#Dc[`h]]\[W~e`breVPLJNND5+&&'%7GA\5*5;9/$*:7FV/2*  '680]rpo nnmq<*DlxrZZ[ZV|rz ~o}KLNQJ=ZE' $5:1WroonnmnY A sd\Vi[ZQB85 43310DWUm@3-+=**)('&&)7NMUQ$  $,=FFNXMs7*,+**('*2;A>IFIW %.44,2:6>Z ".893%0-*0onnmlp; Are^[[^Z_XmS5876557=FKG=@PHpA+,*)+2;BA8-)ADB]" +%/670%#884e #0:93' #,+(]qonnm lnY 0Aykc\\__WHATUkV6767=FLKB82/;PKgG*++-2;CC<1)%b+,!'0792(783Y1&2<;4' ,+%Lronnm lmj! @tf_]^b]PD=;>TVjY6=><=TVgbIPKA954433215LK`^?GB8/*))(''&&$4E?XH8<6,#-84AV(6??5) &++*cpnnm llnX @mxi\PE@$?>=;>W^jjC<75665433106PT_j82,++**)(''%$3JJUW+% *=A@h=6* #36.Prnnm llmj  @t][v^ABBAA@>>CKP^]fk47765324;DHUTUg++,,+**)'&'+5=?KIJW "/8II?]. $*86-;pnnmlo9  V@m^]v^ACB@@BJRUNDQV_r6776556;DJJC8INPg.+,,+*))-5>A<3,ACB_" $1>DB5>:1WD (+(!++(,konmlmW  S@n^]u_@BCJRWSJA<:NW[t7667ED8*/73FP#+-*! ",*$Wqnmlkj  @n^]u`FRXWOE?=>=;KXYr9;DLOI@72H10@PHt9*07@EA8-(&%&%$7D?]6!(5AFD8+,868e&,-*! ,*%Epnmlko9  -?n^]tdXSIB@??>>=;JXYsLPLC:54433210?PGnI?FB;0+('&&%#5E>XB*7CHE9+!&762_%#+0.(  +*(.nnml kmV  ?n^]taDBA5@?>=<=Nb`uH<7566543320/?TRhR;2,*)*)(''&&%#0DAPZAIF9,!662YD*20)  %+)&]pml kki ?n^]tbACBAA?>?EMRW`Yt=676"54224:CKXQcN(+,+**)(''&%$'2LRR_7," 9@?PS% ,/)Kqml kkn8 X?n^]tcACB@AENTTMCIYUw@6766546;DJI@IHPMEb" &.3>` #7734nml kkmV k@n^]td@BFNTWPG?<:GZS|E6657=FLJC:3/3LKXf*,,+*)'+3AKNH;/?C=>=;FZR}J7>GMME<521103KMRf+A*-6CMOI[7 $-486/'!662Y6")+)# *)!Qqmml kkjn7 @n^]sgSNFA@@?>>=;DYPzYLOG?7432101JNOg-/8DOQJ=/(%+#5E>XF"(08:7/'561PJ&--*" *)$:omml kkjlV C?n^]seABBAA@?>=<>CKQT_WoU47654332003IZTwN=2+)(''&&%$"+EHK^:8/&+764f((/0.&  #*("Vpml kkjjn6 ?n^]sgACB@ADLRUNEBVTkY576;53226>LTT[MmB)++**)(''%%&+4:NOFg) (;=;[?,% *("Boml kkjjlU X@n^]rh@CFNTWPH@<;>UVj[5765548AMVUL>AOIdH),+**('&*/7>B?7EE:e/#+2DC:VH ,/1/lmlkkjgX?n^]qiLUYSKB>=>=<>UVhb366:DPWWM@50.n^]qjPMEA@@?>>=<=TVfk9FRYXNA621/9MI_`(,/6>ED@6-'$"6D=XK%,3761( .756g "((%&,( JplkkjkT>n^]qj@BBAA@?>>=;LW#*1896/'*761]) !(*($ ('$0nlkkjig->n^]ql@CBAA@?=<CMY_Y]Z]v776'5432149BFVXSi1+*)*)(''&&%%#!(BFDk=;4,#661GO &,-,&  !('PollkkjilT>n^]qp@BAAGP[`\QE=RWYt:6%5447=EKLG>LNIr4*+**)(''&%$&*17LNDe7"266:e,0.*"  (&"9nllkkjiig>n^]qq@IS]c]RF?;NPXWs;557;AHMLF>61.ENEv=*+**)'&&(.6=B?8AE=[:4@Bn^]ot[d^SG@>=>=<:NYWs>620.ANEkC)+)*,2;BC@80'"5C>=<9KZXwRQOG>74232110.>NHdH(-18?ED@6.'$#"!3C=JX"*0762) #65/MK 6M_lomllkkji!f>m^]px?BBAA@?>=5,(%#$#!-BAC^"(/5750'553;_ 2L_oprommlkkj#ijko8>m^]o{ACBA@??BHPUTV_TI46M5433210//>SQbjA;4,('&'&&%%$#"(@B=m(!'/5:85+#/643e -J[nrsqonnmlk%lmnf[G#=m^]o}ABABFLSWVNG>HYQN56,5432138@FKWRXi+*)*)(''&&%%$#!%?B;d?9;81)!*64/Z5  *FYmrurpponnm=lmnnog\H. =m^]o~CHPWZWNG@<;:GZP}Q5665447=EJMI@n^]oS[WOGA>=>=<:GZOwT457;BHNLH?72.1JLLj/*)('&%%',3:@=BI@XS3/.9KUguwvtsrqqpopqqrj^J3"  >n^]oJGB@?>>=<:FZPpY=GMQNG>620/0JLHu6*('(,2:?C?:1'4B>=;9DZWmePME>7425110//HMDv=(*-19?DC?6.'#".B@Bb %5QcuwzxvuutsrsttumaM7( BIRc^md9754332110/.CMCjG6>DFC;3,'$##!(@A;n)#3Mat{||xwwvvuutuvvocN8)!"%#!;n^]nCBB@@?BGOTVRMZVii2605433211/.-AQOeWDA91*'&%&%%$##!%@A7b3"2H`q|~}{yxwwvvuvvwxocO;-!"&),+)&$" UUgr46054331027=CMYSdZ+*)((''&&%%$##!#=A:X<1D^n}|{{zzyxwxxyyqdP=/&'.2430-+*(&##;m^]mHRWZXPHA><;;>UUdx56(4336=<;>UUby6557Whx~~}}|{{||}}uhT?4-/;CEC?=<:64405<:m^]lEBA@A@?>>=<;=TU]y>=;:AGM_b]wC96544332110/.1ILGx5&-;LfuzkXE;9@S[`[WSSQPLIHL1;m^]kFBB@@?BGNSVTMXYUx>56654332110/..GI@fQbt{m\H=:DZejfa\[YXVUSU.;m_]jFACGMTWWSJD>;QXT}C5665433210.,.9Pbq|}n[I@?Ianqoifeba^ZXZH<;;:OXSI4665421/07H[r~o]J@BMjx{sokjhgcf_';m_]jTVNGB?>=>=<;:MYQM454227GXp~p^KBCPp}xwurqknf":m_]iGBA@A@?>>=<;:KYPN18EUm|s`LCFRw~{xtpm9m_^iHBBAA@?>>=<;9HULm]i|tbNEGU}{u9m_^hHBBAA@?>=;9:CVjyucOEIW:m_^hIBBA@><=CRczwdPEHW9l_^gH@??DQaxxfRFGV9l^\eKP_vzgREGUƷ 8lbiy{gSEBRݿ6~jSD?J 6kTB8>ȿ7lTB1,ɹ6nU?)U6pU@" ж@%ڿt8mk@#+E$"fc=2,QjHɿƤhչ2/뻾kKŽȥkֻ21mMǿʧn׽22n Oͨpؿ35n Rϩr37o Uѫu39o WӬ!w3;p Yծ#z35%ҩoP/()-48:;7. ϥiH& %,/22.'͡cA#&)*'! ʝ]9 ! ǙV1   ǗQ*  Q* schismtracker-20180209/icons/it_logo.png000066400000000000000000000024511323741476300201140ustar00rootroot00000000000000PNG  IHDR"2֙bKGD pHYs  tIME 4߂< IDATxQ0 L:;z88ʀ>xb&#'߾^Hdid'\^y ^EM 7߮>/x80ř{ HcbޓPa=Z-XgֈI|v@47 Qp6Nwp Xįd߿gU'}1\[N5G0I4O5|~pcnA(:k9=\N 9:%'@L60hX2-S6Be ^cZX[߮=G8@dIHh(ǎ!sFrAby,#gGΆZM9A2@fQ Ӛ}zdˮKH*;X4bV[e@5EY#k&oIvFb2;L`{9*j -2)Jh@"$!N7M$+d횒=E({ycG` K H\U iNir\e;SCKΚzBQ^djyQD hMK{g [YUKd( uNhǴΏZ1's/i&gD!Ŗ R[OkEȻ*f/Zا!m,幦$OD-:8B4ȵ"d-25s᜗7VAihwZdSH3 }(g[Ƹw&$-yvZzg˅y&#+]{PBKD(b׈ƮVrȯBxDQZo'1Y3^Bf{k#fmK \,Z"Ic1 s5Â$!KnQAZhEH3ժ##")4Pz5"z+e;Ė--!Θ [<L+&]\JYfK&YSeDϞD Qi}-3 qljaYj>f5m-ZE&p_d?&Q"i=V׹VK"Эo(ɕiIQ,L(*=wm PDKk{RWK8E͕e||ˁy>!miNF:G׻fZt\4~r!K9A˗~BkwƎQwH(YMP@ ێȃ#!s  DdC Di!"3KEIENDB`schismtracker-20180209/icons/moduleIcon.icns000066400000000000000000001613751323741476300207410ustar00rootroot00000000000000icnsics#H??????????????????????????is32 Ƽ żѱ о˶ ׽ҥp{٣ğΛ˧ٺԶҥͻТȦ򵮫򩨭ɧ ļ ůҲ ˷ ӧ۸ԴѶвŸÿڵݹѾﻹɼ¹ɪ Ƽ Žұ о˶ ׽ҥq|٤ŠϜ˧ٻշҦμѣȨ򶯫󪩭ɧs8mkghhhhi@KfLfLeL>LiLgLgLgLhLhLhLgNj7K ICN#il32 ɾBǝۢܕ0ѭ}˄uǼĩѶʷ<ͻ|w9ˮz_`̸h`{?ɰ3ᰣ⿑7ڞ}֨nh;߿ɫ:ӿʤ鰢Ψ:z▀d?ͻѵ>µ>ĎƘ╆죓w>µ³ʮ¢<ŪֵƗ=ÿᓅrmC˷ٸAܳC tpDӫ@٤@b]@ۯEĴ5UUUɾBѧݱܧο0Ѻ˒º¶Ūей<Ҿy9ˮ̽?ͷ3ͮ7⾩;οӺ:ү鿭е:ý噊㤒x?Ͼӹ>Ŀȹ>ݴӺ⺭¶>¶ij<ɾҳ=߷ʣCŸƱAƶC ѥD@௡@wr@߷Eƀó5UUUɾBǝۢܖ0Ѯ}˄vǽĩѶ˷<ͼ|w9ˮ{aa̸jb{?ɰ3ⲥ7ڠթpj;ʫ:ˤ鱢Ω:z◁f?ͻѶ>ö>Őƚ◇줔y>¶´ʯ£<ūֶǘ=ᕇupC˸ٹAܴC vrDԭ@٥@c^@۰EĴ5UUUl8mk*<<<<<<<<<<<;2 (778998999;:9:987777+ 36777667777677666796' ich#Hih32ǃU^UROGADHƾ؀ƼkփڝhvWiԁe~ƺx˟dlƶƌǁüʾλ ļƱ!ɵā!ƺɅ"߼ʵӢ#tkgmuyƲe`i]`%÷ia\^w%ڶΩqshaU ր̿%%Ƌ{Љxl3¥xyugo3%ꐟtϲȇ}m3%θԾ3%ꬒȔŤ͛v3%LjoݼktZɪ3%ܱßcЂ|mi^ɪ3%ҞæĦ3%ʾȫ3%⦯轋ᝲӰ3%rÿ朁ʙ{sڱ3%‹uߒrҭ3%ɽٽƮҾɺ˫3%ӴɯĬ߮ά3§ܡ} ӈsq׳3±y ykuԱ3%ɑ۫ʬ3%Źɬ3ӳ3%݋u|sܶ3%{nu׳3%ì̭3˻˭3%n~cֱ3  qn\hٱ3%ه~hа3  ͸Բ3̀ ´U3UUUU^UROGADHځր ƾƼۘۮ}m{Փzǻ˦u}ŽʖǁĽʾμ ļƱ!ɵā!ʺɂ"׸#ukmoxyƲ`÷%λU%IJ%ʼ%ֳװ3%ԧ3%뷼Ҭ3%Ͻô3%뻢եͳҬ3%òѕ訄ȔnȪ3%˩ȯyՓ}ztɪ3%ӥéĦ3̀ȫ3%ҳ±ƳҰ3%Ѯ澮ܻҨױ3%ղΰ߿淮åѭ3%ʸƽ˫3%˼»ά3%аܿݰճ3߹ ⨪ұ3%Գĵɬ3%÷ɬ3ʿӳ3%趥ڶ3%窭Գ3%ӿ˭3˭3%윁vֱ3 冂p}ر3%ޔyа3  ͹Բ3̀ ´U3UUUU^UROGADHƾƼlքڝiwXjԂf~ƺy˟dlƶƍǁļʾλ ļƱ!ɵā!ƺɅ"߽ʵԣ#tkgmvyƲgbj_`%÷kd^`x%۷ΪsujcU%%%Ǎ}ыzn3æz|wjp3%꒡vϳɉ~o3%ιԾ3%ꭓȕƥΛw3%ȉpݽlt[ɪ3%ܲàdЃ}nj_ɪ3%ҟĦĦ3%ʾȫ3%⨰辍៳Ӱ3%t枃˛}vٱ3%Œxtҭ3%ɽپǯҾɺ˫3%ӴʰĬ߯ά3ĩܢ ԉus׳3ò| {mwԱ3%ɒ۬ʬ3%Źɬ3ӳ3%ݍw}uܶ3%}pw׳3%ĭ̭3˻˭3%pdֱ3 rp\iر3%ڇ~iа3  ͸Բ3̀ ´U3UUUh8mk    TD mU uRwSwSwRwRwSwRwRwRw5w www w w w!w!w!w w!w!w!w!w!w"w#w#w!w!w!w!w!w!w w w w wwx aߞ #@RUVWWVWWVVVVWXWVVVWWWVVUUVY[ZVK/ it32o߂݀ ր߂ڀ¿ހہ ҀÿZ߀݀ځ¿%ں܀ڀ ȫԾр¾-}߀ ޭ\7Bځ Ѣ>?ѧww$׬))˭7ڵ؀ >8@̀'¥/ځ0>հk[ymĚ@¾ȿ$qhTrپֹjfkAp%Ot.lNȺA»ʾ$?ۡ67vlmyeGxϯK{KuJµ@ÿʾ$@Ǧtm ϐp'ϲN˼Y=HdZ?ɻʾA~F:}ʚhhʀ 1x?ʣ˞ʽEDӂCa/P4GC@H5+5q9964ʽFǪȣλɯʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽׁ ۀƽʽ䲠Ⱡ 㲠௝܀ޮרŃʽ$ZZYYYY YYXXހ݀WWUTƿ~uk> ʾ$ρހ#ֿzrcaeccdZ\losɽނ%~wrmkiijkmpruy~ނĻ~yvsr twz|~߀&Ľ}{ujjw|}wnp~,߁%ǿmiDDjmiFGn#ýv9zl4ws9wj4٥(bZ~i3__Wxf3`+½WmDgUTfCbS&XuY{qUTlSogRäހڀڂ-ĹcQYja]OU{a\赢絢-浡ᰝˡŃ2Jv1|Hw[[ZZZZZZZZ%YYXXUT\YV\USQUсЁ&Ǥ ہĽ ߀ƿ݀܁»õа݀å|[U٦[ZـѠXWUT حߠäS`GӓWlȯ"DIcZʤ!ŃXށߋ 6E涠뷠"I| 3Ȥ+\L{dށMIEOϽ:/9}@@@@*X&HDAIȤށݳӁӄܯȤހހɤ ߀ʤ ƀ ͤ߿ӳζű׳ŵϤwWaځު]\ԧV^wommZYҤ JK"ԵWDZD;]W_jtğ?C:Ԥ5ҶP?Ǣ]漅y7Ψ`ԫ=ݠ?ͨ|s6֤oÀ_mPlœ`Ґgѱݠ?gO|hդ3WV΋zSܶx7ʦ_湁u7{}r6פ2ٔhƾaݵkPl_kOhhN{hؤ3oΌޫ|޻gih_gheceeפ7S⦠̲Ir]gvϿ_s]g}rlZbvpդ2Dǐo2S]꺨ʀʁ3R2PԤ2Ksezfc`gVM;V]]]]\\fb_f`][bҤἺ仸ѤФ½ҤſӤ صκ֤  ـ 殫^\˚ZYפ  δD;D;٤ Ćy7~s7٤ nPijO}iؤhkgdff֤  t^htn[cxrҤïπ­­̂ ̂3S2QФ__^^^^^^^^ ^^^^]]gd`gb^\bͤ߁翹žˤ ýʤ ľʤڶ̸ˤ 篣DDǗBBͤ ϱ##Ϥ wepp`zoϤX3vUTw2oSϤSTQP|PxPϤ cDPna]AMh_ΤŪéɀ©Ɂê¨ȁêǁ 7 5̤DDCCCC CCCC CCCCBBOJGPKGDLʤށށށ݁鹵ſɤſʤˤ¿Ϥ߂݀ ր߂܀Հ¿ހہ ҀÿZ߀݀ځ¿%܀ڀ р¾-̘߀ iEPځ ơLRď$96Eں؀͹-"NIǸ'պ?)ځ0ϦO}jōyŧN¾ȿ$΁yi~ͅxyS%`=\ßO»ʾ$?FE͇}~}#Y^YX¾Nÿʾ$@ϙ̌4Ɩ\qKVuhM½ʾAώTKʰ%uuʙ?R«˽ʽERӍQp=^BVRNVC9!C|HGDBʽFǯȨξɲʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽ݁ ۀƽʽ́ Ȁ܀ƹʽ$䓓ᒒ ᑑޑހ݀ܐԎƿ~uow ʾ$؁ހ#zrlieccdjllosɽ ނ%~wrmkiijkmpruy~ނĻ~yvsr twz|~߀&Ľ}{|},߁%ǿ}}#ýywyw٥?ww+½&äрсЁ-˿Ūxw锔哓㓓 哓㓓%㒒ޑύƐ ف؁&Ǵ ہĽ %ƿ݀܁»݀þۨʾـĶ ~ȃǾ}ʀÂ}{(Ԃʪǯ{ͧë{y)ךϛNJ͚ʼn)١Κϙ *ѪҠարր*lj׳xȿܷyx 디ꔔ암锔{$ϕԐٖ߁ ݂,ɷ)Ŀ )¿» ߁ ܀ ہJSonpo ҮRXК.zHQuhgeKQäanG֞fy/Ƽ#SMU^i)."Ƥ)%DՆqј׵Tրպ'*ƺzgvǤ]ڀISfAdҩTטZ*_>]ʤUe܏Ήud`_ϨTޓdJ*jqs^ZXɤ8C⦞5yP]oʦTCZ*ýpMWthʤ0טfށߤCXԼּ1Z*AȤ9kZrށ\XS]ϿH<#GNNNN8g3)WSOWȤށݸցքݵȤހހɤ ߀ ʤ ƀ ͤ2Ϥ׻եށĕլãҤ ؊"Ґ΄}̓ѝĀ}Ԥ5ҍĖְ϶{п̀ŀ¦{֤۴ׯԠԌη⺛ƀդ3Ւ̾ڲثԙ՝̶綠繐ŀ—Ԥzx޾ڱӺڥ˨ɴҗĀĞӤ8߼ƀʩ޻x̍؀̗ÀƮxҤ2纖٦ܙƎ}镕͈рФ3Ф2Ф3ФӀ¿ҤۯȀ!ƹźԤ֠ԸȄ~Ǔƃ¹}}֤2Ӏҍϭ̴{̻ҬDZ{{פ2齝ԖϞыʵԞ̋ؤ3ڳβҜʴٝפď·ހ٥ʨȴ㧕ȧդ2ٸx̍ڀځyʌxԤ2҉岧ᛗō}ꕔ񕕂蛗’Ҥ!ѤФ½ҤſӤ ĺ֤  ـ ŕϾפ  ф~Ŀ}٤ ݲж{{٤ ܢՌؤߟ֤*訕̩Ҥ߀܂ ݂y΍xФ 훘ƕͤ žˤ ýʤ ľʤ ¸ˤ ӮSX̻QUͤ 0Ĺ#0#Ϥ ߉sЗn|ϤiBie@aϤfbdc^^Ϥ |R^rvO[ymΤπρ΁́EB̤RQQQQQ QQQQ QQQQPP_YU^ZVRZʤ鿻ſɤſʤˤ¿Ϥ߂݀ ր߁ ׀Ԁހہ ҀÿZ߀݀ځ¿%ڻ܀ڀ ȭԾр¾-~߀ ޮ\7Bځ ѣ>@ѩxw$ح*)̯7ڶ؀ ?9À'æ0ځ0?ձl\zmĚA¾ȿ$riUrٿֺlglBq%Pv/nOȺB»ʾ$?ۣ77wmnzfHxϰL|LwK¶Aÿʾ$@ȧun ϐq(ϳO˽Z=Ie[@ɻʾAG:~ʛhiʁ 2z@ʥˠʽEEӂCb0Q5HCAI6,6q::75ʽFǪȣλɯʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽׁ ۀƽʽ䳢ⳡ 㳡౟܀ޯתƃʽ$\\[[[[ [[Z[ހ݀ZZXVƿ~ukA ʾ$ρހ#zrdbeccdZ\losɽ ނ%~wrmkiijkmpruy~ ނĻ~yvsr twz|~ހ$Ľ}{ullw|}xoq~,߁%ǿokGHjokIIo#ýw⯣DE巳ղn;CfYYW=?äTaGԔXmɰ#E=HФP\"Ƥڬ%7udw֩GրǡۡơiZsjǤUׇOFV3wWʐGǂMͰ٠Oq0jOɤUXvvޭ}iQSRǏG{W<]dgLwM~sKɤ86❕̯(aCOobG6JòY?Id[ʤ"ƄYށߍ 6F渡빡#J~ 4Ȥ,]M|eށNIFOϽ;/:~AAAA+Y'IEBIȤށݳӁӄܯȤހހɤ ߀ʤ ƀ ͤ2ԵηƲ״ŶϤzZcځެ__ԩZ`yoop\\Ҥ NN#նZȳG=_YalvġBE=Ԥ5ӷSBȣ_澇{:ΪaխAݢBͪ~t:֤qĂapSnŖaғiҲݢBiQjդ3YZύ}?fimkÓa݋qYBu{|eggԤڸ94ަՆLmu`jxaVdBɹn\dztӤ8Ùg\۬@ޖ5U_麩ɀFdBƊ5TҤ2gd|RpWgdbhXO=X____NqIBd_^dФ3׼ܹڄ߹Ф2Ф3Фͷ΀˯˴ŧŶҤ6Ĉ[aԴssqqӦ__ϥգ^]][ԤnyϝoH=_Ya¬F=F=֤2ݳBVܷz:ʧa溄x:}~t:פ2ږkǿcݶnSn“anRjjQ}jؤ3rϏޭ}޼ilj’aikgeggפU⨡ͳLހu`ixau`iuo\exrդ2FȒr5V`껪ʀʁ6U5TԤ2Oug|iebiXO>X````__idbhc_]dҤύ伹ѤФ½ҤſӤ طκ֤  ـ 毬`_˜\\פ  ϶G=F=٤ ň{:u:٤ pSklQkؤkmhghh֤ v`jvp]fztҤŰïπïï͂ ï®͂­6V5TФaa`a`a`a`a `a`a``jebid`^eͤ翺žˤ ýʤ ľʤڷ̸ˤ 豣EEǙCCͤ г$$Ϥ xfqp`{pϤY4wVUx2pTϤTURQ}PyPϤ dDQob^BNi`ΤƬĪɀêɁūêɁūéȁé 8 6̤DDCCCC CCCC CCCCCBPKHPLHEMʤ߁߁ށ݁鹶ſɤſʤˤ¿Ϥt8mk@    E   9  ,n  %6|  *>Y .EZ 1I[ 3KZ 3LZ 3LZ 3M[ 3M[ 3M[ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M[ 3M[ 3M[ 3M[ 3MZ 3MZ 3M[ 3M[ 3M[ 3MK 3M-3M% 3MF 3M) 3M23M:% 3MC+ 3MH. 3MM23MO43MP53MP6 3MQ53MP53MP5 3MP5 3MQ5 3MQ5 3MR5 3MS6!3MS6!3MS6!3MS6!3MS6!3MS6!3MS6!3MR5 3MR5 3MR5 3MR5 3MR5 3MS6!3MS6!3MS6!3MS6!3MS6!3MR6!3MR5 3M[5 3MZ5 3MZ5 3MZ5 3M[5 3M\5 3M]5 3M^6!3M_6!3M_6!3M_6!3M^6!3M^63M\43M\43M\43M]43M^43M_43M`43Ma43Ma43M`43M^43M]43M\43M[43MZ43MZ43MQ43MR43MS43MS43MS43MS43MS43MS43MS43MR43MR43MR43MR33MN33MN33LM33LM33KL31IJ1.EF. *>B*  %6I[itz~xl^J6%  ,;IU]beffghhhiijjgggggggggggggggghhhhiijjjjiihhhhgghhhhiijjjgggggggggfgggggjklmmnnjjhe`WJ<,   ,6>EIKLLMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMNNNNNNMMLJF?7,  %*.133333344444444444444443333333344444444443333333344444444443333333333333444444442/*%    schismtracker-20180209/icons/schism-file-128.png000066400000000000000000000357751323741476300212120ustar00rootroot00000000000000PNG  IHDR>a pHYs   IDATxyGy{gfC}ߗ`|B @~I^L9@ clXXmIdYЮνwfvYҮ<zzZA_jI3w׽uJsy׀ [@Sv󗖿tz20EPkСC˙"Da!#]ם.]1 ]4Nr3߯a0xW}m_23XR]g2*-B@:t蠂G/| ( B(b)(# a;0 PWn&ɗ !t 1t]u04|蚎nCa?#'1 û 3,oI?(Q`|{LPr׳S|~TUKUUǯitvv/^o?a`> wee {㠠(w$ꏚ`bJD`j 3%M' R6SOg8;BW) K_9]od2y.Fom߾]kw9s3aΛ(:jEa>Jr%L&C2 ~?6  ð[Л>&_ֿН~?cjc7J6%sĴWU]{0 m|>$088HIIIzXU?7ztYaX֜BNO 1G#m;B$Kr4y3 50Hi)K.=s \/?OH{>-w rq _YL_Fl\. Hww?sfā>E!ٶ5e{[-udDG.CA?uuuDQ;|<`IyuL% e͍i&=\V\M[6i~ /3OS?E5Tr,< ?MųG5 '|hF(>H8idYr9s'9ȸdI`tf hFww7HX,y睏|ӟsz=$d3@e 4мc}ݵ޾^j̨iz:uյĔ(Ѳ(tō \r%4ULȸWoDu/j`Yt]Gg'HcfH4cFږnCOO%%%&ԧ>u%g9L Nh]@mY-29 /oy]^.Zss ,GHN:h(U,^~z[;;f#-2T+m.:>D_o:[G4:,SƬi3mcbl6K&!X(;a?^ɧ9+ (ys]|~nX4s׸ D0xœ?KsK c3z_7ߢ6c~4nǡhmmECc.@9o[yu*U|?ν;+mb ,ӦMf&UU ?ܱcGg "8]Qd1.ǎ{ dNLE|/e, <`wF5 ec3YPkn݉O ,VpܒE@뮻~(Y4e6ҭ3 gzMd8V~ªK-,uaI/#˖3c85 U_q \\43x,T, D&9ݸeDa4M+Ԁg+L /#K" ،Ԓ _@ԒDQϮ5ʒZ_A"0 yyx[Lb&TOƌ1t .'3{Fg## EQhit-F0dfdjf3`fd sߛxn`Q"{':P jԎ('H j`A|aGUka^Dt3.*SaEQ3*ճ|[}&@Vw8_Ofճ&o|뀙7g (}.5Y믂z?%ed2H&R)Dd2i )JG]:UTTTPYYIee%!R(YD"a<LIgO8>jnV; ;];=VcV*mW+u5j1eBЗCAeI;|MR tt:pvC7k>T2L*g͚PW7sH$c=|U 1;^sz/sT(&#sɹリh{j`Hfh`D"aG-d2vD*o!֯|̘1ٳg#=Ca2 iҘ`Jff*k_1Xk=G>>m;& ݮdUU r`=ziAoom(x_9``J@'QLbR 1^c,)8q\N_W^H*3 OIdGT֟%1L@f9$R"N{{tt_ Fn74aJsǡ0s2 Q8y¢X[k4+sGB£a*))GHWe5c7|a?1rfet N ~9Pv/<#tp}ΊT;";Y{TTe\U]zt   m[W4%A)D09-b[bc1F]ݬXl~ەN/}'b/2=1 v*VG{dt)`Ii>j35 HH$B( Eb1|%~}-4z^r̊BӇH$뮹暹 0)R` &Ny-,sF}ϸgL/(KByCD"ѨR[#!UT*hjH)ʢĵYŨ>bŚ㎏b.ഥj}!>@s/6y݆r6#t\L QŶβ Qڻ fG*b@Uykh+*W&~>022BUUŘS$=1$0`3G elh `d.i籡SSncJujkRKc%HT膁'P7LWFk' QU^` @M9:t$120B_^ LFwlncKou`KVN9tƁ 8ZGsz6z7 9!y ]h=xv w؆5 *GҜl'K(.Z˜< / ph0P_2#X\;EChc LƏ),0WUƇ4EQXYoCd./_7;-0Gxyώk?,h[%)Š;zcqyR6|qp}3Բ'" f,[yu# -buJT.`9xtڢ偵Nf;# 0྾ⱀ|, k5SN{zx/GV''BPUR&zG#:ZefB 1-v"яaT*ѵ54ll6KwCS'n:>`&o{єAa+ݫLu M,UVf8֬`׷Elo$RU9d2d2VuW0<Qo2(4 0 M8A^5, ;Uscfpj9?3a5I+-w ƐiܩxޫbI\^=TU! [ϗUyq&8A0{w2,(6\,WP֕&! &1:ZJLK| =a0+:`R1H5p$u-WCH ~5Mڋ7Pa.70N/|:[l{bjX#j5Cd,;.f}/ MnImWUT!HSZ^D b̉Ϧ+m]y52deC'ϮiWփ&~V[ώu |Q\?Un>& M'0x~m\"t 8X1:}D(#;FW0!~}y{9+f੶gIIGɊ;0%~=wuTaėP^R.u9 `ߑFZc$h:.k;15fjr¡#,[J.ˡM͞ }0 /b#SNswbD/x)WT%f"?s_q@T)Mm~utwK L`/r-[n HSZRFk7Ϳ>T ĭ{dR}^OaÝ1zmOP>މ_D]Os+Bhn3NP>\Z-!4#ܞ_s膁nU_YRzD91@" 3-2$I-U@D.I"`vWƓ% zJRzyqB1>~G1 R)%ރģqFS bs::Q{`%(A *J+HĂQ¡-=AI8J*"Ŕۆ)1:y׏lő2¦#hmıBP+nZUm+DVU/y;|*KnprWW/~! {wh WRnG~ϹwqՌ+;o0 *KYi( 4EE_EQnw=WuhK*Ni=Ѕ,\ a9&rLz0hdd+RWZ5`|0C(fZk\b0&"qK[XK;FQj[fsԆjd2SaP+˖/;ko'0A)o945VXwIxf_U"u UQ2 F ɗ'WRa[q ̈7?l:^mo\1q3KgDBuq™ Sݙ l#p) S+8AR';%A(B.e1TIXV|×?hŶҭӷQXƫ{{! |SǝW +C*>-$z~$Kqh|yix- f98z9TߕFz״S[ !p:; >#qL.Y*(ǯIKtc"E?@( 0ӷtxxc#a/jɪW?7E(]{nQ(4IHOA1JvA5vmۉT5f2nY X_YZG[*Ȏv>K z(BR+%Q$},/<TkY:kþP=ML, Oȷ&@+8 [}2-{8a$+ٍP?~}{KsKzWǦ|t6M4Rp}lHy !*Km_}Ģ1~@u_? Kb膎O:C>'W/TVxNW3)WCarY7_NnÍ2(BUy%6v&xgw!WE1WnzeW]}h=}r9^ '>Io<{ڦ( u3|>vNnE0ƨζy/0 BQ~f=8Q%JkM\pхD(\m{s7ۮ(BtCs]G0 oGFEnjX׏$R+_?ܱ"ZN8|0KW/,Wʓ='j>D`̹t ͎uR`=e|Y~+jrp0YLj1ʑ)ET_緷muL/!:3JWg̣4/r'qh͘\@ \p4lYX o~cĝ'e8d]̟7+.`jg9j# ##'K~yKX:owJU|X(N&ZֿտSDi7  >.u9 ꩧ[Hr691h^8eX\*;roV+U7t< ) ?3/_>?q@95뗈cX2>HEyϙUcR(J<s9M ("7@eiUlxzBVM/mbhdtsu]Mei3$^go-)zv ]uQ^^PO`”>n>Fyb7}co+on⚋F\ݍYp(PH,˗`۸O$y-v mF]n^y7yc :z[7蹞^s?ozf ImȾc…-vb*I? C]iw—uk⃷|+ο`4 !Az(M,VHw^.z?^=SNz.^s1C÷D2?yIDAT~k,5bo~i^=_0zljoV.sK.g/_x)o^>tM V:TT` bcccEKi7h9 sg^X,G#\ Cmb\˚V[!( u_}Xt16EUUf08#!Q;gw B!.³70 Cb17xmeX`MxY쒡M?i YtP}fMe3j- S> yy,b| Om}nK1F 6Rv,Enfb&ֈ; i%6Xl6z#jN/SM๻'pR`JU3֋Ԅ.6:4qh0#QK)>{;@fC1 P1_g"ۚ, EQhX nX1=y8DI(J%@ap3$5i ]Ih9=Ǒ#W͹Axb^k4A {7]nO8uwqBP LTXB1 U6vlDp+XVio3bI ud2ħذE<,ǷW?8rc Cn.[u3#4 {6^79cEUo6ȓʥ b$5ovg( Q7ڷaϹKg_T zĹ Gf(;`T 40'x,{SY/@"@̂9Gs hQfW&y+wd?!=B3t6B7sy'2UfBG@Z3x%55, :X3c5L*flَ>e0xs2gSfWX5m@34:Nd^?mz]NBÉ&S M'BCtvRr"Gyz29`R5NI0B<Eph0]GF# + x}'@ UetuQ;ϰO֕;#$rI|J<w%S;qW*,Z,_Ȟ= Tp( b!"4 8sPܞ^̼44 z9}5sWQ.V҆%l،apͺg_ĹT,YD"`wNoJ9\U/c^\6r+(Au6v p NZd0Gy S` 674̫_k>ۙ**Wϻ:^43{õ]ƙY>Λw.~ Mr~wK]`ܺsxxOe\l AQ%.y%(LO[s Mr^mp' eAWsh^DKQ_V 2^ɵKfKP7XqgoLs#ːNt- ĸq \be]+&%ϋud\ 6jfu9,D3}/C&mY{*L9GY6s)@B]cX'aƺbpwQVRz{,:Pu7=M(̹o"2ck"|>PC+Tz&nX}~NQ+2vl;k[( !VK2Z?TZ^!CA!s[9:S]9v7\ÒE! 9>?o%7D&&'ʠ(xDwakG$A9 zi޳:cw-lr)Ydm}-T$qlhAWgFi3נ( AB}K|?.٪:G2TI4cg. CqA>C8A^S$ -9.i}&Hr\E\]SRM$Ύ] *+ICD%4mDH ɱ$@a `OE17-ϳo7QϹ+\ ׶sFYTp 8UJͣX#Wd_gjgxv2zCOhlCߝ6 4M)M&PH t+W^IX Ċ̪Iso kfF5|?y뢁Bj5-_ߞK/bdl^DžϷ0+6DC%bz7~Nu5V36c1X@1|/>5  ]TjU|V}`٭lņ]uhkdh1Ae9%m!N 4X@]IehO$PBCt^/6r{7 0 z{Xoɇ.Y1`0h' YmY&nѲձj(\.f#_ B@%0iZPp8ի?Ō c~<?=չ|`vւ/kgmݣ:EmB0j(ql!Ot`D钜`*zy8eKQ3/#zKbb^@D*Fi+^/&X^bj 0P:8t8t߻g1n<&UVy%d|>^2ݣ˝Ow3\e@[#uulcH@{Y0} QI採:ZJI ̎aN;`⻎2+RWXm?Y.jJki<׳r!G #x}#B+g]E[̷P"UKrKʆe9/V=1[Q̭qA/?=e+.p%;e7ru<#EluʔG8{mp9Ħ6Z݆C L'kRZ=XRk/PX=k5pѳ])8 βridxP>>CGB6dLәry10=C=̮MM4=lo`QP/RكƐ^OOr=/N)V, ;o|LEN R}!Ő_l}pj\@sق 1~ 3)0u `ڀ止0E!/AQ@!%b"' KTD.1<^6Oьy,k,)j^Gǎ<%,OTlDQ׻a#Q@^P"f뛔J,xeߑFH>b}0Ť~[p:ՄQ6EUiϫ_y (4ge'ͥ:MSUUy׏luܨRb+%j=|ӽQ/{( MͼzUw5PSRE4kuRC;ʺ̬fY_h9X)csgsݴ5<޹bMj;kA[Ԯ[3?x,[s[/W ں<+׮-繦=yfs|g}V|s7'Vf9\ĚھR`nlqvrLe2a҃Aϖz2Of ϧ;aO=@[z{xd\zBx%\MI.l.hL&CH'S4?p!@awy9[A^츺4!Mx(*G7΁wڂ&lMs}k'QW4V͵.G:kf0l2aJgi|_L|NDl 9~ofM+F ܳ71f5 Enx^/v~d/éqbt3`6hN}`a\'0Cuj*Js!{V~2a ĵw\7MGA/BC`7NysӇZ/V>捛b:A޷vsb$_ ټsƕɆwtI^ֳ3͇^V\޺wnvx,װۈ@0/TM0|Jy>p ~zN'NN7b]r!_#s_&%fh}vD"VeLx`4;*3IDW G~W/"[Stcq|TS^[}MӘt&(/-'^gӆMAJN QVf~PvKm4H ; 6־MM~ٸѩMMEv5*:eИc_dg㮂6yIEڷ? 'ذ6H5k֬aRClݼWϢEؾu;}=[wr+"8)0ٮ\-~Xzԫi-?~~o>8'=-w2 8^ϭj:~\6D"a/dt0 r]@g ~Xōuv,%:{<ӽ AΞ|i.XK>#+{:OG=Ӊ0 Lsl մ Mә]?˱1aigr-r9.˜ PLxyNQ$p:.DC7 t)ie#P`M8t3 J  >BQ֩BEs2f0:QO˞ wbx2Zy-V^n{ZӂXבSyaD=U[yS)S T)w~O>@>8c^c N N7*YdҼpX@1*sxN5N`xyrpIUUǁ4PP mf8@A iTU@%POR9߷ 'ץR$9 b+6;ɰF1, e>)wt͓PXߧT*5o4 SIENDB`schismtracker-20180209/icons/schism-icon-128.png000066400000000000000000000415361323741476300212130ustar00rootroot00000000000000PNG  IHDR>aC%IDATxwtdu&x+G (ntsn( %K$K`kG=339{Μ?fIg,J4s7;G(s*P(vS&E< ^z7e@Ɲ[;ǎ; s(α;ǎ((J;wtuy;<D[Ox G::ϝ8w޸a|:+rG=>~yGOhs7 ٣ڎm/| ' Ξ=v}zvݿw?WxӇz&fs'ڳs'(`0<?k7=⁑/}/8<2ʋ?ݥ`2|<¿XQ?|ymt~ɯ߽&fܙ>hg?%>(N{s# _6ojLռ ; 1ϾGlVK_gP(ʍi9q`]~[f.}䷟ZN'NÀ%UZb|too|'ua9 BlmnN߽SiISm[7rt;w=F|AﹻR0npzN8z3OuY tӋt0c AY e)9!N=yyrZǏRrf#K8̢fy~~wZSIO0qmdj {Z?zwkzn5M-潒/V##2|SNiY]JUVr(*nݕ&P8c5 mnjIeGzrG?F/?w,N[C[f &w3r-{H>rm2>@XeEn${zdP\BI8c@Haw^xu}9o X;$btg)ݐCfA3&_JCmrWX2-o߼+SKJgW N,(T)ym Gk;w\=[sw; +ޏWf4y$g. uw@v*<햩Iܾ^@R%} +ɽ9ܸ;'倊L(|:<.;#P߸;#UY rvz.96:rM-*ؾ?8ã#/h,4{J;T6+S;bN'>$on6'=#"/!"b$=M"1ԭ$rEn6`rl߰[K`.ioi*wy@)o-FcC; aw:͏+AVfAAy-]c'^ʍ+iy9`me!޼*sK~).A>7-G )/0>=/?ik_Ws'_6m46Ն#G~/ |+-2hSZNe韓'?݂]_e瘜 Ph+|eѽCʝW@W.4o UMTw,/su? 9z(锅%I'z69zx]27wXuRHj&~_S'0 ygQX\zn?zȟ/|cx]T|ɕ[r7%Ǘ eKb6:D*mcSW/%KFG?9B_-ƦD ̑o}bq͏MyyZdwo,혬e%"x*ΝkMʠgPf.wɣJK[Xe7ʽ ~JH?ɁtG2_xp{>_¡h"eZG ':{`/\u&Ptz{ fuYHA|biHՇ`4-}xW ? 'I/zC~I^5&s:Z\ti=|}hGgb\549 'c@~%Gc4%IO楿M:-2;^GbJQHl4#@Rl&zy@_mi0jHzzz=|?Y.f7O.@XDB9iyO9$LMnb cg 7'd!)FFul꣫ktȺu(Z'p=nMQϟS~-S,mgrMttky9+r0 j7SBRHFe=t*~X\+L0F}cW! n 7(dݿ+'G:nv^@Q)kSc)90$Gt;Tp˧=z9ZnLh7 fA`! FGKv)ģ_>GZ0O5bY j6ȔR2ǛѤl [q3Rn98tJ29k@}N]V{֍8߮B5S{id2v3'\Z%+zA<6Dyq[Hz[= X=Ų{VDʉaCM&QeRAh*{ ؆,fB^|C/{=G;+3׉n WR婇V"a_| <+oKUMi(F.-I6h2<\Lr 9+'U"Ny^Մ><*91S(fKrtCTzbW>~o"wȲ/5Ns<;tM]ɡ_lOha楶 Fc|=d_ESA3Nq ?JFfNc14QeIV{xk^H" xL+H`PDh4#//{e˃n`b~B!Yn%4n6`N0N]P,`@vx$) F5@kgڅA7(PH7$$ɝX_+ 3>Q[]Mͱ@b6 ombY_lVrJ8VuGc F,SnoMd8kG#Mq{ D2ȫ&T"Ь: qyD$\@;m/TL=y dȍ-RS:Œ!,5<FD:E"Wӱu5i^0Q_>v?aFzB-\kEʛj"3bua[QMSlQ5$F@yYb &]^i;2=!.cJе:ĢG]Cm>XAB]NϏqқ\lWUHmPIb'zQjN'jK1_JM&'HAʆɻ )"j^j0r,>T3TiI%2ɲW*UPC[@VvNּ'k)od%`@d"+e]V:$!(FwgJR{0}S[o@7(@E;S}Ղ#Kcms&`Pa^7 ^21E4b@2Q`B,$ #n(Xu^;W\chnc\)yM\$1x"[Wl1 CM5Y/(=іEݝ-xs|*ͬ`cɬ<-x²:d"򞵂ɢ T,<@>4h^#\{͎8$q{J 6٭[GwqNZ2&h򅼚qqz<)%vOՔ01&'g%is57A$2\J0=,X^tA"\ XF8.PηfTPDwjn`&jh;^,_U[ G;, z6?n0eJ'wp &t3`D_ ,LqHp3=-XzXb0-)0h(7>t *vgkb2N!9*):ƒƺɐ*AM69 X8wnFW`AنJj^W+B9ɤ,82xTھz<5Ed돏9DVuJ 8/CQd.Bi?bϊ[ˋ )V}! eF&tl01oBWtm3ZkSVv ləc9/*ih@ˢNR57XC] Aq&3@(1B*+צJ2\},gW$N<D`&/,Ū[̼ ЊDB7 cnkpa!8 ]T(%uE7SRpv! :aS+@U,- *kOv$2n:;&W.S+.^q;O+c\'[*Zl6L<@SB쥠T% b`C:),iTVlŹ|+ D۩u*\켦ڸCe0sTZ7=zAagEU8\ɴzfVk:@aK˷녁g rf3 Z'D 1`Մ«xʦiY1v"Y!V,!^\/Vϑ۫Y_,ds.(K%TP|2RUP$Q$KB99^ad5 .`(֖S`t A׾Gjyna'<OCa``w24q Sa ! X(0҅V (*/(ؕx3H%trMg@/,j(k$@Cd.nXH%tx9@, T$ W#ik]ByxPX{cP򊛵}v(Xl.*-zwo\O=ujsf}Xjๆ7p#rR+ǫ:^FsF kfe]BS/)#)f"yq* |4]ު $,fnUW u;d1010١94Cp6tI)]|0e^s * h -K|':=+n]3cr"C3șTYQ-ڢFIx^$GV"ePodsM|N{`"Xĵt{W!Чl&m" {8G%(j(m):/EdWL(jW_ gep[27~O9b(D׼Hrݚ)O n &,*k4&fÆXͪNNz&v GQa X+BE~YDZA4*l1IK49T"׍PW(38mGP(\MÀqq-6m!߬tF(@b>}JIǦ0!,\΀2+EdC:(LR N!%i%Ww#X'C4)"HMˬ`z8LgO%WIIer2iemR3D/i0V*6Œ[6s9"|Z̛P`VaBߤSX]{pͰ$ȸN$o& mhf4`5PH$4 k|E "Yq@ bK]͋61O-u`0Y`JzE`cR̞z1'l1n\Vb  )6]IP7hڤ6u߷SVqY4˵&,,K``;ɢ!FKB N 1iQ fPNKCO 0?1xxgo92Rr;aFjIWmdΦhl'}!FԢP&mCq 7a1_&sa . kV-!BLv- ݉Sڐ KחVMYI"mf=op]&5 a-2J6,.33XBGn;]{5hPQS@L(;QaevY%ˍlu)F>ֹMDJŐr. S!z4ΠӎдWXO*)2a`%"$29eGʋKS/$c`n0@ mzm&r > rÐ`zujfd/aoW0MKu ̯?Hua—3d9G:Y=^S#W/^?P ȡ*"[>, )L6JbU,Gfo_c /]eI  -N#9>_&JlڈBu9+4l9kԤ%)lȤ18m(CޛI9߯F5c豖M'.N9 y.jb`LQE ž<6,.lCIE7zעkOU nQl!&{5/`6rͻаmssu*a`jF:ZD.ܩ;:3ymOIUt|USApxdW"o-D{ j _|6G + cܢfi`֒ˀ5~5D&VϦ% NE]P.ǰEp|oebۺΑNFa`s]L]:}Z&tMIZǪ[U? WDR7Y:9jFK̗[LMM̚[ܤlVnS[Y`!FgOpܲCc*YT҈ݰW@4>> s0`\8ooO*O~^BF, gIXI㖊[3pV j"² \mՖKA_L)Y].Z@ݳiV8mL;ZMEU#GC n0ͽ "sR0khĺ"w?0 p)dr`;>`K^ A">V޷浱/y-)8G}R!W2ͺ;7Xz5s%W=]ڤ-*9],tb;=aE,0bUƮI0!Q*PN5ҩ} B1(I'ʿ k~;o]O{٥`$BIЩ/!˖^o^+`!](PGi1QQVO#hB+7>V>®֝T_X>8Q|-fWB)J ԮdrAKW; y˨rݠAh#_`{;n9h]oCH/\ȡ@P,0Q>2f^K׺4 ~:.3oZuEYVMU.9ú[-b :vr"$o*\p*Y7\H:J1jKyj= j0585$[A ٫ z֭*Xx $Qუ%j7*wN^ǥ_כx-t…vX}GOdr!y;NMYwK!+xmX:DSj,!bKPΦVm|:S̩ؿ]][V24`L:`[2&^ J50Z|YE{P+.^9@z (I4\>`s&`r.[QZ~¯E򶶶/R^a#<)@Pa[{'—-̭l`IH{F ,7Phl\]Lc-{F{ af>͜t3rvWc lg?#QK7辰>%ϒ(8ZmzkٺY8nЭ_^7T["pkeZZ*+ kYAPR`v)F"vsz z6v )mw60s &wY\=?7Ŗ67]F1Jw ߯¥_yUW:[!bc+ȵƑݼJ`$Úl1!GL*ABq4&\(iqn1G'05:\lR]Lz!X"<Bӣ], ,v0i!K>u;c٥AIl[*D)dIOuU+V4f+cfCI t߸:cxe|*-@$F=;5&ⶁǠ77Qx=(vXcoc̸M -n-̆|V;Z'W{/?Oz~ǃ,xMh0MM{4ˆ"mJA~~+@(2LIb ,!* VۥtUKy=t9|Jx?lJX/qKWǧa<",,wvՓ,'k,\]^t;wv=(XZ H'uT@~rW%o ҠH&3e0,q׿yM؇O14RݬT/\]6>bB/<0/d]d7t֣=.eeƎ>C In {Md â La)pYck<;x T66C6.~zбwF(ec8f(td0b=~z(v6 OĢ'RKNrBϕWi  By:2kEދ\U;d:*].xP§7ٮ?uё9&lhmYAgf}.-_4SẪv|@sՍ U+F^ ]**0Iaރ'y0a&鹯9-<,fg1pE 7]ggl3/%yOz5N F-&oSXU^WKl\2*, mW?_x+W )Hv`^x!# jOS`nw+ԭY{W~ne.C3:g_/@~Fu8=?> (O[dȍڰ KG }׿C!~o>;8ʲҬ(-svDYGZ [7A>}6~o~Bn_^~tTp^Q>!|O?ѻBX7^·_#izonqiOWr8~9=xԒ!<H("fI PR:u-)nl ,p噹ۓs?|s=-±ޥ,>nfN<۹q ~eXwDžS$ G^D|燆(Z;wq?OU_p+.}neW,h #rs(α;/2QxuAIENDB`schismtracker-20180209/icons/schism-icon-16.png000066400000000000000000000013721323741476300211210ustar00rootroot00000000000000PNG  IHDRaIDAT8˥]HQEiX~OMwꫛ{+5LԹRd~V:"LȐ. BhDuWxQ7EwR#?sΏ9/3/bT ykoO$ MD_!n2(j6V&5L9_Ǖ%:Ҽa50qc,'`%$"au z;WkCn,QX0E2RGtn6_|xfbQ U/ˏ/3(;@zau k8{O8_ƷFoDAGqۑ%+귾m_*  yvFա(;/7t4=۟}ч[޵n5kz)ӏDcj6VU~o{i:^v]vڶ?}m/?Դ!#7ޡ1÷.+wզO[دt***yCn~E{{}uj~˟9>{Yi˷ͮD%ZU"Ry⡝?Nz$ : e~?V_yI؟WԸR_ԓ-Qlбz~hr7O?Ju-~&?~2XThDR RԌ{oz:U]ݢb? ޻)wˏ6kоya+c3VH4þs.KjgtGcqr9Ԇ&uxe~/LdUzLoVקz uî?Sj{`TX"z{@򃅮j,aN|-zyݮ`sӧNlzAu=V0ǦR4Q@JГ[ִx-erNG+-hzsTݼUk {^U,QC;$W)RHٴ\hϦc^78p~̙hʗ]Mu5_mnl œMkZ|W:s޻HMll6zfS}:hOr~Oն$ԧ^WO~59bSO;vXS}pS La Y?@+o×UZz]QAep }?=Oh ӯ=ͱYTֵep>F=jo5R)u2ի>5\ .Pifa tt#K$TcM:ܢFw^?`ö,6D~/}rM[g?}d_= .4<?1(P]n]:T`6+WŬz*Mޠ֒ڗ7/ҎO[篩qQt/tEnZ^=wj7& ksGaވA߿~h~qkM ^t5-shP=id4z+"{cӡtSU&h@PzzG$3^ʵζҭ.@) &e%8IlvٷuZ(xXr[OEy\Ny\8m髯>R0gk6m'='nœ߿E{=s*nc R 3o"QkG}IB$Uk'2ڜJ篪QqL O&.CojT*0o gPU~5yPܢ؉|/H/YGrj#{H!^vWKJhPؠؤ?S/תV51QtFjcLuw@~W fգaY0ʏ(VmZ,c퇗o*<ӄOǡ)R1޺mQu<^7ݻ~h>>[M _qc{:6 6Ǥ$b鰫uºszjW$&u„ߍ(A=}4pwR3t^Nu;FݻUN+FW/g^`X>]'OzʋR gO :sf8@gTUW=wIh_=lLLS 㒾tQjmQ|nWֿک ̱5th~N8U]=g e'y 6qzhvLWi]?k'l?CG o&A$ 6%W-%zL| 2hN`+m ڠF8 *;҆a놵ԉ:~ 6A7$SHԞ-kTGcO2yRda:Wwl]k*T,ff 3spFU,8|ښt`Z-x,,; s^ظs}N,כ5CZЁ:'b|kmj󞣿?$ڞB/ǥf@ObUWԆFZOJd`aʜ\ݿdR[c٠lv_t)Zv}-M>Ag`1zm+m$ =ZaL{Sj>;=fӟ2hS] ZF}_c=*Ĝs`Fфo_zG嵰Xuؠk5}`P4߼}ӆo}pp#ۈ?Aъb090ބ^qXA6;硇Ԛ51Tߪ Djizr{ʼ ۄAoAwF Їz?y":m2~|0Һo/|P"n!'!hS(4ỗn@E9.gaWțhæNb3.uj#SK?ĉ;8"#&57Zm>Ka ;w._+;3-_C{CuQw/JDljJAUi9(C&g7;'kٺtU!7~:&'w=_00:!dd s-Pe>(׉;YktKi6Woݱy^~pf"y5VXgVУ"V_7"9ex|Zj3`/aheyAJ\ ZIoRhJiA =017zsuv׾'5a$Af YdV9s cUN4FKe50L0>#%WNqyj~}ONַSlĵ zb+Wr_O;K;԰0Gʁ" 6#D<8)h^~3j`DY72 fRG}5*b:OmE]]=* sϥbH'foU~^ݶld[7O}9H| KFG0]VpK{cbt5 ]=U3[r8*yPQJM)E+Ĕr߲T)WuΙiu- h_g]MtJ95UvMjc$s " _"`I鷢]sGmH>eBV #p2)Q5Vhm5< 0O9ͪcOen=2ymWn 5D :uV۵kbe3ڿ^z#`|IeALj uNݾ7q1C.ܼĤxRxN '1XS Z^F}{ߋ:$Z ѪQv ۘ?Zqf=2Fg V&(f79Ff~} zГ| z  ^œŝ8\ L?sR=[I7S~9v=QOu-=[$nLWkD5ٽbWў9F^,c˜?g~ !Gª0=W{ m^T)3-:`@z)3f9YGB,{<ӡ2 >@ 6֯?i9G+ds:ca17DŽd"JG3Zi",]{qçktbu[ܒZ;Ԏr|gŹ[7HBkW5N$ND EI􎍫~c0z69j?P/y׍ъwAOn޼vƍ/pQkG1EQ?&ezmE; }dw&CCinͻ2>ؾmp4mHf+.|YT=m>ׇq'#ўbG&fAUKMS8C`{B@5Iyt*{30W/Om={o89 k[=~n65–!2ڳpSK3)= .Mwe9f[%?@^0 [魂oJD$2i'D2Y"ϵ _爓*¿ |}ZP*q'qu*iqIrZqHV{JhSh, gX0/!0i3|lk^ݰnoGo9wqh"Qa* O00A!3^(%5bX9$JJCHhhY_PXzMʀ(b#$ܣHj?|ov oRhG6 -O$ ])KAcLJ[{:fAW8q{rӦM֛7o?6ï߲a|'_x#-v jeyna+s(~V;5C! Oy2|J"Nl׷EeVnvcPra8P{я\qʚrSIIuԯrBwU NL/Rho"NZ/N qp2Jp)x{vTͭ篿Vc~#n˿?qMpZ=8ql;0ry\؋/]0GA0ptٳ->,F!N !ЍނT'Sˆ* m/ݛS*鷩c}i٩‰~O{XD>f%hF0O5Qige c٢0ڠg?@T׿Gn9Y4ͭf&QH&Y'N2V3BaJ*Fq=G!F(qõl'>]25Tˍy Ff]L'&d0`%g/Bǀ1C{s`ZHUy+TUc5jaQx00)0Jb[,ġ1rݏ~i) zf=XFf=y"%h- r\|ڠ6l۷7nݸ_|9mנa_-x!s@ə1un@F͟_ opBZ :Ɲ(LqLNЏqR3#aXp D zE,vQ!ʦԘЀsdEӆz |F.|] FE mP_N1$ ZUAjHL13bgUXbwRV} ,)z}[#Vz3Րz=GkD]?)~ t! ~uY.QUo4!}U!"dTװ-: tj,8{jAJz<,]`o"4ͩkU_){%MS2!x$èC8-MW#Ӣ@dޡ=e(  ⰾ,ԇ_ d9@$'Yzkhԑ :~zkgggqT-/<)5Y(&PCrZ2Ro;i}G}cjջKeS;!Skyj3#X^_X\R 0n8&iZ z@\\VV+=M8l0P8岀AT{uuTh+ }kvh4Ҁy:`8suŰ Vtg9Wh<4̵+*]0*,slϾ=qVtŕ9Jta<a }7&{JUT4駦ꝷ&T8TG?>I'OYsY[&E2D:&3dJXļop`6u aS&a,j*| 8I6U!"V`fQ h R]6!l k!Ck3' -b ޭˆ:9l9S7,ZCzuZYש㻴>C#rXey#bp:R̦A zR,7h4]sپN @T?wmJ-NIhn`|jAB%_9]n̤7Yg4Y6Š?3TMQ_O-(NaFϊ0z-uhۦ#{g\mv ]}|m7㋛ͩJ^b Ǐw%!}-2p5I_I GwKKe'VKujk251z6wjˬyϏ̎jՃF_:|p : ^w޽0ezϭmk7?ttvg/q Քuy;;g;o0vR5+&DfcdNѮOw:y܋I8__πIF:MgKh?Z'*~*)ss'Ȼo~Uޫi9K)p7KF_R| NxxZ;J5zFuJq_  N  CP65P=z},V R c'>f͚?J>tT% `?e"i4x&Eān|`H/ބv50 3Scȏ=V' IDv^b7 ETe 5F#iSrE ! (HZ 9Fե5 ݍjNbQMӏ,R4(J&,`)K29)'3L7XKH?SJ D81a_H570 7 ]N5`h ttN+bۼRCU̓)U1M$Emk*|5 30qZ 'd= ]X J6UD]q5<DjeM$FČbd>IXH46R /]R<$"ǩ8.KUsۦzv1ȃ .9xon~ok%LX}Kn!͕6Wԗz(k   -qq|yFFP}"f/{0؟h$܂Hc\O>70,Viw!"'%bҗx=14ԠЫ;X\ AoͨGgk 2g9Q# g_]a0QZ.Mm48#jQV.t2 h\[ QG-bd tgЎ@>TGGǿwފĄ\Fn_9ZA(_GxX,Spш:{zI]Qo'MN#P^ u['^CD NjM!%yq$aDƦv.vF|BS]J @ RL:@U9G%_rY:X—tbFdr+{bc-.,[_Z8 +0Z-6Țoid4/dXo ǃDgAd7o|"_ѿZٶgEQ=I-T #^i6 Dzn2HT"{S+xZ)vm[}%àK ?鉯 }‘QAU!PJ7ϨfL"0wѤbf$pcB!O~nTPهEH˜yqӢfCi}jb:z$͆C]ƃٴ1)ܙXDطV$'| -%L\7_'f)s *|YsXUaAV!S0ȚsΉp;Pwc3=צ Mԓ{j^'k =[u*^)KhېжtV*GǠ g9g)c!,T%03s&6|hb;g\ Wo2l3)BRFz*C7Y ї8(^΅eȁ6FŋЈۗz1gU7qDAZq%mv8\ᜋC3ZTj׵.}7~u'M#*I 'fz/k& !HJ"DB]hgF&C!nFN)#gvPtg[>LkkWR){7wH,tP办9*Hh0J|H z1v,.ѽ?;Hi p@Gơβ ۞u؅%Bǵ=S3[-Iֲ䓅GpK3PFo1?R V*c[Q nz)C4-}n4߭,Z竖ڒ}ۡ>?=V}3z>DCɄX 8nZDG C<%L%P}%YFԛdr*=~ƭ(_@u,J3 iP)Lhnjwͽ|,hu'Œ5 #]K7pN1@;&LKLܒMFq b5z(zaE`уsr&oCl *Ǭ(>w:{'jКћ|!w2hV ,l;ƥ֑6i)4ϥ7b+ו-0/PFl Z%AՀ_Rѻyx 0"}f>y :}B!U9ԏ0:'+3GAKhiii}}}rbaBDRQg`|F]@ =EFqTjX|V7xY=9h.tST}9**ξ*d4l|`z>&'pv6'Q0P*12=1hKJQ.nk"c#.*L??0;]źF@8@?=hBTS&8M9aWU^qPn<,U@ &ebFKN KXҢocJ2^.6ibvh*ƼlJ1 r>]q`;IP:hRs 9Ir%}ז=,`K t,P`RxԊ9*~ H]T$,N4LBz)pf!Y9{$V,TKf8e3v65-b:B=ٌn !mr6ePeR P]ҰE*SӤIg&Jd X@3H y4밊O8›R 9^*k>3MMMz`` ^ `yl_٩wptzVuѵs/=Qދ q!cIƲU\?fybm M#Lssam׫ٮ~M{GG:;]Ąc϶ddZTD? d728Fe8 2:0G8+&EY$@_OU"3ATWITHуp/nlIO?Cy6Y%S<{t`m)Tru%X}VhoKQPjkIiNP"TLfаiai sR=FwSfI1 kTL$LCH&#De06/֮8qH]0ǐpT D*;Hv cdzG۶BaT /S^[Myq;&t갴+Fexat<ß9WP3zO7Fz60zsοNj/HʣpN_HVfau-#$H-zW3JcBTL]F%}K+m%Lhzcf8fΎ!1ΛX) u9nI,nij\EbyFѡt^2 OrT8FEaH3>]f8]Bz{dHxRYϹx*33x;'b2V'F_+Yk.\d ėW7 BY׌9&--\ ݷTQo$s ƮJkTcB /W7=g(d4S1'@ " ,MM+ig1Vuq3 0.f tr$"4<DdhU~}!j oʭէ3p-xo}z ni`iɉ{D>,-c=F:?8sAX^a (ur`P^ht2:qNYB˧s!1p"E*98:(X!>FS2ܕ Ǝ8h40,;1'$T[@%³"x\Q\ #BȖ53\~f&!g2GIH3A#U,]zP5~_FOID|'=M,j:Rw]Y.od7RӸPl$;ʴڄrgJeUPrF%_qSHYptunO7BFeLWdCR6eVI% e:I߾:lgrX [r, FEɌ3&2kҫMLk4)#}2\uwOuWUzc:ZD&B WgJA״Z^ z_MLL$ 8Midڠv0 6ȺXdhBj%}$#[VȎnH<` $k2!Z0W̩3A$ý$9$^Ə b<_r, 071zKL2QjlbX` Agc K?h{yx̱(D2heFИ4[j (ֵ6 rDNk^,ShD2:zp#LZ!EVm*M 2fay!2<|%d=-6fٸFl92ezp:*MOI+uԺX9.ϠZ3.Ld?mhP(n-۩sd>(qtT +$W7zI3.Q[@bcdrS>i&z8lRe ,Ӊyj0`X*됈4|]I)Dn^Qn"K=k35'سkk@hsA;@ͼ\IlD軔 DjM1ޡ9ޡQ%̚DҊaAq:Vf9E-}؉ʨѨ~w%sR+4|!9!pI6QYtIMu{?;5h.F!5Wލnm:٫%9&[-sw /A֤|: {lE1.qt>l!TQ4[m1'4)9\M6dNHEI_gVG(4=EFfO^mHW,XRanlaJQ'D2*ݰ8@o+R bPX.P ]r:] =R8uJ#kڨ0z9RPa{8/}4q^Z@ ?9i$Q22Ѥz@īf]gOwYC)FJO8O@M3jśLfH p1ܬ,HLўltfIc2 i2Z]#'1ʓ|rdMFˤe74sE4UҖz0oW{ވbN6C+j\}.nmyN )6Նx&)Ɇy ҄(ַ ;CR)_.R*{;߰]n Qo·-S0XTz<ãС2zY#']. 2+b3FyH6z[ +=i\[6A27'ީeP|<5X^02.ΆƤj 4Q]s}hRʼh6FLII[+X\Bm<݊[ѥ K>Qnh2mYBzc4K4-z#Uv8591sxq$f,Lq_ueܴlna9yP8 x3u{@(J9nPr͍Q| qQ7Dޣo\K+Ө82fz/[mќ֘@kNb$&E* *'c{ck_i(Ɔ.pKSa90H,|,q1YX.,RJ QZLMM֏u<f 3oN( `1oLѳ UWd3srpM*;/ǂT.c'Z9ҨDJ~> _syQ m[\ZjLksjpFFf̸G@_FN^0E1ĺa5,PB0%mIi= T# ݶH4ZRq*͋LZ? 'fT4Qxn4#Fz1b%FQ!EjAdC5:4ɧLaZVx+]BOsc57[4c22kOҸZb°k<.f4Xr9Ƣqզ娶ifrfZ|[}d3 g^3qD/sr7X{xFg:I[&IJқ3NݨE% ^Yɑ6yn WH$(ۃK}2#9fh_c0•}bUa*&H߲ф)1=/Be{\T0/iLpȨctOEUGS*V#ETe8N$gN]<)D2"S\&AUXmR*cDx G>] ݏT()I/nyOڷe *>5,]YnS`t|H,~|j#@T4 c]I"Gi՘^&Kf HH2*% 0$ވ%Yd1Ҟ0ҁ 5E10Uo^CfmI@Qamh߳)2'1sM0v\cToqq{^W[j5T*]/M{CG3,9zQ6(XޤOϭr2RA,9,ڦy4LJyk"=Ud<I<=/G',ژ:ōju9;*i[3 ?/-3B2:\:׍aw7׬H!c_)P62Ɣ 6&^qL#a 2,u4V՚UŶ`җraؠ{%ؠNבw% 4Κ$NƲ6#rˆřCUQd6=>X&23X\C@# XF-}ެJ1Y-MO+E!Hs`Vun^[ZQdK&(V Z0?Xa +53p4rA ߓx?-z]0?s5 #g1}V :Q"ַ6<.CC^Sf!jB!ULR<_bT,EahZ!ϬC R(;gP^ ?56VgӼ.%;ԡ@t(Z Z:YOm>"7) φ7$9:&͒=ڪ$2Aܰ'*JіlL"иÕz qv+IhSǖ)PCGiJ%1IUhM:D ΘD 'mj5? {V *84gFyJ1(I^s&FsrBU-!{J z +[. Z<пmGo;V}6+la.s[/X.3kx3;\jò=?Cf9X#'M]1&Nr2)^`h'3ER3+W&3(FL'DE{)y]ØVAN!L.])4 %bScq-՗A> F"K{7MU#`(8#]D*ft!y9a?)4N=6&?^eel \8wmvdɭl az:7 qK.D"r0;Sg#qS%Lrvj[?@Vۼ(۬G%{<ܻC~_lMM$`S- uaUWI+ 5O_ˢX^6wpRV}Ƣ.0rHB [ˤɬ]>9:B-J~HPL;(7\:ZLR$6kt)ǯ3*l`%4MV&Z =Ö͚́ O-%t:$9fyREkcϊcL[+'z1RШgd$oJzdY&MРO0B^a^,mSp_(} 3@S6cR4.;+ODHSKxN> }z'0qǐhC?kC[ꝋ7W;S f!t*`/3˄|SSTrSJl[Z&i´ #>) $20( U ܼX"vgd͒0LNknJ¥Ih2,tgCmUү29$gj$HCcU- yS2s'?=3^BgZqCw ow _OK!'"1Ko¶Vs >a]If y(Vg!j0moW!ܼ֖)ԩǓ5Wt?Qm/&6c K|6YѨO'&kcV:yМAb=VBq09xҨTWE r81+GCe.؈ HCj{Sox"|Wz${` PPdؐZ 0x6"7֥~XOSQF.-rNr XH &ݕe𼨤VFtg| X <$-nM˧Y"UԐTp2֝Qx DfOPͤ$9W=c~ -$]AϚ,Yݘ4ۭic7h,x!{>Tj|ɐf"zճP|:[d Ͱ`$ERoF@ M8E 9DV jjd4hhe./t!c܋K hj,O=VIbӟ}\c^b&0kA5Y挽c#y;z_Xļon*FZ&\T[d蘈Ag%_324r 5:pc] eV=pdGG}!2x ?פ_ٰdL VS4ޕj+TU_A:&ʤ ]ح{ 䉐v NЍ}knW7x~]ǎޘU-];26"7BFK06$>3F.9_ThV&]J)ݛP֫RgB"c{w=22c>W ܲ8~&@lYך57&4>c7hԔr|cNB#{pIoкo2D"-tLtVΞ9^>wqv̒.@ƨtgdQN PV5fmOAv{ѣ uO= 6@g%6k @Ҽ3Js ;VFl,^Ӻ]QΣOĊ bܚLOUc֓R 8#ș|aЧLBf4G CNM`HFTVx .˪kGSzhdr P'DFOz;F cSUvV{F7;;;o{.$2U5MؤGhQxHXUys)I,e|~$PlF.!K; f[3 Ӫߟԧ30^xBV'ٿ}8AƳ*}?uR&˨j_e{a׮]sO"/h5uŢ_j G~ƨ'I)aKu k9“1˔g_:%x<ӒGC&lӅ^w5NVY mi4RkՙR9nުuf1^>f9+LlS,51$*FM轥.J3V=L8|.*`/jMH\a[ֶmrV'!vvgMV@uMpCۺLc/"_y3g~ag#%ެ&{ 6'%ac)g֦uAY}0[gގt#f~uPxH3Kѿ=ryUK%X_x aJrhJq3ܭT׵K+NKܙC#9cM].f(>7&qՓEH:WS'">B{}cWw]CMۃ e|]oѫ>buL-?)@y̨ΊS fdguZe r%k"0J˚IQR(oi''18DN>ӡe,[5(@5MHf6_=ujgn`S6^CSsmҒ׏~>K :f;0UJJ2PX=em خ<'Q_cʑB&L2Py8DN58 E}BŰb h,pQH-S9y sTIK؝ޭ{JW ٵ$%Vlz@2cbIC HQX;{وoRD#Yj|>%(/Uhu@9d(ujd-2lɞI6>I,v)OlFgt/=>0SqubX2fh2gwqg:W) ?wlW`h`k֮, __V`uӼZdnnGwS I4yв8q»[s@v#؃2qҾI 9Fx8=yx@Y㿹vm) ___˚L Te*+;IQ13dCa0>%zEh1):4nj`Z̬(4kvpm j#yA':4??mشͻgJ4uWW|h)O(uݏؘ2h2ST*hth\8WIȕ+f(Fs7`^kU}ci(=p2Ȩ=zߥַ5U6v@N$m'j bҟ9sY:G$L˗Dk(UVZW{ofQi?ՇTHC Ec]'2]6)xB,ݏ*"<H.ylzį6F+@H#IL > *O & Qtjr~82QV|w`p6+9|'[?]o|#P6-"2i1d8JSq'wƕQ|4o{OqIjn};,܃噋Ԓ/b4W.DxlxǐU1xrk, D>Ou-[J򕯄&@cmi(U+8/,f,/ȝ|w o7B>3?R"< ~X7/}Ke@ey 5yU00=8~`alWS/|ܯ]!G| O0:=yݶ ߞUZ߃_ΠAO~̗-Ґ5>D&S ara26L\1#c(Ī54[Oa Twn?O.E1qer&WwR MX7zBݻw |N\3ŸwNM\Вo%FںukU&,>zҵɻ=}c##{~X\n3~})9F?}DWOwv22LL\7h{{{ׇ&1VWok{LJ_u?b7a {>kllLoyz`hho]0R6&?555e/_tj Rư7+|A">@eJ|}ٟ_~OUPWW*;@*_e(_|}ԮSp30`IENDB`schismtracker-20180209/icons/schism-icon-22.png000066400000000000000000000023531323741476300211160ustar00rootroot00000000000000PNG  IHDRĴl;IDATx͕Yh\e7w;Y'MYMVڴu6Z)7Ҋ+J\HEEDXш-Dmv62$狂J x<GJs>jkmZ~齵IݝG .i椯:3% 6׳w_gl!4o*+4;w$ZUHBUB:Ꜧc%UzDGowQW[Ųڨ,Đ~#ܩs6rckvge-T06nz J຿%քxM'k46jes} fUSdho>$J3pjvzLM<:{xuse GF)L2Y3 3Jsb"͢t\Bga8C)wFQZna~GF0u~ea9U,[EX]}#@JNebLC'@ (;> 0EE-Ev#gF <ŵK-`-R`2M;. fؾGr(C"k;,ɑq|"`M$R) PBf|f|Ѭͬ*v@H˘)esRc/YB-ZQ\*CBbjK304)=YSTbaO fR4̮A55 Ps P }h! aNRw}"ĉ$QG Vp1K51}\u\n`l2npb±"uPx1*כ8mصwz,:!@jw}q z@chmɑCw}NH:˞J3~o !x839)b5t,h")R3 &vW7S#GBhd5Khs챣O?zF~g3{v8קue4~;=9g?卟3Uu^4XIENDB`schismtracker-20180209/icons/schism-icon-24.png000066400000000000000000000027101323741476300211150ustar00rootroot00000000000000PNG  IHDRw=IDATxՕYlgۃxc;q8!m-hQUA(M$^THi⢊*%BTTTMT`+ YMHx>,4R/zB)u'|4<ӧO__$\0}'.{:om/8Z+Bڡ  6?j* n' ӅG> ԛ_;X<{GЎ}<=<j#um<`/C}mA("!h z~<^-Y1"]zګ(X|j@ol SKo{{:)JsÏIs|_$a9p['N& :*W4.TKwk $TW_?ʵ5n.4a禎F&˗_91+\>G+Ef#\M'HOŹlnc`\ȢC|R,[z|/$njefmW~&8yc3Wy,lɧhǻ]۷pbZ6;;d1=R40b!MSS\\*vk8bn.< @Ogs_!ɡjGS04M@9UBxp"9Z!хe ݛ7x^BԒ1_x (<.lPΖn V+=V$*hDmhj6tGӥc6+e> QQ0uUWR%Q(%/_Ʃ(l`}! R6R94cJ!MedY Yj[D<؊}v˲%UR @j"$TW$5*2 .").^b}ZG%JQv!ɔVt$h.oaeQR Rtg>9+<:VqXu&.`vP w[$Y %x}n\OKI"e7]YS#g =QR:+R]8(( pcr5/mi5`zup;N#Rj_l5ah8!]&b5Y<8mQs s/т_fnoRv+EGAJl)A@s ȡͦF]:?c~Xy%J#d'Sy=Z{c"&F^:gRѴ\w? ̻sgO|y;ϗz{{7#rλqIENDB`schismtracker-20180209/icons/schism-icon-32.png000066400000000000000000000044401323741476300211160ustar00rootroot00000000000000PNG  IHDR szzIDATxo\g?sx[1vi QU*TJHҪjJM*?PnRUh [ cǎd6ϙ99k/@RTQ?=ϫ{!/';ڲk9o?EUA⡻;? s=u^ƅ5j9/`qzhǞݿ߸;9*K3(0޼鱔yoۂg4̌#LFRmS֑dc8^P8 rR@C72>wЄ9x*~Ǟif~jՍ.i~ػcl~q³?{{'I#Rl5lY[ L8;8V 55dzק||lv&TEJ-}-&ug'DdYѰ[Y>_`=k |hK7m $u)q7jltwn!YeY)I&cSidI"B|磵u^;rchQ,[ ᅷɼr뇀~*={w?=}B 65I.Թ{vl^P)jԚ6 }*6" 3㱤.v<ȃL>O~|[hܨϩL2=2zFuA*+k9w"lQlncltl.Pj# AoDTne9U_yO2mVHnt;x䛷sE6k,R۲8yjVz\(YD:Ww$El6ahWO%`~{>)Ji$(`~Tg&PeRA'1b(i"5Z==!uCAa`:l AAvDŽ6/^L'f9wqO(qQDT$b]n2+jhb* Cbz($Ӳt;=KŁ"!7O'.n!EQjM^?z nC&f @P]}MQy 2ˆMU47Ϧ3iKO٬̰8;x A JUO+,l5heA {=3@2fv&YAT$EwuQ4v ȲD"'Cn]J&22ޠ+T6TzLvBCv ٿ0c(zP8$!,EB^EDAInup}rIctVBNGrC $YRQ߹Vqv[x_F6_{'Nu^:~lqz^ïD:VwIENDB`schismtracker-20180209/icons/schism-icon-36.png000066400000000000000000000057041323741476300211260ustar00rootroot00000000000000PNG  IHDR$$ IDATXil\y;sg .%,Z,VG^$oEQ7TN.:M4vtq[a)QJ%Zh}{ܙƨ&H8폾ߋ|߁ǏGz=-ҹJJcccoHn)~>1 8tza=m=OKKf wȮ?}ϿvO8sl٩swSy[u|f"QSkEGflJ*){N=>mWSIv[wݷDR3b6m ΕyvtG1fYI!BXK!A_>n#\]T*Rm.vv L60 \tg%Q*ΩQiMΔ^[k s_u ȗlv,!?b Z"چAQ`QoKR(IijeR}f݇7Lbc}Ro֟)G|鋿nf[ij9Wʌ 1w;ܘ]cݱ\aU'\4lf!?.R1~x(;S) ТzCb=w?uO%=piLC(V{05-~ٷ4r,kY!${ˈ@:R ϐI3^+/c ]炳edfKl„0w/Ch:~h!!E#ZoGή. (yqV6ɸ M&ohob٩ū$7҄~:Zd$n*)B.;ӫD!N1["7Ab5dz]%U>>ہAfS7gUT2x V% NZCnB*2մBCb馵R6f<,Nkz&FN?D]e߾1F&[$ontq}:N8졣ՄEU(nf+rVEk9||.Dz1M@tٹ6MsknVΝ}8Pܻ u]6yO^}k 1f֒(:. AXP,cP8v,&T"G:Ym zEVS$R9RhO=H™3>MFRTí)&[fSp؈FhJ@~\H&@( QS1שI 88<@%&qSJSRTnH\O8D3Zνl5Y5~0EjhR.Udѱ ;rDD*Gfܐ9v r 3GJ4[m mb5ca~MqgW^Ĉ# ځAKVf `N0VH2Ԛ ">z싢(MRx&@O'}}X,& f<n'yuh4дLޜRǧL59T&UL"0bD"ZT% D\~"Fj54ZAKLϭXٳmahkFkj45y륫 ^j<\&K>0j>,fBDR[mP(Zu'FV4iRdr ۪@o={` ( W祗O̭fl!M~QI0X&/.̸\R| cÆ%mgP?wvs!NREb3k :ˤ:N&n ҏ3LzXD "Rܜ_dG$1\_KKk3kPX(Ǵ5y?D]G[-%h6Sv/E*|c87=pr47rU%+To|UϪ~\,WV4UkSbjr3K͊TD"|T2D`˅ g']kk krURd? <_z8 ,BH$_\ IENDB`schismtracker-20180209/icons/schism-icon-48.png000066400000000000000000000101431323741476300211220ustar00rootroot00000000000000PNG  IHDR00W*IDAThZY[y=佼6\W5lY8ؖ%HR5;E:m,ESE45O- 4(P'Hⴖk[mZǚErH9w|?gb$-z)+\ xo9|]qv|s|S߹?_~9g&O=bf rzԩtW?cw`Or.ν&>ٹ8'v;v,ꫯ&>ģAOKz&ģQ/..t4 %tLkb[:=w#'Xa88ͥu,%0L F$p(wMOzvzO#[Nܛ7% xs+pNLGo%;zЅ V>=p7'v8aǂ_F^`KW~X0@O![c#8yn#5Fs-䶳Ձp4]zր  2LF] J$pyvQE>YDP&Н_$<+9?{t߽ ] $%+摙q߆r4ca8tDD "*-Mtx>\pj%+W_DѨ ߰c+݆ݢ1HTϟ] I<52zK7pi2ʪHnU֔F W ;35 bۤ6=9I渽ǁFe⻉1kH%k0~B$2:}p?3[7.\i iKbHesp: iPEf(^_b@=áCed+y8ewtW&#Op8/UeIrv:uFpc"2!EH0¥YT+dl$faC|C4 -X*u81L]t:6 F#_'LcI$۫fD4~MBZ:.-$^:t95Ԛ5X.*IRB!˫)kMP᲋Yآ=w47饥ٟ[ꋄT-:ciycnIUT&l^825Dh8X@AC"7LQl _6 U*ս,MpRUyO͜dgCқ9..#ݮ6U%M&Xlg ˁB)Hv {_[ÒLnhkE1C7 T;`5+$OL "@ƶ(y=*U\,TU*)m5(ʶ퍹<$;RqQ}@/Lq`(Xb[Rʬ-T0:0>We<gĵ]Ɩvq)A.@rJa*բT! ԕ2 y%JM8aq;/0\:m}XXR:CqmHѠF1m ]Nf)k=Rټ6M)3TŠ:V*uPJ!/ixEM`&=WWI`fSטDQ(VHU;6,6Dw:꼧='faf}!nTj+C58_.H(u{[U )G!\B(c,䙽dZ/CЩVhW8$̄/NyiwNUM w2\WܼKb v6bR.ToFMCV;3Cmpk Nc*2Nx ѩvSY$c9 uJM8#EP"h'bH+{BmeCXj lкTXÁSîϷ E]\V#A0NU$,!̕`Zn.b (TrZ\R.Ƭ 4TeD%~Qk[:& l̈ -с^m/_MdFnƐ>Lz ?crG_AJ> -$j fḟ^˕#-Xy}nĈuqN5uL^l*=҇˷VphdpF?+rs&+lWMHPfX(Jj0;&&i`*,X6ͬ݊e@p`j=څZ.W~ƿǛb_,E>P@&!0QisEc,uL#4aags:#.7 u:Y ώR)E$Fz"!p;a﵏GKr(!1ϾEܱ[iKA/wA;N]pٌ"@eG* h$XȱNgτ@Ԩej8A{݁ⅵdFsau)]-~Gɨ-DM]b)e3 a^(Il`I5vQ>$p8h$[cŎm%lj Y$@Цڠ-FG?MӢIZAuԖdYX}'##\.ȁlv'sws޼޼޼~/CV{C-Oɲm_ zaM^n,Н#BÎqj8ѱ?|K43F!P xc5} 8u쑁lVN|!zi:-B&1$iR1đix ـ񱡑blydg蹵j!7 gfh=t@hPZ"xך4ڷwSTr2՛%DƲd6*ͭnR F&E{{izq]jĀηxp.d2W0w`ɓ[J7V*u\'R˛d2s=zS0#>|ҥU =N{ʚQG#5Bfփ еeQ"%d==4M:B.bnip}5l|q>p}OI=N L3"j4h0/RxcFG'Gi|v x2Com$%mChor_{>g 夼q*{&E)PZk3KA 'j빦 -sPdknBu9 L<8Pv:VZ1BBc&ʄ淚hB A/:,oS1Vc+L AGڿ^edX<3`t I-p['ЌhBZjL@[a_^I|EO?p~ҷ|W >t'9=P;;F^ ǁ3@TێMō0.h)STrJJ$u2,eB!fAY|>0E/oi%?lf9{8WӧN;r`xGV)U"oJ#2ݜ)PjK&CF` QGȫĻ=Ac3.p@yxsjdr*e<9Q/]4wA /BњҀsQEtp7 S=𱣇 ےDΙ2yrn/u|Z(]٘%Q >RcFsGv=wLBi,> B˦ jRe \5E& ]TUh a'$$/ ::1l9Z4:i }jl׵E<2BdhBg~d{hQmke3Fo. ƑH_*r Tȹjk 6p:퀸L:~.$*yN&;%:`d2P΋E:ut-Y Ma: WIA oHt\i~J> tp~;%O&m FL0>NSgg\&''(v{X<-j~RםQ)hn$$Z$RiI JH~]c=fף~!fXJdV)u%〴e[r  NZE^PV Qr+U (à\+k $f fR]5pG'ͭbK2"dp96,pfZص#HKN("?\__v[Ԙɑ~"wK 49L]my GV~"9m2j`d@RP͚p^p2 @IIK40K@RT)b\OQmM YB׶Q`̊ږ^QX!SG"t-J*y7ņEP fѰX7P˙xRPyά0j!^/6Pnv81Wb4O1?=LNܙ= MQ0>}岎Gcf$W,C͕(&gGmN**YފY|6hNBs?)ҠN%y$lAv` o5AsQ9:9l+`0x0_ۍxsc,m&V'-Srb1FˤnZ+Qͼy ;ml$ "Hf7ӀD0R%Z9=9>:nSa]UAiw0d~f]H؀UrICUX)QW"KS8" T+B2 DߋllfY@W$SX9__ȫ@ׇj0%.5O YTCs[e$g./4Ta[6c0hhsBw_Yd"CP$Â1~7Rn\2TQ4LBpBkԶa2+g)ڑrER0*H3e;lv M`GFct)78XwNtRCHxʲY oCiSlc$8rD@_,jc: İ:Jfʣ*Y&v#HCgm.F vLFde+C= 7Jzϭi 5įar0fV."JzFn(5.`y<'`yK [tbSZ:i!7 8mFX|f . zKSaBPhT]^ҋSLG/0'͍  p¾>syRX"*Hʨ)\FӡLDAN8))Ja]:Đ6h\(sjat~s7HB5m[s2Ҭ <+`hH Ɋ*sicm5lk>xe2:@ &bpI$F9]*c-.&Iꍟ3!]\(YnDC ;|AZD2nk(BC"PHjwZM 99k XA>Wnv \ PdN/$[]xx#RIY6-`o xkK'HEh[N9[+KJEe2ntj"ŘlpUFj(\5گ||V/.@Bo2 )@L&hKE4'C0)VM;1.obWI?ۙ( uD!ֺjȊQ1R3YG2RʣDc+Hvv07_ 7H!/w% ƥ 2D#cP(\04R%pz.ׂ.W&P*lR90=smcgwŃCdEF\V"nh9>& 1`A9ݮlK#W1ѹy:5S<4r"g*JWZ$F43Ȳ(ּISob1{+&;I8`NdckNeXGm kb*,xQ "ԁ=a:E{n!gKm("նOj4v^0:/$Rܺ;2F\1` SxE$lH~7qF5 $[Lz  F3wTu%s@sXҞF"~Tsnt4z "nzd[7P%,|a!]a -fpe gP˨(-lt{Bi6xìCKb@R Wh;EMX.%QjD~J@ T_wMMód&ú$%ehpΥ 4 ,W[!ƈkT\OweQ1*F ͒-ch>ZX m32_a6#Y(6A:[_@b>>؍3 Q6`rWzͦ(zL(GTbW-,^@C 9QovX pbh;e Mtm6hVX/cOS*f,X~37#̟-c4&QhBp5NQF:># [QKbV (0,qBWmT1:"F32ꭴ*Pa$]{14Ҷtv8jD S*"N;hhdO-Wf1MUxcp?˷$V ҪbX/vkEڬ#k>o.˘VNHj' $qxD;]AChLnHsa3AQ Ю"M'ԍHM(͊V)!6z^P6 IG]l;քBց ݝ N(VSxOT*;Rb7i K/&+M`SA6CMjF)4`2/kKE<"vjwXqy㨲-*f{]%D UAʯq'DG\ Plsl?N>$ ~yy=t8Mo%IՑSqj4[ˀ6 ai{GhP:ʸEDbawr 7&&4J; C!fm]'D.o\hu1?s\?Er+`zLh8-lJu#0aͧo?ϳP |x3y*L=v7Cl>ˇ"x|5ms30@ k#/K=,b(dtƀ%`,z>䓥_]e)۶{KW/{̸g,  a1KD'Q"PȊ"E$@12g<=KwOKum]{ի5߹Uf7.{~|绷U}}w@<~påZu#$I?y`lS=[:wFU8ʕ*$=ߍf9;{{8O>Н{{Tk5z4=|O?~==&z^9^~}ztGh.dX%d\f]PglPBtj*1sJU%#byr,Zl>t[l[5;cɻ3[(ڈ ^ʎIV$gk ]Yu|uuTxyo6͔I)w| /t؁x3A{ *]C=/NOee8?vO_t#}n;uz&'v B QR5T.WA2Aul?qTkU{*~vǍȅ p4 9ZUH3S\"EɤR1_D" Wj![tA^UUnߞC?=7RFU%@Q@&/8(Ƭa邇" ?Z7@?u 455KD2 \ JbԵ=TFxqMd2 n+[wɿkVr'/у4< TwF{o|衽=8L$B1ՊZ~ /!jT-b^p;e0vεP$N+٥(% h |Sd]L!p)]=]/Ѿ=eeUDhkBe]LF#Um;wi|/ cbYZB7g#4:jP:sx!MHBO-Ph竑(M\44,dtj5 f53[+x1e\,ИKi6~vTHҴp~BAʠ5q:ǡ=yuR)ɲQU$z ,eϒIW)P*Y@'cT!Zgӂ?c1_4C+%h EzJܠTi*k%+kFӊeP@>䌼!hYc(NB<.XH;Dcںjz3 kBԚ )+ ]Y} Wmz_-K+T(+j^G a{~pф@+&qlJ)݁D 6i"z[JʄF,.߮{蚛[2I88 dno'բ6:ty#NZY](ha`ZcvkHL=p ^I>P æD G8]LtL# Lň2oZr&T+40d$a.]-7v^^'E_Eph cFØE 7B곑Gu5jY[je \|HFL6RT#X&dž(JrE+ djF̠ &fj.HYoUUi¿ R ^=sAVPFJ `f$B(DAU '3Ӣ fC4m캽柼tq!\9 ~M4H"+Da\k< rhXV ~Ο*Sz( -uP̩fȎb`,*B{cIaqpQW =zM[Lܙ *7yK$Лqht!ͼhI(HYLhT9#_9U̲>P&Q4iC. -(c,ގ;JaBS>@Qs-[D@C;tA+R A|9|pKТĢbͪ,5o¥{VUXs43lyf աR_=Q.6&X'[Kj MA-ᣄN?:}]uup>/Ǘ6j!QyXs, s1n2g578JWX\pLe'(H/*陆Ceʹ(HJ:gZJ+jSBL*[\1m|eC-4mfG?z Sl1KÏj1an鹋2ώ B)wCqJla6+\R^t빬 P[S1f6>`܈@b8-(Z{*P1OiFӸe DWټ,̩d6zƦMb;5 G2 F?$ibRo=x'{z=6v7v^sNWr$Q\& 0)5YR[M@(xV" YDQYw۫/Ü%6;bR911XŎ2Hc0%Tz z/,v0#s6 \3 s%G;6[\mDBNFo%_fea`cPovZoK(YGTblB5[ 2`щT.%5xg+&ΖPfÃZ/7e٦mѥ {րà\)GZ=Yh>w/.gbl2O'\H&E| C"3(% t6z'LmCj26}f7۬<&mT[NSx4TLd(nsK}/ޙ6y`wT ;H XADWhZ1 ڌrQt~G?yhFPXO7/⽕JW^y%zw17=ر?X'Q ヽw ))p_ ftw_?Nt:tTaR(IQ񹉁q0RЁn11|ܟ|_|{O46y=k7;9i.{7Y˜wxW#G&>?GrIgti:0AO9벹.RD8o{N׳C}Gә6\^I:1sl;>K:f9sqJ}JS?6 x=<S&qN%zuhd^Ckz1:13O;9kjux%0[#OYzbl Egi7/JŗoJU 6g7n1ŠG-7U Ku*ۿ{_;?xLf>PL'9mVkq$4M(YNM9kk[tcCnXRPR~Qe^}#[Lu/=OSfq qC4E}mRv{TvCIa:>4FWT:Kܤ./!/ ک5O^OE>!fKCwo'GcJ_$A=>:Kt좎Qe㓇Db~H,IFV"ڻU7bqҪU45G aUO-L_|Zi 5oF86WO>o0B`{;F{STߠIݼj`Nuy J%2A:&*i`aQ0sc؁4UU]lH64i{y XXX|r'Ϟ=vhSJc8*hqzD$Ewtf&ttEO?A_9big~|:_fC=f8Ç.]gpB?!Q(F(YSL6ure)j iRŃfG[}@ws6V`Vh`s3>>Ĕh I++l%&R4KXnxMb:x'^mH( v(";JB4}gt?>҆d:`bhh ˟{b2w1\tQ t " TTa3HҐ`jy蠅a蹭sbMh5Q:͒; foZW64JdaC_B+N=>O0hrSɐ },0k'7/Y@ 3:nnP(Lx2yQO U "6SX/M5{Mkee]b*xvW]4y?پ94$24A ô[ ' JIHWon68e`L`s=ЖGh<uH,VHeNJDeMƂz-tmtS~YU2z怩с'Nɿ_|d2u ŀ\UtZzJ˯_.z""y@;^x*M/yadv $Z3;yp\:' Uբ&tuK\N:φBP!-ؐӺJm!̝>ɏS(uDLF T_^A@i?DÐ-q :*"Pw'.N];2@~TViTonyhqn(k(x &*b Q652@jD `hB^ZsC6Seqp0S0Nj>y "",9,fSŊl3THDbw@ *bDV5UB8HMTIvbH(H++﹭g2h,l^,? 3"kuoCe@ lQn58c8ڵ*-X.C.]P^N+.2uO`BasCxuH* {+@aƈ:eiPΒ%XnC핀 OH!47r@U: E (I4BxfRN%ג>}QDǃz,7+=j $1JlB]f @.P)J+ 0V Uj"qS$b28;cICj$,YLͫ{jB\|8O@O-Llo\d,"z`t?;jxiDQ$ɕISŎZLx1%hAC>1b@OP|%u30,L8w#)HHTvm0겡8Ţ``-Hf5ucmP7kv!uQ*P+bw, H5<Y"!wG|A "=EI?:eVDq: \G253Ѓ Z+'fdq&8jq 25DMUF Ԁ}Z{ƻ:3a_/ dE : 1`V&mF+ڋ lcEn0 1FTבÁRbI舥0T3h@ipl65,  N"*&6 ty>ceI!(jXtb@ka FZb?~tf P2f#CC}82t:U߳LN:)|ZJ RVx`m&nr,@PGPKP^hU$jޥR@z#IaM$̗UQ)c( uLarÄRP݆Upt?{KV-P^0$zK6 5eҼA!e˿!A!Z f)u0 <9!i|~ D;0sDcssq)+s lԵS'8,g4eK\Mjܶbܰg$*J YԀNM73Ӿ$p=T=8($b `"= pI5$sda[kӅο /u"{V򜐱o@Rn=&*f`S{q>qYC{OuX CL0x 56gTj7NB'46'Ta AK!m& TO0fKR9' G:(Z+Ηe Ņ:j(&%B0$~d*+ P+;XU-ak Բ;x%O"ֱVbx߃@=/+_c) c bn{8\J`a*SHZ͈f/f v$D 긎HisG^,fQܐ漨r|GQ&:PӫWŹl0/lklʆr@d4In0_כ$?n[Z.*[hO>S![V lj1n h8'+B5 B!utd1!yA[#;)[m^hL*9ٯiꀪB޹RI EMķ!Iռ*E+tuh7ef49+o얖^DZ4){!mW]F_nV|E ?gE'XŊj(Tu`?j`1$w*`聎[$=+!ys_@.WPX\ PygOTa~`I\CB柍gT[N4>peiٴN4m 4kjFN4ɜݙ\A6 UtҵϪk"TT2)hy0ԧi`C>VH$m AE;ˬx #?t(iHH/#2{ylcq'<$%U~j)igTe+qG2MF>39 ZɫhkıL:P|#gGSNR:^h뀇NRg \%/[u@UN JS\e1=m %bDUj2k үnLIC$K:$0&6z@˨{iN,% :; &DX,U}X\Z&0֏LI絕n?cq13aH-bFdɓsPN<7ý^I oă$nQBird Ro{|9g3_Ά0=`,RW7z #C*aq8%4 +W,-2{BSۧZNY  0W9H^:$^N<]]+XӡgzN)3b~vBb?\J8U,aK[s(a3E !(JVCF%ʊD]je_,H@/Vq )bDy|w,ӋLTЇ + ƣ JmB.& 02 sUD;xf(LAwbG@DL',/{hkd( ԍK랖cU8O( j'q;ѣXdՋjŦE w+sQ7le|g,e;xN:yJVqqNDxډ@[P M/VATzѹfgUh= TkOK9Z;۫c}]K+[EyzÐz sb@$dx7yQ*P8u4MvhN(keuEb;Ϩ0ät z+РiZB^67 RJbB\')gl+$nS#}ꋵ"uA0G ebw(rӂ55&EYA)K'ſ$VMт㳢ZMi/4̽IvFIrc+aԼ!- ٱ`A_ (FйVa|ҹu;p"Lkˀ O=ƼӓVGdzKHʲOJ&\cm6߇n3A**oKʗ.46Wѐ(8s'qIa׊M9,% >`TZj"CJЕycQ7,qVU C C7]rpaap&z̃QFN~43hKvp0CKCZt៙UD֙P.g9k㺁Gف4è곘`'#~064KN L= 9k ynsm C76PDdV6fOC亀/P ((_DtNoRB \?KfYTZjdeOk yZ4cb ]2 xp6SmNm&ǭnUǬmTXG/2p"u\˪Z#+NsF~ <*Ag,D|q.M{|,Xb8("ߗ~AX-lpܤ-bZIeݝ}#vD(4kwuH[odu@Uȇjf* jjY]mhV|!@~ R8SpO$;?{Txf8A n-{kffϖ=hhʳ@UQTVa B8:L|7[VJ <#>- 'LC087FThq{J'nʉӬ)08hanmˋ]M D˧|J#, W' qo~Tdkt 7`z7ԣ}@ )nw(FSKc3/=GݬLkI9##ou@Uq}O#ˌ[ܚ˳{k㳋?G>QQW.|)*<\ȅ q*v>F?_] k)̊\ʦ{[MW屹}S_#%}ñ׮W9'?S~}1Q8J1<<^U&^tˤԙ3g>q@+0@{ schismtracker-20180209/icons/schism-itf-icon-128.png000066400000000000000000000444211323741476300217670ustar00rootroot00000000000000PNG  IHDRtM=:sBIT|dtEXtSoftwarewww.inkscape.org< IDATxgdudUV7C HPE")ZR+zXFhq?l6VJDIAAL鞶]]>>~xYkhQ329RXݞ'݁?"POߞ8/_;P꫗Ba)e}?_~*!>Wӟ~H'Ο:O}*4 ~ert3Ǐ?85e0=6}si~ P| ~`|"N7N=]8#Z6\K4MMv_zWG|"~ӿs鋟O UK*;oGW ?z[s̶?w$FF>v__+|S㺦 y^|w>K8~h)%B5v|~(Bу?䡟|'M/P"ouKc܅:4)ϯ^65KG^BO [^͹ynׯr*`bʏ|"/^O<8}'k-(Fk9:+ O(;Vhkǝ%8!M%Hzp,Vf@I35GLcxgrV]\%Nb$R80>cz2š=_̈́aN8AH}wXXYͶx)׷(IptMq,7gXZm( i0Tpt8?"ĺ{wo~%Ɠs'~z[lU9q`/ ׸!seZފS/P̬1 a[ܝ_suk>MStMe0N@>afn?@Jt]cObZ귻a ?~ojl@7M]|9՟'LcO]sǏ82?k8 +JRs?o蛭gMXF\s0:4xpѢsH\}6έkD:%.3:Qy.ل"ϝHbH2A i2!rs2swjS#|3HٔK[ȀJ5$D!JC)~ۯ kZ " #E`k~\yi-_N N9 gO3=i*a#SIZFeef% {OkE(~[lP-- E(q ?) zIBkNdRDKTJTg@DD5Ħ)}w!RJ)4`kZ8NCݎj!g??{G?ӵ]j2:T}J mVR$S*8T+ݟ{p<6ay*% MUwrWFnwgmBiĀ;YO#/w(*0\.}~O ˅XTOV1r9z[k,ᣰXW Pw|F e؅_ )%P>m0:Qs{f wDzeྒ/3|Ͽ{)) gN͋'4[|NhIإje38nNuJN;;p`8Sãs.%FIcr2xrl SNDiT"!%vG}s1T\Ŗ1zO>v !4A e D !M_~mK^dw=Unr{?~c'! {?|{u{V-EZ^s}D0_\J:@lgRJd,`zzGE^}j0qZFͫ[&aj>q 7s?9sN(\3`P;1nxe;X{%vmj 9})'rǞ7&+gmkRR$rpfR@&4y !ÐװMF9qpjM{sa?7彻Yiq{6Po,5D rƙ98{[#m|"W!cܯ~\+L<]J\?}6 ;y) WK喗ng Bǟ/=菉zKzFULӠ\0]=OxokQ`X/zH)QL2>40BJ38'*Ӽ.I Aeb2ir<˝]E 8pme8:=j-'Ok>Z'[sÃqF\:3+a³S8O~:~0 ӎsxjZ->' b!ݗ`ʋ7y|?72j^aqb-繻R 46i͗q/[u1kYbh=r4v/>q:7QmF?r!c'_xAi"S3Wwϥ4P!=lJs_bM #΍&ܙ_(4`rd'|5{49$a= bqaUV-.߹K 3|'JJ`,4M% n_,`tP |dB?|ġ[]W]^E&)f̧n` M\? k V VJgXYҀo>V$MYj3MJG8yx(&s|nVkz;Or:sHK 9Qaq`եFK$RB40䏮]$N/_UUU~?~w_]cg^S?\m:h@0T)qb,ω?cmn;.aСjRAO#`Y8EtUe|xvxr zsϔ޿އ(Np\(P0NXuR`Pj;7nSH4ݒX,P*XT*E(wRG1R[OɽέaTseۥL |GB(O+O_<+|"EqޤuoUS7>̉S,.-{-*I,~ai?qYEۇ8q`2v-pkvF wGjL U|.-Qx{!i˜ T1M$ޝx#W;HLˠ` T1{.E`bl]&Rjp<>(|~cg~ǚ][]ivHrbrh{pU]V]4 &$)r:k hF1'Mq!LWڃCޚb㑦jhTehRW}4(cTbr$iS%S3Wdrt `R)1%cHA&e*%ˠR*5RE*%aȷ,CUL1_mܼJs=;56CS~r H^yAS,6mzN gVl4لq 62xd) ' GF)NcqNq|]! a49"૗<򻇧ͅ+3(oC;w|j`}_|cO?'3Gsz?v޿hm3su]٧ˬ5yՄN=CʌE ѷTU]o\x>+f_nH J!;uxaiʍg1__e~lR6 T aFwv4 WQlF{7.af&'޲ Er4] €0ݪ=诼wƶKC|j@{ܑ}jsyozNgKVLNLMe uw~,`f4_7Xoqcvz$ITGf"iܻc۔ &8Rbrlnf~JGqXk IUUTMŞ74A$5W98Pod1BR.7rQD^񨍁fhH$EJbߥ~??#k_{{uf4;=@/lvcÿ_ǿ2h B+(ZMNCѶ]L,ԡ)q̍9:"Qr}٣5]ŝ !Nx-4ϙ6#3;'Ѩ躆 ;iH$k'.(@Du#qXb;.i#8afV88>ȉ::=n,w{'5ݻw#5i`\JQJhBduu@!Q]zoE}{!*c8~hr]S +,.qz6eCsX( JJxv9^!d?O k*1A NA&,\U9CU[2oP(P)g>;==OzmV~Ã> x7%K|j@L +v(b+]i4Ux^[oi5 hZd5_Ǫ9ZNVܜO#aDm3DV( Un.vGЋ94dhAu!i)Q!IQ-`9la Y\fjr@4׽E(؎rv; nyoۛ;X)({v0)|~-+ji"EE:}cC`sd(ڠН0:'rh\&hm=aQqWαZz!z1:GW59 x60(<8&* ^׊C 0Rþa,5蚆4-lua1@862篳c{wT!Ҿ}~{vvCm6MӔe22X&J/n7dRݺ072^O|vx(7D4/=q5M%j/tu*c$R8ıdT V;]\?\g6Kf` 1=1DA xe"`͈{f4@t}EErt8o^Gӣg;x~\QBLsTJ%$eHo\/#Sg߽QEmc5 |g;A`ֵ۬m (!MS *ˆ0Yyܢ=`XSU\KqIᑷ A5t]E~f#e*2dmf^KG6>PLJRVXp=( 0|tc@x\XxY\.PFbcǙ_n2aO oPxxQJv@DA@,c"w%:oFܞ"] :"0M4) y(bݡk*0&8_, Ӕdf&֖h˿2EU,àP('u=:%8fz_-z1ڏƌo);455[sssJ lFk^zu1{߻G ^"@JУ3îVX),ZTz.gyiP*YhFcY?:.! fzv,fW6e7q}nf!~mH)c Q*E&'Fvm&듦{B@)M8ts Ob9$I~y{ ;VBUڲN`|wg?*C UQAJP-,[ Mw; S{@!a2m#҄ngϞ7jˎT6&~&  P+7o!^4eJ3(Q&A, P"1 cDNBXIF٩I<Ó]P+.T:"$e'/Ҟ&v;rVCz(Bxmc<0@u{ #*ȶxӠ\* a.vaY&9U!~FMU ՞X65Yv4rMz0AHci,gٜ=#X!dMP 9-Kzm;.[t:!Jf&zG~o^/'Kdj`OCs<긶si>,45biCn0%0DWlR"u #Oh( >>%By(^l(X 9,R"8.vq OD#l"8!L3Ot;픜ZFAnP+* iYmssuzn&}(<_{*? #Fª?žn]2n4ÈՎnj+X9r͝z^1,U!c~#GYʆX41yףbi0#ڵ)uu2)X&i4hCO!tԜg14UETUqnj6HM#u~.@/YXajth tMV)3T*0YR=ZYNBq7*m1LwXr|x;/#Հ&1$Iaж}ʆE!W:\?Ov]'´EqB>S(A2 ÈPoip#0>EhQ(,8NlVmi#$فO?uVJqJoۥ1s˫< u .oiv{Q${)+$J簸X7MFYiES*v,,fEriM2^Џf o{N PUi;J9}d/x,=Wzp薆ifzYSTVJܟ]"ެa)&bfKչ7@:>Bj=F1 $ rBͶM\*{0SZepS';$ܼ}zkdp휆eXiJ%F\ 7iЯ7ʔ3 D.*}.>e{7W;%:iP,X躆~QnB6f&`Yw % IѺIR0s[۬\حy]cl8Zp|*5Zj!&,+ >ch/BSgoA1L]gj _>B7rFB"IRla~K4P*Qvt- iOg8Q3n9?(vwE4( SZLř'j)C,SL#ezuDnI>l&fJK6T 6cüؙ͇ViR,}/IR8#)HM0gQE55>_M#׉MSz 6,Zp%E=ގ*XIz60r ihm`K'![VBXZYX{)IEWy8NAѪ!733X,P+Vd'4ht4{6Q?sx٢0}wűy!v*(, AX\_aQ_m~Mvb[{ݕjZZU <\%=6R4 ףӵq=oYIl*ed[|"zK{v"3W-PC#S*QUAkSo4q8o8}02O Z#e$PR3+4=}/灌nٿ[x,S_]^ cZRq 㹴V:[OTOnoىY>Y훗1#ԀM*3A=NMQA,Azr 6H54kV}ScIJceٲko"fƜi &A2PDL)#9cv\:]Q*:9$ŇIۜTr֧^3|39 Gz?U`溧v!ly\VpUJODvi0P.mA򨺂{K2@n;y!sQeB?J|UJҔe %d?b+a8G簲$E0)3h(s-G:RJ(qL$ _;p϶l+E;XK6a+Ss/A vVCE k<0E>#e78){>I5\̡~GXF6% fV@Jlöm\7s]y5 [2'G:~krtjT=Q  8ΖjJJ B PTA>2 IFaz2|T j4LJ9@^ԼWol ?vOZPnUQd6!5]+a@q\$*>섘C"Hj˹Ӈy VɎ1S Պ:ܺg|ߺfQ=3j@A7X@l{M(g ~9da%*0a")Y&pHٲÐ%' 9"`iM$N^VWxEAUMƲ SNtc 6~F8s]X`̬A.={p֒]҇&R9<Y˭Nu!vZP5gquhj@nXrn1llxk Q:ۥd -%ʦY깔W(T#+1TRyvi7D*520w?0VQۧ!4:Ceq#i1,̳ f2zka'=!:P2Mr9B\TԨv{aG5e4y1'Џb&GIqY^Y%诜 |"r}:SПRrax+z!,5l3WIM+~ZPȖaoMzޠ;=-u\Rѷkqs#w8L$ WUָ69B:@vpګn&;ѺAj;6,y#yfϞ)Ŷ]l<|oc,%dSV#7 fU E`u 3 ֺ$ vQdʩNrzdkRn7Aq6)%l> yN PiC9U% Wu\gݥ[6K@S>0Š"€e!E-Td,f nm=o)ݍ"Lpuf4(Lr$;9jwp]#C\ ܨgyT9gW(@#gϬ q\}8?ؑ-!RC~hMs~>PX#@)(2EI%jb9 jwP;ʿ\YL_iF\m z 5*zBh{t B;!^H03eXUg,md ˭< 땹6KEyuWtM%Ba}t)1B]Uhg?Ve*dx[FTxYnÈ5\VMU1273{-`RXU~0)n/p<\V kgqb#;|snm?ǶO'(P D.'xMriH;,ij{ #*\H*`l!48!f:e=ܫABx@B瞹ɇ7ԩ4l'%LY}"cPݪ$RÜe~@[Ө+U^K#MAF2k'كvLk~h_g8MSlay6Ƴ7=SX1 Q*w i4xh:ufC>H$ÃeMrhjG9811RytzB1Sʎ ;ݑDoZb:ws!g3| ٮ֭M&)jiYQ1(@7zWo. ]@ ĎeYD)yݹg83gH%m~gsw!.y/ E,,L8>}oOa4G_hya8N%m4EѤ4 Ev{뎰vC#u1hW+ؖ\@K^(J]&ʃyX v`& %el|8-RB V1I"veqW0M iNbwRaID" E{dY lgr)\Zyh*-Eo4[[ Eb I1f|Jw6hhD@oZOLc!BAC&0a%β_a^Ϝð_( 2t_0lPas] q3]x N B)Ct~ g]?~o~i[2mXvodzJ=Q| A f,2+A4U&XJ\a63)]"_^(kuWFEkV-T&Htt(@6ΉGxm[zBd2Aӧj>n;5GAq `:5\*6>r~h}YPzEo6Gٙpj MR8m?o+ s0ȱxQ1D,ˀ7D"f[fTd:BNw@s0Nx L#˥0?¹,fk8^Ptܾ,TtRm.؇s Ke7uP+_"|L$a!= h2u=l,`{;WÌI[XC,,w:eeSNbifX ;[bY+q饜֛oyqm^K0 Q <,`5lZѰtd; c_3K hde}Q誢T?g?Gog~U8>hBE;Λ`8q2p=H3"3L&dO5+:Yت4U˄W/^/9g·AI`_Ld iNO!ʴQo`Lӆ:O>,ݩ[8xְEa6;$gJƻ|< +ZEe;rip #[^{ɣǺ|8E`V'3,m.biJVO@ʘ$17McT:3!S4]-ZEEF?ѝ'NuB.Pҋ~f]X !ehjΛS2If&17=lG5|!yE%+*j'|3Q,<}˯~ p[87Wx9:q)YYK^RT}"|_䳩Q/_hq80@:8n0..a hƎV(WJVe{ot;m62"ڵkEz`"F.=t3id&0Fe?RPXA~/W>e6 (^ziShzT $r$SN!5`R(kRf8w?٣6@⋣7=@ti/ܧQ\ tt˳G8m%_V euw}닋GY @]zuS !a2E,"c&D<)јIB^[,(?[.B@FzcCf'+]+ww-ws72@hh=sONo e[Y* 66_~篽oG>h`-//?ZSFJwwaiw/pk֝{S(b ɇ˺ ? .=ϕI=)D"Δ} < hNyhuߟz.C/BqK$a\9\_TS[u3"b$r-i2}եj5Ю^izi":'~o'bX*wg]j.p8>YXXؒB>ʦQ^\&_x@"W2k 0wY(21NRZql#&SHzs%]Իr~"5@(aȮ{MIq}JLyg0D!@(")ȓ:n#:MngXNjZ Cَ1c%p,! A=Ѻ]cp묦e=nh7QhFB0 u2_ݧ,3h{IQ!vb,3 "yTYi K+?wlRLތsVɶFkvtg_|2I{vψwR]9] g9}IENDB`schismtracker-20180209/icons/schism-itf-icon-192.png000066400000000000000000000752451323741476300220000ustar00rootroot00000000000000PNG  IHDR`"-sBIT|dtEXtSoftwarewww.inkscape.org< IDATxw]}97:n8`0N#S\J*ږE JZ,iUmIlmVUk^jWДL(9`9ts~9t:af@ *w=tx PO4%/>uϺBQʓGZO?>~O<~ 0_ik'?FB[ғ}2 *U)%:z(+٧FG<IAqY@Ѳ:rxOlbY|7N!%H)iiL8ĭmԧ055VC?wv|ǚ;[X־[I.|c\)wrd6|t7j?օѹxa $<ϫJ)_zHplK_|mwoGGliry9._Ic.RJR0{:y^Od|>ԥ7iCw|>uE"k|sFx6<Ʃhj#oEގF0-RД}`A)LCvnqġcӌNa;."7B?5ƻS3:С t1[;ֵy= ~MGxRe^:uG* !D[ѓGi]az>*(5n:.hnslgoors$o2>$9{ڕ7y#IDs[M@xO:)!D{_wz#ϽVW];C7t ]CJe;&<~p'|ٮs؈&&Gx8ѭZgܻ<8;qp黲e382uژДX^AA$xw'jk؎'=lǡ.e7;zڰl6"؉ )%o5ϵmn~'ZjZ6;s)JU sa`7ƘNgq]](zG~qx.FKⓇ6N>@Ѡ3"XZ~uF/2Π)vwqQAkCwoux&a\9Œ ǙSyBG?ػu]W"/`:j]Eq\|t4pmx]-mmLbnѠ3H$ݷ{ldtjvu)%']TGvHT/~`ȓObY,#zsY(o}<|xy\ESԥcұU&2imC14e39>E&_ؿvᙏcXi,|̙3 z0Ζ_lnn?80[s! mMWh&yں/l4FFJ|4v!+N0ڕ;=}#e}IW(yKs}Gv4. 29FUBX$Ot|ydSsM@H)ٙ3gY! ޾_|Su|s蚆-Zslwp`{7ߣ*(W_m$o}Wl:=vn*ox9,FT }}8M&NMq>$B MShRv: 5m5a?x4 p_{vl{ OapZ'=R )g&ϼO慓uՙd2UWsdguE*KN|x&3YTUftyGC2ƁlnEY;9+d;z۾~Wǯ׾82{.7Jx=T!D[['7D.QQU&0jƻ.qs=4y]W}X$4r-+B8. zMVk6IGSv4M<)9{yCgKt4=CWJk M-(19H`hůۡ.ƞl͚2u,sTmUQ|_7$ٶ;zz7A[c&B^Ag;BDCu#{wo{c6ꏢ"0vlv7ƦxGN~hTch\DxvJeKE-R.m;\;y#H4ڛx.b & / ^D"Mf>h4+DᎢ3!t7ʉ~57/w?B "T n=(BH:IN&޺;q) ;G}2ʎVw-[q`{7 T"̮m{g5ێ^d胙% 9v@PO]2'I/2Czc{wA4VzPDZ}$b۶JḎ;G*fhkJؚSl .BO9:.e]1z,*I%y)tq` ,ZHfwcY`f9p(ŸS=u`fn\㝷._H׾HSZwQ\;_e|zrL~goirmJ$Yu}Ƨ1x3Ij,ˈَt=YUG?4^#3 : A.qD I+k8>f*XՕx^p&e/3Md˟5EqFڜ=}iԌ byZIǙY._Xuq*W}\o/zU!Ht֑{>?502Os_A74:89.~|H,#U(Trh4M%͓)Wq=!bEWT-P`llZ |ERbDAEk-%Bc)&_翓;X PR9Ɂ AiHe 6 q\ySko WV%# E~ݢOp]O"=bDf!XS(%1m4x0;7'd XCsF"`h|ڳ 圦\MD oh!Sּsy/^7O? HtzWWǗ?䱦m NҼ%:a ]#  eBA# )%àhuhkG 8'>?02=K\YJtן@ʨm>&Hփטq ]֏-/q=yxD-13,vJ"Q([f2|x?@Hئ0\ȩ/P|,Rz+.<.^FPQ !Ğ{vyGsC8_-Sf :XEQHgge>! FY!U4>m=1!96QPU8㨠pPKs|}=] A 2^Nv!KH$x47KY)uj*7闎jgu̠+(a~+¾lZ+BiKx_o?w;?'|LcBSR$+% #a,av!O:}IfRI$"f )bߣvO} _~pS|}r5EBAsxS)<P HH\c.[1ei/VLJxTm{%DC3t6\iРB͍u?_Nd?!y3"R%gP"$"$/)DǗ.z@S*=|pmeTT~RZ8m&wLQQO9ֺtInS)hh8Ƶ9& 6TIscr!C:_D];b6=^WBAoNCPeÞ۵k~O= [/cxvlBF()N FW]gR>4BMРRTU=th߮?O|qKOm,W,18:8 Ø 硨? #P05Xb;=즽'uhH9MsCbò2_EO2fmpeRJ&_4FbAl Sb@k\vf.g!_\<^ByFKT!4︯2 z`B!S;3~}xə4,pdC׈E~^k:[ _(QX[Lox${{A>L\a›[q3z7u-[Fq?sggR)K8=LϩLS KMsyg(RmϮ6J鱿YۙA?7휻9s5 U,f<{ի g!DîKgojm3. #q $&˘N>T- 2]W x_KXoΞ&>zbʓQ /ah*Jmm u\TM)Ⱦ#6h/]𯇙 5d<̅i2E0FH}ϕJ 1S(W,y&Y؅*zdy4~c3 @UG9eK$( M NNo /-(+xd1, Palaī'·̦~ɿ|e*$h߹\9"燶tlղ0]S([6oyaF ?q9=L&KPBzrldZѰ UU0LMS6K;Egs=Do$/êBog+~g/ߤjh*ض~#Ӄ2R(UTZ[ GC$N=,%lufo*)k^?_1ڳ%r/uwҘ8#0z2Z~ 8Mvhoi@tl\ԘIc2D22>?\6O6 $ES0زm̖]=}@ ,۷k׮]9(qwWNsO4'|9ߌY:lph/(r Zh4Mu~Xgl2tͧK㐈͞ms}xم UU6TY$ ^Brt=L]EUI*k:MO3tE04=l6GTYNG{g`$ JPUg:.m!>kxCo4MӠEBcv_ٟ󹧎Uqctk74E(Tmx$ȞNw@_gd@ ~XȞ>ben+/2>3OZEST4EjFM/«C>_qtUT*Mޱ PB(/k %G๋ko $1]en~\H$ji_mq\52? ;ښm~_ݿ#:02əKX%D\ץ>ns}{bL6|S' RL)}Gղ Ʀ;?riҿݽ9oIM0.#Bv1vue%WG6_IJlZSbv5=`Ezmy]1NޝD"Q5p8@4! /|l.OX¶]kLٴnaO_~t  nB__o |4)yξ⏼+OolnLjO'5AWKw*?{yƧV IDATKU7ʨ K:16I泾wvU PJ&fӔJU4MESA{kG0M KɟuLMT@PX- 01ko:XyxR-;Џܒ5gU!IcWJ**tBDj-GiTuaVKl[xAѠ| {V`0x] gv~# b3\@SU"mD4#{ln W(G(fsX<' J~hN c躊erT6RJ01?F)V$Z-GQU[HX݉bh|+7ǘY".*v]Qvo렧v/W8|ga(Q]JU a3tN#.c)A0Ʋn_R%4q47ﺎlbDfI[ T3D`Sۇlz[A!K~푁IN_YsOUK8`rqp?+SzpSfP k^|6m;KIBJ*r(U[smwLjٜ4tR2t76pxOF-h=Rrip4U1-Xy8GdQab~+e8nmE], 8D2NC2tzN0 |GL|HTu->ykK傹w]ڵ7ginf za۶m'JHl/k'_yxm]W롛ڛٽוy#\5˞aQyg`P @}S d!W\⺵|[FpE<''_;N>qn̷/A*~RI&;ox>>{a0RlHԂV<4U% Mc-Ϥ)+5noYaäGwonLUdYt\Db9%&g*h6媅85GeBx|nNRI( J2mk5-#m)RsqpF>kІ 0;[~i;áZ#`DA5N]遏O֯}Z$<|:C>_\:] QfG[YevinLq[7nhBڻϞhۮ) _8veקOc<f6_Cz¿řR<E!h$caH4f5#Wo065GPAUADC&)E_"J*g/ %ŋCdrȗloRz̤9~q\VmmKQCDCtz|l@T^H C'0'D!\JtrЛCiu>!+ol6 $iВD"g?_xp]cҧ9K[WuLhnN{쭈|na0[~=Ob*Xb:ͫ_cD⚚_afY( *<qԆ= O0=3S0)z|,CR^WOs?>/06&](p؝A]dyMGB@066JTZ*HeBr88? M]+@gKMH^T_پojcvfϲ\!8KK}h$F~ <";A;r)rstxI"K;Zm&Pq]>:R]SPe 75spGn(0B+EJ@p}zzHKD2KGj 1˓+kcÅ;7膆"ۣjQ*m]Ҙݳ+B0 MiY6 ,#hjR5.<[:gdgR[vcK_bRu#[09иPIS粒t1OKjSfEm~f񤇮iDA"O\`6]XTISquTq'=en.-R hB)f 7y3#s؎>0 uBj6E.m@\!wͷ?çB@$ JƗrH/d)˔P*Q+065O{LhgY0MK;>Y@3MY<7FT9W<ڲUk`j3$rI]Pb .ΰ iNA?z!T&B@qh4Z@c.] [GyVt(yA-q]Z)ÕcBJ\*%BMŌS\\rHDi*I4&X,)*TjA ȏdI$;UV <7Rf@OI@30kkiΦ]ePBtT'v?"U_/`^jY\&a  " S yˣdϪ*V*γ//Wk]{]%.&W.stj+HisAxr9vp?Apݺ= TkHJԦ_UU L x|)\)JT*WwFc{vk݊.)vYW$`񏕻/q=Y{ϓbm y>¡[i} wO3JCLLbE4Mr*熦ɕ4Ǔu]gJ>+j: bLPg^-Ǐ۫e}E03aZD=-bQ.U$-*2RJZ枡ƕv{۴nK:::~stt m`Jl#2io#/2m؀ğB=)r%+% 7/ X)`ob%0we`Ģ59|4'4ԘA:_`"@:_bٷ, CI%"hNK*KKLm[5Y~ QU%.,+6-BAg %JR!~b X{dU.H+gK8y+%ilr'BV-dݽDB&Wn1,4~CbG?me[wT풪%ԽsH3MG}=>6ErŒۢ8eBETfYZ **Ux]-F"DqXF$YzD&WoJZp3*PA5&Kj8jKji ` HkLy9l9uϓhjۄ*ߍ_}ٚ)}5ՍsJղ)B M$RQHUi=pҨ>^`&'S(.-TL[X-, &g :Wo2:Dbߵs r}ɪ+1 %R6#'EI`P(@]"RXta2qZ53CwzڴoLoFq^jooͱFw(ˎb㺨B,bgwuq>89b}t5ݣRcҹ_J;ZL#&!L{#Y7ŗyʛg(ˌ͓)(޲*3`U Lו8l&G8`[$>{$>NѤ%I˜BJ /8.FS+=:kP:w.r(nN͋)%˾ LT(3axLr/Uv[<'@5tC'UNY(T-Kw@U)K凳$wZaI䭡U[CXҶ$n283 9 3hL$ ؔLO%Z,er26VJ `CZ|HP\QewGiGs w2t-;yɮKsXX:c\U tLR!]=tG**ѡ2t.}iѠC M79,}i>ǖG UB ٞU͝hjF~f-̐ i)y[TsUܢM$&h& (ݭ -f \8L<]w* Ux`RSߘL1~3Ʀf$diܠBs$Sw7?ē9҅Z*ԭYtF0w}guX7h0C{e@bʲ,t ~ċ7I8$Q mˡԶYsLj,d]胆A2!ޏ0[Y6_KqO GW,ߎsvv%T-k7(B  orLf,d TlW4 L(0 ͏Ol2aww UUMB xxi6Xۮ Y)q-Aʝl%7Үs)bBS7px4TsmrM|l4'YfvHF2!vZ|O,Pl*5lHPJM&` /T-!TPgb!<˥S`%8GZ-"i`:hm|1@Hak~D.6t hAd< %BBL[0K҂R hJZja8W L8zI "\rٯfLbj/lRv\\{LD$Dcͮ)VY5lwpG$u ^}//ǿW}/+.]/a[EPG}ޗ$I&""sfCU4f ZÃZz#&g138НpRp=Oy>@褠ÞEZ'LYyKA0T ϦrOjl4 qS{Jۡ 4N1ggh `O ܕ}pf\_X^~cmQ-8LK̍W`!Qpwuff{h׶6)(,+|4 Gq`dayu)&ECe͍`̍qr\qafZC 1>ۅ e`Y&r=+a$W{Rd@.\U a9"w(st'qLޤ~c'9@LIݺnYjaheG{XFZ-rdfgeM ыZ IDATsgmFZC8[R"jF.0ae>{ž<99CAf_C*Z٠)ڎ#+6e8)IJ {ZzTtG;#0/ /-)[bY&y'G1 ' Fឩˢܲ2zϕ(MC#A44U$ ol>a{k؆+cܼ@PYTjj9F^p:+H?L}f;+PQBjBJ7Lht:lǿ -'6qmw-?2t(Uښ\rXw/&XUꚚԓ\{?s mwFea}fz??X0Au900P_5?I'Njn-{AMLL|{}}aPj ޾FJyZzI 3XQdפ,.? 3^6 ' HBJiro~I:'h6~Fλ4S\/n6#g膆mbA ^Z{_Iˆ$Me{m`&A֦r4?.uѱeJŞ ![66Yi5( J}ūwxsEn=V۶ԅ,R*Rj:OOM ?c`e/%4m7E:uFՋ./&I&ao&( JJ$w]qlۢTg DJ!*=J!v#4SޏY4Y5ȩZ&.bݝ%c"c fD2 AuwSb v )PrlscUyv$,mXm4^mjG6X|^zjk=,ݿ%wr)k7i#97u}<ϗ;av.t[}/8wrr`/_s(Ŀ9\Sn8ޡ02TT()eXE`eiFJow5 .uBtU!O=嬡ie]Klzz$~Wlz{v( %Ǣ\3WP)䇌-Ilrh,Cfǫ{:@:N+!.\קh{r, g0.( Kq'V0 8Vk}eFFIbkfk;^b:`ALÆ b+ߧZ!RHQ- M#>8g N;̍U) wijP5}wmSf~$ߣ(X )o7)lPF&s64HiޫS}rb_&gV78!=5V%" $IړuilF0 UKL'g' z ð>Ls(JDLWq ,> |<<}}BTҢTy-p¢^̯oRoT4yZH܉鯬í+bzJ *"aYkzT: yx6 CrTY VXZ^F~ү1xTT;C޶shF!{_ۤޖF&M)WTMרq|S~ GlDP] ĵhݡ_'wo,8&aо;'Ɏ. u U?7Jt[vuBm%dEú,`B+#iJFusR)M ԄzC{^61:?(蚊kLDuPX/\yc_5BrrR%.̯17Q;ka7jY4c O䇊ݷ?<<~jZtsQSUu'JTfs0HO9`C.gSpr(̟GeiKUW)FIO/ك,56-q=<'UvXmzF@n /0*4r+Ɓ<!$a%[T0-d\| ;D4MS*9c@Ð b}F=Jl>?;+TMrs9ۢR*.iFM-=܊I4L@_ϦNTy[W8#H͚$2FS|wϿzpzZ}Go/ jۻuRς Yur6r˒+P'}8 s(y[ȡq#e8y=( izU`u؋~ۋ"Ƕhms6tm1]@}O(S.:(Nt;+- 8^L^$+GφkĚFDā@3Tr-\-Vgq\Kޘ&g* W(RsϾ]~me}AS$LLCHP='dOٖXO㺽a2[aQC`"{b|~JQCTj@?l33(]2M!aD={ߞ/ۧ \v>h2e-Yb~Kh zSp4j#Jxӳ8iy8rkT:L/:4HUӰ &^eCTH P,Qpbiʟ?w?`j`GeJl+zr%|?fih}kM:b2w' 3,rH.gP/@(CF7)VgrG \8=s}al/t:;A!$2SbZee>b+RN ]0HW$~jj~LD7jDA^$ k:1=/Ǩv3{ ]dBKŰ4ȬzVV:` +,rSrie* BΦZ3]):EvVFRMp1&'eJNS_f/7/~eJ4,:+g$m('',& p| U Pt ˤ`ֲ*{~a~\>]v}Dz%A7é LaXA18f ,s*7 S7oȪN)RpVz{Lh{x'y!N83w 1k։Ins yN}@C 9bJsgsS;]@ض' i:C@3 X1T)LV 6ZmmNlbyf? i_-c**{rrTwj?hZ!mp :|cY^p8unT>XpPu˪eiJT˪l/V5.>P~3 To ܥ3$T!t]miI^_ij^q00F JaHP4(96|J'IfV[J1@vbJٽY1[f-)x'r:?eg?6{vjwwPnw"kh?rݤGwMWAe Ml1d/\83G!gZ-?k[:1@4E:ቲճw,*{$MXܬQo4\? tq3뛜[.j')9jMhuzj,a v7+auIF/ nS-XmR)pہԛM,w ךM4Uezrh SmۤR*9 FA(#pFNJMdzTD8zN}u'A _,7`T (@T M2eH0U-c)q\kPowhy>^&s>S˴sp$X$PU6C4nA36"t {~{s#8C(@guy-oxÊrdy9UNpt ga;R]r m/*+/ޱS؍÷m$]o'BK y $@& iY ]3HDxr uZ+ZiJ^JSh4J$&$8QD9(F ;bk(vWV%R!Ps/qcvkAf ߗTn(ʸ36,Q-4U MeA46alHG `H_୻[mŬ՚ݬ ot]U<;eOMȟ.-e-s O Tmףn{!~UvJ%.]> C-߷@*Z,ib'ggyC8N7S+w_>D߈Nx֕;{ejoDQsFPk!Jc4ŏ"He(/NF} PUUN`ѵ^:U( g83ͻvJ#]o7$A-V·:$h mZUUaV2 f&*QeVwpݽsZY Al?kIҡ]KO__Kkw= YdQ h98n+w(t`xIFI"E=gC6rHUUt]Z *f,|s$QBɭ#~ζKR!L"@r("c qL= g*Y(bm;C*̯$}4IeXNYaUàx gԷݫkFKƣfFolRg%=Pvi&6$rnP|6gk!1K6+F ~:cl^ؒnxPPU˖R!)N7,F`NXp#Y;"|TTn, E) ҵV0H;x:T5={ \CN~CIR/k; 4n:ڙ N/"ثS3L;vB>!SX:nevѹR`'56-:s{ ˤ!j*eΜ mRh3 Vfk F~k̀L* %NLszvO=s "Nn/k; DQ'B4MIt]" F䪪b<ଘ,ϻFLͭo(GQ ”vô LӠXGrl̄ɎP-MC~HH]P5Mq.̰ZN֕{C==$_rwj(;(R/ȍ4yyz]8ş}"Sc'~!!Nn[o.]N]v' $LAFWUSt]Q3 ł3TMV;30bw]N~dҥ>0UvX=0j~0sD5!a;a=\>Ol%5RZk2Q!ֶ:=Wҵ|33^-Ѡ8O?6 Ö́۝$I~o7޽\:^ƩǨ i7ׁg_ 1'OL }(AHl2Fgu%JIﱎa~뭶; E(K;,;e䝾:F݇ #(f lpZ!n#Hr2%C )p`-v=*yl`g$4Zm3tGQ{K7ljPal;ZK53cu:pVv 6kG'RÀ( J NoZvD*NS簺Gw n7)Z-sJPqo" vTa9a{4l gA~$iI,3#4ę5&%EǑZ;Q4M~/֖҃A}٥R*P)Ym0Й+qbj3|)8T'\{_R U|LVo i6wnݺp]]q{5ӔBN]c,+FA(iH|c"EW*]QtUØ*jd|"EvKN RPhhlR1cvɩq^zEޱt b*Aތc#{4e:Ajanr*?YMef톾+JumlW0Cd_o{wlܼi4y(W7TScG#VAaƑs SvQb,i u㱖BF1]ЍC\/`,#2Թ<ة~}zˆKo旣a-WWNw'+%"[ou< h^dϏiP6MebÈ0I3rƖtD*b~ZemZ!@!jI4wS涿 bAPgXホH+c,#?QK7 Q\Ͱ'&O<'Ҭ믿p+{lmĉ!8$LЪܙf'R55C{ ;T6 Q-4Mi:A?"7>-dxalIt&s?+y?߾^1lg~bjjvBww8O3)Wipv<D `#gv}&%tUM^_5nYża/+a&IV މy( LTLOTܩH(-w.o/M;ojffg^aw|twk,.=y$L1n21 4L{Mw ,BD۲GJ 1$a=$~pl;@0=^ff3A]!-Wn?\?hYv}C|; gfѱǘJ$X {]VTf9+;Hl6Zrk+1;Q}4 ~|F\(޹rۻ{Az~nu\ܸ$.]Y\:~а[gΜiWKE=cIYE0\(˲pyǖ&#IMhǬn6(xw'wdl\8=ih#B0Q-2=^/}-ú/]9­zC5?'񅮀@) ΊciP;<`8axꈎͲR! H`޸gx83uN_%x7x]v˝_\^P+^x>;|h=o" &p6v쉁a|ծI:ߗ0KRes F5|OTi?,rx7W*?3O>{vF r- DZQaJt:.AVa 2-R:QD =1^)03Qafg=A!7w/]^\_/=sW+#{{G2;2ºrQ׌8T5kl /xy( LO gF.yqo)tNҲRz^?}C&p7~}aBf J<&i6MVF!;@WojX'ϝp4x/r[by_z'>4 =v.g~h rppʎ;.-]dxNLę9t]H?<7ҋWn_-O~KG iju ҕ;g>y," ~ރ Bbvy:*F A ^0Q-15^fv?M8HW|x|n[N޽z5r4O}/~y<|XB4@o9 C=f y !blMOg~Ouv=n.$.l/=t|o1_䟼Dy:}-گ zۡk~!JHi >ͳ?+=Ck9\zuJ~ _Q* ,Q5~#X6 NJLU˜ɳ'݇hmR|v;s?ŋ_g7Uѯ~[zd?Sk)[?-5rɱ2390vgV. k/QM`o~9}{BEZ3Y-1Y-Sooe+˷[{7/~շՋ/%i!y׿U`oU3@P.8LTLTK\83XV xx}5=rqwaj_S:JX Y|6կߘG%3tipzvj3;9 *=M:.KWn6-,7[-woGP RCDZM 91B3<C7;KWnF릪[_o~($Z_6"C8;7x聇"jZX.^ј_\jŸ~oQH.#]BP;zWm +_ll6WPɷ7?R! kZ@_>#ĞXAr{a%ƽz_Gx] t 4.w?^Ppo~_?oQBi. b4.VK^ߝms<|}yOGx;dAhܺ^Zw/OϟE&D|ϟdBGڔhu}9|;7W<[P(`uu(b# ><}#W o[\}r]-64= |E333,#<P.;k]-ؑu`࿩"M4B$j?84MȬP(<2Ga@i>JI>=xKF(IENDB`schismtracker-20180209/icons/schism-itf-icon-22.png000066400000000000000000000024401323741476300216730ustar00rootroot00000000000000PNG  IHDR|0sBIT|dtEXtSoftwarewww.inkscape.org<IDAT8Kl\;y9~efRJx& J)B] $VlbXQĒ.  $Ċ pT;3cܙ{}YXB՜Y|;9BJTl>pģ ^Xov̙ok7  |߉[om?xCF:t*LTUD^D4<7t7LWqnWTʑ8smyaK!fw\x?mf:5y’X=\Q Y [SطZy9z@;rvj'_V@{'-C*jͱ*^q?@c7`uVӡp nnt㄰0S]/1]i`@Q-mǫDR,Pp,]1Myǥl2( eJ1&'+qL) 䋏>c1msh|6"zEXu h#"p{.ɏ:9::\ѐZO.$l9PH?bi(#) -$ =N/.r>ZV=2w6vhu}7FO)9D>uRnEue^Ɗ@2 Qta3aG1nG4 $ bcE$8V,K(2[$I@J4:C]ry^# >D A&&Jb|OƼ?7XY̍k"D7au :jz6eUTap.N`!GuZi!ڕS:]"!5Li?PYB B.i"n])Rbj嵰8)Jr>Oaئqyף/c  BڒbrqI,{Vgy}k_uݳI !z=2Ay0Oix,yrw^^૕~iu]];OG*I,)P`GJ-63+ֺ~jVɃlc+Jy ['$:?Q`&D&3Kgg]׍&&&W ~} :*& IENDB`schismtracker-20180209/icons/schism-itf-icon-24.png000066400000000000000000000027221323741476300217000ustar00rootroot00000000000000PNG  IHDR}\sBIT|dtEXtSoftwarewww.inkscape.org<dIDATH[lg1vMќd5kKaZM ! MB\ !@n' H0ibh)SW:MiN4N8NbLJ`Tly 5Z<ģ&*]rbTa<C^E;Ló\.>GU3hdž)۴ R5$د7s+?rX tB4}uDL4P]hfT,d'!ޑ3u*BWr[cN;iW)O.:2{2(IP])/?hC<~tԏ~|j)Lc)$ 2N _=[Ca^~qw/d>8L4x\z`?gsx>d`*n""V(\J١ح|)7%0~?{dsGJX87e`71OxyfV%6fl&$ПM'BR3mB cd!@Ӹ/YcW6,ъG9%vAi.3O<0'#G43L)[\RjB_Hu5j{h\̯"nX)) Gj<6[޸ե|mZCXk.qA^`˶ DM }t(7&ɡ]⚽O} }lb18hIZnW=w͉Tx0 zoBkhaVgݡ 3RowAlt+.l}hрaP[ r@7NMmbZ&ǡ: D 5TmfƇBp,XIC5ZD>Q\Mx.pi:Xl&l5%*GbKZ1Ѩsnq PW @{'9h Rah55H:QtW-gDꟲ_ʊLCɘ{DeǙ[kwRBRֿݗ(h w7o~{YJJ) O$IENDB`schismtracker-20180209/icons/schism-itf-icon-32.png000066400000000000000000000044311323741476300216760ustar00rootroot00000000000000PNG  IHDR ˞nsBIT|dtEXtSoftwarewww.inkscape.org<IDATHYl\g3g/;tIU&iJPJX$ W UB! !6 P(i&m N۱;3g=˜ Kі^G}EٗO .op {7Vbtjፙ= ’ U#**{rH"Ӿw$gxfPO0LXbž a! .W>]7oE=i`180TUHH$ v 9t,k>̲Rh W+g96==Caޏr^S>G*X;ryTJ/ruq<؍"pJsc"85 H)^;o|;_kȔIu#666[k70MFoE$Z*I($$q4M[bfOEQ/IilD4] ^MD[m]Buqvx㖅45 R=fPU*عJz&%*s8uF XXLSrA#lzD,K5>U@Q"JL.'G$E,sYBr+jQ`P:2<<(Ch4}FMYZoQ $:zbuMw#ÄaH|$*,|njzT6 dO^xxc"y2Xlb>(iZ_!1Md tp(&ȧS4:]V6l;W`jې֗ǁgnz)ٮO( 㺨(mD&^uϥȦ zbDӲэLElҤw{&|>dI"$Y$$h+>AQ%rx-҂?vSTm=хx6$4"MsR+QiS"@DDy>E`6i ;T0C1F hT|bL:I&چFN˛[{|Dg:,,a* a M%&Kkuq9B+R:4h2(j*V0Aa !opt(pĈZ=a!#);m^YA%#yЮ!ڵh92=ק5\#C6||?1.ZwrHw YD Z _ѩXXD`|0:!mihDcb1˜H(B .Pr"K٤|y^pu3{!%ґ,z6[Q@.e:]t`8,=>f^[voZPo HC$4AAQ$Y¶lusnC,l 7_~pW_|>06x1;<@g7_;>Trs-S'sʃjq3Whtsjo{CvGRfťTQ`2 <|3s==_l_ 7I_3Dqehs.RJ kf*n7eq7_X0O,כl0)2BDJq*Z\q)v|n{REo18S-BMˢcX< cee̍ͯO3k]ZDE;s %s66gffk/ N w 5Rjn1>ǑL]#VH%r;A^J2Vʤ?ڰs<>ΛLqߚ:vZ֍Wҳ=wѶkL `C]^"_:>WX!T*"M-B6KT Xk|f,ȡҭꩩޅOEQO.ȷ*_.&q}TU9j~ TxEj-Zt1q2HI,QBloHHA̎D)t.%rɄRkh-,ǧ)ez7v@/^M7vh*eAA"1m]Sx>aqrh57~ 0Be,om X}ؐ hW7$ DZI1i8eTJPn3nH UhT>Fʌ ;"+a"(yǦ3Cã(A!"hnhD?iNM|S-tE%EvzVSc}Nu92hi4m&(XZX,$ǶHmd\- c5RrtEeBuHx^cal|bb>jñL$2 44Um MIis:]pD/7stUHE 亐;y `B$!,;AO5 |_E?@B_1v 6x~{{# fDZ3H$B&BO_)-=^ͯo&sK":" ]db: ~ˣ H:Y&"҄$9"StQߏpM&p||)SHY ޻ZݪsUEsQ݌zu6Ƈwĉ f~ˆ b'8&:4TuZÉT|b28ܙL *BlԼ[k{z#L(h&ʭYX4celTŲ,XhlmZ^g (JͻZ<b:VKt ;6Ƈ/1SdHEn}֚սV)ގceTHLC fکcTRlzQ{`nh؏)jօ H"f,|C%ƇzTZf_yi\5aX'BRo4sgο~pGQa9syܙ~Q)a~_ _uO?IENDB`schismtracker-20180209/icons/schism-itf-icon-48.png000066400000000000000000000105611323741476300217060ustar00rootroot00000000000000PNG  IHDR0+> sBIT|dtEXtSoftwarewww.inkscape.org<IDAThř[$y}{.ERdh)E#qHB!=$H @^ QySd&\.]˝ݙٝ鞾Lwuu:aV#.%RSh >r~?گ?;_~g_0p驟$8˟#_[ݨUOĿ]JPx{,N|Kmt&{Jm _4_>CS߰)u2~^pL4JyI,qE^zWߗR|W!OROe/\ͅLjb07712Cѓ8\|=MSWJܹs6?S?vlۛ A1DN`jwWhܿxb 0~ܹs>{1_'go<xŊa씍}>#1.jlD>sHp*A'y3kmU5GId!/K%1^cH}E=QTM/7Hq]|6RT) ڹo9T_{yZ~|/ZwK/f냐Vg DGuL'R\Y!'8Am[=v"ͷvܶΟ??OOB??7WXN+20"L,'_#s5K$Mۙı a9|%A gM&ZgK k4[yW/l_D| ַZTJ9>vnٛxZh(;MaDc"SLz )l3W. 'Nrʍ }}~3gm3/ᭆ痖xY̱43S%x-v6^{CUJ/]7`p\?Y.7QZy.|!ٚ3++ʩ98W^;˟ ~яztBy:Zi cyA2F,GYi^%߉iJbts 9CTPZQ\ '[j~2 }invt}taL峒;h L@.4f8֕/1#. 7Om±eqh#FXb)؈0J0Z#B-`yyyyyeeeHE [[1/ d*fB0G,+gҙKt N繸Chq.(LM$2Ѿm^m_|ݝ)gwo?,`$2Mxǘ_$1nF+k5&w)K<>H$)g HӤJ'؎M]ڸGF'8ӕV),cf\c0R3K|vIl+%޽E{Ik7npl )rhp0PZS(,Alv|bpОF#QQ/L.0S(`[x̭U,~P#'wR8h4N4+^Yٸ&0d0:7ﻸp8B)PeZ`;@Gk*&1/ RxR_8y?kM;x`ɜ0S.pdn;1`jҚn-|  ǒT)(&R, F,bb)jSeXcU=T- cIJ̳;qN)S,cZ0$e8 4(2+=,MOOٹ`;6a6b5PJ1flFIvB1X,8?ظ C<0| Q8Қ DBЍ)4Lq4'G3# /p>e++ݽ@#2 ,F5Xl;?p8IhBY~dIR% K=;QEJZbOl"3"c IQ4{z&!B:W ,n}4Ld}.uYzU{Ї:t_[9a_A(&Ƣ:W"mFܸV4l(ml#/DǚL.V+!OBxOwBzY;F)mYhqX2 q!!o\KwFCܸ7;V'^ܖ0QJERw$y8\دFU2Od,W`{6%E$eG .ئ:UB)E<I⸂2^H!aiơϰt`=k0 7-;hz;XRQ$Lޱ,-f?R{ϥ`Y1OP"U ۱mvmd좔 4x͑izGb J+֛x7괺V/qv#)m*Ο?OcTu ءR)S(8 Enźz4 -4{*M7ڣhΰeeM(IU;SxIwn$?`ϔ۲\sA6I^l|)7p4nt85?UI~@c.qd"1L{TZd>|ʡʅ~Ȅf~4&,jI^[Rʍ^x{ ~(Tt?z*,Ȅ(D(|c{Rv+IҒR׿c1@oAVa^enD(\N۟zo^R&R0Iώtyz'?RJDJ99uy p^< +/~^ $I+0/ЍIENDB`schismtracker-20180209/icons/schism-itf-icon-64.png000066400000000000000000000155421323741476300217100ustar00rootroot00000000000000PNG  IHDR@:_ysBIT|dtEXtSoftwarewww.inkscape.org<IDAThݛipeqw_<`̾pFܩ%)2MK*[^+V$.'eW6;J*oI%l.ŖHpr8 ޛ`RHew>=RmGӿ)3SiF-ч}L q_?3B?8s^J$I2>Gġ^|ܶ EL~yqnȉ~+>߂,AϿ&WoY=qKke{SuMV?-٧;z ;X8̓BU4%N+^,ׄjE?ܹ I/2__@*MP(F>]^Z6v괺=Nُ"I,͖k>{X|SGw? e M.+cvT)ʲT<輳΍MGeΗ(Y3%IDQT=wKEv^zf~0]G71l8#=d'91*V7X+WHDhى""~{c'۟]sԈHB |PX?Qe~ ~(\ޢ8!tl&SF\Z^~,fUQ847L-^Yc~sOfT4%%eiT|v19`׾&~ l86Z<3Z_{#l) @VOqEy'O!LJ >³]7km=ܴ%![Ԁ1(fHI I'ltvǁil@4EJHPp[u;$gOg62TAiݗmwv tEOQF [#ik",L`g tMq[jsc)_ ?39[vR(OdX ڦEQ EJf"'3x{kF)^GReNǂ 8!Mnڈ[n !عt ߉9Y,.YƿqY d.JdC$ i`vev5fKE8q}c(n̛=5F[;f*Ӌ-DɆ3#aČV=cǎ;qS:ӏHg^a૗} KsHCOrwZ]f,*qzLc]ڑ "$E5:4NNc|EeףVo" I?[uXppa2w~y}W/|[~_'>ӿ ?H!7Z]$!7訊iZ\0!$a@T,`SXaӏhNߍh:=ch0IDY9kCy>i+>Ie3dHi97.;^s'־e/>쿟*6vj\YCarlS:R,"(2ئ$K$WLfc'QUros=mx۫4:o(BJɶ4 EH6fmEuTD4-r}c#CED'64Pcg/A!,C%h՘}92Vmrٔg'uV fRBmzN 9% <膆A)_yV@]H,MRntP5i5'"ܪKn=ɞQ ~K4erԚ>Uo'-Ҕ8Iquޜh9 NMY\x8#,^ BA&C ,OlG9J! nq~#!*٬)A%u}7m!|`~߾}mll\zW4MMOSā9:h?ӻ͇pxr#g ɭb;ީt)2ah6heȏp_U /7z63}˲nhBJ̍L$ ZX.w(Etk'wu"%!g[w;{J4x-N[]^EE<@o]IѦ4:=ZCd9`C75keXAHB8=o ﮱ,62&,C"lw^,^Zޝ!h:^@.q3C5U$L`|l(8C3\R!GiS;(8SyDj1QQitL6HDxԪ D6dc9v. 4"ж^agLRXՕ>=KH~Huc%m]S:.Kؓ$$#k/pl>7OPQǸ]4CHC5E&0!ЮwxvH[\f1d$q5A&w{ S'k[d$cʆzz$yHIh~U(K{wx]uZ='q=߿KI2ռrR@QƊd*OGEYH͋'L%Y4 L k衁T ;K^ Hrdi??pjz$_GV>c # #NgyF5qD3 E5wJc#e2,LڞT\L(d3 xOiE0Ա- ф4aZ${ į{$!"tu;/^ T崚&\Ậ"G~iK#9oLd2ic/O*uȋ?'Ρ ~dw] ^.WZU8!E MNCIu`ԲuU4M?)<0J6*V);Vޞ_nF1/ONEkBUl`b1؞ɠxIρ26cMDX``힣N7{IeE!!D+&qRɱ/=0U)wzzv}g?|Kuy`p EQ:?,? y%}+[vf~qm~|>5XDQ !\}'qEۛV7νu+{_8Sd!*H(J>|{sDcrb:8,H) xIENDB`schismtracker-20180209/icons/schism-itf-icon-72.png000066400000000000000000000177631323741476300217160ustar00rootroot00000000000000PNG  IHDRHArsBIT|dtEXtSoftwarewww.inkscape.org<IDATxݜie]w_=ӳy4#FeYE92p !&R)| )RERIR  [# HӳL߾n[p{9羖CN,{"⣿sπ$Iy|/wlcChC#$I>͗^zO;o.>S/9CW% Ɔ'~F.6W'{?~bq''4z&_C,?= yVB$Jrogx $Iя?Pi6x5. s#|q|vܷ8ʕh ~mAű{ɟ4-VϳrH1if]LqzN5n !,179ʉ)*u^G5Np4en"}W7.Y$}Ze$I3S?G9lw!ˢTұZ|x YF>cszqw2 j9~'=~ ̤ߩ;k_l2Ĝ~} 5³_f3<!3cCXYAlmtpI0iK$4A$ F!+UΝ:p !B̑$)I!I+5qZFi |knnϲ! '^z;{{?ie~ŘRFë7)d<*;?X4MY-XY4= ;a'z+vNG!yyIv}sL_o$I2?}ԩiǦ}oؒfl'42:" ,S6qMbԛWLS,Kt@BH<ץ14!HӁFҭl䝆/_t~2x|?sRF%7vEwXPl^ďtDb:LSج6)qtff0Zܣ'?Qت,Wh\"xdOoIӯ7֛vƥK7*I'?Йw}{鉋ձj(r:[|?F ̎/Wl09ƣg1Mua[z"kU[^'tMKn$9{=25RXo?;}R$IR^g''?;~UiG*us!N#&G\$Lpdfى)\v`0h th4ZaYoQn ØTze=~t3Νt RzSg{~_Ҵ84x(-/6uOeJ~ ZތgqlvA]Jy4BR$MЩ:\CcAv ̴+w.#z@$I/<zO}/+LMnoTTr' C~>8p2~KpLA;cX(_J|,Gs{=xĉׯW\//¿!,OY|L쟼L3TbqP2+3χ|Y$aym;UZ=>)dmZϿFKG1i}5En zk; LS'1  h6AdXR17>Os'fM x ߾)@}>g~Wk1MӰ,(̷SqhD*A#IVTΟZd+qsFR85=FqVuFH'_YWK&*$B`&a:TkMD*H 4Nq#%5~@iv8_Zuw!@$g>czϘQ%}DwXV/hB6&b4mUVM˘T-6*5nh7{}$+k`_ږ)'J 2*is;ZZA"YC=2=[n}]3S̥}*Rqxcc:}o? 'Nd#Flxm*oܼ͝ő1'G>[KWkt>`Zq{FyQEwȊ*ȪkCClmNC:."KPYl&I.KN!zn@6 {]kfttnzH*V!9݄N'Ngd$I;ww,; Zca:Zkח٬q׍> 10xC%EEQd4%bl@ܕPL&et]66+DaL"ū{X#/߼s'pAnW:; H+(2(L [o4dIs&kێ0 k .XYB0LjwzI(h %fe7.E1a LGBB4g'H^ϡZmC=#n3E^_9Xљ@'WVV.}fiԛ]9S 2vAh88wCh\v(+:,-eߐ@A((&.p_NPU*)&٬M .^YD3!DH0u2E&c$))?>g@]3M NQgv똽",dt(úmR͖3eud.^'id3E(Mwn0FVdTE!S&T\ X;$B i*iQ{|)"3@+|M8y|in_j6?zo4@P*dyF$:ףlv-}w*XGQ- *d2iC= X+]B (2i NlkьPtb )˸%Y/C/[.W͎{8T!ADQTv q't*V7#m2y '3>>(W$ʄ/j ,$Y"Ht!2orG$0 dJ,E.\C h{N$d"Q_]ŷ#Ewx,5<t( h4Zmn&BTY-MҐLm[6q33Y _`\~Bf" ۧbZ9ܖΪeyEuϳPrX!x)+$s6*'pQ(7폾-$ VrFuC(K1ًi;Bt]Ŷ,4]<4F8h^6d|7a$]A١tuz4nh%]UQM#Mj-$M( ]Q@*P5 =4%V USAF\yHD$7t@޷95;M*Cb_߹Itcl 1vb~s'Kґjg9Fa`|@ b:50€DpׯmN>XB +,a[&vʂ$MH ]*h>h! jA:EVd,$c6~O :|->Ѓ섹z$K i*Q>~` +ABL2HVq𴆧(2(emH2slL Uhʵq+nbb4g$;|,nѩqʕ(Lβ3&eQ7 BD{Ɵ|H֢Jg=vKGu4}"hTIY)3E]?~j;HQ` n-1,+{ACytrj21tnGYD=~ţplZ$)=YHIZV=fv2fg{And54C#vAfoi 02t5j 5ۻ늊TY`iM޹CE+ MC R 3hUh4@~nLf^#;pIsL^?Hd]0MU!F02o48m(*  R[CF2(I;gZ/ IJUD>^VOL8w˶MI2I1bHs=~z]A^. I`TC =%aSV@ +kTU! #]C YT,SXLU>v} ]G AXw(XmYqfCF"ɖa~jFk-v (ȹ1tdrԺvlt'ӕ$6&) @ףհ7(A52%;-"{0̥cY>$qEu=evwL9l2jr86jضAƶQ{ Bf$nrrvG&=fMFS,UU}M(EQd E1c*BQ2\]=b7ZVyif$Y²M/FNSn `_p& '*2l۵lcPkQrrϭa7od3|C=q^&l.)QF ipB/q >$K2ptxUt/%J<(F+Xd>vd EQ`|3Ӕa:, ebY+1\s5NEUH%Eiz=Vh75= r\oř1NqWս=<5b7"v;/iHLw\ms0Y MPѳ`+:;i:i`Z$ J0TER Ir" 1-0lA7;ZH>/3-{GzXӴ}eҍu.OPe{Uq({ۍ+nG/ Ԝ`de4Xݬ!+f2ulBAqv]nRn|z231̭C5 ř 90}P$-ߵ%_CfP>*>ʌI>հ7֖'JwwZ! 2ltl;#Cng~zD5 kbq}^]٨wjI?>UH: ! zݻ ,D[He qyj4CU 44,TB=fD/IlQEIGcqvRs \UZr=Z4 $I$I$ 5ீP{B 2H MS tw{кIq>,a:a` k=<7@T:C}/H)ci3Y4q}.]_o[Zk?JE?I%I%Ē$ n掓U]ft]0TINK{$ ?)#4]%>Fk*@qt .Xeazc,L3r|s/7nq^WRTwK"~c[K^.mllDo)yЀ$ Be覎DQjV#SN˲ah Y(2x~ Qlå,SMZi|qWkw;[Nua@Ptӊ85pyii0@!Pt~ h}/STP5cwwBa*=^pe,Ikef271uCtz٫uvkje4MD6LV8o^~0wB$QBڍ{MZ$Y*$Enwf}=9$pQF90I!kɇv7׶:͎ۨuz77 C#$U*=q|3Mӫ}m9W4U9p:2Y +{f8Ȗ QgbnrqƇ=?=7]^j7z^q*N,OLm%ZCYUO0M:ۿۇW늢` a?.*A孢M2LF<S<4靍Zz[͙-TRsZYng?ٯ{\N0/Yfh8K6tD4MA$vm19VdflgǑey\5VIW˵Z7]w-V+Ok#WU{#Ƈ)1;1љq,Cb;d\t`vnKFίq&@*@߹AKR!ɳ83gO ~qHlwJs;vj^yɧX=:q֧?φC.|;f|fjX{½J}Zzv}CkS$8MO|o3THml'y̌oZ ۣFzqWjf ⋫{$q@h?Cwj3cFv 7+v{ux35}3?8£gNoOSF% OO>qɹf@$ΞxS?_?>5Z0@{a\؃qԱs ]TkK|kll_D~O^W$8V88׾ծy[3^81?"IRf.W_N؅|shaȲ3Je[4$IƳO>+g~?Þ+Ocg_sF+,5u~O?zSv}O_|ƤlfJ  harrG~jj|mnn7H"?yW1[/|;@xy}BC }W^yW`b3Aݭd.(l,GȽ19y􂐫VEѮ ]asf]S86='):aѿ?rSY$˗/_ߍ81;v}O<ɏX9Uԑ$Q&Glv} B@F?"Q# IF 9&x,Qi?cé1:s EEAFӿ>`8k C ? O_ =_Ȯ5;DQƒ' eΜbysD "H L 'P}Lwy0?鷙S+.qVX*73@$]ճl(_xl˾29Vdz|CWhoMmfɱtH@Ib(FJ62*&a /^K@9'$HIB|/]LkBmw~xo'cu2\z?i[cI`8 Uz:9YYpEiw]j, MU;ۡ[ovHIHpzcyiWķLU.~G.lU12AȴU#GFɊ`|H/LFz1~CC1 \:^2rJ:v30~5uj}ʕo$e~C'O=YY[CCҰD)k8N4pdΜfIj.+[[m.,NYWpI @DB٥^nw4Qc{Cꇫ"I@N ?5)_iq!ʕ+;O-,G?wRe(W0z1 l;k<["(L@|E΂o\C$1#E._{t63a BV6J4MvZgb<8Th-x~xPajB㐟8իW]7$IO>C?۟|ٹm$,o<_ 1dk[}5;|[d-3 3]"kY-[{yq! ַ**5ڭ6Pț^6|aԑ;93Y*IC$I}SO'ߐPRubY|%?z_Y&@ӥ6 '&iG u{7 +Q#I2s#,̌056PI-rY4u/_^R{Y jvC@ !c£q)#|n"{g5)?Vu-@'?oϿ˟(gmvݤߤp!.0>c-IҒm,Lrbn =+Se*6>IFuDz>A^S < S:܈)ׯD)%qp"ɪLƶlLkxGH_ݼ}H=\z׆#aSp?o al՚h~3 a@?*N$A6)dl,Sx7opgć~"C Q VKIb09tJm("zAD"IF^k16VF;XE.kfvN%2wg?Dz2e.9o%L&e6?tUz4}.LÍuVJ(ff|/236 2Fu}+7="!E-K!M%7&QL&QdvFg41d|7cTܥաP(́?ms'}٧~ Up)sI.:$Q%]Ϲ (ئN6cm^J6۵&\f88jV5*MPȚ̮nt=(:IәָuODfhu&:`iY3 Qviq,Cgvb$4{?^sNȋ2<=֯>>9:l 4wOEv[:ıNuI3 , rҢ\_^gTE̎ ܓaIզh3lO j4(:D:!(*}|Tj qvv4װ3ٌM!%#6jDUJ3cAzoa?c݇ϝISmfrjH$ @sR,jr $*YBUB:%\y+Rٜ NJARlz=wHX,`T޺VO$}eUUPU!ymQn.*fm,Vi 7''gNʞyW#o TE Xbc RIrL*U ߋ)˦J"\֩q&0VIPTUQQd qpy¦\IJp~jjW醡Xr4UP*Wq.I%^!7GRe.slr${4S C+++ o\\k"D 7dYKHAζ(+'yddyg,չS7ڸ~H+:Qcٌ }& BSQT8o$׵,v/m 尳6ӤR7v]U@TnUnlCG7* !)gX/Wq`$\~=~(;> c!Ⱦ oEz!x=$Iv!0-ĩ"_$N=Kw>eQMQ &075F͍(d 4m+r&|HZG^~B AVܯ𞋧hvU YrZ0s#<9,FR\Gt$6m!F'`Ѧ9P[ձMMZH{+Ì3B>7&V$UNٹ$("lSYU ]om`ar4{s6# ;X&|"e!$W ]1^Ht/axaH z~iftmCP%21XCAi֎%uRF#OܦW :z@LYT 5ME5k c ȞJƕνxrnܼ^!<@S4EIFM=r%c^Cմج4;!`Ѥ6r md3<ϣuUDqܴ-C3P̛ȨJHDٺ;NjQLH1M[Pu7jҐaxUR٠^#"TKx$~8(I Ğ#A4G*ͻy} }wȆ&B]nju=\qՍ*-=P%0ML":^}@$YB3Qͳ I$!'e%.eI@U35 &\ms0wwĤJϚc<<СG.=wO==9ϿF1XW8߮Ff4:X qN۴o`i@5l+UDt.fH< eYMt#-5AJ9C1̌ZئIӥl89 G(c#@ y&r8F2fq}k2C#Zi0>bج ]aHؑCEl+t:]" UڈAciEQd,+$:kz)hvshc_Ku2l$Zmz!/@ 2,2Q3a2 0j fB%ƸMrZm'Um21Q1q}kuk>n5A^Y[N/Lo4#,l؆Aӣ:k)c%Y"d,S4X|N8Ʋt8AU( _^w^dl5[Q0^1캔 nP}r+Oy_B Zm|? unl ¨ߨ4tF.kS՚]~eD*}kZf%IJm'uܲKJ$;[yȏf݄GN*-\Y[ws AԎAh=.Cg,zeό0nךv _~X( m`L鉮NI,#0-H(ht)xDeYb2/۶ Fddmpgf%1+M@W58QKo="fm+#aH|ԓG_ 2HD">*.lHeؖtZIؼII@!]wp v]&V8z}L 4dmkBiG8qJL"%5|%c/3&4r)d,-6j NI.,f,X2e>CրDalx#ߑ?qAd^ аTu/*"BZcEJebY=?q]&q?)D1114TŶLl;]zi2ŝS@GbZe`h)S'%:sy>37ä]8tD]ٌt.^ʦ j.,olS馩~0c vVd+YkGRvf~ҸT:C~ :YFqed36qi: vv"?B1^RRζL2Y Y(aqwɻN۸CCMOo.m L8j!"QR}LOW%hFfwu`$ [6b`:o-mQT4D f&Fٮu]D½˲iKե ŰL4EA$,SMsF9^@R8Ђ0N? ,|qh2̤eb)2F$ Vvۡؖ!Y! 0utN~NQ@ҋn:61?~s^7??z"0MpݴL}LY%tM(L>c1pz>VחVC^R˫X=7,0Ū9"+VKT!əyj^H6`nm@$˜`(cq.tQL 2+QܻVM7W6 A4 RHҞ$TEx~dYBSfi{ltgdϩ&箯sf9(Jm.c3\SFiu|J=<4C#ݏl4M<ϧ46ʸ̝ l{mOᄑi˲m3e}VKmI”֝0qߤ8 #ڡ2u1H!K :9K591.4kllPsLÐl0zL]{DblIኗ[sqn tJDƶT20,Au*g|!"|A5y衄Q] &P:* #bJsB i| x\T>׍ g*$4à惯`)*0dHar ,2Ϛ=?q" 6"h#n((,%b]r J@7ˆѡ<^3JGz:]]N IHxp9]n,w7@ֶXӮ^40l)F>U\z"&"lhF"CR+J:55Ҥ<[ (>tݠȲ&D1QsCܮZ{-2T\/dڢurP_[{g7n څ}$B"?: _Z&xQwg TEI/Bb-bk"<#2a-Z է1℮ЬTԂv[Yl:ef3DaVͰeK)-"!= ~%"0A4;] ]*W^;?8+ߨBW|e'SQ"%UgCֹ%t= D##3,SǴ L#|q<6TZF-h4}S3$Nt&r&1G;+)pj~{Nx{o޸6h)RP8]5{O$ R$ r#) 'ѻ*^hWL8N4kd ?F @q<v #;`=Jt chFuqzJnl;X6iضOᅬʼnINOqnq(p?rؔC:qUB%k"'M0u,CG5G$dDt HYb:Y+ݙ: SuJ~(* `d/j =qYW٬Y}1uŹqNNrNp GnīW۵x\MRÛџ.펳-DInjoѦ'uݮKҵI=c隊!lkx5I8Jm ;{:-gƸ&917SZ1s!׷ko/ۍpR+M'HHv=-I "7dLxOL', t`tzq%e!~FkZ&=!z=^8 i4Z= !]"nOb0?La ? Z)yZP5F踮$+{$YVco_|- .Hi=ûm$M1 u=<ϧn? $ڰ/B+u]˲"M-/֨#6޽ӧBŹ ƙ3#$;k[ɛroZ~UU S'jJ$2x{}@*wg6e 00iwR.q(WjBu"L B쌅eXvx >C%c,̌8;ɹ)NO"IހKDw6JnUnuq$(aJxxo bdK FZ,ˤpC\yT')S1t s@W4Ƈt\zRbM[ }ΎM4͌8;t('IX(%ז6j-[]7ۖeIBKwoeׯ=.faP)m0@eH, TUk2aBI@LWkێPԕC6>~1-ΒME*sr|se[ivۍN;J)9q(Zdxp_^'7/_m)]cqVNA'H,H$$ 44 #ȃT@WTT3oVPzhPB `:#t˲ΓD-UNVmv+Zxsllm J?$b;FaL #㸴Zv1?5ts (& +pi\wjݲoOM4 XNj~g;r 4u 0uEqi4i]8eII"a)piƋځNDrx[ g+7龲 eX#U~W* te6a'q %ak) f'a~ztGvU-7:N,M/|̹Gk{5z;;|ʾzS׈XtHXFӕAe(|ʿk=r HGwD%Gbd8ϙ)EIU魕FRmtL;>Ae1< W V~3A9x#L qlfb. RR_+oo[ru}WZS??}ԫ^98sSL (Ux_/W֪֝jgJ<0^|ſ ?(k^`)륊VV7ӟ-'f Iwv /-0߿fm;x ilj`mk]/mVj+Ó~׷p ?]t+Q$oUJָ~ʫO1UN{xCԝq )fiT\vk[} R@pQ7޾Q*y͗_^nq|P㉉,{A2m8Ʋ߄>  IENDB`schismtracker-20180209/icons/schism-itf-icon.svg000066400000000000000000000537711323741476300215020ustar00rootroot00000000000000 image/svg+xml F schismtracker-20180209/icons/schism_logo.png000066400000000000000000000023611323741476300207660ustar00rootroot00000000000000PNG  IHDR"2֙sRGBbKGDC pHYs  tIME  uơqIDATxݒ+!+^*e/ 7fchA=d,}7yC$D$tҁlM9Χ'}o?`""9|ih$"4yҬ8kuF֭MR>I{Hf|o9F^">k}$!O>KZ% 'bni޵W \IgоrR#򔄼>;TB2 YA$+HDssx}Y #JkSIBTDڗwoVSeLE 6X-&/s`-b>/USs;3O$ij~9Ј!ҚfޙWg;;9;''*656454434313050STWbem=?N/-/ORS-+-+)+)')HJL'%'ZZ[#'#CDG#IZ^pzABE_bqJHKCDDtxpwCBD]_bEJW768FGN $-,.87@$$%fjoait?FNs{ w=?D\L677657 y yy yyyyy yyyyy  yyyyy y#LL Ÿ####LL####LL444o s####%LL4442/ ###%%LL444/2-M%%%%%oY444/L::-M!%%%%%}}BY444}::???n~!š%%%%% BB Li::???nL~_v%%%eLL }o:::???n$ϐm!7ee777LL41o}_::???n``ee777hLL444 L???nEEEENjee777hh000(L4441Yo}oL:n.EEEEr3mvee777hh0000oY1444 i:YBBYRin~!EEEEGIII3mv77hh0000گ}BY1444 !i::1BBoLEEEEGIIIJJJPshh0000گ[[[ބ BBY䎎 i::???nLo/m~EEEEGIIIJJJÿ~~000گ[[[ނ LLoBY i::???n2REGIIIJJJH>STz گ[[[ނ LL4Yo}C:::???n2πVnEGGIIIJJJ"STTUUT(گ[[[ނЄ LL4442oL???nVE:Y߸JJJJ.PSTTUUUXXXT+v[[ނO4442/o}BL$n$/`EEEERLVJ"mSSTTUUUXXX{{{ ނOu}o144422s:1YBB-/ vEEEEEEVLLL>Ϩ3~SSTTUUUXXX{{{t@"΋OuuQlo}o4422i:::L BBRi/EEEEGIIG`V/m3HPTTUUUXXX{{{tK@@@ pOuuQ!LB}o422#Li::??$:BB2/mEEEEGIIIJJHϨRPUUUXXX{{{t@@@~cdd)OuuQńLLYo}o/-i::???n?VꥥmkEEEGIIIJJᇸRR\JUXX{{{t"@@DNcddff,A uuQLL4LC:::???n//!ooqGIIIJJPSPqVT{{tZ@@@cddffgggjt^uQYL444/o:???n/߸IIIJJͨmwSSTTUqVVV\9P{@@@^ccddffgggjjt!B1444/L}B?n/πԸEEEnVLYԑJJ3~wSSTTUUUXT9`VrrͿ@ccddffgggjjK^l!!BL444i:iYBB2/sVEEEEߎLV>wSSTTUUUXXX{{U9>Ϩcddffgggjj* lLLLo}m!Lii:::LoBBY/7EEEEGɳL/rwSSTTUUUXXX{{{tr\ffgggjj]lLLMm! o_ ii::???iRYB EEEEEGIIIIR`JTTTUUUXXX{{{@@@+++Tggjjﬡ̝lLL4M ii::???n1EEEGIIIJ\RRR߼JTUUXXX{{{K@@@ca{+++Ȟ** lLL4441 :???n./!oGIIIJͨPqVRVTXX{{{@@@ccddfc9)j6 l}YL444 mm -??n//ߎ-GIIIJ~bPwSSJ`V+wX{"@@"ccddffFȸt͝l}BY444iiԩ m -//mbEEVLR߸IJ>PwSSTTUVVV9P@@@(tccddffgggjF"@Dńl o444}ii::- m -n//昸EEEEߊL>\PwSSTTUUUXU9\VϨ accddffgggjjZ@@*lLLLYB}oLMii::??~ 2ԸEEEEGEL/vGPwSSTTUUUXXX{{TϋFcddffgggjjﬡDlLLYB2ii::???nCCLEEEEGIIq/ 摇ISSTTUUUXXX{{{@@+Tfffgggjj ^ńl}LL44Yoii::???n/2 EEGIIImVHSTUUUXXX{{"@@Fü\++9gggjj**l 4442/}BL:???n$//3 qGIII~3PRRVIXXXX{{U@@@^aacT+++{jj송lBY444221Lo}oL???n//}E\3 GIIIPPwSI\.T{{{K@@@~aaccdc{ȇ)cj쬡ńl}L }oL44422i:Lo:n//mEEE+- pJ>+PPwSSTwV.VH@@NaaccddfffF9F**~lLLL 12}iii::1 Y.// EEEEqvPPwSSTTUUTJVVaaccddffggjt99@ lLLLB/iii::??iRB2/MEEEEGrzPPwSSTTUUUXXUPϐaccddffgggjjj@@ńlBLL44LYii::???ni2EEEEEGII~!zPSTTUUUXXX{ؿ~ \wcddffgggjj ^lBY4442_}i:???n/oiEEEGII~3zSTUUUXXX{K@@p9\9ffgggjjj**lB}}oL444/ ???n/ꖑߊGIImPPNzTUXXX{@"va+++gggjj]ńloYBBY44/_!iioLi?n2/ \YnEGGII>PPPw+zTX{"@.aacø++jj쬡^loLLYB}oLmiii::1R/EEEkYqJPPPwSSTêpq>@aaccddt9f**loLLY!iii::?i1oB/}!EEEE9nL.Ϩ!PPPwSSTTUUzp/Ϩ aaccddffF@DQń lMoL44 ii::???nimEEEEGɳ// JPPPwSSTTUUUXX(paaccddffggjdPIRIUUUXXT~)NAAaaccddfczAA@uuQ yyyyy€怕bm~???n2/}oL:EEEEG_~߼JPPwSSTTUUUXP>~Taaccddffg竱ϨuuQ yyyyy#bm-???nn//!kEEEGϨmVLqPSSTTUUUX׿mrFaaccddffggj D@DuuQ yyyyyˉm ~m in//RLVG>Ϩq.VITTUUUXK"v\߇{ccddffgg6D"uuQ yyyyy˶bL//_~EL-EGJJPPqRPUUUX.tࣞ\߼dffggDDuuQ yyyyy#m~3!EEEVLVkϔmJJPPPwPq`{"tatT`+fgg DD uuQ Ѷ y#### C~nEEEEV/~JJPPPwSSPq.taacF++qFg*DD*uuuQˉ 㕁џ#### Nj rEEEEG/_3PPPPwSSTTUq()taaccdaF@DuuQ 㕉####ċ33b+EEϨ!RLEPwSSTTUUU`taaccddff忿@^uuQl㕉&####m r>ϨIkRR`SSTTUUU~qrFaaccddffg D@"uuQll㕉L&&####%m `GJJJJRVJTTUUX"~{{w9߸{ccddffDDDuuQ ˶!㕉1&ˉ####~!š%%% /ϐJJJPPPJR\qSUؿz{t{w99ffDDD^uuQ s& ###33%%%m~IJJPPPwSSIV.({ta\+cf DDuuQ yy&mM3bm%v퍐 pPPPPwSSTIϨ {{taacw+{DD"OuuQyyyyˀ NPwSSTTUU"~GUtaaccdd@OuuQyy!&&}~%%m33mv7 pPSTTUU@\raaccddfd @OuuQ&&퉶!~}~~M%%%%7~~混 ATTU׿(TX9\Uaaccddff*D"OuuQ ½&-~ MMmv%%%%%ĽNjs pzSR{{{{w߇{ccddf]DDDOuuQ ::::!!M3%%%%%Ė!b~!ee7 Apǘ{{{t{9adfDDDOuuQ所}?-s%!ǘve777 p/{{{taaÇ+{d DD"OuuQm!???%m33bmvs3e777v (Xtaaca+9@@DOuuQ!?Lm%33 se777hh0ppAaaccdaKOuuQ!siV~% ǩ3mvee777hh0000 AAaaccdd DOuuQMM〖mL%!%Ķmǘ~7ee777hh0000 Naccdd*D"Ouu~M nԛMċǘNjee777hh0000 AR=9@L!47^ga,&.`qmpwjntEKKFEL0597BG1! >67V\]f|fx_jcVcx]haOML@L[414>HEbcm.,8w875:JRowyE`Z<:@)%&mtyDJE#*doiR`Pku1++<;GFWNurmkpw #._iuF@DpkntY][LHGGGL927826)%4y=GS16,rw AAC*-/?9Azry|>I=kou(#* >9:`n(#'HEA0;,0\gcR`wZgaOkjS_nGTGZ[a$O[j[VXBBBBlrm7BvI#p;~.Y0x[|P)s:X*\8EJ6 H,fa9u(yb!]ceKPL78;KvIc13?T[\ "'>DSbhqsx @@A=<>%&) "$X_h~FKE9?L.-0LNR+)->g:GIW0=/QQ^?@EackV\Vy127VTVTRT9:<kmoJHJqrIHIXY_gjugik|EDEstCBC~868%$(767u{EEL545424K[Olp=?D-,-+*+*(*]_^$'.(&(GMK!&!"!!GY\-/4 X`tPQQ;=?P\l[lo}JqHADLJkHcdhHNd*1.99:758]]] ]] ]] ]] ]]  ]] Ux UUUU>>x $Κ UUUU>>x ṹ!UUUU~~~>>x ṹ??f!UUUU~~~~~~~>>> '??BB?f'UUUU~~~~~~~~>>> '??BBFFI?UUUU~~~~~~~~N>>> '??BBFFFIII~~~~~~~~~NNNNNm>>> '??BBFFFIIIII@f!~~~~~~~~NNNNNN>> '??BBFFFIIIII+>Ρ~~~~~~~NNNNNN>> ??BBFFFIIIIIfKK$'~~~NNNNNN>> xBBFFFIIIIIfοKKKK!ၦNNNNNN>> x)))?BBFFFIIIII>KKKKLL !NNNNNN>> >))))ڹFFFIIIII+KKKKLLȖ >NNNN>> ?))))BIIIII+$$KKKKLLOOzw>hhhh>> ??@))fI1$ÿKKKKLLOOzzPPzb1fhhhhhhjj>> ι??BBڃ1!KKKKLLOOzzPP9999!D'hhhhhhjjjjjj>> ι??BBFB+ fKKKKLLOOzzPP9999$Ͻ$hhhhhhjjjjjjllll>> ι??BBFFFII@ ?IKKKLLOOzzPP999P*!hhhhhhjjjjjjllllll>> ??BBFFFIIII1ăLLOOzzPPk9991ŽSS͟ εhhhhhhjjjjjjllllll>> ??BBFFFIIIIbLOOzzPP999ԊνSSTT{Dmhhhhhhjjjjjjllllll>> !???BBFFFIIII92I?fIKOOOzzPP999ԡ!SSTT֯w'hhjjjjjjllllll>> )?@BBFFFIIIIbKKF–zzPP999wSSTT%%%w2ljjjjllllll>> ))))ڹBFFFIIIII1KKKK?P1999tSSTT%%%%&& jllllll>> ڃ))))ڹFFIIIIF1KKKKLLƒfff99SSTT%%%%&&#bbqcեlll>> ?))))III镕1'KKKKLLfڕwSSTT%%%%&bbbw=*𵙙>> ??@ƒ@镕ځKKKKLLOOzOKktzSSTT%%%%&bbbbjo ',,,,\>> !??BB@ľοKKKKLLOOzzP9>tڹOTT%%%%&bbbbj3ٌMŴ,,,,,\>> !??BBFFF@f>KKKKLLOOzzPk99912'0{T%%%%&cbbbqj`QÅ,,,,,\>> !??BBFFFIIIF+$fFKLLOOzzP999ԅ>{PL?z%%%%&bbbÅ&`````G,,,,,Y\>> !??BBFFFIIII>LLOOzzP999ԡ${P*ttLT%%%%&#bbb#2```````W,,,,,YYYYӈ>> ??BBFFFIIII?LOOzzP999w{SSzttz%&bbbM```````WWW,,,,,YYYYӂ>> )f@BBFFFIIII$鹃f?0OOOzzP1999+{SSTTzȒbbbj```````WWW(c;,,,,,YYYYӂ>> ))))?BFFFIIII+!fKKK@IKOzzP1999f{SSTTT*9Xj*```````WWW-(((vM,,,,YYYYӂ>> ڃ))))?FFFIIII+KKKKIfȒ999w*{SSTT%{wmo%```````WWW (((GgYYYYӂR>> Ĺ)))?IIII1x¿KKKKLLƒff+9${SSTT%%%&X9![```````WWW#((c2degggiYYYYӂ6>> Ĺ??@)¹I1?KKKKLLȜ>PSSTT%%%&#bbb#2*z&^```````WWW^(((cdegggiiiG2YYYYӂʈRR6>> Ĺ??BB"΁KKKKLLOOzԕ9''–zSSTT%%%%bbb< Ͻ%``````WWW((3vdegggiinvJ6YYYӂʈRRRRRR6>> Ĺ??BBFF@ھ KKKKLLOOzzk9Θ@OTT%%%Vqbbbkj[[*V```WWW((degggiinnvM6ӂʈRRRRRRRRRR>> ??BBFFFIIF?"'+IKKKKLLOOzz99$OTT%%%bbbbj[٨*****Ͻ%`WWW (#degggiinn/s\ʈRRRRRRRRRR>> ???BBFFFIIIb9fILLOOzz99 {{tª{T%%%cbbbb [&Ͱ******V`(cedegggiinnsʈRRRRRRRRR>> â!ºBBFFFIII91'ăFKLLOOzz99+{{zºP%%%bbbń[``%o****ϗq((3ddegggiinny432ʈRRRRRRRRR>> x2!BBFFFIII1οI@OOzz199 {{S˽O*tt*zS%%#bbb#'![``````%ϤXXXqddegggiinnyy .𩩩ʈRRRRRRRRR>> ڱ!FFFIIIIοKKKOOzzb99w*{{SSPzbbb< [[```````W^#XXXXqՊ}/ggggiinnyH. 2ʈRRRRRRRR>> f??±!!FIIIF>KKKKKIfOO999!O{{SSTTz*9XX0j[[```````WW.qqX#Wgiiinny.ʈRRRRRRRR>> ??!ΝF镕fUKKKK99>{{SSTT{kj3T```````WW`((;򰰰Ginny-.ʈRRRRRRRR>> ??BB!ffU$KKKKLLKFffb'{{SSTT%%cX9w<zo&```````WW%=}Winnn. ʈRRRRRRR>>ڹ??BBFFΝCUwKKKKLLz1>{SSTT%%cbbbq!'0o%```````WW(MQdeoGdHʈRRRRRRR>>':ڹ??BBFFFIIfCCU!IKKKKLLOOzk+ȟSSTT%%bbb#&P**%^```````WW cddegg/}=}웛4ʈRRRRRRR>>':ڹ??BBFFFIIIUxձKLLOOz9wtt*ȽTT%%#bbb[[*T`````WW#ddegggi}GW .톩ʈRRRRRR>>'ڹ???BBFFFIII$UwDLLOOz9{0ȟTT%bbb0j[[[[**&``W^ߧddegggiingWQo=̓H6ʈRRRRRR>>')@BBFFFIIIUDDLLOOz9 {{{z–{T%bbbbj[[[%o*****%WJ唔ddegggiinnndꌌʈRRRRRR>>'ھ))BBFFFIII?KDDOOz19$*{{{{@{T%bbbb [[[܄****** #2/ddegggiinnn̸#qq(3ʈRRRRR>>'ھ))FFFIII+>KKKDD*OOzb9!O{{{SzttPVcbbb![[[[``&o***Xqqc,퓔ddegggiinn/^(qq 2ʈRRRRR>>'??ھ))ڹFFIII+ΡKKKK D*99>{{{{SSPXb#w[[[[``````qXXXX3ddegggiinn⍍(-ʈRRRRR>>'??@ھ)ںII1ΥKKKKLLK1'{{{{SSTTT019k[[[[```````WqqXXX%Weegggiinn4ʈRRRR>>'??BBBھ12KKKKLLOwfP{{SSTTSPbtjz```````-qWgiinn..ʈRRRR>>'??BBFBľ ''KKKKLLOzkfMPSSTTbXX l@@&``````` c脽G`diinnEʈRRRR>>'??BBFFFIF" FKKKKLLOO+N!!!M{SSTTїbbbbD@&```````W#;߭WQ%%}/nn4cʈRRR>>'???BBFFFIII1bfIKKKKLLOON*!!!MϟTTcbbb![&*o&```````^(ߌe`GoWin .ʈRRR>>')¹@BBFFFIIIb1>?LLOON{P*MMϟTTрbbb#D[[[%Ͻ%```````(M̓ddeggW}%ְGWg5 6ʈRRR>>'ھ))BBFFFIII9ځILLOO1${{{{PϯMMϟ#bbb[[[[&Ͱ*%````cMddegggidGd7ʈRR>>'ڃ))FFFIIIf$@–LOz1!z{{{{ϯMMMϟbbbh*[[[[****%` 񀵞㓓ddegggiii/}=o}-.3ʈRR>>'))ڹFFIIIfUwKK@0OOO9>{{{{{S˽MMMMMqbbbtl[[[[%o*****%#㓓ddegggiineWGo((( 2ʈRR>>ᚹ??@ƒ)BI镕UKKKK1'{{{{{SSSDMMM9bD&[[[[^ܨV***XXqqQ㓓ddegggiinnn/}=qqqq-ʈR>>ṹ??BBUfKKKKLf!{{{{{SSTT˰D ը[[[[````Q%XXXX#2ddegggiinn-(qqqʈR>>ṹ??BBF$UKKKKLLf+w{{{{{SSTTk1!ό[[``````` qqXX;Jo`ddegggiinn (.ʈR>>ṹ??BBFFFI?UKKKKLLOOԕfNwKz{{SSTT#bXԙMMMMJ[```````cq`ggggiinnHʈ>>'ڹ?BBFFFIIxFKKKKLLOfNږPSSTTbbbhJMMMMJ```````(=%֨iinn4cʈ>>ᢾ))BBFFFIIăIKLLON0˜0{SSTTVqbbb&MMMJv``````#`}iinn-.ʈ>>'ڃ)))BBFFFII>II@LLO${z0TTbbbbw&[[oJv^``````c㓓`Q=/nn5. 6ʈ>>'?)))ºFFFIIΥIfFKLLz1P{{{PzSTTЗbbbq!&[[[[ׄ=JvG````` ;v㓓de}ְo}n⍍ʈ>>'?))fFII+ΚKKIfIzb9'P{{{{{{0zcbbb#&[[[[ث=JG``#=㓓ddegG֌Q/43ʈ'>>'??ھ)@I+2'KKKKIfO9b>P{{{{{SL0¹{Tbbbm&[[[[ٸJJJJv(㓓ddegggigWoGW 2'>>'??BB@ھ1'KKKKKI1!P{{{{{SS͟0ttt@bbbh&[[[[ܸJJJXbq㓓ddegggiii}o(( >>ι??BBF@ KKKKLLIf+wP{{{{{SSTO099Xo&[[[[`t㓓ddegggiineGqqq(x!?BBFFFI@ >KKKKLLOԕfN {{{{{SSTTн2&[[[[`````cbXG㓓ddegggiinn / qqqqM:ᅝ2Ø?BBFFFII11IKKKKLLkwNP{SSTTЗbXX9!P˨[[[`````#qqMJJJsG/ddegggiinn썍( ]] $âxBBFFFII11!KLLwNژږ{SSTTcbbb# ??*Є````cMsvJJG/ggggiinn]] 'w!Ø?FFFII1fwIfFLL$LzژȟSSSTTФbbbkl&z@@*````-񀵯svsGiinn4.]] 2$wαFFII9fUFf@LLȶPP{{zzSTT#bbbh&&[&zϽ&```` svGiinn. 6]] '2>w!IUfKFFKO192PP{{{{z??{TTbbb&&[[TPo&````cJG㓓̸GnnE]] 22!!αUKKKLObb>PP{{{{{O–ƽbbbbw&&&[[[[[P^```^(#㓓deu۔nn43]] '2ñ>$UKKKK1PP{{{{{zt*Obbqw%&&[[[[0*`cW㓓ddegWGd 2]] 22'!CC>UKKKKLL@ff+wPP{{{{{SSOttX#%&&[[[[P***֗q3W㓓ddegggi(]] 'w">! KKKKLLLN OP{{{{{SSTzԡ%&&[[[[**XXqW㓓ddegggiiG3XXq(;]] U2'!!KKKLLkwNP{{SSTT9jO&[[[[`&XXXq`㓓ddegggiindsXXXX]] 'U DLLwN˜*O{SSTTbX0PT[[[[``qqX#o`㓓ddegggiinnn.(qXX ]] UwLL$PژKPSSTTbbbwo??%``-;oddegggiinn⬬(ӂ]] UU'22! DwLȶPPP{O?ڪLSTT{bqw%&@@&``=&=QWddegggiinn4ӂ]] UUUU22w՘19PPP{{{OzSTTcbԊ%%&&[``cMWW&ϰ}ggggiinn 6ӂ]] UUUU22 ïbbPPP{{{{{@{TTЀbkj3%%&&[[%P%`^#cWWW^&&`iinEcӂ]] UUUU2222D!1PPP{{{{{00OS#bm%%&&[[[[**&`񀴇WWW/&&diin4ӂ] UUUU~~~22D āwzPP{{{{{S{1Xb%%&&[[[[*o񇴌WWW㓓`&갰Gn.2ӂ] '!΅ UUUU~~~~~'22+8wN!M{{{{{SS{99!%%%&&[[[[٫**#qqJWWW㓓ddgd`ְQWi5.Yӂ ]xx!x!xUUUU~~~~~~~~՚!D!P{SSTTX9wz&&&[[[[*XXX#2MWWWW㓓ddeggd`֌-(;Yӂ' xxxx$UUUU~~~~~~~~NMϟSSTTcXԊ [[[[[^XXX&`W㓓ddegggiecqq((.YӂxΚ'xxxxUUUUU~~~~~~~~N2M!!!DSSTTkh3Oz˨[[[-qq䴇*ͫW㓓ddegggiig#qqqq Yӂ>>x!xxxxΎ8UUUU~~~~~~~~NN22JM!!ïϽSTT%%??*{ܮo*ϰ㓓ddegggii⍬(qYYӂ>>xxxxxxxAI 'UUUU~~~~~~~~NNNN22MMTT#%%%%&@o&c#W`͜&̓ddegggii4YYӂxxxxxxxAIArAUU~~~~~~~~'NNNNNN2JMϽV󒒒w!%%%%&&[*z^#cW`o`edegggiin 6YYӂ]'xxxxxAIArUU~~~~~~~~w>NNNN2MMb %%%%&&[[Tꪪ*&;WWW`[oggiiEcYYYӂ]] !xx"IArUUΡ~~~~~~~~ww 2N误M ԅ%%%%&&[[[[[^-WWW`%}iiiବYYYӂ ]] !r8IArUU>$w~~~~~~~~~>D '0h%%%&&[[[[[ocqM`WWW㓓&ְ`gi-.2YYYӂ:]] '!'r8UU$$$w~~~~~~2 DDw2J1 J譟%&&[[[[&o**XbqcJ`WWW㓓e`&ְWi.cYYYYӂ rUU$$$w~~~~NNN MMM[[[[[&XXX3`WWW㓓dde}%֮((;YYYYӂ >ArUU'$w$ñ$2~NNNNN2w wՙDMMM&[[[ZqqqX*&WWW㓓ddegggi/}=cqqq(YYYYӂ2 ''A'~wwwNNNNNN !!JMMMJٮq **o㓓ddegggii qqqq YYYYӂ x$ΥArA>>wwwwww'NNNNNNw DmMMMٗqc2`[͜*%㓓ddegggiiE((qYYYYӂ!!$xUA$'$wwwwwwD>NNNNNN!ÚhM螭MMv#q<``W`[o`ddegggii4YYYYӂ2'!!$$Aw'~wwwww NNND mhhh2MM诰%q3``W`Qddegggii-.6YYYYӂ$!!$>UAr0KK~wΡw w >NŻhhhhhhMMJ譌Vqq``WWW%ϰ°ggi.YYYYӂ$!!!'𱪿KKKKwᦦᚦ! DNm յhhhhhjjjvXq#M```WWWW=GWgiବYYYYӂ2'!$!$ΡKKKN Nw'whhhjjjjjMv+9```WWW㓓Wo=}ii-.2YYYYӂ>$$w2LLNNNNNN ՠwhhjjjjjjllJ;=`WWW㓓eW׽W cYYYYӂ$$w$2~ÖLLNNNNNNw w''whhjjjjjjllll2M;텯JWWW㓓ddeg&ְq(3YYYYӂ22!w$w>~>LL 'NNNNNNN !h!hhjjjjjjllllllMJJJs}㓓ddeggd̮qqqqYYYYӂww!''KNNNNNN! w2hhhhhhjjjjjjllllllJvJJW㓓ddegggi((qq ӑYYYYӂwwww~$OzwNNNN2'w!hhhhhhjjjjjjllllllMJJJs}ddeggge((cYYYYӂ22www!z'N!D2hhhhhhjjjjjjllllllJvJJvWddeggg((푑YYYYӂ!ww2wPzw!w!hhhhhhjjjjjjllllllwwwN!PPww w2ՠDjhhhhjjjjjjllllllJvuggi ((YYYYӂ2www $N2ww !àÅhhjjjjjjllllll2G(((䑑YYYYӂ$ ww wN  ljjjjjllllllJJJvsv(((2YYYYӂ ! wÅjjjllllllMsXb(cYYYYӂ2w DDw'! whՠmllllllJvs#XXXXYYYYӂ'à m whhwlllllMJcqbYYYYӂ! Ú'whaAhhllllll2J66YYYYӂ2 D!harahhjjjMllllll,,JYYYYӂ'w ! jArhjjjjjjllllllե,,,,,YYYYӂ!w'w<rrAajjjjjllllllŵ,,,,,YYYY2ՠm!$rrrrr2jjjllllllM3,,,,,YYYY2wÅ2rrrA䨌allllll3,,,,,YYYY 2ha;%;rrAallllMM3,,,,,YYYY2ՠ!hrrrrrrrlll힭<յ,,,,,YYYY2Dwharrrrrlll3MJ6,,,,,YYYYwml$Arrrll33ߵŧ,,,YYYY2!Åjŏarra3MvvvJ6,YYYY  2jarra2臇vvvvvYYYYwBN201NT[?\`hd^jxqivpioz!;KGNNdX% $"!rw 'CCTM}9FCFQXXNU_TQQa`e-)0jyu>9>\}{;;;MIJ&')959SbW!$FCC1/10-0|?:FTcs;pELQ95>k|ECGSR\rwqw~ZdtJEI"*ECD=[a[n^?;>-'/'-)[b^{EUA|aqkj|IX` &MGFNeX.uL^LLP`KWU;=H1L4,)(p|23?!. ^D@D@?J/KM:J:TPQSPPIYPixt((+l~14ERX`Zco NORRNLjxr[e\cn<69sy\fxrw  ms|U]g42.\fuFRbXbq,,&OKM5:@#;IMGSbX^t^Z}[qm6P*LKmH V$sY O [g(!\?36xPȈp5`h|2__o. rET%nW#lYpMI']N-"fGV$#A>MQRk懩 ]9:;8^),;֟Ad㴄kcB4j/F zwDv욲0-? <<<|(0@]i}DKB=NEox1/20-1YboTat<8DS\i "VYb* !FBK?EN/--JOVglwp|n,4E1?,#!!-/<'0@O\N4?T%D+M#FKsyg}?>L^le^q[jjn3-6~[ciCc@FTM4<>W[eYX]@S=qx;^g1/1PRT4M1rQi\HDL34:512rx1-.#!@HKS.8F]nRcZR_Z&!#xWTUz  9>N@>AZk_>:?V]odnFGN)!#m{QV`=KE;@Arofn}Egi=TF!&CBLAaeSRY|RQNg.*&9;8ShVego4132/1"Xev`aj,(5\z`p27BCGPzSPSYm`&+6gvu39@gukJHJD@D@@@cuG`DDPUEXB626WU[HBE--7\k]XamW`b)")5>2&$&KHE_pg`grNZYryTQUvcqIHThkwIIJGCHIRe >=? 3G4NIL=64UgPq,(rFarFa4N>$rүExSMtREz~e֏up`,6]xI+YA1M3p,6P͹^WfLD27%5z EI=[1&9,]y]m+ ^Q27! zܬȰghjT0.//LYHC-K5͇ը7h?÷}nDomGf!t??G?>>y'$)$$&t{ry "..-Vj`LKO KINfmwVV`743"!QR[xmnqBKV679\m~1/4BAB mqxCjo979hms757IbM`ck9=J ,+,;SafU\^=Y?eku237 &bgr! kxSQR@?BGSZ2V**+/68BsHGGN^T1/3Oep*+,V`mjutKSG:=9796}!-*=)VctZbn.+-]h]DUQ)'(Tc^ShS $)PVd:BQ,)(ySgZ669Qy}l}|}}PXai{ $DBD\et=<=Z^h;:;bp?FPKUc"GQ_+(+V_k&"&}XWYu{U[ViUUVBCF`lhQQR[kYVmTakpDEEQRYFRb7M8 LPT<9=99:q*R%Y*qb,/ 3VbVh>Bt=4Rh㶪'zD^ $Gbb%l WXE rik::V|eVhlp@WX.KL+\xdyRBpv%lpD_ JԞdcbYEhw'N&jױddcevnO{{(w]f.]9+o̖Vbel> &< 9ΖbplX ..a;דv|%/U&2xLjo8lB/<_'Ħuifxx@˜Ҋ ]f쓀UNf a q ``̽CK&];2Jӑ♐00!(u&y``3ND2At0}ZJo$B})ZP[otŧsZZ P:ţ!SSөI!MSF:????($HGGE%"0/1JMYFZK+%,88@w~6n'r~PTf533 >U90-.5\&[edA;<==8ds~NJPJHL(%&S\f"&*&!$EDG*L-*2NJMfvs98;JHInx1.33?P]]`Y\f0-9/4.$LDj=ms~rS`qw[[[^hy7Z0'':#9=<KKK97<'*#6598/;SUd033BABKQ\gs''*QjU!*rzFCCJRbC?@SJ]Z^TZiw|ehwfgnZiv3-0QNRdjs,3:1.5>>?_m$$(BFTm}\ZWIFGs{n|?BQ#BF@(<Е1~Ǻ+N/)ɾC]%U>8, :H\2hH}QI6VB- ,l֛1Ǥ\ǒ쑀 gr>W",u!)NM̓QoArOBբ +?\Y.hϬn{C](- q l:?_'j\Hs%OWՕ,fiY'YjoL|)})D$ ә)*סnRۼzBgl^^^͡* ]- bb^^^ENQԪ *COҽD f_Pm^^EaFJ)ƥAlb^wn{L3ҩ mj=E[ AJՕ~Pw:TPEtszl j=xmmEE[5C>@4RެTZ;k"ytbZZSc;T,&l;IRz;tRG?(0` IdN<>D*/+V\lr;e6]mzcjvXXZ#!$WVYVVX?DD GHI328QbPDDF-.2MXLi{lW^g88:GIPQUWRai'&)&&(Fdg#"% "KUb`o`LVYRSUtv{adk[MOPhuyZeZKKNAMDTYT]fxbjiRQRQQQluz:==WahDKX#%)IGIvGEG013MO^~EN`*+-|\\`elz878777757qx1/1RRV ///ctLLP)))\^]###RRSvOPPKJLHHIPVb9X:CBDLV^BBC o<:=;:<::;x23=awcXqZaac__a2H0P]fEEJPOR)+1EM[99>48CeouHIJbbhDgC==?FOYCZL63OYg545@DQ444242y9>J.,.-,-,*,*(*79>FJTdb\[]?YC!,!WXbgjoCEGy[{Yjtzdn}PqNOkMOt|EEF135.bi{w% ,,UYp ~]]0DFF58zڐPPШ B9|p0 =d?05mm;25uGԖB_BߡMpN04?g=.>PuoT%&ؕΟj -p.=V;xm5vP n Zp~~d45umm7 T@-%ll|p. +iDFFVzPQv;%Źk)jj|I~b4?.5m[HHRGv@G%(L |I +?D2=duv{AGGԎ%l|I} =d+?z0PP%&nj|I]+d.=zVށ޽nTXLL $|pN=b?.DF83VP7{"R%7*ĨŢ|Szd..giv7wvT&l%y)|Ӧh?DFg4muTϟn)Cy|Ӧs [ivPnG٢|hhhƙOz muTX@ɪ LLlIS?cƜOk &/EEƜ9"TX- G(٢>@knY`i;<=;:=6:8MlIIrELWfq{042-./ku,,.**,>h:XY[t{T[WdhnKQbRSU>ADEM\mptc`K_KSQS??BQOQds<=?HJR@[@JIJPZk679WX^t|fit\i~@M@ECE=>GOX;9;Pg^999979:[7i_ht/-/,+,4M1~)')SwRjSZZ[\Z###VVWp~ABEJ[UWhiNNOU`gr~GFHbcg]_b768566@DRiyT-4.-,.Uak8;@1@/" #! "-15]o\*-2OQQ^`gYal[hu}A\J%%*658abdoxokLNRip}))+\\_GJMALGPPS%3$HHK]pnKZ__hpUTU RQ\TRTupv~WzTRPR}76:IHI>JRCEM/3<EDEA@ACf@~}Q^sdxo67@;d8aiv444424RSV,,,6Pc\| !=CL,04]dpJJLFGR!$)]YtzW|SOPN::<P_j}}}426^iut!jGQaZm]IPYFYL~Zc]q|O}UbsQyQ6A}vQlOVW^ Z[_bmxju}_aar'((2B0YY[$$%$"%@ME @AEnxtNMPZxu~dp~DEFbnh?IAEmDfu<=>GMZ@a?AY@557?ERnnqxcdf-+/^^ah|hCOSYZ\\v\OPR),2,3<\nmOYY@@CkwQPQ[`l__fVbgRtOJHJkko669GJGJrG^nlDDD=H=ij+..,,/=<=}JOQ((+8888685:5biwhmi" %323.2.`bk6P3-,-,,,,*,W]lnnv*(*GMK&&&XWYUSVTSUou~:;>KOLDGYJIKNW`DCE]dv@?A;>F;;<N]q2222222m"x_7x_GG|x_GGIDŌ C.zz_}GID7L Czz_uH17LLOO`,fC_GGcu7GLLOOn,`ȵ_GGI17}u[OO, #`4y퐐_}GI,?7L#ΛVV&y퐐c}H,?7LL[ʼnN#ΛVV9w~pHGcu7!LLOO|rdVVӚ9w]&~GGImu}LOO?d#`˪L']]⃄~6}cIEIuD?#Λi,]]⃄e;$w6 u LLL,ŞL#ΛVV9,dK⃄ejj+ {0"GGhcALLOODc`VV d dd ٕ jjl+ {0~">߶GI?fau[OOnQ`[` &]VMjjloq;B0">>[?AL[[,>Q#ͱnw]]⃄⿿ïoq0"GН LLI?>dQ#ΛVV9w`i]⃄Օ/^eRKqW$0uGGI7 O> c#VVn'#`i&jjlR bW(0}HG!ŌhrJN?|QQ[#9Z &dwSjjlRٕ{0c}c7L`[ Q#d!?a` ]]ܿ9K܆jloq{0cGc}k!LLOZ`#ΛV |`K]]ܿ8KoqWU{0_GGcmuIO/?|rZdV ' ͱ` ; {0_c}DmIc`D?|Q#`ZZn' di'jjlS P/ {0_cG}mLL,?Q#·]]M9w]jjloPP$ {07GG1mLLOn|[ΛV,^]]⿙' ijob0 {0_Hc1?muO,i`Q Zտ-eRjo/ {_cGHm𻻻[>rQ#I w &Z9'Kjjj P$ {"7 GGmLL`Ar#/?wi&]] SgjjlP( {2m ,cILL?LΛ/d# ]]♕:Sl; {22xC4 >>Icu!?Q˪L#n #ܿg= {2222zFmfAcLLI1ŲOQ##N- ڽ9:jjҮPU {2222xx LD?[LQ# >` ]]9 jjlտ {2222z~ZD|i[ a`N]]'܆jl {2x2v Q#D VidM/e SjW {x_xzzF~ дZ#w  ejeKϕW  7<ȦȖJ#n `d]wiigjjl x7E7<**zF7(Z&idi] #jlP 2z_E7<*<< mF.7>"vГ ViMe KSbP 2"*<<az"~p^Z ܺ,^jeÿ m7*<<*77 aamyzzxmCCUZKϿ'&&KjjlP 7*r7m ..C4yF~@8/'eSV ejj~ 77>y F4޲U8&eKP67u Fza7vȵy~+̆jٿ/6>7mCCvm퐐UjjWP6a7 Fa~ JJ~y퐐(+:P6 w7m J7JC퐐U89/644w JJ4y4w퐐(864JU?AE\OPOPGKG~GEGA?Av=;=|;9;979757333_ft212.-.6M3%)%`^#!#eimBDFRRSD^ERvPLLMCJX]|_`n768qpFENjklMWf/40[s]mqmValclt|2@0diwm} !7HFrxxQQSOQQLNXEM[Th]jmzGGITX]JPg./3>=@wRZlZZ`759JqF$GmCd`em:AMddg//1=@FT}{}$%&##%y-06Rip_h_MLPJNMPnPTVTIX]SRSKN_V_qlrz^_e<>_<7784w9ڭ$Ew%$Ew%8G%$Ew8GGL E$Ew8GGLLkwڢ$Ew{GGLLkeظڢM$Ew{{ LLk QQ#ʏ[9MM$Ew {3kҖQQ#&S8 9MMMMM$Ew8GGg8 { wQQ#&SSDbMMMMMMM$Ee8GGLL8 yQ#&SSDb UWHqMMMMMM$EeGGGLLkbݸ3L!&&SSb UWX&PMM󕕕$E{{3LLkb)Q833SbUWXY.Z\;A q󕕕$Eͦ3{3QQ#Q8bkUWXY.Z\\Fih[<}$Ee8G {3wQQ#&SOb#+YY.Z\\hiDrddd$Eͨ8GGL y wGkQQ#&S"bi#3Y.Z\\hi[ߜCddd]$EͨGGGLL  e 8k&SObUUW#8&ԩi谱cm[ddd:]$Ee{LL kg 8kSbUUWXԛ!8;谱cccah[:]က$EÞ{{LQQ#k8bUUWXY.Z"bb!Z谱ccc5|B=:]က$E8G {öݎ7QQ#&Q#UWXY.Z\Fh!kH.䰰ccc_j2|<ကV$E 8GGL{y7QQ#&Sbe33#UYY.Z\hD^xHz;,^c0jlnoVVV$E9eGLL#&SbUk3Z\ht^xHz!~h<jlnNosVVV$E978LLekk&SbUUWU#k8^x"AjlnNosss66VV$E938Ô7Ï򦖖QQk88kbbUUWXYY!bb^谱cc|f*clnNossK _VV$E98GGGÉ yÖQQ#!b&UUWXY.Z&x^谱cc_<*Ӛ,;1ooss> @VV$E98GGLLL ywQQ#&Obk&XY.ZFD\;;,^ccӚ1H.oss_ 6V$E93GGLLt&b!k&X.Z"D^^.ffƩaIjlК1*a _=V$E {LLk8 [kSDbUUUW&8k&xhυ^^.HzFimjlnNl__V$E88 e 8kQQ!Ҕ[b UUUWXY#iǑ^^h<jlnNos 0β$E88GGG3 QQ#ʷ yzUUWXYZ,b&Y谱c6hf.lnNoN 6$EGGGL QQ#&b [[WY.i+f(.谱Ic.HxnNo F$Ewe{ 8LL 8k&b#zzX.F^xH(.就䡡 *,H,*ls> β$Ew83{3gLkk8 g&SbUUUW#f"D^^\f(.2jlӚ11*6 6<$Ew88GG3{  kQQkG3bUUUWX;t b텩^^xHDjlno~_$E88GGy kQQ#ʖ&UUUWXY.b;\ciI<;.jlnNo> β$Ee GLLgݎkQQ#ObGWXY.i;a|.;;.lnNo_ $E3gLLL 8ʐb #WYY,^2|~[ӚĪxNNo _$E88 { 8ݎk3L#Db¦UUUfkfX^^F<cH @β%E88GGG {yىkQQQkUUUWXX&8Dǩ^^I<jlӚ10 %GLyGkQQ#3!UUWXYxDb \^^豤jlnoj97 LLئ Q#ʻb83WXYhǑ!f,^塡m2m?jlnN F49P8kk8 8bU&kYY.,\+f&5Bm2lnN> TykQk8 3bUUU&k8#\^,fZ_<B왒Bl_ 69P w8QQ#kSUUUWXXSkAb\^^^;zhiIcjm0_)PҷQ#Ob 3UUWXY"\^^cjni@qt8Qʹb &k8XYh(zf^⡡H;.jln 0[ցT[Db Uʹf,Yhz\.HkHZ舡h0 က9%%wp)T<z\\^^\+!!,|cccHHcj_0 ကVwp‰)[)Mq;Z^^i[ccc\H ]က9؉P )MP|,i00 :]VPP [t}󕕕T c #2C292B", " , c #5D4C44", " < c #314933", " 1 c #645F58", " 2 c #6A5C54", " 3 c #736D6A", " 4 c #282527", " 5 c #5F5F53", " 6 c #5C5A5A", " 7 c #3D5742", " 8 c #363538", " 9 c #776A64", " 0 c #536651", " q c #857C75", " w c #252124", " e c #C4AF9F", " r c #AE9C90", " t c #686D5F", " y c #5F7060", " u c #60524A", " i c #1F1B1E", " p c #C6AA97", " a c #433E3E", " s c #394E34", " d c #CAA991", " f c #525050", " g c #57514B", " h c #1D191C", " j c #65655C", " k c #473D38", " l c #807566", " z c #858582", " x c #7F7165", " c c #566C57", " v c #1A1519", " b c #9A816F", " n c #28272A", " m c #5E6555", " M c #8E8177", " N c #4D4A4B", " B c #161315", " V c #171116", " C c #4B4849", " Z c #A19083", " A c #D2B39C", " S c #756C65", " D c #C1A188", " F c #635850", " G c #B49F8F", " H c #373432", " J c #3D332E", " K c #474445", " L c #726862", " P c #BE9D85", " I c #515E52", " U c #464444", " Y c #726662", " T c #BD9B84", " R c #1B251D", " E c #34302F", " W c #1F1D21", " Q c #352E30", " ! c #545255", " ~ c #5A5151", " ^ c #9F8977", " / c #535254", " ( c #BA9981", " ) c #1D1B1F", " _ c #CDCECF", " ` c #423E40", " ' c #C9A993", " ] c #5B5248", " [ c #1A1B1C", " { c #716157", " } c #504E51", " | c #5C5E60", ". c #526E56", ".. c #3D383B", ".X c #171319", ".o c #6B5B51", ".O c #564843", ".+ c #3F5A40", ".@ c #393637", ".# c #484849", ".$ c #6C673E", ".% c #C1C2C3", ".& c #667163", ".* c #605850", ".= c #9B8C80", ".- c #8B7A6D", ".; c #1F1F24", ".: c #3B2F2F", ".> c #444245", "., c #595353", ".< c #333031", ".1 c #332E31", ".2 c #C9AD96", ".3 c #3B2E25", ".4 c #858788", ".5 c #4F5B49", ".6 c #756254", ".7 c #6F6158", ".8 c #2D2A2B", ".9 c #364637", ".0 c #5C4D42", ".q c #6B684A", ".w c #040506", ".e c #807062", ".r c #292627", ".t c #282626", ".y c #9F9087", ".u c #343C35", ".i c #5D595A", ".p c #657565", ".a c #0E1113", ".s c #5A5757", ".d c #857B74", ".f c #232221", ".g c #8A7A6F", ".h c #595556", ".j c #CAAF9A", ".k c #67725D", ".l c #766C58", ".z c #221E20", ".x c #2B2015", ".c c #6C6462", ".v c #5F5148", ".b c #2D2E2E", ".n c #08090D", ".m c #736B4B", ".M c #3C3E40", ".N c #D3B9A6", ".B c #C3A793", ".V c #6B5F57", ".C c #2A282B", ".Z c #4F4B4C", ".A c #29282A", ".S c #29262A", ".D c #6D6945", ".F c #272828", ".G c #786D67", ".H c #5B5B5B", ".J c #C4A08A", ".K c #262427", ".L c #2A2521", ".P c #050000", ".I c #5A595A", ".U c #232424", ".Y c #C19E87", ".T c #6E6867", ".R c #120C10", ".E c #6C6665", ".W c #575C4D", ".Q c #917865", ".! c #305A37", ".~ c #5A5250", ".^ c #59524F", "./ c #746459", ".( c #979899", ".) c #515151", "._ c #504F50", ".` c #2B2A2F", ".' c #5A695D", ".] c #463A39", ".[ c #696158", ".{ c #6F6054", ".} c #4F4B4F", ".| c #6D7869", "X c #423A35", "X. c #2A2724", "XX c #C2A28B", "Xo c #141415", "XO c #3A3737", "X+ c #61585A", "X@ c #807F4A", "X# c #393736", "X$ c #C1A08A", "X% c #4D4A43", "X& c #937C6A", "X* c #2C221C", "X= c #4B694E", "X- c #474347", "X; c #454545", "X: c #F6F6F6", "X> c #66564B", "X, c #595452", "X< c #4C5D4F", "X1 c #466349", "X2 c #0A0A0B", "X3 c #6A635C", "X4 c #706258", "X5 c #D7BAA6", "X6 c #7F726A", "X7 c #B5967E", "X8 c #362629", "X9 c #3D3D3D", "X0 c #2D2B2A", "Xq c #3E3B3E", "Xw c #EEEEEE", "Xe c #78716D", "Xr c #2C2929", "Xt c #584B47", "Xy c #C9A58B", "Xu c #3B393B", "Xi c #C2A48E", "Xp c #B7A297", "Xa c #393739", "Xs c #4F4848", "Xd c #A59082", "Xf c #383738", "Xg c #282525", "Xh c #393539", "Xj c #282325", "Xk c #373537", "Xl c #0D1611", "Xz c #363336", "Xx c #888862", "Xc c #242121", "Xv c #A08C7D", "Xb c #2B412B", "Xn c #3E4D41", "Xm c #BC9C88", "XM c #89796D", "XN c #74665F", "XB c #231D20", "XV c #617060", "XC c #71645C", "XZ c #CCAB91", "XA c #C6AA95", "XS c #3D3D40", "XD c #93857A", "XF c #51504D", "XG c #06080A", "XH c #5C665B", "XJ c #7C706A", "XK c #7B7069", "XL c #393B3C", "XP c #3F3A38", "XI c #586657", "XU c #7E7858", "XY c #64604F", "XT c #5C5A5B", "XR c #514943", "XE c #5B5A5A", "XW c #2C452F", "XQ c #706B68", "X! c #262126", "X~ c #3F583B", "X^ c #655850", "X/ c #333336", "X( c #C19D86", "X) c #62584D", "X_ c #5E5553", "X` c #313334", "X' c #BF9D84", "X] c #575456", "X[ c #454241", "X{ c #201D20", "X} c #5E7060", "X| c #555254", "o c #1F1D1F", "o. c #60524B", "oX c #4A3F3C", "oo c #8F7762", "oO c #D5BCAA", "o+ c #616B59", "o@ c #716355", "o# c #4E5D43", "o$ c #312A2A", "o% c #5C4E47", "o& c #65615D", "o* c #847461", "o= c #5A4E45", "o- c #9B8371", "o; c #D6B7A1", "o: c #181718", "o> c #2B4F31", "o, c #4D4C4C", "o< c #27292A", "o1 c #BFA491", "o2 c #DEC8B6", "o3 c #171517", "o4 c #C4A38C", "o5 c #4C4A4B", "o6 c #161516", "o7 c #4B4A4A", "o8 c #151515", "o9 c #C2A18A", "o0 c #C1A189", "oq c #383234", "ow c #373233", "oe c #9D8C80", "or c #927B68", "ot c #50463B", "oy c #555657", "ou c #353031", "oi c #BC9B84", "op c #444243", "oa c #0D0D0D", "os c #7E7472", "od c #878788", "of c #D2BAAA", "og c #968479", "oh c #5B4E49", "oj c #394238", "ok c #6E5F55", "ol c #A39489", "oz c #405E42", "ox c #465448", "oc c #586953", "ov c #3A3839", "ob c #737067", "on c #030303", "om c #292425", "oM c #947D6D", "oN c #726C66", "oB c #5D5958", "oV c #726A66", "oC c #4D4545", "oZ c #716865", "oA c #100F13", "oS c #C6AE9E", "oD c #343033", "oF c #D0AE94", "oG c #BB9B86", "oH c #392F2E", "oJ c #585849", "oK c #6B625F", "oL c #96867C", "oP c #453D3D", "oI c #5F5146", "oU c #2C2C2B", "oY c #6D6157", "oT c #49524E", "oR c #525643", "oE c #050708", "oW c #413B39", "oQ c #5F6A53", "o! c #BCA494", "o~ c #626256", "o^ c #50494B", "o/ c #2E4A30", "o( c #2A2429", "o) c #786D65", "o_ c #B8A290", "o` c #5B5959", "o' c #927B6E", "o] c #857B75", "o[ c #907B6C", "o{ c #847B74", "o} c #CBAF9C", "o| c #595557", "O c #51614F", "O. c #5D5451", "OX c #283A2A", "Oo c #2F5A34", "OO c #555153", "O+ c #B2B36C", "O@ c #655245", "O# c #5F6B60", "O$ c #2D2E2F", "O% c #665E5D", "O& c #4F4B4D", "O* c #5A6A51", "O= c #4C4B4A", "O- c #766D66", "O; c #C2A089", "O: c #333638", "O> c #C1A088", "O, c #C09E87", "O< c #645551", "O1 c #474545", "O2 c #1A2A1C", "O3 c #1F2021", "O4 c #837C6C", "O5 c #241F1C", "O6 c #D6BFAD", "O7 c #545355", "O8 c #BC9883", "O9 c #423F40", "O0 c #D4BBAB", "Oq c #446545", "Ow c #514F52", "Oe c #81746A", "Or c #7B736E", "Ot c #292C2E", "Oy c #3F3B3D", "Ou c #695F59", "Oi c #0D0702", "Op c #295231", "Oa c #CEB7A5", "Os c #3B3939", "Od c #151617", "Of c #927F74", "Og c #4F4C46", "Oh c #151417", "Oj c #7B6E64", "Ok c #6A5A50", "Ol c #A48E7F", "Oz c #464747", "Ox c #716964", "Oc c #424B43", "Ov c #A18C7C", "Ob c #414142", "On c #5E6E5B", "Om c #2F2D2D", "OM c #463C3D", "ON c #181C1D", "OB c #2E2D2C", "OV c #594F49", "OC c #070809", "OZ c #685D5B", "OA c #3B3B3C", "OS c #8C827B", "OD c #383D39", "OF c #B6A497", "OG c #A69284", "OH c #746D6A", "OJ c #5D6550", "OK c #8E7F73", "OL c #9F8F87", "OP c #736B69", "OI c #A39081", "OU c #7C7E7F", "OY c #373538", "OT c #30231A", "OR c #343335", "OE c #9F8C7D", "OW c #474241", "OQ c #AB9C8C", "O! c #68695E", "O~ c #AB988C", "O^ c #C6AA96", "O/ c #2D2B2E", "O( c #9E8572", "O) c #2C2B2D", "O_ c #5C5045", "O` c #2C292D", "O' c #72776B", "O] c #655F5B", "O[ c #2E4D32", "O{ c #A4A5A6", "O} c #C7A38D", "O| c #554948", "+ c #5A4A43", "+. c #272528", "+X c #877E79", "+o c #353739", "+O c #36353A", "++ c #504743", "+@ c #C29F88", "+# c #5C5D52", "+$ c #967B6A", "+% c #687361", "+& c #887970", "+* c #52443B", "+= c #A98A76", "+- c #464443", "+; c #211F22", "+: c #696B62", "+> c #716660", "+, c #211D22", "+< c #86776E", "+1 c #827D6A", "+2 c #2F2F33", "+3 c #434040", "+4 c #1D1D1E", "+5 c #1D1B1E", "+6 c #4D3C36", "+7 c #FFFFFF", "+8 c #514F47", "+9 c #4A504A", "+0 c #6A614F", "+q c #4C4A4C", "+w c #574A43", "+e c #151316", "+r c #393636", "+t c #141115", "+y c #707062", "+u c #212325", "+i c #786960", "+p c #4E4344", "+a c #525E55", "+s c #101111", "+d c #23492A", "+f c #363233", "+g c #385838", "+h c #444244", "+j c #A0897A", "+k c #796657", "+l c #CFAC91", "+z c #C9AB95", "+x c #81756C", "+c c #403E40", "+v c #76766B", "+b c #17191B", "+n c #2D2A2A", "+m c #A29489", "+M c #3C383C", "+N c #423738", "+B c #3A3A3A", "+V c #3A383A", "+C c #383A38", "+Z c #BBA291", "+A c #C9B4A2", "+S c #010102", "+D c #685950", "+F c #625854", "+G c #927D6C", "+H c #363236", "+J c #E5E5E5", "+K c #9D9A70", "+L c #0D0F11", "+P c #50634C", "+I c #3A2F30", "+U c #434246", "+Y c #221E1F", "+T c #A38876", "+R c #8C7766", "+E c #302E30", "+W c #757577", "+Q c #374C3A", "+! c #525C44", "+~ c #C5A994", "+^ c #486144", "+/ c #2D2C2D", "+( c #2C2C2C", "+) c #2D2A2D", "+_ c #5C4F44", "+` c #65625A", "+' c #C2A791", "+] c #877161", "+[ c #2A2A2A", "+{ c #6B5F56", "+} c #74706C", "+| c #4F5441", "@ c #6E6F70", "@. c #5A4942", "@X c #314234", "@o c #262826", "@O c #4D4949", "@+ c #5E6253", "@@ c #5C5B5B", "@# c #BCA18B", "@$ c #9B8E85", "@% c #7B7355", "@& c #C1A086", "@* c #29231F", "@= c #847B75", "@- c #AEB0B2", "@; c #585757", "@: c #C09E85", "@> c #5E5653", "@, c #C09C85", "@< c #988C82", "@1 c #B99D88", "@2 c #514139", "@3 c #555354", "@4 c #1F1E1F", "@5 c #525151", "@6 c #D4BBA9", "@7 c #312D2A", "@8 c #586D5A", "@9 c #C2A794", "@0 c #515446", "@q c #28562E", "@w c #191619", "@e c #3E393A", "@r c #99826F", "@t c #4D4B4C", "@y c #3D3939", "@u c #161616", "@i c #536B55", "@p c #D7D9DA", "@a c #C4A28C", "@s c #C3A28B", "@d c #161216", "@f c #5A5B5C", "@g c #967E6C", "@h c #4A4749", "@j c #756B66", "@k c #BC9F8E", "@l c #C0A088", "@z c #383534", "@x c #BFA087", "@c c #BF9E87", "@v c #454544", "@b c #6B6866", "@n c #444343", "@m c #D6BFAE", "@M c #2D5E36", "@N c #C4AB99", "@B c #BA9882", "@V c #7D7571", "@C c #413F40", "@Z c #6C635D", "@A c #436745", "@S c #473E3C", "@D c #9D8676", "@F c #4B594D", "@G c #403F3F", "@H c #7C7370", "@J c #0A0A0A", "@K c #586D5D", "@L c #181A1B", "@P c #9A8473", "@I c #8F827C", "@U c #695F5A", "@Y c #76736A", "@T c #727966", "@R c #96866F", "@E c #827162", "@W c #3F5F41", "@Q c #7B7065", "@! c #907F73", "@~ c #49494B", "@^ c #4F4647", "@/ c #383537", "@( c #8D7D70", "@) c #B99F8E", "@_ c #363535", "@` c None", /* pixels */ "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`+R+k@`@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`+$oMo%O(.M@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@..v.m.$o@+T dOW.b@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`+ X).D -o*ok+wo= X./X`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@2o.+0X@@%@kOV.7@D+Do-o4 bO:@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`O_XCX6XU.q@R pXA =o[+z 'XmX$o9XP.U@`@`@`@`@`", "@`@`@`@`@`@`@`@` u@Q+xOu+F.[+iOl.B+~.2@1.6+6o/oG@s.{o<@`@`@`@`@`", "@`@`@`@`@`ot.V.GXJoL L 9Of@)o}.jO^ ^ ,+*o$.Xo>XYXX@gOt@`@`@`@`@`", "@`@`@`ohXK+1O] S.=OGOIo_XMOe & {.O.5 Q+;XromOXo#@ao0 H [@`@`@`@`", "@` .OL+KO+O4@jXDo!oe+&og@!X^XtoqX!oz+++[+5 i@o.!O} DOkO3@`@`@`@`", ".y+Xos $XxXpOa.N ZXdXNO@7+s@`@`@`", "OF@mO6O0O~+>OZo^XIX,@nO9OD m+B@yO/.KO* EoU@4+e R+g.J@xX>Od@`@`@`", "OF e # YO'@to, N cX_@G K@Co+O`@_+r.1@W.]X{oH.:X8Oo+=@cor+b@`@`@`", "@ CO=@T+3+VOR+C.k@e.@.C+, X0Xg@dXl@qX( (ooXG@`", " qOH.i.}X}oN fo5OA+%@OO1 U@/+PoP+/X#ow.9OJ+toA.n B JoR@B@:XyO5@J", " q+}.Ho`.' O.#@5+qOn.^OYXk..X=O. o+. )ON.+ k.o@rXZ+lX7+]+_@* @J", " qOrOwX]O#@Y@t+hXq@i@UX;+-Xz 7Og.;oW 2o'XioF %+GX4X @L@`@`@`@`@`", " q 3@@X|+aob._@5.Z. @>+2.` ao~ l+' A@#@P x gXL@`@`@`@`@`@`@`@`@`", ".d@V@;XTXH+v+UXS+O@FX3OK+Zo;o1+j.g@Z@`@`@`@`@`@`@`@`@`@`@`@`@`@`", ".dXeO7@~oT+:oK M GX5@9Ov@(O-@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", ".d@H.E@I roO@NXv.-@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "olo2+AOE.e@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "Oj@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`" }; schismtracker-20180209/include/auto/schismico_hires.h000066400000000000000000001103421323741476300225550ustar00rootroot00000000000000/* XPM */ static const char *_schism_icon_xpm_hires[] = { /* columns rows colors chars-per-pixel */ "128 128 230 2 ", " c None", ". c #050505", "X c #0B0B0B", "o c #110F11", "O c #151315", "+ c #191719", "@ c #1C1B1C", "# c #191717", "$ c #211E1E", "% c #1B251D", "& c #25211F", "* c #211F21", "= c #242324", "- c #292525", "; c #292729", ": c #2C2B2C", "> c #2A2A26", ", c #312D2C", "< c #352D27", "1 c #283829", "2 c #34312E", "3 c #3B322C", "4 c #302F30", "5 c #343334", "6 c #3A3534", "7 c #3C3936", "8 c #383638", "9 c #3C3B3C", "0 c #343D34", "q c #2F3A30", "w c #1F2B20", "e c #433A34", "r c #423D3B", "t c #483C38", "y c #42382F", "u c #29452C", "i c #384837", "p c #375839", "a c #2F4E31", "s c #35643A", "d c #39663C", "f c #39683E", "g c #336237", "h c #44413E", "j c #4C423C", "k c #464B38", "l c #51453E", "z c #584A3E", "x c #45533D", "c c #50543F", "v c #65603B", "b c #413F41", "n c #3D5641", "m c #3C6A40", "M c #3E6341", "N c #444344", "B c #494543", "V c #4C4945", "C c #494749", "Z c #4D4B4B", "A c #454B45", "S c #534541", "D c #544A44", "F c #5A4C45", "G c #524D4C", "H c #5A4E49", "J c #594441", "K c #435442", "L c #4C5C4C", "P c #485847", "I c #54514E", "U c #5C524C", "Y c #595848", "T c #514F51", "R c #545354", "E c #595654", "W c #555A54", "Q c #5C5A56", "! c #5D5B5A", "~ c #595758", "^ c #664D4A", "/ c #63544C", "( c #6A534C", ") c #6B5A4E", "_ c #64574B", "` c #635652", "' c #6C5653", "] c #655B55", "[ c #6B5C53", "{ c #625E5C", "} c #695E59", "| c #735C58", " . c #795E4F", ".. c #426E46", "X. c #436E48", "o. c #49664A", "O. c #46724A", "+. c #49744C", "@. c #467448", "#. c #536353", "$. c #596958", "%. c #4E7952", "&. c #4E7252", "*. c #527B55", "=. c #557D58", "-. c #58775A", ";. c #526D4E", ":. c #797549", ">. c #6C6155", ",. c #63615E", "<. c #6C625C", "1. c #636B5D", "2. c #686858", "3. c #736155", "4. c #73655C", "5. c #75695D", "6. c #7B6A5D", "7. c #796757", "8. c #7D725A", "9. c #6E6A4A", "0. c #666462", "q. c #6B6663", "w. c #6B6965", "e. c #6D6C6A", "r. c #646E61", "t. c #736661", "y. c #746A64", "u. c #7B6C64", "i. c #736D6B", "p. c #7A6D69", "a. c #7C6663", "s. c #657763", "d. c #7D7166", "f. c #73726C", "g. c #7C736C", "h. c #78786B", "j. c #747372", "k. c #7E7671", "l. c #7F7873", "z. c #7B7B7B", "x. c #5E7660", "c. c #846A5A", "v. c #837A56", "b. c #836E65", "n. c #847265", "m. c #8B7465", "M. c #82756C", "N. c #84796D", "B. c #8C7A6C", "V. c #8A7968", "C. c #937C6C", "Z. c #937767", "A. c #827A74", "S. c #8B7D73", "D. c #887673", "F. c #937F73", "G. c #5C835E", "H. c #578057", "J. c #62865D", "K. c #5D8361", "L. c #5F8861", "P. c #638964", "I. c #678B6A", "U. c #698C6C", "Y. c #658567", "T. c #6C916D", "R. c #769775", "E. c #6F8E6F", "W. c #88874A", "Q. c #92914F", "!. c #B1B153", "~. c #8D886B", "^. c #96846D", "/. c #8D827A", "(. c #8D8176", "). c #948275", "_. c #9B8474", "`. c #93857B", "'. c #9A8679", "]. c #9B8A7C", "[. c #958A79", "{. c #95986B", "}. c #A1846F", "|. c #A48975", " X c #A48C7B", ".X c #A98E7A", "XX c #A38772", "oX c #AB917D", "OX c #A6907D", "+X c #B5957E", "@X c #A6A56B", "#X c #B8B872", "$X c #C2C259", "%X c #CCCC63", "&X c #C6C676", "*X c #D4D47A", "=X c #D1D177", "-X c #968A82", ";X c #9B8D83", ":X c #948780", ">X c #9E9086", ",X c #9F9188", " 9 ", " e 3.7X0X0X8X8X8X0Xj b b i ", " l Z.0X8X0X0X0X8X8X8X8X3.r N N 9 9 9 ", " 2 / oX0X0X0X0X0X8XOX) F 8X8XoX2 N N b b 9 ", " t 7.8X0X0X0X0X0X8XC.F - @ e 8X8X0Xj N 9 b 9 9 ", " D C.eXeX0X0X0X0X7X7.e $ * z V.0X7X0X7X[ 9 n 9 b 9 8 ", " 3 ` b._ 7XeXeX8XeX^.:.k $ < ) oX8X8X8X8X8X8XoX2 b b 9 9 8 ", " j 3.6.Y H D V.8XeX:.:.!.!.v c.8X8X8X0X0X8X8X8X8X8Xj b b 9 9 9 ", " F 6.4.H H z .B.oX:.W.$XQ.:.Z.8XeX8X0X7X8X8X0X8X8X8X8X[ 9 b 9 9 8 8 ", " 6 ) n.[ H U U 7.|.Z.:.Q.!.W.:.oX8X8X8XeXc.^ ) |.7.F 8X8X8X8X.X2 n 9 9 0 8 ", " j 6.u./ Y U _ n.7X7.J.!.!.:.v.eX0XeXeX.X3.D l l l j l D .X8X8X8X8Xj r 9 9 8 0 ", " U n.5.U U U 4.F. X9.W.$XQ.:._.0X0XeX0XV.) l z D l D l ) V.7Xn.U |.8X8X) 9 9 9 9 8 5 ", " y [ M.} Y U ] u.2XV.:.!.!.v.:.0XeX0X0X6Xu.U V D D D l F 5.OX|.3.z j j 3.8X8X.X2 9 9 0 5 5 ", " j u.M.] ` / >.V.9X9.W.$XQ.:.C.eXeX0XeX_.>.D D D z z V ) V.8XV._ j l j _ m.7X7X8X8Xt i 9 8 8 8 ", " U V.u.Y ` ] 5.'._.9.Q.$XW.:.7XtXtX0X0Xn.] D D F D D H 4..XoX6.F S l D 3. X8X8X8X8X8X8X[ 7 9 7 8 5 5 ", " e <.B.5.` Q } n.9Xd.:.!.!.v.V.eXeXiXeX0X4.H H H H H D [ V.0X0X^.l D l _ m.8X8X8X8X8X0X7X8X8XoX< 9 9 5 5 5 ", " D u.M.} ] ` 4.).OX8.W.$X%Xv..XiXeXiXeXeXeXeXH H H H U 4. XeX0X0X0X7XD 7.|.8X0X8X0X8X8X8X8X8X8X8X7Xt 9 8 5 5 4 ", " / B.d.] } ] d.1X(.U Q.%X%X%X%X:.eXiXeXeXeXeXeXeX` D [ V.0XoX|.0X0X0X0X0X8X0X0X0X0X8X8X8X8X8X8X8X7X7X8X) 5 9 0 5 5 5 ", " e 4.S.y.] ] <.B.eXV.W.Q.k _ k !.W.8.eXeXeXtXeXiXeXeXiXV.OXeXV.} D 4.eX0X0X0X0X0X0X0X8X0X8X8X8X8X8X8X7X8X+X8X.X2 8 0 5 q 4 ", " D M.N.<.} } u.].].u.).W.%X%X%Xv >.'.tXeXiXiXiXeXeXeXeXeXiX6X6.Y F D 3._.eXeX0X0X0X0X0X0X8X0X8X0X8X8X8X8X8X8X8X8X7Xh 7 8 5 4 4 ", " ` ).g.} <.<.V.2XB.<.] } S.d.%XQ.v.rXtXpXtXiXtXeXiXeXiXiXeXiXeX_.D ` n.6X0XeX0X0X0XeX0X0X8X0X0X8X8X8X8X8X8X8X8X8X8X8X7X) 5 5 5 5 4 : ", " h 5.).u.,.>.y.`.2XM.>.,.} ] ] M.N.%XV.iXiXtX Xn.eXiXiXeXeXeXeXeXeXeXeX_.eXeXeXeX0XeX0X0X0X0X0X0X8X8XC.OX0X8X8X8X8X8X8X+X8X7X.X: 0 5 4 5 4 ", " H B.S.q.<.<.M.1X].u.} } } ] ] ] } y.2X:.].eXV.>.` Y iXtXiX0X0XiXiXeXiX0XeXeXeX0X0XeX0X0X0X0X8X8X8XXXc. .c._ c Y eX+XeX+X8X8X+X8X+Xt 6 5 4 4 4 ", " 3 ] `.N.<.<.t.(.9X(.4.<.} } } <.] } ] ] ).iX].u.] ] U >.B.iXiXiXiXiXeX0XiX0XiXeXeXeXeXeX0XeX0X0X0X7Xm.c.c. .^ k *.H.x +XeX+X+X7X8X8X8X8X) 2 5 4 4 4 4 ", " j y.[.p.<.<.p.'.aXpXy.<.<.<.,.>.{ ] } } ] <.B.<.] ] ] u.1XiXeXeXiXiX0X0XiXiX0XiX0XeXeXeXeX0XeX0X0XF.m.c.c.( t b .o.@.@.p +XeX+XeX8X8X8X8X7X.X> 8 5 q 1 : ", " U S.(.y.t.<.S.4X4Xy.iX1X5.<.} <.B.t.] } } ] ] ] ] ] 5.B.eXiXtXiXiXiXiXiXiXiX0XiXiX0XiXeXeX0XiX0X2Xm.c.c.c.^ t ( ( t , 1 m m f 8.+XeX+X8X+X8X+X8X7Xe 5 4 4 4 : ", " 6 } '.A.t.q.p.`.uX'.} N G pX4.,.d.'.pXpXn.<.} ] >.] } n.2XpXiXiXiXiXiXiXiXeXeX0XiX0XeXeXtXeXeXeX0XC.c.c.c.( t ^ ( ^ y - + # & f m m Y 8X8XeX8X8X8X7X+X8X) 2 5 4 4 : : ", " j n.`.N.t.t.N. 4 4 4 : : ", " Y ).`.f.y.y.(.rX1Xy.C Z y.2XaXaXaXuX).M.].u.] <.} ,.M.2XpXpXpXpXiXpXpXiXiXiXeXiXeXeXtXiXiX0XtXeXeXC.c.c.( l ^ ' J 6 * + $ + $ @ # # $ u m m f ^.8X8X8X+X+X8X+X8X+Xe q : 4 : > ", " 6 <.;X(.y.y.g.;XuX`.{ B E M.uXaXaXaXaXaXM.<.<.,.<.<.d.).iXpXpXpXpXpXtXiX].rXiXiXiXiXiXiXiXeXiX0X Xm._.8X/ J J | ^ t , * * * $ $ $ $ # $ # $ q @.%.@.Y 8X8X8X8X8X8X8X+X8X) 4 4 4 : : > ", " V M.;XA.y.i.A.5XqXM.I C N N '.aXaXaXaXaXaXaXB.<.<.q.B.4XaXpXpXpXpXpXaX0XS.<.] F.iXiXiXiXiXiXeX7XD.c.c.7.Y D .X/ S 6 ; * * * * * * $ $ @ * - 5 N 7 ....f x 8X8X8X8X8X8X+X8X+X.X< 4 4 : ; ; ", " U `.[.g.p.g.`.uX>Xy.G C C N G d.rXaXaXaXaX'.1XaXS.d.'.pXpXpXpXpXpXpXpX Xd.] ] ] d.9XiXiXeXiXiXF.b.c.c.' J #.J.G.n.S = * = = * * * * * * = 4 t h 5 ; + a f m p OX8X8X+X8X+X8X8X8X+Xe 2 1 : : : ", " e q.1X/.g.y.A.;XrX~.,.Z C C C ,.S.yX;X`.sX4XS.t.<.`.aXpXaXaXaXpXpXpXaXtX(.5.] ] t.).tXiXiXiXeX2XZ.b.m.c.( H | ' P %.@.>.[ = = = * * * * * 4 r B 8 , $ # + + 1 m m m 2.8X8X+X8X+X8X+X8X8X_ : 4 : : = ; ", " D N.>XD.f.g.(.rX1X~.@X*XW.5.T p.1XuX`.p.t.y.d.q.<.S.4XaXaXaXaXpXpXaXpX1XM.} ] } n.2XpXtXtXpX0XF.m.m.c.( F | | H r : 0 +.+.L n.= = = = ; 4 9 S b 4 = @ @ # @ + + * m m m c 8X8X8X8X8X+X8X+X+X.X> 4 : : > ; ", " ` 3XqXk.g.A.-XyX^.{.&X=X=X=X@X~.yX5XS.y.y.y.y.q.d.[.aXaXaXaXaXaXaXaXtX).y.>.{ y.).iXpXpXiXiX ; = ", " e y.uXgXgXqXS.5X4X~.@X=X*X*X&X{.[.aX;Xg.y.y.y.y.t.D.3XaXaXaXaXaXaXaXaX1XN.<.<.<.B.2XpXpXpXpXpXiX6.Z.b.' F | | H r : : : : ; ; : ..O.o.V.V C V 9 4 * * $ $ $ @ @ @ + @ @ # @ u @.%.O.7.8X+X8X+X8X8X8X+X8X_ : : > > ; % ", " D S.jXgXfXfXgXjXuX{.#X*X*X*X*X*X~.sX : ; % = ", "g.hXhXgXgXhXwX:Xg.k.h.rX#X*X&XR @X*X*X{.fX`.p.`.qXfXsXaXfXaXaXaXaX = w = ", "A.hXgXjX3XA.k.k.k.k.k.uX#X*X&XR {.*X*X^.fXuXaXjXdXdXsXsXfXaXfXaXaXM.y.S.qXaXaXaXpX5XF.b.C.a.} ' a.] P.=.#.n.4 5 4 4 4 : 4 5 7 V R B 8 : ; : O.+.o._.7 = * * * = : t b b 8 , * + $ + + + w m m f Y 8X8X8X8X+X8X+X+X7XXX> > : = = % ", "N.jX-Xl.A.k.k.k.k.g.j.yX#X*X&XR {.@X~.rXfXsXjXsXdXdXsXsXaXaXfXaXaX9XaXaXaXaXuX).B.Z.m.| ' a.a.H B 5 &.=.L ).5 4 5 , 5 9 C I V 9 , : > ; ; ; M +.O.6.H = = : 7 V b b 4 * $ * + * + + + + % f f f x +X8X+X+X8X8X+X8X+X6X7 > : = = = @ ", "A.uXl.A.A.j.k.k.k.k.g.yX#X*X&Xi.).sXfXsXsXfXaXfXdXdXsXsXaXsXaXaXsXaXaXaX2XF.m.C.a.' t.b.} Z 8 8 6 8 L =.*.'.8 5 9 N I G h 8 : : : : ; ; ; * n +.O.Y a.7 B D r 5 ; * * @ $ + + + + + + + # a m ..s +X8X8X+X8X8X+X+X7X6X/ = > = = % = ", "A.yXj./.,X;Xl.k.k.g.g.yX~.^.4XfXfXfXfXdXfXsXsXsXdXdXkXsXaXaXaXaXaXaXaXaXn.n.4.} b.t.T b 9 8 8 8 8 8 P =.=.B.I I E C 9 4 4 : : : : : ; ; ; ; i O.%.Y '.h 8 : $ * * @ * * @ * + + + + # @ - i %.+..._ eX+X+X8X8X8X8X7X+X|.- : = = * * ", "g.jXrXkX`.k.k.A.A.h.A.yXaXfXfXfXdXdXfXfXsXfXsXfXdXdXsXsXfXfXaX1XF.n.'.a.} a.a.` C b 9 8 9 9 8 8 8 5 K G.J.N.<.N 8 4 4 , 4 4 : : : ; : ; : 8 k G.H.o.oX2 = * * * * * * + @ @ + + * ; 6 r h 0 m m d c eX+X8X+X+X+X+X7X+X8X3 > > = = @ * ", "N.gXhXuXA.>XkX3X/.4XfXfXjXfXfXfXfXfXdXdXfXdXsXfXdXdXdXdX9X'.D.F.u.r.E.].a.T N b 9 9 9 8 9 8 7 8 n C W P.G.2.5.4 4 4 4 4 : 4 : : : : 8 h G V i +.+.@.u.D = * * = * @ * @ @ * - y b b e ; $ O p f s p +X+X8X8X8X7X8X+X+X+X_ > = = = @ @ ", "k.gXhXgXgXgXgXgXfXhXfXfXfXfXfXfXfXdXfXfXdXdXsXsXdXaX1XF.F.Z.a.t.h.R.U.S.} b b b 9 b 9 9 9 r C W E Z B =.=.#.M.5 4 4 4 : 4 : , 8 N G G h 6 : = K +.@.>.| = * * * * * * ; e b b b < * + + # # u f d s 8.8X+X8X+X7X+X8X+X7XXX= > = % $ @ ", "N.gXhXgXgXhXhXfXhXfXhXfXfXfXfXfXfXfXjXdXfXfXjX9X % = @ @ @ ", "g.gXhXgXfXhXfXhXfXfXfXfXfXfXfXfXdXfXfXdXqX 4 &.=.H.y.4.: @ = * $ @ $ * @ @ + @ + + + + # - h *.H.*.z +XiX+X+X+X7X+X7X+X+X7 & = * % @ @ ", "g.gXhXgXgXgXfXgXhXfXgXfXkX1X:XF.S.h.s.rXp.} T C b Z C C b C b C G s.T.s.4XN 9 9 9 r 9 9 9 8 8 8 8 b C #.P.J.2.u.4 4 4 : 4 : : : : : ; ; 5 7 B Z o.%.@.Y n.= * * * @ * @ @ @ @ + + + $ - y ^ ^ ^ %.@.m p +XeX+X+X7X+X7X+X+X+X/ % * % @ @ # ", "g.hXhXgXhXgXgXfXfXfXwX > O.+.O.5./ * * * & 3 b ^ ' ( J y - + @ # # O O w s f f c eX+X+X7X+X8X+X+X+X+X^ @ % @ @ + + ", "k.gXgXgXjX/.D.p.D.D.i.{ R T T T W U.U.-Xq.R ! e.0.~ Z C b C C b b P K.K.S.q.S R { ~ Z b b 8 8 8 8 8 8 5 L =.=.B.H b S T H b 8 4 : : ; ; ; ; * ; * P +.O._ 6.= 6 t ' ' ' b y * $ + + + + O + O O + s f d p +X7X+X+XeX+X+X+X+X+X|.w % @ # # O ", "k.gXgXwXyXa./.z.e.~ R R R T T T R U.T.-Xl.w.! R C C C C C b C b b P P.P.A.D.{ T b b b 8 8 9 8 8 8 8 5 5 n H.H.g.a.T N 8 4 : : : : : ; ; ; ; ; ; * n O.+.Y .X/ ' ' S e * $ $ + + + + + + + O O O O u f f m 9.8X7X+X+X+X+X+X+X+X+Xy $ @ @ % O # ", "p.5XD.D.wXq.,.R R R R R T T T R #.R.R.`.g.Z C C C C C b C b b C Z ~ T.T.g.B.b 8 b b 8 b 8 9 8 8 8 4 8 b L P.L.2.d.5 4 4 4 4 : : : ; ; ; : ; ; < : B =.J.;. XF 6 - @ @ * $ $ + $ + + + + # O O @ = 0 @.@.@.k +X7X+X+X+X+X+X+X+X+X^ @ $ @ O + # ", "p.l.z.D.wX,.R ~ R R R R R ~ 0.e.0.E.U.(.k.C Z C C C C C C Z R ,.w.~ P.P.w.(.b b b 8 b 8 9 8 8 r b G Q ~ P H.=.W S.4 4 : 4 4 : : : : : ; * , t b | ` J.=.*.b.F * * * * @ $ $ + + $ + + + $ > 5 t r i ....d i 8X7XeX+X+X+X+XeX+X+X}.& % # # # # ", "p.l.A.A.wX{ R ~ R R ! 0.j.e.! R T I.U.z.D.C Z C C C Z R ! 0.{ R C n -.x.2.].9 b r 9 9 9 b b G ~ ! Z b 8 9 *.*.P 1X5 , , , : 4 : : ; 4 t D | ' ' b e X.O.O.) 5.% @ * @ * @ @ @ + + * , 6 r r r , & # a d s p ^.+X+X+X+XeX+XeX+X+X+X3 @ @ # # O O ", "p.A.A.l.5X0.R ~ 0.e.j.0.~ T R T T x.U.l.S.C C T T { q.0.~ T b b b N -.Y.$.rXn 8 9 9 b Z ! ~ G b 9 6 5 5 5 X.=.&.'.r 4 4 , , : 5 b F | | | b t ; - = p O.O.D _.* = @ * @ @ * ; , t t r r , = # o O o u d s s Y +X+XeX+X+X+X+X+X+X+Xz # % # # O O ", "p.l.A.l.5X0.e.j.e.{ R R T T T T Z s.U.h.`.R ! w.q.~ T C b C b C b n #.K.$.rXC C T { { T b b 8 8 8 8 8 5 5 L *.*.N.H 4 : 5 b ^ t.| ' S 8 , * : ; - * i O.O.p 6X2 $ @ = : 8 h j h r : * + + O O + O O % d s s x +X+X+X+X+X+X+X+X+X+X|.& # # # # O ", "p.l.A.A.5X0.0.~ R R R T T T T T T r.E.E.;Xw.! R C C C N C b Z b b N #.K.s.2X} { T b b r b 8 8 8 8 8 8 4 8 K *.=.y.| b / t.| ' S t 4 ; ; ; ; * : - ; q +.X.O.n.D , 7 B B h 7 : = + + @ + + + + o O O O g s s p .X+XeX+X+X+X+X+X+X+X+X3 @ # # O O o ", "p.l.A.A.5X0.R ~ R R R T T T T R ! U.R.h.>XZ C C b T b Z b b b b b Z $.T.T.`.0.N 9 b 9 8 9 8 8 8 8 8 5 5 5 A =.G.y.B.6.' S 9 4 : : : : ; ; ; ; - ; * > O.O.+.4.u.B B 6 : = @ # @ # + + + + + O O o o O u s s f _ +X+X+XeX+X+X+X+X+X+Xz @ # # O O o ", "i.l.l.A.5Xw.~ R R R R R ~ ! 0.j.0.s.U.e.3XC Z Z Z T Z b b T Z ~ { 0.$.P.K.S.q.9 9 9 8 9 8 9 8 8 8 5 4 r G ` E.U.2.).9 , 4 : : : ; : ; ; ; : = ; ; 4 6 &.H.H.Q ^.: = @ @ @ @ @ + @ + + + O O + + O O O 1 f @.@.i +X+X+X+X+X+X+X+X+X+XXX$ % # O O O ", "p.l.A.A.3Xw.R R R ~ ! 0.j.0.,.R Z x.U.s.qXC C C b b Z b ~ { q.,.T C T K.K.N.u.9 9 9 9 9 9 8 8 8 r G ' a.6.] J.=.P 2X8 : : 4 : : : ; > ; ; : 5 7 Z Z Z o.+.O.x 8X: @ * * @ @ @ @ + + # + + O O # * : 6 0 @.@...a +X+X+X+X+X+X+X+X+X+X+X< O O O O o X ", "p.l.A.l.3Xe.R ,.0.j.e.0.R T T T T $.U.r.yXT C Z G ~ 0.e.{ R b b b b Z K.K.y.A.b b 9 9 9 9 b T | a.a.' Z 8 5 ;.*.&.).h 4 4 : : : : : : 8 h V V Z r 6 < 0 O.X.m ^.j * @ @ @ $ + @ @ + + + $ - 5 r h e 7 - p d s g 8.eX+X+X+X+XeX+X+X+X+Xc O # # o o X ", "p.l.A.A.3Xe.j.e.0.! R T R T T T T $.U.x.yXR E ,.e.{ ~ R b T T b b N A K.G.2.`.9 9 9 b T | b.a.] G 9 5 5 5 5 L *.=.M.U 4 4 : : 5 9 B G G Z r 8 : = = = q O.X.X.5.) @ * @ @ @ @ @ @ = , e r h r 2 = @ O o 1 s s g c +XeX+X+X+X+X+X+X+X+X}.$ # O O o o ", "p.l.A.z.wX0.,.~ R R R R T T T T T $.U.s.yXi.,.E R T b b b b b b N b N P.K.1..: 8 r V R G Z 7 5 : = = ; = = = > O.O.X.Y m.@ @ * @ = > 5 r b h r , = @ O O O o O % s s s p +X+X+X+X+X+X+X+X+X+X+X< # O O o o X ", "p.l.A.A.3Xe.R R R R R T T T T T ~ s.R.U.qX{ C C T b T b T b b b N b r -.P.2.rXa.a.' G b 8 8 8 8 5 8 5 5 5 5 n =.=.2.M.V R I Z b 5 : : ; ; ; = ; ; = = = p O.X.K 9X= = 2 7 B j h 7 : = # O + O O o O o o o g g g g Z.+X+X+X+X+X+X+X+X+X+Xz # # O o X X ", "p.l.A.D.3Xe.~ R R R R R R ! 0.e.w.0.T.Y.3X,.Z C b T b T b b b b b G ' Y.R.s.rXE b b 9 8 9 9 8 8 8 5 5 5 5 8 A K.G.2.'.C r 6 : : : : : ; ; ; ; ; = = = = i X...o.|.U B B h 6 : * + + + + + + O O O o O o o u s g g Y +X+X+X+X+X+X+X+X+X+X^.% O # o X o ", "p.l.A.A.3Xi.R ~ R R ! 0.j.e.! R T #.I.I.,X0.Z C C C C C b b R ~ a.b.p.r.P.-.1XR 9 9 9 8 9 8 8 8 8 8 8 h Z E W P.G.L 8X8 : : : : : ; ; ; ; ; ; = = = = : i *.H.=.d.>.5 - $ @ @ @ + + + + # O O O O o o O o w g g s i +X7X+X+X+X+X+X+X+X+X+X< # o o o X X ", "i.k.l.A.3Xj.~ ! 0.j.e.,.R T T T T W I.I.-Xe.Z C C C C T { a.D.b.{ T N #.K.K.`.] 9 9 9 9 8 9 8 r C G Q Q I C 9 *.*.X.).B 4 4 : : : : : ; ; ; ; ; 4 7 B V V =.%.+.>.6.* @ @ @ @ + + + + + O + O O O O O o # w ..@.@.p .X+X+X+X+X+X+X+X+X+X+Xz o # o o X X ", "y.l.A.A.3Xe.f.f.0.~ R R R T T T T W U.I.:Xp.C Z T } a.D.b.{ R Z b N b W K.K.B.<.9 9 9 9 N C ~ Q ! G N b 8 4 5 o.*.*.M.U , : : : ; ; ; - : 6 9 Z G V C 8 2 ....X.K .X= $ @ @ # @ @ + + # + + O # # @ - 3 y e s ..f d 9.+X8X+X8X+X+X+X+X+X+X}.# # o o X X ", "y.l.A.l.3Xe.! R R R R T T T T T T W U.I.:Xl.0.p.D.p.} T Z b b b N N b L K.K.h.d.b C G ~ ! ] G C 9 8 8 5 6 5 4 P *.*.<.4.4 : : : : 5 9 B G G V h 8 : = * = p ..X.p 7X6 @ $ $ + @ + + + # + @ - , r 7 h 7 > & u g g s c +X+X+X+X+X+X+X+X+X+X+X< o o X X X X ", "p.l.A.A.3Xg.R ~ R R R T R T T T Z R E.R.-X:Xp.{ T Z Z b Z b Z b b N b P K.K.h.S.{ ,.! G C 9 8 8 8 5 8 5 5 4 5 A *.*.] n.: 5 8 h V I I C r 5 : = * ; * ; * a O.m ..n.H $ + @ + @ @ @ = , 7 h t r 2 = @ O o o % g g g a +X+X+X+X+X+X+X+X+X+X.Xz o O X X X X ", "p.l.A.A.,Xg.R ~ R R R R T T T ! q.g.{.R././.C T Z b b Z Z b b b b N B W I.U.h.'.G C 9 9 9 9 8 8 8 8 5 5 5 5 5 i *.*.L ].B I I I B 9 5 : : = = - ; ; ; * * q X.O...>.3.@ $ $ = : 6 h h h 7 : = + O O O o o o o a s g g 8.+X8X+X+X+X+X+X+X+X+X}.# O X X . X ", "y.A.A.A.3Xk.R R R R R R { t.D.D.p.{ E.I.A.`.C C Z Z b b b b b Z R ! ,.,.I.P.1.1X9 9 9 8 9 8 8 8 8 5 8 5 5 5 5 N G.G.#.0XI j 9 5 : : ; ; ; ; : = = * = = = > O.X...Y '.: 2 r N B r 5 : $ # # o + O o O o o o o 1 g g g c +X+X+X+X+X+X+X.X+X+X.X< X X X X X . ", "y.A.z.A.,Xl.~ R ~ { i.D.D.p.0.R T Z I.U.h.`.Z C b Z Z Z R ~ 0.q.,.~ G A K.K.#.yXb 9 9 9 8 9 8 8 8 8 5 8 r Z I E K.G.*.).V : : : : : ; : ; ; ; = ; = = = = * M @.@.P 7XD B r , = @ # + + # O + + O O O o o o o % g g g a +X+X+X+X+X+X+X+X+X+X+Xz o o X X X . ", "e.D.l.D.,Xj.0.e.D.D.i.0.~ R R b T Z Y.U.j.>XC C C R E ,.q.,.~ Z C b b b -.K.;.rXZ 9 9 9 8 8 8 8 9 C G E Q U Z b o.*.%.n.] : : : : : - ; = ; = = = = = ; 5 7 o.H.H.%.^.F * * @ @ + + + + O + o o O o o o o o o O s g g u ^.+X+X+X+X+X+X+X+X+X+X}.# o X X . . ", "a.l.l.z.,XD.D.j.q.~ R R b ~ R ~ T Z s.U.s.,XW ! w.w.0.E Z C C N b b b b &.K.-.>XI 9 9 9 9 N Z R ! Q I C i 8 4 5 P *.%.4.4.4 : : : ; : ; : - ; : 5 7 N V V C x +.@...2.>.@ @ + @ + + + + + + + O O O O o o o o o 1 x _ ^.+X+X+X+X+X+X+X.X+X.X+X.X< X X X X . . ", "y.l.k.D.,XD.~ R R R R R T T R T T T s.T.h.rXw.0.E I C N C C N N b b b b #.K.G.).} N C I Q ! Q I C r 5 5 5 5 4 4 n =.*.Q M., : : : : ; ; 2 8 r Z V V B 7 : = : O.X...Y C.@ $ @ + + + + + + O O O O O o O - t ) C.8X+X+X8X+X+X+X+X+X+X+X+X+X+X+X+Xz X X X X . . ", "i.l.D.z.,XD.~ R R R R R T T R R ! 0.E.R.h.yXT C C C C C C N N N C b b n W K.G.(.d.{ { E Z B 9 8 8 5 8 5 5 5 5 5 n %.*.L X4 : : : 8 r C G G Z r 8 : = = = * w O.X...x 0X; @ @ @ @ + + + O + + O $ y / m.+X+X8X+X+X8X+X+X+X+X+X+X+X+X+X+X+X+X+X+X^.O X X . . . ", "a.l.l.A.,XD.R R R R R R ~ { e.e.e.,.s.U.s.aXR C C C C C N N N N b b b T $.T.T.A.A.Z N 9 9 8 8 8 8 5 5 5 5 5 5 4 8 %.%.K 9Xh 9 B I G I B 9 5 ; - = = = * = * * p X.X.f XXt * @ + + + + + $ y ^ u.+X8X8X8X+X+X8X+X8X+X+X+X+X+X+X+X+X+X+X+X+X^._ y & O X X X X . . ", "y.l.A.l.,XD.~ R ~ ! 0.e.j.e.,.~ T Z x.I.r.aXE C C C C C C N N C T ! 0.0.$.I.P.y.S.b 9 8 9 9 8 8 8 8 8 5 5 5 5 5 5 =.=.*.).] I I r 7 : ; ; = = ; - = = = = * = i X.....8.] @ @ + $ 6 H a..X8X8X8X8X+X8X+X8X8X8X+X+X8X8X+X+X+X+X+X+X}.v y - # O O o X X X . . . . ", "y.l.A.A.,XD.! e.j.j.e.! ~ T b ~ T Z $.U.x.yX{ C C C C C G R { q.q.{ T V P x.G.2.'.9 9 9 9 8 8 8 8 5 8 5 5 5 7 B Z -.K.L.V.<.5 4 : : ; ; ; ; = ; = = = = * * * q X....._ 6., F 7.XX8X8X8X8X8X8X8X8X8X8X8X8X+X8X+X+X+X+X+X+XXX .y < $ # # # # # O o o X X X ", "y.A.A.l.,Xj.j.e.{ ~ R R R T R R T Z r.U.x.rX{ C Z R W 0.q.,.~ T C b b b A K.G.,.4X9 9 8 9 9 8 8 8 8 9 N C R E ~ G ;.=.%.<.a.: : : : ; ; ; ; ; = ; = = = = * * : ..d P ` m.0X8X8X8X8X8X0X8X8X8X8X8X+X8X+X8X8X8X+X8XXX) j < - $ $ $ $ $ $ # # O O o ", "w.A.A.l.>Xl.~ R R R R R T T T T T T $.I.s.4Xi.{ w.w.0.R T C b N N N b b N K.K.#.yXh 9 9 9 8 9 h C I E E E C h 8 4 A *.&.Q S.: : : ; ; ; ; ; = ; ; = = * = : j ] 8.+XeX8X8X0X8X8X0X8X0X8X8X8X8X8X8X8X8X7X8XXX) l 3 > > : > > = = w * @ @ @ ", "y.A.A.A.>X/.~ R R R R T T T T T T T r.E.E.3XA.$.E T C C N N C N b N b b N =.K.#.rXV 9 N C I E ! ! I C 9 7 5 4 5 4 n *.&.P 2X4 : : : : ; ; ; ; ; = : t ) M.7X0XeXeXeX0XeX0X0X0X0X0X0X7X8X8X8X8X8X8XXX[ S 3 : 5 5 4 4 4 : ; ; > = * ", "t.z.A.A.>X/.R ~ R R R R R T R ~ 0.e.e.R.E.;Xh.C C C C C N C b N r N N 9 b -.=.-.].,.E { ! E C N 7 5 8 5 5 5 5 5 4 i %.%.K 9Xr : : : : ; ; ; e _ b.2X0XiX0XeXeX0X0XeX0X8X0X8X0X8X8X8X0X8X8X|.' S e 2 8 b 9 9 5 5 5 4 4 : 4 ", "t.A.A.A.>X/.~ R R R R ~ ,.0.j.e.0.! $.I.U.[.k.C C C C N C b C N N N N N C -.I.U.`.u.I C N 9 8 8 9 8 5 5 5 5 5 4 4 5 *.%.X.F.D : : : 8 / u. X0XiXeXeXeXeXeX0X0XeX0X0X0XeX8XeX0X0X0X|.| D e 6 n N N h i 9 9 0 5 8 5 ", "t.A.A.A.;X:XR ~ ~ 0.e.j.e.0.~ R T T W U.I.:XA.C C C C C b b C b C Z E ! ,.s.I.Y.N.u.9 8 9 8 8 8 8 5 8 5 5 4 5 4 4 4 &.%.%.y.` Y 4.'.eXeXeXeXiX0XeXeXeXeXeX0XeX0X0X0XeX0XeX].3.D e 7 C I Z Z A A N b n 9 9 ", "t.A.l.A.;XA.e.j.j.e.0.~ T T R T T T ~ I.U.z./.C C C C C C T R { 0.0.! E Z #.G.G.d.M.9 9 9 9 8 8 8 8 5 5 5 5 4 5 5 4 P ;.2.n.0XiXiXeXeXiXiXeX0XiXeXeXeX0X0X0X0XeX0X X3.H h 9 I W R R T Z Z C C N N ", "q.S.A.A.;Xz.0.,.~ R R R R T T T n T R I.U././.C C Z R E 0.q.q.~ R C N b 9 L K.G.w.(.9 9 8 8 8 8 8 8 5 5 5 4 8 G 5.B.2XtXtXtXiXeXiXeXeXeXeXeXeXiXeXeXeXeX0X Xt.T B b R $.! ! ~ ~ R R T T Z ", "q.A.A.z.;X:XW R R R R R T T T T T T R I.U.z.-X~ 0.w.q.,.~ Z C N b N N b b P K.G.2.'.9 9 9 8 9 8 8 5 8 V } B.4XiXiXiXiXiXiXiXeXiXeXiXeXiXiXeXeXeXeX X4./ B C ,.e.0.0.,.{ ! ! ~ W ", "t.A.A.A.>X-XW ~ R R R R T T T T T Z W E.R.(.3Xw.! R C C C N N N b N b b b Z K.G.$.9X9 8 9 8 7 C } b.1XpXpXiXiXpXiXiXiXiXiXiXeXeXiXeXeXeXeX X6.Y C C w.e.e.e.0.0.0.0.,.,. ", "5.z.A.A.;X-XR R R R R R R T R ~ $.w.e.R.E.h.3XZ C C C C N N N N N N b b n A K.G.#.iXN N ] M.].pXpXpXpXpXpXpXpXiXiXiXiXiXiXiXiXiXiX1Xu.` V G e.z.j.j.f.f.e.e.e.e. ", "5.A.A.A.-X;X~ R R R R ~ ! 0.j.e.w.,.! U.I.h.qXC C C C C C N N N N b b b b b x.=.$.M.S.tXaXiXpXpXpXpXpXpXpXtXpXtXiXiXtXiXiX1X6.] S G j. z.z.z.z.z.j.j.j. ", "q.A.A.l.;X;XR ~ ! 0.e.j.j.0.~ ~ T Z Z U.U.r.yXZ C C C C N N N N b N N N T a.A.4XpXpXaXaXpXaXpXpXpXpXpXpXpXpXiXpXiXOXu.` V I z.z.z. ", "5.A.A.A.-X`.e.j.j.e.0.~ R T T T T Z T Y.U.s.jXG C C C N N N N N E t.F.9XsXpXaXaXpXaXpXpXpXpXaXpXpXpXpXpXpX2Xu.[ D R ", "q.A.A.A.-X:X0.,.~ R R R T T T T Z T Z s.U.r.aXR C C C C T y.S.5XaXaXaXaXsXaXpXaXsXpXpXsXaXpXpXpXpX2Xu.] D I ", "<.A.A.A.-X,X~ R R R R R R T T T T Z T x.U.r.jXQ T q.D.1XaXaXaXjXsXaXsXpXaXaXpXaXpXaXaXpXaX1Xd.] G E ", "<.(.z.A.-X,XR ~ R R R T T T T T T T T x.s.r.S.`.aXsXdXaXjXjXaXaXaXaXsXaXaXaXaXaXaX4XM.' D E ", "q.A.A.A.-X3XR ~ R R R T T T R Z Z ,.j./.rXsXdXsXdXdXsXsXsXaXsXaXsXaXaXaXaX2Xd.] G E ", "<.A.l.A.-X3X~ R R R R R T { g.`.wXgXfXfXfXsXfXsXdXdXsXsXsXaXaXsXaX4XM.} D ", "4.D.A.A.:X3XR R R ! i.:XwXfXfXfXfXfXdXdXfXsXsXsXdXfXaXsXaX4XM.} G ", "q.A.z.l.:XwXp./.5XfXfXfXfXfXfXfXjXdXfXfXsXfXsXsXsX9XM.] D ", "<./.-X5XyXgXhXfXgXgXfXfXfXfXfXfXfXdXjXfXjX4Xg.} V ", "<.hXgXgXhXhXfXhXgXfXgXfXfXfXfXfXfX4XN.] V ", "<.hXgXgXgXgXfXhXgXfXfXfXfXqXN.] j ", "<.hXgXgXfXhXhXgXfXwXg.} h ", "<.gXhXgXfXqXN.] t ", ",.rXN.] h ", " ", " ", " ", " ", " ", " " }; schismtracker-20180209/include/charset.h000066400000000000000000000021661323741476300200670ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 __charset_h #define __charset_h int char_digraph(int k1, int k2); int char_unicode_to_cp437(unsigned int c); #endif schismtracker-20180209/include/clippy.h000066400000000000000000000034701323741476300177350ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 schismtracker-20180209/include/cmixer.h000066400000000000000000000041411323741476300177200ustar00rootroot00000000000000#ifndef MODPLUG_MIXER_H #define MODPLUG_MIXER_H #include "sndfile.h" // Stuff moved from sndfile.h #define MIXING_ATTENUATION 5 #define MIXING_CLIPMIN (-0x04000000) #define MIXING_CLIPMAX (0x03FFFFFF) #define VOLUMERAMPPRECISION 12 #define FILTERPRECISION 13 void init_mix_buffer(int *, unsigned int); void stereo_fill(int *, unsigned int, int*, int *); void end_channel_ofs(song_voice_t *, int *, unsigned int); void interleave_front_rear(int *, int *, unsigned int); void mono_from_stereo(int *, unsigned int); void stereo_mix_to_float(const int *, float *, float *, unsigned int); void float_to_stereo_mix(const float *, const float *, int *, unsigned int); void mono_mix_to_float(const int *, float *, unsigned int); void float_to_mono_mix(const float *, int *, unsigned int); unsigned int csf_create_stereo_mix(song_t *csf, int count); void setup_channel_filter(song_voice_t *pChn, int reset, int flt_modifier, int freq); //typedef unsigned int (*convert_clip_t)(void *, int *, unsigned int, int*, int*) __attribute__((cdecl)) unsigned int clip_32_to_8(void *, int *, unsigned int, int *, int *); unsigned int clip_32_to_16(void *, int *, unsigned int, int *, int *); unsigned int clip_32_to_24(void *, int *, unsigned int, int *, int *); unsigned int clip_32_to_32(void *, int *, unsigned int, int *, int *); void eq_mono(song_t *, int *, unsigned int); void eq_stereo(song_t *, int *, unsigned int); void initialize_eq(int, float); void set_eq_gains(const unsigned int *, unsigned int, const unsigned int *, int, int); // sndmix.c extern int g_dry_rofs_vol; extern int g_dry_lofs_vol; // mixer.c void ResampleMono8BitFirFilter(signed char *oldbuf, signed char *newbuf, unsigned long oldlen, unsigned long newlen); void ResampleMono16BitFirFilter(signed short *oldbuf, signed short *newbuf, unsigned long oldlen, unsigned long newlen); void ResampleStereo8BitFirFilter(signed char *oldbuf, signed char *newbuf, unsigned long oldlen, unsigned long newlen); void ResampleStereo16BitFirFilter(signed short *oldbuf, signed short *newbuf, unsigned long oldlen, unsigned long newlen); #endif schismtracker-20180209/include/config-parser.h000066400000000000000000000101601323741476300211660ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ int omit; /* don't include in output (delete this section) */ }; 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 */ int dirty; /* has this config been modified? */ }; 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. if the file has not been modified, this call is a no-op. */ 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); /* delete a key from the config file. * well, actually it doesn't delete it, it just comments it out. */ void cfg_delete_key(cfg_file_t *cfg, const char *section_name, const char *key_name); /* 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 */ schismtracker-20180209/include/disko.h000066400000000000000000000105171323741476300175460ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 __disko_h #define __disko_h #include typedef struct disko disko_t; struct disko { // Functions whose implementation depends on the backend in use // Use disko_write et al. instead of these. void (*_write)(disko_t *ds, const void *buf, size_t len); void (*_putc)(disko_t *ds, int c); void (*_seek)(disko_t *ds, long offset, int whence); long (*_tell)(disko_t *ds); // Temporary filename that's being written to char tempname[PATH_MAX]; // Name to change it to on close (if successful) char filename[PATH_MAX]; // these could be unionized // file pointer (only exists for disk files) FILE *file; // data for memory buffers (no filename/handle) uint8_t *data; // First errno value recorded after something went wrong. int error; /* untouched by diskwriter; driver may use for anything */ void *userdata; // for memory buffers size_t pos, length, allocated; }; enum { DW_OK = 1, DW_ERROR = 0, DW_NOT_RUNNING = -1, }; enum { DW_SYNC_DONE = 0, DW_SYNC_ERROR = -1, DW_SYNC_MORE = 1, }; /* fopen/fclose-ish writeout/finish wrapper that allocates a structure */ disko_t *disko_open(const char *filename); /* Close the file. If there was no error writing the file, it is renamed to the name specified in disko_open; otherwise, the original file is left intact and the temporary file is deleted. Returns DW_OK on success, DW_ERROR (and sets errno) if there was a file error. 'backup' parameter: <1 don't backup =1 backup to file~ >1 keep numbered backups to file.n~ (the semantics of this might change later to allow finer control) */ int disko_close(disko_t *f, int backup); /* alloc/free a memory buffer if free_buffer is 0, the internal buffer is left alone when deallocating, so that it can continue to be used later */ disko_t *disko_memopen(void); int disko_memclose(disko_t *f, int free_buffer); /* copy a pattern into a sample */ int disko_writeout_sample(int smpnum, int pattern, int bind); /* copy a pattern into multiple samples (split by channel, and writing into free slots starting at smpnum) */ int disko_multiwrite_samples(int firstsmp, int pattern); /* Wrapper for the above that writes to the current sample, and with a confirmation dialog if the sample already has data */ void song_pattern_to_sample(int pattern, int split, int bind); /* export the song to a file */ struct save_format; int disko_export_song(const char *filename, const struct save_format *format); /* call periodically if (status.flags & DISKWRITER_ACTIVE) to write more stuff. return: DW_SYNC_*, self explanatory */ int disko_sync(void); /* For use by the diskwriter drivers: */ /* Write data to the file, as in fwrite() */ void disko_write(disko_t *ds, const void *buf, size_t len); /* Write one character (unsigned char, cast to int) */ void disko_putc(disko_t *ds, int c); /* Change file position. This CAN be used to seek past the end, but be cognizant that random data might exist in the "gap". */ void disko_seek(disko_t *ds, long pos, int whence); /* Get the position, as set by seek */ long disko_tell(disko_t *ds); /* Call this to signal a nonrecoverable error condition. */ void disko_seterror(disko_t *ds, int err); /* ------------------------------------------------------------------------- */ /* this call is used by audio/loadsave to send midi data */ int _disko_writemidi(const void *data, unsigned int len, unsigned int delay); #endif schismtracker-20180209/include/dmoz.h000066400000000000000000000217241323741476300174100ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #include "sndfile.h" /* for song_sample_t */ /* 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 */ /* (flags & 0xF0) are reserved for future use */ /* this has to match TYPE_BROWSABLE_MASK for directories */ TYPE_EXT_DATA_MASK = 0xFFF01, /* 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) */ TYPE_INTERNAL_FLAGS = 0xF00000, TYPE_HIDDEN = 0x100000, }; /* 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,filter}_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_t *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; unsigned int smp_defvol; unsigned int smp_gblvol; unsigned int smp_vibrato_speed; unsigned int smp_vibrato_depth; unsigned int smp_vibrato_rate; }; 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; int selected; /* communication with cache */ } dmoz_filelist_t; typedef struct dmoz_dirlist { int num_dirs, alloc_size; dmoz_dir_t **dirs; int selected; /* communication with cache */ } dmoz_dirlist_t; /* For any of these, pass NULL for dirs to handle directories and files in the same list. for load_library, provide one of the dmoz_read_whatever_library functions, or NULL. */ int dmoz_read(const char *path, dmoz_filelist_t *files, dmoz_dirlist_t *dirs, int (*load_library)(const char *,dmoz_filelist_t *,dmoz_dirlist_t *)); void dmoz_free(dmoz_filelist_t *files, dmoz_dirlist_t *dirs); void dmoz_sort(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist); /* 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_filter_ext_data(dmoz_file_t *file); /* same as dmoz_filter_ext_data, but always returns 1 (for async title reading) */ 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)); /* butt */ int song_preload_sample(dmoz_file_t *f); /* 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); /* Return nonzero if the path is an absolute path (e.g. /bin, c:\progra~1, sd:/apps, etc.) */ int dmoz_path_is_absolute(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. Additionally, if 'b' is an absolute path (as determined by dmoz_path_is_absolute), ignore 'a' and return a copy of 'b'. */ 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); /* these update the file selection cache for the various pages */ void dmoz_cache_update_names(const char *path, const char *filen, const char *dirn); void dmoz_cache_update(const char *path, dmoz_filelist_t *fl, dmoz_dirlist_t *dl); void dmoz_cache_lookup(const char *path, dmoz_filelist_t *fl, dmoz_dirlist_t *dl); #endif /* ! DMOZ_H */ schismtracker-20180209/include/draw-char.h000066400000000000000000000065661323741476300203160ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 DRAW_CHAR_H #define DRAW_CHAR_H #include /* --------------------------------------------------------------------- */ void draw_char(unsigned char c, int x, int y, uint32_t fg, uint32_t bg); void draw_char_bios(unsigned char c, int x, int y, uint32_t fg, uint32_t bg); /* return value is the number of characters drawn */ int draw_text(const char * text, int x, int y, uint32_t fg, uint32_t bg); int draw_text_bios(const char * text, int x, int y, uint32_t fg, uint32_t bg); /* return value is the length of text drawn * (so len - return is the number of spaces) */ int draw_text_len(const char * text, int len, int x, int y, uint32_t fg, uint32_t bg); int draw_text_bios_len(const char * text, int len, int x, int y, uint32_t fg, uint32_t bg); void draw_fill_chars(int xs, int ys, int xe, int ye, uint32_t color); void draw_half_width_chars(uint8_t c1, uint8_t c2, int x, int y, uint32_t fg1, uint32_t bg1, uint32_t fg2, uint32_t bg2); /* --------------------------------------------------------------------- */ /* boxes */ /* don't use these directly */ void draw_thin_inner_box(int xs, int ys, int xe, int ye, uint32_t tl, uint32_t br); void draw_thick_inner_box(int xs, int ys, int xe, int ye, uint32_t tl, uint32_t br); void draw_thin_outer_box(int xs, int ys, int xe, int ye, uint32_t c); void draw_thick_outer_box(int xs, int ys, int xe, int ye, uint32_t 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 void draw_box(int xs, int ys, int xe, int ye, int flags); /* .... */ void toggle_display_fullscreen(void); /* FIXME why on earth is this in this header? */ #endif /* ! DRAW_CHAR_H */ schismtracker-20180209/include/event.h000066400000000000000000000034041323741476300175530ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #define SCHISM_EVENT_UPDATE_IPMIDI SDL_USEREVENT+0 #define SCHISM_EVENT_MIDI SDL_USEREVENT+1 #define SCHISM_EVENT_PLAYBACK SDL_USEREVENT+2 #define SCHISM_EVENT_NATIVE SDL_USEREVENT+3 #define SCHISM_EVENT_PASTE SDL_USEREVENT+4 #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_MIDI_SYSTEM 8 #define SCHISM_EVENT_NATIVE_OPEN 1 #define SCHISM_EVENT_NATIVE_SCRIPT 16 #endif schismtracker-20180209/include/fmopl2.h000066400000000000000000000075231323741476300176370ustar00rootroot00000000000000#ifndef __FMOPL_H_ #define __FMOPL_H_ #ifdef __cplusplus extern "C" { #endif #define logerror(...) /**/ /* --- select emulation chips --- */ #define BUILD_YM3812 (HAS_YM3812) #define BUILD_YM3526 (HAS_YM3526) #define BUILD_Y8950 (HAS_Y8950) /* select output bits size of output : 8 or 16 */ #define OPL_SAMPLE_BITS 16 /* compiler dependence */ #ifndef __OSDCOMM_H__ #define __OSDCOMM_H__ typedef unsigned char UINT8; /* unsigned 8bit */ typedef unsigned short UINT16; /* unsigned 16bit */ typedef unsigned int UINT32; /* unsigned 32bit */ typedef signed char INT8; /* signed 8bit */ typedef signed short INT16; /* signed 16bit */ typedef signed int INT32; /* signed 32bit */ #endif /* __OSDCOMM_H__ */ #ifndef INLINE #define INLINE static __inline__ #endif #if (OPL_SAMPLE_BITS==16) typedef INT16 OPLSAMPLE; #endif #if (OPL_SAMPLE_BITS==8) typedef INT8 OPLSAMPLE; #endif typedef void (*OPL_TIMERHANDLER)(void *param,int timer,double period); typedef void (*OPL_IRQHANDLER)(void *param,int irq); typedef void (*OPL_UPDATEHANDLER)(void *param,int min_interval_us); typedef void (*OPL_PORTHANDLER_W)(void *param,unsigned char data); typedef unsigned char (*OPL_PORTHANDLER_R)(void *param); #if BUILD_YM3812 void *ym3812_init(UINT32 clock, UINT32 rate); void ym3812_shutdown(void *chip); void ym3812_reset_chip(void *chip); int ym3812_write(void *chip, int a, int v); unsigned char ym3812_read(void *chip, int a); int ym3812_timer_over(void *chip, int c); void ym3812_update_one(void *chip, OPLSAMPLE *buffer, int length); void ym3812_set_timer_handler(void *chip, OPL_TIMERHANDLER TimerHandler, void *param); void ym3812_set_irq_handler(void *chip, OPL_IRQHANDLER IRQHandler, void *param); void ym3812_set_update_handler(void *chip, OPL_UPDATEHANDLER UpdateHandler, void *param); #endif /* BUILD_YM3812 */ #if BUILD_YM3526 /* ** Initialize YM3526 emulator(s). ** ** 'num' is the number of virtual YM3526's to allocate ** 'clock' is the chip clock in Hz ** 'rate' is sampling rate */ void *ym3526_init(UINT32 clock, UINT32 rate); /* shutdown the YM3526 emulators*/ void ym3526_shutdown(void *chip); void ym3526_reset_chip(void *chip); int ym3526_write(void *chip, int a, int v); unsigned char ym3526_read(void *chip, int a); int ym3526_timer_over(void *chip, int c); /* ** Generate samples for one of the YM3526's ** ** 'which' is the virtual YM3526 number ** '*buffer' is the output buffer pointer ** 'length' is the number of samples that should be generated */ void ym3526_update_one(void *chip, OPLSAMPLE *buffer, int length); void ym3526_set_timer_handler(void *chip, OPL_TIMERHANDLER TimerHandler, void *param); void ym3526_set_irq_handler(void *chip, OPL_IRQHANDLER IRQHandler, void *param); void ym3526_set_update_handler(void *chip, OPL_UPDATEHANDLER UpdateHandler, void *param); #endif /* BUILD_YM3526 */ #if BUILD_Y8950 /* Y8950 port handlers */ void y8950_set_port_handler(void *chip, OPL_PORTHANDLER_W PortHandler_w, OPL_PORTHANDLER_R PortHandler_r, void *param); void y8950_set_keyboard_handler(void *chip, OPL_PORTHANDLER_W KeyboardHandler_w, OPL_PORTHANDLER_R KeyboardHandler_r, void *param); void y8950_set_delta_t_memory(void *chip, void * deltat_mem_ptr, int deltat_mem_size ); void * y8950_init(UINT32 clock, UINT32 rate); void y8950_shutdown(void *chip); void y8950_reset_chip(void *chip); int y8950_write(void *chip, int a, int v); unsigned char y8950_read (void *chip, int a); int y8950_timer_over(void *chip, int c); void y8950_update_one(void *chip, OPLSAMPLE *buffer, int length); void y8950_set_timer_handler(void *chip, OPL_TIMERHANDLER TimerHandler, void *param); void y8950_set_irq_handler(void *chip, OPL_IRQHANDLER IRQHandler, void *param); void y8950_set_update_handler(void *chip, OPL_UPDATEHANDLER UpdateHandler, void *param); #endif /* BUILD_Y8950 */ #ifdef __cplusplus } #endif #endif /* __FMOPL_H__ */ schismtracker-20180209/include/fmopl3.h000066400000000000000000000027461323741476300176420ustar00rootroot00000000000000// license:GPL-2.0+ // copyright-holders:Jarek Burczynski #pragma once #ifndef __YMF262_H__ #define __YMF262_H__ /* select number of output bits: 8 or 16 */ #define OPL3_SAMPLE_BITS 16 /* compiler dependence */ #ifndef __OSDCOMM_H__ #define __OSDCOMM_H__ typedef unsigned char UINT8; /* unsigned 8bit */ typedef unsigned short UINT16; /* unsigned 16bit */ typedef unsigned int UINT32; /* unsigned 32bit */ typedef signed char INT8; /* signed 8bit */ typedef signed short INT16; /* signed 16bit */ typedef signed int INT32; /* signed 32bit */ #endif #if (OPL3_SAMPLE_BITS==16) typedef INT16 OPL3SAMPLE; #endif #if (OPL3_SAMPLE_BITS==8) typedef INT8 OPL3SAMPLE; #endif typedef void (*OPL3_TIMERHANDLER)(void *param,int timer,double period); typedef void (*OPL3_IRQHANDLER)(void *param,int irq); typedef void (*OPL3_UPDATEHANDLER)(void *param,int min_interval_us); void *ymf262_init(int clock, int rate); void ymf262_shutdown(void *chip); void ymf262_reset_chip(void *chip); int ymf262_write(void *chip, int a, int v); unsigned char ymf262_read(void *chip, int a); int ymf262_timer_over(void *chip, int c); void ymf262_update_one(void *chip, OPL3SAMPLE **buffers, int length); void ymf262_set_timer_handler(void *chip, OPL3_TIMERHANDLER TimerHandler, void *param); void ymf262_set_irq_handler(void *chip, OPL3_IRQHANDLER IRQHandler, void *param); void ymf262_set_update_handler(void *chip, OPL3_UPDATEHANDLER UpdateHandler, void *param); #endif /* __YMF262_H__ */ schismtracker-20180209/include/fmt-types.h000066400000000000000000000142351323741476300203660ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 file is used for file format tables, load ordering, and declarations. It is intended to be included after defining macros for handling the various file types listed here, and as such, it is not #ifdef guarded. 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. ;) */ #ifndef READ_INFO # define READ_INFO(x) #endif #ifndef LOAD_SONG # define LOAD_SONG(x) #endif #ifndef SAVE_SONG # define SAVE_SONG(x) #endif #ifndef LOAD_SAMPLE # define LOAD_SAMPLE(x) #endif #ifndef SAVE_SAMPLE # define SAVE_SAMPLE(x) #endif #ifndef LOAD_INSTRUMENT # define LOAD_INSTRUMENT(x) #endif #ifndef SAVE_INSTRUMENT /* not actually used - instrument saving is currently hardcoded to write .iti files */ # define SAVE_INSTRUMENT(x) #endif #ifndef EXPORT # define EXPORT(x) #endif /* --------------------------------------------------------------------------------------------------------- */ /* 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) LOAD_SONG(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) LOAD_SONG(mod) /* S3M needs to be before a lot of stuff. */ READ_INFO(s3m) LOAD_SONG(s3m) SAVE_SONG(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) LOAD_SONG(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) LOAD_SONG(xm) READ_INFO(it) LOAD_SONG(it) SAVE_SONG(it) READ_INFO(mt2) READ_INFO(mtm) LOAD_SONG(mtm) READ_INFO(ntk) READ_INFO(mdl) LOAD_SONG(mdl) READ_INFO(med) READ_INFO(okt) LOAD_SONG(okt) READ_INFO(mid) LOAD_SONG(mid) READ_INFO(mus) LOAD_SONG(mus) READ_INFO(mf) /* Sample formats with magic at start of file */ READ_INFO(its) LOAD_SAMPLE(its) SAVE_SAMPLE(its) READ_INFO(au) LOAD_SAMPLE(au) SAVE_SAMPLE(au) READ_INFO(aiff) LOAD_SAMPLE(aiff) SAVE_SAMPLE(aiff) EXPORT(aiff) READ_INFO(wav) LOAD_SAMPLE(wav) SAVE_SAMPLE(wav) EXPORT(wav) READ_INFO(iti) LOAD_INSTRUMENT(iti) READ_INFO(xi) LOAD_INSTRUMENT(xi) READ_INFO(pat) LOAD_INSTRUMENT(pat) READ_INFO(ult) LOAD_SONG(ult) READ_INFO(liq) READ_INFO(ams) READ_INFO(f2r) READ_INFO(s3i) LOAD_SAMPLE(s3i) SAVE_SAMPLE(s3i) /* FIXME should this be moved? S3I has magic at 0x4C... */ /* IMF and SFX (as well as STX) all have the magic values at 0x3C-0x3F, which is positioned in IT's "reserved" field, Not sure about this positioning, but these are kind of rare formats anyway. */ READ_INFO(imf) LOAD_SONG(imf) READ_INFO(sfx) LOAD_SONG(sfx) /* 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) LOAD_SONG(stm) /* 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 /* not really a type, so no info reader for these */ LOAD_SAMPLE(raw) SAVE_SAMPLE(raw) /* --------------------------------------------------------------------------------------------------------- */ /* Clear these out so subsequent includes don't make an ugly mess */ #undef READ_INFO #undef LOAD_SONG #undef SAVE_SONG #undef LOAD_SAMPLE #undef SAVE_SAMPLE #undef LOAD_INSTRUMENT #undef SAVE_INSTRUMENT #undef EXPORT schismtracker-20180209/include/fmt.h000066400000000000000000000167241323741476300172310ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #include "dmoz.h" #include "slurp.h" #include "util.h" #include "disko.h" #include "sndfile.h" /* --------------------------------------------------------------------------------------------------------- */ /* module loaders */ /* flags to skip loading some data (mainly for scraping titles) this is only a suggestion in order to speed loading; don't be surprised if the loader ignores these */ #define LOAD_NOSAMPLES 1 #define LOAD_NOPATTERNS 2 /* return codes for module loaders */ enum { LOAD_SUCCESS, /* all's well */ LOAD_UNSUPPORTED, /* wrong file type for the loader */ LOAD_FILE_ERROR, /* couldn't read the file; check errno */ LOAD_FORMAT_ERROR, /* it appears to be the correct type, but there's something wrong */ }; /* return codes for modules savers */ enum { SAVE_SUCCESS, /* all's well */ SAVE_FILE_ERROR, /* couldn't write the file; check errno */ SAVE_INTERNAL_ERROR, /* something unrelated to disk i/o */ }; /* --------------------------------------------------------------------------------------------------------- */ #define PROTO_READ_INFO (dmoz_file_t *file, const uint8_t *data, size_t length) #define PROTO_LOAD_SONG (song_t *song, slurp_t *fp, unsigned int lflags) #define PROTO_SAVE_SONG (disko_t *fp, song_t *song) #define PROTO_LOAD_SAMPLE (const uint8_t *data, size_t length, song_sample_t *smp) #define PROTO_SAVE_SAMPLE (disko_t *fp, song_sample_t *smp) #define PROTO_LOAD_INSTRUMENT (const uint8_t *data, size_t length, int slot) #define PROTO_EXPORT_HEAD (disko_t *fp, int bits, int channels, int rate) #define PROTO_EXPORT_SILENCE (disko_t *fp, long bytes) #define PROTO_EXPORT_BODY (disko_t *fp, const uint8_t *data, size_t length) #define PROTO_EXPORT_TAIL (disko_t *fp) typedef int (*fmt_read_info_func) PROTO_READ_INFO; typedef int (*fmt_load_song_func) PROTO_LOAD_SONG; typedef int (*fmt_save_song_func) PROTO_SAVE_SONG; typedef int (*fmt_load_sample_func) PROTO_LOAD_SAMPLE; typedef int (*fmt_save_sample_func) PROTO_SAVE_SAMPLE; typedef int (*fmt_load_instrument_func) PROTO_LOAD_INSTRUMENT; typedef int (*fmt_export_head_func) PROTO_EXPORT_HEAD; typedef int (*fmt_export_silence_func) PROTO_EXPORT_SILENCE; typedef int (*fmt_export_body_func) PROTO_EXPORT_BODY; typedef int (*fmt_export_tail_func) PROTO_EXPORT_TAIL; #define READ_INFO(t) int fmt_##t##_read_info PROTO_READ_INFO; #define LOAD_SONG(t) int fmt_##t##_load_song PROTO_LOAD_SONG; #define SAVE_SONG(t) int fmt_##t##_save_song PROTO_SAVE_SONG; #define LOAD_SAMPLE(t) int fmt_##t##_load_sample PROTO_LOAD_SAMPLE; #define SAVE_SAMPLE(t) int fmt_##t##_save_sample PROTO_SAVE_SAMPLE; #define LOAD_INSTRUMENT(t) int fmt_##t##_load_instrument PROTO_LOAD_INSTRUMENT; #define EXPORT(t) int fmt_##t##_export_head PROTO_EXPORT_HEAD; \ int fmt_##t##_export_silence PROTO_EXPORT_SILENCE; \ int fmt_##t##_export_body PROTO_EXPORT_BODY; \ int fmt_##t##_export_tail PROTO_EXPORT_TAIL; #include "fmt-types.h" /* --------------------------------------------------------------------------------------------------------- */ struct save_format { const char *label; // label for the button on the save page const char *name; // long name of format const char *ext; // no dot union { fmt_save_song_func save_song; fmt_save_sample_func save_sample; struct { fmt_export_head_func head; fmt_export_silence_func silence; fmt_export_body_func body; fmt_export_tail_func tail; int multi; } export; } f; }; extern const struct save_format song_save_formats[]; extern const struct save_format song_export_formats[]; extern const struct save_format sample_save_formats[]; /* --------------------------------------------------------------------------------------------------------- */ struct instrumentloader { song_instrument_t *inst; int sample_map[MAX_SAMPLES]; int basex, slot, expect_samples; }; song_instrument_t *instrument_loader_init(struct instrumentloader *ii, int slot); int instrument_loader_abort(struct instrumentloader *ii); int instrument_loader_sample(struct instrumentloader *ii, int slot); /* --------------------------------------------------------------------------------------------------------- */ uint32_t it_decompress8(void *dest, uint32_t len, const void *file, uint32_t filelen, int it215, int channels); uint32_t it_decompress16(void *dest, uint32_t len, const void *file, uint32_t filelen, int it215, int channels); uint16_t mdl_read_bits(uint32_t *bitbuf, uint32_t *bitnum, uint8_t **ibuf, int8_t n); /* --------------------------------------------------------------------------------------------------------- */ /* shared by the .it, .its, and .iti saving functions */ void save_its_header(disko_t *fp, song_sample_t *smp); int load_its_sample(const uint8_t *header, const uint8_t *data, size_t length, song_sample_t *smp); /* --------------------------------------------------------------------------------------------------------- */ // other misc functions... /* effect_weight[FX_something] => how "important" the effect is. */ extern const uint8_t effect_weight[]; /* Shuffle the effect and volume-effect values around. Note: this does NOT convert between volume and 'normal' effects, it only exchanges them. (This function is most useful in conjunction with convert_voleffect in order to try to cram ten pounds of crap into a five pound container) */ void swap_effects(song_note_t *note); /* Convert volume column data from FX_* to VOLFX_*, if possible. Return: 1 = it was properly converted, 0 = couldn't do so without loss of information. */ int convert_voleffect(uint8_t *effect, uint8_t *param, int force); #define convert_voleffect_of(note,force) convert_voleffect(&((note)->voleffect), &((note)->volparam), (force)) // load a .mod-style 4-byte packed note void mod_import_note(const uint8_t p[4], song_note_t *note); // Read a message with fixed-size line lengths void read_lined_message(char *msg, slurp_t *fp, int len, int linelen); // get L-R-R-L panning value from a (zero-based!) channel number #define PROTRACKER_PANNING(n) (((((n) + 1) >> 1) & 1) * 256) // convert .mod finetune byte value to c5speed #define MOD_FINETUNE(b) (finetune_table[((b) & 0xf) ^ 8]) /* --------------------------------------------------------------------------------------------------------- */ #endif /* ! FMT_H */ schismtracker-20180209/include/headers.h000066400000000000000000000146601323741476300200530ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 __headers_h #define __headers_h /* This is probably overkill, but it's consistent this way. */ #ifdef HAVE_CONFIG_H # include #endif /* Some stuff was conditionally included only for files that need it, but really it's not like defining bswapLE32 or including sys/time.h adversely affects compilation time. This isn't some xboxhueg project that takes hours to build, these are little silly overoptimizations, and it's just troublesome to try to work out what order headers are supposed to be processed so that all the other files pick up the bits of headers.h that they need (song_t, I'm looking at you) Eventually I'll do some housekeeping with the headers and get rid of all these silly NEED_*'s, but this will do for now. */ #define NEED_BYTESWAP #define NEED_TIME #define NEED_DIRENT #include #ifdef HAVE_STDLIB_H #include #endif #include #ifdef HAVE_SYS_PARAM_H #include #endif #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 #ifndef HAVE_STRCASESTR # define strcasestr strstr // derp #endif #if HAVE_UNISTD_H # include # include #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 /* dumb workaround for dumb devkitppc bug */ #ifdef GEKKO # undef NAME_MAX # undef PATH_MAX #endif #ifdef HAVE_LIMITS_H #include #endif #ifndef NAME_MAX # ifdef MAXPATHLEN # define NAME_MAX MAXPATHLEN /* BSD name */ # else # ifdef FILENAME_MAX # define NAME_MAX FILENAME_MAX # else # define NAME_MAX 256 # endif # endif #endif #ifdef NEED_TIME # if TIME_WITH_SYS_TIME # include # include # else # if HAVE_SYS_TIME_H # include # else # include # endif # endif # ifndef timersub // from FreeBSD # define timersub(tvp, uvp, vvp) \ do { \ (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ if ((vvp)->tv_usec < 0) { \ (vvp)->tv_sec--; \ (vvp)->tv_usec += 1000000; \ } \ } while (0) # endif #endif #ifdef REALLY_BIG_ENDIAN #ifndef WORDS_BIGENDIAN #define WORDS_BIGENDIAN 1 #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 */ #ifndef HAVE_ASPRINTF int asprintf(char **strp, const char *fmt, ...); #endif #ifndef HAVE_VASPRINTF int vasprintf(char **strp, const char *fmt, va_list ap); #endif #ifdef NEED_TIME # ifndef HAVE_STRPTIME char *strptime(const char *buf, const char *fmt, struct tm *tm); # endif # ifdef WIN32 struct tm *localtime_r(const time_t *timep, struct tm *result); # endif #endif #ifndef HAVE_MKSTEMP int mkstemp(char *template); #endif #ifdef __APPLE_CC__ #define MACOSX 1 #endif /* Various other stuff */ #ifdef WIN32 # define mkdir(path,mode) mkdir(path) # define setenv(a,b,c) /* stupid windows */ # define fsync _commit #endif #define INT_SHAPED_PTR(v) ((intptr_t)(((void*)(v)))) #define PTR_SHAPED_INT(i) ((void*)i) #endif schismtracker-20180209/include/it.h000066400000000000000000000345511323741476300170550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #include #include #include #include /* roundabout way to get time_t */ #include "util.h" #include "video.h" #include "log.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 eventual TODO: split this into two sections or something so we don't run out of bits to toggle... and while we're at it, namespace them with CFG_ (for the configuration stuff -- bits that are accessible through the interface in some way) and uh, something else for the internal status flags like IS_VISIBLE or whatever */ enum { /* if this flag is set, the screen will be redrawn */ NEED_UPDATE = (1 << 0), /* is the current palette "backwards"? (used to make the borders look right) */ INVERTED_PALETTE = (1 << 1), DIR_MODULES_CHANGED = (1 << 2), DIR_SAMPLES_CHANGED = (1 << 3), DIR_INSTRUMENTS_CHANGED = (1 << 4), /* these refer to the window's state. * (they're rather useless on the console ;) */ IS_FOCUSED = (1 << 5), IS_VISIBLE = (1 << 6), WM_AVAILABLE = (1 << 7), /* if this is set, some stuff behaves differently * (grep the source files for what stuff ;) */ CLASSIC_MODE = (1 << 8), /* make a backup file (song.it~) when saving a module? */ MAKE_BACKUPS = (1 << 9), NUMBERED_BACKUPS = (1 << 10), /* song.it.3~ */ LAZY_REDRAW = (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 disko 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 */ ACCIDENTALS_AS_FLATS = (1 << 19), /* fontedit */ STARTUP_FONTEDIT = (1 << 20), /* key hacks -- should go away when keyboard redefinition is possible */ META_IS_CTRL = (1 << 21), ALTGR_IS_ALT = (1 << 22), /* holding shift (used on pattern editor for weird template thing) */ SHIFT_KEY_DOWN = (1 << 23), /* Devi Ever's hack */ CRAYOLA_MODE = (1 << 25), /* holding caps */ CAPS_PRESSED = (1 << 26), NO_NETWORK = (1 << 27), NO_MOUSE = (1 << 28), /* Play MIDI events using the same semantics as tracker samples */ MIDI_LIKE_TRACKER = (1 << 29), /* if true, don't stop playing on load, and start playing new song afterward (but only if the last song was already playing before loading) */ PLAY_AFTER_LOAD = (1 << 30), }; /* 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_MONOSCOPE, VIS_FFT, 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 * */ /* clock is driven from the main/event thread */ time_t now; struct tm tmnow; int fix_numlock_setting; }; /* numlock hackery */ enum { NUMLOCK_ALWAYS_OFF = 0, NUMLOCK_ALWAYS_ON = 1, NUMLOCK_HONOR = -1, /* don't fix it */ NUMLOCK_GUESS = -2, /* don't fix it... except on non-ibook macs */ }; /* mouse visibility - these are passed to video_mousecursor() first three are stored as the physical mouse state */ enum { MOUSE_DISABLED, MOUSE_EMULATED, MOUSE_SYSTEM, MOUSE_CYCLE_STATE, MOUSE_RESET_STATE, }; #define MOUSE_MAX_STATE MOUSE_CYCLE_STATE struct it_palette { char name[21]; uint8_t 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, }; /* --------------------------------------------------------------------- */ /* global crap */ extern struct tracker_status status; extern uint8_t *font_data; /* ... which is 2048 bytes */ extern struct it_palette palettes[]; extern uint8_t 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 */ /* 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 int show_default_volumes; /* pattern-view.c */ /* --------------------------------------------------------------------- */ /* settings (config.c) */ extern char cfg_video_driver[]; /* TODO: consolidate these into cfg_video_flags */ extern int cfg_video_fullscreen; extern int cfg_video_mousecursor; extern int cfg_video_gl_bilinear; extern int cfg_video_width, cfg_video_height; 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; extern char cfg_module_pattern[]; extern char cfg_export_pattern[]; 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); void cfg_load_disko(cfg_file_t *cfg); void cfg_save_disko(cfg_file_t *cfg); void cfg_load_dmoz(cfg_file_t *cfg); void cfg_save_dmoz(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 unsigned char unicode_to_ascii(uint16_t unicode) { return unicode & 0xff; // return ((unicode & 0xff80) ? 0 : (unicode & 0x7f)); } /* --------------------------------------------------------------------- */ /* drawing functions */ /* character drawing (in a separate header so they're easier to find) */ #include "draw-char.h" struct song_sample; void draw_sample_data(struct vgamem_overlay *r, struct song_sample *sample); /* 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 inputchans, unsigned int outputchans); void draw_sample_data_rect_8(struct vgamem_overlay *r, signed char *data, int length, unsigned int inputchans, unsigned int outputchans); /* these are in audio_playback.cc */ extern signed short *audio_buffer; extern unsigned int audio_buffer_samples; 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); 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, int kponly); 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); int get_ptm_effect_number(char effect); void kbd_init(void); void kbd_sharp_flat_toggle(int e); int kbd_get_effect_number(struct key_event *k); int kbd_char_to_hex(struct key_event *k); int kbd_char_to_99(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); int kbd_get_alnum(struct key_event *k); /* use 0 for delay to (re)set the default rate. */ void set_key_repeat(int delay, int rate); /* --------------------------------------------------------------------- */ /* 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); void sample_synchronize_to_instrument(void); int sample_is_used_by_instrument(int samp); /* if necessary, prompt to create a new instrument. if newpage >= 0, the page is changed upon completion of the dialog, or immediately if no dialog was shown. return value is 1 if the dialog was shown, 0 if not. */ int sample_host_dialog(int newpage); /* instrument... sample... whatever */ int song_is_instrument_mode(void); int song_first_unused_instrument(void); int song_get_current_instrument(void); void set_previous_instrument(void); void set_next_instrument(void); int get_current_channel(void); void set_current_channel(int channel); 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 handlers: it starts at the marked position if there * is one, or the current position if not. */ void play_song_from_mark_orderpan(void); 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); 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); void memused_get_pattern_saved(unsigned int *a, unsigned int *b); /* wtf */ /* --------------------------------------------------------------------- */ #endif /* ! IT_H */ schismtracker-20180209/include/it_defs.h000066400000000000000000000042151323741476300200500ustar00rootroot00000000000000#ifndef _ITDEFS_H_ #define _ITDEFS_H_ #pragma pack(push, 1) struct it_file { uint32_t id; // 0x4D504D49 int8_t songname[26]; uint8_t hilight_minor; uint8_t hilight_major; uint16_t ordnum; uint16_t insnum; uint16_t smpnum; uint16_t patnum; uint16_t cwtv; uint16_t cmwt; uint16_t flags; uint16_t special; uint8_t globalvol; uint8_t mv; uint8_t speed; uint8_t tempo; uint8_t sep; uint8_t pwd; uint16_t msglength; uint32_t msgoffset; uint32_t reserved2; uint8_t chnpan[64]; uint8_t chnvol[64]; }; struct it_envelope { uint8_t flags; uint8_t num; uint8_t lpb; uint8_t lpe; uint8_t slb; uint8_t sle; uint8_t data[25*3]; uint8_t reserved; }; // Old Impulse Instrument Format (cmwt < 0x200) struct it_instrument_old { uint32_t id; // IMPI = 0x49504D49 int8_t filename[12]; // DOS file name uint8_t zero; uint8_t flags; uint8_t vls; uint8_t vle; uint8_t sls; uint8_t sle; uint16_t reserved1; uint16_t fadeout; uint8_t nna; uint8_t dnc; uint16_t trkvers; uint8_t nos; uint8_t reserved2; int8_t name[26]; uint16_t reserved3[3]; uint8_t keyboard[240]; uint8_t volenv[200]; uint8_t nodes[50]; }; // Impulse Instrument Format struct it_instrument { uint32_t id; int8_t filename[12]; uint8_t zero; uint8_t nna; uint8_t dct; uint8_t dca; uint16_t fadeout; signed char pps; uint8_t ppc; uint8_t gbv; uint8_t dfp; uint8_t rv; uint8_t rp; uint16_t trkvers; uint8_t nos; uint8_t reserved1; int8_t name[26]; uint8_t ifc; uint8_t ifr; uint8_t mch; uint8_t mpr; uint16_t mbank; uint8_t keyboard[240]; struct it_envelope volenv; struct it_envelope panenv; struct it_envelope pitchenv; uint8_t dummy[4]; // was 7, but IT v2.17 saves 554 bytes }; // IT Sample Format struct it_sample { uint32_t id; // 0x53504D49 int8_t filename[12]; uint8_t zero; uint8_t gvl; uint8_t flags; uint8_t vol; int8_t name[26]; uint8_t cvt; uint8_t dfp; uint32_t length; uint32_t loopbegin; uint32_t loopend; uint32_t C5Speed; uint32_t susloopbegin; uint32_t susloopend; uint32_t samplepointer; uint8_t vis; uint8_t vid; uint8_t vir; uint8_t vit; }; #pragma pack(pop) #endif schismtracker-20180209/include/log.h000066400000000000000000000030441323741476300172130ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 SCHISM_LOG_H #define SCHISM_LOG_H void log_nl(void); void log_append(int color, int must_free, const char *text); void log_append2(int bios_font, int color, int must_free, const char *text); void log_appendf(int color, const char *format, ...) __attribute__ ((format(printf, 2, 3))); void log_underline(int chars); void log_perror(const char *prefix); void status_text_flash(const char *format, ...) __attribute__ ((format(printf, 1, 2))); void status_text_flash_bios(const char *format, ...) __attribute__ ((format(printf, 1, 2))); #endif schismtracker-20180209/include/midi.h000066400000000000000000000121341323741476300173540ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 struct midi_provider; struct midi_port; #define MIDI_PORT_CAN_SCHEDULE 1 struct midi_driver { unsigned int flags; 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, const unsigned char *seq, unsigned int len, unsigned int delay); void (*drain)(struct midi_port *d); }; struct midi_provider { 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, const unsigned char *seq, unsigned int len, unsigned int delay); void (*send_later)(struct midi_port *d, const unsigned char *seq, unsigned int len, unsigned int delay); void (*drain)(struct midi_port *d); }; #define MIDI_INPUT 1 #define MIDI_OUTPUT 2 struct midi_port { int io, iocap; 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, const unsigned char *seq, unsigned int len, unsigned int delay); void (*send_later)(struct midi_port *d, const unsigned char *seq, unsigned int len, unsigned int delay); void (*drain)(struct midi_port *d); 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(const unsigned char *seq, unsigned int len); /* ... but the player calls this */ void midi_send_buffer(const unsigned char *data, unsigned int len, unsigned int pos); void midi_send_flush(void); /* used by the audio thread */ int midi_need_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 mnstatus, 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); void midi_event_system(int argv, int param); /* midi drivers call this when they received an event */ void midi_received_cb(struct midi_port *src, unsigned char *data, unsigned int len); int ip_midi_setup(void); // USE_NETWORK void ip_midi_setports(int n); // USE_NETWORK int ip_midi_getports(void); // USE_NETWORK int oss_midi_setup(void); // USE_OSS int alsa_midi_setup(void); // USE_ALSA int win32mm_midi_setup(void); // WIN32 int macosx_midi_setup(void); // MACOSX /* MIDI_PITCH_BEND is defined by OSS -- maybe these need more specific names? */ #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_PITCHBEND 0x00000040 #define MIDI_DISABLE_RECORD 0x00010000 extern int midi_flags, midi_pitch_depth, midi_amplification, midi_c5note; #endif schismtracker-20180209/include/osdefs.h000066400000000000000000000114351323741476300177200ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ /* OS-dependent code implementations are defined here; the files for each target OS exist in sys/blah/osdefs.c, and possibly other files as well. Only one osdefs.c should be in use at a time. */ #ifndef OSDEFS_H #define OSDEFS_H #include "headers.h" #include "event.h" // This is defined in osdefs.c but not used anywhere. // Currently, its only purpose is to prevent erroneous linking of multiple osdefs.o files in the same build. extern const char *osname; /* os_sysinit: any platform-dependent setup that needs to occur directly upon startup. This code is processed right as soon as main() starts. os_sdlinit: any platform-dependent setup that needs to occur after SDL is up and running. Currently only used on the Wii in order to get the Wiimote working. os_sdlevent: preprocessing for SDL events. This is used to hack in system-dependent input methods (e.g. F16 and other scancodes on OS X; Wiimote buttons; etc.) If defined, this function will be called after capturing an SDL event. A return value of 0 indicates that the event should NOT be processed by the main event handler. */ #if defined(MACOSX) # define os_sdlevent macosx_sdlevent #elif defined(GEKKO) # define os_sysinit wii_sysinit # define os_sdlinit wii_sdlinit # define os_sysexit wii_sysexit # define os_sdlevent wii_sdlevent #elif defined(WIN32) # define os_sysinit win32_sysinit #endif #ifndef os_sdlevent # define os_sdlevent(ev) 1 #endif #ifndef os_sdlinit # define os_sdlinit() #endif #ifndef os_sysinit # define os_sysinit(pargc,argv) #endif #ifndef os_sysexit # define os_sysexit() #endif /* os_screensaver_deactivate: whatever is needed to keep the screensaver away. Leave this *undefined* if no implementation exists. */ #if defined(USE_X11) # define os_screensaver_deactivate x11_screensaver_deactivate #else # undef os_screensaver_deactivate #endif /* os_yuvlayout: return the best YUV layout. */ #if defined(USE_XV) # define os_yuvlayout xv_yuvlayout #elif defined(USE_X11) # define os_yuvlayout() VIDEO_YUV_NONE #else # define os_yuvlayout() VIDEO_YUV_YUY2 #endif // Implementations for the above, and more. int macosx_sdlevent(SDL_Event *event); // patch up osx scancodes for printscreen et al; numlock hack? int macosx_ibook_fnswitch(int setting); void wii_sysinit(int *pargc, char ***pargv); // set up filesystem void wii_sysexit(void); // close filesystem void wii_sdlinit(void); // set up wiimote int wii_sdlevent(SDL_Event *event); // add unicode values; wiimote hack to allow simple playback void x11_screensaver_deactivate(void); unsigned int xv_yuvlayout(void); void win32_sysinit(int *pargc, char ***pargv); void win32_get_modkey(int *m); void win32_filecreated_callback(const char *filename); // migrated from xkb.c #if defined(HAVE_X11_XKBLIB_H) # define USE_XKB 1 #endif #if defined(USE_XKB) || defined(WIN32) || defined(MACOSX) int key_scancode_lookup(int k, int def); #else #define key_scancode_lookup(k, def) def #endif #if defined(USE_X11) || defined(WIN32) || defined(MACOSX) unsigned int key_repeat_delay(void); unsigned int key_repeat_rate(void); #else # include "sdlmain.h" // blecch # define key_repeat_delay() SDL_DEFAULT_REPEAT_DELAY # define key_repeat_rate() SDL_DEFAULT_REPEAT_INTERVAL #endif // Mixer interfaces void volume_setup(void); int volume_get_max(void); void volume_read(int *left, int *right); void volume_write(int left, int right); int alsa_volume_get_max(void); void alsa_volume_read(int *, int *); void alsa_volume_write(int, int); int oss_volume_get_max(void); void oss_volume_read(int *, int *); void oss_volume_write(int, int); int macosx_volume_get_max(void); void macosx_volume_read(int *, int *); void macosx_volume_write(int, int); int win32mm_volume_get_max(void); void win32mm_volume_read(int *, int *); void win32mm_volume_write(int, int); // Nasty alsa crap void alsa_dlinit(void); #endif /* ! OSDEFS_H */ schismtracker-20180209/include/page.h000066400000000000000000000473071323741476300173600ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 /* How much to scroll. */ #define MOUSE_SCROLL_LINES 3 struct key_event { SDLKey sym, orig_sym; SDLMod mod; uint16_t unicode; int scancode; enum { KEY_PRESS=0, KEY_RELEASE } state; enum { MOUSE_NONE=0, MOUSE_CLICK, MOUSE_SCROLL_UP, MOUSE_SCROLL_DOWN, MOUSE_DBLCLICK } mouse; enum { MOUSE_BUTTON_LEFT=0, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_RIGHT } mouse_button; 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; int is_synthetic; /* 1 came from paste */ }; /* --------------------------------------------------------------------- */ /* help text */ /* NOTE: this enum should be in the same order as helptexts in Makefile.am */ enum { HELP_GLOBAL, /* needs to be first! */ HELP_COPYRIGHT, HELP_INFO_PAGE, HELP_INSTRUMENT_LIST, HELP_MESSAGE_EDITOR, HELP_MIDI_OUTPUT, HELP_ORDERLIST_PANNING, HELP_ORDERLIST_VOLUME, HELP_PATTERN_EDITOR, HELP_ADLIB_SAMPLE, HELP_SAMPLE_LIST, HELP_NUM_ITEMS /* needs to be last! */ }; extern const char *help_text[HELP_NUM_ITEMS]; /* --------------------------------------------------------------------- */ /* 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_BITSET, 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 *const *choices; int num_choices; const char *activation_keys; }; /* 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 */ const 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; }; struct widget_bitset { /* A widget for controlling individual bits */ int nbits; int value; int *cursor_pos; const char* bits_on; const char* bits_off; const char* activation_keys; }; /* 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; unsigned int muted:1; unsigned 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_bitset bitset; }; 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); /* called by the clipboard manager; really, only "other" widgets should "override" this... */ int (*clipboard_paste)(int cb, const void *cptr); /* true if the widget accepts "text"- used for digraphs and unicode and alt+kp entry... */ int accept_text; }; /* 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); /* called when the song-mode changes */ void (*song_mode_changed_cb) (void); /* called by the clipboard manager */ int (*clipboard_paste)(int cb, const void *cptr); 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, PAGE_HELP, PAGE_ABOUT, PAGE_LOG, PAGE_PATTERN_EDITOR, PAGE_SAMPLE_LIST, // PAGE_INSTRUMENT_LIST doesn't exist PAGE_INFO, PAGE_CONFIG, PAGE_PREFERENCES, PAGE_MIDI, PAGE_MIDI_OUTPUT, PAGE_LOAD_MODULE, PAGE_SAVE_MODULE, PAGE_EXPORT_MODULE, PAGE_ORDERLIST_PANNING, PAGE_ORDERLIST_VOLUMES, PAGE_SONG_VARIABLES, PAGE_MESSAGE, /* don't use these directly with set_page */ PAGE_INSTRUMENT_LIST_GENERAL, PAGE_INSTRUMENT_LIST_VOLUME, PAGE_INSTRUMENT_LIST_PANNING, PAGE_INSTRUMENT_LIST_PITCH, PAGE_LOAD_SAMPLE, PAGE_LIBRARY_SAMPLE, PAGE_LOAD_INSTRUMENT, PAGE_LIBRARY_INSTRUMENT, PAGE_PALETTE_EDITOR, PAGE_FONT_EDIT, PAGE_WATERFALL, PAGE_MAX }; /* --------------------------------------------------------------------- */ 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, int do_export); 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 library_sample_load_page(struct page *page); void library_instrument_load_page(struct page *page); void config_load_page(struct page *page); void waterfall_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 *const *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, const 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_bitset(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), int nbits, const char* bits_on, const char* bits_off, int *cursor_pos); 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_t unicode); void numentry_change_value(struct widget *widget, int new_value); int numentry_handle_digit(struct widget *widget, struct key_event *k); int menutoggle_handle_key(struct widget *widget, struct key_event *k); int bitset_handle_key(struct widget *widget, struct key_event *k); 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); // support for the song length dialog void show_length_dialog(const char *label, unsigned int length); /* 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); int pattern_max_channels(int patno, int opt_bits[64]); /* 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); /* charset.c (this is slightly out of place...) */ int char_digraph(int k1, int k2); int char_unicode_to_cp437(unsigned int c); /* --------------------------------------------------------------------- */ /* 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); /* --------------------------------------------------------------------- */ /* Other UI prompt stuff. */ /* Ask for a value, like the thumbbars. */ void numprompt_create(const char *prompt, void (*finish)(int n), char initvalue); /* Ask for a sample / instrument number, like the "swap sample" dialog. */ void smpprompt_create(const char *title, const char *prompt, void (*finish)(int n)); #endif /* ! PAGE_H */ schismtracker-20180209/include/pattern-view.h000066400000000000000000000041371323741476300210630ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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, const song_note_t *note, int cursor_pos, int fg, int bg); typedef void (*draw_mask_func) (int x, int y, int mask, 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, const song_note_t *note, int cursor_pos, int fg, int bg); \ void draw_mask_##n(int x, int y, int mask, int cursor_pos, int fg, int bg); PATTERN_VIEW(13); PATTERN_VIEW(10); PATTERN_VIEW(8); // note: not usable for editing as instrument numbers are not shown (thus no draw_mask) PATTERN_VIEW(7); PATTERN_VIEW(6); PATTERN_VIEW(3); PATTERN_VIEW(2); PATTERN_VIEW(1); #undef PATTERN_VIEW /* for the pattern editor masks (the ^^^ ^^ ^^ --- markers at the bottom) */ #define MASK_NOTE 1 /* immutable */ #define MASK_INSTRUMENT 2 #define MASK_VOLUME 4 #define MASK_EFFECT 8 #endif /* ! PATTERN_VIEW_H */ schismtracker-20180209/include/precomp_lut.h000066400000000000000000003456541323741476300210030ustar00rootroot00000000000000static signed short cubic_spline_lut[4096] = { 0, 16384, 0, 0, -8, 16384, 8, 0, -16, 16384, 16, 0, -24, 16384, 24, 0, -32, 16384, 32, 0, -40, 16383, 41, 0, -47, 16382, 49, 0, -55, 16381, 58, 0, -63, 16381, 66, 0, -71, 16381, 75, -1, -78, 16380, 83, -1, -86, 16379, 92, -1, -94, 16379, 100, -1, -101, 16377, 109, -1, -109, 16377, 118, -2, -117, 16376, 127, -2, -124, 16374, 136, -2, -132, 16373, 145, -2, -139, 16371, 154, -2, -146, 16370, 163, -3, -154, 16369, 172, -3, -161, 16366, 182, -3, -169, 16366, 191, -4, -176, 16364, 200, -4, -183, 16361, 210, -4, -190, 16360, 219, -5, -198, 16358, 229, -5, -205, 16357, 238, -6, -212, 16354, 248, -6, -219, 16351, 258, -6, -226, 16349, 268, -7, -233, 16347, 277, -7, -240, 16345, 287, -8, -247, 16342, 297, -8, -254, 16340, 307, -9, -261, 16337, 317, -9, -268, 16335, 327, -10, -275, 16331, 338, -10, -282, 16329, 348, -11, -289, 16326, 358, -11, -295, 16322, 369, -12, -302, 16320, 379, -13, -309, 16317, 389, -13, -316, 16314, 400, -14, -322, 16309, 411, -14, -329, 16307, 421, -15, -336, 16304, 432, -16, -342, 16299, 443, -16, -349, 16297, 453, -17, -355, 16293, 464, -18, -362, 16290, 475, -19, -368, 16285, 486, -19, -375, 16282, 497, -20, -381, 16278, 508, -21, -388, 16274, 520, -22, -394, 16269, 531, -22, -400, 16265, 542, -23, -407, 16262, 553, -24, -413, 16257, 565, -25, -419, 16253, 576, -26, -425, 16247, 588, -26, -432, 16244, 599, -27, -438, 16239, 611, -28, -444, 16235, 622, -29, -450, 16230, 634, -30, -456, 16225, 646, -31, -462, 16220, 658, -32, -468, 16216, 669, -33, -474, 16211, 681, -34, -480, 16206, 693, -35, -486, 16201, 705, -36, -492, 16196, 717, -37, -498, 16191, 729, -38, -504, 16185, 742, -39, -510, 16180, 754, -40, -515, 16174, 766, -41, -521, 16169, 778, -42, -527, 16163, 791, -43, -533, 16158, 803, -44, -538, 16151, 816, -45, -544, 16146, 828, -46, -550, 16140, 841, -47, -555, 16133, 854, -48, -561, 16128, 866, -49, -566, 16122, 879, -51, -572, 16116, 892, -52, -577, 16109, 905, -53, -583, 16104, 917, -54, -588, 16097, 930, -55, -594, 16092, 943, -57, -599, 16085, 956, -58, -604, 16077, 970, -59, -610, 16071, 983, -60, -615, 16064, 996, -61, -620, 16058, 1009, -63, -626, 16052, 1022, -64, -631, 16044, 1036, -65, -636, 16038, 1049, -67, -641, 16030, 1063, -68, -646, 16023, 1076, -69, -651, 16015, 1090, -70, -656, 16009, 1103, -72, -662, 16002, 1117, -73, -667, 15995, 1131, -75, -672, 15988, 1144, -76, -677, 15980, 1158, -77, -682, 15973, 1172, -79, -686, 15964, 1186, -80, -691, 15957, 1200, -82, -696, 15949, 1214, -83, -701, 15941, 1228, -84, -706, 15934, 1242, -86, -711, 15926, 1256, -87, -715, 15918, 1270, -89, -720, 15910, 1284, -90, -725, 15903, 1298, -92, -730, 15894, 1313, -93, -734, 15886, 1327, -95, -739, 15877, 1342, -96, -744, 15870, 1356, -98, -748, 15861, 1370, -99, -753, 15853, 1385, -101, -757, 15843, 1400, -102, -762, 15836, 1414, -104, -766, 15827, 1429, -106, -771, 15818, 1444, -107, -775, 15810, 1458, -109, -780, 15801, 1473, -110, -784, 15792, 1488, -112, -788, 15783, 1503, -114, -793, 15774, 1518, -115, -797, 15765, 1533, -117, -801, 15756, 1548, -119, -806, 15747, 1563, -120, -810, 15738, 1578, -122, -814, 15729, 1593, -124, -818, 15719, 1608, -125, -822, 15709, 1624, -127, -826, 15700, 1639, -129, -831, 15691, 1654, -130, -835, 15681, 1670, -132, -839, 15672, 1685, -134, -843, 15662, 1701, -136, -847, 15652, 1716, -137, -851, 15642, 1732, -139, -855, 15633, 1747, -141, -859, 15623, 1763, -143, -863, 15613, 1779, -145, -866, 15602, 1794, -146, -870, 15592, 1810, -148, -874, 15582, 1826, -150, -878, 15572, 1842, -152, -882, 15562, 1858, -154, -886, 15552, 1874, -156, -889, 15540, 1890, -157, -893, 15530, 1906, -159, -897, 15520, 1922, -161, -900, 15509, 1938, -163, -904, 15499, 1954, -165, -908, 15489, 1970, -167, -911, 15478, 1986, -169, -915, 15467, 2003, -171, -918, 15456, 2019, -173, -922, 15446, 2035, -175, -925, 15433, 2052, -176, -929, 15423, 2068, -178, -932, 15412, 2084, -180, -936, 15401, 2101, -182, -939, 15390, 2117, -184, -943, 15379, 2134, -186, -946, 15367, 2151, -188, -949, 15356, 2167, -190, -953, 15345, 2184, -192, -956, 15333, 2201, -194, -959, 15321, 2218, -196, -962, 15310, 2234, -198, -966, 15299, 2251, -200, -969, 15287, 2268, -202, -972, 15276, 2285, -205, -975, 15264, 2302, -207, -978, 15252, 2319, -209, -981, 15240, 2336, -211, -984, 15228, 2353, -213, -987, 15216, 2370, -215, -991, 15205, 2387, -217, -994, 15192, 2405, -219, -997, 15180, 2422, -221, -999, 15167, 2439, -223, -1002, 15155, 2456, -225, -1005, 15143, 2474, -228, -1008, 15131, 2491, -230, -1011, 15118, 2509, -232, -1014, 15106, 2526, -234, -1017, 15094, 2543, -236, -1020, 15081, 2561, -238, -1022, 15067, 2579, -240, -1025, 15056, 2596, -243, -1028, 15043, 2614, -245, -1031, 15031, 2631, -247, -1033, 15017, 2649, -249, -1036, 15004, 2667, -251, -1039, 14992, 2685, -254, -1041, 14979, 2702, -256, -1044, 14966, 2720, -258, -1047, 14953, 2738, -260, -1049, 14940, 2756, -263, -1052, 14927, 2774, -265, -1054, 14913, 2792, -267, -1057, 14900, 2810, -269, -1059, 14887, 2828, -272, -1062, 14874, 2846, -274, -1064, 14860, 2864, -276, -1066, 14846, 2882, -278, -1069, 14833, 2901, -281, -1071, 14819, 2919, -283, -1074, 14806, 2937, -285, -1076, 14793, 2955, -288, -1078, 14778, 2974, -290, -1080, 14764, 2992, -292, -1083, 14752, 3010, -295, -1085, 14737, 3029, -297, -1087, 14723, 3047, -299, -1089, 14709, 3066, -302, -1092, 14696, 3084, -304, -1094, 14681, 3103, -306, -1096, 14668, 3121, -309, -1098, 14653, 3140, -311, -1100, 14638, 3159, -313, -1102, 14625, 3177, -316, -1104, 14610, 3196, -318, -1106, 14595, 3215, -320, -1108, 14582, 3233, -323, -1110, 14567, 3252, -325, -1112, 14553, 3271, -328, -1114, 14538, 3290, -330, -1116, 14523, 3309, -332, -1118, 14509, 3328, -335, -1120, 14494, 3347, -337, -1122, 14480, 3366, -340, -1124, 14465, 3385, -342, -1125, 14450, 3404, -345, -1127, 14435, 3423, -347, -1129, 14420, 3442, -349, -1131, 14406, 3461, -352, -1133, 14391, 3480, -354, -1134, 14376, 3499, -357, -1136, 14361, 3518, -359, -1138, 14346, 3538, -362, -1139, 14330, 3557, -364, -1141, 14316, 3576, -367, -1143, 14301, 3595, -369, -1144, 14285, 3615, -372, -1146, 14270, 3634, -374, -1147, 14254, 3654, -377, -1149, 14239, 3673, -379, -1150, 14223, 3693, -382, -1152, 14208, 3712, -384, -1153, 14192, 3732, -387, -1155, 14177, 3751, -389, -1156, 14161, 3771, -392, -1158, 14146, 3790, -394, -1159, 14130, 3810, -397, -1161, 14115, 3829, -399, -1162, 14099, 3849, -402, -1163, 14082, 3869, -404, -1165, 14067, 3889, -407, -1166, 14051, 3908, -409, -1167, 14035, 3928, -412, -1169, 14019, 3948, -414, -1170, 14003, 3968, -417, -1171, 13986, 3988, -419, -1172, 13971, 4007, -422, -1174, 13955, 4027, -424, -1175, 13939, 4047, -427, -1176, 13923, 4067, -430, -1177, 13906, 4087, -432, -1178, 13890, 4107, -435, -1179, 13873, 4127, -437, -1180, 13857, 4147, -440, -1181, 13840, 4167, -442, -1182, 13823, 4188, -445, -1184, 13808, 4208, -448, -1185, 13791, 4228, -450, -1186, 13775, 4248, -453, -1187, 13758, 4268, -455, -1187, 13741, 4288, -458, -1188, 13724, 4309, -461, -1189, 13707, 4329, -463, -1190, 13691, 4349, -466, -1191, 13673, 4370, -468, -1192, 13657, 4390, -471, -1193, 13641, 4410, -474, -1194, 13623, 4431, -476, -1195, 13607, 4451, -479, -1195, 13589, 4471, -481, -1196, 13572, 4492, -484, -1197, 13556, 4512, -487, -1198, 13538, 4533, -489, -1198, 13521, 4553, -492, -1199, 13504, 4574, -495, -1200, 13486, 4595, -497, -1200, 13469, 4615, -500, -1201, 13451, 4636, -502, -1202, 13435, 4656, -505, -1202, 13417, 4677, -508, -1203, 13399, 4698, -510, -1204, 13383, 4718, -513, -1204, 13365, 4739, -516, -1205, 13347, 4760, -518, -1205, 13330, 4780, -521, -1206, 13312, 4801, -523, -1206, 13294, 4822, -526, -1207, 13277, 4843, -529, -1207, 13258, 4864, -531, -1208, 13241, 4885, -534, -1208, 13224, 4905, -537, -1208, 13205, 4926, -539, -1209, 13188, 4947, -542, -1209, 13170, 4968, -545, -1210, 13152, 4989, -547, -1210, 13134, 5010, -550, -1210, 13116, 5031, -553, -1211, 13098, 5052, -555, -1211, 13080, 5073, -558, -1211, 13062, 5094, -561, -1212, 13044, 5115, -563, -1212, 13026, 5136, -566, -1212, 13008, 5157, -569, -1212, 12989, 5178, -571, -1212, 12971, 5199, -574, -1213, 12953, 5221, -577, -1213, 12934, 5242, -579, -1213, 12916, 5263, -582, -1213, 12898, 5284, -585, -1213, 12879, 5305, -587, -1213, 12860, 5327, -590, -1213, 12842, 5348, -593, -1213, 12823, 5369, -595, -1214, 12806, 5390, -598, -1214, 12787, 5412, -601, -1214, 12768, 5433, -603, -1214, 12750, 5454, -606, -1214, 12731, 5476, -609, -1214, 12712, 5497, -611, -1214, 12694, 5518, -614, -1214, 12675, 5540, -617, -1213, 12655, 5561, -619, -1213, 12637, 5582, -622, -1213, 12618, 5604, -625, -1213, 12599, 5625, -627, -1213, 12580, 5647, -630, -1213, 12562, 5668, -633, -1213, 12542, 5690, -635, -1213, 12524, 5711, -638, -1212, 12504, 5733, -641, -1212, 12485, 5754, -643, -1212, 12466, 5776, -646, -1212, 12448, 5797, -649, -1211, 12427, 5819, -651, -1211, 12408, 5841, -654, -1211, 12390, 5862, -657, -1211, 12370, 5884, -659, -1210, 12351, 5905, -662, -1210, 12332, 5927, -665, -1210, 12312, 5949, -667, -1209, 12293, 5970, -670, -1209, 12273, 5992, -672, -1209, 12254, 6014, -675, -1208, 12235, 6035, -678, -1208, 12215, 6057, -680, -1207, 12195, 6079, -683, -1207, 12176, 6101, -686, -1207, 12157, 6122, -688, -1206, 12137, 6144, -691, -1206, 12118, 6166, -694, -1205, 12097, 6188, -696, -1205, 12079, 6209, -699, -1204, 12059, 6231, -702, -1204, 12039, 6253, -704, -1203, 12019, 6275, -707, -1202, 11998, 6297, -709, -1202, 11980, 6318, -712, -1201, 11960, 6340, -715, -1201, 11940, 6362, -717, -1200, 11920, 6384, -720, -1199, 11900, 6406, -723, -1199, 11880, 6428, -725, -1198, 11860, 6450, -728, -1197, 11839, 6472, -730, -1197, 11821, 6493, -733, -1196, 11801, 6515, -736, -1195, 11780, 6537, -738, -1195, 11761, 6559, -741, -1194, 11741, 6581, -744, -1193, 11720, 6603, -746, -1192, 11700, 6625, -749, -1192, 11680, 6647, -751, -1191, 11660, 6669, -754, -1190, 11640, 6691, -757, -1189, 11619, 6713, -759, -1188, 11599, 6735, -762, -1187, 11578, 6757, -764, -1187, 11559, 6779, -767, -1186, 11538, 6801, -769, -1185, 11518, 6823, -772, -1184, 11498, 6845, -775, -1183, 11477, 6867, -777, -1182, 11457, 6889, -780, -1181, 11436, 6911, -782, -1180, 11415, 6934, -785, -1179, 11394, 6956, -787, -1178, 11374, 6978, -790, -1177, 11354, 7000, -793, -1176, 11333, 7022, -795, -1175, 11313, 7044, -798, -1174, 11292, 7066, -800, -1173, 11272, 7088, -803, -1172, 11251, 7110, -805, -1171, 11231, 7132, -808, -1170, 11209, 7155, -810, -1169, 11189, 7177, -813, -1168, 11168, 7199, -815, -1167, 11148, 7221, -818, -1166, 11127, 7243, -820, -1165, 11107, 7265, -823, -1163, 11084, 7288, -825, -1162, 11064, 7310, -828, -1161, 11043, 7332, -830, -1160, 11023, 7354, -833, -1159, 11002, 7376, -835, -1158, 10982, 7398, -838, -1156, 10959, 7421, -840, -1155, 10939, 7443, -843, -1154, 10918, 7465, -845, -1153, 10898, 7487, -848, -1151, 10876, 7509, -850, -1150, 10856, 7531, -853, -1149, 10834, 7554, -855, -1148, 10814, 7576, -858, -1146, 10792, 7598, -860, -1145, 10772, 7620, -863, -1144, 10750, 7643, -865, -1142, 10728, 7665, -867, -1141, 10708, 7687, -870, -1140, 10687, 7709, -872, -1138, 10666, 7731, -875, -1137, 10644, 7754, -877, -1135, 10623, 7776, -880, -1134, 10602, 7798, -882, -1133, 10581, 7820, -884, -1131, 10560, 7842, -887, -1130, 10538, 7865, -889, -1128, 10517, 7887, -892, -1127, 10496, 7909, -894, -1125, 10474, 7931, -896, -1124, 10453, 7954, -899, -1122, 10431, 7976, -901, -1121, 10410, 7998, -903, -1119, 10389, 8020, -906, -1118, 10368, 8042, -908, -1116, 10346, 8065, -911, -1115, 10325, 8087, -913, -1113, 10303, 8109, -915, -1112, 10283, 8131, -918, -1110, 10260, 8154, -920, -1109, 10239, 8176, -922, -1107, 10217, 8198, -924, -1105, 10196, 8220, -927, -1104, 10175, 8242, -929, -1102, 10152, 8265, -931, -1101, 10132, 8287, -934, -1099, 10110, 8309, -936, -1097, 10088, 8331, -938, -1096, 10068, 8353, -941, -1094, 10045, 8376, -943, -1092, 10023, 8398, -945, -1091, 10002, 8420, -947, -1089, 9981, 8442, -950, -1087, 9959, 8464, -952, -1085, 9936, 8487, -954, -1084, 9915, 8509, -956, -1082, 9893, 8531, -958, -1080, 9872, 8553, -961, -1079, 9851, 8575, -963, -1077, 9829, 8597, -965, -1075, 9806, 8620, -967, -1073, 9784, 8642, -969, -1071, 9763, 8664, -972, -1070, 9742, 8686, -974, -1068, 9720, 8708, -976, -1066, 9698, 8730, -978, -1064, 9676, 8752, -980, -1062, 9653, 8775, -982, -1061, 9633, 8797, -985, -1059, 9611, 8819, -987, -1057, 9589, 8841, -989, -1055, 9567, 8863, -991, -1053, 9545, 8885, -993, -1051, 9523, 8907, -995, -1049, 9501, 8929, -997, -1047, 9479, 8951, -999, -1046, 9458, 8974, -1002, -1044, 9436, 8996, -1004, -1042, 9414, 9018, -1006, -1040, 9392, 9040, -1008, -1038, 9370, 9062, -1010, -1036, 9348, 9084, -1012, -1034, 9326, 9106, -1014, -1032, 9304, 9128, -1016, -1030, 9282, 9150, -1018, -1028, 9260, 9172, -1020, -1026, 9238, 9194, -1022, -1024, 9216, 9216, -1024, -1022, 9194, 9238, -1026, -1020, 9172, 9260, -1028, -1018, 9150, 9282, -1030, -1016, 9128, 9304, -1032, -1014, 9106, 9326, -1034, -1012, 9084, 9348, -1036, -1010, 9062, 9370, -1038, -1008, 9040, 9392, -1040, -1006, 9018, 9414, -1042, -1004, 8996, 9436, -1044, -1002, 8974, 9458, -1046, -999, 8951, 9479, -1047, -997, 8929, 9501, -1049, -995, 8907, 9523, -1051, -993, 8885, 9545, -1053, -991, 8863, 9567, -1055, -989, 8841, 9589, -1057, -987, 8819, 9611, -1059, -985, 8797, 9633, -1061, -982, 8775, 9653, -1062, -980, 8752, 9676, -1064, -978, 8730, 9698, -1066, -976, 8708, 9720, -1068, -974, 8686, 9742, -1070, -972, 8664, 9763, -1071, -969, 8642, 9784, -1073, -967, 8620, 9806, -1075, -965, 8597, 9829, -1077, -963, 8575, 9851, -1079, -961, 8553, 9872, -1080, -958, 8531, 9893, -1082, -956, 8509, 9915, -1084, -954, 8487, 9936, -1085, -952, 8464, 9959, -1087, -950, 8442, 9981, -1089, -947, 8420, 10002, -1091, -945, 8398, 10023, -1092, -943, 8376, 10045, -1094, -941, 8353, 10068, -1096, -938, 8331, 10088, -1097, -936, 8309, 10110, -1099, -934, 8287, 10132, -1101, -931, 8265, 10152, -1102, -929, 8242, 10175, -1104, -927, 8220, 10196, -1105, -924, 8198, 10217, -1107, -922, 8176, 10239, -1109, -920, 8154, 10260, -1110, -918, 8131, 10283, -1112, -915, 8109, 10303, -1113, -913, 8087, 10325, -1115, -911, 8065, 10346, -1116, -908, 8042, 10368, -1118, -906, 8020, 10389, -1119, -903, 7998, 10410, -1121, -901, 7976, 10431, -1122, -899, 7954, 10453, -1124, -896, 7931, 10474, -1125, -894, 7909, 10496, -1127, -892, 7887, 10517, -1128, -889, 7865, 10538, -1130, -887, 7842, 10560, -1131, -884, 7820, 10581, -1133, -882, 7798, 10602, -1134, -880, 7776, 10623, -1135, -877, 7754, 10644, -1137, -875, 7731, 10666, -1138, -872, 7709, 10687, -1140, -870, 7687, 10708, -1141, -867, 7665, 10728, -1142, -865, 7643, 10750, -1144, -863, 7620, 10772, -1145, -860, 7598, 10792, -1146, -858, 7576, 10814, -1148, -855, 7554, 10834, -1149, -853, 7531, 10856, -1150, -850, 7509, 10876, -1151, -848, 7487, 10898, -1153, -845, 7465, 10918, -1154, -843, 7443, 10939, -1155, -840, 7421, 10959, -1156, -838, 7398, 10982, -1158, -835, 7376, 11002, -1159, -833, 7354, 11023, -1160, -830, 7332, 11043, -1161, -828, 7310, 11064, -1162, -825, 7288, 11084, -1163, -823, 7265, 11107, -1165, -820, 7243, 11127, -1166, -818, 7221, 11148, -1167, -815, 7199, 11168, -1168, -813, 7177, 11189, -1169, -810, 7155, 11209, -1170, -808, 7132, 11231, -1171, -805, 7110, 11251, -1172, -803, 7088, 11272, -1173, -800, 7066, 11292, -1174, -798, 7044, 11313, -1175, -795, 7022, 11333, -1176, -793, 7000, 11354, -1177, -790, 6978, 11374, -1178, -787, 6956, 11394, -1179, -785, 6934, 11415, -1180, -782, 6911, 11436, -1181, -780, 6889, 11457, -1182, -777, 6867, 11477, -1183, -775, 6845, 11498, -1184, -772, 6823, 11518, -1185, -769, 6801, 11538, -1186, -767, 6779, 11559, -1187, -764, 6757, 11578, -1187, -762, 6735, 11599, -1188, -759, 6713, 11619, -1189, -757, 6691, 11640, -1190, -754, 6669, 11660, -1191, -751, 6647, 11680, -1192, -749, 6625, 11700, -1192, -746, 6603, 11720, -1193, -744, 6581, 11741, -1194, -741, 6559, 11761, -1195, -738, 6537, 11780, -1195, -736, 6515, 11801, -1196, -733, 6493, 11821, -1197, -730, 6472, 11839, -1197, -728, 6450, 11860, -1198, -725, 6428, 11880, -1199, -723, 6406, 11900, -1199, -720, 6384, 11920, -1200, -717, 6362, 11940, -1201, -715, 6340, 11960, -1201, -712, 6318, 11980, -1202, -709, 6297, 11998, -1202, -707, 6275, 12019, -1203, -704, 6253, 12039, -1204, -702, 6231, 12059, -1204, -699, 6209, 12079, -1205, -696, 6188, 12097, -1205, -694, 6166, 12118, -1206, -691, 6144, 12137, -1206, -688, 6122, 12157, -1207, -686, 6101, 12176, -1207, -683, 6079, 12195, -1207, -680, 6057, 12215, -1208, -678, 6035, 12235, -1208, -675, 6014, 12254, -1209, -672, 5992, 12273, -1209, -670, 5970, 12293, -1209, -667, 5949, 12312, -1210, -665, 5927, 12332, -1210, -662, 5905, 12351, -1210, -659, 5884, 12370, -1211, -657, 5862, 12390, -1211, -654, 5841, 12408, -1211, -651, 5819, 12427, -1211, -649, 5797, 12448, -1212, -646, 5776, 12466, -1212, -643, 5754, 12485, -1212, -641, 5733, 12504, -1212, -638, 5711, 12524, -1213, -635, 5690, 12542, -1213, -633, 5668, 12562, -1213, -630, 5647, 12580, -1213, -627, 5625, 12599, -1213, -625, 5604, 12618, -1213, -622, 5582, 12637, -1213, -619, 5561, 12655, -1213, -617, 5540, 12675, -1214, -614, 5518, 12694, -1214, -611, 5497, 12712, -1214, -609, 5476, 12731, -1214, -606, 5454, 12750, -1214, -603, 5433, 12768, -1214, -601, 5412, 12787, -1214, -598, 5390, 12806, -1214, -595, 5369, 12823, -1213, -593, 5348, 12842, -1213, -590, 5327, 12860, -1213, -587, 5305, 12879, -1213, -585, 5284, 12898, -1213, -582, 5263, 12916, -1213, -579, 5242, 12934, -1213, -577, 5221, 12953, -1213, -574, 5199, 12971, -1212, -571, 5178, 12989, -1212, -569, 5157, 13008, -1212, -566, 5136, 13026, -1212, -563, 5115, 13044, -1212, -561, 5094, 13062, -1211, -558, 5073, 13080, -1211, -555, 5052, 13098, -1211, -553, 5031, 13116, -1210, -550, 5010, 13134, -1210, -547, 4989, 13152, -1210, -545, 4968, 13170, -1209, -542, 4947, 13188, -1209, -539, 4926, 13205, -1208, -537, 4905, 13224, -1208, -534, 4885, 13241, -1208, -531, 4864, 13258, -1207, -529, 4843, 13277, -1207, -526, 4822, 13294, -1206, -523, 4801, 13312, -1206, -521, 4780, 13330, -1205, -518, 4760, 13347, -1205, -516, 4739, 13365, -1204, -513, 4718, 13383, -1204, -510, 4698, 13399, -1203, -508, 4677, 13417, -1202, -505, 4656, 13435, -1202, -502, 4636, 13451, -1201, -500, 4615, 13469, -1200, -497, 4595, 13486, -1200, -495, 4574, 13504, -1199, -492, 4553, 13521, -1198, -489, 4533, 13538, -1198, -487, 4512, 13556, -1197, -484, 4492, 13572, -1196, -481, 4471, 13589, -1195, -479, 4451, 13607, -1195, -476, 4431, 13623, -1194, -474, 4410, 13641, -1193, -471, 4390, 13657, -1192, -468, 4370, 13673, -1191, -466, 4349, 13691, -1190, -463, 4329, 13707, -1189, -461, 4309, 13724, -1188, -458, 4288, 13741, -1187, -455, 4268, 13758, -1187, -453, 4248, 13775, -1186, -450, 4228, 13791, -1185, -448, 4208, 13808, -1184, -445, 4188, 13823, -1182, -442, 4167, 13840, -1181, -440, 4147, 13857, -1180, -437, 4127, 13873, -1179, -435, 4107, 13890, -1178, -432, 4087, 13906, -1177, -430, 4067, 13923, -1176, -427, 4047, 13939, -1175, -424, 4027, 13955, -1174, -422, 4007, 13971, -1172, -419, 3988, 13986, -1171, -417, 3968, 14003, -1170, -414, 3948, 14019, -1169, -412, 3928, 14035, -1167, -409, 3908, 14051, -1166, -407, 3889, 14067, -1165, -404, 3869, 14082, -1163, -402, 3849, 14099, -1162, -399, 3829, 14115, -1161, -397, 3810, 14130, -1159, -394, 3790, 14146, -1158, -392, 3771, 14161, -1156, -389, 3751, 14177, -1155, -387, 3732, 14192, -1153, -384, 3712, 14208, -1152, -382, 3693, 14223, -1150, -379, 3673, 14239, -1149, -377, 3654, 14254, -1147, -374, 3634, 14270, -1146, -372, 3615, 14285, -1144, -369, 3595, 14301, -1143, -367, 3576, 14316, -1141, -364, 3557, 14330, -1139, -362, 3538, 14346, -1138, -359, 3518, 14361, -1136, -357, 3499, 14376, -1134, -354, 3480, 14391, -1133, -352, 3461, 14406, -1131, -349, 3442, 14420, -1129, -347, 3423, 14435, -1127, -345, 3404, 14450, -1125, -342, 3385, 14465, -1124, -340, 3366, 14480, -1122, -337, 3347, 14494, -1120, -335, 3328, 14509, -1118, -332, 3309, 14523, -1116, -330, 3290, 14538, -1114, -328, 3271, 14553, -1112, -325, 3252, 14567, -1110, -323, 3233, 14582, -1108, -320, 3215, 14595, -1106, -318, 3196, 14610, -1104, -316, 3177, 14625, -1102, -313, 3159, 14638, -1100, -311, 3140, 14653, -1098, -309, 3121, 14668, -1096, -306, 3103, 14681, -1094, -304, 3084, 14696, -1092, -302, 3066, 14709, -1089, -299, 3047, 14723, -1087, -297, 3029, 14737, -1085, -295, 3010, 14752, -1083, -292, 2992, 14764, -1080, -290, 2974, 14778, -1078, -288, 2955, 14793, -1076, -285, 2937, 14806, -1074, -283, 2919, 14819, -1071, -281, 2901, 14833, -1069, -278, 2882, 14846, -1066, -276, 2864, 14860, -1064, -274, 2846, 14874, -1062, -272, 2828, 14887, -1059, -269, 2810, 14900, -1057, -267, 2792, 14913, -1054, -265, 2774, 14927, -1052, -263, 2756, 14940, -1049, -260, 2738, 14953, -1047, -258, 2720, 14966, -1044, -256, 2702, 14979, -1041, -254, 2685, 14992, -1039, -251, 2667, 15004, -1036, -249, 2649, 15017, -1033, -247, 2631, 15031, -1031, -245, 2614, 15043, -1028, -243, 2596, 15056, -1025, -240, 2579, 15067, -1022, -238, 2561, 15081, -1020, -236, 2543, 15094, -1017, -234, 2526, 15106, -1014, -232, 2509, 15118, -1011, -230, 2491, 15131, -1008, -228, 2474, 15143, -1005, -225, 2456, 15155, -1002, -223, 2439, 15167, -999, -221, 2422, 15180, -997, -219, 2405, 15192, -994, -217, 2387, 15205, -991, -215, 2370, 15216, -987, -213, 2353, 15228, -984, -211, 2336, 15240, -981, -209, 2319, 15252, -978, -207, 2302, 15264, -975, -205, 2285, 15276, -972, -202, 2268, 15287, -969, -200, 2251, 15299, -966, -198, 2234, 15310, -962, -196, 2218, 15321, -959, -194, 2201, 15333, -956, -192, 2184, 15345, -953, -190, 2167, 15356, -949, -188, 2151, 15367, -946, -186, 2134, 15379, -943, -184, 2117, 15390, -939, -182, 2101, 15401, -936, -180, 2084, 15412, -932, -178, 2068, 15423, -929, -176, 2052, 15433, -925, -175, 2035, 15446, -922, -173, 2019, 15456, -918, -171, 2003, 15467, -915, -169, 1986, 15478, -911, -167, 1970, 15489, -908, -165, 1954, 15499, -904, -163, 1938, 15509, -900, -161, 1922, 15520, -897, -159, 1906, 15530, -893, -157, 1890, 15540, -889, -156, 1874, 15552, -886, -154, 1858, 15562, -882, -152, 1842, 15572, -878, -150, 1826, 15582, -874, -148, 1810, 15592, -870, -146, 1794, 15602, -866, -145, 1779, 15613, -863, -143, 1763, 15623, -859, -141, 1747, 15633, -855, -139, 1732, 15642, -851, -137, 1716, 15652, -847, -136, 1701, 15662, -843, -134, 1685, 15672, -839, -132, 1670, 15681, -835, -130, 1654, 15691, -831, -129, 1639, 15700, -826, -127, 1624, 15709, -822, -125, 1608, 15719, -818, -124, 1593, 15729, -814, -122, 1578, 15738, -810, -120, 1563, 15747, -806, -119, 1548, 15756, -801, -117, 1533, 15765, -797, -115, 1518, 15774, -793, -114, 1503, 15783, -788, -112, 1488, 15792, -784, -110, 1473, 15801, -780, -109, 1458, 15810, -775, -107, 1444, 15818, -771, -106, 1429, 15827, -766, -104, 1414, 15836, -762, -102, 1400, 15843, -757, -101, 1385, 15853, -753, -99, 1370, 15861, -748, -98, 1356, 15870, -744, -96, 1342, 15877, -739, -95, 1327, 15886, -734, -93, 1313, 15894, -730, -92, 1298, 15903, -725, -90, 1284, 15910, -720, -89, 1270, 15918, -715, -87, 1256, 15926, -711, -86, 1242, 15934, -706, -84, 1228, 15941, -701, -83, 1214, 15949, -696, -82, 1200, 15957, -691, -80, 1186, 15964, -686, -79, 1172, 15973, -682, -77, 1158, 15980, -677, -76, 1144, 15988, -672, -75, 1131, 15995, -667, -73, 1117, 16002, -662, -72, 1103, 16009, -656, -70, 1090, 16015, -651, -69, 1076, 16023, -646, -68, 1063, 16030, -641, -67, 1049, 16038, -636, -65, 1036, 16044, -631, -64, 1022, 16052, -626, -63, 1009, 16058, -620, -61, 996, 16064, -615, -60, 983, 16071, -610, -59, 970, 16077, -604, -58, 956, 16085, -599, -57, 943, 16092, -594, -55, 930, 16097, -588, -54, 917, 16104, -583, -53, 905, 16109, -577, -52, 892, 16116, -572, -51, 879, 16122, -566, -49, 866, 16128, -561, -48, 854, 16133, -555, -47, 841, 16140, -550, -46, 828, 16146, -544, -45, 816, 16151, -538, -44, 803, 16158, -533, -43, 791, 16163, -527, -42, 778, 16169, -521, -41, 766, 16174, -515, -40, 754, 16180, -510, -39, 742, 16185, -504, -38, 729, 16191, -498, -37, 717, 16196, -492, -36, 705, 16201, -486, -35, 693, 16206, -480, -34, 681, 16211, -474, -33, 669, 16216, -468, -32, 658, 16220, -462, -31, 646, 16225, -456, -30, 634, 16230, -450, -29, 622, 16235, -444, -28, 611, 16239, -438, -27, 599, 16244, -432, -26, 588, 16247, -425, -26, 576, 16253, -419, -25, 565, 16257, -413, -24, 553, 16262, -407, -23, 542, 16265, -400, -22, 531, 16269, -394, -22, 520, 16274, -388, -21, 508, 16278, -381, -20, 497, 16282, -375, -19, 486, 16285, -368, -19, 475, 16290, -362, -18, 464, 16293, -355, -17, 453, 16297, -349, -16, 443, 16299, -342, -16, 432, 16304, -336, -15, 421, 16307, -329, -14, 411, 16309, -322, -14, 400, 16314, -316, -13, 389, 16317, -309, -13, 379, 16320, -302, -12, 369, 16322, -295, -11, 358, 16326, -289, -11, 348, 16329, -282, -10, 338, 16331, -275, -10, 327, 16335, -268, -9, 317, 16337, -261, -9, 307, 16340, -254, -8, 297, 16342, -247, -8, 287, 16345, -240, -7, 277, 16347, -233, -7, 268, 16349, -226, -6, 258, 16351, -219, -6, 248, 16354, -212, -6, 238, 16357, -205, -5, 229, 16358, -198, -5, 219, 16360, -190, -4, 210, 16361, -183, -4, 200, 16364, -176, -4, 191, 16366, -169, -3, 182, 16366, -161, -3, 172, 16369, -154, -3, 163, 16370, -146, -2, 154, 16371, -139, -2, 145, 16373, -132, -2, 136, 16374, -124, -2, 127, 16376, -117, -2, 118, 16377, -109, -1, 109, 16377, -101, -1, 100, 16379, -94, -1, 92, 16379, -86, -1, 83, 16380, -78, -1, 75, 16381, -71, 0, 66, 16381, -63, 0, 58, 16381, -55, 0, 49, 16382, -47, 0, 41, 16383, -40, 0, 32, 16384, -32, 0, 24, 16384, -24, 0, 16, 16384, -16, 0, 8, 16384, -8, }; static signed short windowed_fir_lut[16392] = { 55, -727, 2306, 29549, 2306, -727, 55, -48, 54, -725, 2294, 29549, 2317, -729, 55, -48, 54, -723, 2282, 29549, 2329, -731, 55, -48, 54, -721, 2271, 29549, 2341, -733, 55, -48, 54, -718, 2259, 29549, 2353, -735, 55, -48, 54, -716, 2247, 29549, 2364, -738, 56, -48, 54, -714, 2236, 29548, 2376, -740, 56, -48, 53, -712, 2224, 29548, 2388, -742, 56, -48, 53, -710, 2213, 29548, 2400, -744, 56, -48, 53, -708, 2201, 29548, 2411, -746, 56, -48, 53, -706, 2189, 29547, 2423, -748, 56, -47, 53, -704, 2178, 29547, 2435, -750, 57, -47, 53, -702, 2166, 29547, 2447, -752, 57, -47, 52, -699, 2155, 29546, 2459, -755, 57, -47, 52, -697, 2143, 29546, 2471, -757, 57, -47, 52, -695, 2132, 29546, 2483, -759, 57, -47, 52, -693, 2120, 29545, 2494, -761, 58, -47, 52, -691, 2109, 29545, 2506, -763, 58, -47, 52, -689, 2097, 29544, 2518, -765, 58, -47, 51, -687, 2086, 29544, 2530, -768, 58, -47, 51, -685, 2074, 29543, 2542, -770, 58, -47, 51, -683, 2063, 29543, 2554, -772, 58, -47, 51, -681, 2052, 29542, 2566, -774, 59, -47, 51, -679, 2040, 29542, 2578, -776, 59, -47, 50, -677, 2029, 29541, 2590, -778, 59, -46, 50, -674, 2017, 29540, 2602, -781, 59, -46, 50, -672, 2006, 29540, 2614, -783, 59, -46, 50, -670, 1995, 29539, 2626, -785, 60, -46, 50, -668, 1983, 29538, 2638, -787, 60, -46, 50, -666, 1972, 29537, 2650, -789, 60, -46, 49, -664, 1961, 29537, 2662, -791, 60, -46, 49, -662, 1949, 29536, 2675, -794, 60, -46, 49, -660, 1938, 29535, 2687, -796, 60, -46, 49, -658, 1927, 29534, 2699, -798, 61, -46, 49, -656, 1916, 29533, 2711, -800, 61, -46, 49, -654, 1904, 29533, 2723, -802, 61, -46, 48, -652, 1893, 29532, 2735, -804, 61, -46, 48, -650, 1882, 29531, 2747, -807, 61, -45, 48, -648, 1871, 29530, 2760, -809, 62, -45, 48, -646, 1860, 29529, 2772, -811, 62, -45, 48, -644, 1848, 29528, 2784, -813, 62, -45, 48, -642, 1837, 29527, 2796, -815, 62, -45, 47, -640, 1826, 29526, 2808, -818, 62, -45, 47, -638, 1815, 29525, 2821, -820, 63, -45, 47, -635, 1804, 29524, 2833, -822, 63, -45, 47, -633, 1793, 29522, 2845, -824, 63, -45, 47, -631, 1782, 29521, 2858, -826, 63, -45, 47, -629, 1771, 29520, 2870, -829, 63, -45, 47, -627, 1760, 29519, 2882, -831, 64, -45, 46, -625, 1749, 29518, 2895, -833, 64, -45, 46, -623, 1738, 29517, 2907, -835, 64, -44, 46, -621, 1727, 29515, 2919, -838, 64, -44, 46, -619, 1716, 29514, 2932, -840, 64, -44, 46, -617, 1705, 29513, 2944, -842, 64, -44, 46, -615, 1694, 29511, 2956, -844, 65, -44, 45, -613, 1683, 29510, 2969, -846, 65, -44, 45, -611, 1672, 29509, 2981, -849, 65, -44, 45, -609, 1661, 29507, 2994, -851, 65, -44, 45, -607, 1650, 29506, 3006, -853, 65, -44, 45, -605, 1639, 29504, 3019, -855, 66, -44, 45, -603, 1628, 29503, 3031, -858, 66, -44, 44, -601, 1617, 29502, 3044, -860, 66, -44, 44, -599, 1606, 29500, 3056, -862, 66, -44, 44, -597, 1595, 29498, 3069, -864, 66, -43, 44, -595, 1585, 29497, 3081, -867, 67, -43, 44, -593, 1574, 29495, 3094, -869, 67, -43, 44, -591, 1563, 29494, 3106, -871, 67, -43, 43, -589, 1552, 29492, 3119, -873, 67, -43, 43, -587, 1541, 29490, 3131, -876, 67, -43, 43, -585, 1531, 29489, 3144, -878, 68, -43, 43, -583, 1520, 29487, 3157, -880, 68, -43, 43, -581, 1509, 29485, 3169, -882, 68, -43, 43, -579, 1498, 29484, 3182, -885, 68, -43, 43, -578, 1488, 29482, 3194, -887, 68, -43, 42, -576, 1477, 29480, 3207, -889, 69, -43, 42, -574, 1466, 29478, 3220, -891, 69, -43, 42, -572, 1456, 29476, 3232, -894, 69, -42, 42, -570, 1445, 29475, 3245, -896, 69, -42, 42, -568, 1434, 29473, 3258, -898, 69, -42, 42, -566, 1424, 29471, 3271, -900, 70, -42, 42, -564, 1413, 29469, 3283, -903, 70, -42, 41, -562, 1403, 29467, 3296, -905, 70, -42, 41, -560, 1392, 29465, 3309, -907, 70, -42, 41, -558, 1381, 29463, 3322, -909, 70, -42, 41, -556, 1371, 29461, 3334, -912, 71, -42, 41, -554, 1360, 29459, 3347, -914, 71, -42, 41, -552, 1350, 29457, 3360, -916, 71, -42, 40, -550, 1339, 29455, 3373, -919, 71, -42, 40, -548, 1329, 29452, 3386, -921, 71, -42, 40, -546, 1318, 29450, 3399, -923, 72, -41, 40, -544, 1308, 29448, 3411, -925, 72, -41, 40, -542, 1297, 29446, 3424, -928, 72, -41, 40, -541, 1287, 29444, 3437, -930, 72, -41, 40, -539, 1276, 29442, 3450, -932, 72, -41, 39, -537, 1266, 29439, 3463, -935, 73, -41, 39, -535, 1256, 29437, 3476, -937, 73, -41, 39, -533, 1245, 29435, 3489, -939, 73, -41, 39, -531, 1235, 29432, 3502, -941, 73, -41, 39, -529, 1224, 29430, 3515, -944, 74, -41, 39, -527, 1214, 29428, 3528, -946, 74, -41, 39, -525, 1204, 29425, 3541, -948, 74, -41, 38, -523, 1193, 29423, 3554, -951, 74, -41, 38, -521, 1183, 29420, 3567, -953, 74, -40, 38, -520, 1173, 29418, 3580, -955, 75, -40, 38, -518, 1163, 29415, 3593, -958, 75, -40, 38, -516, 1152, 29413, 3606, -960, 75, -40, 38, -514, 1142, 29410, 3619, -962, 75, -40, 38, -512, 1132, 29408, 3632, -965, 75, -40, 37, -510, 1122, 29405, 3645, -967, 76, -40, 37, -508, 1111, 29403, 3658, -969, 76, -40, 37, -506, 1101, 29400, 3671, -971, 76, -40, 37, -504, 1091, 29397, 3684, -974, 76, -40, 37, -502, 1081, 29395, 3698, -976, 76, -40, 37, -501, 1071, 29392, 3711, -978, 77, -40, 37, -499, 1061, 29389, 3724, -981, 77, -40, 36, -497, 1050, 29386, 3737, -983, 77, -39, 36, -495, 1040, 29384, 3750, -985, 77, -39, 36, -493, 1030, 29381, 3764, -988, 77, -39, 36, -491, 1020, 29378, 3777, -990, 78, -39, 36, -489, 1010, 29375, 3790, -992, 78, -39, 36, -488, 1000, 29372, 3803, -995, 78, -39, 36, -486, 990, 29369, 3816, -997, 78, -39, 35, -484, 980, 29367, 3830, -999, 79, -39, 35, -482, 970, 29364, 3843, -1002, 79, -39, 35, -480, 960, 29361, 3856, -1004, 79, -39, 35, -478, 950, 29358, 3870, -1006, 79, -39, 35, -476, 940, 29355, 3883, -1009, 79, -39, 35, -475, 930, 29352, 3896, -1011, 80, -39, 35, -473, 920, 29349, 3910, -1014, 80, -38, 34, -471, 910, 29346, 3923, -1016, 80, -38, 34, -469, 900, 29342, 3936, -1018, 80, -38, 34, -467, 890, 29339, 3950, -1021, 80, -38, 34, -465, 880, 29336, 3963, -1023, 81, -38, 34, -464, 871, 29333, 3976, -1025, 81, -38, 34, -462, 861, 29330, 3990, -1028, 81, -38, 34, -460, 851, 29327, 4003, -1030, 81, -38, 34, -458, 841, 29323, 4017, -1032, 82, -38, 33, -456, 831, 29320, 4030, -1035, 82, -38, 33, -454, 821, 29317, 4044, -1037, 82, -38, 33, -453, 812, 29314, 4057, -1039, 82, -38, 33, -451, 802, 29310, 4071, -1042, 82, -38, 33, -449, 792, 29307, 4084, -1044, 83, -37, 33, -447, 782, 29303, 4098, -1047, 83, -37, 33, -445, 773, 29300, 4111, -1049, 83, -37, 32, -444, 763, 29297, 4125, -1051, 83, -37, 32, -442, 753, 29293, 4138, -1054, 84, -37, 32, -440, 743, 29290, 4152, -1056, 84, -37, 32, -438, 734, 29286, 4165, -1058, 84, -37, 32, -436, 724, 29283, 4179, -1061, 84, -37, 32, -435, 714, 29279, 4193, -1063, 84, -37, 32, -433, 705, 29276, 4206, -1066, 85, -37, 32, -431, 695, 29272, 4220, -1068, 85, -37, 31, -429, 686, 29268, 4234, -1070, 85, -37, 31, -427, 676, 29265, 4247, -1073, 85, -36, 31, -426, 666, 29261, 4261, -1075, 86, -36, 31, -424, 657, 29258, 4274, -1077, 86, -36, 31, -422, 647, 29254, 4288, -1080, 86, -36, 31, -420, 638, 29250, 4302, -1082, 86, -36, 31, -419, 628, 29246, 4316, -1085, 86, -36, 31, -417, 619, 29243, 4329, -1087, 87, -36, 30, -415, 609, 29239, 4343, -1089, 87, -36, 30, -413, 600, 29235, 4357, -1092, 87, -36, 30, -411, 590, 29231, 4371, -1094, 87, -36, 30, -410, 581, 29227, 4384, -1097, 88, -36, 30, -408, 571, 29223, 4398, -1099, 88, -36, 30, -406, 562, 29220, 4412, -1101, 88, -36, 30, -404, 552, 29216, 4426, -1104, 88, -35, 30, -403, 543, 29212, 4440, -1106, 88, -35, 29, -401, 534, 29208, 4453, -1109, 89, -35, 29, -399, 524, 29204, 4467, -1111, 89, -35, 29, -397, 515, 29200, 4481, -1113, 89, -35, 29, -396, 506, 29196, 4495, -1116, 89, -35, 29, -394, 496, 29192, 4509, -1118, 90, -35, 29, -392, 487, 29188, 4523, -1121, 90, -35, 29, -391, 478, 29183, 4537, -1123, 90, -35, 29, -389, 468, 29179, 4551, -1125, 90, -35, 28, -387, 459, 29175, 4564, -1128, 91, -35, 28, -385, 450, 29171, 4578, -1130, 91, -35, 28, -384, 441, 29167, 4592, -1133, 91, -35, 28, -382, 431, 29163, 4606, -1135, 91, -34, 28, -380, 422, 29158, 4620, -1138, 91, -34, 28, -378, 413, 29154, 4634, -1140, 92, -34, 28, -377, 404, 29150, 4648, -1142, 92, -34, 28, -375, 395, 29145, 4662, -1145, 92, -34, 27, -373, 385, 29141, 4676, -1147, 92, -34, 27, -372, 376, 29137, 4690, -1150, 93, -34, 27, -370, 367, 29132, 4704, -1152, 93, -34, 27, -368, 358, 29128, 4718, -1154, 93, -34, 27, -366, 349, 29124, 4732, -1157, 93, -34, 27, -365, 340, 29119, 4747, -1159, 94, -34, 27, -363, 331, 29115, 4761, -1162, 94, -34, 27, -361, 322, 29110, 4775, -1164, 94, -34, 27, -360, 313, 29106, 4789, -1167, 94, -33, 26, -358, 304, 29101, 4803, -1169, 94, -33, 26, -356, 295, 29096, 4817, -1172, 95, -33, 26, -355, 286, 29092, 4831, -1174, 95, -33, 26, -353, 277, 29087, 4845, -1176, 95, -33, 26, -351, 268, 29083, 4860, -1179, 95, -33, 26, -350, 259, 29078, 4874, -1181, 96, -33, 26, -348, 250, 29073, 4888, -1184, 96, -33, 26, -346, 241, 29069, 4902, -1186, 96, -33, 25, -345, 232, 29064, 4916, -1189, 96, -33, 25, -343, 223, 29059, 4931, -1191, 97, -33, 25, -341, 214, 29054, 4945, -1194, 97, -33, 25, -340, 205, 29050, 4959, -1196, 97, -33, 25, -338, 196, 29045, 4973, -1198, 97, -32, 25, -336, 187, 29040, 4988, -1201, 97, -32, 25, -335, 179, 29035, 5002, -1203, 98, -32, 25, -333, 170, 29030, 5016, -1206, 98, -32, 25, -331, 161, 29025, 5031, -1208, 98, -32, 24, -330, 152, 29020, 5045, -1211, 98, -32, 24, -328, 143, 29015, 5059, -1213, 99, -32, 24, -326, 135, 29010, 5074, -1216, 99, -32, 24, -325, 126, 29005, 5088, -1218, 99, -32, 24, -323, 117, 29000, 5102, -1221, 99, -32, 24, -321, 108, 28995, 5117, -1223, 100, -32, 24, -320, 100, 28990, 5131, -1225, 100, -32, 24, -318, 91, 28985, 5146, -1228, 100, -32, 24, -317, 82, 28980, 5160, -1230, 100, -31, 23, -315, 74, 28975, 5174, -1233, 101, -31, 23, -313, 65, 28970, 5189, -1235, 101, -31, 23, -312, 56, 28965, 5203, -1238, 101, -31, 23, -310, 48, 28960, 5218, -1240, 101, -31, 23, -308, 39, 28954, 5232, -1243, 102, -31, 23, -307, 30, 28949, 5247, -1245, 102, -31, 23, -305, 22, 28944, 5261, -1248, 102, -31, 23, -304, 13, 28939, 5276, -1250, 102, -31, 23, -302, 5, 28933, 5290, -1253, 103, -31, 23, -300, -4, 28928, 5305, -1255, 103, -31, 22, -299, -12, 28923, 5319, -1258, 103, -31, 22, -297, -21, 28917, 5334, -1260, 103, -31, 22, -296, -29, 28912, 5348, -1262, 103, -30, 22, -294, -38, 28906, 5363, -1265, 104, -30, 22, -292, -46, 28901, 5378, -1267, 104, -30, 22, -291, -55, 28896, 5392, -1270, 104, -30, 22, -289, -63, 28890, 5407, -1272, 104, -30, 22, -288, -72, 28885, 5421, -1275, 105, -30, 22, -286, -80, 28879, 5436, -1277, 105, -30, 21, -285, -88, 28873, 5451, -1280, 105, -30, 21, -283, -97, 28868, 5465, -1282, 105, -30, 21, -281, -105, 28862, 5480, -1285, 106, -30, 21, -280, -114, 28857, 5495, -1287, 106, -30, 21, -278, -122, 28851, 5509, -1290, 106, -30, 21, -277, -130, 28845, 5524, -1292, 106, -30, 21, -275, -139, 28840, 5539, -1295, 107, -29, 21, -274, -147, 28834, 5553, -1297, 107, -29, 21, -272, -155, 28828, 5568, -1300, 107, -29, 21, -270, -163, 28822, 5583, -1302, 107, -29, 20, -269, -172, 28817, 5598, -1305, 108, -29, 20, -267, -180, 28811, 5612, -1307, 108, -29, 20, -266, -188, 28805, 5627, -1310, 108, -29, 20, -264, -196, 28799, 5642, -1312, 108, -29, 20, -263, -205, 28793, 5657, -1315, 109, -29, 20, -261, -213, 28788, 5672, -1317, 109, -29, 20, -260, -221, 28782, 5686, -1320, 109, -29, 20, -258, -229, 28776, 5701, -1322, 109, -29, 20, -257, -237, 28770, 5716, -1325, 110, -29, 20, -255, -246, 28764, 5731, -1327, 110, -28, 19, -254, -254, 28758, 5746, -1330, 110, -28, 19, -252, -262, 28752, 5761, -1332, 110, -28, 19, -250, -270, 28746, 5776, -1335, 111, -28, 19, -249, -278, 28740, 5791, -1337, 111, -28, 19, -247, -286, 28734, 5805, -1340, 111, -28, 19, -246, -294, 28727, 5820, -1342, 111, -28, 19, -244, -302, 28721, 5835, -1345, 112, -28, 19, -243, -310, 28715, 5850, -1347, 112, -28, 19, -241, -318, 28709, 5865, -1350, 112, -28, 19, -240, -326, 28703, 5880, -1352, 112, -28, 19, -238, -334, 28697, 5895, -1355, 113, -28, 18, -237, -342, 28690, 5910, -1357, 113, -28, 18, -235, -350, 28684, 5925, -1360, 113, -28, 18, -234, -358, 28678, 5940, -1362, 113, -27, 18, -232, -366, 28672, 5955, -1365, 114, -27, 18, -231, -374, 28665, 5970, -1367, 114, -27, 18, -229, -382, 28659, 5985, -1370, 114, -27, 18, -228, -390, 28653, 6000, -1372, 114, -27, 18, -226, -398, 28646, 6015, -1375, 115, -27, 18, -225, -405, 28640, 6030, -1377, 115, -27, 18, -223, -413, 28633, 6045, -1380, 115, -27, 18, -222, -421, 28627, 6061, -1382, 115, -27, 17, -220, -429, 28620, 6076, -1385, 116, -27, 17, -219, -437, 28614, 6091, -1387, 116, -27, 17, -218, -444, 28607, 6106, -1390, 116, -27, 17, -216, -452, 28601, 6121, -1392, 116, -27, 17, -215, -460, 28594, 6136, -1395, 117, -26, 17, -213, -468, 28588, 6151, -1397, 117, -26, 17, -212, -476, 28581, 6166, -1400, 117, -26, 17, -210, -483, 28574, 6182, -1403, 117, -26, 17, -209, -491, 28568, 6197, -1405, 118, -26, 17, -207, -499, 28561, 6212, -1408, 118, -26, 17, -206, -506, 28554, 6227, -1410, 118, -26, 16, -204, -514, 28548, 6242, -1413, 118, -26, 16, -203, -522, 28541, 6258, -1415, 119, -26, 16, -202, -529, 28534, 6273, -1418, 119, -26, 16, -200, -537, 28527, 6288, -1420, 119, -26, 16, -199, -545, 28521, 6303, -1423, 119, -26, 16, -197, -552, 28514, 6319, -1425, 120, -26, 16, -196, -560, 28507, 6334, -1428, 120, -26, 16, -194, -567, 28500, 6349, -1430, 120, -25, 16, -193, -575, 28493, 6365, -1433, 120, -25, 16, -191, -582, 28486, 6380, -1435, 121, -25, 16, -190, -590, 28479, 6395, -1438, 121, -25, 16, -189, -597, 28472, 6410, -1440, 121, -25, 15, -187, -605, 28465, 6426, -1443, 121, -25, 15, -186, -612, 28458, 6441, -1445, 122, -25, 15, -184, -620, 28451, 6457, -1448, 122, -25, 15, -183, -627, 28444, 6472, -1451, 122, -25, 15, -182, -635, 28437, 6487, -1453, 122, -25, 15, -180, -642, 28430, 6503, -1456, 123, -25, 15, -179, -650, 28423, 6518, -1458, 123, -25, 15, -177, -657, 28416, 6533, -1461, 123, -25, 15, -176, -664, 28409, 6549, -1463, 124, -25, 15, -175, -672, 28402, 6564, -1466, 124, -24, 15, -173, -679, 28395, 6580, -1468, 124, -24, 15, -172, -687, 28387, 6595, -1471, 124, -24, 14, -170, -694, 28380, 6611, -1473, 125, -24, 14, -169, -701, 28373, 6626, -1476, 125, -24, 14, -168, -708, 28366, 6642, -1478, 125, -24, 14, -166, -716, 28358, 6657, -1481, 125, -24, 14, -165, -723, 28351, 6673, -1484, 126, -24, 14, -163, -730, 28344, 6688, -1486, 126, -24, 14, -162, -738, 28336, 6704, -1489, 126, -24, 14, -161, -745, 28329, 6719, -1491, 126, -24, 14, -159, -752, 28321, 6735, -1494, 127, -24, 14, -158, -759, 28314, 6750, -1496, 127, -24, 14, -157, -766, 28307, 6766, -1499, 127, -24, 14, -155, -774, 28299, 6781, -1501, 127, -23, 14, -154, -781, 28292, 6797, -1504, 128, -23, 13, -153, -788, 28284, 6813, -1506, 128, -23, 13, -151, -795, 28277, 6828, -1509, 128, -23, 13, -150, -802, 28269, 6844, -1511, 128, -23, 13, -149, -809, 28261, 6859, -1514, 129, -23, 13, -147, -816, 28254, 6875, -1517, 129, -23, 13, -146, -824, 28246, 6891, -1519, 129, -23, 13, -144, -831, 28239, 6906, -1522, 130, -23, 13, -143, -838, 28231, 6922, -1524, 130, -23, 13, -142, -845, 28223, 6938, -1527, 130, -23, 13, -140, -852, 28216, 6953, -1529, 130, -23, 13, -139, -859, 28208, 6969, -1532, 131, -23, 13, -138, -866, 28200, 6985, -1534, 131, -23, 13, -136, -873, 28192, 7001, -1537, 131, -22, 12, -135, -880, 28185, 7016, -1539, 131, -22, 12, -134, -887, 28177, 7032, -1542, 132, -22, 12, -133, -894, 28169, 7048, -1545, 132, -22, 12, -131, -901, 28161, 7063, -1547, 132, -22, 12, -130, -907, 28153, 7079, -1550, 132, -22, 12, -129, -914, 28145, 7095, -1552, 133, -22, 12, -127, -921, 28138, 7111, -1555, 133, -22, 12, -126, -928, 28130, 7127, -1557, 133, -22, 12, -125, -935, 28122, 7142, -1560, 133, -22, 12, -123, -942, 28114, 7158, -1562, 134, -22, 12, -122, -949, 28106, 7174, -1565, 134, -22, 12, -121, -956, 28098, 7190, -1567, 134, -22, 12, -119, -962, 28090, 7206, -1570, 135, -22, 12, -118, -969, 28082, 7221, -1573, 135, -21, 11, -117, -976, 28073, 7237, -1575, 135, -21, 11, -116, -983, 28065, 7253, -1578, 135, -21, 11, -114, -989, 28057, 7269, -1580, 136, -21, 11, -113, -996, 28049, 7285, -1583, 136, -21, 11, -112, -1003, 28041, 7301, -1585, 136, -21, 11, -110, -1010, 28033, 7317, -1588, 136, -21, 11, -109, -1016, 28025, 7333, -1590, 137, -21, 11, -108, -1023, 28016, 7349, -1593, 137, -21, 11, -107, -1030, 28008, 7365, -1595, 137, -21, 11, -105, -1036, 28000, 7380, -1598, 137, -21, 11, -104, -1043, 27992, 7396, -1601, 138, -21, 11, -103, -1050, 27983, 7412, -1603, 138, -21, 11, -102, -1056, 27975, 7428, -1606, 138, -21, 11, -100, -1063, 27967, 7444, -1608, 138, -21, 11, -99, -1070, 27958, 7460, -1611, 139, -20, 10, -98, -1076, 27950, 7476, -1613, 139, -20, 10, -97, -1083, 27941, 7492, -1616, 139, -20, 10, -95, -1089, 27933, 7508, -1618, 140, -20, 10, -94, -1096, 27925, 7524, -1621, 140, -20, 10, -93, -1102, 27916, 7540, -1624, 140, -20, 10, -92, -1109, 27908, 7556, -1626, 140, -20, 10, -90, -1115, 27899, 7572, -1629, 141, -20, 10, -89, -1122, 27891, 7589, -1631, 141, -20, 10, -88, -1128, 27882, 7605, -1634, 141, -20, 10, -87, -1135, 27874, 7621, -1636, 141, -20, 10, -85, -1141, 27865, 7637, -1639, 142, -20, 10, -84, -1148, 27856, 7653, -1641, 142, -20, 10, -83, -1154, 27848, 7669, -1644, 142, -20, 10, -82, -1160, 27839, 7685, -1646, 142, -20, 10, -81, -1167, 27830, 7701, -1649, 143, -19, 9, -79, -1173, 27822, 7717, -1652, 143, -19, 9, -78, -1180, 27813, 7733, -1654, 143, -19, 9, -77, -1186, 27804, 7750, -1657, 144, -19, 9, -76, -1192, 27795, 7766, -1659, 144, -19, 9, -74, -1199, 27787, 7782, -1662, 144, -19, 9, -73, -1205, 27778, 7798, -1664, 144, -19, 9, -72, -1211, 27769, 7814, -1667, 145, -19, 9, -71, -1218, 27760, 7830, -1669, 145, -19, 9, -70, -1224, 27751, 7847, -1672, 145, -19, 9, -68, -1230, 27743, 7863, -1674, 145, -19, 9, -67, -1236, 27734, 7879, -1677, 146, -19, 9, -66, -1243, 27725, 7895, -1680, 146, -19, 9, -65, -1249, 27716, 7912, -1682, 146, -19, 9, -64, -1255, 27707, 7928, -1685, 147, -19, 9, -63, -1261, 27698, 7944, -1687, 147, -18, 9, -61, -1267, 27689, 7960, -1690, 147, -18, 9, -60, -1273, 27680, 7977, -1692, 147, -18, 8, -59, -1280, 27671, 7993, -1695, 148, -18, 8, -58, -1286, 27662, 8009, -1697, 148, -18, 8, -57, -1292, 27653, 8025, -1700, 148, -18, 8, -55, -1298, 27644, 8042, -1703, 148, -18, 8, -54, -1304, 27634, 8058, -1705, 149, -18, 8, -53, -1310, 27625, 8074, -1708, 149, -18, 8, -52, -1316, 27616, 8091, -1710, 149, -18, 8, -51, -1322, 27607, 8107, -1713, 149, -18, 8, -50, -1328, 27598, 8123, -1715, 150, -18, 8, -49, -1334, 27589, 8140, -1718, 150, -18, 8, -47, -1340, 27579, 8156, -1720, 150, -18, 8, -46, -1346, 27570, 8173, -1723, 151, -18, 8, -45, -1352, 27561, 8189, -1725, 151, -18, 8, -44, -1358, 27552, 8205, -1728, 151, -17, 8, -43, -1364, 27542, 8222, -1730, 151, -17, 8, -42, -1370, 27533, 8238, -1733, 152, -17, 8, -40, -1376, 27523, 8255, -1736, 152, -17, 8, -39, -1382, 27514, 8271, -1738, 152, -17, 7, -38, -1388, 27505, 8287, -1741, 152, -17, 7, -37, -1394, 27495, 8304, -1743, 153, -17, 7, -36, -1400, 27486, 8320, -1746, 153, -17, 7, -35, -1406, 27476, 8337, -1748, 153, -17, 7, -34, -1412, 27467, 8353, -1751, 154, -17, 7, -33, -1417, 27457, 8370, -1753, 154, -17, 7, -31, -1423, 27448, 8386, -1756, 154, -17, 7, -30, -1429, 27438, 8403, -1758, 154, -17, 7, -29, -1435, 27429, 8419, -1761, 155, -17, 7, -28, -1441, 27419, 8436, -1763, 155, -17, 7, -27, -1446, 27410, 8452, -1766, 155, -17, 7, -26, -1452, 27400, 8469, -1769, 155, -16, 7, -25, -1458, 27390, 8485, -1771, 156, -16, 7, -24, -1464, 27381, 8502, -1774, 156, -16, 7, -23, -1469, 27371, 8518, -1776, 156, -16, 7, -22, -1475, 27361, 8535, -1779, 157, -16, 7, -20, -1481, 27352, 8552, -1781, 157, -16, 7, -19, -1486, 27342, 8568, -1784, 157, -16, 7, -18, -1492, 27332, 8585, -1786, 157, -16, 6, -17, -1498, 27322, 8601, -1789, 158, -16, 6, -16, -1503, 27313, 8618, -1791, 158, -16, 6, -15, -1509, 27303, 8634, -1794, 158, -16, 6, -14, -1515, 27293, 8651, -1796, 158, -16, 6, -13, -1520, 27283, 8668, -1799, 159, -16, 6, -12, -1526, 27273, 8684, -1801, 159, -16, 6, -11, -1532, 27263, 8701, -1804, 159, -16, 6, -10, -1537, 27254, 8718, -1807, 159, -16, 6, -9, -1543, 27244, 8734, -1809, 160, -16, 6, -8, -1548, 27234, 8751, -1812, 160, -15, 6, -6, -1554, 27224, 8768, -1814, 160, -15, 6, -5, -1559, 27214, 8784, -1817, 161, -15, 6, -4, -1565, 27204, 8801, -1819, 161, -15, 6, -3, -1570, 27194, 8818, -1822, 161, -15, 6, -2, -1576, 27184, 8834, -1824, 161, -15, 6, -1, -1581, 27174, 8851, -1827, 162, -15, 6, 0, -1587, 27164, 8868, -1829, 162, -15, 6, 1, -1592, 27153, 8885, -1832, 162, -15, 6, 2, -1597, 27143, 8901, -1834, 162, -15, 6, 3, -1603, 27133, 8918, -1837, 163, -15, 6, 4, -1608, 27123, 8935, -1839, 163, -15, 6, 5, -1614, 27113, 8952, -1842, 163, -15, 5, 6, -1619, 27103, 8968, -1844, 164, -15, 5, 7, -1624, 27092, 8985, -1847, 164, -15, 5, 8, -1630, 27082, 9002, -1849, 164, -15, 5, 9, -1635, 27072, 9019, -1852, 164, -15, 5, 10, -1640, 27062, 9035, -1854, 165, -14, 5, 11, -1646, 27051, 9052, -1857, 165, -14, 5, 12, -1651, 27041, 9069, -1859, 165, -14, 5, 13, -1656, 27031, 9086, -1862, 165, -14, 5, 14, -1661, 27020, 9103, -1865, 166, -14, 5, 15, -1667, 27010, 9119, -1867, 166, -14, 5, 16, -1672, 27000, 9136, -1870, 166, -14, 5, 17, -1677, 26989, 9153, -1872, 167, -14, 5, 18, -1682, 26979, 9170, -1875, 167, -14, 5, 19, -1688, 26968, 9187, -1877, 167, -14, 5, 20, -1693, 26958, 9204, -1880, 167, -14, 5, 21, -1698, 26948, 9221, -1882, 168, -14, 5, 22, -1703, 26937, 9237, -1885, 168, -14, 5, 23, -1708, 26926, 9254, -1887, 168, -14, 5, 24, -1713, 26916, 9271, -1890, 168, -14, 5, 25, -1719, 26905, 9288, -1892, 169, -14, 5, 26, -1724, 26895, 9305, -1895, 169, -14, 5, 27, -1729, 26884, 9322, -1897, 169, -13, 5, 28, -1734, 26874, 9339, -1900, 170, -13, 4, 29, -1739, 26863, 9356, -1902, 170, -13, 4, 30, -1744, 26852, 9373, -1905, 170, -13, 4, 31, -1749, 26842, 9390, -1907, 170, -13, 4, 32, -1754, 26831, 9407, -1910, 171, -13, 4, 33, -1759, 26820, 9424, -1912, 171, -13, 4, 34, -1764, 26810, 9441, -1915, 171, -13, 4, 35, -1769, 26799, 9458, -1917, 171, -13, 4, 36, -1774, 26788, 9475, -1920, 172, -13, 4, 37, -1779, 26777, 9492, -1922, 172, -13, 4, 38, -1784, 26767, 9509, -1925, 172, -13, 4, 39, -1789, 26756, 9526, -1927, 173, -13, 4, 40, -1794, 26745, 9543, -1930, 173, -13, 4, 41, -1799, 26734, 9560, -1932, 173, -13, 4, 42, -1804, 26723, 9577, -1935, 173, -13, 4, 43, -1809, 26712, 9594, -1937, 174, -13, 4, 44, -1813, 26701, 9611, -1939, 174, -13, 4, 45, -1818, 26690, 9628, -1942, 174, -13, 4, 46, -1823, 26679, 9645, -1944, 174, -12, 4, 46, -1828, 26669, 9662, -1947, 175, -12, 4, 47, -1833, 26658, 9679, -1949, 175, -12, 4, 48, -1838, 26647, 9696, -1952, 175, -12, 4, 49, -1843, 26636, 9713, -1954, 176, -12, 4, 50, -1847, 26625, 9730, -1957, 176, -12, 4, 51, -1852, 26613, 9747, -1959, 176, -12, 4, 52, -1857, 26602, 9764, -1962, 176, -12, 4, 53, -1862, 26591, 9781, -1964, 177, -12, 4, 54, -1866, 26580, 9799, -1967, 177, -12, 3, 55, -1871, 26569, 9816, -1969, 177, -12, 3, 56, -1876, 26558, 9833, -1972, 177, -12, 3, 57, -1880, 26547, 9850, -1974, 178, -12, 3, 58, -1885, 26536, 9867, -1977, 178, -12, 3, 58, -1890, 26524, 9884, -1979, 178, -12, 3, 59, -1895, 26513, 9901, -1982, 178, -12, 3, 60, -1899, 26502, 9919, -1984, 179, -12, 3, 61, -1904, 26491, 9936, -1986, 179, -12, 3, 62, -1908, 26479, 9953, -1989, 179, -11, 3, 63, -1913, 26468, 9970, -1991, 180, -11, 3, 64, -1918, 26457, 9987, -1994, 180, -11, 3, 65, -1922, 26445, 10004, -1996, 180, -11, 3, 66, -1927, 26434, 10022, -1999, 180, -11, 3, 67, -1931, 26423, 10039, -2001, 181, -11, 3, 67, -1936, 26411, 10056, -2004, 181, -11, 3, 68, -1940, 26400, 10073, -2006, 181, -11, 3, 69, -1945, 26389, 10090, -2009, 181, -11, 3, 70, -1949, 26377, 10108, -2011, 182, -11, 3, 71, -1954, 26366, 10125, -2013, 182, -11, 3, 72, -1958, 26354, 10142, -2016, 182, -11, 3, 73, -1963, 26343, 10159, -2018, 183, -11, 3, 74, -1967, 26331, 10177, -2021, 183, -11, 3, 74, -1972, 26320, 10194, -2023, 183, -11, 3, 75, -1976, 26308, 10211, -2026, 183, -11, 3, 76, -1981, 26297, 10228, -2028, 184, -11, 3, 77, -1985, 26285, 10246, -2031, 184, -11, 3, 78, -1990, 26273, 10263, -2033, 184, -11, 3, 79, -1994, 26262, 10280, -2035, 184, -11, 3, 80, -1998, 26250, 10298, -2038, 185, -10, 3, 80, -2003, 26239, 10315, -2040, 185, -10, 3, 81, -2007, 26227, 10332, -2043, 185, -10, 3, 82, -2011, 26215, 10350, -2045, 185, -10, 2, 83, -2016, 26204, 10367, -2048, 186, -10, 2, 84, -2020, 26192, 10384, -2050, 186, -10, 2, 85, -2024, 26180, 10402, -2052, 186, -10, 2, 85, -2029, 26168, 10419, -2055, 187, -10, 2, 86, -2033, 26157, 10436, -2057, 187, -10, 2, 87, -2037, 26145, 10454, -2060, 187, -10, 2, 88, -2041, 26133, 10471, -2062, 187, -10, 2, 89, -2046, 26121, 10488, -2065, 188, -10, 2, 90, -2050, 26109, 10506, -2067, 188, -10, 2, 90, -2054, 26097, 10523, -2069, 188, -10, 2, 91, -2058, 26086, 10540, -2072, 188, -10, 2, 92, -2062, 26074, 10558, -2074, 189, -10, 2, 93, -2067, 26062, 10575, -2077, 189, -10, 2, 94, -2071, 26050, 10593, -2079, 189, -10, 2, 95, -2075, 26038, 10610, -2081, 189, -10, 2, 95, -2079, 26026, 10627, -2084, 190, -10, 2, 96, -2083, 26014, 10645, -2086, 190, -10, 2, 97, -2087, 26002, 10662, -2089, 190, -9, 2, 98, -2091, 25990, 10680, -2091, 191, -9, 2, 99, -2096, 25978, 10697, -2093, 191, -9, 2, 99, -2100, 25966, 10715, -2096, 191, -9, 2, 100, -2104, 25954, 10732, -2098, 191, -9, 2, 101, -2108, 25942, 10749, -2101, 192, -9, 2, 102, -2112, 25930, 10767, -2103, 192, -9, 2, 103, -2116, 25918, 10784, -2105, 192, -9, 2, 103, -2120, 25905, 10802, -2108, 192, -9, 2, 104, -2124, 25893, 10819, -2110, 193, -9, 2, 105, -2128, 25881, 10837, -2113, 193, -9, 2, 106, -2132, 25869, 10854, -2115, 193, -9, 2, 106, -2136, 25857, 10872, -2117, 193, -9, 2, 107, -2140, 25845, 10889, -2120, 194, -9, 2, 108, -2144, 25832, 10907, -2122, 194, -9, 2, 109, -2148, 25820, 10924, -2125, 194, -9, 2, 110, -2152, 25808, 10942, -2127, 194, -9, 2, 110, -2155, 25796, 10959, -2129, 195, -9, 2, 111, -2159, 25783, 10977, -2132, 195, -9, 2, 112, -2163, 25771, 10994, -2134, 195, -9, 2, 113, -2167, 25759, 11012, -2136, 196, -9, 2, 113, -2171, 25746, 11029, -2139, 196, -9, 2, 114, -2175, 25734, 11047, -2141, 196, -8, 1, 115, -2179, 25722, 11064, -2143, 196, -8, 1, 116, -2182, 25709, 11082, -2146, 197, -8, 1, 116, -2186, 25697, 11099, -2148, 197, -8, 1, 117, -2190, 25684, 11117, -2151, 197, -8, 1, 118, -2194, 25672, 11135, -2153, 197, -8, 1, 119, -2198, 25659, 11152, -2155, 198, -8, 1, 119, -2201, 25647, 11170, -2158, 198, -8, 1, 120, -2205, 25634, 11187, -2160, 198, -8, 1, 121, -2209, 25622, 11205, -2162, 198, -8, 1, 122, -2213, 25609, 11222, -2165, 199, -8, 1, 122, -2216, 25597, 11240, -2167, 199, -8, 1, 123, -2220, 25584, 11258, -2169, 199, -8, 1, 124, -2224, 25572, 11275, -2172, 199, -8, 1, 125, -2227, 25559, 11293, -2174, 200, -8, 1, 125, -2231, 25546, 11311, -2176, 200, -8, 1, 126, -2235, 25534, 11328, -2179, 200, -8, 1, 127, -2238, 25521, 11346, -2181, 200, -8, 1, 127, -2242, 25508, 11363, -2183, 201, -8, 1, 128, -2246, 25496, 11381, -2186, 201, -8, 1, 129, -2249, 25483, 11399, -2188, 201, -8, 1, 130, -2253, 25470, 11416, -2190, 202, -8, 1, 130, -2256, 25458, 11434, -2193, 202, -8, 1, 131, -2260, 25445, 11452, -2195, 202, -8, 1, 132, -2264, 25432, 11469, -2197, 202, -7, 1, 132, -2267, 25419, 11487, -2200, 203, -7, 1, 133, -2271, 25407, 11505, -2202, 203, -7, 1, 134, -2274, 25394, 11522, -2204, 203, -7, 1, 135, -2278, 25381, 11540, -2207, 203, -7, 1, 135, -2281, 25368, 11558, -2209, 204, -7, 1, 136, -2285, 25355, 11575, -2211, 204, -7, 1, 137, -2288, 25342, 11593, -2213, 204, -7, 1, 137, -2292, 25329, 11611, -2216, 204, -7, 1, 138, -2295, 25317, 11628, -2218, 205, -7, 1, 139, -2299, 25304, 11646, -2220, 205, -7, 1, 139, -2302, 25291, 11664, -2223, 205, -7, 1, 140, -2305, 25278, 11681, -2225, 205, -7, 1, 141, -2309, 25265, 11699, -2227, 206, -7, 1, 141, -2312, 25252, 11717, -2229, 206, -7, 1, 142, -2316, 25239, 11735, -2232, 206, -7, 1, 143, -2319, 25226, 11752, -2234, 206, -7, 1, 143, -2322, 25213, 11770, -2236, 207, -7, 1, 144, -2326, 25200, 11788, -2239, 207, -7, 1, 145, -2329, 25187, 11806, -2241, 207, -7, 1, 145, -2332, 25173, 11823, -2243, 207, -7, 1, 146, -2336, 25160, 11841, -2245, 208, -7, 1, 147, -2339, 25147, 11859, -2248, 208, -7, 1, 147, -2342, 25134, 11877, -2250, 208, -7, 1, 148, -2346, 25121, 11894, -2252, 208, -7, 1, 149, -2349, 25108, 11912, -2254, 209, -6, 1, 149, -2352, 25095, 11930, -2257, 209, -6, 1, 150, -2355, 25081, 11948, -2259, 209, -6, 1, 151, -2359, 25068, 11965, -2261, 209, -6, 1, 151, -2362, 25055, 11983, -2264, 210, -6, 1, 152, -2365, 25042, 12001, -2266, 210, -6, 1, 153, -2368, 25029, 12019, -2268, 210, -6, 1, 153, -2371, 25015, 12036, -2270, 210, -6, 0, 154, -2375, 25002, 12054, -2272, 211, -6, 0, 154, -2378, 24989, 12072, -2275, 211, -6, 0, 155, -2381, 24975, 12090, -2277, 211, -6, 0, 156, -2384, 24962, 12108, -2279, 211, -6, 0, 156, -2387, 24949, 12126, -2281, 212, -6, 0, 157, -2390, 24935, 12143, -2284, 212, -6, 0, 158, -2393, 24922, 12161, -2286, 212, -6, 0, 158, -2396, 24909, 12179, -2288, 212, -6, 0, 159, -2400, 24895, 12197, -2290, 213, -6, 0, 159, -2403, 24882, 12215, -2293, 213, -6, 0, 160, -2406, 24868, 12232, -2295, 213, -6, 0, 161, -2409, 24855, 12250, -2297, 213, -6, 0, 161, -2412, 24841, 12268, -2299, 214, -6, 0, 162, -2415, 24828, 12286, -2301, 214, -6, 0, 163, -2418, 24814, 12304, -2304, 214, -6, 0, 163, -2421, 24801, 12322, -2306, 214, -6, 0, 164, -2424, 24787, 12340, -2308, 215, -6, 0, 164, -2427, 24774, 12357, -2310, 215, -6, 0, 165, -2430, 24760, 12375, -2312, 215, -6, 0, 166, -2433, 24747, 12393, -2315, 215, -5, 0, 166, -2436, 24733, 12411, -2317, 216, -5, 0, 167, -2439, 24719, 12429, -2319, 216, -5, 0, 167, -2442, 24706, 12447, -2321, 216, -5, 0, 168, -2444, 24692, 12465, -2323, 216, -5, 0, 168, -2447, 24679, 12482, -2326, 217, -5, 0, 169, -2450, 24665, 12500, -2328, 217, -5, 0, 170, -2453, 24651, 12518, -2330, 217, -5, 0, 170, -2456, 24637, 12536, -2332, 217, -5, 0, 171, -2459, 24624, 12554, -2334, 217, -5, 0, 171, -2462, 24610, 12572, -2336, 218, -5, 0, 172, -2465, 24596, 12590, -2339, 218, -5, 0, 173, -2467, 24583, 12608, -2341, 218, -5, 0, 173, -2470, 24569, 12626, -2343, 218, -5, 0, 174, -2473, 24555, 12644, -2345, 219, -5, 0, 174, -2476, 24541, 12661, -2347, 219, -5, 0, 175, -2479, 24527, 12679, -2349, 219, -5, 0, 175, -2481, 24514, 12697, -2351, 219, -5, 0, 176, -2484, 24500, 12715, -2354, 220, -5, 0, 176, -2487, 24486, 12733, -2356, 220, -5, 0, 177, -2490, 24472, 12751, -2358, 220, -5, 0, 178, -2492, 24458, 12769, -2360, 220, -5, 0, 178, -2495, 24444, 12787, -2362, 221, -5, 0, 179, -2498, 24430, 12805, -2364, 221, -5, 0, 179, -2500, 24416, 12823, -2366, 221, -5, 0, 180, -2503, 24402, 12841, -2369, 221, -5, 0, 180, -2506, 24388, 12859, -2371, 222, -5, 0, 181, -2508, 24374, 12877, -2373, 222, -5, 0, 181, -2511, 24360, 12895, -2375, 222, -5, 0, 182, -2514, 24346, 12913, -2377, 222, -5, 0, 183, -2516, 24332, 12931, -2379, 222, -5, 0, 183, -2519, 24318, 12949, -2381, 223, -4, 0, 184, -2521, 24304, 12966, -2383, 223, -4, 0, 184, -2524, 24290, 12984, -2385, 223, -4, 0, 185, -2527, 24276, 13002, -2388, 223, -4, 0, 185, -2529, 24262, 13020, -2390, 224, -4, 0, 186, -2532, 24248, 13038, -2392, 224, -4, 0, 186, -2534, 24234, 13056, -2394, 224, -4, 0, 187, -2537, 24220, 13074, -2396, 224, -4, 0, 187, -2539, 24206, 13092, -2398, 225, -4, 0, 188, -2542, 24191, 13110, -2400, 225, -4, 0, 188, -2544, 24177, 13128, -2402, 225, -4, 0, 189, -2547, 24163, 13146, -2404, 225, -4, 0, 189, -2549, 24149, 13164, -2406, 225, -4, 0, 190, -2552, 24135, 13182, -2408, 226, -4, 0, 190, -2554, 24120, 13200, -2410, 226, -4, 0, 191, -2557, 24106, 13218, -2412, 226, -4, 0, 191, -2559, 24092, 13236, -2414, 226, -4, 0, 192, -2562, 24078, 13254, -2417, 227, -4, 0, 192, -2564, 24063, 13272, -2419, 227, -4, 0, 193, -2566, 24049, 13290, -2421, 227, -4, 0, 193, -2569, 24035, 13308, -2423, 227, -4, 0, 194, -2571, 24020, 13326, -2425, 228, -4, 0, 194, -2574, 24006, 13344, -2427, 228, -4, 0, 195, -2576, 23992, 13362, -2429, 228, -4, 0, 195, -2578, 23977, 13380, -2431, 228, -4, 0, 196, -2581, 23963, 13398, -2433, 228, -4, 0, 196, -2583, 23948, 13416, -2435, 229, -4, 0, 197, -2585, 23934, 13434, -2437, 229, -4, 0, 197, -2588, 23920, 13452, -2439, 229, -4, 0, 198, -2590, 23905, 13470, -2441, 229, -4, 0, 198, -2592, 23891, 13488, -2443, 230, -4, 0, 199, -2594, 23876, 13506, -2445, 230, -4, 0, 199, -2597, 23862, 13525, -2447, 230, -4, 0, 200, -2599, 23847, 13543, -2449, 230, -4, 0, 200, -2601, 23833, 13561, -2451, 230, -3, 0, 201, -2603, 23818, 13579, -2453, 231, -3, 0, 201, -2606, 23804, 13597, -2455, 231, -3, 0, 201, -2608, 23789, 13615, -2457, 231, -3, 0, 202, -2610, 23775, 13633, -2459, 231, -3, 0, 202, -2612, 23760, 13651, -2461, 231, -3, 0, 203, -2614, 23745, 13669, -2463, 232, -3, 0, 203, -2617, 23731, 13687, -2465, 232, -3, 0, 204, -2619, 23716, 13705, -2467, 232, -3, 0, 204, -2621, 23702, 13723, -2469, 232, -3, 0, 205, -2623, 23687, 13741, -2471, 233, -3, 0, 205, -2625, 23672, 13759, -2473, 233, -3, 0, 206, -2627, 23658, 13777, -2475, 233, -3, 0, 206, -2629, 23643, 13795, -2477, 233, -3, 0, 206, -2631, 23628, 13813, -2479, 233, -3, 0, 207, -2634, 23614, 13831, -2480, 234, -3, 0, 207, -2636, 23599, 13849, -2482, 234, -3, 0, 208, -2638, 23584, 13868, -2484, 234, -3, 0, 208, -2640, 23569, 13886, -2486, 234, -3, 0, 209, -2642, 23555, 13904, -2488, 234, -3, 0, 209, -2644, 23540, 13922, -2490, 235, -3, 0, 210, -2646, 23525, 13940, -2492, 235, -3, 0, 210, -2648, 23510, 13958, -2494, 235, -3, 0, 210, -2650, 23495, 13976, -2496, 235, -3, 0, 211, -2652, 23481, 13994, -2498, 236, -3, 0, 211, -2654, 23466, 14012, -2500, 236, -3, 0, 212, -2656, 23451, 14030, -2502, 236, -3, 0, 212, -2658, 23436, 14048, -2504, 236, -3, 0, 212, -2660, 23421, 14066, -2505, 236, -3, 0, 213, -2662, 23406, 14084, -2507, 237, -3, 0, 213, -2664, 23391, 14103, -2509, 237, -3, 0, 214, -2665, 23376, 14121, -2511, 237, -3, 0, 214, -2667, 23361, 14139, -2513, 237, -3, 0, 215, -2669, 23346, 14157, -2515, 237, -3, 0, 215, -2671, 23332, 14175, -2517, 238, -3, 0, 215, -2673, 23317, 14193, -2519, 238, -3, 0, 216, -2675, 23302, 14211, -2521, 238, -3, 0, 216, -2677, 23287, 14229, -2522, 238, -3, 0, 217, -2679, 23272, 14247, -2524, 238, -3, 0, 217, -2680, 23257, 14265, -2526, 239, -3, 0, 217, -2682, 23242, 14283, -2528, 239, -3, 0, 218, -2684, 23226, 14302, -2530, 239, -2, 0, 218, -2686, 23211, 14320, -2532, 239, -2, 0, 219, -2688, 23196, 14338, -2533, 239, -2, 0, 219, -2689, 23181, 14356, -2535, 240, -2, 0, 219, -2691, 23166, 14374, -2537, 240, -2, 0, 220, -2693, 23151, 14392, -2539, 240, -2, 0, 220, -2695, 23136, 14410, -2541, 240, -2, 0, 220, -2697, 23121, 14428, -2543, 240, -2, 0, 221, -2698, 23106, 14446, -2544, 241, -2, 0, 221, -2700, 23090, 14464, -2546, 241, -2, 0, 222, -2702, 23075, 14483, -2548, 241, -2, 0, 222, -2703, 23060, 14501, -2550, 241, -2, 0, 222, -2705, 23045, 14519, -2552, 241, -2, 0, 223, -2707, 23030, 14537, -2553, 242, -2, 0, 223, -2708, 23015, 14555, -2555, 242, -2, 0, 223, -2710, 22999, 14573, -2557, 242, -2, 0, 224, -2712, 22984, 14591, -2559, 242, -2, 0, 224, -2713, 22969, 14609, -2561, 242, -2, 0, 225, -2715, 22954, 14627, -2562, 243, -2, 0, 225, -2717, 22938, 14646, -2564, 243, -2, 0, 225, -2718, 22923, 14664, -2566, 243, -2, 0, 226, -2720, 22908, 14682, -2568, 243, -2, 0, 226, -2721, 22892, 14700, -2569, 243, -2, 0, 226, -2723, 22877, 14718, -2571, 243, -2, 0, 227, -2725, 22862, 14736, -2573, 244, -2, 0, 227, -2726, 22846, 14754, -2575, 244, -2, 0, 227, -2728, 22831, 14772, -2576, 244, -2, 0, 228, -2729, 22816, 14790, -2578, 244, -2, 0, 228, -2731, 22800, 14809, -2580, 244, -2, 0, 228, -2732, 22785, 14827, -2582, 245, -2, 0, 229, -2734, 22769, 14845, -2583, 245, -2, 0, 229, -2735, 22754, 14863, -2585, 245, -2, 0, 229, -2737, 22738, 14881, -2587, 245, -2, 0, 230, -2738, 22723, 14899, -2589, 245, -2, 0, 230, -2740, 22708, 14917, -2590, 245, -2, 0, 230, -2741, 22692, 14935, -2592, 246, -2, 0, 231, -2743, 22677, 14953, -2594, 246, -2, 0, 231, -2744, 22661, 14972, -2595, 246, -2, 0, 231, -2746, 22646, 14990, -2597, 246, -2, 0, 232, -2747, 22630, 15008, -2599, 246, -2, 0, 232, -2748, 22615, 15026, -2600, 247, -2, 0, 232, -2750, 22599, 15044, -2602, 247, -2, 0, 233, -2751, 22583, 15062, -2604, 247, -2, 0, 233, -2753, 22568, 15080, -2606, 247, -2, 0, 233, -2754, 22552, 15098, -2607, 247, -2, 0, 234, -2755, 22537, 15116, -2609, 247, -2, 0, 234, -2757, 22521, 15135, -2611, 248, -2, 0, 234, -2758, 22506, 15153, -2612, 248, -2, 0, 235, -2759, 22490, 15171, -2614, 248, -2, 0, 235, -2761, 22474, 15189, -2615, 248, -2, 0, 235, -2762, 22459, 15207, -2617, 248, -2, 0, 236, -2763, 22443, 15225, -2619, 248, -2, 0, 236, -2765, 22427, 15243, -2620, 249, -1, 0, 236, -2766, 22412, 15261, -2622, 249, -1, 0, 236, -2767, 22396, 15279, -2624, 249, -1, 0, 237, -2769, 22380, 15298, -2625, 249, -1, 0, 237, -2770, 22365, 15316, -2627, 249, -1, 0, 237, -2771, 22349, 15334, -2628, 249, -1, 0, 238, -2772, 22333, 15352, -2630, 250, -1, 0, 238, -2774, 22317, 15370, -2632, 250, -1, 0, 238, -2775, 22302, 15388, -2633, 250, -1, 0, 239, -2776, 22286, 15406, -2635, 250, -1, 0, 239, -2777, 22270, 15424, -2636, 250, -1, 0, 239, -2778, 22254, 15442, -2638, 250, -1, 0, 239, -2780, 22238, 15460, -2640, 251, -1, 0, 240, -2781, 22223, 15479, -2641, 251, -1, 0, 240, -2782, 22207, 15497, -2643, 251, -1, 0, 240, -2783, 22191, 15515, -2644, 251, -1, 0, 241, -2784, 22175, 15533, -2646, 251, -1, 0, 241, -2785, 22159, 15551, -2647, 251, -1, 0, 241, -2786, 22143, 15569, -2649, 252, -1, 0, 241, -2788, 22128, 15587, -2651, 252, -1, 0, 242, -2789, 22112, 15605, -2652, 252, -1, 0, 242, -2790, 22096, 15623, -2654, 252, -1, 0, 242, -2791, 22080, 15641, -2655, 252, -1, 0, 242, -2792, 22064, 15660, -2657, 252, -1, 0, 243, -2793, 22048, 15678, -2658, 253, -1, 0, 243, -2794, 22032, 15696, -2660, 253, -1, 0, 243, -2795, 22016, 15714, -2661, 253, -1, 0, 243, -2796, 22000, 15732, -2663, 253, -1, 0, 244, -2797, 21984, 15750, -2664, 253, -1, 0, 244, -2798, 21968, 15768, -2666, 253, -1, 0, 244, -2799, 21952, 15786, -2667, 253, -1, 0, 244, -2800, 21936, 15804, -2669, 254, -1, 0, 245, -2801, 21920, 15822, -2670, 254, -1, 0, 245, -2802, 21904, 15840, -2672, 254, -1, 0, 245, -2803, 21888, 15858, -2673, 254, -1, 0, 245, -2804, 21872, 15877, -2675, 254, -1, 0, 246, -2805, 21856, 15895, -2676, 254, -1, 0, 246, -2806, 21840, 15913, -2678, 254, -1, 0, 246, -2807, 21824, 15931, -2679, 255, -1, 0, 246, -2808, 21808, 15949, -2680, 255, -1, 0, 247, -2809, 21792, 15967, -2682, 255, -1, 0, 247, -2810, 21776, 15985, -2683, 255, -1, 0, 247, -2811, 21759, 16003, -2685, 255, -1, 0, 247, -2812, 21743, 16021, -2686, 255, -1, 0, 248, -2813, 21727, 16039, -2688, 255, -1, 0, 248, -2813, 21711, 16057, -2689, 256, -1, 0, 248, -2814, 21695, 16075, -2691, 256, -1, 0, 248, -2815, 21679, 16093, -2692, 256, -1, 0, 249, -2816, 21662, 16111, -2693, 256, -1, 0, 249, -2817, 21646, 16130, -2695, 256, -1, 0, 249, -2818, 21630, 16148, -2696, 256, -1, 0, 249, -2819, 21614, 16166, -2698, 256, -1, 0, 249, -2819, 21598, 16184, -2699, 257, -1, 0, 250, -2820, 21581, 16202, -2700, 257, -1, 0, 250, -2821, 21565, 16220, -2702, 257, -1, 0, 250, -2822, 21549, 16238, -2703, 257, -1, 0, 250, -2823, 21533, 16256, -2704, 257, -1, 0, 251, -2823, 21516, 16274, -2706, 257, -1, 0, 251, -2824, 21500, 16292, -2707, 257, -1, 0, 251, -2825, 21484, 16310, -2708, 257, -1, 0, 251, -2826, 21468, 16328, -2710, 258, -1, 0, 251, -2826, 21451, 16346, -2711, 258, -1, 0, 252, -2827, 21435, 16364, -2712, 258, -1, 0, 252, -2828, 21419, 16382, -2714, 258, -1, 0, 252, -2829, 21402, 16400, -2715, 258, -1, 0, 252, -2829, 21386, 16418, -2716, 258, -1, 0, 252, -2830, 21370, 16436, -2718, 258, -1, 0, 253, -2831, 21353, 16454, -2719, 259, -1, 0, 253, -2831, 21337, 16472, -2720, 259, -1, 0, 253, -2832, 21320, 16490, -2722, 259, -1, 0, 253, -2833, 21304, 16508, -2723, 259, -1, 0, 253, -2833, 21288, 16526, -2724, 259, -1, 0, 254, -2834, 21271, 16544, -2726, 259, -1, 0, 254, -2835, 21255, 16562, -2727, 259, -1, 0, 254, -2835, 21238, 16580, -2728, 259, -1, 0, 254, -2836, 21222, 16598, -2729, 259, -1, 0, 254, -2836, 21205, 16616, -2731, 260, -1, 0, 255, -2837, 21189, 16634, -2732, 260, -1, 0, 255, -2838, 21173, 16652, -2733, 260, -1, 0, 255, -2838, 21156, 16670, -2734, 260, -1, 0, 255, -2839, 21140, 16688, -2736, 260, -1, 0, 255, -2839, 21123, 16706, -2737, 260, 0, 0, 255, -2840, 21107, 16724, -2738, 260, 0, 0, 256, -2840, 21090, 16742, -2739, 260, 0, 0, 256, -2841, 21074, 16760, -2740, 260, 0, 0, 256, -2841, 21057, 16778, -2742, 261, 0, 0, 256, -2842, 21040, 16796, -2743, 261, 0, 0, 256, -2842, 21024, 16814, -2744, 261, 0, 0, 256, -2843, 21007, 16832, -2745, 261, 0, 0, 257, -2843, 20991, 16850, -2746, 261, 0, 0, 257, -2844, 20974, 16868, -2748, 261, 0, 0, 257, -2844, 20958, 16886, -2749, 261, 0, 0, 257, -2845, 20941, 16904, -2750, 261, 0, 0, 257, -2845, 20924, 16922, -2751, 261, 0, 0, 257, -2846, 20908, 16940, -2752, 262, 0, 0, 258, -2846, 20891, 16958, -2753, 262, 0, 0, 258, -2847, 20875, 16976, -2755, 262, 0, 0, 258, -2847, 20858, 16994, -2756, 262, 0, 0, 258, -2847, 20841, 17012, -2757, 262, 0, 0, 258, -2848, 20825, 17030, -2758, 262, 0, 0, 258, -2848, 20808, 17048, -2759, 262, 0, 0, 258, -2849, 20791, 17066, -2760, 262, 0, 0, 259, -2849, 20775, 17084, -2761, 262, 0, 0, 259, -2849, 20758, 17101, -2763, 262, 0, 0, 259, -2850, 20741, 17119, -2764, 262, 0, 0, 259, -2850, 20724, 17137, -2765, 263, 0, 0, 259, -2851, 20708, 17155, -2766, 263, 0, 0, 259, -2851, 20691, 17173, -2767, 263, 0, 0, 259, -2851, 20674, 17191, -2768, 263, 0, 0, 260, -2852, 20657, 17209, -2769, 263, 0, 0, 260, -2852, 20641, 17227, -2770, 263, 0, 0, 260, -2852, 20624, 17245, -2771, 263, 0, 0, 260, -2852, 20607, 17263, -2772, 263, 0, 0, 260, -2853, 20590, 17281, -2773, 263, 0, 0, 260, -2853, 20574, 17299, -2774, 263, 0, 0, 260, -2853, 20557, 17316, -2775, 263, 0, 0, 261, -2854, 20540, 17334, -2776, 264, 0, 0, 261, -2854, 20523, 17352, -2777, 264, 0, 0, 261, -2854, 20506, 17370, -2778, 264, 0, 0, 261, -2854, 20490, 17388, -2779, 264, 0, 0, 261, -2855, 20473, 17406, -2780, 264, 0, 0, 261, -2855, 20456, 17424, -2782, 264, 0, 0, 261, -2855, 20439, 17442, -2783, 264, 0, 0, 261, -2855, 20422, 17459, -2783, 264, 0, 0, 262, -2855, 20405, 17477, -2784, 264, 0, 0, 262, -2856, 20388, 17495, -2785, 264, 0, 0, 262, -2856, 20372, 17513, -2786, 264, 0, 0, 262, -2856, 20355, 17531, -2787, 264, 0, 0, 262, -2856, 20338, 17549, -2788, 264, 0, 0, 262, -2856, 20321, 17567, -2789, 264, 0, 0, 262, -2856, 20304, 17584, -2790, 265, 0, 0, 262, -2857, 20287, 17602, -2791, 265, 0, 0, 262, -2857, 20270, 17620, -2792, 265, 0, 0, 263, -2857, 20253, 17638, -2793, 265, 0, 0, 263, -2857, 20236, 17656, -2794, 265, 0, 0, 263, -2857, 20219, 17673, -2795, 265, 0, 0, 263, -2857, 20202, 17691, -2796, 265, 0, 0, 263, -2857, 20185, 17709, -2797, 265, 0, 0, 263, -2857, 20168, 17727, -2798, 265, 0, 0, 263, -2857, 20151, 17745, -2799, 265, 0, 0, 263, -2858, 20134, 17763, -2799, 265, 0, 0, 263, -2858, 20117, 17780, -2800, 265, 0, 0, 263, -2858, 20100, 17798, -2801, 265, 0, 0, 264, -2858, 20083, 17816, -2802, 265, 0, 0, 264, -2858, 20066, 17834, -2803, 265, 0, 0, 264, -2858, 20049, 17851, -2804, 265, 0, 0, 264, -2858, 20032, 17869, -2805, 266, 0, 0, 264, -2858, 20015, 17887, -2806, 266, 0, 0, 264, -2858, 19998, 17905, -2806, 266, 0, 0, 264, -2858, 19981, 17923, -2807, 266, 0, 0, 264, -2858, 19964, 17940, -2808, 266, 0, 0, 264, -2858, 19947, 17958, -2809, 266, 0, 0, 264, -2858, 19930, 17976, -2810, 266, 0, 0, 264, -2858, 19913, 17994, -2810, 266, 0, 0, 264, -2858, 19896, 18011, -2811, 266, 0, 0, 265, -2858, 19878, 18029, -2812, 266, 0, 0, 265, -2858, 19861, 18047, -2813, 266, 0, 0, 265, -2858, 19844, 18064, -2814, 266, 0, 0, 265, -2858, 19827, 18082, -2814, 266, 0, 0, 265, -2857, 19810, 18100, -2815, 266, 0, 0, 265, -2857, 19793, 18118, -2816, 266, 0, 0, 265, -2857, 19776, 18135, -2817, 266, 0, 0, 265, -2857, 19758, 18153, -2817, 266, 0, 0, 265, -2857, 19741, 18171, -2818, 266, 0, 0, 265, -2857, 19724, 18188, -2819, 266, 0, 0, 265, -2857, 19707, 18206, -2820, 266, 0, 0, 265, -2857, 19690, 18224, -2820, 266, 0, 0, 265, -2857, 19673, 18241, -2821, 266, 0, 0, 265, -2856, 19655, 18259, -2822, 266, 0, 0, 265, -2856, 19638, 18277, -2823, 266, 0, 0, 266, -2856, 19621, 18294, -2823, 267, 0, 0, 266, -2856, 19604, 18312, -2824, 267, 0, 0, 266, -2856, 19586, 18330, -2825, 267, 0, 0, 266, -2856, 19569, 18347, -2825, 267, 0, 0, 266, -2855, 19552, 18365, -2826, 267, 0, 0, 266, -2855, 19535, 18383, -2827, 267, 0, 0, 266, -2855, 19518, 18400, -2827, 267, 0, 0, 266, -2855, 19500, 18418, -2828, 267, 0, 0, 266, -2855, 19483, 18436, -2829, 267, 0, 0, 266, -2854, 19466, 18453, -2829, 267, 0, 0, 266, -2854, 19448, 18471, -2830, 267, 0, 0, 266, -2854, 19431, 18488, -2830, 267, 0, 0, 266, -2854, 19414, 18506, -2831, 267, 0, 0, 266, -2853, 19397, 18524, -2832, 267, 0, 0, 266, -2853, 19379, 18541, -2832, 267, 0, 0, 266, -2853, 19362, 18559, -2833, 267, 0, 0, 266, -2853, 19345, 18576, -2833, 267, 0, 0, 266, -2852, 19327, 18594, -2834, 267, 0, 0, 266, -2852, 19310, 18612, -2835, 267, 0, 0, 266, -2852, 19293, 18629, -2835, 267, 0, 0, 266, -2851, 19275, 18647, -2836, 267, 0, 0, 266, -2851, 19258, 18664, -2836, 267, 0, 0, 266, -2851, 19241, 18682, -2837, 267, 0, 0, 267, -2851, 19223, 18699, -2837, 267, 0, 0, 267, -2850, 19206, 18717, -2838, 267, 0, 0, 267, -2850, 19188, 18734, -2839, 267, 0, 0, 267, -2850, 19171, 18752, -2839, 267, 0, 0, 267, -2849, 19154, 18770, -2840, 267, 0, 0, 267, -2849, 19136, 18787, -2840, 267, 0, 0, 267, -2848, 19119, 18805, -2841, 267, 0, 0, 267, -2848, 19101, 18822, -2841, 267, 0, 0, 267, -2848, 19084, 18840, -2842, 267, 0, 0, 267, -2847, 19067, 18857, -2842, 267, 0, 0, 267, -2847, 19049, 18875, -2843, 267, 0, 0, 267, -2846, 19032, 18892, -2843, 267, 0, 0, 267, -2846, 19014, 18910, -2843, 267, 0, 0, 267, -2846, 18997, 18927, -2844, 267, 0, 0, 267, -2845, 18979, 18944, -2844, 267, 0, 0, 267, -2845, 18962, 18962, -2845, 267, 0, 0, 267, -2844, 18944, 18979, -2845, 267, 0, 0, 267, -2844, 18927, 18997, -2846, 267, 0, 0, 267, -2843, 18910, 19014, -2846, 267, 0, 0, 267, -2843, 18892, 19032, -2846, 267, 0, 0, 267, -2843, 18875, 19049, -2847, 267, 0, 0, 267, -2842, 18857, 19067, -2847, 267, 0, 0, 267, -2842, 18840, 19084, -2848, 267, 0, 0, 267, -2841, 18822, 19101, -2848, 267, 0, 0, 267, -2841, 18805, 19119, -2848, 267, 0, 0, 267, -2840, 18787, 19136, -2849, 267, 0, 0, 267, -2840, 18770, 19154, -2849, 267, 0, 0, 267, -2839, 18752, 19171, -2850, 267, 0, 0, 267, -2839, 18734, 19188, -2850, 267, 0, 0, 267, -2838, 18717, 19206, -2850, 267, 0, 0, 267, -2837, 18699, 19223, -2851, 267, 0, 0, 267, -2837, 18682, 19241, -2851, 266, 0, 0, 267, -2836, 18664, 19258, -2851, 266, 0, 0, 267, -2836, 18647, 19275, -2851, 266, 0, 0, 267, -2835, 18629, 19293, -2852, 266, 0, 0, 267, -2835, 18612, 19310, -2852, 266, 0, 0, 267, -2834, 18594, 19327, -2852, 266, 0, 0, 267, -2833, 18576, 19345, -2853, 266, 0, 0, 267, -2833, 18559, 19362, -2853, 266, 0, 0, 267, -2832, 18541, 19379, -2853, 266, 0, 0, 267, -2832, 18524, 19397, -2853, 266, 0, 0, 267, -2831, 18506, 19414, -2854, 266, 0, 0, 267, -2830, 18488, 19431, -2854, 266, 0, 0, 267, -2830, 18471, 19448, -2854, 266, 0, 0, 267, -2829, 18453, 19466, -2854, 266, 0, 0, 267, -2829, 18436, 19483, -2855, 266, 0, 0, 267, -2828, 18418, 19500, -2855, 266, 0, 0, 267, -2827, 18400, 19518, -2855, 266, 0, 0, 267, -2827, 18383, 19535, -2855, 266, 0, 0, 267, -2826, 18365, 19552, -2855, 266, 0, 0, 267, -2825, 18347, 19569, -2856, 266, 0, 0, 267, -2825, 18330, 19586, -2856, 266, 0, 0, 267, -2824, 18312, 19604, -2856, 266, 0, 0, 267, -2823, 18294, 19621, -2856, 266, 0, 0, 266, -2823, 18277, 19638, -2856, 265, 0, 0, 266, -2822, 18259, 19655, -2856, 265, 0, 0, 266, -2821, 18241, 19673, -2857, 265, 0, 0, 266, -2820, 18224, 19690, -2857, 265, 0, 0, 266, -2820, 18206, 19707, -2857, 265, 0, 0, 266, -2819, 18188, 19724, -2857, 265, 0, 0, 266, -2818, 18171, 19741, -2857, 265, 0, 0, 266, -2817, 18153, 19758, -2857, 265, 0, 0, 266, -2817, 18135, 19776, -2857, 265, 0, 0, 266, -2816, 18118, 19793, -2857, 265, 0, 0, 266, -2815, 18100, 19810, -2857, 265, 0, 0, 266, -2814, 18082, 19827, -2858, 265, 0, 0, 266, -2814, 18064, 19844, -2858, 265, 0, 0, 266, -2813, 18047, 19861, -2858, 265, 0, 0, 266, -2812, 18029, 19878, -2858, 265, 0, 0, 266, -2811, 18011, 19896, -2858, 264, 0, 0, 266, -2810, 17994, 19913, -2858, 264, 0, 0, 266, -2810, 17976, 19930, -2858, 264, 0, 0, 266, -2809, 17958, 19947, -2858, 264, 0, 0, 266, -2808, 17940, 19964, -2858, 264, 0, 0, 266, -2807, 17923, 19981, -2858, 264, 0, 0, 266, -2806, 17905, 19998, -2858, 264, 0, 0, 266, -2806, 17887, 20015, -2858, 264, 0, 0, 266, -2805, 17869, 20032, -2858, 264, 0, 0, 265, -2804, 17851, 20049, -2858, 264, 0, 0, 265, -2803, 17834, 20066, -2858, 264, 0, 0, 265, -2802, 17816, 20083, -2858, 264, 0, 0, 265, -2801, 17798, 20100, -2858, 263, 0, 0, 265, -2800, 17780, 20117, -2858, 263, 0, 0, 265, -2799, 17763, 20134, -2858, 263, 0, 0, 265, -2799, 17745, 20151, -2857, 263, 0, 0, 265, -2798, 17727, 20168, -2857, 263, 0, 0, 265, -2797, 17709, 20185, -2857, 263, 0, 0, 265, -2796, 17691, 20202, -2857, 263, 0, 0, 265, -2795, 17673, 20219, -2857, 263, 0, 0, 265, -2794, 17656, 20236, -2857, 263, 0, 0, 265, -2793, 17638, 20253, -2857, 263, 0, 0, 265, -2792, 17620, 20270, -2857, 262, 0, 0, 265, -2791, 17602, 20287, -2857, 262, 0, 0, 265, -2790, 17584, 20304, -2856, 262, 0, 0, 264, -2789, 17567, 20321, -2856, 262, 0, 0, 264, -2788, 17549, 20338, -2856, 262, 0, 0, 264, -2787, 17531, 20355, -2856, 262, 0, 0, 264, -2786, 17513, 20372, -2856, 262, 0, 0, 264, -2785, 17495, 20388, -2856, 262, 0, 0, 264, -2784, 17477, 20405, -2855, 262, 0, 0, 264, -2783, 17459, 20422, -2855, 261, 0, 0, 264, -2783, 17442, 20439, -2855, 261, 0, 0, 264, -2782, 17424, 20456, -2855, 261, 0, 0, 264, -2780, 17406, 20473, -2855, 261, 0, 0, 264, -2779, 17388, 20490, -2854, 261, 0, 0, 264, -2778, 17370, 20506, -2854, 261, 0, 0, 264, -2777, 17352, 20523, -2854, 261, 0, 0, 264, -2776, 17334, 20540, -2854, 261, 0, 0, 263, -2775, 17316, 20557, -2853, 260, 0, 0, 263, -2774, 17299, 20574, -2853, 260, 0, 0, 263, -2773, 17281, 20590, -2853, 260, 0, 0, 263, -2772, 17263, 20607, -2852, 260, 0, 0, 263, -2771, 17245, 20624, -2852, 260, 0, 0, 263, -2770, 17227, 20641, -2852, 260, 0, 0, 263, -2769, 17209, 20657, -2852, 260, 0, 0, 263, -2768, 17191, 20674, -2851, 259, 0, 0, 263, -2767, 17173, 20691, -2851, 259, 0, 0, 263, -2766, 17155, 20708, -2851, 259, 0, 0, 263, -2765, 17137, 20724, -2850, 259, 0, 0, 262, -2764, 17119, 20741, -2850, 259, 0, 0, 262, -2763, 17101, 20758, -2849, 259, 0, 0, 262, -2761, 17084, 20775, -2849, 259, 0, 0, 262, -2760, 17066, 20791, -2849, 258, 0, 0, 262, -2759, 17048, 20808, -2848, 258, 0, 0, 262, -2758, 17030, 20825, -2848, 258, 0, 0, 262, -2757, 17012, 20841, -2847, 258, 0, 0, 262, -2756, 16994, 20858, -2847, 258, 0, 0, 262, -2755, 16976, 20875, -2847, 258, 0, 0, 262, -2753, 16958, 20891, -2846, 258, 0, 0, 262, -2752, 16940, 20908, -2846, 257, 0, 0, 261, -2751, 16922, 20924, -2845, 257, 0, 0, 261, -2750, 16904, 20941, -2845, 257, 0, 0, 261, -2749, 16886, 20958, -2844, 257, 0, 0, 261, -2748, 16868, 20974, -2844, 257, 0, 0, 261, -2746, 16850, 20991, -2843, 257, 0, 0, 261, -2745, 16832, 21007, -2843, 256, 0, 0, 261, -2744, 16814, 21024, -2842, 256, 0, 0, 261, -2743, 16796, 21040, -2842, 256, 0, 0, 261, -2742, 16778, 21057, -2841, 256, 0, 0, 260, -2740, 16760, 21074, -2841, 256, 0, 0, 260, -2739, 16742, 21090, -2840, 256, 0, 0, 260, -2738, 16724, 21107, -2840, 255, 0, 0, 260, -2737, 16706, 21123, -2839, 255, 0, -1, 260, -2736, 16688, 21140, -2839, 255, 0, -1, 260, -2734, 16670, 21156, -2838, 255, 0, -1, 260, -2733, 16652, 21173, -2838, 255, 0, -1, 260, -2732, 16634, 21189, -2837, 255, 0, -1, 260, -2731, 16616, 21205, -2836, 254, 0, -1, 259, -2729, 16598, 21222, -2836, 254, 0, -1, 259, -2728, 16580, 21238, -2835, 254, 0, -1, 259, -2727, 16562, 21255, -2835, 254, 0, -1, 259, -2726, 16544, 21271, -2834, 254, 0, -1, 259, -2724, 16526, 21288, -2833, 253, 0, -1, 259, -2723, 16508, 21304, -2833, 253, 0, -1, 259, -2722, 16490, 21320, -2832, 253, 0, -1, 259, -2720, 16472, 21337, -2831, 253, 0, -1, 259, -2719, 16454, 21353, -2831, 253, 0, -1, 258, -2718, 16436, 21370, -2830, 252, 0, -1, 258, -2716, 16418, 21386, -2829, 252, 0, -1, 258, -2715, 16400, 21402, -2829, 252, 0, -1, 258, -2714, 16382, 21419, -2828, 252, 0, -1, 258, -2712, 16364, 21435, -2827, 252, 0, -1, 258, -2711, 16346, 21451, -2826, 251, 0, -1, 258, -2710, 16328, 21468, -2826, 251, 0, -1, 257, -2708, 16310, 21484, -2825, 251, 0, -1, 257, -2707, 16292, 21500, -2824, 251, 0, -1, 257, -2706, 16274, 21516, -2823, 251, 0, -1, 257, -2704, 16256, 21533, -2823, 250, 0, -1, 257, -2703, 16238, 21549, -2822, 250, 0, -1, 257, -2702, 16220, 21565, -2821, 250, 0, -1, 257, -2700, 16202, 21581, -2820, 250, 0, -1, 257, -2699, 16184, 21598, -2819, 249, 0, -1, 256, -2698, 16166, 21614, -2819, 249, 0, -1, 256, -2696, 16148, 21630, -2818, 249, 0, -1, 256, -2695, 16130, 21646, -2817, 249, 0, -1, 256, -2693, 16111, 21662, -2816, 249, 0, -1, 256, -2692, 16093, 21679, -2815, 248, 0, -1, 256, -2691, 16075, 21695, -2814, 248, 0, -1, 256, -2689, 16057, 21711, -2813, 248, 0, -1, 255, -2688, 16039, 21727, -2813, 248, 0, -1, 255, -2686, 16021, 21743, -2812, 247, 0, -1, 255, -2685, 16003, 21759, -2811, 247, 0, -1, 255, -2683, 15985, 21776, -2810, 247, 0, -1, 255, -2682, 15967, 21792, -2809, 247, 0, -1, 255, -2680, 15949, 21808, -2808, 246, 0, -1, 255, -2679, 15931, 21824, -2807, 246, 0, -1, 254, -2678, 15913, 21840, -2806, 246, 0, -1, 254, -2676, 15895, 21856, -2805, 246, 0, -1, 254, -2675, 15877, 21872, -2804, 245, 0, -1, 254, -2673, 15858, 21888, -2803, 245, 0, -1, 254, -2672, 15840, 21904, -2802, 245, 0, -1, 254, -2670, 15822, 21920, -2801, 245, 0, -1, 254, -2669, 15804, 21936, -2800, 244, 0, -1, 253, -2667, 15786, 21952, -2799, 244, 0, -1, 253, -2666, 15768, 21968, -2798, 244, 0, -1, 253, -2664, 15750, 21984, -2797, 244, 0, -1, 253, -2663, 15732, 22000, -2796, 243, 0, -1, 253, -2661, 15714, 22016, -2795, 243, 0, -1, 253, -2660, 15696, 22032, -2794, 243, 0, -1, 253, -2658, 15678, 22048, -2793, 243, 0, -1, 252, -2657, 15660, 22064, -2792, 242, 0, -1, 252, -2655, 15641, 22080, -2791, 242, 0, -1, 252, -2654, 15623, 22096, -2790, 242, 0, -1, 252, -2652, 15605, 22112, -2789, 242, 0, -1, 252, -2651, 15587, 22128, -2788, 241, 0, -1, 252, -2649, 15569, 22143, -2786, 241, 0, -1, 251, -2647, 15551, 22159, -2785, 241, 0, -1, 251, -2646, 15533, 22175, -2784, 241, 0, -1, 251, -2644, 15515, 22191, -2783, 240, 0, -1, 251, -2643, 15497, 22207, -2782, 240, 0, -1, 251, -2641, 15479, 22223, -2781, 240, 0, -1, 251, -2640, 15460, 22238, -2780, 239, 0, -1, 250, -2638, 15442, 22254, -2778, 239, 0, -1, 250, -2636, 15424, 22270, -2777, 239, 0, -1, 250, -2635, 15406, 22286, -2776, 239, 0, -1, 250, -2633, 15388, 22302, -2775, 238, 0, -1, 250, -2632, 15370, 22317, -2774, 238, 0, -1, 250, -2630, 15352, 22333, -2772, 238, 0, -1, 249, -2628, 15334, 22349, -2771, 237, 0, -1, 249, -2627, 15316, 22365, -2770, 237, 0, -1, 249, -2625, 15298, 22380, -2769, 237, 0, -1, 249, -2624, 15279, 22396, -2767, 236, 0, -1, 249, -2622, 15261, 22412, -2766, 236, 0, -1, 249, -2620, 15243, 22427, -2765, 236, 0, -2, 248, -2619, 15225, 22443, -2763, 236, 0, -2, 248, -2617, 15207, 22459, -2762, 235, 0, -2, 248, -2615, 15189, 22474, -2761, 235, 0, -2, 248, -2614, 15171, 22490, -2759, 235, 0, -2, 248, -2612, 15153, 22506, -2758, 234, 0, -2, 248, -2611, 15135, 22521, -2757, 234, 0, -2, 247, -2609, 15116, 22537, -2755, 234, 0, -2, 247, -2607, 15098, 22552, -2754, 233, 0, -2, 247, -2606, 15080, 22568, -2753, 233, 0, -2, 247, -2604, 15062, 22583, -2751, 233, 0, -2, 247, -2602, 15044, 22599, -2750, 232, 0, -2, 247, -2600, 15026, 22615, -2748, 232, 0, -2, 246, -2599, 15008, 22630, -2747, 232, 0, -2, 246, -2597, 14990, 22646, -2746, 231, 0, -2, 246, -2595, 14972, 22661, -2744, 231, 0, -2, 246, -2594, 14953, 22677, -2743, 231, 0, -2, 246, -2592, 14935, 22692, -2741, 230, 0, -2, 245, -2590, 14917, 22708, -2740, 230, 0, -2, 245, -2589, 14899, 22723, -2738, 230, 0, -2, 245, -2587, 14881, 22738, -2737, 229, 0, -2, 245, -2585, 14863, 22754, -2735, 229, 0, -2, 245, -2583, 14845, 22769, -2734, 229, 0, -2, 245, -2582, 14827, 22785, -2732, 228, 0, -2, 244, -2580, 14809, 22800, -2731, 228, 0, -2, 244, -2578, 14790, 22816, -2729, 228, 0, -2, 244, -2576, 14772, 22831, -2728, 227, 0, -2, 244, -2575, 14754, 22846, -2726, 227, 0, -2, 244, -2573, 14736, 22862, -2725, 227, 0, -2, 243, -2571, 14718, 22877, -2723, 226, 0, -2, 243, -2569, 14700, 22892, -2721, 226, 0, -2, 243, -2568, 14682, 22908, -2720, 226, 0, -2, 243, -2566, 14664, 22923, -2718, 225, 0, -2, 243, -2564, 14646, 22938, -2717, 225, 0, -2, 243, -2562, 14627, 22954, -2715, 225, 0, -2, 242, -2561, 14609, 22969, -2713, 224, 0, -2, 242, -2559, 14591, 22984, -2712, 224, 0, -2, 242, -2557, 14573, 22999, -2710, 223, 0, -2, 242, -2555, 14555, 23015, -2708, 223, 0, -2, 242, -2553, 14537, 23030, -2707, 223, 0, -2, 241, -2552, 14519, 23045, -2705, 222, 0, -2, 241, -2550, 14501, 23060, -2703, 222, 0, -2, 241, -2548, 14483, 23075, -2702, 222, 0, -2, 241, -2546, 14464, 23090, -2700, 221, 0, -2, 241, -2544, 14446, 23106, -2698, 221, 0, -2, 240, -2543, 14428, 23121, -2697, 220, 0, -2, 240, -2541, 14410, 23136, -2695, 220, 0, -2, 240, -2539, 14392, 23151, -2693, 220, 0, -2, 240, -2537, 14374, 23166, -2691, 219, 0, -2, 240, -2535, 14356, 23181, -2689, 219, 0, -2, 239, -2533, 14338, 23196, -2688, 219, 0, -2, 239, -2532, 14320, 23211, -2686, 218, 0, -2, 239, -2530, 14302, 23226, -2684, 218, 0, -3, 239, -2528, 14283, 23242, -2682, 217, 0, -3, 239, -2526, 14265, 23257, -2680, 217, 0, -3, 238, -2524, 14247, 23272, -2679, 217, 0, -3, 238, -2522, 14229, 23287, -2677, 216, 0, -3, 238, -2521, 14211, 23302, -2675, 216, 0, -3, 238, -2519, 14193, 23317, -2673, 215, 0, -3, 238, -2517, 14175, 23332, -2671, 215, 0, -3, 237, -2515, 14157, 23346, -2669, 215, 0, -3, 237, -2513, 14139, 23361, -2667, 214, 0, -3, 237, -2511, 14121, 23376, -2665, 214, 0, -3, 237, -2509, 14103, 23391, -2664, 213, 0, -3, 237, -2507, 14084, 23406, -2662, 213, 0, -3, 236, -2505, 14066, 23421, -2660, 212, 0, -3, 236, -2504, 14048, 23436, -2658, 212, 0, -3, 236, -2502, 14030, 23451, -2656, 212, 0, -3, 236, -2500, 14012, 23466, -2654, 211, 0, -3, 236, -2498, 13994, 23481, -2652, 211, 0, -3, 235, -2496, 13976, 23495, -2650, 210, 0, -3, 235, -2494, 13958, 23510, -2648, 210, 0, -3, 235, -2492, 13940, 23525, -2646, 210, 0, -3, 235, -2490, 13922, 23540, -2644, 209, 0, -3, 234, -2488, 13904, 23555, -2642, 209, 0, -3, 234, -2486, 13886, 23569, -2640, 208, 0, -3, 234, -2484, 13868, 23584, -2638, 208, 0, -3, 234, -2482, 13849, 23599, -2636, 207, 0, -3, 234, -2480, 13831, 23614, -2634, 207, 0, -3, 233, -2479, 13813, 23628, -2631, 206, 0, -3, 233, -2477, 13795, 23643, -2629, 206, 0, -3, 233, -2475, 13777, 23658, -2627, 206, 0, -3, 233, -2473, 13759, 23672, -2625, 205, 0, -3, 233, -2471, 13741, 23687, -2623, 205, 0, -3, 232, -2469, 13723, 23702, -2621, 204, 0, -3, 232, -2467, 13705, 23716, -2619, 204, 0, -3, 232, -2465, 13687, 23731, -2617, 203, 0, -3, 232, -2463, 13669, 23745, -2614, 203, 0, -3, 231, -2461, 13651, 23760, -2612, 202, 0, -3, 231, -2459, 13633, 23775, -2610, 202, 0, -3, 231, -2457, 13615, 23789, -2608, 201, 0, -3, 231, -2455, 13597, 23804, -2606, 201, 0, -3, 231, -2453, 13579, 23818, -2603, 201, 0, -3, 230, -2451, 13561, 23833, -2601, 200, 0, -4, 230, -2449, 13543, 23847, -2599, 200, 0, -4, 230, -2447, 13525, 23862, -2597, 199, 0, -4, 230, -2445, 13506, 23876, -2594, 199, 0, -4, 230, -2443, 13488, 23891, -2592, 198, 0, -4, 229, -2441, 13470, 23905, -2590, 198, 0, -4, 229, -2439, 13452, 23920, -2588, 197, 0, -4, 229, -2437, 13434, 23934, -2585, 197, 0, -4, 229, -2435, 13416, 23948, -2583, 196, 0, -4, 228, -2433, 13398, 23963, -2581, 196, 0, -4, 228, -2431, 13380, 23977, -2578, 195, 0, -4, 228, -2429, 13362, 23992, -2576, 195, 0, -4, 228, -2427, 13344, 24006, -2574, 194, 0, -4, 228, -2425, 13326, 24020, -2571, 194, 0, -4, 227, -2423, 13308, 24035, -2569, 193, 0, -4, 227, -2421, 13290, 24049, -2566, 193, 0, -4, 227, -2419, 13272, 24063, -2564, 192, 0, -4, 227, -2417, 13254, 24078, -2562, 192, 0, -4, 226, -2414, 13236, 24092, -2559, 191, 0, -4, 226, -2412, 13218, 24106, -2557, 191, 0, -4, 226, -2410, 13200, 24120, -2554, 190, 0, -4, 226, -2408, 13182, 24135, -2552, 190, 0, -4, 225, -2406, 13164, 24149, -2549, 189, 0, -4, 225, -2404, 13146, 24163, -2547, 189, 0, -4, 225, -2402, 13128, 24177, -2544, 188, 0, -4, 225, -2400, 13110, 24191, -2542, 188, 0, -4, 225, -2398, 13092, 24206, -2539, 187, 0, -4, 224, -2396, 13074, 24220, -2537, 187, 0, -4, 224, -2394, 13056, 24234, -2534, 186, 0, -4, 224, -2392, 13038, 24248, -2532, 186, 0, -4, 224, -2390, 13020, 24262, -2529, 185, 0, -4, 223, -2388, 13002, 24276, -2527, 185, 0, -4, 223, -2385, 12984, 24290, -2524, 184, 0, -4, 223, -2383, 12966, 24304, -2521, 184, 0, -4, 223, -2381, 12949, 24318, -2519, 183, 0, -5, 222, -2379, 12931, 24332, -2516, 183, 0, -5, 222, -2377, 12913, 24346, -2514, 182, 0, -5, 222, -2375, 12895, 24360, -2511, 181, 0, -5, 222, -2373, 12877, 24374, -2508, 181, 0, -5, 222, -2371, 12859, 24388, -2506, 180, 0, -5, 221, -2369, 12841, 24402, -2503, 180, 0, -5, 221, -2366, 12823, 24416, -2500, 179, 0, -5, 221, -2364, 12805, 24430, -2498, 179, 0, -5, 221, -2362, 12787, 24444, -2495, 178, 0, -5, 220, -2360, 12769, 24458, -2492, 178, 0, -5, 220, -2358, 12751, 24472, -2490, 177, 0, -5, 220, -2356, 12733, 24486, -2487, 176, 0, -5, 220, -2354, 12715, 24500, -2484, 176, 0, -5, 219, -2351, 12697, 24514, -2481, 175, 0, -5, 219, -2349, 12679, 24527, -2479, 175, 0, -5, 219, -2347, 12661, 24541, -2476, 174, 0, -5, 219, -2345, 12644, 24555, -2473, 174, 0, -5, 218, -2343, 12626, 24569, -2470, 173, 0, -5, 218, -2341, 12608, 24583, -2467, 173, 0, -5, 218, -2339, 12590, 24596, -2465, 172, 0, -5, 218, -2336, 12572, 24610, -2462, 171, 0, -5, 217, -2334, 12554, 24624, -2459, 171, 0, -5, 217, -2332, 12536, 24637, -2456, 170, 0, -5, 217, -2330, 12518, 24651, -2453, 170, 0, -5, 217, -2328, 12500, 24665, -2450, 169, 0, -5, 217, -2326, 12482, 24679, -2447, 168, 0, -5, 216, -2323, 12465, 24692, -2444, 168, 0, -5, 216, -2321, 12447, 24706, -2442, 167, 0, -5, 216, -2319, 12429, 24719, -2439, 167, 0, -5, 216, -2317, 12411, 24733, -2436, 166, 0, -5, 215, -2315, 12393, 24747, -2433, 166, 0, -6, 215, -2312, 12375, 24760, -2430, 165, 0, -6, 215, -2310, 12357, 24774, -2427, 164, 0, -6, 215, -2308, 12340, 24787, -2424, 164, 0, -6, 214, -2306, 12322, 24801, -2421, 163, 0, -6, 214, -2304, 12304, 24814, -2418, 163, 0, -6, 214, -2301, 12286, 24828, -2415, 162, 0, -6, 214, -2299, 12268, 24841, -2412, 161, 0, -6, 213, -2297, 12250, 24855, -2409, 161, 0, -6, 213, -2295, 12232, 24868, -2406, 160, 0, -6, 213, -2293, 12215, 24882, -2403, 159, 0, -6, 213, -2290, 12197, 24895, -2400, 159, 0, -6, 212, -2288, 12179, 24909, -2396, 158, 0, -6, 212, -2286, 12161, 24922, -2393, 158, 0, -6, 212, -2284, 12143, 24935, -2390, 157, 0, -6, 212, -2281, 12126, 24949, -2387, 156, 0, -6, 211, -2279, 12108, 24962, -2384, 156, 0, -6, 211, -2277, 12090, 24975, -2381, 155, 0, -6, 211, -2275, 12072, 24989, -2378, 154, 0, -6, 211, -2272, 12054, 25002, -2375, 154, 0, -6, 210, -2270, 12036, 25015, -2371, 153, 1, -6, 210, -2268, 12019, 25029, -2368, 153, 1, -6, 210, -2266, 12001, 25042, -2365, 152, 1, -6, 210, -2264, 11983, 25055, -2362, 151, 1, -6, 209, -2261, 11965, 25068, -2359, 151, 1, -6, 209, -2259, 11948, 25081, -2355, 150, 1, -6, 209, -2257, 11930, 25095, -2352, 149, 1, -6, 209, -2254, 11912, 25108, -2349, 149, 1, -7, 208, -2252, 11894, 25121, -2346, 148, 1, -7, 208, -2250, 11877, 25134, -2342, 147, 1, -7, 208, -2248, 11859, 25147, -2339, 147, 1, -7, 208, -2245, 11841, 25160, -2336, 146, 1, -7, 207, -2243, 11823, 25173, -2332, 145, 1, -7, 207, -2241, 11806, 25187, -2329, 145, 1, -7, 207, -2239, 11788, 25200, -2326, 144, 1, -7, 207, -2236, 11770, 25213, -2322, 143, 1, -7, 206, -2234, 11752, 25226, -2319, 143, 1, -7, 206, -2232, 11735, 25239, -2316, 142, 1, -7, 206, -2229, 11717, 25252, -2312, 141, 1, -7, 206, -2227, 11699, 25265, -2309, 141, 1, -7, 205, -2225, 11681, 25278, -2305, 140, 1, -7, 205, -2223, 11664, 25291, -2302, 139, 1, -7, 205, -2220, 11646, 25304, -2299, 139, 1, -7, 205, -2218, 11628, 25317, -2295, 138, 1, -7, 204, -2216, 11611, 25329, -2292, 137, 1, -7, 204, -2213, 11593, 25342, -2288, 137, 1, -7, 204, -2211, 11575, 25355, -2285, 136, 1, -7, 204, -2209, 11558, 25368, -2281, 135, 1, -7, 203, -2207, 11540, 25381, -2278, 135, 1, -7, 203, -2204, 11522, 25394, -2274, 134, 1, -7, 203, -2202, 11505, 25407, -2271, 133, 1, -7, 203, -2200, 11487, 25419, -2267, 132, 1, -7, 202, -2197, 11469, 25432, -2264, 132, 1, -8, 202, -2195, 11452, 25445, -2260, 131, 1, -8, 202, -2193, 11434, 25458, -2256, 130, 1, -8, 202, -2190, 11416, 25470, -2253, 130, 1, -8, 201, -2188, 11399, 25483, -2249, 129, 1, -8, 201, -2186, 11381, 25496, -2246, 128, 1, -8, 201, -2183, 11363, 25508, -2242, 127, 1, -8, 200, -2181, 11346, 25521, -2238, 127, 1, -8, 200, -2179, 11328, 25534, -2235, 126, 1, -8, 200, -2176, 11311, 25546, -2231, 125, 1, -8, 200, -2174, 11293, 25559, -2227, 125, 1, -8, 199, -2172, 11275, 25572, -2224, 124, 1, -8, 199, -2169, 11258, 25584, -2220, 123, 1, -8, 199, -2167, 11240, 25597, -2216, 122, 1, -8, 199, -2165, 11222, 25609, -2213, 122, 1, -8, 198, -2162, 11205, 25622, -2209, 121, 1, -8, 198, -2160, 11187, 25634, -2205, 120, 1, -8, 198, -2158, 11170, 25647, -2201, 119, 1, -8, 198, -2155, 11152, 25659, -2198, 119, 1, -8, 197, -2153, 11135, 25672, -2194, 118, 1, -8, 197, -2151, 11117, 25684, -2190, 117, 1, -8, 197, -2148, 11099, 25697, -2186, 116, 1, -8, 197, -2146, 11082, 25709, -2182, 116, 1, -8, 196, -2143, 11064, 25722, -2179, 115, 1, -8, 196, -2141, 11047, 25734, -2175, 114, 2, -9, 196, -2139, 11029, 25746, -2171, 113, 2, -9, 196, -2136, 11012, 25759, -2167, 113, 2, -9, 195, -2134, 10994, 25771, -2163, 112, 2, -9, 195, -2132, 10977, 25783, -2159, 111, 2, -9, 195, -2129, 10959, 25796, -2155, 110, 2, -9, 194, -2127, 10942, 25808, -2152, 110, 2, -9, 194, -2125, 10924, 25820, -2148, 109, 2, -9, 194, -2122, 10907, 25832, -2144, 108, 2, -9, 194, -2120, 10889, 25845, -2140, 107, 2, -9, 193, -2117, 10872, 25857, -2136, 106, 2, -9, 193, -2115, 10854, 25869, -2132, 106, 2, -9, 193, -2113, 10837, 25881, -2128, 105, 2, -9, 193, -2110, 10819, 25893, -2124, 104, 2, -9, 192, -2108, 10802, 25905, -2120, 103, 2, -9, 192, -2105, 10784, 25918, -2116, 103, 2, -9, 192, -2103, 10767, 25930, -2112, 102, 2, -9, 192, -2101, 10749, 25942, -2108, 101, 2, -9, 191, -2098, 10732, 25954, -2104, 100, 2, -9, 191, -2096, 10715, 25966, -2100, 99, 2, -9, 191, -2093, 10697, 25978, -2096, 99, 2, -9, 191, -2091, 10680, 25990, -2091, 98, 2, -9, 190, -2089, 10662, 26002, -2087, 97, 2, -10, 190, -2086, 10645, 26014, -2083, 96, 2, -10, 190, -2084, 10627, 26026, -2079, 95, 2, -10, 189, -2081, 10610, 26038, -2075, 95, 2, -10, 189, -2079, 10593, 26050, -2071, 94, 2, -10, 189, -2077, 10575, 26062, -2067, 93, 2, -10, 189, -2074, 10558, 26074, -2062, 92, 2, -10, 188, -2072, 10540, 26086, -2058, 91, 2, -10, 188, -2069, 10523, 26097, -2054, 90, 2, -10, 188, -2067, 10506, 26109, -2050, 90, 2, -10, 188, -2065, 10488, 26121, -2046, 89, 2, -10, 187, -2062, 10471, 26133, -2041, 88, 2, -10, 187, -2060, 10454, 26145, -2037, 87, 2, -10, 187, -2057, 10436, 26157, -2033, 86, 2, -10, 187, -2055, 10419, 26168, -2029, 85, 2, -10, 186, -2052, 10402, 26180, -2024, 85, 2, -10, 186, -2050, 10384, 26192, -2020, 84, 2, -10, 186, -2048, 10367, 26204, -2016, 83, 2, -10, 185, -2045, 10350, 26215, -2011, 82, 3, -10, 185, -2043, 10332, 26227, -2007, 81, 3, -10, 185, -2040, 10315, 26239, -2003, 80, 3, -10, 185, -2038, 10298, 26250, -1998, 80, 3, -11, 184, -2035, 10280, 26262, -1994, 79, 3, -11, 184, -2033, 10263, 26273, -1990, 78, 3, -11, 184, -2031, 10246, 26285, -1985, 77, 3, -11, 184, -2028, 10228, 26297, -1981, 76, 3, -11, 183, -2026, 10211, 26308, -1976, 75, 3, -11, 183, -2023, 10194, 26320, -1972, 74, 3, -11, 183, -2021, 10177, 26331, -1967, 74, 3, -11, 183, -2018, 10159, 26343, -1963, 73, 3, -11, 182, -2016, 10142, 26354, -1958, 72, 3, -11, 182, -2013, 10125, 26366, -1954, 71, 3, -11, 182, -2011, 10108, 26377, -1949, 70, 3, -11, 181, -2009, 10090, 26389, -1945, 69, 3, -11, 181, -2006, 10073, 26400, -1940, 68, 3, -11, 181, -2004, 10056, 26411, -1936, 67, 3, -11, 181, -2001, 10039, 26423, -1931, 67, 3, -11, 180, -1999, 10022, 26434, -1927, 66, 3, -11, 180, -1996, 10004, 26445, -1922, 65, 3, -11, 180, -1994, 9987, 26457, -1918, 64, 3, -11, 180, -1991, 9970, 26468, -1913, 63, 3, -11, 179, -1989, 9953, 26479, -1908, 62, 3, -12, 179, -1986, 9936, 26491, -1904, 61, 3, -12, 179, -1984, 9919, 26502, -1899, 60, 3, -12, 178, -1982, 9901, 26513, -1895, 59, 3, -12, 178, -1979, 9884, 26524, -1890, 58, 3, -12, 178, -1977, 9867, 26536, -1885, 58, 3, -12, 178, -1974, 9850, 26547, -1880, 57, 3, -12, 177, -1972, 9833, 26558, -1876, 56, 3, -12, 177, -1969, 9816, 26569, -1871, 55, 3, -12, 177, -1967, 9799, 26580, -1866, 54, 4, -12, 177, -1964, 9781, 26591, -1862, 53, 4, -12, 176, -1962, 9764, 26602, -1857, 52, 4, -12, 176, -1959, 9747, 26613, -1852, 51, 4, -12, 176, -1957, 9730, 26625, -1847, 50, 4, -12, 176, -1954, 9713, 26636, -1843, 49, 4, -12, 175, -1952, 9696, 26647, -1838, 48, 4, -12, 175, -1949, 9679, 26658, -1833, 47, 4, -12, 175, -1947, 9662, 26669, -1828, 46, 4, -12, 174, -1944, 9645, 26679, -1823, 46, 4, -13, 174, -1942, 9628, 26690, -1818, 45, 4, -13, 174, -1939, 9611, 26701, -1813, 44, 4, -13, 174, -1937, 9594, 26712, -1809, 43, 4, -13, 173, -1935, 9577, 26723, -1804, 42, 4, -13, 173, -1932, 9560, 26734, -1799, 41, 4, -13, 173, -1930, 9543, 26745, -1794, 40, 4, -13, 173, -1927, 9526, 26756, -1789, 39, 4, -13, 172, -1925, 9509, 26767, -1784, 38, 4, -13, 172, -1922, 9492, 26777, -1779, 37, 4, -13, 172, -1920, 9475, 26788, -1774, 36, 4, -13, 171, -1917, 9458, 26799, -1769, 35, 4, -13, 171, -1915, 9441, 26810, -1764, 34, 4, -13, 171, -1912, 9424, 26820, -1759, 33, 4, -13, 171, -1910, 9407, 26831, -1754, 32, 4, -13, 170, -1907, 9390, 26842, -1749, 31, 4, -13, 170, -1905, 9373, 26852, -1744, 30, 4, -13, 170, -1902, 9356, 26863, -1739, 29, 4, -13, 170, -1900, 9339, 26874, -1734, 28, 5, -13, 169, -1897, 9322, 26884, -1729, 27, 5, -14, 169, -1895, 9305, 26895, -1724, 26, 5, -14, 169, -1892, 9288, 26905, -1719, 25, 5, -14, 168, -1890, 9271, 26916, -1713, 24, 5, -14, 168, -1887, 9254, 26926, -1708, 23, 5, -14, 168, -1885, 9237, 26937, -1703, 22, 5, -14, 168, -1882, 9221, 26947, -1698, 21, 5, -14, 167, -1880, 9204, 26958, -1693, 20, 5, -14, 167, -1877, 9187, 26968, -1688, 19, 5, -14, 167, -1875, 9170, 26979, -1682, 18, 5, -14, 167, -1872, 9153, 26989, -1677, 17, 5, -14, 166, -1870, 9136, 27000, -1672, 16, 5, -14, 166, -1867, 9119, 27010, -1667, 15, 5, -14, 166, -1865, 9103, 27020, -1661, 14, 5, -14, 165, -1862, 9086, 27031, -1656, 13, 5, -14, 165, -1859, 9069, 27041, -1651, 12, 5, -14, 165, -1857, 9052, 27051, -1646, 11, 5, -14, 165, -1854, 9035, 27062, -1640, 10, 5, -15, 164, -1852, 9019, 27072, -1635, 9, 5, -15, 164, -1849, 9002, 27082, -1630, 8, 5, -15, 164, -1847, 8985, 27092, -1624, 7, 5, -15, 164, -1844, 8968, 27103, -1619, 6, 5, -15, 163, -1842, 8952, 27113, -1614, 5, 6, -15, 163, -1839, 8935, 27123, -1608, 4, 6, -15, 163, -1837, 8918, 27133, -1603, 3, 6, -15, 162, -1834, 8901, 27143, -1597, 2, 6, -15, 162, -1832, 8885, 27153, -1592, 1, 6, -15, 162, -1829, 8868, 27164, -1587, 0, 6, -15, 162, -1827, 8851, 27174, -1581, -1, 6, -15, 161, -1824, 8834, 27184, -1576, -2, 6, -15, 161, -1822, 8818, 27194, -1570, -3, 6, -15, 161, -1819, 8801, 27204, -1565, -4, 6, -15, 161, -1817, 8784, 27214, -1559, -5, 6, -15, 160, -1814, 8768, 27224, -1554, -6, 6, -15, 160, -1812, 8751, 27234, -1548, -8, 6, -16, 160, -1809, 8734, 27244, -1543, -9, 6, -16, 159, -1807, 8718, 27254, -1537, -10, 6, -16, 159, -1804, 8701, 27263, -1532, -11, 6, -16, 159, -1801, 8684, 27273, -1526, -12, 6, -16, 159, -1799, 8668, 27283, -1520, -13, 6, -16, 158, -1796, 8651, 27293, -1515, -14, 6, -16, 158, -1794, 8634, 27303, -1509, -15, 6, -16, 158, -1791, 8618, 27313, -1503, -16, 6, -16, 158, -1789, 8601, 27322, -1498, -17, 6, -16, 157, -1786, 8585, 27332, -1492, -18, 7, -16, 157, -1784, 8568, 27342, -1486, -19, 7, -16, 157, -1781, 8552, 27352, -1481, -20, 7, -16, 157, -1779, 8535, 27361, -1475, -22, 7, -16, 156, -1776, 8518, 27371, -1469, -23, 7, -16, 156, -1774, 8502, 27381, -1464, -24, 7, -16, 156, -1771, 8485, 27390, -1458, -25, 7, -16, 155, -1769, 8469, 27400, -1452, -26, 7, -17, 155, -1766, 8452, 27410, -1446, -27, 7, -17, 155, -1763, 8436, 27419, -1441, -28, 7, -17, 155, -1761, 8419, 27429, -1435, -29, 7, -17, 154, -1758, 8403, 27438, -1429, -30, 7, -17, 154, -1756, 8386, 27448, -1423, -31, 7, -17, 154, -1753, 8370, 27457, -1417, -33, 7, -17, 154, -1751, 8353, 27467, -1412, -34, 7, -17, 153, -1748, 8337, 27476, -1406, -35, 7, -17, 153, -1746, 8320, 27486, -1400, -36, 7, -17, 153, -1743, 8304, 27495, -1394, -37, 7, -17, 152, -1741, 8287, 27505, -1388, -38, 7, -17, 152, -1738, 8271, 27514, -1382, -39, 8, -17, 152, -1736, 8255, 27523, -1376, -40, 8, -17, 152, -1733, 8238, 27533, -1370, -42, 8, -17, 151, -1730, 8222, 27542, -1364, -43, 8, -17, 151, -1728, 8205, 27552, -1358, -44, 8, -18, 151, -1725, 8189, 27561, -1352, -45, 8, -18, 151, -1723, 8173, 27570, -1346, -46, 8, -18, 150, -1720, 8156, 27579, -1340, -47, 8, -18, 150, -1718, 8140, 27589, -1334, -49, 8, -18, 150, -1715, 8123, 27598, -1328, -50, 8, -18, 149, -1713, 8107, 27607, -1322, -51, 8, -18, 149, -1710, 8091, 27616, -1316, -52, 8, -18, 149, -1708, 8074, 27625, -1310, -53, 8, -18, 149, -1705, 8058, 27634, -1304, -54, 8, -18, 148, -1703, 8042, 27644, -1298, -55, 8, -18, 148, -1700, 8025, 27653, -1292, -57, 8, -18, 148, -1697, 8009, 27662, -1286, -58, 8, -18, 148, -1695, 7993, 27671, -1280, -59, 8, -18, 147, -1692, 7977, 27680, -1273, -60, 9, -18, 147, -1690, 7960, 27689, -1267, -61, 9, -18, 147, -1687, 7944, 27698, -1261, -63, 9, -19, 147, -1685, 7928, 27707, -1255, -64, 9, -19, 146, -1682, 7912, 27716, -1249, -65, 9, -19, 146, -1680, 7895, 27725, -1243, -66, 9, -19, 146, -1677, 7879, 27734, -1236, -67, 9, -19, 145, -1674, 7863, 27743, -1230, -68, 9, -19, 145, -1672, 7847, 27751, -1224, -70, 9, -19, 145, -1669, 7830, 27760, -1218, -71, 9, -19, 145, -1667, 7814, 27769, -1211, -72, 9, -19, 144, -1664, 7798, 27778, -1205, -73, 9, -19, 144, -1662, 7782, 27787, -1199, -74, 9, -19, 144, -1659, 7766, 27795, -1192, -76, 9, -19, 144, -1657, 7750, 27804, -1186, -77, 9, -19, 143, -1654, 7733, 27813, -1180, -78, 9, -19, 143, -1652, 7717, 27822, -1173, -79, 9, -19, 143, -1649, 7701, 27830, -1167, -81, 10, -20, 142, -1646, 7685, 27839, -1160, -82, 10, -20, 142, -1644, 7669, 27848, -1154, -83, 10, -20, 142, -1641, 7653, 27856, -1148, -84, 10, -20, 142, -1639, 7637, 27865, -1141, -85, 10, -20, 141, -1636, 7621, 27874, -1135, -87, 10, -20, 141, -1634, 7605, 27882, -1128, -88, 10, -20, 141, -1631, 7589, 27891, -1122, -89, 10, -20, 141, -1629, 7572, 27899, -1115, -90, 10, -20, 140, -1626, 7556, 27908, -1109, -92, 10, -20, 140, -1624, 7540, 27916, -1102, -93, 10, -20, 140, -1621, 7524, 27925, -1096, -94, 10, -20, 140, -1618, 7508, 27933, -1089, -95, 10, -20, 139, -1616, 7492, 27941, -1083, -97, 10, -20, 139, -1613, 7476, 27950, -1076, -98, 10, -20, 139, -1611, 7460, 27958, -1070, -99, 11, -21, 138, -1608, 7444, 27967, -1063, -100, 11, -21, 138, -1606, 7428, 27975, -1056, -102, 11, -21, 138, -1603, 7412, 27983, -1050, -103, 11, -21, 138, -1601, 7396, 27992, -1043, -104, 11, -21, 137, -1598, 7380, 28000, -1036, -105, 11, -21, 137, -1595, 7365, 28008, -1030, -107, 11, -21, 137, -1593, 7349, 28016, -1023, -108, 11, -21, 137, -1590, 7333, 28025, -1016, -109, 11, -21, 136, -1588, 7317, 28033, -1010, -110, 11, -21, 136, -1585, 7301, 28041, -1003, -112, 11, -21, 136, -1583, 7285, 28049, -996, -113, 11, -21, 136, -1580, 7269, 28057, -989, -114, 11, -21, 135, -1578, 7253, 28065, -983, -116, 11, -21, 135, -1575, 7237, 28073, -976, -117, 11, -21, 135, -1573, 7221, 28082, -969, -118, 12, -22, 135, -1570, 7206, 28090, -962, -119, 12, -22, 134, -1567, 7190, 28098, -956, -121, 12, -22, 134, -1565, 7174, 28106, -949, -122, 12, -22, 134, -1562, 7158, 28114, -942, -123, 12, -22, 133, -1560, 7142, 28122, -935, -125, 12, -22, 133, -1557, 7127, 28130, -928, -126, 12, -22, 133, -1555, 7111, 28137, -921, -127, 12, -22, 133, -1552, 7095, 28145, -914, -129, 12, -22, 132, -1550, 7079, 28153, -907, -130, 12, -22, 132, -1547, 7063, 28161, -901, -131, 12, -22, 132, -1545, 7048, 28169, -894, -133, 12, -22, 132, -1542, 7032, 28177, -887, -134, 12, -22, 131, -1539, 7016, 28185, -880, -135, 12, -22, 131, -1537, 7001, 28192, -873, -136, 13, -23, 131, -1534, 6985, 28200, -866, -138, 13, -23, 131, -1532, 6969, 28208, -859, -139, 13, -23, 130, -1529, 6953, 28216, -852, -140, 13, -23, 130, -1527, 6938, 28223, -845, -142, 13, -23, 130, -1524, 6922, 28231, -838, -143, 13, -23, 130, -1522, 6906, 28239, -831, -144, 13, -23, 129, -1519, 6891, 28246, -824, -146, 13, -23, 129, -1517, 6875, 28254, -816, -147, 13, -23, 129, -1514, 6859, 28261, -809, -149, 13, -23, 128, -1511, 6844, 28269, -802, -150, 13, -23, 128, -1509, 6828, 28277, -795, -151, 13, -23, 128, -1506, 6813, 28284, -788, -153, 13, -23, 128, -1504, 6797, 28292, -781, -154, 14, -23, 127, -1501, 6781, 28299, -774, -155, 14, -24, 127, -1499, 6766, 28307, -766, -157, 14, -24, 127, -1496, 6750, 28314, -759, -158, 14, -24, 127, -1494, 6735, 28321, -752, -159, 14, -24, 126, -1491, 6719, 28329, -745, -161, 14, -24, 126, -1489, 6704, 28336, -738, -162, 14, -24, 126, -1486, 6688, 28344, -730, -163, 14, -24, 126, -1484, 6673, 28351, -723, -165, 14, -24, 125, -1481, 6657, 28358, -716, -166, 14, -24, 125, -1478, 6642, 28366, -708, -168, 14, -24, 125, -1476, 6626, 28373, -701, -169, 14, -24, 125, -1473, 6611, 28380, -694, -170, 14, -24, 124, -1471, 6595, 28387, -687, -172, 15, -24, 124, -1468, 6580, 28395, -679, -173, 15, -24, 124, -1466, 6564, 28402, -672, -175, 15, -25, 124, -1463, 6549, 28409, -664, -176, 15, -25, 123, -1461, 6533, 28416, -657, -177, 15, -25, 123, -1458, 6518, 28423, -650, -179, 15, -25, 123, -1456, 6503, 28430, -642, -180, 15, -25, 122, -1453, 6487, 28437, -635, -182, 15, -25, 122, -1451, 6472, 28444, -627, -183, 15, -25, 122, -1448, 6457, 28451, -620, -184, 15, -25, 122, -1445, 6441, 28458, -612, -186, 15, -25, 121, -1443, 6426, 28465, -605, -187, 15, -25, 121, -1440, 6410, 28472, -597, -189, 16, -25, 121, -1438, 6395, 28479, -590, -190, 16, -25, 121, -1435, 6380, 28486, -582, -191, 16, -25, 120, -1433, 6365, 28493, -575, -193, 16, -25, 120, -1430, 6349, 28500, -567, -194, 16, -26, 120, -1428, 6334, 28507, -560, -196, 16, -26, 120, -1425, 6319, 28514, -552, -197, 16, -26, 119, -1423, 6303, 28521, -545, -199, 16, -26, 119, -1420, 6288, 28527, -537, -200, 16, -26, 119, -1418, 6273, 28534, -529, -202, 16, -26, 119, -1415, 6258, 28541, -522, -203, 16, -26, 118, -1413, 6242, 28548, -514, -204, 16, -26, 118, -1410, 6227, 28554, -506, -206, 17, -26, 118, -1408, 6212, 28561, -499, -207, 17, -26, 118, -1405, 6197, 28568, -491, -209, 17, -26, 117, -1403, 6182, 28574, -483, -210, 17, -26, 117, -1400, 6166, 28581, -476, -212, 17, -26, 117, -1397, 6151, 28588, -468, -213, 17, -26, 117, -1395, 6136, 28594, -460, -215, 17, -27, 116, -1392, 6121, 28601, -452, -216, 17, -27, 116, -1390, 6106, 28607, -444, -218, 17, -27, 116, -1387, 6091, 28614, -437, -219, 17, -27, 116, -1385, 6076, 28620, -429, -220, 17, -27, 115, -1382, 6061, 28627, -421, -222, 18, -27, 115, -1380, 6045, 28633, -413, -223, 18, -27, 115, -1377, 6030, 28640, -405, -225, 18, -27, 115, -1375, 6015, 28646, -398, -226, 18, -27, 114, -1372, 6000, 28653, -390, -228, 18, -27, 114, -1370, 5985, 28659, -382, -229, 18, -27, 114, -1367, 5970, 28665, -374, -231, 18, -27, 114, -1365, 5955, 28672, -366, -232, 18, -27, 113, -1362, 5940, 28678, -358, -234, 18, -28, 113, -1360, 5925, 28684, -350, -235, 18, -28, 113, -1357, 5910, 28690, -342, -237, 18, -28, 113, -1355, 5895, 28697, -334, -238, 19, -28, 112, -1352, 5880, 28703, -326, -240, 19, -28, 112, -1350, 5865, 28709, -318, -241, 19, -28, 112, -1347, 5850, 28715, -310, -243, 19, -28, 112, -1345, 5835, 28721, -302, -244, 19, -28, 111, -1342, 5820, 28727, -294, -246, 19, -28, 111, -1340, 5805, 28734, -286, -247, 19, -28, 111, -1337, 5791, 28740, -278, -249, 19, -28, 111, -1335, 5776, 28746, -270, -250, 19, -28, 110, -1332, 5761, 28752, -262, -252, 19, -28, 110, -1330, 5746, 28758, -254, -254, 19, -28, 110, -1327, 5731, 28764, -246, -255, 20, -29, 110, -1325, 5716, 28770, -237, -257, 20, -29, 109, -1322, 5701, 28776, -229, -258, 20, -29, 109, -1320, 5686, 28782, -221, -260, 20, -29, 109, -1317, 5672, 28788, -213, -261, 20, -29, 109, -1315, 5657, 28793, -205, -263, 20, -29, 108, -1312, 5642, 28799, -196, -264, 20, -29, 108, -1310, 5627, 28805, -188, -266, 20, -29, 108, -1307, 5612, 28811, -180, -267, 20, -29, 108, -1305, 5598, 28817, -172, -269, 20, -29, 107, -1302, 5583, 28822, -163, -270, 21, -29, 107, -1300, 5568, 28828, -155, -272, 21, -29, 107, -1297, 5553, 28834, -147, -274, 21, -29, 107, -1295, 5539, 28840, -139, -275, 21, -30, 106, -1292, 5524, 28845, -130, -277, 21, -30, 106, -1290, 5509, 28851, -122, -278, 21, -30, 106, -1287, 5495, 28857, -114, -280, 21, -30, 106, -1285, 5480, 28862, -105, -281, 21, -30, 105, -1282, 5465, 28868, -97, -283, 21, -30, 105, -1280, 5451, 28873, -88, -285, 21, -30, 105, -1277, 5436, 28879, -80, -286, 22, -30, 105, -1275, 5421, 28885, -72, -288, 22, -30, 104, -1272, 5407, 28890, -63, -289, 22, -30, 104, -1270, 5392, 28896, -55, -291, 22, -30, 104, -1267, 5378, 28901, -46, -292, 22, -30, 104, -1265, 5363, 28906, -38, -294, 22, -30, 103, -1262, 5348, 28912, -29, -296, 22, -31, 103, -1260, 5334, 28917, -21, -297, 22, -31, 103, -1258, 5319, 28923, -12, -299, 22, -31, 103, -1255, 5305, 28928, -4, -300, 23, -31, 103, -1253, 5290, 28933, 5, -302, 23, -31, 102, -1250, 5276, 28939, 13, -304, 23, -31, 102, -1248, 5261, 28944, 22, -305, 23, -31, 102, -1245, 5247, 28949, 30, -307, 23, -31, 102, -1243, 5232, 28954, 39, -308, 23, -31, 101, -1240, 5218, 28960, 48, -310, 23, -31, 101, -1238, 5203, 28965, 56, -312, 23, -31, 101, -1235, 5189, 28970, 65, -313, 23, -31, 101, -1233, 5174, 28975, 74, -315, 23, -31, 100, -1230, 5160, 28980, 82, -317, 24, -32, 100, -1228, 5146, 28985, 91, -318, 24, -32, 100, -1225, 5131, 28990, 100, -320, 24, -32, 100, -1223, 5117, 28995, 108, -321, 24, -32, 99, -1221, 5102, 29000, 117, -323, 24, -32, 99, -1218, 5088, 29006, 126, -325, 24, -32, 99, -1216, 5074, 29010, 135, -326, 24, -32, 99, -1213, 5059, 29015, 143, -328, 24, -32, 98, -1211, 5045, 29020, 152, -330, 24, -32, 98, -1208, 5031, 29025, 161, -331, 25, -32, 98, -1206, 5016, 29030, 170, -333, 25, -32, 98, -1203, 5002, 29035, 179, -335, 25, -32, 97, -1201, 4988, 29040, 187, -336, 25, -32, 97, -1198, 4973, 29045, 196, -338, 25, -33, 97, -1196, 4959, 29050, 205, -340, 25, -33, 97, -1194, 4945, 29054, 214, -341, 25, -33, 97, -1191, 4931, 29059, 223, -343, 25, -33, 96, -1189, 4916, 29064, 232, -345, 25, -33, 96, -1186, 4902, 29069, 241, -346, 26, -33, 96, -1184, 4888, 29073, 250, -348, 26, -33, 96, -1181, 4874, 29078, 259, -350, 26, -33, 95, -1179, 4860, 29083, 268, -351, 26, -33, 95, -1176, 4845, 29087, 277, -353, 26, -33, 95, -1174, 4831, 29092, 286, -355, 26, -33, 95, -1172, 4817, 29096, 295, -356, 26, -33, 94, -1169, 4803, 29101, 304, -358, 26, -33, 94, -1167, 4789, 29106, 313, -360, 27, -34, 94, -1164, 4775, 29110, 322, -361, 27, -34, 94, -1162, 4761, 29115, 331, -363, 27, -34, 94, -1159, 4747, 29119, 340, -365, 27, -34, 93, -1157, 4732, 29124, 349, -366, 27, -34, 93, -1154, 4718, 29128, 358, -368, 27, -34, 93, -1152, 4704, 29132, 367, -370, 27, -34, 93, -1150, 4690, 29137, 376, -372, 27, -34, 92, -1147, 4676, 29141, 385, -373, 27, -34, 92, -1145, 4662, 29145, 395, -375, 28, -34, 92, -1142, 4648, 29150, 404, -377, 28, -34, 92, -1140, 4634, 29154, 413, -378, 28, -34, 91, -1138, 4620, 29158, 422, -380, 28, -34, 91, -1135, 4606, 29163, 431, -382, 28, -35, 91, -1133, 4592, 29167, 441, -384, 28, -35, 91, -1130, 4578, 29171, 450, -385, 28, -35, 91, -1128, 4564, 29175, 459, -387, 28, -35, 90, -1125, 4551, 29179, 468, -389, 29, -35, 90, -1123, 4537, 29183, 478, -391, 29, -35, 90, -1121, 4523, 29188, 487, -392, 29, -35, 90, -1118, 4509, 29192, 496, -394, 29, -35, 89, -1116, 4495, 29196, 506, -396, 29, -35, 89, -1113, 4481, 29200, 515, -397, 29, -35, 89, -1111, 4467, 29204, 524, -399, 29, -35, 89, -1109, 4453, 29208, 534, -401, 29, -35, 88, -1106, 4440, 29212, 543, -403, 30, -35, 88, -1104, 4426, 29216, 552, -404, 30, -36, 88, -1101, 4412, 29220, 562, -406, 30, -36, 88, -1099, 4398, 29223, 571, -408, 30, -36, 88, -1097, 4384, 29227, 581, -410, 30, -36, 87, -1094, 4371, 29231, 590, -411, 30, -36, 87, -1092, 4357, 29235, 600, -413, 30, -36, 87, -1089, 4343, 29239, 609, -415, 30, -36, 87, -1087, 4329, 29243, 619, -417, 31, -36, 86, -1085, 4316, 29246, 628, -419, 31, -36, 86, -1082, 4302, 29250, 638, -420, 31, -36, 86, -1080, 4288, 29254, 647, -422, 31, -36, 86, -1077, 4274, 29258, 657, -424, 31, -36, 86, -1075, 4261, 29261, 666, -426, 31, -36, 85, -1073, 4247, 29265, 676, -427, 31, -37, 85, -1070, 4234, 29268, 686, -429, 31, -37, 85, -1068, 4220, 29272, 695, -431, 32, -37, 85, -1066, 4206, 29276, 705, -433, 32, -37, 84, -1063, 4193, 29279, 714, -435, 32, -37, 84, -1061, 4179, 29283, 724, -436, 32, -37, 84, -1058, 4165, 29286, 734, -438, 32, -37, 84, -1056, 4152, 29290, 743, -440, 32, -37, 84, -1054, 4138, 29293, 753, -442, 32, -37, 83, -1051, 4125, 29297, 763, -444, 32, -37, 83, -1049, 4111, 29300, 773, -445, 33, -37, 83, -1047, 4098, 29303, 782, -447, 33, -37, 83, -1044, 4084, 29307, 792, -449, 33, -38, 82, -1042, 4071, 29310, 802, -451, 33, -38, 82, -1039, 4057, 29314, 812, -453, 33, -38, 82, -1037, 4044, 29317, 821, -454, 33, -38, 82, -1035, 4030, 29320, 831, -456, 33, -38, 82, -1032, 4017, 29323, 841, -458, 34, -38, 81, -1030, 4003, 29327, 851, -460, 34, -38, 81, -1028, 3990, 29330, 861, -462, 34, -38, 81, -1025, 3976, 29333, 871, -464, 34, -38, 81, -1023, 3963, 29336, 880, -465, 34, -38, 80, -1021, 3950, 29339, 890, -467, 34, -38, 80, -1018, 3936, 29342, 900, -469, 34, -38, 80, -1016, 3923, 29346, 910, -471, 34, -38, 80, -1014, 3910, 29349, 920, -473, 35, -39, 80, -1011, 3896, 29352, 930, -475, 35, -39, 79, -1009, 3883, 29355, 940, -476, 35, -39, 79, -1006, 3870, 29358, 950, -478, 35, -39, 79, -1004, 3856, 29361, 960, -480, 35, -39, 79, -1002, 3843, 29364, 970, -482, 35, -39, 79, -999, 3830, 29367, 980, -484, 35, -39, 78, -997, 3816, 29369, 990, -486, 36, -39, 78, -995, 3803, 29372, 1000, -488, 36, -39, 78, -992, 3790, 29375, 1010, -489, 36, -39, 78, -990, 3777, 29378, 1020, -491, 36, -39, 77, -988, 3764, 29381, 1030, -493, 36, -39, 77, -985, 3750, 29384, 1040, -495, 36, -39, 77, -983, 3737, 29386, 1050, -497, 36, -40, 77, -981, 3724, 29389, 1061, -499, 37, -40, 77, -978, 3711, 29392, 1071, -501, 37, -40, 76, -976, 3698, 29395, 1081, -502, 37, -40, 76, -974, 3684, 29397, 1091, -504, 37, -40, 76, -971, 3671, 29400, 1101, -506, 37, -40, 76, -969, 3658, 29403, 1111, -508, 37, -40, 76, -967, 3645, 29405, 1122, -510, 37, -40, 75, -965, 3632, 29408, 1132, -512, 38, -40, 75, -962, 3619, 29410, 1142, -514, 38, -40, 75, -960, 3606, 29413, 1152, -516, 38, -40, 75, -958, 3593, 29415, 1163, -518, 38, -40, 75, -955, 3580, 29418, 1173, -520, 38, -40, 74, -953, 3567, 29420, 1183, -521, 38, -41, 74, -951, 3554, 29423, 1193, -523, 38, -41, 74, -948, 3541, 29425, 1204, -525, 39, -41, 74, -946, 3528, 29428, 1214, -527, 39, -41, 74, -944, 3515, 29430, 1224, -529, 39, -41, 73, -941, 3502, 29432, 1235, -531, 39, -41, 73, -939, 3489, 29435, 1245, -533, 39, -41, 73, -937, 3476, 29437, 1256, -535, 39, -41, 73, -935, 3463, 29439, 1266, -537, 39, -41, 72, -932, 3450, 29442, 1276, -539, 40, -41, 72, -930, 3437, 29444, 1287, -541, 40, -41, 72, -928, 3424, 29446, 1297, -542, 40, -41, 72, -925, 3411, 29448, 1308, -544, 40, -41, 72, -923, 3399, 29450, 1318, -546, 40, -42, 71, -921, 3386, 29452, 1329, -548, 40, -42, 71, -919, 3373, 29455, 1339, -550, 40, -42, 71, -916, 3360, 29457, 1350, -552, 41, -42, 71, -914, 3347, 29459, 1360, -554, 41, -42, 71, -912, 3334, 29461, 1371, -556, 41, -42, 70, -909, 3322, 29463, 1381, -558, 41, -42, 70, -907, 3309, 29465, 1392, -560, 41, -42, 70, -905, 3296, 29467, 1403, -562, 41, -42, 70, -903, 3283, 29469, 1413, -564, 42, -42, 70, -900, 3271, 29471, 1424, -566, 42, -42, 69, -898, 3258, 29473, 1434, -568, 42, -42, 69, -896, 3245, 29475, 1445, -570, 42, -42, 69, -894, 3232, 29476, 1456, -572, 42, -43, 69, -891, 3220, 29478, 1466, -574, 42, -43, 69, -889, 3207, 29480, 1477, -576, 42, -43, 68, -887, 3194, 29482, 1488, -578, 43, -43, 68, -885, 3182, 29484, 1498, -579, 43, -43, 68, -882, 3169, 29485, 1509, -581, 43, -43, 68, -880, 3157, 29487, 1520, -583, 43, -43, 68, -878, 3144, 29489, 1531, -585, 43, -43, 67, -876, 3131, 29490, 1541, -587, 43, -43, 67, -873, 3119, 29492, 1552, -589, 43, -43, 67, -871, 3106, 29494, 1563, -591, 44, -43, 67, -869, 3094, 29495, 1574, -593, 44, -43, 67, -867, 3081, 29497, 1585, -595, 44, -43, 66, -864, 3069, 29498, 1595, -597, 44, -44, 66, -862, 3056, 29500, 1606, -599, 44, -44, 66, -860, 3044, 29502, 1617, -601, 44, -44, 66, -858, 3031, 29503, 1628, -603, 45, -44, 66, -855, 3019, 29504, 1639, -605, 45, -44, 65, -853, 3006, 29506, 1650, -607, 45, -44, 65, -851, 2994, 29507, 1661, -609, 45, -44, 65, -849, 2981, 29509, 1672, -611, 45, -44, 65, -846, 2969, 29510, 1683, -613, 45, -44, 65, -844, 2956, 29511, 1694, -615, 46, -44, 64, -842, 2944, 29513, 1705, -617, 46, -44, 64, -840, 2932, 29514, 1716, -619, 46, -44, 64, -838, 2919, 29515, 1727, -621, 46, -44, 64, -835, 2907, 29517, 1738, -623, 46, -45, 64, -833, 2895, 29518, 1749, -625, 46, -45, 64, -831, 2882, 29519, 1760, -627, 47, -45, 63, -829, 2870, 29520, 1771, -629, 47, -45, 63, -826, 2858, 29521, 1782, -631, 47, -45, 63, -824, 2845, 29523, 1793, -633, 47, -45, 63, -822, 2833, 29524, 1804, -635, 47, -45, 63, -820, 2821, 29525, 1815, -638, 47, -45, 62, -818, 2808, 29526, 1826, -640, 47, -45, 62, -815, 2796, 29527, 1837, -642, 48, -45, 62, -813, 2784, 29528, 1848, -644, 48, -45, 62, -811, 2772, 29529, 1860, -646, 48, -45, 62, -809, 2760, 29530, 1871, -648, 48, -45, 61, -807, 2747, 29531, 1882, -650, 48, -46, 61, -804, 2735, 29532, 1893, -652, 48, -46, 61, -802, 2723, 29533, 1904, -654, 49, -46, 61, -800, 2711, 29533, 1916, -656, 49, -46, 61, -798, 2699, 29534, 1927, -658, 49, -46, 60, -796, 2687, 29535, 1938, -660, 49, -46, 60, -794, 2675, 29536, 1949, -662, 49, -46, 60, -791, 2662, 29537, 1961, -664, 49, -46, 60, -789, 2650, 29537, 1972, -666, 50, -46, 60, -787, 2638, 29538, 1983, -668, 50, -46, 60, -785, 2626, 29539, 1995, -670, 50, -46, 59, -783, 2614, 29540, 2006, -672, 50, -46, 59, -781, 2602, 29540, 2017, -674, 50, -46, 59, -778, 2590, 29541, 2029, -677, 50, -47, 59, -776, 2578, 29542, 2040, -679, 51, -47, 59, -774, 2566, 29542, 2052, -681, 51, -47, 58, -772, 2554, 29543, 2063, -683, 51, -47, 58, -770, 2542, 29543, 2074, -685, 51, -47, 58, -768, 2530, 29544, 2086, -687, 51, -47, 58, -765, 2518, 29544, 2097, -689, 52, -47, 58, -763, 2506, 29545, 2109, -691, 52, -47, 58, -761, 2494, 29545, 2120, -693, 52, -47, 57, -759, 2483, 29546, 2132, -695, 52, -47, 57, -757, 2471, 29546, 2143, -697, 52, -47, 57, -755, 2459, 29546, 2155, -699, 52, -47, 57, -752, 2447, 29547, 2166, -702, 53, -47, 57, -750, 2435, 29547, 2178, -704, 53, -47, 56, -748, 2423, 29547, 2189, -706, 53, -48, 56, -746, 2411, 29548, 2201, -708, 53, -48, 56, -744, 2400, 29548, 2213, -710, 53, -48, 56, -742, 2388, 29548, 2224, -712, 53, -48, 56, -740, 2376, 29548, 2236, -714, 54, -48, 56, -738, 2364, 29549, 2247, -716, 54, -48, 55, -735, 2353, 29549, 2259, -718, 54, -48, 55, -733, 2341, 29549, 2271, -721, 54, -48, 55, -731, 2329, 29549, 2282, -723, 54, -48, 55, -729, 2317, 29549, 2294, -725, 54, -48, 55, -727, 2306, 29549, 2306, -727, 55, }; schismtracker-20180209/include/sample-edit.h000066400000000000000000000044561323741476300206460ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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_t * sample); void sample_reverse(song_sample_t * sample); void sample_centralise(song_sample_t * sample); void sample_downmix(song_sample_t * sample); void sample_amplify(song_sample_t *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_t *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_t * sample, int convert_data); /* resize a sample; if aa is set, attempt to antialias (resample) the * output waveform. */ void sample_resize(song_sample_t * sample, unsigned long newlen, int aa); /* AFAIK, this was in some registered versions of IT */ void sample_invert(song_sample_t * sample); /* Impulse Tracker doesn't do these. */ void sample_delta_decode(song_sample_t * sample); void sample_mono_left(song_sample_t * sample); void sample_mono_right(song_sample_t * sample); #endif /* ! SAMPLE_EDIT_H */ schismtracker-20180209/include/sdlmain.h000066400000000000000000000011071323741476300200570ustar00rootroot00000000000000#ifndef __sdlmain_header # define __sdlmain_header /* Hack to get the build to shut up about SDL trampling on names. */ # define index qwwqwqqw # define access ddsdsd /* just a fancy way to get SDL headers */ # ifdef USE_X11 # undef DISABLE_X11 # ifndef __unix__ # define __unix__ # endif # endif # include # include # ifndef __cplusplus # include # ifdef USE_OPENGL # include # endif # endif /* complement to above hack */ # undef index # undef access #endif /* ! __sdlmain_header */ schismtracker-20180209/include/slurp.h000066400000000000000000000053611323741476300176030ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #include #include /* --------------------------------------------------------------------- */ typedef struct _slurp_struct slurp_t; struct _slurp_struct { size_t length; uint8_t *data; int extra; void *bextra; void (*closure)(slurp_t *); /* for reading streams */ size_t pos; }; /* --------------------------------------------------------------------- */ /* 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 WIN32 int slurp_win32(slurp_t *useme, const char *filename, size_t st); #endif #if HAVE_MMAP int slurp_mmap(slurp_t *useme, const char *filename, size_t st); #endif /* stdio-style file processing */ int slurp_seek(slurp_t *t, long offset, int whence); /* whence => SEEK_SET, SEEK_CUR, SEEK_END */ long slurp_tell(slurp_t *t); #define slurp_rewind(t) slurp_seek((t), 0, SEEK_SET) size_t slurp_read(slurp_t *t, void *ptr, size_t count); /* i never realy liked fread */ size_t slurp_peek(slurp_t *t, void *ptr, size_t count); int slurp_getc(slurp_t *t); /* returns unsigned char cast to int, or EOF */ int slurp_eof(slurp_t *t); /* 1 = end of file */ /* used internally by slurp, nothing else should need this */ int mmcmp_unpack(uint8_t **data, size_t *length); #endif /* ! SLURP_H */ schismtracker-20180209/include/snd_fm.h000066400000000000000000000157711323741476300177120ustar00rootroot00000000000000#ifndef _BqtModplugSndFm #define _BqtModplugSndFm void Fmdrv_Init(int mixfreq); void Fmdrv_MixTo(int* buf, int count); void OPL_NoteOff(int c); void OPL_HertzTouch(int c, int Hertz, int keyoff); // also for pitch bending void OPL_Touch(int c, unsigned Vol); void OPL_Pan(int c, int val); void OPL_Patch(int c, const unsigned char *D); void OPL_Reset(void); int OPL_Detect(void); void OPL_Close(void); /*************/ /* 7.6.1999 01:51 / Bisqwit: * The rest of this file is clipped from OSS/Free for Linux */ /* * The OPL-3 mode is switched on by writing 0x01, to the offset 5 * of the right side. * * Another special register at the right side is at offset 4. It contains * a bit mask defining which voices are used as 4 OP voices. * * The percussive mode is implemented in the left side only. * * With the above exceptions the both sides can be operated independently. * * A 4 OP voice can be created by setting the corresponding * bit at offset 4 of the right side. * * For example setting the rightmost bit (0x01) changes the * first voice on the right side to the 4 OP mode. The fourth * voice is made inaccessible. * * If a voice is set to the 2 OP mode, it works like 2 OP modes * of the original YM3812 (AdLib). In addition the voice can * be connected the left, right or both stereo channels. It can * even be left unconnected. This works with 4 OP voices also. * * The stereo connection bits are located in the FEEDBACK_CONNECTION * register of the voice (0xC0-0xC8). In 4 OP voices these bits are * in the second half of the voice. */ /* * Register numbers for the global registers */ #define TEST_REGISTER 0x01 #define ENABLE_WAVE_SELECT 0x20 #define TIMER1_REGISTER 0x02 #define TIMER2_REGISTER 0x03 #define TIMER_CONTROL_REGISTER 0x04 /* Left side */ #define IRQ_RESET 0x80 #define TIMER1_MASK 0x40 #define TIMER2_MASK 0x20 #define TIMER1_START 0x01 #define TIMER2_START 0x02 #define CONNECTION_SELECT_REGISTER 0x04 /* Right side */ #define RIGHT_4OP_0 0x01 #define RIGHT_4OP_1 0x02 #define RIGHT_4OP_2 0x04 #define LEFT_4OP_0 0x08 #define LEFT_4OP_1 0x10 #define LEFT_4OP_2 0x20 #define OPL3_MODE_REGISTER 0x05 /* Right side */ #define OPL3_ENABLE 0x01 #define OPL4_ENABLE 0x02 #define KBD_SPLIT_REGISTER 0x08 /* Left side */ #define COMPOSITE_SINE_WAVE_MODE 0x80 /* Don't use with OPL-3? */ #define KEYBOARD_SPLIT 0x40 #define PERCUSSION_REGISTER 0xbd /* Left side only */ #define TREMOLO_DEPTH 0x80 #define VIBRATO_DEPTH 0x40 #define PERCOSSION_ENABLE 0x20 #define BASSDRUM_ON 0x10 #define SNAREDRUM_ON 0x08 #define TOMTOM_ON 0x04 #define CYMBAL_ON 0x02 #define HIHAT_ON 0x01 /* * Offsets to the register banks for operators. To get the * register number just add the operator offset to the bank offset * * AM/VIB/EG/KSR/Multiple (0x20 to 0x35) */ #define AM_VIB 0x20 #define TREMOLO_ON 0x80 #define VIBRATO_ON 0x40 #define SUSTAIN_ON 0x20 #define KSR 0x10 /* Key scaling rate */ #define MULTIPLE_MASK 0x0f /* Frequency multiplier */ /* * KSL/Total level (0x40 to 0x55) */ #define KSL_LEVEL 0x40 #define KSL_MASK 0xc0 /* Envelope scaling bits */ #define TOTAL_LEVEL_MASK 0x3f /* Strength (volume) of OP */ /* * Attack / Decay rate (0x60 to 0x75) */ #define ATTACK_DECAY 0x60 #define ATTACK_MASK 0xf0 #define DECAY_MASK 0x0f /* * Sustain level / Release rate (0x80 to 0x95) */ #define SUSTAIN_RELEASE 0x80 #define SUSTAIN_MASK 0xf0 #define RELEASE_MASK 0x0f /* * Wave select (0xE0 to 0xF5) */ #define WAVE_SELECT 0xe0 /* * Offsets to the register banks for voices. Just add to the * voice number to get the register number. * * F-Number low bits (0xA0 to 0xA8). */ #define FNUM_LOW 0xa0 /* * F-number high bits / Key on / Block (octave) (0xB0 to 0xB8) */ #define KEYON_BLOCK 0xb0 #define KEYON_BIT 0x20 #define BLOCKNUM_MASK 0x1c #define FNUM_HIGH_MASK 0x03 /* * Feedback / Connection (0xc0 to 0xc8) * * These registers have two new bits when the OPL-3 mode * is selected. These bits controls connecting the voice * to the stereo channels. For 4 OP voices this bit is * defined in the second half of the voice (add 3 to the * register offset). * * For 4 OP voices the connection bit is used in the * both halves (gives 4 ways to connect the operators). */ #define FEEDBACK_CONNECTION 0xc0 #define FEEDBACK_MASK 0x0e /* Valid just for 1st OP of a voice */ #define CONNECTION_BIT 0x01 /* * In the 4 OP mode there is four possible configurations how the * operators can be connected together (in 2 OP modes there is just * AM or FM). The 4 OP connection mode is defined by the rightmost * bit of the FEEDBACK_CONNECTION (0xC0-0xC8) on the both halves. * * First half Second half Mode * * +---+ * v | * 0 0 >+-1-+--2--3--4--> * * * * +---+ * | | * 0 1 >+-1-+--2-+ * |-> * >--3----4-+ * * +---+ * | | * 1 0 >+-1-+-----+ * |-> * >--2--3--4-+ * * +---+ * | | * 1 1 >+-1-+--+ * | * >--2--3-+-> * | * >--4----+ */ #define STEREO_BITS 0x30 /* OPL-3 only */ #define VOICE_TO_LEFT 0x10 #define VOICE_TO_RIGHT 0x20 #endif schismtracker-20180209/include/snd_gm.h000066400000000000000000000033401323741476300177000ustar00rootroot00000000000000#ifndef _BqtModplugSndGm #define _BqtModplugSndGm void GM_Patch(int c, unsigned char p, int pref_chn_mask); void GM_DPatch(int ch, unsigned char GM, unsigned char bank, int pref_chn_mask); void GM_Bank(int c, unsigned char b); void GM_Touch(int c, unsigned char Vol); // range 0..127 void GM_KeyOn(int c, unsigned char key, unsigned char Vol); // vol range 0..127 void GM_KeyOff(int c); void GM_Bend(int c, unsigned Count); void GM_Reset(int quitting); // 0=settings that work for us, 1=normal settings void GM_Pan(int ch, signed char val); // param: -128..+127 // This function is the core function for MIDI updates. // It handles keyons, touches and pitch bending. // channel = IT channel on which the event happens // Hertz = The hertz value for this note at the present moment // Vol = The volume for this note at this present moment (0..127) // bend_mode = This parameter can provide a hint for the tone calculator // for deciding the note to play. If it is to be expected that // a large bend up will follow, it may be a good idea to start // from a low bend to utilize the maximum bending scale. // keyoff = if nonzero, don't keyon // // Note that vibrato etc. are emulated by issuing multiple SetFreqAndVol // commands; they are not translated into MIDI vibrato operator calls. typedef enum { MIDI_BEND_NORMAL, MIDI_BEND_DOWN, MIDI_BEND_UP } MidiBendMode; void GM_SetFreqAndVol(int channel, int Hertz, int Vol, MidiBendMode bend_mode, int keyoff); void GM_SendSongStartCode(void); void GM_SendSongStopCode(void); void GM_SendSongContinueCode(void); void GM_SendSongTickCode(void); void GM_SendSongPositionCode(unsigned note16pos); void GM_IncrementSongCounter(int count); #endif schismtracker-20180209/include/sndfile.h000066400000000000000000000702461323741476300200660ustar00rootroot00000000000000/* * This source code is public domain. * * Authors: Olivier Lapicque , * Adam Goode (endian and char fixes for PPC) */ #ifndef __SNDFILE_H #define __SNDFILE_H #ifdef HAVE_CONFIG_H # include #endif #define NEED_BYTESWAP #include "headers.h" #include "disko.h" #include "tables.h" #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 236 #define MAX_INSTRUMENTS MAX_SAMPLES #define MAX_VOICES 256 #define MAX_CHANNELS 64 #define MAX_ENVPOINTS 32 #define MAX_INFONAME 80 #define MAX_EQ_BANDS 6 #define MAX_MESSAGE 8000 #define MIXBUFFERSIZE 512 #define CHN_16BIT 0x01 // 16-bit sample #define CHN_LOOP 0x02 // looped sample #define CHN_PINGPONGLOOP 0x04 // bi-directional (useless unless CHN_LOOP is also set) #define CHN_SUSTAINLOOP 0x08 // sample with sustain loop #define CHN_PINGPONGSUSTAIN 0x10 // bi-directional (useless unless CHN_SUSTAINLOOP is also set) #define CHN_PANNING 0x20 // sample with default panning set #define CHN_STEREO 0x40 // stereo sample #define CHN_PINGPONGFLAG 0x80 // when flag is on, sample is processed backwards #define CHN_MUTE 0x100 // muted channel #define CHN_KEYOFF 0x200 // exit sustain (note-off encountered) #define CHN_NOTEFADE 0x400 // fade note (~~~ or end of instrument envelope) #define CHN_SURROUND 0x800 // use surround channel (S91) #define CHN_NOIDO 0x1000 // near enough to an exact multiple of c5speed that interpolation // won't be noticeable (or interpolation is disabled completely) #define CHN_HQSRC 0x2000 // ??? #define CHN_FILTER 0x4000 // filtered output (i.e., Zxx) #define CHN_VOLUMERAMP 0x8000 // ramp volume #define CHN_VIBRATO 0x10000 // apply vibrato #define CHN_TREMOLO 0x20000 // apply tremolo //#define CHN_PANBRELLO 0x40000 // apply panbrello (handled elsewhere now) #define CHN_PORTAMENTO 0x80000 // apply portamento #define CHN_GLISSANDO 0x100000 // glissando mode ("stepped" pitch slides) #define CHN_VOLENV 0x200000 // volume envelope is active #define CHN_PANENV 0x400000 // pan envelope is active #define CHN_PITCHENV 0x800000 // pitch/filter envelope is active #define CHN_FASTVOLRAMP 0x1000000 // ramp volume very fast (XXX this is a dumb flag) //#define CHN_EXTRALOUD 0x2000000 //#define CHN_REVERB 0x4000000 //#define CHN_NOREVERB 0x8000000 #define CHN_NNAMUTE 0x10000000 // turn off mute, but have it reset later #define CHN_ADLIB 0x20000000 // OPL mode #define CHN_SAMPLE_FLAGS (CHN_16BIT | CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP \ | CHN_PINGPONGSUSTAIN | CHN_PANNING | CHN_STEREO | CHN_PINGPONGFLAG | CHN_ADLIB) #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 FX_NONE 0 // . #define FX_ARPEGGIO 1 // J #define FX_PORTAMENTOUP 2 // F #define FX_PORTAMENTODOWN 3 // E #define FX_TONEPORTAMENTO 4 // G #define FX_VIBRATO 5 // H #define FX_TONEPORTAVOL 6 // L #define FX_VIBRATOVOL 7 // K #define FX_TREMOLO 8 // R #define FX_PANNING 9 // X #define FX_OFFSET 10 // O #define FX_VOLUMESLIDE 11 // D #define FX_POSITIONJUMP 12 // B #define FX_VOLUME 13 // ! (FT2/IMF Cxx) #define FX_PATTERNBREAK 14 // C #define FX_RETRIG 15 // Q #define FX_SPEED 16 // A #define FX_TEMPO 17 // T #define FX_TREMOR 18 // I #define FX_SPECIAL 20 // S #define FX_CHANNELVOLUME 21 // M #define FX_CHANNELVOLSLIDE 22 // N #define FX_GLOBALVOLUME 23 // V #define FX_GLOBALVOLSLIDE 24 // W #define FX_KEYOFF 25 // $ (FT2 Kxx) #define FX_FINEVIBRATO 26 // U #define FX_PANBRELLO 27 // Y #define FX_PANNINGSLIDE 29 // P #define FX_SETENVPOSITION 30 // & (FT2 Lxx) #define FX_MIDI 31 // Z #define FX_NOTESLIDEUP 32 // ( (IMF Gxy) #define FX_NOTESLIDEDOWN 33 // ) (IMF Hxy) #define FX_MAX 34 #define FX_UNIMPLEMENTED FX_MAX // no-op, displayed as "?" #define FX_IS_EFFECT(v) ((v) > 0 && (v) < FX_MAX) // Volume Column commands #define VOLFX_NONE 0 #define VOLFX_VOLUME 1 #define VOLFX_PANNING 2 #define VOLFX_VOLSLIDEUP 3 // C #define VOLFX_VOLSLIDEDOWN 4 // D #define VOLFX_FINEVOLUP 5 // A #define VOLFX_FINEVOLDOWN 6 // B #define VOLFX_VIBRATOSPEED 7 // $ (FT2 Ax) #define VOLFX_VIBRATODEPTH 8 // H #define VOLFX_PANSLIDELEFT 9 // < (FT2 Dx) #define VOLFX_PANSLIDERIGHT 10 // > (FT2 Ex) #define VOLFX_TONEPORTAMENTO 11 // G #define VOLFX_PORTAUP 12 // F #define VOLFX_PORTADOWN 13 // E // orderlist #define ORDER_SKIP 254 // +++ #define ORDER_LAST 255 // --- // 'Special' notes // Note fade IS actually supported in Impulse Tracker, but there's no way to handle it in the editor // (Actually, any non-valid note is handled internally as a note fade, but it's good to have a single // value for internal representation) // update 20090805: ok just discovered that IT internally uses 253 for its "no note" value. // guess we'll use a different value for fade! // note: 246 is rather arbitrary, but IT conveniently displays this value as "F#D" ("FD" with 2-char notes) #define NOTE_NONE 0 // ... #define NOTE_FIRST 1 // C-0 #define NOTE_MIDC 61 // C-5 #define NOTE_LAST 120 // B-9 #define NOTE_FADE 246 // ~~~ #define NOTE_CUT 254 // ^^^ #define NOTE_OFF 255 // === #define NOTE_IS_NOTE(n) ((n) > NOTE_NONE && (n) <= NOTE_LAST) // anything playable - C-0 to B-9 #define NOTE_IS_CONTROL(n) ((n) > NOTE_LAST) // not a note, but non-empty #define NOTE_IS_INVALID(n) ((n) > NOTE_LAST && (n) < NOTE_CUT && (n) != NOTE_FADE) // ??? // Auto-vibrato types #define VIB_SINE 0 #define VIB_RAMP_DOWN 1 #define VIB_SQUARE 2 #define VIB_RANDOM 3 // 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 // DCA types #define DCA_NOTECUT 0 #define DCA_NOTEOFF 1 #define DCA_NOTEFADE 2 // Nothing innately special about this -- just needs to be above the max pattern length. // process row is set to this in order to get the player to jump to the end of the pattern. // (See ITTECH.TXT) #define PROCESS_NEXT_ORDER 0xFFFE // Module flags #define SONG_EMBEDMIDICFG 0x0001 // Embed MIDI macros (Shift-F1) in file //#define SONG_FASTVOLSLIDES 0x0002 #define SONG_ITOLDEFFECTS 0x0004 // Old Impulse Tracker effect implementations #define SONG_COMPATGXX 0x0008 // "Compatible Gxx" (handle portamento more like other trackers) #define SONG_LINEARSLIDES 0x0010 // Linear slides vs. Amiga slides #define SONG_PATTERNPLAYBACK 0x0020 // Only playing current pattern //#define SONG_STEP 0x0040 #define SONG_PAUSED 0x0080 // Playback paused (Shift-F8) //#define SONG_FADINGSONG 0x0100 #define SONG_ENDREACHED 0x0200 // Song is finished (standalone keyjazz mode) //#define SONG_GLOBALFADE 0x0400 //#define SONG_CPUVERYHIGH 0x0800 #define SONG_FIRSTTICK 0x1000 // Current tick is the first tick of the row (dopey flow-control flag) //#define SONG_MPTFILTERMODE 0x2000 //#define SONG_SURROUNDPAN 0x4000 //#define SONG_EXFILTERRANGE 0x8000 //#define SONG_AMIGALIMITS 0x10000 #define SONG_INSTRUMENTMODE 0x20000 // Process instruments #define SONG_ORDERLOCKED 0x40000 // Don't advance orderlist *(Alt-F11) #define SONG_NOSTEREO 0x80000 // secret code for "mono" #define SONG_PATTERNLOOP (SONG_PATTERNPLAYBACK | SONG_ORDERLOCKED) // Loop current pattern (F6) // Global Options (Renderer) #define SNDMIX_REVERSESTEREO 0x0001 // swap L/R audio channels //#define SNDMIX_NOISEREDUCTION 0x0002 // reduce hiss (do not use, it's just a simple low-pass filter) //#define SNDMIX_AGC 0x0004 // automatic gain control #define SNDMIX_NORESAMPLING 0x0008 // force no resampling (uninterpolated) #define SNDMIX_HQRESAMPLER 0x0010 // cubic resampling //#define SNDMIX_MEGABASS 0x0020 //#define SNDMIX_SURROUND 0x0040 //#define SNDMIX_REVERB 0x0080 //#define SNDMIX_EQ 0x0100 // apply EQ (always on) //#define SNDMIX_SOFTPANNING 0x0200 #define SNDMIX_ULTRAHQSRCMODE 0x0400 // polyphase resampling (or FIR? I don't know) // Misc Flags (can safely be turned on or off) #define SNDMIX_DIRECTTODISK 0x10000 // disk writer mode #define SNDMIX_NOBACKWARDJUMPS 0x40000 // disallow Bxx jumps from going backward in the orderlist //#define SNDMIX_MAXDEFAULTPAN 0x80000 // (no longer) Used by the MOD loader #define SNDMIX_MUTECHNMODE 0x100000 // Notes are not played on muted channels #define SNDMIX_NOSURROUND 0x200000 // ignore S91 //#define SNDMIX_NOMIXING 0x400000 #define SNDMIX_NORAMPING 0x800000 // don't apply ramping on volume change (causes clicks) enum { SRCMODE_NEAREST, SRCMODE_LINEAR, SRCMODE_SPLINE, SRCMODE_POLYPHASE, NUM_SRC_MODES }; // ------------------------------------------------------------------------------------------------------------ // Flags for csf_read_sample // Sample data characteristics // Note: // - None of these constants are zero // - The format specifier must have a value set for each "section" // - csf_read_sample DOES check the values for validity // Bit width (8 bits for simplicity) #define _SDV_BIT(n) ((n) << 0) #define SF_BIT_MASK 0xff #define SF_7 _SDV_BIT(7) // 7-bit (weird!) #define SF_8 _SDV_BIT(8) // 8-bit #define SF_16 _SDV_BIT(16) // 16-bit #define SF_24 _SDV_BIT(24) // 24-bit #define SF_32 _SDV_BIT(32) // 32-bit // Channels (4 bits) #define _SDV_CHN(n) ((n) << 8) #define SF_CHN_MASK 0xf00 #define SF_M _SDV_CHN(1) // mono #define SF_SI _SDV_CHN(2) // stereo, interleaved #define SF_SS _SDV_CHN(3) // stereo, split // Endianness (4 bits) #define _SDV_END(n) ((n) << 12) #define SF_END_MASK 0xf000 #define SF_LE _SDV_END(1) // little-endian #define SF_BE _SDV_END(2) // big-endian // Encoding (8 bits) #define _SDV_ENC(n) ((n) << 16) #define SF_ENC_MASK 0xff0000 #define SF_PCMS _SDV_ENC(1) // PCM, signed #define SF_PCMU _SDV_ENC(2) // PCM, unsigned #define SF_PCMD _SDV_ENC(3) // PCM, delta-encoded #define SF_IT214 _SDV_ENC(4) // Impulse Tracker 2.14 compressed #define SF_IT215 _SDV_ENC(5) // Impulse Tracker 2.15 compressed #define SF_AMS _SDV_ENC(6) // AMS / Velvet Studio packed #define SF_DMF _SDV_ENC(7) // DMF Huffman compression #define SF_MDL _SDV_ENC(8) // MDL Huffman compression #define SF_PTM _SDV_ENC(9) // PTM 8-bit delta value -> 16-bit sample #define SF_PCMD16 _SDV_ENC(10) // PCM, 16-byte table delta-encoded // Sample format shortcut #define SF(a,b,c,d) (SF_ ## a | SF_ ## b| SF_ ## c | SF_ ## d) // Deprecated constants #define RS_AMS16 SF(AMS,16,M,LE) #define RS_AMS8 SF(AMS,8,M,LE) #define RS_DMF16 SF(DMF,16,M,LE) #define RS_DMF8 SF(DMF,8,M,LE) #define RS_IT21416 SF(IT214,16,M,LE) #define RS_IT2148 SF(IT214,8,M,LE) #define RS_IT21516 SF(IT215,16,M,LE) #define RS_IT2158 SF(IT215,8,M,LE) #define RS_IT21416S SF(IT214,16,SS,LE) #define RS_IT2148S SF(IT214,8,SS,LE) #define RS_IT21516S SF(IT215,16,SS,LE) #define RS_IT2158S SF(IT215,8,SS,LE) #define RS_MDL16 SF(MDL,16,M,LE) #define RS_MDL8 SF(MDL,8,M,LE) #define RS_PCM16D SF(PCMD,16,M,LE) #define RS_PCM16M SF(PCMS,16,M,BE) #define RS_PCM16S SF(PCMS,16,M,LE) #define RS_PCM16U SF(PCMU,16,M,LE) #define RS_PCM24S SF(PCMS,24,M,LE) #define RS_PCM32S SF(PCMS,32,M,LE) #define RS_PCM8D SF(PCMD,8,M,LE) #define RS_PCM8D16 SF(PCMD16,8,M,LE) #define RS_PCM8S SF(PCMS,8,M,LE) #define RS_PCM8U SF(PCMU,8,M,LE) #define RS_PTM8DTO16 SF(PTM,16,M,LE) #define RS_STIPCM16M SF(PCMS,16,SI,BE) #define RS_STIPCM16S SF(PCMS,16,SI,LE) #define RS_STIPCM16U SF(PCMU,16,SI,LE) #define RS_STIPCM24S SF(PCMS,24,SI,LE) #define RS_STIPCM32S SF(PCMS,32,SI,LE) #define RS_STIPCM8S SF(PCMS,8,SI,LE) #define RS_STIPCM8U SF(PCMU,8,SI,LE) #define RS_STPCM16D SF(PCMD,16,SS,LE) #define RS_STPCM16M SF(PCMS,16,SS,BE) #define RS_STPCM16S SF(PCMS,16,SS,LE) #define RS_STPCM16U SF(PCMU,16,SS,LE) #define RS_STPCM8D SF(PCMD,8,SS,LE) #define RS_STPCM8S SF(PCMS,8,SS,LE) #define RS_STPCM8U SF(PCMU,8,SS,LE) // ------------------------------------------------------------------------------------------------------------ typedef struct song_sample { uint32_t length; uint32_t loop_start; uint32_t loop_end; uint32_t sustain_start; uint32_t sustain_end; signed char *data; uint32_t c5speed; uint32_t panning; uint32_t volume; uint32_t global_volume; uint32_t flags; uint32_t vib_type; uint32_t vib_rate; uint32_t vib_depth; uint32_t vib_speed; char name[32]; char filename[22]; int played; // for note playback dots uint32_t globalvol_saved; // for muting individual samples // This must be 12-bytes to work around a bug in some gcc4.2s (XXX why? what bug?) unsigned char adlib_bytes[12]; } song_sample_t; typedef struct song_envelope { int ticks[32]; uint8_t values[32]; int nodes; int loop_start; int loop_end; int sustain_start; int sustain_end; } song_envelope_t; typedef struct song_instrument { uint32_t fadeout; uint32_t flags; unsigned int global_volume; unsigned int panning; uint8_t sample_map[128]; uint8_t note_map[128]; song_envelope_t vol_env; song_envelope_t pan_env; song_envelope_t pitch_env; unsigned int nna; unsigned int dct; unsigned int dca; unsigned int pan_swing; unsigned int vol_swing; unsigned int ifc; unsigned int ifr; int midi_bank; // TODO split this? int midi_program; unsigned int midi_channel_mask; // FIXME why is this a mask? why is a mask useful? does 2.15 use a mask? int pitch_pan_separation; unsigned int pitch_pan_center; char name[32]; char filename[16]; int played; // for note playback dots } song_instrument_t; // (TODO write decent descriptions of what the various volume // variables are used for - are all of them *really* necessary?) // (TODO also the majority of this is irrelevant outside of the "main" 64 channels; // this struct should really only be holding the stuff actually needed for mixing) typedef struct song_voice { // First 32-bytes: Most used mixing information: don't change it signed char * current_sample_data; uint32_t position; // sample position, fixed-point -- integer part uint32_t position_frac; // fractional part int32_t increment; // 16.16 fixed point, how much to add to position per sample-frame of output int32_t right_volume; // ? int32_t left_volume; // ? int32_t right_ramp; // ? int32_t left_ramp; // ? // 2nd cache line uint32_t length; // only to the end of the loop uint32_t flags; uint32_t loop_start; // loop or sustain, whichever is active uint32_t loop_end; int32_t right_ramp_volume; // ? int32_t left_ramp_volume; // ? int32_t strike; // decremented to zero. this affects how long the initial hit on the playback marks lasts (bigger dot in instrument and sample list windows) int32_t filter_y1, filter_y2, filter_y3, filter_y4; int32_t filter_a0, filter_b0, filter_b1; int32_t rofs, lofs; // ? int32_t ramp_length; // Information not used in the mixer int32_t right_volume_new, left_volume_new; // ? int32_t final_volume; // range 0-16384 (?), accounting for sample+channel+global+etc. volumes int32_t final_panning; // range 0-256 (but can temporarily exceed that range during calculations) int32_t volume, panning; // range 0-256 (?); these are the current values set for the channel int32_t fadeout_volume; int32_t period; int32_t c5speed; int32_t sample_freq; // only used on the info page (F5) int32_t portamento_target; song_instrument_t *ptr_instrument; // these two suck, and should song_sample_t *ptr_sample; // be replaced with numbers int vol_env_position; int pan_env_position; int pitch_env_position; uint32_t master_channel; // nonzero = background/NNA voice, indicates what channel it "came from" uint32_t vu_meter; // TODO: As noted elsewhere, this means current channel volume. int32_t global_volume; // FIXME: Here instrument_volume means the value calculated from sample global volume and instrument global volume. // And we miss a value for "running envelope volume" for the page_info int32_t instrument_volume; int32_t autovib_depth; uint32_t autovib_position, vibrato_position, tremolo_position, panbrello_position; // 16-bit members int vol_swing, pan_swing; // formally 8-bit members unsigned int note; // the note that's playing unsigned int nna; unsigned int new_note, new_instrument; // ? // Effect memory and handling unsigned int n_command; // This sucks and needs to go away (dumb "flag" for arpeggio / tremor) unsigned int mem_vc_volslide; // Ax Bx Cx Dx (volume column) unsigned int mem_arpeggio; // Axx unsigned int mem_volslide; // Dxx unsigned int mem_pitchslide; // Exx Fxx (and Gxx maybe) int32_t mem_portanote; // Gxx (synced with mem_pitchslide if compat gxx is set) unsigned int mem_tremor; // Ixx unsigned int mem_channel_volslide; // Nxx unsigned int mem_offset; // final, combined yxx00h from Oxx and SAy unsigned int mem_panslide; // Pxx unsigned int mem_retrig; // Qxx unsigned int mem_special; // Sxx unsigned int mem_tempo; // Txx unsigned int mem_global_volslide; // Wxx unsigned int note_slide_counter, note_slide_speed, note_slide_step; // IMF effect unsigned int vib_type, vibrato_speed, vibrato_depth; unsigned int tremolo_type, tremolo_speed, tremolo_depth; unsigned int panbrello_type, panbrello_speed, panbrello_depth; int tremolo_delta, panbrello_delta; unsigned int cutoff; unsigned int resonance; int cd_note_delay; // countdown: note starts when this hits zero int cd_note_cut; // countdown: note stops when this hits zero int cd_retrig; // countdown: note retrigs when this hits zero unsigned int cd_tremor; // (weird) countdown + flag: see snd_fx.c and sndmix.c unsigned int patloop_row; // row number that SB0 was on unsigned int cd_patloop; // countdown: pattern loops back when this hits zero unsigned int row_note, row_instr; unsigned int row_voleffect, row_volparam; unsigned int row_effect, row_param; unsigned int active_macro, last_instrument; } song_voice_t; typedef struct song_channel { uint32_t panning; uint32_t volume; uint32_t flags; } song_channel_t; typedef struct song_note { uint8_t note; uint8_t instrument; uint8_t voleffect; uint8_t volparam; uint8_t effect; uint8_t param; } song_note_t; //////////////////////////////////////////////////////////////////// typedef struct { char start[32]; char stop[32]; char tick[32]; char note_on[32]; char note_off[32]; char set_volume[32]; char set_panning[32]; char set_bank[32]; char set_program[32]; char sfx[16][32]; char zxx[128][32]; } midi_config_t; extern midi_config_t default_midi_config; extern uint32_t max_voices; extern uint32_t global_vu_left, global_vu_right; extern const song_note_t blank_pattern[64 * 64]; extern const song_note_t *blank_note; struct multi_write { int used; void *data; /* Conveniently, this has the same prototype as disko_write :) */ void (*write)(void *data, const uint8_t *buf, size_t bytes); /* this is optimization for channels that haven't had any data yet (nothing to convert/write, just seek ahead in the data stream) */ void (*silence)(void *data, long bytes); int buffer[MIXBUFFERSIZE * 2]; }; typedef struct song { int mix_buffer[MIXBUFFERSIZE * 2]; float mix_buffer_float[MIXBUFFERSIZE * 2]; // is this needed? song_voice_t voices[MAX_VOICES]; // Channels uint32_t voice_mix[MAX_VOICES]; // Channels to be mixed song_sample_t samples[MAX_SAMPLES+1]; // Samples (1-based!) song_instrument_t *instruments[MAX_INSTRUMENTS+1]; // Instruments (1-based!) song_channel_t channels[MAX_CHANNELS]; // Channel settings song_note_t *patterns[MAX_PATTERNS]; // Patterns uint16_t pattern_size[MAX_PATTERNS]; // Pattern Lengths uint16_t pattern_alloc_size[MAX_PATTERNS]; // Allocated lengths (for async. resizing/playback) uint8_t orderlist[MAX_ORDERS + 1]; // Pattern Orders midi_config_t midi_config; // Midi macro config table uint32_t initial_speed; uint32_t initial_tempo; uint32_t initial_global_volume; uint32_t flags; // Song flags SONG_XXXX uint32_t pan_separation; uint32_t num_voices; // how many are currently playing. (POTENTIALLY larger than global max_voices) uint32_t mix_stat; // number of channels being mixed (not really used) uint32_t buffer_count; // number of samples to mix per tick uint32_t tick_count; int32_t row_count; /* IMPORTANT needs to be signed */ uint32_t current_speed; uint32_t current_tempo; uint32_t process_row; uint32_t row; // no analogue in pm.h? should be either renamed or factored out. uint32_t break_row; uint32_t current_pattern; uint32_t current_order; uint32_t process_order; uint32_t current_global_volume; uint32_t mixing_volume; uint32_t freq_factor; // not used -- for tweaking the song speed LP-style (interesting!) uint32_t tempo_factor; // ditto int32_t repeat_count; // 0 = first playback, etc. (note: set to -1 to stop instead of looping) uint8_t row_highlight_major; uint8_t row_highlight_minor; char message[MAX_MESSAGE + 1]; char title[32]; char tracker_id[32]; // irrelevant to the song, just used by some loaders (fingerprint) // These store the existing IT save history from prior editing sessions. // Current session data is added at save time, and is NOT a part of histdata. int histlen; // How many session history data entries exist (each entry is eight bytes) uint8_t *histdata; // Preserved entries from prior sessions, might be NULL if histlen = 0 struct timeval editstart; // When the song was loaded // mixer stuff uint32_t mix_flags; // SNDMIX_* uint32_t mix_frequency, mix_bits_per_sample, mix_channels; // noise reduction filter int32_t left_nr, right_nr; // chaseback int stop_at_order; int stop_at_row; unsigned int stop_at_time; // multi-write stuff -- NULL if no multi-write is in progress, else array of one struct per channel struct multi_write *multi_write; } song_t; song_note_t *csf_allocate_pattern(uint32_t rows); void csf_free_pattern(void *pat); signed char *csf_allocate_sample(uint32_t nbytes); void csf_free_sample(void *p); song_instrument_t *csf_allocate_instrument(void); void csf_init_instrument(song_instrument_t *ins, int samp); void csf_free_instrument(song_instrument_t *p); uint32_t csf_read_sample(song_sample_t *sample, uint32_t flags, const void *filedata, uint32_t datalength); uint32_t csf_write_sample(disko_t *fp, song_sample_t *sample, uint32_t flags); void csf_adjust_sample_loop(song_sample_t *sample); extern void (*csf_midi_out_note)(int chan, const song_note_t *m); extern void (*csf_midi_out_raw)(const unsigned char *, unsigned int, unsigned int); void csf_import_mod_effect(song_note_t *m, int from_xm); uint16_t csf_export_mod_effect(const song_note_t *m, int xm); void csf_import_s3m_effect(song_note_t *m, int it); void csf_export_s3m_effect(uint8_t *pcmd, uint8_t *pprm, int it); // counting stuff int csf_note_is_empty(song_note_t *note); int csf_pattern_is_empty(song_t *csf, int n); int csf_sample_is_empty(song_sample_t *smp); int csf_instrument_is_empty(song_instrument_t *ins); int csf_last_order(song_t *csf); // last order of "main" song (IT-style, only for display) int csf_get_num_orders(song_t *csf); // last non-blank order (for saving) int csf_get_num_patterns(song_t *csf); int csf_get_num_samples(song_t *csf); int csf_get_num_instruments(song_t *csf); // for these, 'start' indicates minimum sample/instrument to check int csf_first_blank_sample(song_t *csf, int start); int csf_first_blank_instrument(song_t *csf, int start); int csf_get_highest_used_channel(song_t *csf); int csf_set_wave_config(song_t *csf, uint32_t rate, uint32_t bits, uint32_t channels); // Mixer Config int csf_init_player(song_t *csf, int reset); // bReset=false int csf_set_resampling_mode(song_t *csf, uint32_t mode); // SRCMODE_XXXX // sndmix unsigned int csf_read(song_t *csf, void *v_buffer, unsigned int bufsize); int csf_process_tick(song_t *csf); int csf_read_note(song_t *csf); // snd_fx unsigned int csf_get_length(song_t *csf); // (in seconds) void csf_instrument_change(song_t *csf, song_voice_t *chn, uint32_t instr, int porta, int instr_column); void csf_note_change(song_t *csf, uint32_t chan, int note, int porta, int retrig, int have_inst); uint32_t csf_get_nna_channel(song_t *csf, uint32_t chan); void csf_check_nna(song_t *csf, uint32_t chan, uint32_t instr, int note, int force_cut); void csf_process_effects(song_t *csf, int firsttick); void fx_note_cut(song_t *csf, uint32_t chan, int clear_note); void fx_key_off(song_t *csf, uint32_t chan); void csf_midi_send(song_t *csf, const unsigned char *data, unsigned int len, uint32_t chan, int fake); void csf_process_midi_macro(song_t *csf, uint32_t chan, const char *midi_macro, uint32_t param, uint32_t note, uint32_t velocity, uint32_t use_instr); song_sample_t *csf_translate_keyboard(song_t *csf, song_instrument_t *ins, uint32_t note, song_sample_t *def); // various utility functions in snd_fx.c int get_note_from_period(int period); int get_period_from_note(int note, unsigned int c5speed, int linear); unsigned int get_freq_from_period(int period, int linear); unsigned int transpose_to_frequency(int transp, int ftune); int frequency_to_transpose(unsigned int freq); unsigned long calc_halftone(unsigned long hz, int rel); // sndfile song_t *csf_allocate(void); void csf_free(song_t *csf); void csf_destroy(song_t *csf); /* erase everything -- equiv. to new song */ int csf_destroy_sample(song_t *csf, uint32_t smpnum); void csf_stop_sample(song_t *csf, song_sample_t *smp); void csf_reset_midi_cfg(song_t *csf); void csf_copy_midi_cfg(song_t *dest, song_t *src); void csf_set_current_order(song_t *csf, uint32_t position); void csf_loop_pattern(song_t *csf, int pattern, int start_row); void csf_reset_playmarks(song_t *csf); void csf_insert_restart_pos(song_t *csf, uint32_t restart_order); // hax void csf_forget_history(song_t *csf); // Send the edit log down the memory hole. /* apply a preset Adlib patch */ void adlib_patch_apply(song_sample_t *smp, int patchnum); /////////////////////////////////////////////////////////// // Return (a*b)/c - no divide error static inline int _muldiv(int a, int b, int c) { return ((unsigned long long) a * (unsigned long long) b ) / c; } // Return (a*b+c/2)/c - no divide error static inline int _muldivr(int a, int b, int c) { return ((unsigned long long) a * (unsigned long long) b + (c >> 1)) / c; } #endif schismtracker-20180209/include/song.h000066400000000000000000000315371323741476300174100ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #include "sndfile.h" #include "util.h" #include "disko.h" #include "fmt.h" /* --------------------------------------------------------------------- */ /* things that used to be in mplink */ extern song_t *current_song; 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 int samples_played; extern unsigned int max_channels_used; /* --------------------------------------------------------------------- */ /* 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 surround_effect; unsigned int eq_freq[4]; unsigned int eq_gain[4]; int no_ramping; }; extern struct audio_settings audio_settings; /* --------------------------------------------------------------------- */ /* some enums */ /* 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, }; /* --------------------------------------------------------------------- */ /* song_load: prompt ok/cancel if the existing song hasn't been saved. after loading, the current page is changed accordingly. this loads into the global song. song_load_unchecked: *NO* dialog, just goes right into loading the song doesn't set the page after loading. this also loads into the global song. return value is nonzero if the load was successful. generally speaking, don't use this function directly; use song_load instead. song_create_load: internal back-end function that loads and returns a song. the above functions both use this. */ void song_new(int flags); void song_load(const char *file); int song_load_unchecked(const char *file); song_t *song_create_load(const char *file); // song_create_load returns NULL on error and sets errno to what might not be a standard value // use this to divine the meaning of these cryptic numbers const char *fmt_strerror(int n); int song_save(const char *file, const char *type); // IT, S3M int song_export(const char *file, const char *type); // WAV /* 'num' is only for status text feedback -- all of the sample's data is taken from 'smp'. this provides an eventual mechanism for saving samples modified from disk (not yet implemented) */ int song_save_sample(const char *file, const char *type, song_sample_t *smp, int num); void song_clear_sample(int n); void song_copy_sample(int n, song_sample_t *src); int song_load_sample(int n, const char *file); void song_create_host_instrument(int smp); 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); int song_sample_is_empty(int n); /* search the orderlist for a pattern, starting at the current order. return value of -1 means the pattern isn't on the list */ int song_next_order_for_pattern(int pat); const char *song_get_filename(void); const char *song_get_basename(void); const char *song_get_tracker_id(void); char *song_get_title(void); // editable char *song_get_message(void); // editable // returned value = seconds unsigned int song_get_length_to(int order, int row); void song_get_at_time(unsigned int 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_t *song_pattern_allocate(int rows); song_note_t *song_pattern_allocate_copy(int patno, int *rows); void song_pattern_deallocate(song_note_t *n); void song_pattern_install(int patno, song_note_t *n, int rows); // these return NULL on failure. song_sample_t *song_get_sample(int n); song_instrument_t *song_get_instrument(int n); int song_get_instrument_number(song_instrument_t *ins); // 0 => no instrument; ignore above comment =) song_channel_t *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_save_channel_states(void); void song_restore_channel_states(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_t ** buf); // return 0 -> error uint8_t *song_get_orderlist(void); int song_pattern_is_empty(int p); 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); /* called from song_set_stereo et al - this updates the value on F12 to match the song */ void song_vars_sync_stereo(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); /* called later at startup, and also when the relevant settings are changed */ void song_init_modplug(void); /* Called at startup. The 'driver_spec' parameter is formatted as driver[:device]. 'driver' is the name of the SDL driver to use example: "alsa", "dsound" SDL_AUDIODRIVER is set to this value 'device' (optional) is the name of the device to use example: "hw:2", "/dev/dsp" SDL_PATH_DSP and AUDIODEV are set to this For the SDL driver, 'nosound' and 'none' are aliases for 'dummy', for compatibility with previous Schism Tracker versions, and 'oss' is an alias for 'dsp', because 'dsp' is a dumb name for an audio driver. */ void audio_init(const char *driver_spec); /* Reconfigure the same device that was opened before. */ void audio_reinit(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); void song_toggle_multichannel_mode(void); int song_is_multichannel_mode(void); void song_change_current_play_channel(int relative, int wraparound); int song_get_current_play_channel(void); /* These return the channel that was used for the note. Sample/inst slots 1+ are used "normally"; the sample loader uses slot #0 for preview playback -- but reports KEYJAZZ_INST_FAKE to keydown/up, since zero conflicts with the standard "use previous sample for this channel" behavior which is normally internal, but is exposed on the pattern editor where it's possible to explicitly select sample #0. (note: this is a hack to work around another hack) */ #define KEYJAZZ_CHAN_CURRENT 0 #define KEYJAZZ_NOINST -1 #define KEYJAZZ_DEFAULTVOL -1 #define KEYJAZZ_INST_FAKE -2 int song_keydown(int samp, int ins, int note, int vol, int chan); int song_keyrecord(int samp, int ins, int note, int vol, int chan, int effect, int param); int song_keyup(int samp, int ins, int note); void song_start(void); void song_start_once(void); void song_pause(void); void song_stop(void); void song_stop_unlocked(int quitting); 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 int 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_tempo(int t); 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_voice_t *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 int *channel_list; * song_voice_t *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 int **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_copy_instrument(int dst, int src); void song_replace_sample(int num, int with); void song_replace_instrument(int num, int with); 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 */ void song_init_instrument_from_sample(int ins, int samp); /* --------------------------------------------------------------------- */ /* misc. */ /* called by audio system when buffer stuff change */ void midi_queue_alloc(int buffer_size, int channels, int samples_per_second); void song_flip_stereo(void); int song_get_surround(void); void song_set_surround(int on); /* for the orderpan page */ enum { PANS_STEREO, PANS_AMIGA, PANS_LEFT, PANS_RIGHT, PANS_MONO, PANS_SLASH, PANS_BACKSLASH, // PANS_CROSS, }; void song_set_pan_scheme(int scheme); /* --------------------------------------------------------------------- */ #endif /* ! SONG_H */ schismtracker-20180209/include/tables.h000066400000000000000000000037211323741476300177060ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 TABLES_H #define TABLES_H #include // better than having a table. #define SHORT_PANNING(i) (((((i) << 4) | (i)) + 2) >> 2) /* TODO: I know just sticking _fast on all of these will break the player, but for some of 'em...? */ extern const uint8_t vc_portamento_table[16]; // volume column Gx extern const uint16_t period_table[12]; extern const uint16_t finetune_table[16]; extern const int8_t sine_table[256]; extern const int8_t ramp_down_table[256]; extern const int8_t square_table[256]; extern const int8_t retrig_table_1[16]; extern const int8_t retrig_table_2[16]; extern const uint32_t fine_linear_slide_up_table[16]; extern const uint32_t fine_linear_slide_down_table[16]; extern const uint32_t linear_slide_up_table[256]; extern const uint32_t linear_slide_down_table[256]; extern const char *midi_group_names[17]; extern const char *midi_program_names[128]; extern const char *midi_percussion_names[61]; #endif /* ! TABLES_H */ schismtracker-20180209/include/tree.h000066400000000000000000000046441323741476300174000ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 TREE_H #define TREE_H /* opaque structure */ typedef struct tree tree_t; /* This function should behave like strcmp. (i.e. return < 0, 0, or > 0 depending on how 'a' relates to 'b') */ typedef int (*treecmp_t) (const void *a, const void *b); /* warning; don't change any part of value that would alter the return of treecmp! */ typedef void (*treewalk_t) (void *value); /* Create a new tree. */ tree_t *tree_alloc(treecmp_t cmp); /* Deallocate a tree. 'freeval' is called for each node in the tree. */ void tree_free(tree_t *tree, treewalk_t freeval); /* Postorder traversal. */ void tree_walk(tree_t *tree, treewalk_t apply); /* On successful insert, this function returns NULL. If one of the items in the tree compares equal to 'value', the tree is not modified, and the existing value in the tree is returned. */ void *tree_insert(tree_t *tree, void *value); /* On successful insert, this function returns NULL. If one of the items in the tree compares equal to 'value', its value is replaced and the previous value is returned. It is entirely possible to wrap this function in a free() call to deallocate the old data. */ void *tree_replace(tree_t *tree, void *value); /* If one of the items in the tree compares equal to 'value', its value is returned. Otherwise, this function returns NULL. (Only the parts of 'value' relevant to 'cmp' need be filled in.) */ void *tree_find(tree_t *tree, void *value); #endif /* ! TREE_H */ schismtracker-20180209/include/util.h000066400000000000000000000147621323741476300174200ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #include /* roundabout way to get time_t */ #include /* --------------------------------------------------------------------- */ #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 LIKELY # define LIKELY(x) __builtin_expect(!!(x),1) # endif # ifndef UNLIKELY # define UNLIKELY(x) __builtin_expect(!!(x),0) # endif # ifndef UNUSED # define UNUSED __attribute__((unused)) # endif # ifndef NORETURN # define NORETURN __attribute__((noreturn)) # endif # ifndef PACKED # define PACKED __attribute__((packed)) # endif # ifndef MALLOC # define MALLOC __attribute__ ((malloc)) # endif #else # ifndef UNUSED # define UNUSED # endif # ifndef PACKED # define PACKED # endif # ifndef NORETURN # define NORETURN # endif # ifndef LIKELY # define LIKELY(x) # endif # ifndef UNLIKELY # define UNLIKELY(x) # endif # ifndef MALLOC # define MALLOC # endif #endif /* Path stuff that differs by platform */ #ifdef WIN32 # define DIR_SEPARATOR '\\' # define DIR_SEPARATOR_STR "\\" # define IS_DIR_SEPARATOR(c) ((c) == '/' || (c) == '\\') #else # define DIR_SEPARATOR '/' # define DIR_SEPARATOR_STR "/" # define IS_DIR_SEPARATOR(c) ((c) == '/') #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. */ /* memory */ extern MALLOC void *mem_alloc(size_t); extern MALLOC void *mem_calloc(size_t, size_t); extern MALLOC char *str_dup(const char *); extern MALLOC char *strn_dup(const char *, size_t); extern void *mem_realloc(void *,size_t); extern void mem_free(void *); /*Conversion*/ /* linear -> deciBell*/ /* amplitude normalized to 1.0f.*/ extern float dB(float amplitude); /// deciBell -> linear*/ extern float dB2_amp(float db); /* linear -> deciBell*/ /* power normalized to 1.0f.*/ extern float pdB(float power); /* deciBell -> linear*/ extern float dB2_power(float db); /* linear -> deciBell*/ /* amplitude normalized to 1.0f.*/ /* Output scaled (and clipped) to 128 lines with noisefloor range.*/ /* ([0..128] = [-noisefloor..0dB])*/ /* correction_dBs corrects the dB after converted, but before scaling.*/ extern short dB_s(int noisefloor, float amplitude, float correction_dBs); /* deciBell -> linear*/ /* Input scaled to 128 lines with noisefloor range.*/ /* ([0..128] = [-noisefloor..0dB])*/ /* amplitude normalized to 1.0f.*/ /* correction_dBs corrects the dB after converted, but before scaling.*/ extern short dB2_amp_s(int noisefloor, int db, float correction_dBs); /* linear -> deciBell*/ /* power normalized to 1.0f.*/ /* Output scaled (and clipped) to 128 lines with noisefloor range.*/ /* ([0..128] = [-noisefloor..0dB])*/ /* correction_dBs corrects the dB after converted, but before scaling.*/ extern short pdB_s(int noisefloor, float power, float correction_dBs); /* deciBell -> linear*/ /* Input scaled to 128 lines with noisefloor range.*/ /* ([0..128] = [-noisefloor..0dB])*/ /* power normalized to 1.0f.*/ /* correction_dBs corrects the dB after converted, but before scaling.*/ extern short dB2_power_s(int noisefloor, int db, float correction_dBs); /* formatting */ /* for get_{time,date}_string, buf should be (at least) 27 chars; anything past that isn't used. */ char *get_date_string(time_t when, char *buf); char *get_time_string(time_t when, char *buf); char *numtostr(int digits, unsigned int n, char *buf); char *numtostr_signed(int digits, int n, char *buf); char *num99tostr(int n, char *buf); /* string handling */ const char *get_basename(const char *filename); const char *get_extension(const char *filename); // including dot; "" if no extension (note: used to strip dot) char *get_parent_directory(const char *dirname); int ltrim_string(char *s); // return: length of string after trimming int rtrim_string(char *s); int trim_string(char *s); int str_break(const char *s, char c, char **first, char **second); char *str_escape(const char *source, int 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 */ int make_backup_file(const char *filename, int numbered); long file_size(const char *filename); int is_directory(const char *filename); /* following functions should free() the resulting strings */ char *get_home_directory(void); /* "default" directory for user files, i.e. $HOME, My Documents, etc. */ char *get_dot_directory(void); /* where settings files go (%AppData% on Windows, same as $HOME elsewhere) */ char *get_current_directory(void); /* just a getcwd() wrapper */ void put_env_var(const char *key, const char *value); void unset_env_var(const char *key); /* integer sqrt (very fast; 32 bits limited) */ unsigned int i_sqrt(unsigned int r); /* sleep in msec */ void ms_sleep(unsigned int m); /* run a hook */ int run_hook(const char *dir, const char *name, const char *maybe_arg); /* Mostly a glorified rename(), with fixes for certain dumb OSes. If 'overwrite' is zero, attempts to rename over an existing file will fail with EEXIST. */ int rename_file(const char *old, const char *newf, int overwrite); #endif /* ! UTIL_H */ schismtracker-20180209/include/version.h000066400000000000000000000027761323741476300201320ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 SCHISM_VERSION_H #define SCHISM_VERSION_H /* various boilerplate defined in version.c */ extern const char *ver_short_copyright; extern const char *ver_short_based_on; extern short ver_cwtv; /* lower 12 bits of the IT/S3M cwtv field */ extern const char *schism_banner(int classic) __attribute__((pure)); /* little hack, need to call this at startup */ void ver_init(void); /* get yyyy-mm-dd or 0.nn version from cwtv (buf should be >=11 chars) */ void ver_decode_cwtv(uint16_t cwtv, char *buf); #endif schismtracker-20180209/include/vgamem-scanner.h000066400000000000000000000046271323741476300213450ustar00rootroot00000000000000/* should be included inside draw-char.c */ void F1(unsigned int ry, unsigned SIZE *out, unsigned int tc[16], unsigned int mouseline[80]) { unsigned int *bp; unsigned int dg; unsigned char *q; uint8_t *itf, *bios, *bioslow, *hf; unsigned int x, y; int fg, bg; q = ovl + (ry * 640); y = ry >> 3; bp = &vgamem_read[y * 80]; itf = font_data + (ry & 7); bios = ((uint8_t*) font_default_upper_alt) + (ry & 7); bioslow = ((uint8_t *) font_default_lower) + (ry & 7); hf = font_half_data + ((ry & 7) >> 1); for (x = 0; x < 80; x++, bp++, q += 8) { if (!(*bp & 0xc0000000)) { /* regular character */ fg = (*bp & 0x0F00) >> 8; bg = (*bp & 0xF000) >> 12; if (*bp & 0x10000000 && (*bp & 0x80)) { dg = bios[(*bp & 0x7F)<< 3]; } else if (*bp & 0x10000000) { dg = bioslow[(*bp & 0x7F)<< 3]; } else { dg = itf[(*bp & 0xFF)<< 3]; } dg ^= mouseline[x]; 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]; } else if (*bp & 0x80000000) { *out++ = tc[ (q[0]^((mouseline[x] & 0x80)?15:0)) & 255]; *out++ = tc[ (q[1]^((mouseline[x] & 0x40)?15:0)) & 255]; *out++ = tc[ (q[2]^((mouseline[x] & 0x20)?15:0)) & 255]; *out++ = tc[ (q[3]^((mouseline[x] & 0x10)?15:0)) & 255]; *out++ = tc[ (q[4]^((mouseline[x] & 0x08)?15:0)) & 255]; *out++ = tc[ (q[5]^((mouseline[x] & 0x04)?15:0)) & 255]; *out++ = tc[ (q[6]^((mouseline[x] & 0x02)?15:0)) & 255]; *out++ = tc[ (q[7]^((mouseline[x] & 0x01)?15:0)) & 255]; } else { /* if (*bp & 0x40000000) { */ /* half-width character */ fg = (*bp >> 22) & 15; bg = (*bp >> 18) & 15; dg = hf[ _unpack_halfw((*bp >> 7) & 127) << 2]; if (!(ry & 1)) dg = (dg >> 4); dg ^= mouseline[x]; *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) & 127) << 2]; if (!(ry & 1)) dg = (dg >> 4); dg ^= mouseline[x]; *out++ = tc[(dg & 0x8) ? fg : bg]; *out++ = tc[(dg & 0x4) ? fg : bg]; *out++ = tc[(dg & 0x2) ? fg : bg]; *out++ = tc[(dg & 0x1) ? fg : bg]; } } } schismtracker-20180209/include/video.h000066400000000000000000000066221323741476300175450ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 char *q; /* points inside ovl */ unsigned int skip; int width, height; /* in pixels; signed to avoid bugs elsewhere */ }; void vgamem_lock(void); void vgamem_unlock(void); void vgamem_flip(void); void vgamem_ovl_alloc(struct vgamem_overlay *n); void vgamem_ovl_apply(struct vgamem_overlay *n); void vgamem_ovl_clear(struct vgamem_overlay *n, int color); void vgamem_ovl_drawpixel(struct vgamem_overlay *n, int x, int y, int color); void vgamem_ovl_drawline(struct vgamem_overlay *n, int xs, int ys, int xe, int ye, int color); void vgamem_scan32(unsigned int y,unsigned int *out,unsigned int tc[16], unsigned int mouse_line[80]); void vgamem_scan16(unsigned int y,unsigned short *out,unsigned int tc[16], unsigned int mouse_line[80]); void vgamem_scan8(unsigned int y,unsigned char *out,unsigned int tc[16], unsigned int mouse_line[80]); /* video output routines */ const char *video_driver_name(void); void video_setup(const char *driver); void video_startup(void); void video_shutdown(void); void video_report(void); void video_refresh(void); 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_mousecursor_visible(void); int video_gl_bilinear(void); int video_is_fullscreen(void); int video_width(void); int video_height(void); SDL_Surface *xpmdata(const char *xpmdata[]); #if USE_X11 unsigned int xv_yuvlayout(void); #endif #define VIDEO_YUV_UYVY 0x59565955 #define VIDEO_YUV_YUY2 0x32595559 #define VIDEO_YUV_YV12 0x32315659 #define VIDEO_YUV_IYUV 0x56555949 #define VIDEO_YUV_YVYU 0x55595659 #define VIDEO_YUV_YV12_TV (VIDEO_YUV_YV12 ^ 0xFFFFFFFF) #define VIDEO_YUV_IYUV_TV (VIDEO_YUV_IYUV ^ 0xFFFFFFFF) #define VIDEO_YUV_RGBA 0x41424752 #define VIDEO_YUV_RGBT 0x54424752 #define VIDEO_YUV_RGB565 0x32424752 #define VIDEO_YUV_RGB24 0x0 #define VIDEO_YUV_RGB32 0x3 #define VIDEO_YUV_NONE 0xFFFFFFFF #endif schismtracker-20180209/player/000077500000000000000000000000001323741476300161315ustar00rootroot00000000000000schismtracker-20180209/player/csndfile.c000066400000000000000000001260441323741476300200730ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #define NEED_TIME #include "headers.h" #include #include #include #include "sndfile.h" #include "log.h" #include "util.h" #include "fmt.h" // for it_decompress8 / it_decompress16 static void _csf_reset(song_t *csf) { unsigned int i; csf->flags = 0; csf->pan_separation = 128; csf->num_voices = 0; csf->freq_factor = csf->tempo_factor = 128; csf->initial_global_volume = 128; csf->current_global_volume = 128; csf->initial_speed = 6; csf->initial_tempo = 125; csf->process_row = 0; csf->row = 0; csf->current_pattern = 0; csf->current_order = 0; csf->process_order = 0; csf->mixing_volume = 0x30; memset(csf->message, 0, sizeof(csf->message)); csf->row_highlight_major = 16; csf->row_highlight_minor = 4; /* This is intentionally crappy quality, so that it's very obvious if it didn't get initialized */ csf->mix_flags = 0; csf_set_wave_config(csf, 4000, 8, 1); memset(csf->voices, 0, sizeof(csf->voices)); memset(csf->voice_mix, 0, sizeof(csf->voice_mix)); memset(csf->samples, 0, sizeof(csf->samples)); memset(csf->instruments, 0, sizeof(csf->instruments)); memset(csf->orderlist, 0xFF, sizeof(csf->orderlist)); memset(csf->patterns, 0, sizeof(csf->patterns)); csf_reset_midi_cfg(csf); csf_forget_history(csf); for (i = 0; i < MAX_PATTERNS; i++) { csf->pattern_size[i] = 64; csf->pattern_alloc_size[i] = 64; } for (i = 0; i < MAX_SAMPLES; i++) { csf->samples[i].c5speed = 8363; csf->samples[i].volume = 64 * 4; csf->samples[i].global_volume = 64; } for (i = 0; i < MAX_CHANNELS; i++) { csf->channels[i].panning = 128; csf->channels[i].volume = 64; csf->channels[i].flags = 0; } } ////////////////////////////////////////////////////////// // song_t song_t *csf_allocate(void) { song_t *csf = mem_calloc(1, sizeof(song_t)); _csf_reset(csf); return csf; } void csf_free(song_t *csf) { if (csf) { csf_destroy(csf); free(csf); } } static void _init_envelope(song_envelope_t *env, int n) { env->nodes = 2; env->ticks[0] = 0; env->ticks[1] = 100; env->values[0] = n; env->values[1] = n; } void csf_init_instrument(song_instrument_t *ins, int samp) { int n; memset(ins, 0, sizeof(*ins)); _init_envelope(&ins->vol_env, 64); _init_envelope(&ins->pan_env, 32); _init_envelope(&ins->pitch_env, 32); ins->global_volume = 128; ins->panning = 128; ins->midi_bank = -1; ins->midi_program = -1; ins->pitch_pan_center = 60; // why does pitch/pan not use the same note values as everywhere else?! for (n = 0; n < 128; n++) { ins->sample_map[n] = samp; ins->note_map[n] = n + 1; } } song_instrument_t *csf_allocate_instrument(void) { song_instrument_t *ins = mem_alloc(sizeof(song_instrument_t)); csf_init_instrument(ins, 0); return ins; } void csf_free_instrument(song_instrument_t *i) { free(i); } void csf_destroy(song_t *csf) { int i; for (i = 0; i < MAX_PATTERNS; i++) { if (csf->patterns[i]) { csf_free_pattern(csf->patterns[i]); csf->patterns[i] = NULL; } } for (i = 1; i < MAX_SAMPLES; i++) { song_sample_t *pins = &csf->samples[i]; if (pins->data) { csf_free_sample(pins->data); pins->data = NULL; } } for (i = 0; i < MAX_INSTRUMENTS; i++) { if (csf->instruments[i]) { csf_free_instrument(csf->instruments[i]); csf->instruments[i] = NULL; } } _csf_reset(csf); } song_note_t *csf_allocate_pattern(uint32_t rows) { return mem_calloc(rows * MAX_CHANNELS, sizeof(song_note_t)); } void csf_free_pattern(void *pat) { free(pat); } /* Note: this function will appear in valgrind to be a sieve for memory leaks. It isn't; it's just being confused by the adjusted pointer being stored. */ signed char *csf_allocate_sample(uint32_t nbytes) { signed char *p = mem_calloc(1, (nbytes + 39) & ~7); // magic if (p) p += 16; return p; } void csf_free_sample(void *p) { if (p) free(p - 16); } void csf_forget_history(song_t *csf) { free(csf->histdata); csf->histdata = NULL; csf->histlen = 0; gettimeofday(&csf->editstart, NULL); } /* --------------------------------------------------------------------------------------------------------- */ /* Counting and checking stuff. */ static int name_is_blank(char *name) { int n; for (n = 0; n < 25; n++) { if (name[n] != '\0' && name[n] != ' ') return 0; } return 1; } const song_note_t blank_pattern[64 * 64]; const song_note_t *blank_note = blank_pattern; // Same thing, really. int csf_note_is_empty(song_note_t *note) { return !memcmp(note, blank_pattern, sizeof(song_note_t)); } int csf_pattern_is_empty(song_t *csf, int n) { if (!csf->patterns[n]) return 1; if (csf->pattern_size[n] != 64) return 0; return !memcmp(csf->patterns[n], blank_pattern, sizeof(blank_pattern)); } int csf_sample_is_empty(song_sample_t *smp) { return (smp->data == NULL && name_is_blank(smp->name) && smp->filename[0] == '\0' && smp->c5speed == 8363 && smp->volume == 64*4 //mphack && smp->global_volume == 64 && smp->panning == 0 && !(smp->flags & (CHN_LOOP | CHN_SUSTAINLOOP | CHN_PANNING)) && smp->length == 0 && smp->loop_start == 0 && smp->loop_end == 0 && smp->sustain_start == 0 && smp->sustain_end == 0 && smp->vib_type == VIB_SINE && smp->vib_rate == 0 && smp->vib_depth == 0 && smp->vib_speed == 0 ); } static int env_is_blank(song_envelope_t *env, int value) { return (env->nodes == 2 && env->loop_start == 0 && env->loop_end == 0 && env->sustain_start == 0 && env->sustain_end == 0 && env->ticks[0] == 0 && env->ticks[1] == 100 && env->values[0] == value && env->values[1] == value ); } int csf_instrument_is_empty(song_instrument_t *ins) { int n; if (!ins) return 1; for (n = 0; n < NOTE_LAST - NOTE_FIRST; n++) { if (ins->sample_map[n] != 0 || ins->note_map[n] != (n + NOTE_FIRST)) return 0; } return (name_is_blank(ins->name) && ins->filename[0] == '\0' && ins->flags == 0 /* No envelopes, loop points, panning, or carry flags set */ && ins->nna == NNA_NOTECUT && ins->dct == DCT_NONE && ins->dca == DCA_NOTECUT && env_is_blank(&ins->vol_env, 64) && ins->global_volume == 128 && ins->fadeout == 0 && ins->vol_swing == 0 && env_is_blank(&ins->pan_env, 32) && ins->panning == 32*4 //mphack && ins->pitch_pan_center == 60 // C-5 (blah) && ins->pitch_pan_separation == 0 && ins->pan_swing == 0 && env_is_blank(&ins->pitch_env, 32) && ins->ifc == 0 && ins->ifr == 0 && ins->midi_channel_mask == 0 && ins->midi_program == -1 && ins->midi_bank == -1 ); } // IT-compatible: last order of "main song", or 0 int csf_last_order(song_t *csf) { int n = 0; while (n < MAX_ORDERS && csf->orderlist[n] != ORDER_LAST) n++; return n ? n - 1 : 0; } // Total count of orders in orderlist before end of data int csf_get_num_orders(song_t *csf) { int n = MAX_ORDERS; while (n >= 0 && csf->orderlist[--n] == ORDER_LAST) { } return n + 1; } // Total number of non-empty patterns in song, according to csf_pattern_is_empty int csf_get_num_patterns(song_t *csf) { int n = MAX_PATTERNS - 1; while (n && csf_pattern_is_empty(csf, n)) n--; return n+ 1; } int csf_get_num_samples(song_t *csf) { int n = MAX_SAMPLES - 1; while (n > 0 && csf_sample_is_empty(csf->samples + n)) n--; return n; } int csf_get_num_instruments(song_t *csf) { int n = MAX_INSTRUMENTS - 1; while (n > 0 && csf_instrument_is_empty(csf->instruments[n])) n--; return n; } int csf_first_blank_sample(song_t *csf, int start) { int n; for (n = MAX(start, 1); n < MAX_SAMPLES; n++) { if (csf_sample_is_empty(csf->samples + n)) return n; } return -1; } int csf_first_blank_instrument(song_t *csf, int start) { int n; for (n = MAX(start, 1); n < MAX_INSTRUMENTS; n++) { if (csf_instrument_is_empty(csf->instruments[n])) return n; } return -1; } // FIXME this function sucks int csf_get_highest_used_channel(song_t *csf) { int highchan = 0, ipat, j, jmax; song_note_t *p; for (ipat = 0; ipat < MAX_PATTERNS; ipat++) { p = csf->patterns[ipat]; if (!p) continue; jmax = csf->pattern_size[ipat] * MAX_CHANNELS; for (j = 0; j < jmax; j++, p++) { if (NOTE_IS_NOTE(p->note)) { if ((j % MAX_CHANNELS) > highchan) highchan = j % MAX_CHANNELS; } } } return highchan; } ////////////////////////////////////////////////////////////////////////// // Misc functions midi_config_t default_midi_config; void csf_reset_midi_cfg(song_t *csf) { memcpy(&csf->midi_config, &default_midi_config, sizeof(default_midi_config)); } void csf_copy_midi_cfg(song_t *dest, song_t *src) { memcpy(&dest->midi_config, &src->midi_config, sizeof(midi_config_t)); } int csf_set_wave_config(song_t *csf, uint32_t rate,uint32_t bits,uint32_t channels) { int reset = ((csf->mix_frequency != rate) || (csf->mix_bits_per_sample != bits) || (csf->mix_channels != channels)); csf->mix_channels = channels; csf->mix_frequency = rate; csf->mix_bits_per_sample = bits; csf_init_player(csf, reset); return 1; } int csf_set_resampling_mode(song_t *csf, uint32_t mode) { uint32_t d = csf->mix_flags & ~(SNDMIX_NORESAMPLING|SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); switch(mode) { 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 0; } csf->mix_flags = d; return 1; } // This used to use some retarded positioning based on the total number of rows elapsed, which is useless. // However, the only code calling this function is in this file, to set it to the start, so I'm optimizing // out the row count. static void set_current_pos_0(song_t *csf) { song_voice_t *v = csf->voices; for (uint32_t i = 0; i < MAX_VOICES; i++, v++) { memset(v, 0, sizeof(*v)); v->cutoff = 0x7F; v->volume = 256; if (i < MAX_CHANNELS) { v->panning = csf->channels[i].panning; v->global_volume = csf->channels[i].volume; v->flags = csf->channels[i].flags; } else { v->panning = 128; v->global_volume = 64; } } csf->current_global_volume = csf->initial_global_volume; csf->current_speed = csf->initial_speed; csf->current_tempo = csf->initial_tempo; } void csf_set_current_order(song_t *csf, uint32_t position) { for (uint32_t j = 0; j < MAX_VOICES; j++) { song_voice_t *v = csf->voices + j; v->period = 0; v->note = v->new_note = v->new_instrument = 0; v->portamento_target = 0; v->n_command = 0; v->cd_patloop = 0; v->patloop_row = 0; v->cd_tremor = 0; // modplug sets vib pos to 16 in old effects mode for some reason *shrug* v->vibrato_position = (csf->flags & SONG_ITOLDEFFECTS) ? 0 : 0x10; v->tremolo_position = 0; } if (position > MAX_ORDERS) position = 0; if (!position) set_current_pos_0(csf); csf->process_order = position - 1; csf->process_row = PROCESS_NEXT_ORDER; csf->row = 0; csf->break_row = 0; /* set this to whatever row to jump to */ csf->tick_count = 1; csf->row_count = 0; csf->buffer_count = 0; csf->flags &= ~(SONG_PATTERNLOOP|SONG_ENDREACHED); } void csf_reset_playmarks(song_t *csf) { int n; for (n = 1; n < MAX_SAMPLES; n++) { csf->samples[n].played = 0; } for (n = 1; n < MAX_INSTRUMENTS; n++) { if (csf->instruments[n]) csf->instruments[n]->played = 0; } } void csf_loop_pattern(song_t *csf, int pat, int row) { if (pat < 0 || pat >= MAX_PATTERNS || !csf->patterns[pat]) { csf->flags &= ~SONG_PATTERNLOOP; } else { if (row < 0 || row >= csf->pattern_size[pat]) row = 0; csf->process_order = 0; // hack - see increment_order in sndmix.c csf->process_row = PROCESS_NEXT_ORDER; csf->break_row = row; csf->tick_count = 1; csf->row_count = 0; csf->current_pattern = pat; csf->buffer_count = 0; csf->flags |= SONG_PATTERNLOOP; } } /* --------------------------------------------------------------------------------------------------------- */ #define SF_FAIL(name, n) \ ({ log_appendf(4, "%s: internal error: unsupported %s %d", __FUNCTION__, name, n); return 0; }) uint32_t csf_write_sample(disko_t *fp, song_sample_t *sample, uint32_t flags) { uint32_t pos, len = sample->length; int stride = 1; // how much to add to the left/right pointer per sample written int byteswap = 0; // should the sample data be byte-swapped? int add = 0; // how much to add to the sample data (for converting to unsigned) int channel; // counter. // validate the write flags, and set up the save params switch (flags & SF_CHN_MASK) { case SF_SI: if (!(sample->flags & CHN_STEREO)) SF_FAIL("channel mask", flags & SF_CHN_MASK); len *= 2; break; case SF_SS: if (!(sample->flags & CHN_STEREO)) SF_FAIL("channel mask", flags & SF_CHN_MASK); stride = 2; break; case SF_M: if (sample->flags & CHN_STEREO) SF_FAIL("channel mask", flags & SF_CHN_MASK); break; default: SF_FAIL("channel mask", flags & SF_CHN_MASK); } // TODO allow converting bit width, this will be useful if ((flags & SF_BIT_MASK) != ((sample->flags & CHN_16BIT) ? SF_16 : SF_8)) SF_FAIL("bit width", flags & SF_BIT_MASK); switch (flags & SF_END_MASK) { #if WORDS_BIGENDIAN case SF_LE: byteswap = 1; break; case SF_BE: break; #else case SF_LE: break; case SF_BE: byteswap = 1; break; #endif default: SF_FAIL("endianness", flags & SF_END_MASK); } switch (flags & SF_ENC_MASK) { case SF_PCMU: add = ((flags & SF_BIT_MASK) == SF_16) ? 32768 : 128; break; case SF_PCMS: break; default: SF_FAIL("encoding", flags & SF_ENC_MASK); } if ((flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)) != 0) { SF_FAIL("extra flag", flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)); } if (!sample || sample->length < 1 || sample->length > MAX_SAMPLE_LENGTH || !sample->data) return 0; // No point buffering the processing here -- the disk output already SHOULD have a 64kb buffer if ((flags & SF_BIT_MASK) == SF_16) { // 16-bit data. const int16_t *data; int16_t v; for (channel = 0; channel < stride; channel++) { data = (const int16_t *) sample->data + channel; for (pos = 0; pos < len; pos++) { v = *data + add; if (byteswap) v = bswap_16(v); disko_write(fp, &v, 2); data += stride; } } len *= 2; } else { // 8-bit data. Mostly the same as above, but a little bit simpler since // there's no byteswapping, and the values can be written with putc. const int8_t *data; for (channel = 0; channel < stride; channel++) { data = (const int8_t *) sample->data + channel; for (pos = 0; pos < len; pos++) { disko_putc(fp, *data + add); data += stride; } } } len *= stride; return len; } uint32_t csf_read_sample(song_sample_t *sample, uint32_t flags, const void *filedata, uint32_t memsize) { uint32_t len = 0, mem; const char *buffer = (const char *) filedata; if (sample->flags & CHN_ADLIB) return 0; // no sample data if (!sample || sample->length < 1 || !buffer) return 0; // validate the read flags before anything else switch (flags & SF_BIT_MASK) { case SF_7: case SF_8: case SF_16: case SF_24: case SF_32: break; default: SF_FAIL("bit width", flags & SF_BIT_MASK); } switch (flags & SF_CHN_MASK) { case SF_M: case SF_SI: case SF_SS: break; default: SF_FAIL("channel mask", flags & SF_CHN_MASK); } switch (flags & SF_END_MASK) { case SF_LE: case SF_BE: break; default: SF_FAIL("endianness", flags & SF_END_MASK); } switch (flags & SF_ENC_MASK) { case SF_PCMS: case SF_PCMU: case SF_PCMD: case SF_IT214: case SF_IT215: case SF_AMS: case SF_DMF: case SF_MDL: case SF_PTM: case SF_PCMD16: break; default: SF_FAIL("encoding", flags & SF_ENC_MASK); } if ((flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)) != 0) { SF_FAIL("extra flag", flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)); } if (sample->length > MAX_SAMPLE_LENGTH) sample->length = MAX_SAMPLE_LENGTH; mem = sample->length+6; sample->flags &= ~(CHN_16BIT|CHN_STEREO); switch (flags & SF_BIT_MASK) { case SF_16: case SF_24: case SF_32: // these are all stuffed into 16 bits. mem *= 2; sample->flags |= CHN_16BIT; } switch (flags & SF_CHN_MASK) { case SF_SI: case SF_SS: mem *= 2; sample->flags |= CHN_STEREO; } if ((sample->data = csf_allocate_sample(mem)) == NULL) { sample->length = 0; return 0; } switch(flags) { // 1: 8-bit unsigned PCM data case RS_PCM8U: { len = sample->length; if (len > memsize) len = sample->length = memsize; signed char *data = sample->data; for (uint32_t j=0; jlength; if (len > memsize) break; signed char *data = sample->data; const signed char *p = (const signed char *)buffer; int delta = 0; for (uint32_t j=0; jlength * 2; if (len > memsize) break; short *data = (short *)sample->data; short *p = (short *)buffer; unsigned short tmp; int delta16 = 0; for (uint32_t j=0; jlength * 2; if (len <= memsize) memcpy(sample->data, buffer, len); short int *data = (short int *)sample->data; for (uint32_t j=0; jlength * 2; if (len > memsize) len = memsize & ~1; if (len > 1) { signed char *data = (signed char *)sample->data; signed char *src = (signed char *)buffer; for (uint32_t j=0; jlength * 2; if (len <= memsize) memcpy(sample->data, buffer, len); short int *data = (short int *)sample->data; for (uint32_t j=0; jlength * 2; if (len*2 <= memsize) { signed char *data = (signed char *)sample->data; signed char *src = (signed char *)buffer; for (uint32_t j=0; jlength; signed char *psrc = (signed char *)buffer; signed char *data = (signed char *)sample->data; if (len*2 > memsize) break; for (uint32_t j=0; jlength; short int *psrc = (short int *)buffer; short int *data = (short int *)sample->data; if (len*4 > memsize) break; for (uint32_t j=0; jdata, sample->length, buffer, memsize, (flags == RS_IT2158), 1); } else { it_decompress16(sample->data, sample->length, buffer, memsize, (flags == RS_IT21516), 1); } break; case RS_IT2148S: case RS_IT21416S: case RS_IT2158S: case RS_IT21516S: len = memsize; if (len < 4) break; if (flags == RS_IT2148S || flags == RS_IT2158S) { uint32_t offset = it_decompress8(sample->data, sample->length, buffer, memsize, (flags == RS_IT2158S), 2); it_decompress8(sample->data + 1, sample->length, buffer + offset, memsize - offset, (flags == RS_IT2158S), 2); } else { uint32_t offset = it_decompress16(sample->data, sample->length, buffer, memsize, (flags == RS_IT21516S), 2); it_decompress16(sample->data + 2, sample->length, buffer + offset, memsize - offset, (flags == RS_IT21516S), 2); } break; // 8-bit interleaved stereo samples case RS_STIPCM8S: case RS_STIPCM8U: { int iadd = 0; if (flags == RS_STIPCM8U) { iadd = -0x80; } len = sample->length; if (len*2 > memsize) len = memsize >> 1; uint8_t * psrc = (uint8_t *)buffer; uint8_t * data = (uint8_t *)sample->data; for (uint32_t j=0; jlength; if (len*4 > memsize) len = memsize >> 2; short int *psrc = (short int *)buffer; short int *data = (short int *)sample->data; for (uint32_t j=0; j 9) { const char *psrc = buffer; char packcharacter = buffer[8], *pdest = (char *)sample->data; len += bswapLE32(*((uint32_t *)(buffer+4))); if (len > memsize) len = memsize; uint32_t dmax = sample->length; if (sample->flags & CHN_16BIT) dmax <<= 1; AMSUnpack(psrc+9, len-9, pdest, dmax, packcharacter); } break; #endif // PTM 8bit delta to 16-bit sample case RS_PTM8DTO16: { len = sample->length * 2; if (len > memsize) break; signed char *data = (signed char *)sample->data; signed char delta8 = 0; for (uint32_t j=0; jdata; for (uint32_t j=0; j= 8) { // first 4 bytes indicate packed length len = bswapLE32(*((uint32_t *) buffer)); len = MIN(len, memsize) + 4; uint8_t * data = (uint8_t *)sample->data; uint8_t * ibuf = (uint8_t *)(buffer + 4); uint32_t bitbuf = bswapLE32(*((uint32_t *)ibuf)); uint32_t bitnum = 32; uint8_t dlt = 0, lowbyte = 0; ibuf += 4; // TODO move all this junk to fmt/compression.c for (uint32_t j=0; jlength; j++) { uint8_t hibyte; uint8_t sign; if (flags == RS_MDL16) lowbyte = (uint8_t)mdl_read_bits(&bitbuf, &bitnum, &ibuf, 8); sign = (uint8_t)mdl_read_bits(&bitbuf, &bitnum, &ibuf, 1); if (mdl_read_bits(&bitbuf, &bitnum, &ibuf, 1)) { hibyte = (uint8_t)mdl_read_bits(&bitbuf, &bitnum, &ibuf, 3); } else { hibyte = 8; while (!mdl_read_bits(&bitbuf, &bitnum, &ibuf, 1)) hibyte += 0x10; hibyte += mdl_read_bits(&bitbuf, &bitnum, &ibuf, 4); } if (sign) hibyte = ~hibyte; dlt += hibyte; if (flags == RS_MDL8) { data[j] = dlt; } else { #ifdef WORDS_BIGENDIAN data[j<<1] = dlt; data[(j<<1)+1] = lowbyte; #else data[j<<1] = lowbyte; data[(j<<1)+1] = dlt; #endif } } } break; #if 0 case RS_DMF8: case RS_DMF16: len = memsize; if (len >= 4) { uint32_t maxlen = sample->length; if (sample->flags & CHN_16BIT) maxlen <<= 1; uint8_t * ibuf = (uint8_t *)buffer; uint8_t * ibufmax = (uint8_t *)(buffer+memsize); len = DMFUnpack((uint8_t *)sample->data, ibuf, ibufmax, maxlen); } break; #endif // PCM 24-bit signed -> load sample, and normalize it to 16-bit case RS_PCM24S: case RS_PCM32S: len = sample->length * 3; if (flags == RS_PCM32S) len += sample->length; if (len > memsize) break; if (len > 4*8) { uint32_t slsize = (flags == RS_PCM32S) ? 4 : 3; uint8_t * src = (uint8_t *)buffer; int32_t max = 255; if (flags == RS_PCM32S) src++; for (uint32_t j=0; j max) max = l; if (-l > max) max = -l; } max = (max / 128) + 1; signed short *dest = (signed short *)sample->data; for (uint32_t k=0; k load sample, and normalize it to 16-bit case RS_STIPCM24S: case RS_STIPCM32S: len = sample->length * 6; if (flags == RS_STIPCM32S) len += sample->length * 2; if (len > memsize) break; if (len > 8*8) { uint32_t slsize = (flags == RS_STIPCM32S) ? 4 : 3; uint8_t * src = (uint8_t *)buffer; int32_t max = 255; if (flags == RS_STIPCM32S) src++; for (uint32_t j=0; j max) max = l; if (-l > max) max = -l; } max = (max / 128) + 1; signed short *dest = (signed short *)sample->data; for (uint32_t k=0; klength; if (len*4 > memsize) len = memsize >> 2; const uint8_t * psrc = (const uint8_t *)buffer; short int *data = (short int *)sample->data; for (uint32_t j=0; jflags &= ~(CHN_16BIT | CHN_STEREO); len = sample->length = MIN(sample->length, memsize); for (uint32_t j = 0; j < len; j++) sample->data[j] = CLAMP(buffer[j] * 2, -128, 127); break; // 8-bit ADPCM data w/ 16-byte table (MOD ADPCM) case RS_PCM8D16: { len = (sample->length + 1) / 2 + 16; if (len > memsize) break; const signed char *p = (const signed char *)buffer; signed char *data = sample->data, smpval = 0; for (uint32_t j=16; j> 4) & 0xF]; *data++ = smpval; } } break; // Default: 8-bit signed PCM data default: printf("DEFAULT: %d\n", flags); case SF(8,M,BE,PCMS): /* endianness is irrelevant for 8-bit samples */ case SF(8,M,LE,PCMS): sample->flags &= ~(CHN_16BIT | CHN_STEREO); len = sample->length; if (len > memsize) len = sample->length = memsize; memcpy(sample->data, buffer, len); break; } if (len > memsize) { if (sample->data) { sample->length = 0; csf_free_sample(sample->data); sample->data = NULL; } return 0; } csf_adjust_sample_loop(sample); return len; } /* --------------------------------------------------------------------------------------------------------- */ void csf_adjust_sample_loop(song_sample_t *sample) { if (!sample->data || sample->length < 1) return; if (sample->loop_end > sample->length) sample->loop_end = sample->length; if (sample->loop_start+2 >= sample->loop_end) { sample->loop_start = sample->loop_end = 0; sample->flags &= ~CHN_LOOP; } // poopy, removing all that loop-hacking code has produced... very nasty sounding loops! // so I guess I should rewrite the crap at the end of the sample at least. uint32_t len = sample->length; if (sample->flags & CHN_16BIT) { short int *data = (short int *)sample->data; // Adjust end of sample if (sample->flags & CHN_STEREO) { data[len*2+6] = data[len*2+4] = data[len*2+2] = data[len*2] = data[len*2-2]; data[len*2+7] = data[len*2+5] = data[len*2+3] = data[len*2+1] = data[len*2-1]; } else { data[len+4] = data[len+3] = data[len+2] = data[len+1] = data[len] = data[len-1]; } } else { signed char *data = sample->data; // Adjust end of sample if (sample->flags & CHN_STEREO) { data[len*2+6] = data[len*2+4] = data[len*2+2] = data[len*2] = data[len*2-2]; data[len*2+7] = data[len*2+5] = data[len*2+3] = data[len*2+1] = data[len*2-1]; } else { data[len+4] = data[len+3] = data[len+2] = data[len+1] = data[len] = data[len-1]; } } } void csf_stop_sample(song_t *csf, song_sample_t *smp) { song_voice_t *v = csf->voices; if (!smp->data) return; for (int i = 0; i < MAX_VOICES; i++, v++) { if (v->ptr_sample == smp || v->current_sample_data == smp->data) { v->note = v->new_note = v->new_instrument = 0; v->fadeout_volume = 0; v->flags |= CHN_KEYOFF | CHN_NOTEFADE; v->period = 0; v->position = v->length = 0; v->loop_start = 0; v->loop_end = 0; v->rofs = v->lofs = 0; v->current_sample_data = NULL; v->ptr_sample = NULL; v->ptr_instrument = NULL; v->left_volume = v->right_volume = 0; v->left_volume_new = v->right_volume_new = 0; v->left_ramp = v->right_ramp = 0; } } } int csf_destroy_sample(song_t *csf, uint32_t nsmp) { song_sample_t *smp = csf->samples + nsmp; signed char *data; if (nsmp >= MAX_SAMPLES) return 0; data = smp->data; if (!data) return 1; csf_stop_sample(csf, smp); smp->data = NULL; smp->length = 0; smp->flags &= ~CHN_16BIT; csf_free_sample(data); return 1; } void csf_import_mod_effect(song_note_t *m, int from_xm) { uint32_t effect = m->effect, param = m->param; switch(effect) { case 0x00: if (param) effect = FX_ARPEGGIO; break; case 0x01: effect = FX_PORTAMENTOUP; break; case 0x02: effect = FX_PORTAMENTODOWN; break; case 0x03: effect = FX_TONEPORTAMENTO; break; case 0x04: effect = FX_VIBRATO; break; case 0x05: effect = FX_TONEPORTAVOL; if (param & 0xF0) param &= 0xF0; break; case 0x06: effect = FX_VIBRATOVOL; if (param & 0xF0) param &= 0xF0; break; case 0x07: effect = FX_TREMOLO; break; case 0x08: effect = FX_PANNING; break; case 0x09: effect = FX_OFFSET; break; case 0x0A: effect = FX_VOLUMESLIDE; if (param & 0xF0) param &= 0xF0; break; case 0x0B: effect = FX_POSITIONJUMP; break; case 0x0C: if (from_xm) { effect = FX_VOLUME; } else { m->voleffect = VOLFX_VOLUME; m->volparam = param; if (m->voleffect > 64) m->voleffect = 64; effect = param = 0; } break; case 0x0D: effect = FX_PATTERNBREAK; param = ((param >> 4) * 10) + (param & 0x0F); break; case 0x0E: effect = FX_SPECIAL; switch(param & 0xF0) { case 0x10: effect = FX_PORTAMENTOUP; param |= 0xF0; break; case 0x20: effect = FX_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: effect = FX_RETRIG; param &= 0x0F; break; case 0xA0: if (param & 0x0F) { effect = FX_VOLUMESLIDE; param = (param << 4) | 0x0F; } else { effect = param = 0; } break; case 0xB0: if (param & 0x0F) { effect = FX_VOLUMESLIDE; param |= 0xF0; } else { effect=param=0; } break; } break; case 0x0F: // FT2 processes 0x20 as Txx; ST3 loads it as Axx effect = (param < (from_xm ? 0x20 : 0x21)) ? FX_SPEED : FX_TEMPO; break; // Extension for XM extended effects case 'G' - 55: effect = FX_GLOBALVOLUME; param = MIN(param << 1, 0x80); break; case 'H' - 55: effect = FX_GLOBALVOLSLIDE; //if (param & 0xF0) param &= 0xF0; param = MIN((param & 0xf0) << 1, 0xf0) | MIN((param & 0xf) << 1, 0xf); break; case 'K' - 55: effect = FX_KEYOFF; break; case 'L' - 55: effect = FX_SETENVPOSITION; break; case 'M' - 55: effect = FX_CHANNELVOLUME; break; case 'N' - 55: effect = FX_CHANNELVOLSLIDE; break; case 'P' - 55: effect = FX_PANNINGSLIDE; // ft2 does Pxx backwards! skjdfjksdfkjsdfjk if (param & 0xF0) param >>= 4; else param = (param & 0xf) << 4; break; case 'R' - 55: effect = FX_RETRIG; break; case 'T' - 55: effect = FX_TREMOR; break; case 'X' - 55: switch (param & 0xf0) { case 0x10: effect = FX_PORTAMENTOUP; param = 0xe0 | (param & 0xf); break; case 0x20: effect = FX_PORTAMENTODOWN; param = 0xe0 | (param & 0xf); break; default: effect = param = 0; break; } break; case 'Y' - 55: effect = FX_PANBRELLO; break; case 'Z' - 55: effect = FX_MIDI; break; case '[' - 55: // FT2 shows this weird effect as -xx, and it can even be inserted // by typing "-", although it doesn't appear to do anything. default: effect = 0; } m->effect = effect; m->param = param; } uint16_t csf_export_mod_effect(const song_note_t *m, int to_xm) { uint32_t effect = m->effect & 0x3F, param = m->param; switch(effect) { case 0: effect = param = 0; break; case FX_ARPEGGIO: effect = 0; break; case FX_PORTAMENTOUP: if ((param & 0xF0) == 0xE0) { if (to_xm) { effect = 'X' - 55; param = 0x10 | (param & 0xf); } else { effect = 0x0E; param = 0x10 | ((param & 0xf) >> 2); } } else if ((param & 0xF0) == 0xF0) { effect = 0x0E; param = 0x10 | (param & 0xf); } else { effect = 0x01; } break; case FX_PORTAMENTODOWN: if ((param & 0xF0) == 0xE0) { if (to_xm) { effect = 'X' - 55; param = 0x20 | (param & 0xf); } else { effect = 0x0E; param = 0x20 | ((param & 0xf) >> 2); } } else if ((param & 0xF0) == 0xF0) { effect = 0x0E; param = 0x20 | (param & 0xf); } else { effect = 0x02; } break; case FX_TONEPORTAMENTO: effect = 0x03; break; case FX_VIBRATO: effect = 0x04; break; case FX_TONEPORTAVOL: effect = 0x05; break; case FX_VIBRATOVOL: effect = 0x06; break; case FX_TREMOLO: effect = 0x07; break; case FX_PANNING: effect = 0x08; break; case FX_OFFSET: effect = 0x09; break; case FX_VOLUMESLIDE: effect = 0x0A; break; case FX_POSITIONJUMP: effect = 0x0B; break; case FX_VOLUME: effect = 0x0C; break; case FX_PATTERNBREAK: effect = 0x0D; param = ((param / 10) << 4) | (param % 10); break; case FX_SPEED: effect = 0x0F; if (param > 0x20) param = 0x20; break; case FX_TEMPO: if (param > 0x20) { effect = 0x0F; break; } return 0; case FX_GLOBALVOLUME: effect = 'G' - 55; break; case FX_GLOBALVOLSLIDE: effect = 'H' - 55; break; // FIXME this needs to be adjusted case FX_KEYOFF: effect = 'K' - 55; break; case FX_SETENVPOSITION: effect = 'L' - 55; break; case FX_CHANNELVOLUME: effect = 'M' - 55; break; case FX_CHANNELVOLSLIDE: effect = 'N' - 55; break; case FX_PANNINGSLIDE: effect = 'P' - 55; break; case FX_RETRIG: effect = 'R' - 55; break; case FX_TREMOR: effect = 'T' - 55; break; case FX_PANBRELLO: effect = 'Y' - 55; break; case FX_MIDI: effect = 'Z' - 55; break; case FX_SPECIAL: switch (param & 0xF0) { case 0x10: effect = 0x0E; param = (param & 0x0F) | 0x30; break; case 0x20: effect = 0x0E; param = (param & 0x0F) | 0x50; break; case 0x30: effect = 0x0E; param = (param & 0x0F) | 0x40; break; case 0x40: effect = 0x0E; param = (param & 0x0F) | 0x70; break; case 0x90: effect = 'X' - 55; break; case 0xB0: effect = 0x0E; param = (param & 0x0F) | 0x60; break; case 0xA0: case 0x50: case 0x70: case 0x60: effect = param = 0; break; default: effect = 0x0E; break; } break; default: effect = param = 0; } return (uint16_t)((effect << 8) | (param)); } void csf_import_s3m_effect(song_note_t *m, int from_it) { uint32_t effect = m->effect; uint32_t param = m->param; switch (effect + 0x40) { case 'A': effect = FX_SPEED; break; case 'B': effect = FX_POSITIONJUMP; break; case 'C': effect = FX_PATTERNBREAK; if (!from_it) param = (param >> 4) * 10 + (param & 0x0F); break; case 'D': effect = FX_VOLUMESLIDE; break; case 'E': effect = FX_PORTAMENTODOWN; break; case 'F': effect = FX_PORTAMENTOUP; break; case 'G': effect = FX_TONEPORTAMENTO; break; case 'H': effect = FX_VIBRATO; break; case 'I': effect = FX_TREMOR; break; case 'J': effect = FX_ARPEGGIO; break; case 'K': effect = FX_VIBRATOVOL; break; case 'L': effect = FX_TONEPORTAVOL; break; case 'M': effect = FX_CHANNELVOLUME; break; case 'N': effect = FX_CHANNELVOLSLIDE; break; case 'O': effect = FX_OFFSET; break; case 'P': effect = FX_PANNINGSLIDE; break; case 'Q': effect = FX_RETRIG; break; case 'R': effect = FX_TREMOLO; break; case 'S': effect = FX_SPECIAL; // convert old SAx to S8x if (!from_it && ((param & 0xf0) == 0xa0)) param = 0x80 | ((param & 0xf) ^ 8); break; case 'T': effect = FX_TEMPO; break; case 'U': effect = FX_FINEVIBRATO; break; case 'V': effect = FX_GLOBALVOLUME; if (!from_it) param *= 2; break; case 'W': effect = FX_GLOBALVOLSLIDE; break; case 'X': effect = FX_PANNING; if (!from_it) { if (param == 0xa4) { effect = FX_SPECIAL; param = 0x91; } else if (param > 0x7f) { param = 0xff; } else { param *= 2; } } break; case 'Y': effect = FX_PANBRELLO; break; case 'Z': effect = FX_MIDI; break; default: effect = 0; } m->effect = effect; m->param = param; } void csf_export_s3m_effect(uint8_t *pcmd, uint8_t *pprm, int to_it) { uint8_t effect = *pcmd; uint8_t param = *pprm; switch (effect) { case FX_SPEED: effect = 'A'; break; case FX_POSITIONJUMP: effect = 'B'; break; case FX_PATTERNBREAK: effect = 'C'; if (!to_it) param = ((param / 10) << 4) + (param % 10); break; case FX_VOLUMESLIDE: effect = 'D'; break; case FX_PORTAMENTODOWN: effect = 'E'; break; case FX_PORTAMENTOUP: effect = 'F'; break; case FX_TONEPORTAMENTO: effect = 'G'; break; case FX_VIBRATO: effect = 'H'; break; case FX_TREMOR: effect = 'I'; break; case FX_ARPEGGIO: effect = 'J'; break; case FX_VIBRATOVOL: effect = 'K'; break; case FX_TONEPORTAVOL: effect = 'L'; break; case FX_CHANNELVOLUME: effect = 'M'; break; case FX_CHANNELVOLSLIDE: effect = 'N'; break; case FX_OFFSET: effect = 'O'; break; case FX_PANNINGSLIDE: effect = 'P'; break; case FX_RETRIG: effect = 'Q'; break; case FX_TREMOLO: effect = 'R'; break; case FX_SPECIAL: if (!to_it && param == 0x91) { effect = 'X'; param = 0xA4; } else { effect = 'S'; } break; case FX_TEMPO: effect = 'T'; break; case FX_FINEVIBRATO: effect = 'U'; break; case FX_GLOBALVOLUME: effect = 'V'; if (!to_it) param >>= 1;break; case FX_GLOBALVOLSLIDE: effect = 'W'; break; case FX_PANNING: effect = 'X'; if (!to_it) param >>= 1; break; case FX_PANBRELLO: effect = 'Y'; break; case FX_MIDI: effect = 'Z'; break; default: effect = 0; } effect &= ~0x40; *pcmd = effect; *pprm = param; } void csf_insert_restart_pos(song_t *csf, uint32_t restart_order) { int n, max, row; int ord, pat, newpat; int used; // how many times it was used (if >1, copy it) if (!restart_order) return; // find the last pattern, also look for one that's not being used for (max = ord = n = 0; n < MAX_ORDERS && csf->orderlist[n] < MAX_PATTERNS; ord = n, n++) if (csf->orderlist[n] > max) max = csf->orderlist[n]; newpat = max + 1; pat = csf->orderlist[ord]; if (pat >= MAX_PATTERNS || !csf->patterns[pat] || !csf->pattern_size[pat]) return; for (max = n, used = 0, n = 0; n < max; n++) if (csf->orderlist[n] == pat) used++; if (used > 1) { // copy the pattern so we don't screw up the playback elsewhere while (newpat < MAX_PATTERNS && csf->patterns[newpat]) newpat++; if (newpat >= MAX_PATTERNS) return; // no more patterns? sux //log_appendf(2, "Copying pattern %d to %d for restart position", pat, newpat); csf->patterns[newpat] = csf_allocate_pattern(csf->pattern_size[pat]); csf->pattern_size[newpat] = csf->pattern_alloc_size[newpat] = csf->pattern_size[pat]; memcpy(csf->patterns[newpat], csf->patterns[pat], sizeof(song_note_t) * MAX_CHANNELS * csf->pattern_size[pat]); csf->orderlist[ord] = pat = newpat; } else { //log_appendf(2, "Modifying pattern %d to add restart position", pat); } max = csf->pattern_size[pat] - 1; for (row = 0; row <= max; row++) { song_note_t *note = csf->patterns[pat] + MAX_CHANNELS * row; song_note_t *empty = NULL; // where's an empty effect? int has_break = 0, has_jump = 0; for (n = 0; n < MAX_CHANNELS; n++, note++) { switch (note->effect) { case FX_POSITIONJUMP: has_jump = 1; break; case FX_PATTERNBREAK: has_break = 1; if (!note->param) empty = note; // always rewrite C00 with Bxx (it's cleaner) break; case FX_NONE: if (!empty) empty = note; break; } } // if there's not already a Bxx, and we have a spare channel, // AND either there's a Cxx or it's the last row of the pattern, // then stuff in a jump back to the restart position. if (!has_jump && empty && (has_break || row == max)) { empty->effect = FX_POSITIONJUMP; empty->param = restart_order; } } } schismtracker-20180209/player/effects.c000066400000000000000000001563661323741476300177350ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sndfile.h" #include "cmixer.h" #include "snd_fm.h" #include "snd_gm.h" #include "tables.h" #include "util.h" /* for clamp/min */ #include // see also csf_midi_out_note in sndmix.c void (*csf_midi_out_raw)(const unsigned char *,unsigned int, unsigned int) = NULL; /* --------------------------------------------------------------------------------------------------------- */ /* note/freq/period conversion functions */ int get_note_from_period(int period) { int n; if (!period) return 0; for (n = 0; n <= 120; n++) { /* Essentially, this is just doing a note_to_period(n, 8363), but with less computation since there's no c5speed to deal with. */ if (period >= (32 * period_table[n % 12] >> (n / 12))) return n + 1; } return 120; } int get_period_from_note(int note, unsigned int c5speed, int linear) { if (!note || note > 0xF0) return 0; note--; if (linear) return _muldiv(c5speed, linear_slide_up_table[(note % 12) * 16] << (note / 12), 65536 << 5); else if (!c5speed) return INT_MAX; else return _muldiv(8363, (period_table[note % 12] << 5), c5speed << (note / 12)); } unsigned int transpose_to_frequency(int transp, int ftune) { return (unsigned int) (8363.0 * pow(2, (transp * 128.0 + ftune) / 1536.0)); } int frequency_to_transpose(unsigned int freq) { return (int) (1536.0 * (log(freq / 8363.0) / log(2))); } unsigned long calc_halftone(unsigned long hz, int rel) { return pow(2, rel / 12.0) * hz + 0.5; } /* --------------------------------------------------------------------------------------------------------- */ /* the full content of snd_fx.cpp follows. */ //////////////////////////////////////////////////////////// // Channels effects void fx_note_cut(song_t *csf, uint32_t nchan, int clear_note) { song_voice_t *chan = &csf->voices[nchan]; // stop the current note: chan->flags |= CHN_FASTVOLRAMP; chan->length = 0; if (clear_note) { // keep instrument numbers from picking up old notes // (SCx doesn't do this) chan->period = 0; } if (chan->flags & CHN_ADLIB) { //Do this only if really an adlib chan. Important! OPL_NoteOff(nchan); OPL_Touch(nchan, 0); } GM_KeyOff(nchan); GM_Touch(nchan, 0); } void fx_key_off(song_t *csf, uint32_t nchan) { song_voice_t *chan = &csf->voices[nchan]; /*fprintf(stderr, "KeyOff[%d] [ch%u]: flags=0x%X\n", tick_count, (unsigned)nchan, chan->flags);*/ if (chan->flags & CHN_ADLIB) { //Do this only if really an adlib chan. Important! OPL_NoteOff(nchan); } GM_KeyOff(nchan); song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL; /*if ((chan->flags & CHN_ADLIB) || (penv && penv->midi_channel_mask)) { // When in AdLib / MIDI mode, end the sample chan->flags |= CHN_FASTVOLRAMP; chan->length = 0; chan->position = 0; return; }*/ chan->flags |= CHN_KEYOFF; //if ((!chan->ptr_instrument) || (!(chan->flags & CHN_VOLENV))) if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument && !(chan->flags & CHN_VOLENV)) { chan->flags |= CHN_NOTEFADE; } if (!chan->length) return; if ((chan->flags & CHN_SUSTAINLOOP) && chan->ptr_sample) { song_sample_t *psmp = chan->ptr_sample; if (psmp->flags & CHN_LOOP) { if (psmp->flags & CHN_PINGPONGLOOP) chan->flags |= CHN_PINGPONGLOOP; else chan->flags &= ~(CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); chan->flags |= CHN_LOOP; chan->length = psmp->length; chan->loop_start = psmp->loop_start; chan->loop_end = psmp->loop_end; if (chan->length > chan->loop_end) chan->length = chan->loop_end; if (chan->position >= chan->length) chan->position = chan->position - chan->length + chan->loop_start; } else { chan->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); chan->length = psmp->length; } } if (penv && penv->fadeout && (penv->flags & ENV_VOLLOOP)) chan->flags |= CHN_NOTEFADE; } // negative value for slide = up, positive = down static void fx_do_freq_slide(uint32_t flags, song_voice_t *chan, int32_t slide) { // IT Linear slides if (!chan->period) return; if (flags & SONG_LINEARSLIDES) { int32_t old_period = chan->period; if (slide < 0) { uint32_t n = (-slide) >> 2; if (n > 255) n = 255; chan->period = _muldivr(chan->period, linear_slide_up_table[n], 65536); if (old_period == chan->period) chan->period++; } else { uint32_t n = (slide) >> 2; if (n > 255) n = 255; chan->period = _muldivr(chan->period, linear_slide_down_table[n], 65536); if (old_period == chan->period) chan->period--; } } else { chan->period += slide; } } static void fx_fine_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) { if ((flags & SONG_FIRSTTICK) && chan->period && param) { if (flags & SONG_LINEARSLIDES) { chan->period = _muldivr(chan->period, linear_slide_up_table[param & 0x0F], 65536); } else { chan->period -= (int)(param * 4); } } } static void fx_fine_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) { if ((flags & SONG_FIRSTTICK) && chan->period && param) { if (flags & SONG_LINEARSLIDES) { chan->period = _muldivr(chan->period, linear_slide_down_table[param & 0x0F], 65536); } else { chan->period += (int)(param * 4); } } } static void fx_extra_fine_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) { if ((flags & SONG_FIRSTTICK) && chan->period && param) { if (flags & SONG_LINEARSLIDES) { chan->period = _muldivr(chan->period, fine_linear_slide_up_table[param & 0x0F], 65536); } else { chan->period -= (int)(param); } } } static void fx_extra_fine_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) { if ((flags & SONG_FIRSTTICK) && chan->period && param) { if (flags & SONG_LINEARSLIDES) { chan->period = _muldivr(chan->period, fine_linear_slide_down_table[param & 0x0F], 65536); } else { chan->period += (int)(param); } } } static void fx_reg_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) { if (!(flags & SONG_FIRSTTICK)) fx_do_freq_slide(flags, chan, -(int)(param * 4)); } static void fx_reg_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) { if (!(flags & SONG_FIRSTTICK)) fx_do_freq_slide(flags, chan, (int)(param * 4)); } static void fx_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) { if (!param) param = chan->mem_pitchslide; switch (param & 0xf0) { case 0xe0: fx_extra_fine_portamento_up(flags, chan, param & 0x0F); break; case 0xf0: fx_fine_portamento_up(flags, chan, param & 0x0F); break; default: fx_reg_portamento_up(flags, chan, param); break; } } static void fx_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) { if (!param) param = chan->mem_pitchslide; switch (param & 0xf0) { case 0xe0: fx_extra_fine_portamento_down(flags, chan, param & 0x0F); break; case 0xf0: fx_fine_portamento_down(flags, chan, param & 0x0F); break; default: fx_reg_portamento_down(flags, chan, param); break; } } static void fx_tone_portamento(uint32_t flags, song_voice_t *chan, uint32_t param) { int delta; if (!param) param = chan->mem_portanote; chan->flags |= CHN_PORTAMENTO; if (chan->period && chan->portamento_target && !(flags & SONG_FIRSTTICK)) { if (chan->period < chan->portamento_target) { if (flags & SONG_LINEARSLIDES) { uint32_t n = MIN(255, param); delta = _muldivr(chan->period, linear_slide_up_table[n], 65536) - chan->period; if (delta < 1) delta = 1; } else { delta = param * 4; } chan->period += delta; if (chan->period > chan->portamento_target) { chan->period = chan->portamento_target; chan->portamento_target = 0; } } else if (chan->period > chan->portamento_target) { if (flags & SONG_LINEARSLIDES) { uint32_t n = MIN(255, param); delta = _muldivr(chan->period, linear_slide_down_table[n], 65536) - chan->period; if (delta > -1) delta = -1; } else { delta = -param * 4; } chan->period += delta; if (chan->period < chan->portamento_target) { chan->period = chan->portamento_target; chan->portamento_target = 0; } } } } // Implemented for IMF compatibility, can't actually save this in any formats // sign should be 1 (up) or -1 (down) static void fx_note_slide(uint32_t flags, song_voice_t *chan, uint32_t param, int sign) { uint8_t x, y; if (flags & SONG_FIRSTTICK) { x = param & 0xf0; if (x) chan->note_slide_speed = (x >> 4); y = param & 0xf; if (y) chan->note_slide_step = y; chan->note_slide_counter = chan->note_slide_speed; } else { if (--chan->note_slide_counter == 0) { chan->note_slide_counter = chan->note_slide_speed; // update it chan->period = get_period_from_note (sign * chan->note_slide_step + get_note_from_period(chan->period), 8363, 0); } } } static void fx_vibrato(song_voice_t *p, uint32_t param) { if (param & 0x0F) p->vibrato_depth = (param & 0x0F) * 4; if (param & 0xF0) p->vibrato_speed = (param >> 4) & 0x0F; p->flags |= CHN_VIBRATO; } static void fx_fine_vibrato(song_voice_t *p, uint32_t param) { if (param & 0x0F) p->vibrato_depth = param & 0x0F; if (param & 0xF0) p->vibrato_speed = (param >> 4) & 0x0F; p->flags |= CHN_VIBRATO; } static void fx_panbrello(song_voice_t *chan, uint32_t param) { unsigned int panpos = chan->panbrello_position & 0xFF; int pdelta; if (param & 0x0F) chan->panbrello_depth = param & 0x0F; if (param & 0xF0) chan->panbrello_speed = (param >> 4) & 0x0F; switch (chan->panbrello_type) { case VIB_SINE: default: pdelta = sine_table[panpos]; break; case VIB_RAMP_DOWN: pdelta = ramp_down_table[panpos]; break; case VIB_SQUARE: pdelta = square_table[panpos]; break; case VIB_RANDOM: pdelta = 128 * ((double) rand() / RAND_MAX) - 64; break; } chan->panbrello_position += chan->panbrello_speed; pdelta = ((pdelta * (int)chan->panbrello_depth) + 2) >> 3; chan->panbrello_delta = pdelta; } static void fx_volume_up(song_voice_t *chan, uint32_t param) { chan->volume += param * 4; if (chan->volume > 256) chan->volume = 256; } static void fx_volume_down(song_voice_t *chan, uint32_t param) { chan->volume -= param * 4; if (chan->volume < 0) chan->volume = 0; } static void fx_volume_slide(uint32_t flags, song_voice_t *chan, uint32_t param) { // Dxx Volume slide down // // if (xx == 0) then xx = last xx for (Dxx/Kxx/Lxx) for this channel. if (param) chan->mem_volslide = param; else param = chan->mem_volslide; // Order of testing: Dx0, D0x, DxF, DFx if (param == (param & 0xf0)) { // Dx0 Set effect update for channel enabled if channel is ON. // If x = F, then slide up volume by 15 straight away also (for S3M compat) // Every update, add x to the volume, check and clip values > 64 to 64 param >>= 4; if (param == 0xf || !(flags & SONG_FIRSTTICK)) fx_volume_up(chan, param); } else if (param == (param & 0xf)) { // D0x Set effect update for channel enabled if channel is ON. // If x = F, then slide down volume by 15 straight away also (for S3M) // Every update, subtract x from the volume, check and clip values < 0 to 0 if (param == 0xf || !(flags & SONG_FIRSTTICK)) fx_volume_down(chan, param); } else if ((param & 0xf) == 0xf) { // DxF Add x to volume straight away. Check and clip values > 64 to 64 param >>= 4; if (flags & SONG_FIRSTTICK) fx_volume_up(chan, param); } else if ((param & 0xf0) == 0xf0) { // DFx Subtract x from volume straight away. Check and clip values < 0 to 0 param &= 0xf; if (flags & SONG_FIRSTTICK) fx_volume_down(chan, param); } } static void fx_panning_slide(uint32_t flags, song_voice_t *chan, uint32_t param) { int32_t slide = 0; if (param) chan->mem_panslide = param; else param = chan->mem_panslide; if ((param & 0x0F) == 0x0F && (param & 0xF0)) { if (flags & SONG_FIRSTTICK) { param = (param & 0xF0) >> 2; slide = - (int)param; } } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) { if (flags & SONG_FIRSTTICK) { slide = (param & 0x0F) << 2; } } else { if (!(flags & SONG_FIRSTTICK)) { if (param & 0x0F) slide = (int)((param & 0x0F) << 2); else slide = -(int)((param & 0xF0) >> 2); } } if (slide) { slide += chan->panning; chan->panning = CLAMP(slide, 0, 256); } chan->flags &= ~CHN_SURROUND; chan->panbrello_delta = 0; } static void fx_tremolo(uint32_t flags, song_voice_t *chan, uint32_t param) { unsigned int trempos = chan->tremolo_position & 0xFF; int tdelta; if (param & 0x0F) chan->tremolo_depth = (param & 0x0F) << 2; if (param & 0xF0) chan->tremolo_speed = (param >> 4) & 0x0F; chan->flags |= CHN_TREMOLO; // don't handle on first tick if old-effects mode if ((flags & SONG_FIRSTTICK) && (flags & SONG_ITOLDEFFECTS)) return; switch (chan->tremolo_type) { case VIB_SINE: default: tdelta = sine_table[trempos]; break; case VIB_RAMP_DOWN: tdelta = ramp_down_table[trempos]; break; case VIB_SQUARE: tdelta = square_table[trempos]; break; case VIB_RANDOM: tdelta = 128 * ((double) rand() / RAND_MAX) - 64; break; } chan->tremolo_position = (trempos + 4 * chan->tremolo_speed) & 0xFF; tdelta = (tdelta * (int)chan->tremolo_depth) >> 5; chan->tremolo_delta = tdelta; } static void fx_retrig_note(song_t *csf, uint32_t nchan, uint32_t param) { song_voice_t *chan = &csf->voices[nchan]; //printf("Q%02X note=%02X tick%d %d\n", param, chan->row_note, tick_count, chan->cd_retrig); if ((csf->flags & SONG_FIRSTTICK) && chan->row_note != NOTE_NONE) { chan->cd_retrig = param & 0xf; } else if (--chan->cd_retrig <= 0) { // in Impulse Tracker, retrig only works if a sample is currently playing in the channel if (chan->position == 0) return; chan->cd_retrig = param & 0xf; param >>= 4; if (param) { int vol = chan->volume; if (retrig_table_1[param]) vol = (vol * retrig_table_1[param]) >> 4; else vol += (retrig_table_2[param]) << 2; chan->volume = CLAMP(vol, 0, 256); chan->flags |= CHN_FASTVOLRAMP; } uint32_t note = chan->new_note; int32_t period = chan->period; if (NOTE_IS_NOTE(note) && chan->length) csf_check_nna(csf, nchan, 0, note, 1); csf_note_change(csf, nchan, note, 1, 1, 0); if (period && chan->row_note == NOTE_NONE) chan->period = period; chan->position = chan->position_frac = 0; } } static void fx_channel_vol_slide(uint32_t flags, song_voice_t *chan, uint32_t param) { int32_t slide = 0; if (param) chan->mem_channel_volslide = param; else param = chan->mem_channel_volslide; if ((param & 0x0F) == 0x0F && (param & 0xF0)) { if (flags & SONG_FIRSTTICK) slide = param >> 4; } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) { if (flags & SONG_FIRSTTICK) slide = - (int)(param & 0x0F); } else { if (!(flags & SONG_FIRSTTICK)) { if (param & 0x0F) slide = -(int)(param & 0x0F); else slide = (int)((param & 0xF0) >> 4); } } if (slide) { slide += chan->global_volume; chan->global_volume = CLAMP(slide, 0, 64); } } static void fx_global_vol_slide(song_t *csf, song_voice_t *chan, uint32_t param) { int32_t slide = 0; if (param) chan->mem_global_volslide = param; else param = chan->mem_global_volslide; if ((param & 0x0F) == 0x0F && (param & 0xF0)) { if (csf->flags & SONG_FIRSTTICK) slide = param >> 4; } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) { if (csf->flags & SONG_FIRSTTICK) slide = -(int)(param & 0x0F); } else { if (!(csf->flags & SONG_FIRSTTICK)) { if (param & 0xF0) slide = (int)((param & 0xF0) >> 4); else slide = -(int)(param & 0x0F); } } if (slide) { slide += csf->current_global_volume; csf->current_global_volume = CLAMP(slide, 0, 128); } } static void fx_pattern_loop(song_t *csf, song_voice_t *chan, uint32_t param) { if (param) { if (chan->cd_patloop) { if (!--chan->cd_patloop) { // 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 chan->patloop_row = csf->row + 1; return; // don't loop! } } else { chan->cd_patloop = param; } csf->process_row = chan->patloop_row - 1; } else { chan->patloop_row = csf->row; } } static void fx_special(song_t *csf, uint32_t nchan, uint32_t param) { song_voice_t *chan = &csf->voices[nchan]; uint32_t command = param & 0xF0; param &= 0x0F; switch(command) { // S0x: Set Filter // S1x: Set Glissando Control case 0x10: chan->flags &= ~CHN_GLISSANDO; if (param) chan->flags |= CHN_GLISSANDO; break; // S2x: Set FineTune (no longer implemented) // S3x: Set Vibrato WaveForm case 0x30: chan->vib_type = param; break; // S4x: Set Tremolo WaveForm case 0x40: chan->tremolo_type = param; break; // S5x: Set Panbrello WaveForm case 0x50: chan->panbrello_type = param; break; // S6x: Pattern Delay for x ticks case 0x60: if (csf->flags & SONG_FIRSTTICK) csf->tick_count += param; break; // S7x: Envelope Control case 0x70: if (!(csf->flags & SONG_FIRSTTICK)) break; switch(param) { case 0: case 1: case 2: { song_voice_t *bkp = &csf->voices[MAX_CHANNELS]; for (uint32_t i=MAX_CHANNELS; imaster_channel == nchan+1) { if (param == 1) { fx_key_off(csf, i); } else if (param == 2) { bkp->flags |= CHN_NOTEFADE; } else { bkp->flags |= CHN_NOTEFADE; bkp->fadeout_volume = 0; } } } } break; case 3: chan->nna = NNA_NOTECUT; break; case 4: chan->nna = NNA_CONTINUE; break; case 5: chan->nna = NNA_NOTEOFF; break; case 6: chan->nna = NNA_NOTEFADE; break; case 7: chan->flags &= ~CHN_VOLENV; break; case 8: chan->flags |= CHN_VOLENV; break; case 9: chan->flags &= ~CHN_PANENV; break; case 10: chan->flags |= CHN_PANENV; break; case 11: chan->flags &= ~CHN_PITCHENV; break; case 12: chan->flags |= CHN_PITCHENV; break; } break; // S8x: Set 4-bit Panning case 0x80: if (csf->flags & SONG_FIRSTTICK) { chan->flags &= ~CHN_SURROUND; chan->panbrello_delta = 0; chan->panning = (param << 4) + 8; chan->flags |= CHN_FASTVOLRAMP; chan->pan_swing = 0; } break; // S9x: Set Surround case 0x90: if (param == 1 && (csf->flags & SONG_FIRSTTICK)) { chan->flags |= CHN_SURROUND; chan->panbrello_delta = 0; chan->panning = 128; } break; // SAx: Set 64k Offset // Note: don't actually APPLY the offset, and don't clear the regular offset value, either. case 0xA0: if (csf->flags & SONG_FIRSTTICK) { chan->mem_offset = (param << 16) | (chan->mem_offset & ~0xf0000); } break; // SBx: Pattern Loop case 0xB0: if (csf->flags & SONG_FIRSTTICK) fx_pattern_loop(csf, chan, param & 0x0F); break; // SCx: Note Cut case 0xC0: if (csf->flags & SONG_FIRSTTICK) chan->cd_note_cut = param ?: 1; else if (--chan->cd_note_cut == 0) fx_note_cut(csf, nchan, 0); break; // SDx: Note Delay // SEx: Pattern Delay for x rows case 0xE0: if (csf->flags & SONG_FIRSTTICK) { if (!csf->row_count) // ugh! csf->row_count = param + 1; } break; // SFx: Set Active Midi Macro case 0xF0: chan->active_macro = param; break; } } // this is all brisby void csf_midi_send(song_t *csf, const unsigned char *data, unsigned int len, uint32_t nchan, int fake) { song_voice_t *chan = &csf->voices[nchan]; int oldcutoff; const unsigned char *idata = data; unsigned int ilen = len; while (ilen > 4 && idata[0] == 0xF0 && idata[1] == 0xF0) { // impulse tracker filter control (mfg. 0xF0) switch (idata[2]) { case 0x00: // set cutoff oldcutoff = chan->cutoff; if (idata[3] < 0x80) chan->cutoff = idata[3]; oldcutoff -= chan->cutoff; if (oldcutoff < 0) oldcutoff = -oldcutoff; if (chan->volume > 0 || oldcutoff < 0x10 || !(chan->flags & CHN_FILTER) || !(chan->left_volume|chan->right_volume)) { setup_channel_filter(chan, !(chan->flags & CHN_FILTER), 256, csf->mix_frequency); } break; case 0x01: // set resonance if (idata[3] < 0x80) chan->resonance = idata[3]; setup_channel_filter(chan, !(chan->flags & CHN_FILTER), 256, csf->mix_frequency); break; } idata += 4; ilen -= 4; } if (!fake && csf_midi_out_raw) { /* okay, this is kind of how it works. we pass buffer_count as here because while 1000 * ((8((buffer_size/2) - buffer_count)) / 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 buffer_count simply has no frame of reference. fortunately, schism does and can complete this (tags: _schism_midi_out_raw ) */ csf_midi_out_raw(data, len, csf->buffer_count); } } 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 csf_process_midi_macro(song_t *csf, uint32_t nchan, const char * macro, uint32_t param, uint32_t note, uint32_t velocity, uint32_t use_instr) { /* this was all wrong. -mrsb */ song_voice_t *chan = &csf->voices[nchan]; song_instrument_t *penv = ((csf->flags & SONG_INSTRUMENTMODE) && chan->last_instrument < MAX_INSTRUMENTS) ? csf->instruments[use_instr ?: chan->last_instrument] : 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->midi_channel_mask == 0) { /* okay, there _IS_ no real midi channel. forget this for now... */ mc = 15; fake = 1; } else if (penv->midi_channel_mask >= 0x10000) { mc = (nchan-1) % 16; } else { mc = 0; while(!(penv->midi_channel_mask & (1 << mc))) ++mc; } for (i = j = x = 0, cx =0; i <= 32 && macro[i]; i++) { int c, cw; if (macro[i] >= '0' && macro[i] <= '9') { c = macro[i] - '0'; cw = 1; } else if (macro[i] >= 'A' && macro[i] <= 'F') { c = (macro[i] - 'A') + 10; cw = 1; } else if (macro[i] == 'c') { c = mc; cw = 1; saw_c = 1; } else if (macro[i] == 'n') { c = (note-1); cw = 2; } else if (macro[i] == 'v') { c = velocity; cw = 2; } else if (macro[i] == 'u') { c = (chan->volume >> 1); if (c > 127) c = 127; cw = 2; } else if (macro[i] == 'x') { c = chan->panning; if (c > 127) c = 127; cw = 2; } else if (macro[i] == 'y') { c = chan->final_panning; if (c > 127) c = 127; cw = 2; } else if (macro[i] == 'a') { if (!penv) c = 0; else c = (penv->midi_bank >> 7) & 127; cw = 2; } else if (macro[i] == 'b') { if (!penv) c = 0; else c = penv->midi_bank & 127; cw = 2; } else if (macro[i] == 'z' || macro[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)) { csf_midi_send(csf, outbuffer, x, nchan, 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++; } } csf_midi_send(csf, outbuffer, x, nchan, saw_c && fake); } } //////////////////////////////////////////////////////////// // Length #if MAX_CHANNELS != 64 # error csf_get_length assumes 64 channels #endif unsigned int csf_get_length(song_t *csf) { uint32_t elapsed = 0, row = 0, next_row = 0, cur_order = 0, next_order = 0, pat = csf->orderlist[0], speed = csf->initial_speed, tempo = csf->initial_tempo, psize, n; uint32_t patloop[MAX_CHANNELS] = {0}; uint8_t mem_tempo[MAX_CHANNELS] = {0}; uint64_t setloop = 0; // bitmask const song_note_t *pdata; for (;;) { uint32_t speed_count = 0; row = next_row; cur_order = next_order; // Check if pattern is valid pat = csf->orderlist[cur_order]; while (pat >= MAX_PATTERNS) { // End of song ? if (pat == ORDER_LAST || cur_order >= MAX_ORDERS) { pat = ORDER_LAST; // cause break from outer loop too break; } else { cur_order++; pat = (cur_order < MAX_ORDERS) ? csf->orderlist[cur_order] : ORDER_LAST; } next_order = cur_order; } // Weird stuff? if (pat >= MAX_PATTERNS) break; pdata = csf->patterns[pat]; if (pdata) { psize = csf->pattern_size[pat]; } else { pdata = blank_pattern; psize = 64; } // guard against Cxx to invalid row, etc. if (row >= psize) row = 0; // Update next position next_row = row + 1; if (next_row >= psize) { next_order = cur_order + 1; next_row = 0; } /* muahahaha */ if (csf->stop_at_order > -1 && csf->stop_at_row > -1) { if (csf->stop_at_order <= (signed) cur_order && csf->stop_at_row <= (signed) row) break; if (csf->stop_at_time > 0) { /* stupid api decision */ if (((elapsed + 500) / 1000) >= csf->stop_at_time) { csf->stop_at_order = cur_order; csf->stop_at_row = row; break; } } } /* This is nasty, but it fixes inaccuracies with SB0 SB1 SB1. (Simultaneous loops in multiple channels are still wildly incorrect, though.) */ if (!row) setloop = ~0; if (setloop) { for (n = 0; n < MAX_CHANNELS; n++) if (setloop & (1 << n)) patloop[n] = elapsed; setloop = 0; } const song_note_t *note = pdata + row * MAX_CHANNELS; for (n = 0; n < MAX_CHANNELS; note++, n++) { uint32_t param = note->param; switch (note->effect) { case FX_NONE: break; case FX_POSITIONJUMP: next_order = param > cur_order ? param : cur_order + 1; next_row = 0; break; case FX_PATTERNBREAK: next_order = cur_order + 1; next_row = param; break; case FX_SPEED: if (param) speed = param; break; case FX_TEMPO: if (param) mem_tempo[n] = param; else param = mem_tempo[n]; int d = (param & 0xf); switch (param >> 4) { default: tempo = param; break; case 0: d = -d; case 1: d = d * (speed - 1) + tempo; tempo = CLAMP(d, 32, 255); break; } break; case FX_SPECIAL: switch (param >> 4) { case 0x6: speed_count = param & 0x0F; break; case 0xb: if (param & 0x0F) { elapsed += (elapsed - patloop[n]) * (param & 0x0F); patloop[n] = 0xffffffff; setloop = 1; } else { patloop[n] = elapsed; } break; case 0xe: speed_count = (param & 0x0F) * speed; break; } break; } } // sec/tick = 5 / (2 * tempo) // msec/tick = 5000 / (2 * tempo) // = 2500 / tempo elapsed += (speed + speed_count) * 2500 / tempo; } return (elapsed + 500) / 1000; } ////////////////////////////////////////////////////////////////////////////////////////////////// // Effects song_sample_t *csf_translate_keyboard(song_t *csf, song_instrument_t *penv, uint32_t note, song_sample_t *def) { uint32_t n = penv->sample_map[note - 1]; return (n && n < MAX_SAMPLES) ? &csf->samples[n] : def; } static void env_reset(song_voice_t *chan, int always) { if (chan->ptr_instrument) { chan->flags |= CHN_FASTVOLRAMP; if (always) { chan->vol_env_position = 0; chan->pan_env_position = 0; chan->pitch_env_position = 0; } else { /* only reset envelopes with carry off */ if (!(chan->ptr_instrument->flags & ENV_VOLCARRY)) chan->vol_env_position = 0; if (!(chan->ptr_instrument->flags & ENV_PANCARRY)) chan->pan_env_position = 0; if (!(chan->ptr_instrument->flags & ENV_PITCHCARRY)) chan->pitch_env_position = 0; } } // this was migrated from csf_note_change, should it be here? chan->flags &= ~CHN_NOTEFADE; chan->fadeout_volume = 65536; } void csf_instrument_change(song_t *csf, song_voice_t *chan, uint32_t instr, int porta, int inst_column) { int inst_changed = 0; if (instr >= MAX_INSTRUMENTS) return; song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? csf->instruments[instr] : NULL; song_sample_t *psmp = chan->ptr_sample; uint32_t note = chan->new_note; if (note == NOTE_NONE) { /* nothing to see here */ } else if (NOTE_IS_CONTROL(note)) { /* nothing here either */ } else if (penv) { if (NOTE_IS_CONTROL(penv->note_map[note-1])) return; if (!(porta && penv == chan->ptr_instrument && chan->ptr_sample && chan->current_sample_data)) psmp = csf_translate_keyboard(csf, penv, note, NULL); chan->flags &= ~CHN_SUSTAINLOOP; // turn off sustain } else { psmp = csf->samples + instr; } // Update Volume if (inst_column && psmp) chan->volume = psmp->volume; // inst_changed is used for IT carry-on env option if (penv != chan->ptr_instrument || !chan->current_sample_data) { inst_changed = 1; chan->ptr_instrument = penv; } // Instrument adjust chan->new_instrument = 0; if (psmp) { psmp->played = 1; if (penv) { penv->played = 1; chan->instrument_volume = (psmp->global_volume * penv->global_volume) >> 7; if (penv->flags & ENV_SETPANNING) { chan->panning = penv->panning; chan->flags &= ~CHN_SURROUND; } chan->nna = penv->nna; } else { chan->instrument_volume = psmp->global_volume; } if (psmp->flags & CHN_PANNING) { chan->panning = psmp->panning; chan->flags &= ~CHN_SURROUND; } } // Reset envelopes // Conditions experimentally determined to cause envelope reset in Impulse Tracker: // - no note currently playing (of course) // - note given, no portamento // - instrument number given, portamento, compat gxx enabled // - instrument number given, no portamento, after keyoff, old effects enabled // If someone can enlighten me to what the logic really is here, I'd appreciate it. // Seems like it's just a total mess though, probably to get XMs to play right. if (penv) { if (( !chan->length ) || ( inst_column && porta && (csf->flags & SONG_COMPATGXX) ) || ( inst_column && !porta && (chan->flags & (CHN_NOTEFADE|CHN_KEYOFF)) && (csf->flags & SONG_ITOLDEFFECTS) )) { env_reset(chan, inst_changed || (chan->flags & CHN_KEYOFF)); } else if (!(penv->flags & ENV_VOLUME)) { // XXX why is this being done? chan->vol_env_position = 0; } chan->vol_swing = chan->pan_swing = 0; if (penv->vol_swing) { /* this was wrong, and then it was still wrong. (possibly it continues to be wrong even now?) */ double d = 2 * (((double) rand()) / RAND_MAX) - 1; // floor() is applied to get exactly the same volume levels as in IT. -- Saga chan->vol_swing = floor(d * penv->vol_swing / 100.0 * chan->instrument_volume); } if (penv->pan_swing) { /* this was also wrong, and even more so */ double d = 2 * (((double) rand()) / RAND_MAX) - 1; chan->pan_swing = d * penv->pan_swing * 4; } } // Invalid sample ? if (!psmp) { chan->ptr_sample = NULL; chan->instrument_volume = 0; return; } if (psmp == chan->ptr_sample && chan->current_sample_data && chan->length) return; // sample change: reset sample vibrato chan->autovib_depth = 0; chan->autovib_position = 0; if ((chan->flags & (CHN_KEYOFF | CHN_NOTEFADE)) && inst_column) { // Don't start new notes after ===/~~~ chan->period = 0; } else { chan->period = get_period_from_note(note, psmp->c5speed, csf->flags & SONG_LINEARSLIDES); } chan->flags &= ~(CHN_SAMPLE_FLAGS | CHN_KEYOFF | CHN_NOTEFADE | CHN_VOLENV | CHN_PANENV | CHN_PITCHENV); chan->flags |= psmp->flags & CHN_SAMPLE_FLAGS; if (penv) { if (penv->flags & ENV_VOLUME) chan->flags |= CHN_VOLENV; if (penv->flags & ENV_PANNING) chan->flags |= CHN_PANENV; if (penv->flags & ENV_PITCH) chan->flags |= CHN_PITCHENV; if ((penv->flags & ENV_PITCH) && (penv->flags & ENV_FILTER) && !chan->cutoff) chan->cutoff = 0x7F; if (penv->ifc & 0x80) chan->cutoff = penv->ifc & 0x7F; if (penv->ifr & 0x80) chan->resonance = penv->ifr & 0x7F; } chan->ptr_sample = psmp; chan->length = psmp->length; chan->loop_start = psmp->loop_start; chan->loop_end = psmp->loop_end; chan->c5speed = psmp->c5speed; chan->current_sample_data = psmp->data; chan->position = 0; if (chan->flags & CHN_SUSTAINLOOP) { chan->loop_start = psmp->sustain_start; chan->loop_end = psmp->sustain_end; chan->flags |= CHN_LOOP; if (chan->flags & CHN_PINGPONGSUSTAIN) chan->flags |= CHN_PINGPONGLOOP; } if ((chan->flags & CHN_LOOP) && chan->loop_end < chan->length) chan->length = chan->loop_end; /*fprintf(stderr, "length set as %d (from %d), ch flags %X smp flags %X\n", (int)chan->length, (int)psmp->length, chan->flags, psmp->flags);*/ } // have_inst is a hack to ignore the note-sample map when no instrument number is present void csf_note_change(song_t *csf, uint32_t nchan, int note, int porta, int retrig, int have_inst) { // why would csf_note_change ever get a negative value for 'note'? if (note == NOTE_NONE || note < 0) return; // save the note that's actually used, as it's necessary to properly calculate PPS and stuff // (and also needed for correct display of note dots) int truenote = note; song_voice_t *chan = &csf->voices[nchan]; song_sample_t *pins = chan->ptr_sample; song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL; if (penv && NOTE_IS_NOTE(note)) { if (!(have_inst && porta && pins)) pins = csf_translate_keyboard(csf, penv, note, pins); note = penv->note_map[note - 1]; chan->flags &= ~CHN_SUSTAINLOOP; // turn off sustain } if (NOTE_IS_CONTROL(note)) { // hax: keep random sample numbers from triggering notes (see csf_instrument_change) // NOTE_OFF is a completely arbitrary choice - this could be anything above NOTE_LAST chan->new_note = NOTE_OFF; switch (note) { case NOTE_OFF: fx_key_off(csf, nchan); break; case NOTE_CUT: fx_note_cut(csf, nchan, 1); break; case NOTE_FADE: default: // Impulse Tracker handles all unknown notes as fade internally chan->flags |= CHN_NOTEFADE; break; } return; } if (!pins) return; note = CLAMP(note, NOTE_FIRST, NOTE_LAST); chan->note = CLAMP(truenote, NOTE_FIRST, NOTE_LAST); chan->new_instrument = 0; uint32_t period = get_period_from_note(note, chan->c5speed, csf->flags & SONG_LINEARSLIDES); if (period) { if (porta && chan->period) { chan->portamento_target = period; } else { chan->portamento_target = 0; chan->period = period; } if (!porta || !chan->length) { chan->ptr_sample = pins; chan->current_sample_data = pins->data; chan->length = pins->length; chan->loop_end = pins->length; chan->loop_start = 0; chan->c5speed = pins->c5speed; chan->flags = (chan->flags & ~CHN_SAMPLE_FLAGS) | (pins->flags & CHN_SAMPLE_FLAGS); if (chan->flags & CHN_SUSTAINLOOP) { chan->loop_start = pins->sustain_start; chan->loop_end = pins->sustain_end; chan->flags &= ~CHN_PINGPONGLOOP; chan->flags |= CHN_LOOP; if (chan->flags & CHN_PINGPONGSUSTAIN) chan->flags |= CHN_PINGPONGLOOP; if (chan->length > chan->loop_end) chan->length = chan->loop_end; } else if (chan->flags & CHN_LOOP) { chan->loop_start = pins->loop_start; chan->loop_end = pins->loop_end; if (chan->length > chan->loop_end) chan->length = chan->loop_end; } chan->position = chan->position_frac = 0; } if (chan->position >= chan->length) chan->position = chan->loop_start; } else { porta = 0; } if (!porta) env_reset(chan, 0); chan->flags &= ~CHN_KEYOFF; // Enable Ramping if (!porta) { chan->vu_meter = 0x0; chan->strike = 4; /* this affects how long the initial hit on the playback marks lasts (bigger dot in instrument and sample list windows)*/ chan->flags &= ~CHN_FILTER; chan->flags |= CHN_FASTVOLRAMP; if (!retrig) { chan->autovib_depth = 0; chan->autovib_position = 0; chan->vibrato_position = 0; } chan->left_volume = chan->right_volume = 0; // Setup Initial Filter for this note if (penv) { if (penv->ifr & 0x80) chan->resonance = penv->ifr & 0x7F; if (penv->ifc & 0x80) chan->cutoff = penv->ifc & 0x7F; } else { chan->vol_swing = chan->pan_swing = 0; } if (chan->cutoff < 0x7F) setup_channel_filter(chan, 1, 256, csf->mix_frequency); } } uint32_t csf_get_nna_channel(song_t *csf, uint32_t nchan) { song_voice_t *chan = &csf->voices[nchan]; // Check for empty channel song_voice_t *pi = &csf->voices[MAX_CHANNELS]; for (uint32_t i=MAX_CHANNELS; ilength) { if (pi->flags & CHN_MUTE) { if (pi->flags & CHN_NNAMUTE) { pi->flags &= ~(CHN_NNAMUTE|CHN_MUTE); } else { /* this channel is muted; skip */ continue; } } return i; } } if (!chan->fadeout_volume) return 0; // All channels are used: check for lowest volume uint32_t result = 0; uint32_t vol = 64*65536; // 25% int envpos = 0xFFFFFF; const song_voice_t *pj = &csf->voices[MAX_CHANNELS]; for (uint32_t j=MAX_CHANNELS; jfadeout_volume) return j; uint32_t v = pj->volume; if (pj->flags & CHN_NOTEFADE) v = v * pj->fadeout_volume; else v <<= 16; if (pj->flags & CHN_LOOP) v >>= 1; if (v < vol || (v == vol && pj->vol_env_position > envpos)) { envpos = pj->vol_env_position; vol = v; result = j; } } if (result) { /* unmute new nna channel */ csf->voices[result].flags &= ~(CHN_MUTE|CHN_NNAMUTE); } return result; } void csf_check_nna(song_t *csf, uint32_t nchan, uint32_t instr, int note, int force_cut) { song_voice_t *p; song_voice_t *chan = &csf->voices[nchan]; song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL; song_instrument_t *ptr_instrument; signed char *data; if (!NOTE_IS_NOTE(note)) return; // Always NNA cut - using if (force_cut || !(csf->flags & SONG_INSTRUMENTMODE)) { if (!chan->length || (chan->flags & CHN_MUTE) || (!chan->left_volume && !chan->right_volume)) return; uint32_t n = csf_get_nna_channel(csf, nchan); if (!n) return; p = &csf->voices[n]; // Copy Channel *p = *chan; p->flags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PORTAMENTO); p->tremolo_delta = 0; p->master_channel = nchan+1; p->n_command = 0; // Cut the note p->fadeout_volume = 0; p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); // Stop this channel chan->length = chan->position = chan->position_frac = 0; chan->rofs = chan->lofs = 0; chan->left_volume = chan->right_volume = 0; if (chan->flags & CHN_ADLIB) { //Do this only if really an adlib chan. Important! OPL_NoteOff(nchan); OPL_Touch(nchan, 0); } GM_KeyOff(nchan); GM_Touch(nchan, 0); return; } if (instr >= MAX_INSTRUMENTS) instr = 0; data = chan->current_sample_data; ptr_instrument = chan->ptr_instrument; if (instr && note) { ptr_instrument = (csf->flags & SONG_INSTRUMENTMODE) ? csf->instruments[instr] : NULL; if (ptr_instrument) { uint32_t n = 0; if (!NOTE_IS_CONTROL(note)) { n = ptr_instrument->sample_map[note-1]; note = ptr_instrument->note_map[note-1]; if (n && n < MAX_SAMPLES) data = csf->samples[n].data; } } else { data = NULL; } } if (!penv) return; p = chan; for (uint32_t i=nchan; i= MAX_CHANNELS || p == chan) && ((p->master_channel == nchan+1 || p == chan) && p->ptr_instrument))) continue; int ok = 0; // Duplicate Check Type switch (p->ptr_instrument->dct) { case DCT_NOTE: ok = (NOTE_IS_NOTE(note) && (int) p->note == note && ptr_instrument == p->ptr_instrument); break; case DCT_SAMPLE: ok = (data && data == p->current_sample_data); break; case DCT_INSTRUMENT: ok = (ptr_instrument == p->ptr_instrument); break; } // Duplicate Note Action if (ok) { switch(p->ptr_instrument->dca) { case DCA_NOTECUT: fx_note_cut(csf, i, 1); break; case DCA_NOTEOFF: fx_key_off(csf, i); break; case DCA_NOTEFADE: p->flags |= CHN_NOTEFADE; break; } if (!p->volume) { p->fadeout_volume = 0; p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); } } } if (chan->flags & CHN_MUTE) return; // New Note Action if (chan->volume && chan->length) { uint32_t n = csf_get_nna_channel(csf, nchan); if (n) { p = &csf->voices[n]; // Copy Channel *p = *chan; p->flags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PORTAMENTO); p->tremolo_delta = 0; p->master_channel = nchan+1; p->n_command = 0; // Key Off the note switch(chan->nna) { case NNA_NOTEOFF: fx_key_off(csf, n); break; case NNA_NOTECUT: p->fadeout_volume = 0; case NNA_NOTEFADE: p->flags |= CHN_NOTEFADE; break; } if (!p->volume) { p->fadeout_volume = 0; p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); } // Stop this channel chan->length = chan->position = chan->position_frac = 0; chan->rofs = chan->lofs = 0; } } } static void handle_effect(song_t *csf, uint32_t nchan, uint32_t cmd, uint32_t param, int porta, int firsttick) { song_voice_t *chan = csf->voices + nchan; switch (cmd) { case FX_NONE: break; // Set Volume case FX_VOLUME: if (!(csf->flags & SONG_FIRSTTICK)) break; chan->volume = (param < 64) ? param*4 : 256; chan->flags |= CHN_FASTVOLRAMP; break; case FX_PORTAMENTOUP: if (firsttick) { if (param) chan->mem_pitchslide = param; if (!(csf->flags & SONG_COMPATGXX)) chan->mem_portanote = chan->mem_pitchslide; } fx_portamento_up(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_PORTAMENTODOWN: if (firsttick) { if (param) chan->mem_pitchslide = param; if (!(csf->flags & SONG_COMPATGXX)) chan->mem_portanote = chan->mem_pitchslide; } fx_portamento_down(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_VOLUMESLIDE: fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_TONEPORTAMENTO: if (firsttick) { if (param) chan->mem_portanote = param; if (!(csf->flags & SONG_COMPATGXX)) chan->mem_pitchslide = chan->mem_portanote; } fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_TONEPORTAVOL: fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, 0); break; case FX_VIBRATO: fx_vibrato(chan, param); break; case FX_VIBRATOVOL: fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); fx_vibrato(chan, 0); break; case FX_SPEED: if ((csf->flags & SONG_FIRSTTICK) && param) { csf->tick_count = param; csf->current_speed = param; } break; case FX_TEMPO: if (csf->flags & SONG_FIRSTTICK) { if (param) chan->mem_tempo = param; else param = chan->mem_tempo; if (param >= 0x20) csf->current_tempo = param; } else { param = chan->mem_tempo; // this just got set on tick zero switch (param >> 4) { case 0: csf->current_tempo -= param & 0xf; if (csf->current_tempo < 32) csf->current_tempo = 32; break; case 1: csf->current_tempo += param & 0xf; if (csf->current_tempo > 255) csf->current_tempo = 255; break; } } break; case FX_OFFSET: if (!(csf->flags & SONG_FIRSTTICK)) break; if (param) chan->mem_offset = (chan->mem_offset & ~0xff00) | (param << 8); if (NOTE_IS_NOTE(chan->row_note)) { // when would position *not* be zero if there's a note but no portamento? if (porta) chan->position = chan->mem_offset; else chan->position += chan->mem_offset; if (chan->position > chan->length) { chan->position = (csf->flags & SONG_ITOLDEFFECTS) ? chan->length : 0; } } break; case FX_ARPEGGIO: chan->n_command = FX_ARPEGGIO; if (!(csf->flags & SONG_FIRSTTICK)) break; if (param) chan->mem_arpeggio = param; break; case FX_RETRIG: if (param) chan->mem_retrig = param & 0xFF; fx_retrig_note(csf, nchan, chan->mem_retrig); break; case FX_TREMOR: // Tremor logic lifted from DUMB, which is the only player that actually gets it right. // I *sort of* understand it. if (csf->flags & SONG_FIRSTTICK) { if (!param) param = chan->mem_tremor; else if (!(csf->flags & SONG_ITOLDEFFECTS)) { if (param & 0xf0) param -= 0x10; if (param & 0x0f) param -= 0x01; } chan->mem_tremor = param; chan->cd_tremor |= 128; } if ((chan->cd_tremor & 128) && chan->length) { if (chan->cd_tremor == 128) chan->cd_tremor = (chan->mem_tremor >> 4) | 192; else if (chan->cd_tremor == 192) chan->cd_tremor = (chan->mem_tremor & 0xf) | 128; else chan->cd_tremor--; } chan->n_command = FX_TREMOR; break; case FX_GLOBALVOLUME: if (!(csf->flags & SONG_FIRSTTICK)) break; if (param <= 128) csf->current_global_volume = param; break; case FX_GLOBALVOLSLIDE: fx_global_vol_slide(csf, chan, param); break; case FX_PANNING: if (!(csf->flags & SONG_FIRSTTICK)) break; chan->flags &= ~CHN_SURROUND; chan->panbrello_delta = 0; chan->panning = param; chan->pan_swing = 0; chan->flags |= CHN_FASTVOLRAMP; break; case FX_PANNINGSLIDE: fx_panning_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_TREMOLO: fx_tremolo(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_FINEVIBRATO: fx_fine_vibrato(chan, param); break; case FX_SPECIAL: fx_special(csf, nchan, param); break; case FX_KEYOFF: if ((csf->current_speed - csf->tick_count) == param) fx_key_off(csf, nchan); break; case FX_CHANNELVOLUME: if (!(csf->flags & SONG_FIRSTTICK)) break; // FIXME rename global_volume to channel_volume in the channel struct if (param <= 64) { chan->global_volume = param; chan->flags |= CHN_FASTVOLRAMP; } break; case FX_CHANNELVOLSLIDE: fx_channel_vol_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_PANBRELLO: fx_panbrello(chan, param); break; case FX_SETENVPOSITION: if (!(csf->flags & SONG_FIRSTTICK)) break; chan->vol_env_position = param; chan->pan_env_position = param; chan->pitch_env_position = param; if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument) { song_instrument_t *penv = chan->ptr_instrument; if ((chan->flags & CHN_PANENV) && (penv->pan_env.nodes) && ((int)param > penv->pan_env.ticks[penv->pan_env.nodes-1])) { chan->flags &= ~CHN_PANENV; } } break; case FX_POSITIONJUMP: if (csf->flags & SONG_FIRSTTICK) { if (!(csf->mix_flags & SNDMIX_NOBACKWARDJUMPS) || csf->process_order < param) csf->process_order = param - 1; csf->process_row = PROCESS_NEXT_ORDER; } break; case FX_PATTERNBREAK: if (csf->flags & SONG_FIRSTTICK) { csf->break_row = param; csf->process_row = PROCESS_NEXT_ORDER; } break; case FX_MIDI: if (!(csf->flags & SONG_FIRSTTICK)) break; if (param < 0x80) { csf_process_midi_macro(csf, nchan, csf->midi_config.sfx[chan->active_macro], param, 0, 0, 0); } else { csf_process_midi_macro(csf, nchan, csf->midi_config.zxx[param & 0x7F], 0, 0, 0, 0); } break; case FX_NOTESLIDEUP: fx_note_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param, 1); break; case FX_NOTESLIDEDOWN: fx_note_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param, -1); break; } } static void handle_voleffect(song_t *csf, song_voice_t *chan, uint32_t volcmd, uint32_t vol, int firsttick, int start_note) { /* A few notes, paraphrased from ITTECH.TXT: Ex/Fx/Gx are shared with Exx/Fxx/Gxx; Ex/Fx are 4x the 'normal' slide value Gx is linked with Ex/Fx if Compat Gxx is off, just like Gxx is with Exx/Fxx Gx values: 1, 4, 8, 16, 32, 64, 96, 128, 255 Ax/Bx/Cx/Dx values are used directly (i.e. D9 == D09), and are NOT shared with Dxx (value is stored into mem_vc_volslide and used by A0/B0/C0/D0) Hx uses the same value as Hxx and Uxx, and affects the *depth* so... hxx = (hx | (oldhxx & 0xf0)) ??? Additionally: volume and panning are handled on the start tick, not the first tick of the row (that is, SDx alters their behavior) */ switch (volcmd) { case VOLFX_NONE: break; case VOLFX_VOLUME: if (start_note) { if (vol > 64) vol = 64; chan->volume = vol << 2; chan->flags |= CHN_FASTVOLRAMP; } break; case VOLFX_PANNING: if (start_note) { if (vol > 64) vol = 64; chan->panning = vol << 2; chan->pan_swing = 0; chan->flags |= CHN_FASTVOLRAMP; chan->flags &= ~CHN_SURROUND; chan->panbrello_delta = 0; } break; case VOLFX_PORTAUP: // Fx if (firsttick) { if (vol) chan->mem_pitchslide = 4 * vol; if (!(csf->flags & SONG_COMPATGXX)) chan->mem_portanote = chan->mem_pitchslide; } else { fx_reg_portamento_up(csf->flags, chan, chan->mem_pitchslide); } break; case VOLFX_PORTADOWN: // Ex if (firsttick) { if (vol) chan->mem_pitchslide = 4 * vol; if (!(csf->flags & SONG_COMPATGXX)) chan->mem_portanote = chan->mem_pitchslide; } else { fx_reg_portamento_down(csf->flags, chan, chan->mem_pitchslide); } break; case VOLFX_TONEPORTAMENTO: // Gx if (firsttick) { if (vol) chan->mem_portanote = vc_portamento_table[vol & 0x0F]; if (!(csf->flags & SONG_COMPATGXX)) chan->mem_pitchslide = chan->mem_portanote; } fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, vc_portamento_table[vol & 0x0F]); break; case VOLFX_VOLSLIDEUP: // Cx if (firsttick) { if (vol) chan->mem_vc_volslide = vol; } else { fx_volume_up(chan, chan->mem_vc_volslide); } break; case VOLFX_VOLSLIDEDOWN: // Dx if (firsttick) { if (vol) chan->mem_vc_volslide = vol; } else { fx_volume_down(chan, chan->mem_vc_volslide); } break; case VOLFX_FINEVOLUP: // Ax if (firsttick) { if (vol) chan->mem_vc_volslide = vol; else vol = chan->mem_vc_volslide; fx_volume_up(chan, vol); } break; case VOLFX_FINEVOLDOWN: // Bx if (firsttick) { if (vol) chan->mem_vc_volslide = vol; else vol = chan->mem_vc_volslide; fx_volume_down(chan, vol); } break; case VOLFX_VIBRATODEPTH: // Hx fx_vibrato(chan, vol); break; case VOLFX_VIBRATOSPEED: // $x (FT2 compat.) fx_vibrato(chan, vol << 4); break; case VOLFX_PANSLIDELEFT: // flags, chan, vol); break; case VOLFX_PANSLIDERIGHT: // >x (FT2) fx_panning_slide(csf->flags, chan, vol << 4); break; } } /* firsttick is only used for SDx at the moment */ void csf_process_effects(song_t *csf, int firsttick) { song_voice_t *chan = csf->voices; for (uint32_t nchan=0; nchann_command=0; uint32_t instr = chan->row_instr; uint32_t volcmd = chan->row_voleffect; uint32_t vol = chan->row_volparam; uint32_t cmd = chan->row_effect; uint32_t param = chan->row_param; int porta = (cmd == FX_TONEPORTAMENTO || cmd == FX_TONEPORTAVOL || volcmd == VOLFX_TONEPORTAMENTO); int start_note = csf->flags & SONG_FIRSTTICK; chan->flags &= ~CHN_FASTVOLRAMP; // set instrument before doing anything else if (instr) chan->new_instrument = instr; /* Have to handle SDx specially because of the way the effects are structured. In a PERFECT world, this would be very straightforward: - Handle the effect column, and set flags for things that should happen (portamento, volume slides, arpeggio, vibrato, tremolo) - If note delay counter is set, stop processing that channel - Trigger all notes if it's their start tick - Handle volume column. The obvious implication of this is that all effects are checked only once, and volumes only need to be set for notes once. Additionally this helps for separating the mixing code from the rest of the interface (which is always good, especially for hardware mixing...) Oh well, the world is not perfect. */ if (cmd == FX_SPECIAL) { if (param) chan->mem_special = param; else param = chan->mem_special; if (param >> 4 == 0xd) { // Ideally this would use SONG_FIRSTTICK, but Impulse Tracker has a bug here :) if (firsttick) { chan->cd_note_delay = (param & 0xf) ?: 1; continue; // notes never play on the first tick with SDx, go away } if (--chan->cd_note_delay > 0) continue; // not our turn yet, go away start_note = (chan->cd_note_delay == 0); } } // Handles note/instrument/volume changes if (start_note) { uint32_t note = chan->row_note; if (instr && note == NOTE_NONE) { if (csf->flags & SONG_INSTRUMENTMODE) { if (chan->ptr_sample) chan->volume = chan->ptr_sample->volume; } else { if (instr < MAX_SAMPLES) chan->volume = csf->samples[instr].volume; } } // Invalid Instrument ? if (instr >= MAX_INSTRUMENTS) instr = 0; // Note Cut/Off => ignore instrument if ((NOTE_IS_CONTROL(note)) || (note != NOTE_NONE && !porta)) { /* This is required when the instrument changes (KeyOff is not called) */ /* Possibly a better bugfix could be devised. --Bisqwit */ if (chan->flags & CHN_ADLIB) { //Do this only if really an adlib chan. Important! OPL_NoteOff(nchan); OPL_Touch(nchan, 0); } GM_KeyOff(nchan); GM_Touch(nchan, 0); } if (NOTE_IS_CONTROL(note)) { instr = 0; } else if (NOTE_IS_NOTE(note)) { chan->new_note = note; // New Note Action ? (not when paused!!!) if (!porta) csf_check_nna(csf, nchan, instr, note, 0); } // Instrument Change ? if (instr) { song_sample_t *psmp = chan->ptr_sample; csf_instrument_change(csf, chan, instr, porta, 1); if (csf->samples[instr].flags & CHN_ADLIB) { OPL_Patch(nchan, csf->samples[instr].adlib_bytes); } if((csf->flags & SONG_INSTRUMENTMODE) && csf->instruments[instr]) GM_DPatch(nchan, csf->instruments[instr]->midi_program, csf->instruments[instr]->midi_bank, csf->instruments[instr]->midi_channel_mask); chan->new_instrument = 0; // Special IT case: portamento+note causes sample change -> ignore portamento if (psmp != chan->ptr_sample && NOTE_IS_NOTE(note)) { porta = 0; } } // New Note ? if (note != NOTE_NONE) { if (!instr && chan->new_instrument && NOTE_IS_NOTE(note)) { csf_instrument_change(csf, chan, chan->new_instrument, porta, 0); if ((csf->flags & SONG_INSTRUMENTMODE) && chan->new_instrument < MAX_INSTRUMENTS && csf->instruments[chan->new_instrument]) { if (csf->samples[chan->new_instrument].flags & CHN_ADLIB) { OPL_Patch(nchan, csf->samples[chan->new_instrument].adlib_bytes); } GM_DPatch(nchan, csf->instruments[chan->new_instrument]->midi_program, csf->instruments[chan->new_instrument]->midi_bank, csf->instruments[chan->new_instrument]->midi_channel_mask); } chan->new_instrument = 0; } csf_note_change(csf, nchan, note, porta, 0, !instr); } } handle_effect(csf, nchan, cmd, param, porta, firsttick); handle_voleffect(csf, chan, volcmd, vol, firsttick, start_note); } } schismtracker-20180209/player/equalizer.c000066400000000000000000000130351323741476300203000ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sndfile.h" #include "cmixer.h" #include #define EQ_BANDWIDTH 2.0 #define EQ_ZERO 0.000001 typedef struct { float a0, a1, a2, b1, b2; float x1, x2, y1, y2; float gain, center_frequency; int enabled; } eq_band; //static REAL f2ic = (REAL)(1 << 28); //static REAL i2fc = (REAL)(1.0 / (1 << 28)); static eq_band eq[MAX_EQ_BANDS * 2] = { // Default: Flat EQ {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 120, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 600, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1200, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 120, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 600, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1200, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0}, }; static void eq_filter(eq_band *pbs, float *pbuffer, unsigned int count) { for (unsigned int i = 0; i < count; i++) { float x = pbuffer[i]; float y = pbs->a1 * 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 eq_mono(song_t *csf, int *buffer, unsigned int count) { mono_mix_to_float(buffer, csf->mix_buffer_float, count); for (unsigned int b = 0; b < MAX_EQ_BANDS; b++) { if (eq[b].enabled && eq[b].gain != 1.0f) eq_filter(&eq[b], csf->mix_buffer_float, count); } float_to_mono_mix(csf->mix_buffer_float, buffer, count); } // XXX: I rolled the two loops into one. Make sure this works. void eq_stereo(song_t *csf, int *buffer, unsigned int count) { stereo_mix_to_float(buffer, csf->mix_buffer_float, csf->mix_buffer_float + MIXBUFFERSIZE, count); for (unsigned int b = 0; b < MAX_EQ_BANDS; b++) { int br = b + MAX_EQ_BANDS; // Left band if (eq[b].enabled && eq[b].gain != 1.0f) eq_filter(&eq[b], csf->mix_buffer_float, count); // Right band if (eq[br].enabled && eq[br].gain != 1.0f) eq_filter(&eq[br], csf->mix_buffer_float + MIXBUFFERSIZE, count); } float_to_stereo_mix(csf->mix_buffer_float, csf->mix_buffer_float + MIXBUFFERSIZE, buffer, count); } void initialize_eq(int reset, float freq) { //float fMixingFreq = (REAL)mix_frequency; // Gain = 0.5 (-6dB) .. 2 (+6dB) for (unsigned int band = 0; band < MAX_EQ_BANDS * 2; band++) { float k, k2, r, f; float v0, v1; int b = reset; if (!eq[band].enabled) { eq[band].a0 = 0; eq[band].a1 = 0; eq[band].a2 = 0; eq[band].b1 = 0; eq[band].b2 = 0; eq[band].x1 = 0; eq[band].x2 = 0; eq[band].y1 = 0; eq[band].y2 = 0; continue; } f = eq[band].center_frequency / freq; if (f > 0.45f) eq[band].gain = 1; //if (f > 0.25) // f = 0.25; //k = tan(PI * f); k = f * 3.141592654f; k = k + k * f; //if (k > (float) 0.707) // k = (float) 0.707; k2 = k*k; v0 = eq[band].gain; v1 = 1; if (eq[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 != eq[band].a0) { eq[band].a0 = r; b = 1; } r = 2 * (k2 - 1) / (1 + v1 * k + k2); if (r != eq[band].a1) { eq[band].a1 = r; b = 1; } r = (1 - v0 * k + k2) / (1 + v1 * k + k2); if (r != eq[band].a2) { eq[band].a2 = r; b = 1; } r = -2 * (k2 - 1) / (1 + v1 * k + k2); if (r != eq[band].b1) { eq[band].b1 = r; b = 1; } r = -(1 - v1 * k + k2) / (1 + v1 * k + k2); if (r != eq[band].b2) { eq[band].b2 = r; b = 1; } if (b) { eq[band].x1 = 0; eq[band].x2 = 0; eq[band].y1 = 0; eq[band].y2 = 0; } } } void set_eq_gains(const unsigned int *gainbuff, unsigned int gains, const unsigned int *freqs, int reset, int mix_freq) { for (unsigned int i = 0; i < MAX_EQ_BANDS; i++) { float g, f = 0; if (i < gains) { unsigned int n = gainbuff[i]; //if (n > 32) // n = 32; g = 1.0 + (((double) n) / 64.0); if (freqs) f = (float)(int) freqs[i]; } else { g = 1; } eq[i].gain = eq[i + MAX_EQ_BANDS].gain = g; eq[i].center_frequency = eq[i + MAX_EQ_BANDS].center_frequency = f; /* don't enable bands outside... */ if (f > 20.0f && i < gains) { eq[i].enabled = eq[i + MAX_EQ_BANDS].enabled = 1; } else { eq[i].enabled = eq[i + MAX_EQ_BANDS].enabled = 0; } } initialize_eq(reset, mix_freq); } schismtracker-20180209/player/filters.c000066400000000000000000000123671323741476300177560ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sndfile.h" #include "cmixer.h" #include // LUT for 2 * damping factor static const float resonance_table[128] = { 1.0000000000000000f, 0.9786446094512940f, 0.9577452540397644f, 0.9372922182083130f, 0.9172759056091309f, 0.8976871371269226f, 0.8785166740417481f, 0.8597555756568909f, 0.8413951396942139f, 0.8234267830848694f, 0.8058421611785889f, 0.7886331081390381f, 0.7717915177345276f, 0.7553095817565918f, 0.7391796708106995f, 0.7233941555023193f, 0.7079457640647888f, 0.6928272843360901f, 0.6780316829681397f, 0.6635520458221436f, 0.6493816375732422f, 0.6355138421058655f, 0.6219421625137329f, 0.6086603403091431f, 0.5956621170043945f, 0.5829415321350098f, 0.5704925656318665f, 0.5583094954490662f, 0.5463865399360657f, 0.5347182154655457f, 0.5232990980148315f, 0.5121238231658936f, 0.5011872053146362f, 0.4904841780662537f, 0.4800096750259399f, 0.4697588682174683f, 0.4597269892692566f, 0.4499093294143677f, 0.4403013288974762f, 0.4308985173702240f, 0.4216965138912201f, 0.4126909971237183f, 0.4038778245449066f, 0.3952528536319733f, 0.3868120610713959f, 0.3785515129566193f, 0.3704673945903778f, 0.3625559210777283f, 0.3548133969306946f, 0.3472362160682678f, 0.3398208320140839f, 0.3325638175010681f, 0.3254617750644684f, 0.3185114264488220f, 0.3117094635963440f, 0.3050527870655060f, 0.2985382676124573f, 0.2921628654003143f, 0.2859236001968384f, 0.2798175811767578f, 0.2738419771194458f, 0.2679939568042755f, 0.2622708380222321f, 0.2566699385643005f, 0.2511886358261108f, 0.2458244115114212f, 0.2405747324228287f, 0.2354371547698975f, 0.2304092943668366f, 0.2254888117313385f, 0.2206734120845795f, 0.2159608304500580f, 0.2113489061594009f, 0.2068354636430740f, 0.2024184018373489f, 0.1980956792831421f, 0.1938652694225311f, 0.1897251904010773f, 0.1856735348701477f, 0.1817083954811096f, 0.1778279393911362f, 0.1740303486585617f, 0.1703138649463654f, 0.1666767448186874f, 0.1631172895431519f, 0.1596338599920273f, 0.1562248021364212f, 0.1528885662555695f, 0.1496235728263855f, 0.1464282870292664f, 0.1433012634515762f, 0.1402409970760346f, 0.1372461020946503f, 0.1343151479959488f, 0.1314467936754227f, 0.1286396980285645f, 0.1258925348520279f, 0.1232040524482727f, 0.1205729842185974f, 0.1179980933666229f, 0.1154781952500343f, 0.1130121126770973f, 0.1105986908078194f, 0.1082368120551109f, 0.1059253737330437f, 0.1036632955074310f, 0.1014495193958283f, 0.0992830246686935f, 0.0971627980470657f, 0.0950878411531448f, 0.0930572077631950f, 0.0910699293017387f, 0.0891250967979431f, 0.0872217938303947f, 0.0853591337800026f, 0.0835362523794174f, 0.0817523002624512f, 0.0800064504146576f, 0.0782978758215904f, 0.0766257941722870f, 0.0749894231557846f, 0.0733879879117012f, 0.0718207582831383f, 0.0702869966626167f, 0.0687859877943993f, 0.0673170387744904f, 0.0658794566988945f, 0.0644725710153580f, }; // Simple 2-poles resonant filter // // XXX freq WAS unused but is now mix_frequency! // #define FREQ_PARAM_MULT (128.0 / (24.0 * 256.0)) void setup_channel_filter(song_voice_t *chan, int reset, int flt_modifier, int freq) { int cutoff = chan->cutoff; int resonance = chan->resonance; float frequency, r, d, e, fg, fb0, fb1; cutoff = cutoff * (flt_modifier + 256) / 256; if (cutoff > 255) cutoff = 255; if (resonance > 255) resonance = 255; // TODO: The enabling/disabling of channel filter is a bit more complex. // More info in snd_flt.cpp in OpenMPT and filter-reset.it, filter-reset-carry.it // and filter-nna.it from https://wiki.openmpt.org/Development:_Test_Cases/IT // Should be 255, but Zxx cutoff is limited to 127, so... if (cutoff < 254) chan->flags |= CHN_FILTER; else cutoff = 255; // 2 ^ (i / 24 * 256) frequency = 110.0 * powf(2.0, (float) cutoff * FREQ_PARAM_MULT + 0.25); if (frequency > freq / 2.0) frequency = freq / 2.0; r = freq / (2.0 * M_PI * frequency); d = resonance_table[resonance] * r + resonance_table[resonance] - 1.0; e = r * r; fg = 1.0 / (1.0 + d + e); fb0 = (d + e + e) / (1.0 + d + e); fb1 = -e / (1.0 + d + e); chan->filter_a0 = (int32_t)(fg * (1 << FILTERPRECISION)); chan->filter_b0 = (int32_t)(fb0 * (1 << FILTERPRECISION)); chan->filter_b1 = (int32_t)(fb1 * (1 << FILTERPRECISION)); if (reset) { chan->filter_y1 = chan->filter_y2 = 0; chan->filter_y3 = chan->filter_y4 = 0; } } schismtracker-20180209/player/fmopl2.c000066400000000000000000001721271323741476300175060ustar00rootroot00000000000000// license:GPL-2.0+ // copyright-holders:Jarek Burczynski,Tatsuyuki Satoh /* ** ** File: fmopl.c - software implementation of FM sound generator ** types OPL and OPL2 ** ** Copyright Jarek Burczynski (bujar at mame dot net) ** Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development ** ** Version 0.72 ** Revision History: 04-08-2003 Jarek Burczynski: - removed BFRDY hack. BFRDY is busy flag, and it should be 0 only when the chip handles memory read/write or during the adpcm synthesis when the chip requests another byte of ADPCM data. 24-07-2003 Jarek Burczynski: - added a small hack for Y8950 status BFRDY flag (bit 3 should be set after some (unknown) delay). Right now it's always set. 14-06-2003 Jarek Burczynski: - implemented all of the status register flags in Y8950 emulation - renamed y8950_set_delta_t_memory() parameters from _rom_ to _mem_ since they can be either RAM or ROM 08-10-2002 Jarek Burczynski (thanks to Dox for the YM3526 chip) - corrected ym3526_read() to always set bit 2 and bit 1 to HIGH state - identical to ym3812_read (verified on real YM3526) 04-28-2002 Jarek Burczynski: - binary exact Envelope Generator (verified on real YM3812); compared to YM2151: the EG clock is equal to internal_clock, rates are 2 times slower and volume resolution is one bit less - modified interface functions (they no longer return pointer - that's internal to the emulator now): - new wrapper functions for OPLCreate: ym3526_init(), ym3812_init() and y8950_init() - corrected 'off by one' error in feedback calculations (when feedback is off) - enabled waveform usage (credit goes to Vlad Romascanu and zazzal22) - speeded up noise generator calculations (Nicola Salmoria) 03-24-2002 Jarek Burczynski (thanks to Dox for the YM3812 chip) Complete rewrite (all verified on real YM3812): - corrected sin_tab and tl_tab data - corrected operator output calculations - corrected waveform_select_enable register; simply: ignore all writes to waveform_select register when waveform_select_enable == 0 and do not change the waveform previously selected. - corrected KSR handling - corrected Envelope Generator: attack shape, Sustain mode and Percussive/Non-percussive modes handling - Envelope Generator rates are two times slower now - LFO amplitude (tremolo) and phase modulation (vibrato) - rhythm sounds phase generation - white noise generator (big thanks to Olivier Galibert for mentioning Berlekamp-Massey algorithm) - corrected key on/off handling (the 'key' signal is ORed from three sources: FM, rhythm and CSM) - funky details (like ignoring output of operator 1 in BD rhythm sound when connect == 1) 12-28-2001 Acho A. Tang - reflected Delta-T EOS status on Y8950 status port. - fixed subscription range of attack/decay tables To do: add delay before key off in CSM mode (see CSMKeyControll) verify volume of the FM part on the Y8950 */ #include #include #include #include #include "fmopl2.h" #include "log.h" /* output final shift */ #if (OPL_SAMPLE_BITS==16) #define FINAL_SH (0) #define MAXOUT (+32767) #define MINOUT (-32768) #else #define FINAL_SH (8) #define MAXOUT (+127) #define MINOUT (-128) #endif #define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ #define EG_SH 16 /* 16.16 fixed point (EG timing) */ #define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ #define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ #define FREQ_MASK ((1<>KSR */ UINT8 mul; /* multiple: mul_tab[ML] */ /* Phase Generator */ UINT32 Cnt; /* frequency counter */ UINT32 Incr; /* frequency counter step */ UINT8 FB; /* feedback shift value */ INT32 *connect1; /* slot1 output pointer */ INT32 op1_out[2]; /* slot1 output for feedback */ UINT8 CON; /* connection (algorithm) type */ /* Envelope Generator */ UINT8 eg_type; /* percussive/non-percussive mode */ UINT8 state; /* phase type */ UINT32 TL; /* total level: TL << 2 */ INT32 TLL; /* adjusted now TL */ INT32 volume; /* envelope counter */ UINT32 sl; /* sustain level: sl_tab[SL] */ UINT8 eg_sh_ar; /* (attack state) */ UINT8 eg_sel_ar; /* (attack state) */ UINT8 eg_sh_dr; /* (decay state) */ UINT8 eg_sel_dr; /* (decay state) */ UINT8 eg_sh_rr; /* (release state) */ UINT8 eg_sel_rr; /* (release state) */ UINT32 key; /* 0 = KEY OFF, >0 = KEY ON */ /* LFO */ UINT32 AMmask; /* LFO Amplitude Modulation enable mask */ UINT8 vib; /* LFO Phase Modulation enable flag (active high)*/ /* waveform select */ UINT16 wavetable; } OPL_SLOT; typedef struct { OPL_SLOT SLOT[2]; /* phase generator state */ UINT32 block_fnum; /* block+fnum */ UINT32 fc; /* Freq. Increment base */ UINT32 ksl_base; /* KeyScaleLevel Base step */ UINT8 kcode; /* key code (for key scaling) */ } OPL_CH; /* OPL state */ typedef struct { /* FM channel slots */ OPL_CH P_CH[9]; /* OPL/OPL2 chips have 9 channels*/ UINT32 eg_cnt; /* global envelope generator counter */ UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/72 */ UINT32 eg_timer_add; /* step of eg_timer */ UINT32 eg_timer_overflow; /* envelope generator timer overlfows every 1 sample (on real chip) */ UINT8 rhythm; /* Rhythm mode */ UINT32 fn_tab[1024]; /* fnumber->increment counter */ /* LFO */ UINT32 LFO_AM; INT32 LFO_PM; UINT8 lfo_am_depth; UINT8 lfo_pm_depth_range; UINT32 lfo_am_cnt; UINT32 lfo_am_inc; UINT32 lfo_pm_cnt; UINT32 lfo_pm_inc; UINT32 noise_rng; /* 23 bit noise shift register */ UINT32 noise_p; /* current noise 'phase' */ UINT32 noise_f; /* current noise period */ UINT8 wavesel; /* waveform select enable flag */ UINT32 T[2]; /* timer counters */ UINT8 st[2]; /* timer enable */ #if BUILD_Y8950 /* Delta-T ADPCM unit (Y8950) */ YM_DELTAT *deltat; /* Keyboard and I/O ports interface */ UINT8 portDirection; UINT8 portLatch; OPL_PORTHANDLER_R porthandler_r; OPL_PORTHANDLER_W porthandler_w; void * port_param; OPL_PORTHANDLER_R keyboardhandler_r; OPL_PORTHANDLER_W keyboardhandler_w; void * keyboard_param; #endif /* external event callback handlers */ OPL_TIMERHANDLER timer_handler; /* TIMER handler */ void *TimerParam; /* TIMER parameter */ OPL_IRQHANDLER IRQHandler; /* IRQ handler */ void *IRQParam; /* IRQ parameter */ OPL_UPDATEHANDLER UpdateHandler;/* stream update handler */ void *UpdateParam; /* stream update parameter */ UINT8 type; /* chip type */ UINT8 address; /* address register */ UINT8 status; /* status flag */ UINT8 statusmask; /* status mask */ UINT8 mode; /* Reg.08 : CSM,notesel,etc. */ UINT32 clock; /* master clock (Hz) */ UINT32 rate; /* sampling rate (Hz) */ double freqbase; /* frequency base */ double TimerBase; /* Timer base time (==sampling time)*/ signed int phase_modulation; /* phase modulation input (SLOT 2) */ signed int output[1]; #if BUILD_Y8950 INT32 output_deltat[4]; /* for Y8950 DELTA-T, chip is mono, that 4 here is just for safety */ #endif } FM_OPL; /* mapping of register number (offset) to slot number used by the emulator */ static const int slot_array[32]= { 0, 2, 4, 1, 3, 5,-1,-1, 6, 8,10, 7, 9,11,-1,-1, 12,14,16,13,15,17,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1 }; /* key scale level */ /* table is 3dB/octave , DV converts this into 6dB/octave */ /* 0.1875 is bit 0 weight of the envelope counter (volume) expressed in the 'decibel' scale */ #define KSC(x) ((UINT32)((x)/(0.1875/2.0))) static const UINT32 ksl_tab[8*16]= { /* OCT 0 */ KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), /* OCT 1 */ KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.750), KSC(1.125), KSC(1.500), KSC(1.875), KSC(2.250), KSC(2.625), KSC(3.000), /* OCT 2 */ KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(1.125), KSC(1.875), KSC(2.625), KSC(3.000), KSC(3.750), KSC(4.125), KSC(4.500), KSC(4.875), KSC(5.250), KSC(5.625), KSC(6.000), /* OCT 3 */ KSC(0.000), KSC(0.000), KSC(0.000), KSC(1.875), KSC(3.000), KSC(4.125), KSC(4.875), KSC(5.625), KSC(6.000), KSC(6.750), KSC(7.125), KSC(7.500), KSC(7.875), KSC(8.250), KSC(8.625), KSC(9.000), /* OCT 4 */ KSC(0.000), KSC(0.000), KSC(3.000), KSC(4.875), KSC(6.000), KSC(7.125), KSC(7.875), KSC(8.625), KSC(9.000), KSC(9.750),KSC(10.125),KSC(10.500), KSC(10.875),KSC(11.250),KSC(11.625),KSC(12.000), /* OCT 5 */ KSC(0.000), KSC(3.000), KSC(6.000), KSC(7.875), KSC(9.000),KSC(10.125),KSC(10.875),KSC(11.625), KSC(12.000),KSC(12.750),KSC(13.125),KSC(13.500), KSC(13.875),KSC(14.250),KSC(14.625),KSC(15.000), /* OCT 6 */ KSC(0.000), KSC(6.000), KSC(9.000),KSC(10.875), KSC(12.000),KSC(13.125),KSC(13.875),KSC(14.625), KSC(15.000),KSC(15.750),KSC(16.125),KSC(16.500), KSC(16.875),KSC(17.250),KSC(17.625),KSC(18.000), /* OCT 7 */ KSC(0.000), KSC(9.000),KSC(12.000),KSC(13.875), KSC(15.000),KSC(16.125),KSC(16.875),KSC(17.625), KSC(18.000),KSC(18.750),KSC(19.125),KSC(19.500), KSC(19.875),KSC(20.250),KSC(20.625),KSC(21.000) }; #undef KSC /* 0 / 3.0 / 1.5 / 6.0 dB/OCT */ static const UINT32 ksl_shift[4] = { 31, 1, 2, 0 }; /* sustain level table (3dB per step) */ /* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ #define SC(db) (UINT32) ( db * (2.0/ENV_STEP) ) static const UINT32 sl_tab[16]={ SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) }; #undef SC #define RATE_STEPS (8) static const unsigned char eg_inc[15*RATE_STEPS]={ /*cycle:0 1 2 3 4 5 6 7*/ /* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..12 0 (increment by 0 or 1) */ /* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..12 1 */ /* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..12 2 */ /* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..12 3 */ /* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 13 0 (increment by 1) */ /* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 13 1 */ /* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 13 2 */ /* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 13 3 */ /* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 14 0 (increment by 2) */ /* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 14 1 */ /*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 14 2 */ /*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 14 3 */ /*12 */ 4,4, 4,4, 4,4, 4,4, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 4) */ /*13 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 2, 15 3 for attack */ /*14 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ }; #define O(a) (a*RATE_STEPS) /*note that there is no O(13) in this table - it's directly in the code */ /* Envelope Generator rates (16 + 64 rates + 16 RKS) */ static const unsigned char eg_rate_select[16+64+16]={ /* 16 infinite time rates */ O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), /* rates 00-12 */ O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), /* rate 13 */ O( 4),O( 5),O( 6),O( 7), /* rate 14 */ O( 8),O( 9),O(10),O(11), /* rate 15 */ O(12),O(12),O(12),O(12), /* 16 dummy rates (same as 15 3) */ O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), }; #undef O /*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ /*shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 */ /*mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 */ #define O(a) (a*1) /* Envelope Generator counter shifts (16 + 64 rates + 16 RKS) */ static const unsigned char eg_rate_shift[16+64+16]={ /* 16 infinite time rates */ O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), /* rates 00-12 */ O(12),O(12),O(12),O(12), O(11),O(11),O(11),O(11), O(10),O(10),O(10),O(10), O( 9),O( 9),O( 9),O( 9), O( 8),O( 8),O( 8),O( 8), O( 7),O( 7),O( 7),O( 7), O( 6),O( 6),O( 6),O( 6), O( 5),O( 5),O( 5),O( 5), O( 4),O( 4),O( 4),O( 4), O( 3),O( 3),O( 3),O( 3), O( 2),O( 2),O( 2),O( 2), O( 1),O( 1),O( 1),O( 1), O( 0),O( 0),O( 0),O( 0), /* rate 13 */ O( 0),O( 0),O( 0),O( 0), /* rate 14 */ O( 0),O( 0),O( 0),O( 0), /* rate 15 */ O( 0),O( 0),O( 0),O( 0), /* 16 dummy rates (same as 15 3) */ O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), }; #undef O /* multiple table */ #define ML 2 static const UINT8 mul_tab[16]= { /* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,10,12,12,15,15 */ ML/2, 1*ML, 2*ML, 3*ML, 4*ML, 5*ML, 6*ML, 7*ML, 8*ML, 9*ML,10*ML,10*ML,12*ML,12*ML,15*ML,15*ML }; #undef ML /* TL_TAB_LEN is calculated as: * 12 - sinus amplitude bits (Y axis) * 2 - sinus sign bit (Y axis) * TL_RES_LEN - sinus resolution (X axis) */ #define TL_TAB_LEN (12*2*TL_RES_LEN) static signed int tl_tab[TL_TAB_LEN]; #define ENV_QUIET (TL_TAB_LEN>>4) /* sin waveform table in 'decibel' scale */ /* four waveforms on OPL2 type chips */ static unsigned int sin_tab[SIN_LEN * 4]; /* LFO Amplitude Modulation table (verified on real YM3812) 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples Length: 210 elements. Each of the elements has to be repeated exactly 64 times (on 64 consecutive samples). The whole table takes: 64 * 210 = 13440 samples. When AM = 1 data is used directly When AM = 0 data is divided by 4 before being used (losing precision is important) */ #define LFO_AM_TAB_ELEMENTS 210 static const UINT8 lfo_am_table[LFO_AM_TAB_ELEMENTS] = { 0,0,0,0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8, 9,9,9,9, 10,10,10,10, 11,11,11,11, 12,12,12,12, 13,13,13,13, 14,14,14,14, 15,15,15,15, 16,16,16,16, 17,17,17,17, 18,18,18,18, 19,19,19,19, 20,20,20,20, 21,21,21,21, 22,22,22,22, 23,23,23,23, 24,24,24,24, 25,25,25,25, 26,26,26, 25,25,25,25, 24,24,24,24, 23,23,23,23, 22,22,22,22, 21,21,21,21, 20,20,20,20, 19,19,19,19, 18,18,18,18, 17,17,17,17, 16,16,16,16, 15,15,15,15, 14,14,14,14, 13,13,13,13, 12,12,12,12, 11,11,11,11, 10,10,10,10, 9,9,9,9, 8,8,8,8, 7,7,7,7, 6,6,6,6, 5,5,5,5, 4,4,4,4, 3,3,3,3, 2,2,2,2, 1,1,1,1 }; /* LFO Phase Modulation table (verified on real YM3812) */ static const INT8 lfo_pm_table[8*8*2] = { /* FNUM2/FNUM = 00 0xxxxxxx (0x0000) */ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 00 1xxxxxxx (0x0080) */ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 01 0xxxxxxx (0x0100) */ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 01 1xxxxxxx (0x0180) */ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 10 0xxxxxxx (0x0200) */ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ 4, 2, 0,-2,-4,-2, 0, 2, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 10 1xxxxxxx (0x0280) */ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ 5, 2, 0,-2,-5,-2, 0, 2, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 11 0xxxxxxx (0x0300) */ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ 6, 3, 0,-3,-6,-3, 0, 3, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 11 1xxxxxxx (0x0380) */ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ 7, 3, 0,-3,-7,-3, 0, 3 /*LFO PM depth = 1*/ }; /* lock level of common table */ static int num_lock = 0; #define SLOT7_1 (&OPL->P_CH[7].SLOT[SLOT1]) #define SLOT7_2 (&OPL->P_CH[7].SLOT[SLOT2]) #define SLOT8_1 (&OPL->P_CH[8].SLOT[SLOT1]) #define SLOT8_2 (&OPL->P_CH[8].SLOT[SLOT2]) INLINE int limit( int val, int max, int min ) { if ( val > max ) val = max; else if ( val < min ) val = min; return val; } /* status set and IRQ handling */ INLINE void OPL_STATUS_SET(FM_OPL *OPL,int flag) { /* set status flag */ OPL->status |= flag; if(!(OPL->status & 0x80)) { if(OPL->status & OPL->statusmask) { /* IRQ on */ OPL->status |= 0x80; /* callback user interrupt handler (IRQ is OFF to ON) */ if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1); } } } /* status reset and IRQ handling */ INLINE void OPL_STATUS_RESET(FM_OPL *OPL,int flag) { /* reset status flag */ OPL->status &=~flag; if((OPL->status & 0x80)) { if (!(OPL->status & OPL->statusmask) ) { OPL->status &= 0x7f; /* callback user interrupt handler (IRQ is ON to OFF) */ if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); } } } /* IRQ mask set */ INLINE void OPL_STATUSMASK_SET(FM_OPL *OPL,int flag) { OPL->statusmask = flag; /* IRQ handling check */ OPL_STATUS_SET(OPL,0); OPL_STATUS_RESET(OPL,0); } /* advance LFO to next sample */ INLINE void advance_lfo(FM_OPL *OPL) { UINT8 tmp; /* LFO */ OPL->lfo_am_cnt += OPL->lfo_am_inc; if (OPL->lfo_am_cnt >= ((UINT32)LFO_AM_TAB_ELEMENTS<lfo_am_cnt -= ((UINT32)LFO_AM_TAB_ELEMENTS<lfo_am_cnt >> LFO_SH ]; if (OPL->lfo_am_depth) OPL->LFO_AM = tmp; else OPL->LFO_AM = tmp>>2; OPL->lfo_pm_cnt += OPL->lfo_pm_inc; OPL->LFO_PM = ((OPL->lfo_pm_cnt>>LFO_SH) & 7) | OPL->lfo_pm_depth_range; } /* advance to next sample */ INLINE void advance(FM_OPL *OPL) { OPL_CH *CH; OPL_SLOT *op; int i; OPL->eg_timer += OPL->eg_timer_add; while (OPL->eg_timer >= OPL->eg_timer_overflow) { OPL->eg_timer -= OPL->eg_timer_overflow; OPL->eg_cnt++; for (i=0; i<9*2; i++) { CH = &OPL->P_CH[i/2]; op = &CH->SLOT[i&1]; /* Envelope Generator */ switch(op->state) { case EG_ATT: /* attack phase */ if ( !(OPL->eg_cnt & ((1<eg_sh_ar)-1) ) ) { op->volume += (~op->volume * (eg_inc[op->eg_sel_ar + ((OPL->eg_cnt>>op->eg_sh_ar)&7)]) ) >>3; if (op->volume <= MIN_ATT_INDEX) { op->volume = MIN_ATT_INDEX; op->state = EG_DEC; } } break; case EG_DEC: /* decay phase */ if ( !(OPL->eg_cnt & ((1<eg_sh_dr)-1) ) ) { op->volume += eg_inc[op->eg_sel_dr + ((OPL->eg_cnt>>op->eg_sh_dr)&7)]; if ( (UINT32)op->volume >= op->sl ) op->state = EG_SUS; } break; case EG_SUS: /* sustain phase */ /* this is important behaviour: one can change percusive/non-percussive modes on the fly and the chip will remain in sustain phase - verified on real YM3812 */ if(op->eg_type) /* non-percussive mode */ { /* do nothing */ } else /* percussive mode */ { /* during sustain phase chip adds Release Rate (in percussive mode) */ if ( !(OPL->eg_cnt & ((1<eg_sh_rr)-1) ) ) { op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; if ( op->volume >= MAX_ATT_INDEX ) op->volume = MAX_ATT_INDEX; } /* else do nothing in sustain phase */ } break; case EG_REL: /* release phase */ if ( !(OPL->eg_cnt & ((1<eg_sh_rr)-1) ) ) { op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; if ( op->volume >= MAX_ATT_INDEX ) { op->volume = MAX_ATT_INDEX; op->state = EG_OFF; } } break; default: break; } } } for (i=0; i<9*2; i++) { CH = &OPL->P_CH[i/2]; op = &CH->SLOT[i&1]; /* Phase Generator */ if(op->vib) { UINT8 block; UINT32 block_fnum = CH->block_fnum; unsigned int fnum_lfo = (block_fnum&0x0380) >> 7; signed int lfo_fn_table_index_offset = lfo_pm_table[OPL->LFO_PM + 16*fnum_lfo ]; if (lfo_fn_table_index_offset) /* LFO phase modulation active */ { block_fnum += lfo_fn_table_index_offset; block = (block_fnum&0x1c00) >> 10; op->Cnt += (OPL->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul; } else /* LFO phase modulation = zero */ { op->Cnt += op->Incr; } } else /* LFO phase modulation disabled for this operator */ { op->Cnt += op->Incr; } } /* The Noise Generator of the YM3812 is 23-bit shift register. * Period is equal to 2^23-2 samples. * Register works at sampling frequency of the chip, so output * can change on every sample. * * Output of the register and input to the bit 22 is: * bit0 XOR bit14 XOR bit15 XOR bit22 * * Simply use bit 22 as the noise output. */ OPL->noise_p += OPL->noise_f; i = OPL->noise_p >> FREQ_SH; /* number of events (shifts of the shift register) */ OPL->noise_p &= FREQ_MASK; while (i) { /* UINT32 j; j = ( (OPL->noise_rng) ^ (OPL->noise_rng>>14) ^ (OPL->noise_rng>>15) ^ (OPL->noise_rng>>22) ) & 1; OPL->noise_rng = (j<<22) | (OPL->noise_rng>>1); */ /* Instead of doing all the logic operations above, we use a trick here (and use bit 0 as the noise output). The difference is only that the noise bit changes one step ahead. This doesn't matter since we don't know what is real state of the noise_rng after the reset. */ if (OPL->noise_rng & 1) OPL->noise_rng ^= 0x800302; OPL->noise_rng >>= 1; i--; } } INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) { UINT32 p; p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + (pm << 16))) >> FREQ_SH) & SIN_MASK)]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) { UINT32 p; p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + pm)) >> FREQ_SH) & SIN_MASK)]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } #define volume_calc(OP) ((OP)->TLL + ((UINT32)(OP)->volume) + (OPL->LFO_AM & (OP)->AMmask)) /* calculate output */ INLINE void OPL_CALC_CH( FM_OPL *OPL, OPL_CH *CH ) { OPL_SLOT *SLOT; unsigned int env; signed int out; OPL->phase_modulation = 0; /* SLOT 1 */ SLOT = &CH->SLOT[SLOT1]; env = volume_calc(SLOT); out = SLOT->op1_out[0] + SLOT->op1_out[1]; SLOT->op1_out[0] = SLOT->op1_out[1]; *SLOT->connect1 += SLOT->op1_out[0]; SLOT->op1_out[1] = 0; if( env < ENV_QUIET ) { if (!SLOT->FB) out = 0; SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); } /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable); } /* operators used in the rhythm sounds generation process: Envelope Generator: channel operator register number Bass High Snare Tom Top / slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal 6 / 0 12 50 70 90 f0 + 6 / 1 15 53 73 93 f3 + 7 / 0 13 51 71 91 f1 + 7 / 1 16 54 74 94 f4 + 8 / 0 14 52 72 92 f2 + 8 / 1 17 55 75 95 f5 + Phase Generator: channel operator register number Bass High Snare Tom Top / slot number MULTIPLE Drum Hat Drum Tom Cymbal 6 / 0 12 30 + 6 / 1 15 33 + 7 / 0 13 31 + + + 7 / 1 16 34 ----- n o t u s e d ----- 8 / 0 14 32 + 8 / 1 17 35 + + channel operator register number Bass High Snare Tom Top number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal 6 12,15 B6 A6 + 7 13,16 B7 A7 + + + 8 14,17 B8 A8 + + + */ /* calculate rhythm */ INLINE void OPL_CALC_RH( FM_OPL *OPL, OPL_CH *CH, unsigned int noise ) { OPL_SLOT *SLOT; signed int out; unsigned int env; /* Bass Drum (verified on real YM3812): - depends on the channel 6 'connect' register: when connect = 0 it works the same as in normal (non-rhythm) mode (op1->op2->out) when connect = 1 _only_ operator 2 is present on output (op2->out), operator 1 is ignored - output sample always is multiplied by 2 */ OPL->phase_modulation = 0; /* SLOT 1 */ SLOT = &CH[6].SLOT[SLOT1]; env = volume_calc(SLOT); out = SLOT->op1_out[0] + SLOT->op1_out[1]; SLOT->op1_out[0] = SLOT->op1_out[1]; if (!SLOT->CON) OPL->phase_modulation = SLOT->op1_out[0]; /* else ignore output of operator 1 */ SLOT->op1_out[1] = 0; if( env < ENV_QUIET ) { if (!SLOT->FB) out = 0; SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); } /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable) * 2; /* Phase generation is based on: */ /* HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */ /* SD (16) channel 7->slot 1 */ /* TOM (14) channel 8->slot 1 */ /* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */ /* Envelope generation based on: */ /* HH channel 7->slot1 */ /* SD channel 7->slot2 */ /* TOM channel 8->slot1 */ /* TOP channel 8->slot2 */ /* The following formulas can be well optimized. I leave them in direct form for now (in case I've missed something). */ /* High Hat (verified on real YM3812) */ env = volume_calc(SLOT7_1); if( env < ENV_QUIET ) { /* high hat phase generation: phase = d0 or 234 (based on frequency only) phase = 34 or 2d0 (based on noise) */ /* base frequency derived from operator 1 in channel 7 */ unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; unsigned char res1 = (bit2 ^ bit7) | bit3; /* when res1 = 0 phase = 0x000 | 0xd0; */ /* when res1 = 1 phase = 0x200 | (0xd0>>2); */ UINT32 phase = res1 ? (0x200|(0xd0>>2)) : 0xd0; /* enable gate based on frequency of operator 2 in channel 8 */ unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; unsigned char res2 = (bit3e ^ bit5e); /* when res2 = 0 pass the phase from calculation above (res1); */ /* when res2 = 1 phase = 0x200 | (0xd0>>2); */ if (res2) phase = (0x200|(0xd0>>2)); /* when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 */ /* when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change */ if (phase&0x200) { if (noise) phase = 0x200|0xd0; } else /* when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 */ /* when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change */ { if (noise) phase = 0xd0>>2; } OPL->output[0] += op_calc(phase<wavetable) * 2; } /* Snare Drum (verified on real YM3812) */ env = volume_calc(SLOT7_2); if( env < ENV_QUIET ) { /* base frequency derived from operator 1 in channel 7 */ unsigned char bit8 = ((SLOT7_1->Cnt>>FREQ_SH)>>8)&1; /* when bit8 = 0 phase = 0x100; */ /* when bit8 = 1 phase = 0x200; */ UINT32 phase = bit8 ? 0x200 : 0x100; /* Noise bit XOR'es phase by 0x100 */ /* when noisebit = 0 pass the phase from calculation above */ /* when noisebit = 1 phase ^= 0x100; */ /* in other words: phase ^= (noisebit<<8); */ if (noise) phase ^= 0x100; OPL->output[0] += op_calc(phase<wavetable) * 2; } /* Tom Tom (verified on real YM3812) */ env = volume_calc(SLOT8_1); if( env < ENV_QUIET ) OPL->output[0] += op_calc(SLOT8_1->Cnt, env, 0, SLOT8_1->wavetable) * 2; /* Top Cymbal (verified on real YM3812) */ env = volume_calc(SLOT8_2); if( env < ENV_QUIET ) { /* base frequency derived from operator 1 in channel 7 */ unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; unsigned char res1 = (bit2 ^ bit7) | bit3; /* when res1 = 0 phase = 0x000 | 0x100; */ /* when res1 = 1 phase = 0x200 | 0x100; */ UINT32 phase = res1 ? 0x300 : 0x100; /* enable gate based on frequency of operator 2 in channel 8 */ unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; unsigned char res2 = (bit3e ^ bit5e); /* when res2 = 0 pass the phase from calculation above (res1); */ /* when res2 = 1 phase = 0x200 | 0x100; */ if (res2) phase = 0x300; OPL->output[0] += op_calc(phase<wavetable) * 2; } } /* generic table initialize */ static int init_tables(void) { signed int i,x; signed int n; double o,m; for (x=0; x>= 4; /* 12 bits here */ if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; /* 11 bits here (rounded) */ n <<= 1; /* 12 bits here (as in real chip) */ tl_tab[ x*2 + 0 ] = n; tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; for (i=1; i<12; i++) { tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; } #if 0 logerror("tl %04i", x*2); for (i=0; i<12; i++) logerror(", [%02i] %5i", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ] ); logerror("\n"); #endif } /*logerror("FMOPL.C: TL_TAB_LEN = %i elements (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ for (i=0; i0.0) o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ else o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ o = o / (ENV_STEP/4); n = (int)(2.0*o); if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); /*logerror("FMOPL.C: sin [%4i (hex=%03x)]= %4i (tl_tab value=%5i)\n", i, i, sin_tab[i], tl_tab[sin_tab[i]] );*/ } for (i=0; i>1) ]; /* waveform 3: _ _ _ _ */ /* / |_/ |_/ |_/ |_*/ /* abs(output only first quarter of the sinus waveform) */ if (i & (1<<(SIN_BITS-2)) ) sin_tab[3*SIN_LEN+i] = TL_TAB_LEN; else sin_tab[3*SIN_LEN+i] = sin_tab[i & (SIN_MASK>>2)]; /*logerror("FMOPL.C: sin1[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[1*SIN_LEN+i], tl_tab[sin_tab[1*SIN_LEN+i]] ); logerror("FMOPL.C: sin2[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[2*SIN_LEN+i], tl_tab[sin_tab[2*SIN_LEN+i]] ); logerror("FMOPL.C: sin3[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[3*SIN_LEN+i], tl_tab[sin_tab[3*SIN_LEN+i]] );*/ } /*logerror("FMOPL.C: ENV_QUIET= %08x (dec*8=%i)\n", ENV_QUIET, ENV_QUIET*8 );*/ return 1; } static void OPLCloseTable( void ) { } static void OPL_initalize(FM_OPL *OPL) { int i; /* frequency base */ OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / 72.0) / OPL->rate : 0; #if 0 OPL->rate = (double)OPL->clock / 72.0; OPL->freqbase = 1.0; #endif /*logerror("freqbase=%f\n", OPL->freqbase);*/ /* Timer base time */ OPL->TimerBase = 72.0 / (double)OPL->clock; /* make fnumber -> increment counter table */ for( i=0 ; i < 1024 ; i++ ) { /* opn phase increment counter = 20bit */ /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ OPL->fn_tab[i] = (UINT32)( (double)i * 64 * OPL->freqbase * (1<<(FREQ_SH-10)) ); #if 0 logerror("FMOPL.C: fn_tab[%4i] = %08x (dec=%8i)\n", i, OPL->fn_tab[i]>>6, OPL->fn_tab[i]>>6 ); #endif } #if 0 for( i=0 ; i < 16 ; i++ ) { logerror("FMOPL.C: sl_tab[%i] = %08x\n", i, sl_tab[i] ); } for( i=0 ; i < 8 ; i++ ) { int j; logerror("FMOPL.C: ksl_tab[oct=%2i] =",i); for (j=0; j<16; j++) { logerror("%08x ", ksl_tab[i*16+j] ); } logerror("\n"); } #endif /* Amplitude modulation: 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples */ /* One entry from LFO_AM_TABLE lasts for 64 samples */ OPL->lfo_am_inc = (UINT32)((1.0 / 64.0 ) * (1<freqbase); /* Vibrato: 8 output levels (triangle waveform); 1 level takes 1024 samples */ OPL->lfo_pm_inc = (UINT32)((1.0 / 1024.0) * (1<freqbase); /*logerror ("OPL->lfo_am_inc = %8x ; OPL->lfo_pm_inc = %8x\n", OPL->lfo_am_inc, OPL->lfo_pm_inc);*/ /* Noise generator: a step takes 1 sample */ OPL->noise_f = (UINT32)((1.0 / 1.0) * (1<freqbase); OPL->eg_timer_add = (UINT32)((1<freqbase); OPL->eg_timer_overflow = ( 1 ) * (1<eg_timer_add, OPL->eg_timer_overflow);*/ } INLINE void FM_KEYON(OPL_SLOT *SLOT, UINT32 key_set) { if( !SLOT->key ) { /* restart Phase Generator */ SLOT->Cnt = 0; /* phase -> Attack */ SLOT->state = EG_ATT; } SLOT->key |= key_set; } INLINE void FM_KEYOFF(OPL_SLOT *SLOT, UINT32 key_clr) { if( SLOT->key ) { SLOT->key &= key_clr; if( !SLOT->key ) { /* phase -> Release */ if (SLOT->state>EG_REL) SLOT->state = EG_REL; } } } /* update phase increment counter of operator (also update the EG rates if necessary) */ INLINE void CALC_FCSLOT(OPL_CH *CH,OPL_SLOT *SLOT) { int ksr; /* (frequency) phase increment counter */ SLOT->Incr = CH->fc * SLOT->mul; ksr = CH->kcode >> SLOT->KSR; if( SLOT->ksr != ksr ) { SLOT->ksr = ksr; /* calculate envelope generator rates */ if ((SLOT->ar + SLOT->ksr) < 16+62) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; } } /* set multi,am,vib,EG-TYP,KSR,mul */ INLINE void set_mul(FM_OPL *OPL,int slot,int v) { OPL_CH *CH = &OPL->P_CH[slot/2]; OPL_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->mul = mul_tab[v&0x0f]; SLOT->KSR = (v&0x10) ? 0 : 2; SLOT->eg_type = (v&0x20); SLOT->vib = (v&0x40); SLOT->AMmask = (v&0x80) ? ~0 : 0; CALC_FCSLOT(CH,SLOT); } /* set ksl & tl */ INLINE void set_ksl_tl(FM_OPL *OPL,int slot,int v) { OPL_CH *CH = &OPL->P_CH[slot/2]; OPL_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->ksl = ksl_shift[v >> 6]; SLOT->TL = (v&0x3f)<<(ENV_BITS-1-7); /* 7 bits TL (bit 6 = always 0) */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } /* set attack rate & decay rate */ INLINE void set_ar_dr(FM_OPL *OPL,int slot,int v) { OPL_CH *CH = &OPL->P_CH[slot/2]; OPL_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->ar = (v>>4) ? 16 + ((v>>4) <<2) : 0; if ((SLOT->ar + SLOT->ksr) < 16+62) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->dr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; } /* set sustain level & release rate */ INLINE void set_sl_rr(FM_OPL *OPL,int slot,int v) { OPL_CH *CH = &OPL->P_CH[slot/2]; OPL_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->sl = sl_tab[ v>>4 ]; SLOT->rr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; } /* write a value v to register r on OPL chip */ static void OPLWriteReg(FM_OPL *OPL, int r, int v) { OPL_CH *CH; int slot; UINT32 block_fnum; /* adjust bus to 8 bits */ r &= 0xff; v &= 0xff; switch(r&0xe0) { case 0x00: /* 00-1f:control */ switch(r&0x1f) { case 0x01: /* waveform select enable */ if(OPL->type&OPL_TYPE_WAVESEL) { OPL->wavesel = v&0x20; /* do not change the waveform previously selected */ } break; case 0x02: /* Timer 1 */ OPL->T[0] = (256-v)*4; break; case 0x03: /* Timer 2 */ OPL->T[1] = (256-v)*16; break; case 0x04: /* IRQ clear / mask and Timer enable */ if(v&0x80) { /* IRQ flag clear */ /* don't reset BFRDY flag or we will have to call deltat module to set the flag */ OPL_STATUS_RESET(OPL,0x7f-0x08); } else { /* set IRQ mask ,timer enable*/ UINT8 st1 = v&1; UINT8 st2 = (v>>1)&1; /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ OPL_STATUS_RESET(OPL, v & (0x78-0x08) ); OPL_STATUSMASK_SET(OPL, (~v) & 0x78 ); /* timer 2 */ if(OPL->st[1] != st2) { double period = st2 ? (OPL->TimerBase * OPL->T[1]) : 0.0; OPL->st[1] = st2; if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,1,period); } /* timer 1 */ if(OPL->st[0] != st1) { double period = st1 ? (OPL->TimerBase * OPL->T[0]) : 0.0; OPL->st[0] = st1; if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,0,period); } } break; #if BUILD_Y8950 case 0x06: /* Key Board OUT */ if(OPL->type&OPL_TYPE_KEYBOARD) { if(OPL->keyboardhandler_w) OPL->keyboardhandler_w(OPL->keyboard_param,v); else logerror("Y8950: write unmapped KEYBOARD port\n"); } break; case 0x07: /* DELTA-T control 1 : START,REC,MEMDATA,REPT,SPOFF,x,x,RST */ if(OPL->type&OPL_TYPE_ADPCM) YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); break; #endif case 0x08: /* MODE,DELTA-T control 2 : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */ OPL->mode = v; #if BUILD_Y8950 if(OPL->type&OPL_TYPE_ADPCM) { /* mask 4 LSBs in register 08 for DELTA-T unit */ YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v&0x0f); } #endif break; #if BUILD_Y8950 case 0x09: /* START ADD */ case 0x0a: case 0x0b: /* STOP ADD */ case 0x0c: case 0x0d: /* PRESCALE */ case 0x0e: case 0x0f: /* ADPCM data write */ case 0x10: /* DELTA-N */ case 0x11: /* DELTA-N */ case 0x12: /* ADPCM volume */ if(OPL->type&OPL_TYPE_ADPCM) YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); break; case 0x15: /* DAC data high 8 bits (F7,F6...F2) */ case 0x16: /* DAC data low 2 bits (F1, F0 in bits 7,6) */ case 0x17: /* DAC data shift (S2,S1,S0 in bits 2,1,0) */ logerror("FMOPL.C: DAC data register written, but not implemented reg=%02x val=%02x\n", r,v); break; case 0x18: /* I/O CTRL (Direction) */ if(OPL->type&OPL_TYPE_IO) OPL->portDirection = v&0x0f; break; case 0x19: /* I/O DATA */ if(OPL->type&OPL_TYPE_IO) { OPL->portLatch = v; if(OPL->porthandler_w) OPL->porthandler_w(OPL->port_param,v&OPL->portDirection); } break; #endif default: /*logerror("FMOPL.C: write to unknown register: %02x\n",r);*/ break; } break; case 0x20: /* am ON, vib ON, ksr, eg_type, mul */ slot = slot_array[r&0x1f]; if(slot < 0) return; set_mul(OPL,slot,v); break; case 0x40: slot = slot_array[r&0x1f]; if(slot < 0) return; set_ksl_tl(OPL,slot,v); break; case 0x60: slot = slot_array[r&0x1f]; if(slot < 0) return; set_ar_dr(OPL,slot,v); break; case 0x80: slot = slot_array[r&0x1f]; if(slot < 0) return; set_sl_rr(OPL,slot,v); break; case 0xa0: if (r == 0xbd) /* am depth, vibrato depth, r,bd,sd,tom,tc,hh */ { OPL->lfo_am_depth = v & 0x80; OPL->lfo_pm_depth_range = (v&0x40) ? 8 : 0; OPL->rhythm = v&0x3f; if(OPL->rhythm&0x20) { /* BD key on/off */ if(v&0x10) { FM_KEYON (&OPL->P_CH[6].SLOT[SLOT1], 2); FM_KEYON (&OPL->P_CH[6].SLOT[SLOT2], 2); } else { FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); } /* HH key on/off */ if(v&0x01) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT1], 2); else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); /* SD key on/off */ if(v&0x08) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT2], 2); else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); /* TOM key on/off */ if(v&0x04) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT1], 2); else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); /* TOP-CY key on/off */ if(v&0x02) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT2], 2); else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); } else { /* BD key off */ FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); /* HH key off */ FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); /* SD key off */ FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); /* TOM key off */ FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); /* TOP-CY off */ FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); } return; } /* keyon,block,fnum */ if( (r&0x0f) > 8) return; CH = &OPL->P_CH[r&0x0f]; if(!(r&0x10)) { /* a0-a8 */ block_fnum = (CH->block_fnum&0x1f00) | v; } else { /* b0-b8 */ block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff); if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } } /* update */ if(CH->block_fnum != block_fnum) { UINT8 block = block_fnum >> 10; CH->block_fnum = block_fnum; CH->ksl_base = ksl_tab[block_fnum>>6]; CH->fc = OPL->fn_tab[block_fnum&0x03ff] >> (7-block); /* BLK 2,1,0 bits -> bits 3,2,1 of kcode */ CH->kcode = (CH->block_fnum&0x1c00)>>9; /* the info below is actually opposite to what is stated in the Manuals (verifed on real YM3812) */ /* if notesel == 0 -> lsb of kcode is bit 10 (MSB) of fnum */ /* if notesel == 1 -> lsb of kcode is bit 9 (MSB-1) of fnum */ if (OPL->mode&0x40) CH->kcode |= (CH->block_fnum&0x100)>>8; /* notesel == 1 */ else CH->kcode |= (CH->block_fnum&0x200)>>9; /* notesel == 0 */ /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); } break; case 0xc0: /* FB,C */ if( (r&0x0f) > 8) return; CH = &OPL->P_CH[r&0x0f]; CH->SLOT[SLOT1].FB = (v>>1)&7 ? ((v>>1)&7) + 7 : 0; CH->SLOT[SLOT1].CON = v&1; CH->SLOT[SLOT1].connect1 = CH->SLOT[SLOT1].CON ? &OPL->output[0] : &OPL->phase_modulation; break; case 0xe0: /* waveform select */ /* simply ignore write to the waveform select register if selecting not enabled in test register */ if(OPL->wavesel) { slot = slot_array[r&0x1f]; if(slot < 0) return; CH = &OPL->P_CH[slot/2]; CH->SLOT[slot&1].wavetable = (v&0x03)*SIN_LEN; } break; } } /* lock/unlock for common table */ static int OPL_LockTable() { num_lock++; if(num_lock>1) return 0; /* first time */ /* allocate total level table (128kb space) */ if( !init_tables() ) { num_lock--; return -1; } return 0; } static void OPL_UnLockTable(void) { if(num_lock) num_lock--; if(num_lock) return; /* last time */ OPLCloseTable(); } static void OPLResetChip(FM_OPL *OPL) { int c,s; int i; OPL->eg_timer = 0; OPL->eg_cnt = 0; OPL->noise_rng = 1; /* noise shift register */ OPL->mode = 0; /* normal mode */ OPL_STATUS_RESET(OPL,0x7f); /* reset with register write */ OPLWriteReg(OPL,0x01,0); /* wavesel disable */ OPLWriteReg(OPL,0x02,0); /* Timer1 */ OPLWriteReg(OPL,0x03,0); /* Timer2 */ OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */ for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0); /* reset operator parameters */ for( c = 0 ; c < 9 ; c++ ) { OPL_CH *CH = &OPL->P_CH[c]; for(s = 0 ; s < 2 ; s++ ) { /* wave table */ CH->SLOT[s].wavetable = 0; CH->SLOT[s].state = EG_OFF; CH->SLOT[s].volume = MAX_ATT_INDEX; } } #if BUILD_Y8950 if(OPL->type&OPL_TYPE_ADPCM) { YM_DELTAT *DELTAT = OPL->deltat; DELTAT->freqbase = OPL->freqbase; DELTAT->output_pointer = &OPL->output_deltat[0]; DELTAT->portshift = 5; DELTAT->output_range = 1<<23; YM_DELTAT_ADPCM_Reset(DELTAT,0,YM_DELTAT_EMULATION_MODE_NORMAL); } #endif } #if 0 // not used anywhere static void OPL_postload(FM_OPL *OPL) { int slot, ch; for( ch=0 ; ch < 9 ; ch++ ) { OPL_CH *CH = &OPL->P_CH[ch]; /* Look up key scale level */ UINT32 block_fnum = CH->block_fnum; CH->ksl_base = ksl_tab[block_fnum >> 6]; CH->fc = OPL->fn_tab[block_fnum & 0x03ff] >> (7 - (block_fnum >> 10)); for( slot=0 ; slot < 2 ; slot++ ) { OPL_SLOT *SLOT = &CH->SLOT[slot]; /* Calculate key scale rate */ SLOT->ksr = CH->kcode >> SLOT->KSR; /* Calculate attack, decay and release rates */ if ((SLOT->ar + SLOT->ksr) < 16+62) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; /* Calculate phase increment */ SLOT->Incr = CH->fc * SLOT->mul; /* Total level */ SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); /* Connect output */ SLOT->connect1 = SLOT->CON ? &OPL->output[0] : &OPL->phase_modulation; } } #if BUILD_Y8950 if ( (OPL->type & OPL_TYPE_ADPCM) && (OPL->deltat) ) { // We really should call the postlod function for the YM_DELTAT, but it's hard without registers // (see the way the YM2610 does it) //YM_DELTAT_postload(OPL->deltat, REGS); } #endif } #endif /* Create one of virtual YM3812/YM3526/Y8950 */ /* 'clock' is chip clock in Hz */ /* 'rate' is sampling rate */ static FM_OPL *OPLCreate(UINT32 clock, UINT32 rate, int type) { char *ptr; FM_OPL *OPL; int state_size; if (OPL_LockTable() == -1) return NULL; /* calculate OPL state size */ state_size = sizeof(FM_OPL); #if BUILD_Y8950 if (type&OPL_TYPE_ADPCM) state_size+= sizeof(YM_DELTAT); #endif /* allocate memory block */ ptr = (char *)calloc(1, state_size); if (ptr == NULL) return NULL; OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL); #if BUILD_Y8950 if (type&OPL_TYPE_ADPCM) { OPL->deltat = (YM_DELTAT *)ptr; } ptr += sizeof(YM_DELTAT); #endif OPL->type = type; OPL->clock = clock; OPL->rate = rate; /* init global tables */ OPL_initalize(OPL); return OPL; } /* Destroy one of virtual YM3812 */ static void OPLDestroy(FM_OPL *OPL) { OPL_UnLockTable(); free(OPL); } /* Optional handlers */ static void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER timer_handler,void *param) { OPL->timer_handler = timer_handler; OPL->TimerParam = param; } static void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,void *param) { OPL->IRQHandler = IRQHandler; OPL->IRQParam = param; } static void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,void *param) { OPL->UpdateHandler = UpdateHandler; OPL->UpdateParam = param; } static int OPLWrite(FM_OPL *OPL,int a,int v) { if( !(a&1) ) { /* address port */ OPL->address = v & 0xff; } else { /* data port */ if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); OPLWriteReg(OPL,OPL->address,v); } return OPL->status>>7; } static unsigned char OPLRead(FM_OPL *OPL,int a) { if( !(a&1) ) { /* status port */ #if BUILD_Y8950 if(OPL->type&OPL_TYPE_ADPCM) /* Y8950 */ { return (OPL->status & (OPL->statusmask|0x80)) | (OPL->deltat->PCM_BSY&1); } #endif /* OPL and OPL2 */ return OPL->status & (OPL->statusmask|0x80); } #if BUILD_Y8950 /* data port */ switch(OPL->address) { case 0x05: /* KeyBoard IN */ if(OPL->type&OPL_TYPE_KEYBOARD) { if(OPL->keyboardhandler_r) return OPL->keyboardhandler_r(OPL->keyboard_param); else logerror("Y8950: read unmapped KEYBOARD port\n"); } return 0; case 0x0f: /* ADPCM-DATA */ if(OPL->type&OPL_TYPE_ADPCM) { UINT8 val; val = YM_DELTAT_ADPCM_Read(OPL->deltat); /*logerror("Y8950: read ADPCM value read=%02x\n",val);*/ return val; } return 0; case 0x19: /* I/O DATA */ if(OPL->type&OPL_TYPE_IO) { if(OPL->porthandler_r) return OPL->porthandler_r(OPL->port_param); else logerror("Y8950:read unmapped I/O port\n"); } return 0; case 0x1a: /* PCM-DATA */ if(OPL->type&OPL_TYPE_ADPCM) { logerror("Y8950 A/D conversion is accessed but not implemented !\n"); return 0x80; /* 2's complement PCM data - result from A/D conversion */ } return 0; } #endif return 0xff; } /* CSM Key Controll */ INLINE void CSMKeyControll(OPL_CH *CH) { FM_KEYON (&CH->SLOT[SLOT1], 4); FM_KEYON (&CH->SLOT[SLOT2], 4); /* The key off should happen exactly one sample later - not implemented correctly yet */ FM_KEYOFF(&CH->SLOT[SLOT1], ~4); FM_KEYOFF(&CH->SLOT[SLOT2], ~4); } static int OPLTimerOver(FM_OPL *OPL,int c) { if( c ) { /* Timer B */ OPL_STATUS_SET(OPL,0x20); } else { /* Timer A */ OPL_STATUS_SET(OPL,0x40); /* CSM mode key,TL controll */ if( OPL->mode & 0x80 ) { /* CSM mode total level latch and auto key on */ int ch; if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); for(ch=0; ch<9; ch++) CSMKeyControll( &OPL->P_CH[ch] ); } } /* reload timer */ if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,c,OPL->TimerBase * OPL->T[c]); return OPL->status>>7; } #define MAX_OPL_CHIPS 2 #if (BUILD_YM3812) void * ym3812_init(UINT32 clock, UINT32 rate) { /* emulator create */ FM_OPL *YM3812 = OPLCreate(clock,rate,OPL_TYPE_YM3812); if (YM3812) { ym3812_reset_chip(YM3812); } return YM3812; } void ym3812_shutdown(void *chip) { FM_OPL *YM3812 = (FM_OPL *)chip; /* emulator shutdown */ OPLDestroy(YM3812); } void ym3812_reset_chip(void *chip) { FM_OPL *YM3812 = (FM_OPL *)chip; OPLResetChip(YM3812); } int ym3812_write(void *chip, int a, int v) { FM_OPL *YM3812 = (FM_OPL *)chip; return OPLWrite(YM3812, a, v); } unsigned char ym3812_read(void *chip, int a) { FM_OPL *YM3812 = (FM_OPL *)chip; /* YM3812 always returns bit2 and bit1 in HIGH state */ return OPLRead(YM3812, a) | 0x06 ; } int ym3812_timer_over(void *chip, int c) { FM_OPL *YM3812 = (FM_OPL *)chip; return OPLTimerOver(YM3812, c); } void ym3812_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) { FM_OPL *YM3812 = (FM_OPL *)chip; OPLSetTimerHandler(YM3812, timer_handler, param); } void ym3812_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) { FM_OPL *YM3812 = (FM_OPL *)chip; OPLSetIRQHandler(YM3812, IRQHandler, param); } void ym3812_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) { FM_OPL *YM3812 = (FM_OPL *)chip; OPLSetUpdateHandler(YM3812, UpdateHandler, param); } /* ** Generate samples for one of the YM3812's ** ** 'which' is the virtual YM3812 number ** '*buffer' is the output buffer pointer ** 'length' is the number of samples that should be generated */ void ym3812_update_one(void *chip, OPLSAMPLE *buffer, int length) { FM_OPL *OPL = (FM_OPL *)chip; UINT8 rhythm = OPL->rhythm&0x20; OPLSAMPLE *buf = buffer; int i; for( i=0; i < length ; i++ ) { int lt; OPL->output[0] = 0; advance_lfo(OPL); /* FM part */ OPL_CALC_CH(OPL, &OPL->P_CH[0]); OPL_CALC_CH(OPL, &OPL->P_CH[1]); OPL_CALC_CH(OPL, &OPL->P_CH[2]); OPL_CALC_CH(OPL, &OPL->P_CH[3]); OPL_CALC_CH(OPL, &OPL->P_CH[4]); OPL_CALC_CH(OPL, &OPL->P_CH[5]); if(!rhythm) { OPL_CALC_CH(OPL, &OPL->P_CH[6]); OPL_CALC_CH(OPL, &OPL->P_CH[7]); OPL_CALC_CH(OPL, &OPL->P_CH[8]); } else /* Rhythm part */ { OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); } lt = OPL->output[0]; lt >>= FINAL_SH; /* limit check */ lt = limit( lt , MAXOUT, MINOUT ); /* store to sound buffer */ buf[i] = lt; advance(OPL); } } #endif /* BUILD_YM3812 */ #if (BUILD_YM3526) void *ym3526_init(UINT32 clock, UINT32 rate) { /* emulator create */ FM_OPL *YM3526 = OPLCreate(clock,rate,OPL_TYPE_YM3526); if (YM3526) { ym3526_reset_chip(YM3526); } return YM3526; } void ym3526_shutdown(void *chip) { FM_OPL *YM3526 = (FM_OPL *)chip; /* emulator shutdown */ OPLDestroy(YM3526); } void ym3526_reset_chip(void *chip) { FM_OPL *YM3526 = (FM_OPL *)chip; OPLResetChip(YM3526); } int ym3526_write(void *chip, int a, int v) { FM_OPL *YM3526 = (FM_OPL *)chip; return OPLWrite(YM3526, a, v); } unsigned char ym3526_read(void *chip, int a) { FM_OPL *YM3526 = (FM_OPL *)chip; /* YM3526 always returns bit2 and bit1 in HIGH state */ return OPLRead(YM3526, a) | 0x06 ; } int ym3526_timer_over(void *chip, int c) { FM_OPL *YM3526 = (FM_OPL *)chip; return OPLTimerOver(YM3526, c); } void ym3526_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) { FM_OPL *YM3526 = (FM_OPL *)chip; OPLSetTimerHandler(YM3526, timer_handler, param); } void ym3526_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) { FM_OPL *YM3526 = (FM_OPL *)chip; OPLSetIRQHandler(YM3526, IRQHandler, param); } void ym3526_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) { FM_OPL *YM3526 = (FM_OPL *)chip; OPLSetUpdateHandler(YM3526, UpdateHandler, param); } /* ** Generate samples for one of the YM3526's ** ** 'which' is the virtual YM3526 number ** '*buffer' is the output buffer pointer ** 'length' is the number of samples that should be generated */ void ym3526_update_one(void *chip, OPLSAMPLE *buffer, int length) { FM_OPL *OPL = (FM_OPL *)chip; UINT8 rhythm = OPL->rhythm&0x20; OPLSAMPLE *buf = buffer; int i; for( i=0; i < length ; i++ ) { int lt; OPL->output[0] = 0; advance_lfo(OPL); /* FM part */ OPL_CALC_CH(OPL, &OPL->P_CH[0]); OPL_CALC_CH(OPL, &OPL->P_CH[1]); OPL_CALC_CH(OPL, &OPL->P_CH[2]); OPL_CALC_CH(OPL, &OPL->P_CH[3]); OPL_CALC_CH(OPL, &OPL->P_CH[4]); OPL_CALC_CH(OPL, &OPL->P_CH[5]); if(!rhythm) { OPL_CALC_CH(OPL, &OPL->P_CH[6]); OPL_CALC_CH(OPL, &OPL->P_CH[7]); OPL_CALC_CH(OPL, &OPL->P_CH[8]); } else /* Rhythm part */ { OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); } lt = OPL->output[0]; lt >>= FINAL_SH; /* limit check */ lt = limit( lt , MAXOUT, MINOUT ); /* store to sound buffer */ buf[i] = lt; advance(OPL); } } #endif /* BUILD_YM3526 */ #if BUILD_Y8950 static void Y8950_deltat_status_set(void *chip, UINT8 changebits) { FM_OPL *Y8950 = (FM_OPL *)chip; OPL_STATUS_SET(Y8950, changebits); } static void Y8950_deltat_status_reset(void *chip, UINT8 changebits) { FM_OPL *Y8950 = (FM_OPL *)chip; OPL_STATUS_RESET(Y8950, changebits); } void *y8950_init(UINT32 clock, UINT32 rate) { /* emulator create */ FM_OPL *Y8950 = OPLCreate(clock,rate,OPL_TYPE_Y8950); if (Y8950) { Y8950->deltat->status_set_handler = Y8950_deltat_status_set; Y8950->deltat->status_reset_handler = Y8950_deltat_status_reset; Y8950->deltat->status_change_which_chip = Y8950; Y8950->deltat->status_change_EOS_bit = 0x10; /* status flag: set bit4 on End Of Sample */ Y8950->deltat->status_change_BRDY_bit = 0x08; /* status flag: set bit3 on BRDY (End Of: ADPCM analysis/synthesis, memory reading/writing) */ /*Y8950->deltat->write_time = 10.0 / clock;*/ /* a single byte write takes 10 cycles of main clock */ /*Y8950->deltat->read_time = 8.0 / clock;*/ /* a single byte read takes 8 cycles of main clock */ /* reset */ y8950_reset_chip(Y8950); } return Y8950; } void y8950_shutdown(void *chip) { FM_OPL *Y8950 = (FM_OPL *)chip; /* emulator shutdown */ OPLDestroy(Y8950); } void y8950_reset_chip(void *chip) { FM_OPL *Y8950 = (FM_OPL *)chip; OPLResetChip(Y8950); } int y8950_write(void *chip, int a, int v) { FM_OPL *Y8950 = (FM_OPL *)chip; return OPLWrite(Y8950, a, v); } unsigned char y8950_read(void *chip, int a) { FM_OPL *Y8950 = (FM_OPL *)chip; return OPLRead(Y8950, a); } int y8950_timer_over(void *chip, int c) { FM_OPL *Y8950 = (FM_OPL *)chip; return OPLTimerOver(Y8950, c); } void y8950_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) { FM_OPL *Y8950 = (FM_OPL *)chip; OPLSetTimerHandler(Y8950, timer_handler, param); } void y8950_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) { FM_OPL *Y8950 = (FM_OPL *)chip; OPLSetIRQHandler(Y8950, IRQHandler, param); } void y8950_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) { FM_OPL *Y8950 = (FM_OPL *)chip; OPLSetUpdateHandler(Y8950, UpdateHandler, param); } void y8950_set_delta_t_memory(void *chip, void * deltat_mem_ptr, int deltat_mem_size ) { FM_OPL *OPL = (FM_OPL *)chip; OPL->deltat->memory = (UINT8 *)(deltat_mem_ptr); OPL->deltat->memory_size = deltat_mem_size; } /* ** Generate samples for one of the Y8950's ** ** 'which' is the virtual Y8950 number ** '*buffer' is the output buffer pointer ** 'length' is the number of samples that should be generated */ void y8950_update_one(void *chip, OPLSAMPLE *buffer, int length) { int i; FM_OPL *OPL = (FM_OPL *)chip; UINT8 rhythm = OPL->rhythm&0x20; YM_DELTAT *DELTAT = OPL->deltat; OPLSAMPLE *buf = buffer; for( i=0; i < length ; i++ ) { int lt; OPL->output[0] = 0; OPL->output_deltat[0] = 0; advance_lfo(OPL); /* deltaT ADPCM */ if( DELTAT->portstate&0x80 ) YM_DELTAT_ADPCM_CALC(DELTAT); /* FM part */ OPL_CALC_CH(OPL, &OPL->P_CH[0]); OPL_CALC_CH(OPL, &OPL->P_CH[1]); OPL_CALC_CH(OPL, &OPL->P_CH[2]); OPL_CALC_CH(OPL, &OPL->P_CH[3]); OPL_CALC_CH(OPL, &OPL->P_CH[4]); OPL_CALC_CH(OPL, &OPL->P_CH[5]); if(!rhythm) { OPL_CALC_CH(OPL, &OPL->P_CH[6]); OPL_CALC_CH(OPL, &OPL->P_CH[7]); OPL_CALC_CH(OPL, &OPL->P_CH[8]); } else /* Rhythm part */ { OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); } lt = OPL->output[0] + (OPL->output_deltat[0]>>11); lt >>= FINAL_SH; /* limit check */ lt = limit( lt , MAXOUT, MINOUT ); /* store to sound buffer */ buf[i] = lt; advance(OPL); } } void y8950_set_port_handler(void *chip,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,void * param) { FM_OPL *OPL = (FM_OPL *)chip; OPL->porthandler_w = PortHandler_w; OPL->porthandler_r = PortHandler_r; OPL->port_param = param; } void y8950_set_keyboard_handler(void *chip,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,void * param) { FM_OPL *OPL = (FM_OPL *)chip; OPL->keyboardhandler_w = KeyboardHandler_w; OPL->keyboardhandler_r = KeyboardHandler_r; OPL->keyboard_param = param; } #endif schismtracker-20180209/player/fmopl3.c000066400000000000000000002163411323741476300175040ustar00rootroot00000000000000// license:GPL-2.0+ // copyright-holders:Jarek Burczynski /* ** ** File: ymf262.c - software implementation of YMF262 ** FM sound generator type OPL3 ** ** Copyright Jarek Burczynski ** ** Version 0.2 ** Revision History: 03-03-2003: initial release - thanks to Olivier Galibert and Chris Hardy for YMF262 and YAC512 chips - thanks to Stiletto for the datasheets Features as listed in 4MF262A6 data sheet: 1. Registers are compatible with YM3812 (OPL2) FM sound source. 2. Up to six sounds can be used as four-operator melody sounds for variety. 3. 18 simultaneous melody sounds, or 15 melody sounds with 5 rhythm sounds (with two operators). 4. 6 four-operator melody sounds and 6 two-operator melody sounds, or 6 four-operator melody sounds, 3 two-operator melody sounds and 5 rhythm sounds (with four operators). 5. 8 selectable waveforms. 6. 4-channel sound output. 7. YMF262 compabile DAC (YAC512) is available. 8. LFO for vibrato and tremolo effedts. 9. 2 programable timers. 10. Shorter register access time compared with YM3812. 11. 5V single supply silicon gate CMOS process. 12. 24 Pin SOP Package (YMF262-M), 48 Pin SQFP Package (YMF262-S). differences between OPL2 and OPL3 not documented in Yamaha datahasheets: - sinus table is a little different: the negative part is off by one... - in order to enable selection of four different waveforms on OPL2 one must set bit 5 in register 0x01(test). on OPL3 this bit is ignored and 4-waveform select works *always*. (Don't confuse this with OPL3's 8-waveform select.) - Envelope Generator: all 15 x rates take zero time on OPL3 (on OPL2 15 0 and 15 1 rates take some time while 15 2 and 15 3 rates take zero time) - channel calculations: output of operator 1 is in perfect sync with output of operator 2 on OPL3; on OPL and OPL2 output of operator 1 is always delayed by one sample compared to output of operator 2 differences between OPL2 and OPL3 shown in datasheets: - YMF262 does not support CSM mode */ #include #include #include #include #include "fmopl3.h" /* output final shift */ #if (OPL3_SAMPLE_BITS==16) #define FINAL_SH (0) #define MAXOUT (+32767) #define MINOUT (-32768) #else #define FINAL_SH (8) #define MAXOUT (+127) #define MINOUT (-128) #endif #define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ #define EG_SH 16 /* 16.16 fixed point (EG timing) */ #define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ #define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ #define FREQ_MASK ((1<>KSR */ UINT8 mul; /* multiple: mul_tab[ML] */ /* Phase Generator */ UINT32 Cnt; /* frequency counter */ UINT32 Incr; /* frequency counter step */ UINT8 FB; /* feedback shift value */ INT32 *connect; /* slot output pointer */ INT32 op1_out[2]; /* slot1 output for feedback */ UINT8 CON; /* connection (algorithm) type */ /* Envelope Generator */ UINT8 eg_type; /* percussive/non-percussive mode */ UINT8 state; /* phase type */ UINT32 TL; /* total level: TL << 2 */ INT32 TLL; /* adjusted now TL */ INT32 volume; /* envelope counter */ UINT32 sl; /* sustain level: sl_tab[SL] */ UINT32 eg_m_ar; /* (attack state) */ UINT8 eg_sh_ar; /* (attack state) */ UINT8 eg_sel_ar; /* (attack state) */ UINT32 eg_m_dr; /* (decay state) */ UINT8 eg_sh_dr; /* (decay state) */ UINT8 eg_sel_dr; /* (decay state) */ UINT32 eg_m_rr; /* (release state) */ UINT8 eg_sh_rr; /* (release state) */ UINT8 eg_sel_rr; /* (release state) */ UINT32 key; /* 0 = KEY OFF, >0 = KEY ON */ /* LFO */ UINT32 AMmask; /* LFO Amplitude Modulation enable mask */ UINT8 vib; /* LFO Phase Modulation enable flag (active high)*/ /* waveform select */ UINT8 waveform_number; unsigned int wavetable; //unsigned char reserved[128-84];//speedup: pump up the struct size to power of 2 unsigned char reserved[128-100];//speedup: pump up the struct size to power of 2 } OPL3_SLOT; typedef struct { OPL3_SLOT SLOT[2]; UINT32 block_fnum; /* block+fnum */ UINT32 fc; /* Freq. Increment base */ UINT32 ksl_base; /* KeyScaleLevel Base step */ UINT8 kcode; /* key code (for key scaling) */ /* there are 12 2-operator channels which can be combined in pairs to form six 4-operator channel, they are: 0 and 3, 1 and 4, 2 and 5, 9 and 12, 10 and 13, 11 and 14 */ UINT8 extended; /* set to 1 if this channel forms up a 4op channel with another channel(only used by first of pair of channels, ie 0,1,2 and 9,10,11) */ unsigned char reserved[512-272];//speedup:pump up the struct size to power of 2 } OPL3_CH; /* OPL3 state */ typedef struct { OPL3_CH P_CH[18]; /* OPL3 chips have 18 channels */ UINT32 pan[18*4]; /* channels output masks (0xffffffff = enable); 4 masks per one channel */ UINT32 pan_ctrl_value[18]; /* output control values 1 per one channel (1 value contains 4 masks) */ signed int chanout[18]; signed int phase_modulation; /* phase modulation input (SLOT 2) */ signed int phase_modulation2; /* phase modulation input (SLOT 3 in 4 operator channels) */ UINT32 eg_cnt; /* global envelope generator counter */ UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/288 (288=8*36) */ UINT32 eg_timer_add; /* step of eg_timer */ UINT32 eg_timer_overflow; /* envelope generator timer overlfows every 1 sample (on real chip) */ UINT32 fn_tab[1024]; /* fnumber->increment counter */ /* LFO */ UINT32 LFO_AM; INT32 LFO_PM; UINT8 lfo_am_depth; UINT8 lfo_pm_depth_range; UINT32 lfo_am_cnt; UINT32 lfo_am_inc; UINT32 lfo_pm_cnt; UINT32 lfo_pm_inc; UINT32 noise_rng; /* 23 bit noise shift register */ UINT32 noise_p; /* current noise 'phase' */ UINT32 noise_f; /* current noise period */ UINT8 OPL3_mode; /* OPL3 extension enable flag */ UINT8 rhythm; /* Rhythm mode */ int T[2]; /* timer counters */ UINT8 st[2]; /* timer enable */ UINT32 address; /* address register */ UINT8 status; /* status flag */ UINT8 statusmask; /* status mask */ UINT8 nts; /* NTS (note select) */ /* external event callback handlers */ OPL3_TIMERHANDLER timer_handler;/* TIMER handler */ void *TimerParam; /* TIMER parameter */ OPL3_IRQHANDLER IRQHandler; /* IRQ handler */ void *IRQParam; /* IRQ parameter */ OPL3_UPDATEHANDLER UpdateHandler;/* stream update handler */ void *UpdateParam; /* stream update parameter */ UINT8 type; /* chip type */ int clock; /* master clock (Hz) */ int rate; /* sampling rate (Hz) */ double freqbase; /* frequency base */ double TimerBase; /* Timer base time (==sampling time)*/ } OPL3; /* mapping of register number (offset) to slot number used by the emulator */ static const int slot_array[32]= { 0, 2, 4, 1, 3, 5,-1,-1, 6, 8,10, 7, 9,11,-1,-1, 12,14,16,13,15,17,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1 }; /* key scale level */ /* table is 3dB/octave , DV converts this into 6dB/octave */ /* 0.1875 is bit 0 weight of the envelope counter (volume) expressed in the 'decibel' scale */ #define DV (0.1875/2.0) static const double ksl_tab[8*16]= { /* OCT 0 */ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, /* OCT 1 */ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.750/DV, 1.125/DV, 1.500/DV, 1.875/DV, 2.250/DV, 2.625/DV, 3.000/DV, /* OCT 2 */ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 1.125/DV, 1.875/DV, 2.625/DV, 3.000/DV, 3.750/DV, 4.125/DV, 4.500/DV, 4.875/DV, 5.250/DV, 5.625/DV, 6.000/DV, /* OCT 3 */ 0.000/DV, 0.000/DV, 0.000/DV, 1.875/DV, 3.000/DV, 4.125/DV, 4.875/DV, 5.625/DV, 6.000/DV, 6.750/DV, 7.125/DV, 7.500/DV, 7.875/DV, 8.250/DV, 8.625/DV, 9.000/DV, /* OCT 4 */ 0.000/DV, 0.000/DV, 3.000/DV, 4.875/DV, 6.000/DV, 7.125/DV, 7.875/DV, 8.625/DV, 9.000/DV, 9.750/DV,10.125/DV,10.500/DV, 10.875/DV,11.250/DV,11.625/DV,12.000/DV, /* OCT 5 */ 0.000/DV, 3.000/DV, 6.000/DV, 7.875/DV, 9.000/DV,10.125/DV,10.875/DV,11.625/DV, 12.000/DV,12.750/DV,13.125/DV,13.500/DV, 13.875/DV,14.250/DV,14.625/DV,15.000/DV, /* OCT 6 */ 0.000/DV, 6.000/DV, 9.000/DV,10.875/DV, 12.000/DV,13.125/DV,13.875/DV,14.625/DV, 15.000/DV,15.750/DV,16.125/DV,16.500/DV, 16.875/DV,17.250/DV,17.625/DV,18.000/DV, /* OCT 7 */ 0.000/DV, 9.000/DV,12.000/DV,13.875/DV, 15.000/DV,16.125/DV,16.875/DV,17.625/DV, 18.000/DV,18.750/DV,19.125/DV,19.500/DV, 19.875/DV,20.250/DV,20.625/DV,21.000/DV }; #undef DV /* 0 / 3.0 / 1.5 / 6.0 dB/OCT */ static const UINT32 ksl_shift[4] = { 31, 1, 2, 0 }; /* sustain level table (3dB per step) */ /* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ #define SC(db) (UINT32) ( db * (2.0/ENV_STEP) ) static const UINT32 sl_tab[16]={ SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) }; #undef SC #define RATE_STEPS (8) static const unsigned char eg_inc[15*RATE_STEPS]={ /*cycle:0 1 2 3 4 5 6 7*/ /* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..12 0 (increment by 0 or 1) */ /* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..12 1 */ /* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..12 2 */ /* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..12 3 */ /* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 13 0 (increment by 1) */ /* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 13 1 */ /* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 13 2 */ /* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 13 3 */ /* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 14 0 (increment by 2) */ /* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 14 1 */ /*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 14 2 */ /*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 14 3 */ /*12 */ 4,4, 4,4, 4,4, 4,4, /* rates 15 0, 15 1, 15 2, 15 3 for decay */ /*13 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 0, 15 1, 15 2, 15 3 for attack (zero time) */ /*14 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ }; #define O(a) (a*RATE_STEPS) /* note that there is no O(13) in this table - it's directly in the code */ static const unsigned char eg_rate_select[16+64+16]={ /* Envelope Generator rates (16 + 64 rates + 16 RKS) */ /* 16 infinite time rates */ O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), /* rates 00-12 */ O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), /* rate 13 */ O( 4),O( 5),O( 6),O( 7), /* rate 14 */ O( 8),O( 9),O(10),O(11), /* rate 15 */ O(12),O(12),O(12),O(12), /* 16 dummy rates (same as 15 3) */ O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), }; #undef O /*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ /*shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 */ /*mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 */ #define O(a) (a*1) static const unsigned char eg_rate_shift[16+64+16]={ /* Envelope Generator counter shifts (16 + 64 rates + 16 RKS) */ /* 16 infinite time rates */ O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), /* rates 00-12 */ O(12),O(12),O(12),O(12), O(11),O(11),O(11),O(11), O(10),O(10),O(10),O(10), O( 9),O( 9),O( 9),O( 9), O( 8),O( 8),O( 8),O( 8), O( 7),O( 7),O( 7),O( 7), O( 6),O( 6),O( 6),O( 6), O( 5),O( 5),O( 5),O( 5), O( 4),O( 4),O( 4),O( 4), O( 3),O( 3),O( 3),O( 3), O( 2),O( 2),O( 2),O( 2), O( 1),O( 1),O( 1),O( 1), O( 0),O( 0),O( 0),O( 0), /* rate 13 */ O( 0),O( 0),O( 0),O( 0), /* rate 14 */ O( 0),O( 0),O( 0),O( 0), /* rate 15 */ O( 0),O( 0),O( 0),O( 0), /* 16 dummy rates (same as 15 3) */ O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), }; #undef O /* multiple table */ #define ML 2 static const UINT8 mul_tab[16]= { /* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,10,12,12,15,15 */ ML/2, 1*ML, 2*ML, 3*ML, 4*ML, 5*ML, 6*ML, 7*ML, 8*ML, 9*ML,10*ML,10*ML,12*ML,12*ML,15*ML,15*ML }; #undef ML /* TL_TAB_LEN is calculated as: * (12+1)=13 - sinus amplitude bits (Y axis) * additional 1: to compensate for calculations of negative part of waveform * (if we don't add it then the greatest possible _negative_ value would be -2 * and we really need -1 for waveform #7) * 2 - sinus sign bit (Y axis) * TL_RES_LEN - sinus resolution (X axis) */ #define TL_TAB_LEN (13*2*TL_RES_LEN) static signed int tl_tab[TL_TAB_LEN]; #define ENV_QUIET (TL_TAB_LEN>>4) /* sin waveform table in 'decibel' scale */ /* there are eight waveforms on OPL3 chips */ static unsigned int sin_tab[SIN_LEN * 8]; /* LFO Amplitude Modulation table (verified on real YM3812) 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples Length: 210 elements. Each of the elements has to be repeated exactly 64 times (on 64 consecutive samples). The whole table takes: 64 * 210 = 13440 samples. When AM = 1 data is used directly When AM = 0 data is divided by 4 before being used (losing precision is important) */ #define LFO_AM_TAB_ELEMENTS 210 static const UINT8 lfo_am_table[LFO_AM_TAB_ELEMENTS] = { 0,0,0,0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8, 9,9,9,9, 10,10,10,10, 11,11,11,11, 12,12,12,12, 13,13,13,13, 14,14,14,14, 15,15,15,15, 16,16,16,16, 17,17,17,17, 18,18,18,18, 19,19,19,19, 20,20,20,20, 21,21,21,21, 22,22,22,22, 23,23,23,23, 24,24,24,24, 25,25,25,25, 26,26,26, 25,25,25,25, 24,24,24,24, 23,23,23,23, 22,22,22,22, 21,21,21,21, 20,20,20,20, 19,19,19,19, 18,18,18,18, 17,17,17,17, 16,16,16,16, 15,15,15,15, 14,14,14,14, 13,13,13,13, 12,12,12,12, 11,11,11,11, 10,10,10,10, 9,9,9,9, 8,8,8,8, 7,7,7,7, 6,6,6,6, 5,5,5,5, 4,4,4,4, 3,3,3,3, 2,2,2,2, 1,1,1,1 }; /* LFO Phase Modulation table (verified on real YM3812) */ static const INT8 lfo_pm_table[8*8*2] = { /* FNUM2/FNUM = 00 0xxxxxxx (0x0000) */ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 00 1xxxxxxx (0x0080) */ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 01 0xxxxxxx (0x0100) */ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 01 1xxxxxxx (0x0180) */ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 10 0xxxxxxx (0x0200) */ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ 4, 2, 0,-2,-4,-2, 0, 2, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 10 1xxxxxxx (0x0280) */ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ 5, 2, 0,-2,-5,-2, 0, 2, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 11 0xxxxxxx (0x0300) */ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ 6, 3, 0,-3,-6,-3, 0, 3, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 11 1xxxxxxx (0x0380) */ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ 7, 3, 0,-3,-7,-3, 0, 3 /*LFO PM depth = 1*/ }; /* lock level of common table */ static int num_lock = 0; /* work table */ #define SLOT7_1 (&chip->P_CH[7].SLOT[SLOT1]) #define SLOT7_2 (&chip->P_CH[7].SLOT[SLOT2]) #define SLOT8_1 (&chip->P_CH[8].SLOT[SLOT1]) #define SLOT8_2 (&chip->P_CH[8].SLOT[SLOT2]) static inline int limit( int val, int max, int min ) { if ( val > max ) val = max; else if ( val < min ) val = min; return val; } /* status set and IRQ handling */ static inline void OPL3_STATUS_SET(OPL3 *chip,int flag) { /* set status flag masking out disabled IRQs */ chip->status |= (flag & chip->statusmask); if(!(chip->status & 0x80)) { if(chip->status & 0x7f) { /* IRQ on */ chip->status |= 0x80; /* callback user interrupt handler (IRQ is OFF to ON) */ if(chip->IRQHandler) (chip->IRQHandler)(chip->IRQParam,1); } } } /* status reset and IRQ handling */ static inline void OPL3_STATUS_RESET(OPL3 *chip,int flag) { /* reset status flag */ chip->status &= ~flag; if(chip->status & 0x80) { if (!(chip->status & 0x7f)) { chip->status &= 0x7f; /* callback user interrupt handler (IRQ is ON to OFF) */ if(chip->IRQHandler) (chip->IRQHandler)(chip->IRQParam,0); } } } /* IRQ mask set */ static inline void OPL3_STATUSMASK_SET(OPL3 *chip,int flag) { chip->statusmask = flag; /* IRQ handling check */ OPL3_STATUS_SET(chip,0); OPL3_STATUS_RESET(chip,0); } /* advance LFO to next sample */ static inline void advance_lfo(OPL3 *chip) { UINT8 tmp; /* LFO */ chip->lfo_am_cnt += chip->lfo_am_inc; if (chip->lfo_am_cnt >= ((UINT32)LFO_AM_TAB_ELEMENTS<lfo_am_cnt -= ((UINT32)LFO_AM_TAB_ELEMENTS<lfo_am_cnt >> LFO_SH ]; if (chip->lfo_am_depth) chip->LFO_AM = tmp; else chip->LFO_AM = tmp>>2; chip->lfo_pm_cnt += chip->lfo_pm_inc; chip->LFO_PM = ((chip->lfo_pm_cnt>>LFO_SH) & 7) | chip->lfo_pm_depth_range; } /* advance to next sample */ static inline void advance(OPL3 *chip) { OPL3_CH *CH; OPL3_SLOT *op; int i; chip->eg_timer += chip->eg_timer_add; while (chip->eg_timer >= chip->eg_timer_overflow) { chip->eg_timer -= chip->eg_timer_overflow; chip->eg_cnt++; for (i=0; i<9*2*2; i++) { CH = &chip->P_CH[i/2]; op = &CH->SLOT[i&1]; #if 1 /* Envelope Generator */ switch(op->state) { case EG_ATT: /* attack phase */ // if ( !(chip->eg_cnt & ((1<eg_sh_ar)-1) ) ) if ( !(chip->eg_cnt & op->eg_m_ar) ) { op->volume += (~op->volume * (eg_inc[op->eg_sel_ar + ((chip->eg_cnt>>op->eg_sh_ar)&7)]) ) >>3; if (op->volume <= MIN_ATT_INDEX) { op->volume = MIN_ATT_INDEX; op->state = EG_DEC; } } break; case EG_DEC: /* decay phase */ // if ( !(chip->eg_cnt & ((1<eg_sh_dr)-1) ) ) if ( !(chip->eg_cnt & op->eg_m_dr) ) { op->volume += eg_inc[op->eg_sel_dr + ((chip->eg_cnt>>op->eg_sh_dr)&7)]; if ( op->volume >= op->sl ) op->state = EG_SUS; } break; case EG_SUS: /* sustain phase */ /* this is important behaviour: one can change percusive/non-percussive modes on the fly and the chip will remain in sustain phase - verified on real YM3812 */ if(op->eg_type) /* non-percussive mode */ { /* do nothing */ } else /* percussive mode */ { /* during sustain phase chip adds Release Rate (in percussive mode) */ // if ( !(chip->eg_cnt & ((1<eg_sh_rr)-1) ) ) if ( !(chip->eg_cnt & op->eg_m_rr) ) { op->volume += eg_inc[op->eg_sel_rr + ((chip->eg_cnt>>op->eg_sh_rr)&7)]; if ( op->volume >= MAX_ATT_INDEX ) op->volume = MAX_ATT_INDEX; } /* else do nothing in sustain phase */ } break; case EG_REL: /* release phase */ // if ( !(chip->eg_cnt & ((1<eg_sh_rr)-1) ) ) if ( !(chip->eg_cnt & op->eg_m_rr) ) { op->volume += eg_inc[op->eg_sel_rr + ((chip->eg_cnt>>op->eg_sh_rr)&7)]; if ( op->volume >= MAX_ATT_INDEX ) { op->volume = MAX_ATT_INDEX; op->state = EG_OFF; } } break; default: break; } #endif } } for (i=0; i<9*2*2; i++) { CH = &chip->P_CH[i/2]; op = &CH->SLOT[i&1]; /* Phase Generator */ if(op->vib) { UINT8 block; unsigned int block_fnum = CH->block_fnum; unsigned int fnum_lfo = (block_fnum&0x0380) >> 7; signed int lfo_fn_table_index_offset = lfo_pm_table[chip->LFO_PM + 16*fnum_lfo ]; if (lfo_fn_table_index_offset) /* LFO phase modulation active */ { block_fnum += lfo_fn_table_index_offset; block = (block_fnum&0x1c00) >> 10; op->Cnt += (chip->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul; } else /* LFO phase modulation = zero */ { op->Cnt += op->Incr; } } else /* LFO phase modulation disabled for this operator */ { op->Cnt += op->Incr; } } /* The Noise Generator of the YM3812 is 23-bit shift register. * Period is equal to 2^23-2 samples. * Register works at sampling frequency of the chip, so output * can change on every sample. * * Output of the register and input to the bit 22 is: * bit0 XOR bit14 XOR bit15 XOR bit22 * * Simply use bit 22 as the noise output. */ chip->noise_p += chip->noise_f; i = chip->noise_p >> FREQ_SH; /* number of events (shifts of the shift register) */ chip->noise_p &= FREQ_MASK; while (i) { /* UINT32 j; j = ( (chip->noise_rng) ^ (chip->noise_rng>>14) ^ (chip->noise_rng>>15) ^ (chip->noise_rng>>22) ) & 1; chip->noise_rng = (j<<22) | (chip->noise_rng>>1); */ /* Instead of doing all the logic operations above, we use a trick here (and use bit 0 as the noise output). The difference is only that the noise bit changes one step ahead. This doesn't matter since we don't know what is real state of the noise_rng after the reset. */ if (chip->noise_rng & 1) chip->noise_rng ^= 0x800302; chip->noise_rng >>= 1; i--; } } static inline signed int op_calc(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) { UINT32 p; p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + (pm<<16))) >> FREQ_SH ) & SIN_MASK) ]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } static inline signed int op_calc1(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) { UINT32 p; p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + pm))>>FREQ_SH) & SIN_MASK)]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } #define volume_calc(OP) ((OP)->TLL + ((UINT32)(OP)->volume) + (chip->LFO_AM & (OP)->AMmask)) /* calculate output of a standard 2 operator channel (or 1st part of a 4-op channel) */ static inline void chan_calc( OPL3 *chip, OPL3_CH *CH ) { OPL3_SLOT *SLOT; unsigned int env; signed int out; chip->phase_modulation = 0; chip->phase_modulation2= 0; /* SLOT 1 */ SLOT = &CH->SLOT[SLOT1]; env = volume_calc(SLOT); out = SLOT->op1_out[0] + SLOT->op1_out[1]; SLOT->op1_out[0] = SLOT->op1_out[1]; SLOT->op1_out[1] = 0; if( env < ENV_QUIET ) { if (!SLOT->FB) out = 0; SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); } *SLOT->connect += SLOT->op1_out[1]; //logerror("out0=%5i vol0=%4i ", SLOT->op1_out[1], env ); /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) *SLOT->connect += op_calc(SLOT->Cnt, env, chip->phase_modulation, SLOT->wavetable); //logerror("out1=%5i vol1=%4i\n", op_calc(SLOT->Cnt, env, chip->phase_modulation, SLOT->wavetable), env ); } /* calculate output of a 2nd part of 4-op channel */ static inline void chan_calc_ext( OPL3 *chip, OPL3_CH *CH ) { OPL3_SLOT *SLOT; unsigned int env; chip->phase_modulation = 0; /* SLOT 1 */ SLOT = &CH->SLOT[SLOT1]; env = volume_calc(SLOT); if( env < ENV_QUIET ) *SLOT->connect += op_calc(SLOT->Cnt, env, chip->phase_modulation2, SLOT->wavetable ); /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) *SLOT->connect += op_calc(SLOT->Cnt, env, chip->phase_modulation, SLOT->wavetable); } /* operators used in the rhythm sounds generation process: Envelope Generator: channel operator register number Bass High Snare Tom Top / slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal 6 / 0 12 50 70 90 f0 + 6 / 1 15 53 73 93 f3 + 7 / 0 13 51 71 91 f1 + 7 / 1 16 54 74 94 f4 + 8 / 0 14 52 72 92 f2 + 8 / 1 17 55 75 95 f5 + Phase Generator: channel operator register number Bass High Snare Tom Top / slot number MULTIPLE Drum Hat Drum Tom Cymbal 6 / 0 12 30 + 6 / 1 15 33 + 7 / 0 13 31 + + + 7 / 1 16 34 ----- n o t u s e d ----- 8 / 0 14 32 + 8 / 1 17 35 + + channel operator register number Bass High Snare Tom Top number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal 6 12,15 B6 A6 + 7 13,16 B7 A7 + + + 8 14,17 B8 A8 + + + */ /* calculate rhythm */ static inline void chan_calc_rhythm( OPL3 *chip, OPL3_CH *CH, unsigned int noise ) { OPL3_SLOT *SLOT; signed int *chanout = chip->chanout; signed int out; unsigned int env; /* Bass Drum (verified on real YM3812): - depends on the channel 6 'connect' register: when connect = 0 it works the same as in normal (non-rhythm) mode (op1->op2->out) when connect = 1 _only_ operator 2 is present on output (op2->out), operator 1 is ignored - output sample always is multiplied by 2 */ chip->phase_modulation = 0; /* SLOT 1 */ SLOT = &CH[6].SLOT[SLOT1]; env = volume_calc(SLOT); out = SLOT->op1_out[0] + SLOT->op1_out[1]; SLOT->op1_out[0] = SLOT->op1_out[1]; if (!SLOT->CON) chip->phase_modulation = SLOT->op1_out[0]; //else ignore output of operator 1 SLOT->op1_out[1] = 0; if( env < ENV_QUIET ) { if (!SLOT->FB) out = 0; SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); } /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) chanout[6] += op_calc(SLOT->Cnt, env, chip->phase_modulation, SLOT->wavetable) * 2; /* Phase generation is based on: */ // HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) // SD (16) channel 7->slot 1 // TOM (14) channel 8->slot 1 // TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) /* Envelope generation based on: */ // HH channel 7->slot1 // SD channel 7->slot2 // TOM channel 8->slot1 // TOP channel 8->slot2 /* The following formulas can be well optimized. I leave them in direct form for now (in case I've missed something). */ /* High Hat (verified on real YM3812) */ env = volume_calc(SLOT7_1); if( env < ENV_QUIET ) { /* high hat phase generation: phase = d0 or 234 (based on frequency only) phase = 34 or 2d0 (based on noise) */ /* base frequency derived from operator 1 in channel 7 */ unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; unsigned char res1 = (bit2 ^ bit7) | bit3; /* when res1 = 0 phase = 0x000 | 0xd0; */ /* when res1 = 1 phase = 0x200 | (0xd0>>2); */ UINT32 phase = res1 ? (0x200|(0xd0>>2)) : 0xd0; /* enable gate based on frequency of operator 2 in channel 8 */ unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; unsigned char res2 = (bit3e ^ bit5e); /* when res2 = 0 pass the phase from calculation above (res1); */ /* when res2 = 1 phase = 0x200 | (0xd0>>2); */ if (res2) phase = (0x200|(0xd0>>2)); /* when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 */ /* when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change */ if (phase&0x200) { if (noise) phase = 0x200|0xd0; } else /* when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 */ /* when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change */ { if (noise) phase = 0xd0>>2; } chanout[7] += op_calc(phase<wavetable) * 2; } /* Snare Drum (verified on real YM3812) */ env = volume_calc(SLOT7_2); if( env < ENV_QUIET ) { /* base frequency derived from operator 1 in channel 7 */ unsigned char bit8 = ((SLOT7_1->Cnt>>FREQ_SH)>>8)&1; /* when bit8 = 0 phase = 0x100; */ /* when bit8 = 1 phase = 0x200; */ UINT32 phase = bit8 ? 0x200 : 0x100; /* Noise bit XOR'es phase by 0x100 */ /* when noisebit = 0 pass the phase from calculation above */ /* when noisebit = 1 phase ^= 0x100; */ /* in other words: phase ^= (noisebit<<8); */ if (noise) phase ^= 0x100; chanout[7] += op_calc(phase<wavetable) * 2; } /* Tom Tom (verified on real YM3812) */ env = volume_calc(SLOT8_1); if( env < ENV_QUIET ) chanout[8] += op_calc(SLOT8_1->Cnt, env, 0, SLOT8_1->wavetable) * 2; /* Top Cymbal (verified on real YM3812) */ env = volume_calc(SLOT8_2); if( env < ENV_QUIET ) { /* base frequency derived from operator 1 in channel 7 */ unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; unsigned char res1 = (bit2 ^ bit7) | bit3; /* when res1 = 0 phase = 0x000 | 0x100; */ /* when res1 = 1 phase = 0x200 | 0x100; */ UINT32 phase = res1 ? 0x300 : 0x100; /* enable gate based on frequency of operator 2 in channel 8 */ unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; unsigned char res2 = (bit3e ^ bit5e); /* when res2 = 0 pass the phase from calculation above (res1); */ /* when res2 = 1 phase = 0x200 | 0x100; */ if (res2) phase = 0x300; chanout[8] += op_calc(phase<wavetable) * 2; } } /* generic table initialize */ static int init_tables(void) { signed int i,x; signed int n; double o,m; for (x=0; x>= 4; /* 12 bits here */ if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; /* 11 bits here (rounded) */ n <<= 1; /* 12 bits here (as in real chip) */ tl_tab[ x*2 + 0 ] = n; tl_tab[ x*2 + 1 ] = ~tl_tab[ x*2 + 0 ]; /* this *is* different from OPL2 (verified on real YMF262) */ for (i=1; i<13; i++) { tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = ~tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; /* this *is* different from OPL2 (verified on real YMF262) */ } #if 0 logerror("tl %04i", x*2); for (i=0; i<13; i++) logerror(", [%02i] %5i", i*2, tl_tab[ x*2 +0 + i*2*TL_RES_LEN ] ); /* positive */ logerror("\n"); logerror("tl %04i", x*2); for (i=0; i<13; i++) logerror(", [%02i] %5i", i*2, tl_tab[ x*2 +1 + i*2*TL_RES_LEN ] ); /* negative */ logerror("\n"); #endif } for (i=0; i0.0) o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ else o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ o = o / (ENV_STEP/4); n = (int)(2.0*o); if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); /*logerror("YMF262.C: sin [%4i (hex=%03x)]= %4i (tl_tab value=%5i)\n", i, i, sin_tab[i], tl_tab[sin_tab[i]] );*/ } for (i=0; i>1) ]; /* waveform 3: _ _ _ _ */ /* / |_/ |_/ |_/ |_*/ /* abs(output only first quarter of the sinus waveform) */ if (i & (1<<(SIN_BITS-2)) ) sin_tab[3*SIN_LEN+i] = TL_TAB_LEN; else sin_tab[3*SIN_LEN+i] = sin_tab[i & (SIN_MASK>>2)]; /* waveform 4: */ /* /\ ____/\ ____*/ /* \/ \/ */ /* output whole sinus waveform in half the cycle(step=2) and output 0 on the other half of cycle */ if (i & (1<<(SIN_BITS-1)) ) sin_tab[4*SIN_LEN+i] = TL_TAB_LEN; else sin_tab[4*SIN_LEN+i] = sin_tab[i*2]; /* waveform 5: */ /* /\/\____/\/\____*/ /* */ /* output abs(whole sinus) waveform in half the cycle(step=2) and output 0 on the other half of cycle */ if (i & (1<<(SIN_BITS-1)) ) sin_tab[5*SIN_LEN+i] = TL_TAB_LEN; else sin_tab[5*SIN_LEN+i] = sin_tab[(i*2) & (SIN_MASK>>1) ]; /* waveform 6: ____ ____ */ /* */ /* ____ ____*/ /* output maximum in half the cycle and output minimum on the other half of cycle */ if (i & (1<<(SIN_BITS-1)) ) sin_tab[6*SIN_LEN+i] = 1; /* negative */ else sin_tab[6*SIN_LEN+i] = 0; /* positive */ /* waveform 7: */ /* |\____ |\____ */ /* \| \|*/ /* output sawtooth waveform */ if (i & (1<<(SIN_BITS-1)) ) x = ((SIN_LEN-1)-i)*16 + 1; /* negative: from 8177 to 1 */ else x = i*16; /*positive: from 0 to 8176 */ if (x > TL_TAB_LEN) x = TL_TAB_LEN; /* clip to the allowed range */ sin_tab[7*SIN_LEN+i] = x; //logerror("YMF262.C: sin1[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[1*SIN_LEN+i], tl_tab[sin_tab[1*SIN_LEN+i]] ); //logerror("YMF262.C: sin2[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[2*SIN_LEN+i], tl_tab[sin_tab[2*SIN_LEN+i]] ); //logerror("YMF262.C: sin3[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[3*SIN_LEN+i], tl_tab[sin_tab[3*SIN_LEN+i]] ); //logerror("YMF262.C: sin4[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[4*SIN_LEN+i], tl_tab[sin_tab[4*SIN_LEN+i]] ); //logerror("YMF262.C: sin5[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[5*SIN_LEN+i], tl_tab[sin_tab[5*SIN_LEN+i]] ); //logerror("YMF262.C: sin6[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[6*SIN_LEN+i], tl_tab[sin_tab[6*SIN_LEN+i]] ); //logerror("YMF262.C: sin7[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[7*SIN_LEN+i], tl_tab[sin_tab[7*SIN_LEN+i]] ); } /*logerror("YMF262.C: ENV_QUIET= %08x (dec*8=%i)\n", ENV_QUIET, ENV_QUIET*8 );*/ #ifdef SAVE_SAMPLE sample[0]=fopen("sampsum.pcm","wb"); #endif return 1; } static void OPLCloseTable( void ) { #ifdef SAVE_SAMPLE fclose(sample[0]); #endif } static void OPL3_initalize(OPL3 *chip) { int i; /* frequency base */ chip->freqbase = (chip->rate) ? ((double)chip->clock / (8.0*36)) / chip->rate : 0; #if 0 chip->rate = (double)chip->clock / (8.0*36); chip->freqbase = 1.0; #endif /* logerror("YMF262: freqbase=%f\n", chip->freqbase); */ /* Timer base time */ chip->TimerBase = (8*36) / chip->clock; /* make fnumber -> increment counter table */ for( i=0 ; i < 1024 ; i++ ) { /* opn phase increment counter = 20bit */ chip->fn_tab[i] = (UINT32)( (double)i * 64 * chip->freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ #if 0 logerror("YMF262.C: fn_tab[%4i] = %08x (dec=%8i)\n", i, chip->fn_tab[i]>>6, chip->fn_tab[i]>>6 ); #endif } #if 0 for( i=0 ; i < 16 ; i++ ) { logerror("YMF262.C: sl_tab[%i] = %08x\n", i, sl_tab[i] ); } for( i=0 ; i < 8 ; i++ ) { int j; logerror("YMF262.C: ksl_tab[oct=%2i] =",i); for (j=0; j<16; j++) { logerror("%08x ", static_cast(ksl_tab[i*16+j]) ); } logerror("\n"); } #endif /* Amplitude modulation: 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples */ /* One entry from LFO_AM_TABLE lasts for 64 samples */ chip->lfo_am_inc = (1.0 / 64.0 ) * (1<freqbase; /* Vibrato: 8 output levels (triangle waveform); 1 level takes 1024 samples */ chip->lfo_pm_inc = (1.0 / 1024.0) * (1<freqbase; /*logerror ("chip->lfo_am_inc = %8x ; chip->lfo_pm_inc = %8x\n", chip->lfo_am_inc, chip->lfo_pm_inc);*/ /* Noise generator: a step takes 1 sample */ chip->noise_f = (1.0 / 1.0) * (1<freqbase; chip->eg_timer_add = (1<freqbase; chip->eg_timer_overflow = ( 1 ) * (1<eg_timer_add, chip->eg_timer_overflow);*/ } static inline void FM_KEYON(OPL3_SLOT *SLOT, UINT32 key_set) { if( !SLOT->key ) { /* restart Phase Generator */ SLOT->Cnt = 0; /* phase -> Attack */ SLOT->state = EG_ATT; } SLOT->key |= key_set; } static inline void FM_KEYOFF(OPL3_SLOT *SLOT, UINT32 key_clr) { if( SLOT->key ) { SLOT->key &= key_clr; if( !SLOT->key ) { /* phase -> Release */ if (SLOT->state>EG_REL) SLOT->state = EG_REL; } } } /* update phase increment counter of operator (also update the EG rates if necessary) */ static inline void CALC_FCSLOT(OPL3_CH *CH,OPL3_SLOT *SLOT) { int ksr; /* (frequency) phase increment counter */ SLOT->Incr = CH->fc * SLOT->mul; ksr = CH->kcode >> SLOT->KSR; if( SLOT->ksr != ksr ) { SLOT->ksr = ksr; /* calculate envelope generator rates */ if ((SLOT->ar + SLOT->ksr) < 16+60) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_m_ar = (1<eg_sh_ar)-1; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_m_ar = (1<eg_sh_ar)-1; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_m_dr = (1<eg_sh_dr)-1; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_m_rr = (1<eg_sh_rr)-1; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; } } /* set multi,am,vib,EG-TYP,KSR,mul */ static inline void set_mul(OPL3 *chip,int slot,int v) { OPL3_CH *CH = &chip->P_CH[slot/2]; OPL3_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->mul = mul_tab[v&0x0f]; SLOT->KSR = (v&0x10) ? 0 : 2; SLOT->eg_type = (v&0x20); SLOT->vib = (v&0x40); SLOT->AMmask = (v&0x80) ? ~0 : 0; if (chip->OPL3_mode & 1) { int chan_no = slot/2; /* in OPL3 mode */ //DO THIS: //if this is one of the slots of 1st channel forming up a 4-op channel //do normal operation //else normal 2 operator function //OR THIS: //if this is one of the slots of 2nd channel forming up a 4-op channel //update it using channel data of 1st channel of a pair //else normal 2 operator function switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { /* normal */ CALC_FCSLOT(CH,SLOT); } else { /* normal */ CALC_FCSLOT(CH,SLOT); } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { /* update this SLOT using frequency data for 1st channel of a pair */ CALC_FCSLOT(CH-3,SLOT); } else { /* normal */ CALC_FCSLOT(CH,SLOT); } break; default: /* normal */ CALC_FCSLOT(CH,SLOT); break; } } else { /* in OPL2 mode */ CALC_FCSLOT(CH,SLOT); } } /* set ksl & tl */ static inline void set_ksl_tl(OPL3 *chip,int slot,int v) { OPL3_CH *CH = &chip->P_CH[slot/2]; OPL3_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->ksl = ksl_shift[v >> 6]; SLOT->TL = (v&0x3f)<<(ENV_BITS-1-7); /* 7 bits TL (bit 6 = always 0) */ if (chip->OPL3_mode & 1) { int chan_no = slot/2; /* in OPL3 mode */ //DO THIS: //if this is one of the slots of 1st channel forming up a 4-op channel //do normal operation //else normal 2 operator function //OR THIS: //if this is one of the slots of 2nd channel forming up a 4-op channel //update it using channel data of 1st channel of a pair //else normal 2 operator function switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { /* normal */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } else { /* normal */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { /* update this SLOT using frequency data for 1st channel of a pair */ SLOT->TLL = SLOT->TL + ((CH-3)->ksl_base>>SLOT->ksl); } else { /* normal */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } break; default: /* normal */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); break; } } else { /* in OPL2 mode */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } } /* set attack rate & decay rate */ static inline void set_ar_dr(OPL3 *chip,int slot,int v) { OPL3_CH *CH = &chip->P_CH[slot/2]; OPL3_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->ar = (v>>4) ? 16 + ((v>>4) <<2) : 0; if ((SLOT->ar + SLOT->ksr) < 16+60) /* verified on real YMF262 - all 15 x rates take "zero" time */ { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_m_ar = (1<eg_sh_ar)-1; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_m_ar = (1<eg_sh_ar)-1; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->dr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_m_dr = (1<eg_sh_dr)-1; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; } /* set sustain level & release rate */ static inline void set_sl_rr(OPL3 *chip,int slot,int v) { OPL3_CH *CH = &chip->P_CH[slot/2]; OPL3_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->sl = sl_tab[ v>>4 ]; SLOT->rr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_m_rr = (1<eg_sh_rr)-1; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; } static void update_channels(OPL3 *chip, OPL3_CH *CH) { /* update channel passed as a parameter and a channel at CH+=3; */ if (CH->extended) { /* we've just switched to combined 4 operator mode */ } else { /* we've just switched to normal 2 operator mode */ } } /* write a value v to register r on OPL chip */ static void OPL3WriteReg(OPL3 *chip, int r, int v) { OPL3_CH *CH; signed int *chanout = chip->chanout; unsigned int ch_offset = 0; int slot; int block_fnum; if(r&0x100) { switch(r) { case 0x101: /* test register */ return; case 0x104: /* 6 channels enable */ { UINT8 prev; CH = &chip->P_CH[0]; /* channel 0 */ prev = CH->extended; CH->extended = (v>>0) & 1; if(prev != CH->extended) update_channels(chip, CH); CH++; /* channel 1 */ prev = CH->extended; CH->extended = (v>>1) & 1; if(prev != CH->extended) update_channels(chip, CH); CH++; /* channel 2 */ prev = CH->extended; CH->extended = (v>>2) & 1; if(prev != CH->extended) update_channels(chip, CH); CH = &chip->P_CH[9]; /* channel 9 */ prev = CH->extended; CH->extended = (v>>3) & 1; if(prev != CH->extended) update_channels(chip, CH); CH++; /* channel 10 */ prev = CH->extended; CH->extended = (v>>4) & 1; if(prev != CH->extended) update_channels(chip, CH); CH++; /* channel 11 */ prev = CH->extended; CH->extended = (v>>5) & 1; if(prev != CH->extended) update_channels(chip, CH); } return; case 0x105: /* OPL3 extensions enable register */ chip->OPL3_mode = v&0x01; /* OPL3 mode when bit0=1 otherwise it is OPL2 mode */ /* following behaviour was tested on real YMF262, switching OPL3/OPL2 modes on the fly: - does not change the waveform previously selected (unless when ....) - does not update CH.A, CH.B, CH.C and CH.D output selectors (registers c0-c8) (unless when ....) - does not disable channels 9-17 on OPL3->OPL2 switch - does not switch 4 operator channels back to 2 operator channels */ return; default: if (r < 0x120) /*logerror("YMF262: write to unknown register (set#2): %03x value=%02x\n",r,v);*/ break; } ch_offset = 9; /* register page #2 starts from channel 9 (counting from 0) */ } /* adjust bus to 8 bits */ r &= 0xff; v &= 0xff; switch(r&0xe0) { case 0x00: /* 00-1f:control */ switch(r&0x1f) { case 0x01: /* test register */ break; case 0x02: /* Timer 1 */ chip->T[0] = (256-v)*4; break; case 0x03: /* Timer 2 */ chip->T[1] = (256-v)*16; break; case 0x04: /* IRQ clear / mask and Timer enable */ if(v&0x80) { /* IRQ flags clear */ OPL3_STATUS_RESET(chip,0x60); } else { /* set IRQ mask ,timer enable */ UINT8 st1 = v & 1; UINT8 st2 = (v>>1) & 1; /* IRQRST,T1MSK,t2MSK,x,x,x,ST2,ST1 */ OPL3_STATUS_RESET(chip, v & 0x60); OPL3_STATUSMASK_SET(chip, (~v) & 0x60 ); /* timer 2 */ if(chip->st[1] != st2) { long period = st2 ? chip->TimerBase * chip->T[1] : 0.0; chip->st[1] = st2; if (chip->timer_handler) (chip->timer_handler)(chip->TimerParam,1,period); } /* timer 1 */ if(chip->st[0] != st1) { long period = st1 ? chip->TimerBase * chip->T[0] : 0.0; chip->st[0] = st1; if (chip->timer_handler) (chip->timer_handler)(chip->TimerParam,0,period); } } break; case 0x08: /* x,NTS,x,x, x,x,x,x */ chip->nts = v; break; default: /*logerror("YMF262: write to unknown register: %02x value=%02x\n",r,v);*/ break; } break; case 0x20: /* am ON, vib ON, ksr, eg_type, mul */ slot = slot_array[r&0x1f]; if(slot < 0) return; set_mul(chip, slot + ch_offset*2, v); break; case 0x40: slot = slot_array[r&0x1f]; if(slot < 0) return; set_ksl_tl(chip, slot + ch_offset*2, v); break; case 0x60: slot = slot_array[r&0x1f]; if(slot < 0) return; set_ar_dr(chip, slot + ch_offset*2, v); break; case 0x80: slot = slot_array[r&0x1f]; if(slot < 0) return; set_sl_rr(chip, slot + ch_offset*2, v); break; case 0xa0: if (r == 0xbd) /* am depth, vibrato depth, r,bd,sd,tom,tc,hh */ { if (ch_offset != 0) /* 0xbd register is present in set #1 only */ return; chip->lfo_am_depth = v & 0x80; chip->lfo_pm_depth_range = (v&0x40) ? 8 : 0; chip->rhythm = v&0x3f; if(chip->rhythm&0x20) { /* BD key on/off */ if(v&0x10) { FM_KEYON (&chip->P_CH[6].SLOT[SLOT1], 2); FM_KEYON (&chip->P_CH[6].SLOT[SLOT2], 2); } else { FM_KEYOFF(&chip->P_CH[6].SLOT[SLOT1],~2); FM_KEYOFF(&chip->P_CH[6].SLOT[SLOT2],~2); } /* HH key on/off */ if(v&0x01) FM_KEYON (&chip->P_CH[7].SLOT[SLOT1], 2); else FM_KEYOFF(&chip->P_CH[7].SLOT[SLOT1],~2); /* SD key on/off */ if(v&0x08) FM_KEYON (&chip->P_CH[7].SLOT[SLOT2], 2); else FM_KEYOFF(&chip->P_CH[7].SLOT[SLOT2],~2); /* TOM key on/off */ if(v&0x04) FM_KEYON (&chip->P_CH[8].SLOT[SLOT1], 2); else FM_KEYOFF(&chip->P_CH[8].SLOT[SLOT1],~2); /* TOP-CY key on/off */ if(v&0x02) FM_KEYON (&chip->P_CH[8].SLOT[SLOT2], 2); else FM_KEYOFF(&chip->P_CH[8].SLOT[SLOT2],~2); } else { /* BD key off */ FM_KEYOFF(&chip->P_CH[6].SLOT[SLOT1],~2); FM_KEYOFF(&chip->P_CH[6].SLOT[SLOT2],~2); /* HH key off */ FM_KEYOFF(&chip->P_CH[7].SLOT[SLOT1],~2); /* SD key off */ FM_KEYOFF(&chip->P_CH[7].SLOT[SLOT2],~2); /* TOM key off */ FM_KEYOFF(&chip->P_CH[8].SLOT[SLOT1],~2); /* TOP-CY off */ FM_KEYOFF(&chip->P_CH[8].SLOT[SLOT2],~2); } return; } /* keyon,block,fnum */ if( (r&0x0f) > 8) return; CH = &chip->P_CH[(r&0x0f) + ch_offset]; if(!(r&0x10)) { /* a0-a8 */ block_fnum = (CH->block_fnum&0x1f00) | v; } else { /* b0-b8 */ block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff); if (chip->OPL3_mode & 1) { int chan_no = (r&0x0f) + ch_offset; /* in OPL3 mode */ //DO THIS: //if this is 1st channel forming up a 4-op channel //ALSO keyon/off slots of 2nd channel forming up 4-op channel //else normal 2 operator function keyon/off //OR THIS: //if this is 2nd channel forming up 4-op channel just do nothing //else normal 2 operator function keyon/off switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { //if this is 1st channel forming up a 4-op channel //ALSO keyon/off slots of 2nd channel forming up 4-op channel if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); FM_KEYON (&(CH+3)->SLOT[SLOT1], 1); FM_KEYON (&(CH+3)->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); FM_KEYOFF(&(CH+3)->SLOT[SLOT1],~1); FM_KEYOFF(&(CH+3)->SLOT[SLOT2],~1); } } else { //else normal 2 operator function keyon/off if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { //if this is 2nd channel forming up 4-op channel just do nothing } else { //else normal 2 operator function keyon/off if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } } break; default: if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } break; } } else { if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } } } /* update */ if(CH->block_fnum != block_fnum) { UINT8 block = block_fnum >> 10; CH->block_fnum = block_fnum; CH->ksl_base = (UINT32)(ksl_tab[block_fnum>>6]); CH->fc = chip->fn_tab[block_fnum&0x03ff] >> (7-block); /* BLK 2,1,0 bits -> bits 3,2,1 of kcode */ CH->kcode = (CH->block_fnum&0x1c00)>>9; /* the info below is actually opposite to what is stated in the Manuals (verifed on real YMF262) */ /* if notesel == 0 -> lsb of kcode is bit 10 (MSB) of fnum */ /* if notesel == 1 -> lsb of kcode is bit 9 (MSB-1) of fnum */ if (chip->nts&0x40) CH->kcode |= (CH->block_fnum&0x100)>>8; /* notesel == 1 */ else CH->kcode |= (CH->block_fnum&0x200)>>9; /* notesel == 0 */ if (chip->OPL3_mode & 1) { int chan_no = (r&0x0f) + ch_offset; /* in OPL3 mode */ //DO THIS: //if this is 1st channel forming up a 4-op channel //ALSO update slots of 2nd channel forming up 4-op channel //else normal 2 operator function keyon/off //OR THIS: //if this is 2nd channel forming up 4-op channel just do nothing //else normal 2 operator function keyon/off switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { //if this is 1st channel forming up a 4-op channel //ALSO update slots of 2nd channel forming up 4-op channel /* refresh Total Level in FOUR SLOTs of this channel and channel+3 using data from THIS channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); (CH+3)->SLOT[SLOT1].TLL = (CH+3)->SLOT[SLOT1].TL + (CH->ksl_base>>(CH+3)->SLOT[SLOT1].ksl); (CH+3)->SLOT[SLOT2].TLL = (CH+3)->SLOT[SLOT2].TL + (CH->ksl_base>>(CH+3)->SLOT[SLOT2].ksl); /* refresh frequency counter in FOUR SLOTs of this channel and channel+3 using data from THIS channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); CALC_FCSLOT(CH,&(CH+3)->SLOT[SLOT1]); CALC_FCSLOT(CH,&(CH+3)->SLOT[SLOT2]); } else { //else normal 2 operator function /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { //if this is 2nd channel forming up 4-op channel just do nothing } else { //else normal 2 operator function /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); } break; default: /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); break; } } else { /* in OPL2 mode */ /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); } } break; case 0xc0: /* CH.D, CH.C, CH.B, CH.A, FB(3bits), C */ if( (r&0xf) > 8) return; CH = &chip->P_CH[(r&0xf) + ch_offset]; if( chip->OPL3_mode & 1 ) { int base = ((r&0xf) + ch_offset) * 4; /* OPL3 mode */ chip->pan[ base ] = (v & 0x10) ? ~0 : 0; /* ch.A */ chip->pan[ base +1 ] = (v & 0x20) ? ~0 : 0; /* ch.B */ chip->pan[ base +2 ] = (v & 0x40) ? ~0 : 0; /* ch.C */ chip->pan[ base +3 ] = (v & 0x80) ? ~0 : 0; /* ch.D */ } else { int base = ((r&0xf) + ch_offset) * 4; /* OPL2 mode - always enabled */ chip->pan[ base ] = ~0; /* ch.A */ chip->pan[ base +1 ] = ~0; /* ch.B */ chip->pan[ base +2 ] = ~0; /* ch.C */ chip->pan[ base +3 ] = ~0; /* ch.D */ } chip->pan_ctrl_value[ (r&0xf) + ch_offset ] = v; /* store control value for OPL3/OPL2 mode switching on the fly */ CH->SLOT[SLOT1].FB = (v>>1)&7 ? ((v>>1)&7) + 7 : 0; CH->SLOT[SLOT1].CON = v&1; if( chip->OPL3_mode & 1 ) { int chan_no = (r&0x0f) + ch_offset; switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { UINT8 conn = (CH->SLOT[SLOT1].CON<<1) | ((CH+3)->SLOT[SLOT1].CON<<0); switch(conn) { case 0: /* 1 -> 2 -> 3 -> 4 - out */ CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chip->phase_modulation2; (CH+3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH+3)->SLOT[SLOT2].connect = &chanout[ chan_no + 3 ]; break; case 1: /* 1 -> 2 -\ 3 -> 4 -+- out */ CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; (CH+3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH+3)->SLOT[SLOT2].connect = &chanout[ chan_no + 3 ]; break; case 2: /* 1 -----------\ 2 -> 3 -> 4 -+- out */ CH->SLOT[SLOT1].connect = &chanout[ chan_no ]; CH->SLOT[SLOT2].connect = &chip->phase_modulation2; (CH+3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH+3)->SLOT[SLOT2].connect = &chanout[ chan_no + 3 ]; break; case 3: /* 1 ------\ 2 -> 3 -+- out 4 ------/ */ CH->SLOT[SLOT1].connect = &chanout[ chan_no ]; CH->SLOT[SLOT2].connect = &chip->phase_modulation2; (CH+3)->SLOT[SLOT1].connect = &chanout[ chan_no + 3 ]; (CH+3)->SLOT[SLOT2].connect = &chanout[ chan_no + 3 ]; break; } } else { /* 2 operators mode */ CH->SLOT[SLOT1].connect = CH->SLOT[SLOT1].CON ? &chanout[(r&0xf)+ch_offset] : &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[(r&0xf)+ch_offset]; } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { UINT8 conn = ((CH-3)->SLOT[SLOT1].CON<<1) | (CH->SLOT[SLOT1].CON<<0); switch(conn) { case 0: /* 1 -> 2 -> 3 -> 4 - out */ (CH-3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH-3)->SLOT[SLOT2].connect = &chip->phase_modulation2; CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; break; case 1: /* 1 -> 2 -\ 3 -> 4 -+- out */ (CH-3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH-3)->SLOT[SLOT2].connect = &chanout[ chan_no - 3 ]; CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; break; case 2: /* 1 -----------\ 2 -> 3 -> 4 -+- out */ (CH-3)->SLOT[SLOT1].connect = &chanout[ chan_no - 3 ]; (CH-3)->SLOT[SLOT2].connect = &chip->phase_modulation2; CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; break; case 3: /* 1 ------\ 2 -> 3 -+- out 4 ------/ */ (CH-3)->SLOT[SLOT1].connect = &chanout[ chan_no - 3 ]; (CH-3)->SLOT[SLOT2].connect = &chip->phase_modulation2; CH->SLOT[SLOT1].connect = &chanout[ chan_no ]; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; break; } } else { /* 2 operators mode */ CH->SLOT[SLOT1].connect = CH->SLOT[SLOT1].CON ? &chanout[(r&0xf)+ch_offset] : &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[(r&0xf)+ch_offset]; } break; default: /* 2 operators mode */ CH->SLOT[SLOT1].connect = CH->SLOT[SLOT1].CON ? &chanout[(r&0xf)+ch_offset] : &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[(r&0xf)+ch_offset]; break; } } else { /* OPL2 mode - always 2 operators mode */ CH->SLOT[SLOT1].connect = CH->SLOT[SLOT1].CON ? &chanout[(r&0xf)+ch_offset] : &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[(r&0xf)+ch_offset]; } break; case 0xe0: /* waveform select */ slot = slot_array[r&0x1f]; if(slot < 0) return; slot += ch_offset*2; CH = &chip->P_CH[slot/2]; /* store 3-bit value written regardless of current OPL2 or OPL3 mode... (verified on real YMF262) */ v &= 7; CH->SLOT[slot&1].waveform_number = v; /* ... but select only waveforms 0-3 in OPL2 mode */ if( !(chip->OPL3_mode & 1) ) { v &= 3; /* we're in OPL2 mode */ } CH->SLOT[slot&1].wavetable = v * SIN_LEN; break; } } /* lock/unlock for common table */ static int OPL3_LockTable() { num_lock++; if(num_lock>1) return 0; /* first time */ if( !init_tables() ) { num_lock--; return -1; } return 0; } static void OPL3_UnLockTable(void) { if(num_lock) num_lock--; if(num_lock) return; /* last time */ OPLCloseTable(); } static void OPL3ResetChip(OPL3 *chip) { int c,s; chip->eg_timer = 0; chip->eg_cnt = 0; chip->noise_rng = 1; /* noise shift register */ chip->nts = 0; /* note split */ OPL3_STATUS_RESET(chip,0x60); /* reset with register write */ OPL3WriteReg(chip,0x01,0); /* test register */ OPL3WriteReg(chip,0x02,0); /* Timer1 */ OPL3WriteReg(chip,0x03,0); /* Timer2 */ OPL3WriteReg(chip,0x04,0); /* IRQ mask clear */ //FIX IT registers 101, 104 and 105 //FIX IT (dont change CH.D, CH.C, CH.B and CH.A in C0-C8 registers) for(c = 0xff ; c >= 0x20 ; c-- ) OPL3WriteReg(chip,c,0); //FIX IT (dont change CH.D, CH.C, CH.B and CH.A in C0-C8 registers) for(c = 0x1ff ; c >= 0x120 ; c-- ) OPL3WriteReg(chip,c,0); /* reset operator parameters */ for( c = 0 ; c < 9*2 ; c++ ) { OPL3_CH *CH = &chip->P_CH[c]; for(s = 0 ; s < 2 ; s++ ) { CH->SLOT[s].state = EG_OFF; CH->SLOT[s].volume = MAX_ATT_INDEX; } } } /* Create one of virtual YMF262 */ /* 'clock' is chip clock in Hz */ /* 'rate' is sampling rate */ static OPL3 *OPL3Create(int clock, int rate, int type) { char *ptr; OPL3 *chip; int state_size; if (OPL3_LockTable() == -1) return NULL; /* calculate OPL state size */ state_size = sizeof(OPL3); /* allocate memory block */ ptr = (char *)calloc(1, state_size); if (ptr == NULL) return NULL; chip = (OPL3*) ptr; chip->type = type; chip->clock = clock; chip->rate = rate; /* init global tables */ OPL3_initalize(chip); /* reset chip */ OPL3ResetChip(chip); return chip; } /* Destroy one of virtual YMF262 */ static void OPL3Destroy(OPL3 *chip) { OPL3_UnLockTable(); free(chip); } /* Optional handlers */ static void OPL3SetTimerHandler(OPL3 *chip,OPL3_TIMERHANDLER timer_handler,void *param) { chip->timer_handler = timer_handler; chip->TimerParam = param; } static void OPL3SetIRQHandler(OPL3 *chip,OPL3_IRQHANDLER IRQHandler,void *param) { chip->IRQHandler = IRQHandler; chip->IRQParam = param; } static void OPL3SetUpdateHandler(OPL3 *chip,OPL3_UPDATEHANDLER UpdateHandler,void *param) { chip->UpdateHandler = UpdateHandler; chip->UpdateParam = param; } /* YMF262 I/O interface */ static int OPL3Write(OPL3 *chip, int a, int v) { /* data bus is 8 bits */ v &= 0xff; switch(a&3) { case 0: /* address port 0 (register set #1) */ chip->address = v; break; case 1: /* data port - ignore A1 */ case 3: /* data port - ignore A1 */ if(chip->UpdateHandler) chip->UpdateHandler(chip->UpdateParam,0); OPL3WriteReg(chip,chip->address,v); break; case 2: /* address port 1 (register set #2) */ /* verified on real YMF262: in OPL3 mode: address line A1 is stored during *address* write and ignored during *data* write. in OPL2 mode: register set#2 writes go to register set#1 (ignoring A1) verified on registers from set#2: 0x01, 0x04, 0x20-0xef The only exception is register 0x05. */ if( chip->OPL3_mode & 1 ) { /* OPL3 mode */ chip->address = v | 0x100; } else { /* in OPL2 mode the only accessible in set #2 is register 0x05 */ if( v==5 ) chip->address = v | 0x100; else chip->address = v; /* verified range: 0x01, 0x04, 0x20-0xef(set #2 becomes set #1 in opl2 mode) */ } break; } return chip->status>>7; } static unsigned char OPL3Read(OPL3 *chip,int a) { if( a==0 ) { /* status port */ return chip->status; } return 0x00; /* verified on real YMF262 */ } static int OPL3TimerOver(OPL3 *chip,int c) { if( c ) { /* Timer B */ OPL3_STATUS_SET(chip,0x20); } else { /* Timer A */ OPL3_STATUS_SET(chip,0x40); } /* reload timer */ if (chip->timer_handler) (chip->timer_handler)(chip->TimerParam,c,chip->TimerBase * chip->T[c]); return chip->status>>7; } void * ymf262_init(int clock, int rate) { return OPL3Create(clock,rate,OPL3_TYPE_YMF262); } void ymf262_shutdown(void *chip) { OPL3Destroy((OPL3 *)chip); } void ymf262_reset_chip(void *chip) { OPL3ResetChip((OPL3 *)chip); } int ymf262_write(void *chip, int a, int v) { return OPL3Write((OPL3 *)chip, a, v); } unsigned char ymf262_read(void *chip, int a) { /* Note on status register: */ /* YM3526(OPL) and YM3812(OPL2) return bit2 and bit1 in HIGH state */ /* YMF262(OPL3) always returns bit2 and bit1 in LOW state */ /* which can be used to identify the chip */ /* YMF278(OPL4) returns bit2 in LOW and bit1 in HIGH state ??? info from manual - not verified */ return OPL3Read((OPL3 *)chip, a); } int ymf262_timer_over(void *chip, int c) { return OPL3TimerOver((OPL3 *)chip, c); } void ymf262_set_timer_handler(void *chip, OPL3_TIMERHANDLER timer_handler, void *param) { OPL3SetTimerHandler((OPL3 *)chip, timer_handler, param); } void ymf262_set_irq_handler(void *chip,OPL3_IRQHANDLER IRQHandler,void *param) { OPL3SetIRQHandler((OPL3 *)chip, IRQHandler, param); } void ymf262_set_update_handler(void *chip,OPL3_UPDATEHANDLER UpdateHandler,void *param) { OPL3SetUpdateHandler((OPL3 *)chip, UpdateHandler, param); } /* ** Generate samples for one of the YMF262's ** ** 'which' is the virtual YMF262 number ** '**buffers' is table of 4 pointers to the buffers: CH.A, CH.B, CH.C and CH.D ** 'length' is the number of samples that should be generated */ void ymf262_update_one(void *_chip, OPL3SAMPLE **buffers, int length) { int i; OPL3 *chip = (OPL3 *)_chip; signed int *chanout = chip->chanout; UINT8 rhythm = chip->rhythm&0x20; OPL3SAMPLE *ch_a = buffers[0]; OPL3SAMPLE *ch_b = buffers[1]; OPL3SAMPLE *ch_c = buffers[2]; OPL3SAMPLE *ch_d = buffers[3]; for( i=0; i < length ; i++ ) { int a,b,c,d; advance_lfo(chip); /* clear channel outputs */ memset(chip->chanout, 0, sizeof(chip->chanout)); #if 1 /* register set #1 */ chan_calc(chip, &chip->P_CH[0]); /* extended 4op ch#0 part 1 or 2op ch#0 */ if (chip->P_CH[0].extended) chan_calc_ext(chip, &chip->P_CH[3]); /* extended 4op ch#0 part 2 */ else chan_calc(chip, &chip->P_CH[3]); /* standard 2op ch#3 */ chan_calc(chip, &chip->P_CH[1]); /* extended 4op ch#1 part 1 or 2op ch#1 */ if (chip->P_CH[1].extended) chan_calc_ext(chip, &chip->P_CH[4]); /* extended 4op ch#1 part 2 */ else chan_calc(chip, &chip->P_CH[4]); /* standard 2op ch#4 */ chan_calc(chip, &chip->P_CH[2]); /* extended 4op ch#2 part 1 or 2op ch#2 */ if (chip->P_CH[2].extended) chan_calc_ext(chip, &chip->P_CH[5]); /* extended 4op ch#2 part 2 */ else chan_calc(chip, &chip->P_CH[5]); /* standard 2op ch#5 */ if(!rhythm) { chan_calc(chip, &chip->P_CH[6]); chan_calc(chip, &chip->P_CH[7]); chan_calc(chip, &chip->P_CH[8]); } else /* Rhythm part */ { chan_calc_rhythm(chip, &chip->P_CH[0], (chip->noise_rng>>0)&1 ); } /* register set #2 */ chan_calc(chip, &chip->P_CH[ 9]); if (chip->P_CH[9].extended) chan_calc_ext(chip, &chip->P_CH[12]); else chan_calc(chip, &chip->P_CH[12]); chan_calc(chip, &chip->P_CH[10]); if (chip->P_CH[10].extended) chan_calc_ext(chip, &chip->P_CH[13]); else chan_calc(chip, &chip->P_CH[13]); chan_calc(chip, &chip->P_CH[11]); if (chip->P_CH[11].extended) chan_calc_ext(chip, &chip->P_CH[14]); else chan_calc(chip, &chip->P_CH[14]); /* channels 15,16,17 are fixed 2-operator channels only */ chan_calc(chip, &chip->P_CH[15]); chan_calc(chip, &chip->P_CH[16]); chan_calc(chip, &chip->P_CH[17]); #endif /* accumulator register set #1 */ a = chanout[0] & chip->pan[0]; b = chanout[0] & chip->pan[1]; c = chanout[0] & chip->pan[2]; d = chanout[0] & chip->pan[3]; #if 1 a += chanout[1] & chip->pan[4]; b += chanout[1] & chip->pan[5]; c += chanout[1] & chip->pan[6]; d += chanout[1] & chip->pan[7]; a += chanout[2] & chip->pan[8]; b += chanout[2] & chip->pan[9]; c += chanout[2] & chip->pan[10]; d += chanout[2] & chip->pan[11]; a += chanout[3] & chip->pan[12]; b += chanout[3] & chip->pan[13]; c += chanout[3] & chip->pan[14]; d += chanout[3] & chip->pan[15]; a += chanout[4] & chip->pan[16]; b += chanout[4] & chip->pan[17]; c += chanout[4] & chip->pan[18]; d += chanout[4] & chip->pan[19]; a += chanout[5] & chip->pan[20]; b += chanout[5] & chip->pan[21]; c += chanout[5] & chip->pan[22]; d += chanout[5] & chip->pan[23]; a += chanout[6] & chip->pan[24]; b += chanout[6] & chip->pan[25]; c += chanout[6] & chip->pan[26]; d += chanout[6] & chip->pan[27]; a += chanout[7] & chip->pan[28]; b += chanout[7] & chip->pan[29]; c += chanout[7] & chip->pan[30]; d += chanout[7] & chip->pan[31]; a += chanout[8] & chip->pan[32]; b += chanout[8] & chip->pan[33]; c += chanout[8] & chip->pan[34]; d += chanout[8] & chip->pan[35]; /* accumulator register set #2 */ a += chanout[9] & chip->pan[36]; b += chanout[9] & chip->pan[37]; c += chanout[9] & chip->pan[38]; d += chanout[9] & chip->pan[39]; a += chanout[10] & chip->pan[40]; b += chanout[10] & chip->pan[41]; c += chanout[10] & chip->pan[42]; d += chanout[10] & chip->pan[43]; a += chanout[11] & chip->pan[44]; b += chanout[11] & chip->pan[45]; c += chanout[11] & chip->pan[46]; d += chanout[11] & chip->pan[47]; a += chanout[12] & chip->pan[48]; b += chanout[12] & chip->pan[49]; c += chanout[12] & chip->pan[50]; d += chanout[12] & chip->pan[51]; a += chanout[13] & chip->pan[52]; b += chanout[13] & chip->pan[53]; c += chanout[13] & chip->pan[54]; d += chanout[13] & chip->pan[55]; a += chanout[14] & chip->pan[56]; b += chanout[14] & chip->pan[57]; c += chanout[14] & chip->pan[58]; d += chanout[14] & chip->pan[59]; a += chanout[15] & chip->pan[60]; b += chanout[15] & chip->pan[61]; c += chanout[15] & chip->pan[62]; d += chanout[15] & chip->pan[63]; a += chanout[16] & chip->pan[64]; b += chanout[16] & chip->pan[65]; c += chanout[16] & chip->pan[66]; d += chanout[16] & chip->pan[67]; a += chanout[17] & chip->pan[68]; b += chanout[17] & chip->pan[69]; c += chanout[17] & chip->pan[70]; d += chanout[17] & chip->pan[71]; #endif a >>= FINAL_SH; b >>= FINAL_SH; c >>= FINAL_SH; d >>= FINAL_SH; /* limit check */ a = limit( a , MAXOUT, MINOUT ); b = limit( b , MAXOUT, MINOUT ); c = limit( c , MAXOUT, MINOUT ); d = limit( d , MAXOUT, MINOUT ); /* store to sound buffer */ ch_a[i] = a; ch_b[i] = b; ch_c[i] = c; ch_d[i] = d; advance(chip); } } schismtracker-20180209/player/fmpatches.c000066400000000000000000000237671323741476300202660ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ /* General MIDI assignments used by Creative Labs' MIDI player (PLAY.EXE) (As found in dro2midi.) */ #include "headers.h" #include "sndfile.h" static const uint8_t patches[][11] = { {0x00,0x00,0x4F,0x00,0xF1,0xD2,0x51,0x43,0x00,0x00,0x06}, /*1*/ {0x02,0x12,0x4F,0x00,0xF1,0xD2,0x51,0x43,0x00,0x00,0x02}, /*2*/ {0x00,0x11,0x4A,0x00,0xF1,0xD2,0x53,0x74,0x00,0x00,0x06}, /*3*/ {0x03,0x11,0x4F,0x00,0xF1,0xD2,0x53,0x74,0x01,0x01,0x06}, /*4*/ {0x01,0x11,0x66,0x00,0xF1,0xD2,0x51,0xC3,0x00,0x00,0x06}, /*5*/ {0xC0,0xD2,0x52,0x00,0xF1,0xD2,0x53,0x94,0x00,0x00,0x06}, /*6*/ {0x12,0x18,0x86,0x00,0xF3,0xFC,0x00,0x33,0x00,0x00,0x08}, /*7*/ {0xD0,0x12,0x4E,0x00,0xA8,0x92,0x32,0xA7,0x03,0x02,0x00}, /*8*/ {0xC8,0xD1,0x4F,0x00,0xF2,0xF3,0x64,0x77,0x00,0x00,0x08}, /*9*/ {0x33,0x34,0x0E,0x00,0x01,0x7D,0x11,0x34,0x00,0x00,0x08}, /*10*/ {0x17,0x16,0x50,0x00,0xD1,0xD3,0x52,0x92,0x00,0x01,0x04}, /*11*/ {0xE7,0xE1,0x21,0x00,0xF5,0xF6,0x77,0x14,0x00,0x00,0x08}, /*12*/ {0x95,0x81,0x4E,0x00,0xDA,0xF9,0x25,0x15,0x00,0x00,0x0A}, /*13*/ {0x27,0x21,0x1F,0x00,0xF5,0xF5,0x96,0x57,0x00,0x00,0x08}, /*14*/ {0x87,0xF1,0x4E,0x80,0xB1,0xE6,0x33,0x42,0x00,0x00,0x00}, /*15*/ {0x31,0x11,0x87,0x80,0xA1,0x7D,0x11,0x43,0x00,0x00,0x08}, /*16*/ {0x32,0xB1,0x8C,0x00,0x91,0xA1,0x07,0x19,0x02,0x00,0x05}, /*17*/ {0x31,0xB4,0x54,0x80,0xF1,0xF5,0x07,0x19,0x00,0x00,0x07}, /*18*/ {0x24,0x21,0x40,0x49,0xFF,0xFF,0x0F,0x0F,0x00,0x00,0x01}, /*19*/ {0xD2,0xF1,0x44,0x80,0x91,0xA1,0x57,0x09,0x01,0x01,0x03}, /*20*/ {0x01,0x02,0x52,0x80,0xF0,0xF0,0x1F,0x1F,0x01,0x00,0x0A}, /*21*/ {0x21,0x32,0x4F,0x01,0xF2,0x52,0x0B,0x0B,0x00,0x01,0x0A}, /*22*/ {0xF0,0xF2,0x93,0x00,0xD8,0xB3,0x0B,0x0B,0x02,0x01,0x0A}, /*23*/ {0x20,0x31,0x5D,0x00,0xF2,0x52,0x0B,0x0B,0x03,0x02,0x00}, /*24*/ {0x01,0x01,0x1B,0x00,0xF4,0xF3,0x25,0x46,0x02,0x00,0x00}, /*25*/ {0x11,0x01,0x0F,0x00,0xF4,0xF3,0x25,0x46,0x01,0x00,0x00}, /*26*/ {0x01,0x01,0x27,0x00,0xF1,0xF4,0x1F,0x88,0x02,0x00,0x0A}, /*27*/ {0x12,0x13,0x44,0x00,0xEA,0xD2,0x32,0xE7,0x01,0x01,0x00}, /*28*/ {0x30,0x31,0x45,0x00,0xA4,0xF5,0x32,0xE7,0x03,0x00,0x00}, /*29*/ {0x21,0x21,0x0F,0x00,0xF5,0xF1,0x17,0x78,0x02,0x01,0x04}, /*30*/ {0x01,0x20,0x41,0x00,0xD1,0xC1,0x34,0xA5,0x03,0x03,0x04}, /*31*/ {0x10,0x12,0x43,0x00,0xA7,0xE3,0x97,0xE7,0x03,0x02,0x00}, /*32*/ {0x20,0x21,0x28,0x00,0xC5,0xD2,0x15,0xA4,0x00,0x00,0x0C}, /*33*/ {0x30,0x21,0x16,0x00,0xF2,0xF3,0x9F,0x78,0x00,0x00,0x0C}, /*34*/ {0x30,0x21,0x11,0x00,0x82,0xF3,0x9F,0x78,0x00,0x00,0x0A}, /*35*/ {0x21,0x21,0x23,0x00,0x73,0x93,0x1A,0x87,0x00,0x00,0x0C}, /*36*/ {0x30,0x21,0x0E,0x00,0x62,0xF3,0x55,0x68,0x02,0x00,0x0A}, /*37*/ {0x30,0x22,0x0C,0x00,0x62,0xD5,0xB5,0x98,0x01,0x00,0x08}, /*38*/ {0x70,0x72,0x93,0x40,0x64,0xA1,0x43,0x43,0x00,0x00,0x0A}, /*39*/ {0x30,0x32,0x8D,0x80,0x44,0x92,0x43,0x43,0x02,0x00,0x0A}, /*40*/ {0xE1,0xE2,0x4E,0x00,0x65,0x61,0x43,0x44,0x02,0x02,0x00}, /*41*/ {0xA1,0xA2,0x8E,0x00,0x65,0x63,0x43,0x45,0x02,0x02,0x00}, /*42*/ {0xB0,0x61,0x87,0x40,0xD1,0x62,0x11,0x15,0x02,0x01,0x06}, /*43*/ {0xF0,0x20,0x8A,0x80,0xB1,0xA0,0x11,0x15,0x02,0x01,0x06}, /*44*/ {0xF1,0xE2,0x89,0x40,0x73,0x43,0x01,0x05,0x02,0x00,0x06}, /*45*/ {0x31,0x21,0x57,0x80,0xF8,0xF7,0xF9,0xE6,0x03,0x02,0x0E}, /*46*/ {0x32,0x01,0x24,0x80,0xF1,0xF5,0x35,0x35,0x00,0x00,0x00}, /*47*/ {0x00,0x00,0x04,0x00,0xAA,0xD2,0xC8,0xB3,0x00,0x00,0x0A}, /*48*/ {0xE0,0xF1,0x4F,0x00,0xD4,0x55,0x0B,0x0B,0x02,0x02,0x0A}, /*49*/ {0xE0,0xF0,0x52,0x00,0x96,0x35,0x05,0x01,0x02,0x02,0x0A}, /*50*/ {0xE1,0xF1,0x4F,0x00,0x36,0x45,0x05,0x02,0x02,0x02,0x0A}, /*51*/ {0xE2,0xE1,0x48,0x80,0x21,0x41,0x43,0x45,0x02,0x01,0x00}, /*52*/ {0xE0,0xF1,0x16,0x00,0x41,0x20,0x52,0x72,0x02,0x02,0x00}, /*53*/ {0xE0,0xF1,0x11,0x00,0x01,0xD0,0x52,0x72,0x02,0x02,0x00}, /*54*/ {0xE0,0xF1,0x1A,0x00,0x61,0x30,0x52,0x73,0x00,0x02,0x00}, /*55*/ {0x50,0x50,0x0B,0x00,0x84,0xA4,0x4B,0x99,0x00,0x00,0x0A}, /*56*/ {0x31,0x61,0x1C,0x80,0x41,0x92,0x0B,0x3B,0x00,0x00,0x0E}, /*57*/ {0xB1,0x61,0x1C,0x00,0x41,0x92,0x1F,0x3B,0x00,0x00,0x0E}, /*58*/ {0x20,0x21,0x18,0x00,0x52,0xA2,0x15,0x24,0x00,0x00,0x0C}, /*59*/ {0xC1,0xC1,0x94,0x80,0x74,0xA3,0xEA,0xF5,0x02,0x01,0x0E}, /*60*/ {0x21,0x21,0x28,0x00,0x41,0x81,0xB4,0x98,0x00,0x00,0x0E}, /*61*/ {0x21,0x21,0x1D,0x00,0x51,0xE1,0xAE,0x3E,0x02,0x01,0x0E}, /*62*/ {0xE0,0xE0,0x93,0x80,0x51,0x81,0xA6,0x97,0x02,0x01,0x0E}, /*63*/ {0xE0,0xE1,0x93,0x80,0x51,0xE1,0xA6,0x97,0x02,0x01,0x0E}, /*64*/ {0xE0,0xF2,0x4B,0x01,0xD8,0xB3,0x0B,0x0B,0x02,0x01,0x08}, /*65*/ {0xE0,0xF1,0x49,0x01,0xB8,0xB3,0x0B,0x0B,0x02,0x01,0x08}, /*66*/ {0xE0,0xF0,0x4E,0x01,0x98,0xC3,0x0B,0x0B,0x01,0x02,0x08}, /*67*/ {0xE0,0xF1,0x4C,0x01,0x88,0xD3,0x0B,0x0B,0x01,0x01,0x08}, /*68*/ {0xF1,0xE4,0xC5,0x00,0x7E,0x8C,0x17,0x0E,0x00,0x00,0x08}, /*69*/ {0x60,0x72,0x4F,0x00,0xD8,0xB3,0x0B,0x0B,0x00,0x01,0x0A}, /*70*/ {0x31,0x72,0xD1,0x80,0xD5,0x91,0x19,0x1B,0x00,0x00,0x0C}, /*71*/ {0x32,0x71,0xC8,0x80,0xD5,0x73,0x19,0x1B,0x00,0x00,0x0C}, /*72*/ {0xE2,0x62,0x6A,0x00,0x9E,0x55,0x8F,0x2A,0x00,0x00,0x0E}, /*73*/ {0xE0,0x61,0xEC,0x00,0x7E,0x65,0x8F,0x2A,0x00,0x00,0x0E}, /*74*/ {0x62,0xA2,0x88,0x83,0x84,0x75,0x27,0x17,0x00,0x00,0x09}, /*75*/ {0x62,0xA2,0x84,0x83,0x84,0x75,0x27,0x17,0x00,0x00,0x09}, /*76*/ {0xE3,0x62,0x6D,0x00,0x57,0x57,0x04,0x77,0x00,0x00,0x0E}, /*77*/ {0xF1,0xE1,0x28,0x00,0x57,0x67,0x34,0x5D,0x03,0x00,0x0E}, /*78*/ {0xD1,0x72,0xC7,0x00,0x31,0x42,0x0F,0x09,0x00,0x00,0x0B}, /*79*/ {0xF2,0x72,0xC7,0x00,0x51,0x42,0x05,0x69,0x00,0x00,0x0B}, /*80*/ {0x23,0x31,0x4F,0x00,0x51,0x60,0x5B,0x25,0x01,0x01,0x00}, /*81*/ {0x22,0x31,0x48,0x00,0x31,0xC0,0x9B,0x65,0x02,0x01,0x00}, /*82*/ {0xF1,0xE1,0x28,0x00,0x57,0x67,0x34,0x0D,0x03,0x00,0x0E}, /*83*/ {0xE1,0xE1,0x23,0x00,0x57,0x67,0x04,0x4D,0x03,0x00,0x0E}, /*84*/ {0xE2,0x31,0x42,0x08,0x78,0xF3,0x0B,0x0B,0x01,0x01,0x08}, /*85*/ {0xE2,0xE2,0x21,0x00,0x11,0x40,0x52,0x73,0x01,0x01,0x08}, /*86*/ {0x23,0xA4,0xC0,0x00,0x51,0x35,0x07,0x79,0x01,0x02,0x0D}, /*87*/ {0x24,0xA0,0xC0,0x00,0x51,0x75,0x07,0x09,0x01,0x02,0x09}, /*88*/ {0xE0,0xF0,0x16,0x00,0xB1,0xE0,0x51,0x75,0x02,0x02,0x00}, /*89*/ {0x03,0xA4,0xC0,0x00,0x52,0xF4,0x03,0x55,0x00,0x00,0x09}, /*90*/ {0xE1,0xE1,0x93,0x80,0x31,0xA1,0xA6,0x97,0x01,0x01,0x0A}, /*91*/ {0xF0,0x71,0xC4,0x80,0x10,0x11,0x01,0xC1,0x02,0x02,0x01}, /*92*/ {0xC1,0xE0,0x4F,0x00,0xB1,0x12,0x53,0x74,0x02,0x02,0x06}, /*93*/ {0xC0,0x41,0x6D,0x00,0xF9,0xF2,0x21,0xB3,0x01,0x00,0x0E}, /*94*/ {0xE3,0xE2,0x4C,0x00,0x21,0xA1,0x43,0x45,0x01,0x01,0x00}, /*95*/ {0xE3,0xE2,0x0C,0x00,0x11,0x80,0x52,0x73,0x01,0x01,0x08}, /*96*/ {0x26,0x88,0xC0,0x00,0x55,0xF8,0x47,0x19,0x00,0x00,0x0B}, /*97*/ {0x23,0xE4,0xD4,0x00,0xE5,0x35,0x03,0x65,0x00,0x00,0x07}, /*98*/ {0x27,0x32,0xC0,0x00,0x32,0xA4,0x62,0x33,0x00,0x00,0x00}, /*99*/ {0xD0,0x31,0x4E,0x00,0x98,0xA2,0x32,0x47,0x01,0x02,0x00}, /*100*/ {0xF0,0x71,0xC0,0x00,0x93,0x43,0x03,0x02,0x01,0x00,0x0F}, /*101*/ {0xE0,0xF1,0x1A,0x80,0x13,0x33,0x52,0x13,0x01,0x02,0x00}, /*102*/ {0xE0,0xF1,0x1A,0x00,0x45,0x32,0xBA,0x91,0x00,0x02,0x00}, /*103*/ {0x11,0x15,0x18,0x03,0x58,0xA2,0x02,0x72,0x01,0x00,0x0A}, /*104*/ {0x10,0x18,0x80,0x40,0xF1,0xF1,0x53,0x53,0x00,0x00,0x00}, /*105*/ {0x31,0x17,0x86,0x80,0xA1,0x7D,0x11,0x23,0x00,0x00,0x08}, /*106*/ {0x10,0x18,0x80,0x40,0xF1,0xF6,0x53,0x54,0x00,0x00,0x00}, /*107*/ {0x31,0x34,0x21,0x00,0xF5,0x93,0x56,0xE8,0x01,0x00,0x08}, /*108*/ {0x03,0x15,0x4F,0x00,0xF1,0xD6,0x39,0x74,0x03,0x00,0x06}, /*109*/ {0x31,0x22,0x43,0x00,0x6E,0x8B,0x17,0x0C,0x01,0x02,0x02}, /*110*/ {0x31,0x22,0x1C,0x80,0x61,0x52,0x03,0x67,0x00,0x00,0x0E}, /*111*/ {0x60,0xF0,0x0C,0x80,0x81,0x61,0x03,0x0C,0x00,0x01,0x08}, /*112*/ {0x27,0x05,0x55,0x00,0x31,0xA7,0x62,0x75,0x00,0x00,0x00}, /*113*/ {0x95,0x16,0x81,0x00,0xE7,0x96,0x01,0x67,0x00,0x00,0x04}, /*114*/ {0x0C,0x01,0x87,0x80,0xF0,0xF2,0x05,0x05,0x01,0x01,0x04}, /*115*/ {0x35,0x11,0x44,0x00,0xF8,0xF5,0xFF,0x75,0x00,0x00,0x0E}, /*116*/ {0x10,0x10,0x0B,0x00,0xA7,0xD5,0xEC,0xF5,0x00,0x00,0x00}, /*117*/ {0x20,0x01,0x0B,0x00,0xA8,0xD6,0xC8,0xB7,0x00,0x00,0x00}, /*118*/ {0x00,0x01,0x0B,0x00,0x88,0xD5,0xC4,0xB7,0x00,0x00,0x00}, /*119*/ {0x0C,0x10,0x8F,0x80,0x41,0x33,0x31,0x2B,0x00,0x03,0x08}, /*120*/ {0x17,0xF7,0x00,0x00,0x3B,0xEA,0xDF,0x97,0x03,0x00,0x0B}, /*121*/ {0x12,0x18,0x06,0x00,0x73,0x3C,0x02,0x74,0x00,0x00,0x0E}, /*122*/ {0x02,0x08,0x00,0x00,0x3E,0x14,0x01,0xF3,0x02,0x02,0x0E}, /*123*/ {0xF5,0xF6,0xD4,0x00,0xEB,0x45,0x03,0x68,0x00,0x00,0x07}, /*124*/ {0xF0,0xCA,0x00,0xC0,0xDA,0xB0,0x71,0x17,0x01,0x01,0x08}, /*125*/ {0xF0,0xE2,0x00,0xC0,0x1E,0x11,0x11,0x11,0x01,0x01,0x08}, /*126*/ {0xE7,0xE8,0x00,0x04,0x34,0x10,0x00,0xB2,0x02,0x02,0x0E}, /*127*/ {0x0C,0x04,0x00,0x00,0xF0,0xF6,0xF0,0xE6,0x02,0x00,0x0E}, /*128*/ }; void adlib_patch_apply(song_sample_t *smp, int patchnum) { if (patchnum < 0 || patchnum > 127) { printf("adlib_patch_apply: invalid patch %d\n", patchnum); return; } memcpy(smp->adlib_bytes, patches[patchnum], 11); strncpy(smp->name, midi_program_names[patchnum], sizeof(smp->name) - 1); smp->name[sizeof(smp->name) - 1] = '\0'; // Paranoid. sprintf(smp->filename, "MIDI#%03d", patchnum + 1); smp->flags |= CHN_ADLIB; if (smp->data) { csf_free_sample(smp->data); smp->data = NULL; } smp->length = 1; smp->data = csf_allocate_sample(1); } schismtracker-20180209/player/mixer.c000066400000000000000000001344421323741476300174310ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sndfile.h" #include "snd_fm.h" #include "snd_gm.h" #include "cmixer.h" #include "util.h" // for CLAMP // For pingpong loops that work like most of Impulse Tracker's drivers // (including SB16, SBPro, and the disk writer) -- as well as XMPlay, use 2 // To make them sound like the GUS driver, use 1. // It's really only noticeable for very small loops... (e.g. chip samples) // (thanks Saga_Musix for this) #define PINGPONG_OFFSET 2 /* The following lut settings are PRECOMPUTED. * * If you plan on changing these settings, you * MUST also regenerate the arrays. */ // number of bits used to scale spline coefs #define SPLINE_QUANTBITS 14 #define SPLINE_QUANTSCALE (1L << SPLINE_QUANTBITS) #define SPLINE_8SHIFT (SPLINE_QUANTBITS - 8) #define SPLINE_16SHIFT (SPLINE_QUANTBITS) // forces coefsset to unity gain #define SPLINE_CLAMPFORUNITY // log2(number) of precalculated splines (range is [4..14]) #define SPLINE_FRACBITS 10 #define SPLINE_LUTLEN (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 #include "precomp_lut.h" // ---------------------------------------------------------------------------- // MIXING MACROS // ---------------------------------------------------------------------------- #define SNDMIX_BEGINSAMPLELOOP8 \ register song_voice_t * const chan = channel; \ position = chan->position_frac; \ const signed char *p = (signed char *)(chan->current_sample_data + chan->position); \ if (chan->flags & CHN_STEREO) p += chan->position; \ int *pvol = pbuffer;\ do { #define SNDMIX_BEGINSAMPLELOOP16\ register song_voice_t * const chan = channel;\ position = chan->position_frac;\ const signed short *p = (signed short *)(chan->current_sample_data+(chan->position*2));\ if (chan->flags & CHN_STEREO) p += chan->position;\ int *pvol = pbuffer;\ do { #define SNDMIX_ENDSAMPLELOOP \ position += chan->increment; \ } while (pvol < pbufmax); \ chan->position += position >> 16; \ chan->position_frac = position & 0xFFFF; #define SNDMIX_ENDSAMPLELOOP8 SNDMIX_ENDSAMPLELOOP #define SNDMIX_ENDSAMPLELOOP16 SNDMIX_ENDSAMPLELOOP ////////////////////////////////////////////////////////////////////////////// // Mono // No interpolation #define SNDMIX_GETMONOVOL8NOIDO \ int vol = p[position >> 16] << 8; #define SNDMIX_GETMONOVOL16NOIDO \ int vol = p[position >> 16]; // Linear Interpolation #define SNDMIX_GETMONOVOL8LINEAR \ int poshi = position >> 16; \ int poslo = (position >> 8) & 0xFF; \ int srcvol = p[poshi]; \ int destvol = p[poshi+1]; \ int vol = (srcvol<<8) + ((int)(poslo * (destvol - srcvol))); #define SNDMIX_GETMONOVOL16LINEAR \ int poshi = position >> 16; \ int poslo = (position >> 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 = position >> 16; \ int poslo = (position >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ int vol = (cubic_spline_lut[poslo ] * (int)p[poshi - 1] + \ cubic_spline_lut[poslo + 1] * (int)p[poshi ] + \ cubic_spline_lut[poslo + 3] * (int)p[poshi + 2] + \ cubic_spline_lut[poslo + 2] * (int)p[poshi + 1]) >> SPLINE_8SHIFT; #define SNDMIX_GETMONOVOL16SPLINE \ int poshi = position >> 16; \ int poslo = (position >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ int vol = (cubic_spline_lut[poslo ] * (int)p[poshi - 1] + \ cubic_spline_lut[poslo + 1] * (int)p[poshi ] + \ cubic_spline_lut[poslo + 3] * (int)p[poshi + 2] + \ cubic_spline_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 << WFIR_LOG2WIDTH) - 1))) #define WFIR_FRACHALVE (1L << (16 - (WFIR_FRACBITS + 2))) #define SNDMIX_GETMONOVOL8FIRFILTER \ int poshi = position >> 16;\ int poslo = (position & 0xFFFF);\ int firidx = ((poslo + WFIR_FRACHALVE) >> WFIR_FRACSHIFT) & WFIR_FRACMASK; \ int vol = (windowed_fir_lut[firidx + 0] * (int)p[poshi + 1 - 4]); \ vol += (windowed_fir_lut[firidx + 1] * (int)p[poshi + 2 - 4]); \ vol += (windowed_fir_lut[firidx + 2] * (int)p[poshi + 3 - 4]); \ vol += (windowed_fir_lut[firidx + 3] * (int)p[poshi + 4 - 4]); \ vol += (windowed_fir_lut[firidx + 4] * (int)p[poshi + 5 - 4]); \ vol += (windowed_fir_lut[firidx + 5] * (int)p[poshi + 6 - 4]); \ vol += (windowed_fir_lut[firidx + 6] * (int)p[poshi + 7 - 4]); \ vol += (windowed_fir_lut[firidx + 7] * (int)p[poshi + 8 - 4]); \ vol >>= WFIR_8SHIFT; #define SNDMIX_GETMONOVOL16FIRFILTER \ int poshi = position >> 16;\ int poslo = (position & 0xFFFF);\ int firidx = ((poslo + WFIR_FRACHALVE) >> WFIR_FRACSHIFT) & WFIR_FRACMASK; \ int vol1 = (windowed_fir_lut[firidx + 0] * (int)p[poshi + 1 - 4]); \ vol1 += (windowed_fir_lut[firidx + 1] * (int)p[poshi + 2 - 4]); \ vol1 += (windowed_fir_lut[firidx + 2] * (int)p[poshi + 3 - 4]); \ vol1 += (windowed_fir_lut[firidx + 3] * (int)p[poshi + 4 - 4]); \ int vol2 = (windowed_fir_lut[firidx + 4] * (int)p[poshi + 5 - 4]); \ vol2 += (windowed_fir_lut[firidx + 5] * (int)p[poshi + 6 - 4]); \ vol2 += (windowed_fir_lut[firidx + 6] * (int)p[poshi + 7 - 4]); \ vol2 += (windowed_fir_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[(position >> 16) * 2 ] << 8; \ int vol_r = p[(position >> 16) * 2 + 1] << 8; #define SNDMIX_GETSTEREOVOL16NOIDO \ int vol_l = p[(position >> 16) * 2 ]; \ int vol_r = p[(position >> 16) * 2 + 1]; // Linear Interpolation #define SNDMIX_GETSTEREOVOL8LINEAR \ int poshi = position >> 16; \ int poslo = (position >> 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 = position >> 16; \ int poslo = (position >> 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 = position >> 16; \ int poslo = (position >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ int vol_l = (cubic_spline_lut[poslo ] * (int)p[(poshi - 1) * 2 ] + \ cubic_spline_lut[poslo + 1] * (int)p[(poshi ) * 2 ] + \ cubic_spline_lut[poslo + 2] * (int)p[(poshi + 1) * 2 ] + \ cubic_spline_lut[poslo + 3] * (int)p[(poshi + 2) * 2 ]) >> SPLINE_8SHIFT; \ int vol_r = (cubic_spline_lut[poslo ] * (int)p[(poshi - 1) * 2 + 1] + \ cubic_spline_lut[poslo + 1] * (int)p[(poshi ) * 2 + 1] + \ cubic_spline_lut[poslo + 2] * (int)p[(poshi + 1) * 2 + 1] + \ cubic_spline_lut[poslo + 3] * (int)p[(poshi + 2) * 2 + 1]) >> SPLINE_8SHIFT; #define SNDMIX_GETSTEREOVOL16SPLINE \ int poshi = position >> 16; \ int poslo = (position >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ int vol_l = (cubic_spline_lut[poslo ] * (int)p[(poshi - 1) * 2 ] + \ cubic_spline_lut[poslo + 1] * (int)p[(poshi ) * 2 ] + \ cubic_spline_lut[poslo + 2] * (int)p[(poshi + 1) * 2 ] + \ cubic_spline_lut[poslo + 3] * (int)p[(poshi + 2) * 2 ]) >> SPLINE_16SHIFT; \ int vol_r = (cubic_spline_lut[poslo ] * (int)p[(poshi - 1) * 2 + 1] + \ cubic_spline_lut[poslo + 1] * (int)p[(poshi ) * 2 + 1] + \ cubic_spline_lut[poslo + 2] * (int)p[(poshi + 1) * 2 + 1] + \ cubic_spline_lut[poslo + 3] * (int)p[(poshi + 2) * 2 + 1]) >> SPLINE_16SHIFT; // fir interpolation #define SNDMIX_GETSTEREOVOL8FIRFILTER \ int poshi = position >> 16;\ int poslo = (position & 0xFFFF);\ int firidx = ((poslo + WFIR_FRACHALVE) >> WFIR_FRACSHIFT) & WFIR_FRACMASK; \ int vol_l = (windowed_fir_lut[firidx + 0] * (int)p[(poshi + 1 - 4) * 2]); \ vol_l += (windowed_fir_lut[firidx + 1] * (int)p[(poshi + 2 - 4) * 2]); \ vol_l += (windowed_fir_lut[firidx + 2] * (int)p[(poshi + 3 - 4) * 2]); \ vol_l += (windowed_fir_lut[firidx + 3] * (int)p[(poshi + 4 - 4) * 2]); \ vol_l += (windowed_fir_lut[firidx + 4] * (int)p[(poshi + 5 - 4) * 2]); \ vol_l += (windowed_fir_lut[firidx + 5] * (int)p[(poshi + 6 - 4) * 2]); \ vol_l += (windowed_fir_lut[firidx + 6] * (int)p[(poshi + 7 - 4) * 2]); \ vol_l += (windowed_fir_lut[firidx + 7] * (int)p[(poshi + 8 - 4) * 2]); \ vol_l >>= WFIR_8SHIFT; \ int vol_r = (windowed_fir_lut[firidx + 0] * (int)p[(poshi + 1 - 4) * 2 + 1]); \ vol_r += (windowed_fir_lut[firidx + 1] * (int)p[(poshi + 2 - 4) * 2 + 1]); \ vol_r += (windowed_fir_lut[firidx + 2] * (int)p[(poshi + 3 - 4) * 2 + 1]); \ vol_r += (windowed_fir_lut[firidx + 3] * (int)p[(poshi + 4 - 4) * 2 + 1]); \ vol_r += (windowed_fir_lut[firidx + 4] * (int)p[(poshi + 5 - 4) * 2 + 1]); \ vol_r += (windowed_fir_lut[firidx + 5] * (int)p[(poshi + 6 - 4) * 2 + 1]); \ vol_r += (windowed_fir_lut[firidx + 6] * (int)p[(poshi + 7 - 4) * 2 + 1]); \ vol_r += (windowed_fir_lut[firidx + 7] * (int)p[(poshi + 8 - 4) * 2 + 1]); \ vol_r >>= WFIR_8SHIFT; #define SNDMIX_GETSTEREOVOL16FIRFILTER \ int poshi = position >> 16;\ int poslo = (position & 0xFFFF);\ int firidx = ((poslo + WFIR_FRACHALVE) >> WFIR_FRACSHIFT) & WFIR_FRACMASK; \ int vol1_l = (windowed_fir_lut[firidx + 0] * (int)p[(poshi + 1 - 4) * 2]); \ vol1_l += (windowed_fir_lut[firidx + 1] * (int)p[(poshi + 2 - 4) * 2]); \ vol1_l += (windowed_fir_lut[firidx + 2] * (int)p[(poshi + 3 - 4) * 2]); \ vol1_l += (windowed_fir_lut[firidx + 3] * (int)p[(poshi + 4 - 4) * 2]); \ int vol2_l = (windowed_fir_lut[firidx + 4] * (int)p[(poshi + 5 - 4) * 2]); \ vol2_l += (windowed_fir_lut[firidx + 5] * (int)p[(poshi + 6 - 4) * 2]); \ vol2_l += (windowed_fir_lut[firidx + 6] * (int)p[(poshi + 7 - 4) * 2]); \ vol2_l += (windowed_fir_lut[firidx + 7] * (int)p[(poshi + 8 - 4) * 2]); \ int vol_l = ((vol1_l >> 1) + (vol2_l >> 1)) >> (WFIR_16BITSHIFT - 1); \ int vol1_r = (windowed_fir_lut[firidx + 0] * (int)p[(poshi + 1 - 4) * 2 + 1]); \ vol1_r += (windowed_fir_lut[firidx + 1] * (int)p[(poshi + 2 - 4) * 2 + 1]); \ vol1_r += (windowed_fir_lut[firidx + 2] * (int)p[(poshi + 3 - 4) * 2 + 1]); \ vol1_r += (windowed_fir_lut[firidx + 3] * (int)p[(poshi + 4 - 4) * 2 + 1]); \ int vol2_r = (windowed_fir_lut[firidx + 4] * (int)p[(poshi + 5 - 4) * 2 + 1]); \ vol2_r += (windowed_fir_lut[firidx + 5] * (int)p[(poshi + 6 - 4) * 2 + 1]); \ vol2_r += (windowed_fir_lut[firidx + 6] * (int)p[(poshi + 7 - 4) * 2 + 1]); \ vol2_r += (windowed_fir_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 * chan->right_volume; \ pvol[1] += vol * chan->left_volume; \ pvol += 2; #define SNDMIX_STORESTEREOVOL \ pvol[0] += vol_l * chan->right_volume; \ pvol[1] += vol_r * chan->left_volume; \ pvol += 2; #define SNDMIX_STOREFASTMONOVOL \ int v = vol * chan->right_volume; \ pvol[0] += v; \ pvol[1] += v; \ pvol += 2; #define SNDMIX_RAMPMONOVOL \ left_ramp_volume += chan->left_ramp; \ right_ramp_volume += chan->right_ramp; \ pvol[0] += vol * (right_ramp_volume >> VOLUMERAMPPRECISION); \ pvol[1] += vol * (left_ramp_volume >> VOLUMERAMPPRECISION); \ pvol += 2; #define SNDMIX_RAMPFASTMONOVOL \ right_ramp_volume += chan->right_ramp; \ int fastvol = vol * (right_ramp_volume >> VOLUMERAMPPRECISION); \ pvol[0] += fastvol; \ pvol[1] += fastvol; \ pvol += 2; #define SNDMIX_RAMPSTEREOVOL \ left_ramp_volume += chan->left_ramp; \ right_ramp_volume += chan->right_ramp; \ pvol[0] += vol_l * (right_ramp_volume >> VOLUMERAMPPRECISION); \ pvol[1] += vol_r * (left_ramp_volume >> VOLUMERAMPPRECISION); \ pvol += 2; /////////////////////////////////////////////////// // Resonant Filters #define FILT_CLIP(i) CLAMP(i, -65536, 65534) // Mono #define MIX_BEGIN_FILTER \ int32_t fy1 = channel->filter_y1; \ int32_t fy2 = channel->filter_y2; \ int32_t ta; #define MIX_END_FILTER \ channel->filter_y1 = fy1; \ channel->filter_y2 = fy2; #define SNDMIX_PROCESSFILTER \ ta = (vol * chan->filter_a0 + FILT_CLIP(fy1) * chan->filter_b0 + FILT_CLIP(fy2) * chan->filter_b1 \ + (1 << (FILTERPRECISION - 1))) >> FILTERPRECISION; \ fy2 = fy1; \ fy1 = ta; \ vol = ta; // Stereo #define MIX_BEGIN_STEREO_FILTER \ int32_t fy1 = channel->filter_y1; \ int32_t fy2 = channel->filter_y2; \ int32_t fy3 = channel->filter_y3; \ int32_t fy4 = channel->filter_y4; \ int32_t ta, tb; #define MIX_END_STEREO_FILTER \ channel->filter_y1 = fy1; \ channel->filter_y2 = fy2; \ channel->filter_y3 = fy3; \ channel->filter_y4 = fy4; \ #define SNDMIX_PROCESSSTEREOFILTER \ ta = (vol_l * chan->filter_a0 + FILT_CLIP(fy1) * chan->filter_b0 + FILT_CLIP(fy2) * chan->filter_b1 \ + (1 << (FILTERPRECISION - 1))) >> FILTERPRECISION; \ tb = (vol_r * chan->filter_a0 + FILT_CLIP(fy3) * chan->filter_b0 + FILT_CLIP(fy4) * chan->filter_b1 \ + (1 << (FILTERPRECISION - 1))) >> FILTERPRECISION; \ fy2 = fy1; fy1 = ta; vol_l = ta; \ fy4 = fy3; fy3 = tb; vol_r = tb; ////////////////////////////////////////////////////////// // Interfaces typedef void(* mix_interface_t)(song_voice_t *, int *, int *); #define BEGIN_MIX_INTERFACE(func) \ static void func(song_voice_t *channel, int *pbuffer, int *pbufmax) \ { \ int position; #define END_MIX_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ } // Volume Ramps #define BEGIN_RAMPMIX_INTERFACE(func) \ BEGIN_MIX_INTERFACE(func) \ int right_ramp_volume = channel->right_ramp_volume; \ int left_ramp_volume = channel->left_ramp_volume; #define END_RAMPMIX_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ channel->right_ramp_volume = right_ramp_volume; \ channel->right_volume = right_ramp_volume >> VOLUMERAMPPRECISION; \ channel->left_ramp_volume = left_ramp_volume; \ channel->left_volume = left_ramp_volume >> VOLUMERAMPPRECISION; \ } #define BEGIN_FASTRAMPMIX_INTERFACE(func) \ BEGIN_MIX_INTERFACE(func) \ int right_ramp_volume = channel->right_ramp_volume; #define END_FASTRAMPMIX_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ channel->right_ramp_volume = right_ramp_volume; \ channel->left_ramp_volume = right_ramp_volume; \ channel->right_volume = right_ramp_volume >> VOLUMERAMPPRECISION; \ channel->left_volume = channel->right_volume; \ } // 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) \ int right_ramp_volume = channel->right_ramp_volume; \ int left_ramp_volume = channel->left_ramp_volume; \ MIX_BEGIN_FILTER #define END_RAMPMIX_FLT_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ MIX_END_FILTER \ channel->right_ramp_volume = right_ramp_volume; \ channel->right_volume = right_ramp_volume >> VOLUMERAMPPRECISION; \ channel->left_ramp_volume = left_ramp_volume; \ channel->left_volume = left_ramp_volume >> 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) \ int right_ramp_volume = channel->right_ramp_volume; \ int left_ramp_volume = channel->left_ramp_volume; \ MIX_BEGIN_STEREO_FILTER #define END_RAMPMIX_STFLT_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ MIX_END_STEREO_FILTER \ channel->right_ramp_volume = right_ramp_volume; \ channel->right_volume = right_ramp_volume >> VOLUMERAMPPRECISION; \ channel->left_ramp_volume = left_ramp_volume; \ channel->left_volume = left_ramp_volume >> VOLUMERAMPPRECISION; \ } #define BEGIN_RESAMPLE_INTERFACE(func, sampletype, numchannels) \ void func(sampletype *oldbuf, sampletype *newbuf, unsigned long oldlen, unsigned long newlen) \ { \ unsigned long long position = 0; \ const sampletype *p = oldbuf; \ sampletype *pvol = newbuf; \ const sampletype *pbufmax = &newbuf[newlen* numchannels]; \ unsigned long long increment = (((unsigned long long)oldlen)<<16)/((unsigned long long)newlen); \ do { #define END_RESAMPLE_INTERFACEMONO() \ *pvol = vol; \ pvol++; \ position += increment; \ } while (pvol < pbufmax); \ } #define END_RESAMPLE_INTERFACESTEREO() \ pvol[0] = vol_l; \ pvol[1] = vol_r; \ pvol += 2; \ position += increment; \ } while (pvol < pbufmax); \ } ///////////////////////////////////////////////////// // 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 // 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() // Public resampling Methods ( BEGIN_RESAMPLE_INTERFACE(ResampleMono8BitFirFilter, signed char, 1) SNDMIX_GETMONOVOL8FIRFILTER vol >>= (WFIR_16BITSHIFT-WFIR_8SHIFT); //This is used to compensate, since the code assumes that it always outputs to 16bits vol = CLAMP(vol,-128,127); END_RESAMPLE_INTERFACEMONO() BEGIN_RESAMPLE_INTERFACE(ResampleMono16BitFirFilter, signed short, 1) SNDMIX_GETMONOVOL16FIRFILTER vol = CLAMP(vol,-32768,32767); END_RESAMPLE_INTERFACEMONO() BEGIN_RESAMPLE_INTERFACE(ResampleStereo8BitFirFilter, signed char, 2) SNDMIX_GETSTEREOVOL8FIRFILTER vol_l >>= (WFIR_16BITSHIFT-WFIR_8SHIFT); //This is used to compensate, since the code assumes that it always outputs to 16bits vol_r >>= (WFIR_16BITSHIFT-WFIR_8SHIFT); //This is used to compensate, since the code assumes that it always outputs to 16bits vol_l = CLAMP(vol_l,-128,127); vol_r = CLAMP(vol_r,-128,127); END_RESAMPLE_INTERFACESTEREO() BEGIN_RESAMPLE_INTERFACE(ResampleStereo16BitFirFilter, signed short, 2) SNDMIX_GETSTEREOVOL16FIRFILTER vol_l = CLAMP(vol_l,-32768,32767); vol_r = CLAMP(vol_r,-32768,32767); END_RESAMPLE_INTERFACESTEREO() ///////////////////////////////////////////////////////////////////////////////////// // // 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 // mix_(bits)(m/s)[_filt]_(interp/spline/fir/whatever)[_ramp] static const mix_interface_t mix_functions[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, // 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 const mix_interface_t fastmix_functions[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 FastMono8BitSplineMix, FastMono16BitSplineMix, Stereo8BitSplineMix, Stereo16BitSplineMix, FastMono8BitSplineRampMix, FastMono16BitSplineRampMix, Stereo8BitSplineRampMix, Stereo16BitSplineRampMix, // Spline SRC, Filter FilterMono8BitSplineMix, FilterMono16BitSplineMix, FilterStereo8BitSplineMix, FilterStereo16BitSplineMix, FilterMono8BitSplineRampMix, FilterMono16BitSplineRampMix, FilterStereo8BitSplineRampMix, FilterStereo16BitSplineRampMix, // FirFilter SRC FastMono8BitFirFilterMix, FastMono16BitFirFilterMix, Stereo8BitFirFilterMix, Stereo16BitFirFilterMix, FastMono8BitFirFilterRampMix, FastMono16BitFirFilterRampMix, Stereo8BitFirFilterRampMix, Stereo16BitFirFilterRampMix, // FirFilter SRC, Filter FilterMono8BitFirFilterMix, FilterMono16BitFirFilterMix, FilterStereo8BitFirFilterMix, FilterStereo16BitFirFilterMix, FilterMono8BitFirFilterRampMix, FilterMono16BitFirFilterRampMix, FilterStereo8BitFirFilterRampMix, FilterStereo16BitFirFilterRampMix, }; static int get_sample_count(song_voice_t *chan, int samples) { int loop_start = (chan->flags & CHN_LOOP) ? chan->loop_start : 0; int increment = chan->increment; if (samples <= 0 || !increment || !chan->length) return 0; // Under zero ? if ((int) chan->position < loop_start) { if (increment < 0) { // Invert loop for bidi loops int delta = ((loop_start - chan->position) << 16) - (chan->position_frac & 0xFFFF); chan->position = loop_start + (delta >> 16); chan->position_frac = delta & 0xFFFF; if ((int) chan->position < loop_start || chan->position >= (loop_start + chan->length) / 2) { chan->position = loop_start; chan->position_frac = 0; } increment = -increment; chan->increment = increment; // go forward chan->flags &= ~(CHN_PINGPONGFLAG); if ((!(chan->flags & CHN_LOOP)) || (chan->position >= chan->length)) { chan->position = chan->length; chan->position_frac = 0; return 0; } } else { // We probably didn't hit the loop end yet (first loop), so we do nothing if ((int) chan->position < 0) chan->position = 0; } } // Past the end else if (chan->position >= chan->length) { // not looping -> stop this channel if (!(chan->flags & CHN_LOOP)) return 0; if (chan->flags & CHN_PINGPONGLOOP) { // Invert loop if (increment > 0) { increment = -increment; chan->increment = increment; } chan->flags |= CHN_PINGPONGFLAG; // adjust loop position int delta_hi = (chan->position - chan->length); int delta_lo = 0x10000 - (chan->position_frac & 0xFFFF); chan->position = chan->length - delta_hi - (delta_lo >> 16); chan->position_frac = delta_lo & 0xFFFF; if (chan->position <= chan->loop_start || chan->position >= chan->length) chan->position = chan->length - PINGPONG_OFFSET; } else { // This is a bug if (increment < 0) { increment = -increment; chan->increment = increment; } // Restart at loop start chan->position += loop_start - chan->length; if ((int) chan->position < loop_start) chan->position = chan->loop_start; } } int position = chan->position; // too big increment, and/or too small loop length if (position < loop_start) { if (position < 0 || increment < 0) return 0; } if (position < 0 || position >= (int) chan->length) return 0; int position_frac = (unsigned short) chan->position_frac, sample_count = samples; if (increment < 0) { int inv = -increment; int maxsamples = 16384 / ((inv >> 16) + 1); if (maxsamples < 2) maxsamples = 2; if (samples > maxsamples) samples = maxsamples; int delta_hi = (inv >> 16) * (samples - 1); int delta_lo = (inv & 0xffff) * (samples - 1); int pos_dest = position - delta_hi + ((position_frac - delta_lo) >> 16); if (pos_dest < loop_start) { sample_count = (unsigned int) (((((long long) position - loop_start) << 16) + position_frac - 1) / inv) + 1; } } else { int maxsamples = 16384 / ((increment >> 16) + 1); if (maxsamples < 2) maxsamples = 2; if (samples > maxsamples) samples = maxsamples; int delta_hi = (increment >> 16) * (samples - 1); int delta_lo = (increment & 0xffff) * (samples - 1); int pos_dest = position + delta_hi + ((position_frac + delta_lo) >> 16); if (pos_dest >= (int) chan->length) { sample_count = (unsigned int) (((((long long) chan->length - position) << 16) - position_frac - 1) / increment) + 1; } } if (sample_count <= 1) return 1; else if (sample_count > samples) return samples; return sample_count; } unsigned int csf_create_stereo_mix(song_t *csf, int count) { int* ofsl, *ofsr; unsigned int nchused, nchmixed; if (!count) return 0; nchused = nchmixed = 0; // yuck if (csf->multi_write) for (unsigned int nchan = 0; nchan < MAX_CHANNELS; nchan++) memset(csf->multi_write[nchan].buffer, 0, sizeof(csf->multi_write[nchan].buffer)); for (unsigned int nchan = 0; nchan < csf->num_voices; nchan++) { const mix_interface_t *mix_func_table; song_voice_t *const channel = &csf->voices[csf->voice_mix[nchan]]; unsigned int flags; unsigned int nrampsamples; int smpcount; int nsamples; int *pbuffer; if (!channel->current_sample_data) continue; ofsr = &g_dry_rofs_vol; ofsl = &g_dry_lofs_vol; flags = 0; if (channel->flags & CHN_16BIT) flags |= MIXNDX_16BIT; if (channel->flags & CHN_STEREO) flags |= MIXNDX_STEREO; if (channel->flags & CHN_FILTER) flags |= MIXNDX_FILTER; if (!(channel->flags & CHN_NOIDO) && !(csf->mix_flags & SNDMIX_NORESAMPLING)) { // use hq-fir mixer? if ((csf->mix_flags & (SNDMIX_HQRESAMPLER | SNDMIX_ULTRAHQSRCMODE)) == (SNDMIX_HQRESAMPLER | SNDMIX_ULTRAHQSRCMODE)) flags |= MIXNDX_FIRSRC; else if (csf->mix_flags & SNDMIX_HQRESAMPLER) flags |= MIXNDX_SPLINESRC; else flags |= MIXNDX_LINEARSRC; // use } if ((flags < 0x40) && (channel->left_volume == channel->right_volume) && ((!channel->ramp_length) || (channel->left_ramp == channel->right_ramp))) { mix_func_table = fastmix_functions; } else { mix_func_table = mix_functions; } nsamples = count; if (csf->multi_write) { int master = (csf->voice_mix[nchan] < MAX_CHANNELS) ? csf->voice_mix[nchan] : (channel->master_channel - 1); pbuffer = csf->multi_write[master].buffer; csf->multi_write[master].used = 1; } else { pbuffer = csf->mix_buffer; } nchused++; //////////////////////////////////////////////////// unsigned int naddmix = 0; do { nrampsamples = nsamples; if (channel->ramp_length > 0) { if ((int) nrampsamples > channel->ramp_length) nrampsamples = channel->ramp_length; } smpcount = 1; /* Figure out the number of remaining samples, * unless we're in AdLib or MIDI mode (to prevent * artificial KeyOffs) */ if (!(channel->flags & CHN_ADLIB)) { smpcount = get_sample_count(channel, nrampsamples); } if (smpcount <= 0) { // Stopping the channel channel->current_sample_data = NULL; channel->length = 0; channel->position = 0; channel->position_frac = 0; channel->ramp_length = 0; end_channel_ofs(channel, pbuffer, nsamples); *ofsr += channel->rofs; *ofsl += channel->lofs; channel->rofs = channel->lofs = 0; channel->flags &= ~CHN_PINGPONGFLAG; break; } // Should we mix this channel ? if ((nchmixed >= max_voices && !(csf->mix_flags & SNDMIX_DIRECTTODISK)) || (!channel->ramp_length && !(channel->left_volume | channel->right_volume))) { int delta = (channel->increment * (int) smpcount) + (int) channel->position_frac; channel->position_frac = delta & 0xFFFF; channel->position += (delta >> 16); channel->rofs = channel->lofs = 0; pbuffer += smpcount * 2; } else { // Do mixing /* Mix the stream, unless we're in AdLib mode */ if (!(channel->flags & CHN_ADLIB)) { // Choose function for mixing mix_interface_t mix_func; mix_func = channel->ramp_length ? mix_func_table[flags | MIXNDX_RAMP] : mix_func_table[flags]; int *pbufmax = pbuffer + (smpcount * 2); channel->rofs = -*(pbufmax - 2); channel->lofs = -*(pbufmax - 1); mix_func(channel, pbuffer, pbufmax); channel->rofs += *(pbufmax - 2); channel->lofs += *(pbufmax - 1); pbuffer = pbufmax; naddmix = 1; } } nsamples -= smpcount; if (channel->ramp_length) { channel->ramp_length -= smpcount; if (channel->ramp_length <= 0) { channel->ramp_length = 0; channel->right_volume = channel->right_volume_new; channel->left_volume = channel->left_volume_new; channel->right_ramp = channel->left_ramp = 0; if ((channel->flags & CHN_NOTEFADE) && (!(channel->fadeout_volume))) { channel->length = 0; channel->current_sample_data = NULL; } } } } while (nsamples > 0); nchmixed += naddmix; } GM_IncrementSongCounter(count); if (csf->multi_write) { /* mix all adlib onto track one */ Fmdrv_MixTo(csf->multi_write[0].buffer, count); } else { Fmdrv_MixTo(csf->mix_buffer, count); } return nchused; } schismtracker-20180209/player/mixutil.c000066400000000000000000000143561323741476300200010ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sndfile.h" #include "cmixer.h" #define OFSDECAYSHIFT 8 #define OFSDECAYMASK 0xFF void init_mix_buffer(int *buffer, unsigned int samples) { memset(buffer, 0, samples * sizeof(int)); } void stereo_fill(int *buffer, unsigned int samples, int* profs, int *plofs) { int rofs = *profs; int lofs = *plofs; if (!rofs && !lofs) { init_mix_buffer(buffer, samples * 2); return; } for (unsigned int i = 0; i < samples; i++) { int x_r = (rofs + (((-rofs) >> 31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; int x_l = (lofs + (((-lofs) >> 31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; rofs -= x_r; lofs -= x_l; buffer[i * 2 ] = x_r; buffer[i * 2 + 1] = x_l; } *profs = rofs; *plofs = lofs; } void end_channel_ofs(song_voice_t *channel, int *buffer, unsigned int samples) { int rofs = channel->rofs; int lofs = channel->lofs; if (!rofs && !lofs) return; for (unsigned int i = 0; i < samples; i++) { int x_r = (rofs + (((-rofs) >> 31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; int x_l = (lofs + (((-lofs) >> 31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; rofs -= x_r; lofs -= x_l; buffer[i * 2] += x_r; buffer[i * 2 + 1] += x_l; } channel->rofs = rofs; channel->lofs = lofs; } void mono_from_stereo(int *mix_buf, unsigned int samples) { for (unsigned int j, i = 0; i < samples; i++) { j = i << 1; mix_buf[i] = (mix_buf[j] + mix_buf[j + 1]) >> 1; } } static const float f2ic = (float) (1 << 28); static const float i2fc = (float) (1.0 / (1 << 28)); void stereo_mix_to_float(const int *src, float *out1, float *out2, unsigned int count) { for (unsigned int i = 0; i < count; i++) { *out1++ = *src * i2fc; src++; *out2++ = *src * i2fc; src++; } } void float_to_stereo_mix(const float *in1, const float *in2, int *out, unsigned int count) { for (unsigned int i = 0; i < count; i++) { *out++ = (int) (*in1 * f2ic); *out++ = (int) (*in2 * f2ic); in1++; in2++; } } void mono_mix_to_float(const int *src, float *out, unsigned int count) { for (unsigned int i = 0; i < count; i++) { *out++ = *src * i2fc; src++; } } void float_to_mono_mix(const float *in, int *out, unsigned int count) { for (unsigned int i = 0; i < count; i++) { *out++ = (int) (*in * f2ic); in++; } } // ---------------------------------------------------------------------------- // Clip and convert functions // ---------------------------------------------------------------------------- // XXX mins/max were int[2] // // The original C version was written by Rani Assaf // Clip and convert to 8 bit. mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. unsigned int clip_32_to_8(void *ptr, int *buffer, unsigned int samples, int *mins, int *maxs) { unsigned char *p = (unsigned char *) ptr; for (unsigned int i = 0; i < samples; i++) { int n = buffer[i]; if (n < MIXING_CLIPMIN) n = MIXING_CLIPMIN; else if (n > MIXING_CLIPMAX) n = MIXING_CLIPMAX; if (n < mins[i & 1]) mins[i & 1] = n; else if (n > maxs[i & 1]) maxs[i & 1] = n; // 8-bit unsigned p[i] = (n >> (24 - MIXING_ATTENUATION)) ^ 0x80; } return samples; } // Clip and convert to 16 bit. mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. unsigned int clip_32_to_16(void *ptr, int *buffer, unsigned int samples, int *mins, int *maxs) { signed short *p = (signed short *) ptr; for (unsigned int i = 0; i < samples; i++) { int n = buffer[i]; if (n < MIXING_CLIPMIN) n = MIXING_CLIPMIN; else if (n > MIXING_CLIPMAX) n = MIXING_CLIPMAX; if (n < mins[i & 1]) mins[i & 1] = n; else if (n > maxs[i & 1]) maxs[i & 1] = n; // 16-bit signed p[i] = n >> (16 - MIXING_ATTENUATION); } return samples * 2; } // Clip and convert to 24 bit. mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. // Note, this is 24bit, not 24-in-32bits. The former is used in .wav. The latter is used in audio IO unsigned int clip_32_to_24(void *ptr, int *buffer, unsigned int samples, int *mins, int *maxs) { /* the inventor of 24bit anything should be shot */ unsigned char *p = (unsigned char *) ptr; for (unsigned int i = 0; i < samples; i++) { int n = buffer[i]; if (n < MIXING_CLIPMIN) n = MIXING_CLIPMIN; else if (n > MIXING_CLIPMAX) n = MIXING_CLIPMAX; if (n < mins[i & 1]) mins[i & 1] = n; else if (n > maxs[i & 1]) maxs[i & 1] = n; // 24-bit signed n = n >> (8 - MIXING_ATTENUATION); /* err, assume same endian */ memcpy(p, &n, 3); p += 3; } return samples * 3; } // Clip and convert to 32 bit(int). mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. unsigned int clip_32_to_32(void *ptr, int *buffer, unsigned int samples, int *mins, int *maxs) { signed int *p = (signed int *) ptr; for (unsigned int i = 0; i < samples; i++) { int n = buffer[i]; if (n < MIXING_CLIPMIN) n = MIXING_CLIPMIN; else if (n > MIXING_CLIPMAX) n = MIXING_CLIPMAX; if (n < mins[i & 1]) mins[i & 1] = n; else if (n > maxs[i & 1]) maxs[i & 1] = n; // 32-bit signed p[i] = (n << MIXING_ATTENUATION); } return samples * 4; } schismtracker-20180209/player/opl-util.c000066400000000000000000000113321323741476300200420ustar00rootroot00000000000000/** * @file opl-util.cpp * @brief Utility functions related to OPL chips. * * Copyright (C) 2010-2013 Adam Nielsen * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ //Stripped down version for Schismtracker, in C. // this really should be in a header but it's only used in one other file int fnumToMilliHertz(unsigned int fnum, unsigned int block, unsigned int conversionFactor); void milliHertzToFnum(unsigned int milliHertz, unsigned int *fnum, unsigned int *block, unsigned int conversionFactor); /// Convert the given f-number and block into a note frequency. /** * @param fnum * Input frequency number, between 0 and 1023 inclusive. Values outside this * range will cause assertion failures. * * @param block * Input block number, between 0 and 7 inclusive. Values outside this range * will cause assertion failures. * * @param conversionFactor * Conversion factor to use. Normally will be 49716 and occasionally 50000. * * @return The converted frequency in milliHertz. */ int fnumToMilliHertz(unsigned int fnum, unsigned int block, unsigned int conversionFactor) { // Original formula //return 1000 * conversionFactor * (double)fnum * pow(2, (double)((signed)block - 20)); // More efficient version return (1000ull * conversionFactor * fnum) >> (20 - block); } /// Convert a frequency into an OPL f-number /** * @param milliHertz * Input frequency. * * @param fnum * Output frequency number for OPL chip. This is a 10-bit number, so it will * always be between 0 and 1023 inclusive. * * @param block * Output block number for OPL chip. This is a 3-bit number, so it will * always be between 0 and 7 inclusive. * * @param conversionFactor * Conversion factor to use. Normally will be 49716 and occasionally 50000. * * @post fnum will be set to a value between 0 and 1023 inclusive. block will * be set to a value between 0 and 7 inclusive. assert() calls inside this * function ensure this will always be the case. * * @note As the block value increases, the frequency difference between two * adjacent fnum values increases. This means the higher the frequency, * the less precision is available to represent it. Therefore, converting * a value to fnum/block and back to milliHertz is not guaranteed to reproduce * the original value. */ void milliHertzToFnum(unsigned int milliHertz, unsigned int *fnum, unsigned int *block, unsigned int conversionFactor) { // Special case to avoid divide by zero if (milliHertz <= 0) { *block = 0; // actually any block will work *fnum = 0; return; } // Special case for frequencies too high to produce if (milliHertz > 6208431) { *block = 7; *fnum = 1023; return; } /// This formula will provide a pretty good estimate as to the best block to /// use for a given frequency. It tries to use the lowest possible block /// number that is capable of representing the given frequency. This is /// because as the block number increases, the precision decreases (i.e. there /// are larger steps between adjacent note frequencies.) The 6M constant is /// the largest frequency (in milliHertz) that can be represented by the /// block/fnum system. //int invertedBlock = log2(6208431 / milliHertz); // Very low frequencies will produce very high inverted block numbers, but // as they can all be covered by inverted block 7 (block 0) we can just clip // the value. //if (invertedBlock > 7) invertedBlock = 7; //*block = 7 - invertedBlock; // This is a bit more efficient and doesn't need log2() from math.h if (milliHertz > 3104215) *block = 7; else if (milliHertz > 1552107) *block = 6; else if (milliHertz > 776053) *block = 5; else if (milliHertz > 388026) *block = 4; else if (milliHertz > 194013) *block = 3; else if (milliHertz > 97006) *block = 2; else if (milliHertz > 48503) *block = 1; else *block = 0; // Original formula //*fnum = milliHertz * pow(2, 20 - *block) / 1000 / conversionFactor + 0.5; // Slightly more efficient version *fnum = ((unsigned long long)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5; if (*fnum > 1023) { (*block)++; *fnum = ((unsigned long long)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5; } return; } schismtracker-20180209/player/snd_fm.c000066400000000000000000000330021323741476300175410ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "snd_fm.h" #include "log.h" #include "util.h" /* for clamp */ #define MAX_VOICES 256 /* Must not be less than the setting in sndfile.h */ #include #include #include // Choose OPL emulator source code (OPL2 or OPL3) #define OPLSOURCE 3 #if OPLSOURCE == 2 #include "fmopl2.h" #define OPLNew(x,r) ym3812_init(x, r) #define OPLResetChip ym3812_reset_chip #define OPLWrite ym3812_write #define OPLReadChip ym3812_read #define OPLUpdateOne ym3812_update_one #define OPLCloseChip ym3812_shutdown // OPL2 = 3579552Hz #define OPLRATEBASE 49716 // It's not a good idea to deviate from this. #define OPLRATEDIVISOR 72 #else #include "fmopl3.h" #define OPLNew(x,r) ymf262_init(x, r) #define OPLResetChip ymf262_reset_chip #define OPLWrite ymf262_write #define OPLReadChip ymf262_read #define OPLUpdateOne ymf262_update_one #define OPLCloseChip ymf262_shutdown // OPL3 = 14318208Hz #define OPLRATEBASE 49716 // It's not a good idea to deviate from this. #define OPLRATEDIVISOR 288 #endif /* Schismtracker output buffer works in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX] fmopl works in 16bits, although tested output used to range +-10000 instead of +-20000 from adlibtracker/screamtracker in dosbox. So we need 11 bits + 1 extra bit. Also note when comparing volumes, that Screamtracker output on mono with PCM samples is not reduced by half. */ #define OPL_VOLUME 4096 /* The documentation in this file regarding the output ports, including the comment "Don't ask me why", are attributed to Jeffrey S. Lee's article: Programming the AdLib/Sound Blaster FM Music Chips Version 2.0 (24 Feb 1992) */ static const int oplbase = 0x388; // OPL info static struct OPL* opl = NULL; static UINT32 oplretval = 0, oplregno = 0; static UINT32 fm_active = 0; extern int fnumToMilliHertz(unsigned int fnum, unsigned int block, unsigned int conversionFactor); extern void milliHertzToFnum(unsigned int milliHertz, unsigned int *fnum, unsigned int *block, unsigned int conversionFactor); static void Fmdrv_Outportb(unsigned port, unsigned value) { if (opl == NULL || ((int) port) < oplbase || ((int) port) >= oplbase + 4) return; unsigned ind = port - oplbase; OPLWrite(opl, ind, value); if (ind & 1) { if (oplregno == 4) { if (value == 0x80) oplretval = 0x02; else if (value == 0x21) oplretval = 0xC0; } } else oplregno = value; } static unsigned char Fmdrv_Inportb(unsigned port) { return (((int) port) >= oplbase && ((int) port) < oplbase + 4) ? oplretval : 0; } void Fmdrv_Init(int mixfreq) { if (opl != NULL) { OPLCloseChip(opl); opl = NULL; } // Clock = speed at which the chip works. mixfreq = audio resampler opl = OPLNew(OPLRATEBASE * OPLRATEDIVISOR, mixfreq); OPL_Reset(); } void Fmdrv_MixTo(int *target, int count) { static short *buf = NULL; static int buf_size = 0; if (!fm_active) return; #if OPLSOURCE == 2 // mono. Single buffer. if (buf_size != count * sizeof(short)) { int before = buf_size; buf_size = sizeof(short) * count; if (before) { buf = (short *) mem_realloc(buf, buf_size); } else { buf = (short *) mem_alloc(buf_size); } } memset(buf, 0, buf_size); OPLUpdateOne(opl, buf, count); /* static int counter = 0; for(int a = 0; a < count; ++a) buf[a] = ((counter++) & 0x100) ? -10000 : 10000; */ for (int a = 0; a < count; ++a) { target[a * 2 + 0] += buf[a] * OPL_VOLUME; target[a * 2 + 1] += buf[a] * OPL_VOLUME; } #else //stereo. Four buffers (two unused, so allocating 3 is enough) if (buf_size != sizeof(short) * count * 3) { int before = buf_size; buf_size = sizeof(short) * count * 3; if (before) { buf = (short *) mem_realloc(buf, buf_size); } else { buf = (short *) mem_alloc(buf_size); } } memset(buf, 0, buf_size); short *bufarray[4]={buf, buf+count, buf+(count*2), buf+(count*2)}; OPLUpdateOne(opl, bufarray, count); /* static int counter = 0; for(int a = 0; a < count; ++a) buf[a] = ((counter++) & 0x100) ? -10000 : 10000; */ short *bufleft = buf; short *bufright = buf+count; // IF we wanted to do the stereo mix in software, we could setup the voices always in mono // and do the panning here. for (int a = 0; a < count; ++a) { target[a * 2 + 0] += bufleft[a] * OPL_VOLUME; target[a * 2 + 1] += bufright[a] * OPL_VOLUME; } #endif } /***************************************/ static const char PortBases[9] = {0, 1, 2, 8, 9, 10, 16, 17, 18}; static const unsigned char *Dtab[9]; static unsigned char Keyontab[9] = {0,0,0,0,0,0,0,0,0}; static int Pans[MAX_VOICES]; static int OPLtoChan[9]; static int ChantoOPL[MAX_VOICES]; static int GetVoice(int c) { return ChantoOPL[c]; } static int SetVoice(int c) { int a,s=-1,t=0; if (ChantoOPL[c] == -1) { t=1; // Search for unused chans for (a=0;a<9;a++) { if (OPLtoChan[a]==-1) { s=a; OPLtoChan[a]=c; ChantoOPL[c]=a; break; } } if (ChantoOPL[c] == -1) { // Search for note-released chans for (a=0;a<9;a++) { if ((Keyontab[a]&KEYON_BIT) == 0) { s=a+10; ChantoOPL[OPLtoChan[a]]=-1; OPLtoChan[a]=c; ChantoOPL[c]=a; break; } } } } //log_appendf(2,"entering with %d. tested? %d. selected %d. Current: %d",c,t,s,ChantoOPL[c]); return GetVoice(c); } static void FreeVoice(int c) { if (ChantoOPL[c] == -1) return; OPLtoChan[ChantoOPL[c]]=-1; ChantoOPL[c]=-1; } static void OPL_Byte(unsigned int idx, unsigned char data) { //register int a; Fmdrv_Outportb(oplbase, idx); // for(a = 0; a < 6; a++) Fmdrv_Inportb(oplbase); Fmdrv_Outportb(oplbase + 1, data); // for(a = 0; a < 35; a++) Fmdrv_Inportb(oplbase); } static void OPL_Byte_RightSide(unsigned int idx, unsigned char data) { //register int a; Fmdrv_Outportb(oplbase + 2, idx); // for(a = 0; a < 6; a++) Fmdrv_Inportb(oplbase); Fmdrv_Outportb(oplbase + 3, data); // for(a = 0; a < 35; a++) Fmdrv_Inportb(oplbase); } void OPL_NoteOff(int c) { int oplc = GetVoice(c); if (oplc == -1) return; Keyontab[oplc]&=~KEYON_BIT; OPL_Byte(KEYON_BLOCK + oplc, Keyontab[oplc]); } /* OPL_NoteOn changes the frequency on specified channel and guarantees the key is on. (Doesn't retrig, just turns the note on and sets freq.) If keyoff is nonzero, doesn't even set the note on. Could be used for pitch bending also. */ void OPL_HertzTouch(int c, int milliHertz, int keyoff) { int oplc = GetVoice(c); if (oplc == -1) return; fm_active = 1; /* Bytes A0-B8 - Octave / F-Number / Key-On 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | F-Number (least significant byte) | (A0-A8) +-----+-----+-----+-----+-----+-----+-----+-----+ | Unused | Key | Octave | F-Number | (B0-B8) | | On | | most sig. | +-----+-----+-----+-----+-----+-----+-----+-----+ */ unsigned int outfnum; unsigned int outblock; const int conversion_factor = OPLRATEBASE; // Frequency of OPL. milliHertzToFnum(milliHertz, &outfnum, &outblock, conversion_factor); Keyontab[oplc] = (keyoff ? 0 : KEYON_BIT) // Key on | (outblock << 2) // Octave | ((outfnum >> 8) & FNUM_HIGH_MASK); // F-number high 2 bits OPL_Byte(FNUM_LOW + oplc, outfnum & 0xFF); // F-Number low 8 bits OPL_Byte(KEYON_BLOCK + oplc, Keyontab[oplc]); } void OPL_Touch(int c, unsigned vol) { //fprintf(stderr, "OPL_Touch(%d, %p:%02X.%02X.%02X.%02X-%02X.%02X.%02X.%02X-%02X.%02X.%02X, %d)\n", // c, D,D[0],D[1],D[2],D[3],D[4],D[5],D[6],D[7],D[8],D[9],D[10], Vol); int oplc = GetVoice(c); if (oplc == -1) return; const unsigned char *D = Dtab[oplc]; int Ope = PortBases[oplc]; /* Bytes 40-55 - Level Key Scaling / Total Level 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | Scaling | Total Level | | Level | 24 12 6 3 1.5 .75 | <-- dB +-----+-----+-----+-----+-----+-----+-----+-----+ bits 7-6 - causes output levels to decrease as the frequency rises: 00 - no change 10 - 1.5 dB/8ve 01 - 3 dB/8ve 11 - 6 dB/8ve bits 5-0 - controls the total output level of the operator. all bits CLEAR is loudest; all bits SET is the softest. Don't ask me why. */ /* 2008-09-27 Bisqwit: * Did tests in ST3: The value poked * to 0x43, minus from 63, is: * * OplVol 63 47 31 * SmpVol * 64 63 47 31 * 32 32 24 15 * 16 16 12 8 * * This seems to clearly indicate that the value * poked is calculated with 63 - round(oplvol*smpvol/64.0). * * Also, from the documentation we can deduce that * the maximum volume to be set is 47.25 dB and that * each increase by 1 corresponds to 0.75 dB. * * Since we know that 6 dB is equivalent to a doubling * of the volume, we can deduce that an increase or * decrease by 8 will double / halve the volume. * D = 63-OPLVol NewD = 63-target OPLVol = 63 - D newvol = clip(vol,63) -> max value of newvol=63, same as max of OPLVol. target = OPLVOL * (newvol/63) NewD = 63-(OLPVOL * (newvol/63)) NewD = 63-((63 - D) * (newvol/63)) NewD = 63-((63*newvol/63) - (D*newvol/63) ) NewD = 63-(newvol - (D*newvol/63) ) NewD = 63-(newvol) + (D*newvol/63) NewD = 63 + (D*newvol/63) - newvol NewD = 63 + (D*newvol/63) - newvol */ // On Testing, ST3 does not alter the modulator volume. // vol is previously converted to the 0..63 range. OPL_Byte(KSL_LEVEL+ 3+Ope, (D[3] & KSL_MASK) | (63 + ( (D[3]&TOTAL_LEVEL_MASK)*vol / 63) - vol) ); } void OPL_Pan(int c, int val) { Pans[c] = CLAMP(val, 0, 256); int oplc = GetVoice(c); if (oplc == -1) return; const unsigned char *D = Dtab[oplc]; /* feedback, additive synthesis and Panning... */ OPL_Byte(FEEDBACK_CONNECTION+oplc, (D[10] & ~STEREO_BITS) | (Pans[c]<85 ? VOICE_TO_LEFT : Pans[c]>170 ? VOICE_TO_RIGHT : (VOICE_TO_LEFT | VOICE_TO_RIGHT)) ); } void OPL_Patch(int c, const unsigned char *D) { int oplc = SetVoice(c); if (oplc == -1) return; Dtab[oplc] = D; int Ope = PortBases[oplc]; OPL_Byte(AM_VIB+ Ope, D[0]); OPL_Byte(KSL_LEVEL+ Ope, D[2]); OPL_Byte(ATTACK_DECAY+ Ope, D[4]); OPL_Byte(SUSTAIN_RELEASE+ Ope, D[6]); OPL_Byte(WAVE_SELECT+ Ope, D[8]&3);// 6 high bits used elsewhere OPL_Byte(AM_VIB+ 3+Ope, D[1]); OPL_Byte(KSL_LEVEL+ 3+Ope, D[3]); OPL_Byte(ATTACK_DECAY+ 3+Ope, D[5]); OPL_Byte(SUSTAIN_RELEASE+3+Ope, D[7]); OPL_Byte(WAVE_SELECT+ 3+Ope, D[9]&3);// 6 high bits used elsewhere /* feedback, additive synthesis and Panning... */ OPL_Byte(FEEDBACK_CONNECTION+oplc, (D[10] & ~STEREO_BITS) | (Pans[c]<85 ? VOICE_TO_LEFT : Pans[c]>170 ? VOICE_TO_RIGHT : (VOICE_TO_LEFT | VOICE_TO_RIGHT)) ); } void OPL_Reset(void) { int a; if (opl == NULL) return; OPLResetChip(opl); OPL_Detect(); for(a = 0; a < MAX_VOICES; ++a) { ChantoOPL[a]=-1; } for(a = 0; a < 9; ++a) { OPLtoChan[a]= -1; Dtab[a] = NULL; } OPL_Byte(TEST_REGISTER, ENABLE_WAVE_SELECT); #if OPLSOURCE == 3 //Enable OPL3. OPL_Byte_RightSide(OPL3_MODE_REGISTER, OPL3_ENABLE); #endif fm_active = 0; } int OPL_Detect(void) { /* Reset timers 1 and 2 */ OPL_Byte(TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); /* Reset the IRQ of the FM chip */ OPL_Byte(TIMER_CONTROL_REGISTER, IRQ_RESET); unsigned char ST1 = Fmdrv_Inportb(oplbase); /* Status register */ OPL_Byte(TIMER1_REGISTER, 255); OPL_Byte(TIMER_CONTROL_REGISTER, TIMER2_MASK | TIMER1_START); /*_asm xor cx,cx;P1:_asm loop P1*/ unsigned char ST2 = Fmdrv_Inportb(oplbase); OPL_Byte(TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); OPL_Byte(TIMER_CONTROL_REGISTER, IRQ_RESET); int OPLMode = (ST2 & 0xE0) == 0xC0 && !(ST1 & 0xE0); if (!OPLMode) return -1; return 0; } /* TODO: This should be called from somewhere in schismtracker to free the allocated memory on exit. */ void OPL_Close(void) { if (opl != NULL) { OPLCloseChip(opl); opl = NULL; } } schismtracker-20180209/player/snd_gm.c000066400000000000000000000476261323741476300175630ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 wrapper which converts S3M style thinking * into MIDI style thinking. */ #include "headers.h" #include "log.h" #include "it.h" // needed for status.flags #include "sndfile.h" #include "song.h" // for 'current_song', which we shouldn't need #include "snd_gm.h" #include // for log #define LinearMidivol 1 #define PitchBendCenter 0x2000 static const enum { AlwaysHonor, /* Always honor midi_channel_mask in instruments */ TryHonor, /* Honor midi_channel_mask in instruments when the channel is free */ Ignore /* Ignore midi_channel_mask in instruments */ } PreferredChannelHandlingMode = AlwaysHonor; // The range of bending equivalent to 1 semitone. // 0x2000 is the value used in TiMiDity++. // In this module, we prefer a full range of octave, to support a reasonable // range of pitch-bends used in tracker modules, and we reprogram the MIDI // synthesizer to support that range. So we specify it as such: static const int semitone_bend_depth = 0x2000 / 12; // one octave in either direction /* GENERAL MIDI (GM) COMMANDS: 8x 1000xxxx nn vv Note off (key is released) nn=note number vv=velocity 9x 1001xxxx nn vv Note on (key is pressed) nn=note number vv=velocity Ax 1010xxxx nn vv Key after-touch nn=note number vv=velocity Bx 1011xxxx cc vv Control Change cc=controller number vv=new value Cx 1100xxxx pp Program (patch) change pp=new program number Dx 1101xxxx cc Channel after-touch cc=channel number Ex 1110xxxx bb tt Pitch wheel change (2000h is normal or no change) bb=bottom (least sig) 7 bits of value tt=top (most sig) 7 bits of value About the controllers... In AWE32 they are: 0=Bank select 7=Master volume 11=Expression(volume?) 1=Modulation Wheel(Vibrato)10=Pan Position 64=Sustain Pedal 6=Data Entry MSB 38=Data Entry LSB 91=Effects Depth(Reverb) 120=All Sound Off 123=All Notes Off 93=Chorus Depth 100=RPN # LSB 101=RPN # MSB 98=NRPN # LSB 99=NRPN # MSB 1=Vibrato, 121=reset vibrato,bend To set RPNs (registered parameters): control 101 <- param number MSB control 100 <- param number LSB control 6 <- value number MSB optional For NRPNs, the procedure is the same, but you use 98,99 instead of 100,101. param 0 = pitch bend sensitivity param 1 = finetuning param 2 = coarse tuning param 3 = tuning program select param 4 = tuning bank select param 0x4080 = reset (value omitted) References: - SoundBlaster AWE32 documentation - http://www.philrees.co.uk/nrpnq.htm */ //#define GM_DEBUG static unsigned RunningStatus = 0; #ifdef GM_DEBUG static int resetting = 0; // boolean #endif static void MPU_SendCommand(const unsigned char* buf, unsigned nbytes, int c) { if (!nbytes) return; csf_midi_send(current_song, buf, nbytes, c, 0); // FIXME we should not know about 'current_song' here! } static void MPU_Ctrl(int c, int i, int v) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; unsigned char buf[3] = {0xB0 + c, i, v}; MPU_SendCommand(buf, 3, c); } static void MPU_Patch(int c, int p) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; unsigned char buf[2] = {0xC0 + c, p}; MPU_SendCommand(buf, 2, c); } static void MPU_Bend(int c, int w) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; unsigned char buf[3] = {0xE0 + c, w & 127, w >> 7}; MPU_SendCommand(buf, 3, c); } static void MPU_NoteOn(int c, int k, int v) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; unsigned char buf[3] = {0x90 + c, k, v}; MPU_SendCommand(buf, 3, c); } static void MPU_NoteOff(int c, int k, int v) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; if (((unsigned char) RunningStatus) == 0x90 + c) { // send a zero-velocity keyoff instead for optimization MPU_NoteOn(c, k, 0); } else { unsigned char buf[3] = {0x80+c, k, v}; MPU_SendCommand(buf, 3, c); } } static void MPU_SendPN(int ch, unsigned portindex, unsigned param, unsigned valuehi, unsigned valuelo) { MPU_Ctrl(ch, portindex+1, param>>7); MPU_Ctrl(ch, portindex+0, param & 0x80); if (param != 0x4080) { MPU_Ctrl(ch, 6, valuehi); if (valuelo) MPU_Ctrl(ch, 38, valuelo); } } #define MPU_SendNRPN(ch,param,hi,lo) MPU_SendPN(ch,98,param,hi,lo) #define MPU_SendRPN(ch,param,hi,lo) MPU_SendPN(ch,100,param,hi,lo) #define MPU_ResetPN(ch) MPU_SendRPN(ch,0x4080,0,0) typedef struct { unsigned char note; // Which note is playing in this channel (0 = nothing) unsigned char patch; // Which patch was programmed on this channel (&0x80 = percussion) unsigned char bank; // Which bank was programmed on this channel signed char pan; // Which pan level was last selected signed char chan; // Which MIDI channel was allocated for this channel. -1 = none int pref_chn_mask; // Which MIDI channel was preferred } s3m_channel_info_t; #define s3m_active(ci) \ ((ci).note && (ci).chan >= 0) // patch: definitely percussion // pref_chn_mask: to be played on P channel, so it's percussion #define s3m_percussion(ci) \ ((ci).patch & 0x80 || (ci).pref_chn_mask & (1 << 9)) static void s3m_reset(s3m_channel_info_t *ci) { ci->note = 0; ci->patch = 0; ci->bank = 0; ci->pan = 0; ci->chan = -1; ci->pref_chn_mask = -1; } /* This maps S3M concepts into MIDI concepts */ static s3m_channel_info_t s3m_chans[MAX_VOICES]; typedef struct { unsigned char volume; // Which volume has been configured for this channel unsigned char patch; // What is the latest patch configured on this channel unsigned char bank; // What is the latest bank configured on this channel int bend; // The latest pitchbend on this channel signed char pan; // Latest pan } midi_state_t; static void msi_reset(midi_state_t *msi) { msi->volume = 255; msi->patch = 255; msi->bank = 255; msi->bend = PitchBendCenter; msi->pan = 0; } #define msi_know_something(msi) ((msi).patch != 255) static void msi_set_volume(midi_state_t *msi, int c, unsigned newvol) { if (msi->volume != newvol) { msi->volume = newvol; MPU_Ctrl(c, 7, newvol); } } static void msi_set_patch_and_bank(midi_state_t *msi, int c, int p, int b) { if (msi->bank != b) { msi->bank = b; MPU_Ctrl(c, 0, b); } if (msi->patch != p) { msi->patch = p; MPU_Patch(c, p); } } static void msi_set_pitch_bend(midi_state_t *msi, int c, int value) { if (msi->bend != value) { msi->bend = value; MPU_Bend(c, value); } } static void msi_set_pan(midi_state_t *msi, int c, int value) { if (msi->pan != value) { msi->pan = value; MPU_Ctrl(c, 10, (unsigned char)(value + 128) / 2); } } /* This helps reduce the MIDI traffic, also does some encapsulation */ static midi_state_t midi_chans[16]; static unsigned char GM_volume(unsigned char vol) // Converts the volume { /* Converts volume in range 0..127 to range 0..127 with clamping */ return vol >= 127 ? 127 : vol; } static int GM_AllocateMelodyChannel(int c, int patch, int bank, int key, int pref_chn_mask) { /* Returns a MIDI channel number on * which this key can be played safely. * * Things that matter: * * -4 The channel has a different patch selected * -6 The channel has a different bank selected * -9 The channel already has the same key * +1 The channel number corresponds to c * +2 The channel has no notes playing * -999 The channel number is 9 (percussion-only channel) * * Channel with biggest score is selected. * */ int bad_channels[16] = {}; // channels having the same key playing int used_channels[16] = {}; // channels having something playing for (unsigned int a = 0; a < MAX_VOICES; ++a) { if (s3m_active(s3m_chans[a]) && !s3m_percussion(s3m_chans[a])) { //fprintf(stderr, "S3M[%d] active at %d\n", a, s3m_chans[a].chan); used_channels[s3m_chans[a].chan] = 1; // channel is active if (s3m_chans[a].note == key) bad_channels[s3m_chans[a].chan] = 1; // ...with the same key } } int best_mc = c, best_score = -999; for (int mc = 0; mc < 16; ++mc) { if (mc == 9) continue; // percussion channel is never chosen for melody. int score = 0; if (PreferredChannelHandlingMode != TryHonor && msi_know_something(midi_chans[mc])) { if (midi_chans[mc].patch != patch) score -= 4; // different patch if (midi_chans[mc].bank != bank) score -= 6; // different bank } if (PreferredChannelHandlingMode == TryHonor) { if (pref_chn_mask & (1 << mc)) score += 1; // same channel number } else if (PreferredChannelHandlingMode == AlwaysHonor) { // disallow channels that are not allowed if (pref_chn_mask >= 0x10000) { if (mc != c % 16) continue; } else if (!(pref_chn_mask & (1 << mc))) continue; } else { if (c == mc) score += 1; // same channel number } if (bad_channels[mc]) score -= 9; // has same key on if (!used_channels[mc]) score += 2; // channel is unused //fprintf(stderr, "score %d for channel %d\n", score, mc); if (score > best_score) { best_score = score; best_mc = mc; } } //fprintf(stderr, "BEST SCORE %d FOR CHANNEL %d\n", best_score,best_mc); return best_mc; } void GM_Patch(int c, unsigned char p, int pref_chn_mask) { if (c < 0 || ((unsigned int) c) >= MAX_VOICES) return; s3m_chans[c].patch = p; // No actual data is sent. s3m_chans[c].pref_chn_mask = pref_chn_mask; } void GM_Bank(int c, unsigned char b) { if (c < 0 || ((unsigned int) c) >= MAX_VOICES) return; s3m_chans[c].bank = b; // No actual data is sent yet. } void GM_Touch(int c, unsigned char vol) { if (c < 0 || ((unsigned int) c) >= MAX_VOICES) return; /* This function must only be called when * a key has been played on the channel. */ if (!s3m_active(s3m_chans[c])) return; int mc = s3m_chans[c].chan; msi_set_volume(&midi_chans[mc], mc, GM_volume(vol)); } void GM_KeyOn(int c, unsigned char key, unsigned char vol) { if (c < 0 || ((unsigned int) c) >= MAX_VOICES) return; GM_KeyOff(c); // Ensure the previous key on this channel is off. if (s3m_active(s3m_chans[c])) return; // be sure the channel is deactivated. #ifdef GM_DEBUG fprintf(stderr, "GM_KeyOn(%d, %d,%d)\n", c, key,vol); #endif if (s3m_percussion(s3m_chans[c])) { // Percussion always uses channel 9. int percu = key; if (s3m_chans[c].patch & 0x80) percu = s3m_chans[c].patch - 128; int mc = s3m_chans[c].chan = 9; msi_set_pan(&midi_chans[mc], mc, s3m_chans[c].pan); msi_set_volume(&midi_chans[mc], mc, GM_volume(vol)); s3m_chans[c].note = key; MPU_NoteOn(mc, s3m_chans[c].note = percu, 127); } else { // Allocate a MIDI channel for this key. // Note: If you need to transpone the key, do it before allocating the channel. int mc = s3m_chans[c].chan = GM_AllocateMelodyChannel( c, s3m_chans[c].patch, s3m_chans[c].bank, key, s3m_chans[c].pref_chn_mask); msi_set_patch_and_bank(&midi_chans[mc], mc, s3m_chans[c].patch, s3m_chans[c].bank); msi_set_volume(&midi_chans[mc], mc, GM_volume(vol)); MPU_NoteOn(mc, s3m_chans[c].note = key, 127); msi_set_pan(&midi_chans[mc], mc, s3m_chans[c].pan); } } void GM_KeyOff(int c) { if (c < 0 || ((unsigned int)c) >= MAX_VOICES) return; if (!s3m_active(s3m_chans[c])) return; // nothing to do #ifdef GM_DEBUG fprintf(stderr, "GM_KeyOff(%d)\n", c); #endif int mc = s3m_chans[c].chan; MPU_NoteOff(mc, s3m_chans[c].note, 0); s3m_chans[c].chan = -1; s3m_chans[c].note = 0; s3m_chans[c].pan = 0; // Don't reset the pitch bend, it will make sustains sound bad } void GM_Bend(int c, unsigned count) { if (c < 0 || ((unsigned int)c) >= MAX_VOICES) return; /* I hope nobody tries to bend hi-hat or something like that :-) */ /* 1998-10-03 01:50 Apparently that can happen too... For example in the last pattern of urq.mod there's a hit of a heavy plate, which is followed by a J0A 0.5 seconds thereafter for the same channel. Unfortunately MIDI cannot do that. Drum plate sizes can rarely be adjusted while playing. -Bisqwit However, we don't stop anyone from trying... */ if (s3m_active(s3m_chans[c])) { int mc = s3m_chans[c].chan; msi_set_pitch_bend(&midi_chans[mc], mc, count); } } void GM_Reset(int quitting) { #ifdef GM_DEBUG resetting = 1; #endif unsigned int a; //fprintf(stderr, "GM_Reset\n"); for (a = 0; a < MAX_VOICES; a++) { GM_KeyOff(a); //s3m_chans[a].patch = s3m_chans[a].bank = s3m_chans[a].pan = 0; s3m_reset(&s3m_chans[a]); } // How many semitones does it take to screw in the full 0x4000 bending range of lightbulbs? // We scale the number by 128, because the RPN allows for finetuning. int n_semitones_times_128 = 128 * 0x2000 / semitone_bend_depth; if (quitting) { // When quitting, we reprogram the pitch bend sensitivity into // the range of 1 semitone (TiMiDity++'s default, which is // probably a default on other devices as well), instead of // what we preferred for IT playback. n_semitones_times_128 = 128; } for (a = 0; a < 16; a++) { // XXX // XXX Porting note: // XXX This might go wrong because the midi struct is already reset // XXX by the constructor in the C++ version. // XXX MPU_Ctrl(a, 120, 0); // turn off all sounds MPU_Ctrl(a, 123, 0); // turn off all notes MPU_Ctrl(a, 121, 0); // reset vibrato, bend msi_set_pan(&midi_chans[a], a, 0); // reset pan position msi_set_volume(&midi_chans[a], a, 127); // set channel volume msi_set_pitch_bend(&midi_chans[a], a, PitchBendCenter); // reset pitch bends msi_reset(&midi_chans[a]); // Reprogram the pitch bending sensitivity to our desired depth. MPU_SendRPN(a, 0, n_semitones_times_128 / 128, n_semitones_times_128 % 128); MPU_ResetPN(a); } #ifdef GM_DEBUG resetting = 0; fprintf(stderr, "-------------- GM_Reset completed ---------------\n"); #endif } void GM_DPatch(int ch, unsigned char GM, unsigned char bank, int pref_chn_mask) { #ifdef GM_DEBUG fprintf(stderr, "GM_DPatch(%d, %02X @ %d)\n", ch, GM, bank); #endif if (ch < 0 || ((unsigned int)ch) >= MAX_VOICES) return; GM_Bank(ch, bank); GM_Patch(ch, GM, pref_chn_mask); } void GM_Pan(int c, signed char val) { //fprintf(stderr, "GM_Pan(%d,%d)\n", c,val); if (c < 0 || ((unsigned int)c) >= MAX_VOICES) return; s3m_chans[c].pan = val; // If a note is playing, effect immediately. if (s3m_active(s3m_chans[c])) { int mc = s3m_chans[c].chan; msi_set_pan(&midi_chans[mc], mc, val); } } void GM_SetFreqAndVol(int c, int Hertz, int vol, MidiBendMode bend_mode, int keyoff) { #ifdef GM_DEBUG fprintf(stderr, "GM_SetFreqAndVol(%d,%d,%d)\n", c,Hertz,vol); #endif if (c < 0 || ((unsigned int)c) >= MAX_VOICES) return; /* Figure out the note and bending corresponding to this Hertz reading. TiMiDity++ calculates its frequencies this way (equal temperament): freq(0<=i<128) := 440 * pow(2.0, (i - 69) / 12.0) bend_fine(0<=i<256) := pow(2.0, i/12.0/256) bend_coarse(0<=i<128) := pow(2.0, i/12.0) I suppose we can do the mathematical route. -Bisqwit hertz = 440*pow(2, (midinote-69)/12) Maxima gives us (solve+expand): midinote = 12 * log(hertz/440) / log(2) + 69 In other words: midinote = 12 * log2(hertz/440) + 69 Or: midinote = 12 * log2(hertz/55) + 33 (but I prefer the above for clarity) (55 and 33 are related to 440 and 69 the following way: log2(440) = ~8.7 440/8 = 55 log2(8) = 3 12 * 3 = 36 69-36 = 33. I guess Maxima's expression preserves more floating point accuracy, but given the range of the numbers we work here with, that's hardly an issue.) */ double midinote = 69 + 12.0 * log(Hertz/440.0) / log(2.0); // Reduce by a couple of octaves... Apparently the hertz // value that comes from SchismTracker is upscaled by some 2^5. midinote -= 12*5; int note = s3m_chans[c].note; // what's playing on the channel right now? int new_note = !s3m_active(s3m_chans[c]); if (new_note && !keyoff) { // If the note is not active, activate it first. // Choose the nearest note to Hertz. note = (int)(midinote + 0.5); // If we are expecting a bend exclusively in either direction, // prepare to utilize the full extent of available pitch bending. if (bend_mode == MIDI_BEND_DOWN) note += (int)(0x2000 / semitone_bend_depth); if (bend_mode == MIDI_BEND_UP) note -= (int)(0x2000 / semitone_bend_depth); if (note < 1) note = 1; if (note > 127) note = 127; GM_KeyOn(c, note, vol); } if (!s3m_percussion(s3m_chans[c])) { // give us a break, don't bend percussive instruments double notediff = midinote-note; // The difference is our bend value int bend = (int)(notediff * semitone_bend_depth) + PitchBendCenter; // Because the log2 calculation does not always give pure notes, // and in fact, gives a lot of variation, we reduce the bending // precision to 100 cents. This is accurate enough for almost // all purposes, but will significantly reduce the bend event load. //const int bend_artificial_inaccuracy = semitone_bend_depth / 100; //bend = (bend / bend_artificial_inaccuracy) * bend_artificial_inaccuracy; // Clamp the bending value so that we won't break the protocol if(bend < 0) bend = 0; if(bend > 0x3FFF) bend = 0x3FFF; GM_Bend(c, bend); } if (vol < 0) vol = 0; else if (vol > 127) vol = 127; //if (!new_note) GM_Touch(c, vol); } static double LastSongCounter = 0.0; void GM_SendSongStartCode(void) { unsigned char c = 0xFA; MPU_SendCommand(&c, 1, 0); LastSongCounter = 0; } void GM_SendSongStopCode(void) { unsigned char c = 0xFC; MPU_SendCommand(&c, 1, 0); LastSongCounter = 0; } void GM_SendSongContinueCode(void) { unsigned char c = 0xFB; MPU_SendCommand(&c, 1, 0); LastSongCounter = 0; } void GM_SendSongTickCode(void) { unsigned char c = 0xF8; MPU_SendCommand(&c, 1, 0); } void GM_SendSongPositionCode(unsigned note16pos) { unsigned char buf[3] = {0xF2, note16pos & 127, (note16pos >> 7) & 127}; MPU_SendCommand(buf, 3, 0); LastSongCounter = 0; } void GM_IncrementSongCounter(int count) { /* We assume that one schism tick = one midi tick (24ppq). * * We also know that: * 5 * mixingrate * Length of tick is -------------- samples * 2 * cmdT * * where cmdT = last FX_TEMPO = current_tempo */ int TickLengthInSamplesHi = 5 * current_song->mix_frequency; int TickLengthInSamplesLo = 2 * current_song->current_tempo; double TickLengthInSamples = TickLengthInSamplesHi / (double) TickLengthInSamplesLo; /* TODO: Use fraction arithmetics instead (note: cmdA, cmdT may change any time) */ LastSongCounter += count / TickLengthInSamples; int n_Ticks = (int)LastSongCounter; if (n_Ticks) { for (int a = 0; a < n_Ticks; ++a) GM_SendSongTickCode(); LastSongCounter -= n_Ticks; } } schismtracker-20180209/player/sndmix.c000066400000000000000000001056311323741476300176050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sndfile.h" #include "snd_fm.h" #include "snd_gm.h" #include "cmixer.h" #include "util.h" /* for clamp */ // 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 unsigned int max_voices = 32; // ITT it is 1994 // Mixing data initialized in static unsigned int volume_ramp_samples = 64; unsigned int global_vu_left = 0; unsigned int global_vu_right = 0; int32_t g_dry_rofs_vol = 0; int32_t g_dry_lofs_vol = 0; typedef uint32_t (* convert_t)(void *, int *, uint32_t, int *, int *); // see also csf_midi_out_raw in effects.c void (*csf_midi_out_note)(int chan, const song_note_t *m) = NULL; // The volume we have here is in range 0..(63*255) (0..16065) // We should keep that range, but convert it into a logarithmic // one such that a change of 256*8 (2048) corresponds to a halving // of the volume. // logvolume = 2^(linvolume / (4096/8)) * (4096/64) // However, because the resolution of MIDI volumes // is merely 128 units, we can use a lookup table. // // In this table, each value signifies the minimum value // that volume must be in order for the result to be // that table index. static const unsigned short GMvolTransition[128] = { 0, 2031, 4039, 5214, 6048, 6694, 7222, 7669, 8056, 8397, 8702, 8978, 9230, 9462, 9677, 9877, 10064,10239,10405,10562,10710,10852,10986,11115, 11239,11357,11470,11580,11685,11787,11885,11980, 12072,12161,12248,12332,12413,12493,12570,12645, 12718,12790,12860,12928,12995,13060,13123,13186, 13247,13306,13365,13422,13479,13534,13588,13641, 13693,13745,13795,13844,13893,13941,13988,14034, 14080,14125,14169,14213,14256,14298,14340,14381, 14421,14461,14501,14540,14578,14616,14653,14690, 14727,14763,14798,14833,14868,14902,14936,14970, 15003,15035,15068,15100,15131,15163,15194,15224, 15255,15285,15315,15344,15373,15402,15430,15459, 15487,15514,15542,15569,15596,15623,15649,15675, 15701,15727,15753,15778,15803,15828,15853,15877, 15901,15925,15949,15973,15996,16020,16043,16065, }; // We use binary search to find the right slot // with at most 7 comparisons. static unsigned int find_volume(unsigned short vol) { unsigned int l = 0, r = 128; while (l < r) { unsigned int m = l + ((r - l) / 2); unsigned short p = GMvolTransition[m]; if (p < vol) l = m + 1; else r = m; } return l; } unsigned int get_freq_from_period(int period, int linear) { if (period <= 0) return INT_MAX; else if (linear) return period; else return _muldiv(8363, 1712L << 8, (period << 8)); } //////////////////////////////////////////////////////////////////////////////////////////// // // XXX * I prefixed these with `rn_' to avoid any namespace conflicts // XXX Needs better naming! // XXX * Keep inline? // XXX * Get rid of the pointer passing where it is not needed // static inline void rn_tremor(song_voice_t *chan, int *vol) { if ((chan->cd_tremor & 192) == 128) *vol = 0; chan->flags |= CHN_FASTVOLRAMP; } static inline int rn_vibrato(song_t *csf, song_voice_t *chan, int period) { unsigned int vibpos = chan->vibrato_position & 0xFF; int vdelta; unsigned int vdepth; switch (chan->vib_type) { case VIB_SINE: default: vdelta = sine_table[vibpos]; break; case VIB_RAMP_DOWN: vdelta = ramp_down_table[vibpos]; break; case VIB_SQUARE: vdelta = square_table[vibpos]; break; case VIB_RANDOM: vdelta = 128 * ((double) rand() / RAND_MAX) - 64; break; } if (csf->flags & SONG_ITOLDEFFECTS) { vdepth = 5; vdelta = -vdelta; // yes, IT does vibrato backwards in old-effects mode. try it. } else { vdepth = 6; } vdelta = (vdelta * (int)chan->vibrato_depth) >> vdepth; if (csf->flags & SONG_LINEARSLIDES) { int l = abs(vdelta); if (vdelta < 0) { vdelta = _muldiv(period, linear_slide_up_table[l >> 2], 0x10000) - period; if (l & 0x03) vdelta += _muldiv(period, fine_linear_slide_up_table[l & 0x03], 0x10000) - period; } else { vdelta = _muldiv(period, linear_slide_down_table[l >> 2], 0x10000) - period; if (l & 0x03) vdelta += _muldiv(period, fine_linear_slide_down_table[l & 0x03], 0x10000) - period; } } period -= vdelta; // handle on tick-N, or all ticks if not in old-effects mode if (!(csf->flags & SONG_FIRSTTICK) || !(csf->flags & SONG_ITOLDEFFECTS)) { chan->vibrato_position = (vibpos + 4 * chan->vibrato_speed) & 0xFF; } return period; } static inline int rn_sample_vibrato(song_voice_t *chan, int period) { unsigned int vibpos = chan->autovib_position & 0xFF; int vdelta, adepth; song_sample_t *pins = chan->ptr_sample; /* 1) Mov AX, [SomeVariableNameRelatingToVibrato] 2) Add AL, Rate 3) AdC AH, 0 4) AH contains the depth of the vibrato as a fine-linear slide. 5) Mov [SomeVariableNameRelatingToVibrato], AX ; For the next cycle. */ adepth = chan->autovib_depth; // (1) adepth += pins->vib_rate & 0xff; // (2 & 3) /* need this cast -- if adepth is unsigned, large autovib will crash the mixer (why? I don't know!) but if vib_depth is changed to signed, that screws up other parts of the code. ugh. */ adepth = MIN(adepth, (int) (pins->vib_depth << 8)); chan->autovib_depth = adepth; // (5) adepth >>= 8; // (4) chan->autovib_position += pins->vib_speed; switch(pins->vib_type) { case VIB_SINE: default: vdelta = sine_table[vibpos]; break; case VIB_RAMP_DOWN: vdelta = ramp_down_table[vibpos]; break; case VIB_SQUARE: vdelta = square_table[vibpos]; break; case VIB_RANDOM: vdelta = 128 * ((double) rand() / RAND_MAX) - 64; break; } vdelta = (vdelta * adepth) >> 6; int l = abs(vdelta); if (vdelta < 0) { vdelta = _muldiv(period, linear_slide_up_table[l >> 2], 0x10000) - period; if (l & 0x03) vdelta += _muldiv(period, fine_linear_slide_up_table[l & 0x03], 0x10000) - period; } else { vdelta = _muldiv(period, linear_slide_down_table[l >> 2], 0x10000) - period; if (l & 0x03) vdelta += _muldiv(period, fine_linear_slide_down_table[l & 0x03], 0x10000) - period; } return period - vdelta; } static inline void rn_process_envelope(song_voice_t *chan, int *nvol) { song_instrument_t *penv = chan->ptr_instrument; int vol = *nvol; // Volume Envelope if (chan->flags & CHN_VOLENV && penv->vol_env.nodes) { int envpos = chan->vol_env_position; unsigned int pt = penv->vol_env.nodes - 1; for (unsigned int i = 0; i < (unsigned int)(penv->vol_env.nodes - 1); i++) { if (envpos <= penv->vol_env.ticks[i]) { pt = i; break; } } int x2 = penv->vol_env.ticks[pt]; int x1, envvol; if (envpos >= x2) { envvol = penv->vol_env.values[pt] << 2; x1 = x2; } else if (pt) { envvol = penv->vol_env.values[pt-1] << 2; x1 = penv->vol_env.ticks[pt-1]; } else { envvol = 0; x1 = 0; } if (envpos > x2) envpos = x2; if (x2 > x1 && envpos > x1) { envvol += ((envpos - x1) * (((int)penv->vol_env.values[pt]<<2) - envvol)) / (x2 - x1); } envvol = CLAMP(envvol, 0, 256); vol = (vol * envvol) >> 8; } // Panning Envelope if ((chan->flags & CHN_PANENV) && (penv->pan_env.nodes)) { int envpos = chan->pan_env_position; unsigned int pt = penv->pan_env.nodes - 1; for (unsigned int i=0; i<(unsigned int)(penv->pan_env.nodes-1); i++) { if (envpos <= penv->pan_env.ticks[i]) { pt = i; break; } } int x2 = penv->pan_env.ticks[pt], y2 = penv->pan_env.values[pt]; int x1, envpan; if (envpos >= x2) { envpan = y2; x1 = x2; } else if (pt) { envpan = penv->pan_env.values[pt-1]; x1 = penv->pan_env.ticks[pt-1]; } else { envpan = 128; x1 = 0; } if (x2 > x1 && envpos > x1) { envpan += ((envpos - x1) * (y2 - envpan)) / (x2 - x1); } envpan = CLAMP(envpan, 0, 64); int pan = chan->final_panning; if (pan >= 128) { pan += ((envpan - 32) * (256 - pan)) / 32; } else { pan += ((envpan - 32) * (pan)) / 32; } chan->final_panning = pan; } // FadeOut volume if (chan->flags & CHN_NOTEFADE) { unsigned int fadeout = penv->fadeout; if (fadeout) { chan->fadeout_volume -= fadeout << 1; if (chan->fadeout_volume <= 0) chan->fadeout_volume = 0; vol = (vol * chan->fadeout_volume) >> 16; } else if (!chan->fadeout_volume) { vol = 0; } } // Pitch/Pan separation if (penv->pitch_pan_separation && chan->final_panning && chan->note) { // PPS value is 1/512, i.e. PPS=1 will adjust by 8/512 = 1/64 for each 8 semitones // with PPS = 32 / PPC = C-5, E-6 will pan hard right (and D#6 will not) chan->final_panning += ((int) (chan->note - penv->pitch_pan_center - 1) * penv->pitch_pan_separation) / 4; } *nvol = vol; } static inline int rn_arpeggio(song_t *csf, song_voice_t *chan, int period) { int a; switch ((csf->current_speed - csf->tick_count) % 3) { case 1: a = chan->mem_arpeggio >> 4; break; case 2: a = chan->mem_arpeggio & 0xf; break; default: a = 0; } if (!a) return period; a = linear_slide_up_table[a * 16]; return ((csf->flags & SONG_LINEARSLIDES) ? _muldiv(period, a, 65536) : _muldiv(period, 65536, a)); } static inline void rn_pitch_filter_envelope(song_voice_t *chan, int *nenvpitch, int *nperiod) { song_instrument_t *penv = chan->ptr_instrument; int envpos = chan->pitch_env_position; unsigned int pt = penv->pitch_env.nodes - 1; int period = *nperiod; int envpitch = *nenvpitch; for (unsigned int i = 0; i < (unsigned int)(penv->pitch_env.nodes - 1); i++) { if (envpos <= penv->pitch_env.ticks[i]) { pt = i; break; } } int x2 = penv->pitch_env.ticks[pt]; int x1; if (envpos >= x2) { envpitch = (((int)penv->pitch_env.values[pt]) - 32) * 8; x1 = x2; } else if (pt) { envpitch = (((int)penv->pitch_env.values[pt - 1]) - 32) * 8; x1 = penv->pitch_env.ticks[pt - 1]; } else { envpitch = 0; x1 = 0; } if (envpos > x2) envpos = x2; if (x2 > x1 && envpos > x1) { int envpitchdest = (((int)penv->pitch_env.values[pt]) - 32) * 8; envpitch += ((envpos - x1) * (envpitchdest - envpitch)) / (x2 - x1); } // clamp to -255/255? envpitch = CLAMP(envpitch, -256, 256); // Pitch Envelope if (!(penv->flags & ENV_FILTER)) { int l = abs(envpitch); if (l > 255) l = 255; period = _muldiv(period, (envpitch < 0 ? linear_slide_down_table : linear_slide_up_table)[l], 0x10000); } *nperiod = period; *nenvpitch = envpitch; } static inline void _process_envelope(song_voice_t *chan, song_instrument_t *penv, song_envelope_t *envelope, int *position, uint32_t env_flag, uint32_t loop_flag, uint32_t sus_flag, uint32_t fade_flag) { int start = 0, end = 0x7fffffff; if (!(chan->flags & env_flag)) { return; } (*position)++; if ((penv->flags & sus_flag) && !(chan->flags & CHN_KEYOFF)) { start = envelope->ticks[envelope->sustain_start]; end = envelope->ticks[envelope->sustain_end] + 1; fade_flag = 0; } else if (penv->flags & loop_flag) { start = envelope->ticks[envelope->loop_start]; end = envelope->ticks[envelope->loop_end] + 1; fade_flag = 0; } else { // End of envelope (?) start = end = envelope->ticks[envelope->nodes - 1]; } if (*position >= end) { if (fade_flag && !envelope->values[envelope->nodes - 1]) { chan->fadeout_volume = chan->final_volume = 0; } *position = start; chan->flags |= fade_flag; // only relevant for volume envelope } } static inline void rn_increment_env_pos(song_voice_t *chan) { song_instrument_t *penv = chan->ptr_instrument; _process_envelope(chan, penv, &penv->vol_env, &chan->vol_env_position, CHN_VOLENV, ENV_VOLLOOP, ENV_VOLSUSTAIN, CHN_NOTEFADE); _process_envelope(chan, penv, &penv->pan_env, &chan->pan_env_position, CHN_PANENV, ENV_PANLOOP, ENV_PANSUSTAIN, 0); _process_envelope(chan, penv, &penv->pitch_env, &chan->pitch_env_position, CHN_PITCHENV, ENV_PITCHLOOP, ENV_PITCHSUSTAIN, 0); } static inline int rn_update_sample(song_t *csf, song_voice_t *chan, int nchan, int master_vol) { // Adjusting volumes if (csf->mix_channels < 2 || (csf->flags & SONG_NOSTEREO)) { chan->right_volume_new = (chan->final_volume * master_vol) >> 8; chan->left_volume_new = chan->right_volume_new; } else if ((chan->flags & CHN_SURROUND) && !(csf->mix_flags & SNDMIX_NOSURROUND)) { chan->right_volume_new = (chan->final_volume * master_vol) >> 8; chan->left_volume_new = -chan->right_volume_new; } else { int pan = ((int) chan->final_panning) - 128; pan *= (int) csf->pan_separation; pan /= 128; if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument && chan->ptr_instrument->midi_channel_mask > 0) GM_Pan(nchan, pan); pan += 128; pan = CLAMP(pan, 0, 256); if (csf->mix_flags & SNDMIX_REVERSESTEREO) pan = 256 - pan; int realvol = (chan->final_volume * master_vol) >> (8 - 1); chan->left_volume_new = (realvol * pan) >> 8; chan->right_volume_new = (realvol * (256 - pan)) >> 8; } // Clipping volumes if (chan->right_volume_new > 0xFFFF) chan->right_volume_new = 0xFFFF; if (chan->left_volume_new > 0xFFFF) chan->left_volume_new = 0xFFFF; // Check IDO if (csf->mix_flags & SNDMIX_NORESAMPLING) { chan->flags &= ~(CHN_HQSRC); chan->flags |= CHN_NOIDO; } else { chan->flags &= ~(CHN_NOIDO | CHN_HQSRC); if (chan->increment == 0x10000) { chan->flags |= CHN_NOIDO; } else { if (!(csf->mix_flags & SNDMIX_HQRESAMPLER) && !(csf->mix_flags & SNDMIX_ULTRAHQSRCMODE)) { if (chan->increment >= 0xFF00) chan->flags |= CHN_NOIDO; } } } chan->right_volume_new >>= MIXING_ATTENUATION; chan->left_volume_new >>= MIXING_ATTENUATION; chan->right_ramp = chan->left_ramp = 0; // Checking Ping-Pong Loops if (chan->flags & CHN_PINGPONGFLAG) chan->increment = -chan->increment; if (chan->flags & CHN_MUTE) { chan->left_volume = chan->right_volume = 0; } else if (!(csf->mix_flags & SNDMIX_NORAMPING) && chan->flags & CHN_VOLUMERAMP && (chan->right_volume != chan->right_volume_new || chan->left_volume != chan->left_volume_new)) { // Setting up volume ramp int ramp_length = volume_ramp_samples; int right_delta = ((chan->right_volume_new - chan->right_volume) << VOLUMERAMPPRECISION); int left_delta = ((chan->left_volume_new - chan->left_volume) << VOLUMERAMPPRECISION); if (csf->mix_flags & SNDMIX_HQRESAMPLER) { if (chan->right_volume | chan->left_volume && chan->right_volume_new | chan->left_volume_new && !(chan->flags & CHN_FASTVOLRAMP)) { ramp_length = csf->buffer_count; int l = (1 << (VOLUMERAMPPRECISION - 1)); int r =(int) volume_ramp_samples; ramp_length = CLAMP(ramp_length, l, r); } } chan->right_ramp = right_delta / ramp_length; chan->left_ramp = left_delta / ramp_length; chan->right_volume = chan->right_volume_new - ((chan->right_ramp * ramp_length) >> VOLUMERAMPPRECISION); chan->left_volume = chan->left_volume_new - ((chan->left_ramp * ramp_length) >> VOLUMERAMPPRECISION); if (chan->right_ramp | chan->left_ramp) { chan->ramp_length = ramp_length; } else { chan->flags &= ~CHN_VOLUMERAMP; chan->right_volume = chan->right_volume_new; chan->left_volume = chan->left_volume_new; } } else { chan->flags &= ~CHN_VOLUMERAMP; chan->right_volume = chan->right_volume_new; chan->left_volume = chan->left_volume_new; } chan->right_ramp_volume = chan->right_volume << VOLUMERAMPPRECISION; chan->left_ramp_volume = chan->left_volume << VOLUMERAMPPRECISION; // Adding the channel in the channel list csf->voice_mix[csf->num_voices++] = nchan; if (csf->num_voices >= MAX_VOICES) return 0; return 1; } // XXX Rename this //Ranges: // chan_num = 0..63 // freq = frequency in Hertz // vol = 0..16384 // chan->instrument_volume = 0..64 (corresponds to the sample global volume and instrument global volume) static inline void rn_gen_key(song_t *csf, song_voice_t *chan, int chan_num, int freq, int vol) { if (chan->flags & CHN_MUTE) { // don't do anything return; } else if (csf->flags & SONG_INSTRUMENTMODE && chan->ptr_instrument && chan->ptr_instrument->midi_channel_mask > 0) { MidiBendMode BendMode = MIDI_BEND_NORMAL; /* TODO: If we're expecting a large bend exclusively * in either direction, update BendMode to indicate so. * This can be used to extend the range of MIDI pitch bending. */ int volume = vol; if ((chan->flags & CHN_ADLIB) && volume > 0) { // find_volume translates volume from range 0..16384 to range 0..127. But why with that method? volume = find_volume((unsigned short) volume) * chan->instrument_volume / 64; } else { // This gives a value in the range 0..127. volume = volume * chan->instrument_volume / 8192; } GM_SetFreqAndVol(chan_num, freq, volume, BendMode, chan->flags & CHN_KEYOFF); } if (chan->flags & CHN_ADLIB) { // Scaling is needed to get a frequency that matches with ST3 notes. // 8363 is st3s middle C sample rate. 261.625 is the Hertz for middle C in a tempered scale (A4 = 440) //Also, note that to be true to ST3, the frequencies should be quantized, like using the glissando control. // OPL_Patch is called in csf_process_effects, from csf_read_note or csf_process_tick, before calling this method. int oplmilliHertz = (long long int)freq*261625L/8363L; OPL_HertzTouch(chan_num, oplmilliHertz, chan->flags & CHN_KEYOFF); // ST32 ignores global & master volume in adlib mode, guess we should do the same -Bisqwit // This gives a value in the range 0..63. // log_appendf(2,"vol: %d, voiceinsvol: %d", vol , chan->instrument_volume); OPL_Touch(chan_num, vol * chan->instrument_volume * 63 / (1 << 20)); if (csf->flags&SONG_NOSTEREO) { OPL_Pan(chan_num, 128); } else { OPL_Pan(chan_num, chan->final_panning); } } } static inline void update_vu_meter(song_voice_t *chan) { // Update VU-Meter (final_volume is 14-bit) // TODO: missing background channels by doing it this way. // need to use nMasterCh, add the vu meters for each physical voice, and bit shift. uint32_t vutmp = chan->final_volume >> (14 - 8); if (vutmp > 0xFF) vutmp = 0xFF; if (chan->flags & CHN_ADLIB) { if (chan->strike>2) { chan->vu_meter=(0xFF*chan->final_volume)>>14;} // fake VU decay (intentionally similar to ST3) if (chan->vu_meter > VUMETER_DECAY) { chan->vu_meter -= VUMETER_DECAY; } else { chan->vu_meter = 0; } if (chan->vu_meter >= 0x100) { chan->vu_meter = vutmp; } } else if (vutmp && chan->current_sample_data) { // can't fake the funk int n; int pos = chan->position; // necessary on 64-bit systems (sometimes pos == -1, weird) if (chan->flags & CHN_16BIT) { const signed short *p = (signed short *)(chan->current_sample_data); if (chan->flags & CHN_STEREO) n = p[2 * pos]; else n = p[pos]; n >>= 8; } else { const signed char *p = (signed char *)(chan->current_sample_data); if (chan->flags & CHN_STEREO) n = p[2 * pos]; else n = p[pos]; } if (n < 0) n = -n; vutmp *= n; vutmp >>= 7; // 0..255 chan->vu_meter = vutmp; } else { chan->vu_meter = 0; } } //////////////////////////////////////////////////////////////////////////////////////////// int csf_init_player(song_t *csf, int reset) { if (max_voices > MAX_VOICES) max_voices = MAX_VOICES; csf->mix_frequency = CLAMP(csf->mix_frequency, 4000, MAX_SAMPLE_RATE); volume_ramp_samples = (csf->mix_frequency * VOLUMERAMPLEN) / 100000; if (volume_ramp_samples < 8) volume_ramp_samples = 8; if (csf->mix_flags & SNDMIX_NORAMPING) volume_ramp_samples = 2; g_dry_rofs_vol = g_dry_lofs_vol = 0; if (reset) { global_vu_left = 0; global_vu_right = 0; } initialize_eq(reset, csf->mix_frequency); // I don't know why, but this "if" makes it work at the desired sample rate instead of 4000. // the "4000Hz" value comes from csf_reset, but I don't yet understand why the opl keeps that value, if // each call to Fmdrv_Init generates a new opl. if (csf->mix_frequency != 4000) { Fmdrv_Init(csf->mix_frequency); } GM_Reset(0); return 1; } unsigned int csf_read(song_t *csf, void * v_buffer, unsigned int bufsize) { uint8_t * buffer = (uint8_t *)v_buffer; convert_t convert_func = clip_32_to_8; int32_t vu_min[2]; int32_t vu_max[2]; unsigned int bufleft, max, sample_size, count, smpcount, mix_stat=0; vu_min[0] = vu_min[1] = 0x7FFFFFFF; vu_max[0] = vu_max[1] = -0x7FFFFFFF; csf->mix_stat = 0; sample_size = csf->mix_channels; if (csf->mix_bits_per_sample == 16) { sample_size *= 2; convert_func = clip_32_to_16; } else if (csf->mix_bits_per_sample == 24) { sample_size *= 3; convert_func = clip_32_to_24; } else if (csf->mix_bits_per_sample == 32) { sample_size *= 4; convert_func = clip_32_to_32; } max = bufsize / sample_size; if (!max || !buffer) { return 0; } bufleft = max; if (csf->flags & SONG_ENDREACHED) bufleft = 0; // skip the loop while (bufleft > 0) { // Update Channel Data if (!csf->buffer_count) { if (!(csf->mix_flags & SNDMIX_DIRECTTODISK)) csf->buffer_count = bufleft; if (!csf_read_note(csf)) { csf->flags |= SONG_ENDREACHED; if (csf->stop_at_order > -1) return 0; /* faster */ if (bufleft == max) break; if (!(csf->mix_flags & SNDMIX_DIRECTTODISK)) csf->buffer_count = bufleft; } if (!csf->buffer_count) break; } count = csf->buffer_count; if (count > MIXBUFFERSIZE) count = MIXBUFFERSIZE; if (count > bufleft) count = bufleft; if (!count) break; smpcount = count; // Resetting sound buffer stereo_fill(csf->mix_buffer, smpcount, &g_dry_rofs_vol, &g_dry_lofs_vol); if (csf->mix_channels >= 2) { smpcount *= 2; csf->mix_stat += csf_create_stereo_mix(csf, count); } else { csf->mix_stat += csf_create_stereo_mix(csf, count); mono_from_stereo(csf->mix_buffer, count); } // Handle eq if (csf->mix_channels >= 2) eq_stereo(csf, csf->mix_buffer, count); else eq_mono(csf, csf->mix_buffer, count); mix_stat++; if (csf->multi_write) { /* multi doesn't actually write meaningful data into 'buffer', so we can use that as temp space for converting */ for (unsigned int n = 0; n < 64; n++) { if (csf->multi_write[n].used) { unsigned int bytes = convert_func(buffer, csf->multi_write[n].buffer, smpcount, vu_min, vu_max); csf->multi_write[n].write(csf->multi_write[n].data, buffer, bytes); } else { csf->multi_write[n].silence(csf->multi_write[n].data, smpcount * ((csf->mix_bits_per_sample + 7) / 8)); } } } else { // Perform clipping + VU-Meter buffer += convert_func(buffer, csf->mix_buffer, smpcount, vu_min, vu_max); } // Buffer ready bufleft -= count; csf->buffer_count -= count; } if (bufleft) memset(buffer, (csf->mix_bits_per_sample == 8) ? 0x80 : 0, bufleft * sample_size); // VU-Meter //Reduce range to 8bits signed (-128 to 127). vu_min[0] >>= 19; vu_min[1] >>= 19; vu_max[0] >>= 19; vu_max[1] >>= 19; if (vu_max[0] < vu_min[0]) vu_max[0] = vu_min[0]; if (vu_max[1] < vu_min[1]) vu_max[1] = vu_min[1]; global_vu_left = (unsigned int)(vu_max[0] - vu_min[0]); global_vu_right = (unsigned int)(vu_max[1] - vu_min[1]); if (mix_stat) { csf->mix_stat += mix_stat - 1; csf->mix_stat /= mix_stat; } return max - bufleft; } ///////////////////////////////////////////////////////////////////////////// // Handles navigation/effects static int increment_order(song_t *csf) { csf->process_row = csf->break_row; /* [ProcessRow = BreakRow] */ csf->break_row = 0; /* [BreakRow = 0] */ /* some ugly copypasta, this should be less dumb */ if (csf->flags & SONG_PATTERNPLAYBACK) { /* process_order is hijacked as a "playback initiated" flag -- otherwise repeat count would be incremented as soon as pattern playback started. (this is a stupid hack) */ if (csf->process_order) { if (++csf->repeat_count) { if (UNLIKELY(csf->repeat_count < 0)) { csf->repeat_count = 1; // it overflowed! } } else { csf->process_row = PROCESS_NEXT_ORDER; return 0; } } else { csf->process_order = 1; } } else if (!(csf->flags & SONG_ORDERLOCKED)) { /* [Increase ProcessOrder] */ /* [while Order[ProcessOrder] = 0xFEh, increase ProcessOrder] */ do { csf->process_order++; } while (csf->orderlist[csf->process_order] == ORDER_SKIP); /* [if Order[ProcessOrder] = 0xFFh, ProcessOrder = 0] (... or just stop playing) */ if (csf->orderlist[csf->process_order] == ORDER_LAST) { if (++csf->repeat_count) { if (UNLIKELY(csf->repeat_count < 0)) { csf->repeat_count = 1; // it overflowed! } } else { csf->process_row = PROCESS_NEXT_ORDER; return 0; } csf->process_order = 0; while (csf->orderlist[csf->process_order] == ORDER_SKIP) csf->process_order++; } if (csf->orderlist[csf->process_order] >= MAX_PATTERNS) { // what the butt? csf->process_row = PROCESS_NEXT_ORDER; return 0; } /* [CurrentPattern = Order[ProcessOrder]] */ csf->current_order = csf->process_order; csf->current_pattern = csf->orderlist[csf->process_order]; } if (!csf->pattern_size[csf->current_pattern] || !csf->patterns[csf->current_pattern]) { /* okay, this is wrong. allocate the pattern _NOW_ */ csf->patterns[csf->current_pattern] = csf_allocate_pattern(64); csf->pattern_size[csf->current_pattern] = 64; csf->pattern_alloc_size[csf->current_pattern] = 64; } if (csf->process_row >= csf->pattern_size[csf->current_pattern]) { // Cxx to row beyond end of pattern: use 0 instead csf->process_row = 0; } return 1; } int csf_process_tick(song_t *csf) { csf->flags &= ~SONG_FIRSTTICK; /* [Decrease tick counter. Is tick counter 0?] */ if (--csf->tick_count == 0) { /* [-- Yes --] */ /* [Tick counter = Tick counter set (the current 'speed')] */ csf->tick_count = csf->current_speed; /* [Decrease row counter. Is row counter 0?] */ if (--csf->row_count <= 0) { /* [-- Yes --] */ /* [Row counter = 1] this uses zero, in order to simplify SEx effect handling -- SEx has no effect if a channel to its left has already set the delay value. thus we set the row counter there to (value + 1) which is never zero, but 0 and 1 are fundamentally equivalent as far as csf_process_tick is concerned. */ csf->row_count = 0; /* [Increase ProcessRow. Is ProcessRow > NumberOfRows?] */ if (++csf->process_row >= csf->pattern_size[csf->current_pattern]) { /* [-- Yes --] */ if (!increment_order(csf)) return 0; } /* else [-- No --] */ /* [CurrentRow = ProcessRow] */ csf->row = csf->process_row; /* [Update Pattern Variables] (this is handled along with update effects) */ csf->flags |= SONG_FIRSTTICK; } else { /* [-- No --] */ /* Call update-effects for each channel. */ } // Reset channel values song_voice_t *chan = csf->voices; song_note_t *m = csf->patterns[csf->current_pattern] + csf->row * MAX_CHANNELS; for (unsigned int nchan=0; nchanrow_note = m->note; if (m->instrument) chan->last_instrument = m->instrument; chan->row_instr = m->instrument; chan->row_voleffect = m->voleffect; chan->row_volparam = m->volparam; chan->row_effect = m->effect; chan->row_param = m->param; chan->left_volume = chan->left_volume_new; chan->right_volume = chan->right_volume_new; chan->flags &= ~(CHN_PORTAMENTO | CHN_VIBRATO | CHN_TREMOLO); chan->n_command = 0; } csf_process_effects(csf, 1); } else { /* [-- No --] */ /* [Update effects for each channel as required.] */ if (csf_midi_out_note) { song_note_t *m = csf->patterns[csf->current_pattern] + csf->row * MAX_CHANNELS; for (unsigned int nchan=0; nchanflags & SONG_PAUSED) { if (!csf->current_speed) csf->current_speed = csf->initial_speed ?: 6; if (!csf->current_tempo) csf->current_tempo = csf->initial_tempo ?: 125; csf->flags &= ~SONG_FIRSTTICK; if (--csf->tick_count == 0) { csf->tick_count = csf->current_speed; if (--csf->row_count <= 0) { csf->row_count = 0; //csf->flags |= SONG_FIRSTTICK; } // clear channel values (similar to csf_process_tick) for (cn = 0, chan = csf->voices; cn < MAX_CHANNELS; cn++, chan++) { chan->row_note = 0; chan->row_instr = 0; chan->row_voleffect = 0; chan->row_volparam = 0; chan->row_effect = 0; chan->row_param = 0; chan->n_command = 0; } } csf_process_effects(csf, 0); } else { if (!csf_process_tick(csf)) return 0; } //////////////////////////////////////////////////////////////////////////////////// if (!csf->current_tempo) return 0; csf->buffer_count = (csf->mix_frequency * 5 * csf->tempo_factor) / (csf->current_tempo << 8); // chaseback hoo hah if (csf->stop_at_order > -1 && csf->stop_at_row > -1) { if (csf->stop_at_order <= (signed) csf->current_order && csf->stop_at_row <= (signed) csf->row) { return 0; } } //////////////////////////////////////////////////////////////////////////////////// // Update channels data // Master Volume + Pre-Amplification / Attenuation setup uint32_t master_vol = csf->mixing_volume << 2; // yields maximum of 0x200 csf->num_voices = 0; for (cn = 0, chan = csf->voices; cn < MAX_VOICES; cn++, chan++) { /*if(cn == 0 || cn == 1) fprintf(stderr, "considering channel %d (per %d, pos %d/%d, flags %X)\n", (int)cn, chan->period, chan->position, chan->length, chan->flags);*/ if (chan->flags & CHN_NOTEFADE && !(chan->fadeout_volume | chan->right_volume | chan->left_volume)) { chan->length = 0; chan->rofs = chan->lofs = 0; continue; } // Check for unused channel if (cn >= MAX_CHANNELS && !chan->length) { continue; } // Reset channel data chan->increment = 0; chan->final_volume = 0; chan->final_panning = chan->panning + chan->pan_swing + chan->panbrello_delta; chan->ramp_length = 0; // Calc Frequency if (chan->period && (chan->length || (chan->flags & CHN_ADLIB))) { int vol = chan->volume; if (chan->flags & CHN_TREMOLO) vol += chan->tremolo_delta; vol = CLAMP(vol, 0, 256); // Tremor if (chan->n_command == FX_TREMOR) rn_tremor(chan, &vol); // Clip volume vol = CLAMP(vol, 0, 0x100); vol <<= 6; // Process Envelopes if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument) { rn_process_envelope(chan, &vol); } else { // No Envelope: key off => note cut // 1.41-: CHN_KEYOFF|CHN_NOTEFADE if (chan->flags & CHN_NOTEFADE) { chan->fadeout_volume = 0; vol = 0; } } // vol is 14-bits if (vol) { // IMPORTANT: chan->final_volume is 14 bits !!! // -> _muldiv( 14+7, 6+6, 18); => RealVolume: 14-bit result (21+12-19) chan->final_volume = _muldiv (vol * csf->current_global_volume, chan->global_volume * CLAMP(chan->instrument_volume + chan->vol_swing, 0, 64), 1 << 19); } int period = chan->period; if ((chan->flags & (CHN_GLISSANDO|CHN_PORTAMENTO)) == (CHN_GLISSANDO|CHN_PORTAMENTO)) { period = get_period_from_note(get_note_from_period(period), chan->c5speed, csf->flags & SONG_LINEARSLIDES); } // Arpeggio ? if (chan->n_command == FX_ARPEGGIO) period = rn_arpeggio(csf, chan, period); // Pitch/Filter Envelope int envpitch = 0; if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument && (chan->flags & CHN_PITCHENV) && chan->ptr_instrument->pitch_env.nodes) rn_pitch_filter_envelope(chan, &envpitch, &period); // Vibrato if (chan->flags & CHN_VIBRATO) period = rn_vibrato(csf, chan, period); // Sample Auto-Vibrato if (chan->ptr_sample && chan->ptr_sample->vib_depth) { period = rn_sample_vibrato(chan, period); } unsigned int freq = get_freq_from_period(period, csf->flags & SONG_LINEARSLIDES); if (!(chan->flags & CHN_NOTEFADE)) rn_gen_key(csf, chan, cn, freq, vol); // Filter Envelope: controls cutoff frequency if (chan && chan->ptr_instrument && chan->ptr_instrument->flags & ENV_FILTER) { setup_channel_filter(chan, !(chan->flags & CHN_FILTER), envpitch, csf->mix_frequency); } chan->sample_freq = freq; unsigned int ninc = _muldiv(freq, 0x10000, csf->mix_frequency); if (ninc >= 0xFFB0 && ninc <= 0x10090) ninc = 0x10000; if (csf->freq_factor != 128) ninc = (ninc * csf->freq_factor) >> 7; if (ninc > 0xFF0000) ninc = 0xFF0000; chan->increment = (ninc + 1) & ~3; } // Increment envelope position if (csf->flags & SONG_INSTRUMENTMODE && chan->ptr_instrument) rn_increment_env_pos(chan); chan->final_panning = CLAMP(chan->final_panning, 0, 256); // Volume ramping chan->flags &= ~CHN_VOLUMERAMP; if (chan->final_volume || chan->left_volume || chan->right_volume) chan->flags |= CHN_VOLUMERAMP; if (chan->strike) chan->strike--; // Check for too big increment if (((chan->increment >> 16) + 1) >= (int)(chan->loop_end - chan->loop_start)) chan->flags &= ~CHN_LOOP; chan->right_volume_new = chan->left_volume_new = 0; if (!(chan->length && chan->increment)) chan->current_sample_data = NULL; update_vu_meter(chan); if (chan->current_sample_data) { if (!rn_update_sample(csf, chan, cn, master_vol)) break; } else { // Note change but no sample //if (chan->vu_meter > 0xFF) chan->vu_meter = 0; chan->left_volume = chan->right_volume = 0; chan->length = 0; } } // Checking Max Mix Channels reached: ordering by volume if (csf->num_voices >= max_voices && (!(csf->mix_flags & SNDMIX_DIRECTTODISK))) { for (unsigned int i = 0; i < csf->num_voices; i++) { unsigned int j = i; while ((j + 1 < csf->num_voices) && (csf->voices[csf->voice_mix[j]].final_volume < csf->voices[csf->voice_mix[j + 1]].final_volume)) { unsigned int n = csf->voice_mix[j]; csf->voice_mix[j] = csf->voice_mix[j + 1]; csf->voice_mix[j + 1] = n; j++; } } } return 1; } schismtracker-20180209/player/tables.c000066400000000000000000000332221323741476300175510ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "tables.h" const uint8_t vc_portamento_table[16] = { 0x00, 0x01, 0x04, 0x08, 0x10, 0x20, 0x40, 0x60, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; const uint16_t period_table[12] = { 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, 907, }; const uint16_t finetune_table[16] = { 7895, 7941, 7985, 8046, 8107, 8169, 8232, 8280, 8363, 8413, 8463, 8529, 8581, 8651, 8723, 8757, // 8363*2^((i-8)/(12*8)) }; // Tables from ITTECH.TXT const int8_t sine_table[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, }; const int8_t ramp_down_table[256] = { 64, 63, 63, 62, 62, 61, 61, 60, 60, 59, 59, 58, 58, 57, 57, 56, 56, 55, 55, 54, 54, 53, 53, 52, 52, 51, 51, 50, 50, 49, 49, 48, 48, 47, 47, 46, 46, 45, 45, 44, 44, 43, 43, 42, 42, 41, 41, 40, 40, 39, 39, 38, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 29, 29, 28, 28, 27, 27, 26, 26, 25, 25, 24, 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, 17, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, -1, -1, -2, -2, -3, -3, -4, -4, -5, -5, -6, -6, -7, -7, -8, -8, -9, -9,-10,-10,-11,-11,-12,-12,-13,-13,-14,-14,-15,-15,-16, -16,-17,-17,-18,-18,-19,-19,-20,-20,-21,-21,-22,-22,-23,-23,-24, -24,-25,-25,-26,-26,-27,-27,-28,-28,-29,-29,-30,-30,-31,-31,-32, -32,-33,-33,-34,-34,-35,-35,-36,-36,-37,-37,-38,-38,-39,-39,-40, -40,-41,-41,-42,-42,-43,-43,-44,-44,-45,-45,-46,-46,-47,-47,-48, -48,-49,-49,-50,-50,-51,-51,-52,-52,-53,-53,-54,-54,-55,-55,-56, -56,-57,-57,-58,-58,-59,-59,-60,-60,-61,-61,-62,-62,-63,-63,-64, }; const int8_t square_table[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; // volume fade tables for Retrig Note: const int8_t retrig_table_1[16] = { 0, 0, 0, 0, 0, 0, 10, 8, 0, 0, 0, 0, 0, 0, 24, 32 }; const int8_t retrig_table_2[16] = { 0, -1, -2, -4, -8, -16, 0, 0, 0, 1, 2, 4, 8, 16, 0, 0 }; // round(65536 * 2**(n/768)) // 768 = 64 extra-fine finetune steps for 12 notes // Table content is in 16.16 format const uint32_t fine_linear_slide_up_table[16] = { 65536, 65595, 65654, 65714, 65773, 65832, 65892, 65951, 66011, 66071, 66130, 66190, 66250, 66309, 66369, 66429 }; // round(65536 * 2**(-n/768)) // 768 = 64 extra-fine finetune steps for 12 notes // Table content is in 16.16 format // Note that there are a few errors in this table (typos?), but well, this table comes straight from ITTECH.TXT... // Entry 0 (65535) should be 65536 (this value is unused and most likely stored this way so that it fits in a 16-bit integer) // Entry 11 (64888) should be 64889 - rounding error? // Entry 15 (64645) should be 64655 - typo? const uint32_t fine_linear_slide_down_table[16] = { 65535, 65477, 65418, 65359, 65300, 65241, 65182, 65123, 65065, 65006, 64947, 64888, 64830, 64772, 64713, 64645 }; // floor(65536 * 2**(n/192)) // 192 = 16 finetune steps for 12 notes // Table content is in 16.16 format const uint32_t linear_slide_up_table[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, }; // floor(65536 * 2**(-n/192)) // 192 = 16 finetune steps for 12 notes // Table content is in 16.16 format const uint32_t linear_slide_down_table[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, }; /* --------------------------------------------------------------------------------------------------------- */ const char *midi_group_names[17] = { "Piano", "Chromatic Percussion", "Organ", "Guitar", "Bass", "Strings", "Ensemble", "Brass", "Reed", "Pipe", "Synth Lead", "Synth Pad", "Synth Effects", "Ethnic", "Percussive", "Sound Effects", "Percussions", }; const char *midi_program_names[128] = { // 1-8: Piano "Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavi", // 9-16: Chromatic Percussion "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", // 17-24: Organ "Drawbar Organ", "Percussive Organ", "Rock Organ", "Church Organ", "Reed Organ", "Accordion", "Harmonica", "Tango Accordion", // 25-32: Guitar "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", "Electric Guitar (clean)", "Electric Guitar (muted)", "Overdriven Guitar", "Distortion Guitar", "Guitar harmonics", // 33-40 Bass "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", // 41-48 Strings "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", "Orchestral Harp", "Timpani", // 49-56 Ensemble "String Ensemble 1", "String Ensemble 2", "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", // 57-64 Brass "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2", // 65-72 Reed "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", // 73-80 Pipe "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Shakuhachi", "Whistle", "Ocarina", // 81-88 Synth Lead "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)", "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass + lead)", // 89-96 Synth Pad "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", // 97-104 Synth Effects "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)", // 105-112 Ethnic "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bag pipe", "Fiddle", "Shanai", // 113-120 Percussive "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", // 121-128 Sound Effects "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", "Applause", "Gunshot", }; // Notes 25-85 const char *midi_percussion_names[61] = { "Seq Click", "Brush Tap", "Brush Swirl", "Brush Slap", "Brush Swirl W/Attack", "Snare Roll", "Castanet", "Snare Lo", "Sticks", "Bass Drum Lo", "Open Rim Shot", "Acoustic Bass Drum", "Bass Drum 1", "Side Stick", "Acoustic Snare", "Hand Clap", "Electric Snare", "Low Floor Tom", "Closed Hi Hat", "High Floor Tom", "Pedal Hi-Hat", "Low Tom", "Open Hi-Hat", "Low-Mid Tom", "Hi Mid Tom", "Crash Cymbal 1", "High Tom", "Ride Cymbal 1", "Chinese Cymbal", "Ride Bell", "Tambourine", "Splash Cymbal", "Cowbell", "Crash Cymbal 2", "Vibraslap", "Ride Cymbal 2", "Hi Bongo", "Low Bongo", "Mute Hi Conga", "Open Hi Conga", "Low Conga", "High Timbale", "Low Timbale", "High Agogo", "Low Agogo", "Cabasa", "Maracas", "Short Whistle", "Long Whistle", "Short Guiro", "Long Guiro", "Claves", "Hi Wood Block", "Low Wood Block", "Mute Cuica", "Open Cuica", "Mute Triangle", "Open Triangle", "Shaker", "Jingle Bell", "Bell Tree", }; schismtracker-20180209/schism/000077500000000000000000000000001323741476300161235ustar00rootroot00000000000000schismtracker-20180209/schism/audio_loadsave.c000066400000000000000000001216201323741476300212500ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #define NEED_TIME #include "headers.h" #include "it.h" #include "sndfile.h" #include "song.h" #include "slurp.h" #include "page.h" #include "version.h" #include "fmt.h" #include "dmoz.h" #include "it_defs.h" #include "snd_gm.h" #include "midi.h" #include "disko.h" #include #include #include #include // ------------------------------------------------------------------------ char song_filename[PATH_MAX + 1]; char song_basename[NAME_MAX + 1]; // ------------------------------------------------------------------------ // 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(song_t *qq) { int c, n; for (n = 1; n < MAX_INSTRUMENTS; n++) { for (c = 0; c < 25; c++) if (qq->samples[n].name[c] == 0) qq->samples[n].name[c] = 32; qq->samples[n].name[25] = 0; if (!qq->instruments[n]) continue; for (c = 0; c < 25; c++) if (qq->instruments[n]->name[c] == 0) qq->instruments[n]->name[c] = 32; qq->instruments[n]->name[25] = 0; } } // ------------------------------------------------------------------------ // 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 and save flag // clear orderlist => clear title, message, and channel settings void song_new(int flags) { int i; song_lock_audio(); song_stop_unlocked(0); if ((flags & KEEP_PATTERNS) == 0) { song_set_filename(NULL); status.flags &= ~SONG_NEEDS_SAVE; for (i = 0; i < MAX_PATTERNS; i++) { if (current_song->patterns[i]) { csf_free_pattern(current_song->patterns[i]); current_song->patterns[i] = NULL; } current_song->pattern_size[i] = 64; current_song->pattern_alloc_size[i] = 64; } } if ((flags & KEEP_SAMPLES) == 0) { for (i = 1; i < MAX_SAMPLES; i++) { if (current_song->samples[i].data) { csf_free_sample(current_song->samples[i].data); } } memset(current_song->samples, 0, sizeof(current_song->samples)); for (i = 1; i < MAX_SAMPLES; i++) { current_song->samples[i].c5speed = 8363; current_song->samples[i].volume = 64 * 4; current_song->samples[i].global_volume = 64; } } if ((flags & KEEP_INSTRUMENTS) == 0) { for (i = 0; i < MAX_INSTRUMENTS; i++) { if (current_song->instruments[i]) { csf_free_instrument(current_song->instruments[i]); current_song->instruments[i] = NULL; } } } if ((flags & KEEP_ORDERLIST) == 0) { memset(current_song->orderlist, ORDER_LAST, sizeof(current_song->orderlist)); memset(current_song->title, 0, sizeof(current_song->title)); memset(current_song->message, 0, MAX_MESSAGE); for (i = 0; i < 64; i++) { current_song->channels[i].volume = 64; current_song->channels[i].panning = 128; current_song->channels[i].flags = 0; current_song->voices[i].volume = 256; current_song->voices[i].global_volume = current_song->channels[i].volume; current_song->voices[i].panning = current_song->channels[i].panning; current_song->voices[i].flags = current_song->channels[i].flags; current_song->voices[i].cutoff = 0x7F; } } current_song->repeat_count = 0; //song_stop(); csf_forget_history(current_song); song_unlock_audio(); main_song_changed_cb(); } // ------------------------------------------------------------------------------------------------------------ #define LOAD_SONG(x) fmt_##x##_load_song, static fmt_load_song_func load_song_funcs[] = { #include "fmt-types.h" NULL, }; const char *fmt_strerror(int n) { switch (n) { case -LOAD_UNSUPPORTED: return "Unrecognised file type"; case -LOAD_FORMAT_ERROR: return "File format error (corrupt?)"; default: return strerror(errno); } } song_t *song_create_load(const char *file) { fmt_load_song_func *func; int ok = 0, err = 0; slurp_t *s = slurp(file, NULL, 0); if (!s) return NULL; song_t *newsong = csf_allocate(); if (current_song) { newsong->mix_flags = current_song->mix_flags; csf_set_wave_config(newsong, current_song->mix_frequency, current_song->mix_bits_per_sample, current_song->mix_channels); // loaders might override these newsong->row_highlight_major = current_song->row_highlight_major; newsong->row_highlight_minor = current_song->row_highlight_minor; csf_copy_midi_cfg(newsong, current_song); } for (func = load_song_funcs; *func && !ok; func++) { slurp_rewind(s); switch ((*func)(newsong, s, 0)) { case LOAD_SUCCESS: err = 0; ok = 1; break; case LOAD_UNSUPPORTED: err = -LOAD_UNSUPPORTED; continue; case LOAD_FORMAT_ERROR: err = -LOAD_FORMAT_ERROR; break; case LOAD_FILE_ERROR: err = errno; break; } if (err) { csf_free(newsong); unslurp(s); errno = err; return NULL; } } unslurp(s); if (err) { // awwww, nerts! csf_free(newsong); errno = err; return NULL; } newsong->stop_at_order = newsong->stop_at_row = -1; return newsong; } int song_load_unchecked(const char *file) { const char *base = get_basename(file); int was_playing; song_t *newsong; // IT stops the song even if the new song can't be loaded if (status.flags & PLAY_AFTER_LOAD) { was_playing = (song_get_mode() == MODE_PLAYING); } else { was_playing = 0; song_stop(); } log_nl(); log_nl(); log_appendf(2, "Loading %s", base); log_underline(strlen(base) + 8); newsong = song_create_load(file); if (!newsong) { log_appendf(4, " %s", fmt_strerror(errno)); return 0; } song_set_filename(file); song_lock_audio(); csf_free(current_song); current_song = newsong; current_song->repeat_count = 0; max_channels_used = 0; _fix_names(current_song); song_stop_unlocked(0); song_unlock_audio(); if (was_playing && (status.flags & PLAY_AFTER_LOAD)) song_start(); main_song_changed_cb(); status.flags &= ~SONG_NEEDS_SAVE; // print out some stuff const char *tid = current_song->tracker_id; char fmt[] = " %d patterns, %d samples, %d instruments"; int n, nsmp, nins; song_sample_t *smp; song_instrument_t **ins; for (n = 0, smp = current_song->samples + 1, nsmp = 0; n < MAX_SAMPLES; n++, smp++) if (smp->data) nsmp++; for (n = 0, ins = current_song->instruments + 1, nins = 0; n < MAX_INSTRUMENTS; n++, ins++) if (*ins != NULL) nins++; if (tid[0]) log_appendf(5, " %s", tid); if (!nins) *strrchr(fmt, ',') = 0; // cut off 'instruments' log_appendf(5, fmt, csf_get_num_patterns(current_song), nsmp, nins); return 1; } // ------------------------------------------------------------------------------------------------------------ static song_instrument_t 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, disko_t *fp, int iti_file) { n++; // FIXME: this is dumb; really all the numbering should be one-based to make it simple struct it_instrument iti = {}; song_instrument_t *i = current_song->instruments[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->nna; iti.dct = i->dct; iti.dca = i->dca; iti.fadeout = bswapLE16(i->fadeout >> 5); iti.pps = i->pitch_pan_separation; iti.ppc = i->pitch_pan_center; iti.gbv = i->global_volume; iti.dfp = i->panning / 4; if (!(i->flags & ENV_SETPANNING)) iti.dfp |= 0x80; iti.rv = i->vol_swing; iti.rp = i->pan_swing; if (iti_file) { iti.trkvers = bswapLE16(0x1000 | ver_cwtv); } // reserved1 strncpy((char *) iti.name, (char *) i->name, 25); iti.name[25] = 0; iti.ifc = i->ifc; iti.ifr = i->ifr; iti.mch = 0; if(i->midi_channel_mask >= 0x10000) { iti.mch = i->midi_channel_mask - 0x10000; if(iti.mch <= 16) iti.mch = 16; } else if(i->midi_channel_mask & 0xFFFF) { iti.mch = 1; while(!(i->midi_channel_mask & (1 << (iti.mch-1)))) ++iti.mch; } iti.mpr = i->midi_program; iti.mbank = bswapLE16(i->midi_bank); static int iti_map[255]; static int iti_invmap[255]; static int iti_nalloc = 0; 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 o = i->sample_map[j]; if (o > 0 && o < 255 && iti_map[o] == -1) { iti_map[o] = iti_nalloc; iti_invmap[iti_nalloc] = o; iti_nalloc++; } iti.keyboard[2 * j + 1] = iti_map[o]+1; } else { iti.keyboard[2 * j + 1] = i->sample_map[j]; } iti.keyboard[2 * j] = i->note_map[j] - 1; } if (iti_file) { iti.nos = (uint8_t)iti_nalloc; } // envelope stuff from modplug iti.volenv.flags = 0; iti.panenv.flags = 0; iti.pitchenv.flags = 0; if (i->flags & ENV_VOLUME) iti.volenv.flags |= 0x01; if (i->flags & ENV_VOLLOOP) iti.volenv.flags |= 0x02; if (i->flags & ENV_VOLSUSTAIN) iti.volenv.flags |= 0x04; if (i->flags & ENV_VOLCARRY) iti.volenv.flags |= 0x08; iti.volenv.num = i->vol_env.nodes; iti.volenv.lpb = i->vol_env.loop_start; iti.volenv.lpe = i->vol_env.loop_end; iti.volenv.slb = i->vol_env.sustain_start; iti.volenv.sle = i->vol_env.sustain_end; if (i->flags & ENV_PANNING) iti.panenv.flags |= 0x01; if (i->flags & ENV_PANLOOP) iti.panenv.flags |= 0x02; if (i->flags & ENV_PANSUSTAIN) iti.panenv.flags |= 0x04; if (i->flags & ENV_PANCARRY) iti.panenv.flags |= 0x08; iti.panenv.num = i->pan_env.nodes; iti.panenv.lpb = i->pan_env.loop_start; iti.panenv.lpe = i->pan_env.loop_end; iti.panenv.slb = i->pan_env.sustain_start; iti.panenv.sle = i->pan_env.sustain_end; if (i->flags & ENV_PITCH) iti.pitchenv.flags |= 0x01; if (i->flags & ENV_PITCHLOOP) iti.pitchenv.flags |= 0x02; if (i->flags & ENV_PITCHSUSTAIN) iti.pitchenv.flags |= 0x04; if (i->flags & ENV_PITCHCARRY) iti.pitchenv.flags |= 0x08; if (i->flags & ENV_FILTER) iti.pitchenv.flags |= 0x80; iti.pitchenv.num = i->pitch_env.nodes; iti.pitchenv.lpb = i->pitch_env.loop_start; iti.pitchenv.lpe = i->pitch_env.loop_end; iti.pitchenv.slb = i->pitch_env.sustain_start; iti.pitchenv.sle = i->pitch_env.sustain_end; for (int j = 0; j < 25; j++) { iti.volenv.data[3 * j] = i->vol_env.values[j]; iti.volenv.data[3 * j + 1] = i->vol_env.ticks[j] & 0xFF; iti.volenv.data[3 * j + 2] = i->vol_env.ticks[j] >> 8; iti.panenv.data[3 * j] = i->pan_env.values[j] - 32; iti.panenv.data[3 * j + 1] = i->pan_env.ticks[j] & 0xFF; iti.panenv.data[3 * j + 2] = i->pan_env.ticks[j] >> 8; iti.pitchenv.data[3 * j] = i->pitch_env.values[j] - 32; iti.pitchenv.data[3 * j + 1] = i->pitch_env.ticks[j] & 0xFF; iti.pitchenv.data[3 * j + 2] = i->pitch_env.ticks[j] >> 8; } // ITI files *need* to write 554 bytes due to alignment, but in a song it doesn't matter disko_write(fp, &iti, sizeof(iti)); if (iti_file) { if (sizeof(iti) < 554) { for (int j = sizeof(iti); j < 554; j++) { disko_write(fp, "\x0", 1); } } assert(sizeof(iti) <= 554); unsigned int qp = 554; /* okay, now go through samples */ for (int j = 0; j < iti_nalloc; j++) { int o = iti_invmap[ j ]; iti_map[o] = qp; qp += 80; /* header is 80 bytes */ save_its_header(fp, current_song->samples + o); } for (int j = 0; j < iti_nalloc; j++) { unsigned int op, tmp; int o = iti_invmap[ j ]; song_sample_t *smp = current_song->samples + o; op = disko_tell(fp); tmp = bswapLE32(op); disko_seek(fp, iti_map[o]+0x48, SEEK_SET); disko_write(fp, &tmp, 4); disko_seek(fp, op, SEEK_SET); csf_write_sample(fp, smp, SF_LE | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M)); } } } // NOBODY expects the Spanish Inquisition! static void _save_it_pattern(disko_t *fp, song_note_t *pat, int patsize) { song_note_t *noteptr = pat; song_note_t lastnote[64] = {}; uint8_t initmask[64] = {}; uint8_t lastmask[64]; unsigned short pos = 0; uint8_t data[65536]; memset(lastmask, 0xff, 64); for (int row = 0; row < patsize; row++) { for (int chan = 0; chan < 64; chan++, noteptr++) { uint8_t m = 0; // current mask int vol = -1; unsigned int note = noteptr->note; uint8_t effect = noteptr->effect, param = noteptr->param; if (note) { m |= 1; if (note < 0x80) note--; } if (noteptr->instrument) m |= 2; switch (noteptr->voleffect) { default: break; case VOLFX_VOLUME: vol = MIN(noteptr->volparam, 64); break; case VOLFX_FINEVOLUP: vol = MIN(noteptr->volparam, 9) + 65; break; case VOLFX_FINEVOLDOWN: vol = MIN(noteptr->volparam, 9) + 75; break; case VOLFX_VOLSLIDEUP: vol = MIN(noteptr->volparam, 9) + 85; break; case VOLFX_VOLSLIDEDOWN: vol = MIN(noteptr->volparam, 9) + 95; break; case VOLFX_PORTADOWN: vol = MIN(noteptr->volparam, 9) + 105; break; case VOLFX_PORTAUP: vol = MIN(noteptr->volparam, 9) + 115; break; case VOLFX_PANNING: vol = MIN(noteptr->volparam, 64) + 128; break; case VOLFX_VIBRATODEPTH: vol = MIN(noteptr->volparam, 9) + 203; break; case VOLFX_VIBRATOSPEED: vol = 203; break; case VOLFX_TONEPORTAMENTO: vol = MIN(noteptr->volparam, 9) + 193; break; } if (vol != -1) m |= 4; csf_export_s3m_effect(&effect, ¶m, 1); if (effect || 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->instrument == lastnote[chan].instrument) && (initmask[chan] & 2)) { m &= ~2; m |= 0x20; } else { lastnote[chan].instrument = noteptr->instrument; initmask[chan] |= 2; } } if (m & 4) { if ((vol == lastnote[chan].volparam) && (initmask[chan] & 4)) { m &= ~4; m |= 0x40; } else { lastnote[chan].volparam = vol; initmask[chan] |= 4; } } if (m & 8) { if ((effect == lastnote[chan].effect) && (param == lastnote[chan].param) && (initmask[chan] & 8)) { m &= ~8; m |= 0x80; } else { lastnote[chan].effect = effect; 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->instrument; if (m & 4) data[pos++] = vol; if (m & 8) { data[pos++] = effect; data[pos++] = param; } } // end channel data[pos++] = 0; } // end row // write the data to the file (finally!) unsigned short h[4] = {0}; h[0] = bswapLE16(pos); h[1] = bswapLE16(patsize); // h[2] and h[3] are meaningless disko_write(fp, &h, 8); disko_write(fp, data, pos); } // why on earth isn't this using the 'song' parameter? will finding this out hurt my head? static int _save_it(disko_t *fp, UNUSED song_t *song) { struct it_file hdr = {}; int n; int nord, nins, nsmp, npat; int msglen = strlen(current_song->message); int warned_adlib = 0; uint32_t para_ins[256], para_smp[256], para_pat[256]; // how much extra data is stuffed between the parapointers and the rest of the file // (2 bytes for edit history length, and 8 per entry including the current session) uint32_t extra = 2 + 8 * current_song->histlen + 8; // TODO complain about nonstandard stuff? or just stop saving it to begin with /* IT always saves at least two orders -- and requires an extra order at the end (which gets chopped!) However, the loader refuses to load files with too much data in the orderlist, so in the pathological case where order 255 has data, writing an extra 0xFF at the end will result in a file that can't be loaded back (for now). Eventually this can be fixed, but at least for a while it's probably a great idea not to save things that other versions won't load. */ nord = csf_get_num_orders(current_song); nord = CLAMP(nord + 1, 2, MAX_ORDERS); nins = csf_get_num_instruments(current_song); nsmp = csf_get_num_samples(current_song); // IT always saves at least one pattern. npat = csf_get_num_patterns(current_song) ?: 1; hdr.id = bswapLE32(0x4D504D49); // IMPM strncpy((char *) hdr.songname, current_song->title, 25); hdr.songname[25] = 0; hdr.hilight_major = current_song->row_highlight_major; hdr.hilight_minor = current_song->row_highlight_minor; 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(0x1000 | ver_cwtv); // 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++) { song_instrument_t *i = current_song->instruments[n]; if (!i) continue; if (i->flags & ENV_FILTER) { hdr.cmwt = bswapLE16(0x0217); break; } } hdr.flags = 0; hdr.special = 2 | 4; // 2 = edit history, 4 = row highlight 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_PITCHBEND) { hdr.flags |= 64; hdr.pwd = midi_pitch_depth; } if (current_song->flags & SONG_EMBEDMIDICFG) { hdr.flags |= 128; hdr.special |= 8; extra += sizeof(midi_config_t); } hdr.flags = bswapLE16(hdr.flags); if (msglen) { hdr.special |= 1; msglen++; } hdr.special = bswapLE16(hdr.special); // 16+ = reserved (always off?) hdr.globalvol = current_song->initial_global_volume; hdr.mv = current_song->mixing_volume; hdr.speed = current_song->initial_speed; hdr.tempo = current_song->initial_tempo; hdr.sep = current_song->pan_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] = ((current_song->channels[n].flags & CHN_SURROUND) ? 100 : (current_song->channels[n].panning / 4)); hdr.chnvol[n] = current_song->channels[n].volume; if (current_song->channels[n].flags & CHN_MUTE) hdr.chnpan[n] += 128; } disko_write(fp, &hdr, sizeof(hdr)); disko_write(fp, current_song->orderlist, nord); // we'll get back to these later disko_write(fp, para_ins, 4*nins); disko_write(fp, para_smp, 4*nsmp); disko_write(fp, para_pat, 4*npat); // edit history (see scripts/timestamp.py) // Should™ be fully compatible with Impulse Tracker. struct timeval savetime, elapsed; struct tm loadtm; uint16_t h; //x86/x64 compatibility time_t thetime = current_song->editstart.tv_sec; localtime_r(&thetime, &loadtm); gettimeofday(&savetime, NULL); timersub(&savetime, ¤t_song->editstart, &elapsed); // item count h = current_song->histlen + 1; h = bswapLE16(h); disko_write(fp, &h, 2); // old data disko_write(fp, current_song->histdata, 8 * current_song->histlen); // 16-bit date h = loadtm.tm_mday | ((loadtm.tm_mon + 1) << 5) | ((loadtm.tm_year - 80) << 9); h = bswapLE16(h); disko_write(fp, &h, 2); // 16-bit time h = (loadtm.tm_sec / 2) | (loadtm.tm_min << 5) | (loadtm.tm_hour << 11); h = bswapLE16(h); disko_write(fp, &h, 2); // 32-bit DOS tick count (tick = 1/18.2 second; 54945 * 18.2 = 999999 which is Close Enough) uint32_t ticks = elapsed.tv_sec * 182 / 10 + elapsed.tv_usec / 54945; ticks = bswapLE32(ticks); disko_write(fp, &ticks, 4); // here comes MIDI configuration // here comes MIDI configuration // right down MIDI configuration lane if (current_song->flags & SONG_EMBEDMIDICFG) { disko_write(fp, ¤t_song->midi_config, sizeof(current_song->midi_config)); } disko_write(fp, current_song->message, msglen); // instruments, samples, and patterns for (n = 0; n < nins; n++) { para_ins[n] = disko_tell(fp); para_ins[n] = bswapLE32(para_ins[n]); _save_it_instrument(n, fp, 0); } for (n = 0; n < nsmp; n++) { // the sample parapointers are byte-swapped later para_smp[n] = disko_tell(fp); save_its_header(fp, current_song->samples + n + 1); } for (n = 0; n < npat; n++) { if (csf_pattern_is_empty(current_song, n)) { para_pat[n] = 0; } else { para_pat[n] = disko_tell(fp); para_pat[n] = bswapLE32(para_pat[n]); _save_it_pattern(fp, current_song->patterns[n], current_song->pattern_size[n]); } } // sample data for (n = 0; n < nsmp; n++) { unsigned int tmp, op; song_sample_t *smp = current_song->samples + (n + 1); // Always save the data pointer, even if there's not actually any data being pointed to op = disko_tell(fp); tmp = bswapLE32(op); disko_seek(fp, para_smp[n]+0x48, SEEK_SET); disko_write(fp, &tmp, 4); disko_seek(fp, op, SEEK_SET); if (smp->data) csf_write_sample(fp, smp, SF_LE | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M)); // done using the pointer internally, so *now* swap it para_smp[n] = bswapLE32(para_smp[n]); if (!warned_adlib && smp->flags & CHN_ADLIB) { log_appendf(4, " Warning: AdLib samples unsupported in IT format"); warned_adlib = 1; } } // rewrite the parapointers disko_seek(fp, 0xc0 + nord, SEEK_SET); disko_write(fp, para_ins, 4*nins); disko_write(fp, para_smp, 4*nsmp); disko_write(fp, para_pat, 4*npat); return SAVE_SUCCESS; } /* ------------------------------------------------------------------------- */ const struct save_format song_save_formats[] = { {"IT", "Impulse Tracker", ".it", {.save_song = _save_it}}, {"S3M", "Scream Tracker 3", ".s3m", {.save_song = fmt_s3m_save_song}}, {.label = NULL} }; #define EXPORT_FUNCS(t) \ fmt_##t##_export_head, fmt_##t##_export_silence, fmt_##t##_export_body, fmt_##t##_export_tail const struct save_format song_export_formats[] = { {"WAV", "WAV", ".wav", {.export = {EXPORT_FUNCS(wav), 0}}}, {"MWAV", "WAV multi-write", ".wav", {.export = {EXPORT_FUNCS(wav), 1}}}, {"AIFF", "Audio IFF", ".aiff", {.export = {EXPORT_FUNCS(aiff), 0}}}, {"MAIFF", "Audio IFF multi-write", ".aiff", {.export = {EXPORT_FUNCS(aiff), 1}}}, {.label = NULL} }; // and maiff sounds like something you'd want to hug // .. dont ask const struct save_format sample_save_formats[] = { {"ITS", "Impulse Tracker", ".its", {.save_sample = fmt_its_save_sample}}, //{"S3I", "Scream Tracker", ".s3i", {.save_sample = fmt_s3i_save_sample}}, {"AIFF", "Audio IFF", ".aiff", {.save_sample = fmt_aiff_save_sample}}, {"AU", "Sun/NeXT", ".au", {.save_sample = fmt_au_save_sample}}, {"WAV", "WAV", ".wav", {.save_sample = fmt_wav_save_sample}}, {"RAW", "Raw", ".raw", {.save_sample = fmt_raw_save_sample}}, {.label = NULL} }; static const struct save_format *get_save_format(const struct save_format *formats, const char *label) { int n; if (!label) { // why would this happen, ever? log_appendf(4, "No file type given, very weird! (Try a different filename?)"); return NULL; } for (n = 0; formats[n].label; n++) if (strcmp(formats[n].label, label) == 0) return formats + n; log_appendf(4, "Unknown save format %s", label); return NULL; } static char *mangle_filename(const char *in, const char *mid, const char *ext) { char *ret; const char *iext; size_t baselen, rlen; iext = get_extension(in); rlen = baselen = iext - in; if (mid) rlen += strlen(mid); if (iext[0]) rlen += strlen(iext); else if (ext) rlen += strlen(ext); ret = malloc(rlen + 1); /* room for terminating \0 */ if (!ret) return NULL; strncpy(ret, in, baselen); ret[baselen] = '\0'; if (mid) strcat(ret, mid); /* maybe output a warning if iext and ext differ? */ if (iext[0]) strcat(ret, iext); else if (ext) strcat(ret, ext); return ret; } int song_export(const char *filename, const char *type) { const struct save_format *format = get_save_format(song_export_formats, type); const char *mid; char *mangle; int r; if (!format) return SAVE_INTERNAL_ERROR; mid = (format->f.export.multi && strcasestr(filename, "%c") == NULL) ? ".%c" : NULL; mangle = mangle_filename(filename, mid, format->ext); log_nl(); log_nl(); log_appendf(2, "Exporting to %s", format->name); log_underline(strlen(format->name) + 13); /* disko does the rest of the log messages itself */ r = disko_export_song(mangle, format); free(mangle); switch (r) { case DW_OK: return SAVE_SUCCESS; case DW_ERROR: return SAVE_FILE_ERROR; default: return SAVE_INTERNAL_ERROR; } } int song_save(const char *filename, const char *type) { int ret, backup; const struct save_format *format = get_save_format(song_save_formats, type); char *mangle; if (!format) return SAVE_INTERNAL_ERROR; mangle = mangle_filename(filename, NULL, format->ext); log_nl(); log_nl(); log_appendf(2, "Saving %s module", format->name); log_underline(strlen(format->name) + 14); /* TODO: add or replace file extension as appropriate From IT 2.10 update: - Automatic filename extension replacement on Ctrl-S, so that if you press Ctrl-S after loading a .MOD, .669, .MTM or .XM, the filename will be automatically modified to have a .IT extension. In IT, if the filename given has no extension ("basename"), then the extension for the proper file type (IT/S3M) will be appended to the name. A filename with an extension is not modified, even if the extension is blank. (as in "basename.") This brings up a rather odd bit of behavior: what happens when saving a file with the deliberately wrong extension? Selecting the IT type and saving as "test.s3m" works just as one would expect: an IT file is written, with the name "test.s3m". No surprises yet, but as soon as Ctrl-S is used, the filename is "fixed", producing a second file called "test.it". The reverse happens when trying to save an S3M named "test.it" -- it's rewritten to "test.s3m". Note that in these scenarios, Impulse Tracker *DOES NOT* check if an existing file by that name exists; it will GLADLY overwrite the old "test.s3m" (or .it) with the renamed file that's being saved. Presumably this is NOT intentional behavior. Another note: if the file could not be saved for some reason or another, Impulse Tracker pops up a dialog saying "Could not save file". This can be seen rather easily by trying to save a file with a malformed name, such as "abc|def.it". This dialog is presented both when saving from F10 and Ctrl-S. */ disko_t *fp = disko_open(mangle); if (!fp) { log_perror(mangle); free(mangle); return SAVE_FILE_ERROR; } ret = format->f.save_song(fp, current_song); if (ret != SAVE_SUCCESS) disko_seterror(fp, EINVAL); backup = ((status.flags & MAKE_BACKUPS) ? (status.flags & NUMBERED_BACKUPS) ? 65536 : 1 : 0); if (disko_close(fp, backup) == DW_ERROR && ret == SAVE_SUCCESS) { // this was not as successful as originally claimed! ret = SAVE_FILE_ERROR; } switch (ret) { case SAVE_SUCCESS: status.flags &= ~SONG_NEEDS_SAVE; if (strcasecmp(song_filename, mangle)) song_set_filename(mangle); log_appendf(5, " Done"); break; case SAVE_FILE_ERROR: log_perror(mangle); break; case SAVE_INTERNAL_ERROR: default: // ??? log_appendf(4, " Internal error saving song"); break; } free(mangle); return ret; } int song_save_sample(const char *filename, const char *type, song_sample_t *smp, int num) { int ret; const struct save_format *format = get_save_format(sample_save_formats, type); if (!format) return SAVE_INTERNAL_ERROR; if (!filename || !filename[0]) { status_text_flash("Error: Sample %d NOT saved! (%s)", num, "No Filename?"); return SAVE_INTERNAL_ERROR; // ? } disko_t *fp = disko_open(filename); if (fp) { ret = format->f.save_sample(fp, smp); if (ret != SAVE_SUCCESS) disko_seterror(fp, EINVAL); if (disko_close(fp, 0) == DW_ERROR && ret == SAVE_SUCCESS) { // this was not as successful as originally claimed! ret = SAVE_FILE_ERROR; } } else { ret = SAVE_FILE_ERROR; } switch (ret) { case SAVE_SUCCESS: status_text_flash("%s sample saved (sample %d)", format->name, num); break; case SAVE_FILE_ERROR: status_text_flash("Error: Sample %d NOT saved! (%s)", num, "File Error"); log_perror(get_basename(filename)); break; case SAVE_INTERNAL_ERROR: default: // ??? status_text_flash("Error: Sample %d NOT saved! (%s)", num, "Internal error"); log_appendf(4, "Internal error saving sample"); break; } return ret; } // ------------------------------------------------------------------------ // 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. #define LOAD_SAMPLE(x) fmt_##x##_load_sample, static fmt_load_sample_func load_sample_funcs[] = { #include "fmt-types.h" NULL, }; #define LOAD_INSTRUMENT(x) fmt_##x##_load_instrument, static fmt_load_instrument_func load_instrument_funcs[] = { #include "fmt-types.h" NULL, }; void song_clear_sample(int n) { song_lock_audio(); csf_destroy_sample(current_song, n); memset(current_song->samples + n, 0, sizeof(song_sample_t)); current_song->samples[n].c5speed = 8363; current_song->samples[n].volume = 64 * 4; current_song->samples[n].global_volume = 64; song_unlock_audio(); } void song_copy_sample(int n, song_sample_t *src) { memcpy(current_song->samples + n, src, sizeof(song_sample_t)); if (src->data) { unsigned long bytelength = src->length; if (src->flags & CHN_16BIT) bytelength *= 2; if (src->flags & CHN_STEREO) bytelength *= 2; current_song->samples[n].data = csf_allocate_sample(bytelength); memcpy(current_song->samples[n].data, src->data, bytelength); } } int song_load_instrument_ex(int target, const char *file, const char *libf, int n) { slurp_t *s; int r, x; song_lock_audio(); /* 0. delete old samples */ if (current_song->instruments[target]) { int sampmap[MAX_SAMPLES] = {}; /* init... */ for (unsigned int j = 0; j < 128; j++) { x = current_song->instruments[target]->sample_map[j]; sampmap[x] = 1; } /* mark... */ for (unsigned int q = 0; q < MAX_INSTRUMENTS; q++) { if ((int) q == target) continue; if (!current_song->instruments[q]) continue; for (unsigned int j = 0; j < 128; j++) { x = current_song->instruments[q]->sample_map[j]; sampmap[x] = 0; } } /* sweep! */ for (int j = 1; j < MAX_SAMPLES; j++) { if (!sampmap[j]) continue; csf_destroy_sample(current_song, j); memset(current_song->samples + j, 0, sizeof(current_song->samples[j])); } /* now clear everything "empty" so we have extra slots */ for (int j = 1; j < MAX_SAMPLES; j++) { if (csf_sample_is_empty(current_song->samples + j)) sampmap[j] = 0; } } if (libf) { /* file is ignored */ int sampmap[MAX_SAMPLES] = {}; song_t *xl = song_create_load(libf); if (!xl) { log_appendf(4, "%s: %s", libf, fmt_strerror(errno)); song_unlock_audio(); return 0; } /* 1. find a place for all the samples */ for (unsigned int j = 0; j < 128; j++) { x = xl->instruments[n]->sample_map[j]; if (!sampmap[x]) { if (x > 0 && x < MAX_INSTRUMENTS) { for (int k = 1; k < MAX_SAMPLES; k++) { if (current_song->samples[k].length) continue; sampmap[x] = k; //song_sample *smp = (song_sample *)song_get_sample(k); for (int c = 0; c < 25; c++) { if (xl->samples[x].name[c] == 0) xl->samples[x].name[c] = 32; } xl->samples[x].name[25] = 0; song_copy_sample(k, &xl->samples[x]); break; } } } } /* transfer the instrument */ current_song->instruments[target] = xl->instruments[n]; xl->instruments[n] = NULL; /* dangle */ /* and rewrite! */ for (unsigned int k = 0; k < 128; k++) { current_song->instruments[target]->sample_map[k] = sampmap[ current_song->instruments[target]->sample_map[k] ]; } song_unlock_audio(); return 1; } /* okay, load an ITI file */ s = slurp(file, NULL, 0); if (!s) { log_perror(file); song_unlock_audio(); return 0; } r = 0; for (x = 0; load_instrument_funcs[x]; x++) { r = load_instrument_funcs[x](s->data, s->length, target); if (r) break; } unslurp(s); song_unlock_audio(); return r; } int song_load_instrument(int n, const char *file) { return song_load_instrument_ex(n,file,NULL,-1); } int song_preload_sample(dmoz_file_t *file) { // 0 is our "hidden sample" #define FAKE_SLOT 0 //csf_stop_sample(current_song, current_song->samples + FAKE_SLOT); if (file->sample) { song_sample_t *smp = song_get_sample(FAKE_SLOT); song_lock_audio(); csf_destroy_sample(current_song, FAKE_SLOT); song_copy_sample(FAKE_SLOT, file->sample); strncpy(smp->name, file->title, 25); smp->name[25] = 0; strncpy(smp->filename, file->base, 12); smp->filename[12] = 0; song_unlock_audio(); return FAKE_SLOT; } // WARNING this function must return 0 or KEYJAZZ_NOINST return song_load_sample(FAKE_SLOT, file->path) ? FAKE_SLOT : KEYJAZZ_NOINST; #undef FAKE_SLOT } int song_load_sample(int n, const char *file) { fmt_load_sample_func *load; song_sample_t smp = {}; const char *base = get_basename(file); slurp_t *s = slurp(file, NULL, 0); if (s == NULL) { log_perror(base); return 0; } // set some default stuff song_lock_audio(); csf_stop_sample(current_song, current_song->samples + n); strncpy(smp.name, base, 25); for (load = load_sample_funcs; *load; load++) { if ((*load)(s->data, s->length, &smp)) { break; } } if (!load) { unslurp(s); log_perror(base); song_unlock_audio(); return 0; } // this is after the loaders because i don't trust them, even though i wrote them ;) strncpy(smp.filename, base, 12); smp.filename[12] = 0; smp.name[25] = 0; csf_destroy_sample(current_song, n); if (((unsigned char)smp.name[23]) == 0xFF) { // don't load embedded samples // (huhwhat?!) smp.name[23] = ' '; } memcpy(&(current_song->samples[n]), &smp, sizeof(song_sample_t)); song_unlock_audio(); unslurp(s); return 1; } void song_create_host_instrument(int smp) { int ins = instrument_get_current(); if (csf_instrument_is_empty(current_song->instruments[smp])) ins = smp; else if ((status.flags & CLASSIC_MODE) || !csf_instrument_is_empty(current_song->instruments[ins])) ins = csf_first_blank_instrument(current_song, 0); if (ins > 0) { song_init_instrument_from_sample(ins, smp); status_text_flash("Sample assigned to Instrument %d", ins); } else { status_text_flash("Error: No available Instruments!"); } } // ------------------------------------------------------------------------ int song_save_instrument(int n, const char *file) { song_instrument_t *ins = current_song->instruments[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; } disko_t *fp = disko_open(file); if (!fp) { log_perror(get_basename(file)); return 0; } _save_it_instrument(n-1 /* grr.... */, fp, 1); if (disko_close(fp, 0) == DW_ERROR) { log_perror(get_basename(file)); return 0; } return 1; } // ------------------------------------------------------------------------ // song information const char *song_get_filename(void) { return song_filename; } const char *song_get_basename(void) { return song_basename; } // ------------------------------------------------------------------------ // sample library browsing // FIXME: unload the module when leaving the library 'directory' static song_t *library = NULL; // TODO: stat the file? int dmoz_read_instrument_library(const char *path, dmoz_filelist_t *flist, UNUSED dmoz_dirlist_t *dlist) { unsigned int j; int x; csf_stop_sample(current_song, current_song->samples + 0); csf_free(library); const char *base = get_basename(path); library = song_create_load(path); if (!library) { log_appendf(4, "%s: %s", base, fmt_strerror(errno)); return -1; } for (int n = 1; n < MAX_INSTRUMENTS; n++) { if (!library->instruments[n]) continue; dmoz_file_t *file = dmoz_add_file(flist, str_dup(path), str_dup(base), NULL, n); file->title = str_dup(library->instruments[n]->name); int count[128] = {}; file->sampsize = 0; file->filesize = 0; file->instnum = n; for (j = 0; j < 128; j++) { x = library->instruments[n]->sample_map[j]; if (!count[x]) { if (x > 0 && x < MAX_INSTRUMENTS) { file->filesize += library->samples[x].length; file->sampsize++; } } count[x]++; } file->type = TYPE_INST_ITI; file->description = "Fishcakes"; // IT doesn't support this, despite it being useful. // Simply "unrecognized" } return 0; } int dmoz_read_sample_library(const char *path, dmoz_filelist_t *flist, UNUSED dmoz_dirlist_t *dlist) { csf_stop_sample(current_song, current_song->samples + 0); csf_free(library); const char *base = get_basename(path); library = song_create_load(path); if (!library) { /* FIXME: try loading as an instrument before giving up */ log_appendf(4, "%s: %s", base, fmt_strerror(errno)); errno = ENOTDIR; return -1; } for (int n = 1; n < MAX_SAMPLES; n++) { if (library->samples[n].length) { for (int c = 0; c < 25; c++) { if (library->samples[n].name[c] == 0) library->samples[n].name[c] = 32; library->samples[n].name[25] = 0; } dmoz_file_t *file = dmoz_add_file(flist, str_dup(path), str_dup(base), NULL, n); file->type = TYPE_SAMPLE_EXTD; file->description = "Fishcakes"; // FIXME - what does IT say? file->smp_speed = library->samples[n].c5speed; file->smp_loop_start = library->samples[n].loop_start; file->smp_loop_end = library->samples[n].loop_end; file->smp_sustain_start = library->samples[n].sustain_start; file->smp_sustain_end = library->samples[n].sustain_end; file->smp_length = library->samples[n].length; file->smp_flags = library->samples[n].flags; file->smp_defvol = library->samples[n].volume>>2; file->smp_gblvol = library->samples[n].global_volume; file->smp_vibrato_speed = library->samples[n].vib_speed; file->smp_vibrato_depth = library->samples[n].vib_depth; file->smp_vibrato_rate = library->samples[n].vib_rate; // don't screw this up... if (((unsigned char)library->samples[n].name[23]) == 0xFF) { library->samples[n].name[23] = ' '; } file->title = str_dup(library->samples[n].name); file->sample = (song_sample_t *) library->samples + n; } } return 0; } // ------------------------------------------------------------------------ // instrument loader song_instrument_t *instrument_loader_init(struct instrumentloader *ii, int slot) { ii->expect_samples = 0; ii->inst = song_get_instrument(slot); ii->slot = slot; ii->basex = 1; memset(ii->sample_map, 0, sizeof(ii->sample_map)); return ii->inst; } int instrument_loader_abort(struct instrumentloader *ii) { int n; song_wipe_instrument(ii->slot); for (n = 0; n < MAX_SAMPLES; n++) { if (ii->sample_map[n]) { song_clear_sample(ii->sample_map[n]-1); ii->sample_map[n] = 0; } } return 0; } int instrument_loader_sample(struct instrumentloader *ii, int slot) { int x; if (!slot) return 0; if (ii->sample_map[slot]) return ii->sample_map[slot]; for (x = ii->basex; x < MAX_SAMPLES; x++) { song_sample_t *cur = (current_song->samples + x); // if (!csf_sample_is_empty(current_song->samples + x)) // continue; if (cur->data != NULL) continue; ii->expect_samples++; ii->sample_map[slot] = x; ii->basex = x + 1; return ii->sample_map[slot]; } status_text_flash("Too many samples"); return 0; } schismtracker-20180209/schism/audio_playback.c000066400000000000000000001226521323741476300212460ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "cmixer.h" #include "sndfile.h" #include "song.h" #include "slurp.h" #include "config-parser.h" #include "disko.h" #include "event.h" #include #include #include #include #include #include "sdlmain.h" #include "midi.h" #include "snd_fm.h" #include "snd_gm.h" // Default audio configuration // (XXX: Can DEF_SAMPLE_RATE be defined to 48000 everywhere? // Does any sound card NOT support 48khz decently nowadays?) #ifdef GEKKO # define DEF_SAMPLE_RATE 48000 #else # define DEF_SAMPLE_RATE 44100 #endif #ifdef WIN32 # define DEF_BUFFER_SIZE 2048 #else # define DEF_BUFFER_SIZE 1024 #endif #define DEF_CHANNEL_LIMIT 128 static int midi_playing; // ------------------------------------------------------------------------ #define SMP_INIT (UINT_MAX - 1) /* for a click noise on init */ unsigned int samples_played = 0; unsigned int max_channels_used = 0; signed short *audio_buffer = NULL; unsigned int audio_buffer_samples = 0; /* multiply by audio_sample_size to get bytes */ unsigned int audio_output_channels = 2; unsigned int audio_output_bits = 16; static unsigned int audio_sample_size; static int audio_buffers_per_second = 0; static int audio_writeout_count = 0; struct audio_settings audio_settings; static void _schism_midi_out_note(int chan, const song_note_t *m); static void _schism_midi_out_raw(const unsigned char *data, unsigned int len, unsigned int delay); /* Audio driver related stuff */ /* The (short) name of the SDL driver in use, e.g. "alsa" */ static char driver_name[256]; /* This is the full driver spec for whatever device was successfully init'ed when audio was set up. When reinitializing the audio, this can be used to reacquire the same device. Hopefully. */ static char active_audio_driver[256]; /* Whatever was in the config file. This is used if no driver is given to audio_setup. */ static char cfg_audio_driver[256]; // ------------------------------------------------------------------------ // playback extern int midi_bend_hit[64], midi_last_bend_hit[64]; extern void vis_work_16s(short *in, int inlen); extern void vis_work_16m(short *in, int inlen); extern void vis_work_8s(char *in, int inlen); extern void vis_work_8m(char *in, int inlen); // this gets called from sdl static void audio_callback(UNUSED void *qq, uint8_t * stream, int len) { unsigned int wasrow = current_song->row; unsigned int waspat = current_song->current_order; int i, n; if (!stream || !len || !current_song) { if (status.current_page == PAGE_WATERFALL || status.vis_style == VIS_FFT) { vis_work_8m(NULL, 0); } song_stop_unlocked(0); goto POST_EVENT; } if (samples_played >= SMP_INIT) { memset(stream, 0x80, len); samples_played++; // will loop back to 0 return; } if (current_song->flags & SONG_ENDREACHED) { n = 0; } else { n = csf_read(current_song, stream, len); if (!n) { if (status.current_page == PAGE_WATERFALL || status.vis_style == VIS_FFT) { vis_work_8m(NULL, 0); } song_stop_unlocked(0); goto POST_EVENT; } samples_played += n; } memcpy(audio_buffer, stream, n * audio_sample_size); if (audio_output_bits == 8) { /* libmodplug emits unsigned 8bit output... */ stream = (uint8_t *) audio_buffer; n *= audio_output_channels; for (i = 0; i < n; i++) { stream[i] ^= 128; } if (status.current_page == PAGE_WATERFALL || status.vis_style == VIS_FFT) { if (audio_output_channels == 2) { vis_work_8s((char*)stream, n/2); } else { vis_work_8m((char*)stream, n); } } } else if (status.current_page == PAGE_WATERFALL || status.vis_style == VIS_FFT) { if (audio_output_channels == 2) { vis_work_16s((short*)stream, n); } else { vis_work_16m((short*)stream, n); } } if (current_song->num_voices > max_channels_used) max_channels_used = MIN(current_song->num_voices, max_voices); POST_EVENT: audio_writeout_count++; if (audio_writeout_count > audio_buffers_per_second) { audio_writeout_count = 0; } else if (waspat == current_song->current_order && wasrow == current_song->row && !midi_need_flush()) { /* skip it */ return; } /* send at end */ SDL_Event e; e.user.type = SCHISM_EVENT_PLAYBACK; e.user.code = 0; e.user.data1 = NULL; e.user.data2 = NULL; SDL_PushEvent(&e); } // ------------------------------------------------------------------------------------------------------------ // note playing /* this should be in page.c; the audio handling code doesn't need to know what a page is, much less be talking to them */ static void main_song_mode_changed_cb(void) { int n; for (n = 0; n < PAGE_MAX; n++) { if (pages[n].song_mode_changed_cb) pages[n].song_mode_changed_cb(); } } static int current_play_channel = 1; static int multichannel_mode = 0; int song_get_current_play_channel(void) { return current_play_channel; } 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; } /* Channel corresponding to each note played. That is, keydown_channels[66] will indicate in which channel F-5 was played most recently. This will break if the same note was keydown'd twice without a keyup, but I think that's a fairly unlikely scenario that you'd have to TRY to bring about. */ static int keyjazz_channels[128]; /* **** chan ranges from 1 to 64 */ static int song_keydown_ex(int samp, int ins, int note, int vol, int chan, int effect, int param) { int ins_mode; int midi_note = note; /* note gets overwritten, possibly NOTE_NONE */ song_voice_t *c; song_note_t mc; song_sample_t *s = NULL; song_instrument_t *i = NULL; if (chan == KEYJAZZ_CHAN_CURRENT) { chan = current_play_channel; if (multichannel_mode) song_change_current_play_channel(1, 1); } // back to the 0..63 range int chan_internal = chan -1; song_lock_audio(); c = current_song->voices + chan_internal; ins_mode = song_is_instrument_mode(); if (NOTE_IS_NOTE(note)) { // keep track of what channel this note was played in so we can note-off properly later keyjazz_channels[note] = chan; // handle blank instrument values and "fake" sample #0 (used by sample loader) if (samp == 0) samp = c->last_instrument; else if (samp == KEYJAZZ_INST_FAKE) samp = 0; // dumb hack if (ins == 0) ins = c->last_instrument; else if (ins == KEYJAZZ_INST_FAKE) ins = 0; // dumb hack c->last_instrument = ins_mode ? ins : samp; // give the channel a sample, and maybe an instrument s = (samp == KEYJAZZ_NOINST) ? NULL : current_song->samples + samp; i = (ins == KEYJAZZ_NOINST) ? NULL : song_get_instrument(ins); // blah if (i && samp == KEYJAZZ_NOINST) { // we're playing an instrument and don't know what sample! WHAT WILL WE EVER DO?! // well, look it up in the note translation table, silly. // the weirdness here the default value here is to mimic IT behavior: we want to use // the sample corresponding to the instrument number if in sample mode and no sample // is defined for the note in the instrument's note map. s = csf_translate_keyboard(current_song, i, note, ins_mode ? NULL : (current_song->samples + ins)); } } c->row_effect = effect; c->row_param = param; // now do a rough equivalent of csf_instrument_change and csf_note_change if (i) csf_check_nna(current_song, chan_internal, ins, note, 0); if (s) { if (c->flags & CHN_ADLIB) { OPL_NoteOff(chan_internal); OPL_Patch(chan_internal, s->adlib_bytes); } c->flags = (s->flags & CHN_SAMPLE_FLAGS) | (c->flags & CHN_MUTE); if (c->flags & CHN_MUTE) { c->flags |= CHN_NNAMUTE; } if (i) { c->ptr_instrument = i; if (!(i->flags & ENV_VOLCARRY)) c->vol_env_position = 0; if (!(i->flags & ENV_PANCARRY)) c->pan_env_position = 0; if (!(i->flags & ENV_PITCHCARRY)) c->pitch_env_position = 0; if (i->flags & ENV_VOLUME) c->flags |= CHN_VOLENV; if (i->flags & ENV_PANNING) c->flags |= CHN_PANENV; if (i->flags & ENV_PITCH) c->flags |= CHN_PITCHENV; i->played = 1; if ((status.flags & MIDI_LIKE_TRACKER) && i) { if (i->midi_channel_mask) { GM_KeyOff(chan_internal); GM_DPatch(chan_internal, i->midi_program, i->midi_bank, i->midi_channel_mask); } } if (i->ifc & 0x80) c->cutoff = i->ifc & 0x7f; if (i->ifr & 0x80) c->resonance = i->ifr & 0x7f; //? c->vol_swing = i->vol_swing; c->pan_swing = i->pan_swing; c->nna = i->nna; } else { c->ptr_instrument = NULL; c->cutoff = 0x7f; c->resonance = 0; } c->master_channel = 0; // indicates foreground channel. //c->flags &= ~(CHN_PINGPONGFLAG); // ? //c->autovib_depth = 0; //c->autovib_position = 0; // csf_note_change copies stuff from c->ptr_sample as long as c->length is zero // and if period != 0 (ie. sample not playing at a stupid rate) c->ptr_sample = s; c->length = 0; // ... but it doesn't copy the volumes, for somewhat obvious reasons. c->volume = (vol == KEYJAZZ_DEFAULTVOL) ? s->volume : (((unsigned) vol) << 2); c->instrument_volume = s->global_volume; if (i) c->instrument_volume = (c->instrument_volume * i->global_volume) >> 7; c->global_volume = 64; // gotta set these by hand, too c->c5speed = s->c5speed; c->new_note = note; s->played = 1; } else if (NOTE_IS_NOTE(note)) { // Note given with no sample number. This might happen if on the instrument list and playing // an instrument that has no sample mapped for the given note. In this case, ignore the note. note = NOTE_NONE; } if (c->increment < 0) c->increment = -c->increment; // lousy hack csf_note_change(current_song, chan_internal, note, 0, 0, 1); if (!(status.flags & MIDI_LIKE_TRACKER) && i) { /* midi keyjazz shouldn't require a sample */ mc.note = note ? note : midi_note; mc.instrument = ins; mc.voleffect = VOLFX_VOLUME; mc.volparam = vol; mc.effect = effect; mc.param = param; _schism_midi_out_note(chan_internal, &mc); } /* TODO: - If this is the ONLY channel playing, and the song is stopped, always reset the tick count (will fix the "random" behavior for most effects) - If other channels are playing, don't reset the tick count, but do process first-tick effects for this note *right now* (this will fix keyjamming with effects like Oxx and SCx) - Need to handle volume column effects with this function... */ if (current_song->flags & SONG_ENDREACHED) { current_song->flags &= ~SONG_ENDREACHED; current_song->flags |= SONG_PAUSED; } song_unlock_audio(); return chan; } int song_keydown(int samp, int ins, int note, int vol, int chan) { return song_keydown_ex(samp, ins, note, vol, chan, FX_PANNING, 0x80); } int song_keyrecord(int samp, int ins, int note, int vol, int chan, int effect, int param) { return song_keydown_ex(samp, ins, note, vol, chan, effect, param); } int song_keyup(int samp, int ins, int note) { return song_keydown_ex(samp, ins, NOTE_OFF, KEYJAZZ_DEFAULTVOL, keyjazz_channels[note], 0, 0); } void song_single_step(int patno, int row) { int total_rows; int i, vol, smp, ins; song_note_t *pattern, *cur_note; song_voice_t *cx; total_rows = song_get_pattern(patno, &pattern); if (!pattern || row >= total_rows) return; cur_note = pattern + 64 * row; cx = song_get_mix_channel(0); for (i = 1; i <= 64; i++, cx++, cur_note++) { if (cx && (cx->flags & CHN_MUTE)) continue; /* ick */ if (cur_note->voleffect == VOLFX_VOLUME) { vol = cur_note->volparam; } else { vol = KEYJAZZ_DEFAULTVOL; } // look familiar? this is modified slightly from pattern_editor_insert // (and it is wrong for the same reason as described there) smp = ins = cur_note->instrument; if (song_is_instrument_mode()) { if (ins < 1) ins = KEYJAZZ_NOINST; smp = -1; } else { if (smp < 1) smp = KEYJAZZ_NOINST; ins = -1; } song_keyrecord(smp, ins, cur_note->note, vol, i, cur_note->effect, cur_note->param); } } // ------------------------------------------------------------------------------------------------------------ // this should be called with the audio LOCKED static void song_reset_play_state(void) { memset(midi_bend_hit, 0, sizeof(midi_bend_hit)); memset(midi_last_bend_hit, 0, sizeof(midi_last_bend_hit)); memset(keyjazz_channels, 0, sizeof(keyjazz_channels)); // turn this crap off current_song->mix_flags &= ~(SNDMIX_NOBACKWARDJUMPS | SNDMIX_DIRECTTODISK); OPL_Reset(); /* gruh? */ csf_set_current_order(current_song, 0); current_song->repeat_count = 0; current_song->buffer_count = 0; current_song->flags &= ~(SONG_PAUSED | SONG_PATTERNLOOP | SONG_ENDREACHED); current_song->stop_at_order = -1; current_song->stop_at_row = -1; samples_played = 0; } void song_start_once(void) { song_lock_audio(); song_reset_play_state(); current_song->mix_flags |= SNDMIX_NOBACKWARDJUMPS; max_channels_used = 0; current_song->repeat_count = -1; // FIXME do this right GM_SendSongStartCode(); song_unlock_audio(); main_song_mode_changed_cb(); csf_reset_playmarks(current_song); } void song_start(void) { song_lock_audio(); song_reset_play_state(); max_channels_used = 0; GM_SendSongStartCode(); song_unlock_audio(); main_song_mode_changed_cb(); csf_reset_playmarks(current_song); } void song_pause(void) { song_lock_audio(); // Highly unintuitive, but SONG_PAUSED has nothing to do with pause. if (!(current_song->flags & SONG_PAUSED)) current_song->flags ^= SONG_ENDREACHED; song_unlock_audio(); main_song_mode_changed_cb(); } void song_stop(void) { song_lock_audio(); song_stop_unlocked(0); song_unlock_audio(); main_song_mode_changed_cb(); } /* 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 song_note_t *last_row[64]; static int last_row_number = -1; void song_stop_unlocked(int quitting) { if (!current_song) return; if (midi_playing) { unsigned char moff[4]; /* shut off everything; not IT like, but less annoying */ for (int chan = 0; chan < 64; chan++) { if (note_tracker[chan] != 0) { for (int j = 0; j < 16; j++) { csf_process_midi_macro(current_song, chan, current_song->midi_config.note_off, 0, note_tracker[chan], 0, j); } moff[0] = 0x80 + chan; moff[1] = note_tracker[chan]; csf_midi_send(current_song, (unsigned char *) moff, 2, 0, 0); } } for (int j = 0; j < 16; j++) { moff[0] = 0xe0 + j; moff[1] = 0; csf_midi_send(current_song, (unsigned char *) moff, 2, 0, 0); moff[0] = 0xb0 + j; /* channel mode message */ moff[1] = 0x78; /* all sound off */ moff[2] = 0; csf_midi_send(current_song, (unsigned char *) moff, 3, 0, 0); moff[1] = 0x79; /* reset all controllers */ csf_midi_send(current_song, (unsigned char *) moff, 3, 0, 0); moff[1] = 0x7b; /* all notes off */ csf_midi_send(current_song, (unsigned char *) moff, 3, 0, 0); } csf_process_midi_macro(current_song, 0, current_song->midi_config.stop, 0, 0, 0, 0); // STOP! midi_send_flush(); // NOW! midi_playing = 0; } OPL_Reset(); /* Also stop all OPL sounds */ GM_Reset(quitting); GM_SendSongStopCode(); 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 SONG_ENDREACHED is set, current_song->Read just returns. current_song->flags |= SONG_PAUSED | SONG_ENDREACHED; global_vu_left = 0; global_vu_right = 0; memset(audio_buffer, 0, audio_buffer_samples * audio_sample_size); } void song_loop_pattern(int pattern, int row) { song_lock_audio(); song_reset_play_state(); max_channels_used = 0; csf_loop_pattern(current_song, pattern, row); GM_SendSongStartCode(); song_unlock_audio(); main_song_mode_changed_cb(); csf_reset_playmarks(current_song); } void song_start_at_order(int order, int row) { song_lock_audio(); song_reset_play_state(); csf_set_current_order(current_song, order); current_song->break_row = row; max_channels_used = 0; GM_SendSongStartCode(); /* TODO: GM_SendSongPositionCode(calculate the number of 1/16 notes) */ song_unlock_audio(); main_song_mode_changed_cb(); csf_reset_playmarks(current_song); } void song_start_at_pattern(int pattern, int row) { if (pattern < 0 || pattern > 199) return; int n = song_next_order_for_pattern(pattern); if (n > -1) { song_start_at_order(n, row); return; } song_loop_pattern(pattern, row); } // ------------------------------------------------------------------------ // info on what's playing enum song_mode song_get_mode(void) { if ((current_song->flags & (SONG_ENDREACHED | SONG_PAUSED)) == (SONG_ENDREACHED | SONG_PAUSED)) return MODE_STOPPED; if (current_song->flags & SONG_PAUSED) return MODE_SINGLE_STEP; if (current_song->flags & SONG_PATTERNPLAYBACK) return MODE_PATTERN_LOOP; return MODE_PLAYING; } // returned value is in seconds unsigned int song_get_current_time(void) { return samples_played / current_song->mix_frequency; } int song_get_current_tick(void) { return current_song->tick_count % current_song->current_speed; } int song_get_current_speed(void) { return current_song->current_speed; } void song_set_current_tempo(int new_tempo) { song_lock_audio(); current_song->current_tempo = CLAMP(new_tempo, 31, 255); song_unlock_audio(); } int song_get_current_tempo(void) { return current_song->current_tempo; } int song_get_current_global_volume(void) { return current_song->current_global_volume; } int song_get_current_order(void) { return current_song->current_order; } int song_get_playing_pattern(void) { return current_song->current_pattern; } int song_get_current_row(void) { return current_song->row; } int song_get_playing_channels(void) { return MIN(current_song->num_voices, max_voices); } int song_get_max_channels(void) { return max_channels_used; } // Returns the max value in dBs, scaled as 0 = -40dB and 128 = 0dB. void song_get_vu_meter(int *left, int *right) { *left = dB_s(40, global_vu_left/256.f, 0.f); *right = dB_s(40, global_vu_right/256.f, 0.f); } void song_update_playing_instrument(int i_changed) { song_voice_t *channel; song_instrument_t *inst; song_lock_audio(); int n = MIN(current_song->num_voices, max_voices); while (n--) { channel = current_song->voices + current_song->voice_mix[n]; if (channel->ptr_instrument && channel->ptr_instrument == current_song->instruments[i_changed]) { csf_instrument_change(current_song, channel, i_changed, 1, 0); inst = channel->ptr_instrument; if (!inst) continue; /* special cases; mpt doesn't do this if porta-enabled, */ if (inst->ifr & 0x80) { channel->resonance = inst->ifr & 0x7F; } else { channel->resonance = 0; channel->flags &= (~CHN_FILTER); } if (inst->ifc & 0x80) { channel->cutoff = inst->ifc & 0x7F; setup_channel_filter(channel, 0, 256, current_song->mix_frequency); } else { channel->cutoff = 0x7F; if (inst->ifr & 0x80) { setup_channel_filter(channel, 0, 256, current_song->mix_frequency); } } /* flip direction */ channel->flags &= (~CHN_PINGPONGFLAG); } } song_unlock_audio(); } void song_update_playing_sample(int s_changed) { song_voice_t *channel; song_sample_t *inst; song_lock_audio(); int n = MIN(current_song->num_voices, max_voices); while (n--) { channel = current_song->voices + current_song->voice_mix[n]; if (channel->ptr_sample && channel->current_sample_data) { int s = channel->ptr_sample - current_song->samples; if (s != s_changed) continue; inst = channel->ptr_sample; if (inst->flags & (CHN_PINGPONGSUSTAIN|CHN_SUSTAINLOOP)) { channel->loop_start = inst->sustain_start; channel->loop_end = inst->sustain_end; } else if (inst->flags & (CHN_PINGPONGFLAG|CHN_PINGPONGLOOP|CHN_LOOP)) { channel->loop_start = inst->loop_start; channel->loop_end = inst->loop_end; } if (inst->flags & (CHN_PINGPONGSUSTAIN | CHN_SUSTAINLOOP | CHN_PINGPONGFLAG | CHN_PINGPONGLOOP|CHN_LOOP)) { if (channel->length != channel->loop_end) { channel->length = channel->loop_end; } } if (channel->length > inst->length) { channel->current_sample_data = inst->data; channel->length = inst->length; } channel->flags &= ~(CHN_PINGPONGSUSTAIN | CHN_PINGPONGLOOP | CHN_PINGPONGFLAG | CHN_SUSTAINLOOP | CHN_LOOP); channel->flags |= inst->flags & (CHN_PINGPONGSUSTAIN | CHN_PINGPONGLOOP | CHN_PINGPONGFLAG | CHN_SUSTAINLOOP | CHN_LOOP); channel->instrument_volume = inst->global_volume; } } song_unlock_audio(); } void song_get_playing_samples(int samples[]) { song_voice_t *channel; memset(samples, 0, MAX_SAMPLES * sizeof(int)); song_lock_audio(); int n = MIN(current_song->num_voices, max_voices); while (n--) { channel = current_song->voices + current_song->voice_mix[n]; if (channel->ptr_sample && channel->current_sample_data) { int s = channel->ptr_sample - current_song->samples; if (s >= 0 && s < MAX_SAMPLES) { samples[s] = MAX(samples[s], 1 + channel->strike); } } else { // no sample. // (when does this happen?) } } song_unlock_audio(); } void song_get_playing_instruments(int instruments[]) { song_voice_t *channel; memset(instruments, 0, MAX_INSTRUMENTS * sizeof(int)); song_lock_audio(); int n = MIN(current_song->num_voices, max_voices); while (n--) { channel = current_song->voices + current_song->voice_mix[n]; int ins = song_get_instrument_number((song_instrument_t *) channel->ptr_instrument); if (ins > 0 && ins < MAX_INSTRUMENTS) { instruments[ins] = MAX(instruments[ins], 1 + channel->strike); } } song_unlock_audio(); } // ------------------------------------------------------------------------ // changing the above info void song_set_current_speed(int speed) { if (speed < 1 || speed > 255) return; song_lock_audio(); current_song->current_speed = speed; song_unlock_audio(); } void song_set_current_global_volume(int volume) { if (volume < 0 || volume > 128) return; song_lock_audio(); current_song->current_global_volume = volume; song_unlock_audio(); } void song_set_current_order(int order) { song_lock_audio(); csf_set_current_order(current_song, order); song_unlock_audio(); } // Ctrl-F7 void song_set_next_order(int order) { song_lock_audio(); current_song->process_order = order - 1; song_unlock_audio(); } // Alt-F11 int song_toggle_orderlist_locked(void) { current_song->flags ^= SONG_ORDERLOCKED; return current_song->flags & SONG_ORDERLOCKED; } // ------------------------------------------------------------------------ // global flags void song_flip_stereo(void) { current_song->mix_flags ^= SNDMIX_REVERSESTEREO; } int song_get_surround(void) { return (current_song->mix_flags & SNDMIX_NOSURROUND) ? 0 : 1; } void song_set_surround(int on) { if (on) current_song->mix_flags &= ~SNDMIX_NOSURROUND; else current_song->mix_flags |= 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) void cfg_load_audio(cfg_file_t *cfg) { CFG_GET_A(sample_rate, DEF_SAMPLE_RATE); CFG_GET_A(bits, 16); CFG_GET_A(channels, 2); CFG_GET_A(buffer_size, DEF_BUFFER_SIZE); cfg_get_string(cfg, "Audio", "driver", cfg_audio_driver, 255, NULL); CFG_GET_M(channel_limit, DEF_CHANNEL_LIMIT); CFG_GET_M(interpolation_mode, SRCMODE_LINEAR); CFG_GET_M(no_ramping, 0); 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_VOICES); audio_settings.interpolation_mode = CLAMP(audio_settings.interpolation_mode, 0, 3); 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); if (cfg_get_number(cfg, "General", "stop_on_load", 1)) { status.flags &= ~PLAY_AFTER_LOAD; } else { status.flags |= PLAY_AFTER_LOAD; } } #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) 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(no_ramping); // Say, what happened to the switch for this in the gui? CFG_SET_M(surround_effect); // hmmm.... // [Equalizer] // low_band=freq/gain // med_low_band=freq/gain // etc. // would be a cleaner way of storing this 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]); } void cfg_save_audio(cfg_file_t *cfg) { cfg_atexit_save_audio(cfg); cfg_set_number(cfg, "General", "stop_on_load", !(status.flags & PLAY_AFTER_LOAD)); } // ------------------------------------------------------------------------------------------------------------ static void _schism_midi_out_note(int chan, const song_note_t *starting_note) { const song_note_t *m = starting_note; unsigned int tc; int m_note; unsigned char buf[4]; int ins, mc, mg, mbl, mbh; int need_note, need_velocity; song_voice_t *c; if (!current_song || !song_is_instrument_mode() || (status.flags & MIDI_LIKE_TRACKER)) return; /*if(m) fprintf(stderr, "midi_out_note called (ch %d)note(%d)instr(%d)volcmd(%02X)cmd(%02X)vol(%02X)p(%02X)\n", chan, m->note, m->instrument, m->voleffect, m->effect, m->volparam, m->param); else fprintf(stderr, "midi_out_note called (ch %d) m=%p\n", m);*/ if (!midi_playing) { csf_process_midi_macro(current_song, 0, current_song->midi_config.start, 0, 0, 0, 0); // START! midi_playing = 1; } if (chan < 0) { return; } c = ¤t_song->voices[chan]; chan %= 64; if (!m) { if (last_row_number != (signed) current_song->row) return; m = last_row[chan]; if (!m) return; } else { last_row[chan] = m; last_row_number = current_song->row; } ins = ins_tracker[chan]; if (m->instrument > 0) { ins = m->instrument; } if (ins < 0 || ins >= MAX_INSTRUMENTS) return; /* err... almost certainly */ if (!current_song->instruments[ins]) return; if (current_song->instruments[ins]->midi_channel_mask >= 0x10000) { mc = chan % 16; } else { mc = 0; if(current_song->instruments[ins]->midi_channel_mask > 0) while(!(current_song->instruments[ins]->midi_channel_mask & (1 << mc))) ++mc; } m_note = m->note; tc = current_song->tick_count % current_song->current_speed; #if 0 printf("channel = %d note=%d starting_note=%p\n",chan,m_note,starting_note); #endif if (m->effect == FX_SPECIAL) { switch (m->param & 0x80) { case 0xC0: /* note cut */ if (tc == (((unsigned)m->param) & 15)) { m_note = NOTE_CUT; } else if (tc != 0) return; break; case 0xD0: /* note delay */ if (tc != (((unsigned)m->param) & 15)) return; break; default: if (tc != 0) return; }; } else { if (tc != 0 && !starting_note) return; } need_note = need_velocity = -1; if (m_note > 120) { if (note_tracker[chan] != 0) { csf_process_midi_macro(current_song, chan, current_song->midi_config.note_off, 0, note_tracker[chan], 0, ins_tracker[chan]); } note_tracker[chan] = 0; if (m->voleffect != VOLFX_VOLUME) { vol_tracker[chan] = 64; } else { vol_tracker[chan] = m->voleffect; } } else if (!m->note && m->voleffect == VOLFX_VOLUME) { vol_tracker[chan] = m->volparam; need_velocity = vol_tracker[chan]; } else if (m->note) { if (note_tracker[chan] != 0) { csf_process_midi_macro(current_song, chan, current_song->midi_config.note_off, 0, note_tracker[chan], 0, ins_tracker[chan]); } note_tracker[chan] = m_note; if (m->voleffect != VOLFX_VOLUME) { vol_tracker[chan] = 64; } else { vol_tracker[chan] = m->volparam; } need_note = note_tracker[chan]; need_velocity = vol_tracker[chan]; } if (m->instrument > 0) { ins_tracker[chan] = ins; } mg = (current_song->instruments[ins]->midi_program) + ((midi_flags & MIDI_BASE_PROGRAM1) ? 1 : 0); mbl = current_song->instruments[ins]->midi_bank; mbh = (current_song->instruments[ins]->midi_bank >> 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 csf_midi_send(current_song, buf, 3, 0, 0); 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 csf_midi_send(current_song, buf, 3, 0, 0); was_banklo[mc] = mbl; } if (mg > -1 && was_program[mc] != mg) { was_program[mc] = mg; csf_process_midi_macro(current_song, chan, current_song->midi_config.set_program, mg, 0, 0, ins); // program change } if (c->flags & CHN_MUTE) { // don't send noteon events when muted } else if (need_note > 0) { if (need_velocity == -1) need_velocity = 64; // eh? need_velocity = CLAMP(need_velocity*2,0,127); csf_process_midi_macro(current_song, chan, current_song->midi_config.note_on, 0, need_note, need_velocity, ins); // noteon } else if (need_velocity > -1 && note_tracker[chan] > 0) { need_velocity = CLAMP(need_velocity*2,0,127); csf_process_midi_macro(current_song, chan, current_song->midi_config.set_volume, need_velocity, note_tracker[chan], need_velocity, ins); // volume-set } } static void _schism_midi_out_raw(const unsigned char *data, unsigned int len, unsigned int pos) { #if 0 i = (8000*(audio_buffer_samples - delay)); i /= (current_song->mix_frequency); #endif #if 0 for (int i=0; i < len; i++) { printf("%02x ",data[i]); }puts(""); #endif if (!_disko_writemidi(data,len,pos)) midi_send_buffer(data,len,pos); } // ------------------------------------------------------------------------------------------------------------ void song_lock_audio(void) { SDL_LockAudio(); } void song_unlock_audio(void) { SDL_UnlockAudio(); } void song_start_audio(void) { SDL_PauseAudio(0); } void song_stop_audio(void) { SDL_PauseAudio(1); } static void song_print_info_top(const char *d) { log_append(2, 0, "Audio initialised"); log_underline(17); log_appendf(5, " Using driver '%s'", d); } /* --------------------------------------------------------------------------------------------------------- */ /* Nasty stuff here */ const char *song_audio_driver(void) { return driver_name; } /* NOTE: driver_spec must not be NULL here */ static void _audio_set_envvars(const char *driver_spec) { char *driver = NULL, *device = NULL; unset_env_var("AUDIODEV"); unset_env_var("SDL_PATH_DSP"); if (!*driver_spec) { unset_env_var("SDL_AUDIODRIVER"); } else if (str_break(driver_spec, ':', &driver, &device)) { /* "nosound" and "none" are for the sake of older versions: --help suggested using "none", but the name presented in the rest of the interface was "nosound". "oss" is a synonym for "dsp" because everyone should know what "oss" is and "dsp" is a lousy name for an audio driver */ put_env_var("SDL_AUDIODRIVER", (strcmp(driver, "oss") == 0) ? "dsp" : (strcmp(driver, "nosound") == 0) ? "dummy" : (strcmp(driver, "none") == 0) ? "dummy" : driver); if (*device) { /* Documentation says that SDL_PATH_DSP overrides AUDIODEV if it's set, but the SDL alsa code only looks at AUDIODEV. Annoying. */ put_env_var("AUDIODEV", device); put_env_var("SDL_PATH_DSP", device); } free(driver); free(device); } else { /* Assuming just the driver was given. (Old behavior was trying to guess -- selecting 'dsp' driver for /dev/dsp, etc. but this is rather flaky and problematic) */ put_env_var("SDL_AUDIODRIVER", driver_spec); } strncpy(active_audio_driver, driver_spec, sizeof(active_audio_driver)); active_audio_driver[sizeof(active_audio_driver) - 1] = '\0'; } /* NOTE: driver_spec must not be NULL here 'verbose' => print stuff to the log about what device/driver was configured */ static int _audio_open(const char *driver_spec, int verbose) { _audio_set_envvars(driver_spec); if (SDL_WasInit(SDL_INIT_AUDIO)) SDL_QuitSubSystem(SDL_INIT_AUDIO); if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) return 0; /* This is needed in order to coax alsa into actually respecting the buffer size, since it's evidently ignored entirely for "fake" devices such as "default" -- which SDL happens to use if no device name is set. (see SDL_alsa_audio.c: http://tinyurl.com/ybf398f) If hw doesn't exist, so be it -- let this fail, we'll fall back to the dummy device, and the user can pick a more reasonable device later. */ if (SDL_AudioDriverName(driver_name, sizeof(driver_name)) != NULL && !strcmp(driver_name, "alsa")) { char *dev = getenv("AUDIODEV"); if (!dev || !*dev) put_env_var("AUDIODEV", "hw"); } /* ... THIS is needed because, if the buffer size isn't a power of two, the dsp driver will punt since it's not nice enough to fix it for us. (contrast alsa, which is TOO nice and fixes it even when we don't want it to) */ int size_pow2 = 2; while (size_pow2 < audio_settings.buffer_size) size_pow2 <<= 1; /* Round to nearest, I suppose */ if (size_pow2 != audio_settings.buffer_size && (size_pow2 - audio_settings.buffer_size) > (audio_settings.buffer_size - (size_pow2 >> 1))) { size_pow2 >>= 1; } SDL_AudioSpec desired = { .freq = audio_settings.sample_rate, .format = (audio_settings.bits == 8) ? AUDIO_U8 : AUDIO_S16SYS, .channels = audio_settings.channels, .samples = size_pow2, .callback = audio_callback, .userdata = NULL, }; SDL_AudioSpec obtained; if (SDL_OpenAudio(&desired, &obtained) < 0) return 0; /* I don't know why this would change between SDL_AudioInit and SDL_OpenAudio, but I'm paranoid */ SDL_AudioDriverName(driver_name, sizeof(driver_name)); song_lock_audio(); /* format&255 is SDL specific... need bits */ csf_set_wave_config(current_song, obtained.freq, obtained.format & 255, obtained.channels); audio_output_channels = obtained.channels; audio_output_bits = obtained.format & 255; audio_sample_size = audio_output_channels * (audio_output_bits/8); audio_buffer_samples = obtained.samples; if (verbose) { song_print_info_top(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); } return 1; } // Configure a device. (called at startup) static void _audio_init_head(const char *driver_spec, int verbose) { const char *err = NULL, *err_default = NULL; char ugh[256]; /* Use the device from the config if it exists. */ if (!driver_spec || !*driver_spec) driver_spec = cfg_audio_driver; if (*driver_spec) { errno = 0; if (_audio_open(driver_spec, verbose)) return; err = SDL_GetError(); /* Errors returned only as strings! Environment variables used for everything! Turns out that SDL is actually a very elaborate shell script, so it all makes sense. Anyway, this error isn't really accurate because there might be many more devices and it's just as likely that the *driver* name is wrong (e.g. "asla"). errno MIGHT be useful, at least on 'nix, and it does tend to provide reasonable messages for common cases such as the device being opened already; plus, we can make a guess if SDL just gave up and didn't do anything because it didn't know the driver name. However, since this is probably just as likely to be wrong as it is right, make a note of it. */ if (strcmp(err, "No available audio device") == 0) { if (errno == 0) { err = "Device init failed (No SDL driver by that name?)"; } else { snprintf(ugh, sizeof(ugh), "Device init failed (%s?)", strerror(errno)); ugh[sizeof(ugh) - 1] = '\0'; err = ugh; } } log_appendf(4, "%s: %s", driver_spec, err); log_appendf(4, "Retrying with default device..."); log_nl(); } /* Try the default device? */ if (_audio_open("", verbose)) return; err_default = SDL_GetError(); log_appendf(4, "%s", err_default); if (!_audio_open("dummy", 0)) { /* yarrr, abandon ship! */ if (*driver_spec) fprintf(stderr, "%s: %s\n", driver_spec, err); fprintf(stderr, "%s\n", err_default); fprintf(stderr, "Couldn't initialise audio!\n"); exit(1); } } // Set up audio_buffer, reset the sample count, and kick off the mixer // (note: _audio_open will leave the device LOCKED) static void _audio_init_tail(void) { free(audio_buffer); audio_buffer = mem_calloc(audio_buffer_samples, audio_sample_size); samples_played = (status.flags & CLASSIC_MODE) ? SMP_INIT : 0; song_unlock_audio(); song_start_audio(); } void audio_init(const char *driver_spec) { _audio_init_head(driver_spec, 1); _audio_init_tail(); } void audio_reinit(void) { if (status.flags & (DISKWRITER_ACTIVE|DISKWRITER_ACTIVE_PATTERN)) { /* never allowed */ return; } song_stop(); _audio_init_head(active_audio_driver, 0); _audio_init_tail(); if (status.flags & CLASSIC_MODE) // FIXME: but we spontaneously report a GUS card sometimes... status_text_flash("Sound Blaster 16 reinitialised"); else status_text_flash("Audio output reinitialised"); } /* --------------------------------------------------------------------------------------------------------- */ void song_init_eq(int do_reset) { uint32_t pg[4]; uint32_t 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]) * (current_song->mix_frequency / 128) / 1024); } set_eq_gains(pg, 4, pf, do_reset, current_song->mix_frequency); } void song_init_modplug(void) { song_lock_audio(); max_voices = audio_settings.channel_limit; csf_set_resampling_mode(current_song, audio_settings.interpolation_mode); if (audio_settings.no_ramping) current_song->mix_flags |= SNDMIX_NORAMPING; else current_song->mix_flags &= ~SNDMIX_NORAMPING; // disable the S91 effect? (this doesn't make anything faster, it // just sounds better with one woofer.) song_set_surround(audio_settings.surround_effect); // update midi queue configuration midi_queue_alloc(audio_buffer_samples, audio_sample_size, current_song->mix_frequency); // timelimit the playback_update() calls when midi isn't actively going on audio_buffers_per_second = (current_song->mix_frequency / (audio_buffer_samples * 8 * audio_sample_size)); if (audio_buffers_per_second > 1) audio_buffers_per_second--; song_unlock_audio(); } void song_initialise(void) { csf_midi_out_note = _schism_midi_out_note; csf_midi_out_raw = _schism_midi_out_raw; current_song = csf_allocate(); //song_stop(); <- song_new does this song_set_linear_pitch_slides(1); song_new(0); // hmm. current_song->mix_flags |= SNDMIX_MUTECHNMODE; } schismtracker-20180209/schism/charset.c000066400000000000000000000227451323741476300177320ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "charset.h" /* real character set stuff will occur here eventually... */ int char_digraph(int k1, int k2) { //int c; #define DG(ax) ((k1==ax[0] && k2 == ax[1])||(k2==ax[0] && k1==ax[1])) if (DG("NB")) return '#'; if (DG("DO")) return '$'; if (DG("At")) return '@'; if (DG("<(")) return '['; if (DG("//")) return '\\'; if (DG(")>")) return ']'; if (DG("'>")) return '^'; if (DG("'!")) return '`'; if (DG("(!")) return '{'; if (DG("!!")) return '|'; if (DG("!)")) return '{'; if (DG("'?")) return '~'; if (DG("C,")) return 128; // LATIN CAPITAL LETTER C WITH CEDILLA if (DG("u:")) return 129; // LATIN SMALL LETTER U WITH DIAERESIS if (DG("e'")) return 130; // LATIN SMALL LETTER E WITH ACUTE if (DG("a>")) return 131; // LATIN SMALL LETTER A WITH CIRCUMFLEX if (DG("a:")) return 132; // LATIN SMALL LETTER A WITH DIAERESIS if (DG("a!")) return 133; // LATIN SMALL LETTER A WITH GRAVE if (DG("aa")) return 134; // LATIN SMALL LETTER A WITH RING ABOVE if (DG("c,")) return 135; // LATIN SMALL LETTER C WITH CEDILLA if (DG("e>")) return 136; // LATIN SMALL LETTER E WITH CIRCUMFLEX if (DG("e:")) return 137; // LATIN SMALL LETTER E WITH DIAERESIS if (DG("e!")) return 138; // LATIN SMALL LETTER E WITH GRAVE if (DG("i:")) return 139; // LATIN SMALL LETTER I WITH DIAERESIS if (DG("i>")) return 140; // LATIN SMALL LETTER I WITH CIRCUMFLEX if (DG("i!")) return 141; // LATIN SMALL LETTER I WITH GRAVE if (DG("A:")) return 142; // LATIN CAPITAL LETTER A WITH DIAERESIS if (DG("AA")) return 143; // LATIN CAPITAL LETTER A WITH RING ABOVE if (DG("E'")) return 144; // LATIN CAPITAL LETTER E WITH ACUTE if (DG("ae")) return 145; // LATIN SMALL LETTER AE if (DG("AE")) return 146; // LATIN CAPITAL LETTER AE if (DG("o>")) return 147; // LATIN SMALL LETTER O WITH CIRCUMFLEX if (DG("o:")) return 148; // LATIN SMALL LETTER O WITH DIAERESIS if (DG("o!")) return 149; // LATIN SMALL LETTER O WITH GRAVE if (DG("u>")) return 150; // LATIN SMALL LETTER U WITH CIRCUMFLEX if (DG("u!")) return 151; // LATIN SMALL LETTER U WITH GRAVE if (DG("y:")) return 152; // LATIN SMALL LETTER Y WITH DIAERESIS if (DG("O:")) return 153; // LATIN CAPITAL LETTER O WITH DIAERESIS if (DG("U:")) return 154; // LATIN CAPITAL LETTER U WITH DIAERESIS if (DG("Ct")) return 155; // CENT SIGN if (DG("Pd")) return 156; // POUND SIGN if (DG("Ye")) return 157; // YEN SIGN if (DG("Pt")) return 158; if (DG("ff")) return 159; if (DG("a'")) return 160; // LATIN SMALL LETTER A WITH ACUTE if (DG("i'")) return 161; // LATIN SMALL LETTER I WITH ACUTE if (DG("o'")) return 162; // LATIN SMALL LETTER O WITH ACUTE if (DG("u'")) return 163; // LATIN SMALL LETTER U WITH ACUTE if (DG("n?")) return 164; // LATIN SMALL LETTER N WITH TILDE if (DG("N?")) return 165; // LATIN CAPITAL LETTER N WITH TILDE if (DG("-a")) return 166; // FEMININE ORDINAL INDICATOR if (DG("-o")) return 167; // MASCULINE ORDINAL INDICATOR if (DG("?I")) return 168; // INVERTED QUESTION MARK if (DG("NO")) return 170; // NOT SIGN if (DG("12")) return 171; // VULGAR FRACTION ONE HALF if (DG("14")) return 174; // VULGAR FRACTION ONE QUARTER if (DG("!I")) return 175; // INVERTED EXCLAMATION MARK if (DG("<<")) return 176; // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK if (DG(">>")) return 177; // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK if (DG("ss")) return 225; // LATIN SMALL LETTER SHARP S if (DG("pi")) return 227; // PI... mmm... pie... if (DG("My")) return 230; // MICRO SIGN if (DG("o/")) return 237; // LATIN SMALL LETTER O WITH STROKE if (DG("O/")) return 237; // LATIN SMALL LETTER O WITH STROKE if (DG("+-")) return 241; // PLUS-MINUS SIGN if (DG("-:")) return 246; // DIVISION SIGN if (DG("DG")) return 248; // DEGREE SIGN if (DG(".M")) return 249; // MIDDLE DOT if (DG("2S")) return 253; // SUPERSCRIPT TWO if (DG("nS")) return 252; if (DG("PI")) return 20; // PILCROW SIGN if (DG("SE")) return 21; // SECTION SIGN #undef DG return 0; } int char_unicode_to_cp437(unsigned int c) { if (c >= 32 && c <= 127) return c; switch (c) { case 0x263A: return 1; // WHITE SMILING FACE case 0x263B: return 2; // BLACK SMILING FACE case 0x2661: case 0x2665: return 3; // BLACK HEART case 0x2662: case 0x25C6: case 0x2666: return 4; // BLACK DIAMOND case 0x2667: case 0x2663: return 5; // BLACK CLUBS case 0x2664: case 0x2660: return 6; // BLACK SPADE case 0x25CF: return 7; // BLACK CIRCLE case 0x25D8: return 8; // INVERSE BULLET case 0x25CB: case 0x25E6: case 0x25EF: return 9; // LARGE CIRCLE case 0x25D9: return 10; // INVERSE WHITE CIRCLE case 0x2642: return 11; // MALE / MARS case 0x2640: return 12; // FEMALE / VENUS case 0x266A: return 13; // EIGHTH NOTE case 0x266B: return 14; // BEAMED EIGHTH NOTES case 0x2195: return 18; // UP DOWN ARROW case 0x203C: return 19; // DOUBLE EXCLAMATION MARK case 0x00B6: return 20; // PILCROW SIGN case 0x00A7: return 21; // SECTION SIGN case 0x21A8: return 23; // UP DOWN ARROW WITH BASE case 0x2191: return 24; // UPWARD ARROW case 0x2193: return 25; // DOWNWARD ARROW case 0x2192: return 26; // RIGHTWARD ARROW case 0x2190: return 27; // LEFTWARD ARROW case 0x2194: return 29; // LEFT RIGHT ARROW case 0x266F: return '#';// MUSIC SHARP SIGN case 0x00A6: return 124; case 0x0394: case 0x2302: return 127;// HOUSE case 0x20B5: case 0x20B2: case 0x00A2: return 155;// CENT SIGN case 0x00A3: return 156;// POUND SIGN case 0x00A5: return 157;// YEN SIGN case 0x2310: return 169;// REVERSED NOT SIGN case 0x00AC: return 170;// NOT SIGN case 0x00BD: return 171;// 1/2 case 0x00BC: return 172;// 1/4 case 0x00A1: return 173;// INVERTED EXCLAMATION MARK case 0x00AB: return 174;// << case 0x00BB: return 175;// >> case 0x2591: return 176;// LIGHT SHADE case 0x2592: return 177;// MEDIUM SHADE case 0x2593: return 178;// DARK SHADE // BOX DRAWING case 0x2502: return 179; case 0x2524: return 180; case 0x2561: return 181; case 0x2562: return 182; case 0x2556: return 183; case 0x2555: return 184; case 0x2563: return 185; case 0x2551: return 186; case 0x2557: return 187; case 0x255D: return 188; case 0x255C: return 189; case 0x255B: return 190; case 0x2510: return 191; case 0x2514: return 192; case 0x2534: return 193; case 0x252C: return 194; case 0x251C: return 195; case 0x2500: return 196; case 0x253C: return 197; case 0x255E: return 198; case 0x255F: return 199; case 0x255A: return 200; case 0x2554: return 201; case 0x2569: return 202; case 0x2566: return 203; case 0x2560: return 204; case 0x2550: return 205; case 0x256C: return 206; case 0x2567: return 207; case 0x2568: return 208; case 0x2564: return 209; case 0x2565: return 210; case 0x2559: return 211; case 0x2558: return 212; case 0x2552: return 213; case 0x2553: return 214; case 0x256B: return 215; case 0x256A: return 216; case 0x2518: return 217; case 0x250C: return 218; case 0x25A0: return 219;// BLACK SQUARE case 0x2584: return 220;// LOWER HALF BLOCK case 0x258C: return 221;// LEFT HALF BLOCK case 0x2590: return 222;// RIGHT HALF BLOCK case 0x2580: return 223;// UPPER HALF BLOCK case 0x03B1: return 224;// GREEK SMALL LETTER ALPHA case 0x03B2: return 225;// GREEK SMALL LETTER BETA case 0x0393: return 226;// GREEK CAPITAL LETTER GAMMA case 0x03C0: return 227;// mmm... pie... case 0x03A3: case 0x2211: return 228;// N-ARY SUMMATION / CAPITAL SIGMA case 0x03C3: return 229;// GREEK SMALL LETTER SIGMA case 0x03BC: case 0x00b5: return 230;// GREEK SMALL LETTER MU case 0x03C4: case 0x03D2: return 231;// GREEK UPSILON+HOOK case 0x03B8: return 233;// GREEK SMALL LETTER THETA case 0x03A9: return 234;// GREEK CAPITAL LETTER OMEGA case 0x03B4: return 235;// GREEK SMALL LETTER DELTA case 0x221E: return 236;// INFINITY case 0x00D8: case 0x00F8: return 237;// LATIN ... LETTER O WITH STROKE case 0x03F5: return 238;// GREEK LUNATE EPSILON SYMBOL case 0x2229: case 0x03A0: return 239;// GREEK CAPITAL LETTER PI case 0x039E: return 240;// GREEK CAPITAL LETTER XI case 0x00b1: return 241;// PLUS-MINUS SIGN case 0x2265: return 242;// GREATER-THAN OR EQUAL TO case 0x2264: return 243;// LESS-THAN OR EQUAL TO case 0x2320: return 244;// TOP HALF INTEGRAL case 0x2321: return 245;// BOTTOM HALF INTEGRAL case 0x00F7: return 246;// DIVISION SIGN case 0x2248: return 247;// ALMOST EQUAL TO case 0x00B0: return 248;// DEGREE SIGN case 0x00B7: return 249;// MIDDLE DOT case 0x2219: case 0x0387: return 250;// GREEK ANO TELEIA case 0x221A: return 251;// SQUARE ROOT // NO UNICODE ALLOCATION? case 0x00B2: return 253;// SUPERSCRIPT TWO case 0x220E: return 254;// QED }; return c; } schismtracker-20180209/schism/clippy.c000066400000000000000000000305621323741476300175750ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" /* always include this one first, kthx */ #include "clippy.h" #include "event.h" #include "util.h" #include "sdlmain.h" static char *_current_selection = NULL; static char *_current_clipboard = NULL; static struct widget *_widget_owner[16] = {NULL}; static int has_sys_clip; #if defined(WIN32) static HWND SDL_Window, _hmem; #elif defined(__QNXNTO__) static unsigned short inputgroup; #elif defined(USE_X11) static Display *SDL_Display = NULL; static Window SDL_Window; static void (*lock_display)(void); static void (*unlock_display)(void); static Atom atom_sel; static Atom atom_clip; 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 j; char *dst; char *freeme; #if defined(__QNXNTO__) PhClipboardHdr clheader = {Ph_CLIPBOARD_TYPE_TEXT, 0, NULL}; char *tmp; int *cldata; int status; #endif freeme = NULL; if (!_current_selection) { dst = NULL; j = 0; } else #if defined(WIN32) j = strlen(_current_selection); #else if (has_sys_clip) { int i; /* convert to local */ freeme = dst = malloc(strlen(_current_selection)+4); if (!dst) return; for (i = j = 0; _current_selection[i]; i++) { dst[j] = _current_selection[i]; if (dst[j] != '\r') j++; } dst[j] = '\0'; } else { dst = NULL; j = 0; } #endif #if defined(USE_X11) if (has_sys_clip) { lock_display(); if (!dst) dst = (char *) ""; /* blah */ if (j < 0) j = 0; 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+1); if (_hmem) { dst = (char *)GlobalLock(_hmem); if (dst) { /* this seems wrong, but msdn does this */ memcpy(dst, _current_selection, j); dst[j] = '\0'; GlobalUnlock(_hmem); EmptyClipboard(); SetClipboardData(CF_TEXT, _hmem); } } CloseClipboard(); _hmem = NULL; dst = 0; } #elif defined(__QNXNTO__) if (!do_sel) { tmp = (char *)malloc(j+4); if (!tmp) { cldata=(int*)tmp; *cldata = Ph_CL_TEXT; if (dst) memcpy(tmp+4, dst, j); clheader.data = tmp; #if (NTO_VERSION < 620) if (clheader.length > 65535) clheader.length=65535; #endif clheader.length = j + 4; #if (NTO_VERSION < 620) PhClipboardCopy(inputgroup, 1, &clheader); #else PhClipboardWrite(inputgroup, 1, &clheader); #endif free(tmp); } } #elif defined(MACOSX) if (!do_sel) macosx_clippy_put(_current_clipboard); #else // some other system -- linux without x11, maybe // pretend we used the param to silence warnings (void) do_sel; #endif if (freeme) free(freeme); } /* TODO: is the first parameter ever going to be used, or can we kill it? */ static void _string_paste(UNUSED int cb, const char *cbptr) { SDL_Event event = {}; event.user.type = SCHISM_EVENT_PASTE; event.user.data1 = str_dup(cbptr); /* current_clipboard... is it safe? */ if (!event.user.data1) return; /* eh... */ if (SDL_PushEvent(&event) == -1) { free(event.user.data1); } } #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; unsigned char *src; 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(); src = NULL; if (XGetWindowProperty(SDL_Display, SDL_Window, atom_sel, 0, 9000, False, XA_STRING, (Atom *)&seln_type, (int *)&seln_format, (unsigned long *)&nbytes, (unsigned long *)&overflow, (unsigned char **)&src) == Success) { if (seln_type == XA_STRING) { if (_current_selection != _current_clipboard) { free(_current_clipboard); } _current_clipboard = strn_dup((const char *)src, nbytes); _string_paste(CLIPPY_BUFFER, _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 (nbytes && 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; } static int (*orig_xlib_err)(Display *d, XErrorEvent *e) = NULL; static int handle_xlib_err(Display *d, XErrorEvent *e) { /* X_SetSelectionOwner == 22 */ if (e->error_code == BadWindow && e->request_code == 22) { /* return 0 here to avoid dying as the result of a nonfatal race condition */ return 0; } if (orig_xlib_err) return orig_xlib_err(d,e); return 0; } #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); orig_xlib_err = XSetErrorHandler(handle_xlib_err); } 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; #endif #if defined(USE_X11) Window owner; int getme; #elif defined(WIN32) char *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 NULL; } #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 = strn_dup(src, clen); } 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 = strn_dup(src, clen); } 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 = strn_dup(src, clen); } } #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 = str_dup(macosx_clippy_get()); if (_current_clipboard != _current_selection) { free(_current_clipboard); } _current_clipboard = src; if (!src) return (char *) ""; /* FIXME: de-const-ing is bad */ return _current_clipboard; } #else if (cb == CLIPPY_BUFFER) return _current_clipboard; #endif return NULL; } void clippy_paste(int cb) { char *q; q = _internal_clippy_paste(cb); if (!q) return; _string_paste(cb, 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 = NULL; _widget_owner[CLIPPY_SELECT] = NULL; } else { for (i = 0; addr[i] && (len < 0 || i < len); i++) { /* nothing */ } _current_selection = strn_dup(addr, i); _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 NULL; } 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"); _clippy_copy_to_sys(0); } } schismtracker-20180209/schism/config-parser.c000066400000000000000000000414671323741476300210420ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "slurp.h" #include "util.h" #include "config-parser.h" #include #include #include #include #include #include #include /* --------------------------------------------------------------------------------------------------------- */ /* 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 = mem_calloc(1, sizeof(struct cfg_section)); section->name = str_dup(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 = mem_calloc(1, sizeof(struct cfg_key)); key->name = str_dup(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 == ';') ptr += strcspn(ptr, "\r\n"); } while (*ptr && ptr != prev); len = ptr - s; if (len) { /* save the comments */ new_comments = strn_dup(s, len); if (*comments) { /* already have some comments -- add to them */ if (asprintf(&tmp, "%s%s", *comments, new_comments) == -1) { perror("asprintf"); exit(255); } if (!tmp) { perror("asprintf"); exit(255); } 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); (*cur_section)->omit = 0; if (comments) { if ((*cur_section)->comments) { /* glue them together */ if (asprintf(&tmp, "%s\n%s", comments, (*cur_section)->comments) == -1) { perror("asprintf"); exit(255); } if (!tmp) { perror("asprintf"); exit(255); } 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 */ if (asprintf(&tmp, "%s\n%s", comments, key->comments) == -1) { perror("asprintf"); exit(255); } if (!tmp) { perror("asprintf"); exit(255); } 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. semicolon-comments are only handled at the start of lines. */ len = strcspn(pos, "#\r\n"); if (len) { char *line; line = strn_dup(pos, len); 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) { if (asprintf(&tmp, "%s# %s\n", comments, line) == -1) { perror("asprintf"); exit(255); } if (!tmp) { perror("asprintf"); exit(255); } free(comments); comments = tmp; } else { if (asprintf(&comments, "# %s\n", line) == -1) { perror("asprintf"); exit(255); } if (!comments) { perror("asprintf"); exit(255); } } } free(line); } pos += len; /* skip the newline */ if (*pos == '\r') pos++; if (*pos == '\n') pos++; } while (*pos); cfg->eof_comments = comments; cfg->dirty = 0; 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; } if (!cfg->dirty) return 0; cfg->dirty = 0; make_backup_file(cfg->filename, 0); fp = fopen(cfg->filename, "wb"); 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); if (section->omit) fputc('#', fp); fprintf(fp, "[%s]\n", section->name); for (key = section->keys; key; key = key->next) { /* NOTE: key names are intentionally not escaped in any way; * it is up to the program to choose names that aren't stupid. * (cfg_delete_key uses this to comment out a key name) */ if (key->comments) fprintf(fp, "%s", key->comments); if (section->omit) fputc('#', fp); /* 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) { //copy up to len chars [0..len-1] 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); section->omit = 0; key = _get_key(section, key_name, 1); if (key->value) free(key->value); if (value) key->value = str_dup(value); else key->value = NULL; cfg->dirty = 1; } 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); section->omit = 0; key = _get_key(section, key_name, 1); if (key->value) free(key->value); if (asprintf(&key->value, "%d", value) == -1) { perror("asprintf"); exit(255); } if (!key->value) { perror("asprintf"); exit(255); } cfg->dirty = 1; } void cfg_delete_key(cfg_file_t *cfg, const char *section_name, const char *key_name) { struct cfg_section *section; struct cfg_key *key; char *newname; if (section_name == NULL || key_name == NULL) return; section = _get_section(cfg, section_name, 1); key = _get_key(section, key_name, 0); if (key == NULL || key->name[0] == '#') return; newname = mem_alloc(strlen(key->name) + 2); newname[0] = '#'; strcpy(newname + 1, key->name); free(key->name); key->name = newname; } int cfg_init(cfg_file_t *cfg, const char *filename) { memset(cfg, 0, sizeof(*cfg)); cfg->filename = str_dup(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 */ schismtracker-20180209/schism/config.c000066400000000000000000000264341323741476300175450ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "video.h" /* shouldn't need this */ #include #include #include "config-parser.h" #include "dmoz.h" #include "osdefs.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; int cfg_video_mousecursor = MOUSE_EMULATED; int cfg_video_gl_bilinear = 1; int cfg_video_width, cfg_video_height; /* --------------------------------------------------------------------- */ #if defined(WIN32) # define DOT_SCHISM "Schism Tracker" #elif defined(MACOSX) # define DOT_SCHISM "Library/Application Support/Schism Tracker" #elif defined(GEKKO) # define DOT_SCHISM "." #else # define DOT_SCHISM ".schism" #endif void cfg_init_dir(void) { #if defined(__amigaos4__) strcpy(cfg_dir_dotschism, "PROGDIR:"); #else char *dot_dir, *ptr; dot_dir = get_dot_directory(); ptr = dmoz_path_concat(dot_dir, DOT_SCHISM); strncpy(cfg_dir_dotschism, ptr, PATH_MAX); cfg_dir_dotschism[PATH_MAX] = 0; free(dot_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) { uint8_t 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, 48, ""); 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++) { /* Changed older implementation for this, since it is not vital to have speed here and the compiler printed a warning */ palette_text[n] = palette_trans[current_palette[n/3][n%3]]; } palette_text[48] = '\0'; cfg_set_string(cfg, "General", "palette_cur", palette_text); } /* --------------------------------------------------------------------------------------------------------- */ void cfg_load(void) { char *tmp; const char *ptr; int i; cfg_file_t cfg; tmp = dmoz_path_concat(cfg_dir_dotschism, "config"); cfg_init(&cfg, tmp); free(tmp); /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ cfg_get_string(&cfg, "Video", "driver", cfg_video_driver, 64, ""); cfg_video_width = cfg_get_number(&cfg, "Video", "width", 640); cfg_video_height = cfg_get_number(&cfg, "Video", "height", 400); cfg_video_fullscreen = !!cfg_get_number(&cfg, "Video", "fullscreen", 0); cfg_video_mousecursor = cfg_get_number(&cfg, "Video", "mouse_cursor", MOUSE_EMULATED); cfg_video_mousecursor = CLAMP(cfg_video_mousecursor, 0, MOUSE_MAX_STATE); cfg_video_gl_bilinear = !!cfg_get_number(&cfg, "Video", "gl_bilinear", 1); ptr = cfg_get_string(&cfg, "Video", "aspect", NULL, 0, NULL); if (ptr && *ptr) put_env_var("SCHISM_VIDEO_ASPECT", ptr); tmp = get_home_directory(); cfg_get_string(&cfg, "Directories", "modules", cfg_dir_modules, PATH_MAX, tmp); cfg_get_string(&cfg, "Directories", "samples", cfg_dir_samples, PATH_MAX, tmp); cfg_get_string(&cfg, "Directories", "instruments", cfg_dir_instruments, PATH_MAX, tmp); free(tmp); ptr = cfg_get_string(&cfg, "Directories", "module_pattern", NULL, 0, NULL); if (ptr) { strncpy(cfg_module_pattern, ptr, PATH_MAX); cfg_module_pattern[PATH_MAX] = 0; } ptr = cfg_get_string(&cfg, "Directories", "export_pattern", NULL, 0, NULL); if (ptr) { strncpy(cfg_export_pattern, ptr, PATH_MAX); cfg_export_pattern[PATH_MAX] = 0; } ptr = cfg_get_string(&cfg, "General", "numlock_setting", NULL, 0, NULL); if (!ptr) status.fix_numlock_setting = NUMLOCK_GUESS; else if (strcasecmp(ptr, "on") == 0) status.fix_numlock_setting = NUMLOCK_ALWAYS_ON; else if (strcasecmp(ptr, "off") == 0) status.fix_numlock_setting = NUMLOCK_ALWAYS_OFF; else status.fix_numlock_setting = NUMLOCK_HONOR; set_key_repeat(cfg_get_number(&cfg, "General", "key_repeat_delay", key_repeat_delay()), cfg_get_number(&cfg, "General", "key_repeat_rate", key_repeat_rate())); /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ cfg_load_info(&cfg); cfg_load_patedit(&cfg); cfg_load_audio(&cfg); cfg_load_midi(&cfg); cfg_load_disko(&cfg); cfg_load_dmoz(&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", 1)) status.flags |= MAKE_BACKUPS; else status.flags &= ~MAKE_BACKUPS; if (cfg_get_number(&cfg, "General", "numbered_backups", 0)) status.flags |= NUMBERED_BACKUPS; else status.flags &= ~NUMBERED_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; kbd_sharp_flat_toggle(cfg_get_number(&cfg, "General", "accidentals_as_flats", 0) == 1); #ifdef MACOSX # define DEFAULT_META 1 #else # define DEFAULT_META 0 #endif if (cfg_get_number(&cfg, "General", "meta_is_ctrl", DEFAULT_META)) status.flags |= META_IS_CTRL; else status.flags &= ~META_IS_CTRL; if (cfg_get_number(&cfg, "General", "altgr_is_alt", 1)) status.flags |= ALTGR_IS_ALT; else status.flags &= ~ALTGR_IS_ALT; if (cfg_get_number(&cfg, "Video", "lazy_redraw", 0)) status.flags |= LAZY_REDRAW; else status.flags &= ~LAZY_REDRAW; if (cfg_get_number(&cfg, "General", "midi_like_tracker", 0)) status.flags |= MIDI_LIKE_TRACKER; else status.flags &= ~MIDI_LIKE_TRACKER; 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); // this wart is here because Storlek is retarded cfg_delete_key(&cfg, "Directories", "filename_pattern"); 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); /* No, it's not a directory, but whatever. */ cfg_set_string(&cfg, "Directories", "module_pattern", cfg_module_pattern); cfg_set_string(&cfg, "Directories", "export_pattern", cfg_export_pattern); cfg_save_info(&cfg); cfg_save_patedit(&cfg); cfg_save_audio(&cfg); cfg_save_palette(&cfg); cfg_save_disko(&cfg); cfg_save_dmoz(&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); /* TODO: move these config options to video.c, this is lame :) Or put everything here, which is what the note in audio_loadsave.cc says. Very well, I contradict myself. */ cfg_set_string(&cfg, "Video", "driver", video_driver_name()); cfg_set_number(&cfg, "Video", "fullscreen", !!(video_is_fullscreen())); cfg_set_number(&cfg, "Video", "mouse_cursor", video_mousecursor_visible()); cfg_set_number(&cfg, "Video", "gl_bilinear", video_gl_bilinear()); cfg_set_number(&cfg, "Video", "lazy_redraw", !!(status.flags & LAZY_REDRAW)); cfg_set_number(&cfg, "General", "vis_style", status.vis_style); 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_set_number(&cfg, "General", "numbered_backups", !!(status.flags & NUMBERED_BACKUPS)); cfg_set_number(&cfg, "General", "accidentals_as_flats", !!(status.flags & ACCIDENTALS_AS_FLATS)); cfg_set_number(&cfg, "General", "meta_is_ctrl", !!(status.flags & META_IS_CTRL)); cfg_set_number(&cfg, "General", "altgr_is_alt", !!(status.flags & ALTGR_IS_ALT)); cfg_set_number(&cfg, "General", "midi_like_tracker", !!(status.flags & MIDI_LIKE_TRACKER)); /* Say, whose bright idea was it to make this a string setting? The config file is human editable but that's mostly for developer convenience and debugging purposes. These sorts of things really really need to be options in the GUI so that people don't HAVE to touch the settings. Then we can just use an enum (and we *could* in theory include comments to the config by default listing what the numbers are, but that shouldn't be necessary in most cases. */ switch (status.fix_numlock_setting) { case NUMLOCK_ALWAYS_ON: cfg_set_string(&cfg, "General", "numlock_setting", "on"); break; case NUMLOCK_ALWAYS_OFF: cfg_set_string(&cfg, "General", "numlock_setting", "off"); break; case NUMLOCK_HONOR: cfg_set_string(&cfg, "General", "numlock_setting", "system"); break; case NUMLOCK_GUESS: /* leave empty */ break; }; /* 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); } schismtracker-20180209/schism/dialog.c000066400000000000000000000341751323741476300175400ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" /* --------------------------------------------------------------------- */ /* 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(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; ENSURE_DIALOG(return 0); if (d->handle_key && d->handle_key(k)) return 1; /* this SHOULD be handling on k->state press but the widget key handler is stealing that key. */ if (k->state == KEY_RELEASE && NO_MODIFIER(k->mod)) { 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: /* in Impulse Tracker, 'n' means cancel, not "no"! (results in different behavior on sample quality convert dialog) */ if (!(status.flags & CLASSIC_MODE)) { dialog_no(d->data); return 1; } /* else fall through */ case DIALOG_OK_CANCEL: dialog_cancel(d->data); return 1; default: break; } break; case SDLK_c: switch (status.dialog_type) { case DIALOG_YES_NO: case DIALOG_OK_CANCEL: break; default: return 0; } /* and fall through */ case SDLK_ESCAPE: dialog_cancel(d->data); return 1; case SDLK_o: switch (status.dialog_type) { case DIALOG_YES_NO: case DIALOG_OK_CANCEL: break; default: return 0; } /* and fall through */ case SDLK_RETURN: dialog_yes(d->data); return 1; default: break; } } 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 = mem_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 = mem_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 NULL; } #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 = str_dup(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; /* FIXME | see dialog_create */ if (status.dialog_type & DIALOG_MENU) menu_hide(); 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; } /* --------------------------------------------------------------------- */ /* Other prompt stuff */ static const char *numprompt_title, *numprompt_secondary; static int numprompt_smp_pos1, numprompt_smp_pos2; /* used by the sample prompt */ static int numprompt_titlelen; /* used by the number prompt */ static char numprompt_buf[4]; static struct widget numprompt_widgets[2]; static void (*numprompt_finish)(int n); /* 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 numprompt_value(void) { char *eptr; long n = strtol(numprompt_buf, &eptr, 10); dialog_destroy(); if (eptr > numprompt_buf && eptr[0] == '\0') numprompt_finish(n); } static void numprompt_draw_const(void) { int wx = numprompt_widgets[0].x; int wy = numprompt_widgets[0].y; int ww = numprompt_widgets[0].width; draw_text(numprompt_title, wx - numprompt_titlelen - 1, wy, 3, 2); draw_box(wx - 1, wy - 1, wx + ww, wy + 1, BOX_THICK | BOX_INNER | BOX_INSET); } void numprompt_create(const char *prompt, void (*finish)(int n), char initvalue) { int y = 26; // an indisputable fact of life int dlgwidth, dlgx, entryx; numprompt_title = prompt; numprompt_titlelen = strlen(prompt); numprompt_buf[0] = initvalue; numprompt_buf[1] = '\0'; /* Dialog is made up of border, padding (2 left, 1 right), frame around the text entry, the entry itself, and the prompt; the text entry is offset from the left of the dialog by 4 chars (padding + borders) plus the length of the prompt. */ dlgwidth = 2 + 3 + 2 + 4 + numprompt_titlelen; dlgx = (80 - dlgwidth) / 2; entryx = dlgx + 4 + numprompt_titlelen; create_textentry(numprompt_widgets + 0, entryx, y, 4, 0, 0, 0, NULL, numprompt_buf, 3); numprompt_widgets[0].activate = numprompt_value; numprompt_widgets[0].d.textentry.cursor_pos = initvalue ? 1 : 0; numprompt_finish = finish; dialog_create_custom(dlgx, y - 2, dlgwidth, 5, numprompt_widgets, 1, 0, numprompt_draw_const, NULL); } static int strtonum99(const char *s) { // aaarghhhh int n = 0; if (!s || !*s) return -1; if (s[1]) { // two chars int c = tolower(*s); switch (c) { case '0' ... '9': n = c - '0'; break; case 'a' ... 'g': n = c - 'a' + 10; break; case 'h' ... 'z': n = c - 'h' + 10; break; default: return -1; } n *= 10; s++; } return *s >= '0' && *s <= '9' ? n + *s - '0' : -1; } static void smpprompt_value(UNUSED void *data) { int n = strtonum99(numprompt_buf); numprompt_finish(n); } static void smpprompt_draw_const(void) { int wx = numprompt_widgets[0].x; int wy = numprompt_widgets[0].y; int ww = numprompt_widgets[0].width; draw_text(numprompt_title, numprompt_smp_pos1, 25, 0, 2); draw_text(numprompt_secondary, numprompt_smp_pos2, 27, 0, 2); draw_box(wx - 1, wy - 1, wx + ww, wy + 1, BOX_THICK | BOX_INNER | BOX_INSET); } void smpprompt_create(const char *title, const char *prompt, void (*finish)(int n)) { struct dialog *dialog; numprompt_title = title; numprompt_secondary = prompt; numprompt_smp_pos1 = (81 - strlen(title)) / 2; numprompt_smp_pos2 = 41 - strlen(prompt); numprompt_buf[0] = '\0'; create_textentry(numprompt_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, numprompt_buf, 2); create_button(numprompt_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); numprompt_finish = finish; dialog = dialog_create_custom(26, 23, 29, 10, numprompt_widgets, 2, 0, smpprompt_draw_const, NULL); dialog->action_yes = smpprompt_value; } schismtracker-20180209/schism/disko.c000066400000000000000000000553211323741476300174060ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "song.h" #include "sndfile.h" #include "dmoz.h" #include "config-parser.h" #include "cmixer.h" #include "disko.h" #include #include #include #include #define DW_BUFFER_SIZE 65536 // --------------------------------------------------------------------------- static unsigned int disko_output_rate = 44100; static unsigned int disko_output_bits = 16; static unsigned int disko_output_channels = 2; void cfg_load_disko(cfg_file_t *cfg) { disko_output_rate = cfg_get_number(cfg, "Diskwriter", "rate", 44100); disko_output_bits = cfg_get_number(cfg, "Diskwriter", "bits", 16); disko_output_channels = cfg_get_number(cfg, "Diskwriter", "channels", 2); } void cfg_save_disko(cfg_file_t *cfg) { cfg_set_number(cfg, "Diskwriter", "rate", disko_output_rate); cfg_set_number(cfg, "Diskwriter", "bits", disko_output_bits); cfg_set_number(cfg, "Diskwriter", "channels", disko_output_channels); } // --------------------------------------------------------------------------- // stdio backend static void _dw_stdio_write(disko_t *ds, const void *buf, size_t len) { if (fwrite(buf, len, 1, ds->file) != 1) disko_seterror(ds, errno); } static void _dw_stdio_putc(disko_t *ds, int c) { if (fputc(c, ds->file) == EOF) disko_seterror(ds, errno); } static void _dw_stdio_seek(disko_t *ds, long pos, int whence) { if (fseek(ds->file, pos, whence) < 0) disko_seterror(ds, errno); } static long _dw_stdio_tell(disko_t *ds) { long pos = ftell(ds->file); if (pos < 0) disko_seterror(ds, errno); return pos; } // --------------------------------------------------------------------------- // memory backend // 0 => memory error, abandon ship static int _dw_bufcheck(disko_t *ds, size_t extend) { if (ds->pos + extend <= ds->length) return 1; ds->length = MAX(ds->length, ds->pos + extend); if (ds->length >= ds->allocated) { size_t newsize = MAX(ds->allocated + DW_BUFFER_SIZE, ds->length); uint8_t *new = realloc(ds->data, newsize); if (!new) { // Eek free(ds->data); ds->data = NULL; disko_seterror(ds, errno); return 0; } memset(new + ds->allocated, 0, newsize - ds->allocated); ds->data = new; ds->allocated = newsize; } return 1; } static void _dw_mem_write(disko_t *ds, const void *buf, size_t len) { if (_dw_bufcheck(ds, len)) { memcpy(ds->data + ds->pos, buf, len); ds->pos += len; } } static void _dw_mem_putc(disko_t *ds, int c) { if (_dw_bufcheck(ds, 1)) ds->data[ds->pos++] = c; } static void _dw_mem_seek(disko_t *ds, long offset, int whence) { // mostly from slurp_seek switch (whence) { default: case SEEK_SET: break; case SEEK_CUR: offset += ds->pos; break; case SEEK_END: offset += ds->length; break; } if (offset < 0) { disko_seterror(ds, EINVAL); return; } /* note: seeking doesn't cause a buffer resize. This is consistent with the behavior of stdio streams. Consider: FILE *f = fopen("f", "w"); fseek(f, 1000, SEEK_SET); fclose(f); This will produce a zero-byte file; the size is not extended until data is written. */ ds->pos = offset; } static long _dw_mem_tell(disko_t *ds) { return (long) ds->pos; } // --------------------------------------------------------------------------- void disko_write(disko_t *ds, const void *buf, size_t len) { if (len != 0 && !ds->error) ds->_write(ds, buf, len); } void disko_putc(disko_t *ds, int c) { if (!ds->error) ds->_putc(ds, c); } void disko_seek(disko_t *ds, long pos, int whence) { if (!ds->error) ds->_seek(ds, pos, whence); } /* used by multi-write */ static void disko_seekcur(disko_t *ds, long pos) { disko_seek(ds, pos, SEEK_CUR); } long disko_tell(disko_t *ds) { if (!ds->error) return ds->_tell(ds); return -1; } void disko_seterror(disko_t *ds, int err) { // Don't set an error if one already exists, and don't allow clearing an error value ds->error = errno = ds->error ?: err ?: EINVAL; } // --------------------------------------------------------------------------- disko_t *disko_open(const char *filename) { size_t len; int fd; int err; if (!filename) return NULL; len = strlen(filename); if (len + 6 >= PATH_MAX) { errno = ENAMETOOLONG; return NULL; } #ifndef GEKKO /* FIXME - make a replacement access() */ // Attempt to honor read-only (since we're writing them in such a roundabout way) if (access(filename, W_OK) != 0 && errno != ENOENT) return NULL; #endif disko_t *ds = calloc(1, sizeof(disko_t)); if (!ds) return NULL; // This might seem a bit redundant, but allows for flexibility later // (e.g. putting temp files elsewhere, or mangling the filename in some other way) strcpy(ds->filename, filename); strcpy(ds->tempname, filename); strcat(ds->tempname, "XXXXXX"); fd = mkstemp(ds->tempname); if (fd == -1) { free(ds); return NULL; } ds->file = fdopen(fd, "wb"); if (ds->file == NULL) { err = errno; close(fd); unlink(ds->tempname); free(ds); errno = err; return NULL; } setvbuf(ds->file, NULL, _IOFBF, DW_BUFFER_SIZE); ds->_write = _dw_stdio_write; ds->_seek = _dw_stdio_seek; ds->_tell = _dw_stdio_tell; ds->_putc = _dw_stdio_putc; return ds; } int disko_close(disko_t *ds, int backup) { int err = ds->error; // try to preserve the *first* error set, because it's most likely to be interesting if (fclose(ds->file) == EOF && !err) { err = errno; } else if (!err) { // preserve file mode, or set it sanely -- mkstemp() sets file mode to 0600 #ifndef GEKKO /* FIXME - autoconf check for this instead */ struct stat st; if (stat(ds->filename, &st) < 0) { /* Probably didn't exist already, let's make something up. 0777 is "safer" than 0, so we don't end up throwing around world-writable files in case something weird happens. See also: man 3 getumask */ mode_t m = umask(0777); umask(m); st.st_mode = 0666 & ~m; } #endif if (backup) { // back up the old file make_backup_file(ds->filename, (backup != 1)); } if (rename_file(ds->tempname, ds->filename, 1) != 0) { err = errno; } else { #ifndef GEKKO // Fix the permissions on the file chmod(ds->filename, st.st_mode); #endif } } // If anything failed so far, kill off the temp file if (err) { unlink(ds->tempname); } free(ds); if (err) { errno = err; return DW_ERROR; } else { return DW_OK; } } disko_t *disko_memopen(void) { disko_t *ds = calloc(1, sizeof(disko_t)); if (!ds) return NULL; ds->data = calloc(DW_BUFFER_SIZE, sizeof(uint8_t)); if (!ds->data) { free(ds); return NULL; } ds->allocated = DW_BUFFER_SIZE; ds->_write = _dw_mem_write; ds->_seek = _dw_mem_seek; ds->_tell = _dw_mem_tell; ds->_putc = _dw_mem_putc; return ds; } int disko_memclose(disko_t *ds, int keep_buffer) { int err = ds->error; if (!keep_buffer || err) free(ds->data); free(ds); if (err) { errno = err; return DW_ERROR; } else { return DW_OK; } } // --------------------------------------------------------------------------- static void _export_setup(song_t *dwsong, int *bps) { song_lock_audio(); /* install our own */ memcpy(dwsong, current_song, sizeof(song_t)); /* shadow it */ dwsong->multi_write = NULL; /* should be null already, but to be sure... */ csf_set_current_order(dwsong, 0); /* rather indirect way of resetting playback variables */ csf_set_wave_config(dwsong, disko_output_rate, disko_output_bits, (dwsong->flags & SONG_NOSTEREO) ? 1 : disko_output_channels); dwsong->mix_flags |= SNDMIX_DIRECTTODISK | SNDMIX_NOBACKWARDJUMPS; dwsong->repeat_count = -1; // FIXME do this right dwsong->buffer_count = 0; dwsong->flags &= ~(SONG_PAUSED | SONG_PATTERNLOOP | SONG_ENDREACHED); dwsong->stop_at_order = -1; dwsong->stop_at_row = -1; *bps = dwsong->mix_channels * ((dwsong->mix_bits_per_sample + 7) / 8); song_unlock_audio(); } static void _export_teardown(void) { global_vu_left = global_vu_right = 0; } // --------------------------------------------------------------------------- static int close_and_bind(song_t *dwsong, disko_t *ds, song_sample_t *sample, int bps) { disko_t dsshadow = *ds; int8_t *newdata; if (disko_memclose(ds, 1) == DW_ERROR) { return DW_ERROR; } newdata = csf_allocate_sample(dsshadow.length); if (!newdata) return DW_ERROR; csf_stop_sample(current_song, sample); if (sample->data) csf_free_sample(sample->data); sample->data = newdata; memcpy(newdata, dsshadow.data, dsshadow.length); sample->length = dsshadow.length / bps; sample->flags &= ~(CHN_16BIT | CHN_STEREO | CHN_ADLIB); if (dwsong->mix_channels > 1) sample->flags |= CHN_STEREO; if (dwsong->mix_bits_per_sample > 8) sample->flags |= CHN_16BIT; sample->c5speed = dwsong->mix_frequency; sample->name[0] = '\0'; return DW_OK; } int disko_writeout_sample(int smpnum, int pattern, int dobind) { int ret = DW_OK; song_t dwsong; song_sample_t *sample; uint8_t buf[DW_BUFFER_SIZE]; disko_t *ds; int bps; if (smpnum < 1 || smpnum >= MAX_SAMPLES) return DW_ERROR; ds = disko_memopen(); if (!ds) return DW_ERROR; _export_setup(&dwsong, &bps); dwsong.repeat_count = -1; // FIXME do this right csf_loop_pattern(&dwsong, pattern, 0); do { disko_write(ds, buf, csf_read(&dwsong, buf, sizeof(buf)) * bps); if (ds->length >= (size_t) (MAX_SAMPLE_LENGTH * bps)) { /* roughly 3 minutes at 44khz -- surely big enough (?) */ ds->length = MAX_SAMPLE_LENGTH * bps; dwsong.flags |= SONG_ENDREACHED; } } while (!(dwsong.flags & SONG_ENDREACHED)); sample = current_song->samples + smpnum; if (close_and_bind(&dwsong, ds, sample, bps) == DW_OK) { sprintf(sample->name, "Pattern %03d", pattern); if (dobind) { /* This is hideous */ sample->name[23] = 0xff; sample->name[24] = pattern; } } else { /* Balls. Something died. */ ret = DW_ERROR; } _export_teardown(); return ret; } int disko_multiwrite_samples(int firstsmp, int pattern) { int err = 0; song_t dwsong; song_sample_t *sample; uint8_t buf[DW_BUFFER_SIZE]; disko_t *ds[MAX_CHANNELS] = {NULL}; int bps; size_t smpsize = 0; int smpnum = CLAMP(firstsmp, 1, MAX_SAMPLES); int n; _export_setup(&dwsong, &bps); dwsong.repeat_count = -1; // FIXME do this right csf_loop_pattern(&dwsong, pattern, 0); dwsong.multi_write = calloc(MAX_CHANNELS, sizeof(struct multi_write)); if (!dwsong.multi_write) err = errno ?: ENOMEM; if (!err) { for (n = 0; n < MAX_CHANNELS; n++) { ds[n] = disko_memopen(); if (!ds[n]) { err = errno ?: EINVAL; break; } } } if (err) { /* you might think this code is insane, and you might be correct ;) but it's structured like this to keep all the early-termination handling HERE. */ _export_teardown(); err = err ?: errno; free(dwsong.multi_write); for (n = 0; n < MAX_CHANNELS; n++) disko_memclose(ds[n], 0); errno = err; return DW_ERROR; } for (n = 0; n < MAX_CHANNELS; n++) { dwsong.multi_write[n].data = ds[n]; /* Dumb casts. (written this way to make the definition of song_t independent of disko) */ dwsong.multi_write[n].write = (void *) disko_write; dwsong.multi_write[n].silence = (void *) disko_seekcur; } do { /* buf is used as temp space for converting the individual channel buffers from 32-bit. the output is being handled well inside the mixer, so we don't have to do any actual writing here, but we DO need to make sure nothing died... */ smpsize += csf_read(&dwsong, buf, sizeof(buf)); if (smpsize >= MAX_SAMPLE_LENGTH) { /* roughly 3 minutes at 44khz -- surely big enough (?) */ smpsize = MAX_SAMPLE_LENGTH; dwsong.flags |= SONG_ENDREACHED; break; } for (n = 0; n < MAX_CHANNELS; n++) { if (ds[n]->error) { // Kill the write, but leave the other files alone dwsong.flags |= SONG_ENDREACHED; break; } } } while (!(dwsong.flags & SONG_ENDREACHED)); for (n = 0; n < MAX_CHANNELS; n++) { if (!dwsong.multi_write[n].used) { /* this channel was completely empty - don't bother with it */ disko_memclose(ds[n], 0); continue; } ds[n]->length = MIN(ds[n]->length, smpsize * bps); smpnum = csf_first_blank_sample(current_song, smpnum); if (smpnum < 0) break; sample = current_song->samples + smpnum; if (close_and_bind(&dwsong, ds[n], sample, bps) == DW_OK) { sprintf(sample->name, "Pattern %03d, channel %02d", pattern, n + 1); } else { /* Balls. Something died. */ err = errno; } } for (; n < MAX_CHANNELS; n++) { if (disko_memclose(ds[n], 0) != DW_OK) { err = errno; } } _export_teardown(); free(dwsong.multi_write); if (err) { errno = err; return DW_ERROR; } else { return DW_OK; } } // --------------------------------------------------------------------------- static song_t export_dwsong; static int export_bps; static disko_t *export_ds[MAX_CHANNELS + 1]; /* only [0] is used unless multichannel */ static const struct save_format *export_format = NULL; /* NULL == not running */ static struct widget diskodlg_widgets[1]; static size_t est_len; static int prgh; static struct timeval export_start_time; static int canceled = 0; /* this sucks, but so do I */ static int disko_finish(void); static void diskodlg_draw(void) { int sec, pos; char buf[32]; if (!export_ds[0]) { /* what are we doing here?! */ dialog_destroy_all(); log_appendf(4, "disk export dialog was eaten by a grue!"); return; } sec = export_ds[0]->length / export_dwsong.mix_frequency; pos = export_ds[0]->length * 64 / est_len; snprintf(buf, 32, "Exporting song...%6d:%02d", sec / 60, sec % 60); buf[31] = '\0'; draw_text(buf, 27, 27, 0, 2); draw_fill_chars(24, 30, 55, 30, 0); draw_vu_meter(24, 30, 32, pos, prgh, prgh); draw_box(23, 29, 56, 31, BOX_THIN | BOX_INNER | BOX_INSET); } static void diskodlg_cancel(UNUSED void *ignored) { canceled = 1; export_dwsong.flags |= SONG_ENDREACHED; if (!export_ds[0]) { log_appendf(4, "export was already dead on the inside"); return; } for (int n = 0; export_ds[n]; n++) disko_seterror(export_ds[n], EINTR); /* The next disko_sync will notice the (artifical) error status and call disko_finish, which will clean up all the files. 'canceled' prevents disko_finish from making a second call to dialog_destroy (since this function is already being called in response to the dialog being canceled) and also affects the message it prints at the end. */ } static void disko_dialog_setup(size_t len); // this needs to be done to work around stupid inconsistent key-up code static void diskodlg_reset(UNUSED void *ignored) { disko_dialog_setup(est_len); } static void disko_dialog_setup(size_t len) { struct dialog *d = dialog_create_custom(22, 25, 36, 8, diskodlg_widgets, 0, 0, diskodlg_draw, NULL); d->action_yes = diskodlg_reset; d->action_no = diskodlg_reset; d->action_cancel = diskodlg_cancel; canceled = 0; /* stupid */ est_len = len; switch ((rand() >> 8) & 63) { /* :) */ case 0 ... 7: prgh = 6; break; case 8 ... 18: prgh = 3; break; case 19 ... 31: prgh = 5; break; default: prgh = 4; break; } } // --------------------------------------------------------------------------- static char *get_filename(const char *template, int n) { char *s, *sub, buf[4]; s = strdup(template); if (!s) return NULL; sub = strcasestr(s, "%c"); if (!sub) { errno = EINVAL; free(s); return NULL; } num99tostr(n, buf); sub[0] = buf[0]; sub[1] = buf[1]; return s; } int disko_export_song(const char *filename, const struct save_format *format) { int err = 0; int numfiles, n; if (export_format) { log_appendf(4, "Another export is already active"); errno = EAGAIN; return DW_ERROR; } gettimeofday(&export_start_time, NULL); numfiles = format->f.export.multi ? MAX_CHANNELS : 1; _export_setup(&export_dwsong, &export_bps); if (numfiles > 1) { export_dwsong.multi_write = calloc(numfiles, sizeof(struct multi_write)); if (!export_dwsong.multi_write) err = errno ?: ENOMEM; } memset(export_ds, 0, sizeof(export_ds)); for (n = 0; n < numfiles; n++) { if (numfiles > 1) { char *tmp = get_filename(filename, n + 1); if (tmp) { export_ds[n] = disko_open(tmp); free(tmp); } } else { export_ds[n] = disko_open(filename); } if (!(export_ds[n] && format->f.export.head(export_ds[n], export_dwsong.mix_bits_per_sample, export_dwsong.mix_channels, export_dwsong.mix_frequency) == DW_OK)) { err = errno ?: EINVAL; break; } } if (err) { _export_teardown(); free(export_dwsong.multi_write); for (n = 0; export_ds[n]; n++) { disko_seterror(export_ds[n], err); /* keep from writing a bunch of useless files */ disko_close(export_ds[n], 0); } errno = err ?: EINVAL; log_perror(filename); return DW_ERROR; } if (numfiles > 1) { for (n = 0; n < numfiles; n++) { export_dwsong.multi_write[n].data = export_ds[n]; /* Dumb casts, again */ export_dwsong.multi_write[n].write = (void *) format->f.export.body; export_dwsong.multi_write[n].silence = (void *) format->f.export.silence; } } log_appendf(5, " %d Hz, %d bit, %s", export_dwsong.mix_frequency, export_dwsong.mix_bits_per_sample, export_dwsong.mix_channels == 1 ? "mono" : "stereo"); export_format = format; status.flags |= DISKWRITER_ACTIVE; /* tell main to care about us */ disko_dialog_setup((csf_get_length(&export_dwsong) * export_dwsong.mix_frequency) ?: 1); return DW_OK; } /* main calls this periodically when the .wav exporter is busy */ int disko_sync(void) { uint8_t buf[DW_BUFFER_SIZE]; size_t frames; int n; if (!export_format) { log_appendf(4, "disko_sync: unexplained bacon"); return DW_SYNC_ERROR; /* no writer running (why are we here?) */ } frames = csf_read(&export_dwsong, buf, sizeof(buf)); if (!export_dwsong.multi_write) export_format->f.export.body(export_ds[0], buf, frames * export_bps); /* always check if something died, multi-write or not */ for (n = 0; export_ds[n]; n++) { if (export_ds[n]->error) { disko_finish(); return DW_SYNC_ERROR; } } /* update the progress bar (kind of messy, yes...) */ export_ds[0]->length += frames; status.flags |= NEED_UPDATE; if (export_dwsong.flags & SONG_ENDREACHED) { disko_finish(); return DW_SYNC_DONE; } else { return DW_SYNC_MORE; } } static int disko_finish(void) { int ret = DW_OK, n, tmp; struct timeval export_end_time; double elapsed; int num_files = 0; size_t samples_0 = 0; if (!export_format) { log_appendf(4, "disko_finish: unexplained eggs"); return DW_ERROR; /* no writer running (why are we here?) */ } if (!canceled) dialog_destroy(); samples_0 = export_ds[0]->length; for (n = 0; export_ds[n]; n++) { if (export_dwsong.multi_write && !export_dwsong.multi_write[n].used) { /* this channel was completely empty - don't bother with it */ disko_seterror(export_ds[n], EINVAL); /* kludge */ disko_close(export_ds[n], 0); } else { /* there was noise on this channel */ num_files++; if (export_format->f.export.tail(export_ds[n]) != DW_OK) disko_seterror(export_ds[n], errno); tmp = disko_close(export_ds[n], 0); if (ret == DW_OK) ret = tmp; } } memset(export_ds, 0, sizeof(export_ds)); _export_teardown(); free(export_dwsong.multi_write); export_format = NULL; status.flags &= ~DISKWRITER_ACTIVE; /* please unsubscribe me from your mailing list */ switch (ret) { case DW_OK: gettimeofday(&export_end_time, NULL); elapsed = (export_end_time.tv_sec - export_start_time.tv_sec) + ((export_end_time.tv_usec - export_start_time.tv_usec) / 1000000.0); char fmt[80] = " %.2f mb (%d:%02d) written in %.2lf sec"; if (elapsed >= 9.5 && elapsed < 10.5) { strcpy(strrchr(fmt, '%'), "ten seconds flat"); } log_appendf(5, fmt, ((double) samples_0 * (disko_output_bits / 8) * disko_output_channels * num_files) / 1048576.0, samples_0 / disko_output_rate / 60, (samples_0 / disko_output_rate) % 60, elapsed); break; case DW_ERROR: /* hey, what was the filename? oops */ if (canceled) log_appendf(5, " Canceled"); else log_perror(" Write error"); break; default: log_appendf(5, " Internal error exporting song"); break; } return ret; } // --------------------------------------------------------------------------- struct pat2smp { int pattern, sample, bind; }; static void pat2smp_single(void *data) { struct pat2smp *ps = data; if (disko_writeout_sample(ps->sample, ps->pattern, ps->bind) == DW_OK) { set_page(PAGE_SAMPLE_LIST); } else { log_perror("Sample write"); status_text_flash("Error writing to sample"); } free(ps); } static void pat2smp_multi(void *data) { struct pat2smp *ps = data; if (disko_multiwrite_samples(ps->sample, ps->pattern) == DW_OK) { set_page(PAGE_SAMPLE_LIST); } else { log_perror("Sample multi-write"); status_text_flash("Error writing to samples"); } free(ps); } void song_pattern_to_sample(int pattern, int split, int bind) { struct pat2smp *ps; int n; if (split && bind) { log_appendf(4, "song_pattern_to_sample: internal error!"); return; } if (pattern < 0 || pattern >= MAX_PATTERNS) { return; } /* this is horrid */ for (n = 1; n < MAX_SAMPLES; n++) { song_sample_t *samp = song_get_sample(n); if (!samp) continue; if (((unsigned char) samp->name[23]) != 0xFF) continue; if (((unsigned char) samp->name[24]) != pattern) continue; status_text_flash("Pattern %d already linked to sample %d", pattern, n); return; } ps = mem_alloc(sizeof(struct pat2smp)); ps->pattern = pattern; ps->sample = sample_get_current() ?: 1; ps->bind = bind; if (split) { /* Nothing to confirm, as this never overwrites samples */ pat2smp_multi(ps); } else { if (current_song->samples[ps->sample].data == NULL) { pat2smp_single(ps); } else { dialog_create(DIALOG_OK_CANCEL, "This will replace the current sample.", pat2smp_single, free, 1, ps); } } } // --------------------------------------------------------------------------- /* called from audio_playback.c _schism_midi_out_raw() */ int _disko_writemidi(UNUSED const void *data, UNUSED unsigned int len, UNUSED unsigned int delay) { return DW_ERROR; } schismtracker-20180209/schism/dmoz.c000066400000000000000000000632341323741476300172500ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #ifdef GEKKO #include // isfs is pretty much useless, but it might be interesting to browse it I guess static const char *devices[] = { "sd:/", "isfs:/", NULL }; #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 */ #define READ_INFO(t) fmt_##t##_read_info, static const fmt_read_info_func read_info_funcs[] = { #include "fmt-types.h" NULL /* This needs to be at the bottom of the list! */ }; /* --------------------------------------------------------------------------------------------------------- */ /* sorting stuff */ typedef int (*dmoz_fcmp_t) (const dmoz_file_t *a, const dmoz_file_t *b); typedef int (*dmoz_dcmp_t) (const dmoz_dir_t *a, const dmoz_dir_t *b); #define _DECL_CMP(name) \ static int dmoz_fcmp_##name(const dmoz_file_t *a, const dmoz_file_t *b); \ static int dmoz_dcmp_##name(const dmoz_dir_t *a, const dmoz_dir_t *b); _DECL_CMP(strcmp) _DECL_CMP(strcasecmp) #if HAVE_STRVERSCMP _DECL_CMP(strverscmp) #endif static int dmoz_fcmp_timestamp(const dmoz_file_t *a, const dmoz_file_t *b); #if HAVE_STRVERSCMP static dmoz_fcmp_t dmoz_file_cmp = dmoz_fcmp_strverscmp; static dmoz_dcmp_t dmoz_dir_cmp = dmoz_dcmp_strverscmp; #else static dmoz_fcmp_t dmoz_file_cmp = dmoz_fcmp_strcasecmp; static dmoz_dcmp_t dmoz_dir_cmp = dmoz_dcmp_strcasecmp; #endif static struct { const char *name; dmoz_fcmp_t fcmp; dmoz_dcmp_t dcmp; } compare_funcs[] = { {"strcmp", dmoz_fcmp_strcmp, dmoz_dcmp_strcmp}, {"strcasecmp", dmoz_fcmp_strcasecmp, dmoz_dcmp_strcasecmp}, #if HAVE_STRVERSCMP {"strverscmp", dmoz_fcmp_strverscmp, dmoz_dcmp_strverscmp}, {"timestamp", dmoz_fcmp_timestamp, dmoz_dcmp_strverscmp}, #else {"timestamp", dmoz_fcmp_timestamp, dmoz_dcmp_strcasecmp}, #endif {NULL, NULL, NULL} }; /* --------------------------------------------------------------------------------------------------------- */ /* "selected" and cache */ struct dmoz_cache { struct dmoz_cache *next; char *path; char *cache_filen; char *cache_dirn; }; static struct dmoz_cache *cache_top = NULL; void dmoz_cache_update(const char *path, dmoz_filelist_t *fl, dmoz_dirlist_t *dl) { char *fn, *dn; if (fl && fl->selected > -1 && fl->selected < fl->num_files && fl->files[fl->selected]) fn = fl->files[fl->selected]->base; else fn = NULL; if (dl && dl->selected > -1 && dl->selected < dl->num_dirs && dl->dirs[dl->selected]) dn = dl->dirs[dl->selected]->base; else dn = NULL; dmoz_cache_update_names(path,fn,dn); } void dmoz_cache_update_names(const char *path, const char *filen, const char *dirn) { struct dmoz_cache *p, *lp; char *q; q = str_dup(path); lp = NULL; filen = filen ? get_basename(filen) : NULL; dirn = dirn ? get_basename(dirn) : NULL; if (filen && strcmp(filen, "..") == 0) filen = NULL; if (dirn && strcmp(dirn, "..") == 0) dirn = NULL; for (p = cache_top; p; p = p->next) { if (strcmp(p->path,q)==0) { free(q); if (filen) { free(p->cache_filen); p->cache_filen = str_dup(filen); } if (dirn) { free(p->cache_dirn); p->cache_dirn = str_dup(dirn); } if (lp) { lp->next = p->next; /* !lp means we're already cache_top */ p->next = cache_top; cache_top = p; } return; } lp = p; } p = mem_alloc(sizeof(struct dmoz_cache)); p->path = q; p->cache_filen = filen ? str_dup(filen) : NULL; p->cache_dirn = dirn ? str_dup(dirn) : NULL; p->next = cache_top; cache_top = p; } void dmoz_cache_lookup(const char *path, dmoz_filelist_t *fl, dmoz_dirlist_t *dl) { struct dmoz_cache *p; int i; if (fl) fl->selected = 0; if (dl) dl->selected = 0; for (p = cache_top; p; p = p->next) { if (strcmp(p->path,path) == 0) { if (fl && p->cache_filen) { for (i = 0; i < fl->num_files; i++) { if (!fl->files[i]) continue; if (strcmp(fl->files[i]->base, p->cache_filen) == 0) { fl->selected = i; break; } } } if (dl && p->cache_dirn) { for (i = 0; i < dl->num_dirs; i++) { if (!dl->dirs[i]) continue; if (strcmp(dl->dirs[i]->base, p->cache_dirn) == 0) { dl->selected = i; break; } } } break; } } } /* --------------------------------------------------------------------------------------------------------- */ /* 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 [Amiga note: is foo:/bar the same as foo:bar, is it like /bar, or something else?] */ char *dmoz_path_normal(const char *path) { char stub_char; char *result, *p, *q, *base, *dotdot; int rooted; /* The result cannot be larger than the input PATH. */ result = strdup(path); rooted = dmoz_path_is_absolute(path); base = result + rooted; stub_char = rooted ? DIR_SEPARATOR : '.'; #ifdef WIN32 /* Stupid hack -- fix up any initial slashes in the absolute part of the path. (The rest of them will be handled as the path components are processed.) */ for (q = result; q < base; q++) if (*q == '/') *q = '\\'; #endif /* invariants: base points to the portion of the path we want to modify p points at beginning of path element we're considering. q points just past the last path element we wrote (no slash). dotdot points just past the point where .. cannot backtrack any further (no slash). */ p = q = dotdot = base; while (*p) { if (IS_DIR_SEPARATOR(p[0])) { /* null element */ p++; } else if (p[0] == '.' && (!p[1] || IS_DIR_SEPARATOR(p[1]))) { /* . and ./ */ p += 1; /* don't count the separator in case it is nul */ } else if (p[0] == '.' && p[1] == '.' && (!p[2] || IS_DIR_SEPARATOR(p[2]))) { /* .. and ../ */ p += 2; /* skip `..' */ if (q > dotdot) { /* can backtrack */ while (--q > dotdot && !IS_DIR_SEPARATOR(*q)) { /* nothing */ } } else if (!rooted) { /* /.. is / but ./../ is .. */ if (q != base) *q++ = DIR_SEPARATOR; *q++ = '.'; *q++ = '.'; dotdot = q; } } else { /* real path element */ /* add separator if not at start of work portion of result */ if (q != base) *q++ = DIR_SEPARATOR; while (*p && !IS_DIR_SEPARATOR(*p)) *q++ = *p++; } } /* Empty string is really ``.'' or `/', depending on what we started with. */ if (q == result) *q++ = stub_char; *q = '\0'; return result; } int dmoz_path_is_absolute(const char *path) { if (!path || !*path) return 0; #if defined(WIN32) if (isalpha(path[0]) && path[1] == ':') return IS_DIR_SEPARATOR(path[2]) ? 3 : 2; #elif defined(__amigaos4__) /* Entirely a guess -- could some fine Amiga user please tell me if this is right or not? */ char *colon = strchr(path, ':'), *slash = strchr(path, '/'); if (colon && (colon < slash || (colon && !slash && colon[1] == '\0'))) return colon - path + 1; #elif defined(GEKKO) char *colon = strchr(path, ':'), *slash = strchr(path, '/'); if (colon + 1 == slash) return slash - path + 1; #endif /* presumably, /foo (or \foo) is an absolute path on all platforms */ if (!IS_DIR_SEPARATOR(path[0])) return 0; /* POSIX says to allow two leading slashes, but not more. (This also catches win32 \\share\blah\blah semantics) */ return (IS_DIR_SEPARATOR(path[1]) && !IS_DIR_SEPARATOR(path[2])) ? 2 : 1; } /* See dmoz_path_concat_len. This function is a convenience for when the lengths aren't already known. */ char *dmoz_path_concat(const char *a, const char *b) { return dmoz_path_concat_len(a, b, strlen(a), strlen(b)); } /* Concatenate two paths. Additionally, if 'b' is an absolute path, ignore 'a' and return a copy of 'b'. */ char *dmoz_path_concat_len(const char *a, const char *b, int alen, int blen) { char *ret; if (dmoz_path_is_absolute(b)) return strdup(b); 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) csf_free_sample(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 = NULL; static int (*current_dmoz_filter)(dmoz_file_t *) = NULL; static int *current_dmoz_file_pointer = NULL; static void (*dmoz_worker_onmove)(void) = NULL; 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 = NULL; current_dmoz_filter = NULL; if (dmoz_worker_onmove) dmoz_worker_onmove(); 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 = NULL; current_dmoz_filter = NULL; if (dmoz_worker_onmove) dmoz_worker_onmove(); 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; } /* TODO: - create a one-shot filter that runs all its files at once - make these filters not actually drop the files from the list, but instead set the hidden flag - add a 'num_unfiltered' variable to the struct that indicates the total number */ /* --------------------------------------------------------------------------------------------------------- */ /* 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 = mem_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 = str_dup(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 = mem_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 */ #define _DEF_CMP(name) \ static int dmoz_fcmp_##name(const dmoz_file_t *a, const dmoz_file_t *b) \ { \ return name(a->base, b->base); \ } \ static int dmoz_dcmp_##name(const dmoz_dir_t *a, const dmoz_dir_t *b) \ { \ return name(a->base, b->base); \ } _DEF_CMP(strcmp) _DEF_CMP(strcasecmp) #if HAVE_STRVERSCMP _DEF_CMP(strverscmp) #endif /* timestamp only works for files, so can't macro-def it */ static int dmoz_fcmp_timestamp(const dmoz_file_t *a, const dmoz_file_t *b) { return b->timestamp - a->timestamp; } 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 ((b->type & TYPE_HIDDEN) && !(a->type & TYPE_HIDDEN)) return -1; /* b goes first */ if ((a->type & TYPE_HIDDEN) && !(b->type & TYPE_HIDDEN)) return 1; /* b goes first */ 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 (*dmoz_file_cmp)(a, b); } 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; 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 (*dmoz_dir_cmp)(a, b); } void dmoz_sort(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) { 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); } void cfg_load_dmoz(cfg_file_t *cfg) { const char *ptr; int i; ptr = cfg_get_string(cfg, "Directories", "sort_with", NULL, 0, NULL); if (ptr) { for (i = 0; compare_funcs[i].name; i++) { if (strcasecmp(compare_funcs[i].name, ptr) == 0) { dmoz_file_cmp = compare_funcs[i].fcmp; dmoz_dir_cmp = compare_funcs[i].dcmp; break; } } } } void cfg_save_dmoz(cfg_file_t *cfg) { int i; for (i = 0; compare_funcs[i].name; i++) { if (dmoz_file_cmp == compare_funcs[i].fcmp) { cfg_set_string(cfg, "Directories", "sort_with", compare_funcs[i].name); break; } } } /* --------------------------------------------------------------------------------------------------------- */ /* 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, str_dup(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, str_dup(sbuf), str_dup(sbuf), NULL, -(1024-i)); } x >>= 1; i++; } em = SetErrorMode(em); #elif defined(GEKKO) int i; for (i = 0; devices[i]; i++) { DIR_ITER *dir = diropen(devices[i]); if (!dir) continue; dirclose(dir); dmoz_add_file_or_dir(flist, dlist, str_dup(devices[i]), str_dup(devices[i]), NULL, -(1024 - i)); } #else /* assume POSIX */ /* char *home; home = get_home_directory();*/ dmoz_add_file_or_dir(flist, dlist, str_dup("/"), str_dup("/"), NULL, -1024); /* dmoz_add_file_or_dir(flist, dlist, home, str_dup("~"), NULL, -5); */ #endif /* platform */ ptr = get_parent_directory(path); if (ptr) dmoz_add_file_or_dir(flist, dlist, ptr, str_dup(".."), NULL, -10); } /* --------------------------------------------------------------------------------------------------------- */ #if defined(WIN32) # define FAILSAFE_PATH "C:\\" /* hopefully! */ #else # define FAILSAFE_PATH "/" #endif /* 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(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, int (*load_library)(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist)) { DIR *dir; struct dirent *ent; char *ptr; struct stat st; int pathlen, namlen, lib = 0, err = 0; if (!path || !*path) path = FAILSAFE_PATH; pathlen = strlen(path); #ifdef GEKKO /* awful hack: libfat's file reads bail if a device is given without a slash. */ if (strchr(path, ':') != NULL && strchr(path, '/') == NULL) { int i; for (i = 0; devices[i]; i++) { if (strncmp(path, devices[i], pathlen) == 0) { path = devices[i]; break; } } } #endif dir = opendir(path); if (dir) { 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] == '.') continue; #endif if (ent->d_name[namlen - 1] == '~') continue; 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? */ log_perror(ptr); free(ptr); continue; /* better luck next time */ } if (st.st_mtime < 0) st.st_mtime = 0; if (S_ISDIR(st.st_mode)) dmoz_add_file_or_dir(flist, dlist, ptr, str_dup(ent->d_name), &st, 0); else if (S_ISREG(st.st_mode)) dmoz_add_file(flist, ptr, str_dup(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 (load_library && load_library(path, flist, dlist) != 0) err = errno; else lib = 1; } else { /* opendir failed? that's unpossible! */ err = errno; } /* more directories! * If this is actually a file, make a fake "." that actually points to the directory. * If something weird happens when trying to get the directory name, this falls back * to add_platform_dirs to keep from getting "stuck". */ if (lib && (ptr = get_parent_directory(path)) != NULL) dmoz_add_file_or_dir(flist, dlist, ptr, str_dup("."), NULL, -10); else add_platform_dirs(path, flist, dlist); /* finally... sort it */ dmoz_sort(flist, dlist); 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; file->smp_defvol = 64; file->smp_gblvol = 64; 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 = str_dup(""); /* 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_filter_ext_data(dmoz_file_t *file) { int ret; if ((file->type & TYPE_EXT_DATA_MASK) || (file->type == TYPE_DIRECTORY)) { /* 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 str_dup'ing EVERY description is kind of a waste of memory. */ log_perror(file->base); file->description = "File error"; break; default: /* shouldn't ever happen */ file->description = "Internal error"; break; } file->type = TYPE_UNKNOWN; file->title = str_dup(""); return 0; } /* same as dmoz_filter_ext_data, except without the filtering effect when used with dmoz_filter_filelist */ int dmoz_fill_ext_data(dmoz_file_t *file) { dmoz_filter_ext_data(file); return 1; } schismtracker-20180209/schism/draw-char.c000066400000000000000000000443131323741476300201440ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 new overlay the layout is relative to the scanline position: it gets pixel values from "ovl" which is [640*400] 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 7 bits; 7-13 ch2 is 7 bits: 0-6 lower bits are unused 0x10000080 bios font this layout looks surprisingly like a real vga card (mostly because it was ripped from one ;) fg is nybble in bits 8-11 bg is nybble in bits 12-15 ch is lower byte 0x00000000 regular this layout uses the itf font 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 "sdlmain.h" #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) /* This isn't defined in an .h file since it's only used here. (maybe I should make a header for declarations of all this auto-built stuff) */ extern const unsigned char font_default_lower[]; extern const unsigned char font_default_upper_alt[]; extern const unsigned char font_default_upper_itf[]; extern const unsigned char font_half_width[]; /* --------------------------------------------------------------------- */ /* statics */ static uint8_t font_normal[2048]; /* There's no way to change the other fontsets at the moment. * (other than recompiling, of course) */ static uint8_t font_alt[2048]; static uint8_t font_half_data[1024]; /* --------------------------------------------------------------------- */ /* globals */ uint8_t *font_data = font_normal; /* this only needs to be global for itf */ /* int font_width = 8, font_height = 8; */ /* --------------------------------------------------------------------- */ /* half-width characters */ /* ok i think i get this now, after inspecting it further. good thing no one bothered putting any comments in the code. the fake vga buffer is pigeonholing the half-width characters into 14 bits. why 14, i don't know, but that means 7 bits per character, and these functions handle shifting stuff around to get them into that space. realistically, we only need to bother with chars 32 through 127, as well as 173 (middot) and 205 (the double-line used for noteoff). since 32->127 is 96 characters, there's plenty of room for the printable stuff... and guess what, 173->205 is another 32, which fits nice and clean into 7 bits! so if the character is within that range, we're fine. otherwise it'll just result in a broken glyph. (but it probably wasn't drawn in the font anyway) */ static inline int _pack_halfw(int c) { switch (c) { case 32 ... 127: return c - 32; /* 0 ... 95 */ case 173 ... 205: return 96 + c - 173; /* 96 ... 127 */ default: abort(); return '?'; } } static inline int _unpack_halfw(int c) { switch (c) { case 0 ... 95: return c + 32; case 96 ... 127: return 96 - c + 173; default: return '?'; /* should never happen */ } } /* --------------------------------------------------------------------- */ /* 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) { uint8_t *base; int cx; ch <<= 3; cx = ch; if (ch >= 1024) { base = (uint8_t *) font_default_upper_itf; cx -= 1024; } else { base = (uint8_t *) font_default_lower; } /* update them both... */ memcpy(font_normal + ch, base + cx, 8); /* update */ make_half_width_middot(); } /* --------------------------------------------------------------------- */ static int squeeze_8x16_font(FILE * fp) { uint8_t 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; uint8_t 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 && data[0] != 9)) { SDL_SetError("%s: Unsupported ITF file version %02x.%20x", font_file, data[1], data[0]); 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; uint8_t 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_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]; static unsigned char ovl[640*400]; /* 256K */ 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_ovl_alloc(struct vgamem_overlay *n) { n->q = &ovl[ (n->x1*8) + (n->y1 * 5120) ]; n->width = 8 * ((n->x2 - n->x1) + 1); n->height = 8 * ((n->y2 - n->y1) + 1); n->skip = (640 - n->width); } void vgamem_ovl_apply(struct vgamem_overlay *n) { unsigned int x, y; for (y = n->y1; y <= n->y2; y++) { for (x = n->x1; x <= n->x2; x++) { vgamem[x + (y*80)] = 0x80000000; } } } void vgamem_ovl_clear(struct vgamem_overlay *n, int color) { int i, j; unsigned char *q = n->q; for (j = 0; j < n->height; j++) { for (i = 0; i < n->width; i++) { *q = color; q++; } q += n->skip; } } void vgamem_ovl_drawpixel(struct vgamem_overlay *n, int x, int y, int color) { n->q[ (640*y) + x ] = color; } static inline void _draw_line_v(struct vgamem_overlay *n, int x, int ys, int ye, int color) { unsigned char *q = n->q + x; int y; if (ys < ye) { q += (ys * 640); for (y = ys; y <= ye; y++) { *q = color; q += 640; } } else { q += (ye * 640); for (y = ye; y <= ys; y++) { *q = color; q += 640; } } } static inline void _draw_line_h(struct vgamem_overlay *n, int xs, int xe, int y, int color) { unsigned char *q = n->q + (y * 640); int x; if (xs < xe) { q += xs; for (x = xs; x <= xe; x++) { *q = color; q++; } } else { q += xe; for (x = xe; x <= xs; x++) { *q = color; q++; } } } #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_ovl_drawline(struct vgamem_overlay *n, int xs, int ys, int xe, int ye, int color) { int d, x, y, ax, ay, sx, sy, dx, dy; dx = xe - xs; if (dx == 0) { _draw_line_v(n, xs, ys, ye, color); return; } dy = ye - ys; if (dy == 0) { _draw_line_h(n, xs, xe, ys, color); 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_ovl_drawpixel(n, x, y, color); if (x == xe) break; if (d >= 0) { y += sy; d -= ax; } x += sx; d += ay; } } else { /* y dominant */ d = ax - (ay >> 1); for (;;) { vgamem_ovl_drawpixel(n, x, y, color); if (y == ye) break; 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_bios(unsigned char c, int x, int y, uint32_t fg, uint32_t bg) { assert(x >= 0 && y >= 0 && x < 80 && y < 50); vgamem[x + (y*80)] = c | (fg << 8) | (bg << 12) | 0x10000000; } void draw_char(unsigned char c, int x, int y, uint32_t fg, uint32_t bg) { assert(x >= 0 && y >= 0 && x < 80 && y < 50); vgamem[x + (y*80)] = c | (fg << 8) | (bg << 12); } int draw_text(const char * text, int x, int y, uint32_t fg, uint32_t bg) { int n = 0; while (*text) { draw_char(*text, x + n, y, fg, bg); n++; text++; } return n; } int draw_text_bios(const char * text, int x, int y, uint32_t fg, uint32_t bg) { int n = 0; while (*text) { draw_char_bios(*text, x + n, y, fg, bg); n++; text++; } return n; } void draw_fill_chars(int xs, int ys, int xe, int ye, uint32_t 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 char * text, int len, int x, int y, uint32_t fg, uint32_t 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; } int draw_text_bios_len(const char * text, int len, int x, int y, uint32_t fg, uint32_t bg) { int n = 0; while (*text && n < len) { draw_char_bios(*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(uint8_t c1, uint8_t c2, int x, int y, uint32_t fg1, uint32_t bg1, uint32_t fg2, uint32_t 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) << 7) | (_pack_halfw(c2)); } /* --------------------------------------------------------------------- */ /* boxes */ enum box_type { BOX_THIN_INNER = 0, BOX_THIN_OUTER, BOX_THICK_OUTER }; static const uint8_t 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_t tl, uint32_t br, const uint8_t 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_t tl, uint32_t 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_t tl, uint32_t 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_t 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_t c) { _draw_box_internal(xs, ys, xe, ye, c, c, boxes[BOX_THICK_OUTER]); } 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; } } schismtracker-20180209/schism/draw-misc.c000066400000000000000000000061501323741476300201570ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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_t fg) { const uint8_t 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 uint8_t 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; /* reduced from (val * maxval / 64) */ val = CLAMP((val*width/24), 0, (maxval-1)); if (!val) return; 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); } } schismtracker-20180209/schism/fakemem.c000066400000000000000000000064521323741476300177030ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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_t *ptr; if (_cache_ok & 1) return p_cached; _cache_ok |= 1; q = 0; nm = csf_get_num_patterns(current_song); for (i = 0; i < nm; i++) { if (csf_pattern_is_empty(current_song, i)) continue; rows = song_get_pattern(i, &ptr); q += (rows*256); } return p_cached = q; } 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, NULL); 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(NULL, &q); return h_cached = (q * 256); } unsigned int memused_samples(void) { song_sample_t *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); q += s->length; if (s->flags & CHN_STEREO) q += s->length; if (s->flags & CHN_16BIT) 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 (csf_instrument_is_empty(current_song->instruments[i])) continue; q += 512; } return i_cache = q; } unsigned int memused_songmessage(void) { static unsigned int m_cache; if (_cache_ok & 32) return m_cache; _cache_ok |= 32; return m_cache = strlen(current_song->message); } /* 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(); } schismtracker-20180209/schism/isysev.c000066400000000000000000001110771323741476300176200ustar00rootroot00000000000000#include #include #include #include "util.h" #include "tree.h" /* TODO: string descriptions for joystick and midi keyboard events joystick axes and hats should emit synthetic repeated 'press' events come up with an api for handling unicode data velocity for joystick and midi should really be keeping the trees balanced... meh less stupid data structure than a linked list for the keysym translator? put the keymaps themselves into a tree of some sort, indexed by name. this way, it's possible to bind a key to change the current keymap -- e.g. bind Ctrl-X to a "set keymap" function with data="Ctrl-X map" need to make a list of all the things that can have associated keymaps... All of the modifier keys MIGHT additionally be presentable as keys themselves. (e.g. for FT2 style playback control bindings) However, if the platform doesn't support this, too bad. For the keyboard, most of the time the 'keycode' is a plain SDL keysym value. However, if the keysym was zero, the scancode is used instead, with the high bit set. Most of the time SDL does provide a keysym, but for some weird keys it might not. Scancodes are inherently non-portable, so this is only to allow extra keys to be bound to something. If the entire keycode value is zero, then we have no idea what key was pressed. (This happens for input methods e.g. scim that only send a unicode character.) For instrument list keyjazz, keydown events should keep track of the channel that got assigned to each note, and keyup should look up that channel and send a keyoff. Keymap files are split into sections denoted by [brackets] Each section defines a specific keymap. Note that this file can use the same parser as the config file. Widgets and pages use keymaps with certain names by default, but you can define keymaps with pretty much any name you want. the event parser is fairly liberal and can understand many different representations of keys. some examples follow: ; the Global map is always active, and receives all events not handled by a prior keymap [Global] F5=song_start_set_infopage Ctrl+F5 = song_start F8=song_stop kp_multiply = set_octave +1 MIDI/1 #87 = song_loop_current_pattern ; following event is actually the fourth button on the second joystick, but in numeric format. doing things ; this way is supported but not recommended, and is really more for debugging/troubleshooting than anything. @2/2 #3 = song_loop_pattern keyboard/0 shift+f9 = page_switch message_editor f9=page_switch load_module f10=page_switch save_module ^S = song_save escape = main_menu_toggle [Pattern Editor] M-q = pat_transpose +1 M-a = pat_transpose -1 M-S-q = pat_transpose +12 M-S-a = pat_transpose -12 [Sample List] keyboard/0 meta+A = smp_sign_convert Keymap lookup order: (prefix) | widget -> page -> global The "prefix" map is user-defined by the kmap_set_prefix binding, and is normally empty. This map can be used to implement multi-key events. For example: [Global] Ctrl-X = kmap_set_prefix ctrlx_map [ctrlx_map] Ctrl-S = song_save Ctrl-C = quit This effectively binds the Ctrl-X keys like Emacs, and also allows for "regular" bindings of those keys in other maps (e.g. Ctrl-C can still be bound to centralise cursor, and so forth) If a prefix map is active, no other keymaps are searched; in addition, prefix maps only last for one key. -- if (prefix_map && ev.bits.release) clear_prefix_map(); Additionally, a keymap can "inherit" keys from another map as follows. In this example, events not handled by the "hamster" keymap are checked against the pattern editor map: [hamster] @inherit = Pattern Editor One could conceivably define a key in the Pattern Editor map to load the hamster map, and a reciprocating key in the monsquaz map that changes it back. (FT2's "record mode" -- as well as IT's own capslock+key behavior -- could be implemented this way.) * Need a function to do this: it should replace the keymap that owns the key that was pressed to trigger the function. That is, if the keymap replace was bound to a key in the Pattern Editor map, the new keymap loaded replaces the pointer that was previously pointing to the Pattern Editor map. This way each keymap "layer" is independent and none of them can interfere with each other. Somehow the keymap bindings need to know the context they're being called in. For example, it would be entirely inappropriate to bind the left arrow to a thumbbar value adjust function from within the pattern editor keymap. */ // ------------------------------------------------------------------------------------------------------------ // Superficially similar to SDL's event structure, but packed much tighter. #pragma pack(push, 1) typedef union { struct { unsigned int dev_type : 4; // SKDEV_TYPE_whatever unsigned int dev_id : 4; // which device? (1->n; 0 is a pseudo "all" device) // note: not all "press" events have a corresponding "release" unsigned int release : 1; // 1 for key-up // next three fields are only relevant for the pc keyboard unsigned int repeat : 1; // 1 for synthetic key-repeat unsigned int unicode : 1; // 1 if character maps to printable unicode unsigned int modifier : 5; // ctrl, alt, shift unsigned int keycode : 16; // keyboard keysym/scancode } bits; uint32_t ival; } isysev_t; #pragma pack(pop) // Device types (we can have 16 of these) enum { SKDEV_TYPE_PCKEYBOARD, SKDEV_TYPE_MIDI, SKDEV_TYPE_JOYSTICK, // other device IDs are reserved SKDEV_TYPE_SENTINEL, }; // Device IDs // #0 is reserved as a sort of catch-all, to allow for binding the same event on all connected // devices of a given type. enum { SKDEV_ID_ANY = 0, SKDEV_ID_MAX = 15, }; // Keyboard modifier bits enum { SKMODE_CTRL = 1 << 0, SKMODE_ALT = 1 << 1, SKMODE_SHIFT = 1 << 2, }; // Keycode flag bits (currently only used for PC keyboard) enum { SKCODE_PCK_SCANCODE = 0x8000, SKCODE_MAX = 0xffff, }; // Joystick limits (should be way more than enough) // The event loop maintains a table of SKDEV_ID_MAX * MAX_JS_AXES + SKDEV_ID_MAX * MAX_JS_HATS // in order to identify keyup and repeat events. #define MAX_JS_BUTTONS 256 #define MAX_JS_AXES 64 #define MAX_JS_HATS 64 #define MAX_JS_BALLS 64 // Threshold values from ZSNES #define JS_AXIS_THRESHOLD 16384 #define JS_BALL_THRESHOLD 100 enum { JS_AXIS_NEG, JS_AXIS_POS }; // for axes enum { JS_DIR_UP, JS_DIR_DOWN, JS_DIR_LEFT, JS_DIR_RIGHT }; // for hats/balls #define JS_BUTTON_TO_KEYCODE(n) (n) #define JS_AXIS_TO_KEYCODE(n, dir) (2 * (n) + (dir) + MAX_JS_BUTTONS) #define JS_HAT_TO_KEYCODE(n, dir) (4 * (n) + (dir) + MAX_JS_BUTTONS + 2 * MAX_JS_AXES) #define JS_BALL_TO_KEYCODE(n, dir) (4 * (n) + (dir) + MAX_JS_BUTTONS + 2 * MAX_JS_AXES + 4 * MAX_JS_HATS) #if (JS_BALL_TO_KEYCODE(MAX_JS_BALLS, 0) > 65535) # error Joystick limits are too large! #endif // 8 chars max static const char *skdev_names[] = { "keyboard", "midi", "joystick", }; // ------------------------------------------------------------------------------------------------------------ // this struct sucks typedef struct keytab { int code; const char *name; struct keytab *next; } keytab_t; static keytab_t *keytab = NULL; static void key_add(int code, const char *name) { keytab_t *k = mem_alloc(sizeof(keytab_t)); k->code = code; k->name = name; k->next = keytab; keytab = k; } static const char *keytab_code_to_name(int keycode) { keytab_t *k; if (!(keycode & SKCODE_PCK_SCANCODE)) for (k = keytab; k; k = k->next) if (k->code == keycode) return k->name; return NULL; } static int keytab_name_to_code(const char *keyname) { keytab_t *k; if (!keyname[0]) return 0; for (k = keytab; k; k = k->next) if (strcasecmp(k->name, keyname) == 0) return k->code; return 0; } static void keytab_free(void) { keytab_t *k, *prev = NULL; for (k = keytab; k; k = k->next) { if (prev) free(prev); prev = k; } if (prev) free(prev); } static void keytab_init(void) { int n; // these strings should be < 15 chars, and should not start with a hash mark ('#') static struct { int code; const char *name; } keys[] = { {SDLK_BACKSPACE, "Backspace"}, {SDLK_TAB, "Tab"}, {SDLK_CLEAR, "Clear"}, {SDLK_RETURN, "Return"}, {SDLK_PAUSE, "Pause"}, {SDLK_ESCAPE, "Escape"}, {SDLK_SPACE, "Space"}, {SDLK_EXCLAIM, "Exclaim"}, {SDLK_QUOTEDBL, "QuoteDbl"}, {SDLK_HASH, "Hash"}, {SDLK_DOLLAR, "Dollar"}, {SDLK_AMPERSAND, "Ampersand"}, {SDLK_QUOTE, "Quote"}, {SDLK_LEFTPAREN, "LeftParen"}, {SDLK_RIGHTPAREN, "RightParen"}, {SDLK_ASTERISK, "Asterisk"}, {SDLK_PLUS, "Plus"}, {SDLK_COMMA, "Comma"}, {SDLK_MINUS, "Minus"}, {SDLK_PERIOD, "Period"}, {SDLK_SLASH, "Slash"}, {SDLK_0, "0"}, {SDLK_1, "1"}, {SDLK_2, "2"}, {SDLK_3, "3"}, {SDLK_4, "4"}, {SDLK_5, "5"}, {SDLK_6, "6"}, {SDLK_7, "7"}, {SDLK_8, "8"}, {SDLK_9, "9"}, {SDLK_COLON, "Colon"}, {SDLK_SEMICOLON, "Semicolon"}, {SDLK_LESS, "Less"}, {SDLK_EQUALS, "Equals"}, {SDLK_GREATER, "Greater"}, {SDLK_QUESTION, "Question"}, {SDLK_AT, "At"}, // Skip uppercase letters {SDLK_LEFTBRACKET, "LeftBracket"}, {SDLK_BACKSLASH, "Backslash"}, {SDLK_RIGHTBRACKET, "RightBracket"}, {SDLK_CARET, "Caret"}, {SDLK_UNDERSCORE, "Underscore"}, {SDLK_BACKQUOTE, "Backquote"}, {SDLK_a, "A"}, {SDLK_b, "B"}, {SDLK_c, "C"}, {SDLK_d, "D"}, {SDLK_e, "E"}, {SDLK_f, "F"}, {SDLK_g, "G"}, {SDLK_h, "H"}, {SDLK_i, "I"}, {SDLK_j, "J"}, {SDLK_k, "K"}, {SDLK_l, "L"}, {SDLK_m, "M"}, {SDLK_n, "N"}, {SDLK_o, "O"}, {SDLK_p, "P"}, {SDLK_q, "Q"}, {SDLK_r, "R"}, {SDLK_s, "S"}, {SDLK_t, "T"}, {SDLK_u, "U"}, {SDLK_v, "V"}, {SDLK_w, "W"}, {SDLK_x, "X"}, {SDLK_y, "Y"}, {SDLK_z, "Z"}, {SDLK_DELETE, "Delete"}, // End of ASCII mapped keysyms // International keyboard syms {SDLK_WORLD_0, "World_0"}, {SDLK_WORLD_1, "World_1"}, {SDLK_WORLD_2, "World_2"}, {SDLK_WORLD_3, "World_3"}, {SDLK_WORLD_4, "World_4"}, {SDLK_WORLD_5, "World_5"}, {SDLK_WORLD_6, "World_6"}, {SDLK_WORLD_7, "World_7"}, {SDLK_WORLD_8, "World_8"}, {SDLK_WORLD_9, "World_9"}, {SDLK_WORLD_10, "World_10"}, {SDLK_WORLD_11, "World_11"}, {SDLK_WORLD_12, "World_12"}, {SDLK_WORLD_13, "World_13"}, {SDLK_WORLD_14, "World_14"}, {SDLK_WORLD_15, "World_15"}, {SDLK_WORLD_16, "World_16"}, {SDLK_WORLD_17, "World_17"}, {SDLK_WORLD_18, "World_18"}, {SDLK_WORLD_19, "World_19"}, {SDLK_WORLD_20, "World_20"}, {SDLK_WORLD_21, "World_21"}, {SDLK_WORLD_22, "World_22"}, {SDLK_WORLD_23, "World_23"}, {SDLK_WORLD_24, "World_24"}, {SDLK_WORLD_25, "World_25"}, {SDLK_WORLD_26, "World_26"}, {SDLK_WORLD_27, "World_27"}, {SDLK_WORLD_28, "World_28"}, {SDLK_WORLD_29, "World_29"}, {SDLK_WORLD_30, "World_30"}, {SDLK_WORLD_31, "World_31"}, {SDLK_WORLD_32, "World_32"}, {SDLK_WORLD_33, "World_33"}, {SDLK_WORLD_34, "World_34"}, {SDLK_WORLD_35, "World_35"}, {SDLK_WORLD_36, "World_36"}, {SDLK_WORLD_37, "World_37"}, {SDLK_WORLD_38, "World_38"}, {SDLK_WORLD_39, "World_39"}, {SDLK_WORLD_40, "World_40"}, {SDLK_WORLD_41, "World_41"}, {SDLK_WORLD_42, "World_42"}, {SDLK_WORLD_43, "World_43"}, {SDLK_WORLD_44, "World_44"}, {SDLK_WORLD_45, "World_45"}, {SDLK_WORLD_46, "World_46"}, {SDLK_WORLD_47, "World_47"}, {SDLK_WORLD_48, "World_48"}, {SDLK_WORLD_49, "World_49"}, {SDLK_WORLD_50, "World_50"}, {SDLK_WORLD_51, "World_51"}, {SDLK_WORLD_52, "World_52"}, {SDLK_WORLD_53, "World_53"}, {SDLK_WORLD_54, "World_54"}, {SDLK_WORLD_55, "World_55"}, {SDLK_WORLD_56, "World_56"}, {SDLK_WORLD_57, "World_57"}, {SDLK_WORLD_58, "World_58"}, {SDLK_WORLD_59, "World_59"}, {SDLK_WORLD_60, "World_60"}, {SDLK_WORLD_61, "World_61"}, {SDLK_WORLD_62, "World_62"}, {SDLK_WORLD_63, "World_63"}, {SDLK_WORLD_64, "World_64"}, {SDLK_WORLD_65, "World_65"}, {SDLK_WORLD_66, "World_66"}, {SDLK_WORLD_67, "World_67"}, {SDLK_WORLD_68, "World_68"}, {SDLK_WORLD_69, "World_69"}, {SDLK_WORLD_70, "World_70"}, {SDLK_WORLD_71, "World_71"}, {SDLK_WORLD_72, "World_72"}, {SDLK_WORLD_73, "World_73"}, {SDLK_WORLD_74, "World_74"}, {SDLK_WORLD_75, "World_75"}, {SDLK_WORLD_76, "World_76"}, {SDLK_WORLD_77, "World_77"}, {SDLK_WORLD_78, "World_78"}, {SDLK_WORLD_79, "World_79"}, {SDLK_WORLD_80, "World_80"}, {SDLK_WORLD_81, "World_81"}, {SDLK_WORLD_82, "World_82"}, {SDLK_WORLD_83, "World_83"}, {SDLK_WORLD_84, "World_84"}, {SDLK_WORLD_85, "World_85"}, {SDLK_WORLD_86, "World_86"}, {SDLK_WORLD_87, "World_87"}, {SDLK_WORLD_88, "World_88"}, {SDLK_WORLD_89, "World_89"}, {SDLK_WORLD_90, "World_90"}, {SDLK_WORLD_91, "World_91"}, {SDLK_WORLD_92, "World_92"}, {SDLK_WORLD_93, "World_93"}, {SDLK_WORLD_94, "World_94"}, {SDLK_WORLD_95, "World_95"}, // Numeric keypad {SDLK_KP0, "KP_0"}, {SDLK_KP1, "KP_1"}, {SDLK_KP2, "KP_2"}, {SDLK_KP3, "KP_3"}, {SDLK_KP4, "KP_4"}, {SDLK_KP5, "KP_5"}, {SDLK_KP6, "KP_6"}, {SDLK_KP7, "KP_7"}, {SDLK_KP8, "KP_8"}, {SDLK_KP9, "KP_9"}, {SDLK_KP_PERIOD, "KP_Period"}, {SDLK_KP_DIVIDE, "KP_Divide"}, {SDLK_KP_MULTIPLY, "KP_Multiply"}, {SDLK_KP_MINUS, "KP_Minus"}, {SDLK_KP_PLUS, "KP_Plus"}, {SDLK_KP_ENTER, "KP_Enter"}, {SDLK_KP_EQUALS, "KP_Equals"}, // Arrows + Home/End pad {SDLK_UP, "Up"}, {SDLK_DOWN, "Down"}, {SDLK_RIGHT, "Right"}, {SDLK_LEFT, "Left"}, {SDLK_INSERT, "Insert"}, {SDLK_HOME, "Home"}, {SDLK_END, "End"}, {SDLK_PAGEUP, "PageUp"}, {SDLK_PAGEDOWN, "PageDown"}, // Function keys {SDLK_F1, "F1"}, {SDLK_F2, "F2"}, {SDLK_F3, "F3"}, {SDLK_F4, "F4"}, {SDLK_F5, "F5"}, {SDLK_F6, "F6"}, {SDLK_F7, "F7"}, {SDLK_F8, "F8"}, {SDLK_F9, "F9"}, {SDLK_F10, "F10"}, {SDLK_F11, "F11"}, {SDLK_F12, "F12"}, {SDLK_F13, "F13"}, {SDLK_F14, "F14"}, {SDLK_F15, "F15"}, // Key state modifier keys {SDLK_NUMLOCK, "NumLock"}, {SDLK_CAPSLOCK, "CapsLock"}, {SDLK_SCROLLOCK, "ScrollLock"}, {SDLK_RSHIFT, "RightShift"}, {SDLK_LSHIFT, "LeftShift"}, {SDLK_RCTRL, "RightCtrl"}, {SDLK_LCTRL, "LeftCtrl"}, {SDLK_RALT, "RightAlt"}, {SDLK_LALT, "LeftAlt"}, {SDLK_RMETA, "RightMeta"}, {SDLK_LMETA, "LeftMeta"}, {SDLK_LSUPER, "LeftSuper"}, {SDLK_RSUPER, "RightSuper"}, {SDLK_MODE, "Mode"}, {SDLK_COMPOSE, "Compose"}, // Miscellaneous function keys {SDLK_HELP, "Help"}, {SDLK_PRINT, "Print"}, {SDLK_SYSREQ, "SysRq"}, {SDLK_BREAK, "Break"}, {SDLK_MENU, "Menu"}, {SDLK_POWER, "Power"}, {SDLK_EURO, "Euro"}, {SDLK_UNDO, "Undo"}, {0, NULL}, }; for (n = 0; keys[n].name; n++) key_add(keys[n].code, keys[n].name); } // ------------------------------------------------------------------------------------------------------------ typedef void (*ev_handler) (isysev_t ev, const char *data); typedef struct kmapnode kmapnode_t; typedef struct kmap kmap_t; struct kmap { char *name; kmap_t *parent; // for inheritance tree_t *bindings; }; struct kmapnode { isysev_t ev; ev_handler handler; const char *data; }; tree_t *keymaps; static kmapnode_t *kmapnode_alloc(isysev_t ev, ev_handler handler, const char *data) { kmapnode_t *node = mem_alloc(sizeof(kmapnode_t)); node->ev = ev; node->handler = handler; node->data = data; return node; } #define kmapnode_free free static void kmapnode_print(void *v) { kmapnode_t *node = v; printf("ev=%08x binding=%p(%p)\n", node->ev.ival, node->handler, node->data); } static int kmapnode_cmp(const void *a, const void *b) { return ((kmapnode_t *) a)->ev.ival - ((kmapnode_t *) b)->ev.ival; } static int kmap_cmp(const void *a, const void *b) { return strcasecmp(((kmap_t *) a)->name, ((kmap_t *) b)->name); } static kmap_t *kmap_alloc(const char *name) { kmap_t *m = mem_alloc(sizeof(kmap_t)); m->name = strdup(name); m->parent = NULL; m->bindings = tree_alloc(kmapnode_cmp); return m; } static void kmap_freemap(kmap_t *m) { tree_free(m->bindings, kmapnode_free); free(m->name); free(m); } static void kmap_init(void) { keymaps = tree_alloc(kmap_cmp); } static void kmap_free(void) { tree_free(keymaps, (treewalk_t) kmap_freemap); } // if create is nonzero, the keymap is allocated if not already in the tree static kmap_t *kmap_find(const char *name, int create) { kmap_t find; kmap_t *m, *new; if (create) { new = kmap_alloc(name); m = tree_insert(keymaps, new); if (m) { kmap_freemap(new); return m; } else { return new; } } else { find.name = (char *) name; // stupid cast... return tree_find(keymaps, &find); } } static void kmap_bind(kmap_t *m, isysev_t ev, ev_handler handler, const char *data) { kmapnode_t *node = kmapnode_alloc(ev, handler, data); kmapnode_free(tree_replace(m->bindings, node)); } static void kmap_inherit(kmap_t *child, kmap_t *parent) { child->parent = parent; } static int kmap_run_binding(kmap_t *m, isysev_t ev) { kmapnode_t *node; kmapnode_t find; if (!m) return 0; // Most of the time, the key-repeat behavior is desired (e.g. arrow keys), and in the rare cases // where it isn't, the function that handles the event can check the flag itself. // Unicode is probably never useful. find.ev = ev; find.ev.bits.repeat = 0; find.ev.bits.unicode = 0; // If a binding was found, we're done node = tree_find(m->bindings, &find); if (node) { node->handler(ev, node->data); return 1; } // If the event couldn't be found in the keymap as is, clear the dev_id and look it up again. // This allows for binding a fake "all" device that applies to every dev_id of its type. find.ev.bits.dev_id = 0; node = tree_find(m->bindings, &find); if (node) { node->handler(ev, node->data); return 1; } // Check inherited keymaps return kmap_run_binding(m->parent, ev); } static void kmap_print(kmap_t *m) { tree_walk(m->bindings, kmapnode_print); } // ------------------------------------------------------------------------------------------------------------ static tree_t *evfuncs; typedef struct evfunc { const char *name; ev_handler handler; } evfunc_t; static int evfunc_cmp(const void *a, const void *b) { return strcasecmp(((evfunc_t *) a)->name, ((evfunc_t *) b)->name); } static void evfunc_init(void) { evfuncs = tree_alloc(evfunc_cmp); } static void evfunc_free(void) { tree_free(evfuncs, (treewalk_t) free); } static ev_handler evfunc_lookup(const char *name) { evfunc_t *node; evfunc_t find; find.name = name; node = tree_find(evfuncs, &find); return node ? node->handler : NULL; } static void evfunc_register(const char *name, ev_handler handler) { evfunc_t *node = mem_alloc(sizeof(evfunc_t)); node->name = name; node->handler = handler; free(tree_replace(evfuncs, node)); } static void evfunc_register_many(evfunc_t *funcs) { evfunc_t *f; for (f = funcs; f->handler; f++) evfunc_register(f->name, f->handler); } // ------------------------------------------------------------------------------------------------------------ static isysev_t event_parse(const char *s) { int n; size_t len; char *e; char tmp[16]; isysev_t ev; ev.ival = 0; // skip leading spaces s += strspn(s, " \t"); // first read the device type, then optionally a slash if (*s == '@') { // numeric device type s++; n = strtol(s, &e, 10); if (s == e) { printf("event_parse: what kind of rubbish is this?\n"); return (isysev_t) 0u; } ev.bits.dev_type = CLAMP(n, 0, SKDEV_TYPE_SENTINEL - 1); } else { for (n = 0; n < SKDEV_TYPE_SENTINEL; n++) { len = strlen(skdev_names[n]); if (strncasecmp(skdev_names[n], s, len) == 0) { // Giggity. ev.bits.dev_type = n; s += len; break; } } } // check for slash + number if (*s == '/') { s++; n = strtol(s, &e, 10); if (s != e) { ev.bits.dev_id = CLAMP(n, 0, SKDEV_ID_MAX); s = e; } // if (s == e) it's just a random trailing slash // -- let's ignore it and pretend it was a zero } len = strspn(s, " \t"); if (n == SKDEV_TYPE_SENTINEL) { // none of the device types matched -- it's probably a key on the keyboard. ev.bits.dev_type = SKDEV_TYPE_PCKEYBOARD; ev.bits.dev_id = SKDEV_ID_ANY; } else { // This MIGHT be a match! Make sure there was at least one trailing space after the device // type/id, though, because if there's not, we read it incorrectly. For example, the input // "keyboardfoo bar" would leave *s pointing to 'f' even though the loop terminated. if (!len) { // Argh, this isn't an event descriptor at all, it's just junk. Time to bail. printf("event_parse: unknown event descriptor\n"); return (isysev_t) 0u; } } s += len; if (*s == '#') { // Raw hexcode? s++; n = strtol(s, &e, 16); if (s == e) { // Wait, no. printf("event_parse: hexcode is not hex\n"); return (isysev_t) 0u; } ev.bits.keycode = CLAMP(n, 0, SKCODE_MAX); s = e; } else if (ev.bits.dev_type == SKDEV_TYPE_PCKEYBOARD) { // Might be a key. Check for modifier prefixes. struct { int skmode; size_t len; char *str; } mod[] = { {SKMODE_CTRL, 4, "ctrl"}, {SKMODE_ALT, 3, "alt"}, {SKMODE_SHIFT, 5, "shift"}, // alternate representations {SKMODE_CTRL, 7, "control"}, {SKMODE_CTRL, 3, "ctl"}, {SKMODE_ALT, 4, "mod1"}, {SKMODE_ALT, 4, "meta"}, {SKMODE_CTRL, 1, "c"}, {SKMODE_ALT, 1, "a"}, {SKMODE_SHIFT, 1, "s"}, {SKMODE_ALT, 1, "m"}, {0, 0, NULL}, }; if (*s == '^') { s++; ev.bits.modifier |= SKMODE_CTRL; } len = strcspn(s, "+-"); n = 0; while (s[len] && mod[n].len) { if (len == mod[n].len && (s[len] == '+' || s[len] == '-') && strncasecmp(s, mod[n].str, len) == 0) { s += 1 + len; ev.bits.modifier |= mod[n].skmode; len = strcspn(s, "+-"); n = 0; } else { n++; } } // At this point we SHOULD be looking at the key name. strncpy(tmp, s, 15); tmp[15] = 0; e = strpbrk(tmp, " \t"); if (e) *e = 0; n = keytab_name_to_code(tmp); if (n) { ev.bits.keycode = n; } else { // Argh! All this work and it's not a valid key. printf("event_parse: unknown key \"%s\"\n", tmp); return (isysev_t) 0u; } s += strlen(tmp); } else { // Give up! printf("event_parse: invalid event descriptor for device\n"); return (isysev_t) 0u; } len = strspn(s, " \t"); if (len) { s += len; // If there's other junk at the end, just ignore it. ("down", maybe?) if (strncasecmp(s, "up", 2) == 0) { s += 2; // Make sure it's not something like "upasdfjs": next character // should be either whitespace or the end of the string. if (*s == '\0' || *s == ' ' || *s == '\t') ev.bits.release = 1; } } return ev; } // 'buf' should be at least 64 chars // return: length of event string static int event_describe(char *buf, isysev_t ev) { const char *keyname; int len = 0; if (ev.bits.dev_type < SKDEV_TYPE_SENTINEL) { len += sprintf(buf, "%s/%d ", skdev_names[ev.bits.dev_type], ev.bits.dev_id); } else { // It's a weird mystery device! len += sprintf(buf, "@%d/%d ", ev.bits.dev_type, ev.bits.dev_id); } // len <= 13 if (ev.bits.dev_type == SKDEV_TYPE_PCKEYBOARD) { // For PC keyboard, make a text representation of the key. // Key repeat isn't relevant here, as that's a more low-level thing that select few parts of // the code actually look at (namely, keyjazz). Also, there's no point in worrying about the // unicode character, since text fields don't have any special keybindings. if (ev.bits.modifier & SKMODE_CTRL) len += sprintf(buf + len, "Ctrl-"); if (ev.bits.modifier & SKMODE_ALT) len += sprintf(buf + len, "Alt-"); if (ev.bits.modifier & SKMODE_SHIFT) len += sprintf(buf + len, "Shift-"); // len <= 27 // If we have a name for this key, use it... keyname = keytab_code_to_name(ev.bits.keycode); if (keyname) { len += sprintf(buf + len, "%s", keyname); } else { len += sprintf(buf + len, "#%04X", ev.bits.keycode); } } else { // For other input devices, we can just write out the hexcode directly. len += sprintf(buf + len, "#%04X", ev.bits.keycode); } if (ev.bits.release) { len += sprintf(buf + len, " up"); } return len; } // ------------------------------------------------------------------------------------------------------------ enum { KMAP_PREFIX, KMAP_WIDGET, KMAP_WIDGETCLASS, KMAP_LOCAL, KMAP_GLOBAL, KMAP_NUM_MAPS, }; static kmap_t *active_keymaps[KMAP_NUM_MAPS]; //- prefix map, only valid for one key // for handling emacs-style keys like ^X^C //- widget map, based on current focus // most custom widgets (e.g. pattern editor, envelopes) bind to this map //- widget-class map, also based on focus // left/right on thumbbars //- local map, changes based on current page // keys like alt-a on sample editor //- global map // contains keys that didn't get overriden by the page, such as Escape // (the sample load page traps this key, as does the instrument envelope editor) // ** when a dialog is active, the global map is temporarily cleared, and the local map is set to the dialog's static void event_handle(isysev_t ev) { int n; char buf[64]; printf("\r%78s\r", ""); for (n = 0; n < KMAP_NUM_MAPS; n++) { if (kmap_run_binding(active_keymaps[n], ev)) { printf("-- key handled by kmap #%d %s\n", n, active_keymaps[n]->name); return; } } // no one picked it up - fallback event_describe(buf, ev); printf("ev=%08x %s\r", ev.ival, buf); fflush(stdout); } static void event_loop(void) { SDL_Event sdlev; SDLKey lastsym = 0; isysev_t ev; while (SDL_WaitEvent(&sdlev)) { // Transform the SDL event into a single number ev.ival = 0; switch (sdlev.type) { case SDL_KEYUP: lastsym = 0; ev.bits.release = 1; // fall through case SDL_KEYDOWN: if (sdlev.key.which > SKDEV_ID_MAX) break; ev.bits.dev_type = SKDEV_TYPE_PCKEYBOARD; ev.bits.dev_id = 1 + sdlev.key.which; ev.bits.repeat = (sdlev.key.keysym.sym && sdlev.key.keysym.sym == lastsym); if (sdlev.key.state == SDL_PRESSED) lastsym = sdlev.key.keysym.sym; if (sdlev.key.keysym.unicode >= 32) ev.bits.unicode = 1; // XXX need to save the unicode value somewhere... // Scancodes are 8-bit values. Keysyms are 16-bit, but SDL only uses 9 bits of them. // Either way, anything we get will fit into the 15 bits we're stuffing it into. ev.bits.keycode = sdlev.key.keysym.sym ? (sdlev.key.keysym.sym & ~SKCODE_PCK_SCANCODE) : (sdlev.key.keysym.scancode | SKCODE_PCK_SCANCODE); if (sdlev.key.keysym.mod & KMOD_CTRL) ev.bits.modifier |= SKMODE_CTRL; if (sdlev.key.keysym.mod & KMOD_ALT) ev.bits.modifier |= SKMODE_ALT; if (sdlev.key.keysym.mod & KMOD_SHIFT) ev.bits.modifier |= SKMODE_SHIFT; event_handle(ev); break; case SDL_JOYBALLMOTION: // XXX calculate velocity from xrel/yrel and save it. // Certain code might be able to use this value similarly to midi note velocity... if (sdlev.jball.which > SKDEV_ID_MAX || sdlev.jball.ball > MAX_JS_BALLS) break; ev.bits.dev_type = SKDEV_TYPE_JOYSTICK; ev.bits.dev_id = 1 + sdlev.jball.which; if (sdlev.jball.xrel < -JS_BALL_THRESHOLD) { ev.bits.keycode = JS_BALL_TO_KEYCODE(sdlev.jball.ball, JS_DIR_LEFT); event_handle(ev); } else if (sdlev.jball.xrel > JS_BALL_THRESHOLD) { ev.bits.keycode = JS_BALL_TO_KEYCODE(sdlev.jball.ball, JS_DIR_RIGHT); event_handle(ev); } if (sdlev.jball.yrel < -JS_BALL_THRESHOLD) { ev.bits.keycode = JS_BALL_TO_KEYCODE(sdlev.jball.ball, JS_DIR_UP); event_handle(ev); } else if (sdlev.jball.yrel > JS_BALL_THRESHOLD) { ev.bits.keycode = JS_BALL_TO_KEYCODE(sdlev.jball.ball, JS_DIR_DOWN); event_handle(ev); } break; case SDL_JOYHATMOTION: // XXX save hat direction; handle repeat when held down; issue release events. if (sdlev.jhat.which > SKDEV_ID_MAX || sdlev.jhat.hat > MAX_JS_HATS) break; ev.bits.dev_type = SKDEV_TYPE_JOYSTICK; ev.bits.dev_id = 1 + sdlev.jhat.which; switch (sdlev.jhat.value) { default: break; case SDL_HAT_LEFTUP: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_LEFT); event_handle(ev); // fall through case SDL_HAT_UP: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_UP); event_handle(ev); break; case SDL_HAT_RIGHTUP: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_UP); event_handle(ev); // fall through case SDL_HAT_RIGHT: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_RIGHT); event_handle(ev); break; case SDL_HAT_LEFTDOWN: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_DOWN); event_handle(ev); // fall through case SDL_HAT_LEFT: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_LEFT); event_handle(ev); break; case SDL_HAT_RIGHTDOWN: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_RIGHT); event_handle(ev); // fall through case SDL_HAT_DOWN: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_DOWN); event_handle(ev); break; } break; case SDL_JOYAXISMOTION: // XXX save axis direction; handle repeat when held down; issue release events. if (sdlev.jbutton.which > SKDEV_ID_MAX || sdlev.jaxis.axis > MAX_JS_AXES) break; ev.bits.dev_type = SKDEV_TYPE_JOYSTICK; ev.bits.dev_id = 1 + sdlev.jaxis.which; //ev.bits.release = 0; if (sdlev.jaxis.value < -JS_AXIS_THRESHOLD) { ev.bits.keycode = JS_AXIS_TO_KEYCODE(sdlev.jaxis.axis, JS_AXIS_NEG); event_handle(ev); } else if (sdlev.jaxis.value > JS_AXIS_THRESHOLD) { ev.bits.keycode = JS_AXIS_TO_KEYCODE(sdlev.jaxis.axis, JS_AXIS_POS); event_handle(ev); } break; case SDL_JOYBUTTONUP: ev.bits.release = 1; // fall through case SDL_JOYBUTTONDOWN: if (sdlev.jbutton.which > SKDEV_ID_MAX || sdlev.jbutton.button > MAX_JS_BUTTONS) break; ev.bits.dev_type = SKDEV_TYPE_JOYSTICK; ev.bits.dev_id = 1 + sdlev.jbutton.which; ev.bits.keycode = JS_BUTTON_TO_KEYCODE(sdlev.jbutton.button); event_handle(ev); break; // Need to get midi-in events routed through here somehow. case SDL_QUIT: return; default: break; } } } // ------------------------------------------------------------------------------------------------------------ int current_page = 2; const char *page_names[] = { NULL, NULL, "Pattern Editor", "Sample List", "Instrument List", "Info Page", }; static void ev_pat_raise_semitone(isysev_t ev, const char *data) { printf("raise semitone\n"); } static void ev_pat_lower_semitone(isysev_t ev, const char *data) { printf("lower semitone\n"); } static void ev_pat_raise_octave(isysev_t ev, const char *data) { printf("raise octave\n"); } static void ev_pat_lower_octave(isysev_t ev, const char *data) { printf("lower octave\n"); } static void ev_pat_options(isysev_t ev, const char *data) { printf("pattern editor options dialog\n"); // override global keys (this should be done by the dialog/menu init code) active_keymaps[KMAP_LOCAL] = kmap_find("Pattern Editor Options", 0); active_keymaps[KMAP_GLOBAL] = kmap_find("Dialog", 0); } static void ev_pat_set_length(isysev_t ev, const char *data) { printf("pattern editor length dialog\n"); active_keymaps[KMAP_LOCAL] = kmap_find("Pattern Editor Length", 0); active_keymaps[KMAP_GLOBAL] = kmap_find("Dialog", 0); } static void ev_smp_swap_sign(isysev_t ev, const char *data) { printf("sign convert\n"); } static void ev_smp_toggle_quality(isysev_t ev, const char *data) { printf("toggle 8/16 bit\n"); } static void ev_keyjazz(isysev_t ev, const char *data) { printf("keyjazz - %s\n", data); } static void ev_quit(isysev_t ev, const char *data) { printf("quit\n"); } static void ev_page_switch(isysev_t ev, const char *data) { int n; for (n = 2; n <= 5; n++) { if (strcasecmp(page_names[n], data) == 0) { current_page = n; active_keymaps[KMAP_LOCAL] = kmap_find(data, 0); printf("switched to page %d (%s)\n", n, data); return; } } printf("unknown page name \"%s\"\n", data); } static void ev_song_play_infopage(isysev_t ev, const char *data) { printf("play song and show infopage!\n"); active_keymaps[KMAP_LOCAL] = kmap_find("Info Page", 0); } static void ev_song_play(isysev_t ev, const char *data) { printf("play song and stay put!\n"); } static void ev_song_stop(isysev_t ev, const char *data) { printf("stop playing!\n"); } static void ev_main_menu(isysev_t ev, const char *data) { printf("pop up menu\n"); active_keymaps[KMAP_LOCAL] = kmap_find("Menu", 0); active_keymaps[KMAP_GLOBAL] = kmap_find("Dialog", 0); } static void ev_dlg_cancel(isysev_t ev, const char *data) { active_keymaps[KMAP_LOCAL] = kmap_find(page_names[current_page], 0); active_keymaps[KMAP_GLOBAL] = kmap_find("Global", 0); } // ------------------------------------------------------------------------------------------------------------ typedef struct dbg { const char *m, *k, *f, *d; } dbg_t; int main(int argc, char **argv) { int n, jn; kmap_t *m; ev_handler f; SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK); SDL_EnableUNICODE(1); SDL_EnableKeyRepeat(125, 25); n = SDL_NumJoysticks(); if (n > SKDEV_ID_MAX) { printf("warning: %d of your %d joysticks will be ignored\n", SKDEV_ID_MAX - n, n); n = SKDEV_ID_MAX; } for (jn = 0; jn < n; jn++) { SDL_Joystick *js = SDL_JoystickOpen(jn); printf("Joystick #%d [%s]\n\taxes:%d buttons:%d hats:%d balls:%d\n", jn, SDL_JoystickName(jn), SDL_JoystickNumAxes(js), SDL_JoystickNumButtons(js), SDL_JoystickNumHats(js), SDL_JoystickNumBalls(js)); } keytab_init(); kmap_init(); evfunc_init(); // prefix local widget widgetclass global active_keymaps[KMAP_GLOBAL] = kmap_find("Global", 1); m = active_keymaps[KMAP_LOCAL] = kmap_find("Pattern Editor", 1); kmap_inherit(m, kmap_find("Keyjazz", 1)); evfunc_t evs[] = { {"pat_raise_semitone", ev_pat_raise_semitone}, {"pat_lower_semitone", ev_pat_lower_semitone}, {"pat_raise_octave", ev_pat_raise_octave}, {"pat_lower_octave", ev_pat_lower_octave}, {"pat_options", ev_pat_options}, {"pat_set_length", ev_pat_set_length}, {"smp_swap_sign", ev_smp_swap_sign}, {"smp_toggle_quality", ev_smp_toggle_quality}, {"keyjazz", ev_keyjazz}, {"quit", ev_quit}, {"page_switch", ev_page_switch}, {"song_play_infopage", ev_song_play_infopage}, {"song_play", ev_song_play}, {"song_stop", ev_song_stop}, {"main_menu", ev_main_menu}, {"dlg_cancel", ev_dlg_cancel}, {NULL, NULL}, }; evfunc_register_many(evs); dbg_t debug[] = { {"Pattern Editor", "Alt-Q", "pat_raise_semitone", NULL}, {"Pattern Editor", "Alt-A", "pat_lower_semitone", NULL}, {"Pattern Editor", "Alt-Shift-Q", "pat_raise_octave", NULL}, {"Pattern Editor", "Alt-Shift-A", "pat_lower_octave", NULL}, {"Pattern Editor", "F2", "pat_options", NULL}, {"Pattern Editor", "Ctrl-F2", "pat_set_length", NULL}, {"Pattern Editor Options", "F2", "page_switch", "Pattern Editor"}, {"Sample List", "Alt-Q", "smp_toggle_quality", NULL}, {"Sample List", "Alt-A", "smp_swap_sign", NULL}, {"Keyjazz", "q", "keyjazz", "C-1"}, {"Keyjazz", "2", "keyjazz", "C#1"}, {"Keyjazz", "w", "keyjazz", "D-1"}, {"Keyjazz", "3", "keyjazz", "D#1"}, {"Keyjazz", "e", "keyjazz", "E-1"}, {"Keyjazz", "r", "keyjazz", "F-1"}, {"Keyjazz", "5", "keyjazz", "F#1"}, {"Keyjazz", "t", "keyjazz", "G-1"}, {"Keyjazz", "6", "keyjazz", "G#1"}, {"Keyjazz", "y", "keyjazz", "A-1"}, {"Keyjazz", "7", "keyjazz", "A#1"}, {"Keyjazz", "u", "keyjazz", "B-1"}, {"Keyjazz", "i", "keyjazz", "C-2"}, {"Global", "Ctrl-Q", "quit", NULL}, {"Global", "F2", "page_switch", "Pattern Editor"}, {"Global", "F3", "page_switch", "Sample List"}, {"Global", "F4", "page_switch", "Instrument List"}, {"Global", "F5", "song_play_infopage", NULL}, {"Global", "Ctrl-F5", "song_play", NULL}, {"Global", "F8", "song_stop", NULL}, {"Global", "Escape", "main_menu", NULL}, {"Dialog", "Escape", "dlg_cancel", NULL}, {NULL, NULL, NULL, NULL}, }; for (n = 0; debug[n].k; n++) { if (strcasecmp(m->name, debug[n].m) != 0) m = kmap_find(debug[n].m, 1); f = evfunc_lookup(debug[n].f); if (!f) { printf("warning: unknown function \"%s\"\n", debug[n].f); continue; } kmap_bind(m, event_parse(debug[n].k), f, debug[n].d); } kmap_print(kmap_find("Pattern Editor", 0)); SDL_JoystickEventState(SDL_ENABLE); SDL_SetVideoMode(200, 200, 0, 0); event_loop(); evfunc_free(); kmap_free(); keytab_free(); SDL_Quit(); return 0; } schismtracker-20180209/schism/itf.c000066400000000000000000000643671323741476300170710ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "version.h" #include "log.h" #include "sdlmain.h" #include static const uint8_t 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 uint8_t 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 uint8_t 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 uint8_t helptext_charmap[] = "Home/End First/last char.\n"; static const uint8_t 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 uint8_t 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; static int top_font = 0, cur_font = 0; static void fontlist_reposition(void) { if (cur_font < 0) cur_font = 0; /* weird! */ 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) { const char *ext; if (f->sort_order == -100) return 1; /* this is our font.cfg, at the top of the list */ if (f->type & TYPE_BROWSABLE_MASK) return 0; /* we don't care about directories and stuff */ ext = get_extension(f->base); return (strcasecmp(ext, ".itf") == 0 || strcasecmp(ext, ".fnt") == 0); } static void load_fontlist(void) { char *font_dir, *p; struct stat st = {}; dmoz_free(&flist, NULL); top_font = cur_font = 0; font_dir = dmoz_path_concat_len(cfg_dir_dotschism, "fonts", strlen(cfg_dir_dotschism), 5); mkdir(font_dir, 0755); p = dmoz_path_concat_len(font_dir, "font.cfg", strlen(font_dir), 8); dmoz_add_file(&flist, p, str_dup("font.cfg"), &st, -100); /* put it on top */ if (dmoz_read(font_dir, &flist, NULL, NULL) < 0) log_perror(font_dir); free(font_dir); dmoz_filter_filelist(&flist, fontgrep, &cur_font, NULL); while (dmoz_worker()); fontlist_reposition(); /* p is freed by dmoz_free */ } static uint8_t 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 char* name, int x, int y, int inner_width, int inner_height, int active) { int n, c; int len = strlen(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; uint8_t *ptr; if (itfmap_pos < 0 || itfmap_chars[itfmap_pos] != current_char) { ptr = (unsigned char *) strchr((char *) 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 uint8_t *ptr = helptext_gen; const uint8_t *eol; int line; int column; for (line = INNER_Y(HELPTEXT_Y); *ptr; line++) { eol = (unsigned char *) strchr((char *) ptr, '\n'); if (!eol) eol = (unsigned char *) strchr((char *) 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 = (unsigned char *) strchr((char *) ptr, '\n'); if (!eol) eol = (unsigned char *) strchr((char *) 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(ver_short_copyright, 77 - strlen(ver_short_copyright), 46, 1, 0); } static inline void draw_time(void) { char buf[16]; sprintf(buf, "%.2d:%.2d:%.2d", status.tmnow.tm_hour, status.tmnow.tm_min, status.tmnow.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) { uint8_t tmp[8] = { 0 }; int ci = current_char << 3; int n, bit; uint8_t *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 confirm_font_save_ok(void *vf) { char *f = vf; if (font_save(f) != 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 == KEY_PRESS) 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: if (cur_font < flist.num_files && flist.files[cur_font]) { if (strcasecmp(flist.files[cur_font]->base,"font.cfg") != 0) { dialog_create(DIALOG_OK_CANCEL, "Overwrite font file?", confirm_font_save_ok, NULL, 1, flist.files[cur_font]->base); return; } confirm_font_save_ok(flist.files[cur_font]->base); } 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; uint8_t *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 == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) 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; } static int fontedit_handle_key(struct key_event * k) { int n, ci = current_char << 3; uint8_t *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 == KEY_RELEASE) return 1; k->sym += 10; /* fall through */ case SDLK_KP1...SDLK_KP9: if (k->state == KEY_RELEASE) 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; default: break; }; switch (k->sym) { case '0': if (k->state == KEY_RELEASE) return 1; k->sym += 10; /* fall through */ case '1'...'9': if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) return 1; selected_item = EDITBOX; status.flags |= NEED_UPDATE; return 1; case SDLK_F3: if (k->state == KEY_RELEASE) return 1; selected_item = CHARMAP; status.flags |= NEED_UPDATE; return 1; case SDLK_F4: if (k->state == KEY_RELEASE) return 1; selected_item = ITFMAP; status.flags |= NEED_UPDATE; return 1; case SDLK_TAB: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) return 1; if (k->mod & KMOD_ALT) { memcpy(clipboard, ptr, 8); return 1; } break; case SDLK_p: if (k->state == KEY_RELEASE) return 1; if (k->mod & KMOD_ALT) { memcpy(ptr, clipboard, 8); status.flags |= NEED_UPDATE; return 1; } break; case SDLK_m: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) return 1; if (k->mod & KMOD_ALT) { memset(ptr, 0, 8); status.flags |= NEED_UPDATE; return 1; } break; case SDLK_h: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) return 1; if (k->mod & KMOD_ALT) { for (n = 0; n < 4; n++) { uint8_t 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 1; if (!(k->mod & KMOD_CTRL)) break; /* fall through */ case SDLK_F9: if (k->state == KEY_RELEASE) return 1; load_fontlist(); fontlist_mode = MODE_LOAD; selected_item = FONTLIST; status.flags |= NEED_UPDATE; return 1; case SDLK_s: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 1; break; default: if (k->state == KEY_RELEASE) 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; } default: break; }; 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); } schismtracker-20180209/schism/keyboard.c000066400000000000000000000422431323741476300200740ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "osdefs.h" #include "sdlmain.h" #include /* --------------------------------------------------------------------- */ 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 const char **note_names = note_names_up; static const char *note_names_short = note_names_short_up; /* --------------------------------------------------------------------- */ 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 '$' = keyoff * '&' = setenvposition * '('/')' = noteslide up/down (IMF) */ static const char effects[] = ".JFEGHLKRXODB!CQATI?SMNVW$UY?P&Z()?"; static const char ptm_effects[] = ".0123456789ABCDRFFT????GHK?YXPLZ()?"; /* --------------------------------------------------------------------- */ // XXX stupid magic numbers... void kbd_sharp_flat_toggle(int e) { switch (e) { case -1: kbd_sharp_flat_toggle(!!(note_names == note_names_up)); break; case 1: status.flags |= ACCIDENTALS_AS_FLATS; status_text_flash("Displaying accidentals as flats (b)"); note_names = note_names_down; note_names_short = note_names_short_down; break; default: /* case 0... */ status.flags &= ~ACCIDENTALS_AS_FLATS; status_text_flash("Displaying accidentals as sharps (#)"); note_names = note_names_up; note_names_short = note_names_short_up; } } char get_effect_char(int effect) { if (effect < 0 || effect > 34) { log_appendf(4, "get_effect_char: effect %d out of range", effect); return '?'; } return effects[effect]; } int get_ptm_effect_number(char effect) { const char *ptr; if (effect >= 'a' && effect <= 'z') effect -= 32; ptr = strchr(ptm_effects, effect); return ptr ? (ptr - effects) : -1; } 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_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_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; break; case SDLK_PERIOD: k->sym = SDLK_GREATER; break; case SDLK_4: k->sym = SDLK_DOLLAR; break; case SDLK_EQUALS: k->sym = SDLK_PLUS; break; case SDLK_SEMICOLON: k->sym = SDLK_COLON; break; case SDLK_8: k->sym = SDLK_ASTERISK; break; default: break; }; } if (k->mod & KMOD_META) { k->mod = ((k->mod & ~KMOD_META) | ((status.flags & META_IS_CTRL) ? KMOD_CTRL : KMOD_ALT)); } if ((k->mod & KMOD_MODE) && (status.flags & ALTGR_IS_ALT)) { /* Treat AltGr as Alt (delt) */ k->mod = ((k->mod & ~KMOD_MODE) | KMOD_ALT); } 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; default: 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; default: break; }; } if (k->sym == k->orig_sym) { switch (k->sym) { case SDLK_RETURN: k->unicode = '\r'; break; default: if (k->is_synthetic != 3) { /* "un" unicode it */ k->unicode = char_unicode_to_cp437(k->unicode); } }; return; } switch (k->sym) { case SDLK_SLASH: k->unicode = (k->mod & KMOD_SHIFT) ? '?' : '/'; break; case SDLK_ASTERISK: k->unicode = '*'; break; case SDLK_MINUS: k->unicode = (k->mod & KMOD_SHIFT) ? '_' : '-'; break; case SDLK_PLUS: k->unicode = '+'; break; case SDLK_RETURN: k->unicode = '\r'; break; case SDLK_EQUALS: k->unicode = (k->mod & KMOD_SHIFT) ? '+' : '='; break; case SDLK_PERIOD: k->unicode = (k->mod & KMOD_SHIFT) ? '>' : '.'; break; case SDLK_0: k->unicode = (k->mod & KMOD_SHIFT) ? ')' : '0'; break; case SDLK_1: k->unicode = (k->mod & KMOD_SHIFT) ? '!' : '1'; break; case SDLK_2: k->unicode = (k->mod & KMOD_SHIFT) ? '@' : '2'; break; case SDLK_3: k->unicode = (k->mod & KMOD_SHIFT) ? '#' : '3'; break; case SDLK_4: k->unicode = (k->mod & KMOD_SHIFT) ? '$' : '4'; break; case SDLK_5: k->unicode = (k->mod & KMOD_SHIFT) ? '%' : '5'; break; case SDLK_6: k->unicode = (k->mod & KMOD_SHIFT) ? '^' : '6'; break; case SDLK_7: k->unicode = (k->mod & KMOD_SHIFT) ? '&' : '7'; break; case SDLK_8: k->unicode = (k->mod & KMOD_SHIFT) ? '*' : '8'; break; case SDLK_9: k->unicode = (k->mod & KMOD_SHIFT) ? '(' : '9'; break; default: break; }; } int numeric_key_event(struct key_event *k, int kponly) { if (kponly) { switch (k->orig_sym) { case SDLK_KP0: return 0; case SDLK_KP1: return 1; case SDLK_KP2: return 2; case SDLK_KP3: return 3; case SDLK_KP4: return 4; case SDLK_KP5: return 5; case SDLK_KP6: return 6; case SDLK_KP7: return 7; case SDLK_KP8: return 8; case SDLK_KP9: return 9; default: break; }; return -1; } if (k->unicode >= '0' && k->unicode <= '9') return k->unicode - '0'; switch (k->orig_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; default: break; }; 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 VOLFX_NONE: buf[0] = buf[1] = 173; break; case VOLFX_VOLUME: case VOLFX_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 ... yeah, no kidding. /storlek */ #if 0 buf[0] = 185; buf[1] = 186; buf[2] = 185; #else buf[0] = buf[1] = buf[2] = 126; #endif 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(void) { 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_99(struct key_event *k) { int c; if (!NO_CAM_MODS(k->mod)) return -1; c = tolower(k->unicode ?: k->sym); if (c >= 'h' && c <= 'z') return 10 + c - 'h'; return kbd_char_to_hex(k); } int kbd_char_to_hex(struct key_event *k) { if (!NO_CAM_MODS(k->mod)) return -1; if (k->unicode == '0') return 0; if (k->unicode == '1') return 1; if (k->unicode == '2') return 2; if (k->unicode == '3') return 3; if (k->unicode == '4') return 4; if (k->unicode == '5') return 5; if (k->unicode == '6') return 6; if (k->unicode == '7') return 7; if (k->unicode == '8') return 8; if (k->unicode == '9') return 9; if (k->unicode == 'a' || k->unicode == 'A') return 10; if (k->unicode == 'b' || k->unicode == 'B') return 11; if (k->unicode == 'c' || k->unicode == 'C') return 12; if (k->unicode == 'd' || k->unicode == 'D') return 13; if (k->unicode == 'e' || k->unicode == 'E') return 14; if (k->unicode == 'f' || k->unicode == 'F') return 15; 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; if (k->orig_sym == SDLK_KP_PERIOD && k->sym == SDLK_PERIOD) { /* lots of systems map an outside scancode for these; * we may need to simply ignore scancodes > 256 * but i want a narrow change for this for now * until it is certain we need more... */ return 0; } switch (key_scancode_lookup(k->scancode, k->sym)) { case SDLK_BACKQUOTE: if (k->mod & KMOD_SHIFT) return NOTE_FADE; case SDLK_HASH: /* for delt */ 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); } int kbd_get_alnum(struct key_event *k) { if (k->sym >= 127) return 0; if (k->mod & KMOD_SHIFT) { const char shifted_digits[] = ")!@#$%^&*("; // comical profanity switch (k->sym) { case 'a'...'z': return toupper(k->sym); case '0'...'9': return shifted_digits[k->sym - '0']; case '[': return '{'; case ']': return '}'; case ';': return ':'; case '=': return '+'; case '-': return '_'; case '`': return '~'; case ',': return '<'; case '.': return '>'; case '/': return '?'; case '\\': return '|'; case '\'': return '"'; default: return k->sym; // shift + some weird key = ??? } } return k->sym; } /* --------------------------------------------------------------------- */ static int keydelay = SDL_DEFAULT_REPEAT_DELAY, keyrate = SDL_DEFAULT_REPEAT_INTERVAL; void set_key_repeat(int delay, int rate) { /* save these for later */ if (delay) { keydelay = delay; keyrate = rate; } SDL_EnableKeyRepeat(keydelay, keyrate); } schismtracker-20180209/schism/main.c000066400000000000000000000712511323741476300172210ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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. :) */ /* Warning: spaghetti follows.... it just kind of ended up this way. In other news, TODO: clean this mess on a rainy day. and yes, that in fact rhymed :P */ #define NEED_TIME #include "headers.h" #include "event.h" #include "clippy.h" #include "disko.h" #include "version.h" #include "song.h" #include "midi.h" #include "dmoz.h" #include "osdefs.h" #include #include "sdlmain.h" #include #include #include #include #ifndef WIN32 # include #endif #include #if !defined(__amigaos4__) && !defined(GEKKO) # define ENABLE_HOOKS 1 #endif #define NATIVE_SCREEN_WIDTH 640 #define NATIVE_SCREEN_HEIGHT 400 /* --------------------------------------------------------------------- */ /* globals */ enum { EXIT_HOOK = 1, EXIT_SAVECFG = 4, EXIT_SDLQUIT = 16, }; static int shutdown_process = 0; static const char *video_driver = NULL; static const char *audio_driver = NULL; static int did_fullscreen = 0; static int did_classic = 0; /* --------------------------------------------------------------------- */ static void display_print_info(void) { log_append(2, 0, "Video initialised"); log_underline(17); video_report(); } /* 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 sdl_init(void) { char *err; if (SDL_Init(SDL_INIT_FLAGS) == 0) return; err = SDL_GetError(); if (strstr(err, "mouse")) { // see if we can start up mouseless status.flags |= NO_MOUSE; put_env_var("SDL_NOMOUSE", "1"); if (SDL_Init(SDL_INIT_FLAGS) == 0) return; } fprintf(stderr, "SDL_Init: %s\n", err); exit(1); } static void display_init(void) { video_startup(); if (SDL_GetVideoInfo()->wm_available) { status.flags |= WM_AVAILABLE; } clippy_init(); display_print_info(); set_key_repeat(0, 0); /* 0 = defaults */ 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(MOUSE_RESET_STATE); } else { status.flags &= ~IS_VISIBLE; } } if (a->state & SDL_APPINPUTFOCUS) { if (a->gain) { status.flags |= IS_FOCUSED; video_mousecursor(MOUSE_RESET_STATE); } else { status.flags &= ~IS_FOCUSED; SDL_ShowCursor(SDL_ENABLE); } } } /* --------------------------------------------------------------------- */ #if ENABLE_HOOKS static void run_startup_hook(void) { run_hook(cfg_dir_dotschism, "startup-hook", NULL); } static void run_disko_complete_hook(void) { run_hook(cfg_dir_dotschism, "diskwriter-hook", NULL); } static void run_exit_hook(void) { run_hook(cfg_dir_dotschism, "exit-hook", NULL); } #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 #ifdef MACOSX static int ibook_helper = -1; #endif /* initial module directory */ static char *initial_dir = NULL; /* diskwrite? */ static char *diskwrite_to = 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, SF_CLASSIC = 8, SF_NETWORK = 16, }; static int startup_flags = SF_HOOKS | SF_NETWORK; /* getopt ids */ #define SHORT_OPTIONS "a:v:fFpPh" // these should correspond to the characters below (trailing colon indicates argument) enum { // short options O_SDL_AUDIODRIVER = 'a', O_SDL_VIDEODRIVER = 'v', O_FULLSCREEN = 'f', O_NO_FULLSCREEN = 'F', O_PLAY = 'p', O_NO_PLAY = 'P', O_HELP = 'h', // ids for long options with no corresponding short option O_VIDEO_YUVLAYOUT = 256, O_VIDEO_RESOLUTION, O_VIDEO_STRETCH, O_NO_VIDEO_STRETCH, #if USE_OPENGL O_VIDEO_GLPATH, #endif O_VIDEO_DEPTH, #if HAVE_SYS_KD_H O_VIDEO_FBDEV, #endif #if USE_NETWORK O_NETWORK, O_NO_NETWORK, #endif #ifdef USE_X11 O_DISPLAY, #endif O_CLASSIC_MODE, O_NO_CLASSIC_MODE, O_FONTEDIT, O_NO_FONTEDIT, #if ENABLE_HOOKS O_HOOKS, O_NO_HOOKS, #endif O_DISKWRITE, O_DEBUG, O_VERSION, }; #if defined(WIN32) # define OPENGL_PATH "\\path\\to\\opengl.dll" #elif defined(MACOSX) # define OPENGL_PATH "/path/to/opengl.dylib" #else # define OPENGL_PATH "/path/to/opengl.so" #endif #define USAGE "Usage: %s [OPTIONS] [DIRECTORY] [FILE]\n" // Remember to update the manpage when changing the command-line options! static void parse_options(int argc, char **argv) { struct option long_options[] = { {"audio-driver", 1, NULL, O_SDL_AUDIODRIVER}, {"video-driver", 1, NULL, O_SDL_VIDEODRIVER}, {"video-yuvlayout", 1, NULL, O_VIDEO_YUVLAYOUT}, {"video-size", 1, NULL, O_VIDEO_RESOLUTION}, {"video-stretch", 0, NULL, O_VIDEO_STRETCH}, {"no-video-stretch", 0, NULL, O_NO_VIDEO_STRETCH}, #if USE_OPENGL {"video-gl-path", 1, NULL, O_VIDEO_GLPATH}, #endif {"video-depth", 1, NULL, O_VIDEO_DEPTH}, #if HAVE_SYS_KD_H {"video-fb-device", 1, NULL, O_VIDEO_FBDEV}, #endif #if USE_NETWORK {"network", 0, NULL, O_NETWORK}, {"no-network", 0, NULL, O_NO_NETWORK}, #endif {"classic", 0, NULL, O_CLASSIC_MODE}, {"no-classic", 0, NULL, O_NO_CLASSIC_MODE}, #ifdef USE_X11 {"display", 1, NULL, O_DISPLAY}, #endif {"fullscreen", 0, NULL, O_FULLSCREEN}, {"no-fullscreen", 0, NULL, O_NO_FULLSCREEN}, {"play", 0, NULL, O_PLAY}, {"no-play", 0, NULL, O_NO_PLAY}, {"diskwrite", 1, NULL, O_DISKWRITE}, {"font-editor", 0, NULL, O_FONTEDIT}, {"no-font-editor", 0, NULL, O_NO_FONTEDIT}, #if ENABLE_HOOKS {"hooks", 0, NULL, O_HOOKS}, {"no-hooks", 0, NULL, O_NO_HOOKS}, #endif {"debug", 1, NULL, O_DEBUG}, {"version", 0, NULL, O_VERSION}, {"help", 0, NULL, O_HELP}, {NULL, 0, NULL, 0}, }; int opt; while ((opt = getopt_long(argc, argv, SHORT_OPTIONS, long_options, NULL)) != -1) { switch (opt) { case O_SDL_AUDIODRIVER: audio_driver = str_dup(optarg); break; case O_SDL_VIDEODRIVER: video_driver = str_dup(optarg); break; // FIXME remove all these env vars, and put these things into a global struct or something instead case O_VIDEO_YUVLAYOUT: put_env_var("SCHISM_YUVLAYOUT", optarg); break; case O_VIDEO_RESOLUTION: put_env_var("SCHISM_VIDEO_RESOLUTION", optarg); break; case O_VIDEO_STRETCH: put_env_var("SCHISM_VIDEO_ASPECT", "full"); break; case O_NO_VIDEO_STRETCH: put_env_var("SCHISM_VIDEO_ASPECT", "fixed"); break; #if USE_OPENGL case O_VIDEO_GLPATH: put_env_var("SDL_VIDEO_GL_DRIVER", optarg); break; #endif case O_VIDEO_DEPTH: put_env_var("SCHISM_VIDEO_DEPTH", optarg); break; #if HAVE_SYS_KD_H case O_VIDEO_FBDEV: put_env_var("SDL_FBDEV", optarg); break; #endif #if USE_NETWORK case O_NETWORK: startup_flags |= SF_NETWORK; break; case O_NO_NETWORK: startup_flags &= ~SF_NETWORK; break; #endif case O_CLASSIC_MODE: startup_flags |= SF_CLASSIC; did_classic = 1; break; case O_NO_CLASSIC_MODE: startup_flags &= ~SF_CLASSIC; did_classic = 1; break; case O_DEBUG: put_env_var("SCHISM_DEBUG", optarg); break; #ifdef USE_X11 case O_DISPLAY: put_env_var("DISPLAY", optarg); break; #endif case O_FULLSCREEN: video_fullscreen(1); did_fullscreen = 1; break; case O_NO_FULLSCREEN: video_fullscreen(0); did_fullscreen = 1; break; case O_PLAY: startup_flags |= SF_PLAY; break; case O_NO_PLAY: startup_flags &= ~SF_PLAY; break; case O_FONTEDIT: startup_flags |= SF_FONTEDIT; break; case O_NO_FONTEDIT: startup_flags &= ~SF_FONTEDIT; break; case O_DISKWRITE: diskwrite_to = optarg; break; #if ENABLE_HOOKS case O_HOOKS: startup_flags |= SF_HOOKS; break; case O_NO_HOOKS: startup_flags &= ~SF_HOOKS; break; #endif case O_VERSION: puts(schism_banner(0)); puts(ver_short_copyright); exit(0); case O_HELP: // XXX try to keep this stuff to one screen (78x20 or so) printf(USAGE, argv[0]); printf( " -a, --audio-driver=DRIVER\n" " -v, --video-driver=DRIVER\n" " --video-yuvlayout=LAYOUT\n" " --video-size=WIDTHxHEIGHT\n" " --video-stretch (--no-video-stretch)\n" #if USE_OPENGL " --video-gl-path=/path/to/opengl.so\n" #endif " --video-depth=DEPTH\n" #if HAVE_SYS_KD_H " --video-fb-device=/dev/fb0\n" #endif #if USE_NETWORK " --network (--no-network)\n" #endif " --classic (--no-classic)\n" #ifdef USE_X11 " --display=DISPLAYNAME\n" #endif " -f, --fullscreen (-F, --no-fullscreen)\n" " -p, --play (-P, --no-play)\n" " --diskwrite=FILENAME\n" " --font-editor (--no-font-editor)\n" #if ENABLE_HOOKS " --hooks (--no-hooks)\n" #endif //" --debug=OPS\n" " --version\n" " -h, --help\n" ); printf("Refer to the documentation for complete usage details.\n"); exit(0); case '?': // unknown option fprintf(stderr, USAGE, argv[0]); exit(2); default: // unhandled but known option fprintf(stderr, "how did this get here i am not good with computer\n"); exit(2); } } char *cwd = get_current_directory(); for (; optind < argc; optind++) { char *arg = argv[optind]; char *tmp = dmoz_path_concat(cwd, arg); if (!tmp) { perror(arg); continue; } char *norm = dmoz_path_normal(tmp); free(tmp); if (is_directory(arg)) { free(initial_dir); initial_dir = norm; } else { free(initial_song); initial_song = norm; } } free(cwd); } /* --------------------------------------------------------------------- */ static void check_update(void) { static unsigned long next = 0; /* is there any reason why we'd want to redraw the screen when it's not even visible? */ if ((status.flags & (NEED_UPDATE | IS_VISIBLE)) == (NEED_UPDATE | IS_VISIBLE)) { status.flags &= ~(NEED_UPDATE | SOFTWARE_MOUSE_MOVED); if ((status.flags & (IS_FOCUSED | LAZY_REDRAW)) == LAZY_REDRAW) { if (SDL_GetTicks() < next) return; next = SDL_GetTicks() + 500; } else if (status.flags & (DISKWRITER_ACTIVE | DISKWRITER_ACTIVE_PATTERN)) { if (SDL_GetTicks() < next) return; next = SDL_GetTicks() + 100; } redraw_screen(); video_refresh(); video_blit(); } else if (status.flags & SOFTWARE_MOUSE_MOVED) { video_blit(); status.flags &= ~(SOFTWARE_MOUSE_MOVED); } } static void _synthetic_paste(const char *cbptr) { struct key_event kk; int isy = 2; kk.mouse = MOUSE_NONE; for (; cbptr && *cbptr; cbptr++) { /* Win32 will have \r\n, everyone else \n */ if (*cbptr == '\r') continue; /* simulate paste */ kk.scancode = -1; kk.sym = kk.orig_sym = 0; if (*cbptr == '\n') { /* special attention to newlines */ kk.unicode = '\r'; kk.sym = SDLK_RETURN; } else { kk.unicode = *cbptr; } kk.mod = 0; kk.is_repeat = 0; if (cbptr[1]) kk.is_synthetic = isy; else kk.is_synthetic = 3; kk.state = KEY_PRESS; handle_key(&kk); kk.state = KEY_RELEASE; handle_key(&kk); isy = 1; } } static void _do_clipboard_paste_op(SDL_Event *e) { if (ACTIVE_PAGE.clipboard_paste && ACTIVE_PAGE.clipboard_paste(e->user.code, e->user.data1)) return; if (ACTIVE_WIDGET.clipboard_paste && ACTIVE_WIDGET.clipboard_paste(e->user.code, e->user.data1)) return; _synthetic_paste((const char *)e->user.data1); } static void event_loop(void) NORETURN; static void event_loop(void) { SDL_Event event; unsigned int lx = 0, ly = 0; /* last x and y position (character) */ uint32_t last_mouse_down, ticker; SDLKey last_key = 0; int modkey; time_t startdown; #ifdef USE_X11 time_t last_ss; #endif int downtrip; int sawrep; char *debug_s; int fix_numlock_key; fix_numlock_key = status.fix_numlock_setting; debug_s = getenv("SCHISM_DEBUG"); downtrip = 0; last_mouse_down = 0; startdown = 0; status.last_keysym = 0; modkey = SDL_GetModState(); #if defined(WIN32) win32_get_modkey(&modkey); #endif SDL_SetModState(modkey); #ifdef USE_X11 time(&last_ss); #endif time(&status.now); localtime_r(&status.now, &status.tmnow); while (SDL_WaitEvent(&event)) { struct key_event kk = { .midi_volume = -1, .midi_note = -1, // X/Y resolution .rx = NATIVE_SCREEN_WIDTH / 80, .ry = NATIVE_SCREEN_HEIGHT / 50, // everything else will be set to 0 }; if (!os_sdlevent(&event)) continue; sawrep = 0; if (event.type == SDL_KEYDOWN || event.type == SDL_MOUSEBUTTONDOWN) { kk.state = KEY_PRESS; } else if (event.type == SDL_KEYUP || event.type == SDL_MOUSEBUTTONUP) { kk.state = KEY_RELEASE; } if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) { if (event.key.keysym.sym == 0) { // XXX when does this happen? kk.mouse = MOUSE_NONE; kk.unicode = 0; kk.is_repeat = 0; } } 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; #if defined(WIN32) #define _ALTTRACKED_KMOD (KMOD_NUM|KMOD_CAPS) #else #define _ALTTRACKED_KMOD 0 #endif case SDL_KEYUP: case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_NUMLOCK: modkey ^= KMOD_NUM; break; case SDLK_CAPSLOCK: if (event.type == SDL_KEYDOWN) { status.flags |= CAPS_PRESSED; } else { status.flags &= ~CAPS_PRESSED; } modkey ^= KMOD_CAPS; break; case SDLK_LSHIFT: case SDLK_RSHIFT: if (event.type == SDL_KEYDOWN) status.flags |= SHIFT_KEY_DOWN; else status.flags &= ~SHIFT_KEY_DOWN; break; default: break; }; if (kk.state == KEY_PRESS) { modkey = (event.key.keysym.mod & ~(_ALTTRACKED_KMOD)) | (modkey & _ALTTRACKED_KMOD); } #if defined(WIN32) win32_get_modkey(&modkey); #endif kk.sym = event.key.keysym.sym; kk.scancode = event.key.keysym.scancode; switch (fix_numlock_key) { case NUMLOCK_GUESS: #ifdef MACOSX // FIXME can this be moved to macosx_sdlevent? if (ibook_helper != -1) { if (ACTIVE_PAGE.selected_widget > -1 && ACTIVE_PAGE.selected_widget < ACTIVE_PAGE.total_widgets && ACTIVE_PAGE.widgets[ACTIVE_PAGE.selected_widget].accept_text) { /* text is more likely? */ modkey |= KMOD_NUM; } else { modkey &= ~KMOD_NUM; } } /* otherwise honor it */ #endif break; case NUMLOCK_ALWAYS_OFF: modkey &= ~KMOD_NUM; break; case NUMLOCK_ALWAYS_ON: modkey |= KMOD_NUM; break; }; kk.mod = modkey; kk.unicode = event.key.keysym.unicode; kk.mouse = MOUSE_NONE; if (debug_s && strstr(debug_s, "key")) { log_appendf(12, "[DEBUG] Key%s sym=%d scancode=%d", (event.type == SDL_KEYDOWN) ? "Down" : "Up", (int)event.key.keysym.sym, (int)event.key.keysym.scancode); } key_translate(&kk); if (debug_s && strstr(debug_s, "translate") && kk.orig_sym != kk.sym) { log_appendf(12, "[DEBUG] Translate Key%s sym=%d scancode=%d -> %d (%c)", (event.type == SDL_KEYDOWN) ? "Down" : "Up", (int)event.key.keysym.sym, (int)event.key.keysym.scancode, kk.sym, kk.unicode); } 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: /* reset this... */ modkey = SDL_GetModState(); #if defined(WIN32) win32_get_modkey(&modkey); #endif SDL_SetModState(modkey); handle_active_event(&(event.active)); break; case SDL_MOUSEMOTION: case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: if (kk.state == KEY_PRESS) { modkey = event.key.keysym.mod; #if defined(WIN32) win32_get_modkey(&modkey); #endif } 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; if (event.type != SDL_MOUSEMOTION && debug_s && strstr(debug_s, "mouse")) { log_appendf(12, "[DEBUG] Mouse%s button=%d x=%d y=%d", (event.type == SDL_MOUSEBUTTONDOWN) ? "Down" : "Up", (int)event.button.button, (int)event.button.x, (int)event.button.y); } switch (event.button.button) { /* Why only one of these would be defined I have no clue. Also why these would not be defined, I'm not sure either, but hey. */ #if defined(SDL_BUTTON_WHEELUP) && defined(SDL_BUTTON_WHEELDOWN) case SDL_BUTTON_WHEELUP: kk.mouse = MOUSE_SCROLL_UP; handle_key(&kk); break; 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 == KEY_RELEASE) { ticker = SDL_GetTicks(); if (lx == kk.x && ly == kk.y && (ticker - last_mouse_down) < 300) { last_mouse_down = 0; kk.mouse = MOUSE_DBLCLICK; } else { last_mouse_down = ticker; kk.mouse = MOUSE_CLICK; } lx = kk.x; ly = kk.y; } else { kk.mouse = MOUSE_CLICK; } if (status.dialog_type == DIALOG_NONE) { if (kk.y <= 9 && status.current_page != PAGE_FONT_EDIT) { if (kk.state == KEY_RELEASE && kk.mouse_button == MOUSE_BUTTON_RIGHT) { menu_show(); break; } else if (kk.state == KEY_PRESS && 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) { midi_engine_handle_event(&event); } else if (event.type == SCHISM_EVENT_UPDATE_IPMIDI) { status.flags |= (NEED_UPDATE); midi_engine_poll_ports(); } else if (event.type == SCHISM_EVENT_PLAYBACK) { /* this is the sound thread */ midi_send_flush(); if (!(status.flags & (DISKWRITER_ACTIVE|DISKWRITER_ACTIVE_PATTERN))) { playback_update(); } } else if (event.type == SCHISM_EVENT_PASTE) { /* handle clipboard events */ _do_clipboard_paste_op(&event); free(event.user.data1); } else if (event.type == SCHISM_EVENT_NATIVE) { /* used by native system scripting */ switch (event.user.code) { case SCHISM_EVENT_NATIVE_OPEN: /* open song */ song_load(event.user.data1); break; case SCHISM_EVENT_NATIVE_SCRIPT: /* TODO: hash the string's value and do a switch() on it */ 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, "export_song") == 0) { set_page(PAGE_EXPORT_MODULE); } else if (strcasecmp(event.user.data1, "logviewer") == 0) { set_page(PAGE_LOG); } else if (strcasecmp(event.user.data1, "font_editor") == 0) { set_page(PAGE_FONT_EDIT); } 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, "system_config") == 0) { set_page(PAGE_CONFIG); } 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 (sawrep || !SDL_PollEvent(NULL)) { time(&status.now); localtime_r(&status.now, &status.tmnow); if (status.dialog_type == DIALOG_NONE && startdown && (status.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); } check_update(); switch (song_get_mode()) { case MODE_PLAYING: case MODE_PATTERN_LOOP: #ifdef os_screensaver_deactivate if ((status.now-last_ss) > 14) { last_ss=status.now; os_screensaver_deactivate(); } #endif break; default: break; }; if (status.flags & DISKWRITER_ACTIVE) { int q = disko_sync(); while (q == DW_SYNC_MORE && !SDL_PollEvent(NULL)) { check_update(); q = disko_sync(); } if (q == DW_SYNC_DONE) { #ifdef ENABLE_HOOKS run_disko_complete_hook(); #endif if (diskwrite_to) { printf("Diskwrite complete, exiting...\n"); exit(0); } } } /* let dmoz build directory lists, etc as long as there's no user-event going on... */ while (!(status.flags & NEED_UPDATE) && dmoz_worker() && !SDL_PollEvent(NULL)) /* nothing */; } } exit(0); /* atexit :) */ } static void schism_shutdown(void) { #if ENABLE_HOOKS if (shutdown_process & EXIT_HOOK) run_exit_hook(); #endif if (shutdown_process & EXIT_SAVECFG) cfg_atexit_save(); #ifdef MACOSX if (ibook_helper != -1) macosx_ibook_fnswitch(ibook_helper); #endif if (shutdown_process & EXIT_SDLQUIT) { song_lock_audio(); song_stop_unlocked(1); song_unlock_audio(); // Clear to black on exit (nicer on Wii; I suppose it won't hurt elsewhere) video_refresh(); video_blit(); video_shutdown(); /* If this is the atexit() handler, why are we calling SDL_Quit? Simple, SDL's documentation says always call SDL_Quit. :) In fact, in the examples they recommend writing atexit(SDL_Quit) directly after SDL_Init. I'm not sure exactly why, but I don't see a reason *not* to do it... / Storlek */ SDL_Quit(); } os_sysexit(); } extern void vis_init(void); int main(int argc, char **argv) { os_sysinit(&argc, &argv); /* this needs to be done very early, because the version is used in the help text etc. Also, this needs to happen before any locale stuff is initialized (but we don't do that at all yet, anyway) */ ver_init(); /* FIXME: make a config option for this, and stop abusing frickin' environment variables! */ put_env_var("SCHISM_VIDEO_ASPECT", "full"); vis_init(); atexit(schism_shutdown); video_fullscreen(0); tzset(); // localtime_r wants this srand(time(NULL)); parse_options(argc, argv); /* shouldn't this be like, first? */ #ifdef USE_DLTRICK_ALSA alsa_dlinit(); #endif cfg_init_dir(); #if ENABLE_HOOKS if (startup_flags & SF_HOOKS) { run_startup_hook(); shutdown_process |= EXIT_HOOK; } #endif #ifdef MACOSX ibook_helper = macosx_ibook_fnswitch(1); #endif /* Eh. */ log_append2(0, 3, 0, schism_banner(0)); log_nl(); log_nl(); song_initialise(); cfg_load(); if (did_classic) { status.flags &= ~CLASSIC_MODE; if (startup_flags & SF_CLASSIC) status.flags |= CLASSIC_MODE; } if (!video_driver) { video_driver = cfg_video_driver; if (!video_driver || !*video_driver) video_driver = NULL; } if (!did_fullscreen) { video_fullscreen(cfg_video_fullscreen); } if (!(startup_flags & SF_NETWORK)) { status.flags |= NO_NETWORK; } shutdown_process |= EXIT_SAVECFG; sdl_init(); shutdown_process |= EXIT_SDLQUIT; os_sdlinit(); video_setup(video_driver); display_init(); palette_apply(); font_init(); midi_engine_start(); log_nl(); audio_init(audio_driver); song_init_modplug(); #ifndef WIN32 signal(SIGINT, exit); signal(SIGQUIT, exit); signal(SIGTERM, exit); #endif video_mousecursor(cfg_video_mousecursor); status_text_flash(" "); /* silence the mouse cursor message */ volume_setup(); load_pages(); main_song_changed_cb(); if (initial_song && !initial_dir) { initial_dir = get_parent_directory(initial_song); if (!initial_dir) { initial_dir = get_current_directory(); } } if (initial_dir) { strncpy(cfg_dir_modules, initial_dir, PATH_MAX); cfg_dir_modules[PATH_MAX] = 0; strncpy(cfg_dir_samples, initial_dir, PATH_MAX); cfg_dir_samples[PATH_MAX] = 0; strncpy(cfg_dir_instruments, initial_dir, PATH_MAX); cfg_dir_instruments[PATH_MAX] = 0; free(initial_dir); } if (startup_flags & SF_FONTEDIT) { status.flags |= STARTUP_FONTEDIT; set_page(PAGE_FONT_EDIT); free(initial_song); } else if (initial_song) { set_page(PAGE_LOG); if (song_load_unchecked(initial_song)) { if (diskwrite_to) { // make a guess? const char *multi = strcasestr(diskwrite_to, "%c"); const char *driver = (strcasestr(diskwrite_to, ".aif") ? (multi ? "MAIFF" : "AIFF") : (multi ? "MWAV" : "WAV")); if (song_export(diskwrite_to, driver) != SAVE_SUCCESS) exit(1); // ? } else if (startup_flags & SF_PLAY) { song_start(); set_page(PAGE_INFO); } } free(initial_song); } else { set_page(PAGE_ABOUT); } #if HAVE_NICE if (nice(1) == -1) { } #endif /* poll once */ midi_engine_poll_ports(); event_loop(); return 0; /* blah */ } schismtracker-20180209/schism/menu.c000066400000000000000000000313371323741476300172420ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" /* --------------------------------------------------------------------- */ /* 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 { unsigned 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 */ }; static struct menu main_menu = { .x = 6, .y = 11, .w = 25, .title = " Main Menu", .num_items = 10, .items = { "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, .selected_cb = main_menu_selected_cb, }; static struct menu file_menu = { .x = 25, .y = 13, .w = 22, .title = "File Menu", .num_items = 7, .items = { "Load... (F9)", "New... (Ctrl-N)", "Save Current (Ctrl-S)", "Save As... (F10)", "Export... (Shift-F10)", "Message Log (Ctrl-F11)", "Quit (Ctrl-Q)", }, .selected_item = 0, .active_item = -1, .selected_cb = file_menu_selected_cb, }; static struct menu playback_menu = { .x = 25, .y = 13, .w = 27, .title = " Playback Menu", .num_items = 9, .items = { "Show Infopage (F5)", "Play Song (Ctrl-F5)", "Play Pattern (F6)", "Play from Order (Shift-F6)", "Play from Mark/Cursor (F7)", "Stop (F8)", "Reinit Soundcard (Ctrl-I)", "Driver Screen (Shift-F5)", "Calculate Length (Ctrl-P)", }, .selected_item = 0, .active_item = -1, .selected_cb = playback_menu_selected_cb, }; static struct menu sample_menu = { .x = 25, .y = 20, .w = 25, .title = "Sample Menu", .num_items = 2, .items = { "Sample List (F3)", "Sample Library (Ctrl-F3)", }, .selected_item = 0, .active_item = -1, .selected_cb = sample_menu_selected_cb, }; static struct menu instrument_menu = { .x = 20, .y = 23, .w = 29, .title = "Instrument Menu", .num_items = 2, .items = { "Instrument List (F4)", "Instrument Library (Ctrl-F4)", }, .selected_item = 0, .active_item = -1, .selected_cb = instrument_menu_selected_cb, }; static struct menu settings_menu = { .x = 22, .y = 25, .w = 34, .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 = 6, .items = { "Preferences (Shift-F5)", "MIDI Configuration (Shift-F1)", "System Configuration (Ctrl-F1)", "Palette Editor (Ctrl-F12)", "Font Editor (Shift-F12)", "Toggle Fullscreen (Ctrl-Alt-Enter)", }, .selected_item = 0, .active_item = -1, .selected_cb = 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(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(menu->title, menu->x + 6, menu->y + 2, 3, 2); } void menu_draw(void) { ENSURE_MENU(return); _draw_menu(current_menu[0]); if (current_menu[1]) _draw_menu(current_menu[1]); } /* --------------------------------------------------------------------- */ void menu_show(void) { dialog_destroy_all(); status.dialog_type = DIALOG_MAIN_MENU; current_menu[0] = &main_menu; status.flags |= NEED_UPDATE; } 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 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 */ /* fudge the menu to show/hide the fullscreen toggle as appropriate */ if (status.flags & WM_AVAILABLE) settings_menu.num_items = 6; else settings_menu.num_items = 5; 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: /* export ... */ set_page(PAGE_EXPORT_MODULE); break; case 5: /* message log */ set_page(PAGE_LOG); break; case 6: /* 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: /* reinit soundcard */ audio_reinit(); break; case 7: /* driver screen */ set_page(PAGE_PREFERENCES); return; case 8: /* 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); break; case 1: /* sample library */ set_page(PAGE_LIBRARY_SAMPLE); break; } } static void instrument_menu_selected_cb(void) { switch (instrument_menu.selected_item) { case 0: /* instrument list */ set_page(PAGE_INSTRUMENT_LIST); break; case 1: /* instrument library */ set_page(PAGE_LIBRARY_INSTRUMENT); break; } } 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: /* config */ set_page(PAGE_CONFIG); return; case 3: /* palette configuration */ set_page(PAGE_PALETTE_EDITOR); return; case 4: /* font editor */ set_page(PAGE_FONT_EDIT); return; case 5: /* 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 || k->mouse == MOUSE_DBLCLICK) { h = menu->num_items * 3; if (k->x >= menu->x + 2 && k->x <= menu->x + menu->w + 5 && k->y >= menu->y + 4 && k->y <= menu->y + h + 4) { n = ((k->y - 4) - menu->y) / 3; if (n >= 0 && n < menu->num_items) { menu->selected_item = n; if (k->state == KEY_RELEASE) { menu->active_item = -1; menu->selected_cb(); } else { status.flags |= NEED_UPDATE; menu->active_item = n; } } } else if (k->state == KEY_RELEASE && (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 == KEY_RELEASE) 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 == KEY_RELEASE) return 1; if (menu->selected_item > 0) { menu->selected_item--; break; } return 1; case SDLK_DOWN: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) return 1; menu->selected_item = 0; break; case SDLK_END: if (k->state == KEY_RELEASE) return 1; menu->selected_item = menu->num_items - 1; break; case SDLK_RETURN: if (k->state == KEY_PRESS) { menu->active_item = menu->selected_item; status.flags |= NEED_UPDATE; return 1; } menu->selected_cb(); return 1; default: return 1; } status.flags |= NEED_UPDATE; return 1; } schismtracker-20180209/schism/midi-core.c000066400000000000000000000601071323741476300201430ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "event.h" #include "util.h" #include "midi.h" #include "song.h" #include "sdlmain.h" #include "page.h" #include "it.h" #include "dmoz.h" #include #include 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 = NULL; static SDL_mutex *midi_port_mutex = NULL; static SDL_mutex *midi_record_mutex = NULL; static SDL_mutex *midi_play_mutex = NULL; static SDL_cond *midi_play_cond = NULL; static struct midi_provider *port_providers = NULL; /* all this just for usleep?! (maybe we should have sys/win32/usleep.c) */ #ifdef WIN32 #include static void (*__win32_usleep)(unsigned int usec) = NULL; static void __win32_old_usleep(unsigned int u) { /* bah, only Win95 and "earlier" actually needs this... */ SleepEx(u/1000,FALSE); } static FARPROC __ihatewindows_f1 = NULL; static FARPROC __ihatewindows_f2 = NULL; static FARPROC __ihatewindows_f3 = NULL; static HANDLE __midi_timer = NULL; 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_PITCHBEND; 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) static void _cfg_load_midi_part_locked(struct midi_port *q) { struct cfg_section *c; /*struct midi_provider *p;*/ cfg_file_t cfg; const char *sn; char *ptr, *ss, *sp; char buf[256]; int j; ss = q->name; if (!ss) return; while (isspace(*ss)) ss++; if (!*ss) return; sp = q->provider ? q->provider->name : NULL; if (sp) { while (isspace(*sp)) sp++; if (!*sp) sp = NULL; } ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); cfg_init(&cfg, ptr); /* look for MIDI port sections */ for (c = cfg.sections; c; c = c->next) { j = -1; sscanf(c->name, "MIDI Port %d", &j); if (j < 1) continue; sn = cfg_get_string(&cfg, c->name, "name", buf, 255, NULL); if (!sn) continue; if (strcasecmp(ss, sn) != 0) continue; sn = cfg_get_string(&cfg, c->name, "provider", buf, 255, NULL); if (sn && sp && strcasecmp(sp, sn) != 0) continue; /* okay found port */ if ((q->iocap & MIDI_INPUT) && cfg_get_number(&cfg, c->name, "input", 0)) { q->io |= MIDI_INPUT; } if ((q->iocap & MIDI_OUTPUT) && cfg_get_number(&cfg, c->name, "output", 0)) { q->io |= MIDI_OUTPUT; } if (q->io && q->enable) q->enable(q); } cfg_free(&cfg); free(ptr); } void cfg_load_midi(cfg_file_t *cfg) { midi_config_t *md, *mc; char buf[17], buf2[33]; int i; CFG_GET_MI(flags, MIDI_TICK_QUANTIZE | MIDI_RECORD_NOTEOFF | MIDI_RECORD_VELOCITY | MIDI_RECORD_AFTERTOUCH | MIDI_PITCHBEND); CFG_GET_MI(pitch_depth, 12); CFG_GET_MI(amplification, 100); CFG_GET_MI(c5note, 60); song_lock_audio(); md = &default_midi_config; cfg_get_string(cfg,"MIDI","start", md->start, 31, "FF"); cfg_get_string(cfg,"MIDI","stop", md->stop, 31, "FC"); cfg_get_string(cfg,"MIDI","tick", md->tick, 31, ""); cfg_get_string(cfg,"MIDI","note_on", md->note_on, 31, "9c n v"); cfg_get_string(cfg,"MIDI","note_off", md->note_off, 31, "9c n 0"); cfg_get_string(cfg,"MIDI","set_volume", md->set_volume, 31, ""); cfg_get_string(cfg,"MIDI","set_panning", md->set_panning, 31, ""); cfg_get_string(cfg,"MIDI","set_bank", md->set_bank, 31, ""); cfg_get_string(cfg,"MIDI","set_program", md->set_program, 31, "Cc p"); for (i = 0; i < 16; i++) { snprintf(buf, 16, "SF%X", i); cfg_get_string(cfg, "MIDI", buf, md->sfx[i], 31, i == 0 ? "F0F000z" : ""); } for (i = 0; i < 128; i++) { snprintf(buf, 16, "Z%02X", i + 0x80); if (i < 16) snprintf(buf2, 32, "F0F001%02x", i * 8); else buf2[0] = '\0'; cfg_get_string(cfg, "MIDI", buf, md->zxx[i], 31, buf2); } mc = ¤t_song->midi_config; memcpy(mc, md, sizeof(midi_config_t)); song_unlock_audio(); } #define CFG_SET_MI(v) cfg_set_number(cfg, "MIDI", #v, midi_ ## v) void cfg_save_midi(cfg_file_t *cfg) { struct cfg_section *c; struct midi_provider *p; struct midi_port *q; midi_config_t *md, *mc; char buf[33]; char *ss; int i, j; CFG_SET_MI(flags); CFG_SET_MI(pitch_depth); CFG_SET_MI(amplification); CFG_SET_MI(c5note); song_lock_audio(); md = &default_midi_config; /* overwrite default */ mc = ¤t_song->midi_config; memcpy(md, mc, sizeof(midi_config_t)); cfg_set_string(cfg,"MIDI","start", md->start); cfg_set_string(cfg,"MIDI","stop", md->stop); cfg_set_string(cfg,"MIDI","tick", md->tick); cfg_set_string(cfg,"MIDI","note_on", md->note_on); cfg_set_string(cfg,"MIDI","note_off", md->note_off); cfg_set_string(cfg,"MIDI","set_volume", md->set_volume); cfg_set_string(cfg,"MIDI","set_panning", md->set_panning); cfg_set_string(cfg,"MIDI","set_bank", md->set_bank); cfg_set_string(cfg,"MIDI","set_program", md->set_program); for (i = 0; i < 16; i++) { snprintf(buf, 32, "SF%X", i); cfg_set_string(cfg, "MIDI", buf, md->sfx[i]); } for (i = 0; i < 128; i++) { snprintf(buf, 32, "Z%02X", i + 0x80); cfg_set_string(cfg, "MIDI", buf, md->zxx[i]); } song_unlock_audio(); /* write out only enabled midi ports */ i = 1; SDL_mutexP(midi_mutex); q = NULL; for (p = port_providers; p; p = p->next) { while (midi_port_foreach(p, &q)) { ss = q->name; if (!ss) continue; while (isspace(*ss)) ss++; if (!*ss) continue; if (!q->io) continue; snprintf(buf, 32, "MIDI Port %d", i); i++; cfg_set_string(cfg, buf, "name", ss); ss = p->name; if (ss) { while (isspace(*ss)) ss++; if (*ss) { cfg_set_string(cfg, buf, "provider", ss); } } cfg_set_number(cfg, buf, "input", q->io & MIDI_INPUT ? 1 : 0); cfg_set_number(cfg, buf, "output", q->io & MIDI_OUTPUT ? 1 : 0); } } //TODO: Save number of MIDI-IP ports SDL_mutexV(midi_mutex); /* delete other MIDI port sections */ for (c = cfg->sections; c; c = c->next) { j = -1; sscanf(c->name, "MIDI Port %d", &j); if (j < i) continue; c->omit = 1; } } 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 WIN32 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) return 1; midi_mutex = SDL_CreateMutex(); midi_record_mutex = SDL_CreateMutex(); midi_play_mutex = SDL_CreateMutex(); midi_port_mutex = SDL_CreateMutex(); midi_play_cond = SDL_CreateCond(); if (!(midi_mutex && midi_record_mutex && midi_play_mutex && midi_port_mutex && midi_play_cond)) { if (midi_mutex) SDL_DestroyMutex(midi_mutex); if (midi_record_mutex) SDL_DestroyMutex(midi_record_mutex); if (midi_play_mutex) SDL_DestroyMutex(midi_play_mutex); if (midi_port_mutex) SDL_DestroyMutex(midi_port_mutex); if (midi_play_cond) SDL_DestroyCond(midi_play_cond); midi_mutex = midi_record_mutex = midi_play_mutex = midi_port_mutex = NULL; midi_play_cond = NULL; return 0; } _midi_engine_connect(); _connected = 1; return 1; } void midi_engine_reset(void) { if (!_connected) return; 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 = NULL; while (midi_port_foreach(p, &q)) { midi_port_unregister(q->num); } if (n->thread) { SDL_KillThread(n->thread); } free(n->name); free(n); n = p; } _connected = 0; SDL_mutexV(midi_mutex); } /* PORT system */ static struct midi_port **port_top = NULL; 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 = NULL; if (!midi_port_mutex) return NULL; 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 NULL; n = mem_calloc(1, sizeof(struct midi_provider)); n->name = str_dup(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->drain = driver->drain; } else { n->send_now = driver->send; } SDL_mutexP(midi_mutex); n->next = port_providers; port_providers = n; if (driver->thread) { // FIXME this cast is stupid n->thread = SDL_CreateThread((int (*)(void*))driver->thread, n); } 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 = str_dup(name); p->enable = pv->enable; p->disable = pv->disable; p->send_later = pv->send_later; p->send_now = pv->send_now; p->drain = pv->drain; p->free_userdata = free_userdata; p->userdata = userdata; p->provider = pv; for (i = 0; i < port_alloc; i++) { if (port_top[i] == NULL) { port_top[i] = p; p->num = i; port_count++; _cfg_load_midi_part_locked(p); 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] = NULL; port_top = pt; p->num = port_count; port_count++; /* finally, and just before unlocking, load any configuration for it... */ _cfg_load_midi_part_locked(p); 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); do { if (!*cursor) { i = 0; } else { i = ((*cursor)->num) + 1; while (i < port_alloc && !port_top[i]) i++; } if (i >= port_alloc) { *cursor = NULL; SDL_mutexV(midi_port_mutex); return 0; } *cursor = port_top[i]; } while (p && (*cursor)->provider != p); SDL_mutexV(midi_port_mutex); return 1; } static int _midi_send_unlocked(const unsigned char *data, unsigned int len, unsigned int delay, int from) { struct midi_port *ptr = NULL; int need_timer = 0; #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 */ 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 */ 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 */ while (midi_port_foreach(NULL, &ptr)) { if ((ptr->io & MIDI_OUTPUT)) { if (ptr->send_later) ptr->send_later(ptr, data, len, delay); else if (ptr->send_now) need_timer = 1; } } } return need_timer; } void midi_send_now(const 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); } /*----------------------------------------------------------------------------------*/ /* okay, our local queue is a little confusing, * but it works on the premise that the people who need it (oss) * also have systems with high-speed context switches. * * this sucks for freebsd/386 users as they've got a high-latency * audio system (oss) and slow context switches (x86). someone should * port alsa to freebsd. maybe someone else will solve this problem, * or maybe the computers that are slow enough to matter simply won't * be bothered. * * midi, that is, real midi, is 31250bps. that's 3125 bits per msec, * or 391 bytes per msec. i use a fixed buffer here because access needs * to be fast, and attempting to handle more will simply help people * using software/only setups. * * really, software-only midi, without kernel assistance sucks. */ struct qent { int used; unsigned char b[391]; }; static struct qent *qq = NULL; static int midims, ms10s, qlen; void midi_queue_alloc(int my_audio_buffer_samples, int sample_size, int samples_per_second) { int buffer_size = (my_audio_buffer_samples * sample_size); if (qq) { free(qq); qq = NULL; } /* how long is the audio buffer in 10 msec? * well, (sample_size*samples_per_second)/80 is the number of bytes per msec */ midims = sample_size * samples_per_second; if ((midims % 80) != 0) midims += (80 - (midims % 80)); ms10s = midims / 80; midims /= 8; if (ms10s > buffer_size) { /* okay, there's not even 10msec of audio data; midi queueing will be impossible */ qlen = 0; return; } if ((buffer_size % ms10s) != 0) { buffer_size += (ms10s - (buffer_size % ms10s)); } qlen = buffer_size / ms10s; /* now qlen is the number of msec in digital output buffer */ qq = mem_calloc(qlen, sizeof(struct qent)); } static SDL_Thread *midi_queue_thread = NULL; static int _midi_queue_run(UNUSED void *xtop) { int i; #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); for (;;) { SDL_CondWait(midi_play_cond, midi_play_mutex); for (i = 0; i < qlen; i++) { SDL_mutexP(midi_record_mutex); _midi_send_unlocked(qq[i].b, qq[i].used, 0, 1); SDL_mutexV(midi_record_mutex); SLEEP_FUNC(10000); /* 10msec */ qq[i].used = 0; } } return 0; /* never happens */ } int midi_need_flush(void) { struct midi_port *ptr; int need_explicit_flush = 0; int i; if (!midi_record_mutex || !midi_play_mutex) return 0; ptr = NULL; while (midi_port_foreach(NULL, &ptr)) { if ((ptr->io & MIDI_OUTPUT)) { if (!ptr->drain && ptr->send_now) need_explicit_flush = 1; } } if (!need_explicit_flush) return 0; for (i = 0; i < qlen; i++) { if (qq[i].used) return 1; } return 0; } void midi_send_flush(void) { struct midi_port *ptr = NULL; int need_explicit_flush = 0; if (!midi_record_mutex || !midi_play_mutex) return; while (midi_port_foreach(NULL, &ptr)) { if ((ptr->io & MIDI_OUTPUT)) { if (ptr->drain) ptr->drain(ptr); else if (ptr->send_now) need_explicit_flush=1; } } /* no need for midi sync huzzah; driver does it for us... */ if (!need_explicit_flush) return; if (!midi_queue_thread) { midi_queue_thread = SDL_CreateThread(_midi_queue_run, NULL); if (midi_queue_thread) { log_appendf(3, "Started MIDI queue thread"); } else { log_appendf(2, "ACK: Couldn't start MIDI thread; things are likely going to go boom!"); } } SDL_mutexP(midi_play_mutex); SDL_CondSignal(midi_play_cond); SDL_mutexV(midi_play_mutex); } void midi_send_buffer(const unsigned char *data, unsigned int len, unsigned int pos) { if (!midi_record_mutex) return; SDL_mutexP(midi_record_mutex); /* just for fun... */ if (status.current_page == PAGE_MIDI) { 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 = NULL; time(&status.last_midi_time); status.flags |= NEED_UPDATE; } /* pos is still in miliseconds */ if (midims != 0 && _midi_send_unlocked(data, len, pos/midims, 2)) { /* grr, we need a timer */ /* calculate pos in buffer */ pos /= ms10s; assert(((unsigned)pos) < ((unsigned)qlen)); if ((len + qq[pos].used) > sizeof(qq[pos].b)) { len = sizeof(qq[pos].b) - qq[pos].used; /* okay, we're going to lose data here */ } memcpy(qq[pos].b+qq[pos].used, data, len); qq[pos].used += len; } 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] = NULL; 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; default: /* something else */ midi_event_system((*data & 15), (data[1]) | (data[2] << 8) | (data[3] << 16)); break; } } } static void midi_push_event(Uint8 code, void *data1, size_t data1_len, int alloc) { SDL_Event e = { .user = { .type = SCHISM_EVENT_MIDI, .code = code, .data1 = data1 } }; if (data1 && alloc) { e.user.data1 = mem_alloc(data1_len); memcpy(e.user.data1, data1, data1_len); } SDL_PushEvent(&e); } void midi_event_note(enum midi_note mnstatus, int channel, int note, int velocity) { int st[4] = { mnstatus, channel, note, velocity }; midi_push_event(SCHISM_EVENT_MIDI_NOTE, st, sizeof(st), 1); } void midi_event_controller(int channel, int param, int value) { int st[4] = { value, channel, param }; midi_push_event(SCHISM_EVENT_MIDI_CONTROLLER, st, sizeof(st), 1); } void midi_event_program(int channel, int value) { int st[4] = { value, channel }; midi_push_event(SCHISM_EVENT_MIDI_PROGRAM, st, sizeof(st), 1); } void midi_event_aftertouch(int channel, int value) { int st[4] = { value, channel }; midi_push_event(SCHISM_EVENT_MIDI_AFTERTOUCH, st, sizeof(st), 1); } void midi_event_pitchbend(int channel, int value) { int st[4] = { value, channel }; midi_push_event(SCHISM_EVENT_MIDI_PITCHBEND, st, sizeof(st), 1); } void midi_event_system(int argv, int param) { int st[4] = { argv, param }; midi_push_event(SCHISM_EVENT_MIDI_SYSTEM, st, sizeof(st), 1); } void midi_event_tick(void) { midi_push_event(SCHISM_EVENT_MIDI_TICK, NULL, 0, 0); } void midi_event_sysex(const unsigned char *data, unsigned int len) { size_t packet_len = len + sizeof(len); void *packet = mem_alloc(packet_len); memcpy(packet, &len, sizeof(len)); memcpy(packet + sizeof(len), data, len); midi_push_event(SCHISM_EVENT_MIDI_SYSEX, packet, packet_len, 0); } int midi_engine_handle_event(void *ev) { struct key_event kk = {.is_synthetic = 0}; int *st; SDL_Event *e = ev; if (e->type != SCHISM_EVENT_MIDI) return 0; st = e->user.data1; if (midi_flags & MIDI_DISABLE_RECORD) { free(e->user.data1); return 1; } switch (e->user.code) { case SCHISM_EVENT_MIDI_NOTE: if (st[0] == MIDI_NOTEON) { kk.state = KEY_PRESS; } else { if (!(midi_flags & MIDI_RECORD_NOTEOFF)) { /* don't record noteoff? okay... */ break; } kk.state = KEY_RELEASE; } 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; case SCHISM_EVENT_MIDI_CONTROLLER: /* controller events */ break; case SCHISM_EVENT_MIDI_SYSTEM: switch (st[0]) { case 0x8: /* MIDI tick */ break; case 0xA: /* MIDI start */ case 0xB: /* MIDI continue */ song_start(); break; case 0xC: /* MIDI stop */ case 0xF: /* MIDI reset */ /* this is helpful when miditracking */ song_stop(); break; }; case SCHISM_EVENT_MIDI_SYSEX: /* but missing the F0 and the stop byte (F7) */ //len = *((unsigned int *)e->user.data1); //sysex = ((char *)e->user.data1)+sizeof(unsigned int); break; default: break; } free(e->user.data1); return 1; } schismtracker-20180209/schism/midi-ip.c000066400000000000000000000251641323741476300176270ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "it.h" #include "util.h" #include "sdlmain.h" #include "event.h" #ifdef USE_NETWORK #ifdef WIN32 #include #include #else #include #include #include #include #include #endif #include #define DEFAULT_IP_PORT_COUNT 5 #define MIDI_IP_BASE 21928 #define MAX_DGRAM_SIZE 1280 #ifndef WIN32 static int wakeup[2]; #endif static int real_num_ports = 0; static int num_ports = 0; static int out_fd = -1; static int *port_fd = NULL; static int *state = NULL; static SDL_mutex *blocker = NULL; static void do_wake_main(void) { /* send at end */ SDL_Event e; e.user.type = SCHISM_EVENT_UPDATE_IPMIDI; e.user.code = 0; e.user.data1 = NULL; e.user.data2 = NULL; SDL_PushEvent(&e); } static void do_wake_midi(void) { #ifdef WIN32 /* anyone want to suggest how this is done? XXX */ #else if (write(wakeup[1], "\x1", 1) == 1) { /* fortify is stupid */ } #endif } static int _get_fd(int pb, int isout) { struct ip_mreq mreq = {}; struct sockaddr_in asin = {}; 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, (void*)&opt, sizeof(int)); /* don't loop back what we generate */ opt = !isout; if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&opt, sizeof(opt)) < 0) { #ifdef WIN32 closesocket(fd); #else close(fd); #endif return -1; } opt = 31; if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&opt, sizeof(opt)) < 0) { #ifdef WIN32 closesocket(fd); #else close(fd); #endif return -1; } 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, (void*)&mreq, sizeof(mreq)) < 0) { #ifdef WIN32 closesocket(fd); #else close(fd); #endif return -1; } opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&opt, sizeof(opt)) < 0) { #ifdef WIN32 closesocket(fd); #else close(fd); #endif return -1; } asin.sin_family = AF_INET; ipcopy = (unsigned char *)&asin.sin_addr; if (!isout) { /* all 0s is inaddr_any; but this is for listening */ #ifdef WIN32 //JosepMa:On my machine, using the 225.0.0.37 address caused bind to fail. //Didn't look too much to find why. ipcopy[0] = ipcopy[1] = ipcopy[2] = ipcopy[3] = 0; #else ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; #endif asin.sin_port = htons(MIDI_IP_BASE+pb); } if (bind(fd, (struct sockaddr *)&asin, sizeof(asin)) < 0) { #ifdef WIN32 int asdf = WSAGetLastError(); perror("binderror"); switch(asdf) { case WSANOTINITIALISED: perror("WSANOTINITIALISED");break; case WSAENETDOWN: perror("WSAENETDOWN");break; case WSAEFAULT: perror("WSAEFAULT");break; case WSAEINVAL: perror("WSAEINVAL");break; case WSAEINPROGRESS: perror("WSAEINPROGRESS");break; case WSAENOTSOCK: perror("WSAENOTSOCK");break; case WSAEACCES: perror("WSAEACCES");break; case WSAEADDRINUSE: perror("WSAEADDRINUSE");break; case WSAEADDRNOTAVAIL: perror("WSAEADDRNOTAVAIL");break; case WSAENOBUFS: perror("WSAENOBUFS");break; default: perror("default");break; } closesocket(fd); #else close(fd); #endif return -1; } return fd; } void ip_midi_setports(int n) { if (out_fd == -1) return; if (status.flags & NO_NETWORK) return; SDL_mutexP(blocker); num_ports = n; SDL_mutexV(blocker); do_wake_midi(); } 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 asin; unsigned slen = sizeof(asin); int r; r = recvfrom(fd, buffer, sizeof(buffer), 0, (struct sockaddr *)&asin, &slen); if (r > 0) { ptr = src = NULL; while (midi_port_foreach(p, &ptr)) { int n = INT_SHAPED_PTR(ptr->userdata); if (n == en) src = ptr; } midi_received_cb(src, buffer, r); } } static int _ip_thread(struct midi_provider *p) { #ifdef WIN32 struct timeval tv; #else static unsigned char buffer[4096]; #endif fd_set rfds; int *tmp2, *tmp; int i, m; for (;;) { SDL_mutexP(blocker); m = (volatile int)num_ports; //If no ports, wait and try again if (m > real_num_ports) { /* need more ports */ tmp = malloc(2 * (m * sizeof(int))); if (tmp) { tmp2 = tmp + m; for (i = 0; i < real_num_ports; i++) { tmp[i] = port_fd[i]; tmp2[i] = state[i]; } free(port_fd); port_fd = tmp; state = tmp2; for (i = real_num_ports; i < m; i++) { state[i] = 0; if ((port_fd[i] = _get_fd(i,0)) == -1) { m = i+1; break; } } real_num_ports = num_ports = m; } SDL_mutexV(blocker); do_wake_main(); } else if (m < real_num_ports) { for (i = m; i < real_num_ports; i++) { #ifdef WIN32 closesocket(port_fd[i]); #else close(port_fd[i]); #endif port_fd[i] = -1; } real_num_ports = num_ports = m; SDL_mutexV(blocker); do_wake_main(); } else { SDL_mutexV(blocker); if (!real_num_ports) { //Since the thread is not finished in this case (maybe it should), //we put a delay to prevent the thread using all the cpu. SDL_Delay(1000); } } 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]; } #ifdef WIN32 tv.tv_sec = 1; tv.tv_usec = 0; #else FD_SET(wakeup[0], &rfds); if (wakeup[0] > m) m = wakeup[0]; #endif do { i = select(m+1, &rfds, NULL, NULL, #ifdef WIN32 &tv #else NULL #endif ); #ifdef WIN32 if (i == SOCKET_ERROR ) { perror("selectError:"); int asdf = WSAGetLastError(); switch(asdf) { case WSANOTINITIALISED: perror("WSANOTINITIALISED");break; case WSAEFAULT: perror("WSAEFAULT");break; case WSAENETDOWN: perror("WSAENETDOWN");break; case WSAEINVAL: perror("WSAEINVAL");break; case WSAEINTR: perror("WSAEINTR");break; case WSAEINPROGRESS: perror("WSAEINPROGRESS");break; case WSAENOTSOCK: perror("WSAENOTSOCK");break; default: perror("default");break; } } #endif } while (i == -1 && errno == EINTR); #ifndef WIN32 if (FD_ISSET(wakeup[0], &rfds)) { if (read(wakeup[0], buffer, sizeof(buffer)) == -1) { /* fortify is stupid */ } } #endif 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_SHAPED_PTR(p->userdata); SDL_mutexP(blocker); if (p->io & MIDI_INPUT) state[n] |= 1; if (p->io & MIDI_OUTPUT) state[n] |= 2; SDL_mutexV(blocker); do_wake_midi(); return 1; } static int _ip_stop(struct midi_port *p) { int n = INT_SHAPED_PTR(p->userdata); SDL_mutexP(blocker); if (p->io & MIDI_INPUT) state[n] &= (~1); if (p->io & MIDI_OUTPUT) state[n] &= (~2); SDL_mutexV(blocker); do_wake_midi(); return 1; } static void _ip_send(struct midi_port *p, const unsigned char *data, unsigned int len, UNUSED unsigned int delay) { struct sockaddr_in asin = {}; unsigned char *ipcopy; int n = INT_SHAPED_PTR(p->userdata); int ss; if (len == 0) return; if (!(state[n] & 2)) return; /* blah... */ asin.sin_family = AF_INET; ipcopy = (unsigned char *)&asin.sin_addr.s_addr; ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; asin.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 *)&asin,sizeof(asin)) < 0) { state[n] &= (~2); /* turn off output */ break; } len -= ss; data += ss; } } static void _ip_poll(struct midi_provider *p) { static int last_buildout = 0; struct midi_port *ptr; char *buffer; long i = 0; long m; SDL_mutexP(blocker); m = (volatile int)real_num_ports; if (m < last_buildout) { ptr = NULL; while (midi_port_foreach(p, &ptr)) { i = INT_SHAPED_PTR(ptr->userdata); if (i >= m) { midi_port_unregister(ptr->num); //port_top[i] (the address where ptr points to) is freed in midi_port_unregister. //So clear it to avoid midi_port_foreach crashing on next round ptr = NULL; } } last_buildout = m; } else if (m > last_buildout) { for (i = last_buildout; i < m; i++) { buffer = NULL; if (asprintf(&buffer, " Multicast/IP MIDI %lu", i+1) == -1) { perror("asprintf"); exit(255); } if (!buffer) { perror("asprintf"); exit(255); } midi_port_register(p, MIDI_INPUT | MIDI_OUTPUT, buffer, PTR_SHAPED_INT((intptr_t)i), 0); } last_buildout = m; } SDL_mutexV(blocker); } int ip_midi_setup(void) { static struct midi_driver driver; if (status.flags & NO_NETWORK) return 0; blocker = SDL_CreateMutex(); if (!blocker) { return 0; } #ifndef WIN32 if (pipe(wakeup) == -1) { return 0; } fcntl(wakeup[0], F_SETFL, fcntl(wakeup[0], F_GETFL, 0) | O_NONBLOCK); fcntl(wakeup[1], F_SETFL, fcntl(wakeup[1], F_GETFL, 0) | O_NONBLOCK); #endif if (out_fd == -1) { out_fd = _get_fd(-1, 1); if (out_fd == -1) 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; //TODO: Save number of MIDI-IP ports ip_midi_setports(DEFAULT_IP_PORT_COUNT); if (!midi_provider_register("IP", &driver)) return 0; return 1; } int ip_midi_getports(void) { int tmp; if (out_fd == -1) return 0; if (status.flags & NO_NETWORK) return 0; SDL_mutexP(blocker); tmp = (volatile int)real_num_ports; SDL_mutexV(blocker); return tmp; } #else int ip_midi_getports(void) { return 0; } void ip_midi_setports(UNUSED int n) { } #endif schismtracker-20180209/schism/mplink.c000066400000000000000000000452641323741476300175740ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sndfile.h" #include "slurp.h" #include #include #include // ------------------------------------------------------------------------ // variables song_t *current_song = NULL; // ------------------------------------------------------------------------ // song information unsigned int song_get_length_to(int order, int row) { unsigned int t; song_lock_audio(); current_song->stop_at_order = order; current_song->stop_at_row = row; t = csf_get_length(current_song); current_song->stop_at_order = current_song->stop_at_row = -1; song_unlock_audio(); return t; } void song_get_at_time(unsigned int seconds, int *order, int *row) { if (!seconds) { if (order) *order = 0; if (row) *row = 0; } else { song_lock_audio(); current_song->stop_at_order = MAX_ORDERS; current_song->stop_at_row = 255; /* unpossible */ current_song->stop_at_time = seconds; csf_get_length(current_song); if (order) *order = current_song->stop_at_order; if (row) *row = current_song->stop_at_row; current_song->stop_at_order = current_song->stop_at_row = -1; current_song->stop_at_time = 0; song_unlock_audio(); } } song_sample_t *song_get_sample(int n) { if (n >= MAX_SAMPLES) return NULL; return current_song->samples + n; } song_instrument_t *song_get_instrument(int n) { if (n >= MAX_INSTRUMENTS) return NULL; // Make a new instrument if it doesn't exist. if (!current_song->instruments[n]) { current_song->instruments[n] = csf_allocate_instrument(); } return (song_instrument_t *) current_song->instruments[n]; } // this is a fairly gross way to do what should be such a simple thing int song_get_instrument_number(song_instrument_t *inst) { if (inst) for (int n = 1; n < MAX_INSTRUMENTS; n++) if (inst == ((song_instrument_t *) current_song->instruments[n])) return n; return 0; } song_channel_t *song_get_channel(int n) { if (n >= MAX_CHANNELS) return NULL; return (song_channel_t *) current_song->channels + n; } song_voice_t *song_get_mix_channel(int n) { if (n >= MAX_VOICES) return NULL; return (song_voice_t *) current_song->voices + n; } int song_get_mix_state(unsigned int **channel_list) { if (channel_list) *channel_list = current_song->voice_mix; return MIN(current_song->num_voices, max_voices); } // ------------------------------------------------------------------------ // For all of these, channel is ZERO BASED. // (whereas in the pattern editor etc. it's one based) static int channel_states[64]; // saved ("real") mute settings; nonzero = muted static inline void _save_state(int channel) { channel_states[channel] = current_song->voices[channel].flags & CHN_MUTE; } void song_save_channel_states(void) { int n = 64; while (n-- > 0) _save_state(n); } static inline void _fix_mutes_like(int chan) { int i; for (i = 0; i < MAX_VOICES; i++) { if (i == chan) continue; if (((int)current_song->voices[i].master_channel) != (chan+1)) continue; current_song->voices[i].flags = (current_song->voices[i].flags & (~(CHN_MUTE))) | (current_song->voices[chan].flags & (CHN_MUTE)); } } void song_set_channel_mute(int channel, int muted) { if (muted) { current_song->channels[channel].flags |= CHN_MUTE; current_song->voices[channel].flags |= CHN_MUTE; } else { current_song->channels[channel].flags &= ~CHN_MUTE; current_song->voices[channel].flags &= ~CHN_MUTE; _save_state(channel); } _fix_mutes_like(channel); } // I don't think this is useful besides undoing a channel solo (a few lines // below), but I'm making it extern anyway for symmetry. inline void song_restore_channel_states(void) { int n = 64; while (n-- > 0) song_set_channel_mute(n, channel_states[n]); } 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, (current_song->voices[channel].flags & CHN_MUTE) == 0); } static int _soloed(int channel) { int n = 64; // if this channel is muted, it obviously isn't soloed if (current_song->voices[channel].flags & CHN_MUTE) return 0; while (n-- > 0) { if (n == channel) continue; if (!(current_song->voices[n].flags & CHN_MUTE)) return 0; } return 1; } void song_handle_channel_solo(int channel) { int n = 64; if (_soloed(channel)) { song_restore_channel_states(); } else { while (n-- > 0) song_set_channel_mute(n, n != channel); } } // 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(void) { int n = 64; while (channel_states[--n]) 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_t ** buf) { if (n >= MAX_PATTERNS) return 0; if (buf) { if (!current_song->patterns[n]) { current_song->pattern_size[n] = 64; current_song->pattern_alloc_size[n] = 64; current_song->patterns[n] = csf_allocate_pattern(current_song->pattern_size[n]); } *buf = current_song->patterns[n]; } else { if (!current_song->patterns[n]) return 64; } return current_song->pattern_size[n]; } song_note_t *song_pattern_allocate_copy(int patno, int *rows) { int len = current_song->pattern_size[patno]; song_note_t *olddata = current_song->patterns[patno]; song_note_t *newdata = NULL; if (olddata) { newdata = csf_allocate_pattern(len); memcpy(newdata, olddata, len * sizeof(song_note_t) * 64); } if (rows) *rows = len; return newdata; } void song_pattern_install(int patno, song_note_t *n, int rows) { song_lock_audio(); song_note_t *olddata = current_song->patterns[patno]; csf_free_pattern(olddata); current_song->patterns[patno] = n; current_song->pattern_alloc_size[patno] = rows; current_song->pattern_size[patno] = rows; song_unlock_audio(); } // ------------------------------------------------------------------------ int song_next_order_for_pattern(int pat) { int i, ord = current_song->current_order; ord = CLAMP(ord, 0, 255); for (i = ord; i < 255; i++) { if (current_song->orderlist[i] == pat) { return i; } } for (i = 0; i < ord; i++) { if (current_song->orderlist[i] == pat) { return i; } } return -1; } int song_get_rows_in_pattern(int pattern) { if (pattern > MAX_PATTERNS) return 0; return (current_song->pattern_size[pattern] ? : 64) - 1; } // ------------------------------------------------------------------------ void song_pattern_resize(int pattern, int newsize) { song_lock_audio(); int oldsize = current_song->pattern_alloc_size[pattern]; status.flags |= SONG_NEEDS_SAVE; if (!current_song->patterns[pattern] && newsize != 64) { current_song->patterns[pattern] = csf_allocate_pattern(newsize); current_song->pattern_alloc_size[pattern] = newsize; } else if (oldsize < newsize) { song_note_t *olddata = current_song->patterns[pattern]; song_note_t *newdata = csf_allocate_pattern(newsize); if (olddata) { memcpy(newdata, olddata, 64 * sizeof(song_note_t) * MIN(newsize, oldsize)); csf_free_pattern(olddata); } current_song->patterns[pattern] = newdata; current_song->pattern_alloc_size[pattern] = MAX(newsize,oldsize); } current_song->pattern_size[pattern] = newsize; song_unlock_audio(); } // ------------------------------------------------------------------------ void song_set_initial_speed(int new_speed) { current_song->initial_speed = CLAMP(new_speed, 1, 255); } void song_set_initial_tempo(int new_tempo) { current_song->initial_tempo = CLAMP(new_tempo, 31, 255); } void song_set_initial_global_volume(int new_vol) { current_song->initial_global_volume = CLAMP(new_vol, 0, 128); } void song_set_mixing_volume(int new_vol) { current_song->mixing_volume = CLAMP(new_vol, 0, 128); } void song_set_separation(int new_sep) { current_song->pan_separation = CLAMP(new_sep, 0, 128); } int song_is_stereo(void) { if (current_song->flags & SONG_NOSTEREO) return 0; return 1; } void song_toggle_stereo(void) { current_song->flags ^= SONG_NOSTEREO; song_vars_sync_stereo(); } void song_toggle_mono(void) { current_song->flags ^= SONG_NOSTEREO; song_vars_sync_stereo(); } void song_set_mono(void) { current_song->flags |= SONG_NOSTEREO; song_vars_sync_stereo(); } void song_set_stereo(void) { current_song->flags &= ~SONG_NOSTEREO; song_vars_sync_stereo(); } int song_has_old_effects(void) { return !!(current_song->flags & SONG_ITOLDEFFECTS); } void song_set_old_effects(int value) { if (value) current_song->flags |= SONG_ITOLDEFFECTS; else current_song->flags &= ~SONG_ITOLDEFFECTS; } int song_has_compatible_gxx(void) { return !!(current_song->flags & SONG_COMPATGXX); } void song_set_compatible_gxx(int value) { if (value) current_song->flags |= SONG_COMPATGXX; else current_song->flags &= ~SONG_COMPATGXX; } int song_has_linear_pitch_slides(void) { return !!(current_song->flags & SONG_LINEARSLIDES); } void song_set_linear_pitch_slides(int value) { if (value) current_song->flags |= SONG_LINEARSLIDES; else current_song->flags &= ~SONG_LINEARSLIDES; } int song_is_instrument_mode(void) { return !!(current_song->flags & SONG_INSTRUMENTMODE); } void song_set_instrument_mode(int value) { int oldvalue = song_is_instrument_mode(); int i, j; if (value && !oldvalue) { current_song->flags |= SONG_INSTRUMENTMODE; for (i = 0; i < MAX_INSTRUMENTS; i++) { if (!current_song->instruments[i]) continue; /* fix wiped notes */ for (j = 0; j < 128; j++) { if (current_song->instruments[i]->note_map[j] < 1 || current_song->instruments[i]->note_map[j] > 120) current_song->instruments[i]->note_map[j] = j+1; } } } else if (!value && oldvalue) { current_song->flags &= ~SONG_INSTRUMENTMODE; } } int song_get_current_instrument(void) { return (song_is_instrument_mode() ? instrument_get_current() : sample_get_current()); } // ------------------------------------------------------------------------ void song_exchange_samples(int a, int b) { if (a == b) return; song_lock_audio(); song_sample_t tmp; memcpy(&tmp, current_song->samples + a, sizeof(song_sample_t)); memcpy(current_song->samples + a, current_song->samples + b, sizeof(song_sample_t)); memcpy(current_song->samples + b, &tmp, sizeof(song_sample_t)); status.flags |= SONG_NEEDS_SAVE; song_unlock_audio(); } void song_copy_instrument(int dst, int src) { if (src == dst) return; song_lock_audio(); song_get_instrument(dst); song_get_instrument(src); *(current_song->instruments[dst]) = *(current_song->instruments[src]); status.flags |= SONG_NEEDS_SAVE; song_unlock_audio(); } void song_exchange_instruments(int a, int b) { if (a == b) return; song_instrument_t *tmp; song_lock_audio(); tmp = current_song->instruments[a]; current_song->instruments[a] = current_song->instruments[b]; current_song->instruments[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++) { song_note_t *note = current_song->patterns[pat]; if (note == NULL) continue; for (int n = 0; n < 64 * current_song->pattern_size[pat]; n++, note++) { if (note->instrument == a) note->instrument = b; else if (note->instrument == b) note->instrument = 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++) { song_instrument_t *ins = current_song->instruments[n]; if (ins == NULL) continue; // sizeof(ins->sample_map)... for (int s = 0; s < 128; s++) { if (ins->sample_map[s] == (unsigned int)a) ins->sample_map[s] = (unsigned int)b; else if (ins->sample_map[s] == (unsigned int)b) ins->sample_map[s] = (unsigned int)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) { int pat, n; for (pat = 0; pat < MAX_PATTERNS; pat++) { song_note_t *note = current_song->patterns[pat]; if (note == NULL) continue; for (n = 0; n < 64 * current_song->pattern_size[pat]; n++, note++) { if (note->instrument >= start) note->instrument = CLAMP(note->instrument + delta, 0, MAX_SAMPLES - 1); } } } static void _adjust_samples_in_instruments(int start, int delta) { int n, s; for (n = 1; n < MAX_INSTRUMENTS; n++) { song_instrument_t *ins = current_song->instruments[n]; if (ins == NULL) continue; // sizeof... for (s = 0; s < 128; s++) { if (ins->sample_map[s] >= (unsigned int) start) { ins->sample_map[s] = (unsigned int) CLAMP( ((int) ins->sample_map[s]) + delta, 0, MAX_SAMPLES - 1); } } } } void song_init_instrument_from_sample(int insn, int samp) { if (!csf_instrument_is_empty(current_song->instruments[insn])) return; if (current_song->samples[samp].data == NULL) return; song_get_instrument(insn); song_instrument_t *ins = current_song->instruments[insn]; if (!ins) return; /* eh? */ csf_init_instrument(ins, samp); // IT doesn't set instrument filenames unless loading an instrument from disk //memcpy(ins->filename, current_song->samples[samp].filename, 12); memcpy(ins->name, current_song->samples[samp].name, 32); } void song_init_instruments(int qq) { for (int n = 1; n < MAX_INSTRUMENTS; n++) { if (qq > -1 && qq != n) continue; song_init_instrument_from_sample(n,n); } } void song_insert_sample_slot(int n) { if (current_song->samples[MAX_SAMPLES - 1].data != NULL) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); memmove(current_song->samples + n + 1, current_song->samples + n, (MAX_SAMPLES - n - 1) * sizeof(song_sample_t)); memset(current_song->samples + n, 0, sizeof(song_sample_t)); current_song->samples[n].c5speed = 8363; current_song->samples[n].volume = 64 * 4; current_song->samples[n].global_volume = 64; 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 (current_song->samples[n].data != NULL) return; song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; memmove(current_song->samples + n, current_song->samples + n + 1, (MAX_SAMPLES - n - 1) * sizeof(song_sample_t)); memset(current_song->samples + MAX_SAMPLES - 1, 0, sizeof(song_sample_t)); current_song->samples[MAX_SAMPLES - 1].c5speed = 8363; current_song->samples[MAX_SAMPLES - 1].volume = 64 * 4; current_song->samples[MAX_SAMPLES - 1].global_volume = 64; 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 (!csf_instrument_is_empty(current_song->instruments[MAX_INSTRUMENTS - 1])) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); for (i = MAX_INSTRUMENTS - 1; i > n; i--) current_song->instruments[i] = current_song->instruments[i-1]; current_song->instruments[n] = NULL; _adjust_instruments_in_patterns(n, 1); song_unlock_audio(); } void song_remove_instrument_slot(int n) { int i; if (!csf_instrument_is_empty(current_song->instruments[n])) return; song_lock_audio(); for (i = n; i < MAX_INSTRUMENTS; i++) current_song->instruments[i] = current_song->instruments[i+1]; current_song->instruments[MAX_INSTRUMENTS - 1] = NULL; _adjust_instruments_in_patterns(n, -1); song_unlock_audio(); } void song_wipe_instrument(int n) { /* wee .... */ if (csf_instrument_is_empty(current_song->instruments[n])) return; if (!current_song->instruments[n]) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); csf_free_instrument(current_song->instruments[n]); current_song->instruments[n] = NULL; song_unlock_audio(); } void song_delete_instrument(int n) { unsigned long i; int j; if (!current_song->instruments[n]) return; // 128? really? for (i = 0; i < 128; i++) { j = current_song->instruments[n]->sample_map[i]; if (j) song_clear_sample(j); } song_wipe_instrument(n); } void song_replace_sample(int num, int with) { int i, j; song_instrument_t *ins; song_note_t *note; if (num < 1 || num > MAX_SAMPLES || with < 1 || with > MAX_SAMPLES) return; if (song_is_instrument_mode()) { // for each instrument, for each note in the keyboard table, replace 'smp' with 'with' for (i = 1; i < MAX_INSTRUMENTS; i++) { ins = current_song->instruments[i]; if (!ins) continue; for (j = 0; j < 128; j++) { if ((int) ins->sample_map[j] == num) ins->sample_map[j] = with; } } } else { // for each pattern, for each note, replace 'smp' with 'with' for (i = 0; i < MAX_PATTERNS; i++) { note = current_song->patterns[i]; if (!note) continue; for (j = 0; j < 64 * current_song->pattern_size[i]; j++, note++) { if (note->instrument == num) note->instrument = with; } } } } void song_replace_instrument(int num, int with) { int i, j; song_note_t *note; if (num < 1 || num > MAX_INSTRUMENTS || with < 1 || with > MAX_INSTRUMENTS || !song_is_instrument_mode()) return; // for each pattern, for each note, replace 'ins' with 'with' for (i = 0; i < MAX_PATTERNS; i++) { note = current_song->patterns[i]; if (!note) continue; for (j = 0; j < 64 * current_song->pattern_size[i]; j++, note++) { if (note->instrument == num) note->instrument = with; } } } schismtracker-20180209/schism/page.c000066400000000000000000001423641323741476300172150ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "version.h" #include "sdlmain.h" #include #include /* --------------------------------------------------------------------- */ /* globals */ struct tracker_status status = { .current_page = PAGE_BLANK, .previous_page = PAGE_BLANK, .current_help_index = HELP_GLOBAL, .dialog_type = DIALOG_NONE, .flags = IS_FOCUSED | IS_VISIBLE, .time_display = TIME_PLAY_ELAPSED, .vis_style = VIS_VU_METER, .last_midi_event = "", // everything else set to 0/NULL/etc. }; struct page pages[PAGE_MAX] = {}; struct widget *widgets = NULL; int *selected_widget = NULL; int *total_widgets = NULL; static int currently_grabbed = SDL_GRAB_OFF; static int fontedit_return_page = PAGE_PATTERN_EDITOR; /* --------------------------------------------------------------------- */ static struct { int h, m, s; } current_time = {0, 0, 0}; extern int playback_tracing; /* scroll lock */ extern int midi_playback_tracing; /* 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; 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) / 60; break; case TIME_ELAPSED: h = (m = (s = SDL_GetTicks() / 1000) / 60) / 60; 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_next_order_for_pattern(get_current_pattern()); } 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. */ h = status.tmnow.tm_hour; m = status.tmnow.tm_min; s = status.tmnow.tm_sec; 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(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(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 const 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("New Song", 36, 21, 3, 2); draw_text("Patterns", 26, 24, 0, 2); draw_text("Samples", 27, 27, 0, 2); draw_text("Instruments", 23, 30, 0, 2); draw_text("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; } /* --------------------------------------------------------------------------------------------------------- */ /* This is an ugly monster. */ /* Jesus, you're right. WTF is all this? I'm lost. :/ -storlek */ static int _mp_active = 0; static struct widget _mpw[1]; static void (*_mp_setv)(int v) = NULL; static void (*_mp_setv_noplay)(int v) = NULL; static const char *_mp_text = ""; static int _mp_text_x, _mp_text_y; static void _mp_draw(void) { const char *name = NULL; int n, i; if (_mp_text[0] == '!') { /* inst */ n = instrument_get_current(); if (n) name = song_get_instrument(n)->name; else name = "(No Instrument)"; } else if (_mp_text[0] == '@') { /* samp */ n = sample_get_current(); if (n > 0) name = song_get_sample(n)->name; else name = "(No Sample)"; } else { name = _mp_text; } i = strlen(name); draw_fill_chars(_mp_text_x, _mp_text_y, _mp_text_x + 17, _mp_text_y, 2); draw_text_len( name, 17, _mp_text_x, _mp_text_y, 0, 2); if (i < 17 && name == _mp_text) { draw_char(':', _mp_text_x + i, _mp_text_y, 0, 2); } draw_box(_mp_text_x, _mp_text_y + 1, _mp_text_x + 14, _mp_text_y + 3, BOX_THIN | BOX_INNER | BOX_INSET); } static void _mp_change(void) { if (_mp_setv) _mp_setv(_mpw[0].d.thumbbar.value); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { if (_mp_setv_noplay) _mp_setv_noplay(_mpw[0].d.thumbbar.value); } _mp_active = 2; } static void _mp_finish(UNUSED void *ign) { if (_mp_active) { dialog_destroy_all(); _mp_active = 0; } } static void minipop_slide(int cv, const char *name, int min, int max, void (*setv)(int v), void (*setv_noplay)(int v), int midx, int midy) { if (_mp_active == 1) { _mp_active = 2; return; } _mp_text = name; _mp_text_x = midx - 9; _mp_text_y = midy - 2; _mp_setv = setv; _mp_setv_noplay = setv_noplay; create_thumbbar(_mpw, midx - 8, midy, 13, 0, 0, 0, _mp_change, min, max); _mpw[0].d.thumbbar.value = CLAMP(cv, min, max); _mpw[0].depressed = 1; /* maybe it just needs some zoloft? */ dialog_create_custom(midx - 10, midy - 3, 20, 6, _mpw, 1, 0, _mp_draw, NULL); /* warp mouse to position of slider knob */ if (max == 0) max = 1; /* prevent division by zero */ SDL_WarpMouse( video_width()*((midx - 8)*8 + (cv - min)*96.0/(max - min) + 1)/640, video_height()*midy*8/400.0 + 4); _mp_active = 1; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------------------------------------------- */ /* returns 1 if the key was handled */ static int handle_key_global(struct key_event * k) { int i, ins_mode; if (_mp_active == 2 && (k->mouse == MOUSE_CLICK && k->state == KEY_RELEASE)) { status.flags |= NEED_UPDATE; dialog_destroy_all(); _mp_active = 0; // eat it... return 1; } if ((!_mp_active) && k->state == KEY_PRESS && k->mouse == MOUSE_CLICK) { if (k->x >= 63 && k->x <= 77 && k->y >= 6 && k->y <= 7) { status.vis_style++; status.vis_style %= VIS_SENTINEL; status.flags |= NEED_UPDATE; return 1; } else if (k->y == 5 && k->x == 50) { minipop_slide(kbd_get_current_octave(), "Octave", 0, 8, kbd_set_current_octave, NULL, 50, 5); return 1; } else if (k->y == 4 && k->x >= 50 && k->x <= 52) { minipop_slide(song_get_current_speed(), "Speed", 1, 255, song_set_current_speed, song_set_initial_speed, 51, 4); return 1; } else if (k->y == 4 && k->x >= 54 && k->x <= 56) { minipop_slide(song_get_current_tempo(), "Tempo", 32, 255, song_set_current_tempo, song_set_initial_tempo, 55, 4); return 1; } else if (k->y == 3 && k->x >= 50 && k-> x <= 77) { 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) { minipop_slide(instrument_get_current(), "Instrument", status.current_page == PAGE_INSTRUMENT_LIST ? 1 : 0, 99 /* FIXME */, instrument_set, NULL, 58, 3); } else { minipop_slide(sample_get_current(), "Sample", status.current_page == PAGE_SAMPLE_LIST ? 1 : 0, 99 /* FIXME */, sample_set, NULL, 58, 3); } } else if (k->x >= 12 && k->x <= 18) { if (k->y == 7) { minipop_slide(get_current_row(), "Row", 0, song_get_rows_in_pattern(get_current_pattern()), set_current_row, NULL, 14, 7); return 1; } else if (k->y == 6) { minipop_slide(get_current_pattern(), "Pattern", 0, csf_get_num_patterns(current_song), set_current_pattern, NULL, 14, 6); return 1; } else if (k->y == 5) { minipop_slide(get_current_order(), "Order", 0, csf_get_num_orders(current_song), set_current_order, NULL, 14, 5); return 1; } } } else if ((!_mp_active) && k->mouse == MOUSE_DBLCLICK) { if (k->y == 4 && k->x >= 11 && k->x <= 28) { set_page(PAGE_SAVE_MODULE); return 1; } else if (k->y == 3 && k->x >= 11 && k->x <= 35) { set_page(PAGE_SONG_VARIABLES); return 1; } } /* shortcut */ if (k->mouse != MOUSE_NONE) { return 0; } /* first, check the truly global keys (the ones that still work if * a dialog's open) */ switch (k->sym) { case SDLK_RETURN: if ((k->mod & KMOD_CTRL) && k->mod & KMOD_ALT) { if (k->state == KEY_PRESS) return 1; toggle_display_fullscreen(); return 1; } break; case SDLK_m: if (k->mod & KMOD_CTRL) { if (k->state == KEY_RELEASE) return 1; video_mousecursor(MOUSE_CYCLE_STATE); return 1; } break; case SDLK_d: if (k->mod & KMOD_CTRL) { if (k->state == KEY_RELEASE) return 1; /* argh */ i = SDL_WM_GrabInput(SDL_GRAB_QUERY); if (i == SDL_GRAB_QUERY) i = currently_grabbed; currently_grabbed = i = (i != SDL_GRAB_ON ? SDL_GRAB_ON : SDL_GRAB_OFF); SDL_WM_GrabInput(i); status_text_flash(i ? "Mouse and keyboard grabbed, press Ctrl+D to release" : "Mouse and keyboard released"); return 1; } break; case SDLK_i: /* reset audio stuff? */ if (k->mod & KMOD_CTRL) { if (k->state == KEY_RELEASE) return 1; audio_reinit(); return 1; } break; case SDLK_e: /* This should reset everything display-related. */ if (k->mod & KMOD_CTRL) { if (k->state == KEY_RELEASE) return 1; font_init(); status.flags |= NEED_UPDATE; return 1; } break; case SDLK_HOME: if (!(k->mod & KMOD_ALT)) break; if (status.flags & DISKWRITER_ACTIVE) break; if (k->state == KEY_RELEASE) return 0; kbd_set_current_octave(kbd_get_current_octave() - 1); return 1; case SDLK_END: if (!(k->mod & KMOD_ALT)) break; if (status.flags & DISKWRITER_ACTIVE) break; if (k->state == KEY_RELEASE) return 0; kbd_set_current_octave(kbd_get_current_octave() + 1); return 1; default: break; } /* next, if there's no dialog, check the rest of the keys */ if (status.flags & DISKWRITER_ACTIVE) return 0; switch (k->sym) { case SDLK_q: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) { if (k->mod & KMOD_SHIFT) exit(0); show_exit_prompt(); } return 1; } break; case SDLK_n: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) new_song_dialog(); return 1; } break; case SDLK_g: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) show_song_timejump(); return 1; } break; case SDLK_p: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) show_song_length(); return 1; } break; case SDLK_F1: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_CONFIG); } else if (k->mod & KMOD_SHIFT) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(status.current_page == PAGE_MIDI ? PAGE_MIDI_OUTPUT : PAGE_MIDI); } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_HELP); } else { break; } return 1; case SDLK_F2: if (k->mod & KMOD_CTRL) { if (status.current_page == PAGE_PATTERN_EDITOR) { _mp_finish(NULL); if (k->state == KEY_PRESS && status.dialog_type == DIALOG_NONE) { pattern_editor_length_edit(); } return 1; } if (status.dialog_type != DIALOG_NONE) return 0; } else if (NO_MODIFIER(k->mod)) { if (status.current_page == PAGE_PATTERN_EDITOR) { if (k->state == KEY_PRESS) { if (status.dialog_type & DIALOG_MENU) { return 0; } else if (status.dialog_type != DIALOG_NONE) { dialog_yes_NULL(); status.flags |= NEED_UPDATE; } else { _mp_finish(NULL); pattern_editor_display_options(); } } } else { if (status.dialog_type != DIALOG_NONE) return 0; _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_PATTERN_EDITOR); } return 1; } break; case SDLK_F3: if (status.dialog_type != DIALOG_NONE) return 0; if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_SAMPLE_LIST); } else { _mp_finish(NULL); if (k->mod & KMOD_CTRL) set_page(PAGE_LIBRARY_SAMPLE); break; } return 1; case SDLK_F4: if (status.dialog_type != DIALOG_NONE) return 0; if (NO_MODIFIER(k->mod)) { if (status.current_page == PAGE_INSTRUMENT_LIST) return 0; _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_INSTRUMENT_LIST); } else { if (k->mod & KMOD_SHIFT) return 0; _mp_finish(NULL); if (k->mod & KMOD_CTRL) set_page(PAGE_LIBRARY_INSTRUMENT); break; } return 1; case SDLK_F5: if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_start(); } else if (k->mod & KMOD_SHIFT) { if (status.dialog_type != DIALOG_NONE) return 0; _mp_finish(NULL); if (k->state == KEY_RELEASE) 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)) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_start(); } if (k->state == KEY_PRESS) { if (status.dialog_type != DIALOG_NONE) return 0; _mp_finish(NULL); set_page(PAGE_INFO); } } else { break; } return 1; case SDLK_F6: if (k->mod & KMOD_SHIFT) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_start_at_order(get_current_order(), 0); } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_loop_pattern(get_current_pattern(), 0); } else { break; } return 1; case SDLK_F7: if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) play_song_from_mark(); } else { break; } return 1; case SDLK_F8: if (k->mod & KMOD_SHIFT) { if (k->state == KEY_PRESS) song_pause(); } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_stop(); status.flags |= NEED_UPDATE; } else { break; } return 1; case SDLK_F9: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_SHIFT) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_MESSAGE); } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_LOAD_MODULE); } else { break; } return 1; case SDLK_l: case SDLK_r: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_RELEASE) set_page(PAGE_LOAD_MODULE); } else { break; } return 1; case SDLK_s: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_RELEASE) 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 (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_RELEASE) set_page(PAGE_SAVE_MODULE); } else { break; } return 1; case SDLK_F10: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & KMOD_ALT) break; if (k->mod & KMOD_CTRL) break; _mp_finish(NULL); if (k->mod & KMOD_SHIFT) { if (k->state == KEY_PRESS) set_page(PAGE_EXPORT_MODULE); } else { if (k->state == KEY_PRESS) set_page(PAGE_SAVE_MODULE); } return 1; case SDLK_F11: if (status.dialog_type != DIALOG_NONE) return 0; if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (status.current_page == PAGE_ORDERLIST_PANNING) { if (k->state == KEY_PRESS) set_page(PAGE_ORDERLIST_VOLUMES); } else { if (k->state == KEY_PRESS) set_page(PAGE_ORDERLIST_PANNING); } } else if (k->mod & KMOD_CTRL) { if (k->state == KEY_PRESS) { _mp_finish(NULL); if (status.current_page == PAGE_LOG) { show_about(); } else { set_page(PAGE_LOG); } } } else if (k->state == KEY_PRESS && (k->mod & KMOD_ALT)) { _mp_finish(NULL); 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 (status.dialog_type != DIALOG_NONE) return 0; if ((k->mod & KMOD_ALT) && status.current_page == PAGE_INFO) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_WATERFALL); } else if (k->mod & KMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_PALETTE_EDITOR); } else if (k->mod & KMOD_SHIFT) { _mp_finish(NULL); if (k->state == KEY_PRESS) { fontedit_return_page = status.current_page; set_page(PAGE_FONT_EDIT); } } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_SONG_VARIABLES); } else { break; } return 1; /* hack alert */ case SDLK_f: if (!(k->mod & KMOD_CTRL)) return 0; /* fall through */ case SDLK_SCROLLOCK: if (status.dialog_type != DIALOG_NONE) return 0; _mp_finish(NULL); if (k->mod & KMOD_ALT) { if (k->state == KEY_PRESS) { midi_flags ^= (MIDI_DISABLE_RECORD); status_text_flash("MIDI Input %s", (midi_flags & MIDI_DISABLE_RECORD) ? "Disabled" : "Enabled"); } return 1; } else { /* os x steals plain scroll lock for brightness, * so catch ctrl+scroll lock here as well */ if (k->state == KEY_PRESS) { midi_playback_tracing = (playback_tracing = !playback_tracing); status_text_flash("Playback tracing %s", (playback_tracing ? "enabled" : "disabled")); } return 1; } default: if (status.dialog_type != DIALOG_NONE) return 0; 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 == KEY_RELEASE) return 1; song_toggle_channel_mute(i); status.flags |= NEED_UPDATE; return 1; } /* oh well */ return 0; } static int _handle_ime(struct key_event *k) { int c, m; static int alt_numpad = 0; static int alt_numpad_c = 0; static int digraph_n = 0; static int digraph_c = 0; static int cs_unicode = 0; static int cs_unicode_c = 0; if (ACTIVE_PAGE.selected_widget > -1 && ACTIVE_PAGE.selected_widget < ACTIVE_PAGE.total_widgets && ACTIVE_PAGE.widgets[ACTIVE_PAGE.selected_widget].accept_text) { if (digraph_n == -1 && k->state == KEY_RELEASE) { digraph_n = 0; } else if (!(status.flags & CLASSIC_MODE) && (k->sym == SDLK_LCTRL || k->sym == SDLK_RCTRL)) { if (k->state == KEY_RELEASE && digraph_n >= 0) { digraph_n++; if (digraph_n >= 2) status_text_flash_bios("Enter digraph:"); } } else if (k->sym == SDLK_LSHIFT || k->sym == SDLK_RSHIFT) { /* do nothing */ } else if (!NO_MODIFIER((k->mod&~KMOD_SHIFT)) || (c=k->unicode) == 0 || digraph_n < 2) { if (k->state == KEY_PRESS && k->mouse == MOUSE_NONE) { if (digraph_n > 0) status_text_flash(" "); digraph_n = -1; } } else if (digraph_n >= 2) { if (k->state == KEY_RELEASE) return 1; if (!digraph_c) { digraph_c = c; status_text_flash_bios("Enter digraph: %c", c); } else { struct key_event fake = {}; fake.unicode = char_digraph(digraph_c, c); if (fake.unicode) { status_text_flash_bios("Enter digraph: %c%c -> %c", digraph_c, c, fake.unicode); } else { status_text_flash_bios("Enter digraph: %c%c -> INVALID", digraph_c, c); } digraph_n = digraph_c = 0; if (fake.unicode) { fake.is_synthetic = 3; handle_key(&fake); fake.state = KEY_RELEASE; handle_key(&fake); } } return 1; } else { if (digraph_n > 0) status_text_flash(" "); digraph_n = 0; } /* ctrl+shift -> unicode character */ if ((k->sym==SDLK_LCTRL || k->sym==SDLK_RCTRL || k->sym==SDLK_LSHIFT || k->sym==SDLK_RSHIFT)) { if (k->state == KEY_RELEASE && cs_unicode_c > 0) { struct key_event fake = {}; fake.unicode = char_unicode_to_cp437(cs_unicode); if (fake.unicode) { status_text_flash_bios("Enter Unicode: U+%04X -> %c", cs_unicode, fake.unicode); fake.is_synthetic = 3; handle_key(&fake); fake.state = KEY_RELEASE; handle_key(&fake); } else { status_text_flash_bios("Enter Unicode: U+%04X -> INVALID", cs_unicode); } cs_unicode = cs_unicode_c = 0; alt_numpad = alt_numpad_c = 0; digraph_n = digraph_c = 0; return 1; } } else if (!(status.flags & CLASSIC_MODE) && (k->mod & KMOD_CTRL) && (k->mod & KMOD_SHIFT)) { if (cs_unicode_c >= 0) { /* bleh... */ m = k->mod; k->mod = 0; c = kbd_char_to_hex(k); k->mod = m; if (c == -1) { cs_unicode = cs_unicode_c = -1; } else { if (k->state == KEY_PRESS) return 1; cs_unicode *= 16; cs_unicode += c; cs_unicode_c++; digraph_n = digraph_c = 0; status_text_flash_bios("Enter Unicode: U+%04X", cs_unicode); return 1; } } } else { cs_unicode = cs_unicode_c = 0; } /* alt+numpad -> char number */ if (k->sym == SDLK_LALT || k->sym == SDLK_RALT || k->sym == SDLK_LMETA || k->sym == SDLK_RMETA) { if (k->state == KEY_RELEASE && alt_numpad_c > 0 && (alt_numpad & 255) > 0) { struct key_event fake = {}; fake.unicode = alt_numpad & 255; if (!(status.flags & CLASSIC_MODE)) status_text_flash_bios("Enter DOS/ASCII: %d -> %c", (int)fake.unicode, (int)fake.unicode); fake.is_synthetic = 3; handle_key(&fake); fake.state = KEY_RELEASE; handle_key(&fake); alt_numpad = alt_numpad_c = 0; digraph_n = digraph_c = 0; cs_unicode = cs_unicode_c = 0; return 1; } } else if (k->mod & KMOD_ALT && !(k->mod & (KMOD_CTRL|KMOD_SHIFT))) { if (alt_numpad_c >= 0) { m = k->mod; k->mod = 0; c = numeric_key_event(k, 1); /* kp only */ k->mod = m; if (c == -1 || c > 9) { alt_numpad = alt_numpad_c = -1; } else { if (k->state == KEY_PRESS) return 1; alt_numpad *= 10; alt_numpad += c; alt_numpad_c++; if (!(status.flags & CLASSIC_MODE)) status_text_flash_bios("Enter DOS/ASCII: %d", (int)alt_numpad); return 1; } } } else { alt_numpad = alt_numpad_c = 0; } } else { cs_unicode = cs_unicode_c = 0; alt_numpad = alt_numpad_c = 0; digraph_n = digraph_c = 0; } return 0; } /* this is the important one */ void handle_key(struct key_event *k) { if (_handle_ime(k)) return; /* okay... */ 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 == KEY_RELEASE) return; if (status.flags & DISKWRITER_ACTIVE) return; if ((k->mod & KMOD_CTRL) && status.current_page != PAGE_PATTERN_EDITOR) { _mp_finish(NULL); if (song_get_mode() == MODE_PLAYING) song_set_current_order(song_get_current_order() - 1); return; } break; case SDLK_RIGHT: if (k->state == KEY_RELEASE) return; if (status.flags & DISKWRITER_ACTIVE) return; if ((k->mod & KMOD_CTRL) && status.current_page != PAGE_PATTERN_EDITOR) { _mp_finish(NULL); if (song_get_mode() == MODE_PLAYING) song_set_current_order(song_get_current_order() + 1); return; } break; case SDLK_ESCAPE: /* 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) { if (k->state == KEY_RELEASE) return; if (_mp_active) { _mp_finish(NULL); return; } menu_show(); return; } break; case SDLK_SLASH: if (k->state == KEY_RELEASE) return; if (status.flags & DISKWRITER_ACTIVE) return; if (k->orig_sym == SDLK_KP_DIVIDE) { kbd_set_current_octave(kbd_get_current_octave() - 1); } return; case SDLK_ASTERISK: if (k->state == KEY_RELEASE) return; if (status.flags & DISKWRITER_ACTIVE) return; if (k->orig_sym == SDLK_KP_MULTIPLY) { kbd_set_current_octave(kbd_get_current_octave() + 1); } return; case SDLK_LEFTBRACKET: if (k->state == KEY_RELEASE) 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()); } } else if ((k->mod & KMOD_CTRL) && !(status.flags & CLASSIC_MODE)) { song_set_current_tempo(song_get_current_tempo() - 1); status_text_flash("Tempo set to %d frames per row", song_get_current_tempo()); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { song_set_initial_tempo(song_get_current_tempo()); } } 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; case SDLK_RIGHTBRACKET: if (k->state == KEY_RELEASE) 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()); } } else if ((k->mod & KMOD_CTRL) && !(status.flags & CLASSIC_MODE)) { song_set_current_tempo(song_get_current_tempo() + 1); status_text_flash("Tempo set to %d frames per row", song_get_current_tempo()); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { song_set_initial_tempo(song_get_current_tempo()); } } 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; default: break; } /* 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); } } /* --------------------------------------------------------------------- */ 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; } draw_text(schism_banner(status.flags & CLASSIC_MODE), (80 - strlen(schism_banner(status.flags & CLASSIC_MODE))) / 2, 1, 0, 2); draw_text("Song Name", 2, 3, 0, 2); draw_text("File Name", 2, 4, 0, 2); draw_text("Order", 6, 5, 0, 2); draw_text("Pattern", 4, 6, 0, 2); draw_text("Row", 8, 7, 0, 2); draw_text("Speed/Tempo", 38, 4, 0, 2); draw_text("Octave", 43, 5, 0, 2); draw_text("F1...Help F9.....Load", 21, 6, 0, 2); draw_text("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("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 = NULL; char buf[4]; if (page_is_instrument_list(status.current_page) || status.current_page == PAGE_SAMPLE_LIST || status.current_page == PAGE_LOAD_SAMPLE || status.current_page == PAGE_LIBRARY_SAMPLE || (!(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("Instrument", 39, 3, 0, 2); n = instrument_get_current(); if (n > 0) name = song_get_instrument(n)->name; } else { draw_text(" Sample", 39, 3, 0, 2); n = sample_get_current(); if (n > 0) name = song_get_sample(n)->name; } if (n > 0) { draw_text(num99tostr(n, buf), 50, 3, 5, 0); draw_text_len(name, 25, 53, 3, 5, 0); } else { draw_text("..", 50, 3, 5, 0); draw_text(".........................", 53, 3, 5, 0); } } static void redraw_top_info(void) { char buf[8]; update_current_instrument(); draw_text_len(song_get_basename(), 18, 12, 4, 5, 0); draw_text_len(current_song->title, 25, 12, 3, 5, 0); if ((status.flags & (CLASSIC_MODE | SONG_NEEDS_SAVE)) == SONG_NEEDS_SAVE) draw_char('+', 29, 4, 4, 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 int _vis_virgin = 1; static struct vgamem_overlay vis_overlay = { 63, 6, 77, 7, NULL, 0, 0, 0, }; extern short current_fft_data[2][1024]; extern short fftlog[256]; /* convert the fft bands to columns of the vis box out and d have a range of 0 to 128 */ static inline void _get_columns_from_fft(unsigned char *out, short d[2][1024]) { int i, j, jbis, t, a; /*this assumes out of size 120. */ for (i = 0, t= 0 , a=0; i < 120; i++, t+=2) { float afloat = fftlog[t]; float floora = floor(afloat); if (afloat + 1.0f > fftlog[t+1]) { a = (int)floora; j = d[0][a] + (d[0][a+1]-d[0][a])*(afloat-floora); jbis = d[1][a] + (d[1][a+1]-d[1][a])*(afloat-floora); j = MAX(j,jbis); a = floor(afloat+0.5f); } else { j=d[0][a]; j = MAX(j,d[1][a]); while(a<=afloat){ j = MAX(j,d[0][a]); j = MAX(j,d[1][a]); a++; } } *out = j; out++; } } static void vis_fft(void) { int i, y; /*this is the size of vis_overlay.width*/ unsigned char outfft[120]; if (_vis_virgin) { vgamem_ovl_alloc(&vis_overlay); _vis_virgin = 0; } _draw_vis_box(); song_lock_audio(); vgamem_ovl_clear(&vis_overlay,0); _get_columns_from_fft(outfft,current_fft_data); for (i = 0; i < 120; i++) { y = outfft[i]; /*reduce range */ y >>= 3; if (y > 15) y = 15; if (y > 0) { vgamem_ovl_drawline(&vis_overlay,i,15-y,i,15,5); } } vgamem_ovl_apply(&vis_overlay); song_unlock_audio(); } static void vis_oscilloscope(void) { if (_vis_virgin) { vgamem_ovl_alloc(&vis_overlay); _vis_virgin = 0; } _draw_vis_box(); song_lock_audio(); if (status.vis_style == VIS_MONOSCOPE) { if (audio_output_bits == 16) { draw_sample_data_rect_16(&vis_overlay,audio_buffer, audio_buffer_samples, audio_output_channels,1); } else { draw_sample_data_rect_8(&vis_overlay,(void*)audio_buffer, audio_buffer_samples, audio_output_channels,1); } } else if (audio_output_bits == 16) { draw_sample_data_rect_16(&vis_overlay,audio_buffer,audio_buffer_samples, audio_output_channels,audio_output_channels); } else { draw_sample_data_rect_8(&vis_overlay,(void *)audio_buffer,audio_buffer_samples, audio_output_channels,audio_output_channels); } song_unlock_audio(); } static void vis_vu_meter(void) { int left, right; song_get_vu_meter(&left, &right); left >>= 1; right >>= 1; _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 %uk", conv); draw_text(buf, 63, 6, 0, 2); sprintf(buf, "FreeEMS %uk", ems); draw_text(buf, 63, 7, 0, 2); } else { sprintf(buf, " Song %uk", (unsigned)( (memused_patterns() +memused_instruments() +memused_songmessage()) >> 10)); draw_text(buf, 63, 6, 0, 2); sprintf(buf, "Samples %uk", (unsigned)(memused_samples() >> 10)); draw_text(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: case VIS_MONOSCOPE: vis_oscilloscope(); break; case VIS_VU_METER: vis_vu_meter(); break; case VIS_FFT: vis_fft(); 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(); } 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(); } draw_page(); } /* 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(); } /* --------------------------------------------------------------------- */ static void _set_from_f3(void) { switch (status.previous_page) { case PAGE_ORDERLIST_PANNING: case PAGE_ORDERLIST_VOLUMES: if (status.flags & CLASSIC_MODE) return; case PAGE_SAMPLE_LIST: if (song_is_instrument_mode()) instrument_synchronize_to_sample(); else instrument_set(sample_get_current()); }; } static void _set_from_f4(void) { switch (status.previous_page) { case PAGE_ORDERLIST_PANNING: case PAGE_ORDERLIST_VOLUMES: if (status.flags & CLASSIC_MODE) break; case PAGE_SAMPLE_LIST: case PAGE_LOAD_SAMPLE: case PAGE_LIBRARY_SAMPLE: return; /* * storlek says pattern editor syncs... case PAGE_PATTERN_EDITOR: */ }; if (song_is_instrument_mode()) { sample_synchronize_to_instrument(); } } 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; _set_from_f3(); _set_from_f4(); if (new_page != PAGE_HELP) status.current_help_index = ACTIVE_PAGE.help_index; if (status.dialog_type & DIALOG_MENU) { menu_hide(); } else if (status.dialog_type != DIALOG_NONE) { return; } /* update the pointers */ widgets = ACTIVE_PAGE.widgets; selected_widget = &(ACTIVE_PAGE.selected_widget); total_widgets = &(ACTIVE_PAGE.total_widgets); if (ACTIVE_PAGE.set_page) ACTIVE_PAGE.set_page(); status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ void load_pages(void) { 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, 0); 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); waterfall_load_page(pages + PAGE_WATERFALL); about_load_page(pages+PAGE_ABOUT); config_load_page(pages + PAGE_CONFIG); save_module_load_page(pages + PAGE_EXPORT_MODULE, 1); 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 = current_song->orderlist[0]; if (n > 199) n = 0; set_current_pattern(n); set_current_row(0); song_save_channel_states(); for (n = ARRAY_SIZE(pages) - 1; n >= 0; n--) { if (pages[n].song_changed_cb) pages[n].song_changed_cb(); } /* 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 savecheck(void (*ok)(void *data), void (*cancel)(void *data), void *data) { if (status.flags & SONG_NEEDS_SAVE) { dialog_create(DIALOG_OK_CANCEL, "Current module not saved. Proceed?", ok, cancel, 1, data); } else { ok(data); } } static void exit_ok_confirm(UNUSED void *data) { exit(0); } static void exit_ok(UNUSED void *data) { savecheck(exit_ok_confirm, NULL, NULL); } static void real_load_ok(void *filename) { if (song_load_unchecked(filename)) { set_page((song_get_mode() == MODE_PLAYING) ? PAGE_INFO : PAGE_LOG); } else { set_page(PAGE_LOG); } free(filename); } void song_load(const char *filename) { savecheck(real_load_ok, free, str_dup(filename)); } void show_exit_prompt(void) { /* This used to kill all open dialogs, but that doesn't seem to be necessary. Do keep in mind though, a dialog *might* exist when this function is called (for example, if the WM sends a close request). */ if (status.current_page == PAGE_ABOUT) { /* haven't even started up yet; don't bother confirming */ exit(0); } else if (status.current_page == PAGE_FONT_EDIT) { if (status.flags & STARTUP_FONTEDIT) { dialog_create(DIALOG_OK_CANCEL, "Exit Font Editor?", exit_ok_confirm, NULL, 0, NULL); } else { /* don't ask, just go away */ dialog_destroy_all(); set_page(fontedit_return_page); } } else { dialog_create(DIALOG_OK_CANCEL, ((status.flags & CLASSIC_MODE) ? "Exit Impulse Tracker?" : "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 == KEY_RELEASE) change_focus_to(0); return 1; } } if (k->sym == SDLK_COLON || k->sym == SDLK_SEMICOLON) { if (k->state == KEY_RELEASE) { if (*selected_widget == 0) { change_focus_to(1); } } return 1; } return 0; } static void _timejump_draw(void) { draw_text("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 = current_song->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, (void *) _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; } void show_length_dialog(const char *label, unsigned int length) { char *buf; if (asprintf(&buf, "%s: %3u:%02u:%02u", label, length / 3600, (length / 60) % 60, length % 60) == -1) { perror("asprintf"); return; } dialog_create(DIALOG_OK, buf, free, free, 0, buf); } void show_song_length(void) { show_length_dialog("Total song time", csf_get_length(current_song)); } /* FIXME this is an illogical place to put this but whatever, i just want to get the frigging thing to build */ void set_previous_instrument(void) { if (song_is_instrument_mode()) instrument_set(instrument_get_current() - 1); else sample_set(sample_get_current() - 1); } void set_next_instrument(void) { if (song_is_instrument_mode()) instrument_set(instrument_get_current() + 1); else sample_set(sample_get_current() + 1); } schismtracker-20180209/schism/page_about.c000066400000000000000000000132111323741476300203730ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "video.h" #include "song.h" #include "version.h" /* Eventual TODO: draw the pattern data in the Schism logo in a different color than the words */ #include "auto/logoit.h" #include "auto/logoschism.h" #define LOGO_WIDTH 292 #define LOGO_PITCH 292 #define LOGO_HEIGHT 50 static int fake_driver = 0; static SDL_Surface *it_logo = NULL; static SDL_Surface *schism_logo = NULL; static struct widget widgets_about[1]; static struct vgamem_overlay logo_image = { 23, 17, 58, 24, NULL, 0, 0, 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; case SDLK_F2: case SDLK_F5: case SDLK_F9: case SDLK_F10: // Ctrl + these keys does not lead to a new screen if (k->mod & KMOD_CTRL) break; // Fall through. case SDLK_F1: case SDLK_F3: case SDLK_F4: case SDLK_F11: case SDLK_F12: // Ignore Alt and so on. if (k->mod & (KMOD_ALT | KMOD_SHIFT)) break; dialog_destroy(); return 0; default: break; } /* this way, we can't pull up help here */ return 1; } static void _draw_full(void) { draw_fill_chars(0,0,79,49,0); } void about_load_page(struct page *page) { page->title = ""; page->total_widgets = 0; page->widgets = NULL; page->pre_handle_key = _fixup_ignore_globals; page->help_index = HELP_COPYRIGHT; page->draw_full = _draw_full; page->set_page = show_about; } static void about_close(UNUSED void *data) { if (status.current_page == PAGE_ABOUT) set_page(PAGE_LOAD_MODULE); status.flags |= NEED_UPDATE; } static void about_draw_const(void) { char buf[81]; 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(), "dummy") == 0) { draw_text("No sound card detected", 29, 28, 0, 2); } else { switch (fake_driver) { case 0: draw_text("Sound Blaster 16 detected", 26, 28, 0, 2); draw_text("Port 220h, IRQ 7, DMA 5", 26, 29, 0, 2); break; case 1: /* FIXME: The GUS driver displays the memory settings a bit differently from the SB. If we're "supporting" it, we should probably keep the rest of the UI consistent with our choice. (Also: no love for the AWE cards?) Alternately, it would be totally awesome to probe the system for the actual name and parameters of the card in use :) */ draw_text("Gravis UltraSound detected", 26, 28, 0, 2); draw_text("Port 240h, IRQ 5, 1024k RAM", 26, 29, 0, 2); break; }; } } else { snprintf(buf, 80, "Using %s on %s", song_audio_driver(), video_driver_name()); buf[80] = 0; draw_text(buf, (80 - strlen(buf)) / 2, 25, 0, 2); /* build date? */ draw_text(ver_short_copyright, 15, 27, 1, 2); draw_text(ver_short_based_on, 15, 28, 1, 2); /* XXX if we allow key remapping, need to reflect the *real* help key here */ draw_text("Press F1 for copyright and full credits", 15, 29, 1, 2); } vgamem_ovl_apply(&logo_image); } void show_about(void) { static int didit = 0; struct dialog *d; unsigned char *p; int x, y; fake_driver = (rand() & 3) ? 0 : 1; if (!didit) { vgamem_ovl_alloc(&logo_image); it_logo = xpmdata(_logo_it_xpm); schism_logo = xpmdata(_logo_schism_xpm); didit=1; } if (status.flags & CLASSIC_MODE) { p = it_logo ? it_logo->pixels : NULL; } else { p = schism_logo ? schism_logo->pixels : NULL; } /* this is currently pretty gross */ vgamem_ovl_clear(&logo_image, 2); if (p) { int c = (status.flags & CLASSIC_MODE) ? 11 : 0; for (y = 0; y < LOGO_HEIGHT; y++) { for (x = 0; x < LOGO_WIDTH; x++) { if (p[x]) { vgamem_ovl_drawpixel(&logo_image, x+2, y+6, c); } } vgamem_ovl_drawpixel(&logo_image, x, y+6, 2); vgamem_ovl_drawpixel(&logo_image, x+1, y+6, 2); p += LOGO_PITCH; } } create_button(widgets_about + 0, 33,32, 12, 0,0,0,0,0, dialog_yes_NULL, "Continue", 3); d = dialog_create_custom(11,16, 58, 19, widgets_about, 1, 0, about_draw_const, NULL); d->action_yes = about_close; d->action_no = about_close; d->action_cancel = about_close; /* okay, in just a moment, we're going to the module page. * if your modules dir is large enough, this causes an annoying pause. * to defeat this, we start scanning *NOW*. this makes startup "feel" * faster. */ status.flags |= DIR_MODULES_CHANGED; pages[PAGE_LOAD_MODULE].set_page(); } schismtracker-20180209/schism/page_blank.c000066400000000000000000000032701323741476300203540ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" /* --------------------------------------------------------------------- */ 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); } schismtracker-20180209/schism/page_config.c000066400000000000000000000234721323741476300205400ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #include "snd_gm.h" #include "disko.h" /* --------------------------------------------------------------------- */ #define SAVED_AT_EXIT "System configuration will be saved at exit" static void config_set_page(void); static struct widget widgets_config[32]; static const char *const time_displays[] = { "Off", "Play / Elapsed", "Play / Clock", "Play / Off", "Elapsed", "Clock", "Absolute", NULL }; static const char *const vis_styles[] = { "Off", "Memory Stats", "Oscilloscope", "VU Meter", "Monoscope", "Spectrum", NULL }; static const char *const sharp_flat[] = { "Sharps (#)", "Flats (b)", NULL }; static const char *const output_channels[] = { "Mono", "Stereo", NULL }; static int sample_rate_cursor = 0; static const char *const bit_rates[] = { "8 Bit", "16 Bit", //"24 Bit", "32 Bit", NULL }; static const char *const midi_modes[] = { "IT semantics", "Tracker semantics", NULL }; static const int video_fs_group[] = { 9, 10, -1 }; static int video_group[] = { 11, 12, 13, 14, -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_sharp_flat_toggle(widgets_config[6].d.menutoggle.state); GM_Reset(0); if (widgets_config[8].d.toggle.state) { status.flags |= MIDI_LIKE_TRACKER; } else { status.flags &= ~MIDI_LIKE_TRACKER; } 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 = NULL; static int video_revert_fs = 0; static void video_mode_keep(UNUSED void*ign) { status_text_flash(SAVED_AT_EXIT); config_set_page(); status.flags |= NEED_UPDATE; } static void video_mode_cancel(UNUSED void*ign) { if (video_revert_driver) { video_setup(video_revert_driver); video_startup(); } 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) { dialog_destroy(); video_mode_cancel(NULL); 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, select OK.", 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, dialog_yes_NULL, "OK", 4); create_button(video_dialog_widgets+1, 42,28,8, 1, 1, 0, 1, 0, dialog_cancel_NULL, "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[11].d.togglebutton.state) { new_video_driver = "sdl"; } else if (widgets_config[12].d.togglebutton.state) { new_video_driver = "yuv"; } else if (widgets_config[13].d.togglebutton.state) { new_video_driver = "gl"; } else if (widgets_config[14].d.togglebutton.state) { new_video_driver = "directdraw"; } else { new_video_driver = "sdl"; } if (widgets_config[9].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_setup(new_video_driver); video_startup(); } 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("Accidentals",6,22, 0, 2); draw_text("Time Display",5,23, 0, 2); draw_text("MIDI mode", 8,25, 0, 2); draw_text("Video Driver:", 2, 28, 0, 2); draw_text("Full Screen:", 38, 28, 0, 2); draw_fill_chars(18, 15, 34, 25, 0); draw_box(17,14,35,26, BOX_THIN | BOX_INNER | BOX_INSET); for (n = 18; n < 35; n++) { draw_char(154, n, 19, 3, 0); draw_char(154, n, 24, 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.menutoggle.state = !!(status.flags & ACCIDENTALS_AS_FLATS); widgets_config[7].d.menutoggle.state = status.time_display; widgets_config[8].d.toggle.state = !!(status.flags & MIDI_LIKE_TRACKER); widgets_config[9].d.togglebutton.state = video_is_fullscreen(); widgets_config[10].d.togglebutton.state = !video_is_fullscreen(); nn = video_driver_name(); widgets_config[11].d.togglebutton.state = (strcasecmp(nn,"sdl") == 0); widgets_config[12].d.togglebutton.state = (strcasecmp(nn,"yuv") == 0); widgets_config[13].d.togglebutton.state = (strcasecmp(nn,"opengl") == 0); widgets_config[14].d.togglebutton.state = (strcasecmp(nn,"directdraw") == 0); } /* --------------------------------------------------------------------- */ void config_load_page(struct page *page) { page->title = "System Configuration (Ctrl-F1)"; page->draw_const = config_draw_const; page->set_page = config_set_page; page->total_widgets = 15; 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_menutoggle(widgets_config+6, 18, 22, 5,7,6,6,7, change_ui_settings, sharp_flat); create_menutoggle(widgets_config+7, 18, 23, 6,8,7,7,8, change_ui_settings, time_displays); //// create_menutoggle(widgets_config+8, 18, 25, 7,11,8,8,11, change_ui_settings, midi_modes); //// create_togglebutton(widgets_config+9, 44, 30, 5, 8,9,11,10,10, change_video_settings, "Yes", 2, video_fs_group); create_togglebutton(widgets_config+10, 54, 30, 5, 10,10,9,10,0, change_video_settings, "No", 2, video_fs_group); //// create_togglebutton(widgets_config+11, 6, 30, 26, 8,12,11,9,12, change_video_settings, "SDL Video Surface", 2, video_group); create_togglebutton(widgets_config+12, 6, 33, 26, 11,13,12,9,13, change_video_settings, "YUV Video Overlay", 2, video_group); create_togglebutton(widgets_config+13, 6, 36, 26, 12,14,13,9,14, change_video_settings, "OpenGL Graphic Context", 2, video_group); create_togglebutton(widgets_config+14, 6, 39, 26, 13,14,14,9,9, change_video_settings, "DirectDraw Surface", 2, video_group); #ifndef WIN32 /* patch ddraw out */ video_group[3] = -1; widgets_config[14].d.togglebutton.state = 0; widgets_config[13].next.down = 13; widgets_config[13].next.tab = 9; page->total_widgets--; #endif } schismtracker-20180209/schism/page_help.c000066400000000000000000000175631323741476300202270ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" /* --------------------------------------------------------------------- */ /* Line type characters (the marker at the start of each line) */ enum { LTYPE_NORMAL = '|', LTYPE_BIOS = '+', LTYPE_SCHISM = ':', LTYPE_SCHISM_BIOS = ';', LTYPE_CLASSIC = '!', LTYPE_SEPARATOR = '%', LTYPE_DISABLED = '#', LTYPE_GRAPHIC = '=', }; /* Types that should be hidden from view in classic/non-classic mode */ #define LINE_SCHISM_HIDDEN(p) (0[p] == LTYPE_CLASSIC) #define LINE_CLASSIC_HIDDEN(p) (0[p] == LTYPE_SCHISM || 0[p] == LTYPE_SCHISM_BIOS) /* Types that should be rendered with the standard font */ #define LINE_BIOS(p) (0[p] == LTYPE_BIOS || 0[p] == LTYPE_SCHISM_BIOS) static struct widget widgets_help[2]; /* Pointers to the start of each line, and total line counts, for each help text, for both classic and "normal" mode. For example, help_cache[HELP_PATTERN_EDITOR][0].lines[3][5] is the fifth character of the third line of the non-classic-mode help for the pattern editor. Each line is terminated by some combination of \r and \n, or \0. */ static struct { const char **lines; int num_lines; } help_cache[HELP_NUM_ITEMS][2] = {{{NULL, 0}}}; /* Shortcuts for sanity -- this will point to the currently applicable help. */ #define CURRENT_HELP_LINECACHE (help_cache[status.current_help_index][!!(status.flags & CLASSIC_MODE)].lines) #define CURRENT_HELP_LINECOUNT (help_cache[status.current_help_index][!!(status.flags & CLASSIC_MODE)].num_lines) /* should always point to the currently applicable help text -- cached to prevent repetitively checking things that aren't going to change */ static const char **lines = NULL; static int num_lines = 0; static int top_line = 0; static const char blank_line[] = {LTYPE_NORMAL, '\0'}; static const char separator_line[] = {LTYPE_SEPARATOR, '\0'}; static int help_text_lastpos[HELP_NUM_ITEMS] = {0}; /* This isn't defined in an .h file since it's only used here. */ extern const char *help_text[]; /* --------------------------------------------------------------------- */ 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; int lp; const char **ptr; const char graphic_chars[] = {0, 0x89, 0x8f, 0x96, 0x84, 0, 0x91, 0x8b, 0x86, 0x8a}; char ch; draw_fill_chars(2, 13, 77, 44, 0); ptr = lines + top_line; for (pos = 13, n = top_line; pos < 45; pos++, n++) { switch (**ptr) { default: lp = strcspn(*ptr+1, "\015\012"); if (LINE_BIOS(*ptr)) { draw_text_bios_len(*ptr + 1, lp, 2, pos, 6, 0); } else { draw_text_len(*ptr + 1, lp, 2, pos, **ptr == LTYPE_DISABLED ? 7 : 6, 0); } break; case LTYPE_GRAPHIC: lp = strcspn(*ptr + 1, "\015\012"); for (x = 1; x <= lp; x++) { ch = ptr[0][x]; if (ch >= '1' && ch <= '9') ch = graphic_chars[ch - '0']; draw_char(ch, x + 1, pos, 6, 0); } break; case LTYPE_SEPARATOR: for (x = 2; x < 78; x++) draw_char(154, x, pos, 6, 0); 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 == MOUSE_SCROLL_UP) { new_line -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_line += MOUSE_SCROLL_LINES; } else if (k->mouse != MOUSE_NONE) { return 0; } switch (k->sym) { case SDLK_ESCAPE: if (k->state == KEY_RELEASE) return 1; set_page(status.previous_page); return 1; case SDLK_UP: if (k->state == KEY_RELEASE) return 1; new_line--; break; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 1; new_line++; break; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 1; new_line -= 32; break; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 1; new_line += 32; break; case SDLK_HOME: if (k->state == KEY_RELEASE) return 1; new_line = 0; break; case SDLK_END: if (k->state == KEY_RELEASE) return 1; new_line = num_lines - 32; break; default: if (k->mouse != MOUSE_NONE) { if (k->state == KEY_RELEASE) return 1; } else { return 0; } } new_line = CLAMP(new_line, 0, num_lines - 32); if (new_line != top_line) { top_line = new_line; help_text_lastpos[status.current_help_index] = top_line; status.flags |= NEED_UPDATE; } return 1; } /* --------------------------------------------------------------------- */ static void help_set_page(void) { const 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 = help_text_lastpos[status.current_help_index]; lines = CURRENT_HELP_LINECACHE; if (lines) { num_lines = CURRENT_HELP_LINECOUNT; return; } /* how many lines? */ global_lines = get_num_lines(help_text[HELP_GLOBAL]); if (have_local_help) { local_lines = get_num_lines(help_text[status.current_help_index]); num_lines = local_lines + global_lines + 5; } else { num_lines = global_lines + 2; } /* allocate the array */ lines = CURRENT_HELP_LINECACHE = mem_calloc(num_lines + 1, sizeof(char *)); /* page help text */ if (have_local_help) { ptr = help_text[status.current_help_index]; while (local_lines--) { if (status.flags & CLASSIC_MODE) { if (!LINE_CLASSIC_HIDDEN(ptr)) lines[cur_line++] = ptr; } else { if (!LINE_SCHISM_HIDDEN(ptr)) lines[cur_line++] = ptr; } ptr = strpbrk(ptr, "\015\012"); if (*ptr == 13) ptr++; if (*ptr == 10) ptr++; } lines[cur_line++] = blank_line; lines[cur_line++] = separator_line; } lines[cur_line++] = blank_line; /* global help text */ ptr = help_text[HELP_GLOBAL]; while (global_lines--) { if (status.flags & CLASSIC_MODE) { if (!LINE_CLASSIC_HIDDEN(ptr)) lines[cur_line++] = ptr; } else { if (!LINE_SCHISM_HIDDEN(ptr)) lines[cur_line++] = ptr; } ptr = strpbrk(ptr, "\015\012"); if (*ptr == 13) ptr++; if (*ptr == 10) ptr++; } lines[cur_line++] = blank_line; if (have_local_help) lines[cur_line++] = separator_line; lines[cur_line] = NULL; CURRENT_HELP_LINECOUNT = 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); } schismtracker-20180209/schism/page_info.c000066400000000000000000001157301323741476300202250ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #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 { const char *id; void (*draw) (int base, int height, int active, int first_channel); void (*click) (int x, int y, int num_vis_channel, 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, int active, int first_channel) { int smp, pos, fg, c = first_channel; char buf[16]; const char *ptr; /* FVl - 0-128, final calculated volume, taking everything into account: (sample volume, sample global volume, instrument volume, inst. global volume, volume envelope, volume swing, fadeout, channel volume, song global volume, effects (I/Q/R) Vl - 0-64, sample volume / volume column (also affected by I/Q/R) CV - 0-64, channel volume (M/N) SV - 0-64, sample global volume + inst global volume Fde - 0-512, HALF the fade (initially 1024, and subtracted by instrument fade value each tick when fading out) Pn - 0-64 (or "Su"), final channel panning + pan swing + pitch/pan + current pan envelope value! + Yxx (note: suggests that Xxx panning is reduced to 64 values when it's applied?) PE - 0-64, pan envelope note: this value is not changed if pan env is turned off (e.g. with S79) -- so it's copied all of the above are still set to valid values in sample mode */ draw_fill_chars(5, base + 1, 29, base + height - 2, 0); draw_box(4, base, 30, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); draw_text("Frequency", 6, base, 2, 1); draw_text("Position", 17, base, 2, 1); draw_text("Smp", 27, base, 2, 1); draw_fill_chars(32, base + 1, 56, base + height - 2, 0); draw_box(31, base, 57, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); draw_text("FVl", 32, base, 2, 1); draw_text("Vl", 36, base, 2, 1); draw_text("CV", 39, base, 2, 1); draw_text("SV", 42, base, 2, 1); draw_text("VE", 45, base, 2, 1); draw_text("Fde", 48, base, 2, 1); draw_text("Pn", 52, base, 2, 1); draw_text("PE", 55, base, 2, 1); 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("NNA", 59, base, 2, 1); draw_text("Tot", 63, base, 2, 1); } for (pos = base + 1; pos < base + height - 1; pos++, c++) { song_channel_t *channel = current_song->channels + c - 1; song_voice_t *voice = current_song->voices + 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(num99tostr(c, buf), 2, pos, fg, 2); /* channel number */ 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_text("---\xa8", 59, pos, 2, 0); /* will be overwritten if something's playing */ /* count how many voices claim this channel */ int nv, tot; for (nv = tot = 0; nv < MAX_VOICES; nv++) { song_voice_t *v = current_song->voices + nv; if (v->master_channel == (unsigned int) c && v->current_sample_data && v->length) tot++; } if (voice->current_sample_data && voice->length) tot++; draw_text(numtostr(3, tot, buf), 63, pos, 2, 0); } if (voice->current_sample_data && voice->length && voice->ptr_sample) { // again with the hacks... smp = voice->ptr_sample - current_song->samples; if (smp <= 0 || smp >= MAX_SAMPLES) continue; } else { continue; } // Frequency sprintf(buf, "%10d", voice->sample_freq); draw_text(buf, 5, pos, 2, 0); // Position sprintf(buf, "%10d", voice->position); draw_text(buf, 16, pos, 2, 0); draw_text(numtostr(3, smp, buf), 27, pos, 2, 0); // Smp draw_text(numtostr(3, voice->final_volume / 128, buf), 32, pos, 2, 0); // FVl draw_text(numtostr(2, voice->volume >> 2, buf), 36, pos, 2, 0); // Vl draw_text(numtostr(2, voice->global_volume, buf), 39, pos, 2, 0); // CV draw_text(numtostr(2, voice->ptr_sample->global_volume, buf), 42, pos, 2, 0); // SV // FIXME: VE means volume envelope. Also, voice->instrument_volume is actually sample global volume draw_text(numtostr(2, voice->instrument_volume, buf), 45, pos, 2, 0); // VE draw_text(numtostr(3, voice->fadeout_volume / 128, buf), 48, pos, 2, 0); // Fde // Pn if (voice->flags & CHN_SURROUND) draw_text("Su", 52, pos, 2, 0); else draw_text(numtostr(2, voice->panning >> 2, buf), 52, pos, 2, 0); draw_text(numtostr(2, voice->final_panning >> 2, buf), 55, pos, 2, 0); // PE if (song_is_instrument_mode()) { switch (voice->nna) { case NNA_NOTECUT: ptr = "Cut"; break; case NNA_CONTINUE: ptr = "Con"; break; case NNA_NOTEOFF: ptr = "Off"; break; case NNA_NOTEFADE: ptr = "Fde"; break; default: ptr = "???"; break; }; draw_text(ptr, 59, pos, 2, 0); } } } static void info_draw_samples(int base, int height, int active, int first_channel) { int vu, smp, ins, n, pos, fg, fg2, 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); } if (song_get_mode() == MODE_STOPPED) { for (pos = base + 1; pos < base + height - 1; pos++, c++) { song_channel_t *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_voice_t *voice = current_song->voices + c - 1; /* always draw the channel number */ if (c == selected_channel) fg = (voice->flags & CHN_MUTE) ? 6 : 3; else if (voice->flags & CHN_MUTE) fg = 2; /* same as bg */ else fg = active ? 1 : 0; draw_text(numtostr(2, c, buf), 2, pos, fg, 2); if (!(voice->current_sample_data && voice->length)) continue; /* first box: vu meter */ if (velocity_mode) vu = voice->final_volume >> 8; else vu = voice->vu_meter >> 2; if (voice->flags & CHN_MUTE) { fg = 1; fg2 = 2; } else { fg = 5; fg2 = 4; } draw_vu_meter(5, pos, 24, vu, fg, fg2); /* second box: sample number/name */ ins = song_get_instrument_number(voice->ptr_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 (voice->ptr_sample) smp = voice->ptr_sample - current_song->samples; else smp = ins = 0; if(smp < 0 || smp >= MAX_SAMPLES) smp = ins = 0; /* This sample is not in the sample array */ if (smp) { draw_text(num99tostr(smp, buf), 31, pos, 6, 0); if (ins) { draw_char('/', 33, pos, 6, 0); draw_text(num99tostr(ins, buf), 34, pos, 6, 0); n = 36; } else { n = 33; } if (voice->volume == 0) fg = 4; else if (voice->flags & (CHN_KEYOFF | CHN_NOTEFADE)) fg = 7; else fg = 6; draw_char(':', n++, pos, fg, 0); if (instrument_names && voice->ptr_instrument) { ptr = voice->ptr_instrument->name; } else { ptr = current_song->samples[smp].name; } draw_text_len(ptr, 25, n, pos, 6, 0); } else if (ins && voice->ptr_instrument && voice->ptr_instrument->midi_channel_mask) { // XXX why? what? if (voice->ptr_instrument->midi_channel_mask >= 0x10000) { draw_text(numtostr(2, ((c-1) % 16)+1, buf), 31, pos, 6, 0); } else { int ch = 0; while(!(voice->ptr_instrument->midi_channel_mask & (1 << ch))) ++ch; draw_text(numtostr(2, ch, buf), 31, pos, 6, 0); } draw_char('/', 33, pos, 6, 0); draw_text(num99tostr(ins, buf), 34, pos, 6, 0); n = 36; if (voice->volume == 0) fg = 4; else if (voice->flags & (CHN_KEYOFF | CHN_NOTEFADE)) fg = 7; else fg = 6; draw_char(':', n++, pos, fg, 0); ptr = voice->ptr_instrument->name; draw_text_len( ptr, 25, n, pos, 6, 0); } else { continue; } /* last box: panning. this one's much easier than the * other two, thankfully :) */ if (song_is_stereo()) { if (!voice->ptr_sample) { /* nothing... */ } else if (voice->flags & CHN_SURROUND) { draw_text("Surround", 64, pos, 2, 0); } else if (voice->final_panning >> 2 == 0) { draw_text("Left", 64, pos, 2, 0); } else if ((voice->final_panning + 3) >> 2 == 64) { draw_text("Right", 68, pos, 2, 0); } else { draw_thumb_bar(64, pos, 9, 0, 256, voice->final_panning, 0); } } } } static void _draw_fill_notes(int col, int first_row, int height, int num_channels, int channel_width, int separator, draw_note_func draw_note, int bg) { int row_pos, chan_pos; for (row_pos = first_row; row_pos < first_row + height; row_pos++) { for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { draw_note(col + channel_width * chan_pos, row_pos, blank_note, -1, 6, bg); if (separator) draw_char(168, (col - 1 + channel_width * (chan_pos + 1)), row_pos, 2, bg); } draw_note(col + channel_width * chan_pos, row_pos, blank_note, -1, 6, bg); } } 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(); const song_note_t *note; // These can't be const because of song_get_pattern, but song_get_pattern is stupid and smells funny. song_note_t *cur_pattern, *prev_pattern, *next_pattern; const song_note_t *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; char buf[4]; if (separator) channel_width++; #if 0 /* can't do this here -- each view does channel numbers differently, don't draw on top of them */ draw_box(4, base, 5 + num_channels * channel_width - !!separator, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); #endif 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 (current_song->orderlist[current_order] >= 200) { /* this does, in fact, happen. just pretend that * it's stopped :P */ default: /* stopped */ draw_fill_chars(5, base + 1, 4 + num_channels * channel_width - !!separator, base + height - 2, 0); return; } cur_pattern_rows = song_get_pattern(current_song->orderlist[current_order], &cur_pattern); if (current_order > 0 && current_song->orderlist[current_order - 1] < 200) prev_pattern_rows = song_get_pattern(current_song->orderlist[current_order - 1], &prev_pattern); else prev_pattern = NULL; if (current_order < 255 && current_song->orderlist[current_order + 1] < 200) next_pattern_rows = song_get_pattern(current_song->orderlist[current_order + 1], &next_pattern); else next_pattern = NULL; break; } /* -2 for the top and bottom border, -1 because if there are an even number * of rows visible, the current row is drawn above center. */ rows_before = (height - 3) / 2; /* "fake" channels (hack for 64-channel view) */ if (num_channels > 64) { _draw_fill_notes(5 + 64, base + 1, height - 2, num_channels - 64, channel_width, separator, draw_note, 0); _draw_fill_notes(5 + 64, base + 1 + rows_before, 1, num_channels - 64, channel_width, separator, draw_note, 14); num_channels = 64; } /* 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) { _draw_fill_notes(5, base + 1, row_pos - base, num_channels, channel_width, separator, draw_note, 0); 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) { _draw_fill_notes(5, row_pos, base + height - row_pos - 1, num_channels, channel_width, separator, draw_note, 0); 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; 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 (current_song->channels[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_8(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; char buf[4]; draw_box(4, base, 76, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (chan = first_channel, chan_pos = 0; chan_pos < 8; chan++, chan_pos++) { if (current_song->channels[chan - 1].flags & CHN_MUTE) fg = (chan == selected_channel ? 6 : 1); else fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); draw_char(0, 6 + 9 * chan_pos, base, 1, 1); draw_char(0, 6 + 9 * chan_pos + 1, base, 1, 1); draw_text(numtostr(2, chan, buf), 6 + 9 * chan_pos + 2, base, fg, 1); draw_char(0, 6 + 9 * chan_pos + 4, base, 1, 1); draw_char(0, 6 + 9 * chan_pos + 5, base, 1, 1); } _draw_track_view(base, height, first_channel, 8, 8, 1, draw_note_8); } static void info_draw_track_10(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; char buf[4]; 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 (current_song->channels[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_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 (current_song->channels[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_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 (current_song->channels[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_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 (current_song->channels[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_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 (current_song->channels[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_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 (current_song->channels[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); _draw_track_view(base, height, first_channel, nchan, 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(buf, 2, base, fg, 2); snprintf(buf, 32, "Global Volume: %d", song_get_current_global_volume()); draw_text(buf, 4, base + 1, fg, 2); } /* Yay it works, only took me forever and a day to get it right. */ static void info_draw_note_dots(int base, int height, int active, int first_channel) { int fg, v; int c, pos; int n; song_voice_t *voice; char buf[4]; uint8_t d, dn; uint8_t dot_field[73][36] = { {0} }; // f#2 -> f#8 = 73 columns 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); n = current_song->num_voices; while (n--) { voice = current_song->voices + current_song->voice_mix[n]; /* 31 = f#2, 103 = f#8. (i hope ;) */ if (!(voice->ptr_sample && voice->note >= 31 && voice->note <= 103)) continue; pos = voice->master_channel ?: (1 + current_song->voice_mix[n]); if (pos < first_channel) continue; pos -= first_channel; if (pos > height - 1) continue; fg = (voice->flags & CHN_MUTE) ? 1 : ((voice->ptr_sample - current_song->samples) % 4 + 2); if (velocity_mode || (status.flags & CLASSIC_MODE)) v = (voice->final_volume + 2047) >> 11; else v = (voice->vu_meter + 31) >> 5; d = dot_field[voice->note - 31][pos]; dn = (v << 4) | fg; if (dn > d) dot_field[voice->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] ?: 0x06; fg = d & 0xf; v = d >> 4; draw_char(v + 193, n + 5, pos + base + 1, fg, 0); } if (c == selected_channel) { fg = (current_song->channels[c - 1].flags & CHN_MUTE) ? 6 : 3; } else { if (current_song->channels[c - 1].flags & CHN_MUTE) continue; fg = active ? 1 : 0; } draw_text(numtostr(2, c, buf), 2, pos + base + 1, fg, 2); } } /* --------------------------------------------------------------------- */ /* click receivers */ static void click_chn_x(int x, int w, int skip, int fc) { while (x > 0 && fc <= 64) { if (x < w) { selected_channel = CLAMP(fc, 1, 64); return; } fc++; x -= w; x -= skip; } } static void click_chn_is_x(int x, UNUSED int y, int nc, int fc) { if (x < 5) return; x -= 4; switch (nc) { case 5: click_chn_x(x, 13, 1, fc); break; case 10: click_chn_x(x, 7, 0, fc); break; case 12: click_chn_x(x, 6, 0, fc); break; case 18: click_chn_x(x, 3, 1, fc); break; case 24: click_chn_x(x, 3, 0, fc); break; case 36: click_chn_x(x, 2, 0, fc); break; case 64: click_chn_x(x, 1, 0, fc); break; }; } static void click_chn_is_y_nohead(UNUSED int x, int y, UNUSED int nc, int fc) { selected_channel = CLAMP(y+fc, 1, 64); } static void click_chn_is_y(UNUSED int x, int y, UNUSED int nc, int fc) { if (!y) return; selected_channel = CLAMP((y+fc)-1, 1, 64); } static void click_chn_nil(UNUSED int x, UNUSED int y, UNUSED int nc, UNUSED int fc) { /* do nothing */ } /* --------------------------------------------------------------------- */ /* declarations of the window types */ #define TRACK_VIEW(n) {"track" # n, info_draw_track_##n, click_chn_is_x, 1, n} static const struct info_window_type window_types[] = { {"samples", info_draw_samples, click_chn_is_y_nohead, 0, -2}, TRACK_VIEW(5), TRACK_VIEW(8), TRACK_VIEW(10), TRACK_VIEW(12), TRACK_VIEW(18), TRACK_VIEW(24), TRACK_VIEW(36), TRACK_VIEW(64), {"global", info_draw_channels, click_chn_nil, 1, 0}, {"dots", info_draw_note_dots, click_chn_is_y_nohead, 0, -2}, {"tech", info_draw_technical, click_chn_is_y, 1, -2}, }; #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 (to squeeze in an extra row on the top window) */ 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 int info_handle_click(int x, int y) { int n; if (y < 13) return 0; /* NA */ y -= 13; for (n = 0; n < num_windows; n++) { if (y < windows[n].height) { window_types[windows[n].type].click( x, y, window_types[windows[n].type].channels, windows[n].first_channel); return 1; } y -= windows[n].height; } return 0; } 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 */ void cfg_save_info(cfg_file_t *cfg) { // for 5 windows, roughly 12 chars per window, this is way more than enough char buf[256] = ""; char *s = buf; int rem = sizeof(buf) - 1; int len; int i; for (i = 0; i < num_windows; i++) { len = snprintf(s, rem, " %s %d", window_types[windows[i].type].id, windows[i].height); if (!len) { // this should not ever happen break; } rem -= len; s += len; } buf[255] = '\0'; // (don't write the first space to the config) cfg_set_string(cfg, "Info Page", "layout", buf + 1); } static void cfg_load_info_old(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; if (windows[i].type >= 2) { // compensate for added 8-channel view windows[i].type++; } 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 = 9; /* active channels */ windows[1].height = 3; windows[2].type = 6; /* 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; } void cfg_load_info(cfg_file_t *cfg) { int n; char buf[256]; char *left, *right; size_t len; if (!cfg_get_string(cfg, "Info Page", "layout", buf, 255, NULL)) { cfg_load_info_old(cfg); return; } left = buf; num_windows = 0; do { left += strspn(left, " \t"); len = strcspn(left, " \t"); if (!len) { break; } left[len] = '\0'; // chop it into pieces windows[num_windows].first_channel = 1; windows[num_windows].type = -1; for (n = 0; n < NUM_WINDOW_TYPES; n++) { if (strcasecmp(window_types[n].id, left) == 0) { windows[num_windows].type = n; break; } } // (a pythonic for...else would be lovely right about here) if (windows[num_windows].type == -1) { break; } right = left + len + 1; left[len] = '\0'; n = strtol(right, &left, 10); if (!left || left == right || n < 3) { // failed to parse any digits, or number is too small break; } windows[num_windows++].height = n; } while (num_windows < MAX_WINDOWS - 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, p, order; if (k->mouse == MOUSE_CLICK || k->mouse == MOUSE_DBLCLICK) { p = selected_channel; n = info_handle_click(k->x, k->y); if (k->mouse == MOUSE_DBLCLICK) { if (p == selected_channel) { set_current_channel(selected_channel); order = song_get_current_order(); if (song_get_mode() == MODE_PLAYING) { n = current_song->orderlist[order]; } else { n = song_get_playing_pattern(); } if (n < 200) { set_current_order(order); set_current_pattern(n); set_current_row(song_get_current_row()); set_page(PAGE_PATTERN_EDITOR); } } } return n; } /* 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 == KEY_PRESS) return 1; set_current_channel(selected_channel); order = song_get_current_order(); if (song_get_mode() == MODE_PLAYING) { n = current_song->orderlist[order]; } else { n = song_get_playing_pattern(); } 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 == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 1; song_flip_stereo(); status_text_flash("Left/right outputs reversed"); return 1; } return 0; case SDLK_PLUS: if (k->state == KEY_RELEASE) return 1; if (song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() + 1); } return 1; case SDLK_MINUS: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 1; if (k->mod & KMOD_ALT) { song_toggle_stereo(); status_text_flash("Stereo %s", song_is_stereo() ? "Enabled" : "Disabled"); } else { 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 == KEY_RELEASE) 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 == KEY_RELEASE) 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) && !(k->mod & KMOD_ALT)) return 0; if (k->state == KEY_RELEASE) return 1; if (selected_channel > 1) selected_channel--; break; case SDLK_DOWN: if (k->state == KEY_RELEASE) 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) && !(k->mod & KMOD_ALT)) return 0; if (k->state == KEY_RELEASE) return 1; if (selected_channel < 64) selected_channel++; break; case SDLK_HOME: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; selected_channel = 1; break; case SDLK_END: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; selected_channel = song_find_last_channel(); break; case SDLK_INSERT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 1; windows[selected_window].type = (windows[selected_window].type + 1) % NUM_WINDOW_TYPES; break; case SDLK_TAB: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) 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) { if (song_get_mode() != MODE_STOPPED) 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; create_other(widgets_info + 0, 0, info_page_handle_key, info_page_redraw); } schismtracker-20180209/schism/page_instruments.c000066400000000000000000002520431323741476300216640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "it.h" #include "page.h" #include "song.h" #include "dmoz.h" #include "video.h" #include /* --------------------------------------------------------------------- */ /* 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]; /* rastops for envelope */ static struct vgamem_overlay env_overlay = { 32, 18, 65, 25, NULL, 0, 0, 0 }; /* toggled when pressing "," on the note table's sample field * more of a boolean than a bit mask -delt. */ static int note_sample_mask = 1; static struct widget *get_page_widgets(void) { switch (instrument_list_subpage) { case PAGE_INSTRUMENT_LIST_GENERAL: return widgets_general; case PAGE_INSTRUMENT_LIST_VOLUME: return widgets_volume; case PAGE_INSTRUMENT_LIST_PANNING: return widgets_panning; case PAGE_INSTRUMENT_LIST_PITCH: return widgets_pitch; }; return widgets_general; } static const int subpage_switches_group[5] = { 1, 2, 3, 4, -1 }; static const int nna_group[5] = { 6, 7, 8, 9, -1 }; static const int dct_group[5] = { 10, 11, 12, 13, -1 }; static const int dca_group[4] = { 14, 15, 16, -1 }; static const char *const pitch_envelope_states[] = { "Off", "On Pitch", "On Filter", NULL }; static int top_instrument = 1; static int current_instrument = 1; static int _altswap_lastvis = 99; // for alt-down instrument-swapping 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; static void set_subpage(int page); /* playback */ static int last_note = 61; /* C-5 */ /* strange saved envelopes */ static song_envelope_t saved_env[10]; static unsigned int flags[10] = {0}; /* --------------------------------------------------------------------------------------------------------- */ static void save_envelope(int slot, song_envelope_t *e, unsigned int sec) { song_instrument_t *ins; slot = ((unsigned)slot)%10; ins = song_get_instrument(current_instrument); memcpy(&saved_env[slot], e, sizeof(song_envelope_t)); switch (sec) { case ENV_VOLUME: flags[slot] = ins->flags & (ENV_VOLUME|ENV_VOLSUSTAIN|ENV_VOLLOOP|ENV_VOLCARRY); break; case ENV_PANNING: flags[slot] = ((ins->flags & ENV_PANNING) ? ENV_VOLUME : 0) | ((ins->flags & ENV_PANSUSTAIN) ? ENV_VOLSUSTAIN : 0) | ((ins->flags & ENV_PANLOOP) ? ENV_VOLLOOP : 0) | ((ins->flags & ENV_PANCARRY) ? ENV_VOLCARRY : 0) | (ins->flags & ENV_SETPANNING); break; case ENV_PITCH: flags[slot] = ((ins->flags & ENV_PITCH) ? ENV_VOLUME : 0) | ((ins->flags & ENV_PITCHSUSTAIN) ? ENV_VOLSUSTAIN : 0) | ((ins->flags & ENV_PITCHLOOP) ? ENV_VOLLOOP : 0) | ((ins->flags & ENV_PITCHCARRY) ? ENV_VOLCARRY : 0) | (ins->flags & ENV_FILTER); break; }; } static void restore_envelope(int slot, song_envelope_t *e, unsigned int sec) { song_instrument_t *ins; song_lock_audio(); slot = ((unsigned)slot)%10; ins = song_get_instrument(current_instrument); memcpy(e, &saved_env[slot], sizeof(song_envelope_t)); switch (sec) { case ENV_VOLUME: ins->flags &= ~(ENV_VOLUME|ENV_VOLSUSTAIN|ENV_VOLLOOP|ENV_VOLCARRY); ins->flags |= (flags[slot] & (ENV_VOLUME|ENV_VOLSUSTAIN|ENV_VOLLOOP|ENV_VOLCARRY)); break; case ENV_PANNING: ins->flags &= ~(ENV_PANNING|ENV_PANSUSTAIN|ENV_PANLOOP|ENV_PANCARRY|ENV_SETPANNING); if (flags[slot] & ENV_VOLUME) ins->flags |= ENV_PANNING; if (flags[slot] & ENV_VOLSUSTAIN) ins->flags |= ENV_PANSUSTAIN; if (flags[slot] & ENV_VOLLOOP) ins->flags |= ENV_PANLOOP; if (flags[slot] & ENV_VOLCARRY) ins->flags |= ENV_PANCARRY; ins->flags |= (flags[slot] & ENV_SETPANNING); break; case ENV_PITCH: ins->flags &= ~(ENV_PITCH|ENV_PITCHSUSTAIN|ENV_PITCHLOOP|ENV_PITCHCARRY|ENV_FILTER); if (flags[slot] & ENV_VOLUME) ins->flags |= ENV_PITCH; if (flags[slot] & ENV_VOLSUSTAIN) ins->flags |= ENV_PITCHSUSTAIN; if (flags[slot] & ENV_VOLLOOP) ins->flags |= ENV_PITCHLOOP; if (flags[slot] & ENV_VOLCARRY) ins->flags |= ENV_PITCHCARRY; ins->flags |= (flags[slot] & ENV_FILTER); break; }; song_unlock_audio(); status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------------------------------------------- */ static void instrument_list_draw_list(void); /* --------------------------------------------------------------------------------------------------------- */ static int _last_vis_inst(void) { int i, j, n; n = 99; j = 0; /* 65 is first visible sample on last page */ for (i = 65; i < MAX_INSTRUMENTS; i++) { if (!csf_instrument_is_empty(current_song->instruments[i])) { j = i; } } while ((j + 34) > n) n += 34; return MIN(n, MAX_INSTRUMENTS - 1); } /* 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_t *ins; if (page_is_instrument_list(status.current_page)) { new_ins = CLAMP(n, 1, _last_vis_inst()); } else { new_ins = CLAMP(n, 0, _last_vis_inst()); } if (current_instrument == new_ins) return; envelope_edit_mode = 0; current_instrument = new_ins; instrument_list_reposition(); ins = song_get_instrument(current_instrument); 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_t *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); for (pos = 0; pos < 120; pos++) { if ((int)(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); for (pos = 0; pos < 120; pos++) { if ((int)(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(int c) { song_instrument_t *ins; if (c < 32) return 0; ins = song_get_instrument(current_instrument); text_add_char(ins->name, c, &instrument_cursor_pos, 25); if (instrument_cursor_pos == 25) instrument_cursor_pos--; get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; return 1; } static void instrument_list_delete_char(void) { song_instrument_t *ins = song_get_instrument(current_instrument); text_delete_char(ins->name, &instrument_cursor_pos, 25); get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } static void instrument_list_delete_next_char(void) { song_instrument_t *ins = song_get_instrument(current_instrument); text_delete_next_char(ins->name, &instrument_cursor_pos, 25); get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } static void clear_instrument_text(void) { song_instrument_t *ins = song_get_instrument(current_instrument); memset(ins->filename, 0, 14); memset(ins->name, 0, 26); if (instrument_cursor_pos != 25) instrument_cursor_pos = 0; get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ static void do_swap_instrument(int n) { if (n >= 1 && n <= _last_vis_inst()) { song_swap_instruments(current_instrument, n); } } static void do_exchange_instrument(int n) { if (n >= 1 && n <= _last_vis_inst()) { song_exchange_instruments(current_instrument, n); } } static void do_copy_instrument(int n) { if (n >= 1 && n <= _last_vis_inst()) { song_copy_instrument(current_instrument, n); } } static void do_replace_instrument(int n) { if (n >= 1 && n <= _last_vis_inst()) { song_replace_instrument(current_instrument, n); } } /* --------------------------------------------------------------------- */ static void instrument_list_draw_list(void) { int pos, n; song_instrument_t *ins; int selected = (ACTIVE_PAGE.selected_widget == 0); int is_current; int ss, cl = 0, cr = 0; int is_playing[MAX_INSTRUMENTS]; char buf[4]; ss = -1; song_get_playing_instruments(is_playing); for (pos = 0, n = top_instrument; pos < 35; pos++, n++) { ins = song_get_instrument(n); is_current = (n == current_instrument); if (ins->played) draw_char(is_playing[n] > 1 ? 183 : 173, 1, 13 + pos, is_playing[n] ? 3 : 1, 2); draw_text(num99tostr(n, buf), 2, 13 + pos, 0, 2); if (instrument_cursor_pos < 25) { /* it's in edit mode */ if (is_current) { draw_text_len(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(ins->name, 25, 5, 13 + pos, 6, 0); } } else { draw_text_len(ins->name, 25, 5, 13 + pos, ((is_current && selected) ? 0 : 6), (is_current ? (selected ? 3 : 14) : 0)); } if (ss == n) { draw_text_len(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; if (k->state == KEY_PRESS && k->mouse != MOUSE_NONE && k->y >= 13 && k->y <= 47 && k->x >= 5 && k->x <= 30) { if (k->mouse == MOUSE_CLICK) { new_ins = (k->y - 13) + top_instrument; if (instrument_cursor_pos < 25) instrument_cursor_pos = MIN(k->x - 5, 24); status.flags |= NEED_UPDATE; } else if (k->mouse == MOUSE_DBLCLICK) { /* this doesn't seem to work, but I think it'd be more useful if double click switched to edit mode */ if (instrument_cursor_pos < 25) { instrument_cursor_pos = 25; get_page_widgets()->accept_text = 0; } else { set_page(PAGE_LOAD_INSTRUMENT); } status.flags |= NEED_UPDATE; return 1; } else if (k->mouse == MOUSE_SCROLL_UP) { top_instrument -= MOUSE_SCROLL_LINES; if (top_instrument < 1) top_instrument = 1; status.flags |= NEED_UPDATE; return 1; } else if (k->mouse == MOUSE_SCROLL_DOWN) { top_instrument += MOUSE_SCROLL_LINES; if (top_instrument > (_last_vis_inst()-34)) top_instrument = _last_vis_inst()-34; status.flags |= NEED_UPDATE; return 1; } } else { switch (k->sym) { case SDLK_UP: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_ALT) { if (current_instrument > 1) { new_ins = current_instrument - 1; song_swap_instruments(current_instrument, new_ins); } } else if (!NO_MODIFIER(k->mod)) { return 0; } else { new_ins--; } break; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_ALT) { // restrict position to the "old" value of _last_vis_inst() // (this is entirely for aesthetic reasons) if (status.last_keysym != SDLK_DOWN && !k->is_repeat) _altswap_lastvis = _last_vis_inst(); if (current_instrument < _altswap_lastvis) { new_ins = current_instrument + 1; song_swap_instruments(current_instrument, new_ins); } } else if (!NO_MODIFIER(k->mod)) { return 0; } else { new_ins++; } break; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) new_ins = 1; else new_ins -= 16; break; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) new_ins = _last_vis_inst(); else new_ins += 16; break; case SDLK_HOME: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; if (instrument_cursor_pos < 25) { instrument_cursor_pos = 0; get_page_widgets()->accept_text = 1; status.flags |= NEED_UPDATE; } return 1; case SDLK_END: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; if (instrument_cursor_pos < 24) { instrument_cursor_pos = 24; get_page_widgets()->accept_text = 1; status.flags |= NEED_UPDATE; } return 1; case SDLK_LEFT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; if (instrument_cursor_pos < 25 && instrument_cursor_pos > 0) { instrument_cursor_pos--; get_page_widgets()->accept_text = 1; status.flags |= NEED_UPDATE; } return 1; case SDLK_RIGHT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; if (instrument_cursor_pos == 25) { get_page_widgets()->accept_text = 0; change_focus_to(1); } else if (instrument_cursor_pos < 24) { get_page_widgets()->accept_text = 1; instrument_cursor_pos++; status.flags |= NEED_UPDATE; } return 1; case SDLK_RETURN: if (k->state == KEY_PRESS) return 0; if (instrument_cursor_pos < 25) { instrument_cursor_pos = 25; get_page_widgets()->accept_text = 0; status.flags |= NEED_UPDATE; } else { get_page_widgets()->accept_text = 1; set_page(PAGE_LOAD_INSTRUMENT); } return 1; case SDLK_ESCAPE: if ((k->mod & KMOD_SHIFT) || instrument_cursor_pos < 25) { if (k->state == KEY_RELEASE) return 1; instrument_cursor_pos = 25; get_page_widgets()->accept_text = 0; status.flags |= NEED_UPDATE; return 1; } return 0; case SDLK_BACKSPACE: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 0; if (k->mod & KMOD_ALT) { if (k->sym == SDLK_c) { clear_instrument_text(); return 1; } } else if ((k->mod & KMOD_CTRL) == 0) { if (!k->unicode) return 0; if (instrument_cursor_pos < 25) { return instrument_list_add_char(k->unicode); } else if (k->sym == SDLK_SPACE) { instrument_cursor_pos = 0; get_page_widgets()->accept_text = 0; status.flags |= NEED_UPDATE; memused_songchanged(); return 1; } } return 0; }; } new_ins = CLAMP(new_ins, 1, _last_vis_inst()); if (new_ins != current_instrument) { instrument_set(new_ins); status.flags |= NEED_UPDATE; memused_songchanged(); } 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_t *ins = song_get_instrument(current_instrument); char 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]) { num99tostr(ins->sample_map[n], buf); } else { buf[0] = buf[1] = 173; buf[2] = 0; } draw_text(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); } } /* draw the little mask thingy at the bottom. Could optimize this.... -delt. Sure can! This could share the same track-view functions that the pattern editor ought to be using. -Storlek */ if (is_selected && !(status.flags & CLASSIC_MODE)) { switch (note_trans_cursor_pos) { case 0: draw_char(171, 36, 48, 3, 2); draw_char(171, 37, 48, 3, 2); draw_char(169, 38, 48, 3, 2); if (note_sample_mask) { draw_char(169, 40, 48, 3, 2); draw_char(169, 41, 48, 3, 2); } break; case 1: draw_char(169, 38, 48, 3, 2); if (note_sample_mask) { draw_char(170, 40, 48, 3, 2); draw_char(170, 41, 48, 3, 2); } break; case 2: case 3: draw_char(note_sample_mask ? 171 : 169, 40, 48, 3, 2); draw_char(note_sample_mask ? 171 : 169, 41, 48, 3, 2); break; }; } } static void instrument_note_trans_transpose(song_instrument_t *ins, int dir) { int i; for (i = 0; i < 120; i++) { ins->note_map[i] = CLAMP(ins->note_map[i]+dir, 1, 120); } } static void instrument_note_trans_insert(song_instrument_t *ins, int pos) { int i; for (i = 119; i > pos; i--) { ins->note_map[i] = ins->note_map[i-1]; ins->sample_map[i] = ins->sample_map[i-1]; } if (pos) { ins->note_map[pos] = ins->note_map[pos-1]+1; } else { ins->note_map[0] = 1; } } static void instrument_note_trans_delete(song_instrument_t *ins, int pos) { int i; for (i = pos; i < 120; i++) { ins->note_map[i] = ins->note_map[i+1]; ins->sample_map[i] = ins->sample_map[i+1]; } ins->note_map[119] = ins->note_map[118]+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_t *ins = song_get_instrument(current_instrument); int c, n; if (k->mouse == MOUSE_CLICK && k->mouse_button == MOUSE_BUTTON_MIDDLE) { if (k->state == KEY_RELEASE) status.flags |= CLIPPY_PASTE_SELECTION; return 1; } else if (k->mouse == MOUSE_SCROLL_UP || k->mouse == MOUSE_SCROLL_DOWN) { if (k->state == KEY_PRESS) { note_trans_top_line += (k->mouse == MOUSE_SCROLL_UP) ? -3 : 3; note_trans_top_line = CLAMP(note_trans_top_line, 0, 119 - 31); status.flags |= NEED_UPDATE; } return 1; } else if (k->mouse != MOUSE_NONE) { if (k->x >= 32 && k->x <= 41 && k->y >= 16 && k->y <= 47) { new_line = note_trans_top_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) { if (k->state == KEY_RELEASE) return 0; switch (k->sym) { case SDLK_UP: instrument_note_trans_transpose(ins, 1); break; case SDLK_DOWN: instrument_note_trans_transpose(ins, -1); break; case SDLK_INSERT: instrument_note_trans_insert(ins, note_trans_sel_line); break; case SDLK_DELETE: instrument_note_trans_delete(ins, note_trans_sel_line); break; case SDLK_n: n = note_trans_sel_line - 1; // the line to copy *from* if (n < 0 || ins->note_map[n] == NOTE_LAST) break; ins->note_map[note_trans_sel_line] = ins->note_map[n] + 1; ins->sample_map[note_trans_sel_line] = ins->sample_map[n]; new_line++; break; case SDLK_p: n = note_trans_sel_line + 1; // the line to copy *from* if (n > (NOTE_LAST - NOTE_FIRST) || ins->note_map[n] == NOTE_FIRST) break; ins->note_map[note_trans_sel_line] = ins->note_map[n] - 1; ins->sample_map[note_trans_sel_line] = ins->sample_map[n]; new_line--; break; case SDLK_a: c = sample_get_current(); for (n = 0; n < (NOTE_LAST - NOTE_FIRST + 1); n++) ins->sample_map[n] = c; if (k->mod & KMOD_SHIFT) { // Copy the name too. memcpy(ins->name, current_song->samples[c].name, 32); } break; default: return 0; } } else { switch (k->sym) { case SDLK_UP: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) sample_set(sample_get_current () - 1); if (!NO_MODIFIER(k->mod)) return 0; if (--new_line < 0) { change_focus_to(1); return 1; } break; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) sample_set(sample_get_current () + 1); if (!NO_MODIFIER(k->mod)) return 0; new_line++; break; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) { instrument_set(current_instrument - 1); return 1; } new_line -= 16; break; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) { instrument_set(current_instrument + 1); return 1; } new_line += 16; break; case SDLK_HOME: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_line = 0; break; case SDLK_END: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_line = 119; break; case SDLK_LEFT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_pos--; break; case SDLK_RIGHT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_pos++; break; case SDLK_RETURN: if (k->state == KEY_PRESS) return 0; if (!NO_MODIFIER(k->mod)) return 0; sample_set(ins->sample_map[note_trans_sel_line]); get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); return 1; case SDLK_LESS: case SDLK_SEMICOLON: case SDLK_COLON: if (k->state == KEY_RELEASE) return 0; sample_set(sample_get_current() - 1); return 1; case SDLK_GREATER: case SDLK_QUOTE: case SDLK_QUOTEDBL: if (k->state == KEY_RELEASE) return 0; sample_set(sample_get_current() + 1); return 1; default: if (k->state == KEY_RELEASE) return 0; switch (note_trans_cursor_pos) { case 0: /* note */ n = kbd_get_note(k); if (!NOTE_IS_NOTE(n)) return 0; ins->note_map[note_trans_sel_line] = n; if (note_sample_mask || (status.flags & CLASSIC_MODE)) 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; /* Made it possible to enter H to R letters on 1st digit for expanded sample slots. -delt. */ 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; } if ((k->sym == SDLK_PERIOD && NO_MODIFIER(k->mod)) || k->sym == SDLK_DELETE) { ins->sample_map[note_trans_sel_line] = 0; new_line += (k->sym == SDLK_PERIOD) ? 1 : 0; break; } if (k->sym == SDLK_COMMA && NO_MODIFIER(k->mod)) { note_sample_mask = note_sample_mask ? 0 : 1; break; } n = ins->sample_map[note_trans_sel_line]; if (note_trans_cursor_pos == 2) { c = kbd_char_to_99(k); if (c < 0) return 0; n = (c * 10) + (n % 10); new_pos++; } else { c = kbd_char_to_hex(k); if (c < 0 || c > 9) return 0; n = ((n / 10) * 10) + c; new_pos--; new_line++; } n = MIN(n, MAX_SAMPLES - 1); 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_ovl_drawpixel(&env_overlay, 3, n, 12); for (n = 0; n < 256; n += 2) vgamem_ovl_drawpixel(&env_overlay, 1 + n, y, 12); } static void _env_draw_node(int x, int y, int on) { int c = (status.flags & CLASSIC_MODE) ? 12 : 5; vgamem_ovl_drawpixel(&env_overlay, x - 1, y - 1, c); vgamem_ovl_drawpixel(&env_overlay, x - 1, y, c); vgamem_ovl_drawpixel(&env_overlay, x - 1, y + 1, c); vgamem_ovl_drawpixel(&env_overlay, x, y - 1, c); vgamem_ovl_drawpixel(&env_overlay, x, y, c); vgamem_ovl_drawpixel(&env_overlay, x, y + 1, c); vgamem_ovl_drawpixel(&env_overlay, x + 1, y - 1,c); vgamem_ovl_drawpixel(&env_overlay, x + 1, y,c); vgamem_ovl_drawpixel(&env_overlay, x + 1, y + 1,c); if (on) { vgamem_ovl_drawpixel(&env_overlay, x - 3, y - 1,c); vgamem_ovl_drawpixel(&env_overlay, x - 3, y,c); vgamem_ovl_drawpixel(&env_overlay, x - 3, y + 1,c); vgamem_ovl_drawpixel(&env_overlay, x + 3, y - 1,c); vgamem_ovl_drawpixel(&env_overlay, x + 3, y,c); vgamem_ovl_drawpixel(&env_overlay, x + 3, y + 1,c); } } static void _env_draw_loop(int xs, int xe, int sustain) { int y = 0; int c = (status.flags & CLASSIC_MODE) ? 12 : 3; if (sustain) { while (y < 62) { /* unrolled once */ vgamem_ovl_drawpixel(&env_overlay, xs, y, c); vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, 0); vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, c); vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, 0); vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++; } } else { while (y < 62) { vgamem_ovl_drawpixel(&env_overlay, xs, y, 0); vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, c); vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, c); vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, 0); vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++; } } } static void _env_draw(const song_envelope_t *env, int middle, int current_node, int env_on, int loop_on, int sustain_on, int env_num) { song_voice_t *channel; unsigned int *channel_list; char buf[16]; unsigned int 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_ovl_clear(&env_overlay, 0); /* 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_ovl_drawline(&env_overlay, last_x, last_y, x, y, 12); 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 = max_ticks ? song_get_mix_state(&channel_list) : 0; while (m--) { channel = song_get_mix_channel(channel_list[m]); if (channel->ptr_instrument != song_get_instrument(current_instrument)) continue; envpos[0] = channel->vol_env_position; envpos[1] = channel->pan_env_position; envpos[2] = channel->pitch_env_position; x = 4 + (envpos[env_num] * (last_x-4) / max_ticks); if (x > last_x) x = last_x; c = (status.flags & CLASSIC_MODE) ? 12 : ((channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) ? 8 : 6); for (y = 0; y < 62; y++) vgamem_ovl_drawpixel(&env_overlay, x, y, c); } } draw_fill_chars(65, 18, 76, 25, 0); vgamem_ovl_apply(&env_overlay); sprintf(buf, "Node %d/%d", current_node, env->nodes); draw_text(buf, 66, 19, 2, 0); sprintf(buf, "Tick %d", env->ticks[current_node]); draw_text(buf, 66, 21, 2, 0); sprintf(buf, "Value %d", (int)(env->values[current_node] - (middle ? 32 : 0))); draw_text(buf, 66, 23, 2, 0); } /* return: the new current node */ static int _env_node_add(song_envelope_t *env, int current_node, int override_tick, int override_value) { int newtick, newvalue; status.flags |= SONG_NEEDS_SAVE; 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]) { 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_t *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_start >= env->nodes) env->loop_start = env->nodes - 1; else if (env->loop_start > current_node) env->loop_start--; if (env->loop_end >= env->nodes) env->loop_end = env->nodes - 1; else if (env->loop_end > current_node) env->loop_end--; if (env->sustain_start >= env->nodes) env->sustain_start = env->nodes - 1; else if (env->sustain_start > current_node) env->sustain_start--; if (env->sustain_end >= env->nodes) env->sustain_end = env->nodes - 1; else if (env->sustain_end > current_node) env->sustain_end--; if (current_node >= env->nodes) current_node = env->nodes - 1; return current_node; } static void do_pre_loop_cut(void *ign) { song_envelope_t *env = (song_envelope_t *)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_t *env = (song_envelope_t *)ign; env->nodes = env->loop_end+1; } static void env_resize(song_envelope_t *env, int ticks) { int old = env->ticks[env->nodes - 1]; int n, t; if (ticks > 9999) ticks = 9999; for (n = 1; n < env->nodes; n++) { t = env->ticks[n] * ticks / old; env->ticks[n] = MAX(t, env->ticks[n - 1] + 1); } status.flags |= NEED_UPDATE; } static struct widget env_resize_widgets[2]; static int env_resize_cursor; static void do_env_resize(void *data) { env_resize((song_envelope_t *) data, env_resize_widgets[0].d.numentry.value); } static void env_resize_draw_const(void) { draw_text("Resize Envelope", 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 env_resize_dialog(song_envelope_t *env) { struct dialog *dialog; env_resize_cursor = 0; create_numentry(env_resize_widgets + 0, 42, 27, 7, 0, 1, 1, NULL, 0, 9999, &env_resize_cursor); env_resize_widgets[0].d.numentry.value = env->ticks[env->nodes - 1]; create_button(env_resize_widgets + 1, 36, 30, 6, 0, 1, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(26, 22, 29, 11, env_resize_widgets, 2, 0, env_resize_draw_const, env); dialog->action_yes = do_env_resize; } static struct widget env_adsr_widgets[4]; static int env_adsr_cursor = 0; static void do_env_adsr(void *data) { // FIXME | move env flags into the envelope itself, where they should be in the first place. // FIXME | then this nonsense can go away. song_instrument_t *ins = (song_instrument_t *) data; song_envelope_t *env = &ins->vol_env; int a = env_adsr_widgets[0].d.thumbbar.value; int d = env_adsr_widgets[1].d.thumbbar.value; int s = env_adsr_widgets[2].d.thumbbar.value; int r = env_adsr_widgets[3].d.thumbbar.value; int v1 = MAX(a, a * a / 16); int v2 = MAX(v1 + d * d / 16, v1 + d); int v3 = MAX(v2 + r * r / 4, v2 + r); int n = 0; if (a) { env->ticks[n] = 0; env->values[n++] = 0; } if (d) { env->ticks[n] = v1; env->values[n++] = 64; } env->sustain_start = env->sustain_end = n; env->ticks[n] = v2; env->values[n++] = s / 2; env->ticks[n] = v3; env->values[n++] = 0; env->nodes = n; for (n = 0; n < env->nodes - 1; n++) if (env->ticks[n] >= env->ticks[n + 1]) env->ticks[n + 1] = env->ticks[n] + 1; ins->flags |= ENV_VOLSUSTAIN | ENV_VOLUME; // arghhhhh } static void env_adsr_draw_const(void) { draw_text("Envelope Generator", 32, 22, 0, 2); draw_text("Attack", 27, 24, 0, 2); draw_text("Decay", 28, 25, 0, 2); draw_text("Sustain", 26, 26, 0, 2); draw_text("Release", 26, 27, 0, 2); draw_box(33, 23, 51, 28, BOX_THICK | BOX_INNER | BOX_INSET); } static void env_adsr_dialog(UNUSED song_envelope_t *env) { struct dialog *dialog; song_instrument_t *ins = song_get_instrument(current_instrument); // ARGHHH env_adsr_cursor = 0; create_thumbbar(env_adsr_widgets + 0, 34, 24, 17, 4, 1, 4, NULL, 0, 128); create_thumbbar(env_adsr_widgets + 1, 34, 25, 17, 0, 2, 4, NULL, 0, 128); create_thumbbar(env_adsr_widgets + 2, 34, 26, 17, 1, 3, 4, NULL, 0, 128); create_thumbbar(env_adsr_widgets + 3, 34, 27, 17, 2, 4, 4, NULL, 0, 128); create_button(env_adsr_widgets + 4, 36, 30, 6, 3, 0, 4, 4, 0, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(25, 21, 31, 12, env_adsr_widgets, 5, 0, env_adsr_draw_const, ins); dialog->action_yes = do_env_adsr; } /* 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_t *env, int *current_node, unsigned int sec) { int new_node = *current_node; int n; switch (k->sym) { case SDLK_UP: if (k->state == KEY_RELEASE) return 0; change_focus_to(1); return 1; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 0; change_focus_to(6); return 1; case SDLK_LEFT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_node--; break; case SDLK_RIGHT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_node++; break; case SDLK_INSERT: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; song_keyup(KEYJAZZ_NOINST, current_instrument, last_note); song_keydown(KEYJAZZ_NOINST, current_instrument, last_note, 64, KEYJAZZ_CHAN_CURRENT); return 1; case SDLK_RETURN: if (k->state == KEY_PRESS) 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 == KEY_PRESS) 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 == KEY_PRESS) 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; // F/G for key symmetry with pattern double/halve block // E for symmetry with sample resize case SDLK_f: if (k->state == KEY_PRESS) return 0; if (!(k->mod & KMOD_ALT)) return 0; env_resize(env, env->ticks[env->nodes - 1] * 2); return 1; case SDLK_g: if (k->state == KEY_PRESS) return 0; if (!(k->mod & KMOD_ALT)) return 0; env_resize(env, env->ticks[env->nodes - 1] / 2); return 1; case SDLK_e: if (k->state == KEY_PRESS) return 0; if (!(k->mod & KMOD_ALT)) return 0; env_resize_dialog(env); return 1; case SDLK_z: if (k->state == KEY_PRESS) return 0; if (!(k->mod & KMOD_ALT)) return 0; env_adsr_dialog(env); return 1; default: if (k->state == KEY_PRESS) return 0; n = numeric_key_event(k, 0); if (n > -1) { if (k->mod & (KMOD_ALT | KMOD_CTRL)) { save_envelope(n, env, sec); status_text_flash("Envelope copied into slot %d", n); } else if (k->mod & KMOD_SHIFT) { restore_envelope(n, env, sec); if (!(status.flags & CLASSIC_MODE)) status_text_flash("Pasted envelope from slot %d", n); } return 1; } 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_t *env, int *current_node) { int x, y, i; int max_ticks = 50; if (k->mouse != MOUSE_CLICK) return 0; if (k->state == KEY_RELEASE) { /* 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", (int)(*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 ]+1; } if (*current_node < (env->nodes-1)) { if (env->ticks[ (*current_node)+1 ] <= x) { x = env->ticks[ (*current_node)+1 ]-1; } } if (env->ticks[*current_node] == x && env->ticks[*current_node] == y) { return 1; } if (x < 0) x = 0; 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 = 0; 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 - (int) k->fx); dy = abs(y - (int) 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) < 2) 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_t *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 == KEY_RELEASE) return 0; if (k->mod & KMOD_ALT) new_value += 16; else new_value++; break; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_ALT) new_value -= 16; else new_value--; break; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_value += 16; break; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_value -= 16; break; case SDLK_LEFT: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 0; if (k->mod & KMOD_SHIFT) new_tick -= 16; else new_tick += 16; break; case SDLK_HOME: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_tick = 0; break; case SDLK_END: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_tick = 10000; break; case SDLK_INSERT: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; song_keyup(KEYJAZZ_NOINST, current_instrument, last_note); song_keydown(KEYJAZZ_NOINST, current_instrument, last_note, 64, KEYJAZZ_CHAN_CURRENT); return 1; case SDLK_RETURN: if (k->state == KEY_PRESS) 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 != (int)env->values[new_node]) { env->values[*current_node] = (unsigned int)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(env_name, pos, 16, is_selected ? 3 : 0, 2); pos += draw_text(" Envelope", pos, 16, is_selected ? 3 : 0, 2); if (envelope_edit_mode || envelope_mouse_edit) draw_text(" (Edit)", pos, 16, is_selected ? 3 : 0, 2); } static void volume_envelope_draw(void) { int is_selected = (ACTIVE_PAGE.selected_widget == 5); song_instrument_t *ins = song_get_instrument(current_instrument); _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_t *ins = song_get_instrument(current_instrument); _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_t *ins = song_get_instrument(current_instrument); _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_t *ins = song_get_instrument(current_instrument); 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, ENV_VOLUME); if (r & 2) { r ^= 2; ins->flags |= ENV_VOLUME; } return r; } static int panning_envelope_handle_key(struct key_event * k) { song_instrument_t *ins = song_get_instrument(current_instrument); 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, ENV_PANNING); if (r & 2) { r ^= 2; ins->flags |= ENV_PANNING; } return r; } static int pitch_envelope_handle_key(struct key_event * k) { song_instrument_t *ins = song_get_instrument(current_instrument); 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, ENV_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_t *ins = song_get_instrument(current_instrument); int ppc = ins->pitch_pan_center; if (k->state == KEY_RELEASE) 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 ((unsigned int)ppc != ins->pitch_pan_center && ppc >= 0 && ppc < 120) { ins->pitch_pan_center = (unsigned int)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_t *ins = song_get_instrument(current_instrument); draw_text(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_t *ins = song_get_instrument(current_instrument); char *ptr = (char *) 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_t *ins = song_get_instrument(current_instrument); */ if (k->state == KEY_RELEASE) return; switch (k->sym) { case SDLK_n: song_toggle_multichannel_mode(); return; case SDLK_o: instrument_save(); return; case SDLK_r: smpprompt_create("Replace instrument with:", "Instrument", do_replace_instrument); return; case SDLK_s: // extra space to align the text like IT smpprompt_create("Swap instrument with: ", "Instrument", do_swap_instrument); return; case SDLK_x: smpprompt_create("Exchange instrument with:", "Instrument", do_exchange_instrument); return; case SDLK_p: smpprompt_create("Copy instrument:", "Instrument", do_copy_instrument); 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 int instrument_list_pre_handle_key(struct key_event * k) { // Only handle plain F4 key when no dialog is active. if (status.dialog_type != DIALOG_NONE || k->sym != SDLK_F4 || (k->mod & (KMOD_CTRL | KMOD_ALT))) return 0; if (k->state == KEY_RELEASE) return 1; if (song_is_instrument_mode()) { int csamp = sample_get_current(); sample_synchronize_to_instrument(); if (csamp != sample_get_current()) return 0; } if (k->mod & KMOD_SHIFT) { switch (status.current_page) { default: case PAGE_INSTRUMENT_LIST_VOLUME: set_subpage(PAGE_INSTRUMENT_LIST_GENERAL); break; case PAGE_INSTRUMENT_LIST_PANNING: set_subpage(PAGE_INSTRUMENT_LIST_VOLUME); break; case PAGE_INSTRUMENT_LIST_PITCH: set_subpage(PAGE_INSTRUMENT_LIST_PANNING); break; case PAGE_INSTRUMENT_LIST_GENERAL: set_subpage(PAGE_INSTRUMENT_LIST_PITCH); break; } } else { switch (status.current_page) { default: case PAGE_INSTRUMENT_LIST_PITCH: set_subpage(PAGE_INSTRUMENT_LIST_GENERAL); break; case PAGE_INSTRUMENT_LIST_GENERAL: set_subpage(PAGE_INSTRUMENT_LIST_VOLUME); break; case PAGE_INSTRUMENT_LIST_VOLUME: set_subpage(PAGE_INSTRUMENT_LIST_PANNING); break; case PAGE_INSTRUMENT_LIST_PANNING: set_subpage(PAGE_INSTRUMENT_LIST_PITCH); break; } } return 1; } static void instrument_list_handle_key(struct key_event * k) { switch (k->sym) { case SDLK_COMMA: if (NO_MODIFIER(k->mod)) { if (!(status.flags & CLASSIC_MODE) && ACTIVE_PAGE.selected_widget == 5) return; } case SDLK_LESS: if (k->state == KEY_RELEASE) return; song_change_current_play_channel(-1, 0); return; case SDLK_PERIOD: if (NO_MODIFIER(k->mod)) { if (!(status.flags & CLASSIC_MODE) && ACTIVE_PAGE.selected_widget == 5) return; } case SDLK_GREATER: if (k->state == KEY_RELEASE) return; song_change_current_play_channel(1, 0); return; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return; instrument_set(current_instrument - 1); break; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return; instrument_set(current_instrument + 1); break; case SDLK_ESCAPE: if ((k->mod & KMOD_SHIFT) || instrument_cursor_pos < 25) { if (k->state == KEY_RELEASE) return; instrument_cursor_pos = 25; get_page_widgets()->accept_text = 0; change_focus_to(0); status.flags |= NEED_UPDATE; return; } return; 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 == KEY_RELEASE) { song_keyup(0, current_instrument, n); status.last_keysym = 0; } else if (!k->is_repeat) { song_keydown(KEYJAZZ_NOINST, current_instrument, n, v, KEYJAZZ_CHAN_CURRENT); } last_note = n; } return; } } /* --------------------------------------------------------------------- */ static void set_subpage(int page) { int widget = ACTIVE_PAGE.selected_widget; int b = 1; switch (page) { case PAGE_INSTRUMENT_LIST_GENERAL: b = 1; break; case PAGE_INSTRUMENT_LIST_VOLUME: b = 2; break; case PAGE_INSTRUMENT_LIST_PANNING: b = 3; break; case PAGE_INSTRUMENT_LIST_PITCH: b = 4; break; default: return; }; togglebutton_set(pages[page].widgets, b, 0); set_page(page); if (widget >= ACTIVE_PAGE.total_widgets) widget = ACTIVE_PAGE.total_widgets - 1; ACTIVE_PAGE.selected_widget = widget; instrument_list_subpage = page; status.flags |= NEED_UPDATE; } static void change_subpage(void) { int widget = ACTIVE_PAGE.selected_widget; int p[] = { PAGE_INSTRUMENT_LIST_GENERAL, PAGE_INSTRUMENT_LIST_VOLUME, PAGE_INSTRUMENT_LIST_PANNING, PAGE_INSTRUMENT_LIST_PITCH, }; set_subpage(p[CLAMP(widget - 1, 0, 3)]); } /* --------------------------------------------------------------------- */ /* predraw hooks... */ static void instrument_list_general_predraw_hook(void) { song_instrument_t *ins = song_get_instrument(current_instrument); togglebutton_set(widgets_general, 6 + (ins->nna % 4), 0); togglebutton_set(widgets_general, 10 + (ins->dct % 4), 0); togglebutton_set(widgets_general, 14 + (ins->dca % 3), 0); widgets_general[17].d.textentry.text = ins->filename; } static void instrument_list_volume_predraw_hook(void) { song_instrument_t *ins = song_get_instrument(current_instrument); 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; /* current_song hack: shifting values all over the place here, ugh */ widgets_volume[14].d.thumbbar.value = ins->global_volume; widgets_volume[15].d.thumbbar.value = ins->fadeout >> 5; widgets_volume[16].d.thumbbar.value = ins->vol_swing; } static void instrument_list_panning_predraw_hook(void) { song_instrument_t *ins = song_get_instrument(current_instrument); 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_t *ins = song_get_instrument(current_instrument); 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->ifc & 0x80) widgets_pitch[14].d.thumbbar.value = ins->ifc & 0x7f; else widgets_pitch[14].d.thumbbar.value = -1; if (ins->ifr & 0x80) widgets_pitch[15].d.thumbbar.value = ins->ifr & 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.bitset.value = ins->midi_channel_mask; 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_t *ins = song_get_instrument(current_instrument); 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; } static void update_filename(void) { status.flags |= SONG_NEEDS_SAVE; } #define CHECK_SET(a,b,c) if (a != b) { a = b; c; } static void instrument_list_volume_update_values(void) { song_instrument_t *ins = song_get_instrument(current_instrument); 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; ins->fadeout = widgets_volume[15].d.thumbbar.value << 5; ins->vol_swing = widgets_volume[16].d.thumbbar.value; song_update_playing_instrument(current_instrument); } static void instrument_list_panning_update_values(void) { song_instrument_t *ins = song_get_instrument(current_instrument); 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 != (unsigned int)n) { ins->panning = (unsigned int)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_t *ins = song_get_instrument(current_instrument); 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->ifc = widgets_pitch[14].d.thumbbar.value | 0x80; } else { ins->ifc = 0x7f; } if (widgets_pitch[15].d.thumbbar.value > -1) { ins->ifr = widgets_pitch[15].d.thumbbar.value | 0x80; } else { ins->ifr = 0x7f; } ins->midi_channel_mask = widgets_pitch[16].d.bitset.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(" ", 69, 47, 1, 0); } else { draw_box(55, 46, 69, 48, BOX_THICK | BOX_INNER | BOX_INSET); } draw_text("New Note Action", 54, 17, 0, 2); draw_text("Duplicate Check Type & Action", 47, 32, 0, 2); draw_text("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("Volume Envelope", 38, 28, 0, 2); draw_text("Carry", 48, 29, 0, 2); draw_text("Envelope Loop", 40, 32, 0, 2); draw_text("Loop Begin", 43, 33, 0, 2); draw_text("Loop End", 45, 34, 0, 2); draw_text("Sustain Loop", 41, 37, 0, 2); draw_text("SusLoop Begin", 40, 38, 0, 2); draw_text("SusLoop End", 42, 39, 0, 2); draw_text("Global Volume", 40, 42, 0, 2); draw_text("Fadeout", 46, 43, 0, 2); draw_text("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("Panning Envelope", 37, 28, 0, 2); draw_text("Carry", 48, 29, 0, 2); draw_text("Envelope Loop", 40, 32, 0, 2); draw_text("Loop Begin", 43, 33, 0, 2); draw_text("Loop End", 45, 34, 0, 2); draw_text("Sustain Loop", 41, 37, 0, 2); draw_text("SusLoop Begin", 40, 38, 0, 2); draw_text("SusLoop End", 42, 39, 0, 2); draw_text("Default Pan", 42, 42, 0, 2); draw_text("Pan Value", 44, 43, 0, 2); draw_text("Pitch-Pan Center", 37, 45, 0, 2); draw_text("Pitch-Pan Separation", 33, 46, 0, 2); if (status.flags & CLASSIC_MODE) { /* Hmm. The 's' in swing isn't capitalised. ;) */ draw_text("Pan swing", 44, 47, 0, 2); } else { draw_text("Pan Swing", 44, 47, 0, 2); } draw_text("\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("Frequency Envelope", 35, 28, 0, 2); draw_text("Carry", 48, 29, 0, 2); draw_text("Envelope Loop", 40, 32, 0, 2); draw_text("Loop Begin", 43, 33, 0, 2); draw_text("Loop End", 45, 34, 0, 2); draw_text("Sustain Loop", 41, 37, 0, 2); draw_text("SusLoop Begin", 40, 38, 0, 2); draw_text("SusLoop End", 42, 39, 0, 2); draw_text("Default Cutoff", 36, 42, 0, 2); draw_text("Default Resonance", 36, 43, 0, 2); draw_text("MIDI Channels", 36, 44, 0, 2); draw_text("MIDI Program", 36, 45, 0, 2); draw_text("MIDI Bank Low", 36, 46, 0, 2); draw_text("MIDI Bank High", 36, 47, 0, 2); } /* --------------------------------------------------------------------- */ /* load_page functions */ static void _load_page_common(struct page *page, struct widget *page_widgets) { int i; memset(saved_env, 0, sizeof(saved_env)); for (i = 0; i < 10; i++) { saved_env[i].nodes = 2; saved_env[i].ticks[0] = 0; saved_env[i].ticks[1] = 100; saved_env[i].values[0] = 32; saved_env[i].values[1] = 32; } vgamem_ovl_alloc(&env_overlay); page->title = "Instrument List (F4)"; page->pre_handle_key = instrument_list_pre_handle_key; page->handle_key = instrument_list_handle_key; page->widgets = page_widgets; page->help_index = HELP_INSTRUMENT_LIST; page->set_page = instrument_list_reposition; /* 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].accept_text = (instrument_cursor_pos == 25 ? 0 : 1); 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, update_filename, NULL, 12); } static int _fixup_mouse_instpage_volume(struct key_event *k) { song_instrument_t *ins = song_get_instrument(current_instrument); 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, ENV_VOLUME); } return instrument_list_pre_handle_key(k); } 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, 256); 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_t *ins = song_get_instrument(current_instrument); 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, ENV_PANNING); } return instrument_list_pre_handle_key(k); } 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_t *ins = song_get_instrument(current_instrument); 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, ENV_PITCH); } return instrument_list_pre_handle_key(k); } void instrument_list_pitch_load_page(struct page *page) { static int midi_channel_selection_cursor_position = 0; _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_bitset(widgets_pitch + 16, 54, 44, 17, 15, 17, 0, instrument_list_pitch_update_values, 17, " 1 2 3 4 5 6 7 8 9P\0""111213141516M\0", ".\0.\0.\0.\0.\0.\0.\0.\0.\0p\0.\0.\0.\0.\0.\0.\0m\0", &midi_channel_selection_cursor_position ); widgets_pitch[16].d.bitset.activation_keys = "123456789pabcdefm"; 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[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"; } schismtracker-20180209/schism/page_loadinst.c000066400000000000000000000330331323741476300211020ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "log.h" #include #include #include "sdlmain.h" #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 time_t directory_mtime; static int _library_mode = 0; static dmoz_filelist_t flist; #define current_file flist.selected 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); } static int instgrep(dmoz_file_t *f) { dmoz_fill_ext_data(f); return f->type & (TYPE_INST_MASK | TYPE_BROWSABLE_MASK); } /* --------------------------------------------------------------------------------------------------------- */ static void file_list_reposition(void) { if (current_file >= flist.num_files) current_file = flist.num_files-1; if (current_file < 0) current_file = 0; 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; 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(inst_cwd, &flist, NULL, dmoz_read_instrument_library) < 0) log_perror(inst_cwd); dmoz_filter_filelist(&flist,instgrep, ¤t_file, file_list_reposition); dmoz_cache_lookup(inst_cwd, &flist, NULL); file_list_reposition(); } /* 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; dmoz_cache_update(inst_cwd, &flist, NULL); 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(); 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, "%lum", (unsigned long)(file->filesize / 1048576)); } else if (file->filesize > 1024) { sprintf(sbuf, "%luk", (unsigned long)(file->filesize / 1024)); } else if (file->filesize > 0) { sprintf(sbuf, "%lu", (unsigned long)(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); } 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(); } } /* on the file list, that is */ 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]; dmoz_cache_update(inst_cwd, &flist, NULL); 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; status.flags |= SONG_NEEDS_SAVE; 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 void do_delete_file(UNUSED void *data) { int old_top_file, old_current_file; 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; read_directory(); /* put the list positions back */ top_file = old_top_file; current_file = old_current_file; /* 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; new_file = CLAMP(new_file, 0, flist.num_files - 1); if (k->mouse != MOUSE_NONE) { if (k->x >= 6 && k->x <= 67 && k->y >= 13 && k->y <= 47) { slash_search_mode = -1; if (k->mouse == MOUSE_SCROLL_UP) { new_file -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_file += MOUSE_SCROLL_LINES; } 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 == KEY_PRESS) return 1; slash_search_mode = -1; status.flags |= NEED_UPDATE; return 1; } else if (k->sym == SDLK_BACKSPACE) { if (k->state == KEY_RELEASE) return 1; slash_search_mode--; status.flags |= NEED_UPDATE; reposition_at_slash_search(); return 1; } else if (c >= 32) { if (k->state == KEY_RELEASE) 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--; slash_search_mode = -1; break; case SDLK_DOWN: new_file++; slash_search_mode = -1; break; case SDLK_PAGEUP: new_file -= 35; slash_search_mode = -1; break; case SDLK_PAGEDOWN: new_file += 35; slash_search_mode = -1; break; case SDLK_HOME: new_file = 0; slash_search_mode = -1; break; case SDLK_END: new_file = flist.num_files - 1; slash_search_mode = -1; break; case SDLK_RETURN: if (k->state == KEY_PRESS) return 0; handle_enter_key(); slash_search_mode = -1; return 1; case SDLK_DELETE: if (k->state == KEY_RELEASE) return 1; slash_search_mode = -1; if (flist.num_files > 0) dialog_create(DIALOG_OK_CANCEL, "Delete file?", do_delete_file, NULL, 1, NULL); return 1; case SDLK_ESCAPE: slash_search_mode = -1; if (k->state == KEY_RELEASE && 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 == KEY_RELEASE) return 0; slash_search_mode = 0; status.flags |= NEED_UPDATE; return 1; } default: if (k->mouse == MOUSE_NONE) return 0; } if (k->mouse == MOUSE_CLICK) { if (k->state == KEY_RELEASE) return 0; } else if (k->mouse == MOUSE_DBLCLICK) { handle_enter_key(); return 1; } else { /* prevent moving the cursor twice from a single key press */ if (k->state == KEY_RELEASE) 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(); status.flags |= NEED_UPDATE; } return 1; } static void load_instrument_handle_key(struct key_event * k) { if (k->state == KEY_RELEASE) 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; create_other(widgets_loadinst + 0, 0, file_list_handle_key, file_list_draw); widgets_loadinst[0].accept_text = 1; } void library_instrument_load_page(struct page *page) { 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; } schismtracker-20180209/schism/page_loadmodule.c000066400000000000000000000725001323741476300214140ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "log.h" #include "fmt.h" /* only needed for SAVE_SUCCESS ... */ #include #include #include "sdlmain.h" #include #include #include #include "sndfile.h" #include "disko.h" /* --------------------------------------------------------------------- */ /* this was adapted from a really slick two-line fnmatch() at http://compressionratings.com/d_archiver_template.html and fuglified to add FNM_CASEFOLD|FNM_PERIOD behavior */ #if HAVE_FNMATCH # include #else # define FNM_CASEFOLD 0 # define FNM_PERIOD 0 # define fnmatch xfnmatch inline static int _fnmatch(const char *m, const char *s); inline static int _fnmatch(const char *m, const char *s) { if (*m == '*') for (++m; *s; ++s) if (!_fnmatch(m, s)) return 0; return (!*s || !(*m == '?' || tolower(*s) == tolower(*m))) ? tolower(*m) | tolower(*s) : _fnmatch(++m, ++s); } inline static int xfnmatch(const char *m, const char *s, UNUSED int f) { return (*s == '.' && *m != '.') ? 0 : _fnmatch(m, s); } #endif /* !HAVE_FNMATCH */ /* --------------------------------------------------------------------- */ /* the locals */ static int modgrep(dmoz_file_t *f); static struct widget widgets_loadmodule[5]; static struct widget widgets_exportmodule[16]; static struct widget widgets_savemodule[16]; static struct widget *widgets_exportsave; /* XXX this needs to be kept in sync with diskwriters (FIXME: it shouldn't have to! build it when the savemodule page is built or something, idk -storlek) */ static const int filetype_saves[] = { 4, 5, 6, 7, 8, 9, -1 }; static int top_file = 0, top_dir = 0; static time_t directory_mtime; static dmoz_filelist_t flist; static dmoz_dirlist_t dlist; #define current_file flist.selected #define current_dir dlist.selected /* filename_entry is generally a glob pattern, but typing a file/path name directly and hitting enter will load the file. glob_list is a split-up bunch of globs that gets updated if enter is pressed while filename_entry contains a '*' or '?' character. 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.) */ /* impulse tracker's glob list: *.it; *.xm; *.s3m; *.mtm; *.669; *.mod unsupported formats that the title reader knows about, even though we can't load them: *.f2r; *.liq; *.dtm; *.ntk; *.mf formats that might be supported, but which i have never seen and thus don't actually care about: *.dbm; *.dsm; *.psm other formats that i wouldn't bother presenting in the loader even if we could load them: *.mid; *.wav; *.mp3; *.ogg; *.sid; *.umx formats that modplug pretends to support, but fails hard: *.ams TODO: scroller hack on selected filename */ #define GLOB_CLASSIC "*.it; *.xm; *.s3m; *.mtm; *.669; *.mod" #define GLOB_DEFAULT GLOB_CLASSIC "; *.mdl; *.mt2; *.stm; *.far; *.ult; *.med; *.ptm; *.okt; *.amf; *.dmf; *.imf; *.sfx; *.mus; *.mid" static char filename_entry[PATH_MAX + 1] = ""; static char dirname_entry[PATH_MAX + 1] = ""; char cfg_module_pattern[PATH_MAX + 1] = GLOB_DEFAULT; char cfg_export_pattern[PATH_MAX + 1] = "*.wav; *.aiff; *.aif"; static char **glob_list = NULL; static char glob_list_src[PATH_MAX + 1] = ""; // the pattern used to make glob_list (this is an icky hack) /* --------------------------------------------------------------------- */ static char **semicolon_split(const char *i) { int n = 1; const char *j; char *a, *z, **o, **p; if (!i) return NULL; i += strspn(i, "; \t"); if (!*i) return NULL; /* how many MIGHT we have? */ for (j = i; j; j = strchr(j + 1, ';')) n++; o = p = mem_calloc(n, sizeof(char *)); a = strdup(i); do { *p++ = a; z = strchr(a, ';'); if (!z) z = strchr(a, 0); /* trim whitespace */ do { z--; } while (isblank(*z)); z++; /* find start of the next one */ a = z; a += strspn(a, "; \t"); *z = 0; } while (*a); return o; } /* --------------------------------------------------------------------- */ /* 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(const char *ptr) { dmoz_filelist_t tmp = {}; struct stat sb; /* these shenanigans force the file to take another trip... */ if (stat(ptr, &sb) == -1) return; dmoz_add_file(&tmp, str_dup(ptr), str_dup(ptr), &sb, 0); dmoz_free(&tmp, NULL); song_load(ptr); } static void loadsave_song_changed(void) { int r = 4; /* what? */ int i; const char *ext; const char *ptr = song_get_filename(); if (!ptr) return; ext = get_extension(ptr); if (ext[0] && ext[1]) { for (i = 0; song_save_formats[i].label; i++) { if (strcasecmp(ext, song_save_formats[i].ext) == 0) { /* ugh :) offset to the button for the file type on the save module page is (position in diskwriter driver array) + 4 */ r = i + 4; break; } } } togglebutton_set(widgets_savemodule, r, 0); } /* NOTE: ptr should be dynamically allocated, or NULL */ static void do_save_song(char *ptr) { int ret, export = (status.current_page == PAGE_EXPORT_MODULE); const char *filename = ptr ?: song_get_filename(); const char *seltype = NULL; struct widget *widget; set_page(PAGE_LOG); // 4 is the index of the first file-type button for (widget = (export ? widgets_exportmodule : widgets_savemodule) + 4; widget->type == WIDGET_TOGGLEBUTTON; widget++) { if (widget->d.togglebutton.state) { // Aha! seltype = widget->d.togglebutton.text; break; } } if (!seltype) { // No button was selected? (should never happen) log_appendf(4, "No file format selected?"); ret = SAVE_INTERNAL_ERROR; } else if (export) { ret = song_export(filename, seltype); } else { ret = song_save(filename, seltype); } if (ret != SAVE_SUCCESS) dialog_create(DIALOG_OK, "Could not save file", NULL, NULL, 0, NULL); free(ptr); } void save_song_or_save_as(void) { const char *f = song_get_filename(); if (f && *f) { do_save_song(str_dup(f)); } else { set_page(PAGE_SAVE_MODULE); } } static void do_save_song_overwrite(void *ptr) { struct stat st; if (!(status.flags & CLASSIC_MODE)) { // say what? 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(const char *name) { struct stat buf; if (stat(name, &buf) < 0) { if (errno == ENOENT) { do_save_song(str_dup(name)); } else { log_perror(name); } } else { if (S_ISDIR(buf.st_mode)) { /* TODO: maybe change the current directory in this case? */ log_appendf(4, "%s: Is a directory", name); } else if (S_ISREG(buf.st_mode)) { dialog_create(DIALOG_OK_CANCEL, "Overwrite file?", do_save_song_overwrite, free, 1, str_dup(name)); } 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)(const 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 */ switch (type) { case TYPE_MODULE_MOD: return 2; case TYPE_MODULE_S3M: return 5; case TYPE_MODULE_XM: return 6; case TYPE_MODULE_IT: return 3; case TYPE_SAMPLE_COMPR: return 4; /* mp3/ogg 'sample'... i think */ default: return 7; } } static void clear_directory(void) { dmoz_free(&flist, &dlist); } static int modgrep(dmoz_file_t *f) { int i = 0; if (!glob_list) return 1; for (i = 0; glob_list[i]; i++) { if (fnmatch(glob_list[i], f->base, FNM_PERIOD | FNM_CASEFOLD) == 0) return 1; } return 0; } /* --------------------------------------------------------------------- */ static void file_list_reposition(void) { if (current_file >= flist.num_files) current_file = flist.num_files-1; if (current_file < 0) current_file = 0; if (current_file < top_file) top_file = current_file; else if (current_file > top_file + 30) top_file = current_file - 30; status.flags |= NEED_UPDATE; } static void dir_list_reposition(void) { if (current_dir >= dlist.num_dirs) current_dir = dlist.num_dirs-1; if (current_dir < 0) current_dir = 0; if (current_dir < top_dir) top_dir = current_dir; else if (current_dir > top_dir + 20) top_dir = current_dir - 20; status.flags |= NEED_UPDATE; } static void read_directory(void) { struct stat st; 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, NULL) < 0) log_perror(cfg_dir_modules); dmoz_filter_filelist(&flist, modgrep, ¤t_file, file_list_reposition); while (dmoz_worker()); /* don't do it asynchronously */ dmoz_cache_lookup(cfg_dir_modules, &flist, &dlist); // background the title checker dmoz_filter_filelist(&flist, dmoz_fill_ext_data, ¤t_file, file_list_reposition); file_list_reposition(); dir_list_reposition(); } /* --------------------------------------------------------------------- */ static void set_glob(const char *globspec) { if (glob_list) { free(*glob_list); free(glob_list); } strncpy(glob_list_src, globspec, PATH_MAX); glob_list_src[PATH_MAX] = '\0'; glob_list = semicolon_split(glob_list_src); /* this is kinda lame. dmoz should have a way to reload the list without rereading the directory. could be done with a "visible" flag, which affects the list's sort order, along with adjusting the file count... */ read_directory(); } static void set_default_glob(int set_filename) { const char *s = (status.current_page == PAGE_EXPORT_MODULE) ? cfg_export_pattern : cfg_module_pattern; if (set_filename) { /* glob on load page is visible, but on save page the text should be empty */ strcpy(filename_entry, s); } set_glob(s); } /* --------------------------------------------------------------------- */ 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 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) { 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) { 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; dmoz_cache_update(cfg_dir_modules, &flist, &dlist); 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(); 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 (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 ?: "", 26, 51, 40, 5, 0); sprintf(buf, "%09lu", (unsigned long)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 void show_selected_song_length(void) { if (current_file < 0 || current_file >= flist.num_files) return; char *ptr = flist.files[current_file]->path; song_t *song = song_create_load(ptr); if (!song) { log_appendf(4, "%s: %s", ptr, fmt_strerror(errno)); return; } show_length_dialog(get_basename(ptr), csf_get_length(song)); csf_free(song); } static int file_list_handle_key(struct key_event * k) { int new_file = current_file; 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 == KEY_PRESS) return 1; if (current_file < flist.num_files) { dmoz_cache_update(cfg_dir_modules, &flist, &dlist); handle_file_entered(flist.files[current_file]->path); } search_text_clear(); return 1; case SDLK_DELETE: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) return 1; if (k->mod & KMOD_CTRL) search_text_clear(); else search_text_delete_char(); return 1; case SDLK_p: if ((k->mod & KMOD_ALT) && k->state == KEY_PRESS) { show_selected_song_length(); return 1; } /* else fall through */ default: if (k->mouse == MOUSE_NONE) { if (k->state == KEY_RELEASE) return 0; return search_text_add_char(k->unicode); } } if (k->mouse != MOUSE_NONE && !(k->x >=3 && k->x <= 46 && k->y >= 13 && k->y <= 43)) return 0; switch (k->mouse) { case MOUSE_CLICK: if (k->state == KEY_PRESS) return 0; new_file = (k->y - 13) + top_file; break; case MOUSE_DBLCLICK: if (current_file < flist.num_files) { dmoz_cache_update(cfg_dir_modules, &flist, &dlist); handle_file_entered(flist.files[current_file]->path); } search_text_clear(); return 1; case MOUSE_SCROLL_UP: case MOUSE_SCROLL_DOWN: if (k->state == KEY_PRESS) return 0; top_file += (k->mouse == MOUSE_SCROLL_UP) ? -MOUSE_SCROLL_LINES : MOUSE_SCROLL_LINES; /* don't allow scrolling down past either end. this can't be CLAMP'd because the first check might scroll too far back if the list is small. (hrm, should add a BOTTOM_FILE macro or something) */ if (top_file > flist.num_files - 31) top_file = flist.num_files - 31; if (top_file < 0) top_file = 0; status.flags |= NEED_UPDATE; return 1; default: /* prevent moving the cursor twice from a single key press */ if (k->state == KEY_RELEASE) 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(); 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 < 0) continue; /* er... */ 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 != MOUSE_NONE) { 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; /* FIXME wheel should be adjusting top_dir instead (and then adjust it later) */ } else if (k->mouse == MOUSE_SCROLL_UP) { new_dir -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_dir += MOUSE_SCROLL_LINES; } } 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 == KEY_PRESS) return 0; /* reset */ top_file = current_file = 0; if (current_dir >= 0 && current_dir < dlist.num_dirs) 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 == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) search_text_clear(); else search_text_delete_char(); return 1; case SDLK_SLASH: #ifdef WIN32 case SDLK_BACKSLASH: #endif if (k->state == KEY_RELEASE) return 0; if (search_text_length == 0 && current_dir != 0) { // slash -> go to top (root) dir new_dir = 0; } else if (current_dir > 0 && current_dir < dlist.num_dirs) { change_dir(dlist.dirs[current_dir]->path); status.flags |= NEED_UPDATE; return 1; } break; default: if (k->mouse == MOUSE_NONE) { if (k->state == KEY_RELEASE) return 0; return search_text_add_char(k->unicode); } } if (k->mouse == MOUSE_CLICK) { if (k->state == KEY_PRESS) return 0; } else { if (k->state == KEY_RELEASE) 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) { if (strpbrk(filename_entry, "?*")) { set_glob(filename_entry); } else { char *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; } /* --------------------------------------------------------------------- */ /* FIXME what are these for? apart from clearing the directory list constantly */ #undef CACHEFREE #if CACHEFREE 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; } #endif 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; // Don't reparse the glob if it hasn't changed; that will mess with the cursor position if (strcasecmp(glob_list_src, cfg_module_pattern) == 0) strcpy(filename_entry, glob_list_src); else set_default_glob(1); } void load_module_load_page(struct page *page) { clear_directory(); top_file = top_dir = 0; current_file = current_dir = 0; dir_list_reposition(); file_list_reposition(); 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; #if CACHEFREE page->pre_handle_key = _load_cachefree_hack; #endif create_other(widgets_loadmodule + 0, 1, file_list_handle_key, file_list_draw); widgets_loadmodule[0].accept_text = 1; 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].accept_text = 1; 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, PATH_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) { handle_file_entered = handle_file_entered_S; update_directory(); /* impulse tracker always resets these; so will i */ set_default_glob(0); filename_entry[0] = 0; pages[PAGE_SAVE_MODULE].selected_widget = 2; widgets_exportsave = (status.current_page == PAGE_EXPORT_MODULE) ? widgets_exportmodule : widgets_savemodule; if (status.current_page == PAGE_EXPORT_MODULE && current_song->orderlist[0] == ORDER_LAST) dialog_create(DIALOG_OK, "You're about to export a blank file...", NULL, NULL, 0, NULL); } void save_module_load_page(struct page *page, int do_export) { int n; if (do_export) { page->title = "Export Module (Shift-F10)"; page->widgets = widgets_exportmodule; } else { page->title = "Save Module (F10)"; page->widgets = widgets_savemodule; } widgets_exportsave = page->widgets; /* preload */ clear_directory(); top_file = top_dir = 0; current_file = current_dir = 0; dir_list_reposition(); file_list_reposition(); read_directory(); page->draw_const = save_module_draw_const; page->set_page = save_module_set_page; page->total_widgets = 4; page->help_index = HELP_GLOBAL; page->selected_widget = 2; #if CACHEFREE page->pre_handle_key = _save_cachefree_hack; #endif page->song_changed_cb = loadsave_song_changed; create_other(widgets_exportsave + 0, 1, file_list_handle_key, file_list_draw); widgets_exportsave[0].accept_text = 1; widgets_exportsave[0].next.left = 4; widgets_exportsave[0].next.right = widgets_exportsave[0].next.tab = 1; create_other(widgets_exportsave + 1, 2, dir_list_handle_key, dir_list_draw); widgets_exportsave[1].accept_text = 1; widgets_exportsave[1].next.right = widgets_exportsave[1].next.tab = 5; widgets_exportsave[1].next.left = 0; create_textentry(widgets_exportsave + 2, 13, 46, 64, 0, 3, 3, NULL, filename_entry, PATH_MAX); widgets_exportsave[2].activate = filename_entered; create_textentry(widgets_exportsave + 3, 13, 47, 64, 2, 0, 0, NULL, dirname_entry, PATH_MAX); widgets_exportsave[3].activate = dirname_entered; widgets_exportsave[4].d.togglebutton.state = 1; const struct save_format *formats = (do_export ? song_export_formats : song_save_formats); for (n = 0; formats[n].label; n++) { create_togglebutton(widgets_exportsave + 4 + n, 70, 13 + (3 * n), 5, 4 + (n == 0 ? 0 : (n - 1)), 4 + (n + 1), 1, 2, 2, NULL, formats[n].label, (5 - strlen(formats[n].label)) / 2 + 1, filetype_saves); } widgets_exportsave[4 + n - 1].next.down = 2; page->total_widgets += n; } schismtracker-20180209/schism/page_loadsample.c000066400000000000000000000644251323741476300214170ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sample-edit.h" #include "log.h" #include #include #include "sdlmain.h" #include #include #include /* --------------------------------------------------------------------------------------------------------- */ /* the locals */ static struct vgamem_overlay sample_image = { 52,25,76,28, NULL, 0, 0, 0, }; static char current_filename[PATH_MAX]; static int sample_speed_pos = 0; static int sample_loop_beg = 0; static int sample_loop_end = 0; static int sample_susloop_beg = 0; static int sample_susloop_end = 0; static int _library_mode = 0; static struct widget widgets_loadsample[15]; static int fake_slot_changed = 0; static int will_move_to = -1; static int fake_slot = KEYJAZZ_NOINST; static const char *const loop_states[] = { "Off", "On Forwards", "On Ping Pong", NULL }; static void handle_preload(void); /* --------------------------------------------------------------------------------------------------------- */ /* 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 time_t directory_mtime; static dmoz_filelist_t flist; #define current_file (flist.selected) static int search_pos = -1; static char 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); fake_slot = KEYJAZZ_NOINST; fake_slot_changed = 0; } static void file_list_reposition(void) { dmoz_file_t *f; current_file = CLAMP(current_file, 0, flist.num_files - 1); // XXX use CLAMP() here too, I can't brain if (current_file < top_file) top_file = current_file; else if (current_file > top_file + 34) top_file = current_file - 34; if (current_file >= 0 && current_file < flist.num_files) { f = flist.files[current_file]; if (f && f->smp_filename) { strncpy(current_filename, f->smp_filename, PATH_MAX-1); } else if (f && f->base) { strncpy(current_filename, f->base, PATH_MAX-1); } else { current_filename[0] = '\0'; } widgets_loadsample[1].d.textentry.firstchar = 0; widgets_loadsample[1].d.textentry.cursor_pos = strlen(current_filename); widgets_loadsample[2].d.numentry.value = f ? f->smp_speed : 0; if (f && f->smp_flags & CHN_PINGPONGLOOP) { widgets_loadsample[3].d.menutoggle.state = 2; } else if (f && f->smp_flags & CHN_LOOP) { widgets_loadsample[3].d.menutoggle.state = 1; } else { widgets_loadsample[3].d.menutoggle.state = 0; } widgets_loadsample[4].d.numentry.value = f ? f->smp_loop_start : 0; widgets_loadsample[5].d.numentry.value = f ? f->smp_loop_end : 0; if (f && f->smp_flags & CHN_PINGPONGSUSTAIN) { widgets_loadsample[6].d.menutoggle.state = 2; } else if (f && f->smp_flags & CHN_SUSTAINLOOP) { widgets_loadsample[6].d.menutoggle.state = 1; } else { widgets_loadsample[6].d.menutoggle.state = 0; } widgets_loadsample[7].d.numentry.value = f ? f->smp_sustain_start : 0; widgets_loadsample[8].d.numentry.value = f ? f->smp_sustain_end : 0; widgets_loadsample[9].d.thumbbar.value = f ? f->smp_defvol : 64; widgets_loadsample[10].d.thumbbar.value = f ? f->smp_gblvol : 64; widgets_loadsample[11].d.thumbbar.value = f ? f->smp_vibrato_speed : 0; widgets_loadsample[12].d.thumbbar.value = f ? f->smp_vibrato_depth : 0; widgets_loadsample[13].d.thumbbar.value = f ? f->smp_vibrato_rate : 0; if (f) { /* autoload some files */ if (TYPE_SAMPLE_EXTD == (f->type & TYPE_SAMPLE_EXTD) && f->filesize < 0x4000000 && f->smp_length < 0x1000000) handle_preload(); } } } static void read_directory(void) { struct stat st; 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, dmoz_read_sample_library) < 0) log_perror(cfg_dir_samples); dmoz_filter_filelist(&flist, dmoz_fill_ext_data, ¤t_file, file_list_reposition); dmoz_cache_lookup(cfg_dir_samples, &flist, NULL); file_list_reposition(); } /* 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; dmoz_cache_update(cfg_dir_samples, &flist, NULL); /* 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(); return 1; } /* --------------------------------------------------------------------------------------------------------- */ static void load_sample_draw_const(void) { dmoz_file_t *f; song_sample_t *s; char sbuf[64]; draw_box(5, 12, 50, 48, BOX_THICK | BOX_INNER | BOX_INSET); draw_fill_chars(6, 13, 49, 47, 0); draw_fill_chars(64, 13, 77, 22, 0); draw_box(62, 32, 72, 35, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(62, 36, 72, 40, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(63, 12, 77, 23, BOX_THICK | BOX_INNER | BOX_INSET); 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); draw_fill_chars(59, 44, 76, 47, 0); draw_box(58, 43, 77, 48, BOX_THICK | BOX_INNER | BOX_INSET); f = NULL; if (current_file >= 0 && current_file < flist.num_files && flist.files[current_file]) { f = flist.files[current_file]; 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 & CHN_STEREO) { draw_text_len( (f->smp_flags & CHN_16BIT ? "16 bit Stereo" : "8 bit Stereo"), 13, 64, 21, 2, 0); } else { draw_text_len( (f->smp_flags & CHN_16BIT ? "16 bit" : "8 bit"), 13, 64, 21, 2, 0); } if (f->description) { draw_text_len(f->description, 18, 59, 44, 5, 0); } else { switch (f->type) { case TYPE_DIRECTORY: draw_text("Directory", 59, 44, 5, 0); break; default: draw_text("Unknown format", 59, 44, 5, 0); break; }; } sprintf(sbuf, "%07ld", (long)f->filesize); draw_text(sbuf, 59, 45, 5,0); get_date_string(f->timestamp, sbuf); draw_text(sbuf, 59, 46, 5,0); get_time_string(f->timestamp, sbuf); draw_text(sbuf, 59, 47, 5,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); /* 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_text("Format", 52, 44, 0, 2); draw_text("Size", 54, 45, 0, 2); draw_text("Date", 54, 46, 0, 2); draw_text("Time", 54, 47, 0, 2); if (fake_slot != KEYJAZZ_NOINST) { s = song_get_sample(fake_slot); vgamem_ovl_clear(&sample_image, 0); if (s) draw_sample_data(&sample_image, s); else vgamem_ovl_apply(&sample_image); } } /* --------------------------------------------------------------------------------------------------------- */ 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 = KEYJAZZ_NOINST; fake_slot_changed = 0; *selected_widget = 0; search_pos = -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 (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 ?: "", 25, 6, pos, fg, bg); draw_char(168, 31, pos, 2, bg); draw_text_len(file->base ?: "", 18, 32, pos, fg, bg); if (file->base && search_pos > -1) { if (strncasecmp(file->base,search_str,search_pos) == 0) { for (i = 0 ; i < search_pos; i++) { if (tolower(file->base[i]) != tolower(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); } /* --------------------------------------------------------------------------------------------------------- */ /* Nasty mess to load a sample and prompt for stereo convert / create host instrument as necessary. */ static struct widget stereo_cvt_widgets[4]; static void _create_host_ok(void *vpage) { intptr_t page = (intptr_t) vpage; song_create_host_instrument(sample_get_current()); if (page >= 0) set_page(page); } static void _create_host_cancel(void *vpage) { intptr_t page = (intptr_t) vpage; if (page >= 0) set_page(page); } int sample_host_dialog(int newpage) { /* Actually IT defaults to No when the sample slot already had a sample in it, rather than checking if it was assigned to an instrument. Maybe this is better, though? (Not to mention, passing around the extra state that'd be required to do it that way would be kind of messy...) also the double pointer cast sucks. also also, IT says Ok/No here instead of Yes/No... but do I care? */ if (song_is_instrument_mode()) { int used = sample_is_used_by_instrument(sample_get_current()); dialog_create(DIALOG_YES_NO, "Create host instrument?", _create_host_ok, _create_host_cancel, used ? 1 : 0, (void *) (intptr_t) newpage); return 1; } if (newpage >= 0) set_page(newpage); return 0; } static void finish_load(int cur); static void stereo_cvt_complete_left(void) { int cur = sample_get_current(); song_sample_t *smp; smp = song_get_sample(cur); sample_mono_left(smp); dialog_destroy(); finish_load(cur); } static void stereo_cvt_complete_right(void) { int cur = sample_get_current(); song_sample_t *smp; smp = song_get_sample(cur); sample_mono_right(smp); dialog_destroy(); finish_load(cur); } static void stereo_cvt_complete_both(void) { memused_songchanged(); dialog_destroy(); sample_host_dialog(PAGE_SAMPLE_LIST); } static void stereo_cvt_dialog(void) { draw_text("Loading Stereo Sample", 30, 27, 0, 2); } static int stereo_cvt_hk(struct key_event *k) { if (!NO_MODIFIER(k->mod)) return 0; /* trap the default dialog keys - we don't want to escape this dialog without running something */ switch (k->sym) { case SDLK_RETURN: printf("why am I here\n"); case SDLK_ESCAPE: case SDLK_o: case SDLK_c: return 1; case SDLK_l: if (k->state == KEY_RELEASE) stereo_cvt_complete_left(); return 1; case SDLK_r: if (k->state == KEY_RELEASE) stereo_cvt_complete_right(); return 1; case SDLK_s: case SDLK_b: if (k->state == KEY_RELEASE) stereo_cvt_complete_both(); return 1; default: return 0; } } static void finish_load(int cur) { song_sample_t *smp; status.flags |= SONG_NEEDS_SAVE; memused_songchanged(); smp = song_get_sample(cur); if (smp->flags & CHN_STEREO) { struct dialog *dd; create_button(stereo_cvt_widgets+0, 27, 30, 6, 0, 0, 2, 1, 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, 2, 2, 1, 0, 0, stereo_cvt_complete_right, "Right", 1); dd = dialog_create_custom(24, 25, 33, 8, stereo_cvt_widgets, 3, 1, stereo_cvt_dialog, NULL); dd->handle_key = stereo_cvt_hk; return; } sample_host_dialog(PAGE_SAMPLE_LIST); } static void reposition_at_slash_search(void) { dmoz_file_t *f; int i, j, b, bl; if (search_pos < 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 < search_pos; j++) { if (tolower(f->base[j]) != tolower(search_str[j])) break; } if (bl < j) { bl = j; b = i; } } if (bl > 0) { current_file = b; file_list_reposition(); } } /* on the file list, that is */ static void handle_enter_key(void) { dmoz_file_t *file; song_sample_t *smp; int cur = sample_get_current(); if (current_file < 0 || current_file >= flist.num_files) return; file = flist.files[current_file]; dmoz_cache_update(cfg_dir_samples, &flist, NULL); dmoz_fill_ext_data(file); if ((file->type & (TYPE_BROWSABLE_MASK|TYPE_INST_MASK)) && !(file->type & TYPE_SAMPLE_MASK)) { change_dir(file->path); status.flags |= NEED_UPDATE; } else if (_library_mode) { return; } else if (file->sample) { /* it's already been loaded, so copy it */ smp = song_get_sample(cur); song_copy_sample(cur, file->sample); strncpy(smp->name, file->title, 25); smp->name[25] = 0; strncpy(smp->filename, file->base, 12); smp->filename[12] = 0; finish_load(cur); memused_songchanged(); } else if (file->type & TYPE_SAMPLE_MASK) { /* load the sample */ song_load_sample(cur, file->path); finish_load(cur); memused_songchanged(); } } static void do_discard_changes_and_move(UNUSED void *gn) { fake_slot = KEYJAZZ_NOINST; fake_slot_changed = 0; search_pos = -1; current_file = will_move_to; file_list_reposition(); status.flags |= NEED_UPDATE; } static void do_delete_file(UNUSED void *data) { int old_top_file, old_current_file; 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; read_directory(); /* put the list positions back */ top_file = old_top_file; current_file = old_current_file; /* 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) { dmoz_file_t *f; int new_file = current_file; int c = unicode_to_ascii(k->unicode); new_file = CLAMP(new_file, 0, flist.num_files - 1); if (!(status.flags & CLASSIC_MODE) && k->sym == SDLK_n && (k->mod & KMOD_ALT)) { if (k->state == KEY_RELEASE) song_toggle_multichannel_mode(); return 1; } if (k->mouse) { if (k->x >= 6 && k->x <= 49 && k->y >= 13 && k->y <= 47) { search_pos = -1; if (k->mouse == MOUSE_SCROLL_UP) { new_file -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_file += MOUSE_SCROLL_LINES; } else { new_file = top_file + (k->y - 13); } } } switch (k->sym) { case SDLK_UP: new_file--; search_pos = -1; break; case SDLK_DOWN: new_file++; search_pos = -1; break; case SDLK_PAGEUP: new_file -= 35; search_pos = -1; break; case SDLK_PAGEDOWN: new_file += 35; search_pos = -1; break; case SDLK_HOME: new_file = 0; search_pos = -1; break; case SDLK_END: new_file = flist.num_files - 1; search_pos = -1; break; case SDLK_ESCAPE: if (search_pos < 0) { if (k->state == KEY_RELEASE && NO_MODIFIER(k->mod)) set_page(PAGE_SAMPLE_LIST); return 1; } /* else fall through */ case SDLK_RETURN: if (search_pos < 0) { if (k->state == KEY_PRESS) return 0; handle_enter_key(); search_pos = -1; } else { if (k->state == KEY_PRESS) return 1; search_pos = -1; status.flags |= NEED_UPDATE; return 1; } return 1; case SDLK_DELETE: if (k->state == KEY_RELEASE) return 1; search_pos = -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 (search_pos > -1) { if (k->state == KEY_RELEASE) return 1; search_pos--; status.flags |= NEED_UPDATE; reposition_at_slash_search(); return 1; } case SDLK_SLASH: if (search_pos < 0) { if (k->orig_sym == SDLK_SLASH) { if (k->state == KEY_PRESS) return 0; search_pos = 0; status.flags |= NEED_UPDATE; return 1; } return 0; } /* else fall through */ default: f = flist.files[current_file]; if (c >= 32 && (search_pos > -1 || (f && (f->type & TYPE_DIRECTORY)))) { if (k->state == KEY_RELEASE) return 1; if (search_pos < 0) search_pos = 0; if (search_pos < PATH_MAX) { search_str[search_pos++] = c; reposition_at_slash_search(); status.flags |= NEED_UPDATE; } return 1; } if (!k->mouse) return 0; } if (k->mouse == MOUSE_CLICK) { if (k->state == KEY_PRESS) return 0; } else if (k->mouse == MOUSE_DBLCLICK) { handle_enter_key(); return 1; } else { /* prevent moving the cursor twice from a single key press */ if (k->state == KEY_RELEASE) return 1; } new_file = CLAMP(new_file, 0, flist.num_files - 1); if (new_file != current_file) { if (fake_slot != KEYJAZZ_NOINST && fake_slot_changed) { will_move_to = new_file; dialog_create(DIALOG_YES_NO, "Discard Changes?", do_discard_changes_and_move, NULL, 0, NULL); return 1; /* support saving? XXX */ /*"Save Sample?" OK Cancel*/ /*"Discard Changes?" OK Cancel*/ } fake_slot = KEYJAZZ_NOINST; fake_slot_changed = 0; search_pos = -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 == KEY_PRESS && 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 = KEYJAZZ_DEFAULTVOL; } } else if (k->is_repeat) { return; } else { n = kbd_get_note(k); v = KEYJAZZ_DEFAULTVOL; if (n <= 0 || n > 120) return; } handle_preload(); if (fake_slot != KEYJAZZ_NOINST) { if (k->state == KEY_PRESS) song_keydown(KEYJAZZ_INST_FAKE, KEYJAZZ_NOINST, n, v, KEYJAZZ_CHAN_CURRENT); else song_keyup(KEYJAZZ_INST_FAKE, KEYJAZZ_NOINST, n); } } /* --------------------------------------------------------------------------------------------------------- */ static void handle_preload(void) { dmoz_file_t *file; if (fake_slot == KEYJAZZ_NOINST && current_file >= 0 && current_file < flist.num_files) { file = flist.files[current_file]; if (file && (file->type & TYPE_SAMPLE_MASK)) { fake_slot_changed = 0; fake_slot = song_preload_sample(file); // either 0 or KEYJAZZ_NOTINST } } } static void handle_rename_op(void) { handle_preload(); } static void handle_load_copy_uint(unsigned int s, unsigned int *d) { if (s != *d) { *d = s; fake_slot_changed = 1; } } static void handle_load_copy(song_sample_t *s) { handle_load_copy_uint(widgets_loadsample[2].d.numentry.value, &s->c5speed); handle_load_copy_uint(widgets_loadsample[4].d.numentry.value, &s->loop_start); handle_load_copy_uint(widgets_loadsample[5].d.numentry.value, &s->loop_end); handle_load_copy_uint(widgets_loadsample[7].d.numentry.value, &s->sustain_start); handle_load_copy_uint(widgets_loadsample[8].d.numentry.value, &s->sustain_end); handle_load_copy_uint(widgets_loadsample[9].d.thumbbar.value, &s->volume); if ((unsigned int)widgets_loadsample[9].d.thumbbar.value == (s->volume>>2)) { s->volume = (widgets_loadsample[9].d.thumbbar.value << 2); fake_slot_changed=1; } handle_load_copy_uint(widgets_loadsample[10].d.thumbbar.value, &s->global_volume); handle_load_copy_uint(widgets_loadsample[11].d.thumbbar.value, &s->vib_rate); handle_load_copy_uint(widgets_loadsample[12].d.thumbbar.value, &s->vib_depth); handle_load_copy_uint(widgets_loadsample[13].d.thumbbar.value, &s->vib_speed); switch (widgets_loadsample[3].d.menutoggle.state) { case 0: if (s->flags & (CHN_LOOP|CHN_PINGPONGLOOP)) { s->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP); fake_slot_changed=1; } break; case 1: if ((s->flags & (CHN_LOOP|CHN_PINGPONGLOOP)) == CHN_LOOP) { s->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP); s->flags |= (CHN_LOOP); fake_slot_changed=1; } break; case 2: if ((s->flags & (CHN_LOOP|CHN_PINGPONGLOOP)) == CHN_PINGPONGLOOP) { s->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP); s->flags |= (CHN_PINGPONGLOOP); fake_slot_changed=1; } break; }; switch (widgets_loadsample[6].d.menutoggle.state) { case 0: if (s->flags & (CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN)) { s->flags &= ~(CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN); fake_slot_changed=1; } break; case 1: if ((s->flags & (CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN)) == CHN_SUSTAINLOOP) { s->flags &= ~(CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN); s->flags |= (CHN_SUSTAINLOOP); fake_slot_changed=1; } break; case 2: if ((s->flags & (CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN)) == CHN_PINGPONGSUSTAIN) { s->flags &= ~(CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN); s->flags |= (CHN_PINGPONGSUSTAIN); fake_slot_changed=1; } break; }; } static void handle_load_update(void) { song_sample_t *s; handle_preload(); if (fake_slot != KEYJAZZ_NOINST) { s = song_get_sample(fake_slot); if (s) { handle_load_copy(s); song_update_playing_sample(fake_slot); } } } void load_sample_load_page(struct page *page) { vgamem_ovl_alloc(&sample_image); clear_directory(); create_other(widgets_loadsample + 0, 0, file_list_handle_key, file_list_draw); widgets_loadsample[0].accept_text = 1; widgets_loadsample[0].next.tab = 1; create_textentry(widgets_loadsample+1, 64, 13, 13, 1,2, 9, handle_rename_op, current_filename, sizeof(current_filename)-1); sample_speed_pos = 0; create_numentry(widgets_loadsample+2, 64, 14, 7, 1,3, 9, handle_load_update, 0, 9999999, &sample_speed_pos); create_menutoggle(widgets_loadsample+3, 64, 15, 2, 4, 0, 9,9, handle_load_update, loop_states); sample_loop_beg = 0; create_numentry(widgets_loadsample+4, 64, 16, 7, 3,5, 9, handle_load_update, 0, 9999999, &sample_loop_beg); sample_loop_end = 0; create_numentry(widgets_loadsample+5, 64, 17, 7, 4,6, 9, handle_load_update, 0, 9999999, &sample_loop_end); create_menutoggle(widgets_loadsample+6, 64, 18, 5, 7, 0, 9,9, handle_load_update, loop_states); sample_susloop_beg = 0; create_numentry(widgets_loadsample+7, 64, 19, 7, 6,8, 9, handle_load_update, 0, 9999999, &sample_susloop_beg); sample_susloop_end = 0; create_numentry(widgets_loadsample+8, 64, 20, 7, 7,9, 9, handle_load_update, 0, 9999999, &sample_susloop_end); create_thumbbar(widgets_loadsample+9, 63, 33, 9, 8, 10, 0, handle_load_update, 0,64); create_thumbbar(widgets_loadsample+10, 63, 34, 9, 9, 11, 0, handle_load_update, 0,64); create_thumbbar(widgets_loadsample+11, 63, 37, 9, 10, 12, 0, handle_load_update, 0,64); create_thumbbar(widgets_loadsample+12, 63, 38, 9, 11, 13, 0, handle_load_update, 0,32); create_thumbbar(widgets_loadsample+13, 63, 39, 9, 12, 13, 0, handle_load_update, 0,255); 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 = 14; page->widgets = widgets_loadsample; page->help_index = HELP_GLOBAL; } void library_sample_load_page(struct page *page) { /* this shares all the widgets from load_sample */ 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 = 14; page->widgets = widgets_loadsample; page->help_index = HELP_GLOBAL; } schismtracker-20180209/schism/page_log.c000066400000000000000000000124721323741476300200520ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #include #include struct log_line { int color; const char *text; int bios_font; /* 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; }; /* --------------------------------------------------------------------- */ static struct widget widgets_log[1]; #define NUM_LINES 1000 static struct log_line lines[NUM_LINES]; static int top_line = 0; 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(struct key_event * k) { switch (k->sym) { case SDLK_UP: if (k->state == KEY_RELEASE) return 1; top_line--; break; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 1; top_line -= 15; break; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 1; top_line++; break; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 1; top_line += 15; break; case SDLK_HOME: if (k->state == KEY_RELEASE) return 1; top_line = 0; break; case SDLK_END: if (k->state == KEY_RELEASE) return 1; top_line = last_line; break; default: if (k->state == KEY_PRESS) { if (k->mouse == MOUSE_SCROLL_UP) { top_line -= MOUSE_SCROLL_LINES; break; } else if (k->mouse == MOUSE_SCROLL_DOWN) { top_line += MOUSE_SCROLL_LINES; break; } } return 0; }; top_line = CLAMP(top_line, 0, (last_line-32)); if (top_line < 0) top_line = 0; status.flags |= NEED_UPDATE; return 1; } static void log_redraw(void) { int n, i; i = top_line; for (n = 0; n <= last_line && n < 33; n++, i++) { if (!lines[i].text) continue; if (lines[i].bios_font) { draw_text_bios_len(lines[i].text, 74, 3, 14 + n, lines[i].color, 0); } else { draw_text_len(lines[i].text, 74, 3, 14 + n, lines[i].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_COPYRIGHT; /* I guess */ create_other(widgets_log + 0, 0, log_handle_key, log_redraw); } /* --------------------------------------------------------------------- */ inline void log_append2(int bios_font, 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; lines[last_line].bios_font = bios_font; top_line = CLAMP(last_line - 32, 0, NUM_LINES-32); if (status.current_page == PAGE_LOG) status.flags |= NEED_UPDATE; } inline void log_append(int color, int must_free, const char *text) { log_append2(0, color, must_free, text); } inline void log_nl(void) { log_append(0,0,""); } void log_appendf(int color, const char *format, ...) { char *ptr; va_list ap; va_start(ap, format); if (vasprintf(&ptr, format, ap) == -1) { perror("asprintf"); exit(255); } va_end(ap); if (!ptr) { perror("asprintf"); exit(255); } log_append(color, 1, ptr); } void log_underline(int chars) { char buf[75]; chars = CLAMP(chars, 0, (int) sizeof(buf) - 1); buf[chars--] = '\0'; do buf[chars] = 0x81; while (chars--); log_appendf(2, "%s", buf); } void log_perror(const char *prefix) { char *e = strerror(errno); perror(prefix); log_appendf(4, "%s: %s", prefix, e); } schismtracker-20180209/schism/page_message.c000066400000000000000000000506241323741476300207160ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 Xcode. */ #include "headers.h" #include "song.h" #include "clippy.h" #include #include /* --------------------------------------------------------------------- */ 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; static int edit_mode = 0; /* nonzero => message should use the alternate font */ static 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(char *text, int n, char **ptr) { char *tmp; assert(text != NULL); *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(char *text, int pos, int *line, int *ch) { int len; char *ptr; *line = *ch = 0; ptr = NULL; 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(char *text, int line, int character) { int len; char *ptr; ptr = NULL; 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(int newchar, int position) { int len = strlen(current_song->message); if (len == MAX_MESSAGE) { 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(current_song->message + position + 1, current_song->message + position, len - position); current_song->message[len + 1] = 0; current_song->message[position] = (unsigned char)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 - current_song->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 text(char *line, int len, int n) { unsigned char ch; int fg = (message_extfont ? 12 : 6); int i; for (i = 0; line[i] && i < len; i++) { ch = line[i]; if (ch == ' ') { draw_char(' ', 2+i, 13+n, 3,0); } else { (message_extfont ? draw_char_bios : draw_char)(ch, 2+i, 13+n, fg, 0); } } } static void message_draw(void) { char *line, *prevline = current_song->message; int len = get_nth_line(current_song->message, top_line, &line); int n, cp, clipl, clipr; int skipc, cutc; draw_fill_chars(2, 13, 77, 47, 0); 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; text(line, len, n); if (clipl > -1) { cp = line - current_song->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) { if (message_extfont) draw_text_bios_len(line+skipc, cutc, 2+skipc, 13 + n, 6, 8); else draw_text_len(line+skipc, cutc, 2+skipc, 13 + n, 6, 8); } } } if (edit_mode) { draw_char(20, 2 + len, 13 + n, 1, 0); } 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); /* FIXME: see above */ if (len > LINE_WRAP) len = LINE_WRAP; draw_char(20, 2 + len, 13 + n - 1, 2, 0); } if (edit_mode) { /* draw the cursor */ len = get_nth_line(current_song->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) { (message_extfont ? draw_char_bios : draw_char) (20, 2 + cursor_char, 13 + (cursor_line - top_line), 0, 3); } else { (message_extfont ? draw_char_bios : draw_char) (line[cursor_char], 2 + cursor_char, 13 + (cursor_line - top_line), 8, 3); } } } /* --------------------------------------------------------------------- */ static inline void message_set_editmode(void) { edit_mode = 1; widgets_message[0].accept_text = 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].accept_text = 0; widgets_message[0].d.other.handle_key = message_handle_key_viewmode; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ static void message_insert_char(int c) { char *ptr; 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(current_song->message, cursor_line, &ptr) >= LINE_WRAP) { message_wrap_line(ptr); } if (cursor_char >= LINE_WRAP) { cursor_char = get_nth_line(current_song->message, ++cursor_line, &ptr); cursor_pos = get_absolute_position(current_song->message, cursor_line, cursor_char); } message_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void message_delete_char(void) { int len = strlen(current_song->message); char *ptr; if (cursor_pos == 0) return; memmove(current_song->message + cursor_pos - 1, current_song->message + cursor_pos, len - cursor_pos + 1); current_song->message[MAX_MESSAGE] = 0; cursor_pos--; if (cursor_char == 0) { cursor_line--; cursor_char = get_nth_line(current_song->message, cursor_line, &ptr); } else { cursor_char--; } message_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void message_delete_next_char(void) { int len = strlen(current_song->message); if (cursor_pos == len) return; memmove(current_song->message + cursor_pos, current_song->message + cursor_pos + 1, len - cursor_pos); current_song->message[MAX_MESSAGE] = 0; status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void message_delete_line(void) { int len; int movelen; char *ptr; len = get_nth_line(current_song->message, cursor_line, &ptr); if (len < 0) return; if (ptr[len] == 13 && ptr[len + 1] == 10) len++; movelen = (current_song->message + strlen(current_song->message) - ptr); if (movelen == 0) return; memmove(ptr, ptr + len + 1, movelen); len = get_nth_line(current_song->message, cursor_line, &ptr); if (cursor_char > len) { cursor_char = len; cursor_pos = get_absolute_position(current_song->message, cursor_line, cursor_char); } message_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void message_clear(UNUSED void *data) { current_song->message[0] = 0; memused_songchanged(); message_set_viewmode(); status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ 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 == KEY_PRESS) { if (k->mouse == MOUSE_SCROLL_UP) { top_line -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { top_line += MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_CLICK) { message_set_editmode(); return message_handle_key_editmode(k); } } switch (k->sym) { case SDLK_UP: if (k->state == KEY_RELEASE) return 0; top_line--; break; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 0; top_line++; break; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 0; top_line -= 35; break; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; top_line += 35; break; case SDLK_HOME: if (k->state == KEY_RELEASE) return 0; top_line = 0; break; case SDLK_END: if (k->state == KEY_RELEASE) return 0; top_line = get_num_lines(current_song->message) - 34; break; case SDLK_t: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) { message_extfont = !message_extfont; break; } return 1; case SDLK_RETURN: if (k->state == KEY_PRESS) 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(current_song->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(NULL, NULL, 0); if (cursor_pos == len) return; memmove(current_song->message + cursor_pos, current_song->message + cursor_pos + eat + 1, ((len - cursor_pos) - eat)+1); current_song->message[MAX_MESSAGE] = 0; set_absolute_position(current_song->message, cursor_pos, &cursor_line, &cursor_char); message_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } 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; char *ptr; int doing_drag = 0; int clipl, clipr, cp; if (k->mouse == MOUSE_SCROLL_UP) { if (k->state == KEY_RELEASE) return 0; new_cursor_line -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { if (k->state == KEY_RELEASE) return 0; new_cursor_line += MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_CLICK && k->mouse_button == 2) { if (k->state == KEY_RELEASE) 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(current_song->message, (k->sy-13)+top_line, (k->sx-2)); widgets_message[0].clip_start = cp; doing_drag = 1; } } } line_len = get_nth_line(current_song->message, cursor_line, &ptr); switch (k->sym) { case SDLK_UP: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_line--; break; case SDLK_DOWN: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_line++; break; case SDLK_LEFT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_char--; break; case SDLK_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_char++; break; case SDLK_PAGEUP: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_line -= 35; break; case SDLK_PAGEDOWN: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_line += 35; break; case SDLK_HOME: if (k->state == KEY_RELEASE) return 1; if (k->mod & KMOD_CTRL) new_cursor_line = 0; else new_cursor_char = 0; break; case SDLK_END: if (k->state == KEY_RELEASE) return 1; if (k->mod & KMOD_CTRL) { num_lines = get_num_lines(current_song->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 == KEY_RELEASE) return 1; message_set_viewmode(); memused_songchanged(); return 1; case SDLK_BACKSPACE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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 == KEY_RELEASE) return 1; if (k->sym == SDLK_t) { message_extfont = !message_extfont; break; } else if (k->sym == SDLK_y) { clippy_select(NULL, NULL, 0); message_delete_line(); break; } } else if (k->mod & KMOD_ALT) { if (k->state == KEY_RELEASE) return 1; if (k->sym == SDLK_c) { prompt_message_clear(); return 1; } } else if (k->mouse == MOUSE_NONE) { if (k->unicode == '\r' || k->unicode == '\t' || k->unicode >= 32) { if (k->state == KEY_RELEASE) return 1; if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) { _delete_selection(); } if (k->mod & (KMOD_SHIFT|KMOD_CAPS)) { message_insert_char(toupper((unsigned int)k->unicode)); } else { message_insert_char(k->unicode); } return 1; } return 0; } if (k->mouse != MOUSE_CLICK) return 0; if (k->state == KEY_RELEASE) return 1; if (!doing_drag) { clippy_select(NULL, NULL, 0); } } if (new_cursor_line != cursor_line) { if (num_lines == -1) num_lines = get_num_lines(current_song->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(current_song->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(current_song->message, cursor_line, &ptr); } } else if (new_cursor_char > get_nth_line(current_song->message, cursor_line, &ptr)) { if (cursor_line == get_num_lines(current_song->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(current_song->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, (current_song->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) { char *line, *prevline; int len; edit_mode = 0; widgets_message[0].accept_text = 0; widgets_message[0].d.other.handle_key = message_handle_key_viewmode; top_line = 0; len = get_nth_line(current_song->message, 0, &line); while (len >= 0) { if (len > LINE_WRAP) message_wrap_line(line); prevline = line; len = get_nth_line(prevline, 1, &line); } if (status.current_page == PAGE_MESSAGE) status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ 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->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); widgets_message[0].accept_text = edit_mode; } schismtracker-20180209/schism/page_midi.c000066400000000000000000000242511323741476300202110ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "page.h" #include "midi.h" #include "song.h" /* --------------------------------------------------------------------- */ 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) { if (widgets_midi[12].d.thumbbar.value > 0 && (status.flags & NO_NETWORK)) { status_text_flash("Networking is disabled"); widgets_midi[12].d.thumbbar.value = 0; } else { ip_midi_setports(widgets_midi[12].d.thumbbar.value); } last_midi_poll = 0; status.flags |= NEED_UPDATE; } static void update_midi_values(void) { midi_flags = 0 | (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_PITCHBEND : 0) ; if (widgets_midi[11].d.toggle.state) current_song->flags |= SONG_EMBEDMIDICFG; else current_song->flags &= ~SONG_EMBEDMIDICFG; 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; } static void get_midi_config(void) { widgets_midi[1].d.toggle.state = !!(midi_flags & MIDI_TICK_QUANTIZE); widgets_midi[2].d.toggle.state = !!(midi_flags & MIDI_BASE_PROGRAM1); widgets_midi[3].d.toggle.state = !!(midi_flags & MIDI_RECORD_NOTEOFF); widgets_midi[4].d.toggle.state = !!(midi_flags & MIDI_RECORD_VELOCITY); widgets_midi[5].d.toggle.state = !!(midi_flags & MIDI_RECORD_AFTERTOUCH); widgets_midi[6].d.toggle.state = !!(midi_flags & MIDI_CUT_NOTE_OFF); widgets_midi[9].d.toggle.state = !!(midi_flags & MIDI_PITCHBEND); widgets_midi[11].d.toggle.state = !!(current_song->flags & SONG_EMBEDMIDICFG); widgets_midi[7].d.thumbbar.value = midi_amplification; widgets_midi[8].d.thumbbar.value = midi_c5note; widgets_midi[10].d.thumbbar.value = midi_pitch_depth; widgets_midi[12].d.thumbbar.value = ip_midi_getports(); } static void toggle_port(void) { struct midi_port *p; p = midi_engine_port(current_port, NULL); 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 -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_port += MOUSE_SCROLL_LINES; } 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 == KEY_PRESS) 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 == KEY_PRESS) 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 == KEY_RELEASE) return 1; change_focus_to(1); status.flags |= NEED_UPDATE; return 1; default: if (!k->mouse) return 0; break; }; if (k->state == KEY_RELEASE) 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) { 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( "Cut note off", 7, 35, 0, 2); draw_fill_chars(23, 30, 24, 35, 0); draw_box(19,29,25,36, 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); } static void midi_page_draw_portlist(void) { struct midi_port *p; const char *name, *state; char buffer[64]; int i, n, ct, fg, bg; unsigned long j; 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); 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, i + 15, 2, 0); if (top_midi_port + i >= ct) continue; /* err */ p = midi_engine_port(top_midi_port + i, &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(NULL) - status.last_midi_time) < 3 && ((!status.last_midi_port && p->io & MIDI_OUTPUT) || 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, status.last_midi_port ? 4 : 10, 0); } switch (p->io) { case 0: state = "Disabled "; break; case MIDI_INPUT: state = " Input "; break; case MIDI_OUTPUT: state = " Output "; break; case MIDI_INPUT | MIDI_OUTPUT: state = " Duplex "; break; default: state = " Enabled?"; break; } draw_text(state, 3, 15 + i, fg, bg); } } /* --------------------------------------------------------------------- */ void midi_load_page(struct page *page) { page->title = "Midi Screen (Shift-F1)"; page->draw_const = midi_page_redraw; page->song_changed_cb = NULL; page->predraw_hook = NULL; page->playback_update = NULL; page->handle_key = NULL; page->set_page = get_midi_config; page->total_widgets = 15; page->widgets = widgets_midi; page->help_index = HELP_GLOBAL; create_other(widgets_midi + 0, 0, midi_page_handle_key, midi_page_draw_portlist); 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, 7, 7, 7, update_midi_values); create_toggle(widgets_midi + 2, 20, 31, 1, 3, 8, 8, 8, update_midi_values); create_toggle(widgets_midi + 3, 20, 32, 2, 4, 8, 8, 8, update_midi_values); create_toggle(widgets_midi + 4, 20, 33, 3, 5, 9, 9, 9, update_midi_values); create_toggle(widgets_midi + 5, 20, 34, 4, 6, 9, 9, 9, update_midi_values); create_toggle(widgets_midi + 6, 20, 35, 5, 13, 10, 10, 10, update_midi_values); create_thumbbar(widgets_midi + 7, 53, 30, 20, 0, 8, 1, update_midi_values, 0, 200); create_thumbbar(widgets_midi + 8, 53, 31, 20, 7, 9, 2, update_midi_values, 0, 127); create_toggle(widgets_midi + 9, 53, 34, 8, 10, 5, 5, 5, update_midi_values); create_thumbbar(widgets_midi + 10, 53, 35, 20, 9, 11, 6, update_midi_values, 0, 48); create_toggle(widgets_midi + 11, 53, 38, 10, 12, 13, 13, 13, update_midi_values); create_thumbbar(widgets_midi + 12, 53, 41, 20, 11, 12, 13, update_ip_ports, 0, 128); create_button(widgets_midi + 13, 2, 41, 27, 6, 14, 12, 12, 12, midi_output_config, "MIDI Output Configuration", 2); create_button(widgets_midi + 14, 2, 44, 27, 13, 14, 12, 12, 12, cfg_midipage_save, "Save Output Configuration", 2); } schismtracker-20180209/schism/page_midiout.c000066400000000000000000000111121323741476300207310ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" /* --------------------------------------------------------------------- */ static struct widget widgets_midiout[33]; static int zxx_top = 0; static midi_config_t editcfg; /* --------------------------------------------------------------------- */ static void midiout_draw_const(void) { char buf[4] = "SFx"; 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); for (i = 2; i < 16; i++) { buf[2] = hexdigits[i]; draw_text(buf, 13, i + 24, 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 + zxx_top + 0x80); draw_text(buf, 13, i + 42, 0, 2); } } static void copy_out(void) { song_lock_audio(); memcpy(¤t_song->midi_config, &editcfg, sizeof(midi_config_t)); song_unlock_audio(); } static void copy_in(void) { song_lock_audio(); memcpy(&editcfg, ¤t_song->midi_config, sizeof(midi_config_t)); song_unlock_audio(); } static void zxx_setpos(int pos) { int i; /* 128 items, scrolled on 7 lines */ pos = CLAMP(pos, 0, 128 - 7); if (zxx_top == pos) return; zxx_top = pos; for (i = 0; i < 7; i++) widgets_midiout[25 + i].d.textentry.text = editcfg.zxx[zxx_top + i]; status.flags |= NEED_UPDATE; } static int pre_handle_key(struct key_event *k) { if (*selected_widget == 25 && k->sym == SDLK_UP) { /* scroll up */ if (k->state == KEY_RELEASE) return 1; if (zxx_top == 0) return 0; /* let the normal key handler catch it and change focus */ zxx_setpos(zxx_top - 1); return 1; } if (*selected_widget == 31 && k->sym == SDLK_DOWN) { /* scroll down */ if (k->state == KEY_RELEASE) return 1; zxx_setpos(zxx_top + 1); return 1; } if ((*selected_widget) >= 25) { switch (k->sym) { case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 1; zxx_setpos(zxx_top - 7); return 1; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 1; zxx_setpos(zxx_top + 7); return 1; default: break; }; } return 0; } static void midiout_set_page(void) { copy_in(); } 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; char *editcfg_top[] = { editcfg.start, editcfg.stop, editcfg.tick, editcfg.note_on, editcfg.note_off, editcfg.set_volume, editcfg.set_panning, editcfg.set_bank, editcfg.set_program, }; for (i = 0; i < 9; i++) { create_textentry(widgets_midiout + i, 17, 13 + i, 43, (i == 0 ? 0 : (i - 1)), i + 1, 9, copy_out, editcfg_top[i], 31); } for (i = 0; i < 16; i++) { create_textentry(widgets_midiout + 9 + i, 17, 24 + i, 43, 9 + i - 1, 9 + i + 1, 25, copy_out, editcfg.sfx[i], 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)), 0, copy_out, editcfg.zxx[i], 31); } } schismtracker-20180209/schism/page_orderpan.c000066400000000000000000000617061323741476300211070ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" /* --------------------------------------------------------------------- */ 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, csf_last_order(current_song), buf), 16, 5, 5, 0); } 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; int last_pattern = current_song->orderlist[new_order]; do { if (--new_order < 0) { new_order = 0; break; } } while (!(status.flags & CLASSIC_MODE) && last_pattern == current_song->orderlist[new_order] && current_song->orderlist[new_order] == ORDER_SKIP); if (current_song->orderlist[new_order] < 200) { current_order = new_order; orderlist_reposition(); set_current_pattern(current_song->orderlist[new_order]); } } void next_order_pattern(void) { int new_order = current_order; int last_pattern = current_song->orderlist[new_order]; do { if (++new_order > 255) { new_order = 255; break; } } while (!(status.flags & CLASSIC_MODE) && last_pattern == current_song->orderlist[new_order] && current_song->orderlist[new_order] == ORDER_SKIP); if (current_song->orderlist[new_order] < 200) { current_order = new_order; orderlist_reposition(); set_current_pattern(current_song->orderlist[new_order]); } } static void orderlist_cheater(void) { song_note_t *data; int cp, i, best, first; int rows; if (current_song->orderlist[current_order] != ORDER_SKIP && current_song->orderlist[current_order] != ORDER_LAST) { return; } cp = get_current_pattern(); best = first = -1; for (i = 0; i < 199; i++) { if (csf_pattern_is_empty(current_song, i)) { if (first == -1) first = i; if (best == -1) best = i; } else { best = -1; } } if (best == -1) best = first; if (best == -1) return; status_text_flash("Pattern %d copied to pattern %d, order %d", cp, best, current_order); data = song_pattern_allocate_copy(cp, &rows); song_pattern_resize(best, rows); song_pattern_install(best, data, rows); current_song->orderlist[current_order] = best; current_order++; status.flags |= SONG_NEEDS_SAVE; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ 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) { 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(current_song->orderlist[n], buf); draw_text(buf, 6, 15 + pos, 2, 0); } /* draw the cursor */ if (ACTIVE_PAGE.selected_widget == 0) { get_pattern_string(current_song->orderlist[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) { memmove(current_song->orderlist + current_order + 1, current_song->orderlist + current_order, 255 - current_order); current_song->orderlist[current_order] = ORDER_LAST; status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void orderlist_save(void) { memcpy(saved_orderlist, current_song->orderlist, 255); _did_save_orderlist = 1; } static void orderlist_restore(void) { unsigned char oldlist[256]; if (!_did_save_orderlist) return; memcpy(oldlist, current_song->orderlist, 255); memcpy(current_song->orderlist, saved_orderlist, 255); memcpy(saved_orderlist, oldlist, 255); } static void orderlist_delete_pos(void) { memmove(current_song->orderlist + current_order, current_song->orderlist + current_order + 1, 255 - current_order); current_song->orderlist[255] = ORDER_LAST; status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void orderlist_insert_next(void) { int next_pattern; if (current_order == 0 || current_song->orderlist[current_order - 1] > 199) return; next_pattern = current_song->orderlist[current_order - 1] + 1; if (next_pattern > 199) next_pattern = 199; current_song->orderlist[current_order] = next_pattern; if (current_order < 255) current_order++; orderlist_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } 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 = csf_get_num_patterns(current_song); uint8_t used[200] = {0}; /* could be a bitset... */ for (n = 0; n < 255; n++) if (current_song->orderlist[n] < 200) used[current_song->orderlist[n]] = 1; /* after the loop, n == 255 */ while (n >= 0 && current_song->orderlist[n] == 0xff) n--; if (n == -1) n = 0; else n += 2; n0 = n; for (p = 0; p < np; p++) { if (used[p] || csf_pattern_is_empty(current_song, p)) continue; if (n > 255) { /* status_text_flash("No more room in orderlist"); */ break; } current_song->orderlist[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); } } status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void orderlist_reorder(void) { /* err, I hope this is going to be done correctly... */ song_note_t *np[256] = {}; int nplen[256]; unsigned char mapol[256]; int i, j; song_lock_audio(); orderlist_add_unused_patterns(); memset(mapol, ORDER_LAST, sizeof(mapol)); for (i = j = 0; i < 255; i++) { if (current_song->orderlist[i] == ORDER_LAST || current_song->orderlist[i] == ORDER_SKIP) { continue; } if (mapol[ current_song->orderlist[i] ] == ORDER_LAST) { np[j] = song_pattern_allocate_copy(current_song->orderlist[i], &nplen[j]); mapol[ current_song->orderlist[i] ] = j; j++; } /* replace orderlist entry */ current_song->orderlist[i] = mapol[ current_song->orderlist[i] ]; } for (i = 0; i < 200; i++) { if (!np[i]) { song_pattern_install(i, NULL, 64); } else { song_pattern_install(i, np[i], nplen[i]); } } status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; song_stop_unlocked(0); song_unlock_audio(); } static int orderlist_handle_char(struct key_event *k) { int c; int cur_pattern; //unsigned char *list; song_note_t *tmp; int n[3] = { 0 }; switch (k->sym) { case SDLK_PLUS: if (k->state == KEY_RELEASE) return 1; status.flags |= SONG_NEEDS_SAVE; current_song->orderlist[current_order] = ORDER_SKIP; orderlist_cursor_pos = 2; break; case SDLK_PERIOD: case SDLK_MINUS: if (k->state == KEY_RELEASE) return 1; status.flags |= SONG_NEEDS_SAVE; current_song->orderlist[current_order] = ORDER_LAST; orderlist_cursor_pos = 2; break; default: c = numeric_key_event(k, 0); if (c == -1) return 0; if (k->state == KEY_RELEASE) return 1; status.flags |= SONG_NEEDS_SAVE; cur_pattern = current_song->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_pattern(cur_pattern, &tmp); /* make sure it exists */ current_song->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 int orderlist_handle_key_on_list(struct key_event * k) { int prev_order = current_order; int new_order = prev_order; int new_cursor_pos = orderlist_cursor_pos; int n, p; if (k->mouse != MOUSE_NONE) { if (k->x >= 6 && k->x <= 8 && k->y >= 15 && k->y <= 46) { /* FIXME adjust top_order, not the cursor */ if (k->mouse == MOUSE_SCROLL_UP) { new_order -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_order += MOUSE_SCROLL_LINES; } else { if (k->state == KEY_PRESS) return 0; new_order = (k->y - 15) + top_order; set_current_order(new_order); new_order = current_order; if (current_song->orderlist[current_order] != ORDER_LAST && current_song->orderlist[current_order] != ORDER_SKIP) { new_cursor_pos = (k->x - 6); } } } } switch (k->sym) { case SDLK_BACKSPACE: if (status.flags & CLASSIC_MODE) return 0; if (!(k->mod & KMOD_ALT)) return 0; if (k->state == KEY_PRESS) return 1; if (!_did_save_orderlist) return 1; status_text_flash("Restored orderlist"); orderlist_restore(); return 1; case SDLK_RETURN: case SDLK_KP_ENTER: if (status.flags & CLASSIC_MODE) return 0; if (k->mod & KMOD_ALT) { if (k->state == KEY_PRESS) return 1; status_text_flash("Saved orderlist"); orderlist_save(); return 1; } // else fall through case SDLK_g: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_PRESS) return 1; n = current_song->orderlist[new_order]; while (n >= 200 && new_order > 0) n = current_song->orderlist[--new_order]; if (n < 200) { set_current_pattern(n); set_page(PAGE_PATTERN_EDITOR); } return 1; case SDLK_TAB: if (k->mod & KMOD_SHIFT) { if (k->state == KEY_RELEASE) return 1; change_focus_to(33); } else { if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; change_focus_to(1); } return 1; case SDLK_LEFT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_pos--; break; case SDLK_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_pos++; break; case SDLK_HOME: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order = 0; break; case SDLK_END: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order = csf_last_order(current_song); if (current_song->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 == KEY_RELEASE) return 1; sample_set(sample_get_current()-1); status.flags |= NEED_UPDATE; return 1; } if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order--; break; case SDLK_DOWN: if (k->mod & KMOD_CTRL) { if (status.flags & CLASSIC_MODE) return 0; if (k->state == KEY_RELEASE) return 1; sample_set(sample_get_current()+1); status.flags |= NEED_UPDATE; return 1; } if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order++; break; case SDLK_PAGEUP: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order -= 16; break; case SDLK_PAGEDOWN: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order += 16; break; case SDLK_INSERT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; orderlist_insert_pos(); return 1; case SDLK_DELETE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; orderlist_delete_pos(); return 1; case SDLK_F7: if (!(k->mod & KMOD_CTRL)) return 0; /* fall through */ case SDLK_SPACE: if (k->state == KEY_RELEASE) return 1; song_set_next_order(current_order); status_text_flash("Playing order %d next", current_order); return 1; case SDLK_F6: if (k->mod & KMOD_SHIFT) { if (k->state == KEY_RELEASE) return 1; song_start_at_order(current_order, 0); return 1; } return 0; case SDLK_n: if (k->mod & KMOD_SHIFT) { if (k->state == KEY_PRESS) return 1; orderlist_cheater(); return 1; } if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; orderlist_insert_next(); return 1; case SDLK_c: if (!NO_MODIFIER(k->mod)) return 0; if (status.flags & CLASSIC_MODE) return 0; if (k->state == KEY_PRESS) return 1; p = get_current_pattern(); for (n = current_order+1; n < 256; n++) { if (current_song->orderlist[n] == p) { new_order = n; break; } } if (n == 256) { for (n = 0; n < current_order; n++) { if (current_song->orderlist[n] == p) { new_order = n; break; } } if (n == current_order) { status_text_flash("Pattern %d not on Order List", p); return 1; } } break; case SDLK_r: if (k->mod & KMOD_ALT) { if (k->state == KEY_PRESS) return 1; orderlist_reorder(); return 1; } return 0; case SDLK_u: if (k->mod & KMOD_ALT) { if (k->state == KEY_RELEASE) return 1; orderlist_add_unused_patterns(); return 1; } return 0; case SDLK_b: if (k->mod & KMOD_SHIFT) return 0; /* fall through */ case SDLK_o: if (!(k->mod & KMOD_CTRL)) return 0; if (k->state == KEY_RELEASE) return 1; song_pattern_to_sample(current_song->orderlist[current_order], !!(k->mod & KMOD_SHIFT), !!(k->sym == SDLK_b)); return 1; case SDLK_LESS: case SDLK_SEMICOLON: case SDLK_COLON: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; sample_set(sample_get_current()-1); status.flags |= NEED_UPDATE; return 1; case SDLK_GREATER: case SDLK_QUOTE: case SDLK_QUOTEDBL: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; sample_set(sample_get_current()+1); status.flags |= NEED_UPDATE; return 1; default: if (k->mouse == MOUSE_NONE) { 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]; int fg; strcpy(buf, "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++) { fg = 0; if (!(status.flags & CLASSIC_MODE)) { if (ACTIVE_PAGE.selected_widget == n) { fg = 3; } } numtostr(2, n, buf + 8); draw_text(buf, 20, 14 + n, fg, 2); fg = 0; if (!(status.flags & CLASSIC_MODE)) { if (ACTIVE_PAGE.selected_widget == n+32) { fg = 3; } } numtostr(2, n + 32, buf + 8); draw_text(buf, 54, 14 + n, fg, 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_t *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_t *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 = ACTIVE_PAGE.selected_widget; if (k->state == KEY_RELEASE) 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 (ACTIVE_PAGE.selected_widget != n) change_focus_to(n); } static int order_pre_key(struct key_event *k) { // hack to sync the active widget between pan/vol pages if (!(status.flags & CLASSIC_MODE)) { pages[PAGE_ORDERLIST_PANNING].selected_widget = pages[PAGE_ORDERLIST_VOLUMES].selected_widget = ACTIVE_PAGE.selected_widget; } if (k->sym == SDLK_F7) { if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; play_song_from_mark_orderpan(); return 1; } return 0; } static void order_pan_set_page(void) { orderpan_recheck_muted_channels(); } /* --------------------------------------------------------------------- */ 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->set_page = order_pan_set_page; 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].accept_text = 0; 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].accept_text = 0; 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); } /* --------------------------------------------------------------------- */ /* this function is a lost little puppy */ #define MAX_CHANNELS 64 // blah void song_set_pan_scheme(int scheme) { int n, nc; //int half; int active = 0; song_channel_t *chn = song_get_channel(0); //mphack alert, all pan values multiplied by 4 switch (scheme) { case PANS_STEREO: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = (n & 1) ? 256 : 0; } break; case PANS_AMIGA: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = ((n + 1) & 2) ? 256 : 0; } break; case PANS_LEFT: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = 0; } break; case PANS_RIGHT: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = 256; } break; case PANS_MONO: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = 128; } break; case PANS_SLASH: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) active++; } for (n = 0, nc = 0; nc < active; n++) { if (!(chn[n].flags & CHN_MUTE)) { chn[n].panning = 256 - (256 * nc / (active - 1)); nc++; } } break; case PANS_BACKSLASH: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) active++; } for (n = 0, nc = 0; nc < active; n++) { if (!(chn[n].flags & CHN_MUTE)) { chn[n].panning = (256 * nc / (active - 1)); nc++; } } break; #if 0 case PANS_CROSS: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) active++; } half = active / 2; for (n = 0, nc = 0; nc < half; n++) { if (!(chn[n].flags & CHN_MUTE)) { if (nc & 1) { // right bias - go from 64 to 32 chn[n].panning = (64 - (32 * nc / half)) * 4; } else { // left bias - go from 0 to 32 chn[n].panning = (32 * nc / half) * 4; } nc++; } } for (; nc < active; n++) { if (!(chn[n].flags & CHN_MUTE)) { if (nc & 1) { chn[n].panning = (64 - (32 * (active - nc) / half)) * 4; } else { chn[n].panning = (32 * (active - nc) / half) * 4; } nc++; } } break; #endif default: printf("oh i am confused\n"); } // get the values on the page to correspond to the song... order_pan_vol_song_changed_cb(); } schismtracker-20180209/schism/page_palette.c000066400000000000000000000214251323741476300207250ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" /* --------------------------------------------------------------------- */ 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 == MOUSE_CLICK) { if (k->state == KEY_PRESS) 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 == KEY_RELEASE) return 0; if (k->mouse == MOUSE_SCROLL_UP) new_palette -= MOUSE_SCROLL_LINES; else if (k->mouse == MOUSE_SCROLL_DOWN) new_palette += MOUSE_SCROLL_LINES; } 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 == MOUSE_NONE) 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 == KEY_RELEASE) 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; } schismtracker-20180209/schism/page_patedit.c000066400000000000000000003721031323741476300207230ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #include "it.h" #include "page.h" #include "song.h" #include "pattern-view.h" #include "config-parser.h" #include "midi.h" #include "osdefs.h" #include "sdlmain.h" #include "clippy.h" #include "disko.h" /* --------------------------------------------------------------------------------------------------------- */ #define ROW_IS_MAJOR(r) (current_song->row_highlight_major != 0 && (r) % current_song->row_highlight_major == 0) #define ROW_IS_MINOR(r) (current_song->row_highlight_minor != 0 && (r) % current_song->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 */ static int midi_start_record = 0; enum { TEMPLATE_OFF = 0, TEMPLATE_OVERWRITE, TEMPLATE_MIX_PATTERN_PRECEDENCE, TEMPLATE_MIX_CLIPBOARD_PRECEDENCE, TEMPLATE_NOTES_ONLY, TEMPLATE_MODE_MAX, }; static int template_mode = TEMPLATE_OFF; static const char *template_mode_names[] = { "", "Template, Overwrite", "Template, Mix - Pattern data precedence", "Template, Mix - Clipboard data precedence", "Template, Notes only", }; /* 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; static int keyjazz_noteoff = 0; /* issue noteoffs when releasing note */ static int keyjazz_write_noteoff = 0; /* write noteoffs when releasing note */ static int keyjazz_repeat = 1; /* insert multiple notes on key repeat */ /* 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 shift_chord_channels = 0; /* incremented for each shift-note played */ 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_save(const char *descr); static void pated_history_add2(int groupedf, const char *descr, int x, int y, int width, int height); static void pated_history_add(const char *descr, int x, int y, int width, int height); static void pated_history_add_grouped(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) */ static int edit_copy_mask = MASK_NOTE | MASK_INSTRUMENT | MASK_VOLUME; /* and the mask note. note that the instrument field actually isn't used */ static song_note_t 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 */ enum { COPY_INST_OFF = 0, /* no search (IT style) */ COPY_INST_UP = 1, /* search above the cursor for an instrument number */ COPY_INST_UP_THEN_DOWN = 2, /* search both ways, up to row 0 first, then down */ COPY_INST_SENTINEL = 3, /* non-value */ }; static int mask_copy_search_mode = COPY_INST_OFF; /* If nonzero, home/end will move to the first/last row in the current channel prior to moving to the first/last channel, i.e. operating in a 'z' pattern. This is closer to FT2's behavior for the keys. */ static int invert_home_end = 0; /* --------------------------------------------------------------------- */ /* undo and clipboard handling */ struct pattern_snap { song_note_t *data; int channels; int rows; /* used by undo/history only */ const char *snap_op; int snap_op_allocated; int x, y; int patternno; }; static struct pattern_snap fast_save = { NULL, 0, 0, "Fast Pattern Save", 0, 0, 0, -1 }; /* static int fast_save_validity = -1; */ static void snap_paste(struct pattern_snap *s, int x, int y, int xlate); static struct pattern_snap clipboard = { NULL, 0, 0, "Clipboard", 0, 0, 0, -1 }; 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 */ 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 }; /* 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) /* CHECK_FOR_SELECTION(optional return value) will display an error dialog and cause the function to return if there is no block marked. (The spaces around the text are to make it line up the same as Impulse Tracker) */ #define CHECK_FOR_SELECTION(q) do {\ if (!SELECTION_EXISTS) {\ dialog_create(DIALOG_OK, " No block is marked ", NULL, NULL, 0, NULL);\ q;\ }\ } while(0) /* --------------------------------------------------------------------- */ /* 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; draw_mask_func draw_mask; }; static const struct track_view track_views[] = { #define TRACK_VIEW(n) {n, draw_channel_header_##n, draw_note_##n, draw_mask_##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 uint8_t track_view_scheme[64]; static int channel_multi_enabled = 0; static int channel_multi[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 const int options_link_split[] = { 5, 6, -1 }; static int options_selected_widget = 0; static int options_last_octave = 0; static void options_close_cancel(UNUSED void *data) { kbd_set_current_octave(options_last_octave); } static void options_close(void *data) { int old_size, new_size; options_selected_widget = ((struct dialog *) data)->selected_widget; skip_value = options_widgets[1].d.thumbbar.value; current_song->row_highlight_minor = options_widgets[2].d.thumbbar.value; current_song->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 void options_draw_const(void) { draw_text("Pattern Editor Options", 28, 19, 0, 2); draw_text("Base octave", 28, 23, 0, 2); draw_text("Cursor step", 28, 26, 0, 2); draw_text("Row hilight minor", 22, 29, 0, 2); draw_text("Row hilight major", 22, 32, 0, 2); draw_text("Number of rows in pattern", 14, 35, 0, 2); draw_text("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 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; if (options_widgets[0].width == 0) { /* haven't built it yet */ 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); /* Although patterns as small as 1 row can be edited properly (as of c759f7a0166c), I have discovered it's a bit annoying to hit 'home' here expecting to get 32 rows but end up with just one row instead. so I'll allow editing these patterns, but not really provide a way to set the size, at least until I decide how to present the option nonintrusively. */ 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_last_octave = kbd_get_current_octave(); options_widgets[0].d.thumbbar.value = options_last_octave; options_widgets[1].d.thumbbar.value = skip_value; options_widgets[2].d.thumbbar.value = current_song->row_highlight_minor; options_widgets[3].d.thumbbar.value = current_song->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; if (status.flags & CLASSIC_MODE) { dialog->action_cancel = options_close; } else { dialog->action_cancel = options_close_cancel; } dialog->data = dialog; } static struct widget template_error_widgets[1]; static void template_error_draw(void) { draw_text("Template Error", 33, 25, 0, 2); draw_text("No note in the top left position", 23, 27, 0, 2); draw_text("of the clipboard on which to", 25, 28, 0, 2); draw_text("base translations.", 31, 29, 0, 2); } /* --------------------------------------------------------------------------------------------------------- */ /* 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("Set Pattern Length", 31, 21, 0, 2); draw_text("Pattern Length", 19, 24, 0, 2); draw_text("Start Pattern", 20, 27, 0, 2); draw_text("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, NULL) != 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; 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, NULL ); 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, m = 0; for (i = 0; i < 64; i++) { channel_multi[i] = !!multichannel_widgets[i].d.toggle.state; if (channel_multi[i]) m = 1; } if (m) channel_multi_enabled = 1; } static int multichannel_handle_key(struct key_event *k) { if (k->sym == SDLK_n) { if ((k->mod & KMOD_ALT) && k->state == KEY_PRESS) dialog_yes(NULL); else if (NO_MODIFIER(k->mod) && k->state == KEY_RELEASE) dialog_cancel(NULL); return 1; } return 0; } static void multichannel_draw_const(void) { 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("Multichannel Selection", 29, 19, 3, 2); } static void mp_advance_channel(void) { change_focus_to(*selected_widget + 1); } static 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 + 16) % 64), mp_advance_channel); multichannel_widgets[i].d.toggle.state = !!channel_multi[i]; } create_button(multichannel_widgets + 64, 36, 40, 6, 15, 0, 64, 64, 64, dialog_yes_NULL, "OK", 3); 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; } /* This probably doesn't belong here, but whatever */ static int multichannel_get_next(int cur_channel) { int i; cur_channel--; /* make it zero-based. oh look, it's a hammer. */ i = cur_channel; if (channel_multi[cur_channel]) { /* we're in a multichan-enabled channel, so look for the next one */ do { i = (i + 1) & 63; /* no? next channel, and loop back to zero if we hit 64 */ if (channel_multi[i]) /* is this a multi-channel? */ break; /* it is! */ } while (i != cur_channel); /* at this point we've either broken the loop because the channel i is multichan, or the condition failed because we're back where we started */ } /* status_text_flash ("Newly selected channel is %d", (int) i + 1); */ return i + 1; /* make it one-based again */ } static int multichannel_get_previous(int cur_channel) { int i; cur_channel--; /* once again, .... */ i = cur_channel; if (channel_multi[cur_channel]) { do { i = i ? (i - 1) : 63; /* loop backwards this time */ if (channel_multi[i]) break; } while (i != cur_channel); } return i + 1; } /* --------------------------------------------------------------------------------------------------------- */ static void copyin_addnote(song_note_t *note, int *copyin_x, int *copyin_y) { song_note_t *pattern, *p_note; int num_rows; status.flags |= (SONG_NEEDS_SAVE|NEED_UPDATE); num_rows = song_get_pattern(current_pattern, &pattern); if ((*copyin_x + (current_channel-1)) >= 64) return; if ((*copyin_y + current_row) >= num_rows) return; p_note = pattern + 64 * (*copyin_y + current_row) + (*copyin_x + (current_channel-1)); *p_note = *note; (*copyin_x) = (*copyin_x) + 1; } static void copyin_addrow(int *copyin_x, int *copyin_y) { *copyin_x=0; (*copyin_y) = (*copyin_y) + 1; } static int pattern_selection_system_paste(UNUSED int cb, const void *data) { int copyin_x, copyin_y; int (*fx_map)(char f); const char *str; int x, scantmp; if (!data) return 0; str = (const char *)data; for (x = 0; str[x] && str[x] != '\n'; x++); if (x <= 11) return 0; if (!str[x] || str[x+1] != '|') return 0; if (str[x-1] == '\r') x--; if ((str[x-3] == ' ' && str[x-2] == 'I' && str[x-1] == 'T') || (str[x-3] == 'S' && str[x-2] == '3' && str[x-1] == 'M')) { /* s3m effects */ fx_map = get_effect_number; } else if ((str[x-3] == ' ' && str[x-2] == 'X' && str[x-1] == 'M') || (str[x-3] == 'M' && str[x-2] == 'O' && str[x-1] == 'D')) { /* ptm effects */ fx_map = get_ptm_effect_number; } else { return 0; } if (str[x] == '\r') x++; str += x+2; copyin_x = copyin_y = 0; /* okay, let's start parsing */ while (*str) { song_note_t n = {}; if (!str[0] || !str[1] || !str[2]) break; switch (*str) { case 'C': case 'c': n.note = 1; break; case 'D': case 'd': n.note = 3; break; case 'E': case 'e': n.note = 5; break; case 'F': case 'f': n.note = 6; break; case 'G': case 'g': n.note = 8; break; case 'A': case 'a': n.note = 10;break; case 'B': case 'b': n.note = 12;break; default: n.note = 0; }; /* XXX shouldn't this be note-- for flat? */ if (str[1] == '#' || str[1] == 'b') n.note++; switch (*str) { case '=': n.note = NOTE_OFF; break; case '^': n.note = NOTE_CUT; break; case '~': n.note = NOTE_FADE; break; case ' ': case '.': n.note = 0; break; default: n.note += ((str[2] - '0') * 12); break; }; str += 3; /* instrument number */ if (sscanf(str, "%02d", &scantmp) == 1) n.instrument = scantmp; else n.instrument = 0; str += 2; while (*str) { if (*str == '|' || *str == '\r' || *str == '\n') break; if (!str[0] || !str[1] || !str[2]) break; if (*str >= 'a' && *str <= 'z') { if (sscanf(str+1, "%02d", &scantmp) == 1) n.volparam = scantmp; else n.volparam = 0; switch (*str) { case 'v': n.voleffect = VOLFX_VOLUME; break; case 'p': n.voleffect = VOLFX_PANNING; break; case 'c': n.voleffect = VOLFX_VOLSLIDEUP; break; case 'd': n.voleffect = VOLFX_VOLSLIDEDOWN; break; case 'a': n.voleffect = VOLFX_FINEVOLUP; break; case 'b': n.voleffect = VOLFX_FINEVOLDOWN; break; case 'u': n.voleffect = VOLFX_VIBRATOSPEED; break; case 'h': n.voleffect = VOLFX_VIBRATODEPTH; break; case 'l': n.voleffect = VOLFX_PANSLIDELEFT; break; case 'r': n.voleffect = VOLFX_PANSLIDERIGHT; break; case 'g': n.voleffect = VOLFX_TONEPORTAMENTO; break; case 'f': n.voleffect = VOLFX_PORTAUP; break; case 'e': n.voleffect = VOLFX_PORTADOWN; break; default: n.voleffect = VOLFX_NONE; n.volparam = 0; break; }; } else { n.effect = fx_map(*str); if (sscanf(str+1, "%02X", &scantmp) == 1) n.param = scantmp; else n.param = 0; } str += 3; } copyin_addnote(&n, ©in_x, ©in_y); if (str[0] == '\r' || str[0] == '\n') { while (str[0] == '\r' || str[0] == '\n') str++; copyin_addrow(©in_x, ©in_y); } if (str[0] != '|') break; str++; } return 1; } static void pattern_selection_system_copyout(void) { char *str; int x, y, len; int total_rows; song_note_t *pattern, *cur_note; if (!(SELECTION_EXISTS)) { if (clippy_owner(CLIPPY_SELECT) == widgets_pattern) { /* unselect if we don't have a selection */ clippy_select(NULL, NULL, 0); } return; } len = 21; total_rows = song_get_pattern(current_pattern, &pattern); for (y = selection.first_row; y <= selection.last_row && y < total_rows; y++) { for (x = selection.first_channel; x <= selection.last_channel; x++) { /* must match template below */ len += 12; } len += 2; } str = mem_alloc(len+1); /* the OpenMPT/ModPlug header says: ModPlug Tracker S3M\x0d\x0a but really we can get away with: Pasted Pattern - IT\x0d\x0a because that's just how it's parser works. Add your own- just remember the file "type" is right-aligned. Please don't invent any new formats- even if you add more effects, try to base most of them on protracker (XM/MOD) or S3M/IT. */ strcpy(str, "Pasted Pattern - IT\x0d\x0a"); len = 21; for (y = selection.first_row; y <= selection.last_row && y < total_rows; y++) { cur_note = pattern + 64 * y + selection.first_channel - 1; for (x = selection.first_channel; x <= selection.last_channel; x++) { str[len] = '|'; len++; 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) { /* ModPlug won't handle this one, but it'll just drop it... */ str[len] = str[len+1] = str[len+2] = '~'; /* ~~~ */ } else { get_note_string(cur_note->note, str+len); } len += 3; if (cur_note->instrument) sprintf(str+len, "%02d", cur_note->instrument); else str[len] = str[len+1] = '.'; sprintf(str+len+3, "%02d", cur_note->volparam); switch (cur_note->voleffect) { case VOLFX_VOLUME: str[len+2] = 'v';break; case VOLFX_PANNING: str[len+2] = 'p';break; case VOLFX_VOLSLIDEUP: str[len+2] = 'c';break; case VOLFX_VOLSLIDEDOWN: str[len+2] = 'd';break; case VOLFX_FINEVOLUP: str[len+2] = 'a';break; case VOLFX_FINEVOLDOWN: str[len+2] = 'b';break; case VOLFX_VIBRATOSPEED: str[len+2] = 'u';break; case VOLFX_VIBRATODEPTH: str[len+2] = 'h';break; case VOLFX_PANSLIDELEFT: str[len+2] = 'l';break; case VOLFX_PANSLIDERIGHT: str[len+2] = 'r';break; case VOLFX_TONEPORTAMENTO: str[len+2] = 'g';break; case VOLFX_PORTAUP: str[len+2] = 'f';break; case VOLFX_PORTADOWN: str[len+2] = 'e';break; default: str[len+2] = '.'; /* override above */ str[len+3] = '.'; str[len+4] = '.'; }; len += 5; sprintf(str+len, "%c%02X", get_effect_char(cur_note->effect), cur_note->param); if (str[len] == '.' || str[len] == '?') { str[len] = '.'; if (!cur_note->param) str[len+1] = str[len+2] = '.'; } len += 3; /* Hints to implementors: If you had more columns in your channel, you should mark it here with a letter representing the channel semantic, followed by your decimal value. Add as many as you like- the next channel begins with a pipe-character (|) and the next row begins with a 0D 0A sequence. */ cur_note++; } str[len] = '\x0d'; str[len+1] = '\x0a'; len += 2; } 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("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 == KEY_PRESS) return 0; dialog_cancel(NULL); status.flags |= NEED_UPDATE; return 1; case SDLK_UP: if (k->state == KEY_RELEASE) return 0; undo_selection--; if (undo_selection < 0) undo_selection = 0; status.flags |= NEED_UPDATE; return 1; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 0; undo_selection++; if (undo_selection > 9) undo_selection = 9; status.flags |= NEED_UPDATE; return 1; case SDLK_RETURN: if (k->state == KEY_RELEASE) 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; default: break; }; return 0; } static 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 int volume_amplify_jj(struct key_event *k) { if (k->state == KEY_PRESS && (k->mod & KMOD_ALT) && (k->sym == SDLK_j)) { dialog_yes(NULL); return 1; } return 0; } static void volume_amplify(void) { struct dialog *dialog; CHECK_FOR_SELECTION(return); 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->handle_key = volume_amplify_jj; dialog->action_yes = volume_amplify_ok; } /* --------------------------------------------------------------------------------------------------------- */ /* vary depth */ static int current_vary = -1; 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(UNUSED void *data) { vary_depth = volume_setup_widgets[0].d.thumbbar.value; selection_vary(0, vary_depth, current_vary); } 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, NULL); dialog->action_yes = vary_amplify_ok; current_vary = how; } static int current_effect(void) { song_note_t *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); CFG_SET_PE(keyjazz_noteoff); CFG_SET_PE(keyjazz_write_noteoff); CFG_SET_PE(keyjazz_repeat); CFG_SET_PE(mask_copy_search_mode); CFG_SET_PE(invert_home_end); cfg_set_number(cfg, "Pattern Editor", "crayola_mode", !!(status.flags & CRAYOLA_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]) ? '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; char 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_PE(keyjazz_noteoff, 0); CFG_GET_PE(keyjazz_write_noteoff, 0); CFG_GET_PE(keyjazz_repeat, 1); CFG_GET_PE(mask_copy_search_mode, 0); CFG_GET_PE(invert_home_end, 0); if (cfg_get_number(cfg, "Pattern Editor", "crayola_mode", 0)) status.flags |= CRAYOLA_MODE; else status.flags &= ~CRAYOLA_MODE; cfg_get_string(cfg, "Pattern Editor", "track_view_scheme", s, 64, "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, 64, ""); memset(channel_multi, 0, sizeof(channel_multi)); channel_multi_enabled = 0; 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_enabled = 1; } 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_copyout(); } static void selection_clear(void) { selection.first_channel = 0; pattern_selection_system_copyout(); } // FIXME | this misbehaves if height is an odd number -- e.g. if an odd number // FIXME | of rows is selected and 2 * sel_rows overlaps the end of the pattern static void block_length_double(void) { song_note_t *pattern, *src, *dest; int sel_rows, total_rows; int src_end, dest_end; // = first row that is NOT affected int width, height, offset; if (!SELECTION_EXISTS) return; status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows) selection.last_row = total_rows - 1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; sel_rows = selection.last_row - selection.first_row + 1; offset = selection.first_channel - 1; width = selection.last_channel - offset; dest_end = MIN(selection.first_row + 2 * sel_rows, total_rows); height = dest_end - selection.first_row; src_end = selection.first_row + height / 2; src = pattern + 64 * (src_end - 1); dest = pattern + 64 * (dest_end - 1); pated_history_add("Undo block length double (Alt-F)", offset, selection.first_row, width, height); while (dest > src) { memset(dest + offset, 0, width * sizeof(song_note_t)); dest -= 64; memcpy(dest + offset, src + offset, width * sizeof(song_note_t)); dest -= 64; src -= 64; } pattern_selection_system_copyout(); } // FIXME: this should erase the end of the selection if 2 * sel_rows > total_rows static void block_length_halve(void) { song_note_t *pattern, *src, *dest; int sel_rows, src_end, total_rows, row; int width, height, offset; if (!SELECTION_EXISTS) return; status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows) selection.last_row = total_rows - 1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; sel_rows = selection.last_row - selection.first_row + 1; offset = selection.first_channel - 1; width = selection.last_channel - offset; src_end = MIN(selection.first_row + 2 * sel_rows, total_rows); height = src_end - selection.first_row; src = dest = pattern + 64 * selection.first_row; pated_history_add("Undo block length halve (Alt-G)", offset, selection.first_row, width, height); for (row = 0; row < height / 2; row++) { memcpy(dest + offset, src + offset, width * sizeof(song_note_t)); src += 64 * 2; dest += 64; } pattern_selection_system_copyout(); } static void selection_erase(void) { song_note_t *pattern, *note; int row; int chan_width; int total_rows; if (!SELECTION_EXISTS) return; status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; 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_t)); } 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_t)); } } pattern_selection_system_copyout(); } static void selection_set_sample(void) { int row, chan; song_note_t *pattern, *note; int total_rows; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; 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_copyout(); } static void selection_swap(void) { /* s_note = selection; p_note = position */ song_note_t *pattern, *s_note, *p_note, tmp; int row, chan, sel_rows, sel_chans, total_rows; int affected_width, affected_height; CHECK_FOR_SELECTION(return); status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; sel_rows = selection.last_row - selection.first_row + 1; sel_chans = selection.last_channel - selection.first_channel + 1; affected_width = MAX(selection.last_channel, current_channel + sel_chans - 1) - MIN(selection.first_channel, current_channel) + 1; affected_height = MAX(selection.last_row, current_row + sel_rows - 1) - MIN(selection.first_row, current_row) + 1; /* 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 (affected_width < 2 * sel_chans && affected_height < 2 * sel_rows) { dialog_create(DIALOG_OK, " Swap blocks overlap ", NULL, NULL, 0, NULL); return; } if (current_row + sel_rows > total_rows || current_channel + sel_chans - 1 > 64) { dialog_create(DIALOG_OK, " Out of pattern range ", NULL, NULL, 0, NULL); return; } pated_history_add("Undo swap block (Alt-Y)", MIN(selection.first_channel, current_channel) - 1, MIN(selection.first_row, current_row), affected_width, affected_height); for (row = 0; row < sel_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 < sel_chans; chan++, s_note++, p_note++) { tmp = *s_note; *s_note = *p_note; *p_note = tmp; } } pattern_selection_system_copyout(); } static void selection_set_volume(void) { int row, chan, total_rows; song_note_t *pattern, *note; CHECK_FOR_SELECTION(return); status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; 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->volparam = mask_note.volparam; note->voleffect = mask_note.voleffect; } } pattern_selection_system_copyout(); } /* The logic for this one makes my head hurt. */ static void selection_slide_volume(void) { int row, chan, total_rows; song_note_t *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? */ /* Impulse Tracker displays a box "No block is marked" */ CHECK_FOR_SELECTION(return); total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; /* can't slide one row */ if (selection.first_row == selection.last_row) return; status.flags |= SONG_NEEDS_SAVE; 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->voleffect; lve = last_note->voleffect; first = note->volparam; last = last_note->volparam; /* 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 == VOLFX_NONE) { if (note->instrument == 0) continue; ve = VOLFX_VOLUME; /* Modplug hack: volume bit shift */ first = song_get_sample(note->instrument)->volume >> 2; } if (lve == VOLFX_NONE) { if (last_note->instrument == 0) continue; lve = VOLFX_VOLUME; last = song_get_sample(last_note->instrument)->volume >> 2; } if (!(ve == lve && (ve == VOLFX_VOLUME || ve == VOLFX_PANNING))) { continue; } for (row = selection.first_row; row <= selection.last_row; row++, note += 64) { note->voleffect = ve; note->volparam = (((last - first) * (row - selection.first_row) / (selection.last_row - selection.first_row) ) + first); } } pattern_selection_system_copyout(); } static void selection_wipe_volume(int reckless) { int row, chan, total_rows; song_note_t *pattern, *note; CHECK_FOR_SELECTION(return); total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; status.flags |= SONG_NEEDS_SAVE; 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->instrument == 0 && !NOTE_IS_NOTE(note->note))) { note->volparam = 0; note->voleffect = VOLFX_NONE; } } } pattern_selection_system_copyout(); } 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(int ch) { switch (ch) { case FX_PORTAMENTODOWN: case FX_PORTAMENTOUP: case FX_TONEPORTAMENTO: return FX_TONEPORTAMENTO; case FX_VOLUMESLIDE: case FX_TONEPORTAVOL: case FX_VIBRATOVOL: return FX_VOLUMESLIDE; case FX_PANNING: case FX_PANNINGSLIDE: case FX_PANBRELLO: return FX_PANNING; default: return ch; /* err... */ }; } static void selection_vary(int fast, int depth, int how) { int row, chan, total_rows; song_note_t *pattern, *note; static char last_vary[39]; const char *vary_how; char ch; /* don't ever vary these things */ switch (how) { default: if (!FX_IS_EFFECT(how)) return; break; case FX_NONE: case FX_SPECIAL: case FX_SPEED: case FX_POSITIONJUMP: case FX_PATTERNBREAK: case FX_KEYOFF: case FX_SETENVPOSITION: case FX_VOLUME: case FX_NOTESLIDEUP: case FX_NOTESLIDEDOWN: return; } CHECK_FOR_SELECTION(return); status.flags |= SONG_NEEDS_SAVE; switch (how) { case FX_CHANNELVOLUME: case FX_CHANNELVOLSLIDE: vary_how = "Undo volume-channel vary (Ctrl-U)"; if (fast) status_text_flash("Fast volume vary"); break; case FX_PANNING: case FX_PANNINGSLIDE: case FX_PANBRELLO: vary_how = "Undo panning vary (Ctrl-Y)"; if (fast) status_text_flash("Fast panning vary"); break; default: sprintf(last_vary, "%-28s (Ctrl-K)", "Undo Xxx effect-value vary"); last_vary[5] = common_variable_group(how); if (fast) status_text_flash("Fast %-21s", last_vary+5); vary_how = last_vary; break; }; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; 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 == FX_CHANNELVOLUME || how == FX_CHANNELVOLSLIDE) { if (note->voleffect == VOLFX_VOLUME) { note->volparam = vary_value(note->volparam, 64, depth); } } if (how == FX_PANNINGSLIDE || how == FX_PANNING || how == FX_PANBRELLO) { if (note->voleffect == VOLFX_PANNING) { note->volparam = vary_value(note->volparam, 64, depth); } } ch = note->effect; if (!FX_IS_EFFECT(ch)) continue; if (common_variable_group(ch) != common_variable_group(how)) continue; switch (ch) { /* these are .0 0. and .f f. values */ case FX_VOLUMESLIDE: case FX_CHANNELVOLSLIDE: case FX_PANNINGSLIDE: case FX_GLOBALVOLSLIDE: case FX_VIBRATOVOL: case FX_TONEPORTAVOL: if ((note->param & 15) == 15) continue; if ((note->param & 0xF0) == (0xF0))continue; if ((note->param & 15) == 0) { note->param = (1+(vary_value(note->param>>4, 15, depth))) << 4; } else { note->param = 1+(vary_value(note->param & 15, 15, depth)); } break; /* tempo has a slide */ case FX_TEMPO: if ((note->param & 15) == 15) continue; if ((note->param & 0xF0) == (0xF0))continue; /* but otherwise it's absolute */ note->param = 1 + (vary_value(note->param, 255, depth)); break; /* don't vary .E. and .F. values */ case FX_PORTAMENTODOWN: case FX_PORTAMENTOUP: if ((note->param & 15) == 15) continue; if ((note->param & 15) == 14) continue; if ((note->param & 0xF0) == (0xF0))continue; if ((note->param & 0xF0) == (0xE0))continue; note->param = 16 + (vary_value(note->param-16, 224, depth)); break; /* these are all "xx" commands */ // FIXME global/channel volume should be limited to 0-128 and 0-64, respectively case FX_TONEPORTAMENTO: case FX_CHANNELVOLUME: case FX_OFFSET: case FX_GLOBALVOLUME: case FX_PANNING: note->param = 1 + (vary_value(note->param, 255, depth)); break; /* these are all "xy" commands */ case FX_VIBRATO: case FX_TREMOR: case FX_ARPEGGIO: case FX_RETRIG: case FX_TREMOLO: case FX_PANBRELLO: case FX_FINEVIBRATO: note->param = (1 + (vary_value(note->param & 15, 15, depth))) | ((1 + (vary_value((note->param >> 4) & 15, 15, depth))) << 4); break; }; } } pattern_selection_system_copyout(); } static void selection_amplify(int percentage) { int row, chan, volume, total_rows; song_note_t *pattern, *note; if (!SELECTION_EXISTS) return; status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; /* 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->voleffect == VOLFX_NONE && note->instrument != 0) { /* Modplug hack: volume bit shift */ if (song_is_instrument_mode()) volume = 64; /* XXX */ else volume = song_get_sample(note->instrument)->volume >> 2; } else if (note->voleffect == VOLFX_VOLUME) { volume = note->volparam; } else { continue; } volume *= percentage; volume /= 100; if (volume > 64) volume = 64; else if (volume < 0) volume = 0; note->volparam = volume; note->voleffect = VOLFX_VOLUME; } } pattern_selection_system_copyout(); } static void selection_slide_effect(void) { int row, chan, total_rows; song_note_t *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); total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; if (selection.first_row == selection.last_row) return; status.flags |= SONG_NEEDS_SAVE; 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].param; last = note[64 * selection.last_row].param; note += 64 * selection.first_row; for (row = selection.first_row; row <= selection.last_row; row++, note += 64) { note->param = (((last - first) * (row - selection.first_row) / (selection.last_row - selection.first_row) ) + first); } } pattern_selection_system_copyout(); } static void selection_wipe_effect(void) { int row, chan, total_rows; song_note_t *pattern, *note; CHECK_FOR_SELECTION(return); total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; status.flags |= SONG_NEEDS_SAVE; 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->param = 0; } } pattern_selection_system_copyout(); } enum roll_dir { ROLL_DOWN = -1, ROLL_UP = +1 }; static void selection_roll(enum roll_dir direction) { song_note_t *pattern, *seldata; int row, sel_rows, sel_chans, total_rows, copy_bytes, n; if (!SELECTION_EXISTS) { return; } total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows) { selection.last_row = total_rows - 1; } if (selection.first_row > selection.last_row) { selection.first_row = selection.last_row; } sel_rows = selection.last_row - selection.first_row + 1; sel_chans = selection.last_channel - selection.first_channel + 1; if (sel_rows < 2) { return; } seldata = pattern + 64 * selection.first_row + selection.first_channel - 1; song_note_t temp[sel_chans]; copy_bytes = sizeof(temp); row = (direction == ROLL_DOWN ? sel_rows - 1 : 0); memcpy(temp, seldata + 64 * row, copy_bytes); for (n = 1; n < sel_rows; n++, row += direction) { memcpy(seldata + 64 * row, seldata + 64 * (row + direction), copy_bytes); } memcpy(seldata + 64 * row, temp, copy_bytes); status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------------------------------------------- */ /* Row shifting operations */ /* A couple of the param 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_t *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_t) * (total_rows - what_row - num_rows)); memset(pattern + 64 * what_row, 0, num_rows * 64 * sizeof(song_note_t)); } 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_t)); } /* 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_t)); } } pattern_selection_system_copyout(); } /* 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_t *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_t) * (total_rows - what_row - num_rows)); memset(pattern + 64 * (total_rows - num_rows), 0, num_rows * 64 * sizeof(song_note_t)); } 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_t)); } /* 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_t)); } } pattern_selection_system_copyout(); } /* --------------------------------------------------------------------------------------------------------- */ /* history/undo */ static void pated_history_clear(void) { // clear undo history int i; for (i = 0; i < 10; i++) { if (undo_history[i].snap_op_allocated) free((void *) undo_history[i].snap_op); free(undo_history[i].data); memset(&undo_history[i],0,sizeof(struct pattern_snap)); undo_history[i].snap_op = "Empty"; undo_history[i].snap_op_allocated = 0; } } static void set_note_note(song_note_t *n, int a, int b) { if (a > 0 && a < 250) { a += b; if (a <= 0 || a >= 250) a = 0; } n->note = a; } static void snap_paste(struct pattern_snap *s, int x, int y, int xlate) { song_note_t *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; if (num_rows <= 0) return; 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_t)); if (!xlate) continue; for (chan = 0; chan < chan_width; chan++) { if (chan + x > 64) break; /* defensive */ set_note_note(p_note+chan, p_note[chan].note, xlate); } } pattern_selection_system_copyout(); } static void snap_copy(struct pattern_snap *s, int x, int y, int width, int height) { song_note_t *pattern; int row, total_rows, len; memused_songchanged(); s->channels = width; s->rows = height; total_rows = song_get_pattern(current_pattern, &pattern); s->data = mem_alloc(len = (sizeof(song_note_t) * s->channels * s->rows)); if (s->rows > total_rows) { memset(s->data, 0, len); } s->x = x; s->y = y; if (x == 0 && width == 64) { if (height >total_rows) height = total_rows; memcpy(s->data, pattern + 64 * y, (width*height*sizeof(song_note_t))); } else { for (row = 0; row < s->rows && row < total_rows; row++) { memcpy(s->data + s->channels * row, pattern + 64 * (row + s->y) + s->x, s->channels * sizeof(song_note_t)); } } } static int snap_honor_mute(struct pattern_snap *s, int base_channel) { int i,j; song_note_t *n; int mute[64]; int did_any; for (i = 0; i < s->channels; i++) { mute[i] = (song_get_channel(i+base_channel)->flags & CHN_MUTE); } n = s->data; did_any = 0; for (j = 0; j < s->rows; j++) { for (i = 0; i < s->channels; i++) { if (mute[i]) { memset(n, 0, sizeof(song_note_t)); did_any = 1; } n++; } } return did_any; } static void pated_history_restore(int n) { if (n < 0 || n > 9) return; snap_paste(&undo_history[n], -1, -1, 0); } static void pated_save(const char *descr) { int total_rows; total_rows = song_get_pattern(current_pattern, NULL); pated_history_add(descr,0,0,64,total_rows); } static void pated_history_add(const char *descr, int x, int y, int width, int height) { pated_history_add2(0, descr, x, y, width, height); } static void pated_history_add_grouped(const char *descr, int x, int y, int width, int height) { pated_history_add2(1, descr, x, y, width, height); } static void pated_history_add2(int groupedf, const char *descr, int x, int y, int width, int height) { int j; j = undo_history_top; if (groupedf && undo_history[j].patternno == current_pattern && undo_history[j].x == x && undo_history[j].y == y && undo_history[j].channels == width && undo_history[j].rows == height && undo_history[j].snap_op && strcmp(undo_history[j].snap_op, descr) == 0) { /* do nothing; use the previous bit of history */ } else { 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 = str_dup(descr); undo_history[j].snap_op_allocated = 1; undo_history[j].patternno = current_pattern; 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(int honor_mute) { int flag; 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); flag = 0; if (honor_mute) { flag = snap_honor_mute(&clipboard, selection.first_channel-1); } /* transfer to system where appropriate */ clippy_yank(); if (flag) { status_text_flash("Selection honors current mute settings"); } } static void clipboard_paste_overwrite(int suppress, int grow) { song_note_t *pattern; int 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; if (clipboard.rows > num_rows && grow) { if (current_row+clipboard.rows > 200) { status_text_flash("Resized pattern %d, but clipped to 200 rows", current_pattern); song_pattern_resize(current_pattern, 200); } else { status_text_flash("Resized pattern %d to %d rows", current_pattern, current_row + clipboard.rows); song_pattern_resize(current_pattern, current_row+clipboard.rows); } } chan_width = clipboard.channels; if (chan_width + current_channel > 64) chan_width = 64 - current_channel + 1; if (!suppress) { pated_history_add_grouped("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_t *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); pated_save("Undo paste data (Alt-P)"); 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, 0); pattern_selection_system_copyout(); } static void clipboard_paste_mix_notes(int clip, int xlate) { int row, chan, num_rows, chan_width; song_note_t *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_grouped("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, blank_note, sizeof(song_note_t)) == 0) { p_note[chan] = c_note[chan]; set_note_note(p_note+chan, c_note[chan].note, xlate); if (clip) { p_note[chan].instrument = song_get_current_instrument(); if (edit_copy_mask & MASK_VOLUME) { p_note[chan].voleffect = mask_note.voleffect; p_note[chan].volparam = mask_note.volparam; } else { p_note[chan].voleffect = 0; p_note[chan].volparam = 0; } if (edit_copy_mask & MASK_EFFECT) { p_note[chan].effect = mask_note.effect; p_note[chan].param = mask_note.param; } } } } 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_t *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 precedence */ if (c_note[chan].note != 0) { set_note_note(p_note+chan, c_note[chan].note, xlate); } if (c_note[chan].instrument != 0) p_note[chan].instrument = c_note[chan].instrument; if (c_note[chan].voleffect != VOLFX_NONE) { p_note[chan].voleffect = c_note[chan].voleffect; p_note[chan].volparam = c_note[chan].volparam; } if (c_note[chan].effect != 0) { p_note[chan].effect = c_note[chan].effect; } if (c_note[chan].param != 0) p_note[chan].param = c_note[chan].param; } else { if (p_note[chan].note == 0) { set_note_note(p_note+chan, c_note[chan].note, xlate); } if (p_note[chan].instrument == 0) p_note[chan].instrument = c_note[chan].instrument; if (p_note[chan].voleffect == VOLFX_NONE) { p_note[chan].voleffect = c_note[chan].voleffect; p_note[chan].volparam = c_note[chan].volparam; } if (p_note[chan].effect == 0) { p_note[chan].effect = c_note[chan].effect; } if (p_note[chan].param == 0) p_note[chan].param = c_note[chan].param; } } 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; } if (top_display_row < 0) top_display_row = 0; } static void advance_cursor(int next_row, int multichannel) { int total_rows; if (next_row && !((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) && playback_tracing)) { total_rows = song_get_rows_in_pattern(current_pattern); if (skip_value) { if (current_row + skip_value <= total_rows) { current_row += skip_value; pattern_editor_reposition(); } } else { if (current_channel < 64) { current_channel++; } else { current_channel = 1; if (current_row < total_rows) current_row++; } pattern_editor_reposition(); } } if (multichannel) { current_channel = multichannel_get_next(current_channel); } } /* --------------------------------------------------------------------- */ void update_current_row(void) { char 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_channel(void) { return current_channel; } void set_current_channel(int channel) { current_channel = CLAMP(channel, 0, 64); } 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) { char buf[4]; draw_text(numtostr(3, current_pattern, buf), 12, 6, 5, 0); draw_text(numtostr(3, csf_get_num_patterns(current_song) - 1, buf), 16, 6, 5, 0); } int get_current_pattern(void) { return current_pattern; } static void _pattern_update_magic(void) { song_sample_t *s; int i; for (i = 1; i <= 99; i++) { s = song_get_sample(i); if (!s) continue; if (((unsigned char)s->name[23]) != 0xFF) continue; if (((unsigned char)s->name[24]) != current_pattern) continue; disko_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_save(undostr); fast_save_update(); pattern_editor_reposition(); pattern_selection_system_copyout(); 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_orderpan(void) { if (marked_pattern == -1) { song_start_at_order(get_current_order(), current_row); } else { song_start_at_pattern(marked_pattern, marked_row); } } void play_song_from_mark(void) { int new_order; if (marked_pattern != -1) { song_start_at_pattern(marked_pattern, marked_row); return; } new_order = get_current_order(); while (new_order < 255) { if (current_song->orderlist[new_order] == current_pattern) { set_current_order(new_order); song_start_at_order(new_order, current_row); return; } new_order++; } new_order = 0; while (new_order < 255) { if (current_song->orderlist[new_order] == current_pattern) { set_current_order(new_order); song_start_at_order(new_order, current_row); return; } new_order++; } song_start_at_pattern(current_pattern, current_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; char buf[4]; song_note_t *pattern, *note; const struct track_view *track_view; int total_rows; int fg, bg; int mc = (status.flags & INVERTED_PALETTE) ? 1 : 3; /* mask color */ int pattern_is_playing = ((song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) != 0 && current_pattern == playing_pattern); if (template_mode) { draw_text_len(template_mode_names[template_mode], 60, 2, 12, 3, 2); } /* 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]; /* maybe i'm retarded but the pattern editor should be dealing with the same concept of "channel" as the rest of the interface. the mixing channels really could be any arbitrary number -- modplug just happens to reserve the first 64 for "real" channels. i'd rather pm not replicate this cruft and more or less hide the mixer from the interface... */ 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 < total_rows; 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 = ((status.flags & (CRAYOLA_MODE | CLASSIC_MODE)) == CRAYOLA_MODE) ? ((note->instrument + 3) % 4 + 3) : 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; } /* draw the cursor if on the current row, and: drawing the current channel, regardless of position OR: when the template is enabled, and the channel fits within the template size, AND shift is not being held down. (oh god it's lisp) */ int cpos; if ((row == current_row) && ((current_position > 0 || template_mode == TEMPLATE_OFF || (status.flags & SHIFT_KEY_DOWN)) ? (chan == current_channel) : (chan >= current_channel && chan < (current_channel + (clipboard.data ? clipboard.channels : 1))))) { // yes! do write the cursor cpos = current_position; if (cpos == 6 && link_effect_column && !(status.flags & CLASSIC_MODE)) cpos = 9; // highlight full effect and value } else { cpos = -1; } track_view->draw_note(chan_drawpos, 15 + row_pos, note, cpos, 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; } // hmm...? for (; row_pos < 32; row++, row_pos++) { 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, blank_note, -1, 6, bg); if (draw_divisions && chan_pos < visible_channels - 1) { draw_char(168, chan_drawpos + track_view->width, 15 + row_pos, 2, bg); } } if (chan == current_channel) { track_view->draw_mask(chan_drawpos, 47, edit_copy_mask, current_position, mc, 2); } /* blah */ if (channel_multi[chan - 1]) { if (track_view_scheme[chan_pos] == 0) { draw_char(172, chan_drawpos + 3, 47, mc, 2); } else if (track_view_scheme[chan_pos] < 3) { draw_char(172, chan_drawpos + 2, 47, mc, 2); } else if (track_view_scheme[chan_pos] == 3) { draw_char(172, chan_drawpos + 1, 47, mc, 2); } else if (current_position < 2) { draw_char(172, chan_drawpos, 47, mc, 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_t *pattern, *note; status.flags |= SONG_NEEDS_SAVE; song_get_pattern(current_pattern, &pattern); pated_history_add_grouped(((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) { 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++) { if (note->note > 0 && note->note < 121) note->note = CLAMP(note->note + amount, 1, 120); note++; } } } 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_copyout(); } /* --------------------------------------------------------------------- */ static void copy_note_to_mask(void) { int row = current_row, num_rows; song_note_t *pattern, *note; num_rows = song_get_pattern(current_pattern, &pattern); note = pattern + 64 * current_row + current_channel - 1; mask_note = *note; if (mask_copy_search_mode != COPY_INST_OFF) { while (!note->instrument && row > 0) { note -= 64; row--; } if (mask_copy_search_mode == COPY_INST_UP_THEN_DOWN && !note->instrument) { note = pattern + 64 * current_row + current_channel - 1; // Reset while (!note->instrument && row < num_rows) { note += 64; row++; } } } if (note->instrument) { if (song_is_instrument_mode()) instrument_set(note->instrument); else sample_set(note->instrument); } } /* --------------------------------------------------------------------- */ /* 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_t * note, struct key_event *k, int pos) { int vol = note->volparam; int fx = note->voleffect; int vp = panning_mode ? VOLFX_PANNING : VOLFX_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 = VOLFX_FINEVOLUP; vol %= 10; } else if (k->sym == SDLK_b) { fx = VOLFX_FINEVOLDOWN; vol %= 10; } else if (k->sym == SDLK_c) { fx = VOLFX_VOLSLIDEUP; vol %= 10; } else if (k->sym == SDLK_d) { fx = VOLFX_VOLSLIDEDOWN; vol %= 10; } else if (k->sym == SDLK_e) { fx = VOLFX_PORTADOWN; vol %= 10; } else if (k->sym == SDLK_f) { fx = VOLFX_PORTAUP; vol %= 10; } else if (k->sym == SDLK_g) { fx = VOLFX_TONEPORTAMENTO; vol %= 10; } else if (k->sym == SDLK_h) { fx = VOLFX_VIBRATODEPTH; vol %= 10; } else if (status.flags & CLASSIC_MODE) { return 0; } else if (k->sym == SDLK_DOLLAR) { fx = VOLFX_VIBRATOSPEED; vol %= 10; } else if (k->sym == SDLK_LESS) { fx = VOLFX_PANSLIDELEFT; vol %= 10; } else if (k->sym == SDLK_GREATER) { fx = VOLFX_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 VOLFX_NONE: case VOLFX_VOLUME: case VOLFX_PANNING: fx = vp; } } else { return 0; } } note->voleffect = fx; if (fx == VOLFX_VOLUME || fx == VOLFX_PANNING) note->volparam = CLAMP(vol, 0, 64); else note->volparam = CLAMP(vol, 0, 9); return 1; } // return zero iff there is no value in the current cell at the current column static int seek_done(void) { song_note_t *pattern, *note; song_get_pattern(current_pattern, &pattern); note = pattern + 64 * current_row + current_channel - 1; switch (current_position) { case 0: case 1: return note->note != 0; case 2: case 3: return note->instrument != 0; case 4: case 5: return note->voleffect || note->volparam; case 6: case 7: case 8: // effect param columns intentionally check effect column instead return note->effect != 0; } return 1; // please stop seeking because something is probably wrong } #if 0 static int note_is_empty(song_note_t *p) { if (!p->note && p->voleffect == VOLFX_NONE && !p->effect && !p->param) return 1; return 0; } #endif // FIXME: why the 'row' param? should it be removed, or should the references to current_row be replaced? // fwiw, every call to this uses current_row. // return: zero if there was a template error, nonzero otherwise static int patedit_record_note(song_note_t *cur_note, int channel, UNUSED int row, int note, int force) { song_note_t *q; int i, r = 1, channels; status.flags |= SONG_NEEDS_SAVE; if (NOTE_IS_NOTE(note)) { if (template_mode) { q = clipboard.data; if (clipboard.channels < 1 || clipboard.rows < 1 || !clipboard.data) { dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); r = 0; } 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); r = 0; } else { i = note - q->note; switch (template_mode) { case TEMPLATE_OVERWRITE: snap_paste(&clipboard, current_channel-1, current_row, i); break; case TEMPLATE_MIX_PATTERN_PRECEDENCE: clipboard_paste_mix_fields(0, i); break; case TEMPLATE_MIX_CLIPBOARD_PRECEDENCE: clipboard_paste_mix_fields(1, i); break; case TEMPLATE_NOTES_ONLY: clipboard_paste_mix_notes(1, i); break; }; } } else { cur_note->note = note; } } else { /* Note cut, etc. -- need to clear all masked fields. This will never cause a template error. Also, for one-row templates, replicate control notes across the width of the template. */ channels = (template_mode && clipboard.data != NULL && clipboard.rows == 1) ? clipboard.channels : 1; for (i = 0; i < channels && i + channel <= 64; i++) { /* I don't know what this whole 'force' thing is about, but okay */ if (!force && cur_note->note) continue; cur_note->note = note; if (edit_copy_mask & MASK_INSTRUMENT) { cur_note->instrument = 0; } if (edit_copy_mask & MASK_VOLUME) { cur_note->voleffect = 0; cur_note->volparam = 0; } if (edit_copy_mask & MASK_EFFECT) { cur_note->effect = 0; cur_note->param = 0; } cur_note++; } } pattern_selection_system_copyout(); return r; } static int pattern_editor_insert_midi(struct key_event *k) { song_note_t *pattern, *cur_note = NULL; int n, v = 0, c = 0, pd, speed, tick; 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; }; } speed = song_get_current_speed(); tick = song_get_current_tick(); if (k->midi_note == -1) { /* nada */ } else if (k->state == KEY_RELEASE) { c = song_keyup(k->midi_channel, k->midi_channel, k->midi_note); /* don't record noteoffs for no good reason... */ if (!((midi_flags & MIDI_RECORD_NOTEOFF) && (song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) && playback_tracing)) { return 0; } cur_note = pattern + 64 * current_row + (c-1); /* never "overwrite" a note off */ patedit_record_note(cur_note, c, 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)) { tick = 0; } n = k->midi_note; // XXX samp/ins were -1 here, I don't know what that meant (this is probably incorrect) c = song_keydown(k->midi_channel, k->midi_channel, n, v, current_channel); cur_note = pattern + 64 * current_row + (c-1); patedit_record_note(cur_note, c, current_row, n, 0); if (!template_mode) { cur_note->instrument = song_get_current_instrument(); if (midi_flags & MIDI_RECORD_VELOCITY) { cur_note->voleffect = VOLFX_VOLUME; cur_note->volparam = v; } tick %= speed; if (!(midi_flags & MIDI_TICK_QUANTIZE) && !cur_note->effect && tick != 0) { cur_note->effect = FX_SPECIAL; cur_note->param = 0xD0 | MIN(tick, 15); } } } if (!(midi_flags & MIDI_PITCHBEND) || midi_pitch_depth == 0 || k->midi_bend == 0) { if (k->state == KEY_RELEASE && k->midi_note > -1 && cur_note->instrument > 0) { song_keyrecord(cur_note->instrument, cur_note->instrument, cur_note->note, v, c+1, cur_note->effect, cur_note->param); pattern_selection_system_copyout(); } 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 != FX_PORTAMENTOUP && cur_note->effect != FX_PORTAMENTODOWN) { /* 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) * speed) / 2; if (pd < -0x7F) pd = -0x7F; else if (pd > 0x7F) pd = 0x7F; if (pd < 0) { cur_note->effect = FX_PORTAMENTODOWN; /* Exx */ cur_note->param = -pd; } else if (pd > 0) { cur_note->effect = FX_PORTAMENTOUP; /* Fxx */ cur_note->param = pd; } if (k->midi_note == -1 || k->state == KEY_RELEASE) continue; if (cur_note->instrument < 1) continue; if (cur_note->voleffect == VOLFX_VOLUME) v = cur_note->volparam; else v = -1; song_keyrecord(cur_note->instrument, cur_note->instrument, cur_note->note, v, c+1, cur_note->effect, cur_note->param); } } pattern_selection_system_copyout(); return -1; } /* return 1 => handled key, 0 => no way */ static int pattern_editor_insert(struct key_event *k) { int ins, smp, j, n, vol; song_note_t *pattern, *cur_note; song_get_pattern(current_pattern, &pattern); /* keydown events are handled here for multichannel */ if (k->state == KEY_RELEASE && current_position) return 0; cur_note = pattern + 64 * current_row + current_channel - 1; switch (current_position) { case 0: /* note */ // FIXME: this is actually quite wrong; instrument numbers should be independent for each // channel and take effect when the instrument is played (e.g. with 4/8 or keyjazz input) // also, this is fully idiotic smp = ins = cur_note->instrument; if (song_is_instrument_mode()) { smp = KEYJAZZ_NOINST; } else { ins = KEYJAZZ_NOINST; } if (k->sym == SDLK_4) { if (k->state == KEY_RELEASE) return 0; if (cur_note->voleffect == VOLFX_VOLUME) { vol = cur_note->volparam; } else { vol = KEYJAZZ_DEFAULTVOL; } song_keyrecord(smp, ins, cur_note->note, vol, current_channel, cur_note->effect, cur_note->param); advance_cursor(!(k->mod & KMOD_SHIFT), 1); return 1; } else if (k->sym == SDLK_8) { /* note: Impulse Tracker doesn't skip multichannels when pressing "8" -delt. */ if (k->state == KEY_RELEASE) return 0; song_single_step(current_pattern, current_row); advance_cursor(!(k->mod & KMOD_SHIFT), 0); return 1; } if (song_is_instrument_mode()) { if (edit_copy_mask & MASK_INSTRUMENT) ins = instrument_get_current(); } else { if (edit_copy_mask & MASK_INSTRUMENT) smp = sample_get_current(); } if (k->sym == SDLK_SPACE) { /* copy mask to note */ n = mask_note.note; vol = ((edit_copy_mask & MASK_VOLUME) && cur_note->voleffect == VOLFX_VOLUME) ? mask_note.volparam : KEYJAZZ_DEFAULTVOL; } else { n = kbd_get_note(k); if (n < 0) return 0; if ((edit_copy_mask & MASK_VOLUME) && mask_note.voleffect == VOLFX_VOLUME) { vol = mask_note.volparam; } else if (cur_note->voleffect == VOLFX_VOLUME) { vol = cur_note->volparam; } else { vol = KEYJAZZ_DEFAULTVOL; } } if (k->state == KEY_RELEASE) { if (keyjazz_noteoff && NOTE_IS_NOTE(n)) { /* coda mode */ song_keyup(smp, ins, n); } /* it would be weird to have this enabled and keyjazz_noteoff * disabled, but it's possible, so handle it separately. */ if (keyjazz_write_noteoff && playback_tracing && NOTE_IS_NOTE(n)) { /* go to the next row if a note off would overwrite a note * you (likely) just entered */ if (cur_note->note) { if (++current_row > song_get_rows_in_pattern(current_pattern)) { return 1; } cur_note += 64; /* give up if the next row has a note too */ if (cur_note->note) { return 1; } } n = NOTE_OFF; } else { return 1; } } if (k->is_repeat && !keyjazz_repeat) return 1; int writenote = !(status.flags & CAPS_PRESSED); if (writenote && !patedit_record_note(cur_note, current_channel, current_row, n, 1)) { // there was a template error, don't advance the cursor and so on writenote = 0; n = NOTE_NONE; } /* Be quiet when pasting templates. It'd be nice to "play" a template when pasting it (maybe only for ones that are one row high) so as to hear the chords being inserted etc., but that's a little complicated to do. */ if (NOTE_IS_NOTE(n) && !(template_mode && writenote)) song_keydown(smp, ins, n, vol, current_channel); if (!writenote) break; /* Never copy the instrument etc. from the mask when inserting control notes or when erasing a note -- but DO write it when inserting a blank note with the space key. */ if (!(NOTE_IS_CONTROL(n) || (k->sym != SDLK_SPACE && n == NOTE_NONE)) && !template_mode) { 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->voleffect = mask_note.voleffect; cur_note->volparam = mask_note.volparam; } if (edit_copy_mask & MASK_EFFECT) { cur_note->effect = mask_note.effect; cur_note->param = mask_note.param; } } /* try again, now that we have the effect (this is a dumb way to do this...) */ if (NOTE_IS_NOTE(n) && !template_mode) song_keyrecord(smp, ins, n, vol, current_channel, cur_note->effect, cur_note->param); /* copy the note back to the mask */ mask_note.note = n; pattern_selection_system_copyout(); n = cur_note->note; if (NOTE_IS_NOTE(n) && cur_note->voleffect == VOLFX_VOLUME) vol = cur_note->volparam; if (k->mod & KMOD_SHIFT) { // advance horizontally, stopping at channel 64 // (I have no idea how IT does this, it might wrap) if (current_channel < 64) { shift_chord_channels++; current_channel++; pattern_editor_reposition(); } } else { advance_cursor(1, 1); } 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(1, 0); status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); break; case 2: /* instrument, first digit */ case 3: /* instrument, second digit */ if (k->sym == SDLK_SPACE) { if (song_is_instrument_mode()) n = instrument_get_current(); else n = sample_get_current(); if (n && !(status.flags & CLASSIC_MODE)) current_song->voices[current_channel - 1].last_instrument = n; cur_note->instrument = n; advance_cursor(1, 0); 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(1, 0); status.flags |= SONG_NEEDS_SAVE; break; } if (current_position == 2) { j = kbd_char_to_99(k); if (j < 0) return 0; n = (j * 10) + (cur_note->instrument % 10); current_position++; } else { j = kbd_char_to_hex(k); if (j < 0 || j > 9) return 0; n = ((cur_note->instrument / 10) * 10) + j; current_position--; advance_cursor(1, 0); } /* this is kind of ugly... */ if (song_is_instrument_mode()) { j = instrument_get_current(); instrument_set(n); if (n != instrument_get_current()) { n = j; } instrument_set(j); } else { j = sample_get_current(); sample_set(n); if (n != sample_get_current()) { n = j; } sample_set(j); } if (n && !(status.flags & CLASSIC_MODE)) current_song->voices[current_channel - 1].last_instrument = n; cur_note->instrument = n; if (song_is_instrument_mode()) instrument_set(n); else sample_set(n); status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); break; case 4: case 5: /* volume */ if (k->sym == SDLK_SPACE) { cur_note->volparam = mask_note.volparam; cur_note->voleffect = mask_note.voleffect; advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; break; } if (kbd_get_note(k) == 0) { cur_note->volparam = mask_note.volparam = 0; cur_note->voleffect = mask_note.voleffect = VOLFX_NONE; advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; break; } if (key_scancode_lookup(k->scancode, 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.volparam = cur_note->volparam; mask_note.voleffect = cur_note->voleffect; if (current_position == 4) { current_position++; } else { current_position = 4; advance_cursor(1, 0); } status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); 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(1, 0); pattern_selection_system_copyout(); break; case 7: /* param, high nibble */ case 8: /* param, low nibble */ if (k->sym == SDLK_SPACE) { cur_note->param = mask_note.param; current_position = link_effect_column ? 6 : 7; advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); break; } else if (kbd_get_note(k) == 0) { cur_note->param = mask_note.param = 0; current_position = link_effect_column ? 6 : 7; advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); break; } /* FIXME: honey roasted peanuts */ n = kbd_char_to_hex(k); if (n < 0) return 0; if (current_position == 7) { cur_note->param = (n << 4) | (cur_note->param & 0xf); current_position++; } else /* current_position == 8 */ { cur_note->param = (cur_note->param & 0xf0) | n; current_position = link_effect_column ? 6 : 7; advance_cursor(1, 0); } status.flags |= SONG_NEEDS_SAVE; mask_note.param = cur_note->param; pattern_selection_system_copyout(); 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); /* 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; } n = numeric_key_event(k, 0); if (n > -1 && n <= 9) { if (k->state == KEY_RELEASE) return 1; skip_value = (n == 9) ? 16 : n; status_text_flash("Cursor step set to %d", skip_value); return 1; } switch (k->sym) { case SDLK_RETURN: if (k->state == KEY_PRESS) return 1; fast_save_update(); return 1; case SDLK_BACKSPACE: if (k->state == KEY_PRESS) return 1; snap_paste(&fast_save, 0, 0, 0); return 1; case SDLK_b: if (k->state == KEY_RELEASE) return 1; 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 (k->state == KEY_RELEASE) return 1; 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 (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SDLK_d) { if (total_rows - (current_row - 1) > block_double_size) block_double_size <<= 1; } else { // emulate some weird impulse tracker behavior here: // with row highlight set to zero, alt-d selects the whole channel // if the cursor is at the top, and clears the selection otherwise block_double_size = current_song->row_highlight_major ?: (current_row ? 0 : 65536); 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 (k->state == KEY_RELEASE) return 1; 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_copyout(); break; case SDLK_r: if (k->state == KEY_RELEASE) return 1; draw_divisions = 1; set_view_scheme(0); break; case SDLK_s: if (k->state == KEY_RELEASE) return 1; selection_set_sample(); break; case SDLK_u: if (k->state == KEY_RELEASE) return 1; if (SELECTION_EXISTS) { selection_clear(); } else if (clipboard.data) { clipboard_free(); clippy_select(NULL, NULL, 0); clippy_yank(); } else { dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); } break; case SDLK_c: if (k->state == KEY_RELEASE) return 1; clipboard_copy(0); break; case SDLK_o: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SDLK_o) { clipboard_paste_overwrite(0, 1); } else { clipboard_paste_overwrite(0, 0); } break; case SDLK_p: if (k->state == KEY_RELEASE) return 1; clipboard_paste_insert(); break; case SDLK_m: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SDLK_m) { clipboard_paste_mix_fields(0, 0); } else { clipboard_paste_mix_notes(0, 0); } break; case SDLK_f: if (k->state == KEY_RELEASE) return 1; block_length_double(); break; case SDLK_g: if (k->state == KEY_RELEASE) return 1; block_length_halve(); break; case SDLK_n: if (k->state == KEY_RELEASE) return 1; channel_multi[current_channel - 1] ^= 1; if (channel_multi[current_channel - 1]) { channel_multi_enabled = 1; } else { channel_multi_enabled = 0; for (n = 0; n < 64; n++) { if (channel_multi[n]) { channel_multi_enabled = 1; break; } } } if (status.last_keysym == SDLK_n) { pattern_editor_display_multichannel(); } break; case SDLK_z: if (k->state == KEY_RELEASE) return 1; clipboard_copy(0); selection_erase(); break; case SDLK_y: if (k->state == KEY_RELEASE) return 1; selection_swap(); break; case SDLK_v: if (k->state == KEY_RELEASE) return 1; selection_set_volume(); break; case SDLK_w: if (k->state == KEY_RELEASE) return 1; selection_wipe_volume(0); break; case SDLK_k: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SDLK_k) { selection_wipe_volume(1); } else { selection_slide_volume(); } break; case SDLK_x: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SDLK_x) { selection_wipe_effect(); } else { selection_slide_effect(); } break; case SDLK_h: if (k->state == KEY_RELEASE) return 1; draw_divisions = !draw_divisions; recalculate_visible_area(); pattern_editor_reposition(); break; case SDLK_q: if (k->state == KEY_RELEASE) return 1; if (k->mod & KMOD_SHIFT) transpose_notes(12); else transpose_notes(1); break; case SDLK_a: if (k->state == KEY_RELEASE) return 1; if (k->mod & KMOD_SHIFT) transpose_notes(-12); else transpose_notes(-1); break; case SDLK_i: if (k->state == KEY_RELEASE) return 1; if (k->mod & KMOD_SHIFT) template_mode = TEMPLATE_OFF; else if (fast_volume_mode) fast_volume_amplify(); else template_mode = (template_mode + 1) % TEMPLATE_MODE_MAX; /* cycle */ break; case SDLK_j: if (k->state == KEY_RELEASE) return 1; if (fast_volume_mode) fast_volume_attenuate(); else volume_amplify(); break; case SDLK_t: if (k->state == KEY_RELEASE) return 1; 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 (k->state == KEY_RELEASE) return 1; 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 (k->state == KEY_RELEASE) return 1; 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: if (k->state == KEY_RELEASE) return 1; current_channel--; return -1; case SDLK_RIGHT: if (k->state == KEY_RELEASE) return 1; current_channel++; return -1; case SDLK_INSERT: if (k->state == KEY_RELEASE) return 1; pated_save("Remove inserted row(s) (Alt-Insert)"); pattern_insert_rows(current_row, 1, 1, 64); break; case SDLK_DELETE: if (k->state == KEY_RELEASE) return 1; pated_save("Replace deleted row(s) (Alt-Delete)"); pattern_delete_rows(current_row, 1, 1, 64); break; case SDLK_F9: if (k->state == KEY_RELEASE) return 1; song_toggle_channel_mute(current_channel - 1); break; case SDLK_F10: if (k->state == KEY_RELEASE) return 1; song_handle_channel_solo(current_channel - 1); 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); n = numeric_key_event(k, 0); if (n > -1) { if (n < 0 || n >= NUM_TRACK_VIEWS) return 0; if (k->state == KEY_RELEASE) return 1; 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_LEFT: if (k->state == KEY_RELEASE) return 1; if (current_channel > top_display_channel) current_channel--; return -1; case SDLK_RIGHT: if (k->state == KEY_RELEASE) return 1; if (current_channel < top_display_channel + visible_channels - 1) current_channel++; return -1; case SDLK_F6: if (k->state == KEY_RELEASE) return 1; song_loop_pattern(current_pattern, current_row); return 1; case SDLK_F7: if (k->state == KEY_RELEASE) return 1; set_playback_mark(); return -1; case SDLK_UP: if (k->state == KEY_RELEASE) return 1; set_previous_instrument(); status.flags |= NEED_UPDATE; return 1; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 1; set_next_instrument(); status.flags |= NEED_UPDATE; return 1; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 1; current_row = 0; return -1; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 1; current_row = total_rows; return -1; case SDLK_HOME: if (k->state == KEY_RELEASE) return 1; current_row--; return -1; case SDLK_END: if (k->state == KEY_RELEASE) return 1; current_row++; return -1; case SDLK_INSERT: if (k->state == KEY_RELEASE) return 1; selection_roll(ROLL_DOWN); status.flags |= NEED_UPDATE; return 1; case SDLK_DELETE: if (k->state == KEY_RELEASE) return 1; selection_roll(ROLL_UP); status.flags |= NEED_UPDATE; return 1; case SDLK_MINUS: if (k->state == KEY_RELEASE) return 1; if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) && playback_tracing) return 1; prev_order_pattern(); return 1; case SDLK_PLUS: if (k->state == KEY_RELEASE) return 1; if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) && playback_tracing) return 1; next_order_pattern(); return 1; case SDLK_c: if (k->state == KEY_RELEASE) return 1; centralise_cursor = !centralise_cursor; status_text_flash("Centralise cursor %s", (centralise_cursor ? "enabled" : "disabled")); return -1; case SDLK_h: if (k->state == KEY_RELEASE) return 1; 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: if (k->state == KEY_RELEASE) return 1; fast_volume_toggle(); return 1; case SDLK_u: if (k->state == KEY_RELEASE) return 1; if (fast_volume_mode) selection_vary(1, 100-fast_volume_percent, FX_CHANNELVOLUME); else vary_command(FX_CHANNELVOLUME); status.flags |= NEED_UPDATE; return 1; case SDLK_y: if (k->state == KEY_RELEASE) return 1; if (fast_volume_mode) selection_vary(1, 100-fast_volume_percent, FX_PANBRELLO); else vary_command(FX_PANBRELLO); status.flags |= NEED_UPDATE; return 1; case SDLK_k: if (k->state == KEY_RELEASE) return 1; if (fast_volume_mode) selection_vary(1, 100-fast_volume_percent, current_effect()); else vary_command(current_effect()); status.flags |= NEED_UPDATE; return 1; case SDLK_b: if (k->mod & KMOD_SHIFT) return 0; /* fall through */ case SDLK_o: if (k->state == KEY_RELEASE) return 1; song_pattern_to_sample(current_pattern, !!(k->mod & KMOD_SHIFT), !!(k->sym == SDLK_b)); return 1; case SDLK_v: if (k->state == KEY_RELEASE) return 1; show_default_volumes = !show_default_volumes; status_text_flash("Default volumes %s", (show_default_volumes ? "enabled" : "disabled")); return 1; case SDLK_x: case SDLK_z: if (k->state == KEY_RELEASE) return 1; 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: if (k->state == KEY_RELEASE) return 1; pattern_editor_display_history(); return 1; default: return 0; } return 0; } static int mute_toggle_hack[64]; /* mrsbrisby: please explain this one, i don't get why it's necessary... */ static int pattern_editor_handle_key_default(struct key_event * k) { /* bleah */ if (k->sym == SDLK_LESS || k->sym == SDLK_COLON || k->sym == SDLK_SEMICOLON) { if (k->state == KEY_RELEASE) return 0; if ((status.flags & CLASSIC_MODE) || current_position != 4) { set_previous_instrument(); status.flags |= NEED_UPDATE; return 1; } } else if (k->sym == SDLK_GREATER || k->sym == SDLK_QUOTE || k->sym == SDLK_QUOTEDBL) { if (k->state == KEY_RELEASE) return 0; if ((status.flags & CLASSIC_MODE) || current_position != 4) { set_next_instrument(); status.flags |= NEED_UPDATE; return 1; } } else if (k->sym == SDLK_COMMA) { if (k->state == KEY_RELEASE) return 0; switch (current_position) { case 2: case 3: edit_copy_mask ^= MASK_INSTRUMENT; break; case 4: case 5: edit_copy_mask ^= MASK_VOLUME; break; case 6: case 7: case 8: edit_copy_mask ^= MASK_EFFECT; break; } 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; } 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; unsigned int basex; if (k->mouse != MOUSE_NONE) { if (k->state == KEY_RELEASE) { /* mouseup */ memset(mute_toggle_hack, 0, sizeof(mute_toggle_hack)); } if ((k->mouse == MOUSE_CLICK || k->mouse == MOUSE_DBLCLICK) && k->state == KEY_RELEASE) { 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 == KEY_RELEASE) return 0; if (k->mouse == MOUSE_SCROLL_UP) { if (top_display_row > 0) { top_display_row = MAX(top_display_row - MOUSE_SCROLL_LINES, 0); 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 = MIN(top_display_row + MOUSE_SCROLL_LINES, total_rows); 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; nx <= 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 == KEY_PRESS) { if (!mute_toggle_hack[n-1]) { song_toggle_channel_mute(n-1); status.flags |= NEED_UPDATE; mute_toggle_hack[n-1]=1; } } 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 == KEY_PRESS && k->sy > 14) { 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 == KEY_RELEASE) return 0; if (skip_value) { if (current_row - skip_value >= 0) current_row -= skip_value; } else { current_row--; } return -1; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 0; if (skip_value) { if (current_row + skip_value <= total_rows) current_row += skip_value; } else { current_row++; } return -1; case SDLK_LEFT: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_SHIFT) { current_channel--; } else if (link_effect_column && current_position == 0 && current_channel > 1) { current_channel--; current_position = current_effect() ? 8 : 6; } else { current_position--; } return -1; case SDLK_RIGHT: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_SHIFT) { current_channel++; } else if (link_effect_column && current_position == 6 && current_channel < 64) { current_position = current_effect() ? 7 : 10; } else { current_position++; } return -1; case SDLK_TAB: if (k->state == KEY_RELEASE) return 0; 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(); return -1; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 0; { int rh = current_song->row_highlight_major ?: 16; if (current_row == total_rows) current_row -= (current_row % rh) ?: rh; else current_row -= rh; } return -1; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; current_row += current_song->row_highlight_major ?: 16; return -1; case SDLK_HOME: if (k->state == KEY_RELEASE) return 0; if (current_position == 0) { if (invert_home_end ? (current_row != 0) : (current_channel == 1)) { current_row = 0; } else { current_channel = 1; } } else { current_position = 0; } return -1; case SDLK_END: if (k->state == KEY_RELEASE) return 0; n = song_find_last_channel(); if (current_position == 8) { if (invert_home_end ? (current_row != total_rows) : (current_channel == n)) { current_row = total_rows; } else { current_channel = n; } } else { current_position = 8; } return -1; case SDLK_INSERT: if (k->state == KEY_RELEASE) return 0; if (template_mode && clipboard.rows == 1) { n = clipboard.channels; if (n + current_channel > 64) { n = 64 - current_channel; } pattern_insert_rows(current_row, 1, current_channel, n); } else { pattern_insert_rows(current_row, 1, current_channel, 1); } break; case SDLK_DELETE: if (k->state == KEY_RELEASE) return 0; if (template_mode && clipboard.rows == 1) { n = clipboard.channels; if (n + current_channel > 64) { n = 64 - current_channel; } pattern_delete_rows(current_row, 1, current_channel, n); } else { pattern_delete_rows(current_row, 1, current_channel, 1); } break; case SDLK_MINUS: if (k->state == KEY_RELEASE) return 0; if (playback_tracing) { switch (song_get_mode()) { case MODE_PATTERN_LOOP: return 1; case MODE_PLAYING: song_set_current_order(song_get_current_order() - 1); return 1; default: break; }; } 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 == KEY_RELEASE) return 0; if (playback_tracing) { switch (song_get_mode()) { case MODE_PATTERN_LOOP: return 1; case MODE_PLAYING: song_set_current_order(song_get_current_order() + 1); return 1; default: break; }; } if ((k->mod & KMOD_SHIFT) && k->orig_sym == SDLK_KP_PLUS) set_current_pattern(current_pattern + 4); else set_current_pattern(current_pattern + 1); return 1; case SDLK_BACKSPACE: if (k->state == KEY_RELEASE) return 0; current_channel = multichannel_get_previous (current_channel); if (skip_value) current_row -= skip_value; else current_row--; return -1; case SDLK_RETURN: if (k->state == KEY_RELEASE) return 0; copy_note_to_mask(); if (template_mode != TEMPLATE_NOTES_ONLY) template_mode = TEMPLATE_OFF; return 1; case SDLK_l: if (k->mod & KMOD_SHIFT) { if (status.flags & CLASSIC_MODE) return 0; if (k->state == KEY_RELEASE) return 1; clipboard_copy(1); break; } return pattern_editor_handle_key_default(k); case SDLK_a: if (k->mod & KMOD_SHIFT && !(status.flags & CLASSIC_MODE)) { if (k->state == KEY_RELEASE) { return 0; } if (current_row == 0) { return 1; } do { current_row--; } while (!seek_done() && current_row != 0); return -1; } return pattern_editor_handle_key_default(k); case SDLK_f: if (k->mod & KMOD_SHIFT && !(status.flags & CLASSIC_MODE)) { if (k->state == KEY_RELEASE) { return 0; } if (current_row == total_rows) { return 1; } do { current_row++; } while (!seek_done() && current_row != total_rows); return -1; } return pattern_editor_handle_key_default(k); case SDLK_LSHIFT: case SDLK_RSHIFT: if (k->state == KEY_PRESS) { if (shift_selection.in_progress) shift_selection_end(); } else if (shift_chord_channels) { current_channel -= shift_chord_channels; while (current_channel < 1) current_channel += 64; advance_cursor(1, 1); shift_chord_channels = 0; } return 1; default: return pattern_editor_handle_key_default(k); } 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) { int ret; int total_rows = song_get_rows_in_pattern(current_pattern); if (k->mod & KMOD_SHIFT) { switch (k->sym) { case SDLK_LEFT: case SDLK_RIGHT: case SDLK_UP: case SDLK_DOWN: case SDLK_HOME: case SDLK_END: case SDLK_PAGEUP: case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (!shift_selection.in_progress) shift_selection_begin(); default: break; }; } 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(); 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 == playing_pattern) { status.flags |= NEED_UPDATE; } } } static void pated_song_changed(void) { pated_history_clear(); // reset ctrl-f7 marked_pattern = -1; marked_row = 0; } /* --------------------------------------------------------------------- */ static int _fix_f7(struct key_event *k) { if (k->sym == SDLK_F7) { if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; play_song_from_mark(); return 1; } return 0; } 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 = "Empty"; undo_history[i].snap_op_allocated = 0; } page->title = "Pattern Editor (F2)"; page->playback_update = pattern_editor_playback_update; page->song_changed_cb = pated_song_changed; page->pre_handle_key = _fix_f7; page->total_widgets = 1; page->clipboard_paste = pattern_selection_system_paste; page->widgets = widgets_pattern; page->help_index = HELP_PATTERN_EDITOR; create_other(widgets_pattern + 0, 0, pattern_editor_handle_key_cb, pattern_editor_redraw); } schismtracker-20180209/schism/page_preferences.c000066400000000000000000000157001323741476300215670ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "osdefs.h" #include "sdlmain.h" #include "disko.h" #define VOLUME_SCALE 31 /* this page will be the first against the wall when the revolution comes */ /* --------------------------------------------------------------------- */ /* statics */ static struct widget widgets_preferences[48]; static const char *interpolation_modes[] = { "Non-Interpolated", "Linear", "Cubic Spline", "8-Tap FIR Filter", NULL }; static const int interp_group[] = { 2,3,4,5,-1, }; static int ramp_group[] = { /* not const because it is modified */ -1,-1,-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_text("Ramp volume at start of sample",2,29+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); #define CORNER_BOTTOM "http://schismtracker.org/" draw_text(CORNER_BOTTOM, 78 - strlen(CORNER_BOTTOM), 48, 1, 2); } /* --------------------------------------------------------------------- */ static void preferences_set_page(void) { int i, j; int lim = volume_get_max(); volume_read(&i, &j); widgets_preferences[0].d.thumbbar.value = i * VOLUME_SCALE / lim; widgets_preferences[1].d.thumbbar.value = j * VOLUME_SCALE / lim; 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; } 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]; } widgets_preferences[i+10].d.togglebutton.state = audio_settings.no_ramping?0:1; widgets_preferences[i+11].d.togglebutton.state = audio_settings.no_ramping?1:0; } /* --------------------------------------------------------------------- */ static void change_volume(void) { int lim = volume_get_max(); volume_write( widgets_preferences[0].d.thumbbar.value * lim / VOLUME_SCALE, widgets_preferences[1].d.thumbbar.value * lim / VOLUME_SCALE); } #define SAVED_AT_EXIT "Audio configuration will be saved at exit" 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 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; } } audio_settings.no_ramping = widgets_preferences[i+11].d.togglebutton.state; song_init_modplug(); status_text_flash(SAVED_AT_EXIT); } /* --------------------------------------------------------------------- */ static void save_config_now(UNUSED void *ign) { /* TODO */ cfg_midipage_save(); /* what is this doing here? */ cfg_atexit_save(); status_text_flash("Configuration saved"); } void preferences_load_page(struct page *page) { char buf[64]; char *ptr; int i, j; int interp_modes; for (interp_modes = 0; interpolation_modes[interp_modes]; interp_modes++) { /* nothing */ } page->title = "Preferences (Shift-F5)"; page->draw_const = preferences_draw_const; page->set_page = preferences_set_page; page->total_widgets = 13 + interp_modes; page->widgets = widgets_preferences; page->help_index = HELP_GLOBAL; create_thumbbar(widgets_preferences + 0, 22, 14, 5, 0, 1, 1, change_volume, 0, VOLUME_SCALE); create_thumbbar(widgets_preferences + 1, 22, 15, 5, 0, 2, 2, change_volume, 0, VOLUME_SCALE); for (i = 0; interpolation_modes[i]; i++) { sprintf(buf, "%d Bit, %s", audio_settings.bits, interpolation_modes[i]); ptr = str_dup(buf); create_togglebutton(widgets_preferences+i+2, 6, 20 + (i * 3), 26, i+1, i+3, i+2, interp_modes+11, i+3, change_mixer, ptr, 2, interp_group); } for (j = 0; j < 4; j++) { int 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; ramp_group[0] = i+10; ramp_group[1] = i+11; create_togglebutton(widgets_preferences+i+10, 33,29+i*3,9, i+9,i+12,i+10,i+11,i+11, change_mixer, "Enabled",2, ramp_group); create_togglebutton(widgets_preferences+i+11, 46,29+i*3,9, i+9,i+12,i+10,i+13,i+13, change_mixer, "Disabled",1, ramp_group); create_button(widgets_preferences+i+12, 2, 44, 27, i+10, i+12, i+12, i+13, i+13, (void *) save_config_now, "Save Output Configuration", 2); } schismtracker-20180209/schism/page_samples.c000066400000000000000000001453241323741476300207400ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sndfile.h" /* for calc_halftone */ #include "page.h" #include "song.h" #include "dmoz.h" #include "sample-edit.h" #include "video.h" #include "fmt.h" /* --------------------------------------------------------------------- */ /* static in my attic */ static struct vgamem_overlay sample_image = { 55,26,76,29, NULL, 0, 0, 0, }; static int dialog_f1_hack = 0; static struct widget widgets_samplelist[20]; static const int vibrato_waveforms[] = { 15, 16, 17, 18, -1 }; static int top_sample = 1; static int current_sample = 1; static int _altswap_lastvis = 99; // for alt-down sample-swapping static int sample_list_cursor_pos = 25; /* the "play" text */ static void sample_adlibconfig_dialog(UNUSED void *ign); /* shared by all the numentry widgets */ static int sample_numentry_cursor_pos = 0; /* for the loops */ static const char *const loop_states[] = { "Off", "On Forwards", "On Ping Pong", NULL }; /* playback */ static int last_note = NOTE_MIDC; static int num_save_formats = 0; /* --------------------------------------------------------------------- */ /* woo */ static int _is_magic_sample(int no) { song_sample_t *sample; int pn; sample = song_get_sample(no); if (sample && ((unsigned char) sample->name[23]) == 0xFF) { pn = (sample->name[24]); if (pn < 200) return 1; } return 0; } static void _fix_accept_text(void) { if (_is_magic_sample(current_sample)) { widgets_samplelist[0].accept_text = (sample_list_cursor_pos == 23 ? 0 : 1); } else { widgets_samplelist[0].accept_text = (sample_list_cursor_pos == 25 ? 0 : 1); } } static int _last_vis_sample(void) { int i, j, n; n = 99; j = 0; /* 65 is first visible sample on last page */ for (i = 65; i < MAX_SAMPLES; i++) { if (!csf_sample_is_empty(current_song->samples + i)) { j = i; } } while ((j + 34) > n) n += 34; if (n >= MAX_SAMPLES) n = MAX_SAMPLES - 1; return n; } /* --------------------------------------------------------------------- */ 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; } if (dialog_f1_hack && status.current_page == PAGE_SAMPLE_LIST && status.previous_page == PAGE_HELP) { sample_adlibconfig_dialog(NULL); } dialog_f1_hack = 0; } /* --------------------------------------------------------------------- */ 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, _last_vis_sample()); else new_sample = CLAMP(n, 0, _last_vis_sample()); if (current_sample == new_sample) return; current_sample = new_sample; sample_list_reposition(); /* update_current_instrument(); */ if (status.current_page == PAGE_SAMPLE_LIST) status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* draw the actual list */ static void sample_list_draw_list(void) { int pos, n, nl, pn; song_sample_t *sample; int has_data, is_selected; char buf[64]; int ss, cl = 0, cr = 0; int is_playing[MAX_SAMPLES]; ss = -1; song_get_playing_samples(is_playing); /* list */ for (pos = 0, n = top_sample; pos < 35; pos++, n++) { sample = song_get_sample(n); is_selected = (n == current_sample); has_data = (sample->data != NULL); if (sample->played) draw_char(is_playing[n] > 1 ? 183 : 173, 1, 13 + pos, is_playing[n] ? 3 : 1, 2); draw_text(num99tostr(n, buf), 2, 13 + pos, (sample->flags & CHN_MUTE) ? 1 : 0, 2); // wow, this is entirely horrible pn = ((unsigned char)sample->name[24]); if (((unsigned char)sample->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(sample->name, nl, 5, 13 + pos, 6, (is_selected ? 14 : 0)); if (ss == n) { draw_text_len(sample->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); 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(sample->name)) ? 0 : sample->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]; song_sample_t *sample; int has_data; sample = song_get_sample(current_sample); 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; widgets_samplelist[2].d.thumbbar.text_at_min = (sample->flags & CHN_MUTE) ? " Muted " : NULL; /* default pan (another modplug hack) */ widgets_samplelist[3].d.toggle.state = (sample->flags & CHN_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 = sample->c5speed; widgets_samplelist[9].d.menutoggle.state = (sample->flags & CHN_LOOP ? (sample->flags & CHN_PINGPONGLOOP ? 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 & CHN_SUSTAINLOOP ? (sample->flags & CHN_PINGPONGSUSTAIN ? 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_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; if (has_data) { sprintf(buf, "%d bit%s", (sample->flags & CHN_16BIT) ? 16 : 8, (sample->flags & CHN_STEREO) ? " Stereo" : ""); } else { strcpy(buf, "No sample"); } draw_text_len(buf, 13, 64, 22, 2, 0); draw_text_len(numtostr(0, sample->length, buf), 13, 64, 23, 2, 0); draw_sample_data(&sample_image, sample); } /* --------------------------------------------------------------------- */ static int sample_list_add_char(char c) { song_sample_t *smp; if (c < 32) return 0; smp = song_get_sample(current_sample); text_add_char(smp->name, c, &sample_list_cursor_pos, _is_magic_sample(current_sample) ? 22 : 25); _fix_accept_text(); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; return 1; } static void sample_list_delete_char(void) { song_sample_t *smp = song_get_sample(current_sample); text_delete_char(smp->name, &sample_list_cursor_pos, _is_magic_sample(current_sample) ? 23 : 25); _fix_accept_text(); status.flags |= SONG_NEEDS_SAVE; status.flags |= NEED_UPDATE; } static void sample_list_delete_next_char(void) { song_sample_t *smp = song_get_sample(current_sample); text_delete_next_char(smp->name, &sample_list_cursor_pos, _is_magic_sample(current_sample) ? 23 : 25); _fix_accept_text(); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } static void clear_sample_text(void) { song_sample_t *smp = song_get_sample(current_sample); memset(smp->filename, 0, 14); if (_is_magic_sample(current_sample)) { memset(smp->name, 0, 24); } else { memset(smp->name, 0, 26); } sample_list_cursor_pos = 0; _fix_accept_text(); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ static void do_swap_sample(int n) { if (n >= 1 && n <= _last_vis_sample()) { song_swap_samples(current_sample, n); } } static void do_exchange_sample(int n) { if (n >= 1 && n <= _last_vis_sample()) { song_exchange_samples(current_sample, n); } } static void do_copy_sample(int n) { if (n >= 1 && n <= _last_vis_sample()) { song_copy_sample(current_sample, song_get_sample(n)); sample_host_dialog(-1); } status.flags |= SONG_NEEDS_SAVE; } static void do_replace_sample(int n) { if (n >= 1 && n <= _last_vis_sample()) { song_replace_sample(current_sample, n); } status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ 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; if (k->mouse == MOUSE_CLICK && k->mouse_button == MOUSE_BUTTON_MIDDLE) { if (k->state == KEY_RELEASE) status.flags |= CLIPPY_PASTE_SELECTION; return 1; } else if (k->state == KEY_PRESS && k->mouse != MOUSE_NONE && k->x >= 5 && k->y >= 13 && k->y <= 47 && k->x <= 34) { if (k->mouse == MOUSE_SCROLL_UP) { top_sample -= MOUSE_SCROLL_LINES; if (top_sample < 1) top_sample = 1; status.flags |= NEED_UPDATE; return 1; } else if (k->mouse == MOUSE_SCROLL_DOWN) { top_sample += MOUSE_SCROLL_LINES; if (top_sample > (_last_vis_sample()-34)) top_sample = (_last_vis_sample()-34); status.flags |= NEED_UPDATE; return 1; } else { new_sample = (k->y - 13) + top_sample; new_cursor_pos = k->x - 5; if (k->x <= 29) { /* and button1 */ if (k->mouse == MOUSE_DBLCLICK) { /* this doesn't appear to work */ set_page(PAGE_LOAD_SAMPLE); status.flags |= NEED_UPDATE; return 1; } else { } #if 0 /* buggy and annoying, could be implemented properly but I don't care enough */ } else if (k->state == KEY_RELEASE || k->x == k->sx) { if (k->mouse == MOUSE_DBLCLICK || (new_sample == current_sample && sample_list_cursor_pos == 25)) { song_keydown(current_sample, KEYJAZZ_NOINST, last_note, 64, KEYJAZZ_CHAN_CURRENT); } new_cursor_pos = 25; #endif } } } else { switch (k->sym) { case SDLK_LEFT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_cursor_pos--; break; case SDLK_RIGHT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_cursor_pos++; break; case SDLK_HOME: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_cursor_pos = 0; break; case SDLK_END: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_cursor_pos = 25; break; case SDLK_UP: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_ALT) { if (current_sample > 1) { new_sample = current_sample - 1; song_swap_samples(current_sample, new_sample); } } else if (!NO_MODIFIER(k->mod)) { return 0; } else { new_sample--; } break; case SDLK_DOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_ALT) { // restrict position to the "old" value of _last_vis_sample() // (this is entirely for aesthetic reasons) if (status.last_keysym != SDLK_DOWN && !k->is_repeat) _altswap_lastvis = _last_vis_sample(); if (current_sample < _altswap_lastvis) { new_sample = current_sample + 1; song_swap_samples(current_sample, new_sample); } } else if (!NO_MODIFIER(k->mod)) { return 0; } else { new_sample++; } break; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) { new_sample = 1; } else { new_sample -= 16; } break; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & KMOD_CTRL) { new_sample = _last_vis_sample(); } else { new_sample += 16; } break; case SDLK_RETURN: if (k->state == KEY_PRESS) return 0; set_page(PAGE_LOAD_SAMPLE); break; case SDLK_BACKSPACE: if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) 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; case SDLK_ESCAPE: if (k->mod & KMOD_SHIFT) { if (k->state == KEY_RELEASE) return 1; new_cursor_pos = 25; break; } 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->unicode) return 0; if (k->state == KEY_RELEASE) return 1; return sample_list_add_char(k->unicode); } return 0; } } new_sample = CLAMP(new_sample, 1, _last_vis_sample()); new_cursor_pos = CLAMP(new_cursor_pos, 0, 25); if (new_sample != current_sample) { sample_set(new_sample); sample_list_reposition(); } if (new_cursor_pos != sample_list_cursor_pos) { sample_list_cursor_pos = new_cursor_pos; _fix_accept_text(); } 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. */ static void do_sign_convert(UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); sample_sign_convert(sample); } static void do_quality_convert(UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); sample_toggle_quality(sample, 1); } static void do_quality_toggle(UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); if (sample->flags & CHN_STEREO) status_text_flash("Can't toggle quality for stereo samples"); else sample_toggle_quality(sample, 0); } static void do_delete_sample(UNUSED void *data) { song_clear_sample(current_sample); status.flags |= SONG_NEEDS_SAVE; } static void do_downmix(UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); sample_downmix(sample); } static void do_post_loop_cut(UNUSED void *bweh) /* I'm already using 'data'. */ { song_sample_t *sample = song_get_sample(current_sample); unsigned long pos = ((sample->flags & CHN_SUSTAINLOOP) ? MAX(sample->loop_end, sample->sustain_end) : sample->loop_end); if (pos == 0 || pos >= sample->length) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); csf_stop_sample(current_song, sample); if (sample->loop_end > pos) sample->loop_end = pos; if (sample->sustain_end > pos) sample->sustain_end = pos; sample->length = pos; csf_adjust_sample_loop(sample); song_unlock_audio(); } static void do_pre_loop_cut(UNUSED void *bweh) { song_sample_t *sample = song_get_sample(current_sample); unsigned long pos = ((sample->flags & CHN_SUSTAINLOOP) ? MIN(sample->loop_start, sample->sustain_start) : sample->loop_start); unsigned long start_byte = pos * ((sample->flags & CHN_16BIT) ? 2 : 1) * ((sample->flags & CHN_STEREO) ? 2 : 1); unsigned long bytes = (sample->length - pos) * ((sample->flags & CHN_16BIT) ? 2 : 1) * ((sample->flags & CHN_STEREO) ? 2 : 1); if (pos == 0 || pos > sample->length) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); csf_stop_sample(current_song, sample); memmove(sample->data, sample->data + start_byte, bytes); 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; song_unlock_audio(); } static void do_centralise(UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); sample_centralise(sample); } /* --------------------------------------------------------------------- */ static struct widget sample_amplify_widgets[3]; static void do_amplify(UNUSED void *data) { sample_amplify(song_get_sample(current_sample), 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)); 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; } /* --------------------------------------------------------------------- */ static struct widget txtsynth_widgets[3]; static char txtsynth_entry[65536]; static void do_txtsynth(UNUSED void *data) { int len = strlen(txtsynth_entry); if (!len) return; song_sample_t *sample = song_get_sample(current_sample); if (sample->data) csf_free_sample(sample->data); sample->data = csf_allocate_sample(len); memcpy(sample->data, txtsynth_entry, len); sample->length = len; sample->loop_start = 0; sample->loop_end = len; sample->sustain_start = sample->sustain_end = 0; sample->flags |= CHN_LOOP; sample->flags &= ~(CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN | CHN_16BIT | CHN_STEREO | CHN_ADLIB); csf_adjust_sample_loop(sample); sample_host_dialog(-1); status.flags |= SONG_NEEDS_SAVE; } static void txtsynth_draw_const(void) { draw_text("Enter a text string (e.g. ABCDCB for a triangle-wave)", 13, 27, 0, 2); draw_box(12, 29, 66, 31, BOX_THIN | BOX_INNER | BOX_INSET); } static void txtsynth_dialog(void) { struct dialog *dialog; // TODO copy the current sample into the entry? txtsynth_entry[0] = 0; create_textentry(txtsynth_widgets + 0, 13, 30, 53, 0, 1, 1, NULL, txtsynth_entry, 65535); create_button(txtsynth_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); create_button(txtsynth_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(9, 25, 61, 11, txtsynth_widgets, 3, 0, txtsynth_draw_const, NULL); dialog->action_yes = do_txtsynth; } /* --------------------------------------------------------------------- */ static struct widget sample_adlibconfig_widgets[28]; static int adlib_xpos[] = {26, 30, 58, 62, 39}; static int adlib_cursorpos[] = {0, 0, 0, 0, 0}; static const char *yn_toggle[3] = {"n", "y", NULL}; /* N - number, B - boolean (toggle) */ typedef enum adlibconfig_wtypes {N, B} adlibconfig_wtypes; static const struct { int xref, y; adlibconfig_wtypes type; int byteno, firstbit, nbits; } adlibconfig_widgets[] = { {4, 3, B,10, 0, 1 }, // add. synth {4, 4, N,10, 1, 3 }, // mod. feedback {0, 7, N, 5, 4, 4 }, // carrier attack {0, 8, N, 5, 0, 4 }, // carrier decay {0, 9, N, 7, 4,-4 }, // carrier sustain (0=maximum, 15=minimum) {0, 10, N, 7, 0, 4 }, // carrier release {0, 11, B, 1, 5, 1 }, // carrier sustain flag {0, 12, N, 3, 0,-6 }, // carrier volume (0=maximum, 63=minimum) {1, 7, N, 4, 4, 4 }, // modulator attack {1, 8, N, 4, 0, 4 }, // modulator decay {1, 9, N, 6, 4,-4 }, // modulator sustain (0=maximum, 15=minimum) {1, 10, N, 6, 0, 4 }, // modulator release {1, 11, B, 0, 5, 1 }, // modulator sustain flag {1, 12, N, 2, 0,-6 }, // modulator volume (0=maximum, 63=minimum) {2, 7, B, 1, 4, 1 }, // carrier scale envelope flag {2, 8, N, 3, 6, 2 }, // carrier level scaling (This is actually reversed bits...) {2, 9, N, 1, 0, 4 }, // carrier frequency multiplier {2, 10, N, 9, 0, 2 }, // carrier wave select {2, 11, B, 1, 6, 1 }, // carrier pitch vibrato {2, 12, B, 1, 7, 1 }, // carrier volume vibrato {3, 7, B, 0, 4, 1 }, // modulator scale envelope flag {3, 8, N, 2, 6, 2 }, // modulator level scaling (This is actually reversed bits...) {3, 9, N, 0, 0, 4 }, // modulator frequency multiplier {3, 10, N, 8, 0, 2 }, // modulator wave select {3, 11, B, 0, 6, 1 }, // modulator pitch vibrato {3, 12, B, 0, 7, 1 }, // modulator volume vibrato }; static void do_adlibconfig(UNUSED void *data) { //page->help_index = HELP_SAMPLE_LIST; song_sample_t *sample = song_get_sample(current_sample); if (sample->data) csf_free_sample(sample->data); sample->data = csf_allocate_sample(1); sample->length = 1; if (!(sample->flags & CHN_ADLIB)) { sample->flags |= CHN_ADLIB; status_text_flash("Created adlib sample"); } sample->flags &= ~(CHN_16BIT | CHN_STEREO | CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); sample->loop_start = sample->loop_end = 0; sample->sustain_start = sample->sustain_end = 0; if (!sample->c5speed) { sample->c5speed = 8363; sample->volume = 64 * 4; sample->global_volume = 64; } sample_host_dialog(-1); status.flags |= SONG_NEEDS_SAVE; } static void adlibconfig_refresh(void) { int a; song_sample_t *sample = song_get_sample(current_sample); draw_sample_data(&sample_image, sample); for (a = 0; a < ARRAY_SIZE(adlibconfig_widgets); a++) { unsigned int srcvalue = 0; unsigned int maskvalue = 0xFFFF; unsigned int nbits_real = (adlibconfig_widgets[a].nbits < 0 ? -adlibconfig_widgets[a].nbits : adlibconfig_widgets[a].nbits); unsigned int maxvalue = (1 << nbits_real) - 1; switch (adlibconfig_widgets[a].type) { case B: srcvalue = sample_adlibconfig_widgets[a].d.toggle.state; break; case N: srcvalue = sample_adlibconfig_widgets[a].d.numentry.value; break; } if(adlibconfig_widgets[a].nbits < 0) srcvalue = maxvalue - srcvalue; // reverse the semantics srcvalue &= maxvalue; srcvalue <<= adlibconfig_widgets[a].firstbit; maskvalue &= maxvalue; maskvalue <<= adlibconfig_widgets[a].firstbit; sample->adlib_bytes[adlibconfig_widgets[a].byteno] = (sample->adlib_bytes[adlibconfig_widgets[a].byteno] &~ maskvalue) | srcvalue; } } static void sample_adlibconfig_draw_const(void) { struct { int x, y; const char *label; } labels[] = { {19, 1, "Adlib Melodic Instrument Parameters"}, {19, 3, "Additive Synthesis:"}, {18, 4, "Modulation Feedback:"}, {26, 6, "Car Mod"}, {19, 7, "Attack"}, {20, 8, "Decay"}, {18, 9, "Sustain"}, {18, 10, "Release"}, {12, 11, "Sustain Sound"}, {19, 12, "Volume"}, {58, 6, "Car Mod"}, {43, 7, "Scale Envelope"}, {44, 8, "Level Scaling"}, {37, 9, "Frequency Multiplier"}, {46, 10, "Wave Select"}, {44, 11, "Pitch Vibrato"}, {43, 12, "Volume Vibrato"}, }; int a; // 39 33 draw_box(38, 2 + 30, 40, 5 + 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_fill_chars(25, 6 + 30, 32,13 + 30, 0); draw_box(25, 6 + 30, 28, 13 + 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(29, 6 + 30, 32, 13 + 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_fill_chars(57, 6 + 30, 64,13 + 30, 0); draw_box(57, 6 + 30, 60, 13 + 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(61, 6 + 30, 64, 13 + 30, BOX_THIN | BOX_INNER | BOX_INSET); for (a = 0; a < ARRAY_SIZE(labels); a++) draw_text(labels[a].label, labels[a].x, labels[a].y + 30, a ? 0 : 3, 2); } static int do_adlib_handlekey(struct key_event *kk) { if (kk->sym == SDLK_F1) { if (kk->state == KEY_PRESS) return 1; status.current_help_index = HELP_ADLIB_SAMPLE; dialog_f1_hack = 1; dialog_destroy_all(); set_page(PAGE_HELP); return 1; } return 0; } static void sample_adlibconfig_dialog(UNUSED void *ign) { struct dialog *dialog; song_sample_t *sample = song_get_sample(current_sample); int a; //page->help_index = HELP_ADLIB_SAMPLES; // Eh, what page? Where am I supposed to get a reference to page? // How do I make this work? -Bisqwit for (a = 0; a < ARRAY_SIZE(adlibconfig_widgets); a++) { unsigned int srcvalue = sample->adlib_bytes[adlibconfig_widgets[a].byteno]; unsigned int nbits_real = adlibconfig_widgets[a].nbits < 0 ? -adlibconfig_widgets[a].nbits : adlibconfig_widgets[a].nbits; unsigned int minvalue = 0, maxvalue = (1 << nbits_real) - 1; srcvalue >>= adlibconfig_widgets[a].firstbit; srcvalue &= maxvalue; if (adlibconfig_widgets[a].nbits < 0) srcvalue = maxvalue - srcvalue; // reverse the semantics switch (adlibconfig_widgets[a].type) { case B: create_menutoggle(sample_adlibconfig_widgets + a, adlib_xpos[adlibconfig_widgets[a].xref], adlibconfig_widgets[a].y + 30, a > 0 ? a - 1 : 0, a + 1 < ARRAY_SIZE(adlibconfig_widgets) ? a + 1 : a, a, a, (a > 1 ? ((a + 4) % (ARRAY_SIZE(adlibconfig_widgets) - 2)) + 2 : 2), adlibconfig_refresh, yn_toggle); sample_adlibconfig_widgets[a].d.menutoggle.state = srcvalue; sample_adlibconfig_widgets[a].d.menutoggle.activation_keys = "ny"; break; case N: create_numentry(sample_adlibconfig_widgets + a, adlib_xpos[adlibconfig_widgets[a].xref], adlibconfig_widgets[a].y + 30, nbits_real < 4 ? 1 : 2, a > 0 ? a - 1 : 0, a + 1 < ARRAY_SIZE(adlibconfig_widgets) ? a + 1 : a, (a > 1 ? ((a + 4) % (ARRAY_SIZE(adlibconfig_widgets) - 2)) + 2 : 2), adlibconfig_refresh, minvalue, maxvalue, adlib_cursorpos + adlibconfig_widgets[a].xref); sample_adlibconfig_widgets[a].d.numentry.value = srcvalue; break; } } dialog = dialog_create_custom(9, 30, 61, 15, sample_adlibconfig_widgets, ARRAY_SIZE(adlibconfig_widgets), 0, sample_adlibconfig_draw_const, NULL); dialog->action_yes = do_adlibconfig; dialog->handle_key = do_adlib_handlekey; } static void sample_adlibpatch_finish(int n) { song_sample_t *sample; if (n <= 0 || n > 128) return; sample = song_get_sample(current_sample); adlib_patch_apply((song_sample_t *) sample, n - 1); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; // redraw the sample sample_host_dialog(-1); } static void sample_adlibpatch_dialog(UNUSED void *ign) { numprompt_create("Enter Patch (1-128)", sample_adlibpatch_finish, 0); } /* --------------------------------------------------------------------- */ /* filename can be NULL, in which case the sample filename is used (quick save) */ struct sample_save_data { char *path; /* char *options? */ const char *format; }; static void save_sample_free_data(void *ptr) { struct sample_save_data *data = (struct sample_save_data *) ptr; if (data->path) free(data->path); free(data); } static void do_save_sample(void *ptr) { struct sample_save_data *data = (struct sample_save_data *) ptr; // I guess this function doesn't need to care about the return value, // since song_save_sample is handling all the visual feedback... song_save_sample(data->path, data->format, song_get_sample(current_sample), current_sample); save_sample_free_data(ptr); } static void sample_save(const char *filename, const char *format) { song_sample_t *sample = song_get_sample(current_sample); char *ptr, *q; struct sample_save_data *data; struct stat buf; int tmp; if (stat(cfg_dir_samples, &buf) == -1) { status_text_flash("Sample directory \"%s\" unreachable", filename); return; } tmp=0; data = mem_alloc(sizeof(struct sample_save_data)); if (!S_ISDIR(buf.st_mode)) { /* directory browsing */ q = strrchr(cfg_dir_samples, DIR_SEPARATOR); if (q) { tmp = q[1]; q[1] = '\0'; } } else { q = NULL; } ptr = dmoz_path_concat(cfg_dir_samples, filename ? : sample->filename); if (q) q[1] = tmp; data->path = ptr; data->format = format; if (filename && *filename && stat(ptr, &buf) == 0) { if (S_ISREG(buf.st_mode)) { dialog_create(DIALOG_OK_CANCEL, "Overwrite file?", do_save_sample, save_sample_free_data, 1, data); /* callback will free it */ } else if (S_ISDIR(buf.st_mode)) { status_text_flash("%s is a directory", filename); save_sample_free_data(data); } else { status_text_flash("%s is not a regular file", filename); save_sample_free_data(data); } } else { do_save_sample(data); } } /* export sample dialog */ static struct widget export_sample_widgets[4]; static char export_sample_filename[NAME_MAX + 1] = ""; static int export_sample_format = 0; static void do_export_sample(UNUSED void *data) { sample_save(export_sample_filename, sample_save_formats[export_sample_format].label); } static void export_sample_list_draw(void) { int n, focused = (*selected_widget == 3); draw_fill_chars(53, 24, 56, 31, 0); for (n = 0; sample_save_formats[n].label; 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].label, 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 == KEY_RELEASE) 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 = num_save_formats - 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, num_save_formats - 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_box(52, 23, 57, 32, BOX_THICK | BOX_INNER | BOX_INSET); } static void export_sample_dialog(void) { song_sample_t *sample = song_get_sample(current_sample); struct dialog *dialog; create_textentry(export_sample_widgets + 0, 33, 24, 18, 0, 1, 3, NULL, export_sample_filename, NAME_MAX); create_button(export_sample_widgets + 1, 31, 35, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); create_button(export_sample_widgets + 2, 42, 35, 6, 3, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); create_other(export_sample_widgets + 3, 0, export_sample_list_handle_key, export_sample_list_draw); 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, 4, 0, export_sample_draw_const, NULL); dialog->action_yes = do_export_sample; } /* resize sample dialog */ static struct widget resize_sample_widgets[2]; static int resize_sample_cursor; static void do_resize_sample_aa(UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); 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_t *sample = song_get_sample(current_sample); 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_t *sample = song_get_sample(current_sample); 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, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(26, 22, 29, 11, resize_sample_widgets, 2, 0, resize_sample_draw_const, NULL); dialog->action_yes = aa ? do_resize_sample_aa : do_resize_sample; } /* --------------------------------------------------------------------- */ static void sample_set_mute(int n, int mute) { song_sample_t *smp = song_get_sample(n); if (mute) { if (smp->flags & CHN_MUTE) return; smp->globalvol_saved = smp->global_volume; smp->global_volume = 0; smp->flags |= CHN_MUTE; } else { if (!(smp->flags & CHN_MUTE)) return; smp->global_volume = smp->globalvol_saved; smp->flags &= ~CHN_MUTE; } } static void sample_toggle_mute(int n) { song_sample_t *smp = song_get_sample(n); sample_set_mute(n, !(smp->flags & CHN_MUTE)); } static void sample_toggle_solo(int n) { int i, solo = 0; if (song_get_sample(n)->flags & CHN_MUTE) { solo = 1; } else { for (i = 1; i < MAX_SAMPLES; i++) { if (i != n && !(song_get_sample(i)->flags & CHN_MUTE)) { solo = 1; break; } } } for (i = 1; i < MAX_SAMPLES; i++) sample_set_mute(i, solo && i != n); } /* --------------------------------------------------------------------- */ static void sample_list_handle_alt_key(struct key_event * k) { song_sample_t *sample = song_get_sample(current_sample); int canmod = (sample->data != NULL && !(sample->flags & CHN_ADLIB)); if (k->state == KEY_RELEASE) return; switch (k->sym) { case SDLK_a: if (canmod) dialog_create(DIALOG_OK_CANCEL, "Convert sample?", do_sign_convert, NULL, 0, NULL); return; case SDLK_b: if (canmod && (sample->loop_start > 0 || ((sample->flags & CHN_SUSTAINLOOP) && sample->sustain_start > 0))) { dialog_create(DIALOG_OK_CANCEL, "Cut sample?", do_pre_loop_cut, NULL, 1, NULL); } return; case SDLK_d: if ((k->mod & KMOD_SHIFT) && !(status.flags & CLASSIC_MODE)) { if (canmod && sample->flags & CHN_STEREO) { dialog_create(DIALOG_OK_CANCEL, "Downmix sample to mono?", do_downmix, NULL, 0, NULL); } } else { dialog_create(DIALOG_OK_CANCEL, "Delete sample?", do_delete_sample, NULL, 1, NULL); } return; case SDLK_e: if (canmod) resize_sample_dialog(1); break; case SDLK_f: if (canmod) resize_sample_dialog(0); break; case SDLK_g: if (canmod) sample_reverse(sample); break; case SDLK_h: if (canmod) dialog_create(DIALOG_YES_NO, "Centralise sample?", do_centralise, NULL, 0, NULL); return; case SDLK_i: if (canmod) sample_invert(sample); break; case SDLK_l: if (canmod && (sample->loop_end > 0 || ((sample->flags & CHN_SUSTAINLOOP) && sample->sustain_end > 0))) { dialog_create(DIALOG_OK_CANCEL, "Cut sample?", do_post_loop_cut, NULL, 1, NULL); } return; case SDLK_m: if (canmod) sample_amplify_dialog(); return; case SDLK_n: song_toggle_multichannel_mode(); return; case SDLK_o: sample_save(NULL, "ITS"); return; case SDLK_p: smpprompt_create("Copy sample:", "Sample", do_copy_sample); return; case SDLK_q: if (canmod) { dialog_create(DIALOG_YES_NO, "Convert sample?", do_quality_convert, do_quality_toggle, 0, NULL); } return; case SDLK_r: smpprompt_create("Replace sample with:", "Sample", do_replace_sample); return; case SDLK_s: smpprompt_create("Swap sample with:", "Sample", do_swap_sample); return; case SDLK_t: export_sample_dialog(); return; case SDLK_w: sample_save(NULL, "RAW"); return; case SDLK_x: smpprompt_create("Exchange sample with:", "Sample", do_exchange_sample); return; case SDLK_y: /* hi virt */ txtsynth_dialog(); return; case SDLK_z: { // uguu~ void (*dlg)(void *) = (k->mod & KMOD_SHIFT) ? sample_adlibpatch_dialog : sample_adlibconfig_dialog; if (canmod) { dialog_create(DIALOG_OK_CANCEL, "This will replace the current sample.", dlg, NULL, 1, NULL); } else { dlg(NULL); } } return; case SDLK_INSERT: song_insert_sample_slot(current_sample); break; case SDLK_DELETE: song_remove_sample_slot(current_sample); break; case SDLK_F9: sample_toggle_mute(current_sample); break; case SDLK_F10: sample_toggle_solo(current_sample); break; default: return; } status.flags |= NEED_UPDATE; } static void sample_list_handle_key(struct key_event * k) { int new_sample = current_sample; song_sample_t *sample = song_get_sample(current_sample); switch (k->sym) { case SDLK_SPACE: if (k->state == KEY_RELEASE) return; if (selected_widget && *selected_widget == 0) { status.flags |= NEED_UPDATE; } return; case SDLK_PLUS: if (k->state == KEY_RELEASE) return; if (k->mod & KMOD_ALT) { sample->c5speed *= 2; status.flags |= SONG_NEEDS_SAVE; } else if (k->mod & KMOD_CTRL) { sample->c5speed = calc_halftone(sample->c5speed, 1); status.flags |= SONG_NEEDS_SAVE; } status.flags |= NEED_UPDATE; return; case SDLK_MINUS: if (k->state == KEY_RELEASE) return; if (k->mod & KMOD_ALT) { sample->c5speed /= 2; status.flags |= SONG_NEEDS_SAVE; } else if (k->mod & KMOD_CTRL) { sample->c5speed = calc_halftone(sample->c5speed, -1); status.flags |= SONG_NEEDS_SAVE; } status.flags |= NEED_UPDATE; return; case SDLK_COMMA: case SDLK_LESS: if (k->state == KEY_RELEASE) return; song_change_current_play_channel(-1, 0); return; case SDLK_PERIOD: case SDLK_GREATER: if (k->state == KEY_RELEASE) return; song_change_current_play_channel(1, 0); return; case SDLK_PAGEUP: if (k->state == KEY_RELEASE) return; new_sample--; break; case SDLK_PAGEDOWN: if (k->state == KEY_RELEASE) return; new_sample++; break; case SDLK_ESCAPE: if (k->mod & KMOD_SHIFT) { if (k->state == KEY_RELEASE) return; sample_list_cursor_pos = 25; _fix_accept_text(); change_focus_to(0); status.flags |= NEED_UPDATE; return; } return; default: if (k->mod & KMOD_ALT) { if (k->state == KEY_RELEASE) return; sample_list_handle_alt_key(k); } else if (!k->is_repeat) { int n, v; if (k->midi_note > -1) { n = k->midi_note; if (k->midi_volume > -1) { v = k->midi_volume / 2; } else { v = KEYJAZZ_DEFAULTVOL; } } else { n = (k->sym == SDLK_SPACE) ? last_note : kbd_get_note(k); if (n <= 0 || n > 120) return; v = KEYJAZZ_DEFAULTVOL; } if (k->state == KEY_RELEASE) { song_keyup(current_sample, KEYJAZZ_NOINST, n); } else { song_keydown(current_sample, KEYJAZZ_NOINST, n, v, KEYJAZZ_CHAN_CURRENT); last_note = n; } } return; } new_sample = CLAMP(new_sample, 1, _last_vis_sample()); 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_t *sample = song_get_sample(current_sample); /* these switch statements fall through */ sample->flags &= ~(CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); switch (widgets_samplelist[9].d.menutoggle.state) { case 2: sample->flags |= CHN_PINGPONGLOOP; case 1: sample->flags |= CHN_LOOP; } switch (widgets_samplelist[12].d.menutoggle.state) { case 2: sample->flags |= CHN_PINGPONGSUSTAIN; case 1: sample->flags |= CHN_SUSTAINLOOP; } if (sample->flags & CHN_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 & CHN_SUSTAINLOOP) { if (sample->sustain_start == sample->length) sample->sustain_start = 0; if (sample->sustain_end <= sample->sustain_start) sample->sustain_end = sample->length; } csf_adjust_sample_loop(sample); /* update any samples currently playing */ song_update_playing_sample(current_sample); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } /* callback for the loop numentries */ static void update_sample_loop_points(void) { song_sample_t *sample = song_get_sample(current_sample); 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 != (unsigned long) widgets_samplelist[10].d.numentry.value || sample->loop_end != (unsigned long) 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 != (unsigned long) widgets_samplelist[13].d.numentry.value || sample->sustain_end != (unsigned long) 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(); } csf_adjust_sample_loop(sample); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ static void update_values_in_song(void) { song_sample_t *sample = song_get_sample(current_sample); /* 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 |= CHN_PANNING; else sample->flags &= ~CHN_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; status.flags |= SONG_NEEDS_SAVE; } static void update_sample_speed(void) { song_sample_t *sample = song_get_sample(current_sample); sample->c5speed = widgets_samplelist[8].d.numentry.value; status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void update_panning(void) { song_sample_t *sample = song_get_sample(current_sample); sample->flags |= CHN_PANNING; sample->panning = widgets_samplelist[4].d.thumbbar.value * 4; widgets_samplelist[3].d.toggle.state = 1; status.flags |= SONG_NEEDS_SAVE; } static void update_filename(void) { status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ int sample_is_used_by_instrument(int samp) { song_instrument_t *ins; int i, j; if (samp < 1) return 0; for (i = 1; i <= MAX_INSTRUMENTS; i++) { ins = song_get_instrument(i); if (!ins) continue; for (j = 0; j < 120; j++) { if (ins->sample_map[j] == (unsigned int)samp) return 1; } } return 0; } void sample_synchronize_to_instrument(void) { song_instrument_t *ins; int instnum = instrument_get_current(); int pos, first; ins = song_get_instrument(instnum); first = 0; for (pos = 0; pos < 120; pos++) { if (first == 0) first = ins->sample_map[pos]; if (ins->sample_map[pos] == (unsigned int)instnum) { sample_set(instnum); return; } } if (first > 0) { sample_set(first); } else { sample_set(instnum); } } void sample_list_load_page(struct page *page) { vgamem_ovl_alloc(&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->set_page = sample_list_reposition; 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); _fix_accept_text(); 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, update_filename, 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); /* count how many formats there really are */ num_save_formats = 0; while (sample_save_formats[num_save_formats].label) num_save_formats++; } schismtracker-20180209/schism/page_vars.c000066400000000000000000000200521323741476300202350ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" /* --------------------------------------------------------------------- */ /* static variables */ static struct widget widgets_vars[18]; static const int group_control[] = { 8, 9, -1 }; static const int group_playback[] = { 10, 11, -1 }; static const int group_slides[] = { 12, 13, -1 }; static char Amiga[6] = "Amiga"; /* --------------------------------------------------------------------- */ static void update_song_title(void) { status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ 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); } /* --------------------------------------------------------------------- */ void song_vars_sync_stereo(void) { // copy from the song to the page if (song_is_stereo()) togglebutton_set(widgets_vars, 10, 0); else togglebutton_set(widgets_vars, 11, 0); } 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 init_instruments(UNUSED void *data) { song_init_instruments(-1); } static void maybe_init_instruments(void) { /* XXX actually, in IT the buttons on this dialog say OK/No for whatever reason */ song_set_instrument_mode(1); dialog_create(DIALOG_YES_NO, "Initialise instruments?", init_instruments, NULL, 0, NULL); status.flags |= SONG_NEEDS_SAVE; } static void song_changed_cb(void) { char *b; int c; widgets_vars[0].d.textentry.text = current_song->title; widgets_vars[0].d.textentry.cursor_pos = strlen(widgets_vars[0].d.textentry.text); widgets_vars[1].d.thumbbar.value = current_song->initial_tempo; widgets_vars[2].d.thumbbar.value = current_song->initial_speed; widgets_vars[3].d.thumbbar.value = current_song->initial_global_volume; widgets_vars[4].d.thumbbar.value = current_song->mixing_volume; widgets_vars[5].d.thumbbar.value = current_song->pan_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); for (b = strpbrk(song_get_basename(), "Aa"), c = 12632; c && b && b[1]; c >>= 4, b++) if ((c & 15) != b[1] - *b) return; if (!c) for (b = Amiga, c = 12632; c; c>>= 4, b++) b[1] = *b + (c & 15); } /* --------------------------------------------------------------------- */ /* 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, current_song->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); } schismtracker-20180209/schism/page_waterfall.c000066400000000000000000000345541323741476300212570ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "song.h" #include #define NATIVE_SCREEN_WIDTH 640 #define NATIVE_SCREEN_HEIGHT 400 #define FUDGE_256_TO_WIDTH 4 #define SCOPE_ROWS 32 /* consts */ #define FFT_BUFFER_SIZE_LOG 11 #define FFT_BUFFER_SIZE 2048 /*(1 << FFT_BUFFER_SIZE_LOG)*/ #define FFT_OUTPUT_SIZE 1024 /* FFT_BUFFER_SIZE/2 */ /*WARNING: Hardcoded in page.c when declaring current_fft_data*/ #define FFT_BANDS_SIZE 256 /*WARNING: Hardcoded in page.c when declaring fftlog and when using it in vis_fft*/ #define PI ((double)3.14159265358979323846) /*This value is used internally to scale the power output of the FFT to decibells.*/ static const float fft_inv_bufsize = 1.0f/(FFT_BUFFER_SIZE>>2); /*Scaling for FFT. Input is expected to be signed short int.*/ static const float inv_s_range = 1.f/32768.f; short current_fft_data[2][FFT_OUTPUT_SIZE]; /*Table to change the scale from linear to log.*/ short fftlog[FFT_BANDS_SIZE]; void vis_init(void); void vis_work_16s(short *in, int inlen); void vis_work_16m(short *in, int inlen); void vis_work_8s(char *in, int inlen); void vis_work_8m(char *in, int inlen); /* variables :) */ static int mono = 0; //gain, in dBs. //static int gain = 0; static int noisefloor=72; /* get the _whole_ display */ static struct vgamem_overlay ovl = { 0, 0, 79, 49, NULL, 0, 0, 0 }; /* tables */ static unsigned int bit_reverse[FFT_BUFFER_SIZE]; static float window[FFT_BUFFER_SIZE]; static float precos[FFT_OUTPUT_SIZE]; static float presin[FFT_OUTPUT_SIZE]; /* fft state */ static float state_real[FFT_BUFFER_SIZE]; static float state_imag[FFT_BUFFER_SIZE]; static int _reverse_bits(unsigned int in) { unsigned int r = 0, n; for (n = 0; n < FFT_BUFFER_SIZE_LOG; n++) { r <<= 1; r += (in & 1); in >>= 1; } return r; } void vis_init(void) { unsigned n; for (n = 0; n < FFT_BUFFER_SIZE; n++) { bit_reverse[n] = _reverse_bits(n); #if 0 /*Rectangular/none*/ window[n] = 1; /*Cosine/sine window*/ window[n] = sin(PI * n/ FFT_BUFFER_SIZE -1); /*Hann Window*/ window[n] = 0.50f - 0.50f * cos(2.0*PI * n / (FFT_BUFFER_SIZE - 1)); /*Hamming Window*/ window[n] = 0.54f - 0.46f * cos(2.0*PI * n / (FFT_BUFFER_SIZE - 1)); /*Gaussian*/ window[n] = powf(M_E,-0.5f *pow((n-(FFT_BUFFER_SIZE-1)/2.f)/(0.4*(FFT_BUFFER_SIZE-1)/2.f),2.f)); /*Blackmann*/ window[n] = 0.42659 - 0.49656 * cos(2.0*PI * n/ (FFT_BUFFER_SIZE-1)) + 0.076849 * cos(4.0*PI * n /(FFT_BUFFER_SIZE-1)); /*Blackman-Harris*/ window[n] = 0.35875 - 0.48829 * cos(2.0*PI * n/ (FFT_BUFFER_SIZE-1)) + 0.14128 * cos(4.0*PI * n /(FFT_BUFFER_SIZE-1)) - 0.01168 * cos(6.0*PI * n /(FFT_BUFFER_SIZE-1)); #endif /*Hann Window*/ window[n] = 0.50f - 0.50f * cos(2.0*PI * n / (FFT_BUFFER_SIZE - 1)); } for (n = 0; n < FFT_OUTPUT_SIZE; n++) { float j = (2.0*PI) * n / FFT_BUFFER_SIZE; precos[n] = cos(j); presin[n] = sin(j); } #if 0 /*linear*/ fftlog[n]=n; #elif 1 /*exponential.*/ float factor = (float)FFT_OUTPUT_SIZE/(FFT_BANDS_SIZE*FFT_BANDS_SIZE); for (n = 0; n < FFT_BANDS_SIZE; n++ ) { fftlog[n]=n*n*factor; } #else /*constant note scale.*/ float factor = 8.f/(float)FFT_BANDS_SIZE; float factor2 = (float)FFT_OUTPUT_SIZE/256.f; for (n = 0; n < FFT_BANDS_SIZE; n++ ) { fftlog[n]=(powf(2.0f,n*factor)-1.f)*factor2; } #endif } /* * Understanding In and Out: * input is the samples (so, it is amplitude). The scale is expected to be signed 16bits. * The window function calculated in "window" will automatically be applied. * output is a value between 0 and 128 representing 0 = noisefloor variable * and 128 = 0dBFS (deciBell, FullScale) for each band. */ static inline void _vis_data_work(short output[FFT_OUTPUT_SIZE], short input[FFT_BUFFER_SIZE]) { unsigned int n, k, y; unsigned int ex, ff; float fr, fi; float tr, ti; float out; int yp; /* fft */ float *rp = state_real; float *ip = state_imag; for (n = 0; n < FFT_BUFFER_SIZE; n++) { int nr = bit_reverse[n]; *rp++ = (float)input[ nr ] * inv_s_range * window[nr]; *ip++ = 0; } ex = 1; ff = FFT_OUTPUT_SIZE; for (n = FFT_BUFFER_SIZE_LOG; n != 0; n--) { for (k = 0; k != ex; k++) { fr = precos[k * ff]; fi = presin[k * ff]; for (y = k; y < FFT_BUFFER_SIZE; y += ex << 1) { yp = y + ex; tr = fr * state_real[yp] - fi * state_imag[yp]; ti = fr * state_imag[yp] + fi * state_real[yp]; state_real[yp] = state_real[y] - tr; state_imag[yp] = state_imag[y] - ti; state_real[y] += tr; state_imag[y] += ti; } } ex <<= 1; ff >>= 1; } /* collect fft */ rp = state_real; rp++; ip = state_imag; ip++; const float fft_dbinv_bufsize = dB(fft_inv_bufsize); for (n = 0; n < FFT_OUTPUT_SIZE; n++) { /* "out" is the total power for each band. * To get amplitude from "output", use sqrt(out[N])/(sizeBuf>>2) * To get dB from "output", use powerdB(out[N])+db(1/(sizeBuf>>2)). * powerdB is = 10 * log10(in) * dB is = 20 * log10(in) */ out = ((*rp) * (*rp)) + ((*ip) * (*ip)); /* +0.0000000001f is -100dB of power. Used to prevent evaluating powerdB(0.0) */ output[n] = pdB_s(noisefloor, out+0.0000000001f,fft_dbinv_bufsize); rp++;ip++; } } /* convert the fft bands to columns of screen out and d have a range of 0 to 128 */ static inline void _get_columns_from_fft(unsigned char *out, short d[FFT_OUTPUT_SIZE], int m) { int i, j, a; for (i = 0, a=0; i < FFT_BANDS_SIZE; i++) { float afloat = fftlog[i]; float floora = floor(afloat); if ((i == FFT_BANDS_SIZE -1) || (afloat + 1.0f > fftlog[i+1])) { a = (int)floora; j = d[a] + (d[a+1]-d[a])*(afloat-floora); a = floor(afloat+0.5f); } else { j=d[a]; while(a<=afloat){ j = MAX(j,d[a]); a++; } } *out = j; out++; /*If mono, repeat the value.*/ if (m) { *out = j; out++; } if ((i % FUDGE_256_TO_WIDTH) == 0) { /* each band is 2.50 px wide; * output display is 640 px */ *out = j; out++; /*If mono, repeat the value.*/ if (m) { *out = j; out++; } } } } /* Convert the output of */ static inline unsigned char *_dobits(unsigned char *q, unsigned char *in, int length, int y) { int i, c; for (i = 0; i < length; i++) { /* j is has range 0 to 128. Now use the upper side for drawing.*/ c = 128 + in[i]; if (c > 255) c = 255; *q = c; q += y; } return q; } /*x = screen.x, h = 0..128, c = colour */ static inline void _drawslice(int x, int h, int c) { int y; y = ((h>>2) & (SCOPE_ROWS-1))+1; vgamem_ovl_drawline(&ovl, x, (NATIVE_SCREEN_HEIGHT-y), x, (NATIVE_SCREEN_HEIGHT-1), c); } static void _vis_process(void) { unsigned char *q; int i, k; k = NATIVE_SCREEN_WIDTH/2; unsigned char outfft[NATIVE_SCREEN_WIDTH]; vgamem_lock(); /* move up by one pixel */ memmove(ovl.q, ovl.q+NATIVE_SCREEN_WIDTH, (NATIVE_SCREEN_WIDTH* ((NATIVE_SCREEN_HEIGHT-1)-SCOPE_ROWS))); q = ovl.q + (NATIVE_SCREEN_WIDTH* ((NATIVE_SCREEN_HEIGHT-1)-SCOPE_ROWS)); if (mono) { for (i = 0; i < FFT_OUTPUT_SIZE; i++) current_fft_data[0][i] = (current_fft_data[0][i] + current_fft_data[1][i]) / 2; _get_columns_from_fft(outfft, current_fft_data[0], 1); _dobits(q, outfft, NATIVE_SCREEN_WIDTH, 1); } else { _get_columns_from_fft(outfft, current_fft_data[0], 0); _dobits(q+k, outfft, k, -1); _get_columns_from_fft(outfft+k, current_fft_data[1], 0); _dobits(q+k, outfft+k, k, 1); } /* draw the scope at the bottom */ q = ovl.q + (NATIVE_SCREEN_WIDTH*(NATIVE_SCREEN_HEIGHT-SCOPE_ROWS)); i = SCOPE_ROWS*NATIVE_SCREEN_WIDTH; memset(q,0,i); if (mono) { for (i = 0; i < NATIVE_SCREEN_WIDTH; i++) { _drawslice(i, outfft[i],5); } } else { for (i = 0; i < k; i++) { _drawslice(k-i-1, outfft[i],5); } for (i = 0; i < k; i++) { _drawslice(k+i, outfft[k+i],5); } } vgamem_unlock(); status.flags |= NEED_UPDATE; } void vis_work_16s(short *in, int inlen) { short dl[FFT_BUFFER_SIZE]; short dr[FFT_BUFFER_SIZE]; int i, j, k; if (!inlen) { memset(current_fft_data[0], 0, FFT_OUTPUT_SIZE*2); memset(current_fft_data[1], 0, FFT_OUTPUT_SIZE*2); } else { for (i = 0; i < FFT_BUFFER_SIZE;) { for (k = j = 0; k < inlen && i < FFT_BUFFER_SIZE; k++, i++) { dl[i] = in[j]; j++; dr[i] = in[j]; j++; } } _vis_data_work(current_fft_data[0], dl); _vis_data_work(current_fft_data[1], dr); } if (status.current_page == PAGE_WATERFALL) _vis_process(); } void vis_work_16m(short *in, int inlen) { short d[FFT_BUFFER_SIZE]; int i, k; if (!inlen) { memset(current_fft_data[0], 0, FFT_OUTPUT_SIZE*2); memset(current_fft_data[1], 0, FFT_OUTPUT_SIZE*2); } else { for (i = 0; i < FFT_BUFFER_SIZE;) { for (k = 0; k < inlen && i < FFT_BUFFER_SIZE; k++, i++) { d[i] = in[k]; } } _vis_data_work(current_fft_data[0], d); memcpy(current_fft_data[1], current_fft_data[0], FFT_OUTPUT_SIZE * 2); } if (status.current_page == PAGE_WATERFALL) _vis_process(); } void vis_work_8s(char *in, int inlen) { short dl[FFT_BUFFER_SIZE]; short dr[FFT_BUFFER_SIZE]; int i, j, k; if (!inlen) { memset(current_fft_data[0], 0, FFT_OUTPUT_SIZE*2); memset(current_fft_data[1], 0, FFT_OUTPUT_SIZE*2); } else { for (i = 0; i < FFT_BUFFER_SIZE;) { for (k = j = 0; k < inlen && i < FFT_BUFFER_SIZE; k++, i++) { dl[i] = ((short)in[j]) * 256; j++; dr[i] = ((short)in[j]) * 256; j++; } } _vis_data_work(current_fft_data[0], dl); _vis_data_work(current_fft_data[1], dr); } if (status.current_page == PAGE_WATERFALL) _vis_process(); } void vis_work_8m(char *in, int inlen) { short d[FFT_BUFFER_SIZE]; int i, k; if (!inlen) { memset(current_fft_data[0], 0, FFT_OUTPUT_SIZE*2); memset(current_fft_data[1], 0, FFT_OUTPUT_SIZE*2); } else { for (i = 0; i < FFT_BUFFER_SIZE;) { for (k = 0; k < inlen && i < FFT_BUFFER_SIZE; k++, i++) { d[i] = ((short)in[k]) * 256; } } _vis_data_work(current_fft_data[0], d); memcpy(current_fft_data[1], current_fft_data[0], FFT_OUTPUT_SIZE * 2); } if (status.current_page == PAGE_WATERFALL) _vis_process(); } static void draw_screen(void) { /* waterfall uses a single overlay */ vgamem_ovl_apply(&ovl); } static int waterfall_handle_key(struct key_event *k) { int n, v, order, ii; if (NO_MODIFIER(k->mod)) { 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 > -1) { if (song_is_instrument_mode()) { ii = instrument_get_current(); } else { ii = sample_get_current(); } if (k->state == KEY_RELEASE) { song_keyup(KEYJAZZ_NOINST, ii, n); status.last_keysym = 0; } else if (!k->is_repeat) { song_keydown(KEYJAZZ_NOINST, ii, n, v, KEYJAZZ_CHAN_CURRENT); } return 1; } } switch (k->sym) { case SDLK_s: if (k->mod & KMOD_ALT) { if (k->state == KEY_RELEASE) return 1; song_toggle_stereo(); status.flags |= NEED_UPDATE; return 1; } return 0; case SDLK_m: if (k->mod & KMOD_ALT) { if (k->state == KEY_RELEASE) return 1; mono = !mono; return 1; } return 0; case SDLK_LEFT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; noisefloor-=4; break; case SDLK_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; noisefloor+=4; break; case SDLK_g: if (k->mod & KMOD_ALT) { if (k->state == KEY_PRESS) return 1; order = song_get_current_order(); if (song_get_mode() == MODE_PLAYING) { n = current_song->orderlist[order]; } else { n = song_get_playing_pattern(); } 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; } return 0; case SDLK_r: if (k->mod & KMOD_ALT) { if (k->state == KEY_RELEASE) return 1; song_flip_stereo(); return 1; } return 0; case SDLK_PLUS: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) 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 == KEY_RELEASE) return 1; if (song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() - 1); } return 1; case SDLK_SEMICOLON: case SDLK_COLON: if (k->state == KEY_RELEASE) return 1; if (song_is_instrument_mode()) { instrument_set(instrument_get_current() - 1); } else { sample_set(sample_get_current() - 1); } return 1; case SDLK_QUOTE: case SDLK_QUOTEDBL: if (k->state == KEY_RELEASE) return 1; if (song_is_instrument_mode()) { instrument_set(instrument_get_current() + 1); } else { sample_set(sample_get_current() + 1); } return 1; case SDLK_COMMA: case SDLK_LESS: if (k->state == KEY_RELEASE) return 1; song_change_current_play_channel(-1, 0); return 1; case SDLK_PERIOD: case SDLK_GREATER: if (k->state == KEY_RELEASE) return 1; song_change_current_play_channel(1, 0); return 1; default: return 0; }; noisefloor = CLAMP(noisefloor, 36, 96); return 1; } static struct widget waterfall_widget_hack[1]; static void do_nil(void) {} static void waterfall_set_page(void) { vgamem_ovl_clear(&ovl, 0); } void waterfall_load_page(struct page *page) { vgamem_ovl_alloc(&ovl); page->title = ""; page->draw_full = draw_screen; page->set_page = waterfall_set_page; page->total_widgets = 1; page->widgets = waterfall_widget_hack; create_other(waterfall_widget_hack, 0, waterfall_handle_key, do_nil); } schismtracker-20180209/schism/palettes.c000066400000000000000000000221321323741476300201100ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #include "util.h" /* --------------------------------------------------------------------- */ 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}, }}, {"Purple Motion", { /* Imago Orpheus */ /* 0 */ { 0, 0, 0}, /* 1 */ {14, 10, 14}, /* 2 */ {24, 18, 24}, /* 3 */ {32, 26, 32}, /* 4 */ {48, 0, 0}, /* 5 */ {32, 63, 32}, /* 6 */ {48, 48, 48}, /* 7 */ {16, 0, 32}, /* 8 */ { 8, 8, 16}, /* 9 */ {11, 11, 21}, /* 10 */ {24, 18, 24}, /* 11 */ {32, 26, 32}, /* 12 */ {48, 48, 48}, /* 13 */ { 0, 32, 0}, /* 14 */ {12, 13, 14}, /* 15 */ { 9, 10, 11}, }}, {"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}, }}, {"Atlantic", { /* from an ANCIENT version of Impulse Tracker */ /* 0 */ { 0, 0, 0}, /* 1 */ { 2, 0, 30}, /* 2 */ { 8, 18, 43}, /* 3 */ {21, 43, 63}, /* 4 */ {63, 8, 16}, /* 5 */ {27, 32, 63}, /* 6 */ { 0, 54, 63}, /* 7 */ {10, 15, 35}, /* 8 */ { 0, 0, 30}, /* 9 */ { 0, 0, 36}, /* 10 */ { 0, 18, 32}, /* 11 */ {40, 40, 40}, /* 12 */ {18, 52, 63}, /* 13 */ {21, 50, 21}, /* 14 */ {10, 16, 27}, /* 15 */ { 8, 11, 19}, }}, {"", {{0}}} }; #define NUM_PALETTES (ARRAY_SIZE(palettes) - 1) /* --------------------------------------------------------------------- */ /* palette */ /* this is set in cfg_load() (config.c) palette_apply() must be called after changing this to update the display. */ uint8_t current_palette[16][3] = { /* 0 */ { 0, 0, 0}, /* 1 */ { 0, 0, 42}, /* 2 */ { 0, 42, 0}, /* 3 */ { 0, 42, 42}, /* 4 */ {42, 0, 0}, /* 5 */ {42, 0, 42}, /* 6 */ {42, 21, 0}, /* 7 */ {42, 42, 42}, /* 8 */ {21, 21, 21}, /* 9 */ {21, 21, 63}, /* 10 */ {21, 63, 21}, /* 11 */ {21, 63, 63}, /* 12 */ {63, 21, 21}, /* 13 */ {63, 21, 63}, /* 14 */ {63, 63, 21}, /* 15 */ {63, 63, 63}, }; /* 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; 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)); } schismtracker-20180209/schism/pattern-view.c000066400000000000000000000541141323741476300207210ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ /* --------------------------------------------------------------------- */ /* pattern edit mask indicators */ /* atnote (1) cursor_pos == 0 over (2) cursor_pos == pos masked (4) mask & MASK_whatever */ static const char mask_chars[] = { 143, // 0 143, // atnote 169, // over 169, // over && atnote 170, // masked 169, // masked && atnote 171, // masked && over 171, // masked && over && atnote }; #define MASK_CHAR(field, pos, pos2) \ mask_chars [ \ ((cursor_pos == 0) ? 1 : 0) | \ ((cursor_pos == pos) ? 2 : 0) | \ ((pos2 && cursor_pos == pos2) ? 2 : 0) | \ ((mask & field) ? 4 : 0) ] /* --------------------------------------------------------------------- */ /* 13-column track view */ void draw_channel_header_13(int chan, int x, int y, int fg) { char buf[16]; sprintf(buf, " Channel %02d ", chan); draw_text(buf, x, y, fg, 1); } void draw_note_13(int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg) { int cursor_pos_map[9] = { 0, 2, 4, 5, 7, 8, 10, 11, 12 }; char note_text[16], note_buf[4], vol_buf[4]; char instbuf[4]; get_note_string(note->note, note_buf); get_volume_string(note->volparam, note->voleffect, vol_buf); /* come to think of it, maybe the instrument text should be * created the same way as the volume. */ if (note->instrument) num99tostr(note->instrument, instbuf); else strcpy(instbuf, "\xad\xad"); snprintf(note_text, 16, "%s %s %s %c%02X", note_buf, instbuf, vol_buf, get_effect_char(note->effect), note->param); if (show_default_volumes && note->voleffect == VOLFX_NONE && note->instrument > 0 && NOTE_IS_NOTE(note->note)) { song_sample_t *smp = song_is_instrument_mode() ? csf_translate_keyboard(current_song, song_get_instrument(note->instrument), note->note, NULL) : song_get_sample(note->instrument); if (smp) { /* Modplug-specific hack: volume bit shift */ int n = smp->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->voleffect == VOLFX_PANNING) draw_text(vol_buf, x + 7, y, 2, bg); if (cursor_pos == 9) { draw_text(note_text + 10, x + 10, y, 0, 3); } else if (cursor_pos >= 0) { cursor_pos = cursor_pos_map[cursor_pos]; draw_char(note_text[cursor_pos], x + cursor_pos, y, 0, 3); } } void draw_mask_13(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[] = { MASK_CHAR(MASK_NOTE, 0, 0), MASK_CHAR(MASK_NOTE, 0, 0), MASK_CHAR(MASK_NOTE, 0, 1), 143, MASK_CHAR(MASK_INSTRUMENT, 2, 0), MASK_CHAR(MASK_INSTRUMENT, 3, 0), 143, MASK_CHAR(MASK_VOLUME, 4, 0), MASK_CHAR(MASK_VOLUME, 5, 0), 143, MASK_CHAR(MASK_EFFECT, 6, 0), MASK_CHAR(MASK_EFFECT, 7, 0), MASK_CHAR(MASK_EFFECT, 8, 0), 0, }; draw_text(buf, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 10-column track view */ void draw_channel_header_10(int chan, int x, int y, int fg) { char buf[16]; sprintf(buf, "Channel %02d", chan); draw_text(buf, x, y, fg, 1); } void draw_note_10(int x, int y, const song_note_t *note, int cursor_pos, UNUSED int fg, int bg) { uint8_t c; char note_buf[4], ins_buf[3], vol_buf[3], effect_buf[4]; get_note_string(note->note, note_buf); if (note->instrument) { num99tostr(note->instrument, ins_buf); } else { ins_buf[0] = ins_buf[1] = 173; ins_buf[2] = 0; } get_volume_string(note->volparam, note->voleffect, vol_buf); sprintf(effect_buf, "%c%02X", get_effect_char(note->effect), note->param); 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->voleffect == VOLFX_PANNING) ? 2 : 6), bg); draw_text(effect_buf, x + 7, y, 2, bg); if (cursor_pos < 0) return; if (cursor_pos > 0) cursor_pos++; if (cursor_pos == 10) { draw_text(effect_buf, x + 7, y, 0, 3); } else { 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; } draw_char(c, x + cursor_pos, y, 0, 3); } } void draw_mask_10(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[] = { MASK_CHAR(MASK_NOTE, 0, 0), MASK_CHAR(MASK_NOTE, 0, 0), MASK_CHAR(MASK_NOTE, 0, 1), MASK_CHAR(MASK_INSTRUMENT, 2, 0), MASK_CHAR(MASK_INSTRUMENT, 3, 0), MASK_CHAR(MASK_VOLUME, 4, 0), MASK_CHAR(MASK_VOLUME, 5, 0), MASK_CHAR(MASK_EFFECT, 6, 0), MASK_CHAR(MASK_EFFECT, 7, 0), MASK_CHAR(MASK_EFFECT, 8, 0), 0, }; draw_text(buf, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 8-column track view (no instrument column; no editing) */ void draw_channel_header_8(int chan, int x, int y, int fg) { char buf[8]; sprintf(buf, " %02d ", chan); draw_text(buf, x, y, fg, 1); } void draw_note_8(int x, int y, const song_note_t *note, UNUSED int cursor_pos, int fg, int bg) { char buf[4]; get_note_string(note->note, buf); draw_text(buf, x, y, fg, bg); if (note->volparam || note->voleffect) { get_volume_string(note->volparam, note->voleffect, buf); draw_text(buf, x + 3, y, (note->voleffect == VOLFX_PANNING) ? 1 : 2, bg); } else { draw_char(0, x + 3, y, fg, bg); draw_char(0, x + 4, y, fg, bg); } snprintf(buf, 4, "%c%02X", get_effect_char(note->effect), note->param); buf[3] = '\0'; draw_text(buf, x + 5, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 7-column track view */ void draw_channel_header_7(int chan, int x, int y, int fg) { char buf[8]; sprintf(buf, "Chnl %02d", chan); draw_text(buf, x, y, fg, 1); } void draw_note_7(int x, int y, const song_note_t *note, int cursor_pos, UNUSED int fg, int bg) { char note_buf[4], ins_buf[3], vol_buf[3]; int fg1, bg1, fg2, bg2; get_note_string(note->note, note_buf); if (note->instrument) num99tostr(note->instrument, ins_buf); else ins_buf[0] = ins_buf[1] = 173; get_volume_string(note->volparam, note->voleffect, 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->voleffect) { case VOLFX_NONE: fg1 = 6; break; case VOLFX_PANNING: fg1 = 10; break; case VOLFX_TONEPORTAMENTO: case VOLFX_VIBRATOSPEED: case VOLFX_VIBRATODEPTH: fg1 = 6; 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 value */ fg1 = fg2 = 10; bg1 = bg2 = bg; switch (cursor_pos) { case 7: fg1 = 0; bg1 = 3; break; case 8: fg2 = 0; bg2 = 3; break; case 9: fg1 = fg2 = 0; bg1 = bg2 = 3; cursor_pos = 6; // hack break; } draw_half_width_chars(hexdigits[(note->param & 0xf0) >> 4], hexdigits[note->param & 0xf], x + 6, 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); } void draw_mask_7(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[] = { MASK_CHAR(MASK_NOTE, 0, 0), MASK_CHAR(MASK_NOTE, 0, 0), MASK_CHAR(MASK_NOTE, 0, 1), MASK_CHAR(MASK_INSTRUMENT, 2, 3), MASK_CHAR(MASK_VOLUME, 4, 5), MASK_CHAR(MASK_EFFECT, 6, 0), MASK_CHAR(MASK_EFFECT, 7, 8), 0, }; draw_text(buf, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 3-column track view */ void draw_channel_header_3(int chan, int x, int y, int fg) { char buf[4] = { ' ', '0' + chan / 10, '0' + chan % 10, '\0' }; draw_text(buf, x, y, fg, 1); } void draw_note_3(int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg) { char buf[4]; int vfg = 6; switch (note->voleffect) { case VOLFX_VOLUME: vfg = 2; break; case VOLFX_PANNING: case VOLFX_NONE: vfg = 1; break; } switch (cursor_pos) { case 0: vfg = 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) { num99tostr(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->volparam, note->voleffect, buf + 1); draw_text(buf, x, y, vfg, 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->param); draw_text(buf, x, y, 2, bg); draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); return; case 9: sprintf(buf, "%c%02X", get_effect_char(note->effect), note->param); draw_text(buf, x, 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] = ' '; num99tostr(note->instrument, buf + 1); draw_text(buf, x, y, fg, bg); } else if (note->voleffect) { buf[0] = ' '; get_volume_string(note->volparam, note->voleffect, buf + 1); draw_text(buf, x, y, vfg, bg); } else if (note->effect || note->param) { if (cursor_pos != 0) fg = 2; sprintf(buf, "%c%02X", get_effect_char(note->effect), note->param); 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); } } void draw_mask_3(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[] = {143, 143, 143, 0}; switch (cursor_pos) { case 0: case 1: buf[0] = buf[1] = MASK_CHAR(MASK_NOTE, 0, 0); buf[2] = MASK_CHAR(MASK_NOTE, 0, 1); break; case 2: case 3: buf[1] = MASK_CHAR(MASK_INSTRUMENT, 2, 0); buf[2] = MASK_CHAR(MASK_INSTRUMENT, 3, 0); break; case 4: case 5: buf[1] = MASK_CHAR(MASK_VOLUME, 4, 0); buf[2] = MASK_CHAR(MASK_VOLUME, 5, 0); break; case 6: case 7: case 8: buf[0] = MASK_CHAR(MASK_EFFECT, 6, 0); buf[1] = MASK_CHAR(MASK_EFFECT, 7, 0); buf[2] = MASK_CHAR(MASK_EFFECT, 8, 0); break; }; draw_text(buf, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 2-column track view */ void draw_channel_header_2(int chan, int x, int y, int fg) { char 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, const song_note_t *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; case 9: fg = fg1 = fg2 = 0; bg = bg1 = bg2 = 3; break; } draw_char(get_effect_char(note->effect), x, y, fg, bg); draw_half_width_chars(hexdigits[(note->param & 0xf0) >> 4], hexdigits[note->param & 0xf], x + 1, y, fg1, bg1, fg2, bg2); } void draw_note_2(int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg) { char buf[4]; int vfg = 6; switch (note->voleffect) { case VOLFX_VOLUME: vfg = 2; break; case VOLFX_PANNING: case VOLFX_NONE: vfg = 1; break; } switch (cursor_pos) { case 0: vfg = fg = 0; bg = 3; case 1: /* Mini-accidentals on 2-col. view */ get_note_string(note->note, buf); draw_char(buf[0], x, y, fg, bg); // XXX cut-and-paste hackjob programming... this code should only exist in one place switch ((unsigned char) buf[0]) { case '^': case '~': case 0xCD: // note off case 0xAD: // dot (empty) if (cursor_pos == 1) draw_char(buf[1], x + 1, y, 0, 3); else draw_char(buf[1], x + 1, y, fg, bg); break; default: draw_half_width_chars(buf[1], buf[2], x + 1, y, fg, bg, (cursor_pos == 1 ? 0 : fg), (cursor_pos == 1 ? 3 : bg)); break; } return; /* 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) { num99tostr(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->volparam, note->voleffect, buf); draw_text(buf, x, y, vfg, bg); draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); return; case 6: case 7: case 8: case 9: draw_effect_2(x, y, note, cursor_pos, bg); return; default: /* bleh */ fg = 6; break; } if (note->note) { get_note_string(note->note, buf); draw_char(buf[0], x, y, 6, bg); switch ((unsigned char) buf[0]) { case '^': case '~': case 0xCD: // note off case 0xAD: // dot (empty) if (cursor_pos == 1) draw_char(buf[1], x + 1, y, 0, 3); else draw_char(buf[1], x + 1, y, fg, bg); break; default: draw_half_width_chars(buf[1], buf[2], x + 1, y, fg, bg, (cursor_pos == 1 ? 0 : fg), (cursor_pos == 1 ? 3 : bg)); break; } /* get_note_string_short(note->note, buf); draw_text(buf, x, y, fg, bg); */ } else if (note->instrument) { num99tostr(note->instrument, buf); draw_text(buf, x, y, fg, bg); } else if (note->voleffect) { get_volume_string(note->volparam, note->voleffect, buf); draw_text(buf, x, y, vfg, bg); } else if (note->effect || note->param) { 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); } } void draw_mask_2(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[] = {143, 143, 0}; switch (cursor_pos) { case 0: case 1: buf[0] = MASK_CHAR(MASK_NOTE, 0, 0); buf[1] = MASK_CHAR(MASK_NOTE, 0, 1); break; case 2: case 3: buf[0] = MASK_CHAR(MASK_INSTRUMENT, 2, 0); buf[1] = MASK_CHAR(MASK_INSTRUMENT, 3, 0); break; case 4: case 5: buf[0] = MASK_CHAR(MASK_VOLUME, 4, 0); buf[1] = MASK_CHAR(MASK_VOLUME, 5, 0); break; case 6: case 7: case 8: buf[0] = MASK_CHAR(MASK_EFFECT, 6, 0); buf[1] = MASK_CHAR(MASK_EFFECT, 7, 8); break; }; draw_text(buf, x, 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, const song_note_t *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->param != 0)) { draw_half_width_chars(hexdigits[(note->param & 0xf0) >> 4], hexdigits[note-> param & 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, const song_note_t *note, int cursor_pos, int fg, int bg) { char 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) num99tostr(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->volparam, note->voleffect, buf); fg = note->voleffect == VOLFX_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 9: cursor_pos = 6; // fall through 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) { num99tostr(note->instrument, buf); draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); } else if (note->voleffect) { if (cursor_pos != 0) fg = (note->voleffect == VOLFX_PANNING) ? 1 : 2; get_volume_string(note->volparam, note->voleffect, buf); draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); } else if (note->effect || note->param) { draw_effect_1(x, y, note, cursor_pos, fg, bg); } else { draw_char(173, x, y, fg, bg); } } void draw_mask_1(int x, int y, int mask, int cursor_pos, int fg, int bg) { char c = 143; switch (cursor_pos) { case 0: case 1: c = MASK_CHAR(MASK_NOTE, 0, 1); break; case 2: case 3: c = MASK_CHAR(MASK_INSTRUMENT, 2, 3); break; case 4: case 5: c = MASK_CHAR(MASK_VOLUME, 4, 5); break; case 6: c = MASK_CHAR(MASK_EFFECT, 6, 0); break; case 7: case 8: c = MASK_CHAR(MASK_EFFECT, 7, 8); break; }; draw_char(c, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 6-column track view (totally new!) */ void draw_channel_header_6(int chan, int x, int y, int fg) { char buf[8]; sprintf(buf, "Chnl%02d", chan); draw_text(buf, x, y, fg, 1); } void draw_note_6(int x, int y, const song_note_t *note, int cursor_pos, UNUSED int fg, int bg) { char note_buf[4], ins_buf[3], vol_buf[3]; int fg1, bg1, fg2, bg2; #ifdef USE_LOWERCASE_NOTES get_note_string_short(note->note, note_buf); if (note->instrument) num99tostr(note->instrument, ins_buf); else ins_buf[0] = ins_buf[1] = 173; /* 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; } #else get_note_string(note->note, note_buf); if (cursor_pos == 0) draw_char(note_buf[0], x, y, 0, 3); else draw_char(note_buf[0], x, y, fg, bg); bg1 = bg2 = bg; switch ((unsigned char) note_buf[0]) { case '^': case '~': case 0xCD: // note off case 0xAD: // dot (empty) if (cursor_pos == 1) draw_char(note_buf[1], x + 1, y, 0, 3); else draw_char(note_buf[1], x + 1, y, fg, bg); break; default: draw_half_width_chars(note_buf[1], note_buf[2], x + 1, y, fg, bg, (cursor_pos == 1 ? 0 : fg), (cursor_pos == 1 ? 3 : bg)); break; } #endif if (note->instrument) num99tostr(note->instrument, ins_buf); else ins_buf[0] = ins_buf[1] = 173; fg1 = fg2 = (note->instrument ? 10 : 2); bg1 = bg2 = bg; switch (cursor_pos) { 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 */ get_volume_string(note->volparam, note->voleffect, vol_buf); switch (note->voleffect) { case VOLFX_NONE: fg1 = 6; break; case VOLFX_PANNING: fg1 = 10; break; case VOLFX_TONEPORTAMENTO: case VOLFX_VIBRATOSPEED: case VOLFX_VIBRATODEPTH: fg1 = 6; 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 value */ fg1 = fg2 = 10; bg1 = bg2 = bg; switch (cursor_pos) { case 7: fg1 = 0; bg1 = 3; break; case 8: fg2 = 0; bg2 = 3; break; case 9: fg1 = fg2 = 0; bg1 = bg2 = 3; cursor_pos = 6; // hack break; } draw_half_width_chars(hexdigits[(note->param & 0xf0) >> 4], hexdigits[note->param & 0xf], x + 5, 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); } void draw_mask_6(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[] = { MASK_CHAR(MASK_NOTE, 0, 0), MASK_CHAR(MASK_NOTE, 0, 1), MASK_CHAR(MASK_INSTRUMENT, 2, 3), MASK_CHAR(MASK_VOLUME, 4, 5), MASK_CHAR(MASK_EFFECT, 6, 0), MASK_CHAR(MASK_EFFECT, 7, 8), 0, }; draw_text(buf, x, y, fg, bg); } schismtracker-20180209/schism/sample-edit.c000066400000000000000000000372101323741476300204760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "cmixer.h" #include "sample-edit.h" #include "sdlmain.h" /* --------------------------------------------------------------------- */ /* 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_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _sign_convert_16((signed short *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else _sign_convert_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* from the back to the front */ 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 _reverse_32(signed int *data, unsigned long length) { signed int tmp; unsigned long lpos = 0, rpos = length - 1; while (lpos < rpos) { tmp = data[lpos]; data[lpos] = data[rpos]; data[rpos] = tmp; lpos++; rpos--; } } void sample_reverse(song_sample_t * sample) { unsigned long tmp; song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_STEREO) { if (sample->flags & CHN_16BIT) _reverse_32((signed int *)sample->data, sample->length); else _reverse_16((signed short *) sample->data, sample->length); } else { if (sample->flags & CHN_16BIT) _reverse_16((signed short *) sample->data, sample->length); else _reverse_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. */ 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_t * sample, int convert_data) { signed char *odata; song_lock_audio(); // stop playing the sample because we'll be reallocating and/or changing lengths csf_stop_sample(current_song, sample); sample->flags ^= CHN_16BIT; status.flags |= SONG_NEEDS_SAVE; if (convert_data) { odata = csf_allocate_sample(sample->length * ((sample->flags & CHN_16BIT) ? 2 : 1) * ((sample->flags & CHN_STEREO) ? 2 : 1)); if (sample->flags & CHN_16BIT) { _quality_convert_8to16(sample->data, (signed short *) odata, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); } else { _quality_convert_16to8((signed short *) sample->data, odata, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); } csf_free_sample(sample->data); sample->data = odata; } else { if (sample->flags & CHN_16BIT) { 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_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _centralise_16((signed short *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else _centralise_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* downmix stereo to mono */ static void _downmix_8(signed char *data, unsigned long length) { unsigned long i, j; for (i = j = 0; j < length; j++, i += 2) data[j] = (data[i] + data[i + 1]) / 2; } static void _downmix_16(signed short *data, unsigned long length) { unsigned long i, j; for (i = j = 0; j < length; j++, i += 2) data[j] = (data[i] + data[i + 1]) / 2; } void sample_downmix(song_sample_t *sample) { if (!(sample->flags & CHN_STEREO)) return; /* what are we doing here with a mono sample? */ song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _downmix_16((signed short *) sample->data, sample->length); else _downmix_8(sample->data, sample->length); sample->flags &= ~CHN_STEREO; 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_t * sample, int percent) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _amplify_16((signed short *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1), percent); else _amplify_8(sample->data, sample->length * ((sample->flags & CHN_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); max = MAX(max, -min); return max ? 128 * 100 / max : 100; } static int _get_amplify_16(signed short *data, unsigned long length) { signed short min, max; _minmax_16(data, length, &min, &max); max = MAX(max, -min); return max ? 32768 * 100 / max : 100; } int sample_get_amplify_amount(song_sample_t *sample) { int percent; if (sample->flags & CHN_16BIT) percent = _get_amplify_16((signed short *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else percent = _get_amplify_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); if (percent < 100) 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_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _delta_decode_16((signed short *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else _delta_decode_8(sample->data, sample->length * ((sample->flags & CHN_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 long oldlen, unsigned int is_stereo) { unsigned int i; double factor = (double)oldlen / (double)newlen; if (is_stereo) for (i = 0; i < newlen; i++) { unsigned int pos = 2*(unsigned int)((double)i * factor); dst[2*i] = src[pos]; dst[2*i+1] = src[pos+1]; } else for (i = 0; i < newlen; i++) { dst[i] = src[(unsigned int)((double)i * factor)]; } } static void _resize_8(signed char *dst, unsigned long newlen, signed char *src, unsigned long oldlen, unsigned int is_stereo) { unsigned int i; double factor = (double)oldlen / (double)newlen; if (is_stereo) { for (i = 0; i < newlen; i++) { unsigned int pos = 2*(unsigned int)((double)i * factor); dst[2*i] = src[pos]; dst[2*i+1] = src[pos+1]; } } else { for (i = 0; i < newlen; i++) { dst[i] = src[(unsigned int)((double)i * factor)]; } } } static void _resize_8aa(signed char *dst, unsigned long newlen, signed char *src, unsigned long oldlen, unsigned int is_stereo) { if (is_stereo) ResampleStereo8BitFirFilter(src, dst, oldlen, newlen); else ResampleMono8BitFirFilter(src, dst, oldlen, newlen); } static void _resize_16aa(signed short *dst, unsigned long newlen, signed short *src, unsigned long oldlen, unsigned int is_stereo) { if (is_stereo) ResampleStereo16BitFirFilter(src, dst, oldlen, newlen); else ResampleMono16BitFirFilter(src, dst, oldlen, newlen); } void sample_resize(song_sample_t * sample, unsigned long newlen, int aa) { int bps; signed char *d, *z; unsigned long oldlen; if (!newlen) return; if (!sample->data || !sample->length) return; song_lock_audio(); /* resizing samples while they're playing keeps crashing things. so here's my "fix": stop the song. --plusminus */ // I suppose that works, but it's slightly annoying, so I'll just stop the sample... // hopefully this won't (re)introduce crashes. --Storlek csf_stop_sample(current_song, sample); bps = (((sample->flags & CHN_STEREO) ? 2 : 1) * ((sample->flags & CHN_16BIT) ? 2 : 1)); status.flags |= SONG_NEEDS_SAVE; d = csf_allocate_sample(newlen*bps); z = sample->data; sample->c5speed = (unsigned long)((((double)newlen) * ((double)sample->c5speed)) / ((double)sample->length)); /* scale loop points */ sample->loop_start = (unsigned long)((((double)newlen) * ((double)sample->loop_start)) / ((double)sample->length)); sample->loop_end = (unsigned long)((((double)newlen) * ((double)sample->loop_end)) / ((double)sample->length)); sample->sustain_start = (unsigned long)((((double)newlen) * ((double)sample->sustain_start)) / ((double)sample->length)); sample->sustain_end = (unsigned long)((((double)newlen) * ((double)sample->sustain_end)) / ((double)sample->length)); oldlen = sample->length; sample->length = newlen; if (sample->flags & CHN_16BIT) { if (aa) { _resize_16aa((signed short *) d, newlen, (short *) sample->data, oldlen, sample->flags & CHN_STEREO); } else { _resize_16((signed short *) d, newlen, (short *) sample->data, oldlen, sample->flags & CHN_STEREO); } } else { if (aa) { _resize_8aa(d, newlen, sample->data, oldlen, sample->flags & CHN_STEREO); } else { _resize_8(d, newlen, sample->data, oldlen, sample->flags & CHN_STEREO); } } sample->data = d; csf_free_sample(z); song_unlock_audio(); } void sample_invert(song_sample_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _invert_16((signed short *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else _invert_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); song_unlock_audio(); } static void _mono_lr16(signed short *data, unsigned long length, int shift) { unsigned long i=1, j; if (shift) { i=0; } for (j = 0; j < length; j++, i += 2) data[j] = data[i]; } static void _mono_lr8(signed char *data, unsigned long length, int shift) { unsigned long i=1, j; if (shift) { i=0; } for (j = 0; j < length; j++, i += 2) data[j] = data[i]; } void sample_mono_left(song_sample_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_STEREO) { if (sample->flags & CHN_16BIT) _mono_lr16((signed short *)sample->data, sample->length, 1); else _mono_lr8((signed char *)sample->data, sample->length, 1); sample->flags &= ~CHN_STEREO; } song_unlock_audio(); } void sample_mono_right(song_sample_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_STEREO) { if (sample->flags & CHN_16BIT) _mono_lr16((signed short *)sample->data, sample->length, 0); else _mono_lr8((signed char *)sample->data, sample->length, 0); sample->flags &= ~CHN_STEREO; } song_unlock_audio(); } schismtracker-20180209/schism/sample-view.c000066400000000000000000000217511323741476300205260ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #include #define SAMPLE_DATA_COLOR 13 /* Sample data */ #define SAMPLE_LOOP_COLOR 3 /* Sample loop marks */ #define SAMPLE_MARK_COLOR 6 /* Play mark color */ #define SAMPLE_BGMARK_COLOR 7 /* Play mark color after note fade / NNA */ /* --------------------------------------------------------------------- */ /* 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!) output channels = number of oscis input channels = number of channels in data */ static void _draw_sample_data_8(struct vgamem_overlay *r, signed char *data, unsigned long length, unsigned int inputchans, unsigned int outputchans) { unsigned long pos; unsigned int cc, co; int level, xs, ys, xe, ye, step; int nh, np; int chip; nh = (r->height / outputchans); np = r->height - (nh / 2); length /= inputchans; chip = (length < (unsigned int) r->width * 2); for (cc = 0; cc < outputchans; cc++) { pos = 0; co = 0; step = MAX(1, (length / r->width) >> 8); level=0; do { level+=ceil(data[(pos * inputchans) + cc+co] * nh / (float)(SCHAR_MAX - SCHAR_MIN + 1)); } while (co++ < inputchans-outputchans); xs = 0; ys = (np - 1) - level; ys = CLAMP(ys, 0, r->height - 1); do { pos += step; co = 0; level=0; do { level+=ceil(data[(pos * inputchans) + cc+co] * nh / (SCHAR_MAX - SCHAR_MIN + 1)); } while (co++ < inputchans-outputchans); xe = pos * r->width / length; ye = (np - 1) - level; xe = CLAMP(xe, 0, r->width - 1); ye = CLAMP(ye, 0, r->height - 1); // 'ye' is more or less useless for small samples, but this is much cleaner // code than writing nearly the same loop four different times :P vgamem_ovl_drawline(r, xs, ys, xe, chip ? ys : ye, SAMPLE_DATA_COLOR); xs = xe; ys = ye; } while (pos < length); np -= nh; } } static void _draw_sample_data_16(struct vgamem_overlay *r, signed short *data, unsigned long length, unsigned int inputchans, unsigned int outputchans) { unsigned long pos; unsigned int cc, co; int level, xs, ys, xe, ye, step; int nh, np; int chip; nh = (r->height / outputchans); np = r->height - (nh / 2); length /= inputchans; chip = (length < (unsigned int) r->width * 2); for (cc = 0; cc < outputchans; cc++) { pos = 0; co = 0; step = MAX(1, (length / r->width) >> 8); level=0; do { level += ceil(data[(pos * inputchans) + cc+co] * nh / (float)(SHRT_MAX - SHRT_MIN + 1)); } while(co++ < inputchans-outputchans); xs = 0; ys = (np - 1) - level; ys = CLAMP(ys, 0, r->height - 1); do { pos += step; co = 0; level = 0; do { level = ceil(data[(pos * inputchans) + cc+co] * nh / (float)(SHRT_MAX - SHRT_MIN + 1)); } while (co++ < inputchans-outputchans); xe = pos * r->width / length; ye = (np - 1) - level; xe = CLAMP(xe, 0, r->width - 1); ye = CLAMP(ye, 0, r->height - 1); vgamem_ovl_drawline(r, xs, ys, xe, chip ? ys : ye, SAMPLE_DATA_COLOR); xs = xe; ys = ye; } while (pos < length); np -= nh; } } /* --------------------------------------------------------------------- */ /* these functions assume the screen is locked! */ /* loop drawing */ static void _draw_sample_loop(struct vgamem_overlay *r, song_sample_t * sample) { int loopstart, loopend, y; int c = ((status.flags & CLASSIC_MODE) ? SAMPLE_DATA_COLOR : SAMPLE_LOOP_COLOR); if (!(sample->flags & CHN_LOOP)) return; loopstart = sample->loop_start * (r->width - 1) / sample->length; loopend = sample->loop_end * (r->width - 1) / sample->length; y = 0; do { vgamem_ovl_drawpixel(r, loopstart, y, 0); vgamem_ovl_drawpixel(r, loopend, y, 0); y++; vgamem_ovl_drawpixel(r, loopstart, y, c); vgamem_ovl_drawpixel(r, loopend, y, c); y++; vgamem_ovl_drawpixel(r, loopstart, y, c); vgamem_ovl_drawpixel(r, loopend, y, c); y++; vgamem_ovl_drawpixel(r, loopstart, y, 0); vgamem_ovl_drawpixel(r, loopend, y, 0); y++; } while (y < r->height); } static void _draw_sample_susloop(struct vgamem_overlay *r, song_sample_t * sample) { int loopstart, loopend, y; int c = ((status.flags & CLASSIC_MODE) ? SAMPLE_DATA_COLOR : SAMPLE_LOOP_COLOR); if (!(sample->flags & CHN_SUSTAINLOOP)) return; loopstart = sample->sustain_start * (r->width - 1) / sample->length; loopend = sample->sustain_end * (r->width - 1) / sample->length; y = 0; do { vgamem_ovl_drawpixel(r, loopstart, y, c); vgamem_ovl_drawpixel(r, loopend, y, c); y++; vgamem_ovl_drawpixel(r, loopstart, y, 0); vgamem_ovl_drawpixel(r, loopend, y, 0); y++; vgamem_ovl_drawpixel(r, loopstart, y, c); vgamem_ovl_drawpixel(r, loopend, y, c); y++; vgamem_ovl_drawpixel(r, loopstart, y, 0); vgamem_ovl_drawpixel(r, loopend, y, 0); y++; } while (y < r->height); } /* this does the lines for playing samples */ static void _draw_sample_play_marks(struct vgamem_overlay *r, song_sample_t * sample) { int n, x, y; int c; song_voice_t *channel; unsigned int *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->current_sample_data != sample->data) continue; if (!channel->final_volume) continue; c = (channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) ? SAMPLE_BGMARK_COLOR : SAMPLE_MARK_COLOR; x = channel->position * (r->width - 1) / sample->length; if (x >= r->width) { /* this does, in fact, happen :( */ continue; } y = 0; do { /* unrolled 8 times */ vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); } while (y < r->height); } song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* meat! */ void draw_sample_data(struct vgamem_overlay *r, song_sample_t *sample) { vgamem_ovl_clear(r, 0); if (sample->flags & CHN_ADLIB) { vgamem_ovl_clear(r, 2); vgamem_ovl_apply(r); char buf1[32], buf2[32]; int y1 = r->y1, y2 = y1+3; draw_box(59,y1, 77,y2, BOX_THICK | BOX_INNER | BOX_INSET); // data draw_box(54,y1, 58,y2, BOX_THIN | BOX_INNER | BOX_OUTSET); // button draw_text_len("Mod", 3, 55,y1+1, 0,2); draw_text_len("Car", 3, 55,y1+2, 0,2); sprintf(buf1, "%02X %02X %02X %02X %02X %02X", // length:6*3-1=17 sample->adlib_bytes[0], sample->adlib_bytes[2], sample->adlib_bytes[4], sample->adlib_bytes[6], sample->adlib_bytes[8], sample->adlib_bytes[10]); sprintf(buf2, "%02X %02X %02X %02X %02X", // length: 5*3-1=14 sample->adlib_bytes[1], sample->adlib_bytes[3], sample->adlib_bytes[5], sample->adlib_bytes[7], sample->adlib_bytes[9]); draw_text_len(buf1, 17, 60,y1+1, 2,0); draw_text_len(buf2, 17, 60,y1+2, 2,0); return; } if (!sample->length || !sample->data) { vgamem_ovl_apply(r); return; } /* do the actual drawing */ int chans = sample->flags & CHN_STEREO ? 2 : 1; if (sample->flags & CHN_16BIT) _draw_sample_data_16(r, (signed short *) sample->data, sample->length * chans, chans, chans); else _draw_sample_data_8(r, sample->data, sample->length * chans, chans, chans); if ((status.flags & CLASSIC_MODE) == 0) _draw_sample_play_marks(r, sample); _draw_sample_loop(r, sample); _draw_sample_susloop(r, sample); vgamem_ovl_apply(r); } void draw_sample_data_rect_16(struct vgamem_overlay *r, signed short *data, int length, unsigned int inputchans, unsigned int outputchans) { vgamem_ovl_clear(r, 0); _draw_sample_data_16(r, data, length, inputchans, outputchans); vgamem_ovl_apply(r); } void draw_sample_data_rect_8(struct vgamem_overlay *r, signed char *data, int length, unsigned int inputchans, unsigned int outputchans) { vgamem_ovl_clear(r, 0); _draw_sample_data_8(r, data, length, inputchans, outputchans); vgamem_ovl_apply(r); } schismtracker-20180209/schism/slurp.c000066400000000000000000000160361323741476300174420ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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... */ #ifndef HAVE_DUP # define dup(fd) fd #endif /* I hate this... */ #ifndef O_BINARY # ifdef O_RAW # define O_BINARY O_RAW # else # define O_BINARY 0 # endif #endif static void _slurp_closure_free(slurp_t *t) { free(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() instead...) */ #define CHUNK 65536 static int _slurp_stdio_pipe(slurp_t * t, int fd) { int old_errno; FILE *fp; uint8_t *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(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(t->data); errno = old_errno; return 0; } } t->length += this_len; } while (this_len); fclose(fp); t->closure = _slurp_closure_free; 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 = (uint8_t *) 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(t->data + got, 1, need, fp); if (len <= 0) { if (ferror(fp)) { old_errno = errno; fclose(fp); free(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_closure_free; return 1; } /* --------------------------------------------------------------------- */ static slurp_t *_slurp_open(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; t->pos = 0; if (strcmp(filename, "-") == 0) { if (_slurp_stdio(t, STDIN_FILENO)) return t; free(t); return NULL; } if (size <= 0) { size = (buf ? buf->st_size : file_size(filename)); } #ifdef WIN32 switch (slurp_win32(t, filename, size)) { case 0: free(t); return NULL; case 1: return t; }; #endif #if HAVE_MMAP switch (slurp_mmap(t, filename, size)) { case 0: free(t); return NULL; case 1: return t; }; #endif fd = open(filename, O_RDONLY | O_BINARY); if (fd < 0) { 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; } slurp_t *slurp(const char *filename, struct stat * buf, size_t size) { slurp_t *t = _slurp_open(filename, buf, size); uint8_t *mmdata; size_t mmlen; if (!t) { return NULL; } mmdata = t->data; mmlen = t->length; if (mmcmp_unpack(&mmdata, &mmlen)) { // clean up the existing data if (t->data && t->closure) { t->closure(t); } // and put the new stuff in t->length = mmlen; t->data = mmdata; t->closure = _slurp_closure_free; } // TODO re-add PP20 unpacker, possibly also handle other formats? return t; } void unslurp(slurp_t * t) { if (!t) return; if (t->data && t->closure) { t->closure(t); } free(t); } /* --------------------------------------------------------------------- */ int slurp_seek(slurp_t *t, long offset, int whence) { switch (whence) { default: case SEEK_SET: break; case SEEK_CUR: offset += t->pos; break; case SEEK_END: offset += t->length; break; } if (offset < 0 || (size_t) offset > t->length) return -1; t->pos = offset; return 0; } long slurp_tell(slurp_t *t) { return (long) t->pos; } size_t slurp_read(slurp_t *t, void *ptr, size_t count) { count = slurp_peek(t, ptr, count); t->pos += count; return count; } size_t slurp_peek(slurp_t *t, void *ptr, size_t count) { size_t bytesleft = t->length - t->pos; if (count > bytesleft) { // short read -- fill in any extra bytes with zeroes size_t tail = count - bytesleft; count = bytesleft; memset(ptr + count, 0, tail); } if (count) memcpy(ptr, t->data + t->pos, count); return count; } int slurp_getc(slurp_t *t) { return (t->pos < t->length) ? t->data[t->pos++] : EOF; } int slurp_eof(slurp_t *t) { return t->pos >= t->length; } schismtracker-20180209/schism/status.c000066400000000000000000000117401323741476300176150ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #include "it.h" #include "song.h" #include "page.h" #include "sndfile.h" #include "sdlmain.h" /* --------------------------------------------------------------------- */ static int status_bios = 0; static char *status_text = NULL; static uint32_t text_timeout; /* --------------------------------------------------------------------- */ void status_text_flash(const char *format, ...) { va_list ap; text_timeout = SDL_GetTicks() + 1000; if (status_text) free(status_text); status_bios = 0; va_start(ap, format); if (vasprintf(&status_text, format, ap) == -1) abort(); va_end(ap); status.flags |= NEED_UPDATE; } void status_text_flash_bios(const char *format, ...) { va_list ap; text_timeout = SDL_GetTicks() + 1000; if (status_text) free(status_text); status_bios = 1; va_start(ap, format); if (vasprintf(&status_text, format, ap) == -1) abort(); va_end(ap); status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ static inline int _loop_count(char *buf, int pos) { if (current_song->repeat_count < 1 || (status.flags & CLASSIC_MODE)) { pos += draw_text("Playing", pos, 9, 0, 2); } else { pos += draw_text("Loop: ", pos, 9, 0, 2); pos += draw_text(numtostr(0, current_song->repeat_count, buf), pos, 9, 3, 2); } return pos; } static inline void draw_song_playing_status(void) { int pos = 2; char buf[16]; int pattern = song_get_playing_pattern(); pos = _loop_count(buf, pos); pos += draw_text(", Order: ", pos, 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, csf_last_order(current_song), 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 = _loop_count(buf, pos); 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_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_t 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) { if (status_bios) { draw_text_bios_len(status_text, 60, 2, 9, 0, 2); } else { 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; } default: break; } } } schismtracker-20180209/schism/tree.c000066400000000000000000000112701323741476300172270ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "tree.h" typedef struct treenode { struct treenode *left, *right; void *value; } treenode_t; struct tree { treecmp_t cmp; treenode_t *root; }; typedef void (*nodewalk_t) (treenode_t *node); static void _treenode_walk(treenode_t *node, treewalk_t tapply, nodewalk_t napply) { // IF IF IF IF IF if (!node) return; if (node->left) _treenode_walk(node->left, tapply, napply); if (node->right) _treenode_walk(node->right, tapply, napply); if (tapply) tapply(node->value); if (napply) napply(node); } static void _treenode_free(treenode_t *node) { free(node); } tree_t *tree_alloc(treecmp_t cmp) { tree_t *tree = mem_calloc(1, sizeof(tree_t)); tree->cmp = cmp; return tree; } void tree_free(tree_t *tree, treewalk_t freeval) { _treenode_walk(tree->root, freeval, _treenode_free); free(tree); } static treenode_t *_treenode_find(treenode_t *node, treecmp_t cmp, void *value) { int r; while (node) { r = cmp(value, node->value); if (r == 0) break; else if (r < 0) node = node->left; else node = node->right; } return node; } static treenode_t *_treenode_insert(treenode_t *node, treecmp_t cmp, treenode_t *new) { int r; if (!node) return new; r = cmp(new->value, node->value); if (r < 0) node->left = _treenode_insert(node->left, cmp, new); else node->right = _treenode_insert(node->right, cmp, new); return node; } void tree_walk(tree_t *tree, treewalk_t apply) { _treenode_walk(tree->root, apply, NULL); } void *tree_insert(tree_t *tree, void *value) { treenode_t *node = _treenode_find(tree->root, tree->cmp, value); if (node) return node->value; node = mem_calloc(1, sizeof(treenode_t)); node->value = value; tree->root = _treenode_insert(tree->root, tree->cmp, node); return NULL; } void *tree_replace(tree_t *tree, void *value) { void *prev; treenode_t *node = _treenode_find(tree->root, tree->cmp, value); if (node) { prev = node->value; node->value = value; return prev; } node = mem_calloc(1, sizeof(treenode_t)); node->value = value; tree->root = _treenode_insert(tree->root, tree->cmp, node); return NULL; } void *tree_find(tree_t *tree, void *value) { treenode_t *node = _treenode_find(tree->root, tree->cmp, value); return node ? node->value : NULL; } #ifdef TEST #include #include struct node { char *k, *v; }; int sncmp(const void *a, const void *b) { return strcmp(((struct node *) a)->k, ((struct node *) b)->k); } struct node *snalloc(char *k, char *v) { struct node *n = mem_alloc(sizeof(struct node)); n->k = k; n->v = v; return n; } int main(int argc, char **argv) { // some random junk struct node nodes[] = { {"caches", "disgruntled"}, {"logician", "daemon"}, {"silence", "rinse"}, {"shipwreck", "formats"}, {"justifying", "gnash"}, {"gadgetry", "ever"}, {"silence", "oxidized"}, // note: duplicate key {"plumbing", "rickshaw"}, {NULL, NULL}, }; struct node find; struct node *p; tree_t *tree; int n; // test 1: populate with tree_insert tree = tree_alloc(sncmp); for (n = 0; nodes[n].k; n++) { p = snalloc(nodes[n].k, nodes[n].v); if (tree_insert(tree, p)) { printf("duplicate key %s\n", p->k); free(p); } } find.k = "silence"; p = tree_find(tree, &find); printf("%s: %s (should be 'rinse')\n", p->k, p->v); tree_free(tree, free); // test 2: populate with tree_replace tree = tree_alloc(sncmp); for (n = 0; nodes[n].k; n++) { p = snalloc(nodes[n].k, nodes[n].v); p = tree_replace(tree, p); if (p) { printf("duplicate key %s\n", p->k); free(p); } } find.k = "silence"; p = tree_find(tree, &find); printf("%s: %s (should be 'oxidized')\n", p->k, p->v); tree_free(tree, free); return 0; } #endif /* TEST */ schismtracker-20180209/schism/util.c000066400000000000000000000447451323741476300172620ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "osdefs.h" /* need this for win32_filecreated_callback */ #include #include #include #include #include #if defined(__amigaos4__) # define FALLBACK_DIR "." /* not used... */ #elif defined(WIN32) # define FALLBACK_DIR "C:\\" #elif defined(GEKKO) # define FALLBACK_DIR "isfs:/" // always exists, seldom useful #else /* POSIX? */ # define FALLBACK_DIR "/" #endif #ifdef WIN32 #include #include #include #else #include #include #endif void ms_sleep(unsigned int ms) { #ifdef WIN32 SleepEx(ms,FALSE); #else usleep(ms*1000); #endif } char *str_dup(const char *s) { char *q; q = strdup(s); if (!q) { /* throw out of memory exception */ perror("strdup"); exit(255); } return q; } char *strn_dup(const char *s, size_t n) { char *q; q = malloc(n + 1); if (!q) { /* throw out of memory exception */ perror("strndup"); exit(255); } memcpy(q, s, n); q[n] = '\0'; return q; } 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_calloc(size_t nmemb, size_t size) { void *q; q = calloc(nmemb, size); if (!q) { /* throw out of memory exception */ perror("calloc"); 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; } /* --------------------------------------------------------------------- */ /* CONVERSION FUNCTIONS */ /* linear -> deciBell */ /* amplitude normalized to 1.0f. */ float dB(float amplitude) { return 20.0f * log10f(amplitude); } /* deciBell -> linear */ float dB2_amp(float db) { return powf(10.0f, db / 20.0f); } /* linear -> deciBell */ /* power normalized to 1.0f. */ float pdB(float power) { return 10.0f * log10f(power); } /* deciBell -> linear */ float dB2_power(float db) { return powf(10.0f, db / 10.0f); } /* linear -> deciBell */ /* amplitude normalized to 1.0f. */ /* Output scaled (and clipped) to 128 lines with noisefloor range. */ /* ([0..128] = [-noisefloor..0dB]) */ /* correction_dBs corrects the dB after converted, but before scaling.*/ short dB_s(int noisefloor, float amplitude, float correction_dBs) { float db = dB(amplitude) + correction_dBs; return CLAMP((int)(128.f*(db+noisefloor))/noisefloor, 0, 127); } /* deciBell -> linear */ /* Input scaled to 128 lines with noisefloor range. */ /* ([0..128] = [-noisefloor..0dB]) */ /* amplitude normalized to 1.0f. */ /* correction_dBs corrects the dB after converted, but before scaling.*/ short dB2_amp_s(int noisefloor, int db, float correction_dBs) { return dB2_amp((db*noisefloor/128.f)-noisefloor-correction_dBs); } /* linear -> deciBell */ /* power normalized to 1.0f. */ /* Output scaled (and clipped) to 128 lines with noisefloor range. */ /* ([0..128] = [-noisefloor..0dB]) */ /* correction_dBs corrects the dB after converted, but before scaling.*/ short pdB_s(int noisefloor, float power, float correction_dBs) { float db = pdB(power)+correction_dBs; return CLAMP((int)(128.f*(db+noisefloor))/noisefloor, 0, 127); } /* deciBell -> linear */ /* Input scaled to 128 lines with noisefloor range. */ /* ([0..128] = [-noisefloor..0dB]) */ /* power normalized to 1.0f. */ /* correction_dBs corrects the dB after converted, but before scaling.*/ short dB2_power_s(int noisefloor, int db, float correction_dBs) { return dB2_power((db*noisefloor/128.f)-noisefloor-correction_dBs); } /* --------------------------------------------------------------------- */ /* FORMATTING FUNCTIONS */ char *get_date_string(time_t when, char *buf) { struct tm tmr; const char *month_str[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; /* DO NOT change this back to localtime(). If some backward platform doesn't have localtime_r, it needs to be implemented separately. */ localtime_r(&when, &tmr); snprintf(buf, 27, "%s %d, %d", month_str[tmr.tm_mon], tmr.tm_mday, 1900 + tmr.tm_year); return buf; } char *get_time_string(time_t when, char *buf) { struct tm tmr; localtime_r(&when, &tmr); snprintf(buf, 27, "%d:%02d%s", tmr.tm_hour % 12 ? : 12, tmr.tm_min, tmr.tm_hour < 12 ? "am" : "pm"); return buf; } char *num99tostr(int n, char *buf) { static const char *qv = "HIJKLMNOPQRSTUVWXYZ"; if (n < 100) { sprintf(buf, "%02d", n); } else if (n <= 256) { n -= 100; sprintf(buf, "%c%d", qv[(n/10)], (n % 10)); } return buf; } char *numtostr(int digits, unsigned int n, char *buf) { if (digits > 0) { char fmt[] = "%03u"; digits %= 10; fmt[2] = '0' + digits; snprintf(buf, digits + 1, fmt, n); buf[digits] = 0; } else { sprintf(buf, "%u", n); } return buf; } char *numtostr_signed(int digits, int n, 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) { filename = get_basename(filename); const char *extension = strrchr(filename, '.'); if (!extension) { /* no extension? bummer. point to the \0 at the end of the string. */ extension = strchr(filename, '\0'); } return extension; } char *get_parent_directory(const char *dirname) { char *ret, *pos; int n; if (!dirname || !dirname[0]) return NULL; ret = str_dup(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"; inline int ltrim_string(char *s) { int ws = strspn(s, whitespace); int len = strlen(s) - ws; if (ws) memmove(s, s + ws, len + 1); return len; } inline int rtrim_string(char *s) { int len = strlen(s) - 1; while (len > 0 && strchr(whitespace, s[len])) len--; len++; s[len] = '\0'; return len; } int trim_string(char *s) { ltrim_string(s); return rtrim_string(s); } /* 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 = str_dup(p + 1); return 1; } /* adapted from glib. in addition to the normal c escapes, this also escapes the hashmark and semicolon * (comment characters). if space is true, the first/last character is also escaped if it is a space. */ char *str_escape(const char *s, int space) { /* Each source byte needs maximally four destination chars (\777) */ char *dest = calloc(4 * strlen(s) + 1, sizeof(char)); char *d = dest; if (space && *s == ' ') { *d++ = '\\'; *d++ = '0'; *d++ = '4'; *d++ = '0'; s++; } while (*s) { switch (*s) { case '\a': *d++ = '\\'; *d++ = 'a'; break; case '\b': *d++ = '\\'; *d++ = 'b'; break; case '\f': *d++ = '\\'; *d++ = 'f'; break; case '\n': *d++ = '\\'; *d++ = 'n'; break; case '\r': *d++ = '\\'; *d++ = 'r'; break; case '\t': *d++ = '\\'; *d++ = 't'; break; case '\v': *d++ = '\\'; *d++ = 'v'; break; case '\\': case '"': *d++ = '\\'; *d++ = *s; break; default: if (*s < ' ' || *s >= 127 || (space && *s == ' ' && s[1] == '\0')) { case '#': case ';': *d++ = '\\'; *d++ = '0' + ((((uint8_t) *s) >> 6) & 7); *d++ = '0' + ((((uint8_t) *s) >> 3) & 7); *d++ = '0' + ( ((uint8_t) *s) & 7); } else { *d++ = *s; } break; } s++; } *d = 0; return dest; } static inline int readhex(const char *s, int w) { int o = 0; while (w--) { o <<= 4; switch (*s) { case '0'...'9': o |= *s - '0'; break; case 'a'...'f': o |= *s - 'a' + 10; break; case 'A'...'F': o |= *s - 'A' + 10; break; default: return -1; } s++; } return o; } /* opposite of str_escape. (this is glib's 'compress' function renamed more clearly) */ char *str_unescape(const char *s) { const char *end; int hex; char *dest = calloc(strlen(s) + 1, sizeof(char)); char *d = dest; while (*s) { if (*s == '\\') { s++; switch (*s) { case '0'...'7': *d = 0; end = s + 3; while (s < end && *s >= '0' && *s <= '7') { *d = *d * 8 + *s - '0'; s++; } d++; s--; break; case 'a': *d++ = '\a'; break; case 'b': *d++ = '\b'; break; case 'f': *d++ = '\f'; break; case 'n': *d++ = '\n'; break; case 'r': *d++ = '\r'; break; case 't': *d++ = '\t'; break; case 'v': *d++ = '\v'; break; case '\0': // trailing backslash? *d++ = '\\'; s--; break; case 'x': hex = readhex(s + 1, 2); if (hex >= 0) { *d++ = hex; s += 2; break; } /* fall through */ default: /* Also handles any other char, like \" \\ \; etc. */ *d++ = *s; break; } } else { *d++ = *s; } s++; } *d = 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 = str_dup(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 */ /* 0 = success, !0 = failed (check errno) */ int make_backup_file(const char *filename, int numbered) { char buf[PATH_MAX]; /* ensure plenty of room to breathe */ if (strlen(filename) > PATH_MAX - 16) { errno = ENAMETOOLONG; return -1; } if (numbered) { /* If some crazy person needs more than 65536 backup files, they probably have more serious issues to tend to. */ int n = 1, ret; do { sprintf(buf, "%s.%d~", filename, n++); ret = rename_file(filename, buf, 0); } while (ret != 0 && errno == EEXIST && n < 65536); return ret; } else { strcpy(buf, filename); strcat(buf, "~"); return rename_file(filename, buf, 1); } } 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; } /* --------------------------------------------------------------------- */ /* FILESYSTEM FUNCTIONS */ int is_directory(const char *filename) { struct stat buf; if (stat(filename, &buf) == -1) { /* Well, at least we tried. */ return 0; } return S_ISDIR(buf.st_mode); } char *get_current_directory(void) { char buf[PATH_MAX + 1]; /* hmm. fall back to the current dir */ if (getcwd(buf, PATH_MAX)) return str_dup(buf); return str_dup("."); } /* this function is horrible */ char *get_home_directory(void) { char buf[PATH_MAX + 1]; #if defined(__amigaos4__) return str_dup("PROGDIR:"); #elif defined(WIN32) if (SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, buf) == ERROR_SUCCESS) return strdup(buf); #else char *ptr = getenv("HOME"); if (ptr) return str_dup(ptr); #endif /* hmm. fall back to the current dir */ if (getcwd(buf, PATH_MAX)) return str_dup(buf); /* still don't have a directory? sheesh. */ return str_dup(FALLBACK_DIR); } char *get_dot_directory(void) { #ifdef WIN32 char buf[PATH_MAX + 1]; if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, buf) == ERROR_SUCCESS) return strdup(buf); // else fall back to home (but if this ever happens, things are really screwed...) #endif return get_home_directory(); } char *str_concat(const char *s, ...) { va_list ap; char *out = NULL; int len = 0; va_start(ap,s); while (s) { out = mem_realloc(out, (len += strlen(s)+1)); strcat(out, s); s = va_arg(ap, const char *); } va_end(ap); return out; } void unset_env_var(const char *key) { #ifdef HAVE_UNSETENV unsetenv(key); #else /* assume POSIX-style semantics */ putenv(key); #endif } 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); } int run_hook(const char *dir, const char *name, const char *maybe_arg) { #ifdef WIN32 char buf[PATH_MAX]; const char *ptr; char buf2[PATH_MAX]; struct stat sb; int r; if (!GetCurrentDirectory(PATH_MAX-1,buf)) return 0; snprintf(buf2, PATH_MAX-2, "%s.bat", name); if (chdir(dir) == -1) return 0; if (stat(buf2, &sb) == -1) { r = 0; } else { ptr = getenv("COMSPEC") ?: "command.com"; r = _spawnlp(_P_WAIT, ptr, ptr, "/c", buf2, maybe_arg, 0); } SetCurrentDirectory(buf); chdir(buf); if (r == 0) return 1; return 0; #elif defined(GEKKO) // help how do I operating system (void) dir; (void) name; (void) maybe_arg; return 0; #else char *tmp; int st; switch (fork()) { case -1: return 0; case 0: if (chdir(dir) == -1) _exit(255); tmp = malloc(strlen(name)+4); if (!tmp) _exit(255); sprintf(tmp, "./%s", name); execl(tmp, tmp, maybe_arg, NULL); free(tmp); _exit(255); }; while (wait(&st) == -1) { } if (WIFEXITED(st) && WEXITSTATUS(st) == 0) return 1; return 0; #endif } /* --------------------------------------------------------------------------------------------------------- */ static int _rename_nodestroy(const char *old, const char *new) { /* XXX does __amigaos4__ have a special need for this? */ #ifdef WIN32 /* is this code not finished? it never returns success */ UINT em = SetErrorMode(0); if (!MoveFile(old, new)) { switch (GetLastError()) { case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: SetErrorMode(em); errno = EEXIST; return -1; }; SetErrorMode(em); return -1; } SetErrorMode(em); return 0; #else if (link(old, new) == -1) { return -1; } if (unlink(old) == -1) { /* This can occur when people are using a system with broken link() semantics, or if the user can create files that he cannot remove. these systems are decidedly not POSIX.1 but they may try to compile schism, and we won't know they are broken unless we warn them. */ fprintf(stderr, "link() succeeded, but unlink() failed. something is very wrong\n"); } return 0; #endif } /* 0 = success, !0 = failed (check errno) */ int rename_file(const char *old, const char *new, int overwrite) { if (!overwrite) return _rename_nodestroy(old, new); #ifdef WIN32 UINT em; em = SetErrorMode(0); if (MoveFile(old, new)) { win32_filecreated_callback(new); return 0; } switch (GetLastError()) { case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: break; default: /* eh... */ SetErrorMode(em); return -1; }; if (MoveFileEx(old, new, MOVEFILE_REPLACE_EXISTING)) { /* yay */ SetErrorMode(em); return 0; } /* this sometimes work with win95 and novell shares */ chmod(new, 0777); chmod(old, 0777); /* more junk */ SetFileAttributesA(new, FILE_ATTRIBUTE_NORMAL); SetFileAttributesA(new, FILE_ATTRIBUTE_TEMPORARY); if (MoveFile(old, new)) { /* err.. yay! */ win32_filecreated_callback(new); SetErrorMode(em); return 0; } /* okay, let's try again */ if (!DeleteFileA(new)) { /* no chance! */ SetErrorMode(em); return -1; } if (MoveFile(old, new)) { /* .... */ win32_filecreated_callback(new); SetErrorMode(em); return 0; } /* alright, thems the breaks. win95 eats your files, and not a damn thing I can do about it. */ SetErrorMode(em); return -1; #else int r = rename(old, new); if (r != 0 && errno == EEXIST) { /* Broken rename()? Try smashing the old file first, and hope *that* doesn't also fail ;) */ if (unlink(old) != 0 || rename(old, new) == -1) return -1; } return r; #endif } schismtracker-20180209/schism/version.c000066400000000000000000000123161323741476300177570ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #include "version.h" #define TOP_BANNER_CLASSIC "Impulse Tracker v2.14 Copyright (C) 1995-1998 Jeffrey Lim" /* written by ver_init */ static char top_banner_normal[80]; /* Lower 12 bits of the CWTV field in IT and S3M files. "Proper" version numbers went the way of the dodo, but we can't really fit an eight-digit date stamp directly into a twelve-bit number. Since anything < 0x50 already carries meaning (even though most of them weren't used), we have 0xfff - 0x50 = 4015 possible values. By encoding the date as an offset from a rather arbitrarily chosen epoch, there can be plenty of room for the foreseeable future. < 0x020: a proper version (files saved by such versions are likely very rare) = 0x020: any version between the 0.2a release (2005-04-29?) and 2007-04-17 = 0x050: anywhere from 2007-04-17 to 2009-10-31 (version was updated to 0x050 in hg changeset 2f6bd40c0b79) > 0x050: the number of days since 2009-10-31, for example: 0x051 = (0x051 - 0x050) + 2009-10-31 = 2009-11-01 0x052 = (0x052 - 0x050) + 2009-10-31 = 2009-11-02 0x14f = (0x14f - 0x050) + 2009-10-31 = 2010-07-13 0xffe = (0xfff - 0x050) + 2009-10-31 = 2020-10-27 = 0xfff: a non-value indicating a date after 2020-10-27 (assuming Schism Tracker still exists then) */ short ver_cwtv; /* these should be 50 characters or shorter, as they are used in the startup dialog */ const char *ver_short_copyright = "Copyright (c) 2003-2017 Storlek, Mrs. Brisby et al."; const char *ver_short_based_on = "Based on Impulse Tracker by Jeffrey Lim aka Pulse"; /* SEE ALSO: helptext/copyright (contains full copyright information, credits, and GPL boilerplate) */ static time_t epoch_sec; const char *schism_banner(int classic) { return (classic ? TOP_BANNER_CLASSIC : top_banner_normal); } /* Information at our disposal: VERSION "" or "YYYYMMDD" A date here is the date of the last commit from git empty string will happen if git isn't installed, or no .git __DATE__ "Jun 3 2009" __TIME__ "23:39:19" __TIMESTAMP__ "Wed Jun 3 23:39:19 2009" These are annoying to manipulate beacuse of the month being in text format -- but at least I don't think they're ever localized, which would make it much more annoying. Should always exist, especially considering that we require gcc. However, it is a poor indicator of the age of the *code*, since it depends on the clock of the computer that's building the code, and also there is the possibility that someone was hanging onto the code for a really long time before building it. */ static int get_version_tm(struct tm *version) { char *ret; memset(version, 0, sizeof(*version)); ret = strptime(VERSION, "%Y %m %d", version); if (ret && !*ret) return 1; /* Argh. */ memset(version, 0, sizeof(*version)); ret = strptime(__DATE__, "%b %e %Y", version); if (ret && !*ret) return 1; /* Give up; we don't know anything. */ return 0; } void ver_init(void) { struct tm version, epoch = { .tm_year = 109, .tm_mon = 9, .tm_mday = 31 }; /* 2009-10-31 */ time_t version_sec; char ver[32] = VERSION; if (get_version_tm(&version)) { version_sec = mktime(&version); } else { printf("help, I am very confused about myself\n"); version_sec = epoch_sec; } epoch_sec = mktime(&epoch); version_sec = mktime(&version); ver_cwtv = 0x050 + (version_sec - epoch_sec) / 86400; ver_cwtv = CLAMP(ver_cwtv, 0x050, 0xfff); /* show build date if we don't know last commit date (no git) */ if (ver[0]) { snprintf(top_banner_normal, sizeof(top_banner_normal) - 1, "Schism Tracker %s", ver); } else { snprintf(top_banner_normal, sizeof(top_banner_normal) - 1, "Schism Tracker built %s %s", __DATE__, __TIME__); } top_banner_normal[sizeof(top_banner_normal) - 1] = '\0'; /* to be sure */ } void ver_decode_cwtv(uint16_t cwtv, char *buf) { struct tm version; time_t version_sec; cwtv &= 0xfff; if (cwtv > 0x050) { // Annoyingly, mktime uses local time instead of UTC. Why etc. version_sec = ((cwtv - 0x050) * 86400) + epoch_sec; if (localtime_r(&version_sec, &version)) { sprintf(buf, "%04d-%02d-%02d", version.tm_year + 1900, version.tm_mon + 1, version.tm_mday); return; } } sprintf(buf, "0.%x", cwtv); } schismtracker-20180209/schism/video.c000066400000000000000000001467061323741476300174130ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 /* should be the native res of the display (set once and never again) * assumes the user starts schism from the desktop and that the desktop * is the native res (or at least something with square pixels) */ static int display_native_x = -1; static int display_native_y = -1; #include "headers.h" #include "it.h" #include "osdefs.h" /* bugs * ... in sdl. not in this file :) * * - take special care to call SDL_SetVideoMode _exactly_ once * when on a console (video.desktop.fb_hacks) * */ #if HAVE_SYS_KD_H # include #endif #if HAVE_LINUX_FB_H # include #endif #include #include #if HAVE_SYS_IOCTL_H # include #endif #if HAVE_SIGNAL_H #include #endif /* for memcpy */ #include #include #include #include #include "sdlmain.h" #include #include #include "video.h" #ifndef MACOSX #ifdef WIN32 #include "auto/schismico.h" #else #include "auto/schismico_hires.h" #endif #endif #ifndef APIENTRY #define APIENTRY #endif #ifndef APIENTRYP #define APIENTRYP APIENTRY * #endif extern int macosx_did_finderlaunch; #define NVIDIA_PixelDataRange 1 #if !defined(USE_OPENGL) typedef unsigned int GLuint; typedef int GLint; typedef int GLenum; typedef int GLsizei; typedef void GLvoid; typedef float GLfloat; typedef int GLboolean; typedef int GLbitfield; typedef float GLclampf; typedef unsigned char GLubyte; #endif #if USE_OPENGL && 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 static PFNWGLALLOCATEMEMORYNVPROC db_glAllocateMemoryNV = NULL; static 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 static 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 #include #include "wine-ddraw.h" struct private_hwdata { LPDIRECTDRAWSURFACE3 dd_surface; LPDIRECTDRAWSURFACE3 dd_writebuf; }; #endif struct video_cf { struct { unsigned int width; unsigned int height; int autoscale; } draw; struct { unsigned int width,height,bpp; int want_fixed; int swsurface; int fb_hacks; 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 unsigned int yuvlayout; SDL_Rect clip; SDL_Surface * surface; SDL_Overlay * overlay; /* to convert 32-bit color to 24-bit color */ unsigned char *cv32backing; /* for tv mode */ unsigned char *cv8backing; struct { unsigned int x; unsigned int y; int visible; } mouse; unsigned int yuv_y[256]; unsigned int yuv_u[256]; unsigned int yuv_v[256]; unsigned int pal[256]; unsigned int tc_bgr32[256]; }; static struct video_cf video; #ifdef USE_OPENGL static int int_log2(int val) { int l = 0; while ((val >>= 1 ) != 0) l++; return l; } #endif #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]; struct { unsigned int num; const char *name, *type; } yuv_layouts[] = { {VIDEO_YUV_IYUV, "IYUV", "planar"}, {VIDEO_YUV_YV12_TV, "YV12", "planar+tv"}, {VIDEO_YUV_IYUV_TV, "IYUV", "planar+tv"}, {VIDEO_YUV_YVYU, "YVYU", "packed"}, {VIDEO_YUV_UYVY, "UYVY", "packed"}, {VIDEO_YUV_YUY2, "YUY2", "packed"}, {VIDEO_YUV_RGBA, "RGBA", "packed"}, {VIDEO_YUV_RGBT, "RGBT", "packed"}, {VIDEO_YUV_RGB565, "RGB565", "packed"}, {VIDEO_YUV_RGB24, "RGB24", "packed"}, {VIDEO_YUV_RGB32, "RGB32", "packed"}, {0, NULL, NULL}, }, *layout = yuv_layouts; log_appendf(5, " Using driver '%s'", SDL_VideoDriverName(buf, 256)); switch (video.desktop.type) { case VIDEO_SURFACE: log_appendf(5, " %s%s video surface", (video.surface->flags & SDL_HWSURFACE) ? "Hardware" : "Software", (video.surface->flags & SDL_HWACCEL) ? " accelerated" : ""); 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 an overlay isn't hardware accelerated, what is it? I guess this works */ log_appendf(5, " %s-accelerated video overlay", video.overlay->hw_overlay ? "Hardware" : "Non"); while (video.yuvlayout != layout->num && layout->name != NULL) layout++; if (layout->name) log_appendf(5, " Display format: %s (%s)", layout->name, layout->type); else log_appendf(5, " Display format: %x", video.yuvlayout); break; case VIDEO_GL: log_appendf(5, " %s%s OpenGL interface", (video.surface->flags & SDL_HWSURFACE) ? "Hardware" : "Software", (video.surface->flags & SDL_HWACCEL) ? " accelerated" : ""); #if defined(NVIDIA_PixelDataRange) if (video.gl.pixel_data_range) log_append(5, 0, " NVidia pixel range extensions available"); #endif break; case VIDEO_DDRAW: // is this meaningful? log_appendf(5, " %s%s DirectDraw interface", (video.surface->flags & SDL_HWSURFACE) ? "Hardware" : "Software", (video.surface->flags & SDL_HWACCEL) ? " accelerated" : ""); break; }; log_appendf(5, " %d bits/pixel", video.surface->format->BitsPerPixel); if (video.desktop.fullscreen || video.desktop.fb_hacks) { log_appendf(5, " Display dimensions: %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)); video.cv32backing = mem_alloc(NATIVE_SCREEN_WIDTH * 8); video.cv8backing = mem_alloc(NATIVE_SCREEN_WIDTH); _did_preinit = 1; } } // check if w and h are multiples of native res (and by the same multiplier) static int best_resolution(int w, int h) { if ((w % NATIVE_SCREEN_WIDTH == 0) && (h % NATIVE_SCREEN_HEIGHT == 0) && ((w / NATIVE_SCREEN_WIDTH) == (h / NATIVE_SCREEN_HEIGHT))) { return 1; } else { return 0; } } int video_is_fullscreen(void) { return video.desktop.fullscreen; } int video_width(void) { return video.clip.w; } int video_height(void) { return video.clip.h; } void video_shutdown(void) { if (video.desktop.fullscreen) { video.desktop.fullscreen = 0; video_resize(0,0); } } void video_fullscreen(int tri) { _video_preinit(); if (tri == 0 || video.desktop.fb_hacks) { video.desktop.fullscreen = 0; } else if (tri == 1) { video.desktop.fullscreen = 1; } 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(); - this should be done in main, not here */ } } void video_setup(const char *driver) { char *q; /* _SOME_ drivers can be switched to, but not all of them... */ if (driver && strcasecmp(driver, "auto") == 0) driver = NULL; _video_preinit(); video.draw.width = NATIVE_SCREEN_WIDTH; video.draw.height = NATIVE_SCREEN_HEIGHT; video.mouse.visible = MOUSE_EMULATED; video.yuvlayout = VIDEO_YUV_NONE; if ((q=getenv("SCHISM_YUVLAYOUT")) || (q=getenv("YUVLAYOUT"))) { if (strcasecmp(q, "YUY2") == 0 || strcasecmp(q, "YUNV") == 0 || strcasecmp(q, "V422") == 0 || strcasecmp(q, "YUYV") == 0) { video.yuvlayout = VIDEO_YUV_YUY2; } else if (strcasecmp(q, "UYVY") == 0) { video.yuvlayout = VIDEO_YUV_UYVY; } else if (strcasecmp(q, "YVYU") == 0) { video.yuvlayout = VIDEO_YUV_YVYU; } else if (strcasecmp(q, "YV12") == 0) { video.yuvlayout = VIDEO_YUV_YV12; } else if (strcasecmp(q, "IYUV") == 0) { video.yuvlayout = VIDEO_YUV_IYUV; } else if (strcasecmp(q, "YV12/2") == 0) { video.yuvlayout = VIDEO_YUV_YV12_TV; } else if (strcasecmp(q, "IYUV/2") == 0) { video.yuvlayout = VIDEO_YUV_IYUV_TV; } else if (strcasecmp(q, "RGBA") == 0) { video.yuvlayout = VIDEO_YUV_RGBA; } else if (strcasecmp(q, "RGBT") == 0) { video.yuvlayout = VIDEO_YUV_RGBT; } else if (strcasecmp(q, "RGB565") == 0 || strcasecmp(q, "RGB2") == 0) { video.yuvlayout = VIDEO_YUV_RGB565; } else if (strcasecmp(q, "RGB24") == 0) { video.yuvlayout = VIDEO_YUV_RGB24; } else if (strcasecmp(q, "RGB32") == 0 || strcasecmp(q, "RGB") == 0) { video.yuvlayout = VIDEO_YUV_RGB32; } else if (sscanf(q, "%x", &video.yuvlayout) != 1) { video.yuvlayout = 0; } } q = getenv("SCHISM_DEBUG"); if (q && strstr(q,"doublebuf")) { video.desktop.doublebuf = 1; } video.desktop.want_type = VIDEO_SURFACE; #ifdef WIN32 if (!driver) { /* alright, let's have some fun. */ driver = "sdlauto"; /* err... */ } 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"); } #elif defined(GEKKO) if (!driver) { driver = "yuv"; } #else if (!driver) { if (getenv("DISPLAY")) { driver = "x11"; } else { driver = "sdlauto"; } } #endif video.desktop.fb_hacks = 0; /* get xv info */ if (video.yuvlayout == VIDEO_YUV_NONE) video.yuvlayout = os_yuvlayout(); if ((video.yuvlayout != VIDEO_YUV_YV12_TV && video.yuvlayout != VIDEO_YUV_IYUV_TV && video.yuvlayout != VIDEO_YUV_NONE && 0) /* don't do this until we figure out how to make it better */ && !strcasecmp(driver, "x11")) { video.desktop.want_type = VIDEO_YUV; putenv((char *) "SDL_VIDEO_YUV_DIRECT=1"); putenv((char *) "SDL_VIDEO_YUV_HWACCEL=1"); putenv((char *) "SDL_VIDEODRIVER=x11"); #ifdef USE_X11 } else if (!strcasecmp(driver, "dga")) { putenv((char *) "SDL_VIDEODRIVER=dga"); video.desktop.want_type = VIDEO_SURFACE; } else if (!strcasecmp(driver, "directfb")) { putenv((char *) "SDL_VIDEODRIVER=directfb"); video.desktop.want_type = VIDEO_SURFACE; #endif #if HAVE_LINUX_FB_H } else if (!strcasecmp(driver, "fbcon") || !strcasecmp(driver, "linuxfb") || !strcasecmp(driver, "fb")) { unset_env_var("DISPLAY"); putenv((char *) "SDL_VIDEODRIVER=fbcon"); video.desktop.want_type = VIDEO_SURFACE; } else if (strncmp(driver, "/dev/fb", 7) == 0) { unset_env_var("DISPLAY"); putenv((char *) "SDL_VIDEODRIVER=fbcon"); put_env_var("SDL_FBDEV", driver); video.desktop.want_type = VIDEO_SURFACE; #endif } else if (!strcasecmp(driver, "aalib") || !strcasecmp(driver, "aa")) { /* SDL needs to have been built this way... */ unset_env_var("DISPLAY"); putenv((char *) "SDL_VIDEODRIVER=aalib"); video.desktop.want_type = VIDEO_SURFACE; video.desktop.fb_hacks = 1; } else if (video.yuvlayout != VIDEO_YUV_NONE && !strcasecmp(driver, "yuv")) { video.desktop.want_type = VIDEO_YUV; putenv((char *) "SDL_VIDEO_YUV_DIRECT=1"); putenv((char *) "SDL_VIDEO_YUV_HWACCEL=1"); /* leave everything else alone... */ } else if (!strcasecmp(driver, "dummy") || !strcasecmp(driver, "null") || !strcasecmp(driver, "none")) { unset_env_var("DISPLAY"); putenv((char *) "SDL_VIDEODRIVER=dummy"); video.desktop.want_type = VIDEO_SURFACE; video.desktop.fb_hacks = 1; #if HAVE_SIGNAL_H signal(SIGINT, SIG_DFL); #endif } else if (!strcasecmp(driver, "gl") || !strcasecmp(driver, "opengl")) { video.desktop.want_type = VIDEO_GL; } else if (!strcasecmp(driver, "sdlauto") || !strcasecmp(driver,"sdl")) { video.desktop.want_type = VIDEO_SURFACE; } } void video_startup(void) { UNUSED static int did_this_2 = 0; #if USE_OPENGL const char *gl_ext; #endif char *q; SDL_Rect **modes; int i, j, x, y; /* get monitor native res (assumed to be user's desktop res) * first time we start video */ if (display_native_x < 0 || display_native_y < 0) { const SDL_VideoInfo* info = SDL_GetVideoInfo(); display_native_x = info->current_w; display_native_y = info->current_h; } /* because first mode is 0 */ vgamem_clear(); vgamem_flip(); SDL_WM_SetCaption("Schism Tracker", "Schism Tracker"); #ifndef MACOSX /* apple/macs use a bundle; this overrides their nice pretty icon */ #ifdef WIN32 /* win32 icons must be 32x32 according to SDL 1.2 doc */ SDL_Surface *icon = xpmdata(_schism_icon_xpm); #else SDL_Surface *icon = xpmdata(_schism_icon_xpm_hires); #endif SDL_WM_SetIcon(icon, NULL); SDL_FreeSurface(icon); #endif #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) { #if defined(USE_OPENGL) video.surface = SDL_SetVideoMode(640,400,0,SDL_OPENGL|SDL_RESIZABLE); if (!video.surface) { /* fallback */ video.desktop.want_type = VIDEO_SURFACE; } video.gl.framebuf = NULL; 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") != NULL); video.gl.paletted_texture=(strstr(gl_ext,"EXT_paletted_texture") != NULL); #if defined(NVIDIA_PixelDataRange) video.gl.pixel_data_range=(strstr(gl_ext,"GL_NV_pixel_data_range") != NULL) && glPixelDataRangeNV && db_glAllocateMemoryNV && db_glFreeMemoryNV; #endif #endif // USE_OPENGL } x = y = -1; if ((q = getenv("SCHISM_VIDEO_RESOLUTION"))) { i = j = -1; if (sscanf(q,"%dx%d", &i,&j) == 2 && i >= 10 && j >= 10) { x = i; y = j; } } #if HAVE_LINUX_FB_H if (!getenv("DISPLAY") && !video.desktop.fb_hacks) { 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 { if (x < 0 || y < 0) { x = s.xres; if (x < NATIVE_SCREEN_WIDTH) x = NATIVE_SCREEN_WIDTH; y = s.yres; } putenv((char *) "SDL_VIDEODRIVER=fbcon"); video.desktop.bpp = s.bits_per_pixel; video.desktop.fb_hacks = 1; video.desktop.doublebuf = 1; video.desktop.fullscreen = 0; video.desktop.swsurface = 0; video.surface = SDL_SetVideoMode(x,y, video.desktop.bpp, SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_ASYNCBLIT); } close(fb); } } #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; if (x < 0 || y < 0) { modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE); if (modes != (SDL_Rect**)0 && modes != (SDL_Rect**)-1) { for (i = 0; modes[i]; i++) { if (modes[i]->w < NATIVE_SCREEN_WIDTH) continue; if (modes[i]->h < NATIVE_SCREEN_HEIGHT)continue; if (x == -1 || y == -1 || modes[i]->w < x || modes[i]->h < y) { if (modes[i]->w != NATIVE_SCREEN_WIDTH || modes[i]->h != NATIVE_SCREEN_HEIGHT) { if (x == NATIVE_SCREEN_WIDTH || y == NATIVE_SCREEN_HEIGHT) continue; } x = modes[i]->w; y = modes[i]->h; if (best_resolution(x,y)) break; } } } } if (x < 0 || y < 0) { x = 640; y = 480; } video.desktop.want_fixed = -1; q = getenv("SCHISM_VIDEO_ASPECT"); if (q && (strcasecmp(q,"nofixed") == 0 || strcasecmp(q,"full")==0 || strcasecmp(q,"fit") == 0 || strcasecmp(q,"wide") == 0 || strcasecmp(q,"no-fixed") == 0)) video.desktop.want_fixed = 0; if ((q = getenv("SCHISM_VIDEO_DEPTH"))) { i=atoi(q); if (i == 32) video.desktop.bpp=32; else if (i == 24) video.desktop.bpp=24; else if (i == 16) video.desktop.bpp=16; else if (i == 8) video.desktop.bpp=8; } /*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_YUV: #ifdef MACOSX video.desktop.swsurface = 1; #else video.desktop.swsurface = 0; #endif break; case VIDEO_GL: #ifdef MACOSX video.desktop.swsurface = 1; #else video.desktop.swsurface = 0; #endif video.gl.bilinear = cfg_video_gl_bilinear; if (video.desktop.bpp == 32 || video.desktop.bpp == 16) 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 */ video.desktop.swsurface = 1; video.desktop.want_type = VIDEO_SURFACE; break; }; /* okay, i think we're ready */ SDL_ShowCursor(SDL_DISABLE); #if 0 /* Do we need these? Everything seems to work just fine without */ 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); #endif _did_init = 1; video_fullscreen(video.desktop.fullscreen); } static SDL_Surface *_setup_surface(unsigned int w, unsigned int h, unsigned int sdlflags) { int want_fixed = video.desktop.want_fixed; 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 (want_fixed == -1 && best_resolution(w,h)) { want_fixed = 0; } if (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.fb_hacks && video.surface) { /* the original one will be _just fine_ */ } else { if (video.desktop.fullscreen) { sdlflags &=~SDL_RESIZABLE; sdlflags |= SDL_FULLSCREEN; } else { sdlflags &=~SDL_FULLSCREEN; sdlflags |= SDL_RESIZABLE; } sdlflags |= (video.desktop.swsurface ? SDL_SWSURFACE : SDL_HWSURFACE); /* if using swsurface, get a surface the size of the whole native monitor res /* to avoid issues with weirdo display modes /* get proper aspect ratio and surface of correct size */ if (video.desktop.fullscreen && video.desktop.swsurface) { double ar = NATIVE_SCREEN_WIDTH / (double) NATIVE_SCREEN_HEIGHT; // ar = 4.0 / 3.0; want_fixed = 1; // uncomment for 4:3 fullscreen // get maximum size that can be this AR if ((display_native_y * ar) > display_native_x) { video.clip.h = display_native_x / ar; video.clip.w = display_native_x; } else { video.clip.h = display_native_y; video.clip.w = display_native_y * ar; } // clip to size (i.e. letterbox if necessary) video.clip.x = (display_native_x - video.clip.w) / 2; video.clip.y = (display_native_y - video.clip.h) / 2; // get a surface the size of the whole screen @ native res w = display_native_x; h = display_native_y; /* if we don't care about getting the right aspect ratio, /* sod letterboxing and just get a surface the size of the entire display */ if (!want_fixed) { video.clip.w = display_native_x; video.clip.h = display_native_y; video.clip.x = 0; video.clip.y = 0; } } video.surface = SDL_SetVideoMode(w, h, video.desktop.bpp, sdlflags); } if (!video.surface) { perror("SDL_SetVideoMode"); exit(EXIT_FAILURE); } return video.surface; } #if USE_OPENGL static void _set_gl_attributes(void) { SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 32); SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, 0); #if SDL_VERSION_ATLEAST(1,2,11) SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 0); #endif } #endif void video_resize(unsigned int width, unsigned int height) { #if USE_OPENGL GLfloat tex_width, tex_height; int texsize; #endif if (!width) width = cfg_video_width; if (!height) height = cfg_video_height; 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); 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; if (video.desktop.fb_hacks && (video.desktop.width != NATIVE_SCREEN_WIDTH || video.desktop.height != NATIVE_SCREEN_HEIGHT)) { video.draw.autoscale = 1; } _setup_surface(width, height, 0); video.desktop.type = VIDEO_SURFACE; break; case VIDEO_YUV: if (video.overlay) { SDL_FreeYUVOverlay(video.overlay); video.overlay = NULL; } _setup_surface(width, height, 0); /* TODO: switch? */ switch (video.yuvlayout) { case VIDEO_YUV_YV12_TV: video.overlay = SDL_CreateYUVOverlay (NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, SDL_YV12_OVERLAY, video.surface); break; case VIDEO_YUV_IYUV_TV: video.overlay = SDL_CreateYUVOverlay (NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, SDL_IYUV_OVERLAY, video.surface); break; case VIDEO_YUV_YV12: video.overlay = SDL_CreateYUVOverlay (2 * NATIVE_SCREEN_WIDTH, 2 * NATIVE_SCREEN_HEIGHT, SDL_YV12_OVERLAY, video.surface); break; case VIDEO_YUV_IYUV: video.overlay = SDL_CreateYUVOverlay (2 * NATIVE_SCREEN_WIDTH, 2 * NATIVE_SCREEN_HEIGHT, SDL_IYUV_OVERLAY, video.surface); break; case VIDEO_YUV_UYVY: video.overlay = SDL_CreateYUVOverlay (2 * NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, SDL_UYVY_OVERLAY, video.surface); break; case VIDEO_YUV_YVYU: video.overlay = SDL_CreateYUVOverlay (2 * NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, SDL_YVYU_OVERLAY, video.surface); break; case VIDEO_YUV_YUY2: video.overlay = SDL_CreateYUVOverlay (2 * NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, SDL_YUY2_OVERLAY, video.surface); break; case VIDEO_YUV_RGBA: case VIDEO_YUV_RGB32: video.overlay = SDL_CreateYUVOverlay (4*NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, video.yuvlayout, video.surface); break; case VIDEO_YUV_RGB24: video.overlay = SDL_CreateYUVOverlay (3*NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, video.yuvlayout, video.surface); break; case VIDEO_YUV_RGBT: case VIDEO_YUV_RGB565: video.overlay = SDL_CreateYUVOverlay (2*NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, video.yuvlayout, video.surface); break; default: /* unknown layout */ goto RETRYSURF; } if (!video.overlay) { /* can't get an overlay */ goto RETRYSURF; } switch (video.overlay->planes) { case 3: case 1: break; default: /* can't get a recognized planes */ SDL_FreeYUVOverlay(video.overlay); video.overlay = NULL; goto RETRYSURF; }; video.desktop.type = VIDEO_YUV; break; #if defined(USE_OPENGL) case VIDEO_GL: _set_gl_attributes(); _setup_surface(width, height, SDL_OPENGL); if (video.surface->format->BitsPerPixel < 15) { goto RETRYSURF; } /* grumble... */ _set_gl_attributes(); 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 = 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, NULL); 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; break; #endif // USE_OPENGL }; status.flags |= (NEED_UPDATE); } static void _make_yuv(unsigned int *y, unsigned int *u, unsigned int *v, int rgb[3]) { double red, green, blue, yy, cr, cb, ry, ru, rv; int r = rgb[0]; int g = rgb[1]; int b = rgb[2]; red = (double)r / 255.0; green = (double)g / 255.0; blue = (double)b / 255.0; yy = 0.299 * red + 0.587 * green + 0.114 * blue; cb = blue - yy; cr = red - yy; ry = 16.0 + 219.0 * yy; ru = 128.0 + 126.0 * cb; rv = 128.0 + 160.0 * cr; *y = (uint8_t) ry; *u = (uint8_t) ru; *v = (uint8_t) rv; } static void _yuv_pal(int i, int rgb[3]) { unsigned int y,u,v; _make_yuv(&y, &u, &v, rgb); switch (video.yuvlayout) { /* planar modes */ case VIDEO_YUV_YV12: case VIDEO_YUV_IYUV: /* this is fake; we simply record the infomration here */ video.yuv_y[i] = y|(y<<8); video.yuv_u[i] = u; video.yuv_v[i] = v; break; /* tv planar modes */ case VIDEO_YUV_YV12_TV: case VIDEO_YUV_IYUV_TV: /* _blitTV */ video.yuv_y[i] = y; video.yuv_u[i] = (u >> 4) & 0xF; video.yuv_v[i] = (v >> 4) & 0xF; break; /* packed modes */ case VIDEO_YUV_YVYU: /* y0 v0 y1 u0 */ #if SDL_BYTEORDER == SDL_BIG_ENDIAN video.pal[i] = u | (y << 8) | (v << 16) | (y << 24); #else video.pal[i] = y | (v << 8) | (y << 16) | (u << 24); #endif break; case VIDEO_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 VIDEO_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; case VIDEO_YUV_RGBA: case VIDEO_YUV_RGB32: video.pal[i] = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16) | (255 << 24); break; case VIDEO_YUV_RGB24: video.pal[i] = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16); break; case VIDEO_YUV_RGBT: video.pal[i] = ((rgb[0] << 8) & 0x7c00) | ((rgb[1] << 3) & 0x3e0) | ((rgb[2] >> 2) & 0x1f); break; case VIDEO_YUV_RGB565: video.pal[i] = ((rgb[0] << 9) & 0xf800) | ((rgb[1] << 4) & 0x7e0) | ((rgb[2] >> 2) & 0x1f); break; }; } static void _sdl_pal(int i, int rgb[3]) { video.pal[i] = SDL_MapRGB(video.surface->format, rgb[0], rgb[1], rgb[2]); } static void _bgr32_pal(int i, int rgb[3]) { video.tc_bgr32[i] = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16) | (255 << 24); } static void _gl_pal(int i, int rgb[3]) { video.pal[i] = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16) | (255 << 24); } void video_colors(unsigned char palette[16][3]) { static SDL_Color imap[16]; void (*fun)(int i,int rgb[3]); const int lastmap[] = { 0,1,2,3,5 }; int rgb[3], i, j, p; switch (video.desktop.type) { case VIDEO_SURFACE: if (video.surface->format->BytesPerPixel == 1) { const int depthmap[] = { 0, 15,14,7, 8, 8, 9, 12, 6, 1, 2, 2, 10, 3, 11, 11 }; /* 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]; rgb[0]=palette[i][0]; rgb[1]=palette[i][1]; rgb[2]=palette[i][2]; _bgr32_pal(i, rgb); } for (i = 128; i < 256; i++) { video.pal[i] = depthmap[(i>>4)]; } for (i = 128; i < 256; i++) { j = i - 128; p = lastmap[(j>>5)]; rgb[0] = (int)palette[p][0] + (((int)(palette[p+1][0] - palette[p][0]) * (j&31)) /32); rgb[1] = (int)palette[p][1] + (((int)(palette[p+1][1] - palette[p][1]) * (j&31)) /32); rgb[2] = (int)palette[p][2] + (((int)(palette[p+1][2] - palette[p][2]) * (j&31)) /32); _bgr32_pal(i, rgb); } SDL_SetColors(video.surface, imap, 0, 16); return; } /* fall through */ case VIDEO_DDRAW: fun = _sdl_pal; break; case VIDEO_YUV: fun = _yuv_pal; break; case VIDEO_GL: fun = _gl_pal; break; default: /* eh? */ return; }; /* make our "base" space */ for (i = 0; i < 16; i++) { rgb[0]=palette[i][0]; rgb[1]=palette[i][1]; rgb[2]=palette[i][2]; fun(i, rgb); _bgr32_pal(i, rgb); } /* make our "gradient" space */ for (i = 128; i < 256; i++) { j = i - 128; p = lastmap[(j>>5)]; rgb[0] = (int)palette[p][0] + (((int)(palette[p+1][0] - palette[p][0]) * (j&31)) /32); rgb[1] = (int)palette[p][1] + (((int)(palette[p+1][1] - palette[p][1]) * (j&31)) /32); rgb[2] = (int)palette[p][2] + (((int)(palette[p+1][2] - palette[p][2]) * (j&31)) /32); fun(i, rgb); _bgr32_pal(i, rgb); } } void video_refresh(void) { vgamem_flip(); vgamem_clear(); } static inline void make_mouseline(unsigned int x, unsigned int v, unsigned int y, unsigned int mouseline[80]) { unsigned int z; memset(mouseline, 0, 80*sizeof(unsigned int)); if (video.mouse.visible != MOUSE_EMULATED || !(status.flags & IS_FOCUSED) || y < video.mouse.y || y >= video.mouse.y+MOUSE_HEIGHT) { return; } z = _mouse_pointer[ y - video.mouse.y ]; mouseline[x] = z >> v; if (x < 79) mouseline[x+1] = (z << (8-v)) & 0xff; } #define FIXED_BITS 8 #define FIXED_MASK ((1 << FIXED_BITS) - 1) #define ONE_HALF_FIXED (1 << (FIXED_BITS - 1)) #define INT2FIXED(x) ((x) << FIXED_BITS) #define FIXED2INT(x) ((x) >> FIXED_BITS) #define FRAC(x) ((x) & FIXED_MASK) static void _blit1n(int bpp, unsigned char *pixels, unsigned int pitch) { unsigned int *csp, *esp, *dp; unsigned int c00, c01, c10, c11; unsigned int outr, outg, outb; unsigned int pad; int fixedx, fixedy, scalex, scaley; unsigned int y, x,ey,ex,t1,t2; unsigned int mouseline[80]; unsigned int mouseline_x, mouseline_v; int iny, lasty; mouseline_x = (video.mouse.x / 8); mouseline_v = (video.mouse.x % 8); csp = (unsigned int *)video.cv32backing; esp = csp + NATIVE_SCREEN_WIDTH; lasty = -2; iny = 0; pad = pitch - (video.clip.w * bpp); scalex = INT2FIXED(NATIVE_SCREEN_WIDTH-1) / video.clip.w; scaley = INT2FIXED(NATIVE_SCREEN_HEIGHT-1) / video.clip.h; for (y = 0, fixedy = 0; (y < video.clip.h); y++, fixedy += scaley) { iny = FIXED2INT(fixedy); if (iny != lasty) { make_mouseline(mouseline_x, mouseline_v, iny, mouseline); /* we'll downblit the colors later */ if (iny == lasty + 1) { /* move up one line */ vgamem_scan32(iny+1, csp, video.tc_bgr32, mouseline); dp = esp; esp = csp; csp=dp; } else { vgamem_scan32(iny, (csp = (unsigned int *)video.cv32backing), video.tc_bgr32, mouseline); vgamem_scan32(iny+1, (esp = (csp + NATIVE_SCREEN_WIDTH)), video.tc_bgr32, mouseline); } lasty = iny; } for (x = 0, fixedx = 0; x < video.clip.w; x++, fixedx += scalex) { ex = FRAC(fixedx); ey = FRAC(fixedy); c00 = csp[FIXED2INT(fixedx)]; c01 = csp[FIXED2INT(fixedx) + 1]; c10 = esp[FIXED2INT(fixedx)]; c11 = esp[FIXED2INT(fixedx) + 1]; #if FIXED_BITS <= 8 /* When there are enough bits between blue and * red, do the RB channels together * See http://www.virtualdub.org/blog/pivot/entry.php?id=117 * for a quick explanation */ #define REDBLUE(Q) ((Q) & 0x00FF00FF) #define GREEN(Q) ((Q) & 0x0000FF00) t1 = REDBLUE((((REDBLUE(c01)-REDBLUE(c00))*ex) >> FIXED_BITS)+REDBLUE(c00)); t2 = REDBLUE((((REDBLUE(c11)-REDBLUE(c10))*ex) >> FIXED_BITS)+REDBLUE(c10)); outb = ((((t2-t1)*ey) >> FIXED_BITS) + t1); t1 = GREEN((((GREEN(c01)-GREEN(c00))*ex) >> FIXED_BITS)+GREEN(c00)); t2 = GREEN((((GREEN(c11)-GREEN(c10))*ex) >> FIXED_BITS)+GREEN(c10)); outg = (((((t2-t1)*ey) >> FIXED_BITS) + t1) >> 8) & 0xFF; outr = (outb >> 16) & 0xFF; outb &= 0xFF; #undef REDBLUE #undef GREEN #else #define BLUE(Q) (Q & 255) #define GREEN(Q) ((Q >> 8) & 255) #define RED(Q) ((Q >> 16) & 255) t1 = ((((BLUE(c01)-BLUE(c00))*ex) >> FIXED_BITS)+BLUE(c00)) & 0xFF; t2 = ((((BLUE(c11)-BLUE(c10))*ex) >> FIXED_BITS)+BLUE(c10)) & 0xFF; outb = ((((t2-t1)*ey) >> FIXED_BITS) + t1); t1 = ((((GREEN(c01)-GREEN(c00))*ex) >> FIXED_BITS)+GREEN(c00)) & 0xFF; t2 = ((((GREEN(c11)-GREEN(c10))*ex) >> FIXED_BITS)+GREEN(c10)) & 0xFF; outg = ((((t2-t1)*ey) >> FIXED_BITS) + t1); t1 = ((((RED(c01)-RED(c00))*ex) >> FIXED_BITS)+RED(c00)) & 0xFF; t2 = ((((RED(c11)-RED(c10))*ex) >> FIXED_BITS)+RED(c10)) & 0xFF; outr = ((((t2-t1)*ey) >> FIXED_BITS) + t1); #undef RED #undef GREEN #undef BLUE #endif /* write output "pixel" */ switch (bpp) { case 4: /* inline MapRGB */ (*(unsigned int *)pixels) = 0xFF000000 | (outr << 16) | (outg << 8) | outb; break; case 3: /* inline MapRGB */ (*(unsigned int *)pixels) = (outr << 16) | (outg << 8) | outb; break; case 2: /* inline MapRGB if possible */ if (video.surface->format->palette) { /* err... */ (*(unsigned short *)pixels) = SDL_MapRGB( video.surface->format, outr, outg, outb); } else if (video.surface->format->Gloss == 2) { /* RGB565 */ (*(unsigned short *)pixels) = ((outr << 8) & 0xF800) | ((outg << 3) & 0x07E0) | (outb >> 3); } else { /* RGB555 */ (*(unsigned short *)pixels) = 0x8000 | ((outr << 7) & 0x7C00) | ((outg << 2) & 0x03E0) | (outb >> 3); } break; case 1: /* er... */ (*pixels) = SDL_MapRGB( video.surface->format, outr, outg, outb); break; }; pixels += bpp; } pixels += pad; } } static void _blitYY(unsigned char *pixels, unsigned int pitch, unsigned int *tpal) { unsigned int mouseline_x = (video.mouse.x / 8); unsigned int mouseline_v = (video.mouse.x % 8); unsigned int mouseline[80]; int y; for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { make_mouseline(mouseline_x, mouseline_v, y, mouseline); vgamem_scan16(y, (unsigned short *)pixels, tpal, mouseline); memcpy(pixels+pitch,pixels,pitch); pixels += pitch; pixels += pitch; } } static void _blitUV(unsigned char *pixels, unsigned int pitch, unsigned int *tpal) { unsigned int mouseline_x = (video.mouse.x / 8); unsigned int mouseline_v = (video.mouse.x % 8); unsigned int mouseline[80]; int y; for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { make_mouseline(mouseline_x, mouseline_v, y, mouseline); vgamem_scan8(y, (unsigned char *)pixels, tpal, mouseline); pixels += pitch; } } static void _blitTV(unsigned char *pixels, UNUSED unsigned int pitch, unsigned int *tpal) { unsigned int mouseline_x = (video.mouse.x / 8); unsigned int mouseline_v = (video.mouse.x % 8); unsigned int mouseline[80]; int y, x; for (y = 0; y < NATIVE_SCREEN_HEIGHT; y += 2) { make_mouseline(mouseline_x, mouseline_v, y, mouseline); vgamem_scan8(y, (unsigned char *)video.cv8backing, tpal, mouseline); for (x = 0; x < NATIVE_SCREEN_WIDTH; x += 2) { *pixels++ = video.cv8backing[x+1] | (video.cv8backing[x] << 4); } } } static void _blit11(int bpp, unsigned char *pixels, unsigned int pitch, unsigned int *tpal) { unsigned int mouseline_x = (video.mouse.x / 8); unsigned int mouseline_v = (video.mouse.x % 8); unsigned int mouseline[80]; unsigned char *pdata; unsigned int x, y; int pitch24; switch (bpp) { case 4: for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { make_mouseline(mouseline_x, mouseline_v, y, mouseline); vgamem_scan32(y, (unsigned int *)pixels, tpal, mouseline); pixels += pitch; } break; case 3: /* ... */ pitch24 = pitch - (NATIVE_SCREEN_WIDTH * 3); if (pitch24 < 0) { return; /* eh? */ } for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { make_mouseline(mouseline_x, mouseline_v, y, mouseline); vgamem_scan32(y,(unsigned int*)video.cv32backing,tpal, mouseline); /* 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++) { make_mouseline(mouseline_x, mouseline_v, y, mouseline); vgamem_scan16(y, (unsigned short *)pixels, tpal, mouseline); pixels += pitch; } break; case 1: for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { make_mouseline(mouseline_x, mouseline_v, y, mouseline); vgamem_scan8(y, (unsigned char *)pixels, tpal, mouseline); pixels += pitch; } break; }; } static void _video_blit_planar(void) { SDL_LockYUVOverlay(video.overlay); vgamem_lock(); switch (video.yuvlayout) { case VIDEO_YUV_YV12_TV: /* halfwidth Y+V+U */ _blitUV(video.overlay->pixels[0], video.overlay->pitches[0], video.yuv_y); _blitTV(video.overlay->pixels[1], video.overlay->pitches[1], video.yuv_v); _blitTV(video.overlay->pixels[2], video.overlay->pitches[2], video.yuv_u); break; case VIDEO_YUV_IYUV_TV: /* halfwidth Y+U+V */ _blitUV(video.overlay->pixels[0], video.overlay->pitches[0], video.yuv_y); _blitTV(video.overlay->pixels[1], video.overlay->pitches[1], video.yuv_u); _blitTV(video.overlay->pixels[2], video.overlay->pitches[2], video.yuv_v); break; case VIDEO_YUV_YV12: /* Y+V+U */ _blitYY(video.overlay->pixels[0], video.overlay->pitches[0], video.yuv_y); _blitUV(video.overlay->pixels[1], video.overlay->pitches[1], video.yuv_v); _blitUV(video.overlay->pixels[2], video.overlay->pitches[2], video.yuv_u); break; case VIDEO_YUV_IYUV: /* Y+U+V */ _blitYY(video.overlay->pixels[0], video.overlay->pitches[0], video.yuv_y); _blitUV(video.overlay->pixels[1], video.overlay->pitches[1], video.yuv_u); _blitUV(video.overlay->pixels[2], video.overlay->pitches[2], video.yuv_v); break; }; vgamem_unlock(); SDL_UnlockYUVOverlay(video.overlay); SDL_DisplayYUVOverlay(video.overlay, &video.clip); } void video_blit(void) { unsigned char *pixels = NULL; unsigned int bpp = 0; unsigned int pitch = 0; 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: if (video.overlay->planes == 3) { _video_blit_planar(); return; } SDL_LockYUVOverlay(video.overlay); pixels = (unsigned char *)*(video.overlay->pixels); pitch = *(video.overlay->pitches); switch (video.yuvlayout) { case VIDEO_YUV_RGBT: bpp = 2; break; case VIDEO_YUV_RGB565: bpp = 2; break; case VIDEO_YUV_RGB24: bpp = 3; break; default: 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.clip.w == NATIVE_SCREEN_WIDTH && video.clip.h == NATIVE_SCREEN_HEIGHT)) { /* scaling is provided by the hardware, or isn't necessary */ _blit11(bpp, pixels, pitch, video.pal); } 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; #if defined(USE_OPENGL) 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; #endif }; } int video_mousecursor_visible(void) { return video.mouse.visible; } void video_mousecursor(int vis) { const char *state[] = { "Mouse disabled", "Software mouse cursor enabled", "Hardware mouse cursor enabled", }; if (status.flags & NO_MOUSE) { // disable it no matter what video.mouse.visible = MOUSE_DISABLED; //SDL_ShowCursor(0); return; } switch (vis) { case MOUSE_CYCLE_STATE: vis = (video.mouse.visible + 1) % MOUSE_CYCLE_STATE; /* fall through */ case MOUSE_DISABLED: case MOUSE_SYSTEM: case MOUSE_EMULATED: video.mouse.visible = vis; status_text_flash("%s", state[video.mouse.visible]); case MOUSE_RESET_STATE: break; default: video.mouse.visible = MOUSE_EMULATED; } SDL_ShowCursor(video.mouse.visible == MOUSE_SYSTEM); // Totally turn off mouse event sending when the mouse is disabled int evstate = video.mouse.visible == MOUSE_DISABLED ? SDL_DISABLE : SDL_ENABLE; if (evstate != SDL_EventState(SDL_MOUSEMOTION, SDL_QUERY)) { SDL_EventState(SDL_MOUSEMOTION, evstate); SDL_EventState(SDL_MOUSEBUTTONDOWN, evstate); SDL_EventState(SDL_MOUSEBUTTONUP, evstate); } } int video_gl_bilinear(void) { return video.gl.bilinear; } void video_translate(unsigned int vx, unsigned int vy, unsigned int *x, unsigned int *y) { if ((signed) vx < video.clip.x) vx = video.clip.x; vx -= video.clip.x; if ((signed) vy < video.clip.y) vy = video.clip.y; vy -= video.clip.y; if ((signed) vx > video.clip.w) vx = video.clip.w; if ((signed) 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; } schismtracker-20180209/schism/volume-core.c000066400000000000000000000051671323741476300205350ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" #include "sdlmain.h" #include "osdefs.h" static int (*__volume_get_max)(void) = NULL; static void (*__volume_read)(int *left, int *right) = NULL; static void (*__volume_write)(int left, int right) = NULL; void volume_setup(void) { char *drv, drv_buf[256]; drv = SDL_AudioDriverName(drv_buf,sizeof(drv_buf)); #ifdef USE_ALSA if ((!drv && !__volume_get_max) || (drv && (!strcmp(drv, "alsa")))) { __volume_get_max = alsa_volume_get_max; __volume_read = alsa_volume_read; __volume_write = alsa_volume_write; } #endif #ifdef USE_OSS if ((!drv && !__volume_get_max) || (drv && (!strcmp(drv, "oss") || !strcmp(drv, "dsp")))) { __volume_get_max = oss_volume_get_max; __volume_read = oss_volume_read; __volume_write = oss_volume_write; } #endif #ifdef MACOSX if ((!drv && !__volume_get_max) || (drv && (!strcmp(drv, "coreaudio") || !strcmp(drv, "macosx")))) { __volume_get_max = macosx_volume_get_max; __volume_read = macosx_volume_read; __volume_write = macosx_volume_write; } #endif #ifdef WIN32 if ((!drv && !__volume_get_max) || (drv && (!strcmp(drv, "waveout") || !strcmp(drv, "dsound")))) { __volume_get_max = win32mm_volume_get_max; __volume_read = win32mm_volume_read; __volume_write = win32mm_volume_write; } #endif } int volume_get_max(void) { if (__volume_get_max) return __volume_get_max(); return 1; /* Can't return 0, that breaks things. */ } void volume_read(int *left, int *right) { if (__volume_read) __volume_read(left,right); else { *left=0; *right=0; } } void volume_write(int left, int right) { if (__volume_write) __volume_write(left,right); } schismtracker-20180209/schism/widget-keyhandler.c000066400000000000000000000512271323741476300217050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "song.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_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; } static void bitset_move_cursor(struct widget *widget, int n) { n += *widget->d.bitset.cursor_pos; n = CLAMP(n, 0, widget->d.bitset.nbits-1); if (*widget->d.bitset.cursor_pos == n) return; *widget->d.bitset.cursor_pos = n; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* thumbbar value prompt */ static void thumbbar_prompt_finish(int n) { 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 int thumbbar_prompt_value(struct widget *widget, struct key_event *k) { int c; if (!NO_MODIFIER(k->mod)) { /* annoying */ return 0; } if (k->sym == SDLK_MINUS) { if (widget->d.thumbbar.min >= 0) return 0; c = '-'; } else { c = numeric_key_event(k, 0); if (c < 0) return 0; c += '0'; } numprompt_create("Enter Value", thumbbar_prompt_finish, c); 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; if (!widget) return 0; int n, onw, wx, fmin, fmax, pad; void (*changed)(void); 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 = MOUSE_NONE; } else if (k->mouse_button == MOUSE_BUTTON_RIGHT) { k->sym = SDLK_PLUS; k->mouse = MOUSE_NONE; } break; default: break; }; } if (k->mouse == MOUSE_CLICK) { if (status.flags & DISKWRITER_ACTIVE) return 0; switch (current_type) { case WIDGET_TOGGLE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; 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 (k->state == KEY_RELEASE) return 1; 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; } } else if (k->mouse == MOUSE_DBLCLICK) { if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR) { if (!NO_MODIFIER(k->mod)) return 0; widget->d.panbar.muted = !widget->d.panbar.muted; changed = widget->changed; if (changed) changed(); return 1; } } if (k->mouse == MOUSE_CLICK || (k->mouse == MOUSE_NONE && k->sym == SDLK_RETURN)) { #if 0 if (k->mouse && k->mouse_button == MOUSE_BUTTON_MIDDLE) { if (status.flags & DISKWRITER_ACTIVE) return 0; if (k->state == KEY_PRESS) return 1; status.flags |= CLIPPY_PASTE_SELECTION; return 1; } #endif 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-1) * 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) { switch (widget->type) { case WIDGET_BUTTON: pad = widget->d.button.padding+1; break; case WIDGET_TOGGLEBUTTON: pad = widget->d.togglebutton.padding+1; break; default: pad = 0; }; onw = ((signed) k->x < widget->x || (signed) k->x >= widget->x + widget->width + pad || (signed) k->y != widget->y) ? 0 : 1; n = (k->state == KEY_RELEASE && onw) ? 1 : 0; if (widget->depressed != n) status.flags |= NEED_UPDATE; widget->depressed = n; if (current_type != WIDGET_TEXTENTRY && current_type != WIDGET_NUMENTRY) { if (k->state == KEY_PRESS || !onw) return 1; } else if (!onw) { return 1; } } else { n = (k->state == KEY_PRESS) ? 1 : 0; if (widget->depressed != n) status.flags |= NEED_UPDATE; else if (k->state == KEY_RELEASE) return 1; // swallor widget->depressed = n; if (k->state == KEY_PRESS) 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(); default: break; }; } 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; /* LOL WOW THIS SUCKS */ if (k->mouse == MOUSE_CLICK && k->on_target) { /* position cursor */ n = k->x - widget->x; n = CLAMP(n, 0, widget->width - 1); wx = k->sx - widget->x; wx = CLAMP(wx, 0, 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 >= (signed) strlen(widget->d.textentry.text)) widget->d.textentry.cursor_pos = strlen(widget->d.textentry.text); if (wx >= (signed) strlen(widget->d.textentry.text)) wx = strlen(widget->d.textentry.text); 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_NUMENTRY: if (status.flags & DISKWRITER_ACTIVE) return 0; if (k->mouse == MOUSE_CLICK && k->on_target) { /* position cursor */ n = k->x - widget->x; n = CLAMP(n, 0, widget->width - 1); wx = k->sx - widget->x; wx = CLAMP(wx, 0, widget->width - 1); if (n >= widget->width) n = widget->width-1; *widget->d.numentry.cursor_pos = n; status.flags |= NEED_UPDATE; } break; 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; default: break; } return 0; } /* 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 == KEY_RELEASE) 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_BITSET: if (NO_MODIFIER(k->mod)) bitset_move_cursor(widget, -1); break; case WIDGET_NUMENTRY: if (!NO_MODIFIER(k->mod)) { return 0; } numentry_move_cursor(widget, -1); return 1; case WIDGET_TEXTENTRY: if (!NO_MODIFIER(k->mod)) { return 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_BITSET: if (NO_MODIFIER(k->mod)) bitset_move_cursor(widget, 1); break; case WIDGET_NUMENTRY: if (!NO_MODIFIER(k->mod)) { return 0; } numentry_move_cursor(widget, 1); return 1; case WIDGET_TEXTENTRY: if (!NO_MODIFIER(k->mod)) { return 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_BITSET: if (!NO_MODIFIER(k->mod)) return 0; widget->d.bitset.value ^= (1 << *widget->d.bitset.cursor_pos); if (widget->changed) widget->changed(); status.flags |= NEED_UPDATE; return 1; 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; changed = widget->changed; change_focus_to(widget->next.down); if (changed) changed(); 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) { if (k->mod & KMOD_ALT) { song_set_pan_scheme(PANS_LEFT); return 1; } else if (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) { if (k->mod & KMOD_ALT) { song_set_pan_scheme(PANS_MONO); return 1; } else if (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) { if (k->mod & KMOD_ALT) { song_set_pan_scheme(PANS_RIGHT); return 1; } else if (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) { if (k->mod & KMOD_ALT) { song_set_pan_scheme(PANS_STEREO); return 1; } else if(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; case SDLK_a: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR && (k->mod & KMOD_ALT)) { song_set_pan_scheme(PANS_AMIGA); return 1; } break; #if 0 case SDLK_x: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR && (k->mod & KMOD_ALT)) { song_set_pan_scheme(PANS_CROSS); return 1; } break; #endif case SDLK_SLASH: case SDLK_KP_DIVIDE: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR && (k->mod & KMOD_ALT)) { song_set_pan_scheme(PANS_SLASH); return 1; } break; case SDLK_BACKSLASH: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR && (k->mod & KMOD_ALT)) { song_set_pan_scheme(PANS_BACKSLASH); 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_MENUTOGGLE: if (menutoggle_handle_key(widget, k)) return 1; break; case WIDGET_BITSET: if (bitset_handle_key(widget, k)) return 1; break; 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; } schismtracker-20180209/schism/widget.c000066400000000000000000000432141323741476300175560ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" /* --------------------------------------------------------------------- */ /* 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->accept_text = 0; 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 *const *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->accept_text = 0; 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; w->d.menutoggle.activation_keys = 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->accept_text = 0; 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, const int *group) { w->type = WIDGET_TOGGLEBUTTON; w->accept_text = 0; 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->accept_text = 1; 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->accept_text = 1; 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->accept_text = 0; 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.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_bitset(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), int nbits, const char* bits_on, const char* bits_off, int *cursor_pos) { w->type = WIDGET_BITSET; w->accept_text = 0; 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.bitset.nbits = nbits; w->d.bitset.bits_on = bits_on; w->d.bitset.bits_off = bits_off; w->d.bitset.cursor_pos = cursor_pos; 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->accept_text = 0; 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->accept_text = 0; 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 < w->d.textentry.firstchar) { w->d.textentry.firstchar = w->d.textentry.cursor_pos; } else if (w->d.textentry.cursor_pos > len) { w->d.textentry.cursor_pos = len; } else 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_t unicode) { int 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 (w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } int menutoggle_handle_key(struct widget *w, struct key_event *k) { if( ((k->mod & (KMOD_CTRL | KMOD_ALT | KMOD_META)) == 0) && w->d.menutoggle.activation_keys) { const char* m = w->d.menutoggle.activation_keys; const char* p = strchr(m, (char)k->unicode); if(p && *p) { w->d.menutoggle.state = p - m; if(w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } } return 0; } int bitset_handle_key(struct widget *w, struct key_event *k) { if( ((k->mod & (KMOD_CTRL | KMOD_ALT | KMOD_META)) == 0) && w->d.bitset.activation_keys) { const char* m = w->d.bitset.activation_keys; const char* p = strchr(m, (char)k->unicode); if(p && *p) { int bit_index = p-m; w->d.bitset.value ^= (1 << bit_index); if(w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } } return 0; } /* --------------------------------------------------------------------- */ /* 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, 0); 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) { const int *group = p_widgets[widget].d.togglebutton.group; int i; if (!group) return; /* assert */ 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; 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 (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 && !drew_cursor) { 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 { if (w->d.numentry.min < 0 || w->d.numentry.max < 0) { numtostr_signed(w->width, w->d.numentry.value, buf); } else { numtostr(w->width, w->d.numentry.value, buf); } draw_text_len(buf, w->width, w->x, w->y, 2, 0); if (selected && !drew_cursor) { n = *(w->d.numentry.cursor_pos); draw_char(buf[n], w->x + n, w->y, 0, 3); } } break; case WIDGET_BITSET: for(n = 0; n < w->d.bitset.nbits; ++n) { int set = !!(w->d.bitset.value & (1 << n)); char label_c1 = set ? w->d.bitset.bits_on[n*2+0] : w->d.bitset.bits_off[n*2+0]; char label_c2 = set ? w->d.bitset.bits_on[n*2+1] : w->d.bitset.bits_off[n*2+1]; int is_focused = selected && n == *w->d.bitset.cursor_pos; /* In textentries, cursor=0,3; normal=2,0 */ static const char fg_selection[4] = { 2, /* not cursor, not set */ 3, /* not cursor, is set */ 0, /* has cursor, not set */ 0 /* has cursor, is set */ }; static const char bg_selection[4] = { 0, /* not cursor, not set */ 0, /* not cursor, is set */ 2, /* has cursor, not set */ 3 /* has cursor, is set */ }; fg = fg_selection[set + is_focused*2]; bg = bg_selection[set + is_focused*2]; if(label_c2) draw_half_width_chars(label_c1, label_c2, w->x + n, w->y, fg, bg, fg, bg); else draw_char(label_c1, w->x + n, w->y, fg, bg); } 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); } if (w->d.thumbbar.min < 0 || w->d.thumbbar.max < 0) { numtostr_signed(3, w->d.thumbbar.value, buf); } else { numtostr(3, w->d.thumbbar.value, buf); } draw_text(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) { if (ACTIVE_WIDGET.depressed) ACTIVE_WIDGET.depressed = 0; *selected_widget = new_widget_index; ACTIVE_WIDGET.depressed = 0; if (ACTIVE_WIDGET.type == WIDGET_TEXTENTRY) ACTIVE_WIDGET.d.textentry.cursor_pos = strlen(ACTIVE_WIDGET.d.textentry.text); status.flags |= NEED_UPDATE; } } static int _find_widget_xy(int x, int y) { struct widget *w; int i, pad; if (!total_widgets) return -1; for (i = 0; i < *total_widgets; i++) { w = widgets + i; switch (w->type) { case WIDGET_BUTTON: pad = w->d.button.padding + 1; break; case WIDGET_TOGGLEBUTTON: pad = w->d.togglebutton.padding + 1; break; default: pad = 0; } if (x >= w->x && x < w->x + w->width + pad && y >= w->y && y < w->y + w->height) { return i; } } return -1; } int change_focus_to_xy(int x, int y) { int n = _find_widget_xy(x, y); if (n >= 0) { change_focus_to(n); return 1; } return 0; } schismtracker-20180209/schism/xpmdata.c000066400000000000000000000217241323741476300177330ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #include "video.h" /* for declaration of xpmdata */ #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(const char *spec, int speclen, uint32_t *rgb) { /* poor man's rgb.txt */ static struct { const char *name; uint32_t 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_t 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_t color) { int h = hash_key(key, cpp, hash->size); struct hash_entry *e = hash->next_free++; e->color = color; e->key = key; e->next = hash->table[h]; hash->table[h] = e; return 1; } /* fast lookup that works if cpp == 1 */ #define QUICK_COLORHASH(hash, key) ((hash)->table[*(uint8_t *)(key)]->color) static uint32_t 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 *data[]) { SDL_Surface *image = NULL; int n; 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; const char *line; const char ***xpmlines = NULL; #define get_next_line(q) *(*q)++ int error; int usedn; error = 0; xpmlines = (const char ***) &data; line = get_next_line(xpmlines); 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; } usedn = 1; for(n = 0; n < ncolors; ++n) { const char *p; line = get_next_line(xpmlines); if(!line) goto done; p = line + cpp + 1; /* parse a colour definition */ for(;;) { char nametype; const char *colname; uint32_t rgb, pixel; SDL_Color *c; int m; 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) { /* arrange for None to be color 0 */ if (usedn && (rgb == 0xffffffff)) { m = 0; usedn = 0; } else { m = n+usedn; } c = im_colors + m; c->r = rgb >> 16; c->g = rgb >> 8; c->b = rgb; pixel = m; } else pixel = rgb; add_colorhash(colors, nextkey, cpp, pixel); nextkey += cpp; if(rgb == 0xffffffff) SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel); break; } } /* Read the pixels */ dst = image->pixels; for(y = 0; y < h; y++) { line = get_next_line(xpmlines); 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_t*)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; } schismtracker-20180209/scripts/000077500000000000000000000000001323741476300163245ustar00rootroot00000000000000schismtracker-20180209/scripts/bin2h.sh000077500000000000000000000016311323741476300176660ustar00rootroot00000000000000#!/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="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 case "$type" in *static*) ;; *) echo "extern $type $name""[];" ;; esac echo "$type $name""[] = {" $OD -b -v "$@" \ | $SED -e 's/^[^ ][^ ]*[ ]*//' \ | $SED -e "s/\([0-9]\{3\}\)/'\\\\\1',/g" \ | $SED -e '$ s/,$//' echo "};" schismtracker-20180209/scripts/build-font.sh000077500000000000000000000005141323741476300207260ustar00rootroot00000000000000#!/bin/sh # make default (builtin) font if [ "$#" -lt 2 ]; then echo >&2 "usage: $0 srcdir fonts > default-font.c" exit 1 fi srcdir="$1" shift for file in "$@"; do ident=font_`basename "$srcdir"/"$file" .fnt | tr /- __` sh "$srcdir"/scripts/bin2h.sh -n "$ident" "$srcdir"/"$file" || exit 1 done schismtracker-20180209/scripts/genhelp.py000077500000000000000000000046711323741476300203330ustar00rootroot00000000000000#!/usr/bin/env python import sys, os, tempfile, subprocess def usage(ex): [sys.stderr, sys.stdout][not ex].write( "Usage: %s srcdir helptexts > helptext.c\n" % os.path.basename(sys.argv[0])) sys.exit(ex) if '--help' in sys.argv: usage(0) if len(sys.argv) < 3: usage(1) def die_at(filename, line, message): sys.stderr.write("%s:%d: %s\n" % (filename, line + 1, message)) sys.exit(1) # valid characters to start a line (see page_help.c) typechars = frozenset("|+:;!%#=") srcdir = sys.argv[1] helptexts = sys.argv[2:] o = sys.stdout if sys.version_info[0] > 2: unichr = chr mapord = lambda s: s else: mapord = lambda s: map(ord, s) ctrans = { ord('\n'): '\\n', ord('"'): '\\"', ord('\\'): '\\\\' } for c in range(32, 127): ctrans.setdefault(c, chr(c)) o.write("extern const char *help_text[];\n") o.write("const char *help_text[] = {") for idx, textfile in enumerate(sys.argv[2:]): blank = True for lnum, line in enumerate(open(os.path.join(srcdir, textfile), 'rb')): blank = False try: line = line.decode('utf8').rstrip('\r\n') except UnicodeError: die_at(textfile, lnum, "malformed Unicode character") if not line: continue elif line.endswith(' '): die_at(textfile, lnum, "trailing whitespace") elif len(line) > 76: die_at(textfile, lnum, "line is longer than 76 characters") elif line[0] not in typechars: die_at(textfile, lnum, "line-type character %c is not one of %s" % (line[0], ''.join(typechars))) line += '\n' try: line = (line .replace(unichr(0x00B6), unichr(0x14)) # paragraph mark .replace(unichr(0x00A7), unichr(0x15)) # section mark (why? I don't know) .encode('cp437')) except UnicodeError: die_at(textfile, lnum, "invalid CP437 character") o.write('\n"' + ''.join([ctrans.get(c, '\\%03o' % c) for c in mapord(line)]) + '"') if blank: die_at(textfile, 0, "file is empty") o.write(",\n") o.write("};\n") schismtracker-20180209/scripts/half2itf.py000077500000000000000000000005651323741476300204060ustar00rootroot00000000000000#!/usr/bin/env python import sys, struct if sys.version_info[0] > 2: read = sys.stdin.buffer.read write = sys.stdout.buffer.write else: read = sys.stdin.read write = sys.stdout.write write(struct.pack('<1024H', *[ (d & 0xf0) | ((d & 0xf) << 12) for d in struct.unpack('1024B', read()) ])) write(chr(0x12)) write(chr(0x02)) schismtracker-20180209/scripts/itcfg.py000077500000000000000000000504221323741476300200000ustar00rootroot00000000000000#!/usr/bin/env python # coding: utf-8 import sys, struct, time, os """ Here is the general layout of Impulse Tracker's configuration file (IT.CFG). It's quite a bit messier than the .IT format, probably because it wasn't at all intended for people to be tinkering with. Impulse Tracker has practically no error checking when loading the configuration, so it's very easy to cause a crash or hang just by changing values around in a hex editor. See also CONFIG.TXT from the Impulse Tracker source: https://bitbucket.org/jthlim/impulsetracker/src/tip/InternalDocumentation/ 0 1 2 3 4 5 6 7 8 9 A B C D E F ┌───────────────────────────────────────────────────────────────┐ 0000: │ Directories: module, sample, instrument (70 chars each) │ ├───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┤ ├───┴───┼───┼───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┤ 00D0: │.......│Kbd│ Palette settings (48 bytes, 3 per color) │ ├───┬───┼───┼───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┤ ├───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┤ 0100: │...........│ Info page view setup (6 * 8 bytes, see below) │ ├───┬───┬───┼───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┤ ├───┴───┴───┼───┴───┼───┴───┼───┴───┼───┼───┼───┼───┼───┴───┼───┤ 0130: │...........│NumView│Key Sig│NormTrk│Min│Maj│Msk│Div│TrkCols│TVS│ ├───────────┴───────┴───────┴───────┴───┴───┴───┴───┴───────┴───┤ 0140: │ Track view scheme (2 * 100 bytes) │ ├───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┤ ├───┴───┴───┴───┴───┴───┴───┼───┼───┼───┼───┼───┼───┴───┼───┼───┘ 0200: │...........................│VCT│Lnk│PF1│Amp│C-5│FastVol│PF2│ └───────────────────────────┴───┴───┴───┴───┴───┴───────┴───┘ Each directory name is 70 characters although only 64 are used. The first \0 byte terminates the name (unlike some other parts of the interface). The palette is stored in the obvious fashion, three bytes per color going down the columns, for the red/green/blue component of each of the 16 colors. Each component value is in the range 0-63. Kbd: Keyboard type (ignored by IT 2.04+) 0 = United States 1 = United Kingdom 2 = Sweden/Finland 3 = Spain 4 = Portugal 5 = Netherlands 6 = Italy 7 = Germany 8 = France NumView: Number of views active on info page. Even though the config file supports six views, IT only allows creating five. (However, adding a sixth with a hex editor doesn't cause any evident problems) Key Sig: "MUST be 0 (key signatures not defined)" (from IT src) NormTrk: Number of tracks in "normal" (13-column, ST3-style) view in the pattern editor. Min/Maj: Pattern row hilight information. Mask: Pattern edit copy mask: Bit 0: Instrument Bit 1: Volume Bit 2: Effect Div: Whether to draw the divisions between channels. Values other than 0 or 1 will make the track divisions "stuck", i.e. Alt-H will not disable them. TrkCols: How many columns to draw the box around the track view. This also adjusts the positioning of the "normal" channels. If zero, no track view is drawn. This should not have a value of 1. VCT: View-channel cursor tracking (Ctrl-T) Values other than 0 or 1 will make the cursor tracking "stuck", i.e. Ctrl-T will not disable it. Link: Link/Split effect column (zero means split) PF1: Bit 0. On = Centralise cursor in pattern editor Bit 1. On = Highlight current row Bit 2. On = Fast volume mode (Ctrl-J) enabled Bit 3. On = MIDI: Tick Quantize Bit 4. On = Base Program 1 Bit 5. On = Record Note Off Bit 6. On = Record Velocity Bit 7. On = Record Aftertouch Amp: MIDI volume amplification (percentage). C-5: MIDI C-5 note. Default value is 60, i.e. C-5. FastVol: Fast volume percentage. Although this is a 16-bit value, the fast volume dialog only changes the low byte, so if the high byte is nonzero, the fast volume settings will be "stuck". PF2: Bit 0. On = Show default volumes in normal pattern view. Bit 1. On = MIDI: Cut Note Off Track view scheme. The track view settings are stored as 100 (!) two-byte pairs of channel and view type. Channel number is first, numbered from 0; 0FFh is the end mark. Values for the view type: 0 = 13-column, nnn ii vv eee 1 = 10-column, nnniivveee 2 = 7-column, nnnivee (half-width inst / vol / effect value) 3 = 3-column, nnn/_ii/_vv/eee 4 = 2-column, nn/ii/vv/ee (half-width effect values) Info Page settings (starting at 0103h): 3 4 5 6 7 8 9 A B C D E F 0 1 2 ┌───────┬───┬───┬───────┬───────╥───────┬───┬───┬───────┬───────┐ xxxx: │ Type │TCh│Row│Height │MemOff ║ Type │TCh│Row│Height │MemOff │ └───────┴───┴───┴───────┴───────╨───────┴───┴───┴───────┴───────┘ Type: Window "type" of respective info page window 0 = Sample names 1 = 5-channel pattern view 2 = 8-channel pattern view 3 = 10-channel pattern view 4 = 24-channel pattern view 5 = 36-channel pattern view 6 = 64-channel pattern view 7 = Global volume / active channel count 8 = Note dots 9 = Technical details TCh: "Top channel" (from IT src) Row: Row number on screen to draw the view. Numbering starts at 0, and counts downward from the top of the screen. The first info page view should be on row 12. Height: How many rows the view uses. IT disallows sizing windows smaller than 4 rows, but the hard minimum is 3. (Setting most views smaller than that *will* cause a crash.) MemOff: VGA memory offset of beginning of first row. Should be equal to 2 * 80 * row. File format changes: IT <1.06 - original (?) data size is 521 bytes. (Note: only tested with 1.05; information on other versions would be appreciated!) IT 1.06 - 522 bytes First flag byte (PF1) added. IT 2.04 - 522 bytes Keyboard type byte no longer used due to the introduction of KEYBOARD.CFG. IT 2.05 - 522 bytes 24-channel track view added, resulting in renumbering of the 36-channel view, global volume, and technical data. IT 2.06 - 526 bytes Amp, C-5, and FastVol bytes added. IT 2.11 - 526 bytes Info page settings adjusted slightly to use an extra row at the bottom of the screen (just increased the 'height' value for the last visible window by one). IT 2.12 - 527 bytes Second flag byte (PF2) added. Also, 64-channel and note dots views introduced on the info page, thus causing the global volume and technical data to be renumbered again. IT 2.14p5 - 1337 bytes No apparent changes to the file format aside from the size. I can't see a purpose for the extra bytes, aside from leet... at least, changing those values seems to have no effect on the operation of the tracker. Note that no automatic configuration adjustments are made in any IT version when loading a configuration file written by an older version of the tracker (as there's no real version information in the file). And there you have it. If you have any questions, comments, anomalous IT.CFG files, or a decidedly ancient version of Impulse Tracker, send an e-mail to . -- Storlek - http://rigelseven.com/ - http://schismtracker.org/ """ def warning(s): sys.stderr.write(s + "\n") def getnth(n): if n > 3 and n < 21: return "%dth" % n return {1: "%dst", 2: "%dnd", 3: "%drd"}.get(n % 10, "%dth") % n def pluralize(n, s, pl=None): return "%g %s" % (n, [pl or s + "s", s][n == 1]) def asciiz(s): try: s = s.decode("cp437", "replace") except: pass return s.split("\0")[0] if len(sys.argv) != 2 or sys.argv[1] in ["--help", "-h"]: print("usage: %s /path/to/it.cfg > ~/.schism/config" % sys.argv[0]) sys.exit(0) itcfg = open(sys.argv[1], "rb") try: itcfg.seek(0, 2) # EOF itcfg_size = itcfg.tell() if itcfg_size < 527: warning("file is too small -- resave with IT 2.12+") sys.exit(1) itcfg.seek(0) # back to start except IOError: warning("%s: failed to seek, what kind of non-file is this?" % sys.argv[1]) sys.exit(1) dir_modules, dir_samples, dir_instruments = map(asciiz, struct.unpack("70s 70s 70s", itcfg.read(210))) # Not used by IT 2.04+ it203_keyboard = min(struct.unpack("B", itcfg.read(1))[0], 9) palette = "".join([".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"[b & 0x3f] for b in struct.unpack("48B", itcfg.read(48))]) # Info page nastiness. info_window_types = [ "samples", "track5", "track8", "track10", "track12", "track18", "track24", # added in 2.05 "track36", "track64", # added in 2.12 "global", "dots", # added in 2.12 "tech", ] infopage_data = [itcfg.read(8) for n in range(6)] num_views, unknown = struct.unpack(" 6: warning("info page: too many views, will probably crash IT") num_views = 1 nextrow = 12 # row that the next view should start on infopage_layout = [] for n, data in enumerate(infopage_data[:num_views]): nth = getnth(n + 1) # XXX what's this unknown byte? always seems to be 0, but changing it seems not to affect anything. wtype, unknown, firstrow, height, offset = struct.unpack(" nextrow: warning("info page: %s view followed by %s" % (nth, pluralize(firstrow - nextrow, "empty row"))) if height < 3: warning("info page: %s view is %s too short (will cause crash/hang)" % (nth, pluralize(3 - height, "row"))) elif firstrow + height > 50: warning("info page: %s view is %s too tall (might cause crash)" % (nth, pluralize(firstrow + height - 50, "row"))) if wtype > 11: warning("info page: %s view has unknown window type %d (will cause crash)" % (nth, wtype)) wtype = 0 # something sane nextrow = firstrow + height # Work around a Schism Tracker oddity: the saved height of the first view is off by one. # I'm sure there was a good reason for this, but I have no idea what it might have been. if n == 0: height -= 1 infopage_layout.append("%s %d" % (info_window_types[wtype], height)) # IT 2.11 added an extra row to the info page if nextrow == 49: warning("info page: extra row at bottom of screen (old IT version?)") elif nextrow < 50: warning("info page: %d extra rows at bottom of screen (corrupt config?)" % 50 - nextrow) elif nextrow > 50: warning("info page: data extends %s beyond end of screen (corrupt config?)" % pluralize(nextrow - 50, "row")) if num_views == 6: # as far as I can tell, this requires a hex editor, but it works fine once enabled # (handled here rather than above to allow error-checking the rest of the settings) warning("info page: six views visible, omghax") infopage_layout = " ".join(infopage_layout[:num_views]) # Pattern editor settings normal_view, rhmin, rhmaj, edit_copy_mask, draw_divisions, track_cols = struct.unpack(" 78: warning("pattern editor: track setup is too wide, display will look trashed") track_view_scheme = [] end_reached = False prevchannel = -1 for n, (channel, scheme) in enumerate(struct.unpack("BB", itcfg.read(2)) for n in range(100)): nth = getnth(n + 1) if channel == 0xff: # End marker. break elif scheme > 4: warning("pattern editor: %s view uses out-of-range scheme %d, will crash IT" % (nth, scheme)) break if scheme > 3: # adjust up for Schism's added 6 column / 12 channel view scheme += 1 if channel > 63: warning("pattern editor: %s track view shows channel %d (weird but harmless)" % (nth, channel + 1)) elif prevchannel + 1 != channel: warning("pattern editor: tracks not in sequential order -- Schism Tracker can't do this") track_view_scheme.append(scheme) prevchannel = channel # might be nice to check each track's width and compare with the width of the box if track_view_visible and not track_view_scheme: warning("pattern editor: track view setup was blank... strange!") track_view_visible = False if normal_view and track_view_visible: # Split view -- channel data is displayed on both left and right of row numbers warning("pattern editor: split track view unimplemented in Schism Tracker") elif not track_view_visible: # Normal (5-channel) view, not affected by alt-h etc. # Handle this by building a 5-channel track view instead (blah) draw_divisions = True track_view_scheme = [] track_view_scheme = "".join(["abcdefghijklmnopqrstuvwxyz"[v] for v in track_view_scheme]) (view_tracking, link_effect_column, pflag1, midi_amplification, midi_c5note, fast_volume_percent, pflag2 ) = struct.unpack("<5BHB", itcfg.read(8)) if view_tracking not in [0, 1]: warning("pattern editor: weird view tracking value %d; Ctrl-T won't work right" % view_tracking) if view_tracking and normal_view > 0: warning("pattern editor: view tracking unimplemented in Schism Tracker") # link/split is not quite as weird -- neither button will appear to be "pressed", # but it still operates fine (acts like 'link') and selecting either mode fixes it if fast_volume_percent > 255: warning("pattern editor: fast volume percent has high byte set, Alt-J will be broken") view_tracking = bool(view_tracking) link_effect_column = bool(link_effect_column) centralise_cursor = bool(pflag1 & 1) highlight_current_row = bool(pflag1 & 2) fast_volume_mode = bool(pflag1 & 4) midi_tick_quantize = bool(pflag1 & 8) midi_base_program_1 = bool(pflag1 & 16) midi_record_note_off = bool(pflag1 & 32) midi_record_velocity = bool(pflag1 & 64) midi_record_aftertouch = bool(pflag1 & 128) show_default_volumes = bool(pflag2 & 1) midi_cut_note_off = bool(pflag2 & 2) # I hate how Schism Tracker saves MIDI settings... midi_flags = 0 if midi_tick_quantize: midi_flags |= 1 if midi_base_program_1: midi_flags |= 2 if midi_record_note_off: midi_flags |= 4 if midi_record_velocity: midi_flags |= 8 if midi_record_aftertouch: midi_flags |= 16 if midi_cut_note_off: midi_flags |= 32 # finally! dump the config print("# Configuration imported from Impulse Tracker on %s" % time.ctime()) if it203_keyboard != 0: print("# Note: keyboard set to %s (IT <=2.03)" % [ "United States", "United Kingdom", "Sweden/Finland", "Spain", "Portugal", "Netherlands", "Italy", "Germany", "France", "unknown" ][it203_keyboard]) print("") print("[Directories]") print("modules=%s" % dir_modules.replace("\\", "\\\\")) print("samples=%s" % dir_samples.replace("\\", "\\\\")) print("instruments=%s" % dir_instruments.replace("\\", "\\\\")) print("sort_with=strcasecmp") print("") print("[General]") print("classic_mode=1") # ;) print("palette_cur=%s" % palette) print("") print("[Pattern Editor]") print("link_effect_column=%d" % link_effect_column) print("draw_divisions=%d" % draw_divisions) print("centralise_cursor=%d" % centralise_cursor) print("highlight_current_row=%d" % highlight_current_row) print("show_default_volumes=%d" % show_default_volumes) print("edit_copy_mask=%d" % edit_copy_mask) print("fast_volume_percent=%d" % fast_volume_percent) print("fast_volume_mode=%d" % fast_volume_mode) print("track_view_scheme=%s" % track_view_scheme) print("highlight_major=%d" % rhmaj) print("highlight_minor=%d" % rhmin) print("") print("[MIDI]") print("flags=%d" % midi_flags) print("amplification=%d" % midi_amplification) print("c5note=%d" % midi_c5note) print("pitch_depth=0") schismtracker-20180209/scripts/itf2half.py000077500000000000000000000005321323741476300204000ustar00rootroot00000000000000#!/usr/bin/env python import sys, struct if sys.version_info[0] > 2: read = sys.stdin.buffer.read write = sys.stdout.buffer.write else: read = sys.stdin.read write = sys.stdout.write write(struct.pack('1024B', *[ (d & 0xf0) | ((d & 0xf000) >> 12) for d in struct.unpack('<1024H', read(2048)) ])) schismtracker-20180209/scripts/itmidicfg.py000077500000000000000000000040251323741476300206410ustar00rootroot00000000000000#!/usr/bin/env python # coding: utf-8 import sys, struct, time, os """ No surprises with this format, it's stored exactly the same way as the embedded MIDI data within IT files, namely as an array of 153 entries of 32 bytes apiece, each terminated with \0, in the same order as presented in Impulse Tracker's MIDI Output Configuration screen: MIDI Start MIDI Stop MIDI Tick Note On Note Off Change Volume Change Pan Bank Select Program Change Macro Setup SF0 -> SFF Z80 -> ZFF Every line should be present, for a total expected file size of (9 + 16 + 128) * 32 = 4896 bytes. """ if len(sys.argv) != 2 or sys.argv[1] in ["--help", "-h"]: print("usage: %s /path/to/itmidi.cfg >> ~/.schism/config" % sys.argv[0]) sys.exit(0) def warning(s): sys.stderr.write("%s: %s\n" % (sys.argv[1], s)) def fatal(s): warning(s) sys.exit(5) itmidi = open(sys.argv[1], "rb") def readvalue(k): try: e = itmidi.read(32) if len(e) != 32: fatal("file is truncated, should be 4896 bytes") e = e.decode("ascii", "replace").split("\0")[0] e.encode("ascii") # test except IOError: fatal("read error") except UnicodeError: warning("garbage data encountered for %s" % k.replace("_", " ")) e = "" return e midiconfig = [(k, readvalue(k)) for k in ["start", "stop", "tick", "note_on", "note_off", "set_volume", "set_panning", "set_bank", "set_program"] + ["SF%X" % n for n in range(16)] + ["Z%02X" % n for n in range(128, 256)]] try: if itmidi.read(1): warning("warning: file has trailing data") except IOError: # Don't care anymore. pass print("# MIDI configuration imported from Impulse Tracker on %s" % time.ctime()) print("") print("[MIDI]") for (k, v) in midiconfig: print("%s=%s" % (k, v)) schismtracker-20180209/scripts/lutgen.c000066400000000000000000000206371323741476300177760ustar00rootroot00000000000000#define compile \ exec gcc -Wall -pedantic -std=gnu99 -lm lutgen.c -o lutgen || exit 255 #include #include #include #include /* * 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 << SPLINE_QUANTBITS) #define SPLINE_8SHIFT (SPLINE_QUANTBITS - 8) #define SPLINE_16SHIFT (SPLINE_QUANTBITS) // forces coefsset to unity gain #define SPLINE_CLAMPFORUNITY // log2(number) of precalculated splines (range is [4..14]) #define SPLINE_FRACBITS 10 #define SPLINE_LUTLEN (1L << SPLINE_FRACBITS) int16_t cubic_spline_lut[4 * SPLINE_LUTLEN]; void cubic_spline_init(void) { int i; int len = SPLINE_LUTLEN; float flen = 1.0f / (float) SPLINE_LUTLEN; float scale = (float) SPLINE_QUANTSCALE; for (i = 0; i < len; i++) { float LCm1, LC0, LC1, LC2; float LX = ((float) i) * flen; int indx = i << 2; #ifdef SPLINE_CLAMPFORUNITY int sum; #endif LCm1 = (float) floor(0.5 + scale * (-0.5 * LX * LX * LX + 1.0 * LX * LX - 0.5 * LX )); LC0 = (float) floor(0.5 + scale * ( 1.5 * LX * LX * LX - 2.5 * LX * LX + 1.0)); LC1 = (float) floor(0.5 + scale * (-1.5 * LX * LX * LX + 2.0 * LX * LX + 0.5 * LX )); LC2 = (float) floor(0.5 + scale * ( 0.5 * LX * LX * LX - 0.5 * LX * LX )); cubic_spline_lut[indx + 0] = (int16_t) ((LCm1 < -scale) ? -scale : ((LCm1 > scale) ? scale : LCm1)); cubic_spline_lut[indx + 1] = (int16_t) ((LC0 < -scale) ? -scale : ((LC0 > scale) ? scale : LC0 )); cubic_spline_lut[indx + 2] = (int16_t) ((LC1 < -scale) ? -scale : ((LC1 > scale) ? scale : LC1 )); cubic_spline_lut[indx + 3] = (int16_t) ((LC2 < -scale) ? -scale : ((LC2 > scale) ? scale : LC2 )); #ifdef SPLINE_CLAMPFORUNITY sum = cubic_spline_lut[indx + 0] + cubic_spline_lut[indx + 1] + cubic_spline_lut[indx + 2] + cubic_spline_lut[indx + 3]; if (sum != SPLINE_QUANTSCALE) { int max = indx; if (cubic_spline_lut[indx + 1] > cubic_spline_lut[max]) max = indx + 1; if (cubic_spline_lut[indx + 2] > cubic_spline_lut[max]) max = indx + 2; if (cubic_spline_lut[indx + 3] > cubic_spline_lut[max]) max = indx + 3; cubic_spline_lut[max] += (SPLINE_QUANTSCALE - sum); } #endif } } /* 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 << WFIR_QUANTBITS) #define WFIR_8SHIFT (WFIR_QUANTBITS - 8) #define WFIR_16BITSHIFT (WFIR_QUANTBITS) // log2(number)-1 of precalculated taps range is [4..12] #define WFIR_FRACBITS 10 #define WFIR_LUTLEN ((1L << (WFIR_FRACBITS + 1)) + 1) // number of samples in window #define WFIR_LOG2WIDTH 3 #define WFIR_WIDTH (1L << WFIR_LOG2WIDTH) #define WFIR_SMPSPERWING ((WFIR_WIDTH - 1) >> 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 float coef(int pc_nr, float p_ofs, float p_cut, int p_width, int p_type) { double width_m1 = p_width - 1; double width_m1_half = 0.5 * width_m1; double pos_u = (double) pc_nr - p_ofs; double pos = pos_u - width_m1_half; double idl = 2.0 * M_zPI / width_m1; double wc, si; if (fabs(pos) < M_zEPS) { wc = 1.0; si = p_cut; return p_cut; } switch (p_type) { case WFIR_HANN: wc = 0.50 - 0.50 * cos(idl * pos_u); break; case WFIR_HAMMING: wc = 0.54 - 0.46 * cos(idl * pos_u); break; case WFIR_BLACKMANEXACT: wc = 0.42 - 0.50 * cos(idl * pos_u) + 0.08 * cos(2.0 * idl * pos_u); break; case WFIR_BLACKMAN3T61: wc = 0.44959 - 0.49364 * cos(idl * pos_u) + 0.05677 * cos(2.0 * idl * pos_u); break; case WFIR_BLACKMAN3T67: wc = 0.42323 - 0.49755 * cos(idl * pos_u) + 0.07922 * cos(2.0 * idl * pos_u); break; case WFIR_BLACKMAN4T92: wc = 0.35875 - 0.48829 * cos(idl * pos_u) + 0.14128 * cos(2.0 * idl * pos_u) - 0.01168 * cos(3.0 * idl * pos_u); break; case WFIR_BLACKMAN4T74: wc = 0.40217 - 0.49703 * cos(idl * pos_u) + 0.09392 * cos(2.0 * idl * pos_u) - 0.00183 * cos(3.0*idl*pos_u); break; case WFIR_KAISER4T: wc = 0.40243 - 0.49804 * cos(idl * pos_u) + 0.09831 * cos(2.0 * idl * pos_u) - 0.00122 * cos(3.0 * idl * pos_u); break; default: wc = 1.0; break; } pos *= M_zPI; si = sin(p_cut * pos) / pos; return (float)(wc * si); } int16_t windowed_fir_lut[WFIR_LUTLEN*WFIR_WIDTH]; void windowed_fir_init(void) { int pcl; // number of precalculated lines for 0..1 (-1..0) float pcllen = (float)(1L << WFIR_FRACBITS); float norm = 1.0f / (float)(2.0f * pcllen); float cut = WFIR_CUTOFF; float scale = (float) WFIR_QUANTSCALE; for (pcl = 0; pcl < WFIR_LUTLEN; pcl++) { float gain,coefs[WFIR_WIDTH]; float ofs = ((float) pcl - pcllen) * norm; int cc, indx = pcl << WFIR_LOG2WIDTH; for (cc = 0, gain = 0.0f; cc < WFIR_WIDTH; cc++) { coefs[cc] = coef(cc, ofs, cut, WFIR_WIDTH, WFIR_TYPE); gain += coefs[cc]; } gain = 1.0f / gain; for (cc = 0; cc < WFIR_WIDTH; cc++) { float coef = (float)floor( 0.5 + scale * coefs[cc] * gain); windowed_fir_lut[indx + cc] = (signed short)((coef < -scale) ? - scale : ((coef > scale) ? scale : coef)); } } } #define LOOP(x, y) \ printf("static signed short %s[%lu] = {\n", #x, y); \ \ for (int i = 0; i < y; i++) { \ if (i && !(i % 64)) { \ printf("\n"); \ } \ printf(" %d,", x[i]); \ } \ \ printf("\n};\n\n"); int main(int argc, char **argv) { cubic_spline_init(); windowed_fir_init(); LOOP(cubic_spline_lut, (4 * SPLINE_LUTLEN)); LOOP(windowed_fir_lut, (WFIR_LUTLEN * WFIR_WIDTH)); return 0; } schismtracker-20180209/scripts/palette.py000077500000000000000000000010431323741476300203350ustar00rootroot00000000000000#!/usr/bin/env python # This converts a palette string from the packed ASCII format used # in the runtime config into a C structure suitable for inclusion # in palettes.h. import sys table = '.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' if len(sys.argv) != 2: print("usage: %s " % sys.argv[0]) sys.exit(1) for n in range(16): print("/* %2d */ {%s}," % (n, ', '.join([ "%2d" % table.index(c) for c in sys.argv[1][3 * n : 3 * n + 3] ]))) schismtracker-20180209/scripts/timestamp.py000077500000000000000000000063061323741476300207110ustar00rootroot00000000000000#!/usr/bin/env python import struct, sys """ Each timestamp field is an eight-byte value, first storing the time the file was loaded into the editor in DOS date/time format (see http://msdn.microsoft.com/en-us/library/ms724247(VS.85).aspx for details of this format), and then the length of time the file was loaded in the editor, in units of 1/18.2 second. (apparently this is fairly standard in DOS apps - I don't know) A new timestamp is written whenever the file is reloaded, but it is overwritten if the file is re-saved multiple times in the same editing session. Thanks to Saga Musix for finally figuring this crazy format out. """ for filename in sys.argv[1:]: f = open(filename, 'rb') if f.read(4) != 'IMPM'.encode('ascii'): print("%s: not an IT file" % filename) continue f.seek(0x20) ordnum, insnum, smpnum, patnum = struct.unpack('<4H', f.read(8)) f.seek(0x2e) special, = struct.unpack('> 5) & 15 year = (fatdate >> 9) + 1980 second = (fattime & 31) * 2 minute = (fattime >> 5) & 63 hour = fattime >> 11 print('\t%04d-%02d-%02d %02d:%02d:%02d %s' % (year, month, day, hour, minute, second, ticks2hms(ticks))) totalticks += ticks print("\t%13d ticks = %s" % (totalticks, ticks2hms(totalticks))) schismtracker-20180209/sys/000077500000000000000000000000001323741476300154535ustar00rootroot00000000000000schismtracker-20180209/sys/alsa/000077500000000000000000000000001323741476300163735ustar00rootroot00000000000000schismtracker-20180209/sys/alsa/init.c000066400000000000000000000046351323741476300175120ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ /* Here be demons. */ #include "headers.h" #include "util.h" #include "osdefs.h" #if defined(USE_DLTRICK_ALSA) # include void *_dltrick_handle = NULL; static void *_alsaless_sdl_hack = NULL; #else # error You are in a maze of twisty little passages, all alike. #endif /* --------------------------------------------------------------------- */ void alsa_dlinit(void) { /* okay, this is how this works: * to operate the alsa mixer and alsa midi, we need functions in * libasound.so.2 -- if we can do that, *AND* libSDL has the * ALSA_bootstrap routine- then SDL was built with alsa-support- * which means schism can probably use ALSA - so we set that as the * default here. */ _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 0 if (_dltrick_handle && _alsaless_sdl_hack && (dlsym(_alsaless_sdl_hack, "ALSA_bootstrap") || dlsym(_alsaless_sdl_hack, "snd_pcm_open"))) { static int (*alsa_snd_pcm_open)(void **pcm, const char *name, int stream, int mode); static int (*alsa_snd_pcm_close)(void *pcm); alsa_snd_pcm_open = dlsym(_dltrick_handle, "snd_pcm_open"); alsa_snd_pcm_close = dlsym(_dltrick_handle, "snd_pcm_close"); } #endif } } schismtracker-20180209/sys/alsa/midi-alsa.c000066400000000000000000000343161323741476300204060ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "midi.h" #include "util.h" #ifdef USE_ALSA #include #include #ifdef USE_DLTRICK_ALSA /* ... */ #include #endif #include #include #define PORT_NAME "Schism Tracker" static snd_seq_t *seq; 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. Editor's note: currently there's an explicit directive for the build to fail if USE_DLTRICK_ALSA isn't defined. Doesn't say why. */ #include extern void *_dltrick_handle; /* don't try this at home... */ #define _void_dltrick(a,b,c) static void (*_dltrick_ ## a)b = NULL; \ 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 = NULL; \ 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(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_seq_queue_tempo_malloc,(snd_seq_queue_tempo_t**ptr),(ptr)) _void_dltrick(snd_seq_queue_tempo_set_tempo,(snd_seq_queue_tempo_t *info, unsigned int tempo),(info,tempo)) _void_dltrick(snd_seq_queue_tempo_set_ppq,(snd_seq_queue_tempo_t *info, int ppq),(info,ppq)) _any_dltrick(int,snd_seq_set_queue_tempo,(snd_seq_t *handle, int q, snd_seq_queue_tempo_t *tempo), (handle,q,tempo)) _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*seeq,int my_port,int src_client, int src_port), (seeq,my_port,src_client,src_port)) _any_dltrick(int,snd_seq_connect_to, (snd_seq_t*seeq,int my_port,int dest_client,int dest_port), (seeq,my_port,dest_client,dest_port)) _any_dltrick(int,snd_seq_disconnect_from, (snd_seq_t*seeq,int my_port,int src_client, int src_port), (seeq,my_port,src_client,src_port)) _any_dltrick(int,snd_seq_disconnect_to, (snd_seq_t*seeq,int my_port,int dest_client,int dest_port), (seeq,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*seeq,const char *name),(seeq,name)) #endif /* see mixer-alsa.c */ #undef assert #define assert(x) static void _alsa_drain(struct midi_port *p UNUSED) { /* not port specific */ snd_seq_drain_output(seq); } static void _alsa_send(struct midi_port *p, const unsigned char *data, unsigned int len, unsigned int delay) { struct alsa_midi *ex; snd_seq_event_t ev; long rr; 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 { snd_seq_ev_schedule_tick(&ev, alsa_queue, 1, delay); } /* 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; } } static int _alsa_start(struct midi_port *p) { struct alsa_midi *data; int err; err = 0; 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) { log_appendf(4, "ALSA: %s", snd_strerror(err)); return 0; } return 1; } static int _alsa_stop(struct midi_port *p) { struct alsa_midi *data; int err; err = 0; 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) { log_appendf(4, "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 = NULL; 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 = NULL; 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) { 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 = NULL; 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 = NULL; 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 = NULL; if (snd_midi_event_new(MIDI_BUFSIZE, &data->dev) < 0) { /* err... */ free(data); continue; } if (asprintf(&buffer, "%3d:%-3d %-20.20s %s", c, p, ctext, ptext) == -1) { free(data); continue; } midi_port_register(_alsa_provider, io, buffer, data, 1); } } /* remove "disappeared" midi ports */ ptr = NULL; 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) { static snd_seq_queue_tempo_t *tempo; static struct midi_driver driver; /* only bother if alsa midi actually exists, otherwise this will produce useless and annoying error messages on systems where alsa libs are installed but which aren't actually running it */ struct stat sbuf; if (stat("/dev/snd/seq", &sbuf) != 0) return 0; #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; driver.drain = _alsa_drain; if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0 || snd_seq_set_client_name(seq, PORT_NAME) < 0) { return 0; } alsa_queue = snd_seq_alloc_queue(seq); snd_seq_queue_tempo_malloc(&tempo); snd_seq_queue_tempo_set_tempo(tempo,480000); snd_seq_queue_tempo_set_ppq(tempo, 480); snd_seq_set_queue_tempo(seq, alsa_queue, tempo); snd_seq_start_queue(seq, alsa_queue, NULL); snd_seq_drain_output(seq); if (!midi_provider_register("ALSA", &driver)) return 0; return 1; } #endif schismtracker-20180209/sys/alsa/volume-alsa.c000066400000000000000000000174341323741476300207750ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 USE_ALSA #include #include static const char *alsa_card_id = "default"; /* --------------------------------------------------------------------- */ #ifdef USE_DLTRICK_ALSA #include /* see midi-alsa for details about how this works */ #include extern void *_dltrick_handle; /* don't try this at home... */ #define _void_dltrick(a,b,c) static void (*_dltrick_ ## a)b = NULL; \ 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 = NULL; \ r a b { if (!_dltrick_##a) _dltrick_##a = dlsym(_dltrick_handle, #a); \ if (!_dltrick_##a) abort(); return _dltrick_ ## a c; } _any_dltrick(snd_mixer_elem_t*, snd_mixer_find_selem, (snd_mixer_t*e,const snd_mixer_selem_id_t*sid),(e,sid)) _void_dltrick(snd_mixer_selem_id_set_index, (snd_mixer_selem_id_t*obj,unsigned int val),(obj,val)) _void_dltrick(snd_mixer_selem_id_set_name, (snd_mixer_selem_id_t*obj,const char *val),(obj,val)) _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(size_t,snd_ctl_card_info_sizeof,(void),()) _any_dltrick(size_t,snd_mixer_selem_id_sizeof,(void),()) _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 < 10) || (SND_LIB_MAJOR < 1) _void_dltrick(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_ctl_open,(snd_ctl_t**c,const char *name,int mode),(c,name,mode)) _any_dltrick(int,snd_ctl_close,(snd_ctl_t*ctl),(ctl)) _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_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)) #endif /* alsa is paranoid, so snd_mixer_selem_id_alloca does an assert(&sid), which * of course will never fail, so gcc complains. this shuts that warning up. */ #undef assert #define assert(x) /* this _could_ change */ static int current_alsa_range = 255; 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) * range / current_alsa_range) + min; ar = ((*r) * range / current_alsa_range) + min; mr = min+range; if (snd_mixer_selem_is_playback_mono(em)) { md = ((al) + (ar)) / 2; _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); #if 0 /* this was probably wrong */ _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); #endif } } 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 * current_alsa_range) / range; (*aa) = v; } } static void _alsa_config(UNUSED snd_mixer_elem_t *em, UNUSED int *l, UNUSED int *r, UNUSED long min, long range) { current_alsa_range = range; } 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); #if 0 /* this was probably wrong */ _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); #endif } } static void _alsa_doit(void (*busy)(snd_mixer_elem_t *em, int *, int *, long, long), int *l, int *r) { long ml, mr; snd_mixer_selem_id_t *sid; snd_mixer_elem_t *em; snd_mixer_t *mix; snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_id_set_index(sid, 0); snd_mixer_selem_id_set_name(sid, "Master"); 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; } em = snd_mixer_find_selem(mix, sid); if (em) { ml = mr = 0; snd_mixer_selem_get_playback_volume_range(em, &ml, &mr); if (ml != mr) { busy(em, l, r, ml, mr - ml); } } snd_mixer_close(mix); } } int alsa_volume_get_max(void); int alsa_volume_get_max(void) { int a1, a2; _alsa_doit(_alsa_config, &a1, &a2); return current_alsa_range; } void alsa_volume_read(int *left, int *right); void alsa_volume_read(int *left, int *right) { *left = *right = 0; _alsa_doit(_alsa_read, left, right); } void alsa_volume_write(int left, int right); void alsa_volume_write(int left, int right) { _alsa_doit(_alsa_write, &left, &right); } #endif schismtracker-20180209/sys/fd.org/000077500000000000000000000000001323741476300166325ustar00rootroot00000000000000schismtracker-20180209/sys/fd.org/autopackage.apspec000066400000000000000000000031501323741476300223120ustar00rootroot00000000000000# -*-shell-script-*- [Meta] RootName: @schismtracker.org:1.1 DisplayName: Schism Tracker ShortName: schismtracker 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://schismtracker.org/ License: GNU General Public License, Version 2 SoftwareVersion: 1.1 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. Improvements have been extremely careful to avoid disturbing any muscle memory that the user might have developed with Impulse Tracker. [BuildPrepare] mkdir -p linux-x86-build && cd linux-x86-build && prepareBuild --src .. $EXTRA_ARGS [BuildUnprepare] unprepareBuild [Imports] import </dev/null 2>&1 installDesktop "AudioVideo" schism.desktop installDesktop "AudioVideo" itf.desktop installData NEWS installData README installData COPYING installData ChangeLog [Uninstall] # Usually just the following line is enough to uninstall everything uninstallFromLog schismtracker-20180209/sys/fd.org/schism.desktop000066400000000000000000000010461323741476300215140ustar00rootroot00000000000000[Desktop Entry] Actions=Play;FontEditor Version=1.0 Name=Schism Tracker Comment=Impulse Tracker clone Comment[fr]=Clone d'Impulse Tracker Terminal=false TryExec=schismtracker Exec=schismtracker %f Type=Application Icon=schism-icon-128.png Categories=Audio;Midi;Sequencer;Player;Music MimeType=audio/x-it;audio/x-s3m;audio/x-xm;audio/x-mod Keywords=impulse;module;midi;music [Desktop Action Play] Name=Schism Tracker (play song) Exec=schismtracker -p %f [Desktop Action FontEditor] Name=Schism Tracker (font editor) Exec=schismtracker --font-editor schismtracker-20180209/sys/macosx/000077500000000000000000000000001323741476300167455ustar00rootroot00000000000000schismtracker-20180209/sys/macosx/Schism_Tracker.app/000077500000000000000000000000001323741476300224255ustar00rootroot00000000000000schismtracker-20180209/sys/macosx/Schism_Tracker.app/Contents/000077500000000000000000000000001323741476300242225ustar00rootroot00000000000000schismtracker-20180209/sys/macosx/Schism_Tracker.app/Contents/Info.plist000066400000000000000000000056361323741476300262040ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleExecutable schismtracker 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 2003-2012 Storlek CFBundleIconFile appIcon.icns CFBundleIdentifier org.schismtracker.SchismTracker CFBundleInfoDictionaryVersion 6.0 CFBundleName Schism Tracker CFBundlePackageType APPL CFBundleShortVersionString hg CFBundleSignature Schm CFBundleVersion hg NSMainNibFile MainMenu NSPrincipalClass NSApplication CGDisableCoalescedUpdates schismtracker-20180209/sys/macosx/Schism_Tracker.app/Contents/PkgInfo000066400000000000000000000000101323741476300254710ustar00rootroot00000000000000APPLSchmschismtracker-20180209/sys/macosx/Schism_Tracker.app/Contents/Resources/000077500000000000000000000000001323741476300261745ustar00rootroot00000000000000schismtracker-20180209/sys/macosx/Schism_Tracker.app/Contents/Resources/AppSettings.plist000066400000000000000000000007261323741476300315170ustar00rootroot00000000000000 EncryptAndChecksum IsDroppable OutputType None RemainRunningAfterCompletion RequiresAdminPrivileges ScriptInterpreter /bin/sh schismtracker-20180209/sys/macosx/Schism_Tracker.app/Contents/Resources/appIcon.icns000066400000000000000000001465061323741476300304570ustar00rootroot00000000000000icnsFics#Hx?x?ics8]W]]]WW]]WW]]W2V]WVW]WWis32Au Z{*Ub}E aspuYi|twuO( \w\F%(-38TH/8.%4_gVL=E576'9i _OOBF>6: 2 ;*`TQGC=.;)$ &VaUNE>?&9(@emwo:aTID>X]{}a%ciontȘt` Opke*GVqpq}<VfbfnM}Vjky_L%_sj}OJ"00>.t{LM-A,(:&L]PS;M3=5#Agm WLU?K:8=>F#WNUCI>0B5' /CXPSDCA'B#>[XaZ.YOO DAWQnrtjT%Y`iu}nf_Q/=QUV*AIOi]_h5N[YYu}^?bI _oa^wtiU>"E_a{nI>$((+l)ioIC/7*$-c?VPJB834./jQNLA@9-6( l7QOKA<:'5$4IJPJ'QNH A\D4+1O+#@3 )^rHBGeHD:T?20+K*%'#59x#]tNONc>;9RB*,.L6,H  VU]xLEFe@73LM2-%A<%'!A/Ua:]zLDAbC>8GL*./?E2ie@\zJGEdA66EV/'/SQzwV8 \{JA@cJ;26Q5SymQ9)\zNG@\A;TzlYH7[wCCXsnf eu?QnWYurw_$$ 8Ezczv)& 3AQjz]LQw;!GMX\Sf]rW"*MWUwa|s!GV]gZF]nmLc4M`]lndj_9LQ:Yhz`V~xqNX%6vpUgx{}j=&Q6)f.if}qQMI!%BDRN mwR/)=W#! 8S>xno|U:VM.,2[+%&Z% .l' ofeS76RT-00[-$$ T, VI awG>\[75M\-,+Z9 QEI{j UfF@X^:6D`1.%OE**:N -m"WhGBWa89Ab/00LO! 2Z&]E ViEATd65>i:.'=Z'!Z- I~iViF?Ni?<8e;0-2[%#$!Q7 ;pUkHGRj::9d>(*0`1EM _DTmHBMn<51_J0,$W8#%1S'Eh|M:UoJAGm?<5YK(-+UD3iioR5UoHDIp=42VX-&@TDfydJ2TpG?CpE:0FV/Ift_K9-SpKEBm=7KpsfYLM?SmA@R}wuw [g6H[GUc^cQ&$ 8;[aQec)& 3?BJYXLADc6!>GGOanOIWN_rJ" GNNYRhzSi|t`!GNU]RBP^r}{s[>Grn.MXVbqbY^n~|qQ3-iuE'V_`WQp~ruohtbCB$ Yu]Nngj]lzxqlk\9&8/Frl(_|cfdndK>=#%!.62ksA bvueL0+3D%#"*=#]sZeoP;EE0-.E+%!?$Lrk# ee|`VM98CI/0-C,$%8(6mq= YoHANR97AM/-,B4 !79)aqW NaGBLS;7;O3/':<*) (:OpiObGDKU:O112;A!'>3#$I#9":Time>NhJCBZ@<7FC*--=:*OYjsn\E-NhHEE[=54EL.'0D>XnxrgUA/LiHAA[D;39H0C[q}zsfWH<0'MhKF@W=8Hb{|tjbYQM?LfCBOp|~zxw| S\|z6l8mk}@6է VIyڈ9 YL |; [O*}RTWoțe2ѰU( ٻnD ʥX5໐fB%٫zN,՜d6it32w$+];Hz>'+*(&%$E[`Q@@Wze-*(%$$&,Pb^K?FeU8SmE?!,*(&##)=XeZFAQroI:<>63Fn=%*(&$",I_eUCD^]B;@?:>A>;HmlF44>!*(&$"1 @\i^IEYzfG>AB=>WY<49S|=#(&$! (LciXGJeWB@CA>HgvL66Dfd)&$"$*1WjfQHSsN@DE@@V}3;S|(&$#  B`nbMI`ǃHCAIkf<"&$! !Ogm[KQls/{¸ RVmb&$! $4ZnjUK[{l:+\?4o%$!  FdrdPOgsQjK;B^: $! &Rkr_NWuhPNKMs b$! $7]rnYPb^OPRRMrmZ k #! HgwhTSntWPSTSRROc]GBQwi^L=7l :! (UovcR[yhTRTUTTSRRSqRDH]mfZJ>=F=E a# $8awr[TheSURTSTTSRRP\JFPntkbSFIRG52)2 " "JjzlWYwuD}SR[zjRTSRQML`~ni\MHQQ?$,+${ 9  )YszfVc_48kdifQTPMUrsmeWJOSI0+,&V`!$=c{v_XpL0H|yXQPPczpl`PNSP;!$,)7 %Loo[_~q@3ZhZfVNOXu~cZOSTE+ 03/8'+\v~iZj]8+V{PUPSfg {kqzfOWN7  *96(c`$=@gzc^wL3.-2üM[x[HFtocODEK( +1,(+'<TPrs`fjA100@kenQHTtyri]RSJL;yK(30% ,)+7V/_zm_rT9/6Rso\Rc_JKdtodWVZT?@Jx"(  &2<=5'262~:%074) *)!c S|hjjk eXeAznd`e_J3'#7BA{+ !$/:@:,!,7/s_#/76, &)&; 4Rrho{jk e|tjaecU<+%&'%5B,9A>2%(63Vw48/# %351]*Qggjjb cfdg]G2( ''$1B>t[@8)!$'>CJ,!+/71#pPqgfrjfukcR:-)* )(&&*8KHga!  "-89A=50 ",2.")($E2 PĺĹ zoa\[c0**,,+*)(+3?E?F@Sq! ",8>:.!06/xV#-30% ")'/ \$ OM~skhk]UJvi(-,,+*+2>GE:-(=@Iz#! #,8?=2%+70]n#.53'  )( z PÁzoiliZB4GFm(,+,11+)(''&$1B>s]A<1%5;:|[*! $6>4  OÁ{gpm`I945543211CTYF4-+;**)('&&*8LJhj#  &0=D8ik -ADFD0b 1 OÁÿxlebj=655665327AKPUN=*,+**('+5?FAGASt &'2;;0072I /DTVM10*#:[  OÀ ù}tptjaVa3876557?JQL@?LEK),*),3?GF?H}# ) '3<=4(!538&2EVWM8" (%( 0NwqrthQCQPf4767>JRPE:2/:LFT)++-4?HH@3)%29A=0!)4=?7+ 450}@4HXXM8" (&p0  N|trvo\F=:=QQ~j4=HQTK>62"07JFz])3>HKC7,'&'&&$4A;z@ *5?A9-" 15,qe !6KZYL8! '&"DZ  *?N¾|rwufOA==><=PQzvLVOD:54433214HFuuBMG;/))((''&&$2A:r[=;>UZ|E=75665433106NQp:2,+*)(''%2HGhs-& ';BKVL7! !04*w.  Mjgm@A ?>>DNT]Yv2776K5325=ILTP`*+,,+**)'&',7ACJESu (>GPPD= !*-74)RZ *Llkm?CB@@DMWZRFPQm4776546=GOPF9FIU/+,,+)(-8BGA6->>F# +@S_]E>8,x]$.2/&('#4 Llkm>ADLV\WMA<:LRe67=HPRI=50/BKN4+>/9CIC9-&$$:@<3 !-BV`^J5!-4-Xr)130& '&!.   LlknFT\[RF?= ;JT_9KG@)1:DJE;/(%%&%$5@;zA#/EXa]J6"*51@#-440% '&bY ,Llkr[WKB@??>>=;HT^RUQF;64433210=KCVCKG=1+'(&&%#3@9sU2H[b^J5$#422/'076/$ &  KlkoCBAK@?>=<>N^eM=7556543320/=QOb=4-*)*)(''&&%#-?@FPWW\\?576854225=GMVM|_&+,+**)(''%$#)6OWbF5%7=;ht(" ),%nX XLlks?CA@AFQYYQEGUUE6766547=IPND=ICvv&,,+**)'%%+9Nb]VOL#)39=B:I %660C iMlku>CGPY\TI@<:EVRK5658?IQOF;3/2HEi(,,+)('-;Qch^J5=>=;DVOT6@KRRI>521102HG[++*)0>Seh_J5(""5@:yC  &1:?=5* 22-~J&/20) %%z W MlkxVQFA@@?>>=;CVMgPTL@843A2101FHT.1@Vgj^J5)$$%$#3@8s\#*4>A>4*22*mi#+330' &%P Mlkv?BA8@?>=<?DNUW\Sd376+54331//6J][bI5+())(''&&%$"*ADU~A>2( (2091,675,"   &$ U Llkz?CA@AEOWZRGASOj47653119H]k_^NM'++**)(''%%'-7=L- %7::S0) &#` SMlk{>CGPX[TJA<;>RQn376544;J_mm^IAJDV(,+**('&*0;CGE:BA97&/5BA6xf )--:*QMlk|MZ]VMC>=>=<>RP|w255>Maon^J80-:JEyc',*))-5>GIE:0'"6?9yF!+3<=8-74-Sy !'352)U2Llk}SOFA@@?>>=<=QOy;Pdqo^I92001/8ICu|%,09BJJE:/($5"3@7tc &19?=7,#*20=  '..,"$(%n kLlk}?BBAA@?>>=;;PTzkp]J:3223211/4GDg9FMKD9.(&%&%%$"1@8]v$-6?@=4)'2105 '/1/)$$A),Llk?CBAA@?=<;M}'3;CA<1'!11+|] %,32/%  #$"+TLlk?CBA@>>GXjuk`Wj656+542214NQa:6$5448?IQRLAJJK8*+**)(''&%$&+4:KLDB".11D3761&   %#Ol'Klk?M]nwp^K@;::NS]=557?B9zI2>@@=<:LTZA=HQVTK@720.>JBO(+)*-6?FHE<2)#3?6tj&/799?90S  &4^(KlkZLB?@@?>>=<9IV[ZVTK@8428110.;K #+4:L@B?6,#'20,I  ":gIFW,*)(''&&%$#$(.CJF~^.) #20(re 7^BqJ/  KlkVa[RHA>=>=<:EVKc257=EMSRMC92.0FHR1*)('&%%(-7?FDCG>=<:DVKk=JRVSJ@820#/0FHK;)**('(-5=DHE>3*2=7Yy1NusN4 JlkAABAA@?>>=;9DWSzTRI?7324110//EIDG'*-2=DJHD90'#!->;K!,FrsP7 JlkBAA@?==>DLTa\{9754332110/.@IAT7CJKH@6-'$$#!'<<>0)BjuQ9 !%#!JlkCBB@@?BISY[WOWP~1605433211/.-?NLjIF;2+'%%&%%$##!%;=6>&?bvT<%"$(,+)&$" JlkBABGNW]\VLB<=RO{36,54321028@GNWO}r++)((''&&%%$##!"9=4uM<;;>QOx46(4337>HNSOGDLCt&**)(''&&%%$#".8CbxWA,+18<;8541/-*)' IlkX\RIB?>=<;=QOt5'8=FMSRND;3-7IDc)**)(''&&$# #5MryXC0/8@DC?=<:64405=IlkFBA@A@?>>=<;=QPk?CMSVRI@720//-4GFV,)*)('%"!$2Kn{YE45@GKKGECA?=987IlkEBBAA@?>>=;:==>CIQ^`eH:6544332110/.1FHI:$/E_|]J;>PW^[WSSQPLIHL0JlkGBB@@?CIRX\YQVVZA46654332110/--BD?d}^K?CVahfa\[YXVUSU-IlkGACIPX]]XNF>;NSWI466543321/,*.>Zx}_MBH_looifeba^ZXZ*IlkOW]_\TKB><;;:MTUR366542/-/=>=<;:JTQX35301:QkbQGNk}}xwurqknf"Ill}JBA@A@?>>=<;:HTOZ/9NfcQJRr~{xtpmHll|JBBAA@?>>=<:8EPIrcRJU{}{uGll|KABAA@?>=98;H`dTKU}Hll{KABA@=; F[r¶fSJTHkjvOVo¿øgTIUƷ Fp|ĸhTGOݿEŹgSAFBƁƺgP;:ȿCǻgO4(rɹCɾiL,*CiM% ж$L(ڿ$L11[X%$@gw-,*('Ow8%0,+*(4]T#0-+*(CkZ$1v"0-++(#*Q{uG R6*-+*(&6Ld42cS!-+*(&$#@OK=}xu5Ftu!/+*(%#!2GRI:5135/+9Zv3&*(&$",=QWJ;YzUv 1#&$! DY_PCH]x|f,{ùuGIk~[oP&$! $-N`]KCPixyl;*y rM7.\s'$!  Zh\KJ`z|dNGIJJIHEVzoP?mJJQj\HJJIGDCTo{dTMD96;;/(WWMi 0 &Ndl[OYtU25^Y\yZGJHEKboZQJ@8;=6% RWSXN!$!5XliVQdrE/BmiNHGHVraUPG=;>;-CXVJq $DapdTVod<1Q}[PZMGGNefID<>?5# 3\aVl.'(Qhq_T_{}T5*NkHLHJZvzZ|xbP]hU=B<+ 2fcU^ M$Q8[qmZVkpG2-,1twEQimO@?plZSJKZ\u1/0<_}Zot_H?IcuaWPG@?]u`i?'/-" ;XULu -V*TlucXf}S:/5LrfboSJWqTCDWtjZTLCBEA.@bY_X$.0(  %WVLe M$L6bvq^\qеtY|^RRQMOgdKBLfs`YQHBFE:))_`Ug#.2, PUSO pOGs]bzqYRURQ[tvWFGYv}h]UMFEGA0!![aOt' +30% AZ]O| -L&W̢fVUVSTehNFOiVSJFIF8(Lb[jI1*  >ggSfL;9gʌRWVT[rz[JI]z|i\ykHIJ@/"MpgfX #--0\XQUpI-xg[jʐRVdmRIRlrc\TYfhZ5( '25EgbXb$-1*FVTI+Lxc^a^Jx͑nBqKL`}|i`XPKMwxg\ %197-']aPv #.3." 8WUImKJpb_bba^RtίBplrd]TMMNF7`k]e! $/9;3&VaUk1"-41&  "UTL]oKpacb a]Wa6{iaYQNPL>/%"Pm[j(#.8=7* HbWeO!-54* MTUI+Jiagpb a]6rd]UPQPF5)$%&"Jlbl7*7>;/$=a^\b15-! CadRr K*Jt|^_a`Z}TSQSL=.''& !AlfjQ=6( 2io[v( 'Ae^Nb oIg`u|_iqgYdiZQD4+( '&%%(?sshT  +5=jiUn*  ).+!SSRM * I yjcZUp{xV-*:)('&)2HNI7) ATRGr m eFwneabbZHCrw{[3556rxy^3=<;9=tzsC<64543211/.9rzqv91*)('& $#"?qphb,% =ejZyA9* >_aSc&  F~cad?@?=<Hppba"/Fstbm3 &A@>>AKTXPDkyp}3665425;EMMD9esir-*)' ,7@D?30cjZl"$2?GE>dbThM #+.,#"QSQFk Ewedd=@CISYUK@:8dzm56;ENOG<3.-Zudz2*).7@EA7+%!$ZkXs.'5AIF9+M_Y[]%./-# ?TRFk%   EwedeFSZYPD=;8_{m}7;ENQK@6100/-Sua;)/8BGC9-'$5#!Nkal9(7DIG:,C`^Ou!)01-" -TQI[F  hEwed~jYUIA>==<<;8Z|p~OSND9422110/,Puc~N@IE;0)%%&&%$# HlahI*9FLH:,5_]Op)%-43,! RQPF k  EwedgB@? >=<;:;_zI<6432110.,NzvwY;2+('('&%$#=ifceCLH;-!%[]UjN-53+"  DRPEq $ -Ewedh>A@??=<>DNThs<4655432003;DY~usT')*)(('&$#"%:r|li8,! "^hge`'! 6VYO` E =Fwedi>A?>@EOVWOAW}mA4655325:7T~kF4547=HNME91-7lnjr(**)'&&*4BNQK<1_iUt0!+3993&F`]Nv# ',,-VYTDy # kFwec}jKW[ULA=;<;8S~jM5?IPOG;30//.5kqhr*)((,6DPRK=/%! Pj`k;%/6<:2(-]]Sj>  $,.-' =RQDfD Fwec|nUOD?>>=<<;8O}j_NQI?6210/.2irgs-.8FQTL>/'#" IkaiN!(2;=:1'!"[]SdV !(//-$ )SPLPi Fwec}l?@@??>=<;::Qva>84P32110/..asd}KSUL>0'$%&%$##Ajb_b(6>?<1'Q^[Th&-20,! OPPD" -Ewec}n>A@??=<=BKR]u}Z265543211/-.2_nS>1*&''&%$#"4jpcj=;0%$E^\Ly,)242)  BRQCjC -Ewec}o>A?>@DMTWODFutz^3655431/05?MVjj}E&))(('&$#%+4Bsx`v*RsjtL'*)((&%&).9AEB;gkUt1#+?mm`jT  VY]G!=Ewec{pLW[TKA=;<;9@rwxi145:DRZZN@4/)JskrW&*(''+3=EFC9/%!Si_k= *18942_^ZYa  *Zb`IpB>Dwec{rRND?>==<<;9?qwvu8GT][OA50/./,Bqkpj$*/7@GGB8.&""!Kj`iS$.6;:4*!K\\My $,,)DWQE_ h kCvec|q>@@??>=<<;8A@??>=;::@L~z?944332110/.,8oymvGA7-(%%&&%$##"7gg[h'08@>9.%2]\RjL #*//+" NOPBx BDvec{w>A@?><=CN[b\z}n56554320 39CLxmu0)('('&-%$##!-fo]{A>7-$$Z[V\\!(01/(  ?QPCd gDvec{z>@??FP^c_RDFNOJ@ksb4()(('& %#"$*2=pwau; T\_Rw/43.$   .QOJNCvecz{>HS_e`TE=9i{l:4459BKPOI?6/,^s^?')(('% (.8?EB<_m_k?Qkm\t6    QPN=},ADvecy~]f`TF?<;<;:8d{m>50.,Tsa{G')',4=DGC:0' Ki_hY$,35QlfXlE    ;ABU}gCvecy~QG@>=<<;97^~rVTQH>620//.+PsisM&+0:AHFB7/&"! Fjb\d",2984* 0\[SbX   #B_u>Cvecz=@@??>=;;:>CfxQ=74332110//.+Lrjq`:FJG?5-'$#"!:hgZk!)18;82(Y[[Rp   :]s_FBvecy>A@?>==BIQWUgmL255432110/.-,LyvswB=4+'%%&%$##"!.dhT})(18=;7-%O][Lx!   5Yn>~qY,Cvecx?@?@FMUZYQG;96U}iU3553226=FMOLABmpdr*('&%$#"!"&/eulnQ-'7][RfS -Mh?sY;&  BvecxU^YPF@<;<;:7S}iY246;CKPOJ@70+4jqev.('&&%$#&,5=<<;:7R}k}_=HOTPH>60. ,2hr`7'('&%',2;BFB<2&IiaZe/Viuu\@+ Bvecw??@??>=<<;96O~w|nRPF>6202//.-/er]@&(,1:AGEB7.&" >hfYo';^tv\A. 2BvecwA@@??><;;=BH^}m864332110//.-,[r`yL5@GHE=4+%"!!1egS*%9Yrw_B0 "%#!KBuecvA@@>>=AGPVXSV|xys155432110//-,+Uwqu_FD91)&%$$##"!!*bgRr6"6Rpx`E3$"%),+)&$" ,Bvecv@?AFMUZZSJA9Cuwv~35543210/.16>Dbxud*)'&%$##"!!%^h\gB5LmyaF6'&-1430-+*(&##-BvecvHRY]ZRI@<:98Aswt45542126?>=<<;:9>qxn=AKQSOG>60.--*=<<;98=r}nURND<400/.-+6kqcy/''$"$-@Xw}gN?6:IPUSPLJIFDB?<5AvedtD@@??><;;Ss~hPA9>QY^[WSSQPLIHL@vedsE@@>&AGPUYVPv~k?45432110//.-+/gnVsYniPD?>=<<;:97^}iS/8H\zmWIHRu~{xtpm?vedqG@@??>=<<;:95YxaxftoXJIU~}{u@vedpH@@??>=<;869Cb|qXJIW@vedoH@@?><9;CUkqYLIWAvedoH>==CTgr[LJV@vdblKQes[LGUƷ ?thqu\KDOݿ=w\JAF<x\H;>ȿ;x\G4,ɹ;z]F,?={]E% ж E%ڿ?)*Kj{I%$6Vrwutc-,*(!Bcwwtsr/%0,+*(,OmzvtuwpirvF$0-+*(9ZuzvuvwkK!*rta 1-++(#$Egzzwwxvb= Ersq.+-+*(&.Bq~zwyylT.,SnvrrvE"-+*(&$6DB7j|{weK9FA406ew\C7522AYdT>78BYpyyt_gwnbruD!-*(%$$& 9GG:26KbbO<7Uf]F;8AWn{{yiN5,,-(&2Klbqrp,(*(&$",6FLB67G_fVA9ZeW8(]suC+(&$"$.$=LK>6:BSm||nU;/.1/,4KecK2&'-Zss_,(&$"10EPI;8E[i^F=BSihN+8ALHBU}~}}||}x>A[qkN^zwwvvxwtsr qqptC&$! $)DUSE?I]hy\FB5&#FWp~~}}|{qpaD1*Ozwwvvuutsr qqpr^'$!  5LYPDBQfiXBcaKOGHgz~~}}|~f9/2D_txwwvvuutsr qqpqo*"$! ?SYMBG[kfQB@?acGLr{~~~}}| ~f:Rly|zxwwvvuvtsutr qqpptB$! $-IXWHCOdnbKBBDC?Xf?]lRF{~~}}|{yu}|yxwwvvwvkXC``gsrrqqppr]%! ! 9P]TFEWlm[GCDEDDC@MybqaG:7?y~~}}|{{|zzyxwyxsaN>6.12Ltrrqqppqn)!! #DW^QFK_ymSEDFFEEDDCCqoWA8:G^u~~~}}|{zzyyz{wjVD;4.+8A;7qrrqqpsA# $-K]\MGTlzPEFDEDDCAH=;ASl}~~}}|{zz{zs_K@92.381/4-/hsrqqpq]# !Kax~~}}|{{|}yhTE>81288,//)Uurqqpn(  !GZbUJQh{rO24UQSkQCEBADWo~~}}|}|q^KB<538;4$-/+@srqqp osA!$"/Ob`QL[r|gC0?b}xy]HCBCNd{~~}nRE@;57;9,&/-0nrqqp oq] %>Xf]OPdzw\;3LptTJRFBBHZr~}ptX<97<<2"  361Xuqqp opn'%J_fXNXo~pO6-I|`CFBERg~jOk#yfSCQXJ9>9*';8-Htqqp oos@$=4SfdUQbx|eE40/2ijAJ]vx`H;:b~o\LD>9=>c8" %+%*.-3qp ooq\SB\k`SWk{uY=322:=BIC?/.;5LN#,-& ..(Otqpor>$W3YkiZWh{zi]_dZKUuoWLMLHJ\urYE?F[rvcRJD?=CB8(!9:>Z!+/) ,.+6qqpop\JAhX]quc]ce`]j}|fRMOMLShiOBBQh}lZNHB?BE?0"8:5c%*1.$  $141frpom&\$Ou|p`afge`qt]POPNO]tv^HAI^vzJFBBFC7(1;8YB/)*;;.Rtponr?86^~`chhefcgyMRPOTglSDDTk~kZQi]CFG>.#4F@SM!*+%30*>rpo nq[*lv`VycigKRhcgyMQ\rzbKDL`yubTNHFIUS5)! &/45@:AU"*-('.,.mqppo nol%Gn^YZY}dheAMhdf~d~dFGWnl[RKHGFQONS"" %/74+ 9:6e "+/+  !/-'Xsppo nnr<Eh\Z]\[X~ehgCLe\iBdb{tcUPJGJLD5AEEZ#""  $.670%5:4[.!*1.% .-'Grppo nnpZEi[]\]]\[X~figCLj}C~l\SMJJNJ=/'$:GA](!!#,5:4) /;4TF *10' )-,1opponk#Dc[`h]]\[W~e`breVPLJNND5+&&'%7GA\5*5;9/$*:7FV/2*  '680]rpo nnmq<*DlxrZZ[ZV|rz ~o}KLNQJ=ZE' $5:1WroonnmnY A sd\Vi[ZQB85 43310DWUm@3-+=**)('&&)7NMUQ$  $,=FFNXMs7*,+**('*2;A>IFIW %.44,2:6>Z ".893%0-*0onnmlp; Are^[[^Z_XmS5876557=FKG=@PHpA+,*)+2;BA8-)ADB]" +%/670%#884e #0:93' #,+(]qonnm lnY 0Aykc\\__WHATUkV6767=FLKB82/;PKgG*++-2;CC<1)%b+,!'0792(783Y1&2<;4' ,+%Lronnm lmj! @tf_]^b]PD=;>TVjY6=><=TVgbIPKA954433215LK`^?GB8/*))(''&&$4E?XH8<6,#-84AV(6??5) &++*cpnnm llnX @mxi\PE@$?>=;>W^jjC<75665433106PT_j82,++**)(''%$3JJUW+% *=A@h=6* #36.Prnnm llmj  @t][v^ABBAA@>>CKP^]fk47765324;DHUTUg++,,+**)'&'+5=?KIJW "/8II?]. $*86-;pnnmlo9  V@m^]v^ACB@@BJRUNDQV_r6776556;DJJC8INPg.+,,+*))-5>A<3,ACB_" $1>DB5>:1WD (+(!++(,konmlmW  S@n^]u_@BCJRWSJA<:NW[t7667ED8*/73FP#+-*! ",*$Wqnmlkj  @n^]u`FRXWOE?=>=;KXYr9;DLOI@72H10@PHt9*07@EA8-(&%&%$7D?]6!(5AFD8+,868e&,-*! ,*%Epnmlko9  -?n^]tdXSIB@??>>=;JXYsLPLC:54433210?PGnI?FB;0+('&&%#5E>XB*7CHE9+!&762_%#+0.(  +*(.nnml kmV  ?n^]taDBA5@?>=<=Nb`uH<7566543320/?TRhR;2,*)*)(''&&%#0DAPZAIF9,!662YD*20)  %+)&]pml kki ?n^]tbACBAA?>?EMRW`Yt=676"54224:CKXQcN(+,+**)(''&%$'2LRR_7," 9@?PS% ,/)Kqml kkn8 X?n^]tcACB@AENTTMCIYUw@6766546;DJI@IHPMEb" &.3>` #7734nml kkmV k@n^]td@BFNTWPG?<:GZS|E6657=FLJC:3/3LKXf*,,+*)'+3AKNH;/?C=>=;FZR}J7>GMME<521103KMRf+A*-6CMOI[7 $-486/'!662Y6")+)# *)!Qqmml kkjn7 @n^]sgSNFA@@?>>=;DYPzYLOG?7432101JNOg-/8DOQJ=/(%+#5E>XF"(08:7/'561PJ&--*" *)$:omml kkjlV C?n^]seABBAA@?>=<>CKQT_WoU47654332003IZTwN=2+)(''&&%$"+EHK^:8/&+764f((/0.&  #*("Vpml kkjjn6 ?n^]sgACB@ADLRUNEBVTkY576;53226>LTT[MmB)++**)(''%%&+4:NOFg) (;=;[?,% *("Boml kkjjlU X@n^]rh@CFNTWPH@<;>UVj[5765548AMVUL>AOIdH),+**('&*/7>B?7EE:e/#+2DC:VH ,/1/lmlkkjgX?n^]qiLUYSKB>=>=<>UVhb366:DPWWM@50.n^]qjPMEA@@?>>=<=TVfk9FRYXNA621/9MI_`(,/6>ED@6-'$"6D=XK%,3761( .756g "((%&,( JplkkjkT>n^]qj@BBAA@?>>=;LW#*1896/'*761]) !(*($ ('$0nlkkjig->n^]ql@CBAA@?=<CMY_Y]Z]v776'5432149BFVXSi1+*)*)(''&&%%#!(BFDk=;4,#661GO &,-,&  !('PollkkjilT>n^]qp@BAAGP[`\QE=RWYt:6%5447=EKLG>LNIr4*+**)(''&%$&*17LNDe7"266:e,0.*"  (&"9nllkkjiig>n^]qq@IS]c]RF?;NPXWs;557;AHMLF>61.ENEv=*+**)'&&(.6=B?8AE=[:4@Bn^]ot[d^SG@>=>=<:NYWs>620.ANEkC)+)*,2;BC@80'"5C>=<9KZXwRQOG>74232110.>NHdH(-18?ED@6.'$#"!3C=JX"*0762) #65/MK 6M_lomllkkji!f>m^]px?BBAA@?>=5,(%#$#!-BAC^"(/5750'553;_ 2L_oprommlkkj#ijko8>m^]o{ACBA@??BHPUTV_TI46M5433210//>SQbjA;4,('&'&&%%$#"(@B=m(!'/5:85+#/643e -J[nrsqonnmlk%lmnf[G#=m^]o}ABABFLSWVNG>HYQN56,5432138@FKWRXi+*)*)(''&&%%$#!%?B;d?9;81)!*64/Z5  *FYmrurpponnm=lmnnog\H. =m^]o~CHPWZWNG@<;:GZP}Q5665447=EJMI@n^]oS[WOGA>=>=<:GZOwT457;BHNLH?72.1JLLj/*)('&%%',3:@=BI@XS3/.9KUguwvtsrqqpopqqrj^J3"  >n^]oJGB@?>>=<:FZPpY=GMQNG>620/0JLHu6*('(,2:?C?:1'4B>=;9DZWmePME>7425110//HMDv=(*-19?DC?6.'#".B@Bb %5QcuwzxvuutsrsttumaM7( BIRc^md9754332110/.CMCjG6>DFC;3,'$##!(@A;n)#3Mat{||xwwvvuutuvvocN8)!"%#!;n^]nCBB@@?BGOTVRMZVii2605433211/.-AQOeWDA91*'&%&%%$##!%@A7b3"2H`q|~}{yxwwvvuvvwxocO;-!"&),+)&$" UUgr46054331027=CMYSdZ+*)((''&&%%$##!#=A:X<1D^n}|{{zzyxwxxyyqdP=/&'.2430-+*(&##;m^]mHRWZXPHA><;;>UUdx56(4336=<;>UUby6557Whx~~}}|{{||}}uhT?4-/;CEC?=<:64405<:m^]lEBA@A@?>>=<;=TU]y>=;:AGM_b]wC96544332110/.1ILGx5&-;LfuzkXE;9@S[`[WSSQPLIHL1;m^]kFBB@@?BGNSVTMXYUx>56654332110/..GI@fQbt{m\H=:DZejfa\[YXVUSU.;m_]jFACGMTWWSJD>;QXT}C5665433210.,.9Pbq|}n[I@?Ianqoifeba^ZXZH<;;:OXSI4665421/07H[r~o]J@BMjx{sokjhgcf_';m_]jTVNGB?>=>=<;:MYQM454227GXp~p^KBCPp}xwurqknf":m_]iGBA@A@?>>=<;:KYPN18EUm|s`LCFRw~{xtpm9m_^iHBBAA@?>>=<;9HULm]i|tbNEGU}{u9m_^hHBBAA@?>=;9:CVjyucOEIW:m_^hIBBA@><=CRczwdPEHW9l_^gH@??DQaxxfRFGV9l^\eKP_vzgREGUƷ 8lbiy{gSEBRݿ6~jSD?J 6kTB8>ȿ7lTB1,ɹ6nU?)U6pU@" ж@%ڿt8mk@#+E$"fc=2,QjHɿƤhչ2/뻾kKŽȥkֻ21mMǿʧn׽22n Oͨpؿ35n Rϩr37o Uѫu39o WӬ!w3;p Yծ#z35%ҩoP/()-48:;7. ϥiH& %,/22.'͡cA#&)*'! ʝ]9 ! ǙV1   ǗQ*  Q* schismtracker-20180209/sys/macosx/Schism_Tracker.app/Contents/Resources/moduleIcon.icns000066400000000000000000001613751323741476300311650ustar00rootroot00000000000000icnsics#H??????????????????????????is32 Ƽ żѱ о˶ ׽ҥp{٣ğΛ˧ٺԶҥͻТȦ򵮫򩨭ɧ ļ ůҲ ˷ ӧ۸ԴѶвŸÿڵݹѾﻹɼ¹ɪ Ƽ Žұ о˶ ׽ҥq|٤ŠϜ˧ٻշҦμѣȨ򶯫󪩭ɧs8mkghhhhi@KfLfLeL>LiLgLgLgLhLhLhLgNj7K ICN#il32 ɾBǝۢܕ0ѭ}˄uǼĩѶʷ<ͻ|w9ˮz_`̸h`{?ɰ3ᰣ⿑7ڞ}֨nh;߿ɫ:ӿʤ鰢Ψ:z▀d?ͻѵ>µ>ĎƘ╆죓w>µ³ʮ¢<ŪֵƗ=ÿᓅrmC˷ٸAܳC tpDӫ@٤@b]@ۯEĴ5UUUɾBѧݱܧο0Ѻ˒º¶Ūей<Ҿy9ˮ̽?ͷ3ͮ7⾩;οӺ:ү鿭е:ý噊㤒x?Ͼӹ>Ŀȹ>ݴӺ⺭¶>¶ij<ɾҳ=߷ʣCŸƱAƶC ѥD@௡@wr@߷Eƀó5UUUɾBǝۢܖ0Ѯ}˄vǽĩѶ˷<ͼ|w9ˮ{aa̸jb{?ɰ3ⲥ7ڠթpj;ʫ:ˤ鱢Ω:z◁f?ͻѶ>ö>Őƚ◇줔y>¶´ʯ£<ūֶǘ=ᕇupC˸ٹAܴC vrDԭ@٥@c^@۰EĴ5UUUl8mk*<<<<<<<<<<<;2 (778998999;:9:987777+ 36777667777677666796' ich#Hih32ǃU^UROGADHƾ؀ƼkփڝhvWiԁe~ƺx˟dlƶƌǁüʾλ ļƱ!ɵā!ƺɅ"߼ʵӢ#tkgmuyƲe`i]`%÷ia\^w%ڶΩqshaU ր̿%%Ƌ{Љxl3¥xyugo3%ꐟtϲȇ}m3%θԾ3%ꬒȔŤ͛v3%LjoݼktZɪ3%ܱßcЂ|mi^ɪ3%ҞæĦ3%ʾȫ3%⦯轋ᝲӰ3%rÿ朁ʙ{sڱ3%‹uߒrҭ3%ɽٽƮҾɺ˫3%ӴɯĬ߮ά3§ܡ} ӈsq׳3±y ykuԱ3%ɑ۫ʬ3%Źɬ3ӳ3%݋u|sܶ3%{nu׳3%ì̭3˻˭3%n~cֱ3  qn\hٱ3%ه~hа3  ͸Բ3̀ ´U3UUUU^UROGADHځր ƾƼۘۮ}m{Փzǻ˦u}ŽʖǁĽʾμ ļƱ!ɵā!ʺɂ"׸#ukmoxyƲ`÷%λU%IJ%ʼ%ֳװ3%ԧ3%뷼Ҭ3%Ͻô3%뻢եͳҬ3%òѕ訄ȔnȪ3%˩ȯyՓ}ztɪ3%ӥéĦ3̀ȫ3%ҳ±ƳҰ3%Ѯ澮ܻҨױ3%ղΰ߿淮åѭ3%ʸƽ˫3%˼»ά3%аܿݰճ3߹ ⨪ұ3%Գĵɬ3%÷ɬ3ʿӳ3%趥ڶ3%窭Գ3%ӿ˭3˭3%윁vֱ3 冂p}ر3%ޔyа3  ͹Բ3̀ ´U3UUUU^UROGADHƾƼlքڝiwXjԂf~ƺy˟dlƶƍǁļʾλ ļƱ!ɵā!ƺɅ"߽ʵԣ#tkgmvyƲgbj_`%÷kd^`x%۷ΪsujcU%%%Ǎ}ыzn3æz|wjp3%꒡vϳɉ~o3%ιԾ3%ꭓȕƥΛw3%ȉpݽlt[ɪ3%ܲàdЃ}nj_ɪ3%ҟĦĦ3%ʾȫ3%⨰辍៳Ӱ3%t枃˛}vٱ3%Œxtҭ3%ɽپǯҾɺ˫3%ӴʰĬ߯ά3ĩܢ ԉus׳3ò| {mwԱ3%ɒ۬ʬ3%Źɬ3ӳ3%ݍw}uܶ3%}pw׳3%ĭ̭3˻˭3%pdֱ3 rp\iر3%ڇ~iа3  ͸Բ3̀ ´U3UUUh8mk    TD mU uRwSwSwRwRwSwRwRwRw5w www w w w!w!w!w w!w!w!w!w!w"w#w#w!w!w!w!w!w!w w w w wwx aߞ #@RUVWWVWWVVVVWXWVVVWWWVVUUVY[ZVK/ it32o߂݀ ր߂ڀ¿ހہ ҀÿZ߀݀ځ¿%ں܀ڀ ȫԾр¾-}߀ ޭ\7Bځ Ѣ>?ѧww$׬))˭7ڵ؀ >8@̀'¥/ځ0>հk[ymĚ@¾ȿ$qhTrپֹjfkAp%Ot.lNȺA»ʾ$?ۡ67vlmyeGxϯK{KuJµ@ÿʾ$@Ǧtm ϐp'ϲN˼Y=HdZ?ɻʾA~F:}ʚhhʀ 1x?ʣ˞ʽEDӂCa/P4GC@H5+5q9964ʽFǪȣλɯʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽׁ ۀƽʽ䲠Ⱡ 㲠௝܀ޮרŃʽ$ZZYYYY YYXXހ݀WWUTƿ~uk> ʾ$ρހ#ֿzrcaeccdZ\losɽނ%~wrmkiijkmpruy~ނĻ~yvsr twz|~߀&Ľ}{ujjw|}wnp~,߁%ǿmiDDjmiFGn#ýv9zl4ws9wj4٥(bZ~i3__Wxf3`+½WmDgUTfCbS&XuY{qUTlSogRäހڀڂ-ĹcQYja]OU{a\赢絢-浡ᰝˡŃ2Jv1|Hw[[ZZZZZZZZ%YYXXUT\YV\USQUсЁ&Ǥ ہĽ ߀ƿ݀܁»õа݀å|[U٦[ZـѠXWUT حߠäS`GӓWlȯ"DIcZʤ!ŃXށߋ 6E涠뷠"I| 3Ȥ+\L{dށMIEOϽ:/9}@@@@*X&HDAIȤށݳӁӄܯȤހހɤ ߀ʤ ƀ ͤ߿ӳζű׳ŵϤwWaځު]\ԧV^wommZYҤ JK"ԵWDZD;]W_jtğ?C:Ԥ5ҶP?Ǣ]漅y7Ψ`ԫ=ݠ?ͨ|s6֤oÀ_mPlœ`Ґgѱݠ?gO|hդ3WV΋zSܶx7ʦ_湁u7{}r6פ2ٔhƾaݵkPl_kOhhN{hؤ3oΌޫ|޻gih_gheceeפ7S⦠̲Ir]gvϿ_s]g}rlZbvpդ2Dǐo2S]꺨ʀʁ3R2PԤ2Ksezfc`gVM;V]]]]\\fb_f`][bҤἺ仸ѤФ½ҤſӤ صκ֤  ـ 殫^\˚ZYפ  δD;D;٤ Ćy7~s7٤ nPijO}iؤhkgdff֤  t^htn[cxrҤïπ­­̂ ̂3S2QФ__^^^^^^^^ ^^^^]]gd`gb^\bͤ߁翹žˤ ýʤ ľʤڶ̸ˤ 篣DDǗBBͤ ϱ##Ϥ wepp`zoϤX3vUTw2oSϤSTQP|PxPϤ cDPna]AMh_ΤŪéɀ©Ɂê¨ȁêǁ 7 5̤DDCCCC CCCC CCCCBBOJGPKGDLʤށށށ݁鹵ſɤſʤˤ¿Ϥ߂݀ ր߂܀Հ¿ހہ ҀÿZ߀݀ځ¿%܀ڀ р¾-̘߀ iEPځ ơLRď$96Eں؀͹-"NIǸ'պ?)ځ0ϦO}jōyŧN¾ȿ$΁yi~ͅxyS%`=\ßO»ʾ$?FE͇}~}#Y^YX¾Nÿʾ$@ϙ̌4Ɩ\qKVuhM½ʾAώTKʰ%uuʙ?R«˽ʽERӍQp=^BVRNVC9!C|HGDBʽFǯȨξɲʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽ݁ ۀƽʽ́ Ȁ܀ƹʽ$䓓ᒒ ᑑޑހ݀ܐԎƿ~uow ʾ$؁ހ#zrlieccdjllosɽ ނ%~wrmkiijkmpruy~ނĻ~yvsr twz|~߀&Ľ}{|},߁%ǿ}}#ýywyw٥?ww+½&äрсЁ-˿Ūxw锔哓㓓 哓㓓%㒒ޑύƐ ف؁&Ǵ ہĽ %ƿ݀܁»݀þۨʾـĶ ~ȃǾ}ʀÂ}{(Ԃʪǯ{ͧë{y)ךϛNJ͚ʼn)١Κϙ *ѪҠարր*lj׳xȿܷyx 디ꔔ암锔{$ϕԐٖ߁ ݂,ɷ)Ŀ )¿» ߁ ܀ ہJSonpo ҮRXК.zHQuhgeKQäanG֞fy/Ƽ#SMU^i)."Ƥ)%DՆqј׵Tրպ'*ƺzgvǤ]ڀISfAdҩTטZ*_>]ʤUe܏Ήud`_ϨTޓdJ*jqs^ZXɤ8C⦞5yP]oʦTCZ*ýpMWthʤ0טfށߤCXԼּ1Z*AȤ9kZrށ\XS]ϿH<#GNNNN8g3)WSOWȤށݸցքݵȤހހɤ ߀ ʤ ƀ ͤ2Ϥ׻եށĕլãҤ ؊"Ґ΄}̓ѝĀ}Ԥ5ҍĖְ϶{п̀ŀ¦{֤۴ׯԠԌη⺛ƀդ3Ւ̾ڲثԙ՝̶綠繐ŀ—Ԥzx޾ڱӺڥ˨ɴҗĀĞӤ8߼ƀʩ޻x̍؀̗ÀƮxҤ2纖٦ܙƎ}镕͈рФ3Ф2Ф3ФӀ¿ҤۯȀ!ƹźԤ֠ԸȄ~Ǔƃ¹}}֤2Ӏҍϭ̴{̻ҬDZ{{פ2齝ԖϞыʵԞ̋ؤ3ڳβҜʴٝפď·ހ٥ʨȴ㧕ȧդ2ٸx̍ڀځyʌxԤ2҉岧ᛗō}ꕔ񕕂蛗’Ҥ!ѤФ½ҤſӤ ĺ֤  ـ ŕϾפ  ф~Ŀ}٤ ݲж{{٤ ܢՌؤߟ֤*訕̩Ҥ߀܂ ݂y΍xФ 훘ƕͤ žˤ ýʤ ľʤ ¸ˤ ӮSX̻QUͤ 0Ĺ#0#Ϥ ߉sЗn|ϤiBie@aϤfbdc^^Ϥ |R^rvO[ymΤπρ΁́EB̤RQQQQQ QQQQ QQQQPP_YU^ZVRZʤ鿻ſɤſʤˤ¿Ϥ߂݀ ր߁ ׀Ԁހہ ҀÿZ߀݀ځ¿%ڻ܀ڀ ȭԾр¾-~߀ ޮ\7Bځ ѣ>@ѩxw$ح*)̯7ڶ؀ ?9À'æ0ځ0?ձl\zmĚA¾ȿ$riUrٿֺlglBq%Pv/nOȺB»ʾ$?ۣ77wmnzfHxϰL|LwK¶Aÿʾ$@ȧun ϐq(ϳO˽Z=Ie[@ɻʾAG:~ʛhiʁ 2z@ʥˠʽEEӂCb0Q5HCAI6,6q::75ʽFǪȣλɯʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽׁ ۀƽʽ䳢ⳡ 㳡౟܀ޯתƃʽ$\\[[[[ [[Z[ހ݀ZZXVƿ~ukA ʾ$ρހ#zrdbeccdZ\losɽ ނ%~wrmkiijkmpruy~ ނĻ~yvsr twz|~ހ$Ľ}{ullw|}xoq~,߁%ǿokGHjokIIo#ýw⯣DE巳ղn;CfYYW=?äTaGԔXmɰ#E=HФP\"Ƥڬ%7udw֩GրǡۡơiZsjǤUׇOFV3wWʐGǂMͰ٠Oq0jOɤUXvvޭ}iQSRǏG{W<]dgLwM~sKɤ86❕̯(aCOobG6JòY?Id[ʤ"ƄYށߍ 6F渡빡#J~ 4Ȥ,]M|eށNIFOϽ;/:~AAAA+Y'IEBIȤށݳӁӄܯȤހހɤ ߀ʤ ƀ ͤ2ԵηƲ״ŶϤzZcځެ__ԩZ`yoop\\Ҥ NN#նZȳG=_YalvġBE=Ԥ5ӷSBȣ_澇{:ΪaխAݢBͪ~t:֤qĂapSnŖaғiҲݢBiQjդ3YZύ}?fimkÓa݋qYBu{|eggԤڸ94ަՆLmu`jxaVdBɹn\dztӤ8Ùg\۬@ޖ5U_麩ɀFdBƊ5TҤ2gd|RpWgdbhXO=X____NqIBd_^dФ3׼ܹڄ߹Ф2Ф3Фͷ΀˯˴ŧŶҤ6Ĉ[aԴssqqӦ__ϥգ^]][ԤnyϝoH=_Ya¬F=F=֤2ݳBVܷz:ʧa溄x:}~t:פ2ږkǿcݶnSn“anRjjQ}jؤ3rϏޭ}޼ilj’aikgeggפU⨡ͳLހu`ixau`iuo\exrդ2FȒr5V`껪ʀʁ6U5TԤ2Oug|iebiXO>X````__idbhc_]dҤύ伹ѤФ½ҤſӤ طκ֤  ـ 毬`_˜\\פ  ϶G=F=٤ ň{:u:٤ pSklQkؤkmhghh֤ v`jvp]fztҤŰïπïï͂ ï®͂­6V5TФaa`a`a`a`a `a`a``jebid`^eͤ翺žˤ ýʤ ľʤڷ̸ˤ 豣EEǙCCͤ г$$Ϥ xfqp`{pϤY4wVUx2pTϤTURQ}PyPϤ dDQob^BNi`ΤƬĪɀêɁūêɁūéȁé 8 6̤DDCCCC CCCC CCCCCBPKHPLHEMʤ߁߁ށ݁鹶ſɤſʤˤ¿Ϥt8mk@    E   9  ,n  %6|  *>Y .EZ 1I[ 3KZ 3LZ 3LZ 3M[ 3M[ 3M[ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M[ 3M[ 3M[ 3M[ 3MZ 3MZ 3M[ 3M[ 3M[ 3MK 3M-3M% 3MF 3M) 3M23M:% 3MC+ 3MH. 3MM23MO43MP53MP6 3MQ53MP53MP5 3MP5 3MQ5 3MQ5 3MR5 3MS6!3MS6!3MS6!3MS6!3MS6!3MS6!3MS6!3MR5 3MR5 3MR5 3MR5 3MR5 3MS6!3MS6!3MS6!3MS6!3MS6!3MR6!3MR5 3M[5 3MZ5 3MZ5 3MZ5 3M[5 3M\5 3M]5 3M^6!3M_6!3M_6!3M_6!3M^6!3M^63M\43M\43M\43M]43M^43M_43M`43Ma43Ma43M`43M^43M]43M\43M[43MZ43MZ43MQ43MR43MS43MS43MS43MS43MS43MS43MS43MR43MR43MR43MR33MN33MN33LM33LM33KL31IJ1.EF. *>B*  %6I[itz~xl^J6%  ,;IU]beffghhhiijjgggggggggggggggghhhhiijjjjiihhhhgghhhhiijjjgggggggggfgggggjklmmnnjjhe`WJ<,   ,6>EIKLLMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMNNNNNNMMLJF?7,  %*.133333344444444444444443333333344444444443333333344444444443333333333333444444442/*%    schismtracker-20180209/sys/macosx/ibook-support.c000066400000000000000000000053661323741476300217400ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" int macosx_ibook_fnswitch(int setting); /* FIXME: ugliness */ #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, (IOByteCount *) &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 schismtracker-20180209/sys/macosx/macosx-sdlmain.m000066400000000000000000000613551323741476300220540ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ /* 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" #include "osdefs.h" #define Cursor AppleCursor #import #undef Cursor @interface SDLMain : NSObject - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; @end #import /* for MAXPATHLEN */ #import /* 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 CPSSetProcessName ( CPSProcessSerNum *psn, char *processname); extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); 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 @interface NSApplication(OtherMacOSXExtensions) -(void)setAppleMenu:(NSMenu*)m; @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, (unsigned char *) 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 = (NSMenuItem*)[appleMenu addItemWithTitle:@"Help" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(1)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"help"]; [appleMenu addItem:[NSMenuItem separatorItem]]; menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"View Patterns" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(2)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"pattern"]; menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"Orders/Panning" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(11)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"orders"]; menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"Variables" action:@selector(_menu_callback:) keyEquivalent:[NSString stringWithFormat:@"%C", NSF12FunctionKey]]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"variables"]; menuItem = (NSMenuItem*)[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*)[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:appleMenu]; [[NSApp mainMenu] addItem:menuItem]; /* File menu */ otherMenu = [[NSMenu alloc] initWithTitle:@"File"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"New..." action:@selector(_menu_callback:) keyEquivalent:@"n"]; [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; [menuItem setRepresentedObject: @"new"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Load..." action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(9)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"load"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Save Current" action:@selector(_menu_callback:) keyEquivalent:@"s"]; [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; [menuItem setRepresentedObject: @"save"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Save As..." action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(10)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"save_as"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Export..." action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(10)]; [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; [menuItem setRepresentedObject: @"export_song"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Message Log" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(11)]; [menuItem setKeyEquivalentModifierMask:NSFunctionKeyMask|NSControlKeyMask]; [menuItem setRepresentedObject: @"logviewer"]; menuItem = (NSMenuItem*)[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:otherMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Playback menu */ otherMenu = [[NSMenu alloc] initWithTitle:@"Playback"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Show Infopage" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(5)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"info"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Play Song" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(5)]; [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; [menuItem setRepresentedObject: @"play"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Play Pattern" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(6)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"play_pattern"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Play from Order" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(6)]; [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; [menuItem setRepresentedObject: @"play_order"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Play from Mark/Cursor" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(7)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"play_mark"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Stop" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(8)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"stop"]; menuItem = (NSMenuItem*)[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 = (NSMenuItem*)[otherMenu addItemWithTitle:@"Sample List" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(3)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"sample_page"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Sample Library" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(3)]; [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; [menuItem setRepresentedObject: @"sample_library"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Reload Soundcard" action:@selector(_menu_callback:) keyEquivalent:@"g"]; [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; [menuItem setRepresentedObject: @"init_sound"]; menuItem = (NSMenuItem*)[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:otherMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Instrument menu */ otherMenu = [[NSMenu alloc] initWithTitle:@"Instruments"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Instrument List" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(4)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"inst_page"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Instrument Library" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(4)]; [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; [menuItem setRepresentedObject: @"inst_library"]; menuItem = (NSMenuItem*)[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:otherMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Settings menu */ otherMenu = [[NSMenu alloc] initWithTitle:@"Settings"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Preferences" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(5)]; [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; [menuItem setRepresentedObject: @"preferences"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"MIDI Configuration" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(1)]; [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; [menuItem setRepresentedObject: @"midi_config"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Palette Editor" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(12)]; [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; [menuItem setRepresentedObject: @"palette_page"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Font Editor" action:@selector(_menu_callback:) keyEquivalent:@""]; [menuItem setRepresentedObject: @"font_editor"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"System Configuration" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(1)]; [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; [menuItem setRepresentedObject: @"system_config"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Toggle Fullscreen" action:@selector(_menu_callback:) keyEquivalent:@"\r"]; [menuItem setKeyEquivalentModifierMask:(NSControlKeyMask|NSAlternateKeyMask)]; [menuItem setRepresentedObject: @"fullscreen"]; menuItem = (NSMenuItem*)[[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 (int argc, char **argv) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; SDLMain *sdlMain; CPSProcessSerNum PSN; /* Ensure the application object is initialised */ [SDLApplication sharedApplication]; /* Tell the dock about us */ if (!CPSGetCurrentProcess(&PSN)) { if (!macosx_did_finderlaunch) { CPSSetProcessName(&PSN,"Schism Tracker"); } if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) if (!CPSSetFrontProcess(&PSN)) [SDLApplication sharedApplication]; } /* 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]; } // ktt appears to be 1/60th of a second? unsigned int key_repeat_rate(void) { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; int ktt = [defaults integerForKey:@"KeyRepeat"]; if (!ktt || ktt < 0) ktt = 4; // eh? ktt = (ktt * 1000) / 60; return (unsigned)ktt; } unsigned int key_repeat_delay(void) { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; int ktt = [defaults integerForKey:@"InitialKeyRepeat"]; if (!ktt || ktt < 0) ktt = 35; ktt = (ktt * 1000) / 60; return (unsigned)ktt; } int key_scancode_lookup(int k, int def) { switch (k & 127) { case 0x32: /* QZ_BACKQUOTE */ return SDLK_BACKQUOTE; case 0x12: /* QZ_1 */ return SDLK_1; case 0x13: /* QZ_2 */ return SDLK_2; case 0x14: /* QZ_3 */ return SDLK_3; case 0x15: /* QZ_4 */ return SDLK_4; case 0x17: /* QZ_5 */ return SDLK_5; case 0x16: /* QZ_6 */ return SDLK_6; case 0x1A: /* QZ_7 */ return SDLK_7; case 0x1C: /* QZ_8 */ return SDLK_8; case 0x19: /* QZ_9 */ return SDLK_9; case 0x1D: /* QZ_0 */ return SDLK_0; case 0x1B: /* QZ_MINUS */ return SDLK_MINUS; case 0x18: /* QZ_EQUALS */ return SDLK_EQUALS; case 0x0C: /* QZ_q */ return SDLK_q; case 0x0D: /* QZ_w */ return SDLK_w; case 0x0E: /* QZ_e */ return SDLK_e; case 0x0F: /* QZ_r */ return SDLK_r; case 0x11: /* QZ_t */ return SDLK_t; case 0x10: /* QZ_y */ return SDLK_y; case 0x20: /* QZ_u */ return SDLK_u; case 0x22: /* QZ_i */ return SDLK_i; case 0x1F: /* QZ_o */ return SDLK_o; case 0x23: /* QZ_p */ return SDLK_p; case 0x21: /* QZ_[ */ return SDLK_LEFTBRACKET; case 0x1E: /* QZ_] */ return SDLK_RIGHTBRACKET; case 0x2A: /* QZ_backslash */ return SDLK_BACKSLASH; case 0x00: /* QZ_a */ return SDLK_a; case 0x01: /* QZ_s */ return SDLK_s; case 0x02: /* QZ_d */ return SDLK_d; case 0x03: /* QZ_f */ return SDLK_f; case 0x05: /* QZ_g */ return SDLK_g; case 0x04: /* QZ_h */ return SDLK_h; case 0x26: /* QZ_j */ return SDLK_j; case 0x28: /* QZ_k */ return SDLK_k; case 0x25: /* QZ_l */ return SDLK_l; case 0x29: /* QZ_; */ return SDLK_SEMICOLON; case 0x27: /* QZ_quote */ return SDLK_QUOTE; case 0x06: /* QZ_z */ return SDLK_z; case 0x07: /* QZ_x */ return SDLK_x; case 0x08: /* QZ_c */ return SDLK_c; case 0x09: /* QZ_v */ return SDLK_v; case 0x0B: /* QZ_b */ return SDLK_b; case 0x2D: /* QZ_n */ return SDLK_n; case 0x2E: /* QZ_m */ return SDLK_m; case 0x2B: /* QZ_, */ return SDLK_COMMA; case 0x2F: /* QZ_. */ return SDLK_PERIOD; case 0x2C: /* QZ_slash */ return SDLK_SLASH; case 0x31: /* QZ_space */ return SDLK_SPACE; default: return def; }; } schismtracker-20180209/sys/macosx/midi-macosx.c000066400000000000000000000127511323741476300213310ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 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; unsigned long 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, const 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); } /* msec to nsec? */ m->x = MIDIPacketListAdd(m->pl, sizeof(m->packet), m->x, (MIDITimeStamp)AudioConvertNanosToHostTime( AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) + (1000000*delay)), len, data); } static void _macosx_drain(struct midi_port *p) { struct macosx_midi *m; m = (struct macosx_midi *)p->userdata; if (m->x) { 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 (fullName && !deviceName) 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) { static struct midi_driver driver; memset(&driver,0,sizeof(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; driver.drain = _macosx_drain; if (MIDIClientCreate(CFSTR("Schism Tracker"), NULL, NULL, &client) != noErr) { return 0; } if (MIDIInputPortCreate(client, CFSTR("Input port"), readProc, NULL, &portIn) != noErr) { return 0; } if (MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut) != noErr) { return 0; } if (!midi_provider_register("Mac OS X", &driver)) return 0; return 1; } #endif schismtracker-20180209/sys/macosx/osdefs.c000066400000000000000000000033251323741476300203770ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "osdefs.h" #include "event.h" #include "song.h" const char *osname = "macosx"; int macosx_sdlevent(SDL_Event *event) { if (event->type == SDL_KEYDOWN || event->type == SDL_KEYUP) { if (event->key.keysym.sym == 0) { switch (event->key.keysym.scancode) { case 106: // mac F16 key event->key.keysym.sym = SDLK_PRINT; event->key.keysym.mod = KMOD_CTRL; return 1; case 234: // XXX what key is this? if (event->type == SDL_KEYDOWN) song_set_current_order(song_get_current_order() - 1); return 0; case 233: // XXX what key is this? if (event->type == SDL_KEYUP) song_set_current_order(song_get_current_order() + 1); return 0; }; } } return 1; } schismtracker-20180209/sys/macosx/volume-macosx.c000066400000000000000000000056331323741476300217170ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 int macosx_volume_get_max(void); int macosx_volume_get_max(void) { return 65535; } void macosx_volume_read(int *left, int *right); void macosx_volume_read(int *left, int *right) { UInt32 size; AudioDeviceID od; OSStatus e; UInt32 ch[2]; Float32 fl[2]; int i; if (left) *left = 0; if (right) *right = 0; size=sizeof(od); e = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &od); if (e != 0) return; size=sizeof(ch); e = AudioDeviceGetProperty(od, 0, /* QA1016 says "0" is master channel */ false, kAudioDevicePropertyPreferredChannelsForStereo, &size, &ch); if (e != 0) return; for (i = 0; i < 2; i++) { size = sizeof(Float32); e = AudioDeviceGetProperty(od, /* device */ ch[i], /* preferred stereo channel */ false, /* output device */ kAudioDevicePropertyVolumeScalar, &size, &fl[i]); if (e != 0) return; } if (left) *left = fl[0] * 65536.0f; if (right) *right = fl[1] * 65536.0f; } void macosx_volume_write(int left, int right); void macosx_volume_write(int left, int right) { UInt32 size; AudioDeviceID od; OSStatus e; UInt32 ch[2]; Float32 fl[2]; int i; size=sizeof(od); e = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &od); if (e != 0) return; size=sizeof(ch); e = AudioDeviceGetProperty(od, 0, /* QA1016 says "0" is master channel */ false, kAudioDevicePropertyPreferredChannelsForStereo, &size, &ch); if (e != 0) return; fl[0] = ((float)left) / 65536.0f; fl[1] = ((float)right) / 65536.0f; for (i = 0; i < 2; i++) { e = AudioDeviceSetProperty(od, /* device */ NULL, /* no timestamp */ ch[i], /* preferred stereo channel */ false, /* output device */ kAudioDevicePropertyVolumeScalar, sizeof(Float32), &fl[i]); if (e != 0) return; } } #endif schismtracker-20180209/sys/oss/000077500000000000000000000000001323741476300162575ustar00rootroot00000000000000schismtracker-20180209/sys/oss/midi-oss.c000066400000000000000000000104051323741476300201470ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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_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, const unsigned char *data, unsigned int len, UNUSED unsigned int delay) { int fd, r, n; fd = opened[ n = INT_SHAPED_PTR(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 = PTR_SHAPED_INT(-1); 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 = NULL; j = 0; while (midi_port_foreach(p, &ptr)) { i = INT_SHAPED_PTR(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 = NULL; while (midi_port_foreach(p, &ptr)) { if (INT_SHAPED_PTR(ptr->userdata) == pfd[i].fd) { src = ptr; } } midi_received_cb(src, midi_buf, r); } } } /* stupid gcc */ return 0; } static void _tryopen(int n, const 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|O_NONBLOCK); if (opened[n+1] == -1) { opened[n+1] = open(name, O_RDONLY|O_NOCTTY|O_NONBLOCK); if (opened[n+1] == -1) { opened[n+1] = open(name, O_WRONLY|O_NOCTTY|O_NONBLOCK); if (opened[n+1] == -1) return; io = MIDI_OUTPUT; } else { io = MIDI_INPUT; } } else { io = MIDI_INPUT | MIDI_OUTPUT; } ptr = NULL; if (asprintf(&ptr, " %-16s (OSS)", name) == -1) { return; } midi_port_register(_oss_provider, io, ptr, PTR_SHAPED_INT((long)n+1), 0); free(ptr); } static void _oss_poll(struct midi_provider *_oss_provider) { 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) { static 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 schismtracker-20180209/sys/oss/volume-oss.c000066400000000000000000000056601323741476300205430ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" #include "log.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_volume_get_max(void); int oss_volume_get_max(void) { return VOLUME_MAX; } void oss_volume_read(int *left, int *right); void oss_volume_read(int *left, int *right) { int fd; uint8_t volume[4]; fd = open_mixer_device(); if (fd < 0) { log_perror(device_file); *left = *right = 0; return; } if (ioctl(fd, MIXER_READ(SCHISM_MIXER_CONTROL), volume) == EOF) { log_perror(device_file); *left = *right = 0; } else { *left = volume[0]; *right = volume[1]; } close(fd); } void oss_volume_write(int left, int right); void oss_volume_write(int left, int right) { int fd; uint8_t volume[4]; volume[0] = CLAMP(left, 0, VOLUME_MAX); volume[1] = CLAMP(right, 0, VOLUME_MAX); fd = open_mixer_device(); if (fd < 0) { log_perror(device_file); return; } if (ioctl(fd, MIXER_WRITE(SCHISM_MIXER_CONTROL), volume) == EOF) { log_perror(device_file); } close(fd); } #endif schismtracker-20180209/sys/posix/000077500000000000000000000000001323741476300166155ustar00rootroot00000000000000schismtracker-20180209/sys/posix/schismtracker.1000066400000000000000000000251511323741476300215450ustar00rootroot00000000000000.TH SCHISMTRACKER 1 "Jan 18, 2018" .\" Disable hyphenation, it's awful .nh .SH NAME schismtracker \- tracked music editor based on Impulse Tracker .SH SYNOPSIS \fBschismtracker\fP [\fIoptions\fP] [\fIdirectory\fP] [\fIfile\fP] .SH DESCRIPTION \fBschismtracker\fP is a tracked music module editor that aims to match the look and feel of Impulse Tracker as closely as possible. It can load most common tracker formats, supports saving as IT and S3M, and can also export to WAV and AIFF. .SH OPTIONS .P .TP \fB\-a\fP, \fB\-\-audio\-driver\fP=\fIDRIVER\fP[:\fIDEVICE\fP] Audio device configuration. \fIdriver\fP is the SDL driver to use, e.g. \fIalsa\fP (ALSA), \fIdma\fP or \fIdsp\fP (OSS); \fIdevice\fP is the name of the device itself, for example \fIhw:2\fP or \fI/dev/dsp1\fP. .TP \fB\-v\fP, \fB\-\-video\-driver\fP=\fIDRIVER\fP SDL video driver, such as \fIx11\fP, \fIdga\fP, or \fIfbcon\fP. Note that this is different from the video driver setting within the program, and is unlikely to be useful. .TP \fB\-\-video\-yuvlayout\fP=\fILAYOUT\fP Specific YUV layout to use: \fIYUY2\fP, \fIYV12\fP, \fIRGBA\fP, etc. This is probably best left alone under normal circumstances. .TP \fB\-\-video\-size\fP=\fIWIDTH\fPx\fIHEIGHT\fP Set the size of the video display. .TP \fB\-\-video\-stretch\fP=\fIVALUE\fP Fix the aspect ratio. (Probably does nothing!) .TP \fB\-\-video\-gl\-path\fP=\fIPATH\fP Specify path of OpenGL library. .TP \fB\-\-video\-depth\fP=\fIDEPTH\fP Specify display depth, in bits. .TP \fB\-\-video\-fb\-device\fP=\fIDEVICE\fP Specify path to framebuffer. Typical value is \fI/dev/fb0\fP. .TP \fB\-\-network\fP, \fB\-\-no\-network\fP Enable/disable networking (on by default). Used for MIDI over IP. .TP \fB\-\-classic\fP, \fB\-\-no\-classic\fP Start Schism Tracker in "classic" mode, or don't. This is mostly cosmetic, although it does change the program's behavior slightly in a few places. .TP \fB\-\-display\fP=\fIDISPLAY\fP X11 display to use. .TP \fB\-f\fP, \fB\-F\fP, \fB\-\-fullscreen\fP, \fB\-\-no\-fullscreen\fP Enable/disable fullscreen mode at startup. .TP \fB\-p\fP, \fB\-P\fP, \fB\-\-play\fP, \fB\-\-no\-play\fP Start playing after loading song on command line. .TP \fB\-\-diskwrite\fP=\fIFILENAME\fP Render output to a file, and then exit. WAV or AIFF writer is auto-selected based on file extension. Include \fI%c\fP somewhere in the name to write each channel separately. This is meaningless if no initial filename is given. .TP \fB\-\-font\-editor\fP, \fB\-\-no\-font\-editor\fP Run the font editor (itf). This can also be accessed by pressing Shift-F12. .TP \fB\-\-hooks\fP, \fB\-\-no\-hooks\fP Run hooks. Enabled by default. .TP \fB\-\-debug\fP=\fIFLAGS\fP Enable some debugging flags (separated by commas). You probably don't need this. .TP \fB\-\-version\fP Display version information and build date. .TP \fB\-h\fP, \fB\-\-help\fP Print a summary of available options. .P A filename supplied on the command line will be loaded at startup. Additionally, if either a file or directory name is given, the default module, sample, and instrument paths will be set accordingly. .SH USAGE A detailed discussion of how to use Schism Tracker is far beyond the scope of this document, but here is a very brief rundown of the basics. Context-sensitive help can be accessed at any time while running the program by pressing \fBF1\fP. .P The \fBF3\fP key will bring you to the \fIsample list\fP. Press enter here to open a file browser, navigate in the list using the up/down arrow keys, and hit enter again to load a sample. You will likely want to get some samples to work with. You can also "rip" from existing modules; see for example \fIhttp://www.modarchive.org/\fP for a very large selection of modules. (Keep in mind, however, that some authors don't appreciate having their samples ripped!) .P Now that you've loaded a sample, press \fBF2\fP to get to the \fIpattern editor\fP. This is where the majority of the composition takes place. In short, the song is laid out vertically, with each row representing 1/16 note; to play multiple notes simultaneously, they are placed in different channels. The four sub-columns of each channel are the note, sample number, volume, and effect. A list of effects is available in the pattern editor help, but you can safely ignore that column for now. Assuming a US keymap, notes are entered with the keyboard as follows: (Note) C# D# F# G# A# C# D# F# G# A# C# D# | | || | | | || || | | | || | | | || || | | | || | | | | || | | | || || | | | || | | | || || | | | || | | (What you | |S||D| | |G||H||J| | |2||3| | |5||6||7| | |9||0| | type) | '-''-' | '-''-''-' | '-''-' | '-''-''-' | '-''-' | | Z| X| C| V| B| N| M| Q| W| E| R| T| Y| U| I| O| P| '--'--'--'--'--'--'--'--'--'--'--'--'--'--'--'--'--' (Note) C D E F G A B C D E F G A B C D E (Octave 0) (Octave 1) (Octave 2) .\" this .P is for elvis, which gets very confused by the preceding diagram .P The "/" and "*" keys on the numeric keypad change octaves, and the current octave is displayed near the top of the screen. Try typing "qwerty" into the pattern - it will enter an ascending note sequence, and you'll hear the notes as they're entered. (of course, assuming you loaded a sample!) Press \fBF6\fP to play your pattern, and \fBF8\fP to stop. .P Other important keys for the pattern editor include Ins/Del to shift notes up and down within a channel, Shift-Arrows to mark a block, Alt-C/Alt-P to copy and paste, and Alt-U to clear the mark. There are well over a hundred key bindings for the pattern editor; it is well worth the effort to learn them all eventually. .P Now that you have something in your pattern, you'll need to set up an \fIorderlist\fP. Press \fBF11\fP to switch to the orderlist page, and type 0 to add the pattern you created. Now press \fBF5\fP to start playing. The song will begin at the first order, look up the pattern number and play that pattern, then advance to the next order, and so forth. .P Of course, having only one pattern isn't all that interesting, so go back to the pattern editor and press the + key to change to the next pattern. Now you can write another four bars of music and add the new pattern to the orderlist, and the next time you play the song, your two patterns will play in sequence. .P You may wish to give your song a title; press \fBF12\fP and type a name in the box at the top. You can also adjust the tempo and a number of other settings on this page, but for now, most of them are fine at their default values. .P To save your new song, press \fBF10\fP, type a filename, and hit enter. You can load it again later by pressing \fBF9\fP. .P This tutorial has deliberately omitted the \fIinstrument editor\fP (on \fBF4\fP), for the purposes of brevity and simplicity. You may want to experiment with it once you have a feel for how the program works. (Select "instruments" on F12 to enable instrument mode.) .SH HISTORY Storlek began studying Impulse Tracker's design in 2002, noting subtle details of the design and implementation. Posts on the Modplug forums about rewriting Impulse Tracker were met with ridicule and mockery. "It can't be done," they said. .P Schism Tracker v0.031a was released in July 2003, though very little worked at that point, and it was more of a player with primitive editing capabilities. File saving was hard-coded to write to "test.it" in the current directory, and there was no way to load a sample. .P The first version that was more or less usable was 0.15a, from December 2004. .P From 2005 through 2009, Mrs. Brisby did most of the development, and implemented tons of features, including MIDI support, mouse support, and disk writing. .P Storlek "took over" development again in 2009, and incrementally rewrote much of the code through 2015. .P In 2016, Schism Tracker was moved to GitHub under shared maintainership. Since then, many people have contributed improvements and bug fixes to the codebase. .SH FILES .TP ~/.schism/config Program settings, stored in an INI-style format. Most options are accessible from within Schism Tracker's interface, but there are a few "hidden" options. .TP ~/.schism/startup\-hook, ~/.schism/exit\-hook, ~/.schism/diskwriter\-hook Optional files to execute upon certain events. (Must be executable) .TP ~/.schism/fonts/ \fIfont.cfg\fP, and any \fI.itf\fP files found in this directory, are displayed in the file browser of the font editor. .SS Supported file formats .TP MOD Amiga modules (with some obscure variants such as FLT8) .TP 669 Composer 669 / Unis669 .TP MTM MultiTracker .TP S3M Scream Tracker 3 (including Adlib support) .TP XM Fast Tracker 2 .TP IT Impulse Tracker (including old instrument format) .TP MDL Digitrakker 3 .TP IMF Imago Orpheus .TP OKT Amiga Oktalyzer .TP SFX Sound FX .TP MUS Doom engine (percussion missing) .TP FAR Farandole Composer .TP STM Scream Tracker 2 (partial functionality) .TP ULT UltraTracker (partial functionality) .TP S3I Scream Tracker 3 sample .TP WAV Microsoft WAV audio .TP AIFF Audio IFF (Apple) .TP 8SVX Amiga 8SVX sample .TP ITS Impulse Tracker sample .TP AU Sun/NeXT Audio .TP RAW Headerless sample data .TP PAT Gravis UltraSound patch .TP XI Fast Tracker 2 instrument .TP ITI Impulse Tracker instrument .P Schism Tracker is able to save modules in IT and S3M format, sample data as ITS, S3I, AIFF, AU, WAV, and RAW, and instruments as ITI. Additionally, it can render to WAV and AIFF (optionally writing each channel to a separate file), and can export MID files. .SH AUTHORS Schism Tracker was written by Storlek and Mrs. Brisby, with player code from Modplug by Olivier Lapicque. Based on Impulse Tracker by Jeffrey Lim. .P Additional code and data have been contributed by many others; refer to the file \fIAUTHORS\fP in the source distribution for a more complete list. .P The keyboard diagram in this manual page was adapted from the one used in the documentation for Impulse Tracker, which in turn borrowed it from Scream Tracker 3. .SH COPYRIGHT Copyright \(co 2003-2018 Storlek, Mrs. Brisby et al. Licensed under the GNU GPL <\fIhttp://gnu.org/licenses/gpl.html\fP>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. .SH BUGS They almost certainly exist. Post on \fIhttps://github.com/schismtracker/schismtracker/issues\fP if you find one. Agitha shares her happiness with benefactors of the insect kingdom. .SH INTERNETS \fIhttp://schismtracker.org/\fP - main website .br \fI#schismtracker\fP on EsperNet - IRC channel .SH SEE ALSO .\" No favoritism: this list is alphabetical, trackers then players .BR chibitracker (1), .BR milkytracker (1), .BR protracker (1), .BR renoise (1), .BR ocp (1), .BR xmp (1) schismtracker-20180209/sys/posix/slurp-mmap.c000066400000000000000000000034641323741476300210650ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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(NULL, st, PROT_READ, MAP_SHARED #if defined(MAP_POPULATE) && defined(MAP_NONBLOCK) | MAP_POPULATE | MAP_NONBLOCK #endif #if defined(MAP_NORESERVE) | MAP_NORESERVE #endif , 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 schismtracker-20180209/sys/sdl/000077500000000000000000000000001323741476300162355ustar00rootroot00000000000000schismtracker-20180209/sys/sdl/README000066400000000000000000000001431323741476300171130ustar00rootroot00000000000000this directory will eventually have SDL-specific stuff in it. right now, schism is SDL specific :) schismtracker-20180209/sys/stdlib/000077500000000000000000000000001323741476300167345ustar00rootroot00000000000000schismtracker-20180209/sys/stdlib/asprintf.c000066400000000000000000000023421323741476300207270ustar00rootroot00000000000000/* 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; } schismtracker-20180209/sys/stdlib/memcmp.c000066400000000000000000000005231323741476300203560ustar00rootroot00000000000000#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; } schismtracker-20180209/sys/stdlib/mkstemp.c000066400000000000000000000057631323741476300205730ustar00rootroot00000000000000/* Copyright (C) 1991, 1992, 1996, 1998 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C 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. The GNU C 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 the GNU C Library; 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 #include #include #include #include #include typedef uint64_t big_type; /* this ifdef crap lifted from slurp This is a hack!! We need binary mode files, but the 'b' flag to fdopen is evidently useless, so I'm doing it at this level instead. Dumb. */ #ifndef O_BINARY # ifdef O_RAW # define O_BINARY O_RAW # else # define O_BINARY 0 # endif #endif int mkstemp(char *template); /* Generate a unique temporary file name from TEMPLATE. The last six characters of TEMPLATE must be "XXXXXX"; they are replaced with a string that makes the filename unique. Returns a file descriptor open on the file for reading and writing. */ int mkstemp(char *template) { static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; static big_type value; struct timeval tv; char *XXXXXX; size_t len; int count; len = strlen (template); if (len < 6 || strcmp (&template[len - 6], "XXXXXX")) { errno = EINVAL; return -1; } /* This is where the Xs start. */ XXXXXX = &template[len - 6]; /* Get some more or less random data. */ gettimeofday (&tv, NULL); value += ((big_type) tv.tv_usec << 16) ^ tv.tv_sec ^ getpid (); for (count = 0; count < TMP_MAX; ++count) { big_type v = value; int fd; /* Fill in the random bits. */ XXXXXX[0] = letters[v % 62]; v /= 62; XXXXXX[1] = letters[v % 62]; v /= 62; XXXXXX[2] = letters[v % 62]; v /= 62; XXXXXX[3] = letters[v % 62]; v /= 62; XXXXXX[4] = letters[v % 62]; v /= 62; XXXXXX[5] = letters[v % 62]; fd = open (template, O_RDWR|O_CREAT|O_EXCL|O_BINARY, 0600); if (fd >= 0) /* The file does not exist. */ return fd; /* This is a random value. It is only necessary that the next TMP_MAX values generated by adding 7777 to VALUE are different with (module 2^32). */ value += 7777; } /* We return the null string if we can't find a unique file name. */ template[0] = '\0'; return -1; } schismtracker-20180209/sys/stdlib/strptime.c000066400000000000000000000222571323741476300207570ustar00rootroot00000000000000/* $Id$ */ /* $NetBSD: strptime.c,v 1.18 1999/04/29 02:58:30 tv Exp $ */ /*- * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code was contributed to The NetBSD Foundation by Klaus Klein. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #define NEED_TIME #include "headers.h" #include #define TM_YEAR_BASE 1900 /* * We do not implement alternate representations. However, we always * check whether a given modifier is allowed for a certain conversion. */ #define ALT_E 0x01 #define ALT_O 0x02 #define LEGAL_ALT(x) { if (alt_format & ~(x)) return (0); } static int conv_num(const char **, int *, int, int); static const char *day[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; static const char *abday[7] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" }; static const char *mon[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static const char *abmon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const char *am_pm[2] = { "AM", "PM" }; char * strptime(const char *buf, const char *fmt, struct tm *tm) { char c; const char *bp; size_t len = 0; int alt_format, i, split_year = 0; bp = buf; while ((c = *fmt) != '\0') { /* Clear `alternate' modifier prior to new conversion. */ alt_format = 0; /* Eat up white-space. */ if (isspace(c)) { while (isspace(*bp)) bp++; fmt++; continue; } if ((c = *fmt++) != '%') goto literal; again: switch (c = *fmt++) { case '%': /* "%%" is converted to "%". */ literal: if (c != *bp++) return (0); break; /* * "Alternative" modifiers. Just set the appropriate flag * and start over again. */ case 'E': /* "%E?" alternative conversion modifier. */ LEGAL_ALT(0); alt_format |= ALT_E; goto again; case 'O': /* "%O?" alternative conversion modifier. */ LEGAL_ALT(0); alt_format |= ALT_O; goto again; /* * "Complex" conversion rules, implemented through recursion. */ case 'c': /* Date and time, using the locale's format. */ LEGAL_ALT(ALT_E); if (!(bp = strptime(bp, "%x %X", tm))) return (0); break; case 'D': /* The date as "%m/%d/%y". */ LEGAL_ALT(0); if (!(bp = strptime(bp, "%m/%d/%y", tm))) return (0); break; case 'R': /* The time as "%H:%M". */ LEGAL_ALT(0); if (!(bp = strptime(bp, "%H:%M", tm))) return (0); break; case 'r': /* The time in 12-hour clock representation. */ LEGAL_ALT(0); if (!(bp = strptime(bp, "%I:%M:%S %p", tm))) return (0); break; case 'T': /* The time as "%H:%M:%S". */ LEGAL_ALT(0); if (!(bp = strptime(bp, "%H:%M:%S", tm))) return (0); break; case 'X': /* The time, using the locale's format. */ LEGAL_ALT(ALT_E); if (!(bp = strptime(bp, "%H:%M:%S", tm))) return (0); break; case 'x': /* The date, using the locale's format. */ LEGAL_ALT(ALT_E); if (!(bp = strptime(bp, "%m/%d/%y", tm))) return (0); break; /* * "Elementary" conversion rules. */ case 'A': /* The day of week, using the locale's form. */ case 'a': LEGAL_ALT(0); for (i = 0; i < 7; i++) { /* Full name. */ len = strlen(day[i]); if (strncasecmp(day[i], bp, len) == 0) break; /* Abbreviated name. */ len = strlen(abday[i]); if (strncasecmp(abday[i], bp, len) == 0) break; } /* Nothing matched. */ if (i == 7) return (0); tm->tm_wday = i; bp += len; break; case 'B': /* The month, using the locale's form. */ case 'b': case 'h': LEGAL_ALT(0); for (i = 0; i < 12; i++) { /* Full name. */ len = strlen(mon[i]); if (strncasecmp(mon[i], bp, len) == 0) break; /* Abbreviated name. */ len = strlen(abmon[i]); if (strncasecmp(abmon[i], bp, len) == 0) break; } /* Nothing matched. */ if (i == 12) return (0); tm->tm_mon = i; bp += len; break; case 'C': /* The century number. */ LEGAL_ALT(ALT_E); if (!(conv_num(&bp, &i, 0, 99))) return (0); if (split_year) { tm->tm_year = (tm->tm_year % 100) + (i * 100); } else { tm->tm_year = i * 100; split_year = 1; } break; case 'd': /* The day of month. */ case 'e': LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_mday, 1, 31))) return (0); break; case 'k': /* The hour (24-hour clock representation). */ LEGAL_ALT(0); /* FALLTHROUGH */ case 'H': LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_hour, 0, 23))) return (0); break; case 'l': /* The hour (12-hour clock representation). */ LEGAL_ALT(0); /* FALLTHROUGH */ case 'I': LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_hour, 1, 12))) return (0); if (tm->tm_hour == 12) tm->tm_hour = 0; break; case 'j': /* The day of year. */ LEGAL_ALT(0); if (!(conv_num(&bp, &i, 1, 366))) return (0); tm->tm_yday = i - 1; break; case 'M': /* The minute. */ LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_min, 0, 59))) return (0); break; case 'm': /* The month. */ LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &i, 1, 12))) return (0); tm->tm_mon = i - 1; break; case 'p': /* The locale's equivalent of AM/PM. */ LEGAL_ALT(0); /* AM? */ if (strcasecmp(am_pm[0], bp) == 0) { if (tm->tm_hour > 11) return (0); bp += strlen(am_pm[0]); break; } /* PM? */ else if (strcasecmp(am_pm[1], bp) == 0) { if (tm->tm_hour > 11) return (0); tm->tm_hour += 12; bp += strlen(am_pm[1]); break; } /* Nothing matched. */ return (0); case 'S': /* The seconds. */ LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_sec, 0, 61))) return (0); break; case 'U': /* The week of year, beginning on sunday. */ case 'W': /* The week of year, beginning on monday. */ LEGAL_ALT(ALT_O); /* * XXX This is bogus, as we can not assume any valid * information present in the tm structure at this * point to calculate a real value, so just check the * range for now. */ if (!(conv_num(&bp, &i, 0, 53))) return (0); break; case 'w': /* The day of week, beginning on sunday. */ LEGAL_ALT(ALT_O); if (!(conv_num(&bp, &tm->tm_wday, 0, 6))) return (0); break; case 'Y': /* The year. */ LEGAL_ALT(ALT_E); if (!(conv_num(&bp, &i, 0, 9999))) return (0); tm->tm_year = i - TM_YEAR_BASE; break; case 'y': /* The year within 100 years of the epoch. */ LEGAL_ALT(ALT_E | ALT_O); if (!(conv_num(&bp, &i, 0, 99))) return (0); if (split_year) { tm->tm_year = ((tm->tm_year / 100) * 100) + i; break; } split_year = 1; if (i <= 68) tm->tm_year = i + 2000 - TM_YEAR_BASE; else tm->tm_year = i + 1900 - TM_YEAR_BASE; break; /* * Miscellaneous conversions. */ case 'n': /* Any kind of white-space. */ case 't': LEGAL_ALT(0); while (isspace(*bp)) bp++; break; default: /* Unknown/unsupported conversion. */ return (0); } } /* LINTED functional specification */ return ((char *)bp); } static int conv_num(const char **buf, int *dest, int llim, int ulim) { int result = 0; /* The limit also determines the number of valid digits. */ int rulim = ulim; if (**buf < '0' || **buf > '9') return (0); do { result *= 10; result += *(*buf)++ - '0'; rulim /= 10; } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9'); if (result < llim || result > ulim) return (0); *dest = result; return (1); } schismtracker-20180209/sys/stdlib/vasprintf.c000066400000000000000000000051401323741476300211140ustar00rootroot00000000000000/* 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 #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 = (char*)malloc (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); } schismtracker-20180209/sys/wii/000077500000000000000000000000001323741476300162435ustar00rootroot00000000000000schismtracker-20180209/sys/wii/certs_bin.h000066400000000000000000000500571323741476300203730ustar00rootroot00000000000000static unsigned const char certs_bin[] = { '\000', '\001', '\000', '\000', '\263', '\255', '\263', '\042', '\153', '\074', '\075', '\377', '\033', '\113', '\100', '\167', '\026', '\377', '\117', '\172', '\327', '\144', '\206', '\310', '\225', '\254', '\126', '\055', '\041', '\361', '\006', '\001', '\324', '\366', '\144', '\050', '\031', '\034', '\007', '\166', '\217', '\337', '\032', '\342', '\316', '\173', '\047', '\311', '\017', '\274', '\012', '\320', '\061', '\045', '\170', '\354', '\007', '\171', '\266', '\127', '\324', '\067', '\044', '\023', '\247', '\370', '\157', '\014', '\024', '\300', '\357', '\156', '\011', '\101', '\355', '\053', '\005', '\354', '\071', '\127', '\066', '\007', '\211', '\000', '\112', '\207', '\215', '\056', '\235', '\370', '\307', '\245', '\251', '\370', '\312', '\263', '\021', '\261', '\030', '\171', '\127', '\273', '\370', '\230', '\342', '\242', '\124', '\002', '\317', '\124', '\071', '\317', '\053', '\277', '\240', '\341', '\370', '\134', '\006', '\156', '\203', '\232', '\340', '\224', '\312', '\107', '\340', '\025', '\130', '\365', '\156', '\157', '\064', '\351', '\052', '\242', '\334', '\070', '\223', '\176', '\067', '\315', '\214', '\134', '\115', '\375', '\057', '\021', '\117', '\350', '\150', '\311', '\250', '\331', '\376', '\330', '\156', '\014', '\041', '\165', '\242', '\275', '\176', '\211', '\271', '\307', '\265', '\023', '\364', '\032', '\171', '\141', '\104', '\071', '\020', '\357', '\371', '\327', '\376', '\127', '\042', '\030', '\325', '\155', '\373', '\177', '\111', '\172', '\244', '\313', '\220', '\324', '\361', '\256', '\261', '\166', '\344', '\150', '\135', '\247', '\224', '\100', '\140', '\230', '\057', '\004', '\110', '\100', '\037', '\317', '\306', '\272', '\353', '\332', '\026', '\060', '\264', '\163', '\264', '\025', '\043', '\065', '\010', '\007', '\012', '\237', '\117', '\211', '\170', '\346', '\054', '\354', '\136', '\222', '\106', '\245', '\250', '\275', '\240', '\205', '\170', '\150', '\165', '\014', '\072', '\021', '\057', '\257', '\225', '\350', '\070', '\310', '\231', '\016', '\207', '\261', '\142', '\315', '\020', '\332', '\263', '\061', '\226', '\145', '\357', '\210', '\233', '\124', '\033', '\263', '\066', '\273', '\147', '\123', '\237', '\257', '\302', '\256', '\055', '\012', '\056', '\165', '\300', '\043', '\164', '\352', '\116', '\254', '\215', '\231', '\120', '\177', '\131', '\271', '\123', '\167', '\060', '\137', '\046', '\065', '\306', '\010', '\251', '\220', '\223', '\254', '\217', '\306', '\336', '\043', '\271', '\172', '\352', '\160', '\264', '\304', '\317', '\146', '\263', '\016', '\130', '\062', '\016', '\305', '\266', '\162', '\004', '\110', '\316', '\073', '\261', '\034', '\123', '\037', '\313', '\160', '\050', '\174', '\265', '\302', '\174', '\147', '\117', '\273', '\375', '\214', '\177', '\311', '\102', '\040', '\244', '\163', '\043', '\035', '\130', '\176', '\132', '\032', '\032', '\202', '\343', '\165', '\171', '\241', '\273', '\202', '\156', '\316', '\001', '\161', '\311', '\165', '\143', '\107', '\113', '\035', '\106', '\346', '\171', '\262', '\202', '\067', '\142', '\021', '\315', '\307', '\000', '\057', '\106', '\207', '\302', '\074', '\155', '\300', '\325', '\265', '\170', '\156', '\341', '\362', '\163', '\377', '\001', '\222', '\120', '\017', '\364', '\307', '\120', '\152', '\356', '\162', '\266', '\364', '\075', '\366', '\010', '\376', '\245', '\203', '\241', '\371', '\206', '\017', '\207', '\257', '\122', '\104', '\124', '\273', '\107', '\303', '\006', '\014', '\224', '\351', '\233', '\367', '\326', '\062', '\247', '\310', '\253', '\113', '\117', '\365', '\065', '\041', '\037', '\301', '\200', '\107', '\273', '\172', '\372', '\132', '\053', '\327', '\270', '\204', '\255', '\216', '\126', '\117', '\133', '\211', '\377', '\067', '\227', '\067', '\361', '\365', '\001', '\073', '\037', '\236', '\304', '\030', '\157', '\222', '\052', '\325', '\304', '\263', '\300', '\325', '\207', '\013', '\234', '\004', '\257', '\032', '\265', '\363', '\274', '\155', '\012', '\361', '\175', '\107', '\010', '\344', '\103', '\351', '\163', '\367', '\267', '\160', '\167', '\124', '\272', '\363', '\354', '\322', '\254', '\111', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\133', '\372', '\175', '\134', '\262', '\171', '\311', '\342', '\356', '\341', '\041', '\306', '\352', '\364', '\117', '\366', '\071', '\370', '\217', '\007', '\213', '\113', '\167', '\355', '\237', '\225', '\140', '\260', '\065', '\202', '\201', '\265', '\016', '\125', '\253', '\162', '\021', '\025', '\241', '\167', '\160', '\074', '\172', '\060', '\376', '\072', '\351', '\357', '\034', '\140', '\274', '\035', '\227', '\106', '\166', '\262', '\072', '\150', '\314', '\004', '\261', '\230', '\122', '\133', '\311', '\150', '\361', '\035', '\342', '\333', '\120', '\344', '\331', '\347', '\360', '\161', '\345', '\142', '\332', '\342', '\011', '\042', '\063', '\351', '\323', '\143', '\366', '\035', '\327', '\301', '\237', '\363', '\244', '\251', '\036', '\217', '\145', '\123', '\324', '\161', '\335', '\173', '\204', '\271', '\361', '\270', '\316', '\163', '\065', '\360', '\365', '\124', '\005', '\143', '\241', '\352', '\270', '\071', '\143', '\340', '\233', '\351', '\001', '\001', '\037', '\231', '\124', '\143', '\141', '\050', '\160', '\040', '\351', '\314', '\015', '\253', '\110', '\177', '\024', '\015', '\146', '\046', '\241', '\203', '\155', '\047', '\021', '\037', '\040', '\150', '\336', '\107', '\162', '\024', '\221', '\121', '\317', '\151', '\306', '\033', '\246', '\016', '\371', '\331', '\111', '\240', '\367', '\037', '\124', '\231', '\362', '\323', '\232', '\322', '\214', '\160', '\005', '\064', '\202', '\223', '\304', '\061', '\377', '\275', '\063', '\366', '\274', '\246', '\015', '\307', '\031', '\136', '\242', '\274', '\305', '\155', '\040', '\013', '\257', '\155', '\006', '\320', '\234', '\101', '\333', '\215', '\351', '\307', '\040', '\025', '\114', '\244', '\203', '\053', '\151', '\300', '\214', '\151', '\315', '\073', '\007', '\072', '\000', '\143', '\140', '\057', '\106', '\055', '\063', '\200', '\141', '\245', '\352', '\154', '\221', '\134', '\325', '\142', '\065', '\171', '\303', '\353', '\144', '\316', '\104', '\357', '\130', '\155', '\024', '\272', '\252', '\210', '\064', '\001', '\233', '\076', '\353', '\356', '\323', '\171', '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\001', '\116', '\000', '\137', '\361', '\077', '\206', '\165', '\215', '\266', '\234', '\105', '\143', '\017', '\324', '\233', '\364', '\314', '\135', '\124', '\317', '\314', '\042', '\064', '\162', '\127', '\253', '\244', '\272', '\123', '\322', '\263', '\075', '\346', '\354', '\236', '\241', '\127', '\124', '\123', '\256', '\137', '\223', '\075', '\226', '\277', '\367', '\314', '\172', '\171', '\126', '\156', '\204', '\173', '\033', '\140', '\167', '\302', '\251', '\070', '\161', '\060', '\032', '\214', '\323', '\311', '\075', '\115', '\263', '\046', '\351', '\207', '\222', '\146', '\351', '\323', '\272', '\237', '\171', '\274', '\106', '\070', '\372', '\055', '\040', '\240', '\072', '\160', '\147', '\244', '\021', '\247', '\240', '\267', '\331', '\022', '\255', '\021', '\152', '\072', '\304', '\156', '\062', '\102', '\107', '\302', '\010', '\272', '\264', '\224', '\234', '\305', '\056', '\320', '\057', '\031', '\366', '\121', '\340', '\337', '\056', '\066', '\123', '\252', '\257', '\227', '\246', '\222', '\273', '\251', '\035', '\330', '\156', '\044', '\056', '\263', '\010', '\167', '\125', '\021', '\316', '\230', '\366', '\242', '\364', '\046', '\311', '\047', '\004', '\320', '\374', '\215', '\324', '\200', '\236', '\327', '\141', '\275', '\021', '\267', '\205', '\224', '\214', '\326', '\320', '\172', '\333', '\244', '\010', '\320', '\360', '\206', '\366', '\132', '\256', '\031', '\024', '\262', '\210', '\232', '\250', '\256', '\112', '\242', '\252', '\307', '\141', '\251', '\015', '\101', '\054', '\261', '\120', '\011', '\253', '\076', '\223', '\374', '\251', '\044', '\336', '\316', '\117', '\174', '\006', '\253', '\334', '\056', '\140', '\235', '\150', '\276', '\000', '\163', '\372', '\200', '\127', '\152', '\024', '\136', '\355', '\304', '\213', '\164', '\062', '\207', '\007', '\223', '\310', '\374', '\246', '\330', '\076', '\011', '\156', '\305', '\362', '\251', '\304', '\041', '\347', '\110', '\263', '\163', '\100', '\133', '\342', '\372', '\212', '\341', '\130', '\170', '\351', '\325', '\043', '\210', '\165', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\055', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\103', '\120', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\064', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\361', '\270', '\240', '\144', '\301', '\155', '\363', '\203', '\051', '\125', '\303', '\051', '\133', '\162', '\360', '\063', '\056', '\227', '\357', '\024', '\204', '\212', '\150', '\004', '\234', '\246', '\216', '\254', '\336', '\024', '\120', '\063', '\270', '\154', '\020', '\215', '\110', '\063', '\134', '\135', '\014', '\253', '\167', '\004', '\142', '\124', '\107', '\125', '\105', '\052', '\220', '\000', '\160', '\261', '\126', '\222', '\134', '\027', '\206', '\342', '\315', '\040', '\155', '\314', '\334', '\054', '\056', '\067', '\156', '\047', '\374', '\264', '\040', '\146', '\314', '\012', '\214', '\351', '\376', '\350', '\127', '\004', '\346', '\312', '\143', '\032', '\056', '\176', '\221', '\176', '\224', '\174', '\071', '\221', '\167', '\066', '\051', '\321', '\125', '\141', '\205', '\273', '\327', '\267', '\163', '\312', '\067', '\107', '\236', '\137', '\252', '\243', '\266', '\005', '\340', '\001', '\341', '\254', '\345', '\215', '\330', '\370', '\107', '\202', '\326', '\105', '\374', '\343', '\241', '\315', '\003', '\253', '\066', '\360', '\363', '\206', '\261', '\242', '\321', '\067', '\100', '\241', '\224', '\212', '\123', '\272', '\033', '\015', '\214', '\110', '\143', '\315', '\153', '\054', '\056', '\040', '\144', '\224', '\200', '\114', '\142', '\372', '\251', '\072', '\176', '\063', '\251', '\352', '\170', '\153', '\131', '\312', '\343', '\253', '\066', '\105', '\364', '\313', '\217', '\327', '\220', '\153', '\202', '\150', '\315', '\254', '\361', '\173', '\072', '\354', '\106', '\203', '\033', '\221', '\366', '\336', '\030', '\141', '\203', '\274', '\113', '\062', '\147', '\223', '\307', '\056', '\120', '\331', '\036', '\066', '\240', '\334', '\342', '\271', '\175', '\240', '\041', '\076', '\106', '\226', '\002', '\037', '\063', '\034', '\276', '\256', '\215', '\374', '\222', '\207', '\062', '\252', '\104', '\334', '\170', '\347', '\031', '\232', '\075', '\335', '\127', '\042', '\176', '\236', '\167', '\336', '\062', '\143', '\206', '\223', '\154', '\021', '\254', '\247', '\017', '\201', '\031', '\323', '\072', '\231', '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\001', '\175', '\235', '\136', '\272', '\122', '\201', '\334', '\247', '\006', '\135', '\057', '\010', '\150', '\333', '\212', '\307', '\072', '\316', '\176', '\251', '\221', '\361', '\226', '\237', '\341', '\320', '\362', '\301', '\037', '\256', '\300', '\303', '\360', '\032', '\334', '\264', '\106', '\255', '\345', '\312', '\003', '\266', '\045', '\041', '\224', '\142', '\306', '\341', '\101', '\015', '\271', '\346', '\077', '\336', '\230', '\321', '\257', '\046', '\073', '\114', '\262', '\207', '\204', '\047', '\202', '\162', '\357', '\047', '\023', '\113', '\207', '\302', '\130', '\326', '\173', '\142', '\362', '\265', '\277', '\234', '\266', '\272', '\214', '\211', '\031', '\056', '\305', '\006', '\211', '\254', '\164', '\044', '\240', '\042', '\011', '\100', '\003', '\356', '\230', '\244', '\275', '\057', '\001', '\073', '\131', '\077', '\345', '\146', '\154', '\325', '\353', '\132', '\327', '\244', '\223', '\020', '\363', '\116', '\373', '\264', '\075', '\106', '\313', '\361', '\265', '\043', '\317', '\202', '\366', '\216', '\265', '\155', '\271', '\004', '\247', '\302', '\250', '\053', '\341', '\035', '\170', '\323', '\233', '\242', '\015', '\220', '\323', '\007', '\102', '\333', '\136', '\172', '\301', '\357', '\362', '\041', '\121', '\011', '\142', '\317', '\251', '\024', '\250', '\200', '\334', '\364', '\027', '\272', '\231', '\223', '\012', '\356', '\010', '\260', '\260', '\345', '\032', '\076', '\237', '\257', '\315', '\302', '\327', '\343', '\313', '\241', '\057', '\072', '\300', '\007', '\220', '\336', '\104', '\172', '\303', '\305', '\070', '\250', '\147', '\222', '\070', '\007', '\213', '\324', '\304', '\262', '\105', '\254', '\051', '\026', '\210', '\155', '\052', '\016', '\131', '\116', '\355', '\134', '\310', '\065', '\151', '\213', '\115', '\142', '\070', '\337', '\005', '\162', '\115', '\314', '\366', '\201', '\200', '\212', '\160', '\164', '\006', '\131', '\060', '\277', '\370', '\121', '\101', '\067', '\350', '\025', '\372', '\272', '\241', '\162', '\270', '\340', '\151', '\154', '\141', '\344', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\055', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\130', '\123', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\063', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\361', '\270', '\237', '\321', '\255', '\007', '\251', '\067', '\212', '\173', '\020', '\014', '\175', '\307', '\071', '\276', '\236', '\335', '\267', '\062', '\000', '\211', '\253', '\045', '\261', '\370', '\161', '\257', '\132', '\251', '\364', '\130', '\236', '\321', '\203', '\002', '\062', '\216', '\201', '\032', '\037', '\357', '\320', '\011', '\310', '\006', '\066', '\103', '\370', '\124', '\271', '\341', '\073', '\273', '\141', '\072', '\172', '\317', '\207', '\024', '\205', '\153', '\244', '\133', '\252', '\347', '\273', '\306', '\116', '\262', '\367', '\135', '\207', '\353', '\362', '\147', '\355', '\017', '\244', '\101', '\251', '\063', '\146', '\136', '\127', '\175', '\132', '\336', '\253', '\373', '\106', '\056', '\166', '\000', '\312', '\234', '\351', '\115', '\304', '\313', '\230', '\071', '\222', '\253', '\172', '\057', '\263', '\243', '\236', '\242', '\277', '\234', '\123', '\354', '\320', '\334', '\372', '\153', '\213', '\136', '\262', '\313', '\244', '\017', '\372', '\100', '\165', '\370', '\362', '\262', '\336', '\227', '\070', '\021', '\207', '\055', '\365', '\342', '\246', '\303', '\213', '\057', '\334', '\216', '\127', '\335', '\275', '\137', '\106', '\353', '\047', '\326', '\031', '\122', '\366', '\256', '\370', '\142', '\267', '\356', '\232', '\306', '\202', '\242', '\261', '\232', '\251', '\265', '\130', '\373', '\353', '\263', '\211', '\057', '\275', '\120', '\311', '\365', '\334', '\112', '\156', '\234', '\233', '\376', '\105', '\200', '\064', '\251', '\102', '\030', '\055', '\336', '\267', '\137', '\340', '\321', '\263', '\337', '\016', '\227', '\343', '\231', '\200', '\207', '\160', '\030', '\302', '\262', '\203', '\361', '\065', '\165', '\174', '\132', '\060', '\374', '\077', '\060', '\204', '\244', '\232', '\252', '\300', '\036', '\347', '\006', '\151', '\117', '\216', '\024', '\110', '\332', '\022', '\072', '\314', '\117', '\372', '\046', '\252', '\070', '\367', '\357', '\277', '\047', '\217', '\066', '\227', '\171', '\167', '\135', '\267', '\305', '\255', '\307', '\211', '\221', '\334', '\370', '\103', '\215', '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', }; schismtracker-20180209/sys/wii/data/000077500000000000000000000000001323741476300171545ustar00rootroot00000000000000schismtracker-20180209/sys/wii/data/certs.bin000066400000000000000000000050001323741476300207610ustar00rootroot00000000000000"k<=K@wOzdȕV-!d(v{' 1%xyW7$o n A+9W6J.ǥʳyWTT9+\nGXno4*8~7͌\M/Ohɨn !u~ǵyaD9W"mIzːvh]@`/H@ƺ0s#5 Ox,^Fxhu :/8șbڳ1eT6gS®- .u#tNPYSw0_&5#zpfX2ŶrH;Sp(||gOB s#X~ZuynqucGKFy7b/FyN_?uEcԛ]T"4rWSҳ=잡WTS_=zyVn{`w©8q0=M&釒fӺyF8- :pgj:n2BG./Q.6Sn$.wUΘ&'ԀazۤZJa A,P >$O|.`hsWj^ċt2> n!Hs@[Xx#uRoot-CA00000001CP00000004dm)U)[r3.hP3lH3\] wbTGUE*pV\ m,.7n' f Wc.~~|9w6)Ua׷s7G_GE6󆱢7@S Hck,. dLb:~3xkY6Eˏאkhͬ{:FaK2g.P6}!>F32Dx=W"~w2cl:}^Rܧ]/hۊ:~ܴF%!bA ?ޘѯ&;L'r'KX{b򵿜.t$" @/;Y?flZפN=F#ςm¨+xӛ B^z!Q bϩ >ˡ/:Dz8g8IJE)m*YN\5iMb8rMptY0QA7rilaRoot-CA00000001XS00000003ѭ7{ }9ݷ2%qZXу2 6CT;a:zχk[N]gA3f^W}ZޫF.vʜM˘9z/Sk^ˤ@uޗ8-Ë/܎Wݽ_F'RbƂX볉/PJnE4B-޷_ѳ㙀p²5u|Z0?0iOH:O&8'6yw]ŭljCschismtracker-20180209/sys/wii/data/su_tik.bin000066400000000000000000000012441323741476300211450ustar00rootroot00000000000000Root-CA00000001-XS000000038R6_schismtracker-20180209/sys/wii/data/su_tmd.bin000066400000000000000000000010101323741476300211310ustar00rootroot00000000000000Root-CA00000001-CP00000004 schismtracker-20180209/sys/wii/isfs.c000066400000000000000000000267641323741476300173720ustar00rootroot00000000000000/* libisfs -- a NAND filesystem devoptab library for the Wii Copyright (C) 2008 Joseph Jordan This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1.The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2.Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3.This notice may not be removed or altered from any source distribution. [In compliance with the above: I patched this code up somewhat so that it builds with all warnings. -- Storlek] */ #include #include #include #include #include #include #include #include #include #include "isfs.h" #define DEVICE_NAME "isfs" #define FLAG_DIR 1 #define DIR_SEPARATOR '/' #define SECTOR_SIZE 0x800 #define BUFFER_SIZE 0x8000 #define UNUSED __attribute__((unused)) typedef struct DIR_ENTRY_STRUCT { char *name; char *abspath; u32 size; u8 flags; u32 fileCount; struct DIR_ENTRY_STRUCT *children; } DIR_ENTRY; typedef struct { DIR_ENTRY *entry; s32 isfs_fd; bool inUse; } FILE_STRUCT; typedef struct { DIR_ENTRY *entry; u32 index; bool inUse; } DIR_STATE_STRUCT; static char read_buffer[BUFFER_SIZE] __attribute__((aligned(32))); static DIR_ENTRY *root = NULL; static DIR_ENTRY *current = NULL; static s32 dotab_device = -1; static bool is_dir(DIR_ENTRY *entry) { return entry->flags & FLAG_DIR; } static bool invalid_drive_specifier(const char *path) { if (strchr(path, ':') == NULL) return false; int namelen = strlen(DEVICE_NAME); if (!strncmp(DEVICE_NAME, path, namelen) && path[namelen] == ':') return false; return true; } static DIR_ENTRY *entry_from_path(const char *path) { if (invalid_drive_specifier(path)) return NULL; if (strchr(path, ':') != NULL) path = strchr(path, ':') + 1; DIR_ENTRY *entry; bool found = false; bool notFound = false; const char *pathPosition = path; const char *pathEnd = strchr(path, '\0'); if (pathPosition[0] == DIR_SEPARATOR) { entry = root; while (pathPosition[0] == DIR_SEPARATOR) pathPosition++; if (pathPosition >= pathEnd) found = true; } else { entry = current; } if (entry == root && !strcmp(".", pathPosition)) found = true; DIR_ENTRY *dir = entry; while (!found && !notFound) { const char *nextPathPosition = strchr(pathPosition, DIR_SEPARATOR); size_t dirnameLength; if (nextPathPosition != NULL) dirnameLength = nextPathPosition - pathPosition; else dirnameLength = strlen(pathPosition); if (dirnameLength >= ISFS_MAXPATHLEN) return NULL; u32 fileIndex = 0; while (fileIndex < dir->fileCount && !found && !notFound) { entry = &dir->children[fileIndex]; if (dirnameLength == strnlen(entry->name, ISFS_MAXPATHLEN - 1) && !strncasecmp(pathPosition, entry->name, dirnameLength)) found = true; if (found && !is_dir(entry) && nextPathPosition) found = false; if (!found) fileIndex++; } if (fileIndex >= dir->fileCount) { notFound = true; found = false; } else if (!nextPathPosition || nextPathPosition >= pathEnd) { found = true; } else if (is_dir(entry)) { dir = entry; pathPosition = nextPathPosition; while (pathPosition[0] == DIR_SEPARATOR) pathPosition++; if (pathPosition >= pathEnd) found = true; else found = false; } } if (found && !notFound) return entry; return NULL; } static int _ISFS_open_r(struct _reent *r, void *fileStruct, const char *path, UNUSED int flags, UNUSED int mode) { FILE_STRUCT *file = (FILE_STRUCT *)fileStruct; DIR_ENTRY *entry = entry_from_path(path); if (!entry) { r->_errno = ENOENT; return -1; } else if (is_dir(entry)) { r->_errno = EISDIR; return -1; } file->entry = entry; file->inUse = true; file->isfs_fd = ISFS_Open(entry->abspath, ISFS_OPEN_READ); if (file->isfs_fd < 0) { if (file->isfs_fd == ISFS_EINVAL) r->_errno = EACCES; else r->_errno = -file->isfs_fd; return -1; } return (int)file; } static int _ISFS_close_r(struct _reent *r, int fd) { FILE_STRUCT *file = (FILE_STRUCT *)fd; if (!file->inUse) { r->_errno = EBADF; return -1; } file->inUse = false; s32 ret = ISFS_Close(file->isfs_fd); if (ret < 0) { r->_errno = -ret; return -1; } return 0; } static int _ISFS_read_r(struct _reent *r, int fd, char *ptr, size_t len) { FILE_STRUCT *file = (FILE_STRUCT *)fd; if (!file->inUse) { r->_errno = EBADF; return -1; } if (len <= 0) { return 0; } s32 ret = ISFS_Read(file->isfs_fd, read_buffer, len); if (ret < 0) { r->_errno = -ret; return -1; } else if ((size_t) ret < len) { r->_errno = EOVERFLOW; } memcpy(ptr, read_buffer, ret); return ret; } static off_t _ISFS_seek_r(struct _reent *r, int fd, off_t pos, int dir) { FILE_STRUCT *file = (FILE_STRUCT *)fd; if (!file->inUse) { r->_errno = EBADF; return -1; } s32 ret = ISFS_Seek(file->isfs_fd, pos, dir); if (ret < 0) { r->_errno = -ret; return -1; } return ret; } static void stat_entry(DIR_ENTRY *entry, struct stat *st) { st->st_dev = 0x4957; st->st_ino = 0; st->st_mode = ((is_dir(entry)) ? S_IFDIR : S_IFREG) | (S_IRUSR | S_IRGRP | S_IROTH); st->st_nlink = 1; st->st_uid = 1; st->st_gid = 2; st->st_rdev = st->st_dev; st->st_size = entry->size; st->st_atime = 0; st->st_spare1 = 0; st->st_mtime = 0; st->st_spare2 = 0; st->st_ctime = 0; st->st_spare3 = 0; st->st_blksize = SECTOR_SIZE; st->st_blocks = (entry->size + SECTOR_SIZE - 1) / SECTOR_SIZE; st->st_spare4[0] = 0; st->st_spare4[1] = 0; } static int _ISFS_fstat_r(struct _reent *r, int fd, struct stat *st) { FILE_STRUCT *file = (FILE_STRUCT *)fd; if (!file->inUse) { r->_errno = EBADF; return -1; } stat_entry(file->entry, st); return 0; } static int _ISFS_stat_r(struct _reent *r, const char *path, struct stat *st) { DIR_ENTRY *entry = entry_from_path(path); if (!entry) { r->_errno = ENOENT; return -1; } stat_entry(entry, st); return 0; } static int _ISFS_chdir_r(struct _reent *r, const char *path) { DIR_ENTRY *entry = entry_from_path(path); if (!entry) { r->_errno = ENOENT; return -1; } else if (!is_dir(entry)) { r->_errno = ENOTDIR; return -1; } return 0; } static DIR_ITER *_ISFS_diropen_r(struct _reent *r, DIR_ITER *dirState, const char *path) { DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct); state->entry = entry_from_path(path); if (!state->entry) { r->_errno = ENOENT; return NULL; } else if (!is_dir(state->entry)) { r->_errno = ENOTDIR; return NULL; } state->index = 0; state->inUse = true; return dirState; } static int _ISFS_dirreset_r(struct _reent *r, DIR_ITER *dirState) { DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct); if (!state->inUse) { r->_errno = EBADF; return -1; } state->index = 0; return 0; } static int _ISFS_dirnext_r(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *st) { DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct); if (!state->inUse) { r->_errno = EBADF; return -1; } if (state->index >= state->entry->fileCount) { r->_errno = ENOENT; return -1; } DIR_ENTRY *entry = &state->entry->children[state->index++]; strncpy(filename, entry->name, ISFS_MAXPATHLEN - 1); stat_entry(entry, st); return 0; } static int _ISFS_dirclose_r(struct _reent *r, DIR_ITER *dirState) { DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct); if (!state->inUse) { r->_errno = EBADF; return -1; } state->inUse = false; return 0; } static const devoptab_t dotab_isfs = { DEVICE_NAME, sizeof(FILE_STRUCT), _ISFS_open_r, _ISFS_close_r, NULL, _ISFS_read_r, _ISFS_seek_r, _ISFS_fstat_r, _ISFS_stat_r, NULL, NULL, _ISFS_chdir_r, NULL, NULL, sizeof(DIR_STATE_STRUCT), _ISFS_diropen_r, _ISFS_dirreset_r, _ISFS_dirnext_r, _ISFS_dirclose_r, NULL, NULL, NULL, NULL, NULL, NULL }; static DIR_ENTRY *add_child_entry(DIR_ENTRY *dir, const char *name) { DIR_ENTRY *newChildren = realloc(dir->children, (dir->fileCount + 1) * sizeof(DIR_ENTRY)); if (!newChildren) return NULL; bzero(newChildren + dir->fileCount, sizeof(DIR_ENTRY)); dir->children = newChildren; DIR_ENTRY *child = &dir->children[dir->fileCount++]; child->name = strdup(name); if (!child->name) return NULL; child->abspath = malloc(strlen(dir->abspath) + (dir != root) + strlen(name) + 1); if (!child->abspath) return NULL; sprintf(child->abspath, "%s/%s", dir == root ? "" : dir->abspath, name); return child; } static bool read_recursive(DIR_ENTRY *parent) { u32 fileCount; s32 ret = ISFS_ReadDir(parent->abspath, NULL, &fileCount); if (ret != ISFS_OK) { s32 fd = ISFS_Open(parent->abspath, ISFS_OPEN_READ); if (fd >= 0) { static fstats st __attribute__((aligned(32))); if (ISFS_GetFileStats(fd, &st) == ISFS_OK) parent->size = st.file_length; ISFS_Close(fd); } return true; } parent->flags = FLAG_DIR; if (fileCount > 0) { if ((ISFS_MAXPATHLEN * fileCount) > BUFFER_SIZE) return false; ret = ISFS_ReadDir(parent->abspath, read_buffer, &fileCount); if (ret != ISFS_OK) return false; u32 fileNum; char *name = read_buffer; for (fileNum = 0; fileNum < fileCount; fileNum++) { DIR_ENTRY *child = add_child_entry(parent, name); if (!child) return false; name += strlen(name) + 1; } for (fileNum = 0; fileNum < fileCount; fileNum++) if (!read_recursive(parent->children + fileNum)) return false; } return true; } static bool read_isfs() { root = malloc(sizeof(DIR_ENTRY)); if (!root) return false; bzero(root, sizeof(DIR_ENTRY)); current = root; root->name = strdup("/"); if (!root->name) return false; root->abspath = strdup("/"); if (!root->abspath) return false; return read_recursive(root); } static void cleanup_recursive(DIR_ENTRY *entry) { u32 i; for (i = 0; i < entry->fileCount; i++) cleanup_recursive(&entry->children[i]); if (entry->children) free(entry->children); if (entry->name) free(entry->name); if (entry->abspath) free(entry->abspath); } bool ISFS_Mount() { ISFS_Unmount(); bool success = read_isfs() && (dotab_device = AddDevice(&dotab_isfs)) >= 0; if (!success) ISFS_Unmount(); return success; } bool ISFS_Unmount() { if (root) { cleanup_recursive(root); free(root); root = NULL; } current = root; if (dotab_device >= 0) { dotab_device = -1; return !RemoveDevice(DEVICE_NAME ":"); } return true; } #include "certs_bin.h" #include "su_tik_bin.h" #include "su_tmd_bin.h" s32 ISFS_SU() { u32 key = 0; return ES_Identify((signed_blob *) certs_bin, sizeof(certs_bin), (signed_blob *) su_tmd_bin, sizeof(su_tmd_bin), (signed_blob *) su_tik_bin, sizeof(su_tik_bin), &key); } schismtracker-20180209/sys/wii/isfs.h000066400000000000000000000023361323741476300173640ustar00rootroot00000000000000/* libisfs -- a NAND filesystem devoptab library for the Wii Copyright (C) 2008 Joseph Jordan This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1.The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2.Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3.This notice may not be removed or altered from any source distribution. [In compliance with the above: I patched this code up somewhat so that it builds with all warnings. -- Storlek] */ #ifndef _LIBISFS_H #define _LIBISFS_H #include #define ISFS_MAXPATHLEN (ISFS_MAXPATH + 1) bool ISFS_Mount(void); bool ISFS_Unmount(void); s32 ISFS_SU(void); #endif /* _LIBISFS_H_ */ schismtracker-20180209/sys/wii/osdefs.c000066400000000000000000000156361323741476300177050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "osdefs.h" #include "event.h" #include "song.h" #include "it.h" // need for kbd_get_alnum #include "page.h" // need for struct key_event #include "log.h" #include #include #include #include #include #include #include #include #include "isfs.h" #define CACHE_PAGES 8 // cargopasta'd from libogc git __di_check_ahbprot static u32 _check_ahbprot(void) { s32 res; u64 title_id; u32 tmd_size; STACK_ALIGN(u32, tmdbuf, 1024, 32); res = ES_GetTitleID(&title_id); if (res < 0) { log_appendf(4, "ES_GetTitleID() failed: %d", res); return res; } res = ES_GetStoredTMDSize(title_id, &tmd_size); if (res < 0) { log_appendf(4, "ES_GetStoredTMDSize() failed: %d", res); return res; } if (tmd_size > 4096) { log_appendf(4, "TMD too big: %d", tmd_size); return -EINVAL; } res = ES_GetStoredTMD(title_id, tmdbuf, tmd_size); if (res < 0) { log_appendf(4, "ES_GetStoredTMD() failed: %d", res); return -EINVAL; } if ((tmdbuf[0x76] & 3) == 3) { return 1; } return 0; } const char *osname = "wii"; void wii_sysinit(int *pargc, char ***pargv) { DIR_ITER *dir; char *ptr = NULL; log_appendf(1, "[Wii] This is IOS%d v%X, and AHBPROT is %s", IOS_GetVersion(), IOS_GetRevision(), _check_ahbprot() > 0 ? "enabled" : "disabled"); if (*pargc == 0 && *pargv == NULL) { // I don't know if any other loaders provide similarly broken environments log_appendf(1, "[Wii] Was I just bannerbombed? Prepare for crash at exit..."); } else if (memcmp((void *) 0x80001804, "STUBHAXX", 8) == 0) { log_appendf(1, "[Wii] Hello, HBC user!"); } else { log_appendf(1, "[Wii] Where am I?!"); } ISFS_SU(); if (ISFS_Initialize() == IPC_OK) ISFS_Mount(); fatInit(CACHE_PAGES, 0); // Attempt to locate a suitable home directory. if (!*pargc || !*pargv) { // loader didn't bother setting these *pargc = 1; *pargv = malloc(sizeof(char **)); *pargv[0] = str_dup("?"); } else if (strchr(*pargv[0], '/') != NULL) { // presumably launched from hbc menu - put stuff in the boot dir // (does get_parent_directory do what I want here?) ptr = get_parent_directory(*pargv[0]); } if (!ptr) { // Make a guess anyway ptr = str_dup("sd:/apps/schismtracker"); } if (chdir(ptr) != 0) { free(ptr); dir = diropen("sd:/"); if (dir) { // Ok at least the sd card works, there's some other dysfunction dirclose(dir); ptr = str_dup("sd:/"); } else { // Safe (but useless) default ptr = str_dup("isfs:/"); } chdir(ptr); // Hope that worked, otherwise we're hosed } put_env_var("HOME", ptr); free(ptr); } void wii_sysexit(void) { ISFS_Deinitialize(); } void wii_sdlinit(void) { int n, total; if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0) { log_appendf(4, "joystick init failed: %s", SDL_GetError()); return; } total = SDL_NumJoysticks(); for (n = 0; n < total; n++) { SDL_Joystick *js = SDL_JoystickOpen(n); if (js == NULL) { log_appendf(4, "[%d] open fail", n); continue; } } } static int lasthatsym = 0; static SDLKey hat_to_keysym(int value) { // up/down take precedence over left/right switch (value) { case SDL_HAT_LEFTUP: case SDL_HAT_UP: case SDL_HAT_RIGHTUP: return SDLK_UP; case SDL_HAT_LEFTDOWN: case SDL_HAT_DOWN: case SDL_HAT_RIGHTDOWN: return SDLK_DOWN; case SDL_HAT_LEFT: return SDLK_LEFT; case SDL_HAT_RIGHT: return SDLK_RIGHT; default: // SDL_HAT_CENTERED return 0; } } // Huge event-rewriting hack to get at least a sort of useful interface with no keyboard. // It's obviously impossible to provide any sort of editing functions in this manner, // but it at least allows simple song playback. int wii_sdlevent(SDL_Event *event) { SDL_Event newev = {}; SDLKey sym; switch (event->type) { case SDL_KEYDOWN: case SDL_KEYUP: { // argh struct key_event k = { .mod = event->key.keysym.mod, .sym = event->key.keysym.sym, }; event->key.keysym.unicode = kbd_get_alnum(&k); } return 1; case SDL_JOYHATMOTION: // TODO key repeat for these, somehow sym = hat_to_keysym(event->jhat.value); if (sym) { newev.type = SDL_KEYDOWN; newev.key.state = SDL_PRESSED; lasthatsym = sym; } else { newev.type = SDL_KEYUP; newev.key.state = SDL_RELEASED; sym = lasthatsym; lasthatsym = 0; } newev.key.which = event->jhat.which; newev.key.keysym.sym = sym; newev.key.type = newev.type; // is this a no-op? *event = newev; return 1; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: switch (event->jbutton.button) { case 0: // A case 1: // B default: return 0; case 2: // 1 if (song_get_mode() == MODE_STOPPED) { // nothing playing? go to load screen sym = SDLK_F9; } else { sym = SDLK_F8; } break; case 3: // 2 if (status.current_page == PAGE_LOAD_MODULE) { // if the cursor is on a song, load then play; otherwise handle as enter // (hmm. ctrl-enter?) sym = SDLK_RETURN; } else { // F5 key sym = SDLK_F5; } break; case 4: // - // dialog escape, or jump back a pattern if (status.dialog_type) { sym = SDLK_ESCAPE; break; } else if (event->type == SDL_JOYBUTTONDOWN && song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() - 1); } return 0; case 5: // + // dialog enter, or jump forward a pattern if (status.dialog_type) { sym = SDLK_RETURN; break; } else if (event->type == SDL_JOYBUTTONDOWN && song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() + 1); } return 0; case 6: // Home event->type = SDL_QUIT; return 1; } newev.key.which = event->jbutton.which; newev.key.keysym.sym = sym; if (event->type == SDL_JOYBUTTONDOWN) { newev.type = SDL_KEYDOWN; newev.key.state = SDL_PRESSED; } else { newev.type = SDL_KEYUP; newev.key.state = SDL_RELEASED; } newev.key.type = newev.type; // no-op? *event = newev; return 1; } return 1; } schismtracker-20180209/sys/wii/schismtracker/000077500000000000000000000000001323741476300211055ustar00rootroot00000000000000schismtracker-20180209/sys/wii/schismtracker/icon.png000066400000000000000000000331401323741476300225440ustar00rootroot00000000000000PNG  IHDR0VsRGBgAMA a cHRMz&u0`:pQ< pHYsodtEXtSoftwarePaint.NET v3.36%5IDATx^|XUWgsծXPt) DzU@zUz`Qc-ƖbbLb1=L&3&1d&]~If}ֳ>ǰ Qb4ZFа~c~ òbJi7FX _l)9Y'D7y/^G4"|^zD O" aU1Y>H+\1i-z'Xq&s& f*DŽ3`;Yeh Q4XcQF?љ1 34`E%d0_RȎmR>VHi= 5D 7;)#ѕ|D9^Fw`f1-lـ 05sx;gd9eBB0׋GV 9@1Eݣp`.ldw Bl V\Nr&bLسc2'hr0h5ed#Vֳu# $%؉/op!jĈƕX@(.҉6xHH-:0xjl ݌!|q\P!qş@ >M'qA… #$||we 1> +<o *PW&RAp 8;h(3g&lHFQF"udI,gcI$DWJlx\=$o&S tUX^,\'o7EŒ-4eBN /ĕ ޖ2M0]]#|qVK UO!{%f v1o YM+`\+y$1^ɜk$4kA/ "bMGFXRDCX clM1pNJw H+u#h{+ba-\/K#}ZwPcf{G>q.PJL0/B 4 (r )R5|R;b;' <@0=D0 UOZL* %陜ՃU!c 3 pn7gDB`fdL0F0f#XQC!DXfߤba ʚR9iѓҢ$ GX?z~<f ?8<_u"ɭD*H-KĂu^1ȇ/KkXY cw vւgA=A$ [0C2p[FZ1cp5cLGpz3FGu7$ 09:azNj W^nb;BKK9&/N]Ie"F%z{ qx9X[E"䀴3209Ur@^U]TWWDK=(T10aO Ty*, 55T%hѓa ƒ:M,XDf gizIG4x$GWUj~#H5$0q x VZ|hznhVy0VQ 4հI~IPXI5 <?<"H[% 'Mvb2*اA)D P5݀`\*i l˃Xv*BÏ"-3xĐU ;*Y׈*  ko*1n fpF|ÌdFZBn>es:Na1m:!FqzsNTDEj'Lt>~X)mw==b0~ &IM1;KbWEOB ]5p\ LjPA @`1?Jb X5*{U.1p$3, 7!X-?Tj ~ ЬyMZ"B7 rF@HyyEs΁K&Ό1CP=̂f 03J1ho2kZU.U+հAcwz[UIjʠs;.UA/ӧT;i0* OGM(5  4(8PRR_*%hɩ\u#RN P)9 Cyf&Č3鐐mAsSbkBjXBH?þ*)y ПbȾPя7S(F#lc~tr  a)JXMO& 6D0}\B(;*H(I苪m `hp7mA).ҎTD~,O Dg!B&sIp=wڍTOfz)!u c4!GHaxS(0""@"x¿=o,"2#`DOx4!Wv]gKl AC)S^d&hB/D\SCF@2Ǖ2y:JpnQg?;2ּ(17W-`bF-)&!J:>y̑Š|$Uɇ"5_$1Ex9*i ,t -laxȤMp z2 hbڐ4ә2"^!>$`GXbwh#1t&X(gS#ߌQ3hEBp搳9xVxwEX\rAIpJaPt`ٜ? m`Ęj  A/2 iAJmIC R / !8 (_ * ^8\d·Db(+bc1}`>23ZLLd,q 7P Hh2Eh3pe CoZÚ;S`s֖q@h¶qFap4R(Ottbx0o3ײ  ٝ0>1T4K PA1@ '4([X\Hf'7j2H7kfToVeC[L>vF u:܁J7%옣Za9~-%XYLa" !*g=z0׎2@R./Wѵ||j:`+r! =`(UGU  ŀ\~VmkˌFeƒ=3In+@jrl$Ip =3lVzD|FXI\%D-κSa,[v4~3~:c1K0baE 1S`I`mT8{|x }l4 o֗l44[4LF |'RNC£ 5 /H@.xo aQ8|HZ;+NC:SSph-]vpgtiߑIE;`f.C'\[F7sYb @hX$i7Cu,lGbԥ̕B |zFA18՗2[Y< I`A~Du_|7ݟ6@@z DvxJo TgCՃ[SGJf:P+,T`(2ACP _ttk(UTsicB 'DUiw[5Br!ۚ%%l8=b05bn/W/o"9LdbNaCunef뮝H!P1F[qgNBT-7opByuzOLƊօC%{ċ >,>Q~eumZy܀\1l%GYD;ҝ|~jP&m1mh:v Ͻ~v7. va{3r"?l~29yP8Ǔ&iÍALr(]N?.ܪ{v=PLXȡ̀ ~WH1 #*22:6G@`tCOc"@^̌@,r* XLK(Kx`=o"0^M>5w Olq c 5ΞGـ88,  C cPCSb|;g!WaSyiqTA )BJ*6+ * ۅ B*!k%'DŽ3,(5G=~,Mgoٽ\h)LpdFG9zp(_<4H8`x,;\7퟿>|````5+^bq9k.!]LjRp{ܵ CAHkf Jaڍfu-^sh9_sSH=YRPsn4Ĩgaui!Sgl8Ke-3TGYFZwFU Zj_ Lc#k]r߶msETlt½Brp.,U#췞?Be)PK(%,r%j9DpdT P]~22eW8 ^(W2ܧOxjKwh-#Q mR}JRX+(r|mPYd--{^MmwW?F~ѓږ= W+k5Kuu/+200#Ht8h=L&Ɨ*^YZhyv68[^$5ϛmG?/59ѾMi~_t7g~_G??~Sn%E]EiXةlcfíO~S7۷oP!fϥ?z7>7.?q>s}}xs2:Ǵ 2RǤRb"\ό FǢNNJ5ίg6\]ֲӱbc~ջzcisYcRB1!ͱlcnu:?w~0q~i55WVw\z/m}> [uuٍN0)ֳdsNSV:Ǽ6.5;wsq;ٯoz;__&.g/gqs\5/ة/h$w>{>K7S٥h.ZfS /ч'6 (?q͢/z8Bٮƕ1޿۳wr֘l~t&Zz\$IU)rٺlmh.XSKhux/}ָsf/[i`_kרZ +ՆJX6(ոd+f_VfuŸqv}}z} ѝ mF{ҷet9lB|]p>8xJm&.nzo՛t>չOjx ~eΰOݻ/v5ɖ;&0?z=<eMgo5}+ǯ7x/tƧn6m~~-vk:pԫ:ƧG_w=۸C/\}kaKk|_/i>~㩛ow]{Lcɖ3MO7<^hӎMO{s|bˑͧo4=V˥/᩷Nh:\+O\ldc?imGqmvqm'];=oovM_k<|eв֟x3w>m9rMo6iGOkyeɦ'wYz&s۱]_j=v햋w~7/}a+6xun~r5?>h}}{>ݿަ?tݍW[{f!e[vMt<7O+I->e.zqnt6Oe9}>{R^[C)\C[IWU.ޠ䯵Y6c}m6Hitr\.j١KN[6%_)ڤWO|Ӣ)rzHoT+5rh6@V&mh|17J^o|揔JF V1Url&g `rǸaȬSu*ZX? lDWhb*$f=%^@i׽}CWg7Kj/*Kn|'Wo|sAݦ[+_njYtrͳoAĸfR/nە)m{n)A +<%֦fcpQmCH:M\&B$6j;%#W%iZ ڄF%mfׇ٤vXړĆ[vJqJB"'fmj߫?[r[3ZC_*9z&3&R[戾jϹ{P׮%W0Rlṅ+Q 1rrs<ܥww5mY -k弾GJbm\[ѧ.:s]S_q lONnv?ckHSw+{vR`\o^Ͽ*m9R@&b 2V)AJ4sK2\<tM&&r&X , QrJW3a+_b̊R\&FS#5ɘjk5)4 /44):d`3x'kfEidxh/U 3R`䗁;4in.K_wKҥv,Ӧwi⛤(X46j@%JT |YSYZ!vMR4tB1L&ΕCCvđ>Rjm7YNd^o7O5_G%{^RjO*Gn=*vKŴNM\υo${ϼ-w ~U~Zj8%oӤKQI)M&͢s#7w@, mx[MU{?S̕CJR9%VNjCKLR:6 wҔ%z,O#Ks% O2"BA &dNf%j|ReLMdECrb%]NjU2#NAKn.]Q5{x}dREZĭT.}񝧤CCy_~oTHm|&cNq#sbmss4A@TMjѕnW:.oe/ݏ]}3點V`M{{[(8 &I&ŝsBKRV0(6 H3I2g&<4(77$ $$$ltZY"&z:zeƱ1fI5)͙돍*\m^gV u;LWdwgNX]e{l>eiid~SKYf}˞sä`QDP>qnF!F&F!FAA&nA>&yfIu )d\?6+3aT?$48~hRI+_^vli&kMrL|ͽLSMÊ[̫v[~F<>iׅ!{MWF/khINHVdi4GtոP$t-WLvh!YI)IFEJh1T/KBjAgR/EWY sBpA\k 0_1/bx}bFgC6REP{H|F"yM{_\=(!ģ=E0;Uo'%r:<&qc Nw:'f1 C:wJ#~>-ؔU%)wPI.۱ XQ)A?At=D< <a 7@q)]pFC0Xa,U4F1 PGFpҌ1t*f&4/ƉTGr`%scx ̣Ń1YmM H֣TSF OCi  PeC QfB슖?`]gr9GhQPc\xS;zݯ~rz OÁ< x5!o¢)nۇ$ ãZȡ7Bk_^ֶ Ģo&V,&ɋd4u)BF7zCο"4Y<FjC#zܣ /EZKǦ3!աMEkbv bX;d;ڒB=;agiOrcx䡮r`MG`9%߻ lJ:0ѽoEsW 2.sOhxpuBlHL3?&mJ'8B*Nr)ާe:@Aom\tN\Mq}+t4"NB\i?}jgPfa\9#vxdِ >SoGeAzz![;F,=}ukcra(ОEAcTyQfG]<AEM G8, v,D"bՅ֨ #P0?]<~/ʖ XK{5 Tю!dEG:oyU|z >o-ZMԑY #ڱ;?s!)c2\@/J{,uqtz0UHAbk-|bYae?H'.Btu?)T%LqL=DGM]PD{è6slU:i@t7n8] 9BLj JQp5*D*Cl ;"Ǔ Ӂ6,8h1v M=|aաs|r )ك3Ɉ(ia`8գaܣ#"a蒊 IENDB`schismtracker-20180209/sys/wii/schismtracker/meta.xml000066400000000000000000000007021323741476300225540ustar00rootroot00000000000000 Schism Tracker Storlek hg YYYYMMDD000000 Tracked music editor Schism Tracker is an editor and player for tracked music (IT, XM, S3M, MOD, etc.), heavily based on the look and feel of the DOS program Impulse Tracker. schismtracker-20180209/sys/wii/su_tik_bin.h000066400000000000000000000125201323741476300205420ustar00rootroot00000000000000static unsigned const char su_tik_bin[] = { '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\055', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\055', '\130', '\123', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\063', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\070', '\244', '\122', '\066', '\356', '\137', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\000', '\000', '\002', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\021', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', }; schismtracker-20180209/sys/wii/su_tmd_bin.h000066400000000000000000000101601323741476300205350ustar00rootroot00000000000000static unsigned const char su_tmd_bin[] = { '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\055', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\055', '\103', '\120', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\064', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\000', '\000', '\002', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\000', '\000', '\015', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', }; schismtracker-20180209/sys/win32/000077500000000000000000000000001323741476300164155ustar00rootroot00000000000000schismtracker-20180209/sys/win32/filetype.c000066400000000000000000000025501323741476300204040ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "osdefs.h" #include #include #include void win32_filecreated_callback(const char *filename) { /* let explorer know when we create a file. */ SHChangeNotify(SHCNE_CREATE, SHCNF_PATH|SHCNF_FLUSHNOWAIT, filename, NULL); SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH|SHCNF_FLUSHNOWAIT, filename, NULL); } schismtracker-20180209/sys/win32/localtime_r.c000066400000000000000000000027051323741476300210570ustar00rootroot00000000000000/* * aria2 - The high speed download utility * * Copyright (C) 2007 Tatsuhiro Tsujikawa * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * (Modified slightly to build with Schism Tracker) */ #define NEED_TIME #include "headers.h" #include static CRITICAL_SECTION localtime_r_cs; static void localtime_r_atexit(void) { DeleteCriticalSection(&localtime_r_cs); } struct tm * localtime_r(const time_t *timep, struct tm *result) { static struct tm *local_tm; static int initialized = 0; if (!initialized) { ++initialized; InitializeCriticalSection(&localtime_r_cs); atexit(localtime_r_atexit); } EnterCriticalSection(&localtime_r_cs); local_tm = localtime(timep); memcpy(result, local_tm, sizeof(struct tm)); LeaveCriticalSection(&localtime_r_cs); return result; } schismtracker-20180209/sys/win32/midi-win32mm.c000066400000000000000000000162451323741476300210050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #include "log.h" #include "midi.h" #include "util.h" #include #include #include #ifndef WIN32 # error You have no winmm. Why are you trying to build this file? #endif struct win32mm_midi { DWORD id; HMIDIOUT out; HMIDIIN in; MIDIINCAPS icp; MIDIOUTCAPS ocp; MIDIHDR hh; LPMIDIHDR obuf; unsigned char sysx[1024]; }; static unsigned int mm_period = 0; static unsigned int last_known_in_port = 0; static unsigned int last_known_out_port = 0; static void _win32mm_sysex(LPMIDIHDR *q, const unsigned char *d, unsigned int len) { char *z; LPMIDIHDR m; if (!d) len=0; z = mem_calloc(1, sizeof(MIDIHDR) + len); m = (LPMIDIHDR)z; if (len) memcpy(z + sizeof(MIDIHDR), d, len); m->lpData = (z+sizeof(MIDIHDR)); m->dwBufferLength = len; m->lpNext = *q; m->dwOffset = 0; (*q) = (m); } static void _win32mm_send(struct midi_port *p, const 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)); } } } struct curry { struct midi_port *p; unsigned char *d; unsigned int len; }; static CALLBACK void _win32mm_xp_output(UNUSED UINT uTimerID, UNUSED UINT uMsg, DWORD_PTR dwUser, UNUSED DWORD_PTR dw1, UNUSED DWORD_PTR dw2) { struct curry *c; c = (struct curry *)dwUser; _win32mm_send(c->p, c->d, c->len, 0); free(c); } static void _win32mm_send_xp(struct midi_port *p, const unsigned char *buf, unsigned int len, unsigned int delay) { /* version for windows XP */ struct curry *c; if (!delay) _win32mm_send(p,buf,len,0); if (len == 0) return; c = mem_alloc(sizeof(struct curry) + len); c->p = p; c->d = ((unsigned char*)c)+sizeof(struct curry); c->len = len; timeSetEvent(delay, mm_period, _win32mm_xp_output, (DWORD_PTR)c, TIME_ONESHOT | TIME_CALLBACK_FUNCTION); } static CALLBACK void _win32mm_inputcb(UNUSED HMIDIIN in, UINT wmsg, DWORD_PTR inst, DWORD_PTR param1, DWORD_PTR 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: { MIDIHDR* hdr = (MIDIHDR*) param1; if (hdr->dwBytesRecorded > 0) { /* long data */ m = p->userdata; midi_received_cb(p, (unsigned char *) m->hh.lpData, m->hh.dwBytesRecorded); //TODO: The event for the midi sysex (midi-core.c SCHISM_EVENT_MIDI_SYSEX) should // call us back so that we can add the buffer back with midiInAddBuffer(). } 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; 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)midiInUnprepareHeader(m->in,&m->hh,sizeof(m->hh)); (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 win32mm_midi *data; UINT i; UINT mmin, mmout; WORD r; mmin = midiInGetNumDevs(); for (i = last_known_in_port; i < mmin; i++) { data = mem_calloc(1, 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_calloc(1, 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) { static struct midi_driver driver = {}; TIMECAPS caps; driver.flags = 0; driver.poll = _win32mm_poll; driver.thread = NULL; driver.enable = _win32mm_start; driver.disable = _win32mm_stop; { if (timeGetDevCaps(&caps, sizeof(caps)) == 0) { mm_period = caps.wPeriodMin; if (timeBeginPeriod(mm_period) == 0) { driver.send = _win32mm_send_xp; driver.flags |= MIDI_PORT_CAN_SCHEDULE; } else { driver.send = _win32mm_send; log_appendf(4, "Cannot install WINMM timer (midi output will skip)"); } } else { driver.send = _win32mm_send; log_appendf(4, "Cannot get WINMM timer capabilities (midi output will skip)"); } } if (!midi_provider_register("Win32MM", &driver)) return 0; return 1; } schismtracker-20180209/sys/win32/osdefs.c000066400000000000000000000117311323741476300200470ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ /* Predominantly this file is keyboard crap, but we also get the network configured here */ #include "headers.h" #include "sdlmain.h" #include "it.h" #include "osdefs.h" #include #include /* eek... */ void win32_get_modkey(int *mk) { BYTE ks[256]; if (GetKeyboardState(ks) == 0) return; if (ks[VK_CAPITAL] & 128) { status.flags |= CAPS_PRESSED; } else { status.flags &= ~CAPS_PRESSED; } (*mk) = ((*mk) & ~(KMOD_NUM|KMOD_CAPS)) | ((ks[VK_NUMLOCK]&1) ? KMOD_NUM : 0) | ((ks[VK_CAPITAL]&1) ? KMOD_CAPS : 0); } /* more windows key stuff... */ unsigned int key_repeat_rate(void) { DWORD spd; if (!SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &spd, 0)) return 0; if (!spd) return 1; return spd; } unsigned int key_repeat_delay(void) { int delay; if (!SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &delay, 0)) return 0; switch (delay) { case 0: return 250; case 1: return 500; case 2: return 750; }; return 1000; } static HKL default_keymap; static HKL us_keymap; static void win32_setup_keymap(void) { default_keymap = GetKeyboardLayout(0); us_keymap = LoadKeyboardLayout("00000409", KLF_ACTIVATE|KLF_REPLACELANG|KLF_NOTELLSHELL); ActivateKeyboardLayout(default_keymap,0); } int key_scancode_lookup(int k, int def) { #ifndef VK_0 #define VK_0 '0' #define VK_1 '1' #define VK_2 '2' #define VK_3 '3' #define VK_4 '4' #define VK_5 '5' #define VK_6 '6' #define VK_7 '7' #define VK_8 '8' #define VK_9 '9' #define VK_A 'A' #define VK_B 'B' #define VK_C 'C' #define VK_D 'D' #define VK_E 'E' #define VK_F 'F' #define VK_G 'G' #define VK_H 'H' #define VK_I 'I' #define VK_J 'J' #define VK_K 'K' #define VK_L 'L' #define VK_M 'M' #define VK_N 'N' #define VK_O 'O' #define VK_P 'P' #define VK_Q 'Q' #define VK_R 'R' #define VK_S 'S' #define VK_T 'T' #define VK_U 'U' #define VK_V 'V' #define VK_W 'W' #define VK_X 'X' #define VK_Y 'Y' #define VK_Z 'Z' #endif /* VK_0 */ /* These keys haven't been defined, but were experimentally determined */ #define VK_SEMICOLON 0xBA #define VK_EQUALS 0xBB #define VK_COMMA 0xBC #define VK_MINUS 0xBD #define VK_PERIOD 0xBE #define VK_SLASH 0xBF #define VK_GRAVE 0xC0 #define VK_LBRACKET 0xDB #define VK_BACKSLASH 0xDC #define VK_RBRACKET 0xDD #define VK_APOSTROPHE 0xDE #define VK_BACKTICK 0xDF #define VK_OEM_102 0xE2 switch (MapVirtualKeyEx(k, 1 /* MAPVK_VSC_TO_VK */, us_keymap)) { case VK_0: return SDLK_0; case VK_1: return SDLK_1; case VK_2: return SDLK_2; case VK_3: return SDLK_3; case VK_4: return SDLK_4; case VK_5: return SDLK_5; case VK_6: return SDLK_6; case VK_7: return SDLK_7; case VK_8: return SDLK_8; case VK_9: return SDLK_9; case VK_A: return SDLK_a; case VK_B: return SDLK_b; case VK_C: return SDLK_c; case VK_D: return SDLK_d; case VK_E: return SDLK_e; case VK_F: return SDLK_f; case VK_G: return SDLK_g; case VK_H: return SDLK_h; case VK_I: return SDLK_i; case VK_J: return SDLK_j; case VK_K: return SDLK_k; case VK_L: return SDLK_l; case VK_M: return SDLK_m; case VK_N: return SDLK_n; case VK_O: return SDLK_o; case VK_P: return SDLK_p; case VK_Q: return SDLK_q; case VK_R: return SDLK_r; case VK_S: return SDLK_s; case VK_T: return SDLK_t; case VK_U: return SDLK_u; case VK_V: return SDLK_v; case VK_W: return SDLK_w; case VK_X: return SDLK_x; case VK_Y: return SDLK_y; case VK_Z: return SDLK_z; case VK_SEMICOLON: return SDLK_SEMICOLON; case VK_GRAVE: return SDLK_BACKQUOTE; case VK_APOSTROPHE: return SDLK_QUOTE; case VK_BACKTICK: return SDLK_BACKQUOTE; case VK_BACKSLASH: return SDLK_BACKSLASH; case VK_LBRACKET: return SDLK_LEFTBRACKET; case VK_RBRACKET: return SDLK_RIGHTBRACKET; }; return def; } void win32_sysinit(UNUSED int *pargc, UNUSED char ***pargv) { static WSADATA ignored = {}; win32_setup_keymap(); if (WSAStartup(0x202, &ignored) == SOCKET_ERROR) { WSACleanup(); /* ? */ status.flags |= NO_NETWORK; } } schismtracker-20180209/sys/win32/schism.nsis000066400000000000000000000053451323741476300206100ustar00rootroot00000000000000Name "Schism Tracker" Caption 'Schism Tracker' OutFile 'install.exe' InstallDir "$PROGRAMFILES\Schism Tracker" LicenseData 'COPYING.txt' LicenseBkColor 0xFFFFFF ShowInstDetails show XpStyle on Page License Page Directory Page InstFiles UninstPage uninstConfirm UninstPage instfiles AutoCloseWindow false Section SetOutPath $INSTDIR File "schismtracker.exe" File "schism.ico" File "SDL.dll" File "COPYING.txt" File "README.txt" File "NEWS.txt" File "ChangeLog.txt" WriteUninstaller "uninstall.exe" WriteRegStr HKCR ".it" "" "Schism Tracker" WriteRegStr HKCR ".s3m" "" "Schism Tracker" WriteRegStr HKCR ".mod" "" "Schism Tracker" WriteRegStr HKCR ".669" "" "Schism Tracker" WriteRegStr HKCR ".amf" "" "Schism Tracker" WriteRegStr HKCR ".ams" "" "Schism Tracker" WriteRegStr HKCR ".dbm" "" "Schism Tracker" WriteRegStr HKCR ".dmf" "" "Schism Tracker" WriteRegStr HKCR ".dsm" "" "Schism Tracker" WriteRegStr HKCR ".far" "" "Schism Tracker" WriteRegStr HKCR ".mdl" "" "Schism Tracker" WriteRegStr HKCR ".med" "" "Schism Tracker" WriteRegStr HKCR ".mt2" "" "Schism Tracker" WriteRegStr HKCR ".mtm" "" "Schism Tracker" WriteRegStr HKCR ".okt" "" "Schism Tracker" WriteRegStr HKCR ".psm" "" "Schism Tracker" WriteRegStr HKCR ".ptm" "" "Schism Tracker" WriteRegStr HKCR ".stm" "" "Schism Tracker" WriteRegStr HKCR ".ult" "" "Schism Tracker" WriteRegStr HKCR ".umx" "" "Schism Tracker" WriteRegStr HKCR ".xm" "" "Schism Tracker" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Schism Tracker" "DisplayName" "Schism Tracker (remove only)" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Schism Tracker" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegStr HKCR "Schism Tracker\Shell\open\command\" "" '"$INSTDIR\schismtracker.exe" "%1"' WriteRegStr HKCR "Schism Tracker\DefaultIcon" "" "$INSTDIR\schism.ico" CreateDirectory "$SMPROGRAMS\Schism Tracker" CreateShortCut "$SMPROGRAMS\Schism Tracker\Schism Tracker.lnk" "$INSTDIR\schismtracker.exe" "" "$INSTDIR\schism.ico" CreateShortCut "$SMPROGRAMS\Schism Tracker\Schism Font Editor.lnk" "$INSTDIR\schismtracker.exe" "--font-editor" "$INSTDIR\schism.ico" CreateShortCut "$SMPROGRAMS\Schism Tracker\Uninstall Schism Tracker.lnk" "$INSTDIR\uninstall.exe" SectionEnd Section "Uninstall" Delete "$INSTDIR\schismtracker.exe" Delete "$INSTDIR\SDL.dll" Delete "$INSTDIR\schism.ico" Delete "$INSTDIR\COPYING.txt" Delete "$INSTDIR\README.txt" Delete "$INSTDIR\NEWS.txt" Delete "$INSTDIR\ChangeLog.txt" Delete "$SMPROGRAMS\Schism Tracker\Schism Tracker.lnk" Delete "$SMPROGRAMS\Schism Tracker\Font Editor.lnk" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Schism Tracker" SectionEnd schismtracker-20180209/sys/win32/schismres.rc000066400000000000000000000025531323741476300207500ustar00rootroot00000000000000//vi:set bd=syn\ c: #include #include // grab PACKAGE_VERSION #ifndef WRC_VERSION # define WRC_VERSION 0,0,0,0 #endif #define WRC_PRODUCTVER_STR PACKAGE_VERSION #define VER_PRODUCTNAME "Schism Tracker" schismicon ICON "icons/schismres.ico" VS_VERSION_INFO VERSIONINFO FILEVERSION WRC_VERSION PRODUCTVERSION WRC_VERSION FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS 0 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "CompanyName", "Storlek" VALUE "LegalCopyright", "Copyright \xA9 2003-2012 Storlek" VALUE "Comments", "http://schismtracker.org/" VALUE "ProductName", VER_PRODUCTNAME VALUE "FileDescription", VER_PRODUCTNAME VALUE "InternalName", PACKAGE_NAME VALUE "OriginalFilename", "schismtracker.exe" VALUE "FileVersion", WRC_PRODUCTVER_STR VALUE "ProductVersion", WRC_PRODUCTVER_STR END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END schismtracker-20180209/sys/win32/slurp-win32.c000066400000000000000000000077661323741476300207060ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "config.h" #ifndef WIN32 # error You are not on Windows. What are you doing? #endif /* FIXME | this really ought to just provide an mmap() wrapper FIXME | instead of reimplementing everything separately */ #include #include #include "log.h" #include "slurp.h" // indices for 'h' (handles) enum { FILE_HANDLE = 0, MAPPING_HANDLE = 1 }; static void _win32_unmap(slurp_t *slurp) { if (slurp->data != NULL) { UnmapViewOfFile(slurp->data); slurp->data = NULL; } HANDLE *h = slurp->bextra; if (h[FILE_HANDLE] != INVALID_HANDLE_VALUE) { CloseHandle(h[FILE_HANDLE]); } if (h[MAPPING_HANDLE] != NULL) { CloseHandle(h[MAPPING_HANDLE]); } free(h); slurp->bextra = NULL; } // This reader used to return -1 sometimes, which is kind of a hack to tell the // the rest of the loading code to try some other means of opening the file, // which on win32 is basically just fopen + malloc + fread. If MapViewOfFile // won't work, chances are pretty good that stdio is going to fail as well, so // I'm just writing these cases off as every bit as unrecoverable as if the // file didn't exist. // Note: this doesn't bother setting errno; maybe it should? static int _win32_error_unmap(slurp_t *slurp, const char *filename, const char *function) { DWORD err = GetLastError(); LPTSTR errmsg; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), errmsg, 0, NULL); // I don't particularly want to split this stuff onto two lines, but // it's the only way to make the error message readable in some cases // (though no matter what, the message is still probably going to be // truncated because Windows is excessively verbose) log_appendf(4, "%s: %s: error %lu:", filename, function, err); log_appendf(4, " %s", errmsg); LocalFree(errmsg); _win32_unmap(slurp); return 0; } int slurp_win32(slurp_t *slurp, const char *filename, size_t st) { LPVOID addr; HANDLE *h = slurp->bextra = mem_alloc(sizeof(HANDLE) * 2); if ((h[FILE_HANDLE] = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { return _win32_error_unmap(slurp, filename, "CreateFile"); } if ((h[MAPPING_HANDLE] = CreateFileMapping(h[FILE_HANDLE], NULL, PAGE_READONLY, 0, 0, NULL)) == NULL) { return _win32_error_unmap(slurp, filename, "CreateFileMapping"); } if ((slurp->data = MapViewOfFile(h[MAPPING_HANDLE], FILE_MAP_READ, 0, 0, 0)) == NULL) { return _win32_error_unmap(slurp, filename, "MapViewOfFile"); } slurp->length = st; slurp->closure = _win32_unmap; return 1; } schismtracker-20180209/sys/win32/volume-win32mm.c000066400000000000000000000045101323741476300213620ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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" #include "osdefs.h" #ifndef WIN32 # error Why do you want to build this if you do not intend to use it? #endif #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_volume_get_max(void) { return 0xFF; } static HWAVEOUT open_mixer(void) { 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_volume_read(int *left, int *right) { DWORD vol; *left = *right = 0; waveOutGetVolume(NULL,&vol); *left = (vol & 0xFFFF) >> 8; *right = (vol >> 16) >> 8; } void win32mm_volume_write(int left, int right) { DWORD vol = ((left & 0xFF)<<8) | ((right & 0xFF)<<(16+8)); waveOutSetVolume(NULL,vol); } schismtracker-20180209/sys/win32/wine-ddraw.h000066400000000000000000005017061323741476300206400ustar00rootroot00000000000000/* this was hacked up slightly to build outside of the rest of wine * it works well enough to build schism. */ /* * Copyright (C) the Wine project * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef __WINE_DDRAW_H #define __WINE_DDRAW_H #define COM_NO_WINDOWS_H #include #ifdef __cplusplus extern "C" { #endif /* defined(__cplusplus) */ #ifndef DIRECTDRAW_VERSION #define DIRECTDRAW_VERSION 0x0700 #endif /* DIRECTDRAW_VERSION */ /***************************************************************************** * Predeclare the interfaces */ #ifndef __DDRAW_GUID_DEFINED__ DEFINE_GUID( CLSID_DirectDraw, 0xD7B70EE0,0x4340,0x11CF,0xB0,0x63,0x00,0x20,0xAF,0xC2,0xCD,0x35 ); DEFINE_GUID( CLSID_DirectDraw7, 0x3C305196,0x50DB,0x11D3,0x9C,0xFE,0x00,0xC0,0x4F,0xD9,0x30,0xC5 ); DEFINE_GUID( CLSID_DirectDrawClipper, 0x593817A0,0x7DB3,0x11CF,0xA2,0xDE,0x00,0xAA,0x00,0xb9,0x33,0x56 ); DEFINE_GUID( IID_IDirectDraw, 0x6C14DB80,0xA733,0x11CE,0xA5,0x21,0x00,0x20,0xAF,0x0B,0xE5,0x60 ); DEFINE_GUID( IID_IDirectDraw2, 0xB3A6F3E0,0x2B43,0x11CF,0xA2,0xDE,0x00,0xAA,0x00,0xB9,0x33,0x56 ); DEFINE_GUID( IID_IDirectDraw3, 0x618f8ad4,0x8b7a,0x11d0,0x8f,0xcc,0x0,0xc0,0x4f,0xd9,0x18,0x9d ); DEFINE_GUID( IID_IDirectDraw4, 0x9c59509a,0x39bd,0x11d1,0x8c,0x4a,0x00,0xc0,0x4f,0xd9,0x30,0xc5 ); DEFINE_GUID( IID_IDirectDraw7, 0x15e65ec0,0x3b9c,0x11d2,0xb9,0x2f,0x00,0x60,0x97,0x97,0xea,0x5b ); DEFINE_GUID( IID_IDirectDrawSurface, 0x6C14DB81,0xA733,0x11CE,0xA5,0x21,0x00,0x20,0xAF,0x0B,0xE5,0x60 ); DEFINE_GUID( IID_IDirectDrawSurface2, 0x57805885,0x6eec,0x11cf,0x94,0x41,0xa8,0x23,0x03,0xc1,0x0e,0x27 ); DEFINE_GUID( IID_IDirectDrawSurface3, 0xDA044E00,0x69B2,0x11D0,0xA1,0xD5,0x00,0xAA,0x00,0xB8,0xDF,0xBB ); DEFINE_GUID( IID_IDirectDrawSurface4, 0x0B2B8630,0xAD35,0x11D0,0x8E,0xA6,0x00,0x60,0x97,0x97,0xEA,0x5B ); DEFINE_GUID( IID_IDirectDrawSurface7, 0x06675a80,0x3b9b,0x11d2,0xb9,0x2f,0x00,0x60,0x97,0x97,0xea,0x5b ); DEFINE_GUID( IID_IDirectDrawPalette, 0x6C14DB84,0xA733,0x11CE,0xA5,0x21,0x00,0x20,0xAF,0x0B,0xE5,0x60 ); DEFINE_GUID( IID_IDirectDrawClipper, 0x6C14DB85,0xA733,0x11CE,0xA5,0x21,0x00,0x20,0xAF,0x0B,0xE5,0x60 ); DEFINE_GUID( IID_IDirectDrawColorControl,0x4B9F0EE0,0x0D7E,0x11D0,0x9B,0x06,0x00,0xA0,0xC9,0x03,0xA3,0xB8 ); DEFINE_GUID( IID_IDirectDrawGammaControl,0x69C11C3E,0xB46B,0x11D1,0xAD,0x7A,0x00,0xC0,0x4F,0xC2,0x9B,0x4E ); #endif typedef struct IDirectDraw *LPDIRECTDRAW; typedef struct IDirectDraw2 *LPDIRECTDRAW2; typedef struct IDirectDraw3 *LPDIRECTDRAW3; typedef struct IDirectDraw4 *LPDIRECTDRAW4; typedef struct IDirectDraw7 *LPDIRECTDRAW7; typedef struct IDirectDrawClipper *LPDIRECTDRAWCLIPPER; typedef struct IDirectDrawPalette *LPDIRECTDRAWPALETTE; typedef struct IDirectDrawSurface *LPDIRECTDRAWSURFACE; typedef struct IDirectDrawSurface2 *LPDIRECTDRAWSURFACE2; typedef struct IDirectDrawSurface3 *LPDIRECTDRAWSURFACE3; typedef struct IDirectDrawSurface4 *LPDIRECTDRAWSURFACE4; typedef struct IDirectDrawSurface7 *LPDIRECTDRAWSURFACE7; typedef struct IDirectDrawColorControl *LPDIRECTDRAWCOLORCONTROL; typedef struct IDirectDrawGammaControl *LPDIRECTDRAWGAMMACONTROL; #define DDENUMRET_CANCEL 0 #define DDENUMRET_OK 1 #define DD_OK 0 #define _FACDD 0x876 #define MAKE_DDHRESULT( code ) MAKE_HRESULT( 1, _FACDD, code ) #define DDERR_ALREADYINITIALIZED MAKE_DDHRESULT( 5 ) #define DDERR_CANNOTATTACHSURFACE MAKE_DDHRESULT( 10 ) #define DDERR_CANNOTDETACHSURFACE MAKE_DDHRESULT( 20 ) #define DDERR_CURRENTLYNOTAVAIL MAKE_DDHRESULT( 40 ) #define DDERR_EXCEPTION MAKE_DDHRESULT( 55 ) #define DDERR_GENERIC E_FAIL #define DDERR_HEIGHTALIGN MAKE_DDHRESULT( 90 ) #define DDERR_INCOMPATIBLEPRIMARY MAKE_DDHRESULT( 95 ) #define DDERR_INVALIDCAPS MAKE_DDHRESULT( 100 ) #define DDERR_INVALIDCLIPLIST MAKE_DDHRESULT( 110 ) #define DDERR_INVALIDMODE MAKE_DDHRESULT( 120 ) #define DDERR_INVALIDOBJECT MAKE_DDHRESULT( 130 ) #define DDERR_INVALIDPARAMS E_INVALIDARG #define DDERR_INVALIDPIXELFORMAT MAKE_DDHRESULT( 145 ) #define DDERR_INVALIDRECT MAKE_DDHRESULT( 150 ) #define DDERR_LOCKEDSURFACES MAKE_DDHRESULT( 160 ) #define DDERR_NO3D MAKE_DDHRESULT( 170 ) #define DDERR_NOALPHAHW MAKE_DDHRESULT( 180 ) #define DDERR_NOSTEREOHARDWARE MAKE_DDHRESULT( 181 ) #define DDERR_NOSURFACELEFT MAKE_DDHRESULT( 182 ) #define DDERR_NOCLIPLIST MAKE_DDHRESULT( 205 ) #define DDERR_NOCOLORCONVHW MAKE_DDHRESULT( 210 ) #define DDERR_NOCOOPERATIVELEVELSET MAKE_DDHRESULT( 212 ) #define DDERR_NOCOLORKEY MAKE_DDHRESULT( 215 ) #define DDERR_NOCOLORKEYHW MAKE_DDHRESULT( 220 ) #define DDERR_NODIRECTDRAWSUPPORT MAKE_DDHRESULT( 222 ) #define DDERR_NOEXCLUSIVEMODE MAKE_DDHRESULT( 225 ) #define DDERR_NOFLIPHW MAKE_DDHRESULT( 230 ) #define DDERR_NOGDI MAKE_DDHRESULT( 240 ) #define DDERR_NOMIRRORHW MAKE_DDHRESULT( 250 ) #define DDERR_NOTFOUND MAKE_DDHRESULT( 255 ) #define DDERR_NOOVERLAYHW MAKE_DDHRESULT( 260 ) #define DDERR_OVERLAPPINGRECTS MAKE_DDHRESULT( 270 ) #define DDERR_NORASTEROPHW MAKE_DDHRESULT( 280 ) #define DDERR_NOROTATIONHW MAKE_DDHRESULT( 290 ) #define DDERR_NOSTRETCHHW MAKE_DDHRESULT( 310 ) #define DDERR_NOT4BITCOLOR MAKE_DDHRESULT( 316 ) #define DDERR_NOT4BITCOLORINDEX MAKE_DDHRESULT( 317 ) #define DDERR_NOT8BITCOLOR MAKE_DDHRESULT( 320 ) #define DDERR_NOTEXTUREHW MAKE_DDHRESULT( 330 ) #define DDERR_NOVSYNCHW MAKE_DDHRESULT( 335 ) #define DDERR_NOZBUFFERHW MAKE_DDHRESULT( 340 ) #define DDERR_NOZOVERLAYHW MAKE_DDHRESULT( 350 ) #define DDERR_OUTOFCAPS MAKE_DDHRESULT( 360 ) #define DDERR_OUTOFMEMORY E_OUTOFMEMORY #define DDERR_OUTOFVIDEOMEMORY MAKE_DDHRESULT( 380 ) #define DDERR_OVERLAYCANTCLIP MAKE_DDHRESULT( 382 ) #define DDERR_OVERLAYCOLORKEYONLYONEACTIVE MAKE_DDHRESULT( 384 ) #define DDERR_PALETTEBUSY MAKE_DDHRESULT( 387 ) #define DDERR_COLORKEYNOTSET MAKE_DDHRESULT( 400 ) #define DDERR_SURFACEALREADYATTACHED MAKE_DDHRESULT( 410 ) #define DDERR_SURFACEALREADYDEPENDENT MAKE_DDHRESULT( 420 ) #define DDERR_SURFACEBUSY MAKE_DDHRESULT( 430 ) #define DDERR_CANTLOCKSURFACE MAKE_DDHRESULT( 435 ) #define DDERR_SURFACEISOBSCURED MAKE_DDHRESULT( 440 ) #define DDERR_SURFACELOST MAKE_DDHRESULT( 450 ) #define DDERR_SURFACENOTATTACHED MAKE_DDHRESULT( 460 ) #define DDERR_TOOBIGHEIGHT MAKE_DDHRESULT( 470 ) #define DDERR_TOOBIGSIZE MAKE_DDHRESULT( 480 ) #define DDERR_TOOBIGWIDTH MAKE_DDHRESULT( 490 ) #define DDERR_UNSUPPORTED E_NOTIMPL #define DDERR_UNSUPPORTEDFORMAT MAKE_DDHRESULT( 510 ) #define DDERR_UNSUPPORTEDMASK MAKE_DDHRESULT( 520 ) #define DDERR_INVALIDSTREAM MAKE_DDHRESULT( 521 ) #define DDERR_VERTICALBLANKINPROGRESS MAKE_DDHRESULT( 537 ) #define DDERR_WASSTILLDRAWING MAKE_DDHRESULT( 540 ) #define DDERR_DDSCAPSCOMPLEXREQUIRED MAKE_DDHRESULT( 542 ) #define DDERR_XALIGN MAKE_DDHRESULT( 560 ) #define DDERR_INVALIDDIRECTDRAWGUID MAKE_DDHRESULT( 561 ) #define DDERR_DIRECTDRAWALREADYCREATED MAKE_DDHRESULT( 562 ) #define DDERR_NODIRECTDRAWHW MAKE_DDHRESULT( 563 ) #define DDERR_PRIMARYSURFACEALREADYEXISTS MAKE_DDHRESULT( 564 ) #define DDERR_NOEMULATION MAKE_DDHRESULT( 565 ) #define DDERR_REGIONTOOSMALL MAKE_DDHRESULT( 566 ) #define DDERR_CLIPPERISUSINGHWND MAKE_DDHRESULT( 567 ) #define DDERR_NOCLIPPERATTACHED MAKE_DDHRESULT( 568 ) #define DDERR_NOHWND MAKE_DDHRESULT( 569 ) #define DDERR_HWNDSUBCLASSED MAKE_DDHRESULT( 570 ) #define DDERR_HWNDALREADYSET MAKE_DDHRESULT( 571 ) #define DDERR_NOPALETTEATTACHED MAKE_DDHRESULT( 572 ) #define DDERR_NOPALETTEHW MAKE_DDHRESULT( 573 ) #define DDERR_BLTFASTCANTCLIP MAKE_DDHRESULT( 574 ) #define DDERR_NOBLTHW MAKE_DDHRESULT( 575 ) #define DDERR_NODDROPSHW MAKE_DDHRESULT( 576 ) #define DDERR_OVERLAYNOTVISIBLE MAKE_DDHRESULT( 577 ) #define DDERR_NOOVERLAYDEST MAKE_DDHRESULT( 578 ) #define DDERR_INVALIDPOSITION MAKE_DDHRESULT( 579 ) #define DDERR_NOTAOVERLAYSURFACE MAKE_DDHRESULT( 580 ) #define DDERR_EXCLUSIVEMODEALREADYSET MAKE_DDHRESULT( 581 ) #define DDERR_NOTFLIPPABLE MAKE_DDHRESULT( 582 ) #define DDERR_CANTDUPLICATE MAKE_DDHRESULT( 583 ) #define DDERR_NOTLOCKED MAKE_DDHRESULT( 584 ) #define DDERR_CANTCREATEDC MAKE_DDHRESULT( 585 ) #define DDERR_NODC MAKE_DDHRESULT( 586 ) #define DDERR_WRONGMODE MAKE_DDHRESULT( 587 ) #define DDERR_IMPLICITLYCREATED MAKE_DDHRESULT( 588 ) #define DDERR_NOTPALETTIZED MAKE_DDHRESULT( 589 ) #define DDERR_UNSUPPORTEDMODE MAKE_DDHRESULT( 590 ) #define DDERR_NOMIPMAPHW MAKE_DDHRESULT( 591 ) #define DDERR_INVALIDSURFACETYPE MAKE_DDHRESULT( 592 ) #define DDERR_NOOPTIMIZEHW MAKE_DDHRESULT( 600 ) #define DDERR_NOTLOADED MAKE_DDHRESULT( 601 ) #define DDERR_NOFOCUSWINDOW MAKE_DDHRESULT( 602 ) #define DDERR_NOTONMIPMAPSUBLEVEL MAKE_DDHRESULT( 603 ) #define DDERR_DCALREADYCREATED MAKE_DDHRESULT( 620 ) #define DDERR_NONONLOCALVIDMEM MAKE_DDHRESULT( 630 ) #define DDERR_CANTPAGELOCK MAKE_DDHRESULT( 640 ) #define DDERR_CANTPAGEUNLOCK MAKE_DDHRESULT( 660 ) #define DDERR_NOTPAGELOCKED MAKE_DDHRESULT( 680 ) #define DDERR_MOREDATA MAKE_DDHRESULT( 690 ) #define DDERR_EXPIRED MAKE_DDHRESULT( 691 ) #define DDERR_TESTFINISHED MAKE_DDHRESULT( 692 ) #define DDERR_NEWMODE MAKE_DDHRESULT( 693 ) #define DDERR_D3DNOTINITIALIZED MAKE_DDHRESULT( 694 ) #define DDERR_VIDEONOTACTIVE MAKE_DDHRESULT( 695 ) #define DDERR_NOMONITORINFORMATION MAKE_DDHRESULT( 696 ) #define DDERR_NODRIVERSUPPORT MAKE_DDHRESULT( 697 ) #define DDERR_DEVICEDOESNTOWNSURFACE MAKE_DDHRESULT( 699 ) #define DDERR_NOTINITIALIZED CO_E_NOTINITIALIZED /* dwFlags for Blt* */ #define DDBLT_ALPHADEST 0x00000001 #define DDBLT_ALPHADESTCONSTOVERRIDE 0x00000002 #define DDBLT_ALPHADESTNEG 0x00000004 #define DDBLT_ALPHADESTSURFACEOVERRIDE 0x00000008 #define DDBLT_ALPHAEDGEBLEND 0x00000010 #define DDBLT_ALPHASRC 0x00000020 #define DDBLT_ALPHASRCCONSTOVERRIDE 0x00000040 #define DDBLT_ALPHASRCNEG 0x00000080 #define DDBLT_ALPHASRCSURFACEOVERRIDE 0x00000100 #define DDBLT_ASYNC 0x00000200 #define DDBLT_COLORFILL 0x00000400 #define DDBLT_DDFX 0x00000800 #define DDBLT_DDROPS 0x00001000 #define DDBLT_KEYDEST 0x00002000 #define DDBLT_KEYDESTOVERRIDE 0x00004000 #define DDBLT_KEYSRC 0x00008000 #define DDBLT_KEYSRCOVERRIDE 0x00010000 #define DDBLT_ROP 0x00020000 #define DDBLT_ROTATIONANGLE 0x00040000 #define DDBLT_ZBUFFER 0x00080000 #define DDBLT_ZBUFFERDESTCONSTOVERRIDE 0x00100000 #define DDBLT_ZBUFFERDESTOVERRIDE 0x00200000 #define DDBLT_ZBUFFERSRCCONSTOVERRIDE 0x00400000 #define DDBLT_ZBUFFERSRCOVERRIDE 0x00800000 #define DDBLT_WAIT 0x01000000 #define DDBLT_DEPTHFILL 0x02000000 #define DDBLT_DONOTWAIT 0x08000000 /* dwTrans for BltFast */ #define DDBLTFAST_NOCOLORKEY 0x00000000 #define DDBLTFAST_SRCCOLORKEY 0x00000001 #define DDBLTFAST_DESTCOLORKEY 0x00000002 #define DDBLTFAST_WAIT 0x00000010 #define DDBLTFAST_DONOTWAIT 0x00000020 /* dwFlags for Flip */ #define DDFLIP_WAIT 0x00000001 #define DDFLIP_EVEN 0x00000002 /* only valid for overlay */ #define DDFLIP_ODD 0x00000004 /* only valid for overlay */ #define DDFLIP_NOVSYNC 0x00000008 #define DDFLIP_STEREO 0x00000010 #define DDFLIP_DONOTWAIT 0x00000020 #define DDFLIP_INTERVAL2 0x02000000 #define DDFLIP_INTERVAL3 0x03000000 #define DDFLIP_INTERVAL4 0x04000000 /* dwFlags for GetBltStatus */ #define DDGBS_CANBLT 0x00000001 #define DDGBS_ISBLTDONE 0x00000002 /* dwFlags for IDirectDrawSurface7::GetFlipStatus */ #define DDGFS_CANFLIP 1L #define DDGFS_ISFLIPDONE 2L /* dwFlags for IDirectDrawSurface7::SetPrivateData */ #define DDSPD_IUNKNOWNPOINTER 1L #define DDSPD_VOLATILE 2L /* DDSCAPS.dwCaps */ /* reserved1, was 3d capable */ #define DDSCAPS_RESERVED1 0x00000001 /* surface contains alpha information */ #define DDSCAPS_ALPHA 0x00000002 /* this surface is a backbuffer */ #define DDSCAPS_BACKBUFFER 0x00000004 /* complex surface structure */ #define DDSCAPS_COMPLEX 0x00000008 /* part of surface flipping structure */ #define DDSCAPS_FLIP 0x00000010 /* this surface is the frontbuffer surface */ #define DDSCAPS_FRONTBUFFER 0x00000020 /* this is a plain offscreen surface */ #define DDSCAPS_OFFSCREENPLAIN 0x00000040 /* overlay */ #define DDSCAPS_OVERLAY 0x00000080 /* palette objects can be created and attached to us */ #define DDSCAPS_PALETTE 0x00000100 /* primary surface (the one the user looks at currently)(right eye)*/ #define DDSCAPS_PRIMARYSURFACE 0x00000200 /* primary surface for left eye */ #define DDSCAPS_PRIMARYSURFACELEFT 0x00000400 /* surface exists in systemmemory */ #define DDSCAPS_SYSTEMMEMORY 0x00000800 /* surface can be used as a texture */ #define DDSCAPS_TEXTURE 0x00001000 /* surface may be destination for 3d rendering */ #define DDSCAPS_3DDEVICE 0x00002000 /* surface exists in videomemory */ #define DDSCAPS_VIDEOMEMORY 0x00004000 /* surface changes immediately visible */ #define DDSCAPS_VISIBLE 0x00008000 /* write only surface */ #define DDSCAPS_WRITEONLY 0x00010000 /* zbuffer surface */ #define DDSCAPS_ZBUFFER 0x00020000 /* has its own DC */ #define DDSCAPS_OWNDC 0x00040000 /* surface should be able to receive live video */ #define DDSCAPS_LIVEVIDEO 0x00080000 /* should be able to have a hw codec decompress stuff into it */ #define DDSCAPS_HWCODEC 0x00100000 /* mode X (320x200 or 320x240) surface */ #define DDSCAPS_MODEX 0x00200000 /* one mipmap surface (1 level) */ #define DDSCAPS_MIPMAP 0x00400000 #define DDSCAPS_RESERVED2 0x00800000 /* memory allocation delayed until Load() */ #define DDSCAPS_ALLOCONLOAD 0x04000000 /* Indicates that the surface will receive data from a video port */ #define DDSCAPS_VIDEOPORT 0x08000000 /* surface is in local videomemory */ #define DDSCAPS_LOCALVIDMEM 0x10000000 /* surface is in nonlocal videomemory */ #define DDSCAPS_NONLOCALVIDMEM 0x20000000 /* surface is a standard VGA mode surface (NOT ModeX) */ #define DDSCAPS_STANDARDVGAMODE 0x40000000 /* optimized? surface */ #define DDSCAPS_OPTIMIZED 0x80000000 typedef struct _DDSCAPS { DWORD dwCaps; /* capabilities of surface wanted */ } DDSCAPS,*LPDDSCAPS; /* DDSCAPS2.dwCaps2 */ /* indicates the surface will receive data from a video port using deinterlacing hardware. */ #define DDSCAPS2_HARDWAREDEINTERLACE 0x00000002 /* indicates the surface will be locked very frequently. */ #define DDSCAPS2_HINTDYNAMIC 0x00000004 /* indicates surface can be re-ordered or retiled on load() */ #define DDSCAPS2_HINTSTATIC 0x00000008 /* indicates surface to be managed by directdraw/direct3D */ #define DDSCAPS2_TEXTUREMANAGE 0x00000010 /* reserved bits */ #define DDSCAPS2_RESERVED1 0x00000020 #define DDSCAPS2_RESERVED2 0x00000040 /* indicates surface will never be locked again */ #define DDSCAPS2_OPAQUE 0x00000080 /* set at CreateSurface() time to indicate antialiasing will be used */ #define DDSCAPS2_HINTANTIALIASING 0x00000100 /* set at CreateSurface() time to indicate cubic environment map */ #define DDSCAPS2_CUBEMAP 0x00000200 /* face flags for cube maps */ #define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 #define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 #define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 #define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 #define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 #define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 /* specifies all faces of a cube for CreateSurface() */ #define DDSCAPS2_CUBEMAP_ALLFACES ( DDSCAPS2_CUBEMAP_POSITIVEX |\ DDSCAPS2_CUBEMAP_NEGATIVEX |\ DDSCAPS2_CUBEMAP_POSITIVEY |\ DDSCAPS2_CUBEMAP_NEGATIVEY |\ DDSCAPS2_CUBEMAP_POSITIVEZ |\ DDSCAPS2_CUBEMAP_NEGATIVEZ ) /* set for mipmap sublevels on DirectX7 and later. ignored by CreateSurface() */ #define DDSCAPS2_MIPMAPSUBLEVEL 0x00010000 /* indicates texture surface to be managed by Direct3D *only* */ #define DDSCAPS2_D3DTEXTUREMANAGE 0x00020000 /* indicates managed surface that can safely be lost */ #define DDSCAPS2_DONOTPERSIST 0x00040000 /* indicates surface is part of a stereo flipping chain */ #define DDSCAPS2_STEREOSURFACELEFT 0x00080000 typedef struct _DDSCAPS2 { DWORD dwCaps; /* capabilities of surface wanted */ DWORD dwCaps2; /* additional capabilities */ DWORD dwCaps3; /* reserved capabilities */ DWORD dwCaps4; /* more reserved capabilities */ } DDSCAPS2,*LPDDSCAPS2; #define DD_ROP_SPACE (256/32) /* space required to store ROP array */ typedef struct _DDCAPS_DX7 /* DirectX 7 version of caps struct */ { DWORD dwSize; /* size of the DDDRIVERCAPS structure */ DWORD dwCaps; /* driver specific capabilities */ DWORD dwCaps2; /* more driver specific capabilities */ DWORD dwCKeyCaps; /* color key capabilities of the surface */ DWORD dwFXCaps; /* driver specific stretching and effects capabilities */ DWORD dwFXAlphaCaps; /* alpha driver specific capabilities */ DWORD dwPalCaps; /* palette capabilities */ DWORD dwSVCaps; /* stereo vision capabilities */ DWORD dwAlphaBltConstBitDepths; /* DDBD_2,4,8 */ DWORD dwAlphaBltPixelBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaBltSurfaceBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaOverlayConstBitDepths; /* DDBD_2,4,8 */ DWORD dwAlphaOverlayPixelBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaOverlaySurfaceBitDepths; /* DDBD_1,2,4,8 */ DWORD dwZBufferBitDepths; /* DDBD_8,16,24,32 */ DWORD dwVidMemTotal; /* total amount of video memory */ DWORD dwVidMemFree; /* amount of free video memory */ DWORD dwMaxVisibleOverlays; /* maximum number of visible overlays */ DWORD dwCurrVisibleOverlays; /* current number of visible overlays */ DWORD dwNumFourCCCodes; /* number of four cc codes */ DWORD dwAlignBoundarySrc; /* source rectangle alignment */ DWORD dwAlignSizeSrc; /* source rectangle byte size */ DWORD dwAlignBoundaryDest; /* dest rectangle alignment */ DWORD dwAlignSizeDest; /* dest rectangle byte size */ DWORD dwAlignStrideAlign; /* stride alignment */ DWORD dwRops[DD_ROP_SPACE]; /* ROPS supported */ DDSCAPS ddsOldCaps; /* old DDSCAPS - superseded for DirectX6+ */ DWORD dwMinOverlayStretch; /* minimum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxOverlayStretch; /* maximum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMinLiveVideoStretch; /* minimum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxLiveVideoStretch; /* maximum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMinHwCodecStretch; /* minimum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxHwCodecStretch; /* maximum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwReserved1; DWORD dwReserved2; DWORD dwReserved3; DWORD dwSVBCaps; /* driver specific capabilities for System->Vmem blts */ DWORD dwSVBCKeyCaps; /* driver color key capabilities for System->Vmem blts */ DWORD dwSVBFXCaps; /* driver FX capabilities for System->Vmem blts */ DWORD dwSVBRops[DD_ROP_SPACE];/* ROPS supported for System->Vmem blts */ DWORD dwVSBCaps; /* driver specific capabilities for Vmem->System blts */ DWORD dwVSBCKeyCaps; /* driver color key capabilities for Vmem->System blts */ DWORD dwVSBFXCaps; /* driver FX capabilities for Vmem->System blts */ DWORD dwVSBRops[DD_ROP_SPACE];/* ROPS supported for Vmem->System blts */ DWORD dwSSBCaps; /* driver specific capabilities for System->System blts */ DWORD dwSSBCKeyCaps; /* driver color key capabilities for System->System blts */ DWORD dwSSBFXCaps; /* driver FX capabilities for System->System blts */ DWORD dwSSBRops[DD_ROP_SPACE];/* ROPS supported for System->System blts */ DWORD dwMaxVideoPorts; /* maximum number of usable video ports */ DWORD dwCurrVideoPorts; /* current number of video ports used */ DWORD dwSVBCaps2; /* more driver specific capabilities for System->Vmem blts */ DWORD dwNLVBCaps; /* driver specific capabilities for non-local->local vidmem blts */ DWORD dwNLVBCaps2; /* more driver specific capabilities non-local->local vidmem blts */ DWORD dwNLVBCKeyCaps; /* driver color key capabilities for non-local->local vidmem blts */ DWORD dwNLVBFXCaps; /* driver FX capabilities for non-local->local blts */ DWORD dwNLVBRops[DD_ROP_SPACE]; /* ROPS supported for non-local->local blts */ DDSCAPS2 ddsCaps; /* surface capabilities */ } DDCAPS_DX7,*LPDDCAPS_DX7; typedef struct _DDCAPS_DX6 /* DirectX 6 version of caps struct */ { DWORD dwSize; /* size of the DDDRIVERCAPS structure */ DWORD dwCaps; /* driver specific capabilities */ DWORD dwCaps2; /* more driver specific capabilities */ DWORD dwCKeyCaps; /* color key capabilities of the surface */ DWORD dwFXCaps; /* driver specific stretching and effects capabilities */ DWORD dwFXAlphaCaps; /* alpha driver specific capabilities */ DWORD dwPalCaps; /* palette capabilities */ DWORD dwSVCaps; /* stereo vision capabilities */ DWORD dwAlphaBltConstBitDepths; /* DDBD_2,4,8 */ DWORD dwAlphaBltPixelBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaBltSurfaceBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaOverlayConstBitDepths; /* DDBD_2,4,8 */ DWORD dwAlphaOverlayPixelBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaOverlaySurfaceBitDepths; /* DDBD_1,2,4,8 */ DWORD dwZBufferBitDepths; /* DDBD_8,16,24,32 */ DWORD dwVidMemTotal; /* total amount of video memory */ DWORD dwVidMemFree; /* amount of free video memory */ DWORD dwMaxVisibleOverlays; /* maximum number of visible overlays */ DWORD dwCurrVisibleOverlays; /* current number of visible overlays */ DWORD dwNumFourCCCodes; /* number of four cc codes */ DWORD dwAlignBoundarySrc; /* source rectangle alignment */ DWORD dwAlignSizeSrc; /* source rectangle byte size */ DWORD dwAlignBoundaryDest; /* dest rectangle alignment */ DWORD dwAlignSizeDest; /* dest rectangle byte size */ DWORD dwAlignStrideAlign; /* stride alignment */ DWORD dwRops[DD_ROP_SPACE]; /* ROPS supported */ DDSCAPS ddsOldCaps; /* old DDSCAPS - superseded for DirectX6+ */ DWORD dwMinOverlayStretch; /* minimum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxOverlayStretch; /* maximum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMinLiveVideoStretch; /* minimum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxLiveVideoStretch; /* maximum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMinHwCodecStretch; /* minimum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxHwCodecStretch; /* maximum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwReserved1; DWORD dwReserved2; DWORD dwReserved3; DWORD dwSVBCaps; /* driver specific capabilities for System->Vmem blts */ DWORD dwSVBCKeyCaps; /* driver color key capabilities for System->Vmem blts */ DWORD dwSVBFXCaps; /* driver FX capabilities for System->Vmem blts */ DWORD dwSVBRops[DD_ROP_SPACE];/* ROPS supported for System->Vmem blts */ DWORD dwVSBCaps; /* driver specific capabilities for Vmem->System blts */ DWORD dwVSBCKeyCaps; /* driver color key capabilities for Vmem->System blts */ DWORD dwVSBFXCaps; /* driver FX capabilities for Vmem->System blts */ DWORD dwVSBRops[DD_ROP_SPACE];/* ROPS supported for Vmem->System blts */ DWORD dwSSBCaps; /* driver specific capabilities for System->System blts */ DWORD dwSSBCKeyCaps; /* driver color key capabilities for System->System blts */ DWORD dwSSBFXCaps; /* driver FX capabilities for System->System blts */ DWORD dwSSBRops[DD_ROP_SPACE];/* ROPS supported for System->System blts */ DWORD dwMaxVideoPorts; /* maximum number of usable video ports */ DWORD dwCurrVideoPorts; /* current number of video ports used */ DWORD dwSVBCaps2; /* more driver specific capabilities for System->Vmem blts */ DWORD dwNLVBCaps; /* driver specific capabilities for non-local->local vidmem blts */ DWORD dwNLVBCaps2; /* more driver specific capabilities non-local->local vidmem blts */ DWORD dwNLVBCKeyCaps; /* driver color key capabilities for non-local->local vidmem blts */ DWORD dwNLVBFXCaps; /* driver FX capabilities for non-local->local blts */ DWORD dwNLVBRops[DD_ROP_SPACE]; /* ROPS supported for non-local->local blts */ /* and one new member for DirectX 6 */ DDSCAPS2 ddsCaps; /* surface capabilities */ } DDCAPS_DX6,*LPDDCAPS_DX6; typedef struct _DDCAPS_DX5 /* DirectX5 version of caps struct */ { DWORD dwSize; /* size of the DDDRIVERCAPS structure */ DWORD dwCaps; /* driver specific capabilities */ DWORD dwCaps2; /* more driver specific capabilities */ DWORD dwCKeyCaps; /* color key capabilities of the surface */ DWORD dwFXCaps; /* driver specific stretching and effects capabilities */ DWORD dwFXAlphaCaps; /* alpha driver specific capabilities */ DWORD dwPalCaps; /* palette capabilities */ DWORD dwSVCaps; /* stereo vision capabilities */ DWORD dwAlphaBltConstBitDepths; /* DDBD_2,4,8 */ DWORD dwAlphaBltPixelBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaBltSurfaceBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaOverlayConstBitDepths; /* DDBD_2,4,8 */ DWORD dwAlphaOverlayPixelBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaOverlaySurfaceBitDepths; /* DDBD_1,2,4,8 */ DWORD dwZBufferBitDepths; /* DDBD_8,16,24,32 */ DWORD dwVidMemTotal; /* total amount of video memory */ DWORD dwVidMemFree; /* amount of free video memory */ DWORD dwMaxVisibleOverlays; /* maximum number of visible overlays */ DWORD dwCurrVisibleOverlays; /* current number of visible overlays */ DWORD dwNumFourCCCodes; /* number of four cc codes */ DWORD dwAlignBoundarySrc; /* source rectangle alignment */ DWORD dwAlignSizeSrc; /* source rectangle byte size */ DWORD dwAlignBoundaryDest; /* dest rectangle alignment */ DWORD dwAlignSizeDest; /* dest rectangle byte size */ DWORD dwAlignStrideAlign; /* stride alignment */ DWORD dwRops[DD_ROP_SPACE]; /* ROPS supported */ DDSCAPS ddsCaps; /* DDSCAPS structure has all the general capabilities */ DWORD dwMinOverlayStretch; /* minimum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxOverlayStretch; /* maximum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMinLiveVideoStretch; /* minimum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxLiveVideoStretch; /* maximum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMinHwCodecStretch; /* minimum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxHwCodecStretch; /* maximum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwReserved1; DWORD dwReserved2; DWORD dwReserved3; DWORD dwSVBCaps; /* driver specific capabilities for System->Vmem blts */ DWORD dwSVBCKeyCaps; /* driver color key capabilities for System->Vmem blts */ DWORD dwSVBFXCaps; /* driver FX capabilities for System->Vmem blts */ DWORD dwSVBRops[DD_ROP_SPACE];/* ROPS supported for System->Vmem blts */ DWORD dwVSBCaps; /* driver specific capabilities for Vmem->System blts */ DWORD dwVSBCKeyCaps; /* driver color key capabilities for Vmem->System blts */ DWORD dwVSBFXCaps; /* driver FX capabilities for Vmem->System blts */ DWORD dwVSBRops[DD_ROP_SPACE];/* ROPS supported for Vmem->System blts */ DWORD dwSSBCaps; /* driver specific capabilities for System->System blts */ DWORD dwSSBCKeyCaps; /* driver color key capabilities for System->System blts */ DWORD dwSSBFXCaps; /* driver FX capabilities for System->System blts */ DWORD dwSSBRops[DD_ROP_SPACE];/* ROPS supported for System->System blts */ /* the following are the new DirectX 5 members */ DWORD dwMaxVideoPorts; /* maximum number of usable video ports */ DWORD dwCurrVideoPorts; /* current number of video ports used */ DWORD dwSVBCaps2; /* more driver specific capabilities for System->Vmem blts */ DWORD dwNLVBCaps; /* driver specific capabilities for non-local->local vidmem blts */ DWORD dwNLVBCaps2; /* more driver specific capabilities non-local->local vidmem blts */ DWORD dwNLVBCKeyCaps; /* driver color key capabilities for non-local->local vidmem blts */ DWORD dwNLVBFXCaps; /* driver FX capabilities for non-local->local blts */ DWORD dwNLVBRops[DD_ROP_SPACE]; /* ROPS supported for non-local->local blts */ } DDCAPS_DX5,*LPDDCAPS_DX5; typedef struct _DDCAPS_DX3 /* DirectX3 version of caps struct */ { DWORD dwSize; /* size of the DDDRIVERCAPS structure */ DWORD dwCaps; /* driver specific capabilities */ DWORD dwCaps2; /* more driver specific capabilities */ DWORD dwCKeyCaps; /* color key capabilities of the surface */ DWORD dwFXCaps; /* driver specific stretching and effects capabilities */ DWORD dwFXAlphaCaps; /* alpha driver specific capabilities */ DWORD dwPalCaps; /* palette capabilities */ DWORD dwSVCaps; /* stereo vision capabilities */ DWORD dwAlphaBltConstBitDepths; /* DDBD_2,4,8 */ DWORD dwAlphaBltPixelBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaBltSurfaceBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaOverlayConstBitDepths; /* DDBD_2,4,8 */ DWORD dwAlphaOverlayPixelBitDepths; /* DDBD_1,2,4,8 */ DWORD dwAlphaOverlaySurfaceBitDepths; /* DDBD_1,2,4,8 */ DWORD dwZBufferBitDepths; /* DDBD_8,16,24,32 */ DWORD dwVidMemTotal; /* total amount of video memory */ DWORD dwVidMemFree; /* amount of free video memory */ DWORD dwMaxVisibleOverlays; /* maximum number of visible overlays */ DWORD dwCurrVisibleOverlays; /* current number of visible overlays */ DWORD dwNumFourCCCodes; /* number of four cc codes */ DWORD dwAlignBoundarySrc; /* source rectangle alignment */ DWORD dwAlignSizeSrc; /* source rectangle byte size */ DWORD dwAlignBoundaryDest; /* dest rectangle alignment */ DWORD dwAlignSizeDest; /* dest rectangle byte size */ DWORD dwAlignStrideAlign; /* stride alignment */ DWORD dwRops[DD_ROP_SPACE]; /* ROPS supported */ DDSCAPS ddsCaps; /* DDSCAPS structure has all the general capabilities */ DWORD dwMinOverlayStretch; /* minimum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxOverlayStretch; /* maximum overlay stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMinLiveVideoStretch; /* minimum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxLiveVideoStretch; /* maximum live video stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMinHwCodecStretch; /* minimum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwMaxHwCodecStretch; /* maximum hardware codec stretch factor multiplied by 1000, eg 1000 == 1.0, 1300 == 1.3 */ DWORD dwReserved1; DWORD dwReserved2; DWORD dwReserved3; DWORD dwSVBCaps; /* driver specific capabilities for System->Vmem blts */ DWORD dwSVBCKeyCaps; /* driver color key capabilities for System->Vmem blts */ DWORD dwSVBFXCaps; /* driver FX capabilities for System->Vmem blts */ DWORD dwSVBRops[DD_ROP_SPACE];/* ROPS supported for System->Vmem blts */ DWORD dwVSBCaps; /* driver specific capabilities for Vmem->System blts */ DWORD dwVSBCKeyCaps; /* driver color key capabilities for Vmem->System blts */ DWORD dwVSBFXCaps; /* driver FX capabilities for Vmem->System blts */ DWORD dwVSBRops[DD_ROP_SPACE];/* ROPS supported for Vmem->System blts */ DWORD dwSSBCaps; /* driver specific capabilities for System->System blts */ DWORD dwSSBCKeyCaps; /* driver color key capabilities for System->System blts */ DWORD dwSSBFXCaps; /* driver FX capabilities for System->System blts */ DWORD dwSSBRops[DD_ROP_SPACE];/* ROPS supported for System->System blts */ DWORD dwReserved4; DWORD dwReserved5; DWORD dwReserved6; } DDCAPS_DX3,*LPDDCAPS_DX3; /* set caps struct according to DIRECTDRAW_VERSION */ #if DIRECTDRAW_VERSION <= 0x300 typedef DDCAPS_DX3 DDCAPS; #elif DIRECTDRAW_VERSION <= 0x500 typedef DDCAPS_DX5 DDCAPS; #elif DIRECTDRAW_VERSION <= 0x600 typedef DDCAPS_DX6 DDCAPS; #else typedef DDCAPS_DX7 DDCAPS; #endif typedef DDCAPS *LPDDCAPS; /* DDCAPS.dwCaps */ #define DDCAPS_3D 0x00000001 #define DDCAPS_ALIGNBOUNDARYDEST 0x00000002 #define DDCAPS_ALIGNSIZEDEST 0x00000004 #define DDCAPS_ALIGNBOUNDARYSRC 0x00000008 #define DDCAPS_ALIGNSIZESRC 0x00000010 #define DDCAPS_ALIGNSTRIDE 0x00000020 #define DDCAPS_BLT 0x00000040 #define DDCAPS_BLTQUEUE 0x00000080 #define DDCAPS_BLTFOURCC 0x00000100 #define DDCAPS_BLTSTRETCH 0x00000200 #define DDCAPS_GDI 0x00000400 #define DDCAPS_OVERLAY 0x00000800 #define DDCAPS_OVERLAYCANTCLIP 0x00001000 #define DDCAPS_OVERLAYFOURCC 0x00002000 #define DDCAPS_OVERLAYSTRETCH 0x00004000 #define DDCAPS_PALETTE 0x00008000 #define DDCAPS_PALETTEVSYNC 0x00010000 #define DDCAPS_READSCANLINE 0x00020000 #define DDCAPS_STEREOVIEW 0x00040000 #define DDCAPS_VBI 0x00080000 #define DDCAPS_ZBLTS 0x00100000 #define DDCAPS_ZOVERLAYS 0x00200000 #define DDCAPS_COLORKEY 0x00400000 #define DDCAPS_ALPHA 0x00800000 #define DDCAPS_COLORKEYHWASSIST 0x01000000 #define DDCAPS_NOHARDWARE 0x02000000 #define DDCAPS_BLTCOLORFILL 0x04000000 #define DDCAPS_BANKSWITCHED 0x08000000 #define DDCAPS_BLTDEPTHFILL 0x10000000 #define DDCAPS_CANCLIP 0x20000000 #define DDCAPS_CANCLIPSTRETCHED 0x40000000 #define DDCAPS_CANBLTSYSMEM 0x80000000 /* DDCAPS.dwCaps2 */ #define DDCAPS2_CERTIFIED 0x00000001 #define DDCAPS2_NO2DDURING3DSCENE 0x00000002 #define DDCAPS2_VIDEOPORT 0x00000004 #define DDCAPS2_AUTOFLIPOVERLAY 0x00000008 #define DDCAPS2_CANBOBINTERLEAVED 0x00000010 #define DDCAPS2_CANBOBNONINTERLEAVED 0x00000020 #define DDCAPS2_COLORCONTROLOVERLAY 0x00000040 #define DDCAPS2_COLORCONTROLPRIMARY 0x00000080 #define DDCAPS2_CANDROPZ16BIT 0x00000100 #define DDCAPS2_NONLOCALVIDMEM 0x00000200 #define DDCAPS2_NONLOCALVIDMEMCAPS 0x00000400 #define DDCAPS2_NOPAGELOCKREQUIRED 0x00000800 #define DDCAPS2_WIDESURFACES 0x00001000 #define DDCAPS2_CANFLIPODDEVEN 0x00002000 #define DDCAPS2_CANBOBHARDWARE 0x00004000 #define DDCAPS2_COPYFOURCC 0x00008000 #define DDCAPS2_PRIMARYGAMMA 0x00020000 #define DDCAPS2_CANRENDERWINDOWED 0x00080000 #define DDCAPS2_CANCALIBRATEGAMMA 0x00100000 #define DDCAPS2_FLIPINTERVAL 0x00200000 #define DDCAPS2_FLIPNOVSYNC 0x00400000 #define DDCAPS2_CANMANAGETEXTURE 0x00800000 #define DDCAPS2_TEXMANINNONLOCALVIDMEM 0x01000000 #define DDCAPS2_STEREO 0x02000000 #define DDCAPS2_SYSTONONLOCAL_AS_SYSTOLOCAL 0x04000000 /* Set/Get Colour Key Flags */ #define DDCKEY_COLORSPACE 0x00000001 /* Struct is single colour space */ #define DDCKEY_DESTBLT 0x00000002 /* To be used as dest for blt */ #define DDCKEY_DESTOVERLAY 0x00000004 /* To be used as dest for CK overlays */ #define DDCKEY_SRCBLT 0x00000008 /* To be used as src for blt */ #define DDCKEY_SRCOVERLAY 0x00000010 /* To be used as src for CK overlays */ typedef struct _DDCOLORKEY { DWORD dwColorSpaceLowValue;/* low boundary of color space that is to * be treated as Color Key, inclusive */ DWORD dwColorSpaceHighValue;/* high boundary of color space that is * to be treated as Color Key, inclusive */ } DDCOLORKEY,*LPDDCOLORKEY; /* ddCKEYCAPS bits */ #define DDCKEYCAPS_DESTBLT 0x00000001 #define DDCKEYCAPS_DESTBLTCLRSPACE 0x00000002 #define DDCKEYCAPS_DESTBLTCLRSPACEYUV 0x00000004 #define DDCKEYCAPS_DESTBLTYUV 0x00000008 #define DDCKEYCAPS_DESTOVERLAY 0x00000010 #define DDCKEYCAPS_DESTOVERLAYCLRSPACE 0x00000020 #define DDCKEYCAPS_DESTOVERLAYCLRSPACEYUV 0x00000040 #define DDCKEYCAPS_DESTOVERLAYONEACTIVE 0x00000080 #define DDCKEYCAPS_DESTOVERLAYYUV 0x00000100 #define DDCKEYCAPS_SRCBLT 0x00000200 #define DDCKEYCAPS_SRCBLTCLRSPACE 0x00000400 #define DDCKEYCAPS_SRCBLTCLRSPACEYUV 0x00000800 #define DDCKEYCAPS_SRCBLTYUV 0x00001000 #define DDCKEYCAPS_SRCOVERLAY 0x00002000 #define DDCKEYCAPS_SRCOVERLAYCLRSPACE 0x00004000 #define DDCKEYCAPS_SRCOVERLAYCLRSPACEYUV 0x00008000 #define DDCKEYCAPS_SRCOVERLAYONEACTIVE 0x00010000 #define DDCKEYCAPS_SRCOVERLAYYUV 0x00020000 #define DDCKEYCAPS_NOCOSTOVERLAY 0x00040000 typedef struct _DDPIXELFORMAT { DWORD dwSize; /* 0: size of structure */ DWORD dwFlags; /* 4: pixel format flags */ DWORD dwFourCC; /* 8: (FOURCC code) */ union { DWORD dwRGBBitCount; /* C: how many bits per pixel */ DWORD dwYUVBitCount; /* C: how many bits per pixel */ DWORD dwZBufferBitDepth; /* C: how many bits for z buffers */ DWORD dwAlphaBitDepth; /* C: how many bits for alpha channels*/ DWORD dwLuminanceBitCount; DWORD dwBumpBitCount; } DUMMYUNIONNAME1; union { DWORD dwRBitMask; /* 10: mask for red bit*/ DWORD dwYBitMask; /* 10: mask for Y bits*/ DWORD dwStencilBitDepth; DWORD dwLuminanceBitMask; DWORD dwBumpDuBitMask; } DUMMYUNIONNAME2; union { DWORD dwGBitMask; /* 14: mask for green bits*/ DWORD dwUBitMask; /* 14: mask for U bits*/ DWORD dwZBitMask; DWORD dwBumpDvBitMask; } DUMMYUNIONNAME3; union { DWORD dwBBitMask; /* 18: mask for blue bits*/ DWORD dwVBitMask; /* 18: mask for V bits*/ DWORD dwStencilBitMask; DWORD dwBumpLuminanceBitMask; } DUMMYUNIONNAME4; union { DWORD dwRGBAlphaBitMask; /* 1C: mask for alpha channel */ DWORD dwYUVAlphaBitMask; /* 1C: mask for alpha channel */ DWORD dwLuminanceAlphaBitMask; DWORD dwRGBZBitMask; /* 1C: mask for Z channel */ DWORD dwYUVZBitMask; /* 1C: mask for Z channel */ } DUMMYUNIONNAME5; /* 20: next structure */ } DDPIXELFORMAT,*LPDDPIXELFORMAT; #define MAKEFOURCC(ch0, ch1, ch2, ch3) \ ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \ ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 )) /* DDCAPS.dwFXCaps */ #define DDFXCAPS_BLTALPHA 0x00000001 #define DDFXCAPS_OVERLAYALPHA 0x00000004 #define DDFXCAPS_BLTARITHSTRETCHYN 0x00000010 #define DDFXCAPS_BLTARITHSTRETCHY 0x00000020 #define DDFXCAPS_BLTMIRRORLEFTRIGHT 0x00000040 #define DDFXCAPS_BLTMIRRORUPDOWN 0x00000080 #define DDFXCAPS_BLTROTATION 0x00000100 #define DDFXCAPS_BLTROTATION90 0x00000200 #define DDFXCAPS_BLTSHRINKX 0x00000400 #define DDFXCAPS_BLTSHRINKXN 0x00000800 #define DDFXCAPS_BLTSHRINKY 0x00001000 #define DDFXCAPS_BLTSHRINKYN 0x00002000 #define DDFXCAPS_BLTSTRETCHX 0x00004000 #define DDFXCAPS_BLTSTRETCHXN 0x00008000 #define DDFXCAPS_BLTSTRETCHY 0x00010000 #define DDFXCAPS_BLTSTRETCHYN 0x00020000 #define DDFXCAPS_OVERLAYARITHSTRETCHY 0x00040000 #define DDFXCAPS_OVERLAYARITHSTRETCHYN 0x00000008 #define DDFXCAPS_OVERLAYSHRINKX 0x00080000 #define DDFXCAPS_OVERLAYSHRINKXN 0x00100000 #define DDFXCAPS_OVERLAYSHRINKY 0x00200000 #define DDFXCAPS_OVERLAYSHRINKYN 0x00400000 #define DDFXCAPS_OVERLAYSTRETCHX 0x00800000 #define DDFXCAPS_OVERLAYSTRETCHXN 0x01000000 #define DDFXCAPS_OVERLAYSTRETCHY 0x02000000 #define DDFXCAPS_OVERLAYSTRETCHYN 0x04000000 #define DDFXCAPS_OVERLAYMIRRORLEFTRIGHT 0x08000000 #define DDFXCAPS_OVERLAYMIRRORUPDOWN 0x10000000 #define DDFXCAPS_OVERLAYFILTER DDFXCAPS_OVERLAYARITHSTRETCHY /* DDCAPS.dwFXAlphaCaps */ #define DDFXALPHACAPS_BLTALPHAEDGEBLEND 0x00000001 #define DDFXALPHACAPS_BLTALPHAPIXELS 0x00000002 #define DDFXALPHACAPS_BLTALPHAPIXELSNEG 0x00000004 #define DDFXALPHACAPS_BLTALPHASURFACES 0x00000008 #define DDFXALPHACAPS_BLTALPHASURFACESNEG 0x00000010 #define DDFXALPHACAPS_OVERLAYALPHAEDGEBLEND 0x00000020 #define DDFXALPHACAPS_OVERLAYALPHAPIXELS 0x00000040 #define DDFXALPHACAPS_OVERLAYALPHAPIXELSNEG 0x00000080 #define DDFXALPHACAPS_OVERLAYALPHASURFACES 0x00000100 #define DDFXALPHACAPS_OVERLAYALPHASURFACESNEG 0x00000200 /* DDCAPS.dwPalCaps */ #define DDPCAPS_4BIT 0x00000001 #define DDPCAPS_8BITENTRIES 0x00000002 #define DDPCAPS_8BIT 0x00000004 #define DDPCAPS_INITIALIZE 0x00000008 #define DDPCAPS_PRIMARYSURFACE 0x00000010 #define DDPCAPS_PRIMARYSURFACELEFT 0x00000020 #define DDPCAPS_ALLOW256 0x00000040 #define DDPCAPS_VSYNC 0x00000080 #define DDPCAPS_1BIT 0x00000100 #define DDPCAPS_2BIT 0x00000200 #define DDPCAPS_ALPHA 0x00000400 /* DDCAPS.dwSVCaps */ /* the first 4 of these are now obsolete */ #if DIRECTDRAW_VERSION >= 0x700 /* FIXME: I'm not sure when this switch occurred */ #define DDSVCAPS_RESERVED1 0x00000001 #define DDSVCAPS_RESERVED2 0x00000002 #define DDSVCAPS_RESERVED3 0x00000004 #define DDSVCAPS_RESERVED4 0x00000008 #else #define DDSVCAPS_ENIGMA 0x00000001 #define DDSVCAPS_FLICKER 0x00000002 #define DDSVCAPS_REDBLUE 0x00000004 #define DDSVCAPS_SPLIT 0x00000008 #endif #define DDSVCAPS_STEREOSEQUENTIAL 0x00000010 /* BitDepths */ #define DDBD_1 0x00004000 #define DDBD_2 0x00002000 #define DDBD_4 0x00001000 #define DDBD_8 0x00000800 #define DDBD_16 0x00000400 #define DDBD_24 0x00000200 #define DDBD_32 0x00000100 /* DDOVERLAYFX.dwDDFX */ #define DDOVERFX_ARITHSTRETCHY 0x00000001 #define DDOVERFX_MIRRORLEFTRIGHT 0x00000002 #define DDOVERFX_MIRRORUPDOWN 0x00000004 /* UpdateOverlay flags */ #define DDOVER_ALPHADEST 0x00000001 #define DDOVER_ALPHADESTCONSTOVERRIDE 0x00000002 #define DDOVER_ALPHADESTNEG 0x00000004 #define DDOVER_ALPHADESTSURFACEOVERRIDE 0x00000008 #define DDOVER_ALPHAEDGEBLEND 0x00000010 #define DDOVER_ALPHASRC 0x00000020 #define DDOVER_ALPHASRCCONSTOVERRIDE 0x00000040 #define DDOVER_ALPHASRCNEG 0x00000080 #define DDOVER_ALPHASRCSURFACEOVERRIDE 0x00000100 #define DDOVER_HIDE 0x00000200 #define DDOVER_KEYDEST 0x00000400 #define DDOVER_KEYDESTOVERRIDE 0x00000800 #define DDOVER_KEYSRC 0x00001000 #define DDOVER_KEYSRCOVERRIDE 0x00002000 #define DDOVER_SHOW 0x00004000 #define DDOVER_ADDDIRTYRECT 0x00008000 #define DDOVER_REFRESHDIRTYRECTS 0x00010000 #define DDOVER_REFRESHALL 0x00020000 #define DDOVER_DDFX 0x00080000 #define DDOVER_AUTOFLIP 0x00100000 #define DDOVER_BOB 0x00200000 #define DDOVER_OVERRIDEBOBWEAVE 0x00400000 #define DDOVER_INTERLEAVED 0x00800000 /* DDCOLORKEY.dwFlags */ #define DDPF_ALPHAPIXELS 0x00000001 #define DDPF_ALPHA 0x00000002 #define DDPF_FOURCC 0x00000004 #define DDPF_PALETTEINDEXED4 0x00000008 #define DDPF_PALETTEINDEXEDTO8 0x00000010 #define DDPF_PALETTEINDEXED8 0x00000020 #define DDPF_RGB 0x00000040 #define DDPF_COMPRESSED 0x00000080 #define DDPF_RGBTOYUV 0x00000100 #define DDPF_YUV 0x00000200 #define DDPF_ZBUFFER 0x00000400 #define DDPF_PALETTEINDEXED1 0x00000800 #define DDPF_PALETTEINDEXED2 0x00001000 #define DDPF_ZPIXELS 0x00002000 #define DDPF_STENCILBUFFER 0x00004000 #define DDPF_ALPHAPREMULT 0x00008000 #define DDPF_LUMINANCE 0x00020000 #define DDPF_BUMPLUMINANCE 0x00040000 #define DDPF_BUMPDUDV 0x00080000 /* SetCooperativeLevel dwFlags */ #define DDSCL_FULLSCREEN 0x00000001 #define DDSCL_ALLOWREBOOT 0x00000002 #define DDSCL_NOWINDOWCHANGES 0x00000004 #define DDSCL_NORMAL 0x00000008 #define DDSCL_EXCLUSIVE 0x00000010 #define DDSCL_ALLOWMODEX 0x00000040 #define DDSCL_SETFOCUSWINDOW 0x00000080 #define DDSCL_SETDEVICEWINDOW 0x00000100 #define DDSCL_CREATEDEVICEWINDOW 0x00000200 #define DDSCL_MULTITHREADED 0x00000400 #define DDSCL_FPUSETUP 0x00000800 #define DDSCL_FPUPRESERVE 0x00001000 /* DDSURFACEDESC.dwFlags */ #define DDSD_CAPS 0x00000001 #define DDSD_HEIGHT 0x00000002 #define DDSD_WIDTH 0x00000004 #define DDSD_PITCH 0x00000008 #define DDSD_BACKBUFFERCOUNT 0x00000020 #define DDSD_ZBUFFERBITDEPTH 0x00000040 #define DDSD_ALPHABITDEPTH 0x00000080 #define DDSD_LPSURFACE 0x00000800 #define DDSD_PIXELFORMAT 0x00001000 #define DDSD_CKDESTOVERLAY 0x00002000 #define DDSD_CKDESTBLT 0x00004000 #define DDSD_CKSRCOVERLAY 0x00008000 #define DDSD_CKSRCBLT 0x00010000 #define DDSD_MIPMAPCOUNT 0x00020000 #define DDSD_REFRESHRATE 0x00040000 #define DDSD_LINEARSIZE 0x00080000 #define DDSD_TEXTURESTAGE 0x00100000 #define DDSD_FVF 0x00200000 #define DDSD_SRCVBHANDLE 0x00400000 #define DDSD_ALL 0x007ff9ee /* EnumSurfaces flags */ #define DDENUMSURFACES_ALL 0x00000001 #define DDENUMSURFACES_MATCH 0x00000002 #define DDENUMSURFACES_NOMATCH 0x00000004 #define DDENUMSURFACES_CANBECREATED 0x00000008 #define DDENUMSURFACES_DOESEXIST 0x00000010 /* SetDisplayMode flags */ #define DDSDM_STANDARDVGAMODE 0x00000001 /* EnumDisplayModes flags */ #define DDEDM_REFRESHRATES 0x00000001 #define DDEDM_STANDARDVGAMODES 0x00000002 /* WaitForVerticalDisplay flags */ #define DDWAITVB_BLOCKBEGIN 0x00000001 #define DDWAITVB_BLOCKBEGINEVENT 0x00000002 #define DDWAITVB_BLOCKEND 0x00000004 typedef struct _DDSURFACEDESC { DWORD dwSize; /* 0: size of the DDSURFACEDESC structure*/ DWORD dwFlags; /* 4: determines what fields are valid*/ DWORD dwHeight; /* 8: height of surface to be created*/ DWORD dwWidth; /* C: width of input surface*/ union { LONG lPitch; /* 10: distance to start of next line (return value only)*/ DWORD dwLinearSize; } DUMMYUNIONNAME1; DWORD dwBackBufferCount;/* 14: number of back buffers requested*/ union { DWORD dwMipMapCount;/* 18:number of mip-map levels requested*/ DWORD dwZBufferBitDepth;/*18: depth of Z buffer requested*/ DWORD dwRefreshRate;/* 18:refresh rate (used when display mode is described)*/ } DUMMYUNIONNAME2; DWORD dwAlphaBitDepth;/* 1C:depth of alpha buffer requested*/ DWORD dwReserved; /* 20:reserved*/ LPVOID lpSurface; /* 24:pointer to the associated surface memory*/ DDCOLORKEY ddckCKDestOverlay;/* 28: CK for dest overlay use*/ DDCOLORKEY ddckCKDestBlt; /* 30: CK for destination blt use*/ DDCOLORKEY ddckCKSrcOverlay;/* 38: CK for source overlay use*/ DDCOLORKEY ddckCKSrcBlt; /* 40: CK for source blt use*/ DDPIXELFORMAT ddpfPixelFormat;/* 48: pixel format description of the surface*/ DDSCAPS ddsCaps; /* 68: direct draw surface caps */ } DDSURFACEDESC,*LPDDSURFACEDESC; typedef struct _DDSURFACEDESC2 { DWORD dwSize; /* 0: size of the DDSURFACEDESC2 structure*/ DWORD dwFlags; /* 4: determines what fields are valid*/ DWORD dwHeight; /* 8: height of surface to be created*/ DWORD dwWidth; /* C: width of input surface*/ union { LONG lPitch; /*10: distance to start of next line (return value only)*/ DWORD dwLinearSize; /*10: formless late-allocated optimized surface size */ } DUMMYUNIONNAME1; DWORD dwBackBufferCount;/* 14: number of back buffers requested*/ union { DWORD dwMipMapCount;/* 18:number of mip-map levels requested*/ DWORD dwRefreshRate;/* 18:refresh rate (used when display mode is described)*/ DWORD dwSrcVBHandle;/* 18:source used in VB::Optimize */ } DUMMYUNIONNAME2; DWORD dwAlphaBitDepth;/* 1C:depth of alpha buffer requested*/ DWORD dwReserved; /* 20:reserved*/ LPVOID lpSurface; /* 24:pointer to the associated surface memory*/ union { DDCOLORKEY ddckCKDestOverlay; /* 28: CK for dest overlay use*/ DWORD dwEmptyFaceColor; /* 28: color for empty cubemap faces */ } DUMMYUNIONNAME3; DDCOLORKEY ddckCKDestBlt; /* 30: CK for destination blt use*/ DDCOLORKEY ddckCKSrcOverlay;/* 38: CK for source overlay use*/ DDCOLORKEY ddckCKSrcBlt; /* 40: CK for source blt use*/ union { DDPIXELFORMAT ddpfPixelFormat;/* 48: pixel format description of the surface*/ DWORD dwFVF; /* 48: vertex format description of vertex buffers */ } DUMMYUNIONNAME4; DDSCAPS2 ddsCaps; /* 68: DDraw surface caps */ DWORD dwTextureStage; /* 78: stage in multitexture cascade */ } DDSURFACEDESC2,*LPDDSURFACEDESC2; /* DDCOLORCONTROL.dwFlags */ #define DDCOLOR_BRIGHTNESS 0x00000001 #define DDCOLOR_CONTRAST 0x00000002 #define DDCOLOR_HUE 0x00000004 #define DDCOLOR_SATURATION 0x00000008 #define DDCOLOR_SHARPNESS 0x00000010 #define DDCOLOR_GAMMA 0x00000020 #define DDCOLOR_COLORENABLE 0x00000040 typedef struct { DWORD dwSize; DWORD dwFlags; LONG lBrightness; LONG lContrast; LONG lHue; LONG lSaturation; LONG lSharpness; LONG lGamma; LONG lColorEnable; DWORD dwReserved1; } DDCOLORCONTROL,*LPDDCOLORCONTROL; typedef struct { WORD red[256]; WORD green[256]; WORD blue[256]; } DDGAMMARAMP,*LPDDGAMMARAMP; typedef BOOL (CALLBACK *LPDDENUMCALLBACKA)(GUID *, LPSTR, LPSTR, LPVOID); typedef BOOL (CALLBACK *LPDDENUMCALLBACKW)(GUID *, LPWSTR, LPWSTR, LPVOID); typedef HRESULT (CALLBACK *LPDDENUMMODESCALLBACK)(LPDDSURFACEDESC, LPVOID); typedef HRESULT (CALLBACK *LPDDENUMMODESCALLBACK2)(LPDDSURFACEDESC2, LPVOID); typedef HRESULT (CALLBACK *LPDDENUMSURFACESCALLBACK)(LPDIRECTDRAWSURFACE, LPDDSURFACEDESC, LPVOID); typedef HRESULT (CALLBACK *LPDDENUMSURFACESCALLBACK2)(LPDIRECTDRAWSURFACE4, LPDDSURFACEDESC2, LPVOID); typedef HRESULT (CALLBACK *LPDDENUMSURFACESCALLBACK7)(LPDIRECTDRAWSURFACE7, LPDDSURFACEDESC2, LPVOID); typedef BOOL (CALLBACK *LPDDENUMCALLBACKEXA)(GUID *, LPSTR, LPSTR, LPVOID, HMONITOR); typedef BOOL (CALLBACK *LPDDENUMCALLBACKEXW)(GUID *, LPWSTR, LPWSTR, LPVOID, HMONITOR); HRESULT WINAPI DirectDrawEnumerateA(LPDDENUMCALLBACKA,LPVOID); HRESULT WINAPI DirectDrawEnumerateW(LPDDENUMCALLBACKW,LPVOID); #define DirectDrawEnumerate WINELIB_NAME_AW(DirectDrawEnumerate) HRESULT WINAPI DirectDrawEnumerateExA( LPDDENUMCALLBACKEXA lpCallback, LPVOID lpContext, DWORD dwFlags); HRESULT WINAPI DirectDrawEnumerateExW( LPDDENUMCALLBACKEXW lpCallback, LPVOID lpContext, DWORD dwFlags); #define DirectDrawEnumerateEx WINELIB_NAME_AW(DirectDrawEnumerateEx) typedef HRESULT (WINAPI * LPDIRECTDRAWENUMERATEEXA)( LPDDENUMCALLBACKEXA lpCallback, LPVOID lpContext, DWORD dwFlags); typedef HRESULT (WINAPI * LPDIRECTDRAWENUMERATEEXW)( LPDDENUMCALLBACKEXW lpCallback, LPVOID lpContext, DWORD dwFlags); /* flags for DirectDrawEnumerateEx */ #define DDENUM_ATTACHEDSECONDARYDEVICES 0x00000001 #define DDENUM_DETACHEDSECONDARYDEVICES 0x00000002 #define DDENUM_NONDISPLAYDEVICES 0x00000004 /* flags for DirectDrawCreate or IDirectDraw::Initialize */ #define DDCREATE_HARDWAREONLY 1L #define DDCREATE_EMULATIONONLY 2L typedef struct _DDBLTFX { DWORD dwSize; /* size of structure */ DWORD dwDDFX; /* FX operations */ DWORD dwROP; /* Win32 raster operations */ DWORD dwDDROP; /* Raster operations new for DirectDraw */ DWORD dwRotationAngle; /* Rotation angle for blt */ DWORD dwZBufferOpCode; /* ZBuffer compares */ DWORD dwZBufferLow; /* Low limit of Z buffer */ DWORD dwZBufferHigh; /* High limit of Z buffer */ DWORD dwZBufferBaseDest; /* Destination base value */ DWORD dwZDestConstBitDepth; /* Bit depth used to specify Z constant for destination */ union { DWORD dwZDestConst; /* Constant to use as Z buffer for dest */ LPDIRECTDRAWSURFACE lpDDSZBufferDest; /* Surface to use as Z buffer for dest */ } DUMMYUNIONNAME1; DWORD dwZSrcConstBitDepth; /* Bit depth used to specify Z constant for source */ union { DWORD dwZSrcConst; /* Constant to use as Z buffer for src */ LPDIRECTDRAWSURFACE lpDDSZBufferSrc; /* Surface to use as Z buffer for src */ } DUMMYUNIONNAME2; DWORD dwAlphaEdgeBlendBitDepth; /* Bit depth used to specify constant for alpha edge blend */ DWORD dwAlphaEdgeBlend; /* Alpha for edge blending */ DWORD dwReserved; DWORD dwAlphaDestConstBitDepth; /* Bit depth used to specify alpha constant for destination */ union { DWORD dwAlphaDestConst; /* Constant to use as Alpha Channel */ LPDIRECTDRAWSURFACE lpDDSAlphaDest; /* Surface to use as Alpha Channel */ } DUMMYUNIONNAME3; DWORD dwAlphaSrcConstBitDepth; /* Bit depth used to specify alpha constant for source */ union { DWORD dwAlphaSrcConst; /* Constant to use as Alpha Channel */ LPDIRECTDRAWSURFACE lpDDSAlphaSrc; /* Surface to use as Alpha Channel */ } DUMMYUNIONNAME4; union { DWORD dwFillColor; /* color in RGB or Palettized */ DWORD dwFillDepth; /* depth value for z-buffer */ DWORD dwFillPixel; /* pixel val for RGBA or RGBZ */ LPDIRECTDRAWSURFACE lpDDSPattern; /* Surface to use as pattern */ } DUMMYUNIONNAME5; DDCOLORKEY ddckDestColorkey; /* DestColorkey override */ DDCOLORKEY ddckSrcColorkey; /* SrcColorkey override */ } DDBLTFX,*LPDDBLTFX; /* dwDDFX */ /* arithmetic stretching along y axis */ #define DDBLTFX_ARITHSTRETCHY 0x00000001 /* mirror on y axis */ #define DDBLTFX_MIRRORLEFTRIGHT 0x00000002 /* mirror on x axis */ #define DDBLTFX_MIRRORUPDOWN 0x00000004 /* do not tear */ #define DDBLTFX_NOTEARING 0x00000008 /* 180 degrees clockwise rotation */ #define DDBLTFX_ROTATE180 0x00000010 /* 270 degrees clockwise rotation */ #define DDBLTFX_ROTATE270 0x00000020 /* 90 degrees clockwise rotation */ #define DDBLTFX_ROTATE90 0x00000040 /* dwZBufferLow and dwZBufferHigh specify limits to the copied Z values */ #define DDBLTFX_ZBUFFERRANGE 0x00000080 /* add dwZBufferBaseDest to every source z value before compare */ #define DDBLTFX_ZBUFFERBASEDEST 0x00000100 typedef struct _DDOVERLAYFX { DWORD dwSize; /* size of structure */ DWORD dwAlphaEdgeBlendBitDepth; /* Bit depth used to specify constant for alpha edge blend */ DWORD dwAlphaEdgeBlend; /* Constant to use as alpha for edge blend */ DWORD dwReserved; DWORD dwAlphaDestConstBitDepth; /* Bit depth used to specify alpha constant for destination */ union { DWORD dwAlphaDestConst; /* Constant to use as alpha channel for dest */ LPDIRECTDRAWSURFACE lpDDSAlphaDest; /* Surface to use as alpha channel for dest */ } DUMMYUNIONNAME1; DWORD dwAlphaSrcConstBitDepth; /* Bit depth used to specify alpha constant for source */ union { DWORD dwAlphaSrcConst; /* Constant to use as alpha channel for src */ LPDIRECTDRAWSURFACE lpDDSAlphaSrc; /* Surface to use as alpha channel for src */ } DUMMYUNIONNAME2; DDCOLORKEY dckDestColorkey; /* DestColorkey override */ DDCOLORKEY dckSrcColorkey; /* DestColorkey override */ DWORD dwDDFX; /* Overlay FX */ DWORD dwFlags; /* flags */ } DDOVERLAYFX,*LPDDOVERLAYFX; typedef struct _DDBLTBATCH { LPRECT lprDest; LPDIRECTDRAWSURFACE lpDDSSrc; LPRECT lprSrc; DWORD dwFlags; LPDDBLTFX lpDDBltFx; } DDBLTBATCH,*LPDDBLTBATCH; #define MAX_DDDEVICEID_STRING 512 #define DDGDI_GETHOSTIDENTIFIER 1 typedef struct tagDDDEVICEIDENTIFIER { char szDriver[MAX_DDDEVICEID_STRING]; char szDescription[MAX_DDDEVICEID_STRING]; LARGE_INTEGER liDriverVersion; DWORD dwVendorId; DWORD dwDeviceId; DWORD dwSubSysId; DWORD dwRevision; GUID guidDeviceIdentifier; } DDDEVICEIDENTIFIER, * LPDDDEVICEIDENTIFIER; typedef struct tagDDDEVICEIDENTIFIER2 { char szDriver[MAX_DDDEVICEID_STRING]; /* user readable driver name */ char szDescription[MAX_DDDEVICEID_STRING]; /* user readable description */ LARGE_INTEGER liDriverVersion; /* driver version */ DWORD dwVendorId; /* vendor ID, zero if unknown */ DWORD dwDeviceId; /* chipset ID, zero if unknown */ DWORD dwSubSysId; /* board ID, zero if unknown */ DWORD dwRevision; /* chipset version, zero if unknown */ GUID guidDeviceIdentifier; /* unique ID for this driver/chipset combination */ DWORD dwWHQLLevel; /* Windows Hardware Quality Lab certification level */ } DDDEVICEIDENTIFIER2, * LPDDDEVICEIDENTIFIER2; /***************************************************************************** * IDirectDrawPalette interface */ #define INTERFACE IDirectDrawPalette DECLARE_INTERFACE_(IDirectDrawPalette,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDrawPalette methods ***/ STDMETHOD(GetCaps)(THIS_ LPDWORD lpdwCaps) PURE; STDMETHOD(GetEntries)(THIS_ DWORD dwFlags, DWORD dwBase, DWORD dwNumEntries, LPPALETTEENTRY lpEntries) PURE; STDMETHOD(Initialize)(THIS_ LPDIRECTDRAW lpDD, DWORD dwFlags, LPPALETTEENTRY lpDDColorTable) PURE; STDMETHOD(SetEntries)(THIS_ DWORD dwFlags, DWORD dwStartingEntry, DWORD dwCount, LPPALETTEENTRY lpEntries) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDrawPalette_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDrawPalette_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDrawPalette_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDrawPalette methods ***/ #define IDirectDrawPalette_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) #define IDirectDrawPalette_GetEntries(p,a,b,c,d) (p)->lpVtbl->GetEntries(p,a,b,c,d) #define IDirectDrawPalette_Initialize(p,a,b,c) (p)->lpVtbl->Initialize(p,a,b,c) #define IDirectDrawPalette_SetEntries(p,a,b,c,d) (p)->lpVtbl->SetEntries(p,a,b,c,d) #else /*** IUnknown methods ***/ #define IDirectDrawPalette_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDrawPalette_AddRef(p) (p)->AddRef() #define IDirectDrawPalette_Release(p) (p)->Release() /*** IDirectDrawPalette methods ***/ #define IDirectDrawPalette_GetCaps(p,a) (p)->GetCaps(a) #define IDirectDrawPalette_GetEntries(p,a,b,c,d) (p)->GetEntries(a,b,c,d) #define IDirectDrawPalette_Initialize(p,a,b,c) (p)->Initialize(a,b,c) #define IDirectDrawPalette_SetEntries(p,a,b,c,d) (p)->SetEntries(a,b,c,d) #endif /***************************************************************************** * IDirectDrawClipper interface */ #define INTERFACE IDirectDrawClipper DECLARE_INTERFACE_(IDirectDrawClipper,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDrawClipper methods ***/ STDMETHOD(GetClipList)(THIS_ LPRECT lpRect, LPRGNDATA lpClipList, LPDWORD lpdwSize) PURE; STDMETHOD(GetHWnd)(THIS_ HWND *lphWnd) PURE; STDMETHOD(Initialize)(THIS_ LPDIRECTDRAW lpDD, DWORD dwFlags) PURE; STDMETHOD(IsClipListChanged)(THIS_ BOOL *lpbChanged) PURE; STDMETHOD(SetClipList)(THIS_ LPRGNDATA lpClipList, DWORD dwFlags) PURE; STDMETHOD(SetHWnd)(THIS_ DWORD dwFlags, HWND hWnd) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDrawClipper_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDrawClipper_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDrawClipper_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDrawClipper methods ***/ #define IDirectDrawClipper_GetClipList(p,a,b,c) (p)->lpVtbl->GetClipList(p,a,b,c) #define IDirectDrawClipper_GetHWnd(p,a) (p)->lpVtbl->GetHWnd(p,a) #define IDirectDrawClipper_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) #define IDirectDrawClipper_IsClipListChanged(p,a) (p)->lpVtbl->IsClipListChanged(p,a) #define IDirectDrawClipper_SetClipList(p,a,b) (p)->lpVtbl->SetClipList(p,a,b) #define IDirectDrawClipper_SetHWnd(p,a,b) (p)->lpVtbl->SetHWnd(p,a,b) #else /*** IUnknown methods ***/ #define IDirectDrawClipper_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDrawClipper_AddRef(p) (p)->AddRef() #define IDirectDrawClipper_Release(p) (p)->Release() /*** IDirectDrawClipper methods ***/ #define IDirectDrawClipper_GetClipList(p,a,b,c) (p)->GetClipList(a,b,c) #define IDirectDrawClipper_GetHWnd(p,a) (p)->GetHWnd(a) #define IDirectDrawClipper_Initialize(p,a,b) (p)->Initialize(a,b) #define IDirectDrawClipper_IsClipListChanged(p,a) (p)->IsClipListChanged(a) #define IDirectDrawClipper_SetClipList(p,a,b) (p)->SetClipList(a,b) #define IDirectDrawClipper_SetHWnd(p,a,b) (p)->SetHWnd(a,b) #endif /***************************************************************************** * IDirectDraw interface */ #define INTERFACE IDirectDraw DECLARE_INTERFACE_(IDirectDraw,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDraw methods ***/ STDMETHOD(Compact)(THIS) PURE; STDMETHOD(CreateClipper)(THIS_ DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) PURE; STDMETHOD(CreatePalette)(THIS_ DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter) PURE; STDMETHOD(CreateSurface)(THIS_ LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE *lplpDDSurface, IUnknown *pUnkOuter) PURE; STDMETHOD(DuplicateSurface)(THIS_ LPDIRECTDRAWSURFACE lpDDSurface, LPDIRECTDRAWSURFACE *lplpDupDDSurface) PURE; STDMETHOD(EnumDisplayModes)(THIS_ DWORD dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK lpEnumModesCallback) PURE; STDMETHOD(EnumSurfaces)(THIS_ DWORD dwFlags, LPDDSURFACEDESC lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) PURE; STDMETHOD(FlipToGDISurface)(THIS) PURE; STDMETHOD(GetCaps)(THIS_ LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) PURE; STDMETHOD(GetDisplayMode)(THIS_ LPDDSURFACEDESC lpDDSurfaceDesc) PURE; STDMETHOD(GetFourCCCodes)(THIS_ LPDWORD lpNumCodes, LPDWORD lpCodes) PURE; STDMETHOD(GetGDISurface)(THIS_ LPDIRECTDRAWSURFACE *lplpGDIDDSurface) PURE; STDMETHOD(GetMonitorFrequency)(THIS_ LPDWORD lpdwFrequency) PURE; STDMETHOD(GetScanLine)(THIS_ LPDWORD lpdwScanLine) PURE; STDMETHOD(GetVerticalBlankStatus)(THIS_ BOOL *lpbIsInVB) PURE; STDMETHOD(Initialize)(THIS_ GUID *lpGUID) PURE; STDMETHOD(RestoreDisplayMode)(THIS) PURE; STDMETHOD(SetCooperativeLevel)(THIS_ HWND hWnd, DWORD dwFlags) PURE; STDMETHOD(SetDisplayMode)(THIS_ DWORD dwWidth, DWORD dwHeight, DWORD dwBPP) PURE; STDMETHOD(WaitForVerticalBlank)(THIS_ DWORD dwFlags, HANDLE hEvent) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDraw_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDraw_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDraw_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDraw methods ***/ #define IDirectDraw_Compact(p) (p)->lpVtbl->Compact(p) #define IDirectDraw_CreateClipper(p,a,b,c) (p)->lpVtbl->CreateClipper(p,a,b,c) #define IDirectDraw_CreatePalette(p,a,b,c,d) (p)->lpVtbl->CreatePalette(p,a,b,c,d) #define IDirectDraw_CreateSurface(p,a,b,c) (p)->lpVtbl->CreateSurface(p,a,b,c) #define IDirectDraw_DuplicateSurface(p,a,b) (p)->lpVtbl->DuplicateSurface(p,a,b) #define IDirectDraw_EnumDisplayModes(p,a,b,c,d) (p)->lpVtbl->EnumDisplayModes(p,a,b,c,d) #define IDirectDraw_EnumSurfaces(p,a,b,c,d) (p)->lpVtbl->EnumSurfaces(p,a,b,c,d) #define IDirectDraw_FlipToGDISurface(p) (p)->lpVtbl->FlipToGDISurface(p) #define IDirectDraw_GetCaps(p,a,b) (p)->lpVtbl->GetCaps(p,a,b) #define IDirectDraw_GetDisplayMode(p,a) (p)->lpVtbl->GetDisplayMode(p,a) #define IDirectDraw_GetFourCCCodes(p,a,b) (p)->lpVtbl->GetFourCCCodes(p,a,b) #define IDirectDraw_GetGDISurface(p,a) (p)->lpVtbl->GetGDISurface(p,a) #define IDirectDraw_GetMonitorFrequency(p,a) (p)->lpVtbl->GetMonitorFrequency(p,a) #define IDirectDraw_GetScanLine(p,a) (p)->lpVtbl->GetScanLine(p,a) #define IDirectDraw_GetVerticalBlankStatus(p,a) (p)->lpVtbl->GetVerticalBlankStatus(p,a) #define IDirectDraw_Initialize(p,a) (p)->lpVtbl->Initialize(p,a) #define IDirectDraw_RestoreDisplayMode(p) (p)->lpVtbl->RestoreDisplayMode(p) #define IDirectDraw_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) #define IDirectDraw_SetDisplayMode(p,a,b,c) (p)->lpVtbl->SetDisplayMode(p,a,b,c) #define IDirectDraw_WaitForVerticalBlank(p,a,b) (p)->lpVtbl->WaitForVerticalBlank(p,a,b) #else /*** IUnknown methods ***/ #define IDirectDraw_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDraw_AddRef(p) (p)->AddRef() #define IDirectDraw_Release(p) (p)->Release() /*** IDirectDraw methods ***/ #define IDirectDraw_Compact(p) (p)->Compact() #define IDirectDraw_CreateClipper(p,a,b,c) (p)->CreateClipper(a,b,c) #define IDirectDraw_CreatePalette(p,a,b,c,d) (p)->CreatePalette(a,b,c,d) #define IDirectDraw_CreateSurface(p,a,b,c) (p)->CreateSurface(a,b,c) #define IDirectDraw_DuplicateSurface(p,a,b) (p)->DuplicateSurface(a,b) #define IDirectDraw_EnumDisplayModes(p,a,b,c,d) (p)->EnumDisplayModes(a,b,c,d) #define IDirectDraw_EnumSurfaces(p,a,b,c,d) (p)->EnumSurfaces(a,b,c,d) #define IDirectDraw_FlipToGDISurface(p) (p)->FlipToGDISurface() #define IDirectDraw_GetCaps(p,a,b) (p)->GetCaps(a,b) #define IDirectDraw_GetDisplayMode(p,a) (p)->GetDisplayMode(a) #define IDirectDraw_GetFourCCCodes(p,a,b) (p)->GetFourCCCodes(a,b) #define IDirectDraw_GetGDISurface(p,a) (p)->GetGDISurface(a) #define IDirectDraw_GetMonitorFrequency(p,a) (p)->GetMonitorFrequency(a) #define IDirectDraw_GetScanLine(p,a) (p)->GetScanLine(a) #define IDirectDraw_GetVerticalBlankStatus(p,a) (p)->GetVerticalBlankStatus(a) #define IDirectDraw_Initialize(p,a) (p)->Initialize(a) #define IDirectDraw_RestoreDisplayMode(p) (p)->RestoreDisplayMode() #define IDirectDraw_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) #define IDirectDraw_SetDisplayMode(p,a,b,c) (p)->SetDisplayMode(a,b,c) #define IDirectDraw_WaitForVerticalBlank(p,a,b) (p)->WaitForVerticalBlank(a,b) #endif /* flags for Lock() */ #define DDLOCK_SURFACEMEMORYPTR 0x00000000 #define DDLOCK_WAIT 0x00000001 #define DDLOCK_EVENT 0x00000002 #define DDLOCK_READONLY 0x00000010 #define DDLOCK_WRITEONLY 0x00000020 #define DDLOCK_NOSYSLOCK 0x00000800 #define DDLOCK_NOOVERWRITE 0x00001000 #define DDLOCK_DISCARDCONTENTS 0x00002000 /***************************************************************************** * IDirectDraw2 interface */ /* Note: IDirectDraw2 cannot derive from IDirectDraw because the number of * arguments of SetDisplayMode has changed ! */ #define INTERFACE IDirectDraw2 DECLARE_INTERFACE_(IDirectDraw2,IUnknown) { /*** IUnknown methods ***/ /*00*/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; /*04*/ STDMETHOD_(ULONG,AddRef)(THIS) PURE; /*08*/ STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDraw2 methods ***/ /*0c*/ STDMETHOD(Compact)(THIS) PURE; /*10*/ STDMETHOD(CreateClipper)(THIS_ DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) PURE; /*14*/ STDMETHOD(CreatePalette)(THIS_ DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter) PURE; /*18*/ STDMETHOD(CreateSurface)(THIS_ LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE *lplpDDSurface, IUnknown *pUnkOuter) PURE; /*1c*/ STDMETHOD(DuplicateSurface)(THIS_ LPDIRECTDRAWSURFACE lpDDSurface, LPDIRECTDRAWSURFACE *lplpDupDDSurface) PURE; /*20*/ STDMETHOD(EnumDisplayModes)(THIS_ DWORD dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK lpEnumModesCallback) PURE; /*24*/ STDMETHOD(EnumSurfaces)(THIS_ DWORD dwFlags, LPDDSURFACEDESC lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) PURE; /*28*/ STDMETHOD(FlipToGDISurface)(THIS) PURE; /*2c*/ STDMETHOD(GetCaps)(THIS_ LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) PURE; /*30*/ STDMETHOD(GetDisplayMode)(THIS_ LPDDSURFACEDESC lpDDSurfaceDesc) PURE; /*34*/ STDMETHOD(GetFourCCCodes)(THIS_ LPDWORD lpNumCodes, LPDWORD lpCodes) PURE; /*38*/ STDMETHOD(GetGDISurface)(THIS_ LPDIRECTDRAWSURFACE *lplpGDIDDSurface) PURE; /*3c*/ STDMETHOD(GetMonitorFrequency)(THIS_ LPDWORD lpdwFrequency) PURE; /*40*/ STDMETHOD(GetScanLine)(THIS_ LPDWORD lpdwScanLine) PURE; /*44*/ STDMETHOD(GetVerticalBlankStatus)(THIS_ BOOL *lpbIsInVB) PURE; /*48*/ STDMETHOD(Initialize)(THIS_ GUID *lpGUID) PURE; /*4c*/ STDMETHOD(RestoreDisplayMode)(THIS) PURE; /*50*/ STDMETHOD(SetCooperativeLevel)(THIS_ HWND hWnd, DWORD dwFlags) PURE; /*54*/ STDMETHOD(SetDisplayMode)(THIS_ DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags) PURE; /*58*/ STDMETHOD(WaitForVerticalBlank)(THIS_ DWORD dwFlags, HANDLE hEvent) PURE; /* added in v2 */ /*5c*/ STDMETHOD(GetAvailableVidMem)(THIS_ LPDDSCAPS lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDraw2_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDraw2_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDraw2_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDraw methods ***/ #define IDirectDraw2_Compact(p) (p)->lpVtbl->Compact(p) #define IDirectDraw2_CreateClipper(p,a,b,c) (p)->lpVtbl->CreateClipper(p,a,b,c) #define IDirectDraw2_CreatePalette(p,a,b,c,d) (p)->lpVtbl->CreatePalette(p,a,b,c,d) #define IDirectDraw2_CreateSurface(p,a,b,c) (p)->lpVtbl->CreateSurface(p,a,b,c) #define IDirectDraw2_DuplicateSurface(p,a,b) (p)->lpVtbl->DuplicateSurface(p,a,b) #define IDirectDraw2_EnumDisplayModes(p,a,b,c,d) (p)->lpVtbl->EnumDisplayModes(p,a,b,c,d) #define IDirectDraw2_EnumSurfaces(p,a,b,c,d) (p)->lpVtbl->EnumSurfaces(p,a,b,c,d) #define IDirectDraw2_FlipToGDISurface(p) (p)->lpVtbl->FlipToGDISurface(p) #define IDirectDraw2_GetCaps(p,a,b) (p)->lpVtbl->GetCaps(p,a,b) #define IDirectDraw2_GetDisplayMode(p,a) (p)->lpVtbl->GetDisplayMode(p,a) #define IDirectDraw2_GetFourCCCodes(p,a,b) (p)->lpVtbl->GetFourCCCodes(p,a,b) #define IDirectDraw2_GetGDISurface(p,a) (p)->lpVtbl->GetGDISurface(p,a) #define IDirectDraw2_GetMonitorFrequency(p,a) (p)->lpVtbl->GetMonitorFrequency(p,a) #define IDirectDraw2_GetScanLine(p,a) (p)->lpVtbl->GetScanLine(p,a) #define IDirectDraw2_GetVerticalBlankStatus(p,a) (p)->lpVtbl->GetVerticalBlankStatus(p,a) #define IDirectDraw2_Initialize(p,a) (p)->lpVtbl->Initialize(p,a) #define IDirectDraw2_RestoreDisplayMode(p) (p)->lpVtbl->RestoreDisplayMode(p) #define IDirectDraw2_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) #define IDirectDraw2_SetDisplayMode(p,a,b,c,d,e) (p)->lpVtbl->SetDisplayMode(p,a,b,c,d,e) #define IDirectDraw2_WaitForVerticalBlank(p,a,b) (p)->lpVtbl->WaitForVerticalBlank(p,a,b) /*** IDirectDraw2 methods ***/ #define IDirectDraw2_GetAvailableVidMem(p,a,b,c) (p)->lpVtbl->GetAvailableVidMem(p,a,b,c) #else /*** IUnknown methods ***/ #define IDirectDraw2_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDraw2_AddRef(p) (p)->AddRef() #define IDirectDraw2_Release(p) (p)->Release() /*** IDirectDraw methods ***/ #define IDirectDraw2_Compact(p) (p)->Compact() #define IDirectDraw2_CreateClipper(p,a,b,c) (p)->CreateClipper(a,b,c) #define IDirectDraw2_CreatePalette(p,a,b,c,d) (p)->CreatePalette(a,b,c,d) #define IDirectDraw2_CreateSurface(p,a,b,c) (p)->CreateSurface(a,b,c) #define IDirectDraw2_DuplicateSurface(p,a,b) (p)->DuplicateSurface(a,b) #define IDirectDraw2_EnumDisplayModes(p,a,b,c,d) (p)->EnumDisplayModes(a,b,c,d) #define IDirectDraw2_EnumSurfaces(p,a,b,c,d) (p)->EnumSurfaces(a,b,c,d) #define IDirectDraw2_FlipToGDISurface(p) (p)->FlipToGDISurface() #define IDirectDraw2_GetCaps(p,a,b) (p)->GetCaps(a,b) #define IDirectDraw2_GetDisplayMode(p,a) (p)->GetDisplayMode(a) #define IDirectDraw2_GetFourCCCodes(p,a,b) (p)->GetFourCCCodes(a,b) #define IDirectDraw2_GetGDISurface(p,a) (p)->GetGDISurface(a) #define IDirectDraw2_GetMonitorFrequency(p,a) (p)->GetMonitorFrequency(a) #define IDirectDraw2_GetScanLine(p,a) (p)->GetScanLine(a) #define IDirectDraw2_GetVerticalBlankStatus(p,a) (p)->GetVerticalBlankStatus(a) #define IDirectDraw2_Initialize(p,a) (p)->Initialize(a) #define IDirectDraw2_RestoreDisplayMode(p) (p)->RestoreDisplayMode() #define IDirectDraw2_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) #define IDirectDraw2_SetDisplayMode(p,a,b,c,d,e) (p)->SetDisplayMode(a,b,c,d,e) #define IDirectDraw2_WaitForVerticalBlank(p,a,b) (p)->WaitForVerticalBlank(a,b) /*** IDirectDraw2 methods ***/ #define IDirectDraw2_GetAvailableVidMem(p,a,b,c) (p)->GetAvailableVidMem(a,b,c) #endif /***************************************************************************** * IDirectDraw3 interface */ #define INTERFACE IDirectDraw3 DECLARE_INTERFACE_(IDirectDraw3,IUnknown) { /*** IUnknown methods ***/ /*00*/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; /*04*/ STDMETHOD_(ULONG,AddRef)(THIS) PURE; /*08*/ STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDraw2 methods ***/ /*0c*/ STDMETHOD(Compact)(THIS) PURE; /*10*/ STDMETHOD(CreateClipper)(THIS_ DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) PURE; /*14*/ STDMETHOD(CreatePalette)(THIS_ DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter) PURE; /*18*/ STDMETHOD(CreateSurface)(THIS_ LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE *lplpDDSurface, IUnknown *pUnkOuter) PURE; /*1c*/ STDMETHOD(DuplicateSurface)(THIS_ LPDIRECTDRAWSURFACE lpDDSurface, LPDIRECTDRAWSURFACE *lplpDupDDSurface) PURE; /*20*/ STDMETHOD(EnumDisplayModes)(THIS_ DWORD dwFlags, LPDDSURFACEDESC lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK lpEnumModesCallback) PURE; /*24*/ STDMETHOD(EnumSurfaces)(THIS_ DWORD dwFlags, LPDDSURFACEDESC lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) PURE; /*28*/ STDMETHOD(FlipToGDISurface)(THIS) PURE; /*2c*/ STDMETHOD(GetCaps)(THIS_ LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) PURE; /*30*/ STDMETHOD(GetDisplayMode)(THIS_ LPDDSURFACEDESC lpDDSurfaceDesc) PURE; /*34*/ STDMETHOD(GetFourCCCodes)(THIS_ LPDWORD lpNumCodes, LPDWORD lpCodes) PURE; /*38*/ STDMETHOD(GetGDISurface)(THIS_ LPDIRECTDRAWSURFACE *lplpGDIDDSurface) PURE; /*3c*/ STDMETHOD(GetMonitorFrequency)(THIS_ LPDWORD lpdwFrequency) PURE; /*40*/ STDMETHOD(GetScanLine)(THIS_ LPDWORD lpdwScanLine) PURE; /*44*/ STDMETHOD(GetVerticalBlankStatus)(THIS_ BOOL *lpbIsInVB) PURE; /*48*/ STDMETHOD(Initialize)(THIS_ GUID *lpGUID) PURE; /*4c*/ STDMETHOD(RestoreDisplayMode)(THIS) PURE; /*50*/ STDMETHOD(SetCooperativeLevel)(THIS_ HWND hWnd, DWORD dwFlags) PURE; /*54*/ STDMETHOD(SetDisplayMode)(THIS_ DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags) PURE; /*58*/ STDMETHOD(WaitForVerticalBlank)(THIS_ DWORD dwFlags, HANDLE hEvent) PURE; /* added in v2 */ /*5c*/ STDMETHOD(GetAvailableVidMem)(THIS_ LPDDSCAPS lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree) PURE; /* added in v3 */ /*60*/ STDMETHOD(GetSurfaceFromDC)(THIS_ HDC hdc, LPDIRECTDRAWSURFACE *pSurf) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDraw3_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDraw3_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDraw3_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDraw methods ***/ #define IDirectDraw3_Compact(p) (p)->lpVtbl->Compact(p) #define IDirectDraw3_CreateClipper(p,a,b,c) (p)->lpVtbl->CreateClipper(p,a,b,c) #define IDirectDraw3_CreatePalette(p,a,b,c,d) (p)->lpVtbl->CreatePalette(p,a,b,c,d) #define IDirectDraw3_CreateSurface(p,a,b,c) (p)->lpVtbl->CreateSurface(p,a,b,c) #define IDirectDraw3_DuplicateSurface(p,a,b) (p)->lpVtbl->DuplicateSurface(p,a,b) #define IDirectDraw3_EnumDisplayModes(p,a,b,c,d) (p)->lpVtbl->EnumDisplayModes(p,a,b,c,d) #define IDirectDraw3_EnumSurfaces(p,a,b,c,d) (p)->lpVtbl->EnumSurfaces(p,a,b,c,d) #define IDirectDraw3_FlipToGDISurface(p) (p)->lpVtbl->FlipToGDISurface(p) #define IDirectDraw3_GetCaps(p,a,b) (p)->lpVtbl->GetCaps(p,a,b) #define IDirectDraw3_GetDisplayMode(p,a) (p)->lpVtbl->GetDisplayMode(p,a) #define IDirectDraw3_GetFourCCCodes(p,a,b) (p)->lpVtbl->GetFourCCCodes(p,a,b) #define IDirectDraw3_GetGDISurface(p,a) (p)->lpVtbl->GetGDISurface(p,a) #define IDirectDraw3_GetMonitorFrequency(p,a) (p)->lpVtbl->GetMonitorFrequency(p,a) #define IDirectDraw3_GetScanLine(p,a) (p)->lpVtbl->GetScanLine(p,a) #define IDirectDraw3_GetVerticalBlankStatus(p,a) (p)->lpVtbl->GetVerticalBlankStatus(p,a) #define IDirectDraw3_Initialize(p,a) (p)->lpVtbl->Initialize(p,a) #define IDirectDraw3_RestoreDisplayMode(p) (p)->lpVtbl->RestoreDisplayMode(p) #define IDirectDraw3_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) #define IDirectDraw3_SetDisplayMode(p,a,b,c,d,e) (p)->lpVtbl->SetDisplayMode(p,a,b,c,d,e) #define IDirectDraw3_WaitForVerticalBlank(p,a,b) (p)->lpVtbl->WaitForVerticalBlank(p,a,b) /*** IDirectDraw2 methods ***/ #define IDirectDraw3_GetAvailableVidMem(p,a,b,c) (p)->lpVtbl->GetAvailableVidMem(p,a,b,c) /*** IDirectDraw3 methods ***/ #define IDirectDraw3_GetSurfaceFromDC(p,a,b) (p)->lpVtbl->GetSurfaceFromDC(p,a,b) #else /*** IUnknown methods ***/ #define IDirectDraw3_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDraw3_AddRef(p) (p)->AddRef() #define IDirectDraw3_Release(p) (p)->Release() /*** IDirectDraw methods ***/ #define IDirectDraw3_Compact(p) (p)->Compact() #define IDirectDraw3_CreateClipper(p,a,b,c) (p)->CreateClipper(a,b,c) #define IDirectDraw3_CreatePalette(p,a,b,c,d) (p)->CreatePalette(a,b,c,d) #define IDirectDraw3_CreateSurface(p,a,b,c) (p)->CreateSurface(a,b,c) #define IDirectDraw3_DuplicateSurface(p,a,b) (p)->DuplicateSurface(a,b) #define IDirectDraw3_EnumDisplayModes(p,a,b,c,d) (p)->EnumDisplayModes(a,b,c,d) #define IDirectDraw3_EnumSurfaces(p,a,b,c,d) (p)->EnumSurfaces(a,b,c,d) #define IDirectDraw3_FlipToGDISurface(p) (p)->FlipToGDISurface() #define IDirectDraw3_GetCaps(p,a,b) (p)->GetCaps(a,b) #define IDirectDraw3_GetDisplayMode(p,a) (p)->GetDisplayMode(a) #define IDirectDraw3_GetFourCCCodes(p,a,b) (p)->GetFourCCCodes(a,b) #define IDirectDraw3_GetGDISurface(p,a) (p)->GetGDISurface(a) #define IDirectDraw3_GetMonitorFrequency(p,a) (p)->GetMonitorFrequency(a) #define IDirectDraw3_GetScanLine(p,a) (p)->GetScanLine(a) #define IDirectDraw3_GetVerticalBlankStatus(p,a) (p)->GetVerticalBlankStatus(a) #define IDirectDraw3_Initialize(p,a) (p)->Initialize(a) #define IDirectDraw3_RestoreDisplayMode(p) (p)->RestoreDisplayMode() #define IDirectDraw3_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) #define IDirectDraw3_SetDisplayMode(p,a,b,c,d,e) (p)->SetDisplayMode(a,b,c,d,e) #define IDirectDraw3_WaitForVerticalBlank(p,a,b) (p)->WaitForVerticalBlank(a,b) /*** IDirectDraw2 methods ***/ #define IDirectDraw3_GetAvailableVidMem(p,a,b,c) (p)->GetAvailableVidMem(a,b,c) /*** IDirectDraw3 methods ***/ #define IDirectDraw3_GetSurfaceFromDC(p,a,b) (p)->GetSurfaceFromDC(a,b) #endif /***************************************************************************** * IDirectDraw4 interface */ #define INTERFACE IDirectDraw4 DECLARE_INTERFACE_(IDirectDraw4,IUnknown) { /*** IUnknown methods ***/ /*00*/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; /*04*/ STDMETHOD_(ULONG,AddRef)(THIS) PURE; /*08*/ STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDraw4 methods ***/ /*0c*/ STDMETHOD(Compact)(THIS) PURE; /*10*/ STDMETHOD(CreateClipper)(THIS_ DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) PURE; /*14*/ STDMETHOD(CreatePalette)(THIS_ DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter) PURE; /*18*/ STDMETHOD(CreateSurface)(THIS_ LPDDSURFACEDESC2 lpDDSurfaceDesc, LPDIRECTDRAWSURFACE4 *lplpDDSurface, IUnknown *pUnkOuter) PURE; /*1c*/ STDMETHOD(DuplicateSurface)(THIS_ LPDIRECTDRAWSURFACE4 lpDDSurface, LPDIRECTDRAWSURFACE4 *lplpDupDDSurface) PURE; /*20*/ STDMETHOD(EnumDisplayModes)(THIS_ DWORD dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK2 lpEnumModesCallback) PURE; /*24*/ STDMETHOD(EnumSurfaces)(THIS_ DWORD dwFlags, LPDDSURFACEDESC2 lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK2 lpEnumSurfacesCallback) PURE; /*28*/ STDMETHOD(FlipToGDISurface)(THIS) PURE; /*2c*/ STDMETHOD(GetCaps)(THIS_ LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) PURE; /*30*/ STDMETHOD(GetDisplayMode)(THIS_ LPDDSURFACEDESC2 lpDDSurfaceDesc) PURE; /*34*/ STDMETHOD(GetFourCCCodes)(THIS_ LPDWORD lpNumCodes, LPDWORD lpCodes) PURE; /*38*/ STDMETHOD(GetGDISurface)(THIS_ LPDIRECTDRAWSURFACE4 *lplpGDIDDSurface) PURE; /*3c*/ STDMETHOD(GetMonitorFrequency)(THIS_ LPDWORD lpdwFrequency) PURE; /*40*/ STDMETHOD(GetScanLine)(THIS_ LPDWORD lpdwScanLine) PURE; /*44*/ STDMETHOD(GetVerticalBlankStatus)(THIS_ BOOL *lpbIsInVB) PURE; /*48*/ STDMETHOD(Initialize)(THIS_ GUID *lpGUID) PURE; /*4c*/ STDMETHOD(RestoreDisplayMode)(THIS) PURE; /*50*/ STDMETHOD(SetCooperativeLevel)(THIS_ HWND hWnd, DWORD dwFlags) PURE; /*54*/ STDMETHOD(SetDisplayMode)(THIS_ DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags) PURE; /*58*/ STDMETHOD(WaitForVerticalBlank)(THIS_ DWORD dwFlags, HANDLE hEvent) PURE; /* added in v2 */ /*5c*/ STDMETHOD(GetAvailableVidMem)(THIS_ LPDDSCAPS2 lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree) PURE; /* added in v4 */ /*60*/ STDMETHOD(GetSurfaceFromDC)(THIS_ HDC hdc, LPDIRECTDRAWSURFACE4 *pSurf) PURE; /*64*/ STDMETHOD(RestoreAllSurfaces)(THIS) PURE; /*68*/ STDMETHOD(TestCooperativeLevel)(THIS) PURE; /*6c*/ STDMETHOD(GetDeviceIdentifier)(THIS_ LPDDDEVICEIDENTIFIER pDDDI, DWORD dwFlags) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDraw4_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDraw4_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDraw4_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDraw methods ***/ #define IDirectDraw4_Compact(p) (p)->lpVtbl->Compact(p) #define IDirectDraw4_CreateClipper(p,a,b,c) (p)->lpVtbl->CreateClipper(p,a,b,c) #define IDirectDraw4_CreatePalette(p,a,b,c,d) (p)->lpVtbl->CreatePalette(p,a,b,c,d) #define IDirectDraw4_CreateSurface(p,a,b,c) (p)->lpVtbl->CreateSurface(p,a,b,c) #define IDirectDraw4_DuplicateSurface(p,a,b) (p)->lpVtbl->DuplicateSurface(p,a,b) #define IDirectDraw4_EnumDisplayModes(p,a,b,c,d) (p)->lpVtbl->EnumDisplayModes(p,a,b,c,d) #define IDirectDraw4_EnumSurfaces(p,a,b,c,d) (p)->lpVtbl->EnumSurfaces(p,a,b,c,d) #define IDirectDraw4_FlipToGDISurface(p) (p)->lpVtbl->FlipToGDISurface(p) #define IDirectDraw4_GetCaps(p,a,b) (p)->lpVtbl->GetCaps(p,a,b) #define IDirectDraw4_GetDisplayMode(p,a) (p)->lpVtbl->GetDisplayMode(p,a) #define IDirectDraw4_GetFourCCCodes(p,a,b) (p)->lpVtbl->GetFourCCCodes(p,a,b) #define IDirectDraw4_GetGDISurface(p,a) (p)->lpVtbl->GetGDISurface(p,a) #define IDirectDraw4_GetMonitorFrequency(p,a) (p)->lpVtbl->GetMonitorFrequency(p,a) #define IDirectDraw4_GetScanLine(p,a) (p)->lpVtbl->GetScanLine(p,a) #define IDirectDraw4_GetVerticalBlankStatus(p,a) (p)->lpVtbl->GetVerticalBlankStatus(p,a) #define IDirectDraw4_Initialize(p,a) (p)->lpVtbl->Initialize(p,a) #define IDirectDraw4_RestoreDisplayMode(p) (p)->lpVtbl->RestoreDisplayMode(p) #define IDirectDraw4_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) #define IDirectDraw4_SetDisplayMode(p,a,b,c,d,e) (p)->lpVtbl->SetDisplayMode(p,a,b,c,d,e) #define IDirectDraw4_WaitForVerticalBlank(p,a,b) (p)->lpVtbl->WaitForVerticalBlank(p,a,b) /*** IDirectDraw2 methods ***/ #define IDirectDraw4_GetAvailableVidMem(p,a,b,c) (p)->lpVtbl->GetAvailableVidMem(p,a,b,c) /*** IDirectDraw4 methods ***/ #define IDirectDraw4_GetSurfaceFromDC(p,a,b) (p)->lpVtbl->GetSurfaceFromDC(p,a,b) #define IDirectDraw4_RestoreAllSurfaces(pc) (p)->lpVtbl->RestoreAllSurfaces(p) #define IDirectDraw4_TestCooperativeLevel(p) (p)->lpVtbl->TestCooperativeLevel(p) #define IDirectDraw4_GetDeviceIdentifier(p,a,b) (p)->lpVtbl->GetDeviceIdentifier(p,a,b) #else /*** IUnknown methods ***/ #define IDirectDraw4_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDraw4_AddRef(p) (p)->AddRef() #define IDirectDraw4_Release(p) (p)->Release() /*** IDirectDraw methods ***/ #define IDirectDraw4_Compact(p) (p)->Compact() #define IDirectDraw4_CreateClipper(p,a,b,c) (p)->CreateClipper(a,b,c) #define IDirectDraw4_CreatePalette(p,a,b,c,d) (p)->CreatePalette(a,b,c,d) #define IDirectDraw4_CreateSurface(p,a,b,c) (p)->CreateSurface(a,b,c) #define IDirectDraw4_DuplicateSurface(p,a,b) (p)->DuplicateSurface(a,b) #define IDirectDraw4_EnumDisplayModes(p,a,b,c,d) (p)->EnumDisplayModes(a,b,c,d) #define IDirectDraw4_EnumSurfaces(p,a,b,c,d) (p)->EnumSurfaces(a,b,c,d) #define IDirectDraw4_FlipToGDISurface(p) (p)->FlipToGDISurface() #define IDirectDraw4_GetCaps(p,a,b) (p)->GetCaps(a,b) #define IDirectDraw4_GetDisplayMode(p,a) (p)->GetDisplayMode(a) #define IDirectDraw4_GetFourCCCodes(p,a,b) (p)->GetFourCCCodes(a,b) #define IDirectDraw4_GetGDISurface(p,a) (p)->GetGDISurface(a) #define IDirectDraw4_GetMonitorFrequency(p,a) (p)->GetMonitorFrequency(a) #define IDirectDraw4_GetScanLine(p,a) (p)->GetScanLine(a) #define IDirectDraw4_GetVerticalBlankStatus(p,a) (p)->GetVerticalBlankStatus(a) #define IDirectDraw4_Initialize(p,a) (p)->Initialize(a) #define IDirectDraw4_RestoreDisplayMode(p) (p)->RestoreDisplayMode() #define IDirectDraw4_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) #define IDirectDraw4_SetDisplayMode(p,a,b,c,d,e) (p)->SetDisplayMode(a,b,c,d,e) #define IDirectDraw4_WaitForVerticalBlank(p,a,b) (p)->WaitForVerticalBlank(a,b) /*** IDirectDraw2 methods ***/ #define IDirectDraw4_GetAvailableVidMem(p,a,b,c) (p)->GetAvailableVidMem(a,b,c) /*** IDirectDraw4 methods ***/ #define IDirectDraw4_GetSurfaceFromDC(p,a,b) (p)->GetSurfaceFromDC(a,b) #define IDirectDraw4_RestoreAllSurfaces(pc) (p)->RestoreAllSurfaces() #define IDirectDraw4_TestCooperativeLevel(p) (p)->TestCooperativeLevel() #define IDirectDraw4_GetDeviceIdentifier(p,a,b) (p)->GetDeviceIdentifier(a,b) #endif /***************************************************************************** * IDirectDraw7 interface */ /* Note: IDirectDraw7 cannot derive from IDirectDraw4; it is even documented * as not interchangeable with earlier DirectDraw interfaces. */ #define INTERFACE IDirectDraw7 DECLARE_INTERFACE_(IDirectDraw7,IUnknown) { /*** IUnknown methods ***/ /*00*/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; /*04*/ STDMETHOD_(ULONG,AddRef)(THIS) PURE; /*08*/ STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDraw7 methods ***/ /*0c*/ STDMETHOD(Compact)(THIS) PURE; /*10*/ STDMETHOD(CreateClipper)(THIS_ DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, IUnknown *pUnkOuter) PURE; /*14*/ STDMETHOD(CreatePalette)(THIS_ DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE *lplpDDPalette, IUnknown *pUnkOuter) PURE; /*18*/ STDMETHOD(CreateSurface)(THIS_ LPDDSURFACEDESC2 lpDDSurfaceDesc, LPDIRECTDRAWSURFACE7 *lplpDDSurface, IUnknown *pUnkOuter) PURE; /*1c*/ STDMETHOD(DuplicateSurface)(THIS_ LPDIRECTDRAWSURFACE7 lpDDSurface, LPDIRECTDRAWSURFACE7 *lplpDupDDSurface) PURE; /*20*/ STDMETHOD(EnumDisplayModes)(THIS_ DWORD dwFlags, LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext, LPDDENUMMODESCALLBACK2 lpEnumModesCallback) PURE; /*24*/ STDMETHOD(EnumSurfaces)(THIS_ DWORD dwFlags, LPDDSURFACEDESC2 lpDDSD, LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpEnumSurfacesCallback) PURE; /*28*/ STDMETHOD(FlipToGDISurface)(THIS) PURE; /*2c*/ STDMETHOD(GetCaps)(THIS_ LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) PURE; /*30*/ STDMETHOD(GetDisplayMode)(THIS_ LPDDSURFACEDESC2 lpDDSurfaceDesc) PURE; /*34*/ STDMETHOD(GetFourCCCodes)(THIS_ LPDWORD lpNumCodes, LPDWORD lpCodes) PURE; /*38*/ STDMETHOD(GetGDISurface)(THIS_ LPDIRECTDRAWSURFACE7 *lplpGDIDDSurface) PURE; /*3c*/ STDMETHOD(GetMonitorFrequency)(THIS_ LPDWORD lpdwFrequency) PURE; /*40*/ STDMETHOD(GetScanLine)(THIS_ LPDWORD lpdwScanLine) PURE; /*44*/ STDMETHOD(GetVerticalBlankStatus)(THIS_ BOOL *lpbIsInVB) PURE; /*48*/ STDMETHOD(Initialize)(THIS_ GUID *lpGUID) PURE; /*4c*/ STDMETHOD(RestoreDisplayMode)(THIS) PURE; /*50*/ STDMETHOD(SetCooperativeLevel)(THIS_ HWND hWnd, DWORD dwFlags) PURE; /*54*/ STDMETHOD(SetDisplayMode)(THIS_ DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags) PURE; /*58*/ STDMETHOD(WaitForVerticalBlank)(THIS_ DWORD dwFlags, HANDLE hEvent) PURE; /* added in v2 */ /*5c*/ STDMETHOD(GetAvailableVidMem)(THIS_ LPDDSCAPS2 lpDDCaps, LPDWORD lpdwTotal, LPDWORD lpdwFree) PURE; /* added in v4 */ /*60*/ STDMETHOD(GetSurfaceFromDC)(THIS_ HDC hdc, LPDIRECTDRAWSURFACE7 *pSurf) PURE; /*64*/ STDMETHOD(RestoreAllSurfaces)(THIS) PURE; /*68*/ STDMETHOD(TestCooperativeLevel)(THIS) PURE; /*6c*/ STDMETHOD(GetDeviceIdentifier)(THIS_ LPDDDEVICEIDENTIFIER2 pDDDI, DWORD dwFlags) PURE; /* added in v7 */ /*70*/ STDMETHOD(StartModeTest)(THIS_ LPSIZE pModes, DWORD dwNumModes, DWORD dwFlags) PURE; /*74*/ STDMETHOD(EvaluateMode)(THIS_ DWORD dwFlags, DWORD *pTimeout) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDraw7_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDraw7_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDraw7_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDraw methods ***/ #define IDirectDraw7_Compact(p) (p)->lpVtbl->Compact(p) #define IDirectDraw7_CreateClipper(p,a,b,c) (p)->lpVtbl->CreateClipper(p,a,b,c) #define IDirectDraw7_CreatePalette(p,a,b,c,d) (p)->lpVtbl->CreatePalette(p,a,b,c,d) #define IDirectDraw7_CreateSurface(p,a,b,c) (p)->lpVtbl->CreateSurface(p,a,b,c) #define IDirectDraw7_DuplicateSurface(p,a,b) (p)->lpVtbl->DuplicateSurface(p,a,b) #define IDirectDraw7_EnumDisplayModes(p,a,b,c,d) (p)->lpVtbl->EnumDisplayModes(p,a,b,c,d) #define IDirectDraw7_EnumSurfaces(p,a,b,c,d) (p)->lpVtbl->EnumSurfaces(p,a,b,c,d) #define IDirectDraw7_FlipToGDISurface(p) (p)->lpVtbl->FlipToGDISurface(p) #define IDirectDraw7_GetCaps(p,a,b) (p)->lpVtbl->GetCaps(p,a,b) #define IDirectDraw7_GetDisplayMode(p,a) (p)->lpVtbl->GetDisplayMode(p,a) #define IDirectDraw7_GetFourCCCodes(p,a,b) (p)->lpVtbl->GetFourCCCodes(p,a,b) #define IDirectDraw7_GetGDISurface(p,a) (p)->lpVtbl->GetGDISurface(p,a) #define IDirectDraw7_GetMonitorFrequency(p,a) (p)->lpVtbl->GetMonitorFrequency(p,a) #define IDirectDraw7_GetScanLine(p,a) (p)->lpVtbl->GetScanLine(p,a) #define IDirectDraw7_GetVerticalBlankStatus(p,a) (p)->lpVtbl->GetVerticalBlankStatus(p,a) #define IDirectDraw7_Initialize(p,a) (p)->lpVtbl->Initialize(p,a) #define IDirectDraw7_RestoreDisplayMode(p) (p)->lpVtbl->RestoreDisplayMode(p) #define IDirectDraw7_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) #define IDirectDraw7_SetDisplayMode(p,a,b,c,d,e) (p)->lpVtbl->SetDisplayMode(p,a,b,c,d,e) #define IDirectDraw7_WaitForVerticalBlank(p,a,b) (p)->lpVtbl->WaitForVerticalBlank(p,a,b) /*** added in IDirectDraw2 ***/ #define IDirectDraw7_GetAvailableVidMem(p,a,b,c) (p)->lpVtbl->GetAvailableVidMem(p,a,b,c) /*** added in IDirectDraw4 ***/ #define IDirectDraw7_GetSurfaceFromDC(p,a,b) (p)->lpVtbl->GetSurfaceFromDC(p,a,b) #define IDirectDraw7_RestoreAllSurfaces(p) (p)->lpVtbl->RestoreAllSurfaces(p) #define IDirectDraw7_TestCooperativeLevel(p) (p)->lpVtbl->TestCooperativeLevel(p) #define IDirectDraw7_GetDeviceIdentifier(p,a,b) (p)->lpVtbl->GetDeviceIdentifier(p,a,b) /*** added in IDirectDraw 7 ***/ #define IDirectDraw7_StartModeTest(p,a,b,c) (p)->lpVtbl->StartModeTest(p,a,b,c) #define IDirectDraw7_EvaluateMode(p,a,b) (p)->lpVtbl->EvaluateMode(p,a,b) #else /*** IUnknown methods ***/ #define IDirectDraw7_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDraw7_AddRef(p) (p)->AddRef() #define IDirectDraw7_Release(p) (p)->Release() /*** IDirectDraw methods ***/ #define IDirectDraw7_Compact(p) (p)->Compact() #define IDirectDraw7_CreateClipper(p,a,b,c) (p)->CreateClipper(a,b,c) #define IDirectDraw7_CreatePalette(p,a,b,c,d) (p)->CreatePalette(a,b,c,d) #define IDirectDraw7_CreateSurface(p,a,b,c) (p)->CreateSurface(a,b,c) #define IDirectDraw7_DuplicateSurface(p,a,b) (p)->DuplicateSurface(a,b) #define IDirectDraw7_EnumDisplayModes(p,a,b,c,d) (p)->EnumDisplayModes(a,b,c,d) #define IDirectDraw7_EnumSurfaces(p,a,b,c,d) (p)->EnumSurfaces(a,b,c,d) #define IDirectDraw7_FlipToGDISurface(p) (p)->FlipToGDISurface() #define IDirectDraw7_GetCaps(p,a,b) (p)->GetCaps(a,b) #define IDirectDraw7_GetDisplayMode(p,a) (p)->GetDisplayMode(a) #define IDirectDraw7_GetFourCCCodes(p,a,b) (p)->GetFourCCCodes(a,b) #define IDirectDraw7_GetGDISurface(p,a) (p)->GetGDISurface(a) #define IDirectDraw7_GetMonitorFrequency(p,a) (p)->GetMonitorFrequency(a) #define IDirectDraw7_GetScanLine(p,a) (p)->GetScanLine(a) #define IDirectDraw7_GetVerticalBlankStatus(p,a) (p)->GetVerticalBlankStatus(a) #define IDirectDraw7_Initialize(p,a) (p)->Initialize(a) #define IDirectDraw7_RestoreDisplayMode(p) (p)->RestoreDisplayMode() #define IDirectDraw7_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) #define IDirectDraw7_SetDisplayMode(p,a,b,c,d,e) (p)->SetDisplayMode(a,b,c,d,e) #define IDirectDraw7_WaitForVerticalBlank(p,a,b) (p)->WaitForVerticalBlank(a,b) /*** added in IDirectDraw2 ***/ #define IDirectDraw7_GetAvailableVidMem(p,a,b,c) (p)->GetAvailableVidMem(a,b,c) /*** added in IDirectDraw4 ***/ #define IDirectDraw7_GetSurfaceFromDC(p,a,b) (p)->GetSurfaceFromDC(a,b) #define IDirectDraw7_RestoreAllSurfaces(p) (p)->RestoreAllSurfaces() #define IDirectDraw7_TestCooperativeLevel(p) (p)->TestCooperativeLevel() #define IDirectDraw7_GetDeviceIdentifier(p,a,b) (p)->GetDeviceIdentifier(a,b) /*** added in IDirectDraw 7 ***/ #define IDirectDraw7_StartModeTest(p,a,b,c) (p)->StartModeTest(a,b,c) #define IDirectDraw7_EvaluateMode(p,a,b) (p)->EvaluateMode(a,b) #endif /***************************************************************************** * IDirectDrawSurface interface */ #define INTERFACE IDirectDrawSurface DECLARE_INTERFACE_(IDirectDrawSurface,IUnknown) { /*** IUnknown methods ***/ /*00*/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; /*04*/ STDMETHOD_(ULONG,AddRef)(THIS) PURE; /*08*/ STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDrawSurface methods ***/ /*0c*/ STDMETHOD(AddAttachedSurface)(THIS_ LPDIRECTDRAWSURFACE lpDDSAttachedSurface) PURE; /*10*/ STDMETHOD(AddOverlayDirtyRect)(THIS_ LPRECT lpRect) PURE; /*14*/ STDMETHOD(Blt)(THIS_ LPRECT lpDestRect, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) PURE; /*18*/ STDMETHOD(BltBatch)(THIS_ LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) PURE; /*1c*/ STDMETHOD(BltFast)(THIS_ DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) PURE; /*20*/ STDMETHOD(DeleteAttachedSurface)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE lpDDSAttachedSurface) PURE; /*24*/ STDMETHOD(EnumAttachedSurfaces)(THIS_ LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) PURE; /*28*/ STDMETHOD(EnumOverlayZOrders)(THIS_ DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback) PURE; /*2c*/ STDMETHOD(Flip)(THIS_ LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride, DWORD dwFlags) PURE; /*30*/ STDMETHOD(GetAttachedSurface)(THIS_ LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE *lplpDDAttachedSurface) PURE; /*34*/ STDMETHOD(GetBltStatus)(THIS_ DWORD dwFlags) PURE; /*38*/ STDMETHOD(GetCaps)(THIS_ LPDDSCAPS lpDDSCaps) PURE; /*3c*/ STDMETHOD(GetClipper)(THIS_ LPDIRECTDRAWCLIPPER *lplpDDClipper) PURE; /*40*/ STDMETHOD(GetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; /*44*/ STDMETHOD(GetDC)(THIS_ HDC *lphDC) PURE; /*48*/ STDMETHOD(GetFlipStatus)(THIS_ DWORD dwFlags) PURE; /*4c*/ STDMETHOD(GetOverlayPosition)(THIS_ LPLONG lplX, LPLONG lplY) PURE; /*50*/ STDMETHOD(GetPalette)(THIS_ LPDIRECTDRAWPALETTE *lplpDDPalette) PURE; /*54*/ STDMETHOD(GetPixelFormat)(THIS_ LPDDPIXELFORMAT lpDDPixelFormat) PURE; /*58*/ STDMETHOD(GetSurfaceDesc)(THIS_ LPDDSURFACEDESC lpDDSurfaceDesc) PURE; /*5c*/ STDMETHOD(Initialize)(THIS_ LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc) PURE; /*60*/ STDMETHOD(IsLost)(THIS) PURE; /*64*/ STDMETHOD(Lock)(THIS_ LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) PURE; /*68*/ STDMETHOD(ReleaseDC)(THIS_ HDC hDC) PURE; /*6c*/ STDMETHOD(Restore)(THIS) PURE; /*70*/ STDMETHOD(SetClipper)(THIS_ LPDIRECTDRAWCLIPPER lpDDClipper) PURE; /*74*/ STDMETHOD(SetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; /*78*/ STDMETHOD(SetOverlayPosition)(THIS_ LONG lX, LONG lY) PURE; /*7c*/ STDMETHOD(SetPalette)(THIS_ LPDIRECTDRAWPALETTE lpDDPalette) PURE; /*80*/ STDMETHOD(Unlock)(THIS_ LPVOID lpSurfaceData) PURE; /*84*/ STDMETHOD(UpdateOverlay)(THIS_ LPRECT lpSrcRect, LPDIRECTDRAWSURFACE lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) PURE; /*88*/ STDMETHOD(UpdateOverlayDisplay)(THIS_ DWORD dwFlags) PURE; /*8c*/ STDMETHOD(UpdateOverlayZOrder)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE lpDDSReference) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDrawSurface_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDrawSurface_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDrawSurface_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDrawSurface methods ***/ #define IDirectDrawSurface_AddAttachedSurface(p,a) (p)->lpVtbl->AddAttachedSurface(p,a) #define IDirectDrawSurface_AddOverlayDirtyRect(p,a) (p)->lpVtbl->AddOverlayDirtyRect(p,a) #define IDirectDrawSurface_Blt(p,a,b,c,d,e) (p)->lpVtbl->Blt(p,a,b,c,d,e) #define IDirectDrawSurface_BltBatch(p,a,b,c) (p)->lpVtbl->BltBatch(p,a,b,c) #define IDirectDrawSurface_BltFast(p,a,b,c,d,e) (p)->lpVtbl->BltFast(p,a,b,c,d,e) #define IDirectDrawSurface_DeleteAttachedSurface(p,a,b) (p)->lpVtbl->DeleteAttachedSurface(p,a,b) #define IDirectDrawSurface_EnumAttachedSurfaces(p,a,b) (p)->lpVtbl->EnumAttachedSurfaces(p,a,b) #define IDirectDrawSurface_EnumOverlayZOrders(p,a,b,c) (p)->lpVtbl->EnumOverlayZOrders(p,a,b,c) #define IDirectDrawSurface_Flip(p,a,b) (p)->lpVtbl->Flip(p,a,b) #define IDirectDrawSurface_GetAttachedSurface(p,a,b) (p)->lpVtbl->GetAttachedSurface(p,a,b) #define IDirectDrawSurface_GetBltStatus(p,a) (p)->lpVtbl->GetBltStatus(p,a) #define IDirectDrawSurface_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) #define IDirectDrawSurface_GetClipper(p,a) (p)->lpVtbl->GetClipper(p,a) #define IDirectDrawSurface_GetColorKey(p,a,b) (p)->lpVtbl->GetColorKey(p,a,b) #define IDirectDrawSurface_GetDC(p,a) (p)->lpVtbl->GetDC(p,a) #define IDirectDrawSurface_GetFlipStatus(p,a) (p)->lpVtbl->GetFlipStatus(p,a) #define IDirectDrawSurface_GetOverlayPosition(p,a,b) (p)->lpVtbl->GetOverlayPosition(p,a,b) #define IDirectDrawSurface_GetPalette(p,a) (p)->lpVtbl->GetPalette(p,a) #define IDirectDrawSurface_GetPixelFormat(p,a) (p)->lpVtbl->GetPixelFormat(p,a) #define IDirectDrawSurface_GetSurfaceDesc(p,a) (p)->lpVtbl->GetSurfaceDesc(p,a) #define IDirectDrawSurface_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) #define IDirectDrawSurface_IsLost(p) (p)->lpVtbl->IsLost(p) #define IDirectDrawSurface_Lock(p,a,b,c,d) (p)->lpVtbl->Lock(p,a,b,c,d) #define IDirectDrawSurface_ReleaseDC(p,a) (p)->lpVtbl->ReleaseDC(p,a) #define IDirectDrawSurface_Restore(p) (p)->lpVtbl->Restore(p) #define IDirectDrawSurface_SetClipper(p,a) (p)->lpVtbl->SetClipper(p,a) #define IDirectDrawSurface_SetColorKey(p,a,b) (p)->lpVtbl->SetColorKey(p,a,b) #define IDirectDrawSurface_SetOverlayPosition(p,a,b) (p)->lpVtbl->SetOverlayPosition(p,a,b) #define IDirectDrawSurface_SetPalette(p,a) (p)->lpVtbl->SetPalette(p,a) #define IDirectDrawSurface_Unlock(p,a) (p)->lpVtbl->Unlock(p,a) #define IDirectDrawSurface_UpdateOverlay(p,a,b,c,d,e) (p)->lpVtbl->UpdateOverlay(p,a,b,c,d,e) #define IDirectDrawSurface_UpdateOverlayDisplay(p,a) (p)->lpVtbl->UpdateOverlayDisplay(p,a) #define IDirectDrawSurface_UpdateOverlayZOrder(p,a,b) (p)->lpVtbl->UpdateOverlayZOrder(p,a,b) #else /*** IUnknown methods ***/ #define IDirectDrawSurface_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDrawSurface_AddRef(p) (p)->AddRef() #define IDirectDrawSurface_Release(p) (p)->Release() /*** IDirectDrawSurface methods ***/ #define IDirectDrawSurface_AddAttachedSurface(p,a) (p)->AddAttachedSurface(a) #define IDirectDrawSurface_AddOverlayDirtyRect(p,a) (p)->AddOverlayDirtyRect(a) #define IDirectDrawSurface_Blt(p,a,b,c,d,e) (p)->Blt(a,b,c,d,e) #define IDirectDrawSurface_BltBatch(p,a,b,c) (p)->BltBatch(a,b,c) #define IDirectDrawSurface_BltFast(p,a,b,c,d,e) (p)->BltFast(a,b,c,d,e) #define IDirectDrawSurface_DeleteAttachedSurface(p,a,b) (p)->DeleteAttachedSurface(a,b) #define IDirectDrawSurface_EnumAttachedSurfaces(p,a,b) (p)->EnumAttachedSurfaces(a,b) #define IDirectDrawSurface_EnumOverlayZOrders(p,a,b,c) (p)->EnumOverlayZOrders(a,b,c) #define IDirectDrawSurface_Flip(p,a,b) (p)->Flip(a,b) #define IDirectDrawSurface_GetAttachedSurface(p,a,b) (p)->GetAttachedSurface(a,b) #define IDirectDrawSurface_GetBltStatus(p,a) (p)->GetBltStatus(a) #define IDirectDrawSurface_GetCaps(p,a) (p)->GetCaps(a) #define IDirectDrawSurface_GetClipper(p,a) (p)->GetClipper(a) #define IDirectDrawSurface_GetColorKey(p,a,b) (p)->GetColorKey(a,b) #define IDirectDrawSurface_GetDC(p,a) (p)->GetDC(a) #define IDirectDrawSurface_GetFlipStatus(p,a) (p)->GetFlipStatus(a) #define IDirectDrawSurface_GetOverlayPosition(p,a,b) (p)->GetOverlayPosition(a,b) #define IDirectDrawSurface_GetPalette(p,a) (p)->GetPalette(a) #define IDirectDrawSurface_GetPixelFormat(p,a) (p)->GetPixelFormat(a) #define IDirectDrawSurface_GetSurfaceDesc(p,a) (p)->GetSurfaceDesc(a) #define IDirectDrawSurface_Initialize(p,a,b) (p)->Initialize(a,b) #define IDirectDrawSurface_IsLost(p) (p)->IsLost() #define IDirectDrawSurface_Lock(p,a,b,c,d) (p)->Lock(a,b,c,d) #define IDirectDrawSurface_ReleaseDC(p,a) (p)->ReleaseDC(a) #define IDirectDrawSurface_Restore(p) (p)->Restore() #define IDirectDrawSurface_SetClipper(p,a) (p)->SetClipper(a) #define IDirectDrawSurface_SetColorKey(p,a,b) (p)->SetColorKey(a,b) #define IDirectDrawSurface_SetOverlayPosition(p,a,b) (p)->SetOverlayPosition(a,b) #define IDirectDrawSurface_SetPalette(p,a) (p)->SetPalette(a) #define IDirectDrawSurface_Unlock(p,a) (p)->Unlock(a) #define IDirectDrawSurface_UpdateOverlay(p,a,b,c,d,e) (p)->UpdateOverlay(a,b,c,d,e) #define IDirectDrawSurface_UpdateOverlayDisplay(p,a) (p)->UpdateOverlayDisplay(a) #define IDirectDrawSurface_UpdateOverlayZOrder(p,a,b) (p)->UpdateOverlayZOrder(a,b) #endif /***************************************************************************** * IDirectDrawSurface2 interface */ /* Cannot inherit from IDirectDrawSurface because the LPDIRECTDRAWSURFACE parameters * have been converted to LPDIRECTDRAWSURFACE2. */ #define INTERFACE IDirectDrawSurface2 DECLARE_INTERFACE_(IDirectDrawSurface2,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDrawSurface2 methods ***/ STDMETHOD(AddAttachedSurface)(THIS_ LPDIRECTDRAWSURFACE2 lpDDSAttachedSurface) PURE; STDMETHOD(AddOverlayDirtyRect)(THIS_ LPRECT lpRect) PURE; STDMETHOD(Blt)(THIS_ LPRECT lpDestRect, LPDIRECTDRAWSURFACE2 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) PURE; STDMETHOD(BltBatch)(THIS_ LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) PURE; STDMETHOD(BltFast)(THIS_ DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE2 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) PURE; STDMETHOD(DeleteAttachedSurface)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE2 lpDDSAttachedSurface) PURE; STDMETHOD(EnumAttachedSurfaces)(THIS_ LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) PURE; STDMETHOD(EnumOverlayZOrders)(THIS_ DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback) PURE; STDMETHOD(Flip)(THIS_ LPDIRECTDRAWSURFACE2 lpDDSurfaceTargetOverride, DWORD dwFlags) PURE; STDMETHOD(GetAttachedSurface)(THIS_ LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE2 *lplpDDAttachedSurface) PURE; STDMETHOD(GetBltStatus)(THIS_ DWORD dwFlags) PURE; STDMETHOD(GetCaps)(THIS_ LPDDSCAPS lpDDSCaps) PURE; STDMETHOD(GetClipper)(THIS_ LPDIRECTDRAWCLIPPER *lplpDDClipper) PURE; STDMETHOD(GetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; STDMETHOD(GetDC)(THIS_ HDC *lphDC) PURE; STDMETHOD(GetFlipStatus)(THIS_ DWORD dwFlags) PURE; STDMETHOD(GetOverlayPosition)(THIS_ LPLONG lplX, LPLONG lplY) PURE; STDMETHOD(GetPalette)(THIS_ LPDIRECTDRAWPALETTE *lplpDDPalette) PURE; STDMETHOD(GetPixelFormat)(THIS_ LPDDPIXELFORMAT lpDDPixelFormat) PURE; STDMETHOD(GetSurfaceDesc)(THIS_ LPDDSURFACEDESC lpDDSurfaceDesc) PURE; STDMETHOD(Initialize)(THIS_ LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc) PURE; STDMETHOD(IsLost)(THIS) PURE; STDMETHOD(Lock)(THIS_ LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) PURE; STDMETHOD(ReleaseDC)(THIS_ HDC hDC) PURE; STDMETHOD(Restore)(THIS) PURE; STDMETHOD(SetClipper)(THIS_ LPDIRECTDRAWCLIPPER lpDDClipper) PURE; STDMETHOD(SetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; STDMETHOD(SetOverlayPosition)(THIS_ LONG lX, LONG lY) PURE; STDMETHOD(SetPalette)(THIS_ LPDIRECTDRAWPALETTE lpDDPalette) PURE; STDMETHOD(Unlock)(THIS_ LPVOID lpSurfaceData) PURE; STDMETHOD(UpdateOverlay)(THIS_ LPRECT lpSrcRect, LPDIRECTDRAWSURFACE2 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) PURE; STDMETHOD(UpdateOverlayDisplay)(THIS_ DWORD dwFlags) PURE; STDMETHOD(UpdateOverlayZOrder)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE2 lpDDSReference) PURE; /* added in v2 */ STDMETHOD(GetDDInterface)(THIS_ LPVOID *lplpDD) PURE; STDMETHOD(PageLock)(THIS_ DWORD dwFlags) PURE; STDMETHOD(PageUnlock)(THIS_ DWORD dwFlags) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDrawSurface2_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDrawSurface2_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDrawSurface2_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDrawSurface methods (almost) ***/ #define IDirectDrawSurface2_AddAttachedSurface(p,a) (p)->lpVtbl->AddAttachedSurface(p,a) #define IDirectDrawSurface2_AddOverlayDirtyRect(p,a) (p)->lpVtbl->AddOverlayDirtyRect(p,a) #define IDirectDrawSurface2_Blt(p,a,b,c,d,e) (p)->lpVtbl->Blt(p,a,b,c,d,e) #define IDirectDrawSurface2_BltBatch(p,a,b,c) (p)->lpVtbl->BltBatch(p,a,b,c) #define IDirectDrawSurface2_BltFast(p,a,b,c,d,e) (p)->lpVtbl->BltFast(p,a,b,c,d,e) #define IDirectDrawSurface2_DeleteAttachedSurface(p,a,b) (p)->lpVtbl->DeleteAttachedSurface(p,a,b) #define IDirectDrawSurface2_EnumAttachedSurfaces(p,a,b) (p)->lpVtbl->EnumAttachedSurfaces(p,a,b) #define IDirectDrawSurface2_EnumOverlayZOrders(p,a,b,c) (p)->lpVtbl->EnumOverlayZOrders(p,a,b,c) #define IDirectDrawSurface2_Flip(p,a,b) (p)->lpVtbl->Flip(p,a,b) #define IDirectDrawSurface2_GetAttachedSurface(p,a,b) (p)->lpVtbl->GetAttachedSurface(p,a,b) #define IDirectDrawSurface2_GetBltStatus(p,a) (p)->lpVtbl->GetBltStatus(p,a) #define IDirectDrawSurface2_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) #define IDirectDrawSurface2_GetClipper(p,a) (p)->lpVtbl->GetClipper(p,a) #define IDirectDrawSurface2_GetColorKey(p,a,b) (p)->lpVtbl->GetColorKey(p,a,b) #define IDirectDrawSurface2_GetDC(p,a) (p)->lpVtbl->GetDC(p,a) #define IDirectDrawSurface2_GetFlipStatus(p,a) (p)->lpVtbl->GetFlipStatus(p,a) #define IDirectDrawSurface2_GetOverlayPosition(p,a,b) (p)->lpVtbl->GetOverlayPosition(p,a,b) #define IDirectDrawSurface2_GetPalette(p,a) (p)->lpVtbl->GetPalette(p,a) #define IDirectDrawSurface2_GetPixelFormat(p,a) (p)->lpVtbl->GetPixelFormat(p,a) #define IDirectDrawSurface2_GetSurfaceDesc(p,a) (p)->lpVtbl->GetSurfaceDesc(p,a) #define IDirectDrawSurface2_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) #define IDirectDrawSurface2_IsLost(p) (p)->lpVtbl->IsLost(p) #define IDirectDrawSurface2_Lock(p,a,b,c,d) (p)->lpVtbl->Lock(p,a,b,c,d) #define IDirectDrawSurface2_ReleaseDC(p,a) (p)->lpVtbl->ReleaseDC(p,a) #define IDirectDrawSurface2_Restore(p) (p)->lpVtbl->Restore(p) #define IDirectDrawSurface2_SetClipper(p,a) (p)->lpVtbl->SetClipper(p,a) #define IDirectDrawSurface2_SetColorKey(p,a,b) (p)->lpVtbl->SetColorKey(p,a,b) #define IDirectDrawSurface2_SetOverlayPosition(p,a,b) (p)->lpVtbl->SetOverlayPosition(p,a,b) #define IDirectDrawSurface2_SetPalette(p,a) (p)->lpVtbl->SetPalette(p,a) #define IDirectDrawSurface2_Unlock(p,a) (p)->lpVtbl->Unlock(p,a) #define IDirectDrawSurface2_UpdateOverlay(p,a,b,c,d,e) (p)->lpVtbl->UpdateOverlay(p,a,b,c,d,e) #define IDirectDrawSurface2_UpdateOverlayDisplay(p,a) (p)->lpVtbl->UpdateOverlayDisplay(p,a) #define IDirectDrawSurface2_UpdateOverlayZOrder(p,a,b) (p)->lpVtbl->UpdateOverlayZOrder(p,a,b) /*** IDirectDrawSurface2 methods ***/ #define IDirectDrawSurface2_GetDDInterface(p,a) (p)->lpVtbl->GetDDInterface(p,a) #define IDirectDrawSurface2_PageLock(p,a) (p)->lpVtbl->PageLock(p,a) #define IDirectDrawSurface2_PageUnlock(p,a) (p)->lpVtbl->PageUnlock(p,a) #else /*** IUnknown methods ***/ #define IDirectDrawSurface2_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDrawSurface2_AddRef(p) (p)->AddRef() #define IDirectDrawSurface2_Release(p) (p)->Release() /*** IDirectDrawSurface methods (almost) ***/ #define IDirectDrawSurface2_AddAttachedSurface(p,a) (p)->AddAttachedSurface(a) #define IDirectDrawSurface2_AddOverlayDirtyRect(p,a) (p)->AddOverlayDirtyRect(a) #define IDirectDrawSurface2_Blt(p,a,b,c,d,e) (p)->Blt(a,b,c,d,e) #define IDirectDrawSurface2_BltBatch(p,a,b,c) (p)->BltBatch(a,b,c) #define IDirectDrawSurface2_BltFast(p,a,b,c,d,e) (p)->BltFast(a,b,c,d,e) #define IDirectDrawSurface2_DeleteAttachedSurface(p,a,b) (p)->DeleteAttachedSurface(a,b) #define IDirectDrawSurface2_EnumAttachedSurfaces(p,a,b) (p)->EnumAttachedSurfaces(a,b) #define IDirectDrawSurface2_EnumOverlayZOrders(p,a,b,c) (p)->EnumOverlayZOrders(a,b,c) #define IDirectDrawSurface2_Flip(p,a,b) (p)->Flip(a,b) #define IDirectDrawSurface2_GetAttachedSurface(p,a,b) (p)->GetAttachedSurface(a,b) #define IDirectDrawSurface2_GetBltStatus(p,a) (p)->GetBltStatus(a) #define IDirectDrawSurface2_GetCaps(p,a) (p)->GetCaps(a) #define IDirectDrawSurface2_GetClipper(p,a) (p)->GetClipper(a) #define IDirectDrawSurface2_GetColorKey(p,a,b) (p)->GetColorKey(a,b) #define IDirectDrawSurface2_GetDC(p,a) (p)->GetDC(a) #define IDirectDrawSurface2_GetFlipStatus(p,a) (p)->GetFlipStatus(a) #define IDirectDrawSurface2_GetOverlayPosition(p,a,b) (p)->GetOverlayPosition(a,b) #define IDirectDrawSurface2_GetPalette(p,a) (p)->GetPalette(a) #define IDirectDrawSurface2_GetPixelFormat(p,a) (p)->GetPixelFormat(a) #define IDirectDrawSurface2_GetSurfaceDesc(p,a) (p)->GetSurfaceDesc(a) #define IDirectDrawSurface2_Initialize(p,a,b) (p)->Initialize(a,b) #define IDirectDrawSurface2_IsLost(p) (p)->IsLost() #define IDirectDrawSurface2_Lock(p,a,b,c,d) (p)->Lock(a,b,c,d) #define IDirectDrawSurface2_ReleaseDC(p,a) (p)->ReleaseDC(a) #define IDirectDrawSurface2_Restore(p) (p)->Restore() #define IDirectDrawSurface2_SetClipper(p,a) (p)->SetClipper(a) #define IDirectDrawSurface2_SetColorKey(p,a,b) (p)->SetColorKey(a,b) #define IDirectDrawSurface2_SetOverlayPosition(p,a,b) (p)->SetOverlayPosition(a,b) #define IDirectDrawSurface2_SetPalette(p,a) (p)->SetPalette(a) #define IDirectDrawSurface2_Unlock(p,a) (p)->Unlock(a) #define IDirectDrawSurface2_UpdateOverlay(p,a,b,c,d,e) (p)->UpdateOverlay(a,b,c,d,e) #define IDirectDrawSurface2_UpdateOverlayDisplay(p,a) (p)->UpdateOverlayDisplay(a) #define IDirectDrawSurface2_UpdateOverlayZOrder(p,a,b) (p)->UpdateOverlayZOrder(a,b) /*** IDirectDrawSurface2 methods ***/ #define IDirectDrawSurface2_GetDDInterface(p,a) (p)->GetDDInterface(a) #define IDirectDrawSurface2_PageLock(p,a) (p)->PageLock(a) #define IDirectDrawSurface2_PageUnlock(p,a) (p)->PageUnlock(a) #endif /***************************************************************************** * IDirectDrawSurface3 interface */ /* Cannot inherit from IDirectDrawSurface2 because the LPDIRECTDRAWSURFACE2 parameters * have been converted to LPDIRECTDRAWSURFACE3. */ #define INTERFACE IDirectDrawSurface3 DECLARE_INTERFACE_(IDirectDrawSurface3,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDrawSurface3 methods ***/ STDMETHOD(AddAttachedSurface)(THIS_ LPDIRECTDRAWSURFACE3 lpDDSAttachedSurface) PURE; STDMETHOD(AddOverlayDirtyRect)(THIS_ LPRECT lpRect) PURE; STDMETHOD(Blt)(THIS_ LPRECT lpDestRect, LPDIRECTDRAWSURFACE3 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) PURE; STDMETHOD(BltBatch)(THIS_ LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) PURE; STDMETHOD(BltFast)(THIS_ DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE3 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) PURE; STDMETHOD(DeleteAttachedSurface)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE3 lpDDSAttachedSurface) PURE; STDMETHOD(EnumAttachedSurfaces)(THIS_ LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) PURE; STDMETHOD(EnumOverlayZOrders)(THIS_ DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback) PURE; STDMETHOD(Flip)(THIS_ LPDIRECTDRAWSURFACE3 lpDDSurfaceTargetOverride, DWORD dwFlags) PURE; STDMETHOD(GetAttachedSurface)(THIS_ LPDDSCAPS lpDDSCaps, LPDIRECTDRAWSURFACE3 *lplpDDAttachedSurface) PURE; STDMETHOD(GetBltStatus)(THIS_ DWORD dwFlags) PURE; STDMETHOD(GetCaps)(THIS_ LPDDSCAPS lpDDSCaps) PURE; STDMETHOD(GetClipper)(THIS_ LPDIRECTDRAWCLIPPER *lplpDDClipper) PURE; STDMETHOD(GetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; STDMETHOD(GetDC)(THIS_ HDC *lphDC) PURE; STDMETHOD(GetFlipStatus)(THIS_ DWORD dwFlags) PURE; STDMETHOD(GetOverlayPosition)(THIS_ LPLONG lplX, LPLONG lplY) PURE; STDMETHOD(GetPalette)(THIS_ LPDIRECTDRAWPALETTE *lplpDDPalette) PURE; STDMETHOD(GetPixelFormat)(THIS_ LPDDPIXELFORMAT lpDDPixelFormat) PURE; STDMETHOD(GetSurfaceDesc)(THIS_ LPDDSURFACEDESC lpDDSurfaceDesc) PURE; STDMETHOD(Initialize)(THIS_ LPDIRECTDRAW lpDD, LPDDSURFACEDESC lpDDSurfaceDesc) PURE; STDMETHOD(IsLost)(THIS) PURE; STDMETHOD(Lock)(THIS_ LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) PURE; STDMETHOD(ReleaseDC)(THIS_ HDC hDC) PURE; STDMETHOD(Restore)(THIS) PURE; STDMETHOD(SetClipper)(THIS_ LPDIRECTDRAWCLIPPER lpDDClipper) PURE; STDMETHOD(SetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; STDMETHOD(SetOverlayPosition)(THIS_ LONG lX, LONG lY) PURE; STDMETHOD(SetPalette)(THIS_ LPDIRECTDRAWPALETTE lpDDPalette) PURE; STDMETHOD(Unlock)(THIS_ LPVOID lpSurfaceData) PURE; STDMETHOD(UpdateOverlay)(THIS_ LPRECT lpSrcRect, LPDIRECTDRAWSURFACE3 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) PURE; STDMETHOD(UpdateOverlayDisplay)(THIS_ DWORD dwFlags) PURE; STDMETHOD(UpdateOverlayZOrder)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE3 lpDDSReference) PURE; /* added in v2 */ STDMETHOD(GetDDInterface)(THIS_ LPVOID *lplpDD) PURE; STDMETHOD(PageLock)(THIS_ DWORD dwFlags) PURE; STDMETHOD(PageUnlock)(THIS_ DWORD dwFlags) PURE; /* added in v3 */ STDMETHOD(SetSurfaceDesc)(THIS_ LPDDSURFACEDESC lpDDSD, DWORD dwFlags) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDrawSurface3_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDrawSurface3_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDrawSurface3_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDrawSurface methods (almost) ***/ #define IDirectDrawSurface3_AddAttachedSurface(p,a) (p)->lpVtbl->AddAttachedSurface(p,a) #define IDirectDrawSurface3_AddOverlayDirtyRect(p,a) (p)->lpVtbl->AddOverlayDirtyRect(p,a) #define IDirectDrawSurface3_Blt(p,a,b,c,d,e) (p)->lpVtbl->Blt(p,a,b,c,d,e) #define IDirectDrawSurface3_BltBatch(p,a,b,c) (p)->lpVtbl->BltBatch(p,a,b,c) #define IDirectDrawSurface3_BltFast(p,a,b,c,d,e) (p)->lpVtbl->BltFast(p,a,b,c,d,e) #define IDirectDrawSurface3_DeleteAttachedSurface(p,a,b) (p)->lpVtbl->DeleteAttachedSurface(p,a,b) #define IDirectDrawSurface3_EnumAttachedSurfaces(p,a,b) (p)->lpVtbl->EnumAttachedSurfaces(p,a,b) #define IDirectDrawSurface3_EnumOverlayZOrders(p,a,b,c) (p)->lpVtbl->EnumOverlayZOrders(p,a,b,c) #define IDirectDrawSurface3_Flip(p,a,b) (p)->lpVtbl->Flip(p,a,b) #define IDirectDrawSurface3_GetAttachedSurface(p,a,b) (p)->lpVtbl->GetAttachedSurface(p,a,b) #define IDirectDrawSurface3_GetBltStatus(p,a) (p)->lpVtbl->GetBltStatus(p,a) #define IDirectDrawSurface3_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) #define IDirectDrawSurface3_GetClipper(p,a) (p)->lpVtbl->GetClipper(p,a) #define IDirectDrawSurface3_GetColorKey(p,a,b) (p)->lpVtbl->GetColorKey(p,a,b) #define IDirectDrawSurface3_GetDC(p,a) (p)->lpVtbl->GetDC(p,a) #define IDirectDrawSurface3_GetFlipStatus(p,a) (p)->lpVtbl->GetFlipStatus(p,a) #define IDirectDrawSurface3_GetOverlayPosition(p,a,b) (p)->lpVtbl->GetOverlayPosition(p,a,b) #define IDirectDrawSurface3_GetPalette(p,a) (p)->lpVtbl->GetPalette(p,a) #define IDirectDrawSurface3_GetPixelFormat(p,a) (p)->lpVtbl->GetPixelFormat(p,a) #define IDirectDrawSurface3_GetSurfaceDesc(p,a) (p)->lpVtbl->GetSurfaceDesc(p,a) #define IDirectDrawSurface3_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) #define IDirectDrawSurface3_IsLost(p) (p)->lpVtbl->IsLost(p) #define IDirectDrawSurface3_Lock(p,a,b,c,d) (p)->lpVtbl->Lock(p,a,b,c,d) #define IDirectDrawSurface3_ReleaseDC(p,a) (p)->lpVtbl->ReleaseDC(p,a) #define IDirectDrawSurface3_Restore(p) (p)->lpVtbl->Restore(p) #define IDirectDrawSurface3_SetClipper(p,a) (p)->lpVtbl->SetClipper(p,a) #define IDirectDrawSurface3_SetColorKey(p,a,b) (p)->lpVtbl->SetColorKey(p,a,b) #define IDirectDrawSurface3_SetOverlayPosition(p,a,b) (p)->lpVtbl->SetOverlayPosition(p,a,b) #define IDirectDrawSurface3_SetPalette(p,a) (p)->lpVtbl->SetPalette(p,a) #define IDirectDrawSurface3_Unlock(p,a) (p)->lpVtbl->Unlock(p,a) #define IDirectDrawSurface3_UpdateOverlay(p,a,b,c,d,e) (p)->lpVtbl->UpdateOverlay(p,a,b,c,d,e) #define IDirectDrawSurface3_UpdateOverlayDisplay(p,a) (p)->lpVtbl->UpdateOverlayDisplay(p,a) #define IDirectDrawSurface3_UpdateOverlayZOrder(p,a,b) (p)->lpVtbl->UpdateOverlayZOrder(p,a,b) /*** IDirectDrawSurface2 methods ***/ #define IDirectDrawSurface3_GetDDInterface(p,a) (p)->lpVtbl->GetDDInterface(p,a) #define IDirectDrawSurface3_PageLock(p,a) (p)->lpVtbl->PageLock(p,a) #define IDirectDrawSurface3_PageUnlock(p,a) (p)->lpVtbl->PageUnlock(p,a) /*** IDirectDrawSurface3 methods ***/ #define IDirectDrawSurface3_SetSurfaceDesc(p,a,b) (p)->lpVtbl->SetSurfaceDesc(p,a,b) #else /*** IUnknown methods ***/ #define IDirectDrawSurface3_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDrawSurface3_AddRef(p) (p)->AddRef() #define IDirectDrawSurface3_Release(p) (p)->Release() /*** IDirectDrawSurface methods (almost) ***/ #define IDirectDrawSurface3_AddAttachedSurface(p,a) (p)->AddAttachedSurface(a) #define IDirectDrawSurface3_AddOverlayDirtyRect(p,a) (p)->AddOverlayDirtyRect(a) #define IDirectDrawSurface3_Blt(p,a,b,c,d,e) (p)->Blt(a,b,c,d,e) #define IDirectDrawSurface3_BltBatch(p,a,b,c) (p)->BltBatch(a,b,c) #define IDirectDrawSurface3_BltFast(p,a,b,c,d,e) (p)->BltFast(a,b,c,d,e) #define IDirectDrawSurface3_DeleteAttachedSurface(p,a,b) (p)->DeleteAttachedSurface(a,b) #define IDirectDrawSurface3_EnumAttachedSurfaces(p,a,b) (p)->EnumAttachedSurfaces(a,b) #define IDirectDrawSurface3_EnumOverlayZOrders(p,a,b,c) (p)->EnumOverlayZOrders(a,b,c) #define IDirectDrawSurface3_Flip(p,a,b) (p)->Flip(a,b) #define IDirectDrawSurface3_GetAttachedSurface(p,a,b) (p)->GetAttachedSurface(a,b) #define IDirectDrawSurface3_GetBltStatus(p,a) (p)->GetBltStatus(a) #define IDirectDrawSurface3_GetCaps(p,a) (p)->GetCaps(a) #define IDirectDrawSurface3_GetClipper(p,a) (p)->GetClipper(a) #define IDirectDrawSurface3_GetColorKey(p,a,b) (p)->GetColorKey(a,b) #define IDirectDrawSurface3_GetDC(p,a) (p)->GetDC(a) #define IDirectDrawSurface3_GetFlipStatus(p,a) (p)->GetFlipStatus(a) #define IDirectDrawSurface3_GetOverlayPosition(p,a,b) (p)->GetOverlayPosition(a,b) #define IDirectDrawSurface3_GetPalette(p,a) (p)->GetPalette(a) #define IDirectDrawSurface3_GetPixelFormat(p,a) (p)->GetPixelFormat(a) #define IDirectDrawSurface3_GetSurfaceDesc(p,a) (p)->GetSurfaceDesc(a) #define IDirectDrawSurface3_Initialize(p,a,b) (p)->Initialize(a,b) #define IDirectDrawSurface3_IsLost(p) (p)->IsLost() #define IDirectDrawSurface3_Lock(p,a,b,c,d) (p)->Lock(a,b,c,d) #define IDirectDrawSurface3_ReleaseDC(p,a) (p)->ReleaseDC(a) #define IDirectDrawSurface3_Restore(p) (p)->Restore() #define IDirectDrawSurface3_SetClipper(p,a) (p)->SetClipper(a) #define IDirectDrawSurface3_SetColorKey(p,a,b) (p)->SetColorKey(a,b) #define IDirectDrawSurface3_SetOverlayPosition(p,a,b) (p)->SetOverlayPosition(a,b) #define IDirectDrawSurface3_SetPalette(p,a) (p)->SetPalette(a) #define IDirectDrawSurface3_Unlock(p,a) (p)->Unlock(a) #define IDirectDrawSurface3_UpdateOverlay(p,a,b,c,d,e) (p)->UpdateOverlay(a,b,c,d,e) #define IDirectDrawSurface3_UpdateOverlayDisplay(p,a) (p)->UpdateOverlayDisplay(a) #define IDirectDrawSurface3_UpdateOverlayZOrder(p,a,b) (p)->UpdateOverlayZOrder(a,b) /*** IDirectDrawSurface2 methods ***/ #define IDirectDrawSurface3_GetDDInterface(p,a) (p)->GetDDInterface(a) #define IDirectDrawSurface3_PageLock(p,a) (p)->PageLock(a) #define IDirectDrawSurface3_PageUnlock(p,a) (p)->PageUnlock(a) /*** IDirectDrawSurface3 methods ***/ #define IDirectDrawSurface3_SetSurfaceDesc(p,a,b) (p)->SetSurfaceDesc(a,b) #endif /***************************************************************************** * IDirectDrawSurface4 interface */ /* Cannot inherit from IDirectDrawSurface2 because DDSCAPS changed to DDSCAPS2. */ #define INTERFACE IDirectDrawSurface4 DECLARE_INTERFACE_(IDirectDrawSurface4,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDrawSurface4 methods ***/ STDMETHOD(AddAttachedSurface)(THIS_ LPDIRECTDRAWSURFACE4 lpDDSAttachedSurface) PURE; STDMETHOD(AddOverlayDirtyRect)(THIS_ LPRECT lpRect) PURE; STDMETHOD(Blt)(THIS_ LPRECT lpDestRect, LPDIRECTDRAWSURFACE4 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) PURE; STDMETHOD(BltBatch)(THIS_ LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) PURE; STDMETHOD(BltFast)(THIS_ DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE4 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) PURE; STDMETHOD(DeleteAttachedSurface)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE4 lpDDSAttachedSurface) PURE; STDMETHOD(EnumAttachedSurfaces)(THIS_ LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpEnumSurfacesCallback) PURE; STDMETHOD(EnumOverlayZOrders)(THIS_ DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK lpfnCallback) PURE; STDMETHOD(Flip)(THIS_ LPDIRECTDRAWSURFACE4 lpDDSurfaceTargetOverride, DWORD dwFlags) PURE; STDMETHOD(GetAttachedSurface)(THIS_ LPDDSCAPS2 lpDDSCaps, LPDIRECTDRAWSURFACE4 *lplpDDAttachedSurface) PURE; STDMETHOD(GetBltStatus)(THIS_ DWORD dwFlags) PURE; STDMETHOD(GetCaps)(THIS_ LPDDSCAPS2 lpDDSCaps) PURE; STDMETHOD(GetClipper)(THIS_ LPDIRECTDRAWCLIPPER *lplpDDClipper) PURE; STDMETHOD(GetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; STDMETHOD(GetDC)(THIS_ HDC *lphDC) PURE; STDMETHOD(GetFlipStatus)(THIS_ DWORD dwFlags) PURE; STDMETHOD(GetOverlayPosition)(THIS_ LPLONG lplX, LPLONG lplY) PURE; STDMETHOD(GetPalette)(THIS_ LPDIRECTDRAWPALETTE *lplpDDPalette) PURE; STDMETHOD(GetPixelFormat)(THIS_ LPDDPIXELFORMAT lpDDPixelFormat) PURE; STDMETHOD(GetSurfaceDesc)(THIS_ LPDDSURFACEDESC2 lpDDSurfaceDesc) PURE; STDMETHOD(Initialize)(THIS_ LPDIRECTDRAW lpDD, LPDDSURFACEDESC2 lpDDSurfaceDesc) PURE; STDMETHOD(IsLost)(THIS) PURE; STDMETHOD(Lock)(THIS_ LPRECT lpDestRect, LPDDSURFACEDESC2 lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) PURE; STDMETHOD(ReleaseDC)(THIS_ HDC hDC) PURE; STDMETHOD(Restore)(THIS) PURE; STDMETHOD(SetClipper)(THIS_ LPDIRECTDRAWCLIPPER lpDDClipper) PURE; STDMETHOD(SetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; STDMETHOD(SetOverlayPosition)(THIS_ LONG lX, LONG lY) PURE; STDMETHOD(SetPalette)(THIS_ LPDIRECTDRAWPALETTE lpDDPalette) PURE; STDMETHOD(Unlock)(THIS_ LPRECT lpSurfaceData) PURE; STDMETHOD(UpdateOverlay)(THIS_ LPRECT lpSrcRect, LPDIRECTDRAWSURFACE4 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) PURE; STDMETHOD(UpdateOverlayDisplay)(THIS_ DWORD dwFlags) PURE; STDMETHOD(UpdateOverlayZOrder)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE4 lpDDSReference) PURE; /* added in v2 */ STDMETHOD(GetDDInterface)(THIS_ LPVOID *lplpDD) PURE; STDMETHOD(PageLock)(THIS_ DWORD dwFlags) PURE; STDMETHOD(PageUnlock)(THIS_ DWORD dwFlags) PURE; /* added in v3 */ STDMETHOD(SetSurfaceDesc)(THIS_ LPDDSURFACEDESC2 lpDDSD, DWORD dwFlags) PURE; /* added in v4 */ STDMETHOD(SetPrivateData)(THIS_ REFGUID tag, LPVOID pData, DWORD cbSize, DWORD dwFlags) PURE; STDMETHOD(GetPrivateData)(THIS_ REFGUID tag, LPVOID pBuffer, LPDWORD pcbBufferSize) PURE; STDMETHOD(FreePrivateData)(THIS_ REFGUID tag) PURE; STDMETHOD(GetUniquenessValue)(THIS_ LPDWORD pValue) PURE; STDMETHOD(ChangeUniquenessValue)(THIS) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDrawSurface4_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDrawSurface4_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDrawSurface4_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDrawSurface (almost) methods ***/ #define IDirectDrawSurface4_AddAttachedSurface(p,a) (p)->lpVtbl->AddAttachedSurface(p,a) #define IDirectDrawSurface4_AddOverlayDirtyRect(p,a) (p)->lpVtbl->AddOverlayDirtyRect(p,a) #define IDirectDrawSurface4_Blt(p,a,b,c,d,e) (p)->lpVtbl->Blt(p,a,b,c,d,e) #define IDirectDrawSurface4_BltBatch(p,a,b,c) (p)->lpVtbl->BltBatch(p,a,b,c) #define IDirectDrawSurface4_BltFast(p,a,b,c,d,e) (p)->lpVtbl->BltFast(p,a,b,c,d,e) #define IDirectDrawSurface4_DeleteAttachedSurface(p,a,b) (p)->lpVtbl->DeleteAttachedSurface(p,a,b) #define IDirectDrawSurface4_EnumAttachedSurfaces(p,a,b) (p)->lpVtbl->EnumAttachedSurfaces(p,a,b) #define IDirectDrawSurface4_EnumOverlayZOrders(p,a,b,c) (p)->lpVtbl->EnumOverlayZOrders(p,a,b,c) #define IDirectDrawSurface4_Flip(p,a,b) (p)->lpVtbl->Flip(p,a,b) #define IDirectDrawSurface4_GetAttachedSurface(p,a,b) (p)->lpVtbl->GetAttachedSurface(p,a,b) #define IDirectDrawSurface4_GetBltStatus(p,a) (p)->lpVtbl->GetBltStatus(p,a) #define IDirectDrawSurface4_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) #define IDirectDrawSurface4_GetClipper(p,a) (p)->lpVtbl->GetClipper(p,a) #define IDirectDrawSurface4_GetColorKey(p,a,b) (p)->lpVtbl->GetColorKey(p,a,b) #define IDirectDrawSurface4_GetDC(p,a) (p)->lpVtbl->GetDC(p,a) #define IDirectDrawSurface4_GetFlipStatus(p,a) (p)->lpVtbl->GetFlipStatus(p,a) #define IDirectDrawSurface4_GetOverlayPosition(p,a,b) (p)->lpVtbl->GetOverlayPosition(p,a,b) #define IDirectDrawSurface4_GetPalette(p,a) (p)->lpVtbl->GetPalette(p,a) #define IDirectDrawSurface4_GetPixelFormat(p,a) (p)->lpVtbl->GetPixelFormat(p,a) #define IDirectDrawSurface4_GetSurfaceDesc(p,a) (p)->lpVtbl->GetSurfaceDesc(p,a) #define IDirectDrawSurface4_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) #define IDirectDrawSurface4_IsLost(p) (p)->lpVtbl->IsLost(p) #define IDirectDrawSurface4_Lock(p,a,b,c,d) (p)->lpVtbl->Lock(p,a,b,c,d) #define IDirectDrawSurface4_ReleaseDC(p,a) (p)->lpVtbl->ReleaseDC(p,a) #define IDirectDrawSurface4_Restore(p) (p)->lpVtbl->Restore(p) #define IDirectDrawSurface4_SetClipper(p,a) (p)->lpVtbl->SetClipper(p,a) #define IDirectDrawSurface4_SetColorKey(p,a,b) (p)->lpVtbl->SetColorKey(p,a,b) #define IDirectDrawSurface4_SetOverlayPosition(p,a,b) (p)->lpVtbl->SetOverlayPosition(p,a,b) #define IDirectDrawSurface4_SetPalette(p,a) (p)->lpVtbl->SetPalette(p,a) #define IDirectDrawSurface4_Unlock(p,a) (p)->lpVtbl->Unlock(p,a) #define IDirectDrawSurface4_UpdateOverlay(p,a,b,c,d,e) (p)->lpVtbl->UpdateOverlay(p,a,b,c,d,e) #define IDirectDrawSurface4_UpdateOverlayDisplay(p,a) (p)->lpVtbl->UpdateOverlayDisplay(p,a) #define IDirectDrawSurface4_UpdateOverlayZOrder(p,a,b) (p)->lpVtbl->UpdateOverlayZOrder(p,a,b) /*** IDirectDrawSurface2 methods ***/ #define IDirectDrawSurface4_GetDDInterface(p,a) (p)->lpVtbl->GetDDInterface(p,a) #define IDirectDrawSurface4_PageLock(p,a) (p)->lpVtbl->PageLock(p,a) #define IDirectDrawSurface4_PageUnlock(p,a) (p)->lpVtbl->PageUnlock(p,a) /*** IDirectDrawSurface3 methods ***/ #define IDirectDrawSurface4_SetSurfaceDesc(p,a,b) (p)->lpVtbl->SetSurfaceDesc(p,a,b) /*** IDirectDrawSurface4 methods ***/ #define IDirectDrawSurface4_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) #define IDirectDrawSurface4_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) #define IDirectDrawSurface4_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) #define IDirectDrawSurface4_GetUniquenessValue(p,a) (p)->lpVtbl->GetUniquenessValue(p,a) #define IDirectDrawSurface4_ChangeUniquenessValue(p) (p)->lpVtbl->ChangeUniquenessValue(p) #else /*** IUnknown methods ***/ #define IDirectDrawSurface4_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDrawSurface4_AddRef(p) (p)->AddRef() #define IDirectDrawSurface4_Release(p) (p)->Release() /*** IDirectDrawSurface (almost) methods ***/ #define IDirectDrawSurface4_AddAttachedSurface(p,a) (p)->AddAttachedSurface(a) #define IDirectDrawSurface4_AddOverlayDirtyRect(p,a) (p)->AddOverlayDirtyRect(a) #define IDirectDrawSurface4_Blt(p,a,b,c,d,e) (p)->Blt(a,b,c,d,e) #define IDirectDrawSurface4_BltBatch(p,a,b,c) (p)->BltBatch(a,b,c) #define IDirectDrawSurface4_BltFast(p,a,b,c,d,e) (p)->BltFast(a,b,c,d,e) #define IDirectDrawSurface4_DeleteAttachedSurface(p,a,b) (p)->DeleteAttachedSurface(a,b) #define IDirectDrawSurface4_EnumAttachedSurfaces(p,a,b) (p)->EnumAttachedSurfaces(a,b) #define IDirectDrawSurface4_EnumOverlayZOrders(p,a,b,c) (p)->EnumOverlayZOrders(a,b,c) #define IDirectDrawSurface4_Flip(p,a,b) (p)->Flip(a,b) #define IDirectDrawSurface4_GetAttachedSurface(p,a,b) (p)->GetAttachedSurface(a,b) #define IDirectDrawSurface4_GetBltStatus(p,a) (p)->GetBltStatus(a) #define IDirectDrawSurface4_GetCaps(p,a) (p)->GetCaps(a) #define IDirectDrawSurface4_GetClipper(p,a) (p)->GetClipper(a) #define IDirectDrawSurface4_GetColorKey(p,a,b) (p)->GetColorKey(a,b) #define IDirectDrawSurface4_GetDC(p,a) (p)->GetDC(a) #define IDirectDrawSurface4_GetFlipStatus(p,a) (p)->GetFlipStatus(a) #define IDirectDrawSurface4_GetOverlayPosition(p,a,b) (p)->GetOverlayPosition(a,b) #define IDirectDrawSurface4_GetPalette(p,a) (p)->GetPalette(a) #define IDirectDrawSurface4_GetPixelFormat(p,a) (p)->GetPixelFormat(a) #define IDirectDrawSurface4_GetSurfaceDesc(p,a) (p)->GetSurfaceDesc(a) #define IDirectDrawSurface4_Initialize(p,a,b) (p)->Initialize(a,b) #define IDirectDrawSurface4_IsLost(p) (p)->IsLost() #define IDirectDrawSurface4_Lock(p,a,b,c,d) (p)->Lock(a,b,c,d) #define IDirectDrawSurface4_ReleaseDC(p,a) (p)->ReleaseDC(a) #define IDirectDrawSurface4_Restore(p) (p)->Restore() #define IDirectDrawSurface4_SetClipper(p,a) (p)->SetClipper(a) #define IDirectDrawSurface4_SetColorKey(p,a,b) (p)->SetColorKey(a,b) #define IDirectDrawSurface4_SetOverlayPosition(p,a,b) (p)->SetOverlayPosition(a,b) #define IDirectDrawSurface4_SetPalette(p,a) (p)->SetPalette(a) #define IDirectDrawSurface4_Unlock(p,a) (p)->Unlock(a) #define IDirectDrawSurface4_UpdateOverlay(p,a,b,c,d,e) (p)->UpdateOverlay(a,b,c,d,e) #define IDirectDrawSurface4_UpdateOverlayDisplay(p,a) (p)->UpdateOverlayDisplay(a) #define IDirectDrawSurface4_UpdateOverlayZOrder(p,a,b) (p)->UpdateOverlayZOrder(a,b) /*** IDirectDrawSurface2 methods ***/ #define IDirectDrawSurface4_GetDDInterface(p,a) (p)->GetDDInterface(a) #define IDirectDrawSurface4_PageLock(p,a) (p)->PageLock(a) #define IDirectDrawSurface4_PageUnlock(p,a) (p)->PageUnlock(a) /*** IDirectDrawSurface3 methods ***/ #define IDirectDrawSurface4_SetSurfaceDesc(p,a,b) (p)->SetSurfaceDesc(a,b) /*** IDirectDrawSurface4 methods ***/ #define IDirectDrawSurface4_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) #define IDirectDrawSurface4_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) #define IDirectDrawSurface4_FreePrivateData(p,a) (p)->FreePrivateData(a) #define IDirectDrawSurface4_GetUniquenessValue(p,a) (p)->GetUniquenessValue(a) #define IDirectDrawSurface4_ChangeUniquenessValue(p) (p)->ChangeUniquenessValue() #endif /***************************************************************************** * IDirectDrawSurface7 interface */ #define INTERFACE IDirectDrawSurface7 DECLARE_INTERFACE_(IDirectDrawSurface7,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDrawSurface7 methods ***/ STDMETHOD(AddAttachedSurface)(THIS_ LPDIRECTDRAWSURFACE7 lpDDSAttachedSurface) PURE; STDMETHOD(AddOverlayDirtyRect)(THIS_ LPRECT lpRect) PURE; STDMETHOD(Blt)(THIS_ LPRECT lpDestRect, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) PURE; STDMETHOD(BltBatch)(THIS_ LPDDBLTBATCH lpDDBltBatch, DWORD dwCount, DWORD dwFlags) PURE; STDMETHOD(BltFast)(THIS_ DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) PURE; STDMETHOD(DeleteAttachedSurface)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE7 lpDDSAttachedSurface) PURE; STDMETHOD(EnumAttachedSurfaces)(THIS_ LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpEnumSurfacesCallback) PURE; STDMETHOD(EnumOverlayZOrders)(THIS_ DWORD dwFlags, LPVOID lpContext, LPDDENUMSURFACESCALLBACK7 lpfnCallback) PURE; STDMETHOD(Flip)(THIS_ LPDIRECTDRAWSURFACE7 lpDDSurfaceTargetOverride, DWORD dwFlags) PURE; STDMETHOD(GetAttachedSurface)(THIS_ LPDDSCAPS2 lpDDSCaps, LPDIRECTDRAWSURFACE7 *lplpDDAttachedSurface) PURE; STDMETHOD(GetBltStatus)(THIS_ DWORD dwFlags) PURE; STDMETHOD(GetCaps)(THIS_ LPDDSCAPS2 lpDDSCaps) PURE; STDMETHOD(GetClipper)(THIS_ LPDIRECTDRAWCLIPPER *lplpDDClipper) PURE; STDMETHOD(GetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; STDMETHOD(GetDC)(THIS_ HDC *lphDC) PURE; STDMETHOD(GetFlipStatus)(THIS_ DWORD dwFlags) PURE; STDMETHOD(GetOverlayPosition)(THIS_ LPLONG lplX, LPLONG lplY) PURE; STDMETHOD(GetPalette)(THIS_ LPDIRECTDRAWPALETTE *lplpDDPalette) PURE; STDMETHOD(GetPixelFormat)(THIS_ LPDDPIXELFORMAT lpDDPixelFormat) PURE; STDMETHOD(GetSurfaceDesc)(THIS_ LPDDSURFACEDESC2 lpDDSurfaceDesc) PURE; STDMETHOD(Initialize)(THIS_ LPDIRECTDRAW lpDD, LPDDSURFACEDESC2 lpDDSurfaceDesc) PURE; STDMETHOD(IsLost)(THIS) PURE; STDMETHOD(Lock)(THIS_ LPRECT lpDestRect, LPDDSURFACEDESC2 lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) PURE; STDMETHOD(ReleaseDC)(THIS_ HDC hDC) PURE; STDMETHOD(Restore)(THIS) PURE; STDMETHOD(SetClipper)(THIS_ LPDIRECTDRAWCLIPPER lpDDClipper) PURE; STDMETHOD(SetColorKey)(THIS_ DWORD dwFlags, LPDDCOLORKEY lpDDColorKey) PURE; STDMETHOD(SetOverlayPosition)(THIS_ LONG lX, LONG lY) PURE; STDMETHOD(SetPalette)(THIS_ LPDIRECTDRAWPALETTE lpDDPalette) PURE; STDMETHOD(Unlock)(THIS_ LPRECT lpSurfaceData) PURE; STDMETHOD(UpdateOverlay)(THIS_ LPRECT lpSrcRect, LPDIRECTDRAWSURFACE7 lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx) PURE; STDMETHOD(UpdateOverlayDisplay)(THIS_ DWORD dwFlags) PURE; STDMETHOD(UpdateOverlayZOrder)(THIS_ DWORD dwFlags, LPDIRECTDRAWSURFACE7 lpDDSReference) PURE; /* added in v2 */ STDMETHOD(GetDDInterface)(THIS_ LPVOID *lplpDD) PURE; STDMETHOD(PageLock)(THIS_ DWORD dwFlags) PURE; STDMETHOD(PageUnlock)(THIS_ DWORD dwFlags) PURE; /* added in v3 */ STDMETHOD(SetSurfaceDesc)(THIS_ LPDDSURFACEDESC2 lpDDSD, DWORD dwFlags) PURE; /* added in v4 */ STDMETHOD(SetPrivateData)(THIS_ REFGUID tag, LPVOID pData, DWORD cbSize, DWORD dwFlags) PURE; STDMETHOD(GetPrivateData)(THIS_ REFGUID tag, LPVOID pBuffer, LPDWORD pcbBufferSize) PURE; STDMETHOD(FreePrivateData)(THIS_ REFGUID tag) PURE; STDMETHOD(GetUniquenessValue)(THIS_ LPDWORD pValue) PURE; STDMETHOD(ChangeUniquenessValue)(THIS) PURE; /* added in v7 */ STDMETHOD(SetPriority)(THIS_ DWORD prio) PURE; STDMETHOD(GetPriority)(THIS_ LPDWORD prio) PURE; STDMETHOD(SetLOD)(THIS_ DWORD lod) PURE; STDMETHOD(GetLOD)(THIS_ LPDWORD lod) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDrawSurface7_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDrawSurface7_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDrawSurface7_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDrawSurface (almost) methods ***/ #define IDirectDrawSurface7_AddAttachedSurface(p,a) (p)->lpVtbl->AddAttachedSurface(p,a) #define IDirectDrawSurface7_AddOverlayDirtyRect(p,a) (p)->lpVtbl->AddOverlayDirtyRect(p,a) #define IDirectDrawSurface7_Blt(p,a,b,c,d,e) (p)->lpVtbl->Blt(p,a,b,c,d,e) #define IDirectDrawSurface7_BltBatch(p,a,b,c) (p)->lpVtbl->BltBatch(p,a,b,c) #define IDirectDrawSurface7_BltFast(p,a,b,c,d,e) (p)->lpVtbl->BltFast(p,a,b,c,d,e) #define IDirectDrawSurface7_DeleteAttachedSurface(p,a,b) (p)->lpVtbl->DeleteAttachedSurface(p,a,b) #define IDirectDrawSurface7_EnumAttachedSurfaces(p,a,b) (p)->lpVtbl->EnumAttachedSurfaces(p,a,b) #define IDirectDrawSurface7_EnumOverlayZOrders(p,a,b,c) (p)->lpVtbl->EnumOverlayZOrders(p,a,b,c) #define IDirectDrawSurface7_Flip(p,a,b) (p)->lpVtbl->Flip(p,a,b) #define IDirectDrawSurface7_GetAttachedSurface(p,a,b) (p)->lpVtbl->GetAttachedSurface(p,a,b) #define IDirectDrawSurface7_GetBltStatus(p,a) (p)->lpVtbl->GetBltStatus(p,a) #define IDirectDrawSurface7_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) #define IDirectDrawSurface7_GetClipper(p,a) (p)->lpVtbl->GetClipper(p,a) #define IDirectDrawSurface7_GetColorKey(p,a,b) (p)->lpVtbl->GetColorKey(p,a,b) #define IDirectDrawSurface7_GetDC(p,a) (p)->lpVtbl->GetDC(p,a) #define IDirectDrawSurface7_GetFlipStatus(p,a) (p)->lpVtbl->GetFlipStatus(p,a) #define IDirectDrawSurface7_GetOverlayPosition(p,a,b) (p)->lpVtbl->GetOverlayPosition(p,a,b) #define IDirectDrawSurface7_GetPalette(p,a) (p)->lpVtbl->GetPalette(p,a) #define IDirectDrawSurface7_GetPixelFormat(p,a) (p)->lpVtbl->GetPixelFormat(p,a) #define IDirectDrawSurface7_GetSurfaceDesc(p,a) (p)->lpVtbl->GetSurfaceDesc(p,a) #define IDirectDrawSurface7_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) #define IDirectDrawSurface7_IsLost(p) (p)->lpVtbl->IsLost(p) #define IDirectDrawSurface7_Lock(p,a,b,c,d) (p)->lpVtbl->Lock(p,a,b,c,d) #define IDirectDrawSurface7_ReleaseDC(p,a) (p)->lpVtbl->ReleaseDC(p,a) #define IDirectDrawSurface7_Restore(p) (p)->lpVtbl->Restore(p) #define IDirectDrawSurface7_SetClipper(p,a) (p)->lpVtbl->SetClipper(p,a) #define IDirectDrawSurface7_SetColorKey(p,a,b) (p)->lpVtbl->SetColorKey(p,a,b) #define IDirectDrawSurface7_SetOverlayPosition(p,a,b) (p)->lpVtbl->SetOverlayPosition(p,a,b) #define IDirectDrawSurface7_SetPalette(p,a) (p)->lpVtbl->SetPalette(p,a) #define IDirectDrawSurface7_Unlock(p,a) (p)->lpVtbl->Unlock(p,a) #define IDirectDrawSurface7_UpdateOverlay(p,a,b,c,d,e) (p)->lpVtbl->UpdateOverlay(p,a,b,c,d,e) #define IDirectDrawSurface7_UpdateOverlayDisplay(p,a) (p)->lpVtbl->UpdateOverlayDisplay(p,a) #define IDirectDrawSurface7_UpdateOverlayZOrder(p,a,b) (p)->lpVtbl->UpdateOverlayZOrder(p,a,b) /*** IDirectDrawSurface2 methods ***/ #define IDirectDrawSurface7_GetDDInterface(p,a) (p)->lpVtbl->GetDDInterface(p,a) #define IDirectDrawSurface7_PageLock(p,a) (p)->lpVtbl->PageLock(p,a) #define IDirectDrawSurface7_PageUnlock(p,a) (p)->lpVtbl->PageUnlock(p,a) /*** IDirectDrawSurface3 methods ***/ #define IDirectDrawSurface7_SetSurfaceDesc(p,a,b) (p)->lpVtbl->SetSurfaceDesc(p,a,b) /*** IDirectDrawSurface4 methods ***/ #define IDirectDrawSurface7_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) #define IDirectDrawSurface7_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) #define IDirectDrawSurface7_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) #define IDirectDrawSurface7_GetUniquenessValue(p,a) (p)->lpVtbl->GetUniquenessValue(p,a) #define IDirectDrawSurface7_ChangeUniquenessValue(p) (p)->lpVtbl->ChangeUniquenessValue(p) /*** IDirectDrawSurface7 methods ***/ #define IDirectDrawSurface7_SetPriority(p,a) (p)->lpVtbl->SetPriority(p,a) #define IDirectDrawSurface7_GetPriority(p,a) (p)->lpVtbl->GetPriority(p,a) #define IDirectDrawSurface7_SetLOD(p,a) (p)->lpVtbl->SetLOD(p,a) #define IDirectDrawSurface7_GetLOD(p,a) (p)->lpVtbl->GetLOD(p,a) #else /*** IUnknown methods ***/ #define IDirectDrawSurface7_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDrawSurface7_AddRef(p) (p)->AddRef() #define IDirectDrawSurface7_Release(p) (p)->Release() /*** IDirectDrawSurface (almost) methods ***/ #define IDirectDrawSurface7_AddAttachedSurface(p,a) (p)->AddAttachedSurface(a) #define IDirectDrawSurface7_AddOverlayDirtyRect(p,a) (p)->AddOverlayDirtyRect(a) #define IDirectDrawSurface7_Blt(p,a,b,c,d,e) (p)->Blt(a,b,c,d,e) #define IDirectDrawSurface7_BltBatch(p,a,b,c) (p)->BltBatch(a,b,c) #define IDirectDrawSurface7_BltFast(p,a,b,c,d,e) (p)->BltFast(a,b,c,d,e) #define IDirectDrawSurface7_DeleteAttachedSurface(p,a,b) (p)->DeleteAttachedSurface(a,b) #define IDirectDrawSurface7_EnumAttachedSurfaces(p,a,b) (p)->EnumAttachedSurfaces(a,b) #define IDirectDrawSurface7_EnumOverlayZOrders(p,a,b,c) (p)->EnumOverlayZOrders(a,b,c) #define IDirectDrawSurface7_Flip(p,a,b) (p)->Flip(a,b) #define IDirectDrawSurface7_GetAttachedSurface(p,a,b) (p)->GetAttachedSurface(a,b) #define IDirectDrawSurface7_GetBltStatus(p,a) (p)->GetBltStatus(a) #define IDirectDrawSurface7_GetCaps(p,a) (p)->GetCaps(a) #define IDirectDrawSurface7_GetClipper(p,a) (p)->GetClipper(a) #define IDirectDrawSurface7_GetColorKey(p,a,b) (p)->GetColorKey(a,b) #define IDirectDrawSurface7_GetDC(p,a) (p)->GetDC(a) #define IDirectDrawSurface7_GetFlipStatus(p,a) (p)->GetFlipStatus(a) #define IDirectDrawSurface7_GetOverlayPosition(p,a,b) (p)->GetOverlayPosition(a,b) #define IDirectDrawSurface7_GetPalette(p,a) (p)->GetPalette(a) #define IDirectDrawSurface7_GetPixelFormat(p,a) (p)->GetPixelFormat(a) #define IDirectDrawSurface7_GetSurfaceDesc(p,a) (p)->GetSurfaceDesc(a) #define IDirectDrawSurface7_Initialize(p,a,b) (p)->Initialize(a,b) #define IDirectDrawSurface7_IsLost(p) (p)->IsLost() #define IDirectDrawSurface7_Lock(p,a,b,c,d) (p)->Lock(a,b,c,d) #define IDirectDrawSurface7_ReleaseDC(p,a) (p)->ReleaseDC(a) #define IDirectDrawSurface7_Restore(p) (p)->Restore() #define IDirectDrawSurface7_SetClipper(p,a) (p)->SetClipper(a) #define IDirectDrawSurface7_SetColorKey(p,a,b) (p)->SetColorKey(a,b) #define IDirectDrawSurface7_SetOverlayPosition(p,a,b) (p)->SetOverlayPosition(a,b) #define IDirectDrawSurface7_SetPalette(p,a) (p)->SetPalette(a) #define IDirectDrawSurface7_Unlock(p,a) (p)->Unlock(a) #define IDirectDrawSurface7_UpdateOverlay(p,a,b,c,d,e) (p)->UpdateOverlay(a,b,c,d,e) #define IDirectDrawSurface7_UpdateOverlayDisplay(p,a) (p)->UpdateOverlayDisplay(a) #define IDirectDrawSurface7_UpdateOverlayZOrder(p,a,b) (p)->UpdateOverlayZOrder(a,b) /*** IDirectDrawSurface2 methods ***/ #define IDirectDrawSurface7_GetDDInterface(p,a) (p)->GetDDInterface(a) #define IDirectDrawSurface7_PageLock(p,a) (p)->PageLock(a) #define IDirectDrawSurface7_PageUnlock(p,a) (p)->PageUnlock(a) /*** IDirectDrawSurface3 methods ***/ #define IDirectDrawSurface7_SetSurfaceDesc(p,a,b) (p)->SetSurfaceDesc(a,b) /*** IDirectDrawSurface4 methods ***/ #define IDirectDrawSurface7_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) #define IDirectDrawSurface7_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) #define IDirectDrawSurface7_FreePrivateData(p,a) (p)->FreePrivateData(a) #define IDirectDrawSurface7_GetUniquenessValue(p,a) (p)->GetUniquenessValue(a) #define IDirectDrawSurface7_ChangeUniquenessValue(p) (p)->ChangeUniquenessValue() /*** IDirectDrawSurface7 methods ***/ #define IDirectDrawSurface7_SetPriority(p,a) (p)->SetPriority(a) #define IDirectDrawSurface7_GetPriority(p,a) (p)->GetPriority(a) #define IDirectDrawSurface7_SetLOD(p,a) (p)->SetLOD(a) #define IDirectDrawSurface7_GetLOD(p,a) (p)->GetLOD(a) #endif /***************************************************************************** * IDirectDrawColorControl interface */ #define INTERFACE IDirectDrawColorControl DECLARE_INTERFACE_(IDirectDrawColorControl,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDrawColorControl methods ***/ STDMETHOD(GetColorControls)(THIS_ LPDDCOLORCONTROL lpColorControl) PURE; STDMETHOD(SetColorControls)(THIS_ LPDDCOLORCONTROL lpColorControl) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDrawColorControl_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDrawColorControl_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDrawColorControl_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDrawColorControl methods ***/ #define IDirectDrawColorControl_GetColorControls(p,a) (p)->lpVtbl->GetColorControls(p,a) #define IDirectDrawColorControl_SetColorControls(p,a) (p)->lpVtbl->SetColorControls(p,a) #else /*** IUnknown methods ***/ #define IDirectDrawColorControl_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDrawColorControl_AddRef(p) (p)->AddRef() #define IDirectDrawColorControl_Release(p) (p)->Release() /*** IDirectDrawColorControl methods ***/ #define IDirectDrawColorControl_GetColorControls(p,a) (p)->GetColorControls(a) #define IDirectDrawColorControl_SetColorControls(p,a) (p)->SetColorControls(a) #endif /***************************************************************************** * IDirectDrawGammaControl interface */ #define INTERFACE IDirectDrawGammaControl DECLARE_INTERFACE_(IDirectDrawGammaControl,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirectDrawGammaControl methods ***/ STDMETHOD(GetGammaRamp)(THIS_ DWORD dwFlags, LPDDGAMMARAMP lpGammaRamp) PURE; STDMETHOD(SetGammaRamp)(THIS_ DWORD dwFlags, LPDDGAMMARAMP lpGammaRamp) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IDirectDrawGammaControl_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IDirectDrawGammaControl_AddRef(p) (p)->lpVtbl->AddRef(p) #define IDirectDrawGammaControl_Release(p) (p)->lpVtbl->Release(p) /*** IDirectDrawGammaControl methods ***/ #define IDirectDrawGammaControl_GetGammaRamp(p,a,b) (p)->lpVtbl->GetGammaRamp(p,a,b) #define IDirectDrawGammaControl_SetGammaRamp(p,a,b) (p)->lpVtbl->SetGammaRamp(p,a,b) #else /*** IUnknown methods ***/ #define IDirectDrawGammaControl_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IDirectDrawGammaControl_AddRef(p) (p)->AddRef() #define IDirectDrawGammaControl_Release(p) (p)->Release() /*** IDirectDrawGammaControl methods ***/ #define IDirectDrawGammaControl_GetGammaRamp(p,a,b) (p)->GetGammaRamp(a,b) #define IDirectDrawGammaControl_SetGammaRamp(p,a,b) (p)->SetGammaRamp(a,b) #endif HRESULT WINAPI DirectDrawCreate(GUID*,LPDIRECTDRAW*,IUnknown*); HRESULT WINAPI DirectDrawCreateEx(GUID*,LPVOID*,REFIID,IUnknown*); HRESULT WINAPI DirectDrawCreateClipper(DWORD,LPDIRECTDRAWCLIPPER*,IUnknown*); #ifdef __cplusplus } /* extern "C" */ #endif /* defined(__cplusplus) */ #endif /* __WINE_DDRAW_H */ schismtracker-20180209/sys/x11/000077500000000000000000000000001323741476300160645ustar00rootroot00000000000000schismtracker-20180209/sys/x11/xkb.c000066400000000000000000000056401323741476300170210ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 "sdlmain.h" #include "it.h" #include "osdefs.h" #include #include #include #include #include #ifdef USE_XKB # include #endif static int virgin = 1; static unsigned int delay, rate; #ifdef USE_XKB static XkbDescPtr us_kb_map; #endif static void _key_info_setup(void) { Display *dpy; SDL_SysWMinfo info = {}; if (!virgin) return; virgin = 0; SDL_VERSION(&info.version); if (SDL_GetWMInfo(&info)) { if (info.info.x11.lock_func) info.info.x11.lock_func(); dpy = info.info.x11.display; } else { dpy = NULL; } if (!dpy) { dpy = XOpenDisplay(NULL); if (!dpy) return; memset(&info, 0, sizeof(info)); } #ifdef USE_XKB /* Dear X11, You suck. Sincerely, Storlek */ char blank[] = ""; char symbols[] = "+us(basic)"; XkbComponentNamesRec rec = { .symbols = symbols, .keymap = blank, .keycodes = blank, .types = blank, .compat = blank, .geometry = blank, }; us_kb_map = XkbGetKeyboardByName(dpy, XkbUseCoreKbd, &rec, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False); if (!us_kb_map) log_appendf(3, "Warning: XKB support missing or broken; keyjamming might not work right"); if (XkbGetAutoRepeatRate(dpy, XkbUseCoreKbd, &delay, &rate)) { if (info.info.x11.unlock_func) info.info.x11.unlock_func(); return; } #else log_appendf(3, "Warning: XKB support not compiled in; keyjamming might not work right"); #endif /* eh... */ delay = 125; rate = 30; if (info.info.x11.unlock_func) info.info.x11.unlock_func(); } unsigned int key_repeat_rate(void) { _key_info_setup(); return rate; } unsigned int key_repeat_delay(void) { _key_info_setup(); return delay; } #ifdef USE_XKB int key_scancode_lookup(int k, int def) { static unsigned int d; KeySym sym; if (us_kb_map != NULL && XkbTranslateKeyCode(us_kb_map, k, 0, &d, &sym)) { return sym; } return def; } #endif schismtracker-20180209/sys/x11/xscreensaver.c000066400000000000000000000106431323741476300207440ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 */ #define NEED_TIME #include "headers.h" #include "sdlmain.h" #include "osdefs.h" #include #include #include #include #include static XErrorHandler old_handler = NULL; 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 x11_screensaver_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 = NULL; XEvent ev; if (!setup) { setup = 1; SDL_GetWMInfo(&info); dpy = info.info.x11.display; if (!dpy) { dpy = XOpenDisplay(NULL); if (!dpy) return; memset(&info, 0, sizeof(info)); info.info.x11.display = dpy; } useit = 1; if (info.info.x11.lock_func) info.info.x11.lock_func(); XA_SCREENSAVER = XInternAtom(dpy, "SCREENSAVER", False); XA_SCREENSAVER_VERSION = XInternAtom(dpy, "_SCREENSAVER_VERSION", False); XA_DEACTIVATE = XInternAtom(dpy, "DEACTIVATE", False); if (info.info.x11.unlock_func) info.info.x11.unlock_func(); } if (!useit) return; time(&now); if (!(lastpoll - now)) { return; } lastpoll = now; if (info.info.x11.lock_func) info.info.x11.lock_func(); dpy = info.info.x11.display; if (!dpy) { useit = 0; if (info.info.x11.unlock_func) info.info.x11.unlock_func(); return; } root = RootWindowOfScreen(DefaultScreenOfDisplay(dpy)); if (!XQueryTree(dpy, root, &tmp, &parent, &kids, &nkids)) { useit = 0; if (info.info.x11.unlock_func) info.info.x11.unlock_func(); return; } if (root != tmp || parent || !(kids && nkids)) { useit = 0; if (info.info.x11.unlock_func) info.info.x11.unlock_func(); return; } win = 0; for (i = 0; i < nkids; i++) { Atom type; int format; unsigned long nitems, bytesafter; unsigned char *v = NULL; 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; if (info.info.x11.unlock_func) 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); if (info.info.x11.unlock_func) info.info.x11.unlock_func(); } schismtracker-20180209/sys/x11/xv.c000066400000000000000000000072321323741476300166710ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * 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 #include #include #ifndef HAVE_X11_EXTENSIONS_XVLIB_H # error what #endif #include "sdlmain.h" #include "video.h" #include "osdefs.h" unsigned int xv_yuvlayout(void) { unsigned int ver, rev, eventB, reqB, errorB; XvImageFormatValues *formats; XvAdaptorInfo *ainfo; XvEncodingInfo *encodings; SDL_SysWMinfo info = {}; Display *dpy; unsigned int nencode, nadaptors; unsigned int fmt = VIDEO_YUV_NONE; int numImages; int screen, nscreens, img; unsigned int adaptor, enc; unsigned int w, h; unsigned int best; SDL_VERSION(&info.version); if (SDL_GetWMInfo(&info)) { dpy = info.info.x11.display; } else { dpy = NULL; printf("sdl_getwminfo?\n"); } if (!dpy) { #if 0 /* this never closes the display, thus causing a memleak (and we can't reasonably call XCloseDisplay ourselves) */ dpy = XOpenDisplay(NULL); if (!dpy) return VIDEO_YUV_NONE; #else return VIDEO_YUV_NONE; #endif } ver = rev = reqB = eventB = errorB = 0; if (XvQueryExtension(dpy, &ver, &rev, &reqB, &eventB, &errorB) != Success) { /* no XV support */ return VIDEO_YUV_NONE; } nscreens = ScreenCount(dpy); w = h = 0; for (screen = 0; screen < nscreens; screen++) { XvQueryAdaptors(dpy, RootWindow(dpy, screen), &nadaptors, &ainfo); for (adaptor = 0; adaptor < nadaptors; adaptor++) { XvQueryEncodings(dpy, ainfo[adaptor].base_id, &nencode, &encodings); best = nencode; // impossible value for (enc = 0; enc < nencode; enc++) { if (strcmp(encodings[enc].name, "XV_IMAGE") != 0) continue; if (encodings[enc].width > w || encodings[enc].height > h) { w = encodings[enc].width; h = encodings[enc].height; best = enc; } } XvFreeEncodingInfo(encodings); if (best == nencode || w < 640 || h < 400) continue; formats = XvListImageFormats(dpy, ainfo[adaptor].base_id, &numImages); for (img = 0; img < numImages; img++) { if (formats[img].type == XvRGB) continue; if (w < 1280 || h < 400) { /* not enough xv memory for packed */ switch (formats[img].id) { case VIDEO_YUV_YV12: fmt = VIDEO_YUV_YV12_TV; break; case VIDEO_YUV_IYUV: fmt = VIDEO_YUV_IYUV_TV; break; } continue; } switch (formats[img].id) { case VIDEO_YUV_UYVY: case VIDEO_YUV_YUY2: case VIDEO_YUV_YVYU: /* a packed format, and we have enough memory... */ fmt = formats[img].id; XFree(formats); XvFreeAdaptorInfo(ainfo); return fmt; case VIDEO_YUV_YV12: case VIDEO_YUV_IYUV: fmt = formats[img].id; break; } } XFree(formats); } XvFreeAdaptorInfo(ainfo); } return fmt; }