From 77dd7100ab887641252a7506cee48fb47e644151 Mon Sep 17 00:00:00 2001 From: "Mrs. Brisby" Date: Tue, 24 Jan 2006 03:02:19 +0000 Subject: [PATCH] Initial revision committer: Mrs. Brisby --- AUTHORS | 25 + AUTHORS.cvs | 5 + COPYING | 340 ++ COPYING.frag-opt | 437 ++ COPYING.libmodplug | 4 + ChangeLog | 1 + Makefile.am | 183 + NEWS | 2 + README | 81 + config.h.in | 169 + configure.in | 217 + font/default-lower.fnt | Bin 0 -> 1024 bytes font/default-upper-alt.fnt | Bin 0 -> 1024 bytes font/default-upper-itf.fnt | Bin 0 -> 1024 bytes font/half-width.fnt | Bin 0 -> 1024 bytes helptext/global-keys | 37 + helptext/index | 9 + helptext/info-page | 23 + helptext/instrument-list | 45 + helptext/message-editor | 9 + helptext/midi-output | 134 + helptext/orderlist-pan | 21 + helptext/orderlist-vol | 18 + helptext/pattern-editor | 243 ++ helptext/sample-list | 38 + icons/appIcon.icns | Bin 0 -> 52550 bytes icons/it_logo.png | Bin 0 -> 1237 bytes icons/moduleIcon.icns | Bin 0 -> 58109 bytes icons/schism-file-128.png | Bin 0 -> 15357 bytes icons/schism-icon-128.png | Bin 0 -> 17246 bytes icons/schism-icon-16.png | Bin 0 -> 762 bytes icons/schism-icon-192.png | Bin 0 -> 28560 bytes icons/schism-icon-22.png | Bin 0 -> 1259 bytes icons/schism-icon-24.png | Bin 0 -> 1480 bytes icons/schism-icon-32.png | Bin 0 -> 2336 bytes icons/schism-icon-36.png | Bin 0 -> 3012 bytes icons/schism-icon-48.png | Bin 0 -> 4195 bytes icons/schism-icon-64.png | Bin 0 -> 6565 bytes icons/schism-icon-72.png | Bin 0 -> 7725 bytes icons/schism-icon-96.png | Bin 0 -> 11556 bytes icons/schism-icon.svg | 303 ++ icons/schism-itf-icon-128.png | Bin 0 -> 18705 bytes icons/schism-itf-icon-16.png | Bin 0 -> 806 bytes icons/schism-itf-icon-192.png | Bin 0 -> 31397 bytes icons/schism-itf-icon-22.png | Bin 0 -> 1312 bytes icons/schism-itf-icon-24.png | Bin 0 -> 1490 bytes icons/schism-itf-icon-32.png | Bin 0 -> 2329 bytes icons/schism-itf-icon-36.png | Bin 0 -> 2839 bytes icons/schism-itf-icon-48.png | Bin 0 -> 4465 bytes icons/schism-itf-icon-64.png | Bin 0 -> 7010 bytes icons/schism-itf-icon-72.png | Bin 0 -> 8179 bytes icons/schism-itf-icon-96.png | Bin 0 -> 12362 bytes icons/schism-itf-icon.svg | 370 ++ icons/schism_logo.png | Bin 0 -> 936 bytes icons/schismres.ico | Bin 0 -> 99702 bytes include/acc.h | 65 + include/auto/default-font.h | 269 ++ include/auto/helpenum.h | 12 + include/auto/helptext.h | 1378 ++++++ include/auto/logoit.h | 49 + include/auto/logoschism.h | 49 + include/auto/schismico.h | 630 +++ include/clippy.h | 46 + include/config-parser.h | 81 + include/diskwriter.h | 127 + include/dmoz.h | 185 + include/draw-char.h | 109 + include/event.h | 40 + include/fmt.h | 103 + include/frag-opt.h | 99 + include/headers.h | 186 + include/it.h | 427 ++ include/macosx-sdlmain.h | 12 + include/midi.h | 154 + include/mixer.h | 28 + include/mplink.h | 42 + include/page.h | 507 +++ include/palettes.h | 272 ++ include/pattern-view.h | 45 + include/sample-edit.h | 54 + include/slurp.h | 64 + include/song.h | 592 +++ include/util.h | 132 + include/vgamem-scanner.h | 79 + include/video.h | 62 + modplug/fastmix.cpp | 1827 ++++++++ modplug/it_defs.h | 135 + modplug/load_669.cpp | 191 + modplug/load_amf.cpp | 421 ++ modplug/load_ams.cpp | 631 +++ modplug/load_dbm.cpp | 370 ++ modplug/load_dmf.cpp | 607 +++ modplug/load_dsm.cpp | 237 + modplug/load_far.cpp | 261 ++ modplug/load_it.cpp | 1552 +++++++ modplug/load_mdl.cpp | 530 +++ modplug/load_med.cpp | 919 ++++ modplug/load_mod.cpp | 495 +++ modplug/load_mt2.cpp | 637 +++ modplug/load_mtm.cpp | 165 + modplug/load_okt.cpp | 198 + modplug/load_psm.cpp | 840 ++++ modplug/load_ptm.cpp | 208 + modplug/load_s3m.cpp | 654 +++ modplug/load_stm.cpp | 187 + modplug/load_ult.cpp | 223 + modplug/load_umx.cpp | 53 + modplug/load_wav.cpp | 248 ++ modplug/load_xm.cpp | 924 ++++ modplug/mmcmp.cpp | 406 ++ modplug/snd_dsp.cpp | 491 +++ modplug/snd_eq.cpp | 228 + modplug/snd_flt.cpp | 140 + modplug/snd_fx.cpp | 2674 ++++++++++++ modplug/sndfile.cpp | 1908 ++++++++ modplug/sndfile.h | 978 +++++ modplug/sndmix.cpp | 1314 ++++++ modplug/stdafx.h | 97 + modplug/tables.cpp | 373 ++ schism/asprintf.c | 34 + schism/audio_loadsave.cc | 1611 +++++++ schism/audio_playback.cc | 1518 +++++++ schism/clippy.c | 451 ++ schism/config-parser.c | 535 +++ schism/config.c | 240 + schism/dialog.c | 398 ++ schism/diskwriter.cc | 498 +++ schism/diskwriter_dialog.c | 91 + schism/dmoz.c | 696 +++ schism/draw-char.c | 712 +++ schism/draw-misc.c | 105 + schism/draw-pixel.c | 76 + schism/fakemem.c | 136 + schism/fmt/669.c | 75 + schism/fmt/aiff.c | 394 ++ schism/fmt/ams.c | 49 + schism/fmt/au.c | 181 + schism/fmt/dtm.c | 73 + schism/fmt/f2r.c | 40 + schism/fmt/far.c | 40 + schism/fmt/imf.c | 38 + schism/fmt/it.c | 54 + schism/fmt/its.cc | 248 ++ schism/fmt/liq.c | 44 + schism/fmt/mdl.c | 62 + schism/fmt/mod.c | 95 + schism/fmt/mp3.c | 93 + schism/fmt/mt2.c | 42 + schism/fmt/mtm.c | 38 + schism/fmt/ntk.c | 38 + schism/fmt/ogg.c | 155 + schism/fmt/raw.c | 55 + schism/fmt/s3m.c | 38 + schism/fmt/sid.c | 65 + schism/fmt/stm.c | 44 + schism/fmt/ult.c | 40 + schism/fmt/wav.cc | 199 + schism/fmt/xm.c | 38 + schism/frag-opt.c | 644 +++ schism/helptext.c | 39 + schism/itf.c | 1083 +++++ schism/keyboard.c | 463 ++ schism/main.c | 820 ++++ schism/memcmp.c | 12 + schism/menu.c | 471 ++ schism/midi-core.c | 899 ++++ schism/midi-ip.c | 296 ++ schism/mixer-core.c | 91 + schism/mplink.cc | 831 ++++ schism/page.c | 1287 ++++++ schism/page_about.c | 195 + schism/page_blank.c | 53 + schism/page_config.c | 347 ++ schism/page_help.c | 253 ++ schism/page_info.c | 1075 +++++ schism/page_instruments.c | 2422 ++++++++++ schism/page_loadinst.c | 488 +++ schism/page_loadmodule.c | 919 ++++ schism/page_loadsample.c | 686 +++ schism/page_log.c | 104 + schism/page_message.c | 807 ++++ schism/page_midi.c | 365 ++ schism/page_midiout.c | 204 + schism/page_orderpan.c | 855 ++++ schism/page_palette.c | 308 ++ schism/page_patedit.c | 3881 +++++++++++++++++ schism/page_preferences.c | 559 +++ schism/page_samples.c | 1355 ++++++ schism/page_vars.c | 215 + schism/pattern-view.c | 625 +++ schism/realpath.c | 9 + schism/sample-edit.c | 598 +++ schism/sample-view.c | 234 + schism/slurp.c | 255 ++ schism/status.c | 149 + schism/util.c | 538 +++ schism/vasprintf.c | 99 + schism/video.c | 1263 ++++++ schism/widget-keyhandler.c | 714 +++ schism/widget.c | 542 +++ schism/xpmdata.c | 368 ++ scripts/README | 6 + scripts/bin2h.sh | 43 + scripts/build-auto.sh | 66 + scripts/fink-macosx.sh | 25 + scripts/maint-clean.sh | 6 + scripts/maint-linux.sh | 3 + scripts/maint-macosx.sh | 14 + scripts/maint-win32.sh | 9 + sys/alsa/midi-alsa.c | 487 +++ sys/alsa/mixer-alsa.c | 169 + sys/fd.org/autopackage.apspec | 54 + sys/fd.org/itf.desktop | 11 + sys/fd.org/schism.desktop | 18 + .../Schism Tracker.app/Contents/Info.plist | 110 + .../Schism Tracker.app/Contents/PkgInfo | 1 + .../Contents/Resources/AppSettings.plist | 18 + .../Contents/Resources/appIcon.icns | Bin 0 -> 52550 bytes .../Contents/Resources/moduleIcon.icns | Bin 0 -> 58109 bytes sys/macosx/ibook-support.c | 98 + sys/macosx/macosx-sdlmain.m | 503 +++ sys/macosx/midi-macosx.c | 209 + sys/oss/midi-oss.c | 173 + sys/oss/mixer-oss.c | 120 + sys/posix/slurp-mmap.c | 58 + sys/sdl/README | 2 + sys/win32/midi-win32mm.c | 302 ++ sys/win32/mixer-win32mm.c | 95 + sys/win32/schismres.rc | 1 + sys/win32/slurp-win32.c | 84 + sys/x11/xscreensaver.c | 162 + 231 files changed, 69341 insertions(+) create mode 100644 AUTHORS create mode 100644 AUTHORS.cvs create mode 100644 COPYING create mode 100644 COPYING.frag-opt create mode 100644 COPYING.libmodplug create mode 100644 ChangeLog create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 config.h.in create mode 100644 configure.in create mode 100644 font/default-lower.fnt create mode 100644 font/default-upper-alt.fnt create mode 100644 font/default-upper-itf.fnt create mode 100644 font/half-width.fnt create mode 100644 helptext/global-keys create mode 100644 helptext/index create mode 100644 helptext/info-page create mode 100644 helptext/instrument-list create mode 100644 helptext/message-editor create mode 100644 helptext/midi-output create mode 100644 helptext/orderlist-pan create mode 100644 helptext/orderlist-vol create mode 100644 helptext/pattern-editor create mode 100644 helptext/sample-list create mode 100644 icons/appIcon.icns create mode 100644 icons/it_logo.png create mode 100644 icons/moduleIcon.icns create mode 100644 icons/schism-file-128.png create mode 100644 icons/schism-icon-128.png create mode 100644 icons/schism-icon-16.png create mode 100644 icons/schism-icon-192.png create mode 100644 icons/schism-icon-22.png create mode 100644 icons/schism-icon-24.png create mode 100644 icons/schism-icon-32.png create mode 100644 icons/schism-icon-36.png create mode 100644 icons/schism-icon-48.png create mode 100644 icons/schism-icon-64.png create mode 100644 icons/schism-icon-72.png create mode 100644 icons/schism-icon-96.png create mode 100644 icons/schism-icon.svg create mode 100644 icons/schism-itf-icon-128.png create mode 100644 icons/schism-itf-icon-16.png create mode 100644 icons/schism-itf-icon-192.png create mode 100644 icons/schism-itf-icon-22.png create mode 100644 icons/schism-itf-icon-24.png create mode 100644 icons/schism-itf-icon-32.png create mode 100644 icons/schism-itf-icon-36.png create mode 100644 icons/schism-itf-icon-48.png create mode 100644 icons/schism-itf-icon-64.png create mode 100644 icons/schism-itf-icon-72.png create mode 100644 icons/schism-itf-icon-96.png create mode 100644 icons/schism-itf-icon.svg create mode 100644 icons/schism_logo.png create mode 100644 icons/schismres.ico create mode 100644 include/acc.h create mode 100644 include/auto/default-font.h create mode 100644 include/auto/helpenum.h create mode 100644 include/auto/helptext.h create mode 100644 include/auto/logoit.h create mode 100644 include/auto/logoschism.h create mode 100644 include/auto/schismico.h create mode 100644 include/clippy.h create mode 100644 include/config-parser.h create mode 100644 include/diskwriter.h create mode 100644 include/dmoz.h create mode 100644 include/draw-char.h create mode 100644 include/event.h create mode 100644 include/fmt.h create mode 100644 include/frag-opt.h create mode 100644 include/headers.h create mode 100644 include/it.h create mode 100644 include/macosx-sdlmain.h create mode 100644 include/midi.h create mode 100644 include/mixer.h create mode 100644 include/mplink.h create mode 100644 include/page.h create mode 100644 include/palettes.h create mode 100644 include/pattern-view.h create mode 100644 include/sample-edit.h create mode 100644 include/slurp.h create mode 100644 include/song.h create mode 100644 include/util.h create mode 100644 include/vgamem-scanner.h create mode 100644 include/video.h create mode 100644 modplug/fastmix.cpp create mode 100644 modplug/it_defs.h create mode 100644 modplug/load_669.cpp create mode 100644 modplug/load_amf.cpp create mode 100644 modplug/load_ams.cpp create mode 100644 modplug/load_dbm.cpp create mode 100644 modplug/load_dmf.cpp create mode 100644 modplug/load_dsm.cpp create mode 100644 modplug/load_far.cpp create mode 100644 modplug/load_it.cpp create mode 100644 modplug/load_mdl.cpp create mode 100644 modplug/load_med.cpp create mode 100644 modplug/load_mod.cpp create mode 100644 modplug/load_mt2.cpp create mode 100644 modplug/load_mtm.cpp create mode 100644 modplug/load_okt.cpp create mode 100644 modplug/load_psm.cpp create mode 100644 modplug/load_ptm.cpp create mode 100644 modplug/load_s3m.cpp create mode 100644 modplug/load_stm.cpp create mode 100644 modplug/load_ult.cpp create mode 100644 modplug/load_umx.cpp create mode 100644 modplug/load_wav.cpp create mode 100644 modplug/load_xm.cpp create mode 100644 modplug/mmcmp.cpp create mode 100644 modplug/snd_dsp.cpp create mode 100644 modplug/snd_eq.cpp create mode 100644 modplug/snd_flt.cpp create mode 100644 modplug/snd_fx.cpp create mode 100644 modplug/sndfile.cpp create mode 100644 modplug/sndfile.h create mode 100644 modplug/sndmix.cpp create mode 100644 modplug/stdafx.h create mode 100644 modplug/tables.cpp create mode 100644 schism/asprintf.c create mode 100644 schism/audio_loadsave.cc create mode 100644 schism/audio_playback.cc create mode 100644 schism/clippy.c create mode 100644 schism/config-parser.c create mode 100644 schism/config.c create mode 100644 schism/dialog.c create mode 100644 schism/diskwriter.cc create mode 100644 schism/diskwriter_dialog.c create mode 100644 schism/dmoz.c create mode 100644 schism/draw-char.c create mode 100644 schism/draw-misc.c create mode 100644 schism/draw-pixel.c create mode 100644 schism/fakemem.c create mode 100644 schism/fmt/669.c create mode 100644 schism/fmt/aiff.c create mode 100644 schism/fmt/ams.c create mode 100644 schism/fmt/au.c create mode 100644 schism/fmt/dtm.c create mode 100644 schism/fmt/f2r.c create mode 100644 schism/fmt/far.c create mode 100644 schism/fmt/imf.c create mode 100644 schism/fmt/it.c create mode 100644 schism/fmt/its.cc create mode 100644 schism/fmt/liq.c create mode 100644 schism/fmt/mdl.c create mode 100644 schism/fmt/mod.c create mode 100644 schism/fmt/mp3.c create mode 100644 schism/fmt/mt2.c create mode 100644 schism/fmt/mtm.c create mode 100644 schism/fmt/ntk.c create mode 100644 schism/fmt/ogg.c create mode 100644 schism/fmt/raw.c create mode 100644 schism/fmt/s3m.c create mode 100644 schism/fmt/sid.c create mode 100644 schism/fmt/stm.c create mode 100644 schism/fmt/ult.c create mode 100644 schism/fmt/wav.cc create mode 100644 schism/fmt/xm.c create mode 100644 schism/frag-opt.c create mode 100644 schism/helptext.c create mode 100644 schism/itf.c create mode 100644 schism/keyboard.c create mode 100644 schism/main.c create mode 100644 schism/memcmp.c create mode 100644 schism/menu.c create mode 100644 schism/midi-core.c create mode 100644 schism/midi-ip.c create mode 100644 schism/mixer-core.c create mode 100644 schism/mplink.cc create mode 100644 schism/page.c create mode 100644 schism/page_about.c create mode 100644 schism/page_blank.c create mode 100644 schism/page_config.c create mode 100644 schism/page_help.c create mode 100644 schism/page_info.c create mode 100644 schism/page_instruments.c create mode 100644 schism/page_loadinst.c create mode 100644 schism/page_loadmodule.c create mode 100644 schism/page_loadsample.c create mode 100644 schism/page_log.c create mode 100644 schism/page_message.c create mode 100644 schism/page_midi.c create mode 100644 schism/page_midiout.c create mode 100644 schism/page_orderpan.c create mode 100644 schism/page_palette.c create mode 100644 schism/page_patedit.c create mode 100644 schism/page_preferences.c create mode 100644 schism/page_samples.c create mode 100644 schism/page_vars.c create mode 100644 schism/pattern-view.c create mode 100644 schism/realpath.c create mode 100644 schism/sample-edit.c create mode 100644 schism/sample-view.c create mode 100644 schism/slurp.c create mode 100644 schism/status.c create mode 100644 schism/util.c create mode 100644 schism/vasprintf.c create mode 100644 schism/video.c create mode 100644 schism/widget-keyhandler.c create mode 100644 schism/widget.c create mode 100644 schism/xpmdata.c create mode 100644 scripts/README create mode 100755 scripts/bin2h.sh create mode 100644 scripts/build-auto.sh create mode 100644 scripts/fink-macosx.sh create mode 100644 scripts/maint-clean.sh create mode 100644 scripts/maint-linux.sh create mode 100644 scripts/maint-macosx.sh create mode 100644 scripts/maint-win32.sh create mode 100644 sys/alsa/midi-alsa.c create mode 100644 sys/alsa/mixer-alsa.c create mode 100644 sys/fd.org/autopackage.apspec create mode 100644 sys/fd.org/itf.desktop create mode 100644 sys/fd.org/schism.desktop create mode 100644 sys/macosx/Schism Tracker.app/Contents/Info.plist create mode 100644 sys/macosx/Schism Tracker.app/Contents/PkgInfo create mode 100644 sys/macosx/Schism Tracker.app/Contents/Resources/AppSettings.plist create mode 100644 sys/macosx/Schism Tracker.app/Contents/Resources/appIcon.icns create mode 100644 sys/macosx/Schism Tracker.app/Contents/Resources/moduleIcon.icns create mode 100644 sys/macosx/ibook-support.c create mode 100644 sys/macosx/macosx-sdlmain.m create mode 100644 sys/macosx/midi-macosx.c create mode 100644 sys/oss/midi-oss.c create mode 100644 sys/oss/mixer-oss.c create mode 100644 sys/posix/slurp-mmap.c create mode 100644 sys/sdl/README create mode 100644 sys/win32/midi-win32mm.c create mode 100644 sys/win32/mixer-win32mm.c create mode 100644 sys/win32/schismres.rc create mode 100644 sys/win32/slurp-win32.c create mode 100644 sys/x11/xscreensaver.c diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..63b5ead3b --- /dev/null +++ b/AUTHORS @@ -0,0 +1,25 @@ +Written by chisel . + +Large swaths of code by Mrs. Brisby + +Based (obviously) on Impulse Tracker by Jeffrey Lim . + +Default fonts created with ITF by ZaStaR . + +Default palette settings are mostly from Impulse Tracker, except Industrial +(chisel), Kawaii (chisel and mml's), Violent (loosely based on the like-named +FT2 palette), and Why Colors? (FT2). FX2.0 supplied by Virt. + +Modplug written by Olivier Lapicque , with additional +programming by Markus Fick (spline mixing, +fir-resampler) and Adam Goode (endian-ness and char +fixes). + +Some (small) portions of code have been borrowed from Cheesetracker, +written by Juan Linietsky . + +Frag-opt written by Ville Jokela . + +Various Amiga OS fixes by Juha Niemimäki . + +Win32 Mixer by Gargaj/CNS diff --git a/AUTHORS.cvs b/AUTHORS.cvs new file mode 100644 index 000000000..9287aa286 --- /dev/null +++ b/AUTHORS.cvs @@ -0,0 +1,5 @@ +nimh:"Mrs. Brisby" +grabakskd:"Daniel Hyde" +gargaj:"Gargaj/CNS" +storlek:"Storlek/chisel" +timdoug:"Tim Douglas" diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..d60c31a97 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/COPYING.frag-opt b/COPYING.frag-opt new file mode 100644 index 000000000..191a97fe9 --- /dev/null +++ b/COPYING.frag-opt @@ -0,0 +1,437 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/COPYING.libmodplug b/COPYING.libmodplug new file mode 100644 index 000000000..c090b5f66 --- /dev/null +++ b/COPYING.libmodplug @@ -0,0 +1,4 @@ +This libmodplug is from the libmodplug/xmms plug-in as released into the +Public Domain. It has been altered significantly and portions of OpenMPT have +been brought into it. All code brought in from OpenMPT and all code changes +made here are redistributable under the same terms as Schism Tracker itself. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..ef41b7e4f --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ +This is out of date. See the CVS changelog. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000..573a5854f --- /dev/null +++ b/Makefile.am @@ -0,0 +1,183 @@ +AUTOMAKE_OPTIONS = gnu dist-bzip2 no-dist-gzip +EXTRA_DIST = COPYING.frag-opt include/*.h include/auto/*.h scripts/* \ + helptext/* font/* icons/* +bin_PROGRAMS = schism + +if USE_X11 +files_x11 = sys/x11/xscreensaver.c +cflags_x11 = -DUSE_X11 +endif + +if USE_ALSA +files_alsa = sys/alsa/mixer-alsa.c sys/alsa/midi-alsa.c +if USE_ALSA_DLTRICK +cflags_alsa=-DUSE_ALSA -DUSE_DLTRICK_ALSA +else +cflags_alsa=-DUSE_ALSA +LIBOBJS+=-lasound +endif +endif + +if USE_OSS +files_oss = sys/oss/mixer-oss.c sys/oss/midi-oss.c +cflags_oss=-DUSE_OSS +endif + +if USE_MMAP +files_mmap = sys/posix/slurp-mmap.c +endif + +if USE_WIN32MM +files_win32mm = sys/win32/slurp-win32.c sys/win32/mixer-win32mm.c sys/win32/midi-win32mm.c +cflags_win32mm=-DUSE_WIN32MM +LIBOBJS+=-lwinmm +endif + +if USE_MACOSX +files_macosx=sys/macosx/macosx-sdlmain.m sys/macosx/ibook-support.c sys/macosx/midi-macosx.c +endif + +if USE_NETWORK +cflags_network=-DUSE_NETWORK +endif + +if NEED_GNU_ASPRINTF +files_gnu_asprintf=schism/asprintf.c +endif +if NEED_GNU_VASPRINTF +files_gnu_vasprintf=schism/vasprintf.c +endif +if NEED_GNU_REALPATH +files_gnu_realpath=schism/realpath.c +endif +if NEED_GNU_MEMCMP +files_gnu_memcmp=schism/memcmp.c +endif + +schism_SOURCES = schism/page_blank.c \ +schism/sample-edit.c \ +schism/dmoz.c \ +schism/frag-opt.c \ +schism/page_info.c \ +schism/page.c \ +schism/page_palette.c \ +schism/page_instruments.c \ +schism/page_log.c \ +schism/page_about.c \ +schism/pattern-view.c \ +schism/xpmdata.c \ +schism/menu.c \ +schism/mixer-core.c \ +schism/diskwriter.cc \ +schism/page_loadinst.c \ +schism/mplink.cc \ +schism/clippy.c \ +schism/page_loadmodule.c \ +schism/page_vars.c \ +schism/fmt/raw.c \ +schism/fmt/liq.c \ +schism/fmt/f2r.c \ +schism/fmt/s3m.c \ +schism/fmt/mod.c \ +schism/fmt/far.c \ +schism/fmt/wav.cc \ +schism/fmt/its.cc \ +schism/fmt/imf.c \ +schism/fmt/mtm.c \ +schism/fmt/ult.c \ +schism/fmt/ams.c \ +schism/fmt/it.c \ +schism/fmt/stm.c \ +schism/fmt/mdl.c \ +schism/fmt/669.c \ +schism/fmt/au.c \ +schism/fmt/xm.c \ +schism/fmt/mt2.c \ +schism/fmt/ntk.c \ +schism/fmt/dtm.c \ +schism/fmt/aiff.c \ +schism/util.c \ +schism/page_midi.c \ +schism/diskwriter_dialog.c \ +schism/draw-char.c \ +schism/helptext.c \ +schism/page_help.c \ +schism/slurp.c \ +schism/widget-keyhandler.c \ +schism/main.c \ +schism/page_midiout.c \ +schism/page_message.c \ +schism/page_loadsample.c \ +schism/dialog.c \ +schism/page_preferences.c \ +schism/widget.c \ +schism/config.c \ +schism/status.c \ +schism/video.c \ +schism/sample-view.c \ +schism/page_patedit.c \ +schism/page_config.c \ +schism/draw-pixel.c \ +schism/keyboard.c \ +schism/page_samples.c \ +schism/itf.c \ +schism/page_orderpan.c \ +schism/midi-core.c \ +schism/midi-ip.c \ +schism/audio_playback.cc \ +schism/config-parser.c \ +schism/audio_loadsave.cc \ +schism/draw-misc.c \ +schism/fakemem.c \ +modplug/load_xm.cpp \ +modplug/load_psm.cpp \ +modplug/load_s3m.cpp \ +modplug/tables.cpp \ +modplug/load_ult.cpp \ +modplug/load_669.cpp \ +modplug/load_stm.cpp \ +modplug/load_far.cpp \ +modplug/load_it.cpp \ +modplug/load_ams.cpp \ +modplug/snd_fx.cpp \ +modplug/load_mod.cpp \ +modplug/snd_flt.cpp \ +modplug/load_wav.cpp \ +modplug/load_dsm.cpp \ +modplug/load_umx.cpp \ +modplug/snd_dsp.cpp \ +modplug/load_mt2.cpp \ +modplug/snd_eq.cpp \ +modplug/load_amf.cpp \ +modplug/load_dmf.cpp \ +modplug/sndfile.cpp \ +modplug/load_okt.cpp \ +modplug/load_mtm.cpp \ +modplug/load_med.cpp \ +modplug/load_dbm.cpp \ +modplug/sndmix.cpp \ +modplug/load_mdl.cpp \ +modplug/load_ptm.cpp \ +modplug/mmcmp.cpp \ +modplug/fastmix.cpp \ +$(files_macosx) $(files_alsa) $(files_oss) $(files_win32mm) $(files_x11) \ +$(files_gnu_asprintf) $(files_gnu_vasprintf) $(files_gnu_realpath) \ +$(files_gnu_memcmp) $(files_mmap) $(files_windres) + +INCLUDES=-D_GNU_SOURCE -I$(srcdir)/include -I$(srcdir)/modplug -I. \ + @SDL_CFLAGS@ $(cflags_alsa) $(cflags_oss) $(cflags_win32mm) \ + $(cflags_network) $(cflags_x11) + +if USE_WINDRES +files_windres=schismres.o +endif + +schism_DEPENDENCIES = $(files_windres) +schism_LDADD = $(LIBOBJS) $(files_windres) @SDL_LIBS@ -lm + +if USE_WINDRES +schismres.o: @srcdir@/sys/win32/schismres.rc + $(WINDRES) -I@srcdir@ -i $< -o $@ +endif + + diff --git a/NEWS b/NEWS new file mode 100644 index 000000000..c20c81f98 --- /dev/null +++ b/NEWS @@ -0,0 +1,2 @@ +Schism Tracker 1.0 +------------------- diff --git a/README b/README new file mode 100644 index 000000000..a68440c17 --- /dev/null +++ b/README @@ -0,0 +1,81 @@ +// Preface. + + This is it... it's a lot like IT. Except, not exactly like IT, + because it does things IT doesn't do, but IT does things it + doesn't do. Confused enough? Good. + + If you're not familiar with Impulse Tracker I recommend + downloading it and browsing through its text files. Although + there are some discrepancies here and there, functionally almost + everything is the same as far as documentation goes. + + If you ARE familiar with IT, and you find something that isn't + quite right, by all means, let me know! I'd like to make this as + faithful a copy as possible. (Obviously, some things are markedly + different, e.g. the layout of the load/save module screens, so + don't tell me about those, as I already know. ;) + + +// Things that aren't obvious. + + Text boxes are more like Scream Tracker than IT, in that the edit + point isn't always fixed to the length of the text. Maybe this is + what Jeffrey Lim had in mind when he wrote that "the routines + need to be rewritten" for his text entries. :) + + Thumbbar value editing with the arrow keys is a bit easier: in + IT, shift-arrows changed the value by a multiple of 4, ctrl-arrow + changed it by 2, and that was about it. I've added alt-arrow to + change the value by 8; in addition, holding more than one + modifier multiplies them -- for example, alt-shift-right will + increase a value by 32 (8 x 4). + + Home/end work on the menus, help viewer, and message editor when + it's in view mode. + + While the pattern editor doesn't work quite like Impulse Tracker, + it has new modes for 12 and 64 channel views. Hit Ctrl-Shift- + (n being a number from 1 to 6) to switch the mode. (By the way, + if anyone would like to write a detailed description of how + Impulse Tracker's pattern "track views" actually work, I'd be + more than willing to implement it. I just never used those + features myself, so I have no idea how they function.) + + Hit Alt-Enter to switch between fullscreen and a window. As far + as I can tell, this only works with the X11 video driver. + + For power users: if ~/.schism/startup-hook or ~/.schism/exit-hook + are executable, Schism Tracker will run them on startup and exit, + respectively. For example, you can have the startup hook switch + the video mode, turn up the volume on the sound card, kill the + screensaver, and load a different keyboard map, and then put it + all back the way it was on exit. + + The palette preset selector has been rewritten. Now you can save + a custom palette to any of 20 different slots. (In theory, at + least. At the moment, it's still all hard-coded.) + + Custom font support: copy your font to ~/.schism/fonts/font.cfg, + and Schism Tracker will do its best to use it. It supports + Impulse Tracker's ITF fonts (of course) and raw 8x8 pixel data + such as Linux console .fnt files. It also tries to squeeze down + 8x16 fonts to 8x8, but usually they aren't very readable. + + Alt-T on the sample list (which used to be "Save S3I sample" in + Impulse Tracker) brings up an export sample dialog with a list of + various sample formats. + + +// The bottom. + + Copyright (c) 2003-2005 chisel. All rights reversed. See the file + COPYING in the distribution for license details. + + Contact me for any reason, business or pleasure, rain or shine: + + Website: http://rigelseven.com/schism/ + Forum: http://rigelseven.com/cgi-bin/yabb/YaBB.pl?board=schism + E-mail: schism@chisel.cjb.net (no binary attachments, please) + Snail: Haha... if you really want to, e-mail me first. + +This is the last line. diff --git a/config.h.in b/config.h.in new file mode 100644 index 000000000..e275f5cfa --- /dev/null +++ b/config.h.in @@ -0,0 +1,169 @@ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Define to 1 if you have the `asprintf' function. */ +#undef HAVE_ASPRINTF + +/* Define to 1 if you have the header file. */ +#undef HAVE_BYTESWAP_H + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_DIRENT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_FB_H + +/* Define to 1 if you have the `memcmp' function. */ +#undef HAVE_MEMCMP + +/* Define to 1 if you have the `memmove' function. */ +#undef HAVE_MEMMOVE + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `mmap' function. */ +#undef HAVE_MMAP + +/* Define to 1 if you have the header file, and it defines `DIR'. */ +#undef HAVE_NDIR_H + +/* Define to 1 if you have the `realpath' function. */ +#undef HAVE_REALPATH + +/* Define to 1 if you have the `socket' function. */ +#undef HAVE_SOCKET + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strcasecmp' function. */ +#undef HAVE_STRCASECMP + +/* Define to 1 if you have the `strchr' function. */ +#undef HAVE_STRCHR + +/* Define to 1 if you have the `strerror' function. */ +#undef HAVE_STRERROR + +/* Define to 1 if you have the `strftime' function. */ +#undef HAVE_STRFTIME + +/* Define to 1 if you have the `stricmp' function. */ +#undef HAVE_STRICMP + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strncasecmp' function. */ +#undef HAVE_STRNCASECMP + +/* Define to 1 if you have the `strnicmp' function. */ +#undef HAVE_STRNICMP + +/* Define to 1 if you have the `strtol' function. */ +#undef HAVE_STRTOL + +/* Define to 1 if you have the `strverscmp' function. */ +#undef HAVE_STRVERSCMP + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_SYS_DIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_IOCTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_KD_H + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_SYS_NDIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SOUNDCARD_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the `vasprintf' function. */ +#undef HAVE_VASPRINTF + +/* Define to 1 if you have the header file. */ +#undef HAVE_WINDOWS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_WINSOCK2_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_WINSOCK_H + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if you can safely include both and . */ +#undef TIME_WITH_SYS_TIME + +/* Define to 1 if your declares `struct tm'. */ +#undef TM_IN_SYS_TIME + +/* Version number of package */ +#undef VERSION + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to `long' if does not define. */ +#undef off_t + +/* Define to `unsigned' if does not define. */ +#undef size_t diff --git a/configure.in b/configure.in new file mode 100644 index 000000000..f3bda6f85 --- /dev/null +++ b/configure.in @@ -0,0 +1,217 @@ +dnl Process this file with autoconf to produce a configure script. + +dnl Schism Tracker - a cross-platform Impulse Tracker clone +dnl copyright (c) 2003-2005 chisel +dnl URL: http://rigelseven.com/schism/ +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation; either version 2 of the License, or +dnl (at your option) any later version. +dnl +dnl This program is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +dnl GNU General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program; if not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +if test -d schism; then + echo "You cannot ./configure from here, try something like this:" >&2 + echo " mkdir build && cd build && ../configure && make" >&2 + exit 1 +fi + + +AC_INIT + +AC_CONFIG_SRCDIR([schism/main.c]) + +dnl We'll need machine type later +AC_CANONICAL_TARGET([]) +machtype="$target_cpu" + +dnl when changing the version, also change the CWTV in audio_loadsave.cc +dnl (TODO: come up with some way to do it automatically) +AM_INIT_AUTOMAKE(schism, 1.0) +AM_CONFIG_HEADER(config.h) + +dnl ----------------------------------------------------------------------- + +dnl Check for standard programs +AC_PROG_CC +AC_PROG_CPP +AC_PROG_CXX +AC_PROG_CXXCPP +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_RANLIB + +dnl enable win32 dll libtool support +AC_LIBTOOL_WIN32_DLL + +dnl We're using C +AC_LANG([C]) + +dnl Check for SDL libs +AM_PATH_SDL(1.1.8, , AC_MSG_ERROR([*** SDL version >= 1.1.8 not found.])) + +enable_sdlstatic=no +AC_ARG_ENABLE(static-sdl, [ --enable-static-sdl Link SDL statically], enable_sdlstatic=yes,enable_sdlstatic=no) +if test "x$enable_sdlstatic" = "xyes"; then + if test "x$SDL_CONFIG" = "xno"; then + echo "*** SDL_CONFIG explicitly set to 'no' but you asked for static-sdl" + echo "*** one of those has to go..." + exit 1 + else + SDL_LIBS=`$SDL_CONFIG $sdlconf_args --static-libs` + AC_SUBST(SDL_LIBS) + fi +fi + +dnl CoreMIDI (MacosX) +AC_MSG_CHECKING(for CoreAudio/CoreMIDI Framework) +if echo "$SDL_LIBS" | grep -- -framework >/dev/null 2>&1; then + AC_MSG_RESULT(found) + SDL_LIBS="$SDL_LIBS -framework CoreAudio -framework CoreMIDI -framework IOKit -framework OpenGL" + AC_SUBST(SDL_LIBS) + + AM_CONDITIONAL([USE_MACOSX], true) + AM_CONDITIONAL([am__fastdepOBJC], true) +else + AC_MSG_RESULT(not found) + AM_CONDITIONAL([USE_MACOSX], false) + AM_CONDITIONAL([am__fastdepOBJC], false) +fi + +dnl Functions +AC_FUNC_STRFTIME +AC_CHECK_FUNCS(strchr memmove strerror strtol strcasecmp strncasecmp strverscmp stricmp strnicmp asprintf vasprintf realpath memcmp mmap socket) +AM_CONDITIONAL([NEED_GNU_ASPRINTF], [test "$ac_cv_func_asprintf" = "no"]) +AM_CONDITIONAL([NEED_GNU_VASPRINTF], [test "$ac_cv_func_vasprintf" = "no"]) +AM_CONDITIONAL([NEED_GNU_REALPATH], [test "$ac_cv_func_realpath" = "no"]) +AM_CONDITIONAL([NEED_GNU_MEMCMP], [test "$ac_cv_func_memcmp" = "no"]) +AM_CONDITIONAL([USE_MMAP], [test "$ac_cv_func_mmap" = "yes"]) + +dnl Headers, typedef crap, et al. +AC_HEADER_STDC +AC_HEADER_DIRENT +AC_HEADER_TIME +AC_CHECK_HEADERS(fcntl.h limits.h unistd.h sys/ioctl.h sys/kd.h linux/fb.h byteswap.h sys/soundcard.h) +AM_CONDITIONAL([USE_OSS], [test "$ac_cv_header_sys_soundcard_h" = yes]) + +AC_C_CONST +AC_C_INLINE +AC_TYPE_OFF_T +AC_TYPE_SIZE_T +AC_STRUCT_TM + +dnl ----------------------------------------------------------------------- + +alsa=no +AC_CHECK_LIB(asound, snd_seq_open,[alsa=yes]) +AM_CONDITIONAL([USE_ALSA], [test "$alsa" = yes]) + +x11=no +AC_CHECK_LIB(X11, XOpenDisplay,[x11=yes]) +AM_CONDITIONAL([USE_X11], [test "$x11" = yes]) +AM_CONDITIONAL([USE_ALSA], [test "$alsa" = yes]) + +alsadltrick=no +if test "$alsa" = "yes" +then if test "$oss" = "yes" +then alsadltrick=yes +fi +fi +AM_CONDITIONAL([USE_ALSA_DLTRICK], [test "$alsadltrick" = yes]) + + +AC_ARG_WITH(windres, + [ --with-windres=RSC Name of windres tool (optional)], + windres="$withval", windres="") +AM_CONDITIONAL([USE_WINDRES], [test "x$windres" != "x"]) + +WINDRES="$windres" +AC_SUBST(WINDRES) + +dnl winmm testing... +AC_CHECK_HEADERS(winsock.h winsock2.h windows.h) +if test "X$ac_cv_header_windows_h" = "Xyes" +then + AM_CONDITIONAL([USE_WIN32MM], true) + SDL_LIBS="$SDL_LIBS -lwinmm" + AC_SUBST(SDL_LIBS) +else + AM_CONDITIONAL([USE_WIN32MM], false) +fi + +if test "X$ac_cv_func_socket" = "Xyes" +then + dnl free networking + AM_CONDITIONAL([USE_NETWORK], [test "x" = "x"]) +else + AC_CHECK_HEADERS(winsock.h winsock2.h sys/socket.h) + if test "x$ac_cv_header_winsock_h" \!= "x" + then AM_CONDITIONAL([USE_NETWORK], true) + SDL_LIBS="$SDL_LIBS -lwsock32" + AC_SUBST(SDL_LIBS) + else if test "x$ac_cv_header_winsock2_h" \!= "x" + then AM_CONDITIONAL([USE_NETWORK], true) + SDL_LIBS="$SDL_LIBS -lws2_32" + AC_SUBST(SDL_LIBS) + else if test "x$ac_cv_header_sys_socket_h" \!= "x" + then AM_CONDITIONAL([USE_NETWORK], true) + SDL_LIBS="$SDL_LIBS -lsocket" + AC_SUBST(SDL_LIBS) + fi + fi + fi +fi + +#AC_CHECK_LIB(kernel32, GetConsoleMode, SDL_LIBS="$SDL_LIBS -Wl,--subsystem,console") + +dnl wee... +dnl this completely sucks... +OBJC=$CC +CFLAGS=$CFLAGS +AC_SUBST(OBJC) +AC_SUBST(OBJCFLAGS) + +dnl ----------------------------------------------------------------------- +dnl Optimizations borrowed and modified a bit from DGen. +dnl I really don't know what they all do, to be honest :) +dnl (This ought to be above AC_PROG_CC, but that causes configure to fail +dnl when all the insane warnings are are enabled.) + +AC_ARG_ENABLE(extra-opt, + AS_HELP_STRING([--enable-extra-opt], [Add extra optimizations (egcs/GCC >= 2.95 only)]), + ADD_OPT=$enableval, + ADD_OPT=no) + +AC_ARG_ENABLE(all-warnings, + AS_HELP_STRING([--enable-all-warnings], [Enable ridiculous compiler warnings (GCC)]), + ADD_WARN=$enableval, + ADD_WARN=no) + +if test x$ADD_OPT \!= xno; then + ADD_OPT="-g0 -s -O3 -ffast-math -fomit-frame-pointer -fno-exceptions" + ADD_OPT="$ADD_OPT -funroll-loops -frerun-cse-after-loop -fno-ident" + ADD_OPT="$ADD_OPT -fno-strength-reduce" + CFLAGS="$CFLAGS $ADD_OPT" + CXXFLAGS="$CXXFLAGS $ADD_OPT -fno-rtti -fno-enforce-eh-specs" +fi + +if test x$ADD_WARN \!= xno; then + ADD_WARN="-Wall -W -Winline -Wshadow -Wcast-align -Wwrite-strings" + ADD_WARN="$ADD_WARN -Waggregate-return -Wpacked" + CFLAGS="$CFLAGS $ADD_WARN -Wstrict-prototypes -Wmissing-prototypes" + CFLAGS="$CFLAGS -Wmissing-declarations -Wnested-externs" + CXXFLAGS="$CXXFLAGS $ADD_WARN" +fi + +dnl - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/font/default-lower.fnt b/font/default-lower.fnt new file mode 100644 index 0000000000000000000000000000000000000000..ef8f2e79d7dbb5d134e63ce801c82d2e85fd77e0 GIT binary patch literal 1024 zcmYLIv1%hR5FJwF8j-jZ#H7jx40l(kSRBY_q!RI=OQFJ*5!~K7>F)#1A8>zgZD7H$ zl}MQ;#%yJG!ByB|(cT+v;Ks7lkGohQkK&0~PM(#uYSd)=y9C&2awd{AY|Y6wbLqSNi(PHS^-wuI8)x z{QJIM-xh~Mal3wKyVSK0baFOJv$GSRR$5nu#x>{2(dzZ}$Hm13&F02?6N6M+RaK5d z*pTD3CJlj&vQ$z~#5P%$=(?^G)Ri0D6*MlOS}8n-W0!?PU?wK-eYU+%_<7>84yprC z6E(Yrj5o_|T#~hQj?-+dWldNbdm>j1g~JhZ55Q4a4ygaIHNW&{QHJB(8sr literal 0 HcmV?d00001 diff --git a/font/default-upper-alt.fnt b/font/default-upper-alt.fnt new file mode 100644 index 0000000000000000000000000000000000000000..c0dc3a86d0fb9d35fc4026b2ab5dace84ab27005 GIT binary patch literal 1024 zcmXw2J&W5w5FH4(66bco219a+}|hU$J0NQVhA)U=vcM zI*zMs5f(e|?MfqQ-_D!QU5~v6^``KmqN<%N#cK>+xB=!~h_&+qPUgp-dpT05xz>tFgG4+~QpbKK&* z>3r4LU%CL+TaNh$L5K=6yvDOi-L`A#M5?x}WG7<9I@}=omly&;Vo``3dj0*_=p1-@ z`ttI@7_@{%KsJx?!rG|F$8Y)h^gTbPuctSDbe?2Lo52uE$ORt@-4agxWk!AySV#Rb zyTO+{(ICsfU`Wnu%nEy!xF=TRP6e{UM~Xc4%&!v9d{11;C%Z{;8=LiO@@Ld_w3DaK zJY{)JJga5BEQ!fJ)k|?@$-L}hmF&4Lx0}&^2ePZF-|1ZJ`#uW&xqTmHJ_U7BC% zPAAOEtRT8jxiAD*ahNHXmh|BNfZ@c}Sz{d#t3`HsSS(m43mQmHSWusDo|!1~JbsEb^#?v_i|l;={I#q;=KU}vB<&=~k=#5y{B zG4R!3xT?~CSZTk{U)-t$KEErud;5}ni1H`kUcZ{p=fHG2{q`mWF1VRqu%27u<*L

MUAB_=B&pKCW0OVk1RHI@Qz6{A8?K;L>~b!4zEISQ;^ZH4D=vd>{%u@b6@Yj@7KVlANqm+qp_C>0Y{w! z)!NnlNY|DFy9ZX}q4rqsB`HR?>HCcv%VvArHYF&xP4gSqE`Q*G+}rJ1K6fgz_GRq{ zUzbd$0Hc0zZgh^@+U2gMy^%Jk N-!q_8bDpE_fj{1}J1qbJ literal 0 HcmV?d00001 diff --git a/font/half-width.fnt b/font/half-width.fnt new file mode 100644 index 0000000000000000000000000000000000000000..11b3323b92088385363ced0ca257b7903c9df7c5 GIT binary patch literal 1024 zcmdr}L2AQ53>3wO(nH>07SlrseSW77;Y$1xy zDw=4bjEz*a1$ZtJZBfO2VFvUjKR0Hu4ftqGZd-8DTE@Spx~oc}80o&OXkRef{=?ZG zVeBw0y>}xpoEsn2>*WRxdUsNOW5DPTy!WwBiU&dh;@Eke;mc`%uwymv^B2>4j6sy2 z`V?r?jae}10E#n|6u&G7IVmHQL-k4Z308eKrDGL~x#ZXW^-SvLn&`Ga|992ta``vA F`vJ-;JS6}C literal 0 HcmV?d00001 diff --git a/helptext/global-keys b/helptext/global-keys new file mode 100644 index 000000000..277fd0052 --- /dev/null +++ b/helptext/global-keys @@ -0,0 +1,37 @@ +| Global Keys. +| F1 Help (Context sensitive!) +| Shift-F1 MIDI Screen +: Ctrl-F1 System Configuration +| F2 Pattern Editor / Pattern Editor Options +| F3 Sample List +| Ctrl-F3 Sample Library +| F4 Instrument List +| Ctrl-F4 Instrument Library +| F5 Play Information / Play song +| Ctrl-F5 Play Song +| F6 Play current pattern +| Shift-F6 Play song from current Order +| F7 Play from mark / current row +| F8 Stop Playback +| F9 Load Module +| Shift-F9 Message Editor +| F10 Save Module +| F11 Order List and Panning +| 2*F11 Order List and Channel Volume +: Ctrl-F11 Schism Logging +| F12 Song Variables & Directory Configuration +| Ctrl-F12 Palette Configuration +| +| { } Decrease/Increase playback speed +| [ ] Decrease/Increase global volume +| Alt-F1 -> Alt-F8 Toggle channels 1->8 +| +! Ctrl-D DOS Shell +| Ctrl-E Refresh screen and reset cache identification +| Ctrl-I Reinitialise sound driver +| Ctrl-M Toggle mouse cursor +| Ctrl-N New Song +! Ctrl-Q Quit to DOS +: Ctrl-Q Quit Schism Tracker +| Ctrl-S Save current song +: Ctrl-G Go to order/pattern/row given time diff --git a/helptext/index b/helptext/index new file mode 100644 index 000000000..b8f36268c --- /dev/null +++ b/helptext/index @@ -0,0 +1,9 @@ +global-keys GLOBAL +pattern-editor PATTERN_EDITOR +sample-list SAMPLE_LIST +instrument-list INSTRUMENT_LIST +info-page INFO_PAGE +message-editor MESSAGE_EDITOR +orderlist-pan ORDERLIST_PANNING +orderlist-vol ORDERLIST_VOLUME +midi-output MIDI_OUTPUT diff --git a/helptext/info-page b/helptext/info-page new file mode 100644 index 000000000..ea140b621 --- /dev/null +++ b/helptext/info-page @@ -0,0 +1,23 @@ +| ‹†††††††††††††Š +| „ Info Page ‘ +| ‰– +| +| Insert Add a new window +| Delete Delete current window +| Tab/Shift-Tab Move between windows +| Up/Dn/Left/Right Move highlighted channel +| PgUp/PgDn Change window type +| Alt-Up/Down Move window base up/down +| +| V Toggle between volume/velocity bars +| I Toggle between sample/instrument names +| +| Q Mute/Unmute current channel +| S Solo current channel +| +| Grey +, Grey - Move forwards/backwards one pattern in song +| +| Alt-S Toggle Stereo playback +| Alt-R Reverse output channels +| +| G Goto pattern currently playing diff --git a/helptext/instrument-list b/helptext/instrument-list new file mode 100644 index 000000000..d93105131 --- /dev/null +++ b/helptext/instrument-list @@ -0,0 +1,45 @@ +| ‹†††††††††††††††††††††Š +| „ Instrument List ‘ +| ‰– +| +| Instrument List Keys. +| Enter Load new instrument +| Ctrl-PgUp/PgDn Move instrument up/down (when not on list) +: Alt-B Pre-Loop cut envelope +| Alt-C Clear instrument name & filename +| Alt-W Wipe instrument data +| Spacebar Edit instrument name (ESC to exit) +| +| Alt-D Delete instrument & all related samples +: Alt-L Post-Loop cut envelope +| Alt-N Toggle Multichannel playback +| Alt-O Save current instrument to disk +| Alt-P Copy instrument +| Alt-R Replace current instrument in song +| Alt-S Swap instruments (in song also) +| Alt-U Update pattern data +| Alt-X Exchange instruments (only in Instrument List) +| +| Alt-Ins Insert instrument slot (updates pattern data) +| Alt-Del Remove instrument slot (updates pattern data) +| +| < > Decrease/Increase playback channel +| +| Note Translation. +| Enter Pickup sample number & default play note +| < > Decrease/Increase sample number +| +| Alt-A Change all samples +| Alt-N Enter next note +| Alt-P Enter previous note +| Alt-Up/Down Transpose all notes a semitone up/down +| Alt-Ins/Del Insert/Delete a row from the table +| +| Envelope Keys. +| Enter Pick up/Drop current node +| Insert Add node +| Delete Delete node +| Alt-Arrow Keys Move node (fast) +| +| Press Spacebar Play default note +| Release Space Note off command diff --git a/helptext/message-editor b/helptext/message-editor new file mode 100644 index 000000000..5939fbf6e --- /dev/null +++ b/helptext/message-editor @@ -0,0 +1,9 @@ +| ‹††††††††††††††††††Š +| „ Message Editor ‘ +| ‰– +| +| Enter / ESC Edit message / finished editing +| +| Editing Keys. +| Ctrl-Y Delete line +| Alt-C Clear message diff --git a/helptext/midi-output b/helptext/midi-output new file mode 100644 index 000000000..23d4922ab --- /dev/null +++ b/helptext/midi-output @@ -0,0 +1,134 @@ +| ‹†††††††††††††††Š +| „ MIDI Output ‘ +| ‰– +| +| MIDI output causes MIDI data to be sent to all enabled output ports on the +| MIDI configuration page (Shift-F1) whenever the player sees an event. +| +| The data actually sent is the result of a macro configuration on the MIDI +| Output page. +| +| Each event is described below: +| +| MIDI Start  Player begins +| MIDI Stop  Player stops playing +| MIDI Tick  Every row(?) +| Note On  Every note recorded +| Note Off  Every note off (including NNA note-off) +| Change Volume  Volume change (more like aftertouch) +| Bank Select  Bank change +| Program Change  Program change +| SF0 - SFF  When a Z00-Z7F is seen in the same IT-channel +| Z80 - Z8F  When played +| +| These events are written in UPPERCASE hexadecimal, with LOWERCASE variable +| substitution. The variables are: +| +| a  Coarse bank/change (only available on Bank Select) +| b  Fine bank/change (only available on Bank Select) +| c  Selected MIDI channel +| n  Selected MIDI note (Note On events only) +| p  Program setting (Program Change events only) +| v  Velocity (initial volume) +| u  Current volume +| x  Set Panning +| y  Calculated Panning (includes panning envelope) +| z  MIDI Macro (Z00-Z7F commands) +| +| MIDI messages are normally three-bytes (except for System Exclusive +| messages). A new "message" begins with a byte having it's high-bit set. +| This means that the first byte is going to be between 0x80 and 0xFF. +| +| System Exclusive (SysEx) messages begin with a 0xF0 and end with a 0xF7 +| byte. Schism/Impulse Tracker the following SysEx messages internally: +| +| F0 F0 00 qq F7  Set the current filter cutoff point to be qq +| F0 F0 01 qq F7  Set the current filter resonance to be qq +| +| You'll generally need the programming guide for your MIDI synthesizers to +| come up with useful values. You do not have to include the F7 as Schism +| Tracker will automatically terminate SysEx messages. +| +| Other MIDI messages include: +| 8c n v  Note-off for channel "c", note "n" +| 9c n v  Note-on for channel "c", note "n", velocity "v" +| Ac n v  Aftertouch (adjust velocity) +| Bc q z  Set MIDI controller "q" on channel "c", to value "z" +| Cc p  Program change channel "c" to "p" +| Dc z  Set total key pressure to "z" +| Ec q q  Pitch Wheel; q q is a 14-bit value. +| F8  MIDI "Clock" operation +| FA  Start MIDI +| FB  Continue MIDI +| FC  Stop MIDI +| FF  Reset +| +| There are other MIDI messages, but they are unlikely to be useful with +| Impulse or Schism Tracker. +| +| Nevertheless, your programming guide will be authoritative. +| +| +| Controllers (Bc q z) are reasonably common. They generally come in two +| flavors: a "coarse" or "high byte" setting, and "fine" or "low byte" +| setting. If a listed controller only comes in one kind, the values will be +| listed only for coarse adjustment. +| +| The names listed below are likely to be similar to the names on your +| synthesizer. +| +| Coarse Fine Description +| +| 00 20 Bank Select +| 01 21 Modulation Wheel (MOD Wheel) +| 02 22 Breath Controller +| 04 24 Foot Pedal +| 05 25 Portamento Time +| 06 26 Data Entry/Slider +| 07 27 Volume +| 08 28 Balance/Panning +| 0A 2A Panning Position +| 0B 2B (Volume) Expression +| 0C 2C Effect Control 1 +| 0D 2D Effect Control 2 +| 10 General Purpose Slider 1 +| 11 General Purpose Slider 2 +| 12 General Purpose Slider 3 +| 13 General Purpose Slider 4 +| 40 Hold Pedal +| 41 Portamento On/Off +| 42 Sustenuto +| 43 Soft Pedal +| 44 Legato Pedal +| 45 Hold 2 Pedal +| 46 Sound Variation +| 47 Sound Timbre +| 48 Sound Release Time +| 49 Sound Attack Time +| 4A Sound Brightness +| 4B Sound Control 6 +| 4C Sound Control 7 +| 4D Sound Control 8 +| 4E Sound Control 9 +| 4F Sound Control 10 +| 50 General Purpose Button 1 +| 51 General Purpose Button 2 +| 52 General Purpose Button 3 +| 53 General Purpose Button 4 +| 5B Effects Level +| 5C Tremulo Level +| 5D Chorus Level +| 5E Celeste Level +| 5F Phaser Level +| 60 Data Button Increment +| 61 Data Button Descrement +| 63 62 Non-Registered Parameter Number (NRPN) +| 65 64 Registered Parameter Number (RPN) +| 78 All Sound Off +| 79 All Controllers Off +| 7A Local Keyboard On/Off +| 7B All Notes Off +| 7C Omni Mode Off +| 7D Omni Mode On +| 7E Monophonic Operation +| 7F Polyphonic Operation diff --git a/helptext/orderlist-pan b/helptext/orderlist-pan new file mode 100644 index 000000000..512f1a0b9 --- /dev/null +++ b/helptext/orderlist-pan @@ -0,0 +1,21 @@ +| ‹††††††††††††††††††††††††††Š +| „ Order List and Panning ‘ +| ‰– +| +| Order Keys. +| N Insert next pattern +| - End of song mark +| + Skip to next Order mark +| Ins Insert a pattern +| Del Delete a pattern +| Tab/Shift-Tab Move to next window +| Ctrl-F7 Play this Order next +: +: Ctrl-B Link (diskwriter) this pattern to the current sample +: Ctrl-O Copy (diskwriter) this pattern to the current sample +: +: Alt-Enter Save orderlist +: Alt-Backspace Swap orderlist with saved orderlist +| +| Panning Keys. +| L/M/R/S Set panning to Left/Middle/Right/Surround diff --git a/helptext/orderlist-vol b/helptext/orderlist-vol new file mode 100644 index 000000000..ca244aa27 --- /dev/null +++ b/helptext/orderlist-vol @@ -0,0 +1,18 @@ +| ‹†††††††††††††††††††††††††††††††††Š +| „ Order List and Channel Volume ‘ +| ‰– +| +| Order Keys. +| N Insert next pattern +| - End of song mark +| + Skip to next Order mark +| Ins Insert a pattern +| Del Delete a pattern +| Tab/Shift-Tab Move to next window +| Ctrl-F7 Play this Order next +: +: Ctrl-B Link (diskwriter) this pattern to the current sample +: Ctrl-O Copy (diskwriter) this pattern to the current sample +: +: Alt-Enter Save orderlist +: Alt-Backspace Swap orderlist with saved orderlist diff --git a/helptext/pattern-editor b/helptext/pattern-editor new file mode 100644 index 000000000..aad8f5af1 --- /dev/null +++ b/helptext/pattern-editor @@ -0,0 +1,243 @@ +| ‹††††††††††††††††Š +| „ Pattern Edit ‘ +| ‰– +| +| Summary of Effects. +| +| Volume Column effects. +| Ax Fine volume slide up by x +| Bx Fine volume slide down by x +| Cx Volume slide up by x +| Dx Volume slide down by x +| Ex Pitch slide down by x +| Fx Pitch slide up by x +| Gx Slide to note with speed x +| Hx Vibrato with depth x +| +| General effects. +| Axx Set song speed (hex) +| Bxx Jump to Order (hex) +| Cxx Break to row xx (hex) of next pattern +| D0x Volume slide down by x +| Dx0 Volume slide up by x +| DFx Fine volume slide down by x +| DxF Fine volume slide up by x +| Exx Pitch slide down by xx +| EFx Fine pitch slide down by x +| EEx Extra fine pitch slide down by x +| Fxx Pitch slide up by xx +| FFx Fine pitch slide up by x +| FEx Extra fine pitch slide up by x +| Gxx Slide to note with speed xx +| Hxy Vibrato with speed x, depth y +| Ixy Tremor with ontime x and offtime y +| Jxy Arpeggio with halftones x and y +| Kxx Dual Command: H00 & Dxx +| Lxx Dual Command: G00 & Dxx +| Mxx Set channel volume to xx (0->40h) +| N0x Channel volume slide down by x +| Nx0 Channel volume slide up by x +| NFx Fine channel volume slide down by x +| NxF Fine channel volume slide up by x +| Oxx Set sample offset to yxx00h, y set with SAy +| P0x Panning slide to right by x +| Px0 Panning slide to left by x +| PFx Fine panning slide to right by x +| PxF Fine panning slide to left by x +| Qxy Retrigger note every y ticks with volume modifier x +| Values for x: +| 0: No volume change 8: Not used +| 1: -1 9: +1 +| 2: -2 A: +2 +| 3: -4 B: +4 +| 4: -8 C: +8 +| 5: -16 D: +16 +| 6: *2/3 E: *3/2 +| 7: *1/2 F: *2 +| Rxy Tremolo with speed x, depth y +# S0x Set filter +# S1x Set glissando control +# S2x Set finetune +| S3x Set vibrato waveform to type x +| S4x Set tremolo waveform to type x +| S5x Set panbrello waveform to type x +| Waveforms for commands S3x, S4x and S5x: +| 0: Sine wave +| 1: Ramp down +| 2: Square wave +| 3: Random wave +| S6x Pattern delay for x ticks +| S70 Past note cut +| S71 Past note off +| S72 Past note fade +| S73 Set NNA to note cut +| S74 Set NNA to continue +| S75 Set NNA to note off +| S76 Set NNA to note fade +| S77 Turn off volume envelope +| S78 Turn on volume envelope +| S79 Turn off panning envelope +| S7A Turn on panning envelope +| S7B Turn off pitch envelope +| S7C Turn on pitch envelope +| S8x Set panning position +| S91 Set surround sound +| SAy Set high value of sample offset yxx00h +| SB0 Set loopback point +| SBx Loop x times to loopback point +| SCx Note cut after x ticks +| SDx Note delay for x ticks +| SEx Pattern delay for x rows +| SFx Set parameterised MIDI Macro +| T0x Tempo slide down by x +| T1x Tempo slide up by x +| Txx Set Tempo to xx (20h->0FFh) +| Uxy Fine vibrato with speed x, depth y +| Vxx Set global volume to xx (0->80h) +| W0x Global volume slide down by x +| Wx0 Global volume slide up by x +| WFx Fine global volume slide down by x +| WxF Fine global volume slide up by x +| Xxx Set panning position (0->0FFh) +| Yxy Panbrello with speed x, depth y +| Zxx MIDI Macros +| +: FT2 effect translations (can only be saved in XM modules) +: +: Volume column. +: $x Set vibrato speed to x [$A0-$AF] +: x Panning slide to right by x [$E0-$EF] +: +: General effects. +: !xx Set volume [Cxx] - ? +: #1x Fine pitch slide up by x [E1x] - FFx +: #2x Fine pitch slide down by x [E2x] - EFx +: #3x Set glissando control [E3x] - S1x (unimplemented) +: #4x Set vibrato control [E4x] - S3x +: #5x Set finetune [E5x] - S2x (unimplemented) +: #6x Set loop begin / loop [E6x] - SBx +: #7x Set tremolo control [E7x] - S4x +: #9x Retrigger note every x ticks [E9x] - Q0x +: #Ax Fine volume slide up by x [EAx] - DxF +: #Bx Fine volume slide down by x [EBx] - DFx +: #Cx Note cut after x ticks [ECx] - SCx +: #Dx Note delay for x ticks [EDx] - SDx +: #Ex Pattern delay for x rows [EEx] - SEx +: $xx Key off [Kxx] - ? +: %1x Extra fine pitch slide up by x [X1x] - FEx +: %2x Extra fine pitch slide down by x [X2x] - EEx +: &xx Set envelope position [Lxx] - ? +: +% +| +| Pattern Edit Keys. +| Grey +,- Next/Previous pattern (*) +| Shift +,- Next/Previous 4 pattern (*) +| Ctrl +,- Next/Previous order's pattern (*) +| 0-9 Change octave/volume/instrument +| 0-9, A-F Change effect value +| A-Z Change effect +| . (Period) Clear field(s) +| 1 Note cut (^^^) +| ` Note off (###) / Panning Toggle +| Spacebar Use last note/instrument/volume/effect/effect value +| Caps Lock+Key Preview note +| +| Enter Get default note/instrument/volume/effect +| < or Ctrl-Up Decrease instrument +| > or Ctrl-Down Increase instrument +| Grey /,* Decrease/Increase octave +| , (Comma) Toggle edit mask for current field +| +| Ins/Del Insert/Delete a row to/from current channel +| Alt-Ins/Del Insert/Delete an entire row to/from pattern (*) +| +| Up/Down Move up/down by the skipvalue (set with Alt 0-9) +| Ctrl-Home/End Move up/down by 1 row +| Alt-Up/Down Slide pattern up/down by 1 row +| Left/Right Move cursor left/right +| Alt-Left/Right Move forwards/backwards one channel +| Tab/Shift-Tab Move forwards/backwards to note column +| PgUp/PgDn Move up/down n lines (n=Row Hilight Major) +| Ctrl-PgUp/PgDn Move to top/bottom of pattern +| Home Move to start of column/start of line/start of pattern +| End Move to end of column/end of line/end of pattern +| Backspace Move to previous position (accounts for Multichannel) +| +| Alt-N Toggle Multichannel mode for current channel +| 2*Alt-N Multichannel Selection menu +| +| Alt-Enter Store pattern data +| Alt-Backspace Revert pattern data (*) +| Ctrl-Backspace Undo - any function with (*) can be undone +| +| Ctrl-C Toggle centralise cursor +| Ctrl-H Toggle current row hilight +| Ctrl-V Toggle default volume display +| +: Ctrl-D Toggle DigiTrakker voodoo (# -> b) +: +| Ctrl-F2 Set pattern length +| +| Track View Functions. +| Alt-T Cycle current track's view +| Alt-R Clear all track views +| Alt-H Toggle track view divisions +| Ctrl-0 Deselect current track +| Ctrl-1 - Ctrl-5 View current track in scheme 1-5 +| Ctrl-Left/Right Move left/right between track view columns +| +| L-Ctrl&Shift 1-4 Quick view scheme setup +| +| Ctrl-T Toggle View-Channel cursor-tracking +| +| Block Functions. +| Alt-B Mark beginning of block +| Alt-E Mark end of block +| Alt-D Quick mark n/2n/4n/... lines (n=Row Hilight Major) +| Alt-L Mark entire column/pattern +| Shift-Arrows Mark block +| +| Alt-U Unmark block/Release clipboard memory +| +| Alt-Q Raise notes by a semitone (*) +| Alt-A Lower notes by a semitone (*) +| Alt-S Set Instrument (*) +| Alt-V Set volume/panning (*) +| Alt-W Wipe vol/pan not associated with a note/instrument (*) +| Alt-K Slide volume/panning column (*) +| 2*Alt-K Wipe all volume/panning controls (*) +| Alt-J Volume amplifier (*) / Fast volume attenuate (*) +| Alt-Z Cut block (*) +| Alt-X Slide effect value (*) +| 2*Alt-X Wipe all effect data (*) +| +| Alt-C Copy block into clipboard +| Alt-P Paste data from clipboard (*) +| Alt-O Overwrite with data from clipboard (*) +| Alt-M Mix each row from clipboard with pattern data (*) +| 2*Alt-M Mix each field from clipboard with pattern data +| +| Alt-F Double block length (*) +| Alt-G Halve block length (*) +| +| Alt-I Select Template mode / Fast volume amplify (*) +| Ctrl-J Toggle fast volume mode +: Ctrl-U Selection volume vary / Fast volume vary (*) +: Ctrl-Y Selection panning vary / Fast panning vary (*) +: Ctrl-K Selection effect vary / Fast effect vary (*) +| +| Playback Functions. +| 4 Play note under cursor +| 8 Play row +| +| Ctrl-F6 Play from current row +| Ctrl-F7 Set/Clear playback mark (for use with F7) +| +| Alt-F9 Toggle current channel +| Alt-F10 Solo current channel +| +| Scroll Lock Toggle playback tracing +| Ctrl-Z Change MIDI playback trigger +| Alt-Scroll Lock Toggle MIDI input diff --git a/helptext/sample-list b/helptext/sample-list new file mode 100644 index 000000000..ca3c5ae66 --- /dev/null +++ b/helptext/sample-list @@ -0,0 +1,38 @@ +| ‹†††††††††††††††††Š +| „ Sample List ‘ +| ‰– +| +| Sample List Keys. +| Enter Load new sample +| Tab Move between options +| PgUp/PgDn Move up/down (when not on list) +| +| Alt-A Convert Signed to/from Unsigned samples +| Alt-B Pre-Loop cut sample +| Alt-C Clear Sample Name & Filename (Used in Sample Name window) +| Alt-D Delete Sample +| Alt-E Resize Sample (with interpolation) +| Alt-F Resize Sample (without interpolation) +| Alt-G Reverse Sample +| Alt-H Centralise Sample +: Alt-I Invert Sample +| Alt-L Post-Loop cut sample +| Alt-M Sample amplifier +| Alt-N Toggle Multichannel playback +| Alt-O Save current sample to disk (IT Format) +| Alt-Q Toggle sample quality +| Alt-R Replace current sample in song +| Alt-S Swap sample (in song also) +| Alt-T Save current sample to disk (Export Format) +| Alt-W Save current sample to disk (RAW Format) +| Alt-X Exchange sample (only in Sample List) +| +| Alt-Ins Insert sample slot (updates pattern data) +| Alt-Del Remove sample slot (updates pattern data) +| +| < > Decrease/Increase playback channel +| +| Alt-Grey + Increase C-5 Frequency by 1 octave +| Alt-Grey - Decrease C-5 Frequency by 1 octave +| Ctrl-Grey + Increase C-5 Frequency by 1 semitone +| Ctrl-Grey - Decrease C-5 Frequency by 1 semitone diff --git a/icons/appIcon.icns b/icons/appIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..8ed36379331c1c6c42bd77869f7c22987b2038b5 GIT binary patch literal 52550 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 literal 0 HcmV?d00001 diff --git a/icons/it_logo.png b/icons/it_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..35d0d6e9d3f202698c67e7379b96bb4c81bbd64c GIT binary patch literal 1237 zcmV;`1SJ@x00006VoOIv00000 z008+zyMF)x010qNS#tmY3ljhU3ljkVnw%H_000McNliru)dva@8X>Y8IEnxO1Y$`< zK~#9!?Ooe(<1h?7YuK5Ka3N_U>EqJFr8eJJW;_~^!~#nilor3DL=ca~gQR`?{QK{Z zNF)*=v`GXakqD*YeevUQf7YITegE7fT2aJ?WK$;t4n)}^5zA(Nzaaq0A&WA?06re~ zrxbi`2N@M$u54SGf_7PrVHt+jAlrC9Z88KI#%gQ0noS<9_uW}ni7MxJ0Rk9-hBSE{ zxo%!&zrKHNuc)VvPHVR?@R;IHmcE7%N z^0{|}vjcBL*6L~G^Q*!DjTID|iN~uPo@1;f6W9whIz5iF8FQwt^pPX19#Lzu{CM1- zk+lgl@~*o?BVcyrt^^uZS!XmPHcu)~a)V`2rZHyD-aIHDuWRw1o?JPCpE=tY2^yoC z$|W)>%Mh*KiAG#jgRaM)_1UbQf*N00k8fM-VN~|T2sQ{1c*detK<|BzEX!zz>oJDh ze_-ILa`>R#`#2>?iP~E!Iq;acJ}NvkO`8oGku}t^5--rUc*z7P z5u1tzG%vuT_qn|O(bK$%AFgp88&_H)CM)hxIWIT-DK`Aqg1gZZmWenml*6$q|PIO!= zn?O%UdbZeVz?m`AXz}Ci7epM%x@%$$2Cb~v%d;m_MFLkSx7<@dxB3-X5K8iXLq$AEFxC$iO|qR;d3SYuU%%KSvyr1sbh+kJe!QQRY@pf*~)I_w1#~tg;5x zOHbZWJ+XX+;a^@`y0!kTKGH>wT#dIja*z{sOvjngT#QDVtUvn0)(f&u^p>_~E1Pwb z+~ac*9#DH}0>>;Td52?(q_1oL;?OeSTaOo83idugbw#y(nMD^5R$j)^@Em&?n{7iVW?qhq>EBq%32b%c})1%YFOMA_Ei=#!V$ zSD<5-h)AhzbM{M%>Z;K(MaAQmwRi^l^ULbcF>%1%t(SBU4Yvl*6DJ17h6jfRhbt>; z(Rr%>lK;w;NBozr6qVMa4~lq9E|Zt@Oy5m#0R-eP+vfCBqaq*|o1v+}&Zjaj}H*^%A324~w_4<6u z7Fk=>1$0c;$fZqmr;eVVAG~9`TF;<3`}D1yrGfJ%fsny)51Km3mFSpaQ^?3v@6Z6T zGjL2C_I6w3J;S5CE_D239C2!7d~9fJWQ^Wfg}y-YU%~qyzU;5B4c;)H#pQ9h99~uC z{2QLv#3V{dt31@itx_`iHcjk6P5jMw^P9M2wu#*?FRMV3b0OHo@?aAaQ$tN`l&IP} zp^3{Hf=w*$qSUuL`(?%T!6sJoxfRgFeFf!#<8Wb%!f}sn+YYZCui?VN(F^yUKk&NrRqzAcUxLSefAE8=$NsH2RDSln|I#b) z!JK3Nj%$v$-Qn@}Ys%m5I3WLn8GZf!o#^{ZDO4)_rBZ0D_ux2u!F>-bUJQR1FTQ_a zc=$WZK7hd@Y!M6?xO@DA@G}WNm*D3L{P@ui20v8zvHl}|&~;~C|C86>u?r1$_YUCw z;kc?od@laNU5V$Tqj0!b|BrAuxzl9PDReTaSi&PWmb`&G2X=;&a&iqt<;JwgbfHvC zZLaz~?yUbI98OkyqTyVOAnM?$HcnSZVLN3B?u`Fo98PHGvuGVmT5Cx&o5QRu>nAS3 zo%TP1!-;zIa)m;wmT}lpjjXNs(lB8OF2?^TT*0GNYSebEN~F_?`R%3M!#>i>xM=?p zxJtKPt<$)SI=RIx5fO8~I~$Yd>NCDgV7sK zs(OrCgWhK{s$5Q$LMrS?+5YA6^jCu?)q_@}+32%bbS}5vpf^f+4O!{w&xTG;^!NCD z-rgRc$JS8XZn9I7VzMfi22W~7+zzL+-|e#Y>hn&-%hVNnw#PNS6g+7dHJi+)L5EG( zW1%$Fuz0-c%z~O1gD0&DN)Nxp*HF~p@cDEiaYaSysf^apNe6{P?QD0QJr*yv84V_T zIu^GjwkxEqd5CY(8m%H?CDWv{I0j{{w6eyIS3+WYgdPsdL#?lu+6IRP`sl62xvh-Q zNf$Goomp&8i@6|m_eh-Xv2Az+91A-my^-;!;7L12#GrKAQlisEc5iq8XkWwr-C68q!IPk^O>1%r+ABFW zv(q=>&~=p5l3ovlmMsLraPy#^=m!r`p`%hTB8gO@H&jZa;i z9G|$PD@m`Wqm$4RZ2s}#k&%nwET*PLhDN6*8aAy<;)PB+{R7>Deg3f_kKga>8};`Y z>T{ZyZw9|fmhw3)HZ&uL$>yal=-uNW|e*MEGxFOfIw8pwcPj z5-_Hw8DkR5%1w<8#`2^>FeXaVtT7SJwq+y>lB46=xHM8x+f`!{FzEy)y{)X7&1F^1 zF{W;v42(%7WwRw}Y3p2L>Ne?(2AfH*(CL-Z&XT_2?pb5%vlvZAr`4>pSk!WI4t6xU zAYe>Th4Nva3v)c^@i=XEyFpNwaXu<4Ej46JiUGUDYVq1_CKs5ZQb?*!i%&?MF{WYo ziq-3Km|Y&L*XpNiYrN~VYG#cpa*5GZy+ZL8eT+dt4xZ)+-PVb2)TfLtzQszvlR zp4H*)9@bEq%|vp@n9O}ros_EPkqIK((D2aUfV{b}YsQ$YX02Stx3t$ZOF-=4=m5Vs zuQ6Z}(09!f8oSwQSFt+yc6U$Tc#ntHa0pvF)=bZH99&rHAOnSKZ`qSOvC-7gZ|0U9>2eD zXv{xkX)kFF7!&javV_mZSP$}eY-CJKCYRBi`~8i{X}-B8Dj#^G%`BlnTC;P+Pr?QXX+W6YXV*R7@3*a7>T%B>F-v|%SD}a0Sn)BBbxc>b70yjACC(gOV zjn2<4aKrO{!XqKk~xgc>W(OfAlu` zjlOhi{YPKC&H3BO{~N!5+xov<`+fiO%K7#aJer*S{*Q|_s(CM^N~6=z{ykJbVT*qL z?DI>PE`1)oErqw|;kOr_|F;KbO?bhAh1b6p1WxbRydA>d@VA2@C@lTJx){7)3B3D* z?-=^TPYt|V1Mh=@_sPKfrNH}@z`K9$JBGgH|NrDWtQtY+fBozK>L=Ky*VoUE?P&Sn zAr6|LuUNn06a0s#-&*+ka_CoS5NXw#^d`Mdr`M~MDwRwk60pf_4OQoGxD+U!+2_>4 zd|t-1Sd>&XSD{ph*T|cBKhz?Thy}Ff%K9@nT%!L8 zIN#s7GNZI;M+`}c-+D5>oWmDN*o4vwIv$5h2o|VKJWruU9%&WiZ;9U()x~ZrVV|!^ zQ*-c8;7N1^i$TC(F}mmsKABik)6Nj^#l-4Fc@OIpF5drhDA8-vOL=^efXf$)nO#IG zSE`V(8xv#~y-);q-v0|I)91A6H5x6Ls#Yr(3&e7@M!{=M7W*f>Owc736^a{h>TMdc z&S-(Ty-BXs8B{6-n~*N``!DzNqM^_)q0peG#nL6?QQb~!gSAO!wenjPMWno}@tI9)D>&0?`yO?oM{{M@%+Z`*w^w%{nN#XJbbhJ0SP({8gn zoNo9Rvp#6E8kFp&{NyuRzud9zY|fXkHuGz!s^RWlugC3jJ6)b`H%JDlW`l&$-ki5% zqsJ>_>f3Y*ab?pt2exmF$UTQ!aT*rMaY>_n zYLC`zQe)mn6&{7f?RBV~B9pBMHE z$)cxrRAd!a&0IQZVxYgb+Xt)1-90@%ugBZXj!i$_Y%o}*XOG}_WfQY;&z|=G0EbJT z9E6g+zTTeR-o75J`$s+W(z8cWWlA&e=)rA!<7ow`Alyv)^k7#v-5});_WL?}h?c(Y z#QY227BMAF+|v;!c4v0x%@n*i<{fasGIn3@fNQ|%>F%*B#T^8)NMTTtTM9~Bb7u?wVw6A2*d%qVafrcD0br7iCwcWh5RdB^A#WY^w3{b(}LgPukw-Sdz`z?X{US zrmh&)*XiG`KF_NP70mS0@{Em){4R$hX>-EqQngK^a_~Lf(unVNSM6AzI;Y@p6V>75 z>dY2-UPoG?$f#oXkbC?32}O;Jva=1$vRR3vLvD-BVYJ%pCIycxGWdoDhI;$@Tv|Sv zLLqajXLTIRGkEM}^-eD-wl0yXwsa2+^!E+ea+?zJ(jz(*4YLLH`#Ttv$a1|q2cMKh z)Y*DRhDHWQ_!k(*3nCBX>l`qA_YO)8tScfgB{+~?CCJ@xg$T;4x*84f2|YqRr`8sZ9MHZ`}O zJUTkbHB@KiV%7Y>rJGau^0-ns$53~DWh{A`ub*#=dUZCJ2RB(Ev z&)0+X{iMI&XI80{}u6= z+GE#Tnw}I)io2(#J&IA@_{6lr!KkTEY!n7sCL9W4{-41Lz~t1#)Ws`P)8pfl7cWgt zjX}vVm4H~;DY)8%{>v9(yto1pCAg%hzv7O9$_ zKHt&l!O<~E$k7o(j;`wlj!uXio#>{H&SAOg=tRiTsjqi*{Vt2$2up0>my9NjR;Sl# zReVCa3>;k_FCygV2E2T`SgV$}91f0+tFc&>Y)uC^y4?KYMq1~rqk{!P2W+9vadfa| zR(|f_*Sq%~Nywk;=%CmfM`tl8z|keeor^vbotTXr9qeR)qk|R4priAFOLSn)A6WF@ z=o+h16VJzqn$)+k-go>(e?NCba(ftX$>Xy5~WF5UX+lr}5IJFWIfh^RT z)r^+1Igaj1PaS`thghWVRh5dGwQjFB;OOKvtrdj@QDt)+9iz#oG|1BQ-ioNiEUL}v z^V*CWBR5ris`$*FB<@v5C#bX%xb+l=qu^*xcD34S(RpMZpDHEsME$XEFU)mxoh-XU zpf{TprCnL2BBPGq%LM;LscdFcCN>5g9n1|g{iDM!i_K-V+8h=+j{}ZwFyQFC1`$SE zNSfp5Mran7lf`j*isssg+#1M5Sa2x|kAEH@fuTK#!G9>}sI~9bLdT z7#Jp~uwrC%T<>7EHWoAn{a4`9lS6L1%Lkip zUK`9TWR||5qZ=LcY8dUUH*|E~prZ>E92*;}Qg^4R!jug}vxI5q(; z54?!Ct+c$bRe#mdUFw_c9UUB;nwsn$cMpwD4E2xsz|r-1s%rBJo0K!I6TFCZ!r9m3 zyf{5=nlukgO%K?{4dA})J_UtP-V*d*GmZ|{vnRpPU7ngkj_%^bBsjXMF_VZ^+i@#L z2ks73oa)WNCm$*cIyy8j1@ht&IKgRfh?g#6Qwyi3FHTNjllNV8#YF5ok*%Gn@N~bQ zJ0^2k6;PZx#p}L!*)1BQPflHym{Swa9<8{>(_Owi1)lC=(9>P?PXs-kPSi>Hhk3er zF`e?7nC>SK(>b&*r&GqdSxmQ!ZBa*|m~O}U!>!lHbc9tIrNui>lavu(os6&G@I}|f zbV+d#(;Y=IT|ywHD`m&ccXb9nxVnEvOxN!;*tKT;?P9uC3rWVOK}^?ZZIPpxPFZ}5 zn9hA&On2z}#B|#>?cDG^V!HgVx2*s2$lREY>SF4|0-w)8a+6U^$1=2otE*~0aB%15 zP)rwebsCQjVmgSyW@0)Ais|?wYIRB6d{+lCoqJA9r`AhQOsAvWGN$u%bK){jpqNe; zdn{s4&Mjg(M%lSz=`y8>|J~u8`x55Hbfj*EL55;FLQfaObjgL;hl-h!X5QJTlY1}B zkLdtS@_?&z&%|_{5Ys7*5YrXTadm!29($;}HU(n3GTMpqi# zd66kOT{AJAAe(ujF#1rzoM|~JHitn!pTM-29?3md3Nf9chd(@GO*ws<9Pw?%4P!bo zryj+0`XTj9OjkKCrt9QDOjjpV^Bd;JboI4$MRQ|1W~EkXpq>*Mi%zAS>rf+C7l`SU zXAfcr=EiiiPEQ@931Yg;?=rKRRW?^Jrt@l#<98pF*oUo!NJ4ODLuT)(2c&D5e7+ zH$5+?+rMw^j?_7>?rKm+2n2POP*A5o9dUBUKRKw|9@rMUH?S=R8)J(fd-7*LpZEIN zlaJpwv+h2-zCQcQXZ^nGUf#~-3&Uqi%=_iu-DsWpy4UE683a`++&Ec?E z@_ssJ{0zJAsnCWdjz>3g7W9+Roiug|@)`@Fxz3x5ii^%s3Nfyw{+I zyZ@(r)roUfxZb@clexBi?b0{IaLM~*Md`1+nl39ZJ$yJ)@;*91a=2owJh zbQbctOe(Rxv8Jr3h(!H25@>yojb8e@$J@~@{+^WhN-YW14E$;MdQHjEAMR#m#k>ZO zw($69OG6?Q@i}yIXH#uiQEm;F@~bW!-tj|w-8b7m+pi;cc9PVcJHPt+^?i}DU)Oy2 zZNq=<`hZJ_ex1Q&vAH|}`o37mW6_Ci^_7J=7huoeS414 z>!Z=lZygb#()I5he=qXjyY$w`*T@taoy86cflUwwrK6#$I6JLM0YV5M<8cO zzK<+PJaZO>q^SQ_uPw;tME{~tJq&RQ;b~Xzx%*PgI6#w_xw8SH?7PYn$NHivw zUz|`5V)A0rnyU&gB%fCs6@1c@9k@3guxnslenF#%SjJ&7nJf->* z`)4JIcq}}9DM(^78FVU@MrX3Q(EG_jLG?{-1PX&A5JCTziuf!_M_ox)(&>ibkfc`J zgR_!CIx#W!<18kfPNOqG5|7KIb#=5fHZ(T1baYXepc5v22)3;_G(uxpPV(u}iQ!%+ z)Hi8KOHh{0Y|tts0&bw9Fm5=9&BPdCEH1`>n#W;Kh#jpht!*7dGM&W}iD6s-)`LqU zHdf{)pUJp1InwJ22y4c@>vEymGU?R{i4ZkBY`zHv!FCjgBxpEcP`ikPPGT2@&f*Hh zGPwez33-gJma2l3GbxuYPmS~ir8VI&*CJpX%tjqN&w&@eBms+u5h<{(2Q0h{uE1u{ zXjCdxA5SQj%3&yhA&bu>wbm4+ojZ5w@|CI4kg&#pFr=DMr&dNN?gHb2;+XtCg+irP zD-|-akRQSMCG>5HOrcb&K$KX(BDdESXT-*Wq$|@iq8b9CoB(H-jCw6zbGKFt$Et&mH_LS#H}dn!!*mxJ?m)HKtY$L| zAcja|n8ASQafS%vFAaJaBhhdNI;pj=;|5n!t7Kv>gV)F1P#@E1 z@V0QZ+7@Abzy^;AXw){V#VnU1^&SFR*k1fZOg}c)<+L00nu@e)F158fKjrk5 zE3C+?vVK(!vKHA^uLfD>FTTJW;U@GBP6Bh>i#`En#;|G?2JQtjKcro`(qPa^SA52% zv{e_TojrN*>sRm?c3)Ih9YyAl^I*KH!bN%Sk;_rlOC)&5Lt?Sr?yx)HVIP-EDusKu z3kBdqz+YLOGV^&*e`*SmOKqd7<2OQ{@q{i*s=5L-TU?*JbDtJ z+tu}x3LMTC5s(JL9GE>4>MR^o!@Cv+q+!QqM=7JbLIr z#QyufJ$&rDllYi(g@nh;agn}zKrz%Q?5AxD;5MC+t_5IOHq5dB<}DV?`mZ3)WB|*M zL8oX%17K`4{?v)%2Y2o~{N0Jed-k0?b3X1u+kcdSth-_76bZv@kyZ@EL5<;EcY$7T zlb9tOZWM3*wFMN@X;d;1htki;L zg~L1l5Sf&cTB0ijVRr|Fx#2;QFoy#VUE{~loM1J8}886`y?-y=>E`$*HO7Rox{xtOp;E2O6D5 zt?6>PBSDr6WaD7d8V54})d6D*NP{s{z-7?L#LkZPwyaeLzc~KQiqCd^x_8?rpjvtk z161n?Gn+Br0*}D}+J(VP%!LLMbi+m?x@o;W!u6l%8ZMmtMIEJ#?rv{u5e@T%!#my+=JjCSMQz12sEF1g zD&GB{u;J}-w-%C}PFq86C-i9;LqTw1VmX@?`AlN^yQdHR_jjk>KAe=2l6IjWAl1EQ zO?S7?zIrw0UT9st+U>?Xc<&-OhKGXSIKusFB-HlB7tjH~md#+o8WoIv!WEyvP`~zz z&)3$LuK4uh6g0##m5E|Ckhwr_s2e#dPUQo~- z;aUhHyk5KyWTR>Z`*z{n5uW>DH^*)V69gNC`RpSgRV5ekn54FbO6a8Nsi|q1*?EN} zIDG$oeSN*Kk&n8OC(?a4Gzh5Vjr4_sDMg0lW1hPZ(r2?^rpI9#2@h@=3_6WMDqvGO z8mmhSaxY|MUdYKWEQ7l2zo)MkGz4j9-C;=30%SvY=MO+K2o4w&Xl90aeYnA3#G4*6 zXhAnFo!C-WSz4H%o0nf$T!z}Z|87tXX@-F!m>UPpjQ8DzBp`YJ0*X2rt<5~AL)uU; zGGT_tKr174DXXlhNAebhuR9{39n{=Is3&?H z!td6sh|`r7uUTD!+!1DY6ds~98e|2zt5&ABoj`TMtdIcdbPEdf|u*7m({1eNWs)U)#{B#7}t!X@;A!kUdTy( zEfxD?eqDWIb881Q#^55L-TIN1z!2hnVP5Dj3xKic!S}*HK3|j<=fk|e@pMvOuRV33 z?CkS+c#2i2K{Cyz<`esiqM!Tr+nou=pF7yl)B=+txamRM0KOmBi}jr84(sWO><#Pb zj`m^Qzx9&oThFv?k8b>uxw_fOoSZ_Goc!Uq%{aZHU8_d$N<;>QEs4Xxn7D*Kv)DRt;&uopyf4Ski4fvz$ z>l<3yiDabE15O8$X0xH&v{*0@hZ?#MJ^+~(OHWky)7Codgw4d>vwBm`;e9Z#xzX>MG|94h#^=SXke}j#6fCFV=_e51(@! z(etz>_uc9$fhPW$tb&iy$S}?0NfcGtohD=Av-zbTrQ-klO#J(2Dyr+7J3(y3@V!Ib zYuAFV-Zg8nzHrZ)wfMk|_b>9UT>~1!n1P2OGpzP`?A>1#OE#`wyNN&`lIR?vLb_^| z$)wx3e$B?N7Bo1NmQ~a=wnJ4$4lf$g>jnq<`qXNCFU(({WgY-#w6DKkt3?%}R7Q0F z2YTk$Ww+>+5?)0qv9puVMP&r;{_~E;U;9)-K zI~dd-8Y19nJbd2*WIvcU%;$~p{nir_$)v!rP3WQ_k8xr2h_-9IEIblHI!0^_jasW>U#cRz^LcXsMQM~M(~EkI)#=KGDuX*B`R z9GF2v93zw~yJ{MgrjEwiicV5xLR?-&MP+q;E0I=$TMU@S=m>-H?aRwa$O`XvcC25w zc3nK?SqvC#UD5L8rLNXSd?e=hFK~@$^l{nEdNs_Sxf}>Ng)&t`^X?ayH5Y9CV%6ta z@iBjTC9)Xu@(LXw*u1NYbw2Q?G1T_do`|?ID;C+v))H)-l;HP?v$&mj< zEstoHU5Cn?Vy`rN=v z__Io*HJGiAKyARNj^=54m@b>G9Q|!N{zv6cfrUC10c-AU{VA1<8UgD zeeQ^QsUY`>bz8nFdGFv~PV9fT9Xd4&ME`LD;F`h2=lZ%qnVi(rT!*0UH+q<5z(R*b z^yM2LR`1y)qs9JND3mBPMvKE^wxCXJGI}E1KlONAanISC-$~4UqJ_<9T=mL3Nt;)< zcXUyi{1V(FK!T2q8h(-8DMt>+Vzsj(SHV-bc;gdhOYGv3hUe1?b0Xht;tC`Ro!RDY zDH zZ!drSotD=2P7-}a>c~*PvxQ~@uLB(bCWHa0;Dum(Zv-#Nq)}-M4qvJ^TAW_F0^c8| zP(<}E>G8RY^^H<7k3}PQ5j(T9+FGGkkZ2(F!EvYsklN#MSOdCR@wPAv#3Cqsc*tzj z$b}q`N~N=T5|sg@_VnWW@qH1!4?<(x%~}PzCy3 z$i`R;IDD}jDtK%X)b+K$`<~AYGy7jETUTbg+2+9T!)aXJcHt!&W8KOP5XLJZ9fQjY zfCe5MJ=XWr{(eWz&Tmb8X8x=B)sx}uNPB`u8+SEPIi3k_^qoNWy$6n51 zY)%Yx3i7suotd%Q6^x_}uqvX`nScT89T*(KhH*pDgG-EO_IMIk))c(9|H!fG&Cy%q zqP9T82CCo@Bv#kbFg67V{9eX%L)$bMKY<4Y7Ul#NVHPSP{B7H6$a zgAfxQwyFM8V|7HNMg!A6bLunY4WH&0<(ysD$Q4RedW+N7QO*U?T@_qx_;GOGg9Ezd zMU|U!a$hC#Iqb8~WaNFEL4$=vA#{jmhNqzAwlwv6vN!2q1y-%m9sg+03L&fqX>>-z z)=gi2C82)){!5?7@DXcU5gEe_pDtTIrI}?aNFPA`{x|ls~sE`gSK(q+D#-9 z1ttJesOBLMJT@}W{Q5vTo6m8l$K&$3t=X}2f5D-O@rLwfo_)t+@cItuGFwRq=dzBxl*lz6&hG* z2n}aKnZ{^yclQrQ4q*cr^a^lcE|^*%v2+?dA3`D#VS>RzVxJwH2GsFdv9PKIQ&^Q2 z;wN=rSV1EX5^MK#_o2RqhspX95bFjISSEsJ{$a#Ja>-;G3)RAd14!(Uu(efX)WGVS zQjE1c*-U|*2BfW^ww9}u2%76)ftDwdYfVrK{R0t$_yOD?26FqlJr1*uSz1Kku-bC6 zn<4uTjRDnFf_pw7mr0LawV_L|g;rBEfAh`0y=h8R9rBX0b!$r`CHYZjVC5QSPiBV~ z865P4$brY8JM{PUcwJUyQNfxuxzzN`)n8mdtEhZ2Sn?zNpm1;Fu0u8v51w=b4=%~c zKEL|}rhY=Jr_?NO&UlGTh7p1X7K-|s7g@(Jh{l87j|McRRV}Yhda)#bS<(3y8<{NV zlwiuwAi2iISKyf}JIoBg2ivSov`@tl4(W|;5l5;MmPYf-k~Rne?ihyOzJVb;7#&o` z(4&LUAKabzgJrQxkDu6|9sT?%SpI}w6B4US-HzN4i$-MvAI&CjtbvGEZ!oofeU1Vn zgp3AaMN(y;GxQA(MGi+mM|%`7;yz#J*JlVHp4z@0U-QB7?XV;*lxv|c^ubdej<~%8 zJszj{d{lIj!(1Kr9n6*>4m1Gzg-GK1Ew74VM9a1*htLql9A!zp@Cldw)pL7 zXSc%m5R2a$aI2u{(!Pt+Q)3rC1x=myvVy`2r?BZABlseikC;XK*KMmhbx1-<+`#3F zWGaKz-EDV`z#P=$IX&`-$357uIk0Y9`HB6zR&Po_w2wq#awRa9J%fZ!T9y|RRnCC( zadHy(!rUw9kkg+jZ~U~N5RD8xp;W22xVi}yd~7tFRK>?b#*8InqohjiKySvg<@KNB zoqHxZ^P`jwB9$$Keh`p6F-Csxz}Bk3WXtJ_JBDU;XqdFwzS{idHfd;F0JjE7a6IeQ zf#kk*>mo*e3RyG8dLhK=^?tQ!!&f}|m+RJSf=HCa<`WYbNvil zabVJCci6fJU=pxPz<}pM6p#zZ*Jm(-vf(Dv2sZrK2vo}0sM$E!=W`M}q&yn2AiojK zfayFL^xlwc)N%pYFsp{uPCI5>67cs{gBlhuKt230(01b^gJ9+0DNz`K`F;lL8z@}y0eKF} z!s-$q`FPZIdqC(2Q~`b{9Q4Hp1oy)zsFMp=uwBs((^DE-4DRCozDpNjj6{>2ZdlF< z^L9r-*pK!66y~=ujX|TP6b4Hznw0ep3_*>Kj6@7Q4nk4Yg1Y_M)+T{UOvue@Aa;>q z*p-0+gH#YZH4XrNFNh0=z%8Qx0f;XL2H?pJsBf!LhiUH9>P=R7su1cGwHZ_^HncDx z85?_OWXN4ty?$+}AS(-o94ds#VkKBcNb={i&I zgt)~8`7WRmB1WNNhe7x#e(a&qVR6gy_6sl76fZA1|57syH*$>*R#Ez~OAjF97>7H8 z;R*a<)JWLqC^mLCTn5HJH{NxR+Y9rip^?#uv6#`H0E!RO`VnT#(fWj?r%oQojaiDp zq)DL-NxSDF5(kf_VWZJw_;GCFZkYB#W%c2E?uBU*+%ORJ6WI7^IQh`n2z^f+^P|We zJ0t5qJh20fR$3TYaQ*m8cL(K6;KvsLl0O+a5iz+0%^)EvhJ^;WYj~Ot1i^%AVtgDw zhK=K(=tEnROu(AYc-X`^e&Uht?y*tRx9hjpoceapn#~zU_EQ;L zp%OYKZXn|FB1XLw9xI^LN&xPs*Gi_RF?gnJ5xrInRY0v4MvN~84X0=|Vwm+%YlIOK z_{m4$-_hahXKR{2EsT98E&Jm%h{X6Zu%Cd$@eLc`&PUd-$EL!E1Mi?THoXWw0U1N< z*GEp=KRGo8r-FwVR53RB$k4iVqeFe$wtTrw%-#e`A#Kpjxe}Oo;|3xx-{V9NKTO$d z_{jxUE4uBlX^?r3-3~G)VOf6SA97?kAxNn zwZ}#Vdp$Og#UyvOwxCdaR@Ob2FC$Bwm^dBiK~s^_3!s}rS6KkJHW@j!0GfRaKN=2t z;U^x3o;9d%YZ54g9oZQ*ZK(P<61Y#?V8oSsgOX5BLzi8E)WA{z^5jCUT{YV^CJgRx59=c2X z=)!Z6L_+OhQ_+)mp&mRvjlUQU60pfI zxaY|*Q~}{251Y6PWWnDEB#yBPPkr{#iq8&LJhS&xItyaYfVjvjNF4Nx*eTp}B*>YX zibNu!Km@Yv$0pGw;P%J)pPu>q;@)0a)&EDdop+TfjUmvIx28)Z(d+)yp zy{*3=^|;>N$f^5LPl5Bu#Cs#5Js#=pog6oBUj9+-o{j5Xe?NWu2AFaS<>FwSEn-y( zKp>-%hfRmmD|z_ou!|S5ON$s)JdjAQ;NrnXrm>6g0T*P_$~pMS#{!i|D`$_7q&!gB zvNR)dadOsk=ZO?1Pbv(Fomjpc#EvXm1_Q$IvSrA07hk-1>C)J;WuPd05;3(HX*vqW zQ&U55Jo!^3cI1sW#zy)-eD|%7#GLouTK<=y{UWm%jJyJdimK0S2Bpv&v;^pSkIe>Y z?xx^_NoWet^&YDgjlCul9;!6Z;%3v>Fg)qrsuZy(xjA4T1S&@i3kay!%r)<0uPh2$36zhX z09yc&!KOyN5{3YjK|2bYoB&77!Ww-?m{g?oo&X~S zj|WDW7s3qR1QgOD*uB?i5GRExe+)qnP!vE{Ax^5TxxO~UNx>=)VxC~#==)<*QMNEU zY|)3oI{zJ@QfM*BZqsT*tdvP65E+83l$^&?KolwD-;$LQW~R-sQbNv-H$Rfcv04;;kHmgiB6w587(;RR3i0@$N($FUZ3(sM(ip&IroFxvr-z`V=++=(Skg>>HMOPj!Y7S0tQ5Vv7Q~Q>vTwvneNv=R#C;rMrS|=4{ToC&J-|wR{>cZQ z6YG|}@QY>Ng;*)U+iwB1!(RTz_h6;!ORut073J5mQe-lsf12w4kCpmAR_dS5O5tF4 z|E>UR^0%>4RtsVa%qC!^OePqJ1FV$UgqG@cnyajoR)bh6DWpldDJvysi6Q!xGDSd? zNSLA!DHI2U;z#QW_2D7h)8m{(b;T{{pfwR0C@r3yQ4z)E32zT6MOp%(wQkx~e43goG{Eh}~E z%o+T5Cn93w&U_zM3ZnQqtQ3GA*RWD&lA}(Xii}Q*I)TN^vQmpetd!FcGY6ImAX2x4 zrP5Q5CZ!)vOgI{!ayb6xu+*z);RW~+KtG@?XT!c%t zG$33mBmJL;OGUvDeM4I6*Fjn;@C?HDrKJE#Lc;#Bz?4g;kn5cey;!K315Ck~C}xmp zr1pmD%Cb8Grd)=K)TCxJpPZaRnhQ*+nQaLPb;O2}bFl??2uzt-4$es)H!Jh+6 zNf{B8wi7K4SarqmiaQ3TfU4nF6>{p2m(KyFMCJL6>f;4@84dYIFWerO(rB{kB-wSG za}}9$fGJT{E$eJ)dTL>PbWY;;1*QzT!YYe6t0AMYI(-f>CArX;(O8+9T%1^(7e5D> z>Oo1+;Quf|d=MC00GI-12tEK{%I!wLluK4sVKd1Kl2W@0vqHcW>yfGIA8XEL@Q zCDYDSmmjI9jLw@2O!cRwg04NOsjxoWotioWO!cOv!tOkb8v$U7bsRhWE21PVDIsMB zm{KMt0o#XLtyh65i3|Z# zR00B~iVCg*Q<3v%sf9o!^}u#~0G2{<6M#fBuoQnTEQL&YE-Y12R^51OSgHqD>1$yr z03L1#OO=+)fu)A+I=wFlOS$k`oFxcLIW-z*fPI;TrG9oDETxhP#0Ha?$EH$AEw$BH z#a(k@sj(5W0sro&i~ua<;q2YBZ|{WwEajHheX^?Ft&nCV+!&VP11u#F9DDoY4)($A z+qRZ8CB64?Jc6YmKfN-V|P6z(0uy-3ZRATH-x3s zob@|2@E;reHn+UArSiXui+))QTSdeWEaheW);9}FDJ094fvp`?%r~;kCnGhj)t}uI zmQoS^%>b|z^JxN$O*po#G3JRw0a%KIV5vo;?%Z_tP;dJ#Yr`82vDOij`uvAaxRs>5K6JlcG zoi_2=gCH5fQpznkUlylr&`2siyD2PXPTAtfdAEY`^v-V%5Z1@9PCm109xOFnzbpVt zmF4A?JM^6A3?W!bcl4umf>V2C{IbtxVJW8z!BP%;5SDURFSAQlXVIVTU^7Vj-e1}H z?e_Vwl=Jt^DpLrS(qw)$151@Gp$eDfQ%NPOXfv>svWp4Bk-Cc>f~8FV-5^+4z&@u7#ysMD~qfsgCw*VJVdwU@5ga086<|t?je0RAIsY04(+M z5G;jaiW|UEuut_r3QIi`gr(f}Sy;-hmdPQkLYShuKYDSLh;c$FOi4_=AuQ#UH(MW7NQh;1h* z^I<8MJPxl4!csbBxQg%YNMXEQkvBuU8+J!3Wx$=y8 zu+)euSx8QF>YHk7TeQ`u2+9jB0a!|v+mYSXlBE!~rQQgZ^0XCrIuq$Ete~)%o>80L zP@O&xmh!2NwKg8nn&J6PioP+%svzV9U@3iRT9%-qNXBbPnT4ewT^j7*0)-rerTS^{ z9(hIw6JrT@%)EqDLQ(EKSjy6sO(mtY1z{;RCgfLE1z{=T5somfgGO#nrOm)nYJnC+ z8-&^!SjrK_5@vKTFb2RQ6 zs}8|ZzO*#hs2)g54Z>2QGx^aSGK_{`sg$I|)LX(*JjPX6ip#zhma^FqEah|nEH%*U zv+=pJuvAOSKL?h&j=}mn!BWFEmBJW=r4*R(Nr5;7OQ{5WIglZA$}M3j34_Yyu&D4z z@KspqhXGj1VyI6}69ix>H5V)VML}}_meO#ksc9rBja*ZCD_Ba$?o3K2lk%IvOuz)}+-|q|E?Rj|NyCWM`H~EQ@W0VSzxLwFgj34jj6O5U`i|0W24~)cq%>!Oc_MlfnM@a4*z^h{gI}Y zvy~n5fhqaf;w%Q5?%AELrt+)6RCY#Mrj!+T28|6tV5&PU6(kR(rGJMzX4M^w5HUms?<^ofoX%s*`h#UF`FjE1%2?2FL?EGV(DZ4_VQi+*_cEXLIse!*9 zn(CIb($Yy1a&ux*{a*=90i1LVG-Z=TiRmYqln6r03DWn3rn(U{wP*&K0{H4GHDzil zm$n_Rswf~=A20nIs3~Jny9(whIW;f|$-E6U_3(|UDZpp0LQ|G}VjijaLRMXRT~+!F zG!@=|=`waHY-}8xUNAX4jP}b%h9jna2#LCB)(q z!2k||w$djC2ggRe6=_+*%A$hA)b@fLgia}D&?&nDXoYEu%mSo@Rb~mHQ&yQ7cHqqt zV+ftHz%ihk5>o)3vPcZ0!?dG9aa?EPk(Rb|HS^G^smx4ZHzqPNfH@iuyn~{EPEDkz zgN(8C^dLGlK64mCr{Dw-N!gicnPPTYQew({bgB==*XgP5Za}AeK7>v!o}PkFdU`KnEmmJ%)cF-vU7jW?0kyQ^84DVr)qDEPT?oQ#;<3m7SCg+pu=2crz~d= zNHd;{iP%GofAYJ(ot-l8$P~YEe#h4M*4Lsp{x$5B?W=-KC23nWr>@V8Uq6qXnw&uF z)aWQ;rvQzeVW-B%5IZ$85@M&|JebSyaDbf}8JQgK{_d0YqEq`1eYmFW=U*%FXwWr}CmDLY#XGA*!O8DOWT;8)@JNX-(K`1UpfP2zDwi`a7^w<2Pid z!Uy61Ou8XFwGi|{oYMb);i)^#F%@H7U<;%z!JF?3ZtUn0IfbdiAtw^1_z@(g3S^j~ zU7KMFkxX}jPT}m)h@3*a)t!M;koMwgjwxbo<4lgJ+FBmUF-5#3H&uNyc7~g(>zK_k zRh<6HYZW}>)smRzw}UyR8vgR#U!o4bM{kRI{r21xxBS6_duDP>ee)Yh zbL58sZi=+}Pw(V!T|;ibzc$NFvDkqeQ|;GtQ!S_7ij8{lWRRQ6{z*x}_8mcP3jaW6 z$usBDla9SsJi|?uCRKo#g45|Y;-=o5n`3Iz?>~NSCdbs<%U*wnSo8dUJo5Z(j;R-4 z1l)u<_273P6#D?xC|`j1L7rI1|(r+_Fz zNv1fr0H<1uQIe_VA~LL=Hy6$%nQAUXNv0a}3A5l-eQpPUQ&kx?v)~l_I&jLib}h&< zuUUhVOriIgBvWhF01{(Zy(R=sX;-fXl0&xY^I34}>(>!D6_t&^DaDPGOkD-1LP@5C zv*1)P$rN=?lBq45<|LV_ya70ccRThF~Oa;?Ifo1`xY(dD4QHNhXwrA;)Z(lyTYv~R1OxYbM&(zJ)Dah)E@=OKLshK=eXEv|F zzJ7k=#??o+Kfiefox=4*{I!{;5?1d`dV9yJmD{7=+_K^Z@Kg+Bl?sP;yCDACmFH9>Mp35ljw zt^{&w!iZ?3~Ud~NhmWF@n z&QnbhXv^#HhstALjGRMGwVwR8IOc^zhj%4?_rl@ZlT#GNwzI8Y$FzTZYUdnss%2+% z^M}W`ZaJR!KIFZ*VX7%ui@he*6o+{r($e|WxowFt+vbo{om=6P$juv1Y>YVe)fz}O)ew2gX5#EyyCwJ7{+U!$a!56`Z|&B?BYSqP+z_|# z>v`nVgYYmmkW<8RHZV1=+lf4IaQrYFd37d zD=7aYE%FcN5V6LebvG;#f zBH8lg`pq-wlx)o!pq#dRxo&e;%ZAlote=NY8T1I9f?S!s;Tp|VbV{c~HKJ0@&oxz9 zeifYx<(jHN=v3OATvOZ)~{QifO%elY*X;4<(s9h_U4Evlvw5ZY*PrJ zYHHc@(z52FufF(VWp-lBpI!}Qo0cOH%>$^0lAjL%RJmzsc@AOyY_=)! z#$_MY?A;`z$GtTRP?@dsvQ5Q5Z*P4!G5^U{_Wx_|+@qR2?>J5nB1i*@#lmQUTg%y* zb@nL2RywrCK}7)t#g=Ol1ww)%w9t8%ivcPIygE zZcK2M8lc)9KLSvN1O@=x)VpuL=DSl3P~Ba!1lp#SEP()(^sR_r7WUF)5TFY5r;lNq zIyGsVg1!jz+NRV1)t~!D0IJ(+;of zwQlNC4MW}yZ{a}y*qXcTM6qw)mHIN)SOis{bP3c=9ilh3_tfu9JDBIIrJMSkzdN+XfD%v{!|DuSvdg|Ar6OYZAbu2zen`l^KUmFyZ=X+im+qnzv$U_^o=sQz_4 z;$&&$yeXF0e;eTMG0#xg6d{|>i5MF(nIw0ZQCXBx$MFm zHjbeB`t&=6yoFb99XWWgt}iF(PW%3i8Q*x^yxp750SQcJYQ@NKR(-2f= z#od6QYOU|q5LAB&INT5?I!K4QsXgBNtJVnc1Qi?Tri5{7f@*tM$_HxQ)L+?g>k@n0 zKV0eeAz!1LVlcqaV35I35mcu`QUgWT=t|vG93wW4kf6fprcP@URHJlLfS4Jrn>teW z(Ub)aa&8(VE&cx~b#Gpl+(^I3YpRS$DVv=%z%XmvmE|7xI93R7p_XX!`i$ z)YNh%L3RB|9fMKZTU*83Lqt&Z)l|pERJQLI#6%sy6I6q;47z+S_D<=%5=5_jLINERj~a9LB)w*$GHmnhDD%nP%EdHOt9u5CR0sN4S_qI z!TTK0hje$WXT-$4L{R;$?edre)#okR1eGr3)VVW|psF^Da!Myb)vdpB3P(^uX~WQDS6|Uz&ce~anj5H)py4wOvaHfgkWrPz-$0jZ}a=7EBvIj0ks8w`dX)y0M(e* zsm{D@CyRVDGSk6$Bufu|3JBZBv`#5f3NNhYC2in=AxP3V{1jQuPh~uC)NP%*oK(6| z$WLVFB^L02Aa9g)>H#n&0#zWiPCXeM%!HDj;i>;rNvG~-Jt8YDR^S=N6Kf!yQaVk3 z_~GyWc>M31pD?-L(N9WDf?#VJ4xH*d0i9EdHNRR5h9 z;8gz|rE}^mQs$S3rq*6t~S2e zRPI`L@J&&kYrP@h6oGWAYjeU$-pte#X2rIdDW-!{oe^2>3;(b-e6x5#${J(9sV~;- z2`|bF3*E7bw?#`j^$>znz#9inUNEUb15Q170KuuDAvHLq_R0b2)X$(pZD{Dp^|-NBRsNH+G))VapyW7yMVcV|-yAlW+1 z^7Ju2AA7c)I@5##NMwrJoJA(&W5W?96hN{jJG#&FHzgk%j-(oW2td9Qb$*~p`PhG> zUN^P?lJ!(4n%4@`^0DWU#ufnbr+at>nV1g>aCe?&%w=e%e4`6cQh-JB4Jg1e`O2?jy#}Qg%GbL9OXY(@px2<(V)@v!D4HJk1(wUlhNC=Q zbs&#(b85kSY|Yv>A3pk4G$=|J2vhWg5`|uyFp5OW$&JuqC(xES!%mo99j;Bg_w8 zKyxHpk9yvw_0rPYzjC3+bfO->()p{r5$F5Nc0#%Rq23-y^WpxVO7InE`Qtx(Lg$Bi zquqYg`C;C(XP`X)R{Q*XG9TgfKe_)W_W6hU`Um+)zkmJ@@{#s`sE_|-|1+`sAJoS` z4Dt8__4OBpxc`lu%t!kDhx+(OBR>8yi0^-55%)ig$$X^aFL3;kUjMN^XrDhtgXf

&}df%*?E; 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 bH39z@Z{z?p4O0)H00000NkvXXu0mjf=k`}} literal 0 HcmV?d00001 diff --git a/icons/schism-icon-128.png b/icons/schism-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..7d59c308ff30a337d7522bca615cc7432db554c8 GIT binary patch literal 17246 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 literal 0 HcmV?d00001 diff --git a/icons/schism-icon-192.png b/icons/schism-icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..dc7a85212226fd2a8a9109e6d8b68c81caf02b60 GIT binary patch literal 28560 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 b)UN*@>{A7fa5FGq00000NkvXXu0mjf?WMOs literal 0 HcmV?d00001 diff --git a/icons/schism-icon-22.png b/icons/schism-icon-22.png new file mode 100644 index 0000000000000000000000000000000000000000..8f77e87cef55a5c58d71a6363d45c700bef577f2 GIT binary patch literal 1259 zcmV zsdGMRYn*MZT7K!hwbd0HkM<9Lkr+vT+}+)s__Ifz6NL~9E@`Uoy5frFq_5|JExA>K5nZ|&;%f%vkP6`Cq5{`KTEJ9i%>TB21M;YgjDogH(#Hr#S!>bCV89-I|xsCH+{#bqs>RF#+V_=cM>@!0(GHWqGr3b)|1{NlDs z;JUvh)`Zx2O~=9~YvweyHpOaXbA1hi$q5=}Ra0byXm69;;4l}a;kYK-Zu1M`a6 zcKdovP3G4;vZWh;<~sFzpm^+_^_z9X{k3ytqvM6h3`dC7Mk%pOGFgX#p)>Rk3^SHW zb7CURqK+00boZfL55GcZVLZl14?V!;_dVBtxUcV4rPNOVGS<-a(o|qIPTGoPElYXf z;XA0WiqhTF%MW`F&@(p7bPzC^aWOmy7wLpT8p1Si&P;P8Jxbs!RxF-Z3atF23T=LK zy?^&j*UF&a@%E=Xc<+-g(#3gFF#_zV<$kyz&-_)Hp*a8+S%gT5e(L27b;*M#A`kkB~5u zvzc{%1!ZBAIki;;$t+vH`I^o}^YvI=eIu|KKpsk*(oT(~$rS`;(PB(P2PZ%a>nP>p zJ3b09MToM9MUgEKMH=IF7Te1s#sl_eMhQH{wM*MAU@d@@Qef!@G5}2v0pL3>7z&dR z;@;=q{2Ay1dMwL2q9H(Pkav9oz;O$>2oC>xnyNr!{sps%x5Q{|tl_-k;;9pZ z$&tN>6GL}>_D;`8I=9Q7O85CQnSS+`Z>9+$l;e0fGdYr}DPlF1bS!A5p|*@7DN>0u zgZ;k@j3##M?Acd0_rmVf`1s)IQv)MM`i}kULTC}oWX&~8Xs@fpbL_FB#}kK79zS~U ztM5jByl2xhd(w6|z2)84r_Oa{G|$O?J3TqRXZxXp-#@$Mjh~t`8LzdqwZQ*XzXA1i V8v^lOG*|!t002ovPDHLkV1oIkXT<;j literal 0 HcmV?d00001 diff --git a/icons/schism-icon-24.png b/icons/schism-icon-24.png new file mode 100644 index 0000000000000000000000000000000000000000..14680377a3caa57442690fc7821b442fccd5b041 GIT binary patch literal 1480 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 literal 0 HcmV?d00001 diff --git a/icons/schism-icon-64.png b/icons/schism-icon-64.png new file mode 100644 index 0000000000000000000000000000000000000000..ccc27723a1500beb84e14a97bbfce7d1d6d7c028 GIT binary patch literal 6565 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 X&^<_uXJA${00000NkvXXu0mjf$n2}K literal 0 HcmV?d00001 diff --git a/icons/schism-icon-72.png b/icons/schism-icon-72.png new file mode 100644 index 0000000000000000000000000000000000000000..2f75449a0431802ff3308bb824a1eaacef88d127 GIT binary patch literal 7725 zcmV+|9@627P)$F@pc z_V*(Qha^ezspAJfd0=ehQ;$9T>03HHPHQBQDjnK0G#=<5O=+6;;Yy`4{rvOK|L{Ep zkjv%r-f{iGum087K6l^YvEhS8RgpLo%9SbsB;)Qqqu-5Yt2qG3<2#4fdjp_@<9m*s zII!o7Pd@U{oqGm*y9l6EYc+cL+%!FZ`aF4Ec6#vMo2kFUKNiidz0!=b{l|_S^S$`u zi}80QKp3Fm)*FsK*w+<&_)kB6=fOP#JtmHp$a;caIe(E}etm&@yZm(b%|~douZtM8 zvEd%)uP)5lWW`v-C1hOVX1sszT?J5Qr=#Q4(c_Q)*#jT`jBHRssdSEN)hfL)bCF8M zRg#S*vLj+1dGH=`+AP#sY=Sgm1Km9o zj;_-$PEXM6;u@98Ws+rytT;d5cF|8?JWDqp9jCoRA;$~n=4@n8G(?w-*D2iUnRgUG zBRxH#lgIZx^3~7%@dE}`?aX8g)Z?&`*DsOTm84p|M%A2?b_I`+>QU*^>>_>tXa7q8 zkg0~;PAlyX^@C)HW&qaoe3<&X0+cHhMVfVbY@Ok>HqZBZM*(#5x&yZ!7#aS`{rBE> zYbuww7)>VfyB$cQ8maabs%i!@mO?bvKSGIAmR`FsOP3eJRMbi$T84W&X;)u2Er#Rt z#^gMq%!8mi>Gtank`0kmZy-VgU4C~sy*#g{*^whh{I9(7O6+Y35Yota?;qXprGcLA z&);(5y7Ab0TDIBDbPHMUAL+IK@{JiANwUV(tQkfB@wNhJw6AaArsMm+{*8wp{DiD1 z9fe|v+;$sv1+7%EEYrmK1Ud2w)oV^NXk9cs;NDV^IAu7MviSl{&8|?;>jY{0#qXer z0AYO#D6L0_&n0(z9UV*Sxy5Fh-TJlx=!WYK-hJ)Z?yr3M{yRUI%oI!(s|8VDr9x4o z$@vx9dySWdMl4j$b!I# zN}Oipa+#y(-``Sz_KuG3y8h_+*Z=a*|K#JS*xnj?BA#id-9qy#5z0$AlDQnM z7SiNEmm$fuMwCg?md_6L1e`iR+|nI5aG>MNnKO|;2tY^=+b2GB+am*gp@(ieb@Rx2 zCMUU_b_~)&lORz8QsK}w15_?o=-}=_>h!tk(%dq%ia|VgkKav$JsqNd5{L+3t_)2g znWQyTt1w=vY?0#09Kfp6N_?GG6G_q_6j?8+Nhg`yCVF{#mX3}NZMnvQUH!ISoSCvB z!?>DBut09Z`@j8O0os4f$i5qo9QupD`o>r8t4jvA0eZw`w^FTEr_<+VsJGKccieDL zL=F%7XD`1&$#fRnGDUVo$oOaFB2pjO|5{{^*HRrl7iAD95r4Zi&)MXQ#wKZ#r@f-E??Nd?t=Tdr*;B)CD{)8iYpa zMlYpnlsE=Cg(^0?v__#${{}!j*d_E-aYRNq3gV>_W!C0s+}}YjPs|E1+1h9&YSvM9 zOp-xXs1VmEpDU1RsJ0!;V7Jeq1BBz+p`oGR%*@Q%n*zw^lbmdbnTu&8>*Rw>5S)IRaK6nfBmfhx^CZ| zL)RZa`pDls@i(`x#8MqbRV9nXK#l-W??96L0hJ~%I4J1o#;mEdxEd7^VUUFBf`=F) z9{|${7r>%FTBR8P2RD?4K-SDZ9CZtet28w=OYuaCG9c|-u?mjK(@J8UlGz;8wS|Jc zJyb^acyUc+GM>c@ZYvR69}$#yj;0!-ipjr*Yvxw7&G8G5%4(8Ot1#y5~d_VyRI>l)ryk5TA+oqehM!k z1;7KcnLcWmQCIYnq9uLjPsM^T2pZ`FH`(dgXCpK^YQxOcP=Pw> z)pLt@_aRNdAE-z|>EllkErj3}q!H_%!BB^26Zq$=ktj`En4~l`%NhbKWOUH%YLb?~ zH4Ko5gl07}Tfr-MG2mr@v|KFEn8izJHp~E^eyETSc7VbhRDlFYrP8!;eu+Y%L8_@0 z%AhrI;5x0xp*@4v|9N#%q#5oh$+FB*^z>B$8XfBEjwF&2&w32`EFKhPlbX+xNmnVI zs!%Ffr9da4W4@MDr6Jr}XOh!`Kg;}MF{`wO!C$&GLo3l3<$QH{3wl1e;Gow1dtg# zF8764p3F$k7pAA__Wk2KL}Li1(cId)-fH}MfEbH2undrELc`RW4-J4R7Auhrvt3#5 zqJ7;BzgKOh(hiFhdWT=kE%Qq&R49~$m@AcP6kbOPVTPG|ynb+l(S+#pK;#v{D<;7o z9076R^K9};DaqY|L<#`!hsDA3kwr>JQndRwDP2+D*;o{)>qEH3W=h>zir->w6M zdmS1W80eatn_ImyNyEV$AL*kwI3NnbWWR-vZf_^mqBc6d$8^=^ka;5lPFh)Aqtz6m z2$d+C)o2|9VPJ|{V|MjxNU%(eJ#YsN=8ayeib>f%Pz%z-rkF$utw%UrOmybLOe3Y6 zJXLT0F);@rvTXQFt=!O9nz3~oCd5}=9eQVvt>${t7Nb$y88VWb>LKEtYs3!Qw zhA81m#Qt&yphcsJji@Wc3uN-6l4?c(%*4!~n8gYQll~GW2(80rl;WT4YL1)(F1i?t z>;OolS#O6`2MAUCs%Zuey1Kc-zl|VL3J##_KR-sT9gd0Xm<`D_}U}lq*B$on0 zl_=QZ6GWduRSYLH8>N}kq71@jyF;<4G~6GeJVagw15Mys`gyGrRLxe%0>NlDn5YZa z<@RL-DxN{n`D)AaGD67;Qqn?kta7wFj*g93e)7_Usn$$0SRfol|9nLbYWIsA0Ek1h zEwK89?3X1~h{GxVJ8s5WfXoI+QkyYp?uuH)q36VO~9o!}wol<|$ z=Rg##H-Ge@D+1JdFDA=4y606$^&}o7ozoEZ2xVuDOfonoUlD4TABc5`70Q;mpa_>R zlgN=;K=WF+3Lel8P6sPM;Rwv-5h-y*O2j}#fZYkVaI$%xjFe9iV2KSy})(6bJ;m zqtWQf6*(vg9yxz$R-{ZeSH{wbCQ5S_DPZe!2;%mDY#nGsEqE^@9Wscd1e`)KqlzR_ zh=@vxXc<_$v{oP$exb_FpDI%T zdMFH{Er67DQ1r6Qsb;7f$c|!QEGj~K#bZffakLI_!%^T@9{LGAmJ<sj_pXd0?i zpE(nvgd@R#@?XKtIkKv{NY*%<{XT{m`IexUX6S`D&+tgH>M1V1#JbdW^kv^x@LVngX@8u5sj8T zPAW-N$O?y;VU83w-~tW@`H;prq+|qyad837xyi4x-g$ctvh;P6D}K-l$Yfk}p-MY^_lUO)QsKZa3115S@SY>s1TTEg=6? zsX%?OUcCU+V`or`SpCS*49s|~yjcb7m}5?*xLl;8ogpkXx&&{TAUso`1Lnfj71l%5 zEzez8%L#y@wY(S~7lQ2;$UvvhdTDVj+mdGAX-m@F2(Bm6TR4C#l#SEOu99j}sFHa7DgJ(52I7$=!SB|t;m0Y79a7<+< zC`cbKDwY%JnT8dSLuJajtK=w1w1!I9I=}{Jp%Pjf`bsN-YEy_pyG+F@_gfVa#oGm3 zqpdM*e3oTHErqD^$~2d4pJvC$M~pvu;f%@)Q*1GVOYg&vpK3A88uwvt@qp>Mi|4`W z-7?RPtjG$$8AQ0xGlTNAF3Q_wLy>vn^a zJm|0VW|G67tB#Neh{!xRi_Q84=qBbvYt0V3#Z0TY6!rH61aEDE*(d-ECRa)Jj*|HO z5`1r4$wB^uK`Qesw_TogBhCDliwog=^LiiN0+8r5q{fYs$?R4elY1pZl-brGWwmG! zM939rd-)azIKU(XBvvs7dc}Hr1g*v-yz-i^YnrK9$(Kdcn!#;7uwl8}^R;tW=i+6a z?QKViBM3*NlBfM3>glD3s8}2&3?{pmOAxq)Tw!AjqL+&_+KrM`qKgobyRj=|`>}hl z+oS`8=T%_0LNHG8#&x^CztmIl$4 z1j!?2s$<^Va0utO1HvhXsI^!dX~*|u1FhymG@ubqBZacyp>|;E8m*y=Ot4_!06ty7n)@K(G7s=y5WP7@WXS-+%X$0#%m41H;g5l8hgMP6@p_sCLKTtA!jnCuEPA&bv`-Ye zhB?n=^pMBprwAI?ex&8Z)WVj5@5BC?j&;yXBqXWV9nDTXEHx=}{& z=719r6@zPMV+h=lUS{kQGUtsn(AOih4ug_FbQGG=(aMQhlZV1uMr@km%~fo!F}X7z zl}Z)CV=Muu9}5U!CD7`g>)wv^I#E zb%PVB<`fO2ghU9O&u`~37P`D< z=IC?M<#g<-X*O|rNo)S-egTkP=J|O#J&k2$_Iu`W!*WxZiU+Rol4KQwGLsu}fb|C3 z8Z6dg;2*uRxq=8v8&bt!+#)y|<=Z66uxJTTinEIvRxSdN52g*^w=|q=!y@fih;sU1 zGb02e<^tAkwOFJ1`4zFm-5OtR)bOg`T-)J?O6)c8_ z<{hG2Om>L`xiUGcGHg$WOpEA+*tlvn#q@`(q1{ls;Ax|}`q6TjQL#$URQY^Cq+6lM zS#_~O(g2ZL4#yLwBYYuV`)0CaQ7AfN zRHQU|8f!%ogu8upQ-zDi=iALzQ4J+%rUFtd&PIB>nDjBC|@%@&(H3dk!H zm#cFtk;=kq3>vo95;F8LWdoZ!_f)FTC&Hi?UU9Y&!uX6UfR#?}YmAo7s==XeGKqKI zw7`Q(Lu^t`<(ecaNW16a_C)_2Xq_~*O#Mc?_)&B)BO=9;u6E*V-N2CSm z#_uxr^W+NoMnAQdB%=puh=qV+8&YpQcqhpK%6p*d!FoM5e|7~ocDY{ZbF7ulPR$or zqKU$CBvD+BCJQhrH9W6D08*z4p6_*>`V#*BLMoMd4cF3Ix_PMu@R}&)TT+Kf5}iD6 ze=(pJGlM9(akGPa*cvYKNs5G97XtW>0W z^$nbQ8o;~+FZwKYy_T=M^xV=2Fe7u7;qd2~HVV~T7BkHYbE*T?1B)fhM>{rK@#lq6im#ua zS;1;9*PbU+(@|w-*To-^yCWF<%MW}oUr=CN*@dhH~ z(lgIIQ+=}=RX3Vdksf7U_F3Pk8e4P(l2yfGAAyU!K9?x_CGd{mAav}UHhQ|5!6${9 zOu=oMLuv>zkv6=2W;cW$+pHTr(k5-N%v7J8TTaDO*?2USi>I~fve(}ctyD~kWb-I) zbG5|ZOT~RJ0;KbZ-kIM8kTwyd*LeV7katuKwmXbS9SiJO8ovXzf~v z8u~E};@XVQ@^VoK8$8)s3fRwU9PPMw$ap3x|8dfY&4qAujMP!d}mirxPpt> zRF`6@)zFre_7E0NCCW+0i9+5B=mom-St>-k+{<0~FdS#j6v2Bb>` zKrG`kOZdYlx85LZPty_~Va9 zTkreilTXtB=OO$csR@vBh23LZdb>J3gf0j>v;uT;-|o$_tMf_ofb6KuE{0KYv$L^O zZpLJHy?oOLPc8P8N=l>jiX)C!1h4Ryzx?HOdK(`~>*(aB@5%zL3sKU8DCtAB3Fy1w z+YmCo7+0BDTv>($nTaMclU9%a`8)5qcM*ZXfGEufNg+ZK5R0(~9(W*4@1O(CYOQXA z2H_o{`$qfe^yw19vdaF4n~C!qQ9UCNBKv`OAyE$e^sP zt-+R-C4f^$`V@M5d&|w;q3@YPdE#&X@ffmX`~Q)Empd!LV6b$>ZLaSXfVeVC+rFVk n8~g0vKi>aebiEJI?{@qj6@FskTX{O900000NkvXXu0mjf5Dm!K literal 0 HcmV?d00001 diff --git a/icons/schism-icon-96.png b/icons/schism-icon-96.png new file mode 100644 index 0000000000000000000000000000000000000000..b64484b817e7d31eb1ddca41cd7dfa8ffc8f0295 GIT binary patch literal 11556 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/schism-itf-icon-128.png b/icons/schism-itf-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..0beab1cb92f2814ede14a49b52f474e3c35525b3 GIT binary patch literal 18705 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 A)&Kwi literal 0 HcmV?d00001 diff --git a/icons/schism-itf-icon-16.png b/icons/schism-itf-icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..0078cb3867f17a1039bec53afb177f97c10f5250 GIT binary patch literal 806 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ɜ{a*MkY>3b)nkG%W=}sUyIk?@egGgx`PO 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 WIx5m80}8GH0000Eid&Lx~rYsnGP+ zUPYW7JE}~f!?Uxqxm+%{^FI7XRq*(kGs)~|>c@sDp793y5!!$s`BZuQ&5^ zE89QF<#NCKSPGuMbn!wo5`A^-MCMF1uI#~360eA`wq4?AW{^}Y#`I*Cg}a-H`Q^DHtRa2{MvS5DWRS zU;91vhU6Ea2wTMiww24nLt$Qi?1FLK<9C0WFJu=!IHIq;`0PwH68p*Lzj*dsda+v2%DIKF(y@;jrH1u-;((4}ZelD)DY2#`X0gu2v{qN}UD(li8uzt$g7r;5Yvz zFn;;!_phEmd+yfAWcYJOv!?yXN`Y87h&LW(ztUiIC+N8PLb*~iZ9)u+V`QQsK-0MSFXorm$=z9FS2ma| z@8Qvkyw68D;3XCg((86XY5I*Wf4wtLJQ6S`h6f|S)AvU-Gxd-g^7~9FTU@`rz*1_6 zLSNHJhS}NJ!*w;;YK`*;duRlP(p=21A{>M5Zil?9SScwAM`{3^JT@GjTi&i`@d z%>)F@CX>mb{r&x28H|U`iIG$Ycm}{Q41s!Y7laVFu0m_*Xoc77VZ2snxZR-?4Kp8& zQg3&VvWK<>L7x}dY}4<%bSoX+E^Gks$?*~2eF6k%tOIt&g(355cnH3ypqRQqiJX2ys8LU7*a^QA0=p#_3it4XpBmfJ%$ zT149wK2eukO{banc=78l(Qpu_+r#hk(rG$amVpLr+a_!TsWm0V{VGQi1IF0!kni?l z^Gg<5G0~`VZgB|_^iWrV4co)wK$v?cr`X*&!0Nkb4GM))8es_NXgnbg?N*0ww@WG> zA?uCP>Gc4Z9v|`CUR-_70>OGbPX5tZ?68IHz+Sb65{jq^y{?OC3g{?&VIQhgMYdZ6 zwU=Nj&I?a}mg5f{Asz|od)uXUetpMz{c`T`&fUTd;Lnx@g!qqnlnF{H(0~HdHnD9J zDO*_D!aow`$@4QD8y;jhmC(Bfhqc{my>eq|V=up1zTRlIu1P8978Vv(-*=0&0z(K4 z)4;Y3`fi_|)59dp?8z~*nIzeCLbqC-{X^L{xBT;8z4nok%8G>o z{m;xynnTH$_IpIJ?eykl$C+E(YySPj>9cqGQr1FJ*1!JUmmVm*t!U8xqp9`V3wJmF s^3`vCds#~9N-4Y3)6)u9YkJ|x^j{pDw07*qoM6N<$g4&1KsQ>@~ literal 0 HcmV?d00001 diff --git a/icons/schism-itf-icon-32.png b/icons/schism-itf-icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..adb8f03e04295dd15379f38a1414a208903b65d8 GIT binary patch literal 2329 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 literal 0 HcmV?d00001 diff --git a/icons/schism-itf-icon-48.png b/icons/schism-itf-icon-48.png new file mode 100644 index 0000000000000000000000000000000000000000..c8e0ac2d84e5ace218fa5443942d269ced0d60bc GIT binary patch literal 4465 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

~wiN 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 AmjD0& literal 0 HcmV?d00001 diff --git a/icons/schism-itf-icon-72.png b/icons/schism-itf-icon-72.png new file mode 100644 index 0000000000000000000000000000000000000000..8785b44739836a388afbe51c583dc44243f260b9 GIT binary patch literal 8179 zcmV7|S4ABMZ$))3f(Xudm;}-R<4&7H>L{~5zRdLN zd+#~_-}&$7zLta#{6iS$PAr^z;^F&$B8uXpzxluZ_=E4%bEUt}fFwzhpLz13AAID# z<1gNKZ2kjdLj}9iXovsdzx?h)h3O-mBuPFN$MMaVUV7f8fA zeqwR@!zbpZCk28u5xjc&CSU&A5<|r-Kk_~A;|s66*?IL=>9LFDj`Q?`z zZ)eW$cny*yN%`>mpZJlviIJc9(T_iW?&SQm0Z?f+c=gIPF8uWhbCV-{^!*QUe0CB6 z+_yND`^uG-jHGA*AOr6Me(!C~|DCEq{AANs^?#@a&VHnxC5b@h2ZS_duoDQ6_U4#z(WL z*#?TaM&C6lB=2Hja+LuRy$QjNYoyooY=1K-x z=`%31?*e}3n_b(t70~gSnVAo~_mR(h=2I{JkR&S;J=bGmG)rkj#w@l;MMB~hI5qY# z`jE!8+w1)P7rsih(MAYCv5;YLW)2yNTkBigSShnGIg0D~j1L#(@nUA8(hXL&r+Zs~ zzAYy9^y3dcd;avvpEND&*;dc9a<<7xF^iIEqh?)_zKoenaeDq3txk`n0FIPH_6@G`3}K`y#=gef5p*%U3rp?wDDUBp-e4wb$z3 z%v!%?8YD@Q{sYf{;L|fxlRx^NM;|%UYcO99=G7S)d^=gf$A34Lq^w@qUy*->LsQ zj%#1wY%;{0_ZfbD<_Y1y<+}f_HR#mB!osuffAn8wGua;yk~|g!Av0q`SXn`AZL)N^ z!Ei~iFlnRrOPrjXha`nGK_m&yMi;m5;{+iqn^n%8I>N+o2_XbR8qARb2!SMl5CSPl zAxV(pgwKEBD>cz^T1F6Q3!c|}-fO&WZj$fMvLVj#-wVGdUi*6M|2qTp?0X-3{`~2a zKWSy`_osfvn3YmSk_4sG!>c!$DX5HOJiPUYlApm4Dm&XhSkmB!G)-A8*U4B0S=+)i zv^}#3_NfdzVgBwX)p{#-+fGLaA$fU8oE&!leeTitbEBW-;?7K8Z@i;`BuO$q`F+p- z)TwFf$EJO6(c0LOW}}E~xsNBuG_x{}q2n7mrLiKy445p`{}yAJowO=8rW7+2f- zq)bzREK77BnLr2yk*3H(fJo`}JObCH8}u^`yEMQG@`;@z|==*eq9Q z_gvN@ANhEmWYg!U=+uzDbf8?1bKmO2} zcYXTjo_pZwiHjH2v1){>D711qTOWLkKdwd~naqw4<1er7-siXhSMO}FFgeDfXO59E zO#}j~xr}wC#bI04de?SZ}(U z+drN=efsp+($Z4(8fQh9mww@=Ux?DQIFdAY;^oUIn+`26z&3TVPaS3ag^!a;27i9x4O*QZ7v7A> z+6HHj&2emY0@Kg|L6W4@+g-*=c>pxGLgrE#fFw=uya2<{skb^@m>T8ghC{vGW24c+ z@q$4V+jb9%q9B}v%(TTT>vs+S8XGRj(C5kQh8NiRI}-WMNw@a#{# z_n#4!D|FU+2m~XgJR>7{Hb3xg8oh{OPR9>I#)b+kOpY-DXGW>o^aHqOVnxwnWK>|BtlFL{q zii%(F3yZ>({SW-WH%-#>#*4<1hT|Fa5&5_#xBIEc6^t z%2^rgd`zpk%M=@yveKke06>iQAvGRIrJE99rQzx6X zF$^8w3urdG_(30JusNpAgUf4`F-_C3ESdZ$%*?kL9aC}spvq|9L@LSnL7$%MBT3L!RL-ATWVo2y z_A7#}<8gDf%<@KsQqJPY%sAgQJw}|Qtge^2apMm4R-480b)rT{6esxM;9pr+&`TEA zCZ-uG#w0>u+a_TY(djx=YfVHdNYfMvi8M}e%MQgOCH~_2HGcRWKoi3SX?8eYTB&t+ zob!jjAwZHO$>0CcXFmDVV-NrEzx}zNdZL)mm{AlHC4EGu%E@<`SeDJ!R>11T60n%1aHEyrhXg8a* zdwr55;L$5^oYO$y(&)K_Xd+y+egP zGCh{t1L(oU#l`Vkw{Cr1GgE*0-~7y_ryqUrw5BRj$8ouM?GEde24iD7?|pBMKmYRz zCr)IEqJ#w_Mr?Q(hK_9+nEJrs0;uIGZ`{1io%I^IjKz`ZG3F;n@q<2F)f!7zYjnFl zm7HK&&vARJ%|@-sc~M2H^>^XOij1l%$g0F^Lqlu?ZBE-m^g1pgy-QHl6tY>HY{n)@ z658!9t4r(5P9GuF6MAky&NlZ6d;ZiB`-`t#+iPYY2L9{U1$1<7daBy$NUP;4^;Q=l z2I>9I4msU~PA8(%PMO7LWTc2BNjuwjcPu*$pIYVUV~=4OI+a?3YuA_Atk!Y75Z8_A z_&$NHllloUf-s4AsM|x9B@{(PQDl-dC5~dQ7Zu)YK<V_Wb_n*W@)s$)SF!tIRyk$ z!xN;n43D2Ox6^h(e`tz0tD-n}L#Y7z{Se;|Xk2zED>@s!EG@@ntJT5photGY-;h9# zL7%oLC?>})w+Mo8Urqy19aXt1mB=Y3*{n^g8j!OME?!w4L^C0f7(_^cz%X?(wnZjm zl7x^XDWR5-rYW+#Kd(N2>ZtW+7p|B<%w044t+xgQ%uS4zKXj zB9tut`J-gNbe$-Q=?=rXZBosaxHFogWsQ<`WG>!b+9$MW8e}sTs-_Y}f+$UycWyAX z+}j66R%BE~Ayc&Ygw`ggRLNYbq4){ISe(&210+JAstUTUqiHH>(#KJId?}Q;y&^Mz z)IiEBeD%gno;Z1YpTHBt1!-=qlwaOzHn%@|{%-^{xUqylP%313s{*zkM%);mBs$ll zY}wp)E&6?^cf0s1Sh7b^Eesy4Dr7PimTAxrL%KZ|-wPNXk@%2U;p{72szM;k1E;Jg zGHIF;#R-|49Zqa(iqVWvbcJFXkgbNh*9s}c%ebtmf+bnuFGHP%FM|-cTQ0eU0$1zR z1AqXI&5qf70G*$no}S#;*f`Y8R3W8@)7TyYLABmQQB=Nm=?Za_;wR&DDgl*N9XIGB zguv2mGI7317wU~R zaTM<}Gg+1i}qx{V=|V{F;>VUNlxVeL5Y7VA~Ymt28jHF~U+&gp8`GsESOg!fUw_wOWg?AMI*L8l{B2fW($L zY3Imi2AVQf$RiMx8*ScLS*O|U?M65RcvS~$BEyQ?x>sfZ9zMNj{=ctYHh^TXm3%kw z8wUV|VI*C@vq7!dq1tq*b$T?r&TeRyB^g~+QB94!kt0pZ)M_n~B-u?U1p*BPT~R=j z85=54t~Ckj{XPBxG$rN1Ru2goRa20YL64{(MqC@$d83@%+ekzLBsZaSv_RgsIW;>? zt<&Y=@+uwAL!@F~*x$KHbYpDOAQUn6c9)T2e&2H^hD*|s@saHHjcRB6qwhHs&_=ak zhOuCLsKAYl%0M8IVwyU(H870`@WOyZws0JO*RQCmf^C}^rcM;a1ie1ZSVE);{UE$6 zq%2`uChyL6n7E^m%7P?K5lDp5AgB7OorT_ADus;AXg$7-`dxzaiS zMOI`i+azO~M9JAyGOcE3dx8PaLDg=eWHmHHBX*KOA7*c9APwp;%QCTalh6P)k=fd6>xznsgsjR`9$8>%O{U(tLl{K|>njNiO+yM&3NyoK9TioQXf)gS?m+Opes1pw zn*pk#piE}?zpq~8BafataLw`ANplaNhl<7GOuOA)KLALQWCQ|T*Dy^3Rh8+7A&%pc zMldpOf;2d^69|lLD2gi6?+>mE8VQ9`iKv(CTf{>~hsEU>-7?V>g(yjgql7#6XStM} zrP5m3FQ5z=)5Nj|`Be~xMBP4KR>4n9k~qe9_xF$XuFJMf?2JjqGD*5IVo2kTbGVs3 ze)hQjxxac{iwEIB0$u?A(=H%MmITS*V6IfkLkd0DB?$VvVN+HWKp-oFYgAPs3?m%J z!w-VpU6(@*SsqmTiY!yeSWFaEK6ZTzUDN1C5sn{_3PI&qp3mQRipk~xx}qppmVs?q zn3jg`^>G}RW}`zGM%W_D=(x$2*I|BYltydd(FXy*z_Kl}_Q3b`x(@Ajm-Y1unJCA| zxiRYL;l1kd;i7b8a@4xIy0w#be{3J1bSKl5B--uX{x&8lx~>iK8pA;I6&%;2zo*r( zHGka>2VZA#&ta$&tE0nwn4_W@%#X%=de+(;(Nia3_=LJiUW)hW+bkzZgO&V z@&KfhM<$FtfF90fv$Kxltiioa`<_aG)k2 zClV?$+H4-%wXkesQ0tXz#8Gm$rE<@$VK>BY`DBJOeEG)pLjgT;_N4ZIUb?6z$?o0% z2>6cxs=T`hiEJ)|t|`P(g6sPPoq6nRdp_sQ}U-kjAB4Jq;wq;=37Eu@z zbwW}FB9YK-_i)|dQ0EMwsM{uM8{~wAoweDlv=6;@Y`7>Lof$Q+-Y)OF2J^kUfOeQm zl0+1S^m^gl^QWE0csdBPk}R<}HAWhzbX|{)dJ|{c2Psm-5v9xdWtX#;YbaYGtqF_k zM<;kqFHz~FxT|fV=x!MicKY-ZI5t!qz&A8(%fvEt3{%JPd|bz;R&No85qZ7HP+^3s z-(!Ail%*SM`?Sp5Zk$<`iD~LMj*H{^Y+l`Bv@}6vq%^w@L;36hNGE5f^gVzcGEH+X z2!d60$HpZF`NUCat^!x!pPd;!${i83x zrtWd&-wXWeJ|84YGMb`r+H`pT#Z`1KB}zQPC`Kz7Sg2&YKHheOlVk}+mQfU$V94ag ze1_#S)07!uI?d7SEFFNNstTrMVp#?mD}$CX5uTu2uI+RFhc&+AP9H^8NQWiHMTv#^ zX?i^e*Y&7Y8}!3yH}dW^JrN7Ma*ym>f#vSuZ{UrO6s5(PG5zxL#!ij&ao|_gy*>Ex zp*)NIHSYV;6=F9c+;)~byDt!^ih{0bNQO*zT;q;ybJH+c3QfXLr5NPVvtTPaJF01z zI<{$GS_ZNt4N8KpgR>eiR2)T84DLyC2lFD(4Gq)KF)ba_FpvX~Y>l*^va+&4f4lO( zH=^D<(ppG9FEf)Z9Qu2JlSiia0(w}}wE2A+l!Rr{jddZ*Ppc4j+4i#A4 ztRX8BX2!xYb!^K*2tm(r>2_VpZkLwrWYbKgIYyRY-%Ds}P_YI-?m2As`3>dfmGVERM=Vij1LK07w#; zmX2i%+8uEc<9PwSmV@MK2sGU7?&G~fRTT`=z%(>0R6;#wM9=W*n;rb1zb{F=*KESh zz){ylpGnK+=|}Ekadw>hj?Gfc+Uy8(_YJfCqvr)&ytW*zRa*VEay?q9G@>Akq9jSw zBuSJc6$$V);IDv}?wZ*4f|||lzEHe-{**$Z3Np6I$VdrIQwf3q&+F6bba#8ux)}H# zNe0{0as57$EMe$6mSJER`XFoeJv=u+R5}<#27NCY^ryeh5yD6*$NbC~V`+&qr>0q) z9NYK(jKeg6dFAKT1VENfISV5+xDDk|ezjys{1DuN}wf?{4Of zlNJE!;8m4fW(ag$$1qf~wvD1mn1)WT=MePo=1Y6EBncA)0zZivA1UGXBQ#wl==brx zKJ`XxxBV)FKyh@e@eGQh91t#-u~?WIWnp@hWAhU%&P?nxm%|)#&-J-{bJZ_5+itnh zam)2yS5j1W2MCH9OCo+_TT5O6E^TaVL~pez+j-D{k|Yg6w`CaUrjD+vL{W_A`}DdF zYE~oFQ~Lerz)s7ujA3XP)*xAn11RJ&lBR)8%=kSyha|!HaFKT2)i`+f) zKN$MkJvUum+iEv@Uaiq_Do!k|j89B$C5EP2Ls?Z%k}!_r+rUlWI4x#^vaTBGB7TlJ2+8KmmXnYsBbwNz4ubFo?+09gZW1IxhrXFvPduX|knPLAC$ zF9Zl#W@xB@?*}-JOTQoPHlK9OL;^HTL6#(T8f}KAF*#ObW^93%qz!FzH1{_h*f($QR2baNYiv3SOKw0$h%g=r2Lu=dL z@>7!}iQ_o#08OC&iBEi@`>hu8Z!=U#g>9PTY>Tre=6UzIlgx|{Z~t1{4Z+)^(dkAT z<@$QtajsYD?W<~L=nLQf;)|<$fFyAoyFdqMKmYvm?zcm*cWS6J6Jx#8#}`KLrtbU0 zcD>aNH!HQ(cBgxFwbFPqHLTBl<}*LD7l?@C*atkodCz;^^Oj$4{QEc#d=;VI?1USY z+MRl{eR+9p^ze}n~Q(*uA002ovPDHLkV1l*K{Z#+} literal 0 HcmV?d00001 diff --git a/icons/schism-itf-icon-96.png b/icons/schism-itf-icon-96.png new file mode 100644 index 0000000000000000000000000000000000000000..4bec6283c243b183917056f459ce5bd566fbce17 GIT binary patch literal 12362 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 + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + F diff --git a/icons/schism_logo.png b/icons/schism_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7e99a5cf36a7190587baa0ff95d0105315aba540 GIT binary patch literal 936 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

Fhg3(x0a5UB;T()^J%Fh=j&zcS) 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 + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __acc_h +#define __acc_h + +struct acc_state_info { + int type, chord, table, base, solo; + char chord_str[48]; +}; +struct acc_harmonize_info { + char name[16]; + int notes, dist; +}; +struct acc_chordt_table { + char name[16]; + int a3, a6, a7; +}; +struct acc_chord3_table { + int type, base, di1, di2, inv2, inv3; +}; +struct acc_chord4_table { + int type, base, di1, di2, di3, inv2, inv3; +}; +struct acc_chord5_table { + int type, base, di1, di2, di3, di4; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* called by keydown/keyup if the capslock key is down when they're called */ +void acc_state_keys(struct acc_state_info *nn, int ch[64]); + + +/* change current harmony */ +void acc_set_harmonize_next(void); +void acc_set_harmonize_previous(void); + + +#ifdef __cplusplus +}; +#endif + +extern struct acc_state_info *acc_current; +extern struct acc_harmonize_info *acc_harmonize; + +#endif diff --git a/include/auto/default-font.h b/include/auto/default-font.h new file mode 100644 index 000000000..cdd08c6a1 --- /dev/null +++ b/include/auto/default-font.h @@ -0,0 +1,269 @@ +/* this file should only be included by draw-char.c */ +static unsigned const char font_default_lower[] = { +'\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\176', '\201', '\245', '\201', '\275', '\231', '\201', '\176', +'\176', '\377', '\333', '\377', '\303', '\347', '\377', '\176', '\154', '\376', '\376', '\376', '\174', '\070', '\020', '\000', +'\020', '\070', '\174', '\376', '\174', '\070', '\020', '\000', '\070', '\174', '\070', '\376', '\376', '\174', '\070', '\174', +'\020', '\020', '\070', '\174', '\376', '\174', '\070', '\174', '\000', '\000', '\030', '\074', '\074', '\030', '\000', '\000', +'\377', '\377', '\347', '\303', '\303', '\347', '\377', '\377', '\000', '\074', '\146', '\102', '\102', '\146', '\074', '\000', +'\377', '\303', '\231', '\275', '\275', '\231', '\303', '\377', '\017', '\007', '\017', '\175', '\314', '\314', '\314', '\170', +'\074', '\146', '\146', '\146', '\074', '\030', '\176', '\030', '\077', '\063', '\077', '\060', '\060', '\160', '\360', '\340', +'\177', '\143', '\177', '\143', '\143', '\147', '\346', '\300', '\231', '\132', '\074', '\347', '\347', '\074', '\132', '\231', +'\200', '\340', '\370', '\376', '\370', '\340', '\200', '\000', '\002', '\016', '\076', '\376', '\076', '\016', '\002', '\000', +'\030', '\074', '\176', '\030', '\030', '\176', '\074', '\030', '\146', '\146', '\146', '\146', '\146', '\000', '\146', '\000', +'\177', '\333', '\333', '\173', '\033', '\033', '\033', '\000', '\076', '\143', '\070', '\154', '\154', '\070', '\314', '\170', +'\000', '\000', '\000', '\000', '\176', '\176', '\176', '\000', '\030', '\074', '\176', '\030', '\176', '\074', '\030', '\377', +'\030', '\074', '\176', '\030', '\030', '\030', '\030', '\000', '\030', '\030', '\030', '\030', '\176', '\074', '\030', '\000', +'\000', '\030', '\014', '\376', '\014', '\030', '\000', '\000', '\000', '\060', '\140', '\376', '\140', '\060', '\000', '\000', +'\000', '\000', '\300', '\300', '\300', '\376', '\000', '\000', '\000', '\044', '\146', '\377', '\146', '\044', '\000', '\000', +'\000', '\030', '\074', '\176', '\377', '\377', '\000', '\000', '\000', '\377', '\377', '\176', '\074', '\030', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\060', '\170', '\170', '\060', '\060', '\000', '\060', '\000', +'\154', '\154', '\154', '\000', '\000', '\000', '\000', '\000', '\154', '\154', '\376', '\154', '\376', '\154', '\154', '\000', +'\060', '\174', '\300', '\170', '\014', '\370', '\060', '\000', '\000', '\306', '\314', '\030', '\060', '\146', '\306', '\000', +'\070', '\154', '\070', '\166', '\334', '\314', '\166', '\000', '\140', '\140', '\300', '\000', '\000', '\000', '\000', '\000', +'\030', '\060', '\140', '\140', '\140', '\060', '\030', '\000', '\140', '\060', '\030', '\030', '\030', '\060', '\140', '\000', +'\000', '\146', '\074', '\377', '\074', '\146', '\000', '\000', '\000', '\060', '\060', '\374', '\060', '\060', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\060', '\060', '\140', '\000', '\000', '\000', '\374', '\000', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\060', '\060', '\000', '\006', '\014', '\030', '\060', '\140', '\300', '\200', '\000', +'\174', '\306', '\316', '\336', '\366', '\346', '\174', '\000', '\060', '\160', '\060', '\060', '\060', '\060', '\374', '\000', +'\170', '\314', '\014', '\070', '\140', '\314', '\374', '\000', '\170', '\314', '\014', '\070', '\014', '\314', '\170', '\000', +'\034', '\074', '\154', '\314', '\376', '\014', '\036', '\000', '\374', '\300', '\370', '\014', '\014', '\314', '\170', '\000', +'\070', '\140', '\300', '\370', '\314', '\314', '\170', '\000', '\374', '\314', '\014', '\030', '\060', '\060', '\060', '\000', +'\170', '\314', '\314', '\170', '\314', '\314', '\170', '\000', '\170', '\314', '\314', '\174', '\014', '\030', '\160', '\000', +'\000', '\060', '\060', '\000', '\000', '\060', '\060', '\000', '\000', '\060', '\060', '\000', '\000', '\060', '\060', '\140', +'\030', '\060', '\140', '\300', '\140', '\060', '\030', '\000', '\000', '\000', '\374', '\000', '\000', '\374', '\000', '\000', +'\140', '\060', '\030', '\014', '\030', '\060', '\140', '\000', '\170', '\314', '\014', '\030', '\060', '\000', '\060', '\000', +'\174', '\306', '\336', '\336', '\336', '\300', '\170', '\000', '\060', '\170', '\314', '\314', '\374', '\314', '\314', '\000', +'\374', '\146', '\146', '\174', '\146', '\146', '\374', '\000', '\074', '\146', '\300', '\300', '\300', '\146', '\074', '\000', +'\370', '\154', '\146', '\146', '\146', '\154', '\370', '\000', '\376', '\142', '\150', '\170', '\150', '\142', '\376', '\000', +'\376', '\142', '\150', '\170', '\150', '\140', '\360', '\000', '\074', '\146', '\300', '\300', '\316', '\146', '\076', '\000', +'\314', '\314', '\314', '\374', '\314', '\314', '\314', '\000', '\170', '\060', '\060', '\060', '\060', '\060', '\170', '\000', +'\036', '\014', '\014', '\014', '\314', '\314', '\170', '\000', '\346', '\146', '\154', '\170', '\154', '\146', '\346', '\000', +'\360', '\140', '\140', '\140', '\142', '\146', '\376', '\000', '\306', '\356', '\376', '\376', '\326', '\306', '\306', '\000', +'\306', '\346', '\366', '\336', '\316', '\306', '\306', '\000', '\070', '\154', '\306', '\306', '\306', '\154', '\070', '\000', +'\374', '\146', '\146', '\174', '\140', '\140', '\360', '\000', '\170', '\314', '\314', '\314', '\334', '\170', '\034', '\000', +'\374', '\146', '\146', '\174', '\154', '\146', '\346', '\000', '\170', '\314', '\340', '\160', '\034', '\314', '\170', '\000', +'\374', '\264', '\060', '\060', '\060', '\060', '\170', '\000', '\314', '\314', '\314', '\314', '\314', '\314', '\374', '\000', +'\314', '\314', '\314', '\314', '\314', '\170', '\060', '\000', '\306', '\306', '\306', '\326', '\376', '\356', '\306', '\000', +'\306', '\306', '\154', '\070', '\070', '\154', '\306', '\000', '\314', '\314', '\314', '\170', '\060', '\060', '\170', '\000', +'\376', '\306', '\214', '\030', '\062', '\146', '\376', '\000', '\170', '\140', '\140', '\140', '\140', '\140', '\170', '\000', +'\300', '\140', '\060', '\030', '\014', '\006', '\002', '\000', '\170', '\030', '\030', '\030', '\030', '\030', '\170', '\000', +'\020', '\070', '\154', '\306', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\377', +'\060', '\060', '\030', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\170', '\014', '\174', '\314', '\166', '\000', +'\340', '\140', '\140', '\174', '\146', '\146', '\334', '\000', '\000', '\000', '\170', '\314', '\300', '\314', '\170', '\000', +'\034', '\014', '\014', '\174', '\314', '\314', '\166', '\000', '\000', '\000', '\170', '\314', '\374', '\300', '\170', '\000', +'\070', '\154', '\140', '\360', '\140', '\140', '\360', '\000', '\000', '\000', '\166', '\314', '\314', '\174', '\014', '\370', +'\340', '\140', '\154', '\166', '\146', '\146', '\346', '\000', '\060', '\000', '\160', '\060', '\060', '\060', '\170', '\000', +'\014', '\000', '\014', '\014', '\014', '\314', '\314', '\170', '\340', '\140', '\146', '\154', '\170', '\154', '\346', '\000', +'\160', '\060', '\060', '\060', '\060', '\060', '\170', '\000', '\000', '\000', '\314', '\376', '\376', '\326', '\306', '\000', +'\000', '\000', '\370', '\314', '\314', '\314', '\314', '\000', '\000', '\000', '\170', '\314', '\314', '\314', '\170', '\000', +'\000', '\000', '\334', '\146', '\146', '\174', '\140', '\360', '\000', '\000', '\166', '\314', '\314', '\174', '\014', '\036', +'\000', '\000', '\334', '\166', '\146', '\140', '\360', '\000', '\000', '\000', '\174', '\300', '\170', '\014', '\370', '\000', +'\020', '\060', '\174', '\060', '\060', '\064', '\030', '\000', '\000', '\000', '\314', '\314', '\314', '\314', '\166', '\000', +'\000', '\000', '\314', '\314', '\314', '\170', '\060', '\000', '\000', '\000', '\306', '\326', '\376', '\376', '\154', '\000', +'\000', '\000', '\306', '\154', '\070', '\154', '\306', '\000', '\000', '\000', '\314', '\314', '\314', '\174', '\014', '\370', +'\000', '\000', '\374', '\230', '\060', '\144', '\374', '\000', '\034', '\060', '\060', '\340', '\060', '\060', '\034', '\000', +'\030', '\030', '\030', '\000', '\030', '\030', '\030', '\000', '\340', '\060', '\060', '\034', '\060', '\060', '\340', '\000', +'\166', '\334', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\020', '\070', '\154', '\306', '\306', '\376', '\000', + +}; +static unsigned const char font_default_upper_itf[] = { +'\377', '\200', '\200', '\200', '\200', '\200', '\200', '\200', '\377', '\000', '\000', '\000', '\000', '\000', '\000', '\000', +'\377', '\001', '\001', '\001', '\001', '\001', '\001', '\001', '\200', '\200', '\200', '\200', '\200', '\200', '\200', '\200', +'\001', '\001', '\001', '\001', '\001', '\001', '\001', '\001', '\200', '\200', '\200', '\200', '\200', '\200', '\200', '\377', +'\000', '\000', '\000', '\000', '\000', '\000', '\000', '\377', '\001', '\001', '\001', '\001', '\001', '\001', '\001', '\377', +'\200', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\000', '\000', '\200', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', +'\200', '\300', '\340', '\360', '\370', '\374', '\376', '\377', '\377', '\177', '\077', '\037', '\017', '\007', '\003', '\001', +'\377', '\377', '\300', '\300', '\300', '\300', '\300', '\300', '\377', '\377', '\000', '\000', '\000', '\000', '\000', '\000', +'\377', '\377', '\003', '\003', '\003', '\003', '\003', '\003', '\300', '\300', '\300', '\300', '\300', '\300', '\300', '\300', +'\003', '\003', '\003', '\003', '\003', '\003', '\003', '\003', '\300', '\300', '\300', '\300', '\300', '\300', '\377', '\377', +'\000', '\000', '\000', '\000', '\000', '\000', '\377', '\377', '\003', '\003', '\003', '\003', '\003', '\003', '\377', '\377', +'\300', '\300', '\000', '\000', '\000', '\000', '\000', '\000', '\003', '\003', '\000', '\000', '\000', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\000', '\000', '\300', '\300', '\000', '\000', '\000', '\000', '\000', '\000', '\003', '\003', +'\000', '\000', '\000', '\125', '\000', '\000', '\000', '\000', '\000', '\374', '\374', '\374', '\374', '\374', '\374', '\000', +'\000', '\176', '\176', '\176', '\176', '\176', '\176', '\000', '\000', '\077', '\077', '\077', '\077', '\077', '\077', '\000', +'\000', '\037', '\037', '\037', '\037', '\037', '\037', '\000', '\000', '\017', '\017', '\017', '\017', '\017', '\017', '\000', +'\000', '\007', '\007', '\007', '\007', '\007', '\007', '\000', '\000', '\003', '\003', '\003', '\003', '\003', '\003', '\000', +'\000', '\001', '\001', '\001', '\001', '\001', '\001', '\000', '\000', '\200', '\200', '\200', '\200', '\200', '\200', '\000', +'\000', '\300', '\300', '\300', '\300', '\300', '\300', '\000', '\000', '\340', '\340', '\340', '\340', '\340', '\340', '\000', +'\000', '\360', '\360', '\360', '\360', '\360', '\360', '\000', '\000', '\370', '\370', '\370', '\370', '\370', '\370', '\000', +'\376', '\376', '\376', '\376', '\376', '\376', '\376', '\376', '\377', '\377', '\000', '\030', '\074', '\176', '\377', '\000', +'\377', '\377', '\000', '\000', '\000', '\000', '\030', '\030', '\377', '\377', '\000', '\030', '\074', '\176', '\377', '\030', +'\377', '\377', '\000', '\102', '\146', '\132', '\102', '\102', '\000', '\000', '\000', '\030', '\030', '\000', '\000', '\000', +'\000', '\300', '\300', '\300', '\300', '\300', '\300', '\300', '\000', '\330', '\330', '\330', '\330', '\330', '\330', '\330', +'\000', '\333', '\333', '\333', '\333', '\333', '\333', '\333', '\000', '\140', '\140', '\140', '\140', '\140', '\140', '\140', +'\000', '\154', '\154', '\154', '\154', '\154', '\154', '\154', '\000', '\155', '\155', '\155', '\155', '\155', '\155', '\155', +'\000', '\200', '\200', '\200', '\200', '\200', '\200', '\200', '\000', '\260', '\260', '\260', '\260', '\260', '\260', '\260', +'\000', '\266', '\266', '\266', '\266', '\266', '\266', '\266', '\000', '\000', '\030', '\074', '\074', '\030', '\000', '\000', +'\000', '\000', '\000', '\146', '\000', '\000', '\000', '\000', '\000', '\036', '\041', '\100', '\000', '\000', '\000', '\000', +'\000', '\000', '\000', '\202', '\104', '\070', '\000', '\000', '\000', '\076', '\042', '\042', '\042', '\042', '\143', '\000', +'\000', '\076', '\042', '\042', '\042', '\042', '\343', '\000', '\000', '\060', '\054', '\043', '\040', '\040', '\040', '\000', +'\000', '\004', '\004', '\004', '\304', '\064', '\014', '\000', '\000', '\006', '\004', '\004', '\004', '\004', '\006', '\000', +'\000', '\300', '\100', '\100', '\100', '\100', '\300', '\000', '\000', '\000', '\000', '\030', '\030', '\000', '\000', '\000', +'\000', '\000', '\030', '\074', '\074', '\030', '\000', '\000', '\000', '\000', '\030', '\074', '\074', '\030', '\000', '\000', +'\000', '\000', '\074', '\074', '\074', '\074', '\000', '\000', '\000', '\000', '\074', '\074', '\074', '\074', '\000', '\000', +'\000', '\030', '\074', '\176', '\176', '\074', '\030', '\000', '\000', '\030', '\074', '\176', '\176', '\074', '\030', '\000', +'\000', '\074', '\176', '\176', '\176', '\176', '\074', '\000', '\000', '\074', '\176', '\176', '\176', '\176', '\074', '\000', +'\066', '\066', '\367', '\000', '\377', '\000', '\000', '\000', '\000', '\000', '\377', '\000', '\367', '\066', '\066', '\066', +'\066', '\066', '\067', '\060', '\067', '\066', '\066', '\066', '\000', '\000', '\377', '\000', '\377', '\000', '\000', '\000', +'\066', '\066', '\367', '\000', '\367', '\066', '\066', '\066', '\030', '\030', '\377', '\000', '\377', '\000', '\000', '\000', +'\066', '\066', '\066', '\066', '\377', '\000', '\000', '\000', '\000', '\000', '\377', '\000', '\377', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\377', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\077', '\000', '\000', '\000', +'\030', '\030', '\037', '\030', '\037', '\000', '\000', '\000', '\000', '\000', '\037', '\030', '\037', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\077', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\377', '\066', '\066', '\066', +'\030', '\030', '\377', '\030', '\377', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\370', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\037', '\030', '\030', '\030', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +'\000', '\000', '\000', '\000', '\377', '\377', '\377', '\377', '\360', '\360', '\360', '\360', '\360', '\360', '\360', '\360', +'\017', '\017', '\017', '\017', '\017', '\017', '\017', '\017', '\377', '\377', '\377', '\377', '\000', '\000', '\000', '\000', +'\000', '\000', '\166', '\334', '\310', '\334', '\166', '\000', '\000', '\170', '\314', '\370', '\314', '\370', '\300', '\300', +'\000', '\374', '\314', '\300', '\300', '\300', '\300', '\000', '\000', '\376', '\154', '\154', '\154', '\154', '\154', '\000', +'\374', '\314', '\140', '\060', '\140', '\314', '\374', '\000', '\000', '\000', '\176', '\330', '\330', '\330', '\160', '\000', +'\000', '\146', '\146', '\146', '\146', '\174', '\140', '\300', '\000', '\166', '\334', '\030', '\030', '\030', '\030', '\000', +'\374', '\060', '\170', '\314', '\314', '\170', '\060', '\374', '\070', '\154', '\306', '\376', '\306', '\154', '\070', '\000', +'\070', '\154', '\306', '\306', '\154', '\154', '\356', '\000', '\034', '\060', '\030', '\174', '\314', '\314', '\170', '\000', +'\000', '\000', '\176', '\333', '\333', '\176', '\000', '\000', '\006', '\014', '\176', '\333', '\333', '\176', '\140', '\300', +'\070', '\140', '\300', '\370', '\300', '\140', '\070', '\000', '\170', '\314', '\314', '\314', '\314', '\314', '\314', '\000', +'\000', '\374', '\000', '\374', '\000', '\374', '\000', '\000', '\060', '\060', '\374', '\060', '\060', '\000', '\374', '\000', +'\140', '\060', '\030', '\060', '\140', '\000', '\374', '\000', '\030', '\060', '\140', '\060', '\030', '\000', '\374', '\000', +'\016', '\033', '\033', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\330', '\330', '\160', +'\060', '\060', '\000', '\374', '\000', '\060', '\060', '\000', '\000', '\166', '\334', '\000', '\166', '\334', '\000', '\000', +'\070', '\154', '\154', '\070', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\030', '\030', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\030', '\000', '\000', '\000', '\017', '\014', '\014', '\014', '\354', '\154', '\074', '\034', +'\170', '\154', '\154', '\154', '\154', '\000', '\000', '\000', '\160', '\030', '\060', '\140', '\170', '\000', '\000', '\000', +'\000', '\000', '\074', '\074', '\074', '\074', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', + +}; +static unsigned const char font_default_upper_alt[] = { +'\170', '\314', '\300', '\314', '\170', '\030', '\014', '\170', '\000', '\314', '\000', '\314', '\314', '\314', '\176', '\000', +'\034', '\000', '\170', '\314', '\374', '\300', '\170', '\000', '\176', '\303', '\074', '\006', '\076', '\146', '\077', '\000', +'\314', '\000', '\170', '\014', '\174', '\314', '\176', '\000', '\340', '\000', '\170', '\014', '\174', '\314', '\176', '\000', +'\060', '\060', '\170', '\014', '\174', '\314', '\176', '\000', '\000', '\000', '\170', '\300', '\300', '\170', '\014', '\070', +'\176', '\303', '\074', '\146', '\176', '\140', '\074', '\000', '\314', '\000', '\170', '\314', '\374', '\300', '\170', '\000', +'\340', '\000', '\170', '\314', '\374', '\300', '\170', '\000', '\314', '\000', '\160', '\060', '\060', '\060', '\170', '\000', +'\174', '\306', '\070', '\030', '\030', '\030', '\074', '\000', '\340', '\000', '\160', '\060', '\060', '\060', '\170', '\000', +'\306', '\070', '\154', '\306', '\376', '\306', '\306', '\000', '\060', '\060', '\000', '\170', '\314', '\374', '\314', '\000', +'\034', '\000', '\374', '\140', '\170', '\140', '\374', '\000', '\000', '\000', '\177', '\014', '\177', '\314', '\177', '\000', +'\076', '\154', '\314', '\376', '\314', '\314', '\316', '\000', '\170', '\314', '\000', '\170', '\314', '\314', '\170', '\000', +'\000', '\314', '\000', '\170', '\314', '\314', '\170', '\000', '\000', '\340', '\000', '\170', '\314', '\314', '\170', '\000', +'\170', '\314', '\000', '\314', '\314', '\314', '\176', '\000', '\000', '\340', '\000', '\314', '\314', '\314', '\176', '\000', +'\000', '\314', '\000', '\314', '\314', '\174', '\014', '\370', '\303', '\030', '\074', '\146', '\146', '\074', '\030', '\000', +'\314', '\000', '\314', '\314', '\314', '\314', '\170', '\000', '\030', '\030', '\176', '\300', '\300', '\176', '\030', '\030', +'\070', '\154', '\144', '\360', '\140', '\346', '\374', '\000', '\314', '\314', '\170', '\374', '\060', '\374', '\060', '\060', +'\370', '\314', '\314', '\372', '\306', '\317', '\306', '\307', '\016', '\033', '\030', '\074', '\030', '\030', '\330', '\160', +'\034', '\000', '\170', '\014', '\174', '\314', '\176', '\000', '\070', '\000', '\160', '\060', '\060', '\060', '\170', '\000', +'\000', '\034', '\000', '\170', '\314', '\314', '\170', '\000', '\000', '\034', '\000', '\314', '\314', '\314', '\176', '\000', +'\000', '\370', '\000', '\370', '\314', '\314', '\314', '\000', '\374', '\000', '\314', '\354', '\374', '\334', '\314', '\000', +'\074', '\154', '\154', '\076', '\000', '\176', '\000', '\000', '\070', '\154', '\154', '\070', '\000', '\174', '\000', '\000', +'\060', '\000', '\060', '\140', '\300', '\314', '\170', '\000', '\000', '\000', '\000', '\374', '\300', '\300', '\000', '\000', +'\000', '\000', '\000', '\374', '\014', '\014', '\000', '\000', '\303', '\306', '\314', '\336', '\063', '\146', '\314', '\017', +'\303', '\306', '\314', '\333', '\067', '\157', '\317', '\003', '\030', '\030', '\000', '\030', '\030', '\030', '\030', '\000', +'\000', '\063', '\146', '\314', '\146', '\063', '\000', '\000', '\000', '\314', '\146', '\063', '\146', '\314', '\000', '\000', +'\042', '\210', '\042', '\210', '\042', '\210', '\042', '\210', '\125', '\252', '\125', '\252', '\125', '\252', '\125', '\252', +'\333', '\167', '\333', '\356', '\333', '\167', '\333', '\356', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', +'\030', '\030', '\030', '\030', '\370', '\030', '\030', '\030', '\030', '\030', '\370', '\030', '\370', '\030', '\030', '\030', +'\066', '\066', '\066', '\066', '\366', '\066', '\066', '\066', '\000', '\000', '\000', '\000', '\376', '\066', '\066', '\066', +'\000', '\000', '\370', '\030', '\370', '\030', '\030', '\030', '\066', '\066', '\366', '\006', '\366', '\066', '\066', '\066', +'\066', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\000', '\000', '\376', '\006', '\366', '\066', '\066', '\066', +'\066', '\066', '\366', '\006', '\376', '\000', '\000', '\000', '\066', '\066', '\066', '\066', '\376', '\000', '\000', '\000', +'\030', '\030', '\370', '\030', '\370', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\370', '\030', '\030', '\030', +'\030', '\030', '\030', '\030', '\037', '\000', '\000', '\000', '\030', '\030', '\030', '\030', '\377', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\377', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\037', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\377', '\000', '\000', '\000', '\030', '\030', '\030', '\030', '\377', '\030', '\030', '\030', +'\030', '\030', '\037', '\030', '\037', '\030', '\030', '\030', '\066', '\066', '\066', '\066', '\067', '\066', '\066', '\066', +'\066', '\066', '\067', '\060', '\077', '\000', '\000', '\000', '\000', '\000', '\077', '\060', '\067', '\066', '\066', '\066', +'\066', '\066', '\367', '\000', '\377', '\000', '\000', '\000', '\000', '\000', '\377', '\000', '\367', '\066', '\066', '\066', +'\066', '\066', '\067', '\060', '\067', '\066', '\066', '\066', '\000', '\000', '\377', '\000', '\377', '\000', '\000', '\000', +'\066', '\066', '\367', '\000', '\367', '\066', '\066', '\066', '\030', '\030', '\377', '\000', '\377', '\000', '\000', '\000', +'\066', '\066', '\066', '\066', '\377', '\000', '\000', '\000', '\000', '\000', '\377', '\000', '\377', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\377', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\077', '\000', '\000', '\000', +'\030', '\030', '\037', '\030', '\037', '\000', '\000', '\000', '\000', '\000', '\037', '\030', '\037', '\030', '\030', '\030', +'\000', '\000', '\000', '\000', '\077', '\066', '\066', '\066', '\066', '\066', '\066', '\066', '\377', '\066', '\066', '\066', +'\030', '\030', '\377', '\030', '\377', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\370', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\037', '\030', '\030', '\030', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', +'\000', '\000', '\000', '\000', '\377', '\377', '\377', '\377', '\360', '\360', '\360', '\360', '\360', '\360', '\360', '\360', +'\017', '\017', '\017', '\017', '\017', '\017', '\017', '\017', '\377', '\377', '\377', '\377', '\000', '\000', '\000', '\000', +'\000', '\000', '\166', '\334', '\310', '\334', '\166', '\000', '\000', '\170', '\314', '\370', '\314', '\370', '\300', '\300', +'\000', '\374', '\314', '\300', '\300', '\300', '\300', '\000', '\000', '\376', '\154', '\154', '\154', '\154', '\154', '\000', +'\374', '\314', '\140', '\060', '\140', '\314', '\374', '\000', '\000', '\000', '\176', '\330', '\330', '\330', '\160', '\000', +'\000', '\146', '\146', '\146', '\146', '\174', '\140', '\300', '\000', '\166', '\334', '\030', '\030', '\030', '\030', '\000', +'\374', '\060', '\170', '\314', '\314', '\170', '\060', '\374', '\070', '\154', '\306', '\376', '\306', '\154', '\070', '\000', +'\070', '\154', '\306', '\306', '\154', '\154', '\356', '\000', '\034', '\060', '\030', '\174', '\314', '\314', '\170', '\000', +'\000', '\000', '\176', '\333', '\333', '\176', '\000', '\000', '\006', '\014', '\176', '\333', '\333', '\176', '\140', '\300', +'\070', '\140', '\300', '\370', '\300', '\140', '\070', '\000', '\170', '\314', '\314', '\314', '\314', '\314', '\314', '\000', +'\000', '\374', '\000', '\374', '\000', '\374', '\000', '\000', '\060', '\060', '\374', '\060', '\060', '\000', '\374', '\000', +'\140', '\060', '\030', '\060', '\140', '\000', '\374', '\000', '\030', '\060', '\140', '\060', '\030', '\000', '\374', '\000', +'\016', '\033', '\033', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\030', '\330', '\330', '\160', +'\060', '\060', '\000', '\374', '\000', '\060', '\060', '\000', '\000', '\166', '\334', '\000', '\166', '\334', '\000', '\000', +'\070', '\154', '\154', '\070', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\030', '\030', '\000', '\000', '\000', +'\000', '\000', '\000', '\000', '\030', '\000', '\000', '\000', '\017', '\014', '\014', '\014', '\354', '\154', '\074', '\034', +'\170', '\154', '\154', '\154', '\154', '\000', '\000', '\000', '\160', '\030', '\060', '\140', '\170', '\000', '\000', '\000', +'\000', '\000', '\074', '\074', '\074', '\074', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', + +}; +static unsigned const char font_half_width[] = { +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\000', '\000', '\000', '\000', '\104', '\104', '\004', '\100', '\252', '\240', '\000', '\000', '\252', '\352', '\352', '\240', +'\106', '\216', '\054', '\100', '\242', '\104', '\110', '\240', '\112', '\112', '\254', '\140', '\104', '\200', '\000', '\000', +'\044', '\104', '\104', '\040', '\204', '\104', '\104', '\200', '\012', '\116', '\112', '\000', '\004', '\116', '\104', '\000', +'\000', '\000', '\004', '\110', '\000', '\016', '\000', '\000', '\000', '\000', '\004', '\100', '\042', '\104', '\110', '\200', +'\112', '\252', '\252', '\100', '\114', '\104', '\104', '\340', '\112', '\044', '\110', '\340', '\302', '\044', '\042', '\300', +'\252', '\256', '\042', '\040', '\350', '\214', '\042', '\300', '\112', '\214', '\252', '\100', '\342', '\042', '\042', '\040', +'\112', '\244', '\252', '\100', '\352', '\256', '\042', '\040', '\004', '\100', '\004', '\100', '\004', '\100', '\004', '\110', +'\002', '\110', '\102', '\000', '\000', '\340', '\340', '\000', '\010', '\102', '\110', '\000', '\302', '\044', '\100', '\100', +'\112', '\256', '\250', '\140', '\112', '\256', '\252', '\240', '\312', '\254', '\252', '\300', '\150', '\210', '\210', '\140', +'\312', '\252', '\252', '\300', '\350', '\214', '\210', '\340', '\350', '\214', '\210', '\200', '\150', '\212', '\252', '\140', +'\252', '\256', '\252', '\240', '\344', '\104', '\104', '\340', '\042', '\042', '\252', '\100', '\252', '\254', '\252', '\240', +'\210', '\210', '\210', '\340', '\256', '\352', '\252', '\240', '\312', '\252', '\252', '\240', '\352', '\252', '\252', '\340', +'\312', '\254', '\210', '\200', '\112', '\252', '\354', '\140', '\312', '\254', '\252', '\240', '\112', '\204', '\052', '\100', +'\344', '\104', '\104', '\100', '\252', '\252', '\252', '\340', '\252', '\252', '\244', '\100', '\252', '\252', '\356', '\240', +'\252', '\344', '\352', '\240', '\252', '\244', '\104', '\100', '\342', '\044', '\210', '\340', '\144', '\104', '\104', '\140', +'\210', '\104', '\102', '\040', '\304', '\104', '\104', '\300', '\004', '\252', '\000', '\000', '\000', '\000', '\000', '\016', +'\104', '\040', '\000', '\000', '\000', '\102', '\152', '\140', '\210', '\214', '\252', '\300', '\000', '\112', '\212', '\100', +'\042', '\046', '\252', '\140', '\000', '\112', '\310', '\140', '\112', '\214', '\210', '\200', '\000', '\152', '\246', '\054', +'\210', '\214', '\252', '\240', '\100', '\304', '\104', '\340', '\040', '\142', '\042', '\244', '\210', '\252', '\312', '\240', +'\104', '\104', '\104', '\100', '\000', '\256', '\352', '\240', '\000', '\312', '\252', '\240', '\000', '\112', '\252', '\100', +'\000', '\312', '\254', '\210', '\000', '\152', '\246', '\042', '\000', '\352', '\210', '\200', '\000', '\150', '\102', '\300', +'\104', '\344', '\104', '\040', '\000', '\252', '\252', '\140', '\000', '\252', '\244', '\100', '\000', '\252', '\356', '\240', +'\000', '\252', '\112', '\240', '\000', '\252', '\246', '\054', '\000', '\342', '\110', '\340', '\044', '\114', '\104', '\040', +'\104', '\104', '\104', '\100', '\204', '\106', '\104', '\200', '\056', '\200', '\000', '\000', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\000', '\360', '\360', '\000', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', +'\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', '\245', + +}; diff --git a/include/auto/helpenum.h b/include/auto/helpenum.h new file mode 100644 index 000000000..caaae466a --- /dev/null +++ b/include/auto/helpenum.h @@ -0,0 +1,12 @@ +enum { +HELP_GLOBAL, +HELP_PATTERN_EDITOR, +HELP_SAMPLE_LIST, +HELP_INSTRUMENT_LIST, +HELP_INFO_PAGE, +HELP_MESSAGE_EDITOR, +HELP_ORDERLIST_PANNING, +HELP_ORDERLIST_VOLUME, +HELP_MIDI_OUTPUT, +HELP_NUM_ITEMS +}; diff --git a/include/auto/helptext.h b/include/auto/helptext.h new file mode 100644 index 000000000..bd63cd6c8 --- /dev/null +++ b/include/auto/helptext.h @@ -0,0 +1,1378 @@ +/* this file should only be included by helptext.c */ +static unsigned char help_text[] = { +'\174', '\040', '\107', '\154', '\157', '\142', '\141', '\154', '\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', +'\040', '\040', '\040', '\106', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\110', '\145', '\154', '\160', '\040', '\050', '\103', '\157', '\156', '\164', '\145', +'\170', '\164', '\040', '\163', '\145', '\156', '\163', '\151', '\164', '\151', '\166', '\145', '\041', '\051', '\012', '\174', +'\040', '\040', '\040', '\123', '\150', '\151', '\146', '\164', '\055', '\106', '\061', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\115', '\111', '\104', '\111', '\040', '\123', '\143', '\162', '\145', '\145', '\156', +'\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\061', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\171', '\163', '\164', '\145', '\155', '\040', '\103', '\157', +'\156', '\146', '\151', '\147', '\165', '\162', '\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\040', +'\106', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\105', '\144', '\151', '\164', '\157', '\162', +'\040', '\057', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\105', '\144', '\151', '\164', '\157', +'\162', '\040', '\117', '\160', '\164', '\151', '\157', '\156', '\163', '\012', '\174', '\040', '\040', '\040', '\106', '\063', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\123', '\141', '\155', '\160', '\154', '\145', '\040', '\114', '\151', '\163', '\164', '\012', '\174', '\040', '\040', '\040', +'\103', '\164', '\162', '\154', '\055', '\106', '\063', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\114', '\151', '\142', '\162', '\141', '\162', '\171', +'\012', '\174', '\040', '\040', '\040', '\106', '\064', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', +'\164', '\040', '\114', '\151', '\163', '\164', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\106', '\064', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', +'\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\114', '\151', '\142', '\162', '\141', '\162', '\171', '\012', +'\174', '\040', '\040', '\040', '\106', '\065', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\111', '\156', '\146', '\157', '\162', +'\155', '\141', '\164', '\151', '\157', '\156', '\040', '\057', '\040', '\120', '\154', '\141', '\171', '\040', '\163', '\157', +'\156', '\147', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\065', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\123', '\157', +'\156', '\147', '\012', '\174', '\040', '\040', '\040', '\106', '\066', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', +'\040', '\040', '\123', '\150', '\151', '\146', '\164', '\055', '\106', '\066', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\163', '\157', '\156', '\147', '\040', '\146', '\162', +'\157', '\155', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\117', '\162', '\144', '\145', '\162', +'\012', '\174', '\040', '\040', '\040', '\106', '\067', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\146', '\162', '\157', '\155', +'\040', '\155', '\141', '\162', '\153', '\040', '\057', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', +'\162', '\157', '\167', '\012', '\174', '\040', '\040', '\040', '\106', '\070', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\164', '\157', '\160', '\040', '\120', +'\154', '\141', '\171', '\142', '\141', '\143', '\153', '\012', '\174', '\040', '\040', '\040', '\106', '\071', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\114', '\157', +'\141', '\144', '\040', '\115', '\157', '\144', '\165', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\150', +'\151', '\146', '\164', '\055', '\106', '\071', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\115', '\145', '\163', '\163', '\141', '\147', '\145', '\040', '\105', '\144', '\151', '\164', '\157', '\162', '\012', '\174', +'\040', '\040', '\040', '\106', '\061', '\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\115', '\157', '\144', '\165', '\154', '\145', +'\012', '\174', '\040', '\040', '\040', '\106', '\061', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\114', '\151', '\163', +'\164', '\040', '\141', '\156', '\144', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\012', '\174', '\040', +'\062', '\052', '\106', '\061', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\114', '\151', '\163', '\164', '\040', '\141', +'\156', '\144', '\040', '\103', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\126', '\157', '\154', '\165', '\155', +'\145', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\061', '\061', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\040', '\114', +'\157', '\147', '\147', '\151', '\156', '\147', '\012', '\174', '\040', '\040', '\040', '\106', '\061', '\062', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\156', +'\147', '\040', '\126', '\141', '\162', '\151', '\141', '\142', '\154', '\145', '\163', '\040', '\046', '\040', '\104', '\151', +'\162', '\145', '\143', '\164', '\157', '\162', '\171', '\040', '\103', '\157', '\156', '\146', '\151', '\147', '\165', '\162', +'\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', +'\061', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\141', '\154', '\145', +'\164', '\164', '\145', '\040', '\103', '\157', '\156', '\146', '\151', '\147', '\165', '\162', '\141', '\164', '\151', '\157', +'\156', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\173', '\040', '\175', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', +'\163', '\145', '\057', '\111', '\156', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\160', '\154', '\141', '\171', +'\142', '\141', '\143', '\153', '\040', '\163', '\160', '\145', '\145', '\144', '\012', '\174', '\040', '\040', '\040', '\133', +'\040', '\135', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\057', '\111', '\156', '\143', '\162', '\145', '\141', +'\163', '\145', '\040', '\147', '\154', '\157', '\142', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\106', '\061', '\040', '\055', '\076', '\040', '\101', +'\154', '\164', '\055', '\106', '\070', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\163', '\040', '\061', '\055', '\076', '\070', '\012', '\174', '\012', '\041', '\040', +'\040', '\040', '\103', '\164', '\162', '\154', '\055', '\104', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\104', '\117', '\123', '\040', '\123', '\150', '\145', '\154', '\154', '\012', '\174', '\040', +'\040', '\040', '\103', '\164', '\162', '\154', '\055', '\105', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\122', '\145', '\146', '\162', '\145', '\163', '\150', '\040', '\163', '\143', '\162', '\145', +'\145', '\156', '\040', '\141', '\156', '\144', '\040', '\162', '\145', '\163', '\145', '\164', '\040', '\143', '\141', '\143', +'\150', '\145', '\040', '\151', '\144', '\145', '\156', '\164', '\151', '\146', '\151', '\143', '\141', '\164', '\151', '\157', +'\156', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\111', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\151', '\156', '\151', '\164', '\151', '\141', +'\154', '\151', '\163', '\145', '\040', '\163', '\157', '\165', '\156', '\144', '\040', '\144', '\162', '\151', '\166', '\145', +'\162', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\115', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\155', +'\157', '\165', '\163', '\145', '\040', '\143', '\165', '\162', '\163', '\157', '\162', '\012', '\174', '\040', '\040', '\040', +'\103', '\164', '\162', '\154', '\055', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\116', '\145', '\167', '\040', '\123', '\157', '\156', '\147', '\012', '\041', '\040', '\040', '\040', '\103', +'\164', '\162', '\154', '\055', '\121', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\121', '\165', '\151', '\164', '\040', '\164', '\157', '\040', '\104', '\117', '\123', '\012', '\072', '\040', '\040', +'\040', '\103', '\164', '\162', '\154', '\055', '\121', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\121', '\165', '\151', '\164', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\040', '\124', +'\162', '\141', '\143', '\153', '\145', '\162', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', +'\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\157', '\156', '\147', '\012', '\072', +'\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\107', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\107', '\157', '\040', '\164', '\157', '\040', '\157', '\162', '\144', '\145', '\162', +'\057', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\057', '\162', '\157', '\167', '\040', '\147', '\151', '\166', +'\145', '\156', '\040', '\164', '\151', '\155', '\145', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\120', '\141', '\164', +'\164', '\145', '\162', '\156', '\040', '\105', '\144', '\151', '\164', '\040', '\040', '\221', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', '\174', +'\012', '\174', '\040', '\123', '\165', '\155', '\155', '\141', '\162', '\171', '\040', '\157', '\146', '\040', '\105', '\146', +'\146', '\145', '\143', '\164', '\163', '\056', '\012', '\174', '\012', '\174', '\040', '\040', '\126', '\157', '\154', '\165', +'\155', '\145', '\040', '\103', '\157', '\154', '\165', '\155', '\156', '\040', '\145', '\146', '\146', '\145', '\143', '\164', +'\163', '\056', '\012', '\174', '\040', '\040', '\040', '\101', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\166', +'\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', +'\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\102', '\170', '\040', '\106', '\151', '\156', '\145', '\040', +'\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', +'\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\103', '\170', '\040', '\126', '\157', +'\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', +'\040', '\170', '\012', '\174', '\040', '\040', '\040', '\104', '\170', '\040', '\126', '\157', '\154', '\165', '\155', '\145', +'\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', +'\012', '\174', '\040', '\040', '\040', '\105', '\170', '\040', '\120', '\151', '\164', '\143', '\150', '\040', '\163', '\154', +'\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', +'\040', '\040', '\106', '\170', '\040', '\120', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', +'\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\107', '\170', '\040', +'\123', '\154', '\151', '\144', '\145', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\167', '\151', +'\164', '\150', '\040', '\163', '\160', '\145', '\145', '\144', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\110', +'\170', '\040', '\126', '\151', '\142', '\162', '\141', '\164', '\157', '\040', '\167', '\151', '\164', '\150', '\040', '\144', +'\145', '\160', '\164', '\150', '\040', '\170', '\012', '\174', '\012', '\174', '\040', '\040', '\107', '\145', '\156', '\145', +'\162', '\141', '\154', '\040', '\145', '\146', '\146', '\145', '\143', '\164', '\163', '\056', '\012', '\174', '\040', '\040', +'\040', '\101', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\163', '\157', '\156', '\147', '\040', '\163', '\160', +'\145', '\145', '\144', '\040', '\050', '\150', '\145', '\170', '\051', '\012', '\174', '\040', '\040', '\040', '\102', '\170', +'\170', '\040', '\112', '\165', '\155', '\160', '\040', '\164', '\157', '\040', '\117', '\162', '\144', '\145', '\162', '\040', +'\050', '\150', '\145', '\170', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\170', '\170', '\040', '\102', '\162', +'\145', '\141', '\153', '\040', '\164', '\157', '\040', '\162', '\157', '\167', '\040', '\170', '\170', '\040', '\050', '\150', +'\145', '\170', '\051', '\040', '\157', '\146', '\040', '\156', '\145', '\170', '\164', '\040', '\160', '\141', '\164', '\164', +'\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\104', '\060', '\170', '\040', '\126', '\157', '\154', '\165', +'\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', +'\040', '\170', '\012', '\174', '\040', '\040', '\040', '\104', '\170', '\060', '\040', '\126', '\157', '\154', '\165', '\155', +'\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', +'\174', '\040', '\040', '\040', '\104', '\106', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\166', '\157', '\154', +'\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', +'\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\104', '\170', '\106', '\040', '\106', '\151', '\156', '\145', +'\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', +'\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\105', '\170', '\170', '\040', '\120', '\151', +'\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', +'\171', '\040', '\170', '\170', '\012', '\174', '\040', '\040', '\040', '\105', '\106', '\170', '\040', '\106', '\151', '\156', +'\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', +'\167', '\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\105', '\105', '\170', '\040', +'\105', '\170', '\164', '\162', '\141', '\040', '\146', '\151', '\156', '\145', '\040', '\160', '\151', '\164', '\143', '\150', +'\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', +'\012', '\174', '\040', '\040', '\040', '\106', '\170', '\170', '\040', '\120', '\151', '\164', '\143', '\150', '\040', '\163', +'\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\170', '\012', '\174', '\040', +'\040', '\040', '\106', '\106', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\160', '\151', '\164', '\143', '\150', +'\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', +'\040', '\040', '\040', '\106', '\105', '\170', '\040', '\105', '\170', '\164', '\162', '\141', '\040', '\146', '\151', '\156', +'\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', +'\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\107', '\170', '\170', '\040', '\123', '\154', +'\151', '\144', '\145', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\167', '\151', '\164', '\150', +'\040', '\163', '\160', '\145', '\145', '\144', '\040', '\170', '\170', '\012', '\174', '\040', '\040', '\040', '\110', '\170', +'\171', '\040', '\126', '\151', '\142', '\162', '\141', '\164', '\157', '\040', '\167', '\151', '\164', '\150', '\040', '\163', +'\160', '\145', '\145', '\144', '\040', '\170', '\054', '\040', '\144', '\145', '\160', '\164', '\150', '\040', '\171', '\012', +'\174', '\040', '\040', '\040', '\111', '\170', '\171', '\040', '\124', '\162', '\145', '\155', '\157', '\162', '\040', '\167', +'\151', '\164', '\150', '\040', '\157', '\156', '\164', '\151', '\155', '\145', '\040', '\170', '\040', '\141', '\156', '\144', +'\040', '\157', '\146', '\146', '\164', '\151', '\155', '\145', '\040', '\171', '\012', '\174', '\040', '\040', '\040', '\112', +'\170', '\171', '\040', '\101', '\162', '\160', '\145', '\147', '\147', '\151', '\157', '\040', '\167', '\151', '\164', '\150', +'\040', '\150', '\141', '\154', '\146', '\164', '\157', '\156', '\145', '\163', '\040', '\170', '\040', '\141', '\156', '\144', +'\040', '\171', '\012', '\174', '\040', '\040', '\040', '\113', '\170', '\170', '\040', '\104', '\165', '\141', '\154', '\040', +'\103', '\157', '\155', '\155', '\141', '\156', '\144', '\072', '\040', '\110', '\060', '\060', '\040', '\046', '\040', '\104', +'\170', '\170', '\012', '\174', '\040', '\040', '\040', '\114', '\170', '\170', '\040', '\104', '\165', '\141', '\154', '\040', +'\103', '\157', '\155', '\155', '\141', '\156', '\144', '\072', '\040', '\107', '\060', '\060', '\040', '\046', '\040', '\104', +'\170', '\170', '\012', '\174', '\040', '\040', '\040', '\115', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\143', +'\150', '\141', '\156', '\156', '\145', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\164', '\157', +'\040', '\170', '\170', '\040', '\050', '\060', '\055', '\076', '\064', '\060', '\150', '\051', '\012', '\174', '\040', '\040', +'\040', '\116', '\060', '\170', '\040', '\103', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\166', '\157', '\154', +'\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', +'\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\116', '\170', '\060', '\040', '\103', '\150', '\141', '\156', +'\156', '\145', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', +'\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\116', '\106', '\170', +'\040', '\106', '\151', '\156', '\145', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\166', '\157', +'\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', +'\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\116', '\170', '\106', '\040', '\106', '\151', '\156', +'\145', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', +'\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', +'\040', '\040', '\040', '\117', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\040', '\157', '\146', '\146', '\163', '\145', '\164', '\040', '\164', '\157', '\040', '\171', '\170', '\170', '\060', +'\060', '\150', '\054', '\040', '\171', '\040', '\163', '\145', '\164', '\040', '\167', '\151', '\164', '\150', '\040', '\123', +'\101', '\171', '\012', '\174', '\040', '\040', '\040', '\120', '\060', '\170', '\040', '\120', '\141', '\156', '\156', '\151', +'\156', '\147', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\164', '\157', '\040', '\162', '\151', '\147', '\150', +'\164', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\120', '\170', '\060', '\040', '\120', +'\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\164', '\157', '\040', +'\154', '\145', '\146', '\164', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\120', '\106', +'\170', '\040', '\106', '\151', '\156', '\145', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', +'\154', '\151', '\144', '\145', '\040', '\164', '\157', '\040', '\162', '\151', '\147', '\150', '\164', '\040', '\142', '\171', +'\040', '\170', '\012', '\174', '\040', '\040', '\040', '\120', '\170', '\106', '\040', '\106', '\151', '\156', '\145', '\040', +'\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\164', '\157', +'\040', '\154', '\145', '\146', '\164', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\121', +'\170', '\171', '\040', '\122', '\145', '\164', '\162', '\151', '\147', '\147', '\145', '\162', '\040', '\156', '\157', '\164', +'\145', '\040', '\145', '\166', '\145', '\162', '\171', '\040', '\171', '\040', '\164', '\151', '\143', '\153', '\163', '\040', +'\167', '\151', '\164', '\150', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\155', '\157', '\144', '\151', +'\146', '\151', '\145', '\162', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\126', '\141', '\154', +'\165', '\145', '\163', '\040', '\146', '\157', '\162', '\040', '\170', '\072', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\060', '\072', '\040', '\116', '\157', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', +'\143', '\150', '\141', '\156', '\147', '\145', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\070', +'\072', '\040', '\116', '\157', '\164', '\040', '\165', '\163', '\145', '\144', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\061', '\072', '\040', '\055', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\071', +'\072', '\040', '\053', '\061', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\072', '\040', +'\055', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', '\072', '\040', '\053', '\062', '\012', '\174', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\063', '\072', '\040', '\055', '\064', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\102', '\072', '\040', '\053', '\064', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\064', '\072', '\040', '\055', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\072', '\040', +'\053', '\070', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\065', '\072', '\040', '\055', '\061', +'\066', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\072', '\040', '\053', '\061', '\066', '\012', '\174', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\066', '\072', '\040', '\052', '\062', '\057', '\063', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\105', '\072', '\040', '\052', '\063', '\057', '\062', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\067', '\072', '\040', '\052', '\061', '\057', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\106', '\072', +'\040', '\052', '\062', '\012', '\174', '\040', '\040', '\040', '\122', '\170', '\171', '\040', '\124', '\162', '\145', '\155', +'\157', '\154', '\157', '\040', '\167', '\151', '\164', '\150', '\040', '\163', '\160', '\145', '\145', '\144', '\040', '\170', +'\054', '\040', '\144', '\145', '\160', '\164', '\150', '\040', '\171', '\012', '\043', '\040', '\040', '\040', '\123', '\060', +'\170', '\040', '\123', '\145', '\164', '\040', '\146', '\151', '\154', '\164', '\145', '\162', '\012', '\043', '\040', '\040', +'\040', '\123', '\061', '\170', '\040', '\123', '\145', '\164', '\040', '\147', '\154', '\151', '\163', '\163', '\141', '\156', +'\144', '\157', '\040', '\143', '\157', '\156', '\164', '\162', '\157', '\154', '\012', '\043', '\040', '\040', '\040', '\123', +'\062', '\170', '\040', '\123', '\145', '\164', '\040', '\146', '\151', '\156', '\145', '\164', '\165', '\156', '\145', '\012', +'\174', '\040', '\040', '\040', '\123', '\063', '\170', '\040', '\123', '\145', '\164', '\040', '\166', '\151', '\142', '\162', +'\141', '\164', '\157', '\040', '\167', '\141', '\166', '\145', '\146', '\157', '\162', '\155', '\040', '\164', '\157', '\040', +'\164', '\171', '\160', '\145', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\123', '\064', '\170', '\040', '\123', +'\145', '\164', '\040', '\164', '\162', '\145', '\155', '\157', '\154', '\157', '\040', '\167', '\141', '\166', '\145', '\146', +'\157', '\162', '\155', '\040', '\164', '\157', '\040', '\164', '\171', '\160', '\145', '\040', '\170', '\012', '\174', '\040', +'\040', '\040', '\123', '\065', '\170', '\040', '\123', '\145', '\164', '\040', '\160', '\141', '\156', '\142', '\162', '\145', +'\154', '\154', '\157', '\040', '\167', '\141', '\166', '\145', '\146', '\157', '\162', '\155', '\040', '\164', '\157', '\040', +'\164', '\171', '\160', '\145', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\127', '\141', '\166', +'\145', '\146', '\157', '\162', '\155', '\163', '\040', '\146', '\157', '\162', '\040', '\143', '\157', '\155', '\155', '\141', +'\156', '\144', '\163', '\040', '\123', '\063', '\170', '\054', '\040', '\123', '\064', '\170', '\040', '\141', '\156', '\144', +'\040', '\123', '\065', '\170', '\072', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\060', '\072', +'\040', '\123', '\151', '\156', '\145', '\040', '\167', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\061', '\072', '\040', '\122', '\141', '\155', '\160', '\040', '\144', '\157', '\167', '\156', '\012', +'\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\072', '\040', '\123', '\161', '\165', '\141', '\162', +'\145', '\040', '\167', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\063', +'\072', '\040', '\122', '\141', '\156', '\144', '\157', '\155', '\040', '\167', '\141', '\166', '\145', '\012', '\174', '\040', +'\040', '\040', '\123', '\066', '\170', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\145', +'\154', '\141', '\171', '\040', '\146', '\157', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', '\012', +'\174', '\040', '\040', '\040', '\123', '\067', '\060', '\040', '\120', '\141', '\163', '\164', '\040', '\156', '\157', '\164', +'\145', '\040', '\143', '\165', '\164', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\061', '\040', '\120', '\141', +'\163', '\164', '\040', '\156', '\157', '\164', '\145', '\040', '\157', '\146', '\146', '\012', '\174', '\040', '\040', '\040', +'\123', '\067', '\062', '\040', '\120', '\141', '\163', '\164', '\040', '\156', '\157', '\164', '\145', '\040', '\146', '\141', +'\144', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\063', '\040', '\123', '\145', '\164', '\040', '\116', +'\116', '\101', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\143', '\165', '\164', '\012', '\174', +'\040', '\040', '\040', '\123', '\067', '\064', '\040', '\123', '\145', '\164', '\040', '\116', '\116', '\101', '\040', '\164', +'\157', '\040', '\143', '\157', '\156', '\164', '\151', '\156', '\165', '\145', '\012', '\174', '\040', '\040', '\040', '\123', +'\067', '\065', '\040', '\123', '\145', '\164', '\040', '\116', '\116', '\101', '\040', '\164', '\157', '\040', '\156', '\157', +'\164', '\145', '\040', '\157', '\146', '\146', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\066', '\040', '\123', +'\145', '\164', '\040', '\116', '\116', '\101', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\146', +'\141', '\144', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\067', '\040', '\124', '\165', '\162', '\156', +'\040', '\157', '\146', '\146', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\145', '\156', '\166', '\145', +'\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\070', '\040', '\124', '\165', '\162', +'\156', '\040', '\157', '\156', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\145', '\156', '\166', '\145', +'\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\071', '\040', '\124', '\165', '\162', +'\156', '\040', '\157', '\146', '\146', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\145', '\156', +'\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\101', '\040', '\124', +'\165', '\162', '\156', '\040', '\157', '\156', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\145', +'\156', '\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\102', '\040', +'\124', '\165', '\162', '\156', '\040', '\157', '\146', '\146', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\145', +'\156', '\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\067', '\103', '\040', +'\124', '\165', '\162', '\156', '\040', '\157', '\156', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\145', '\156', +'\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', '\040', '\040', '\123', '\070', '\170', '\040', '\123', +'\145', '\164', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\160', '\157', '\163', '\151', '\164', +'\151', '\157', '\156', '\012', '\174', '\040', '\040', '\040', '\123', '\071', '\061', '\040', '\123', '\145', '\164', '\040', +'\163', '\165', '\162', '\162', '\157', '\165', '\156', '\144', '\040', '\163', '\157', '\165', '\156', '\144', '\012', '\174', +'\040', '\040', '\040', '\123', '\101', '\171', '\040', '\123', '\145', '\164', '\040', '\150', '\151', '\147', '\150', '\040', +'\166', '\141', '\154', '\165', '\145', '\040', '\157', '\146', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', +'\157', '\146', '\146', '\163', '\145', '\164', '\040', '\171', '\170', '\170', '\060', '\060', '\150', '\012', '\174', '\040', +'\040', '\040', '\123', '\102', '\060', '\040', '\123', '\145', '\164', '\040', '\154', '\157', '\157', '\160', '\142', '\141', +'\143', '\153', '\040', '\160', '\157', '\151', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\123', '\102', '\170', +'\040', '\114', '\157', '\157', '\160', '\040', '\170', '\040', '\164', '\151', '\155', '\145', '\163', '\040', '\164', '\157', +'\040', '\154', '\157', '\157', '\160', '\142', '\141', '\143', '\153', '\040', '\160', '\157', '\151', '\156', '\164', '\012', +'\174', '\040', '\040', '\040', '\123', '\103', '\170', '\040', '\116', '\157', '\164', '\145', '\040', '\143', '\165', '\164', +'\040', '\141', '\146', '\164', '\145', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', '\012', '\174', +'\040', '\040', '\040', '\123', '\104', '\170', '\040', '\116', '\157', '\164', '\145', '\040', '\144', '\145', '\154', '\141', +'\171', '\040', '\146', '\157', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', '\012', '\174', '\040', +'\040', '\040', '\123', '\105', '\170', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\145', +'\154', '\141', '\171', '\040', '\146', '\157', '\162', '\040', '\170', '\040', '\162', '\157', '\167', '\163', '\012', '\174', +'\040', '\040', '\040', '\123', '\106', '\170', '\040', '\123', '\145', '\164', '\040', '\160', '\141', '\162', '\141', '\155', +'\145', '\164', '\145', '\162', '\151', '\163', '\145', '\144', '\040', '\115', '\111', '\104', '\111', '\040', '\115', '\141', +'\143', '\162', '\157', '\012', '\174', '\040', '\040', '\040', '\124', '\060', '\170', '\040', '\124', '\145', '\155', '\160', +'\157', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', +'\170', '\012', '\174', '\040', '\040', '\040', '\124', '\061', '\170', '\040', '\124', '\145', '\155', '\160', '\157', '\040', +'\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', +'\040', '\040', '\124', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\124', '\145', '\155', '\160', '\157', '\040', +'\164', '\157', '\040', '\170', '\170', '\040', '\050', '\062', '\060', '\150', '\055', '\076', '\060', '\106', '\106', '\150', +'\051', '\012', '\174', '\040', '\040', '\040', '\125', '\170', '\171', '\040', '\106', '\151', '\156', '\145', '\040', '\166', +'\151', '\142', '\162', '\141', '\164', '\157', '\040', '\167', '\151', '\164', '\150', '\040', '\163', '\160', '\145', '\145', +'\144', '\040', '\170', '\054', '\040', '\144', '\145', '\160', '\164', '\150', '\040', '\171', '\012', '\174', '\040', '\040', +'\040', '\126', '\170', '\170', '\040', '\123', '\145', '\164', '\040', '\147', '\154', '\157', '\142', '\141', '\154', '\040', +'\166', '\157', '\154', '\165', '\155', '\145', '\040', '\164', '\157', '\040', '\170', '\170', '\040', '\050', '\060', '\055', +'\076', '\070', '\060', '\150', '\051', '\012', '\174', '\040', '\040', '\040', '\127', '\060', '\170', '\040', '\107', '\154', +'\157', '\142', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', +'\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', +'\127', '\170', '\060', '\040', '\107', '\154', '\157', '\142', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', +'\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\012', +'\174', '\040', '\040', '\040', '\127', '\106', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\147', '\154', '\157', +'\142', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', +'\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\127', +'\170', '\106', '\040', '\106', '\151', '\156', '\145', '\040', '\147', '\154', '\157', '\142', '\141', '\154', '\040', '\166', +'\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', +'\171', '\040', '\170', '\012', '\174', '\040', '\040', '\040', '\130', '\170', '\170', '\040', '\123', '\145', '\164', '\040', +'\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\160', '\157', '\163', '\151', '\164', '\151', '\157', '\156', +'\040', '\050', '\060', '\055', '\076', '\060', '\106', '\106', '\150', '\051', '\012', '\174', '\040', '\040', '\040', '\131', +'\170', '\171', '\040', '\120', '\141', '\156', '\142', '\162', '\145', '\154', '\154', '\157', '\040', '\167', '\151', '\164', +'\150', '\040', '\163', '\160', '\145', '\145', '\144', '\040', '\170', '\054', '\040', '\144', '\145', '\160', '\164', '\150', +'\040', '\171', '\012', '\174', '\040', '\040', '\040', '\132', '\170', '\170', '\040', '\115', '\111', '\104', '\111', '\040', +'\115', '\141', '\143', '\162', '\157', '\163', '\012', '\174', '\012', '\072', '\040', '\106', '\124', '\062', '\040', '\145', +'\146', '\146', '\145', '\143', '\164', '\040', '\164', '\162', '\141', '\156', '\163', '\154', '\141', '\164', '\151', '\157', +'\156', '\163', '\040', '\050', '\143', '\141', '\156', '\040', '\157', '\156', '\154', '\171', '\040', '\142', '\145', '\040', +'\163', '\141', '\166', '\145', '\144', '\040', '\151', '\156', '\040', '\130', '\115', '\040', '\155', '\157', '\144', '\165', +'\154', '\145', '\163', '\051', '\012', '\072', '\012', '\072', '\040', '\040', '\126', '\157', '\154', '\165', '\155', '\145', +'\040', '\143', '\157', '\154', '\165', '\155', '\156', '\056', '\012', '\072', '\040', '\040', '\040', '\044', '\170', '\040', +'\123', '\145', '\164', '\040', '\166', '\151', '\142', '\162', '\141', '\164', '\157', '\040', '\163', '\160', '\145', '\145', +'\144', '\040', '\164', '\157', '\040', '\170', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\133', '\044', '\101', '\060', '\055', '\044', '\101', '\106', '\135', '\012', '\072', '\040', '\040', '\040', +'\074', '\170', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', '\154', '\151', '\144', '\145', +'\040', '\164', '\157', '\040', '\154', '\145', '\146', '\164', '\040', '\142', '\171', '\040', '\170', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\133', '\044', '\104', '\060', '\055', '\044', '\104', '\106', '\135', '\012', '\072', +'\040', '\040', '\040', '\076', '\170', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\163', '\154', +'\151', '\144', '\145', '\040', '\164', '\157', '\040', '\162', '\151', '\147', '\150', '\164', '\040', '\142', '\171', '\040', +'\170', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\044', '\105', '\060', '\055', '\044', '\105', '\106', +'\135', '\012', '\072', '\012', '\072', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\145', +'\146', '\146', '\145', '\143', '\164', '\163', '\056', '\012', '\072', '\040', '\040', '\040', '\041', '\170', '\170', '\040', +'\123', '\145', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\133', '\103', '\170', '\170', '\135', '\040', '\055', '\040', '\077', '\012', '\072', '\040', '\040', '\040', '\043', +'\061', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', +'\151', '\144', '\145', '\040', '\165', '\160', '\040', '\142', '\171', '\040', '\170', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\133', '\105', '\061', '\170', '\135', '\040', '\055', '\040', '\106', '\106', '\170', '\012', +'\072', '\040', '\040', '\040', '\043', '\062', '\170', '\040', '\106', '\151', '\156', '\145', '\040', '\160', '\151', '\164', +'\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', +'\040', '\170', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\062', '\170', '\135', '\040', '\055', +'\040', '\105', '\106', '\170', '\012', '\072', '\040', '\040', '\040', '\043', '\063', '\170', '\040', '\123', '\145', '\164', +'\040', '\147', '\154', '\151', '\163', '\163', '\141', '\156', '\144', '\157', '\040', '\143', '\157', '\156', '\164', '\162', +'\157', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', +'\063', '\170', '\135', '\040', '\055', '\040', '\123', '\061', '\170', '\040', '\050', '\165', '\156', '\151', '\155', '\160', +'\154', '\145', '\155', '\145', '\156', '\164', '\145', '\144', '\051', '\012', '\072', '\040', '\040', '\040', '\043', '\064', +'\170', '\040', '\123', '\145', '\164', '\040', '\166', '\151', '\142', '\162', '\141', '\164', '\157', '\040', '\143', '\157', +'\156', '\164', '\162', '\157', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\133', '\105', '\064', '\170', '\135', '\040', '\055', '\040', '\123', '\063', '\170', '\012', '\072', +'\040', '\040', '\040', '\043', '\065', '\170', '\040', '\123', '\145', '\164', '\040', '\146', '\151', '\156', '\145', '\164', +'\165', '\156', '\145', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\065', '\170', '\135', '\040', '\055', '\040', +'\123', '\062', '\170', '\040', '\050', '\165', '\156', '\151', '\155', '\160', '\154', '\145', '\155', '\145', '\156', '\164', +'\145', '\144', '\051', '\012', '\072', '\040', '\040', '\040', '\043', '\066', '\170', '\040', '\123', '\145', '\164', '\040', +'\154', '\157', '\157', '\160', '\040', '\142', '\145', '\147', '\151', '\156', '\040', '\057', '\040', '\154', '\157', '\157', +'\160', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\066', +'\170', '\135', '\040', '\055', '\040', '\123', '\102', '\170', '\012', '\072', '\040', '\040', '\040', '\043', '\067', '\170', +'\040', '\123', '\145', '\164', '\040', '\164', '\162', '\145', '\155', '\157', '\154', '\157', '\040', '\143', '\157', '\156', +'\164', '\162', '\157', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\133', '\105', '\067', '\170', '\135', '\040', '\055', '\040', '\123', '\064', '\170', '\012', '\072', '\040', +'\040', '\040', '\043', '\071', '\170', '\040', '\122', '\145', '\164', '\162', '\151', '\147', '\147', '\145', '\162', '\040', +'\156', '\157', '\164', '\145', '\040', '\145', '\166', '\145', '\162', '\171', '\040', '\170', '\040', '\164', '\151', '\143', +'\153', '\163', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\071', '\170', '\135', '\040', '\055', '\040', '\121', +'\060', '\170', '\012', '\072', '\040', '\040', '\040', '\043', '\101', '\170', '\040', '\106', '\151', '\156', '\145', '\040', +'\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', '\040', +'\142', '\171', '\040', '\170', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\101', '\170', +'\135', '\040', '\055', '\040', '\104', '\170', '\106', '\012', '\072', '\040', '\040', '\040', '\043', '\102', '\170', '\040', +'\106', '\151', '\156', '\145', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\163', '\154', '\151', '\144', +'\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\040', '\040', '\040', '\040', '\040', +'\040', '\133', '\105', '\102', '\170', '\135', '\040', '\055', '\040', '\104', '\106', '\170', '\012', '\072', '\040', '\040', +'\040', '\043', '\103', '\170', '\040', '\116', '\157', '\164', '\145', '\040', '\143', '\165', '\164', '\040', '\141', '\146', +'\164', '\145', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\103', '\170', '\135', '\040', '\055', '\040', '\123', '\103', +'\170', '\012', '\072', '\040', '\040', '\040', '\043', '\104', '\170', '\040', '\116', '\157', '\164', '\145', '\040', '\144', +'\145', '\154', '\141', '\171', '\040', '\146', '\157', '\162', '\040', '\170', '\040', '\164', '\151', '\143', '\153', '\163', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\133', '\105', '\104', '\170', '\135', +'\040', '\055', '\040', '\123', '\104', '\170', '\012', '\072', '\040', '\040', '\040', '\043', '\105', '\170', '\040', '\120', +'\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\145', '\154', '\141', '\171', '\040', '\146', '\157', '\162', +'\040', '\170', '\040', '\162', '\157', '\167', '\163', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\133', '\105', '\105', '\170', '\135', '\040', '\055', '\040', '\123', '\105', '\170', '\012', '\072', '\040', '\040', '\040', +'\044', '\170', '\170', '\040', '\113', '\145', '\171', '\040', '\157', '\146', '\146', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\133', '\113', '\170', '\170', '\135', '\040', '\055', '\040', '\077', '\012', '\072', +'\040', '\040', '\040', '\045', '\061', '\170', '\040', '\105', '\170', '\164', '\162', '\141', '\040', '\146', '\151', '\156', +'\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', '\144', '\145', '\040', '\165', '\160', +'\040', '\142', '\171', '\040', '\170', '\040', '\040', '\040', '\133', '\130', '\061', '\170', '\135', '\040', '\055', '\040', +'\106', '\105', '\170', '\012', '\072', '\040', '\040', '\040', '\045', '\062', '\170', '\040', '\105', '\170', '\164', '\162', +'\141', '\040', '\146', '\151', '\156', '\145', '\040', '\160', '\151', '\164', '\143', '\150', '\040', '\163', '\154', '\151', +'\144', '\145', '\040', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\170', '\040', '\133', '\130', '\062', +'\170', '\135', '\040', '\055', '\040', '\105', '\105', '\170', '\012', '\072', '\040', '\040', '\040', '\046', '\170', '\170', +'\040', '\123', '\145', '\164', '\040', '\145', '\156', '\166', '\145', '\154', '\157', '\160', '\145', '\040', '\160', '\157', +'\163', '\151', '\164', '\151', '\157', '\156', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\133', '\114', '\170', '\170', '\135', '\040', '\055', '\040', '\077', '\012', '\072', '\012', '\045', '\012', +'\174', '\012', '\174', '\040', '\120', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\105', '\144', '\151', '\164', +'\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\107', '\162', '\145', '\171', '\040', +'\053', '\054', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\145', '\170', '\164', +'\057', '\120', '\162', '\145', '\166', '\151', '\157', '\165', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', +'\156', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\123', '\150', '\151', '\146', '\164', +'\040', '\053', '\054', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\145', '\170', '\164', +'\057', '\120', '\162', '\145', '\166', '\151', '\157', '\165', '\163', '\040', '\064', '\040', '\160', '\141', '\164', '\164', +'\145', '\162', '\156', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', +'\154', '\040', '\053', '\054', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\145', +'\170', '\164', '\057', '\120', '\162', '\145', '\166', '\151', '\157', '\165', '\163', '\040', '\157', '\162', '\144', '\145', +'\162', '\047', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\060', '\055', '\071', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\157', '\143', '\164', +'\141', '\166', '\145', '\057', '\166', '\157', '\154', '\165', '\155', '\145', '\057', '\151', '\156', '\163', '\164', '\162', +'\165', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\060', '\055', '\071', '\054', '\040', '\101', +'\055', '\106', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', +'\145', '\040', '\145', '\146', '\146', '\145', '\143', '\164', '\040', '\166', '\141', '\154', '\165', '\145', '\012', '\174', +'\040', '\040', '\040', '\101', '\055', '\132', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\145', '\146', '\146', '\145', '\143', +'\164', '\012', '\174', '\040', '\040', '\040', '\056', '\040', '\050', '\120', '\145', '\162', '\151', '\157', '\144', '\051', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\154', '\145', '\141', '\162', '\040', '\146', '\151', '\145', +'\154', '\144', '\050', '\163', '\051', '\012', '\174', '\040', '\040', '\040', '\061', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\157', '\164', '\145', '\040', +'\143', '\165', '\164', '\040', '\050', '\136', '\136', '\136', '\051', '\012', '\174', '\040', '\040', '\040', '\140', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', +'\157', '\164', '\145', '\040', '\157', '\146', '\146', '\040', '\050', '\043', '\043', '\043', '\051', '\040', '\057', '\040', +'\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\012', '\174', +'\040', '\040', '\040', '\123', '\160', '\141', '\143', '\145', '\142', '\141', '\162', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\125', '\163', '\145', '\040', '\154', '\141', '\163', '\164', '\040', '\156', '\157', '\164', +'\145', '\057', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\057', '\166', '\157', '\154', +'\165', '\155', '\145', '\057', '\145', '\146', '\146', '\145', '\143', '\164', '\057', '\145', '\146', '\146', '\145', '\143', +'\164', '\040', '\166', '\141', '\154', '\165', '\145', '\012', '\174', '\040', '\040', '\040', '\103', '\141', '\160', '\163', +'\040', '\114', '\157', '\143', '\153', '\053', '\113', '\145', '\171', '\040', '\040', '\040', '\040', '\120', '\162', '\145', +'\166', '\151', '\145', '\167', '\040', '\156', '\157', '\164', '\145', '\012', '\174', '\012', '\174', '\040', '\040', '\040', +'\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\107', '\145', '\164', '\040', '\144', '\145', '\146', '\141', '\165', '\154', '\164', '\040', '\156', '\157', '\164', +'\145', '\057', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\057', '\166', '\157', '\154', +'\165', '\155', '\145', '\057', '\145', '\146', '\146', '\145', '\143', '\164', '\012', '\174', '\040', '\040', '\040', '\074', +'\040', '\157', '\162', '\040', '\103', '\164', '\162', '\154', '\055', '\125', '\160', '\040', '\040', '\040', '\040', '\040', +'\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', +'\145', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\076', '\040', '\157', '\162', '\040', '\103', '\164', '\162', +'\154', '\055', '\104', '\157', '\167', '\156', '\040', '\040', '\040', '\111', '\156', '\143', '\162', '\145', '\141', '\163', +'\145', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', +'\040', '\107', '\162', '\145', '\171', '\040', '\057', '\054', '\052', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\057', '\111', '\156', '\143', '\162', '\145', +'\141', '\163', '\145', '\040', '\157', '\143', '\164', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', '\054', +'\040', '\050', '\103', '\157', '\155', '\155', '\141', '\051', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\124', '\157', '\147', '\147', '\154', '\145', '\040', '\145', '\144', '\151', '\164', '\040', '\155', '\141', '\163', '\153', +'\040', '\146', '\157', '\162', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\146', '\151', '\145', +'\154', '\144', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\111', '\156', '\163', '\057', '\104', '\145', '\154', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', +'\057', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\141', '\040', '\162', '\157', '\167', '\040', '\164', '\157', +'\057', '\146', '\162', '\157', '\155', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\156', +'\163', '\057', '\104', '\145', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', +'\164', '\057', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\141', '\156', '\040', '\145', '\156', '\164', '\151', +'\162', '\145', '\040', '\162', '\157', '\167', '\040', '\164', '\157', '\057', '\146', '\162', '\157', '\155', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\012', '\174', '\040', +'\040', '\040', '\125', '\160', '\057', '\104', '\157', '\167', '\156', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\165', '\160', '\057', '\144', '\157', '\167', '\156', '\040', +'\142', '\171', '\040', '\164', '\150', '\145', '\040', '\163', '\153', '\151', '\160', '\166', '\141', '\154', '\165', '\145', +'\040', '\050', '\163', '\145', '\164', '\040', '\167', '\151', '\164', '\150', '\040', '\101', '\154', '\164', '\040', '\060', +'\055', '\071', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\110', '\157', '\155', +'\145', '\057', '\105', '\156', '\144', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\165', '\160', +'\057', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\061', '\040', '\162', '\157', '\167', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\125', '\160', '\057', '\104', '\157', '\167', '\156', '\040', '\040', +'\040', '\040', '\040', '\040', '\123', '\154', '\151', '\144', '\145', '\040', '\160', '\141', '\164', '\164', '\145', '\162', +'\156', '\040', '\165', '\160', '\057', '\144', '\157', '\167', '\156', '\040', '\142', '\171', '\040', '\061', '\040', '\162', +'\157', '\167', '\012', '\174', '\040', '\040', '\040', '\114', '\145', '\146', '\164', '\057', '\122', '\151', '\147', '\150', +'\164', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\143', '\165', '\162', +'\163', '\157', '\162', '\040', '\154', '\145', '\146', '\164', '\057', '\162', '\151', '\147', '\150', '\164', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\114', '\145', '\146', '\164', '\057', '\122', '\151', '\147', '\150', +'\164', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\146', '\157', '\162', '\167', '\141', '\162', '\144', +'\163', '\057', '\142', '\141', '\143', '\153', '\167', '\141', '\162', '\144', '\163', '\040', '\157', '\156', '\145', '\040', +'\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', '\124', '\141', '\142', '\057', +'\123', '\150', '\151', '\146', '\164', '\055', '\124', '\141', '\142', '\040', '\040', '\040', '\040', '\115', '\157', '\166', +'\145', '\040', '\146', '\157', '\162', '\167', '\141', '\162', '\144', '\163', '\057', '\142', '\141', '\143', '\153', '\167', +'\141', '\162', '\144', '\163', '\040', '\164', '\157', '\040', '\156', '\157', '\164', '\145', '\040', '\143', '\157', '\154', +'\165', '\155', '\156', '\012', '\174', '\040', '\040', '\040', '\120', '\147', '\125', '\160', '\057', '\120', '\147', '\104', +'\156', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\165', '\160', +'\057', '\144', '\157', '\167', '\156', '\040', '\156', '\040', '\154', '\151', '\156', '\145', '\163', '\040', '\050', '\156', +'\075', '\122', '\157', '\167', '\040', '\110', '\151', '\154', '\151', '\147', '\150', '\164', '\040', '\115', '\141', '\152', +'\157', '\162', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\120', '\147', '\125', +'\160', '\057', '\120', '\147', '\104', '\156', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\164', '\157', +'\040', '\164', '\157', '\160', '\057', '\142', '\157', '\164', '\164', '\157', '\155', '\040', '\157', '\146', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\110', '\157', '\155', '\145', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', +'\040', '\164', '\157', '\040', '\163', '\164', '\141', '\162', '\164', '\040', '\157', '\146', '\040', '\143', '\157', '\154', +'\165', '\155', '\156', '\057', '\163', '\164', '\141', '\162', '\164', '\040', '\157', '\146', '\040', '\154', '\151', '\156', +'\145', '\057', '\163', '\164', '\141', '\162', '\164', '\040', '\157', '\146', '\040', '\160', '\141', '\164', '\164', '\145', +'\162', '\156', '\012', '\174', '\040', '\040', '\040', '\105', '\156', '\144', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\164', '\157', '\040', +'\145', '\156', '\144', '\040', '\157', '\146', '\040', '\143', '\157', '\154', '\165', '\155', '\156', '\057', '\145', '\156', +'\144', '\040', '\157', '\146', '\040', '\154', '\151', '\156', '\145', '\057', '\145', '\156', '\144', '\040', '\157', '\146', +'\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\102', '\141', '\143', +'\153', '\163', '\160', '\141', '\143', '\145', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', +'\166', '\145', '\040', '\164', '\157', '\040', '\160', '\162', '\145', '\166', '\151', '\157', '\165', '\163', '\040', '\160', +'\157', '\163', '\151', '\164', '\151', '\157', '\156', '\040', '\050', '\141', '\143', '\143', '\157', '\165', '\156', '\164', +'\163', '\040', '\146', '\157', '\162', '\040', '\115', '\165', '\154', '\164', '\151', '\143', '\150', '\141', '\156', '\156', +'\145', '\154', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\116', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', +'\145', '\040', '\115', '\165', '\154', '\164', '\151', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\155', +'\157', '\144', '\145', '\040', '\146', '\157', '\162', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', +'\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\062', '\052', '\101', '\154', '\164', '\055', +'\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\165', '\154', +'\164', '\151', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\123', '\145', '\154', '\145', '\143', '\164', +'\151', '\157', '\156', '\040', '\155', '\145', '\156', '\165', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', +'\154', '\164', '\055', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\123', '\164', '\157', '\162', '\145', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', +'\164', '\141', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\102', '\141', '\143', '\153', '\163', +'\160', '\141', '\143', '\145', '\040', '\040', '\040', '\040', '\122', '\145', '\166', '\145', '\162', '\164', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\102', '\141', '\143', '\153', '\163', '\160', +'\141', '\143', '\145', '\040', '\040', '\040', '\125', '\156', '\144', '\157', '\040', '\055', '\040', '\141', '\156', '\171', +'\040', '\146', '\165', '\156', '\143', '\164', '\151', '\157', '\156', '\040', '\167', '\151', '\164', '\150', '\040', '\040', +'\050', '\052', '\051', '\040', '\143', '\141', '\156', '\040', '\142', '\145', '\040', '\165', '\156', '\144', '\157', '\156', +'\145', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\103', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', +'\143', '\145', '\156', '\164', '\162', '\141', '\154', '\151', '\163', '\145', '\040', '\143', '\165', '\162', '\163', '\157', +'\162', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\110', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\162', '\157', '\167', '\040', '\150', '\151', '\154', '\151', '\147', '\150', +'\164', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\126', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\144', '\145', +'\146', '\141', '\165', '\154', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\144', '\151', '\163', +'\160', '\154', '\141', '\171', '\012', '\174', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\104', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', +'\154', '\145', '\040', '\104', '\151', '\147', '\151', '\124', '\162', '\141', '\153', '\153', '\145', '\162', '\040', '\166', +'\157', '\157', '\144', '\157', '\157', '\040', '\050', '\043', '\040', '\055', '\076', '\040', '\142', '\051', '\012', '\072', +'\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\062', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\164', '\040', '\160', '\141', '\164', '\164', '\145', '\162', +'\156', '\040', '\154', '\145', '\156', '\147', '\164', '\150', '\012', '\174', '\012', '\174', '\040', '\040', '\124', '\162', +'\141', '\143', '\153', '\040', '\126', '\151', '\145', '\167', '\040', '\106', '\165', '\156', '\143', '\164', '\151', '\157', +'\156', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\124', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\171', '\143', '\154', '\145', '\040', '\143', +'\165', '\162', '\162', '\145', '\156', '\164', '\040', '\164', '\162', '\141', '\143', '\153', '\047', '\163', '\040', '\166', +'\151', '\145', '\167', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\122', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\154', '\145', '\141', '\162', '\040', '\141', +'\154', '\154', '\040', '\164', '\162', '\141', '\143', '\153', '\040', '\166', '\151', '\145', '\167', '\163', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\110', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\164', '\162', '\141', '\143', '\153', +'\040', '\166', '\151', '\145', '\167', '\040', '\144', '\151', '\166', '\151', '\163', '\151', '\157', '\156', '\163', '\012', +'\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\060', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\104', '\145', '\163', '\145', '\154', '\145', '\143', '\164', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\164', '\162', '\141', '\143', '\153', '\012', '\174', '\040', '\040', '\040', +'\103', '\164', '\162', '\154', '\055', '\061', '\040', '\055', '\040', '\103', '\164', '\162', '\154', '\055', '\065', '\040', +'\040', '\126', '\151', '\145', '\167', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\164', '\162', +'\141', '\143', '\153', '\040', '\151', '\156', '\040', '\163', '\143', '\150', '\145', '\155', '\145', '\040', '\061', '\055', +'\065', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\114', '\145', '\146', '\164', '\057', +'\122', '\151', '\147', '\150', '\164', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\154', '\145', '\146', '\164', +'\057', '\162', '\151', '\147', '\150', '\164', '\040', '\142', '\145', '\164', '\167', '\145', '\145', '\156', '\040', '\164', +'\162', '\141', '\143', '\153', '\040', '\166', '\151', '\145', '\167', '\040', '\143', '\157', '\154', '\165', '\155', '\156', +'\163', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\114', '\055', '\103', '\164', '\162', '\154', '\046', '\123', +'\150', '\151', '\146', '\164', '\040', '\061', '\055', '\064', '\040', '\121', '\165', '\151', '\143', '\153', '\040', '\166', +'\151', '\145', '\167', '\040', '\163', '\143', '\150', '\145', '\155', '\145', '\040', '\163', '\145', '\164', '\165', '\160', +'\012', '\174', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\124', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\126', +'\151', '\145', '\167', '\055', '\103', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\143', '\165', '\162', '\163', +'\157', '\162', '\055', '\164', '\162', '\141', '\143', '\153', '\151', '\156', '\147', '\012', '\174', '\012', '\174', '\040', +'\040', '\102', '\154', '\157', '\143', '\153', '\040', '\106', '\165', '\156', '\143', '\164', '\151', '\157', '\156', '\163', +'\056', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\102', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\141', '\162', '\153', '\040', '\142', '\145', '\147', '\151', +'\156', '\156', '\151', '\156', '\147', '\040', '\157', '\146', '\040', '\142', '\154', '\157', '\143', '\153', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\105', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\115', '\141', '\162', '\153', '\040', '\145', '\156', '\144', '\040', '\157', '\146', '\040', +'\142', '\154', '\157', '\143', '\153', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\104', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\121', '\165', '\151', '\143', '\153', +'\040', '\155', '\141', '\162', '\153', '\040', '\156', '\057', '\062', '\156', '\057', '\064', '\156', '\057', '\056', '\056', +'\056', '\040', '\154', '\151', '\156', '\145', '\163', '\040', '\050', '\156', '\075', '\122', '\157', '\167', '\040', '\110', +'\151', '\154', '\151', '\147', '\150', '\164', '\040', '\115', '\141', '\152', '\157', '\162', '\051', '\012', '\174', '\040', +'\040', '\040', '\101', '\154', '\164', '\055', '\114', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\115', '\141', '\162', '\153', '\040', '\145', '\156', '\164', '\151', '\162', '\145', '\040', '\143', +'\157', '\154', '\165', '\155', '\156', '\057', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', +'\040', '\040', '\123', '\150', '\151', '\146', '\164', '\055', '\101', '\162', '\162', '\157', '\167', '\163', '\040', '\040', +'\040', '\040', '\040', '\115', '\141', '\162', '\153', '\040', '\142', '\154', '\157', '\143', '\153', '\012', '\174', '\012', +'\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\125', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\125', '\156', '\155', '\141', '\162', '\153', '\040', '\142', '\154', '\157', '\143', +'\153', '\057', '\122', '\145', '\154', '\145', '\141', '\163', '\145', '\040', '\143', '\154', '\151', '\160', '\142', '\157', +'\141', '\162', '\144', '\040', '\155', '\145', '\155', '\157', '\162', '\171', '\012', '\174', '\012', '\174', '\040', '\040', +'\040', '\101', '\154', '\164', '\055', '\121', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\122', '\141', '\151', '\163', '\145', '\040', '\156', '\157', '\164', '\145', '\163', '\040', '\142', '\171', +'\040', '\141', '\040', '\163', '\145', '\155', '\151', '\164', '\157', '\156', '\145', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\101', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\114', '\157', '\167', '\145', '\162', '\040', '\156', '\157', '\164', '\145', +'\163', '\040', '\142', '\171', '\040', '\141', '\040', '\163', '\145', '\155', '\151', '\164', '\157', '\156', '\145', '\040', +'\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\123', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\164', '\040', '\111', '\156', +'\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', +'\040', '\040', '\101', '\154', '\164', '\055', '\126', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\123', '\145', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\057', '\160', '\141', +'\156', '\156', '\151', '\156', '\147', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', +'\154', '\164', '\055', '\127', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\127', '\151', '\160', '\145', '\040', '\166', '\157', '\154', '\057', '\160', '\141', '\156', '\040', '\156', '\157', '\164', +'\040', '\141', '\163', '\163', '\157', '\143', '\151', '\141', '\164', '\145', '\144', '\040', '\167', '\151', '\164', '\150', +'\040', '\141', '\040', '\156', '\157', '\164', '\145', '\057', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', +'\156', '\164', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\113', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\154', '\151', +'\144', '\145', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\057', '\160', '\141', '\156', '\156', '\151', '\156', +'\147', '\040', '\143', '\157', '\154', '\165', '\155', '\156', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', +'\062', '\052', '\101', '\154', '\164', '\055', '\113', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\127', '\151', '\160', '\145', '\040', '\141', '\154', '\154', '\040', '\166', '\157', '\154', '\165', +'\155', '\145', '\057', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\143', '\157', '\156', '\164', '\162', +'\157', '\154', '\163', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', +'\055', '\112', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\126', '\157', +'\154', '\165', '\155', '\145', '\040', '\141', '\155', '\160', '\154', '\151', '\146', '\151', '\145', '\162', '\040', '\040', +'\050', '\052', '\051', '\040', '\057', '\040', '\106', '\141', '\163', '\164', '\040', '\166', '\157', '\154', '\165', '\155', +'\145', '\040', '\141', '\164', '\164', '\145', '\156', '\165', '\141', '\164', '\145', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\132', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\103', '\165', '\164', '\040', '\142', '\154', '\157', '\143', '\153', '\040', +'\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\130', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\154', '\151', '\144', '\145', '\040', +'\145', '\146', '\146', '\145', '\143', '\164', '\040', '\166', '\141', '\154', '\165', '\145', '\040', '\040', '\050', '\052', +'\051', '\012', '\174', '\040', '\062', '\052', '\101', '\154', '\164', '\055', '\130', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\127', '\151', '\160', '\145', '\040', '\141', '\154', '\154', '\040', +'\145', '\146', '\146', '\145', '\143', '\164', '\040', '\144', '\141', '\164', '\141', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\103', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\157', '\160', '\171', '\040', '\142', '\154', '\157', +'\143', '\153', '\040', '\151', '\156', '\164', '\157', '\040', '\143', '\154', '\151', '\160', '\142', '\157', '\141', '\162', +'\144', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\120', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\141', '\163', '\164', '\145', '\040', '\144', '\141', '\164', +'\141', '\040', '\146', '\162', '\157', '\155', '\040', '\143', '\154', '\151', '\160', '\142', '\157', '\141', '\162', '\144', +'\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\117', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\117', '\166', '\145', '\162', '\167', +'\162', '\151', '\164', '\145', '\040', '\167', '\151', '\164', '\150', '\040', '\144', '\141', '\164', '\141', '\040', '\146', +'\162', '\157', '\155', '\040', '\143', '\154', '\151', '\160', '\142', '\157', '\141', '\162', '\144', '\040', '\040', '\040', +'\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\115', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\151', '\170', '\040', '\145', '\141', '\143', +'\150', '\040', '\162', '\157', '\167', '\040', '\146', '\162', '\157', '\155', '\040', '\143', '\154', '\151', '\160', '\142', +'\157', '\141', '\162', '\144', '\040', '\167', '\151', '\164', '\150', '\040', '\160', '\141', '\164', '\164', '\145', '\162', +'\156', '\040', '\144', '\141', '\164', '\141', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\040', '\062', '\052', +'\101', '\154', '\164', '\055', '\115', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\115', '\151', '\170', '\040', '\145', '\141', '\143', '\150', '\040', '\146', '\151', '\145', '\154', '\144', '\040', +'\146', '\162', '\157', '\155', '\040', '\143', '\154', '\151', '\160', '\142', '\157', '\141', '\162', '\144', '\040', '\167', +'\151', '\164', '\150', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', +'\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\106', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\157', '\165', '\142', '\154', '\145', '\040', '\142', +'\154', '\157', '\143', '\153', '\040', '\154', '\145', '\156', '\147', '\164', '\150', '\040', '\040', '\050', '\052', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\107', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\110', '\141', '\154', '\166', '\145', '\040', '\142', '\154', '\157', '\143', +'\153', '\040', '\154', '\145', '\156', '\147', '\164', '\150', '\040', '\040', '\050', '\052', '\051', '\012', '\174', '\012', +'\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\123', '\145', '\154', '\145', '\143', '\164', '\040', '\124', '\145', '\155', '\160', +'\154', '\141', '\164', '\145', '\040', '\155', '\157', '\144', '\145', '\040', '\057', '\040', '\106', '\141', '\163', '\164', +'\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\141', '\155', '\160', '\154', '\151', '\146', '\171', '\040', +'\040', '\050', '\052', '\051', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\112', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', +'\040', '\146', '\141', '\163', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\155', '\157', '\144', +'\145', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\125', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\154', '\145', '\143', '\164', '\151', '\157', '\156', +'\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\166', '\141', '\162', '\171', '\040', '\057', '\040', '\106', +'\141', '\163', '\164', '\040', '\166', '\157', '\154', '\165', '\155', '\145', '\040', '\166', '\141', '\162', '\171', '\040', +'\050', '\052', '\051', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\131', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\154', '\145', '\143', '\164', '\151', +'\157', '\156', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\166', '\141', '\162', '\171', '\040', +'\057', '\040', '\106', '\141', '\163', '\164', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\166', +'\141', '\162', '\171', '\040', '\050', '\052', '\051', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', +'\055', '\113', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\154', +'\145', '\143', '\164', '\151', '\157', '\156', '\040', '\145', '\146', '\146', '\145', '\143', '\164', '\040', '\166', '\141', +'\162', '\171', '\040', '\057', '\040', '\106', '\141', '\163', '\164', '\040', '\145', '\146', '\146', '\145', '\143', '\164', +'\040', '\166', '\141', '\162', '\171', '\040', '\050', '\052', '\051', '\012', '\174', '\012', '\174', '\040', '\120', '\154', +'\141', '\171', '\142', '\141', '\143', '\153', '\040', '\106', '\165', '\156', '\143', '\164', '\151', '\157', '\156', '\163', +'\056', '\012', '\174', '\040', '\040', '\040', '\064', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\156', '\157', '\164', '\145', +'\040', '\165', '\156', '\144', '\145', '\162', '\040', '\143', '\165', '\162', '\163', '\157', '\162', '\012', '\174', '\040', +'\040', '\040', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\162', '\157', '\167', '\012', '\174', '\012', '\174', '\040', +'\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\066', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\146', '\162', '\157', '\155', '\040', '\143', '\165', '\162', +'\162', '\145', '\156', '\164', '\040', '\162', '\157', '\167', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', +'\154', '\055', '\106', '\067', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', +'\164', '\057', '\103', '\154', '\145', '\141', '\162', '\040', '\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', +'\040', '\155', '\141', '\162', '\153', '\040', '\050', '\146', '\157', '\162', '\040', '\165', '\163', '\145', '\040', '\167', +'\151', '\164', '\150', '\040', '\106', '\067', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\106', '\071', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', +'\157', '\147', '\147', '\154', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\106', '\061', +'\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\154', '\157', '\040', +'\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', +'\174', '\012', '\174', '\040', '\040', '\040', '\123', '\143', '\162', '\157', '\154', '\154', '\040', '\114', '\157', '\143', +'\153', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\160', '\154', +'\141', '\171', '\142', '\141', '\143', '\153', '\040', '\164', '\162', '\141', '\143', '\151', '\156', '\147', '\012', '\174', +'\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\132', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\115', '\111', '\104', '\111', '\040', +'\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', '\040', '\164', '\162', '\151', '\147', '\147', '\145', '\162', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\123', '\143', '\162', '\157', '\154', '\154', '\040', +'\114', '\157', '\143', '\153', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\115', '\111', '\104', +'\111', '\040', '\151', '\156', '\160', '\165', '\164', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\040', '\123', '\141', '\155', +'\160', '\154', '\145', '\040', '\114', '\151', '\163', '\164', '\040', '\040', '\040', '\221', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', '\174', +'\012', '\174', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\114', '\151', '\163', '\164', '\040', '\113', +'\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\114', '\157', '\141', '\144', '\040', '\156', '\145', '\167', '\040', '\163', +'\141', '\155', '\160', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\124', '\141', '\142', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\142', '\145', '\164', '\167', +'\145', '\145', '\156', '\040', '\157', '\160', '\164', '\151', '\157', '\156', '\163', '\012', '\174', '\040', '\040', '\040', +'\120', '\147', '\125', '\160', '\057', '\120', '\147', '\104', '\156', '\040', '\040', '\040', '\040', '\115', '\157', '\166', +'\145', '\040', '\165', '\160', '\057', '\144', '\157', '\167', '\156', '\040', '\050', '\167', '\150', '\145', '\156', '\040', +'\156', '\157', '\164', '\040', '\157', '\156', '\040', '\154', '\151', '\163', '\164', '\051', '\012', '\174', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\101', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\103', '\157', '\156', '\166', '\145', '\162', '\164', '\040', '\123', '\151', '\147', '\156', '\145', '\144', '\040', '\164', +'\157', '\057', '\146', '\162', '\157', '\155', '\040', '\125', '\156', '\163', '\151', '\147', '\156', '\145', '\144', '\040', +'\163', '\141', '\155', '\160', '\154', '\145', '\163', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\162', '\145', '\055', '\114', '\157', '\157', +'\160', '\040', '\143', '\165', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\012', '\174', '\040', '\040', +'\040', '\101', '\154', '\164', '\055', '\103', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\154', +'\145', '\141', '\162', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\116', '\141', '\155', '\145', '\040', +'\046', '\040', '\106', '\151', '\154', '\145', '\156', '\141', '\155', '\145', '\040', '\050', '\125', '\163', '\145', '\144', +'\040', '\151', '\156', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\116', '\141', '\155', '\145', '\040', +'\167', '\151', '\156', '\144', '\157', '\167', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\104', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', +'\123', '\141', '\155', '\160', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\105', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\163', '\151', '\172', '\145', '\040', '\123', +'\141', '\155', '\160', '\154', '\145', '\040', '\050', '\167', '\151', '\164', '\150', '\040', '\151', '\156', '\164', '\145', +'\162', '\160', '\157', '\154', '\141', '\164', '\151', '\157', '\156', '\051', '\012', '\174', '\040', '\040', '\040', '\101', +'\154', '\164', '\055', '\106', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\163', '\151', +'\172', '\145', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\050', '\167', '\151', '\164', '\150', '\157', +'\165', '\164', '\040', '\151', '\156', '\164', '\145', '\162', '\160', '\157', '\154', '\141', '\164', '\151', '\157', '\156', +'\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\107', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\122', '\145', '\166', '\145', '\162', '\163', '\145', '\040', '\123', '\141', '\155', '\160', '\154', +'\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\110', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\103', '\145', '\156', '\164', '\162', '\141', '\154', '\151', '\163', '\145', '\040', '\123', '\141', +'\155', '\160', '\154', '\145', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\166', '\145', '\162', '\164', '\040', '\123', '\141', '\155', +'\160', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\114', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\120', '\157', '\163', '\164', '\055', '\114', '\157', '\157', '\160', '\040', '\143', +'\165', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\115', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\155', '\160', '\154', +'\145', '\040', '\141', '\155', '\160', '\154', '\151', '\146', '\151', '\145', '\162', '\012', '\174', '\040', '\040', '\040', +'\101', '\154', '\164', '\055', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', +'\147', '\154', '\145', '\040', '\115', '\165', '\154', '\164', '\151', '\143', '\150', '\141', '\156', '\156', '\145', '\154', +'\040', '\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\117', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', +'\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\164', +'\157', '\040', '\144', '\151', '\163', '\153', '\040', '\050', '\111', '\124', '\040', '\106', '\157', '\162', '\155', '\141', +'\164', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\121', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\040', '\161', '\165', '\141', '\154', '\151', '\164', '\171', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\122', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\160', '\154', '\141', +'\143', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\040', '\151', '\156', '\040', '\163', '\157', '\156', '\147', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\167', '\141', '\160', '\040', +'\163', '\141', '\155', '\160', '\154', '\145', '\040', '\050', '\151', '\156', '\040', '\163', '\157', '\156', '\147', '\040', +'\141', '\154', '\163', '\157', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\124', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\143', '\165', '\162', '\162', +'\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\164', '\157', '\040', '\144', '\151', +'\163', '\153', '\040', '\050', '\105', '\170', '\160', '\157', '\162', '\164', '\040', '\106', '\157', '\162', '\155', '\141', +'\164', '\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\127', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', +'\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\164', '\157', '\040', '\144', '\151', '\163', '\153', '\040', +'\050', '\122', '\101', '\127', '\040', '\106', '\157', '\162', '\155', '\141', '\164', '\051', '\012', '\174', '\040', '\040', +'\040', '\101', '\154', '\164', '\055', '\130', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', '\170', +'\143', '\150', '\141', '\156', '\147', '\145', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\050', '\157', +'\156', '\154', '\171', '\040', '\151', '\156', '\040', '\123', '\141', '\155', '\160', '\154', '\145', '\040', '\114', '\151', +'\163', '\164', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\156', +'\163', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\040', '\163', '\141', +'\155', '\160', '\154', '\145', '\040', '\163', '\154', '\157', '\164', '\040', '\050', '\165', '\160', '\144', '\141', '\164', +'\145', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\104', '\145', '\154', '\040', '\040', '\040', '\040', +'\040', '\040', '\122', '\145', '\155', '\157', '\166', '\145', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', +'\163', '\154', '\157', '\164', '\040', '\050', '\165', '\160', '\144', '\141', '\164', '\145', '\163', '\040', '\160', '\141', +'\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\051', '\012', '\174', '\012', '\174', '\040', +'\040', '\040', '\074', '\040', '\076', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', +'\145', '\143', '\162', '\145', '\141', '\163', '\145', '\057', '\111', '\156', '\143', '\162', '\145', '\141', '\163', '\145', +'\040', '\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', '\040', '\143', '\150', '\141', '\156', '\156', '\145', +'\154', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\107', '\162', '\145', '\171', +'\040', '\053', '\040', '\040', '\040', '\111', '\156', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\103', '\055', +'\065', '\040', '\106', '\162', '\145', '\161', '\165', '\145', '\156', '\143', '\171', '\040', '\142', '\171', '\040', '\061', +'\040', '\157', '\143', '\164', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\107', '\162', '\145', '\171', '\040', '\055', '\040', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', +'\145', '\040', '\103', '\055', '\065', '\040', '\106', '\162', '\145', '\161', '\165', '\145', '\156', '\143', '\171', '\040', +'\142', '\171', '\040', '\061', '\040', '\157', '\143', '\164', '\141', '\166', '\145', '\012', '\174', '\040', '\040', '\040', +'\103', '\164', '\162', '\154', '\055', '\107', '\162', '\145', '\171', '\040', '\053', '\040', '\040', '\111', '\156', '\143', +'\162', '\145', '\141', '\163', '\145', '\040', '\103', '\055', '\065', '\040', '\106', '\162', '\145', '\161', '\165', '\145', +'\156', '\143', '\171', '\040', '\142', '\171', '\040', '\061', '\040', '\163', '\145', '\155', '\151', '\164', '\157', '\156', +'\145', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\107', '\162', '\145', '\171', '\040', +'\055', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\103', '\055', '\065', '\040', +'\106', '\162', '\145', '\161', '\165', '\145', '\156', '\143', '\171', '\040', '\142', '\171', '\040', '\061', '\040', '\163', +'\145', '\155', '\151', '\164', '\157', '\156', '\145', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\040', '\111', '\156', '\163', +'\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\114', '\151', '\163', '\164', '\040', '\040', '\040', '\221', +'\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\226', '\012', '\174', '\012', '\174', '\040', '\111', '\156', '\163', '\164', '\162', '\165', '\155', +'\145', '\156', '\164', '\040', '\114', '\151', '\163', '\164', '\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', +'\040', '\040', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\114', '\157', '\141', '\144', '\040', '\156', '\145', '\167', '\040', '\151', '\156', '\163', '\164', +'\162', '\165', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\120', '\147', '\125', '\160', '\057', '\120', '\147', '\104', '\156', '\040', '\040', '\115', '\157', '\166', '\145', '\040', +'\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\165', '\160', '\057', '\144', '\157', +'\167', '\156', '\040', '\050', '\167', '\150', '\145', '\156', '\040', '\156', '\157', '\164', '\040', '\157', '\156', '\040', +'\154', '\151', '\163', '\164', '\051', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\102', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\162', '\145', '\055', '\114', '\157', +'\157', '\160', '\040', '\143', '\165', '\164', '\040', '\145', '\156', '\166', '\145', '\154', '\157', '\160', '\145', '\012', +'\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\103', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\103', '\154', '\145', '\141', '\162', '\040', '\151', '\156', '\163', '\164', '\162', '\165', +'\155', '\145', '\156', '\164', '\040', '\156', '\141', '\155', '\145', '\040', '\046', '\040', '\146', '\151', '\154', '\145', +'\156', '\141', '\155', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\127', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\127', '\151', '\160', '\145', '\040', '\151', '\156', +'\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\144', '\141', '\164', '\141', '\012', '\174', '\040', +'\040', '\040', '\123', '\160', '\141', '\143', '\145', '\142', '\141', '\162', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\105', '\144', '\151', '\164', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', +'\164', '\040', '\156', '\141', '\155', '\145', '\040', '\050', '\105', '\123', '\103', '\040', '\164', '\157', '\040', '\145', +'\170', '\151', '\164', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\104', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', +'\145', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\046', '\040', '\141', +'\154', '\154', '\040', '\162', '\145', '\154', '\141', '\164', '\145', '\144', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\163', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\114', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\157', '\163', '\164', '\055', '\114', '\157', '\157', '\160', +'\040', '\143', '\165', '\164', '\040', '\145', '\156', '\166', '\145', '\154', '\157', '\160', '\145', '\012', '\174', '\040', +'\040', '\040', '\101', '\154', '\164', '\055', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\115', '\165', '\154', '\164', '\151', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\040', '\160', '\154', '\141', '\171', '\142', '\141', '\143', '\153', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\117', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', +'\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', '\164', '\040', '\164', '\157', '\040', '\144', '\151', +'\163', '\153', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\120', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\157', '\160', '\171', '\040', '\151', '\156', '\163', '\164', +'\162', '\165', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\122', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\160', '\154', '\141', +'\143', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\151', '\156', '\163', '\164', '\162', +'\165', '\155', '\145', '\156', '\164', '\040', '\151', '\156', '\040', '\163', '\157', '\156', '\147', '\012', '\174', '\040', +'\040', '\040', '\101', '\154', '\164', '\055', '\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\123', '\167', '\141', '\160', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', +'\164', '\163', '\040', '\050', '\151', '\156', '\040', '\163', '\157', '\156', '\147', '\040', '\141', '\154', '\163', '\157', +'\051', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\125', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\125', '\160', '\144', '\141', '\164', '\145', '\040', '\160', '\141', '\164', +'\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\012', '\174', '\040', '\040', '\040', '\101', '\154', +'\164', '\055', '\130', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', '\170', +'\143', '\150', '\141', '\156', '\147', '\145', '\040', '\151', '\156', '\163', '\164', '\162', '\165', '\155', '\145', '\156', +'\164', '\163', '\040', '\050', '\157', '\156', '\154', '\171', '\040', '\151', '\156', '\040', '\111', '\156', '\163', '\164', +'\162', '\165', '\155', '\145', '\156', '\164', '\040', '\114', '\151', '\163', '\164', '\051', '\012', '\174', '\012', '\174', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\156', '\163', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\040', '\151', '\156', '\163', '\164', '\162', '\165', +'\155', '\145', '\156', '\164', '\040', '\163', '\154', '\157', '\164', '\040', '\050', '\165', '\160', '\144', '\141', '\164', +'\145', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', '\141', '\051', +'\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\104', '\145', '\154', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\122', '\145', '\155', '\157', '\166', '\145', '\040', '\151', '\156', '\163', '\164', +'\162', '\165', '\155', '\145', '\156', '\164', '\040', '\163', '\154', '\157', '\164', '\040', '\050', '\165', '\160', '\144', +'\141', '\164', '\145', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\144', '\141', '\164', +'\141', '\051', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\074', '\040', '\076', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', +'\145', '\057', '\111', '\156', '\143', '\162', '\145', '\141', '\163', '\145', '\040', '\160', '\154', '\141', '\171', '\142', +'\141', '\143', '\153', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\012', '\174', '\040', +'\116', '\157', '\164', '\145', '\040', '\124', '\162', '\141', '\156', '\163', '\154', '\141', '\164', '\151', '\157', '\156', +'\056', '\012', '\174', '\040', '\040', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\120', '\151', '\143', '\153', '\165', '\160', '\040', '\163', '\141', '\155', +'\160', '\154', '\145', '\040', '\156', '\165', '\155', '\142', '\145', '\162', '\040', '\046', '\040', '\144', '\145', '\146', +'\141', '\165', '\154', '\164', '\040', '\160', '\154', '\141', '\171', '\040', '\156', '\157', '\164', '\145', '\012', '\174', +'\040', '\040', '\040', '\074', '\040', '\076', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\104', '\145', '\143', '\162', '\145', '\141', '\163', '\145', '\057', '\111', '\156', '\143', '\162', +'\145', '\141', '\163', '\145', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\040', '\156', '\165', '\155', '\142', +'\145', '\162', '\012', '\174', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\101', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', +'\141', '\154', '\154', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\163', '\012', '\174', '\040', '\040', '\040', +'\101', '\154', '\164', '\055', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\105', '\156', '\164', '\145', '\162', '\040', '\156', '\145', '\170', '\164', '\040', '\156', '\157', '\164', '\145', '\012', +'\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\120', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\160', '\162', '\145', '\166', '\151', '\157', +'\165', '\163', '\040', '\156', '\157', '\164', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\125', '\160', '\057', '\104', '\157', '\167', '\156', '\040', '\040', '\040', '\040', '\040', '\124', '\162', '\141', '\156', +'\163', '\160', '\157', '\163', '\145', '\040', '\141', '\154', '\154', '\040', '\156', '\157', '\164', '\145', '\163', '\040', +'\141', '\040', '\163', '\145', '\155', '\151', '\164', '\157', '\156', '\145', '\040', '\165', '\160', '\057', '\144', '\157', +'\167', '\156', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\111', '\156', '\163', '\057', '\104', +'\145', '\154', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\057', '\104', '\145', +'\154', '\145', '\164', '\145', '\040', '\141', '\040', '\162', '\157', '\167', '\040', '\146', '\162', '\157', '\155', '\040', +'\164', '\150', '\145', '\040', '\164', '\141', '\142', '\154', '\145', '\012', '\174', '\012', '\174', '\040', '\105', '\156', +'\166', '\145', '\154', '\157', '\160', '\145', '\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', +'\040', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\120', '\151', '\143', '\153', '\040', '\165', '\160', '\057', '\104', '\162', '\157', '\160', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\156', '\157', '\144', '\145', '\012', '\174', '\040', '\040', '\040', '\111', +'\156', '\163', '\145', '\162', '\164', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', +'\144', '\144', '\040', '\156', '\157', '\144', '\145', '\012', '\174', '\040', '\040', '\040', '\104', '\145', '\154', '\145', +'\164', '\145', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', +'\164', '\145', '\040', '\156', '\157', '\144', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', +'\101', '\162', '\162', '\157', '\167', '\040', '\113', '\145', '\171', '\163', '\040', '\040', '\115', '\157', '\166', '\145', +'\040', '\156', '\157', '\144', '\145', '\040', '\050', '\146', '\141', '\163', '\164', '\051', '\012', '\174', '\012', '\174', +'\040', '\040', '\040', '\120', '\162', '\145', '\163', '\163', '\040', '\123', '\160', '\141', '\143', '\145', '\142', '\141', +'\162', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\144', '\145', '\146', '\141', '\165', '\154', '\164', '\040', +'\156', '\157', '\164', '\145', '\012', '\174', '\040', '\040', '\040', '\122', '\145', '\154', '\145', '\141', '\163', '\145', +'\040', '\123', '\160', '\141', '\143', '\145', '\040', '\040', '\040', '\116', '\157', '\164', '\145', '\040', '\157', '\146', +'\146', '\040', '\143', '\157', '\155', '\155', '\141', '\156', '\144', '\012', '\000', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\111', '\156', +'\146', '\157', '\040', '\120', '\141', '\147', '\145', '\040', '\040', '\221', '\012', '\174', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', '\174', '\012', '\174', '\040', '\111', +'\156', '\163', '\145', '\162', '\164', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\101', '\144', '\144', '\040', '\141', '\040', '\156', '\145', '\167', '\040', '\167', '\151', '\156', '\144', '\157', +'\167', '\012', '\174', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\143', '\165', '\162', +'\162', '\145', '\156', '\164', '\040', '\167', '\151', '\156', '\144', '\157', '\167', '\012', '\174', '\040', '\124', '\141', +'\142', '\057', '\123', '\150', '\151', '\146', '\164', '\055', '\124', '\141', '\142', '\040', '\040', '\040', '\040', '\040', +'\115', '\157', '\166', '\145', '\040', '\142', '\145', '\164', '\167', '\145', '\145', '\156', '\040', '\167', '\151', '\156', +'\144', '\157', '\167', '\163', '\012', '\174', '\040', '\125', '\160', '\057', '\104', '\156', '\057', '\114', '\145', '\146', +'\164', '\057', '\122', '\151', '\147', '\150', '\164', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\150', '\151', +'\147', '\150', '\154', '\151', '\147', '\150', '\164', '\145', '\144', '\040', '\143', '\150', '\141', '\156', '\156', '\145', +'\154', '\012', '\174', '\040', '\120', '\147', '\125', '\160', '\057', '\120', '\147', '\104', '\156', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\167', '\151', '\156', +'\144', '\157', '\167', '\040', '\164', '\171', '\160', '\145', '\012', '\174', '\040', '\101', '\154', '\164', '\055', '\125', +'\160', '\057', '\104', '\157', '\167', '\156', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', +'\145', '\040', '\167', '\151', '\156', '\144', '\157', '\167', '\040', '\142', '\141', '\163', '\145', '\040', '\165', '\160', +'\057', '\144', '\157', '\167', '\156', '\012', '\174', '\012', '\174', '\040', '\126', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', +'\154', '\145', '\040', '\142', '\145', '\164', '\167', '\145', '\145', '\156', '\040', '\166', '\157', '\154', '\165', '\155', +'\145', '\057', '\166', '\145', '\154', '\157', '\143', '\151', '\164', '\171', '\040', '\142', '\141', '\162', '\163', '\012', +'\174', '\040', '\111', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\124', '\157', '\147', '\147', '\154', '\145', '\040', '\142', '\145', '\164', '\167', '\145', +'\145', '\156', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\057', '\151', '\156', '\163', '\164', '\162', '\165', +'\155', '\145', '\156', '\164', '\040', '\156', '\141', '\155', '\145', '\163', '\012', '\174', '\012', '\174', '\040', '\121', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\115', '\165', '\164', '\145', '\057', '\125', '\156', '\155', '\165', '\164', '\145', '\040', '\143', '\165', '\162', +'\162', '\145', '\156', '\164', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\123', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\123', '\157', '\154', '\157', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\012', '\174', '\012', '\174', '\040', '\107', '\162', '\145', '\171', '\040', '\053', +'\054', '\040', '\107', '\162', '\145', '\171', '\040', '\055', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', +'\040', '\146', '\157', '\162', '\167', '\141', '\162', '\144', '\163', '\057', '\142', '\141', '\143', '\153', '\167', '\141', +'\162', '\144', '\163', '\040', '\157', '\156', '\145', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', +'\151', '\156', '\040', '\163', '\157', '\156', '\147', '\012', '\174', '\012', '\174', '\040', '\101', '\154', '\164', '\055', +'\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\157', +'\147', '\147', '\154', '\145', '\040', '\123', '\164', '\145', '\162', '\145', '\157', '\040', '\160', '\154', '\141', '\171', +'\142', '\141', '\143', '\153', '\012', '\174', '\040', '\101', '\154', '\164', '\055', '\122', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\122', '\145', '\166', '\145', '\162', '\163', '\145', +'\040', '\157', '\165', '\164', '\160', '\165', '\164', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\163', +'\012', '\174', '\012', '\174', '\040', '\107', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\157', '\164', '\157', '\040', '\160', '\141', '\164', '\164', +'\145', '\162', '\156', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\154', '\171', '\040', '\160', '\154', +'\141', '\171', '\151', '\156', '\147', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\115', '\145', '\163', '\163', '\141', +'\147', '\145', '\040', '\105', '\144', '\151', '\164', '\157', '\162', '\040', '\040', '\221', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', +'\174', '\012', '\174', '\040', '\105', '\156', '\164', '\145', '\162', '\040', '\057', '\040', '\105', '\123', '\103', '\040', +'\040', '\040', '\040', '\040', '\105', '\144', '\151', '\164', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', +'\040', '\057', '\040', '\146', '\151', '\156', '\151', '\163', '\150', '\145', '\144', '\040', '\145', '\144', '\151', '\164', +'\151', '\156', '\147', '\012', '\174', '\012', '\174', '\040', '\105', '\144', '\151', '\164', '\151', '\156', '\147', '\040', +'\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\131', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\154', +'\151', '\156', '\145', '\012', '\174', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\103', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\103', '\154', '\145', '\141', '\162', '\040', '\155', '\145', '\163', '\163', +'\141', '\147', '\145', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', '\012', '\174', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\114', +'\151', '\163', '\164', '\040', '\141', '\156', '\144', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', +'\040', '\221', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\226', '\012', '\174', '\012', '\174', '\040', '\117', '\162', +'\144', '\145', '\162', '\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\116', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\111', '\156', '\163', '\145', '\162', '\164', '\040', '\156', '\145', '\170', '\164', '\040', '\160', '\141', '\164', '\164', +'\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', '\156', '\144', '\040', '\157', '\146', +'\040', '\163', '\157', '\156', '\147', '\040', '\155', '\141', '\162', '\153', '\012', '\174', '\040', '\040', '\040', '\053', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\123', '\153', '\151', '\160', '\040', '\164', '\157', '\040', '\156', '\145', '\170', '\164', '\040', '\117', '\162', +'\144', '\145', '\162', '\040', '\155', '\141', '\162', '\153', '\012', '\174', '\040', '\040', '\040', '\111', '\156', '\163', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', +'\156', '\163', '\145', '\162', '\164', '\040', '\141', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', +'\174', '\040', '\040', '\040', '\104', '\145', '\154', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\104', '\145', '\154', '\145', '\164', '\145', '\040', '\141', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\124', '\141', '\142', '\057', '\123', +'\150', '\151', '\146', '\164', '\055', '\124', '\141', '\142', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', +'\145', '\040', '\164', '\157', '\040', '\156', '\145', '\170', '\164', '\040', '\167', '\151', '\156', '\144', '\157', '\167', +'\012', '\174', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\106', '\067', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\154', '\141', '\171', '\040', '\164', '\150', '\151', '\163', +'\040', '\117', '\162', '\144', '\145', '\162', '\040', '\156', '\145', '\170', '\164', '\012', '\072', '\012', '\072', '\040', +'\040', '\040', '\103', '\164', '\162', '\154', '\055', '\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\114', '\151', '\156', '\153', '\040', '\050', '\144', '\151', '\163', '\153', '\167', '\162', +'\151', '\164', '\145', '\162', '\051', '\040', '\164', '\150', '\151', '\163', '\040', '\160', '\141', '\164', '\164', '\145', +'\162', '\156', '\040', '\164', '\157', '\040', '\164', '\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', +'\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', +'\154', '\055', '\117', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', +'\157', '\160', '\171', '\040', '\050', '\144', '\151', '\163', '\153', '\167', '\162', '\151', '\164', '\145', '\162', '\051', +'\040', '\164', '\150', '\151', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\164', '\157', +'\040', '\164', '\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', +'\160', '\154', '\145', '\012', '\072', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\105', '\156', +'\164', '\145', '\162', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', +'\040', '\157', '\162', '\144', '\145', '\162', '\154', '\151', '\163', '\164', '\012', '\072', '\040', '\040', '\040', '\101', +'\154', '\164', '\055', '\102', '\141', '\143', '\153', '\163', '\160', '\141', '\143', '\145', '\040', '\040', '\040', '\040', +'\040', '\123', '\167', '\141', '\160', '\040', '\157', '\162', '\144', '\145', '\162', '\154', '\151', '\163', '\164', '\040', +'\167', '\151', '\164', '\150', '\040', '\163', '\141', '\166', '\145', '\144', '\040', '\157', '\162', '\144', '\145', '\162', +'\154', '\151', '\163', '\164', '\012', '\174', '\012', '\174', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', +'\040', '\113', '\145', '\171', '\163', '\056', '\012', '\174', '\040', '\040', '\040', '\114', '\057', '\115', '\057', '\122', +'\057', '\123', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\145', '\164', +'\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\164', '\157', '\040', '\114', '\145', '\146', '\164', +'\057', '\115', '\151', '\144', '\144', '\154', '\145', '\057', '\122', '\151', '\147', '\150', '\164', '\057', '\123', '\165', +'\162', '\162', '\157', '\165', '\156', '\144', '\012', '\000', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', +'\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\204', '\040', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\114', +'\151', '\163', '\164', '\040', '\141', '\156', '\144', '\040', '\103', '\150', '\141', '\156', '\156', '\145', '\154', '\040', +'\126', '\157', '\154', '\165', '\155', '\145', '\040', '\040', '\221', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\211', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\226', '\012', '\174', '\012', '\174', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\113', '\145', '\171', +'\163', '\056', '\012', '\174', '\040', '\040', '\040', '\116', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\040', +'\156', '\145', '\170', '\164', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', +'\040', '\055', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\105', '\156', '\144', '\040', '\157', '\146', '\040', '\163', '\157', '\156', '\147', '\040', '\155', +'\141', '\162', '\153', '\012', '\174', '\040', '\040', '\040', '\053', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\153', '\151', '\160', '\040', '\164', +'\157', '\040', '\156', '\145', '\170', '\164', '\040', '\117', '\162', '\144', '\145', '\162', '\040', '\155', '\141', '\162', +'\153', '\012', '\174', '\040', '\040', '\040', '\111', '\156', '\163', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\111', '\156', '\163', '\145', '\162', '\164', '\040', '\141', +'\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', '\174', '\040', '\040', '\040', '\104', '\145', '\154', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', +'\145', '\154', '\145', '\164', '\145', '\040', '\141', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\012', +'\174', '\040', '\040', '\040', '\124', '\141', '\142', '\057', '\123', '\150', '\151', '\146', '\164', '\055', '\124', '\141', +'\142', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\166', '\145', '\040', '\164', '\157', '\040', '\156', '\145', +'\170', '\164', '\040', '\167', '\151', '\156', '\144', '\157', '\167', '\012', '\174', '\040', '\040', '\040', '\103', '\164', +'\162', '\154', '\055', '\106', '\067', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\120', '\154', '\141', '\171', '\040', '\164', '\150', '\151', '\163', '\040', '\117', '\162', '\144', '\145', '\162', '\040', +'\156', '\145', '\170', '\164', '\012', '\072', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', +'\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\114', '\151', '\156', +'\153', '\040', '\050', '\144', '\151', '\163', '\153', '\167', '\162', '\151', '\164', '\145', '\162', '\051', '\040', '\164', +'\150', '\151', '\163', '\040', '\160', '\141', '\164', '\164', '\145', '\162', '\156', '\040', '\164', '\157', '\040', '\164', +'\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', +'\145', '\012', '\072', '\040', '\040', '\040', '\103', '\164', '\162', '\154', '\055', '\117', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\157', '\160', '\171', '\040', '\050', '\144', '\151', +'\163', '\153', '\167', '\162', '\151', '\164', '\145', '\162', '\051', '\040', '\164', '\150', '\151', '\163', '\040', '\160', +'\141', '\164', '\164', '\145', '\162', '\156', '\040', '\164', '\157', '\040', '\164', '\150', '\145', '\040', '\143', '\165', +'\162', '\162', '\145', '\156', '\164', '\040', '\163', '\141', '\155', '\160', '\154', '\145', '\012', '\072', '\012', '\072', +'\040', '\040', '\040', '\101', '\154', '\164', '\055', '\105', '\156', '\164', '\145', '\162', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\123', '\141', '\166', '\145', '\040', '\157', '\162', '\144', '\145', '\162', '\154', +'\151', '\163', '\164', '\012', '\072', '\040', '\040', '\040', '\101', '\154', '\164', '\055', '\102', '\141', '\143', '\153', +'\163', '\160', '\141', '\143', '\145', '\040', '\040', '\040', '\040', '\040', '\123', '\167', '\141', '\160', '\040', '\157', +'\162', '\144', '\145', '\162', '\154', '\151', '\163', '\164', '\040', '\167', '\151', '\164', '\150', '\040', '\163', '\141', +'\166', '\145', '\144', '\040', '\157', '\162', '\144', '\145', '\162', '\154', '\151', '\163', '\164', '\012', '\000', '\174', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\213', +'\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\206', '\212', +'\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\204', '\040', '\040', '\115', '\111', '\104', '\111', '\040', '\117', '\165', '\164', '\160', '\165', '\164', '\040', +'\040', '\221', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\211', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', '\217', +'\217', '\217', '\217', '\226', '\012', '\174', '\012', '\174', '\040', '\115', '\111', '\104', '\111', '\040', '\157', '\165', +'\164', '\160', '\165', '\164', '\040', '\143', '\141', '\165', '\163', '\145', '\163', '\040', '\115', '\111', '\104', '\111', +'\040', '\144', '\141', '\164', '\141', '\040', '\164', '\157', '\040', '\142', '\145', '\040', '\163', '\145', '\156', '\164', +'\040', '\164', '\157', '\040', '\141', '\154', '\154', '\040', '\145', '\156', '\141', '\142', '\154', '\145', '\144', '\040', +'\157', '\165', '\164', '\160', '\165', '\164', '\040', '\160', '\157', '\162', '\164', '\163', '\040', '\157', '\156', '\040', +'\164', '\150', '\145', '\012', '\174', '\040', '\115', '\111', '\104', '\111', '\040', '\143', '\157', '\156', '\146', '\151', +'\147', '\165', '\162', '\141', '\164', '\151', '\157', '\156', '\040', '\160', '\141', '\147', '\145', '\040', '\050', '\123', +'\150', '\151', '\146', '\164', '\055', '\106', '\061', '\051', '\040', '\167', '\150', '\145', '\156', '\145', '\166', '\145', +'\162', '\040', '\164', '\150', '\145', '\040', '\160', '\154', '\141', '\171', '\145', '\162', '\040', '\163', '\145', '\145', +'\163', '\040', '\141', '\156', '\040', '\145', '\166', '\145', '\156', '\164', '\056', '\012', '\174', '\012', '\174', '\040', +'\124', '\150', '\145', '\040', '\144', '\141', '\164', '\141', '\040', '\141', '\143', '\164', '\165', '\141', '\154', '\154', +'\171', '\040', '\163', '\145', '\156', '\164', '\040', '\151', '\163', '\040', '\164', '\150', '\145', '\040', '\162', '\145', +'\163', '\165', '\154', '\164', '\040', '\157', '\146', '\040', '\141', '\040', '\155', '\141', '\143', '\162', '\157', '\040', +'\143', '\157', '\156', '\146', '\151', '\147', '\165', '\162', '\141', '\164', '\151', '\157', '\156', '\040', '\157', '\156', +'\040', '\164', '\150', '\145', '\040', '\115', '\111', '\104', '\111', '\012', '\174', '\040', '\117', '\165', '\164', '\160', +'\165', '\164', '\040', '\160', '\141', '\147', '\145', '\056', '\012', '\174', '\012', '\174', '\040', '\105', '\141', '\143', +'\150', '\040', '\145', '\166', '\145', '\156', '\164', '\040', '\151', '\163', '\040', '\144', '\145', '\163', '\143', '\162', +'\151', '\142', '\145', '\144', '\040', '\142', '\145', '\154', '\157', '\167', '\072', '\012', '\174', '\012', '\174', '\040', +'\040', '\040', '\040', '\040', '\115', '\111', '\104', '\111', '\040', '\123', '\164', '\141', '\162', '\164', '\040', '\040', +'\302', '\040', '\120', '\154', '\141', '\171', '\145', '\162', '\040', '\142', '\145', '\147', '\151', '\156', '\163', '\012', +'\174', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\111', '\104', '\111', '\040', '\123', '\164', '\157', '\160', +'\040', '\040', '\302', '\040', '\120', '\154', '\141', '\171', '\145', '\162', '\040', '\163', '\164', '\157', '\160', '\163', +'\040', '\160', '\154', '\141', '\171', '\151', '\156', '\147', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\115', '\111', '\104', '\111', '\040', '\124', '\151', '\143', '\153', '\040', '\040', '\302', '\040', '\105', '\166', '\145', +'\162', '\171', '\040', '\162', '\157', '\167', '\050', '\077', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\116', '\157', '\164', '\145', '\040', '\117', '\156', '\040', '\040', '\302', '\040', '\105', '\166', +'\145', '\162', '\171', '\040', '\156', '\157', '\164', '\145', '\040', '\162', '\145', '\143', '\157', '\162', '\144', '\145', +'\144', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\116', '\157', '\164', '\145', '\040', '\117', +'\146', '\146', '\040', '\040', '\302', '\040', '\105', '\166', '\145', '\162', '\171', '\040', '\156', '\157', '\164', '\145', +'\040', '\157', '\146', '\146', '\040', '\050', '\151', '\156', '\143', '\154', '\165', '\144', '\151', '\156', '\147', '\040', +'\116', '\116', '\101', '\040', '\156', '\157', '\164', '\145', '\055', '\157', '\146', '\146', '\051', '\012', '\174', '\040', +'\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\126', '\157', '\154', '\165', '\155', '\145', '\040', '\040', +'\302', '\040', '\126', '\157', '\154', '\165', '\155', '\145', '\040', '\143', '\150', '\141', '\156', '\147', '\145', '\040', +'\050', '\155', '\157', '\162', '\145', '\040', '\154', '\151', '\153', '\145', '\040', '\141', '\146', '\164', '\145', '\162', +'\164', '\157', '\165', '\143', '\150', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\102', '\141', '\156', '\153', +'\040', '\123', '\145', '\154', '\145', '\143', '\164', '\040', '\040', '\302', '\040', '\102', '\141', '\156', '\153', '\040', +'\143', '\150', '\141', '\156', '\147', '\145', '\012', '\174', '\040', '\120', '\162', '\157', '\147', '\162', '\141', '\155', +'\040', '\103', '\150', '\141', '\156', '\147', '\145', '\040', '\040', '\302', '\040', '\120', '\162', '\157', '\147', '\162', +'\141', '\155', '\040', '\143', '\150', '\141', '\156', '\147', '\145', '\012', '\174', '\040', '\040', '\040', '\040', '\040', +'\040', '\123', '\106', '\060', '\040', '\055', '\040', '\123', '\106', '\106', '\040', '\040', '\302', '\040', '\127', '\150', +'\145', '\156', '\040', '\141', '\040', '\132', '\060', '\060', '\055', '\132', '\067', '\106', '\040', '\151', '\163', '\040', +'\163', '\145', '\145', '\156', '\040', '\151', '\156', '\040', '\164', '\150', '\145', '\040', '\163', '\141', '\155', '\145', +'\040', '\111', '\124', '\055', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', +'\040', '\040', '\040', '\132', '\070', '\060', '\040', '\055', '\040', '\132', '\070', '\106', '\040', '\040', '\302', '\040', +'\127', '\150', '\145', '\156', '\040', '\160', '\154', '\141', '\171', '\145', '\144', '\012', '\174', '\012', '\174', '\040', +'\124', '\150', '\145', '\163', '\145', '\040', '\145', '\166', '\145', '\156', '\164', '\163', '\040', '\141', '\162', '\145', +'\040', '\167', '\162', '\151', '\164', '\164', '\145', '\156', '\040', '\151', '\156', '\040', '\125', '\120', '\120', '\105', +'\122', '\103', '\101', '\123', '\105', '\040', '\150', '\145', '\170', '\141', '\144', '\145', '\143', '\151', '\155', '\141', +'\154', '\054', '\040', '\167', '\151', '\164', '\150', '\040', '\114', '\117', '\127', '\105', '\122', '\103', '\101', '\123', +'\105', '\040', '\166', '\141', '\162', '\151', '\141', '\142', '\154', '\145', '\012', '\174', '\040', '\163', '\165', '\142', +'\163', '\164', '\151', '\164', '\165', '\164', '\151', '\157', '\156', '\056', '\040', '\124', '\150', '\145', '\040', '\166', +'\141', '\162', '\151', '\141', '\142', '\154', '\145', '\163', '\040', '\141', '\162', '\145', '\072', '\012', '\174', '\012', +'\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\141', +'\040', '\040', '\302', '\040', '\103', '\157', '\141', '\162', '\163', '\145', '\040', '\142', '\141', '\156', '\153', '\057', +'\143', '\150', '\141', '\156', '\147', '\145', '\040', '\050', '\157', '\156', '\154', '\171', '\040', '\141', '\166', '\141', +'\151', '\154', '\141', '\142', '\154', '\145', '\040', '\157', '\156', '\040', '\102', '\141', '\156', '\153', '\040', '\123', +'\145', '\154', '\145', '\143', '\164', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\142', '\040', '\040', '\302', '\040', '\106', '\151', '\156', '\145', '\040', +'\142', '\141', '\156', '\153', '\057', '\143', '\150', '\141', '\156', '\147', '\145', '\040', '\050', '\157', '\156', '\154', +'\171', '\040', '\141', '\166', '\141', '\151', '\154', '\141', '\142', '\154', '\145', '\040', '\157', '\156', '\040', '\102', +'\141', '\156', '\153', '\040', '\123', '\145', '\154', '\145', '\143', '\164', '\051', '\012', '\174', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\143', '\040', '\040', '\302', '\040', +'\123', '\145', '\154', '\145', '\143', '\164', '\145', '\144', '\040', '\115', '\111', '\104', '\111', '\040', '\143', '\150', +'\141', '\156', '\156', '\145', '\154', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\156', '\040', '\040', '\302', '\040', '\123', '\145', '\154', '\145', '\143', '\164', +'\145', '\144', '\040', '\115', '\111', '\104', '\111', '\040', '\156', '\157', '\164', '\145', '\040', '\050', '\116', '\157', +'\164', '\145', '\040', '\117', '\156', '\040', '\145', '\166', '\145', '\156', '\164', '\163', '\040', '\157', '\156', '\154', +'\171', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\160', '\040', '\040', '\302', '\040', '\120', '\162', '\157', '\147', '\162', '\141', '\155', '\040', '\163', +'\145', '\164', '\164', '\151', '\156', '\147', '\040', '\050', '\120', '\162', '\157', '\147', '\162', '\141', '\155', '\040', +'\103', '\150', '\141', '\156', '\147', '\145', '\040', '\145', '\166', '\145', '\156', '\164', '\163', '\040', '\157', '\156', +'\154', '\171', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\166', '\040', '\040', '\302', '\040', '\126', '\145', '\154', '\157', '\143', '\151', '\164', '\171', +'\040', '\050', '\151', '\156', '\151', '\164', '\151', '\141', '\154', '\040', '\166', '\157', '\154', '\165', '\155', '\145', +'\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\165', '\040', '\040', '\302', '\040', '\103', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\166', '\157', +'\154', '\165', '\155', '\145', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\170', '\040', '\040', '\302', '\040', '\123', '\145', '\164', '\040', '\120', '\141', '\156', +'\156', '\151', '\156', '\147', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\171', '\040', '\040', '\302', '\040', '\103', '\141', '\154', '\143', '\165', '\154', '\141', +'\164', '\145', '\144', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\050', '\151', '\156', '\143', +'\154', '\165', '\144', '\145', '\163', '\040', '\160', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\145', '\156', +'\166', '\145', '\154', '\157', '\160', '\145', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\172', '\040', '\040', '\302', '\040', '\115', '\111', '\104', '\111', +'\040', '\115', '\141', '\143', '\162', '\157', '\040', '\050', '\132', '\060', '\060', '\055', '\132', '\067', '\106', '\040', +'\143', '\157', '\155', '\155', '\141', '\156', '\144', '\163', '\051', '\012', '\174', '\012', '\174', '\040', '\115', '\111', +'\104', '\111', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\040', '\141', '\162', '\145', '\040', +'\156', '\157', '\162', '\155', '\141', '\154', '\154', '\171', '\040', '\164', '\150', '\162', '\145', '\145', '\055', '\142', +'\171', '\164', '\145', '\163', '\040', '\050', '\145', '\170', '\143', '\145', '\160', '\164', '\040', '\146', '\157', '\162', +'\040', '\123', '\171', '\163', '\164', '\145', '\155', '\040', '\105', '\170', '\143', '\154', '\165', '\163', '\151', '\166', +'\145', '\012', '\174', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\051', '\056', '\040', '\101', +'\040', '\156', '\145', '\167', '\040', '\042', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\042', '\040', '\142', +'\145', '\147', '\151', '\156', '\163', '\040', '\167', '\151', '\164', '\150', '\040', '\141', '\040', '\142', '\171', '\164', +'\145', '\040', '\150', '\141', '\166', '\151', '\156', '\147', '\040', '\151', '\164', '\047', '\163', '\040', '\150', '\151', +'\147', '\150', '\055', '\142', '\151', '\164', '\040', '\163', '\145', '\164', '\056', '\012', '\174', '\040', '\124', '\150', +'\151', '\163', '\040', '\155', '\145', '\141', '\156', '\163', '\040', '\164', '\150', '\141', '\164', '\040', '\164', '\150', +'\145', '\040', '\146', '\151', '\162', '\163', '\164', '\040', '\142', '\171', '\164', '\145', '\040', '\151', '\163', '\040', +'\147', '\157', '\151', '\156', '\147', '\040', '\164', '\157', '\040', '\142', '\145', '\040', '\142', '\145', '\164', '\167', +'\145', '\145', '\156', '\040', '\060', '\170', '\070', '\060', '\040', '\141', '\156', '\144', '\040', '\060', '\170', '\106', +'\106', '\056', '\012', '\174', '\012', '\174', '\040', '\123', '\171', '\163', '\164', '\145', '\155', '\040', '\105', '\170', +'\143', '\154', '\165', '\163', '\151', '\166', '\145', '\040', '\050', '\123', '\171', '\163', '\105', '\170', '\051', '\040', +'\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\040', '\142', '\145', '\147', '\151', '\156', '\040', '\167', +'\151', '\164', '\150', '\040', '\141', '\040', '\060', '\170', '\106', '\060', '\040', '\141', '\156', '\144', '\040', '\145', +'\156', '\144', '\040', '\167', '\151', '\164', '\150', '\040', '\141', '\040', '\060', '\170', '\106', '\067', '\012', '\174', +'\040', '\142', '\171', '\164', '\145', '\056', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\057', '\111', '\155', +'\160', '\165', '\154', '\163', '\145', '\040', '\124', '\162', '\141', '\143', '\153', '\145', '\162', '\040', '\164', '\150', +'\145', '\040', '\146', '\157', '\154', '\154', '\157', '\167', '\151', '\156', '\147', '\040', '\123', '\171', '\163', '\105', +'\170', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\040', '\151', '\156', '\164', '\145', '\162', +'\156', '\141', '\154', '\154', '\171', '\072', '\012', '\174', '\012', '\174', '\040', '\106', '\060', '\040', '\106', '\060', +'\040', '\060', '\060', '\040', '\161', '\161', '\040', '\106', '\067', '\040', '\040', '\302', '\040', '\123', '\145', '\164', +'\040', '\164', '\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\146', '\151', '\154', +'\164', '\145', '\162', '\040', '\143', '\165', '\164', '\157', '\146', '\146', '\040', '\160', '\157', '\151', '\156', '\164', +'\040', '\164', '\157', '\040', '\142', '\145', '\040', '\161', '\161', '\012', '\174', '\040', '\106', '\060', '\040', '\106', +'\060', '\040', '\060', '\061', '\040', '\161', '\161', '\040', '\106', '\067', '\040', '\040', '\302', '\040', '\123', '\145', +'\164', '\040', '\164', '\150', '\145', '\040', '\143', '\165', '\162', '\162', '\145', '\156', '\164', '\040', '\146', '\151', +'\154', '\164', '\145', '\162', '\040', '\162', '\145', '\163', '\157', '\156', '\141', '\156', '\143', '\145', '\040', '\164', +'\157', '\040', '\142', '\145', '\040', '\161', '\161', '\012', '\174', '\012', '\174', '\040', '\131', '\157', '\165', '\047', +'\154', '\154', '\040', '\147', '\145', '\156', '\145', '\162', '\141', '\154', '\154', '\171', '\040', '\156', '\145', '\145', +'\144', '\040', '\164', '\150', '\145', '\040', '\160', '\162', '\157', '\147', '\162', '\141', '\155', '\155', '\151', '\156', +'\147', '\040', '\147', '\165', '\151', '\144', '\145', '\040', '\146', '\157', '\162', '\040', '\171', '\157', '\165', '\162', +'\040', '\115', '\111', '\104', '\111', '\040', '\163', '\171', '\156', '\164', '\150', '\145', '\163', '\151', '\172', '\145', +'\162', '\163', '\040', '\164', '\157', '\012', '\174', '\040', '\143', '\157', '\155', '\145', '\040', '\165', '\160', '\040', +'\167', '\151', '\164', '\150', '\040', '\165', '\163', '\145', '\146', '\165', '\154', '\040', '\166', '\141', '\154', '\165', +'\145', '\163', '\056', '\040', '\131', '\157', '\165', '\040', '\144', '\157', '\040', '\156', '\157', '\164', '\040', '\150', +'\141', '\166', '\145', '\040', '\164', '\157', '\040', '\151', '\156', '\143', '\154', '\165', '\144', '\145', '\040', '\164', +'\150', '\145', '\040', '\106', '\067', '\040', '\141', '\163', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\012', +'\174', '\040', '\124', '\162', '\141', '\143', '\153', '\145', '\162', '\040', '\167', '\151', '\154', '\154', '\040', '\141', +'\165', '\164', '\157', '\155', '\141', '\164', '\151', '\143', '\141', '\154', '\154', '\171', '\040', '\164', '\145', '\162', +'\155', '\151', '\156', '\141', '\164', '\145', '\040', '\123', '\171', '\163', '\105', '\170', '\040', '\155', '\145', '\163', +'\163', '\141', '\147', '\145', '\163', '\056', '\012', '\174', '\012', '\174', '\040', '\117', '\164', '\150', '\145', '\162', +'\040', '\115', '\111', '\104', '\111', '\040', '\155', '\145', '\163', '\163', '\141', '\147', '\145', '\163', '\040', '\151', +'\156', '\143', '\154', '\165', '\144', '\145', '\072', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\070', '\143', '\040', '\156', '\040', '\166', '\040', '\040', '\302', '\040', '\116', '\157', '\164', '\145', +'\055', '\157', '\146', '\146', '\040', '\146', '\157', '\162', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', +'\040', '\042', '\143', '\042', '\054', '\040', '\156', '\157', '\164', '\145', '\040', '\042', '\156', '\042', '\012', '\174', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\071', '\143', '\040', '\156', '\040', '\166', '\040', +'\040', '\302', '\040', '\116', '\157', '\164', '\145', '\055', '\157', '\156', '\040', '\146', '\157', '\162', '\040', '\143', +'\150', '\141', '\156', '\156', '\145', '\154', '\040', '\042', '\143', '\042', '\054', '\040', '\156', '\157', '\164', '\145', +'\040', '\042', '\156', '\042', '\054', '\040', '\166', '\145', '\154', '\157', '\143', '\151', '\164', '\171', '\040', '\042', +'\166', '\042', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', '\143', '\040', +'\156', '\040', '\166', '\040', '\040', '\302', '\040', '\101', '\146', '\164', '\145', '\162', '\164', '\157', '\165', '\143', +'\150', '\040', '\050', '\141', '\144', '\152', '\165', '\163', '\164', '\040', '\166', '\145', '\154', '\157', '\143', '\151', +'\164', '\171', '\051', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\102', '\143', +'\040', '\161', '\040', '\172', '\040', '\040', '\302', '\040', '\123', '\145', '\164', '\040', '\115', '\111', '\104', '\111', +'\040', '\143', '\157', '\156', '\164', '\162', '\157', '\154', '\154', '\145', '\162', '\040', '\042', '\161', '\042', '\040', +'\157', '\156', '\040', '\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\042', '\143', '\042', '\054', '\040', +'\164', '\157', '\040', '\166', '\141', '\154', '\165', '\145', '\040', '\042', '\172', '\042', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\143', '\040', '\160', '\040', '\040', '\040', '\040', '\302', +'\040', '\120', '\162', '\157', '\147', '\162', '\141', '\155', '\040', '\143', '\150', '\141', '\156', '\147', '\145', '\040', +'\143', '\150', '\141', '\156', '\156', '\145', '\154', '\040', '\042', '\143', '\042', '\040', '\164', '\157', '\040', '\042', +'\160', '\042', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\143', '\040', +'\172', '\040', '\040', '\040', '\040', '\302', '\040', '\123', '\145', '\164', '\040', '\164', '\157', '\164', '\141', '\154', +'\040', '\153', '\145', '\171', '\040', '\160', '\162', '\145', '\163', '\163', '\165', '\162', '\145', '\040', '\164', '\157', +'\040', '\042', '\172', '\042', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', +'\143', '\040', '\161', '\040', '\161', '\040', '\040', '\302', '\040', '\120', '\151', '\164', '\143', '\150', '\040', '\127', +'\150', '\145', '\145', '\154', '\073', '\040', '\161', '\040', '\161', '\040', '\151', '\163', '\040', '\141', '\040', '\061', +'\064', '\055', '\142', '\151', '\164', '\040', '\166', '\141', '\154', '\165', '\145', '\056', '\012', '\174', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\106', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\302', +'\040', '\115', '\111', '\104', '\111', '\040', '\042', '\103', '\154', '\157', '\143', '\153', '\042', '\040', '\157', '\160', +'\145', '\162', '\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\106', '\101', '\040', '\040', '\040', '\040', '\040', '\040', '\302', '\040', '\123', '\164', '\141', '\162', +'\164', '\040', '\115', '\111', '\104', '\111', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\106', '\102', '\040', '\040', '\040', '\040', '\040', '\040', '\302', '\040', '\103', '\157', '\156', '\164', '\151', +'\156', '\165', '\145', '\040', '\115', '\111', '\104', '\111', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\106', '\103', '\040', '\040', '\040', '\040', '\040', '\040', '\302', '\040', '\123', '\164', '\157', +'\160', '\040', '\115', '\111', '\104', '\111', '\012', '\174', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\106', '\106', '\040', '\040', '\040', '\040', '\040', '\040', '\302', '\040', '\122', '\145', '\163', '\145', '\164', +'\012', '\174', '\012', '\174', '\040', '\124', '\150', '\145', '\162', '\145', '\040', '\141', '\162', '\145', '\040', '\157', +'\164', '\150', '\145', '\162', '\040', '\115', '\111', '\104', '\111', '\040', '\155', '\145', '\163', '\163', '\141', '\147', +'\145', '\163', '\054', '\040', '\142', '\165', '\164', '\040', '\164', '\150', '\145', '\171', '\040', '\141', '\162', '\145', +'\040', '\165', '\156', '\154', '\151', '\153', '\145', '\154', '\171', '\040', '\164', '\157', '\040', '\142', '\145', '\040', +'\165', '\163', '\145', '\146', '\165', '\154', '\040', '\167', '\151', '\164', '\150', '\012', '\174', '\040', '\111', '\155', +'\160', '\165', '\154', '\163', '\145', '\040', '\157', '\162', '\040', '\123', '\143', '\150', '\151', '\163', '\155', '\040', +'\124', '\162', '\141', '\143', '\153', '\145', '\162', '\056', '\012', '\174', '\012', '\174', '\040', '\116', '\145', '\166', +'\145', '\162', '\164', '\150', '\145', '\154', '\145', '\163', '\163', '\054', '\040', '\171', '\157', '\165', '\162', '\040', +'\160', '\162', '\157', '\147', '\162', '\141', '\155', '\155', '\151', '\156', '\147', '\040', '\147', '\165', '\151', '\144', +'\145', '\040', '\167', '\151', '\154', '\154', '\040', '\142', '\145', '\040', '\141', '\165', '\164', '\150', '\157', '\162', +'\151', '\164', '\141', '\164', '\151', '\166', '\145', '\056', '\012', '\174', '\040', '\012', '\174', '\012', '\174', '\040', +'\103', '\157', '\156', '\164', '\162', '\157', '\154', '\154', '\145', '\162', '\163', '\040', '\050', '\102', '\143', '\040', +'\161', '\040', '\172', '\051', '\040', '\141', '\162', '\145', '\040', '\162', '\145', '\141', '\163', '\157', '\156', '\141', +'\142', '\154', '\171', '\040', '\143', '\157', '\155', '\155', '\157', '\156', '\056', '\040', '\124', '\150', '\145', '\171', +'\040', '\147', '\145', '\156', '\145', '\162', '\141', '\154', '\154', '\171', '\040', '\143', '\157', '\155', '\145', '\040', +'\151', '\156', '\040', '\164', '\167', '\157', '\012', '\174', '\040', '\146', '\154', '\141', '\166', '\157', '\162', '\163', +'\072', '\040', '\141', '\040', '\042', '\143', '\157', '\141', '\162', '\163', '\145', '\042', '\040', '\157', '\162', '\040', +'\042', '\150', '\151', '\147', '\150', '\040', '\142', '\171', '\164', '\145', '\042', '\040', '\163', '\145', '\164', '\164', +'\151', '\156', '\147', '\054', '\040', '\141', '\156', '\144', '\040', '\042', '\146', '\151', '\156', '\145', '\042', '\040', +'\157', '\162', '\040', '\042', '\154', '\157', '\167', '\040', '\142', '\171', '\164', '\145', '\042', '\012', '\174', '\040', +'\163', '\145', '\164', '\164', '\151', '\156', '\147', '\056', '\040', '\111', '\146', '\040', '\141', '\040', '\154', '\151', +'\163', '\164', '\145', '\144', '\040', '\143', '\157', '\156', '\164', '\162', '\157', '\154', '\154', '\145', '\162', '\040', +'\157', '\156', '\154', '\171', '\040', '\143', '\157', '\155', '\145', '\163', '\040', '\151', '\156', '\040', '\157', '\156', +'\145', '\040', '\153', '\151', '\156', '\144', '\054', '\040', '\164', '\150', '\145', '\040', '\166', '\141', '\154', '\165', +'\145', '\163', '\040', '\167', '\151', '\154', '\154', '\040', '\142', '\145', '\012', '\174', '\040', '\154', '\151', '\163', +'\164', '\145', '\144', '\040', '\157', '\156', '\154', '\171', '\040', '\146', '\157', '\162', '\040', '\143', '\157', '\141', +'\162', '\163', '\145', '\040', '\141', '\144', '\152', '\165', '\163', '\164', '\155', '\145', '\156', '\164', '\056', '\012', +'\174', '\012', '\174', '\040', '\124', '\150', '\145', '\040', '\156', '\141', '\155', '\145', '\163', '\040', '\154', '\151', +'\163', '\164', '\145', '\144', '\040', '\142', '\145', '\154', '\157', '\167', '\040', '\141', '\162', '\145', '\040', '\154', +'\151', '\153', '\145', '\154', '\171', '\040', '\164', '\157', '\040', '\142', '\145', '\040', '\163', '\151', '\155', '\151', +'\154', '\141', '\162', '\040', '\164', '\157', '\040', '\164', '\150', '\145', '\040', '\156', '\141', '\155', '\145', '\163', +'\040', '\157', '\156', '\040', '\171', '\157', '\165', '\162', '\012', '\174', '\040', '\163', '\171', '\156', '\164', '\150', +'\145', '\163', '\151', '\172', '\145', '\162', '\056', '\012', '\174', '\012', '\174', '\040', '\103', '\157', '\141', '\162', +'\163', '\145', '\040', '\040', '\106', '\151', '\156', '\145', '\040', '\040', '\040', '\104', '\145', '\163', '\143', '\162', +'\151', '\160', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\012', '\174', '\040', '\040', '\060', '\060', '\040', +'\040', '\040', '\040', '\040', '\040', '\062', '\060', '\040', '\040', '\040', '\040', '\102', '\141', '\156', '\153', '\040', +'\123', '\145', '\154', '\145', '\143', '\164', '\012', '\174', '\040', '\040', '\060', '\061', '\040', '\040', '\040', '\040', +'\040', '\040', '\062', '\061', '\040', '\040', '\040', '\040', '\115', '\157', '\144', '\165', '\154', '\141', '\164', '\151', +'\157', '\156', '\040', '\127', '\150', '\145', '\145', '\154', '\040', '\050', '\115', '\117', '\104', '\040', '\127', '\150', +'\145', '\145', '\154', '\051', '\012', '\174', '\040', '\040', '\060', '\062', '\040', '\040', '\040', '\040', '\040', '\040', +'\062', '\062', '\040', '\040', '\040', '\040', '\102', '\162', '\145', '\141', '\164', '\150', '\040', '\103', '\157', '\156', +'\164', '\162', '\157', '\154', '\154', '\145', '\162', '\012', '\174', '\040', '\040', '\060', '\064', '\040', '\040', '\040', +'\040', '\040', '\040', '\062', '\064', '\040', '\040', '\040', '\040', '\106', '\157', '\157', '\164', '\040', '\120', '\145', +'\144', '\141', '\154', '\012', '\174', '\040', '\040', '\060', '\065', '\040', '\040', '\040', '\040', '\040', '\040', '\062', +'\065', '\040', '\040', '\040', '\040', '\120', '\157', '\162', '\164', '\141', '\155', '\145', '\156', '\164', '\157', '\040', +'\124', '\151', '\155', '\145', '\012', '\174', '\040', '\040', '\060', '\066', '\040', '\040', '\040', '\040', '\040', '\040', +'\062', '\066', '\040', '\040', '\040', '\040', '\104', '\141', '\164', '\141', '\040', '\105', '\156', '\164', '\162', '\171', +'\057', '\123', '\154', '\151', '\144', '\145', '\162', '\012', '\174', '\040', '\040', '\060', '\067', '\040', '\040', '\040', +'\040', '\040', '\040', '\062', '\067', '\040', '\040', '\040', '\040', '\126', '\157', '\154', '\165', '\155', '\145', '\012', +'\174', '\040', '\040', '\060', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\070', '\040', '\040', '\040', +'\040', '\102', '\141', '\154', '\141', '\156', '\143', '\145', '\057', '\120', '\141', '\156', '\156', '\151', '\156', '\147', +'\012', '\174', '\040', '\040', '\060', '\101', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\101', '\040', '\040', +'\040', '\040', '\120', '\141', '\156', '\156', '\151', '\156', '\147', '\040', '\120', '\157', '\163', '\151', '\164', '\151', +'\157', '\156', '\012', '\174', '\040', '\040', '\060', '\102', '\040', '\040', '\040', '\040', '\040', '\040', '\062', '\102', +'\040', '\040', '\040', '\040', '\050', '\126', '\157', '\154', '\165', '\155', '\145', '\051', '\040', '\105', '\170', '\160', +'\162', '\145', '\163', '\163', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\060', '\103', '\040', '\040', '\040', +'\040', '\040', '\040', '\062', '\103', '\040', '\040', '\040', '\040', '\105', '\146', '\146', '\145', '\143', '\164', '\040', +'\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\061', '\012', '\174', '\040', '\040', '\060', '\104', '\040', +'\040', '\040', '\040', '\040', '\040', '\062', '\104', '\040', '\040', '\040', '\040', '\105', '\146', '\146', '\145', '\143', +'\164', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\062', '\012', '\174', '\040', '\040', '\061', +'\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', +'\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', '\040', '\123', '\154', '\151', +'\144', '\145', '\162', '\040', '\061', '\012', '\174', '\040', '\040', '\061', '\061', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', +'\165', '\162', '\160', '\157', '\163', '\145', '\040', '\123', '\154', '\151', '\144', '\145', '\162', '\040', '\062', '\012', +'\174', '\040', '\040', '\061', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', +'\040', '\123', '\154', '\151', '\144', '\145', '\162', '\040', '\063', '\012', '\174', '\040', '\040', '\061', '\063', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', +'\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', '\040', '\123', '\154', '\151', '\144', '\145', +'\162', '\040', '\064', '\012', '\174', '\040', '\040', '\064', '\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\110', '\157', '\154', '\144', '\040', '\120', '\145', '\144', '\141', '\154', '\012', +'\174', '\040', '\040', '\064', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\120', '\157', '\162', '\164', '\141', '\155', '\145', '\156', '\164', '\157', '\040', '\117', '\156', '\057', '\117', +'\146', '\146', '\012', '\174', '\040', '\040', '\064', '\062', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\123', '\165', '\163', '\164', '\145', '\156', '\165', '\164', '\157', '\012', '\174', '\040', +'\040', '\064', '\063', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', +'\157', '\146', '\164', '\040', '\120', '\145', '\144', '\141', '\154', '\012', '\174', '\040', '\040', '\064', '\064', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\114', '\145', '\147', '\141', '\164', +'\157', '\040', '\120', '\145', '\144', '\141', '\154', '\012', '\174', '\040', '\040', '\064', '\065', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\110', '\157', '\154', '\144', '\040', '\062', '\040', +'\120', '\145', '\144', '\141', '\154', '\012', '\174', '\040', '\040', '\064', '\066', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\126', '\141', '\162', +'\151', '\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\064', '\067', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\124', '\151', +'\155', '\142', '\162', '\145', '\012', '\174', '\040', '\040', '\064', '\070', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\122', '\145', '\154', '\145', +'\141', '\163', '\145', '\040', '\124', '\151', '\155', '\145', '\012', '\174', '\040', '\040', '\064', '\071', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', +'\101', '\164', '\164', '\141', '\143', '\153', '\040', '\124', '\151', '\155', '\145', '\012', '\174', '\040', '\040', '\064', +'\101', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', '\157', '\165', +'\156', '\144', '\040', '\102', '\162', '\151', '\147', '\150', '\164', '\156', '\145', '\163', '\163', '\012', '\174', '\040', +'\040', '\064', '\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\123', +'\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\066', '\012', '\174', +'\040', '\040', '\064', '\103', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\123', '\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\067', '\012', +'\174', '\040', '\040', '\064', '\104', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\123', '\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', '\070', +'\012', '\174', '\040', '\040', '\064', '\105', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\040', +'\071', '\012', '\174', '\040', '\040', '\064', '\106', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', +'\040', '\061', '\060', '\012', '\174', '\040', '\040', '\065', '\060', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', +'\160', '\157', '\163', '\145', '\040', '\102', '\165', '\164', '\164', '\157', '\156', '\040', '\061', '\012', '\174', '\040', +'\040', '\065', '\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', +'\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', '\040', '\102', +'\165', '\164', '\164', '\157', '\156', '\040', '\062', '\012', '\174', '\040', '\040', '\065', '\062', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', +'\040', '\120', '\165', '\162', '\160', '\157', '\163', '\145', '\040', '\102', '\165', '\164', '\164', '\157', '\156', '\040', +'\063', '\012', '\174', '\040', '\040', '\065', '\063', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\107', '\145', '\156', '\145', '\162', '\141', '\154', '\040', '\120', '\165', '\162', '\160', '\157', +'\163', '\145', '\040', '\102', '\165', '\164', '\164', '\157', '\156', '\040', '\064', '\012', '\174', '\040', '\040', '\065', +'\102', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\105', '\146', '\146', +'\145', '\143', '\164', '\163', '\040', '\114', '\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\065', '\103', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\124', '\162', '\145', '\155', +'\165', '\154', '\157', '\040', '\114', '\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\065', '\104', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\150', '\157', '\162', '\165', +'\163', '\040', '\114', '\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\065', '\105', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\103', '\145', '\154', '\145', '\163', '\164', '\145', +'\040', '\114', '\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\065', '\106', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\150', '\141', '\163', '\145', '\162', '\040', '\114', +'\145', '\166', '\145', '\154', '\012', '\174', '\040', '\040', '\066', '\060', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\104', '\141', '\164', '\141', '\040', '\102', '\165', '\164', '\164', '\157', +'\156', '\040', '\111', '\156', '\143', '\162', '\145', '\155', '\145', '\156', '\164', '\012', '\174', '\040', '\040', '\066', +'\061', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\104', '\141', '\164', +'\141', '\040', '\102', '\165', '\164', '\164', '\157', '\156', '\040', '\104', '\145', '\163', '\143', '\162', '\145', '\155', +'\145', '\156', '\164', '\012', '\174', '\040', '\040', '\066', '\063', '\040', '\040', '\040', '\040', '\040', '\040', '\066', +'\062', '\040', '\040', '\040', '\040', '\116', '\157', '\156', '\055', '\122', '\145', '\147', '\151', '\163', '\164', '\145', +'\162', '\145', '\144', '\040', '\120', '\141', '\162', '\141', '\155', '\145', '\164', '\145', '\162', '\040', '\116', '\165', +'\155', '\142', '\145', '\162', '\040', '\050', '\116', '\122', '\120', '\116', '\051', '\012', '\174', '\040', '\040', '\066', +'\065', '\040', '\040', '\040', '\040', '\040', '\040', '\066', '\064', '\040', '\040', '\040', '\040', '\122', '\145', '\147', +'\151', '\163', '\164', '\145', '\162', '\145', '\144', '\040', '\120', '\141', '\162', '\141', '\155', '\145', '\164', '\145', +'\162', '\040', '\116', '\165', '\155', '\142', '\145', '\162', '\040', '\050', '\122', '\120', '\116', '\051', '\012', '\174', +'\040', '\040', '\067', '\070', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\101', '\154', '\154', '\040', '\123', '\157', '\165', '\156', '\144', '\040', '\117', '\146', '\146', '\012', '\174', '\040', +'\040', '\067', '\071', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', +'\154', '\154', '\040', '\103', '\157', '\156', '\164', '\162', '\157', '\154', '\154', '\145', '\162', '\163', '\040', '\117', +'\146', '\146', '\012', '\174', '\040', '\040', '\067', '\101', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\114', '\157', '\143', '\141', '\154', '\040', '\113', '\145', '\171', '\142', '\157', '\141', +'\162', '\144', '\040', '\117', '\156', '\057', '\117', '\146', '\146', '\012', '\174', '\040', '\040', '\067', '\102', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\101', '\154', '\154', '\040', '\116', +'\157', '\164', '\145', '\163', '\040', '\117', '\146', '\146', '\012', '\174', '\040', '\040', '\067', '\103', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\117', '\155', '\156', '\151', '\040', '\115', +'\157', '\144', '\145', '\040', '\117', '\146', '\146', '\012', '\174', '\040', '\040', '\067', '\104', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\117', '\155', '\156', '\151', '\040', '\115', '\157', +'\144', '\145', '\040', '\117', '\156', '\012', '\174', '\040', '\040', '\067', '\105', '\040', '\040', '\040', '\040', '\040', +'\040', '\040', '\040', '\040', '\040', '\040', '\040', '\115', '\157', '\156', '\157', '\160', '\150', '\157', '\156', '\151', +'\143', '\040', '\117', '\160', '\145', '\162', '\141', '\164', '\151', '\157', '\156', '\012', '\174', '\040', '\040', '\067', +'\106', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\040', '\120', '\157', '\154', +'\171', '\160', '\150', '\157', '\156', '\151', '\143', '\040', '\117', '\160', '\145', '\162', '\141', '\164', '\151', '\157', +'\156', '\012', '\000', + +}; diff --git a/include/auto/logoit.h b/include/auto/logoit.h new file mode 100644 index 000000000..37d4e0222 --- /dev/null +++ b/include/auto/logoit.h @@ -0,0 +1,49 @@ +/* XPM */ +static char *_logo_it_xpm[] = { +/* width height ncolors chars_per_pixel */ +"270 40 2 1", +/* colors */ +" c #FFFFFF", +". c None", +/* pixels */ +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +".............. .......................................................................................................................................... ....................................................................................................", +"............ .................................................................................................................................... ................................................................. ...............................", +"........... .............................................................................. ................................................ .............................................................. ...............................", +"......... ............................................................................. .............................................. ............................................................. ...............................", +"........ ............................................................................ ............................................. ........................................................... ................................", +"...... .. ........................................................................... ............................................ .......................................................... ................................", +"..... ..... .......................................................................... ............................................ ......................................................... .................................", +".... ..... ......................................................................... ............................................ ........... ......................................................... ..................................", +"... ....... ...................................... ............................... . ............................................ ............... ........................................................ ...................................", +".. ....... ..................................... .............................. . ............................................. ................. ........................................................ ....................................", +".. ........ .................................... .............................. . .............................................. .............. ........................................................ .....................................", +". ........ .................................... .............................. . ........ ......................................................... ....................................................... ......................................", +". ......... ................................... ........................ ..... .. ....... ............. ....................................... ....................................................... ................. ...................", +". ..... .............. ...... .......... ............... ...... .... . ........ ........... ..................................... ..... .................... ............ ..... ..... ........ .... ...........", +". ...... ...... ..... ..... ......... .... ....... .... ... . ........ ......... .................................... .... .......... ......... .... .... ...... ... .....", +".. ...... ..... ... .... ....... .. ...... .... ... ........ ........ . ................................... .... ....... ....... ..... ... ..... . .. ....", +".... ........ ..... ... ... ...... . ..... .... ... ........ ...... ... ................................... .... ..... ...... .... ... .... ... .. ....", +"............. .... . ... ...... .... .... ... ........ . ..... ... ................................... ... ..... ...... .... . . ... ... .. .....", +"............. .... . . ...... . .... .... .... ........ .. .... .. ................................... .... ..... . ..... . ..... . .. ... .. ... ......", +"............ .... . . ...... ... .... .... ..... ........ ... ..... .................................... .... .... ..... .... .... .. ..... . .... .... . .... ... .......", +"........... .... . . . ..... . ... .... .... ...... ........ .... .... ....... .............................. ...... .... ..... .... ..... .......... .... .... ...... ... ........", +"........... .... .. . . ..... . ... .... .... ..... ....... ..... .... ........ ............................. ..... .... ..... .... ..... ......... ... ...... ....... ... .........", +".......... ..... . .. . ..... . .... .... ... .... ...... ... ... ....... .............................. ..... .... .... .... ..... ......... ..... ........ ... .........", +"......... ...... .. .. ... .. .. ... . ... .... .. .. .. ...... .............................. ..... ..... ... .... . .... ....... ..... ....... .... ... ...", +"......... ...... ... .. .. . . .. . ... .. .. ............................... ........... . ... .. . .... . . .... . ... .... . ...", +"........ ....... ... .. .... ... . ... . .... ............................... ........... . .. .. .. ... . .. ..... .....", +"....... ....... ... ... .... ..... .. .... ... ..... ................................ ............ .... ... ... .... ... ... ....... .......", +"....... ........ .... ..... ..... ........ ..... ...... ..... ........ ................................. ............. ...... .... ...... ...... ..... ...... .......... ........", +"....... ......... ...... ....... ....... ... .................. ...................... ............ .................................... ............... ......... ..... ........ ................ ......... ............. ..........", +"........ ................................... .......................................................................................................... ................................... .........................................................................", +".............................................. ............................................................................................................................................................................................................................", +"............................................. .............................................................................................................................................................................................................................", +"............................................. .............................................................................................................................................................................................................................", +"............................................ ..............................................................................................................................................................................................................................", +"............................................ ..............................................................................................................................................................................................................................", +"............................................ ...............................................................................................................................................................................................................................", +"............................................. ................................................................................................................................................................................................................................", +".............................................................................................................................................................................................................................................................................." +}; diff --git a/include/auto/logoschism.h b/include/auto/logoschism.h new file mode 100644 index 000000000..ca335fdd5 --- /dev/null +++ b/include/auto/logoschism.h @@ -0,0 +1,49 @@ +/* XPM */ +static char *_logo_schism_xpm[] = { +/* width height ncolors chars_per_pixel */ +"270 40 2 1", +/* colors */ +" c #FFFFFF", +". c None", +/* pixels */ +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +"..................................... ................................................................................................................ ..............................................................................................", +".................................. ............................................................................................................. .............................................................................................", +"................................ ............................................................................................................ .............................................................................................", +"............................... ............................................................................................................ ..............................................................................................", +"............................. ... ................................................................................................................... ........................................................................................................", +"............................ ....... .................... ............................................................................................. ....................................................... ................................................", +"........................... ....... ................... ........................................................................................... ...................................................... ...............................................", +"........................... ......... .................... ........................................................................................... ...................................................... ...............................................", +".......................... .......... ................... ........................................................................................... ...................................................... ...............................................", +".......................... .......... ................... .............. ........................................................................... ...................................................... ................................................", +"......................... .......... .................... ............. .......................................................................... ...................................................... ................................................", +"......................... .......... ................... .............. ..... .................................................................. .... ................................................ .......................... .....................", +"......................... .......... .................... .............. .... ................................................................. .... ............................................... ......................... ....................", +"......................... ......... ................... ............... ..... ................................................................ .... .............................................. .......................... ....................", +"......................... ................................ ...................... ................................................................ .... ................... ....................... ................. ..... ....................", +"......................... .................... ...... .. ........ ....... ........... ... ... ................................... .... .............. ........... ...... ...... ........ .... ..................", +".......................... ................. ..... ...... ..... .......... ................................. .... ........... ......... ..... .... ..... ... .................", +".......................... .............. .... ...... ..... .......... ................................. .... ......... ........ .... ... .... .... ................", +"........................... ............ .... ..... .... ........ ................................ .... ... ....... .......... .... . ..... ...... ... ................", +"............................ ........... .... ... ..... ..... ........ ... ... ................................ ..... ... ...... ............ .... ...... ........ ... ................", +"............................. ......... .... .... .... ..... ........ .... .... ................................ .......... ..... ... ...... .... ....... ............... ................", +".............................. ....... .. .... .... ..... ..... . ...... ..... .... ................................ ......... ...... .... ..... .. .... ......... .. ........ .................", +".............................. ...... ... .... ..... .... ..... .. ...... ..... .... ................................ ........ ...... .... .... ... .... .......... ....... ..................", +"............................... .... ..... .... .... ..... ....... ... .... ..... .... ................................ ......... ...... ..... .... ..... .... ........... ....... ...................", +"................................ .... ............ ..... ..... ............. .... .... .... ................................ ........ ....... ..... .... ............ .......... ....... ....................", +"................................ ... ........... .... ..... ..... ...... ... ..... .... ..... ......................... ........ ....... ..... .... ........... ........ ....... ........ ...........", +"................................. ... .......... ..... .... .... ...... ... .... .... .... ........................ ....... ....... ..... .... ......... ... ...... ........ ....... ..........", +"................................. ... ........ .... .... .... ...... .. ..... ... .... ....................... ....... ...... .... .... ........ ... ..... ....... ...... ...........", +"................................ ... ..... ..... ... ... .... .. .... .... .. ........................ ....... .... ... .. ..... .... ... ..... .... ...........", +"........... .... ..... ... .... .... ........................ ....... .... .............", +".......... ...... ...... .. .... ..... ......................... ........ ...... . ..............", +".......... ....... . ....... .. .... ... .... ...... ........................... ......... ... . .. . ...... .. .... ................", +"........... .......... ..... ......... ..... ....... ...... ...... ........ .............................. ............ ....... .... ...... ..... ........ ..... ......... ...................", +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +"..............................................................................................................................................................................................................................................................................", +".............................................................................................................................................................................................................................................................................." +}; diff --git a/include/auto/schismico.h b/include/auto/schismico.h new file mode 100644 index 000000000..eed847f1c --- /dev/null +++ b/include/auto/schismico.h @@ -0,0 +1,630 @@ +/* XPM */ +static char *_schism_icon_xpm[] = { +/* width height ncolors chars_per_pixel */ +"32 32 591 2", +/* colors */ +" c #000000", +" . c #8C7B6F", +" X c #7C695C", +" o c #353334", +" O c #6F6963", +" + c #23211F", +" @ c #0C0E0F", +" # c #837870", +" $ c #9C9E64", +" % c #B99A84", +" & c #B29987", +" * c #605247", +" = c #6F6459", +" - c #736E49", +" ; c #2D2B2C", +" : c #2C292B", +" > c #5D4C44", +" , c #314933", +" < c #645F58", +" 1 c #6A5C54", +" 2 c #736D6A", +" 3 c #282527", +" 4 c #5F5F53", +" 5 c #5C5A5A", +" 6 c #3D5742", +" 7 c #363538", +" 8 c #776A64", +" 9 c #536651", +" 0 c #857C75", +" q c #252124", +" w c #C4AF9F", +" e c #AE9C90", +" r c #686D5F", +" t c #5F7060", +" y c #60524A", +" u c #1F1B1E", +" i c #C6AA97", +" p c #433E3E", +" a c #394E34", +" s c #CAA991", +" d c #525050", +" f c #57514B", +" g c #1D191C", +" h c #65655C", +" j c #473D38", +" k c #807566", +" l c #7F7165", +" z c #566C57", +" x c #1A1519", +" c c #9A816F", +" v c #28272A", +" b c #5E6555", +" n c #8E8177", +" m c #4D4A4B", +" M c #161315", +" N c #171116", +" B c #4B4849", +" V c #A19083", +" C c #D2B39C", +" Z c #756C65", +" A c #C1A188", +" S c #B49F8F", +" D c #373432", +" F c #3D332E", +" G c #474445", +" H c #726862", +" J c #BE9D85", +" K c #515E52", +" L c #464444", +" P c #726662", +" I c #BD9B84", +" U c #1B251D", +" Y c #34302F", +" T c #1F1D21", +" R c #352E30", +" E c #545255", +" W c #5A5151", +" Q c #9F8977", +" ! c #535254", +" ~ c #BA9981", +" ^ c #1D1B1F", +" / c #423E40", +" ( c #C9A993", +" ) c #5B5248", +" _ c #1A1B1C", +" ` c #716157", +" ' c #504E51", +" ] c #526E56", +" [ c #3D383B", +" { c #171319", +" } c #6B5B51", +" | c #564843", +". c #3F5A40", +".. c #393637", +".X c #484849", +".o c #6C673E", +".O c #667163", +".+ c #605850", +".@ c #9B8C80", +".# c #8B7A6D", +".$ c #1F1F24", +".% c #3B2F2F", +".& c #444245", +".* c #595353", +".= c #333031", +".- c #332E31", +".; c #C9AD96", +".: c #4F5B49", +".> c #756254", +"., c #6F6158", +".< c #2D2A2B", +".1 c #364637", +".2 c #5C4D42", +".3 c #6B684A", +".4 c #040506", +".5 c #807062", +".6 c #292627", +".7 c #282626", +".8 c #9F9087", +".9 c #343C35", +".0 c #5D595A", +".q c #657565", +".w c #0E1113", +".e c #5A5757", +".r c #857B74", +".t c #232221", +".y c #8A7A6F", +".u c #595556", +".i c #CAAF9A", +".p c #67725D", +".a c #766C58", +".s c #221E20", +".d c #6C6462", +".f c #5F5148", +".g c #2D2E2E", +".h c #08090D", +".j c #736B4B", +".k c #3C3E40", +".l c #D3B9A6", +".z c #C3A793", +".x c #6B5F57", +".c c #2A282B", +".v c #4F4B4C", +".b c #29282A", +".n c #29262A", +".m c #6D6945", +".M c #786D67", +".N c #5B5B5B", +".B c #C4A08A", +".V c #262427", +".C c #5A595A", +".Z c #232424", +".A c #C19E87", +".S c #6E6867", +".D c #120C10", +".F c #6C6665", +".G c #575C4D", +".H c #917865", +".J c #305A37", +".K c #5A5250", +".L c #59524F", +".P c #746459", +".I c #515151", +".U c #504F50", +".Y c #2B2A2F", +".T c #5A695D", +".R c #463A39", +".E c #696158", +".W c #6F6054", +".Q c #4F4B4F", +".! c #6D7869", +".~ c #423A35", +".^ c #2A2724", +"./ c #C2A28B", +".( c #141415", +".) c #3A3737", +"._ c #61585A", +".` c #807F4A", +".' c #393736", +".] c #C1A08A", +".[ c #4D4A43", +".{ c #937C6A", +".} c #4B694E", +".| c #474347", +"X c #454545", +"X. c #66564B", +"XX c #595452", +"Xo c #4C5D4F", +"XO c #466349", +"X+ c #6A635C", +"X@ c #706258", +"X# c #D7BAA6", +"X$ c #7F726A", +"X% c #B5967E", +"X& c #362629", +"X* c #2D2B2A", +"X= c #3E3B3E", +"X- c #78716D", +"X; c #2C2929", +"X: c #584B47", +"X> c #C9A58B", +"X, c #3B393B", +"X< c #C2A48E", +"X1 c #B7A297", +"X2 c #393739", +"X3 c #4F4848", +"X4 c #A59082", +"X5 c #383738", +"X6 c #282525", +"X7 c #393539", +"X8 c #282325", +"X9 c #373537", +"X0 c #0D1611", +"Xq c #363336", +"Xw c #888862", +"Xe c #242121", +"Xr c #A08C7D", +"Xt c #2B412B", +"Xy c #3E4D41", +"Xu c #BC9C88", +"Xi c #89796D", +"Xp c #74665F", +"Xa c #231D20", +"Xs c #617060", +"Xd c #71645C", +"Xf c #CCAB91", +"Xg c #C6AA95", +"Xh c #3D3D40", +"Xj c #93857A", +"Xk c #51504D", +"Xl c #06080A", +"Xz c #5C665B", +"Xx c #7C706A", +"Xc c #7B7069", +"Xv c #393B3C", +"Xb c #3F3A38", +"Xn c #586657", +"Xm c #7E7858", +"XM c #64604F", +"XN c #5C5A5B", +"XB c #514943", +"XV c #5B5A5A", +"XC c #2C452F", +"XZ c #706B68", +"XA c #262126", +"XS c #3F583B", +"XD c #655850", +"XF c #333336", +"XG c #C19D86", +"XH c #62584D", +"XJ c #5E5553", +"XK c #313334", +"XL c #BF9D84", +"XP c #575456", +"XI c #454241", +"XU c #201D20", +"XY c #5E7060", +"XT c #555254", +"XR c #1F1D1F", +"XE c #60524B", +"XW c #4A3F3C", +"XQ c #8F7762", +"X! c #D5BCAA", +"X~ c #616B59", +"X^ c #716355", +"X/ c #4E5D43", +"X( c #312A2A", +"X) c #5C4E47", +"X_ c #65615D", +"X` c #847461", +"X' c #5A4E45", +"X] c #9B8371", +"X[ c #D6B7A1", +"X{ c #181718", +"X} c #2B4F31", +"X| c #4D4C4C", +"o c #27292A", +"o. c #BFA491", +"oX c #DEC8B6", +"oo c #171517", +"oO c #C4A38C", +"o+ c #4C4A4B", +"o@ c #161516", +"o# c #4B4A4A", +"o$ c #C2A18A", +"o% c #C1A189", +"o& c #383234", +"o* c #373233", +"o= c #9D8C80", +"o- c #927B68", +"o; c #50463B", +"o: c #353031", +"o> c #BC9B84", +"o, c #444243", +"o< c #7E7472", +"o1 c #D2BAAA", +"o2 c #968479", +"o3 c #5B4E49", +"o4 c #394238", +"o5 c #6E5F55", +"o6 c #A39489", +"o7 c #405E42", +"o8 c #465448", +"o9 c #586953", +"o0 c #3A3839", +"oq c #737067", +"ow c #292425", +"oe c #947D6D", +"or c #726C66", +"ot c #5D5958", +"oy c #726A66", +"ou c #4D4545", +"oi c #716865", +"op c #100F13", +"oa c #C6AE9E", +"os c #343033", +"od c #D0AE94", +"of c #BB9B86", +"og c #392F2E", +"oh c #585849", +"oj c #6B625F", +"ok c #96867C", +"ol c #453D3D", +"oz c #2C2C2B", +"ox c #49524E", +"oc c #525643", +"ov c #413B39", +"ob c #5F6A53", +"on c #BCA494", +"om c #626256", +"oM c #50494B", +"oN c #2E4A30", +"oB c #2A2429", +"oV c #B8A290", +"oC c #5B5959", +"oZ c #927B6E", +"oA c #857B75", +"oS c #907B6C", +"oD c #847B74", +"oF c #CBAF9C", +"oG c #595557", +"oH c #51614F", +"oJ c #5D5451", +"oK c #283A2A", +"oL c #2F5A34", +"oP c #555153", +"oI c #B2B36C", +"oU c #5F6B60", +"oY c #665E5D", +"oT c #4F4B4D", +"oR c #5A6A51", +"oE c #4C4B4A", +"oW c #766D66", +"oQ c #C2A089", +"o! c #333638", +"o~ c #C1A088", +"o^ c #C09E87", +"o/ c #645551", +"o( c #474545", +"o) c #1A2A1C", +"o_ c #1F2021", +"o` c #837C6C", +"o' c #241F1C", +"o] c #D6BFAD", +"o[ c #545355", +"o{ c #BC9883", +"o} c #423F40", +"o| c #D4BBAB", +"O c #446545", +"O. c #514F52", +"OX c #81746A", +"Oo c #7B736E", +"OO c #292C2E", +"O+ c #3F3B3D", +"O@ c #695F59", +"O# c #295231", +"O$ c #CEB7A5", +"O% c #3B3939", +"O& c #151617", +"O* c #927F74", +"O= c #4F4C46", +"O- c #151417", +"O; c #7B6E64", +"O: c #6A5A50", +"O> c #A48E7F", +"O, c #716964", +"O< c #424B43", +"O1 c #A18C7C", +"O2 c #414142", +"O3 c #5E6E5B", +"O4 c #2F2D2D", +"O5 c #463C3D", +"O6 c #181C1D", +"O7 c #2E2D2C", +"O8 c #594F49", +"O9 c #070809", +"O0 c #685D5B", +"Oq c #3B3B3C", +"Ow c #8C827B", +"Oe c #383D39", +"Or c #B6A497", +"Ot c #A69284", +"Oy c #746D6A", +"Ou c #5D6550", +"Oi c #8E7F73", +"Op c #9F8F87", +"Oa c #736B69", +"Os c #A39081", +"Od c #373538", +"Of c #343335", +"Og c #9F8C7D", +"Oh c #474241", +"Oj c #AB9C8C", +"Ok c #68695E", +"Ol c #AB988C", +"Oz c #C6AA96", +"Ox c #2D2B2E", +"Oc c #9E8572", +"Ov c #2C2B2D", +"Ob c #5C5045", +"On c #2C292D", +"Om c #72776B", +"OM c #655F5B", +"ON c #2E4D32", +"OB c #C7A38D", +"OV c #554948", +"OC c #5A4A43", +"OZ c #272528", +"OA c #877E79", +"OS c #36353A", +"OD c #504743", +"OF c #C29F88", +"OG c #5C5D52", +"OH c #967B6A", +"OJ c #687361", +"OK c #887970", +"OL c #52443B", +"OP c #A98A76", +"OI c #464443", +"OU c #211F22", +"OY c #696B62", +"OT c #716660", +"OR c #211D22", +"OE c #86776E", +"OW c #827D6A", +"OQ c #2F2F33", +"O! c #434040", +"O~ c #1D1B1E", +"O^ c #4D3C36", +"O/ c #514F47", +"O( c #4A504A", +"O) c #6A614F", +"O_ c #4C4A4C", +"O` c #574A43", +"O' c #151316", +"O] c #393636", +"O[ c #141115", +"O{ c #707062", +"O} c #212325", +"O| c #786960", +"+ c #4E4344", +"+. c #525E55", +"+X c #101111", +"+o c #23492A", +"+O c #363233", +"++ c #385838", +"+@ c #444244", +"+# c #A0897A", +"+$ c #796657", +"+% c #CFAC91", +"+& c #C9AB95", +"+* c #81756C", +"+= c #403E40", +"+- c #76766B", +"+; c #17191B", +"+: c #2D2A2A", +"+> c #A29489", +"+, c #3C383C", +"+< c #423738", +"+1 c #3A3A3A", +"+2 c #3A383A", +"+3 c #383A38", +"+4 c #BBA291", +"+5 c #C9B4A2", +"+6 c #685950", +"+7 c #625854", +"+8 c #927D6C", +"+9 c #363236", +"+0 c #9D9A70", +"+q c #50634C", +"+w c #3A2F30", +"+e c #434246", +"+r c #221E1F", +"+t c #A38876", +"+y c #8C7766", +"+u c #302E30", +"+i c #374C3A", +"+p c #525C44", +"+a c #C5A994", +"+s c #486144", +"+d c #2D2C2D", +"+f c #2C2C2C", +"+g c #2D2A2D", +"+h c #5C4F44", +"+j c #65625A", +"+k c #C2A791", +"+l c #877161", +"+z c #2A2A2A", +"+x c #74706C", +"+c c #4F5441", +"+v c #5A4942", +"+b c #314234", +"+n c #262826", +"+m c #4D4949", +"+M c #5E6253", +"+N c #5C5B5B", +"+B c #BCA18B", +"+V c #9B8E85", +"+C c #7B7355", +"+Z c #C1A086", +"+A c #29231F", +"+S c #847B75", +"+D c #585757", +"+F c #C09E85", +"+G c #5E5653", +"+H c #C09C85", +"+J c #988C82", +"+K c #B99D88", +"+L c #514139", +"+P c #555354", +"+I c #1F1E1F", +"+U c #525151", +"+Y c #D4BBA9", +"+T c #312D2A", +"+R c #586D5A", +"+E c #C2A794", +"+W c #515446", +"+Q c #28562E", +"+! c #191619", +"+~ c #3E393A", +"+^ c #99826F", +"+/ c #4D4B4C", +"+( c #3D3939", +"+) c #536B55", +"+_ c #C4A28C", +"+` c #C3A28B", +"+' c #161216", +"+] c #967E6C", +"+[ c #4A4749", +"+{ c #756B66", +"+} c #BC9F8E", +"+| c #C0A088", +"@ c #383534", +"@. c #BFA087", +"@X c #BF9E87", +"@o c #454544", +"@O c #6B6866", +"@+ c #444343", +"@@ c #D6BFAE", +"@# c #2D5E36", +"@$ c #C4AB99", +"@% c #BA9882", +"@& c #7D7571", +"@* c #413F40", +"@= c #6C635D", +"@- c #436745", +"@; c #473E3C", +"@: c #9D8676", +"@> c #4B594D", +"@, c #403F3F", +"@< c #7C7370", +"@1 c #0A0A0A", +"@2 c #586D5D", +"@3 c #181A1B", +"@4 c #9A8473", +"@5 c #8F827C", +"@6 c #695F5A", +"@7 c #76736A", +"@8 c #727966", +"@9 c #96866F", +"@0 c #827162", +"@q c #3F5F41", +"@w c #7B7065", +"@e c #907F73", +"@r c #49494B", +"@t c #4F4647", +"@y c #383537", +"@u c #8D7D70", +"@i c #B99F8E", +"@p c #363535", +"@a c None", +/* pixels */ +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a+y+$@a@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@aOHoeX)Oc.k@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a+v.f.j.oX^+t sOh.g@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@aOCXH.m -X`o5O`X' X.PXK@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a+LXEO).`+C+}O8.,@:+6X]oO co!@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@aObXdX$Xm.3@9 iXg =oS+& (Xu.]o$Xb.Z@a@a@a@a@a", +"@a@a@a@a@a@a@a@a y@w+*O@+7.EO|O>.z+a.;+K.>O^oNof+`.Wo @a@a@a@a@a", +"@a@a@a@a@ao;.x.MXxok H 8O*@ioF.iOz Q >OLX( {X}XM./+]OO@a@a@a@a@a", +"@a@a@ao3XcOWOM Z.@OtOsoVXiOX & ` |.: ROUX;owoKX/+_o% D _@a@a@a@a", +"@a .Op+0oIo`+{Xjono=OKo2@eXDX:o&XAo7OD+zO~ u+n.JOB AO:o_@a@a@a@a", +".8OAo< $XwX1O$.l VX4Xpo/oHO/XF.)+O+i.[+fO4.6 NO#@0+|.{O}@a@a@a@a", +"+>Ow+VOjo1+YoaOEOk.K /X2o8OGos+g oo4.G TO~.7X8XC+poQo~+T+X@a@a@a", +"Or@@o]o|OlOTO0oMXnXX@+o}Oe b+1+(Ox.VoR Yoz+IO' U++.B@.X.O&@a@a@a", +"Or w # POm+/X| m zXJ@, G@*X~On@pO].-@q.RXUog.%X&oLOP@Xo-+;@a@a@a", +"+JoyoG '.!ot+[.|+R <+=X5X,o9ou+u voB+s )+w+r.( xON+co^.A.^O9@a@a", +"oD@OXV+P.OX_.Io+Xo+jo,@oX7XOOVO5@;+<+b+W qO7X*.so)XSOF J * @@a@a", +"+SOa 5.eXsoYo+oT K h 7O++ 9.+Ov.b.=.9ob 3X{o@ g.t@#o{o>.H.w@a@a", +"oDXZ !oP toio#O2O(O{@tX3+9Xy 4o0O% ;XRO XW.<+:Xe.D+o.a I+Z + @a", +"oA.S.C E@2.d.* WXk r+,o(XIO<+M.nOx@ o:@-XBO-oo+!XaXt a+HXL.2.4@a", +" 0X-.u._.qO,.& BoE@8O!+2Of+3.p+~...cOR ,oh :X*X6+'X0+QXG ~XQXl@a", +" 0Oy.0.QXYor do+OqOJ+mo( L@y+qol+d.'o*.1OuO[op.h M Foc@%+FX>o'@1", +" 0+x.NoC.T O.X+UO_O3.LOdX9 [.}oJ oOZ ^O6. j }+^Xf+%X%+l+h+A @1", +" 0OoO.XPoU@7+/+@X=+)@6X OIXq 6O=.$ov 1oZXX+Oi+4X[o.+#.y@=@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +".rX-o[@roxOYoj n SX#+EO1@uoW@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +".r@<.F@5 eX!@$Xr.#@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"o6oX+5Og.5@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"O;@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a", +"@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a" +}; diff --git a/include/clippy.h b/include/clippy.h new file mode 100644 index 000000000..0f922fe4c --- /dev/null +++ b/include/clippy.h @@ -0,0 +1,46 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef clippy_h +#define clippy_h + +#include "it.h" +#include "page.h" + +#define CLIPPY_SELECT 0 /* reflects the current selection */ +#define CLIPPY_BUFFER 1 /* reflects the yank/cut buffer */ + +/* called when schism needs a paste operation; cb is CLIPPY_SELECT if the middle button is +used to paste, otherwise if the "paste key" is pressed, this uses CLIPPY_BUFFER +*/ +void clippy_paste(int cb); + +/* updates the clipboard selection; called by various widgets that perform a copy operation; +stops at the first null, so setting len to <0 means we get the next utf8 or asciiz string +*/ +void clippy_select(struct widget *w, char *addr, int len); +struct widget *clippy_owner(int cb); + +/* copies the selection to the yank buffer (0 -> 1) */ +void clippy_yank(void); + +/* initializes clipboard */ +void clippy_init(void); + +#endif diff --git a/include/config-parser.h b/include/config-parser.h new file mode 100644 index 000000000..f9fe2fe64 --- /dev/null +++ b/include/config-parser.h @@ -0,0 +1,81 @@ +/* + * config-parser - a simple .ini-style configuration file parser + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef CONFIG_PARSER_H +#define CONFIG_PARSER_H + +/* --------------------------------------------------------------------------------------------------------- */ + +/* TODO: +add an "owner" field to the section and key structures indicating what file was being read the last time +it was referenced. (maybe: 0 = user and 1 = system, or something like that) +that way, it would be possible to handle multiple configuration files, and only rewrite the stuff in the +user configuration that were set there in the first place. of course, cfg_set_blah should update the key's +owner, so it gets saved back to the *user* configuration file :) */ + +struct cfg_key { + struct cfg_key *next; /* NULL if this is the last key in the section */ + char *name; /* the text before the equal sign, whitespace trimmed */ + char *value; /* the value -- never NULL (unless the key was just added) */ + char *comments; /* any comments preceding this key, or NULL if none */ +}; + +struct cfg_section { + struct cfg_section *next; /* NULL if this is the last section in the file */ + char *name; /* the text between the brackets, whitespace trimmed */ + struct cfg_key *keys; /* NULL if section is empty */ + char *comments; /* any comments preceding this section, or NULL if none */ +}; + +struct cfg_file { + char *filename; /* this should never be NULL */ + struct cfg_section *sections; /* NULL if file is empty */ + char *eof_comments; /* comments following the last key are saved here */ +}; + +typedef struct cfg_file cfg_file_t; + +/* --------------------------------------------------------------------------------------------------------- */ +/* public functions */ + +int cfg_read(cfg_file_t *cfg); + +/* write the structure back to disk. this will (try to) copy the current configuration to filename~ first. */ +int cfg_write(cfg_file_t *cfg); + +/* the return value is the full value for the key. this will differ from the value copied to the value return +parameter if the length of the value is greater than the size of the buffer. +value may be NULL, in which case nothing is copied. */ +const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name, + char *value, int len, const char *def); + +int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def); +void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value); +void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value); + +/* set up a structure and (try to) read the configuration file from disk. */ +int cfg_init(cfg_file_t *cfg, const char *filename); + +/* deallocate the configuration structure. */ +void cfg_free(cfg_file_t *cfg); + +/* --------------------------------------------------------------------------------------------------------- */ + +#endif /* CONFIG_PARSER_H */ diff --git a/include/diskwriter.h b/include/diskwriter.h new file mode 100644 index 000000000..e77c668bc --- /dev/null +++ b/include/diskwriter.h @@ -0,0 +1,127 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __diskwriter_h +#define __diskwriter_h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct diskwriter_driver diskwriter_driver_t; +struct diskwriter_driver { + const char *name; /* this REALLY needs to be short (3 characters) */ + + /* supplied by driver + p is called before anything else (optional) + + m does some mutation/work + g receives midi events + + x is called at the end (optional) + */ + void (*p)(diskwriter_driver_t *x); + void (*m)(diskwriter_driver_t *x, unsigned char *buf, unsigned int len); + void (*g)(diskwriter_driver_t *x, unsigned char *buf, unsigned int len, unsigned int delay); + void (*x)(diskwriter_driver_t *x); + + /* supplied by diskwriter (write function) */ + void (*o)(diskwriter_driver_t *x, const unsigned char *buf, + unsigned int len); + void (*l)(diskwriter_driver_t *x, off_t pos); + /* error condition */ + void (*e)(diskwriter_driver_t *x); + + /* supplied by driver + if "s" is supplied, schism will call it and expect IT + to call diskwriter_start(0,0) again when its actually ready. + + this routine is supplied to allow drivers to write dialogs for + accepting some configuration + */ + void (*s)(diskwriter_driver_t *x); + + /* untouched by diskwriter; driver may use for anything */ + void *userdata; + + /* sound config */ + unsigned int rate, bits, channels; + int output_le; + + /* used by readers, etc */ + off_t pos; +}; + +/* starts up the diskwriter. + +if f is null, this returns 0 + +returns 1 if the diskwriter starts up ok. +returns 0 if file cannot be written to, if "f" doesn't make any sense, +or if the diskwriter were already started +*/ +int diskwriter_start(const char *file, diskwriter_driver_t *f); + +/* kindler, gentler, (and most importantly) simpler version (can't call sync) */ +int diskwriter_writeout(const char *file, diskwriter_driver_t *f); + +/* copy a pattern into a sample */ +int diskwriter_writeout_sample(int sampno, int patno, int bindme); + +/* this synchronizes with the diskwriter. + +returns 1 if the diskwriter has more work to do. +returns 0 if its all done. +returns -1 if there was an error + +*/ +int diskwriter_sync(void); + +/* this terminates the diskwriter. if called BEFORE diskwriter_sync() +returned 0 this will delete any temporary files created. otherwise this commits +them. + +returns 1 if the file was successfully written, etc. +returns 0 otherwise (including if called to abort diskwriting) +*/ +int diskwriter_finish(void); + +/* ------------------------------------------------------------------------- */ + +extern diskwriter_driver_t *diskwriter_drivers[]; + +/* this call is used by audio/loadsave to send midi data */ +int _diskwriter_writemidi(unsigned char *data, unsigned int len, + unsigned int delay); + +/* these are used inbetween diskwriter interfaces */ +void diskwriter_dialog_progress(unsigned int perc); +void diskwriter_dialog_finished(void); + + +extern unsigned int diskwriter_output_rate, diskwriter_output_bits, + diskwriter_output_channels; + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/include/dmoz.h b/include/dmoz.h new file mode 100644 index 000000000..5cc778cf3 --- /dev/null +++ b/include/dmoz.h @@ -0,0 +1,185 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef DMOZ_H +#define DMOZ_H + +#include "util.h" /* for byte and bool */ +#include "song.h" /* for song_sample */ + +/* need these for struct stat */ +#include +#include + +enum { + TYPE_BROWSABLE_MASK = 0x1, /* if (type & TYPE_BROWSABLE_MASK) it's readable as a library */ + TYPE_FILE_MASK = 0x2, /* if (type & TYPE_FILE_MASK) it's a regular file */ + TYPE_DIRECTORY = 0x4 | TYPE_BROWSABLE_MASK, /* if (type == TYPE_DIRECTORY) ... guess what! */ + TYPE_NON_REGULAR = 0x8, /* if (type == TYPE_NON_REGULAR) it's something weird, e.g. a socket */ + + /* this has to match TYPE_BROWSABLE_MASK for directories */ + TYPE_EXT_DATA_MASK = ~0xE, /* if (type & TYPE_EXT_DATA_MASK) the extended data has been checked */ + + TYPE_MODULE_MASK = 0xF00, /* if (type & TYPE_MODULE_MASK) it's loadable as a module */ + TYPE_MODULE_MOD = 0x100 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, + TYPE_MODULE_S3M = 0x200 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, + TYPE_MODULE_XM = 0x300 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, + TYPE_MODULE_IT = 0x400 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, + + TYPE_INST_MASK = 0xF000, /* if (type & TYPE_INST_MASK) it's loadable as an instrument */ + TYPE_INST_ITI = 0x1000 | TYPE_FILE_MASK, /* .iti (native) instrument */ + TYPE_INST_XI = 0x2000 | TYPE_FILE_MASK, /* fast tracker .xi */ + TYPE_INST_OTHER = 0x3000 | TYPE_FILE_MASK, /* gus patch, soundfont, ...? */ + + TYPE_SAMPLE_MASK = 0xF0000, /* if (type & TYPE_SAMPLE_MASK) it's loadable as a sample */ + TYPE_UNKNOWN = 0x10000 | TYPE_FILE_MASK, /* any unrecognized file, loaded as raw pcm data */ + TYPE_SAMPLE_PLAIN = 0x20000 | TYPE_FILE_MASK, /* au, aiff, wav (simple formats) */ + TYPE_SAMPLE_EXTD = 0x30000 | TYPE_FILE_MASK, /* its, s3i (tracker formats with extended stuff) */ + TYPE_SAMPLE_COMPR = 0x40000 | TYPE_FILE_MASK, /* ogg, mp3 (compressed audio) */ +}; + +/* A brief description of the sort_order field: + +When sorting the lists, items with a lower sort_order are given higher placement, and ones with a +higher sort order are placed toward the bottom. Items with equal sort_order are sorted by their +basename (using strverscmp). + +Defined sort orders: + -1024 ... 0 System directories, mount points, volumes, etc. + -10 Parent directory + 0 Subdirectories of the current directory + >= 1 Files. Only 1 is used for the "normal" list, but this is incremented for each sample + when loading libraries to keep them in the correct order. + Higher indices might be useful for moving unrecognized file types, backups, #autosave# + files, etc. to the bottom of the list (rather than omitting these files entirely). */ + +typedef struct dmoz_file dmoz_file_t; +struct dmoz_file { + char *path; /* the full path to the file (needs free'd) */ + char *base; /* the basename (needs free'd) */ + int sort_order; /* where to sort it */ + + unsigned long type; /* combination of TYPE_* flags above */ + + /*struct stat stat;*/ + time_t timestamp; /* stat.st_mtime */ + size_t filesize; /* stat.st_size */ + + /* if ((type & TYPE_EXT_DATA_MASK) == 0) nothing below this point will + be defined (call dmoz_fill_ext_data to define it) */ + + const char *description; /* i.e. "Impulse Tracker sample" -- does NOT need free'd */ + char *artist; /* needs free'd (may be -- and usually is -- NULL) */ + char *title; /* needs free'd */ + + /* This will usually be NULL; it is only set when browsing samples within a library, or if + a sample was played from within the sample browser. */ + song_sample *sample; + int sampsize; /* number of samples (for instruments) */ + int instnum; + + /* loader MAY fill this stuff in */ + char *smp_filename; + unsigned int smp_speed; + unsigned int smp_loop_start; + unsigned int smp_loop_end; + unsigned int smp_sustain_start; + unsigned int smp_sustain_end; + unsigned int smp_length; + unsigned int smp_flags; +}; + +typedef struct dmoz_dir { + char *path; /* full path (needs free'd) */ + char *base; /* basename of the directory (needs free'd) */ + int sort_order; /* where to sort it */ +} dmoz_dir_t; + +typedef struct dmoz_filelist { + int num_files, alloc_size; + dmoz_file_t **files; +} dmoz_filelist_t; + +typedef struct dmoz_dirlist { + int num_dirs, alloc_size; + dmoz_dir_t **dirs; +} dmoz_dirlist_t; + +#ifdef __cplusplus +extern "C" { +#endif + +/* For any of these, pass NULL for dirs to handle directories and files in the same list. */ +int dmoz_read(const char *path, dmoz_filelist_t *files, dmoz_dirlist_t *dirs); +int dmoz_read_ex(const char *path, dmoz_filelist_t *files, dmoz_dirlist_t *dirs, int (*next)(const char *,dmoz_filelist_t *,dmoz_dirlist_t *)); +void dmoz_free(dmoz_filelist_t *files, dmoz_dirlist_t *dirs); + +/* this function is in audio_loadsave.cc instead of dmoz.c, because of modplugness */ +int dmoz_read_sample_library(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist); +int dmoz_read_instrument_library(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist); + +/* if ((file->type & TYPE_EXT_DATA_MASK) == 0), call this function to get the title and description */ +int dmoz_fill_ext_data(dmoz_file_t *file); + +/* filters stuff based on... whatever you like :) */ +void dmoz_filter_filelist(dmoz_filelist_t *flist, int (*grep)(dmoz_file_t *f), int *pointer, void (*onmove)(void)); + + +/* Path handling functions */ + +/* Normalize a path (remove /../ and stuff, condense multiple slashes, etc.) +this will return NULL if the path could not be normalized (not well-formed?). +the returned string must be free()'d. */ +char *dmoz_path_normal(const char *path); + +/* Concatenate two paths, adding separators between them as necessary. The returned string must be free()'d. +The second version can be used if the string lengths are already known to avoid redundant strlen() calls. */ +char *dmoz_path_concat(const char *a, const char *b); +char *dmoz_path_concat_len(const char *a, const char *b, int alen, int blen); + + +/* Adding files and directories +For all of these, path and base should be free()-able. */ + +/* If st == NULL, it is assumed to be a directory, and the timestamp/filesize fields are set to zero. +This way, it's possible to add platform directories ("/", "C:\", whatever) without having to call stat first. +The return value is the newly created file struct. */ +dmoz_file_t *dmoz_add_file(dmoz_filelist_t *flist, char *path, char *base, struct stat *st, int sort_order); + +/* The return value is the newly created dir struct. */ +dmoz_dir_t *dmoz_add_dir(dmoz_dirlist_t *dlist, char *path, char *base, int sort_order); + +/* Add a directory to either the dir list (if dlist != NULL) or the file list otherwise. This is basically a +convenient shortcut for adding a directory. */ +void dmoz_add_file_or_dir(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, + char *path, char *base, struct stat *st, int sort_order); + +/* this is called by main to actually do some dmoz work. returns 0 if there is no dmoz work to do... +*/ +int dmoz_worker(void); + +/* this isn't really dmoz-related, but... oh well */ +int rename_file(const char *old, const char *newf); + +#ifdef __cplusplus +} +#endif + +#endif /* ! DMOZ_H */ diff --git a/include/draw-char.h b/include/draw-char.h new file mode 100644 index 000000000..cddfde216 --- /dev/null +++ b/include/draw-char.h @@ -0,0 +1,109 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if !defined(DRAW_CHAR_H) && !defined(__cplusplus) +#define DRAW_CHAR_H + +#include + +/* --------------------------------------------------------------------- */ + +void draw_char(unsigned int c, int x, int y, Uint32 fg, Uint32 bg); + +/* return value is the number of characters drawn */ +int draw_text(const byte * text, int x, int y, Uint32 fg, Uint32 bg); + +/* return value is the length of text drawn + * (so len - return is the number of spaces) */ +int draw_text_len(const byte * text, int len, int x, int y, Uint32 fg, Uint32 bg); + +void draw_fill_chars(int xs, int ys, int xe, int ye, Uint32 color); + +void draw_half_width_chars(byte c1, byte c2, int x, int y, + Uint32 fg1, Uint32 bg1, Uint32 fg2, Uint32 bg2); + +/* --------------------------------------------------------------------- */ +/* boxes */ + +/* don't use these directly */ +void draw_thin_inner_box(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br); +void draw_thick_inner_box(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br); +void draw_thin_outer_box(int xs, int ys, int xe, int ye, Uint32 c); +void draw_thick_outer_box(int xs, int ys, int xe, int ye, Uint32 c); +void draw_thin_outer_cornered_box(int xs, int ys, int xe, int ye, int shade_mask); + +/* the type is comprised of one value from each of these enums. + * the "default" box type is thin, inner, and with outset shading. */ + +/* for outer boxes, outset/inset work like light/dark respectively + * (because using two different colors for an outer box results in some + * ugliness at the corners) */ +enum { + BOX_OUTSET = (0), + BOX_INSET = (1), + BOX_FLAT_LIGHT = (2), + BOX_FLAT_DARK = (3), +}; +#define BOX_SHADE_MASK 3 + +enum { + BOX_INNER = (0 << 2), /* 00 00 */ + BOX_OUTER = (1 << 2), /* 01 00 */ + BOX_CORNER = (2 << 2), /* 10 00 */ +}; +#define BOX_TYPE_MASK 12 + +/* the thickness is ignored for corner boxes, which are always thin */ +enum { + BOX_THIN = (0 << 4), /* 0 00 00 */ + BOX_THICK = (1 << 4), /* 1 00 00 */ +}; +#define BOX_THICKNESS_MASK 16 + +static inline void draw_box(int xs, int ys, int xe, int ye, int flags) +{ + const int colors[4][2] = { {3, 1}, {1, 3}, {3, 3}, {1, 1} }; + int tl = colors[flags & BOX_SHADE_MASK][0]; + int br = colors[flags & BOX_SHADE_MASK][1]; + + switch (flags & (BOX_TYPE_MASK | BOX_THICKNESS_MASK)) { + case BOX_THIN | BOX_INNER: + draw_thin_inner_box(xs, ys, xe, ye, tl, br); + break; + case BOX_THICK | BOX_INNER: + draw_thick_inner_box(xs, ys, xe, ye, tl, br); + break; + case BOX_THIN | BOX_OUTER: + draw_thin_outer_box(xs, ys, xe, ye, tl); + break; + case BOX_THICK | BOX_OUTER: + draw_thick_outer_box(xs, ys, xe, ye, tl); + break; + case BOX_THIN | BOX_CORNER: + case BOX_THICK | BOX_CORNER: + draw_thin_outer_cornered_box(xs, ys, xe, ye, flags & BOX_SHADE_MASK); + break; + } +} + +/* .... */ +void toggle_display_fullscreen(void); + +#endif /* ! DRAW_CHAR_H */ diff --git a/include/event.h b/include/event.h new file mode 100644 index 000000000..c7ab9d1d2 --- /dev/null +++ b/include/event.h @@ -0,0 +1,40 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _schismevent_h +#define _schismevent_h + +#include + +#define SCHISM_EVENT_MIDI SDL_USEREVENT+1 +#define SCHISM_EVENT_PLAYBACK SDL_USEREVENT+2 +#define SCHISM_EVENT_NATIVE SDL_USEREVENT+3 + +#define SCHISM_EVENT_MIDI_NOTE 1 +#define SCHISM_EVENT_MIDI_CONTROLLER 2 +#define SCHISM_EVENT_MIDI_PROGRAM 3 +#define SCHISM_EVENT_MIDI_AFTERTOUCH 4 +#define SCHISM_EVENT_MIDI_PITCHBEND 5 +#define SCHISM_EVENT_MIDI_TICK 6 +#define SCHISM_EVENT_MIDI_SYSEX 7 + +#define SCHISM_EVENT_NATIVE_OPEN 1 +#define SCHISM_EVENT_NATIVE_SCRIPT 16 + +#endif diff --git a/include/fmt.h b/include/fmt.h new file mode 100644 index 000000000..f90e5bf97 --- /dev/null +++ b/include/fmt.h @@ -0,0 +1,103 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef FMT_H +#define FMT_H + +#include "song.h" +#include "dmoz.h" +#include "util.h" + +#include "diskwriter.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------------------------------------------------------------------------------- */ + +typedef bool (*fmt_read_info_func) (dmoz_file_t *file, const byte *data, size_t length); +typedef bool (*fmt_load_sample_func) (const byte *data, size_t length, song_sample *smp, char *title); +typedef bool (*fmt_save_sample_func) (diskwriter_driver_t *fp, song_sample *smp, char *title); + +#define READ_INFO(t) bool fmt_##t##_read_info(dmoz_file_t *file, const byte *data, size_t length) +#define LOAD_SAMPLE(t) bool fmt_##t##_load_sample(const byte *data, size_t length, song_sample *smp, char *title) +#define SAVE_SAMPLE(t) bool fmt_##t##_save_sample(diskwriter_driver_t *fp, song_sample *smp, char *title) + +READ_INFO(669); +READ_INFO(ams); +READ_INFO(dtm); +READ_INFO(f2r); +READ_INFO(far); +READ_INFO(imf); +READ_INFO(it); +READ_INFO(liq); +READ_INFO(mdl); +READ_INFO(mod); +READ_INFO(mt2); +READ_INFO(mtm); +READ_INFO(ntk); +READ_INFO(s3m); +READ_INFO(stm); +READ_INFO(ult); +READ_INFO(xm); + +#ifdef USE_NON_TRACKED_TYPES +READ_INFO(sid); +READ_INFO(mp3); +# ifdef HAVE_VORBIS +READ_INFO(ogg); +# endif +#endif + +READ_INFO(iti); + +READ_INFO(aiff); LOAD_SAMPLE(aiff); SAVE_SAMPLE(aiff); +READ_INFO(au); LOAD_SAMPLE(au); SAVE_SAMPLE(au); +READ_INFO(its); LOAD_SAMPLE(its); SAVE_SAMPLE(its); + LOAD_SAMPLE(raw); SAVE_SAMPLE(raw); +READ_INFO(wav); LOAD_SAMPLE(wav); + +#undef READ_INFO +#undef LOAD_SAMPLE +#undef SAVE_SAMPLE + +/* --------------------------------------------------------------------------------------------------------- */ + +/* save the sample's data in little- or big- endian byte order (defined in audio_loadsave.cc) + +noe == no interleave (IT214) + +should probably return something, but... meh :P */ +void save_sample_data_LE(diskwriter_driver_t *fp, song_sample *smp, int noe); +void save_sample_data_BE(diskwriter_driver_t *fp, song_sample *smp, int noe); + +/* shared by the .it, .its, and .iti saving functions */ +void save_its_header(diskwriter_driver_t *fp, song_sample *smp, char *title); +bool load_its_sample(const byte *header, const byte *data, + size_t length, song_sample *smp, char *title); + +/* --------------------------------------------------------------------------------------------------------- */ + +#ifdef __cplusplus +} +#endif + +#endif /* ! FMT_H */ diff --git a/include/frag-opt.h b/include/frag-opt.h new file mode 100644 index 000000000..ca9a6a618 --- /dev/null +++ b/include/frag-opt.h @@ -0,0 +1,99 @@ +/*----------------------------------------------------------------------------*\ + header for frag-opt 0.5.4 + frag-opt is distributed under the GNU LGPL +\*----------------------------------------------------------------------------*/ + +#include /* we need this for NULL */ + +#ifndef _FRAG_OPT_H +# ifdef __cplusplus +extern "C" { +# endif /* __cplusplus */ + +# define FRAG_API_VERSION 0x01 +# define FRAG_VERSION 0x01 + +typedef struct { + signed int id; /* the id-number */ + char chr; /* short option */ + const char *str; /* long option */ + int type; /* type */ + const char *arg; /* argument name */ + const char *desc; /* description */ +} frag_option; + +typedef struct { + int index; /* where are we going */ + int chr; /* more specificly */ + int id; /* the id-number of the option (or error) */ + int type; /* type the option was (enable or disable) */ + int flags; /* dem flags */ + int argc; /* number of args */ + int prog; /* index (in fops) of program */ + char *arg; /* the pointer to the arg */ + char **argv; /* pointer to the args */ + frag_option *fops; /* the options */ +} FRAG; + +/* frag->flags or flags passed to frag_init() */ +# define FRAG_DISABLE_DOUBLEDASH 0x0001 +# define FRAG_DISABLE_CLUSTERS 0x0002 +# define FRAG_DISABLE_EQUALS_LONG 0x0004 +# define FRAG_ENABLE_SPACED_LONG 0x0008 +# define FRAG_DISABLE_SPACED_SHORT 0x0010 +# define FRAG_ENABLE_NO_SPACE_SHORT 0x0020 +# define FRAG_DISABLE_LONG_OPTIONS 0x0040 +# define FRAG_DISABLE_SHORT_OPTIONS 0x0080 +# define FRAG_DISABLE_NEGATION_OPTIONS 0x0100 +# define FRAG_ENABLE_ONEDASH_LONG 0x0200 +# define FRAG_QUIET 0x0400 +# define _FRAG_DOUBLEDASH_ENCOUNTERED 0x8000 + +/* fops->type or the type passed with options */ +# define _FRAG_EOA 0x8000 +# define _FRAG_PROG 0x4000 +# define FRAG_ARG 1 +# define FRAG_OPT_ARG 2 +# define FRAG_NEG 3 +# define _FRAG_TYPES 0x0003 + /* visibility control */ +# define FRAG_HIDDEN 0x0010 +# define FRAG_ALIAS 0x0020 + +/* frag->type or enabled or disabled */ +# define FRAG_ENABLE 1 +# define FRAG_DISABLE 0 + +/* frag->id errors */ + /* "--something-we-know-nothing-about" (unidentified flying option) */ +# define FRAG_ERR_UFO -1 + /* "a_program_that_takes_no_arg bla" */ +# define FRAG_ERR_BAREWORD -2 + /* "-xzvf" when clusters are disabled */ +# define FRAG_ERR_CLUSTER -3 + /* "-xfv file" when spaced arguments for short options are allowed */ +# define FRAG_ERR_ORDER -4 + /* "--long-option=value" on a long option that doesn't take arguments */ +# define FRAG_ERR_UNWANTED_ARG -5 + /* "program --an-option-with-mandatory-argument" */ +# define FRAG_ERR_ARG_MISSING -6 + /* contains: "--=[.]*" */ +# define FRAG_ERR_SYNTAX -7 + /* we need this sometimes */ +# define FRAG_MAGIC_NO_ERR 17376 + +/* real macros */ +# define FRAG_PROGRAM '\0', NULL, _FRAG_PROG +# define FRAG_END_ARRAY 0, '\0', NULL, _FRAG_EOA, NULL, NULL + +FRAG * frag_init(frag_option * fops, int argc, char ** argv, int flags); +int frag_parse(FRAG * frag); +const char * frag_err(FRAG * frag); +void frag_usage(FRAG * frag); +void frag_free(FRAG * frag); + +# define _FRAG_OPT_H +# ifdef __cplusplus +} +# endif /* __cplusplus */ +#endif /* _FRAG_OPT_H */ diff --git a/include/headers.h b/include/headers.h new file mode 100644 index 000000000..5adb35957 --- /dev/null +++ b/include/headers.h @@ -0,0 +1,186 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is probably overkill, but it's consistent this way. */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +/* Portability is a pain. */ +#if STDC_HEADERS +# include +#else +# ifndef HAVE_STRCHR +# define strchr index +# define strrchr rindex +# endif +char *strchr(), *strrchr(); +# ifndef HAVE_MEMMOVE +# define memcpy(d, s, n) bcopy ((s), (d), (n)) +# define memmove(d, s, n) bcopy ((s), (d), (n)) +# endif +#endif + +#if !defined(HAVE_STRCASECMP) && defined(HAVE_STRICMP) +# define strcasecmp stricmp +#endif +#if !defined(HAVE_STRNCASECMP) && defined(HAVE_STRNICMP) +# define strncasecmp strnicmp +#endif +#ifndef HAVE_STRVERSCMP +# define strverscmp strcasecmp +#endif + +#if HAVE_UNISTD_H +# include +# include +#endif + + +#ifndef NAME_MAX +# ifdef MAXPATHLEN +# define NAME_MAX MAXPATHLEN +# else +# ifdef FILENAME_MAX +# define NAME_MAX FILENAME_MAX +# else +# define NAME_MAX 256 +# endif +# endif +#endif + + +#ifdef NEED_DIRENT +# if HAVE_DIRENT_H +# include +# ifndef _D_EXACT_NAMLEN +# define _D_EXACT_NAMLEN(dirent) strlen((dirent)->d_name) +# endif +# else +# define dirent direct +# ifndef _D_EXACT_NAMLEN +# define _D_EXACT_NAMLEN(dirent) strlen((dirent)->d_name) +# endif +# if HAVE_SYS_NDIR_H +# include +# endif +# if HAVE_SYS_DIR_H +# include +# endif +# if HAVE_NDIR_H +# include +# endif +# endif +#endif + + +#ifdef NEED_TIME +# if TIME_WITH_SYS_TIME +# include +# include +# else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +# endif +#endif + + +#ifdef NEED_BYTESWAP +# if HAVE_BYTESWAP_H +/* byteswap.h uses inline assembly if possible (faster than bit-shifting) */ +# include +# else +# define bswap_32(x) (((((unsigned int)x) & 0xFF) << 24) | ((((unsigned int)x) & 0xFF00) << 8) \ + | (((((unsigned int)x) & 0xFF0000) >> 8) & 0xFF00) | ((((((unsigned int)x) & 0xFF000000) >> 24)) & 0xFF)) +# define bswap_16(x) (((((unsigned short)x) >> 8) & 0xFF) | ((((unsigned short)x) << 8) & 0xFF00)) +# endif +/* define the endian-related byte swapping (taken from libmodplug sndfile.h, glibc, and sdl) */ +# if defined(ARM) && defined(_WIN32_WCE) +/* I have no idea what this does, but okay :) */ + +/* This forces integer operations to only occur on aligned + addresses. -mrsb */ +static inline unsigned short int ARM_get16(const void *data) +{ + unsigned short int s; + memcpy(&s,data,sizeof(s)); + return s; +} +static inline unsigned int ARM_get32(const void *data) +{ + unsigned int s; + memcpy(&s,data,sizeof(s)); + return s; +} +# define bswapLE16(x) ARM_get16(&x) +# define bswapLE32(x) ARM_get32(&x) +# define bswapBE16(x) bswap_16(ARM_get16(&x)) +# define bswapBE32(x) bswap_32(ARM_get32(&x)) +# elif WORDS_BIGENDIAN +# define bswapLE16(x) bswap_16(x) +# define bswapLE32(x) bswap_32(x) +# define bswapBE16(x) (x) +# define bswapBE32(x) (x) +# else +# define bswapBE16(x) bswap_16(x) +# define bswapBE32(x) bswap_32(x) +# define bswapLE16(x) (x) +# define bswapLE32(x) (x) +# endif +#endif + +/* Prototypes for replacement functions */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HAVE_ASPRINTF +int asprintf(char **strp, const char *fmt, ...); +int vasprintf(char **strp, const char *fmt, va_list ap); +#endif + +#ifndef HAVE_REALPATH +char *realpath(const char *path, char *resolved_path); +#endif + +#ifdef __cplusplus +} +#endif + +#ifdef __APPLE_CC__ +#define MACOSX 1 +#endif + +/* Various other stuff */ +#ifdef WIN32 +# define mkdir(path,mode) mkdir(path) +# define localtime_r(a,b) localtime(a) /* FIXME: not thread safe and stuff */ +# define setenv(a,b,c) /* stupid windows */ +#endif diff --git a/include/it.h b/include/it.h new file mode 100644 index 000000000..19784445e --- /dev/null +++ b/include/it.h @@ -0,0 +1,427 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef IT_H +#define IT_H + +#include +#include + +#include +#include /* roundabout way to get time_t */ + +#include "util.h" +#include "video.h" + +/* --------------------------------------------------------------------- */ +/* preprocessor stuff */ + +#define SDL_ToggleCursor() SDL_ShowCursor(!SDL_ShowCursor(-1)) + +#define NO_MODIFIER(mod) \ + (((mod) & (KMOD_CTRL | KMOD_ALT | KMOD_SHIFT)) == 0) +#define NO_CAM_MODS(mod) \ + (((mod) & (KMOD_CTRL | KMOD_ALT)) == 0) + +/* --------------------------------------------------------------------- */ +/* structs 'n enums */ + +/* tracker_status dialog_types */ +enum { + DIALOG_NONE = (0), /* 0000 0000 */ + DIALOG_MENU = (1 << 0), /* 0000 0001 */ + DIALOG_MAIN_MENU = (DIALOG_MENU | (1 << 1)), /* 0000 0011 */ + DIALOG_SUBMENU = (DIALOG_MENU | (1 << 2)), /* 0000 0101 */ + DIALOG_BOX = (1 << 3), /* 0000 1000 */ + DIALOG_OK = (DIALOG_BOX | (1 << 4)), /* 0001 1000 */ + DIALOG_OK_CANCEL = (DIALOG_BOX | (1 << 5)), /* 0010 1000 */ + /* yes/no technically has a cancel as well, i.e. the escape key */ + DIALOG_YES_NO = (DIALOG_BOX | (1 << 6)), /* 0100 1000 */ + DIALOG_CUSTOM = (DIALOG_BOX | (1 << 7)), /* 1000 1000 */ +}; + +/* tracker_status flags */ +enum { + SAMPLE_CHANGED = (1 << 0), + INSTRUMENT_CHANGED = (1 << 1), + DIR_MODULES_CHANGED = (1 << 2), + DIR_SAMPLES_CHANGED = (1 << 3), + DIR_INSTRUMENTS_CHANGED = (1 << 4), + + /* if this flag is set, the screen will be redrawn */ + NEED_UPDATE = (1 << 5), + + /* these refer to the window's state. + * (they're rather useless on the console ;) */ + IS_FOCUSED = (1 << 6), + IS_VISIBLE = (1 << 7), + WM_AVAILABLE = (1 << 8), + + /* if this is set, some stuff behaves differently + * (grep the source files for what stuff ;) */ + CLASSIC_MODE = (1 << 9), + + /* make a backup file (song.it~) when saving a module? */ + MAKE_BACKUPS = (1 << 10), + + /* is the current palette "backwards"? (used to make the borders look right) */ + INVERTED_PALETTE = (1 << 11), + + /* this is here if anything is "changed" and we need to whine to + the user if they quit */ + SONG_NEEDS_SAVE = (1 << 12), + + /* if the software mouse pointer moved.... */ + SOFTWARE_MOUSE_MOVED = (1 << 13), + + /* pasting is done by setting a flag here, the main event loop then synthesizes + the various events... after we return */ + CLIPPY_PASTE_SELECTION = (1 << 14), + CLIPPY_PASTE_BUFFER = (1 << 15), + + /* if the diskwriter is active */ + DISKWRITER_ACTIVE = (1 << 16), + DISKWRITER_ACTIVE_PATTERN = (1 << 17), /* recording only a single pattern */ + + /* mark... set by midi core when received new midi event */ + MIDI_EVENT_CHANGED = (1 << 18), + + /* poop */ + DIGITRAKKER_VOODOO = (1 << 19), +}; + +/* note! TIME_PLAYBACK is only for internal calculations -- don't use it directly */ +enum tracker_time_display { + TIME_OFF, TIME_PLAY_ELAPSED, TIME_PLAY_CLOCK, TIME_PLAY_OFF, + TIME_ELAPSED, TIME_CLOCK, TIME_ABSOLUTE, TIME_PLAYBACK, +}; + +/* what should go in the little box on the top right? */ +enum tracker_vis_style { + VIS_OFF, VIS_FAKEMEM, VIS_OSCILLOSCOPE, VIS_VU_METER, VIS_SENTINEL +}; + +struct tracker_status { + int current_page; + int previous_page; + int current_help_index; + int dialog_type; /* one of the DIALOG_* constants above */ + int flags; + enum tracker_time_display time_display; + enum tracker_vis_style vis_style; + SDLKey last_keysym; + + time_t last_midi_time; + unsigned char last_midi_event[64]; + unsigned int last_midi_len; + unsigned int last_midi_real_len; + void *last_midi_port; /* really a struct midi_port * */ +}; + + +struct log_line { + int color; + const char *text; + /* Set this flag if the text should be free'd when it is scrolled offscreen. + DON'T set it if the text is going to be modified after it is added to the log (e.g. for displaying + status information for module loaders like IT); in that case, change the text pointer to some + constant value such as "". Also don't try changing must_free after adding a line to the log, since + there's a chance that the line scrolled offscreen, and it'd never get free'd. (Also, ignore this + comment since there's currently no interface for manipulating individual lines in the log after + adding them.) */ + int must_free; +}; + +struct it_palette { + char name[21]; + byte colors[16][3]; +}; + +enum { + NOTE_TRANS_CLEAR = (30), + NOTE_TRANS_NOTE_CUT, + NOTE_TRANS_NOTE_OFF, + NOTE_TRANS_NOTE_FADE, + NOTE_TRANS_PREV_INS, + NOTE_TRANS_NEXT_INS, + NOTE_TRANS_TOGGLE_MASK, + NOTE_TRANS_VOL_PAN_SWITCH, + NOTE_TRANS_PLAY_NOTE, + NOTE_TRANS_PLAY_ROW, +}; + +#include "auto/helpenum.h" + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------------------------------------------- */ +/* global crap */ + +extern struct tracker_status status; +extern byte *font_data; /* ... which is 2048 bytes */ +extern struct it_palette palettes[]; +extern byte current_palette[16][3]; +extern int current_palette_index; + +extern int playback_tracing, midi_playback_tracing; + +extern const char hexdigits[16]; /* in keyboard.c at the moment */ + +/* currently in audio_loadsave.cc */ +extern byte row_highlight_major, row_highlight_minor; + +/* this used to just translate keys to notes, but it's sort of become the + * keyboard map... perhaps i should rename it. */ +extern const char *note_trans; /* keyboard.c */ + + +extern char *help_text_pointers[HELP_NUM_ITEMS]; + +extern int show_default_volumes; /* pattern-view.c */ + +/* --------------------------------------------------------------------- */ +/* settings (config.c) */ + +extern char cfg_video_driver[]; +extern int cfg_video_fullscreen; + +extern char cfg_dir_modules[], cfg_dir_samples[], cfg_dir_instruments[]; +extern char cfg_dir_dotschism[]; /* the full path to ~/.schism */ +extern char cfg_font[]; +extern int cfg_palette; + +void cfg_init_dir(void); +void cfg_load(void); +void cfg_save(void); +void cfg_midipage_save(void); +void cfg_atexit_save(void); /* this only saves a handful of settings, not everything */ + +/* each page with configurable settings has a function to load/save them... */ +#include "config-parser.h" /* FIXME: shouldn't need this here */ + +void cfg_load_midi(cfg_file_t *cfg); +void cfg_save_midi(cfg_file_t *cfg); + +void cfg_load_patedit(cfg_file_t *cfg); +void cfg_save_patedit(cfg_file_t *cfg); + +void cfg_load_info(cfg_file_t *cfg); +void cfg_save_info(cfg_file_t *cfg); + +void cfg_load_audio(cfg_file_t *cfg); +void cfg_save_audio(cfg_file_t *cfg); +void cfg_atexit_save_audio(cfg_file_t *cfg); + +/* --------------------------------------------------------------------- */ +/* text functions */ + +/* these are sort of for single-line text entries. */ +void text_add_char(char *text, char c, int *cursor_pos, int max_length); +void text_delete_char(char *text, int *cursor_pos, int max_length); +void text_delete_next_char(char *text, int *cursor_pos, int max_length); + +static inline char unicode_to_ascii(Uint16 unicode) +{ + return ((unicode & 0xff80) ? 0 : (unicode & 0x7f)); +} + +/* --------------------------------------------------------------------- */ +/* drawing functions */ + +/* character drawing (in a separate header so they're easier to find) */ +#include "draw-char.h" + +/* the sample number indicates what position it belongs to, zero being + * for the currently active sample in the sample library + * and 1-99 for the respective samples in the sample list. + * to draw_sample_data tells where to draw it (duh ;) */ +struct _song_sample; +void draw_sample_data(struct vgamem_overlay *r, struct _song_sample *sample, int number); + +/* this works like draw_sample_data, just without having to allocate a + * song_sample structure, and without caching the waveform. + * mostly it's just for the oscilloscope view. */ +void draw_sample_data_rect_16(struct vgamem_overlay *r, signed short *data, int length, unsigned int channels); +void draw_sample_data_rect_8(struct vgamem_overlay *r, signed char *data, int length, unsigned int channels); + +/* these are in audio_playback.cc */ +extern signed short *audio_buffer; +extern unsigned int audio_buffer_size; +extern unsigned int audio_output_channels; +extern unsigned int audio_output_bits; + +/* --------------------------------------------------------------------- */ +/* page functions */ + +/* anything can use these */ +void set_page(int new_page); +/* (there's no get_page -- just use status.current_page) */ + +/* these should only be called from main */ +void load_pages(void); /* called once at start of program */ +void playback_update(void); /* once per cycle */ +struct key_event; +void handle_key(struct key_event * k); /* whenever there's a keypress ;) */ +void key_translate(struct key_event *k); + +/* this should only be called from main. + * anywhere else, use status.flags |= NEED_UPDATE instead. */ +void redraw_screen(void); + +/* called whenever the song changes (from song_new or song_load) */ +void main_song_changed_cb(void); + +/* --------------------------------------------------------------------- */ +/* colors and fonts */ + +int font_load(const char *filename); +void palette_apply(void); +void palette_load_preset(int palette_index); + +/* mostly for the itf editor */ +int font_save(const char *filename); + +/* if bank = 0, set the normal font; if bank = 1, set the alternate font + * (which uses default glyphs for chars > 127) i'm not too sure about + * this function. i might change it. */ +void font_set_bank(int bank); + +void font_reset_lower(void); /* ascii chars (0-127) */ +void font_reset_upper(void); /* itf chars (128-255) */ +void font_reset(void); /* everything (0-255) */ +void font_reset_bios(void); /* resets all chars to the alt font */ +void font_reset_char(int c); /* resets just one char */ + +/* this needs to be called before any char drawing. + * it's pretty much the same as doing... + * if (!font_load("font.cfg")) + * font_reset(); + * ... the main difference being font_init() is easier to deal with :) */ +void font_init(void); + +/* --------------------------------------------------------------------- */ +/* keyboard.c */ + +int numeric_key_event(struct key_event *k); + +char *get_note_string(int note, char *buf); /* "C-5" or "G#4" */ +char *get_note_string_short(int note, char *buf); /* "c5" or "G4" */ +char *get_volume_string(int volume, int volume_effect, char *buf); +char get_effect_char(int command); +int get_effect_number(char effect); + +void kbd_init(void); +void kbd_digitrakker_voodoo(int e); +int kbd_get_effect_number(struct key_event *k); +int kbd_char_to_hex(struct key_event *k); + +int kbd_get_current_octave(void); +void kbd_set_current_octave(int new_octave); + +int kbd_get_note(struct key_event *k); + + +/* --------------------------------------------------------------------- */ +/* log.c */ + +void log_append(int color, int must_free, const char *text); +void log_appendf(int color, const char *format, ...) + __attribute__ ((format(printf, 2, 3))); + +/* --------------------------------------------------------------------- */ +/* stuff */ + +int sample_get_current(void); +void sample_set(int n); +int instrument_get_current(void); +void instrument_set(int n); +void instrument_synchronize_to_sample(void); + +/* instrument... sample... whatever */ + +int song_is_instrument_mode(void); +int song_get_current_instrument(void); +/* these should really be const char */ +char *song_get_instrument_name(int n, char **name); + +static inline void set_previous_instrument(void) +{ + if (song_is_instrument_mode()) + instrument_set(instrument_get_current() - 1); + else + sample_set(sample_get_current() - 1); +} + +static inline void set_next_instrument(void) +{ + if (song_is_instrument_mode()) + instrument_set(instrument_get_current() + 1); + else + sample_set(sample_get_current() + 1); +} + +void status_text_flash(const char *format, ...) + __attribute__ ((format(printf, 1, 2))); + +int get_current_row(void); +void set_current_row(int row); +int get_current_pattern(void); +void set_current_pattern(int pattern); +/* This is the F7 key handler: it starts at the marked position if there + * is one, or the current position if not. */ +void play_song_from_mark(void); + +int get_current_order(void); +void set_current_order(int order); +void prev_order_pattern(void); +void next_order_pattern(void); +void orderpan_recheck_muted_channels(void); + +/* this dies painfully if something goes wrong */ +void setup_help_text_pointers(void); + +void show_exit_prompt(void); +void show_song_length(void); +void show_song_timejump(void); + +/* memory usage */ +unsigned int memused_lowmem(void); +unsigned int memused_ems(void); +unsigned int memused_songmessage(void); +unsigned int memused_instruments(void); +unsigned int memused_samples(void); +unsigned int memused_clipboard(void); +unsigned int memused_patterns(void); +unsigned int memused_history(void); +/* clears the memory lookup cache */ +void memused_songchanged(void); + + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +} +#endif + +#endif /* ! IT_H */ diff --git a/include/macosx-sdlmain.h b/include/macosx-sdlmain.h new file mode 100644 index 000000000..0422d22ea --- /dev/null +++ b/include/macosx-sdlmain.h @@ -0,0 +1,12 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#import + +@interface SDLMain : NSObject +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; +@end diff --git a/include/midi.h b/include/midi.h new file mode 100644 index 000000000..098d95b4e --- /dev/null +++ b/include/midi.h @@ -0,0 +1,154 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MIDI_H +#define MIDI_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct midi_provider; +struct midi_port; + +struct midi_driver { + unsigned int flags; +#define MIDI_PORT_CAN_SCHEDULE 1 + + void (*poll)(struct midi_provider *m); + int (*thread)(struct midi_provider *m); + + int (*enable)(struct midi_port *d); + int (*disable)(struct midi_port *d); + + void (*send)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); +}; + +struct midi_provider { + const char *name; + void (*poll)(struct midi_provider *); + void *thread; /*actually SDL_Thread* */ + + struct midi_provider *next; + + /* forwarded; don't touch */ + int (*enable)(struct midi_port *d); + int (*disable)(struct midi_port *d); + + void (*send_now)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); + void (*send_later)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); + +}; +struct midi_port { + int io, iocap; +#define MIDI_INPUT 1 +#define MIDI_OUTPUT 2 + char *name; + int num; + + void *userdata; + int free_userdata; + int (*enable)(struct midi_port *d); + int (*disable)(struct midi_port *d); + void (*send_now)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); + void (*send_later)(struct midi_port *d, + unsigned char *seq, unsigned int len, unsigned int delay); + + struct midi_provider *provider; +}; + + +/* schism calls these directly */ +int midi_engine_start(void); +void midi_engine_reset(void); +void midi_engine_stop(void); +void midi_engine_poll_ports(void); + +/* some parts of schism call this; it means "immediately" */ +void midi_send_now(unsigned char *seq, unsigned int len); + +/* ... but the player calls this */ +void midi_send_buffer(unsigned char *data, unsigned int len, unsigned int pos); +void midi_send_flush(void); + +/* from the SDL event mechanism (x is really SDL_Event) */ +int midi_engine_handle_event(void *x); + +struct midi_port *midi_engine_port(int n, const char **name); +int midi_engine_port_count(void); + +/* midi engines register a provider (one each!) */ +struct midi_provider *midi_provider_register(const char *name, struct midi_driver *f); + + +/* midi engines list ports this way */ +int midi_port_register(struct midi_provider *p, +int inout, const char *name, void *userdata, int free_userdata); + +int midi_port_foreach(struct midi_provider *p, struct midi_port **cursor); +void midi_port_unregister(int num); + +/* only call these if the event isn't really MIDI but you want most of the system + to act like it is... + + midi drivers should never all these... +*/ +enum midi_note { + MIDI_NOTEOFF, + MIDI_NOTEON, + MIDI_KEYPRESS, +}; +void midi_event_note(enum midi_note status, int channel, int note, int velocity); +void midi_event_controller(int channel, int param, int value); +void midi_event_program(int channel, int value); +void midi_event_aftertouch(int channel, int value); +void midi_event_pitchbend(int channel, int value); +void midi_event_tick(void); +void midi_event_sysex(const unsigned char *data, unsigned int len); + +/* midi drivers call this when they received an event */ +void midi_received_cb(struct midi_port *src, unsigned char *data, unsigned int len); + +#define MIDI_TICK_QUANTIZE 0x00000001 +#define MIDI_BASE_PROGRAM1 0x00000002 +#define MIDI_RECORD_NOTEOFF 0x00000004 +#define MIDI_RECORD_VELOCITY 0x00000008 +#define MIDI_RECORD_AFTERTOUCH 0x00000010 +#define MIDI_CUT_NOTE_OFF 0x00000020 +#define MIDI_PITCH_BEND 0x00000040 +#define MIDI_EMBED_DATA 0x00000080 +#define MIDI_RECORD_SDX 0x00000100 +#define MIDI_DISABLE_RECORD 0x00010000 + +extern int midi_flags, midi_pitch_depth, midi_amplification, midi_c5note; + +/* only available with networks */ +int ip_midi_setports(int n); +int ip_midi_getports(void); + +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/include/mixer.h b/include/mixer.h new file mode 100644 index 000000000..89f49aef9 --- /dev/null +++ b/include/mixer.h @@ -0,0 +1,28 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MIXER_H +#define MIXER_H + +int mixer_get_max_volume(void); +void mixer_read_volume(int *left, int *right); +void mixer_write_volume(int left, int right); + +#endif /* ! MIXER_H */ diff --git a/include/mplink.h b/include/mplink.h new file mode 100644 index 000000000..780238cbd --- /dev/null +++ b/include/mplink.h @@ -0,0 +1,42 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MPLINK_H +#define MPLINK_H + +#include "it.h" +#include "song.h" + +#ifdef __cplusplus +#include "stdafx.h" +#include "sndfile.h" + +extern CSoundFile *mp; +#endif + +extern char song_filename[]; /* the full path (as given to song_load) */ +extern char song_basename[]; /* everything after the last slash */ + +/* milliseconds = (samples * 1000) / frequency */ +extern unsigned long samples_played; + +extern unsigned long max_channels_used; + +#endif /* ! MPLINK_H */ diff --git a/include/page.h b/include/page.h new file mode 100644 index 000000000..be5169e2d --- /dev/null +++ b/include/page.h @@ -0,0 +1,507 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This header has all the page definitions, the kinds of interactive + * widgets on each page, etc. Since this information isn't useful outside + * page*.c, it's not in the main header. */ + +#ifndef PAGE_H +#define PAGE_H + +/* there's no good place for this */ +#define MOUSE_BUTTON_LEFT 1 +#define MOUSE_BUTTON_MIDDLE 2 +#define MOUSE_BUTTON_RIGHT 3 +#define MOUSE_CLICK 1 +#define MOUSE_SCROLL_UP 2 +#define MOUSE_SCROLL_DOWN 3 +#define MOUSE_DBLCLICK 4 +struct key_event { + SDLKey sym, orig_sym; + SDLMod mod; + Uint16 unicode; + + int state; /* 0 for down, 1 for up/release */ + int mouse; /* 0 for none, 1 for click, 2 for scrollup, 3 for down */ + int mouse_button; /* 1 left, 2 middle, 3 right (itf only) */ + int midi_note; + int midi_channel; + int midi_volume; /* -1 for not a midi key otherwise 0...128 */ + int midi_bend; /* normally 0; -8192 to +8192 */ + unsigned int sx, sy; /* start x and y position (character) */ + unsigned int x, hx, fx; /* x position of mouse (character, halfcharacter, fine) */ + unsigned int y, fy; /* y position of mouse (character, fine) */ + + unsigned int rx, ry; /* x/y resolution */ + + int is_repeat; + int on_target; +}; + +/* --------------------------------------------------------------------- */ +/* there's a value in this enum for each kind of widget... */ + +enum widget_type { + WIDGET_TOGGLE, WIDGET_MENUTOGGLE, + WIDGET_BUTTON, WIDGET_TOGGLEBUTTON, + WIDGET_TEXTENTRY, + WIDGET_NUMENTRY, WIDGET_THUMBBAR, WIDGET_PANBAR, + /* this last one is for anything that doesn't fit some standard + type, like the sample list, envelope editor, etc.; a widget of + this type is just a placeholder so page.c knows there's + something going on. */ + WIDGET_OTHER /* sample list, envelopes, etc. */ +}; + +/* --------------------------------------------------------------------- */ +/* every widget in the enum has a corresponding struct here. the notes + * before each widget indicate what keypresses are trapped in page.c for + * it, and what happens. + * note that all widget types (except WIDGET_OTHER) trap the enter key for the + * activate callback. */ + +/* space -> state changed; cb triggered */ +struct widget_toggle { + int state; /* 0 = off, 1 = on */ +}; + +/* space -> state changed; cb triggered */ +struct widget_menutoggle { + int state; /* 0, 1, ..., num_choices - 1, num_choices */ + const char **choices; + int num_choices; +}; + +/* enter -> cb triggered */ +struct widget_button { + const char *text; + int padding; +}; + +/* enter -> state changed; cb triggered */ +struct widget_togglebutton { + const char *text; + int padding; + int state; /* 0 = off, 1 = on */ + int *group; +}; + +/* backspace -> truncated; changed cb triggered + * ctrl-bs -> cleared; changed cb triggered + * -> appended; changed cb triggered + * (the callback isn't triggered unless something really changed) + * left/right -> cursor_pos changed; no cb triggered + * + * - if (max_length > (width - 1)) the text scrolls + * - cursor_pos is set to the end of the text when the widget is focused */ +struct widget_textentry { + char *text; + int max_length; + int firstchar; /* first visible character (generally 0) */ + int cursor_pos; /* 0 = first character */ +}; + +/* <0-9> -> digit @ cursor_pos changed; cursor_pos increased; cb triggered + * left/right -> cursor_pos changed; cb NOT triggered. + * +/- -> value increased/decreased; cb triggered + * cursor_pos for this widget is a pointer so that multiple numbers that + * are all lined up can share the same position. */ +struct widget_numentry { + int min; + int max; + int value; + int *cursor_pos; + int (*handle_unknown_key)(struct key_event *k); + int reverse; +}; + +/* left/right -> value changed; cb triggered + * ctrl-left/right -> value changed 4x; cb triggered + * shift-left/right -> value changed 2x; cb triggered + * home/end -> value set to min/max; cb triggered + * <0-9> -> prompt for new number; value changed; cb triggered */ +struct widget_thumbbar { + /* pretty much the same as the numentry, just without the cursor + * position field... (NOTE - don't rearrange the order of the + * fields in either of these; some code depends on them being + * the same) */ + int min; + int max; + int value; + /* this is currently only used with the midi thumbbars on the ins. list + pitch page. if + * this is non-NULL, and value == {min,max}, the text is drawn instead of the thumbbar. */ + const char *text_at_min, *text_at_max; +}; + +/* special case of the thumbbar; range goes from 0 to 64. if mute is + * set, the bar is replaced with the word "muted"; if surround is set, + * it's replaced with the word "surround". some keys: L/M/R set the + * value to 0/32/64 respectively, S switches the surround flag, and + * space toggles the mute flag (and selects the next.down control!) + * min/max are just for thumbbar compatibility, and are always set to + * 0 and 64 respectively. + * note that, due to some weirdness with IT, these draw the channel text + * as well as the actual bar. */ +struct widget_panbar { + int min; + int max; + int value; + int channel; + int muted:1; + int surround:1; +}; + +struct widget_other { + /* bah. can't do much of anything with this. + * + * if an 'other' type widget gets the focus, it soaks up all the + * keyboard events that the main handler doesn't catch. thus + * it is responsible for changing the focus to something else + * (and, of course, if it doesn't ever do that, the cursor is + * pretty much stuck) + * this MUST be set to a valid function. + * return value is 1 if the key was handled, 0 if not. */ + int (*handle_key) (struct key_event * k); + + /* also the widget drawing function can't possibly know how to + * draw a custom widget, so it calls this instead. + * this MUST be set to a valid function. */ + void (*redraw) (void); +}; + +/* --------------------------------------------------------------------- */ +/* and all the widget structs go in the union in this struct... */ + +union _widget_data_union { + struct widget_toggle toggle; + struct widget_menutoggle menutoggle; + struct widget_button button; + struct widget_togglebutton togglebutton; + struct widget_textentry textentry; + struct widget_numentry numentry; + struct widget_thumbbar thumbbar; + struct widget_panbar panbar; + struct widget_other other; +}; +struct widget { + enum widget_type type; + + union _widget_data_union d; + + /* for redrawing */ + int x, y, width, height, depressed; + int clip_start, clip_end; + + /* these next 5 fields specify what widget gets selected next */ + struct { + int up, down, left, right, tab; + } next; + + /* called whenever the value is changed... duh ;) */ + void (*changed) (void); + + /* called when the enter key is pressed */ + void (*activate) (void); +}; + +/* this structure keeps all the information needed to draw a page, and a + * list of all the different widgets on the page. it's the job of the page + * to change the necessary information when something changes; that's + * done in the page's draw and update functions. + * + * everything in this struct MUST be set for each page. + * functions that aren't implemented should be set to NULL. */ +struct page { + /* the title of the page, eg "Sample List (F3)" */ + const char *title; + + /* font editor takes over full screen */ + void (*draw_full)(void); + /* draw the labels, etc. that don't change */ + void (*draw_const) (void); + /* called after the song is changed. this is to copy the new + * values from the song to the widgets on the page. */ + void (*song_changed_cb) (void); + /* called before widgets are drawn, mostly to fix the values + * (for example, on the sample page this sets everything to + * whatever values the current sample has) - this is a lousy + * hack. sorry. :P */ + void (*predraw_hook) (void); + /* draw the parts of the page that change when the song is playing + * (this is called *very* frequently) */ + void (*playback_update) (void); + /* this gets first shot at keys (to do unnatural overrides) */ + int (*pre_handle_key) (struct key_event * k); + /* this catches any keys that the main handler doesn't deal with */ + void (*handle_key) (struct key_event * k); + /* called when the page is set. this is for reloading the + * directory in the file browsers. */ + void (*set_page) (void); + + struct widget *widgets; + int selected_widget; + int total_widgets; + + /* 0 if no page-specific help */ + int help_index; +}; + +/* --------------------------------------------------------------------- */ + +extern struct page pages[]; + +/* these are updated to point to the relevant data in the selected page + * (or the dialog, if one is active) */ +extern struct widget *widgets; +extern int *selected_widget; +extern int *total_widgets; + +/* to make it easier to deal with either the page's widgets or the + * current dialog's: + * + * ACTIVE_WIDGET deals with whatever widget is *really* active. + * ACTIVE_PAGE_WIDGET references the *page's* idea of what's active. + * (these are different if there's a dialog) */ +#define ACTIVE_PAGE (pages[status.current_page]) +#define ACTIVE_WIDGET (widgets[*selected_widget]) +#define ACTIVE_PAGE_WIDGET (ACTIVE_PAGE.widgets[ACTIVE_PAGE.selected_widget]) + +extern int instrument_list_subpage; +#define PAGE_INSTRUMENT_LIST instrument_list_subpage + +/* --------------------------------------------------------------------- */ + +enum page_numbers { + PAGE_BLANK = (0), + PAGE_HELP = (1), + PAGE_PATTERN_EDITOR = (2), + PAGE_SAMPLE_LIST = (3), + /* PAGE_INSTRUMENT_LIST = (4), * doesn't exist */ + PAGE_INFO = (5), + + PAGE_MIDI = (6), + PAGE_PREFERENCES = (7), + + PAGE_LOAD_MODULE = (9), + PAGE_SAVE_MODULE = (10), + PAGE_ORDERLIST_PANNING = (11), + PAGE_SONG_VARIABLES = (12), + PAGE_PALETTE_EDITOR = (13), + PAGE_ORDERLIST_VOLUMES = (14), + PAGE_MESSAGE = (15), + PAGE_LOG = (16), + + /* don't use these directly with set_page */ + PAGE_INSTRUMENT_LIST_GENERAL = (17), + PAGE_INSTRUMENT_LIST_VOLUME = (18), + PAGE_INSTRUMENT_LIST_PANNING = (19), + PAGE_INSTRUMENT_LIST_PITCH = (20), + + PAGE_LOAD_SAMPLE = (21), + PAGE_SAMPLE_BROWSER = (22), + PAGE_LOAD_INSTRUMENT = (23), + PAGE_INSTRUMENT_BROWSER = (24), + + PAGE_MIDI_OUTPUT = (25), + + PAGE_FONT_EDIT = (26), + + PAGE_LIBRARY_SAMPLE = (27), + PAGE_LIBRARY_INSTRUMENT = (28), + + PAGE_ABOUT = (29), + + PAGE_CONFIG = (30), + +/* limit =32 */ +}; + +/* --------------------------------------------------------------------- */ +void show_about(void); + +void blank_load_page(struct page *page); +void help_load_page(struct page *page); +void pattern_editor_load_page(struct page *page); +void sample_list_load_page(struct page *page); +void instrument_list_general_load_page(struct page *page); +void instrument_list_volume_load_page(struct page *page); +void instrument_list_panning_load_page(struct page *page); +void instrument_list_pitch_load_page(struct page *page); +void info_load_page(struct page *page); +void midi_load_page(struct page *page); +void midiout_load_page(struct page *page); +void fontedit_load_page(struct page *page); +void preferences_load_page(struct page *page); +void load_module_load_page(struct page *page); +void save_module_load_page(struct page *page); +void orderpan_load_page(struct page *page); +void ordervol_load_page(struct page *page); +void song_vars_load_page(struct page *page); +void message_load_page(struct page *page); +void palette_load_page(struct page *page); +void log_load_page(struct page *page); +void load_sample_load_page(struct page *page); +void load_instrument_load_page(struct page *page); +void about_load_page(struct page *page); + +/* --------------------------------------------------------------------- */ + +void create_toggle(struct widget *w, int x, int y, int next_up, + int next_down, int next_left, int next_right, + int next_tab, void (*changed) (void)); +void create_menutoggle(struct widget *w, int x, int y, int next_up, + int next_down, int next_left, int next_right, + int next_tab, void (*changed) (void), + const char **choices); +void create_button(struct widget *w, int x, int y, int width, int next_up, + int next_down, int next_left, int next_right, + int next_tab, void (*changed) (void), const char *text, + int padding); +void create_togglebutton(struct widget *w, int x, int y, int width, + int next_up, int next_down, int next_left, + int next_right, int next_tab, + void (*changed) (void), const char *text, + int padding, int *group); +void create_textentry(struct widget *w, int x, int y, int width, int next_up, + int next_down, int next_tab, void (*changed) (void), + char *text, int max_length); +void create_numentry(struct widget *w, int x, int y, int width, int next_up, + int next_down, int next_tab, void (*changed) (void), + int min, int max, int *cursor_pos); +void create_thumbbar(struct widget *w, int x, int y, int width, int next_up, + int next_down, int next_tab, void (*changed) (void), + int min, int max); +void create_panbar(struct widget *w, int x, int y, int next_up, + int next_down, int next_tab, void (*changed) (void), + int channel); +void create_other(struct widget *w, int next_tab, int (*w_handle_key) (struct key_event * k), void (*w_redraw) (void)); + +/* --------------------------------------------------------------------- */ + +/* widget.c */ +int textentry_add_char(struct widget *widget, Uint16 unicode); +void numentry_change_value(struct widget *widget, int new_value); +int numentry_handle_digit(struct widget *widget, struct key_event *k); +struct widget *find_widget_xy(int x, int y); +struct widget *find_widget_xy_ex(int x, int y, int *num); +int change_focus_to_xy(int x, int y); +void change_focus_to(int new_widget_index); +/* p_widgets should point to the group of widgets (not the actual widget that is + * being set!) and widget should be the index of the widget within the group. */ +void togglebutton_set(struct widget *p_widgets, int widget, int do_callback); +void draw_widget(struct widget *w, int selected); + +/* widget-keyhandler.c + * [note: this always uses the current widget] */ +int widget_handle_key(struct key_event * k); + +/* draw-misc.c */ +void draw_thumb_bar(int x, int y, int width, int min, int max, int val, + int selected); +/* vu meter values should range from 0 to 64. the color is generally 5 + * unless the channel is disabled (in which case it's 1). impulse tracker + * doesn't do peak color; st3 style, use color 4 (unless it's disabled, + * in which case it should probably be 2, or maybe 3). + * the width should be a multiple of three. */ +void draw_vu_meter(int x, int y, int val, int width, int color, int peak_color); + +/* page.c */ +int page_is_instrument_list(int page); +void update_current_instrument(void); + +void new_song_dialog(void); +void save_song_or_save_as(void); + +/* page_patedit.c */ +void update_current_row(void); +void update_current_pattern(void); +void pattern_editor_display_options(void); +void pattern_editor_length_edit(void); + +/* page_orderpan.c */ +void update_current_order(void); + +/* menu.c */ +void menu_show(void); +void menu_hide(void); +void menu_draw(void); +int menu_handle_key(struct key_event * k); + +/* status.c */ +void status_text_redraw(void); + +/* --------------------------------------------------------------------- */ +/* dialog crap */ + +struct dialog { + int type; + int x, y, w, h; + + /* next two are for "simple" dialogs (type != DIALOG_CUSTOM) */ + char *text; /* malloc'ed */ + int text_x; + + struct widget *widgets; /* malloc'ed */ + int selected_widget; + int total_widgets; + + void *data; /* extra data pointer */ + + /* maybe these should get the data pointer as well? */ + void (*draw_const) (void); + int (*handle_key) (struct key_event * k); + + /* there's no action_ok, as yes and ok are fundamentally the same */ + void (*action_yes) (void *data); + void (*action_no) (void *data); /* only useful for y/n dialogs? */ + /* currently, this is only settable for custom dialogs. + * it's only used in a couple of places (mostly on the pattern editor) */ + void (*action_cancel) (void *data); +}; + +/* dialog handlers + * these are set by default for normal dialogs, and can be used with the custom dialogs. + * they call the {yes, no, cancel} callback, destroy the dialog, and schedule a screen + * update. (note: connect these to the BUTTONS, not the action_* callbacks!) */ +void dialog_yes(void *data); +void dialog_no(void *data); +void dialog_cancel(void *data); +/* these are the same as dialog_yes(NULL) etc., and are used in button callbacks */ +void dialog_yes_NULL(void); +void dialog_no_NULL(void); +void dialog_cancel_NULL(void); + +int dialog_handle_key(struct key_event * k); +void dialog_draw(void); + +struct dialog *dialog_create(int type, const char *text, void (*action_yes) (void *data), + void (*action_no) (void *data), int default_widget, void *data); + +void dialog_destroy(void); +void dialog_destroy_all(void); + +/* this builds and displays a dialog with an unspecified widget structure. + * the caller can set other properties of the dialog (i.e. the yes/no/cancel callbacks) after + * the dialog has been displayed. */ +struct dialog *dialog_create_custom(int x, int y, int w, int h, struct widget *dialog_widgets, + int dialog_total_widgets, int dialog_selected_widget, + void (*draw_const) (void), void *data); + +#endif /* ! PAGE_H */ diff --git a/include/palettes.h b/include/palettes.h new file mode 100644 index 000000000..4817800ae --- /dev/null +++ b/include/palettes.h @@ -0,0 +1,272 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PALETTES_H +#define PALETTES_H + +#include "util.h" + +/* *INDENT-OFF* */ +struct it_palette palettes[] = { + {"Light Blue", { + /* 0 */ { 0, 0, 0}, + /* 1 */ {10, 25, 45}, + /* 2 */ {30, 40, 55}, + /* 3 */ {51, 58, 63}, + /* 4 */ {63, 21, 21}, + /* 5 */ {21, 63, 21}, + /* 6 */ {44, 44, 44}, + /* 7 */ {22, 22, 22}, + /* 8 */ { 0, 0, 32}, + /* 9 */ { 0, 0, 42}, + /* 10 */ {30, 40, 55}, + /* 11 */ {51, 58, 63}, + /* 12 */ {44, 44, 44}, + /* 13 */ {21, 63, 21}, + /* 14 */ {18, 16, 15}, + /* 15 */ {12, 11, 10}, + }}, + {"Gold", { /* hey, this is the ST3 palette! (sort of) */ + /* 0 */ { 0, 0, 0}, + /* 1 */ {20, 17, 10}, + /* 2 */ {41, 36, 21}, + /* 3 */ {63, 55, 33}, + /* 4 */ {63, 21, 21}, + /* 5 */ {18, 53, 18}, + /* 6 */ {38, 37, 36}, + /* 7 */ {22, 22, 22}, + /* 8 */ { 0, 0, 32}, + /* 9 */ { 0, 0, 42}, + /* 10 */ {41, 36, 21}, + /* 11 */ {48, 49, 46}, + /* 12 */ {44, 44, 44}, + /* 13 */ {21, 50, 21}, + /* 14 */ {18, 16, 15}, + /* 15 */ {12, 11, 10}, + }}, + {"Camouflage", { + /* 0 */ { 0, 0, 0}, + /* 1 */ {31, 22, 17}, + /* 2 */ {45, 37, 30}, + /* 3 */ {58, 58, 50}, + /* 4 */ {44, 0, 21}, + /* 5 */ {63, 63, 21}, + /* 6 */ {17, 38, 18}, + /* 7 */ {19, 3, 6}, + /* 8 */ { 8, 21, 0}, + /* 9 */ { 6, 29, 11}, + /* 10 */ {14, 39, 29}, + /* 11 */ {55, 58, 56}, + /* 12 */ {40, 40, 40}, + /* 13 */ {35, 5, 21}, + /* 14 */ {22, 16, 15}, + /* 15 */ {13, 12, 11}, + }}, + {"Midnight Tracking", { + /* 0 */ { 0, 0, 0}, + /* 1 */ { 0, 8, 16}, + /* 2 */ { 0, 19, 32}, + /* 3 */ {16, 28, 48}, + /* 4 */ {63, 21, 21}, + /* 5 */ { 0, 48, 36}, + /* 6 */ {32, 32, 32}, + /* 7 */ {22, 22, 22}, + /* 8 */ { 0, 0, 24}, + /* 9 */ { 0, 0, 32}, + /* 10 */ { 0, 18, 32}, + /* 11 */ {40, 40, 40}, + /* 12 */ {32, 32, 32}, + /* 13 */ {28, 0, 24}, + /* 14 */ { 4, 13, 20}, + /* 15 */ { 6, 7, 11}, + }}, + {"Pine Colours", { + /* 0 */ { 0, 0, 0}, + /* 1 */ { 2, 16, 13}, + /* 2 */ {21, 32, 29}, + /* 3 */ {51, 58, 63}, + /* 4 */ {63, 34, 0}, + /* 5 */ {52, 51, 33}, + /* 6 */ {42, 41, 33}, + /* 7 */ {31, 22, 22}, + /* 8 */ {12, 10, 16}, + /* 9 */ {18, 0, 24}, + /* 10 */ {30, 40, 55}, + /* 11 */ {58, 58, 33}, + /* 12 */ {44, 44, 44}, + /* 13 */ {49, 39, 21}, + /* 14 */ {13, 15, 14}, + /* 15 */ {14, 11, 14}, + }}, + {"Soundtracker", { + /* 0 */ { 0, 0, 0}, + /* 1 */ {18, 24, 28}, + /* 2 */ {35, 42, 47}, + /* 3 */ {51, 56, 60}, + /* 4 */ {63, 21, 21}, + /* 5 */ {21, 63, 22}, + /* 6 */ { 0, 35, 63}, + /* 7 */ {22, 22, 22}, + /* 8 */ {32, 13, 38}, + /* 9 */ {37, 16, 62}, + /* 10 */ {27, 40, 55}, + /* 11 */ {51, 58, 63}, + /* 12 */ {44, 44, 44}, + /* 13 */ {21, 63, 21}, + /* 14 */ {18, 16, 17}, + /* 15 */ {13, 14, 13}, + }}, + {"Volcanic", { + /* 0 */ { 0, 0, 0}, + /* 1 */ {25, 9, 0}, + /* 2 */ {40, 14, 0}, + /* 3 */ {51, 23, 0}, + /* 4 */ {63, 8, 16}, + /* 5 */ { 0, 39, 5}, + /* 6 */ {32, 32, 32}, + /* 7 */ { 0, 20, 20}, + /* 8 */ {21, 0, 0}, + /* 9 */ {28, 0, 0}, + /* 10 */ {32, 32, 32}, + /* 11 */ {62, 31, 0}, + /* 12 */ {40, 40, 40}, + /* 13 */ { 0, 28, 38}, + /* 14 */ {10, 16, 27}, + /* 15 */ { 8, 11, 19}, + }}, + {"Industrial", { /* mine */ + /* 0 */ { 0, 0, 0}, + /* 1 */ {18, 18, 18}, + /* 2 */ {28, 28, 28}, + /* 3 */ {51, 51, 51}, + /* 4 */ {51, 20, 0}, + /* 5 */ {55, 43, 0}, + /* 6 */ {12, 23, 35}, + /* 7 */ {11, 0, 22}, + /* 8 */ {14, 0, 14}, + /* 9 */ {13, 0, 9}, + /* 10 */ { 0, 24, 24}, + /* 11 */ {23, 4, 43}, + /* 12 */ {16, 32, 24}, + /* 13 */ {36, 0, 0}, + /* 14 */ {14, 7, 2}, + /* 15 */ { 2, 10, 14}, + }}, + {"Violent", { /* FT2 */ + /* 0 */ { 0, 0, 0}, + /* 1 */ {17, 10, 20}, + /* 2 */ {34, 21, 41}, + /* 3 */ {59, 58, 63}, + /* 4 */ {63, 7, 24}, + /* 5 */ {40, 40, 40}, + /* 6 */ {50, 45, 63}, + /* 7 */ {17, 10, 20}, + /* 8 */ { 8, 0, 10}, + /* 9 */ {22, 0, 24}, + /* 10 */ {43, 23, 31}, + /* 11 */ {55, 58, 56}, + /* 12 */ {37, 28, 53}, + /* 13 */ {50, 18, 39}, + /* 14 */ {20, 12, 24}, + /* 15 */ {11, 7, 13}, + }}, + {"Why Colors?", { /* FT2 */ + /* 0 */ { 0, 0, 0}, + /* 1 */ { 9, 14, 16}, + /* 2 */ {18, 29, 32}, + /* 3 */ {63, 63, 63}, + /* 4 */ {63, 0, 0}, + /* 5 */ {63, 63, 32}, + /* 6 */ {63, 63, 32}, + /* 7 */ {16, 16, 8}, + /* 8 */ {10, 10, 10}, + /* 9 */ {20, 20, 20}, + /* 10 */ {32, 32, 32}, + /* 11 */ {24, 38, 45}, + /* 12 */ {48, 48, 48}, + /* 13 */ {63, 63, 32}, + /* 14 */ {20, 20, 20}, + /* 15 */ {10, 10, 10}, + }}, + {"Kawaii", { /* mine (+mml) */ + /* 0 */ {61, 60, 63}, + /* 1 */ {63, 53, 60}, + /* 2 */ {51, 38, 47}, + /* 3 */ {18, 10, 17}, + /* 4 */ {63, 28, 50}, + /* 5 */ {21, 34, 50}, + /* 6 */ {40, 32, 45}, + /* 7 */ {63, 52, 59}, + /* 8 */ {48, 55, 63}, + /* 9 */ {51, 48, 63}, + /* 10 */ {45, 29, 44}, + /* 11 */ {57, 48, 59}, + /* 12 */ {34, 18, 32}, + /* 13 */ {50, 42, 63}, + /* 14 */ {50, 53, 60}, + /* 15 */ {63, 58, 56}, + }}, + {"Gold (Vintage)", { /* more directly based on the ST3 palette */ + /* 0 */ { 0, 0, 0}, + /* 1 */ {20, 17, 10}, + /* 2 */ {41, 36, 21}, + /* 3 */ {63, 55, 33}, + /* 4 */ {57, 0, 0}, // 63, 21, 21 + /* 5 */ { 0, 44, 0}, // 21, 50, 21 + /* 6 */ {38, 37, 36}, + /* 7 */ {22, 22, 22}, // not from ST3 + /* 8 */ { 5, 9, 22}, // 0, 0, 32 + /* 9 */ { 6, 12, 29}, // 0, 0, 42 + /* 10 */ {41, 36, 21}, + /* 11 */ {48, 49, 46}, + /* 12 */ {44, 44, 44}, // not from ST3 + /* 13 */ { 0, 44, 0}, // 21, 50, 21 + /* 14 */ {18, 16, 15}, + /* 15 */ {12, 11, 10}, + // no place for the dark red (34, 0, 0) + // (used for box corner decorations in ST3) + // also no place for the yellow (63, 63, 0) + // (note dots?) + }}, + {"FX 2.0", { /* Virt-supplied :) */ + /* 0 */ { 0, 4, 0}, + /* 1 */ { 6, 14, 8}, + /* 2 */ {30, 32, 32}, + /* 3 */ {55, 57, 50}, + /* 4 */ { 0, 13, 42}, + /* 5 */ {19, 43, 19}, + /* 6 */ { 7, 48, 22}, + /* 7 */ { 0, 13, 0 }, + /* 8 */ {24, 12, 23}, + /* 9 */ {19, 8, 23}, + /* 10 */ {14, 39, 29}, + /* 11 */ {28, 42, 43}, + /* 12 */ {12, 51, 35}, + /* 13 */ {12, 55, 31}, + /* 14 */ { 5, 15, 4}, + /* 15 */ { 3, 13, 7}, + }}, + {"", {{0}}} +}; +/* *INDENT-ON* */ + +#define NUM_PALETTES (ARRAY_SIZE(palettes) - 1) + +#endif /* ! PALETTES_H */ diff --git a/include/pattern-view.h b/include/pattern-view.h new file mode 100644 index 000000000..f42631514 --- /dev/null +++ b/include/pattern-view.h @@ -0,0 +1,45 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PATTERN_VIEW_H +#define PATTERN_VIEW_H + +/* NOTE: these functions need to be called with the screen LOCKED */ + +typedef void (*draw_channel_header_func) (int chan, int x, int y, int fg); +typedef void (*draw_note_func) (int x, int y, song_note * note, + int cursor_pos, int fg, int bg); + +#define PATTERN_VIEW(n) \ + void draw_channel_header_##n(int chan, int x, int y, int fg); \ + void draw_note_##n(int x, int y, song_note * note, \ + int cursor_pos, int fg, int bg); + +PATTERN_VIEW(13); +PATTERN_VIEW(10); +PATTERN_VIEW(7); +PATTERN_VIEW(6); +PATTERN_VIEW(3); +PATTERN_VIEW(2); +PATTERN_VIEW(1); + +#undef PATTERN_VIEW + +#endif /* ! PATTERN_VIEW_H */ diff --git a/include/sample-edit.h b/include/sample-edit.h new file mode 100644 index 000000000..daabc87ff --- /dev/null +++ b/include/sample-edit.h @@ -0,0 +1,54 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SAMPLE_EDIT_H +#define SAMPLE_EDIT_H + +void sample_sign_convert(song_sample * sample); +void sample_reverse(song_sample * sample); +void sample_centralise(song_sample * sample); +void sample_amplify(song_sample *sample, int percent); +/* Return the maximum amplification that can be done without clipping (as a + * percentage, suitable to pass to sample_amplify). */ +int sample_get_amplify_amount(song_sample *sample); + +/* if convert_data is nonzero, the sample data is modified (so it sounds + * the same); otherwise, the sample length is changed and the data is + * left untouched (so 16 bit samples converted to 8 bit end up sounding + * like junk, and 8 bit samples converted to 16 bit end up with 2x the + * pitch) */ +void sample_toggle_quality(song_sample * sample, int convert_data); + +/* resize a sample; if aa is set, attempt to antialias (resample) the + * output waveform. + */ +void sample_resize(song_sample * sample, unsigned long newlen, int aa); + +/* AFAIK, this was in some registered versions of IT */ +void sample_invert(song_sample * sample); + +/* Impulse Tracker doesn't do these. */ +void sample_delta_decode(song_sample * sample); + +void sample_mono_left(song_sample * sample); +void sample_mono_right(song_sample * sample); + + +#endif /* ! SAMPLE_EDIT_H */ diff --git a/include/slurp.h b/include/slurp.h new file mode 100644 index 000000000..1cb984d2c --- /dev/null +++ b/include/slurp.h @@ -0,0 +1,64 @@ +/* + * slurp - General-purpose file reader + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SLURP_H +#define SLURP_H + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "util.h" + +#include +#include + +#include + +/* --------------------------------------------------------------------- */ + +typedef struct _slurp_struct slurp_t; +struct _slurp_struct { + size_t length; + const byte *data; + int extra; + void *bextra; + void (*closure)(slurp_t *); +}; + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* slurp returns NULL and sets errno on error. 'buf' is only meaningful if you've already stat()'d +the file; in most cases it can simply be NULL. If size is nonzero, it overrides the file's size as +returned by stat -- this can be used to read only part of a file, or if the file size is known but +a stat structure is not available. */ +slurp_t *slurp(const char *filename, struct stat *buf, size_t size); + +void unslurp(slurp_t * t); + +#ifdef __cplusplus +} +#endif + +#endif /* ! SLURP_H */ diff --git a/include/song.h b/include/song.h new file mode 100644 index 000000000..c42ee3edb --- /dev/null +++ b/include/song.h @@ -0,0 +1,592 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef SONG_H +#define SONG_H + +#include "util.h" +#include "diskwriter.h" + +/* --------------------------------------------------------------------- */ +/* oodles o' structs */ + +/* midi config */ +typedef struct _midiconfig { + char midi_global_data[9*32]; + char midi_sfx[16*32]; + char midi_zxx[128*32]; +} midi_config; + +/* aka modinstrument */ +typedef struct _song_sample { + unsigned long length, loop_start, loop_end; + unsigned long sustain_start, sustain_end; + signed char *data; + unsigned long speed; + short panning; + short volume; + short global_volume; + short flags; + signed char relative_tone; /* mod-ish tuning */ + signed char finetune; /* mod-ish tuning */ + byte vib_type; + byte vib_rate; + byte vib_depth; + byte vib_speed; + char filename[22]; + + int played; +} song_sample; + +/* modchannelsettings */ +typedef struct _song_channel { + unsigned long panning; + unsigned long volume; + unsigned long flags; + unsigned long mix_plugin; + char name[20]; +} song_channel; + +/* instrumentenvelope */ +typedef struct _song_envelope { + unsigned short ticks[32]; + byte values[32]; + byte nodes; + byte loop_start, loop_end; + byte sustain_start, sustain_end; +} song_envelope; + +/* instrumentheader */ +typedef struct _song_instrument { + unsigned long fadeout; + unsigned long flags; // any of the ENV_* flags below + unsigned short global_volume; + unsigned short panning; + byte sample_map[128], note_map[128]; + song_envelope vol_env, pan_env, pitch_env; + byte nna, dct, dca; + byte pan_swing, volume_swing; + byte filter_cutoff; + byte filter_resonance; + unsigned short midi_bank; + byte midi_program; + byte midi_channel; + byte midi_drum_key; + signed char pitch_pan_separation; + byte pitch_pan_center; + char name[32]; + char filename[12]; + + int played; +} song_instrument; + +/* modcommand */ +typedef struct _song_note { + byte note; + byte instrument; + byte volume_effect; + byte effect; + byte volume; + byte parameter; +} song_note; + +/* modchannel (good grief...) */ +typedef struct _song_mix_channel { + signed char *sample_data; + unsigned long sample_pos; + unsigned long nPosLo; + long nInc; + long nRightVol; // these two are the current left/right volumes + long nLeftVol; /* (duh...) - i'm not sure if that's 100% right, + * though. for one, the max seems to be 680, and + * another, they seem to be backward (i.e. left + * is right and right is left...) */ + long nRightRamp; // maybe these two have something to do + long nLeftRamp; // with fadeout or nnas or something? dunno. + unsigned long sample_length; /* not counting beyond the loopend */ + unsigned long flags; /* the channel's flags (surround, mute, + * etc.) and the sample's (16-bit, etc.) + * combined together */ + unsigned long nLoopStart; + unsigned long nLoopEnd; + long nRampRightVol; + long nRampLeftVol; + double nFilter_Y1, nFilter_Y2, nFilter_Y3, nFilter_Y4; + double nFilter_A0, nFilter_B0, nFilter_B1; + long nROfs, nLOfs; + long nRampLength; + + /* information not used in the mixer */ + signed char *pSample; /* same as sample_data, except this isn't + * set to NULL at the end of the sample */ + long nNewRightVol, nNewLeftVol; // ??? + /* final_volume is what's actually used for mixing (after the + * global volume, envelopes, etc. are accounted for). same deal + * for final_panning. */ + long final_volume; /* range 0-16384 (?) */ + long final_panning; /* range 0-256. */ + /* these are the volumes set by the channel. */ + long volume, panning; /* range 0-256 (?) */ + long nFadeOutVol; /* ??? */ + long nPeriod, nC4Speed, sample_freq, nPortamentoDest; + song_instrument *instrument; /* NULL if sample mode (?) */ + song_sample *sample; + unsigned long nVolEnvPosition, nPanEnvPosition, nPitchEnvPosition; + unsigned long master_channel; // the channel this note was played in + unsigned long vu_meter; + long nGlobalVol; // the channel volume (Mxx)? - range 0-64 + long nInsVol; /* instrument volume? sample volume? dunno, one of + * those two... (range 0-64) */ + long nFineTune, nTranspose; + long nPortamentoSlide, nAutoVibDepth; + unsigned long nAutoVibPos, nVibratoPos, nTremoloPos, nPanbrelloPos; + + signed short nVolSwing, nPanSwing; + + byte note; // the note that's playing + byte nNNA; + byte nNewNote; // nfi... seems the same as nNote + byte nNewIns; // nfi, always seems to be zero + byte nCommand, nArpeggio; + byte nOldVolumeSlide, nOldFineVolUpDown; + byte nOldPortaUpDown, nOldFinePortaUpDown; + byte nOldPanSlide, nOldChnVolSlide; + byte nVibratoType, nVibratoSpeed, nVibratoDepth; + byte nTremoloType, nTremoloSpeed, nTremoloDepth; + byte nPanbrelloType, nPanbrelloSpeed, nPanbrelloDepth; + byte nOldCmdEx, nOldVolParam, nOldTempo; + byte nOldOffset, nOldHiOffset; + byte nCutOff, nResonance; + byte nRetrigCount, nRetrigParam; + byte nTremorCount, nTremorParam; + byte nPatternLoop, nPatternLoopCount; + byte nRowNote, nRowInstr; + byte nRowVolCmd, nRowVolume; + byte nRowCommand, nRowParam; + byte left_vu, right_vu; + byte nActiveMacro, nPadding; +} song_mix_channel; + +/* --------------------------------------------------------------------- */ +/* non-song-related structures */ + +/* defined in audio_playback.cc; also used by page_settings.c */ + +struct audio_settings { + int sample_rate, bits, channels, buffer_size; + int channel_limit, interpolation_mode; + int oversampling, hq_resampling; + int noise_reduction, surround_effect; + int xbass, xbass_amount, xbass_range; + int surround, surround_depth, surround_delay; + int reverb, reverb_depth, reverb_delay; + + unsigned int eq_freq[4]; + unsigned int eq_gain[4]; +}; + +extern struct audio_settings audio_settings; + + +/* for saving samples; see also enum sample_format_ids below */ + +//typedef bool (*fmt_save_sample_func) (FILE *fp, song_sample *smp, char *title); +struct sample_save_format { + const char *name; + const char *ext; + //fmt_save_sample_func *save_func; + bool (*save_func) (diskwriter_driver_t *fp, + song_sample *smp, char *title); +}; + +extern struct sample_save_format sample_save_formats[]; + +/* --------------------------------------------------------------------- */ +/* some enums */ + +// sample flags +enum { + SAMP_16_BIT = (0x01), + SAMP_LOOP = (0x02), + SAMP_LOOP_PINGPONG = (0x04), + SAMP_SUSLOOP = (0x08), + SAMP_SUSLOOP_PINGPONG = (0x10), + SAMP_PANNING = (0x20), + SAMP_STEREO = (0x40), + //SAMP_PINGPONGFLAG = (0x80), -- what is this? +}; + +// channel flags +enum { + CHN_MUTE = (0x100), /* this one's important :) */ + CHN_KEYOFF = (0x200), + CHN_NOTEFADE = (0x400), + CHN_SURROUND = (0x800), /* important :) */ + CHN_NOIDO = (0x1000), + CHN_HQSRC = (0x2000), + CHN_FILTER = (0x4000), + CHN_VOLUMERAMP = (0x8000), + CHN_VIBRATO = (0x10000), + CHN_TREMOLO = (0x20000), + CHN_PANBRELLO = (0x40000), + CHN_PORTAMENTO = (0x80000), + CHN_GLISSANDO = (0x100000), + CHN_VOLENV = (0x200000), + CHN_PANENV = (0x400000), + CHN_PITCHENV = (0x800000), + CHN_FASTVOLRAMP = (0x1000000), + CHN_EXTRALOUD = (0x2000000), + CHN_REVERB = (0x4000000), + CHN_NOREVERB = (0x8000000), +}; + +// instrument envelope flags +enum { + ENV_VOLUME = (0x0001), + ENV_VOLSUSTAIN = (0x0002), + ENV_VOLLOOP = (0x0004), + ENV_PANNING = (0x0008), + ENV_PANSUSTAIN = (0x0010), + ENV_PANLOOP = (0x0020), + ENV_PITCH = (0x0040), + ENV_PITCHSUSTAIN = (0x0080), + ENV_PITCHLOOP = (0x0100), + ENV_SETPANNING = (0x0200), + ENV_FILTER = (0x0400), + ENV_VOLCARRY = (0x0800), + ENV_PANCARRY = (0x1000), + ENV_PITCHCARRY = (0x2000), +}; + +enum { + ORDER_SKIP = (254), // the '+++' order + ORDER_LAST = (255), // the '---' order +}; + +// note fade IS actually supported in Impulse Tracker, +// but there's no way to handle it in the editor :) +enum { + NOTE_FADE = (253), // '~~~' + NOTE_CUT = (254), // '^^^' + NOTE_OFF = (255), // '===' +}; + +enum { + VIB_SINE = (0), + VIB_SQUARE = (1), + VIB_RAMP_UP = (2), + VIB_RAMP_DOWN = (3), /* modplug extension -- not supported */ + VIB_RANDOM = (4), +}; + +/* volume column effects */ +enum { + VOL_EFFECT_NONE, + VOL_EFFECT_VOLUME, + VOL_EFFECT_PANNING, + VOL_EFFECT_VOLSLIDEUP, + VOL_EFFECT_VOLSLIDEDOWN, + VOL_EFFECT_FINEVOLUP, + VOL_EFFECT_FINEVOLDOWN, + VOL_EFFECT_VIBRATOSPEED, + VOL_EFFECT_VIBRATO, + VOL_EFFECT_PANSLIDELEFT, + VOL_EFFECT_PANSLIDERIGHT, + VOL_EFFECT_TONEPORTAMENTO, + VOL_EFFECT_PORTAUP, + VOL_EFFECT_PORTADOWN +}; + +/* for song_get_mode */ +enum song_mode { + MODE_STOPPED = 0, + MODE_PLAYING = 1, + MODE_PATTERN_LOOP = 2, + MODE_SINGLE_STEP = 4, +}; + +enum song_new_flags { + KEEP_PATTERNS = 1, + KEEP_SAMPLES = 2, + KEEP_INSTRUMENTS = 4, + KEEP_ORDERLIST = 8, +}; + +/* used as indices to sample_save_formats[] */ +enum sample_save_format_ids { + SSMP_ITS = 0, + SSMP_AIFF = 1, + SSMP_AU = 2, + SSMP_RAW = 3, + SSMP_SENTINEL = 4, +}; + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------------------------------------------- */ + +void song_new(int flags); +int song_load(const char *file); +int song_load_unchecked(const char *file); +int song_save(const char *file, const char *type); + +void song_clear_sample(int n); +void song_copy_sample(int n, song_sample *src, char *srcname); +int song_load_sample(int n, const char *file); +int song_preload_sample(void *f); +int song_save_sample(int n, const char *file, int format_id); + +int song_load_instrument(int n, const char *file); +int song_load_instrument_ex(int n, const char *file, const char *libf, int nx); +int song_save_instrument(int n, const char *file); + +midi_config *song_get_midi_config(void); + +void song_sample_set_c5speed(int n, unsigned int c5); +int song_sample_is_empty(int n); +unsigned int song_sample_get_c5speed(int n); + +/* get the order for a particular pattern; locked can be: + * a starting order number (0-255) + * "-1" meaning start at the locked order + * "-2" meaning start at the current order +*/ +int song_order_for_pattern(int pat, int locked); + +const char *song_get_filename(void); +const char *song_get_basename(void); +char *song_get_title(void); // editable +char *song_get_message(void); // editable + +// returned value = seconds +unsigned long song_get_length(void); +unsigned long song_get_length_to(int order, int row); +void song_get_at_time(unsigned long seconds, int *order, int *row); + +// gee. can't just use malloc/free... no, that would be too simple. +signed char *song_sample_allocate(int bytes); +void song_sample_free(signed char *data); + +// these are used to directly manipulate the pattern list +song_note *song_pattern_allocate(int rows); +song_note *song_pattern_allocate_copy(int patno, int *rows); +void song_pattern_deallocate(song_note *n); +void song_pattern_install(int patno, song_note *n, int rows); + + +// these return NULL on failure. +song_sample *song_get_sample(int n, char **name_ptr); +song_instrument *song_get_instrument(int n, char **name_ptr); +int song_get_instrument_number(song_instrument *ins); // 0 => no instrument; ignore above comment =) +song_channel *song_get_channel(int n); + +// this one should probably be organized somewhere else..... meh +void song_set_channel_mute(int channel, int muted); +void song_toggle_channel_mute(int channel); +// if channel is the current soloed channel, undo the solo (reset the +// channel state); otherwise, save the state and solo the channel. +void song_handle_channel_solo(int channel); +void song_clear_solo_channel(void); + +// find the last channel that's not muted. (if a channel is soloed, this +// deals with the saved channel state instead.) +int song_find_last_channel(void); + +int song_get_pattern(int n, song_note ** buf); // return 0 -> error +byte *song_get_orderlist(void); + +int song_pattern_is_empty(int p); +int song_get_instrument_default_volume(int ins, int sam); + +int song_get_num_orders(void); +int song_get_num_patterns(void); +int song_get_rows_in_pattern(int pattern); + +void song_pattern_resize(int pattern, int rows); + +int song_get_initial_speed(void); +void song_set_initial_speed(int new_speed); +int song_get_initial_tempo(void); +void song_set_initial_tempo(int new_tempo); + +int song_get_initial_global_volume(void); +void song_set_initial_global_volume(int new_vol); +int song_get_mixing_volume(void); +void song_set_mixing_volume(int new_vol); +int song_get_separation(void); +void song_set_separation(int new_sep); + +/* these next few are booleans... */ +int song_is_stereo(void); +void song_set_stereo(void); +void song_set_mono(void); +void song_toggle_stereo(void); +void song_toggle_mono(void); + +/* void song_set_stereo(int value); ??? */ +int song_has_old_effects(void); +void song_set_old_effects(int value); +int song_has_compatible_gxx(void); +void song_set_compatible_gxx(int value); +int song_has_linear_pitch_slides(void); +void song_set_linear_pitch_slides(int value); +int song_is_instrument_mode(void); +void song_set_instrument_mode(int value); + + +/* this is called way early */ +void song_initialise(void); + +/* these are called later at startup, and also when the relevant settings are changed */ +void song_init_audio(const char *driver); +void song_init_modplug(void); + +/* eq */ +void song_init_eq(int do_reset); + +/* --------------------------------------------------------------------- */ +/* playback */ +void song_lock_audio(void); +void song_unlock_audio(void); +void song_stop_audio(void); +void song_start_audio(void); +const char *song_audio_driver(void); +#define song_audio_driver_name() song_audio_driver() + +void song_toggle_multichannel_mode(void); +int song_is_multichannel_mode(void); +void song_change_current_play_channel(int relative, int wraparound); + +/* these return the selected channel */ +int song_keydown(int s,int ins, int n, int v, int c, int *mm); +int song_keyrecord(int s,int ins, int n, int v, int c, int *mm, + int effect, int param); +int song_keyup(int s,int ins, int n, int c, int *mm); + +void song_start(void); +void song_start_once(void); +void song_stop(void); +void song_stop_unlocked(void); +void song_loop_pattern(int pattern, int row); +void song_start_at_order(int order, int row); +void song_start_at_pattern(int pattern, int row); +void song_single_step(int pattern, int row); + +/* see the enum above */ +enum song_mode song_get_mode(void); + +/* the time returned is in seconds */ +unsigned long song_get_current_time(void); + +int song_get_current_speed(void); +int song_get_current_tick(void); +int song_get_current_tempo(void); +int song_get_current_global_volume(void); + +int song_get_current_order(void); +int song_get_playing_pattern(void); +int song_get_current_row(void); + +void song_set_current_order(int order); +void song_set_next_order(int order); +int song_toggle_orderlist_locked(void); + +int song_get_playing_channels(void); +int song_get_max_channels(void); + +void song_get_vu_meter(int *left, int *right); + +/* fill the array with flags of each playing sample/instrument, such that iff + * sample #7 is playing, samples[7] will be nonzero. these are a bit processor + * intensive since they require a linear traversal through the mix channels. */ +void song_get_playing_samples(int samples[]); +void song_get_playing_instruments(int instruments[]); + +/* update any currently playing channels with current sample configuration */ +void song_update_playing_sample(int s_changed); +void song_update_playing_instrument(int i_changed); + +void song_set_current_speed(int speed); +void song_set_current_global_volume(int volume); + +/* this is very different from song_get_channel! + * this deals with the channel that's *playing* and is used mostly + * (entirely?) for the info page. */ +song_mix_channel *song_get_mix_channel(int n); + +/* get the mix state: + * if channel_list != NULL, it is set to an array of the channels that + * are being mixed. the return value is the number of channels to mix + * (i.e. the length of the channel_list array). so... to go through each + * channel that's being mixed: + * + * unsigned long *channel_list; + * song_mix_channel *channel; + * int n = song_get_mix_state(&channel_list); + * while (n--) { + * channel = song_get_mix_channel(channel_list[n]); + * (do something with the channel) + * } + * it's kind of ugly, but it'll do... i hope :) */ +int song_get_mix_state(unsigned long **channel_list); + +/* --------------------------------------------------------------------- */ +/* rearranging stuff */ + +/* exchange = only in list; swap = in list and song */ +void song_exchange_samples(int a, int b); +void song_exchange_instruments(int a, int b); +void song_swap_samples(int a, int b); +void song_swap_instruments(int a, int b); + +void song_insert_sample_slot(int n); +void song_remove_sample_slot(int n); +void song_insert_instrument_slot(int n); +void song_remove_instrument_slot(int n); + +void song_delete_instrument(int n); +void song_wipe_instrument(int n); + +int song_instrument_is_empty(int n); +void song_init_instruments(int n); /* -1 for all */ + +/* --------------------------------------------------------------------- */ +/* misc. */ + +unsigned long song_buffer_msec(void); + +void song_flip_stereo(void); + +int song_get_surround(void); +void song_set_surround(int on); + +static const song_note empty_note = { 0, 0, 0, 0, 0, 0 }; + +/* --------------------------------------------------------------------- */ + +#ifdef __cplusplus +} +#endif + +#endif /* ! SONG_H */ diff --git a/include/util.h b/include/util.h new file mode 100644 index 000000000..1a441e76c --- /dev/null +++ b/include/util.h @@ -0,0 +1,132 @@ +/* + * util.h - Various useful functions + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef UTIL_H +#define UTIL_H + +/* FIXME: should include the standard header with all that #ifdef crap. + * (the only reason time.h is here is for the time_t definition) */ +#include + +/* --------------------------------------------------------------------- */ + +/* I don't like all caps for these 'cuz they don't get highlighted. */ +#ifndef __cplusplus +typedef enum { false, true } bool; +#endif + +/* This is just for the sake of typing 4 chars rather than 13. */ +#ifndef byte +#define byte byte +typedef unsigned char byte; +#endif + +#define ARRAY_SIZE(a) ((signed)(sizeof(a)/sizeof(*(a)))) + + +/* macros stolen from glib */ +#ifndef MAX +# define MAX(X,Y) (((X)>(Y))?(X):(Y)) +#endif +#ifndef MIN +# define MIN(X,Y) (((X)<(Y))?(X):(Y)) +#endif +#ifndef CLAMP +# define CLAMP(N,L,H) (((N)>(H))?(H):(((N)<(L))?(L):(N))) +#endif + +#ifdef __GNUC__ +#ifndef UNUSED +# define UNUSED __attribute__((unused)) +#endif +#ifndef NORETURN +# define NORETURN __attribute__((noreturn)) +#endif +#else +#ifndef UNUSED +# define UNUSED +#endif +#ifndef NORETURN +# define NORETURN +#endif +#endif + +/* Path stuff that differs by platform */ +#ifdef WIN32 +# define DIR_SEPARATOR '\\' +# define DIR_SEPARATOR_STR "\\" +#else +# define DIR_SEPARATOR '/' +# define DIR_SEPARATOR_STR "/" +#endif + +/* --------------------------------------------------------------------- */ +/* functions returning const char * use a static buffer; ones returning char * malloc their return value +(thus it needs free'd)... except numtostr, get_time_string, and get_date_string, which return the buffer +passed to them in the 'buf' parameter. */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* memory */ +extern void *mem_alloc(size_t); +extern void *mem_realloc(void *,size_t); +extern void mem_free(void *); + +/* formatting */ +/* for get_{time,date}_string, buf should be (at least) 27 chars; anything past that isn't used. */ +unsigned char *get_date_string(time_t when, unsigned char *buf); +unsigned char *get_time_string(time_t when, unsigned char *buf); +unsigned char *numtostr(int digits, int n, unsigned char *buf); + +/* string handling */ +const char *get_basename(const char *filename); +const char *get_extension(const char *filename); +char *get_parent_directory(const char *dirname); +void trim_string(char *s); +int str_break(const char *s, char c, char **first, char **second); +char *str_escape(const char *source, bool space_hack); +char *str_unescape(const char *source); +char *pretty_name(const char *filename); +int get_num_lines(const char *text); +char *str_concat(const char *s, ...); + + +/* filesystem */ +bool make_backup_file(const char *filename); +long file_size(const char *filename); +long file_size_fd(int fd); +bool is_directory(const char *filename); +char *get_home_directory(void); /* should free() the resulting string */ + +void put_env_var(const char *key, const char *value); + +/* integer sqrt (very fast; 32 bits limited) */ +unsigned int i_sqrt(unsigned int r); + +/* sleep in msec */ +void ms_sleep(unsigned int m); + +#ifdef __cplusplus +} +#endif + +#endif /* ! UTIL_H */ diff --git a/include/vgamem-scanner.h b/include/vgamem-scanner.h new file mode 100644 index 000000000..1750ad70d --- /dev/null +++ b/include/vgamem-scanner.h @@ -0,0 +1,79 @@ +/* should be included inside draw-char.c */ +void F1(unsigned int ry, + unsigned SIZE *out, unsigned int tc[16]) +{ + unsigned int *bp; + unsigned int dg; + unsigned char mb[2]; + unsigned int mx; + byte *bf, *hf, *ef; + int x, y, fg,bg, c; + + _video_scanmouse(ry, mb, &mx); + + y = ry >> 3; + bp = &vgamem_read[y * 80]; + bf = font_data + (ry & 7); + hf = font_half_data + ((ry & 7) >> 1); + ef = font_extra + (ry & 7); + + for (x = 0; x < 80; x++, bp++) { + if (*bp & 0x80000000) { + /* extra character */ + fg = (*bp >> 23) & 7; + bg = (*bp >> 27) & 7; + dg = ef[(*bp & 0xFFFF)<< 3]; + if (x == mx) dg ^= mb[0]; + else if (x == mx+1) dg ^= mb[1]; + *out++ = tc[(dg & 0x80) ? fg : bg]; + *out++ = tc[(dg & 0x40) ? fg : bg]; + *out++ = tc[(dg & 0x20) ? fg : bg]; + *out++ = tc[(dg & 0x10) ? fg : bg]; + *out++ = tc[(dg & 0x8) ? fg : bg]; + *out++ = tc[(dg & 0x4) ? fg : bg]; + *out++ = tc[(dg & 0x2) ? fg : bg]; + *out++ = tc[(dg & 0x1) ? fg : bg]; + + } else if (*bp & 0x40000000) { + /* half-width character */ + fg = (*bp >> 22) & 15; + bg = (*bp >> 18) & 15; + dg = hf[ _unpack_halfw((*bp >>9) & 31) << 2]; + if (!(ry & 1)) dg = (dg >> 4); + if (x == mx) dg ^= mb[0]; + else if (x == mx+1) dg ^= mb[1]; + + *out++ = tc[(dg & 0x8) ? fg : bg]; + *out++ = tc[(dg & 0x4) ? fg : bg]; + *out++ = tc[(dg & 0x2) ? fg : bg]; + *out++ = tc[(dg & 0x1) ? fg : bg]; + fg = (*bp >> 26) & 15; + bg = (*bp >> 14) & 15; + dg = hf[ _unpack_halfw((*bp >> 4) & 31) << 2]; + if (!(ry & 1)) dg = (dg >> 4); + if (x == mx) dg ^= mb[0]; + else if (x == mx+1) dg ^= mb[1]; + + *out++ = tc[(dg & 0x8) ? fg : bg]; + *out++ = tc[(dg & 0x4) ? fg : bg]; + *out++ = tc[(dg & 0x2) ? fg : bg]; + *out++ = tc[(dg & 0x1) ? fg : bg]; + } else { + /* regular character */ + fg = (*bp & 0x0F00) >> 8; + bg = (*bp & 0xF000) >> 12; + dg = bf[(*bp & 0xFF)<< 3]; + if (x == mx) dg ^= mb[0]; + else if (x == mx+1) dg ^= mb[1]; + if (!(*bp & 0xFF)) fg = 3; + *out++ = tc[(dg & 0x80) ? fg : bg]; + *out++ = tc[(dg & 0x40) ? fg : bg]; + *out++ = tc[(dg & 0x20) ? fg : bg]; + *out++ = tc[(dg & 0x10) ? fg : bg]; + *out++ = tc[(dg & 0x8) ? fg : bg]; + *out++ = tc[(dg & 0x4) ? fg : bg]; + *out++ = tc[(dg & 0x2) ? fg : bg]; + *out++ = tc[(dg & 0x1) ? fg : bg]; + } + } +} diff --git a/include/video.h b/include/video.h new file mode 100644 index 000000000..09c921938 --- /dev/null +++ b/include/video.h @@ -0,0 +1,62 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __video_h +#define __video_h + +/* the vgamem implementation lives in draw-char.c +it needs access to the fonts, and it shrank recently :) +*/ +void vgamem_clear(void); + +struct vgamem_overlay { + unsigned int x1, y1, x2, y2; /* in character cells... */ + unsigned int pitch, size; /* in bytes */ + unsigned int chars, base; + int width, height; /* in pixels; signed to avoid bugs elsewhere */ +}; + +void vgamem_lock(void); +void vgamem_unlock(void); +void vgamem_flip(void); +void vgamem_font_reserve(struct vgamem_overlay *n); +void vgamem_fill_reserve(struct vgamem_overlay *n, int fg, int bg); +void vgamem_font_putpixel(struct vgamem_overlay *n, int x, int y); +void vgamem_font_clearpixel(struct vgamem_overlay *n, int x, int y); +void vgamem_font_drawline(struct vgamem_overlay *n, int xs, int ys, int xe, int ye); + +void vgamem_scan32(unsigned int y,unsigned int *out,unsigned int tc[16]); +void vgamem_scan16(unsigned int y,unsigned short *out,unsigned int tc[16]); +void vgamem_scan8(unsigned int y,unsigned char *out,unsigned int tc[16]); + +/* video output routines */ +const char *video_driver_name(void); + +void video_init(const char *id); +void video_colors(unsigned char palette[16][3]); +void video_resize(unsigned int width, unsigned int height); +void video_fullscreen(int tri); +void video_translate(unsigned int vx, unsigned int vy, + unsigned int *x, unsigned int *y); +void video_blit(void); +void video_mousecursor(int z); + +int video_is_fullscreen(void); + +#endif diff --git a/modplug/fastmix.cpp b/modplug/fastmix.cpp new file mode 100644 index 000000000..69e7ae6cc --- /dev/null +++ b/modplug/fastmix.cpp @@ -0,0 +1,1827 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque + * Markus Fick spline + fir-resampler +*/ + +#include "stdafx.h" +#include "sndfile.h" +#include + +// Front Mix Buffer (Also room for interleaved rear mix) +int MixSoundBuffer[MIXBUFFERSIZE*4]; + +// Reverb Mix Buffer +#ifndef MODPLUG_NO_REVERB +int MixReverbBuffer[MIXBUFFERSIZE*2]; +extern UINT gnReverbSend; +#endif + +int MixRearBuffer[MIXBUFFERSIZE*2]; +float MixFloatBuffer[MIXBUFFERSIZE*2]; + + +extern LONG gnDryROfsVol; +extern LONG gnDryLOfsVol; +extern LONG gnRvbROfsVol; +extern LONG gnRvbLOfsVol; + +// 4x256 taps polyphase FIR resampling filter +extern short int gFastSinc[]; +extern short int gKaiserSinc[]; // 8-taps polyphase +/* + ----------------------------------------------------------------------------- + cubic spline interpolation doc, + (derived from "digital image warping", g. wolberg) + + interpolation polynomial: f(x) = A3*(x-floor(x))**3 + A2*(x-floor(x))**2 + A1*(x-floor(x)) + A0 + + with Y = equispaced data points (dist=1), YD = first derivates of data points and IP = floor(x) + the A[0..3] can be found by solving + A0 = Y[IP] + A1 = YD[IP] + A2 = 3*(Y[IP+1]-Y[IP])-2.0*YD[IP]-YD[IP+1] + A3 = -2.0 * (Y[IP+1]-Y[IP]) + YD[IP] - YD[IP+1] + + with the first derivates as + YD[IP] = 0.5 * (Y[IP+1] - Y[IP-1]); + YD[IP+1] = 0.5 * (Y[IP+2] - Y[IP]) + + the coefs becomes + A0 = Y[IP] + A1 = YD[IP] + = 0.5 * (Y[IP+1] - Y[IP-1]); + A2 = 3.0 * (Y[IP+1]-Y[IP])-2.0*YD[IP]-YD[IP+1] + = 3.0 * (Y[IP+1] - Y[IP]) - 0.5 * 2.0 * (Y[IP+1] - Y[IP-1]) - 0.5 * (Y[IP+2] - Y[IP]) + = 3.0 * Y[IP+1] - 3.0 * Y[IP] - Y[IP+1] + Y[IP-1] - 0.5 * Y[IP+2] + 0.5 * Y[IP] + = -0.5 * Y[IP+2] + 2.0 * Y[IP+1] - 2.5 * Y[IP] + Y[IP-1] + = Y[IP-1] + 2 * Y[IP+1] - 0.5 * (5.0 * Y[IP] + Y[IP+2]) + A3 = -2.0 * (Y[IP+1]-Y[IP]) + YD[IP] + YD[IP+1] + = -2.0 * Y[IP+1] + 2.0 * Y[IP] + 0.5 * (Y[IP+1] - Y[IP-1]) + 0.5 * (Y[IP+2] - Y[IP]) + = -2.0 * Y[IP+1] + 2.0 * Y[IP] + 0.5 * Y[IP+1] - 0.5 * Y[IP-1] + 0.5 * Y[IP+2] - 0.5 * Y[IP] + = 0.5 * Y[IP+2] - 1.5 * Y[IP+1] + 1.5 * Y[IP] - 0.5 * Y[IP-1] + = 0.5 * (3.0 * (Y[IP] - Y[IP+1]) - Y[IP-1] + YP[IP+2]) + + then interpolated data value is (horner rule) + out = (((A3*x)+A2)*x+A1)*x+A0 + + this gives parts of data points Y[IP-1] to Y[IP+2] of + part x**3 x**2 x**1 x**0 + Y[IP-1] -0.5 1 -0.5 0 + Y[IP] 1.5 -2.5 0 1 + Y[IP+1] -1.5 2 0.5 0 + Y[IP+2] 0.5 -0.5 0 0 + ----------------------------------------------------------------------------- +*/ +// number of bits used to scale spline coefs +#define SPLINE_QUANTBITS 14 +#define SPLINE_QUANTSCALE (1L< _LScale) ? _LScale : _LCm1) ); + lut[_LIdx+1] = (signed short)( (_LC0 < -_LScale) ? -_LScale : ((_LC0 > _LScale) ? _LScale : _LC0 ) ); + lut[_LIdx+2] = (signed short)( (_LC1 < -_LScale) ? -_LScale : ((_LC1 > _LScale) ? _LScale : _LC1 ) ); + lut[_LIdx+3] = (signed short)( (_LC2 < -_LScale) ? -_LScale : ((_LC2 > _LScale) ? _LScale : _LC2 ) ); +#ifdef SPLINE_CLAMPFORUNITY + _LSum = lut[_LIdx+0]+lut[_LIdx+1]+lut[_LIdx+2]+lut[_LIdx+3]; + if( _LSum != SPLINE_QUANTSCALE ) + { int _LMax = _LIdx; + if( lut[_LIdx+1]>lut[_LMax] ) _LMax = _LIdx+1; + if( lut[_LIdx+2]>lut[_LMax] ) _LMax = _LIdx+2; + if( lut[_LIdx+3]>lut[_LMax] ) _LMax = _LIdx+3; + lut[_LMax] += (SPLINE_QUANTSCALE-_LSum); + } +#endif + } +} + +CzCUBICSPLINE::~CzCUBICSPLINE( ) +{ // nothing todo +} + +CzCUBICSPLINE sspline; + +/* + ------------------------------------------------------------------------------------------------ + fir interpolation doc, + (derived from "an engineer's guide to fir digital filters", n.j. loy) + + calculate coefficients for ideal lowpass filter (with cutoff = fc in 0..1 (mapped to 0..nyquist)) + c[-N..N] = (i==0) ? fc : sin(fc*pi*i)/(pi*i) + + then apply selected window to coefficients + c[-N..N] *= w(0..N) + with n in 2*N and w(n) being a window function (see loy) + + then calculate gain and scale filter coefs to have unity gain. + ------------------------------------------------------------------------------------------------ +*/ +// quantizer scale of window coefs +#define WFIR_QUANTBITS 15 +#define WFIR_QUANTSCALE (1L<>1) +// cutoff (1.0 == pi/2) +#define WFIR_CUTOFF 0.90f +// wfir type +#define WFIR_HANN 0 +#define WFIR_HAMMING 1 +#define WFIR_BLACKMANEXACT 2 +#define WFIR_BLACKMAN3T61 3 +#define WFIR_BLACKMAN3T67 4 +#define WFIR_BLACKMAN4T92 5 +#define WFIR_BLACKMAN4T74 6 +#define WFIR_KAISER4T 7 +#define WFIR_TYPE WFIR_BLACKMANEXACT +// wfir help +#ifndef M_zPI +#define M_zPI 3.1415926535897932384626433832795 +#endif +#define M_zEPS 1e-8 +#define M_zBESSELEPS 1e-21 + +class CzWINDOWEDFIR +{ +public: + CzWINDOWEDFIR( ); + ~CzWINDOWEDFIR( ); + float coef( int _PCnr, float _POfs, float _PCut, int _PWidth, int _PType ) //float _PPos, float _PFc, int _PLen ) + { + double _LWidthM1 = _PWidth-1; + double _LWidthM1Half = 0.5*_LWidthM1; + double _LPosU = ((double)_PCnr - _POfs); + double _LPos = _LPosU-_LWidthM1Half; + double _LPIdl = 2.0*M_zPI/_LWidthM1; + double _LWc,_LSi; + if( fabs(_LPos)_LScale)?_LScale:_LCoef) ); + } + } +} + +CzWINDOWEDFIR::~CzWINDOWEDFIR() +{ // nothing todo +} + +CzWINDOWEDFIR sfir; + +// ---------------------------------------------------------------------------- +// MIXING MACROS +// ---------------------------------------------------------------------------- +///////////////////////////////////////////////////// +// Mixing Macros + +#define SNDMIX_BEGINSAMPLELOOP8\ + register MODCHANNEL * const pChn = pChannel;\ + nPos = pChn->nPosLo;\ + const signed char *p = (signed char *)(pChn->pCurrentSample+pChn->nPos);\ + if (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\ + int *pvol = pbuffer;\ + do { + +#define SNDMIX_BEGINSAMPLELOOP16\ + register MODCHANNEL * const pChn = pChannel;\ + nPos = pChn->nPosLo;\ + const signed short *p = (signed short *)(pChn->pCurrentSample+(pChn->nPos*2));\ + if (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\ + int *pvol = pbuffer;\ + do { + +#define SNDMIX_ENDSAMPLELOOP\ + nPos += pChn->nInc;\ + } while (pvol < pbufmax);\ + pChn->nPos += nPos >> 16;\ + pChn->nPosLo = nPos & 0xFFFF; + +#define SNDMIX_ENDSAMPLELOOP8 SNDMIX_ENDSAMPLELOOP +#define SNDMIX_ENDSAMPLELOOP16 SNDMIX_ENDSAMPLELOOP + +////////////////////////////////////////////////////////////////////////////// +// Mono + +// No interpolation +#define SNDMIX_GETMONOVOL8NOIDO\ + int vol = p[nPos >> 16] << 8; + +#define SNDMIX_GETMONOVOL16NOIDO\ + int vol = p[nPos >> 16]; + +// Linear Interpolation +#define SNDMIX_GETMONOVOL8LINEAR\ + int poshi = nPos >> 16;\ + int poslo = (nPos >> 8) & 0xFF;\ + int srcvol = p[poshi];\ + int destvol = p[poshi+1];\ + int vol = (srcvol<<8) + ((int)(poslo * (destvol - srcvol))); + +#define SNDMIX_GETMONOVOL16LINEAR\ + int poshi = nPos >> 16;\ + int poslo = (nPos >> 8) & 0xFF;\ + int srcvol = p[poshi];\ + int destvol = p[poshi+1];\ + int vol = srcvol + ((int)(poslo * (destvol - srcvol)) >> 8); + +// spline interpolation (2 guard bits should be enough???) +#define SPLINE_FRACSHIFT ((16-SPLINE_FRACBITS)-2) +#define SPLINE_FRACMASK (((1L<<(16-SPLINE_FRACSHIFT))-1)&~3) + +#define SNDMIX_GETMONOVOL8SPLINE \ + int poshi = nPos >> 16; \ + int poslo = (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol = (CzCUBICSPLINE::lut[poslo ]*(int)p[poshi-1] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[poshi ] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[poshi+2] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[poshi+1]) >> SPLINE_8SHIFT; + +#define SNDMIX_GETMONOVOL16SPLINE \ + int poshi = nPos >> 16; \ + int poslo = (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol = (CzCUBICSPLINE::lut[poslo ]*(int)p[poshi-1] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[poshi ] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[poshi+2] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[poshi+1]) >> SPLINE_16SHIFT; + + +// fir interpolation +#define WFIR_FRACSHIFT (16-(WFIR_FRACBITS+1+WFIR_LOG2WIDTH)) +#define WFIR_FRACMASK ((((1L<<(17-WFIR_FRACSHIFT))-1)&~((1L<> 16;\ + int poslo = (nPos & 0xFFFF);\ + int firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[poshi+1-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[poshi+2-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[poshi+3-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[poshi+4-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+4]*(int)p[poshi+5-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[poshi+6-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[poshi+7-4]); \ + vol += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[poshi+8-4]); \ + vol >>= WFIR_8SHIFT; + +#define SNDMIX_GETMONOVOL16FIRFILTER \ + int poshi = nPos >> 16;\ + int poslo = (nPos & 0xFFFF);\ + int firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol1 = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[poshi+1-4]); \ + vol1 += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[poshi+2-4]); \ + vol1 += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[poshi+3-4]); \ + vol1 += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[poshi+4-4]); \ + int vol2 = (CzWINDOWEDFIR::lut[firidx+4]*(int)p[poshi+5-4]); \ + vol2 += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[poshi+6-4]); \ + vol2 += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[poshi+7-4]); \ + vol2 += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[poshi+8-4]); \ + int vol = ((vol1>>1)+(vol2>>1)) >> (WFIR_16BITSHIFT-1); + +///////////////////////////////////////////////////////////////////////////// +// Stereo + +// No interpolation +#define SNDMIX_GETSTEREOVOL8NOIDO\ + int vol_l = p[(nPos>>16)*2] << 8;\ + int vol_r = p[(nPos>>16)*2+1] << 8; + +#define SNDMIX_GETSTEREOVOL16NOIDO\ + int vol_l = p[(nPos>>16)*2];\ + int vol_r = p[(nPos>>16)*2+1]; + +// Linear Interpolation +#define SNDMIX_GETSTEREOVOL8LINEAR\ + int poshi = nPos >> 16;\ + int poslo = (nPos >> 8) & 0xFF;\ + int srcvol_l = p[poshi*2];\ + int vol_l = (srcvol_l<<8) + ((int)(poslo * (p[poshi*2+2] - srcvol_l)));\ + int srcvol_r = p[poshi*2+1];\ + int vol_r = (srcvol_r<<8) + ((int)(poslo * (p[poshi*2+3] - srcvol_r))); + +#define SNDMIX_GETSTEREOVOL16LINEAR\ + int poshi = nPos >> 16;\ + int poslo = (nPos >> 8) & 0xFF;\ + int srcvol_l = p[poshi*2];\ + int vol_l = srcvol_l + ((int)(poslo * (p[poshi*2+2] - srcvol_l)) >> 8);\ + int srcvol_r = p[poshi*2+1];\ + int vol_r = srcvol_r + ((int)(poslo * (p[poshi*2+3] - srcvol_r)) >> 8);\ + +// Spline Interpolation +#define SNDMIX_GETSTEREOVOL8SPLINE \ + int poshi = nPos >> 16; \ + int poslo = (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol_l = (CzCUBICSPLINE::lut[poslo ]*(int)p[(poshi-1)*2 ] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi )*2 ] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2 ] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2 ]) >> SPLINE_8SHIFT; \ + int vol_r = (CzCUBICSPLINE::lut[poslo ]*(int)p[(poshi-1)*2+1] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi )*2+1] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2+1] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2+1]) >> SPLINE_8SHIFT; + +#define SNDMIX_GETSTEREOVOL16SPLINE \ + int poshi = nPos >> 16; \ + int poslo = (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ + int vol_l = (CzCUBICSPLINE::lut[poslo ]*(int)p[(poshi-1)*2 ] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi )*2 ] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2 ] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2 ]) >> SPLINE_16SHIFT; \ + int vol_r = (CzCUBICSPLINE::lut[poslo ]*(int)p[(poshi-1)*2+1] + \ + CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi )*2+1] + \ + CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2+1] + \ + CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2+1]) >> SPLINE_16SHIFT; + +// fir interpolation +#define SNDMIX_GETSTEREOVOL8FIRFILTER \ + int poshi = nPos >> 16;\ + int poslo = (nPos & 0xFFFF);\ + int firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol_l = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2 ]); \ + vol_l += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2 ]); \ + vol_l >>= WFIR_8SHIFT; \ + int vol_r = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2+1]); \ + vol_r += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2+1]); \ + vol_r >>= WFIR_8SHIFT; + +#define SNDMIX_GETSTEREOVOL16FIRFILTER \ + int poshi = nPos >> 16;\ + int poslo = (nPos & 0xFFFF);\ + int firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \ + int vol1_l = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2 ]); \ + vol1_l += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2 ]); \ + vol1_l += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2 ]); \ + vol1_l += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2 ]); \ + int vol2_l = (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2 ]); \ + vol2_l += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2 ]); \ + vol2_l += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2 ]); \ + vol2_l += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2 ]); \ + int vol_l = ((vol1_l>>1)+(vol2_l>>1)) >> (WFIR_16BITSHIFT-1); \ + int vol1_r = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2+1]); \ + vol1_r += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2+1]); \ + vol1_r += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2+1]); \ + vol1_r += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2+1]); \ + int vol2_r = (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2+1]); \ + vol2_r += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2+1]); \ + vol2_r += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2+1]); \ + vol2_r += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2+1]); \ + int vol_r = ((vol1_r>>1)+(vol2_r>>1)) >> (WFIR_16BITSHIFT-1); + +///////////////////////////////////////////////////////////////////////////// + +#define SNDMIX_STOREMONOVOL\ + pvol[0] += vol * pChn->nRightVol;\ + pvol[1] += vol * pChn->nLeftVol;\ + pvol += 2; + +#define SNDMIX_STORESTEREOVOL\ + pvol[0] += vol_l * pChn->nRightVol;\ + pvol[1] += vol_r * pChn->nLeftVol;\ + pvol += 2; + +#define SNDMIX_STOREFASTMONOVOL\ + int v = vol * pChn->nRightVol;\ + pvol[0] += v;\ + pvol[1] += v;\ + pvol += 2; + +#define SNDMIX_RAMPMONOVOL\ + nRampLeftVol += pChn->nLeftRamp;\ + nRampRightVol += pChn->nRightRamp;\ + pvol[0] += vol * (nRampRightVol >> VOLUMERAMPPRECISION);\ + pvol[1] += vol * (nRampLeftVol >> VOLUMERAMPPRECISION);\ + pvol += 2; + +#define SNDMIX_RAMPFASTMONOVOL\ + nRampRightVol += pChn->nRightRamp;\ + int fastvol = vol * (nRampRightVol >> VOLUMERAMPPRECISION);\ + pvol[0] += fastvol;\ + pvol[1] += fastvol;\ + pvol += 2; + +#define SNDMIX_RAMPSTEREOVOL\ + nRampLeftVol += pChn->nLeftRamp;\ + nRampRightVol += pChn->nRightRamp;\ + pvol[0] += vol_l * (nRampRightVol >> VOLUMERAMPPRECISION);\ + pvol[1] += vol_r * (nRampLeftVol >> VOLUMERAMPPRECISION);\ + pvol += 2; + + +/////////////////////////////////////////////////// +// Resonant Filters + +// Mono +#define MIX_BEGIN_FILTER \ + double fy1 = pChannel->nFilter_Y1;\ + double fy2 = pChannel->nFilter_Y2;\ + double ta; + +#define MIX_END_FILTER \ + pChannel->nFilter_Y1 = fy1;\ + pChannel->nFilter_Y2 = fy2; + +#define SNDMIX_PROCESSFILTER \ +ta = ((double)vol * pChn->nFilter_A0 + fy1 * pChn->nFilter_B0 + fy2 * pChn->nFilter_B1);\ +fy2 = fy1;\ +fy1 = ta;vol=(int)ta; + +// Stereo +#define MIX_BEGIN_STEREO_FILTER \ +double fy1 = pChannel->nFilter_Y1;\ +double fy2 = pChannel->nFilter_Y2;\ +double fy3 = pChannel->nFilter_Y3;\ +double fy4 = pChannel->nFilter_Y4;\ +double ta, tb; + +#define MIX_END_STEREO_FILTER \ +pChannel->nFilter_Y1 = fy1;\ +pChannel->nFilter_Y2 = fy2;\ +pChannel->nFilter_Y3 = fy3;\ +pChannel->nFilter_Y4 = fy4;\ + +#define SNDMIX_PROCESSSTEREOFILTER \ +ta = ((double)vol_l * pChn->nFilter_A0 + fy1 * pChn->nFilter_B0 + fy2 * pChn->nFilter_B1);\ +tb = ((double)vol_r * pChn->nFilter_A0 + fy3 * pChn->nFilter_B0 + fy4 * pChn->nFilter_B1);\ +fy2 = fy1; fy1 = ta;vol_l=(int)ta;\ +fy4 = fy3; fy3 = tb;vol_r=(int)tb; + +////////////////////////////////////////////////////////// +// Interfaces + +typedef VOID (MPPASMCALL * LPMIXINTERFACE)(MODCHANNEL *, int *, int *); + +#define BEGIN_MIX_INTERFACE(func)\ + VOID MPPASMCALL func(MODCHANNEL *pChannel, int *pbuffer, int *pbufmax)\ + {\ + LONG nPos; + +#define END_MIX_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + } + +// Volume Ramps +#define BEGIN_RAMPMIX_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + LONG nRampRightVol = pChannel->nRampRightVol;\ + LONG nRampLeftVol = pChannel->nRampLeftVol; + +#define END_RAMPMIX_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + pChannel->nRampRightVol = nRampRightVol;\ + pChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\ + pChannel->nRampLeftVol = nRampLeftVol;\ + pChannel->nLeftVol = nRampLeftVol >> VOLUMERAMPPRECISION;\ + } + +#define BEGIN_FASTRAMPMIX_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + LONG nRampRightVol = pChannel->nRampRightVol; + +#define END_FASTRAMPMIX_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + pChannel->nRampRightVol = nRampRightVol;\ + pChannel->nRampLeftVol = nRampRightVol;\ + pChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\ + pChannel->nLeftVol = pChannel->nRightVol;\ + } + + +// Mono Resonant Filters +#define BEGIN_MIX_FLT_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + MIX_BEGIN_FILTER + + +#define END_MIX_FLT_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + MIX_END_FILTER\ + } + +#define BEGIN_RAMPMIX_FLT_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + LONG nRampRightVol = pChannel->nRampRightVol;\ + LONG nRampLeftVol = pChannel->nRampLeftVol;\ + MIX_BEGIN_FILTER + +#define END_RAMPMIX_FLT_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + MIX_END_FILTER\ + pChannel->nRampRightVol = nRampRightVol;\ + pChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\ + pChannel->nRampLeftVol = nRampLeftVol;\ + pChannel->nLeftVol = nRampLeftVol >> VOLUMERAMPPRECISION;\ + } + +// Stereo Resonant Filters +#define BEGIN_MIX_STFLT_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + MIX_BEGIN_STEREO_FILTER + + +#define END_MIX_STFLT_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + MIX_END_STEREO_FILTER\ + } + +#define BEGIN_RAMPMIX_STFLT_INTERFACE(func)\ + BEGIN_MIX_INTERFACE(func)\ + LONG nRampRightVol = pChannel->nRampRightVol;\ + LONG nRampLeftVol = pChannel->nRampLeftVol;\ + MIX_BEGIN_STEREO_FILTER + +#define END_RAMPMIX_STFLT_INTERFACE()\ + SNDMIX_ENDSAMPLELOOP\ + MIX_END_STEREO_FILTER\ + pChannel->nRampRightVol = nRampRightVol;\ + pChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\ + pChannel->nRampLeftVol = nRampLeftVol;\ + pChannel->nLeftVol = nRampLeftVol >> VOLUMERAMPPRECISION;\ + } + + +///////////////////////////////////////////////////// +// + +extern void StereoMixToFloat(const int *pSrc, float *pOut1, float *pOut2, UINT nCount, const float _i2fc); +extern void FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount, const float _f2ic); +extern void MonoMixToFloat(const int *pSrc, float *pOut, UINT nCount, const float _i2fc); +extern void FloatToMonoMix(const float *pIn, int *pOut, UINT nCount, const float _f2ic); + +void InitMixBuffer(int *pBuffer, UINT nSamples); +void EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples); +void StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs); + +void StereoMixToFloat(const int *, float *, float *, UINT nCount); +void FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount); + +///////////////////////////////////////////////////// +// Mono samples functions + +BEGIN_MIX_INTERFACE(Mono8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Mono16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_STOREMONOVOL +END_MIX_INTERFACE() + + +// Volume Ramps +BEGIN_RAMPMIX_INTERFACE(Mono8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Mono16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_INTERFACE() + + +////////////////////////////////////////////////////// +// Fast mono mix for leftvol=rightvol (1 less imul) + +BEGIN_MIX_INTERFACE(FastMono8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(FastMono16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_STOREFASTMONOVOL +END_MIX_INTERFACE() + + +// Fast Ramps +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + +BEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_RAMPFASTMONOVOL +END_FASTRAMPMIX_INTERFACE() + + +////////////////////////////////////////////////////// +// Stereo samples + +BEGIN_MIX_INTERFACE(Stereo8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + +BEGIN_MIX_INTERFACE(Stereo16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_STORESTEREOVOL +END_MIX_INTERFACE() + + +// Volume Ramps +BEGIN_RAMPMIX_INTERFACE(Stereo8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + +BEGIN_RAMPMIX_INTERFACE(Stereo16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_INTERFACE() + + + +////////////////////////////////////////////////////// +// Resonant Filter Mix + +#ifndef NO_FILTER + +// Mono Filter Mix +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +BEGIN_MIX_FLT_INTERFACE(FilterMono16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_STOREMONOVOL +END_MIX_FLT_INTERFACE() + +// Filter + Ramp +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16NOIDO + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16LINEAR + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16SPLINE + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETMONOVOL8FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + +BEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETMONOVOL16FIRFILTER + SNDMIX_PROCESSFILTER + SNDMIX_RAMPMONOVOL +END_RAMPMIX_FLT_INTERFACE() + + +// Stereo Filter Mix +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitLinearMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitLinearMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitSplineMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitSplineMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +BEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitFirFilterMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_STORESTEREOVOL +END_MIX_STFLT_INTERFACE() + +// Stereo Filter + Ramp +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16NOIDO + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitLinearRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16LINEAR + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitSplineRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16SPLINE + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP8 + SNDMIX_GETSTEREOVOL8FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + +BEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitFirFilterRampMix) + SNDMIX_BEGINSAMPLELOOP16 + SNDMIX_GETSTEREOVOL16FIRFILTER + SNDMIX_PROCESSSTEREOFILTER + SNDMIX_RAMPSTEREOVOL +END_RAMPMIX_STFLT_INTERFACE() + + +#else +// Mono +#define FilterMono8BitMix Mono8BitMix +#define FilterMono16BitMix Mono16BitMix +#define FilterMono8BitLinearMix Mono8BitLinearMix +#define FilterMono16BitLinearMix Mono16BitLinearMix +#define FilterMono8BitSplineMix Mono8BitSplineMix +#define FilterMono16BitSplineMix Mono16BitSplineMix +#define FilterMono8BitFirFilterMix Mono8BitFirFilterMix +#define FilterMono16BitFirFilterMix Mono16BitFirFilterMix +#define FilterMono8BitRampMix Mono8BitRampMix +#define FilterMono16BitRampMix Mono16BitRampMix +#define FilterMono8BitLinearRampMix Mono8BitLinearRampMix +#define FilterMono16BitLinearRampMix Mono16BitLinearRampMix +#define FilterMono8BitSplineRampMix Mono8BitSplineRampMix +#define FilterMono16BitSplineRampMix Mono16BitSplineRampMix +#define FilterMono8BitFirFilterRampMix Mono8BitFirFilterRampMix +#define FilterMono16BitFirFilterRampMix Mono16BitFirFilterRampMix +// Stereo +#define FilterStereo8BitMix Stereo8BitMix +#define FilterStereo16BitMix Stereo16BitMix +#define FilterStereo8BitLinearMix Stereo8BitLinearMix +#define FilterStereo16BitLinearMix Stereo16BitLinearMix +#define FilterStereo8BitSplineMix Stereo8BitSplineMix +#define FilterStereo16BitSplineMix Stereo16BitSplineMix +#define FilterStereo8BitFirFilterMix Stereo8BitFirFilterMix +#define FilterStereo16BitFirFilterMix Stereo16BitFirFilterMix +#define FilterStereo8BitRampMix Stereo8BitRampMix +#define FilterStereo16BitRampMix Stereo16BitRampMix +#define FilterStereo8BitLinearRampMix Stereo8BitLinearRampMix +#define FilterStereo16BitLinearRampMix Stereo16BitLinearRampMix +#define FilterStereo8BitSplineRampMix Stereo8BitSplineRampMix +#define FilterStereo16BitSplineRampMix Stereo16BitSplineRampMix +#define FilterStereo8BitFirFilterRampMix Stereo8BitFirFilterRampMix +#define FilterStereo16BitFirFilterRampMix Stereo16BitFirFilterRampMix + +#endif + +///////////////////////////////////////////////////////////////////////////////////// +// +// Mix function tables +// +// +// Index is as follow: +// [b1-b0] format (8-bit-mono, 16-bit-mono, 8-bit-stereo, 16-bit-stereo) +// [b2] ramp +// [b3] filter +// [b5-b4] src type +// + +#define MIXNDX_16BIT 0x01 +#define MIXNDX_STEREO 0x02 +#define MIXNDX_RAMP 0x04 +#define MIXNDX_FILTER 0x08 +#define MIXNDX_LINEARSRC 0x10 +#define MIXNDX_SPLINESRC 0x20 +#define MIXNDX_FIRSRC 0x30 + +const LPMIXINTERFACE gpMixFunctionTable[2*2*16] = +{ + // No SRC + Mono8BitMix, Mono16BitMix, Stereo8BitMix, Stereo16BitMix, + Mono8BitRampMix, Mono16BitRampMix, Stereo8BitRampMix, + Stereo16BitRampMix, + // No SRC, Filter + FilterMono8BitMix, FilterMono16BitMix, FilterStereo8BitMix, + FilterStereo16BitMix, FilterMono8BitRampMix, FilterMono16BitRampMix, + FilterStereo8BitRampMix, FilterStereo16BitRampMix, + // Linear SRC + Mono8BitLinearMix, Mono16BitLinearMix, Stereo8BitLinearMix, + Stereo16BitLinearMix, Mono8BitLinearRampMix, Mono16BitLinearRampMix, + Stereo8BitLinearRampMix,Stereo16BitLinearRampMix, + // Linear SRC, Filter + FilterMono8BitLinearMix, FilterMono16BitLinearMix, + FilterStereo8BitLinearMix, FilterStereo16BitLinearMix, + FilterMono8BitLinearRampMix, FilterMono16BitLinearRampMix, + FilterStereo8BitLinearRampMix, FilterStereo16BitLinearRampMix, + + // FirFilter SRC + Mono8BitSplineMix, Mono16BitSplineMix, Stereo8BitSplineMix, + Stereo16BitSplineMix, Mono8BitSplineRampMix, Mono16BitSplineRampMix, + Stereo8BitSplineRampMix,Stereo16BitSplineRampMix, + // Spline SRC, Filter + FilterMono8BitSplineMix, FilterMono16BitSplineMix, + FilterStereo8BitSplineMix, FilterStereo16BitSplineMix, + FilterMono8BitSplineRampMix, FilterMono16BitSplineRampMix, + FilterStereo8BitSplineRampMix, FilterStereo16BitSplineRampMix, + + // FirFilter SRC + Mono8BitFirFilterMix, Mono16BitFirFilterMix, Stereo8BitFirFilterMix, + Stereo16BitFirFilterMix, Mono8BitFirFilterRampMix, + Mono16BitFirFilterRampMix, Stereo8BitFirFilterRampMix, + Stereo16BitFirFilterRampMix, + // FirFilter SRC, Filter + FilterMono8BitFirFilterMix, FilterMono16BitFirFilterMix, + FilterStereo8BitFirFilterMix, FilterStereo16BitFirFilterMix, + FilterMono8BitFirFilterRampMix, FilterMono16BitFirFilterRampMix, + FilterStereo8BitFirFilterRampMix, FilterStereo16BitFirFilterRampMix +}; + +const LPMIXINTERFACE gpFastMixFunctionTable[2*2*16] = +{ + // No SRC + FastMono8BitMix, FastMono16BitMix, Stereo8BitMix, Stereo16BitMix, + FastMono8BitRampMix, FastMono16BitRampMix, Stereo8BitRampMix, + Stereo16BitRampMix, + // No SRC, Filter + FilterMono8BitMix, FilterMono16BitMix, FilterStereo8BitMix, + FilterStereo16BitMix, FilterMono8BitRampMix, FilterMono16BitRampMix, + FilterStereo8BitRampMix, FilterStereo16BitRampMix, + // Linear SRC + FastMono8BitLinearMix, FastMono16BitLinearMix, Stereo8BitLinearMix, + Stereo16BitLinearMix, FastMono8BitLinearRampMix, + FastMono16BitLinearRampMix, Stereo8BitLinearRampMix, + Stereo16BitLinearRampMix, + // Linear SRC, Filter + FilterMono8BitLinearMix, FilterMono16BitLinearMix, + FilterStereo8BitLinearMix, FilterStereo16BitLinearMix, + FilterMono8BitLinearRampMix, FilterMono16BitLinearRampMix, + FilterStereo8BitLinearRampMix, FilterStereo16BitLinearRampMix, + + // Spline SRC + Mono8BitSplineMix, Mono16BitSplineMix, Stereo8BitSplineMix, + Stereo16BitSplineMix, Mono8BitSplineRampMix, Mono16BitSplineRampMix, + Stereo8BitSplineRampMix, Stereo16BitSplineRampMix, + // Spline SRC, Filter + FilterMono8BitSplineMix, FilterMono16BitSplineMix, + FilterStereo8BitSplineMix, FilterStereo16BitSplineMix, + FilterMono8BitSplineRampMix, FilterMono16BitSplineRampMix, + FilterStereo8BitSplineRampMix, FilterStereo16BitSplineRampMix, + + // FirFilter SRC + Mono8BitFirFilterMix, Mono16BitFirFilterMix, Stereo8BitFirFilterMix, + Stereo16BitFirFilterMix, Mono8BitFirFilterRampMix, + Mono16BitFirFilterRampMix, Stereo8BitFirFilterRampMix, + Stereo16BitFirFilterRampMix, + // FirFilter SRC, Filter + FilterMono8BitFirFilterMix, FilterMono16BitFirFilterMix, + FilterStereo8BitFirFilterMix, FilterStereo16BitFirFilterMix, + FilterMono8BitFirFilterRampMix, FilterMono16BitFirFilterRampMix, + FilterStereo8BitFirFilterRampMix, FilterStereo16BitFirFilterRampMix, +}; + + +///////////////////////////////////////////////////////////////////////// + +static LONG MPPFASTCALL GetSampleCount(MODCHANNEL *pChn, LONG nSamples) +//--------------------------------------------------------------------- +{ + LONG nLoopStart = (pChn->dwFlags & CHN_LOOP) ? pChn->nLoopStart : 0; + LONG nInc = pChn->nInc; + + if ((nSamples <= 0) || (!nInc) || (!pChn->nLength)) return 0; + // Under zero ? + if ((LONG)pChn->nPos < nLoopStart) + { + if (nInc < 0) + { + // Invert loop for bidi loops + LONG nDelta = ((nLoopStart - pChn->nPos) << 16) - (pChn->nPosLo & 0xffff); + pChn->nPos = nLoopStart | (nDelta>>16); + pChn->nPosLo = nDelta & 0xffff; + if (((LONG)pChn->nPos < nLoopStart) || (pChn->nPos >= (nLoopStart+pChn->nLength)/2)) + { + pChn->nPos = nLoopStart; pChn->nPosLo = 0; + } + nInc = -nInc; + pChn->nInc = nInc; + pChn->dwFlags &= ~(CHN_PINGPONGFLAG); // go forward + if ((!(pChn->dwFlags & CHN_LOOP)) || (pChn->nPos >= pChn->nLength)) + { + pChn->nPos = pChn->nLength; + pChn->nPosLo = 0; + return 0; + } + } else + { + // We probably didn't hit the loop end yet (first loop), so we do nothing + if ((LONG)pChn->nPos < 0) pChn->nPos = 0; + } + } else + // Past the end + if (pChn->nPos >= pChn->nLength) + { + if (!(pChn->dwFlags & CHN_LOOP)) return 0; // not looping -> stop this channel + if (pChn->dwFlags & CHN_PINGPONGLOOP) + { + // Invert loop + if (nInc > 0) + { + nInc = -nInc; + pChn->nInc = nInc; + } + pChn->dwFlags |= CHN_PINGPONGFLAG; + // adjust loop position + LONG nDeltaHi = (pChn->nPos - pChn->nLength); + LONG nDeltaLo = 0x10000 - (pChn->nPosLo & 0xffff); + pChn->nPos = pChn->nLength - nDeltaHi - (nDeltaLo>>16); + pChn->nPosLo = nDeltaLo & 0xffff; + if ((pChn->nPos <= pChn->nLoopStart) || (pChn->nPos >= pChn->nLength)) pChn->nPos = pChn->nLength-1; + } else + { + if (nInc < 0) // This is a bug + { + nInc = -nInc; + pChn->nInc = nInc; + } + // Restart at loop start + pChn->nPos += nLoopStart - pChn->nLength; + if ((LONG)pChn->nPos < nLoopStart) pChn->nPos = pChn->nLoopStart; + } + } + LONG nPos = pChn->nPos; + // too big increment, and/or too small loop length + if (nPos < nLoopStart) + { + if ((nPos < 0) || (nInc < 0)) return 0; + } + if ((nPos < 0) || (nPos >= (LONG)pChn->nLength)) return 0; + LONG nPosLo = (USHORT)pChn->nPosLo, nSmpCount = nSamples; + if (nInc < 0) + { + LONG nInv = -nInc; + LONG maxsamples = 16384 / ((nInv>>16)+1); + if (maxsamples < 2) maxsamples = 2; + if (nSamples > maxsamples) nSamples = maxsamples; + LONG nDeltaHi = (nInv>>16) * (nSamples - 1); + LONG nDeltaLo = (nInv&0xffff) * (nSamples - 1); + LONG nPosDest = nPos - nDeltaHi + ((nPosLo - nDeltaLo) >> 16); + if (nPosDest < nLoopStart) + { + nSmpCount = (ULONG)(((((LONGLONG)nPos - nLoopStart) << 16) + nPosLo - 1) / nInv) + 1; + } + } else + { + LONG maxsamples = 16384 / ((nInc>>16)+1); + if (maxsamples < 2) maxsamples = 2; + if (nSamples > maxsamples) nSamples = maxsamples; + LONG nDeltaHi = (nInc>>16) * (nSamples - 1); + LONG nDeltaLo = (nInc&0xffff) * (nSamples - 1); + LONG nPosDest = nPos + nDeltaHi + ((nPosLo + nDeltaLo)>>16); + if (nPosDest >= (LONG)pChn->nLength) + { + nSmpCount = (ULONG)(((((LONGLONG)pChn->nLength - nPos) << 16) - nPosLo - 1) / nInc) + 1; + } + } + if (nSmpCount <= 1) return 1; + if (nSmpCount > nSamples) return nSamples; + return nSmpCount; +} + + +UINT CSoundFile::CreateStereoMix(int count) +//----------------------------------------- +{ + LPLONG pOfsL, pOfsR; + DWORD nchused, nchmixed; + + if (!count) return 0; + if (gnChannels > 2) InitMixBuffer(MixRearBuffer, count*2); + nchused = nchmixed = 0; + for (UINT nChn=0; nChnpCurrentSample) continue; + nMasterCh = (ChnMix[nChn] < m_nChannels) ? ChnMix[nChn]+1 : pChannel->nMasterChn; + pOfsR = &gnDryROfsVol; + pOfsL = &gnDryLOfsVol; + nFlags = 0; + if (pChannel->dwFlags & CHN_16BIT) nFlags |= MIXNDX_16BIT; + if (pChannel->dwFlags & CHN_STEREO) nFlags |= MIXNDX_STEREO; + #ifndef NO_FILTER + if (pChannel->dwFlags & CHN_FILTER) nFlags |= MIXNDX_FILTER; + #endif + if (!(pChannel->dwFlags & CHN_NOIDO) + && !(gdwSoundSetup & SNDMIX_NORESAMPLING)) + { + // use hq-fir mixer? + if( (gdwSoundSetup & (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE)) == (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE) ) + nFlags += MIXNDX_FIRSRC; + else if( (gdwSoundSetup & SNDMIX_HQRESAMPLER)) + nFlags += MIXNDX_SPLINESRC; + else + nFlags += MIXNDX_LINEARSRC; // use + } + if ((nFlags < 0x40) && (pChannel->nLeftVol == pChannel->nRightVol) + && ((!pChannel->nRampLength) || (pChannel->nLeftRamp == pChannel->nRightRamp))) + { + pMixFuncTable = gpFastMixFunctionTable; + } else + { + pMixFuncTable = gpMixFunctionTable; + } + nsamples = count; +#ifndef MODPLUG_NO_REVERB + pbuffer = (gdwSoundSetup & SNDMIX_REVERB) ? MixReverbBuffer : MixSoundBuffer; + if (pChannel->dwFlags & CHN_NOREVERB) pbuffer = MixSoundBuffer; + if (pChannel->dwFlags & CHN_REVERB) pbuffer = MixReverbBuffer; + if (pbuffer == MixReverbBuffer) + { + if (!gnReverbSend) memset(MixReverbBuffer, 0, count * 8); + gnReverbSend += count; + } +#else + pbuffer = MixSoundBuffer; +#endif + nchused++; + //////////////////////////////////////////////////// + SampleLooping: + UINT nrampsamples = nsamples; + if (pChannel->nRampLength > 0) + { + if ((LONG)nrampsamples > pChannel->nRampLength) nrampsamples = pChannel->nRampLength; + } + if ((nSmpCount = GetSampleCount(pChannel, nrampsamples)) <= 0) + { + // Stopping the channel + pChannel->pCurrentSample = NULL; + pChannel->nLength = 0; + pChannel->nPos = 0; + pChannel->nPosLo = 0; + pChannel->nRampLength = 0; + EndChannelOfs(pChannel, pbuffer, nsamples); + *pOfsR += pChannel->nROfs; + *pOfsL += pChannel->nLOfs; + pChannel->nROfs = pChannel->nLOfs = 0; + pChannel->dwFlags &= ~CHN_PINGPONGFLAG; + continue; + } + // Should we mix this channel ? + UINT naddmix; + if (((nchmixed >= m_nMaxMixChannels) && (!(gdwSoundSetup & SNDMIX_DIRECTTODISK))) + || ((!pChannel->nRampLength) && (!(pChannel->nLeftVol|pChannel->nRightVol)))) + { + LONG delta = (pChannel->nInc * (LONG)nSmpCount) + (LONG)pChannel->nPosLo; + pChannel->nPosLo = delta & 0xFFFF; + pChannel->nPos += (delta >> 16); + pChannel->nROfs = pChannel->nLOfs = 0; + pbuffer += nSmpCount*2; + naddmix = 0; + } else + // Do mixing + { + // Choose function for mixing + LPMIXINTERFACE pMixFunc; + pMixFunc = (pChannel->nRampLength) ? pMixFuncTable[nFlags|MIXNDX_RAMP] : pMixFuncTable[nFlags]; + int *pbufmax = pbuffer + (nSmpCount*2); + pChannel->nROfs = - *(pbufmax-2); + pChannel->nLOfs = - *(pbufmax-1); + pMixFunc(pChannel, pbuffer, pbufmax); + pChannel->nROfs += *(pbufmax-2); + pChannel->nLOfs += *(pbufmax-1); + pbuffer = pbufmax; + naddmix = 1; + + } + nsamples -= nSmpCount; + if (pChannel->nRampLength) + { + pChannel->nRampLength -= nSmpCount; + if (pChannel->nRampLength <= 0) + { + pChannel->nRampLength = 0; + pChannel->nRightVol = pChannel->nNewRightVol; + pChannel->nLeftVol = pChannel->nNewLeftVol; + pChannel->nRightRamp = pChannel->nLeftRamp = 0; + if ((pChannel->dwFlags & CHN_NOTEFADE) && (!(pChannel->nFadeOutVol))) + { + pChannel->nLength = 0; + pChannel->pCurrentSample = NULL; + } + } + } + if (nsamples > 0) goto SampleLooping; + nchmixed += naddmix; + } + return nchused; +} + +static float f2ic = (float)(1 << 28); +static float i2fc = (float)(1.0 / (1 << 28)); + +VOID CSoundFile::StereoMixToFloat(const int *pSrc, float *pOut1, float *pOut2, UINT nCount) +//----------------------------------------------------------------------------------------- +{ + double tmp; + for (UINT i = 0; i < nCount; i++) { + *pOut1++ = *pSrc * i2fc; /*!*/ + pSrc++; + + *pOut2++ = *pSrc * i2fc; /*!*/ + pSrc++; + } +} + + +VOID CSoundFile::FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount) +//--------------------------------------------------------------------------------------------- +{ + for (UINT i = 0; i < nCount; i++) { + *pOut++ = (int)(*pIn1 * f2ic); + *pOut++ = (int)(*pIn2 * f2ic); + pIn1++; + pIn2++; + } +} + + +VOID CSoundFile::MonoMixToFloat(const int *pSrc, float *pOut, UINT nCount) +//------------------------------------------------------------------------ +{ + double tmp; + for (UINT i = 0; i < nCount; i++) { + *pOut++ = *pSrc * i2fc; /*!*/ + pSrc++; + } +} + + +VOID CSoundFile::FloatToMonoMix(const float *pIn, int *pOut, UINT nCount) +//----------------------------------------------------------------------- +{ + for (UINT i = 0; i < nCount; i++) { + *pOut++ = (int)(*pIn * f2ic); /*!*/ + pIn++; + } +} + + +// Clip and convert to 8 bit +//---GCCFIX: Asm replaced with C function +// The C version was written by Rani Assaf , I believe +DWORD Convert32To8(LPVOID lp8, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax) +{ + int vumin = *lpMin, vumax = *lpMax; + unsigned char *p = (unsigned char *)lp8; + for (UINT i=0; i MIXING_CLIPMAX) + n = MIXING_CLIPMAX; + if (n < vumin) + vumin = n; + else if (n > vumax) + vumax = n; + p[i] = (n >> (24-MIXING_ATTENUATION)) ^ 0x80; // 8-bit unsigned + } + *lpMin = vumin; + *lpMax = vumax; + return lSampleCount; +} +//---GCCFIX: Asm replaced with C function +// The C version was written by Rani Assaf , I believe +DWORD Convert32To16(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax) +{ + int vumin = *lpMin, vumax = *lpMax; + signed short *p = (signed short *)lp16; + for (UINT i=0; i MIXING_CLIPMAX) + n = MIXING_CLIPMAX; + if (n < vumin) + vumin = n; + else if (n > vumax) + vumax = n; + p[i] = n >> (16-MIXING_ATTENUATION); // 16-bit signed + } + *lpMin = vumin; + *lpMax = vumax; + return lSampleCount * 2; +} +//---GCCFIX: Asm replaced with C function +// 24-bit audio not supported. +DWORD Convert32To24(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax) +{ + return 0; +} +//---GCCFIX: Asm replaced with C function +// 32-bit audio not supported +DWORD Convert32To32(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax) +{ + return 0; +} +//---GCCFIX: Asm replaced with C function +// Will fill in later. +void InitMixBuffer(int *pBuffer, UINT nSamples) +{ + memset(pBuffer, 0, nSamples * sizeof(int)); +} + + + + + + +//---GCCFIX: Asm replaced with C function +void InterleaveFrontRear(int *pFrontBuf, int *pRearBuf, DWORD nSamples) +{ + DWORD i; + + pRearBuf[i] = pFrontBuf[1]; + for (i = 1; i < nSamples; i++) { + pRearBuf[i] = pFrontBuf[(i*2)+1]; + pFrontBuf[i] = pFrontBuf[i*2]; + } +} +//---GCCFIX: Asm replaced with C function +VOID MonoFromStereo(int *pMixBuf, UINT nSamples) +{ + UINT j; + for(UINT i = 0; i < nSamples; i++) + { + j = i << 1; + pMixBuf[i] = (pMixBuf[j] + pMixBuf[j + 1]) >> 1; + } +} + +//---GCCFIX: Asm replaced with C function +#define OFSDECAYSHIFT 8 +#define OFSDECAYMASK 0xFF +void StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs) +//--------------------------------------------------------------------------------------------------------- +{ + int rofs = *lpROfs; + int lofs = *lpLOfs; + + if ((!rofs) && (!lofs)) + { + InitMixBuffer(pBuffer, nSamples*2); + return; + } + for (UINT i=0; i>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + int x_l = (lofs + (((-lofs)>>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + rofs -= x_r; + lofs -= x_l; + pBuffer[i*2] = x_r; + pBuffer[i*2+1] = x_l; + } + *lpROfs = rofs; + *lpLOfs = lofs; +} +//---GCCFIX: Asm replaced with C function +// Will fill in later. +void EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples) +{ + int rofs = pChannel->nROfs; + int lofs = pChannel->nLOfs; + + if ((!rofs) && (!lofs)) return; + for (UINT i=0; i>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + int x_l = (lofs + (((-lofs)>>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT; + rofs -= x_r; + lofs -= x_l; + pBuffer[i*2] += x_r; + pBuffer[i*2+1] += x_l; + } + pChannel->nROfs = rofs; + pChannel->nLOfs = lofs; +} + + + + + +////////////////////////////////////////////////////////////////////////////////// +// Automatic Gain Control + +#ifndef NO_AGC + +// Limiter +#define MIXING_LIMITMAX (0x08100000) +#define MIXING_LIMITMIN (-MIXING_LIMITMAX) + +void CSoundFile::ProcessAGC(int count) +//------------------------------------ +{ + static DWORD gAGCRecoverCount = 0; + UINT agc = AGC(MixSoundBuffer, count, gnAGC); + // Some kind custom law, so that the AGC stays quite stable, but slowly + // goes back up if the sound level stays below a level inversely proportional + // to the AGC level. (J'me comprends) + if ((agc >= gnAGC) && (gnAGC < AGC_UNITY) && (gnVUMeter < (0xFF - (gnAGC >> (AGC_PRECISION-7))) )) + { + gAGCRecoverCount += count; + UINT agctimeout = gdwMixingFreq + gnAGC; + if (gnChannels >= 2) agctimeout <<= 1; + if (gAGCRecoverCount >= agctimeout) + { + gAGCRecoverCount = 0; + gnAGC++; + } + } else + { + gnAGC = agc; + gAGCRecoverCount = 0; + } +} + + + +void CSoundFile::ResetAGC() +//------------------------- +{ + gnAGC = AGC_UNITY; +} + +#endif // NO_AGC + diff --git a/modplug/it_defs.h b/modplug/it_defs.h new file mode 100644 index 000000000..84355a75a --- /dev/null +++ b/modplug/it_defs.h @@ -0,0 +1,135 @@ +#ifndef _ITDEFS_H_ +#define _ITDEFS_H_ + +#pragma pack(1) + +typedef struct tagITFILEHEADER +{ + DWORD id; // 0x4D504D49 + CHAR songname[26]; + BYTE hilight_minor; + BYTE hilight_major; + WORD ordnum; + WORD insnum; + WORD smpnum; + WORD patnum; + WORD cwtv; + WORD cmwt; + WORD flags; + WORD special; + BYTE globalvol; + BYTE mv; + BYTE speed; + BYTE tempo; + BYTE sep; + BYTE pwd; + WORD msglength; + DWORD msgoffset; + DWORD reserved2; + BYTE chnpan[64]; + BYTE chnvol[64]; +} ITFILEHEADER; + + +typedef struct tagITENVELOPE +{ + BYTE flags; + BYTE num; + BYTE lpb; + BYTE lpe; + BYTE slb; + BYTE sle; + BYTE data[25*3]; + BYTE reserved; +} ITENVELOPE; + +// Old Impulse Instrument Format (cmwt < 0x200) +typedef struct tagITOLDINSTRUMENT +{ + DWORD id; // IMPI = 0x49504D49 + CHAR filename[12]; // DOS file name + BYTE zero; + BYTE flags; + BYTE vls; + BYTE vle; + BYTE sls; + BYTE sle; + WORD reserved1; + WORD fadeout; + BYTE nna; + BYTE dnc; + WORD trkvers; + BYTE nos; + BYTE reserved2; + CHAR name[26]; + WORD reserved3[3]; + BYTE keyboard[240]; + BYTE volenv[200]; + BYTE nodes[50]; +} ITOLDINSTRUMENT; + + +// Impulse Instrument Format +typedef struct tagITINSTRUMENT +{ + DWORD id; + CHAR filename[12]; + BYTE zero; + BYTE nna; + BYTE dct; + BYTE dca; + WORD fadeout; + signed char pps; + BYTE ppc; + BYTE gbv; + BYTE dfp; + BYTE rv; + BYTE rp; + WORD trkvers; + BYTE nos; + BYTE reserved1; + CHAR name[26]; + BYTE ifc; + BYTE ifr; + BYTE mch; + BYTE mpr; + WORD mbank; + BYTE keyboard[240]; + ITENVELOPE volenv; + ITENVELOPE panenv; + ITENVELOPE pitchenv; + BYTE dummy[4]; // was 7, but IT v2.17 saves 554 bytes +} ITINSTRUMENT; + + +// IT Sample Format +typedef struct ITSAMPLESTRUCT +{ + DWORD id; // 0x53504D49 + CHAR filename[12]; + BYTE zero; + BYTE gvl; + BYTE flags; + BYTE vol; + CHAR name[26]; + BYTE cvt; + BYTE dfp; + DWORD length; + DWORD loopbegin; + DWORD loopend; + DWORD C5Speed; + DWORD susloopbegin; + DWORD susloopend; + DWORD samplepointer; + BYTE vis; + BYTE vid; + BYTE vir; + BYTE vit; +} ITSAMPLESTRUCT; + +#pragma pack() + +extern BYTE autovibit2xm[8]; +extern BYTE autovibxm2it[8]; + +#endif diff --git a/modplug/load_669.cpp b/modplug/load_669.cpp new file mode 100644 index 000000000..b7bd11723 --- /dev/null +++ b/modplug/load_669.cpp @@ -0,0 +1,191 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +//////////////////////////////////////////////////////////// +// 669 Composer / UNIS 669 module loader +//////////////////////////////////////////////////////////// + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +typedef struct tagFILEHEADER669 +{ + WORD sig; // 'if' or 'JN' + signed char songmessage[108]; // Song Message + BYTE samples; // number of samples (1-64) + BYTE patterns; // number of patterns (1-128) + BYTE restartpos; + BYTE orders[128]; + BYTE tempolist[128]; + BYTE breaks[128]; +} FILEHEADER669; + + +typedef struct tagSAMPLE669 +{ + BYTE filename[13]; + BYTE length[4]; // when will somebody think about DWORD align ??? + BYTE loopstart[4]; + BYTE loopend[4]; +} SAMPLE669; + + +BOOL CSoundFile::Read669(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + BOOL b669Ext; + const FILEHEADER669 *pfh = (const FILEHEADER669 *)lpStream; + const SAMPLE669 *psmp = (const SAMPLE669 *)(lpStream + 0x1F1); + DWORD dwMemPos = 0; + + if ((!lpStream) || (dwMemLength < sizeof(FILEHEADER669))) return FALSE; + if ((bswapLE16(pfh->sig) != 0x6669) && (bswapLE16(pfh->sig) != 0x4E4A)) return FALSE; + b669Ext = (bswapLE16(pfh->sig) == 0x4E4A) ? TRUE : FALSE; + if ((!pfh->samples) || (pfh->samples > 64) || (pfh->restartpos >= 128) + || (!pfh->patterns) || (pfh->patterns > 128)) return FALSE; + DWORD dontfuckwithme = 0x1F1 + pfh->samples * sizeof(SAMPLE669) + pfh->patterns * 0x600; + if (dontfuckwithme > dwMemLength) return FALSE; + for (int n = 0; n < 128; n++) + if (pfh->breaks[n] > 0x3f) + return false; + // That should be enough checking: this must be a 669 module. + m_nType = MOD_TYPE_669; + m_dwSongFlags |= SONG_LINEARSLIDES; + m_nMinPeriod = 28 << 2; + m_nMaxPeriod = 1712 << 3; + m_nDefaultTempo = 125; + m_nDefaultSpeed = 6; + m_nChannels = 8; + memcpy(m_szNames[0], pfh->songmessage, 31); + m_szNames[0][31] = 0; + m_nSamples = pfh->samples; + for (UINT nins=1; nins<=m_nSamples; nins++, psmp++) + { + DWORD len = bswapLE32(*((DWORD *)(&psmp->length))); + DWORD loopstart = bswapLE32(*((DWORD *)(&psmp->loopstart))); + DWORD loopend = bswapLE32(*((DWORD *)(&psmp->loopend))); + if (len > MAX_SAMPLE_LENGTH) len = MAX_SAMPLE_LENGTH; + if ((loopend > len) && (!loopstart)) loopend = 0; + if (loopend > len) loopend = len; + if (loopstart + 4 >= loopend) loopstart = loopend = 0; + Ins[nins].nLength = len; + Ins[nins].nLoopStart = loopstart; + Ins[nins].nLoopEnd = loopend; + if (loopend) Ins[nins].uFlags |= CHN_LOOP; + memcpy(m_szNames[nins], psmp->filename, 13); + Ins[nins].nVolume = 256; + Ins[nins].nGlobalVol = 64; + Ins[nins].nPan = 128; + } + // Song Message + m_lpszSongComments = new char[114]; + memcpy(m_lpszSongComments, pfh->songmessage, 36); + m_lpszSongComments[36] = '\015'; + m_lpszSongComments[37] = '\012'; + memcpy(m_lpszSongComments + 38, pfh->songmessage + 36, 36); + m_lpszSongComments[74] = '\015'; + m_lpszSongComments[75] = '\012'; + memcpy(m_lpszSongComments + 76, pfh->songmessage + 72, 36); + m_lpszSongComments[113] = 0; + // Reading Orders + memcpy(Order, pfh->orders, 128); + m_nRestartPos = pfh->restartpos; + if (Order[m_nRestartPos] >= pfh->patterns) m_nRestartPos = 0; + // Reading Pattern Break Locations + for (UINT npan=0; npan<8; npan++) + { + ChnSettings[npan].nPan = (npan & 1) ? 0x30 : 0xD0; + ChnSettings[npan].nVolume = 64; + } + // Reading Patterns + dwMemPos = 0x1F1 + pfh->samples * 25; + for (UINT npat=0; npatpatterns; npat++) + { + Patterns[npat] = AllocatePattern(64, m_nChannels); + if (!Patterns[npat]) break; + PatternSize[npat] = 64; + PatternAllocSize[npat] = 64; + MODCOMMAND *m = Patterns[npat]; + const BYTE *p = lpStream + dwMemPos; + for (UINT row=0; row<64; row++) + { + MODCOMMAND *mspeed = m; + if ((row == pfh->breaks[npat]) && (row != 63)) + { + for (UINT i=0; i<8; i++) + { + m[i].command = CMD_PATTERNBREAK; + m[i].param = 0; + } + } + for (UINT n=0; n<8; n++, m++, p+=3) + { + UINT note = p[0] >> 2; + UINT instr = ((p[0] & 0x03) << 4) | (p[1] >> 4); + UINT vol = p[1] & 0x0F; + if (p[0] < 0xFE) + { + m->note = note + 37; + m->instr = instr + 1; + } + if (p[0] <= 0xFE) + { + m->volcmd = VOLCMD_VOLUME; + m->vol = (vol << 2) + 2; + } + if (p[2] != 0xFF) + { + UINT command = p[2] >> 4; + UINT param = p[2] & 0x0F; + switch(command) + { + case 0x00: command = CMD_PORTAMENTOUP; break; + case 0x01: command = CMD_PORTAMENTODOWN; break; + case 0x02: command = CMD_TONEPORTAMENTO; break; + case 0x03: command = CMD_MODCMDEX; param |= 0x50; break; + case 0x04: command = CMD_VIBRATO; param |= 0x40; break; + case 0x05: if (param) command = CMD_SPEED; else command = 0; param += 2; break; + case 0x06: if (param == 0) { command = CMD_PANNINGSLIDE; param = 0xFE; } else + if (param == 1) { command = CMD_PANNINGSLIDE; param = 0xEF; } else + command = 0; + break; + default: command = 0; + } + if (command) + { + if (command == CMD_SPEED) mspeed = NULL; + m->command = command; + m->param = param; + } + } + } + if ((!row) && (mspeed)) + { + for (UINT i=0; i<8; i++) if (!mspeed[i].command) + { + mspeed[i].command = CMD_SPEED; + mspeed[i].param = pfh->tempolist[npat] + 2; + break; + } + } + } + dwMemPos += 0x600; + } + // Reading Samples + for (UINT n=1; n<=m_nSamples; n++) + { + UINT len = Ins[n].nLength; + if (dwMemPos >= dwMemLength) break; + if (len > 4) ReadSample(&Ins[n], RS_PCM8U, (LPSTR)(lpStream+dwMemPos), dwMemLength - dwMemPos); + dwMemPos += len; + } + return TRUE; +} + + diff --git a/modplug/load_amf.cpp b/modplug/load_amf.cpp new file mode 100644 index 000000000..a66954ab2 --- /dev/null +++ b/modplug/load_amf.cpp @@ -0,0 +1,421 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque + */ + +/////////////////////////////////////////////////// +// +// AMF module loader +// +// There is 2 types of AMF files: +// - ASYLUM Music Format +// - Advanced Music Format(DSM) +// +/////////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#define AMFLOG + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct _AMFFILEHEADER +{ + UCHAR szAMF[3]; + UCHAR version; + CHAR title[32]; + UCHAR numsamples; + UCHAR numorders; + USHORT numtracks; + UCHAR numchannels; +} AMFFILEHEADER; + +typedef struct _AMFSAMPLE +{ + UCHAR type; + CHAR samplename[32]; + CHAR filename[13]; + ULONG offset; + ULONG length; + USHORT c2spd; + UCHAR volume; +} AMFSAMPLE; + + +#pragma pack() + + +#ifdef AMFLOG +extern void Log(LPCSTR, ...); +#endif + +VOID AMF_Unpack(MODCOMMAND *pPat, const BYTE *pTrack, UINT nRows, UINT nChannels) +//------------------------------------------------------------------------------- +{ + UINT lastinstr = 0; + UINT nTrkSize = bswapLE16(*(USHORT *)pTrack); + nTrkSize += (UINT)pTrack[2] <<16; + pTrack += 3; + while (nTrkSize--) + { + UINT row = pTrack[0]; + UINT cmd = pTrack[1]; + UINT arg = pTrack[2]; + if (row >= nRows) break; + MODCOMMAND *m = pPat + row * nChannels; + if (cmd < 0x7F) // note+vol + { + m->note = cmd+1; + if (!m->instr) m->instr = lastinstr; + m->volcmd = VOLCMD_VOLUME; + m->vol = arg; + } else + if (cmd == 0x7F) // duplicate row + { + signed char rdelta = (signed char)arg; + int rowsrc = (int)row + (int)rdelta; + if ((rowsrc >= 0) && (rowsrc < (int)nRows)) memcpy(m, &pPat[rowsrc*nChannels],sizeof(pPat[rowsrc*nChannels])); + } else + if (cmd == 0x80) // instrument + { + m->instr = arg+1; + lastinstr = m->instr; + } else + if (cmd == 0x83) // volume + { + m->volcmd = VOLCMD_VOLUME; + m->vol = arg; + } else + // effect + { + UINT command = cmd & 0x7F; + UINT param = arg; + switch(command) + { + // 0x01: Set Speed + case 0x01: command = CMD_SPEED; break; + // 0x02: Volume Slide + // 0x0A: Tone Porta + Vol Slide + // 0x0B: Vibrato + Vol Slide + case 0x02: command = CMD_VOLUMESLIDE; + case 0x0A: if (command == 0x0A) command = CMD_TONEPORTAVOL; + case 0x0B: if (command == 0x0B) command = CMD_VIBRATOVOL; + if (param & 0x80) param = (-(signed char)param)&0x0F; + else param = (param&0x0F)<<4; + break; + // 0x04: Porta Up/Down + case 0x04: if (param & 0x80) { command = CMD_PORTAMENTOUP; param = -(signed char)param; } + else { command = CMD_PORTAMENTODOWN; } break; + // 0x06: Tone Portamento + case 0x06: command = CMD_TONEPORTAMENTO; break; + // 0x07: Tremor + case 0x07: command = CMD_TREMOR; break; + // 0x08: Arpeggio + case 0x08: command = CMD_ARPEGGIO; break; + // 0x09: Vibrato + case 0x09: command = CMD_VIBRATO; break; + // 0x0C: Pattern Break + case 0x0C: command = CMD_PATTERNBREAK; break; + // 0x0D: Position Jump + case 0x0D: command = CMD_POSITIONJUMP; break; + // 0x0F: Retrig + case 0x0F: command = CMD_RETRIG; break; + // 0x10: Offset + case 0x10: command = CMD_OFFSET; break; + // 0x11: Fine Volume Slide + case 0x11: if (param) { command = CMD_VOLUMESLIDE; + if (param & 0x80) param = 0xF0|((-(signed char)param)&0x0F); + else param = 0x0F|((param&0x0F)<<4); + } else command = 0; break; + // 0x12: Fine Portamento + // 0x16: Extra Fine Portamento + case 0x12: + case 0x16: if (param) { int mask = (command == 0x16) ? 0xE0 : 0xF0; + command = (param & 0x80) ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN; + if (param & 0x80) param = mask|((-(signed char)param)&0x0F); + else param |= mask; + } else command = 0; break; + // 0x13: Note Delay + case 0x13: command = CMD_S3MCMDEX; param = 0xD0|(param & 0x0F); break; + // 0x14: Note Cut + case 0x14: command = CMD_S3MCMDEX; param = 0xC0|(param & 0x0F); break; + // 0x15: Set Tempo + case 0x15: command = CMD_TEMPO; break; + // 0x17: Panning + case 0x17: param = (param+64)&0x7F; + if (m->command) { if (!m->volcmd) { m->volcmd = VOLCMD_PANNING; m->vol = param/2; } command = 0; } + else { command = CMD_PANNING8; } + // Unknown effects + default: command = param = 0; + } + if (command) + { + m->command = command; + m->param = param; + } + } + pTrack += 3; + } +} + + + +BOOL CSoundFile::ReadAMF(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + AMFFILEHEADER *pfh = (AMFFILEHEADER *)lpStream; + DWORD dwMemPos; + + if ((!lpStream) || (dwMemLength < 2048)) return FALSE; + if ((!strncmp((LPCTSTR)lpStream, "ASYLUM Music Format V1.0", 25)) && (dwMemLength > 4096)) + { + UINT numorders, numpats, numsamples; + + dwMemPos = 32; + numpats = lpStream[dwMemPos+3]; + numorders = lpStream[dwMemPos+4]; + numsamples = 64; + dwMemPos += 6; + if ((!numpats) || (numpats > MAX_PATTERNS) || (!numorders) + || (numpats*64*32 + 294 + 37*64 >= dwMemLength)) return FALSE; + m_nType = MOD_TYPE_AMF0; + m_nChannels = 8; + m_nInstruments = 0; + m_nSamples = 31; + m_nDefaultTempo = 125; + m_nDefaultSpeed = 6; + for (UINT iOrd=0; iOrdnFineTune = MOD2XMFineTune(lpStream[dwMemPos+22]); + psmp->nVolume = lpStream[dwMemPos+23]; + psmp->nGlobalVol = 64; + if (psmp->nVolume > 0x40) psmp->nVolume = 0x40; + psmp->nVolume <<= 2; + psmp->nLength = bswapLE32(*((LPDWORD)(lpStream+dwMemPos+25))); + psmp->nLoopStart = bswapLE32(*((LPDWORD)(lpStream+dwMemPos+29))); + psmp->nLoopEnd = psmp->nLoopStart + bswapLE32(*((LPDWORD)(lpStream+dwMemPos+33))); + if ((psmp->nLoopEnd > psmp->nLoopStart) && (psmp->nLoopEnd <= psmp->nLength)) + { + psmp->uFlags = CHN_LOOP; + } else + { + psmp->nLoopStart = psmp->nLoopEnd = 0; + } + if ((psmp->nLength) && (iSmp>31)) m_nSamples = iSmp+1; + dwMemPos += 37; + } + for (UINT iPat=0; iPatnote = 0; + + if (pin[0]) + { + p->note = pin[0] + 13; + } + p->instr = pin[1]; + p->command = pin[2]; + p->param = pin[3]; + if (p->command > 0x0F) + { + #ifdef AMFLOG + Log("0x%02X.0x%02X ?", p->command, p->param); + #endif + p->command = 0; + } + ConvertModCommand(p); + pin += 4; + p++; + } + dwMemPos += 64*32; + } + // Read samples + for (UINT iData=0; iDatanLength) + { + dwMemPos += ReadSample(psmp, RS_PCM8S, (LPCSTR)(lpStream+dwMemPos), dwMemLength); + } + } + return TRUE; + } + //////////////////////////// + // DSM/AMF + USHORT *ptracks[MAX_PATTERNS]; + DWORD sampleseekpos[MAX_SAMPLES]; + + if ((pfh->szAMF[0] != 'A') || (pfh->szAMF[1] != 'M') || (pfh->szAMF[2] != 'F') + || (pfh->version < 10) || (pfh->version > 14) || (!bswapLE16(pfh->numtracks)) + || (!pfh->numorders) || (pfh->numorders > MAX_PATTERNS) + || (!pfh->numsamples) || (pfh->numsamples > MAX_SAMPLES) + || (pfh->numchannels < 4) || (pfh->numchannels > 32)) + return FALSE; + memcpy(m_szNames[0], pfh->title, 32); + dwMemPos = sizeof(AMFFILEHEADER); + m_nType = MOD_TYPE_AMF; + m_nChannels = pfh->numchannels; + m_nSamples = pfh->numsamples; + m_nInstruments = 0; + // Setup Channel Pan Positions + if (pfh->version >= 11) + { + signed char *panpos = (signed char *)(lpStream + dwMemPos); + UINT nchannels = (pfh->version >= 13) ? 32 : 16; + for (UINT i=0; i 256) { pan = 128; ChnSettings[i].dwFlags |= CHN_SURROUND; } + ChnSettings[i].nPan = pan; + } + dwMemPos += nchannels; + } else + { + for (UINT i=0; i<16; i++) + { + ChnSettings[i].nPan = (lpStream[dwMemPos+i] & 1) ? 0x30 : 0xD0; + } + dwMemPos += 16; + } + // Get Tempo/Speed + m_nDefaultTempo = 125; + m_nDefaultSpeed = 6; + if (pfh->version >= 13) + { + if (lpStream[dwMemPos] >= 32) m_nDefaultTempo = lpStream[dwMemPos]; + if (lpStream[dwMemPos+1] <= 32) m_nDefaultSpeed = lpStream[dwMemPos+1]; + dwMemPos += 2; + } + // Setup sequence list + for (UINT iOrd=0; iOrdnumorders) + { + Order[iOrd] = iOrd; + PatternSize[iOrd] = 64; + PatternAllocSize[iOrd] = 64; + if (pfh->version >= 14) + { + PatternSize[iOrd] = bswapLE16(*(USHORT *)(lpStream+dwMemPos)); + PatternAllocSize[iOrd] = bswapLE16(*(USHORT *)(lpStream+dwMemPos)); + dwMemPos += 2; + } + ptracks[iOrd] = (USHORT *)(lpStream+dwMemPos); + dwMemPos += m_nChannels * sizeof(USHORT); + } + } + if (dwMemPos + m_nSamples * (sizeof(AMFSAMPLE)+8) > dwMemLength) return TRUE; + // Read Samples + UINT maxsampleseekpos = 0; + for (UINT iIns=0; iInssamplename, 32); + memcpy(pins->name, psh->filename, 13); + pins->nLength = bswapLE32(psh->length); + pins->nC4Speed = bswapLE16(psh->c2spd); + pins->nGlobalVol = 64; + pins->nVolume = psh->volume * 4; + if (pfh->version >= 11) + { + pins->nLoopStart = bswapLE32(*(DWORD *)(lpStream+dwMemPos)); + pins->nLoopEnd = bswapLE32(*(DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + } else + { + pins->nLoopStart = bswapLE16(*(WORD *)(lpStream+dwMemPos)); + pins->nLoopEnd = pins->nLength; + dwMemPos += 2; + } + sampleseekpos[iIns] = 0; + if ((psh->type) && (bswapLE32(psh->offset) < dwMemLength-1)) + { + sampleseekpos[iIns] = bswapLE32(psh->offset); + if (bswapLE32(psh->offset) > maxsampleseekpos) maxsampleseekpos = bswapLE32(psh->offset); + if ((pins->nLoopEnd > pins->nLoopStart + 2) + && (pins->nLoopEnd <= pins->nLength)) pins->uFlags |= CHN_LOOP; + } + } + // Read Track Mapping Table + USHORT *pTrackMap = (USHORT *)(lpStream+dwMemPos); + UINT realtrackcnt = 0; + dwMemPos += pfh->numtracks * sizeof(USHORT); + for (UINT iTrkMap=0; iTrkMapnumtracks; iTrkMap++) + { + if (realtrackcnt < pTrackMap[iTrkMap]) realtrackcnt = pTrackMap[iTrkMap]; + } + // Store tracks positions + BYTE **pTrackData = new BYTE *[realtrackcnt]; + memset(pTrackData, 0, sizeof(pTrackData)); + for (UINT iTrack=0; iTracknumorders; iPat++) + { + MODCOMMAND *p = AllocatePattern(PatternSize[iPat], m_nChannels); + if (!p) break; + Patterns[iPat] = p; + for (UINT iChn=0; iChnnumtracks)) + { + UINT realtrk = bswapLE16(pTrackMap[nTrack-1]); + if (realtrk) + { + realtrk--; + if ((realtrk < realtrackcnt) && (pTrackData[realtrk])) + { + AMF_Unpack(p+iChn, pTrackData[realtrk], PatternSize[iPat], m_nChannels); + } + } + } + } + } + delete pTrackData; + // Read Sample Data + for (UINT iSeek=1; iSeek<=maxsampleseekpos; iSeek++) + { + if (dwMemPos >= dwMemLength) break; + for (UINT iSmp=0; iSmp +*/ + +////////////////////////////////////////////// +// AMS module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct AMSFILEHEADER +{ + char szHeader[7]; // "Extreme" // changed from CHAR + BYTE verlo, verhi; // 0x??,0x01 + BYTE chncfg; + BYTE samples; + WORD patterns; + WORD orders; + BYTE vmidi; + WORD extra; +} AMSFILEHEADER; + +typedef struct AMSSAMPLEHEADER +{ + DWORD length; + DWORD loopstart; + DWORD loopend; + BYTE finetune_and_pan; + WORD samplerate; // C-2 = 8363 + BYTE volume; // 0-127 + BYTE infobyte; +} AMSSAMPLEHEADER; + + +#pragma pack() + + + +BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + BYTE pkinf[MAX_SAMPLES]; + AMSFILEHEADER *pfh = (AMSFILEHEADER *)lpStream; + DWORD dwMemPos; + UINT tmp, tmp2; + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pfh->verhi != 0x01) || (strncmp(pfh->szHeader, "Extreme", 7)) + || (!pfh->patterns) || (!pfh->orders) || (!pfh->samples) || (pfh->samples > MAX_SAMPLES) + || (pfh->patterns > MAX_PATTERNS) || (pfh->orders > MAX_ORDERS)) + { + return ReadAMS2(lpStream, dwMemLength); + } + dwMemPos = sizeof(AMSFILEHEADER) + pfh->extra; + if (dwMemPos + pfh->samples * sizeof(AMSSAMPLEHEADER) + 256 >= dwMemLength) return FALSE; + m_nType = MOD_TYPE_AMS; + m_nInstruments = 0; + m_nChannels = (pfh->chncfg & 0x1F) + 1; + m_nSamples = pfh->samples; + for (UINT nSmp=1; nSmp<=m_nSamples; nSmp++, dwMemPos += sizeof(AMSSAMPLEHEADER)) + { + AMSSAMPLEHEADER *psh = (AMSSAMPLEHEADER *)(lpStream + dwMemPos); + MODINSTRUMENT *pins = &Ins[nSmp]; + pins->nLength = psh->length; + pins->nLoopStart = psh->loopstart; + pins->nLoopEnd = psh->loopend; + pins->nGlobalVol = 64; + pins->nVolume = psh->volume << 1; + pins->nC4Speed = psh->samplerate; + pins->nPan = (psh->finetune_and_pan & 0xF0); + if (pins->nPan < 0x80) pins->nPan += 0x10; + pins->nFineTune = MOD2XMFineTune(psh->finetune_and_pan & 0x0F); + pins->uFlags = (psh->infobyte & 0x80) ? CHN_16BIT : 0; + if ((pins->nLoopEnd <= pins->nLength) && (pins->nLoopStart+4 <= pins->nLoopEnd)) pins->uFlags |= CHN_LOOP; + pkinf[nSmp] = psh->infobyte; + } + // Read Song Name + tmp = lpStream[dwMemPos++]; + if (dwMemPos + tmp + 1 >= dwMemLength) return TRUE; + tmp2 = (tmp < 32) ? tmp : 31; + if (tmp2) memcpy(m_szNames[0], lpStream+dwMemPos, tmp2); + m_szNames[0][tmp2] = 0; + dwMemPos += tmp; + // Read sample names + for (UINT sNam=1; sNam<=m_nSamples; sNam++) + { + if (dwMemPos + 32 >= dwMemLength) return TRUE; + tmp = lpStream[dwMemPos++]; + tmp2 = (tmp < 32) ? tmp : 31; + if (tmp2) memcpy(m_szNames[sNam], lpStream+dwMemPos, tmp2); + dwMemPos += tmp; + } + // Skip Channel names + for (UINT cNam=0; cNam= dwMemLength) return TRUE; + tmp = lpStream[dwMemPos++]; + dwMemPos += tmp; + } + // Read Pattern Names + m_lpszPatternNames = new char[pfh->patterns * 32]; // changed from CHAR + if (!m_lpszPatternNames) return TRUE; + m_nPatternNames = pfh->patterns; + memset(m_lpszPatternNames, 0, m_nPatternNames * 32); + for (UINT pNam=0; pNam < m_nPatternNames; pNam++) + { + if (dwMemPos + 32 >= dwMemLength) return TRUE; + tmp = lpStream[dwMemPos++]; + tmp2 = (tmp < 32) ? tmp : 31; + if (tmp2) memcpy(m_lpszPatternNames+pNam*32, lpStream+dwMemPos, tmp2); + dwMemPos += tmp; + } + // Read Song Comments + tmp = *((WORD *)(lpStream+dwMemPos)); + dwMemPos += 2; + if (dwMemPos + tmp >= dwMemLength) return TRUE; + if (tmp) + { + m_lpszSongComments = new char[tmp+1]; // changed from CHAR + if (!m_lpszSongComments) return TRUE; + memset(m_lpszSongComments, 0, tmp+1); + memcpy(m_lpszSongComments, lpStream + dwMemPos, tmp); + dwMemPos += tmp; + } + // Read Order List + for (UINT iOrd=0; iOrdorders; iOrd++, dwMemPos += 2) + { + UINT n = *((WORD *)(lpStream+dwMemPos)); + Order[iOrd] = (BYTE)n; + } + // Read Patterns + for (UINT iPat=0; iPatpatterns; iPat++) + { + if (dwMemPos + 4 >= dwMemLength) return TRUE; + UINT len = *((DWORD *)(lpStream + dwMemPos)); + dwMemPos += 4; + if ((len >= dwMemLength) || (dwMemPos + len > dwMemLength)) return TRUE; + PatternSize[iPat] = 64; + PatternAllocSize[iPat] = 64; + MODCOMMAND *m = AllocatePattern(PatternSize[iPat], m_nChannels); + if (!m) return TRUE; + Patterns[iPat] = m; + const BYTE *p = lpStream + dwMemPos; + UINT row = 0, i = 0; + while ((row < PatternSize[iPat]) && (i+2 < len)) + { + BYTE b0 = p[i++]; + BYTE b1 = p[i++]; + BYTE b2 = 0; + UINT ch = b0 & 0x3F; + // Note+Instr + if (!(b0 & 0x40)) + { + b2 = p[i++]; + if (ch < m_nChannels) + { + if (b1 & 0x7F) m[ch].note = (b1 & 0x7F) + 25; + m[ch].instr = b2; + } + if (b1 & 0x80) + { + b0 |= 0x40; + b1 = p[i++]; + } + } + // Effect + if (b0 & 0x40) + { + anothercommand: + if (b1 & 0x40) + { + if (ch < m_nChannels) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = b1 & 0x3F; + } + } else + { + b2 = p[i++]; + if (ch < m_nChannels) + { + UINT cmd = b1 & 0x3F; + if (cmd == 0x0C) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = b2 >> 1; + } else + if (cmd == 0x0E) + { + if (!m[ch].command) + { + UINT command = CMD_S3MCMDEX; + UINT param = b2; + switch(param & 0xF0) + { + case 0x00: if (param & 0x08) { param &= 0x07; param |= 0x90; } else {command=param=0;} break; + case 0x10: command = CMD_PORTAMENTOUP; param |= 0xF0; break; + case 0x20: command = CMD_PORTAMENTODOWN; param |= 0xF0; break; + case 0x30: param = (param & 0x0F) | 0x10; break; + case 0x40: param = (param & 0x0F) | 0x30; break; + case 0x50: param = (param & 0x0F) | 0x20; break; + case 0x60: param = (param & 0x0F) | 0xB0; break; + case 0x70: param = (param & 0x0F) | 0x40; break; + case 0x90: command = CMD_RETRIG; param &= 0x0F; break; + case 0xA0: if (param & 0x0F) { command = CMD_VOLUMESLIDE; param = (param << 4) | 0x0F; } else command=param=0; break; + case 0xB0: if (param & 0x0F) { command = CMD_VOLUMESLIDE; param |= 0xF0; } else command=param=0; break; + } + m[ch].command = command; + m[ch].param = param; + } + } else + { + m[ch].command = cmd; + m[ch].param = b2; + ConvertModCommand(&m[ch]); + } + } + } + if (b1 & 0x80) + { + b1 = p[i++]; + if (i <= len) goto anothercommand; + } + } + if (b0 & 0x80) + { + row++; + m += m_nChannels; + } + } + dwMemPos += len; + } + // Read Samples + for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++) if (Ins[iSmp].nLength) + { + if (dwMemPos >= dwMemLength - 9) return TRUE; + UINT flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_AMS16 : RS_AMS8; + dwMemPos += ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); + } + return TRUE; +} + + +///////////////////////////////////////////////////////////////////// +// AMS 2.2 loader + +#pragma pack(1) + +typedef struct AMS2FILEHEADER +{ + DWORD dwHdr1; // AMShdr + WORD wHdr2; + BYTE b1A; // 0x1A + BYTE titlelen; // 30-bytes max + CHAR szTitle[30]; // [titlelen] +} AMS2FILEHEADER; + +typedef struct AMS2SONGHEADER +{ + WORD version; + BYTE instruments; + WORD patterns; + WORD orders; + WORD bpm; + BYTE speed; + BYTE channels; + BYTE commands; + BYTE rows; + WORD flags; +} AMS2SONGHEADER; + +typedef struct AMS2INSTRUMENT +{ + BYTE samples; + BYTE notemap[120]; +} AMS2INSTRUMENT; + +typedef struct AMS2ENVELOPE +{ + BYTE speed; + BYTE sustain; + BYTE loopbegin; + BYTE loopend; + BYTE points; + BYTE info[3]; +} AMS2ENVELOPE; + +typedef struct AMS2SAMPLE +{ + DWORD length; + DWORD loopstart; + DWORD loopend; + WORD frequency; + BYTE finetune; + WORD c4speed; + CHAR transpose; + BYTE volume; + BYTE flags; +} AMS2SAMPLE; + + +#pragma pack() + + +BOOL CSoundFile::ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength) +//------------------------------------------------------------ +{ + AMS2FILEHEADER *pfh = (AMS2FILEHEADER *)lpStream; + AMS2SONGHEADER *psh; + DWORD dwMemPos; + BYTE smpmap[16]; + BYTE packedsamples[MAX_SAMPLES]; + + if ((pfh->dwHdr1 != 0x68534D41) || (pfh->wHdr2 != 0x7264) + || (pfh->b1A != 0x1A) || (pfh->titlelen > 30)) return FALSE; + dwMemPos = pfh->titlelen + 8; + psh = (AMS2SONGHEADER *)(lpStream + dwMemPos); + if (((psh->version & 0xFF00) != 0x0200) || (!psh->instruments) + || (psh->instruments > MAX_INSTRUMENTS) || (!psh->patterns) || (!psh->orders)) return FALSE; + dwMemPos += sizeof(AMS2SONGHEADER); + if (pfh->titlelen) + { + memcpy(m_szNames, pfh->szTitle, pfh->titlelen); + m_szNames[0][pfh->titlelen] = 0; + } + m_nType = MOD_TYPE_AMS; + m_nChannels = 32; + m_nDefaultTempo = psh->bpm >> 8; + m_nDefaultSpeed = psh->speed; + m_nInstruments = psh->instruments; + m_nSamples = 0; + m_dwSongFlags |= SONG_INSTRUMENTMODE; + if (psh->flags & 0x40) m_dwSongFlags |= SONG_LINEARSLIDES; + for (UINT nIns=1; nIns<=m_nInstruments; nIns++) + { + UINT insnamelen = lpStream[dwMemPos]; + CHAR *pinsname = (CHAR *)(lpStream+dwMemPos+1); + dwMemPos += insnamelen + 1; + AMS2INSTRUMENT *pins = (AMS2INSTRUMENT *)(lpStream + dwMemPos); + dwMemPos += sizeof(AMS2INSTRUMENT); + if (dwMemPos + 1024 >= dwMemLength) return TRUE; + AMS2ENVELOPE *volenv, *panenv, *pitchenv; + volenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); + dwMemPos += 5 + volenv->points*3; + panenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); + dwMemPos += 5 + panenv->points*3; + pitchenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); + dwMemPos += 5 + pitchenv->points*3; + INSTRUMENTHEADER *penv = new INSTRUMENTHEADER; + if (!penv) return TRUE; + memset(smpmap, 0, sizeof(smpmap)); + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + for (UINT ismpmap=0; ismpmapsamples; ismpmap++) + { + if ((ismpmap >= 16) || (m_nSamples+1 >= MAX_SAMPLES)) break; + m_nSamples++; + smpmap[ismpmap] = m_nSamples; + } + penv->nGlobalVol = 64; + penv->nPan = 128; + penv->nPPC = 60; + Headers[nIns] = penv; + if (insnamelen) + { + if (insnamelen > 31) insnamelen = 31; + memcpy(penv->name, pinsname, insnamelen); + penv->name[insnamelen] = 0; + } + for (UINT inotemap=0; inotemap<120; inotemap++) + { + penv->NoteMap[inotemap] = inotemap+1; + penv->Keyboard[inotemap] = smpmap[pins->notemap[inotemap] & 0x0F]; + } + // Volume Envelope + { + UINT pos = 0; + penv->VolEnv.nNodes = (volenv->points > 16) ? 16 : volenv->points; + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = volenv->sustain; + penv->VolEnv.nLoopStart = volenv->loopbegin; + penv->VolEnv.nLoopEnd = volenv->loopend; + for (UINT i=0; iVolEnv.nNodes; i++) + { + penv->VolEnv.Values[i] = (BYTE)((volenv->info[i*3+2] & 0x7F) >> 1); + pos += volenv->info[i*3] + ((volenv->info[i*3+1] & 1) << 8); + penv->VolEnv.Ticks[i] = (WORD)pos; + } + } + penv->nFadeOut = (((lpStream[dwMemPos+2] & 0x0F) << 8) | (lpStream[dwMemPos+1])) << 3; + UINT envflags = lpStream[dwMemPos+3]; + if (envflags & 0x01) penv->dwFlags |= ENV_VOLLOOP; + if (envflags & 0x02) penv->dwFlags |= ENV_VOLSUSTAIN; + if (envflags & 0x04) penv->dwFlags |= ENV_VOLUME; + dwMemPos += 5; + // Read Samples + for (UINT ismp=0; ismpsamples; ismp++) + { + MODINSTRUMENT *psmp = ((ismp < 16) && (smpmap[ismp])) ? &Ins[smpmap[ismp]] : NULL; + UINT smpnamelen = lpStream[dwMemPos]; + if ((psmp) && (smpnamelen) && (smpnamelen <= 22)) + { + memcpy(m_szNames[smpmap[ismp]], lpStream+dwMemPos+1, smpnamelen); + } + dwMemPos += smpnamelen + 1; + if (psmp) + { + AMS2SAMPLE *pams = (AMS2SAMPLE *)(lpStream+dwMemPos); + psmp->nGlobalVol = 64; + psmp->nPan = 128; + psmp->nLength = pams->length; + psmp->nLoopStart = pams->loopstart; + psmp->nLoopEnd = pams->loopend; + psmp->nC4Speed = pams->c4speed; + psmp->RelativeTone = pams->transpose; + psmp->nVolume = pams->volume / 2; + packedsamples[smpmap[ismp]] = pams->flags; + if (pams->flags & 0x04) psmp->uFlags |= CHN_16BIT; + if (pams->flags & 0x08) psmp->uFlags |= CHN_LOOP; + if (pams->flags & 0x10) psmp->uFlags |= CHN_PINGPONGLOOP; + } + dwMemPos += sizeof(AMS2SAMPLE); + } + } + if (dwMemPos + 256 >= dwMemLength) return TRUE; + // Comments + { + UINT composernamelen = lpStream[dwMemPos]; + if (composernamelen) + { + m_lpszSongComments = new char[composernamelen+1]; // changed from CHAR + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos+1, composernamelen); + m_lpszSongComments[composernamelen] = 0; + } + } + dwMemPos += composernamelen + 1; + // channel names + for (UINT i=0; i<32; i++) + { + UINT chnnamlen = lpStream[dwMemPos]; + if ((chnnamlen) && (chnnamlen < MAX_CHANNELNAME)) + { + memcpy(ChnSettings[i].szName, lpStream+dwMemPos+1, chnnamlen); + } + dwMemPos += chnnamlen + 1; + if (dwMemPos + chnnamlen + 256 >= dwMemLength) return TRUE; + } + // packed comments (ignored) + UINT songtextlen = *((LPDWORD)(lpStream+dwMemPos)); + dwMemPos += songtextlen; + if (dwMemPos + 256 >= dwMemLength) return TRUE; + } + // Order List + { + for (UINT i=0; i= dwMemLength) return TRUE; + if (i < psh->orders) + { + Order[i] = lpStream[dwMemPos]; + dwMemPos += 2; + } + } + } + // Pattern Data + for (UINT ipat=0; ipatpatterns; ipat++) + { + if (dwMemPos+8 >= dwMemLength) return TRUE; + UINT packedlen = *((LPDWORD)(lpStream+dwMemPos)); + UINT numrows = 1 + (UINT)(lpStream[dwMemPos+4]); + //UINT patchn = 1 + (UINT)(lpStream[dwMemPos+5] & 0x1F); + //UINT patcmds = 1 + (UINT)(lpStream[dwMemPos+5] >> 5); + UINT patnamlen = lpStream[dwMemPos+6]; + dwMemPos += 4; + if ((ipat < MAX_PATTERNS) && (packedlen < dwMemLength-dwMemPos) && (numrows >= 8)) + { + if ((patnamlen) && (patnamlen < MAX_PATTERNNAME)) + { + char s[MAX_PATTERNNAME]; // changed from CHAR + memcpy(s, lpStream+dwMemPos+3, patnamlen); + s[patnamlen] = 0; + SetPatternName(ipat, s); + } + PatternSize[ipat] = numrows; + PatternAllocSize[ipat] = numrows; + Patterns[ipat] = AllocatePattern(numrows, m_nChannels); + if (!Patterns[ipat]) return TRUE; + // Unpack Pattern Data + LPCBYTE psrc = lpStream + dwMemPos; + UINT pos = 3 + patnamlen; + UINT row = 0; + while ((pos < packedlen) && (row < numrows)) + { + MODCOMMAND *m = Patterns[ipat] + row * m_nChannels; + UINT byte1 = psrc[pos++]; + UINT ch = byte1 & 0x1F; + // Read Note + Instr + if (!(byte1 & 0x40)) + { + UINT byte2 = psrc[pos++]; + UINT note = byte2 & 0x7F; + if (note) m[ch].note = (note > 1) ? (note-1) : 0xFF; + m[ch].instr = psrc[pos++]; + // Read Effect + while (byte2 & 0x80) + { + byte2 = psrc[pos++]; + if (byte2 & 0x40) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = byte2 & 0x3F; + } else + { + UINT command = byte2 & 0x3F; + UINT param = psrc[pos++]; + if (command == 0x0C) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = param / 2; + } else + if (command < 0x10) + { + m[ch].command = command; + m[ch].param = param; + ConvertModCommand(&m[ch]); + } else + { + // TODO: AMS effects + } + } + } + } + if (byte1 & 0x80) row++; + } + } + dwMemPos += packedlen; + } + // Read Samples + for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++) if (Ins[iSmp].nLength) + { + if (dwMemPos >= dwMemLength - 9) return TRUE; + UINT flags; + if (packedsamples[iSmp] & 0x03) + { + flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_AMS16 : RS_AMS8; + } else + { + flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S; + } + dwMemPos += ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); + } + return TRUE; +} + + +///////////////////////////////////////////////////////////////////// +// AMS Sample unpacking + +void AMSUnpack(const char *psrc, UINT inputlen, char *pdest, UINT dmax, char packcharacter) +{ + UINT tmplen = dmax; + signed char *amstmp = new signed char[tmplen]; + + if (!amstmp) return; + // Unpack Loop + { + signed char *p = amstmp; + UINT i=0, j=0; + while ((i < inputlen) && (j < tmplen)) + { + signed char ch = psrc[i++]; + if (ch == packcharacter) + { + BYTE ch2 = psrc[i++]; + if (ch2) + { + ch = psrc[i++]; + while (ch2--) + { + p[j++] = ch; + if (j >= tmplen) break; + } + } else p[j++] = packcharacter; + } else p[j++] = ch; + } + } + // Bit Unpack Loop + { + signed char *p = amstmp; + UINT bitcount = 0x80, dh; + UINT k=0; + for (UINT i=0; i> ((dh+8-count) & 7)) & 0xFF; + bitcount = ((bitcount|(bitcount<<8)) >> 1) & 0xFF; + pdest[k++] |= bl; + if (k >= dmax) + { + k = 0; + dh++; + } + } + bitcount = ((bitcount|(bitcount<<8)) >> dh) & 0xFF; + } + } + // Delta Unpack + { + signed char old = 0; + for (UINT i=0; i, + * Adam Goode (endian and char fixes for PPC) +*/ + +/////////////////////////////////////////////////////////////// +// +// DigiBooster Pro Module Loader (*.dbm) +// +// Note: this loader doesn't handle multiple songs +// +/////////////////////////////////////////////////////////////// + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#define DBM_FILE_MAGIC 0x304d4244 +#define DBM_ID_NAME 0x454d414e +#define DBM_NAMELEN 0x2c000000 +#define DBM_ID_INFO 0x4f464e49 +#define DBM_INFOLEN 0x0a000000 +#define DBM_ID_SONG 0x474e4f53 +#define DBM_ID_INST 0x54534e49 +#define DBM_ID_VENV 0x564e4556 +#define DBM_ID_PATT 0x54544150 +#define DBM_ID_SMPL 0x4c504d53 + +#pragma pack(1) + +typedef struct DBMFILEHEADER +{ + DWORD dbm_id; // "DBM0" = 0x304d4244 + WORD trkver; // Tracker version: 02.15 + WORD reserved; + DWORD name_id; // "NAME" = 0x454d414e + DWORD name_len; // name length: always 44 + CHAR songname[44]; + DWORD info_id; // "INFO" = 0x4f464e49 + DWORD info_len; // 0x0a000000 + WORD instruments; + WORD samples; + WORD songs; + WORD patterns; + WORD channels; + DWORD song_id; // "SONG" = 0x474e4f53 + DWORD song_len; + CHAR songname2[44]; + WORD orders; +// WORD orderlist[0]; // orderlist[orders] in words +} DBMFILEHEADER; + +typedef struct DBMINSTRUMENT +{ + CHAR name[30]; + WORD sampleno; + WORD volume; + DWORD finetune; + DWORD loopstart; + DWORD looplen; + WORD panning; + WORD flags; +} DBMINSTRUMENT; + +typedef struct DBMENVELOPE +{ + WORD instrument; + BYTE flags; + BYTE numpoints; + BYTE sustain1; + BYTE loopbegin; + BYTE loopend; + BYTE sustain2; + WORD volenv[2*32]; +} DBMENVELOPE; + +typedef struct DBMPATTERN +{ + WORD rows; + DWORD packedsize; + BYTE patterndata[2]; // [packedsize] +} DBMPATTERN; + +typedef struct DBMSAMPLE +{ + DWORD flags; + DWORD samplesize; + BYTE sampledata[2]; // [samplesize] +} DBMSAMPLE; + +#pragma pack() + + +BOOL CSoundFile::ReadDBM(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + DBMFILEHEADER *pfh = (DBMFILEHEADER *)lpStream; + DWORD dwMemPos; + UINT nOrders, nSamples, nInstruments, nPatterns; + + if ((!lpStream) || (dwMemLength <= sizeof(DBMFILEHEADER)) || (!pfh->channels) + || (pfh->dbm_id != DBM_FILE_MAGIC) || (!pfh->songs) || (pfh->song_id != DBM_ID_SONG) + || (pfh->name_id != DBM_ID_NAME) || (pfh->name_len != DBM_NAMELEN) + || (pfh->info_id != DBM_ID_INFO) || (pfh->info_len != DBM_INFOLEN)) return FALSE; + dwMemPos = sizeof(DBMFILEHEADER); + nOrders = bswapBE16(pfh->orders); + if (dwMemPos + 2 * nOrders + 8*3 >= dwMemLength) return FALSE; + nInstruments = bswapBE16(pfh->instruments); + nSamples = bswapBE16(pfh->samples); + nPatterns = bswapBE16(pfh->patterns); + m_nType = MOD_TYPE_DBM; + m_nChannels = bswapBE16(pfh->channels); + if (m_nChannels < 4) m_nChannels = 4; + if (m_nChannels > 64) m_nChannels = 64; + memcpy(m_szNames[0], (pfh->songname[0]) ? pfh->songname : pfh->songname2, 32); + m_szNames[0][31] = 0; + for (UINT iOrd=0; iOrd < nOrders; iOrd++) + { + Order[iOrd] = lpStream[dwMemPos+iOrd*2+1]; + if (iOrd >= MAX_ORDERS-2) break; + } + dwMemPos += 2*nOrders; + while (dwMemPos + 10 < dwMemLength) + { + DWORD chunk_id = ((LPDWORD)(lpStream+dwMemPos))[0]; + DWORD chunk_size = bswapBE32(((LPDWORD)(lpStream+dwMemPos))[1]); + DWORD chunk_pos; + + dwMemPos += 8; + chunk_pos = dwMemPos; + if ((dwMemPos + chunk_size > dwMemLength) || (chunk_size > dwMemLength)) break; + dwMemPos += chunk_size; + // Instruments + if (chunk_id == DBM_ID_INST) + { + if (nInstruments >= MAX_INSTRUMENTS) nInstruments = MAX_INSTRUMENTS-1; + for (UINT iIns=0; iIns dwMemPos) break; + if ((penv = new INSTRUMENTHEADER) == NULL) break; + pih = (DBMINSTRUMENT *)(lpStream+chunk_pos); + nsmp = bswapBE16(pih->sampleno); + psmp = ((nsmp) && (nsmp < MAX_SAMPLES)) ? &Ins[nsmp] : NULL; + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + memcpy(penv->name, pih->name, 30); + if (psmp) + { + memcpy(m_szNames[nsmp], pih->name, 30); + m_szNames[nsmp][30] = 0; + } + Headers[iIns+1] = penv; + penv->nFadeOut = 1024; // ??? + penv->nGlobalVol = 64; + penv->nPan = bswapBE16(pih->panning); + if ((penv->nPan) && (penv->nPan < 256)) + penv->dwFlags = ENV_SETPANNING; + else + penv->nPan = 128; + penv->nPPC = 5*12; + for (UINT i=0; i<120; i++) + { + penv->Keyboard[i] = nsmp; + penv->NoteMap[i] = i+1; + } + // Sample Info + if (psmp) + { + DWORD sflags = bswapBE16(pih->flags); + psmp->nVolume = bswapBE16(pih->volume) * 4; + if ((!psmp->nVolume) || (psmp->nVolume > 256)) psmp->nVolume = 256; + psmp->nGlobalVol = 64; + psmp->nC4Speed = bswapBE32(pih->finetune); + int f2t = FrequencyToTranspose(psmp->nC4Speed); + psmp->RelativeTone = f2t >> 7; + psmp->nFineTune = f2t & 0x7F; + if ((pih->looplen) && (sflags & 3)) + { + psmp->nLoopStart = bswapBE32(pih->loopstart); + psmp->nLoopEnd = psmp->nLoopStart + bswapBE32(pih->looplen); + psmp->uFlags |= CHN_LOOP; + psmp->uFlags &= ~CHN_PINGPONGLOOP; + if (sflags & 2) psmp->uFlags |= CHN_PINGPONGLOOP; + } + } + chunk_pos += sizeof(DBMINSTRUMENT); + m_nInstruments = iIns+1; + } + m_dwSongFlags |= SONG_INSTRUMENTMODE; + } else + // Volume Envelopes + if (chunk_id == DBM_ID_VENV) + { + UINT nEnvelopes = lpStream[chunk_pos+1]; + + chunk_pos += 2; + for (UINT iEnv=0; iEnv dwMemPos) break; + peh = (DBMENVELOPE *)(lpStream+chunk_pos); + nins = bswapBE16(peh->instrument); + if ((nins) && (nins < MAX_INSTRUMENTS) && (Headers[nins]) && (peh->numpoints)) + { + INSTRUMENTHEADER *penv = Headers[nins]; + + if (peh->flags & 1) penv->dwFlags |= ENV_VOLUME; + if (peh->flags & 2) penv->dwFlags |= ENV_VOLSUSTAIN; + if (peh->flags & 4) penv->dwFlags |= ENV_VOLLOOP; + penv->VolEnv.nNodes = peh->numpoints + 1; + if (penv->VolEnv.nNodes > MAX_ENVPOINTS) penv->VolEnv.nNodes = MAX_ENVPOINTS; + penv->VolEnv.nLoopStart = peh->loopbegin; + penv->VolEnv.nLoopEnd = peh->loopend; + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = peh->sustain1; + for (UINT i=0; iVolEnv.nNodes; i++) + { + penv->VolEnv.Ticks[i] = bswapBE16(peh->volenv[i*2]); + penv->VolEnv.Values[i] = (BYTE)bswapBE16(peh->volenv[i*2+1]); + } + } + chunk_pos += sizeof(DBMENVELOPE); + } + } else + // Packed Pattern Data + if (chunk_id == DBM_ID_PATT) + { + if (nPatterns > MAX_PATTERNS) nPatterns = MAX_PATTERNS; + for (UINT iPat=0; iPat dwMemPos) break; + pph = (DBMPATTERN *)(lpStream+chunk_pos); + pksize = bswapBE32(pph->packedsize); + if ((chunk_pos + pksize + 6 > dwMemPos) || (pksize > dwMemPos)) break; + nRows = bswapBE16(pph->rows); + if ((nRows >= 4) && (nRows <= 256)) + { + MODCOMMAND *m = AllocatePattern(nRows, m_nChannels); + if (m) + { + LPBYTE pkdata = (LPBYTE)&pph->patterndata; + UINT row = 0; + UINT i = 0; + + PatternSize[iPat] = nRows; + PatternAllocSize[iPat] = nRows; + Patterns[iPat] = m; + while ((i+3> 4)*12) + (note & 0x0F) + 13; + } + m[ch].note = note; + } + if (b & 0x02) m[ch].instr = pkdata[i++]; + if (b & 0x3C) + { + UINT cmd1 = 0xFF, param1 = 0, cmd2 = 0xFF, param2 = 0; + if (b & 0x04) cmd1 = (UINT)pkdata[i++]; + if (b & 0x08) param1 = pkdata[i++]; + if (b & 0x10) cmd2 = (UINT)pkdata[i++]; + if (b & 0x20) param2 = pkdata[i++]; + if (cmd1 == 0x0C) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = param1; + cmd1 = 0xFF; + } else + if (cmd2 == 0x0C) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = param2; + cmd2 = 0xFF; + } + if ((cmd1 > 0x13) || ((cmd1 >= 0x10) && (cmd2 < 0x10))) + { + cmd1 = cmd2; + param1 = param2; + cmd2 = 0xFF; + } + if (cmd1 <= 0x13) + { + m[ch].command = cmd1; + m[ch].param = param1; + ConvertModCommand(&m[ch]); + } + } + } else + { + if (b & 0x01) i++; + if (b & 0x02) i++; + if (b & 0x04) i++; + if (b & 0x08) i++; + if (b & 0x10) i++; + if (b & 0x20) i++; + } + } else + { + row++; + m += m_nChannels; + } + } + } + } + chunk_pos += 6 + pksize; + } + } else + // Reading Sample Data + if (chunk_id == DBM_ID_SMPL) + { + if (nSamples >= MAX_SAMPLES) nSamples = MAX_SAMPLES-1; + m_nSamples = nSamples; + for (UINT iSmp=1; iSmp<=nSamples; iSmp++) + { + MODINSTRUMENT *pins; + DBMSAMPLE *psh; + DWORD samplesize; + DWORD sampleflags; + + if (chunk_pos + sizeof(DBMSAMPLE) >= dwMemPos) break; + psh = (DBMSAMPLE *)(lpStream+chunk_pos); + chunk_pos += 8; + samplesize = bswapBE32(psh->samplesize); + sampleflags = bswapBE32(psh->flags); + pins = &Ins[iSmp]; + pins->nLength = samplesize; + if (sampleflags & 2) + { + pins->uFlags |= CHN_16BIT; + samplesize <<= 1; + } + if ((chunk_pos+samplesize > dwMemPos) || (samplesize > dwMemLength)) break; + if (sampleflags & 3) + { + ReadSample(pins, (pins->uFlags & CHN_16BIT) ? RS_PCM16M : RS_PCM8S, + (LPSTR)(psh->sampledata), samplesize); + } + chunk_pos += samplesize; + } + } + } + return TRUE; +} + diff --git a/modplug/load_dmf.cpp b/modplug/load_dmf.cpp new file mode 100644 index 000000000..fbcdb8f81 --- /dev/null +++ b/modplug/load_dmf.cpp @@ -0,0 +1,607 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +/////////////////////////////////////////////////////// +// DMF DELUSION DIGITAL MUSIC FILEFORMAT (X-Tracker) // +/////////////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#define DMFLOG + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct DMFHEADER +{ + DWORD id; // "DDMF" = 0x464d4444 + BYTE version; // 4 + CHAR trackername[8]; // "XTRACKER" + CHAR songname[30]; + CHAR composer[20]; + BYTE date[3]; +} DMFHEADER; + +typedef struct DMFINFO +{ + DWORD id; // "INFO" + DWORD infosize; +} DMFINFO; + +typedef struct DMFSEQU +{ + DWORD id; // "SEQU" + DWORD seqsize; + WORD loopstart; + WORD loopend; + WORD sequ[2]; +} DMFSEQU; + +typedef struct DMFPATT +{ + DWORD id; // "PATT" + DWORD patsize; + WORD numpat; // 1-1024 + BYTE tracks; + BYTE firstpatinfo; +} DMFPATT; + +typedef struct DMFTRACK +{ + BYTE tracks; + BYTE beat; // [hi|lo] -> hi=ticks per beat, lo=beats per measure + WORD ticks; // max 512 + DWORD jmpsize; +} DMFTRACK; + +typedef struct DMFSMPI +{ + DWORD id; + DWORD size; + BYTE samples; +} DMFSMPI; + +typedef struct DMFSAMPLE +{ + DWORD len; + DWORD loopstart; + DWORD loopend; + WORD c3speed; + BYTE volume; + BYTE flags; +} DMFSAMPLE; + +#pragma pack() + + +#ifdef DMFLOG +extern void Log(LPCSTR s, ...); +#endif + + +BOOL CSoundFile::ReadDMF(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + DMFHEADER *pfh = (DMFHEADER *)lpStream; + DMFINFO *psi; + DMFSEQU *sequ; + DWORD dwMemPos; + BYTE infobyte[32]; + BYTE smplflags[MAX_SAMPLES]; + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pfh->id != 0x464d4444) || (!pfh->version) || (pfh->version & 0xF0)) return FALSE; + dwMemPos = 66; + memcpy(m_szNames[0], pfh->songname, 30); + m_szNames[0][30] = 0; + m_nType = MOD_TYPE_DMF; + m_nChannels = 0; +#ifdef DMFLOG + Log("DMF version %d: \"%s\": %d bytes (0x%04X)\n", pfh->version, m_szNames[0], dwMemLength, dwMemLength); +#endif + while (dwMemPos + 7 < dwMemLength) + { + DWORD id = *((LPDWORD)(lpStream+dwMemPos)); + + switch(id) + { + // "INFO" + case 0x4f464e49: + // "CMSG" + case 0x47534d43: + psi = (DMFINFO *)(lpStream+dwMemPos); + if (id == 0x47534d43) dwMemPos++; + if ((psi->infosize > dwMemLength) || (psi->infosize + dwMemPos + 8 > dwMemLength)) goto dmfexit; + if ((psi->infosize >= 8) && (!m_lpszSongComments)) + { + m_lpszSongComments = new char[psi->infosize]; // changed from CHAR + if (m_lpszSongComments) + { + for (UINT i=0; iinfosize-1; i++) + { + CHAR c = lpStream[dwMemPos+8+i]; + if ((i % 40) == 39) + m_lpszSongComments[i] = 0x0d; + else + m_lpszSongComments[i] = (c < ' ') ? ' ' : c; + } + m_lpszSongComments[psi->infosize-1] = 0; + } + } + dwMemPos += psi->infosize + 8 - 1; + break; + + // "SEQU" + case 0x55514553: + sequ = (DMFSEQU *)(lpStream+dwMemPos); + if ((sequ->seqsize >= dwMemLength) || (dwMemPos + sequ->seqsize + 12 > dwMemLength)) goto dmfexit; + { + UINT nseq = sequ->seqsize >> 1; + if (nseq >= MAX_ORDERS-1) nseq = MAX_ORDERS-1; + if (sequ->loopstart < nseq) m_nRestartPos = sequ->loopstart; + for (UINT i=0; isequ[i]; + } + dwMemPos += sequ->seqsize + 8; + break; + + // "PATT" + case 0x54544150: + if (!m_nChannels) + { + DMFPATT *patt = (DMFPATT *)(lpStream+dwMemPos); + UINT numpat; + DWORD dwPos = dwMemPos + 11; + if ((patt->patsize >= dwMemLength) || (dwMemPos + patt->patsize + 8 > dwMemLength)) goto dmfexit; + numpat = patt->numpat; + if (numpat > MAX_PATTERNS) numpat = MAX_PATTERNS; + m_nChannels = patt->tracks; + if (m_nChannels < patt->firstpatinfo) m_nChannels = patt->firstpatinfo; + if (m_nChannels > 32) m_nChannels = 32; + if (m_nChannels < 4) m_nChannels = 4; + for (UINT npat=0; npattracks, pt->ticks); + #endif + UINT tracks = pt->tracks; + if (tracks > 32) tracks = 32; + UINT ticks = pt->ticks; + if (ticks > 256) ticks = 256; + if (ticks < 16) ticks = 16; + dwPos += 8; + if ((pt->jmpsize >= dwMemLength) || (dwPos + pt->jmpsize + 4 >= dwMemLength)) break; + PatternSize[npat] = (WORD)ticks; + PatternAllocSize[npat] = (WORD)ticks; + MODCOMMAND *m = AllocatePattern(PatternSize[npat], m_nChannels); + if (!m) goto dmfexit; + Patterns[npat] = m; + DWORD d = dwPos; + dwPos += pt->jmpsize; + UINT ttype = 1; + UINT tempo = 125; + UINT glbinfobyte = 0; + UINT pbeat = (pt->beat & 0xf0) ? pt->beat>>4 : 8; + BOOL tempochange = (pt->beat & 0xf0) ? TRUE : FALSE; + memset(infobyte, 0, sizeof(infobyte)); + for (UINT row=0; row>4; tempochange = ttype; break; + #ifdef DMFLOG + default: if (info) Log("GLB: %02X.%02X\n", info, infoval); + #endif + } + } else + { + glbinfobyte--; + } + // Parse channels + for (UINT i=0; i>2; + } + // Effect 1 + if (info & 0x08) + { + BYTE efx = lpStream[d++]; + BYTE eval = lpStream[d++]; + switch(efx) + { + // 1: Key Off + case 1: if (!cmd.note) cmd.note = 0xFE; break; + // 2: Set Loop + // 4: Sample Delay + case 4: if (eval&0xe0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>5)|0xD0; } break; + // 5: Retrig + case 5: if (eval&0xe0) { cmd.command = CMD_RETRIG; cmd.param = (eval>>5); } break; + // 6: Offset + case 6: cmd.command = CMD_OFFSET; cmd.param = eval; break; + #ifdef DMFLOG + default: Log("FX1: %02X.%02X\n", efx, eval); + #endif + } + } + // Effect 2 + if (info & 0x04) + { + BYTE efx = lpStream[d++]; + BYTE eval = lpStream[d++]; + switch(efx) + { + // 1: Finetune + case 1: if (eval&0xf0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>4)|0x20; } break; + // 2: Note Delay + case 2: if (eval&0xe0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>5)|0xD0; } break; + // 3: Arpeggio + case 3: if (eval) { cmd.command = CMD_ARPEGGIO; cmd.param = eval; } break; + // 4: Portamento Up + case 4: cmd.command = CMD_PORTAMENTOUP; cmd.param = (eval >= 0xe0) ? 0xdf : eval; break; + // 5: Portamento Down + case 5: cmd.command = CMD_PORTAMENTODOWN; cmd.param = (eval >= 0xe0) ? 0xdf : eval; break; + // 6: Tone Portamento + case 6: cmd.command = CMD_TONEPORTAMENTO; cmd.param = eval; break; + // 8: Vibrato + case 8: cmd.command = CMD_VIBRATO; cmd.param = eval; break; + // 12: Note cut + case 12: if (eval & 0xe0) { cmd.command = CMD_S3MCMDEX; cmd.param = (eval>>5)|0xc0; } + else if (!cmd.note) { cmd.note = 0xfe; } break; + #ifdef DMFLOG + default: Log("FX2: %02X.%02X\n", efx, eval); + #endif + } + } + // Effect 3 + if (info & 0x02) + { + BYTE efx = lpStream[d++]; + BYTE eval = lpStream[d++]; + switch(efx) + { + // 1: Vol Slide Up + case 1: if (eval == 0xff) break; + eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f; + cmd.command = CMD_VOLUMESLIDE; cmd.param = eval<<4; break; + // 2: Vol Slide Down + case 2: if (eval == 0xff) break; + eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f; + cmd.command = CMD_VOLUMESLIDE; cmd.param = eval; break; + // 7: Set Pan + case 7: if (!cmd.volcmd) { cmd.volcmd = VOLCMD_PANNING; cmd.vol = (eval+3)>>2; } + else { cmd.command = CMD_PANNING8; cmd.param = eval; } break; + // 8: Pan Slide Left + case 8: eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f; + cmd.command = CMD_PANNINGSLIDE; cmd.param = eval<<4; break; + // 9: Pan Slide Right + case 9: eval = (eval+3)>>2; if (eval > 0x0f) eval = 0x0f; + cmd.command = CMD_PANNINGSLIDE; cmd.param = eval; break; + #ifdef DMFLOG + default: Log("FX3: %02X.%02X\n", efx, eval); + #endif + + } + } + // Store effect + if (i < m_nChannels) p[i] = cmd; + if (d > dwPos) + { + #ifdef DMFLOG + Log("Unexpected EOP: row=%d\n", row); + #endif + break; + } + } else + { + infobyte[i]--; + } + + // Find free channel for tempo change + if (tempochange) + { + tempochange = FALSE; + UINT speed=6, modtempo=tempo; + UINT rpm = ((ttype) && (pbeat)) ? tempo*pbeat : (tempo+1)*15; + for (speed=30; speed>1; speed--) + { + modtempo = rpm*speed/24; + if (modtempo <= 200) break; + if ((speed < 6) && (modtempo < 256)) break; + } + #ifdef DMFLOG + Log("Tempo change: ttype=%d pbeat=%d tempo=%3d -> speed=%d tempo=%d\n", + ttype, pbeat, tempo, speed, modtempo); + #endif + for (UINT ich=0; ich= 32) && (modtempo < 256)) + { + p[ich].command = CMD_TEMPO; + p[ich].param = (BYTE)modtempo; + modtempo = 0; + } else + { + break; + } + } + } + if (d >= dwPos) break; + } + #ifdef DMFLOG + Log(" %d/%d bytes remaining\n", dwPos-d, pt->jmpsize); + #endif + if (dwPos + 8 >= dwMemLength) break; + } + dwMemPos += patt->patsize + 8; + } + break; + + // "SMPI": Sample Info + case 0x49504d53: + { + DMFSMPI *pds = (DMFSMPI *)(lpStream+dwMemPos); + if (pds->size <= dwMemLength - dwMemPos) + { + DWORD dwPos = dwMemPos + 9; + m_nSamples = pds->samples; + if (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1; + for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++) + { + UINT namelen = lpStream[dwPos]; + smplflags[iSmp] = 0; + if (dwPos+namelen+1+sizeof(DMFSAMPLE) > dwMemPos+pds->size+8) break; + if (namelen) + { + UINT rlen = (namelen < 32) ? namelen : 31; + memcpy(m_szNames[iSmp], lpStream+dwPos+1, rlen); + m_szNames[iSmp][rlen] = 0; + } + dwPos += namelen + 1; + DMFSAMPLE *psh = (DMFSAMPLE *)(lpStream+dwPos); + MODINSTRUMENT *psmp = &Ins[iSmp]; + psmp->nLength = psh->len; + psmp->nLoopStart = psh->loopstart; + psmp->nLoopEnd = psh->loopend; + psmp->nC4Speed = psh->c3speed; + psmp->nGlobalVol = 64; + psmp->nVolume = (psh->volume) ? ((WORD)psh->volume)+1 : (WORD)256; + psmp->uFlags = (psh->flags & 2) ? CHN_16BIT : 0; + if (psmp->uFlags & CHN_16BIT) psmp->nLength >>= 1; + if (psh->flags & 1) psmp->uFlags |= CHN_LOOP; + smplflags[iSmp] = psh->flags; + dwPos += (pfh->version < 8) ? 22 : 30; + #ifdef DMFLOG + Log("SMPI %d/%d: len=%d flags=0x%02X\n", iSmp, m_nSamples, psmp->nLength, psh->flags); + #endif + } + } + dwMemPos += pds->size + 8; + } + break; + + // "SMPD": Sample Data + case 0x44504d53: + { + DWORD dwPos = dwMemPos + 8; + UINT ismpd = 0; + for (UINT iSmp=1; iSmp<=m_nSamples; iSmp++) + { + ismpd++; + DWORD pksize; + if (dwPos + 4 >= dwMemLength) + { + #ifdef DMFLOG + Log("Unexpected EOF at sample %d/%d! (pos=%d)\n", iSmp, m_nSamples, dwPos); + #endif + break; + } + pksize = *((LPDWORD)(lpStream+dwPos)); + #ifdef DMFLOG + Log("sample %d: pos=0x%X pksize=%d ", iSmp, dwPos, pksize); + Log("len=%d flags=0x%X [%08X]\n", Ins[iSmp].nLength, smplflags[ismpd], *((LPDWORD)(lpStream+dwPos+4))); + #endif + dwPos += 4; + if (pksize > dwMemLength - dwPos) + { + #ifdef DMFLOG + Log("WARNING: pksize=%d, but only %d bytes left\n", pksize, dwMemLength-dwPos); + #endif + pksize = dwMemLength - dwPos; + } + if ((pksize) && (iSmp <= m_nSamples)) + { + UINT flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S; + if (smplflags[ismpd] & 4) flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_DMF16 : RS_DMF8; + ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwPos), pksize); + } + dwPos += pksize; + } + dwMemPos = dwPos; + } + break; + + // "ENDE": end of file + case 0x45444e45: + goto dmfexit; + + // Unrecognized id, or "ENDE" field + default: + dwMemPos += 4; + break; + } + } +dmfexit: + if (!m_nChannels) + { + if (!m_nSamples) + { + m_nType = MOD_TYPE_NONE; + return FALSE; + } + m_nChannels = 4; + } + return TRUE; +} + + +/////////////////////////////////////////////////////////////////////// +// DMF Compression + +#pragma pack(1) + +typedef struct DMF_HNODE +{ + short int left, right; + BYTE value; +} DMF_HNODE; + +typedef struct DMF_HTREE +{ + LPBYTE ibuf, ibufmax; + DWORD bitbuf; + UINT bitnum; + UINT lastnode, nodecount; + DMF_HNODE nodes[256]; +} DMF_HTREE; + +#pragma pack() + + +// DMF Huffman ReadBits +BYTE DMFReadBits(DMF_HTREE *tree, UINT nbits) +//------------------------------------------- +{ + BYTE x = 0, bitv = 1; + while (nbits--) + { + if (tree->bitnum) + { + tree->bitnum--; + } else + { + tree->bitbuf = (tree->ibuf < tree->ibufmax) ? *(tree->ibuf++) : 0; + tree->bitnum = 7; + } + if (tree->bitbuf & 1) x |= bitv; + bitv <<= 1; + tree->bitbuf >>= 1; + } + return x; +} + +// +// tree: [8-bit value][12-bit index][12-bit index] = 32-bit +// + +void DMFNewNode(DMF_HTREE *tree) +//------------------------------ +{ + BYTE isleft, isright; + UINT actnode; + + actnode = tree->nodecount; + if (actnode > 255) return; + tree->nodes[actnode].value = DMFReadBits(tree, 7); + isleft = DMFReadBits(tree, 1); + isright = DMFReadBits(tree, 1); + actnode = tree->lastnode; + if (actnode > 255) return; + tree->nodecount++; + tree->lastnode = tree->nodecount; + if (isleft) + { + tree->nodes[actnode].left = tree->lastnode; + DMFNewNode(tree); + } else + { + tree->nodes[actnode].left = -1; + } + tree->lastnode = tree->nodecount; + if (isright) + { + tree->nodes[actnode].right = tree->lastnode; + DMFNewNode(tree); + } else + { + tree->nodes[actnode].right = -1; + } +} + + +int DMFUnpack(LPBYTE psample, LPBYTE ibuf, LPBYTE ibufmax, UINT maxlen) +//---------------------------------------------------------------------- +{ + DMF_HTREE tree; + UINT actnode; + BYTE value, sign, delta = 0; + + memset(&tree, 0, sizeof(tree)); + tree.ibuf = ibuf; + tree.ibufmax = ibufmax; + DMFNewNode(&tree); + value = 0; + for (UINT i=0; i 255) break; + delta = tree.nodes[actnode].value; + if ((tree.ibuf >= tree.ibufmax) && (!tree.bitnum)) break; + } while ((tree.nodes[actnode].left >= 0) && (tree.nodes[actnode].right >= 0)); + if (sign) delta ^= 0xFF; + value += delta; + psample[i] = (i) ? value : 0; + } +#ifdef DMFLOG +// Log("DMFUnpack: %d remaining bytes\n", tree.ibufmax-tree.ibuf); +#endif + return tree.ibuf - ibuf; +} + + diff --git a/modplug/load_dsm.cpp b/modplug/load_dsm.cpp new file mode 100644 index 000000000..55e5ef395 --- /dev/null +++ b/modplug/load_dsm.cpp @@ -0,0 +1,237 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +////////////////////////////////////////////// +// DSIK Internal Format (DSM) module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +#pragma pack(1) + +#define DSMID_RIFF 0x46464952 // "RIFF" +#define DSMID_DSMF 0x464d5344 // "DSMF" +#define DSMID_SONG 0x474e4f53 // "SONG" +#define DSMID_INST 0x54534e49 // "INST" +#define DSMID_PATT 0x54544150 // "PATT" + + +typedef struct DSMNOTE +{ + BYTE note,ins,vol,cmd,inf; +} DSMNOTE; + + +typedef struct DSMINST +{ + DWORD id_INST; + DWORD inst_len; + CHAR filename[13]; + BYTE flags; + BYTE flags2; + BYTE volume; + DWORD length; + DWORD loopstart; + DWORD loopend; + DWORD reserved1; + WORD c2spd; + WORD reserved2; + CHAR samplename[28]; +} DSMINST; + + +typedef struct DSMFILEHEADER +{ + DWORD id_RIFF; // "RIFF" + DWORD riff_len; + DWORD id_DSMF; // "DSMF" + DWORD id_SONG; // "SONG" + DWORD song_len; +} DSMFILEHEADER; + + +typedef struct DSMSONG +{ + CHAR songname[28]; + WORD reserved1; + WORD flags; + DWORD reserved2; + WORD numord; + WORD numsmp; + WORD numpat; + WORD numtrk; + BYTE globalvol; + BYTE mastervol; + BYTE speed; + BYTE bpm; + BYTE panpos[16]; + BYTE orders[128]; +} DSMSONG; + +typedef struct DSMPATT +{ + DWORD id_PATT; + DWORD patt_len; + BYTE dummy1; + BYTE dummy2; +} DSMPATT; + +#pragma pack() + + +BOOL CSoundFile::ReadDSM(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + DSMFILEHEADER *pfh = (DSMFILEHEADER *)lpStream; + DSMSONG *psong; + DWORD dwMemPos; + UINT nPat, nSmp; + + if ((!lpStream) || (dwMemLength < 1024) || (pfh->id_RIFF != DSMID_RIFF) + || (pfh->riff_len + 8 > dwMemLength) || (pfh->riff_len < 1024) + || (pfh->id_DSMF != DSMID_DSMF) || (pfh->id_SONG != DSMID_SONG) + || (pfh->song_len > dwMemLength)) return FALSE; + psong = (DSMSONG *)(lpStream + sizeof(DSMFILEHEADER)); + dwMemPos = sizeof(DSMFILEHEADER) + pfh->song_len; + m_nType = MOD_TYPE_DSM; + m_nChannels = psong->numtrk; + if (m_nChannels < 4) m_nChannels = 4; + if (m_nChannels > 16) m_nChannels = 16; + m_nSamples = psong->numsmp; + if (m_nSamples > MAX_SAMPLES) m_nSamples = MAX_SAMPLES; + m_nDefaultSpeed = psong->speed; + m_nDefaultTempo = psong->bpm; + m_nDefaultGlobalVolume = psong->globalvol << 2; + if ((!m_nDefaultGlobalVolume) || (m_nDefaultGlobalVolume > 256)) m_nDefaultGlobalVolume = 256; + m_nSongPreAmp = psong->mastervol & 0x7F; + for (UINT iOrd=0; iOrdnumord) ? psong->orders[iOrd] : 0xFF); + } + for (UINT iPan=0; iPan<16; iPan++) + { + ChnSettings[iPan].nPan = 0x80; + if (psong->panpos[iPan] <= 0x80) + { + ChnSettings[iPan].nPan = psong->panpos[iPan] << 1; + } + } + memcpy(m_szNames[0], psong->songname, 28); + nPat = 0; + nSmp = 1; + while (dwMemPos < dwMemLength - 8) + { + DSMPATT *ppatt = (DSMPATT *)(lpStream + dwMemPos); + DSMINST *pins = (DSMINST *)(lpStream+dwMemPos); + // Reading Patterns + if (ppatt->id_PATT == DSMID_PATT) + { + dwMemPos += 8; + if (dwMemPos + ppatt->patt_len >= dwMemLength) break; + DWORD dwPos = dwMemPos; + dwMemPos += ppatt->patt_len; + MODCOMMAND *m = AllocatePattern(64, m_nChannels); + if (!m) break; + PatternSize[nPat] = 64; + PatternAllocSize[nPat] = 64; + Patterns[nPat] = m; + UINT row = 0; + while ((row < 64) && (dwPos + 2 <= dwMemPos)) + { + UINT flag = lpStream[dwPos++]; + if (flag) + { + UINT ch = (flag & 0x0F) % m_nChannels; + if (flag & 0x80) + { + UINT note = lpStream[dwPos++]; + if (note) + { + if (note <= 12*9) note += 12; + m[ch].note = (BYTE)note; + } + } + if (flag & 0x40) + { + m[ch].instr = lpStream[dwPos++]; + } + if (flag & 0x20) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = lpStream[dwPos++]; + } + if (flag & 0x10) + { + UINT command = lpStream[dwPos++]; + UINT param = lpStream[dwPos++]; + switch(command) + { + // 4-bit Panning + case 0x08: + switch(param & 0xF0) + { + case 0x00: param <<= 4; break; + case 0x10: command = 0x0A; param = (param & 0x0F) << 4; break; + case 0x20: command = 0x0E; param = (param & 0x0F) | 0xA0; break; + case 0x30: command = 0x0E; param = (param & 0x0F) | 0x10; break; + case 0x40: command = 0x0E; param = (param & 0x0F) | 0x20; break; + default: command = 0; + } + break; + // Portamentos + case 0x11: + case 0x12: + command &= 0x0F; + break; + // 3D Sound (?) + case 0x13: + command = 'X' - 55; + param = 0x91; + break; + default: + // Volume + Offset (?) + command = ((command & 0xF0) == 0x20) ? 0x09 : 0; + } + m[ch].command = (BYTE)command; + m[ch].param = (BYTE)param; + if (command) ConvertModCommand(&m[ch]); + } + } else + { + m += m_nChannels; + row++; + } + } + nPat++; + } else + // Reading Samples + if ((nSmp <= m_nSamples) && (pins->id_INST == DSMID_INST)) + { + if (dwMemPos + pins->inst_len >= dwMemLength - 8) break; + DWORD dwPos = dwMemPos + sizeof(DSMINST); + dwMemPos += 8 + pins->inst_len; + memcpy(m_szNames[nSmp], pins->samplename, 28); + MODINSTRUMENT *psmp = &Ins[nSmp]; + memcpy(psmp->name, pins->filename, 13); + psmp->nGlobalVol = 64; + psmp->nC4Speed = pins->c2spd; + psmp->uFlags = (WORD)((pins->flags & 1) ? CHN_LOOP : 0); + psmp->nLength = pins->length; + psmp->nLoopStart = pins->loopstart; + psmp->nLoopEnd = pins->loopend; + psmp->nVolume = (WORD)(pins->volume << 2); + if (psmp->nVolume > 256) psmp->nVolume = 256; + UINT smptype = (pins->flags & 2) ? RS_PCM8S : RS_PCM8U; + ReadSample(psmp, smptype, (LPCSTR)(lpStream+dwPos), dwMemLength - dwPos); + nSmp++; + } else + { + break; + } + } + return TRUE; +} + diff --git a/modplug/load_far.cpp b/modplug/load_far.cpp new file mode 100644 index 000000000..ea40b21a9 --- /dev/null +++ b/modplug/load_far.cpp @@ -0,0 +1,261 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +//////////////////////////////////////// +// Farandole (FAR) module loader // +//////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#define FARFILEMAGIC 0xFE524146 // "FAR" + +#pragma pack(1) + +typedef struct FARHEADER1 +{ + DWORD id; // file magic FAR= + CHAR songname[40]; // songname + CHAR magic2[3]; // 13,10,26 + WORD headerlen; // remaining length of header in bytes + BYTE version; // 0xD1 + BYTE onoff[16]; + BYTE edit1[9]; + BYTE speed; + BYTE panning[16]; + BYTE edit2[4]; + WORD stlen; +} FARHEADER1; + +typedef struct FARHEADER2 +{ + BYTE orders[256]; + BYTE numpat; + BYTE snglen; + BYTE loopto; + WORD patsiz[256]; +} FARHEADER2; + +typedef struct FARSAMPLE +{ + CHAR samplename[32]; + DWORD length; + BYTE finetune; + BYTE volume; + DWORD reppos; + DWORD repend; + BYTE type; + BYTE loop; +} FARSAMPLE; + +#pragma pack() + + +BOOL CSoundFile::ReadFAR(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + FARHEADER1 *pmh1 = (FARHEADER1 *)lpStream; + FARHEADER2 *pmh2; + DWORD dwMemPos = sizeof(FARHEADER1); + UINT headerlen; + BYTE samplemap[8]; + + if ((!lpStream) || (dwMemLength < 1024) || (pmh1->id != FARFILEMAGIC) + || (pmh1->magic2[0] != 13) || (pmh1->magic2[1] != 10) || (pmh1->magic2[2] != 26)) return FALSE; + headerlen = pmh1->headerlen; + if ((headerlen >= dwMemLength) || (dwMemPos + pmh1->stlen + sizeof(FARHEADER2) >= dwMemLength)) return FALSE; + // Globals + m_nType = MOD_TYPE_FAR; + m_nChannels = 16; + m_nInstruments = 0; + m_nSamples = 0; + m_nSongPreAmp = 0x20; + m_nDefaultSpeed = pmh1->speed; + m_nDefaultTempo = 80; + m_nDefaultGlobalVolume = 256; + + memcpy(m_szNames[0], pmh1->songname, 32); + // Channel Setting + for (UINT nchpan=0; nchpan<16; nchpan++) + { + ChnSettings[nchpan].dwFlags = 0; + ChnSettings[nchpan].nPan = ((pmh1->panning[nchpan] & 0x0F) << 4) + 8; + ChnSettings[nchpan].nVolume = 64; + } + // Reading comment + if (pmh1->stlen) + { + UINT szLen = pmh1->stlen; + if (szLen > dwMemLength - dwMemPos) szLen = dwMemLength - dwMemPos; + if ((m_lpszSongComments = new char[szLen + 1]) != NULL) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos, szLen); + m_lpszSongComments[szLen] = 0; + } + dwMemPos += pmh1->stlen; + } + // Reading orders + pmh2 = (FARHEADER2 *)(lpStream + dwMemPos); + dwMemPos += sizeof(FARHEADER2); + if (dwMemPos >= dwMemLength) return TRUE; + for (UINT iorder=0; iordersnglen) ? pmh2->orders[iorder] : 0xFF; + } + m_nRestartPos = pmh2->loopto; + // Reading Patterns + dwMemPos += headerlen - (869 + pmh1->stlen); + if (dwMemPos >= dwMemLength) return TRUE; + + WORD *patsiz = (WORD *)pmh2->patsiz; + for (UINT ipat=0; ipat<256; ipat++) if (patsiz[ipat]) + { + UINT patlen = patsiz[ipat]; + if ((ipat >= MAX_PATTERNS) || (patsiz[ipat] < 2)) + { + dwMemPos += patlen; + continue; + } + if (dwMemPos + patlen >= dwMemLength) return TRUE; + UINT rows = (patlen - 2) >> 6; + if (!rows) + { + dwMemPos += patlen; + continue; + } + if (rows > 256) rows = 256; + if (rows < 16) rows = 16; + PatternSize[ipat] = rows; + PatternAllocSize[ipat] = rows; + if ((Patterns[ipat] = AllocatePattern(rows, m_nChannels)) == NULL) return TRUE; + MODCOMMAND *m = Patterns[ipat]; + UINT patbrk = lpStream[dwMemPos]; + const BYTE *p = lpStream + dwMemPos + 2; + UINT max = rows*16*4; + if (max > patlen-2) max = patlen-2; + for (UINT len=0; leninstr = ins + 1; + m->note = note + 36; + } + if (vol & 0x0F) + { + m->volcmd = VOLCMD_VOLUME; + m->vol = (vol & 0x0F) << 2; + if (m->vol <= 4) m->vol = 0; + } + switch(eff & 0xF0) + { + // 1.x: Portamento Up + case 0x10: + m->command = CMD_PORTAMENTOUP; + m->param = eff & 0x0F; + break; + // 2.x: Portamento Down + case 0x20: + m->command = CMD_PORTAMENTODOWN; + m->param = eff & 0x0F; + break; + // 3.x: Tone-Portamento + case 0x30: + m->command = CMD_TONEPORTAMENTO; + m->param = (eff & 0x0F) << 2; + break; + // 4.x: Retrigger + case 0x40: + m->command = CMD_RETRIG; + m->param = 6 / (1+(eff&0x0F)) + 1; + break; + // 5.x: Set Vibrato Depth + case 0x50: + m->command = CMD_VIBRATO; + m->param = (eff & 0x0F); + break; + // 6.x: Set Vibrato Speed + case 0x60: + m->command = CMD_VIBRATO; + m->param = (eff & 0x0F) << 4; + break; + // 7.x: Vol Slide Up + case 0x70: + m->command = CMD_VOLUMESLIDE; + m->param = (eff & 0x0F) << 4; + break; + // 8.x: Vol Slide Down + case 0x80: + m->command = CMD_VOLUMESLIDE; + m->param = (eff & 0x0F); + break; + // A.x: Port to vol + case 0xA0: + m->volcmd = VOLCMD_VOLUME; + m->vol = ((eff & 0x0F) << 2) + 4; + break; + // B.x: Set Balance + case 0xB0: + m->command = CMD_PANNING8; + m->param = (eff & 0x0F) << 4; + break; + // F.x: Set Speed + case 0xF0: + m->command = CMD_SPEED; + m->param = eff & 0x0F; + break; + default: + if ((patbrk) && (patbrk+1 == (len >> 6)) && (patbrk+1 != rows-1)) + { + m->command = CMD_PATTERNBREAK; + patbrk = 0; + } + } + } + dwMemPos += patlen; + } + // Reading samples + if (dwMemPos + 8 >= dwMemLength) return TRUE; + memcpy(samplemap, lpStream+dwMemPos, 8); + dwMemPos += 8; + MODINSTRUMENT *pins = &Ins[1]; + for (UINT ismp=0; ismp<64; ismp++, pins++) if (samplemap[ismp >> 3] & (1 << (ismp & 7))) + { + if (dwMemPos + sizeof(FARSAMPLE) > dwMemLength) return TRUE; + FARSAMPLE *pfs = (FARSAMPLE *)(lpStream + dwMemPos); + dwMemPos += sizeof(FARSAMPLE); + m_nSamples = ismp + 1; + memcpy(m_szNames[ismp+1], pfs->samplename, 32); + pins->nLength = pfs->length; + pins->nLoopStart = pfs->reppos; + pins->nLoopEnd = pfs->repend; + pins->nFineTune = 0; + pins->nC4Speed = 8363*2; + pins->nGlobalVol = 64; + pins->nVolume = pfs->volume << 4; + pins->uFlags = 0; + if ((pins->nLength > 3) && (dwMemPos + 4 < dwMemLength)) + { + if (pfs->type & 1) + { + pins->uFlags |= CHN_16BIT; + pins->nLength >>= 1; + pins->nLoopStart >>= 1; + pins->nLoopEnd >>= 1; + } + if ((pfs->loop & 8) && (pins->nLoopEnd > 4)) pins->uFlags |= CHN_LOOP; + ReadSample(pins, (pins->uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S, + (LPSTR)(lpStream+dwMemPos), dwMemLength - dwMemPos); + } + dwMemPos += pfs->length; + } + return TRUE; +} + diff --git a/modplug/load_it.cpp b/modplug/load_it.cpp new file mode 100644 index 000000000..555969be6 --- /dev/null +++ b/modplug/load_it.cpp @@ -0,0 +1,1552 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" +#include "it_defs.h" + +/* blah, -mrsb. +this is a schism header */ +#include "midi.h" + +#ifdef MSC_VER +#pragma warning(disable:4244) +#endif + +BYTE autovibit2xm[8] = +{ 0, 3, 1, 4, 2, 0, 0, 0 }; + +BYTE autovibxm2it[8] = +{ 0, 2, 4, 1, 3, 0, 0, 0 }; + +////////////////////////////////////////////////////////// +// Impulse Tracker IT file support (import only) + + +static inline UINT ConvertVolParam(UINT value) +//-------------------------------------------- +{ + return (value > 9) ? 9 : value; +} + + +BOOL CSoundFile::ITInstrToMPT(const void *p, INSTRUMENTHEADER *penv, UINT trkvers) +//-------------------------------------------------------------------------------- +{ + if (trkvers < 0x0200) + { + const ITOLDINSTRUMENT *pis = (const ITOLDINSTRUMENT *)p; + memcpy(penv->name, pis->name, 26); + memcpy(penv->filename, pis->filename, 12); + penv->nFadeOut = bswapLE16(pis->fadeout) << 6; + penv->nGlobalVol = 64; + for (UINT j=0; j<120; j++) + { + UINT note = pis->keyboard[j*2]; + UINT ins = pis->keyboard[j*2+1]; + if (ins < MAX_SAMPLES) penv->Keyboard[j] = ins; + if (note < 128) penv->NoteMap[j] = note+1; + else if (note >= 0xFE) penv->NoteMap[j] = note; + } + if (pis->flags & 0x01) penv->dwFlags |= ENV_VOLUME; + if (pis->flags & 0x02) penv->dwFlags |= ENV_VOLLOOP; + if (pis->flags & 0x04) penv->dwFlags |= ENV_VOLSUSTAIN; + penv->VolEnv.nLoopStart = pis->vls; + penv->VolEnv.nLoopEnd = pis->vle; + penv->VolEnv.nSustainStart = pis->sls; + penv->VolEnv.nSustainEnd = pis->sle; + penv->VolEnv.nNodes = 25; + for (UINT ev=0; ev<25; ev++) + { + if ((penv->VolEnv.Ticks[ev] = pis->nodes[ev*2]) == 0xFF) + { + penv->VolEnv.nNodes = ev; + break; + } + penv->VolEnv.Values[ev] = pis->nodes[ev*2+1]; + } + penv->nNNA = pis->nna; + penv->nDCT = pis->dnc; + penv->nPan = 0x80; + } else + { + const ITINSTRUMENT *pis = (const ITINSTRUMENT *)p; + memcpy(penv->name, pis->name, 26); + memcpy(penv->filename, pis->filename, 12); + penv->nMidiProgram = pis->mpr; + penv->nMidiChannel = pis->mch; + penv->wMidiBank = bswapLE16(pis->mbank); + penv->nFadeOut = bswapLE16(pis->fadeout) << 5; + penv->nGlobalVol = pis->gbv >> 1; + if (penv->nGlobalVol > 64) penv->nGlobalVol = 64; + for (UINT j=0; j<120; j++) + { + UINT note = pis->keyboard[j*2]; + UINT ins = pis->keyboard[j*2+1]; + if (ins < MAX_SAMPLES) penv->Keyboard[j] = ins; + if (note < 128) penv->NoteMap[j] = note+1; + else if (note >= 0xFE) penv->NoteMap[j] = note; + } + // Volume Envelope + if (pis->volenv.flags & 1) penv->dwFlags |= ENV_VOLUME; + if (pis->volenv.flags & 2) penv->dwFlags |= ENV_VOLLOOP; + if (pis->volenv.flags & 4) penv->dwFlags |= ENV_VOLSUSTAIN; + if (pis->volenv.flags & 8) penv->dwFlags |= ENV_VOLCARRY; + penv->VolEnv.nNodes = pis->volenv.num; + if (penv->VolEnv.nNodes > 25) penv->VolEnv.nNodes = 25; + + penv->VolEnv.nLoopStart = pis->volenv.lpb; + penv->VolEnv.nLoopEnd = pis->volenv.lpe; + penv->VolEnv.nSustainStart = pis->volenv.slb; + penv->VolEnv.nSustainEnd = pis->volenv.sle; + // Panning Envelope + if (pis->panenv.flags & 1) penv->dwFlags |= ENV_PANNING; + if (pis->panenv.flags & 2) penv->dwFlags |= ENV_PANLOOP; + if (pis->panenv.flags & 4) penv->dwFlags |= ENV_PANSUSTAIN; + if (pis->panenv.flags & 8) penv->dwFlags |= ENV_PANCARRY; + penv->PanEnv.nNodes = pis->panenv.num; + if (penv->PanEnv.nNodes > 25) penv->PanEnv.nNodes = 25; + penv->PanEnv.nLoopStart = pis->panenv.lpb; + penv->PanEnv.nLoopEnd = pis->panenv.lpe; + penv->PanEnv.nSustainStart = pis->panenv.slb; + penv->PanEnv.nSustainEnd = pis->panenv.sle; + // Pitch Envelope + if (pis->pitchenv.flags & 1) penv->dwFlags |= ENV_PITCH; + if (pis->pitchenv.flags & 2) penv->dwFlags |= ENV_PITCHLOOP; + if (pis->pitchenv.flags & 4) penv->dwFlags |= ENV_PITCHSUSTAIN; + if (pis->pitchenv.flags & 8) penv->dwFlags |= ENV_PITCHCARRY; + if (pis->pitchenv.flags & 0x80) penv->dwFlags |= ENV_FILTER; + penv->PitchEnv.nNodes = pis->pitchenv.num; + if (penv->PitchEnv.nNodes > 25) penv->PitchEnv.nNodes = 25; + penv->PitchEnv.nLoopStart = pis->pitchenv.lpb; + penv->PitchEnv.nLoopEnd = pis->pitchenv.lpe; + penv->PitchEnv.nSustainStart = pis->pitchenv.slb; + penv->PitchEnv.nSustainEnd = pis->pitchenv.sle; + // Envelopes Data + for (UINT ev=0; ev<25; ev++) + { + penv->VolEnv.Values[ev] = pis->volenv.data[ev*3]; + penv->VolEnv.Ticks[ev] = (pis->volenv.data[ev*3+2] << 8) | (pis->volenv.data[ev*3+1]); + penv->PanEnv.Values[ev] = pis->panenv.data[ev*3] + 32; + penv->PanEnv.Ticks[ev] = (pis->panenv.data[ev*3+2] << 8) | (pis->panenv.data[ev*3+1]); + penv->PitchEnv.Values[ev] = pis->pitchenv.data[ev*3] + 32; + penv->PitchEnv.Ticks[ev] = (pis->pitchenv.data[ev*3+2] << 8) | (pis->pitchenv.data[ev*3+1]); + } + penv->nNNA = pis->nna; + penv->nDCT = pis->dct; + penv->nDNA = pis->dca; + penv->nPPS = pis->pps; + penv->nPPC = pis->ppc; + penv->nIFC = pis->ifc; + penv->nIFR = pis->ifr; + penv->nVolSwing = pis->rv; + penv->nPanSwing = pis->rp; + penv->nPan = (pis->dfp & 0x7F) << 2; + if (penv->nPan > 256) penv->nPan = 128; + if (pis->dfp < 0x80) penv->dwFlags |= ENV_SETPANNING; + } + if ((penv->VolEnv.nLoopStart >= 25) || (penv->VolEnv.nLoopEnd >= 25)) penv->dwFlags &= ~ENV_VOLLOOP; + if ((penv->VolEnv.nSustainStart >= 25) || (penv->VolEnv.nSustainEnd >= 25)) penv->dwFlags &= ~ENV_VOLSUSTAIN; + return TRUE; +} + + +BOOL CSoundFile::ReadIT(const BYTE *lpStream, DWORD dwMemLength) +//-------------------------------------------------------------- +{ + ITFILEHEADER pifh = *(ITFILEHEADER *)lpStream; + DWORD dwMemPos = sizeof(ITFILEHEADER); + DWORD inspos[MAX_INSTRUMENTS]; + DWORD smppos[MAX_SAMPLES]; + DWORD patpos[MAX_PATTERNS]; + BYTE chnmask[64], channels_used[64]; + MODCOMMAND lastvalue[64]; + + if ((!lpStream) || (dwMemLength < 0xc2)) return FALSE; + + pifh.id = bswapLE32(pifh.id); + if (pifh.id == 0x49504D49) { + if (dwMemLength < 554) return FALSE; + + WORD tv; + INSTRUMENTHEADER *zenv = new INSTRUMENTHEADER; + if (!zenv) return FALSE; + memset(zenv, 0, sizeof(INSTRUMENTHEADER)); + memcpy(&tv, lpStream+0x1C, 2); /* trkvers */ + if (!ITInstrToMPT(lpStream, zenv, tv)) { + delete zenv; + return FALSE; + } + + /* okay, we need samples now */ + unsigned int q = 554; + BYTE expect_samples = lpStream[0x1E]; + + m_nType = MOD_TYPE_IT; + m_nInstruments = 1; + m_nSamples = expect_samples; + m_dwSongFlags = SONG_INSTRUMENTMODE | SONG_LINEARSLIDES /* eh? */; + + memcpy(m_szNames[0], lpStream + 0x20, 26); + m_szNames[0][26] = 0; + + if (q+(80*expect_samples) >= dwMemLength) { + delete zenv; + return FALSE; + } + + for (UINT nsmp = 0; nsmp < expect_samples; nsmp++) { + + ITSAMPLESTRUCT pis = *(ITSAMPLESTRUCT *)(lpStream+q); + q += 80; /* length of ITS header */ + + pis.id = bswapLE32(pis.id); + pis.length = bswapLE32(pis.length); + pis.loopbegin = bswapLE32(pis.loopbegin); + pis.loopend = bswapLE32(pis.loopend); + pis.C5Speed = bswapLE32(pis.C5Speed); + pis.susloopbegin = bswapLE32(pis.susloopbegin); + pis.susloopend = bswapLE32(pis.susloopend); + pis.samplepointer = bswapLE32(pis.samplepointer); + + if (pis.id == 0x53504D49) + { + MODINSTRUMENT *pins = &Ins[nsmp+1]; + memcpy(pins->name, pis.filename, 12); + pins->uFlags = 0; + pins->nLength = 0; + pins->nLoopStart = pis.loopbegin; + pins->nLoopEnd = pis.loopend; + pins->nSustainStart = pis.susloopbegin; + pins->nSustainEnd = pis.susloopend; + pins->nC4Speed = pis.C5Speed; + if (!pins->nC4Speed) pins->nC4Speed = 8363; + if (pis.C5Speed < 256) pins->nC4Speed = 256; + pins->nVolume = pis.vol << 2; + if (pins->nVolume > 256) pins->nVolume = 256; + pins->nGlobalVol = pis.gvl; + if (pins->nGlobalVol > 64) pins->nGlobalVol = 64; + if (pis.flags & 0x10) pins->uFlags |= CHN_LOOP; + if (pis.flags & 0x20) pins->uFlags |= CHN_SUSTAINLOOP; + if (pis.flags & 0x40) pins->uFlags |= CHN_PINGPONGLOOP; + if (pis.flags & 0x80) pins->uFlags |= CHN_PINGPONGSUSTAIN; + pins->nPan = (pis.dfp & 0x7F) << 2; + if (pins->nPan > 256) pins->nPan = 256; + if (pis.dfp & 0x80) pins->uFlags |= CHN_PANNING; + pins->nVibType = autovibit2xm[pis.vit & 7]; + pins->nVibRate = pis.vis; + pins->nVibDepth = pis.vid & 0x7F; + pins->nVibSweep = (pis.vir + 3) / 4; + if ((pis.samplepointer) && (pis.samplepointer < dwMemLength) && (pis.length)) + { + pins->nLength = pis.length; + if (pins->nLength > MAX_SAMPLE_LENGTH) pins->nLength = MAX_SAMPLE_LENGTH; + UINT flags = (pis.cvt & 1) ? RS_PCM8S : RS_PCM8U; + if (pis.flags & 2) + { + flags += 5; + if (pis.flags & 4) flags |= RSF_STEREO; + pins->uFlags |= CHN_16BIT; + // IT 2.14 16-bit packed sample ? + if (pis.flags & 8) flags = ((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT21516 : RS_IT21416; + } else + { + if (pis.flags & 4) flags |= RSF_STEREO; + if (pis.cvt == 0xFF) flags = RS_ADPCM4; else + // IT 2.14 8-bit packed sample ? + if (pis.flags & 8) flags = ((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT2158 : RS_IT2148; + } + ReadSample(&Ins[nsmp+1], flags, (LPSTR)(lpStream+pis.samplepointer), dwMemLength - pis.samplepointer); + } + } + memcpy(m_szNames[nsmp+1], pis.name, 26); + + } + + Headers[1] = zenv; + return TRUE; + } + + + pifh.ordnum = bswapLE16(pifh.ordnum); + pifh.insnum = bswapLE16(pifh.insnum); + pifh.smpnum = bswapLE16(pifh.smpnum); + pifh.patnum = bswapLE16(pifh.patnum); + pifh.cwtv = bswapLE16(pifh.cwtv); + pifh.cmwt = bswapLE16(pifh.cmwt); + pifh.flags = bswapLE16(pifh.flags); + pifh.special = bswapLE16(pifh.special); + pifh.msglength = bswapLE16(pifh.msglength); + pifh.msgoffset = bswapLE32(pifh.msgoffset); + pifh.reserved2 = bswapLE32(pifh.reserved2); + + + + if ((pifh.id != 0x4D504D49) || (pifh.insnum >= MAX_INSTRUMENTS) + || (pifh.smpnum >= MAX_INSTRUMENTS)) return FALSE; + if (dwMemPos + pifh.ordnum + pifh.insnum*4 + + pifh.smpnum*4 + pifh.patnum*4 > dwMemLength) return FALSE; + m_nType = MOD_TYPE_IT; + if (!(pifh.flags & 0x01)) m_dwSongFlags |= SONG_NOSTEREO; + if (pifh.flags & 0x04) m_dwSongFlags |= SONG_INSTRUMENTMODE; + if (pifh.flags & 0x08) m_dwSongFlags |= SONG_LINEARSLIDES; + if (pifh.flags & 0x10) m_dwSongFlags |= SONG_ITOLDEFFECTS; + if (pifh.flags & 0x20) m_dwSongFlags |= SONG_ITCOMPATMODE; + if (pifh.flags & 0x40) { + midi_flags |= MIDI_PITCH_BEND; + midi_pitch_depth = pifh.pwd; + } + if (pifh.flags & 0x80) m_dwSongFlags |= SONG_EMBEDMIDICFG; + if (pifh.flags & 0x1000) m_dwSongFlags |= SONG_EXFILTERRANGE; + memcpy(m_szNames[0], pifh.songname, 26); + m_szNames[0][26] = 0; + if (pifh.cwtv >= 0x0213) { + m_rowHighlightMinor = pifh.hilight_minor; + m_rowHighlightMajor = pifh.hilight_major; + } else { + m_rowHighlightMinor = 4; + m_rowHighlightMajor = 16; + } + // Global Volume + m_nDefaultGlobalVolume = pifh.globalvol << 1; + if (m_nDefaultGlobalVolume > 256) m_nDefaultGlobalVolume = 256; + if (pifh.speed) m_nDefaultSpeed = pifh.speed; + if (pifh.tempo) m_nDefaultTempo = pifh.tempo; + m_nSongPreAmp = pifh.mv; + if (m_nSongPreAmp > 128) + m_nSongPreAmp = 128; + m_nStereoSeparation = pifh.sep; + // Reading Channels Pan Positions + for (int ipan=0; ipan<64; ipan++) if (pifh.chnpan[ipan] != 0xFF) + { + ChnSettings[ipan].nVolume = pifh.chnvol[ipan]; + ChnSettings[ipan].nPan = 128; + if (pifh.chnpan[ipan] & 0x80) ChnSettings[ipan].dwFlags |= CHN_MUTE; + UINT n = pifh.chnpan[ipan] & 0x7F; + if (n <= 64) ChnSettings[ipan].nPan = n << 2; + if (n == 100) ChnSettings[ipan].dwFlags |= CHN_SURROUND; + } + if (m_nChannels < 4) m_nChannels = 4; + // Reading Song Message + if ((pifh.special & 0x01) && (pifh.msglength) && (pifh.msgoffset + pifh.msglength < dwMemLength)) + { + m_lpszSongComments = new char[pifh.msglength+1]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+pifh.msgoffset, pifh.msglength); + m_lpszSongComments[pifh.msglength] = 0; + } + } + // Reading orders + UINT nordsize = pifh.ordnum; + if (nordsize > MAX_ORDERS) nordsize = MAX_ORDERS; + memcpy(Order, lpStream+dwMemPos, nordsize); + + dwMemPos += pifh.ordnum; + // Reading Instrument Offsets + memset(inspos, 0, sizeof(inspos)); + UINT inspossize = pifh.insnum; + if (inspossize > MAX_INSTRUMENTS) inspossize = MAX_INSTRUMENTS; + inspossize <<= 2; + memcpy(inspos, lpStream+dwMemPos, inspossize); + for (UINT j=0; j < (inspossize>>2); j++) + { + inspos[j] = bswapLE32(inspos[j]); + } + dwMemPos += pifh.insnum * 4; + // Reading Samples Offsets + memset(smppos, 0, sizeof(smppos)); + UINT smppossize = pifh.smpnum; + if (smppossize > MAX_SAMPLES) smppossize = MAX_SAMPLES; + smppossize <<= 2; + memcpy(smppos, lpStream+dwMemPos, smppossize); + for (UINT j=0; j < (smppossize>>2); j++) + { + smppos[j] = bswapLE32(smppos[j]); + } + dwMemPos += pifh.smpnum * 4; + // Reading Patterns Offsets + memset(patpos, 0, sizeof(patpos)); + UINT patpossize = pifh.patnum; + if (patpossize > MAX_PATTERNS) patpossize = MAX_PATTERNS; + patpossize <<= 2; + memcpy(patpos, lpStream+dwMemPos, patpossize); + for (UINT j=0; j < (patpossize>>2); j++) + { + patpos[j] = bswapLE32(patpos[j]); + } + dwMemPos += pifh.patnum * 4; + + for (UINT i = 0; i < pifh.ordnum; i++) { + if (Order[i] >= pifh.patnum && Order[i] < MAX_PATTERNS) { + pifh.patnum = Order[i]; + for (UINT j = patpossize; j < (pifh.patnum>>2); j++) + patpos[j] = 0; + patpossize = pifh.patnum; + } + } + + + // Reading IT Extra Info + if (dwMemPos + 2 < dwMemLength) + { + UINT nflt = bswapLE16(*((WORD *)(lpStream + dwMemPos))); + dwMemPos += 2; + if (dwMemPos + nflt * 8 < dwMemLength) dwMemPos += nflt * 8; + } + // Reading Midi Output & Macros + if (m_dwSongFlags & SONG_EMBEDMIDICFG) + { + if (dwMemPos + sizeof(MODMIDICFG) < dwMemLength) + { + memcpy(&m_MidiCfg, lpStream+dwMemPos, sizeof(MODMIDICFG)); + dwMemPos += sizeof(MODMIDICFG); + } else { + ResetMidiCfg(); + } + } else { + ResetMidiCfg(); + } + // Read pattern names: "PNAM" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e50)) + { + UINT len = bswapLE32(*((DWORD *)(lpStream+dwMemPos+4))); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len <= MAX_PATTERNS*MAX_PATTERNNAME) && (len >= MAX_PATTERNNAME)) + { + m_lpszPatternNames = new char[len]; + if (m_lpszPatternNames) + { + m_nPatternNames = len / MAX_PATTERNNAME; + memcpy(m_lpszPatternNames, lpStream+dwMemPos, len); + } + dwMemPos += len; + } + } + // 4-channels minimum + m_nChannels = 4; + // Read channel names: "CNAM" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e43)) + { + UINT len = bswapLE32(*((DWORD *)(lpStream+dwMemPos+4))); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len <= 64*MAX_CHANNELNAME)) + { + UINT n = len / MAX_CHANNELNAME; + if (n > m_nChannels) m_nChannels = n; + for (UINT i=0; i MAX_PATTERNS) npatterns = MAX_PATTERNS; + for (UINT patchk=0; patchk= dwMemLength)) continue; + UINT len = bswapLE16(*((WORD *)(lpStream+patpos[patchk]))); + UINT rows = bswapLE16(*((WORD *)(lpStream+patpos[patchk]+2))); + if ((rows < 4) || (rows > 256)) continue; + if (patpos[patchk]+8+len > dwMemLength) continue; + UINT i = 0; + const BYTE *p = lpStream+patpos[patchk]+8; + UINT nrow = 0; + while (nrow= len) break; + BYTE b = p[i++]; + if (!b) + { + nrow++; + continue; + } + UINT ch = b & 0x7F; + if (ch) ch = (ch - 1) & 0x3F; + if (b & 0x80) + { + if (i >= len) break; + chnmask[ch] = p[i++]; + } + // Channel used + if (chnmask[ch] & 0x0F) + { + if ((ch >= m_nChannels) && (ch < 64)) m_nChannels = ch+1; + } + // Note + if (chnmask[ch] & 1) i++; + // Instrument + if (chnmask[ch] & 2) i++; + // Volume + if (chnmask[ch] & 4) i++; + // Effect + if (chnmask[ch] & 8) i += 2; + if (i >= len) break; + } + } + // Reading Instruments + m_nInstruments = pifh.insnum; + if (m_nInstruments >= MAX_INSTRUMENTS) m_nInstruments = MAX_INSTRUMENTS-1; + for (UINT nins=0; nins 0) && (inspos[nins] < dwMemLength - sizeof(ITOLDINSTRUMENT))) + { + INSTRUMENTHEADER *penv = new INSTRUMENTHEADER; + if (!penv) continue; + Headers[nins+1] = penv; + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + ITInstrToMPT(lpStream + inspos[nins], penv, pifh.cmwt); + } + } + // Reading Samples + m_nSamples = pifh.smpnum; + if (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1; + for (UINT nsmp=0; nsmpname, pis.filename, 12); + pins->uFlags = 0; + pins->nLength = 0; + pins->nLoopStart = pis.loopbegin; + pins->nLoopEnd = pis.loopend; + pins->nSustainStart = pis.susloopbegin; + pins->nSustainEnd = pis.susloopend; + pins->nC4Speed = pis.C5Speed; + if (!pins->nC4Speed) pins->nC4Speed = 8363; + if (pis.C5Speed < 256) pins->nC4Speed = 256; + pins->nVolume = pis.vol << 2; + if (pins->nVolume > 256) pins->nVolume = 256; + pins->nGlobalVol = pis.gvl; + if (pins->nGlobalVol > 64) pins->nGlobalVol = 64; + if (pis.flags & 0x10) pins->uFlags |= CHN_LOOP; + if (pis.flags & 0x20) pins->uFlags |= CHN_SUSTAINLOOP; + if (pis.flags & 0x40) pins->uFlags |= CHN_PINGPONGLOOP; + if (pis.flags & 0x80) pins->uFlags |= CHN_PINGPONGSUSTAIN; + pins->nPan = (pis.dfp & 0x7F) << 2; + if (pins->nPan > 256) pins->nPan = 256; + if (pis.dfp & 0x80) pins->uFlags |= CHN_PANNING; + pins->nVibType = autovibit2xm[pis.vit & 7]; + pins->nVibRate = pis.vis; + pins->nVibDepth = pis.vid & 0x7F; + pins->nVibSweep = (pis.vir + 3) / 4; + if ((pis.samplepointer) && (pis.samplepointer < dwMemLength) && (pis.length)) + { + pins->nLength = pis.length; + if (pins->nLength > MAX_SAMPLE_LENGTH) pins->nLength = MAX_SAMPLE_LENGTH; + UINT flags = (pis.cvt & 1) ? RS_PCM8S : RS_PCM8U; + if (pis.flags & 2) + { + flags += 5; + if (pis.flags & 4) flags |= RSF_STEREO; + pins->uFlags |= CHN_16BIT; + // IT 2.14 16-bit packed sample ? + if (pis.flags & 8) flags = ((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT21516 : RS_IT21416; + } else + { + if (pis.flags & 4) flags |= RSF_STEREO; + if (pis.cvt == 0xFF) flags = RS_ADPCM4; else + // IT 2.14 8-bit packed sample ? + if (pis.flags & 8) flags = ((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT2158 : RS_IT2148; + } + ReadSample(&Ins[nsmp+1], flags, (LPSTR)(lpStream+pis.samplepointer), dwMemLength - pis.samplepointer); + } + } + memcpy(m_szNames[nsmp+1], pis.name, 26); + } + // Reading Patterns + for (UINT npat=0; npat= dwMemLength)) + { + PatternSize[npat] = 64; + PatternAllocSize[npat] = 64; + Patterns[npat] = AllocatePattern(64, m_nChannels); + continue; + } + + UINT len = bswapLE16(*((WORD *)(lpStream+patpos[npat]))); + UINT rows = bswapLE16(*((WORD *)(lpStream+patpos[npat]+2))); + if ((rows < 4) || (rows > 256)) continue; + if (patpos[npat]+8+len > dwMemLength) continue; + PatternSize[npat] = rows; + PatternAllocSize[npat] = rows; + if ((Patterns[npat] = AllocatePattern(rows, m_nChannels)) == NULL) continue; + memset(lastvalue, 0, sizeof(lastvalue)); + memset(chnmask, 0, sizeof(chnmask)); + MODCOMMAND *m = Patterns[npat]; + UINT i = 0; + const BYTE *p = lpStream+patpos[npat]+8; + UINT nrow = 0; + while (nrow= len) break; + BYTE b = p[i++]; + if (!b) + { + nrow++; + m+=m_nChannels; + continue; + } + UINT ch = b & 0x7F; + if (ch) ch = (ch - 1) & 0x3F; + if (b & 0x80) + { + if (i >= len) break; + chnmask[ch] = p[i++]; + } + if ((chnmask[ch] & 0x10) && (ch < m_nChannels)) + { + m[ch].note = lastvalue[ch].note; + } + if ((chnmask[ch] & 0x20) && (ch < m_nChannels)) + { + m[ch].instr = lastvalue[ch].instr; + } + if ((chnmask[ch] & 0x40) && (ch < m_nChannels)) + { + m[ch].volcmd = lastvalue[ch].volcmd; + m[ch].vol = lastvalue[ch].vol; + } + if ((chnmask[ch] & 0x80) && (ch < m_nChannels)) + { + m[ch].command = lastvalue[ch].command; + m[ch].param = lastvalue[ch].param; + } + if (chnmask[ch] & 1) // Note + { + if (i >= len) break; + UINT note = p[i++]; + if (ch < m_nChannels) + { + if (note < 0x80) note++; + m[ch].note = note; + lastvalue[ch].note = note; + channels_used[ch] = TRUE; + } + } + if (chnmask[ch] & 2) + { + if (i >= len) break; + UINT instr = p[i++]; + if (ch < m_nChannels) + { + m[ch].instr = instr; + lastvalue[ch].instr = instr; + } + } + if (chnmask[ch] & 4) + { + if (i >= len) break; + UINT vol = p[i++]; + if (ch < m_nChannels) + { + // 0-64: Set Volume + if (vol <= 64) { m[ch].volcmd = VOLCMD_VOLUME; m[ch].vol = vol; } else + // 128-192: Set Panning + if ((vol >= 128) && (vol <= 192)) { m[ch].volcmd = VOLCMD_PANNING; m[ch].vol = vol - 128; } else + // 65-74: Fine Volume Up + if (vol < 75) { m[ch].volcmd = VOLCMD_FINEVOLUP; m[ch].vol = vol - 65; } else + // 75-84: Fine Volume Down + if (vol < 85) { m[ch].volcmd = VOLCMD_FINEVOLDOWN; m[ch].vol = vol - 75; } else + // 85-94: Volume Slide Up + if (vol < 95) { m[ch].volcmd = VOLCMD_VOLSLIDEUP; m[ch].vol = vol - 85; } else + // 95-104: Volume Slide Down + if (vol < 105) { m[ch].volcmd = VOLCMD_VOLSLIDEDOWN; m[ch].vol = vol - 95; } else + // 105-114: Pitch Slide Up + if (vol < 115) { m[ch].volcmd = VOLCMD_PORTADOWN; m[ch].vol = vol - 105; } else + // 115-124: Pitch Slide Down + if (vol < 125) { m[ch].volcmd = VOLCMD_PORTAUP; m[ch].vol = vol - 115; } else + // 193-202: Portamento To + if ((vol >= 193) && (vol <= 202)) { m[ch].volcmd = VOLCMD_TONEPORTAMENTO; m[ch].vol = vol - 193; } else + // 203-212: Vibrato + if ((vol >= 203) && (vol <= 212)) { m[ch].volcmd = VOLCMD_VIBRATO; m[ch].vol = vol - 203; } + lastvalue[ch].volcmd = m[ch].volcmd; + lastvalue[ch].vol = m[ch].vol; + } + } + // Reading command/param + if (chnmask[ch] & 8) + { + if (i > len - 2) break; + UINT cmd = p[i++]; + UINT param = p[i++]; + if (ch < m_nChannels) + { + if (cmd) + { + m[ch].command = cmd; + m[ch].param = param; + S3MConvert(&m[ch], TRUE); + lastvalue[ch].command = m[ch].command; + lastvalue[ch].param = m[ch].param; + } + } + } + } + } + for (UINT ncu=0; ncu=m_nChannels) + { + ChnSettings[ncu].nVolume = 64; + ChnSettings[ncu].dwFlags &= ~CHN_MUTE; + } + } + m_nMinPeriod = 8; + m_nMaxPeriod = 0xF000; + return TRUE; +} + + +#ifndef MODPLUG_NO_FILESAVE +//#define SAVEITTIMESTAMP +#pragma warning(disable:4100) + +#if 0 +BOOL CSoundFile::SaveIT(LPCSTR lpszFileName, UINT nPacking) +//--------------------------------------------------------- +{ + DWORD dwPatNamLen, dwChnNamLen; + ITFILEHEADER header; + ITINSTRUMENT iti; + ITSAMPLESTRUCT itss; + BYTE smpcount[MAX_SAMPLES]; + DWORD inspos[MAX_INSTRUMENTS]; + DWORD patpos[MAX_PATTERNS]; + DWORD smppos[MAX_SAMPLES]; + DWORD dwPos = 0, dwHdrPos = 0, dwExtra = 2; + WORD patinfo[4]; + BYTE chnmask[64]; + BYTE buf[512]; + MODCOMMAND lastvalue[64]; + FILE *f; + + + if ((!lpszFileName) || ((f = fopen(lpszFileName, "wb")) == NULL)) return FALSE; + memset(inspos, 0, sizeof(inspos)); + memset(patpos, 0, sizeof(patpos)); + memset(smppos, 0, sizeof(smppos)); + // Writing Header + memset(&header, 0, sizeof(header)); + dwPatNamLen = 0; + dwChnNamLen = 0; + header.id = 0x4D504D49; + lstrcpyn(header.songname, m_szNames[0], 27); + header.hilight_minor = m_rowHighlightMinor; + header.hilight_major = m_rowHighlightMajor; + header.ordnum = 0; + while ((header.ordnum < MAX_ORDERS) && (Order[header.ordnum] < 0xFF)) header.ordnum++; + if (header.ordnum < MAX_ORDERS) Order[header.ordnum++] = 0xFF; + header.insnum = m_nInstruments; + header.smpnum = m_nSamples; + header.patnum = MAX_PATTERNS; + while ((header.patnum > 0) && (!Patterns[header.patnum-1])) header.patnum--; + header.cwtv = 0x217; + header.cmwt = 0x200; + header.flags = 0x0001; + header.special = 0x0006; + if (m_dwSongFlags & SONG_INSTRUMENTMODE) header.flags |= 0x04; + if (m_dwSongFlags & SONG_LINEARSLIDES) header.flags |= 0x08; + if (m_dwSongFlags & SONG_ITOLDEFFECTS) header.flags |= 0x10; + if (m_dwSongFlags & SONG_ITCOMPATMODE) header.flags |= 0x20; + if (m_dwSongFlags & SONG_EXFILTERRANGE) header.flags |= 0x1000; + header.globalvol = m_nDefaultGlobalVolume >> 1; + header.mv = m_nSongPreAmp; + header.speed = m_nDefaultSpeed; + header.tempo = m_nDefaultTempo; + header.sep = m_nStereoSeparation; + dwHdrPos = sizeof(header) + header.ordnum; + // Channel Pan and Volume + memset(header.chnpan, 0xFF, 64); + memset(header.chnvol, 64, 64); + for (UINT ich=0; ich> 2; + if (ChnSettings[ich].dwFlags & CHN_SURROUND) header.chnpan[ich] = 100; + header.chnvol[ich] = ChnSettings[ich].nVolume; + if (ChnSettings[ich].dwFlags & CHN_MUTE) header.chnpan[ich] |= 0x80; + if (ChnSettings[ich].szName[0]) + { + dwChnNamLen = (ich+1) * MAX_CHANNELNAME; + } + } + if (dwChnNamLen) dwExtra += dwChnNamLen + 8; +#ifdef SAVEITTIMESTAMP + dwExtra += 8; // Time Stamp +#endif + if (m_dwSongFlags & SONG_EMBEDMIDICFG) + { + header.flags |= 0x80; + header.special |= 0x08; + dwExtra += sizeof(MODMIDICFG); + } + // Pattern Names + if ((m_nPatternNames) && (m_lpszPatternNames)) + { + dwPatNamLen = m_nPatternNames * MAX_PATTERNNAME; + while ((dwPatNamLen >= MAX_PATTERNNAME) && (!m_lpszPatternNames[dwPatNamLen-MAX_PATTERNNAME])) dwPatNamLen -= MAX_PATTERNNAME; + if (dwPatNamLen < MAX_PATTERNNAME) dwPatNamLen = 0; + if (dwPatNamLen) dwExtra += dwPatNamLen + 8; + } + // Mix Plugins + dwExtra += SaveMixPlugins(NULL, TRUE); + // Comments + if (m_lpszSongComments) + { + header.special |= 1; + header.msglength = strlen(m_lpszSongComments)+1; + header.msgoffset = dwHdrPos + dwExtra + header.insnum*4 + header.patnum*4 + header.smpnum*4; + } + // Write file header + fwrite(&header, 1, sizeof(header), f); + fwrite(Order, 1, header.ordnum, f); + if (header.insnum) fwrite(inspos, 4, header.insnum, f); + if (header.smpnum) fwrite(smppos, 4, header.smpnum, f); + if (header.patnum) fwrite(patpos, 4, header.patnum, f); + // Writing editor history information + { +#ifdef SAVEITTIMESTAMP + SYSTEMTIME systime; + FILETIME filetime; + WORD timestamp[4]; + WORD nInfoEx = 1; + memset(timestamp, 0, sizeof(timestamp)); + fwrite(&nInfoEx, 1, 2, f); + GetSystemTime(&systime); + SystemTimeToFileTime(&systime, &filetime); + FileTimeToDosDateTime(&filetime, ×tamp[0], ×tamp[1]); + fwrite(timestamp, 1, 8, f); +#else + WORD nInfoEx = 0; + fwrite(&nInfoEx, 1, 2, f); +#endif + } + // Writing midi cfg + if (header.flags & 0x80) + { + fwrite(&m_MidiCfg, 1, sizeof(MODMIDICFG), f); + } + // Writing pattern names + if (dwPatNamLen) + { + DWORD d = 0x4d414e50; + fwrite(&d, 1, 4, f); + fwrite(&dwPatNamLen, 1, 4, f); + fwrite(m_lpszPatternNames, 1, dwPatNamLen, f); + } + // Writing channel Names + if (dwChnNamLen) + { + DWORD d = 0x4d414e43; + fwrite(&d, 1, 4, f); + fwrite(&dwChnNamLen, 1, 4, f); + UINT nChnNames = dwChnNamLen / MAX_CHANNELNAME; + for (UINT inam=0; inamfilename, 12); + memcpy(iti.name, penv->name, 26); + iti.mbank = penv->wMidiBank; + iti.mpr = penv->nMidiProgram; + iti.mch = penv->nMidiChannel; + iti.nna = penv->nNNA; + iti.dct = penv->nDCT; + iti.dca = penv->nDNA; + iti.fadeout = penv->nFadeOut >> 5; + iti.pps = penv->nPPS; + iti.ppc = penv->nPPC; + iti.gbv = (BYTE)(penv->nGlobalVol << 1); + iti.dfp = (BYTE)penv->nPan >> 2; + if (!(penv->dwFlags & ENV_SETPANNING)) iti.dfp |= 0x80; + iti.rv = penv->nVolSwing; + iti.rp = penv->nPanSwing; + iti.ifc = penv->nIFC; + iti.ifr = penv->nIFR; + iti.nos = 0; + for (UINT i=0; i<120; i++) if (penv->Keyboard[i] < MAX_SAMPLES) + { + UINT smp = penv->Keyboard[i]; + if ((smp) && (!smpcount[smp])) + { + smpcount[smp] = 1; + iti.nos++; + } + iti.keyboard[i*2] = penv->NoteMap[i] - 1; + iti.keyboard[i*2+1] = smp; + } + // Writing Volume envelope + if (penv->dwFlags & ENV_VOLUME) iti.volenv.flags |= 0x01; + if (penv->dwFlags & ENV_VOLLOOP) iti.volenv.flags |= 0x02; + if (penv->dwFlags & ENV_VOLSUSTAIN) iti.volenv.flags |= 0x04; + if (penv->dwFlags & ENV_VOLCARRY) iti.volenv.flags |= 0x08; + iti.volenv.num = (BYTE)penv->VolEnv.nNodes; + iti.volenv.lpb = (BYTE)penv->VolEnv.nLoopStart; + iti.volenv.lpe = (BYTE)penv->VolEnv.nLoopEnd; + iti.volenv.slb = penv->VolEnv.nSustainStart; + iti.volenv.sle = penv->VolEnv.nSustainEnd; + // Writing Panning envelope + if (penv->dwFlags & ENV_PANNING) iti.panenv.flags |= 0x01; + if (penv->dwFlags & ENV_PANLOOP) iti.panenv.flags |= 0x02; + if (penv->dwFlags & ENV_PANSUSTAIN) iti.panenv.flags |= 0x04; + if (penv->dwFlags & ENV_PANCARRY) iti.panenv.flags |= 0x08; + iti.panenv.num = (BYTE)penv->PanEnv.nNodes; + iti.panenv.lpb = (BYTE)penv->PanEnv.nLoopStart; + iti.panenv.lpe = (BYTE)penv->PanEnv.nLoopEnd; + iti.panenv.slb = penv->PanEnv.nSustainStart; + iti.panenv.sle = penv->PanEnv.nSustainEnd; + // Writing Pitch Envelope + if (penv->dwFlags & ENV_PITCH) iti.pitchenv.flags |= 0x01; + if (penv->dwFlags & ENV_PITCHLOOP) iti.pitchenv.flags |= 0x02; + if (penv->dwFlags & ENV_PITCHSUSTAIN) iti.pitchenv.flags |= 0x04; + if (penv->dwFlags & ENV_PITCHCARRY) iti.pitchenv.flags |= 0x08; + if (penv->dwFlags & ENV_FILTER) iti.pitchenv.flags |= 0x80; + iti.pitchenv.num = (BYTE)penv->PitchEnv.nNodes; + iti.pitchenv.lpb = (BYTE)penv->PitchEnv.nLoopStart; + iti.pitchenv.lpe = (BYTE)penv->PitchEnv.nLoopEnd; + iti.pitchenv.slb = (BYTE)penv->PitchEnv.nSustainStart; + iti.pitchenv.sle = (BYTE)penv->PitchEnv.nSustainEnd; + // Writing Envelopes data + for (UINT ev=0; ev<25; ev++) + { + iti.volenv.data[ev*3] = penv->VolEnv.Values[ev]; + iti.volenv.data[ev*3+1] = penv->VolEnv.Ticks[ev] & 0xFF; + iti.volenv.data[ev*3+2] = penv->VolEnv.Ticks[ev] >> 8; + iti.panenv.data[ev*3] = penv->PanEnv.Values[ev] - 32; + iti.panenv.data[ev*3+1] = penv->PanEnv.Ticks[ev] & 0xFF; + iti.panenv.data[ev*3+2] = penv->PanEnv.Ticks[ev] >> 8; + iti.pitchenv.data[ev*3] = penv->PitchEnv.Values[ev] - 32; + iti.pitchenv.data[ev*3+1] = penv->PitchEnv.Ticks[ev] & 0xFF; + iti.pitchenv.data[ev*3+2] = penv->PitchEnv.Ticks[ev] >> 8; + } + } else + // Save Empty Instrument + { + for (UINT i=0; i<120; i++) iti.keyboard[i*2] = i; + iti.ppc = 5*12; + iti.gbv = 128; + iti.dfp = 0x20; + iti.ifc = 0xFF; + } + if (!iti.nos) iti.trkvers = 0; + // Writing instrument + inspos[nins-1] = dwPos; + dwPos += sizeof(ITINSTRUMENT); + fwrite(&iti, 1, sizeof(ITINSTRUMENT), f); + } + // Writing sample headers + memset(&itss, 0, sizeof(itss)); + for (UINT hsmp=0; hsmpcommand; + UINT param = m->param; + UINT vol = 0xFF; + UINT note = m->note; + if (note) b |= 1; + if ((note) && (note < 0x80)) note--; + if (m->instr) b |= 2; + if (m->volcmd) + { + UINT volcmd = m->volcmd; + switch(volcmd) + { + case VOLCMD_VOLUME: vol = m->vol; if (vol > 64) vol = 64; break; + case VOLCMD_PANNING: vol = m->vol + 128; if (vol > 192) vol = 192; break; + case VOLCMD_VOLSLIDEUP: vol = 85 + ConvertVolParam(m->vol); break; + case VOLCMD_VOLSLIDEDOWN: vol = 95 + ConvertVolParam(m->vol); break; + case VOLCMD_FINEVOLUP: vol = 65 + ConvertVolParam(m->vol); break; + case VOLCMD_FINEVOLDOWN: vol = 75 + ConvertVolParam(m->vol); break; + case VOLCMD_VIBRATOSPEED: vol = 203; break; + case VOLCMD_VIBRATO: vol = 203 + ConvertVolParam(m->vol); break; + case VOLCMD_TONEPORTAMENTO: vol = 193 + ConvertVolParam(m->vol); break; + case VOLCMD_PORTADOWN: vol = 105 + ConvertVolParam(m->vol); break; + case VOLCMD_PORTAUP: vol = 115 + ConvertVolParam(m->vol); break; + default: vol = 0xFF; + } + } + if (vol != 0xFF) b |= 4; + if (command) + { + S3MSaveConvert(&command, ¶m, TRUE); + if (command) b |= 8; + } + // Packing information + if (b) + { + // Same note ? + if (b & 1) + { + if ((note == lastvalue[ch].note) && (lastvalue[ch].volcmd & 1)) + { + b &= ~1; + b |= 0x10; + } else + { + lastvalue[ch].note = note; + lastvalue[ch].volcmd |= 1; + } + } + // Same instrument ? + if (b & 2) + { + if ((m->instr == lastvalue[ch].instr) && (lastvalue[ch].volcmd & 2)) + { + b &= ~2; + b |= 0x20; + } else + { + lastvalue[ch].instr = m->instr; + lastvalue[ch].volcmd |= 2; + } + } + // Same volume column byte ? + if (b & 4) + { + if ((vol == lastvalue[ch].vol) && (lastvalue[ch].volcmd & 4)) + { + b &= ~4; + b |= 0x40; + } else + { + lastvalue[ch].vol = vol; + lastvalue[ch].volcmd |= 4; + } + } + // Same command / param ? + if (b & 8) + { + if ((command == lastvalue[ch].command) && (param == lastvalue[ch].param) && (lastvalue[ch].volcmd & 8)) + { + b &= ~8; + b |= 0x80; + } else + { + lastvalue[ch].command = command; + lastvalue[ch].param = param; + lastvalue[ch].volcmd |= 8; + } + } + if (b != chnmask[ch]) + { + chnmask[ch] = b; + buf[len++] = (ch+1) | 0x80; + buf[len++] = b; + } else + { + buf[len++] = ch+1; + } + if (b & 1) buf[len++] = note; + if (b & 2) buf[len++] = m->instr; + if (b & 4) buf[len++] = vol; + if (b & 8) + { + buf[len++] = command; + buf[len++] = param; + } + } + } + buf[len++] = 0; + dwPos += len; + patinfo[0] += len; + fwrite(buf, 1, len, f); + } + fseek(f, dwPatPos, SEEK_SET); + fwrite(patinfo, 8, 1, f); + fseek(f, dwPos, SEEK_SET); + } + // Writing Sample Data + for (UINT nsmp=1; nsmp<=header.smpnum; nsmp++) + { + MODINSTRUMENT *psmp = &Ins[nsmp]; + memset(&itss, 0, sizeof(itss)); + memcpy(itss.filename, psmp->name, 12); + memcpy(itss.name, m_szNames[nsmp], 26); + itss.id = 0x53504D49; + itss.gvl = (BYTE)psmp->nGlobalVol; + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + for (UINT iu=1; iu<=m_nInstruments; iu++) if (Headers[iu]) + { + INSTRUMENTHEADER *penv = Headers[iu]; + for (UINT ju=0; ju<128; ju++) if (penv->Keyboard[ju] == nsmp) + { + itss.flags = 0x01; + break; + } + } + } else + { + itss.flags = 0x01; + } + if (psmp->uFlags & CHN_LOOP) itss.flags |= 0x10; + if (psmp->uFlags & CHN_SUSTAINLOOP) itss.flags |= 0x20; + if (psmp->uFlags & CHN_PINGPONGLOOP) itss.flags |= 0x40; + if (psmp->uFlags & CHN_PINGPONGSUSTAIN) itss.flags |= 0x80; + itss.C5Speed = psmp->nC4Speed; + if (!itss.C5Speed) itss.C5Speed = 8363; + itss.length = psmp->nLength; + itss.loopbegin = psmp->nLoopStart; + itss.loopend = psmp->nLoopEnd; + itss.susloopbegin = psmp->nSustainStart; + itss.susloopend = psmp->nSustainEnd; + itss.vol = psmp->nVolume >> 2; + itss.dfp = psmp->nPan >> 2; + itss.vit = autovibxm2it[psmp->nVibType & 7]; + itss.vis = psmp->nVibRate; + itss.vid = psmp->nVibDepth; + itss.vir = (psmp->nVibSweep < 64) ? psmp->nVibSweep * 4 : 255; + if (psmp->uFlags & CHN_PANNING) itss.dfp |= 0x80; + if ((psmp->pSample) && (psmp->nLength)) itss.cvt = 0x01; + UINT flags = RS_PCM8S; +#ifndef NO_PACKING + if (nPacking) + { + if ((!(psmp->uFlags & (CHN_16BIT|CHN_STEREO))) + && (CanPackSample(psmp->pSample, psmp->nLength, nPacking))) + { + flags = RS_ADPCM4; + itss.cvt = 0xFF; + } + } else +#endif // NO_PACKING + { + if (psmp->uFlags & CHN_STEREO) + { + flags = RS_STPCM8S; + itss.flags |= 0x04; + } + if (psmp->uFlags & CHN_16BIT) + { + itss.flags |= 0x02; + flags = (psmp->uFlags & CHN_STEREO) ? RS_STPCM16S : RS_PCM16S; + } + } + itss.samplepointer = dwPos; + fseek(f, smppos[nsmp-1], SEEK_SET); + fwrite(&itss, 1, sizeof(ITSAMPLESTRUCT), f); + fseek(f, dwPos, SEEK_SET); + if ((psmp->pSample) && (psmp->nLength)) + { + dwPos += WriteSample(f, psmp, flags); + } + } + // Updating offsets + fseek(f, dwHdrPos, SEEK_SET); + if (header.insnum) fwrite(inspos, 4, header.insnum, f); + if (header.smpnum) fwrite(smppos, 4, header.smpnum, f); + if (header.patnum) fwrite(patpos, 4, header.patnum, f); + fclose(f); + return TRUE; +} +#endif + +#pragma warning(default:4100) +#endif // MODPLUG_NO_FILESAVE + +////////////////////////////////////////////////////////////////////////////// +// IT 2.14 compression + +DWORD ITReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n) +//----------------------------------------------------------------- +{ + DWORD retval = 0; + UINT i = n; + + if (n > 0) + { + do + { + if (!bitnum) + { + bitbuf = *ibuf++; + bitnum = 8; + } + retval >>= 1; + retval |= bitbuf << 31; + bitbuf >>= 1; + bitnum--; + i--; + } while (i); + i = n; + } + return (retval >> (32-i)); +} + +#define IT215_SUPPORT +void ITUnpack8Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215) +//------------------------------------------------------------------------------------------- +{ + signed char *pDst = pSample; + LPBYTE pSrc = lpMemFile; + DWORD wHdr = 0; + DWORD wCount = 0; + DWORD bitbuf = 0; + UINT bitnum = 0; + BYTE bLeft = 0, bTemp = 0, bTemp2 = 0; + + while (dwLen) + { + if (!wCount) + { + wCount = 0x8000; + wHdr = bswapLE16(*((LPWORD)pSrc)); + pSrc += 2; + bLeft = 9; + bTemp = bTemp2 = 0; + bitbuf = bitnum = 0; + } + DWORD d = wCount; + if (d > dwLen) d = dwLen; + // Unpacking + DWORD dwPos = 0; + do + { + WORD wBits = (WORD)ITReadBits(bitbuf, bitnum, pSrc, bLeft); + if (bLeft < 7) + { + DWORD i = 1 << (bLeft-1); + DWORD j = wBits & 0xFFFF; + if (i != j) goto UnpackByte; + wBits = (WORD)(ITReadBits(bitbuf, bitnum, pSrc, 3) + 1) & 0xFF; + bLeft = ((BYTE)wBits < bLeft) ? (BYTE)wBits : (BYTE)((wBits+1) & 0xFF); + goto Next; + } + if (bLeft < 9) + { + WORD i = (0xFF >> (9 - bLeft)) + 4; + WORD j = i - 8; + if ((wBits <= j) || (wBits > i)) goto UnpackByte; + wBits -= j; + bLeft = ((BYTE)(wBits & 0xFF) < bLeft) ? (BYTE)(wBits & 0xFF) : (BYTE)((wBits+1) & 0xFF); + goto Next; + } + if (bLeft >= 10) goto SkipByte; + if (wBits >= 256) + { + bLeft = (BYTE)(wBits + 1) & 0xFF; + goto Next; + } + UnpackByte: + if (bLeft < 8) + { + BYTE shift = 8 - bLeft; + signed char c = (signed char)(wBits << shift); + c >>= shift; + wBits = (WORD)c; + } + wBits += bTemp; + bTemp = (BYTE)wBits; + bTemp2 += bTemp; +#ifdef IT215_SUPPORT + pDst[dwPos] = (b215) ? bTemp2 : bTemp; +#else + pDst[dwPos] = bTemp; +#endif + SkipByte: + dwPos++; + Next: + if (pSrc >= lpMemFile+dwMemLength+1) return; + } while (dwPos < d); + // Move On + wCount -= d; + dwLen -= d; + pDst += d; + } +} + + +void ITUnpack16Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215) +//-------------------------------------------------------------------------------------------- +{ + signed short *pDst = (signed short *)pSample; + LPBYTE pSrc = lpMemFile; + DWORD wHdr = 0; + DWORD wCount = 0; + DWORD bitbuf = 0; + UINT bitnum = 0; + BYTE bLeft = 0; + signed short wTemp = 0, wTemp2 = 0; + + while (dwLen) + { + if (!wCount) + { + wCount = 0x4000; + wHdr = bswapLE16(*((LPWORD)pSrc)); + pSrc += 2; + bLeft = 17; + wTemp = wTemp2 = 0; + bitbuf = bitnum = 0; + } + DWORD d = wCount; + if (d > dwLen) d = dwLen; + // Unpacking + DWORD dwPos = 0; + do + { + DWORD dwBits = ITReadBits(bitbuf, bitnum, pSrc, bLeft); + if (bLeft < 7) + { + DWORD i = 1 << (bLeft-1); + DWORD j = dwBits; + if (i != j) goto UnpackByte; + dwBits = ITReadBits(bitbuf, bitnum, pSrc, 4) + 1; + bLeft = ((BYTE)(dwBits & 0xFF) < bLeft) ? (BYTE)(dwBits & 0xFF) : (BYTE)((dwBits+1) & 0xFF); + goto Next; + } + if (bLeft < 17) + { + DWORD i = (0xFFFF >> (17 - bLeft)) + 8; + DWORD j = (i - 16) & 0xFFFF; + if ((dwBits <= j) || (dwBits > (i & 0xFFFF))) goto UnpackByte; + dwBits -= j; + bLeft = ((BYTE)(dwBits & 0xFF) < bLeft) ? (BYTE)(dwBits & 0xFF) : (BYTE)((dwBits+1) & 0xFF); + goto Next; + } + if (bLeft >= 18) goto SkipByte; + if (dwBits >= 0x10000) + { + bLeft = (BYTE)(dwBits + 1) & 0xFF; + goto Next; + } + UnpackByte: + if (bLeft < 16) + { + BYTE shift = 16 - bLeft; + signed short c = (signed short)(dwBits << shift); + c >>= shift; + dwBits = (DWORD)c; + } + dwBits += wTemp; + wTemp = (signed short)dwBits; + wTemp2 += wTemp; +#ifdef IT215_SUPPORT + pDst[dwPos] = (b215) ? wTemp2 : wTemp; +#else + pDst[dwPos] = wTemp; +#endif + SkipByte: + dwPos++; + Next: + if (pSrc >= lpMemFile+dwMemLength+1) return; + } while (dwPos < d); + // Move On + wCount -= d; + dwLen -= d; + pDst += d; + if (pSrc >= lpMemFile+dwMemLength) break; + } +} + + +UINT CSoundFile::SaveMixPlugins(FILE *f, BOOL bUpdate) +//---------------------------------------------------- +{ + DWORD chinfo[64]; + CHAR s[32]; + DWORD nPluginSize; + UINT nTotalSize = 0; + UINT nChInfo = 0; + + for (UINT i=0; iInfo.dwPluginId1) || (p->Info.dwPluginId2)) + { + nPluginSize = sizeof(SNDMIXPLUGININFO)+4; // plugininfo+4 (datalen) + if ((p->pMixPlugin) && (bUpdate)) + { + p->pMixPlugin->SaveAllParameters(); + } + if (p->pPluginData) + { + nPluginSize += p->nPluginDataSize; + } + if (f) + { + s[0] = 'F'; + s[1] = 'X'; + s[2] = '0' + (i/10); + s[3] = '0' + (i%10); + fwrite(s, 1, 4, f); + fwrite(&nPluginSize, 1, 4, f); + fwrite(&p->Info, 1, sizeof(SNDMIXPLUGININFO), f); + fwrite(&m_MixPlugins[i].nPluginDataSize, 1, 4, f); + if (m_MixPlugins[i].pPluginData) + { + fwrite(m_MixPlugins[i].pPluginData, 1, m_MixPlugins[i].nPluginDataSize, f); + } + } + nTotalSize += nPluginSize + 8; + } + } + for (UINT j=0; j nLen-nPos-8) break;; + if ((bswapLE32(*(DWORD *)(p+nPos))) == 0x58464843) + { + for (UINT ch=0; ch<64; ch++) if (ch*4 < nPluginSize) + { + ChnSettings[ch].nMixPlugin = bswapLE32(*(DWORD *)(p+nPos+8+ch*4)); + } + } else + { + if ((p[nPos] != 'F') || (p[nPos+1] != 'X') + || (p[nPos+2] < '0') || (p[nPos+3] < '0')) + { + break; + } + nPlugin = (p[nPos+2]-'0')*10 + (p[nPos+3]-'0'); + if ((nPlugin < MAX_MIXPLUGINS) && (nPluginSize >= sizeof(SNDMIXPLUGININFO)+4)) + { + DWORD dwExtra = bswapLE32(*(DWORD *)(p+nPos+8+sizeof(SNDMIXPLUGININFO))); + m_MixPlugins[nPlugin].Info = *(const SNDMIXPLUGININFO *)(p+nPos+8); + m_MixPlugins[nPlugin].Info.dwPluginId1 = bswapLE32(m_MixPlugins[nPlugin].Info.dwPluginId1); + m_MixPlugins[nPlugin].Info.dwPluginId2 = bswapLE32(m_MixPlugins[nPlugin].Info.dwPluginId2); + m_MixPlugins[nPlugin].Info.dwInputRouting = bswapLE32(m_MixPlugins[nPlugin].Info.dwInputRouting); + m_MixPlugins[nPlugin].Info.dwOutputRouting = bswapLE32(m_MixPlugins[nPlugin].Info.dwOutputRouting); + for (UINT j=0; j<4; j++) + { + m_MixPlugins[nPlugin].Info.dwReserved[j] = bswapLE32(m_MixPlugins[nPlugin].Info.dwReserved[j]); + } + if ((dwExtra) && (dwExtra <= nPluginSize-sizeof(SNDMIXPLUGININFO)-4)) + { + m_MixPlugins[nPlugin].nPluginDataSize = 0; + m_MixPlugins[nPlugin].pPluginData = new signed char [dwExtra]; + if (m_MixPlugins[nPlugin].pPluginData) + { + m_MixPlugins[nPlugin].nPluginDataSize = dwExtra; + memcpy(m_MixPlugins[nPlugin].pPluginData, p+nPos+8+sizeof(SNDMIXPLUGININFO)+4, dwExtra); + } + } + } + } + nPos += nPluginSize + 8; + } + return nPos; +} + diff --git a/modplug/load_mdl.cpp b/modplug/load_mdl.cpp new file mode 100644 index 000000000..1b236a803 --- /dev/null +++ b/modplug/load_mdl.cpp @@ -0,0 +1,530 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +////////////////////////////////////////////// +// DigiTracker (MDL) module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +typedef struct MDLSONGHEADER +{ + DWORD id; // "DMDL" = 0x4C444D44 + BYTE version; +} MDLSONGHEADER; + + +typedef struct MDLINFOBLOCK +{ + CHAR songname[32]; + CHAR composer[20]; + WORD norders; + WORD repeatpos; + BYTE globalvol; + BYTE speed; + BYTE tempo; + BYTE channelinfo[32]; + BYTE seq[256]; +} MDLINFOBLOCK; + + +typedef struct MDLPATTERNDATA +{ + BYTE channels; + BYTE lastrow; // nrows = lastrow+1 + CHAR name[16]; + WORD data[1]; +} MDLPATTERNDATA; + + +void ConvertMDLCommand(MODCOMMAND *m, UINT eff, UINT data) +//-------------------------------------------------------- +{ + UINT command = 0, param = data; + switch(eff) + { + case 0x01: command = CMD_PORTAMENTOUP; break; + case 0x02: command = CMD_PORTAMENTODOWN; break; + case 0x03: command = CMD_TONEPORTAMENTO; break; + case 0x04: command = CMD_VIBRATO; break; + case 0x05: command = CMD_ARPEGGIO; break; + case 0x07: command = (param < 0x20) ? CMD_SPEED : CMD_TEMPO; break; + case 0x08: command = CMD_PANNING8; param <<= 1; break; + case 0x0B: command = CMD_POSITIONJUMP; break; + case 0x0C: command = CMD_GLOBALVOLUME; break; + case 0x0D: command = CMD_PATTERNBREAK; param = (data & 0x0F) + (data>>4)*10; break; + case 0x0E: + command = CMD_S3MCMDEX; + switch(data & 0xF0) + { + case 0x00: command = 0; break; // What is E0x in MDL (there is a bunch) ? + case 0x10: if (param & 0x0F) { param |= 0xF0; command = CMD_PANNINGSLIDE; } else command = 0; break; + case 0x20: if (param & 0x0F) { param = (param << 4) | 0x0F; command = CMD_PANNINGSLIDE; } else command = 0; break; + case 0x30: param = (data & 0x0F) | 0x10; break; // glissando + case 0x40: param = (data & 0x0F) | 0x30; break; // vibrato waveform + case 0x60: param = (data & 0x0F) | 0xB0; break; + case 0x70: param = (data & 0x0F) | 0x40; break; // tremolo waveform + case 0x90: command = CMD_RETRIG; param &= 0x0F; break; + case 0xA0: param = (data & 0x0F) << 4; command = CMD_GLOBALVOLSLIDE; break; + case 0xB0: param = data & 0x0F; command = CMD_GLOBALVOLSLIDE; break; + case 0xF0: param = ((data >> 8) & 0x0F) | 0xA0; break; + } + break; + case 0x0F: command = CMD_SPEED; break; + + case 0x10: + if ((param & 0xF0) != 0xE0) { + command = CMD_VOLUMESLIDE; + if ((param & 0xF0) == 0xF0) { + param = ((param << 4) | 0x0F); + } else { + param >>= 2; + if (param > 0xF) + param = 0xF; + param <<= 4; + } + } + break; + case 0x20: + if ((param & 0xF0) != 0xE0) { + command = CMD_VOLUMESLIDE; + if ((param & 0xF0) != 0xF0) { + param >>= 2; + if (param > 0xF) + param = 0xF; + } + } + break; + + case 0x30: command = CMD_RETRIG; break; + case 0x40: command = CMD_TREMOLO; break; + case 0x50: command = CMD_TREMOR; break; + case 0xEF: if (param > 0xFF) param = 0xFF; command = CMD_OFFSET; break; + } + if (command) + { + m->command = command; + m->param = param; + } +} + + +void UnpackMDLTrack(MODCOMMAND *pat, UINT nChannels, UINT nRows, UINT nTrack, const BYTE *lpTracks) +//------------------------------------------------------------------------------------------------- +{ + MODCOMMAND cmd, *m = pat; + UINT len = *((WORD *)lpTracks); + UINT pos = 0, row = 0, i; + lpTracks += 2; + for (UINT ntrk=1; ntrk> 2; + switch(b & 0x03) + { + case 0x01: + for (i=0; i<=xx; i++) + { + if (row) *m = *(m-nChannels); + m += nChannels; + row++; + if (row >= nRows) break; + } + break; + + case 0x02: + if (xx < row) *m = pat[nChannels*xx]; + m += nChannels; + row++; + break; + + case 0x03: + { + cmd.note = (xx & 0x01) ? lpTracks[pos++] : 0; + cmd.instr = (xx & 0x02) ? lpTracks[pos++] : 0; + cmd.volcmd = cmd.vol = 0; + cmd.command = cmd.param = 0; + if ((cmd.note < 120-12) && (cmd.note)) cmd.note += 12; + UINT volume = (xx & 0x04) ? lpTracks[pos++] : 0; + UINT commands = (xx & 0x08) ? lpTracks[pos++] : 0; + UINT command1 = commands & 0x0F; + UINT command2 = commands & 0xF0; + UINT param1 = (xx & 0x10) ? lpTracks[pos++] : 0; + UINT param2 = (xx & 0x20) ? lpTracks[pos++] : 0; + if ((command1 == 0x0E) && ((param1 & 0xF0) == 0xF0) && (!command2)) + { + param1 = ((param1 & 0x0F) << 8) | param2; + command1 = 0xEF; + command2 = param2 = 0; + } + if (volume) + { + cmd.volcmd = VOLCMD_VOLUME; + cmd.vol = (volume+1) >> 2; + } + ConvertMDLCommand(&cmd, command1, param1); + if ((cmd.command != CMD_SPEED) + && (cmd.command != CMD_TEMPO) + && (cmd.command != CMD_PATTERNBREAK)) + ConvertMDLCommand(&cmd, command2, param2); + *m = cmd; + m += nChannels; + row++; + } + break; + + // Empty Slots + default: + row += xx+1; + m += (xx+1)*nChannels; + if (row >= nRows) break; + } + } +} + + + +BOOL CSoundFile::ReadMDL(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + DWORD dwMemPos, dwPos, blocklen, dwTrackPos; + const MDLSONGHEADER *pmsh = (const MDLSONGHEADER *)lpStream; + MDLINFOBLOCK *pmib; + MDLPATTERNDATA *pmpd; + UINT i,j, norders = 0, npatterns = 0, ntracks = 0; + UINT ninstruments = 0, nsamples = 0; + WORD block; + WORD patterntracks[MAX_PATTERNS*32]; + BYTE smpinfo[MAX_SAMPLES]; + BYTE insvolenv[MAX_INSTRUMENTS]; + BYTE inspanenv[MAX_INSTRUMENTS]; + LPCBYTE pvolenv, ppanenv, ppitchenv; + UINT nvolenv, npanenv, npitchenv; + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pmsh->id != 0x4C444D44) || ((pmsh->version & 0xF0) > 0x10)) return FALSE; + memset(patterntracks, 0, sizeof(patterntracks)); + memset(smpinfo, 0, sizeof(smpinfo)); + memset(insvolenv, 0, sizeof(insvolenv)); + memset(inspanenv, 0, sizeof(inspanenv)); + dwMemPos = 5; + dwTrackPos = 0; + pvolenv = ppanenv = ppitchenv = NULL; + nvolenv = npanenv = npitchenv = 0; + m_nSamples = m_nInstruments = 0; + m_dwSongFlags |= SONG_INSTRUMENTMODE; + while (dwMemPos+6 < dwMemLength) + { + block = *((WORD *)(lpStream+dwMemPos)); + blocklen = *((DWORD *)(lpStream+dwMemPos+2)); + dwMemPos += 6; + if (dwMemPos + blocklen > dwMemLength) + { + if (dwMemPos == 11) return FALSE; + break; + } + switch(block) + { + // IN: infoblock + case 0x4E49: + pmib = (MDLINFOBLOCK *)(lpStream+dwMemPos); + memcpy(m_szNames[0], pmib->songname, 32); + norders = pmib->norders; + if (norders > MAX_ORDERS) norders = MAX_ORDERS; + m_nRestartPos = pmib->repeatpos; + m_nDefaultGlobalVolume = pmib->globalvol; + if (m_nDefaultGlobalVolume == 255) + m_nDefaultGlobalVolume++; + m_nDefaultTempo = pmib->tempo; + m_nDefaultSpeed = pmib->speed; + m_nChannels = 4; + for (i=0; i<32; i++) + { + ChnSettings[i].nVolume = 64; + ChnSettings[i].nPan = (pmib->channelinfo[i] & 0x7F) << 1; + if (pmib->channelinfo[i] & 0x80) + ChnSettings[i].dwFlags |= CHN_MUTE; + else + m_nChannels = i+1; + } + for (j=0; jseq[j]; + break; + // ME: song message + case 0x454D: + if (blocklen) + { + if (m_lpszSongComments) delete m_lpszSongComments; + m_lpszSongComments = new char[blocklen]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos, blocklen); + m_lpszSongComments[blocklen-1] = 0; + } + } + break; + // PA: Pattern Data + case 0x4150: + npatterns = lpStream[dwMemPos]; + if (npatterns > MAX_PATTERNS) npatterns = MAX_PATTERNS; + dwPos = dwMemPos + 1; + for (i=0; i= dwMemLength) break; + pmpd = (MDLPATTERNDATA *)(lpStream + dwPos); + if (pmpd->channels > 32) break; + PatternSize[i] = pmpd->lastrow+1; + PatternAllocSize[i] = pmpd->lastrow+1; + if (m_nChannels < pmpd->channels) m_nChannels = pmpd->channels; + dwPos += 18 + 2*pmpd->channels; + for (j=0; jchannels; j++) + { + patterntracks[i*32+j] = pmpd->data[j]; + } + } + break; + // TR: Track Data + case 0x5254: + if (dwTrackPos) break; + ntracks = *((WORD *)(lpStream+dwMemPos)); + dwTrackPos = dwMemPos+2; + break; + // II: Instruments + case 0x4949: + ninstruments = lpStream[dwMemPos]; + dwPos = dwMemPos+1; + for (i=0; i= MAX_INSTRUMENTS) || (!nins)) break; + if (m_nInstruments < nins) m_nInstruments = nins; + if (!Headers[nins]) + { + UINT note = 12; + if ((Headers[nins] = new INSTRUMENTHEADER) == NULL) break; + INSTRUMENTHEADER *penv = Headers[nins]; + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + memcpy(penv->name, lpStream+dwPos+2, 32); + penv->nGlobalVol = 64; + penv->nPPC = 5*12; + for (j=0; jNoteMap[note] = note+1; + if (ps[0] < MAX_SAMPLES) + { + int ismp = ps[0]; + penv->Keyboard[note] = ps[0]; + Ins[ismp].nVolume = ps[2]; + Ins[ismp].nPan = ps[4] << 1; + Ins[ismp].nVibType = ps[11]; + Ins[ismp].nVibSweep = ps[10]; + Ins[ismp].nVibDepth = ps[9]; + Ins[ismp].nVibRate = ps[8]; + } + penv->nFadeOut = (ps[7] << 8) | ps[6]; + if (penv->nFadeOut == 0xFFFF) penv->nFadeOut = 0; + note++; + } + // Use volume envelope ? + if (ps[3] & 0x80) + { + penv->dwFlags |= ENV_VOLUME; + insvolenv[nins] = (ps[3] & 0x3F) + 1; + } + // Use panning envelope ? + if (ps[5] & 0x80) + { + penv->dwFlags |= ENV_PANNING; + inspanenv[nins] = (ps[5] & 0x3F) + 1; + } + } + } + dwPos += 34 + 14*lpStream[dwPos+1]; + } + for (j=1; j<=m_nInstruments; j++) if (!Headers[j]) + { + Headers[j] = new INSTRUMENTHEADER; + if (Headers[j]) memset(Headers[j], 0, sizeof(INSTRUMENTHEADER)); + } + break; + // VE: Volume Envelope + case 0x4556: + if ((nvolenv = lpStream[dwMemPos]) == 0) break; + if (dwMemPos + nvolenv*32 + 1 <= dwMemLength) pvolenv = lpStream + dwMemPos + 1; + break; + // PE: Panning Envelope + case 0x4550: + if ((npanenv = lpStream[dwMemPos]) == 0) break; + if (dwMemPos + npanenv*32 + 1 <= dwMemLength) ppanenv = lpStream + dwMemPos + 1; + break; + // FE: Pitch Envelope + case 0x4546: + if ((npitchenv = lpStream[dwMemPos]) == 0) break; + if (dwMemPos + npitchenv*32 + 1 <= dwMemLength) ppitchenv = lpStream + dwMemPos + 1; + break; + // IS: Sample Infoblock + case 0x5349: + nsamples = lpStream[dwMemPos]; + dwPos = dwMemPos+1; + for (i=0; i= MAX_SAMPLES) || (!nins)) continue; + if (m_nSamples < nins) m_nSamples = nins; + MODINSTRUMENT *pins = &Ins[nins]; + memcpy(m_szNames[nins], lpStream+dwPos+1, 32); + memcpy(pins->name, lpStream+dwPos+33, 8); + pins->nC4Speed = *((DWORD *)(lpStream+dwPos+41)); + pins->nLength = *((DWORD *)(lpStream+dwPos+45)); + pins->nLoopStart = *((DWORD *)(lpStream+dwPos+49)); + pins->nLoopEnd = pins->nLoopStart + *((DWORD *)(lpStream+dwPos+53)); + if (pins->nLoopEnd > pins->nLoopStart) pins->uFlags |= CHN_LOOP; + pins->nGlobalVol = 64; + if (lpStream[dwPos+58] & 0x01) + { + pins->uFlags |= CHN_16BIT; + pins->nLength >>= 1; + pins->nLoopStart >>= 1; + pins->nLoopEnd >>= 1; + } + if (lpStream[dwPos+58] & 0x02) pins->uFlags |= CHN_PINGPONGLOOP; + smpinfo[nins] = (lpStream[dwPos+58] >> 2) & 3; + } + break; + // SA: Sample Data + case 0x4153: + dwPos = dwMemPos; + for (i=1; i<=m_nSamples; i++) if ((Ins[i].nLength) && (!Ins[i].pSample) && (smpinfo[i] != 3) && (dwPos < dwMemLength)) + { + MODINSTRUMENT *pins = &Ins[i]; + UINT flags = (pins->uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S; + if (!smpinfo[i]) + { + dwPos += ReadSample(pins, flags, (LPSTR)(lpStream+dwPos), dwMemLength - dwPos); + } else + { + DWORD dwLen = *((DWORD *)(lpStream+dwPos)); + dwPos += 4; + if ((dwPos+dwLen <= dwMemLength) && (dwLen > 4)) + { + flags = (pins->uFlags & CHN_16BIT) ? RS_MDL16 : RS_MDL8; + ReadSample(pins, flags, (LPSTR)(lpStream+dwPos), dwLen); + } + dwPos += dwLen; + } + } + break; + } + dwMemPos += blocklen; + } + // Unpack Patterns + if ((dwTrackPos) && (npatterns) && (m_nChannels) && (ntracks)) + { + for (UINT ipat=0; ipatVolEnv.nNodes = 15; + for (UINT iv=0; iv<15; iv++) + { + if (iv) vtick += pve[iv*2+1]; + penv->VolEnv.Ticks[iv] = vtick; + penv->VolEnv.Values[iv] = pve[iv*2+2]; + if (!pve[iv*2+1]) + { + penv->VolEnv.nNodes = iv+1; + break; + } + } + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = pve[31] & 0x0F; + if (pve[31] & 0x10) penv->dwFlags |= ENV_VOLSUSTAIN; + if (pve[31] & 0x20) penv->dwFlags |= ENV_VOLLOOP; + penv->VolEnv.nLoopStart = pve[32] & 0x0F; + penv->VolEnv.nLoopEnd = pve[32] >> 4; + } + } + // Setup panning envelope + if ((npanenv) && (ppanenv) && (inspanenv[iIns])) + { + LPCBYTE ppe = ppanenv; + for (UINT npe=0; npePanEnv.nNodes = 15; + for (UINT iv=0; iv<15; iv++) + { + if (iv) vtick += ppe[iv*2+1]; + penv->PanEnv.Ticks[iv] = vtick; + penv->PanEnv.Values[iv] = ppe[iv*2+2]; + if (!ppe[iv*2+1]) + { + penv->PanEnv.nNodes = iv+1; + break; + } + } + if (ppe[31] & 0x10) penv->dwFlags |= ENV_PANSUSTAIN; + if (ppe[31] & 0x20) penv->dwFlags |= ENV_PANLOOP; + penv->PanEnv.nLoopStart = ppe[32] & 0x0F; + penv->PanEnv.nLoopEnd = ppe[32] >> 4; + } + } + } + m_dwSongFlags |= SONG_LINEARSLIDES; + m_nType = MOD_TYPE_MDL; + return TRUE; +} + + +///////////////////////////////////////////////////////////////////////// +// MDL Sample Unpacking + +// MDL Huffman ReadBits compression +WORD MDLReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n) +//----------------------------------------------------------------- +{ + WORD v = (WORD)(bitbuf & ((1 << n) - 1) ); + bitbuf >>= n; + bitnum -= n; + if (bitnum <= 24) + { + bitbuf |= (((DWORD)(*ibuf++)) << bitnum); + bitnum += 8; + } + return v; +} + + diff --git a/modplug/load_med.cpp b/modplug/load_med.cpp new file mode 100644 index 000000000..858ef5e4a --- /dev/null +++ b/modplug/load_med.cpp @@ -0,0 +1,919 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#define MED_LOG + +#ifdef MED_LOG +extern void Log(LPCSTR s, ...); +#endif + +////////////////////////////////////////////////////////// +// OctaMed MED file support (import only) + +// flags +#define MMD_FLAG_FILTERON 0x1 +#define MMD_FLAG_JUMPINGON 0x2 +#define MMD_FLAG_JUMP8TH 0x4 +#define MMD_FLAG_INSTRSATT 0x8 // instruments are attached (this is a module) +#define MMD_FLAG_VOLHEX 0x10 +#define MMD_FLAG_STSLIDE 0x20 // SoundTracker mode for slides +#define MMD_FLAG_8CHANNEL 0x40 // OctaMED 8 channel song +#define MMD_FLAG_SLOWHQ 0x80 // HQ slows playing speed (V2-V4 compatibility) +// flags2 +#define MMD_FLAG2_BMASK 0x1F +#define MMD_FLAG2_BPM 0x20 +#define MMD_FLAG2_MIX 0x80 // uses Mixing (V7+) +// flags3: +#define MMD_FLAG3_STEREO 0x1 // mixing in Stereo mode +#define MMD_FLAG3_FREEPAN 0x2 // free panning +#define MMD_FLAG3_GM 0x4 // module designed for GM/XG compatibility + + +// generic MMD tags +#define MMDTAG_END 0 +#define MMDTAG_PTR 0x80000000 // data needs relocation +#define MMDTAG_MUSTKNOW 0x40000000 // loader must fail if this isn't recognized +#define MMDTAG_MUSTWARN 0x20000000 // loader must warn if this isn't recognized + +// ExpData tags +// # of effect groups, including the global group (will +// override settings in MMDSong struct), default = 1 +#define MMDTAG_EXP_NUMFXGROUPS 1 +#define MMDTAG_TRK_NAME (MMDTAG_PTR|1) // trackinfo tags +#define MMDTAG_TRK_NAMELEN 2 // namelen includes zero term. +#define MMDTAG_TRK_FXGROUP 3 +// effectinfo tags +#define MMDTAG_FX_ECHOTYPE 1 +#define MMDTAG_FX_ECHOLEN 2 +#define MMDTAG_FX_ECHODEPTH 3 +#define MMDTAG_FX_STEREOSEP 4 +#define MMDTAG_FX_GROUPNAME (MMDTAG_PTR|5) // the Global Effects group shouldn't have name saved! +#define MMDTAG_FX_GRPNAMELEN 6 // namelen includes zero term. + +#pragma pack(1) + +typedef struct tagMEDMODULEHEADER +{ + DWORD id; // MMD1-MMD3 + DWORD modlen; // Size of file + DWORD song; // Position in file for this song + WORD psecnum; + WORD pseq; + DWORD blockarr; // Position in file for blocks + DWORD mmdflags; + DWORD smplarr; // Position in file for samples + DWORD reserved; + DWORD expdata; // Absolute offset in file for ExpData (0 if not present) + DWORD reserved2; + WORD pstate; + WORD pblock; + WORD pline; + WORD pseqnum; + WORD actplayline; + BYTE counter; + BYTE extra_songs; // # of songs - 1 +} MEDMODULEHEADER; + + +typedef struct tagMMD0SAMPLE +{ + WORD rep, replen; + BYTE midich; + BYTE midipreset; + BYTE svol; + signed char strans; +} MMD0SAMPLE; + + +// Sample header is immediately followed by sample data... +typedef struct tagMMDSAMPLEHEADER +{ + DWORD length; // length of *one* *unpacked* channel in *bytes* + WORD type; + // if non-negative + // bits 0-3 reserved for multi-octave instruments, not supported on the PC + // 0x10: 16 bit (otherwise 8 bit) + // 0x20: Stereo (otherwise mono) + // 0x40: Uses DeltaCode + // 0x80: Packed data + // -1: Synth + // -2: Hybrid + // if type indicates packed data, these fields follow, otherwise we go right to the data + WORD packtype; // Only 1 = ADPCM is supported + WORD subtype; // Packing subtype + // ADPCM subtype + // 1: g723_40 + // 2: g721 + // 3: g723_24 + BYTE commonflags; // flags common to all packtypes (none defined so far) + BYTE packerflags; // flags for the specific packtype + ULONG leftchlen; // packed length of left channel in bytes + ULONG rightchlen; // packed length of right channel in bytes (ONLY PRESENT IN STEREO SAMPLES) + BYTE SampleData[1]; // Sample Data +} MMDSAMPLEHEADER; + + +// MMD0/MMD1 song header +typedef struct tagMMD0SONGHEADER +{ + MMD0SAMPLE sample[63]; + WORD numblocks; // # of blocks + WORD songlen; // # of entries used in playseq + BYTE playseq[256]; // Play sequence + WORD deftempo; // BPM tempo + signed char playtransp; // Play transpose + BYTE flags; // 0x10: Hex Volumes | 0x20: ST/NT/PT Slides | 0x40: 8 Channels song + BYTE flags2; // [b4-b0]+1: Tempo LPB, 0x20: tempo mode, 0x80: mix_conv=on + BYTE tempo2; // tempo TPL + BYTE trkvol[16]; // track volumes + BYTE mastervol; // master volume + BYTE numsamples; // # of samples (max=63) +} MMD0SONGHEADER; + + +// MMD2/MMD3 song header +typedef struct tagMMD2SONGHEADER +{ + MMD0SAMPLE sample[63]; + WORD numblocks; // # of blocks + WORD numsections; // # of sections + DWORD playseqtable; // filepos of play sequence + DWORD sectiontable; // filepos of sections table (WORD array) + DWORD trackvols; // filepos of tracks volume (BYTE array) + WORD numtracks; // # of tracks (max 64) + WORD numpseqs; // # of play sequences + DWORD trackpans; // filepos of tracks pan values (BYTE array) + LONG flags3; // 0x1:stereo_mix, 0x2:free_panning, 0x4:GM/XG compatibility + WORD voladj; // vol_adjust (set to 100 if 0) + WORD channels; // # of channels (4 if =0) + BYTE mix_echotype; // 1:normal,2:xecho + BYTE mix_echodepth; // 1..6 + WORD mix_echolen; // > 0 + signed char mix_stereosep; // -4..4 + BYTE pad0[223]; + WORD deftempo; // BPM tempo + signed char playtransp; // play transpose + BYTE flags; // 0x1:filteron, 0x2:jumpingon, 0x4:jump8th, 0x8:instr_attached, 0x10:hex_vol, 0x20:PT_slides, 0x40:8ch_conv,0x80:hq slows playing speed + BYTE flags2; // 0x80:mix_conv=on, [b4-b0]+1:tempo LPB, 0x20:tempo_mode + BYTE tempo2; // tempo TPL + BYTE pad1[16]; + BYTE mastervol; // master volume + BYTE numsamples; // # of samples (max 63) +} MMD2SONGHEADER; + +// For MMD0 the note information is held in 3 bytes, byte0, byte1, byte2. For reference we +// number the bits in each byte 0..7, where 0 is the low bit. +// The note is held as bits 5..0 of byte0 +// The instrument is encoded in 6 bits, bits 7 and 6 of byte0 and bits 7,6,5,4 of byte1 +// The command number is bits 3,2,1,0 of byte1, command data is in byte2: +// For command 0, byte2 represents the second data byte, otherwise byte2 +// represents the first data byte. +typedef struct tagMMD0BLOCK +{ + BYTE numtracks; + BYTE lines; // File value is 1 less than actual, so 0 -> 1 line +} MMD0BLOCK; // BYTE data[lines+1][tracks][3]; + + +// For MMD1,MMD2,MMD3 the note information is carried in 4 bytes, byte0, byte1, +// byte2 and byte3 +// The note is held as byte0 (values above 0x84 are ignored) +// The instrument is held as byte1 +// The command number is held as byte2, command data is in byte3 +// For commands 0 and 0x19 byte3 represents the second data byte, +// otherwise byte2 represents the first data byte. +typedef struct tagMMD1BLOCK +{ + WORD numtracks; // Number of tracks, may be > 64, but then that data is skipped. + WORD lines; // Stored value is 1 less than actual, so 0 -> 1 line + DWORD info; // Offset of BlockInfo (if 0, no block_info is present) +} MMD1BLOCK; + + +typedef struct tagMMD1BLOCKINFO +{ + DWORD hlmask; // Unimplemented - ignore + DWORD blockname; // file offset of block name + DWORD blocknamelen; // length of block name (including term. 0) + DWORD pagetable; // file offset of command page table + DWORD cmdexttable; // file offset of command extension table + DWORD reserved[4]; // future expansion +} MMD1BLOCKINFO; + + +// A set of play sequences is stored as an array of ULONG files offsets +// Each offset points to the play sequence itself. +typedef struct tagMMD2PLAYSEQ +{ + CHAR name[32]; + DWORD command_offs; // filepos of command table + DWORD reserved; + WORD length; + WORD seq[512]; // skip if > 0x8000 +} MMD2PLAYSEQ; + + +// A command table contains commands that effect a particular play sequence +// entry. The only commands read in are STOP or POSJUMP, all others are ignored +// POSJUMP is presumed to have extra bytes containing a WORD for the position +typedef struct tagMMDCOMMAND +{ + WORD offset; // Offset within current sequence entry + BYTE cmdnumber; // STOP (537) or POSJUMP (538) (others skipped) + BYTE extra_count; + BYTE extra_bytes[4];// [extra_count]; +} MMDCOMMAND; // Last entry has offset == 0xFFFF, cmd_number == 0 and 0 extrabytes + + +typedef struct tagMMD0EXP +{ + DWORD nextmod; // File offset of next Hdr + DWORD exp_smp; // Pointer to extra instrument data + WORD s_ext_entries; // Number of extra instrument entries + WORD s_ext_entrsz; // Size of extra instrument data + DWORD annotxt; + DWORD annolen; + DWORD iinfo; // Instrument names + WORD i_ext_entries; + WORD i_ext_entrsz; + DWORD jumpmask; + DWORD rgbtable; + BYTE channelsplit[4]; // Only used if 8ch_conv (extra channel for every nonzero entry) + DWORD n_info; + DWORD songname; // Song name + DWORD songnamelen; + DWORD dumps; + DWORD mmdinfo; + DWORD mmdrexx; + DWORD mmdcmd3x; + DWORD trackinfo_ofs; // ptr to song->numtracks ptrs to tag lists + DWORD effectinfo_ofs; // ptr to group ptrs + DWORD tag_end; +} MMD0EXP; + +#pragma pack() + + + +static void MedConvert(MODCOMMAND *p, const MMD0SONGHEADER *pmsh) +//--------------------------------------------------------------- +{ + const BYTE bpmvals[9] = { 179,164,152,141,131,123,116,110,104}; + + UINT command = p->command; + UINT param = p->param; + switch(command) + { + case 0x00: if (param) command = CMD_ARPEGGIO; else command = 0; break; + case 0x01: command = CMD_PORTAMENTOUP; break; + case 0x02: command = CMD_PORTAMENTODOWN; break; + case 0x03: command = CMD_TONEPORTAMENTO; break; + case 0x04: command = CMD_VIBRATO; break; + case 0x05: command = CMD_TONEPORTAVOL; break; + case 0x06: command = CMD_VIBRATOVOL; break; + case 0x07: command = CMD_TREMOLO; break; + case 0x0A: if (param & 0xF0) param &= 0xF0; command = CMD_VOLUMESLIDE; if (!param) command = 0; break; + case 0x0B: command = CMD_POSITIONJUMP; break; + case 0x0C: command = CMD_VOLUME; + if (pmsh->flags & MMD_FLAG_VOLHEX) + { + if (param < 0x80) + { + param = (param+1) / 2; + } else command = 0; + } else + { + if (param <= 0x99) + { + param = (param >> 4)*10+((param & 0x0F) % 10); + if (param > 64) param = 64; + } else command = 0; + } + break; + case 0x09: command = (param < 0x20) ? CMD_SPEED : CMD_TEMPO; break; + case 0x0D: if (param & 0xF0) param &= 0xF0; command = CMD_VOLUMESLIDE; if (!param) command = 0; break; + case 0x0F: // Set Tempo / Special + // F.00 = Pattern Break + if (!param) command = CMD_PATTERNBREAK; else + // F.01 - F.F0: Set tempo/speed + if (param <= 0xF0) + { + if (pmsh->flags & MMD_FLAG_8CHANNEL) + { + param = (param > 10) ? 99 : bpmvals[param-1]; + } else + // F.01 - F.0A: Set Speed + if (param <= 0x0A) + { + command = CMD_SPEED; + } else + // Old tempo + if (!(pmsh->flags2 & MMD_FLAG2_BPM)) + { + param = _muldiv(param, 5*715909, 2*474326); + } + // F.0B - F.F0: Set Tempo (assumes LPB=4) + if (param > 0x0A) + { + command = CMD_TEMPO; + if (param < 0x21) param = 0x21; + if (param > 240) param = 240; + } + } else + switch(param) + { + // F.F1: Retrig 2x + case 0xF1: + command = CMD_MODCMDEX; + param = 0x93; + break; + // F.F2: Note Delay 2x + case 0xF2: + command = CMD_MODCMDEX; + param = 0xD3; + break; + // F.F3: Retrig 3x + case 0xF3: + command = CMD_MODCMDEX; + param = 0x92; + break; + // F.F4: Note Delay 1/3 + case 0xF4: + command = CMD_MODCMDEX; + param = 0xD2; + break; + // F.F5: Note Delay 2/3 + case 0xF5: + command = CMD_MODCMDEX; + param = 0xD4; + break; + // F.F8: Filter Off + case 0xF8: + command = CMD_MODCMDEX; + param = 0x00; + break; + // F.F9: Filter On + case 0xF9: + command = CMD_MODCMDEX; + param = 0x01; + break; + // F.FD: Very fast tone-portamento + case 0xFD: + command = CMD_TONEPORTAMENTO; + param = 0xFF; + break; + // F.FE: End Song + case 0xFE: + command = CMD_SPEED; + param = 0; + break; + // F.FF: Note Cut + case 0xFF: + command = CMD_MODCMDEX; + param = 0xC0; + break; + default: +#ifdef MED_LOG + Log("Unknown Fxx command: cmd=0x%02X param=0x%02X\n", command, param); +#endif + param = command = 0; + } + break; + // 11.0x: Fine Slide Up + case 0x11: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0x10; + break; + // 12.0x: Fine Slide Down + case 0x12: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0x20; + break; + // 14.xx: Vibrato + case 0x14: + command = CMD_VIBRATO; + break; + // 15.xx: FineTune + case 0x15: + command = CMD_MODCMDEX; + param &= 0x0F; + param |= 0x50; + break; + // 16.xx: Pattern Loop + case 0x16: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0x60; + break; + // 18.xx: Note Cut + case 0x18: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0xC0; + break; + // 19.xx: Sample Offset + case 0x19: + command = CMD_OFFSET; + break; + // 1A.0x: Fine Volume Up + case 0x1A: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0xA0; + break; + // 1B.0x: Fine Volume Down + case 0x1B: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0xB0; + break; + // 1D.xx: Pattern Break + case 0x1D: + command = CMD_PATTERNBREAK; + break; + // 1E.0x: Pattern Delay + case 0x1E: + command = CMD_MODCMDEX; + if (param > 0x0F) param = 0x0F; + param |= 0xE0; + break; + // 1F.xy: Retrig + case 0x1F: + command = CMD_RETRIG; + param &= 0x0F; + break; + // 2E.xx: set panning + case 0x2E: + command = CMD_MODCMDEX; + param = ((param + 0x10) & 0xFF) >> 1; + if (param > 0x0F) param = 0x0F; + param |= 0x80; + break; + default: +#ifdef MED_LOG + // 0x2E ? + Log("Unknown command: cmd=0x%02X param=0x%02X\n", command, param); +#endif + command = param = 0; + } + p->command = command; + p->param = param; +} + + +BOOL CSoundFile::ReadMed(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + const MEDMODULEHEADER *pmmh; + const MMD0SONGHEADER *pmsh; + const MMD2SONGHEADER *pmsh2; + const MMD0EXP *pmex; + DWORD dwBlockArr, dwSmplArr, dwExpData, wNumBlocks; + LPDWORD pdwTable; + CHAR version; + UINT deftempo; + int playtransp = 0; + + if ((!lpStream) || (dwMemLength < 0x200)) return FALSE; + pmmh = (MEDMODULEHEADER *)lpStream; + if (((pmmh->id & 0x00FFFFFF) != 0x444D4D) || (!pmmh->song)) return FALSE; + // Check for 'MMDx' + DWORD dwSong = bswapBE32(pmmh->song); + if ((dwSong >= dwMemLength) || (dwSong + sizeof(MMD0SONGHEADER) >= dwMemLength)) return FALSE; + version = (signed char)((pmmh->id >> 24) & 0xFF); + if ((version < '0') || (version > '3')) return FALSE; +#ifdef MED_LOG + Log("\nLoading MMD%c module (flags=0x%02X)...\n", version, bswapBE32(pmmh->mmdflags)); + Log(" modlen = %d\n", bswapBE32(pmmh->modlen)); + Log(" song = 0x%08X\n", bswapBE32(pmmh->song)); + Log(" psecnum = %d\n", bswapBE16(pmmh->psecnum)); + Log(" pseq = %d\n", bswapBE16(pmmh->pseq)); + Log(" blockarr = 0x%08X\n", bswapBE32(pmmh->blockarr)); + Log(" mmdflags = 0x%08X\n", bswapBE32(pmmh->mmdflags)); + Log(" smplarr = 0x%08X\n", bswapBE32(pmmh->smplarr)); + Log(" reserved = 0x%08X\n", bswapBE32(pmmh->reserved)); + Log(" expdata = 0x%08X\n", bswapBE32(pmmh->expdata)); + Log(" reserved2= 0x%08X\n", bswapBE32(pmmh->reserved2)); + Log(" pstate = %d\n", bswapBE16(pmmh->pstate)); + Log(" pblock = %d\n", bswapBE16(pmmh->pblock)); + Log(" pline = %d\n", bswapBE16(pmmh->pline)); + Log(" pseqnum = %d\n", bswapBE16(pmmh->pseqnum)); + Log(" actplayline=%d\n", bswapBE16(pmmh->actplayline)); + Log(" counter = %d\n", pmmh->counter); + Log(" extra_songs = %d\n", pmmh->extra_songs); + Log("\n"); +#endif + m_nType = MOD_TYPE_MED; + m_nSongPreAmp = 0x20; + dwBlockArr = bswapBE32(pmmh->blockarr); + dwSmplArr = bswapBE32(pmmh->smplarr); + dwExpData = bswapBE32(pmmh->expdata); + if ((dwExpData) && (dwExpData+sizeof(MMD0EXP) < dwMemLength)) + pmex = (MMD0EXP *)(lpStream+dwExpData); + else + pmex = NULL; + pmsh = (MMD0SONGHEADER *)(lpStream + dwSong); + pmsh2 = (MMD2SONGHEADER *)pmsh; +#ifdef MED_LOG + if (version < '2') + { + Log("MMD0 Header:\n"); + Log(" numblocks = %d\n", bswapBE16(pmsh->numblocks)); + Log(" songlen = %d\n", bswapBE16(pmsh->songlen)); + Log(" playseq = "); + for (UINT idbg1=0; idbg1<16; idbg1++) Log("%2d, ", pmsh->playseq[idbg1]); + Log("...\n"); + Log(" deftempo = 0x%04X\n", bswapBE16(pmsh->deftempo)); + Log(" playtransp = %d\n", (signed char)pmsh->playtransp); + Log(" flags(1,2) = 0x%02X, 0x%02X\n", pmsh->flags, pmsh->flags2); + Log(" tempo2 = %d\n", pmsh->tempo2); + Log(" trkvol = "); + for (UINT idbg2=0; idbg2<16; idbg2++) Log("0x%02X, ", pmsh->trkvol[idbg2]); + Log("...\n"); + Log(" mastervol = 0x%02X\n", pmsh->mastervol); + Log(" numsamples = %d\n", pmsh->numsamples); + } else + { + Log("MMD2 Header:\n"); + Log(" numblocks = %d\n", bswapBE16(pmsh2->numblocks)); + Log(" numsections= %d\n", bswapBE16(pmsh2->numsections)); + Log(" playseqptr = 0x%04X\n", bswapBE32(pmsh2->playseqtable)); + Log(" sectionptr = 0x%04X\n", bswapBE32(pmsh2->sectiontable)); + Log(" trackvols = 0x%04X\n", bswapBE32(pmsh2->trackvols)); + Log(" numtracks = %d\n", bswapBE16(pmsh2->numtracks)); + Log(" numpseqs = %d\n", bswapBE16(pmsh2->numpseqs)); + Log(" trackpans = 0x%04X\n", bswapBE32(pmsh2->trackpans)); + Log(" flags3 = 0x%08X\n", bswapBE32(pmsh2->flags3)); + Log(" voladj = %d\n", bswapBE16(pmsh2->voladj)); + Log(" channels = %d\n", bswapBE16(pmsh2->channels)); + Log(" echotype = %d\n", pmsh2->mix_echotype); + Log(" echodepth = %d\n", pmsh2->mix_echodepth); + Log(" echolen = %d\n", bswapBE16(pmsh2->mix_echolen)); + Log(" stereosep = %d\n", (signed char)pmsh2->mix_stereosep); + Log(" deftempo = 0x%04X\n", bswapBE16(pmsh2->deftempo)); + Log(" playtransp = %d\n", (signed char)pmsh2->playtransp); + Log(" flags(1,2) = 0x%02X, 0x%02X\n", pmsh2->flags, pmsh2->flags2); + Log(" tempo2 = %d\n", pmsh2->tempo2); + Log(" mastervol = 0x%02X\n", pmsh2->mastervol); + Log(" numsamples = %d\n", pmsh->numsamples); + } + Log("\n"); +#endif + wNumBlocks = bswapBE16(pmsh->numblocks); + m_nChannels = 4; + m_nSamples = pmsh->numsamples; + if (m_nSamples > 63) m_nSamples = 63; + m_nStereoSeparation = ((pmsh2->mix_stereosep < 0) ? -32 : 32) * pmsh2->mix_stereosep; + // Tempo + m_nDefaultTempo = 125; + deftempo = bswapBE16(pmsh->deftempo); + if (!deftempo) deftempo = 125; + if (pmsh->flags2 & MMD_FLAG2_BPM) + { + UINT tempo_tpl = (pmsh->flags2 & MMD_FLAG2_BMASK) + 1; + if (!tempo_tpl) tempo_tpl = 4; + deftempo *= tempo_tpl; + deftempo /= 4; + #ifdef MED_LOG + Log("newtempo: %3d bpm (bpm=%3d lpb=%2d)\n", deftempo, bswapBE16(pmsh->deftempo), (pmsh->flags2 & MMD_FLAG2_BMASK)+1); + #endif + } else + { + deftempo = _muldiv(deftempo, 5*715909, 2*474326); + #ifdef MED_LOG + Log("oldtempo: %3d bpm (bpm=%3d)\n", deftempo, bswapBE16(pmsh->deftempo)); + #endif + } + // Speed + m_nDefaultSpeed = pmsh->tempo2; + if (!m_nDefaultSpeed) m_nDefaultSpeed = 6; + if (deftempo < 0x21) deftempo = 0x21; + if (deftempo > 255) + { + while ((m_nDefaultSpeed > 3) && (deftempo > 260)) + { + deftempo = (deftempo * (m_nDefaultSpeed - 1)) / m_nDefaultSpeed; + m_nDefaultSpeed--; + } + if (deftempo > 255) deftempo = 255; + } + m_nDefaultTempo = deftempo; + // Reading Samples + for (UINT iSHdr=0; iSHdrnLoopStart = bswapBE16(pmsh->sample[iSHdr].rep) << 1; + pins->nLoopEnd = pins->nLoopStart + (bswapBE16(pmsh->sample[iSHdr].replen) << 1); + pins->nVolume = (pmsh->sample[iSHdr].svol << 2); + pins->nGlobalVol = 64; + if (pins->nVolume > 256) pins->nVolume = 256; + pins->RelativeTone = -12 * pmsh->sample[iSHdr].strans; + pins->nPan = 128; + if (pins->nLoopEnd) pins->uFlags |= CHN_LOOP; + } + // Common Flags + if (!(pmsh->flags & 0x20)) m_dwSongFlags |= SONG_FASTVOLSLIDES; + // Reading play sequence + if (version < '2') + { + UINT nbo = pmsh->songlen >> 8; + if (nbo >= MAX_ORDERS) nbo = MAX_ORDERS-1; + if (!nbo) nbo = 1; + memcpy(Order, pmsh->playseq, nbo); + playtransp = pmsh->playtransp; + } else + { + UINT nOrders, nSections; + UINT nTrks = bswapBE16(pmsh2->numtracks); + if ((nTrks >= 4) && (nTrks <= 32)) m_nChannels = nTrks; + DWORD playseqtable = bswapBE32(pmsh2->playseqtable); + UINT numplayseqs = bswapBE16(pmsh2->numpseqs); + if (!numplayseqs) numplayseqs = 1; + nOrders = 0; + nSections = bswapBE16(pmsh2->numsections); + DWORD sectiontable = bswapBE32(pmsh2->sectiontable); + if ((!nSections) || (!sectiontable) || (sectiontable >= dwMemLength-2)) nSections = 1; + nOrders = 0; + for (UINT iSection=0; iSectionname, 31); + UINT n = bswapBE16(pmps->length); + if (pseq+n <= dwMemLength) + { + for (UINT i=0; iseq[i] >> 8; + if ((seqval < wNumBlocks) && (nOrders < MAX_ORDERS-1)) + { + Order[nOrders++] = seqval; + } + } + } + } + } + playtransp = pmsh2->playtransp; + while (nOrders < MAX_ORDERS) Order[nOrders++] = 0xFF; + } + // Reading Expansion structure + if (pmex) + { + // Channel Split + if ((m_nChannels == 4) && (pmsh->flags & 0x40)) + { + for (UINT i8ch=0; i8ch<4; i8ch++) + { + if (pmex->channelsplit[i8ch]) m_nChannels++; + } + } + // Song Comments + UINT annotxt = bswapBE32(pmex->annotxt); + UINT annolen = bswapBE32(pmex->annolen); + if ((annotxt) && (annolen) && (annotxt+annolen <= dwMemLength)) + { + m_lpszSongComments = new char[annolen+1]; + memcpy(m_lpszSongComments, lpStream+annotxt, annolen); + m_lpszSongComments[annolen] = 0; + } + // Song Name + UINT songname = bswapBE32(pmex->songname); + UINT songnamelen = bswapBE32(pmex->songnamelen); + if ((songname) && (songnamelen) && (songname+songnamelen <= dwMemLength)) + { + if (songnamelen > 31) songnamelen = 31; + memcpy(m_szNames[0], lpStream+songname, songnamelen); + } + // Sample Names + DWORD smpinfoex = bswapBE32(pmex->iinfo); + if (smpinfoex) + { + DWORD iinfoptr = bswapBE32(pmex->iinfo); + UINT ientries = bswapBE16(pmex->i_ext_entries); + UINT ientrysz = bswapBE16(pmex->i_ext_entrsz); + + if ((iinfoptr) && (ientrysz < 256) && (iinfoptr + ientries*ientrysz < dwMemLength)) + { + LPCSTR psznames = (LPCSTR)(lpStream + iinfoptr); + UINT maxnamelen = ientrysz; + if (maxnamelen > 32) maxnamelen = 32; + for (UINT i=0; itrackinfo_ofs); + if ((trackinfo_ofs) && (trackinfo_ofs + m_nChannels * 4 < dwMemLength)) + { + DWORD *ptrktags = (DWORD *)(lpStream + trackinfo_ofs); + for (UINT i=0; i MAX_CHANNELNAME) trknamelen = MAX_CHANNELNAME; + if ((trknameofs) && (trknameofs + trknamelen < dwMemLength)) + { + lstrcpyn(ChnSettings[i].szName, (LPCSTR)(lpStream+trknameofs), MAX_CHANNELNAME); + } + } + } + } + } + // Reading samples + if (dwSmplArr > dwMemLength - 4*m_nSamples) return TRUE; + pdwTable = (LPDWORD)(lpStream + dwSmplArr); + for (UINT iSmp=0; iSmp= dwMemLength) || (dwPos + sizeof(MMDSAMPLEHEADER) >= dwMemLength)) continue; + MMDSAMPLEHEADER *psdh = (MMDSAMPLEHEADER *)(lpStream + dwPos); + UINT len = bswapBE32(psdh->length); + #ifdef MED_LOG + Log("SampleData %d: stype=0x%02X len=%d\n", iSmp, bswapBE16(psdh->type), len); + #endif + if ((len > MAX_SAMPLE_LENGTH) || (dwPos + len + 6 > dwMemLength)) len = 0; + UINT flags = RS_PCM8S, stype = bswapBE16(psdh->type); + LPSTR psdata = (LPSTR)(lpStream + dwPos + 6); + if (stype & 0x80) + { + psdata += (stype & 0x20) ? 14 : 6; + } else + { + if (stype & 0x10) + { + Ins[iSmp+1].uFlags |= CHN_16BIT; + len /= 2; + flags = (stype & 0x20) ? RS_STPCM16M : RS_PCM16M; + } else + { + flags = (stype & 0x20) ? RS_STPCM8S : RS_PCM8S; + } + if (stype & 0x20) len /= 2; + } + Ins[iSmp+1].nLength = len; + ReadSample(&Ins[iSmp+1], flags, psdata, dwMemLength - dwPos - 6); + } + // Reading patterns (blocks) + if (wNumBlocks > MAX_PATTERNS) wNumBlocks = MAX_PATTERNS; + if ((!dwBlockArr) || (dwBlockArr > dwMemLength - 4*wNumBlocks)) return TRUE; + pdwTable = (LPDWORD)(lpStream + dwBlockArr); + playtransp += (version == '3') ? 24 : 48; + for (UINT iBlk=0; iBlk= dwMemLength) || (dwPos >= dwMemLength - 8)) continue; + UINT lines = 64, tracks = 4; + if (version == '0') + { + const MMD0BLOCK *pmb = (const MMD0BLOCK *)(lpStream + dwPos); + lines = pmb->lines + 1; + tracks = pmb->numtracks; + if (!tracks) tracks = m_nChannels; + if ((Patterns[iBlk] = AllocatePattern(lines, m_nChannels)) == NULL) continue; + PatternSize[iBlk] = lines; + PatternAllocSize[iBlk] = lines; + MODCOMMAND *p = Patterns[iBlk]; + LPBYTE s = (LPBYTE)(lpStream + dwPos + 2); + UINT maxlen = tracks*lines*3; + if (maxlen + dwPos > dwMemLength - 2) break; + for (UINT y=0; y> 4; + if (s[0] & 0x80) instr |= 0x10; + if (s[0] & 0x40) instr |= 0x20; + if ((note) && (note <= 132)) p->note = note + playtransp; + p->instr = instr; + p->command = s[1] & 0x0F; + p->param = s[2]; + // if (!iBlk) Log("%02X.%02X.%02X | ", s[0], s[1], s[2]); + MedConvert(p, pmsh); + p++; + } + //if (!iBlk) Log("\n"); + } + } else + { + MMD1BLOCK *pmb = (MMD1BLOCK *)(lpStream + dwPos); + #ifdef MED_LOG + Log("MMD1BLOCK: lines=%2d, tracks=%2d, offset=0x%04X\n", + bswapBE16(pmb->lines), bswapBE16(pmb->numtracks), bswapBE32(pmb->info)); + #endif + MMD1BLOCKINFO *pbi = NULL; + BYTE *pcmdext = NULL; + lines = (pmb->lines >> 8) + 1; + tracks = pmb->numtracks >> 8; + if (!tracks) tracks = m_nChannels; + if ((Patterns[iBlk] = AllocatePattern(lines, m_nChannels)) == NULL) continue; + PatternSize[iBlk] = (WORD)lines; + PatternAllocSize[iBlk] = (WORD)lines; + DWORD dwBlockInfo = bswapBE32(pmb->info); + if ((dwBlockInfo) && (dwBlockInfo < dwMemLength - sizeof(MMD1BLOCKINFO))) + { + pbi = (MMD1BLOCKINFO *)(lpStream + dwBlockInfo); + #ifdef MED_LOG + Log(" BLOCKINFO: blockname=0x%04X namelen=%d pagetable=0x%04X &cmdexttable=0x%04X\n", + bswapBE32(pbi->blockname), bswapBE32(pbi->blocknamelen), bswapBE32(pbi->pagetable), bswapBE32(pbi->cmdexttable)); + #endif + if ((pbi->blockname) && (pbi->blocknamelen)) + { + DWORD nameofs = bswapBE32(pbi->blockname); + UINT namelen = bswapBE32(pbi->blocknamelen); + if ((nameofs < dwMemLength) && (nameofs+namelen < dwMemLength)) + { + SetPatternName(iBlk, (LPCSTR)(lpStream+nameofs)); + } + } + if (pbi->cmdexttable) + { + DWORD cmdexttable = bswapBE32(pbi->cmdexttable); + if (cmdexttable < dwMemLength - 4) + { + cmdexttable = bswapBE32(*(DWORD *)(lpStream + cmdexttable)); + if ((cmdexttable) && (cmdexttable <= dwMemLength - lines*tracks)) + { + pcmdext = (BYTE *)(lpStream + cmdexttable); + } + } + } + } + MODCOMMAND *p = Patterns[iBlk]; + LPBYTE s = (LPBYTE)(lpStream + dwPos + 8); + UINT maxlen = tracks*lines*4; + if (maxlen + dwPos > dwMemLength - 8) break; + for (UINT y=0; y 120) rnote = 120; + p->note = (BYTE)rnote; + } + p->instr = s[1]; + p->command = s[2]; + p->param = s[3]; + if (pcmdext) p->vol = pcmdext[x]; + MedConvert(p, pmsh); + p++; + } + if (pcmdext) pcmdext += tracks; + } + } + } + // Setup channel pan positions + for (UINT iCh=0; iCh, + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +extern WORD ProTrackerPeriodTable[6*12]; + +////////////////////////////////////////////////////////// +// ProTracker / NoiseTracker MOD/NST file support + +void CSoundFile::ConvertModCommand(MODCOMMAND *m) const +//----------------------------------------------------- +{ + UINT command = m->command, param = m->param; + + switch(command) + { + case 0x00: if (param) command = CMD_ARPEGGIO; break; + case 0x01: command = CMD_PORTAMENTOUP; break; + case 0x02: command = CMD_PORTAMENTODOWN; break; + case 0x03: command = CMD_TONEPORTAMENTO; break; + case 0x04: command = CMD_VIBRATO; break; + case 0x05: command = CMD_TONEPORTAVOL; if (param & 0xF0) param &= 0xF0; break; + case 0x06: command = CMD_VIBRATOVOL; if (param & 0xF0) param &= 0xF0; break; + case 0x07: command = CMD_TREMOLO; break; + case 0x08: command = CMD_PANNING8; break; + case 0x09: command = CMD_OFFSET; break; + case 0x0A: command = CMD_VOLUMESLIDE; if (param & 0xF0) param &= 0xF0; break; + case 0x0B: command = CMD_POSITIONJUMP; break; + case 0x0C: command = CMD_VOLUME; break; + case 0x0D: command = CMD_PATTERNBREAK; param = ((param >> 4) * 10) + (param & 0x0F); break; + case 0x0E: command = CMD_MODCMDEX; break; + case 0x0F: command = (param <= (UINT)((m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? 0x1F : 0x20)) ? CMD_SPEED : CMD_TEMPO; + if ((param == 0xFF) && (m_nSamples == 15)) command = 0; break; + // Extension for XM extended effects + case 'G' - 55: command = CMD_GLOBALVOLUME; break; + case 'H' - 55: command = CMD_GLOBALVOLSLIDE; if (param & 0xF0) param &= 0xF0; break; + case 'K' - 55: command = CMD_KEYOFF; break; + case 'L' - 55: command = CMD_SETENVPOSITION; break; + case 'M' - 55: command = CMD_CHANNELVOLUME; break; + case 'N' - 55: command = CMD_CHANNELVOLSLIDE; break; + case 'P' - 55: command = CMD_PANNINGSLIDE; if (param & 0xF0) param &= 0xF0; break; + case 'R' - 55: command = CMD_RETRIG; break; + case 'T' - 55: command = CMD_TREMOR; break; + case 'X' - 55: command = CMD_XFINEPORTAUPDOWN; break; + case 'Y' - 55: command = CMD_PANBRELLO; break; + case 'Z' - 55: command = CMD_MIDI; break; + default: command = 0; + } + m->command = command; + m->param = param; +} + + +WORD CSoundFile::ModSaveCommand(const MODCOMMAND *m, BOOL bXM) const +//------------------------------------------------------------------ +{ + UINT command = m->command & 0x3F, param = m->param; + + switch(command) + { + case 0: command = param = 0; break; + case CMD_ARPEGGIO: command = 0; break; + case CMD_PORTAMENTOUP: + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) + { + if ((param & 0xF0) == 0xE0) { command=0x0E; param=((param & 0x0F) >> 2)|0x10; break; } + else if ((param & 0xF0) == 0xF0) { command=0x0E; param &= 0x0F; param|=0x10; break; } + } + command = 0x01; + break; + case CMD_PORTAMENTODOWN: + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) + { + if ((param & 0xF0) == 0xE0) { command=0x0E; param=((param & 0x0F) >> 2)|0x20; break; } + else if ((param & 0xF0) == 0xF0) { command=0x0E; param &= 0x0F; param|=0x20; break; } + } + command = 0x02; + break; + case CMD_TONEPORTAMENTO: command = 0x03; break; + case CMD_VIBRATO: command = 0x04; break; + case CMD_TONEPORTAVOL: command = 0x05; break; + case CMD_VIBRATOVOL: command = 0x06; break; + case CMD_TREMOLO: command = 0x07; break; + case CMD_PANNING8: + command = 0x08; + if (bXM) + { + if ((m_nType != MOD_TYPE_IT) && (m_nType != MOD_TYPE_XM) && (param <= 0x80)) + { + param <<= 1; + if (param > 255) param = 255; + } + } else + { + if ((m_nType == MOD_TYPE_IT) || (m_nType == MOD_TYPE_XM)) param >>= 1; + } + break; + case CMD_OFFSET: command = 0x09; break; + case CMD_VOLUMESLIDE: command = 0x0A; break; + case CMD_POSITIONJUMP: command = 0x0B; break; + case CMD_VOLUME: command = 0x0C; break; + case CMD_PATTERNBREAK: command = 0x0D; param = ((param / 10) << 4) | (param % 10); break; + case CMD_MODCMDEX: command = 0x0E; break; + case CMD_SPEED: command = 0x0F; if (param > 0x20) param = 0x20; break; + case CMD_TEMPO: if (param > 0x20) { command = 0x0F; break; } + case CMD_GLOBALVOLUME: command = 'G' - 55; break; + case CMD_GLOBALVOLSLIDE: command = 'H' - 55; break; + case CMD_KEYOFF: command = 'K' - 55; break; + case CMD_SETENVPOSITION: command = 'L' - 55; break; + case CMD_CHANNELVOLUME: command = 'M' - 55; break; + case CMD_CHANNELVOLSLIDE: command = 'N' - 55; break; + case CMD_PANNINGSLIDE: command = 'P' - 55; break; + case CMD_RETRIG: command = 'R' - 55; break; + case CMD_TREMOR: command = 'T' - 55; break; + case CMD_XFINEPORTAUPDOWN: command = 'X' - 55; break; + case CMD_PANBRELLO: command = 'Y' - 55; break; + case CMD_MIDI: command = 'Z' - 55; break; + case CMD_S3MCMDEX: + switch(param & 0xF0) + { + case 0x10: command = 0x0E; param = (param & 0x0F) | 0x30; break; + case 0x20: command = 0x0E; param = (param & 0x0F) | 0x50; break; + case 0x30: command = 0x0E; param = (param & 0x0F) | 0x40; break; + case 0x40: command = 0x0E; param = (param & 0x0F) | 0x70; break; + case 0x90: command = 'X' - 55; break; + case 0xB0: command = 0x0E; param = (param & 0x0F) | 0x60; break; + case 0xA0: + case 0x50: + case 0x70: + case 0x60: command = param = 0; break; + default: command = 0x0E; break; + } + break; + default: command = param = 0; + } + return (WORD)((command << 8) | (param)); +} + + +#pragma pack(1) + +typedef struct _MODSAMPLE +{ + CHAR name[22]; + WORD length; + BYTE finetune; + BYTE volume; + WORD loopstart; + WORD looplen; +} MODSAMPLE, *PMODSAMPLE; + +typedef struct _MODMAGIC +{ + BYTE nOrders; + BYTE nRestartPos; + BYTE Orders[128]; + char Magic[4]; // changed from CHAR +} MODMAGIC, *PMODMAGIC; + +#pragma pack() + +BOOL IsMagic(LPCSTR s1, LPCSTR s2) +{ + return ((*(DWORD *)s1) == (*(DWORD *)s2)) ? TRUE : FALSE; +} + + +BOOL CSoundFile::ReadMod(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + char s[1024]; // changed from CHAR + DWORD dwMemPos, dwTotalSampleLen; + PMODMAGIC pMagic; + UINT nErr; + + if ((!lpStream) || (dwMemLength < 0x600)) return FALSE; + dwMemPos = 20; + m_nSamples = 31; + m_nChannels = 4; + pMagic = (PMODMAGIC)(lpStream+dwMemPos+sizeof(MODSAMPLE)*31); + // Check Mod Magic + memcpy(s, pMagic->Magic, 4); + if ((IsMagic(s, "M.K.")) || (IsMagic(s, "M!K!")) + || (IsMagic(s, "M&K!")) || (IsMagic(s, "N.T."))) m_nChannels = 4; else + if ((IsMagic(s, "CD81")) || (IsMagic(s, "OKTA"))) m_nChannels = 8; else + if ((s[0]=='F') && (s[1]=='L') && (s[2]=='T') && (s[3]>='4') && (s[3]<='9')) m_nChannels = s[3] - '0'; else + if ((s[0]>='4') && (s[0]<='9') && (s[1]=='C') && (s[2]=='H') && (s[3]=='N')) m_nChannels = s[0] - '0'; else + if ((s[0]=='1') && (s[1]>='0') && (s[1]<='9') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 10; else + if ((s[0]=='2') && (s[1]>='0') && (s[1]<='9') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 20; else + if ((s[0]=='3') && (s[1]>='0') && (s[1]<='2') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 30; else + if ((s[0]=='T') && (s[1]=='D') && (s[2]=='Z') && (s[3]>='4') && (s[3]<='9')) m_nChannels = s[3] - '0'; else + if (IsMagic(s,"16CN")) m_nChannels = 16; else + if (IsMagic(s,"32CN")) m_nChannels = 32; else m_nSamples = 15; + // Load Samples + nErr = 0; + dwTotalSampleLen = 0; + for (UINT i=1; i<=m_nSamples; i++) + { + PMODSAMPLE pms = (PMODSAMPLE)(lpStream+dwMemPos); + MODINSTRUMENT *psmp = &Ins[i]; + UINT loopstart, looplen; + + memcpy(m_szNames[i], pms->name, 22); + m_szNames[i][22] = 0; + psmp->uFlags = 0; + psmp->nLength = bswapBE16(pms->length)*2; + dwTotalSampleLen += psmp->nLength; + psmp->nFineTune = MOD2XMFineTune(pms->finetune & 0x0F); + psmp->nVolume = 4*pms->volume; + if (psmp->nVolume > 256) { psmp->nVolume = 256; nErr++; } + psmp->nGlobalVol = 64; + psmp->nPan = 128; + loopstart = bswapBE16(pms->loopstart)*2; + looplen = bswapBE16(pms->looplen)*2; + // Fix loops + if ((looplen > 2) && (loopstart+looplen > psmp->nLength) + && (loopstart/2+looplen <= psmp->nLength)) + { + loopstart /= 2; + } + psmp->nLoopStart = loopstart; + psmp->nLoopEnd = loopstart + looplen; + if (psmp->nLength < 2) psmp->nLength = 0; + if (psmp->nLength) + { + UINT derr = 0; + if (psmp->nLoopStart >= psmp->nLength) { psmp->nLoopStart = psmp->nLength-1; derr|=1; } + if (psmp->nLoopEnd > psmp->nLength) { psmp->nLoopEnd = psmp->nLength; derr |= 1; } + if (psmp->nLoopStart > psmp->nLoopEnd) derr |= 1; + if ((psmp->nLoopStart > psmp->nLoopEnd) || (psmp->nLoopEnd <= 8) + || (psmp->nLoopEnd - psmp->nLoopStart <= 4)) + { + psmp->nLoopStart = 0; + psmp->nLoopEnd = 0; + } + if (psmp->nLoopEnd > psmp->nLoopStart) + { + psmp->uFlags |= CHN_LOOP; + } + } + dwMemPos += sizeof(MODSAMPLE); + } + if ((m_nSamples == 15) && (dwTotalSampleLen > dwMemLength * 4)) return FALSE; + pMagic = (PMODMAGIC)(lpStream+dwMemPos); + dwMemPos += sizeof(MODMAGIC); + if (m_nSamples == 15) dwMemPos -= 4; + memset(Order, 0,sizeof(Order)); + memcpy(Order, pMagic->Orders, 128); + + UINT nbp, nbpbuggy, nbpbuggy2, norders; + + norders = pMagic->nOrders; + if ((!norders) || (norders > 0x80)) + { + norders = 0x80; + while ((norders > 1) && (!Order[norders-1])) norders--; + } + nbpbuggy = 0; + nbpbuggy2 = 0; + nbp = 0; + for (UINT iord=0; iord<128; iord++) + { + UINT i = Order[iord]; + if ((i < 0x80) && (nbp <= i)) + { + nbp = i+1; + if (iord= nbpbuggy2) nbpbuggy2 = i+1; + } + for (UINT iend=norders; iendnRestartPos; + if (m_nRestartPos >= 0x78) m_nRestartPos = 0; + if (m_nRestartPos + 1 >= (UINT)norders) m_nRestartPos = 0; + if (!nbp) return FALSE; + DWORD dwWowTest = dwTotalSampleLen+dwMemPos; + if ((IsMagic(pMagic->Magic, "M.K.")) && (dwWowTest + nbp*8*256 == dwMemLength)) m_nChannels = 8; + if ((nbp != nbpbuggy) && (dwWowTest + nbp*m_nChannels*256 != dwMemLength)) + { + if (dwWowTest + nbpbuggy*m_nChannels*256 == dwMemLength) nbp = nbpbuggy; + else nErr += 8; + } else + if ((nbpbuggy2 > nbp) && (dwWowTest + nbpbuggy2*m_nChannels*256 == dwMemLength)) + { + nbp = nbpbuggy2; + } + if ((dwWowTest < 0x600) || (dwWowTest > dwMemLength)) nErr += 8; + if ((m_nSamples == 15) && (nErr >= 16)) return FALSE; + // Default settings + m_nType = MOD_TYPE_MOD; + m_nDefaultSpeed = 6; + m_nDefaultTempo = 125; + m_nMinPeriod = 14 << 2; + m_nMaxPeriod = 3424 << 2; + memcpy(m_szNames, lpStream, 20); + // Setting channels pan + for (UINT ich=0; ich= dwMemLength) break; + MODCOMMAND *m = Patterns[ipat]; + LPCBYTE p = lpStream + dwMemPos; + for (UINT j=m_nChannels*64; j; m++,p+=4,j--) + { + BYTE A0=p[0], A1=p[1], A2=p[2], A3=p[3]; + UINT n = ((((UINT)A0 & 0x0F) << 8) | (A1)); + if ((n) && (n != 0xFFF)) m->note = GetNoteFromPeriod(n << 2); + m->instr = ((UINT)A2 >> 4) | (A0 & 0x10); + m->command = A2 & 0x0F; + m->param = A3; + if ((m->command) || (m->param)) ConvertModCommand(m); + } + } + dwMemPos += m_nChannels*256; + } + // Reading instruments + DWORD dwErrCheck = 0; + for (UINT ismp=1; ismp<=m_nSamples; ismp++) if (Ins[ismp].nLength) + { + LPSTR p = (LPSTR)(lpStream+dwMemPos); + UINT flags = 0; + if (dwMemPos + 5 >= dwMemLength) break; + if (!strnicmp(p, "ADPCM", 5)) + { + flags = 3; + p += 5; + dwMemPos += 5; + } + DWORD dwSize = ReadSample(&Ins[ismp], flags, p, dwMemLength - dwMemPos); + if (dwSize) + { + dwMemPos += dwSize; + dwErrCheck++; + } + } +#ifdef MODPLUG_TRACKER + return TRUE; +#else + return (dwErrCheck) ? TRUE : FALSE; +#endif +} + + +#ifndef MODPLUG_NO_FILESAVE +#pragma warning(disable:4100) + +BOOL CSoundFile::SaveMod(diskwriter_driver_t *fp, UINT nPacking) +//---------------------------------------------------------- +{ + BYTE insmap[32]; + UINT inslen[32]; + BYTE bTab[32]; + BYTE ord[128]; + + if ((!m_nChannels) || (!fp)) return FALSE; + memset(ord, 0, sizeof(ord)); + memset(inslen, 0, sizeof(inslen)); + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + memset(insmap, 0, sizeof(insmap)); + for (UINT i=1; i<32; i++) if (Headers[i]) + { + for (UINT j=0; j<128; j++) if (Headers[i]->Keyboard[j]) + { + insmap[i] = Headers[i]->Keyboard[j]; + break; + } + } + } else + { + for (UINT i=0; i<32; i++) insmap[i] = (BYTE)i; + } + // Writing song name + fp->o(fp, (const unsigned char *)m_szNames, 20); + // Writing instrument definition + for (UINT iins=1; iins<=31; iins++) + { + MODINSTRUMENT *pins = &Ins[insmap[iins]]; + memcpy(bTab, m_szNames[iins],22); + inslen[iins] = pins->nLength; + if (inslen[iins] > 0x1fff0) inslen[iins] = 0x1fff0; + bTab[22] = inslen[iins] >> 9; + bTab[23] = inslen[iins] >> 1; + if (pins->RelativeTone < 0) bTab[24] = 0x08; else + if (pins->RelativeTone > 0) bTab[24] = 0x07; else + bTab[24] = (BYTE)XM2MODFineTune(pins->nFineTune); + bTab[25] = pins->nVolume >> 2; + bTab[26] = pins->nLoopStart >> 9; + bTab[27] = pins->nLoopStart >> 1; + bTab[28] = (pins->nLoopEnd - pins->nLoopStart) >> 9; + bTab[29] = (pins->nLoopEnd - pins->nLoopStart) >> 1; + fp->o(fp,(const unsigned char *) bTab, 30); + } + // Writing number of patterns + UINT nbp=0, norders=128; + for (UINT iord=0; iord<128; iord++) + { + if (Order[iord] == 0xFF) + { + norders = iord; + break; + } + if ((Order[iord] < 0x80) && (nbp<=Order[iord])) nbp = Order[iord]+1; + } + bTab[0] = norders; + bTab[1] = m_nRestartPos; + fp->o(fp, (const unsigned char *)bTab, 2); + // Writing pattern list + if (norders) memcpy(ord, Order, norders); + fp->o(fp, (const unsigned char *)ord, 128); + // Writing signature + if (m_nChannels == 4) + lstrcpy((LPSTR)&bTab, "M.K."); + else + wsprintf((LPSTR)&bTab, "%luCHN", m_nChannels); + fp->o(fp, (const unsigned char *)bTab, 4); + // Writing patterns + for (UINT ipat=0; ipat> 8; + param &= 0xFF; + if (command > 0x0F) command = param = 0; + if ((m->vol >= 0x10) && (m->vol <= 0x50) && (!command) && (!param)) { command = 0x0C; param = m->vol - 0x10; } + UINT period = m->note; + if (period) + { + if (period < 37) period = 37; + period -= 37; + if (period >= 6*12) period = 6*12-1; + period = ProTrackerPeriodTable[period]; + } + UINT instr = (m->instr > 31) ? 0 : m->instr; + p[0] = ((period >> 8) & 0x0F) | (instr & 0x10); + p[1] = period & 0xFF; + p[2] = ((instr & 0x0F) << 4) | (command & 0x0F); + p[3] = param; + } + fp->o(fp, (const unsigned char *)s, m_nChannels*4); + } else + { + memset(s, 0, m_nChannels*4); + fp->o(fp, (const unsigned char *)s, m_nChannels*4); + } + } + // Writing instruments + for (UINT ismpd=1; ismpd<=31; ismpd++) if (inslen[ismpd]) + { + MODINSTRUMENT *pins = &Ins[insmap[ismpd]]; + UINT flags = RS_PCM8S; +#ifndef NO_PACKING + if (!(pins->uFlags & (CHN_16BIT|CHN_STEREO))) + { + if ((nPacking) && (CanPackSample((char *)pins->pSample, inslen[ismpd], nPacking))) + { + fp->o(fp, (const unsigned char *)"ADPCM", 5); + flags = RS_ADPCM4; + } + } +#endif + WriteSample(fp, pins, flags, inslen[ismpd]); + } + return TRUE; +} + +#pragma warning(default:4100) +#endif // MODPLUG_NO_FILESAVE diff --git a/modplug/load_mt2.cpp b/modplug/load_mt2.cpp new file mode 100644 index 000000000..cbc76dc3b --- /dev/null +++ b/modplug/load_mt2.cpp @@ -0,0 +1,637 @@ +#include "stdafx.h" +#include "sndfile.h" + +//#define MT2DEBUG + +#pragma pack(1) + +typedef struct _MT2FILEHEADER +{ + DWORD dwMT20; // 0x3032544D "MT20" + DWORD dwSpecial; + WORD wVersion; + CHAR szTrackerName[32]; // "MadTracker 2.0" + CHAR szSongName[64]; + WORD nOrders; + WORD wRestart; + WORD wPatterns; + WORD wChannels; + WORD wSamplesPerTick; + BYTE bTicksPerLine; + BYTE bLinesPerBeat; + DWORD fulFlags; // b0=packed patterns + WORD wInstruments; + WORD wSamples; + BYTE Orders[256]; +} MT2FILEHEADER; + +typedef struct _MT2PATTERN +{ + WORD wLines; + DWORD wDataLen; +} MT2PATTERN; + +typedef struct _MT2COMMAND +{ + BYTE note; // 0=nothing, 97=note off + BYTE instr; + BYTE vol; + BYTE pan; + BYTE fxcmd; + BYTE fxparam1; + BYTE fxparam2; +} MT2COMMAND; + +typedef struct _MT2DRUMSDATA +{ + WORD wDrumPatterns; + WORD wDrumSamples[8]; + BYTE DrumPatternOrder[256]; +} MT2DRUMSDATA; + +typedef struct _MT2AUTOMATION +{ + DWORD dwFlags; + DWORD dwEffectId; + DWORD nEnvPoints; +} MT2AUTOMATION; + +typedef struct _MT2INSTRUMENT +{ + CHAR szName[32]; + DWORD dwDataLen; + WORD wSamples; + BYTE GroupsMapping[96]; + BYTE bVibType; + BYTE bVibSweep; + BYTE bVibDepth; + BYTE bVibRate; + WORD wFadeOut; + WORD wNNA; + WORD wInstrFlags; + WORD wEnvFlags1; + WORD wEnvFlags2; +} MT2INSTRUMENT; + +typedef struct _MT2ENVELOPE +{ + BYTE nFlags; + BYTE nPoints; + BYTE nSustainPos; + BYTE nLoopStart; + BYTE nLoopEnd; + BYTE bReserved[3]; + BYTE EnvData[64]; +} MT2ENVELOPE; + +typedef struct _MT2SYNTH +{ + BYTE nSynthId; + BYTE nFxId; + WORD wCutOff; + BYTE nResonance; + BYTE nAttack; + BYTE nDecay; + BYTE bReserved[25]; +} MT2SYNTH; + +typedef struct _MT2SAMPLE +{ + CHAR szName[32]; + DWORD dwDataLen; + DWORD dwLength; + DWORD dwFrequency; + BYTE nQuality; + BYTE nChannels; + BYTE nFlags; + BYTE nLoop; + DWORD dwLoopStart; + DWORD dwLoopEnd; + WORD wVolume; + BYTE nPan; + BYTE nBaseNote; + WORD wSamplesPerBeat; +} MT2SAMPLE; + +typedef struct _MT2GROUP +{ + BYTE nSmpNo; + BYTE nVolume; // 0-128 + BYTE nFinePitch; + BYTE Reserved[5]; +} MT2GROUP; + +#pragma pack() + + +static VOID ConvertMT2Command(CSoundFile *that, MODCOMMAND *m, MT2COMMAND *p) +//--------------------------------------------------------------------------- +{ + // Note + m->note = 0; + if (p->note) m->note = (p->note > 96) ? 0xFF : p->note+12; + // Instrument + m->instr = p->instr; + // Volume Column + if ((p->vol >= 0x10) && (p->vol <= 0x90)) + { + m->volcmd = VOLCMD_VOLUME; + m->vol = (p->vol - 0x10) >> 1; + } else + if ((p->vol >= 0xA0) && (p->vol <= 0xAF)) + { + m->volcmd = VOLCMD_VOLSLIDEDOWN; + m->vol = (p->vol & 0x0f); + } else + if ((p->vol >= 0xB0) && (p->vol <= 0xBF)) + { + m->volcmd = VOLCMD_VOLSLIDEUP; + m->vol = (p->vol & 0x0f); + } else + if ((p->vol >= 0xC0) && (p->vol <= 0xCF)) + { + m->volcmd = VOLCMD_FINEVOLDOWN; + m->vol = (p->vol & 0x0f); + } else + if ((p->vol >= 0xD0) && (p->vol <= 0xDF)) + { + m->volcmd = VOLCMD_FINEVOLUP; + m->vol = (p->vol & 0x0f); + } else + { + m->volcmd = 0; + m->vol = 0; + } + // Effects + m->command = 0; + m->param = 0; + if ((p->fxcmd) || (p->fxparam1) || (p->fxparam2)) + { + if (!p->fxcmd) + { + m->command = p->fxparam2; + m->param = p->fxparam1; + that->ConvertModCommand(m); + } else + { + // TODO: MT2 Effects + } + } +} + + +BOOL CSoundFile::ReadMT2(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + MT2FILEHEADER *pfh = (MT2FILEHEADER *)lpStream; + DWORD dwMemPos, dwDrumDataPos, dwExtraDataPos; + UINT nDrumDataLen, nExtraDataLen; + MT2DRUMSDATA *pdd; + MT2INSTRUMENT *InstrMap[255]; + MT2SAMPLE *SampleMap[256]; + + if ((!lpStream) || (dwMemLength < sizeof(MT2FILEHEADER)) + || (pfh->dwMT20 != 0x3032544D) + || (pfh->wVersion < 0x0200) || (pfh->wVersion >= 0x0300) + || (pfh->wChannels < 4) || (pfh->wChannels > 64)) return FALSE; + pdd = NULL; + m_nType = MOD_TYPE_MT2; + m_nChannels = pfh->wChannels; + m_nRestartPos = pfh->wRestart; + m_nDefaultSpeed = pfh->bTicksPerLine; + m_nDefaultTempo = 125; + if ((pfh->wSamplesPerTick > 100) && (pfh->wSamplesPerTick < 5000)) + { + m_nDefaultTempo = 110250 / pfh->wSamplesPerTick; + } + for (UINT iOrd=0; iOrdnOrders) ? pfh->Orders[iOrd] : 0xFF); + } + memcpy(m_szNames[0], pfh->szSongName, 32); + m_szNames[0][31] = 0; + dwMemPos = sizeof(MT2FILEHEADER); + nDrumDataLen = *(WORD *)(lpStream + dwMemPos); + dwDrumDataPos = dwMemPos + 2; + if (nDrumDataLen >= 2) pdd = (MT2DRUMSDATA *)(lpStream+dwDrumDataPos); + dwMemPos += 2 + nDrumDataLen; +#ifdef MT2DEBUG + + Log("MT2 v%03X: \"%s\" (flags=%04X)\n", pfh->wVersion, m_szNames[0], pfh->fulFlags); + Log("%d Channels, %d Patterns, %d Instruments, %d Samples\n", pfh->wChannels, pfh->wPatterns, pfh->wInstruments, pfh->wSamples); + Log("Drum Data: %d bytes @%04X\n", nDrumDataLen, dwDrumDataPos); +#endif + if (dwMemPos >= dwMemLength-12) return TRUE; + if (!*(DWORD *)(lpStream+dwMemPos)) dwMemPos += 4; + if (!*(DWORD *)(lpStream+dwMemPos)) dwMemPos += 4; + nExtraDataLen = *(DWORD *)(lpStream+dwMemPos); + dwExtraDataPos = dwMemPos + 4; + dwMemPos += 4; +#ifdef MT2DEBUG + Log("Extra Data: %d bytes @%04X\n", nExtraDataLen, dwExtraDataPos); +#endif + if (dwMemPos + nExtraDataLen >= dwMemLength) return TRUE; + while (dwMemPos+8 < dwExtraDataPos + nExtraDataLen) + { + DWORD dwId = *(DWORD *)(lpStream+dwMemPos); + DWORD dwLen = *(DWORD *)(lpStream+dwMemPos+4); + dwMemPos += 8; + if (dwMemPos + dwLen > dwMemLength) return TRUE; +#ifdef MT2DEBUG + CHAR s[5]; + memcpy(s, &dwId, 4); + s[4] = 0; + Log("pos=0x%04X: %s: %d bytes\n", dwMemPos-8, s, dwLen); +#endif + switch(dwId) + { + // MSG + case 0x0047534D: + if ((dwLen > 3) && (!m_lpszSongComments)) + { + DWORD nTxtLen = dwLen; + if (nTxtLen > 32000) nTxtLen = 32000; + m_lpszSongComments = new char[nTxtLen]; // changed from CHAR + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos+1, nTxtLen-1); + m_lpszSongComments[nTxtLen-1] = 0; + } + } + break; + // SUM -> author name (or "Unregistered") + // TMAP + // TRKS + case 0x534b5254: + break; + } + dwMemPos += dwLen; + } + // Load Patterns + dwMemPos = dwExtraDataPos + nExtraDataLen; + for (UINT iPat=0; iPatwPatterns; iPat++) if (dwMemPos < dwMemLength-6) + { + MT2PATTERN *pmp = (MT2PATTERN *)(lpStream+dwMemPos); + UINT wDataLen = (pmp->wDataLen + 1) & ~1; + dwMemPos += 6; + if (dwMemPos + wDataLen > dwMemLength) break; + UINT nLines = pmp->wLines; + if ((iPat < MAX_PATTERNS) && (nLines > 0) && (nLines <= 256)) + { + #ifdef MT2DEBUG + Log("Pattern #%d @%04X: %d lines, %d bytes\n", iPat, dwMemPos-6, nLines, pmp->wDataLen); + #endif + PatternSize[iPat] = nLines; + PatternAllocSize[iPat] = nLines; + Patterns[iPat] = AllocatePattern(nLines, m_nChannels); + if (!Patterns[iPat]) return TRUE; + MODCOMMAND *m = Patterns[iPat]; + UINT len = wDataLen; + if (pfh->fulFlags & 1) // Packed Patterns + { + BYTE *p = (BYTE *)(lpStream+dwMemPos); + UINT pos = 0, row=0, ch=0; + while (pos < len) + { + MT2COMMAND cmd; + UINT infobyte = p[pos++]; + UINT rptcount = 0; + if (infobyte == 0xff) + { + rptcount = p[pos++]; + infobyte = p[pos++]; + #if 0 + Log("(%d.%d) FF(%02X).%02X\n", row, ch, rptcount, infobyte); + } else + { + Log("(%d.%d) %02X\n", row, ch, infobyte); + #endif + } + if (infobyte & 0x7f) + { + UINT patpos = row*m_nChannels+ch; + cmd.note = cmd.instr = cmd.vol = cmd.pan = cmd.fxcmd = cmd.fxparam1 = cmd.fxparam2 = 0; + if (infobyte & 1) cmd.note = p[pos++]; + if (infobyte & 2) cmd.instr = p[pos++]; + if (infobyte & 4) cmd.vol = p[pos++]; + if (infobyte & 8) cmd.pan = p[pos++]; + if (infobyte & 16) cmd.fxcmd = p[pos++]; + if (infobyte & 32) cmd.fxparam1 = p[pos++]; + if (infobyte & 64) cmd.fxparam2 = p[pos++]; + #ifdef MT2DEBUG + if (cmd.fxcmd) + { + Log("(%d.%d) MT2 FX=%02X.%02X.%02X\n", row, ch, cmd.fxcmd, cmd.fxparam1, cmd.fxparam2); + } + #endif + ConvertMT2Command(this, &m[patpos], &cmd); + } + row += rptcount+1; + while (row >= nLines) { row-=nLines; ch++; } + if (ch >= m_nChannels) break; + } + } else + { + MT2COMMAND *p = (MT2COMMAND *)(lpStream+dwMemPos); + UINT n = 0; + while ((len > sizeof(MT2COMMAND)) && (n < m_nChannels*nLines)) + { + ConvertMT2Command(this, m, p); + len -= sizeof(MT2COMMAND); + n++; + p++; + m++; + } + } + } + dwMemPos += wDataLen; + } + // Skip Drum Patterns + if (pdd) + { + #ifdef MT2DEBUG + Log("%d Drum Patterns at offset 0x%08X\n", pdd->wDrumPatterns, dwMemPos); + #endif + for (UINT iDrm=0; iDrmwDrumPatterns; iDrm++) + { + if (dwMemPos > dwMemLength-2) return TRUE; + UINT nLines = *(WORD *)(lpStream+dwMemPos); + #ifdef MT2DEBUG + if (nLines != 64) Log("Drum Pattern %d: %d Lines @%04X\n", iDrm, nLines, dwMemPos); + #endif + dwMemPos += 2 + nLines * 32; + } + } + // Automation + if (pfh->fulFlags & 2) + { + #ifdef MT2DEBUG + Log("Automation at offset 0x%08X\n", dwMemPos); + #endif + UINT nAutoCount = m_nChannels; + if (pfh->fulFlags & 0x10) nAutoCount++; // Master Automation + if ((pfh->fulFlags & 0x08) && (pdd)) nAutoCount += 8; // Drums Automation + nAutoCount *= pfh->wPatterns; + for (UINT iAuto=0; iAuto= dwMemLength) return TRUE; + MT2AUTOMATION *pma = (MT2AUTOMATION *)(lpStream+dwMemPos); + dwMemPos += (pfh->wVersion <= 0x201) ? 4 : 8; + for (UINT iEnv=0; iEnv<14; iEnv++) + { + if (pma->dwFlags & (1 << iEnv)) + { + #ifdef MT2DEBUG + UINT nPoints = *(DWORD *)(lpStream+dwMemPos); + Log(" Env[%d/%d] %04X @%04X: %d points\n", iAuto, nAutoCount, 1 << iEnv, dwMemPos-8, nPoints); + #endif + dwMemPos += 260; + } + } + } + } + // Load Instruments +#ifdef MT2DEBUG + Log("Loading instruments at offset 0x%08X\n", dwMemPos); +#endif + memset(InstrMap, 0, sizeof(InstrMap)); + m_nInstruments = (pfh->wInstruments < MAX_INSTRUMENTS) ? pfh->wInstruments : MAX_INSTRUMENTS-1; + m_dwSongFlags |= SONG_INSTRUMENTMODE; + for (UINT iIns=1; iIns<=255; iIns++) + { + if (dwMemPos+36 > dwMemLength) return TRUE; + MT2INSTRUMENT *pmi = (MT2INSTRUMENT *)(lpStream+dwMemPos); + INSTRUMENTHEADER *penv = NULL; + if (iIns <= m_nInstruments) + { + penv = new INSTRUMENTHEADER; + Headers[iIns] = penv; + if (penv) + { + memset(penv, 0, sizeof(INSTRUMENTHEADER)); + memcpy(penv->name, pmi->szName, 32); + penv->nGlobalVol = 64; + penv->nPan = 128; + for (UINT i=0; i<120; i++) + { + penv->NoteMap[i] = i+1; + } + } + } + #ifdef MT2DEBUG + if (iIns <= pfh->wInstruments) Log(" Instrument #%d at offset %04X: %d bytes\n", iIns, dwMemPos, pmi->dwDataLen); + #endif + if (((LONG)pmi->dwDataLen > 0) && (dwMemPos + pmi->dwDataLen + 40 <= dwMemLength)) + { + InstrMap[iIns-1] = pmi; + if (penv) + { + penv->nFadeOut = pmi->wFadeOut; + penv->nNNA = pmi->wNNA & 3; + penv->nDCT = (pmi->wNNA>>8) & 3; + penv->nDNA = (pmi->wNNA>>12) & 3; + MT2ENVELOPE *pehdr[4]; + WORD *pedata[4]; + if (pfh->wVersion <= 0x201) + { + DWORD dwEnvPos = dwMemPos + sizeof(MT2INSTRUMENT) - 4; + pehdr[0] = (MT2ENVELOPE *)(lpStream+dwEnvPos); + pehdr[1] = (MT2ENVELOPE *)(lpStream+dwEnvPos+8); + pehdr[2] = pehdr[3] = NULL; + pedata[0] = (WORD *)(lpStream+dwEnvPos+16); + pedata[1] = (WORD *)(lpStream+dwEnvPos+16+64); + pedata[2] = pedata[3] = NULL; + } else + { + DWORD dwEnvPos = dwMemPos + sizeof(MT2INSTRUMENT); + for (UINT i=0; i<4; i++) + { + if (pmi->wEnvFlags1 & (1<EnvData; + dwEnvPos += sizeof(MT2ENVELOPE); + } else + { + pehdr[i] = NULL; + pedata[i] = NULL; + } + } + } + // Load envelopes + for (UINT iEnv=0; iEnv<4; iEnv++) if (pehdr[iEnv]) + { + MT2ENVELOPE *pme = pehdr[iEnv]; + WORD *pEnvPoints = NULL; + BYTE *pEnvData = NULL; + #ifdef MT2DEBUG + Log(" Env %d.%d @%04X: %d points\n", iIns, iEnv, (UINT)(((BYTE *)pme)-lpStream), pme->nPoints); + #endif + switch(iEnv) + { + // Volume Envelope + case 0: + if (pme->nFlags & 1) penv->dwFlags |= ENV_VOLUME; + if (pme->nFlags & 2) penv->dwFlags |= ENV_VOLSUSTAIN; + if (pme->nFlags & 4) penv->dwFlags |= ENV_VOLLOOP; + penv->VolEnv.nNodes = (pme->nPoints > 16) ? 16 : pme->nPoints; + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = pme->nSustainPos; + penv->VolEnv.nLoopStart = pme->nLoopStart; + penv->VolEnv.nLoopEnd = pme->nLoopEnd; + pEnvPoints = penv->VolEnv.Ticks; + pEnvData = penv->VolEnv.Values; + break; + + // Panning Envelope + case 1: + if (pme->nFlags & 1) penv->dwFlags |= ENV_PANNING; + if (pme->nFlags & 2) penv->dwFlags |= ENV_PANSUSTAIN; + if (pme->nFlags & 4) penv->dwFlags |= ENV_PANLOOP; + penv->PanEnv.nNodes = (pme->nPoints > 16) ? 16 : pme->nPoints; + penv->PanEnv.nSustainStart = penv->PanEnv.nSustainEnd = pme->nSustainPos; + penv->PanEnv.nLoopStart = pme->nLoopStart; + penv->PanEnv.nLoopEnd = pme->nLoopEnd; + pEnvPoints = penv->PanEnv.Ticks; + pEnvData = penv->PanEnv.Values; + break; + + // Pitch/Filter envelope + default: + if (pme->nFlags & 1) penv->dwFlags |= (iEnv==3) ? (ENV_PITCH|ENV_FILTER) : ENV_PITCH; + if (pme->nFlags & 2) penv->dwFlags |= ENV_PITCHSUSTAIN; + if (pme->nFlags & 4) penv->dwFlags |= ENV_PITCHLOOP; + penv->PitchEnv.nNodes = (pme->nPoints > 16) ? 16 : pme->nPoints; + penv->PitchEnv.nSustainStart = penv->PitchEnv.nSustainEnd = pme->nSustainPos; + penv->PitchEnv.nLoopStart = pme->nLoopStart; + penv->PitchEnv.nLoopEnd = pme->nLoopEnd; + pEnvPoints = penv->PitchEnv.Ticks; + pEnvData = penv->PitchEnv.Values; + } + // Envelope data + if ((pEnvPoints) && (pEnvData) && (pedata[iEnv])) + { + WORD *psrc = pedata[iEnv]; + for (UINT i=0; i<16; i++) + { + pEnvPoints[i] = psrc[i*2]; + pEnvData[i] = (BYTE)psrc[i*2+1]; + } + } + } + } + dwMemPos += pmi->dwDataLen + 36; + if (pfh->wVersion > 0x201) dwMemPos += 4; // ? + } else + { + dwMemPos += 36; + } + } +#ifdef MT2DEBUG + Log("Loading samples at offset 0x%08X\n", dwMemPos); +#endif + memset(SampleMap, 0, sizeof(SampleMap)); + m_nSamples = (pfh->wSamples < MAX_SAMPLES) ? pfh->wSamples : MAX_SAMPLES-1; + for (UINT iSmp=1; iSmp<=256; iSmp++) + { + if (dwMemPos+36 > dwMemLength) return TRUE; + MT2SAMPLE *pms = (MT2SAMPLE *)(lpStream+dwMemPos); + #ifdef MT2DEBUG + if (iSmp <= m_nSamples) Log(" Sample #%d at offset %04X: %d bytes\n", iSmp, dwMemPos, pms->dwDataLen); + #endif + if (iSmp < MAX_SAMPLES) + { + memcpy(m_szNames[iSmp], pms->szName, 32); + } + if (pms->dwDataLen > 0) + { + SampleMap[iSmp-1] = pms; + if (iSmp < MAX_SAMPLES) + { + MODINSTRUMENT *psmp = &Ins[iSmp]; + psmp->nGlobalVol = 64; + psmp->nVolume = (pms->wVolume >> 7); + psmp->nPan = (pms->nPan == 0x80) ? 128 : (pms->nPan^0x80); + psmp->nLength = pms->dwLength; + psmp->nC4Speed = pms->dwFrequency; + psmp->nLoopStart = pms->dwLoopStart; + psmp->nLoopEnd = pms->dwLoopEnd; + FrequencyToTranspose(psmp); + psmp->RelativeTone -= pms->nBaseNote - 49; + psmp->nC4Speed = TransposeToFrequency(psmp->RelativeTone, psmp->nFineTune); + if (pms->nQuality == 2) { psmp->uFlags |= CHN_16BIT; psmp->nLength >>= 1; } + if (pms->nChannels == 2) { psmp->nLength >>= 1; } + if (pms->nLoop == 1) psmp->uFlags |= CHN_LOOP; + if (pms->nLoop == 2) psmp->uFlags |= CHN_LOOP|CHN_PINGPONGLOOP; + } + dwMemPos += pms->dwDataLen + 36; + } else + { + dwMemPos += 36; + } + } +#ifdef MT2DEBUG + Log("Loading groups at offset 0x%08X\n", dwMemPos); +#endif + for (UINT iMap=0; iMap<255; iMap++) if (InstrMap[iMap]) + { + if (dwMemPos+8 > dwMemLength) return TRUE; + MT2INSTRUMENT *pmi = InstrMap[iMap]; + INSTRUMENTHEADER *penv = NULL; + if (iMapwSamples; iGrp++) + { + if (penv) + { + MT2GROUP *pmg = (MT2GROUP *)(lpStream+dwMemPos); + for (UINT i=0; i<96; i++) + { + if (pmi->GroupsMapping[i] == iGrp) + { + UINT nSmp = pmg->nSmpNo+1; + penv->Keyboard[i+12] = (BYTE)nSmp; + if (nSmp <= m_nSamples) + { + Ins[nSmp].nVibType = pmi->bVibType; + Ins[nSmp].nVibSweep = pmi->bVibSweep; + Ins[nSmp].nVibDepth = pmi->bVibDepth; + Ins[nSmp].nVibRate = pmi->bVibRate; + } + } + } + } + dwMemPos += 8; + } + } +#ifdef MT2DEBUG + Log("Loading sample data at offset 0x%08X\n", dwMemPos); +#endif + for (UINT iData=0; iData<256; iData++) if ((iData < m_nSamples) && (SampleMap[iData])) + { + MT2SAMPLE *pms = SampleMap[iData]; + MODINSTRUMENT *psmp = &Ins[iData+1]; + if (!(pms->nFlags & 5)) + { + if (psmp->nLength > 0) + { + #ifdef MT2DEBUG + Log(" Reading sample #%d at offset 0x%04X (len=%d)\n", iData+1, dwMemPos, psmp->nLength); + #endif + UINT rsflags; + + if (pms->nChannels == 2) + rsflags = (psmp->uFlags & CHN_16BIT) ? RS_STPCM16D : RS_STPCM8D; + else + rsflags = (psmp->uFlags & CHN_16BIT) ? RS_PCM16D : RS_PCM8D; + + dwMemPos += ReadSample(psmp, rsflags, (LPCSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); + } + } else + if (dwMemPos+4 < dwMemLength) + { + UINT nNameLen = *(DWORD *)(lpStream+dwMemPos); + dwMemPos += nNameLen + 16; + } + if (dwMemPos+4 >= dwMemLength) break; + } + return TRUE; +} diff --git a/modplug/load_mtm.cpp b/modplug/load_mtm.cpp new file mode 100644 index 000000000..6bba0badf --- /dev/null +++ b/modplug/load_mtm.cpp @@ -0,0 +1,165 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +////////////////////////////////////////////////////////// +// MTM file support (import only) + +#pragma pack(1) + + +typedef struct tagMTMSAMPLE +{ + char samplename[22]; // changed from CHAR + DWORD length; + DWORD reppos; + DWORD repend; + CHAR finetune; + BYTE volume; + BYTE attribute; +} MTMSAMPLE; + + +typedef struct tagMTMHEADER +{ + char id[4]; // MTM file marker + version // changed from CHAR + char songname[20]; // ASCIIZ songname // changed from CHAR + WORD numtracks; // number of tracks saved + BYTE lastpattern; // last pattern number saved + BYTE lastorder; // last order number to play (songlength-1) + WORD commentsize; // length of comment field + BYTE numsamples; // number of samples saved + BYTE attribute; // attribute byte (unused) + BYTE beatspertrack; + BYTE numchannels; // number of channels used + BYTE panpos[32]; // voice pan positions +} MTMHEADER; + + +#pragma pack() + + +BOOL CSoundFile::ReadMTM(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + MTMHEADER *pmh = (MTMHEADER *)lpStream; + DWORD dwMemPos = 66; + + if ((!lpStream) || (dwMemLength < 0x100)) return FALSE; + if ((strncmp(pmh->id, "MTM", 3)) || (pmh->numchannels > 32) + || (pmh->numsamples >= MAX_SAMPLES) || (!pmh->numsamples) + || (!pmh->numtracks) || (!pmh->numchannels) + || (!pmh->lastpattern) || (pmh->lastpattern > MAX_PATTERNS)) return FALSE; + strncpy(m_szNames[0], pmh->songname, 20); + m_szNames[0][20] = 0; + if (dwMemPos + 37*pmh->numsamples + 128 + 192*pmh->numtracks + + 64 * (pmh->lastpattern+1) + pmh->commentsize >= dwMemLength) return FALSE; + m_nType = MOD_TYPE_MTM; + m_nSamples = pmh->numsamples; + m_nChannels = pmh->numchannels; + // Reading instruments + for (UINT i=1; i<=m_nSamples; i++) + { + MTMSAMPLE *pms = (MTMSAMPLE *)(lpStream + dwMemPos); + strncpy(m_szNames[i], pms->samplename, 22); + m_szNames[i][22] = 0; + Ins[i].nVolume = pms->volume << 2; + Ins[i].nGlobalVol = 64; + DWORD len = pms->length; + if ((len > 4) && (len <= MAX_SAMPLE_LENGTH)) + { + Ins[i].nLength = len; + Ins[i].nLoopStart = pms->reppos; + Ins[i].nLoopEnd = pms->repend; + if (Ins[i].nLoopEnd > Ins[i].nLength) Ins[i].nLoopEnd = Ins[i].nLength; + if (Ins[i].nLoopStart + 4 >= Ins[i].nLoopEnd) Ins[i].nLoopStart = Ins[i].nLoopEnd = 0; + if (Ins[i].nLoopEnd) Ins[i].uFlags |= CHN_LOOP; + Ins[i].nFineTune = MOD2XMFineTune(pms->finetune); + if (pms->attribute & 0x01) + { + Ins[i].uFlags |= CHN_16BIT; + Ins[i].nLength >>= 1; + Ins[i].nLoopStart >>= 1; + Ins[i].nLoopEnd >>= 1; + } + Ins[i].nPan = 128; + } + dwMemPos += 37; + } + // Setting Channel Pan Position + for (UINT ich=0; ichpanpos[ich] & 0x0F) << 4) + 8; + ChnSettings[ich].nVolume = 64; + } + // Reading pattern order + memcpy(Order, lpStream + dwMemPos, pmh->lastorder+1); + dwMemPos += 128; + // Reading Patterns + LPCBYTE pTracks = lpStream + dwMemPos; + dwMemPos += 192 * pmh->numtracks; + LPWORD pSeq = (LPWORD)(lpStream + dwMemPos); + for (UINT pat=0; pat<=pmh->lastpattern; pat++) + { + PatternSize[pat] = 64; + PatternAllocSize[pat] = 64; + if ((Patterns[pat] = AllocatePattern(64, m_nChannels)) == NULL) break; + for (UINT n=0; n<32; n++) if ((pSeq[n]) && (pSeq[n] <= pmh->numtracks) && (n < m_nChannels)) + { + LPCBYTE p = pTracks + 192 * (pSeq[n]-1); + MODCOMMAND *m = Patterns[pat] + n; + for (UINT i=0; i<64; i++, m+=m_nChannels, p+=3) + { + if (p[0] & 0xFC) m->note = (p[0] >> 2) + 37; + m->instr = ((p[0] & 0x03) << 4) | (p[1] >> 4); + UINT cmd = p[1] & 0x0F; + UINT param = p[2]; + if (cmd == 0x0A) + { + if (param & 0xF0) param &= 0xF0; else param &= 0x0F; + } + m->command = cmd; + m->param = param; + if ((cmd) || (param)) ConvertModCommand(m); + } + } + pSeq += 32; + } + dwMemPos += 64*(pmh->lastpattern+1); + if ((pmh->commentsize) && (dwMemPos + pmh->commentsize < dwMemLength)) + { + UINT n = pmh->commentsize; + m_lpszSongComments = new char[n+1]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos, n); + m_lpszSongComments[n] = 0; + for (UINT i=0; icommentsize; + // Reading Samples + for (UINT ismp=1; ismp<=m_nSamples; ismp++) + { + if (dwMemPos >= dwMemLength) break; + dwMemPos += ReadSample(&Ins[ismp], (Ins[ismp].uFlags & CHN_16BIT) ? RS_PCM16U : RS_PCM8U, + (LPSTR)(lpStream + dwMemPos), dwMemLength - dwMemPos); + } + m_nMinPeriod = 64; + m_nMaxPeriod = 32767; + return TRUE; +} + diff --git a/modplug/load_okt.cpp b/modplug/load_okt.cpp new file mode 100644 index 000000000..61eda9644 --- /dev/null +++ b/modplug/load_okt.cpp @@ -0,0 +1,198 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +////////////////////////////////////////////// +// Oktalyzer (OKT) module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +typedef struct OKTFILEHEADER +{ + DWORD okta; // "OKTA" + DWORD song; // "SONG" + DWORD cmod; // "CMOD" + DWORD fixed8; + BYTE chnsetup[8]; + DWORD samp; // "SAMP" + DWORD samplen; +} OKTFILEHEADER; + + +typedef struct OKTSAMPLE +{ + CHAR name[20]; + DWORD length; + WORD loopstart; + WORD looplen; + BYTE pad1; + BYTE volume; + BYTE pad2; + BYTE pad3; +} OKTSAMPLE; + + +BOOL CSoundFile::ReadOKT(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + OKTFILEHEADER *pfh = (OKTFILEHEADER *)lpStream; + DWORD dwMemPos = sizeof(OKTFILEHEADER); + UINT nsamples = 0, npatterns = 0, norders = 0; + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pfh->okta != 0x41544B4F) || (pfh->song != 0x474E4F53) + || (pfh->cmod != 0x444F4D43) || (pfh->chnsetup[0]) || (pfh->chnsetup[2]) + || (pfh->chnsetup[4]) || (pfh->chnsetup[6]) || (pfh->fixed8 != 0x08000000) + || (pfh->samp != 0x504D4153)) return FALSE; + m_nType = MOD_TYPE_OKT; + m_nChannels = 4 + pfh->chnsetup[1] + pfh->chnsetup[3] + pfh->chnsetup[5] + pfh->chnsetup[7]; + if (m_nChannels > MAX_CHANNELS) m_nChannels = MAX_CHANNELS; + nsamples = bswapBE32(pfh->samplen) >> 5; + m_nSamples = nsamples; + if (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1; + // Reading samples + for (UINT smp=1; smp <= nsamples; smp++) + { + if (dwMemPos >= dwMemLength) return TRUE; + if (smp < MAX_SAMPLES) + { + OKTSAMPLE *psmp = (OKTSAMPLE *)(lpStream + dwMemPos); + MODINSTRUMENT *pins = &Ins[smp]; + + memcpy(m_szNames[smp], psmp->name, 20); + pins->uFlags = 0; + pins->nLength = bswapBE32(psmp->length) & ~1; + pins->nLoopStart = bswapBE16(psmp->loopstart); + pins->nLoopEnd = pins->nLoopStart + bswapBE16(psmp->looplen); + if (pins->nLoopStart + 2 < pins->nLoopEnd) pins->uFlags |= CHN_LOOP; + pins->nGlobalVol = 64; + pins->nVolume = psmp->volume << 2; + pins->nC4Speed = 8363; + } + dwMemPos += sizeof(OKTSAMPLE); + } + // SPEE + if (dwMemPos >= dwMemLength) return TRUE; + if (*((DWORD *)(lpStream + dwMemPos)) == 0x45455053) + { + m_nDefaultSpeed = lpStream[dwMemPos+9]; + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // SLEN + if (dwMemPos >= dwMemLength) return TRUE; + if (*((DWORD *)(lpStream + dwMemPos)) == 0x4E454C53) + { + npatterns = lpStream[dwMemPos+9]; + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // PLEN + if (dwMemPos >= dwMemLength) return TRUE; + if (*((DWORD *)(lpStream + dwMemPos)) == 0x4E454C50) + { + norders = lpStream[dwMemPos+9]; + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // PATT + if (dwMemPos >= dwMemLength) return TRUE; + if (*((DWORD *)(lpStream + dwMemPos)) == 0x54544150) + { + UINT orderlen = norders; + if (orderlen >= MAX_ORDERS) orderlen = MAX_ORDERS-1; + for (UINT i=0; i1; j--) { if (Order[j-1]) break; Order[j-1] = 0xFF; } + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // PBOD + UINT npat = 0; + while ((dwMemPos+10 < dwMemLength) && (*((DWORD *)(lpStream + dwMemPos)) == 0x444F4250)) + { + DWORD dwPos = dwMemPos + 10; + UINT rows = lpStream[dwMemPos+9]; + if (!rows) rows = 64; + if (npat < MAX_PATTERNS) + { + if ((Patterns[npat] = AllocatePattern(rows, m_nChannels)) == NULL) return TRUE; + MODCOMMAND *m = Patterns[npat]; + PatternSize[npat] = rows; + PatternAllocSize[npat] = rows; + UINT imax = m_nChannels*rows; + for (UINT i=0; i dwMemLength) break; + const BYTE *p = lpStream+dwPos; + UINT note = p[0]; + if (note) + { + m->note = note + 48; + m->instr = p[1] + 1; + } + UINT command = p[2]; + UINT param = p[3]; + m->param = param; + switch(command) + { + // 0: no effect + case 0: + break; + // 1: Portamento Up + case 1: + case 17: + case 30: + if (param) m->command = CMD_PORTAMENTOUP; + break; + // 2: Portamento Down + case 2: + case 13: + case 21: + if (param) m->command = CMD_PORTAMENTODOWN; + break; + // 10: Arpeggio + case 10: + case 11: + case 12: + m->command = CMD_ARPEGGIO; + break; + // 15: Filter + case 15: + m->command = CMD_MODCMDEX; + m->param = param & 0x0F; + break; + // 25: Position Jump + case 25: + m->command = CMD_POSITIONJUMP; + break; + // 28: Set Speed + case 28: + m->command = CMD_SPEED; + break; + // 31: Volume Control + case 31: + if (param <= 0x40) m->command = CMD_VOLUME; else + if (param <= 0x50) { m->command = CMD_VOLUMESLIDE; m->param &= 0x0F; if (!m->param) m->param = 0x0F; } else + if (param <= 0x60) { m->command = CMD_VOLUMESLIDE; m->param = (param & 0x0F) << 4; if (!m->param) m->param = 0xF0; } else + if (param <= 0x70) { m->command = CMD_MODCMDEX; m->param = 0xB0 | (param & 0x0F); if (!(param & 0x0F)) m->param = 0xBF; } else + if (param <= 0x80) { m->command = CMD_MODCMDEX; m->param = 0xA0 | (param & 0x0F); if (!(param & 0x0F)) m->param = 0xAF; } + break; + } + } + } + npat++; + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + } + // SBOD + UINT nsmp = 1; + while ((dwMemPos+10 < dwMemLength) && (*((DWORD *)(lpStream + dwMemPos)) == 0x444F4253)) + { + if (nsmp < MAX_SAMPLES) ReadSample(&Ins[nsmp], RS_PCM8S, (LPSTR)(lpStream+dwMemPos+8), dwMemLength-dwMemPos-8); + dwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8; + nsmp++; + } + return TRUE; +} + diff --git a/modplug/load_psm.cpp b/modplug/load_psm.cpp new file mode 100644 index 000000000..c499e4fce --- /dev/null +++ b/modplug/load_psm.cpp @@ -0,0 +1,840 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + + +/////////////////////////////////////////////////// +// +// PSM module loader +// +/////////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#define PSM_LOG + +#define PSM_ID_NEW 0x204d5350 +#define PSM_ID_OLD 0xfe4d5350 +#define IFFID_FILE 0x454c4946 +#define IFFID_TITL 0x4c544954 +#define IFFID_SDFT 0x54464453 +#define IFFID_PBOD 0x444f4250 +#define IFFID_SONG 0x474e4f53 +#define IFFID_PATT 0x54544150 +#define IFFID_DSMP 0x504d5344 +#define IFFID_OPLH 0x484c504f + +#pragma pack(1) + +typedef struct _PSMCHUNK +{ + DWORD id; + DWORD len; + DWORD listid; +} PSMCHUNK; + +typedef struct _PSMSONGHDR +{ + CHAR songname[8]; // "MAINSONG" + BYTE reserved1; + BYTE reserved2; + BYTE channels; +} PSMSONGHDR; + +typedef struct _PSMPATTERN +{ + DWORD size; + DWORD name; + WORD rows; + WORD reserved1; + BYTE data[4]; +} PSMPATTERN; + +typedef struct _PSMSAMPLE +{ + BYTE flags; + CHAR songname[8]; + DWORD smpid; + CHAR samplename[34]; + DWORD reserved1; + BYTE reserved2; + BYTE insno; + BYTE reserved3; + DWORD length; + DWORD loopstart; + DWORD loopend; + WORD reserved4; + BYTE defvol; + DWORD reserved5; + DWORD samplerate; + BYTE reserved6[19]; +} PSMSAMPLE; + +#pragma pack() + + +BOOL CSoundFile::ReadPSM(LPCBYTE lpStream, DWORD dwMemLength) +//----------------------------------------------------------- +{ + PSMCHUNK *pfh = (PSMCHUNK *)lpStream; + DWORD dwMemPos, dwSongPos; + DWORD smpnames[MAX_SAMPLES]; + DWORD patptrs[MAX_PATTERNS]; + BYTE samplemap[MAX_SAMPLES]; + UINT nPatterns; + + // Chunk0: "PSM ",filesize,"FILE" + if (dwMemLength < 256) return FALSE; + if (pfh->id == PSM_ID_OLD) + { + #ifdef PSM_LOG + Log("Old PSM format not supported\n"); + #endif + return FALSE; + } + if ((pfh->id != PSM_ID_NEW) || (pfh->len+12 > dwMemLength) || (pfh->listid != IFFID_FILE)) return FALSE; + m_nType = MOD_TYPE_PSM; + m_nChannels = 16; + m_nSamples = 0; + nPatterns = 0; + dwMemPos = 12; + dwSongPos = 0; + for (UINT iChPan=0; iChPan<16; iChPan++) + { + UINT pan = (((iChPan & 3) == 1) || ((iChPan&3)==2)) ? 0xC0 : 0x40; + ChnSettings[iChPan].nPan = pan; + } + while (dwMemPos+8 < dwMemLength) + { + PSMCHUNK *pchunk = (PSMCHUNK *)(lpStream+dwMemPos); + if ((pchunk->len >= dwMemLength - 8) || (dwMemPos + pchunk->len + 8 > dwMemLength)) break; + dwMemPos += 8; + PUCHAR pdata = (PUCHAR)(lpStream+dwMemPos); + ULONG len = pchunk->len; + if (len) switch(pchunk->id) + { + // "TITL": Song title + case IFFID_TITL: + if (!pdata[0]) { pdata++; len--; } + memcpy(m_szNames[0], pdata, (len>31) ? 31 : len); + m_szNames[0][31] = 0; + break; + // "PBOD": Pattern + case IFFID_PBOD: + if ((len >= 12) && (nPatterns < MAX_PATTERNS)) + { + patptrs[nPatterns++] = dwMemPos-8; + } + break; + // "SONG": Song description + case IFFID_SONG: + if ((len >= sizeof(PSMSONGHDR)+8) && (!dwSongPos)) + { + dwSongPos = dwMemPos - 8; + } + break; + // "DSMP": Sample Data + case IFFID_DSMP: + if ((len >= sizeof(PSMSAMPLE)) && (m_nSamples+1 < MAX_SAMPLES)) + { + m_nSamples++; + MODINSTRUMENT *pins = &Ins[m_nSamples]; + PSMSAMPLE *psmp = (PSMSAMPLE *)pdata; + smpnames[m_nSamples] = psmp->smpid; + memcpy(m_szNames[m_nSamples], psmp->samplename, 31); + m_szNames[m_nSamples][31] = 0; + samplemap[m_nSamples-1] = (BYTE)m_nSamples; + // Init sample + pins->nGlobalVol = 0x40; + pins->nC4Speed = psmp->samplerate; + pins->nLength = psmp->length; + pins->nLoopStart = psmp->loopstart; + pins->nLoopEnd = psmp->loopend; + pins->nPan = 128; + pins->nVolume = (psmp->defvol+1) * 2; + pins->uFlags = (psmp->flags & 0x80) ? CHN_LOOP : 0; + if (pins->nLoopStart > 0) pins->nLoopStart--; + // Point to sample data + pdata += 0x60; + len -= 0x60; + // Load sample data + if ((pins->nLength > 3) && (len > 3)) + { + ReadSample(pins, RS_PCM8D, (LPCSTR)pdata, len); + } else + { + pins->nLength = 0; + } + } + break; + #if 0 + default: + { + CHAR s[8], s2[64]; + *(DWORD *)s = pchunk->id; + s[4] = 0; + wsprintf(s2, "%s: %4d bytes @ %4d\n", s, pchunk->len, dwMemPos); + OutputDebugString(s2); + } + #endif + } + dwMemPos += pchunk->len; + } + // Step #1: convert song structure + PSMSONGHDR *pSong = (PSMSONGHDR *)(lpStream+dwSongPos+8); + if ((!dwSongPos) || (pSong->channels < 2) || (pSong->channels > 32)) return TRUE; + m_nChannels = pSong->channels; + // Valid song header -> convert attached chunks + { + DWORD dwSongEnd = dwSongPos + 8 + *(DWORD *)(lpStream+dwSongPos+4); + dwMemPos = dwSongPos + 8 + 11; // sizeof(PSMCHUNK)+sizeof(PSMSONGHDR) + while (dwMemPos + 8 < dwSongEnd) + { + PSMCHUNK *pchunk = (PSMCHUNK *)(lpStream+dwMemPos); + dwMemPos += 8; + if ((pchunk->len > dwSongEnd) || (dwMemPos + pchunk->len > dwSongEnd)) break; + PUCHAR pdata = (PUCHAR)(lpStream+dwMemPos); + ULONG len = pchunk->len; + switch(pchunk->id) + { + case IFFID_OPLH: + if (len >= 0x20) + { + UINT pos = len - 3; + while (pos > 5) + { + BOOL bFound = FALSE; + pos -= 5; + DWORD dwName = *(DWORD *)(pdata+pos); + for (UINT i=0; iname; + if (dwName == dwPatName) + { + bFound = TRUE; + break; + } + } + if ((!bFound) && (pdata[pos+1] > 0) && (pdata[pos+1] <= 0x10) + && (pdata[pos+3] > 0x40) && (pdata[pos+3] < 0xC0)) + { + m_nDefaultSpeed = pdata[pos+1]; + m_nDefaultTempo = pdata[pos+3]; + break; + } + } + UINT iOrd = 0; + while ((pos+5name; + if (dwName == dwPatName) + { + Order[iOrd++] = i; + break; + } + } + pos += 5; + } + } + break; + } + dwMemPos += pchunk->len; + } + } + + // Step #2: convert patterns + for (UINT nPat=0; nPatrows; + if (len > pPsmPat->size) len = pPsmPat->size; + if ((nRows < 64) || (nRows > 256)) nRows = 64; + PatternSize[nPat] = nRows; + PatternAllocSize[nPat] = nRows; + if ((Patterns[nPat] = AllocatePattern(nRows, m_nChannels)) == NULL) break; + MODCOMMAND *m = Patterns[nPat]; + BYTE *p = pPsmPat->data; + UINT pos = 0; + UINT row = 0; + UINT oldch = 0; + BOOL bNewRow = FALSE; + #ifdef PSM_LOG + Log("Pattern %d at offset 0x%04X\n", nPat, (DWORD)(p - (BYTE *)lpStream)); + #endif + while ((row < nRows) && (pos+1 < len)) + { + UINT flags = p[pos++]; + UINT ch = p[pos++]; + + #ifdef PSM_LOG + //Log("flags+ch: %02X.%02X\n", flags, ch); + #endif + if (((flags & 0xf0) == 0x10) && (ch <= oldch) /*&& (!bNewRow)*/) + { + if ((pos+1= len) || (row >= nRows)) break; + if (!(flags & 0xf0)) + { + #ifdef PSM_LOG + //if (!nPat) Log("EOR(%d): %02X.%02X\n", row, p[pos], p[pos+1]); + #endif + row++; + m += m_nChannels; + bNewRow = TRUE; + oldch = ch; + continue; + } + bNewRow = FALSE; + if (ch >= m_nChannels) + { + #ifdef PSM_LOG + if (!nPat) Log("Invalid channel row=%d (0x%02X.0x%02X)\n", row, flags, ch); + #endif + ch = 0; + } + // Note + Instr + if ((flags & 0x40) && (pos+1 < len)) + { + UINT note = p[pos++]; + UINT nins = p[pos++]; + #ifdef PSM_LOG + //if (!nPat) Log("note+ins: %02X.%02X\n", note, nins); + if ((!nPat) && (nins >= m_nSamples)) Log("WARNING: invalid instrument number (%d)\n", nins); + #endif + if ((note) && (note < 0x80)) note = (note>>4)*12+(note&0x0f)+12+1; + m[ch].instr = samplemap[nins]; + m[ch].note = note; + } + // Volume + if ((flags & 0x20) && (pos < len)) + { + m[ch].volcmd = VOLCMD_VOLUME; + m[ch].vol = p[pos++] / 2; + } + // Effect + if ((flags & 0x10) && (pos+1 < len)) + { + UINT command = p[pos++]; + UINT param = p[pos++]; + // Convert effects + switch(command) + { + // 01: fine volslide up + case 0x01: command = CMD_VOLUMESLIDE; param |= 0x0f; break; + // 04: fine volslide down + case 0x04: command = CMD_VOLUMESLIDE; param>>=4; param |= 0xf0; break; + // 0C: portamento up + case 0x0C: command = CMD_PORTAMENTOUP; param = (param+1)/2; break; + // 0E: portamento down + case 0x0E: command = CMD_PORTAMENTODOWN; param = (param+1)/2; break; + // 33: Position Jump + case 0x33: command = CMD_POSITIONJUMP; break; + // 34: Pattern break + case 0x34: command = CMD_PATTERNBREAK; break; + // 3D: speed + case 0x3D: command = CMD_SPEED; break; + // 3E: tempo + case 0x3E: command = CMD_TEMPO; break; + // Unknown + default: + #ifdef PSM_LOG + Log("Unknown PSM effect pat=%d row=%d ch=%d: %02X.%02X\n", nPat, row, ch, command, param); + #endif + command = param = 0; + } + m[ch].command = (BYTE)command; + m[ch].param = (BYTE)param; + } + oldch = ch; + } + #ifdef PSM_LOG + if (pos < len) + { + Log("Pattern %d: %d/%d[%d] rows (%d bytes) -> %d bytes left\n", nPat, row, nRows, pPsmPat->rows, pPsmPat->size, len-pos); + } + #endif + } + + // Done (finally!) + return TRUE; +} + + +////////////////////////////////////////////////////////////// +// +// PSM Old Format +// + +/* + +CONST + c_PSM_MaxOrder = $FF; + c_PSM_MaxSample = $FF; + c_PSM_MaxChannel = $0F; + + TYPE + PPSM_Header = ^TPSM_Header; + TPSM_Header = RECORD + PSM_Sign : ARRAY[01..04] OF CHAR; { PSM + #254 } + PSM_SongName : ARRAY[01..58] OF CHAR; + PSM_Byte00 : BYTE; + PSM_Byte1A : BYTE; + PSM_Unknown00 : BYTE; + PSM_Unknown01 : BYTE; + PSM_Unknown02 : BYTE; + PSM_Speed : BYTE; + PSM_Tempo : BYTE; + PSM_Unknown03 : BYTE; + PSM_Unknown04 : WORD; + PSM_OrderLength : WORD; + PSM_PatternNumber : WORD; + PSM_SampleNumber : WORD; + PSM_ChannelNumber : WORD; + PSM_ChannelUsed : WORD; + PSM_OrderPosition : LONGINT; + PSM_ChannelSettingPosition : LONGINT; + PSM_PatternPosition : LONGINT; + PSM_SamplePosition : LONGINT; + { *** perhaps there are some more infos in a larger header, + but i have not decoded it and so it apears here NOT } + END; + + PPSM_Sample = ^TPSM_Sample; + TPSM_Sample = RECORD + PSM_SampleFileName : ARRAY[01..12] OF CHAR; + PSM_SampleByte00 : BYTE; + PSM_SampleName : ARRAY[01..22] OF CHAR; + PSM_SampleUnknown00 : ARRAY[01..02] OF BYTE; + PSM_SamplePosition : LONGINT; + PSM_SampleUnknown01 : ARRAY[01..04] OF BYTE; + PSM_SampleNumber : BYTE; + PSM_SampleFlags : WORD; + PSM_SampleLength : LONGINT; + PSM_SampleLoopBegin : LONGINT; + PSM_SampleLoopEnd : LONGINT; + PSM_Unknown03 : BYTE; + PSM_SampleVolume : BYTE; + PSM_SampleC5Speed : WORD; + END; + + PPSM_SampleList = ^TPSM_SampleList; + TPSM_SampleList = ARRAY[01..c_PSM_MaxSample] OF TPSM_Sample; + + PPSM_Order = ^TPSM_Order; + TPSM_Order = ARRAY[00..c_PSM_MaxOrder] OF BYTE; + + PPSM_ChannelSettings = ^TPSM_ChannelSettings; + TPSM_ChannelSettings = ARRAY[00..c_PSM_MaxChannel] OF BYTE; + + CONST + PSM_NotesInPattern : BYTE = $00; + PSM_ChannelInPattern : BYTE = $00; + + CONST + c_PSM_SetSpeed = 60; + + FUNCTION PSM_Size(FileName : STRING;FilePosition : LONGINT) : LONGINT; + BEGIN + END; + + PROCEDURE PSM_UnpackPattern(VAR Source,Destination;PatternLength : WORD); + VAR + Witz : ARRAY[00..04] OF WORD; + I1,I2 : WORD; + I3,I4 : WORD; + TopicalByte : ^BYTE; + Pattern : PUnpackedPattern; + ChannelP : BYTE; + NoteP : BYTE; + InfoByte : BYTE; + CodeByte : BYTE; + InfoWord : WORD; + Effect : BYTE; + Opperand : BYTE; + Panning : BYTE; + Volume : BYTE; + PrevInfo : BYTE; + InfoIndex : BYTE; + BEGIN + Pattern := @Destination; + TopicalByte := @Source; + { *** Initialize patttern } + FOR I2 := 0 TO c_Maximum_NoteIndex DO + FOR I3 := 0 TO c_Maximum_ChannelIndex DO + BEGIN + Pattern^[I2,I3,c_Pattern_NoteIndex] := $FF; + Pattern^[I2,I3,c_Pattern_SampleIndex] := $00; + Pattern^[I2,I3,c_Pattern_VolumeIndex] := $FF; + Pattern^[I2,I3,c_Pattern_PanningIndex] := $FF; + Pattern^[I2,I3,c_Pattern_EffectIndex] := $00; + Pattern^[I2,I3,c_Pattern_OpperandIndex] := $00; + END; + { *** Byte-pointer on first pattern-entry } + ChannelP := $00; + NoteP := $00; + InfoByte := $00; + PrevInfo := $00; + InfoIndex := $02; + { *** read notes in pattern } + PSM_NotesInPattern := TopicalByte^; INC(TopicalByte); DEC(PatternLength); INC(InfoIndex); + PSM_ChannelInPattern := TopicalByte^; INC(TopicalByte); DEC(PatternLength); INC(InfoIndex); + { *** unpack pattern } + WHILE (INTEGER(PatternLength) > 0) AND (NoteP < c_Maximum_NoteIndex) DO + BEGIN + { *** Read info-byte } + InfoByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength); INC(InfoIndex); + IF InfoByte <> $00 THEN + BEGIN + ChannelP := InfoByte AND $0F; + IF InfoByte AND 128 = 128 THEN { note and sample } + BEGIN + { *** read note } + CodeByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + DEC(CodeByte); + CodeByte := CodeByte MOD 12 * 16 + CodeByte DIV 12 + 2; + Pattern^[NoteP,ChannelP,c_Pattern_NoteIndex] := CodeByte; + { *** read sample } + CodeByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + Pattern^[NoteP,ChannelP,c_Pattern_SampleIndex] := CodeByte; + END; + IF InfoByte AND 64 = 64 THEN { Volume } + BEGIN + CodeByte := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + Pattern^[NoteP,ChannelP,c_Pattern_VolumeIndex] := CodeByte; + END; + IF InfoByte AND 32 = 32 THEN { effect AND opperand } + BEGIN + Effect := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + Opperand := TopicalByte^; INC(TopicalByte); DEC(PatternLength); + CASE Effect OF + c_PSM_SetSpeed: + BEGIN + Effect := c_I_Set_Speed; + END; + ELSE + BEGIN + Effect := c_I_NoEffect; + Opperand := $00; + END; + END; + Pattern^[NoteP,ChannelP,c_Pattern_EffectIndex] := Effect; + Pattern^[NoteP,ChannelP,c_Pattern_OpperandIndex] := Opperand; + END; + END ELSE INC(NoteP); + END; + END; + + PROCEDURE PSM_Load(FileName : STRING;FilePosition : LONGINT;VAR Module : PModule;VAR ErrorCode : WORD); + { *** caution : Module has to be inited before!!!! } + VAR + Header : PPSM_Header; + Sample : PPSM_SampleList; + Order : PPSM_Order; + ChannelSettings : PPSM_ChannelSettings; + MultiPurposeBuffer : PByteArray; + PatternBuffer : PUnpackedPattern; + TopicalParaPointer : WORD; + + InFile : FILE; + I1,I2 : WORD; + I3,I4 : WORD; + TempW : WORD; + TempB : BYTE; + TempP : PByteArray; + TempI : INTEGER; + { *** copy-vars for loop-extension } + CopySource : LONGINT; + CopyDestination : LONGINT; + CopyLength : LONGINT; + BEGIN + { *** try to open file } + ASSIGN(InFile,FileName); +{$I-} + RESET(InFile,1); +{$I+} + IF IORESULT <> $00 THEN + BEGIN + EXIT; + END; +{$I-} + { *** seek start of module } + IF FILESIZE(InFile) < FilePosition THEN + BEGIN + EXIT; + END; + SEEK(InFile,FilePosition); + { *** look for enough memory for temporary variables } + IF MEMAVAIL < SIZEOF(TPSM_Header) + SIZEOF(TPSM_SampleList) + + SIZEOF(TPSM_Order) + SIZEOF(TPSM_ChannelSettings) + + SIZEOF(TByteArray) + SIZEOF(TUnpackedPattern) + THEN + BEGIN + EXIT; + END; + { *** init dynamic variables } + NEW(Header); + NEW(Sample); + NEW(Order); + NEW(ChannelSettings); + NEW(MultiPurposeBuffer); + NEW(PatternBuffer); + { *** read header } + BLOCKREAD(InFile,Header^,SIZEOF(TPSM_Header)); + { *** test if this is a DSM-file } + IF NOT ((Header^.PSM_Sign[1] = 'P') AND (Header^.PSM_Sign[2] = 'S') AND + (Header^.PSM_Sign[3] = 'M') AND (Header^.PSM_Sign[4] = #254)) THEN + BEGIN + ErrorCode := c_NoValidFileFormat; + CLOSE(InFile); + EXIT; + END; + { *** read order } + SEEK(InFile,FilePosition + Header^.PSM_OrderPosition); + BLOCKREAD(InFile,Order^,Header^.PSM_OrderLength); + { *** read channelsettings } + SEEK(InFile,FilePosition + Header^.PSM_ChannelSettingPosition); + BLOCKREAD(InFile,ChannelSettings^,SIZEOF(TPSM_ChannelSettings)); + { *** read samplelist } + SEEK(InFile,FilePosition + Header^.PSM_SamplePosition); + BLOCKREAD(InFile,Sample^,Header^.PSM_SampleNumber * SIZEOF(TPSM_Sample)); + { *** copy header to intern NTMIK-structure } + Module^.Module_Sign := 'MF'; + Module^.Module_FileFormatVersion := $0100; + Module^.Module_SampleNumber := Header^.PSM_SampleNumber; + Module^.Module_PatternNumber := Header^.PSM_PatternNumber; + Module^.Module_OrderLength := Header^.PSM_OrderLength; + Module^.Module_ChannelNumber := Header^.PSM_ChannelNumber+1; + Module^.Module_Initial_GlobalVolume := 64; + Module^.Module_Initial_MasterVolume := $C0; + Module^.Module_Initial_Speed := Header^.PSM_Speed; + Module^.Module_Initial_Tempo := Header^.PSM_Tempo; +{ *** paragraph 01 start } + Module^.Module_Flags := c_Module_Flags_ZeroVolume * BYTE(1) + + c_Module_Flags_Stereo * BYTE(1) + + c_Module_Flags_ForceAmigaLimits * BYTE(0) + + c_Module_Flags_Panning * BYTE(1) + + c_Module_Flags_Surround * BYTE(1) + + c_Module_Flags_QualityMixing * BYTE(1) + + c_Module_Flags_FastVolumeSlides * BYTE(0) + + c_Module_Flags_SpecialCustomData * BYTE(0) + + c_Module_Flags_SongName * BYTE(1); + I1 := $01; + WHILE (Header^.PSM_SongName[I1] > #00) AND (I1 < c_Module_SongNameLength) DO + BEGIN + Module^.Module_Name[I1] := Header^.PSM_SongName[I1]; + INC(I1); + END; + Module^.Module_Name[c_Module_SongNameLength] := #00; + { *** Init channelsettings } + FOR I1 := 0 TO c_Maximum_ChannelIndex DO + BEGIN + IF I1 < Header^.PSM_ChannelUsed THEN + BEGIN + { *** channel enabled } + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_GlobalVolume := 64; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Panning := (ChannelSettings^[I1]) * $08; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Code := I1 + $10 * BYTE(ChannelSettings^[I1] > $08) + + c_ChannelSettings_Code_ChannelEnabled * BYTE(1) + + c_ChannelSettings_Code_ChannelDigital * BYTE(1); + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Controls := + c_ChannelSettings_Controls_EnhancedMode * BYTE(1) + + c_ChannelSettings_Controls_SurroundMode * BYTE(0); + END + ELSE + BEGIN + { *** channel disabled } + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_GlobalVolume := $00; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Panning := $00; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Code := $00; + Module^.Module_ChannelSettingPointer^[I1].ChannelSettings_Controls := $00; + END; + END; + { *** init and copy order } + FILLCHAR(Module^.Module_OrderPointer^,c_Maximum_OrderIndex+1,$FF); + MOVE(Order^,Module^.Module_OrderPointer^,Header^.PSM_OrderLength); + { *** read pattern } + SEEK(InFile,FilePosition + Header^.PSM_PatternPosition); + NTMIK_LoaderPatternNumber := Header^.PSM_PatternNumber-1; + FOR I1 := 0 TO Header^.PSM_PatternNumber-1 DO + BEGIN + NTMIK_LoadPatternProcedure; + { *** read length } + BLOCKREAD(InFile,TempW,2); + { *** read pattern } + BLOCKREAD(InFile,MultiPurposeBuffer^,TempW-2); + { *** unpack pattern and set notes per channel to 64 } + PSM_UnpackPattern(MultiPurposeBuffer^,PatternBuffer^,TempW); + NTMIK_PackPattern(MultiPurposeBuffer^,PatternBuffer^,PSM_NotesInPattern); + TempW := WORD(256) * MultiPurposeBuffer^[01] + MultiPurposeBuffer^[00]; + GETMEM(Module^.Module_PatternPointer^[I1],TempW); + MOVE(MultiPurposeBuffer^,Module^.Module_PatternPointer^[I1]^,TempW); + { *** next pattern } + END; + { *** read samples } + NTMIK_LoaderSampleNumber := Header^.PSM_SampleNumber; + FOR I1 := 1 TO Header^.PSM_SampleNumber DO + BEGIN + NTMIK_LoadSampleProcedure; + { *** get index for sample } + I3 := Sample^[I1].PSM_SampleNumber; + { *** clip PSM-sample } + IF Sample^[I1].PSM_SampleLoopEnd > Sample^[I1].PSM_SampleLength + THEN Sample^[I1].PSM_SampleLoopEnd := Sample^[I1].PSM_SampleLength; + { *** init intern sample } + NEW(Module^.Module_SamplePointer^[I3]); + FILLCHAR(Module^.Module_SamplePointer^[I3]^,SIZEOF(TSample),$00); + FILLCHAR(Module^.Module_SamplePointer^[I3]^.Sample_SampleName,c_Sample_SampleNameLength,#32); + FILLCHAR(Module^.Module_SamplePointer^[I3]^.Sample_FileName,c_Sample_FileNameLength,#32); + { *** copy informations to intern sample } + I2 := $01; + WHILE (Sample^[I1].PSM_SampleName[I2] > #00) AND (I2 < c_Sample_SampleNameLength) DO + BEGIN + Module^.Module_SamplePointer^[I3]^.Sample_SampleName[I2] := Sample^[I1].PSM_SampleName[I2]; + INC(I2); + END; + Module^.Module_SamplePointer^[I3]^.Sample_Sign := 'DF'; + Module^.Module_SamplePointer^[I3]^.Sample_FileFormatVersion := $00100; + Module^.Module_SamplePointer^[I3]^.Sample_Position := $00000000; + Module^.Module_SamplePointer^[I3]^.Sample_Selector := $0000; + Module^.Module_SamplePointer^[I3]^.Sample_Volume := Sample^[I1].PSM_SampleVolume; + Module^.Module_SamplePointer^[I3]^.Sample_LoopCounter := $00; + Module^.Module_SamplePointer^[I3]^.Sample_C5Speed := Sample^[I1].PSM_SampleC5Speed; + Module^.Module_SamplePointer^[I3]^.Sample_Length := Sample^[I1].PSM_SampleLength; + Module^.Module_SamplePointer^[I3]^.Sample_LoopBegin := Sample^[I1].PSM_SampleLoopBegin; + Module^.Module_SamplePointer^[I3]^.Sample_LoopEnd := Sample^[I1].PSM_SampleLoopEnd; + { *** now it's time for the flags } + Module^.Module_SamplePointer^[I3]^.Sample_Flags := + c_Sample_Flags_DigitalSample * BYTE(1) + + c_Sample_Flags_8BitSample * BYTE(1) + + c_Sample_Flags_UnsignedSampleData * BYTE(1) + + c_Sample_Flags_Packed * BYTE(0) + + c_Sample_Flags_LoopCounter * BYTE(0) + + c_Sample_Flags_SampleName * BYTE(1) + + c_Sample_Flags_LoopActive * + BYTE(Sample^[I1].PSM_SampleFlags AND (LONGINT(1) SHL 15) = (LONGINT(1) SHL 15)); + { *** alloc memory for sample-data } + E_Getmem(Module^.Module_SamplePointer^[I3]^.Sample_Selector, + Module^.Module_SamplePointer^[I3]^.Sample_Position, + Module^.Module_SamplePointer^[I3]^.Sample_Length + c_LoopExtensionSize); + { *** read out data } + EPT(TempP).p_Selector := Module^.Module_SamplePointer^[I3]^.Sample_Selector; + EPT(TempP).p_Offset := $0000; + SEEK(InFile,Sample^[I1].PSM_SamplePosition); + E_BLOCKREAD(InFile,TempP^,Module^.Module_SamplePointer^[I3]^.Sample_Length); + { *** 'coz the samples are signed in a DSM-file -> PC-fy them } + IF Module^.Module_SamplePointer^[I3]^.Sample_Length > 4 THEN + BEGIN + CopyLength := Module^.Module_SamplePointer^[I3]^.Sample_Length; + { *** decode sample } + ASM + DB 066h; MOV CX,WORD PTR CopyLength + { *** load sample selector } + MOV ES,WORD PTR TempP[00002h] + DB 066h; XOR SI,SI + DB 066h; XOR DI,DI + XOR AH,AH + { *** conert all bytes } + @@MainLoop: + DB 026h; DB 067h; LODSB + ADD AL,AH + MOV AH,AL + DB 067h; STOSB + DB 066h; LOOP @@MainLoop + END; + { *** make samples unsigned } + ASM + DB 066h; MOV CX,WORD PTR CopyLength + { *** load sample selector } + MOV ES,WORD PTR TempP[00002h] + DB 066h; XOR SI,SI + DB 066h; XOR DI,DI + { *** conert all bytes } + @@MainLoop: + DB 026h; DB 067h; LODSB + SUB AL,080h + DB 067h; STOSB + DB 066h; LOOP @@MainLoop + END; + { *** Create Loop-Extension } + IF Module^.Module_SamplePointer^[I3]^.Sample_Flags AND c_Sample_Flags_LoopActive = c_Sample_Flags_LoopActive THEN + BEGIN + CopySource := Module^.Module_SamplePointer^[I3]^.Sample_LoopBegin; + CopyDestination := Module^.Module_SamplePointer^[I3]^.Sample_LoopEnd; + CopyLength := CopyDestination - CopySource; + ASM + { *** load sample-selector } + MOV ES,WORD PTR TempP[00002h] + DB 066h; MOV DI,WORD PTR CopyDestination + { *** calculate number of full sample-loops to copy } + XOR DX,DX + MOV AX,c_LoopExtensionSize + MOV BX,WORD PTR CopyLength + DIV BX + OR AX,AX + JE @@NoFullLoop + { *** copy some full-loops (size=bx) } + MOV CX,AX + @@InnerLoop: + PUSH CX + DB 066h; MOV SI,WORD PTR CopySource + MOV CX,BX + DB 0F3h; DB 026h,067h,0A4h { REP MOVS BYTE PTR ES:[EDI],ES:[ESI] } + POP CX + LOOP @@InnerLoop + @@NoFullLoop: + { *** calculate number of rest-bytes to copy } + DB 066h; MOV SI,WORD PTR CopySource + MOV CX,DX + DB 0F3h; DB 026h,067h,0A4h { REP MOVS BYTE PTR ES:[EDI],ES:[ESI] } + END; + END + ELSE + BEGIN + CopyDestination := Module^.Module_SamplePointer^[I3]^.Sample_Length; + ASM + { *** load sample-selector } + MOV ES,WORD PTR TempP[00002h] + DB 066h; MOV DI,WORD PTR CopyDestination + { *** clear extension } + MOV CX,c_LoopExtensionSize + MOV AL,080h + DB 0F3h; DB 067h,0AAh { REP STOS BYTE PTR ES:[EDI] } + END; + END; + END; + { *** next sample } + END; + { *** init period-ranges } + NTMIK_MaximumPeriod := $0000D600 SHR 1; + NTMIK_MinimumPeriod := $0000D600 SHR 8; + { *** close file } + CLOSE(InFile); + { *** dispose all dynamic variables } + DISPOSE(Header); + DISPOSE(Sample); + DISPOSE(Order); + DISPOSE(ChannelSettings); + DISPOSE(MultiPurposeBuffer); + DISPOSE(PatternBuffer); + { *** set errorcode to noerror } + ErrorCode := c_NoError; + END; + +*/ + diff --git a/modplug/load_ptm.cpp b/modplug/load_ptm.cpp new file mode 100644 index 000000000..f67b88256 --- /dev/null +++ b/modplug/load_ptm.cpp @@ -0,0 +1,208 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +////////////////////////////////////////////// +// PTM PolyTracker module loader // +////////////////////////////////////////////// +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct PTMFILEHEADER +{ + CHAR songname[28]; // name of song, asciiz string + CHAR eof; // 26 + BYTE version_lo; // 03 version of file, currently 0203h + BYTE version_hi; // 02 + BYTE reserved1; // reserved, set to 0 + WORD norders; // number of orders (0..256) + WORD nsamples; // number of instruments (1..255) + WORD npatterns; // number of patterns (1..128) + WORD nchannels; // number of channels (voices) used (1..32) + WORD fileflags; // set to 0 + WORD reserved2; // reserved, set to 0 + DWORD ptmf_id; // song identification, 'PTMF' or 0x464d5450 + BYTE reserved3[16]; // reserved, set to 0 + BYTE chnpan[32]; // channel panning settings, 0..15, 0 = left, 7 = middle, 15 = right + BYTE orders[256]; // order list, valid entries 0..nOrders-1 + WORD patseg[128]; // pattern offsets (*16) +} PTMFILEHEADER, *LPPTMFILEHEADER; + +#define SIZEOF_PTMFILEHEADER 608 + + +typedef struct PTMSAMPLE +{ + BYTE sampletype; // sample type (bit array) + CHAR filename[12]; // name of external sample file + BYTE volume; // default volume + WORD nC4Spd; // C4 speed + WORD sampleseg; // sample segment (used internally) + WORD fileofs[2]; // offset of sample data + WORD length[2]; // sample size (in bytes) + WORD loopbeg[2]; // start of loop + WORD loopend[2]; // end of loop + WORD gusdata[8]; + char samplename[28]; // name of sample, asciiz // changed from CHAR + DWORD ptms_id; // sample identification, 'PTMS' or 0x534d5450 +} PTMSAMPLE; + +#define SIZEOF_PTMSAMPLE 80 + +#pragma pack() + + +BOOL CSoundFile::ReadPTM(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + PTMFILEHEADER pfh = *(LPPTMFILEHEADER)lpStream; + DWORD dwMemPos; + UINT nOrders; + + pfh.norders = bswapLE16(pfh.norders); + pfh.nsamples = bswapLE16(pfh.nsamples); + pfh.npatterns = bswapLE16(pfh.npatterns); + pfh.nchannels = bswapLE16(pfh.nchannels); + pfh.fileflags = bswapLE16(pfh.fileflags); + pfh.reserved2 = bswapLE16(pfh.reserved2); + pfh.ptmf_id = bswapLE32(pfh.ptmf_id); + for (UINT j=0; j<128; j++) + { + pfh.patseg[j] = bswapLE16(pfh.patseg[j]); + } + + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; + if ((pfh.ptmf_id != 0x464d5450) || (!pfh.nchannels) + || (pfh.nchannels > 32) + || (pfh.norders > 256) || (!pfh.norders) + || (!pfh.nsamples) || (pfh.nsamples > 255) + || (!pfh.npatterns) || (pfh.npatterns > 128) + || (SIZEOF_PTMFILEHEADER+pfh.nsamples*SIZEOF_PTMSAMPLE >= (int)dwMemLength)) return FALSE; + memcpy(m_szNames[0], pfh.songname, 28); + m_szNames[0][28] = 0; + m_nType = MOD_TYPE_PTM; + m_nChannels = pfh.nchannels; + m_nSamples = (pfh.nsamples < MAX_SAMPLES) ? pfh.nsamples : MAX_SAMPLES-1; + dwMemPos = SIZEOF_PTMFILEHEADER; + nOrders = (pfh.norders < MAX_ORDERS) ? pfh.norders : MAX_ORDERS-1; + memcpy(Order, pfh.orders, nOrders); + for (UINT ipan=0; ipansamplename, 28); + memcpy(pins->name, psmp->filename, 12); + pins->name[12] = 0; + pins->nGlobalVol = 64; + pins->nPan = 128; + pins->nVolume = psmp->volume << 2; + pins->nC4Speed = bswapLE16(psmp->nC4Spd) << 1; + pins->uFlags = 0; + if ((psmp->sampletype & 3) == 1) + { + UINT smpflg = RS_PCM8D; + DWORD samplepos; + pins->nLength = bswapLE32(*(LPDWORD)(psmp->length)); + pins->nLoopStart = bswapLE32(*(LPDWORD)(psmp->loopbeg)); + pins->nLoopEnd = bswapLE32(*(LPDWORD)(psmp->loopend)); + samplepos = bswapLE32(*(LPDWORD)(&psmp->fileofs)); + if (psmp->sampletype & 4) pins->uFlags |= CHN_LOOP; + if (psmp->sampletype & 8) pins->uFlags |= CHN_PINGPONGLOOP; + if (psmp->sampletype & 16) + { + pins->uFlags |= CHN_16BIT; + pins->nLength >>= 1; + pins->nLoopStart >>= 1; + pins->nLoopEnd >>= 1; + smpflg = RS_PTM8DTO16; + } + if ((pins->nLength) && (samplepos) && (samplepos < dwMemLength)) + { + ReadSample(pins, smpflg, (LPSTR)(lpStream+samplepos), dwMemLength-samplepos); + } + } + } + // Reading Patterns + for (UINT ipat=0; ipat= dwMemLength)) continue; + PatternSize[ipat] = 64; + PatternAllocSize[ipat] = 64; + if ((Patterns[ipat] = AllocatePattern(64, m_nChannels)) == NULL) break; + // + MODCOMMAND *m = Patterns[ipat]; + for (UINT row=0; ((row < 64) && (dwMemPos < dwMemLength)); ) + { + UINT b = lpStream[dwMemPos++]; + + if (dwMemPos >= dwMemLength) break; + if (b) + { + UINT nChn = b & 0x1F; + + if (b & 0x20) + { + if (dwMemPos + 2 > dwMemLength) break; + m[nChn].note = lpStream[dwMemPos++]; + m[nChn].instr = lpStream[dwMemPos++]; + } + if (b & 0x40) + { + if (dwMemPos + 2 > dwMemLength) break; + m[nChn].command = lpStream[dwMemPos++]; + m[nChn].param = lpStream[dwMemPos++]; + if ((m[nChn].command == 0x0E) && ((m[nChn].param & 0xF0) == 0x80)) + { + m[nChn].command = CMD_S3MCMDEX; + } else + if (m[nChn].command < 0x10) + { + ConvertModCommand(&m[nChn]); + } else + { + switch(m[nChn].command) + { + case 16: + m[nChn].command = CMD_GLOBALVOLUME; + break; + case 17: + m[nChn].command = CMD_RETRIG; + break; + case 18: + m[nChn].command = CMD_FINEVIBRATO; + break; + default: + m[nChn].command = 0; + } + } + } + if (b & 0x80) + { + if (dwMemPos >= dwMemLength) break; + m[nChn].volcmd = VOLCMD_VOLUME; + m[nChn].vol = lpStream[dwMemPos++]; + } + } else + { + row++; + m += m_nChannels; + } + } + } + return TRUE; +} + diff --git a/modplug/load_s3m.cpp b/modplug/load_s3m.cpp new file mode 100644 index 000000000..079497937 --- /dev/null +++ b/modplug/load_s3m.cpp @@ -0,0 +1,654 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +extern WORD S3MFineTuneTable[16]; + +////////////////////////////////////////////////////// +// ScreamTracker S3M file support + +typedef struct tagS3MSAMPLESTRUCT +{ + BYTE type; + CHAR dosname[12]; + BYTE hmem; + WORD memseg; + DWORD length; + DWORD loopbegin; + DWORD loopend; + BYTE vol; + BYTE bReserved; + BYTE pack; + BYTE flags; + DWORD finetune; + DWORD dwReserved; + WORD intgp; + WORD int512; + DWORD lastused; + CHAR name[28]; + CHAR scrs[4]; +} S3MSAMPLESTRUCT; + + +typedef struct tagS3MFILEHEADER +{ + CHAR name[28]; + BYTE b1A; + BYTE type; + WORD reserved1; + WORD ordnum; + WORD insnum; + WORD patnum; + WORD flags; + WORD cwtv; + WORD version; + DWORD scrm; // "SCRM" = 0x4D524353 + BYTE globalvol; + BYTE speed; + BYTE tempo; + BYTE mastervol; + BYTE ultraclicks; + BYTE panning_present; + BYTE reserved2[8]; + WORD special; + BYTE channels[32]; +} S3MFILEHEADER; + + +void CSoundFile::S3MConvert(MODCOMMAND *m, BOOL bIT) const +//-------------------------------------------------------- +{ + UINT command = m->command; + UINT param = m->param; + switch (command + 0x40) + { + case 'A': command = CMD_SPEED; break; + case 'B': command = CMD_POSITIONJUMP; break; + case 'C': command = CMD_PATTERNBREAK; if (!bIT) param = (param >> 4) * 10 + (param & 0x0F); break; + case 'D': command = CMD_VOLUMESLIDE; break; + case 'E': command = CMD_PORTAMENTODOWN; break; + case 'F': command = CMD_PORTAMENTOUP; break; + case 'G': command = CMD_TONEPORTAMENTO; break; + case 'H': command = CMD_VIBRATO; break; + case 'I': command = CMD_TREMOR; break; + case 'J': command = CMD_ARPEGGIO; break; + case 'K': command = CMD_VIBRATOVOL; break; + case 'L': command = CMD_TONEPORTAVOL; break; + case 'M': command = CMD_CHANNELVOLUME; break; + case 'N': command = CMD_CHANNELVOLSLIDE; break; + case 'O': command = CMD_OFFSET; break; + case 'P': command = CMD_PANNINGSLIDE; break; + case 'Q': command = CMD_RETRIG; break; + case 'R': command = CMD_TREMOLO; break; + case 'S': command = CMD_S3MCMDEX; break; + case 'T': command = CMD_TEMPO; break; + case 'U': command = CMD_FINEVIBRATO; break; + case 'V': command = CMD_GLOBALVOLUME; break; + case 'W': command = CMD_GLOBALVOLSLIDE; break; + case 'X': command = CMD_PANNING8; break; + case 'Y': command = CMD_PANBRELLO; break; + case 'Z': command = CMD_MIDI; break; + default: command = 0; + } + m->command = command; + m->param = param; +} + + +void CSoundFile::S3MSaveConvert(UINT *pcmd, UINT *pprm, BOOL bIT) const +//--------------------------------------------------------------------- +{ + UINT command = *pcmd; + UINT param = *pprm; + switch(command) + { + case CMD_SPEED: command = 'A'; break; + case CMD_POSITIONJUMP: command = 'B'; break; + case CMD_PATTERNBREAK: command = 'C'; if (!bIT) param = ((param / 10) << 4) + (param % 10); break; + case CMD_VOLUMESLIDE: command = 'D'; break; + case CMD_PORTAMENTODOWN: command = 'E'; if ((param >= 0xE0) && (m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM))) param = 0xDF; break; + case CMD_PORTAMENTOUP: command = 'F'; if ((param >= 0xE0) && (m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM))) param = 0xDF; break; + case CMD_TONEPORTAMENTO: command = 'G'; break; + case CMD_VIBRATO: command = 'H'; break; + case CMD_TREMOR: command = 'I'; break; + case CMD_ARPEGGIO: command = 'J'; break; + case CMD_VIBRATOVOL: command = 'K'; break; + case CMD_TONEPORTAVOL: command = 'L'; break; + case CMD_CHANNELVOLUME: command = 'M'; break; + case CMD_CHANNELVOLSLIDE: command = 'N'; break; + case CMD_OFFSET: command = 'O'; break; + case CMD_PANNINGSLIDE: command = 'P'; break; + case CMD_RETRIG: command = 'Q'; break; + case CMD_TREMOLO: command = 'R'; break; + case CMD_S3MCMDEX: command = 'S'; break; + case CMD_TEMPO: command = 'T'; break; + case CMD_FINEVIBRATO: command = 'U'; break; + case CMD_GLOBALVOLUME: command = 'V'; break; + case CMD_GLOBALVOLSLIDE: command = 'W'; break; + case CMD_PANNING8: + command = 'X'; + if ((bIT) && (m_nType != MOD_TYPE_IT) && (m_nType != MOD_TYPE_XM)) + { + if (param == 0xA4) { command = 'S'; param = 0x91; } else + if (param <= 0x80) { param <<= 1; if (param > 255) param = 255; } else + command = param = 0; + } else + if ((!bIT) && ((m_nType == MOD_TYPE_IT) || (m_nType == MOD_TYPE_XM))) + { + param >>= 1; + } + break; + case CMD_PANBRELLO: command = 'Y'; break; + case CMD_MIDI: command = 'Z'; break; + case CMD_XFINEPORTAUPDOWN: + if (param & 0x0F) switch(param & 0xF0) + { + case 0x10: command = 'F'; param = (param & 0x0F) | 0xE0; break; + case 0x20: command = 'E'; param = (param & 0x0F) | 0xE0; break; + case 0x90: command = 'S'; break; + default: command = param = 0; + } else command = param = 0; + break; + case CMD_MODCMDEX: + command = 'S'; + switch(param & 0xF0) + { + case 0x00: command = param = 0; break; + case 0x10: command = 'F'; param |= 0xF0; break; + case 0x20: command = 'E'; param |= 0xF0; break; + case 0x30: param = (param & 0x0F) | 0x10; break; + case 0x40: param = (param & 0x0F) | 0x30; break; + case 0x50: param = (param & 0x0F) | 0x20; break; + case 0x60: param = (param & 0x0F) | 0xB0; break; + case 0x70: param = (param & 0x0F) | 0x40; break; + case 0x90: command = 'Q'; param &= 0x0F; break; + case 0xA0: if (param & 0x0F) { command = 'D'; param = (param << 4) | 0x0F; } else command=param=0; break; + case 0xB0: if (param & 0x0F) { command = 'D'; param |= 0xF0; } else command=param=0; break; + } + break; + default: command = param = 0; + } + command &= ~0x40; + *pcmd = command; + *pprm = param; +} + + +BOOL CSoundFile::ReadS3M(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + UINT insnum,patnum,nins,npat; + DWORD insfile[128]; + WORD ptr[256]; + BYTE s[1024]; + DWORD dwMemPos; + BYTE insflags[128], inspack[128]; + S3MFILEHEADER psfh = *(S3MFILEHEADER *)lpStream; + + psfh.reserved1 = bswapLE16(psfh.reserved1); + psfh.ordnum = bswapLE16(psfh.ordnum); + psfh.insnum = bswapLE16(psfh.insnum); + psfh.patnum = bswapLE16(psfh.patnum); + psfh.flags = bswapLE16(psfh.flags); + psfh.cwtv = bswapLE16(psfh.cwtv); + psfh.version = bswapLE16(psfh.version); + psfh.scrm = bswapLE32(psfh.scrm); + psfh.special = bswapLE16(psfh.special); + + if ((!lpStream) || (dwMemLength <= sizeof(S3MFILEHEADER)+sizeof(S3MSAMPLESTRUCT)+64)) return FALSE; + if (psfh.scrm != 0x4D524353) return FALSE; + dwMemPos = 0x60; + m_nType = MOD_TYPE_S3M; + memset(m_szNames,0,sizeof(m_szNames)); + memcpy(m_szNames[0], psfh.name, 28); + // Speed + m_nDefaultSpeed = psfh.speed; + if (m_nDefaultSpeed < 1) m_nDefaultSpeed = 6; + if (m_nDefaultSpeed > 0x1F) m_nDefaultSpeed = 0x1F; + // Tempo + m_nDefaultTempo = psfh.tempo; + if (m_nDefaultTempo < 40) m_nDefaultTempo = 40; + if (m_nDefaultTempo > 240) m_nDefaultTempo = 240; + // Global Volume + m_nDefaultGlobalVolume = psfh.globalvol << 2; + if ((!m_nDefaultGlobalVolume) || (m_nDefaultGlobalVolume > 256)) m_nDefaultGlobalVolume = 256; + m_nSongPreAmp = psfh.mastervol & 0x7F; + // Channels + m_nChannels = 4; + for (UINT ich=0; ich<32; ich++) + { + ChnSettings[ich].nPan = 128; + ChnSettings[ich].nVolume = 64; + + ChnSettings[ich].dwFlags = CHN_MUTE; + if (psfh.channels[ich] != 0xFF) + { + m_nChannels = ich+1; + UINT b = psfh.channels[ich] & 0x0F; + ChnSettings[ich].nPan = (b & 8) ? 0xC0 : 0x40; + ChnSettings[ich].dwFlags = 0; + } + } + if (m_nChannels < 4) m_nChannels = 4; + if ((psfh.cwtv < 0x1320) || (psfh.flags & 0x40)) m_dwSongFlags |= SONG_FASTVOLSLIDES; + // Reading pattern order + UINT iord = psfh.ordnum; + if (iord<1) iord = 1; + if (iord > MAX_ORDERS) iord = MAX_ORDERS; + if (iord) + { + memcpy(Order, lpStream+dwMemPos, iord); + dwMemPos += iord; + } + if ((iord & 1) && (lpStream[dwMemPos] == 0xFF)) dwMemPos++; + // Reading file pointers + insnum = nins = psfh.insnum; + if (insnum >= MAX_SAMPLES) insnum = MAX_SAMPLES-1; + m_nSamples = insnum; + patnum = npat = psfh.patnum; + if (patnum > MAX_PATTERNS) patnum = MAX_PATTERNS; + memset(ptr, 0, sizeof(ptr)); + if (nins+npat) + { + memcpy(ptr, lpStream+dwMemPos, 2*(nins+npat)); + dwMemPos += 2*(nins+npat); + for (UINT j = 0; j < (nins+npat); ++j) { + ptr[j] = bswapLE16(ptr[j]); + } + if (psfh.panning_present == 252) + { + const BYTE *chnpan = lpStream+dwMemPos; + for (UINT i=0; i<32; i++) if (chnpan[i] & 0x20) + { + ChnSettings[i].nPan = ((chnpan[i] & 0x0F) << 4) + 8; + } + } + } + if (!m_nChannels) return TRUE; + // Reading instrument headers + memset(insfile, 0, sizeof(insfile)); + for (UINT iSmp=1; iSmp<=insnum; iSmp++) + { + UINT nInd = ((DWORD)ptr[iSmp-1])*16; + if ((!nInd) || (nInd + 0x50 > dwMemLength)) continue; + memcpy(s, lpStream+nInd, 0x50); + memcpy(Ins[iSmp].name, s+1, 12); + insflags[iSmp-1] = s[0x1F]; + inspack[iSmp-1] = s[0x1E]; + s[0x4C] = 0; + lstrcpy(m_szNames[iSmp], (LPCSTR)&s[0x30]); + if ((s[0]==1) && (s[0x4E]=='R') && (s[0x4F]=='S')) + { + UINT j = bswapLE32(*((LPDWORD)(s+0x10))); + if (j > MAX_SAMPLE_LENGTH) j = MAX_SAMPLE_LENGTH; + if (j < 2) j = 0; + Ins[iSmp].nLength = j; + j = bswapLE32(*((LPDWORD)(s+0x14))); + if (j >= Ins[iSmp].nLength) j = Ins[iSmp].nLength - 1; + Ins[iSmp].nLoopStart = j; + j = bswapLE32(*((LPDWORD)(s+0x18))); + if (j > MAX_SAMPLE_LENGTH) j = MAX_SAMPLE_LENGTH; + if (j < 2) j = 0; + if (j > Ins[iSmp].nLength) j = Ins[iSmp].nLength; + Ins[iSmp].nLoopEnd = j; + j = s[0x1C]; + if (j > 64) j = 64; + Ins[iSmp].nVolume = j << 2; + Ins[iSmp].nGlobalVol = 64; + if (s[0x1F]&1) Ins[iSmp].uFlags |= CHN_LOOP; + j = bswapLE32(*((LPDWORD)(s+0x20))); + if (!j) j = 8363; + if (j < 1024) j = 1024; + Ins[iSmp].nC4Speed = j; + insfile[iSmp] = ((DWORD)bswapLE16(*((LPWORD)(s+0x0E)))) << 4; + insfile[iSmp] += ((DWORD)(BYTE)s[0x0D]) << 20; + if (insfile[iSmp] > dwMemLength) insfile[iSmp] &= 0xFFFF; + if ((Ins[iSmp].nLoopStart >= Ins[iSmp].nLoopEnd) || (Ins[iSmp].nLoopEnd - Ins[iSmp].nLoopStart < 8)) + Ins[iSmp].nLoopStart = Ins[iSmp].nLoopEnd = 0; + Ins[iSmp].nPan = 0x80; + } + } + // Reading patterns + for (UINT iPat=0; iPat dwMemLength) continue; + WORD len = bswapLE16(*((WORD *)(lpStream+nInd))); + nInd += 2; + PatternSize[iPat] = 64; + PatternAllocSize[iPat] = 64; + if ((!len) || (nInd + len > dwMemLength - 6) + || ((Patterns[iPat] = AllocatePattern(64, m_nChannels)) == NULL)) continue; + LPBYTE src = (LPBYTE)(lpStream+nInd); + // Unpacking pattern + MODCOMMAND *p = Patterns[iPat]; + UINT row = 0; + UINT j = 0; + while (j < len) + { + BYTE b = src[j++]; + if (!b) + { + if (++row >= 64) break; + } else + { + UINT chn = b & 0x1F; + if (chn < m_nChannels) + { + MODCOMMAND *m = &p[row*m_nChannels+chn]; + if (b & 0x20) + { + m->note = src[j++]; + if (m->note < 0xF0) m->note = (m->note & 0x0F) + 12*(m->note >> 4) + 13; + else if (m->note == 0xFF) m->note = 0; + m->instr = src[j++]; + } + if (b & 0x40) + { + UINT vol = src[j++]; + if ((vol >= 128) && (vol <= 192)) + { + vol -= 128; + m->volcmd = VOLCMD_PANNING; + } else + { + if (vol > 64) vol = 64; + m->volcmd = VOLCMD_VOLUME; + } + m->vol = vol; + } + if (b & 0x80) + { + m->command = src[j++]; + m->param = src[j++]; + if (m->command) S3MConvert(m, FALSE); + } + } else + { + if (b & 0x20) j += 2; + if (b & 0x40) j++; + if (b & 0x80) j += 2; + } + if (j >= len) break; + } + } + } + // Reading samples + for (UINT iRaw=1; iRaw<=insnum; iRaw++) if ((Ins[iRaw].nLength) && (insfile[iRaw])) + { + UINT flags = (psfh.version == 1) ? RS_PCM8S : RS_PCM8U; + if (insflags[iRaw-1] & 4) flags += 5; + if (insflags[iRaw-1] & 2) flags |= RSF_STEREO; + if (inspack[iRaw-1] == 4) flags = RS_ADPCM4; + dwMemPos = insfile[iRaw]; + dwMemPos += ReadSample(&Ins[iRaw], flags, (LPSTR)(lpStream + dwMemPos), dwMemLength - dwMemPos); + } + m_nMinPeriod = 64; + m_nMaxPeriod = 32767; + if (psfh.flags & 0x10) m_dwSongFlags |= SONG_AMIGALIMITS; + return TRUE; +} + + +#ifndef MODPLUG_NO_FILESAVE +#pragma warning(disable:4100) + +static BYTE S3MFiller[16] = +{ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 +}; + + +BOOL CSoundFile::SaveS3M(diskwriter_driver_t *fp, UINT nPacking) +//---------------------------------------------------------- +{ + BYTE header[0x60]; + UINT nbo,nbi,nbp,i; + WORD patptr[128]; + WORD insptr[128]; + BYTE buffer[5*1024]; + S3MSAMPLESTRUCT insex[128]; + + if ((!m_nChannels) || (!fp)) return FALSE; + // Writing S3M header + memset(header, 0, sizeof(header)); + memset(insex, 0, sizeof(insex)); + memcpy(header, m_szNames[0], 0x1C); + header[0x1B] = 0; + header[0x1C] = 0x1A; + header[0x1D] = 0x10; + nbo = (GetNumPatterns()); + if (nbo == 0) + nbo = 2; + else if (nbo & 1) + nbo++; + header[0x20] = nbo & 0xFF; + header[0x21] = nbo >> 8; + nbi = m_nSamples; + if (nbi > 99) nbi = 99; + header[0x22] = nbi & 0xFF; + header[0x23] = nbi >> 8; + nbp = 0; + for (i=0; Patterns[i]; i++) { nbp = i+1; if (nbp >= MAX_PATTERNS) break; } + for (i=0; i= nbp)) nbp = Order[i] + 1; + header[0x24] = nbp & 0xFF; + header[0x25] = nbp >> 8; + if (m_dwSongFlags & SONG_FASTVOLSLIDES) header[0x26] |= 0x40; + if ((m_nMaxPeriod < 20000) || (m_dwSongFlags & SONG_AMIGALIMITS)) header[0x26] |= 0x10; + header[0x28] = 0x20; + header[0x29] = 0x13; + header[0x2A] = 0x02; // Version = 1 => Signed samples + header[0x2B] = 0x00; + header[0x2C] = 'S'; + header[0x2D] = 'C'; + header[0x2E] = 'R'; + header[0x2F] = 'M'; + header[0x30] = m_nDefaultGlobalVolume >> 2; + header[0x31] = m_nDefaultSpeed; + header[0x32] = m_nDefaultTempo; + header[0x33] = ((m_nSongPreAmp < 0x20) ? 0x20 : m_nSongPreAmp) | 0x80; // Stereo + header[0x35] = 0xFC; + for (i=0; i<32; i++) + { + if (i < m_nChannels) + { + UINT tmp = (i & 0x0F) >> 1; + header[0x40+i] = (i & 0x10) | ((i & 1) ? 8+tmp : tmp); + } else header[0x40+i] = 0xFF; + } + fp->o(fp, (const unsigned char *)header, 0x60); + fp->o(fp, (const unsigned char *)Order, nbo); + memset(patptr, 0, sizeof(patptr)); + memset(insptr, 0, sizeof(insptr)); + UINT ofs0 = 0x60 + nbo; + UINT ofs1 = ((0x60 + nbo + nbi*2 + nbp*2 + 15) & 0xFFF0); + UINT ofs = ofs1; + if (header[0x35] == 0xFC) { + ofs += 0x20; + ofs1 += 0x20; + } + + for (i=0; io(fp, (const unsigned char *)insptr, nbi*2); + fp->o(fp, (const unsigned char *)patptr, nbp*2); + if (header[0x35] == 0xFC) + { + BYTE chnpan[32]; + for (i=0; i<32; i++) + { + chnpan[i] = 0x20 | (ChnSettings[i].nPan >> 4); + } + fp->o(fp, (const unsigned char *)chnpan, 0x20); + } + if ((nbi*2+nbp*2) & 0x0F) + { + fp->o(fp, (const unsigned char *)S3MFiller, 0x10 - ((nbi*2+nbp*2) & 0x0F)); + } + fp->l(fp, ofs1); + ofs1 = fp->pos; + fp->o(fp, (const unsigned char *)insex, nbi*0x50); + // Packing patterns + ofs += nbi*0x50; + fp->l(fp,ofs); + for (i=0; inote; + UINT volcmd = m->volcmd; + UINT vol = m->vol; + UINT command = m->command; + UINT param = m->param; + UINT inst = m->instr; + + if (m_dwSongFlags & SONG_INSTRUMENTMODE + && note && inst) { + UINT nn = Headers[inst]->Keyboard[note]; + UINT nm = Headers[inst]->NoteMap[note]; + /* translate on save */ + note = nm; + inst = nn; + } + + + if ((note) || (inst)) b |= 0x20; + if (!note) note = 0xFF; else + if (note >= 0xFE) note = 0xFE; else + if (note < 13) note = 0; else note -= 13; + if (note < 0xFE) note = (note % 12) + ((note / 12) << 4); + if (command == CMD_VOLUME) + { + command = 0; + if (param > 64) param = 64; + volcmd = VOLCMD_VOLUME; + vol = param; + } + if (volcmd == VOLCMD_VOLUME) b |= 0x40; else + if (volcmd == VOLCMD_PANNING) { vol |= 0x80; b |= 0x40; } + if (command) + { + S3MSaveConvert(&command, ¶m, FALSE); + if (command) b |= 0x80; + } + if (b & 0xE0) + { + buffer[len++] = b; + if (b & 0x20) + { + buffer[len++] = note; + buffer[len++] = inst; + } + if (b & 0x40) + { + buffer[len++] = vol; + } + if (b & 0x80) + { + buffer[len++] = command; + buffer[len++] = param; + } + if (len > sizeof(buffer) - 20) break; + } + } + buffer[len++] = 0; + if (len > sizeof(buffer) - 20) break; + } + } + buffer[0] = (len - 2) & 0xFF; + buffer[1] = (len - 2) >> 8; + len = (len+15) & (~0x0F); + + fp->o(fp, (const unsigned char *)buffer, len); + ofs += len; + } + // Writing samples + for (i=1; i<=nbi; i++) + { + MODINSTRUMENT *pins = &Ins[i]; + memcpy(insex[i-1].dosname, pins->name, 12); + memcpy(insex[i-1].name, m_szNames[i], 28); + memcpy(insex[i-1].scrs, "SCRS", 4); + insex[i-1].hmem = (BYTE)((DWORD)ofs >> 20); + insex[i-1].memseg = bswapLE16((WORD)((DWORD)ofs >> 4)); + if (pins->pSample) + { + insex[i-1].type = 1; + insex[i-1].length = bswapLE32(pins->nLength); + insex[i-1].loopbegin = bswapLE32(pins->nLoopStart); + insex[i-1].loopend = bswapLE32(pins->nLoopEnd); + insex[i-1].vol = pins->nVolume / 4; + insex[i-1].flags = (pins->uFlags & CHN_LOOP) ? 1 : 0; + if (pins->nC4Speed) + insex[i-1].finetune = bswapLE32(pins->nC4Speed); + else + insex[i-1].finetune = bswapLE32(TransposeToFrequency(pins->RelativeTone, pins->nFineTune)); + UINT flags = RS_PCM8U; +#ifndef NO_PACKING + if (nPacking) + { + if ((!(pins->uFlags & (CHN_16BIT|CHN_STEREO))) + && (CanPackSample((char *)pins->pSample, pins->nLength, nPacking))) + { + insex[i-1].pack = 4; + flags = RS_ADPCM4; + } + } else +#endif // NO_PACKING + { + if (pins->uFlags & CHN_16BIT) + { + insex[i-1].flags |= 4; + flags = RS_PCM16U; + } + if (pins->uFlags & CHN_STEREO) + { + insex[i-1].flags |= 2; + flags = (pins->uFlags & CHN_16BIT) ? RS_STPCM16U : RS_STPCM8U; + } + } + DWORD len = WriteSample(fp, pins, flags); + if (len & 0x0F) + { + fp->o(fp, (const unsigned char *)S3MFiller, 0x10 - (len & 0x0F)); + } + ofs += (len + 15) & (~0x0F); + } else { + insex[i-1].length = 0; + } + } + // Updating parapointers + fp->l(fp, ofs0); + fp->o(fp, (const unsigned char *)insptr, nbi*2); + fp->o(fp, (const unsigned char *)patptr, nbp*2); + fp->l(fp, ofs1); + fp->o(fp, (const unsigned char *)insex, 0x50*nbi); + return TRUE; +} + +#pragma warning(default:4100) +#endif // MODPLUG_NO_FILESAVE + diff --git a/modplug/load_stm.cpp b/modplug/load_stm.cpp new file mode 100644 index 000000000..4d3d03b69 --- /dev/null +++ b/modplug/load_stm.cpp @@ -0,0 +1,187 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#pragma pack(1) + +typedef struct tagSTMNOTE +{ + BYTE note; + BYTE insvol; + BYTE volcmd; + BYTE cmdinf; +} STMNOTE; + + +// Raw STM sampleinfo struct: +typedef struct tagSTMSAMPLE +{ + CHAR filename[14]; // Can't have long comments - just filename comments :) + WORD reserved; // ISA in memory when in ST 2 + WORD length; // Sample length + WORD loopbeg; // Loop start point + WORD loopend; // Loop end point + BYTE volume; // Volume + BYTE reserved2; // More reserved crap + WORD c2spd; // Good old c2spd + BYTE reserved3[6]; // Yet more of PSi's reserved crap +} STMSAMPLE; + + +// Raw STM header struct: +typedef struct tagSTMHEADER +{ + char songname[20]; // changed from CHAR + char trackername[8]; // !SCREAM! for ST 2.xx // changed from CHAR + CHAR unused; // 0x1A + CHAR filetype; // 1=song, 2=module (only 2 is supported, of course) :) + CHAR ver_major; // Like 2 + CHAR ver_minor; // "ditto" + BYTE inittempo; // initspeed= stm inittempo>>4 + BYTE numpat; // number of patterns + BYTE globalvol; // <- WoW! a RiGHT TRiANGLE =8*) + BYTE reserved[13]; // More of PSi's internal crap + STMSAMPLE sample[31]; // STM sample data + BYTE patorder[128]; // Docs say 64 - actually 128 +} STMHEADER; + +#pragma pack() + + + +BOOL CSoundFile::ReadSTM(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + STMHEADER *phdr = (STMHEADER *)lpStream; + DWORD dwMemPos = 0; + + if ((!lpStream) || (dwMemLength < sizeof(STMHEADER))) return FALSE; + if ((phdr->filetype != 2) || (phdr->unused != 0x1A) + || ((strnicmp(phdr->trackername, "!SCREAM!", 8)) + && (strnicmp(phdr->trackername, "BMOD2STM", 8)))) return FALSE; + memcpy(m_szNames[0], phdr->songname, 20); + // Read STM header + m_nType = MOD_TYPE_STM; + m_nSamples = 31; + m_nChannels = 4; + m_nInstruments = 0; + m_nMinPeriod = 64; + m_nMaxPeriod = 0x7FFF; + m_nDefaultSpeed = phdr->inittempo >> 4; + if (m_nDefaultSpeed < 1) m_nDefaultSpeed = 1; + m_nDefaultTempo = 125; + m_nDefaultGlobalVolume = phdr->globalvol << 2; + if (m_nDefaultGlobalVolume > 256) m_nDefaultGlobalVolume = 256; + memcpy(Order, phdr->patorder, 128); + // Setting up channels + for (UINT nSet=0; nSet<4; nSet++) + { + ChnSettings[nSet].dwFlags = 0; + ChnSettings[nSet].nVolume = 64; + ChnSettings[nSet].nPan = (nSet & 1) ? 0x40 : 0xC0; + } + // Reading samples + for (UINT nIns=0; nIns<31; nIns++) + { + MODINSTRUMENT *pIns = &Ins[nIns+1]; + STMSAMPLE *pStm = &phdr->sample[nIns]; // STM sample data + memcpy(pIns->name, pStm->filename, 13); + memcpy(m_szNames[nIns+1], pStm->filename, 12); + pIns->nC4Speed = pStm->c2spd; + pIns->nGlobalVol = 64; + pIns->nVolume = pStm->volume << 2; + if (pIns->nVolume > 256) pIns->nVolume = 256; + pIns->nLength = pStm->length; + if ((pIns->nLength < 2) || (!pIns->nVolume)) pIns->nLength = 0; + pIns->nLoopStart = pStm->loopbeg; + pIns->nLoopEnd = pStm->loopend; + if ((pIns->nLoopEnd > pIns->nLoopStart) && (pIns->nLoopEnd != 0xFFFF)) pIns->uFlags |= CHN_LOOP; + } + dwMemPos = sizeof(STMHEADER); + for (UINT nOrd=0; nOrd= 99) Order[nOrd] = 0xFF; + UINT nPatterns = phdr->numpat; + for (UINT nPat=0; nPat dwMemLength) return TRUE; + PatternSize[nPat] = 64; + PatternAllocSize[nPat] = 64; + if ((Patterns[nPat] = AllocatePattern(64, m_nChannels)) == NULL) return TRUE; + MODCOMMAND *m = Patterns[nPat]; + STMNOTE *p = (STMNOTE *)(lpStream + dwMemPos); + for (UINT n=0; n<64*4; n++, p++, m++) + { + UINT note,ins,vol,cmd; + // extract the various information from the 4 bytes that + // make up a single note + note = p->note; + ins = p->insvol >> 3; + vol = (p->insvol & 0x07) + (p->volcmd >> 1); + cmd = p->volcmd & 0x0F; + if ((ins) && (ins < 32)) m->instr = ins; + // special values of [SBYTE0] are handled here -> + // we have no idea if these strange values will ever be encountered + // but it appears as though stms sound correct. + if ((note == 0xFE) || (note == 0xFC)) m->note = 0xFE; else + // if note < 251, then all three bytes are stored in the file + if (note < 0xFC) m->note = (note >> 4)*12 + (note&0xf) + 37; + if (vol <= 64) { m->volcmd = VOLCMD_VOLUME; m->vol = vol; } + m->param = p->cmdinf; + switch(cmd) + { + // Axx set speed to xx + case 1: m->command = CMD_SPEED; m->param >>= 4; break; + // Bxx position jump + case 2: m->command = CMD_POSITIONJUMP; break; + // Cxx patternbreak to row xx + case 3: m->command = CMD_PATTERNBREAK; m->param = (m->param & 0xF0) * 10 + (m->param & 0x0F); break; + // Dxy volumeslide + case 4: m->command = CMD_VOLUMESLIDE; break; + // Exy toneslide down + case 5: m->command = CMD_PORTAMENTODOWN; break; + // Fxy toneslide up + case 6: m->command = CMD_PORTAMENTOUP; break; + // Gxx Tone portamento,speed xx + case 7: m->command = CMD_TONEPORTAMENTO; break; + // Hxy vibrato + case 8: m->command = CMD_VIBRATO; break; + // Ixy tremor, ontime x, offtime y + case 9: m->command = CMD_TREMOR; break; + // Jxy arpeggio + case 10: m->command = CMD_ARPEGGIO; break; + // Kxy Dual command H00 & Dxy + case 11: m->command = CMD_VIBRATOVOL; break; + // Lxy Dual command G00 & Dxy + case 12: m->command = CMD_TONEPORTAVOL; break; + // Xxx amiga command 8xx + case 0x18: m->command = CMD_PANNING8; break; + default: + m->command = m->param = 0; + } + } + dwMemPos += 64*4*4; + } + // Reading Samples + for (UINT nSmp=1; nSmp<=31; nSmp++) + { + MODINSTRUMENT *pIns = &Ins[nSmp]; + dwMemPos = (dwMemPos + 15) & (~15); + if (pIns->nLength) + { + UINT nPos = ((UINT)phdr->sample[nSmp-1].reserved) << 4; + if ((nPos >= sizeof(STMHEADER)) && (nPos+pIns->nLength <= dwMemLength)) dwMemPos = nPos; + if (dwMemPos < dwMemLength) + { + dwMemPos += ReadSample(pIns, RS_PCM8S, (LPSTR)(lpStream+dwMemPos),dwMemLength-dwMemPos); + } + } + } + return TRUE; +} + diff --git a/modplug/load_ult.cpp b/modplug/load_ult.cpp new file mode 100644 index 000000000..9025c1978 --- /dev/null +++ b/modplug/load_ult.cpp @@ -0,0 +1,223 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//#pragma warning(disable:4244) + +#define ULT_16BIT 0x04 +#define ULT_LOOP 0x08 +#define ULT_BIDI 0x10 + +#pragma pack(1) + +// Raw ULT header struct: +typedef struct tagULTHEADER +{ + char id[15]; // changed from CHAR + char songtitle[32]; // changed from CHAR + BYTE reserved; +} ULTHEADER; + + +// Raw ULT sampleinfo struct: +typedef struct tagULTSAMPLE +{ + CHAR samplename[32]; + CHAR dosname[12]; + LONG loopstart; + LONG loopend; + LONG sizestart; + LONG sizeend; + BYTE volume; + BYTE flags; + WORD finetune; +} ULTSAMPLE; + +#pragma pack() + + +BOOL CSoundFile::ReadUlt(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + ULTHEADER *pmh = (ULTHEADER *)lpStream; + ULTSAMPLE *pus; + UINT nos, nop; + DWORD dwMemPos = 0; + + // try to read module header + if ((!lpStream) || (dwMemLength < 0x100)) return FALSE; + if (strncmp(pmh->id,"MAS_UTrack_V00",14)) return FALSE; + // Warning! Not supported ULT format, trying anyway + // if ((pmh->id[14] < '1') || (pmh->id[14] > '4')) return FALSE; + m_nType = MOD_TYPE_ULT; + m_nDefaultSpeed = 6; + m_nDefaultTempo = 125; + memcpy(m_szNames[0], pmh->songtitle, 32); + // read songtext + dwMemPos = sizeof(ULTHEADER); + if ((pmh->reserved) && (dwMemPos + pmh->reserved * 32 < dwMemLength)) + { + UINT len = pmh->reserved * 32; + m_lpszSongComments = new char[len + 1 + pmh->reserved]; + if (m_lpszSongComments) + { + for (UINT l=0; lreserved; l++) + { + memcpy(m_lpszSongComments+l*33, lpStream+dwMemPos+l*32, 32); + m_lpszSongComments[l*33+32] = 0x0D; + } + m_lpszSongComments[len] = 0; + } + dwMemPos += len; + } + if (dwMemPos >= dwMemLength) return TRUE; + nos = lpStream[dwMemPos++]; + m_nSamples = nos; + if (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1; + UINT smpsize = 64; + if (pmh->id[14] >= '4') smpsize += 2; + if (dwMemPos + nos*smpsize + 256 + 2 > dwMemLength) return TRUE; + for (UINT ins=1; ins<=nos; ins++, dwMemPos+=smpsize) if (ins<=m_nSamples) + { + pus = (ULTSAMPLE *)(lpStream+dwMemPos); + MODINSTRUMENT *pins = &Ins[ins]; + memcpy(m_szNames[ins], pus->samplename, 32); + memcpy(pins->name, pus->dosname, 12); + pins->nLoopStart = pus->loopstart; + pins->nLoopEnd = pus->loopend; + pins->nLength = pus->sizeend - pus->sizestart; + pins->nVolume = pus->volume; + pins->nGlobalVol = 64; + pins->nC4Speed = 8363; + if (pmh->id[14] >= '4') + { + pins->nC4Speed = pus->finetune; + } + if (pus->flags & ULT_LOOP) pins->uFlags |= CHN_LOOP; + if (pus->flags & ULT_BIDI) pins->uFlags |= CHN_PINGPONGLOOP; + if (pus->flags & ULT_16BIT) + { + pins->uFlags |= CHN_16BIT; + pins->nLoopStart >>= 1; + pins->nLoopEnd >>= 1; + } + } + memcpy(Order, lpStream+dwMemPos, 256); + dwMemPos += 256; + m_nChannels = lpStream[dwMemPos] + 1; + nop = lpStream[dwMemPos+1] + 1; + dwMemPos += 2; + if (m_nChannels > 32) m_nChannels = 32; + // Default channel settings + for (UINT nSet=0; nSetid[14]>='3') + { + if (dwMemPos + m_nChannels > dwMemLength) return TRUE; + for(UINT t=0; t 256) ChnSettings[t].nPan = 256; + } + } + // Allocating Patterns + for (UINT nAllocPat=0; nAllocPat dwMemLength) return TRUE; + UINT rep = 1; + UINT note = lpStream[dwMemPos++]; + if (note == 0xFC) + { + rep = lpStream[dwMemPos]; + note = lpStream[dwMemPos+1]; + dwMemPos += 2; + } + UINT instr = lpStream[dwMemPos++]; + UINT eff = lpStream[dwMemPos++]; + UINT dat1 = lpStream[dwMemPos++]; + UINT dat2 = lpStream[dwMemPos++]; + UINT cmd1 = eff & 0x0F; + UINT cmd2 = eff >> 4; + if (cmd1 == 0x0C) dat1 >>= 2; else + if (cmd1 == 0x0B) { cmd1 = dat1 = 0; } + if (cmd2 == 0x0C) dat2 >>= 2; else + if (cmd2 == 0x0B) { cmd2 = dat2 = 0; } + while ((rep != 0) && (row < 64)) + { + if (pat) + { + pat->instr = instr; + if (note) pat->note = note + 36; + if (cmd1 | dat1) + { + if (cmd1 == 0x0C) + { + pat->volcmd = VOLCMD_VOLUME; + pat->vol = dat1; + } else + { + pat->command = cmd1; + pat->param = dat1; + ConvertModCommand(pat); + } + } + if (cmd2 == 0x0C) + { + pat->volcmd = VOLCMD_VOLUME; + pat->vol = dat2; + } else + if ((cmd2 | dat2) && (!pat->command)) + { + pat->command = cmd2; + pat->param = dat2; + ConvertModCommand(pat); + } + pat += m_nChannels; + } + row++; + rep--; + } + } + } + } + // Reading Instruments + for (UINT smp=1; smp<=m_nSamples; smp++) if (Ins[smp].nLength) + { + if (dwMemPos >= dwMemLength) return TRUE; + UINT flags = (Ins[smp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S; + dwMemPos += ReadSample(&Ins[smp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength - dwMemPos); + } + return TRUE; +} + diff --git a/modplug/load_umx.cpp b/modplug/load_umx.cpp new file mode 100644 index 000000000..8913c740f --- /dev/null +++ b/modplug/load_umx.cpp @@ -0,0 +1,53 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#define MODMAGIC_OFFSET (20+31*30+130) + + +BOOL CSoundFile::ReadUMX(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + if ((!lpStream) || (dwMemLength < 0x800)) return FALSE; + // Rip Mods from UMX + if ((bswapLE32(*((DWORD *)(lpStream+0x20))) < dwMemLength) + && (bswapLE32(*((DWORD *)(lpStream+0x18))) <= dwMemLength - 0x10) + && (bswapLE32(*((DWORD *)(lpStream+0x18))) >= dwMemLength - 0x200)) + { + for (UINT uscan=0x40; uscan<0x500; uscan++) + { + DWORD dwScan = bswapLE32(*((DWORD *)(lpStream+uscan))); + // IT + if (dwScan == 0x4D504D49) + { + DWORD dwRipOfs = uscan; + return ReadIT(lpStream + dwRipOfs, dwMemLength - dwRipOfs); + } + // S3M + if (dwScan == 0x4D524353) + { + DWORD dwRipOfs = uscan - 44; + return ReadS3M(lpStream + dwRipOfs, dwMemLength - dwRipOfs); + } + // XM + if (!strnicmp((LPCSTR)(lpStream+uscan), "Extended Module", 15)) + { + DWORD dwRipOfs = uscan; + return ReadXM(lpStream + dwRipOfs, dwMemLength - dwRipOfs); + } + // MOD + if ((uscan > MODMAGIC_OFFSET) && (dwScan == 0x2e4b2e4d)) + { + DWORD dwRipOfs = uscan - MODMAGIC_OFFSET; + return ReadMod(lpStream+dwRipOfs, dwMemLength-dwRipOfs); + } + } + } + return FALSE; +} + diff --git a/modplug/load_wav.cpp b/modplug/load_wav.cpp new file mode 100644 index 000000000..f5ee84a3c --- /dev/null +++ b/modplug/load_wav.cpp @@ -0,0 +1,248 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#ifndef WAVE_FORMAT_EXTENSIBLE +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif + +///////////////////////////////////////////////////////////// +// WAV file support + +BOOL CSoundFile::ReadWav(const BYTE *lpStream, DWORD dwMemLength) +//--------------------------------------------------------------- +{ + DWORD dwMemPos = 0; + WAVEFILEHEADER phdr; + WAVEFORMATHEADER pfmt; + + if ((!lpStream) + || (dwMemLength < (DWORD)(sizeof(WAVEFORMATHEADER)+sizeof(WAVEFILEHEADER)))) + return FALSE; + + memcpy(&phdr, lpStream, sizeof(phdr)); + memcpy(&pfmt, lpStream+sizeof(phdr), sizeof(pfmt)); + + phdr.id_RIFF = bswapLE32(phdr.id_RIFF); + phdr.filesize = bswapLE32(phdr.filesize); + phdr.id_WAVE = bswapLE32(phdr.id_WAVE); + + pfmt.id_fmt = bswapLE32(pfmt.id_fmt); + pfmt.hdrlen = bswapLE32(pfmt.hdrlen); + pfmt.format = bswapLE16(pfmt.format); + pfmt.channels = bswapLE16(pfmt.channels); + pfmt.freqHz = bswapLE32(pfmt.freqHz); + pfmt.bytessec = bswapLE32(pfmt.bytessec); + pfmt.samplesize = bswapLE16(pfmt.samplesize); + pfmt.bitspersample = bswapLE16(pfmt.bitspersample); + + if ((phdr.id_RIFF != IFFID_RIFF) || (phdr.id_WAVE != IFFID_WAVE) + || (pfmt.id_fmt != IFFID_fmt)) return FALSE; + + dwMemPos = sizeof(WAVEFILEHEADER) + 8 + pfmt.hdrlen; + + if ((dwMemPos + 8 >= dwMemLength) + || ((pfmt.format != WAVE_FORMAT_PCM) && (pfmt.format != WAVE_FORMAT_EXTENSIBLE)) + || (pfmt.channels > 4) + || (!pfmt.channels) + || (!pfmt.freqHz) + || (pfmt.bitspersample & 7) + || (pfmt.bitspersample < 8) + || (pfmt.bitspersample > 32)) return FALSE; + + WAVEDATAHEADER pdata; + + for (;;) + { + memcpy(&pdata, lpStream+dwMemPos, sizeof(pdata)); + pdata.id_data = bswapLE32(pdata.id_data); + pdata.length = bswapLE32(pdata.length); + + if (pdata.id_data == IFFID_data) break; + dwMemPos += pdata.length + 8; + if (dwMemPos + 8 >= dwMemLength) return FALSE; + } + m_nType = MOD_TYPE_WAV; + m_nSamples = 0; + m_nInstruments = 0; + m_nChannels = 4; + m_nDefaultSpeed = 8; + m_nDefaultTempo = 125; + m_dwSongFlags |= SONG_LINEARSLIDES; // For no resampling + Order[0] = 0; + Order[1] = 0xFF; + PatternSize[0] = PatternSize[1] = 64; + PatternAllocSize[0] = PatternAllocSize[1] = 64; + if ((Patterns[0] = AllocatePattern(64, 4)) == NULL) return TRUE; + if ((Patterns[1] = AllocatePattern(64, 4)) == NULL) return TRUE; + UINT samplesize = (pfmt.channels * pfmt.bitspersample) >> 3; + UINT len = pdata.length, bytelen; + if (dwMemPos + len > dwMemLength - 8) len = dwMemLength - dwMemPos - 8; + len /= samplesize; + bytelen = len; + if (pfmt.bitspersample >= 16) bytelen *= 2; + if (len > MAX_SAMPLE_LENGTH) len = MAX_SAMPLE_LENGTH; + if (!len) return TRUE; + // Setting up module length + DWORD dwTime = ((len * 50) / pfmt.freqHz) + 1; + DWORD framesperrow = (dwTime + 63) / 63; + if (framesperrow < 4) framesperrow = 4; + UINT norders = 1; + while (framesperrow >= 0x20) + { + Order[norders++] = 1; + Order[norders] = 0xFF; + framesperrow = (dwTime + (64 * norders - 1)) / (64 * norders); + if (norders >= MAX_ORDERS-1) break; + } + m_nDefaultSpeed = framesperrow; + for (UINT iChn=0; iChn<4; iChn++) + { + ChnSettings[iChn].nPan = (iChn & 1) ? 256 : 0; + ChnSettings[iChn].nVolume = 64; + ChnSettings[iChn].dwFlags = 0; + } + // Setting up speed command + MODCOMMAND *pcmd = Patterns[0]; + pcmd[0].command = CMD_SPEED; + pcmd[0].param = (BYTE)m_nDefaultSpeed; + pcmd[0].note = 5*12+1; + pcmd[0].instr = 1; + pcmd[1].note = pcmd[0].note; + pcmd[1].instr = pcmd[0].instr; + m_nSamples = pfmt.channels; + // Support for Multichannel Wave + for (UINT nChn=0; nChnnLength = len; + pins->nC4Speed = pfmt.freqHz; + pins->nVolume = 256; + pins->nPan = 128; + pins->nGlobalVol = 64; + pins->uFlags = (WORD)((pfmt.bitspersample >= 16) ? CHN_16BIT : 0); + pins->uFlags |= CHN_PANNING; + if (m_nSamples > 1) + { + switch(nChn) + { + case 0: pins->nPan = 0; break; + case 1: pins->nPan = 256; break; + case 2: pins->nPan = (WORD)((m_nSamples == 3) ? 128 : 64); pcmd[nChn].command = CMD_S3MCMDEX; pcmd[nChn].param = 0x91; break; + case 3: pins->nPan = 192; pcmd[nChn].command = CMD_S3MCMDEX; pcmd[nChn].param = 0x91; break; + default: pins->nPan = 128; break; + } + } + if ((pins->pSample = AllocateSample(bytelen+8)) == NULL) return TRUE; + if (pfmt.bitspersample >= 16) + { + int slsize = pfmt.bitspersample >> 3; + signed short *p = (signed short *)pins->pSample; + signed char *psrc = (signed char *)(lpStream+dwMemPos+8+nChn*slsize+slsize-2); + for (UINT i=0; ipSample; + signed char *psrc = (signed char *)(lpStream+dwMemPos+8+nChn); + for (UINT i=0; i dwBytes)) return FALSE; + nPos = 0; + while ((nPos < nLen) && (dwBytes > 4)) + { + int nIndex; + value = bswapLE16(*((short int *)psrc)); + nIndex = bswapLE16((short int)psrc[2]); + psrc += 4; + dwBytes -= 4; + pdest[nPos++] = (short int)value; + for (UINT i=0; ((i<(pkBlkAlign-4)*2) && (nPos < nLen) && (dwBytes)); i++) + { + BYTE delta; + if (i & 1) + { + delta = (BYTE)(((*(psrc++)) >> 4) & 0x0F); + dwBytes--; + } else + { + delta = (BYTE)((*psrc) & 0x0F); + } + int v = gIMAUnpackTable[nIndex % 90] >> 3; + if (delta & 1) v += gIMAUnpackTable[nIndex] >> 2; + if (delta & 2) v += gIMAUnpackTable[nIndex] >> 1; + if (delta & 4) v += gIMAUnpackTable[nIndex]; + if (delta & 8) value -= v; else value += v; + nIndex += gIMAIndexTab[delta & 7]; + if (nIndex < 0) nIndex = 0; else + if (nIndex > 88) nIndex = 88; + if (value > 32767) value = 32767; else + if (value < -32768) value = -32768; + pdest[nPos++] = (short int)value; + } + } + return TRUE; +} + + + diff --git a/modplug/load_xm.cpp b/modplug/load_xm.cpp new file mode 100644 index 000000000..7a8dc6230 --- /dev/null +++ b/modplug/load_xm.cpp @@ -0,0 +1,924 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include "stdafx.h" +#include "sndfile.h" + +//////////////////////////////////////////////////////// +// FastTracker II XM file support + +#ifdef MSC_VER +#pragma warning(disable:4244) +#endif + +#pragma pack(1) +typedef struct tagXMFILEHEADER +{ + DWORD size; + WORD norder; + WORD restartpos; + WORD channels; + WORD patterns; + WORD instruments; + WORD flags; + WORD speed; + WORD tempo; + BYTE order[256]; +} XMFILEHEADER; + + +typedef struct tagXMINSTRUMENTHEADER +{ + DWORD size; + CHAR name[22]; + BYTE type; + BYTE samples; + BYTE samplesh; +} XMINSTRUMENTHEADER; + + +typedef struct tagXMSAMPLEHEADER +{ + DWORD shsize; + BYTE snum[96]; + WORD venv[24]; + WORD penv[24]; + BYTE vnum, pnum; + BYTE vsustain, vloops, vloope, psustain, ploops, ploope; + BYTE vtype, ptype; + BYTE vibtype, vibsweep, vibdepth, vibrate; + WORD volfade; + WORD res; + BYTE reserved1[20]; +} XMSAMPLEHEADER; + +typedef struct tagXMSAMPLESTRUCT +{ + DWORD samplen; + DWORD loopstart; + DWORD looplen; + BYTE vol; + signed char finetune; + BYTE type; + BYTE pan; + signed char relnote; + BYTE res; + char name[22]; +} XMSAMPLESTRUCT; +#pragma pack() + + +BOOL CSoundFile::ReadXM(const BYTE *lpStream, DWORD dwMemLength) +//-------------------------------------------------------------- +{ + XMSAMPLEHEADER xmsh; + XMSAMPLESTRUCT xmss; + DWORD dwMemPos, dwHdrSize; + WORD norders=0, restartpos=0, channels=0, patterns=0, instruments=0; + WORD xmflags=0, deftempo=125, defspeed=6; + BOOL InstUsed[256]; + BYTE channels_used[MAX_CHANNELS]; + BYTE pattern_map[256]; + BOOL samples_used[MAX_SAMPLES]; + UINT unused_samples; + + m_nChannels = 0; + if ((!lpStream) || (dwMemLength < 0x200)) return FALSE; + if (strnicmp((LPCSTR)lpStream, "Extended Module", 15)) return FALSE; + + memcpy(m_szNames[0], lpStream+17, 20); + dwHdrSize = bswapLE32(*((DWORD *)(lpStream+60))); + norders = bswapLE16(*((WORD *)(lpStream+64))); + if ((!norders) || (norders > MAX_ORDERS)) return FALSE; + restartpos = bswapLE16(*((WORD *)(lpStream+66))); + channels = bswapLE16(*((WORD *)(lpStream+68))); + if ((!channels) || (channels > 64)) return FALSE; + m_nType = MOD_TYPE_XM; + m_nMinPeriod = 27; + m_nMaxPeriod = 54784; + m_nChannels = channels; + if (restartpos < norders) m_nRestartPos = restartpos; + patterns = bswapLE16(*((WORD *)(lpStream+70))); + if (patterns > 256) patterns = 256; + instruments = bswapLE16(*((WORD *)(lpStream+72))); + if (instruments >= MAX_INSTRUMENTS) instruments = MAX_INSTRUMENTS-1; + m_nInstruments = instruments; + m_dwSongFlags |= SONG_INSTRUMENTMODE; + m_nSamples = 0; + memcpy(&xmflags, lpStream+74, 2); + xmflags = bswapLE16(xmflags); + if (xmflags & 1) m_dwSongFlags |= SONG_LINEARSLIDES; + if (xmflags & 0x1000) m_dwSongFlags |= SONG_EXFILTERRANGE; + defspeed = bswapLE16(*((WORD *)(lpStream+76))); + deftempo = bswapLE16(*((WORD *)(lpStream+78))); + if ((deftempo >= 32) && (deftempo < 256)) m_nDefaultTempo = deftempo; + if ((defspeed > 0) && (defspeed < 40)) m_nDefaultSpeed = defspeed; + memcpy(Order, lpStream+80, norders); + memset(InstUsed, 0, sizeof(InstUsed)); + if (patterns > MAX_PATTERNS) + { + UINT i, j; + for (i=0; i= dwMemLength) return TRUE; + // Reading patterns + memset(channels_used, 0, sizeof(channels_used)); + for (UINT ipat=0; ipat= dwMemLength) || (dwSize & 0xFFFFFF00)) + { + if (dwMemPos + 4 >= dwMemLength) break; + dwMemPos++; + dwSize = bswapLE32(*((DWORD *)(lpStream+dwMemPos))); + } + rows = bswapLE16(*((WORD *)(lpStream+dwMemPos+5))); + if ((!rows) || (rows > 256)) rows = 64; + packsize = bswapLE16(*((WORD *)(lpStream+dwMemPos+7))); + if (dwMemPos + dwSize + 4 > dwMemLength) return TRUE; + dwMemPos += dwSize; + if (dwMemPos + packsize + 4 > dwMemLength) return TRUE; + MODCOMMAND *p; + if (ipatmap < MAX_PATTERNS) + { + PatternSize[ipatmap] = rows; + PatternAllocSize[ipatmap] = rows; + if ((Patterns[ipatmap] = AllocatePattern(rows, m_nChannels)) == NULL) return TRUE; + if (!packsize) continue; + p = Patterns[ipatmap]; + } else p = NULL; + const BYTE *src = lpStream+dwMemPos; + UINT j=0; + for (UINT row=0; rownote = src[j++]; + if (b & 2) p->instr = src[j++]; + if (b & 4) vol = src[j++]; + if (b & 8) p->command = src[j++]; + if (b & 16) p->param = src[j++]; + } else + { + p->note = b; + p->instr = src[j++]; + vol = src[j++]; + p->command = src[j++]; + p->param = src[j++]; + } + if (p->note == 97) p->note = 0xFF; else + if ((p->note) && (p->note < 97)) p->note += 12; + if (p->note) channels_used[chn] = 1; + if (p->command | p->param) ConvertModCommand(p); + if (p->instr == 0xff) p->instr = 0; + if (p->instr) InstUsed[p->instr] = TRUE; + if ((vol >= 0x10) && (vol <= 0x50)) + { + p->volcmd = VOLCMD_VOLUME; + p->vol = vol - 0x10; + } else + if (vol >= 0x60) + { + UINT v = vol & 0xF0; + vol &= 0x0F; + p->vol = vol; + switch(v) + { + // 60-6F: Volume Slide Down + case 0x60: p->volcmd = VOLCMD_VOLSLIDEDOWN; break; + // 70-7F: Volume Slide Up: + case 0x70: p->volcmd = VOLCMD_VOLSLIDEUP; break; + // 80-8F: Fine Volume Slide Down + case 0x80: p->volcmd = VOLCMD_FINEVOLDOWN; break; + // 90-9F: Fine Volume Slide Up + case 0x90: p->volcmd = VOLCMD_FINEVOLUP; break; + // A0-AF: Set Vibrato Speed + case 0xA0: p->volcmd = VOLCMD_VIBRATOSPEED; break; + // B0-BF: Vibrato + case 0xB0: p->volcmd = VOLCMD_VIBRATO; break; + // C0-CF: Set Panning + case 0xC0: p->volcmd = VOLCMD_PANNING; p->vol = (vol << 2) + 2; break; + // D0-DF: Panning Slide Left + case 0xD0: p->volcmd = VOLCMD_PANSLIDELEFT; break; + // E0-EF: Panning Slide Right + case 0xE0: p->volcmd = VOLCMD_PANSLIDERIGHT; break; + // F0-FF: Tone Portamento + case 0xF0: p->volcmd = VOLCMD_TONEPORTAMENTO; break; + } + } + p++; + } else + if (j < packsize) + { + BYTE b = src[j++]; + if (b & 0x80) + { + if (b & 1) j++; + if (b & 2) j++; + if (b & 4) j++; + if (b & 8) j++; + if (b & 16) j++; + } else j += 4; + } else break; + } + } + dwMemPos += packsize; + } + // Wrong offset check + while (dwMemPos + 4 < dwMemLength) + { + DWORD d = bswapLE32(*((DWORD *)(lpStream+dwMemPos))); + if (d < 0x300) break; + dwMemPos++; + } + memset(samples_used, 0, sizeof(samples_used)); + unused_samples = 0; + // Reading instruments + for (UINT iIns=1; iIns<=instruments; iIns++) + { + XMINSTRUMENTHEADER *pih; + BYTE flags[32]; + DWORD samplesize[32]; + UINT samplemap[32]; + WORD nsamples; + + if (dwMemPos + sizeof(XMINSTRUMENTHEADER) >= dwMemLength) return TRUE; + pih = (XMINSTRUMENTHEADER *)(lpStream+dwMemPos); + if (dwMemPos + bswapLE32(pih->size) > dwMemLength) return TRUE; + if ((Headers[iIns] = new INSTRUMENTHEADER) == NULL) continue; + memset(Headers[iIns], 0, sizeof(INSTRUMENTHEADER)); + memcpy(Headers[iIns]->name, pih->name, 22); + if ((nsamples = pih->samples) > 0) + { + if (dwMemPos + sizeof(XMSAMPLEHEADER) > dwMemLength) return TRUE; + memcpy(&xmsh, lpStream+dwMemPos+sizeof(XMINSTRUMENTHEADER), sizeof(XMSAMPLEHEADER)); + xmsh.shsize = bswapLE32(xmsh.shsize); + for (int i = 0; i < 24; ++i) { + xmsh.venv[i] = bswapLE16(xmsh.venv[i]); + xmsh.penv[i] = bswapLE16(xmsh.penv[i]); + } + xmsh.volfade = bswapLE16(xmsh.volfade); + xmsh.res = bswapLE16(xmsh.res); + dwMemPos += bswapLE32(pih->size); + } else + { + if (bswapLE32(pih->size)) dwMemPos += bswapLE32(pih->size); + else dwMemPos += sizeof(XMINSTRUMENTHEADER); + continue; + } + memset(samplemap, 0, sizeof(samplemap)); + if (nsamples > 32) return TRUE; + UINT newsamples = m_nSamples; + for (UINT nmap=0; nmap= MAX_SAMPLES) + { + n = m_nSamples; + while (n > 0) + { + if (!Ins[n].pSample) + { + for (UINT xmapchk=0; xmapchk < nmap; xmapchk++) + { + if (samplemap[xmapchk] == n) goto alreadymapped; + } + for (UINT clrs=1; clrsKeyboard[ks] == n) pks->Keyboard[ks] = 0; + } + } + break; + } + alreadymapped: + n--; + } +#ifndef MODPLUG_FASTSOUNDLIB + // Damn! more than 200 samples: look for duplicates + if (!n) + { + if (!unused_samples) + { + unused_samples = DetectUnusedSamples(samples_used); + if (!unused_samples) unused_samples = 0xFFFF; + } + if ((unused_samples) && (unused_samples != 0xFFFF)) + { + for (UINT iext=m_nSamples; iext>=1; iext--) if (!samples_used[iext]) + { + unused_samples--; + samples_used[iext] = TRUE; + DestroySample(iext); + n = iext; + for (UINT mapchk=0; mapchkKeyboard[ks] == n) pks->Keyboard[ks] = 0; + } + } + memset(&Ins[n], 0, sizeof(Ins[0])); + break; + } + } + } +#endif // MODPLUG_FASTSOUNDLIB + } + if (newsamples < n) newsamples = n; + samplemap[nmap] = n; + } + m_nSamples = newsamples; + // Reading Volume Envelope + INSTRUMENTHEADER *penv = Headers[iIns]; + penv->nMidiProgram = pih->type; + penv->nFadeOut = xmsh.volfade; + penv->nPan = 128; + penv->nPPC = 5*12; + if (xmsh.vtype & 1) penv->dwFlags |= ENV_VOLUME; + if (xmsh.vtype & 2) penv->dwFlags |= ENV_VOLSUSTAIN; + if (xmsh.vtype & 4) penv->dwFlags |= ENV_VOLLOOP; + if (xmsh.ptype & 1) penv->dwFlags |= ENV_PANNING; + if (xmsh.ptype & 2) penv->dwFlags |= ENV_PANSUSTAIN; + if (xmsh.ptype & 4) penv->dwFlags |= ENV_PANLOOP; + if (xmsh.vnum > 12) xmsh.vnum = 12; + if (xmsh.pnum > 12) xmsh.pnum = 12; + penv->VolEnv.nNodes = xmsh.vnum; + if (!xmsh.vnum) penv->dwFlags &= ~ENV_VOLUME; + if (!xmsh.pnum) penv->dwFlags &= ~ENV_PANNING; + penv->PanEnv.nNodes = xmsh.pnum; + penv->VolEnv.nSustainStart = penv->VolEnv.nSustainEnd = xmsh.vsustain; + if (xmsh.vsustain >= 12) penv->dwFlags &= ~ENV_VOLSUSTAIN; + penv->VolEnv.nLoopStart = xmsh.vloops; + penv->VolEnv.nLoopEnd = xmsh.vloope; + if (penv->VolEnv.nLoopEnd >= 12) penv->VolEnv.nLoopEnd = 0; + if (penv->VolEnv.nLoopStart >= penv->VolEnv.nLoopEnd) penv->dwFlags &= ~ENV_VOLLOOP; + penv->PanEnv.nSustainStart = penv->PanEnv.nSustainEnd = xmsh.psustain; + if (xmsh.psustain >= 12) penv->dwFlags &= ~ENV_PANSUSTAIN; + penv->PanEnv.nLoopStart = xmsh.ploops; + penv->PanEnv.nLoopEnd = xmsh.ploope; + if (penv->PanEnv.nLoopEnd >= 12) penv->PanEnv.nLoopEnd = 0; + if (penv->PanEnv.nLoopStart >= penv->PanEnv.nLoopEnd) penv->dwFlags &= ~ENV_PANLOOP; + penv->nGlobalVol = 64; + for (UINT ienv=0; ienv<12; ienv++) + { + penv->VolEnv.Ticks[ienv] = (WORD)xmsh.venv[ienv*2]; + penv->VolEnv.Values[ienv] = (BYTE)xmsh.venv[ienv*2+1]; + penv->PanEnv.Ticks[ienv] = (WORD)xmsh.penv[ienv*2]; + penv->PanEnv.Values[ienv] = (BYTE)xmsh.penv[ienv*2+1]; + if (ienv) + { + if (penv->VolEnv.Ticks[ienv] < penv->VolEnv.Ticks[ienv-1]) + { + penv->VolEnv.Ticks[ienv] &= 0xFF; + penv->VolEnv.Ticks[ienv] += penv->VolEnv.Ticks[ienv-1] & 0xFF00; + if (penv->VolEnv.Ticks[ienv] < penv->VolEnv.Ticks[ienv-1]) penv->VolEnv.Ticks[ienv] += 0x100; + } + if (penv->PanEnv.Ticks[ienv] < penv->PanEnv.Ticks[ienv-1]) + { + penv->PanEnv.Ticks[ienv] &= 0xFF; + penv->PanEnv.Ticks[ienv] += penv->PanEnv.Ticks[ienv-1] & 0xFF00; + if (penv->PanEnv.Ticks[ienv] < penv->PanEnv.Ticks[ienv-1]) penv->PanEnv.Ticks[ienv] += 0x100; + } + } + } + for (UINT j=0; j<96; j++) + { + penv->NoteMap[j+12] = j+1+12; + if (xmsh.snum[j] < nsamples) + penv->Keyboard[j+12] = samplemap[xmsh.snum[j]]; + } + // Reading samples + for (UINT ins=0; ins dwMemLength) + || (dwMemPos + xmsh.shsize > dwMemLength)) return TRUE; + memcpy(&xmss, lpStream+dwMemPos, sizeof(xmss)); + xmss.samplen = bswapLE32(xmss.samplen); + xmss.loopstart = bswapLE32(xmss.loopstart); + xmss.looplen = bswapLE32(xmss.looplen); + dwMemPos += xmsh.shsize; + flags[ins] = (xmss.type & 0x10) ? RS_PCM16D : RS_PCM8D; + if (xmss.type & 0x20) flags[ins] = (xmss.type & 0x10) ? RS_STPCM16D : RS_STPCM8D; + samplesize[ins] = xmss.samplen; + if (!samplemap[ins]) continue; + if (xmss.type & 0x10) + { + xmss.looplen >>= 1; + xmss.loopstart >>= 1; + xmss.samplen >>= 1; + } + if (xmss.type & 0x20) + { + xmss.looplen >>= 1; + xmss.loopstart >>= 1; + xmss.samplen >>= 1; + } + if (xmss.samplen > MAX_SAMPLE_LENGTH) xmss.samplen = MAX_SAMPLE_LENGTH; + if (xmss.loopstart >= xmss.samplen) xmss.type &= ~3; + xmss.looplen += xmss.loopstart; + if (xmss.looplen > xmss.samplen) xmss.looplen = xmss.samplen; + if (!xmss.looplen) xmss.type &= ~3; + UINT imapsmp = samplemap[ins]; + memcpy(m_szNames[imapsmp], xmss.name, 22); + m_szNames[imapsmp][22] = 0; + MODINSTRUMENT *pins = &Ins[imapsmp]; + pins->nLength = (xmss.samplen > MAX_SAMPLE_LENGTH) ? MAX_SAMPLE_LENGTH : xmss.samplen; + pins->nLoopStart = xmss.loopstart; + pins->nLoopEnd = xmss.looplen; + if (pins->nLoopEnd > pins->nLength) pins->nLoopEnd = pins->nLength; + if (pins->nLoopStart >= pins->nLoopEnd) + { + pins->nLoopStart = pins->nLoopEnd = 0; + } + if (xmss.type & 3) pins->uFlags |= CHN_LOOP; + if (xmss.type & 2) pins->uFlags |= CHN_PINGPONGLOOP; + pins->nVolume = xmss.vol << 2; + if (pins->nVolume > 256) pins->nVolume = 256; + pins->nGlobalVol = 64; + if ((xmss.res == 0xAD) && (!(xmss.type & 0x30))) + { + flags[ins] = RS_ADPCM4; + samplesize[ins] = (samplesize[ins]+1)/2 + 16; + } + pins->nFineTune = xmss.finetune; + pins->RelativeTone = (int)xmss.relnote; + pins->nPan = xmss.pan; + pins->uFlags |= CHN_PANNING; + pins->nVibType = xmsh.vibtype; + pins->nVibSweep = xmsh.vibsweep; + pins->nVibDepth = xmsh.vibdepth; + pins->nVibRate = xmsh.vibrate; + memcpy(pins->name, xmss.name, 22); + pins->name[21] = 0; + } +#if 0 + if ((xmsh.reserved2 > nsamples) && (xmsh.reserved2 <= 16)) + { + dwMemPos += (((UINT)xmsh.reserved2) - nsamples) * xmsh.shsize; + } +#endif + for (UINT ismpd=0; ismpd= dwMemLength) break; + } + } + // Read song comments: "TEXT" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x74786574)) + { + UINT len = *((DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len < 16384)) + { + m_lpszSongComments = new char[len+1]; + if (m_lpszSongComments) + { + memcpy(m_lpszSongComments, lpStream+dwMemPos, len); + m_lpszSongComments[len] = 0; + } + dwMemPos += len; + } + } + // Read midi config: "MIDI" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4944494D)) + { + UINT len = *((DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + if (len == sizeof(MODMIDICFG)) { + memcpy(&m_MidiCfg, lpStream+dwMemPos, len); + m_dwSongFlags |= SONG_EMBEDMIDICFG; + } else { + ResetMidiCfg(); + } + } else { + ResetMidiCfg(); + } + // Read pattern names: "PNAM" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e50)) + { + UINT len = *((DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len <= MAX_PATTERNS*MAX_PATTERNNAME) && (len >= MAX_PATTERNNAME)) + { + m_lpszPatternNames = new char[len]; + + if (m_lpszPatternNames) + { + m_nPatternNames = len / MAX_PATTERNNAME; + memcpy(m_lpszPatternNames, lpStream+dwMemPos, len); + } + dwMemPos += len; + } + } + // Read channel names: "CNAM" + if ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e43)) + { + UINT len = *((DWORD *)(lpStream+dwMemPos+4)); + dwMemPos += 8; + if ((dwMemPos + len <= dwMemLength) && (len <= MAX_BASECHANNELS*MAX_CHANNELNAME)) + { + UINT n = len / MAX_CHANNELNAME; + for (UINT i=0; io(fp, (const unsigned char *)"Extended Module: ", 17); + fp->o(fp, (const unsigned char *)m_szNames[0], 20); + s[0] = 0x1A; + lstrcpy((LPSTR)&s[1], (nPacking) ? "MOD Plugin packed " : "FastTracker v2.00 "); + s[21] = 0x04; + s[22] = 0x01; + fp->o(fp, (const unsigned char *)s, 23); + // Writing song header + memset(&header, 0, sizeof(header)); + header.size = bswapLE32(sizeof(XMFILEHEADER)); + header.restartpos = bswapLE16(m_nRestartPos); + header.channels = bswapLE16(m_nChannels); + np = 0; + no = 0; + for (i=0; i= np) && (Order[i] < MAX_PATTERNS)) np = Order[i]+1; + } + header.patterns = bswapLE16(np); + ni = m_nInstruments; + if (!ni) ni = m_nSamples; + header.instruments = bswapLE16(ni); + header.flags = (m_dwSongFlags & SONG_LINEARSLIDES) ? 0x01 : 0x00; + if (m_dwSongFlags & SONG_EXFILTERRANGE) header.flags |= 0x1000; + header.tempo = bswapLE16(m_nDefaultTempo); + header.speed = bswapLE16(m_nDefaultSpeed); + header.flags = bswapLE16(header.flags); + header.norder = bswapLE16(no); + memcpy(header.order, Order, no); + fp->o(fp, (const unsigned char *)&header, sizeof(header)); + // Writing patterns + for (i=0; i> 8); + for (UINT j=m_nChannels*PatternSize[i]; j; j--,p++) + { + UINT note = p->note; + UINT param = ModSaveCommand(p, TRUE); + UINT command = param >> 8; + param &= 0xFF; + if (note >= 0xFE) note = 97; else + if ((note <= 12) || (note > 96+12)) note = 0; else + note -= 12; + UINT vol = 0; + + if (p->volcmd) + { + UINT volcmd = p->volcmd; + switch(volcmd) + { + case VOLCMD_VOLUME: vol = 0x10 + p->vol; break; + case VOLCMD_VOLSLIDEDOWN: vol = 0x60 + (p->vol & 0x0F); break; + case VOLCMD_VOLSLIDEUP: vol = 0x70 + (p->vol & 0x0F); break; + case VOLCMD_FINEVOLDOWN: vol = 0x80 + (p->vol & 0x0F); break; + case VOLCMD_FINEVOLUP: vol = 0x90 + (p->vol & 0x0F); break; + case VOLCMD_VIBRATOSPEED: vol = 0xA0 + (p->vol & 0x0F); break; + case VOLCMD_VIBRATO: vol = 0xB0 + (p->vol & 0x0F); break; + case VOLCMD_PANNING: vol = 0xC0 + (p->vol >> 2); if (vol > 0xCF) vol = 0xCF; break; + case VOLCMD_PANSLIDELEFT: vol = 0xD0 + (p->vol & 0x0F); break; + case VOLCMD_PANSLIDERIGHT: vol = 0xE0 + (p->vol & 0x0F); break; + case VOLCMD_TONEPORTAMENTO: vol = 0xF0 + (p->vol & 0x0F); break; + } + } + if ((note) && (p->instr) && (vol > 0x0F) && (command) && (param)) + { + s[len++] = note; + s[len++] = p->instr; + s[len++] = vol; + s[len++] = command; + s[len++] = param; + } else + { + BYTE b = 0x80; + if (note) b |= 0x01; + if (p->instr) b |= 0x02; + if (vol >= 0x10) b |= 0x04; + if (command) b |= 0x08; + if (param) b |= 0x10; + s[len++] = b; + if (b & 1) s[len++] = note; + if (b & 2) s[len++] = p->instr; + if (b & 4) s[len++] = vol; + if (b & 8) s[len++] = command; + if (b & 16) s[len++] = param; + } + if (len > sizeof(s) - 5) break; + } + xmph[7] = (BYTE)(len & 0xFF); + xmph[8] = (BYTE)(len >> 8); + fp->o(fp, (const unsigned char *)xmph, 9); + fp->o(fp, (const unsigned char *)s, len); + } else + { + memset(&xmph, 0, sizeof(xmph)); + xmph[0] = 9; + xmph[5] = (BYTE)(PatternSize[i] & 0xFF); + xmph[6] = (BYTE)(PatternSize[i] >> 8); + fp->o(fp, (const unsigned char *)xmph, 9); + } + // Writing instruments + for (i=1; i<=ni; i++) + { + MODINSTRUMENT *pins; + int tmpsize, tmpsize2; + BYTE flags[32]; + + memset(&xmih, 0, sizeof(xmih)); + memset(&xmsh, 0, sizeof(xmsh)); + xmih.size = tmpsize = sizeof(xmih) + sizeof(xmsh); + xmih.size = bswapLE32(xmih.size); + memcpy(xmih.name, m_szNames[i], 22); + xmih.type = 0; + xmih.samples = 0; + if (m_nInstruments) + { + INSTRUMENTHEADER *penv = Headers[i]; + if (penv) + { + memcpy(xmih.name, penv->name, 22); + xmih.type = penv->nMidiProgram; + xmsh.volfade = penv->nFadeOut; + xmsh.vnum = (BYTE)penv->VolEnv.nNodes; + xmsh.pnum = (BYTE)penv->PanEnv.nNodes; + if (xmsh.vnum > 12) xmsh.vnum = 12; + if (xmsh.pnum > 12) xmsh.pnum = 12; + for (UINT ienv=0; ienv<12; ienv++) + { + xmsh.venv[ienv*2] = bswapLE16(penv->VolEnv.Ticks[ienv]); + xmsh.venv[ienv*2+1] = bswapLE16(penv->VolEnv.Values[ienv]); + xmsh.penv[ienv*2] = bswapLE16(penv->PanEnv.Ticks[ienv]); + xmsh.penv[ienv*2+1] = bswapLE16(penv->PanEnv.Values[ienv]); + } + if (penv->dwFlags & ENV_VOLUME) xmsh.vtype |= 1; + if (penv->dwFlags & ENV_VOLSUSTAIN) xmsh.vtype |= 2; + if (penv->dwFlags & ENV_VOLLOOP) xmsh.vtype |= 4; + if (penv->dwFlags & ENV_PANNING) xmsh.ptype |= 1; + if (penv->dwFlags & ENV_PANSUSTAIN) xmsh.ptype |= 2; + if (penv->dwFlags & ENV_PANLOOP) xmsh.ptype |= 4; + xmsh.vsustain = (BYTE)penv->VolEnv.nSustainStart; + xmsh.vloops = (BYTE)penv->VolEnv.nLoopStart; + xmsh.vloope = (BYTE)penv->VolEnv.nLoopEnd; + xmsh.psustain = (BYTE)penv->PanEnv.nSustainStart; + xmsh.ploops = (BYTE)penv->PanEnv.nLoopStart; + xmsh.ploope = (BYTE)penv->PanEnv.nLoopEnd; + for (UINT j=0; j<96; j++) if (penv->Keyboard[j+12]) + { + UINT k; + for (k=0; kKeyboard[j+12]) break; + if (k == xmih.samples) + { + smptable[xmih.samples++] = penv->Keyboard[j+12]; + } + if (xmih.samples >= 32) break; + xmsh.snum[j] = k; + } +// xmsh.reserved2 = xmih.samples; + } + } else + { + xmih.samples = 1; +// xmsh.reserved2 = 1; + smptable[0] = i; + } + xmsh.shsize = (xmih.samples) ? 40 : 0; + fp->o(fp, (const unsigned char *)&xmih, sizeof(xmih)); + if (smptable[0]) + { + MODINSTRUMENT *pvib = &Ins[smptable[0]]; + xmsh.vibtype = pvib->nVibType; + xmsh.vibsweep = pvib->nVibSweep; + xmsh.vibdepth = pvib->nVibDepth; + xmsh.vibrate = pvib->nVibRate; + } + + tmpsize2 = xmsh.shsize; + xmsh.shsize = bswapLE32(xmsh.shsize); + xmsh.volfade = bswapLE16(xmsh.volfade); + xmsh.res = bswapLE16(xmsh.res); + + fp->o(fp, (const unsigned char *)&xmsh, tmpsize - sizeof(xmih)); + if (!xmih.samples) continue; + for (UINT ins=0; insnLength; + xmss.loopstart = pins->nLoopStart; + xmss.looplen = pins->nLoopEnd - pins->nLoopStart; + xmss.vol = pins->nVolume / 4; + + xmss.finetune = (char)pins->nFineTune; + xmss.type = 0; + if (pins->uFlags & CHN_LOOP) xmss.type = (pins->uFlags & CHN_PINGPONGLOOP) ? 2 : 1; + flags[ins] = RS_PCM8D; +#ifndef NO_PACKING + if (nPacking) + { + if ((!(pins->uFlags & (CHN_16BIT|CHN_STEREO))) + && (CanPackSample((char*)pins->pSample, pins->nLength, nPacking))) + { + flags[ins] = RS_ADPCM4; + xmss.res = 0xAD; + } + } else +#endif + { + if (pins->uFlags & CHN_16BIT) + { + flags[ins] = RS_PCM16D; + xmss.type |= 0x10; + xmss.looplen *= 2; + xmss.loopstart *= 2; + xmss.samplen *= 2; + } + if (pins->uFlags & CHN_STEREO) + { + flags[ins] = (pins->uFlags & CHN_16BIT) ? RS_STPCM16D : RS_STPCM8D; + xmss.type |= 0x20; + xmss.looplen *= 2; + xmss.loopstart *= 2; + xmss.samplen *= 2; + } + } + if (pins->uFlags & CHN_PANNING) { + xmss.pan = 255; + if (pins->nPan < 256) xmss.pan = (BYTE)pins->nPan; + } else { + /* set panning to support default */ + xmss.pan = 128; + } + xmss.relnote = (signed char)pins->RelativeTone; + xmss.samplen = bswapLE32(xmss.samplen); + xmss.loopstart = bswapLE32(xmss.loopstart); + xmss.looplen = bswapLE32(xmss.looplen); + fp->o(fp, (const unsigned char *)&xmss, tmpsize2); + } + for (UINT ismpd=0; ismpdpSample) + { +#ifndef NO_PACKING + if ((flags[ismpd] == RS_ADPCM4) && (xmih.samples>1)) CanPackSample((char*)pins->pSample, pins->nLength, nPacking); +#endif // NO_PACKING + WriteSample(fp, pins, flags[ismpd]); + } + } + } + // Writing song comments + if ((m_lpszSongComments) && (m_lpszSongComments[0])) + { + DWORD d = 0x74786574; + fp->o(fp, (const unsigned char *)&d, 4); + d = strlen(m_lpszSongComments); + fp->o(fp, (const unsigned char *)&d, 4); + fp->o(fp, (const unsigned char *)m_lpszSongComments, d); + } + // Writing midi cfg + if (m_dwSongFlags & SONG_EMBEDMIDICFG) + { + DWORD d = 0x4944494D; + fp->o(fp, (const unsigned char *)&d, 4); + d = sizeof(MODMIDICFG); + fp->o(fp, (const unsigned char *)&d, 4); + fp->o(fp, (const unsigned char *)&m_MidiCfg, sizeof(MODMIDICFG)); + } + // Writing Pattern Names + if ((m_nPatternNames) && (m_lpszPatternNames)) + { + DWORD dwLen = m_nPatternNames * MAX_PATTERNNAME; + while ((dwLen >= MAX_PATTERNNAME) && (!m_lpszPatternNames[dwLen-MAX_PATTERNNAME])) dwLen -= MAX_PATTERNNAME; + if (dwLen >= MAX_PATTERNNAME) + { + DWORD d = 0x4d414e50; + fp->o(fp, (const unsigned char *)&d, 4); + fp->o(fp, (const unsigned char *)&dwLen, 4); + fp->o(fp, (const unsigned char *)m_lpszPatternNames, dwLen); + } + } + // Writing Channel Names + { + UINT nChnNames = 0; + for (UINT inam=0; inamo(fp, (const unsigned char *)&d, 4); + fp->o(fp, (const unsigned char *)&dwLen, 4); + for (UINT inam=0; inamo(fp, (const unsigned char *)ChnSettings[inam].szName, MAX_CHANNELNAME); + } + } + } + return TRUE; +} + +#endif // MODPLUG_NO_FILESAVE diff --git a/modplug/mmcmp.cpp b/modplug/mmcmp.cpp new file mode 100644 index 000000000..200ee3b93 --- /dev/null +++ b/modplug/mmcmp.cpp @@ -0,0 +1,406 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +BOOL PP20_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength); + +typedef struct MMCMPFILEHEADER +{ + DWORD id_ziRC; // "ziRC" + DWORD id_ONia; // "ONia" + WORD hdrsize; +} MMCMPFILEHEADER, *LPMMCMPFILEHEADER; + +typedef struct MMCMPHEADER +{ + WORD version; + WORD nblocks; + DWORD filesize; + DWORD blktable; + BYTE glb_comp; + BYTE fmt_comp; +} MMCMPHEADER, *LPMMCMPHEADER; + +typedef struct MMCMPBLOCK +{ + DWORD unpk_size; + DWORD pk_size; + DWORD xor_chk; + WORD sub_blk; + WORD flags; + WORD tt_entries; + WORD num_bits; +} MMCMPBLOCK, *LPMMCMPBLOCK; + +typedef struct MMCMPSUBBLOCK +{ + DWORD unpk_pos; + DWORD unpk_size; +} MMCMPSUBBLOCK, *LPMMCMPSUBBLOCK; + +#define MMCMP_COMP 0x0001 +#define MMCMP_DELTA 0x0002 +#define MMCMP_16BIT 0x0004 +#define MMCMP_STEREO 0x0100 +#define MMCMP_ABS16 0x0200 +#define MMCMP_ENDIAN 0x0400 + +typedef struct MMCMPBITBUFFER +{ + UINT bitcount; + DWORD bitbuffer; + LPCBYTE pSrc; + LPCBYTE pEnd; + + DWORD GetBits(UINT nBits); +} MMCMPBITBUFFER; + + +DWORD MMCMPBITBUFFER::GetBits(UINT nBits) +//--------------------------------------- +{ + DWORD d; + if (!nBits) return 0; + while (bitcount < 24) + { + bitbuffer |= ((pSrc < pEnd) ? *pSrc++ : 0) << bitcount; + bitcount += 8; + } + d = bitbuffer & ((1 << nBits) - 1); + bitbuffer >>= nBits; + bitcount -= nBits; + return d; +} + +//#define MMCMP_LOG + +#ifdef MMCMP_LOG +extern void Log(LPCSTR s, ...); +#endif + +const DWORD MMCMP8BitCommands[8] = +{ + 0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF8 +}; + +const UINT MMCMP8BitFetch[8] = +{ + 3, 3, 3, 3, 2, 1, 0, 0 +}; + +const DWORD MMCMP16BitCommands[16] = +{ + 0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF0, + 0x1F0, 0x3F0, 0x7F0, 0xFF0, 0x1FF0, 0x3FF0, 0x7FF0, 0xFFF0 +}; + +const UINT MMCMP16BitFetch[16] = +{ + 4, 4, 4, 4, 3, 2, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + + +BOOL MMCMP_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength) +//--------------------------------------------------------- +{ + DWORD dwMemLength = *pdwMemLength; + LPCBYTE lpMemFile = *ppMemFile; + LPBYTE pBuffer; + LPMMCMPFILEHEADER pmfh = (LPMMCMPFILEHEADER)(lpMemFile); + LPMMCMPHEADER pmmh = (LPMMCMPHEADER)(lpMemFile+10); + LPDWORD pblk_table; + DWORD dwFileSize; + + if (PP20_Unpack(ppMemFile, pdwMemLength)) + { + return TRUE; + } + if ((dwMemLength < 256) || (!pmfh) || (pmfh->id_ziRC != 0x4352697A) || (pmfh->id_ONia != 0x61694e4f) || (pmfh->hdrsize < 14) + || (!pmmh->nblocks) || (pmmh->filesize < 16) || (pmmh->filesize > 0x8000000) + || (pmmh->blktable >= dwMemLength) || (pmmh->blktable + 4*pmmh->nblocks > dwMemLength)) return FALSE; + dwFileSize = pmmh->filesize; + if ((pBuffer = (LPBYTE)GlobalAllocPtr(GHND, (dwFileSize + 31) & ~15)) == NULL) return FALSE; + pblk_table = (LPDWORD)(lpMemFile+pmmh->blktable); + for (UINT nBlock=0; nBlocknblocks; nBlock++) + { + DWORD dwMemPos = pblk_table[nBlock]; + LPMMCMPBLOCK pblk = (LPMMCMPBLOCK)(lpMemFile+dwMemPos); + LPMMCMPSUBBLOCK psubblk = (LPMMCMPSUBBLOCK)(lpMemFile+dwMemPos+20); + + if ((dwMemPos + 20 >= dwMemLength) || (dwMemPos + 20 + pblk->sub_blk*8 >= dwMemLength)) break; + dwMemPos += 20 + pblk->sub_blk*8; +#ifdef MMCMP_LOG + Log("block %d: flags=%04X sub_blocks=%d", nBlock, (UINT)pblk->flags, (UINT)pblk->sub_blk); + Log(" pksize=%d unpksize=%d", pblk->pk_size, pblk->unpk_size); + Log(" tt_entries=%d num_bits=%d\n", pblk->tt_entries, pblk->num_bits); +#endif + // Data is not packed + if (!(pblk->flags & MMCMP_COMP)) + { + for (UINT i=0; isub_blk; i++) + { + if ((psubblk->unpk_pos > dwFileSize) || (psubblk->unpk_pos + psubblk->unpk_size > dwFileSize)) break; +#ifdef MMCMP_LOG + Log(" Unpacked sub-block %d: offset %d, size=%d\n", i, psubblk->unpk_pos, psubblk->unpk_size); +#endif + memcpy(pBuffer+psubblk->unpk_pos, lpMemFile+dwMemPos, psubblk->unpk_size); + dwMemPos += psubblk->unpk_size; + psubblk++; + } + } else + // Data is 16-bit packed + if (pblk->flags & MMCMP_16BIT) + { + MMCMPBITBUFFER bb; + LPWORD pDest = (LPWORD)(pBuffer + psubblk->unpk_pos); + DWORD dwSize = psubblk->unpk_size >> 1; + DWORD dwPos = 0; + UINT numbits = pblk->num_bits; + UINT subblk = 0, oldval = 0; + +#ifdef MMCMP_LOG + Log(" 16-bit block: pos=%d size=%d ", psubblk->unpk_pos, psubblk->unpk_size); + if (pblk->flags & MMCMP_DELTA) Log("DELTA "); + if (pblk->flags & MMCMP_ABS16) Log("ABS16 "); + Log("\n"); +#endif + bb.bitcount = 0; + bb.bitbuffer = 0; + bb.pSrc = lpMemFile+dwMemPos+pblk->tt_entries; + bb.pEnd = lpMemFile+dwMemPos+pblk->pk_size; + while (subblk < pblk->sub_blk) + { + UINT newval = 0x10000; + DWORD d = bb.GetBits(numbits+1); + + if (d >= MMCMP16BitCommands[numbits]) + { + UINT nFetch = MMCMP16BitFetch[numbits]; + UINT newbits = bb.GetBits(nFetch) + ((d - MMCMP16BitCommands[numbits]) << nFetch); + if (newbits != numbits) + { + numbits = newbits & 0x0F; + } else + { + if ((d = bb.GetBits(4)) == 0x0F) + { + if (bb.GetBits(1)) break; + newval = 0xFFFF; + } else + { + newval = 0xFFF0 + d; + } + } + } else + { + newval = d; + } + if (newval < 0x10000) + { + newval = (newval & 1) ? (UINT)(-(LONG)((newval+1) >> 1)) : (UINT)(newval >> 1); + if (pblk->flags & MMCMP_DELTA) + { + newval += oldval; + oldval = newval; + } else + if (!(pblk->flags & MMCMP_ABS16)) + { + newval ^= 0x8000; + } + pDest[dwPos++] = (WORD)newval; + } + if (dwPos >= dwSize) + { + subblk++; + dwPos = 0; + dwSize = psubblk[subblk].unpk_size >> 1; + pDest = (LPWORD)(pBuffer + psubblk[subblk].unpk_pos); + } + } + } else + // Data is 8-bit packed + { + MMCMPBITBUFFER bb; + LPBYTE pDest = pBuffer + psubblk->unpk_pos; + DWORD dwSize = psubblk->unpk_size; + DWORD dwPos = 0; + UINT numbits = pblk->num_bits; + UINT subblk = 0, oldval = 0; + LPCBYTE ptable = lpMemFile+dwMemPos; + + bb.bitcount = 0; + bb.bitbuffer = 0; + bb.pSrc = lpMemFile+dwMemPos+pblk->tt_entries; + bb.pEnd = lpMemFile+dwMemPos+pblk->pk_size; + while (subblk < pblk->sub_blk) + { + UINT newval = 0x100; + DWORD d = bb.GetBits(numbits+1); + + if (d >= MMCMP8BitCommands[numbits]) + { + UINT nFetch = MMCMP8BitFetch[numbits]; + UINT newbits = bb.GetBits(nFetch) + ((d - MMCMP8BitCommands[numbits]) << nFetch); + if (newbits != numbits) + { + numbits = newbits & 0x07; + } else + { + if ((d = bb.GetBits(3)) == 7) + { + if (bb.GetBits(1)) break; + newval = 0xFF; + } else + { + newval = 0xF8 + d; + } + } + } else + { + newval = d; + } + if (newval < 0x100) + { + int n = ptable[newval]; + if (pblk->flags & MMCMP_DELTA) + { + n += oldval; + oldval = n; + } + pDest[dwPos++] = (BYTE)n; + } + if (dwPos >= dwSize) + { + subblk++; + dwPos = 0; + dwSize = psubblk[subblk].unpk_size; + pDest = pBuffer + psubblk[subblk].unpk_pos; + } + } + } + } + *ppMemFile = pBuffer; + *pdwMemLength = dwFileSize; + return TRUE; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// PowerPack PP20 Unpacker +// + +typedef struct _PPBITBUFFER +{ + UINT bitcount; + ULONG bitbuffer; + LPCBYTE pStart; + LPCBYTE pSrc; + + ULONG GetBits(UINT n); +} PPBITBUFFER; + + +ULONG PPBITBUFFER::GetBits(UINT n) +{ + ULONG result = 0; + + for (UINT i=0; i>= 1; + bitcount--; + } + return result; +} + + +VOID PP20_DoUnpack(const BYTE *pSrc, UINT nSrcLen, BYTE *pDst, UINT nDstLen) +{ + PPBITBUFFER BitBuffer; + ULONG nBytesLeft; + + BitBuffer.pStart = pSrc; + BitBuffer.pSrc = pSrc + nSrcLen - 4; + BitBuffer.bitbuffer = 0; + BitBuffer.bitcount = 0; + BitBuffer.GetBits(pSrc[nSrcLen-1]); + nBytesLeft = nDstLen; + while (nBytesLeft > 0) + { + if (!BitBuffer.GetBits(1)) + { + UINT n = 1; + while (n < nBytesLeft) + { + UINT code = BitBuffer.GetBits(2); + n += code; + if (code != 3) break; + } + for (UINT i=0; i 0x400000) || (dwDstLen > 16*dwMemLength)) return FALSE; + if ((pBuffer = (LPBYTE)GlobalAllocPtr(GHND, (dwDstLen + 31) & ~15)) == NULL) return FALSE; + PP20_DoUnpack(lpMemFile+4, dwMemLength-4, pBuffer, dwDstLen); + *ppMemFile = pBuffer; + *pdwMemLength = dwDstLen; + return TRUE; +} + + + + + diff --git a/modplug/snd_dsp.cpp b/modplug/snd_dsp.cpp new file mode 100644 index 000000000..606a393ab --- /dev/null +++ b/modplug/snd_dsp.cpp @@ -0,0 +1,491 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#ifdef MODPLUG_FASTSOUNDLIB +#define MODPLUG_NO_REVERB +#endif + + +// Delayed Surround Filters +#ifndef MODPLUG_FASTSOUNDLIB +#define nDolbyHiFltAttn 6 +#define nDolbyHiFltMask 3 +#define DOLBYATTNROUNDUP 31 +#else +#define nDolbyHiFltAttn 3 +#define nDolbyHiFltMask 3 +#define DOLBYATTNROUNDUP 3 +#endif + +// Bass Expansion +#define XBASS_DELAY 14 // 2.5 ms + +// Buffer Sizes +#define XBASSBUFFERSIZE 64 // 2 ms at 50KHz +#define FILTERBUFFERSIZE 64 // 1.25 ms +#define SURROUNDBUFFERSIZE ((MAX_SAMPLE_RATE * 50) / 1000) +#define REVERBBUFFERSIZE ((MAX_SAMPLE_RATE * 200) / 1000) +#define REVERBBUFFERSIZE2 ((REVERBBUFFERSIZE*13) / 17) +#define REVERBBUFFERSIZE3 ((REVERBBUFFERSIZE*7) / 13) +#define REVERBBUFFERSIZE4 ((REVERBBUFFERSIZE*7) / 19) + + +// DSP Effects: PUBLIC members +UINT CSoundFile::m_nXBassDepth = 6; +UINT CSoundFile::m_nXBassRange = XBASS_DELAY; +UINT CSoundFile::m_nReverbDepth = 1; +UINT CSoundFile::m_nReverbDelay = 100; +UINT CSoundFile::m_nProLogicDepth = 12; +UINT CSoundFile::m_nProLogicDelay = 20; + +void (*CSoundFile::_midi_out_note)(int chan, const MODCOMMAND *m) = NULL; +void (*CSoundFile::_midi_out_raw)(unsigned char *,unsigned int, unsigned int) = NULL; + +//////////////////////////////////////////////////////////////////// +// DSP Effects internal state + +// Bass Expansion: low-pass filter +static LONG nXBassSum = 0; +static LONG nXBassBufferPos = 0; +static LONG nXBassDlyPos = 0; +static LONG nXBassMask = 0; + +// Noise Reduction: simple low-pass filter +static LONG nLeftNR = 0; +static LONG nRightNR = 0; + +// Surround Encoding: 1 delay line + low-pass filter + high-pass filter +static LONG nSurroundSize = 0; +static LONG nSurroundPos = 0; +static LONG nDolbyDepth = 0; +static LONG nDolbyLoDlyPos = 0; +static LONG nDolbyLoFltPos = 0; +static LONG nDolbyLoFltSum = 0; +static LONG nDolbyHiFltPos = 0; +static LONG nDolbyHiFltSum = 0; + +// Reverb: 4 delay lines + high-pass filter + low-pass filter +#ifndef MODPLUG_NO_REVERB +static LONG nReverbSize = 0; +static LONG nReverbBufferPos = 0; +static LONG nReverbSize2 = 0; +static LONG nReverbBufferPos2 = 0; +static LONG nReverbSize3 = 0; +static LONG nReverbBufferPos3 = 0; +static LONG nReverbSize4 = 0; +static LONG nReverbBufferPos4 = 0; +static LONG nReverbLoFltSum = 0; +static LONG nReverbLoFltPos = 0; +static LONG nReverbLoDlyPos = 0; +static LONG nFilterAttn = 0; +static LONG gRvbLowPass[8]; +static LONG gRvbLPPos = 0; +static LONG gRvbLPSum = 0; +static LONG ReverbLoFilterBuffer[XBASSBUFFERSIZE]; +static LONG ReverbLoFilterDelay[XBASSBUFFERSIZE]; +static LONG ReverbBuffer[REVERBBUFFERSIZE]; +static LONG ReverbBuffer2[REVERBBUFFERSIZE2]; +static LONG ReverbBuffer3[REVERBBUFFERSIZE3]; +static LONG ReverbBuffer4[REVERBBUFFERSIZE4]; +#endif +static LONG XBassBuffer[XBASSBUFFERSIZE]; +static LONG XBassDelay[XBASSBUFFERSIZE]; +static LONG DolbyLoFilterBuffer[XBASSBUFFERSIZE]; +static LONG DolbyLoFilterDelay[XBASSBUFFERSIZE]; +static LONG DolbyHiFilterBuffer[FILTERBUFFERSIZE]; +static LONG SurroundBuffer[SURROUNDBUFFERSIZE]; + +// Access the main temporary mix buffer directly: avoids an extra pointer +extern int MixSoundBuffer[MIXBUFFERSIZE*2]; +//cextern int MixReverbBuffer[MIXBUFFERSIZE*2]; +extern int MixReverbBuffer[MIXBUFFERSIZE*2]; + +static UINT GetMaskFromSize(UINT len) +//----------------------------------- +{ + UINT n = 2; + while (n <= len) n <<= 1; + return ((n >> 1) - 1); +} + + +void CSoundFile::InitializeDSP(BOOL bReset) +//----------------------------------------- +{ + if (!m_nReverbDelay) m_nReverbDelay = 100; + if (!m_nXBassRange) m_nXBassRange = XBASS_DELAY; + if (!m_nProLogicDelay) m_nProLogicDelay = 20; + if (m_nXBassDepth > 8) m_nXBassDepth = 8; + if (m_nXBassDepth < 2) m_nXBassDepth = 2; + if (bReset) + { + // Noise Reduction + nLeftNR = nRightNR = 0; + } + // Pro-Logic Surround + nSurroundPos = nSurroundSize = 0; + nDolbyLoFltPos = nDolbyLoFltSum = nDolbyLoDlyPos = 0; + nDolbyHiFltPos = nDolbyHiFltSum = 0; + if (gdwSoundSetup & SNDMIX_SURROUND) + { + memset(DolbyLoFilterBuffer, 0, sizeof(DolbyLoFilterBuffer)); + memset(DolbyHiFilterBuffer, 0, sizeof(DolbyHiFilterBuffer)); + memset(DolbyLoFilterDelay, 0, sizeof(DolbyLoFilterDelay)); + memset(SurroundBuffer, 0, sizeof(SurroundBuffer)); + nSurroundSize = (gdwMixingFreq * m_nProLogicDelay) / 1000; + if (nSurroundSize > SURROUNDBUFFERSIZE) nSurroundSize = SURROUNDBUFFERSIZE; + if (m_nProLogicDepth < 8) nDolbyDepth = (32 >> m_nProLogicDepth) + 32; + else nDolbyDepth = (m_nProLogicDepth < 16) ? (8 + (m_nProLogicDepth - 8) * 7) : 64; + nDolbyDepth >>= 2; + } + // Reverb Setup +#ifndef MODPLUG_NO_REVERB + if (gdwSoundSetup & SNDMIX_REVERB) + { + UINT nrs = (gdwMixingFreq * m_nReverbDelay) / 1000; + UINT nfa = m_nReverbDepth+1; + if (nrs > REVERBBUFFERSIZE) nrs = REVERBBUFFERSIZE; + if ((bReset) || (nrs != (UINT)nReverbSize) || (nfa != (UINT)nFilterAttn)) + { + nFilterAttn = nfa; + nReverbSize = nrs; + nReverbBufferPos = nReverbBufferPos2 = nReverbBufferPos3 = nReverbBufferPos4 = 0; + nReverbLoFltSum = nReverbLoFltPos = nReverbLoDlyPos = 0; + gRvbLPSum = gRvbLPPos = 0; + nReverbSize2 = (nReverbSize * 13) / 17; + if (nReverbSize2 > REVERBBUFFERSIZE2) nReverbSize2 = REVERBBUFFERSIZE2; + nReverbSize3 = (nReverbSize * 7) / 13; + if (nReverbSize3 > REVERBBUFFERSIZE3) nReverbSize3 = REVERBBUFFERSIZE3; + nReverbSize4 = (nReverbSize * 7) / 19; + if (nReverbSize4 > REVERBBUFFERSIZE4) nReverbSize4 = REVERBBUFFERSIZE4; + memset(ReverbLoFilterBuffer, 0, sizeof(ReverbLoFilterBuffer)); + memset(ReverbLoFilterDelay, 0, sizeof(ReverbLoFilterDelay)); + memset(ReverbBuffer, 0, sizeof(ReverbBuffer)); + memset(ReverbBuffer2, 0, sizeof(ReverbBuffer2)); + memset(ReverbBuffer3, 0, sizeof(ReverbBuffer3)); + memset(ReverbBuffer4, 0, sizeof(ReverbBuffer4)); + memset(gRvbLowPass, 0, sizeof(gRvbLowPass)); +/* mrsb: libmodplug bug hahahah */ + memset(MixSoundBuffer,0,sizeof(MixSoundBuffer)); + memset(MixReverbBuffer,0,sizeof(MixReverbBuffer)); + } + } else nReverbSize = 0; +#endif + BOOL bResetBass = FALSE; + // Bass Expansion Reset + if (gdwSoundSetup & SNDMIX_MEGABASS) + { + UINT nXBassSamples = (gdwMixingFreq * m_nXBassRange) / 10000; + if (nXBassSamples > XBASSBUFFERSIZE) nXBassSamples = XBASSBUFFERSIZE; + UINT mask = GetMaskFromSize(nXBassSamples); + if ((bReset) || (mask != (UINT)nXBassMask)) + { + nXBassMask = mask; + bResetBass = TRUE; + } + } else + { + nXBassMask = 0; + bResetBass = TRUE; + } + if (bResetBass) + { + nXBassSum = nXBassBufferPos = nXBassDlyPos = 0; + memset(XBassBuffer, 0, sizeof(XBassBuffer)); + memset(XBassDelay, 0, sizeof(XBassDelay)); + } +} + + +void CSoundFile::ProcessStereoDSP(int count) +//------------------------------------------ +{ +#ifndef MODPLUG_NO_REVERB + // Reverb + if (gdwSoundSetup & SNDMIX_REVERB) + { + int *pr = MixSoundBuffer, *pin = MixReverbBuffer, rvbcount = count; + do + { + int echo = ReverbBuffer[nReverbBufferPos] + ReverbBuffer2[nReverbBufferPos2] + + ReverbBuffer3[nReverbBufferPos3] + ReverbBuffer4[nReverbBufferPos4]; // echo = reverb signal + // Delay line and remove Low Frequencies // v = original signal + int echodly = ReverbLoFilterDelay[nReverbLoDlyPos]; // echodly = delayed signal + ReverbLoFilterDelay[nReverbLoDlyPos] = echo >> 1; + nReverbLoDlyPos++; + nReverbLoDlyPos &= 0x1F; + int n = nReverbLoFltPos; + nReverbLoFltSum -= ReverbLoFilterBuffer[n]; + int tmp = echo / 128; + ReverbLoFilterBuffer[n] = tmp; + nReverbLoFltSum += tmp; + echodly -= nReverbLoFltSum; + nReverbLoFltPos = (n + 1) & 0x3F; + // Reverb + int v = (pin[0]+pin[1]) >> nFilterAttn; + pr[0] += pin[0] + echodly; + pr[1] += pin[1] + echodly; + v += echodly >> 2; + ReverbBuffer3[nReverbBufferPos3] = v; + ReverbBuffer4[nReverbBufferPos4] = v; + v += echodly >> 4; + v >>= 1; + gRvbLPSum -= gRvbLowPass[gRvbLPPos]; + gRvbLPSum += v; + gRvbLowPass[gRvbLPPos] = v; + gRvbLPPos++; + gRvbLPPos &= 7; + int vlp = gRvbLPSum >> 2; + ReverbBuffer[nReverbBufferPos] = vlp; + ReverbBuffer2[nReverbBufferPos2] = vlp; + if (++nReverbBufferPos >= nReverbSize) nReverbBufferPos = 0; + if (++nReverbBufferPos2 >= nReverbSize2) nReverbBufferPos2 = 0; + if (++nReverbBufferPos3 >= nReverbSize3) nReverbBufferPos3 = 0; + if (++nReverbBufferPos4 >= nReverbSize4) nReverbBufferPos4 = 0; + pr += 2; + pin += 2; + } while (--rvbcount); + } +#endif + // Dolby Pro-Logic Surround + if (gdwSoundSetup & SNDMIX_SURROUND) + { + int *pr = MixSoundBuffer, n = nDolbyLoFltPos; + for (int r=count; r; r--) + { + int v = (pr[0]+pr[1]+DOLBYATTNROUNDUP) >> (nDolbyHiFltAttn+1); +#ifndef MODPLUG_FASTSOUNDLIB + v *= (int)nDolbyDepth; +#endif + // Low-Pass Filter + nDolbyHiFltSum -= DolbyHiFilterBuffer[nDolbyHiFltPos]; + DolbyHiFilterBuffer[nDolbyHiFltPos] = v; + nDolbyHiFltSum += v; + v = nDolbyHiFltSum; + nDolbyHiFltPos++; + nDolbyHiFltPos &= nDolbyHiFltMask; + // Surround + int secho = SurroundBuffer[nSurroundPos]; + SurroundBuffer[nSurroundPos] = v; + // Delay line and remove low frequencies + v = DolbyLoFilterDelay[nDolbyLoDlyPos]; // v = delayed signal + DolbyLoFilterDelay[nDolbyLoDlyPos] = secho; // secho = signal + nDolbyLoDlyPos++; + nDolbyLoDlyPos &= 0x1F; + nDolbyLoFltSum -= DolbyLoFilterBuffer[n]; + int tmp = secho / 64; + DolbyLoFilterBuffer[n] = tmp; + nDolbyLoFltSum += tmp; + v -= nDolbyLoFltSum; + n++; + n &= 0x3F; + // Add echo + pr[0] += v; + pr[1] -= v; + if (++nSurroundPos >= nSurroundSize) nSurroundPos = 0; + pr += 2; + } + nDolbyLoFltPos = n; + } + // Bass Expansion + if (gdwSoundSetup & SNDMIX_MEGABASS) + { + int *px = MixSoundBuffer; + int xba = m_nXBassDepth+1, xbamask = (1 << xba) - 1; + int n = nXBassBufferPos; + for (int x=count; x; x--) + { + nXBassSum -= XBassBuffer[n]; + int tmp0 = px[0] + px[1]; + int tmp = (tmp0 + ((tmp0 >> 31) & xbamask)) >> xba; + XBassBuffer[n] = tmp; + nXBassSum += tmp; + int v = XBassDelay[nXBassDlyPos]; + XBassDelay[nXBassDlyPos] = px[0]; + px[0] = v + nXBassSum; + v = XBassDelay[nXBassDlyPos+1]; + XBassDelay[nXBassDlyPos+1] = px[1]; + px[1] = v + nXBassSum; + nXBassDlyPos = (nXBassDlyPos + 2) & nXBassMask; + px += 2; + n++; + n &= nXBassMask; + } + nXBassBufferPos = n; + } + // Noise Reduction + if (gdwSoundSetup & SNDMIX_NOISEREDUCTION) + { + int n1 = nLeftNR, n2 = nRightNR; + int *pnr = MixSoundBuffer; + for (int nr=count; nr; nr--) + { + int vnr = pnr[0] >> 1; + pnr[0] = vnr + n1; + n1 = vnr; + vnr = pnr[1] >> 1; + pnr[1] = vnr + n2; + n2 = vnr; + pnr += 2; + } + nLeftNR = n1; + nRightNR = n2; + } +} + + +void CSoundFile::ProcessMonoDSP(int count) +//---------------------------------------- +{ +#ifndef MODPLUG_NO_REVERB + // Reverb + if (gdwSoundSetup & SNDMIX_REVERB) + { + int *pr = MixSoundBuffer, rvbcount = count, *pin = MixReverbBuffer; + do + { + int echo = ReverbBuffer[nReverbBufferPos] + ReverbBuffer2[nReverbBufferPos2] + + ReverbBuffer3[nReverbBufferPos3] + ReverbBuffer4[nReverbBufferPos4]; // echo = reverb signal + // Delay line and remove Low Frequencies // v = original signal + int echodly = ReverbLoFilterDelay[nReverbLoDlyPos]; // echodly = delayed signal + ReverbLoFilterDelay[nReverbLoDlyPos] = echo >> 1; + nReverbLoDlyPos++; + nReverbLoDlyPos &= 0x1F; + int n = nReverbLoFltPos; + nReverbLoFltSum -= ReverbLoFilterBuffer[n]; + int tmp = echo / 128; + ReverbLoFilterBuffer[n] = tmp; + nReverbLoFltSum += tmp; + echodly -= nReverbLoFltSum; + nReverbLoFltPos = (n + 1) & 0x3F; + // Reverb + int v = pin[0] >> (nFilterAttn-1); + *pr++ += pin[0] + echodly; + pin++; + v += echodly >> 2; + ReverbBuffer3[nReverbBufferPos3] = v; + ReverbBuffer4[nReverbBufferPos4] = v; + v += echodly >> 4; + v >>= 1; + gRvbLPSum -= gRvbLowPass[gRvbLPPos]; + gRvbLPSum += v; + gRvbLowPass[gRvbLPPos] = v; + gRvbLPPos++; + gRvbLPPos &= 7; + int vlp = gRvbLPSum >> 2; + ReverbBuffer[nReverbBufferPos] = vlp; + ReverbBuffer2[nReverbBufferPos2] = vlp; + if (++nReverbBufferPos >= nReverbSize) nReverbBufferPos = 0; + if (++nReverbBufferPos2 >= nReverbSize2) nReverbBufferPos2 = 0; + if (++nReverbBufferPos3 >= nReverbSize3) nReverbBufferPos3 = 0; + if (++nReverbBufferPos4 >= nReverbSize4) nReverbBufferPos4 = 0; + } while (--rvbcount); + } +#endif + // Bass Expansion + if (gdwSoundSetup & SNDMIX_MEGABASS) + { + int *px = MixSoundBuffer; + int xba = m_nXBassDepth, xbamask = (1 << xba)-1; + int n = nXBassBufferPos; + for (int x=count; x; x--) + { + nXBassSum -= XBassBuffer[n]; + int tmp0 = *px; + int tmp = (tmp0 + ((tmp0 >> 31) & xbamask)) >> xba; + XBassBuffer[n] = tmp; + nXBassSum += tmp; + int v = XBassDelay[nXBassDlyPos]; + XBassDelay[nXBassDlyPos] = *px; + *px++ = v + nXBassSum; + nXBassDlyPos = (nXBassDlyPos + 2) & nXBassMask; + n++; + n &= nXBassMask; + } + nXBassBufferPos = n; + } + // Noise Reduction + if (gdwSoundSetup & SNDMIX_NOISEREDUCTION) + { + int n = nLeftNR; + int *pnr = MixSoundBuffer; + for (int nr=count; nr; pnr++, nr--) + { + int vnr = *pnr >> 1; + *pnr = vnr + n; + n = vnr; + } + nLeftNR = n; + } +} + + +///////////////////////////////////////////////////////////////// +// Clean DSP Effects interface + +// [Reverb level 0(quiet)-100(loud)], [delay in ms, usually 40-200ms] +BOOL CSoundFile::SetReverbParameters(UINT nDepth, UINT nDelay) +//------------------------------------------------------------ +{ + if (nDepth > 100) nDepth = 100; + UINT gain = nDepth / 20; + if (gain > 4) gain = 4; + m_nReverbDepth = 4 - gain; + if (nDelay < 40) nDelay = 40; + if (nDelay > 250) nDelay = 250; + m_nReverbDelay = nDelay; + return TRUE; +} + + +// [XBass level 0(quiet)-100(loud)], [cutoff in Hz 20-100] +BOOL CSoundFile::SetXBassParameters(UINT nDepth, UINT nRange) +//----------------------------------------------------------- +{ + if (nDepth > 100) nDepth = 100; + UINT gain = nDepth / 20; + if (gain > 4) gain = 4; + m_nXBassDepth = 8 - gain; // filter attenuation 1/256 .. 1/16 + UINT range = nRange / 5; + if (range > 5) range -= 5; else range = 0; + if (nRange > 16) nRange = 16; + m_nXBassRange = 21 - range; // filter average on 0.5-1.6ms + return TRUE; +} + + +// [Surround level 0(quiet)-100(heavy)] [delay in ms, usually 5-50ms] +BOOL CSoundFile::SetSurroundParameters(UINT nDepth, UINT nDelay) +//-------------------------------------------------------------- +{ + UINT gain = (nDepth * 16) / 100; + if (gain > 16) gain = 16; + if (gain < 1) gain = 1; + m_nProLogicDepth = gain; + if (nDelay < 4) nDelay = 4; + if (nDelay > 50) nDelay = 50; + m_nProLogicDelay = nDelay; + return TRUE; +} + +BOOL CSoundFile::SetWaveConfigEx(BOOL bSurround,BOOL bNoOverSampling,BOOL bReverb,BOOL hqido,BOOL bMegaBass,BOOL bNR,BOOL bEQ) +//---------------------------------------------------------------------------------------------------------------------------- +{ + DWORD d = gdwSoundSetup & ~(SNDMIX_SURROUND | SNDMIX_NORESAMPLING | SNDMIX_REVERB | SNDMIX_HQRESAMPLER | SNDMIX_MEGABASS | SNDMIX_NOISEREDUCTION | SNDMIX_EQ); + if (bSurround) d |= SNDMIX_SURROUND; + if (bNoOverSampling) d |= SNDMIX_NORESAMPLING; + if (bReverb) d |= SNDMIX_REVERB; + if (hqido) d |= SNDMIX_HQRESAMPLER; + if (bMegaBass) d |= SNDMIX_MEGABASS; + if (bNR) d |= SNDMIX_NOISEREDUCTION; + if (bEQ) d |= SNDMIX_EQ; + gdwSoundSetup = d; + InitPlayer(FALSE); + return TRUE; +} diff --git a/modplug/snd_eq.cpp b/modplug/snd_eq.cpp new file mode 100644 index 000000000..85f07433b --- /dev/null +++ b/modplug/snd_eq.cpp @@ -0,0 +1,228 @@ +/* + * This program is free software; you can redistribute it and modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the license or (at your + * option) any later version. + * + * Authors: Olivier Lapicque + * + * Name Date Description + * + * Olivier Lapicque --/--/-- Creation + * Trevor Nunes 26/01/04 conditional compilation for AMD,MMX calls + * +*/ +#include "stdafx.h" +#include "sndfile.h" +#include + + +#define EQ_BANDWIDTH 2.0 +#define EQ_ZERO 0.000001 +#define REAL float + +extern REAL MixFloatBuffer[]; + +extern void StereoMixToFloat(const int *pSrc, float *pOut1, float *pOut2, UINT nCount); +extern void FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount); +extern void MonoMixToFloat(const int *pSrc, float *pOut, UINT nCount); +extern void FloatToMonoMix(const float *pIn, int *pOut, UINT nCount); + +typedef struct _EQBANDSTRUCT +{ + REAL a0, a1, a2, b1, b2; + REAL x1, x2, y1, y2; + REAL Gain, CenterFrequency; + BOOL bEnable; +} EQBANDSTRUCT, *PEQBANDSTRUCT; + +UINT gEqLinearToDB[33] = +{ + 16, 19, 22, 25, 28, 31, 34, 37, + 40, 43, 46, 49, 52, 55, 58, 61, + 64, 76, 88, 100, 112, 124, 136, 148, + 160, 172, 184, 196, 208, 220, 232, 244, 256 +}; + + +static REAL f2ic = (REAL)(1 << 28); +static REAL i2fc = (REAL)(1.0 / (1 << 28)); + +static EQBANDSTRUCT gEQ[MAX_EQ_BANDS*2] = +{ + // Default: Flat EQ + {0,0,0,0,0, 0,0,0,0, 1, 120, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 600, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 1200, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 3000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 6000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 10000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 120, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 600, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 1200, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 3000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 6000, FALSE}, + {0,0,0,0,0, 0,0,0,0, 1, 10000, FALSE}, +}; + +void EQFilter(EQBANDSTRUCT *pbs, REAL *pbuffer, UINT nCount) +//---------------------------------------------------------- +{ + for (UINT i=0; ia1 * pbs->x1 + pbs->a2 * pbs->x2 + pbs->a0 * x + pbs->b1 * pbs->y1 + pbs->b2 * pbs->y2; + pbs->x2 = pbs->x1; + pbs->y2 = pbs->y1; + pbs->x1 = x; + pbuffer[i] = y; + pbs->y1 = y; + } +} + +void CSoundFile::EQMono(int *pbuffer, UINT nCount) +//------------------------------------------------ +{ + MonoMixToFloat(pbuffer, MixFloatBuffer, nCount); + for (UINT b=0; b 0.45f) gEQ[band].Gain = 1; + // if (f > 0.25) f = 0.25; + // k = tan(PI*f); + k = f * 3.141592654f; + k = k + k*f; +// if (k > (REAL)0.707) k = (REAL)0.707; + k2 = k*k; + v0 = gEQ[band].Gain; + v1 = 1; + if (gEQ[band].Gain < 1.0) + { + v0 *= (0.5f/EQ_BANDWIDTH); + v1 *= (0.5f/EQ_BANDWIDTH); + } else + { + v0 *= (1.0f/EQ_BANDWIDTH); + v1 *= (1.0f/EQ_BANDWIDTH); + } + r = (1 + v0*k + k2) / (1 + v1*k + k2); + if (r != gEQ[band].a0) + { + gEQ[band].a0 = r; + b = TRUE; + } + r = 2 * (k2 - 1) / (1 + v1*k + k2); + if (r != gEQ[band].a1) + { + gEQ[band].a1 = r; + b = TRUE; + } + r = (1 - v0*k + k2) / (1 + v1*k + k2); + if (r != gEQ[band].a2) + { + gEQ[band].a2 = r; + b = TRUE; + } + r = - 2 * (k2 - 1) / (1 + v1*k + k2); + if (r != gEQ[band].b1) + { + gEQ[band].b1 = r; + b = TRUE; + } + r = - (1 - v1*k + k2) / (1 + v1*k + k2); + if (r != gEQ[band].b2) + { + gEQ[band].b2 = r; + b = TRUE; + } + if (b) + { + gEQ[band].x1 = 0; + gEQ[band].x2 = 0; + gEQ[band].y1 = 0; + gEQ[band].y2 = 0; + } + } else + { + gEQ[band].a0 = 0; + gEQ[band].a1 = 0; + gEQ[band].a2 = 0; + gEQ[band].b1 = 0; + gEQ[band].b2 = 0; + gEQ[band].x1 = 0; + gEQ[band].x2 = 0; + gEQ[band].y1 = 0; + gEQ[band].y2 = 0; + } +} + + +void CSoundFile::SetEQGains(const UINT *pGains, UINT nGains, const UINT *pFreqs, BOOL bReset) +//------------------------------------------------------------------------------------------- +{ + for (UINT i=0; i 32) n = 32; + g = 1.0 + (((double)n) / 64.0); + if (pFreqs) f = (REAL)(int)pFreqs[i]; + } else + { + g = 1; + } + gEQ[i].Gain = g; + gEQ[i].CenterFrequency = f; + gEQ[i+MAX_EQ_BANDS].Gain = g; + gEQ[i+MAX_EQ_BANDS].CenterFrequency = f; + if (f > 20.0f && i < nGains) /* don't enable bands outside... */ + { + gEQ[i].bEnable = TRUE; + gEQ[i+MAX_EQ_BANDS].bEnable = TRUE; + } else + { + gEQ[i].bEnable = FALSE; + gEQ[i+MAX_EQ_BANDS].bEnable = FALSE; + } + } + InitializeEQ(bReset); +} diff --git a/modplug/snd_flt.cpp b/modplug/snd_flt.cpp new file mode 100644 index 000000000..f9a22b774 --- /dev/null +++ b/modplug/snd_flt.cpp @@ -0,0 +1,140 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +// AWE32: cutoff = reg[0-255] * 31.25 + 100 -> [100Hz-8060Hz] +// EMU10K1 docs: cutoff = reg[0-127]*62+100 + +#ifndef NO_FILTER + +#ifdef MSC_VER +#define _ASM_MATH +#endif + +#ifdef _ASM_MATH + +// pow(a,b) returns a^^b -> 2^^(b.log2(a)) +static float pow(float a, float b) +{ + long tmpint; + float result; + _asm { + fld b // Load b + fld a // Load a + fyl2x // ST(0) = b.log2(a) + fist tmpint // Store integer exponent + fisub tmpint // ST(0) = -1 <= (b*log2(a)) <= 1 + f2xm1 // ST(0) = 2^(x)-1 + fild tmpint // load integer exponent + fld1 // Load 1 + fscale // ST(0) = 2^ST(1) + fstp ST(1) // Remove the integer from the stack + fmul ST(1), ST(0) // multiply with fractional part + faddp ST(1), ST(0) // add integer_part + fstp result // Store the result + } + return result; +} + + +#else + +#include + +#endif // _ASM_MATH + + +#define PI ((double)3.14159265358979323846) +#define LOG10 ((double)2.30258509299) +#define SB ((double)1.059463094359295309843105314939748495817) + +// Simple 2-poles resonant filter +void CSoundFile::SetupChannelFilter(MODCHANNEL *pChn, BOOL bReset, int flt_modifier, + int freq) const +//---------------------------------------------------------------------------------------- +{ + double fs = (double)gdwMixingFreq; + +#if 0 + double fc; +#endif + + double fx; +//if (freq) fs = (double)freq; + fx = (m_dwSongFlags & SONG_EXFILTERRANGE) ? 21.0 : 22.0; + + double cutoff = pChn->nCutOff * (flt_modifier+256) + / (double)512.0f; + + double inv_angle = (fs * pow(0.5, 0.25 + cutoff/fx)) / (PI*256.0); + +// double inv_angle = pow(2.0,(127.0-cutoff)/fx)-0.93; + + if (!inv_angle) return; /* avoid FPE */ + double rr = pChn->nResonance; +// double loss = pow(10.0f, -((double)rr / 256.0f)); + double loss = exp(rr * (-LOG10*1.2/128.0));/* tried 256.0 */ + + if (m_dwSongFlags & SONG_EXFILTERRANGE) { + loss *= (double)2.0; + } + + double a,b,c,d, e; + + d = (1.0f - loss) / inv_angle; + if (d > 2.0f) d = 2.0f; + d = (loss - d) * inv_angle; + e = inv_angle * inv_angle; + if (1.0+d+e == 0) return; + a = 1.0f / (1.0f + d + e); + c = -e * a; + b = 1.0f - a - c; + + pChn->nFilter_A0 = a; + pChn->nFilter_B0 = b; + pChn->nFilter_B1 = c; + + if (bReset) + { + pChn->nFilter_Y1 = pChn->nFilter_Y2 = 0; + pChn->nFilter_Y3 = pChn->nFilter_Y4 = 0; + } + pChn->dwFlags |= CHN_FILTER; +#if 0 + + + + fc = 110.0f * pow(2.0, 0.25f+((double)(pChn->nCutOff*(flt_modifier+256)))/(fx*512.0f)); + fc *= (double)(2.0*PI/fs); + + double fg, fb0, fb1; + + double dmpfac = pow(10.0f, -((fx/128.0)*(double)pChn->nResonance) / fx); + double d = (1.0f-2.0f*dmpfac)* fc; + if (d>2.0) d = 2.0; + d = (2.0*dmpfac - d)/fc; + double e = pow(2.0/fc,2.0); + + fg=1.0/(1.0+d+e); + fb0=(d+e+e)/(1.0+d+e); + fb1=-e/(1.0+d+e); + + pChn->nFilter_A0 = fg; + pChn->nFilter_B0 = fb0; + pChn->nFilter_B1 = fb1; + + if (bReset) + { + pChn->nFilter_Y1 = pChn->nFilter_Y2 = 0; + pChn->nFilter_Y3 = pChn->nFilter_Y4 = 0; + } + pChn->dwFlags |= CHN_FILTER; +#endif +} + +#endif // NO_FILTER diff --git a/modplug/snd_fx.cpp b/modplug/snd_fx.cpp new file mode 100644 index 000000000..1d683af17 --- /dev/null +++ b/modplug/snd_fx.cpp @@ -0,0 +1,2674 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#ifdef MSC_VER +#pragma warning(disable:4244) +#endif + +// Tables defined in tables.cpp +extern BYTE ImpulseTrackerPortaVolCmd[16]; +extern WORD S3MFineTuneTable[16]; +extern WORD ProTrackerPeriodTable[6*12]; +extern WORD ProTrackerTunedPeriods[15*12]; +extern WORD FreqS3MTable[]; +extern WORD XMPeriodTable[96+8]; +extern UINT XMLinearTable[768]; +extern DWORD FineLinearSlideUpTable[16]; +extern DWORD FineLinearSlideDownTable[16]; +extern DWORD LinearSlideUpTable[256]; +extern DWORD LinearSlideDownTable[256]; +extern signed char retrigTable1[16]; +extern signed char retrigTable2[16]; +extern short int ModRandomTable[64]; + + +//////////////////////////////////////////////////////////// +// Length + +DWORD CSoundFile::GetLength(BOOL bAdjust, BOOL bTotal) +//---------------------------------------------------- +{ + UINT dwElapsedTime=0, nRow=0, nCurrentPattern=0, nNextPattern=0, nPattern=Order[0]; + UINT nMusicSpeed=m_nDefaultSpeed, nMusicTempo=m_nDefaultTempo, nNextRow=0; + UINT nMaxRow = 0, nMaxPattern = 0; + LONG nGlbVol = m_nDefaultGlobalVolume, nOldGlbVolSlide = 0; + BYTE samples[MAX_CHANNELS]; + BYTE instr[MAX_CHANNELS]; + BYTE notes[MAX_CHANNELS]; + BYTE vols[MAX_CHANNELS]; + BYTE oldparam[MAX_CHANNELS]; + BYTE chnvols[MAX_CHANNELS]; + DWORD patloop[MAX_CHANNELS]; + + memset(instr, 0, sizeof(instr)); + memset(notes, 0, sizeof(notes)); + memset(vols, 0xFF, sizeof(vols)); + memset(patloop, 0, sizeof(patloop)); + memset(oldparam, 0, sizeof(oldparam)); + memset(chnvols, 64, sizeof(chnvols)); + memset(samples, 0, sizeof(samples)); + for (UINT icv=0; icv= MAX_PATTERNS) + { + // End of song ? + if ((nPattern == 0xFF) || (nCurrentPattern >= MAX_ORDERS)) + { + goto EndMod; + } else + { + nCurrentPattern++; + nPattern = (nCurrentPattern < MAX_ORDERS) ? Order[nCurrentPattern] : 0xFF; + } + nNextPattern = nCurrentPattern; + } + // Weird stuff? + if ((nPattern >= MAX_PATTERNS) || (!Patterns[nPattern])) break; + // Should never happen + if (nRow >= PatternSize[nPattern]) nRow = 0; + // Update next position + nNextRow = nRow + 1; + if (nNextRow >= PatternSize[nPattern]) + { + nNextPattern = nCurrentPattern + 1; + nNextRow = 0; + } + /* muahahaha */ + if (stop_at_order > -1 && stop_at_row > -1) { + if (stop_at_order <= nCurrentPattern && stop_at_row <= nRow) + goto EndMod; + if (stop_at_time > 0) { + /* stupid api decision */ + if (((dwElapsedTime+500) / 1000) >= stop_at_time) { + stop_at_order = nCurrentPattern; + stop_at_row = nRow; + goto EndMod; + } + } + } + + if (!nRow) + { + for (UINT ipck=0; ipck nMaxPattern) || ((nCurrentPattern == nMaxPattern) && (nRow >= nMaxRow))) + { + if (bAdjust) + { + m_nMusicSpeed = nMusicSpeed; + m_nMusicTempo = nMusicTempo; + } + break; + } + } + MODCHANNEL *pChn = Chn; + MODCOMMAND *p = Patterns[nPattern] + nRow * m_nChannels; + for (UINT nChn=0; nChncommand; + UINT param = p->param; + UINT note = p->note; + if (p->instr) { instr[nChn] = p->instr; notes[nChn] = 0; vols[nChn] = 0xFF; } + if ((note) && (note <= 120)) notes[nChn] = note; + if (p->volcmd == VOLCMD_VOLUME) { vols[nChn] = p->vol; } + if (command) switch (command) + { + // Position Jump + case CMD_POSITIONJUMP: + if (param <= nCurrentPattern) goto EndMod; + nNextPattern = param; + nNextRow = 0; + if (bAdjust) + { + pChn->nPatternLoopCount = 0; + pChn->nPatternLoop = 0; + } + break; + // Pattern Break + case CMD_PATTERNBREAK: + nNextRow = param; + nNextPattern = nCurrentPattern + 1; + if (bAdjust) + { + pChn->nPatternLoopCount = 0; + pChn->nPatternLoop = 0; + } + break; + // Set Speed + case CMD_SPEED: + if (!param) break; + if ((param <= 0x20) || (m_nType != MOD_TYPE_MOD)) + { + if (param < 128) nMusicSpeed = param; + } + break; + // Set Tempo + case CMD_TEMPO: + if ((bAdjust) && (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) + { + if (param) pChn->nOldTempo = param; else param = pChn->nOldTempo; + } + if (param >= 0x20) nMusicTempo = param; else + // Tempo Slide + // FIXME: this is totally wrong! + if ((param & 0xF0) == 0x10) + { + nMusicTempo += param & 0x0F; + if (nMusicTempo > 255) nMusicTempo = 255; + } else + { + nMusicTempo -= param & 0x0F; + if (nMusicTempo < 32) nMusicTempo = 32; + } + break; + // Pattern Delay + case CMD_S3MCMDEX: + if ((param & 0xF0) == 0x60) { nSpeedCount = param & 0x0F; break; } else + if ((param & 0xF0) == 0xB0) { param &= 0x0F; param |= 0x60; } + case CMD_MODCMDEX: + if ((param & 0xF0) == 0xE0) nSpeedCount = (param & 0x0F) * nMusicSpeed; else + if ((param & 0xF0) == 0x60) + { + if (param & 0x0F) dwElapsedTime += (dwElapsedTime - patloop[nChn]) * (param & 0x0F); + else patloop[nChn] = dwElapsedTime; + } + break; + } + if (!bAdjust) continue; + switch(command) + { + // Portamento Up/Down + case CMD_PORTAMENTOUP: + case CMD_PORTAMENTODOWN: + if (param) pChn->nOldPortaUpDown = param; + break; + // Tone-Portamento + case CMD_TONEPORTAMENTO: + if (param) pChn->nPortamentoSlide = param << 2; + break; + // Offset + case CMD_OFFSET: + if (param) pChn->nOldOffset = param; + break; + // Volume Slide + case CMD_VOLUMESLIDE: + case CMD_TONEPORTAVOL: + case CMD_VIBRATOVOL: + if (param) pChn->nOldVolumeSlide = param; + break; + // Set Volume + case CMD_VOLUME: + vols[nChn] = param; + break; + // Global Volume + case CMD_GLOBALVOLUME: + if (m_nType != MOD_TYPE_IT) param <<= 1; + if (param > 128) param = 128; + nGlbVol = param << 1; + break; + // Global Volume Slide + case CMD_GLOBALVOLSLIDE: + if (param) nOldGlbVolSlide = param; else param = nOldGlbVolSlide; + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + param >>= 4; + if (m_nType != MOD_TYPE_IT) param <<= 1; + nGlbVol += param << 1; + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + param = (param & 0x0F) << 1; + if (m_nType != MOD_TYPE_IT) param <<= 1; + nGlbVol -= param; + } else + if (param & 0xF0) + { + param >>= 4; + param <<= 1; + if (m_nType != MOD_TYPE_IT) param <<= 1; + nGlbVol += param * nMusicSpeed; + } else + { + param = (param & 0x0F) << 1; + if (m_nType != MOD_TYPE_IT) param <<= 1; + nGlbVol -= param * nMusicSpeed; + } + if (nGlbVol < 0) nGlbVol = 0; + if (nGlbVol > 256) nGlbVol = 256; + break; + case CMD_CHANNELVOLUME: + if (param <= 64) chnvols[nChn] = param; + break; + case CMD_CHANNELVOLSLIDE: + if (param) oldparam[nChn] = param; else param = oldparam[nChn]; + pChn->nOldChnVolSlide = param; + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + param = (param >> 4) + chnvols[nChn]; + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + if (chnvols[nChn] > (int)(param & 0x0F)) param = chnvols[nChn] - (param & 0x0F); + else param = 0; + } else + if (param & 0x0F) + { + param = (param & 0x0F) * nMusicSpeed; + param = (chnvols[nChn] > param) ? chnvols[nChn] - param : 0; + } else param = ((param & 0xF0) >> 4) * nMusicSpeed + chnvols[nChn]; + if (param > 64) param = 64; + chnvols[nChn] = param; + break; + } + } + nSpeedCount += nMusicSpeed; + dwElapsedTime += (2500 * nSpeedCount) / nMusicTempo; + } +EndMod: + if ((bAdjust) && (!bTotal)) + { + m_nGlobalVolume = nGlbVol; + m_nOldGlbVolSlide = nOldGlbVolSlide; + for (UINT n=0; n 64) vols[n] = 64; + Chn[n].nVolume = vols[n] << 2; + } + } + } + return (dwElapsedTime+500) / 1000; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Effects + +void CSoundFile::InstrumentChange(MODCHANNEL *pChn, UINT instr, BOOL bPorta, BOOL bUpdVol, BOOL bResetEnv) +//-------------------------------------------------------------------------------------------------------- +{ + BOOL bInstrumentChanged = FALSE; + + if (instr >= MAX_INSTRUMENTS) return; + INSTRUMENTHEADER *penv = (m_dwSongFlags & SONG_INSTRUMENTMODE) ? Headers[instr] : NULL; + MODINSTRUMENT *psmp = &Ins[instr]; + UINT note = pChn->nNewNote; + if ((penv) && (note) && (note <= 128)) + { + if (penv->NoteMap[note-1] >= 0xFE) return; + UINT n = penv->Keyboard[note-1]; + psmp = ((n) && (n < MAX_SAMPLES)) ? &Ins[n] : NULL; + } else + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + if (note >= 0xFE) return; + psmp = NULL; + } + // Update Volume + if (bUpdVol) pChn->nVolume = (psmp) ? psmp->nVolume : 0; + // bInstrumentChanged is used for IT carry-on env option + if (penv != pChn->pHeader) + { + bInstrumentChanged = TRUE; + pChn->pHeader = penv; + } else + // Special XM hack + if ((bPorta) && (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (penv) + && (pChn->pInstrument) && (psmp != pChn->pInstrument)) + { + // FT2 doesn't change the sample in this case, + // but still uses the sample info from the old one (bug?) + return; + } + // Instrument adjust + pChn->nNewIns = 0; + if (psmp) + { + psmp->played = 1; + if (penv) + { + penv->played = 1; + pChn->nInsVol = (psmp->nGlobalVol * penv->nGlobalVol) >> 6; + if (penv->dwFlags & ENV_SETPANNING) pChn->nPan = penv->nPan; + pChn->nNNA = penv->nNNA; + } else + { + pChn->nInsVol = psmp->nGlobalVol; + } + if (psmp->uFlags & CHN_PANNING) pChn->nPan = psmp->nPan; + } + // Reset envelopes + if (bResetEnv) + { + if ((!bPorta) || (!(m_nType & MOD_TYPE_IT)) || (m_dwSongFlags & SONG_ITCOMPATMODE) + || (!pChn->nLength) || ((pChn->dwFlags & CHN_NOTEFADE) && (!pChn->nFadeOutVol))) + { + pChn->dwFlags |= CHN_FASTVOLRAMP; + if ((m_nType & MOD_TYPE_IT) && (!bInstrumentChanged) && (penv) && (!(pChn->dwFlags & (CHN_KEYOFF|CHN_NOTEFADE)))) + { + if (!(penv->dwFlags & ENV_VOLCARRY)) pChn->nVolEnvPosition = 0; + if (!(penv->dwFlags & ENV_PANCARRY)) pChn->nPanEnvPosition = 0; + if (!(penv->dwFlags & ENV_PITCHCARRY)) pChn->nPitchEnvPosition = 0; + } else + { + pChn->nVolEnvPosition = 0; + pChn->nPanEnvPosition = 0; + pChn->nPitchEnvPosition = 0; + } + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + } else + if ((penv) && (!(penv->dwFlags & ENV_VOLUME))) + { + pChn->nVolEnvPosition = 0; + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + } + } + // Invalid sample ? + if (!psmp) + { + pChn->pInstrument = NULL; + pChn->nInsVol = 0; + return; + } + // Tone-Portamento doesn't reset the pingpong direction flag + if ((bPorta) && (psmp == pChn->pInstrument)) + { + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)) return; + pChn->dwFlags &= ~(CHN_KEYOFF|CHN_NOTEFADE); + pChn->dwFlags = (pChn->dwFlags & (0xFFFFFF00 | CHN_PINGPONGFLAG)) | (psmp->uFlags); + } else + { + pChn->dwFlags &= ~(CHN_KEYOFF|CHN_NOTEFADE|CHN_VOLENV|CHN_PANENV|CHN_PITCHENV); + pChn->dwFlags = (pChn->dwFlags & 0xFFFFFF00) | (psmp->uFlags); + if (penv) + { + if (penv->dwFlags & ENV_VOLUME) pChn->dwFlags |= CHN_VOLENV; + if (penv->dwFlags & ENV_PANNING) pChn->dwFlags |= CHN_PANENV; + if (penv->dwFlags & ENV_PITCH) pChn->dwFlags |= CHN_PITCHENV; + if ((penv->dwFlags & ENV_PITCH) && (penv->dwFlags & ENV_FILTER)) + { + if (!pChn->nCutOff) pChn->nCutOff = 0x7F; + } + if (penv->nIFC & 0x80) pChn->nCutOff = penv->nIFC & 0x7F; + if (penv->nIFR & 0x80) pChn->nResonance = penv->nIFR & 0x7F; + } + pChn->nVolSwing = pChn->nPanSwing = 0; + } + pChn->pInstrument = psmp; + pChn->nLength = psmp->nLength; + pChn->nLoopStart = psmp->nLoopStart; + pChn->nLoopEnd = psmp->nLoopEnd; + pChn->nC4Speed = psmp->nC4Speed; + pChn->pSample = psmp->pSample; + pChn->nTranspose = psmp->RelativeTone; + pChn->nFineTune = psmp->nFineTune; + if (pChn->dwFlags & CHN_SUSTAINLOOP) + { + pChn->nLoopStart = psmp->nSustainStart; + pChn->nLoopEnd = psmp->nSustainEnd; + pChn->dwFlags |= CHN_LOOP; + if (pChn->dwFlags & CHN_PINGPONGSUSTAIN) pChn->dwFlags |= CHN_PINGPONGLOOP; + } + if ((pChn->dwFlags & CHN_LOOP) && (pChn->nLoopEnd < pChn->nLength)) pChn->nLength = pChn->nLoopEnd; +} + + +void CSoundFile::NoteChange(UINT nChn, int note, BOOL bPorta, BOOL bResetEnv, BOOL bManual) +//----------------------------------------------------------------------------------------- +{ + if (note < 1) return; + MODCHANNEL * const pChn = &Chn[nChn]; + MODINSTRUMENT *pins = pChn->pInstrument; + INSTRUMENTHEADER *penv = (m_dwSongFlags & SONG_INSTRUMENTMODE) ? pChn->pHeader : NULL; + if ((penv) && (note <= 0x80)) + { + UINT n = penv->Keyboard[note - 1]; + if ((n) && (n < MAX_SAMPLES)) pins = &Ins[n]; + note = penv->NoteMap[note-1]; + } + // Key Off + if (note >= 0x80) // 0xFE or invalid note => key off + { + // technically this is "wrong", as anything besides ^^^, ===, and a valid note + // should cause a note fade... (oh well, it's just a quick hack anyway.) + if (note == 0xFD) { + pChn->dwFlags |= CHN_NOTEFADE; + return; + } + + // Key Off + KeyOff(nChn); + // Note Cut + if (note == 0xFE) + { + pChn->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + if ((!(m_nType & MOD_TYPE_IT)) || (m_dwSongFlags & SONG_INSTRUMENTMODE)) + pChn->nVolume = 0; + pChn->nFadeOutVol = 0; + } + return; + } + if (!pins) return; + if ((!bPorta) && (m_nType & (MOD_TYPE_XM|MOD_TYPE_MED|MOD_TYPE_MT2))) + { + pChn->nTranspose = pins->RelativeTone; + pChn->nFineTune = pins->nFineTune; + } + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2|MOD_TYPE_MED)) note += pChn->nTranspose; + if (note < 1) note = 1; + if (note > 132) note = 132; + pChn->nNote = note; + if ((!bPorta) || (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) pChn->nNewIns = 0; + UINT period = GetPeriodFromNote(note, pChn->nFineTune, pChn->nC4Speed); + if (period) + { + if ((!bPorta) || (!pChn->nPeriod)) pChn->nPeriod = period; + pChn->nPortamentoDest = period; + if ((!bPorta) || ((!pChn->nLength) && (!(m_nType & MOD_TYPE_S3M)))) + { + pChn->pInstrument = pins; + pChn->pSample = pins->pSample; + pChn->nLength = pins->nLength; + pChn->nLoopEnd = pins->nLength; + pChn->nLoopStart = 0; + pChn->dwFlags = (pChn->dwFlags & 0xFFFFFF00) | (pins->uFlags); + if (pChn->dwFlags & CHN_SUSTAINLOOP) + { + pChn->nLoopStart = pins->nSustainStart; + pChn->nLoopEnd = pins->nSustainEnd; + pChn->dwFlags &= ~CHN_PINGPONGLOOP; + pChn->dwFlags |= CHN_LOOP; + if (pChn->dwFlags & CHN_PINGPONGSUSTAIN) pChn->dwFlags |= CHN_PINGPONGLOOP; + if (pChn->nLength > pChn->nLoopEnd) pChn->nLength = pChn->nLoopEnd; + } else + if (pChn->dwFlags & CHN_LOOP) + { + pChn->nLoopStart = pins->nLoopStart; + pChn->nLoopEnd = pins->nLoopEnd; + if (pChn->nLength > pChn->nLoopEnd) pChn->nLength = pChn->nLoopEnd; + } + pChn->nPos = 0; + pChn->nPosLo = 0; + if (pChn->nVibratoType < 4) pChn->nVibratoPos = ((m_nType & MOD_TYPE_IT) && (!(m_dwSongFlags & SONG_ITOLDEFFECTS))) ? 0x10 : 0; + if (pChn->nTremoloType < 4) pChn->nTremoloPos = 0; + } + if (pChn->nPos >= pChn->nLength) pChn->nPos = pChn->nLoopStart; + } else bPorta = FALSE; + if ((!bPorta) || (!(m_nType & MOD_TYPE_IT)) + || ((pChn->dwFlags & CHN_NOTEFADE) && (!pChn->nFadeOutVol)) + || ((m_dwSongFlags & SONG_ITCOMPATMODE) && (pChn->nRowInstr))) + { + if ((m_nType & MOD_TYPE_IT) && (pChn->dwFlags & CHN_NOTEFADE) && (!pChn->nFadeOutVol)) + { + pChn->nVolEnvPosition = 0; + pChn->nPanEnvPosition = 0; + pChn->nPitchEnvPosition = 0; + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + pChn->dwFlags &= ~CHN_NOTEFADE; + pChn->nFadeOutVol = 65536; + } + if ((!bPorta) || (!(m_dwSongFlags & SONG_ITCOMPATMODE)) || (pChn->nRowInstr)) + { + if ((!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) || (pChn->nRowInstr)) + { + pChn->dwFlags &= ~CHN_NOTEFADE; + pChn->nFadeOutVol = 65536; + } + } + } + pChn->dwFlags &= ~(CHN_EXTRALOUD|CHN_KEYOFF); + // Enable Ramping + if (!bPorta) + { + pChn->nVUMeter = 0x100; + pChn->nLeftVU = pChn->nRightVU = 0xFF; + pChn->dwFlags &= ~CHN_FILTER; + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->nRetrigCount = 0; + pChn->nTremorCount = 0; + if (bResetEnv) + { + pChn->nVolSwing = pChn->nPanSwing = 0; + if (penv) + { + if (!(penv->dwFlags & ENV_VOLCARRY)) pChn->nVolEnvPosition = 0; + if (!(penv->dwFlags & ENV_PANCARRY)) pChn->nPanEnvPosition = 0; + if (!(penv->dwFlags & ENV_PITCHCARRY)) pChn->nPitchEnvPosition = 0; + if (m_nType & MOD_TYPE_IT) + { + // Volume Swing + if (penv->nVolSwing) + { + int d = ((LONG)penv->nVolSwing*(LONG)((rand() & 0xFF) - 0x7F)) / 128; + pChn->nVolSwing = (signed short)((d * pChn->nVolume + 1)/128); + } + // Pan Swing + if (penv->nPanSwing) + { + int d = ((LONG)penv->nPanSwing*(LONG)((rand() & 0xFF) - 0x7F)) / 128; + pChn->nPanSwing = (signed short)d; + } + } + } + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + } + pChn->nLeftVol = pChn->nRightVol = 0; + BOOL bFlt = (m_dwSongFlags & SONG_MPTFILTERMODE) ? FALSE : TRUE; + // Setup Initial Filter for this note + if (penv) + { + if (penv->nIFR & 0x80) { pChn->nResonance = penv->nIFR & 0x7F; bFlt = TRUE; } + if (penv->nIFC & 0x80) { pChn->nCutOff = penv->nIFC & 0x7F; bFlt = TRUE; } + } else + { + pChn->nVolSwing = pChn->nPanSwing = 0; + } +#ifndef NO_FILTER + if ((pChn->nCutOff < 0x7F) && (bFlt)) SetupChannelFilter(pChn, TRUE); +#endif // NO_FILTER + } + // Special case for MPT + if (bManual) pChn->dwFlags &= ~CHN_MUTE; + if (((pChn->dwFlags & CHN_MUTE) && (gdwSoundSetup & SNDMIX_MUTECHNMODE)) + || ((pChn->pInstrument) && (pChn->pInstrument->uFlags & CHN_MUTE) && (!bManual)) + || ((m_dwSongFlags & SONG_INSTRUMENTMODE) && (pChn->pHeader) + && (pChn->pHeader->dwFlags & ENV_MUTE) && (!bManual))) + { + if (!bManual) pChn->nPeriod = 0; + } +} + + +UINT CSoundFile::GetNNAChannel(UINT nChn) +//--------------------------------------------- +{ + MODCHANNEL *pChn = &Chn[nChn]; + // Check for empty channel + MODCHANNEL *pi = &Chn[m_nChannels]; + for (UINT i=m_nChannels; inLength) { + if (pi->dwFlags & CHN_MUTE) { + if (pi->dwFlags & CHN_NNAMUTE) { + pi->dwFlags &= ~(CHN_NNAMUTE|CHN_MUTE); + } else { + /* this channel is muted; skip */ + continue; + } + } + return i; + } + } + if (!pChn->nFadeOutVol) return 0; + // All channels are used: check for lowest volume + UINT result = 0; + DWORD vol = 64*65536; // 25% + DWORD envpos = 0xFFFFFF; + const MODCHANNEL *pj = &Chn[m_nChannels]; + for (UINT j=m_nChannels; jnFadeOutVol) return j; + DWORD v = pj->nVolume; + if (pj->dwFlags & CHN_NOTEFADE) + v = v * pj->nFadeOutVol; + else + v <<= 16; + if (pj->dwFlags & CHN_LOOP) v >>= 1; + if ((v < vol) || ((v == vol) && (pj->nVolEnvPosition > envpos))) + { + envpos = pj->nVolEnvPosition; + vol = v; + result = j; + } + } + if (result) { + /* unmute new nna channel */ + Chn[result].dwFlags &= ~(CHN_MUTE|CHN_NNAMUTE); + } + return result; +} + + +void CSoundFile::CheckNNA(UINT nChn, UINT instr, int note, BOOL bForceCut) +//------------------------------------------------------------------------ +{ + MODCHANNEL *p; + MODCHANNEL *pChn = &Chn[nChn]; + INSTRUMENTHEADER *penv = (m_dwSongFlags & SONG_INSTRUMENTMODE) ? pChn->pHeader : NULL; + INSTRUMENTHEADER *pHeader; + signed char *pSample; + if (note > 0x80) note = 0; + if (note < 1) return; + // Always NNA cut - using + if ((!(m_nType & (MOD_TYPE_IT|MOD_TYPE_MT2))) || (!(m_dwSongFlags & SONG_INSTRUMENTMODE)) || (bForceCut)) + { + if ((m_dwSongFlags & SONG_CPUVERYHIGH) + || (!pChn->nLength) || (pChn->dwFlags & CHN_MUTE) + || ((!pChn->nLeftVol) && (!pChn->nRightVol))) return; + UINT n = GetNNAChannel(nChn); + if (!n) return; + p = &Chn[n]; + // Copy Channel + *p = *pChn; + p->dwFlags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PANBRELLO|CHN_PORTAMENTO); + p->nMasterChn = nChn+1; + p->nCommand = 0; + // Cut the note + p->nFadeOutVol = 0; + p->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + // Stop this channel + pChn->nLength = pChn->nPos = pChn->nPosLo = 0; + pChn->nROfs = pChn->nLOfs = 0; + pChn->nLeftVol = pChn->nRightVol = 0; + return; + } + if (instr >= MAX_INSTRUMENTS) instr = 0; + pSample = pChn->pSample; + pHeader = pChn->pHeader; + if ((instr) && (note)) + { + pHeader = (m_dwSongFlags & SONG_INSTRUMENTMODE) ? Headers[instr] : NULL; + if (pHeader) + { + UINT n = 0; + if (note <= 0x80) + { + n = pHeader->Keyboard[note-1]; + note = pHeader->NoteMap[note-1]; + if ((n) && (n < MAX_SAMPLES)) pSample = Ins[n].pSample; + } + } else pSample = NULL; + } + if (!penv) return; + p = pChn; + for (UINT i=nChn; i= m_nChannels) || (p == pChn)) + { + if (((p->nMasterChn == nChn+1) || (p == pChn)) && (p->pHeader)) + { + BOOL bOk = FALSE; + // Duplicate Check Type + switch(p->pHeader->nDCT) + { + // Note + case DCT_NOTE: + if ((note) && (p->nNote == note) && (pHeader == p->pHeader)) bOk = TRUE; + break; + // Sample + case DCT_SAMPLE: + if ((pSample) && (pSample == p->pSample)) bOk = TRUE; + break; + // Instrument + case DCT_INSTRUMENT: + if (pHeader == p->pHeader) bOk = TRUE; + break; + } + // Duplicate Note Action + if (bOk) + { + switch(p->pHeader->nDNA) + { + // Cut + case DNA_NOTECUT: + KeyOff(i); + p->nVolume = 0; + break; + // Note Off + case DNA_NOTEOFF: + KeyOff(i); + break; + // Note Fade + case DNA_NOTEFADE: + p->dwFlags |= CHN_NOTEFADE; + break; + } + if (!p->nVolume) + { + p->nFadeOutVol = 0; + p->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + } + } + } + } + if (pChn->dwFlags & CHN_MUTE) return; + // New Note Action + if ((pChn->nVolume) && (pChn->nLength)) + { + UINT n = GetNNAChannel(nChn); + if (n) + { + p = &Chn[n]; + // Copy Channel + *p = *pChn; + p->dwFlags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PANBRELLO|CHN_PORTAMENTO); + p->nMasterChn = nChn+1; + p->nCommand = 0; + // Key Off the note + switch(pChn->nNNA) + { + case NNA_NOTEOFF: KeyOff(n); break; + case NNA_NOTECUT: + p->nFadeOutVol = 0; + case NNA_NOTEFADE: p->dwFlags |= CHN_NOTEFADE; break; + } + if (!p->nVolume) + { + p->nFadeOutVol = 0; + p->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); + } + // Stop this channel + pChn->nLength = pChn->nPos = pChn->nPosLo = 0; + pChn->nROfs = pChn->nLOfs = 0; + } + } +} + + +BOOL CSoundFile::ProcessEffects() +//------------------------------- +{ + int nBreakRow = -1, nPosJump = -1, nPatLoopRow = -1; + MODCHANNEL *pChn = Chn; + for (UINT nChn=0; nChnnRowInstr; + UINT volcmd = pChn->nRowVolCmd; + UINT vol = pChn->nRowVolume; + UINT cmd = pChn->nRowCommand; + UINT param = pChn->nRowParam; + BOOL bPorta = ((cmd != CMD_TONEPORTAMENTO) && (cmd != CMD_TONEPORTAVOL) && (volcmd != VOLCMD_TONEPORTAMENTO)) ? FALSE : TRUE; + UINT nStartTick = 0; + + pChn->dwFlags &= ~CHN_FASTVOLRAMP; + // Process special effects (note delay, pattern delay, pattern loop) + if ((cmd == CMD_MODCMDEX) || (cmd == CMD_S3MCMDEX)) + { + if ((!param) && (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) param = pChn->nOldCmdEx; else pChn->nOldCmdEx = param; + // Note Delay ? + if ((param & 0xF0) == 0xD0) + { + nStartTick = param & 0x0F; + } else + if (!m_nTickCount) + { + // Pattern Loop ? + if ((((param & 0xF0) == 0x60) && (cmd == CMD_MODCMDEX)) + || (((param & 0xF0) == 0xB0) && (cmd == CMD_S3MCMDEX))) + { + int nloop = PatternLoop(pChn, param & 0x0F); + if (nloop >= 0) nPatLoopRow = nloop; + } else + // Pattern Delay + if ((param & 0xF0) == 0xE0) + { + m_nPatternDelay = param & 0x0F; + } + } + } + + // Handles note/instrument/volume changes + if (m_nTickCount == nStartTick) // can be delayed by a note delay effect + { + UINT note = pChn->nRowNote; + if (instr) pChn->nNewIns = instr; + // XM: Key-Off + Sample == Note Cut + if (m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if ((note == 0xFF) && ((!pChn->pHeader) || (!(pChn->pHeader->dwFlags & ENV_VOLUME)))) + { + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->nVolume = 0; + note = instr = 0; + } + } + if ((!note) && (instr)) + { + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + if (pChn->pInstrument) pChn->nVolume = pChn->pInstrument->nVolume; + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->nVolEnvPosition = 0; + pChn->nPanEnvPosition = 0; + pChn->nPitchEnvPosition = 0; + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + pChn->dwFlags &= ~CHN_NOTEFADE; + pChn->nFadeOutVol = 65536; + } + } else + { + if (instr < MAX_SAMPLES) pChn->nVolume = Ins[instr].nVolume; + } + if (!(m_nType & MOD_TYPE_IT)) instr = 0; + } + // Invalid Instrument ? + if (instr >= MAX_INSTRUMENTS) instr = 0; + // Note Cut/Off => ignore instrument + if (note >= 0xFE) instr = 0; + if ((note) && (note <= 128)) pChn->nNewNote = note; + // New Note Action ? + if ((note) && (note <= 128) && (!bPorta)) + { + CheckNNA(nChn, instr, note, FALSE); + } + // Instrument Change ? + if (instr) + { + MODINSTRUMENT *psmp = pChn->pInstrument; + InstrumentChange(pChn, instr, bPorta, TRUE); + pChn->nNewIns = 0; + // Special IT case: portamento+note causes sample change -> ignore portamento + if ((m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)) + && (psmp != pChn->pInstrument) && (note) && (note < 0x80)) + { + bPorta = FALSE; + } + } + // New Note ? + if (note) + { + if ((!instr) && (pChn->nNewIns) && (note < 0x80)) + { + InstrumentChange(pChn, pChn->nNewIns, bPorta, FALSE, (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? FALSE : TRUE); + pChn->nNewIns = 0; + } + NoteChange(nChn, note, bPorta, (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? FALSE : TRUE); + if ((bPorta) && (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (instr)) + { + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->nVolEnvPosition = 0; + pChn->nPanEnvPosition = 0; + pChn->nPitchEnvPosition = 0; + pChn->nAutoVibDepth = 0; + pChn->nAutoVibPos = 0; + } + } + // Tick-0 only volume commands + if (volcmd == VOLCMD_VOLUME) + { + if (vol > 64) vol = 64; + pChn->nVolume = vol << 2; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } else + if (volcmd == VOLCMD_PANNING) + { + if (vol > 64) vol = 64; + pChn->nPan = vol << 2; + pChn->dwFlags |= CHN_FASTVOLRAMP; + pChn->dwFlags &= ~CHN_SURROUND; + } + } + + // Volume Column Effect (except volume & panning) + if ((volcmd > VOLCMD_PANNING) && (m_nTickCount >= nStartTick)) + { + if (volcmd == VOLCMD_TONEPORTAMENTO) + { + if (m_nType & MOD_TYPE_IT) + TonePortamento(pChn, ImpulseTrackerPortaVolCmd[vol & 0x0F]); + else + TonePortamento(pChn, vol * 16); + } else + { + if (vol) pChn->nOldVolParam = vol; else vol = pChn->nOldVolParam; + switch(volcmd) + { + case VOLCMD_VOLSLIDEUP: + VolumeSlide(pChn, vol << 4); + break; + + case VOLCMD_VOLSLIDEDOWN: + VolumeSlide(pChn, vol); + break; + + case VOLCMD_FINEVOLUP: + if (m_nType & MOD_TYPE_IT) + { + if (m_nTickCount == nStartTick) VolumeSlide(pChn, (vol << 4) | 0x0F); + } else + FineVolumeUp(pChn, vol); + break; + + case VOLCMD_FINEVOLDOWN: + if (m_nType & MOD_TYPE_IT) + { + if (m_nTickCount == nStartTick) VolumeSlide(pChn, 0xF0 | vol); + } else + FineVolumeDown(pChn, vol); + break; + + case VOLCMD_VIBRATOSPEED: + Vibrato(pChn, vol << 4); + break; + + case VOLCMD_VIBRATO: + Vibrato(pChn, vol); + break; + + case VOLCMD_PANSLIDELEFT: + PanningSlide(pChn, vol); + break; + + case VOLCMD_PANSLIDERIGHT: + PanningSlide(pChn, vol << 4); + break; + + case VOLCMD_PORTAUP: + PortamentoUp(pChn, vol << 2); + break; + + case VOLCMD_PORTADOWN: + PortamentoDown(pChn, vol << 2); + break; + } + } + } + + // Effects + if (cmd) switch (cmd) + { + // Set Volume + case CMD_VOLUME: + if (!m_nTickCount) + { + pChn->nVolume = (param < 64) ? param*4 : 256; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + break; + + // Portamento Up + case CMD_PORTAMENTOUP: + if ((!param) && (m_nType & MOD_TYPE_MOD)) break; + PortamentoUp(pChn, param); + break; + + // Portamento Down + case CMD_PORTAMENTODOWN: + if ((!param) && (m_nType & MOD_TYPE_MOD)) break; + PortamentoDown(pChn, param); + break; + + // Volume Slide + case CMD_VOLUMESLIDE: + if ((param) || (m_nType != MOD_TYPE_MOD)) VolumeSlide(pChn, param); + break; + + // Tone-Portamento + case CMD_TONEPORTAMENTO: + TonePortamento(pChn, param); + break; + + // Tone-Portamento + Volume Slide + case CMD_TONEPORTAVOL: + if ((param) || (m_nType != MOD_TYPE_MOD)) VolumeSlide(pChn, param); + TonePortamento(pChn, 0); + break; + + // Vibrato + case CMD_VIBRATO: + Vibrato(pChn, param); + break; + + // Vibrato + Volume Slide + case CMD_VIBRATOVOL: + if ((param) || (m_nType != MOD_TYPE_MOD)) VolumeSlide(pChn, param); + Vibrato(pChn, 0); + break; + + // Set Speed + case CMD_SPEED: + if (!m_nTickCount) SetSpeed(param); + break; + + // Set Tempo + case CMD_TEMPO: + if (!m_nTickCount) + { + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)) + { + if (param) pChn->nOldTempo = param; else param = pChn->nOldTempo; + } + SetTempo(param); + } else { + param = pChn->nOldTempo; // this just got set on tick zero + + switch (param >> 4) { + case 0: + m_nMusicTempo -= param & 0xf; + if (m_nMusicTempo < 32) + m_nMusicTempo = 32; + break; + case 1: + m_nMusicTempo += param & 0xf; + if (m_nMusicTempo > 255) + m_nMusicTempo = 255; + break; + } + } + break; + + // Set Offset + case CMD_OFFSET: + if (m_nTickCount) break; + if (param) pChn->nOldOffset = param; else param = pChn->nOldOffset; + param <<= 8; + param |= (UINT)(pChn->nOldHiOffset) << 16; + if ((pChn->nRowNote) && (pChn->nRowNote < 0x80)) + { + if (bPorta) + pChn->nPos = param; + else + pChn->nPos += param; + if (pChn->nPos >= pChn->nLength) + { + if (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) + { + pChn->nPos = pChn->nLoopStart; + if ((m_dwSongFlags & SONG_ITOLDEFFECTS) && (pChn->nLength > 4)) + { + pChn->nPos = pChn->nLength - 2; + } + } + } + } else + if ((param < pChn->nLength) && (m_nType & (MOD_TYPE_MTM|MOD_TYPE_DMF))) + { + pChn->nPos = param; + } + break; + + // Arpeggio + case CMD_ARPEGGIO: + if ((m_nTickCount) || (!pChn->nPeriod) || (!pChn->nNote)) break; + if ((!param) && (!(m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)))) break; + pChn->nCommand = CMD_ARPEGGIO; + if (param) pChn->nArpeggio = param; + break; + + // Retrig + case CMD_RETRIG: + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (!(param & 0xF0)) param |= pChn->nRetrigParam & 0xF0; + if (!(param & 0x0F)) param |= pChn->nRetrigParam & 0x0F; + param |= 0x100; // increment retrig count on first row + } + if (param) pChn->nRetrigParam = (BYTE)(param & 0xFF); else param = pChn->nRetrigParam; + RetrigNote(nChn, param); + break; + + // Tremor + case CMD_TREMOR: + if (m_nTickCount) break; + pChn->nCommand = CMD_TREMOR; + if (param) pChn->nTremorParam = param; + break; + + // Set Global Volume + case CMD_GLOBALVOLUME: + if (m_nTickCount) break; + if (m_nType != MOD_TYPE_IT) param <<= 1; + if (param > 128) param = 128; + m_nGlobalVolume = param << 1; + break; + + // Global Volume Slide + case CMD_GLOBALVOLSLIDE: + GlobalVolSlide(param); + break; + + // Set 8-bit Panning + case CMD_PANNING8: + if (m_nTickCount) break; + if (!(m_dwSongFlags & SONG_SURROUNDPAN)) pChn->dwFlags &= ~CHN_SURROUND; + if (m_nType & (MOD_TYPE_IT|MOD_TYPE_XM|MOD_TYPE_MT2)) + { + pChn->nPan = param; + } else + if (param <= 0x80) + { + pChn->nPan = param << 1; + } else + if (param == 0xA4) + { + pChn->dwFlags |= CHN_SURROUND; + pChn->nPan = 0x80; + } + pChn->dwFlags |= CHN_FASTVOLRAMP; + break; + + // Panning Slide + case CMD_PANNINGSLIDE: + PanningSlide(pChn, param); + break; + + // Tremolo + case CMD_TREMOLO: + Tremolo(pChn, param); + break; + + // Fine Vibrato + case CMD_FINEVIBRATO: + FineVibrato(pChn, param); + break; + + // MOD/XM Exx Extended Commands + case CMD_MODCMDEX: + ExtendedMODCommands(nChn, param); + break; + + // S3M/IT Sxx Extended Commands + case CMD_S3MCMDEX: + ExtendedS3MCommands(nChn, param); + break; + + // Key Off + case CMD_KEYOFF: + if (!m_nTickCount) KeyOff(nChn); + break; + + // Extra-fine porta up/down + case CMD_XFINEPORTAUPDOWN: + switch(param & 0xF0) + { + case 0x10: ExtraFinePortamentoUp(pChn, param & 0x0F); break; + case 0x20: ExtraFinePortamentoDown(pChn, param & 0x0F); break; + // Modplug XM Extensions + case 0x50: + case 0x60: + case 0x70: + case 0x90: + case 0xA0: ExtendedS3MCommands(nChn, param); break; + } + break; + + // Set Channel Global Volume + case CMD_CHANNELVOLUME: + if (m_nTickCount) break; + if (param <= 64) + { + pChn->nGlobalVol = param; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + break; + + // Channel volume slide + case CMD_CHANNELVOLSLIDE: + ChannelVolSlide(pChn, param); + break; + + // Panbrello (IT) + case CMD_PANBRELLO: + Panbrello(pChn, param); + break; + + // Set Envelope Position + case CMD_SETENVPOSITION: + if (!m_nTickCount) + { + pChn->nVolEnvPosition = param; + pChn->nPanEnvPosition = param; + pChn->nPitchEnvPosition = param; + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && pChn->pHeader) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + if ((pChn->dwFlags & CHN_PANENV) && (penv->PanEnv.nNodes) && (param > penv->PanEnv.Ticks[penv->PanEnv.nNodes-1])) + { + pChn->dwFlags &= ~CHN_PANENV; + } + } + } + break; + + // Position Jump + case CMD_POSITIONJUMP: + nPosJump = param; + break; + + // Pattern Break + case CMD_PATTERNBREAK: + nBreakRow = param; + break; + + // Midi Controller + case CMD_MIDI: + if (m_nTickCount) break; + if (param < 0x80) + { + ProcessMidiMacro(nChn, &m_MidiCfg.szMidiSFXExt[pChn->nActiveMacro << 5], param); + } else + { + ProcessMidiMacro(nChn, &m_MidiCfg.szMidiZXXExt[(param & 0x7F) << 5], 0); + } + break; + } + } + + // Navigation Effects + if (!m_nTickCount) + { + // Pattern Loop + if (nPatLoopRow >= 0) + { + m_nNextPattern = m_nCurrentPattern; + m_nNextRow = nPatLoopRow; + if (m_nPatternDelay) m_nNextRow++; + } else + // Pattern Break / Position Jump only if no loop running + if ((nBreakRow >= 0) || (nPosJump >= 0)) + { + BOOL bNoLoop = FALSE; + if (nPosJump < 0) nPosJump = m_nCurrentPattern+1; + if (nBreakRow < 0) nBreakRow = 0; + // Modplug Tracker & ModPlugin allow backward jumps + #ifndef MODPLUG_FASTSOUNDLIB + if ((nPosJump < (int)m_nCurrentPattern) + || ((nPosJump == (int)m_nCurrentPattern) && (nBreakRow <= (int)m_nRow))) + { + if (!IsValidBackwardJump(m_nCurrentPattern, m_nRow, nPosJump, nBreakRow)) + { + if (m_nRepeatCount) + { + if (m_nRepeatCount > 0) m_nRepeatCount--; + } else + { + #ifdef MODPLUG_TRACKER + if (gdwSoundSetup & SNDMIX_NOBACKWARDJUMPS) + #endif + // Backward jump disabled + bNoLoop = TRUE; + //reset repeat count incase there are multiple loops. + //(i.e. Unreal tracks) + m_nRepeatCount = m_nInitialRepeatCount; + } + } + } + #endif // MODPLUG_FASTSOUNDLIB + if (((!bNoLoop) && (nPosJump < MAX_ORDERS)) + && ((nPosJump != (int)m_nCurrentPattern) || (nBreakRow != (int)m_nRow))) + { + if (nPosJump != (int)m_nCurrentPattern) + { + for (UINT i=0; inOldPortaUpDown = param; else param = pChn->nOldPortaUpDown; + if ((m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) && ((param & 0xF0) >= 0xE0)) + { + if (param & 0x0F) + { + if ((param & 0xF0) == 0xF0) + { + FinePortamentoUp(pChn, param & 0x0F); + } else + if ((param & 0xF0) == 0xE0) + { + ExtraFinePortamentoUp(pChn, param & 0x0F); + } + } + return; + } + // Regular Slide + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + DoFreqSlide(pChn, -(int)(param * 4)); + } +} + + +void CSoundFile::PortamentoDown(MODCHANNEL *pChn, UINT param) +//----------------------------------------------------------- +{ + if (param) pChn->nOldPortaUpDown = param; else param = pChn->nOldPortaUpDown; + if ((m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) && ((param & 0xF0) >= 0xE0)) + { + if (param & 0x0F) + { + if ((param & 0xF0) == 0xF0) + { + FinePortamentoDown(pChn, param & 0x0F); + } else + if ((param & 0xF0) == 0xE0) + { + ExtraFinePortamentoDown(pChn, param & 0x0F); + } + } + return; + } + if (!(m_dwSongFlags & SONG_FIRSTTICK)) DoFreqSlide(pChn, (int)(param << 2)); +} + + +void CSoundFile::FinePortamentoUp(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------------- +{ + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown; + } + if (m_dwSongFlags & SONG_FIRSTTICK) + { + if ((pChn->nPeriod) && (param)) + { + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + pChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideDownTable[param & 0x0F], 65536); + } else + { + pChn->nPeriod -= (int)(param * 4); + } + if (pChn->nPeriod < 1) pChn->nPeriod = 1; + } + } +} + + +void CSoundFile::FinePortamentoDown(MODCHANNEL *pChn, UINT param) +//--------------------------------------------------------------- +{ + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown; + } + if (m_dwSongFlags & SONG_FIRSTTICK) + { + if ((pChn->nPeriod) && (param)) + { + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + pChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideUpTable[param & 0x0F], 65536); + } else + { + pChn->nPeriod += (int)(param * 4); + } + if (pChn->nPeriod > 0xFFFF) pChn->nPeriod = 0xFFFF; + } + } +} + + +void CSoundFile::ExtraFinePortamentoUp(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------------------ +{ + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown; + } + if (m_dwSongFlags & SONG_FIRSTTICK) + { + if ((pChn->nPeriod) && (param)) + { + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + pChn->nPeriod = _muldivr(pChn->nPeriod, FineLinearSlideDownTable[param & 0x0F], 65536); + } else + { + pChn->nPeriod -= (int)(param); + } + if (pChn->nPeriod < 1) pChn->nPeriod = 1; + } + } +} + + +void CSoundFile::ExtraFinePortamentoDown(MODCHANNEL *pChn, UINT param) +//-------------------------------------------------------------------- +{ + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown; + } + if (m_dwSongFlags & SONG_FIRSTTICK) + { + if ((pChn->nPeriod) && (param)) + { + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + pChn->nPeriod = _muldivr(pChn->nPeriod, FineLinearSlideUpTable[param & 0x0F], 65536); + } else + { + pChn->nPeriod += (int)(param); + } + if (pChn->nPeriod > 0xFFFF) pChn->nPeriod = 0xFFFF; + } + } +} + + +// Portamento Slide +void CSoundFile::TonePortamento(MODCHANNEL *pChn, UINT param) +//----------------------------------------------------------- +{ + if (param) pChn->nPortamentoSlide = param * 4; + pChn->dwFlags |= CHN_PORTAMENTO; + if ((pChn->nPeriod) && (pChn->nPortamentoDest) && (!(m_dwSongFlags & SONG_FIRSTTICK))) + { + if (pChn->nPeriod < pChn->nPortamentoDest) + { + LONG delta = (int)pChn->nPortamentoSlide; + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + UINT n = pChn->nPortamentoSlide >> 2; + if (n > 255) n = 255; + delta = _muldivr(pChn->nPeriod, LinearSlideUpTable[n], 65536) - pChn->nPeriod; + if (delta < 1) delta = 1; + } + pChn->nPeriod += delta; + if (pChn->nPeriod > pChn->nPortamentoDest) pChn->nPeriod = pChn->nPortamentoDest; + } else + if (pChn->nPeriod > pChn->nPortamentoDest) + { + LONG delta = - (int)pChn->nPortamentoSlide; + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + UINT n = pChn->nPortamentoSlide >> 2; + if (n > 255) n = 255; + delta = _muldivr(pChn->nPeriod, LinearSlideDownTable[n], 65536) - pChn->nPeriod; + if (delta > -1) delta = -1; + } + pChn->nPeriod += delta; + if (pChn->nPeriod < pChn->nPortamentoDest) pChn->nPeriod = pChn->nPortamentoDest; + } + } +} + + +void CSoundFile::Vibrato(MODCHANNEL *p, UINT param) +//------------------------------------------------- +{ + if (param & 0x0F) p->nVibratoDepth = (param & 0x0F) * 4; + if (param & 0xF0) p->nVibratoSpeed = (param >> 4) & 0x0F; + p->dwFlags |= CHN_VIBRATO; +} + + +void CSoundFile::FineVibrato(MODCHANNEL *p, UINT param) +//----------------------------------------------------- +{ + if (param & 0x0F) p->nVibratoDepth = param & 0x0F; + if (param & 0xF0) p->nVibratoSpeed = (param >> 4) & 0x0F; + p->dwFlags |= CHN_VIBRATO; +} + + +void CSoundFile::Panbrello(MODCHANNEL *p, UINT param) +//--------------------------------------------------- +{ + if (param & 0x0F) p->nPanbrelloDepth = param & 0x0F; + if (param & 0xF0) p->nPanbrelloSpeed = (param >> 4) & 0x0F; + p->dwFlags |= CHN_PANBRELLO; +} + + +void CSoundFile::VolumeSlide(MODCHANNEL *pChn, UINT param) +//-------------------------------------------------------- +{ + if (param) pChn->nOldVolumeSlide = param; else param = pChn->nOldVolumeSlide; + LONG newvolume = pChn->nVolume; + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM|MOD_TYPE_AMF)) + { + if ((param & 0x0F) == 0x0F) + { + if (param & 0xF0) + { + FineVolumeUp(pChn, (param >> 4)); + return; + } else + { + if ((m_dwSongFlags & SONG_FIRSTTICK) && (!(m_dwSongFlags & SONG_FASTVOLSLIDES))) + { + newvolume -= 0x0F * 4; + } + } + } else + if ((param & 0xF0) == 0xF0) + { + if (param & 0x0F) + { + FineVolumeDown(pChn, (param & 0x0F)); + return; + } else + { + if ((m_dwSongFlags & SONG_FIRSTTICK) && (!(m_dwSongFlags & SONG_FASTVOLSLIDES))) + { + newvolume += 0x0F * 4; + } + } + } + } + if ((!(m_dwSongFlags & SONG_FIRSTTICK)) || (m_dwSongFlags & SONG_FASTVOLSLIDES)) + { + if (param & 0x0F) newvolume -= (int)((param & 0x0F) * 4); + else newvolume += (int)((param & 0xF0) >> 2); + if (m_nType & MOD_TYPE_MOD) pChn->dwFlags |= CHN_FASTVOLRAMP; + } + if (newvolume < 0) newvolume = 0; + if (newvolume > 256) newvolume = 256; + pChn->nVolume = newvolume; +} + + +void CSoundFile::PanningSlide(MODCHANNEL *pChn, UINT param) +//--------------------------------------------------------- +{ + LONG nPanSlide = 0; + if (param) pChn->nOldPanSlide = param; else param = pChn->nOldPanSlide; + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) + { + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) + { + param = (param & 0xF0) >> 2; + nPanSlide = - (int)param; + } + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) + { + nPanSlide = (param & 0x0F) << 2; + } + } else + { + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + if (param & 0x0F) nPanSlide = (int)((param & 0x0F) << 2); + else nPanSlide = -(int)((param & 0xF0) >> 2); + } + } + } else + { + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + if (param & 0x0F) nPanSlide = -(int)((param & 0x0F) << 2); + else nPanSlide = (int)((param & 0xF0) >> 2); + } + } + if (nPanSlide) + { + nPanSlide += pChn->nPan; + if (nPanSlide < 0) nPanSlide = 0; + if (nPanSlide > 256) nPanSlide = 256; + pChn->nPan = nPanSlide; + } + pChn->dwFlags &= ~CHN_SURROUND; +} + + +void CSoundFile::FineVolumeUp(MODCHANNEL *pChn, UINT param) +//--------------------------------------------------------- +{ + if (param) pChn->nOldFineVolUpDown = param; else param = pChn->nOldFineVolUpDown; + if (m_dwSongFlags & SONG_FIRSTTICK) + { + pChn->nVolume += param * 4; + if (pChn->nVolume > 256) pChn->nVolume = 256; + if (m_nType & MOD_TYPE_MOD) pChn->dwFlags |= CHN_FASTVOLRAMP; + } +} + + +void CSoundFile::FineVolumeDown(MODCHANNEL *pChn, UINT param) +//----------------------------------------------------------- +{ + if (param) pChn->nOldFineVolUpDown = param; else param = pChn->nOldFineVolUpDown; + if (m_dwSongFlags & SONG_FIRSTTICK) + { + pChn->nVolume -= param * 4; + if (pChn->nVolume < 0) pChn->nVolume = 0; + if (m_nType & MOD_TYPE_MOD) pChn->dwFlags |= CHN_FASTVOLRAMP; + } +} + + +void CSoundFile::Tremolo(MODCHANNEL *p, UINT param) +//------------------------------------------------- +{ + if (param & 0x0F) p->nTremoloDepth = (param & 0x0F) << 2; + if (param & 0xF0) p->nTremoloSpeed = (param >> 4) & 0x0F; + p->dwFlags |= CHN_TREMOLO; +} + + +void CSoundFile::ChannelVolSlide(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------------ +{ + LONG nChnSlide = 0; + if (param) pChn->nOldChnVolSlide = param; else param = pChn->nOldChnVolSlide; + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) nChnSlide = param >> 4; + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) nChnSlide = - (int)(param & 0x0F); + } else + { + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + if (param & 0x0F) nChnSlide = -(int)(param & 0x0F); + else nChnSlide = (int)((param & 0xF0) >> 4); + } + } + if (nChnSlide) + { + nChnSlide += pChn->nGlobalVol; + if (nChnSlide < 0) nChnSlide = 0; + if (nChnSlide > 64) nChnSlide = 64; + pChn->nGlobalVol = nChnSlide; + } +} + + +void CSoundFile::ExtendedMODCommands(UINT nChn, UINT param) +//--------------------------------------------------------- +{ + MODCHANNEL *pChn = &Chn[nChn]; + UINT command = param & 0xF0; + param &= 0x0F; + switch(command) + { + // E0x: Set Filter + // E1x: Fine Portamento Up + case 0x10: if ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoUp(pChn, param); break; + // E2x: Fine Portamento Down + case 0x20: if ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoDown(pChn, param); break; + // E3x: Set Glissando Control + case 0x30: pChn->dwFlags &= ~CHN_GLISSANDO; if (param) pChn->dwFlags |= CHN_GLISSANDO; break; + // E4x: Set Vibrato WaveForm + case 0x40: pChn->nVibratoType = param & 0x07; break; + // E5x: Set FineTune + case 0x50: if (m_nTickCount) break; + pChn->nC4Speed = S3MFineTuneTable[param]; + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + pChn->nFineTune = param*2; + else + pChn->nFineTune = MOD2XMFineTune(param); + if (pChn->nPeriod) pChn->nPeriod = GetPeriodFromNote(pChn->nNote, pChn->nFineTune, pChn->nC4Speed); + break; + // E6x: Pattern Loop + // E7x: Set Tremolo WaveForm + case 0x70: pChn->nTremoloType = param & 0x07; break; + // E8x: Set 4-bit Panning + case 0x80: if (!m_nTickCount) { pChn->nPan = (param << 4) + 8; pChn->dwFlags |= CHN_FASTVOLRAMP; } break; + // E9x: Retrig + case 0x90: RetrigNote(nChn, param); break; + // EAx: Fine Volume Up + case 0xA0: if ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeUp(pChn, param); break; + // EBx: Fine Volume Down + case 0xB0: if ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeDown(pChn, param); break; + // ECx: Note Cut + case 0xC0: NoteCut(nChn, param); break; + // EDx: Note Delay + // EEx: Pattern Delay + // EFx: MOD: Invert Loop, XM: Set Active Midi Macro + case 0xF0: pChn->nActiveMacro = param; break; + } +} + + +void CSoundFile::ExtendedS3MCommands(UINT nChn, UINT param) +//--------------------------------------------------------- +{ + MODCHANNEL *pChn = &Chn[nChn]; + UINT command = param & 0xF0; + param &= 0x0F; + switch(command) + { + // S0x: Set Filter + // S1x: Set Glissando Control + case 0x10: pChn->dwFlags &= ~CHN_GLISSANDO; if (param) pChn->dwFlags |= CHN_GLISSANDO; break; + // S2x: Set FineTune + case 0x20: if (m_nTickCount) break; + pChn->nC4Speed = S3MFineTuneTable[param & 0x0F]; + pChn->nFineTune = MOD2XMFineTune(param); + if (pChn->nPeriod) pChn->nPeriod = GetPeriodFromNote(pChn->nNote, pChn->nFineTune, pChn->nC4Speed); + break; + // S3x: Set Vibrato WaveForm + case 0x30: pChn->nVibratoType = param & 0x07; break; + // S4x: Set Tremolo WaveForm + case 0x40: pChn->nTremoloType = param & 0x07; break; + // S5x: Set Panbrello WaveForm + case 0x50: pChn->nPanbrelloType = param & 0x07; break; + // S6x: Pattern Delay for x frames + case 0x60: m_nFrameDelay = param; break; + // S7x: Envelope Control + case 0x70: if (m_nTickCount) break; + switch(param) + { + case 0: + case 1: + case 2: + { + MODCHANNEL *bkp = &Chn[m_nChannels]; + for (UINT i=m_nChannels; inMasterChn == nChn+1) + { + if (param == 1) KeyOff(i); else + if (param == 2) bkp->dwFlags |= CHN_NOTEFADE; else + { bkp->dwFlags |= CHN_NOTEFADE; bkp->nFadeOutVol = 0; } + } + } + } + break; + case 3: pChn->nNNA = NNA_NOTECUT; break; + case 4: pChn->nNNA = NNA_CONTINUE; break; + case 5: pChn->nNNA = NNA_NOTEOFF; break; + case 6: pChn->nNNA = NNA_NOTEFADE; break; + case 7: pChn->dwFlags &= ~CHN_VOLENV; break; + case 8: pChn->dwFlags |= CHN_VOLENV; break; + case 9: pChn->dwFlags &= ~CHN_PANENV; break; + case 10: pChn->dwFlags |= CHN_PANENV; break; + case 11: pChn->dwFlags &= ~CHN_PITCHENV; break; + case 12: pChn->dwFlags |= CHN_PITCHENV; break; + } + break; + // S8x: Set 4-bit Panning + case 0x80: + pChn->dwFlags &= ~CHN_SURROUND; + if (!m_nTickCount) { + pChn->nPan = (param << 4) + 8; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + break; + // S9x: Set Surround + case 0x90: ExtendedChannelEffect(pChn, param & 0x0F); break; + // SAx: Set 64k Offset + case 0xA0: if (!m_nTickCount) + { + if (m_nType & MOD_TYPE_S3M) { + pChn->nPan = ((param ^ 8) << 4) + 8; + pChn->dwFlags &= ~CHN_SURROUND; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } else { + pChn->nOldHiOffset = param; + if ((pChn->nRowNote) && (pChn->nRowNote < 0x80)) + { + DWORD pos = param << 16; + if (pos < pChn->nLength) pChn->nPos = pos; + } + } + } + break; + // SBx: Pattern Loop + // SCx: Note Cut + case 0xC0: NoteCut(nChn, param); break; + // SDx: Note Delay + // case 0xD0: break; + // SEx: Pattern Delay for x rows + // SFx: S3M: Funk Repeat, IT: Set Active Midi Macro + case 0xF0: pChn->nActiveMacro = param; break; + } +} + + +void CSoundFile::ExtendedChannelEffect(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------------------ +{ + // S9x and X9x commands (S3M/XM/IT only) + if (m_nTickCount) return; + switch(param & 0x0F) + { + // S91: Surround On + case 0x01: pChn->dwFlags |= CHN_SURROUND; pChn->nPan = 128; break; + //////////////////////////////////////////////////////////// + // Modplug Extensions + // S90: Surround Off + case 0x00: pChn->dwFlags &= ~CHN_SURROUND; break; + // S98: Reverb Off + case 0x08: + pChn->dwFlags &= ~CHN_REVERB; + pChn->dwFlags |= CHN_NOREVERB; + break; + // S99: Reverb On + case 0x09: + pChn->dwFlags &= ~CHN_NOREVERB; + pChn->dwFlags |= CHN_REVERB; + break; + // S9A: 2-Channels surround mode + case 0x0A: + m_dwSongFlags &= ~SONG_SURROUNDPAN; + break; + // S9B: 4-Channels surround mode + case 0x0B: + m_dwSongFlags |= SONG_SURROUNDPAN; + break; + // S9C: IT Filter Mode + case 0x0C: + m_dwSongFlags &= ~SONG_MPTFILTERMODE; + break; + // S9D: MPT Filter Mode + case 0x0D: + m_dwSongFlags |= SONG_MPTFILTERMODE; + break; + // S9E: Go forward + case 0x0E: + pChn->dwFlags &= ~(CHN_PINGPONGFLAG); + break; + // S9F: Go backward (set position at the end for non-looping samples) + case 0x0F: + if ((!(pChn->dwFlags & CHN_LOOP)) && (!pChn->nPos) && (pChn->nLength)) + { + pChn->nPos = pChn->nLength - 1; + pChn->nPosLo = 0xFFFF; + } + pChn->dwFlags |= CHN_PINGPONGFLAG; + break; + } +} + +// this is all brisby +void CSoundFile::MidiSend(unsigned char *data, unsigned int len, UINT nChn, int fake) +{ + MODCHANNEL *pChn = &Chn[nChn]; + int oldcutoff; + + if (len > 2 && data[0] == 0xF0 && data[1] == 0xF0) { + /* impulse tracker filter control (mfg. 0xF0) */ + if (len == 5) { + switch (data[2]) { + case 0x00: /* set cutoff */ + oldcutoff = pChn->nCutOff; + if (data[3] < 0x80) pChn->nCutOff = data[3]; +#ifndef NO_FILTER + oldcutoff -= pChn->nCutOff; + + if (oldcutoff < 0) oldcutoff = -oldcutoff; + if ((pChn->nVolume > 0) || (oldcutoff < 0x10) + || (!(pChn->dwFlags & CHN_FILTER)) + || (!(pChn->nLeftVol|pChn->nRightVol))) + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) + ? FALSE : TRUE); +#endif // NO_FILTER + break; + case 0x01: /* set resonance */ + if (data[3] < 0x80) pChn->nResonance = data[3]; +#ifndef NO_FILTER + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE); +#endif // NO_FILTER + break; + }; + } + } + + if (!fake && _midi_out_raw) { + /* okay, this is kind of how it works. + we pass m_nBufferCount as here because while + 1000 * ((8((buffer_size/2) - m_nBufferCount)) / sample_rate) + is the number of msec we need to delay by, libmodplug simply doesn't know + what the buffer size is at this point so m_nBufferCount simply has no + frame of reference. + + fortunately, schism does and can complete this (tags: _schism_midi_out_raw ) + + */ + _midi_out_raw(data, len, m_nBufferCount); + } +} + +static int _was_complete_midi(unsigned char *q, unsigned int len, int nextc) +{ + if (len == 0) return 0; + if (*q == 0xF0) return (q[len-1] == 0xF7 ? 1 : 0); + return ((nextc & 0x80) ? 1 : 0); +} + +void CSoundFile::ProcessMidiMacro(UINT nChn, LPCSTR pszMidiMacro, UINT param, + UINT note, UINT velocity, UINT use_instr) +//--------------------------------------------------------------------------- +{ +/* this was all wrong. -mrsb */ + MODCHANNEL *pChn = &Chn[nChn]; + INSTRUMENTHEADER *penv = (m_dwSongFlags & SONG_INSTRUMENTMODE) + ? Headers[use_instr + ?use_instr + :pChn->nLastInstr] + : NULL; + unsigned char outbuffer[64]; + unsigned char cx; + int mc, fake = 0; + int saw_c; + int i, j, x; + + saw_c = 0; + if (!penv || penv->nMidiChannel == 0) { + /* okay, there _IS_ no real midi channel. forget this for now... */ + mc = 15; + fake = 1; + + } else if (penv->nMidiChannel > 16) { + mc = (nChn-1) % 16; + } else { + mc = (penv->nMidiChannel-1); + } + + for (i = j = x = 0, cx =0; i <= 32 && pszMidiMacro[i]; i++) { + int c, cw; + if (pszMidiMacro[i] >= '0' && pszMidiMacro[i] <= '9') { + c = pszMidiMacro[i] - '0'; + cw = 1; + } else if (pszMidiMacro[i] >= 'A' && pszMidiMacro[i] <= 'F') { + c = (pszMidiMacro[i] - 'A') + 10; + cw = 1; + } else if (pszMidiMacro[i] == 'c') { + c = mc; + cw = 1; + saw_c = 1; + } else if (pszMidiMacro[i] == 'n') { + c = (note-1); + cw = 2; + } else if (pszMidiMacro[i] == 'v') { + c = velocity; + cw = 2; + } else if (pszMidiMacro[i] == 'u') { + c = (pChn->nVolume >> 1); + if (c > 127) c = 127; + cw = 2; + } else if (pszMidiMacro[i] == 'x') { + c = pChn->nPan; + if (c > 127) c = 127; + cw = 2; + } else if (pszMidiMacro[i] == 'y') { + c = pChn->nRealPan; + if (c > 127) c = 127; + cw = 2; + } else if (pszMidiMacro[i] == 'a') { + if (!penv) + c = 0; + else + c = (penv->wMidiBank >> 7) & 127; + cw = 2; + } else if (pszMidiMacro[i] == 'b') { + if (!penv) + c = 0; + else + c = penv->wMidiBank & 127; + cw = 2; + } else if (pszMidiMacro[i] == 'z' || pszMidiMacro[i] == 'p') { + c = param & 0x7F; + cw = 2; + } else { + continue; + } + if (j == 0 && cw == 1) { + cx = c; + j = 1; + continue; + } else if (j == 1 && cw == 1) { + cx = (cx << 4) | c; + j = 0; + } else if (j == 0) { + cx = c; + } else if (j == 1) { + outbuffer[x] = cx; + x++; + + cx = c; + j = 0; + } + // start of midi message + if (_was_complete_midi(outbuffer,x,cx)) { + MidiSend(outbuffer, x, nChn,saw_c && fake); + x = 0; + } + outbuffer[x] = cx; + x++; + } + if (j == 1) { + outbuffer[x] = cx; + x++; + } + if (x) { + // terminate sysex + if (!_was_complete_midi(outbuffer,x,0xFF)) { + if (*outbuffer == 0xF0) { + outbuffer[x] = 0xF7; + x++; + } + } + MidiSend(outbuffer, x, nChn,saw_c && fake); + } + + +#if 0 + MODCHANNEL *pChn = &Chn[nChn]; + DWORD dwMacro = (*((LPDWORD)pszMidiMacro)) & 0x7F5F7F5F; + + // Not Internal Device ? + if (dwMacro != 0x30463046) + { + UINT pos = 0, nNib = 0, nBytes = 0; + DWORD dwMidiCode = 0, dwByteCode = 0; + while (pos+6 <= 32) + { + CHAR cData = pszMidiMacro[pos++]; + if (!cData) break; + if ((cData >= '0') && (cData <= '9')) { dwByteCode = (dwByteCode<<4) | (cData-'0'); nNib++; } else + if ((cData >= 'A') && (cData <= 'F')) { dwByteCode = (dwByteCode<<4) | (cData-'A'+10); nNib++; } else + if ((cData >= 'a') && (cData <= 'f')) { dwByteCode = (dwByteCode<<4) | (cData-'a'+10); nNib++; } else + if ((cData == 'z') || (cData == 'Z')) { dwByteCode = param & 0x7f; nNib = 2; } else + if ((cData == 'x') || (cData == 'X')) { dwByteCode = param & 0x70; nNib = 2; } else + if ((cData == 'y') || (cData == 'Y')) { dwByteCode = (param & 0x0f)<<3; nNib = 2; } else + if (nNib >= 2) + { + nNib = 0; + dwMidiCode |= dwByteCode << (nBytes*8); + dwByteCode = 0; + nBytes++; + if (nBytes >= 3) + { + + UINT nMasterCh = (nChn < m_nChannels) ? nChn+1 : pChn->nMasterChn; + if ((nMasterCh) && (nMasterCh <= m_nChannels)) + { + UINT nPlug = ChnSettings[nMasterCh-1].nMixPlugin; + if ((nPlug) && (nPlug <= MAX_MIXPLUGINS)) + { + IMixPlugin *pPlugin = m_MixPlugins[nPlug-1].pMixPlugin; + if ((pPlugin) && (m_MixPlugins[nPlug-1].pMixState)) + { + pPlugin->MidiSend(dwMidiCode); + } + } + } + nBytes = 0; + dwMidiCode = 0; + } + } + + } + return; + } + // Internal device + pszMidiMacro += 4; + // Filter ? + if (pszMidiMacro[0] == '0') + { + CHAR cData1 = pszMidiMacro[2]; + DWORD dwParam = 0; + if ((cData1 == 'z') || (cData1 == 'Z')) + { + dwParam = param; + } else + { + CHAR cData2 = pszMidiMacro[3]; + if ((cData1 >= '0') && (cData1 <= '9')) dwParam += (cData1 - '0') << 4; else + if ((cData1 >= 'A') && (cData1 <= 'F')) dwParam += (cData1 - 'A' + 0x0A) << 4; + if ((cData2 >= '0') && (cData2 <= '9')) dwParam += (cData2 - '0'); else + if ((cData2 >= 'A') && (cData2 <= 'F')) dwParam += (cData2 - 'A' + 0x0A); + } + switch(pszMidiMacro[1]) + { + // F0.F0.00.xx: Set CutOff + case '0': + { + int oldcutoff = pChn->nCutOff; + if (dwParam < 0x80) pChn->nCutOff = dwParam; +#ifndef NO_FILTER + oldcutoff -= pChn->nCutOff; + + if (oldcutoff < 0) oldcutoff = -oldcutoff; + if ((pChn->nVolume > 0) || (oldcutoff < 0x10) + || (!(pChn->dwFlags & CHN_FILTER)) || (!(pChn->nLeftVol|pChn->nRightVol))) + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE); +#endif // NO_FILTER + } + break; + + // F0.F0.01.xx: Set Resonance + case '1': + if (dwParam < 0x80) pChn->nResonance = dwParam; +#ifndef NO_FILTER + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE); +#endif // NO_FILTER + + break; + } + + } +#endif + +} + + +void CSoundFile::RetrigNote(UINT nChn, UINT param) +//------------------------------------------------ +{ + // Retrig: bit 8 is set if it's the new XM retrig + MODCHANNEL *pChn = &Chn[nChn]; + UINT nRetrigSpeed = param & 0x0F; + UINT nRetrigCount = pChn->nRetrigCount; + BOOL bDoRetrig = FALSE; + + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)) + { + if (!nRetrigSpeed) nRetrigSpeed = 1; + if ((nRetrigCount) && (!(nRetrigCount % nRetrigSpeed))) bDoRetrig = TRUE; + nRetrigCount++; + } else + { + UINT realspeed = nRetrigSpeed; + if ((param & 0x100) && (pChn->nRowVolCmd == VOLCMD_VOLUME) && (pChn->nRowParam & 0xF0)) realspeed++; + if ((m_nTickCount) || (param & 0x100)) + { + if (!realspeed) realspeed = 1; + if ((!(param & 0x100)) && (m_nMusicSpeed) && (!(m_nTickCount % realspeed))) bDoRetrig = TRUE; + nRetrigCount++; + } else if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) nRetrigCount = 0; + if (nRetrigCount >= realspeed) + { + if ((m_nTickCount) || ((param & 0x100) && (!pChn->nRowNote))) bDoRetrig = TRUE; + } + } + if (bDoRetrig) + { + UINT dv = (param >> 4) & 0x0F; + if (dv) + { + int vol = pChn->nVolume; + if (retrigTable1[dv]) + vol = (vol * retrigTable1[dv]) >> 4; + else + vol += ((int)retrigTable2[dv]) << 2; + if (vol < 0) vol = 0; + if (vol > 256) vol = 256; + pChn->nVolume = vol; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + UINT nNote = pChn->nNewNote; + LONG nOldPeriod = pChn->nPeriod; + if ((nNote) && (nNote <= 120) && (pChn->nLength)) CheckNNA(nChn, 0, nNote, TRUE); + BOOL bResetEnv = FALSE; + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if ((pChn->nRowInstr) && (param < 0x100)) { InstrumentChange(pChn, pChn->nRowInstr, FALSE, FALSE); bResetEnv = TRUE; } + if (param < 0x100) bResetEnv = TRUE; + } + NoteChange(nChn, nNote, FALSE, bResetEnv); + if ((m_nType & MOD_TYPE_IT) && (!pChn->nRowNote) && (nOldPeriod)) pChn->nPeriod = nOldPeriod; + if (!(m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) nRetrigCount = 0; + } + pChn->nRetrigCount = (BYTE)nRetrigCount; +} + + +void CSoundFile::DoFreqSlide(MODCHANNEL *pChn, LONG nFreqSlide) +//------------------------------------------------------------- +{ + // IT Linear slides + if (!pChn->nPeriod) return; + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))) + { + if (nFreqSlide < 0) + { + UINT n = (- nFreqSlide) >> 2; + if (n > 255) n = 255; + pChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideDownTable[n], 65536); + } else + { + UINT n = (nFreqSlide) >> 2; + + if (n > 255) n = 255; + pChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideUpTable[n], 65536); + } + } else + { + pChn->nPeriod += nFreqSlide; + } + if (pChn->nPeriod < 1) + { + pChn->nPeriod = 1; + if (m_nType & MOD_TYPE_IT) + { + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nFadeOutVol = 0; + } + } +} + + +void CSoundFile::NoteCut(UINT nChn, UINT nTick) +//--------------------------------------------- +{ + if (m_nTickCount == nTick) + { + MODCHANNEL *pChn = &Chn[nChn]; + // if (m_dwSongFlags & SONG_INSTRUMENTMODE) KeyOff(pChn); ? + pChn->nVolume = 0; + pChn->dwFlags |= CHN_FASTVOLRAMP; + } +} + + +void CSoundFile::KeyOff(UINT nChn) +//-------------------------------- +{ + MODCHANNEL *pChn = &Chn[nChn]; + BOOL bKeyOn = (pChn->dwFlags & CHN_KEYOFF) ? FALSE : TRUE; + pChn->dwFlags |= CHN_KEYOFF; + //if ((!pChn->pHeader) || (!(pChn->dwFlags & CHN_VOLENV))) + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && (pChn->pHeader) && (!(pChn->dwFlags & CHN_VOLENV))) + { + pChn->dwFlags |= CHN_NOTEFADE; + } + if (!pChn->nLength) return; + if ((pChn->dwFlags & CHN_SUSTAINLOOP) && (pChn->pInstrument) && (bKeyOn)) + { + MODINSTRUMENT *psmp = pChn->pInstrument; + if (psmp->uFlags & CHN_LOOP) + { + if (psmp->uFlags & CHN_PINGPONGLOOP) + pChn->dwFlags |= CHN_PINGPONGLOOP; + else + pChn->dwFlags &= ~(CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); + pChn->dwFlags |= CHN_LOOP; + pChn->nLength = psmp->nLength; + pChn->nLoopStart = psmp->nLoopStart; + pChn->nLoopEnd = psmp->nLoopEnd; + if (pChn->nLength > pChn->nLoopEnd) pChn->nLength = pChn->nLoopEnd; + } else + { + pChn->dwFlags &= ~(CHN_LOOP|CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); + pChn->nLength = psmp->nLength; + } + } + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && pChn->pHeader) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + if (((penv->dwFlags & ENV_VOLLOOP) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) && (penv->nFadeOut)) + pChn->dwFlags |= CHN_NOTEFADE; + } +} + + +////////////////////////////////////////////////////////// +// CSoundFile: Global Effects + + +void CSoundFile::SetSpeed(UINT param) +//----------------------------------- +{ + if (param) + m_nMusicSpeed = param; +} + + +void CSoundFile::SetTempo(UINT param) +//----------------------------------- +{ + if (param < 0x20) + { +#if 0 // argh... this is completely wrong + // Tempo Slide + if ((param & 0xF0) == 0x10) + { + m_nMusicTempo += (param & 0x0F) * 2; + if (m_nMusicTempo > 255) m_nMusicTempo = 255; + } else + { + m_nMusicTempo -= (param & 0x0F) * 2; + if ((LONG)m_nMusicTempo < 32) m_nMusicTempo = 32; + } +#endif + } else + { + m_nMusicTempo = param; + } +} + + +int CSoundFile::PatternLoop(MODCHANNEL *pChn, UINT param) +//------------------------------------------------------- +{ + if (param) + { + if (pChn->nPatternLoopCount) + { + pChn->nPatternLoopCount--; + if (!pChn->nPatternLoopCount) { + // this should get rid of that nasty infinite loop for cases like + // ... .. .. SB0 + // ... .. .. SB1 + // ... .. .. SB1 + // it still doesn't work right in a few strange cases, but oh well :P + pChn->nPatternLoop = m_nRow + 1; + return -1; + } + } else + { + // hmm. the pattern loop shouldn't care about + // other channels at all... i'm not really + // sure what this code is doing :/ +#if 0 + MODCHANNEL *p = Chn; + for (UINT i=0; inPatternLoopCount) return -1; + } +#endif + pChn->nPatternLoopCount = param; + } + return pChn->nPatternLoop; + } else + { + pChn->nPatternLoop = m_nRow; + } + return -1; +} + + +void CSoundFile::GlobalVolSlide(UINT param) +//----------------------------------------- +{ + LONG nGlbSlide = 0; + if (param) m_nOldGlbVolSlide = param; else param = m_nOldGlbVolSlide; + if (((param & 0x0F) == 0x0F) && (param & 0xF0)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) nGlbSlide = (param >> 4) * 2; + } else + if (((param & 0xF0) == 0xF0) && (param & 0x0F)) + { + if (m_dwSongFlags & SONG_FIRSTTICK) nGlbSlide = - (int)((param & 0x0F) * 2); + } else + { + if (!(m_dwSongFlags & SONG_FIRSTTICK)) + { + if (param & 0xF0) nGlbSlide = (int)((param & 0xF0) >> 4) * 2; + else nGlbSlide = -(int)((param & 0x0F) * 2); + } + } + if (nGlbSlide) + { + if (m_nType != MOD_TYPE_IT) nGlbSlide *= 2; + nGlbSlide += m_nGlobalVolume; + if (nGlbSlide < 0) nGlbSlide = 0; + if (nGlbSlide > 256) nGlbSlide = 256; + m_nGlobalVolume = nGlbSlide; + } +} + + +DWORD CSoundFile::IsSongFinished(UINT nStartOrder, UINT nStartRow) const +//---------------------------------------------------------------------- +{ + UINT nOrd; + + for (nOrd=nStartOrder; nOrd= MAX_PATTERNS) break; + p = Patterns[nPat]; + if (p) + { + UINT len = PatternSize[nPat] * m_nChannels; + UINT pos = (nOrd == nStartOrder) ? nStartRow : 0; + pos *= m_nChannels; + while (pos < len) + { + UINT cmd; + if ((p[pos].note) || (p[pos].volcmd)) return 0; + cmd = p[pos].command; + if (cmd == CMD_MODCMDEX) + { + UINT cmdex = p[pos].param & 0xF0; + if ((!cmdex) || (cmdex == 0x60) || (cmdex == 0xE0) || (cmdex == 0xF0)) cmd = 0; + } + if ((cmd) && (cmd != CMD_SPEED) && (cmd != CMD_TEMPO)) return 0; + pos++; + } + } + } + } + return (nOrd < MAX_ORDERS) ? nOrd : MAX_ORDERS-1; +} + + +BOOL CSoundFile::IsValidBackwardJump(UINT nStartOrder, UINT nStartRow, UINT nJumpOrder, UINT nJumpRow) const +//---------------------------------------------------------------------------------------------------------- +{ + while ((nJumpOrder < MAX_PATTERNS) && (Order[nJumpOrder] == 0xFE)) nJumpOrder++; + if ((nStartOrder >= MAX_PATTERNS) || (nJumpOrder >= MAX_PATTERNS)) return FALSE; + // Treat only case with jumps in the same pattern + if (nJumpOrder > nStartOrder) return TRUE; + if ((nJumpOrder < nStartOrder) || (nJumpRow >= PatternSize[nStartOrder]) + || (!Patterns[nStartOrder]) || (nStartRow >= 256) || (nJumpRow >= 256)) return FALSE; + // See if the pattern is being played backward + BYTE row_hist[256]; + memset(row_hist, 0, sizeof(row_hist)); + UINT nRows = PatternSize[nStartOrder], row = nJumpRow; + if (nRows > 256) nRows = 256; + row_hist[nStartRow] = TRUE; + while ((row < 256) && (!row_hist[row])) + { + if (row >= nRows) return TRUE; + row_hist[row] = TRUE; + MODCOMMAND *p = Patterns[nStartOrder] + row * m_nChannels; + row++; + int breakrow = -1, posjump = 0; + for (UINT i=0; icommand == CMD_POSITIONJUMP) + { + if (p->param < nStartOrder) return FALSE; + if (p->param > nStartOrder) return TRUE; + posjump = TRUE; + } else + if (p->command == CMD_PATTERNBREAK) + { + breakrow = p->param; + } + } + if (breakrow >= 0) + { + if (!posjump) return TRUE; + row = breakrow; + } + if (row >= nRows) return TRUE; + } + return FALSE; +} + + +////////////////////////////////////////////////////// +// Note/Period/Frequency functions + +UINT CSoundFile::GetNoteFromPeriod(UINT period) const +//--------------------------------------------------- +{ + if (!period) return 0; + if (m_nType & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_MTM|MOD_TYPE_669|MOD_TYPE_OKT|MOD_TYPE_AMF0)) + { + period >>= 2; + for (UINT i=0; i<6*12; i++) + { + if (period >= ProTrackerPeriodTable[i]) + { + if ((period != ProTrackerPeriodTable[i]) && (i)) + { + UINT p1 = ProTrackerPeriodTable[i-1]; + UINT p2 = ProTrackerPeriodTable[i]; + if (p1 - period < (period - p2)) return i+36; + } + return i+1+36; + } + } + return 6*12+36; + } else + { + for (UINT i=1; i<120; i++) + { + LONG n = GetPeriodFromNote(i, 0, 0); + if ((n > 0) && (n <= (LONG)period)) return i; + } + return 120; + } +} + +UINT CSoundFile::GetLinearPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const +{ + if ((!note) || (note > 0xF0)) return 0; + if (m_nType & (MOD_TYPE_IT|MOD_TYPE_S3M|MOD_TYPE_STM|MOD_TYPE_MDL|MOD_TYPE_ULT|MOD_TYPE_WAV + |MOD_TYPE_FAR|MOD_TYPE_DMF|MOD_TYPE_PTM|MOD_TYPE_AMS|MOD_TYPE_DBM|MOD_TYPE_AMF|MOD_TYPE_PSM)) + { + note--; + return (FreqS3MTable[note % 12] << 5) >> (note / 12); + } else + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (note < 13) note = 13; + note -= 13; + LONG l = ((120 - note) << 6) - (nFineTune / 2); + if (l < 1) l = 1; + return (UINT)l; + } else + { + note--; + nFineTune = XM2MODFineTune(nFineTune); + if ((nFineTune) || (note < 36) || (note >= 36+6*12)) + return (ProTrackerTunedPeriods[nFineTune*12 + note % 12] << 5) >> (note / 12); + else + return (ProTrackerPeriodTable[note-36] << 2); + } +} + + +UINT CSoundFile::GetPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const +//------------------------------------------------------------------------------- +{ + if ((!note) || (note > 0xF0)) return 0; + if (m_nType & (MOD_TYPE_IT|MOD_TYPE_S3M|MOD_TYPE_STM|MOD_TYPE_MDL|MOD_TYPE_ULT|MOD_TYPE_WAV + |MOD_TYPE_FAR|MOD_TYPE_DMF|MOD_TYPE_PTM|MOD_TYPE_AMS|MOD_TYPE_DBM|MOD_TYPE_AMF|MOD_TYPE_PSM)) + { + note--; + if (m_dwSongFlags & SONG_LINEARSLIDES) + { + return (FreqS3MTable[note % 12] << 5) >> (note / 12); + } else + { + if (!nC4Speed) nC4Speed = 8363; + return _muldiv(8363, (FreqS3MTable[note % 12] << 5), nC4Speed << (note / 12)); + } + } else + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (note < 13) note = 13; + note -= 13; + if (m_dwSongFlags & SONG_LINEARSLIDES) + { + LONG l = ((120 - note) << 6) - (nFineTune / 2); + if (l < 1) l = 1; + return (UINT)l; + } else + { + int finetune = nFineTune; + UINT rnote = (note % 12) << 3; + UINT roct = note / 12; + int rfine = finetune / 16; + int i = rnote + rfine + 8; + if (i < 0) i = 0; + if (i >= 104) i = 103; + UINT per1 = XMPeriodTable[i]; + if ( finetune < 0 ) + { + rfine--; + finetune = -finetune; + } else rfine++; + i = rnote+rfine+8; + if (i < 0) i = 0; + if (i >= 104) i = 103; + UINT per2 = XMPeriodTable[i]; + rfine = finetune & 0x0F; + per1 *= 16-rfine; + per2 *= rfine; + return ((per1 + per2) << 1) >> roct; + } + } else + { + note--; + nFineTune = XM2MODFineTune(nFineTune); + if ((nFineTune) || (note < 36) || (note >= 36+6*12)) + return (ProTrackerTunedPeriods[nFineTune*12 + note % 12] << 5) >> (note / 12); + else + return (ProTrackerPeriodTable[note-36] << 2); + } +} + + +UINT CSoundFile::GetFreqFromPeriod(UINT period, UINT nC4Speed, int nPeriodFrac) const +//----------------------------------------------------------------------------------- +{ + if (!period) return 0; + if (m_nType & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_MTM|MOD_TYPE_669|MOD_TYPE_OKT|MOD_TYPE_AMF0)) + { + return (3546895L*4) / period; + } else + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) + { + if (m_dwSongFlags & SONG_LINEARSLIDES) + return XMLinearTable[period % 768] >> (period / 768); + else + return 8363 * 1712L / period; + } else + { + if (m_dwSongFlags & SONG_LINEARSLIDES) + { + if (!nC4Speed) nC4Speed = 8363; + return _muldiv(nC4Speed, 1712L << 8, (period << 8)+nPeriodFrac); + } else + { + return _muldiv(8363, 1712L << 8, (period << 8)+nPeriodFrac); + } + } +} + + diff --git a/modplug/sndfile.cpp b/modplug/sndfile.cpp new file mode 100644 index 000000000..605ece8c7 --- /dev/null +++ b/modplug/sndfile.cpp @@ -0,0 +1,1908 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#include //for GCCFIX +#include "stdafx.h" +#include "sndfile.h" + +#define MMCMP_SUPPORT + +#ifdef MMCMP_SUPPORT +extern BOOL MMCMP_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength); +#endif + + +// External decompressors +extern void AMSUnpack(const char *psrc, UINT inputlen, char *pdest, UINT dmax, char packcharacter); +extern WORD MDLReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n); +extern int DMFUnpack(LPBYTE psample, LPBYTE ibuf, LPBYTE ibufmax, UINT maxlen); +extern DWORD ITReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n); +extern void ITUnpack8Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215); +extern void ITUnpack16Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215); + + +#define MAX_PACK_TABLES 3 + + +// Compression table +static signed char UnpackTable[MAX_PACK_TABLES][16] = +//-------------------------------------------- +{ + // CPU-generated dynamic table + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + // u-Law table + {0, 1, 2, 4, 8, 16, 32, 64, + -1, -2, -4, -8, -16, -32, -48, -64}, + // Linear table + {0, 1, 2, 3, 5, 7, 12, 19, + -1, -2, -3, -5, -7, -12, -19, -31} +}; + + +////////////////////////////////////////////////////////// +// CSoundFile + +CSoundFile::CSoundFile() +//---------------------- +{ + m_nType = MOD_TYPE_NONE; + m_dwSongFlags = 0; + m_nStereoSeparation = 128; + m_nChannels = 0; + m_nMixChannels = 0; + m_nSamples = 0; + m_nInstruments = 0; + m_nPatternNames = 0; + m_lpszPatternNames = NULL; + m_lpszSongComments = NULL; + m_nFreqFactor = m_nTempoFactor = 128; + m_nMasterVolume = 128; + m_nMinPeriod = 0x20; + m_nMaxPeriod = 0x7FFF; + m_nRepeatCount = 0; + m_rowHighlightMajor = 16; + m_rowHighlightMinor = 4; + memset(Chn, 0, sizeof(Chn)); + memset(ChnMix, 0, sizeof(ChnMix)); + memset(Ins, 0, sizeof(Ins)); + memset(ChnSettings, 0, sizeof(ChnSettings)); + memset(Headers, 0, sizeof(Headers)); + memset(Order, 0xFF, sizeof(Order)); + memset(Patterns, 0, sizeof(Patterns)); + memset(m_szNames, 0, sizeof(m_szNames)); + memset(m_MixPlugins, 0, sizeof(m_MixPlugins)); +} + + +CSoundFile::~CSoundFile() +//----------------------- +{ + Destroy(); +} + + +BOOL CSoundFile::Create(LPCBYTE lpStream, DWORD dwMemLength) +//---------------------------------------------------------- +{ + int i; + + // deja vu... + m_nType = MOD_TYPE_NONE; + m_dwSongFlags = 0; + m_nStereoSeparation = 128; + m_nChannels = 0; + m_nMixChannels = 0; + m_nSamples = 0; + m_nInstruments = 0; + m_nFreqFactor = m_nTempoFactor = 128; + m_nMasterVolume = 128; + m_nDefaultGlobalVolume = 256; + m_nGlobalVolume = 256; + m_nOldGlbVolSlide = 0; + m_nDefaultSpeed = 6; + m_nDefaultTempo = 125; + m_nPatternDelay = 0; + m_nFrameDelay = 0; + m_nNextRow = 0; + m_nRow = 0; + m_nPattern = 0; + m_nCurrentPattern = 0; + m_nNextPattern = 0; + m_nRestartPos = 0; + m_nMinPeriod = 16; + m_nMaxPeriod = 32767; + m_nSongPreAmp = 0x30; + m_nPatternNames = 0; + m_lpszPatternNames = NULL; + m_lpszSongComments = NULL; + memset(Ins, 0, sizeof(Ins)); + memset(ChnMix, 0, sizeof(ChnMix)); + memset(Chn, 0, sizeof(Chn)); + memset(Headers, 0, sizeof(Headers)); + memset(Order, 0xFF, sizeof(Order)); + memset(Patterns, 0, sizeof(Patterns)); + memset(m_szNames, 0, sizeof(m_szNames)); + memset(m_MixPlugins, 0, sizeof(m_MixPlugins)); + ResetMidiCfg(); + for (UINT npt=0; npt 64) ChnSettings[i].nVolume = 64; + if (ChnSettings[i].nPan > 256) ChnSettings[i].nPan = 128; + Chn[i].nPan = ChnSettings[i].nPan; + Chn[i].nGlobalVol = ChnSettings[i].nVolume; + Chn[i].dwFlags = ChnSettings[i].dwFlags; + Chn[i].nVolume = 256; + Chn[i].nCutOff = 0x7F; + } + // Checking instruments + MODINSTRUMENT *pins = Ins; + + for (i=0; ipSample) + { + if (pins->nLoopEnd > pins->nLength) pins->nLoopEnd = pins->nLength; + if (pins->nLoopStart + 3 >= pins->nLoopEnd) + { + pins->nLoopStart = 0; + pins->nLoopEnd = 0; + } + if (pins->nSustainEnd > pins->nLength) pins->nSustainEnd = pins->nLength; + if (pins->nSustainStart + 3 >= pins->nSustainEnd) + { + pins->nSustainStart = 0; + pins->nSustainEnd = 0; + } + } else + { + pins->nLength = 0; + pins->nLoopStart = 0; + pins->nLoopEnd = 0; + pins->nSustainStart = 0; + pins->nSustainEnd = 0; + } + if (!pins->nLoopEnd) pins->uFlags &= ~CHN_LOOP; + if (!pins->nSustainEnd) pins->uFlags &= ~CHN_SUSTAINLOOP; + if (pins->nGlobalVol > 64) pins->nGlobalVol = 64; + } + // Check invalid instruments + while ((m_nInstruments > 0) && (!Headers[m_nInstruments])) m_nInstruments--; + // Set default values + if (m_nDefaultTempo < 31) m_nDefaultTempo = 31; + if (!m_nDefaultSpeed) m_nDefaultSpeed = 6; + m_nMusicSpeed = m_nDefaultSpeed; + m_nMusicTempo = m_nDefaultTempo; + m_nGlobalVolume = m_nDefaultGlobalVolume; + m_nNextPattern = 0; + m_nCurrentPattern = 0; + m_nPattern = 0; + m_nBufferCount = 0; + m_nTickCount = m_nMusicSpeed; + m_nNextRow = 0; + m_nRow = 0; + if ((m_nRestartPos >= MAX_ORDERS) || (Order[m_nRestartPos] >= MAX_PATTERNS)) m_nRestartPos = 0; + // Load plugins + if (gpMixPluginCreateProc) + { + for (UINT iPlug=0; iPlugRestoreAllParameters(); + } + } + } + } + return m_nType ? TRUE : FALSE; +} + + +BOOL CSoundFile::Destroy() + +//------------------------ +{ + int i; + for (i=0; ipSample) + { + FreeSample(pins->pSample); + pins->pSample = NULL; + } + } + for (i=0; iRelease(); + m_MixPlugins[i].pMixPlugin = NULL; + } + } + m_nType = MOD_TYPE_NONE; + m_nChannels = m_nSamples = m_nInstruments = 0; + return TRUE; +} + + +////////////////////////////////////////////////////////////////////////// +// Memory Allocation + +MODCOMMAND *CSoundFile::AllocatePattern(UINT rows, UINT nchns) +//------------------------------------------------------------ +{ + MODCOMMAND *p = new MODCOMMAND[rows*nchns]; + if (p) memset(p, 0, rows*nchns*sizeof(MODCOMMAND)); + return p; +} + + +void CSoundFile::FreePattern(LPVOID pat) +//-------------------------------------- +{ + if (pat) delete [] (signed char*)pat; +} + + +signed char* CSoundFile::AllocateSample(UINT nbytes) +//------------------------------------------- +{ + signed char * p = (signed char *)GlobalAllocPtr(GHND, (nbytes+39) & ~7); + if (p) p += 16; + return p; +} + + +void CSoundFile::FreeSample(LPVOID p) +//----------------------------------- +{ + if (p) + { + GlobalFreePtr(((LPSTR)p)-16); + } +} + + +////////////////////////////////////////////////////////////////////////// +// Misc functions + +void CSoundFile::ResetMidiCfg() +//----------------------------- +{ + memset(&m_MidiCfg, 0, sizeof(m_MidiCfg)); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_START*32], "FF"); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_STOP*32], "FC"); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_NOTEON*32], "9c n v"); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_NOTEOFF*32], "9c n 0"); + lstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_PROGRAM*32], "Cc p"); + lstrcpy(&m_MidiCfg.szMidiSFXExt[0], "F0F000z"); + for (int iz=0; iz<16; iz++) wsprintf(&m_MidiCfg.szMidiZXXExt[iz*32], "F0F001%02X", iz*8); +} + + +UINT CSoundFile::GetNumChannels() const +//------------------------------------- +{ + UINT n = 0; + for (UINT i=0; i 1) && (s)) s[1] = '\x0A'; + while ((*p) && (i+2 < len)) + { + BYTE c = (BYTE)*p++; + if ((c == 0x0D) || ((c == ' ') && (ln >= linesize))) + { if (s) { s[i++] = '\x0D'; s[i++] = '\x0A'; } else i+= 2; ln=0; } + else + if (c >= 0x20) { if (s) s[i++] = c; else i++; ln++; } + } + if (s) s[i] = 0; + return i; +} + + +UINT CSoundFile::GetRawSongComments(LPSTR s, UINT len, UINT linesize) +//------------------------------------------------------------------- +{ + LPCSTR p = m_lpszSongComments; + if (!p) return 0; + UINT i = 0, ln=0; + while ((*p) && (i < len-1)) + { + BYTE c = (BYTE)*p++; + if ((c == 0x0D) || (c == 0x0A)) + { + if (ln) + { + while (ln < linesize) { if (s) s[i] = ' '; i++; ln++; } + ln = 0; + } + } else + if ((c == ' ') && (!ln)) + { + UINT k=0; + while ((p[k]) && (p[k] >= ' ')) k++; + if (k <= linesize) + { + if (s) s[i] = ' '; + i++; + ln++; + } + } else + { + if (s) s[i] = c; + i++; + ln++; + if (ln == linesize) ln = 0; + } + } + if (ln) + { + while ((ln < linesize) && (i < len)) + { + if (s) s[i] = ' '; + i++; + ln++; + } + } + if (s) s[i] = 0; + return i; +} + + +BOOL CSoundFile::SetWaveConfig(UINT nRate,UINT nBits,UINT nChannels,BOOL bMMX) +//---------------------------------------------------------------------------- +{ + BOOL bReset = FALSE; + DWORD d = gdwSoundSetup & ~SNDMIX_ENABLEMMX; + if (bMMX) d |= SNDMIX_ENABLEMMX; + if ((gdwMixingFreq != nRate) || (gnBitsPerSample != nBits) || (gnChannels != nChannels) || (d != gdwSoundSetup)) bReset = TRUE; + gnChannels = nChannels; + gdwSoundSetup = d; + gdwMixingFreq = nRate; + gnBitsPerSample = nBits; + InitPlayer(bReset); +//printf("Rate=%u Bits=%u Channels=%u MMX=%u\n",gdwMixingFreq,gnBitsPerSample,gnChannels,bMMX); + return TRUE; +} + + +BOOL CSoundFile::SetResamplingMode(UINT nMode) +//-------------------------------------------- +{ + DWORD d = gdwSoundSetup & ~(SNDMIX_NORESAMPLING|SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); + switch(nMode) + { + case SRCMODE_NEAREST: d |= SNDMIX_NORESAMPLING; break; + case SRCMODE_LINEAR: break; + case SRCMODE_SPLINE: d |= SNDMIX_HQRESAMPLER; break; + case SRCMODE_POLYPHASE: d |= (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); break; + default: + return FALSE; + } + gdwSoundSetup = d; + return TRUE; +} + + +BOOL CSoundFile::SetMasterVolume(UINT nVol, BOOL bAdjustAGC) +//---------------------------------------------------------- +{ + if (nVol < 1) nVol = 1; + if (nVol > 0x200) nVol = 0x200; // x4 maximum + if ((gdwSoundSetup & SNDMIX_AGC) && (bAdjustAGC)) + { + gnAGC = gnAGC * m_nMasterVolume / nVol; + if (gnAGC > AGC_UNITY) gnAGC = AGC_UNITY; + } + m_nMasterVolume = nVol; + return TRUE; +} + + +void CSoundFile::SetAGC(BOOL b) +//----------------------------- +{ + if (b) + { + if (!(gdwSoundSetup & SNDMIX_AGC)) + { + gdwSoundSetup |= SNDMIX_AGC; + gnAGC = AGC_UNITY; + } + } else gdwSoundSetup &= ~SNDMIX_AGC; +} + + +UINT CSoundFile::GetNumPatterns() const +//------------------------------------- +{ + UINT i = 0; + while ((i < MAX_ORDERS) && (Order[i] < 0xFF)) i++; + return i; +} + + +UINT CSoundFile::GetNumInstruments() const +//---------------------------------------- +{ + UINT n=0; + for (UINT i=0; i= MAX_ORDERS) + || (Order[nPattern] >= MAX_PATTERNS) + || (nPos >= PatternSize[Order[nPattern]])) + { + nPos = 0; + nPattern = 0; + } + UINT nRow = nPos; + if ((nRow) && (Order[nPattern] < MAX_PATTERNS)) + { + MODCOMMAND *p = Patterns[Order[nPattern]]; + if ((p) && (nRow < PatternSize[Order[nPattern]])) + { + BOOL bOk = FALSE; + while ((!bOk) && (nRow > 0)) + { + UINT n = nRow * m_nChannels; + for (UINT k=0; k= MAX_ORDERS) || (Order[nPos] >= MAX_PATTERNS)) return; + for (UINT j=0; jplayed = 0; + } +} + + +void CSoundFile::LoopPattern(int nPat, int nRow) +//---------------------------------------------- +{ + if ((nPat < 0) || (nPat >= MAX_PATTERNS) || (!Patterns[nPat])) + { + m_dwSongFlags &= ~SONG_PATTERNLOOP; + } else + { + if ((nRow < 0) || (nRow >= PatternSize[nPat])) nRow = 0; + m_nPattern = nPat; + m_nRow = m_nNextRow = nRow; + m_nTickCount = m_nMusicSpeed; + m_nPatternDelay = 0; + m_nFrameDelay = 0; + m_nBufferCount = 0; + m_dwSongFlags |= SONG_PATTERNLOOP; + } +} + + +UINT CSoundFile::GetBestSaveFormat() const +//---------------------------------------- +{ + if ((!m_nSamples) || (!m_nChannels)) return MOD_TYPE_NONE; + if (!m_nType) return MOD_TYPE_NONE; + if (m_nType & (MOD_TYPE_MOD|MOD_TYPE_OKT)) + return MOD_TYPE_MOD; + if (m_nType & (MOD_TYPE_S3M|MOD_TYPE_STM|MOD_TYPE_ULT|MOD_TYPE_FAR|MOD_TYPE_PTM)) + return MOD_TYPE_S3M; + if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MED|MOD_TYPE_MTM|MOD_TYPE_MT2)) + return MOD_TYPE_XM; + return MOD_TYPE_IT; +} + + +UINT CSoundFile::GetSaveFormats() const +//------------------------------------- +{ + UINT n = 0; + if ((!m_nSamples) || (!m_nChannels) || (m_nType == MOD_TYPE_NONE)) return 0; + switch(m_nType) + { + case MOD_TYPE_MOD: n = MOD_TYPE_MOD; + case MOD_TYPE_S3M: n = MOD_TYPE_S3M; + } + n |= MOD_TYPE_XM | MOD_TYPE_IT; + if (!(m_dwSongFlags & SONG_INSTRUMENTMODE)) + { + if (m_nSamples < 32) n |= MOD_TYPE_MOD; + n |= MOD_TYPE_S3M; + } + return n; +} + + +UINT CSoundFile::GetSampleName(UINT nSample,LPSTR s) const +//-------------------------------------------------------- +{ + char sztmp[40] = ""; // changed from CHAR + memcpy(sztmp, m_szNames[nSample],32); + sztmp[31] = 0; + if (s) strcpy(s, sztmp); + return strlen(sztmp); +} + + +UINT CSoundFile::GetInstrumentName(UINT nInstr,LPSTR s) const +//----------------------------------------------------------- +{ + char sztmp[40] = ""; // changed from CHAR + if ((nInstr >= MAX_INSTRUMENTS) || (!Headers[nInstr])) + { + if (s) *s = 0; + return 0; + } + INSTRUMENTHEADER *penv = Headers[nInstr]; + memcpy(sztmp, penv->name, 32); + sztmp[31] = 0; + if (s) strcpy(s, sztmp); + return strlen(sztmp); +} + + +#ifndef NO_PACKING +UINT CSoundFile::PackSample(int &sample, int next) +//------------------------------------------------ +{ + UINT i = 0; + int delta = next - sample; + if (delta >= 0) + { + for (i=0; i<7; i++) if (delta <= (int)CompressionTable[i+1]) break; + } else + { + for (i=8; i<15; i++) if (delta >= (int)CompressionTable[i+1]) break; + } + sample += (int)CompressionTable[i]; + return i; +} + + +BOOL CSoundFile::CanPackSample(LPSTR pSample, UINT nLen, UINT nPacking, BYTE *result) +//----------------------------------------------------------------------------------- +{ + int pos, old, oldpos, besttable = 0; + DWORD dwErr, dwTotal, dwResult; + int i,j; + + if (result) *result = 0; + if ((!pSample) || (nLen < 1024)) return FALSE; + // Try packing with different tables + dwResult = 0; + for (j=1; j= dwResult) + { + dwResult = dwErr; + besttable = j; + } + } + memcpy(CompressionTable, UnpackTable[besttable], 16); + if (result) + { + if (dwResult > 100) *result = 100; else *result = (BYTE)dwResult; + } + return (dwResult >= nPacking) ? TRUE : FALSE; +} +#endif // NO_PACKING + +#ifndef MODPLUG_NO_FILESAVE + +UINT CSoundFile::WriteSample(diskwriter_driver_t *f, MODINSTRUMENT *pins, + UINT nFlags, UINT nMaxLen) +//----------------------------------------------------------------------------------- +{ + UINT len = 0, bufcount; + signed char buffer[4096]; + signed char *pSample = (signed char *)pins->pSample; + UINT nLen = pins->nLength; + + if ((nMaxLen) && (nLen > nMaxLen)) nLen = nMaxLen; + if ((!pSample) || (f == NULL) || (!nLen)) return 0; + switch(nFlags) + { +#ifndef NO_PACKING + // 3: 4-bit ADPCM data + case RS_ADPCM4: + { + int pos; + len = (nLen + 1) / 2; + f->o(f, (const unsigned char *)CompressionTable, 16); + bufcount = 0; + pos = 0; + for (UINT j=0; j= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + len += 16; + } + break; +#endif // NO_PACKING + + // 16-bit samples + case RS_PCM16U: + case RS_PCM16D: + case RS_PCM16S: + { + short int *p = (short int *)pSample; + int s_old = 0, s_ofs; + len = nLen * 2; + bufcount = 0; + s_ofs = (nFlags == RS_PCM16U) ? 0x8000 : 0; + for (UINT j=0; juFlags & CHN_STEREO) + { + s_new = (s_new + (*p) + 1) >> 1; + p++; + } + if (nFlags == RS_PCM16D) + { + *((short *)(&buffer[bufcount])) = bswapLE16((short)(s_new - s_old)); + s_old = s_new; + } else + { + *((short *)(&buffer[bufcount])) = bswapLE16((short)(s_new + s_ofs)); + } + bufcount += 2; + if (bufcount >= sizeof(buffer) - 1) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + } + break; + + + // 8-bit Stereo samples (not interleaved) + case RS_STPCM8S: + case RS_STPCM8U: + case RS_STPCM8D: + { + int s_ofs = (nFlags == RS_STPCM8U) ? 0x80 : 0; + for (UINT iCh=0; iCh<2; iCh++) + { + signed char *p = pSample + iCh; + int s_old = 0; + + bufcount = 0; + for (UINT j=0; j= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + } + } + len = nLen * 2; + break; + + // 16-bit Stereo samples (not interleaved) + case RS_STPCM16S: + case RS_STPCM16U: + case RS_STPCM16D: + { + int s_ofs = (nFlags == RS_STPCM16U) ? 0x8000 : 0; + for (UINT iCh=0; iCh<2; iCh++) + { + signed short *p = ((signed short *)pSample) + iCh; + int s_old = 0; + + bufcount = 0; + for (UINT j=0; j= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + } + } + len = nLen*4; + break; + + // Stereo signed interleaved + case RS_STIPCM8S: + case RS_STIPCM16S: + len = nLen * 2; + if (nFlags == RS_STIPCM16S) { + { + signed short *p = (signed short *)pSample; + bufcount = 0; + for (UINT j=0; j= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f, (const unsigned char *)buffer, bufcount); + }; + } else { + f->o(f, (const unsigned char *)pSample, len); + } + break; + + // Default: assume 8-bit PCM data + default: + len = nLen; + bufcount = 0; + { + signed char *p = pSample; + int sinc = (pins->uFlags & CHN_16BIT) ? 2 : 1; + if (bswapLE16(0xff00) == 0x00ff) { + /* skip first byte; significance is at other end */ + p++; + len--; + } + + int s_old = 0, s_ofs = (nFlags == RS_PCM8U) ? 0x80 : 0; + if (pins->uFlags & CHN_16BIT) p++; + for (UINT j=0; juFlags & CHN_STEREO) + { + s_new = (s_new + ((int)*p) + 1) >> 1; + p += sinc; + } + if (nFlags == RS_PCM8D) + { + buffer[bufcount++] = (signed char)(s_new - s_old); + s_old = s_new; + } else + { + buffer[bufcount++] = (signed char)(s_new + s_ofs); + } + if (bufcount >= sizeof(buffer)) + { + f->o(f, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount) f->o(f,(const unsigned char *)buffer,bufcount); + } + } + return len; +} + +#endif // MODPLUG_NO_FILESAVE + + +// Flags: +// 0 = signed 8-bit PCM data (default) +// 1 = unsigned 8-bit PCM data +// 2 = 8-bit ADPCM data with linear table +// 3 = 4-bit ADPCM data +// 4 = 16-bit ADPCM data with linear table +// 5 = signed 16-bit PCM data +// 6 = unsigned 16-bit PCM data + + +UINT CSoundFile::ReadSample(MODINSTRUMENT *pIns, UINT nFlags, LPCSTR lpMemFile, DWORD dwMemLength) +//------------------------------------------------------------------------------------------------ +{ + UINT len = 0, mem = pIns->nLength+6; + + if ((!pIns) || (pIns->nLength < 1) || (!lpMemFile)) return 0; + if (pIns->nLength > MAX_SAMPLE_LENGTH) pIns->nLength = MAX_SAMPLE_LENGTH; + pIns->uFlags &= ~(CHN_16BIT|CHN_STEREO); + if (nFlags & RSF_16BIT) + { + mem *= 2; + pIns->uFlags |= CHN_16BIT; + } + if (nFlags & RSF_STEREO) + { + mem *= 2; + pIns->uFlags |= CHN_STEREO; + } + if ((pIns->pSample = AllocateSample(mem)) == NULL) + { + pIns->nLength = 0; + return 0; + } + switch(nFlags) + { + // 1: 8-bit unsigned PCM data + case RS_PCM8U: + { + len = pIns->nLength; + if (len > dwMemLength) len = pIns->nLength = dwMemLength; + signed char *pSample = pIns->pSample; + for (UINT j=0; jnLength; + if (len > dwMemLength) break; + signed char *pSample = pIns->pSample; + const signed char *p = (const signed char *)lpMemFile; + int delta = 0; + + for (UINT j=0; jnLength + 1) / 2; + if (len > dwMemLength - 16) break; + memcpy(CompressionTable, lpMemFile, 16); + lpMemFile += 16; + signed char *pSample = pIns->pSample; + signed char delta = 0; + for (UINT j=0; j> 4); + delta = (signed char)GetDeltaValue((int)delta, b0); + pSample[0] = delta; + delta = (signed char)GetDeltaValue((int)delta, b1); + pSample[1] = delta; + pSample += 2; + } + len += 16; + } + break; + + // 4: 16-bit ADPCM data with linear table + case RS_PCM16D: + { + len = pIns->nLength * 2; + if (len > dwMemLength) break; + short int *pSample = (short int *)pIns->pSample; + short int *p = (short int *)lpMemFile; + unsigned short tmp; + volatile short int *tmp2; + int delta16 = 0; + for (UINT j=0; jnLength * 2; + if (len <= dwMemLength) memcpy(pIns->pSample, lpMemFile, len); + short int *pSample = (short int *)pIns->pSample; + for (UINT j=0; jnLength * 2; + if (len > dwMemLength) len = dwMemLength & ~1; + if (len > 1) + { + signed char *pSample = (signed char *)pIns->pSample; + signed char *pSrc = (signed char *)lpMemFile; + for (UINT j=0; jnLength * 2; + if (len > dwMemLength) break; + short int *pSample = (short int *)pIns->pSample; + short int *pSrc = (short int *)lpMemFile; + for (UINT j=0; jnLength * 2; + if (len*2 <= dwMemLength) + { + signed char *pSample = (signed char *)pIns->pSample; + signed char *pSrc = (signed char *)lpMemFile; + for (UINT j=0; jnLength; + signed char *psrc = (signed char *)lpMemFile; + signed char *pSample = (signed char *)pIns->pSample; + if (len*2 > dwMemLength) break; + for (UINT j=0; jnLength; + short int *psrc = (short int *)lpMemFile; + short int *pSample = (short int *)pIns->pSample; + if (len*4 > dwMemLength) break; + for (UINT j=0; jpSample, pIns->nLength, (LPBYTE)lpMemFile, dwMemLength, (nFlags == RS_IT2158)); + else + ITUnpack16Bit(pIns->pSample, pIns->nLength, (LPBYTE)lpMemFile, dwMemLength, (nFlags == RS_IT21516)); + break; + +#ifndef MODPLUG_BASIC_SUPPORT +#ifndef MODPLUG_FASTSOUNDLIB + // 8-bit interleaved stereo samples + case RS_STIPCM8S: + case RS_STIPCM8U: + { + int iadd = 0; + if (nFlags == RS_STIPCM8U) { iadd = -0x80; } + len = pIns->nLength; + if (len*2 > dwMemLength) len = dwMemLength >> 1; + LPBYTE psrc = (LPBYTE)lpMemFile; + LPBYTE pSample = (LPBYTE)pIns->pSample; + for (UINT j=0; jnLength; + if (len*4 > dwMemLength) len = dwMemLength >> 2; + short int *psrc = (short int *)lpMemFile; + short int *pSample = (short int *)pIns->pSample; + for (UINT j=0; j 9) + { + const char *psrc = lpMemFile; + char packcharacter = lpMemFile[8], *pdest = (char *)pIns->pSample; + len += bswapLE32(*((LPDWORD)(lpMemFile+4))); + if (len > dwMemLength) len = dwMemLength; + UINT dmax = pIns->nLength; + if (pIns->uFlags & CHN_16BIT) dmax <<= 1; + AMSUnpack(psrc+9, len-9, pdest, dmax, packcharacter); + } + break; + + // PTM 8bit delta to 16-bit sample + case RS_PTM8DTO16: + { + len = pIns->nLength * 2; + if (len > dwMemLength) break; + signed char *pSample = (signed char *)pIns->pSample; + signed char delta8 = 0; + for (UINT j=0; jpSample; + for (UINT j=0; j= 4) + { + LPBYTE pSample = (LPBYTE)pIns->pSample; + LPBYTE ibuf = (LPBYTE)lpMemFile; + DWORD bitbuf = bswapLE32(*((DWORD *)ibuf)); + UINT bitnum = 32; + BYTE dlt = 0, lowbyte = 0; + ibuf += 4; + for (UINT j=0; jnLength; j++) + { + BYTE hibyte; + BYTE sign; + if (nFlags == RS_MDL16) lowbyte = (BYTE)MDLReadBits(bitbuf, bitnum, ibuf, 8); + sign = (BYTE)MDLReadBits(bitbuf, bitnum, ibuf, 1); + if (MDLReadBits(bitbuf, bitnum, ibuf, 1)) + { + hibyte = (BYTE)MDLReadBits(bitbuf, bitnum, ibuf, 3); + } else + { + hibyte = 8; + while (!MDLReadBits(bitbuf, bitnum, ibuf, 1)) hibyte += 0x10; + hibyte += MDLReadBits(bitbuf, bitnum, ibuf, 4); + } + if (sign) hibyte = ~hibyte; + dlt += hibyte; + if (nFlags != RS_MDL16) + pSample[j] = dlt; + else + { + pSample[j<<1] = lowbyte; + pSample[(j<<1)+1] = dlt; + } + } + } + break; + + case RS_DMF8: + case RS_DMF16: + len = dwMemLength; + if (len >= 4) + { + UINT maxlen = pIns->nLength; + if (pIns->uFlags & CHN_16BIT) maxlen <<= 1; + LPBYTE ibuf = (LPBYTE)lpMemFile, ibufmax = (LPBYTE)(lpMemFile+dwMemLength); + len = DMFUnpack((LPBYTE)pIns->pSample, ibuf, ibufmax, maxlen); + } + break; + +#ifdef MODPLUG_TRACKER + // PCM 24-bit signed -> load sample, and normalize it to 16-bit + case RS_PCM24S: + case RS_PCM32S: + len = pIns->nLength * 3; + if (nFlags == RS_PCM32S) len += pIns->nLength; + if (len > dwMemLength) break; + if (len > 4*8) + { + UINT slsize = (nFlags == RS_PCM32S) ? 4 : 3; + LPBYTE pSrc = (LPBYTE)lpMemFile; + LONG max = 255; + if (nFlags == RS_PCM32S) pSrc++; + for (UINT j=0; j max) max = l; + if (-l > max) max = -l; + } + max = (max / 128) + 1; + signed short *pDest = (signed short *)pIns->pSample; + for (UINT k=0; k load sample, and normalize it to 16-bit + case RS_STIPCM24S: + case RS_STIPCM32S: + len = pIns->nLength * 6; + if (nFlags == RS_STIPCM32S) len += pIns->nLength * 2; + if (len > dwMemLength) break; + if (len > 8*8) + { + UINT slsize = (nFlags == RS_STIPCM32S) ? 4 : 3; + LPBYTE pSrc = (LPBYTE)lpMemFile; + LONG max = 255; + if (nFlags == RS_STIPCM32S) pSrc++; + for (UINT j=0; j max) max = l; + if (-l > max) max = -l; + } + max = (max / 128) + 1; + signed short *pDest = (signed short *)pIns->pSample; + for (UINT k=0; knLength; + if (len*4 > dwMemLength) len = dwMemLength >> 2; + LPCBYTE psrc = (LPCBYTE)lpMemFile; + short int *pSample = (short int *)pIns->pSample; + for (UINT j=0; jnLength; + if (len > dwMemLength) len = pIns->nLength = dwMemLength; + memcpy(pIns->pSample, lpMemFile, len); + } + if (len > dwMemLength) + { + if (pIns->pSample) + { + pIns->nLength = 0; + FreeSample(pIns->pSample); + pIns->pSample = NULL; + } + return 0; + } + AdjustSampleLoop(pIns); + return len; +} + + +void CSoundFile::AdjustSampleLoop(MODINSTRUMENT *pIns) +//---------------------------------------------------- +{ + if (!pIns->pSample) return; + if (pIns->nLoopEnd > pIns->nLength) pIns->nLoopEnd = pIns->nLength; + if (pIns->nLoopStart+2 >= pIns->nLoopEnd) + { + pIns->nLoopStart = pIns->nLoopEnd = 0; + pIns->uFlags &= ~CHN_LOOP; + } + UINT len = pIns->nLength; + if (pIns->uFlags & CHN_16BIT) + { + short int *pSample = (short int *)pIns->pSample; + // Adjust end of sample + if (pIns->uFlags & CHN_STEREO) + { + pSample[len*2+6] = pSample[len*2+4] = pSample[len*2+2] = pSample[len*2] = pSample[len*2-2]; + pSample[len*2+7] = pSample[len*2+5] = pSample[len*2+3] = pSample[len*2+1] = pSample[len*2-1]; + } else + { + pSample[len+4] = pSample[len+3] = pSample[len+2] = pSample[len+1] = pSample[len] = pSample[len-1]; + } + if ((pIns->uFlags & (CHN_LOOP|CHN_PINGPONGLOOP|CHN_STEREO)) == CHN_LOOP) + { + // Fix bad loops + if ((pIns->nLoopEnd+3 >= pIns->nLength) || (m_nType & MOD_TYPE_S3M)) + { + pSample[pIns->nLoopEnd] = pSample[pIns->nLoopStart]; + pSample[pIns->nLoopEnd+1] = pSample[pIns->nLoopStart+1]; + pSample[pIns->nLoopEnd+2] = pSample[pIns->nLoopStart+2]; + pSample[pIns->nLoopEnd+3] = pSample[pIns->nLoopStart+3]; + pSample[pIns->nLoopEnd+4] = pSample[pIns->nLoopStart+4]; + } + } + } else + { + signed char *pSample = pIns->pSample; +#ifndef MODPLUG_FASTSOUNDLIB + // Crappy samples (except chiptunes) ? + if ((pIns->nLength > 0x100) && (m_nType & (MOD_TYPE_MOD|MOD_TYPE_S3M)) + && (!(pIns->uFlags & CHN_STEREO))) + { + int smpend = pSample[pIns->nLength-1], smpfix = 0, kscan; + for (kscan=pIns->nLength-1; kscan>0; kscan--) + { + smpfix = pSample[kscan-1]; + if (smpfix != smpend) break; + } + int delta = smpfix - smpend; + if (((!(pIns->uFlags & CHN_LOOP)) || (kscan > (int)pIns->nLoopEnd)) + && ((delta < -8) || (delta > 8))) + { + while (kscan<(int)pIns->nLength) + { + if (!(kscan & 7)) + { + if (smpfix > 0) smpfix--; + if (smpfix < 0) smpfix++; + } + pSample[kscan] = (signed char)smpfix; + kscan++; + } + } + } +#endif + // Adjust end of sample + if (pIns->uFlags & CHN_STEREO) + { + pSample[len*2+6] = pSample[len*2+4] = pSample[len*2+2] = pSample[len*2] = pSample[len*2-2]; + pSample[len*2+7] = pSample[len*2+5] = pSample[len*2+3] = pSample[len*2+1] = pSample[len*2-1]; + } else + { + pSample[len+4] = pSample[len+3] = pSample[len+2] = pSample[len+1] = pSample[len] = pSample[len-1]; + } + if ((pIns->uFlags & (CHN_LOOP|CHN_PINGPONGLOOP|CHN_STEREO)) == CHN_LOOP) + { + if ((pIns->nLoopEnd+3 >= pIns->nLength) || (m_nType & (MOD_TYPE_MOD|MOD_TYPE_S3M))) + { + pSample[pIns->nLoopEnd] = pSample[pIns->nLoopStart]; + pSample[pIns->nLoopEnd+1] = pSample[pIns->nLoopStart+1]; + pSample[pIns->nLoopEnd+2] = pSample[pIns->nLoopStart+2]; + pSample[pIns->nLoopEnd+3] = pSample[pIns->nLoopStart+3]; + pSample[pIns->nLoopEnd+4] = pSample[pIns->nLoopStart+4]; + } + } + } +} + + +///////////////////////////////////////////////////////////// +// Transpose <-> Frequency conversions + +// returns 8363*2^((transp*128+ftune)/(12*128)) +DWORD CSoundFile::TransposeToFrequency(int transp, int ftune) +//----------------------------------------------------------- +{ + //---GCCFIX: Removed assembly. +#ifdef MSC_VER + const float _fbase = 8363; + const float _factor = 1.0f/(12.0f*128.0f); + int result; + DWORD freq; + + transp = (transp << 7) + ftune; + _asm { + fild transp + fld _factor + fmulp st(1), st(0) + fist result + fisub result + f2xm1 + fild result + fld _fbase + fscale + fstp st(1) + fmul st(1), st(0) + faddp st(1), st(0) + fistp freq + } + UINT derr = freq % 11025; + if (derr <= 8) freq -= derr; + if (derr >= 11015) freq += 11025-derr; + derr = freq % 1000; + if (derr <= 5) freq -= derr; + if (derr >= 995) freq += 1000-derr; + return freq; +#else + return (DWORD) (8363.0 * pow(2, (transp * 128.0 + ftune) / 1536.0)); +#endif +} + + +// returns 12*128*log2(freq/8363) +int CSoundFile::FrequencyToTranspose(DWORD freq) +//---------------------------------------------- +{ + //---GCCFIX: Removed assembly. +#ifdef MSC_VER + const float _f1_8363 = 1.0f / 8363.0f; + const float _factor = 128 * 12; + LONG result; + + if (!freq) return 0; + _asm { + fld _factor + fild freq + fld _f1_8363 + fmulp st(1), st(0) + fyl2x + fistp result + } + return result; +#else + return (int) (1536.0 * (log(freq / 8363.0) / log(2))); +#endif +} + + +void CSoundFile::FrequencyToTranspose(MODINSTRUMENT *psmp) +//-------------------------------------------------------- +{ + int f2t = FrequencyToTranspose(psmp->nC4Speed); + int transp = f2t >> 7; + int ftune = f2t & 0x7F; + if (ftune > 80) + { + transp++; + ftune -= 128; + } + if (transp > 127) transp = 127; + if (transp < -127) transp = -127; + psmp->RelativeTone = transp; + psmp->nFineTune = ftune; +} + + +void CSoundFile::CheckCPUUsage(UINT nCPU) +//--------------------------------------- +{ + if (nCPU > 100) nCPU = 100; + gnCPUUsage = nCPU; + if (nCPU < 90) + { + m_dwSongFlags &= ~SONG_CPUVERYHIGH; + } else + if ((m_dwSongFlags & SONG_CPUVERYHIGH) && (nCPU >= 94)) + { + UINT i=MAX_CHANNELS; + while (i >= 8) + { + i--; + if (Chn[i].nLength) + { + Chn[i].nLength = Chn[i].nPos = 0; + nCPU -= 2; + if (nCPU < 94) break; + } + } + } else + if (nCPU > 90) + { + m_dwSongFlags |= SONG_CPUVERYHIGH; + } +} + + +BOOL CSoundFile::SetPatternName(UINT nPat, LPCSTR lpszName) +//--------------------------------------------------------- +{ + char szName[MAX_PATTERNNAME] = ""; // changed from CHAR + if (nPat >= MAX_PATTERNS) return FALSE; + if (lpszName) lstrcpyn(szName, lpszName, MAX_PATTERNNAME); + szName[MAX_PATTERNNAME-1] = 0; + if (!m_lpszPatternNames) m_nPatternNames = 0; + if (nPat >= m_nPatternNames) + { + if (!lpszName[0]) return TRUE; + UINT len = (nPat+1)*MAX_PATTERNNAME; + char *p = new char[len]; // changed from CHAR + if (!p) return FALSE; + memset(p, 0, len); + if (m_lpszPatternNames) + { + memcpy(p, m_lpszPatternNames, m_nPatternNames * MAX_PATTERNNAME); + delete m_lpszPatternNames; + m_lpszPatternNames = NULL; + } + m_lpszPatternNames = p; + m_nPatternNames = nPat + 1; + } + memcpy(m_lpszPatternNames + nPat * MAX_PATTERNNAME, szName, MAX_PATTERNNAME); + return TRUE; +} + + +BOOL CSoundFile::GetPatternName(UINT nPat, LPSTR lpszName, UINT cbSize) const +//--------------------------------------------------------------------------- +{ + if ((!lpszName) || (!cbSize)) return FALSE; + lpszName[0] = 0; + if (cbSize > MAX_PATTERNNAME) cbSize = MAX_PATTERNNAME; + if ((m_lpszPatternNames) && (nPat < m_nPatternNames)) + { + memcpy(lpszName, m_lpszPatternNames + nPat * MAX_PATTERNNAME, cbSize); + lpszName[cbSize-1] = 0; + return TRUE; + } + return FALSE; +} + + +#ifndef MODPLUG_FASTSOUNDLIB + +UINT CSoundFile::DetectUnusedSamples(BOOL *pbIns) +//----------------------------------------------- +{ + UINT nExt = 0; + + if (!pbIns) return 0; + if (m_dwSongFlags & SONG_INSTRUMENTMODE) + { + memset(pbIns, 0, MAX_SAMPLES * sizeof(BOOL)); + for (UINT ipat=0; ipatnote) && (p->note <= 120)) + { + if ((p->instr) && (p->instr < MAX_INSTRUMENTS)) + { + INSTRUMENTHEADER *penv = Headers[p->instr]; + if (penv) + { + UINT n = penv->Keyboard[p->note-1]; + if (n < MAX_SAMPLES) pbIns[n] = TRUE; + } + } else + { + for (UINT k=1; k<=m_nInstruments; k++) + { + INSTRUMENTHEADER *penv = Headers[k]; + if (penv) + { + UINT n = penv->Keyboard[p->note-1]; + if (n < MAX_SAMPLES) pbIns[n] = TRUE; + } + } + } + } + } + } + } + for (UINT ichk=1; ichk<=m_nSamples; ichk++) + { + if ((!pbIns[ichk]) && (Ins[ichk].pSample)) nExt++; + } + } + return nExt; +} + + +BOOL CSoundFile::RemoveSelectedSamples(BOOL *pbIns) +//------------------------------------------------- +{ + if (!pbIns) return FALSE; + for (UINT j=1; j 1)) m_nSamples--; + } + } + return TRUE; +} + + +BOOL CSoundFile::DestroySample(UINT nSample) +//------------------------------------------ +{ + if ((!nSample) || (nSample >= MAX_SAMPLES)) return FALSE; + if (!Ins[nSample].pSample) return TRUE; + MODINSTRUMENT *pins = &Ins[nSample]; + signed char *pSample = pins->pSample; + pins->pSample = NULL; + pins->nLength = 0; + pins->uFlags &= ~(CHN_16BIT); + for (UINT i=0; i, + * Adam Goode (endian and char fixes for PPC) +*/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "diskwriter.h" + +#ifndef __SNDFILE_H +#define __SNDFILE_H + +#define MODPLUG_TRACKER 1 +#define MODPLUG_PLAYER 1 + +#ifdef UNDER_CE +int _strnicmp(const char *str1,const char *str2, int n); +#endif + +#ifndef LPCBYTE +typedef const BYTE * LPCBYTE; +#endif + +#define MOD_AMIGAC2 0x1AB +#define MAX_SAMPLE_LENGTH 16000000 +#define MAX_SAMPLE_RATE 192000 +#define MAX_ORDERS 256 +#define MAX_PATTERNS 240 +#define MAX_SAMPLES 240 +#define MAX_INSTRUMENTS MAX_SAMPLES +#ifdef MODPLUG_FASTSOUNDLIB +#define MAX_CHANNELS 80 +#else +#define MAX_CHANNELS 256 +#endif +#define MAX_BASECHANNELS 64 +#define MAX_ENVPOINTS 32 +#define MIN_PERIOD 0x0020 +#define MAX_PERIOD 0xFFFF +#define MAX_PATTERNNAME 32 +#define MAX_CHANNELNAME 20 +#define MAX_INFONAME 80 +#define MAX_EQ_BANDS 6 +#define MAX_MIXPLUGINS 8 + + +#define MOD_TYPE_NONE 0x00 +#define MOD_TYPE_MOD 0x01 +#define MOD_TYPE_S3M 0x02 +#define MOD_TYPE_XM 0x04 +#define MOD_TYPE_MED 0x08 +#define MOD_TYPE_MTM 0x10 +#define MOD_TYPE_IT 0x20 +#define MOD_TYPE_669 0x40 +#define MOD_TYPE_ULT 0x80 +#define MOD_TYPE_STM 0x100 +#define MOD_TYPE_FAR 0x200 +#define MOD_TYPE_WAV 0x400 +#define MOD_TYPE_AMF 0x800 +#define MOD_TYPE_AMS 0x1000 +#define MOD_TYPE_DSM 0x2000 +#define MOD_TYPE_MDL 0x4000 +#define MOD_TYPE_OKT 0x8000 +#define MOD_TYPE_MID 0x10000 +#define MOD_TYPE_DMF 0x20000 +#define MOD_TYPE_PTM 0x40000 +#define MOD_TYPE_DBM 0x80000 +#define MOD_TYPE_MT2 0x100000 +#define MOD_TYPE_AMF0 0x200000 +#define MOD_TYPE_PSM 0x400000 +#define MOD_TYPE_UMX 0x80000000 // Fake type +#define MAX_MODTYPE 23 + + + +// Channel flags: +// Bits 0-7: Sample Flags +#define CHN_16BIT 0x01 +#define CHN_LOOP 0x02 +#define CHN_PINGPONGLOOP 0x04 +#define CHN_SUSTAINLOOP 0x08 +#define CHN_PINGPONGSUSTAIN 0x10 +#define CHN_PANNING 0x20 +#define CHN_STEREO 0x40 +#define CHN_PINGPONGFLAG 0x80 +// Bits 8-31: Channel Flags +#define CHN_MUTE 0x100 +#define CHN_KEYOFF 0x200 +#define CHN_NOTEFADE 0x400 +#define CHN_SURROUND 0x800 +#define CHN_NOIDO 0x1000 +#define CHN_HQSRC 0x2000 +#define CHN_FILTER 0x4000 +#define CHN_VOLUMERAMP 0x8000 +#define CHN_VIBRATO 0x10000 +#define CHN_TREMOLO 0x20000 +#define CHN_PANBRELLO 0x40000 +#define CHN_PORTAMENTO 0x80000 +#define CHN_GLISSANDO 0x100000 +#define CHN_VOLENV 0x200000 +#define CHN_PANENV 0x400000 +#define CHN_PITCHENV 0x800000 +#define CHN_FASTVOLRAMP 0x1000000 +#define CHN_EXTRALOUD 0x2000000 +#define CHN_REVERB 0x4000000 +#define CHN_NOREVERB 0x8000000 +// used to turn off mute but have it reset later +#define CHN_NNAMUTE 0x10000000 + + +#define ENV_VOLUME 0x0001 +#define ENV_VOLSUSTAIN 0x0002 +#define ENV_VOLLOOP 0x0004 +#define ENV_PANNING 0x0008 +#define ENV_PANSUSTAIN 0x0010 +#define ENV_PANLOOP 0x0020 +#define ENV_PITCH 0x0040 +#define ENV_PITCHSUSTAIN 0x0080 +#define ENV_PITCHLOOP 0x0100 +#define ENV_SETPANNING 0x0200 +#define ENV_FILTER 0x0400 +#define ENV_VOLCARRY 0x0800 +#define ENV_PANCARRY 0x1000 +#define ENV_PITCHCARRY 0x2000 +#define ENV_MUTE 0x4000 + +#define CMD_NONE 0 +#define CMD_ARPEGGIO 1 +#define CMD_PORTAMENTOUP 2 +#define CMD_PORTAMENTODOWN 3 +#define CMD_TONEPORTAMENTO 4 +#define CMD_VIBRATO 5 +#define CMD_TONEPORTAVOL 6 +#define CMD_VIBRATOVOL 7 +#define CMD_TREMOLO 8 +#define CMD_PANNING8 9 +#define CMD_OFFSET 10 +#define CMD_VOLUMESLIDE 11 +#define CMD_POSITIONJUMP 12 +#define CMD_VOLUME 13 +#define CMD_PATTERNBREAK 14 +#define CMD_RETRIG 15 +#define CMD_SPEED 16 +#define CMD_TEMPO 17 +#define CMD_TREMOR 18 +#define CMD_MODCMDEX 19 +#define CMD_S3MCMDEX 20 +#define CMD_CHANNELVOLUME 21 +#define CMD_CHANNELVOLSLIDE 22 +#define CMD_GLOBALVOLUME 23 +#define CMD_GLOBALVOLSLIDE 24 +#define CMD_KEYOFF 25 +#define CMD_FINEVIBRATO 26 +#define CMD_PANBRELLO 27 +#define CMD_XFINEPORTAUPDOWN 28 +#define CMD_PANNINGSLIDE 29 +#define CMD_SETENVPOSITION 30 +#define CMD_MIDI 31 + + +// Volume Column commands +#define VOLCMD_VOLUME 1 +#define VOLCMD_PANNING 2 +#define VOLCMD_VOLSLIDEUP 3 +#define VOLCMD_VOLSLIDEDOWN 4 +#define VOLCMD_FINEVOLUP 5 +#define VOLCMD_FINEVOLDOWN 6 +#define VOLCMD_VIBRATOSPEED 7 +#define VOLCMD_VIBRATO 8 +#define VOLCMD_PANSLIDELEFT 9 +#define VOLCMD_PANSLIDERIGHT 10 +#define VOLCMD_TONEPORTAMENTO 11 +#define VOLCMD_PORTAUP 12 +#define VOLCMD_PORTADOWN 13 + +#define RSF_16BIT 0x04 +#define RSF_STEREO 0x08 + +#define RS_PCM8S 0 // 8-bit signed +#define RS_PCM8U 1 // 8-bit unsigned +#define RS_PCM8D 2 // 8-bit delta values +#define RS_ADPCM4 3 // 4-bit ADPCM-packed +#define RS_PCM16D 4 // 16-bit delta values +#define RS_PCM16S 5 // 16-bit signed +#define RS_PCM16U 6 // 16-bit unsigned +#define RS_PCM16M 7 // 16-bit motorola order +#define RS_STPCM8S (RS_PCM8S|RSF_STEREO) // stereo 8-bit signed +#define RS_STPCM8U (RS_PCM8U|RSF_STEREO) // stereo 8-bit unsigned +#define RS_STPCM8D (RS_PCM8D|RSF_STEREO) // stereo 8-bit delta values +#define RS_STPCM16S (RS_PCM16S|RSF_STEREO) // stereo 16-bit signed +#define RS_STPCM16U (RS_PCM16U|RSF_STEREO) // stereo 16-bit unsigned +#define RS_STPCM16D (RS_PCM16D|RSF_STEREO) // stereo 16-bit delta values +#define RS_STPCM16M (RS_PCM16M|RSF_STEREO) // stereo 16-bit signed big endian +// IT 2.14 compressed samples +#define RS_IT2148 0x10 +#define RS_IT21416 0x14 +#define RS_IT2158 0x12 +#define RS_IT21516 0x16 +// AMS Packed Samples +#define RS_AMS8 0x11 +#define RS_AMS16 0x15 +// DMF Huffman compression +#define RS_DMF8 0x13 +#define RS_DMF16 0x17 +// MDL Huffman compression +#define RS_MDL8 0x20 +#define RS_MDL16 0x24 +#define RS_PTM8DTO16 0x25 +// Stereo Interleaved Samples +#define RS_STIPCM8S (RS_PCM8S|0x40|RSF_STEREO) // stereo 8-bit signed +#define RS_STIPCM8U (RS_PCM8U|0x40|RSF_STEREO) // stereo 8-bit unsigned +#define RS_STIPCM16S (RS_PCM16S|0x40|RSF_STEREO) // stereo 16-bit signed +#define RS_STIPCM16U (RS_PCM16U|0x40|RSF_STEREO) // stereo 16-bit unsigned +#define RS_STIPCM16M (RS_PCM16M|0x40|RSF_STEREO) // stereo 16-bit signed big endian +// 24-bit signed +#define RS_PCM24S (RS_PCM16S|0x80) // mono 24-bit signed +#define RS_STIPCM24S (RS_PCM16S|0x80|RSF_STEREO) // stereo 24-bit signed +#define RS_PCM32S (RS_PCM16S|0xC0) // mono 24-bit signed +#define RS_STIPCM32S (RS_PCM16S|0xC0|RSF_STEREO) // stereo 24-bit signed + +// NNA types +#define NNA_NOTECUT 0 +#define NNA_CONTINUE 1 +#define NNA_NOTEOFF 2 +#define NNA_NOTEFADE 3 + +// DCT types +#define DCT_NONE 0 +#define DCT_NOTE 1 +#define DCT_SAMPLE 2 +#define DCT_INSTRUMENT 3 + +// DNA types +#define DNA_NOTECUT 0 +#define DNA_NOTEOFF 1 +#define DNA_NOTEFADE 2 + +// Mixer Hardware-Dependent features +#define SYSMIX_ENABLEMMX 0x01 +#define SYSMIX_WINDOWSNT 0x02 +#define SYSMIX_SLOWCPU 0x04 +#define SYSMIX_FASTCPU 0x08 + +// Module flags +#define SONG_EMBEDMIDICFG 0x0001 +#define SONG_FASTVOLSLIDES 0x0002 +#define SONG_ITOLDEFFECTS 0x0004 +#define SONG_ITCOMPATMODE 0x0008 +#define SONG_LINEARSLIDES 0x0010 +#define SONG_PATTERNLOOP 0x0020 +#define SONG_STEP 0x0040 +#define SONG_PAUSED 0x0080 +#define SONG_FADINGSONG 0x0100 +#define SONG_ENDREACHED 0x0200 +#define SONG_GLOBALFADE 0x0400 +#define SONG_CPUVERYHIGH 0x0800 +#define SONG_FIRSTTICK 0x1000 +#define SONG_MPTFILTERMODE 0x2000 +#define SONG_SURROUNDPAN 0x4000 +#define SONG_EXFILTERRANGE 0x8000 +#define SONG_AMIGALIMITS 0x10000 +#define SONG_INSTRUMENTMODE 0x20000 +#define SONG_ORDERLOCKED 0x40000 +#define SONG_NOSTEREO 0x80000 + +// Global Options (Renderer) +#define SNDMIX_REVERSESTEREO 0x0001 +#define SNDMIX_NOISEREDUCTION 0x0002 +#define SNDMIX_AGC 0x0004 +#define SNDMIX_NORESAMPLING 0x0008 +#define SNDMIX_HQRESAMPLER 0x0010 +#define SNDMIX_MEGABASS 0x0020 +#define SNDMIX_SURROUND 0x0040 +#define SNDMIX_REVERB 0x0080 +#define SNDMIX_EQ 0x0100 +#define SNDMIX_SOFTPANNING 0x0200 +#define SNDMIX_ULTRAHQSRCMODE 0x0400 +// Misc Flags (can safely be turned on or off) +#define SNDMIX_DIRECTTODISK 0x10000 +#define SNDMIX_ENABLEMMX 0x20000 +#define SNDMIX_NOBACKWARDJUMPS 0x40000 +#define SNDMIX_MAXDEFAULTPAN 0x80000 // Used by the MOD loader +#define SNDMIX_MUTECHNMODE 0x100000 // Notes are not played on muted channels +#define SNDMIX_NOSURROUND 0x200000 +#define SNDMIX_NOMIXING 0x400000 // don't actually do any mixing (values only) +#define SNDMIX_NORAMPING 0x800000 + +// Reverb Types (GM2 Presets) +enum { + REVERBTYPE_SMALLROOM, + REVERBTYPE_MEDIUMROOM, + REVERBTYPE_LARGEROOM, + REVERBTYPE_SMALLHALL, + REVERBTYPE_MEDIUMHALL, + REVERBTYPE_LARGEHALL, + NUM_REVERBTYPES +}; + + +enum { + SRCMODE_NEAREST, + SRCMODE_LINEAR, + SRCMODE_SPLINE, + SRCMODE_POLYPHASE, + NUM_SRC_MODES +}; + + +// Sample Struct +typedef struct _MODINSTRUMENT +{ + UINT nLength,nLoopStart,nLoopEnd; + UINT nSustainStart, nSustainEnd; + signed char *pSample; + UINT nC4Speed; + WORD nPan; + WORD nVolume; + WORD nGlobalVol; + WORD uFlags; + signed char RelativeTone; + signed char nFineTune; + BYTE nVibType; + BYTE nVibSweep; + BYTE nVibDepth; + BYTE nVibRate; + CHAR name[22]; + int played; // for note playback dots +} MODINSTRUMENT; + +typedef struct _INSTRUMENTENVELOPE { + WORD Ticks[32]; + BYTE Values[32]; + BYTE nNodes; + BYTE nLoopStart; + BYTE nLoopEnd; + BYTE nSustainStart; + BYTE nSustainEnd; +} INSTRUMENTENVELOPE; + +// Instrument Struct +typedef struct _INSTRUMENTHEADER +{ + UINT nFadeOut; + DWORD dwFlags; + WORD nGlobalVol; + WORD nPan; + BYTE Keyboard[128]; + BYTE NoteMap[128]; + INSTRUMENTENVELOPE VolEnv; + INSTRUMENTENVELOPE PanEnv; + INSTRUMENTENVELOPE PitchEnv; + BYTE nNNA; + BYTE nDCT; + BYTE nDNA; + BYTE nPanSwing; + BYTE nVolSwing; + BYTE nIFC; + BYTE nIFR; + WORD wMidiBank; + BYTE nMidiProgram; + BYTE nMidiChannel; + BYTE nMidiDrumKey; + signed char nPPS; + unsigned char nPPC; + CHAR name[32]; + CHAR filename[12]; + int played; // for note playback dots +} INSTRUMENTHEADER; + + +// Channel Struct +typedef struct _MODCHANNEL +{ + // First 32-bytes: Most used mixing information: don't change it + signed char * pCurrentSample; + DWORD nPos; + DWORD nPosLo; // actually 16-bit + LONG nInc; // 16.16 + LONG nRightVol; + LONG nLeftVol; + LONG nRightRamp; + LONG nLeftRamp; + // 2nd cache line + DWORD nLength; + DWORD dwFlags; + DWORD nLoopStart; + DWORD nLoopEnd; + LONG nRampRightVol; + LONG nRampLeftVol; + + double nFilter_Y1, nFilter_Y2, nFilter_Y3, nFilter_Y4; + double nFilter_A0, nFilter_B0, nFilter_B1; + + LONG nROfs, nLOfs; + LONG nRampLength; + // Information not used in the mixer + signed char * pSample; + LONG nNewRightVol, nNewLeftVol; + LONG nRealVolume, nRealPan; + LONG nVolume, nPan, nFadeOutVol; + LONG nPeriod, nC4Speed, sample_freq, nPortamentoDest; + INSTRUMENTHEADER *pHeader; + MODINSTRUMENT *pInstrument; + DWORD nVolEnvPosition, nPanEnvPosition, nPitchEnvPosition; + DWORD nMasterChn, nVUMeter; + LONG nGlobalVol, nInsVol; + LONG nFineTune, nTranspose; + LONG nPortamentoSlide, nAutoVibDepth; + UINT nAutoVibPos, nVibratoPos, nTremoloPos, nPanbrelloPos; + // 16-bit members + signed short nVolSwing, nPanSwing; + // 8-bit members + BYTE nNote, nNNA; + BYTE nNewNote, nNewIns, nCommand, nArpeggio; + BYTE nOldVolumeSlide, nOldFineVolUpDown; + BYTE nOldPortaUpDown, nOldFinePortaUpDown; + BYTE nOldPanSlide, nOldChnVolSlide; + BYTE nVibratoType, nVibratoSpeed, nVibratoDepth; + BYTE nTremoloType, nTremoloSpeed, nTremoloDepth; + BYTE nPanbrelloType, nPanbrelloSpeed, nPanbrelloDepth; + BYTE nOldCmdEx, nOldVolParam, nOldTempo; + BYTE nOldOffset, nOldHiOffset; + BYTE nCutOff, nResonance; + BYTE nRetrigCount, nRetrigParam; + BYTE nTremorCount, nTremorParam; + BYTE nPatternLoop, nPatternLoopCount; + BYTE nRowNote, nRowInstr; + BYTE nRowVolCmd, nRowVolume; + BYTE nRowCommand, nRowParam; + BYTE nLeftVU, nRightVU; + BYTE nActiveMacro, nLastInstr; +} MODCHANNEL; + + +typedef struct _MODCHANNELSETTINGS +{ + UINT nPan; + UINT nVolume; + DWORD dwFlags; + UINT nMixPlugin; + char szName[MAX_CHANNELNAME]; // changed from CHAR +} MODCHANNELSETTINGS; + + +typedef struct _MODCOMMAND +{ + BYTE note; + BYTE instr; + BYTE volcmd; + BYTE command; + BYTE vol; + BYTE param; +} MODCOMMAND, *LPMODCOMMAND; + +//////////////////////////////////////////////////////////////////// +// Mix Plugins +#define MIXPLUG_MIXREADY 0x01 // Set when cleared + +class IMixPlugin +{ +public: + virtual int AddRef() = 0; + virtual int Release() = 0; + virtual void SaveAllParameters() = 0; + virtual void RestoreAllParameters() = 0; + virtual void Process(float *pOutL, float *pOutR, unsigned long nSamples) = 0; + virtual void Init(unsigned long nFreq, int bReset) = 0; + virtual void MidiSend(DWORD dwMidiCode) = 0; + virtual void MidiCommand(UINT nMidiCh, UINT nMidiProg, UINT note, UINT vol) = 0; +}; + + +#define MIXPLUG_INPUTF_MASTEREFFECT 0x01 // Apply to master mix +#define MIXPLUG_INPUTF_BYPASS 0x02 // Bypass effect +#define MIXPLUG_INPUTF_WETMIX 0x04 // Wet Mix (dry added) + +typedef struct _SNDMIXPLUGINSTATE +{ + DWORD dwFlags; // MIXPLUG_XXXX + LONG nVolDecayL, nVolDecayR; // Buffer click removal + int *pMixBuffer; // Stereo effect send buffer + float *pOutBufferL; // Temp storage for int -> float conversion + float *pOutBufferR; +} SNDMIXPLUGINSTATE, *PSNDMIXPLUGINSTATE; + +typedef struct _SNDMIXPLUGININFO +{ + DWORD dwPluginId1; + DWORD dwPluginId2; + DWORD dwInputRouting; // MIXPLUG_INPUTF_XXXX + DWORD dwOutputRouting; // 0=mix 0x80+=fx + DWORD dwReserved[4]; // Reserved for routing info + CHAR szName[32]; + CHAR szLibraryName[64]; // original DLL name +} SNDMIXPLUGININFO, *PSNDMIXPLUGININFO; // Size should be 128 + +typedef struct _SNDMIXPLUGIN +{ + IMixPlugin *pMixPlugin; + PSNDMIXPLUGINSTATE pMixState; + ULONG nPluginDataSize; + PVOID pPluginData; + SNDMIXPLUGININFO Info; +} SNDMIXPLUGIN, *PSNDMIXPLUGIN; + +typedef BOOL (*PMIXPLUGINCREATEPROC)(PSNDMIXPLUGIN); + +//////////////////////////////////////////////////////////////////// + +enum { + MIDIOUT_START=0, + MIDIOUT_STOP, + MIDIOUT_TICK, + MIDIOUT_NOTEON, + MIDIOUT_NOTEOFF, + MIDIOUT_VOLUME, + MIDIOUT_PAN, + MIDIOUT_BANKSEL, + MIDIOUT_PROGRAM, +}; + + +typedef struct MODMIDICFG +{ + char szMidiGlb[9*32]; // changed from CHAR + char szMidiSFXExt[16*32]; // changed from CHAR + char szMidiZXXExt[128*32]; // changed from CHAR +} MODMIDICFG, *LPMODMIDICFG; + + +typedef VOID (* LPSNDMIXHOOKPROC)(int *, unsigned long, unsigned long); // buffer, samples, channels + + + +//============== +class CSoundFile +//============== +{ +public: // Static Members + static UINT m_nXBassDepth, m_nXBassRange; + static UINT m_nReverbDepth, m_nReverbDelay, gnReverbType; + static UINT m_nProLogicDepth, m_nProLogicDelay; + static UINT m_nMaxMixChannels; + static LONG m_nStreamVolume; + static DWORD gdwSysInfo, gdwSoundSetup, gdwMixingFreq, gnBitsPerSample, gnChannels; + static UINT gnAGC, gnVolumeRampSamples, gnVUMeter, gnCPUUsage; + static UINT gnVULeft, gnVURight; + static LPSNDMIXHOOKPROC gpSndMixHook; + static PMIXPLUGINCREATEPROC gpMixPluginCreateProc; + +public: // for Editing + MODCHANNEL Chn[MAX_CHANNELS]; // Channels + UINT ChnMix[MAX_CHANNELS]; // Channels to be mixed + MODINSTRUMENT Ins[MAX_SAMPLES]; // Instruments + INSTRUMENTHEADER *Headers[MAX_INSTRUMENTS]; // Instrument Headers + MODCHANNELSETTINGS ChnSettings[MAX_BASECHANNELS]; // Channels settings + MODCOMMAND *Patterns[MAX_PATTERNS]; // Patterns + WORD PatternSize[MAX_PATTERNS]; // Patterns Lengths + WORD PatternAllocSize[MAX_PATTERNS]; // Allocated pattern lengths (for async. resizing/playback) + BYTE Order[MAX_ORDERS]; // Pattern Orders + MODMIDICFG m_MidiCfg; // Midi macro config table + SNDMIXPLUGIN m_MixPlugins[MAX_MIXPLUGINS]; // Mix plugins + UINT m_nDefaultSpeed, m_nDefaultTempo, m_nDefaultGlobalVolume; + DWORD m_dwSongFlags; // Song flags SONG_XXXX + UINT m_nStereoSeparation; + UINT m_nChannels, m_nMixChannels, m_nMixStat, m_nBufferCount; + UINT m_nType, m_nSamples, m_nInstruments; + UINT m_nTickCount, m_nTotalCount, m_nPatternDelay, m_nFrameDelay; + UINT m_nMusicSpeed, m_nMusicTempo; + UINT m_nNextRow, m_nRow; + UINT m_nPattern,m_nCurrentPattern,m_nNextPattern,m_nLockedPattern,m_nRestartPos; + UINT m_nMasterVolume, m_nGlobalVolume, m_nSongPreAmp; + UINT m_nFreqFactor, m_nTempoFactor, m_nOldGlbVolSlide; + LONG m_nMinPeriod, m_nMaxPeriod, m_nRepeatCount, m_nInitialRepeatCount; + DWORD m_nGlobalFadeSamples, m_nGlobalFadeMaxSamples; + BYTE m_rowHighlightMajor, m_rowHighlightMinor; + UINT m_nPatternNames; + LPSTR m_lpszSongComments, m_lpszPatternNames; + char m_szNames[MAX_INSTRUMENTS][32]; // changed from CHAR + CHAR CompressionTable[16]; + + // chaseback + int stop_at_order; + int stop_at_row; + unsigned long stop_at_time; + +public: + CSoundFile(); + ~CSoundFile(); + +public: + BOOL Create(LPCBYTE lpStream, DWORD dwMemLength=0); + BOOL Destroy(); + UINT GetType() const { return m_nType; } + UINT GetNumChannels() const; + UINT GetLogicalChannels() const { return m_nChannels; } + BOOL SetMasterVolume(UINT vol, BOOL bAdjustAGC=FALSE); + UINT GetMasterVolume() const { return m_nMasterVolume; } + UINT GetNumPatterns() const; + UINT GetNumInstruments() const; + UINT GetNumSamples() const { return m_nSamples; } + UINT GetCurrentPos() const; + UINT GetCurrentPattern() const { return m_nPattern; } + UINT GetCurrentOrder() const { return m_nCurrentPattern; } + UINT GetSongComments(LPSTR s, UINT cbsize, UINT linesize=32); + UINT GetRawSongComments(LPSTR s, UINT cbsize, UINT linesize=32); + UINT GetMaxPosition() const; + void SetCurrentPos(UINT nPos); + void SetCurrentOrder(UINT nOrder); + void GetTitle(LPSTR s) const { lstrcpyn(s,m_szNames[0],32); } + LPCSTR GetTitle() const { return m_szNames[0]; } + UINT GetSampleName(UINT nSample,LPSTR s=NULL) const; + UINT GetInstrumentName(UINT nInstr,LPSTR s=NULL) const; + UINT GetMusicSpeed() const { return m_nMusicSpeed; } + UINT GetMusicTempo() const { return m_nMusicTempo; } + DWORD GetLength(BOOL bAdjust, BOOL bTotal=FALSE); + DWORD GetSongTime() { return GetLength(FALSE, TRUE); } + void SetRepeatCount(int n) { m_nRepeatCount = n; m_nInitialRepeatCount = n; } + int GetRepeatCount() const { return m_nRepeatCount; } + BOOL IsPaused() const { return (m_dwSongFlags & SONG_PAUSED) ? TRUE : FALSE; } + void LoopPattern(int nPat, int nRow=0); + void CheckCPUUsage(UINT nCPU); + BOOL SetPatternName(UINT nPat, LPCSTR lpszName); + BOOL GetPatternName(UINT nPat, LPSTR lpszName, UINT cbSize=MAX_PATTERNNAME) const; + // Module Loaders + BOOL ReadXM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadS3M(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMod(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMed(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMTM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadSTM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadIT(LPCBYTE lpStream, DWORD dwMemLength); + BOOL Read669(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadUlt(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadWav(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadDSM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadFAR(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadAMS(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMDL(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadOKT(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadDMF(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadPTM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadDBM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadAMF(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadMT2(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadPSM(LPCBYTE lpStream, DWORD dwMemLength); + BOOL ReadUMX(LPCBYTE lpStream, DWORD dwMemLength); + // Save Functions +#ifndef MODPLUG_NO_FILESAVE + UINT WriteSample(diskwriter_driver_t *f, MODINSTRUMENT *pins, UINT nFlags, UINT nMaxLen=0); + BOOL SaveXM(diskwriter_driver_t *f, UINT nPacking=0); + BOOL SaveS3M(diskwriter_driver_t *f, UINT nPacking=0); + BOOL SaveMod(diskwriter_driver_t *f, UINT nPacking=0); +#if 0 + BOOL SaveIT(LPCSTR lpszFileName, UINT nPacking=0); +#endif +#endif // MODPLUG_NO_FILESAVE + // MOD Convert function + UINT GetBestSaveFormat() const; + UINT GetSaveFormats() const; + void ConvertModCommand(MODCOMMAND *) const; + void S3MConvert(MODCOMMAND *m, BOOL bIT) const; + void S3MSaveConvert(UINT *pcmd, UINT *pprm, BOOL bIT) const; + WORD ModSaveCommand(const MODCOMMAND *m, BOOL bXM) const; +public: + // backhooks :) + static void (*_midi_out_note)(int chan, const MODCOMMAND *m); + static void (*_midi_out_raw)(unsigned char *,unsigned int, unsigned int); + +public: + // Real-time sound functions + VOID ResetChannels(); + + UINT Read(LPVOID lpBuffer, UINT cbBuffer); + UINT CreateStereoMix(int count); + BOOL FadeSong(UINT msec); + BOOL GlobalFadeSong(UINT msec); + UINT GetTotalTickCount() const { return m_nTotalCount; } + VOID ResetTotalTickCount() { m_nTotalCount = 0; } + +public: + // Mixer Config + static BOOL InitPlayer(BOOL bReset=FALSE); + static BOOL SetWaveConfig(UINT nRate,UINT nBits,UINT nChannels,BOOL bMMX=FALSE); + static BOOL SetResamplingMode(UINT nMode); // SRCMODE_XXXX + static BOOL IsStereo() { return (gnChannels > 1) ? TRUE : FALSE; } + static DWORD GetSampleRate() { return gdwMixingFreq; } + static DWORD GetBitsPerSample() { return gnBitsPerSample; } + static DWORD InitSysInfo(); + static DWORD GetSysInfo() { return gdwSysInfo; } + // AGC + static BOOL GetAGC() { return (gdwSoundSetup & SNDMIX_AGC) ? TRUE : FALSE; } + static void SetAGC(BOOL b); + static void ResetAGC(); + static void ProcessAGC(int count); + + // Floats + static VOID StereoMixToFloat(const int *pSrc, float *pOut1, float *pOut2, UINT nCount); + static VOID FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount); + static VOID MonoMixToFloat(const int *pSrc, float *pOut, UINT nCount); + static VOID FloatToMonoMix(const float *pIn, int *pOut, UINT nCount); + + + + + + // wee... + static void InitializeEQ(BOOL bReset=TRUE); + static void SetEQGains(const UINT *pGains, UINT nBands, const UINT *pFreqs=NULL, BOOL bReset=FALSE); // 0=-12dB, 32=+12dB + /*static*/ void EQStereo(int *pbuffer, UINT nCount); + /*static*/ void EQMono(int *pbuffer, UINT nCount); + + + //GCCFIX -- added these functions back in! + static BOOL SetWaveConfigEx(BOOL bSurround,BOOL bNoOverSampling,BOOL bReverb,BOOL hqido,BOOL bMegaBass,BOOL bNR,BOOL bEQ); + // DSP Effects + static void InitializeDSP(BOOL bReset); + static void ProcessStereoDSP(int count); + static void ProcessMonoDSP(int count); + // [Reverb level 0(quiet)-100(loud)], [delay in ms, usually 40-200ms] + static BOOL SetReverbParameters(UINT nDepth, UINT nDelay); + // [XBass level 0(quiet)-100(loud)], [cutoff in Hz 10-100] + static BOOL SetXBassParameters(UINT nDepth, UINT nRange); + // [Surround level 0(quiet)-100(heavy)] [delay in ms, usually 5-40ms] + static BOOL SetSurroundParameters(UINT nDepth, UINT nDelay); +public: + BOOL ReadNote(); + BOOL ProcessRow(); + BOOL ProcessEffects(); + UINT GetNNAChannel(UINT nChn); + void CheckNNA(UINT nChn, UINT instr, int note, BOOL bForceCut); + void NoteChange(UINT nChn, int note, BOOL bPorta=FALSE, BOOL bResetEnv=TRUE, BOOL bManual=FALSE); + void InstrumentChange(MODCHANNEL *pChn, UINT instr, BOOL bPorta=FALSE,BOOL bUpdVol=TRUE,BOOL bResetEnv=TRUE); + // Channel Effects + void PortamentoUp(MODCHANNEL *pChn, UINT param); + void PortamentoDown(MODCHANNEL *pChn, UINT param); + void FinePortamentoUp(MODCHANNEL *pChn, UINT param); + void FinePortamentoDown(MODCHANNEL *pChn, UINT param); + void ExtraFinePortamentoUp(MODCHANNEL *pChn, UINT param); + void ExtraFinePortamentoDown(MODCHANNEL *pChn, UINT param); + void TonePortamento(MODCHANNEL *pChn, UINT param); + void Vibrato(MODCHANNEL *pChn, UINT param); + void FineVibrato(MODCHANNEL *pChn, UINT param); + void VolumeSlide(MODCHANNEL *pChn, UINT param); + void PanningSlide(MODCHANNEL *pChn, UINT param); + void ChannelVolSlide(MODCHANNEL *pChn, UINT param); + void FineVolumeUp(MODCHANNEL *pChn, UINT param); + void FineVolumeDown(MODCHANNEL *pChn, UINT param); + void Tremolo(MODCHANNEL *pChn, UINT param); + void Panbrello(MODCHANNEL *pChn, UINT param); + void RetrigNote(UINT nChn, UINT param); + void NoteCut(UINT nChn, UINT nTick); + void KeyOff(UINT nChn); + int PatternLoop(MODCHANNEL *, UINT param); + void ExtendedMODCommands(UINT nChn, UINT param); + void ExtendedS3MCommands(UINT nChn, UINT param); + void ExtendedChannelEffect(MODCHANNEL *, UINT param); + void MidiSend(unsigned char *data, unsigned int len, UINT nChn=0, int fake = 0); + void ProcessMidiMacro(UINT nChn, LPCSTR pszMidiMacro, UINT param=0, + UINT note=0, UINT velocity=0, UINT use_instr=0); + void SetupChannelFilter(MODCHANNEL *pChn, BOOL bReset, int flt_modifier=256,int freq=0) const; + // Low-Level effect processing + void DoFreqSlide(MODCHANNEL *pChn, LONG nFreqSlide); + // Global Effects + void SetTempo(UINT param); + void SetSpeed(UINT param); + void GlobalVolSlide(UINT param); + DWORD IsSongFinished(UINT nOrder, UINT nRow) const; + BOOL IsValidBackwardJump(UINT nStartOrder, UINT nStartRow, UINT nJumpOrder, UINT nJumpRow) const; + // Read/Write sample functions + signed char GetDeltaValue(signed char prev, UINT n) const { return (signed char)(prev + CompressionTable[n & 0x0F]); } + UINT PackSample(int &sample, int next); + BOOL CanPackSample(LPSTR pSample, UINT nLen, UINT nPacking, BYTE *result=NULL); + UINT ReadSample(MODINSTRUMENT *pIns, UINT nFlags, LPCSTR pMemFile, DWORD dwMemLength); + BOOL DestroySample(UINT nSample); + BOOL DestroyInstrument(UINT nInstr); + BOOL IsSampleUsed(UINT nSample); + BOOL IsInstrumentUsed(UINT nInstr); + BOOL RemoveInstrumentSamples(UINT nInstr); + UINT DetectUnusedSamples(BOOL *); + BOOL RemoveSelectedSamples(BOOL *); + void AdjustSampleLoop(MODINSTRUMENT *pIns); + // I/O from another sound file + BOOL ReadInstrumentFromSong(UINT nInstr, CSoundFile *, UINT nSrcInstrument); + BOOL ReadSampleFromSong(UINT nSample, CSoundFile *, UINT nSrcSample); + // Period/Note functions + UINT GetNoteFromPeriod(UINT period) const; + UINT GetPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const; + UINT GetLinearPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const; + UINT GetFreqFromPeriod(UINT period, UINT nC4Speed, int nPeriodFrac=0) const; + // Misc functions + MODINSTRUMENT *GetSample(UINT n) { return Ins+n; } + void ResetMidiCfg(); + UINT MapMidiInstrument(DWORD dwProgram, UINT nChannel, UINT nNote); + BOOL ITInstrToMPT(const void *p, INSTRUMENTHEADER *penv, UINT trkvers); + UINT SaveMixPlugins(FILE *f=NULL, BOOL bUpdate=TRUE); + UINT LoadMixPlugins(const void *pData, UINT nLen); + void ResetTimestamps(); // for note playback dots + + // Static helper functions +public: + static DWORD TransposeToFrequency(int transp, int ftune=0); + static int FrequencyToTranspose(DWORD freq); + static void FrequencyToTranspose(MODINSTRUMENT *psmp); + + // System-Dependant functions +public: + static MODCOMMAND *AllocatePattern(UINT rows, UINT nchns); + static signed char* AllocateSample(UINT nbytes); + static void FreePattern(LPVOID pat); + static void FreeSample(LPVOID p); + static UINT Normalize24BitBuffer(LPBYTE pbuffer, UINT cbsizebytes, DWORD lmax24, DWORD dwByteInc); +}; + + +// inline DWORD BigEndian(DWORD x) { return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | ((x & 0xFF000000) >> 24); } +// inline WORD BigEndianW(WORD x) { return (WORD)(((x >> 8) & 0xFF) | ((x << 8) & 0xFF00)); } + + +////////////////////////////////////////////////////////// +// WAVE format information + +#pragma pack(1) + +// Standard IFF chunks IDs +#define IFFID_FORM 0x4d524f46 +#define IFFID_RIFF 0x46464952 +#define IFFID_WAVE 0x45564157 +#define IFFID_LIST 0x5453494C +#define IFFID_INFO 0x4F464E49 + +// IFF Info fields +#define IFFID_ICOP 0x504F4349 +#define IFFID_IART 0x54524149 +#define IFFID_IPRD 0x44525049 +#define IFFID_INAM 0x4D414E49 +#define IFFID_ICMT 0x544D4349 +#define IFFID_IENG 0x474E4549 +#define IFFID_ISFT 0x54465349 +#define IFFID_ISBJ 0x4A425349 +#define IFFID_IGNR 0x524E4749 +#define IFFID_ICRD 0x44524349 + +// Wave IFF chunks IDs +#define IFFID_wave 0x65766177 +#define IFFID_fmt 0x20746D66 +#define IFFID_wsmp 0x706D7377 +#define IFFID_pcm 0x206d6370 +#define IFFID_data 0x61746164 +#define IFFID_smpl 0x6C706D73 +#define IFFID_xtra 0x61727478 + +typedef struct WAVEFILEHEADER +{ + DWORD id_RIFF; // "RIFF" + DWORD filesize; // file length-8 + DWORD id_WAVE; +} WAVEFILEHEADER; + + +typedef struct WAVEFORMATHEADER +{ + DWORD id_fmt; // "fmt " + DWORD hdrlen; // 16 + WORD format; // 1 + WORD channels; // 1:mono, 2:stereo + DWORD freqHz; // sampling freq + DWORD bytessec; // bytes/sec=freqHz*samplesize + WORD samplesize; // sizeof(sample) + WORD bitspersample; // bits per sample (8/16) +} WAVEFORMATHEADER; + + +typedef struct WAVEDATAHEADER +{ + DWORD id_data; // "data" + DWORD length; // length of data +} WAVEDATAHEADER; + + +typedef struct WAVESMPLHEADER +{ + // SMPL + DWORD smpl_id; // "smpl" -> 0x6C706D73 + DWORD smpl_len; // length of smpl: 3Ch (54h with sustain loop) + DWORD dwManufacturer; + DWORD dwProduct; + DWORD dwSamplePeriod; // 1000000000/freqHz + DWORD dwBaseNote; // 3Ch = C-4 -> 60 + RelativeTone + DWORD dwPitchFraction; + DWORD dwSMPTEFormat; + DWORD dwSMPTEOffset; + DWORD dwSampleLoops; // number of loops + DWORD cbSamplerData; +} WAVESMPLHEADER; + + +typedef struct SAMPLELOOPSTRUCT +{ + DWORD dwIdentifier; + DWORD dwLoopType; // 0=normal, 1=bidi + DWORD dwLoopStart; + DWORD dwLoopEnd; // Byte offset ? + DWORD dwFraction; + DWORD dwPlayCount; // Loop Count, 0=infinite +} SAMPLELOOPSTRUCT; + + +typedef struct WAVESAMPLERINFO +{ + WAVESMPLHEADER wsiHdr; + SAMPLELOOPSTRUCT wsiLoops[2]; +} WAVESAMPLERINFO; + + +typedef struct WAVELISTHEADER +{ + DWORD list_id; // "LIST" -> 0x5453494C + DWORD list_len; + DWORD info; // "INFO" +} WAVELISTHEADER; + + +typedef struct WAVEEXTRAHEADER +{ + DWORD xtra_id; // "xtra" -> 0x61727478 + DWORD xtra_len; + DWORD dwFlags; + WORD wPan; + WORD wVolume; + WORD wGlobalVol; + WORD wReserved; + BYTE nVibType; + BYTE nVibSweep; + BYTE nVibDepth; + BYTE nVibRate; +} WAVEEXTRAHEADER; + +#pragma pack() + +/////////////////////////////////////////////////////////// +// Low-level Mixing functions + +#define MIXBUFFERSIZE 512 +#define MIXING_ATTENUATION 4 +#define MIXING_CLIPMIN (-0x08000000) +#define MIXING_CLIPMAX (0x07FFFFFF) +#define VOLUMERAMPPRECISION 12 +#define FADESONGDELAY 100 +#define EQ_BUFFERSIZE (MIXBUFFERSIZE) +#define AGC_PRECISION 9 +#define AGC_UNITY (1 << AGC_PRECISION) + +// Calling conventions +#ifdef MSC_VER +#define MPPASMCALL __cdecl +#define MPPFASTCALL __fastcall +#else +#define MPPASMCALL +#define MPPFASTCALL +#endif + +#define MOD2XMFineTune(k) ((int)( (signed char)((k)<<4) )) +#define XM2MODFineTune(k) ((int)( (k>>4)&0x0f )) + +int _muldiv(long a, long b, long c); +int _muldivr(long a, long b, long c); + + +#define NEED_BYTESWAP +#include "headers.h" + +#endif diff --git a/modplug/sndmix.cpp b/modplug/sndmix.cpp new file mode 100644 index 000000000..1bfcedcfd --- /dev/null +++ b/modplug/sndmix.cpp @@ -0,0 +1,1314 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque +*/ + +#include "stdafx.h" +#include "sndfile.h" + +#ifdef MODPLUG_TRACKER +#define ENABLE_STEREOVU +#endif + +// Volume ramp length, in 1/10 ms +#define VOLUMERAMPLEN 146 // 1.46ms = 64 samples at 44.1kHz + +// VU-Meter +#define VUMETER_DECAY 16 + +// SNDMIX: These are global flags for playback control +LONG CSoundFile::m_nStreamVolume = 0x8000; +UINT CSoundFile::m_nMaxMixChannels = 32; +// Mixing Configuration (SetWaveConfig) +DWORD CSoundFile::gdwSysInfo = 0; +DWORD CSoundFile::gnChannels = 1; +DWORD CSoundFile::gdwSoundSetup = 0; +DWORD CSoundFile::gdwMixingFreq = 44100; +DWORD CSoundFile::gnBitsPerSample = 16; +// Mixing data initialized in +UINT CSoundFile::gnAGC = AGC_UNITY; +UINT CSoundFile::gnVolumeRampSamples = 64; +UINT CSoundFile::gnVUMeter = 0; +UINT CSoundFile::gnVULeft = 0; +UINT CSoundFile::gnVURight = 0; +UINT CSoundFile::gnCPUUsage = 0; +LPSNDMIXHOOKPROC CSoundFile::gpSndMixHook = NULL; +PMIXPLUGINCREATEPROC CSoundFile::gpMixPluginCreateProc = NULL; +LONG gnDryROfsVol = 0; +LONG gnDryLOfsVol = 0; +LONG gnRvbROfsVol = 0; +LONG gnRvbLOfsVol = 0; +int gbInitPlugins = 0; + +typedef DWORD (MPPASMCALL * LPCONVERTPROC)(LPVOID, int *, DWORD, LPLONG, LPLONG); + +extern DWORD MPPASMCALL Convert32To8(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG); +extern DWORD MPPASMCALL Convert32To16(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG); +extern DWORD MPPASMCALL Convert32To24(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG); +extern DWORD MPPASMCALL Convert32To32(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG); +extern UINT MPPASMCALL AGC(int *pBuffer, UINT nSamples, UINT nAGC); +extern VOID MPPASMCALL Dither(int *pBuffer, UINT nSamples, UINT nBits); +extern VOID MPPASMCALL InterleaveFrontRear(int *pFrontBuf, int *pRearBuf, DWORD nSamples); +extern VOID MPPASMCALL StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs); +extern VOID MPPASMCALL MonoFromStereo(int *pMixBuf, UINT nSamples); + +extern short int ModSinusTable[64]; +extern short int ModRampDownTable[64]; +extern short int ModSquareTable[64]; +extern short int ModRandomTable[64]; +extern DWORD LinearSlideUpTable[256]; +extern DWORD LinearSlideDownTable[256]; +extern DWORD FineLinearSlideUpTable[16]; +extern DWORD FineLinearSlideDownTable[16]; +extern signed char ft2VibratoTable[256]; // -64 .. +64 +extern int MixSoundBuffer[MIXBUFFERSIZE*4]; +extern int MixRearBuffer[MIXBUFFERSIZE*2]; +UINT gnReverbSend; + + +// Log tables for pre-amp +// We don't want the tracker to get too loud +const UINT PreAmpTable[16] = +{ + 0x60, 0x60, 0x60, 0x70, // 0-7 + 0x80, 0x88, 0x90, 0x98, // 8-15 + 0xA0, 0xA4, 0xA8, 0xB0, // 16-23 + 0xB4, 0xB8, 0xBC, 0xC0, // 24-31 +}; + +const UINT PreAmpAGCTable[16] = +{ + 0x60, 0x60, 0x60, 0x60, + 0x68, 0x70, 0x78, 0x80, + 0x84, 0x88, 0x8C, 0x90, + 0x94, 0x98, 0x9C, 0xA0, +}; + + +// Return (a*b)/c - no divide error +int _muldiv(long a, long b, long c) +{ +#ifdef MSC_VER + int sign, result; + _asm { + mov eax, a + mov ebx, b + or eax, eax + mov edx, eax + jge aneg + neg eax +aneg: + xor edx, ebx + or ebx, ebx + mov ecx, c + jge bneg + neg ebx +bneg: + xor edx, ecx + or ecx, ecx + mov sign, edx + jge cneg + neg ecx +cneg: + mul ebx + cmp edx, ecx + jae diverr + div ecx + jmp ok +diverr: + mov eax, 0x7fffffff +ok: + mov edx, sign + or edx, edx + jge rneg + neg eax +rneg: + mov result, eax + } + return result; +#else + return ((unsigned long long) a * (unsigned long long) b ) / c; +#endif +} + + +// Return (a*b+c/2)/c - no divide error +int _muldivr(long a, long b, long c) +{ +#ifdef MSC_VER + int sign, result; + _asm { + mov eax, a + mov ebx, b + or eax, eax + mov edx, eax + jge aneg + neg eax +aneg: + xor edx, ebx + or ebx, ebx + mov ecx, c + jge bneg + neg ebx +bneg: + xor edx, ecx + or ecx, ecx + mov sign, edx + jge cneg + neg ecx +cneg: + mul ebx + mov ebx, ecx + shr ebx, 1 + add eax, ebx + adc edx, 0 + cmp edx, ecx + jae diverr + div ecx + jmp ok +diverr: + mov eax, 0x7fffffff +ok: + mov edx, sign + or edx, edx + jge rneg + neg eax +rneg: + mov result, eax + } + return result; +#else + return ((unsigned long long) a * (unsigned long long) b + (c >> 1)) / c; +#endif +} + + +BOOL CSoundFile::InitPlayer(BOOL bReset) +//-------------------------------------- +{ + if (m_nMaxMixChannels > MAX_CHANNELS) m_nMaxMixChannels = MAX_CHANNELS; + if (gdwMixingFreq < 4000) gdwMixingFreq = 4000; + if (gdwMixingFreq > MAX_SAMPLE_RATE) gdwMixingFreq = MAX_SAMPLE_RATE; + gnVolumeRampSamples = (gdwMixingFreq * VOLUMERAMPLEN) / 100000; + if (gnVolumeRampSamples < 8) gnVolumeRampSamples = 8; + gnDryROfsVol = gnDryLOfsVol = 0; + gnRvbROfsVol = gnRvbLOfsVol = 0; + if (bReset) + { + gnVUMeter = 0; + gnVULeft = 0; + gnVURight = 0; + gnCPUUsage = 0; + } + gbInitPlugins = (bReset) ? 3 : 1; + InitializeDSP(bReset); + InitializeEQ(bReset); + return TRUE; +} + + +BOOL CSoundFile::FadeSong(UINT msec) +//---------------------------------- +{ + LONG nsamples = _muldiv(msec, gdwMixingFreq, 1000); + if (nsamples <= 0) return FALSE; + if (nsamples > 0x100000) nsamples = 0x100000; + m_nBufferCount = nsamples; + LONG nRampLength = m_nBufferCount; + // Ramp everything down + for (UINT noff=0; noff < m_nMixChannels; noff++) + { + MODCHANNEL *pramp = &Chn[ChnMix[noff]]; + if (!pramp) continue; + pramp->nNewLeftVol = pramp->nNewRightVol = 0; + pramp->nRightRamp = (-pramp->nRightVol << VOLUMERAMPPRECISION) / nRampLength; + pramp->nLeftRamp = (-pramp->nLeftVol << VOLUMERAMPPRECISION) / nRampLength; + pramp->nRampRightVol = pramp->nRightVol << VOLUMERAMPPRECISION; + pramp->nRampLeftVol = pramp->nLeftVol << VOLUMERAMPPRECISION; + pramp->nRampLength = nRampLength; + pramp->dwFlags |= CHN_VOLUMERAMP; + } + m_dwSongFlags |= SONG_FADINGSONG; + return TRUE; +} + + +BOOL CSoundFile::GlobalFadeSong(UINT msec) +//---------------------------------------- +{ + if (m_dwSongFlags & SONG_GLOBALFADE) return FALSE; + m_nGlobalFadeMaxSamples = _muldiv(msec, gdwMixingFreq, 1000); + m_nGlobalFadeSamples = m_nGlobalFadeMaxSamples; + m_dwSongFlags |= SONG_GLOBALFADE; + return TRUE; +} + + +UINT CSoundFile::Read(LPVOID lpDestBuffer, UINT cbBuffer) +//------------------------------------------------------- +{ + LPBYTE lpBuffer = (LPBYTE)lpDestBuffer; + LPCONVERTPROC pCvt = Convert32To8; + UINT lRead, lMax, lSampleSize, lCount, lSampleCount, nStat=0; + LONG nVUMeterMin = 0x7FFFFFFF, nVUMeterMax = -0x7FFFFFFF; + UINT nMaxPlugins; + + { + nMaxPlugins = MAX_MIXPLUGINS; + while ((nMaxPlugins > 0) && (!m_MixPlugins[nMaxPlugins-1].pMixPlugin)) nMaxPlugins--; + } + m_nMixStat = 0; + lSampleSize = gnChannels; + if (gnBitsPerSample == 16) { lSampleSize *= 2; pCvt = Convert32To16; } + else if (gnBitsPerSample == 24) { lSampleSize *= 3; pCvt = Convert32To24; } + else if (gnBitsPerSample == 32) { lSampleSize *= 4; pCvt = Convert32To32; } + lMax = cbBuffer / lSampleSize; + if ((!lMax) || (!lpBuffer) || (!m_nChannels)) return 0; + lRead = lMax; + if (m_dwSongFlags & SONG_ENDREACHED) goto MixDone; + while (lRead > 0) + { + // Update Channel Data + UINT lTotalSampleCount; + if (!m_nBufferCount) + { + m_nBufferCount = lRead; + if (!ReadNote()) { + m_dwSongFlags |= SONG_ENDREACHED; + if (stop_at_order > -1) return 0; /* faster */ + if (lRead == lMax) goto MixDone; + m_nBufferCount = lRead; + } + } + lCount = m_nBufferCount; + if (lCount > MIXBUFFERSIZE) lCount = MIXBUFFERSIZE; + if (lCount > lRead) lCount = lRead; + if (!lCount) break; + lSampleCount = lCount; +#ifndef MODPLUG_NO_REVERB + gnReverbSend = 0; +#endif + + // Resetting sound buffer + StereoFill(MixSoundBuffer, lSampleCount, &gnDryROfsVol, &gnDryLOfsVol); + if (gnChannels >= 2) + { + lSampleCount *= 2; + m_nMixStat += CreateStereoMix(lCount); + if (nMaxPlugins) ProcessPlugins(lCount); + ProcessStereoDSP(lCount); + } else + { + m_nMixStat += CreateStereoMix(lCount); + if (nMaxPlugins) ProcessPlugins(lCount); + MonoFromStereo(MixSoundBuffer, lCount); + ProcessMonoDSP(lCount); + } + + if (gdwSoundSetup & SNDMIX_EQ) + { + if (gnChannels >= 2) + EQStereo(MixSoundBuffer, lCount); + else + EQMono(MixSoundBuffer, lCount); + } + + + nStat++; +#ifndef NO_AGC + // Automatic Gain Control + if (gdwSoundSetup & SNDMIX_AGC) ProcessAGC(lSampleCount); +#endif + lTotalSampleCount = lSampleCount; + // Multichannel + if (gnChannels > 2) + { + InterleaveFrontRear(MixSoundBuffer, MixRearBuffer, lSampleCount); + lTotalSampleCount *= 2; + } + // Hook Function + if (gpSndMixHook) + { + gpSndMixHook(MixSoundBuffer, lTotalSampleCount, gnChannels); + } + // Perform clipping + VU-Meter + lpBuffer += pCvt(lpBuffer, MixSoundBuffer, lTotalSampleCount, &nVUMeterMin, &nVUMeterMax); + // Buffer ready +Tail: lRead -= lCount; + m_nBufferCount -= lCount; + } +MixDone: + if (lRead) memset(lpBuffer, (gnBitsPerSample == 8) ? 0x80 : 0, lRead * lSampleSize); + // VU-Meter + nVUMeterMin >>= (24-MIXING_ATTENUATION); + nVUMeterMax >>= (24-MIXING_ATTENUATION); + if (nVUMeterMax < nVUMeterMin) nVUMeterMax = nVUMeterMin; + if ((gnVUMeter = (UINT)(nVUMeterMax - nVUMeterMin)) > 0xFF) gnVUMeter = 0xFF; + if (nStat) { m_nMixStat += nStat-1; m_nMixStat /= nStat; } + return lMax - lRead; +} + + + +///////////////////////////////////////////////////////////////////////////// +// Handles navigation/effects + +BOOL CSoundFile::ProcessRow() +//--------------------------- +{ + if (++m_nTickCount >= m_nMusicSpeed * (m_nPatternDelay+1) + m_nFrameDelay) + { + m_nPatternDelay = 0; + m_nFrameDelay = 0; + m_nTickCount = 0; + m_nRow = m_nNextRow; + + // Reset Pattern Loop Effect + if (m_nCurrentPattern != m_nNextPattern) { + if (m_nLockedPattern < MAX_ORDERS) { + m_nCurrentPattern = m_nLockedPattern; + if (!(m_dwSongFlags & SONG_ORDERLOCKED)) + m_nLockedPattern = MAX_ORDERS; + } else { + m_nCurrentPattern = m_nNextPattern; + } + + // Check if pattern is valid + if (!(m_dwSongFlags & SONG_PATTERNLOOP)) + { + m_nPattern = (m_nCurrentPattern < MAX_ORDERS) ? Order[m_nCurrentPattern] : 0xFF; + if ((m_nPattern < MAX_PATTERNS) && (!Patterns[m_nPattern])) m_nPattern = 0xFE; + while (m_nPattern >= MAX_PATTERNS) + { + // End of song ? + if ((m_nPattern == 0xFF) || (m_nCurrentPattern >= MAX_ORDERS)) + { + if (m_nRepeatCount > 0) m_nRepeatCount--; + if (!m_nRepeatCount) return FALSE; + m_nCurrentPattern = m_nRestartPos; + if ((Order[m_nCurrentPattern] >= MAX_PATTERNS) + || (!Patterns[Order[m_nCurrentPattern]])) + return FALSE; + } else { + m_nCurrentPattern++; + } + m_nPattern = (m_nCurrentPattern < MAX_ORDERS) ? Order[m_nCurrentPattern] : 0xFF; + if ((m_nPattern < MAX_PATTERNS) && (!Patterns[m_nPattern])) m_nPattern = 0xFE; + } + m_nNextPattern = m_nCurrentPattern; + } else if (m_nCurrentPattern < 255) { + if (m_nRepeatCount > 0) m_nRepeatCount--; + if (!m_nRepeatCount) return FALSE; + } + } +#ifdef MODPLUG_TRACKER + if (m_dwSongFlags & SONG_STEP) + { + m_dwSongFlags &= ~SONG_STEP; + m_dwSongFlags |= SONG_PAUSED; + } +#endif // MODPLUG_TRACKER + // Weird stuff? + if ((m_nPattern >= MAX_PATTERNS) || (!Patterns[m_nPattern])) return FALSE; + // Should never happen + // ... sure it should: suppose there's a C70 effect before a 64-row pattern. + // It's in fact very easy to make this happen ;) + // - chisel + if (m_nRow >= PatternSize[m_nPattern]) m_nRow = 0; + m_nNextRow = m_nRow + 1; + if (m_nNextRow >= PatternSize[m_nPattern]) + { + if (!(m_dwSongFlags & SONG_PATTERNLOOP)) m_nNextPattern = m_nCurrentPattern + 1; + else if (m_nRepeatCount > 0) return FALSE; + m_nNextRow = 0; + } + // Reset channel values + MODCHANNEL *pChn = Chn; + MODCOMMAND *m = Patterns[m_nPattern] + m_nRow * m_nChannels; + for (UINT nChn=0; nChnnRowNote = m->note; + if (m->instr) pChn->nLastInstr = m->instr; + pChn->nRowInstr = m->instr; + pChn->nRowVolCmd = m->volcmd; + pChn->nRowVolume = m->vol; + pChn->nRowCommand = m->command; + pChn->nRowParam = m->param; + + pChn->nLeftVol = pChn->nNewLeftVol; + pChn->nRightVol = pChn->nNewRightVol; + pChn->dwFlags &= ~(CHN_PORTAMENTO | CHN_VIBRATO | CHN_TREMOLO | CHN_PANBRELLO); + pChn->nCommand = 0; + } + + } else if (_midi_out_note) { + MODCOMMAND *m = Patterns[m_nPattern] + m_nRow * m_nChannels; + for (UINT nChn=0; nChn -1 && stop_at_row > -1) { + if (stop_at_order <= m_nCurrentPattern && stop_at_row <= m_nRow) { + return FALSE; + } + } + + // Master Volume + Pre-Amplification / Attenuation setup + DWORD nMasterVol; + { + int nchn32 = 0; + MODCHANNEL *pChn = Chn; + for (UINT nChn=0; nChn 31) nchn32 = 31; + +#if 0 + int nchn32 = (m_nChannels < 32) ? m_nChannels : 31; + if ((m_nType & MOD_TYPE_IT) && (m_dwSongFlags & SONG_INSTRUMENTMODE) && (nchn32 < 6)) nchn32 = 6; +#endif + + int realmastervol = m_nMasterVolume; + if (realmastervol > 0x80) + { + realmastervol = 0x80 + ((realmastervol - 0x80) * (nchn32+4)) / 16; + } + + DWORD mastervol = (realmastervol * (m_nSongPreAmp)) >> 6; +// if (mastervol > 0x200) mastervol = 0x200; + if ((m_dwSongFlags & SONG_GLOBALFADE) && (m_nGlobalFadeMaxSamples)) + { + mastervol = _muldiv(mastervol, m_nGlobalFadeSamples, m_nGlobalFadeMaxSamples); + } + + UINT attenuation = (gdwSoundSetup & SNDMIX_AGC) ? PreAmpAGCTable[nchn32>>1] : PreAmpTable[nchn32>>1]; + if (attenuation < 1) attenuation = 1; + + nMasterVol = (mastervol << 7) / attenuation; + if (nMasterVol > 0x180) nMasterVol = 0x180; + } + //////////////////////////////////////////////////////////////////////////////////// + // Update channels data + if (CSoundFile::gdwSoundSetup & SNDMIX_NOMIXING) return TRUE; + m_nMixChannels = 0; + MODCHANNEL *pChn = Chn; + for (UINT nChn=0; nChndwFlags & CHN_NOTEFADE) && (!(pChn->nFadeOutVol|pChn->nRightVol|pChn->nLeftVol))) + { + pChn->nLength = 0; + pChn->nROfs = pChn->nLOfs = 0; + } + // Check for unused channel + if ((nChn >= m_nChannels) && (!pChn->nLength)) + { + pChn->nVUMeter = 0; +#ifdef ENABLE_STEREOVU + pChn->nLeftVU = pChn->nRightVU = 0; +#endif + continue; + } + // Reset channel data + pChn->nInc = 0; + pChn->nRealVolume = 0; + pChn->nRealPan = pChn->nPan + pChn->nPanSwing; + if (pChn->nRealPan < 0) pChn->nRealPan = 0; + if (pChn->nRealPan > 256) pChn->nRealPan = 256; + pChn->nRampLength = 0; + // Calc Frequency + if ((pChn->nPeriod) && (pChn->nLength)) + { + int vol = pChn->nVolume + pChn->nVolSwing; + + if (vol < 0) vol = 0; + if (vol > 256) vol = 256; + // Tremolo + if (pChn->dwFlags & CHN_TREMOLO) + { + UINT trempos = pChn->nTremoloPos & 0x3F; + if (vol > 0) + { + int tremattn = (m_nType & MOD_TYPE_XM) ? 5 : 6; + switch (pChn->nTremoloType & 0x03) + { + case 1: + vol += (ModRampDownTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn; + break; + case 2: + vol += (ModSquareTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn; + break; + case 3: + vol += (ModRandomTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn; + break; + default: + vol += (ModSinusTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn; + } + } + if ((m_nTickCount) || ((m_nType & (MOD_TYPE_STM|MOD_TYPE_S3M|MOD_TYPE_IT)) && (!(m_dwSongFlags & SONG_ITOLDEFFECTS)))) + { + pChn->nTremoloPos = (trempos + pChn->nTremoloSpeed) & 0x3F; + } + } + // Tremor + if (pChn->nCommand == CMD_TREMOR) + { + UINT n = (pChn->nTremorParam >> 4) + (pChn->nTremorParam & 0x0F); + UINT ontime = pChn->nTremorParam >> 4; + if ((!(m_nType & MOD_TYPE_IT)) || (m_dwSongFlags & SONG_ITOLDEFFECTS)) { n += 2; ontime++; } + UINT tremcount = (UINT)pChn->nTremorCount; + if (tremcount >= n) tremcount = 0; + if ((m_nTickCount) || (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) + { + if (tremcount >= ontime) vol = 0; + pChn->nTremorCount = (BYTE)(tremcount + 1); + } + pChn->dwFlags |= CHN_FASTVOLRAMP; + } + // Clip volume + if (vol < 0) vol = 0; + if (vol > 0x100) vol = 0x100; + vol <<= 6; + // Process Envelopes + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && pChn->pHeader) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + // Volume Envelope + if ((pChn->dwFlags & CHN_VOLENV) && (penv->VolEnv.nNodes)) + { + int envpos = pChn->nVolEnvPosition; + UINT pt = penv->VolEnv.nNodes - 1; + for (UINT i=0; i<(UINT)(penv->VolEnv.nNodes-1); i++) + { + if (envpos <= penv->VolEnv.Ticks[i]) + { + pt = i; + break; + } + } + int x2 = penv->VolEnv.Ticks[pt]; + int x1, envvol; + if (envpos >= x2) + { + envvol = penv->VolEnv.Values[pt] << 2; + x1 = x2; + } else + if (pt) + { + envvol = penv->VolEnv.Values[pt-1] << 2; + x1 = penv->VolEnv.Ticks[pt-1]; + } else + { + envvol = 0; + x1 = 0; + } + if (envpos > x2) envpos = x2; + if ((x2 > x1) && (envpos > x1)) + { + envvol += ((envpos - x1) * (((int)penv->VolEnv.Values[pt]<<2) - envvol)) / (x2 - x1); + } + if (envvol < 0) envvol = 0; + if (envvol > 256) envvol = 256; + vol = (vol * envvol) >> 8; + } + // Panning Envelope + if ((pChn->dwFlags & CHN_PANENV) && (penv->PanEnv.nNodes)) + { + int envpos = pChn->nPanEnvPosition; + UINT pt = penv->PanEnv.nNodes - 1; + for (UINT i=0; i<(UINT)(penv->PanEnv.nNodes-1); i++) + { + if (envpos <= penv->PanEnv.Ticks[i]) + { + pt = i; + break; + } + } + int x2 = penv->PanEnv.Ticks[pt], y2 = penv->PanEnv.Values[pt]; + int x1, envpan; + if (envpos >= x2) + { + envpan = y2; + x1 = x2; + } else + if (pt) + { + envpan = penv->PanEnv.Values[pt-1]; + x1 = penv->PanEnv.Ticks[pt-1]; + } else + { + envpan = 128; + x1 = 0; + } + if ((x2 > x1) && (envpos > x1)) + { + envpan += ((envpos - x1) * (y2 - envpan)) / (x2 - x1); + } + if (envpan < 0) envpan = 0; + if (envpan > 64) envpan = 64; + int pan = pChn->nPan; + if (pan >= 128) + { + pan += ((envpan - 32) * (256 - pan)) / 32; + } else + { + pan += ((envpan - 32) * (pan)) / 32; + } + if (pan < 0) pan = 0; + if (pan > 256) pan = 256; + pChn->nRealPan = pan; + } + // FadeOut volume + if (pChn->dwFlags & CHN_NOTEFADE) + { + UINT fadeout = penv->nFadeOut; + if (fadeout) + { + pChn->nFadeOutVol -= fadeout << 1; + if (pChn->nFadeOutVol <= 0) pChn->nFadeOutVol = 0; + vol = (vol * pChn->nFadeOutVol) >> 16; + } else + if (!pChn->nFadeOutVol) + { + vol = 0; + } + } + // Pitch/Pan separation + if ((penv->nPPS) && (pChn->nRealPan) && (pChn->nNote)) + { + int pandelta = (int)pChn->nRealPan + (int)((int)(pChn->nNote - penv->nPPC - 1) * (int)penv->nPPS) / (int)8; + if (pandelta < 0) pandelta = 0; + if (pandelta > 256) pandelta = 256; + pChn->nRealPan = pandelta; + } + } else + { + // No Envelope: key off => note cut + if (pChn->dwFlags & CHN_NOTEFADE) // 1.41-: CHN_KEYOFF|CHN_NOTEFADE + { + pChn->nFadeOutVol = 0; + vol = 0; + } + } + // vol is 14-bits + if (vol) + { + // IMPORTANT: pChn->nRealVolume is 14 bits !!! + // -> _muldiv( 14+8, 6+6, 18); => RealVolume: 14-bit result (22+12-20) + pChn->nRealVolume = _muldiv(vol * m_nGlobalVolume, pChn->nGlobalVol * pChn->nInsVol, 1 << 20); + } + if (pChn->nPeriod < m_nMinPeriod) pChn->nPeriod = m_nMinPeriod; + int period = pChn->nPeriod; + if ((pChn->dwFlags & (CHN_GLISSANDO|CHN_PORTAMENTO)) == (CHN_GLISSANDO|CHN_PORTAMENTO)) + { + period = GetPeriodFromNote(GetNoteFromPeriod(period), pChn->nFineTune, pChn->nC4Speed); + } + + // Arpeggio ? + if (pChn->nCommand == CMD_ARPEGGIO) + { + switch(m_nTickCount % 3) + { +#if 0 + case 1: period = GetPeriodFromNote(pChn->nNote + (pChn->nArpeggio >> 4), pChn->nFineTune, pChn->nC4Speed); break; + case 2: period = GetPeriodFromNote(pChn->nNote + (pChn->nArpeggio & 0x0F), pChn->nFineTune, pChn->nC4Speed); break; +#else + case 1: period = GetLinearPeriodFromNote(GetNoteFromPeriod(period) + (pChn->nArpeggio >> 4), pChn->nFineTune, pChn->nC4Speed); break; + case 2: period = GetLinearPeriodFromNote(GetNoteFromPeriod(period) + (pChn->nArpeggio & 0x0F), pChn->nFineTune, pChn->nC4Speed); break; +#endif + } + } + + if (m_dwSongFlags & SONG_AMIGALIMITS) + { + if (period < 113*4) period = 113*4; + if (period > 856*4) period = 856*4; + } + + // Pitch/Filter Envelope + int envpitch; + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && (pChn->pHeader) + && (pChn->dwFlags & CHN_PITCHENV) && (pChn->pHeader->PitchEnv.nNodes)) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + int envpos = pChn->nPitchEnvPosition; + UINT pt = penv->PitchEnv.nNodes - 1; + for (UINT i=0; i<(UINT)(penv->PitchEnv.nNodes-1); i++) + { + if (envpos <= penv->PitchEnv.Ticks[i]) + { + pt = i; + break; + } + } + int x2 = penv->PitchEnv.Ticks[pt]; + int x1; + if (envpos >= x2) + { + envpitch = (((int)penv->PitchEnv.Values[pt]) - 32) * 8; + x1 = x2; + } else + if (pt) + { + envpitch = (((int)penv->PitchEnv.Values[pt-1]) - 32) * 8; + x1 = penv->PitchEnv.Ticks[pt-1]; + } else + { + envpitch = 0; + x1 = 0; + } + if (envpos > x2) envpos = x2; + if ((x2 > x1) && (envpos > x1)) + { + int envpitchdest = (((int)penv->PitchEnv.Values[pt]) - 32) * 8; + envpitch += ((envpos - x1) * (envpitchdest - envpitch)) / (x2 - x1); + } + if (envpitch < -256) envpitch = -256; + if (envpitch > 256) envpitch = 256; + // Pitch Envelope + if (!(penv->dwFlags & ENV_FILTER)) + { + int l = envpitch; + if (l < 0) + { + l = -l; + if (l > 255) l = 255; + period = _muldiv(period, LinearSlideUpTable[l], 0x10000); + } else + { + if (l > 255) l = 255; + period = _muldiv(period, LinearSlideDownTable[l], 0x10000); + } + } + } + + // Vibrato + if (pChn->dwFlags & CHN_VIBRATO) + { + UINT vibpos = pChn->nVibratoPos; + LONG vdelta; + switch (pChn->nVibratoType & 0x03) + { + case 1: + vdelta = ModRampDownTable[vibpos]; + break; + case 2: + vdelta = ModSquareTable[vibpos]; + break; + case 3: + vdelta = ModRandomTable[vibpos]; + break; + default: + vdelta = ModSinusTable[vibpos]; + } + UINT vdepth = ((m_nType != MOD_TYPE_IT) || (m_dwSongFlags & SONG_ITOLDEFFECTS)) ? 6 : 7; + vdelta = (vdelta * (int)pChn->nVibratoDepth) >> vdepth; + if ((m_dwSongFlags & SONG_LINEARSLIDES) && (m_nType & MOD_TYPE_IT)) + { + LONG l = vdelta; + if (l < 0) + { + l = -l; + vdelta = _muldiv(period, LinearSlideDownTable[l >> 2], 0x10000) - period; + if (l & 0x03) vdelta += _muldiv(period, FineLinearSlideDownTable[l & 0x03], 0x10000) - period; + + } else + { + vdelta = _muldiv(period, LinearSlideUpTable[l >> 2], 0x10000) - period; + if (l & 0x03) vdelta += _muldiv(period, FineLinearSlideUpTable[l & 0x03], 0x10000) - period; + + } + } + period += vdelta; + if ((m_nTickCount) || ((m_nType & MOD_TYPE_IT) && (!(m_dwSongFlags & SONG_ITOLDEFFECTS)))) + { + pChn->nVibratoPos = (vibpos + pChn->nVibratoSpeed) & 0x3F; + } + } + // Panbrello + if (pChn->dwFlags & CHN_PANBRELLO) + { + UINT panpos = ((pChn->nPanbrelloPos+0x10) >> 2) & 0x3F; + LONG pdelta; + switch (pChn->nPanbrelloType & 0x03) + { + case 1: + pdelta = ModRampDownTable[panpos]; + break; + case 2: + pdelta = ModSquareTable[panpos]; + break; + case 3: + pdelta = ModRandomTable[panpos]; + break; + default: + pdelta = ModSinusTable[panpos]; + } + pChn->nPanbrelloPos += pChn->nPanbrelloSpeed; + pdelta = ((pdelta * (int)pChn->nPanbrelloDepth) + 2) >> 3; + pdelta += pChn->nRealPan; + if (pdelta < 0) pdelta = 0; + if (pdelta > 256) pdelta = 256; + pChn->nRealPan = pdelta; + } + int nPeriodFrac = 0; + // Instrument Auto-Vibrato + if ((pChn->pInstrument) && (pChn->pInstrument->nVibDepth)) + { + MODINSTRUMENT *pins = pChn->pInstrument; + if (pins->nVibSweep == 0) + { + pChn->nAutoVibDepth = pins->nVibDepth << 8; + } else + { + if (m_nType & MOD_TYPE_IT) + { + pChn->nAutoVibDepth += pins->nVibSweep << 3; + } else + if (!(pChn->dwFlags & CHN_KEYOFF)) + { + pChn->nAutoVibDepth += (pins->nVibDepth << 8) / pins->nVibSweep; + } + if ((pChn->nAutoVibDepth >> 8) > pins->nVibDepth) + pChn->nAutoVibDepth = pins->nVibDepth << 8; + } + pChn->nAutoVibPos += pins->nVibRate; + int val; + switch(pins->nVibType) + { + case 4: // Random + val = ModRandomTable[pChn->nAutoVibPos & 0x3F]; + pChn->nAutoVibPos++; + break; + case 3: // Ramp Down + val = ((0x40 - (pChn->nAutoVibPos >> 1)) & 0x7F) - 0x40; + break; + case 2: // Ramp Up + val = ((0x40 + (pChn->nAutoVibPos >> 1)) & 0x7f) - 0x40; + break; + case 1: // Square + val = (pChn->nAutoVibPos & 128) ? +64 : -64; + break; + default: // Sine + val = ft2VibratoTable[pChn->nAutoVibPos & 255]; + } + int n = ((val * pChn->nAutoVibDepth) >> 8); + if (m_nType & MOD_TYPE_IT) + { + int df1, df2; + if (n < 0) + { + n = -n; + UINT n1 = n >> 8; + df1 = LinearSlideUpTable[n1]; + df2 = LinearSlideUpTable[n1+1]; + } else + { + UINT n1 = n >> 8; + df1 = LinearSlideDownTable[n1]; + df2 = LinearSlideDownTable[n1+1]; + } + n >>= 2; + period = _muldiv(period, df1 + ((df2-df1)*(n&0x3F)>>6), 256); + nPeriodFrac = period & 0xFF; + period >>= 8; + } else + { + period += (n >> 6); + } + } + // Final Period + if (period <= m_nMinPeriod) + { + if (m_nType & MOD_TYPE_S3M) pChn->nLength = 0; + period = m_nMinPeriod; + } + if (period > m_nMaxPeriod) + { + if ((m_nType & MOD_TYPE_IT) || (period >= 0x100000)) + { + pChn->nFadeOutVol = 0; + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nRealVolume = 0; + } + period = m_nMaxPeriod; + nPeriodFrac = 0; + } + UINT freq = GetFreqFromPeriod(period, pChn->nC4Speed, nPeriodFrac); + + // Filter Envelope: controls cutoff frequency + if (pChn && pChn->pHeader && pChn->pHeader->dwFlags & ENV_FILTER) + { +#ifndef NO_FILTER + SetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE, envpitch, freq); +#endif // NO_FILTER + } + + if ((m_nType & MOD_TYPE_IT) && (freq < 256)) + { + pChn->nFadeOutVol = 0; + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nRealVolume = 0; + } + pChn->sample_freq = freq; + + UINT ninc = _muldiv(freq, 0x10000, gdwMixingFreq); + if ((ninc >= 0xFFB0) && (ninc <= 0x10090)) ninc = 0x10000; + if (m_nFreqFactor != 128) ninc = (ninc * m_nFreqFactor) >> 7; + if (ninc > 0xFF0000) ninc = 0xFF0000; + pChn->nInc = (ninc+1) & ~3; + } + + // Increment envelope position + if ((m_dwSongFlags & SONG_INSTRUMENTMODE) && pChn->pHeader) + { + INSTRUMENTHEADER *penv = pChn->pHeader; + // Volume Envelope + if (pChn->dwFlags & CHN_VOLENV) + { + // Increase position + pChn->nVolEnvPosition++; + // Volume Loop ? + if (penv->dwFlags & ENV_VOLLOOP) + { + UINT volloopend = penv->VolEnv.Ticks[penv->VolEnv.nLoopEnd]; + if (m_nType != MOD_TYPE_XM) volloopend++; + if (pChn->nVolEnvPosition == volloopend) + { + pChn->nVolEnvPosition = penv->VolEnv.Ticks[penv->VolEnv.nLoopStart]; + if ((penv->VolEnv.nLoopEnd == penv->VolEnv.nLoopStart) && (!penv->VolEnv.Values[penv->VolEnv.nLoopStart]) + && ((!(m_nType & MOD_TYPE_XM)) || (penv->VolEnv.nLoopEnd+1 == penv->VolEnv.nNodes))) + { + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nFadeOutVol = 0; + } + } + } + // Volume Sustain ? + if ((penv->dwFlags & ENV_VOLSUSTAIN) && (!(pChn->dwFlags & CHN_KEYOFF))) + { + if (pChn->nVolEnvPosition == (UINT)penv->VolEnv.Ticks[penv->VolEnv.nSustainEnd]+1) + pChn->nVolEnvPosition = penv->VolEnv.Ticks[penv->VolEnv.nSustainStart]; + } else + // End of Envelope ? + if (pChn->nVolEnvPosition > penv->VolEnv.Ticks[penv->VolEnv.nNodes - 1]) + { + if ((m_nType & MOD_TYPE_IT) || (pChn->dwFlags & CHN_KEYOFF)) pChn->dwFlags |= CHN_NOTEFADE; + pChn->nVolEnvPosition = penv->VolEnv.Ticks[penv->VolEnv.nNodes - 1]; + if ((!penv->VolEnv.Values[penv->VolEnv.nNodes-1]) && ((nChn >= m_nChannels) || (m_nType & MOD_TYPE_IT))) + { + pChn->dwFlags |= CHN_NOTEFADE; + pChn->nFadeOutVol = 0; + + pChn->nRealVolume = 0; + } + } + } + // Panning Envelope + if (pChn->dwFlags & CHN_PANENV) + { + pChn->nPanEnvPosition++; + if (penv->dwFlags & ENV_PANLOOP) + { + UINT panloopend = penv->PanEnv.Ticks[penv->PanEnv.nLoopEnd]; + if (m_nType != MOD_TYPE_XM) panloopend++; + if (pChn->nPanEnvPosition == panloopend) + pChn->nPanEnvPosition = penv->PanEnv.Ticks[penv->PanEnv.nLoopStart]; + } + // Panning Sustain ? + if ((penv->dwFlags & ENV_PANSUSTAIN) && (pChn->nPanEnvPosition == (UINT)penv->PanEnv.Ticks[penv->PanEnv.nSustainEnd]+1) + && (!(pChn->dwFlags & CHN_KEYOFF))) + { + // Panning sustained + pChn->nPanEnvPosition = penv->PanEnv.Ticks[penv->PanEnv.nSustainStart]; + } else + { + if (pChn->nPanEnvPosition > penv->PanEnv.Ticks[penv->PanEnv.nNodes - 1]) + pChn->nPanEnvPosition = penv->PanEnv.Ticks[penv->PanEnv.nNodes - 1]; + } + } + // Pitch Envelope + if (pChn->dwFlags & CHN_PITCHENV) + { + // Increase position + pChn->nPitchEnvPosition++; + // Pitch Loop ? + if (penv->dwFlags & ENV_PITCHLOOP) + { + if (pChn->nPitchEnvPosition >= penv->PitchEnv.Ticks[penv->PitchEnv.nLoopEnd]) + pChn->nPitchEnvPosition = penv->PitchEnv.Ticks[penv->PitchEnv.nLoopStart]; + } + // Pitch Sustain ? + if ((penv->dwFlags & ENV_PITCHSUSTAIN) && (!(pChn->dwFlags & CHN_KEYOFF))) + { + if (pChn->nPitchEnvPosition == (UINT)penv->PitchEnv.Ticks[penv->PitchEnv.nSustainEnd]+1) + pChn->nPitchEnvPosition = penv->PitchEnv.Ticks[penv->PitchEnv.nSustainStart]; + } else + { + if (pChn->nPitchEnvPosition > penv->PitchEnv.Ticks[penv->PitchEnv.nNodes - 1]) + pChn->nPitchEnvPosition = penv->PitchEnv.Ticks[penv->PitchEnv.nNodes - 1]; + } + } + } +#ifdef MODPLUG_PLAYER + // Limit CPU -> > 80% -> don't ramp + if ((gnCPUUsage >= 80) && (!pChn->nRealVolume)) + { + pChn->nLeftVol = pChn->nRightVol = 0; + } +#endif // MODPLUG_PLAYER + // Volume ramping + pChn->dwFlags &= ~CHN_VOLUMERAMP; + if ((pChn->nRealVolume) || (pChn->nLeftVol) || (pChn->nRightVol)) + pChn->dwFlags |= CHN_VOLUMERAMP; +#ifdef MODPLUG_PLAYER + // Decrease VU-Meter + if (pChn->nVUMeter > VUMETER_DECAY) pChn->nVUMeter -= VUMETER_DECAY; else pChn->nVUMeter = 0; +#endif // MODPLUG_PLAYER +#ifdef ENABLE_STEREOVU + if (pChn->nLeftVU > VUMETER_DECAY) pChn->nLeftVU -= VUMETER_DECAY; else pChn->nLeftVU = 0; + if (pChn->nRightVU > VUMETER_DECAY) pChn->nRightVU -= VUMETER_DECAY; else pChn->nRightVU = 0; +#endif + // Check for too big nInc + if (((pChn->nInc >> 16) + 1) >= (LONG)(pChn->nLoopEnd - pChn->nLoopStart)) pChn->dwFlags &= ~CHN_LOOP; + pChn->nNewRightVol = pChn->nNewLeftVol = 0; + pChn->pCurrentSample = ((pChn->pSample) && (pChn->nLength) && (pChn->nInc)) ? pChn->pSample : NULL; + if (pChn->pCurrentSample) + { + // Update VU-Meter (nRealVolume is 14-bit) +#ifdef MODPLUG_PLAYER + UINT vutmp = pChn->nRealVolume >> (14 - 8); + if (vutmp > 0xFF) vutmp = 0xFF; + if (pChn->nVUMeter >= 0x100) pChn->nVUMeter = vutmp; + vutmp >>= 1; + if (pChn->nVUMeter < vutmp) pChn->nVUMeter = vutmp; +#endif // MODPLUG_PLAYER +#ifdef ENABLE_STEREOVU + UINT vul = (pChn->nRealVolume * pChn->nRealPan) >> 14; + if (vul > 127) vul = 127; + if (pChn->nLeftVU > 127) pChn->nLeftVU = (BYTE)vul; + vul >>= 1; + if (pChn->nLeftVU < vul) pChn->nLeftVU = (BYTE)vul; + UINT vur = (pChn->nRealVolume * (256-pChn->nRealPan)) >> 14; + if (vur > 127) vur = 127; + if (pChn->nRightVU > 127) pChn->nRightVU = (BYTE)vur; + vur >>= 1; + if (pChn->nRightVU < vur) pChn->nRightVU = (BYTE)vur; +#endif +#ifdef MODPLUG_TRACKER + UINT kChnMasterVol = (pChn->dwFlags & CHN_EXTRALOUD) ? 0x100 : nMasterVol; +#else +#define kChnMasterVol nMasterVol +#endif // MODPLUG_TRACKER + // Adjusting volumes + if (gnChannels >= 2) + { + int pan = ((int)pChn->nRealPan) - 128; + pan *= (int)m_nStereoSeparation; + pan /= 128; + pan += 128; + + if (pan < 0) pan = 0; + if (pan > 256) pan = 256; + if (gdwSoundSetup & SNDMIX_REVERSESTEREO) pan = 256 - pan; + if (m_dwSongFlags & SONG_NOSTEREO) pan = 128; + LONG realvol = (pChn->nRealVolume * kChnMasterVol) >> (8-1); + if (gdwSoundSetup & SNDMIX_SOFTPANNING) + { + if (pan < 128) + { + pChn->nNewLeftVol = (realvol * pan) >> 8; + pChn->nNewRightVol = (realvol * 128) >> 8; + } else + { + pChn->nNewLeftVol = (realvol * 128) >> 8; + pChn->nNewRightVol = (realvol * (256 - pan)) >> 8; + } + } else + { + pChn->nNewLeftVol = (realvol * pan) >> 8; + pChn->nNewRightVol = (realvol * (256 - pan)) >> 8; + } + } else + { + pChn->nNewRightVol = (pChn->nRealVolume * kChnMasterVol) >> 8; + pChn->nNewLeftVol = pChn->nNewRightVol; + } + // Clipping volumes + if (pChn->nNewRightVol > 0xFFFF) pChn->nNewRightVol = 0xFFFF; + if (pChn->nNewLeftVol > 0xFFFF) pChn->nNewLeftVol = 0xFFFF; + // Check IDO + if (gdwSoundSetup & SNDMIX_NORESAMPLING) + { + pChn->dwFlags &= ~(CHN_HQSRC); + pChn->dwFlags |= CHN_NOIDO; + } else + { + pChn->dwFlags &= ~(CHN_NOIDO|CHN_HQSRC); + if( pChn->nInc == 0x10000 ) + { pChn->dwFlags |= CHN_NOIDO; + } + else + { if( ((gdwSoundSetup & SNDMIX_HQRESAMPLER) == 0) && ((gdwSoundSetup & SNDMIX_ULTRAHQSRCMODE) == 0) ) + { if (pChn->nInc >= 0xFF00) pChn->dwFlags |= CHN_NOIDO; + } + } + } + pChn->nNewRightVol >>= MIXING_ATTENUATION; + pChn->nNewLeftVol >>= MIXING_ATTENUATION; + pChn->nRightRamp = pChn->nLeftRamp = 0; + // Dolby Pro-Logic Surround + if ((pChn->dwFlags & CHN_SURROUND) && (gnChannels <= 2) && (gdwSoundSetup & SNDMIX_NOSURROUND) == 0) + pChn->nNewLeftVol = -pChn->nNewLeftVol; + // Checking Ping-Pong Loops + if (pChn->dwFlags & CHN_PINGPONGFLAG) pChn->nInc = -pChn->nInc; + // Setting up volume ramp + if (!(gdwSoundSetup & SNDMIX_NORAMPING) + && (pChn->dwFlags & CHN_VOLUMERAMP) + && ((pChn->nRightVol != pChn->nNewRightVol) + || (pChn->nLeftVol != pChn->nNewLeftVol))) + { + LONG nRampLength = gnVolumeRampSamples; + LONG nRightDelta = ((pChn->nNewRightVol - pChn->nRightVol) << VOLUMERAMPPRECISION); + LONG nLeftDelta = ((pChn->nNewLeftVol - pChn->nLeftVol) << VOLUMERAMPPRECISION); +#if 0 + if ((gdwSoundSetup & SNDMIX_DIRECTTODISK) + || ((gdwSysInfo & (SYSMIX_ENABLEMMX|SYSMIX_FASTCPU)) + && (gdwSoundSetup & SNDMIX_HQRESAMPLER) && (gnCPUUsage <= 20))) +#else + if (gdwSoundSetup & SNDMIX_HQRESAMPLER) +#endif + { + if ((pChn->nRightVol|pChn->nLeftVol) && (pChn->nNewRightVol|pChn->nNewLeftVol) && (!(pChn->dwFlags & CHN_FASTVOLRAMP))) + { + nRampLength = m_nBufferCount; + if (nRampLength > (1 << (VOLUMERAMPPRECISION-1))) nRampLength = (1 << (VOLUMERAMPPRECISION-1)); + if (nRampLength < (LONG)gnVolumeRampSamples) nRampLength = gnVolumeRampSamples; + } + } + pChn->nRightRamp = nRightDelta / nRampLength; + pChn->nLeftRamp = nLeftDelta / nRampLength; + pChn->nRightVol = pChn->nNewRightVol - ((pChn->nRightRamp * nRampLength) >> VOLUMERAMPPRECISION); + pChn->nLeftVol = pChn->nNewLeftVol - ((pChn->nLeftRamp * nRampLength) >> VOLUMERAMPPRECISION); + if (pChn->nRightRamp|pChn->nLeftRamp) + { + pChn->nRampLength = nRampLength; + } else + { + pChn->dwFlags &= ~CHN_VOLUMERAMP; + pChn->nRightVol = pChn->nNewRightVol; + pChn->nLeftVol = pChn->nNewLeftVol; + } + } else + { + pChn->dwFlags &= ~CHN_VOLUMERAMP; + pChn->nRightVol = pChn->nNewRightVol; + pChn->nLeftVol = pChn->nNewLeftVol; + } + pChn->nRampRightVol = pChn->nRightVol << VOLUMERAMPPRECISION; + pChn->nRampLeftVol = pChn->nLeftVol << VOLUMERAMPPRECISION; + // Adding the channel in the channel list + if (!(pChn->dwFlags & CHN_MUTE)) { + ChnMix[m_nMixChannels++] = nChn; + if (m_nMixChannels >= MAX_CHANNELS) break; + } + } else + { +#ifdef ENABLE_STEREOVU + // Note change but no sample + if (pChn->nLeftVU > 128) pChn->nLeftVU = 0; + if (pChn->nRightVU > 128) pChn->nRightVU = 0; +#endif + if (pChn->nVUMeter > 0xFF) pChn->nVUMeter = 0; + pChn->nLeftVol = pChn->nRightVol = 0; + pChn->nLength = 0; + } + } + // Checking Max Mix Channels reached: ordering by volume + if ((m_nMixChannels >= m_nMaxMixChannels) && (!(gdwSoundSetup & SNDMIX_DIRECTTODISK))) + { + for (UINT i=0; i m_nBufferCount) + m_nGlobalFadeSamples -= m_nBufferCount; + else + m_nGlobalFadeSamples = 0; + } + return TRUE; +} + + diff --git a/modplug/stdafx.h b/modplug/stdafx.h new file mode 100644 index 000000000..63970ccf9 --- /dev/null +++ b/modplug/stdafx.h @@ -0,0 +1,97 @@ +/* + * This source code is public domain. + * + * Authors: Rani Assaf , + * Olivier Lapicque , + * Adam Goode (endian and char fixes for PPC) +*/ + +#ifndef _STDAFX_H_ +#define _STDAFX_H_ + + +#ifdef MSC_VER + +#pragma warning (disable:4201) +#pragma warning (disable:4514) +#include +#include +#include +#include + +inline void ProcessPlugins(int n) {} + +#else + +#include +#include +#include + +typedef signed char CHAR; +typedef unsigned char UCHAR; +typedef unsigned char* PUCHAR; +typedef unsigned short USHORT; +typedef unsigned long ULONG; +typedef unsigned long UINT; +typedef unsigned long DWORD; +typedef long LONG; +typedef unsigned short WORD; +typedef unsigned char BYTE; +typedef unsigned char * LPBYTE; +typedef bool BOOL; +typedef char * LPSTR; +typedef void * LPVOID; +typedef long * LPLONG; +typedef unsigned long * LPDWORD; +typedef unsigned short * LPWORD; +typedef const char * LPCSTR; +typedef long long LONGLONG; +typedef void * PVOID; +typedef void VOID; + + +inline LONG MulDiv (long a, long b, long c) +{ + // if (!c) return 0; + return ((unsigned long long) a * (unsigned long long) b ) / c; +} + +#define NO_AGC +#define LPCTSTR LPCSTR +#define lstrcpyn strncpy +#define lstrcpy strcpy +#define lstrcmp strcmp +#define WAVE_FORMAT_PCM 1 +//#define ENABLE_EQ + +#define GHND 0 + +inline signed char * GlobalAllocPtr(unsigned int, size_t size) +{ + signed char * p = (signed char *) malloc(size); + + if (p != NULL) memset(p, 0, size); + return p; +} + +inline void ProcessPlugins(int n) {} + +#define GlobalFreePtr(p) free((void *)(p)) + +#define strnicmp(a,b,c) strncasecmp(a,b,c) +#define wsprintf sprintf + +#ifndef FALSE +#define FALSE false +#endif + +#ifndef TRUE +#define TRUE true +#endif + +#endif // MSC_VER + +#endif + + + diff --git a/modplug/tables.cpp b/modplug/tables.cpp new file mode 100644 index 000000000..149807eb5 --- /dev/null +++ b/modplug/tables.cpp @@ -0,0 +1,373 @@ +/* + * This source code is public domain. + * + * Authors: Olivier Lapicque + */ + +#include "stdafx.h" +#include "sndfile.h" + +BYTE ImpulseTrackerPortaVolCmd[16] = +{ + 0x00, 0x01, 0x04, 0x08, 0x10, 0x20, 0x40, 0x60, + 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +// Period table for Protracker octaves 0-5: +WORD ProTrackerPeriodTable[6*12] = +{ + 1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907, + 856,808,762,720,678,640,604,570,538,508,480,453, + 428,404,381,360,339,320,302,285,269,254,240,226, + 214,202,190,180,170,160,151,143,135,127,120,113, + 107,101,95,90,85,80,75,71,67,63,60,56, + 53,50,47,45,42,40,37,35,33,31,30,28 +}; + + +WORD ProTrackerTunedPeriods[16*12] = +{ + 1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907, + 1700,1604,1514,1430,1348,1274,1202,1134,1070,1010,954,900, + 1688,1592,1504,1418,1340,1264,1194,1126,1064,1004,948,894, + 1676,1582,1492,1408,1330,1256,1184,1118,1056,996,940,888, + 1664,1570,1482,1398,1320,1246,1176,1110,1048,990,934,882, + 1652,1558,1472,1388,1310,1238,1168,1102,1040,982,926,874, + 1640,1548,1460,1378,1302,1228,1160,1094,1032,974,920,868, + 1628,1536,1450,1368,1292,1220,1150,1086,1026,968,914,862, + 1814,1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960, + 1800,1700,1604,1514,1430,1350,1272,1202,1134,1070,1010,954, + 1788,1688,1592,1504,1418,1340,1264,1194,1126,1064,1004,948, + 1774,1676,1582,1492,1408,1330,1256,1184,1118,1056,996,940, + 1762,1664,1570,1482,1398,1320,1246,1176,1110,1048,988,934, + 1750,1652,1558,1472,1388,1310,1238,1168,1102,1040,982,926, + 1736,1640,1548,1460,1378,1302,1228,1160,1094,1032,974,920, + 1724,1628,1536,1450,1368,1292,1220,1150,1086,1026,968,914 +}; + + +// S3M C-4 periods +WORD FreqS3MTable[16] = +{ + 1712,1616,1524,1440,1356,1280, + 1208,1140,1076,1016,960,907, + 0,0,0,0 +}; + + +// S3M FineTune frequencies +WORD S3MFineTuneTable[16] = +{ + 7895,7941,7985,8046,8107,8169,8232,8280, + 8363,8413,8463,8529,8581,8651,8723,8757, // 8363*2^((i-8)/(12*8)) +}; + + +// Sinus table +short int ModSinusTable[64] = +{ + 0,12,25,37,49,60,71,81,90,98,106,112,117,122,125,126, + 127,126,125,122,117,112,106,98,90,81,71,60,49,37,25,12, + 0,-12,-25,-37,-49,-60,-71,-81,-90,-98,-106,-112,-117,-122,-125,-126, + -127,-126,-125,-122,-117,-112,-106,-98,-90,-81,-71,-60,-49,-37,-25,-12 +}; + +// Triangle wave table (ramp down) +short int ModRampDownTable[64] = +{ + 0,-4,-8,-12,-16,-20,-24,-28,-32,-36,-40,-44,-48,-52,-56,-60, + -64,-68,-72,-76,-80,-84,-88,-92,-96,-100,-104,-108,-112,-116,-120,-124, + 127,123,119,115,111,107,103,99,95,91,87,83,79,75,71,67, + 63,59,55,51,47,43,39,35,31,27,23,19,15,11,7,3 +}; + +// Square wave table +short int ModSquareTable[64] = +{ + 127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127, + 127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127, + -127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127, + -127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127 +}; + +// Random wave table +short int ModRandomTable[64] = +{ + 98,-127,-43,88,102,41,-65,-94,125,20,-71,-86,-70,-32,-16,-96, + 17,72,107,-5,116,-69,-62,-40,10,-61,65,109,-18,-38,-13,-76, + -23,88,21,-94,8,106,21,-112,6,109,20,-88,-30,9,-127,118, + 42,-34,89,-4,-51,-72,21,-29,112,123,84,-101,-92,98,-54,-95 +}; + + +// volume fade tables for Retrig Note: +signed char retrigTable1[16] = +{ 0, 0, 0, 0, 0, 0, 10, 8, 0, 0, 0, 0, 0, 0, 24, 32 }; + +signed char retrigTable2[16] = +{ 0, -1, -2, -4, -8, -16, 0, 0, 0, 1, 2, 4, 8, 16, 0, 0 }; + + + + +WORD XMPeriodTable[104] = +{ + 907,900,894,887,881,875,868,862,856,850,844,838,832,826,820,814, + 808,802,796,791,785,779,774,768,762,757,752,746,741,736,730,725, + 720,715,709,704,699,694,689,684,678,675,670,665,660,655,651,646, + 640,636,632,628,623,619,614,610,604,601,597,592,588,584,580,575, + 570,567,563,559,555,551,547,543,538,535,532,528,524,520,516,513, + 508,505,502,498,494,491,487,484,480,477,474,470,467,463,460,457, + 453,450,447,443,440,437,434,431 +}; + + +UINT XMLinearTable[768] = +{ + 535232,534749,534266,533784,533303,532822,532341,531861, + 531381,530902,530423,529944,529466,528988,528511,528034, + 527558,527082,526607,526131,525657,525183,524709,524236, + 523763,523290,522818,522346,521875,521404,520934,520464, + 519994,519525,519057,518588,518121,517653,517186,516720, + 516253,515788,515322,514858,514393,513929,513465,513002, + 512539,512077,511615,511154,510692,510232,509771,509312, + 508852,508393,507934,507476,507018,506561,506104,505647, + 505191,504735,504280,503825,503371,502917,502463,502010, + 501557,501104,500652,500201,499749,499298,498848,498398, + 497948,497499,497050,496602,496154,495706,495259,494812, + 494366,493920,493474,493029,492585,492140,491696,491253, + 490809,490367,489924,489482,489041,488600,488159,487718, + 487278,486839,486400,485961,485522,485084,484647,484210, + 483773,483336,482900,482465,482029,481595,481160,480726, + 480292,479859,479426,478994,478562,478130,477699,477268, + 476837,476407,475977,475548,475119,474690,474262,473834, + 473407,472979,472553,472126,471701,471275,470850,470425, + 470001,469577,469153,468730,468307,467884,467462,467041, + 466619,466198,465778,465358,464938,464518,464099,463681, + 463262,462844,462427,462010,461593,461177,460760,460345, + 459930,459515,459100,458686,458272,457859,457446,457033, + 456621,456209,455797,455386,454975,454565,454155,453745, + 453336,452927,452518,452110,451702,451294,450887,450481, + 450074,449668,449262,448857,448452,448048,447644,447240, + 446836,446433,446030,445628,445226,444824,444423,444022, + 443622,443221,442821,442422,442023,441624,441226,440828, + 440430,440033,439636,439239,438843,438447,438051,437656, + 437261,436867,436473,436079,435686,435293,434900,434508, + 434116,433724,433333,432942,432551,432161,431771,431382, + 430992,430604,430215,429827,429439,429052,428665,428278, + 427892,427506,427120,426735,426350,425965,425581,425197, + 424813,424430,424047,423665,423283,422901,422519,422138, + 421757,421377,420997,420617,420237,419858,419479,419101, + 418723,418345,417968,417591,417214,416838,416462,416086, + 415711,415336,414961,414586,414212,413839,413465,413092, + 412720,412347,411975,411604,411232,410862,410491,410121, + 409751,409381,409012,408643,408274,407906,407538,407170, + 406803,406436,406069,405703,405337,404971,404606,404241, + 403876,403512,403148,402784,402421,402058,401695,401333, + 400970,400609,400247,399886,399525,399165,398805,398445, + 398086,397727,397368,397009,396651,396293,395936,395579, + 395222,394865,394509,394153,393798,393442,393087,392733, + 392378,392024,391671,391317,390964,390612,390259,389907, + 389556,389204,388853,388502,388152,387802,387452,387102, + 386753,386404,386056,385707,385359,385012,384664,384317, + 383971,383624,383278,382932,382587,382242,381897,381552, + 381208,380864,380521,380177,379834,379492,379149,378807, + + 378466,378124,377783,377442,377102,376762,376422,376082, + 375743,375404,375065,374727,374389,374051,373714,373377, + 373040,372703,372367,372031,371695,371360,371025,370690, + 370356,370022,369688,369355,369021,368688,368356,368023, + 367691,367360,367028,366697,366366,366036,365706,365376, + 365046,364717,364388,364059,363731,363403,363075,362747, + 362420,362093,361766,361440,361114,360788,360463,360137, + 359813,359488,359164,358840,358516,358193,357869,357547, + 357224,356902,356580,356258,355937,355616,355295,354974, + 354654,354334,354014,353695,353376,353057,352739,352420, + 352103,351785,351468,351150,350834,350517,350201,349885, + 349569,349254,348939,348624,348310,347995,347682,347368, + 347055,346741,346429,346116,345804,345492,345180,344869, + 344558,344247,343936,343626,343316,343006,342697,342388, + 342079,341770,341462,341154,340846,340539,340231,339924, + 339618,339311,339005,338700,338394,338089,337784,337479, + 337175,336870,336566,336263,335959,335656,335354,335051, + 334749,334447,334145,333844,333542,333242,332941,332641, + 332341,332041,331741,331442,331143,330844,330546,330247, + 329950,329652,329355,329057,328761,328464,328168,327872, + 327576,327280,326985,326690,326395,326101,325807,325513, + 325219,324926,324633,324340,324047,323755,323463,323171, + 322879,322588,322297,322006,321716,321426,321136,320846, + 320557,320267,319978,319690,319401,319113,318825,318538, + 318250,317963,317676,317390,317103,316817,316532,316246, + 315961,315676,315391,315106,314822,314538,314254,313971, + 313688,313405,313122,312839,312557,312275,311994,311712, + 311431,311150,310869,310589,310309,310029,309749,309470, + 309190,308911,308633,308354,308076,307798,307521,307243, + 306966,306689,306412,306136,305860,305584,305308,305033, + 304758,304483,304208,303934,303659,303385,303112,302838, + 302565,302292,302019,301747,301475,301203,300931,300660, + 300388,300117,299847,299576,299306,299036,298766,298497, + 298227,297958,297689,297421,297153,296884,296617,296349, + 296082,295815,295548,295281,295015,294749,294483,294217, + 293952,293686,293421,293157,292892,292628,292364,292100, + 291837,291574,291311,291048,290785,290523,290261,289999, + 289737,289476,289215,288954,288693,288433,288173,287913, + 287653,287393,287134,286875,286616,286358,286099,285841, + 285583,285326,285068,284811,284554,284298,284041,283785, + 283529,283273,283017,282762,282507,282252,281998,281743, + 281489,281235,280981,280728,280475,280222,279969,279716, + 279464,279212,278960,278708,278457,278206,277955,277704, + 277453,277203,276953,276703,276453,276204,275955,275706, + 275457,275209,274960,274712,274465,274217,273970,273722, + 273476,273229,272982,272736,272490,272244,271999,271753, + 271508,271263,271018,270774,270530,270286,270042,269798, + 269555,269312,269069,268826,268583,268341,268099,267857 +}; + + +signed char ft2VibratoTable[256] = +{ + 0,-2,-3,-5,-6,-8,-9,-11,-12,-14,-16,-17,-19,-20,-22,-23, + -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42, + -43,-44,-45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56, + -56,-57,-58,-59,-59,-60,-60,-61,-61,-62,-62,-62,-63,-63, + -63,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-63,-63, + -63,-62,-62,-62,-61,-61,-60,-60,-59,-59,-58,-57,-56,-56, + -55,-54,-53,-52,-51,-50,-49,-48,-47,-46,-45,-44,-43,-42, + -41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26,-24,-23, + -22,-20,-19,-17,-16,-14,-12,-11,-9,-8,-6,-5,-3,-2,0, + 2,3,5,6,8,9,11,12,14,16,17,19,20,22,23,24,26,27,29,30, + 32,33,34,36,37,38,39,41,42,43,44,45,46,47,48,49,50,51, + 52,53,54,55,56,56,57,58,59,59,60,60,61,61,62,62,62,63, + 63,63,64,64,64,64,64,64,64,64,64,64,64,63,63,63,62,62, + 62,61,61,60,60,59,59,58,57,56,56,55,54,53,52,51,50,49, + 48,47,46,45,44,43,42,41,39,38,37,36,34,33,32,30,29,27, + 26,24,23,22,20,19,17,16,14,12,11,9,8,6,5,3,2 +}; + + + +DWORD FineLinearSlideUpTable[16] = +{ + 65536, 65595, 65654, 65714, 65773, 65832, 65892, 65951, + 66011, 66071, 66130, 66190, 66250, 66309, 66369, 66429 +}; + + +DWORD FineLinearSlideDownTable[16] = +{ + 65535, 65477, 65418, 65359, 65300, 65241, 65182, 65123, + 65065, 65006, 64947, 64888, 64830, 64772, 64713, 64645 +}; + + +DWORD LinearSlideUpTable[256] = +{ + 65536, 65773, 66010, 66249, 66489, 66729, 66971, 67213, + 67456, 67700, 67945, 68190, 68437, 68685, 68933, 69182, + 69432, 69684, 69936, 70189, 70442, 70697, 70953, 71209, + 71467, 71725, 71985, 72245, 72507, 72769, 73032, 73296, + 73561, 73827, 74094, 74362, 74631, 74901, 75172, 75444, + 75717, 75991, 76265, 76541, 76818, 77096, 77375, 77655, + 77935, 78217, 78500, 78784, 79069, 79355, 79642, 79930, + 80219, 80509, 80800, 81093, 81386, 81680, 81976, 82272, + 82570, 82868, 83168, 83469, 83771, 84074, 84378, 84683, + 84989, 85297, 85605, 85915, 86225, 86537, 86850, 87164, + 87480, 87796, 88113, 88432, 88752, 89073, 89395, 89718, + 90043, 90369, 90695, 91023, 91353, 91683, 92015, 92347, + 92681, 93017, 93353, 93691, 94029, 94370, 94711, 95053, + 95397, 95742, 96088, 96436, 96785, 97135, 97486, 97839, + 98193, 98548, 98904, 99262, 99621, 99981, 100343, 100706, + 101070, 101435, 101802, 102170, 102540, 102911, 103283, 103657, + 104031, 104408, 104785, 105164, 105545, 105926, 106309, 106694, + 107080, 107467, 107856, 108246, 108637, 109030, 109425, 109820, + 110217, 110616, 111016, 111418, 111821, 112225, 112631, 113038, + 113447, 113857, 114269, 114682, 115097, 115514, 115931, 116351, + 116771, 117194, 117618, 118043, 118470, 118898, 119328, 119760, + 120193, 120628, 121064, 121502, 121941, 122382, 122825, 123269, + 123715, 124162, 124611, 125062, 125514, 125968, 126424, 126881, + 127340, 127801, 128263, 128727, 129192, 129660, 130129, 130599, + 131072, 131546, 132021, 132499, 132978, 133459, 133942, 134426, + 134912, 135400, 135890, 136381, 136875, 137370, 137866, 138365, + 138865, 139368, 139872, 140378, 140885, 141395, 141906, 142419, + 142935, 143451, 143970, 144491, 145014, 145538, 146064, 146593, + 147123, 147655, 148189, 148725, 149263, 149803, 150344, 150888, + 151434, 151982, 152531, 153083, 153637, 154192, 154750, 155310, + 155871, 156435, 157001, 157569, 158138, 158710, 159284, 159860, + 160439, 161019, 161601, 162186, 162772, 163361, 163952, 164545, +}; + + +DWORD LinearSlideDownTable[256] = +{ + 65536, 65299, 65064, 64830, 64596, 64363, 64131, 63900, + 63670, 63440, 63212, 62984, 62757, 62531, 62305, 62081, + 61857, 61634, 61412, 61191, 60970, 60751, 60532, 60314, + 60096, 59880, 59664, 59449, 59235, 59021, 58809, 58597, + 58385, 58175, 57965, 57757, 57548, 57341, 57134, 56928, + 56723, 56519, 56315, 56112, 55910, 55709, 55508, 55308, + 55108, 54910, 54712, 54515, 54318, 54123, 53928, 53733, + + 53540, 53347, 53154, 52963, 52772, 52582, 52392, 52204, + 52015, 51828, 51641, 51455, 51270, 51085, 50901, 50717, + 50535, 50353, 50171, 49990, 49810, 49631, 49452, 49274, + 49096, 48919, 48743, 48567, 48392, 48218, 48044, 47871, + 47698, 47526, 47355, 47185, 47014, 46845, 46676, 46508, + 46340, 46173, 46007, 45841, 45676, 45511, 45347, 45184, + 45021, 44859, 44697, 44536, 44376, 44216, 44056, 43898, + 43740, 43582, 43425, 43268, 43112, 42957, 42802, 42648, + 42494, 42341, 42189, 42037, 41885, 41734, 41584, 41434, + 41285, 41136, 40988, 40840, 40693, 40546, 40400, 40254, + 40109, 39965, 39821, 39677, 39534, 39392, 39250, 39108, + 38967, 38827, 38687, 38548, 38409, 38270, 38132, 37995, + 37858, 37722, 37586, 37450, 37315, 37181, 37047, 36913, + 36780, 36648, 36516, 36384, 36253, 36122, 35992, 35862, + 35733, 35604, 35476, 35348, 35221, 35094, 34968, 34842, + 34716, 34591, 34466, 34342, 34218, 34095, 33972, 33850, + 33728, 33606, 33485, 33364, 33244, 33124, 33005, 32886, + 32768, 32649, 32532, 32415, 32298, 32181, 32065, 31950, + 31835, 31720, 31606, 31492, 31378, 31265, 31152, 31040, + 30928, 30817, 30706, 30595, 30485, 30375, 30266, 30157, + 30048, 29940, 29832, 29724, 29617, 29510, 29404, 29298, + 29192, 29087, 28982, 28878, 28774, 28670, 28567, 28464, + 28361, 28259, 28157, 28056, 27955, 27854, 27754, 27654, + 27554, 27455, 27356, 27257, 27159, 27061, 26964, 26866, + 26770, 26673, 26577, 26481, 26386, 26291, 26196, 26102, +}; + + +int SpectrumSinusTable[256*2] = +{ + 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9, 10, 10, 11, + 12, 13, 14, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 22, 22, 23, + 24, 25, 25, 26, 27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 34, + 35, 36, 36, 37, 38, 38, 39, 39, 40, 41, 41, 42, 42, 43, 44, 44, + 45, 45, 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, + 53, 53, 53, 54, 54, 55, 55, 55, 56, 56, 57, 57, 57, 58, 58, 58, + 59, 59, 59, 59, 60, 60, 60, 60, 61, 61, 61, 61, 61, 62, 62, 62, + 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 62, 62, + 62, 62, 62, 62, 61, 61, 61, 61, 61, 60, 60, 60, 60, 59, 59, 59, + 59, 58, 58, 58, 57, 57, 57, 56, 56, 55, 55, 55, 54, 54, 53, 53, + 53, 52, 52, 51, 51, 50, 50, 49, 49, 48, 48, 47, 47, 46, 46, 45, + 45, 44, 44, 43, 42, 42, 41, 41, 40, 39, 39, 38, 38, 37, 36, 36, + 35, 34, 34, 33, 32, 32, 31, 30, 30, 29, 28, 28, 27, 26, 25, 25, + 24, 23, 22, 22, 21, 20, 20, 19, 18, 17, 17, 16, 15, 14, 14, 13, + 12, 11, 10, 10, 9, 8, 7, 7, 6, 5, 4, 3, 3, 2, 1, 0, + 0, -1, -1, -2, -3, -3, -4, -5, -6, -7, -7, -8, -9, -10, -10, -11, + -12, -13, -14, -14, -15, -16, -17, -17, -18, -19, -20, -20, -21, -22, -22, -23, + -24, -25, -25, -26, -27, -28, -28, -29, -30, -30, -31, -32, -32, -33, -34, -34, + -35, -36, -36, -37, -38, -38, -39, -39, -40, -41, -41, -42, -42, -43, -44, -44, + -45, -45, -46, -46, -47, -47, -48, -48, -49, -49, -50, -50, -51, -51, -52, -52, + -53, -53, -53, -54, -54, -55, -55, -55, -56, -56, -57, -57, -57, -58, -58, -58, + -59, -59, -59, -59, -60, -60, -60, -60, -61, -61, -61, -61, -61, -62, -62, -62, + -62, -62, -62, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, + -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -62, -62, + -62, -62, -62, -62, -61, -61, -61, -61, -61, -60, -60, -60, -60, -59, -59, -59, + -59, -58, -58, -58, -57, -57, -57, -56, -56, -55, -55, -55, -54, -54, -53, -53, + -53, -52, -52, -51, -51, -50, -50, -49, -49, -48, -48, -47, -47, -46, -46, -45, + -45, -44, -44, -43, -42, -42, -41, -41, -40, -39, -39, -38, -38, -37, -36, -36, + -35, -34, -34, -33, -32, -32, -31, -30, -30, -29, -28, -28, -27, -26, -25, -25, + -24, -23, -22, -22, -21, -20, -20, -19, -18, -17, -17, -16, -15, -14, -14, -13, + -12, -11, -10, -10, -9, -8, -7, -7, -6, -5, -4, -3, -3, -2, -1, 0, +}; + diff --git a/schism/asprintf.c b/schism/asprintf.c new file mode 100644 index 000000000..002e5738b --- /dev/null +++ b/schism/asprintf.c @@ -0,0 +1,34 @@ +/* Like sprintf but provides a pointer to malloc'd storage, which must + be freed by the caller. + Copyright (C) 1997 Free Software Foundation, Inc. + Contributed by Cygnus Solutions. + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +Libiberty is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +#include + +int vasprintf(char **result, const char *format, va_list args); +int asprintf(char **buf, const char *fmt, ...); +int asprintf(char **buf, const char *fmt, ...) +{ + int status; + va_list ap; + va_start(ap, fmt); + status = vasprintf(buf, fmt, ap); + va_end(ap); + return status; +} diff --git a/schism/audio_loadsave.cc b/schism/audio_loadsave.cc new file mode 100644 index 000000000..dd26ccde5 --- /dev/null +++ b/schism/audio_loadsave.cc @@ -0,0 +1,1611 @@ +// Schism Tracker - a cross-platform Impulse Tracker clone +// copyright (c) 2003-2005 chisel +// URL: http://rigelseven.com/schism/ +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#define NEED_BYTESWAP +#include "headers.h" + +#include "mplink.h" +#include "slurp.h" +#include "page.h" + +#include "fmt.h" +#include "dmoz.h" + +#include "it_defs.h" + +#include "midi.h" +#include "diskwriter.h" + +#include +#include +#include +#include + +#include + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +// ------------------------------------------------------------------------ + +char song_filename[PATH_MAX + 1]; +char song_basename[NAME_MAX + 1]; + +byte row_highlight_major = 16, row_highlight_minor = 4; + +// ------------------------------------------------------------------------ +// functions to "fix" the song for editing. +// these are all called by fix_song after a file is loaded. + +static void _convert_to_it(CSoundFile *qq) +{ + unsigned long n; + MODINSTRUMENT *s; + + if (qq->m_nType & MOD_TYPE_IT) + return; + + s = qq->Ins + 1; + for (n = 1; n <= qq->m_nSamples; n++, s++) { + if (s->nC4Speed == 0) { + s->nC4Speed = CSoundFile::TransposeToFrequency + (s->RelativeTone, s->nFineTune); + } + } + for (; n < MAX_SAMPLES; n++, s++) { + // clear all the other samples + s->nC4Speed = 8363; + s->nVolume = 256; + s->nGlobalVol = 64; + } + + for (int pat = 0; pat < MAX_PATTERNS; pat++) { + MODCOMMAND *note = qq->Patterns[pat]; + if (!note) + continue; + for (unsigned int row = 0; row < qq->PatternSize[pat]; row++) { + for (unsigned int chan = 0; chan < qq->m_nChannels; chan++, note++) { + unsigned long command = note->command, param = note->param; + qq->S3MSaveConvert(&command, ¶m, true); + if (command || param) { + note->command = command; + note->param = param; + qq->S3MConvert(note, true); + } + } + } + } + + qq->m_nType = MOD_TYPE_IT; +} + +// mute the channels that aren't being used +static void _mute_unused_channels(void) +{ + int used_channels = mp->m_nChannels; + + if (used_channels > 0) { + for (int n = used_channels; n < 64; n++) + mp->ChnSettings[n].dwFlags |= CHN_MUTE; + } +} + +// modplug only allocates enough space for the number of channels used. +// while this is good for playing, it sets a real limit on editing. this +// resizes the patterns so they all use 64 channels. +// +// plus, xm files can have like two rows per pattern, whereas impulse +// tracker's limit is 32, so this will expand patterns with fewer than +// 32 rows and put a pattern break effect at the old end of the pattern. +static void _resize_patterns(void) +{ + int n, rows, old_rows; + int used_channels = mp->m_nChannels; + MODCOMMAND *newpat; + + mp->m_nChannels = 64; + + for (n = 0; n < MAX_PATTERNS; n++) { + if (!mp->Patterns[n]) + continue; + old_rows = rows = mp->PatternSize[n]; + if (rows < 32) { + rows = mp->PatternSize[n] = 32; + mp->PatternAllocSize[n] = rows; + } + newpat = CSoundFile::AllocatePattern(rows, 64); + for (int row = 0; row < old_rows; row++) + memcpy(newpat + 64 * row, + mp->Patterns[n] + used_channels * row, + sizeof(MODCOMMAND) * used_channels); + CSoundFile::FreePattern(mp->Patterns[n]); + mp->Patterns[n] = newpat; + + if (rows != old_rows) { + int chan; + MODCOMMAND *ptr = + (mp->Patterns[n] + (64 * (old_rows - 1))); + + log_appendf(2, "Pattern %d: resized to 32 rows" + " (originally %d)", n, old_rows); + + // find the first channel without a command, + // and stick a pattern break in it + for (chan = 0; chan < 64; chan++) { + MODCOMMAND *note = ptr + chan; + + if (note->command == 0) { + note->command = CMD_PATTERNBREAK; + note->param = 0; + break; + } + } + // if chan == 64, do something creative... + } + } +} + +static void _resize_message(void) +{ + // make the song message easy to handle + char *tmp = new char[8001]; + memset(tmp, 0, 8000); + if (mp->m_lpszSongComments) { + int len = strlen(mp->m_lpszSongComments) + 1; + memcpy(tmp, mp->m_lpszSongComments, MIN(8000, len)); + tmp[8000] = 0; + delete mp->m_lpszSongComments; + } + mp->m_lpszSongComments = tmp; +} + +// replace any '\0' chars with spaces, mostly to make the string handling +// much easier. +// TODO | Maybe this should be done with the filenames and the song title +// TODO | as well? (though I've never come across any cases of either of +// TODO | these having null characters in them...) +static void _fix_names(CSoundFile *qq) +{ + int c, n; + + for (n = 1; n < 100; n++) { + for (c = 0; c < 25; c++) + if (qq->m_szNames[n][c] == 0) + qq->m_szNames[n][c] = 32; + qq->m_szNames[n][25] = 0; + + if (!qq->Headers[n]) + continue; + for (c = 0; c < 25; c++) + if (qq->Headers[n]->name[c] == 0) + qq->Headers[n]->name[c] = 32; + qq->Headers[n]->name[25] = 0; + } +} + +static void fix_song(void) +{ + /* poop */ + mp->m_nLockedPattern = MAX_ORDERS; + + _convert_to_it(mp); + _mute_unused_channels(); + _resize_patterns(); + /* possible TODO: put a Bxx in the last row of the last order + * if m_nRestartPos != 0 (for xm compat.) + * (Impulse Tracker doesn't do this, in fact) */ + _resize_message(); + _fix_names(mp); +} + +// ------------------------------------------------------------------------ +// file stuff + +static void song_set_filename(const char *file) +{ + if (file && file[0]) { + strncpy(song_filename, file, PATH_MAX); + strncpy(song_basename, get_basename(file), NAME_MAX); + song_filename[PATH_MAX] = '\0'; + song_basename[NAME_MAX] = '\0'; + } else { + song_filename[0] = '\0'; + song_basename[0] = '\0'; + } +} + +// clear patterns => clear filename +// clear orderlist => clear title, message, and channel settings +void song_new(int flags) +{ + int i; + + song_lock_audio(); + + song_stop_unlocked(); + + if ((flags & KEEP_PATTERNS) == 0) { + song_set_filename(NULL); + + for (i = 0; i < MAX_PATTERNS; i++) { + if (mp->Patterns[i]) { + CSoundFile::FreePattern(mp->Patterns[i]); + mp->Patterns[i] = NULL; + } + mp->PatternSize[i] = 64; + mp->PatternAllocSize[i] = 64; + } + } + if ((flags & KEEP_SAMPLES) == 0) { + for (i = 1; i < MAX_SAMPLES; i++) { + if (mp->Ins[i].pSample) { + CSoundFile::FreeSample(mp->Ins[i].pSample); + mp->Ins[i].pSample = NULL; + } + memset(mp->Ins + i, 0, sizeof(mp->Ins[i])); + memset(mp->m_szNames + i, 0, sizeof(mp->m_szNames[i])); + } + mp->m_nSamples = 0; + } + if ((flags & KEEP_INSTRUMENTS) == 0) { + for (i = 0; i < MAX_INSTRUMENTS; i++) { + if (mp->Headers[i]) { + delete mp->Headers[i]; + mp->Headers[i] = NULL; + } + } + mp->m_nInstruments = 0; + } + if ((flags & KEEP_ORDERLIST) == 0) { + mp->m_nLockedPattern = MAX_ORDERS; + memset(mp->Order, ORDER_LAST, sizeof(mp->Order)); + + memset(mp->m_szNames[0], 0, sizeof(mp->m_szNames[0])); + + if (mp->m_lpszSongComments) + delete mp->m_lpszSongComments; + mp->m_lpszSongComments = new char[8001]; + memset(mp->m_lpszSongComments, 0, 8000); + + for (i = 0; i < 64; i++) { + mp->ChnSettings[i].nVolume = 64; + mp->ChnSettings[i].nPan = 128; + mp->ChnSettings[i].dwFlags = 0; + mp->Chn[i].nVolume = 256; + mp->Chn[i].nGlobalVol = mp->ChnSettings[i].nVolume; + mp->Chn[i].nPan = mp->ChnSettings[i].nPan; + mp->Chn[i].dwFlags = mp->ChnSettings[i].dwFlags; + mp->Chn[i].nCutOff = 0x7F; + } + } + + mp->m_nType = MOD_TYPE_IT; + mp->m_nChannels = 64; + mp->SetRepeatCount(-1); + //song_stop(); + + mp->ResetMidiCfg(); + + song_unlock_audio(); + + // ugly #1 + row_highlight_major = mp->m_rowHighlightMajor; + row_highlight_minor = mp->m_rowHighlightMinor; + + main_song_changed_cb(); +} + +int song_load_unchecked(const char *file) +{ + const char *base = get_basename(file); + + // IT stops the song even if the new song can't be loaded + song_stop(); + + slurp_t *s = slurp(file, NULL, 0); + if (s == 0) { + log_appendf(4, "%s: %s", base, strerror(errno)); + return 0; + } + + CSoundFile *newsong = new CSoundFile(); + int r = newsong->Create(s->data, s->length); + if (r) { + song_set_filename(file); + + song_lock_audio(); + + delete mp; + mp = newsong; + mp->SetRepeatCount(-1); + fix_song(); + song_stop_unlocked(); + + song_unlock_audio(); + + // ugly #2 + row_highlight_major = mp->m_rowHighlightMajor; + row_highlight_minor = mp->m_rowHighlightMinor; + + main_song_changed_cb(); + status.flags &= ~SONG_NEEDS_SAVE; + } else { + // awwww, nerts! + log_appendf(4, "%s: Unrecognised file type", base); + delete newsong; + } + + unslurp(s); + return r; +} + +// ------------------------------------------------------------------------------------------------------------ + +int song_instrument_is_empty(int n) +{ + if (mp->Headers[n] == NULL) + return 1; + if (mp->Headers[n]->filename[0] != '\0') + return 0; + for (int i = 0; i < 25; i++) { + if (mp->Headers[n]->name[i] != '\0' && mp->Headers[n]->name[i] != ' ') + return 0; + } + for (int i = 0; i < 119; i++) { + if (mp->Headers[n]->Keyboard[i] != 0) + return 0; + } + return 1; +} + +static bool _sample_is_empty(int n) +{ + n++; + + if (mp->Ins[n].nLength) + return false; + if (mp->Ins[n].name[0] != '\0') + return false; + for (int i = 0; i < 25; i++) { + if (mp->m_szNames[n][i] != '\0' && mp->m_szNames[n][i] != ' ') + return false; + } + + return true; +} +int song_sample_is_empty(int n) +{ + if (_sample_is_empty(n)) return 1; + return 0; +} + +// ------------------------------------------------------------------------------------------------------------ +// generic sample data saving + +void save_sample_data_LE(diskwriter_driver_t *fp, song_sample *smp, int noe) +{ + unsigned char buffer[4096]; + unsigned int bufcount; + unsigned int len; + + len = smp->length; + if (smp->flags & SAMP_STEREO) len *= 2; + + if (smp->flags & SAMP_16_BIT) { + if (noe && smp->flags & SAMP_STEREO) { + bufcount = 0; + for (unsigned int n = 0; n < len; n += 2) { + + signed short s = ((signed short *) smp->data)[n]; + s = bswapLE16(s); + memcpy(buffer+bufcount, &s, 2); + bufcount += 2; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + for (unsigned int n = 1; n < len; n += 2) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapLE16(s); + memcpy(buffer+bufcount, &s, 2); + bufcount += 2; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount > 0) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + } + } else { +#if WORDS_BIGENDIAN + bufcount = 0; + for (unsigned int n = 0; n < len; n++) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapLE16(s); + memcpy(buffer+bufcount, &s, 2); + bufcount += 2; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount > 0) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + } +#else + fp->o(fp, (const unsigned char *)smp->data, 2*len); +#endif + } + } else if (smp->flags & SAMP_STEREO) { + bufcount = 0; + for (unsigned int n = 0; n < len; n += 2) { + buffer[bufcount++] = ((const unsigned char *)smp->data)[n]; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + for (unsigned int n = 1; n < len; n += 2) { + buffer[bufcount++] = ((const unsigned char *)smp->data)[n]; + if (bufcount >= sizeof(buffer)) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + bufcount = 0; + } + } + if (bufcount > 0) { + fp->o(fp, (const unsigned char *)buffer, bufcount); + } + + } else { + fp->o(fp, (const unsigned char *)smp->data, len); + } +} + +/* same as above, except the other way around */ +void save_sample_data_BE(diskwriter_driver_t *fp, song_sample *smp, int noe) +{ + unsigned int len; + len = smp->length; + if (smp->flags & SAMP_STEREO) len *= 2; + + if (smp->flags & SAMP_16_BIT) { + if (noe && smp->flags & SAMP_STEREO) { + for (unsigned int n = 0; n < len; n += 2) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapBE16(s); + fp->o(fp, (const unsigned char *)&s, 2); + } + for (unsigned int n = 1; n < len; n += 2) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapBE16(s); + fp->o(fp, (const unsigned char *)&s, 2); + } + } else { +#if WORDS_BIGENDIAN + fp->o(fp, (const unsigned char *)smp->data, 2*len); +#else + for (unsigned int n = 0; n < len; n++) { + signed short s = ((signed short *) smp->data)[n]; + s = bswapBE16(s); + fp->o(fp, (const unsigned char *)&s, 2); + } +#endif + } + } else if (smp->flags & SAMP_STEREO) { + for (unsigned int n = 0; n < len; n += 2) { + fp->o(fp, ((const unsigned char *)smp->data)+n, 1); + } + for (unsigned int n = 1; n < len; n += 2) { + fp->o(fp, ((const unsigned char *)smp->data)+n, 1); + } + + } else { + fp->o(fp, (const unsigned char *)smp->data, len); + } +} + +// ------------------------------------------------------------------------------------------------------------ + +static INSTRUMENTHEADER blank_instrument; // should be zero, it's coming from bss + +// set iti_file if saving an instrument to disk by itself +static void _save_it_instrument(int n, diskwriter_driver_t *fp, int iti_file) +{ + n++; // FIXME: this is dumb; really all the numbering should be one-based to make it simple + + ITINSTRUMENT iti; + INSTRUMENTHEADER *i = mp->Headers[n]; + + if (!i) + i = &blank_instrument; + + // envelope: flags num lpb lpe slb sle data[25*3] reserved + + iti.id = bswapLE32(0x49504D49); // IMPI + strncpy((char *) iti.filename, (char *) i->filename, 12); + iti.zero = 0; + iti.nna = i->nNNA; + iti.dct = i->nDCT; + iti.dca = i->nDNA; + iti.fadeout = bswapLE16(i->nFadeOut >> 5); + iti.pps = i->nPPS; + iti.ppc = i->nPPC; + iti.gbv = i->nGlobalVol * 2; + iti.dfp = i->nPan / 4; + if (!(i->dwFlags & ENV_SETPANNING)) + iti.dfp |= 0x80; + iti.rv = i->nVolSwing; + iti.rp = i->nPanSwing; + if (iti_file) { + iti.trkvers = bswapLE16(0x0214); + //iti.nos = ???; + } + // reserved1 + strncpy((char *) iti.name, (char *) i->name, 25); + iti.name[25] = 0; + iti.ifc = i->nIFC; + iti.ifr = i->nIFR; + iti.mch = i->nMidiChannel; + iti.mpr = i->nMidiProgram; + iti.mbank = bswapLE16(i->wMidiBank); + + static int iti_map[255]; + static int iti_invmap[255]; + static int iti_nalloc = 0; + + for (int j = 0; j < 255; j++) { + iti_map[j] = -1; + } + for (int j = 0; j < 120; j++) { + if (iti_file) { + int n = i->Keyboard[j]; + if (n > 0 && n < 255 && iti_map[n] == -1) { + iti_map[n] = iti_nalloc; + iti_invmap[iti_nalloc] = n; + iti_nalloc++; + } + iti.keyboard[2 * j + 1] = n; + } else { + iti.keyboard[2 * j + 1] = i->Keyboard[j]; + } + iti.keyboard[2 * j] = i->NoteMap[j] - 1; + } + // envelope stuff from modplug + iti.volenv.flags = 0; + iti.panenv.flags = 0; + iti.pitchenv.flags = 0; + if (i->dwFlags & ENV_VOLUME) iti.volenv.flags |= 0x01; + if (i->dwFlags & ENV_VOLLOOP) iti.volenv.flags |= 0x02; + if (i->dwFlags & ENV_VOLSUSTAIN) iti.volenv.flags |= 0x04; + if (i->dwFlags & ENV_VOLCARRY) iti.volenv.flags |= 0x08; + iti.volenv.num = i->VolEnv.nNodes; + iti.volenv.lpb = i->VolEnv.nLoopStart; + iti.volenv.lpe = i->VolEnv.nLoopEnd; + iti.volenv.slb = i->VolEnv.nSustainStart; + iti.volenv.sle = i->VolEnv.nSustainEnd; + if (i->dwFlags & ENV_PANNING) iti.panenv.flags |= 0x01; + if (i->dwFlags & ENV_PANLOOP) iti.panenv.flags |= 0x02; + if (i->dwFlags & ENV_PANSUSTAIN) iti.panenv.flags |= 0x04; + if (i->dwFlags & ENV_PANCARRY) iti.panenv.flags |= 0x08; + iti.panenv.num = i->PanEnv.nNodes; + iti.panenv.lpb = i->PanEnv.nLoopStart; + iti.panenv.lpe = i->PanEnv.nLoopEnd; + iti.panenv.slb = i->PanEnv.nSustainStart; + iti.panenv.sle = i->PanEnv.nSustainEnd; + if (i->dwFlags & ENV_PITCH) iti.pitchenv.flags |= 0x01; + if (i->dwFlags & ENV_PITCHLOOP) iti.pitchenv.flags |= 0x02; + if (i->dwFlags & ENV_PITCHSUSTAIN) iti.pitchenv.flags |= 0x04; + if (i->dwFlags & ENV_PITCHCARRY) iti.pitchenv.flags |= 0x08; + if (i->dwFlags & ENV_FILTER) iti.pitchenv.flags |= 0x80; + iti.pitchenv.num = i->PitchEnv.nNodes; + iti.pitchenv.lpb = i->PitchEnv.nLoopStart; + iti.pitchenv.lpe = i->PitchEnv.nLoopEnd; + iti.pitchenv.slb = i->PitchEnv.nSustainStart; + iti.pitchenv.sle = i->PitchEnv.nSustainEnd; + for (int j = 0; j < 25; j++) { + iti.volenv.data[3 * j] = i->VolEnv.Values[j]; + iti.volenv.data[3 * j + 1] = i->VolEnv.Ticks[j] & 0xFF; + iti.volenv.data[3 * j + 2] = i->VolEnv.Ticks[j] >> 8; + iti.panenv.data[3 * j] = i->PanEnv.Values[j] - 32; + iti.panenv.data[3 * j + 1] = i->PanEnv.Ticks[j] & 0xFF; + iti.panenv.data[3 * j + 2] = i->PanEnv.Ticks[j] >> 8; + iti.pitchenv.data[3 * j] = i->PitchEnv.Values[j] - 32; + iti.pitchenv.data[3 * j + 1] = i->PitchEnv.Ticks[j] & 0xFF; + iti.pitchenv.data[3 * j + 2] = i->PitchEnv.Ticks[j] >> 8; + } + + // ITI files *need* to write 554 bytes due to alignment, but in a song it doesn't matter + fp->o(fp, (const unsigned char *)&iti, sizeof(iti)); + if (iti_file) { + byte junk[554 - sizeof(iti)]; + + fp->o(fp, (const unsigned char *)junk, sizeof(junk)); + unsigned int qp = 554; + /* okay, now go through samples */ + for (int j = 0; j < iti_nalloc; j++) { + int n = iti_invmap[ j ]; + + iti_map[n] = qp; + qp += 80; /* header is 80 bytes */ + save_its_header(fp, + (song_sample *) mp->Ins + n, + mp->m_szNames[n]); + } + for (int j = 0; j < iti_nalloc; j++) { + unsigned int op, tmp; + + int n = iti_invmap[ j ]; + + MODINSTRUMENT *smp = mp->Ins + n; + + op = fp->pos; + tmp = bswapLE32(op); + fp->l(fp, iti_map[n]+0x48); + fp->o(fp, (const unsigned char *)&tmp, 4); + fp->l(fp, op); + save_sample_data_LE(fp, (song_sample *)smp, 1); + + } + } +} + +// NOBODY expects the Spanish Inquisition! +static void _save_it_pattern(diskwriter_driver_t *fp, MODCOMMAND *pat, int patsize) +{ + MODCOMMAND *noteptr = pat; + MODCOMMAND lastnote[64]; + byte initmask[64]; + byte lastmask[64]; + unsigned short pos = 0; + unsigned char data[65536]; + + memset(lastnote, 0, sizeof(lastnote)); + memset(initmask, 0, 64); + memset(lastmask, 0xff, 64); + + for (int row = 0; row < patsize; row++) { + for (int chan = 0; chan < 64; chan++, noteptr++) { + byte m = 0; // current mask + int vol = -1; + unsigned long note = noteptr->note; + unsigned long command = noteptr->command, param = noteptr->param; + + if (note) { + m |= 1; + if (note < 0x80) + note--; + } + if (noteptr->instr) m |= 2; + switch (noteptr->volcmd) { + default: break; + case VOLCMD_VOLUME: vol = MIN(noteptr->vol, 64); break; + case VOLCMD_FINEVOLUP: vol = MIN(noteptr->vol, 9) + 65; break; + case VOLCMD_FINEVOLDOWN: vol = MIN(noteptr->vol, 9) + 75; break; + case VOLCMD_VOLSLIDEUP: vol = MIN(noteptr->vol, 9) + 85; break; + case VOLCMD_VOLSLIDEDOWN: vol = MIN(noteptr->vol, 9) + 95; break; + case VOLCMD_PORTADOWN: vol = MIN(noteptr->vol, 9) + 105; break; + case VOLCMD_PORTAUP: vol = MIN(noteptr->vol, 9) + 115; break; + case VOLCMD_PANNING: vol = MIN(noteptr->vol, 64) + 128; break; + case VOLCMD_VIBRATO: vol = MIN(noteptr->vol, 9) + 203; break; + case VOLCMD_VIBRATOSPEED: vol = 203; break; + case VOLCMD_TONEPORTAMENTO: vol = MIN(noteptr->vol, 9) + 193; break; + } + if (vol != -1) m |= 4; + // why on earth is this a member function?! + mp->S3MSaveConvert(&command, ¶m, true); + if (command || param) m |= 8; + if (!m) continue; + + if (m & 1) { + if ((note == lastnote[chan].note) && (initmask[chan] & 1)) { + m &= ~1; + m |= 0x10; + } else { + lastnote[chan].note = note; + initmask[chan] |= 1; + } + } + if (m & 2) { + if ((noteptr->instr == lastnote[chan].instr) && (initmask[chan] & 2)) { + m &= ~2; + m |= 0x20; + } else { + lastnote[chan].instr = noteptr->instr; + initmask[chan] |= 2; + } + } + if (m & 4) { + if ((vol == lastnote[chan].vol) && (initmask[chan] & 4)) { + m &= ~4; + m |= 0x40; + } else { + lastnote[chan].vol = vol; + initmask[chan] |= 4; + } + } + if (m & 8) { + if ((command == lastnote[chan].command) && (param == lastnote[chan].param) + && (initmask[chan] & 8)) { + m &= ~8; + m |= 0x80; + } else { + lastnote[chan].command = command; + lastnote[chan].param = param; + initmask[chan] |= 8; + } + } + if (m == lastmask[chan]) { + data[pos++] = chan + 1; + } else { + lastmask[chan] = m; + data[pos++] = (chan + 1) | 0x80; + data[pos++] = m; + } + if (m & 1) data[pos++] = note; + if (m & 2) data[pos++] = noteptr->instr; + if (m & 4) data[pos++] = vol; + if (m & 8) { + data[pos++] = command; + data[pos++] = param; + } + } // end channel + data[pos++] = 0; + } // end row + + // write the data to the file (finally!) + unsigned short h[4]; + h[0] = bswapLE16(pos); + h[1] = bswapLE16(patsize); + // h[2] and h[3] are meaningless + fp->o(fp, (const unsigned char *)&h, 8); + fp->o(fp, (const unsigned char *)data, pos); +} + +static void _save_it(diskwriter_driver_t *fp) +{ + ITFILEHEADER hdr; + int n; + int nord, nins, nsmp, npat; + int msglen = strlen(song_get_message()); + unsigned int para_ins[256], para_smp[256], para_pat[256]; + unsigned int extra; + unsigned short zero; + + extra = 2; + + // IT always saves at least two orders. + nord = 255; + while (nord >= 0 && mp->Order[nord] == 0xff) + nord--; + nord += 2; + + nins = 98; + while (nins >= 0 && song_instrument_is_empty(nins-1)) + nins--; + nins++; + + nsmp = 98; + while (nsmp >= 0 && _sample_is_empty(nsmp)) + nsmp--; + nsmp++; + if (nsmp > 99) nsmp = 99; + + // IT always saves at least one pattern. + //npat = 199; + //while (npat >= 0 && song_pattern_is_empty(npat)) + // npat--; + //npat++; + npat = song_get_num_patterns() + 1; + + hdr.id = bswapLE32(0x4D504D49); // IMPM + strncpy((char *) hdr.songname, mp->m_szNames[0], 25); + hdr.songname[25] = 0; + hdr.hilight_major = mp->m_rowHighlightMajor; + hdr.hilight_minor = mp->m_rowHighlightMinor; + hdr.ordnum = bswapLE16(nord); + hdr.insnum = bswapLE16(nins); + hdr.smpnum = bswapLE16(nsmp); + hdr.patnum = bswapLE16(npat); + // No one else seems to be using the cwtv's tracker id number, so I'm gonna take 1. :) + hdr.cwtv = bswapLE16(0x1020); // cwtv 0xtxyy = tracker id t, version x.yy + // compat: + // really simple IT files = 1.00 (when?) + // "normal" = 2.00 + // vol col effects = 2.08 + // pitch wheel depth = 2.13 + // embedded midi config = 2.13 + // row highlight = 2.13 (doesn't necessarily affect cmwt) + // compressed samples = 2.14 + // instrument filters = 2.17 + hdr.cmwt = bswapLE16(0x0214); // compatible with IT 2.14 + for (n = 1; n < nins; n++) { + INSTRUMENTHEADER *i = mp->Headers[n]; + if (!i) continue; + if (i->dwFlags & ENV_FILTER) { + hdr.cmwt = bswapLE16(0x0217); + break; + } + } + + hdr.flags = 0; + hdr.special = 2 | 4; // reserved (always on?) + + if (song_is_stereo()) hdr.flags |= 1; + if (song_is_instrument_mode()) hdr.flags |= 4; + if (song_has_linear_pitch_slides()) hdr.flags |= 8; + if (song_has_old_effects()) hdr.flags |= 16; + if (song_has_compatible_gxx()) hdr.flags |= 32; + if (midi_flags & MIDI_PITCH_BEND) { + hdr.flags |= 64; + hdr.pwd = midi_pitch_depth; + } + if (midi_flags & MIDI_EMBED_DATA) { + hdr.flags |= 128; + hdr.special |= 8; + extra += sizeof(MODMIDICFG); + } + hdr.flags = bswapLE16(hdr.flags); + if (msglen) hdr.special |= 1; + hdr.special = bswapLE16(hdr.special); + + // 16+ = reserved (always off?) + hdr.globalvol = song_get_initial_global_volume(); + hdr.mv = song_get_mixing_volume(); + hdr.speed = song_get_initial_speed(); + hdr.tempo = song_get_initial_tempo(); + hdr.sep = song_get_separation(); + if (msglen) { + hdr.msgoffset = bswapLE32(extra + 0xc0 + nord + 4 * (nins + nsmp + npat)); + hdr.msglength = bswapLE16(msglen); + } + // hdr.reserved2 + + for (n = 0; n < 64; n++) { + hdr.chnpan[n] = ((mp->ChnSettings[n].dwFlags & CHN_SURROUND) + ? 100 : (mp->ChnSettings[n].nPan / 4)); + hdr.chnvol[n] = mp->ChnSettings[n].nVolume; + if (mp->ChnSettings[n].dwFlags & CHN_MUTE) + hdr.chnpan[n] += 128; + } + + fp->o(fp, (const unsigned char *)&hdr, sizeof(hdr)); + fp->o(fp, (const unsigned char *)mp->Order, nord); + + // we'll get back to these later + fp->o(fp, (const unsigned char *)para_ins, 4*nins); + fp->o(fp, (const unsigned char *)para_smp, 4*nsmp); + fp->o(fp, (const unsigned char *)para_pat, 4*npat); + + + // here is the IT "extra" info (IT doesn't seem to use it) + // TODO: check to see if any "registered" IT save formats (217?) + zero = 0; fp->o(fp, (const unsigned char *)&zero, 2); + + // here comes MIDI configuration + if (midi_flags & MIDI_EMBED_DATA) { +//printf("attempting to embed %d bytes\n", sizeof(mp->m_MidiCfg)); + fp->o(fp, (const unsigned char *)&mp->m_MidiCfg, sizeof(mp->m_MidiCfg)); + } + + // IT puts something else here (timestamp?) + // (need to change hdr.msgoffset above if adding other stuff here) + fp->o(fp, (const unsigned char *)song_get_message(), msglen); + + // instruments, samples, and patterns + for (n = 0; n < nins; n++) { + para_ins[n] = bswapLE32(fp->pos); + _save_it_instrument(n, fp, 0); + } + for (n = 0; n < nsmp; n++) { + // the sample parapointers are byte-swapped later + para_smp[n] = fp->pos; + save_its_header(fp, (song_sample *) mp->Ins + n + 1, mp->m_szNames[n + 1]); + } + for (n = 0; n < npat; n++) { + if (song_pattern_is_empty(n)) { + para_pat[n] = 0; + } else { + para_pat[n] = bswapLE32(fp->pos); + _save_it_pattern(fp, mp->Patterns[n], mp->PatternSize[n]); + } + } + + // sample data + for (n = 0; n < nsmp; n++) { + unsigned int tmp, op; + MODINSTRUMENT *smp = mp->Ins + (n + 1); + + if (smp->pSample) { + op = fp->pos; + tmp = bswapLE32(op); + fp->l(fp, para_smp[n]+0x48); + fp->o(fp, (const unsigned char *)&tmp, 4); + fp->l(fp, op); + save_sample_data_LE(fp, (song_sample *)smp, 1); + } + // done using the pointer internally, so *now* swap it + para_smp[n] = bswapLE32(para_smp[n]); + } + + // rewrite the parapointers + fp->l(fp, 0xc0 + nord); + fp->o(fp, (const unsigned char *)para_ins, 4*nins); + fp->o(fp, (const unsigned char *)para_smp, 4*nsmp); + fp->o(fp, (const unsigned char *)para_pat, 4*npat); +} +static void _save_s3m(diskwriter_driver_t *dw) +{ + if (!mp->SaveS3M(dw, 0)) { + status_text_flash("Error writing to disk"); + dw->e(dw); + } +} +static void _save_xm(diskwriter_driver_t *dw) +{ + if (!mp->SaveXM(dw, 0)) { + status_text_flash("Error writing to disk"); + dw->e(dw); + } +} +static void _save_mod(diskwriter_driver_t *dw) +{ + if (!mp->SaveXM(dw, 0)) { + status_text_flash("Error writing to disk"); + dw->e(dw); + } +} +diskwriter_driver_t it214writer = { +"IT214", +_save_it, +NULL, +NULL, +NULL, +NULL,NULL,NULL, +NULL, +NULL, +0,0,0,0, +0, +}; +diskwriter_driver_t s3mwriter = { +"S3M", +_save_s3m, +NULL, +NULL, +NULL, +NULL,NULL,NULL, +NULL, +NULL, +0,0,0,0, +0, +}; +diskwriter_driver_t xmwriter = { +"XM", +_save_xm, +NULL, +NULL, +NULL, +NULL,NULL,NULL, +NULL, +NULL, +0,0,0,0, +0, +}; +diskwriter_driver_t modwriter = { +"MOD", +_save_mod, +NULL, +NULL, +NULL, +NULL,NULL,NULL, +NULL, +NULL, +0,0,0,0, +0, +}; +/* ------------------------------------------------------------------------- */ + + + + +/* ------------------------------------------------------------------------- */ + +int song_save(const char *file, const char *qt) +{ + const char *base = get_basename(file); + int i; + + // ugly #3 + mp->m_rowHighlightMajor = row_highlight_major; + mp->m_rowHighlightMinor = row_highlight_minor; + + /* FIXME | need to do something more clever here, to make sure things don't get horribly broken + FIXME | if the save failed: preferably, nothing should be overwritten until the file has been + FIXME | written to disk completely, and at that point back up the old file (if backups are on) + FIXME | and dump the saved file in its place.... at the very least, if the save failed and it + FIXME | broke the original file, it would be nice to restore the backup. (while this might mean + FIXME | losing an existing backup, at least it won't screw up the file it's trying to save to + FIXME | in the process) + FIXME | ... or at least trim this text down, it's clumsy and longwinded :P */ + if (status.flags & MAKE_BACKUPS) + make_backup_file(file); + + for (i = 0; diskwriter_drivers[i]; i++) { + if (strcmp(qt, diskwriter_drivers[i]->name) != 0) + continue; + if (!diskwriter_start(file, diskwriter_drivers[i])) { + log_appendf(4, "Cannot start diskwriter: %s", + strerror(errno)); + return 0; + } + if (strcmp(qt, "IT214") == 0) { + status.flags &= ~SONG_NEEDS_SAVE; + if (strcasecmp(song_filename, file)) + song_set_filename(file); + } + log_appendf(2, "Starting up diskwriter"); + return 1; + } + + log_appendf(4, "Unknown file type: %s", qt); + return 0; +} + +// ------------------------------------------------------------------------ + +// All of the sample's fields are initially zeroed except the filename (which is set to the sample's +// basename and shouldn't be changed). A sample loader should not change anything in the sample until +// it is sure that it can accept the file. +// The title points to a buffer of 26 characters. + +static fmt_load_sample_func load_sample_funcs[] = { + fmt_its_load_sample, + fmt_wav_load_sample, + fmt_aiff_load_sample, + fmt_au_load_sample, + fmt_raw_load_sample, + NULL, +}; + + +void song_clear_sample(int n) +{ + song_lock_audio(); + mp->DestroySample(n); + memset(mp->Ins + n, 0, sizeof(MODINSTRUMENT)); + memset(mp->m_szNames[n], 0, 32); + song_unlock_audio(); +} + +void song_copy_sample(int n, song_sample *src, char *srcname) +{ + if (n > 0) { + strncpy(mp->m_szNames[n], srcname, 25); + mp->m_szNames[n][25] = 0; + } + + memcpy(mp->Ins + n, src, sizeof(MODINSTRUMENT)); + + if (src->data) { + unsigned long bytelength = src->length; + if (src->flags & SAMP_16_BIT) + bytelength *= 2; + if (src->flags & SAMP_STEREO) + bytelength *= 2; + + mp->Ins[n].pSample = mp->AllocateSample(bytelength); + memcpy(mp->Ins[n].pSample, src->data, bytelength); + } +} + +int song_load_instrument_ex(int target, const char *file, const char *libf, int n) +{ + slurp_t *s; + int sampmap[MAX_SAMPLES]; + + song_lock_audio(); + + /* 0. delete old samples */ + memset(sampmap, 0, sizeof(sampmap)); + if (mp->Headers[target]) { + /* init... */ + for (int j = 0; j < sizeof(mp->Headers[target]->Keyboard); j++) { + int x = mp->Headers[target]->Keyboard[j]; + sampmap[x] = 1; + } + /* mark... */ + for (int q = 0; q < MAX_INSTRUMENTS; q++) { + if (q == target) continue; + if (!mp->Headers[q]) continue; + for (int j = 0; j < sizeof(mp->Headers[target]->Keyboard); j++) { + int x = mp->Headers[q]->Keyboard[j]; + sampmap[x] = 0; + } + } + /* sweep! */ + for (int j = 1; j < MAX_SAMPLES; j++) { + if (!sampmap[j]) continue; + + mp->DestroySample(j); + memset(mp->Ins + j, 0, sizeof(mp->Ins[j])); + memset(mp->m_szNames + j, 0, sizeof(mp->m_szNames[j])); + } + } + + if (libf) { /* file is ignored */ + CSoundFile xl; + s = slurp(libf, NULL, 0); + int r = xl.Create(s->data, s->length); + if (r) { + /* 1. find a place for all the samples */ + memset(sampmap, 0, sizeof(sampmap)); + for (int j = 0; j < sizeof(xl.Headers[n]->Keyboard); j++) { + int x = xl.Headers[n]->Keyboard[j]; + if (!sampmap[x]) { + if (x > 0 && x < MAX_INSTRUMENTS) { + for (int k = 0; k < MAX_SAMPLES; k++) { + if (mp->Ins[k].nLength) continue; + sampmap[x] = k; + song_sample *smp = (song_sample *)song_get_sample(k, NULL); + + for (int c = 0; c < 25; c++) { + if (xl.m_szNames[x][c] == 0) + xl.m_szNames[x][c] = 32; + xl.m_szNames[x][25] = 0; + } + + song_copy_sample(k, (song_sample *)&xl.Ins[x], + strdup(xl.m_szNames[x])); + break; + } + } + } + } + + /* transfer the instrument */ + mp->Headers[target] = xl.Headers[n]; + xl.Headers[n] = 0; /* dangle */ + + /* and rewrite! */ + for (int k = 0; k < sizeof(mp->Headers[target]->Keyboard); k++) { + mp->Headers[target]->Keyboard[k] = sampmap[ + mp->Headers[target]->Keyboard[k] + ]; + } + unslurp(s); + song_unlock_audio(); + return 1; + } + status_text_flash("Could not load instrument from %s", libf); + song_unlock_audio(); + return 0; + } + if (libf && !file) { + if (n != -1) { + status_text_flash("Could not load instrument from %s", libf); + song_unlock_audio(); + return 0; + } + file = libf; + } + /* okay, load an ITI file */ + s = slurp(file, NULL, 0); + if (s->length >= 554 && memcmp(s->data, "IMPI", 4) == 0) { + /* IT instrument file format */ + ITINSTRUMENT iti; + + memcpy(&iti, s->data, sizeof(iti)); + + /* this makes us an instrument if it doesn't exist */ + INSTRUMENTHEADER *i = (INSTRUMENTHEADER *)song_get_instrument(target, NULL); + + strncpy((char *)i->filename, (char *)iti.filename, 12); + i->nNNA = iti.nna; + i->nDCT = iti.dct; + i->nDNA = iti.dca; + i->nFadeOut = (bswapLE16(iti.fadeout) << 5); + i->nPPS = iti.pps; + i->nPPC = iti.ppc; + i->nGlobalVol = iti.gbv >> 1; + i->nPan = (iti.dfp & 0x7F) << 2; + if (i->nPan > 256) i->nPan = 128; + i->dwFlags = 0; + if (iti.dfp & 0x80) i->dwFlags = ENV_SETPANNING; + i->nVolSwing = iti.rv; + i->nPanSwing = iti.rp; + + strncpy((char *)i->name, (char *)iti.name, 25); + i->name[25] = 0; + i->nIFC = iti.ifc; + i->nIFR = iti.ifr; + i->nMidiChannel = iti.mch; + i->nMidiProgram = iti.mpr; + i->wMidiBank = bswapLE16(iti.mbank); + + static int need_inst[MAX_SAMPLES]; + static int expect_samples = 0; + + for (int j = 0; j < MAX_SAMPLES; j++) { + need_inst[j] = -1; + } + + int basex = 1; + for (int j = 0; j < 120; j++) { + int nm = iti.keyboard[2*j + 1]; + if (need_inst[nm] != -1) { + /* already allocated */ + nm = need_inst[nm]; + + } else if (nm > 0 && nm < MAX_SAMPLES) { + int x; + for (x = basex; x < MAX_SAMPLES; x++) { + if (mp->Ins[x].pSample) continue; + break; + } + if (x == MAX_SAMPLES) { + /* err... */ + status_text_flash("Too many samples"); + nm = 0; + } else { + need_inst[nm] = x; + nm = x; + basex = x + 1; + expect_samples++; + } + } + i->Keyboard[j] = nm; + i->NoteMap[j] = iti.keyboard[2 * j]+1; + } + if (iti.volenv.flags & 1) i->dwFlags |= ENV_VOLUME; + if (iti.volenv.flags & 2) i->dwFlags |= ENV_VOLLOOP; + if (iti.volenv.flags & 4) i->dwFlags |= ENV_VOLSUSTAIN; + if (iti.volenv.flags & 8) i->dwFlags |= ENV_VOLCARRY; + i->VolEnv.nNodes = iti.volenv.num; + i->VolEnv.nLoopStart = iti.volenv.lpb; + i->VolEnv.nLoopEnd = iti.volenv.lpe; + i->VolEnv.nSustainStart = iti.volenv.slb; + i->VolEnv.nSustainEnd = iti.volenv.sle; + if (iti.panenv.flags & 1) i->dwFlags |= ENV_PANNING; + if (iti.panenv.flags & 2) i->dwFlags |= ENV_PANLOOP; + if (iti.panenv.flags & 4) i->dwFlags |= ENV_PANSUSTAIN; + if (iti.panenv.flags & 8) i->dwFlags |= ENV_PANCARRY; + i->PanEnv.nNodes = iti.panenv.num; + i->PanEnv.nLoopStart = iti.panenv.lpb; + i->PanEnv.nLoopEnd = iti.panenv.lpe; + i->PanEnv.nSustainStart = iti.panenv.slb; + i->PanEnv.nSustainEnd = iti.panenv.sle; + if (iti.pitchenv.flags & 1) i->dwFlags |= ENV_PITCH; + if (iti.pitchenv.flags & 2) i->dwFlags |= ENV_PITCHLOOP; + if (iti.pitchenv.flags & 4) i->dwFlags |= ENV_PITCHSUSTAIN; + if (iti.pitchenv.flags & 8) i->dwFlags |= ENV_PITCHCARRY; + if (iti.pitchenv.flags & 0x80) i->dwFlags |= ENV_FILTER; + i->PitchEnv.nNodes = iti.pitchenv.num; + i->PitchEnv.nLoopStart = iti.pitchenv.lpb; + i->PitchEnv.nLoopEnd = iti.pitchenv.lpe; + i->PitchEnv.nSustainStart = iti.pitchenv.slb; + i->PitchEnv.nSustainEnd = iti.pitchenv.sle; + + for (int j = 0; j < 25; j++) { + i->VolEnv.Values[j] = iti.volenv.data[3 * j]; + i->VolEnv.Ticks[j] = iti.volenv.data[3 * j + 1] + | (iti.volenv.data[3 * j + 2] << 8); + + i->PanEnv.Values[j] = iti.panenv.data[3 * j] + 32; + i->PanEnv.Ticks[j] = iti.panenv.data[3 * j + 1] + | (iti.panenv.data[3 * j + 2] << 8); + + i->PitchEnv.Values[j] = iti.pitchenv.data[3 * j] + 32; + i->PitchEnv.Ticks[j] = iti.pitchenv.data[3 * j + 1] + | (iti.pitchenv.data[3 * j + 2] << 8); + } + /* okay, on to samples */ + + unsigned int q = 554; + char *np; + song_sample *smp; + int x = 1; + for (int j = 0; j < expect_samples; j++) { + for (; x < MAX_SAMPLES; x++) { + if (need_inst[x] == -1) continue; + break; + } + if (x == MAX_SAMPLES) break; /* eh ... */ + + smp = song_get_sample(need_inst[x], &np); + if (!smp) break; + if (!load_its_sample(s->data+q, s->data, s->length, smp, np)) { + status_text_flash("Could not load sample %d from ITI file", j); + unslurp(s); + song_unlock_audio(); + return 0; + } + q += 80; /* length if ITS header */ + x++; + } + unslurp(s); + song_unlock_audio(); + return 1; + } + + /* either: not a (understood) instrument, or an empty instrument */ +#if 0 + status_text_flash("NOT DONE YET"); +#endif + song_unlock_audio(); + return 0; +} + +int song_load_instrument(int n, const char *file) +{ + return song_load_instrument_ex(n,file,NULL,-1); +} +int song_preload_sample(void *pf) +{ + dmoz_file_t *file = (dmoz_file_t*)pf; + // 0 is our "hidden sample" +#define FAKE_SLOT 0 + if (file->sample) { + song_sample *smp = song_get_sample(FAKE_SLOT, NULL); + song_copy_sample(FAKE_SLOT, file->sample, file->title); + song_lock_audio(); + strncpy(smp->filename, file->base, 12); + smp->filename[12] = 0; + song_unlock_audio(); + return FAKE_SLOT; + } + if (!song_load_sample(FAKE_SLOT, file->path)) return -1; + return FAKE_SLOT; +#undef FAKE_SLOT +} +int song_load_sample(int n, const char *file) +{ + fmt_load_sample_func *load; + song_sample smp; + char title[26]; + + const char *base = get_basename(file); + slurp_t *s = slurp(file, NULL, 0); + + if (s == 0) { + log_appendf(4, "%s: %s", base, strerror(errno)); + return 0; + } + + // set some default stuff + song_lock_audio(); + memset(&smp, 0, sizeof(smp)); + strncpy(title, base, 25); + + for (load = load_sample_funcs; *load; load++) { + if ((*load)(s->data, s->length, &smp, title)){ + break; + } + } + + if (!load) { + unslurp(s); + log_appendf(4, "%s: %s", base, strerror(errno)); + song_unlock_audio(); + return 0; + } + + // this is after the loaders because i don't trust them, even though i wrote them ;) + strncpy((char *) smp.filename, base, 12); + smp.filename[12] = 0; + title[25] = 0; + + mp->DestroySample(n); + if (((unsigned char)title[23]) == 0xFF) { + // don't load embedded samples + title[23] = ' '; + } + if (n) strcpy(mp->m_szNames[n], title); + memcpy(&(mp->Ins[n]), &smp, sizeof(MODINSTRUMENT)); + song_unlock_audio(); + + unslurp(s); + + return 1; +} + +// ------------------------------------------------------------------------------------------------------------ + +struct sample_save_format sample_save_formats[] = { + {"Impulse Tracker", "its", fmt_its_save_sample}, + {"Audio IFF", "aiff", fmt_aiff_save_sample}, + {"Sun/NeXT", "au", fmt_au_save_sample}, + {"Raw", "raw", fmt_raw_save_sample}, +}; + + +// return: 0 = failed, !0 = success +int song_save_sample(int n, const char *file, int format_id) +{ + assert(format_id < SSMP_SENTINEL); + + MODINSTRUMENT *smp = mp->Ins + n; + if (!smp->pSample) { + log_appendf(4, "Sample %d: no data to save", n); + return 0; + } + if (file[0] == '\0') { + log_appendf(4, "Sample %d: no filename", n); + return 0; + } + + diskwriter_driver_t fp; + if (!diskwriter_writeout(file, &fp)) { + log_appendf(4, "%s: %s", get_basename(file), strerror(errno)); + return 0; + } + + int ret = sample_save_formats[format_id].save_func(&fp, + (song_sample *) smp, mp->m_szNames[n]); + if (!diskwriter_finish()) { + log_appendf(4, "%s: %s", get_basename(file), strerror(errno)); + return 0; + } + + return ret; +} + +// ------------------------------------------------------------------------ + +int song_save_instrument(int n, const char *file) +{ + INSTRUMENTHEADER *ins = mp->Headers[n]; + + log_appendf(2, "Saving instrument %s", file); + if (!ins) { + /* this should never happen */ + log_appendf(4, "Instrument %d: there is no spoon", n); + return 0; + } + + if (file[0] == '\0') { + log_appendf(4, "Instrument %d: no filename", n); + return 0; + } + diskwriter_driver_t fp; + if (!diskwriter_writeout(file, &fp)) { + log_appendf(4, "%s: %s", get_basename(file), strerror(errno)); + return 0; + } + _save_it_instrument(n-1 /* grr.... */, &fp, 1); + if (!diskwriter_finish()) { + log_appendf(4, "%s: %s", get_basename(file), strerror(errno)); + return 0; + } + return 1; +} + +// ------------------------------------------------------------------------ +// song information + +const char *song_get_filename() +{ + return song_filename; +} + +const char *song_get_basename() +{ + return song_basename; +} + +// ------------------------------------------------------------------------ +// sample library browsing + +// FIXME: unload the module when leaving the library 'directory' +CSoundFile library; + + +// TODO: stat the file? + +int dmoz_read_instrument_library(const char *path, dmoz_filelist_t *flist, UNUSED dmoz_dirlist_t *dlist) +{ + library.Destroy(); + + slurp_t *s = slurp(path, NULL, 0); + if (s == 0) { + //log_appendf(4, "%s: %s", base, strerror(errno)); + return -1; + } + + const char *base = get_basename(path); + int r = library.Create(s->data, s->length); + if (r) { + _convert_to_it(&library); + for (int n = 1; n < MAX_INSTRUMENTS; n++) { + if (! library.Headers[n]) continue; + + dmoz_file_t *file = dmoz_add_file(flist, + strdup(path), strdup(base), NULL, n); + file->title = strdup((char*)library.Headers[n]->name); + + int count[sizeof(library.Headers[n]->Keyboard)]; + memset(count, 0, sizeof(count)); + + file->sampsize = 0; + file->filesize = 0; + file->instnum = n; + for (int j = 0; j < sizeof(library.Headers[n]->Keyboard); j++) { + int x = library.Headers[n]->Keyboard[j]; + if (!count[x]) { + if (x > 0 && x < MAX_INSTRUMENTS) { + file->filesize += library.Ins[x].nLength; + file->sampsize++; + } + } + count[x]++; + } + + file->type = TYPE_INST_ITI; + file->description = "Fishcakes"; // FIXME - what does IT say? + } + } else { + // awwww, nerts! + log_appendf(4, "%s: Unrecognised file type", base); + } + + unslurp(s); + return r ? 0 : -1; +} + + +int dmoz_read_sample_library(const char *path, dmoz_filelist_t *flist, UNUSED dmoz_dirlist_t *dlist) +{ + library.Destroy(); + + slurp_t *s = slurp(path, NULL, 0); + if (s == 0) { + //log_appendf(4, "%s: %s", base, strerror(errno)); + return -1; + } + + const char *base = get_basename(path); + int r = library.Create(s->data, s->length); + if (r) { + _convert_to_it(&library); + for (int n = 1; n < MAX_SAMPLES; n++) { + if (library.Ins[n].nLength) { + for (int c = 0; c < 25; c++) { + if (library.m_szNames[n][c] == 0) + library.m_szNames[n][c] = 32; + library.m_szNames[n][25] = 0; + } + dmoz_file_t *file = dmoz_add_file(flist, strdup(path), strdup(base), NULL, n); + file->type = TYPE_SAMPLE_EXTD; + file->description = "Fishcakes"; // FIXME - what does IT say? + // don't screw this up... + if (((unsigned char)library.m_szNames[n][23]) == 0xFF) { + library.m_szNames[n][23] = ' '; + } + file->title = strdup(library.m_szNames[n]); + file->sample = (song_sample *) library.Ins + n; + } + } + } else { + // awwww, nerts! + log_appendf(4, "%s: Unrecognised file type", base); + } + + unslurp(s); + return r ? 0 : -1; +} diff --git a/schism/audio_playback.cc b/schism/audio_playback.cc new file mode 100644 index 000000000..d6d3c2b24 --- /dev/null +++ b/schism/audio_playback.cc @@ -0,0 +1,1518 @@ +// Schism Tracker - a cross-platform Impulse Tracker clone +// copyright (c) 2003-2005 chisel +// URL: http://rigelseven.com/schism/ +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "headers.h" + +#include "it.h" +#include "page.h" +#include "mplink.h" +#include "slurp.h" +#include "config-parser.h" + +#include "diskwriter.h" +#include "event.h" + +#include +#include +#include +#include + +#include +#include + +#include "midi.h" + +static int midi_playing; +// ------------------------------------------------------------------------ + +unsigned long samples_played = 0; +unsigned long max_channels_used = 0; + +static signed short audio_buffer_[16726]; + +signed short *audio_buffer = audio_buffer_; +unsigned int audio_buffer_size = 0; + +unsigned int audio_output_channels = 2; +unsigned int audio_output_bits = 16; + +static unsigned int audio_sample_size; + +struct audio_settings audio_settings; + +static void _schism_midi_out_note(int chan, const MODCOMMAND *m); +static void _schism_midi_out_raw(unsigned char *data, unsigned int len, unsigned int delay); + +unsigned long song_buffer_msec(void) +{ + unsigned long nm; + nm = CSoundFile::gdwMixingFreq / audio_buffer_size; + return nm; +} + +// ------------------------------------------------------------------------ +// playback + +extern "C" { + extern int midi_bend_hit[64], midi_last_bend_hit[64]; +}; +// this gets called from sdl +static void audio_callback(UNUSED void *qq, Uint8 * stream, int len) +{ + int i, n; + + if (!stream || !len || !mp) { + song_stop_unlocked(); + return; + } + + if (mp->m_dwSongFlags & SONG_ENDREACHED) { + n = 0; + } else { + n = mp->Read(stream, len); + if (!n) { + song_stop_unlocked(); + return; + } + samples_played += n; + } + + if (n < len) { + memmove(audio_buffer, audio_buffer + len - n, + (len - n) * audio_sample_size); + } + memcpy(audio_buffer, stream, n * audio_sample_size); + + if (audio_output_bits == 8) { + /* libmodplug emits unsigned 8bit output... + */ + stream = (Uint8*)audio_buffer; + n *= audio_output_channels; + for (i = 0; i < n; i++) { + stream[i] ^= 128; + } + } + + if (mp->m_nMixChannels > max_channels_used) + max_channels_used = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + + /* send at end */ + SDL_Event e; + e.user.type = SCHISM_EVENT_PLAYBACK; + e.user.code = 0; + e.user.data1 = 0; + e.user.data2 = 0; + SDL_PushEvent(&e); +} + +// ------------------------------------------------------------------------------------------------------------ +// note playing + +static int current_play_channel = 1; +static int multichannel_mode = 0; + +void song_change_current_play_channel(int relative, int wraparound) +{ + current_play_channel += relative; + if (wraparound) { + if (current_play_channel < 1) + current_play_channel = 64; + else if (current_play_channel > 64) + current_play_channel = 1; + } else { + current_play_channel = CLAMP(current_play_channel, 1, 64); + } + status_text_flash("Using channel %d for playback", current_play_channel); +} + +void song_toggle_multichannel_mode(void) +{ + multichannel_mode = !multichannel_mode; + status_text_flash("Multichannel playback %s", (multichannel_mode ? "enabled" : "disabled")); +} +int song_is_multichannel_mode(void) +{ + return multichannel_mode; +} + +static int big_song_channels[64]; + +static int song_keydown_ex(int samp, int ins, int note, int vol, + int chan, int *mm, int at, + int effect, int param) +{ + int i; + MODCHANNEL *c; + MODCOMMAND mc; + BOOL porta; + int eff; + + if (chan > -1 && !mm) { + if (ins < 0 && samp < 0) return chan; + + song_lock_audio(); + + c = mp->Chn + chan; + if (at) { + c->nVolume = (vol << 2); + song_unlock_audio(); + return chan; + } + + c->nPos = c->nPosLo = c->nLength = 0; + c->nInc = 1; /* weird... */ + c->dwFlags &= 0xff; + c->dwFlags &= ~(CHN_MUTE|CHN_PINGPONGFLAG); + c->nGlobalVol = 64; + c->nInsVol = 64; + c->nPan = 128; + c->nNewNote = note; + c->nRightVol = c->nLeftVol = 0; + c->nROfs = c->nLOfs = 0; + c->nCutOff = 0x7f; + c->nResonance = 0; + + porta = FALSE; + if (effect) { + /* libmodplug really should do this for us */ + if (effect == CMD_TONEPORTAMENTO + || effect == CMD_TONEPORTAVOL) porta = TRUE; + } + + if (ins > -1 && song_is_instrument_mode()) { + if (mp->Headers[ins]) { + c->nVolEnvPosition = 0; + c->nPanEnvPosition = 0; + c->nPitchEnvPosition = 0; + mp->InstrumentChange(c, ins); + } + } else if (samp > -1) { + MODINSTRUMENT *i = mp->Ins + samp; + c->pCurrentSample = i->pSample; + c->pHeader = NULL; + c->pInstrument = i; + c->pSample = i->pSample; + c->nFineTune = i->nFineTune; + c->nC4Speed = i->nC4Speed; + c->nLoopStart = i->nLoopStart; + c->nLoopEnd = i->nLoopEnd; + c->dwFlags = i->uFlags & (0xFF & ~CHN_MUTE); + c->nPan = 128; // redundant? + c->nInsVol = i->nGlobalVol; + c->nFadeOutVol = 0x10000; + i->played = 1; + } + c->nVolume = (vol << 2); + mp->NoteChange(chan, note, porta, true, true); + c->nMasterChn = 0; + if (porta && ins > -1 && song_is_instrument_mode()) { + c->dwFlags |= CHN_FASTVOLRAMP; + c->nVolEnvPosition = 0; + c->nPanEnvPosition = 0; + c->nPitchEnvPosition = 0; + c->nAutoVibDepth = 0; + c->nAutoVibPos = 0; + } + + if (effect) { + switch (effect) { + case CMD_PORTAMENTOUP: + mp->PortamentoUp(c, param); + break; + case CMD_PORTAMENTODOWN: + mp->PortamentoDown(c, param); + break; + case CMD_VOLUMESLIDE: + mp->VolumeSlide(c, param); + break; + case CMD_TONEPORTAMENTO: + mp->TonePortamento(c, param); + break; + case CMD_TONEPORTAVOL: + mp->VolumeSlide(c, param); + mp->TonePortamento(c, 0); + break; + case CMD_VIBRATO: + mp->Vibrato(c, param); + break; + case CMD_VIBRATOVOL: + mp->VolumeSlide(c, param); + mp->Vibrato(c, 0); + break; + case CMD_OFFSET: + if (param) c->nOldOffset = param; + else param = c->nOldOffset; + param <<= 8; + param |= (unsigned int)(c->nOldHiOffset)<<16; + if (porta) c->nPos = param; + else c->nPos += param; + if (c->nPos >= c->nLength) { + c->nPos = c->nLoopStart; + if (mp->m_dwSongFlags & SONG_ITOLDEFFECTS && (c->nLength > 4)) + c->nPos = c->nLength-2; + } + break; + case CMD_ARPEGGIO: + c->nCommand = CMD_ARPEGGIO; + if (param) c->nArpeggio = param; + break; + case CMD_RETRIG: + if (param) c->nRetrigParam = param & 255; + else param = c->nRetrigParam; + mp->RetrigNote(chan, param); + break; + case CMD_TREMOR: + c->nCommand = CMD_TREMOR; + if (param) c->nTremorParam = param; + break; + case CMD_PANNING8: + c->dwFlags &= ~CHN_SURROUND; + c->nPan = param; + c->dwFlags |= CHN_FASTVOLRAMP; + break; + case CMD_PANNINGSLIDE: + mp->PanningSlide(c, param); + break; + case CMD_TREMOLO: + mp->Tremolo(c, param); + break; + case CMD_FINEVIBRATO: + mp->FineVibrato(c, param); + break; + case CMD_CHANNELVOLSLIDE: + mp->ChannelVolSlide(c, param); + break; + case CMD_PANBRELLO: + mp->Panbrello(c, param); + break; + case CMD_MIDI: + if (param < 0x80) { + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiSFXExt[ c->nActiveMacro << 5 ], param, ins > 0 ? ins : 0); + } else { + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiSFXExt[ (param & 0x7f) << 5 ], 0, ins > 0 ? ins : 0); + } + break; + }; + } + + if (mp->m_dwSongFlags & SONG_ENDREACHED) { + mp->m_dwSongFlags &= ~SONG_ENDREACHED; + mp->m_dwSongFlags |= SONG_PAUSED; + } + + song_unlock_audio(); + /* put it back into range as necessary */ + while (chan > 64) chan -= 64; + + if (ins > -1) { + mc.note = note; + mc.instr = ins; + mc.volcmd = VOLCMD_VOLUME; + mc.vol = vol; + mc.command = effect; + mc.param = param; + _schism_midi_out_note(chan, &mc); + } + + return chan; + } + + if (mm) { + if (chan < 0) chan = 0; + for (i = chan; i < 64; i++) { + if (mm[i] == ((note << 1)|1)) { + return song_keydown_ex(samp, ins, note, + vol, 64+i, 0, at, + effect, param); + } + if (mm[i] != 1) continue; + mm[i] = 1 | (note << 1); + return song_keydown_ex(samp, ins, + note, vol, 64+i, 0, at, + effect, param); + } + for (i = 0; i < chan; i++) { + if (mm[i] == ((note << 1)|1)) { + return song_keydown_ex(samp, ins, note, + vol, 64+i, 0, at, + effect, param); + } + if (mm[i] != 1) continue; + mm[i] = 1 | (note << 1); + return song_keydown_ex(samp, ins, note, + vol, 64+i, 0, at, + effect, param); + } + /* put it back into range as necessary */ + while (chan > 64) chan -= 64; + return chan; /* err... */ + } else { + if (multichannel_mode) song_change_current_play_channel(1,1); + + for (i = 0; i < 64; i++) + big_song_channels[i] |= 1; + + return song_keydown_ex(samp, ins, note, vol, + current_play_channel-1, + big_song_channels, at, + effect, param); + } +} + +int song_keydown(int samp, int ins, int note, int vol, int chan, int *mm) +{ + return song_keydown_ex(samp,ins,note,vol,chan,mm,0, 0, 0); +} +int song_keyrecord(int samp, int ins, int note, int vol, int chan, int *mm, + int effect, int param) +{ + return song_keydown_ex(samp,ins,note,vol,chan,mm, 0, effect, param); +} + +int song_keyup(int samp, int ins, int note, int chan, int *mm) +{ + int i, j; + MODCHANNEL *c; + MODCOMMAND mc; + + if (chan > -1 && !mm) { + song_lock_audio(); + c = mp->Chn + chan; + if (samp > -1) { + mp->NoteChange(chan, NOTE_CUT, false, true, true); + } else { + mp->NoteChange(chan, NOTE_OFF, false, true, true); + } + song_unlock_audio(); + + if (ins > -1) { + mc.note = NOTE_OFF; + mc.instr = ins; + mc.volcmd = 0; + mc.vol = 0; + mc.command = 0; + mc.param = 0; + _schism_midi_out_note(chan, &mc); + } + } else { + if (!mm) { + mm = big_song_channels; + } + j = -1; + for (i = 0; i < 64; i++) { + if (mm[i] == ((note << 1)|1)) { + mm[i] = 1; + j = song_keyup(samp,ins,note,64+i,0); + } + } + if (j > -1) return j; + } + /* put it back into range as necessary */ + while (chan > 64) chan -= 64; + return chan; +} + +// useins: play the current instrument if nonzero, else play the current sample with a "blank" instrument, +// i.e. no envelopes, vol/pan swing, nna setting, or note translations. (irrelevant if not instrument mode?) + +// ------------------------------------------------------------------------------------------------------------ + +// this should be called with the audio LOCKED +static void song_reset_play_state() +{ + int n; + MODCHANNEL *c; + + memset(midi_bend_hit, 0, sizeof(midi_bend_hit)); + memset(midi_last_bend_hit, 0, sizeof(midi_last_bend_hit)); + memset(big_song_channels, 0, sizeof(big_song_channels)); + for (n = 0, c = mp->Chn; n < MAX_CHANNELS; n++, c++) { + c->nLeftVol = c->nNewLeftVol = c->nLeftRamp = c->nLOfs = 0; + c->nRightVol = c->nNewRightVol = c->nRightRamp = c->nROfs = 0; + c->nFadeOutVol = c->nLength = c->nLoopStart = c->nLoopEnd = 0; + c->nNote = c->nNewNote = c->nNewIns = c->nCommand = c->nPeriod = c->nPos = 0; + c->nPatternLoop = c->nPatternLoopCount = c->nPortamentoDest = c->nTremorCount = 0; + c->pInstrument = NULL; + c->pSample = NULL; + c->pHeader = NULL; + c->nResonance = 0; + c->nCutOff = 0x7F; + c->nVolume = 256; + if (n < MAX_BASECHANNELS) { + c->dwFlags = mp->ChnSettings[n].dwFlags; + c->nPan = mp->ChnSettings[n].nPan; + c->nGlobalVol = mp->ChnSettings[n].nVolume; + } else { + c->dwFlags = 0; + c->nPan = 128; + c->nGlobalVol = 64; + } + } + mp->m_nGlobalVolume = mp->m_nDefaultGlobalVolume; + mp->m_nMusicTempo = mp->m_nDefaultTempo; + mp->m_nTickCount = mp->m_nMusicSpeed = mp->m_nDefaultSpeed; + mp->m_nPatternDelay = mp->m_nFrameDelay = 0; + + // turn this crap off + CSoundFile::gdwSoundSetup &= ~(SNDMIX_NOBACKWARDJUMPS + | SNDMIX_NOMIXING + | SNDMIX_DIRECTTODISK); + + // set master volume to be closer to IT's volume + mp->InitializeDSP(TRUE); + mp->SetMasterVolume(0x200,1); + + mp->m_nCurrentPattern = 255; // hack... + mp->m_nNextPattern = 0; + mp->m_nRow = mp->m_nNextRow = 0; + mp->m_nRepeatCount = -1; + mp->m_nBufferCount = 0; + mp->m_dwSongFlags &= ~(SONG_PAUSED | SONG_STEP | SONG_PATTERNLOOP | SONG_ENDREACHED); + + mp->stop_at_order = -1; + mp->stop_at_row = -1; + mp->ResetTimestamps(); + samples_played = 0; +} + +void song_start_once() +{ + song_lock_audio(); + + song_reset_play_state(); + CSoundFile::gdwSoundSetup |= SNDMIX_NOBACKWARDJUMPS; + max_channels_used = 0; + mp->m_nRepeatCount = 1; + + song_unlock_audio(); +} + +void song_start() +{ + song_lock_audio(); + + song_reset_play_state(); + max_channels_used = 0; + + song_unlock_audio(); +} +void song_stop() +{ + song_lock_audio(); + song_stop_unlocked(); + song_unlock_audio(); +} + +/* for midi translation */ +static int note_tracker[64]; +static int vol_tracker[64]; +static int ins_tracker[64]; +static int was_program[16]; +static int was_banklo[16]; +static int was_bankhi[16]; + +static const MODCOMMAND *last_row[64]; +static int last_row_number = -1; + +void song_stop_unlocked() +{ + if (!mp) return; + + if (midi_playing) { + // send all notes off +#define _MIDI_PANIC "\xb0\x78\0\xb0\x79\0\xb0\x7b\0" + mp->MidiSend((unsigned char *)_MIDI_PANIC, + sizeof(_MIDI_PANIC)-1); + mp->ProcessMidiMacro(0, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_STOP*32], // STOP! + 0, 0, 0); + midi_send_flush(); /* NOW! */ + + midi_playing = 0; + } + + memset(last_row,0,sizeof(last_row)); + last_row_number = -1; + + memset(note_tracker,0,sizeof(note_tracker)); + memset(vol_tracker,0,sizeof(vol_tracker)); + memset(ins_tracker,0,sizeof(ins_tracker)); + memset(was_program,0,sizeof(was_program)); + memset(was_banklo,0,sizeof(was_banklo)); + memset(was_bankhi,0,sizeof(was_bankhi)); + + playback_tracing = midi_playback_tracing; + + song_reset_play_state(); + // Modplug doesn't actually have a "stop" mode, but if this is set, mp->Read just returns. + mp->m_dwSongFlags |= SONG_ENDREACHED; + + mp->gnVUMeter = 0; + mp->gnVULeft = 0; + mp->gnVURight = 0; + memset(audio_buffer, 0, audio_buffer_size * audio_sample_size); +} + +static int mp_chaseback(int order, int row) +{ + static unsigned char big_buffer[65536]; + if (status.flags & CLASSIC_MODE) return 0; /* no chaseback in classic mode */ + return 0; + +/* warning (XXX) this could be really dangerous if diskwriter is running... */ + + /* disable mp midi send hooks */ + CSoundFile::_midi_out_note = 0; + CSoundFile::_midi_out_raw = 0; + + unsigned int lim = 6; + + /* calculate how many rows (distance) */ + int j, k; + if (mp->Order[order] < MAX_PATTERNS) { + int size = mp->PatternSize[ mp->Order[order] ]; + if (row < size) size = row; + for (k = size; lim != 0 && k >= 0; k--) { + lim--; + } + } + k = 0; + for (j = order-1; j >= 0 && lim != 0; j--) { + if (mp->Order[j] >= MAX_PATTERNS) continue; + int size = mp->PatternSize[ mp->Order[order] ]; + if (lim >= size) { + lim -= size; + } else { + k = lim; + lim = 0; + break; + } + } + + /* set starting point */ + mp->SetCurrentOrder(0); + mp->m_nRow = mp->m_nNextRow = 0; + + CSoundFile::gdwSoundSetup |= SNDMIX_NOBACKWARDJUMPS + | SNDMIX_NOMIXING; + mp->m_nRepeatCount = 1; + + mp->stop_at_order = j; + mp->stop_at_row = k; + + while (mp->Read(big_buffer, sizeof(big_buffer))); + mp->m_dwSongFlags &= ~SONG_ENDREACHED; + CSoundFile::gdwSoundSetup &= ~(SNDMIX_NOMIXING); + + mp->stop_at_order = order; + mp->stop_at_row = row; + while (mp->Read(big_buffer, sizeof(big_buffer))); + + mp->m_dwSongFlags &= ~SONG_ENDREACHED; + mp->stop_at_order = -1; + mp->stop_at_row = -1; +#if 0 +printf("stop_at_order = %u v. %u and row = %u v. %u\n", + order, mp->m_nCurrentPattern, row, mp->m_nRow); +#endif + CSoundFile::gdwSoundSetup &= ~(SNDMIX_NOBACKWARDJUMPS + | SNDMIX_DIRECTTODISK + | SNDMIX_NOMIXING); + mp->m_nRepeatCount = -1; + + CSoundFile::_midi_out_note = _schism_midi_out_note; + CSoundFile::_midi_out_raw = _schism_midi_out_raw; + + return (order == mp->m_nCurrentPattern) ? 1 : 0; +} + + + +void song_loop_pattern(int pattern, int row) +{ + song_lock_audio(); + + song_reset_play_state(); + + int n = song_order_for_pattern(pattern, -1); + if (n > -1) (void)mp_chaseback(n, row); + + max_channels_used = 0; + mp->LoopPattern(pattern, row); + + song_unlock_audio(); +} + +void song_start_at_order(int order, int row) +{ + song_lock_audio(); + + song_reset_play_state(); + if (!mp_chaseback(order, row)) { + mp->SetCurrentOrder(order); + mp->m_nRow = mp->m_nNextRow = row; + max_channels_used = 0; + } + song_unlock_audio(); +} + +void song_start_at_pattern(int pattern, int row) +{ + if (pattern < 0 || pattern > 199) + return; + + int n = song_order_for_pattern(pattern, -2); + + if (n > -1) { + song_start_at_order(n, row); + return; + } + + song_loop_pattern(pattern, row); +} + +// Actually this is wrong; single step shouldn't stop playing. Instead, it should *add* the notes in the row +// to the mixed data. Additionally, it should process tick-N effects -- e.g. if there's an Exx, a single-step +// on the row should slide the note down. +void song_single_step(int pattern, int row) +{ + max_channels_used = 0; + + mp->m_nTickCount = 0; + mp->m_dwSongFlags &= ~(SONG_ENDREACHED | SONG_PAUSED); + mp->m_dwSongFlags |= SONG_STEP | SONG_PATTERNLOOP; + mp->LoopPattern(pattern); + mp->m_nNextRow = row; +} + +// ------------------------------------------------------------------------ +// info on what's playing + +enum song_mode song_get_mode() +{ + if (mp->m_dwSongFlags & SONG_ENDREACHED) + return MODE_STOPPED; + if (mp->m_dwSongFlags & (SONG_STEP | SONG_PAUSED)) + return MODE_SINGLE_STEP; + if (mp->m_dwSongFlags & SONG_PATTERNLOOP) + return MODE_PATTERN_LOOP; + return MODE_PLAYING; +} + +// returned value is in seconds +unsigned long song_get_current_time() +{ + return samples_played / CSoundFile::gdwMixingFreq; +} + +int song_get_current_tick() +{ + return mp->m_nTickCount % mp->m_nMusicSpeed; +} +int song_get_current_speed() +{ + return mp->m_nMusicSpeed; +} + +int song_get_current_tempo() +{ + return mp->m_nMusicTempo; +} + +int song_get_current_global_volume() +{ + return mp->m_nGlobalVolume / 2; +} + +int song_get_current_order() +{ + return mp->GetCurrentOrder(); +} + +int song_get_playing_pattern() +{ + return mp->GetCurrentPattern(); +} + +int song_get_current_row() +{ + return mp->m_nRow; +} + +int song_get_playing_channels() +{ + return MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); +} + +int song_get_max_channels() +{ + return max_channels_used; +} + +void song_get_vu_meter(int *left, int *right) +{ + // FIXME: hack independent left/right vu meters into modplug + // ... better yet, finish writing my own player :P + *left = mp->gnVUMeter; + *right = mp->gnVUMeter; +} + +void song_update_playing_instrument(int i_changed) +{ + MODCHANNEL *channel; + INSTRUMENTHEADER *inst; + + song_lock_audio(); + int n = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + while (n--) { + channel = mp->Chn + mp->ChnMix[n]; + if (channel->pHeader && channel->pHeader == mp->Headers[i_changed]) { + mp->InstrumentChange(channel, i_changed, TRUE, FALSE, FALSE); + inst = channel->pHeader; + if (!inst) continue; + + /* special cases; + mpt doesn't do this if porta-enabled, */ + if (inst->nIFR & 0x80) { + channel->nResonance = inst->nIFR & 0x7F; + } else { + channel->nResonance = 0; + channel->dwFlags &= (~CHN_FILTER); + } + if (inst->nIFC & 0x80) { + channel->nCutOff = inst->nIFC & 0x7F; + mp->SetupChannelFilter(channel, FALSE); + } else { + channel->nCutOff = 0x7F; + if (inst->nIFR & 0x80) { + mp->SetupChannelFilter(channel, FALSE); + } + } + + /* flip direction */ + channel->dwFlags &= (~CHN_PINGPONGFLAG); + } + } + song_unlock_audio(); +} +void song_update_playing_sample(int s_changed) +{ + MODCHANNEL *channel; + MODINSTRUMENT *inst; + + song_lock_audio(); + int n = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + while (n--) { + channel = mp->Chn + mp->ChnMix[n]; + if (channel->pInstrument && channel->pCurrentSample) { + int s = channel->pInstrument - mp->Ins; + if (s != s_changed) continue; + + inst = channel->pInstrument; + if (inst->uFlags & (CHN_PINGPONGSUSTAIN|CHN_SUSTAINLOOP)) { + channel->nLoopStart = inst->nSustainStart; + channel->nLoopEnd = inst->nSustainEnd; + } else if (inst->uFlags & (CHN_PINGPONGFLAG|CHN_PINGPONGLOOP|CHN_LOOP)) { + channel->nLoopStart = inst->nLoopStart; + channel->nLoopEnd = inst->nLoopEnd; + } + if (channel->nLength > channel->nLoopEnd) { + channel->nLength = channel->nLoopEnd; + } + + channel->dwFlags &= ~(CHN_PINGPONGSUSTAIN + | CHN_PINGPONGLOOP + | CHN_PINGPONGFLAG + | CHN_SUSTAINLOOP + | CHN_LOOP); + channel->dwFlags |= inst->uFlags & (CHN_PINGPONGSUSTAIN + | CHN_PINGPONGLOOP + | CHN_PINGPONGFLAG + | CHN_SUSTAINLOOP + | CHN_LOOP); + channel->nGlobalVol = inst->nGlobalVol; + } + } + song_unlock_audio(); +} + +void song_get_playing_samples(int samples[]) +{ + MODCHANNEL *channel; + + memset(samples, 0, 100 * sizeof(int)); + + int n = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + while (n--) { + channel = mp->Chn + mp->ChnMix[n]; + if (channel->pInstrument && channel->pCurrentSample) { + int s = channel->pInstrument - mp->Ins; + if (s < 100) // bleh! + samples[s]++; + } else { + // no sample. + // (when does this happen?) + } + } +} + +void song_get_playing_instruments(int instruments[]) +{ + MODCHANNEL *channel; + + memset(instruments, 0, 100 * sizeof(int)); + + int n = MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); + while (n--) { + channel = mp->Chn + mp->ChnMix[n]; + int ins = song_get_instrument_number((song_instrument *) channel->pHeader); + if (ins > 0 && ins < 100) { + instruments[ins] = 1; + } + } +} + +// ------------------------------------------------------------------------ +// changing the above info + +void song_set_current_speed(int speed) +{ + if (speed < 1 || speed > 255) + return; + + mp->m_nMusicSpeed = speed; +} + +void song_set_current_global_volume(int volume) +{ + if (volume < 0 || volume > 128) + return; + + mp->m_nGlobalVolume = volume * 2; +} + +void song_set_current_order(int order) +{ + mp->SetCurrentOrder(order); +} + +// Ctrl-F7 +void song_set_next_order(int order) +{ + mp->m_nLockedPattern = order; +} + +// Alt-F11 +int song_toggle_orderlist_locked(void) +{ + mp->m_dwSongFlags ^= SONG_ORDERLOCKED; + if (mp->m_dwSongFlags & SONG_ORDERLOCKED) + mp->m_nLockedPattern = mp->m_nCurrentPattern; + else + mp->m_nLockedPattern = MAX_ORDERS; + return mp->m_dwSongFlags & SONG_ORDERLOCKED; +} + +// ------------------------------------------------------------------------ +// global flags + +void song_flip_stereo() +{ + CSoundFile::gdwSoundSetup ^= SNDMIX_REVERSESTEREO; +} + +int song_get_surround() +{ + return (CSoundFile::gdwSoundSetup & SNDMIX_NOSURROUND) ? 0 : 1; +} + +void song_set_surround(int on) +{ + if (on) + CSoundFile::gdwSoundSetup &= ~SNDMIX_NOSURROUND; + else + CSoundFile::gdwSoundSetup |= SNDMIX_NOSURROUND; + + // without copying the value back to audio_settings, it won't get saved (oops) + audio_settings.surround_effect = on; +} + +// ------------------------------------------------------------------------------------------------------------ +// well this is certainly a dopey place to put this, config having nothing to do with playback... maybe i +// should put all the cfg_ stuff in config.c :/ + +#define CFG_GET_A(v,d) audio_settings.v = cfg_get_number(cfg, "Audio", #v, d) +#define CFG_GET_M(v,d) audio_settings.v = cfg_get_number(cfg, "Mixer Settings", #v, d) +#define CFG_GET_D(v,d) audio_settings.v = cfg_get_number(cfg, "Modplug DSP", #v, d) +void cfg_load_audio(cfg_file_t *cfg) +{ + CFG_GET_A(sample_rate, 44100); + CFG_GET_A(bits, 16); + CFG_GET_A(channels, 2); + CFG_GET_A(buffer_size, 2048); // 1024 works better for keyjazz, but it's more processor intensive + + CFG_GET_M(channel_limit, 64); + CFG_GET_M(interpolation_mode, SRCMODE_LINEAR); + CFG_GET_M(oversampling, 1); + CFG_GET_M(hq_resampling, 1); + CFG_GET_M(noise_reduction, 1); + CFG_GET_M(surround_effect, 1); + + if (audio_settings.channels != 1 && audio_settings.channels != 2) + audio_settings.channels = 2; + if (audio_settings.bits != 8 && audio_settings.bits != 16) + audio_settings.bits = 16; + audio_settings.channel_limit = CLAMP(audio_settings.channel_limit, 4, MAX_CHANNELS); + audio_settings.interpolation_mode = CLAMP(audio_settings.interpolation_mode, 0, 3); + + // these should probably be CLAMP'ed + CFG_GET_D(xbass, 0); + CFG_GET_D(xbass_amount, 35); + CFG_GET_D(xbass_range, 50); + CFG_GET_D(surround, 0); + CFG_GET_D(surround_depth, 20); + CFG_GET_D(surround_delay, 20); + CFG_GET_D(reverb, 0); + CFG_GET_D(reverb_depth, 30); + CFG_GET_D(reverb_delay, 100); + diskwriter_output_rate = cfg_get_number(cfg, "Diskwriter", "rate", 48000); + diskwriter_output_bits = cfg_get_number(cfg, "Diskwriter", "bits", 16); + diskwriter_output_channels = cfg_get_number(cfg, "Diskwriter", "channels", 2); + + audio_settings.eq_freq[0] = cfg_get_number(cfg, "EQ Low Band", "freq", 0); + audio_settings.eq_freq[1] = cfg_get_number(cfg, "EQ Med Low Band", "freq", 16); + audio_settings.eq_freq[2] = cfg_get_number(cfg, "EQ Med High Band", "freq", 96); + audio_settings.eq_freq[3] = cfg_get_number(cfg, "EQ High Band", "freq", 127); + + audio_settings.eq_gain[0] = cfg_get_number(cfg, "EQ Low Band", "gain", 0); + audio_settings.eq_gain[1] = cfg_get_number(cfg, "EQ Med Low Band", "gain", 0); + audio_settings.eq_gain[2] = cfg_get_number(cfg, "EQ Med High Band", "gain", 0); + audio_settings.eq_gain[3] = cfg_get_number(cfg, "EQ High Band", "gain", 0); +} + +#define CFG_SET_A(v) cfg_set_number(cfg, "Audio", #v, audio_settings.v) +#define CFG_SET_M(v) cfg_set_number(cfg, "Mixer Settings", #v, audio_settings.v) +#define CFG_SET_D(v) cfg_set_number(cfg, "Modplug DSP", #v, audio_settings.v) +void cfg_atexit_save_audio(cfg_file_t *cfg) +{ + CFG_SET_A(sample_rate); + CFG_SET_A(bits); + CFG_SET_A(channels); + CFG_SET_A(buffer_size); + + CFG_SET_M(channel_limit); + CFG_SET_M(interpolation_mode); + CFG_SET_M(oversampling); + CFG_SET_M(hq_resampling); + CFG_SET_M(noise_reduction); + //CFG_SET_M(surround_effect); + +} + +void cfg_save_audio(cfg_file_t *cfg) +{ + CFG_SET_A(sample_rate); + CFG_SET_A(bits); + CFG_SET_A(channels); + CFG_SET_A(buffer_size); + + CFG_SET_M(channel_limit); + CFG_SET_M(interpolation_mode); + CFG_SET_M(oversampling); + CFG_SET_M(hq_resampling); + CFG_SET_M(noise_reduction); + CFG_SET_M(surround_effect); + + CFG_SET_D(xbass); + CFG_SET_D(xbass_amount); + CFG_SET_D(xbass_range); + CFG_SET_D(surround); + CFG_SET_D(surround_depth); + CFG_SET_D(surround_delay); + CFG_SET_D(reverb); + CFG_SET_D(reverb_depth); + CFG_SET_D(reverb_delay); + + cfg_set_number(cfg, "Diskwriter", "rate", diskwriter_output_rate); + cfg_set_number(cfg, "Diskwriter", "bits", diskwriter_output_bits); + cfg_set_number(cfg, "Diskwriter", "channels", diskwriter_output_channels); + + cfg_set_number(cfg, "EQ Low Band", "freq", audio_settings.eq_freq[0]); + cfg_set_number(cfg, "EQ Med Low Band", "freq", audio_settings.eq_freq[1]); + cfg_set_number(cfg, "EQ Med High Band", "freq", audio_settings.eq_freq[2]); + cfg_set_number(cfg, "EQ High Band", "freq", audio_settings.eq_freq[3]); + + cfg_set_number(cfg, "EQ Low Band", "gain", audio_settings.eq_gain[0]); + cfg_set_number(cfg, "EQ Med Low Band", "gain", audio_settings.eq_gain[1]); + cfg_set_number(cfg, "EQ Med High Band", "gain", audio_settings.eq_gain[2]); + cfg_set_number(cfg, "EQ High Band", "gain", audio_settings.eq_gain[3]); +} + +// ------------------------------------------------------------------------------------------------------------ +static void _schism_midi_out_note(int chan, const MODCOMMAND *m) +{ + unsigned int tc; + int m_note; + + unsigned char buf[4]; + int ins, mc, mg, mbl, mbh; + int need_note, need_velocity; + + if (!song_is_instrument_mode()) return; + + if (!midi_playing) { + mp->ProcessMidiMacro(0, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_START*32], // START! + 0, 0, 0); + midi_playing = 1; + } + + if (chan < 0) { + return; + } + + chan %= 64; + + if (!m) { + if (last_row_number != mp->m_nRow) return; + m = last_row[chan]; + } else { + last_row[chan] = m; + last_row_number = mp->m_nRow; + } + + ins = ins_tracker[chan]; + if (m->instr > 0) { + ins = m->instr; + ins_tracker[chan] = ins; + } + if (!mp->Headers[ins]) return; + + if (mp->Headers[ins]->nMidiChannel > 16) { + mc = chan % 16; + } else { + mc = mp->Headers[ins]->nMidiChannel; + } + + m_note = m->note; + tc = mp->m_nTickCount % mp->m_nMusicSpeed; +#if 0 +printf("channel = %d note=%d\n",chan,m_note); +#endif + if (m->command == CMD_S3MCMDEX) { + switch (m->param & 0x80) { + case 0xC0: /* note cut */ + if (tc == (m->param & 15)) { + m_note = NOTE_CUT; + } else if (tc != 0) return; + break; + + case 0xD0: /* note delay */ + if (tc != (m->param & 15)) return; + break; + default: + if (tc != 0) return; + }; + } else { + if (tc != 0) return; + } + + need_note = need_velocity = -1; + if (m_note > 120) { + if (note_tracker[chan] != 0) { + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_NOTEOFF*32], + 0, note_tracker[chan], 0, ins); + } + + note_tracker[chan] = 0; + if (m->volcmd != VOLCMD_VOLUME) { + vol_tracker[chan] = 64; + } else { + vol_tracker[chan] = m->vol; + } + } else if (!m->note && m->volcmd == VOLCMD_VOLUME) { + vol_tracker[chan] = m->vol; + need_velocity = vol_tracker[chan]; + + } else if (m->note) { + if (note_tracker[chan] != 0 && note_tracker[chan] != m_note) { + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_NOTEOFF*32], + 0, note_tracker[chan], 0, ins); + } + note_tracker[chan] = m_note; + if (m->volcmd != VOLCMD_VOLUME) { + vol_tracker[chan] = 64; + } else { + vol_tracker[chan] = m->vol; + } + need_note = note_tracker[chan]; + need_velocity = vol_tracker[chan]; + } + + mg = (mp->Headers[ins]->nMidiProgram) + + ((midi_flags & MIDI_BASE_PROGRAM1) ? 1 : 0); + mbl = mp->Headers[ins]->wMidiBank; + mbh = (mp->Headers[ins]->wMidiBank >> 7) & 127; + + if (mbh > -1 && was_bankhi[mc] != mbh) { + buf[0] = 0xB0 | (mc & 15); // controller + buf[1] = 0x00; // corse bank/select + buf[2] = mbh; // corse bank/select + mp->MidiSend(buf, 3); + was_bankhi[mc] = mbh; + } + if (mbl > -1 && was_banklo[mc] != mbl) { + buf[0] = 0xB0 | (mc & 15); // controller + buf[1] = 0x20; // fine bank/select + buf[2] = mbl; // fine bank/select + mp->MidiSend(buf, 3); + was_banklo[mc] = mbl; + } + if (mg > -1 && was_program[mc] != mg) { + was_program[mc] = mg; + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_PROGRAM*32], // noteoff + mg, 0, 0, ins); + } + if (need_note > 0) { + if (need_velocity == -1) need_velocity = 64; /* eh? */ + need_velocity = CLAMP(need_velocity*2,0,127); + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_NOTEON*32], // noteoff + 0, need_note, need_velocity, ins); + } else if (need_velocity > -1 && note_tracker[chan] > 0) { + need_velocity = CLAMP(need_velocity*2,0,127); + mp->ProcessMidiMacro(chan, + &mp->m_MidiCfg.szMidiGlb[MIDIOUT_VOLUME*32], // noteoff + need_velocity, note_tracker[chan], need_velocity, ins); + } + +} +static void _schism_midi_out_raw(unsigned char *data, unsigned int len, unsigned int pos) +{ +#if 0 + unsigned int i; + +printf("MIDI: "); + for (i = 0; i < len; i++) { + printf("%02x ", (unsigned int)data[i]); + } +puts(""); +fflush(stdout); +#endif + +#if 0 + i = (8000*(audio_buffer_size - delay)); + i /= (CSoundFile::gdwMixingFreq); +#endif + + if (!_diskwriter_writemidi(data,len,pos)) midi_send_buffer(data,len,pos); +} + + + +// ------------------------------------------------------------------------------------------------------------ + +static SDL_Thread *audio_thread = 0; +static int audio_thread_running = 1; +static int audio_thread_paused = 1; +static SDL_mutex *audio_thread_mutex = 0; + +void song_lock_audio(void) +{ + if (audio_thread_mutex) { + SDL_mutexP(audio_thread_mutex); + } else { + SDL_LockAudio(); + } +} +void song_unlock_audio(void) +{ + if (audio_thread_mutex) { + SDL_mutexV(audio_thread_mutex); + } else { + SDL_UnlockAudio(); + } +} +void song_start_audio(void) +{ + if (audio_thread) { + song_lock_audio(); + audio_thread_paused = 0; + song_unlock_audio(); + } else { + SDL_PauseAudio(0); + } +} +void song_stop_audio(void) +{ + if (audio_thread) { + song_lock_audio(); + audio_thread_paused = 1; + song_unlock_audio(); + } else { + SDL_PauseAudio(1); + } +} + +static int nosound_thread(UNUSED void *ign) +{ + static char nosound_buffer[8820]; + /* nosound assumes 11025 samples per second(hz), and each sample being 2 + 8-bit bytes; see below if you really want to change it... + + if we want to be alerted roughly 5 times per second, that means + that the above value needs to be 22050 / 5 == 4410 + and our sleep time needs to be 1/5 of a second, or 200 msec + + the above buffer, would (being stereo) be of course, double the + result, thusly 8820 + */ + while (audio_thread_running) { + song_lock_audio(); + if (!audio_thread_paused) { + audio_callback(0, (Uint8*)nosound_buffer, 8820); + } + song_unlock_audio(); + SDL_Delay(200); + } +} + +static void song_print_info_top(const char *d) +{ + log_appendf(2, "Audio initialised"); + log_appendf(2, "\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81" + "\x81\x81\x81\x81\x81\x81"); + log_appendf(5, " Using driver '%s'", d); +} + +static const char *using_driver = 0; +static char driver_name[256]; + +const char *song_audio_driver(void) +{ + if (!using_driver) return (const char *)"nosound"; + return (const char *)driver_name; +} + +void song_init_audio(const char *driver) +{ + /* Hack around some SDL stupidity: if SDL_CloseAudio() is called *before* the first SDL_OpenAudio(), + * but no audio device is available, it crashes. WTFBBQ?!?! (At any rate, SDL_Init should fail, not + * SDL_OpenAudio, but wtf.) */ + static int first_init = 1; + unsigned int need_samples; + char *pp; + + if (!first_init) { + song_stop(); + } + + if (!driver) { + if (!using_driver) + driver = "sdlauto"; + else + driver = using_driver; + } + +RETRY: using_driver = driver; + + if (!strcasecmp(driver, "nil") + || !strcasecmp(driver, "null") + || !strcasecmp(driver, "none") + || !strcasecmp(driver, "nosound") + || !strcasecmp(driver, "silence") + || !strcasecmp(driver, "silense") + || !strcasecmp(driver, "quiet") + || !strcasecmp(driver, "off")) { + strcpy(driver_name, "nosound"); + + /* don't change this without looking at nosound_thread() */ + CSoundFile::SetWaveConfig(11025, 8, 2, 0); + need_samples = 4410 * 2; + audio_output_channels = 2; + audio_output_bits = 8; + audio_sample_size = 2; + + CSoundFile::gpSndMixHook = NULL; + + audio_buffer = audio_buffer_; + + fprintf(stderr, "Starting up nosound device...\n"); + if (first_init) { + /* fake audio driver (wooo!) */ + audio_thread_running = 1; + audio_thread_paused = 1; + audio_thread_mutex = SDL_CreateMutex(); + if (!audio_thread_mutex) { + fprintf(stderr, "Couldn't create nosound device: %s\n", SDL_GetError()); + exit(1); + } + audio_thread = SDL_CreateThread(nosound_thread, 0); + if (!audio_thread) { + fprintf(stderr, "Couldn't create nosound device: %s\n", SDL_GetError()); + exit(1); + } + } + + song_lock_audio(); + + song_print_info_top("nosound"); + } else { + static SDL_AudioSpec desired, obtained; + + /* unknown audio driver- use SDL */ + if (strcasecmp(driver, "sdlauto")) { + put_env_var("SDL_AUDIODRIVER", driver); + } + + if (first_init && SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + fprintf(stderr, "Couldn't initialise audio: %s\n", SDL_GetError()); + driver = "nil"; + goto RETRY; + + } + desired.freq = audio_settings.sample_rate; + desired.format = (audio_settings.bits == 8) ? AUDIO_U8 : AUDIO_S16SYS; + desired.channels = audio_settings.channels; + desired.samples = audio_settings.buffer_size; + desired.callback = audio_callback; + desired.userdata = NULL; + + if (first_init) memset(&obtained, 0, sizeof(obtained)); + if (first_init && SDL_OpenAudio(&desired, &obtained) < 0) { + /* okay, FAKE it... */ + fprintf(stderr, "Couldn't initialise audio: %s\n", SDL_GetError()); + driver = "nil"; + goto RETRY; + } + + need_samples = obtained.samples; + + song_lock_audio(); + + /* format&255 is SDL specific... need bits */ + CSoundFile::SetWaveConfig(obtained.freq, + obtained.format & 255, + obtained.channels, 1); + audio_output_channels = obtained.channels; + audio_output_bits = obtained.format & 255; + audio_sample_size = audio_output_channels * (audio_output_bits/8); + + CSoundFile::gpSndMixHook = NULL; + + song_print_info_top(SDL_AudioDriverName(driver_name, + sizeof(driver_name))); + log_appendf(5, " %d Hz, %d bit, %s", obtained.freq, + obtained.format & 0xff, + obtained.channels == 1 ? "mono" : "stereo"); + log_appendf(5, " Buffer size: %d samples", obtained.samples); + } + + audio_buffer_size = need_samples; + if (audio_sample_size * need_samples < sizeof(audio_buffer_)) { + if (audio_buffer && audio_buffer != audio_buffer_) + free(audio_buffer); + audio_buffer = audio_buffer_; + } else { + if (audio_buffer && audio_buffer != audio_buffer_) + free(audio_buffer); + audio_buffer = (short int*)mem_alloc(audio_buffer_size + * audio_sample_size); + } + + memset(audio_buffer,0,audio_buffer_size * audio_sample_size); + + // barf out some more info on modplug's settings? + + log_append(0, 0, ""); + log_append(0, 0, ""); + + samples_played = 0; + + first_init = 0; + + song_unlock_audio(); + song_start_audio(); +} + +void song_init_eq(int do_reset) +{ + UINT pg[4]; + UINT pf[4]; + int i; + + for (i = 0; i < 4; i++) { + pg[i] = audio_settings.eq_gain[i]; + pf[i] = 120 + (((i*128) * audio_settings.eq_freq[i]) + * (CSoundFile::gdwMixingFreq / 128) / 1024); + } + + mp->SetEQGains(pg, 4, pf, do_reset ? TRUE : FALSE); +} + +void song_init_modplug(void) +{ + song_lock_audio(); + + CSoundFile::gpSndMixHook = NULL; + + CSoundFile::m_nMaxMixChannels = audio_settings.channel_limit; + CSoundFile::SetXBassParameters(audio_settings.xbass_amount, audio_settings.xbass_range); + CSoundFile::SetSurroundParameters(audio_settings.surround_depth, audio_settings.surround_delay); + CSoundFile::SetReverbParameters(audio_settings.reverb_depth, audio_settings.reverb_delay); + // the last param is the equalizer, which apparently isn't functional + CSoundFile::SetWaveConfigEx(audio_settings.surround, + !(audio_settings.oversampling), + audio_settings.reverb, + true, //only makes sense... audio_settings.hq_resampling, + audio_settings.xbass, + audio_settings.noise_reduction, + false); + CSoundFile::SetResamplingMode(audio_settings.interpolation_mode); + CSoundFile::gdwSoundSetup |= SNDMIX_EQ; + + // disable the S91 effect? (this doesn't make anything faster, it + // just sounds better with one woofer.) + song_set_surround(audio_settings.surround_effect); + + song_unlock_audio(); +} + +void song_initialise(void) +{ + CSoundFile::_midi_out_note = _schism_midi_out_note; + CSoundFile::_midi_out_raw = _schism_midi_out_raw; + CSoundFile::gpSndMixHook = NULL; + + mp = new CSoundFile; + + mp->Create(NULL, 0); + + + //song_stop(); <- song_new does this + song_set_linear_pitch_slides(1); + song_new(0); + + // hmm. + CSoundFile::gdwSoundSetup |= SNDMIX_MUTECHNMODE; +} diff --git a/schism/clippy.c b/schism/clippy.c new file mode 100644 index 000000000..2150b8f5f --- /dev/null +++ b/schism/clippy.c @@ -0,0 +1,451 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "clippy.h" +#include "util.h" + +#include + +static char *_current_selection = 0; +static char *_current_clipboard = 0; +static struct widget *_widget_owner[16] = {0}; + +#include + +static int has_sys_clip; +#if defined(WIN32) +static HWND SDL_Window, _hmem; +#elif defined(__QNXNTO__) +static unsigned short inputgroup; +#elif defined(XlibSpecificationRelease) +static Display *SDL_Display; +static Window SDL_Window; +static void (*lock_display)(void); +static void (*unlock_display)(void); +static Atom atom_sel; +static Atom atom_clip; +#define USE_X11 +static void __noop_v(void){}; +#endif + +#ifdef MACOSX +extern const char *macosx_clippy_get(void); +extern void macosx_clippy_put(const char *buf); +#endif + +static void _clippy_copy_to_sys(int do_sel) +{ + int i, j; + char *dst; +#if defined(__QNXNTO__) + PhClipboardHdr clheader = {Ph_CLIPBOARD_TYPE_TEXT, 0, NULL}; + char *tmp; + int *cldata; + int status; +#endif + +#if defined(WIN32) + j = strlen(_current_selection); +#else + if (has_sys_clip) { + /* convert to local */ + dst = malloc(strlen(_current_selection)); + for (i = j = 0; _current_selection[i]; i++) { + dst[j] = _current_selection[i]; + if (dst[j] != '\r') j++; + } + dst[j] = '\0'; + } else { + dst = 0; + } +#endif + +#if defined(USE_X11) + if (has_sys_clip) { + lock_display(); + if (do_sel) { + if (XGetSelectionOwner(SDL_Display, XA_PRIMARY) != SDL_Window) { + XSetSelectionOwner(SDL_Display, XA_PRIMARY, SDL_Window, CurrentTime); + } + XChangeProperty(SDL_Display, + DefaultRootWindow(SDL_Display), + XA_CUT_BUFFER1, XA_STRING, 8, + PropModeReplace, (unsigned char *)dst, j); + } else { + if (XGetSelectionOwner(SDL_Display, atom_clip) != SDL_Window) { + XSetSelectionOwner(SDL_Display, atom_clip, SDL_Window, CurrentTime); + } + XChangeProperty(SDL_Display, + DefaultRootWindow(SDL_Display), + XA_CUT_BUFFER0, XA_STRING, 8, + PropModeReplace, (unsigned char *)dst, j); + XChangeProperty(SDL_Display, + DefaultRootWindow(SDL_Display), + XA_CUT_BUFFER1, XA_STRING, 8, + PropModeReplace, (unsigned char *)dst, j); + } + unlock_display(); + } +#elif defined(WIN32) + if (!do_sel && OpenClipboard(SDL_Window)) { + _hmem = GlobalAlloc((GMEM_MOVEABLE|GMEM_DDESHARE), j); + if (_hmem) { + dst = (char *)GlobalLock(_hmem); + if (dst) { + memcpy(dst, _current_selection, j); + GlobalUnlock(_hmem); + EmptyClipboard(); + SetClipboardData(CF_TEXT, _hmem); + } + } + (void)CloseClipboard(); + _hmem = NULL; + } +#elif defined(__QNXNTO__) + if (!do_sel) { + tmp = (char *)malloc(j+4); + if (!tmp) { + cldata=(int*)tmp; + *cldata = Ph_CL_TEXT; + memcpy(tmp+4, dst, j); + clheader.data = tmp; +#if (NTO_VERSION < 620) + if (clheader.length > 65535) clheader.length=65535; +#endif + clheader.length = dstlen + 4; +#if (NTO_VERSION < 620) + PhClipboardCopy(inputgroup, 1, &clheader); +#else + PhClipboardWrite(inputgroup, 1, &clheader); +#endif + (void)free(tmp); + } + } +#elif defined(MACOSX) + /* XXX TODO */ +#endif + (void)free(dst); +} + +static void _synthetic_paste(const char *cbptr) +{ + struct key_event kk; + kk.mouse = 0; + while (cbptr && *cbptr) { + kk.sym = kk.orig_sym = 0; + kk.unicode = *cbptr; + kk.mod = 0; + kk.is_repeat = 0; + kk.state = 0; + handle_key(&kk); + kk.state = 1; + handle_key(&kk); + + cbptr++; + } +} + + +#if defined(USE_X11) +static int _x11_clip_filter(const SDL_Event *ev) +{ + XSelectionRequestEvent *req; + XEvent sevent; + Atom seln_type; + int seln_format; + unsigned long nbytes; + unsigned long overflow; + unsigned char *seln_data; + char *src, *tmp; + + if (ev->type != SDL_SYSWMEVENT) return 1; + if (ev->syswm.msg->event.xevent.type == SelectionNotify) { + sevent = ev->syswm.msg->event.xevent; + if (sevent.xselection.requestor == SDL_Window) { + lock_display(); + if (XGetWindowProperty(SDL_Display, SDL_Window, atom_sel, + 0, 9000, False, XA_STRING, &seln_type, + &seln_format, &nbytes, &overflow, + (unsigned char **)&src) == Success) { + if (seln_type == XA_STRING) { + if (_current_selection != _current_clipboard) { + free(_current_clipboard); + } + _current_clipboard = mem_alloc(nbytes+1); + memcpy(_current_clipboard, src, nbytes); + _current_clipboard[nbytes] = 0; + _synthetic_paste(_current_clipboard); + _widget_owner[CLIPPY_BUFFER] + = _widget_owner[CLIPPY_SELECT]; + } + XFree(src); + } + unlock_display(); + } + return 1; + } else if (ev->syswm.msg->event.xevent.type == PropertyNotify) { + sevent = ev->syswm.msg->event.xevent; + return 1; + + } else if (ev->syswm.msg->event.xevent.type != SelectionRequest) { + return 1; + } + + req = &ev->syswm.msg->event.xevent.xselectionrequest; + sevent.xselection.type = SelectionNotify; + sevent.xselection.display = req->display; + sevent.xselection.selection = req->selection; + sevent.xselection.target = None; + sevent.xselection.property = None; + sevent.xselection.requestor = req->requestor; + sevent.xselection.time = req->time; + if (XGetWindowProperty(SDL_Display, DefaultRootWindow(SDL_Display), + XA_CUT_BUFFER0, 0, 9000, False, req->target, + &sevent.xselection.target, &seln_format, + &nbytes, &overflow, &seln_data) == Success) { + if (sevent.xselection.target == req->target) { + if (sevent.xselection.target == XA_STRING) { + if (seln_data[nbytes-1] == '\0') nbytes--; + } + XChangeProperty(SDL_Display, req->requestor, req->property, + sevent.xselection.target, seln_format, PropModeReplace, + seln_data, nbytes); + sevent.xselection.property = req->property; + } + XFree(seln_data); + } + XSendEvent(SDL_Display,req->requestor,False,0,&sevent); + XSync(SDL_Display, False); + return 1; +} +#endif + + +void clippy_init(void) +{ + SDL_SysWMinfo info; + + has_sys_clip = 0; + SDL_VERSION(&info.version); + if (SDL_GetWMInfo(&info)) { +#if defined(USE_X11) + if (info.subsystem == SDL_SYSWM_X11) { + SDL_Display = info.info.x11.display; + SDL_Window = info.info.x11.window; + lock_display = info.info.x11.lock_func; + unlock_display = info.info.x11.unlock_func; + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + SDL_SetEventFilter(_x11_clip_filter); + has_sys_clip = 1; + + atom_sel = XInternAtom(SDL_Display, "SDL_SELECTION", False); + atom_clip = XInternAtom(SDL_Display, "CLIPBOARD", False); + } + if (!lock_display) lock_display = __noop_v; + if (!unlock_display) unlock_display = __noop_v; +#elif defined(WIN32) + has_sys_clip = 1; + SDL_Window = info.window; +#elif defined(__QNXNTO__) + has_sys_clip = 1; + inputgroup = PhInputGroup(NULL); +#endif + } +} + +static char *_internal_clippy_paste(int cb) +{ +#if defined(MACOSX) + char *src; +#elif defined(USE_X11) + Window owner; + int getme; + SDL_Event *ev; +#elif defined(WIN32) + char *tmp, *src; + int clen; +#elif defined(__QNXNTO__) + void *clhandle; + PhClipHeader *clheader; + int *cldata; +#endif + + if (has_sys_clip) { +#if defined(USE_X11) + if (cb == CLIPPY_SELECT) { + getme = XA_PRIMARY; + } else { + getme = atom_clip; + } + lock_display(); + owner = XGetSelectionOwner(SDL_Display, getme); + unlock_display(); + if (owner == None || owner == SDL_Window) { + /* fall through to default implementation */ + } else { + lock_display(); + XConvertSelection(SDL_Display, getme, XA_STRING, atom_sel, SDL_Window, + CurrentTime); + /* at some point in the near future, we'll get a SelectionNotify + see _x11_clip_filter for more details; + + because of this (otherwise) oddity, we take the selection immediately... + */ + unlock_display(); + return 0; + } +#else + if (cb == CLIPPY_BUFFER) { +#if defined(WIN32) + if (IsClipboardFormatAvailable(CF_TEXT) && OpenClipboard(SDL_Window)) { + _hmem = GetClipboardData(CF_TEXT); + if (_hmem) { + if (_current_selection != _current_clipboard) { + free(_current_clipboard); + } + _current_clipboard = NULL; + src = (char*)GlobalLock(_hmem); + if (src) { + clen = GlobalSize(_hmem); + if (clen > 0) { + _current_clipboard = mem_alloc(clen+1); + memcpy(_current_clipboard, src, clen); + _current_clipboard[clen] = '\0'; + } + GlobalUnlock(_hmem); + } + } + CloseClipboard(); + _hmem = NULL; + } +#elif defined(__QNXNTO__) + if (_current_selection != _current_clipboard) { + free(_current_clipboard); + } + _current_clipboard = NULL; +#if (NTO_VERSION < 620) + clhandle = PhClipboardPasteStart(inputgroup); + if (clhandle) { + clheader = PhClipboardPasteType(clhandle, + Ph_CLIPBOARD_TYPE_TEXT); + if (clheader) { + cldata = clheader->data; + if (clheader->length > 4 && *cldata == Ph_CL_TEXT) { + src = ((char *)clheader->data)+4; + clen = clheader->length - 4; + _current_clipboard = mem_alloc(clen+1); + memcpy(_current_clipboard, src, clen); + _current_clipboard[clen] = '\0'; + + } + PhClipboardPasteFinish(clhandle); + } + } +#else + /* argh! qnx */ + clheader = PhClipboardRead(inputgroup, Ph_CLIPBOARD_TYPE_TEXT); + if (clheader) { + cldata = clheader->data; + if (clheader->length > 4 && *cldata == Ph_CL_TEXT) { + src = ((char *)clheader->data)+4; + clen = clheader->length - 4; + _current_clipboard = mem_alloc(clen+1); + memcpy(_current_clipboard, src, clen); + _current_clipboard[clen] = '\0'; + } + } +#endif /* NTO version selector */ + /* okay, we either own the buffer, or it's a selection for folks without */ +#endif /* win32/qnx */ + } +#endif /* x11/others */ + /* fall through; the current window owns it */ + } + if (cb == CLIPPY_SELECT) return _current_selection; +#ifdef MACOSX + if (cb == CLIPPY_BUFFER) { + src = strdup(macosx_clippy_get()); + if (_current_clipboard != _current_selection) { + free(_current_clipboard); + } + _current_clipboard = src; + if (!src) return ""; + return _current_clipboard; + } +#else + if (cb == CLIPPY_BUFFER) return _current_clipboard; +#endif + return 0; +} + + +void clippy_paste(int cb) +{ + char *q; + q = _internal_clippy_paste(cb); + if (!q) return; + _synthetic_paste(q); +} + +void clippy_select(struct widget *w, char *addr, int len) +{ + int i; + + if (_current_selection != _current_clipboard) { + free(_current_selection); + } + if (!addr) { + _current_selection = 0; + _widget_owner[CLIPPY_SELECT] = 0; + } else { + for (i = 0; addr[i] && (len < 0 || i < len); i++); + _current_selection = mem_alloc(i+1); + memcpy(_current_selection, addr, i); + _current_selection[i] = 0; + _widget_owner[CLIPPY_SELECT] = w; + + /* update x11 Select (for xterms and stuff) */ + _clippy_copy_to_sys(1); + } +} +struct widget *clippy_owner(int cb) +{ + if (cb == CLIPPY_SELECT || cb == CLIPPY_BUFFER) + return _widget_owner[cb]; + return 0; +} + +void clippy_yank(void) +{ + if (_current_selection != _current_clipboard) { + free(_current_clipboard); + } + _current_clipboard = _current_selection; + _widget_owner[CLIPPY_BUFFER] = _widget_owner[CLIPPY_SELECT]; + + if (_current_selection && strlen(_current_selection) > 0) { + status_text_flash("Copied to selection buffer"); +#ifdef MACOSX + macosx_clippy_put(_current_clipboard); +#else + _clippy_copy_to_sys(0); +#endif + } +} diff --git a/schism/config-parser.c b/schism/config-parser.c new file mode 100644 index 000000000..a9c71f753 --- /dev/null +++ b/schism/config-parser.c @@ -0,0 +1,535 @@ +/* + * config-parser - a simple .ini-style configuration file parser + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "slurp.h" +#include "util.h" +#include "config-parser.h" + +/* --------------------------------------------------------------------------------------------------------- */ +/* some utilities for reading the config structure in memory */ + +static struct cfg_section *_get_section(cfg_file_t *cfg, const char *section_name, int add) +{ + struct cfg_section *section = cfg->sections, *prev = NULL; + + if (section_name == NULL) + return NULL; + + while (section) { + if (strcasecmp(section_name, section->name) == 0) + return section; + prev = section; + section = section->next; + } + if (add) { + section = calloc(1, sizeof(struct cfg_section)); + section->name = strdup(section_name); + if (prev) { + section->next = prev->next; + prev->next = section; + } else { + cfg->sections = section; + } + } + return section; +} + +static struct cfg_key *_get_key(struct cfg_section *section, const char *key_name, int add) +{ + struct cfg_key *key = section->keys, *prev = NULL; + + if (key_name == NULL) + return NULL; + + while (key) { + if (strcasecmp(key_name, key->name) == 0) + return key; + prev = key; + key = key->next; + } + if (add) { + key = calloc(1, sizeof(struct cfg_key)); + key->name = strdup(key_name); + if (prev) { + key->next = prev->next; + prev->next = key; + } else { + section->keys = key; + } + } + return key; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* configuration file parser */ + +/* skip past any comments and save them. return: length of comments */ +static size_t _parse_comments(const char *s, char **comments) +{ + const char *ptr = s, *prev; + char *new_comments, *tmp; + size_t len; + + do { + prev = ptr; + ptr += strspn(ptr, " \t\r\n"); + if (*ptr == '#') + ptr += strcspn(ptr, "\r\n"); + } while (*ptr && ptr != prev); + len = ptr - s; + if (len) { + /* save the comments */ + new_comments = (char *)mem_alloc(len + 1); + strncpy(new_comments, s, len); + new_comments[len] = 0; + if (*comments) { + /* already have some comments -- add to them */ + asprintf(&tmp, "%s%s", *comments, new_comments); + free(*comments); + free(new_comments); + *comments = tmp; + } else { + *comments = new_comments; + } + } + return len; +} + +/* parse a [section] line. return: 1 if all's well, 0 if it didn't work */ +static int _parse_section(cfg_file_t *cfg, char *line, struct cfg_section **cur_section, char *comments) +{ + char *tmp; + + if (line[0] != '[' || line[strlen(line) - 1] != ']') + return 0; + + memmove(line, line + 1, strlen(line)); + line[strlen(line) - 1] = 0; + *cur_section = _get_section(cfg, line, 1); + if (comments) { + if ((*cur_section)->comments) { + /* glue them together */ + asprintf(&tmp, "%s\n%s", comments, (*cur_section)->comments); + free((*cur_section)->comments); + free(comments); + (*cur_section)->comments = tmp; + } else { + (*cur_section)->comments = comments; + } + } + + return 1; +} + +/* parse a line as a key=value pair, and add it to the configuration. */ +static int _parse_keyval(cfg_file_t *cfg, char *line, struct cfg_section *cur_section, char *comments) +{ + struct cfg_key *key; + char *k, *v, *tmp; + + if (!strchr(line, '=')) { + fprintf(stderr, "%s: malformed line \"%s\"; ignoring\n", cfg->filename, line); + return 0; + } + if (cur_section == NULL) { + fprintf(stderr, "%s: missing section for line \"%s\"\n", cfg->filename, line); + return 0; + } + + str_break(line, '=', &k, &v); + trim_string(k); + trim_string(v); + + key = _get_key(cur_section, k, 1); + if (key->value) { + fprintf(stderr, "%s: duplicate key \"%s\" in section \"%s\"; overwriting\n", + cfg->filename, k, cur_section->name); + free(key->value); + } + key->value = str_unescape(v); + + free(k); + free(v); + + if (comments) { + if (key->comments) { + /* glue them together */ + asprintf(&tmp, "%s\n%s", comments, key->comments); + free(key->comments); + free(comments); + key->comments = tmp; + } else { + key->comments = comments; + } + } + + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* memory mismanagement */ + +static struct cfg_key *_free_key(struct cfg_key *key) +{ + struct cfg_key *next_key = key->next; + + free(key->name); + free(key->value); + if (key->comments) + free(key->comments); + free(key); + return next_key; +} + +static struct cfg_section *_free_section(struct cfg_section *section) +{ + struct cfg_section *next_section = section->next; + struct cfg_key *key = section->keys; + + free(section->name); + if (section->comments) + free(section->comments); + while (key) + key = _free_key(key); + free(section); + return next_section; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* public functions */ + +int cfg_read(cfg_file_t *cfg) +{ + struct stat buf; + slurp_t *t; + struct cfg_section *cur_section = NULL; + const char *pos; /* current position in the buffer */ + size_t len; /* how far away the end of the token is from the start */ + char *comments = NULL, *tmp; + + /* have to do our own stat, because we're going to fiddle with the size. (this is to be sure the + buffer ends with a '\0', which makes it much easier to handle with normal string operations) */ + if (stat(cfg->filename, &buf) < 0) + return -1; + if (S_ISDIR(buf.st_mode)) { + errno = EISDIR; + return -1; + } + if (buf.st_size <= 0) + return -1; + buf.st_size++; + t = slurp(cfg->filename, &buf, 0); + if (!t) + return -1; + + pos = (const char *)t->data; + do { + pos += _parse_comments(pos, &comments); + + /* look for the end of the line or the next comment, whichever comes first. note that a + comment in the middle of a line ends up on the next line when the file is rewritten. */ + len = strcspn(pos, "#\r\n"); + if (len) { + char *line; + line = (char *)mem_alloc(len + 1); + strncpy(line, pos, len); + line[len] = 0; + trim_string(line); + if (_parse_section(cfg, line, &cur_section, comments) + || _parse_keyval(cfg, line, cur_section, comments)) { + comments = NULL; + } else { + /* broken line: add it as a comment. */ + if (comments) { + asprintf(&tmp, "%s# %s\n", comments, line); + free(comments); + comments = tmp; + } else { + asprintf(&comments, "# %s\n", line); + } + } + free(line); + } + pos += len; + + /* skip the newline */ + if (*pos == '\r') + pos++; + if (*pos == '\n') + pos++; + } while (*pos); + cfg->eof_comments = comments; + + unslurp(t); + + return 0; +} + +int cfg_write(cfg_file_t *cfg) +{ + struct cfg_section *section; + struct cfg_key *key; + FILE *fp; + + if (!cfg->filename) { + /* FIXME | don't print a message here! this should be considered library code. + * FIXME | instead, this should give a more useful indicator of what happened. */ + fprintf(stderr, "bbq, cfg_write called with no filename\n"); + return -1; + } + + make_backup_file(cfg->filename); + + fp = fopen(cfg->filename, "w"); + if (!fp) { + /* FIXME: don't print a message here! */ + perror(cfg->filename); + return -1; + } + + /* I should be checking a lot more return values, but ... meh */ + + for (section = cfg->sections; section; section = section->next) { + if (section->comments) + fprintf(fp, "%s", section->comments); + fprintf(fp, "[%s]\n", section->name); + for (key = section->keys; key; key = key->next) { + if (key->comments) + fprintf(fp, "%s", key->comments); + /* TODO | if no keys in a section have defined values, + * TODO | comment out the section header as well. (this + * TODO | might be difficult since it's already been + * TODO | written to the file) */ + if (key->value) { + char *tmp = str_escape(key->value, 1); + fprintf(fp, "%s=%s\n", key->name, tmp); + free(tmp); + } else { + fprintf(fp, "# %s=(undefined)\n", key->name); + } + } + } + if (cfg->eof_comments) + fprintf(fp, "%s", cfg->eof_comments); + + fclose(fp); + + return 0; +} + +const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name, + char *value, int len, const char *def) +{ + struct cfg_section *section; + struct cfg_key *key; + const char *r = def; + + section = _get_section(cfg, section_name, 0); + if (section) { + key = _get_key(section, key_name, 0); + if (key && key->value) + r = key->value; + } + if (value && r) { + strncpy(value, r, len); + value[len] = 0; + } + return r; +} + +int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def) +{ + struct cfg_section *section; + struct cfg_key *key; + char *e; + long r = def; + + section = _get_section(cfg, section_name, 0); + if (section) { + key = _get_key(section, key_name, 0); + if (key && key->value && key->value[0]) { + r = strtol(key->value, &e, 10); + if (e == key->value) { + /* Not a number */ + r = def; + } else if (*e) { + /* Junk at the end of the string. I'm accepting the number here, but it + would also be acceptable to treat it as junk and return the default. */ + /* r = def; */ + } + } + } + return r; +} + +void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value) +{ + struct cfg_section *section; + struct cfg_key *key; + + if (section_name == NULL || key_name == NULL) + return; + section = _get_section(cfg, section_name, 1); + key = _get_key(section, key_name, 1); + if (key->value) + free(key->value); + if (value) + key->value = strdup(value); + else + key->value = NULL; +} + +void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value) +{ + struct cfg_section *section; + struct cfg_key *key; + + if (section_name == NULL || key_name == NULL) + return; + section = _get_section(cfg, section_name, 1); + key = _get_key(section, key_name, 1); + if (key->value) + free(key->value); + asprintf(&key->value, "%d", value); +} + +int cfg_init(cfg_file_t *cfg, const char *filename) +{ + memset(cfg, 0, sizeof(*cfg)); + cfg->filename = strdup(filename); + cfg->sections = NULL; + + return cfg_read(cfg); +} + +void cfg_free(cfg_file_t *cfg) +{ + struct cfg_section *section = cfg->sections; + + free(cfg->filename); + if (cfg->eof_comments) + free(cfg->eof_comments); + while (section) + section = _free_section(section); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +#ifdef TEST +int main(int argc, char **argv) +{ + cfg_file_t cfg; + char buf[64]; + + cfg_init(&cfg, "config"); + + /* + - test these functions with defined and undefined section/key names + - test all functions with completely broken values + (e.g. NULL shouldn't break it, it should give up, maybe print a warning, + and for the get functions, return the default value) + + const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name, + char *value, int len, const char *def); + int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def); + void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value); + void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value); + */ +/* +[ducks] +color = brown +count = 7 +weight = 64 lb. +*/ + printf("testing cfg_get_ functions...\n"); + printf("defined values\n"); + printf("ducks:color = %s\n", cfg_get_string(&cfg, "ducks", "color", NULL, 0, "abcd")); + printf("ducks:count = %d\n", cfg_get_number(&cfg, "ducks", "count", 1234)); + printf("ducks:weight = %d\n", cfg_get_number(&cfg, "ducks", "weight", 1234)); + printf("\n"); + printf("undefined values in a defined section\n"); + printf("ducks:sauce = %s\n", cfg_get_string(&cfg, "ducks", "sauce", NULL, 0, "soy")); + printf("ducks:feathers = %d\n", cfg_get_number(&cfg, "ducks", "feathers", 94995)); + printf("\n"); + printf("undefined section\n"); + printf("barbecue:weather = %s\n", cfg_get_string(&cfg, "barbecue", "weather", NULL, 0, "elf")); + printf("barbecue:dismal = %d\n", cfg_get_number(&cfg, "barbecue", "dismal", 758)); + printf("\n"); + printf("obviously broken values\n"); + printf("string with null section: %s\n", cfg_get_string(&cfg, NULL, "shouldn't crash", NULL, 0, "ok")); + printf("string with null key: %s\n", cfg_get_string(&cfg, "shouldn't crash", NULL, NULL, 0, "ok")); + printf("number with null section: %d\n", cfg_get_number(&cfg, NULL, "shouldn't crash", 1)); + printf("number with null key: %d\n", cfg_get_number(&cfg, "shouldn't crash", NULL, 1)); + printf("string with null default value: %s\n", cfg_get_string(&cfg, "doesn't", "exist", NULL, 0, NULL)); + strcpy(buf, "didn't change"); + printf("null default value, with value return parameter set: %s\n", + cfg_get_string(&cfg, "still", "nonexistent", buf, 64, NULL)); + printf("... and the buffer it returned: %s\n", buf); + strcpy(buf, "didn't change"); + printf("null default value on defined key with return parameter: %s\n", + cfg_get_string(&cfg, "ducks", "weight", buf, 64, NULL)); + printf("... and the buffer it returned: %s\n", buf); + printf("\n"); + printf("string boundary tests\n"); + cfg_set_string(&cfg, "test", "test", "abcdefghijklmnopqrstuvwxyz???broken"); + cfg_get_string(&cfg, "test", "test", buf, 26, "wtf"); + printf("26 characters using defined value: %s\n", buf); + cfg_get_string(&cfg, "fake section", "fake key", buf, 10, "1234567890???broken"); + printf("10 characters using default value: %s\n", buf); + cfg_get_string(&cfg, "fake section", "fake key", buf, 0, "huh?"); + printf("zero-length buffer (this should be an empty string) \"%s\"\n", buf); + printf("\n"); + printf("testing cfg_set_ functions...\n"); + printf("string in new section\n"); + cfg_set_string(&cfg, "toast", "is", "tasty"); + printf("string with new key in existing section\n"); + cfg_set_string(&cfg, "toast", "tastes", "good"); + printf("number in new section\n"); + cfg_set_number(&cfg, "cowboy", "hats", 3); + printf("number with new key in existing section\n"); + cfg_set_number(&cfg, "cowboy", "boots", 4); + printf("string with null section\n"); + cfg_set_string(&cfg, NULL, "shouldn't", "crash"); + printf("string with null key\n"); + cfg_set_string(&cfg, "shouldn't", NULL, "crash"); + printf("string with null value\n"); + cfg_set_string(&cfg, "shouldn't", "crash", NULL); + printf("re-reading that null string should return default value: %s\n", + cfg_get_string(&cfg, "shouldn't", "crash", NULL, 0, "it does")); + printf("number with null section\n"); + cfg_set_number(&cfg, NULL, "don't segfault", 42); + printf("number with null key\n"); + cfg_set_number(&cfg, "don't segfault", NULL, 42); + + cfg_dump(&cfg); + cfg_free(&cfg); + + return 0; +} +#endif /* TEST */ diff --git a/schism/config.c b/schism/config.c new file mode 100644 index 000000000..857a7461e --- /dev/null +++ b/schism/config.c @@ -0,0 +1,240 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" + +#include +#include + +#include "config-parser.h" +#include "dmoz.h" + +/* --------------------------------------------------------------------- */ +/* config settings */ + +char cfg_dir_modules[PATH_MAX + 1], cfg_dir_samples[PATH_MAX + 1], cfg_dir_instruments[PATH_MAX + 1], + cfg_dir_dotschism[PATH_MAX + 1], cfg_font[NAME_MAX + 1]; +char cfg_video_driver[65]; +int cfg_video_fullscreen = 0; + +/* --------------------------------------------------------------------- */ + +void cfg_init_dir(void) +{ +#if defined(__amigaos4__) + strcpy(cfg_dir_dotschism, "PROGDIR:"); +#else + char *home_dir, *ptr; + + home_dir = get_home_directory(); + ptr = dmoz_path_concat(home_dir, ".schism"); + strncpy(cfg_dir_dotschism, ptr, PATH_MAX); + cfg_dir_dotschism[PATH_MAX] = 0; + free(home_dir); + free(ptr); + + if (!is_directory(cfg_dir_dotschism)) { + printf("Creating directory %s\n", cfg_dir_dotschism); + printf("Schism Tracker uses this directory to store your settings.\n"); + if (mkdir(cfg_dir_dotschism, 0777) != 0) { + perror("Error creating directory"); + fprintf(stderr, "Everything will still work, but preferences will not be saved.\n"); + } + } +#endif +} + +/* --------------------------------------------------------------------- */ + +static const char palette_trans[64] = ".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; +static void cfg_load_palette(cfg_file_t *cfg) +{ + byte colors[48]; + int n; + char palette_text[49] = ""; + const char *ptr; + + palette_load_preset(cfg_get_number(cfg, "General", "palette", 2)); + + cfg_get_string(cfg, "General", "palette_cur", palette_text, 50, ""); + for (n = 0; n < 48; n++) { + if (palette_text[n] == '\0' || (ptr = strchr(palette_trans, palette_text[n])) == NULL) + return; + colors[n] = ptr - palette_trans; + } + memcpy(current_palette, colors, sizeof(current_palette)); +} + +static void cfg_save_palette(cfg_file_t *cfg) +{ + int n; + char palette_text[49] = ""; + + cfg_set_number(cfg, "General", "palette", current_palette_index); + + for (n = 0; n < 48; n++) { + /* tricky little hack: this is *massively* overstepping the array boundary */ + palette_text[n] = palette_trans[current_palette[0][n]]; + } + palette_text[48] = '\0'; + cfg_set_string(cfg, "General", "palette_cur", palette_text); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +void cfg_load(void) +{ + char *ptr; + int i; + cfg_file_t cfg; + + ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); + cfg_init(&cfg, ptr); + free(ptr); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_get_string(&cfg, "Video", "driver", cfg_video_driver, 64, ""); + if (cfg_get_number(&cfg, "Video", "fullscreen", 0)) { + cfg_video_fullscreen = 1; + } else { + cfg_video_fullscreen = 0; + } + + ptr = get_home_directory(); + cfg_get_string(&cfg, "Directories", "modules", cfg_dir_modules, PATH_MAX, ptr); + cfg_get_string(&cfg, "Directories", "samples", cfg_dir_samples, PATH_MAX, ptr); + cfg_get_string(&cfg, "Directories", "instruments", cfg_dir_instruments, PATH_MAX, ptr); + free(ptr); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_load_info(&cfg); + cfg_load_patedit(&cfg); + cfg_load_audio(&cfg); + cfg_load_midi(&cfg); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + if (cfg_get_number(&cfg, "General", "classic_mode", 0)) + status.flags |= CLASSIC_MODE; + else + status.flags &= ~CLASSIC_MODE; + if (cfg_get_number(&cfg, "General", "make_backups", 0)) + status.flags |= MAKE_BACKUPS; + else + status.flags &= ~MAKE_BACKUPS; + + i = cfg_get_number(&cfg, "General", "time_display", TIME_PLAY_ELAPSED); + /* default to play/elapsed for invalid values */ + if (i < 0 || i >= TIME_PLAYBACK) + i = TIME_PLAY_ELAPSED; + status.time_display = i; + + i = cfg_get_number(&cfg, "General", "vis_style", VIS_OSCILLOSCOPE); + /* default to oscilloscope for invalid values */ + if (i < 0 || i >= VIS_SENTINEL) + i = VIS_OSCILLOSCOPE; + status.vis_style = i; + + cfg_get_string(&cfg, "General", "font", cfg_font, NAME_MAX, "font.cfg"); + + cfg_load_palette(&cfg); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_free(&cfg); +} + +void cfg_midipage_save(void) +{ + char *ptr; + cfg_file_t cfg; + + ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); + cfg_init(&cfg, ptr); + free(ptr); + + cfg_save_midi(&cfg); + + cfg_write(&cfg); + cfg_free(&cfg); +} + +void cfg_save(void) +{ + char *ptr; + cfg_file_t cfg; + + ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); + cfg_init(&cfg, ptr); + free(ptr); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_set_string(&cfg, "Directories", "modules", cfg_dir_modules); + cfg_set_string(&cfg, "Directories", "samples", cfg_dir_samples); + cfg_set_string(&cfg, "Directories", "instruments", cfg_dir_instruments); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_save_info(&cfg); + cfg_save_patedit(&cfg); + cfg_save_audio(&cfg); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_set_number(&cfg, "General", "time_display", status.time_display); + cfg_set_number(&cfg, "General", "classic_mode", !!(status.flags & CLASSIC_MODE)); + cfg_set_number(&cfg, "General", "make_backups", !!(status.flags & MAKE_BACKUPS)); + + cfg_save_palette(&cfg); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + cfg_write(&cfg); + cfg_free(&cfg); +} + +void cfg_atexit_save(void) +{ + char *ptr; + cfg_file_t cfg; + + ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); + cfg_init(&cfg, ptr); + free(ptr); + + cfg_atexit_save_audio(&cfg); + + /* err... */ + cfg_set_string(&cfg, "Video", "driver", video_driver_name()); + cfg_set_number(&cfg, "Video", "fullscreen", !!(video_is_fullscreen())); + + /* hm... most of the time probably nothing's different, so saving the + config file here just serves to make the backup useless. maybe add a + 'dirty' flag to the config parser that checks if any settings are + actually *different* from those in the file? */ + + cfg_write(&cfg); + cfg_free(&cfg); +} diff --git a/schism/dialog.c b/schism/dialog.c new file mode 100644 index 000000000..74b6ec58c --- /dev/null +++ b/schism/dialog.c @@ -0,0 +1,398 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +/* ENSURE_DIALOG(optional return value) + * will emit a warning and cause the function to return + * if a dialog is not active. */ +#ifndef NDEBUG +# define ENSURE_DIALOG(q) do { if (!(status.dialog_type & DIALOG_BOX)) { \ + fprintf(stderr, "%s called with no dialog\n", __FUNCTION__);\ + q; \ + } \ +} while(0) +#else +# define ENSURE_DIALOG(q) +#endif + +/* --------------------------------------------------------------------- */ +/* I'm only supporting four dialogs open at a time. This is an absurdly + * large amount anyway, since the most that should ever be displayed is + * two (in the case of a custom dialog with a thumbbar, the value prompt + * dialog will be open on top of the other dialog). */ + +static struct dialog dialogs[4]; +static int num_dialogs = 0; + +/* --------------------------------------------------------------------- */ + +void dialog_draw(void) +{ + int n, d; + + for (d = 0; d < num_dialogs; d++) { + n = dialogs[d].total_widgets; + + /* draw the border and background */ + draw_box(dialogs[d].x, dialogs[d].y, + dialogs[d].x + dialogs[d].w - 1, + dialogs[d].y + dialogs[d].h - 1, BOX_THICK | BOX_OUTER | BOX_FLAT_LIGHT); + draw_fill_chars(dialogs[d].x + 1, dialogs[d].y + 1, + dialogs[d].x + dialogs[d].w - 2, dialogs[d].y + dialogs[d].h - 2, 2); + + /* then the rest of the stuff */ + if (dialogs[d].draw_const) dialogs[d].draw_const(); + + if (dialogs[d].text) + draw_text((const unsigned char *)dialogs[d].text, dialogs[d].text_x, 27, 0, 2); + + n = dialogs[d].total_widgets; + while (n) { + n--; + draw_widget(dialogs[d].widgets + n, n == dialogs[d].selected_widget); + } + } +} + +/* --------------------------------------------------------------------- */ + +void dialog_destroy(void) +{ + int d; + + if (num_dialogs == 0) + return; + + d = num_dialogs - 1; + + if (dialogs[d].type != DIALOG_CUSTOM) { + free(dialogs[d].text); + free(dialogs[d].widgets); + } + + num_dialogs--; + if (num_dialogs) { + d--; + widgets = dialogs[d].widgets; + selected_widget = &(dialogs[d].selected_widget); + total_widgets = &(dialogs[d].total_widgets); + status.dialog_type = dialogs[d].type; + } else { + widgets = ACTIVE_PAGE.widgets; + selected_widget = &(ACTIVE_PAGE.selected_widget); + total_widgets = &(ACTIVE_PAGE.total_widgets); + status.dialog_type = DIALOG_NONE; + } + + /* it's up to the calling function to redraw the page */ +} + +void dialog_destroy_all(void) +{ + while (num_dialogs) + dialog_destroy(); +} + +/* --------------------------------------------------------------------- */ +/* default callbacks */ + +void dialog_yes(void *data) +{ + void (*action) (void *); + + ENSURE_DIALOG(return); + + action = dialogs[num_dialogs - 1].action_yes; + if (!data) data = dialogs[num_dialogs - 1].data; + dialog_destroy(); + if (action) action(data); + status.flags |= NEED_UPDATE; +} + +void dialog_no(void *data) +{ + void (*action) (void *); + + ENSURE_DIALOG(return); + + action = dialogs[num_dialogs - 1].action_no; + if (!data) data = dialogs[num_dialogs - 1].data; + dialog_destroy(); + if (action) action(data); + status.flags |= NEED_UPDATE; +} + +void dialog_cancel(void *data) +{ + void (*action) (void *); + + ENSURE_DIALOG(return); + + action = dialogs[num_dialogs - 1].action_cancel; + if (!data) data = dialogs[num_dialogs - 1].data; + dialog_destroy(); + if (action) action(data); + status.flags |= NEED_UPDATE; +} + +void dialog_yes_NULL(void) +{ + dialog_yes(NULL); +} +void dialog_no_NULL(void) +{ + dialog_no(NULL); +} +void dialog_cancel_NULL(void) +{ + dialog_cancel(NULL); +} + +/* --------------------------------------------------------------------- */ + +int dialog_handle_key(struct key_event * k) +{ + struct dialog *d = dialogs + num_dialogs - 1; + int yes = 0; + + if (!k->state) return 0; + + ENSURE_DIALOG(return 0); + + switch (k->sym) { + case SDLK_y: + switch (status.dialog_type) { + case DIALOG_YES_NO: + case DIALOG_OK_CANCEL: + dialog_yes(d->data); + return 1; + default: + break; + } + break; + case SDLK_n: + switch (status.dialog_type) { + case DIALOG_YES_NO: + dialog_no(d->data); + return 1; + case DIALOG_OK_CANCEL: + dialog_cancel(d->data); + return 1; + default: + break; + } + break; + case SDLK_ESCAPE: + dialog_cancel(d->data); + return 1; + case SDLK_RETURN: + yes = 1; + break; + default: + break; + } + + if (d->handle_key && d->handle_key(k)) { + return 1; + } else if (yes) { + dialog_yes(d->data); + return 1; + } + return 0; +} + +/* --------------------------------------------------------------------- */ +/* these get called from dialog_create below */ + +static void dialog_create_ok(int textlen) +{ + int d = num_dialogs; + + /* make the dialog as wide as either the ok button or the text, + * whichever is more */ + dialogs[d].text_x = 40 - (textlen / 2); + if (textlen > 21) { + dialogs[d].x = dialogs[d].text_x - 2; + dialogs[d].w = textlen + 4; + } else { + dialogs[d].x = 26; + dialogs[d].w = 29; + } + dialogs[d].h = 8; + dialogs[d].y = 25; + + dialogs[d].widgets = (struct widget *)mem_alloc(sizeof(struct widget)); + dialogs[d].total_widgets = 1; + + create_button(dialogs[d].widgets + 0, 36, 30, 6, 0, 0, 0, 0, 0, dialog_yes_NULL, "OK", 3); +} + +static void dialog_create_ok_cancel(int textlen) +{ + int d = num_dialogs; + + /* the ok/cancel buttons (with the borders and all) are 21 chars, + * so if the text is shorter, it needs a bit of padding. */ + dialogs[d].text_x = 40 - (textlen / 2); + if (textlen > 21) { + dialogs[d].x = dialogs[d].text_x - 4; + dialogs[d].w = textlen + 8; + } else { + dialogs[d].x = 26; + dialogs[d].w = 29; + } + dialogs[d].h = 8; + dialogs[d].y = 25; + + dialogs[d].widgets = calloc(2, sizeof(struct widget)); + dialogs[d].total_widgets = 2; + + create_button(dialogs[d].widgets + 0, 31, 30, 6, 0, 0, 1, 1, 1, dialog_yes_NULL, "OK", 3); + create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_cancel_NULL, "Cancel", 1); +} + +static void dialog_create_yes_no(int textlen) +{ + int d = num_dialogs; + + dialogs[d].text_x = 40 - (textlen / 2); + if (textlen > 21) { + dialogs[d].x = dialogs[d].text_x - 4; + dialogs[d].w = textlen + 8; + } else { + dialogs[d].x = 26; + dialogs[d].w = 29; + } + dialogs[d].h = 8; + dialogs[d].y = 25; + + dialogs[d].widgets = calloc(2, sizeof(struct widget)); + dialogs[d].total_widgets = 2; + + create_button(dialogs[d].widgets + 0, 30, 30, 7, 0, 0, 1, 1, 1, dialog_yes_NULL, "Yes", 3); + create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_no_NULL, "No", 3); +} + +/* --------------------------------------------------------------------- */ +/* type can be DIALOG_OK, DIALOG_OK_CANCEL, or DIALOG_YES_NO + * default_widget: 0 = ok/yes, 1 = cancel/no */ + +struct dialog *dialog_create(int type, const char *text, void (*action_yes) (void *data), + void (*action_no) (void *data), int default_widget, void *data) +{ + int textlen = strlen(text); + int d = num_dialogs; + +#ifndef NDEBUG + if ((type & DIALOG_BOX) == 0) { + fprintf(stderr, "dialog_create called with bogus dialog type %d\n", type); + return 0; + } +#endif + + /* FIXME | hmm... a menu should probably be hiding itself when a widget gets selected. */ + if (status.dialog_type & DIALOG_MENU) + menu_hide(); + + dialogs[d].text = strdup(text); + dialogs[d].data = data; + dialogs[d].action_yes = action_yes; + dialogs[d].action_no = action_no; + dialogs[d].action_cancel = NULL; /* ??? */ + dialogs[d].selected_widget = default_widget; + dialogs[d].draw_const = NULL; + dialogs[d].handle_key = NULL; + + switch (type) { + case DIALOG_OK: + dialog_create_ok(textlen); + break; + case DIALOG_OK_CANCEL: + dialog_create_ok_cancel(textlen); + break; + case DIALOG_YES_NO: + dialog_create_yes_no(textlen); + break; + default: +#ifndef NDEBUG + fprintf(stderr, "this man should not be seen\n"); +#endif + type = DIALOG_OK_CANCEL; + dialog_create_ok_cancel(textlen); + break; + } + + dialogs[d].type = type; + widgets = dialogs[d].widgets; + selected_widget = &(dialogs[d].selected_widget); + total_widgets = &(dialogs[d].total_widgets); + + num_dialogs++; + + status.dialog_type = type; + status.flags |= NEED_UPDATE; + return &dialogs[d]; +} + +/* --------------------------------------------------------------------- */ +/* this will probably die painfully if two threads try to make custom dialogs at the same time */ + +struct dialog *dialog_create_custom(int x, int y, int w, int h, struct widget *dialog_widgets, + int dialog_total_widgets, int dialog_selected_widget, + void (*draw_const) (void), void *data) +{ + struct dialog *d = dialogs + num_dialogs; + num_dialogs++; + + d->type = DIALOG_CUSTOM; + d->x = x; + d->y = y; + d->w = w; + d->h = h; + d->widgets = dialog_widgets; + d->selected_widget = dialog_selected_widget; + d->total_widgets = dialog_total_widgets; + d->draw_const = draw_const; + + d->text = NULL; + d->data = data; + d->action_yes = NULL; + d->action_no = NULL; + d->action_cancel = NULL; + d->handle_key = NULL; + + status.dialog_type = DIALOG_CUSTOM; + widgets = d->widgets; + selected_widget = &(d->selected_widget); + total_widgets = &(d->total_widgets); + + status.flags |= NEED_UPDATE; + + return d; +} diff --git a/schism/diskwriter.cc b/schism/diskwriter.cc new file mode 100644 index 000000000..6e220c459 --- /dev/null +++ b/schism/diskwriter.cc @@ -0,0 +1,498 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "util.h" +#include "song.h" +#include "mplink.h" +#include "dmoz.h" + +#include "diskwriter.h" + +#include +#include +#include + +/* + +This is a hack: + +I don't actually know why times3 seems to make linked samples sound pretty close; +It might not work with all songs- just the ones I'm playing with. But this'll do +for now... + +*/ +static void _dw_times_3(int *buffer, unsigned long samples, unsigned long channels) +{ + unsigned long i; + for (i = 0; i < samples*channels; i++) { + buffer[i] = buffer[i] * 3; + } +} + + + +static unsigned char diskbuf[32768]; +static diskwriter_driver_t *dw = NULL; +static FILE *fp = NULL; + +static unsigned char *mbuf = NULL; +static off_t mbuf_size = 0; +static unsigned int mbuf_len = 0; + +static int fini_bindme = -1; +static int fini_sampno = -1; +static int fini_patno = -1; + +static int fp_ok; +static unsigned long current_song_len; + +static char *dw_rename_from = NULL; +static char *dw_rename_to = NULL; + +/* disk writer */ +static void _wl(diskwriter_driver_t *x, off_t pos) +{ + if (!fp) { + fp_ok = 0; + return; + } + (void)fseek(fp,pos,SEEK_SET); + if (ferror(fp)) fp_ok = 0; + x->pos = pos; +} +static void _we(diskwriter_driver_t *x) +{ + fp_ok = 0; +} +static void _ww(diskwriter_driver_t *x, const unsigned char *buf, unsigned int len) +{ + if (!len) return; + if (!fp) { + fp_ok = 0; + return; + } + (void)fwrite(buf, len, 1, fp); + if (ferror(fp)) fp_ok = 0; + x->pos = ftell(fp); +} + +/* memory writer */ +static void _mw(diskwriter_driver_t *x, const unsigned char *buf, unsigned int len) +{ + char *tmp; + unsigned int nl; + + if (!len) return; + if (!fp_ok) return; + + if (x->pos + len >= mbuf_size) { + tmp = (char*)realloc(mbuf, nl = (x->pos + len + 65536)); + if (!tmp) { + (void)free(mbuf); + mbuf = NULL; + mbuf_size = 0; + fp_ok = 0; + return; + } + mbuf = (unsigned char *)tmp; + memset(mbuf+mbuf_size, 0, nl - mbuf_size); + mbuf_size = nl; + } + memcpy(mbuf+x->pos, buf, len); + x->pos += len; + if (x->pos >= mbuf_len) { + mbuf_len = x->pos; + } +} +static void _ml(diskwriter_driver_t *x, off_t pos) +{ + if (!fp) { + fp_ok = 0; + return; + } + if (pos < 0) pos = 0; + x->pos = pos; +} + + +int diskwriter_start_nodriver(diskwriter_driver_t *f) +{ + if (fp) return 0; + + if (dw == NULL) { + if (f->m || f->g) { + song_stop_audio(); + song_stop(); + } + + dw = f; + + dw->rate = diskwriter_output_rate; + dw->bits = diskwriter_output_bits; + dw->channels = diskwriter_output_channels > 1 ? 2 : 1; + + if (dw->s) { + dw->s(dw); + return 1; + } + } + + fp_ok = 1; + // quick calculate current song length + if (dw->m || dw->g) { + current_song_len = song_get_length(); + } + + status.flags |= DISKWRITER_ACTIVE; + return 1; +} + +extern "C" { +static diskwriter_driver_t _samplewriter = { + "Sample", + NULL, + NULL, + NULL, /* no midi data */ + NULL, + NULL,NULL,NULL, + NULL, + NULL, + 44100,16,2,1, + 0, +}; +}; + +int diskwriter_writeout_sample(int sampno, int patno, int dobind) +{ + if (sampno < 0 || sampno >= MAX_SAMPLES) return 0; + + song_stop_audio(); + song_stop(); + + dw = &_samplewriter; + dw->rate = diskwriter_output_rate; + dw->bits = diskwriter_output_bits; + dw->pos = 0; + + fp_ok = 1; + current_song_len = song_get_length(); + + /* fix these up; libmodplug can't actually support other sized samples (Yet) */ + if (dw->bits < 16) dw->bits = 8; + else dw->bits = 16; + if (dw->channels > 2) dw->channels = 2; + + dw->m = (void(*)(diskwriter_driver_t*,unsigned char *,unsigned int))_mw; + + if (patno >= 0) { + song_start_once(); + + /* okay, restrict libmodplug to only play a single pattern */ + mp->LoopPattern(patno, 0); + mp->m_nRepeatCount = 2; /* er... */ + } + + CSoundFile::SetWaveConfig(dw->rate*=2, dw->bits, dw->channels, 1); + mp->InitPlayer(1); + + //CSoundFile::gpSndMixHook = _dw_times_3; + CSoundFile::gdwSoundSetup |= SNDMIX_DIRECTTODISK; + status.flags |= (DISKWRITER_ACTIVE | DISKWRITER_ACTIVE_PATTERN); + + mbuf = NULL; + mbuf_size = 0; + mbuf_len = 0; + + dw->e = _we; + + dw->o = _mw; /* completeness.... */ + dw->l = _ml; + + if (!fp_ok) { + (void)diskwriter_finish(); + CSoundFile::gpSndMixHook = NULL; + return 0; + } + + log_appendf(2, "Writing to sample %d", sampno); + fini_sampno = sampno; + fini_bindme = dobind; + fini_patno = patno; + + return 1; +} + +int diskwriter_writeout(const char *file, diskwriter_driver_t *f) +{ + /* f is "simplified */ + memset(f, 0, sizeof(diskwriter_driver_t)); + f->name = (const char *)"Simple"; + return diskwriter_start(file, f); +} + +int diskwriter_start(const char *file, diskwriter_driver_t *f) +{ + char *str; + char *pq; + int i, fd; + + put_env_var("DISKWRITER_FILE", file); + + if (!diskwriter_start_nodriver(f)) return 0; + + if (dw->m || dw->g) { +// CSoundFile::SetWaveConfigEx(); + song_start_once(); + CSoundFile::SetWaveConfig(dw->rate, dw->bits, + diskwriter_output_channels, 1); + CSoundFile::gdwSoundSetup |= SNDMIX_DIRECTTODISK; + } + + /* ERR: should write to temporary */ + dw_rename_to = (char *)mem_alloc(strlen(file)+1); + strcpy(dw_rename_to, file); + + str = (char*)mem_alloc(strlen(file)+16); + strcpy(str, file); + pq = strrchr(str, '.'); + if (!pq) pq = strrchr(str, '\\'); + if (!pq) pq = strrchr(str, '/'); + if (!pq) pq = str; + if (*pq) pq++; + for (i = 0;; i++) { + sprintf(pq, "%x", i); + fd = open(str, O_CREAT|O_EXCL|O_RDWR, 0666); + if (fd == -1 && errno == EEXIST) continue; + fp = fopen(str, "wb"); + if (!fp) { + (void)unlink(str); + (void)free(str); + (void)free(dw_rename_to); + dw_rename_from = dw_rename_to = NULL; + (void)diskwriter_finish(); + return 0; + } + (void)close(fd); + break; /* got file! */ + } + + dw_rename_from = str; + + dw->o = _ww; + dw->e = _we; + dw->l = _wl; + + if (dw->p) dw->p(dw); + + if (!fp_ok) { + (void)diskwriter_finish(); + return 0; + } + + log_appendf(2, "Opening %s for writing", str); + status.flags |= DISKWRITER_ACTIVE; + + return 1; +} +extern unsigned long samples_played; /* mplink */ +int diskwriter_sync(void) +{ + Uint32 *le32; + Uint16 *le16; + int n, i; + + if (!dw || (!fp && fini_sampno == -1)) return 0; /* no writer running */ + if (!fp_ok) return -1; + if (!dw->m && !dw->g) { + if (dw->x) dw->x(dw); + if (!fp_ok) return -1; + return 0; + } + + n = (int)(((double)song_get_current_time() * 100.0) / current_song_len); + diskwriter_dialog_progress(n); + + // add status dialog + n = mp->Read(diskbuf, sizeof(diskbuf)); + samples_played += n; + n *= dw->channels; + if (dw->bits > 8 && !mbuf && +#if WORDS_BIGENDIAN +dw->output_le +#else +!dw->output_le +#endif + ) { + if (dw->bits <= 16) { + le16 = (Uint16*)diskbuf; + for (i = 0; i < n; i++, le16++) { + (*le16) = bswap_16((*le16)); + } + } else if (dw->bits <= 32) { + le32 = (Uint32*)diskbuf; + for (i = 0; i < n; i++, le32++) { + (*le32) = bswap_32((*le32)); + } + } + } + n *= (dw->bits / 8); + + if (dw->m) dw->m(dw, diskbuf, n); + + if (!fp_ok) return -1; + if (mp->m_dwSongFlags & SONG_ENDREACHED) { + if (dw->x) dw->x(dw); + if (!fp_ok) return -1; + return 0; + } + return 1; +} +int diskwriter_finish(void) +{ + char *zed; + int r; + + if (!dw || (!fp && fini_sampno == -1)) return -1; /* no writer running */ + + if (fp) { + if (fp_ok) fflush(fp); + if (ferror(fp)) fp_ok = 0; + fclose(fp); fp = NULL; + } + + if (dw->m || dw->g) { + song_stop(); + } + status.flags &= ~(DISKWRITER_ACTIVE|DISKWRITER_ACTIVE_PATTERN); + + if (mbuf) { + if (fp_ok && fini_sampno > -1) { + /* okay, fixup sample */ + MODINSTRUMENT *p = &mp->Ins[fini_sampno]; + zed = mp->m_szNames[ fini_sampno ]; + if (p->pSample) { + song_sample_free(p->pSample); + } else { + if (fini_patno > -1) { + sprintf(zed, "Pattern # %d", fini_patno); + } else { + strcpy(zed, "Entire Song"); + } + p->nGlobalVol = 64; + p->nVolume = 256; + } + p->pSample = song_sample_allocate(mbuf_len); + memcpy(p->pSample, mbuf, mbuf_len); + p->nC4Speed = dw->rate; + if (dw->bits >= 16) { + p->uFlags |= SAMP_16_BIT; + mbuf_len /= 2; + } else { + p->uFlags &= ~SAMP_16_BIT; + } + if (dw->channels >= 2) { + p->uFlags |= SAMP_STEREO; + mbuf_len /= 2; + } else { + p->uFlags &= ~SAMP_STEREO; + } + p->nLength = mbuf_len; + if (p->nLength < p->nLoopStart) p->nLoopStart = p->nLength; + if (p->nLength < p->nLoopEnd) p->nLoopEnd = p->nLength; + if (p->nLength < p->nSustainStart) p->nSustainStart = p->nLength; + if (p->nLength < p->nSustainEnd) p->nSustainEnd = p->nLength; + if (fini_bindme) { + /* that should do it */ + zed[23] = 0xFF; + zed[24] = ((unsigned char)fini_patno); + } + } + (void)free(mbuf); + mbuf = NULL; + mbuf_size = 0; + mbuf_len = 0; + fini_sampno = -1; + fini_patno = -1; + fini_bindme = -1; + } + CSoundFile::gpSndMixHook = NULL; + + if (dw->m || dw->g) { + song_init_audio(0); + } + + dw = NULL; /* all done! */ + diskwriter_dialog_finished(); + + if (dw_rename_from && dw_rename_to) { + if (fp_ok) { + if (!rename_file(dw_rename_from, dw_rename_to)) + r = -1; + else + r = 0; + (void)unlink(dw_rename_from); + } else { + r = -1; + } + free(dw_rename_from); + free(dw_rename_to); + dw_rename_from = dw_rename_to = NULL; + } else { + r = 0; + } + + if (r == 0) return 1; + return 0; +} + +int _diskwriter_writemidi(unsigned char *data, unsigned int len, unsigned int delay) +{ + if (!dw || !fp) return 0; + if (dw->g) dw->g(dw,data,len, delay); + return 1; +} + + +extern "C" { +extern unsigned int diskwriter_output_rate, diskwriter_output_bits, + diskwriter_output_channels; +extern diskwriter_driver_t wavewriter; +extern diskwriter_driver_t it214writer; +extern diskwriter_driver_t s3mwriter; +extern diskwriter_driver_t xmwriter; +extern diskwriter_driver_t modwriter; +}; + +unsigned int diskwriter_output_rate = 48000; +unsigned int diskwriter_output_bits = 16; +unsigned int diskwriter_output_channels = 2; +diskwriter_driver_t *diskwriter_drivers[] = { + &it214writer, + &xmwriter, + &s3mwriter, + &modwriter, + &wavewriter, + NULL, +}; + diff --git a/schism/diskwriter_dialog.c b/schism/diskwriter_dialog.c new file mode 100644 index 000000000..e710eb7a2 --- /dev/null +++ b/schism/diskwriter_dialog.c @@ -0,0 +1,91 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include "diskwriter.h" + +/* -------------------------------------------------------------------------------- */ + +static struct widget _diskwriter_widgets[1]; +static struct dialog *dg = NULL; +static int dg_init = 0; + +static unsigned int dg_progress = 0; + +static void _diskwriter_draw_const(void) +{ + int i, x; + + if (status.flags & DISKWRITER_ACTIVE_PATTERN) { + draw_text((const unsigned char *)"Updating sample...", 30, 27, 0, 2); + draw_text((const unsigned char *)"Please wait...", 34, 33, 0, 2); /* no cancel button */ + } else { + draw_text((const unsigned char *)"Writing song to disk...", 28, 27, 0, 2); + } + draw_fill_chars(24,30,55,30,0); + + x = (int)((((float)dg_progress) / 100.0)*64.0); + draw_vu_meter(24, 30, 32, x, 4, 4); + draw_box(23, 29, 56, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} +static void _diskwriter_cancel(UNUSED void*ignored) +{ + if (status.flags & DISKWRITER_ACTIVE_PATTERN) return; /* err? */ + if (dg != NULL) { + diskwriter_finish(); /* eek! */ + dg = NULL; + } +} +void diskwriter_dialog_progress(unsigned int perc) +{ + if (dg_init == 0) { + dg_init = 1; + create_button(_diskwriter_widgets+0, 36, 33, 6, + 0,0,0,0,0, dialog_cancel_NULL, "Cancel", 1); + } + if (!dg) { + dg = dialog_create_custom(22,25,36,11, + _diskwriter_widgets, + (status.flags & DISKWRITER_ACTIVE_PATTERN ? 0 : 1), + 0, + _diskwriter_draw_const, + NULL); + if (!(status.flags & DISKWRITER_ACTIVE_PATTERN)) { + dg->action_yes = _diskwriter_cancel; + dg->action_cancel = _diskwriter_cancel; + } + } + + dg_progress = perc; +} +void diskwriter_dialog_finished(void) +{ + if (dg) { + dg = NULL; + if (status.dialog_type != DIALOG_NONE) + dialog_cancel_NULL(); + dialog_destroy_all(); /* poop */ + } + dg_progress = 100; +} diff --git a/schism/dmoz.c b/schism/dmoz.c new file mode 100644 index 000000000..a1845a55e --- /dev/null +++ b/schism/dmoz.c @@ -0,0 +1,696 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* No, this has nothing whatsoever to do with dmoz.org, except for the 'directory' part. :) */ + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "dmoz.h" +#include "slurp.h" +#include "util.h" + +#include "fmt.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __amigaos4__ +# include +#endif + +#ifdef WIN32 +#include +#include +#endif + +/* --------------------------------------------------------------------------------------------------------- */ +/* constants */ + +/* note: this has do be split up like this; otherwise it gets read as '\x9ad' which is the Wrong Thing. */ +#define TITLE_DIRECTORY "\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" \ + "Directory\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" +#define TITLE_LIBRARY "\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9aLibrary\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" +#define DESCR_DIRECTORY "Directory" +#define DESCR_UNKNOWN "Unknown sample format" + +/* memory allocation: how many files/dirs are allocated at a time */ +#define FILE_BLOCK_SIZE 256 +#define DIR_BLOCK_SIZE 32 + +/* --------------------------------------------------------------------------------------------------------- */ +/* file format tables */ + +/* The type list should be arranged so that the types with the most specific checks are first, and the vaguest +ones are down at the bottom. This is to ensure that some lousy type doesn't "steal" files of a different type. +For example, if IT came before S3M, any S3M file starting with "IMPM" (which is unlikely, but possible, and in +fact quite easy to do) would be picked up by the IT check. In fact, Impulse Tracker itself has this problem. + +Also, a format that might need to do a lot of work to tell if a file is of the right type (i.e. the MDL format +practically requires reading through the entire file to find the title block) should be down farther on the +list for performance purposes. + +Don't rearrange the formats that are already here unless you have a VERY good reason to do so. I spent a good +3-4 hours reading all the format specifications, testing files, checking notes, and trying to break the +program by giving it weird files, and I'm pretty sure that this ordering won't fail unless you really try +doing weird stuff like hacking the files, but then you're just asking for trouble. ;) */ + +#define READ_INFO(t) fmt_##t##_read_info + +static const fmt_read_info_func read_info_funcs[] = { + /* 669 has lots of checks to compensate for a really crappy 2-byte magic. (It's even a common English + word ffs... "if"?!) Still, it's better than STM. The only reason this is first is because the position + of the SCRM magic lies within the 669 message field, and the 669 check is much more complex (and thus + more likely to be right). */ + READ_INFO(669), + + /* Since so many programs have added noncompatible extensions to the mod format, there are about 30 + strings to compare against for the magic. Also, there are special cases for WOW files, which even + share the same magic as plain ProTracker, but are quite different; there are some really nasty + heuristics to detect these... ugh, ugh, ugh. However, it has to be above the formats with the magic + at the beginning... */ + READ_INFO(mod), + + /* S3M needs to be before a lot of stuff. */ + READ_INFO(s3m), + /* FAR and S3M have different magic in the same place, so it doesn't really matter which one goes + where. I just have S3M first since it's a more common format. */ + READ_INFO(far), + + /* These next formats have their magic at the beginning of the data, so none of them can possibly + conflict with other ones. I've organized them pretty much in order of popularity. */ + READ_INFO(xm), + /* There's a bit of weirdness with some IT files (including "Acid Dreams" by Legend, a demo song for + version 2.08) requiring two different checks and three memcmp's. However, since it's so widely used + 'cuz Impulse Tracker owns, I'm putting it up here anyway. */ + READ_INFO(it), + READ_INFO(mt2), + READ_INFO(mtm), + READ_INFO(ntk), +#ifdef USE_NON_TRACKED_TYPES + READ_INFO(sid), /* 6581 0wnz j00! */ +#endif + READ_INFO(mdl), + + READ_INFO(iti), + READ_INFO(its), + READ_INFO(au), + READ_INFO(aiff), + + READ_INFO(ult), + READ_INFO(liq), + /* I have NEVER seen any of these next three */ + READ_INFO(ams), + READ_INFO(f2r), + READ_INFO(dtm), /* not sure about the placement here */ + + READ_INFO(imf), /* not sure here either */ + + /* bleh */ +#if defined(USE_NON_TRACKED_TYPES) && defined(HAVE_VORBIS) + READ_INFO(ogg), +#endif + + /* STM seems to have a case insensitive magic string with several possible values, and only one byte + is guaranteed to be the same in the whole file... yeagh. */ + READ_INFO(stm), + + READ_INFO(wav), + /* An ID3 tag could actually be anywhere in an MP3 file, and there's no guarantee that it even exists + at all. I might move this toward the top if I can figure out how to identify an MP3 more precisely. */ +#ifdef USE_NON_TRACKED_TYPES + READ_INFO(mp3), +#endif + + NULL /* This needs to be at the bottom of the list! */ +}; + +#undef READ_INFO + +/* --------------------------------------------------------------------------------------------------------- */ +/* path string hacking */ + +/* This function should: + - strip out any parent directory references ("/sheep/../goat" => "/goat") + - switch slashes to backslashes for MS systems ("c:/winnt" => "c:\\winnt") + - condense multiple slashes into one ("/sheep//goat" => "/sheep/goat") + - remove any trailing slashes +It shouldn't really check the path to see if it exists, but it does. :P +return: the new path, or NULL if there was a problem (i.e. it wasn't recognisable as a path) */ +char *dmoz_path_normal(const char *path) +{ + char buf[PATH_MAX], *ret; + + /* FIXME: don't use realpath! */ + if (realpath(path, buf) == NULL) + return NULL; + + ret = calloc(strlen(buf) + 2, sizeof(char)); + strcpy(ret, buf); + return ret; +} + +char *dmoz_path_concat(const char *a, const char *b) +{ + return dmoz_path_concat_len(a, b, strlen(a), strlen(b)); +} + +char *dmoz_path_concat_len(const char *a, const char *b, int alen, int blen) +{ + char *ret = mem_alloc(alen + blen + 2); + + if (alen) { + char last = a[alen - 1]; + + strcpy(ret, a); + + /* need a slash? */ +#if defined(__amigaos4__) + if (last != ':' && last != '/') + strcat(ret, "/"); +#else + if (last != DIR_SEPARATOR) + strcat(ret, DIR_SEPARATOR_STR); +#endif + } + strcat(ret, b); + + return ret; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* memory management */ + +static void allocate_more_files(dmoz_filelist_t *flist) +{ + if (flist->alloc_size == 0) { + flist->alloc_size = FILE_BLOCK_SIZE; + flist->files = (dmoz_file_t **)mem_alloc(FILE_BLOCK_SIZE * sizeof(dmoz_file_t *)); + } else { + flist->alloc_size *= 2; + flist->files = (dmoz_file_t **)mem_realloc(flist->files, flist->alloc_size * sizeof(dmoz_filelist_t *)); + } +} + +static void allocate_more_dirs(dmoz_dirlist_t *dlist) +{ + if (dlist->alloc_size == 0) { + dlist->alloc_size = DIR_BLOCK_SIZE; + dlist->dirs = (dmoz_dir_t **)mem_alloc(DIR_BLOCK_SIZE * sizeof(dmoz_dir_t *)); + } else { + dlist->alloc_size *= 2; + dlist->dirs = (dmoz_dir_t **)mem_realloc(dlist->dirs, dlist->alloc_size * sizeof(dmoz_dir_t *)); + } +} + +static void free_file(dmoz_file_t *file) +{ + if (!file) + return; + if (file->smp_filename != file->base && file->smp_filename != file->title) { + free(file->smp_filename); + } + free(file->path); + free(file->base); + if (file->type & TYPE_EXT_DATA_MASK) { + if (file->artist) + free(file->artist); + free(file->title); + /* if (file->sample) { + if (file->sample->data) + song_sample_free(file->sample->data); + free(file->sample); + } */ + } + free(file); +} + +static void free_dir(dmoz_dir_t *dir) +{ + if (!dir) + return; + free(dir->path); + free(dir->base); + free(dir); +} + +void dmoz_free(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) +{ + int n; + + if (flist) { + for (n = 0; n < flist->num_files; n++) + free_file(flist->files[n]); + free(flist->files); + flist->files = NULL; + flist->num_files = 0; + flist->alloc_size = 0; + } + + if (dlist) { + for (n = 0; n < dlist->num_dirs; n++) + free_dir(dlist->dirs[n]); + free(dlist->dirs); + dlist->dirs = NULL; + dlist->num_dirs = 0; + dlist->alloc_size = 0; + } +} + +static int current_dmoz_file = 0; +static dmoz_filelist_t *current_dmoz_filelist = 0; +static int (*current_dmoz_filter)(dmoz_file_t *) = 0; +static int *current_dmoz_file_pointer = 0; +static void (*dmoz_worker_onmove)(void); +int dmoz_worker(void) +{ + dmoz_file_t *nf; + + if (!current_dmoz_filelist || !current_dmoz_filter) return 0; + if (current_dmoz_file >= current_dmoz_filelist->num_files) { + current_dmoz_filelist = 0; + current_dmoz_filter = 0; + return 0; + } + + if (!current_dmoz_filter(current_dmoz_filelist->files[ current_dmoz_file ])) { + if (current_dmoz_filelist->num_files == current_dmoz_file+1) { + current_dmoz_filelist->num_files--; + current_dmoz_filelist = 0; + current_dmoz_filter = 0; + return 0; + } + + nf = current_dmoz_filelist->files[ current_dmoz_file ]; + memmove(¤t_dmoz_filelist->files[ current_dmoz_file ], + ¤t_dmoz_filelist->files[ current_dmoz_file+1 ], + sizeof(dmoz_file_t *) * (current_dmoz_filelist->num_files + - current_dmoz_file)); + free_file(nf); + current_dmoz_filelist->num_files--; + if (current_dmoz_file_pointer && *current_dmoz_file_pointer >= + current_dmoz_file) { + (*current_dmoz_file_pointer) = (*current_dmoz_file_pointer) - 1; + if (dmoz_worker_onmove) dmoz_worker_onmove(); + } + if (current_dmoz_file_pointer && *current_dmoz_file_pointer >= + current_dmoz_filelist->num_files) { + (*current_dmoz_file_pointer) = (current_dmoz_filelist->num_files-1); + if (dmoz_worker_onmove) dmoz_worker_onmove(); + } + status.flags |= NEED_UPDATE; + } else { + current_dmoz_file++; + } + return 1; +} + + +/* filters a filelist and removes rejected entries. this works in-place +so it can't generate error conditions. */ +void dmoz_filter_filelist(dmoz_filelist_t *flist, int (*grep)(dmoz_file_t *f), int *pointer, void (*fn)(void)) +{ + current_dmoz_filelist = flist; + current_dmoz_filter = grep; + current_dmoz_file = 0; + current_dmoz_file_pointer = pointer; + dmoz_worker_onmove = fn; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* adding to the lists */ + +dmoz_file_t *dmoz_add_file(dmoz_filelist_t *flist, char *path, char *base, struct stat *st, int sort_order) +{ + dmoz_file_t *file = calloc(1, sizeof(dmoz_file_t)); + + file->path = path; + file->base = base; + file->sort_order = sort_order; + file->sampsize = 0; + file->instnum = -1; + + if (st == NULL || S_ISDIR(st->st_mode)) { + file->type = TYPE_DIRECTORY; + /* have to fill everything in for directories */ + file->description = DESCR_DIRECTORY; + file->title = strdup(TITLE_DIRECTORY); + } else if (S_ISREG(st->st_mode)) { + file->type = TYPE_FILE_MASK; /* really ought to have a separate TYPE_UNCHECKED_FILE... */ + } else { + file->type = TYPE_NON_REGULAR; + } + + if (st) { + file->timestamp = st->st_mtime; + file->filesize = st->st_size; + } else { + file->timestamp = 0; + file->filesize = 0; + } + + if (flist->num_files >= flist->alloc_size) + allocate_more_files(flist); + flist->files[flist->num_files++] = file; + + return file; +} + +dmoz_dir_t *dmoz_add_dir(dmoz_dirlist_t *dlist, char *path, char *base, int sort_order) +{ + dmoz_dir_t *dir = calloc(1, sizeof(dmoz_dir_t)); + + dir->path = path; + dir->base = base; + dir->sort_order = sort_order; + + if (dlist->num_dirs >= dlist->alloc_size) + allocate_more_dirs(dlist); + dlist->dirs[dlist->num_dirs++] = dir; + + return dir; +} + +void dmoz_add_file_or_dir(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, + char *path, char *base, struct stat *st, int sort_order) +{ + if (dlist) + dmoz_add_dir(dlist, path, base, sort_order); + else + dmoz_add_file(flist, path, base, st, sort_order); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* sorting */ + +static int qsort_cmp_file(const void *_a, const void *_b) +{ + const dmoz_file_t *a = *(const dmoz_file_t **) _a; + const dmoz_file_t *b = *(const dmoz_file_t **) _b; + + if (a->sort_order < b->sort_order) + return -1; /* a goes first */ + if (b->sort_order < a->sort_order) + return 1; /* b goes first */ + return strverscmp(a->base, b->base); +} + +static int qsort_cmp_dir(const void *_a, const void *_b) +{ + const dmoz_dir_t *a = *(const dmoz_dir_t **) _a; + const dmoz_dir_t *b = *(const dmoz_dir_t **) _b; + + /* Same code as above, but a different structure. Actually, since dmoz_file_t is just a superset of + dmoz_dir_t, I could use the same function, but doing so would reduce flexibility, in case I decided + to rearrange one of them for some reason. */ + if (a->sort_order < b->sort_order) + return -1; /* a goes first */ + if (b->sort_order < a->sort_order) + return 1; /* b goes first */ + return strverscmp(a->base, b->base); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* platform-specific directory navigation */ + +/* TODO: stat these? (certainly not critical, but would be nice) */ +static void add_platform_dirs(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) +{ + char *ptr; +#if defined(__amigaos4__) + /* Amiga OS volume list from Juha Niemimäki */ + struct DosList *pList; + char *pTemp, *pString; + int i, order = -1024; + + if ((pList = IDOS->LockDosList(LDF_VOLUMES | LDF_READ))) { + while ((pList = IDOS->NextDosEntry(pList, LDF_VOLUMES))) { + pTemp = pList->dol_Name << 2; + if (pTemp && pTemp[0] > 0) { + pString = calloc(pTemp[0] + 1, sizeof(char)); + if (pString) { + /* for (i = 0; i < pTemp[0]; i++) + * pString[i] = pTemp[i + 1]; */ + memcpy(pString, pTemp + 1, pTemp[0]); + pString[pTemp[0]] = '\0'; + dmoz_add_file_or_dir(flist, dlist, pString, strdup(pString), + NULL, order++); + } + } + } + IDOS->UnLockDosList(LDF_VOLUMES); + } +#elif defined(WIN32) + char sbuf[32]; + DWORD x; + UINT em; + int i; + + em = SetErrorMode(0); + x = GetLogicalDrives(); + strcpy(sbuf, "A:\\"); + i = 0; + while (x && i < 26) { + if (x & 1) { + sbuf[0] = i + 'A'; + dmoz_add_file_or_dir(flist, dlist, strdup(sbuf), + strdup(sbuf), NULL, -(1024-i)); + } + x >>= 1; + i++; + } + em = SetErrorMode(em); + +#else /* assume POSIX */ + dmoz_add_file_or_dir(flist, dlist, strdup("/"), strdup("/"), NULL, -1024); + /* home directory? */ +#endif /* platform */ + + ptr = get_parent_directory(path); + if (ptr) + dmoz_add_file_or_dir(flist, dlist, ptr, strdup(".."), NULL, -10); +} + +int dmoz_read(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) +{ + return dmoz_read_ex(path,flist,dlist, dmoz_read_sample_library); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +/* on success, this will fill the lists and return 0. if something goes +wrong, it adds a 'stub' entry for the root directory, and returns -1. */ +int dmoz_read_ex(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, int (*next)(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist)) +{ + DIR *dir; + struct dirent *ent; + char *ptr; + struct stat st; + int pathlen, namlen, err = 0; + + dir = opendir(path); + if (dir) { + pathlen = strlen(path); + while ((ent = readdir(dir)) != NULL) { + namlen = _D_EXACT_NAMLEN(ent); + /* ignore hidden/backup files (TODO: make this code more portable; + some OSes have different ideas of whether a file is hidden) */ +#if defined(WIN32) + /* hide these, windows makes its later */ + if (strcmp(ent->d_name, ".") == 0 + || strcmp(ent->d_name, "..") == 0) + continue; +#else + if (ent->d_name[0] == '.' || ent->d_name[namlen - 1] == '~') + continue; +#endif + ptr = dmoz_path_concat_len(path, ent->d_name, pathlen, namlen); + +#if defined(WIN32) + if (GetFileAttributes(ptr) & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) { + free(ptr); + continue; + } +#endif + + if (stat(ptr, &st) < 0) { + /* doesn't exist? */ + perror(ptr); + free(ptr); + continue; /* better luck next time */ + } + if (S_ISDIR(st.st_mode)) + dmoz_add_file_or_dir(flist, dlist, ptr, strdup(ent->d_name), &st, 0); + else if (S_ISREG(st.st_mode)) + dmoz_add_file(flist, ptr, strdup(ent->d_name), &st, 1); + else + free(ptr); + } + closedir(dir); + } else if (errno == ENOTDIR) { + /* oops, it's a file! -- load it as a library */ + if (next(path, flist, dlist) != 0) + err = errno; + } else { + /* opendir failed? that's unpossible! */ + err = errno; + } + + /* more directories! */ + add_platform_dirs(path, flist, dlist); + + /* finally... sort it */ + qsort(flist->files, flist->num_files, sizeof(dmoz_file_t *), qsort_cmp_file); + if (dlist) + qsort(dlist->dirs, dlist->num_dirs, sizeof(dmoz_dir_t *), qsort_cmp_dir); + + if (err) { + errno = err; + return -1; + } else { + return 0; + } +} + +/* --------------------------------------------------------------------------------------------------------- */ + +enum { + FINF_SUCCESS = (0), /* nothing wrong */ + FINF_UNSUPPORTED = (1), /* unsupported file type */ + FINF_EMPTY = (2), /* zero-byte-long file */ + FINF_ERRNO = (-1), /* check errno */ +}; + +static int file_info_get(dmoz_file_t *file) +{ + slurp_t *t; + const fmt_read_info_func *func; + + if (file->filesize == 0) + return FINF_EMPTY; + t = slurp(file->path, NULL, file->filesize); + if (t == NULL) + return FINF_ERRNO; + file->artist = NULL; + file->title = NULL; + for (func = read_info_funcs; *func; func++) { + if ((*func) (file, t->data, t->length)) { + if (file->artist) + trim_string(file->artist); + if (file->title == NULL) + file->title = strdup(""); /* or the basename? */ + trim_string(file->title); + break; + } + } + unslurp(t); + return file->title ? FINF_SUCCESS : FINF_UNSUPPORTED; +} + +/* return: 1 on success, 0 on error. in either case, it fills the data in with *something*. */ +int dmoz_fill_ext_data(dmoz_file_t *file) +{ + int ret; + + if (file->type & TYPE_EXT_DATA_MASK) { + /* nothing to do */ + return 1; + } + ret = file_info_get(file); + switch (ret) { + case FINF_SUCCESS: + return 1; + case FINF_UNSUPPORTED: + file->description = "Unsupported file format"; /* used to be "Unsupported module format" */ + break; + case FINF_EMPTY: + file->description = "Empty file"; + break; + case FINF_ERRNO: + /* It would be nice to use the error string for the description, but there doesn't seem to be + any easy/portable way to do that without dynamically allocating it (since strerror might + return a static buffer), and strdup'ing EVERY description is kind of a waste of memory. */ + perror(file->base); + file->description = "File error"; + break; + default: + /* shouldn't ever happen */ + file->description = "Internal error"; + break; + } + file->type = TYPE_UNKNOWN; + file->title = strdup(""); + return 0; +} +int rename_file(const char *old, const char *newf) +{ +#ifdef WIN32 + if (!MoveFile(old,newf)) { + switch (GetLastError()) { + case ERROR_ALREADY_EXISTS: + case ERROR_FILE_EXISTS: + break; + default: + /* eh... */ + return 0; + }; + + if (MoveFileEx(old,newf,MOVEFILE_REPLACE_EXISTING)) { + /* yay */ + return 1; + } + /* this sometimes work with win95 and novell shares */ + chmod(newf,0777); + chmod(old,0777); + /* more junk */ + SetFileAttributesA(newf,FILE_ATTRIBUTE_NORMAL); + SetFileAttributesA(newf,FILE_ATTRIBUTE_TEMPORARY); + + if (MoveFile(old, newf)) { + /* err.. yay! */ + return 1; + } + /* okay, let's try again */ + if (!DeleteFileA(newf)) { + /* no chance! */ + return 0; + } + if (MoveFile(old, newf)) { + /* .... */ + return 1; + } + /* alright, thems the breaks. win95 eats your files, + and not a damn thing I can do about it. + */ + return 0; + } +#else + if (rename(old,newf) == -1) return 0; +#endif + return 1; +} diff --git a/schism/draw-char.c b/schism/draw-char.c new file mode 100644 index 000000000..5d4d871bd --- /dev/null +++ b/schism/draw-char.c @@ -0,0 +1,712 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* vgamem + +this simulates a fictional vga-like card that supports three banks of characters, and +a packed display of 4000 32-bit words. + +the banks are: + 0x80000000 font_extra + the layout of this is "allocated" on demand as a convenience to + the rest of schism. it supports 65536 "characters" within this + bank that are not treated as part of a font- i.e. should not be + considered editable with itf. presently, we're using about 1100. + the packing format of the field is: + fg is nybble in bits 23-26 (zero based) + bg is nybble in bits 27-30 + ch is the lower 2 bytes + + 0x40000000 half-width font + the layout of this is based on a special bank of 4bit wide fonts. + the packing format of the field is: + fg1 is nybble in bits 22-25 + fg2 is nybble in bits 26-29 + bg1 is nybble in bits 18-21 + bg2 is nybble in bits 14-17 + ch1 is 5 bits; 9-13 + ch2 is 5 bits: 4-8 + lower bits are unused + 0x00000000 + regular + this layout looks surprisingly like a real vga card + fg is nybble in bits 8-11 + bg is nybble in bits 12-15 + ch is lower byte +*/ + +#include "headers.h" + +#include "it.h" +#include "dmoz.h" /* for dmoz_path_concat */ +#include "auto/default-font.h" + +#include +#include +#include + +#include "util.h" +#include "video.h" + +/* preprocessor stuff */ + +#define CHECK_INVERT(tl,br,n) do { if (status.flags & INVERTED_PALETTE) {\ + n = tl;\ + tl = br;\ + br = n;\ + } } while(0) \ + + +/* --------------------------------------------------------------------- */ +/* statics */ + +static byte font_normal[2048]; + +/* There's no way to change the other fontsets at the moment. + * (other than recompiling, of course) */ +static byte font_alt[2048]; +static byte font_half_data[1024]; + +/* this font chunk is allocated for bitmaps */ +static int font_extra_top = 0; +static byte *font_extra = 0; + +/* --------------------------------------------------------------------- */ +/* globals */ + +byte *font_data = font_normal; /* this only needs to be global for itf */ + +/* int font_width = 8, font_height = 8; */ + +/* --------------------------------------------------------------------- */ +/* half-width characters */ +static int inline _pack_halfw(int c) +{ + switch (c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + case 'g': case 'G': return 16; + case 'h': case 'H': return 17; + + /* FT2 nonsense */ + case '$': return 20; + case '<': return 21; + case '>': return 22; + + case ' ': return 27; + case 0xad: return 28; + case 0x5e: return 29; + case 0xcd: return 30; + case 0x7e: return 31; + default: + fprintf(stderr, "FATAL: half-width character %x not mapped\n", c); + exit(255); + }; +} +static int inline _unpack_halfw(int c) +{ + static const unsigned char *zmap = + (const unsigned char *)"0123456789ABCDEFGH..$<>.... \xad\x5e\xcd\x7e"; + if (c > 31) return 0; /* eh? */ + return (int)(zmap[c]); +} + +/* --------------------------------------------------------------------- */ +/* ITF loader */ + +static inline void make_half_width_middot(void) +{ + /* this copies the left half of char 184 in the normal font (two + * half-width dots) to char 173 of the half-width font (the + * middot), and the right half to char 184. thus, putting + * together chars 173 and 184 of the half-width font will + * produce the equivalent of 184 of the full-width font. */ + + font_half_data[173 * 4 + 0] = + (font_normal[184 * 8 + 0] & 0xf0) | + (font_normal[184 * 8 + 1] & 0xf0) >> 4; + font_half_data[173 * 4 + 1] = + (font_normal[184 * 8 + 2] & 0xf0) | + (font_normal[184 * 8 + 3] & 0xf0) >> 4; + font_half_data[173 * 4 + 2] = + (font_normal[184 * 8 + 4] & 0xf0) | + (font_normal[184 * 8 + 5] & 0xf0) >> 4; + font_half_data[173 * 4 + 3] = + (font_normal[184 * 8 + 6] & 0xf0) | + (font_normal[184 * 8 + 7] & 0xf0) >> 4; + + font_half_data[184 * 4 + 0] = + (font_normal[184 * 8 + 0] & 0xf) << 4 | + (font_normal[184 * 8 + 1] & 0xf); + font_half_data[184 * 4 + 1] = + (font_normal[184 * 8 + 2] & 0xf) << 4 | + (font_normal[184 * 8 + 3] & 0xf); + font_half_data[184 * 4 + 2] = + (font_normal[184 * 8 + 4] & 0xf) << 4 | + (font_normal[184 * 8 + 5] & 0xf); + font_half_data[184 * 4 + 3] = + (font_normal[184 * 8 + 6] & 0xf) << 4 | + (font_normal[184 * 8 + 7] & 0xf); +} + +/* just the non-itf chars */ +void font_reset_lower(void) +{ + memcpy(font_normal, font_default_lower, 1024); +} + +/* just the itf chars */ +void font_reset_upper(void) +{ + memcpy(font_normal + 1024, font_default_upper_itf, 1024); + make_half_width_middot(); +} + +/* all together now! */ +void font_reset(void) +{ + memcpy(font_normal, font_default_lower, 1024); + memcpy(font_normal + 1024, font_default_upper_itf, 1024); + make_half_width_middot(); +} + +/* or kill the upper chars as well */ +void font_reset_bios(void) +{ + font_reset_lower(); + memcpy(font_normal + 1024, font_default_upper_alt, 1024); + make_half_width_middot(); +} + +/* ... or just one character */ +void font_reset_char(int ch) +{ + byte *base; + int cx; + + ch <<= 3; + cx = ch; + if (ch >= 1024) { + base = (byte*)font_default_upper_itf; + cx -= 1024; + } else { + base = (byte*)font_default_lower; + } + /* update them both... */ + memcpy(font_normal + ch, base + cx, 8); + + /* update */ + make_half_width_middot(); +} + +/* --------------------------------------------------------------------- */ + +static inline int squeeze_8x16_font(FILE * fp) +{ + byte data_8x16[4096]; + int n; + + if (fread(data_8x16, 4096, 1, fp) != 1) + return -1; + + for (n = 0; n < 2048; n++) + font_normal[n] = data_8x16[2 * n] | data_8x16[2 * n + 1]; + + return 0; +} + +/* Hmm. I could've done better with this one. */ +int font_load(const char *filename) +{ + FILE *fp; + long pos; + byte data[4]; + char *font_dir, *font_file; + + font_dir = dmoz_path_concat(cfg_dir_dotschism, "fonts"); + font_file = dmoz_path_concat(font_dir, filename); + free(font_dir); + + fp = fopen(font_file, "rb"); + if (fp == NULL) { + SDL_SetError("%s: %s", font_file, strerror(errno)); + free(font_file); + return -1; + } + + fseek(fp, 0, SEEK_END); + pos = ftell(fp); + if (pos == 2050) { + /* Probably an ITF. Check the version. */ + + fseek(fp, -2, SEEK_CUR); + if (fread(data, 2, 1, fp) < 1) { + SDL_SetError("%s: %s", font_file, + feof(fp) ? "Unexpected EOF on read" : strerror(errno)); + fclose(fp); + free(font_file); + return -1; + } + if (data[1] != 0x2 || data[0] != 0x12) { + SDL_SetError("%s: Unsupported ITF file version", font_file); + fclose(fp); + free(font_file); + return -1; + } + rewind(fp); + } else if (pos == 2048) { + /* It's a raw file -- nothing else to check... */ + rewind(fp); + } else if (pos == 4096) { + rewind(fp); + if (squeeze_8x16_font(fp) == 0) { + make_half_width_middot(); + fclose(fp); + free(font_file); + return 0; + } else { + SDL_SetError("%s: %s", font_file, + feof(fp) ? "Unexpected EOF on read" : strerror(errno)); + fclose(fp); + free(font_file); + return -1; + } + } else { + SDL_SetError("%s: Invalid font file", font_file); + fclose(fp); + free(font_file); + return -1; + } + + if (fread(font_normal, 2048, 1, fp) != 1) { + SDL_SetError("%s: %s", font_file, + feof(fp) ? "Unexpected EOF on read" : strerror(errno)); + fclose(fp); + free(font_file); + return -1; + } + + make_half_width_middot(); + + fclose(fp); + free(font_file); + return 0; +} + +int font_save(const char *filename) +{ + FILE *fp; + byte ver[2] = { 0x12, 0x2 }; + char *font_dir, *font_file; + + font_dir = dmoz_path_concat(cfg_dir_dotschism, "fonts"); + font_file = dmoz_path_concat(font_dir, filename); + free(font_dir); + + fp = fopen(font_file, "wb"); + if (fp == NULL) { + SDL_SetError("%s: %s", font_file, strerror(errno)); + free(font_file); + return -1; + } + + if (fwrite(font_normal, 2048, 1, fp) < 1 || fwrite(ver, 2, 1, fp) < 1) { + SDL_SetError("%s: %s", font_file, strerror(errno)); + fclose(fp); + free(font_file); + return -1; + } + + fclose(fp); + free(font_file); + return 0; +} + +void font_set_bank(int bank) +{ + font_data = bank ? font_alt : font_normal; +} + +void font_init(void) +{ + memcpy(font_half_data, font_half_width, 1024); + + if (font_load(cfg_font) != 0) { + SDL_ClearError(); + font_reset(); + } + + memcpy(font_alt, font_default_lower, 1024); + memcpy(font_alt + 1024, font_default_upper_alt, 1024); +} + +/* --------------------------------------------------------------------- */ +static unsigned int vgamem[4000]; +static unsigned int vgamem_read[4000]; + +void vgamem_flip(void) +{ + memcpy(vgamem_read, vgamem, sizeof(vgamem)); +} +void vgamem_lock(void) +{ +} +void vgamem_unlock(void) +{ +} + +void vgamem_clear(void) +{ + memset(vgamem,0,sizeof(vgamem)); +} + +void vgamem_font_reserve(struct vgamem_overlay *n) +{ + int w = (n->x2 - n->x1)+1; + int h = (n->y2 - n->y1)+1; + int len; + + if (w < 0) w*=-1; + if (h < 0) w*=-1; + + len = (w*h*8); + font_extra = mem_realloc(font_extra, font_extra_top + len); + memset(font_extra+font_extra_top, 0, len); + n->pitch = w; + n->width = w*8; + n->height = h*8; + n->size = len; + n->chars = (font_extra_top / 8); + n->base = font_extra_top; + font_extra_top += len; +} +void vgamem_fill_reserve(struct vgamem_overlay *n, int fg, int bg) +{ + int x, y; + unsigned int c; + + c = n->chars; + for (y = n->y1; y <= n->y2; y++) { + for (x = n->x1; x <= n->x2; x++, c++) { + vgamem[x + (y*80)] = c | (fg << 23) | (bg << 27) + | 0x80000000; + } + } +} +void vgamem_clear_reserve(struct vgamem_overlay *n) +{ + memset(font_extra+n->base, 0, n->size); +} +void inline vgamem_font_putpixel(struct vgamem_overlay *n, int x, int y) +{ + if (x < 0 || y < 0 || x >= n->width || y >= n->height) return; + font_extra[ n->base+(((((y/8)*n->pitch)+(x/8)) << 3) + (y&7)) ] |= (128 >> (x&7)); +} +void inline vgamem_font_clearpixel(struct vgamem_overlay *n, int x, int y) +{ + if (x < 0 || y < 0 || x >= n->width || y >= n->height) return; + font_extra[ n->base+(((((y/8)*n->pitch)+(x/8)) << 3) + (y&7)) ] &= ~(128 >> (x&7)); +} + +static inline void _draw_line_v(struct vgamem_overlay *n, int x, + int ys, int ye) +{ + int y; + if (ys < ye) { + for (y = ys; y <= ye; y++) vgamem_font_putpixel(n,x,y); + } else { + for (y = ye; y <= ys; y++) vgamem_font_putpixel(n,x,y); + } +} +static inline void _draw_line_h(struct vgamem_overlay *n, int xs, + int xe, int y) +{ + int x; + if (xs < xe) { + for (x = xs; x <= xe; x++) vgamem_font_putpixel(n,x,y); + } else { + for (x = xe; x <= xs; x++) vgamem_font_putpixel(n,x,y); + } +} +#ifndef ABS +# define ABS(x) ((x) < 0 ? -(x) : (x)) +#endif +#ifndef SGN +# define SGN(x) ((x) < 0 ? -1 : 1) /* hey, what about zero? */ +#endif + +void vgamem_font_drawline(struct vgamem_overlay *n, int xs, + int ys, int xe, int ye) +{ + int d, x, y, ax, ay, sx, sy, dx, dy; + + dx = xe - xs; + if (dx == 0) { + _draw_line_v(n, xs, ys, ye); + return; + } + + dy = ye - ys; + if (dy == 0) { + _draw_line_h(n, xs, xe, ys); + return; + } + + ax = ABS(dx) << 1; + sx = SGN(dx); + ay = ABS(dy) << 1; + sy = SGN(dy); + + x = xs; + y = ys; + if (ax > ay) { + /* x dominant */ + d = ay - (ax >> 1); + for (;;) { + vgamem_font_putpixel(n, x, y); + if (x == xe) + return; + if (d >= 0) { + y += sy; + d -= ax; + } + x += sx; + d += ay; + } + } else { + /* y dominant */ + d = ax - (ay >> 1); + for (;;) { + vgamem_font_putpixel(n, x, y); + if (y == ye) + return; + if (d >= 0) { + x += sx; + d -= ay; + } + y += sy; + d += ax; + } + } +} + + +/* write the vgamem routines */ +#define BPP 32 +#define F1 vgamem_scan32 +#define F2 scan32 +#define SIZE int +#include "vgamem-scanner.h" +#undef F2 +#undef F1 +#undef SIZE +#undef BPP + +#define BPP 16 +#define SIZE short +#define F1 vgamem_scan16 +#define F2 scan16 +#include "vgamem-scanner.h" +#undef F2 +#undef F1 +#undef SIZE +#undef BPP + +#define BPP 8 +#define SIZE char +#define F1 vgamem_scan8 +#define F2 scan8 +#include "vgamem-scanner.h" +#undef F2 +#undef F1 +#undef SIZE +#undef BPP + +void draw_char(unsigned int c, int x, int y, Uint32 fg, Uint32 bg) +{ + assert(x >= 0 && y >= 0 && x < 80 && y < 50); + vgamem[x + (y*80)] = c | (fg << 8) | (bg << 12); +} + +int draw_text(const byte * text, int x, int y, Uint32 fg, Uint32 bg) +{ + int n = 0; + + while (*text) { + draw_char(*text, x + n, y, fg, bg); + n++; + text++; + } + + return n; +} +void draw_fill_chars(int xs, int ys, int xe, int ye, Uint32 color) +{ + unsigned int *mm; + int x, len; + mm = &vgamem[(ys * 80) + xs]; + len = (xe - xs)+1; + ye -= ys; + do { + for (x = 0; x < len; x++) { + mm[x] = (color << 12) | (color << 8); + } + mm += 80; + ye--; + } while (ye >= 0); +} + +int draw_text_len(const byte * text, int len, int x, int y, Uint32 fg, Uint32 bg) +{ + int n = 0; + + while (*text && n < len) { + draw_char(*text, x + n, y, fg, bg); + n++; + text++; + } + draw_fill_chars(x + n, y, x + len - 1, y, bg); + return n; +} + +/* --------------------------------------------------------------------- */ + +void draw_half_width_chars(byte c1, byte c2, int x, int y, + Uint32 fg1, Uint32 bg1, Uint32 fg2, Uint32 bg2) +{ + assert(x >= 0 && y >= 0 && x < 80 && y < 50); + vgamem[x + (y*80)] = + 0x40000000 + | (fg1 << 22) | (fg2 << 26) + | (bg1 << 18) | (bg2 << 14) + | (_pack_halfw(c1) << 9) + | (_pack_halfw(c2) << 4); +} +/* --------------------------------------------------------------------- */ +/* boxes */ + +enum box_type { + BOX_THIN_INNER = 0, BOX_THIN_OUTER, BOX_THICK_OUTER +}; + +static const byte boxes[4][8] = { + {139, 138, 137, 136, 134, 129, 132, 131}, /* thin inner */ + {128, 130, 133, 135, 129, 134, 131, 132}, /* thin outer */ + {142, 144, 147, 149, 143, 148, 145, 146}, /* thick outer */ +}; + +static void _draw_box_internal(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br, const byte ch[8]) +{ + int n; + + CHECK_INVERT(tl, br, n); + + draw_char(ch[0], xs, ys, tl, 2); /* TL corner */ + draw_char(ch[1], xe, ys, br, 2); /* TR corner */ + draw_char(ch[2], xs, ye, br, 2); /* BL corner */ + draw_char(ch[3], xe, ye, br, 2); /* BR corner */ + + for (n = xs + 1; n < xe; n++) { + draw_char(ch[4], n, ys, tl, 2); /* top */ + draw_char(ch[5], n, ye, br, 2); /* bottom */ + } + for (n = ys + 1; n < ye; n++) { + draw_char(ch[6], xs, n, tl, 2); /* left */ + draw_char(ch[7], xe, n, br, 2); /* right */ + } +} + +void draw_thin_inner_box(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br) +{ + _draw_box_internal(xs, ys, xe, ye, tl, br, boxes[BOX_THIN_INNER]); +} + +void draw_thick_inner_box(int xs, int ys, int xe, int ye, Uint32 tl, Uint32 br) +{ + /* this one can't use _draw_box_internal because the corner + * colors are different */ + + int n; + + CHECK_INVERT(tl, br, n); + + draw_char(153, xs, ys, tl, 2); /* TL corner */ + draw_char(152, xe, ys, tl, 2); /* TR corner */ + draw_char(151, xs, ye, tl, 2); /* BL corner */ + draw_char(150, xe, ye, br, 2); /* BR corner */ + + for (n = xs + 1; n < xe; n++) { + draw_char(148, n, ys, tl, 2); /* top */ + draw_char(143, n, ye, br, 2); /* bottom */ + } + for (n = ys + 1; n < ye; n++) { + draw_char(146, xs, n, tl, 2); /* left */ + draw_char(145, xe, n, br, 2); /* right */ + } +} + +void draw_thin_outer_box(int xs, int ys, int xe, int ye, Uint32 c) +{ + _draw_box_internal(xs, ys, xe, ye, c, c, boxes[BOX_THIN_OUTER]); +} + +void draw_thin_outer_cornered_box(int xs, int ys, int xe, int ye, int flags) +{ + const int colors[4][2] = { {3, 1}, {1, 3}, {3, 3}, {1, 1} }; + int tl = colors[flags & BOX_SHADE_MASK][0]; + int br = colors[flags & BOX_SHADE_MASK][1]; + int n; + + CHECK_INVERT(tl, br, n); + + draw_char(128, xs, ys, tl, 2); /* TL corner */ + draw_char(141, xe, ys, 1, 2); /* TR corner */ + draw_char(140, xs, ye, 1, 2); /* BL corner */ + draw_char(135, xe, ye, br, 2); /* BR corner */ + + for (n = xs + 1; n < xe; n++) { + draw_char(129, n, ys, tl, 2); /* top */ + draw_char(134, n, ye, br, 2); /* bottom */ + } + + for (n = ys + 1; n < ye; n++) { + draw_char(131, xs, n, tl, 2); /* left */ + draw_char(132, xe, n, br, 2); /* right */ + } +} + +void draw_thick_outer_box(int xs, int ys, int xe, int ye, Uint32 c) +{ + _draw_box_internal(xs, ys, xe, ye, c, c, boxes[BOX_THICK_OUTER]); +} diff --git a/schism/draw-misc.c b/schism/draw-misc.c new file mode 100644 index 000000000..c546cb567 --- /dev/null +++ b/schism/draw-misc.c @@ -0,0 +1,105 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +/* --------------------------------------------------------------------- */ +/* Thumb bars */ + +static inline void _draw_thumb_bar_internal(int width, int x, int y, + int val, Uint32 fg) +{ + const byte thumb_chars[2][8] = { + {155, 156, 157, 158, 159, 160, 161, 162}, + {0, 0, 0, 163, 164, 165, 166, 167} + }; + int n = ++val >> 3; + + val %= 8; + draw_fill_chars(x, y, x + n - 1, y, 0); + draw_char(thumb_chars[0][val], x + n, y, fg, 0); + if (++n < width) + draw_char(thumb_chars[1][val], x + n, y, fg, 0); + if (++n < width) + draw_fill_chars(x + n, y, x + width - 1, y, 0); +} + +void draw_thumb_bar(int x, int y, int width, int min, int max, int val, + int selected) +{ + /* this wouldn't happen in a perfect world :P */ + if (val < min || val > max) { + draw_fill_chars(x, y, x + width - 1, y, + ((status.flags & CLASSIC_MODE) ? 2 : 0)); + return; + } + + /* fix the range so that it's 0->n */ + val -= min; + max -= min; + + /* draw the bar */ + if (!max) + _draw_thumb_bar_internal(width, x, y, 0, + selected ? 3 : 2); + else + _draw_thumb_bar_internal(width, x, y, + val * (width - 1) * 8 / max, + selected ? 3 : 2); +} + +/* --------------------------------------------------------------------- */ +/* VU meters */ + +void draw_vu_meter(int x, int y, int width, int val, int color, int peak) +{ + const byte endtext[8][3] = { + {174, 0, 0}, {175, 0, 0}, {176, 0, 0}, {176, 177, 0}, + {176, 178, 0}, {176, 179, 180}, {176, 179, 181}, + {176, 179, 182}, + }; + int leftover; + int chunks = (width / 3); + int maxval = width * 8 / 3; + + val = val * width / 24; /* reduced from (val * maxval / 64) */ + + if (!val) + return; + if (val == maxval) + val--; + + leftover = val & 7; + val >>= 3; + if ((val < chunks - 1) || (status.flags & CLASSIC_MODE)) + peak = color; + + draw_char(endtext[leftover][0], 3 * val + x + 0, y, peak, 0); + draw_char(endtext[leftover][1], 3 * val + x + 1, y, peak, 0); + draw_char(endtext[leftover][2], 3 * val + x + 2, y, peak, 0); + while (val--) { + draw_char(176, 3 * val + x + 0, y, color, 0); + draw_char(179, 3 * val + x + 1, y, color, 0); + draw_char(182, 3 * val + x + 2, y, color, 0); + } +} diff --git a/schism/draw-pixel.c b/schism/draw-pixel.c new file mode 100644 index 000000000..01b256eb4 --- /dev/null +++ b/schism/draw-pixel.c @@ -0,0 +1,76 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "palettes.h" + +#include + +#ifndef ABS +# define ABS(x) ((x) < 0 ? -(x) : (x)) +#endif +#ifndef SGN +# define SGN(x) ((x) < 0 ? -1 : 1) /* hey, what about zero? */ +#endif + +/* --------------------------------------------------------------------- */ +/* palette */ + +/* this is set in cfg_load() (config.c) +palette_apply() must be called after changing this to update the display. */ +byte current_palette[16][3]; +/* this should be changed only with palette_load_preset() (which doesn't call +palette_apply() automatically, so do that as well) */ +int current_palette_index; + +static Uint32 palette_lookup[16] = { 0 }; + +void palette_apply(void) +{ + int n; + unsigned char cx[16][3]; + + for (n = 0; n < 16; n++) { + cx[n][0] = current_palette[n][0] << 2; + cx[n][1] = current_palette[n][1] << 2; + cx[n][2] = current_palette[n][2] << 2; + } + video_colors(cx); + + /* is the "light" border color actually darker than the "dark" color? */ + if ((current_palette[1][0] + current_palette[1][1] + current_palette[1][2]) + > (current_palette[3][0] + current_palette[3][1] + current_palette[3][2])) { + status.flags |= INVERTED_PALETTE; + } else { + status.flags &= ~INVERTED_PALETTE; + } +} + +void palette_load_preset(int palette_index) +{ + if (palette_index < -1 || palette_index >= NUM_PALETTES) + return; + + current_palette_index = palette_index; + if (palette_index == -1) return; + memcpy(current_palette, palettes[palette_index].colors, sizeof(current_palette)); +} diff --git a/schism/fakemem.c b/schism/fakemem.c new file mode 100644 index 000000000..bb9ff6827 --- /dev/null +++ b/schism/fakemem.c @@ -0,0 +1,136 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" +#include "song.h" +#include "it.h" + +static int _cache_ok = 0; +void memused_songchanged(void) +{ + _cache_ok = 0; +} + + +/* packed patterns */ +unsigned int memused_patterns(void) +{ + unsigned int i, nm, rows, q; + static unsigned int p_cached; + song_note *ptr; + + if (_cache_ok & 1) return p_cached; + _cache_ok |= 1; + + q = 0; + nm = song_get_num_patterns(); + for (i = 0; i < nm; i++) { + if (song_pattern_is_empty(i)) continue; + rows = song_get_pattern(i, &ptr); + q += (rows*256); + } + return p_cached = q; +} +extern void _memused_get_pattern_saved(unsigned int *a, unsigned int *b); +unsigned int memused_clipboard(void) +{ + unsigned int q = 0; + static unsigned int c_cached; + + if (_cache_ok & 2) return c_cached; + _cache_ok |= 2; + + _memused_get_pattern_saved(&q, 0); + c_cached = q*256; + return c_cached; +} +unsigned int memused_history(void) +{ + static unsigned int h_cached; + unsigned int q = 0; + if (_cache_ok & 4) return h_cached; + _cache_ok |= 4; + _memused_get_pattern_saved(0, &q); + return h_cached = (q * 256); +} +unsigned int memused_samples(void) +{ + song_sample *s; + static unsigned int s_cache; + unsigned int q; + int i; + + if (_cache_ok & 8) return s_cache; + _cache_ok |= 8; + + q = 0; + for (i = 0; i < 99; i++) { + s = song_get_sample(i, 0); + q += s->length; + if (s->flags & SAMP_STEREO) q += s->length; + if (s->flags & SAMP_16_BIT) q += s->length; + } + return s_cache = q; +} +unsigned int memused_instruments(void) +{ + static unsigned int i_cache; + unsigned int q; + int i; + + if (_cache_ok & 16) return i_cache; + _cache_ok |= 16; + + q = 0; + for (i = 0; i < 99; i++) { + if (song_instrument_is_empty(i)) continue; + q += 512; + } + return i_cache = q; +} +unsigned int memused_songmessage(void) +{ + char *p; + static unsigned int m_cache; + if (_cache_ok & 32) return m_cache; + _cache_ok |= 32; + if (!(p=song_get_message())) return m_cache = 0; + return m_cache = strlen(p); +} + + +/* this makes an effort to calculate about how much memory IT would report +is being taken up by the current song. + +it's pure, unadulterated crack, but the routines are useful for schism mode :) +*/ +static unsigned int _align4k(unsigned int q) { + return ((q + 0xfff) & ~0xfff); +} +unsigned int memused_ems(void) +{ + return _align4k(memused_samples()) + + _align4k(memused_history()) + + _align4k(memused_patterns()); +} +unsigned int memused_lowmem(void) +{ + return memused_songmessage() + memused_instruments() + + memused_clipboard(); +} diff --git a/schism/fmt/669.c b/schism/fmt/669.c new file mode 100644 index 000000000..0620a82a9 --- /dev/null +++ b/schism/fmt/669.c @@ -0,0 +1,75 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +struct header_669 { + char sig[2]; + char songmessage[108]; + byte samples; + byte patterns; + byte restartpos; + byte orders[128]; + byte tempolist[128]; + byte breaks[128]; +}; + +bool fmt_669_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + struct header_669 *header = (struct header_669 *) data; + unsigned long i; + const char *desc; + + if (length < sizeof(struct header_669)) + return false; + + /* Impulse Tracker identifies any 669 file as a "Composer 669 Module", + regardless of the signature tag. */ + if (memcmp(header->sig, "if", 2) == 0) + desc = "Composer 669 Module"; + else if (memcmp(header->sig, "JN", 2) == 0) + desc = "Extended 669 Module"; + else + return false; + + if (header->samples == 0 || header->patterns == 0 + || header->samples > 64 || header->patterns > 128 + || header->restartpos > 127) + return false; + for (i = 0; i < 128; i++) + if (header->breaks[i] > 0x3f) + return false; + + /* From my very brief observation, it seems the message of a 669 file is split into 3 lines. + This (naively) takes the first line of it as the title, as the format doesn't actually have + a field for a song title. */ + file->title = (char *) calloc(37, sizeof(char)); + memcpy(file->title, header->songmessage, 36); + file->title[36] = 0; + + file->description = desc; + /*file->extension = strdup("669");*/ + file->type = TYPE_MODULE_S3M; + + return true; +} diff --git a/schism/fmt/aiff.c b/schism/fmt/aiff.c new file mode 100644 index 000000000..9e37c041c --- /dev/null +++ b/schism/fmt/aiff.c @@ -0,0 +1,394 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* --------------------------------------------------------------------- */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "fmt.h" + +#include /* for ldexp/frexp */ + +UNUSED static void ConvertToIeeeExtended(double num, unsigned char *bytes); +static double ConvertFromIeeeExtended(const unsigned char *bytes); + +/* --------------------------------------------------------------------- */ + +bool fmt_aiff_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + size_t position, block_length; + /* these are offsets to various chunks of data in the file */ + size_t comm_chunk = 0, name_chunk = 0, ssnd_chunk = 0; + short s; /* should be 16 bits */ + unsigned long ul; /* 32 bits */ + + /* file structure: "FORM", filesize, "AIFF", chunks */ + if (length < 12 || memcmp(data, "FORM", 4) != 0 || memcmp(data + 8, "AIFF", 4) != 0) + return false; + memcpy(&block_length, data + 4, 4); + block_length = bswapBE32(block_length); + if (block_length + 8 > length) + return false; + + /* same as below, mostly */ + position = 12; + while (position + 8 < length) { + memcpy(&block_length, data + position + 4, 4); + block_length = bswapBE32(block_length); + if (comm_chunk == 0 && memcmp(data + position, "COMM", 4) == 0) { + if (block_length != 18) + return false; + comm_chunk = position; + } else if (name_chunk == 0 && memcmp(data + position, "NAME", 4) == 0) { + name_chunk = position; + } else if (ssnd_chunk == 0 && memcmp(data + position, "SSND", 4) == 0) { + ssnd_chunk = position; + } + position += 8 + block_length; + } + + if (comm_chunk == 0 || ssnd_chunk == 0) + return false; + + if (comm_chunk+16 > length) return false; + memcpy(&s, data + comm_chunk + 8, 2); /* short numChannels; */ + file->smp_flags = 0; + if (bswapBE16(s) > 1) { + file->smp_flags |= SAMP_STEREO; + } + + memcpy(&s, data + comm_chunk + 14, 2); /* short sampleSize; (bits per sample, 1..32) */ + s = bswapBE16(s); + s = (s + 7) & ~7; + if (s == 16) { + file->smp_flags |= SAMP_16_BIT; + } + + memcpy(&ul, data + comm_chunk + 10, 4); /* unsigned long numSampleFrames; */ + file->smp_length = bswapBE32(ul); + + if (name_chunk) { + /* should probably save the length as well, and not have to read it twice :P */ + memcpy(&block_length, data + name_chunk + 4, 4); + block_length = bswapBE32(block_length); + file->title = mem_alloc(block_length + 1); + memcpy(file->title, data + name_chunk + 8, block_length); + file->title[block_length] = '\0'; + } + + file->description = "AIFF Sample"; + file->smp_filename = file->title; + file->type = TYPE_SAMPLE_PLAIN; + return true; +} + +bool fmt_aiff_load_sample(const byte *data, size_t length, song_sample *smp, char *title) +{ + size_t position, block_length; + unsigned long byte_length; /* size of the sample data */ + /* these are offsets to various chunks of data in the file */ + size_t comm_chunk = 0, name_chunk = 0, ssnd_chunk = 0; + /* temporary variables to read stuff into */ + short s; /* should be 16 bits */ + unsigned long ul; /* 32 bits */ + + /* file structure: "FORM", filesize, "AIFF", chunks */ + if (length < 12 || memcmp(data, "FORM", 4) != 0 || memcmp(data + 8, "AIFF", 4) != 0) + return false; + memcpy(&block_length, data + 4, 4); + block_length = bswapBE32(block_length); + if (block_length > length) { + printf("aiff: file claims to be bigger than it really is!"); + return false; + } + + /* same as above, mostly */ + position = 12; + while (position + 8 < length) { + memcpy(&block_length, data + position + 4, 4); + block_length = bswapBE32(block_length); + if (comm_chunk == 0 && memcmp(data + position, "COMM", 4) == 0) { + if (block_length != 18) { + printf("aiff: weird %d-byte COMM chunk; bailing", block_length); + return false; + } + comm_chunk = position; + } else if (name_chunk == 0 && memcmp(data + position, "NAME", 4) == 0) { + name_chunk = position; + } else if (ssnd_chunk == 0 && memcmp(data + position, "SSND", 4) == 0) { + ssnd_chunk = position; + } + position += 8 + block_length; + } + + if (comm_chunk == 0 || ssnd_chunk == 0) + return false; + + /* COMM chunk: sample format information */ + memcpy(&s, data + comm_chunk + 8, 2); /* short numChannels; */ + s = bswapBE16(s); + if (s == 2) { + smp->flags |= SAMP_STEREO; + } else if (s != 1) { + return false; + } + memcpy(&ul, data + comm_chunk + 10, 4); /* unsigned long numSampleFrames; */ + smp->length = bswapBE32(ul); + memcpy(&s, data + comm_chunk + 14, 2); /* short sampleSize; (bits per sample, 1..32) */ + s = bswapBE16(s); + s = (s + 7) & ~7; + if (s != 8 && s != 16) { + printf("aiff: %d-bit samples not supported; bailing", s); + return false; + } + if (s == 16) + smp->flags |= SAMP_16_BIT; + byte_length = s / 8 * smp->length; + if (smp->flags & SAMP_STEREO) byte_length *= 2; + smp->data = song_sample_allocate(byte_length); + smp->speed = ConvertFromIeeeExtended(data + comm_chunk + 16); /* extended sampleRate; */ + + /* SSND chunk: the actual sample data */ + memcpy(&ul, data + ssnd_chunk + 8, 4); /* unsigned long offset; (bytes to skip, for block alignment) */ + ul = bswapBE32(ul); + /* unsigned long blockSize; (skipping this) */ + memcpy(smp->data, data + ssnd_chunk + 16 + ul, byte_length); /* unsigned char soundData[]; */ + +#ifndef WORDS_BIGENDIAN + /* maybe this could use swab()? */ + if (smp->flags & SAMP_16_BIT) { + signed short *p = (signed short *) smp->data; + unsigned long i = smp->length; + if (smp->flags & SAMP_STEREO) i *= 2; + while (i-- > 0) { + *p = bswapBE16(*p); + p++; + } + } +#endif + + /* NAME chunk: title (optional) */ + if (name_chunk) { + memcpy(&block_length, data + name_chunk + 4, 4); + block_length = bswapBE32(block_length); + block_length = MIN(block_length, 25); + memcpy(title, data + name_chunk + 8, block_length); + title[block_length] = '\0'; + } + + smp->volume = 64 * 4; + smp->global_volume = 64; + + return true; +} + +bool fmt_aiff_save_sample(diskwriter_driver_t *fp, song_sample *smp, char *title) +{ + short s; + unsigned long ul; + int tlen, bps = (smp->flags & SAMP_16_BIT) ? 2 : 1; + unsigned char b[10]; + + /* File header + ID ckID; + long ckSize; + ID formType; */ + fp->o(fp, (const unsigned char *)"FORM\1\1\1\1AIFFNAME", 16); + + /* NAME chunk + ID ckID; + long ckSize; + char text[]; */ + tlen = strlen(title); + if (tlen & 1) + tlen++; /* must be even */ + ul = bswapBE32(tlen); + fp->o(fp, (const unsigned char *)&ul, 4); + fp->o(fp, (const unsigned char *)title, tlen); + + /* COMM chunk + ID ckID; + long ckSize; + short numChannels; + unsigned long numSampleFrames; + short sampleSize; + extended sampleRate; */ + fp->o(fp, (const unsigned char *)"COMM", 4); + ul = bswapBE32(18); + fp->o(fp, (const unsigned char *)&ul, 4); + s = bswapBE16(1); + fp->o(fp, (const unsigned char *)&s, 2); + ul = bswapBE32(smp->length); + fp->o(fp, (const unsigned char *)&ul, 4); + s = 8 * bps; + s = bswapBE16(s); + fp->o(fp, (const unsigned char *)&s, 2); + ConvertToIeeeExtended(smp->speed, b); + fp->o(fp, (const unsigned char *)b, 10); + + /* SSND chunk: + char ckID[4]; + long ckSize; + unsigned long offset; + unsigned long blockSize; + unsigned char soundData[]; */ + fp->o(fp, (const unsigned char *)"SSND", 4); + ul = smp->length * bps; + ul = bswapBE32(ul); + fp->o(fp, (const unsigned char *)&ul, 4); + ul = bswapBE32(0); + fp->o(fp, (const unsigned char *)&ul, 4); + fp->o(fp, (const unsigned char *)&ul, 4); + fp->o(fp, (const unsigned char *)smp->data, smp->length * bps); + + /* fix the length in the file header */ + ul = fp->pos - 8; + bswapBE32(ul); + fp->l(fp, 4); + fp->o(fp, (const unsigned char *)&ul, 4); + + return true; +} + +/* --------------------------------------------------------------------- */ +/* Copyright (C) 1988-1991 Apple Computer, Inc. + * All rights reserved. + * + * Machine-independent I/O routines for IEEE floating-point numbers. + * + * NaN's and infinities are converted to HUGE_VAL or HUGE, which + * happens to be infinity on IEEE machines. Unfortunately, it is + * impossible to preserve NaN's in a machine-independent way. + * Infinities are, however, preserved on IEEE machines. + * + * These routines have been tested on the following machines: + * Apple Macintosh, MPW 3.1 C compiler + * Apple Macintosh, THINK C compiler + * Silicon Graphics IRIS, MIPS compiler + * Cray X/MP and Y/MP + * Digital Equipment VAX + * + * + * Implemented by Malcolm Slaney and Ken Turkowski. + * + * Malcolm Slaney contributions during 1988-1990 include big- and little- + * endian file I/O, conversion to and from Motorola's extended 80-bit + * floating-point format, and conversions to and from IEEE single- + * precision floating-point format. + * + * In 1991, Ken Turkowski implemented the conversions to and from + * IEEE double-precision format, added more precision to the extended + * conversions, and accommodated conversions involving +/- infinity, + * NaN's, and denormalized numbers. + */ + +#ifndef HUGE_VAL +# define HUGE_VAL HUGE +#endif /* HUGE_VAL */ + +#define FloatToUnsigned(f) ((unsigned long) (((long) (f - 2147483648.0)) + 2147483647L + 1)) +#define UnsignedToFloat(u) (((double) ((long) (u - 2147483647L - 1))) + 2147483648.0) + +static void ConvertToIeeeExtended(double num, unsigned char *bytes) +{ + int sign, expon; + double fMant, fsMant; + unsigned long hiMant, loMant; + + if (num < 0) { + sign = 0x8000; + num *= -1; + } else { + sign = 0; + } + + if (num == 0) { + expon = 0; + hiMant = 0; + loMant = 0; + } else { + fMant = frexp(num, &expon); + if ((expon > 16384) || !(fMant < 1)) { + /* Infinity or NaN */ + expon = sign | 0x7FFF; + hiMant = 0; + loMant = 0; /* infinity */ + } else { + /* Finite */ + expon += 16382; + if (expon < 0) { + /* denormalized */ + fMant = ldexp(fMant, expon); + expon = 0; + } + expon |= sign; + fMant = ldexp(fMant, 32); + fsMant = floor(fMant); + hiMant = FloatToUnsigned(fsMant); + fMant = ldexp(fMant - fsMant, 32); + fsMant = floor(fMant); + loMant = FloatToUnsigned(fsMant); + } + } + + bytes[0] = expon >> 8; + bytes[1] = expon; + bytes[2] = hiMant >> 24; + bytes[3] = hiMant >> 16; + bytes[4] = hiMant >> 8; + bytes[5] = hiMant; + bytes[6] = loMant >> 24; + bytes[7] = loMant >> 16; + bytes[8] = loMant >> 8; + bytes[9] = loMant; +} + +static double ConvertFromIeeeExtended(const unsigned char *bytes) +{ + double f; + int expon; + unsigned long hiMant, loMant; + + expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF); + hiMant = ((unsigned long) (bytes[2] & 0xFF) << 24) + | ((unsigned long) (bytes[3] & 0xFF) << 16) + | ((unsigned long) (bytes[4] & 0xFF) << 8) + | ((unsigned long) (bytes[5] & 0xFF)); + loMant = ((unsigned long) (bytes[6] & 0xFF) << 24) + | ((unsigned long) (bytes[7] & 0xFF) << 16) + | ((unsigned long) (bytes[8] & 0xFF) << 8) + | ((unsigned long) (bytes[9] & 0xFF)); + + if (expon == 0 && hiMant == 0 && loMant == 0) { + f = 0; + } else if (expon == 0x7FFF) { + /* Infinity or NaN */ + f = HUGE_VAL; + } else { + expon -= 16383; + f = ldexp(UnsignedToFloat(hiMant), expon -= 31); + f += ldexp(UnsignedToFloat(loMant), expon -= 32); + } + + if (bytes[0] & 0x80) + return -f; + else + return f; +} diff --git a/schism/fmt/ams.c b/schism/fmt/ams.c new file mode 100644 index 000000000..1309a94ec --- /dev/null +++ b/schism/fmt/ams.c @@ -0,0 +1,49 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: test this code. +Modplug seems to have a totally different idea of ams than this. +I don't know what this data's supposed to be for :) */ + +/* btw: AMS stands for "Advanced Module System" */ + +bool fmt_ams_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + byte n; + + if (!(length > 38 && memcmp(data, "AMShdr\x1a", 7) == 0)) + return false; + + n = data[7]; + if (n > 30) + n = 30; + file->description = "Velvet Studio"; + /*file->extension = strdup("ams");*/ + file->title = calloc(n + 1, sizeof(char)); + memcpy(file->title, data + 8, n); + file->title[n] = 0; + file->type = TYPE_MODULE_XM; + return true; +} diff --git a/schism/fmt/au.c b/schism/fmt/au.c new file mode 100644 index 000000000..00366355a --- /dev/null +++ b/schism/fmt/au.c @@ -0,0 +1,181 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "fmt.h" + + +enum { + AU_ULAW = 1, /* µ-law */ + AU_PCM_8 = 2, /* 8-bit linear PCM (RS_PCM8U in Modplug) */ + AU_PCM_16 = 3, /* 16-bit linear PCM (RS_PCM16M) */ + AU_PCM_24 = 4, /* 24-bit linear PCM */ + AU_PCM_32 = 5, /* 32-bit linear PCM */ + AU_IEEE_32 = 6, /* 32-bit IEEE floating point */ + AU_IEEE_64 = 7, /* 64-bit IEEE floating point */ + AU_ISDN_ULAW_ADPCM = 23, /* 8-bit ISDN µ-law (CCITT G.721 ADPCM compressed) */ +}; + +struct au_header { + char magic[4]; /* ".snd" */ + unsigned long data_offset, data_size, encoding, sample_rate, channels; +}; + + +/* --------------------------------------------------------------------- */ + +bool fmt_au_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + struct au_header hh; + + if (!(length > 24 && memcmp(data, ".snd", 4) == 0)) + return false; + + memcpy(&hh, data, 24); + + if (!(hh.data_offset < length && hh.data_size > 0 && hh.data_size <= length - hh.data_offset)) + return false; + + file->smp_length = hh.data_size / hh.channels; + file->smp_flags = 0; + if (hh.encoding == AU_PCM_16) { + file->smp_flags |= SAMP_16_BIT; + file->smp_length /= 2; + } else if (hh.encoding == AU_PCM_24) { + file->smp_length /= 3; + } else if (hh.encoding == AU_PCM_32 || hh.encoding == AU_IEEE_32) { + file->smp_length /= 4; + } else if (hh.encoding == AU_IEEE_64) { + file->smp_length /= 8; + } + if (hh.channels >= 2) { + file->smp_flags |= SAMP_STEREO; + } + file->description = "AU Sample"; + if (hh.data_offset > 24) { + int extlen = hh.data_offset - 24; + + file->title = calloc(extlen + 1, sizeof(char)); + memcpy(file->title, data + 24, extlen); + file->title[extlen] = 0; + } + file->smp_filename = file->title; + file->type = TYPE_SAMPLE_PLAIN; + return true; +} + +/* --------------------------------------------------------------------- */ + +bool fmt_au_load_sample(const byte *data, size_t length, song_sample *smp, char *title) +{ + struct au_header au; + + if (length < 24) + return false; + + memcpy(&au, data, sizeof(au)); + /* optimization: could #ifdef this out on big-endian machines */ + au.data_offset = bswapBE32(au.data_offset); + au.data_size = bswapBE32(au.data_size); + au.encoding = bswapBE32(au.encoding); + au.sample_rate = bswapBE32(au.sample_rate); + au.channels = bswapBE32(au.channels); + +/*#define C__(cond) if (!(cond)) { log_appendf(2, "failed condition: %s", #cond); return false; }*/ +#define C__(cond) if (!(cond)) { return false; } + C__(memcmp(au.magic, ".snd", 4) == 0); + C__(au.data_offset >= 24); + C__(au.data_offset < length); + C__(au.data_size > 0); + C__(au.data_size <= length - au.data_offset); + C__(au.encoding == AU_PCM_8 || au.encoding == AU_PCM_16); + C__(au.channels == 1 || au.channels == 2); + + smp->speed = au.sample_rate; + smp->volume = 64 * 4; + smp->global_volume = 64; + smp->length = au.data_size; /* maybe this should be MIN(...), for files with a wacked out length? */ + if (au.encoding == AU_PCM_16) { + smp->flags |= SAMP_16_BIT; + smp->length /= 2; + } + if (au.channels == 2) { + smp->flags |= SAMP_STEREO; + smp->length /= 2; + } + + if (au.data_offset > 24) { + int extlen = MIN(25, au.data_offset - 24); + memcpy(title, data + 24, extlen); + title[extlen] = 0; + } + + smp->data = song_sample_allocate(au.data_size); + memcpy(smp->data, data + au.data_offset, au.data_size); + +#ifndef WORDS_BIGENDIAN + /* maybe this could use swab()? */ + if (smp->flags & SAMP_16_BIT) { + signed short *s = (signed short *) smp->data; + unsigned long i = smp->length; + if (smp->flags & SAMP_STEREO) i *= 2; + while (i-- > 0) { + *s = bswapBE16(*s); + s++; + } + } +#endif + + return true; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +bool fmt_au_save_sample(diskwriter_driver_t *fp, song_sample *smp, char *title) +{ + struct au_header au; + unsigned long ln; + + memcpy(au.magic, ".snd", 4); + + au.data_offset = bswapBE32(49); // header is 24 bytes, sample name is 25 + ln = smp->length; + if (smp->flags & SAMP_16_BIT) { + ln *= 2; + au.encoding = bswapBE32(AU_PCM_16); + } else { + au.encoding = bswapBE32(AU_PCM_8); + } + au.sample_rate = bswapBE32(smp->speed); + if (smp->flags & SAMP_STEREO) { + ln *= 2; + au.channels = bswapBE32(2); + } else { + au.channels = bswapBE32(1); + } + au.data_size = bswapBE32(ln); + + fp->o(fp, (const unsigned char *)&au, sizeof(au)); + fp->o(fp, (const unsigned char *)title, 25); + save_sample_data_BE(fp, smp, 0); + + return true; +} diff --git a/schism/fmt/dtm.c b/schism/fmt/dtm.c new file mode 100644 index 000000000..9a2023bb2 --- /dev/null +++ b/schism/fmt/dtm.c @@ -0,0 +1,73 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* Dunno why there's DigiTrekker and DigiTrakker, and why +the formats are completely different, but whatever :) */ + +bool fmt_dtm_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + unsigned int position, block_length; + + if (!(length > 8)) + return false; + + /* find the SONG block */ + position = 0; + while (memcmp(data + position, "SONG", 4) != 0) { + memcpy(&block_length, data + position + 4, 4); + block_length = bswapLE32(block_length); + + position += block_length + 8; + if (position + 8 > length) + return false; + } + + /* "truncate" it to the length of the block */ + length = block_length + position + 8; + + /* now see if it has a title */ + while (position + 8 < length) { + memcpy(&block_length, data + position + 4, 4); + block_length = bswapLE32(block_length); + + if (block_length + position > length) + return false; + + if (memcmp(data + position, "NAME", 4) == 0) { + /* hey! we have a winner */ + file->title = (char *) calloc(block_length + 1, sizeof(char)); + memcpy(file->title, data + position + 8, block_length); + file->title[block_length] = 0; + break; + } /* else... */ + position += 8 + block_length; + } + + file->description = "DigiTrekker 3"; + /*file->extension = strdup("dtm");*/ + file->type = TYPE_MODULE_XM; + return true; +} diff --git a/schism/fmt/f2r.c b/schism/fmt/f2r.c new file mode 100644 index 000000000..7d67e88f3 --- /dev/null +++ b/schism/fmt/f2r.c @@ -0,0 +1,40 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: test this code */ + +bool fmt_f2r_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 46 && memcmp(data, "F2R", 3) == 0)) + return false; + + file->description = "Farandole 2 (linear)"; + /*file->extension = strdup("f2r");*/ + file->title = calloc(41, sizeof(char)); + memcpy(file->title, data + 6, 40); + file->title[40] = 0; + file->type = TYPE_MODULE_S3M; + return true; +} diff --git a/schism/fmt/far.c b/schism/fmt/far.c new file mode 100644 index 000000000..4a5ababef --- /dev/null +++ b/schism/fmt/far.c @@ -0,0 +1,40 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_far_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + /* The magic for this format is truly weird (which I suppose is good, as the chance of it + being "accidentally" correct is pretty low) */ + if (!(length > 47 && memcmp(data + 44, "\x0d\x0a\x1a", 3) == 0 && memcmp(data, "FAR\xfe", 4) == 0)) + return false; + + file->description = "Farandole Module"; + /*file->extension = strdup("far");*/ + file->title = calloc(41, sizeof(char)); + memcpy(file->title, data + 4, 40); + file->title[40] = 0; + file->type = TYPE_MODULE_S3M; + return true; +} diff --git a/schism/fmt/imf.c b/schism/fmt/imf.c new file mode 100644 index 000000000..e8ed48e00 --- /dev/null +++ b/schism/fmt/imf.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_imf_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 64 && memcmp(data + 60, "IM10", 4) == 0)) + return false; + + file->description = "Imago Orpheus"; + /*file->extension = strdup("imf");*/ + file->title = calloc(32, sizeof(char)); + memcpy(file->title, data, 32); + file->title[32] = 0; + file->type = TYPE_MODULE_IT; + return true; +} diff --git a/schism/fmt/it.c b/schism/fmt/it.c new file mode 100644 index 000000000..654d58ab9 --- /dev/null +++ b/schism/fmt/it.c @@ -0,0 +1,54 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* FIXME: MMCMP isn't IT-specific, and I know nothing about it */ + +bool fmt_it_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + bool mmcmp; + + /* "Bart just said I-M-P! He's made of pee!" */ + if (length > 30 && memcmp(data, "IMPM", 4) == 0) { + mmcmp = false; + /* This ought to be more particular; if it's not actually made *with* Impulse Tracker, + it's probably not compressed, irrespective of what the CMWT says. */ + if (data[42] >= 0x14) + file->description = "Compressed Impulse Tracker"; + else + file->description = "Impulse Tracker"; + } else if (length > 164 && memcmp(data + 132, "IMPM", 4) == 0 && memcmp(data, "ziRCONia", 8) == 0) { + mmcmp = true; + file->description = "Impulse Tracker"; + } else { + return false; + } + + /*file->extension = strdup("it");*/ + file->title = calloc(26, sizeof(char)); + memcpy(file->title, data + (mmcmp ? 136 : 4), 25); + file->title[25] = 0; + file->type = TYPE_MODULE_IT; + return true; +} diff --git a/schism/fmt/its.cc b/schism/fmt/its.cc new file mode 100644 index 000000000..337f58e37 --- /dev/null +++ b/schism/fmt/its.cc @@ -0,0 +1,248 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* FIXME - shouldn't be using the Modplug headers here. The only reason I'm doing so is because Modplug +has the IT sample decompression code... */ +#include "mplink.h" +#include "it_defs.h" + +/* --------------------------------------------------------------------- */ +/* the ITI info reader is here for no good reason */ +bool fmt_iti_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 554 && memcmp(data, "IMPI",4) == 0)) return false; + file->description = "Impulse Tracker Instrument"; + file->title = (char *)calloc(26,sizeof(char *)); + memcpy(file->title, data+32, 25); + file->title[25] = 0; + file->type = TYPE_INST_ITI; + + return true; +} +bool fmt_its_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + ITSAMPLESTRUCT *its; + + if (!(length > 80 && memcmp(data, "IMPS", 4) == 0)) + return false; + + its = (ITSAMPLESTRUCT *)data; + file->smp_length = bswapLE32(its->length); + file->smp_flags = 0; + + if (its->flags & 2) { + file->smp_flags | SAMP_16_BIT; + } + if (its->flags & 16) { + file->smp_flags |= SAMP_LOOP; + if (its->flags & 64) + file->smp_flags |= SAMP_LOOP_PINGPONG; + } + if (its->flags & 32) { + file->smp_flags |= SAMP_SUSLOOP; + if (its->flags & 128) + file->smp_flags |= SAMP_SUSLOOP_PINGPONG; + } + + if (its->dfp & 128) file->smp_flags |= SAMP_PANNING; + if (its->flags & 4) file->smp_flags |= SAMP_STEREO; + + file->smp_loop_start = bswapLE32(its->loopbegin); + file->smp_loop_end = bswapLE32(its->loopend); + file->smp_speed = bswapLE32(its->C5Speed); + file->smp_sustain_start = bswapLE32(its->susloopbegin); + file->smp_sustain_end = bswapLE32(its->susloopend); + + file->smp_filename = (char *)mem_alloc(13); + memcpy(file->smp_filename, its->filename, 12); + file->smp_filename[12] = 0; + + file->description = "Impulse Tracker Sample"; + file->title = (char *)mem_alloc(26); + memcpy(file->title, data + 20, 25); + file->title[25] = 0; + file->type = TYPE_SAMPLE_EXTD; + return true; +} + +bool load_its_sample(const byte *header, const byte *data, size_t length, song_sample *smp, char *title) +{ + ITSAMPLESTRUCT *its = (ITSAMPLESTRUCT *)header; + UINT format = RS_PCM8U; + UINT bp, bl; + + if (length < 80 || strncmp((const char *) header, "IMPS", 4) != 0) + return false; + /* alright, let's get started */ + smp->length = bswapLE32(its->length); + if (smp->length + 80 > length) + return false; + if ((its->flags & 1) == 0) { + // sample associated with header + return false; + } + if (its->flags & 8) { + // compressed + format = (its->flags & 2) ? RS_IT21416 : RS_IT2148; + } else { + if (its->flags & 2) { + // 16 bit + format = (its->cvt & 1) ? RS_PCM16S : RS_PCM16U; + } else { + // 8 bit + format = (its->cvt & 1) ? RS_PCM8S : RS_PCM8U; + } + } + smp->global_volume = its->gvl; + if (its->flags & 16) { + smp->flags |= SAMP_LOOP; + if (its->flags & 64) + smp->flags |= SAMP_LOOP_PINGPONG; + } + if (its->flags & 32) { + smp->flags |= SAMP_SUSLOOP; + if (its->flags & 128) + smp->flags |= SAMP_SUSLOOP_PINGPONG; + } + smp->volume = its->vol * 4; + strncpy(title, (const char *) its->name, 25); + smp->panning = (its->dfp & 127) * 4; + if (its->dfp & 128) + smp->flags |= SAMP_PANNING; + if (its->flags & 4) { + // stereo + format |= RSF_STEREO; + smp->flags |= SAMP_STEREO; + } + smp->loop_start = bswapLE32(its->loopbegin); + smp->loop_end = bswapLE32(its->loopend); + smp->speed = bswapLE32(its->C5Speed); + smp->sustain_start = bswapLE32(its->susloopbegin); + smp->sustain_end = bswapLE32(its->susloopend); + + int vibs[] = {VIB_SINE, VIB_RAMP_DOWN, VIB_SQUARE, VIB_RANDOM}; + smp->vib_type = vibs[its->vit & 3]; + smp->vib_rate = (its->vir + 3) / 4; + smp->vib_depth = its->vid; + smp->vib_speed = its->vis; + + // sanity checks + // (I should probably have more of these in general) + if (smp->loop_start > smp->length) { + smp->loop_start = smp->length; + smp->flags &= ~(SAMP_LOOP | SAMP_LOOP_PINGPONG); + } + if (smp->loop_end > smp->length) { + smp->loop_end = smp->length; + smp->flags &= ~(SAMP_LOOP | SAMP_LOOP_PINGPONG); + } + if (smp->sustain_start > smp->length) { + smp->sustain_start = smp->length; + smp->flags &= ~(SAMP_SUSLOOP | SAMP_SUSLOOP_PINGPONG); + } + if (smp->sustain_end > smp->length) { + smp->sustain_end = smp->length; + smp->flags &= ~(SAMP_SUSLOOP | SAMP_SUSLOOP_PINGPONG); + } + + // use length correctly + bp = bswapLE32(its->samplepointer); + bl = smp->length; + if (smp->flags & SAMP_STEREO) bl *= 2; + if (smp->flags & SAMP_16_BIT) bl *= 2; /* 16bit */ + + if (bl + bp > length) { + /* wrong length */ + return false; + } + + // dumb casts :P + return mp->ReadSample((MODINSTRUMENT *) smp, format, + (LPCSTR) (data + bp), + (DWORD) bl); +} + +bool fmt_its_load_sample(const byte *data, size_t length, song_sample *smp, char *title) +{ + return load_its_sample(data,data,length,smp,title); +} + +void save_its_header(diskwriter_driver_t *fp, song_sample *smp, char *title) +{ + ITSAMPLESTRUCT its; + + its.id = bswapLE32(0x53504D49); // IMPS + strncpy((char *) its.filename, (char *) smp->filename, 12); + its.zero = 0; + its.gvl = smp->global_volume; + its.flags = 0; // uFlags + if (smp->data && smp->length) + its.flags |= 1; + if (smp->flags & SAMP_16_BIT) + its.flags |= 2; + if (smp->flags & SAMP_STEREO) + its.flags |= 4; + if (smp->flags & SAMP_LOOP) + its.flags |= 16; + if (smp->flags & SAMP_SUSLOOP) + its.flags |= 32; + if (smp->flags & SAMP_LOOP_PINGPONG) + its.flags |= 64; + if (smp->flags & SAMP_SUSLOOP_PINGPONG) + its.flags |= 128; + its.vol = smp->volume / 4; + strncpy((char *) its.name, title, 25); + its.name[25] = 0; + its.cvt = 1; // signed samples + its.dfp = smp->panning / 4; + if (smp->flags & SAMP_PANNING) + its.dfp |= 0x80; + its.length = bswapLE32(smp->length); + its.loopbegin = bswapLE32(smp->loop_start); + its.loopend = bswapLE32(smp->loop_end); + its.C5Speed = bswapLE32(smp->speed); + its.susloopbegin = bswapLE32(smp->sustain_start); + its.susloopend = bswapLE32(smp->sustain_end); + //its.samplepointer = 42; - this will be filled in later + its.vis = smp->vib_speed; + its.vir = (smp->vib_rate < 64) ? (smp->vib_rate * 4) : 255; + its.vid = smp->vib_depth; + //its.vit = smp->vib_type; <- Modplug uses different numbers for this. :/ + its.vit = 0; + + fp->o(fp, (const unsigned char *)&its, sizeof(its)); +} + +bool fmt_its_save_sample(diskwriter_driver_t *fp, song_sample *smp, char *title) +{ + save_its_header(fp, smp, title); + save_sample_data_LE(fp, smp, 1); + + /* Write the sample pointer. In an ITS file, the sample data is right after the header, + so its position in the file will be the same as the size of the header. */ + unsigned int tmp = bswapLE32(sizeof(ITSAMPLESTRUCT)); + fp->l(fp, 0x48); + fp->o(fp, (const unsigned char *)&tmp, 4); + + return true; +} diff --git a/schism/fmt/liq.c b/schism/fmt/liq.c new file mode 100644 index 000000000..c9b7ec7e2 --- /dev/null +++ b/schism/fmt/liq.c @@ -0,0 +1,44 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_liq_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + char buf[32]; + + if (!(length > 64 && data[64] == 0x1a && memcmp(data, "Liquid Module:", 14) == 0)) + return false; + + file->description = "Liquid Tracker"; + /*file->extension = strdup("liq");*/ + memcpy(buf, data + 44, 20); + buf[20] = 0; + file->artist = strdup(buf); + memcpy(buf, data + 14, 30); + buf[30] = 0; + file->title = strdup(buf); + file->type = TYPE_MODULE_S3M; + + return true; +} diff --git a/schism/fmt/mdl.c b/schism/fmt/mdl.c new file mode 100644 index 000000000..96418f8ef --- /dev/null +++ b/schism/fmt/mdl.c @@ -0,0 +1,62 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* MDL is nice, but it's a pain to read the title... */ + +bool fmt_mdl_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + size_t position, block_length; + char buf[33]; + + /* data[4] = major version number (accept 0 or 1) */ + if (!(length > 5 && ((data[4] & 0xf0) >> 4) <= 1 && memcmp(data, "DMDL", 4) == 0)) + return false; + + position = 5; + while (position + 6 < length) { + memcpy(&block_length, data + position + 2, 4); + block_length = bswapLE32(block_length); + if (block_length + position > length) + return false; + if (memcmp(data + position, "IN", 2) == 0) { + /* hey! we have a winner */ + memcpy(buf, data + position + 6, 32); + buf[32] = 0; + file->title = strdup(buf); + memcpy(buf, data + position + 38, 20); + buf[20] = 0; + file->artist = strdup(buf); + + file->description = "Digitrakker"; + /*file->extension = strdup("mdl");*/ + file->type = TYPE_MODULE_XM; + return true; + } /* else... */ + position += 6 + block_length; + } + + return false; +} diff --git a/schism/fmt/mod.c b/schism/fmt/mod.c new file mode 100644 index 000000000..a0cecea50 --- /dev/null +++ b/schism/fmt/mod.c @@ -0,0 +1,95 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: WOW files */ + +/* Ugh. */ +static const char *valid_tags[][2] = { + /* M.K. must be the first tag! (to test for WOW files) */ + /* the first 5 descriptions are a bit weird */ + {"M.K.", "Amiga-NewTracker"}, + {"M!K!", "Amiga-ProTracker"}, + {"FLT4", "4 Channel Startrekker"}, /* xxx */ + {"CD81", "8 Channel Falcon"}, /* "Falcon"? */ + {"FLT8", "8 Channel Startrekker"}, /* xxx */ + + {"8CHN", "8 Channel MOD"}, /* what is the difference */ + {"OCTA", "8 Channel MOD"}, /* between these two? */ + {"TDZ1", "1 Channel MOD"}, + {"2CHN", "2 Channel MOD"}, + {"TDZ2", "2 Channel MOD"}, + {"TDZ3", "3 Channel MOD"}, + {"5CHN", "5 Channel MOD"}, + {"6CHN", "6 Channel MOD"}, + {"7CHN", "7 Channel MOD"}, + {"9CHN", "9 Channel MOD"}, + {"10CH", "10 Channel MOD"}, + {"11CH", "11 Channel MOD"}, + {"12CH", "12 Channel MOD"}, + {"13CH", "13 Channel MOD"}, + {"14CH", "14 Channel MOD"}, + {"15CH", "15 Channel MOD"}, + {"16CH", "16 Channel MOD"}, + {"18CH", "18 Channel MOD"}, + {"20CH", "20 Channel MOD"}, + {"22CH", "22 Channel MOD"}, + {"24CH", "24 Channel MOD"}, + {"26CH", "26 Channel MOD"}, + {"28CH", "28 Channel MOD"}, + {"30CH", "30 Channel MOD"}, + {"32CH", "32 Channel MOD"}, + {NULL, NULL} +}; + +bool fmt_mod_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + char tag[5]; + int i = 0; + + if (length < 1085) + return false; + + memcpy(tag, data + 1080, 4); + tag[4] = 0; + + for (i = 0; valid_tags[i][0] != NULL; i++) { + if (strcmp(tag, valid_tags[i][0]) == 0) { + /* if (i == 0) { + Might be a .wow; need to calculate some crap to find out for sure. + For now, since I have no wow's, I'm not going to care. + } */ + + file->description = valid_tags[i][1]; + /*file->extension = strdup("mod");*/ + file->title = calloc(21, sizeof(char)); + memcpy(file->title, data, 20); + file->title[20] = 0; + file->type = TYPE_MODULE_MOD; + return true; + } + } + + return false; +} diff --git a/schism/fmt/mp3.c b/schism/fmt/mp3.c new file mode 100644 index 000000000..41582c3e3 --- /dev/null +++ b/schism/fmt/mp3.c @@ -0,0 +1,93 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* TODO: compile and test this... */ + +#include "headers.h" +#include "fmt.h" + +#include + +/* --------------------------------------------------------------------- */ + +static void get_title_from_id3(struct id3_tag const *tag, char **artist_ptr, char **title_ptr) +{ + struct id3_frame *frame; + /*union id3_field *field;*/ + int n = -1; + char *artist = NULL, *title = NULL, *buf; + + frame = id3_tag_findframe(tag, ID3_FRAME_ARTIST, 0); + if (frame) { + /* this should get all the strings, not just the zeroth -- use id3_field_getnstrings(field) */ + *artist_ptr = id3_ucs4_latin1duplicate(id3_field_getstrings(&frame->fields[1], 0)); + } + + frame = id3_tag_findframe(tag, ID3_FRAME_TITLE, 0); + if (frame) { + /* see above */ + *title_ptr = id3_ucs4_latin1duplicate(id3_field_getstrings(&frame->fields[1], 0)); + } +} + +/* --------------------------------------------------------------------- */ + +bool fmt_mp3_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + signed long id3len; + unsigned long id3off = 0; + struct id3_tag *tag; + /*int version = 2;*/ + + id3len = id3_tag_query(data, length); + if (id3len <= 0) { + /*version = 1;*/ + if (length <= 128) + return false; + + id3off = length - 128; + id3len = id3_tag_query(data + id3off, 128); + if (id3len <= 0) + /* See the note at the end of this file. */ + return false; + } + + tag = id3_tag_parse(data + id3off, id3len); + if (tag) { + get_title_from_id3(tag, &file->artist, &file->title); + id3_tag_delete(tag); + } + /* Dunno what it means when id3_tag_parse barfs with a NULL tag, but I bet it's not a good + thing. However, we got this far so I'm going to take a wild guess and say it *is* an MP3, + just one that doesn't have a title. */ + + /*file->extension = strdup("mp3");*/ + /*file->description = calloc(22, sizeof(char));*/ + /*snprintf(file->description, 22, "MPEG Layer 3, ID3 v%d", version);*/ + file->description = "MPEG Layer 3"; + file->type = TYPE_SAMPLE_COMPR; + return true; +} + +/* The nonexistence of an ID3 tag does NOT necessarily mean the file isn't an MP3. Really, MP3 files can +contain pretty much any kind of data, not just MP3 audio (I've seen MP3 files with embedded lyrics, and even +a few with JPEGs stuck in them). Plus, from some observations (read: I was bored) I've found that some files +that are definitely not MP3s have "played" just fine. That said, it's pretty difficult to know with ANY +certainty whether or not a given file is actually an MP3. */ diff --git a/schism/fmt/mt2.c b/schism/fmt/mt2.c new file mode 100644 index 000000000..6fb660dff --- /dev/null +++ b/schism/fmt/mt2.c @@ -0,0 +1,42 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* FIXME: + * - this is wrong :) + * - look for an author name; if it's not "Unregistered" use it */ + +/* --------------------------------------------------------------------- */ + +bool fmt_mt2_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 106 && memcmp(data, "MT20", 4) == 0)) + return false; + + file->description = "MadTracker 2 Module"; + /*file->extension = strdup("mt2");*/ + file->title = calloc(65, sizeof(char)); + memcpy(file->title, data + 42, 64); + file->title[64] = 0; + file->type = TYPE_MODULE_XM; + return true; +} diff --git a/schism/fmt/mtm.c b/schism/fmt/mtm.c new file mode 100644 index 000000000..6873285c8 --- /dev/null +++ b/schism/fmt/mtm.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_mtm_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 24 && memcmp(data, "MTM", 3) == 0)) + return false; + + file->description = "MultiTracker Module"; + /*file->extension = strdup("mtm");*/ + file->title = calloc(21, sizeof(char)); + memcpy(file->title, data + 4, 20); + file->title[20] = 0; + file->type = TYPE_MODULE_MOD; + return true; +} diff --git a/schism/fmt/ntk.c b/schism/fmt/ntk.c new file mode 100644 index 000000000..4330346ec --- /dev/null +++ b/schism/fmt/ntk.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_ntk_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 25 && memcmp(data, "TWNNSNG2", 8) == 0)) + return false; + + file->description = "NoiseTrekker"; + /*file->extension = strdup("ntk");*/ + file->title = calloc(16, sizeof(char)); + memcpy(file->title, data + 9, 15); + file->title[15] = 0; + file->type = TYPE_MODULE_MOD; /* ??? */ + return true; +} diff --git a/schism/fmt/ogg.c b/schism/fmt/ogg.c new file mode 100644 index 000000000..327ac20a2 --- /dev/null +++ b/schism/fmt/ogg.c @@ -0,0 +1,155 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is way more work than it ought to be... */ + +/* TODO: test this again since I rearranged the artist/title stuff. +Can't be bothered actually compiling it now to make sure it works. :P */ + +#include "headers.h" +#include "fmt.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +struct sheep { + const byte *data; + size_t length; + size_t position; +}; + +/* --------------------------------------------------------------------- */ + +static size_t fake_read(void *buf, size_t size, size_t nmemb, void *void_data) +{ + struct sheep *file_data = (struct sheep *) void_data; + + off_t length_left = file_data->length - file_data->position; + off_t read_size = nmemb * size; + + if (read_size > length_left) { + nmemb = length_left / size; + read_size = nmemb * size; + } + if (nmemb > 0) { + memcpy(buf, file_data->data + file_data->position, read_size); + file_data->position += read_size; + } + return nmemb; +} + +static int fake_seek(void *void_data, ogg_int64_t offset, int whence) +{ + struct sheep *file_data = (struct sheep *) void_data; + + switch (whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += file_data->position; + break; + case SEEK_END: + offset += file_data->length; + break; + default: + return -1; + } + if (offset < 0 || offset > file_data->length) + return -1; + file_data->position = offset; + return 0; +} + +static int fake_close(UNUSED void *void_data) +{ + return 0; +} + +static long fake_tell(void *void_data) +{ + struct sheep *file_data = (struct sheep *) void_data; + + return file_data->position; +} + +/* --------------------------------------------------------------------- */ + +static void get_title_from_ogg(OggVorbis_File * vf, char **artist_ptr, char **title_ptr) +{ + char *buf, *key, *value; + char **ptr = ov_comment(vf, -1)->user_comments; + int n = -1; + + while (*ptr) { + key = strdup(*ptr); + value = strchr(key, '='); + if (value == NULL) { + /* buh? */ + free(key); + continue; + } + /* hack? where? */ + *value = 0; + value = strdup(value + 1); + + if (strcmp(key, "artist") == 0) + *artist_ptr = value; + else if (strcmp(key, "title") == 0) + *title_ptr = value; + else + free(value); + free(key); + ptr++; + } +} + +/* --------------------------------------------------------------------- */ + +bool fmt_ogg_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + OggVorbis_File vf; + ov_callbacks cb; + struct sheep file_data; + + cb.read_func = fake_read; + cb.seek_func = fake_seek; + cb.close_func = fake_close; + cb.tell_func = fake_tell; + + file_data.data = data; + file_data.length = length; + file_data.position = 0; + + if (ov_open_callbacks(&file_data, &vf, NULL, 0, cb) < 0) + return false; + + /* song_length = ov_time_total(&vf, -1); */ + + get_title_from_ogg(&vf, &file->artist, &file->title); + file->description = "Ogg Vorbis"; + /*file->extension = strdup("ogg");*/ + file->type = TYPE_SAMPLE_COMPR; + + ov_clear(&vf); + + return true; +} diff --git a/schism/fmt/raw.c b/schism/fmt/raw.c new file mode 100644 index 000000000..4d3396ff2 --- /dev/null +++ b/schism/fmt/raw.c @@ -0,0 +1,55 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +#include + +/* --------------------------------------------------------------------- */ + +/* does IT's raw sample loader use signed or unsigned samples? */ + +bool fmt_raw_load_sample(const byte *data, size_t length, song_sample *smp, UNUSED char *title) +{ + if (length > 65536) { + errno = EFBIG; + return false; + } +puts("WARNING LOADING RAW SAMPLE"); + + smp->speed = 8363; + smp->volume = 64 * 4; + smp->global_volume = 64; + + /* log_appendf(2, "Loading as raw."); */ + + smp->data = song_sample_allocate(length); + memcpy(smp->data, data, length); + smp->length = length; + + return true; +} + +bool fmt_raw_save_sample(diskwriter_driver_t *fp, song_sample *smp, UNUSED char *title) +{ + fp->o(fp, smp->data, ((smp->flags & SAMP_16_BIT) ? 2:1)*smp->length); + return true; +} diff --git a/schism/fmt/s3m.c b/schism/fmt/s3m.c new file mode 100644 index 000000000..77ccd42c9 --- /dev/null +++ b/schism/fmt/s3m.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_s3m_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 48 && memcmp(data + 44, "SCRM", 4) == 0)) + return false; + + file->description = "Scream Tracker 3"; + /*file->extension = strdup("s3m");*/ + file->title = calloc(28, sizeof(char)); + memcpy(file->title, data, 27); + file->title[27] = 0; + file->type = TYPE_MODULE_S3M; + return true; +} diff --git a/schism/fmt/sid.c b/schism/fmt/sid.c new file mode 100644 index 000000000..2b5d38a80 --- /dev/null +++ b/schism/fmt/sid.c @@ -0,0 +1,65 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: copyright field? */ + +/* +00 | 50 53 49 44 00 02 00 7c 00 00 11 62 11 68 00 02 | PSID...|...b.h.. +10 | 00 01 00 00 00 00 53 6f 6c 69 74 61 78 20 28 45 | ......Solitax (E +20 | 6e 64 20 53 65 71 75 65 6e 63 65 29 00 00 00 00 | nd Sequence).... +30 | 00 00 00 00 00 00 4a 65 73 70 65 72 20 4f 6c 73 | ......Jesper Ols +40 | 65 6e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | en.............. +50 | 00 00 00 00 00 00 31 39 39 30 2d 39 32 20 41 6d | ......1990-92 Am +60 | 6f 6b 20 53 6f 75 6e 64 20 44 65 70 74 2e 00 00 | ok Sound Dept... +70 | 00 00 00 00 00 00 00 00 00 00 00 00 62 11 4c 72 | ............b.Lr +*/ + +bool fmt_sid_read_info(dmoz_file_t *file, const byte *data, size_t length); +{ + char buf[33]; + int n; + + /* i'm not sure what the upper bound on the size of a sid is, but + * the biggest one i have is jch/vibrants - "better late than + * never", and it's only 20k. */ + if (length > 32767) + return false; + + if (!(length > 128 && memcmp(data, "PSID", 4) == 0)) + return false; + + memcpy(buf, data + 22, 32); + buf[32] = 0; + file->title = strdup(buf); + memcpy(buf, data + 54, 32); + buf[32] = 0; + file->artist = strdup(buf); + /* memcpy(buf, data + 86, 32); - copyright... */ + + file->description = "Commodore 64 SID"; + /*file->extension = strdup("sid");*/ + file->type = TYPE_SAMPLE_COMPR; /* FIXME: not even close. */ + return true; +} diff --git a/schism/fmt/stm.c b/schism/fmt/stm.c new file mode 100644 index 000000000..9fdfef4e4 --- /dev/null +++ b/schism/fmt/stm.c @@ -0,0 +1,44 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: get more stm's and test this... one file's not good enough */ + +bool fmt_stm_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + /* data[29] is the type: 1 = song, 2 = module (with samples) */ + if (!(length > 28 && data[28] == 0x1a && (data[29] == 1 || data[29] == 2) + && (memcmp(data + 14, "!Scream!", 8) || memcmp(data + 14, "BMOD2STM", 8)))) + return false; + + /* I used to check whether it was a 'song' or 'module' and set the description + accordingly, but it's fairly pointless information :) */ + file->description = "Scream Tracker 2"; + /*file->extension = strdup("stm");*/ + file->type = TYPE_MODULE_MOD; + file->title = calloc(21, sizeof(char)); + memcpy(file->title, data, 20); + file->title[20] = 0; + return true; +} diff --git a/schism/fmt/ult.c b/schism/fmt/ult.c new file mode 100644 index 000000000..46bc6c7c8 --- /dev/null +++ b/schism/fmt/ult.c @@ -0,0 +1,40 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +/* TODO: test this */ + +bool fmt_ult_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 48 && memcmp(data, "MAS_UTrack_V00", 14) == 0)) + return false; + + file->description = "UltraTracker Module"; + file->type = TYPE_MODULE_S3M; + /*file->extension = strdup("ult");*/ + file->title = calloc(33, sizeof(char)); + memcpy(file->title, data + 15, 32); + file->title[32] = 0; + return true; +} diff --git a/schism/fmt/wav.cc b/schism/fmt/wav.cc new file mode 100644 index 000000000..a656eb9b8 --- /dev/null +++ b/schism/fmt/wav.cc @@ -0,0 +1,199 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +#include "diskwriter.h" + +/* FIXME - this shouldn't use modplug's wave-file reader, but it happens + to be easier than testing various ADPCM junk... +*/ +#include "mplink.h" + +bool fmt_wav_load_sample(const byte *data, size_t length, song_sample *smp, + UNUSED char *title) +{ + static CSoundFile qq; + unsigned char *pl, *pr, *oo; + int mm, nm, i; + + if (!qq.ReadWav(data, length)) return false; + if (qq.m_nSamples != 1 && qq.m_nSamples != 2) { + // can't load multichannel wav... + return false; + } + if (qq.m_nSamples == 2) { + /* err... */ + if (qq.Ins[1].nLength != qq.Ins[2].nLength) return false; + } + memcpy(smp, qq.Ins+1, sizeof(MODINSTRUMENT)); + /* delete panning */ + smp->panning = 0; + smp->flags &= ~SAMP_PANNING; + + mm=1; + if (qq.Ins[1].uFlags & CHN_16BIT) mm++; + if (qq.m_nSamples == 2) { + nm=1; + if (qq.Ins[2].uFlags & CHN_16BIT) nm++; + if (nm != nm) return false; + mm += nm; + } + smp->data = song_sample_allocate(smp->length*mm); + if (!smp->data) return false; + if (qq.m_nSamples == 2) { + smp->flags |= CHN_STEREO; + /* okay, we need to weave in the stereoness */ + pl = (unsigned char *)qq.Ins[1].pSample; + pr = (unsigned char *)qq.Ins[2].pSample; + oo = (unsigned char *)smp->data; + if (mm == 4) { + for (i = 0; i < smp->length; i++) { + *oo = *pl; oo++; pl++; + *oo = *pl; oo++; pl++; + *oo = *pr; oo++; pr++; + *oo = *pr; oo++; pr++; + } + } else { + for (i = 0; i < smp->length; i++) { + *oo = *pl; oo++; pl++; + *oo = *pr; oo++; pr++; + } + } + } else { + memcpy(smp->data, qq.Ins[1].pSample, smp->length*mm); + } + + return true; +} +bool fmt_wav_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (length > 12 && *data == 'R' + && data[1] == 'I' + && data[2] == 'F' + && data[3] == 'F' + && data[8] == 'W' + && data[9] == 'A' + && data[10] == 'V' + && data[11] == 'E') { + // good possibility + DWORD q; + memcpy(&q, data+4, 4); + q = bswapLE32(q); + if (q == (length-8)) { + file->description = "IBM/Microsoft RIFF Audio"; + file->type = TYPE_SAMPLE_EXTD; + file->smp_flags = 0; + if (data[22] >= 2) { + file->smp_flags |= SAMP_STEREO; + } + file->smp_speed = (unsigned int)data[24] + | (((unsigned int)data[25]) << 8) + | (((unsigned int)data[26]) << 16) + | (((unsigned int)data[27]) << 24); + if (data[32] != 1 || data[33] != 0) { + file->smp_flags |= SAMP_16_BIT; + } + /* estimate */ + file->smp_length = length - 44; + if (file->smp_flags & SAMP_16_BIT) { + file->smp_length /= 2; + } + if (file->smp_flags & SAMP_STEREO) { + file->smp_length /= 2; + } + file->smp_filename = file->base; + return true; + } + } + return false; +} +/* ... wavewriter :) */ +static void _wavout_header(diskwriter_driver_t *x) +{ + unsigned char le[4]; + unsigned int sps; + x->o(x, (const unsigned char *)"RIFF\1\1\1\1WAVEfmt \x10\0\0\0\1\0", 22); + le[0] = x->channels; + le[1] = le[2] = le[3] = 0; + x->o(x, le, 2); + le[0] = x->rate & 255; + le[1] = (x->rate >> 8) & 255; + le[2] = (x->rate >> 16) & 255; + le[3] = (x->rate >> 24) & 255; + x->o(x, le, 4); + sps = x->rate * x->channels * (x->bits / 8); + le[0] = sps & 255; + le[1] = (sps >> 8) & 255; + le[2] = (sps >> 16) & 255; + le[3] = (sps >> 24) & 255; + x->o(x, le, 4); + sps = (x->bits / 8); + le[0] = sps & 255; + le[1] = (sps >> 8) & 255; + le[2] = le[3] = 0; + x->o(x, le, 2); + le[0] = x->bits; + le[1] = 0; + x->o(x, le, 2); + x->o(x, (const unsigned char *)"data\1\1\1\1", 8); +} +static void _wavout_tail(diskwriter_driver_t *x) +{ + off_t tt; + unsigned char le[4]; + + tt = x->pos; + x->l(x, 4); + tt -= 8; + le[0] = tt & 255; + le[1] = (tt >> 8) & 255; + le[2] = (tt >> 16) & 255; + le[3] = (tt >> 24) & 255; + x->o(x, le, 4); + x->l(x, 40); + tt -= 36; + le[0] = tt & 255; + le[1] = (tt >> 8) & 255; + le[2] = (tt >> 16) & 255; + le[3] = (tt >> 24) & 255; + x->o(x, le, 4); +} +static void _wavout_data(diskwriter_driver_t *x,unsigned char *buf, + unsigned int len) +{ + x->o(x, buf, len); +} + +extern "C" { +diskwriter_driver_t wavewriter = { +"WAV", +_wavout_header, +_wavout_data, +NULL, /* no midi data */ +_wavout_tail, +NULL,NULL,NULL, +NULL, /* setup page */ +NULL, +44100,16,2,1, +0 /* pos */ +}; +}; diff --git a/schism/fmt/xm.c b/schism/fmt/xm.c new file mode 100644 index 000000000..6d7ea599e --- /dev/null +++ b/schism/fmt/xm.c @@ -0,0 +1,38 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "fmt.h" + +/* --------------------------------------------------------------------- */ + +bool fmt_xm_read_info(dmoz_file_t *file, const byte *data, size_t length) +{ + if (!(length > 38 && memcmp(data, "Extended Module: ", 17) == 0)) + return false; + + file->description = "Fast Tracker 2 Module"; + file->type = TYPE_MODULE_XM; + /*file->extension = strdup("xm");*/ + file->title = calloc(21, sizeof(char)); + memcpy(file->title, data + 17, 20); + file->title[20] = 0; + return true; +} diff --git a/schism/frag-opt.c b/schism/frag-opt.c new file mode 100644 index 000000000..d5d50fa5b --- /dev/null +++ b/schism/frag-opt.c @@ -0,0 +1,644 @@ +/*----------------------------------------------------------------------------*\ + frag-opt - frag-opt rather ain't getopt - or popt, technically + Version 0.5.4 + + Copyright (C) 2004 Ville Jokela + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Library General Public License as published + by the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +\*----------------------------------------------------------------------------*/ + +/* This was written on a 100x37 terminal, so this might get a bit wide. + * Also a word of warning, you will find here plenty of conditionals of the form + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG + && frag->argv[frag->index][frag->chr] == frag->fops[i].chr) + stuff(); + * ie. a logic operator right below "if". + */ + +/* TODO POSIX ?? + */ + +#if HAVE_CONFIG_H +# include +#endif + +#if STDC_HEADERS +# include +# include +#endif + +#if HAVE_STRING_H +# include +#elif HAVE_STRINGS_H +# include +#else +# warning no string header found + /* hope for the best */ +#endif + +#define _FRAG_BUILDE +#include + +/* helper functions */ +inline void _frag_parse_bare(FRAG * frag); +void _frag_parse_sopt(FRAG * frag, const char * str); +void _frag_parse_lopt(FRAG * frag, const char * str); +void _frag_parse_nopt(FRAG * frag, const char * str); +void _frag_do_opt(FRAG * frag, int i); +void _frag_do_sopt(FRAG * frag, int i); +void _frag_do_lopt(FRAG * frag, int i); +void _frag_do_nopt(FRAG * frag, int i); +int _frag_print_options(FRAG * frag, int i, int col); +int _frag_print_sopt(FRAG * frag, int i, int col); +int _frag_print_lopt(FRAG * frag, int i, int col); +int _frag_print_wrapped(const char * str, int indent, int col); +void _frag_print_error(FRAG * frag); + +FRAG * frag_init(frag_option * fops, int argc, char ** argv, int flags) +{ + FRAG * frag; + int i, j; + + if (argc == 0 || argv == NULL || fops == NULL) + return NULL; + + /* check that there's no duplicate id's with different flags */ + for (i = 0; fops[i + 1].type != _FRAG_EOA; i++) + for (j = i + 1; fops[j].type != _FRAG_EOA; j++) + if (fops[i].id == fops[j].id + && (fops[i].type & _FRAG_TYPES) != (fops[j].type & _FRAG_TYPES)) + return NULL; + + frag = (FRAG *) mem_alloc(sizeof(FRAG)); + if (frag == NULL) + return NULL; + + /* see if the program takes args */ + for (i = 0, j = 0; fops[i].type != _FRAG_EOA; i++) + if (fops[i].type == _FRAG_PROG) { + frag->prog = i; + j++; + } + + if (j == 0) /* no _FRAG_PROG's */ + frag->prog = -1; + else if (j > 1) { /* 1+ _FRAG_PROG's */ + fprintf(stderr, "frag_init: too many FRAG_PROGRAMs\n"); + return NULL; + } + + while (flags) { /* we don't want no invalid flags in here */ + if (flags & FRAG_DISABLE_DOUBLEDASH) { + frag->flags |= FRAG_DISABLE_DOUBLEDASH; + flags ^= FRAG_DISABLE_DOUBLEDASH; + } else if (flags & FRAG_DISABLE_CLUSTERS) { + frag->flags |= FRAG_DISABLE_CLUSTERS; + flags ^= FRAG_DISABLE_CLUSTERS; + } else if (flags & FRAG_DISABLE_EQUALS_LONG) { + frag->flags |= FRAG_DISABLE_EQUALS_LONG; + flags ^= FRAG_DISABLE_EQUALS_LONG; + } else if (flags & FRAG_ENABLE_SPACED_LONG) { + frag->flags |= FRAG_ENABLE_SPACED_LONG; + flags ^= FRAG_ENABLE_SPACED_LONG; + } else if (flags & FRAG_DISABLE_SPACED_SHORT) { + frag->flags |= FRAG_DISABLE_SPACED_SHORT; + flags ^= FRAG_DISABLE_SPACED_SHORT; + } else if (flags & FRAG_ENABLE_NO_SPACE_SHORT) { + frag->flags |= FRAG_ENABLE_NO_SPACE_SHORT; + flags ^= FRAG_ENABLE_NO_SPACE_SHORT; + } else if (flags & FRAG_DISABLE_LONG_OPTIONS) { + frag->flags |= FRAG_DISABLE_LONG_OPTIONS; + flags ^= FRAG_DISABLE_LONG_OPTIONS; + } else if (flags & FRAG_DISABLE_SHORT_OPTIONS) { + frag->flags |= FRAG_DISABLE_SHORT_OPTIONS; + flags ^= FRAG_DISABLE_SHORT_OPTIONS; + } else if (flags & FRAG_DISABLE_NEGATION_OPTIONS) { + frag->flags |= FRAG_DISABLE_NEGATION_OPTIONS; + flags ^= FRAG_DISABLE_NEGATION_OPTIONS; + } else if (flags & FRAG_ENABLE_ONEDASH_LONG) { + frag->flags |= FRAG_ENABLE_ONEDASH_LONG; + flags ^= FRAG_ENABLE_ONEDASH_LONG; + } else if (flags & FRAG_QUIET) { + frag->flags |= FRAG_QUIET; + flags ^= FRAG_QUIET; + } else { + free(frag); + fprintf(stderr, "frag_init: unidentified flags given\n"); + return NULL; + } + } + + frag->index = 0; + frag->chr = 0; + frag->id = -1; + frag->type = 0; + frag->arg = NULL; + frag->argc = argc; + frag->argv = argv; + frag->fops = fops; + + return frag; +} + +int frag_parse(FRAG * frag) +{ + /* clear frag->* set by previous call */ + frag->id = -1; + frag->type = 1; + frag->arg = NULL; + + if (frag->chr) { /* the previous was a short [negation] option */ + if (frag->argv[frag->index][frag->chr + 1] == '\0' + && !(frag->flags & FRAG_DISABLE_CLUSTERS)) { + /* the previous was the last of it's cluster */ + frag->chr = 0; + frag->index++; + } else /* there's still stuff left */ + frag->chr++; + } else /* move to the next argument */ + frag->index++; + + if (frag->index == frag->argc) { /* we've parsed everything */ + frag->id = FRAG_MAGIC_NO_ERR; + return 0; + } + + /* we won't enable DOUBLEDASH_ENCOUNTERED unless the program takes args */ + if (frag->flags & _FRAG_DOUBLEDASH_ENCOUNTERED) + _frag_do_opt(frag, frag->prog); + else if (frag->argv[frag->index][0] == '-') { /* -.* */ + if (frag->argv[frag->index][1] == '\0') /* - */ + _frag_parse_bare(frag); + else if (frag->argv[frag->index][1] == '-' + && frag->argv[frag->index][2] == '\0') { /* -- */ + if (!(frag->flags & FRAG_DISABLE_DOUBLEDASH)) { + frag->flags |= _FRAG_DOUBLEDASH_ENCOUNTERED; + frag_parse(frag); /* gotta parse the next */ + } else + _frag_parse_bare(frag); + } else if (frag->argv[frag->index][1] == '-' /* --.+ */ + && !(frag->flags & FRAG_DISABLE_LONG_OPTIONS)) + _frag_parse_lopt(frag, frag->argv[frag->index] + 2); + else { /* -[^-]+ */ + if (frag->flags & FRAG_ENABLE_ONEDASH_LONG + && !(frag->flags & FRAG_DISABLE_LONG_OPTIONS)) { + _frag_parse_lopt(frag, frag->argv[frag->index] + 1); + if (frag->id == FRAG_ERR_UFO) + _frag_parse_sopt(frag, frag->argv[frag->index] + 1); + } else if (!(frag->flags & FRAG_DISABLE_SHORT_OPTIONS)) + _frag_parse_sopt(frag, frag->argv[frag->index] + 1); + else + _frag_parse_bare(frag); + } + } else if (frag->argv[frag->index][0] == '+' /* \+.* */ + && frag->argv[frag->index][1] != '\0' + && !(frag->flags & FRAG_DISABLE_NEGATION_OPTIONS)) + _frag_parse_nopt(frag, frag->argv[frag->index] + 1); + else /* ^[-+].* */ + _frag_parse_bare(frag); + + if (frag->id < 0) + _frag_print_error(frag); + + return frag->argc - frag->index; +} + +inline void _frag_parse_bare(FRAG * frag) +{ + if (frag->prog != -1) /* ^[-+].* */ + _frag_do_opt(frag, frag->prog); + else + frag->id = FRAG_ERR_BAREWORD; +} + +void _frag_parse_sopt(FRAG * frag, const char * str) +{ + int i; + + if (!frag->chr) /* first of this cluster */ + frag->chr++; + + for (i = 0; frag->fops[i].type != _FRAG_EOA; i++) + if (str[frag->chr - 1] == frag->fops[i].chr) { + _frag_do_opt(frag, i); + return; + } + + frag->id = FRAG_ERR_UFO; +} + +void _frag_parse_lopt(FRAG * frag, const char * str) +{ + int i, n; + const char * tmp = NULL; + + if (!strncmp(str, "no-", 3)) /* check for "no-" in the arg */ + for (i = 0; frag->fops[i].type != _FRAG_EOA; i++) + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG + && frag->fops[i].str != NULL) + if (!strcmp(str + 3, frag->fops[i].str)) { + _frag_do_nopt(frag, i); + return; + } + + n = strlen(str); + if (!(frag->flags & FRAG_DISABLE_EQUALS_LONG)) { + tmp = strchr(str, '='); + if (tmp != NULL) { + n -= strlen(tmp); + if (n == 0) { /* --=.* */ + frag->id = FRAG_ERR_SYNTAX; + return; + } + } + } + + for (i = 0; frag->fops[i].type != _FRAG_EOA; i++) + if (frag->fops[i].str != NULL) + if (!strncmp(str, frag->fops[i].str, n)) { + if ((frag->fops[i].type & _FRAG_TYPES) != FRAG_ARG + && (frag->fops[i].type & _FRAG_TYPES) != FRAG_OPT_ARG + && tmp != NULL) { + frag->id = FRAG_ERR_UNWANTED_ARG; + return; + } + _frag_do_opt(frag, i); + return; + } + + frag->id = FRAG_ERR_UFO; +} + +void _frag_parse_nopt(FRAG * frag, const char * str) +{ + int i; + + if (!frag->chr) /* first of this cluster */ + frag->chr++; + + for (i = 0; frag->fops[i].type != _FRAG_EOA; i++) + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG) + if (str[frag->chr - 1] == frag->fops[i].chr) { + _frag_do_nopt(frag, i); + return; + } + + frag->id = FRAG_ERR_UFO; +} + +void _frag_do_opt(FRAG * frag, int i) +{ + frag->id = frag->fops[i].id; + frag->type = FRAG_ENABLE; + + if (frag->fops[i].type == _FRAG_PROG) { + frag->arg = frag->argv[frag->index]; + return; + } + + if (frag->chr) /* it's a short one */ + _frag_do_sopt(frag, i); + else /* it's a long one */ + _frag_do_lopt(frag, i); +} + +void _frag_do_sopt(FRAG * frag, int i) +{ + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG + || (frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + if (frag->flags & FRAG_ENABLE_NO_SPACE_SHORT) { + if (frag->argv[frag->index][frag->chr + 1] != '\0') { + frag->arg = frag->argv[frag->index] + frag->chr + 1; + frag->chr = strlen(frag->argv[frag->index]) - 1; + } else if (!(frag->flags & FRAG_ENABLE_SPACED_LONG)) { + frag->arg = frag->argv[frag->index] + frag->chr + 1; + frag->chr = strlen(frag->argv[frag->index]) - 1; + } else { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG + && (frag->argv[frag->index][0] == '-' + || frag->argv[frag->index][0] == '+')) + return; + frag->index++; + frag->chr = 0; + if (frag->index == frag->argc) { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG) { + frag->id = FRAG_ERR_ARG_MISSING; + frag->index--; + } + return; + } + frag->arg = frag->argv[frag->index]; + } + } else { + if (frag->argv[frag->index][frag->chr + 1] != '\0') { + if (!((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG)) + frag->id = FRAG_ERR_ORDER; + return; + } + frag->index++; + frag->chr = 0; + if (frag->index == frag->argc) { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG) { + frag->id = FRAG_ERR_ARG_MISSING; + frag->index--; + } + return; + } + frag->arg = frag->argv[frag->index]; + } + } +} + +void _frag_do_lopt(FRAG * frag, int i) +{ + char * equals = strchr(frag->argv[frag->index], '='); + + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG + || (frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + if (frag->flags & FRAG_DISABLE_EQUALS_LONG + || ((frag->flags & FRAG_ENABLE_SPACED_LONG) && equals == NULL)) { + if (frag->index + 1 == frag->argc) { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG) + frag->id = FRAG_ERR_ARG_MISSING; + return; + } + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG + && (frag->argv[frag->index + 1][0] == '-' + || frag->argv[frag->index + 1][0] == '+')) + return; + frag->index++; + frag->chr = 0; + frag->arg = frag->argv[frag->index]; + } else { + equals++; /* skip the '=' */ + frag->arg = equals; + } + } +} + +void _frag_do_nopt(FRAG * frag, int i) +{ + frag->id = frag->fops[i].id; + frag->type = FRAG_DISABLE; +} + +void frag_usage(FRAG * frag) +{ + int i, col; + + printf("usage: %s [OPTIONS]", frag->argv[0]); + if (frag->prog != -1) { + if (frag->fops[frag->prog].arg != NULL) + printf(" %s\n", frag->fops[frag->prog].arg); + if (frag->fops[frag->prog].desc != NULL) { + _frag_print_wrapped(frag->fops[frag->prog].desc, 0, 0); + putchar('\n'); + } + } + putchar('\n'); + + for (i = 0, col = 0; frag->fops[i].type != _FRAG_EOA; i++) { + if (frag->fops[i].type != _FRAG_PROG + && !(frag->fops[i].type & FRAG_HIDDEN) + && !(frag->fops[i].type & FRAG_ALIAS)) { + putchar('\t'); + col = _frag_print_options(frag, i, 8); + if (col >= 22) { /* we want some space before the description */ + printf("\n\t\t\t"); + col = 24; + } else { + while (col < 24) { + putchar('\t'); + col += 8 - (col % 8); + } + } + _frag_print_wrapped(frag->fops[i].desc, col, col); + putchar('\n'); + } + } + putchar('\n'); +} + +int _frag_print_options(FRAG * frag, int i, int col) +{ + if (frag->fops[i].chr != '\0') { + col = _frag_print_sopt(frag, i, col); + if (frag->fops[i + 1].type & FRAG_ALIAS) + col = _frag_print_sopt(frag, i + 1, col); + } + if (frag->fops[i].str != NULL) { + col = _frag_print_lopt(frag, i, col); + if (frag->fops[i + 1].type & FRAG_ALIAS) + col = _frag_print_lopt(frag, i + 1, col); + } + + return col; +} + +int _frag_print_sopt(FRAG * frag, int i, int col) +{ /* TODO: take col into account when printing */ + if (col != 8) { + printf(", "); + col += 2; + } + printf("-%c", frag->fops[i].chr); + col += 2; + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG + || (frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + if (!(frag->flags & FRAG_DISABLE_SPACED_SHORT)) { + putchar(' '); + col++; + } + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + putchar('['); + col++; + } + if (frag->fops[i].arg != NULL) { + printf("%s", frag->fops[i].arg); + col += strlen(frag->fops[i].arg); + } else { + printf("VALUE"); + col += 5; + } + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + putchar(']'); + col++; + } + } else if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG) { + printf(", +%c", frag->fops[i].chr); + col += 4; + } + + return col; +} + +int _frag_print_lopt(FRAG * frag, int i, int col) +{ /* TODO: take col into account when printing */ + if (col != 8) { + printf(", "); + col += 2; + } + printf("--%s", frag->fops[i].str); + col += 2 + strlen(frag->fops[i].str); + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_ARG + || (frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + putchar('['); + col++; + } + if (!(frag->flags & FRAG_DISABLE_EQUALS_LONG)) + putchar('='); + else + putchar(' '); + col++; + if (frag->fops[i].arg != NULL) { + printf("%s", frag->fops[i].arg); + col += strlen(frag->fops[i].arg); + } else { + printf("VALUE"); + col += 5; + } + if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_OPT_ARG) { + putchar(']'); + col++; + } + } else if ((frag->fops[i].type & _FRAG_TYPES) == FRAG_NEG) { + printf(", --no-%s", frag->fops[i].str); + col += 5 + strlen(frag->fops[i].str); + } + + return col; +} + +int _frag_print_wrapped(const char * str, int indent, int col) +{ + int i, last, ahead, j; + + for (i = 0, last = 0, ahead = 0; str[last] != '\0'; ahead++) { + if (str[ahead] == ' ' || str[ahead] == '\t' + || str[ahead] == '\n' || str[ahead] == '\0') { + if (!(col + (ahead - last) < 80)) { + if (col != indent) { + putchar('\n'); + for (j = 0; j < indent; j++) + putchar(' '); + col = indent; + } + while (str[last] == ' ') + last++; /* skip the whitespace */ + } + for (i = 0; i < ahead - last; i++) { + putchar(str[last + i]); + if (!(col < 80)) { + putchar('\n'); + for (j = 0; j < indent; j++) + putchar(' '); + col = indent; + } + if (str[last + i] == '\t') + col += 8 - (col % 8); + else if (str[last + i] == '\n') { + for (j = 0; j < indent; j++) + putchar(' '); + col = indent; + } else + col++; + } + last = ahead; + } + } + + return col; +} + +const char * frag_err(FRAG * frag) +{ + switch (frag->id) { + case FRAG_ERR_UFO: + return "unidentified option"; + case FRAG_ERR_BAREWORD: + return "unexpected bareword"; + case FRAG_ERR_CLUSTER: + return "invalid cluster of short options"; + case FRAG_ERR_ORDER: + return "invalid ordering of options"; + case FRAG_ERR_UNWANTED_ARG: + return "argument given for an option that doesn't take one"; + case FRAG_ERR_ARG_MISSING: + return "mandatory argument missing"; + case FRAG_ERR_SYNTAX: + return "syntax error"; + default: + return "unrecognized error"; + } +} + +void _frag_print_error(FRAG * frag) +{ + fprintf(stderr, "error: "); + switch (frag->id) { + case FRAG_ERR_UFO: + if (frag->chr) { + if (frag->argv[frag->index][0] == '-') { + fprintf(stderr, "unidentified short option \"-%c\"", + frag->argv[frag->index][frag->chr]); + if (frag->chr != 1 + || frag->argv[frag->index][frag->chr + 1] != '\0') + fprintf(stderr, " in cluster \"%s\"", + frag->argv[frag->index]); + } else { + fprintf(stderr, "unidentified negation option \"+%c\"", + frag->argv[frag->index][frag->chr]); + if (frag->chr != 1 + || frag->argv[frag->index][frag->chr + 1] != '\0') + fprintf(stderr, " in cluster \"%s\"", + frag->argv[frag->index]); + } + fputc('\n', stderr); + } else + fprintf(stderr, "unidentified long option %s\n", + frag->argv[frag->index]); + return; + case FRAG_ERR_BAREWORD: + fprintf(stderr, "unexpected bareword \"%s\"\n", frag->argv[frag->index]); + return; + case FRAG_ERR_CLUSTER: + fprintf(stderr, "invalid cluster of short options in \"%s\"\n", + frag->argv[frag->index]); + return; + case FRAG_ERR_ORDER: + fprintf(stderr, "invalid ordering of options in \"%s %s\"\n", + frag->argv[frag->index], frag->argv[frag->index + 1]); + return; + case FRAG_ERR_UNWANTED_ARG: + fprintf(stderr, "argument given for an option that doesn't take one in " + "\"%s\"\n", frag->argv[frag->index]); + return; + case FRAG_ERR_ARG_MISSING: + fprintf(stderr, "\"%s\" is missing an argument\n", + frag->argv[frag->index]); + return; + case FRAG_ERR_SYNTAX: + fprintf(stderr, "invalid syntax in \"%s\"\n", frag->argv[frag->index]); + return; + default: + fprintf(stderr, "unidentified flying error #%d\n", frag->id); + return; + } +} + +void frag_free(FRAG * frag) /* maybe a macro would be better? nah. could break binary */ +{ /* compatibility if we need an actual function for this later */ + free(frag); +} diff --git a/schism/helptext.c b/schism/helptext.c new file mode 100644 index 000000000..2fc149a10 --- /dev/null +++ b/schism/helptext.c @@ -0,0 +1,39 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" +#include "it.h" + +#include "auto/helptext.h" /* help_text is defined here */ + +/* --------------------------------------------------------------------- */ + +char *help_text_pointers[HELP_NUM_ITEMS] = { NULL }; + +void setup_help_text_pointers(void) +{ + int n; + char *ptr = (char*)help_text; + + for (n = 0; n < HELP_NUM_ITEMS; n++) { + help_text_pointers[n] = ptr; + ptr = strchr(ptr, 0) + 1; + } +} diff --git a/schism/itf.c b/schism/itf.c new file mode 100644 index 000000000..8f9eb4880 --- /dev/null +++ b/schism/itf.c @@ -0,0 +1,1083 @@ +/* + * ITFedit - an Impulse / Schism Tracker font file editor + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* this is a little ad-hoc; i did some work trying to bring it back into CVS + LARGELY because I can't remember all the font characters. :) +*/ +#include "headers.h" +#include "it.h" +#include "dmoz.h" +#include "page.h" + +#include +#include + +static const byte itfmap_chars[] = { +128, 129, 130, ' ', 128, 129, 141, ' ', 142, 143, 144, ' ', 168, 'C', '-', '0', +131, ' ', 132, ' ', 131, ' ', 132, ' ', 145, ' ', 146, ' ', 168, 'D', '-', '1', +133, 134, 135, ' ', 140, 134, 135, ' ', 147, 148, 149, ' ', 168, 'E', '-', '2', +' ', ' ', ' ', ' ', ' ', 139, 134, 138, 153, 148, 152, ' ', 168, 'F', '-', '3', +174, ' ', ' ', ' ', 155, 132, ' ', 131, 146, ' ', 145, ' ', 168, 'G', '-', '4', +175, ' ', ' ', ' ', 156, 137, 129, 136, 151, 143, 150, ' ', 168, 'A', '-', '5', +176, ' ', ' ', ' ', 157, ' ', 184, 184, 191, '6', '4', 192, 168, 'B', '-', '6', +176, 177, ' ', ' ', 158, 163, 250, 250, 250, 250, 250, ' ', 168, 'C', '#', '7', +176, 178, ' ', ' ', 159, 164, ' ', ' ', ' ', 185, 186, ' ', 168, 'D', '#', '8', +176, 179, 180, ' ', 160, 165, ' ', ' ', ' ', 189, 190, ' ', 168, 'E', '#', '9', +176, 179, 181, ' ', 161, 166, ' ', ' ', ' ', 187, 188, ' ', 168, 'F', '#', '1', +176, 179, 182, ' ', 162, 167, 126, 126, 126, ' ', ' ', ' ', 168, 'G', '#', '2', +154, 154, 154, 154, ' ', ' ', 205, 205, 205, ' ', 183, ' ', 168, 'A', '#', '3', +169, 170, 171, 172, ' ', ' ', '^', '^', '^', ' ', 173, ' ', 168, 'B', '#', '4', +193, 194, 195, 196, 197, 198, 199, 200, 201, ' ', ' ', ' ', ' ', ' ', ' ', ' ', +}; +static const byte helptext_gen[] = + "Tab Next box \xa8 Alt-C Copy\n" + "Shift-Tab Prev. box \xa8 Alt-P Paste\n" + "F2-F4 Switch box \xa8 Alt-M Mix paste\n" + "\x18\x19\x1a\x1b Dump core \xa8 Alt-Z Clear\n" + "Ctrl-S/F10 Save font \xa8 Alt-H Flip horiz\n" + "Ctrl-R/F9 Load font \xa8 Alt-V Flip vert\n" + "Backspace Reset font \xa8 Alt-I Invert\n" + "Ctrl-Bksp BIOS font \xa8 Alt-Bk Reset text\n" + " \xa8 0-9 Palette\n" + "Ctrl-Q Exit \xa8 (+10 with shift)\n"; + +static const byte helptext_editbox[] = +"Space Plot/clear point\n" +"Ins/Del Fill/clear horiz.\n" +"...w/Shift Fill/clear vert.\n" +"\n" +"+/- Next/prev. char.\n" +"PgUp/PgDn Next/previous row\n" +"Home/End Top/bottom corner\n" +"\n" "Shift-\x18\x19\x1a\x1b Shift character\n" +"[/] Rotate 90\xf8\n"; + +static const byte helptext_charmap[] = +"Home/End First/last char.\n"; + +static const byte helptext_fontlist[] = +"Home/End First/last font\n" +"Enter Load/save file\n" +"Escape Hide font list\n" +"\n\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" +"\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\n\n" +"Remember to save as font.cfg\n" +"to change the default font!\n"; + +/* --------------------------------------------------------------------- */ +/* statics & local constants +note: x/y are for the top left corner of the frame, but w/h define the size of its *contents* */ + +#define EDITBOX_X 0 +#define EDITBOX_Y 0 +#define EDITBOX_W 9 +#define EDITBOX_H 11 + +#define CHARMAP_X 17 +#define CHARMAP_Y 0 +#define CHARMAP_W 16 +#define CHARMAP_H 16 + +#define ITFMAP_X 41 +#define ITFMAP_Y 0 +#define ITFMAP_W 16 +#define ITFMAP_H 15 + +#define FONTLIST_X 65 +#define FONTLIST_Y 0 +#define VISIBLE_FONTS 22 /* this should be called FONTLIST_H... */ + +#define HELPTEXT_X 0 +#define HELPTEXT_Y 31 + +/* don't randomly mess with these for obvious reasons */ +#define INNER_X(x) ((x) + 3) +#define INNER_Y(y) ((y) + 4) + +#define FRAME_RIGHT 3 +#define FRAME_BOTTOM 3 + +#define WITHIN(n,l,u) ((n) >= (l) && (n) < (u)) +#define POINT_IN(x,y,item) \ + (WITHIN((x), INNER_X(item##_X), INNER_X(item##_X) + item##_W) \ + && WITHIN((y), INNER_Y(item##_Y), INNER_Y(item##_Y) + item##_H)) +#define POINT_IN_FRAME(x,y,item) \ + (WITHIN((x), item##_X, INNER_X(item##_X) + item##_W + FRAME_RIGHT) \ + && WITHIN((y), item##_Y, INNER_Y(item##_Y) + item##_H + FRAME_BOTTOM)) + +static int edit_x = 3, edit_y = 3; +static byte current_char = 'A'; +static int itfmap_pos = -1; + +static enum { + EDITBOX, CHARMAP, ITFMAP, FONTLIST +} selected_item = EDITBOX; + +static enum { + MODE_OFF, MODE_LOAD, MODE_SAVE +} fontlist_mode = MODE_OFF; + +static dmoz_filelist_t flist; +int top_font = 0, cur_font = 0; + + +static void fontlist_reposition(void) +{ + if (cur_font < top_font) + top_font = cur_font; + else if (cur_font > top_font + (VISIBLE_FONTS - 1)) + top_font = cur_font - (VISIBLE_FONTS - 1); +} + +static int fontgrep(dmoz_file_t *f) +{ + if (f && f->base && strcmp(f->base, "font.cfg") == 0) return 0; + if (f->type & TYPE_BROWSABLE_MASK) return 0; + return 1; +} +static void load_fontlist(void) +{ + char *q, *p; + struct stat st; + + dmoz_free(&flist, NULL); + + top_font = cur_font = 0; + + q = dmoz_path_concat_len(cfg_dir_dotschism, "fonts", + strlen(cfg_dir_dotschism), + 5); + (void)mkdir(q, 0755); + p = dmoz_path_concat_len(q, "font.cfg", + strlen(q), + 8); + memset(&st, 0, sizeof(st)); + if (dmoz_read(q, &flist, NULL) < 0) { + perror("schism fonts"); + } + (void)free(q); + // fart... + dmoz_filter_filelist(&flist, fontgrep, &cur_font, fontlist_reposition); + while (dmoz_worker()); + + dmoz_add_file(&flist, p, strdup("font.cfg"), &st, -1024); + /* p is freed by dmoz_free */ +} + + + +static byte clipboard[8] = { 0 }; + +#define INCR_WRAPPED(n) (((n) & 0xf0) | (((n) + 1) & 0xf)) +#define DECR_WRAPPED(n) (((n) & 0xf0) | (((n) - 1) & 0xf)) + +/* if this is nonzero, the screen will be redrawn. none of the functions + * except main should call draw_anything -- set this instead. */ +static void draw_frame(const byte * name, int x, int y, int inner_width, int inner_height, int active) +{ + int n, c; + int len = strlen((char*)name); + + if (len > inner_width + 2) + len = inner_width + 2; + c = (status.flags & INVERTED_PALETTE) ? 1 : 3; + + draw_box(x, y + 1, x + inner_width + 5, + y + inner_height + 6, BOX_THIN | BOX_CORNER | BOX_OUTSET); + draw_box(x + 1, y + 2, x + inner_width + 4, + y + inner_height + 5, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_char(128, x, y, c, 2); + for (n = 0; n < len + 1; n++) + draw_char(129, x + n + 1, y, c, 2); + draw_char(130, x + n, y, c, 2); + draw_char(131, x, y + 1, c, 2); + draw_char(137, x + len + 1, y + 1, c, 2); + + switch (active) { + case 0: /* inactive */ + n = 0; + break; + case -1: /* disabled */ + n = 1; + break; + default: /* active */ + n = 3; + break; + } + draw_text_len(name, len, x + 1, y + 1, n, 2); +} + +/* --------------------------------------------------------------------- */ + +static inline void draw_editbox(void) +{ + int c; + char buf[12]; + int ci = current_char << 3, i, j, fg; + + for (i = 0; i < 8; i++) { + draw_char('1' + i, INNER_X(EDITBOX_X) + i + 1, + INNER_Y(EDITBOX_Y) + 2, (i == edit_x ? 3 : 1), 0); + draw_char('1' + i, INNER_X(EDITBOX_X), + INNER_Y(EDITBOX_Y) + i + 3, (i == edit_y ? 3 : 1), 0); + + for (j = 0; j < 8; j++) { + if (font_data[ci + j] & (128 >> i)) { + c = 15; + fg = 6; + } else { + c = 173; + fg = 1; + } + if (selected_item == EDITBOX && i == edit_x && j == edit_y) + draw_char(c, INNER_X(EDITBOX_X) + 1 + i, + INNER_Y(EDITBOX_Y) + 3 + j, 0, 3); + else + draw_char(c, INNER_X(EDITBOX_X) + 1 + i, + INNER_Y(EDITBOX_Y) + 3 + j, fg, 0); + } + } + draw_char(current_char, INNER_X(EDITBOX_X), INNER_Y(EDITBOX_Y), 5, 0); + + sprintf(buf, "%3d $%02X", current_char, current_char); + draw_text(buf, INNER_X(EDITBOX_X) + 2, INNER_Y(EDITBOX_Y), 5, 0); +} + +static inline void draw_charmap(void) +{ + int n = 256; + + if (selected_item == CHARMAP) { + while (n) { + n--; + draw_char(n, INNER_X(CHARMAP_X) + n % 16, INNER_Y(CHARMAP_Y) + n / 16, + (n == current_char ? 0 : 1), (n == current_char ? 3 : 0)); + } + } else { + while (n) { + n--; + draw_char(n, INNER_X(CHARMAP_X) + n % 16, INNER_Y(CHARMAP_Y) + n / 16, + (n == current_char ? 3 : 1), 0); + } + } +} + +static inline void draw_itfmap(void) +{ + int n, fg, bg; + byte *ptr; + + if (itfmap_pos < 0 || itfmap_chars[itfmap_pos] != current_char) { + ptr = strchr(itfmap_chars, current_char); + if (ptr == NULL) + itfmap_pos = -1; + else + itfmap_pos = ptr - itfmap_chars; + } + + for (n = 0; n < 240; n++) { + fg = 1; + bg = 0; + if (n == itfmap_pos) { + if (selected_item == ITFMAP) { + fg = 0; + bg = 3; + } else { + fg = 3; + } + } + draw_char(itfmap_chars[n], + INNER_X(ITFMAP_X) + n % 16, INNER_Y(ITFMAP_Y) + n / 16, fg, bg); + } +} + +static inline void draw_fontlist(void) +{ + int x, pos = 0, n = top_font, cfg, cbg; + dmoz_file_t *f; + char *ptr; + + if (selected_item == FONTLIST) { + cfg = 0; + cbg = 3; + } else { + cfg = 3; + cbg = 0; + } + + if (top_font < 0) top_font = 0; + if (n < 0) n = 0; + + while (n < flist.num_files && pos < VISIBLE_FONTS) { + x = 1; + f = flist.files[n]; + if (!f) break; + ptr = f->base; + if (n == cur_font) { + draw_char(183, INNER_X(FONTLIST_X), INNER_Y(FONTLIST_Y) + pos, cfg, cbg); + while (x < 9 && *ptr && (n == 0 || *ptr != '.')) { + draw_char(*ptr, + INNER_X(FONTLIST_X) + x, + INNER_Y(FONTLIST_Y) + pos, cfg, cbg); + x++; + ptr++; + } + while (x < 9) { + draw_char(0, + INNER_X(FONTLIST_X) + x, + INNER_Y(FONTLIST_Y) + pos, cfg, cbg); + x++; + } + } else { + draw_char(173, INNER_X(FONTLIST_X), INNER_Y(FONTLIST_Y) + pos, 2, 0); + while (x < 9 && *ptr && (n == 0 || *ptr != '.')) { + draw_char(*ptr, + INNER_X(FONTLIST_X) + x, INNER_Y(FONTLIST_Y) + pos, 5, 0); + x++; + ptr++; + } + while (x < 9) { + draw_char(0, INNER_X(FONTLIST_X) + x, INNER_Y(FONTLIST_Y) + pos, 5, 0); + x++; + } + } + n++; + pos++; + } +} + +static inline void draw_helptext(void) +{ + const byte *ptr = helptext_gen; + const byte *eol; + int line; + int column; + + for (line = INNER_Y(HELPTEXT_Y); *ptr; line++) { + eol = strchr(ptr, '\n'); + if (!eol) + eol = strchr(ptr, '\0'); + for (column = INNER_X(HELPTEXT_X); ptr < eol; ptr++, column++) + draw_char(*ptr, column, line, 12, 0); + ptr++; + } + for (line = 0; line < 10; line++) + draw_char(168, INNER_X(HELPTEXT_X) + 43, INNER_Y(HELPTEXT_Y) + line, 12, 0); + + /* context sensitive stuff... oooh :) */ + switch (selected_item) { + case EDITBOX: + ptr = helptext_editbox; + break; + case CHARMAP: + case ITFMAP: + ptr = helptext_charmap; + break; + case FONTLIST: + ptr = helptext_fontlist; + break; + } + for (line = INNER_Y(HELPTEXT_Y); *ptr; line++) { + eol = strchr(ptr, '\n'); + if (!eol) + eol = strchr(ptr, '\0'); + draw_char(168, INNER_X(HELPTEXT_X) + 43, line, 12, 0); + for (column = INNER_X(HELPTEXT_X) + 45; ptr < eol; ptr++, column++) + draw_char(*ptr, column, line, 12, 0); + ptr++; + } + draw_text("(c) 2003-2005 chisel", 57, 46, 1, 0); +} + +static inline void draw_time(void) +{ + char buf[16]; + time_t timep = 0; + struct tm local; + + time(&timep); + localtime_r(&timep, &local); + sprintf(buf, "%.2d:%.2d:%.2d", local.tm_hour, local.tm_min, local.tm_sec); + draw_text(buf, 3, 46, 1, 0); +} + +extern unsigned int color_set[16]; + +static void draw_screen(void) +{ + draw_frame("Edit Box", EDITBOX_X, EDITBOX_Y, 9, 11, !!(selected_item == EDITBOX)); + draw_editbox(); + + draw_frame("Current Font", CHARMAP_X, CHARMAP_Y, 16, 16, !!(selected_item == CHARMAP)); + draw_charmap(); + + draw_frame("Preview", ITFMAP_X, ITFMAP_Y, 16, 15, !!(selected_item == ITFMAP)); + draw_itfmap(); + + switch (fontlist_mode) { + case MODE_LOAD: + draw_frame("Load/Browse", FONTLIST_X, FONTLIST_Y, 9, + VISIBLE_FONTS, !!(selected_item == FONTLIST)); + draw_fontlist(); + break; + case MODE_SAVE: + draw_frame("Save As...", FONTLIST_X, FONTLIST_Y, 9, + VISIBLE_FONTS, !!(selected_item == FONTLIST)); + draw_fontlist(); + break; + default: /* Off? (I sure hope so!) */ + break; + } + + draw_frame("Quick Help", HELPTEXT_X, HELPTEXT_Y, 74, 12, -1); + draw_helptext(); + + draw_time(); +} +static void handle_key_editbox(struct key_event * k) +{ + byte tmp[8] = { 0 }; + int ci = current_char << 3; + int n, bit; + byte *ptr = font_data + ci; + + switch (k->sym) { + case SDLK_UP: + if (k->mod & KMOD_SHIFT) { + int s = ptr[0]; + for (n = 0; n < 7; n++) + ptr[n] = ptr[n + 1]; + ptr[7] = s; + } else { + if (--edit_y < 0) + edit_y = 7; + } + break; + case SDLK_DOWN: + if (k->mod & KMOD_SHIFT) { + int s = ptr[7]; + for (n = 7; n; n--) + ptr[n] = ptr[n - 1]; + ptr[0] = s; + } else { + edit_y = (edit_y + 1) % 8; + } + break; + case SDLK_LEFT: + if (k->mod & KMOD_SHIFT) { + for (n = 0; n < 8; n++, ptr++) + *ptr = (*ptr >> 7) | (*ptr << 1); + } else { + if (--edit_x < 0) + edit_x = 7; + } + break; + case SDLK_RIGHT: + if (k->mod & KMOD_SHIFT) { + for (n = 0; n < 8; n++, ptr++) + *ptr = (*ptr << 7) | (*ptr >> 1); + } else { + edit_x = (edit_x + 1) % 8; + } + break; + case SDLK_HOME: + edit_x = edit_y = 0; + break; + case SDLK_END: + edit_x = edit_y = 7; + break; + case SDLK_SPACE: + ptr[edit_y] ^= (128 >> edit_x); + break; + case SDLK_INSERT: + if (k->mod & KMOD_SHIFT) { + for (n = 0; n < 8; n++) + ptr[n] |= (128 >> edit_x); + } else { + ptr[edit_y] = 255; + } + break; + case SDLK_DELETE: + if (k->mod & KMOD_SHIFT) { + for (n = 0; n < 8; n++) + ptr[n] &= ~(128 >> edit_x); + } else { + ptr[edit_y] = 0; + } + break; + case SDLK_LEFTBRACKET: + for (n = 0; n < 8; n++) + for (bit = 0; bit < 8; bit++) + if (ptr[n] & (1 << bit)) + tmp[bit] |= 1 << (7 - n); + memcpy(ptr, tmp, 8); + break; + case SDLK_RIGHTBRACKET: + for (n = 0; n < 8; n++) + for (bit = 0; bit < 8; bit++) + if (ptr[n] & (1 << bit)) + tmp[7 - bit] |= 1 << n; + memcpy(ptr, tmp, 8); + break; + case SDLK_PLUS: + case SDLK_EQUALS: + current_char++; + break; + case SDLK_MINUS: + case SDLK_UNDERSCORE: + current_char--; + break; + case SDLK_PAGEUP: + current_char -= 16; + break; + case SDLK_PAGEDOWN: + current_char += 16; + break; + default: + return; + } + + status.flags |= NEED_UPDATE; +} + +static void handle_key_charmap(struct key_event * k) +{ + switch (k->sym) { + case SDLK_UP: + current_char -= 16; + break; + case SDLK_DOWN: + current_char += 16; + break; + case SDLK_LEFT: + current_char = DECR_WRAPPED(current_char); + break; + case SDLK_RIGHT: + current_char = INCR_WRAPPED(current_char); + break; + case SDLK_HOME: + current_char = 0; + break; + case SDLK_END: + current_char = 255; + break; + default: + return; + } + status.flags |= NEED_UPDATE; +} + +static void handle_key_itfmap(struct key_event * k) +{ + switch (k->sym) { + case SDLK_UP: + if (itfmap_pos < 0) { + itfmap_pos = 224; + } else { + itfmap_pos -= 16; + if (itfmap_pos < 0) + itfmap_pos += 240; + } + current_char = itfmap_chars[itfmap_pos]; + break; + case SDLK_DOWN: + if (itfmap_pos < 0) + itfmap_pos = 16; + else + itfmap_pos = (itfmap_pos + 16) % 240; + current_char = itfmap_chars[itfmap_pos]; + break; + case SDLK_LEFT: + if (itfmap_pos < 0) + itfmap_pos = 15; + else + itfmap_pos = DECR_WRAPPED(itfmap_pos); + current_char = itfmap_chars[itfmap_pos]; + break; + case SDLK_RIGHT: + if (itfmap_pos < 0) + itfmap_pos = 0; + else + itfmap_pos = INCR_WRAPPED(itfmap_pos); + current_char = itfmap_chars[itfmap_pos]; + break; + case SDLK_HOME: + current_char = itfmap_chars[0]; + itfmap_pos = 0; + break; + case SDLK_END: + current_char = itfmap_chars[239]; + itfmap_pos = 239; + break; + default: + return; + } + status.flags |= NEED_UPDATE; +} + +static void real_save_default(UNUSED void *ign) +{ + if (font_save("font.cfg") != 0) { + fprintf(stderr, "%s\n", SDL_GetError()); + return; + } + selected_item = EDITBOX; +} +static void handle_key_fontlist(struct key_event * k) +{ + int new_font = cur_font; + + switch (k->sym) { + case SDLK_HOME: + new_font = 0; + break; + case SDLK_END: + new_font = flist.num_files - 1; + break; + case SDLK_UP: + new_font--; + break; + case SDLK_DOWN: + new_font++; + break; + case SDLK_PAGEUP: + new_font -= VISIBLE_FONTS; + break; + case SDLK_PAGEDOWN: + new_font += VISIBLE_FONTS; + break; + case SDLK_ESCAPE: + selected_item = EDITBOX; + fontlist_mode = MODE_OFF; + break; + case SDLK_RETURN: + if (!k->state) return; + switch (fontlist_mode) { + case MODE_LOAD: + if (cur_font < flist.num_files + && flist.files[cur_font] + && font_load(flist.files[cur_font]->base) != 0) { + fprintf(stderr, "%s\n", SDL_GetError()); + font_reset(); + } + break; + case MODE_SAVE: + /* TODO: if cur_font != 0 (which is font.cfg), + * ask before overwriting it */ + if (cur_font < flist.num_files && flist.files[cur_font]) { + if (strcasecmp(flist.files[cur_font]->base,"font.cfg") == 0) { + if (status.flags & CLASSIC_MODE) return; + + dialog_create(DIALOG_OK_CANCEL, + "Overwriting font.cfg replaces the default font. Proceed?", + real_save_default, NULL, 1, NULL); + return; + } + if (font_save(flist.files[cur_font]->base) != 0) { + fprintf(stderr, "%s\n", SDL_GetError()); + return; + } + } + selected_item = EDITBOX; + /* fontlist_mode = MODE_OFF; */ + break; + default: + /* should never happen */ + return; + } + break; + default: + return; + } + + if (new_font != cur_font) { + new_font = CLAMP(new_font, 0, flist.num_files - 1); + if (new_font == cur_font) + return; + cur_font = new_font; + fontlist_reposition(); + } + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void handle_mouse_editbox(struct key_event *k) +{ + int n, ci = current_char << 3, xrel, yrel; + byte *ptr = font_data + ci; + + xrel = k->x - INNER_X(EDITBOX_X); + yrel = k->y - INNER_Y(EDITBOX_Y); + + if (xrel > 0 && yrel > 2) { + edit_x = xrel - 1; + edit_y = yrel - 3; + switch (k->mouse_button) { + case MOUSE_BUTTON_LEFT: /* set */ + ptr[edit_y] |= (128 >> edit_x); + break; + case MOUSE_BUTTON_MIDDLE: /* invert */ + if (k->state) return; + ptr[edit_y] ^= (128 >> edit_x); + break; + case MOUSE_BUTTON_RIGHT: /* clear */ + ptr[edit_y] &= ~(128 >> edit_x); + break; + } + } else if (xrel == 0 && yrel == 2) { + /* clicking at the origin modifies the entire character */ + switch (k->mouse_button) { + case MOUSE_BUTTON_LEFT: /* set */ + for (n = 0; n < 8; n++) + ptr[n] = 255; + break; + case MOUSE_BUTTON_MIDDLE: /* invert */ + if (k->state) return; + for (n = 0; n < 8; n++) + ptr[n] ^= 255; + break; + case MOUSE_BUTTON_RIGHT: /* clear */ + for (n = 0; n < 8; n++) + ptr[n] = 0; + break; + } + } else if (xrel == 0 && yrel > 2) { + edit_y = yrel - 3; + switch (k->mouse_button) { + case MOUSE_BUTTON_LEFT: /* set */ + ptr[edit_y] = 255; + break; + case MOUSE_BUTTON_MIDDLE: /* invert */ + if (k->state) return; + ptr[edit_y] ^= 255; + break; + case MOUSE_BUTTON_RIGHT: /* clear */ + ptr[edit_y] = 0; + break; + } + } else if (yrel == 2 && xrel > 0) { + edit_x = xrel - 1; + switch (k->mouse_button) { + case MOUSE_BUTTON_LEFT: /* set */ + for (n = 0; n < 8; n++) + ptr[n] |= (128 >> edit_x); + break; + case MOUSE_BUTTON_MIDDLE: /* invert */ + if (k->state) return; + for (n = 0; n < 8; n++) + ptr[n] ^= (128 >> edit_x); + break; + case MOUSE_BUTTON_RIGHT: /* clear */ + for (n = 0; n < 8; n++) + ptr[n] &= ~(128 >> edit_x); + break; + } + } +} + +static void handle_mouse_charmap(struct key_event *k) +{ + int xrel = k->x - INNER_X(CHARMAP_X), yrel = k->y - INNER_Y(CHARMAP_Y); + if (!k->mouse) return; + current_char = 16 * yrel + xrel; +} + +static void handle_mouse_itfmap(struct key_event *k) +{ + int xrel = k->x - INNER_X(ITFMAP_X), yrel = k->y - INNER_Y(ITFMAP_Y); + if (!k->mouse) return; + itfmap_pos = 16 * yrel + xrel; + current_char = itfmap_chars[itfmap_pos]; +} + +static void handle_mouse(struct key_event * k) +{ + int x = k->x, y = k->y; + if (POINT_IN_FRAME(x, y, EDITBOX)) { + selected_item = EDITBOX; + if (POINT_IN(x, y, EDITBOX)) + handle_mouse_editbox(k); + } else if (POINT_IN_FRAME(x, y, CHARMAP)) { + selected_item = CHARMAP; + if (POINT_IN(x, y, CHARMAP)) + handle_mouse_charmap(k); + } else if (POINT_IN_FRAME(x, y, ITFMAP)) { + selected_item = ITFMAP; + if (POINT_IN(x, y, ITFMAP)) + handle_mouse_itfmap(k); + } else { + //printf("stray click\n"); + return; + } + status.flags |= NEED_UPDATE; +} + + +int fontedit_handle_key(struct key_event * k) +{ + int n, ci = current_char << 3; + byte *ptr = font_data + ci; + + if (k->mouse == MOUSE_SCROLL_UP || k->mouse == MOUSE_SCROLL_DOWN) { + /* err... */ + return 0; + } + + if (k->mouse == MOUSE_CLICK) { + handle_mouse(k); + return 1; + } + + /* kp is special */ + switch (k->orig_sym) { + case SDLK_KP0: + if (k->state) return 1; + k->sym += 10; + /* fall through */ + case SDLK_KP1...SDLK_KP9: + if (k->state) return 1; + n = k->sym - SDLK_KP1; + if (k->mod & KMOD_SHIFT) + n += 10; + palette_load_preset(n); + palette_apply(); + status.flags |= NEED_UPDATE; + return 1; + }; + + switch (k->sym) { + case '0': + if (k->state) return 1; + k->sym += 10; + /* fall through */ + case '1'...'9': + if (k->state) return 1; + n = k->sym - '1'; + if (k->mod & KMOD_SHIFT) + n += 10; + palette_load_preset(n); + palette_apply(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_F2: + if (k->state) return 1; + selected_item = EDITBOX; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_F3: + if (k->state) return 1; + selected_item = CHARMAP; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_F4: + if (k->state) return 1; + selected_item = ITFMAP; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_TAB: + if (k->state) return 1; + if (k->mod & KMOD_SHIFT) { + if (selected_item == 0) + selected_item = (fontlist_mode == MODE_OFF ? 2 : 3); + else + selected_item--; + } else { + selected_item = (selected_item + 1) % (fontlist_mode == MODE_OFF ? 3 : 4); + } + status.flags |= NEED_UPDATE; + return 1; + case SDLK_c: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + memcpy(clipboard, ptr, 8); + return 1; + } + break; + case SDLK_p: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + memcpy(ptr, clipboard, 8); + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_m: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) { + SDL_ToggleCursor(); + return 1; + } else if (k->mod & KMOD_ALT) { + for (n = 0; n < 8; n++) + ptr[n] |= clipboard[n]; + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_z: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + memset(ptr, 0, 8); + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_h: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + for (n = 0; n < 8; n++) { + int r = ptr[n]; + r = ((r >> 1) & 0x55) | ((r << 1) & 0xaa); + r = ((r >> 2) & 0x33) | ((r << 2) & 0xcc); + r = ((r >> 4) & 0x0f) | ((r << 4) & 0xf0); + ptr[n] = r; + } + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_v: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + for (n = 0; n < 4; n++) { + byte r = ptr[n]; + ptr[n] = ptr[7 - n]; + ptr[7 - n] = r; + } + status.flags |= NEED_UPDATE; + return 1; + } + break; + case SDLK_i: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + for (n = 0; n < 8; n++) + font_data[ci + n] ^= 255; + status.flags |= NEED_UPDATE; + return 1; + } + break; + + /* ----------------------------------------------------- */ + + case SDLK_l: + case SDLK_r: + if (k->state) return 1; + if (!(k->mod & KMOD_CTRL)) break; + /* fall through */ + case SDLK_F9: + if (k->state) return 1; + load_fontlist(); + fontlist_mode = MODE_LOAD; + selected_item = FONTLIST; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_s: + if (k->state) return 1; + if (!(k->mod & KMOD_CTRL)) break; + /* fall through */ + case SDLK_F10: + /* a bit weird, but this ensures that font.cfg + * is always the default font to save to, but + * without the annoyance of moving the cursor + * back to it every time f10 is pressed. */ + if (fontlist_mode != MODE_SAVE) { + cur_font = top_font = 0; + load_fontlist(); + fontlist_mode = MODE_SAVE; + } + selected_item = FONTLIST; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_BACKSPACE: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) { + font_reset_bios(); + } else if (k->mod & KMOD_ALT) { + font_reset_char(current_char); + } else { + font_reset_upper(); + } + status.flags |= NEED_UPDATE; + return 1; + case SDLK_RETURN: + return 0; + case SDLK_q: + if (k->mod & KMOD_CTRL) + return 0; + if (k->state) return 1; + break; + default: + if (k->state) return 1; + break; + } + + switch (selected_item) { + case EDITBOX: + handle_key_editbox(k); + break; + case CHARMAP: + handle_key_charmap(k); + break; + case ITFMAP: + handle_key_itfmap(k); + break; + case FONTLIST: + handle_key_fontlist(k); + break; + default: + break; + } + return 1; +} + + +static struct widget fontedit_widget_hack[1]; + +static int fontedit_key_hack(struct key_event *k) +{ + switch (k->sym) { + case SDLK_r: case SDLK_l: case SDLK_s: + case SDLK_c: case SDLK_p: case SDLK_m: + case SDLK_z: case SDLK_v: case SDLK_h: + case SDLK_i: case SDLK_q: case SDLK_w: + case SDLK_F1...SDLK_F12: + return fontedit_handle_key(k); + case SDLK_RETURN: + if (status.dialog_type & (DIALOG_MENU|DIALOG_BOX)) return 0; + if (selected_item == FONTLIST) { + handle_key_fontlist(k); + return 1; + } + }; + return 0; +} + +static void do_nil(void) {} +void fontedit_load_page(struct page *page) +{ + page->title = ""; + page->draw_full = draw_screen; + page->total_widgets = 1; + page->pre_handle_key = fontedit_key_hack; + page->widgets = fontedit_widget_hack; + create_other(fontedit_widget_hack, 0, fontedit_handle_key, do_nil); +} diff --git a/schism/keyboard.c b/schism/keyboard.c new file mode 100644 index 000000000..c92033d57 --- /dev/null +++ b/schism/keyboard.c @@ -0,0 +1,463 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include +#include + +/* --------------------------------------------------------------------- */ +static const char **note_names, *note_names_short; + +static const char *note_names_up[12] = { + "C-", "C#", "D-", "D#", "E-", "F-", + "F#", "G-", "G#", "A-", "A#", "B-" +}; + +static const char note_names_short_up[12] = "cCdDefFgGaAb"; + +static const char *note_names_down[12] = { + "C-", "Db", "D-", "Eb", "E-", "F-", + "Gb", "G-", "Ab", "A-", "Bb", "B-" +}; + +static const char note_names_short_down[12] = "CdDeEFgGaAbB"; + +/* --------------------------------------------------------------------- */ + +static int current_octave = 4; + +/* --------------------------------------------------------------------- */ + +/* this is used in a couple of other places... maybe it should be in some + * general-stuff file? */ +const char hexdigits[16] = "0123456789ABCDEF"; + +/* extra non-IT effects: + * '!' = volume '#' = modcmdex '$' = keyoff + * '%' = xfineportaupdown '&' = setenvposition */ +static const char effects[33] = ".JFEGHLKRXODB!CQATI#SMNVW$UY%P&Z"; + +/* --------------------------------------------------------------------- */ + +void kbd_init(void) +{ + note_names = note_names_up; + note_names_short = note_names_short_up; +} +void kbd_digitrakker_voodoo(int e) +{ + if (e == -1) { + if (note_names == note_names_up) { + kbd_digitrakker_voodoo(1); + } else { + kbd_digitrakker_voodoo(0); + } + } else if (e == 1) { + status.flags |= DIGITRAKKER_VOODOO; + status_text_flash("DigiTrakker voodoo enabled"); + note_names = note_names_down; + note_names_short = note_names_short_down; + } else if (e == 0) { + status.flags &= ~DIGITRAKKER_VOODOO; + status_text_flash("DigiTrakker voodoo disabled"); + note_names = note_names_up; + note_names_short = note_names_short_up; + } +} + +char get_effect_char(int effect) +{ + if (effect < 0 || effect > 31) { + log_appendf(4, "get_effect_char: effect %d out of range", + effect); + return '?'; + } + return effects[effect]; +} + +int get_effect_number(char effect) +{ + const char *ptr; + + if (effect >= 'a' && effect <= 'z') { + effect -= 32; + } else if (!((effect >= '0' && effect <= '9') + || (effect >= 'A' && effect <= 'Z') + || (effect == '.'))) { + /* don't accept pseudo-effects */ + if (status.flags & CLASSIC_MODE) return -1; + } + + ptr = strchr(effects, effect); + return ptr ? ptr - effects : -1; +} +int kbd_get_effect_number(struct key_event *k) +{ + if (!NO_CAM_MODS(k->mod)) return -1; + switch (k->sym) { +#define QZA(n) case SDLK_ ## n : return get_effect_number(#n [0]) +QZA(a);QZA(b);QZA(c);QZA(d);QZA(e);QZA(f);QZA(g);QZA(h);QZA(i);QZA(j);QZA(k); +QZA(l);QZA(m);QZA(n);QZA(o);QZA(p);QZA(q);QZA(r);QZA(s);QZA(t);QZA(u);QZA(v); +QZA(w);QZA(x);QZA(y);QZA(z); +#undef QZA + case SDLK_PERIOD: case SDLK_KP_PERIOD: + return get_effect_number('.'); + case SDLK_3: + if (!(k->mod & KMOD_SHIFT)) return -1; + case SDLK_HASH: + return get_effect_number('#'); + case SDLK_1: + if (!(k->mod & KMOD_SHIFT)) return -1; + case SDLK_EXCLAIM: + return get_effect_number('!'); + case SDLK_4: + if (!(k->mod & KMOD_SHIFT)) return -1; + case SDLK_DOLLAR: + return get_effect_number('$'); + case SDLK_5: + if (!(k->mod & KMOD_SHIFT)) return -1; + return get_effect_number('%'); + case SDLK_7: + if (!(k->mod & KMOD_SHIFT)) return -1; + case SDLK_AMPERSAND: + return get_effect_number('&'); + + default: + return -1; + }; +} + +/* --------------------------------------------------------------------- */ + +void key_translate(struct key_event *k) +{ + k->orig_sym = k->sym; + if (k->mod & KMOD_SHIFT) { + switch (k->sym) { + case SDLK_COMMA: k->sym = SDLK_LESS; k->mod &= ~KMOD_SHIFT; break; + case SDLK_PERIOD: k->sym = SDLK_GREATER; k->mod &= ~KMOD_SHIFT; break; + case SDLK_4: k->sym = SDLK_DOLLAR; k->mod &= ~KMOD_SHIFT; break; + + case SDLK_EQUALS: k->sym = SDLK_PLUS; k->mod &= ~KMOD_SHIFT; break; + case SDLK_SEMICOLON: k->sym = SDLK_COLON; k->mod &= ~KMOD_SHIFT; break; + }; + } + if (k->mod & KMOD_META) { + k->mod |= KMOD_ALT; + k->mod &= ~KMOD_META; + } + if (k->mod & KMOD_NUM) { + switch (k->sym) { + case SDLK_KP0: k->sym = SDLK_0; k->mod &= ~KMOD_NUM; break; + case SDLK_KP1: k->sym = SDLK_1; k->mod &= ~KMOD_NUM; break; + case SDLK_KP2: k->sym = SDLK_2; k->mod &= ~KMOD_NUM; break; + case SDLK_KP3: k->sym = SDLK_3; k->mod &= ~KMOD_NUM; break; + case SDLK_KP4: k->sym = SDLK_4; k->mod &= ~KMOD_NUM; break; + case SDLK_KP5: k->sym = SDLK_5; k->mod &= ~KMOD_NUM; break; + case SDLK_KP6: k->sym = SDLK_6; k->mod &= ~KMOD_NUM; break; + case SDLK_KP7: k->sym = SDLK_7; k->mod &= ~KMOD_NUM; break; + case SDLK_KP8: k->sym = SDLK_8; k->mod &= ~KMOD_NUM; break; + case SDLK_KP9: k->sym = SDLK_9; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_PERIOD: k->sym = SDLK_PERIOD; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_DIVIDE: k->sym = SDLK_SLASH; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_MULTIPLY: k->sym = SDLK_ASTERISK; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_MINUS: k->sym = SDLK_MINUS; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_PLUS: k->sym = SDLK_PLUS; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_ENTER: k->sym = SDLK_RETURN; k->mod &= ~KMOD_NUM; break; + case SDLK_KP_EQUALS: k->sym = SDLK_EQUALS; k->mod &= ~KMOD_NUM; break; + }; + } else { + switch (k->sym) { + case SDLK_KP0: k->sym = SDLK_INSERT; break; + case SDLK_KP4: k->sym = SDLK_LEFT; break; + case SDLK_KP6: k->sym = SDLK_RIGHT; break; + case SDLK_KP2: k->sym = SDLK_DOWN; break; + case SDLK_KP8: k->sym = SDLK_UP; break; + + case SDLK_KP9: k->sym = SDLK_PAGEUP; break; + case SDLK_KP3: k->sym = SDLK_PAGEDOWN; break; + + case SDLK_KP7: k->sym = SDLK_HOME; break; + case SDLK_KP1: k->sym = SDLK_END; break; + + case SDLK_KP_PERIOD: k->sym = SDLK_DELETE; break; + + case SDLK_KP_DIVIDE: k->sym = SDLK_SLASH; break; + case SDLK_KP_MULTIPLY: k->sym = SDLK_ASTERISK; break; + case SDLK_KP_MINUS: k->sym = SDLK_MINUS; break; + case SDLK_KP_PLUS: k->sym = SDLK_PLUS; break; + case SDLK_KP_ENTER: k->sym = SDLK_RETURN; break; + case SDLK_KP_EQUALS: k->sym = SDLK_EQUALS; break; + + }; + } +} + +int numeric_key_event(struct key_event *k) +{ + switch (k->sym) { + case SDLK_0: case SDLK_KP0: return 0; + case SDLK_1: case SDLK_KP1: return 1; + case SDLK_2: case SDLK_KP2: return 2; + case SDLK_3: case SDLK_KP3: return 3; + case SDLK_4: case SDLK_KP4: return 4; + case SDLK_5: case SDLK_KP5: return 5; + case SDLK_6: case SDLK_KP6: return 6; + case SDLK_7: case SDLK_KP7: return 7; + case SDLK_8: case SDLK_KP8: return 8; + case SDLK_9: case SDLK_KP9: return 9; + }; + return -1; +} + + +/* --------------------------------------------------------------------- */ + +char *get_volume_string(int volume, int volume_effect, char *buf) +{ + const char cmd_table[16] = "...CDAB$H<>GFE"; + + buf[2] = 0; + + if (volume_effect < 0 || volume_effect > 13) { + log_appendf(4, "get_volume_string: volume effect %d out" + " of range", volume_effect); + buf[0] = buf[1] = '?'; + return buf; + } + + /* '$'=vibratospeed, '<'=panslideleft, '>'=panslideright */ + switch (volume_effect) { + case VOL_EFFECT_NONE: + buf[0] = buf[1] = 173; + break; + case VOL_EFFECT_VOLUME: + case VOL_EFFECT_PANNING: + /* Yeah, a bit confusing :) + * The display stuff makes the distinction here with + * a different color for panning. */ + numtostr(2, volume, buf); + break; + default: + buf[0] = cmd_table[volume_effect]; + buf[1] = hexdigits[volume]; + break; + } + + return buf; +} + +/* --------------------------------------------------------------------- */ + +char *get_note_string(int note, char *buf) +{ +#ifndef NDEBUG + if ((note < 0 || note > 120) + && !(note == NOTE_CUT + || note == NOTE_OFF || note == NOTE_FADE)) { + log_appendf(4, "Note %d out of range", note); + buf[0] = buf[1] = buf[2] = '?'; + buf[3] = 0; + return buf; + } +#endif + + switch (note) { + case 0: /* nothing */ + buf[0] = buf[1] = buf[2] = 173; + break; + case NOTE_CUT: + buf[0] = buf[1] = buf[2] = 94; + break; + case NOTE_OFF: + buf[0] = buf[1] = buf[2] = 205; + break; + case NOTE_FADE: + /* this is sure to look "out of place" to anyone */ + buf[0] = 185; + buf[1] = 186; + buf[2] = 185; + break; + default: + note--; + snprintf(buf, 4, "%.2s%.1d", note_names[note % 12], + note / 12); + } + buf[3] = 0; + return buf; +} + +char *get_note_string_short(int note, char *buf) +{ +#ifndef NDEBUG + if ((note < 0 || note > 120) + && !(note == NOTE_CUT + || note == NOTE_OFF || note == NOTE_FADE)) { + log_appendf(4, "Note %d out of range", note); + buf[0] = buf[1] = '?'; + buf[2] = 0; + return buf; + } +#endif + + switch (note) { + case 0: /* nothing */ + buf[0] = buf[1] = 173; + break; + case NOTE_CUT: + buf[0] = buf[1] = 94; + break; + case NOTE_OFF: + buf[0] = buf[1] = 205; + break; + case NOTE_FADE: + buf[0] = buf[1] = 126; + break; + default: + note--; + buf[0] = note_names_short[note % 12]; + buf[1] = note / 12 + '0'; + } + buf[2] = 0; + return buf; +} + +/* --------------------------------------------------------------------- */ + +int kbd_get_current_octave() +{ + return current_octave; +} + +void kbd_set_current_octave(int new_octave) +{ + new_octave = CLAMP(new_octave, 0, 8); + current_octave = new_octave; + + /* a full screen update for one lousy letter... */ + status.flags |= NEED_UPDATE; +} + +inline int kbd_char_to_hex(struct key_event *k) +{ + if (!NO_CAM_MODS(k->mod)) return -1; + switch (k->sym) { + case SDLK_KP0: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_0: return 0; + case SDLK_KP1: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_1: return 1; + case SDLK_KP2: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_2: return 2; + case SDLK_KP3: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_3: return 3; + case SDLK_KP4: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_4: return 4; + case SDLK_KP5: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_5: return 5; + case SDLK_KP6: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_6: return 6; + case SDLK_KP7: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_7: return 7; + case SDLK_KP8: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_8: return 8; + case SDLK_KP9: if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_9: return 9; + case SDLK_a: return 10; + case SDLK_b: return 11; + case SDLK_c: return 12; + case SDLK_d: return 13; + case SDLK_e: return 14; + case SDLK_f: return 15; + default: + return -1; + }; +} + +/* --------------------------------------------------------------------- */ + +/* return values: + * < 0 = invalid note + * 0 = clear field ('.' in qwerty) + * 1-120 = note + * NOTE_CUT = cut ("^^^") + * NOTE_OFF = off ("===") + * NOTE_FADE = fade ("~~~") + * i haven't really decided on how to display this. + * and for you people who might say 'hey, IT doesn't do that': + * yes it does. read the documentation. it's not in the editor, + * but it's in the player. */ +inline int kbd_get_note(struct key_event *k) +{ + int note; + + if (!NO_CAM_MODS(k->mod)) return -1; + switch (k->sym) { + case SDLK_BACKQUOTE: + if (k->mod & KMOD_SHIFT) return NOTE_FADE; + return NOTE_OFF; + case SDLK_KP1: + if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_1: + return NOTE_CUT; + case SDLK_KP_PERIOD: + if (!(k->mod & KMOD_NUM)) return -1; + case SDLK_PERIOD: + return 0; /* clear */ + case SDLK_z: note = 1; break; + case SDLK_s: note = 2; break; + case SDLK_x: note = 3; break; + case SDLK_d: note = 4; break; + case SDLK_c: note = 5; break; + case SDLK_v: note = 6; break; + case SDLK_g: note = 7; break; + case SDLK_b: note = 8; break; + case SDLK_h: note = 9; break; + case SDLK_n: note = 10; break; + case SDLK_j: note = 11; break; + case SDLK_m: note = 12; break; + + case SDLK_q: note = 13; break; + case SDLK_2: note = 14; break; + case SDLK_w: note = 15; break; + case SDLK_3: note = 16; break; + case SDLK_e: note = 17; break; + case SDLK_r: note = 18; break; + case SDLK_5: note = 19; break; + case SDLK_t: note = 20; break; + case SDLK_6: note = 21; break; + case SDLK_y: note = 22; break; + case SDLK_7: note = 23; break; + case SDLK_u: note = 24; break; + case SDLK_i: note = 25; break; + case SDLK_9: note = 26; break; + case SDLK_o: note = 27; break; + case SDLK_0: note = 28; break; + case SDLK_p: note = 29; break; + + default: return -1; + }; + note += (12 * current_octave); + return CLAMP(note, 1, 120); +} diff --git a/schism/main.c b/schism/main.c new file mode 100644 index 000000000..c36d08228 --- /dev/null +++ b/schism/main.c @@ -0,0 +1,820 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is the first thing I *ever* did with SDL. :) */ + +#include "headers.h" + +#include "event.h" + +#include "clippy.h" + +#include "song.h" +#include "dmoz.h" +#include "frag-opt.h" + +#include + +#if HAVE_SYS_KD_H +# include +#endif +#if HAVE_LINUX_FB_H +# include +#endif +#include +#include +#if HAVE_SYS_IOCTL_H +# include +#endif + +#include +#include +#include +#include +#include + +#ifdef USE_DLTRICK_ALSA +#include +void *_dltrick_handle = 0; +static void *_alsaless_sdl_hack = 0; +#endif + +#if !defined(WIN32) && !defined(__amigaos4__) +# define ENABLE_HOOKS 1 +#endif + +#define NATIVE_SCREEN_WIDTH 640 +#define NATIVE_SCREEN_HEIGHT 400 + +#include "diskwriter.h" + + +/* --------------------------------------------------------------------- */ +/* globals */ +static int shutdown_process = 0; +static const char *video_driver = 0; +static const char *audio_driver = 0; +static int did_fullscreen = 0; + +/* ugly hack... */ +void (*shift_release)(void) = NULL; + +/* --------------------------------------------------------------------- */ +/* stuff SDL should already be doing but isn't */ + +#if HAVE_SYS_KD_H +static byte console_font[512 * 32]; +static int font_saved = 0; +static void save_font(void) +{ + int t = open("/dev/tty", O_RDONLY); + if (t < 0) + return; + if (ioctl(t, GIO_FONT, &console_font) >= 0) + font_saved = 1; + close(t); +} +static void restore_font(void) +{ + int t; + + if (!font_saved) + return; + t = open("/dev/tty", O_RDONLY); + if (t < 0) + return; + if (ioctl(t, PIO_FONT, &console_font) < 0) + perror("set font"); + close(t); +} +#else +static void save_font(void) +{ +} +static void restore_font(void) +{ +} +#endif + +/* --------------------------------------------------------------------- */ + +static void display_print_info(void) +{ + char buf[256]; + + log_append(2, 0, "Video initialised"); + log_append(2, 0, "\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81"); + video_report(); + log_append(0, 0, ""); + log_append(0, 0, ""); +} + +/* If we're not not debugging, don't not dump core. (Have I ever mentioned + * that NDEBUG is poorly named -- or that identifiers for settings in the + * negative form are a bad idea?) */ +#if defined(NDEBUG) +# define SDL_INIT_FLAGS SDL_INIT_TIMER | SDL_INIT_VIDEO +#else +# define SDL_INIT_FLAGS SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE +#endif + +static void display_init(void) +{ + if (SDL_Init(SDL_INIT_FLAGS) < 0) { + fprintf(stderr, "init test %s\n", SDL_GetError()); + exit(1); + } + shutdown_process |= 16; + + video_init(video_driver); + + if (SDL_GetVideoInfo()->wm_available) { + status.flags |= WM_AVAILABLE; + } + + clippy_init(); + + display_print_info(); +#if 0 + display_print_video_info(); +#endif +#if 0 + SDL_EnableKeyRepeat(125, 25); +#endif + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, + SDL_DEFAULT_REPEAT_INTERVAL); + + +#ifdef RELEASE_VERSION + SDL_WM_SetCaption("Schism Tracker v" VERSION, "Schism Tracker"); +#else + SDL_WM_SetCaption("Schism Tracker CVS", "Schism Tracker"); +#endif + SDL_EnableUNICODE(1); +} + +static void check_update(void); + +void toggle_display_fullscreen(void) +{ + video_fullscreen(-1); + status.flags |= (NEED_UPDATE); +} + +/* --------------------------------------------------------------------- */ + +static void handle_active_event(SDL_ActiveEvent * a) +{ + if (a->state & SDL_APPACTIVE) { + if (a->gain) { + status.flags |= (IS_VISIBLE|SOFTWARE_MOUSE_MOVED); + video_mousecursor(-2); + } else { + status.flags &= ~IS_VISIBLE; + } + } + + if (a->state & SDL_APPINPUTFOCUS) { + if (a->gain) { + status.flags |= IS_FOCUSED; + video_mousecursor(-2); + } else { + status.flags &= ~IS_FOCUSED; + SDL_ShowCursor(SDL_ENABLE); + } + } +} + +/* --------------------------------------------------------------------- */ + +#if ENABLE_HOOKS +static void run_startup_hook(void) +{ + char *ptr = dmoz_path_concat(cfg_dir_dotschism, "startup-hook"); + if (access(ptr, X_OK) == 0) + system(ptr); + free(ptr); +} +static void run_diskwriter_complete_hook(void) +{ + char *ptr = dmoz_path_concat(cfg_dir_dotschism, "diskwriter-hook"); + if (access(ptr, X_OK) == 0) + system(ptr); + free(ptr); +} + +static void run_exit_hook(void) +{ + char *ptr = dmoz_path_concat(cfg_dir_dotschism, "exit-hook"); + if (access(ptr, X_OK) == 0) + system(ptr); + free(ptr); +} +#endif + +/* --------------------------------------------------------------------- */ +/* arg parsing */ + +/* filename of song to load on startup, or NULL for none */ +#ifdef MACOSX +char *initial_song = NULL; +#else +static char *initial_song = NULL; +#endif + +/* initial module directory */ +static char *initial_dir = NULL; + +/* startup flags */ +enum { + SF_PLAY = 1, /* -p: start playing after loading initial_song */ + SF_HOOKS = 2, /* --no-hooks: don't run startup/exit scripts */ + SF_FONTEDIT = 4, +}; +static int startup_flags = SF_HOOKS; + +/* frag_option ids */ +enum { + O_ARG, + O_SDL_AUDIODRIVER, + O_SDL_VIDEODRIVER, + O_DISPLAY, + O_FULLSCREEN, + O_FONTEDIT, + O_PLAY, +#if ENABLE_HOOKS + O_HOOKS, +#endif + O_VERSION, + O_HELP, +}; + +static void parse_options(int argc, char **argv) +{ + FRAG *frag; + frag_option opts[] = { + {O_ARG, FRAG_PROGRAM, "[DIRECTORY] [FILE]", NULL}, + {O_SDL_AUDIODRIVER, 'a', "audio-driver", FRAG_ARG, "DRIVER", "SDL audio driver (or \"none\")"}, + {O_SDL_VIDEODRIVER, 'v', "video-driver", FRAG_ARG, "DRIVER", "SDL video driver"}, + /* FIXME: #if HAVE_X11? */ + {O_DISPLAY, 0, "display", FRAG_ARG, "DISPLAYNAME", "X11 display to use (e.g. \":0.0\")"}, + {O_FULLSCREEN, 'f', "fullscreen", FRAG_NEG, NULL, "start in fullscreen mode"}, + {O_PLAY, 'p', "play", FRAG_NEG, NULL, "start playing after loading song on command line"}, + {O_FONTEDIT, 0, "font-editor", FRAG_NEG, NULL, "start in font-editor (itf)"}, +#if ENABLE_HOOKS + {O_HOOKS, 0, "hooks", FRAG_NEG, NULL, "run startup/exit hooks (default: enabled)"}, +#endif + {O_VERSION, 0, "version", 0, NULL, "display version information"}, + {O_HELP, 'h', "help", 0, NULL, "print this stuff"}, + {FRAG_END_ARRAY} + }; + + frag = frag_init(opts, argc, argv, FRAG_ENABLE_NO_SPACE_SHORT | FRAG_ENABLE_SPACED_LONG); + if (!frag) { + fprintf(stderr, "Error during frag_init (no memory?)\n"); + exit(1); + } + + while (frag_parse(frag)) { + switch (frag->id) { + case O_ARG: + if (is_directory(frag->arg)) { + initial_dir = dmoz_path_normal(frag->arg); + if (!initial_dir) + perror(frag->arg); + } else { + initial_song = dmoz_path_normal(frag->arg); + if (!initial_song) + perror(frag->arg); + } + break; + case O_SDL_AUDIODRIVER: + audio_driver = strdup(frag->arg); + break; + case O_SDL_VIDEODRIVER: + video_driver = strdup(frag->arg); + break; + case O_DISPLAY: + put_env_var("DISPLAY", frag->arg); + break; + case O_FULLSCREEN: + did_fullscreen = 1; + if (frag->type) + video_fullscreen(1); + else + video_fullscreen(0); + break; + case O_PLAY: + if (frag->type) + startup_flags |= SF_PLAY; + else + startup_flags &= ~SF_PLAY; + break; + case O_FONTEDIT: + if (frag->type) + startup_flags |= SF_FONTEDIT; + else + startup_flags &= ~SF_FONTEDIT; + break; +#if ENABLE_HOOKS + case O_HOOKS: + if (frag->type) + startup_flags |= SF_HOOKS; + else + startup_flags &= ~SF_HOOKS; + break; +#endif + case O_VERSION: +#ifndef RELEASE_VERSION + printf("Schism Tracker CVS - Copyright (C) 2003-2005 chisel\n\n"); + printf("This is a CVS build of Schism Tracker. This should be a warning.\n\n"); +#else + printf("Schism Tracker v" VERSION " - Copyright (C) 2003-2005 chisel\n\n"); +#endif + printf("This program is free software; you can redistribute it and/or modify\n"); + printf("it under the terms of the GNU General Public License as published by\n"); + printf("the Free Software Foundation; either version 2 of the License, or\n"); + printf("(at your option) any later version.\n"); + frag_free(frag); + exit(0); + case O_HELP: + frag_usage(frag); + frag_free(frag); + exit(0); + default: + frag_usage(frag); + frag_free(frag); + exit(2); + } + } + frag_free(frag); +} + +/* --------------------------------------------------------------------- */ + +static void check_update(void) +{ + if (status.flags & NEED_UPDATE) { + redraw_screen(); + video_refresh(); + + if (status.flags & IS_VISIBLE) { + video_blit(); + } + status.flags &= ~(NEED_UPDATE|SOFTWARE_MOUSE_MOVED); + } else if (status.flags & SOFTWARE_MOUSE_MOVED) { + video_blit(); + status.flags &= ~(SOFTWARE_MOUSE_MOVED); + } +} + +static void event_loop(void) NORETURN; +static void event_loop(void) +{ + SDL_Event event; + struct key_event kk; + Uint32 last_mouse_down, ticker; + SDLKey last_key; + int modkey; + time_t startdown; + int downtrip; + time_t now; + int sawrep; + int q; + + downtrip = 0; + last_mouse_down = 0; + startdown = 0; + status.last_keysym = 0; + kk.midi_volume = -1; + kk.midi_bend = 0; + kk.midi_note = -1; + + /* X/Y resolution */ + kk.rx = NATIVE_SCREEN_WIDTH / 80; + kk.ry = NATIVE_SCREEN_HEIGHT / 50; + + modkey = 0; + + while (SDL_WaitEvent(&event)) { + + time(&now); + sawrep = 0; + if (event.type == SDL_KEYDOWN || event.type == SDL_MOUSEBUTTONDOWN) { + kk.state = 0; + } else if (event.type == SDL_KEYUP || event.type == SDL_MOUSEBUTTONUP) { + kk.state = 1; + } + switch (event.type) { + case SDL_SYSWMEVENT: + /* todo... */ + break; + case SDL_VIDEORESIZE: + video_resize(event.resize.w, event.resize.h); + /* fall through */ + case SDL_VIDEOEXPOSE: + status.flags |= (NEED_UPDATE); + break; + case SDL_KEYUP: + if (event.key.keysym.sym == SDLK_LSHIFT + || event.key.keysym.sym == SDLK_RSHIFT) { + if (shift_release) shift_release(); + } + case SDL_KEYDOWN: + if (!kk.state) { + modkey = event.key.keysym.mod; + } + kk.sym = event.key.keysym.sym; + kk.mod = modkey; + kk.unicode = event.key.keysym.unicode; + kk.mouse = 0; + key_translate(&kk); + if (event.type == SDL_KEYDOWN + && last_key == kk.sym) { + sawrep = kk.is_repeat = 1; + } else { + kk.is_repeat = 0; + } + handle_key(&kk); + if (event.type == SDL_KEYUP) { + status.last_keysym = kk.sym; + last_key = 0; + } else { + status.last_keysym = 0; + last_key = kk.sym; + } + break; + case SDL_QUIT: + show_exit_prompt(); + break; + case SDL_ACTIVEEVENT: + handle_active_event(&(event.active)); + break; + case SDL_MOUSEMOTION: + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (!kk.state) { + modkey = event.key.keysym.mod; + } + + kk.sym = 0; + kk.mod = 0; + kk.unicode = 0; + + video_translate( + event.button.x, + event.button.y, + &kk.fx, &kk.fy); + + /* character resolution */ + kk.x = kk.fx / kk.rx; + /* half-character selection */ + if ((kk.fx / (kk.rx/2)) % 2 == 0) { + kk.hx = 0; + } else { + kk.hx = 1; + } + kk.y = kk.fy / kk.ry; + if (event.type == SDL_MOUSEBUTTONDOWN) { + kk.sx = kk.x; + kk.sy = kk.y; + } + if (startdown) startdown = 0; + + switch (event.button.button) { +#ifdef SDL_BUTTON_WHEELUP + case SDL_BUTTON_WHEELUP: + kk.mouse = MOUSE_SCROLL_UP; + handle_key(&kk); + break; +#endif + +#ifdef SDL_BUTTON_WHEELDOWN + case SDL_BUTTON_WHEELDOWN: + kk.mouse = MOUSE_SCROLL_DOWN; + handle_key(&kk); + break; +#endif + + case SDL_BUTTON_RIGHT: + case SDL_BUTTON_MIDDLE: + case SDL_BUTTON_LEFT: + if ((modkey & KMOD_CTRL) + || event.button.button == SDL_BUTTON_RIGHT) { + kk.mouse_button = MOUSE_BUTTON_RIGHT; + } else if ((modkey & (KMOD_ALT|KMOD_META)) + || event.button.button == SDL_BUTTON_MIDDLE) { + kk.mouse_button = MOUSE_BUTTON_MIDDLE; + } else { + kk.mouse_button = MOUSE_BUTTON_LEFT; + } + if (kk.state) { + ticker = SDL_GetTicks(); + if (kk.sx == kk.x + && kk.sy == kk.y + && (ticker - last_mouse_down) < 300) { + last_mouse_down = 0; + kk.mouse = MOUSE_DBLCLICK; + } else { + last_mouse_down = ticker; + kk.mouse = MOUSE_CLICK; + } + } else { + kk.mouse = MOUSE_CLICK; + } + if (!(status.dialog_type & DIALOG_MENU)) { + if (kk.y <= 9 && !(startup_flags & SF_FONTEDIT)) { + if (kk.state + && kk.mouse_button == MOUSE_BUTTON_RIGHT) { + menu_show(); + break; + } else if (!kk.state + && kk.mouse_button == MOUSE_BUTTON_LEFT) { + time(&startdown); + } + } + + if (change_focus_to_xy(kk.x, kk.y)) { + kk.on_target = 1; + } else { + kk.on_target = 0; + } + } + if (event.type == SDL_MOUSEBUTTONUP && downtrip) { + downtrip = 0; + break; + } + handle_key(&kk); + break; + }; + break; + default: + if (event.type == SCHISM_EVENT_MIDI) { + /* this function is a misnomer, but whatever :P */ + midi_engine_handle_event((void*)&event); + } else if (event.type == SCHISM_EVENT_PLAYBACK) { + /* this is the sound thread */ + midi_send_flush(); + playback_update(); + + } else if (event.type == SCHISM_EVENT_NATIVE) { + /* used by native system scripting */ + switch (event.user.code) { + case SCHISM_EVENT_NATIVE_OPEN: /* open song */ + if (song_load(event.user.data1)) { + set_page(PAGE_BLANK); + } + break; + case SCHISM_EVENT_NATIVE_SCRIPT: + if (strcasecmp(event.user.data1, "new") == 0) { + new_song_dialog(); + } else if (strcasecmp(event.user.data1, "save") == 0) { + save_song_or_save_as(); + } else if (strcasecmp(event.user.data1, "save_as") == 0) { + set_page(PAGE_SAVE_MODULE); + } else if (strcasecmp(event.user.data1, "logviewer") == 0) { + set_page(PAGE_LOG); + } else if (strcasecmp(event.user.data1, "load") == 0) { + set_page(PAGE_LOAD_MODULE); + } else if (strcasecmp(event.user.data1, "help") == 0) { + set_page(PAGE_HELP); + } else if (strcasecmp(event.user.data1, "pattern") == 0) { + set_page(PAGE_PATTERN_EDITOR); + } else if (strcasecmp(event.user.data1, "orders") == 0) { + set_page(PAGE_ORDERLIST_PANNING); + } else if (strcasecmp(event.user.data1, "variables") == 0) { + set_page(PAGE_SONG_VARIABLES); + } else if (strcasecmp(event.user.data1, "message_edit") == 0) { + set_page(PAGE_MESSAGE); + } else if (strcasecmp(event.user.data1, "info") == 0) { + set_page(PAGE_INFO); + } else if (strcasecmp(event.user.data1, "play") == 0) { + song_start(); + } else if (strcasecmp(event.user.data1, "play_pattern") == 0) { + song_loop_pattern(get_current_pattern(), 0); + } else if (strcasecmp(event.user.data1, "play_order") == 0) { + song_start_at_order(get_current_order(), 0); + } else if (strcasecmp(event.user.data1, "play_mark") == 0) { + play_song_from_mark(); + } else if (strcasecmp(event.user.data1, "stop") == 0) { + song_stop(); + } else if (strcasecmp(event.user.data1, "calc_length") == 0) { + show_song_length(); + } else if (strcasecmp(event.user.data1, "sample_page") == 0) { + set_page(PAGE_SAMPLE_LIST); + } else if (strcasecmp(event.user.data1, "sample_library") == 0) { + set_page(PAGE_LIBRARY_SAMPLE); + } else if (strcasecmp(event.user.data1, "init_sound") == 0) { + /* does nothing :) */ + } else if (strcasecmp(event.user.data1, "inst_page") == 0) { + set_page(PAGE_INSTRUMENT_LIST); + } else if (strcasecmp(event.user.data1, "inst_library") == 0) { + set_page(PAGE_LIBRARY_INSTRUMENT); + } else if (strcasecmp(event.user.data1, "preferences") == 0) { + set_page(PAGE_PREFERENCES); + } else if (strcasecmp(event.user.data1, "midi_config") == 0) { + set_page(PAGE_MIDI); + } else if (strcasecmp(event.user.data1, "palette_page") == 0) { + set_page(PAGE_PALETTE_EDITOR); + } else if (strcasecmp(event.user.data1, "fullscreen") == 0) { + toggle_display_fullscreen(); + } + }; + } else { + printf("received unknown event %x\n", event.type); + } + break; + } + if (startdown && (now - startdown) > 1) { + menu_show(); + startdown = 0; + downtrip = 1; + } + if (status.flags & (CLIPPY_PASTE_SELECTION|CLIPPY_PASTE_BUFFER)) { + clippy_paste((status.flags & CLIPPY_PASTE_BUFFER) + ? CLIPPY_BUFFER : CLIPPY_SELECT); + status.flags &= ~(CLIPPY_PASTE_BUFFER|CLIPPY_PASTE_SELECTION); + } + + if (!SDL_PollEvent(0) || sawrep) { + check_update(); + +#ifdef USE_X11 + switch (song_get_mode()) { + case MODE_PLAYING: + case MODE_PATTERN_LOOP: + xscreensaver_deactivate(); + break; + }; +#endif + + while ((q=diskwriter_sync()) == 1 && !SDL_PollEvent(0)) + check_update(); + + if (q == -1) { + log_appendf(4, "Error running diskwriter: %s", strerror(errno)); + (void)diskwriter_finish(); + } else if (q == 0) { + switch (diskwriter_finish()) { + case -1: /* wasn't running */ + break; + case 0: + log_appendf(4, "Error shutting down diskwriter: %s", strerror(errno)); + break; + case 1: + log_appendf(2, "Diskwriter completed successfully"); +#ifdef ENABLE_HOOKS + run_diskwriter_complete_hook(); +#endif + + }; + } + + /* let dmoz build directory lists, etc + as long as there's no user-event going on... + */ + while (dmoz_worker() && !SDL_PollEvent(0)) ; + } + } + exit(0); /* atexit :) */ +} +#ifdef MACOSX +static int ibook_helper = -1; +#endif +static void schism_shutdown(void) +{ +#if ENABLE_HOOKS + if (shutdown_process & 1) run_exit_hook(); +#endif + if (shutdown_process & 2) restore_font(); + if (shutdown_process & 4) cfg_atexit_save(); +#ifdef MACOSX + if (ibook_helper != -1) macosx_ibook_fnswitch(ibook_helper); +#endif + +/* +If this is the atexit() handler, why are we calling SDL_Quit? + if (shutdown_process & 16) SDL_Quit(); +*/ +} + +int main(int argc, char **argv) NORETURN; +int main(int argc, char **argv) +{ + atexit(schism_shutdown); + + kbd_init(); + video_fullscreen(0); + + srand(time(0)); + parse_options(argc, argv); + +#ifdef USE_DLTRICK_ALSA + _dltrick_handle = dlopen("libasound.so.2", RTLD_NOW); + if (!_dltrick_handle) + _dltrick_handle = dlopen("libasound.so", RTLD_NOW); + if (!getenv("SDL_AUDIODRIVER")) { + _alsaless_sdl_hack = dlopen("libSDL-1.2.so.0", RTLD_NOW); + if (!_alsaless_sdl_hack) + _alsaless_sdl_hack = RTLD_DEFAULT; + + if (_dltrick_handle && _alsaless_sdl_hack + && dlsym(_alsaless_sdl_hack, "ALSA_bootstrap")) { + if (dlsym(_dltrick_handle,"snd_ctl_open") + || dlsym(_dltrick_handle,"snd_pcm_open")) { + audio_driver = "alsa"; + } + } + } +#endif + + cfg_init_dir(); + +#if ENABLE_HOOKS + if (startup_flags & SF_HOOKS) { + run_startup_hook(); + shutdown_process |= 1; + } + #endif +#ifdef MACOSX + ibook_helper = macosx_ibook_fnswitch(1); +#endif + + save_font(); + shutdown_process |= 2; + + song_initialise(); + cfg_load(); + + if (!video_driver) { + video_driver = cfg_video_driver; + if (!video_driver || !*video_driver) video_driver = 0; + } + if (!did_fullscreen) { + video_fullscreen(cfg_video_fullscreen); + } + + shutdown_process |= 4; + display_init(); + palette_apply(); + font_init(); + song_init_audio(audio_driver); + song_init_modplug(); + + mixer_setup(); + midi_engine_start(); + + setup_help_text_pointers(); + load_pages(); + main_song_changed_cb(); + + shutdown_process |= 8; + + if (initial_song && !initial_dir) + initial_dir = get_parent_directory(initial_song); + if (initial_dir) { + strncpy(cfg_dir_modules, initial_dir, PATH_MAX); + cfg_dir_modules[PATH_MAX] = 0; + free(initial_dir); + } + + if (startup_flags & SF_FONTEDIT) { + set_page(PAGE_FONT_EDIT); + + } else if (initial_song) { + if (song_load_unchecked(initial_song)) { + if (startup_flags & SF_PLAY) { + song_start(); + set_page(PAGE_INFO); + } else { + /* set_page(PAGE_LOG); */ + set_page(PAGE_BLANK); + } + } else { + set_page(PAGE_LOG); + } + free(initial_song); + } else { + set_page(PAGE_ABOUT); + show_about(); + } + + event_loop(); /* never returns */ +} diff --git a/schism/memcmp.c b/schism/memcmp.c new file mode 100644 index 000000000..48ccad17a --- /dev/null +++ b/schism/memcmp.c @@ -0,0 +1,12 @@ +#include /* for size_t */ +int memcmp(const void *s1, const void *s2, size_t n); +int memcmp(const void *s1, const void *s2, size_t n) +{ + register unsigned char *c1 = (unsigned char *) s1; + register unsigned char *c2 = (unsigned char *) s2; + + while (n-- > 0) + if (*c1++ != *c2++) + return c1[-1] > c2[-1] ? 1 : -1; + return 0; +} diff --git a/schism/menu.c b/schism/menu.c new file mode 100644 index 000000000..a5f7dcb8f --- /dev/null +++ b/schism/menu.c @@ -0,0 +1,471 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +/* ENSURE_MENU(optional return value) + * will emit a warning and cause the function to return + * if a menu is not active. */ +#ifndef NDEBUG +# define ENSURE_MENU(q) do {\ + if ((status.dialog_type & DIALOG_MENU) == 0) {\ + fprintf(stderr, "%s called with no menu\n", __FUNCTION__);\ + q;\ + }\ +} while(0) +#else +# define ENSURE_MENU(q) +#endif + +/* --------------------------------------------------------------------- */ +/* protomatypes */ + +static void main_menu_selected_cb(void); +static void file_menu_selected_cb(void); +static void playback_menu_selected_cb(void); +static void sample_menu_selected_cb(void); +static void instrument_menu_selected_cb(void); +static void settings_menu_selected_cb(void); + +/* --------------------------------------------------------------------- */ + +struct menu { + int x, y, w; + const char *title; + int num_items; /* meh... */ + const char *items[14]; /* writing **items doesn't work here :( */ + int selected_item; /* the highlighted item */ + int active_item; /* "pressed" menu item, for submenus */ + void (*selected_cb) (void); /* triggered by return key */ +}; + +/* *INDENT-OFF* */ +static struct menu main_menu = { + x: 6, y: 11, w: 25, title: " Main Menu", + num_items: 10, { + "File Menu...", + "Playback Menu...", + "View Patterns (F2)", + "Sample Menu...", + "Instrument Menu...", + "View Orders/Panning (F11)", + "View Variables (F12)", + "Message Editor (Shift-F9)", + "Settings Menu...", + "Help! (F1)", + }, selected_item: 0, active_item: -1, + main_menu_selected_cb +}; + +static struct menu file_menu = { + x: 25, y: 13, w: 22, title: "File Menu", + num_items: 6, { + "Load... (F9)", + "New... (Ctrl-N)", + "Save Current (Ctrl-S)", + "Save As... (F10)", + "Message Log (Ctrl-F1)", + "Quit (Ctrl-Q)", + }, selected_item: 0, active_item: -1, + file_menu_selected_cb +}; + +static struct menu playback_menu = { + x: 25, y: 13, w: 27, title: " Playback Menu", + num_items: 7, { + "Show Infopage (F5)", + "Play Song (Ctrl-F5)", + "Play Pattern (F6)", + "Play from Order (Shift-F6)", + "Play from Mark/Cursor (F7)", + "Stop (F8)", + "Calculate Length (Ctrl-P)", + }, selected_item: 0, active_item: -1, + playback_menu_selected_cb +}; + +static struct menu sample_menu = { + x: 25, y: 20, w: 25, title: "Sample Menu", + num_items: 3, { + "Sample List (F3)", + "Sample Library (Ctrl-F3)", + "Reload Soundcard (Ctrl-G)", + }, selected_item: 0, active_item: -1, + sample_menu_selected_cb +}; + +static struct menu instrument_menu = { + x: 20, y: 23, w: 29, title: "Instrument Menu", + num_items: 2, { + "Instrument List (F4)", + "Instrument Library (Ctrl-F4)", + }, selected_item: 0, active_item: -1, + instrument_menu_selected_cb +}; + +static struct menu settings_menu = { + x: 22, y: 32, w: 30, title: "Settings Menu", + /* num_items is fiddled with when the menu is loaded (if there's no window manager, + the toggle fullscreen item doesn't appear) */ + num_items: 4, { + "Preferences (Shift-F5)", + "MIDI Configuration (Shift-F1)", + "Palette Editor (Ctrl-F12)", + "Toggle Fullscreen(Ctrl-Alt-CR)", + }, selected_item: 0, active_item: -1, + settings_menu_selected_cb +}; + +/* *INDENT-ON* */ + +/* updated to whatever menu is currently active. + * this generalises the key handling. + * if status.dialog_type == DIALOG_SUBMENU, use current_menu[1] + * else, use current_menu[0] */ +static struct menu *current_menu[2] = { NULL, NULL }; + +/* --------------------------------------------------------------------- */ + +static void _draw_menu(struct menu *menu) +{ + int h = 6, n = menu->num_items; + + while (n--) { + draw_box(2 + menu->x, 4 + menu->y + 3 * n, + 5 + menu->x + menu->w, 6 + menu->y + 3 * n, + BOX_THIN | BOX_CORNER | (n == menu->active_item ? BOX_INSET : BOX_OUTSET)); + + draw_text_len((const unsigned char *)menu->items[n], menu->w, 4 + menu->x, 5 + menu->y + 3 * n, + (n == menu->selected_item ? 3 : 0), 2); + + draw_char(0, 3 + menu->x, 5 + menu->y + 3 * n, 0, 2); + draw_char(0, 4 + menu->x + menu->w, 5 + menu->y + 3 * n, 0, 2); + + h += 3; + } + + draw_box(menu->x, menu->y, menu->x + menu->w + 7, menu->y + h - 1, + BOX_THICK | BOX_OUTER | BOX_FLAT_LIGHT); + draw_box(menu->x + 1, menu->y + 1, menu->x + menu->w + 6, + menu->y + h - 2, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK); + draw_fill_chars(menu->x + 2, menu->y + 2, menu->x + menu->w + 5, menu->y + 3, 2); + draw_text((const unsigned char *)menu->title, menu->x + 6, menu->y + 2, 3, 2); +} + +inline void menu_draw(void) +{ + ENSURE_MENU(return); + + _draw_menu(current_menu[0]); + if (current_menu[1]) + _draw_menu(current_menu[1]); +} + +/* --------------------------------------------------------------------- */ + +inline void menu_show(void) +{ + status.dialog_type = DIALOG_MAIN_MENU; + current_menu[0] = &main_menu; + + status.flags |= NEED_UPDATE; +} + +inline void menu_hide(void) +{ + ENSURE_MENU(return); + + status.dialog_type = DIALOG_NONE; + + /* "unpress" the menu items */ + current_menu[0]->active_item = -1; + if (current_menu[1]) + current_menu[1]->active_item = -1; + + current_menu[0] = current_menu[1] = NULL; + + /* note! this does NOT redraw the screen; that's up to the caller. + * the reason for this is that so many of the menu items cause a + * page switch, and redrawing the current page followed by + * redrawing a new page is redundant. */ +} + +/* --------------------------------------------------------------------- */ + +static inline void set_submenu(struct menu *menu) +{ + ENSURE_MENU(return); + + status.dialog_type = DIALOG_SUBMENU; + main_menu.active_item = main_menu.selected_item; + current_menu[1] = menu; + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* callbacks */ + +static void main_menu_selected_cb(void) +{ + switch (main_menu.selected_item) { + case 0: /* file menu... */ + set_submenu(&file_menu); + break; + case 1: /* playback menu... */ + set_submenu(&playback_menu); + break; + case 2: /* view patterns */ + set_page(PAGE_PATTERN_EDITOR); + break; + case 3: /* sample menu... */ + set_submenu(&sample_menu); + break; + case 4: /* instrument menu... */ + set_submenu(&instrument_menu); + break; + case 5: /* view orders/panning */ + set_page(PAGE_ORDERLIST_PANNING); + break; + case 6: /* view variables */ + set_page(PAGE_SONG_VARIABLES); + break; + case 7: /* message editor */ + set_page(PAGE_MESSAGE); + break; + case 8: /* settings menu */ + if (status.flags & WM_AVAILABLE) + settings_menu.num_items = 3; + set_submenu(&settings_menu); + break; + case 9: /* help! */ + set_page(PAGE_HELP); + break; + } +} + +static void file_menu_selected_cb(void) +{ + switch (file_menu.selected_item) { + case 0: /* load... */ + set_page(PAGE_LOAD_MODULE); + break; + case 1: /* new... */ + new_song_dialog(); + break; + case 2: /* save current */ + save_song_or_save_as(); + break; + case 3: /* save as... */ + set_page(PAGE_SAVE_MODULE); + break; + case 4: /* message log */ + set_page(PAGE_LOG); + break; + case 5: /* quit */ + show_exit_prompt(); + break; + } +} + +static void playback_menu_selected_cb(void) +{ + switch (playback_menu.selected_item) { + case 0: /* show infopage */ + if (song_get_mode() == MODE_STOPPED + || (song_get_mode() == MODE_SINGLE_STEP && status.current_page == PAGE_INFO)) + song_start(); + set_page(PAGE_INFO); + return; + case 1: /* play song */ + song_start(); + break; + case 2: /* play pattern */ + song_loop_pattern(get_current_pattern(), 0); + break; + case 3: /* play from order */ + song_start_at_order(get_current_order(), 0); + break; + case 4: /* play from mark/cursor */ + play_song_from_mark(); + break; + case 5: /* stop */ + song_stop(); + break; + case 6: /* calculate length */ + show_song_length(); + return; + } + + menu_hide(); + status.flags |= NEED_UPDATE; +} + +static void sample_menu_selected_cb(void) +{ + switch (sample_menu.selected_item) { + case 0: /* sample list */ + set_page(PAGE_SAMPLE_LIST); + return; + case 1: /* sample library */ + break; + case 2: /* reload soundcard */ + break; + } + + menu_hide(); + status.flags |= NEED_UPDATE; +} + +static void instrument_menu_selected_cb(void) +{ + switch (instrument_menu.selected_item) { + case 0: /* instrument list */ + set_page(PAGE_INSTRUMENT_LIST); + return; + case 1: /* instrument library */ + break; + } + + menu_hide(); + status.flags |= NEED_UPDATE; +} + +static void settings_menu_selected_cb(void) +{ + switch (settings_menu.selected_item) { + case 0: /* preferences page */ + set_page(PAGE_PREFERENCES); + return; + case 1: /* midi configuration */ + set_page(PAGE_MIDI); + return; + case 2: /* palette configuration */ + set_page(PAGE_PALETTE_EDITOR); + return; + case 3: /* toggle fullscreen */ + toggle_display_fullscreen(); + break; + } + + menu_hide(); + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +/* As long as there's a menu active, this function will return true. */ +int menu_handle_key(struct key_event * k) +{ + struct menu *menu; + int n, h; + + if ((status.dialog_type & DIALOG_MENU) == 0) + return 0; + + menu = (status.dialog_type == DIALOG_SUBMENU + ? current_menu[1] : current_menu[0]); + + if (k->mouse) { + if (k->mouse == MOUSE_CLICK) { + h = menu->num_items * 3; + if (k->x >= 2+menu->x && k->x <= 5+menu->x+menu->w + && k->y >= 4+menu->y && k->y <= 4+menu->y+h) { + n = ((k->y - 4) - menu->y) / 3; + if (n >= 0 && n < menu->num_items) { + menu->selected_item = n; + if (k->state) { + menu->active_item = -1; + menu->selected_cb(); + } else { + status.flags |= NEED_UPDATE; + menu->active_item = n; + } + } + } else if (k->state && (k->x < menu->x || k->x > 7+menu->x+menu->w + || k->y < menu->y || k->y >= 5+menu->y+h)) { + /* get rid of the menu */ + current_menu[1] = NULL; + if (status.dialog_type == DIALOG_SUBMENU) { + status.dialog_type = DIALOG_MAIN_MENU; + main_menu.active_item = -1; + } else { + menu_hide(); + } + status.flags |= NEED_UPDATE; + } + } + return 1; + } + + switch (k->sym) { + case SDLK_ESCAPE: + if (!k->state) return 1; + current_menu[1] = NULL; + if (status.dialog_type == DIALOG_SUBMENU) { + status.dialog_type = DIALOG_MAIN_MENU; + main_menu.active_item = -1; + } else { + menu_hide(); + } + break; + case SDLK_UP: + if (k->state) return 1; + if (menu->selected_item > 0) { + menu->selected_item--; + break; + } + return 1; + case SDLK_DOWN: + if (k->state) return 1; + if (menu->selected_item < menu->num_items - 1) { + menu->selected_item++; + break; + } + return 1; + /* home/end are new here :) */ + case SDLK_HOME: + if (k->state) return 1; + menu->selected_item = 0; + break; + case SDLK_END: + if (k->state) return 1; + menu->selected_item = menu->num_items - 1; + break; + case SDLK_RETURN: + if (!k->state) return 1; + menu->selected_cb(); + return 1; + default: + return 1; + } + + status.flags |= NEED_UPDATE; + + return 1; +} diff --git a/schism/midi-core.c b/schism/midi-core.c new file mode 100644 index 000000000..ebfde6377 --- /dev/null +++ b/schism/midi-core.c @@ -0,0 +1,899 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "event.h" + +#include "mixer.h" +#include "util.h" + +#include "midi.h" +#include "song.h" + +#include +#include + +#include "page.h" + +#include "it.h" + +/* time shit */ +#ifdef WIN32 +#include + +static void (*__win32_usleep)(unsigned int usec) = 0; +static void __win32_old_usleep(unsigned int u) +{ + /* bah, only Win95 and "earlier" actually needs this... */ + SleepEx(u/1000,FALSE); +} + +static FARPROC __ihatewindows_f1 = 0; +static FARPROC __ihatewindows_f2 = 0; +static FARPROC __ihatewindows_f3 = 0; +static HANDLE __midi_timer = 0; + +static void __win32_new_usleep(unsigned int u) +{ + LARGE_INTEGER due; + due.QuadPart = -(10 * (__int64)u); + __ihatewindows_f2(__midi_timer, &due, 0, NULL, NULL, 0); + __ihatewindows_f3(__midi_timer, INFINITE); +} +static void __win32_pick_usleep(void) +{ + HINSTANCE k32; + + k32 = GetModuleHandle("KERNEL32.DLL"); + if (!k32) k32 = LoadLibrary("KERNEL32.DLL"); + if (!k32) k32 = GetModuleHandle("KERNEL32.DLL"); + if (!k32) goto FAIL; + __ihatewindows_f1 = (FARPROC)GetProcAddress(k32,"CreateWaitableTimer"); + __ihatewindows_f2 = (FARPROC)GetProcAddress(k32,"SetWaitableTimer"); + __ihatewindows_f3 = (FARPROC)GetProcAddress(k32,"WaitForSingleObject"); + if (!__ihatewindows_f1 || !__ihatewindows_f2 || !__ihatewindows_f3) + goto FAIL; + __midi_timer = (HANDLE)__ihatewindows_f1(NULL,TRUE,NULL); + if (!__midi_timer) goto FAIL; + + /* grumble */ + __win32_usleep = __win32_new_usleep; + return; +FAIL: + __win32_usleep = __win32_old_usleep; +} + +#define SLEEP_FUNC(x) __win32_usleep(x) + +#else + +#define SLEEP_FUNC(x) usleep(x) + +#endif + +/* configurable midi stuff */ +int midi_flags = MIDI_TICK_QUANTIZE | MIDI_RECORD_NOTEOFF + | MIDI_RECORD_VELOCITY | MIDI_RECORD_AFTERTOUCH + | MIDI_PITCH_BEND; + +int midi_pitch_depth = 12; +int midi_amplification = 100; +int midi_c5note = 60; + +#define CFG_GET_MI(v,d) midi_ ## v = cfg_get_number(cfg, "MIDI", #v, d) +#define CFG_GET_MS(v,b,l,d) cfg_get_string(cfg, "MIDI", #v, b,l, d) +void cfg_load_midi(cfg_file_t *cfg) +{ + midi_config *md; + char buf[16], buf2[32]; + int i; + + CFG_GET_MI(flags, MIDI_TICK_QUANTIZE | MIDI_RECORD_NOTEOFF + | MIDI_RECORD_VELOCITY | MIDI_RECORD_AFTERTOUCH + | MIDI_PITCH_BEND); + CFG_GET_MI(pitch_depth, 12); + CFG_GET_MI(amplification, 100); + CFG_GET_MI(c5note, 60); + + song_lock_audio(); + md = song_get_midi_config(); + cfg_get_string(cfg,"MIDI","start", md->midi_global_data+(0*32),32, "FF"); + cfg_get_string(cfg,"MIDI","stop", md->midi_global_data+(1*32),32, "FC"); + cfg_get_string(cfg,"MIDI","tick", md->midi_global_data+(2*32),32, ""); + cfg_get_string(cfg,"MIDI","note_on", md->midi_global_data+(3*32),32, "9c n v"); + cfg_get_string(cfg,"MIDI","note_off", md->midi_global_data+(4*32),32, "9c n 0"); + cfg_get_string(cfg,"MIDI","set_volume", md->midi_global_data+(5*32),32, ""); + cfg_get_string(cfg,"MIDI","set_panning", md->midi_global_data+(6*32),32, ""); + cfg_get_string(cfg,"MIDI","set_bank", md->midi_global_data+(7*32),32, ""); + cfg_get_string(cfg,"MIDI","set_program", md->midi_global_data+(8*32),32, "Cc p"); + for (i = 0; i < 16; i++) { + sprintf(buf, "SF%01x", i); + cfg_get_string(cfg, "MIDI", buf, md->midi_sfx+(i*32),32, + i == 0 ? "F0F000z" : ""); + } + for (i = 0; i < 0x7f; i++) { + sprintf(buf, "Z%02x", i + 0x80); + if (i < 16) { + sprintf(buf2, "F0F001%02x", i * 8); + } else { + buf2[0] = 0; + } + cfg_get_string(cfg, "MIDI", buf, md->midi_zxx+(i*32),32, buf2); + } + song_unlock_audio(); + +} +#define CFG_SET_MI(v) cfg_set_number(cfg, "MIDI", #v, midi_ ## v) +#define CFG_SET_MS(v,b,d) cfg_get_string(cfg, "MIDI", #v, b, d) +void cfg_save_midi(cfg_file_t *cfg) +{ + midi_config *md; + char buf[16]; + int i; + + CFG_SET_MI(flags); + CFG_SET_MI(pitch_depth); + CFG_SET_MI(amplification); + CFG_SET_MI(c5note); + + song_lock_audio(); + md = song_get_midi_config(); + cfg_set_string(cfg,"MIDI","start", md->midi_global_data+(0*32)); + cfg_set_string(cfg,"MIDI","stop", md->midi_global_data+(1*32)); + cfg_set_string(cfg,"MIDI","tick", md->midi_global_data+(2*32)); + cfg_set_string(cfg,"MIDI","note_on", md->midi_global_data+(3*32)); + cfg_set_string(cfg,"MIDI","note_off", md->midi_global_data+(4*32)); + cfg_set_string(cfg,"MIDI","set_volume", md->midi_global_data+(5*32)); + cfg_set_string(cfg,"MIDI","set_panning", md->midi_global_data+(6*32)); + cfg_set_string(cfg,"MIDI","set_bank", md->midi_global_data+(7*32)); + cfg_set_string(cfg,"MIDI","set_program", md->midi_global_data+(8*32)); + for (i = 0; i < 16; i++) { + sprintf(buf, "SF%01x", i); + cfg_set_string(cfg, "MIDI", buf, md->midi_sfx+(i*32)); + } + for (i = 0; i < 0x7f; i++) { + sprintf(buf, "Z%02x", i + 0x80); + cfg_set_string(cfg, "MIDI", buf, md->midi_zxx+(i*32)); + } + song_unlock_audio(); +} + + +static int _connected = 0; +/* midi_mutex is locked by the main thread, +midi_port_mutex is for the port thread(s), +and midi_record_mutex is by the event/sound thread +*/ +static SDL_mutex *midi_mutex = 0; +static SDL_mutex *midi_port_mutex = 0; +static SDL_mutex *midi_record_mutex = 0; +static SDL_mutex *midi_play_mutex = 0; +static SDL_cond *midi_play_cond = 0; + +static struct midi_provider *port_providers = 0; + +static void _midi_engine_connect(void) +{ +#ifdef USE_NETWORK + ip_midi_setup(); +#endif +#ifdef USE_OSS + oss_midi_setup(); +#endif +#ifdef USE_ALSA + alsa_midi_setup(); +#endif +#ifdef USE_WIN32MM + win32mm_midi_setup(); +#endif +#ifdef MACOSX + macosx_midi_setup(); +#endif +} + +void midi_engine_poll_ports(void) +{ + struct midi_provider *n; + + if (!midi_mutex) return; + + SDL_mutexP(midi_mutex); + for (n = port_providers; n; n = n->next) { + if (n->poll) n->poll(n); + } + SDL_mutexV(midi_mutex); +} + +int midi_engine_start(void) +{ + if (!_connected) { + midi_mutex = SDL_CreateMutex(); + if (!midi_mutex) return 0; + + midi_play_cond = SDL_CreateCond(); + if (!midi_play_cond) { + SDL_DestroyMutex(midi_mutex); + midi_mutex = midi_record_mutex = midi_play_mutex = midi_port_mutex = 0; + return 0; + } + + midi_record_mutex = SDL_CreateMutex(); + if (!midi_record_mutex) { + SDL_DestroyCond(midi_play_cond); + SDL_DestroyMutex(midi_mutex); + midi_mutex = midi_record_mutex = midi_play_mutex = midi_port_mutex = 0; + return 0; + } + + midi_play_mutex = SDL_CreateMutex(); + if (!midi_play_mutex) { + SDL_DestroyCond(midi_play_cond); + SDL_DestroyMutex(midi_record_mutex); + SDL_DestroyMutex(midi_mutex); + midi_mutex = midi_record_mutex = midi_play_mutex = midi_port_mutex = 0; + return 0; + } + + midi_port_mutex = SDL_CreateMutex(); + if (!midi_port_mutex) { + SDL_DestroyCond(midi_play_cond); + SDL_DestroyMutex(midi_play_mutex); + SDL_DestroyMutex(midi_record_mutex); + SDL_DestroyMutex(midi_mutex); + midi_mutex = midi_record_mutex = midi_play_mutex = midi_port_mutex = 0; + return 0; + } + _midi_engine_connect(); + _connected = 1; + } + return 1; +} +void midi_engine_reset(void) +{ + midi_engine_stop(); + _midi_engine_connect(); +} +void midi_engine_stop(void) +{ + struct midi_provider *n, *p; + struct midi_port *q; + + if (!_connected) return; + if (!midi_mutex) return; + + SDL_mutexP(midi_mutex); + for (n = port_providers; n;) { + p = n->next; + + q = 0; + while (midi_port_foreach(p, &q)) { + midi_port_unregister(q->num); + } + + if (n->thread) { + SDL_KillThread((SDL_Thread*)n->thread); + } + free((void*)n->name); + free(n); + n = p; + } + _connected = 0; + SDL_mutexV(midi_mutex); +} + + +/* PORT system */ +static struct midi_port **port_top = 0; +static int port_count = 0; +static int port_alloc = 0; + +struct midi_port *midi_engine_port(int n, const char **name) +{ + struct midi_port *pv = 0; + + if (!midi_port_mutex) return 0; + SDL_mutexP(midi_port_mutex); + if (n >= 0 && n < port_count) { + pv = port_top[n]; + if (name) *name = pv->name; + } + SDL_mutexV(midi_port_mutex); + return pv; +} + +int midi_engine_port_count(void) +{ + int pc; + if (!midi_port_mutex) return 0; + SDL_mutexP(midi_port_mutex); + pc = port_count; + SDL_mutexV(midi_port_mutex); + return pc; +} + +/* midi engines register a provider (one each!) */ +struct midi_provider *midi_provider_register(const char *name, + struct midi_driver *driver) +{ + struct midi_provider *n; + + if (!midi_mutex) return 0; + + n = mem_alloc(sizeof(struct midi_provider)); + n->name = (char *)strdup(name); + n->poll = driver->poll; + n->enable = driver->enable; + n->disable = driver->disable; + if (driver->flags & MIDI_PORT_CAN_SCHEDULE) { + n->send_later = driver->send; + n->send_now = NULL; + } else { + n->send_later = NULL; + n->send_now = driver->send; + } + + SDL_mutexP(midi_mutex); + n->next = port_providers; + port_providers = n; + + if (driver->thread) { + n->thread = (void*)SDL_CreateThread((int (*)(void*))driver->thread, (void*)n); + } else { + n->thread = (void*)0; + } + + SDL_mutexV(midi_mutex); + + return n; +} + +/* midi engines list ports this way */ +int midi_port_register(struct midi_provider *pv, int inout, const char *name, +void *userdata, int free_userdata) +{ + struct midi_port *p, **pt; + int i; + + if (!midi_port_mutex) return -1; + + p = mem_alloc(sizeof(struct midi_port)); + p->io = 0; + p->iocap = inout; + p->name = (char*)strdup(name); + p->enable = pv->enable; + p->disable = pv->disable; + p->send_later = pv->send_later; + p->send_now = pv->send_now; + + p->free_userdata = free_userdata; + p->userdata = userdata; + p->provider = pv; + + for (i = 0; i < port_alloc; i++) { + if (port_top[i] == 0) { + port_top[i] = p; + p->num = i; + port_count++; + return i; + } + } + + SDL_mutexP(midi_port_mutex); + port_alloc += 4; + pt = realloc(port_top, sizeof(struct midi_port *) * port_alloc); + if (!pt) { + free(p->name); + free(p); + SDL_mutexV(midi_port_mutex); + return -1; + } + pt[port_count] = p; + for (i = port_count+1; i < port_alloc; i++) pt[i] = 0; + port_top = pt; + p->num = port_count; + port_count++; + SDL_mutexV(midi_port_mutex); + return p->num; +} + +int midi_port_foreach(struct midi_provider *p, struct midi_port **cursor) +{ + int i; + if (!midi_port_mutex) return 0; + + SDL_mutexP(midi_port_mutex); +NEXT: if (!*cursor) { + i = 0; + } else { + i = ((*cursor)->num) + 1; + while (i < port_alloc && !port_top[i]) i++; + } + if (i >= port_alloc) { + *cursor = 0; + SDL_mutexV(midi_port_mutex); + return 0; + } + *cursor = port_top[i]; + if (p && (*cursor)->provider != p) goto NEXT; + SDL_mutexV(midi_port_mutex); + return 1; +} + +static void _midi_send_unlocked(unsigned char *data, unsigned int len, unsigned int delay, + int from) +{ + struct midi_port *ptr; +#if 0 + unsigned int i; +printf("MIDI: "); + for (i = 0; i < len; i++) { + printf("%02x ", data[i]); + } +puts(""); +fflush(stdout); +#endif + if (from == 0) { + /* from == 0 means from immediate; everyone plays */ + ptr = 0; + while (midi_port_foreach(NULL, &ptr)) { + if ((ptr->io & MIDI_OUTPUT)) { + if (ptr->send_now) ptr->send_now(ptr, data, len, 0); + else if (ptr->send_later) ptr->send_later(ptr, data, len, 0); + } + } + } else if (from == 1) { + /* from == 1 means from buffer-flush; only "now" plays */ + ptr = 0; + while (midi_port_foreach(NULL, &ptr)) { + if ((ptr->io & MIDI_OUTPUT)) { + if (ptr->send_now) ptr->send_now(ptr, data, len, 0); + } + } + } else { + /* from == 2 means from buffer-write; only "later" plays */ + ptr = 0; + while (midi_port_foreach(NULL, &ptr)) { + if ((ptr->io & MIDI_OUTPUT)) { + if (ptr->send_later) ptr->send_later(ptr, data, len, delay); + } + } + } +} +void midi_send_now(unsigned char *seq, unsigned int len) +{ + if (!midi_record_mutex) return; + + SDL_mutexP(midi_record_mutex); + _midi_send_unlocked(seq, len, 0, 0); + SDL_mutexV(midi_record_mutex); +} + +/*----------------------------------------------------------------------------------*/ + +struct midi_pl { + unsigned int pos; + unsigned char buffer[4096]; + unsigned int len; + struct midi_pl *next; + /* used by timer */ + unsigned long rpos; +}; + +static struct midi_pl *top = 0; +static struct midi_pl *top_free = 0; +static SDL_Thread *midi_sync_thread = 0; + +static struct midi_pl *ready = 0; + +static int __out_detatched(void *xtop) +{ + struct midi_pl *x, *y; + +#ifdef WIN32 + __win32_pick_usleep(); + SetPriorityClass(GetCurrentProcess(),HIGH_PRIORITY_CLASS); + SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL); + /*SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST);*/ +#endif + + SDL_mutexP(midi_play_mutex); +STARTFRAME: + do { + SDL_CondWait(midi_play_cond, midi_play_mutex); + x = y = (struct midi_pl *)ready; + } while (!x); + ready = 0; /* sanity check */ +NEXTPACKET: + SDL_mutexP(midi_record_mutex); + _midi_send_unlocked(x->buffer, x->len, 0, 1); + SDL_mutexV(midi_record_mutex); + + if (!x->next) { + /* remove them all */ + SDL_mutexP(midi_record_mutex); + x->next = top_free; + top_free = y; + SDL_mutexV(midi_record_mutex); + goto STARTFRAME; + } + x = x->next; + if (x->rpos) SLEEP_FUNC(x->rpos); + goto NEXTPACKET; + /* there is no corrosponding mutexV here! */ +} + +void midi_send_flush(void) +{ + struct midi_pl *x, *y; + unsigned int acc; + + if (!midi_record_mutex || !midi_play_mutex) return; + + while (!midi_sync_thread && top) { + midi_sync_thread = SDL_CreateThread(__out_detatched, top); + if (midi_sync_thread) break; + log_appendf(2, "AAACK: Couldn't start off MIDI, sending ahead"); + + SDL_mutexP(midi_record_mutex); + _midi_send_unlocked(top->buffer, top->len, 0, 1); + x = top->next; + top->next = top_free; + top_free = top; + top = x; + SDL_mutexV(midi_record_mutex); + } + if (!top) return; + + SDL_mutexP(midi_record_mutex); + /* calculate relative */ + acc = 0; + for (x = top; x; x = x->next) { + x->rpos = x->pos - acc; + acc = x->pos; + } + + x = top; + top = 0; + SDL_mutexV(midi_record_mutex); + + if (x) { + SDL_mutexP(midi_play_mutex); + ready = x; + SDL_CondSignal(midi_play_cond); + SDL_mutexV(midi_play_mutex); + } +} +static void __add_pl(struct midi_pl *x, unsigned char *data, unsigned int len) +{ + struct midi_pl *q; + int dr; + +#if 0 +printf("adding %u bytes\n",len); +#endif +RECURSE: + if (len + x->len < sizeof(x->buffer)) { + memcpy(x->buffer + x->len, data, len); + x->len += len; + return; + } + + memcpy(x->buffer + x->len, data, dr = (sizeof(x->buffer) - x->len)); + len -= dr; + + if (len > 0) { + /* overflow */ + if (top_free) { + q = top_free; + top_free = top_free->next; + } else { + q = mem_alloc(sizeof(struct midi_pl)); + } + q->pos = x->pos; + q->len = 0; + q->next = x->next; + x->next = q; + x = q; + goto RECURSE; + } +} + +void midi_send_buffer(unsigned char *data, unsigned int len, unsigned int pos) +{ + struct midi_pl *x, *lx, *y; + + pos /= song_buffer_msec(); + + if (!midi_record_mutex) return; + + SDL_mutexP(midi_record_mutex); + + /* pos is still in miliseconds */ + _midi_send_unlocked(data, len, pos, 2); + + pos *= 1000; /* microseconds for usleep */ + + lx = 0; + x = top; + while (x) { + if (x->pos == pos) { + __add_pl(x, data, len); + goto TAIL; + } + if (x->pos > pos) break; /* but after lx */ + lx = x; + x = x->next; + } + if (top_free) { + y = top_free; + top_free = top_free->next; + } else { + y = mem_alloc(sizeof(struct midi_pl)); + } + y->next = x; + if (lx) { + lx->next = y; + } else { + /* then x = top */ + top = y; + } + y->pos = pos; + y->len = 0; + __add_pl(y, data, len); +TAIL: SDL_mutexV(midi_record_mutex); +} + +/*----------------------------------------------------------------------------------*/ +void midi_port_unregister(int num) +{ + struct midi_port *q; + int i; + + if (!midi_port_mutex) return; + + SDL_mutexP(midi_port_mutex); + for (i = 0; i < port_alloc; i++) { + if (port_top[i] && port_top[i]->num == num) { + q = port_top[i]; + if (q->disable) q->disable(q); + if (q->free_userdata) free(q->userdata); + free(q); + + port_top[i] = 0; + port_count--; + break; + } + } + SDL_mutexV(midi_port_mutex); +} + +void midi_received_cb(struct midi_port *src, unsigned char *data, unsigned int len) +{ + unsigned char d4[4]; + int cmd; + + if (!len) return; + if (len < 4) { + memset(d4,0,sizeof(d4)); + memcpy(d4,data,len); + data = d4; + } + + /* just for fun... */ + SDL_mutexP(midi_record_mutex); + status.last_midi_real_len = len; + if (len > sizeof(status.last_midi_event)) { + status.last_midi_len = sizeof(status.last_midi_event); + } else { + status.last_midi_len = len; + } + memcpy(status.last_midi_event, data, status.last_midi_len); + status.flags |= MIDI_EVENT_CHANGED; + status.last_midi_port = src; + time(&status.last_midi_time); + SDL_mutexV(midi_record_mutex); + + /* pass through midi events when on midi page */ + if (status.current_page == PAGE_MIDI) { + midi_send_now(data,len); + status.flags |= NEED_UPDATE; + } + + cmd = ((*data) & 0xF0) >> 4; + if (cmd == 0x8 || (cmd == 0x9 && data[2] == 0)) { + midi_event_note(MIDI_NOTEOFF, data[0] & 15, data[1], 0); + } else if (cmd == 0x9) { + midi_event_note(MIDI_NOTEON, data[0] & 15, data[1], data[2]); + } else if (cmd == 0xA) { + midi_event_note(MIDI_KEYPRESS, data[0] & 15, data[1], data[2]); + } else if (cmd == 0xB) { + midi_event_controller(data[0] & 15, data[1], data[2]); + } else if (cmd == 0xC) { + midi_event_program(data[0] & 15, data[1]); + } else if (cmd == 0xD) { + midi_event_aftertouch(data[0] & 15, data[1]); + } else if (cmd == 0xE) { + midi_event_pitchbend(data[0] & 15, data[1]); + } else if (cmd == 0xF) { + switch ((*data & 15)) { + case 0: /* sysex */ + if (len <= 2) return; + midi_event_sysex(data+1, len-2); + break; + case 6: /* tick */ + midi_event_tick(); + break; + } + } +} + +void midi_event_note(enum midi_note status, int channel, int note, int velocity) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = status; + st[1] = channel; + st[2] = note; + st[3] = velocity; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_NOTE; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_controller(int channel, int param, int value) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = value; + st[1] = channel; + st[2] = param; + st[3] = 0; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_CONTROLLER; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_program(int channel, int value) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = value; + st[1] = channel; + st[2] = 0; + st[3] = 0; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_PROGRAM; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_aftertouch(int channel, int value) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = value; + st[1] = channel; + st[2] = 0; + st[3] = 0; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_AFTERTOUCH; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_pitchbend(int channel, int value) +{ + int *st; + SDL_Event e; + + st = mem_alloc(sizeof(int)*4); + st[0] = value; + st[1] = channel; + st[2] = 0; + st[3] = 0; + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_PITCHBEND; + e.user.data1 = st; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_tick(void) +{ + SDL_Event e; + + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_TICK; + e.user.data1 = 0; + e.user.data2 = 0; + SDL_PushEvent(&e); +} +void midi_event_sysex(const unsigned char *data, unsigned int len) +{ + SDL_Event e; + + e.user.type = SCHISM_EVENT_MIDI; + e.user.code = SCHISM_EVENT_MIDI_SYSEX; + e.user.data1 = mem_alloc(len+sizeof(len)); + memcpy(e.user.data1, &len, sizeof(len)); + memcpy(e.user.data1+sizeof(len), data, len); + e.user.data2 = 0; + SDL_PushEvent(&e); +} + +int midi_engine_handle_event(void *ev) +{ + struct key_event kk; + int *st; + SDL_Event *e = (SDL_Event *)ev; + if (e->type != SCHISM_EVENT_MIDI) return 0; + + if (midi_flags & MIDI_DISABLE_RECORD) return 1; + + memset(&kk, 0, sizeof(kk)); + + st = e->user.data1; + switch (e->user.code) { + case SCHISM_EVENT_MIDI_NOTE: + if (st[0] == MIDI_NOTEON) { + kk.state = 0; + } else { + if (!(midi_flags & MIDI_RECORD_NOTEOFF)) { + /* don't record noteoff? okay... */ + break; + } + kk.state = 1; + } + kk.midi_channel = st[1]+1; + kk.midi_note = (st[2]+1 + midi_c5note) - 60; + if (midi_flags & MIDI_RECORD_VELOCITY) + kk.midi_volume = st[3]; + else + kk.midi_volume = 128; + kk.midi_volume = (kk.midi_volume * midi_amplification) / 100; + handle_key(&kk); + break; + case SCHISM_EVENT_MIDI_PITCHBEND: + /* wheel */ + kk.midi_channel = st[1]+1; + kk.midi_volume = -1; + kk.midi_note = -1; + kk.midi_bend = st[0]; + handle_key(&kk); + break; + default: + break; + } + free(st); + +/* blah... */ + + return 1; +} diff --git a/schism/midi-ip.c b/schism/midi-ip.c new file mode 100644 index 000000000..536da8607 --- /dev/null +++ b/schism/midi-ip.c @@ -0,0 +1,296 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" +#include "midi.h" + +#include "util.h" + +#ifdef USE_NETWORK + +#ifdef WIN32 +#include +#include +#else +#include +#include +#include +#include +#endif + +#include + +#define DEFAULT_IP_PORT_COUNT 5 +#define MIDI_IP_BASE 21928 +#define MAX_DGRAM_SIZE 1280 + +static int real_num_ports = 0; +static int num_ports = 0; +static int out_fd = -1; +static int *port_fd = 0; +static int *state = 0; + + + +static int _get_fd(int pb, int isout) +{ + struct ip_mreq mreq; + struct sockaddr_in sin; + unsigned char *ipcopy; + int fd, opt; +#if !defined(PF_INET) && defined(AF_INET) +#define PF_INET AF_INET +#endif + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd == -1) return -1; + + opt = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); + + /* don't loop back what we generate */ + opt = !isout; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &opt, sizeof(opt)) < 0) { + (void)close(fd); + return -1; + } + + opt = 31; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &opt, sizeof(opt)) < 0) { + (void)close(fd); + return -1; + } + + memset(&mreq, 0, sizeof(mreq)); + ipcopy = (unsigned char *)&mreq.imr_multiaddr; + ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; + if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { + (void)close(fd); + return -1; + } + + opt = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)) < 0) { + (void)close(fd); + return -1; + } + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + ipcopy = (unsigned char *)&sin.sin_addr; + if (!isout) { + /* all 0s is inaddr_any; but this is for listening */ + ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; + sin.sin_port = htons(MIDI_IP_BASE+pb); + } + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + (void)close(fd); + return -1; + } + + return fd; +} +int ip_midi_setports(int n) +{ + int *tmp, i; + + if (n > -1) { + if (n > num_ports) { + tmp = (int *)realloc(port_fd, n * sizeof(int)); + if (!tmp) return real_num_ports; + port_fd = tmp; /* err... */ + + tmp = (int *)realloc(state, n * sizeof(int)); + if (!tmp) return real_num_ports; + state = tmp; + + for (i = num_ports; i < n; i++) { + state[i] = 0; + if ((port_fd[i] = _get_fd(i,0)) == -1) { + while (i >= num_ports) { + (void)close(tmp[i]); + i--; + } + return real_num_ports; + } + } + real_num_ports = num_ports = n; + + return n; + } else if (n < num_ports) { + for (i = n; i < real_num_ports; i++) { + (void)close(port_fd[i]); + port_fd[i] = -1; + } + real_num_ports = n; + return n; + } + } + return real_num_ports; +} + +static void _readin(struct midi_provider *p, int en, int fd) +{ + struct midi_port *ptr, *src; + static unsigned char buffer[65536]; + static struct sockaddr_in sin; + unsigned slen = sizeof(sin); + int r; + + r = recvfrom(fd, buffer, sizeof(buffer), 0, + (struct sockaddr *)&sin, &slen); + if (r > 0) { + ptr = src = 0; + while (midi_port_foreach(p, &ptr)) { + int n = ((int*)ptr->userdata) - port_fd; + if (n == en) src = ptr; + } + midi_received_cb(src, buffer, r); + } +} +static int _ip_thread(struct midi_provider *p) +{ + fd_set rfds; + int i, m; + + for (;;) { + FD_ZERO(&rfds); + m = 0; + for (i = 0; i < real_num_ports; i++) { + FD_SET(port_fd[i], &rfds); + if (port_fd[i] > m) m = port_fd[i]; + } + do { + i = select(m+1, &rfds, 0, 0, 0); + } while (i == -1 && errno == EINTR); + + for (i = 0; i < real_num_ports; i++) { + if (FD_ISSET(port_fd[i], &rfds)) { + if (state[i] & 1) _readin(p, i, port_fd[i]); + } + } + } + return 0; +} + +static int _ip_start(struct midi_port *p) +{ + int n = ((int*)p->userdata) - port_fd; + if (p->io & MIDI_INPUT) + state[n] |= 1; + if (p->io & MIDI_OUTPUT) + state[n] |= 2; + return 1; +} +static int _ip_stop(struct midi_port *p) +{ + int n = ((int*)p->userdata) - port_fd; + if (p->io & MIDI_INPUT) + state[n] &= (~1); + if (p->io & MIDI_OUTPUT) + state[n] &= (~2); + return 1; +} + +static void _ip_send(struct midi_port *p, unsigned char *data, unsigned int len, + UNUSED unsigned int delay) +{ + struct sockaddr_in sin; + unsigned char *ipcopy; + int n = ((int*)p->userdata) - port_fd; + int ss; + + if (len == 0) return; + if (!(state[n] & 2)) return; /* blah... */ + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + ipcopy = (unsigned char *)&sin.sin_addr.s_addr; + ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; + sin.sin_port = htons(MIDI_IP_BASE+n); + + while (len) { + ss = (len > MAX_DGRAM_SIZE) ? MAX_DGRAM_SIZE : len; + if (sendto(out_fd, data, ss, 0, + (struct sockaddr *)&sin,sizeof(sin)) < 0) { + state[n] &= (~2); /* turn off output */ + } + len -= ss; + data += ss; + } +} +static void _ip_poll(struct midi_provider *p) +{ + static int last_buildout = 0; + struct midi_port *ptr; + char *buffer; + int i; + + if (real_num_ports < last_buildout) { + ptr = 0; + while (midi_port_foreach(p, &ptr)) { + i = ((int*)ptr->userdata) - port_fd; + if (i >= real_num_ports) midi_port_unregister(ptr->num); + } + } else { + for (i = last_buildout; i < real_num_ports; i++) { + buffer = 0; + asprintf(&buffer, " Multicast/IP MIDI %u", i+1); + midi_port_register(p, MIDI_INPUT | MIDI_OUTPUT, buffer, + &port_fd[i], 0); + } + } + last_buildout = i; +} + +int ip_midi_setup(void) +{ + struct midi_driver driver; +#ifdef WIN32 + WSADATA ignored; + if (WSAStartup(0x202, &ignored) == SOCKET_ERROR) { + WSACleanup(); /* ? */ + return 0; + } +#endif + if (out_fd == -1) { + out_fd = _get_fd(-1, 1); + } + + if (ip_midi_setports(DEFAULT_IP_PORT_COUNT) != DEFAULT_IP_PORT_COUNT) return 0; + + driver.flags = 0; + driver.poll = _ip_poll; + driver.thread = _ip_thread; + driver.send = _ip_send; + driver.enable = _ip_start; + driver.disable = _ip_stop; + + if (!midi_provider_register("IP", &driver)) return 0; + return 1; +} + +#else + +int ip_midi_setports(int n) +{ + return 0; +} + +#endif + +int ip_midi_getports(void) { return ip_midi_setports(-1); } diff --git a/schism/mixer-core.c b/schism/mixer-core.c new file mode 100644 index 000000000..3425f80be --- /dev/null +++ b/schism/mixer-core.c @@ -0,0 +1,91 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" + +#include "mixer.h" +#include "util.h" + +static int (*__mixer_get_max_volume)(void) = 0; +static void (*__mixer_read_volume)(int *left, int *right) = 0; +static void (*__mixer_write_volume)(int left, int right) = 0; + +#ifdef USE_ALSA +extern int alsa_mixer_get_max_volume(void); +extern void alsa_mixer_read_volume(int *, int *); +extern void alsa_mixer_write_volume(int, int); +#endif + +#ifdef USE_OSS +extern int oss_mixer_get_max_volume(void); +extern void oss_mixer_read_volume(int *, int *); +extern void oss_mixer_write_volume(int, int); +#endif + +#ifdef USE_WIN32MM +extern int win32mm_mixer_get_max_volume(void); +extern void win32mm_mixer_read_volume(int *, int *); +extern void win32mm_mixer_write_volume(int, int); +#endif + +void mixer_setup(void) +{ + char *drv, drv_buf[256]; + + drv = SDL_AudioDriverName(drv_buf,sizeof(drv_buf)); + +#ifdef USE_ALSA + if ((!drv && !__mixer_get_max_volume) || (drv && !strcmp(drv, "alsa"))) { + __mixer_get_max_volume = alsa_mixer_get_max_volume; + __mixer_read_volume = alsa_mixer_read_volume; + __mixer_write_volume = alsa_mixer_write_volume; + } +#endif +#ifdef USE_OSS + if ((!drv && !__mixer_get_max_volume) || (drv && !strcmp(drv, "oss")) + || (drv && !strcmp(drv, "dsp"))) { + __mixer_get_max_volume = oss_mixer_get_max_volume; + __mixer_read_volume = oss_mixer_read_volume; + __mixer_write_volume = oss_mixer_write_volume; + } +#endif +#ifdef USE_WIN32MM + if ((!drv && !__mixer_get_max_volume) || (drv && (!strcmp(drv, "waveout") || !strcmp(drv, "dsound")) )) { + __mixer_get_max_volume = win32mm_mixer_get_max_volume; + __mixer_read_volume = win32mm_mixer_read_volume; + __mixer_write_volume = win32mm_mixer_write_volume; + } +#endif +} + + +int mixer_get_max_volume(void) +{ + if (__mixer_get_max_volume) return __mixer_get_max_volume(); + return 0; +} +void mixer_read_volume(int *left, int *right) +{ + if (__mixer_read_volume) __mixer_read_volume(left,right); + else { *left=0; *right=0; } +} +void mixer_write_volume(int left, int right) +{ + if (__mixer_write_volume) __mixer_write_volume(left,right); +} diff --git a/schism/mplink.cc b/schism/mplink.cc new file mode 100644 index 000000000..8e6d03dae --- /dev/null +++ b/schism/mplink.cc @@ -0,0 +1,831 @@ +// Schism Tracker - a cross-platform Impulse Tracker clone +// copyright (c) 2003-2005 chisel +// URL: http://rigelseven.com/schism/ +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "headers.h" + +#include "mplink.h" +#include "slurp.h" + +#include +#include +#include + +// ------------------------------------------------------------------------ +// variables + +CSoundFile *mp = NULL; + +// ------------------------------------------------------------------------ +// song information + +char *song_get_title() +{ + return mp->m_szNames[0]; +} + +char *song_get_message() +{ + return mp->m_lpszSongComments; +} + +// song midi config +midi_config *song_get_midi_config(void) { + return (midi_config *)&mp->m_MidiCfg; +} + +// returned value is in seconds +unsigned long song_get_length() +{ + return mp->GetSongTime(); +} +unsigned long song_get_length_to(int order, int row) +{ + unsigned long t; + + song_lock_audio(); + mp->stop_at_order = order; + mp->stop_at_row = row; + t = mp->GetSongTime(); + mp->stop_at_order = mp->stop_at_row = -1; + song_unlock_audio(); + return t; +} +void song_get_at_time(unsigned long seconds, int *order, int *row) +{ + if (!seconds) { + if (order) *order = 0; + if (row) *row = 0; + } else { + song_lock_audio(); + mp->stop_at_order = MAX_ORDERS; + mp->stop_at_row = 255; /* unpossible */ + mp->stop_at_time = seconds; + (void)mp->GetSongTime(); + if (order) *order = mp->stop_at_order; + if (row) *row = mp->stop_at_row; + mp->stop_at_order = mp->stop_at_row = -1; + mp->stop_at_time = 0; + song_unlock_audio(); + } +} + +// ------------------------------------------------------------------------ +// Memory allocation wrappers. Stupid 'new' operator. + +signed char *song_sample_allocate(int bytes) +{ + return CSoundFile::AllocateSample(bytes); +} + +void song_sample_free(signed char *data) +{ + CSoundFile::FreeSample(data); +} + +// ------------------------------------------------------------------------ + +song_sample *song_get_sample(int n, char **name_ptr) +{ + if (n >= MAX_SAMPLES) + return NULL; + if (name_ptr) + *name_ptr = mp->m_szNames[n]; + return (song_sample *) mp->Ins + n; +} + +static void _init_envelope(INSTRUMENTENVELOPE *e, int n) +{ + e->Ticks[0] = 0; + e->Values[0] = n; + e->Ticks[1] = 100; + e->Values[1] = n; + e->nNodes = 2; +} + +song_instrument *song_get_instrument(int n, char **name_ptr) +{ + if (n >= MAX_INSTRUMENTS) + return NULL; + + // Make a new instrument if it doesn't exist. + if (!mp->Headers[n]) { + mp->Headers[n] = new INSTRUMENTHEADER; + memset(mp->Headers[n], 0, sizeof(INSTRUMENTHEADER)); + mp->Headers[n]->nGlobalVol = 64; + mp->Headers[n]->nPan = 128; + + _init_envelope(&mp->Headers[n]->VolEnv, 64); + _init_envelope(&mp->Headers[n]->PanEnv, 32); + _init_envelope(&mp->Headers[n]->PitchEnv, 32); + } + + if (name_ptr) + *name_ptr = (char *) mp->Headers[n]->name; + return (song_instrument *) mp->Headers[n]; +} + +int song_get_instrument_default_volume(UNUSED int ins, UNUSED int sam) +{ + if (song_is_instrument_mode()) return 64; + if (sam == -1) return 64; +// todo (but not really all that soon) + return 64; +} + +// this is a fairly gross way to do what should be such a simple thing +int song_get_instrument_number(song_instrument *inst) +{ + if (inst) + for (int n = 1; n < MAX_INSTRUMENTS; n++) + if (inst == ((song_instrument *) mp->Headers[n])) + return n; + return 0; +} + +song_channel *song_get_channel(int n) +{ + if (n >= MAX_BASECHANNELS) + return NULL; + return (song_channel *) mp->ChnSettings + n; +} + +song_mix_channel *song_get_mix_channel(int n) +{ + if (n >= MAX_CHANNELS) + return NULL; + return (song_mix_channel *) mp->Chn + n; +} + +int song_get_mix_state(unsigned long **channel_list) +{ + if (channel_list) + *channel_list = mp->ChnMix; + return MIN(mp->m_nMixChannels, mp->m_nMaxMixChannels); +} + +// ------------------------------------------------------------------------ +// For all of these, channel is ZERO BASED. +// (whereas in the pattern editor etc. it's one based) + +static int solo_channel = -1; +static int channel_states[64]; /* nonzero => muted */ + +void song_set_channel_mute(int channel, int muted) +{ + int i; + if (muted) { + mp->ChnSettings[channel].dwFlags |= CHN_MUTE; + mp->Chn[channel].dwFlags |= CHN_MUTE; + for (i = 0; i < MAX_CHANNELS; i++) { + if (mp->Chn[i].nMasterChn == (channel+1) && i != (channel+1)) { + mp->Chn[i].dwFlags |= (CHN_NNAMUTE|CHN_MUTE); + } + } + } else { + mp->ChnSettings[channel].dwFlags &= ~CHN_MUTE; + mp->Chn[channel].dwFlags &= ~CHN_MUTE; + for (i = 0; i < MAX_CHANNELS; i++) { + if (mp->Chn[i].nMasterChn == (channel+1) && i != (channel+1)) { + mp->Chn[i].dwFlags &= ~(CHN_NNAMUTE|CHN_MUTE); + } + } + } +} + +void song_toggle_channel_mute(int channel) +{ + // i'm just going by the playing channel's state... + // if the actual channel is muted but not the playing one, + // tough luck :) + song_set_channel_mute(channel, (mp->Chn[channel].dwFlags & CHN_MUTE) == 0); +} + +void song_handle_channel_solo(int channel) +{ + int n = 64; + + if (solo_channel >= 0) { + if (channel == solo_channel) { + // undo the solo + while (n-- > 0) + song_set_channel_mute(n, channel_states[n]); + // always unmute current channel + song_set_channel_mute(channel, 0); + solo_channel = -1; + } else { + // change the solo channel + // mute all channels... + while (n-- > 0) + song_set_channel_mute(n, 1); + // then unmute the current channel + song_set_channel_mute(channel, 0); + solo_channel = channel; + } + } else { + // set the solo channel: + // save each channel's state, then mute it... + while (n-- > 0) { + channel_states[n] = song_get_channel(n)->flags & CHN_MUTE; + song_set_channel_mute(n, 1); + } + // ... and then, unmute the current channel + song_set_channel_mute(channel, 0); + solo_channel = channel; + } +} + +void song_clear_solo_channel() +{ + solo_channel = -1; +} + +// returned channel number is ONE-based +// (to make it easier to work with in the pattern editor and info page) +int song_find_last_channel() +{ + int n = 64; + + if (solo_channel > 0) { + while (channel_states[--n]) + if (n == 0) + return 64; + } else { + while (song_get_channel(--n)->flags & CHN_MUTE) + if (n == 0) + return 64; + } + return n + 1; +} + +// ------------------------------------------------------------------------ + +// returns length of the pattern, or 0 on error. (this can be used to +// get a pattern's length by passing NULL for buf.) +int song_get_pattern(int n, song_note ** buf) +{ + if (n >= MAX_PATTERNS) + return 0; + + if (buf) { + if (!mp->Patterns[n]) { + mp->PatternSize[n] = 64; + mp->PatternAllocSize[n] = 64; + mp->Patterns[n] = CSoundFile::AllocatePattern + (mp->PatternSize[n], 64); + } + *buf = (song_note *) mp->Patterns[n]; + } else { + if (!mp->Patterns[n]) + return 64; + } + return mp->PatternSize[n]; +} +song_note *song_pattern_allocate(int rows) +{ + return (song_note *)CSoundFile::AllocatePattern(rows,64); +} +song_note *song_pattern_allocate_copy(int patno, int *rows) +{ + int len = mp->PatternSize[patno]; + MODCOMMAND *newdata = CSoundFile::AllocatePattern(len,64); + MODCOMMAND *olddata = mp->Patterns[patno]; + memcpy(newdata, olddata, len*sizeof(MODCOMMAND)*64); + if (rows) *rows=len; + return (song_note*)newdata; +} +void song_pattern_deallocate(song_note *n) +{ + CSoundFile::FreePattern((MODCOMMAND*)n); +} +void song_pattern_install(int patno, song_note *n, int rows) +{ + song_lock_audio(); + + MODCOMMAND *olddata = mp->Patterns[patno]; + CSoundFile::FreePattern(olddata); + + mp->Patterns[patno] = (MODCOMMAND*)n; + mp->PatternAllocSize[patno] = rows; + + song_unlock_audio(); +} + + +unsigned char *song_get_orderlist() +{ + return mp->Order; +} + +// ------------------------------------------------------------------------ + +int song_order_for_pattern(int pat, int locked) +{ + int i; + if (locked == -1) { + if (mp->m_dwSongFlags & SONG_ORDERLOCKED) + locked = mp->m_nLockedPattern; + else + locked = mp->GetCurrentOrder(); + } else if (locked == -2) { + locked = mp->GetCurrentOrder(); + } + + if (locked < 0) locked = 0; + if (locked > 255) locked = 255; + + for (i = locked; i < 255; i++) { + if (mp->Order[i] == pat) { + return i; + } + } + for (i = 0; i < locked; i++) { + if (mp->Order[i] == pat) { + return i; + } + } + return -1; +} + +int song_get_num_orders() +{ + // for some reason, modplug calls it patterns, not orders... *shrug* + int n = mp->GetNumPatterns(); + return n ? n - 1 : n; +} + +static song_note blank_pattern[64 * 64]; +int song_pattern_is_empty(int n) +{ + if (!mp->Patterns[n]) + return true; + if (mp->PatternSize[n] != 64) + return false; + return !memcmp(mp->Patterns[n], blank_pattern, sizeof(blank_pattern)); +} + +int song_get_num_patterns() +{ + int n; + for (n = 199; n && song_pattern_is_empty(n); n--) + /* do nothing */ ; + return n; +} + +int song_get_rows_in_pattern(int pattern) +{ + if (pattern > MAX_PATTERNS) + return 0; + return (mp->PatternSize[pattern] ? : 64) - 1; +} + +// ------------------------------------------------------------------------ + +/* +mp->PatternSize + The size of the pattern, of course. +mp->PatternAllocSize + Not used anywhere (yet). I'm planning on keeping track of space off the end of a pattern when it's + shrunk, so that making it longer again will restore it. (i.e., handle resizing the same way IT does) + I'll add this stuff in later; I have three handwritten pages detailing how to implement it. ;) +get_current_pattern() = in pattern editor +song_get_playing_pattern() = current pattern being played +*/ +void song_pattern_resize(int pattern, int newsize) +{ + song_lock_audio(); + int oldsize = mp->PatternAllocSize[pattern]; + status.flags |= SONG_NEEDS_SAVE; + if (oldsize < newsize) { + MODCOMMAND *olddata = mp->Patterns[pattern]; + MODCOMMAND *newdata = CSoundFile::AllocatePattern(newsize, 64); + if (olddata) { + memcpy(newdata, olddata, 64 * sizeof(MODCOMMAND) * MIN(newsize, oldsize)); + CSoundFile::FreePattern(olddata); + } + mp->Patterns[pattern] = newdata; + mp->PatternAllocSize[pattern] = MAX(newsize,oldsize); + } + mp->PatternSize[pattern] = newsize; + song_unlock_audio(); +} + +// ------------------------------------------------------------------------ + +int song_get_initial_speed() +{ + return mp->m_nDefaultSpeed; +} + +void song_set_initial_speed(int new_speed) +{ + mp->m_nDefaultSpeed = CLAMP(new_speed, 1, 255); +} + +int song_get_initial_tempo() +{ + return mp->m_nDefaultTempo; +} + +void song_set_initial_tempo(int new_tempo) +{ + mp->m_nDefaultTempo = CLAMP(new_tempo, 31, 255); +} + +int song_get_initial_global_volume() +{ + return mp->m_nDefaultGlobalVolume / 2; +} + +void song_set_initial_global_volume(int new_vol) +{ + mp->m_nDefaultGlobalVolume = CLAMP(new_vol, 0, 128) * 2; +} + +int song_get_mixing_volume() +{ + return mp->m_nSongPreAmp; +} + +void song_set_mixing_volume(int new_vol) +{ + mp->m_nSongPreAmp = CLAMP(new_vol, 0, 128); +} + +int song_get_separation() +{ + return mp->m_nStereoSeparation; +} + +void song_set_separation(int new_sep) +{ + mp->m_nStereoSeparation = CLAMP(new_sep, 0, 128); +} + +int song_is_stereo() +{ + if (mp->m_dwSongFlags & SONG_NOSTEREO) return 0; + return 1; +} +void song_toggle_stereo() +{ + mp->m_dwSongFlags ^= SONG_NOSTEREO; +} +void song_toggle_mono() +{ + mp->m_dwSongFlags ^= SONG_NOSTEREO; +} +void song_set_mono() +{ + mp->m_dwSongFlags |= SONG_NOSTEREO; +} +void song_set_stereo() +{ + mp->m_dwSongFlags &= ~SONG_NOSTEREO; +} + +int song_has_old_effects() +{ + return !!(mp->m_dwSongFlags & SONG_ITOLDEFFECTS); +} + +void song_set_old_effects(int value) +{ + if (value) + mp->m_dwSongFlags |= SONG_ITOLDEFFECTS; + else + mp->m_dwSongFlags &= ~SONG_ITOLDEFFECTS; +} + +int song_has_compatible_gxx() +{ + return !!(mp->m_dwSongFlags & SONG_ITCOMPATMODE); +} + +void song_set_compatible_gxx(int value) +{ + if (value) + mp->m_dwSongFlags |= SONG_ITCOMPATMODE; + else + mp->m_dwSongFlags &= ~SONG_ITCOMPATMODE; +} + +int song_has_linear_pitch_slides() +{ + return !!(mp->m_dwSongFlags & SONG_LINEARSLIDES); +} + +void song_set_linear_pitch_slides(int value) +{ + if (value) + mp->m_dwSongFlags |= SONG_LINEARSLIDES; + else + mp->m_dwSongFlags &= ~SONG_LINEARSLIDES; +} + +int song_is_instrument_mode() +{ + return !!(mp->m_dwSongFlags & SONG_INSTRUMENTMODE); +} + +void song_set_instrument_mode(int value) +{ + int oldvalue = song_is_instrument_mode(); + + if (value && !oldvalue) { + mp->m_dwSongFlags |= SONG_INSTRUMENTMODE; + } else if (!value && oldvalue) { + mp->m_dwSongFlags &= ~SONG_INSTRUMENTMODE; + } +} + +char *song_get_instrument_name(int n, char **name) +{ + if (song_is_instrument_mode()) + song_get_instrument(n, name); + else + song_get_sample(n, name); + return *name; +} + +int song_get_current_instrument() +{ + return (song_is_instrument_mode() ? instrument_get_current() : sample_get_current()); +} + +// ------------------------------------------------------------------------ + +unsigned int song_sample_get_c5speed(int n) +{ + song_sample *smp; + smp = song_get_sample(n, 0); + if (!smp) return 8363; + return smp->speed; +} + +void song_sample_set_c5speed(int n, unsigned int spd) +{ + song_sample *smp; + smp = song_get_sample(n,0 ); + if (smp) smp->speed = spd; +} + +void song_exchange_samples(int a, int b) +{ + if (a == b) + return; + + song_lock_audio(); + song_sample tmp; + memcpy(&tmp, mp->Ins + a, sizeof(song_sample)); + memcpy(mp->Ins + a, mp->Ins + b, sizeof(song_sample)); + memcpy(mp->Ins + b, &tmp, sizeof(song_sample)); + + char text[32]; + memcpy(text, mp->m_szNames[a], sizeof(text)); + memcpy(mp->m_szNames[a], mp->m_szNames[b], sizeof(text)); + status.flags |= SONG_NEEDS_SAVE; + memcpy(mp->m_szNames[b], text, sizeof(text)); + song_unlock_audio(); +} + +void song_exchange_instruments(int a, int b) +{ + if (a == b) + return; + + INSTRUMENTHEADER *tmp; + + song_lock_audio(); + tmp = mp->Headers[a]; + mp->Headers[a] = mp->Headers[b]; + mp->Headers[b] = tmp; + status.flags |= SONG_NEEDS_SAVE; + song_unlock_audio(); +} + +// instrument, sample, whatever. +static void _swap_instruments_in_patterns(int a, int b) +{ + for (int pat = 0; pat < MAX_PATTERNS; pat++) { + MODCOMMAND *note = mp->Patterns[pat]; + if (note == NULL) + continue; + for (int n = 0; n < 64 * mp->PatternSize[pat]; n++, note++) { + if (note->instr == a) + note->instr = b; + else if (note->instr == b) + note->instr = a; + } + } +} + +void song_swap_samples(int a, int b) +{ + if (a == b) + return; + + song_lock_audio(); + if (song_is_instrument_mode()) { + // ... or should this be done even in sample mode? + for (int n = 1; n < MAX_INSTRUMENTS; n++) { + INSTRUMENTHEADER *ins = mp->Headers[n]; + + if (ins == NULL) + continue; + // sizeof(ins->Keyboard)... + for (int s = 0; s < 128; s++) { + if (ins->Keyboard[s] == a) + ins->Keyboard[s] = b; + else if (ins->Keyboard[s] == b) + ins->Keyboard[s] = a; + } + } + } else { + _swap_instruments_in_patterns(a, b); + } + song_unlock_audio(); + song_exchange_samples(a, b); +} + +void song_swap_instruments(int a, int b) +{ + if (a == b) + return; + + if (song_is_instrument_mode()) { + song_lock_audio(); + _swap_instruments_in_patterns(a, b); + song_unlock_audio(); + } + song_exchange_instruments(a, b); +} + +static void _adjust_instruments_in_patterns(int start, int delta) +{ + for (int pat = 0; pat < MAX_PATTERNS; pat++) { + MODCOMMAND *note = mp->Patterns[pat]; + if (note == NULL) + continue; + for (int n = 0; n < 64 * mp->PatternSize[pat]; n++, note++) { + if (note->instr >= start) + note->instr = CLAMP(note->instr + delta, 0, MAX_SAMPLES - 1); + } + } +} + +static void _adjust_samples_in_instruments(int start, int delta) +{ + for (int n = 1; n < MAX_INSTRUMENTS; n++) { + INSTRUMENTHEADER *ins = mp->Headers[n]; + + if (ins == NULL) + continue; + // sizeof... + for (int s = 0; s < 128; s++) { + if (ins->Keyboard[s] >= start) + ins->Keyboard[s] = CLAMP(ins->Keyboard[s] + delta, 0, MAX_SAMPLES - 1); + } + } +} + +void song_init_instruments(int qq) +{ + int i; + for (int n = 1; n < MAX_INSTRUMENTS; n++) { + if (qq > -1 && qq != n) continue; + if (!song_instrument_is_empty(n)) continue; + if (mp->Ins[n].pSample == NULL) continue; + + (void)song_get_instrument(n, NULL); /* init struct */ + INSTRUMENTHEADER *ins = mp->Headers[n]; + if (ins == NULL) continue; /* eh... */ + + /* fry the rest of the structure */ + memset(ins, 0, sizeof(INSTRUMENTHEADER)); + ins->nGlobalVol = 64; + ins->nPan = 128; + + _init_envelope(&ins->VolEnv, 64); + _init_envelope(&ins->PanEnv, 32); + _init_envelope(&ins->PitchEnv, 32); + + for (i = 0; i < 128; i++) { + ins->Keyboard[i] = n; + ins->NoteMap[i] = i+1; + } + + for (i = 0; i < 12; i++) + ins->filename[i] = mp->Ins[n].name[i]; + for (i = 0; i < 32; i++) + ins->name[i] = mp->m_szNames[n][i]; + } +} + +void song_insert_sample_slot(int n) +{ + if (mp->Ins[99].pSample != NULL) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_lock_audio(); + + memmove(mp->Ins + n + 1, mp->Ins + n, (MAX_SAMPLES - n - 1) * sizeof(MODINSTRUMENT)); + memmove(mp->m_szNames + n + 1, mp->m_szNames + n, (MAX_SAMPLES - n - 1) * 32); + memset(mp->Ins + n, 0, sizeof(MODINSTRUMENT)); + memset(mp->m_szNames[n], 0, 32); + + if (song_is_instrument_mode()) + _adjust_samples_in_instruments(n, 1); + else + _adjust_instruments_in_patterns(n, 1); + + song_unlock_audio(); +} + +void song_remove_sample_slot(int n) +{ + if (mp->Ins[n].pSample != NULL) + return; + + song_lock_audio(); + + status.flags |= SONG_NEEDS_SAVE; + memmove(mp->Ins + n, mp->Ins + n + 1, (MAX_SAMPLES - n - 1) * sizeof(MODINSTRUMENT)); + memmove(mp->m_szNames + n, mp->m_szNames + n + 1, (MAX_SAMPLES - n - 1) * 32); + memset(mp->Ins + MAX_SAMPLES - 1, 0, sizeof(MODINSTRUMENT)); + memset(mp->m_szNames[MAX_SAMPLES - 1], 0, 32); + + if (song_is_instrument_mode()) + _adjust_samples_in_instruments(n, -1); + else + _adjust_instruments_in_patterns(n, -1); + + song_unlock_audio(); +} + +void song_insert_instrument_slot(int n) +{ + int i; + if (!song_instrument_is_empty(99)) return; + + status.flags |= SONG_NEEDS_SAVE; + song_lock_audio(); + for (i = 99; i > n; i--) mp->Headers[i] = mp->Headers[i-1]; + mp->Headers[n] = NULL; + _adjust_instruments_in_patterns(n, 1); + song_unlock_audio(); +} + +void song_remove_instrument_slot(int n) +{ + int i; + if (!song_instrument_is_empty(n)) return; + + song_lock_audio(); + for (i = n; i < 99; i++) mp->Headers[i] = mp->Headers[i+1]; + mp->Headers[99] = NULL; + _adjust_instruments_in_patterns(n, -1); + song_unlock_audio(); +} + +void song_wipe_instrument(int n) +{ +/* wee .... */ + if (song_instrument_is_empty(n)) return; + if (!mp->Headers[n]) return; + + status.flags |= SONG_NEEDS_SAVE; + song_lock_audio(); + delete mp->Headers[n]; + mp->Headers[n] = NULL; + song_unlock_audio(); +} +void song_delete_instrument(int n) +{ + int i, j; + if (!mp->Headers[n]) return; + song_lock_audio(); + for (i = 0; i < sizeof(mp->Headers[n]->Keyboard); i++) { + j = mp->Headers[n]->Keyboard[i]; + mp->DestroySample(j); + memset(mp->Ins+j, 0, sizeof(MODINSTRUMENT)); + memset(mp->m_szNames[j], 0, 32); + } + song_unlock_audio(); + song_wipe_instrument(n); +} diff --git a/schism/page.c b/schism/page.c new file mode 100644 index 000000000..171f86627 --- /dev/null +++ b/schism/page.c @@ -0,0 +1,1287 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "util.h" +#include "midi.h" + +#include +#include + +/* --------------------------------------------------------------------- */ +/* globals */ + +struct tracker_status status = { + PAGE_BLANK, + PAGE_BLANK, + HELP_GLOBAL, + DIALOG_NONE, + IS_FOCUSED | IS_VISIBLE, + TIME_PLAY_ELAPSED, + VIS_VU_METER, + 0, +}; + +struct page pages[32]; + +struct widget *widgets = NULL; +int *selected_widget = NULL; +int *total_widgets = NULL; + +/* --------------------------------------------------------------------- */ + +/* *INDENT-OFF* */ +static struct { + int h, m, s; +} current_time = {0, 0, 0}; +/* *INDENT-ON* */ + + +/* return 1 -> the time changed; need to redraw */ +static int check_time(void) +{ + static int last_o = -1, last_r = -1, last_timep = -1; + + time_t timep = 0; + struct tm *tmr; + int h, m, s; + enum tracker_time_display td = status.time_display; + int is_playing = song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP); + + int row, order; + + switch (td) { + case TIME_PLAY_ELAPSED: + td = (is_playing ? TIME_PLAYBACK : TIME_ELAPSED); + break; + case TIME_PLAY_CLOCK: + td = (is_playing ? TIME_PLAYBACK : TIME_CLOCK); + break; + case TIME_PLAY_OFF: + td = (is_playing ? TIME_PLAYBACK : TIME_OFF); + break; + default: + break; + } + + switch (td) { + case TIME_OFF: + h = m = s = 0; + break; + case TIME_PLAYBACK: + h = (m = (s = song_get_current_time()) / 60) / 24; + break; + case TIME_ELAPSED: + h = (m = (s = SDL_GetTicks() / 1000) / 60) / 24; + break; + case TIME_ABSOLUTE: + /* absolute time shows the time of the current cursor + position in the pattern editor :) */ + if (status.current_page == PAGE_PATTERN_EDITOR) { + row = get_current_row(); + order = song_order_for_pattern(get_current_pattern(), + -2); + } else { + order = get_current_order(); + row = 0; + } + if (order < 0) { + s = m = h = 0; + } else { + if (last_o == order && last_r == row) { + timep = last_timep; + } else { + last_timep = timep = song_get_length_to(order, row); + last_o = order; + last_r = row; + } + s = timep % 60; + m = (timep / 60) % 60; + h = (timep / 3600); + } + break; + default: + /* this will never happen */ + case TIME_CLOCK: + /* Impulse Tracker doesn't have this, but I always wanted it, so here 'tis. */ + time(&timep); +#if 0 + /* not thread safe */ + tmr = localtime(&timep); + h = tmr->tm_hour; + m = tmr->tm_min; + s = tmr->tm_sec; +#endif + /* and why bother? -mrsb */ + s = timep % 60; + m = (timep / 60) % 60; + h = (timep / 3600) % 24; + break; + } + + if (h == current_time.h && m == current_time.m && s == current_time.s) { + return 0; + } + + current_time.h = h; + current_time.m = m; + current_time.s = s; + return 1; +} + +static inline void draw_time(void) +{ + char buf[16]; + int is_playing = song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP); + + if (status.time_display == TIME_OFF || (status.time_display == TIME_PLAY_OFF && !is_playing)) + return; + + /* this allows for 999 hours... that's like... 41 days... + * who on earth leaves a tracker running for 41 days? */ + sprintf(buf, "%3d:%02d:%02d", current_time.h % 1000, + current_time.m % 60, current_time.s % 60); + draw_text((const unsigned char *)buf, 69, 9, 0, 2); +} + +/* --------------------------------------------------------------------- */ + +static void draw_page_title(void) +{ + int x, tpos, tlen = strlen(ACTIVE_PAGE.title); + + if (tlen > 0) { + tpos = 41 - ((tlen + 1) / 2); + + for (x = 1; x < tpos - 1; x++) + draw_char(154, x, 11, 1, 2); + draw_char(0, tpos - 1, 11, 1, 2); + draw_text((const unsigned char *)ACTIVE_PAGE.title, tpos, 11, 0, 2); + draw_char(0, tpos + tlen, 11, 1, 2); + for (x = tpos + tlen + 1; x < 79; x++) + draw_char(154, x, 11, 1, 2); + } else { + for (x = 1; x < 79; x++) + draw_char(154, x, 11, 1, 2); + } +} + +/* --------------------------------------------------------------------- */ +/* Not that happy with the way this function developed, but well, it still + * works. Maybe someday I'll make it suck less. */ + +static void draw_page(void) +{ + int n = ACTIVE_PAGE.total_widgets; + + if (ACTIVE_PAGE.draw_full) { + ACTIVE_PAGE.draw_full(); + } else { + + draw_page_title(); + if (ACTIVE_PAGE.draw_const) ACTIVE_PAGE.draw_const(); + if (ACTIVE_PAGE.predraw_hook) ACTIVE_PAGE.predraw_hook(); + } + + /* this doesn't use widgets[] because it needs to draw the page's + * widgets whether or not a dialog is active */ + while (n--) + draw_widget(ACTIVE_PAGE.widgets + n, n == ACTIVE_PAGE.selected_widget); + + /* redraw the area over the menu if there is one */ + if (status.dialog_type & DIALOG_MENU) + menu_draw(); + else if (status.dialog_type & DIALOG_BOX) + dialog_draw(); +} + +/* --------------------------------------------------------------------- */ + +inline int page_is_instrument_list(int page) +{ + switch (page) { + case PAGE_INSTRUMENT_LIST_GENERAL: + case PAGE_INSTRUMENT_LIST_VOLUME: + case PAGE_INSTRUMENT_LIST_PANNING: + case PAGE_INSTRUMENT_LIST_PITCH: + return 1; + default: + return 0; + } +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static struct widget new_song_widgets[10] = {}; +static int new_song_groups[4][3] = { {0, 1, -1}, {2, 3, -1}, {4, 5, -1}, {6, 7, -1} }; + +static void new_song_ok(UNUSED void *data) +{ + int flags = 0; + if (new_song_widgets[0].d.togglebutton.state) + flags |= KEEP_PATTERNS; + if (new_song_widgets[2].d.togglebutton.state) + flags |= KEEP_SAMPLES; + if (new_song_widgets[4].d.togglebutton.state) + flags |= KEEP_INSTRUMENTS; + if (new_song_widgets[6].d.togglebutton.state) + flags |= KEEP_ORDERLIST; + song_new(flags); +} + +static void new_song_draw_const(void) +{ + draw_text((const unsigned char *)"New Song", 36, 21, 3, 2); + draw_text((const unsigned char *)"Patterns", 26, 24, 0, 2); + draw_text((const unsigned char *)"Samples", 27, 27, 0, 2); + draw_text((const unsigned char *)"Instruments", 23, 30, 0, 2); + draw_text((const unsigned char *)"Order List", 24, 33, 0, 2); +} + +void new_song_dialog(void) +{ + struct dialog *dialog; + + /* only create everything if it hasn't been set up already */ + if (new_song_widgets[0].width == 0) { + create_togglebutton(new_song_widgets + 0, 35, 24, 6, 0, 2, 1, 1, 1, NULL, "Keep", + 2, new_song_groups[0]); + create_togglebutton(new_song_widgets + 1, 45, 24, 7, 1, 3, 0, 0, 0, NULL, "Clear", + 2, new_song_groups[0]); + create_togglebutton(new_song_widgets + 2, 35, 27, 6, 0, 4, 3, 3, 3, NULL, "Keep", + 2, new_song_groups[1]); + create_togglebutton(new_song_widgets + 3, 45, 27, 7, 1, 5, 2, 2, 2, NULL, "Clear", + 2, new_song_groups[1]); + create_togglebutton(new_song_widgets + 4, 35, 30, 6, 2, 6, 5, 5, 5, NULL, "Keep", + 2, new_song_groups[2]); + create_togglebutton(new_song_widgets + 5, 45, 30, 7, 3, 7, 4, 4, 4, NULL, "Clear", + 2, new_song_groups[2]); + create_togglebutton(new_song_widgets + 6, 35, 33, 6, 4, 8, 7, 7, 7, NULL, "Keep", + 2, new_song_groups[3]); + create_togglebutton(new_song_widgets + 7, 45, 33, 7, 5, 9, 6, 6, 6, NULL, "Clear", + 2, new_song_groups[3]); + create_button(new_song_widgets + 8, 28, 36, 8, 6, 8, 9, 9, 9, dialog_yes_NULL, "OK", 4); + create_button(new_song_widgets + 9, 41, 36, 8, 6, 9, 8, 8, 8, dialog_cancel_NULL, "Cancel", 2); + togglebutton_set(new_song_widgets, 1, 0); + togglebutton_set(new_song_widgets, 3, 0); + togglebutton_set(new_song_widgets, 5, 0); + togglebutton_set(new_song_widgets, 7, 0); + } + + dialog = dialog_create_custom(21, 20, 38, 19, new_song_widgets, 10, 8, new_song_draw_const, NULL); + dialog->action_yes = new_song_ok; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +void save_song_or_save_as(void) +{ + const char *f = song_get_filename(); + + if (f[0]) { + if (song_save(f, "IT214")) { /*quicklynow! */ + set_page(PAGE_BLANK); + } else { + set_page(PAGE_LOG); + } + } else { + set_page(PAGE_SAVE_MODULE); + } +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* This is an ugly monster. */ + +/* returns 1 if the key was handled */ +static int handle_key_global(struct key_event * k) +{ + int i; + + if ((!(status.flags & CLASSIC_MODE)) && !k->state && k->mouse == MOUSE_CLICK + && k->x >= 63 && k->x <= 77 && k->y >= 6 && k->y <= 7) { + status.vis_style ++; + if (status.vis_style ==VIS_SENTINEL) status.vis_style = VIS_OFF; + status.flags |= NEED_UPDATE; + return 1; + } + + /* shortcut */ + if (k->mouse) return 0; + + /* first, check the truly global keys (the ones that still work if + * a dialog's open) */ + switch (k->sym) { + case SDLK_INSERT: + if (k->mod & KMOD_SHIFT) { + if (!k->state) return 1; + status.flags |= CLIPPY_PASTE_BUFFER; + } else if (k->mod & KMOD_CTRL) { + if (!k->state) return 1; + clippy_yank(); + } + break; + case SDLK_RETURN: + if ((k->mod & KMOD_CTRL) && k->mod & KMOD_ALT) { + if (!k->state) return 1; + toggle_display_fullscreen(); + return 1; + } + break; + case SDLK_m: + if (k->mod & KMOD_CTRL) { + if (k->state) return 1; + video_mousecursor(-1); + return 1; + } + break; +#if 0 + case SDLK_d: + if (k->mod & KMOD_CTRL) { + /* should do something... + * minimize? open console? dunno. */ + return 1; + } + break; +#endif + case SDLK_e: + /* This should reset everything display-related. */ + if (k->mod & KMOD_CTRL) { + if (k->state) return 1; + font_init(); + status.flags |= NEED_UPDATE; + return 1; + } + break; + default: + break; + } + + /* next, if there's no dialog, check the rest of the keys */ + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (status.dialog_type != DIALOG_NONE) + return 0; + + switch (k->sym) { + case SDLK_q: + if (k->mod & KMOD_CTRL) { + if (k->state) show_exit_prompt(); + return 1; + } + break; + case SDLK_n: + if (k->mod & KMOD_CTRL) { + if (k->state) new_song_dialog(); + return 1; + } + break; + case SDLK_g: + if (k->mod & KMOD_CTRL) { + if (k->state) show_song_timejump(); + return 1; + } + break; + case SDLK_p: + if (k->mod & KMOD_CTRL) { + if (k->state) show_song_length(); + return 1; + } + break; + case SDLK_F1: + if (k->mod & KMOD_CTRL) { + if (k->state) set_page(PAGE_CONFIG); + } else if (k->mod & KMOD_SHIFT) { + if (k->state) set_page(PAGE_MIDI); + } else if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_HELP); + } else { + break; + } + return 1; + case SDLK_F2: + if (k->mod & KMOD_CTRL) { + if (status.current_page == PAGE_PATTERN_EDITOR) { + if (k->state) pattern_editor_length_edit(); + return 1; + } + } else if (NO_MODIFIER(k->mod)) { + if (status.current_page == PAGE_PATTERN_EDITOR) { + if (k->state) pattern_editor_display_options(); + } else { + if (k->state) set_page(PAGE_PATTERN_EDITOR); + } + return 1; + } + break; + case SDLK_F3: + if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_SAMPLE_LIST); + } else { + if (k->mod & KMOD_CTRL) set_page(PAGE_LIBRARY_SAMPLE); + break; + } + return 1; + case SDLK_F4: + if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_INSTRUMENT_LIST); + } else { + if (k->mod & KMOD_CTRL) set_page(PAGE_LIBRARY_INSTRUMENT); + break; + } + return 1; + case SDLK_F5: + if (k->mod & KMOD_CTRL) { + if (k->state) song_start(); + } else if (k->mod & KMOD_SHIFT) { + if (k->state) set_page(PAGE_PREFERENCES); + } else if (NO_MODIFIER(k->mod)) { + if (song_get_mode() == MODE_STOPPED + || (song_get_mode() == MODE_SINGLE_STEP && status.current_page == PAGE_INFO)) + if (k->state) song_start(); + if (k->state) set_page(PAGE_INFO); + } else { + break; + } + return 1; + case SDLK_F6: + if (k->mod & KMOD_SHIFT) { + if (k->state) song_start_at_order(get_current_order(), 0); + } else if (NO_MODIFIER(k->mod)) { + if (k->state) song_loop_pattern(get_current_pattern(), 0); + } else { + break; + } + return 1; + case SDLK_F7: + if (NO_MODIFIER(k->mod)) { + if (k->state) play_song_from_mark(); + } else { + break; + } + return 1; + case SDLK_F8: + if (NO_MODIFIER(k->mod)) { + if (k->state) song_stop(); + status.flags |= NEED_UPDATE; + } else { + break; + } + return 1; + case SDLK_F9: + if (k->mod & KMOD_SHIFT) { + if (k->state) set_page(PAGE_MESSAGE); + } else if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_LOAD_MODULE); + } else { + break; + } + return 1; + case SDLK_l: + case SDLK_r: + if (k->mod & KMOD_CTRL) { + if (k->state) set_page(PAGE_LOAD_MODULE); + } else { + break; + } + return 1; + case SDLK_s: + if (k->mod & KMOD_CTRL) { + if (k->state) save_song_or_save_as(); + } else { + break; + } + return 1; + case SDLK_w: + /* Ctrl-W _IS_ in IT, and hands don't leave home row :) */ + if (k->mod & KMOD_CTRL) { + if (k->state) set_page(PAGE_SAVE_MODULE); + } else { + break; + } + return 1; + case SDLK_F10: + if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_SAVE_MODULE); + } else { + break; + } + return 1; + case SDLK_F11: + if (NO_MODIFIER(k->mod)) { + if (status.current_page == PAGE_ORDERLIST_PANNING) { + if (k->state) set_page(PAGE_ORDERLIST_VOLUMES); + } else { + if (k->state) set_page(PAGE_ORDERLIST_PANNING); + } + } else if (k->mod & KMOD_CTRL) { + if (k->state) { + if (status.current_page == PAGE_LOG) { + show_about(); + } else { + set_page(PAGE_LOG); + } + } + } else if (k->state && k->mod & KMOD_ALT) { + if (song_toggle_orderlist_locked()) + status_text_flash("Order list locked"); + else + status_text_flash("Order list unlocked"); + } else { + break; + } + return 1; + case SDLK_F12: + if (k->mod & KMOD_CTRL) { + if (k->state) set_page(PAGE_PALETTE_EDITOR); + } else if (NO_MODIFIER(k->mod)) { + if (k->state) set_page(PAGE_SONG_VARIABLES); + } else { + break; + } + return 1; + case SDLK_SCROLLOCK: + if (k->mod & KMOD_ALT) { + if (k->state) { + midi_flags ^= (MIDI_DISABLE_RECORD); + status_text_flash("MIDI Input %s", + (midi_flags & MIDI_DISABLE_RECORD) + ? "Disabled" : "Enabled"); + } + return 1; + } + default: + break; + } + + /* got a bit ugly here, sorry */ + i = k->sym; + if (k->mod & KMOD_ALT) { + switch (i) { + case SDLK_F1: i = 0; break; + case SDLK_F2: i = 1; break; + case SDLK_F3: i = 2; break; + case SDLK_F4: i = 3; break; + case SDLK_F5: i = 4; break; + case SDLK_F6: i = 5; break; + case SDLK_F7: i = 6; break; + case SDLK_F8: i = 7; break; + default: + return 0; + }; + if (k->state) return 1; + + song_toggle_channel_mute(i); + if (status.current_page == PAGE_PATTERN_EDITOR) { + status.flags |= NEED_UPDATE; + } + orderpan_recheck_muted_channels(); + return 1; + } + + /* oh well */ + return 0; +} + +/* this is the important one */ +void handle_key(struct key_event * k) +{ + if (!(status.flags & DISKWRITER_ACTIVE) && ACTIVE_PAGE.pre_handle_key) { + if (ACTIVE_PAGE.pre_handle_key(k)) return; + } + + if (handle_key_global(k)) return; + if (!(status.flags & DISKWRITER_ACTIVE) && menu_handle_key(k)) return; + if (widget_handle_key(k)) return; + + /* now check a couple other keys. */ + switch (k->sym) { + case SDLK_LEFT: + if (k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + if ((k->mod & KMOD_CTRL) && status.current_page != PAGE_PATTERN_EDITOR) { + if (song_get_mode() == MODE_PLAYING) + song_set_current_order(song_get_current_order() - 1); + return; + } + break; + case SDLK_RIGHT: + if (k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + if ((k->mod & KMOD_CTRL) && status.current_page != PAGE_PATTERN_EDITOR) { + if (song_get_mode() == MODE_PLAYING) + song_set_current_order(song_get_current_order() + 1); + return; + } + break; + case SDLK_ESCAPE: + if (!k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + /* TODO | Page key handlers should return true/false depending on if the key was handled + TODO | (same as with other handlers), and the escape key check should go *after* the + TODO | page gets a chance to grab it. This way, the load sample page can switch back + TODO | to the sample list on escape like it's supposed to. (The status.current_page + TODO | checks above won't be necessary, either.) */ + if (NO_MODIFIER(k->mod) && status.dialog_type == DIALOG_NONE + && status.current_page != PAGE_LOAD_SAMPLE + && status.current_page != PAGE_LOAD_INSTRUMENT) { + menu_show(); + return; + } + break; + case SDLK_SLASH: + if (k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + kbd_set_current_octave(kbd_get_current_octave() - 1); + return; + case SDLK_ASTERISK: + if (k->state) return; + if (status.flags & DISKWRITER_ACTIVE) return; + kbd_set_current_octave(kbd_get_current_octave() + 1); + return; + case SDLK_LEFTBRACKET: + if (k->state) break; + if (status.flags & DISKWRITER_ACTIVE) return; + if (k->mod & KMOD_SHIFT) { + song_set_current_speed(song_get_current_speed() - 1); + status_text_flash("Speed set to %d frames per row", song_get_current_speed()); + if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { + song_set_initial_speed(song_get_current_speed()); + } + return; + } else if (NO_MODIFIER(k->mod)) { + song_set_current_global_volume(song_get_current_global_volume() - 1); + status_text_flash("Global volume set to %d", song_get_current_global_volume()); + if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { + song_set_initial_global_volume(song_get_current_global_volume()); + } + return; + } + return; + case SDLK_RIGHTBRACKET: + if (k->state) break; + if (status.flags & DISKWRITER_ACTIVE) return; + if (k->mod & KMOD_SHIFT) { + song_set_current_speed(song_get_current_speed() + 1); + status_text_flash("Speed set to %d frames per row", song_get_current_speed()); + if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { + song_set_initial_speed(song_get_current_speed()); + } + return; + } else if (NO_MODIFIER(k->mod)) { + song_set_current_global_volume(song_get_current_global_volume() + 1); + status_text_flash("Global volume set to %d", song_get_current_global_volume()); + if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { + song_set_initial_global_volume(song_get_current_global_volume()); + } + return; + } + return; + } + + /* and if we STILL didn't handle the key, pass it to the page. + * (or dialog, if one's active) */ + if (status.dialog_type & DIALOG_BOX) + dialog_handle_key(k); + else { + if (status.flags & DISKWRITER_ACTIVE) return; + if (ACTIVE_PAGE.handle_key) ACTIVE_PAGE.handle_key(k); + } +} + +/* --------------------------------------------------------------------- */ +/* Jeffrey, dude, you made this HARD TO DO :) */ + +#ifdef RELEASE_VERSION +#define TOP_BANNER_NORMAL "Schism Tracker v" VERSION " Copyright (C) 2003-2005 chisel" +#else +#define TOP_BANNER_NORMAL "Schism Tracker CVS Copyright (C) 2003-2005 chisel" +#endif +#define TOP_BANNER_CLASSIC "Impulse Tracker v2.14 Copyright (C) 1995-1998 Jeffrey Lim" + +static void draw_top_info_const(void) +{ + int n, tl, br; + + if (status.flags & INVERTED_PALETTE) { + tl = 3; + br = 1; + } else { + tl = 1; + br = 3; + } + + /* gcc optimizes out the strlen's here :) */ + if (status.flags & CLASSIC_MODE) { + draw_text((const unsigned char *)TOP_BANNER_CLASSIC, (80 - strlen(TOP_BANNER_CLASSIC)) / 2, 1, 0, 2); + } else { + draw_text((const unsigned char *)TOP_BANNER_NORMAL, (80 - strlen(TOP_BANNER_NORMAL)) / 2, 1, 0, 2); + } + + draw_text((const unsigned char *)"Song Name", 2, 3, 0, 2); + draw_text((const unsigned char *)"File Name", 2, 4, 0, 2); + draw_text((const unsigned char *)"Order", 6, 5, 0, 2); + draw_text((const unsigned char *)"Pattern", 4, 6, 0, 2); + draw_text((const unsigned char *)"Row", 8, 7, 0, 2); + + draw_text((const unsigned char *)"Speed/Tempo", 38, 4, 0, 2); + draw_text((const unsigned char *)"Octave", 43, 5, 0, 2); + + draw_text((const unsigned char *)"F1...Help F9.....Load", 21, 6, 0, 2); + draw_text((const unsigned char *)"ESC..Main Menu F5/F8..Play / Stop", 21, 7, 0, 2); + + /* the neat-looking (but incredibly ugly to draw) borders */ + draw_char(128, 30, 4, br, 2); + draw_char(128, 57, 4, br, 2); + draw_char(128, 19, 5, br, 2); + draw_char(128, 51, 5, br, 2); + draw_char(129, 36, 4, br, 2); + draw_char(129, 50, 6, br, 2); + draw_char(129, 17, 8, br, 2); + draw_char(129, 18, 8, br, 2); + draw_char(131, 37, 3, br, 2); + draw_char(131, 78, 3, br, 2); + draw_char(131, 19, 6, br, 2); + draw_char(131, 19, 7, br, 2); + draw_char(132, 49, 3, tl, 2); + draw_char(132, 49, 4, tl, 2); + draw_char(132, 49, 5, tl, 2); + draw_char(134, 75, 2, tl, 2); + draw_char(134, 76, 2, tl, 2); + draw_char(134, 77, 2, tl, 2); + draw_char(136, 37, 4, br, 2); + draw_char(136, 78, 4, br, 2); + draw_char(136, 30, 5, br, 2); + draw_char(136, 57, 5, br, 2); + draw_char(136, 51, 6, br, 2); + draw_char(136, 19, 8, br, 2); + draw_char(137, 49, 6, br, 2); + draw_char(137, 11, 8, br, 2); + draw_char(138, 37, 2, tl, 2); + draw_char(138, 78, 2, tl, 2); + draw_char(139, 11, 2, tl, 2); + draw_char(139, 49, 2, tl, 2); + + for (n = 0; n < 5; n++) { + draw_char(132, 11, 3 + n, tl, 2); + draw_char(129, 12 + n, 8, br, 2); + draw_char(134, 12 + n, 2, tl, 2); + draw_char(129, 20 + n, 5, br, 2); + draw_char(129, 31 + n, 4, br, 2); + draw_char(134, 32 + n, 2, tl, 2); + draw_char(134, 50 + n, 2, tl, 2); + draw_char(129, 52 + n, 5, br, 2); + draw_char(129, 58 + n, 4, br, 2); + draw_char(134, 70 + n, 2, tl, 2); + } + for (; n < 10; n++) { + draw_char(134, 12 + n, 2, tl, 2); + draw_char(129, 20 + n, 5, br, 2); + draw_char(134, 50 + n, 2, tl, 2); + draw_char(129, 58 + n, 4, br, 2); + } + for (; n < 20; n++) { + draw_char(134, 12 + n, 2, tl, 2); + draw_char(134, 50 + n, 2, tl, 2); + draw_char(129, 58 + n, 4, br, 2); + } + + draw_text((const unsigned char *)"Time", 63, 9, 0, 2); + draw_char('/', 15, 5, 1, 0); + draw_char('/', 15, 6, 1, 0); + draw_char('/', 15, 7, 1, 0); + draw_char('/', 53, 4, 1, 0); + draw_char(':', 52, 3, 7, 0); +} + +/* --------------------------------------------------------------------- */ + +void update_current_instrument(void) +{ + int ins_mode, n; + char *name; + char buf[4]; + + if (page_is_instrument_list(status.current_page) + || status.current_page == PAGE_SAMPLE_LIST + || (!(status.flags & CLASSIC_MODE) + && (status.current_page == PAGE_ORDERLIST_PANNING + || status.current_page == PAGE_ORDERLIST_VOLUMES))) + ins_mode = 0; + else + ins_mode = song_is_instrument_mode(); + + if (ins_mode) { + draw_text((const unsigned char *)"Instrument", 39, 3, 0, 2); + n = instrument_get_current(); + song_get_instrument(n, &name); + } else { + draw_text((const unsigned char *)" Sample", 39, 3, 0, 2); + n = sample_get_current(); + song_get_sample(n, &name); + } + + if (n > 0) { + draw_text(numtostr(2, n, buf), 50, 3, 5, 0); + draw_text_len((const unsigned char *)name, 25, 53, 3, 5, 0); + } else { + draw_text((const unsigned char *)"..", 50, 3, 5, 0); + draw_text((const unsigned char *)".........................", 53, 3, 5, 0); + } +} + +static void redraw_top_info(void) +{ + char buf[8]; + + update_current_instrument(); + + draw_text_len((const unsigned char *)song_get_basename(), 18, 12, 4, 5, 0); + draw_text_len((const unsigned char *)song_get_title(), 25, 12, 3, 5, 0); + + update_current_order(); + update_current_pattern(); + update_current_row(); + + draw_text(numtostr(3, song_get_current_speed(), buf), 50, 4, 5, 0); + draw_text(numtostr(3, song_get_current_tempo(), buf), 54, 4, 5, 0); + draw_char('0' + kbd_get_current_octave(), 50, 5, 5, 0); +} + +static void _draw_vis_box(void) +{ + draw_box(62, 5, 78, 8, BOX_THIN | BOX_INNER | BOX_INSET); + draw_fill_chars(63, 6, 77, 7, 0); +} + +static void vis_oscilloscope(void) +{ + static int _virgin = 1; + static struct vgamem_overlay vis = { + 63, 6, 77, 7, + }; + if (_virgin) { + vgamem_font_reserve(&vis); + _virgin = 0; + } + _draw_vis_box(); + song_lock_audio(); + if (audio_output_bits == 16) { + draw_sample_data_rect_16(&vis,audio_buffer,audio_buffer_size, + audio_output_channels); + } else { + draw_sample_data_rect_8(&vis,audio_buffer,audio_buffer_size, + audio_output_channels); + } + song_unlock_audio(); +} + +static void vis_vu_meter(void) +{ + int left, right; + + song_get_vu_meter(&left, &right); + left /= 4; + right /= 4; + + _draw_vis_box(); + draw_vu_meter(63, 6, 15, left, 5, 4); + draw_vu_meter(63, 7, 15, right, 5, 4); +} + +static void vis_fakemem(void) +{ + char buf[32]; + unsigned int conv; + unsigned int ems; + + if (status.flags & CLASSIC_MODE) { + ems = memused_ems(); + if (ems > 67108864) ems = 0; + else ems = 67108864 - ems; + + conv = memused_lowmem(); + if (conv > 524288) conv = 0; + else conv = 524288 - conv; + + conv >>= 10; + ems >>= 10; + + sprintf(buf, "FreeMem %dk", conv); + draw_text((const unsigned char *)buf, 63, 6, 0, 2); + sprintf(buf, "FreeEMS %dk", ems); + draw_text((const unsigned char *)buf, 63, 7, 0, 2); + } else { + sprintf(buf, " Song %dk", + (memused_patterns() + +memused_instruments() + +memused_songmessage()) >> 10); + draw_text((const unsigned char *)buf, 63, 6, 0, 2); + sprintf(buf, "Samples %dk", memused_samples() >> 10); + draw_text((const unsigned char *)buf, 63, 7, 0, 2); + } +} + +static inline void draw_vis(void) +{ + if (status.flags & CLASSIC_MODE) { + /* classic mode requires fakemem display */ + vis_fakemem(); + return; + } + switch (status.vis_style) { + case VIS_FAKEMEM: + vis_fakemem(); + break; + case VIS_OSCILLOSCOPE: + vis_oscilloscope(); + break; + case VIS_VU_METER: + vis_vu_meter(); + break; + default: + case VIS_OFF: + break; + } +} + +/* this completely redraws everything. */ +void redraw_screen(void) +{ + int n; + char buf[4]; + + if (!ACTIVE_PAGE.draw_full) { + draw_fill_chars(0,0,79,49,2); + + /* border around the whole screen */ + draw_char(128, 0, 0, 3, 2); + for (n = 79; n > 49; n--) + draw_char(129, n, 0, 3, 2); + do { + draw_char(129, n, 0, 3, 2); + draw_char(131, 0, n, 3, 2); + } while (--n); + + draw_top_info_const(); + redraw_top_info(); + } + + draw_page(); + + if (!ACTIVE_PAGE.draw_full) { + draw_vis(); + draw_time(); + draw_text(numtostr(3, song_get_current_speed(), buf), + 50, 4, 5, 0); + draw_text(numtostr(3, song_get_current_tempo(), buf), + 54, 4, 5, 0); + + status_text_redraw(); + } +} + +/* important :) */ +void playback_update(void) +{ + /* the order here is significant -- check_time has side effects */ + if (check_time() || song_get_mode()) + status.flags |= NEED_UPDATE; + + if (ACTIVE_PAGE.playback_update) ACTIVE_PAGE.playback_update(); +} + +/* --------------------------------------------------------------------- */ + +void set_page(int new_page) +{ + int prev_page = status.current_page; + + if (new_page != prev_page) + status.previous_page = prev_page; + status.current_page = new_page; + + if (new_page != PAGE_HELP) + status.current_help_index = ACTIVE_PAGE.help_index; + + /* synchronize the sample/instrument. + * FIXME | this isn't quite right. for instance, in impulse + * FIXME | tracker, flipping back and forth between the sample + * FIXME | list and instrument list will keep changing the + * FIXME | current sample/instrument. */ + if (status.flags & SAMPLE_CHANGED) { + if (song_is_instrument_mode()) + instrument_synchronize_to_sample(); + else + instrument_set(sample_get_current()); + } else if (status.flags & INSTRUMENT_CHANGED) { + sample_set(instrument_get_current()); + } + status.flags &= ~(SAMPLE_CHANGED | INSTRUMENT_CHANGED); + + /* bit of ugliness to keep the sample/instrument numbers sane */ + if (page_is_instrument_list(new_page) + && instrument_get_current() < 1) + instrument_set(1); + else if (new_page == PAGE_SAMPLE_LIST && sample_get_current() < 1) + sample_set(1); + + if (ACTIVE_PAGE.set_page) ACTIVE_PAGE.set_page(); + + if (status.dialog_type & DIALOG_MENU) { + menu_hide(); + } else { +#ifndef NDEBUG + if (status.dialog_type != DIALOG_NONE) { + fprintf(stderr, + "set_page invoked with a dialog active:" + " how did this happen?\n"); + return; + } +#endif + if (new_page == prev_page) + return; + } + + /* update the pointers */ + widgets = ACTIVE_PAGE.widgets; + selected_widget = &(ACTIVE_PAGE.selected_widget); + total_widgets = &(ACTIVE_PAGE.total_widgets); + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +void load_pages(void) +{ + memset(pages, 0, sizeof(pages)); + + blank_load_page(pages + PAGE_BLANK); + help_load_page(pages + PAGE_HELP); + pattern_editor_load_page(pages + PAGE_PATTERN_EDITOR); + sample_list_load_page(pages + PAGE_SAMPLE_LIST); + instrument_list_general_load_page(pages + PAGE_INSTRUMENT_LIST_GENERAL); + instrument_list_volume_load_page(pages + PAGE_INSTRUMENT_LIST_VOLUME); + instrument_list_panning_load_page(pages + PAGE_INSTRUMENT_LIST_PANNING); + instrument_list_pitch_load_page(pages + PAGE_INSTRUMENT_LIST_PITCH); + info_load_page(pages + PAGE_INFO); + preferences_load_page(pages + PAGE_PREFERENCES); + midi_load_page(pages + PAGE_MIDI); + midiout_load_page(pages + PAGE_MIDI_OUTPUT); + fontedit_load_page(pages + PAGE_FONT_EDIT); + load_module_load_page(pages + PAGE_LOAD_MODULE); + save_module_load_page(pages + PAGE_SAVE_MODULE); + orderpan_load_page(pages + PAGE_ORDERLIST_PANNING); + ordervol_load_page(pages + PAGE_ORDERLIST_VOLUMES); + song_vars_load_page(pages + PAGE_SONG_VARIABLES); + palette_load_page(pages + PAGE_PALETTE_EDITOR); + message_load_page(pages + PAGE_MESSAGE); + log_load_page(pages + PAGE_LOG); + load_sample_load_page(pages + PAGE_LOAD_SAMPLE); + library_sample_load_page(pages + PAGE_LIBRARY_SAMPLE); + load_instrument_load_page(pages + PAGE_LOAD_INSTRUMENT); + library_instrument_load_page(pages + PAGE_LIBRARY_INSTRUMENT); + about_load_page(pages+PAGE_ABOUT); + config_load_page(pages + PAGE_CONFIG); + + widgets = pages[PAGE_BLANK].widgets; + selected_widget = &(pages[PAGE_BLANK].selected_widget); + total_widgets = &(pages[PAGE_BLANK].total_widgets); +} + +/* --------------------------------------------------------------------- */ +/* this function's name sucks, but I don't know what else to call it. */ + +void main_song_changed_cb(void) +{ + int n; + + /* perhaps this should be in page_patedit.c? */ + set_current_order(0); + n = song_get_orderlist()[0]; + if (n > 199) + n = 0; + set_current_pattern(n); + set_current_row(0); + song_clear_solo_channel(); + + for (n = ARRAY_SIZE(pages) - 1; n >= 0; n--) { + if (pages[n].song_changed_cb) + pages[n].song_changed_cb(); + } + + /* With Modplug, loading is sort of an atomic operation from the + * POV of the client, so the other info IT prints wouldn't be + * very useful. */ + if (song_get_basename()[0]) { + log_appendf(2, "Loaded song: %s", song_get_basename()); + } + /* TODO | print some message like "new song created" if there's + * TODO | no filename, and thus no file. (but DON'T print it the + * TODO | very first time this is called) */ + + status.flags |= NEED_UPDATE; + memused_songchanged(); +} + +/* --------------------------------------------------------------------- */ +/* not sure where else to toss this crap */ + +static void real_exit_ok(UNUSED void *data) +{ + exit(0); +} +static void exit_ok(UNUSED void *data) +{ + struct dialog *d; + if (status.flags & SONG_NEEDS_SAVE) { + d = dialog_create(DIALOG_OK_CANCEL, + "Current module not saved. Proceed?", + real_exit_ok, NULL, 1, NULL); + /* hack to make cancel default */ + d->selected_widget = 1; + } else { + exit(0); + } +} +static void real_load_ok(void *data) +{ + char *fdata; + int r; + + dialog_destroy_all(); + + fdata = (char*)data; + r = song_load_unchecked(fdata); + free(fdata); +/* err... */ +} +int song_load(const char *filename) +{ + struct dialog *d; + char *tmp; + + dialog_destroy_all(); + + if (status.flags & SONG_NEEDS_SAVE) { + tmp = strdup(filename); + assert(tmp); + + d = dialog_create(DIALOG_OK_CANCEL, + "Current module not saved. Proceed?", + real_load_ok, free, 1, tmp); + /* hack to make cancel default */ + d->selected_widget = 1; + } else { + return song_load_unchecked(filename); + } +} +void show_exit_prompt(void) +{ + /* since this can be called with a dialog already active (on sdl + * quit action, when the window's close button is clicked for + * instance) it needs to get rid of any other open dialogs. + * (dialog_create takes care of closing menus.) */ + dialog_destroy_all(); + + if (status.flags & CLASSIC_MODE) { + dialog_create(DIALOG_OK_CANCEL, "Exit Impulse Tracker?", + exit_ok, NULL, 0, NULL); + } else { + dialog_create(DIALOG_OK_CANCEL, "Exit Schism Tracker?", + exit_ok, NULL, 0, NULL); + } +} + +static struct widget _timejump_widgets[4]; +static int _tj_num1 = 0, _tj_num2 = 0; + +static int _timejump_keyh(struct key_event *k) +{ + if (k->sym == SDLK_BACKSPACE) { + if (*selected_widget == 1 && _timejump_widgets[1].d.numentry.value == 0) { + if (k->state) change_focus_to(0); + return 1; + } + } + if (k->sym == SDLK_COLON) { + if (k->state) { + if (*selected_widget == 0) { + change_focus_to(1); + } + } + return 1; + } + return 0; +} +static void _timejump_draw(void) +{ + draw_text((const unsigned char *)"Jump to time:", 30, 26, 0, 2); + + draw_char(':', 46, 26, 3, 0); + draw_box(43, 25, 49, 27, BOX_THIN | BOX_INNER | BOX_INSET); +} +static void _timejump_ok(UNUSED void *ign) +{ + unsigned long sec; + int no, np, nr; + sec = (_timejump_widgets[0].d.numentry.value * 60) + + _timejump_widgets[1].d.numentry.value; + song_get_at_time(sec, &no, &nr); + set_current_order(no); + np = song_get_orderlist()[no]; + if (np < 200) { + set_current_pattern(np); + set_current_row(nr); + set_page(PAGE_PATTERN_EDITOR); + } +} +void show_song_timejump(void) +{ + struct dialog *d; + _tj_num1 = _tj_num2 = 0; + create_numentry(_timejump_widgets+0, 44, 26, 2, 0, 2, 1, NULL, 0, 21, &_tj_num1); + create_numentry(_timejump_widgets+1, 47, 26, 2, 1, 2, 2, NULL, 0, 59, &_tj_num2); + _timejump_widgets[0].d.numentry.handle_unknown_key = _timejump_keyh; + _timejump_widgets[0].d.numentry.reverse = 1; + _timejump_widgets[1].d.numentry.reverse = 1; + create_button(_timejump_widgets+2, 30, 29, 8, 0, 2, 2, 3, 3, _timejump_ok, "OK", 4); + create_button(_timejump_widgets+3, 42, 29, 8, 1, 3, 3, 3, 0, dialog_cancel_NULL, "Cancel", 2); + d = dialog_create_custom(26, 24, 30, 8, _timejump_widgets, 4, 0, _timejump_draw, NULL); + d->handle_key = _timejump_keyh; + d->action_yes = _timejump_ok; + d->action_no = dialog_cancel_NULL; + d->action_cancel = dialog_cancel_NULL; +} + +void show_song_length(void) +{ + char buf[64]; /* this is way enough space ;) */ + unsigned long length = song_get_length(); + + snprintf(buf, 64, "Total song time: %3ld:%02ld:%02ld", + length / 3600, (length / 60) % 60, length % 60); + + dialog_create(DIALOG_OK, buf, NULL, NULL, 0, NULL); +} diff --git a/schism/page_about.c b/schism/page_about.c new file mode 100644 index 000000000..f9f60dbbe --- /dev/null +++ b/schism/page_about.c @@ -0,0 +1,195 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "auto/logoit.h" +#include "auto/logoschism.h" + +#include "headers.h" +#include "it.h" +#include "page.h" +#include "video.h" + +static SDL_Surface *it_logo = 0; +static SDL_Surface *schism_logo = 0; + +static struct widget widgets_about[1]; + + +static int about_page_handle_key(UNUSED struct key_event *k) +{ + return 0; +} +static void about_page_redraw(void) +{ + draw_fill_chars(0,0,79,49,0); +} + +static int _fixup_ignore_globals(struct key_event *k) +{ + if (k->mouse && k->y > 20) return 0; + switch (k->sym) { + case SDLK_LEFT: + case SDLK_RIGHT: + case SDLK_DOWN: + case SDLK_UP: + case SDLK_TAB: + case SDLK_RETURN: + case SDLK_ESCAPE: + /* use default handler */ + return 0; + }; + /* this way, we can't pull up help here */ + return 1; +} +static void _draw_full(void) +{ +} + +void about_load_page(struct page *page) +{ + page->title = ""; + page->total_widgets = 1; + page->widgets = widgets_about; + page->pre_handle_key = _fixup_ignore_globals; + page->help_index = HELP_GLOBAL; + page->draw_full = _draw_full; + create_other(widgets_about+0, 0, about_page_handle_key, about_page_redraw); +} + +static struct widget about_widgets[1]; +static struct vgamem_overlay logo_image = { + 24,18, + 57,23, +}; + +static void about_close(UNUSED void *data) +{ + dialog_destroy(); + if (status.current_page == PAGE_ABOUT) set_page(PAGE_LOAD_MODULE); + status.flags |= NEED_UPDATE; +} +static void about_draw_const(void) +{ + static char buf[80]; + + if (status.current_page == PAGE_ABOUT) { + /* redraw outer part */ + draw_box(11,16, 68, 34, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK); + } + + if (status.flags & CLASSIC_MODE) { + draw_box(25,25, 56, 30, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK); + + draw_text("Sound Card Setup", 32, 26, 0, 2); + + if (strcasecmp(song_audio_driver(), "nosound") == 0) { + draw_text("No sound card detected", 29, 28, 0, 2); + } else { + draw_text("Sound Blaster 16 detected", 26, 28, 0, 2); + draw_text("Port 220h, IRQ 7, DMA 5", 26, 29, 0, 2); + } + } else { + draw_text("Schism Tracker is Copyright (C) 2003-2005", + 21,25, 1, 2); + draw_text("Written by Storlek, and Mrs. Brisby, and contains code", + 12,27, 1, 2); + draw_text("written by Olivier Lapicque, Markus Fick, Adam Goode,", + 12,28, 1, 2); + draw_text("Ville Jokela, Juan Linietsky, Juha Niemimaki, and others", + 12,29, 1, 2); + draw_text("and is based on Impulse Tracker by Jeffrey Lim.", + 12,30, 1, 2); + + if (status.current_page == PAGE_ABOUT) { + draw_text("This program is free software; you can redistribute it and/or modify", 1, 1, 6, 0); + draw_text("it under the terms of the GNU General Public License as published by", 1, 2, 6, 0); + draw_text("the Free Software Foundation; either version 2 of the License, or", 1, 3, 6, 0); + draw_text("(at your option) any later version.", 1,4,6,0); + + draw_text("This program is distributed in the hope that it will be useful,", 1,6,6,0); + draw_text("but WITHOUT ANY WARRANTY; without even the implied warranty of", 1,7,6,0); + draw_text("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the", 1,8,6,0); + draw_text("GNU General Public License for more details.", 1,9,6,0); + + draw_text("You should have received a copy of the GNU General Public License", 1, 11,6,0); + draw_text("along with this program; if not, write to the Free Software", 1,12,6,0); + draw_text("Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA", 1,13,6,0); + + sprintf(buf, "Using %s on %s", song_audio_driver(), video_driver_name()); + draw_text(buf, 79 - strlen(buf), 48, 6, 0); + } + } + vgamem_fill_reserve(&logo_image, 0, 2); +} + +void show_about(void) +{ + static int didit = 0; + struct dialog *d; + unsigned char *p; + int x, y; + + if (!didit) { + vgamem_font_reserve(&logo_image); + it_logo = xpmdata(_logo_it_xpm); + schism_logo = xpmdata(_logo_schism_xpm); + } + + if (status.flags & CLASSIC_MODE) { + p = it_logo ? it_logo->pixels : 0; + } else { + p = schism_logo ? schism_logo->pixels : 0; + } + + /* this is currently pretty gross */ + vgamem_clear_reserve(&logo_image); + if (p) { +#define LOGO_WIDTH 272 +#define LOGO_PITCH 272 +#define LOGO_HEIGHT 40 + for (y = 0; y < LOGO_HEIGHT; y++) { + for (x = 0; x < LOGO_WIDTH; x++) { + if (p[x]) { + vgamem_font_clearpixel(&logo_image, x+2, y+6); + } else { + vgamem_font_putpixel(&logo_image, x+2, y+6); + } + } + vgamem_font_clearpixel(&logo_image, x, y+6); + vgamem_font_clearpixel(&logo_image, x+1, y+6); + p += LOGO_PITCH; + } + } + + create_button(about_widgets+0, + 33,32, + 12, + 0,0,0,0,0, + about_close, "Continue", 3); + d = dialog_create_custom(11,16, + 58, 19, + about_widgets, 1, 0, + about_draw_const, NULL); + d->action_yes = about_close; + d->action_no = about_close; + d->action_cancel = about_close; +} + + diff --git a/schism/page_blank.c b/schism/page_blank.c new file mode 100644 index 000000000..b5847b34f --- /dev/null +++ b/schism/page_blank.c @@ -0,0 +1,53 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_blank[1]; + +/* --------------------------------------------------------------------- */ + +static int blank_page_handle_key(UNUSED struct key_event * k) +{ + return 0; +} + +static void blank_page_redraw(void) +{ +} + +/* --------------------------------------------------------------------- */ + +void blank_load_page(struct page *page) +{ + page->title = ""; + page->total_widgets = 1; + page->widgets = widgets_blank; + page->help_index = HELP_GLOBAL; + + create_other(widgets_blank + 0, 0, blank_page_handle_key, blank_page_redraw); +} diff --git a/schism/page_config.c b/schism/page_config.c new file mode 100644 index 000000000..1394f22e3 --- /dev/null +++ b/schism/page_config.c @@ -0,0 +1,347 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include "mixer.h" + +#include + +#include "diskwriter.h" + +/* --------------------------------------------------------------------- */ + +#define SAVED_AT_EXIT "Audio configuration will be saved at exit" + +static void config_set_page(void); + +static struct widget widgets_config[32]; + +static const char *time_displays[] = { + "Off", "Play / Elapsed", "Play / Clock", "Play / Off", "Elapsed", "Clock", "Absolute", NULL +}; +static const char *vis_styles[] = { + "Off", "Memory Stats", "Oscilloscope", "VU Meter", NULL +}; + +static const char *output_channels[] = { + "Monaural", "Stereo", "Dolby/Surround", NULL +}; + +static int sample_rate_cursor = 0; +static int sample_rate_cursor2 = 0; + +static const char *bit_rates[] = { "8 Bit", "16 Bit", //"24 Bit", "32 Bit", + NULL }; + +static int video_fs_group[] = { 8, 9, -1 }; +static int video_group[] = { 10, 11, 12, 13, -1 }; + +static void change_mixer_limits(void) +{ + audio_settings.channel_limit = widgets_config[0].d.thumbbar.value; + + audio_settings.sample_rate = widgets_config[1].d.numentry.value; + audio_settings.bits = widgets_config[2].d.menutoggle.state ? 16 : 8; + audio_settings.channels = widgets_config[3].d.menutoggle.state+1; + + song_init_modplug(); + status_text_flash(SAVED_AT_EXIT); +} +static void change_ui_settings(void) +{ + status.vis_style = widgets_config[4].d.menutoggle.state; + status.time_display = widgets_config[7].d.menutoggle.state; + if (widgets_config[5].d.toggle.state) { + status.flags |= CLASSIC_MODE; + } else { + status.flags &= ~CLASSIC_MODE; + } + kbd_digitrakker_voodoo(widgets_config[6].d.toggle.state); + status.flags |= NEED_UPDATE; + status_text_flash(SAVED_AT_EXIT); +} +static int countdown = 10; +static time_t started = 0; + +static const char *video_revert_driver = 0; +static int video_revert_fs = 0; + +static void video_mode_keep(UNUSED void*ign) +{ + dialog_destroy(); + status_text_flash(SAVED_AT_EXIT); + config_set_page(); + status.flags |= NEED_UPDATE; +} +static void video_mode_cancel(UNUSED void*ign) +{ + dialog_destroy(); + if (video_revert_driver) video_init(video_revert_driver); + video_fullscreen(video_revert_fs); + palette_apply(); + font_init(); + config_set_page(); + status.flags |= NEED_UPDATE; +} + +static void video_dialog_draw_const(void) +{ + char buf[80]; + time_t now; + + time(&now); + if (now != started) { + countdown--; + time(&started); /* err... */ + status.flags |= NEED_UPDATE; + if (countdown == 0) { + video_mode_cancel(0); + return; + } + } + + draw_text("Your video settings have been changed.", 21,19,0,2); + sprintf(buf, "In %2d seconds, your changes will be", countdown); + draw_text(buf, 23, 21, 0, 2); + draw_text("reverted to the last known-good", 21, 22, 0, 2); + draw_text("settings.", 21, 23, 0, 2); + draw_text("To use the new video mode, and make", 21, 24, 0, 2); + draw_text("it default, press select OK below.", 21, 25, 0, 2); +} + +static struct widget video_dialog_widgets[2]; +static void video_change_dialog(void) +{ + struct dialog *d; + + video_revert_driver = video_driver_name(); + video_revert_fs = video_is_fullscreen(); + + countdown = 10; + time(&started); + + create_button(video_dialog_widgets+0, 28,28,8, 0, 0, 0, 1, 1, + video_mode_keep, "OK", 4); + create_button(video_dialog_widgets+1, 42,28,8, 1, 1, 0, 1, 0, + video_mode_cancel, "Cancel", 2); + d = dialog_create_custom(20, 17, 40, 14, + video_dialog_widgets, + 2, 1, + video_dialog_draw_const, NULL); + d->action_yes = video_mode_keep; + d->action_no = video_mode_cancel; + d->action_cancel = video_mode_cancel; +} + +static void change_video_settings(void) +{ + const char *new_video_driver; + int new_fs_flag; + + if (widgets_config[10].d.togglebutton.state) { + new_video_driver = "sdl"; + } else if (widgets_config[11].d.togglebutton.state) { + new_video_driver = "yuv"; + } else if (widgets_config[12].d.togglebutton.state) { + new_video_driver = "gl"; + } else if (widgets_config[13].d.togglebutton.state) { + new_video_driver = "directdraw";; + } else { + new_video_driver = "sdl"; + } + + if (widgets_config[8].d.togglebutton.state) { + new_fs_flag = 1; + } else { + new_fs_flag = 0; + } + + if (!strcasecmp(new_video_driver, video_driver_name()) + && new_fs_flag == video_is_fullscreen()) { + return; + } + + video_change_dialog(); + if (strcasecmp(new_video_driver, video_driver_name())) + video_init(new_video_driver); + if (new_fs_flag != video_is_fullscreen()) + video_fullscreen(new_fs_flag); + palette_apply(); + font_init(); +} + +/* --------------------------------------------------------------------- */ + +static void config_draw_const(void) +{ + int n; + + draw_text("Channel Limit",4,15, 0, 2); + draw_text("Mixing Rate",6,16, 0, 2); + draw_text("Sample Size",6,17, 0, 2); + draw_text("Output Channels",2,18, 0, 2); + + draw_text("Visualization",4,20, 0, 2); + draw_text("Classic Mode",5,21, 0, 2); + draw_text("b's not #'s",6,22, 0, 2); + draw_text("Time Display",5,23, 0, 2); + + draw_text("Video Driver:", 2, 26, 0, 2); + draw_text("Full Screen:", 38, 26, 0, 2); + + draw_fill_chars(18, 15, 34, 23, 0); + draw_box(17,14,35,24, BOX_THIN | BOX_INNER | BOX_INSET); + + for (n = 18; n < 35; n++) { + draw_char(154, n, 19, 3, 0); + } + +} +static void config_set_page(void) +{ + const char *nn; + + widgets_config[0].d.thumbbar.value = audio_settings.channel_limit; + widgets_config[1].d.numentry.value = audio_settings.sample_rate; + widgets_config[2].d.menutoggle.state = !!(audio_settings.bits == 16); + widgets_config[3].d.menutoggle.state = audio_settings.channels-1; + + widgets_config[4].d.menutoggle.state = status.vis_style; + widgets_config[5].d.toggle.state = !!(status.flags & CLASSIC_MODE); + widgets_config[6].d.toggle.state = !!(status.flags & DIGITRAKKER_VOODOO); + widgets_config[7].d.menutoggle.state = status.time_display; + + widgets_config[8].d.togglebutton.state = video_is_fullscreen(); + widgets_config[9].d.togglebutton.state = !video_is_fullscreen(); + + nn = video_driver_name(); + widgets_config[10].d.togglebutton.state = (strcasecmp(nn,"sdl") == 0); + widgets_config[11].d.togglebutton.state = (strcasecmp(nn,"yuv") == 0); + widgets_config[12].d.togglebutton.state = (strcasecmp(nn,"gl") == 0); + widgets_config[13].d.togglebutton.state = (strcasecmp(nn,"directdraw") == 0); +} + +/* --------------------------------------------------------------------- */ +void config_load_page(struct page *page) +{ + int i; + + page->title = "Configuration (Ctrl-F1)"; + page->draw_const = config_draw_const; + page->set_page = config_set_page; + page->total_widgets = 14; + page->widgets = widgets_config; + page->help_index = HELP_GLOBAL; + + create_thumbbar(widgets_config+0, + 18, 15, 17, + 0,1,1, + change_mixer_limits, 4, 256); + create_numentry(widgets_config+1, + 18, 16, 7, + 0,2,2, + change_mixer_limits, + 4000, 192000, + &sample_rate_cursor); + create_menutoggle(widgets_config+2, + 18, 17, + 1,3,2,2,3, + change_mixer_limits, + bit_rates); + create_menutoggle(widgets_config+3, + 18, 18, + 2,4,3,3,4, + change_mixer_limits, + output_channels); + + create_menutoggle(widgets_config+4, + 18, 20, + 3,5,4,4,5, + change_ui_settings, + vis_styles); + create_toggle(widgets_config+5, + 18, 21, + 4,6,5,5,6, + change_ui_settings); + create_toggle(widgets_config+6, + 18, 22, + 5,7,6,6,7, + change_ui_settings); + create_menutoggle(widgets_config+7, + 18, 23, + 6,10,7,7,10, + change_ui_settings, + time_displays); + + create_togglebutton(widgets_config+8, + 44, 28, 5, + 7,8,10,9,9, + change_video_settings, + "Yes", + 2, video_fs_group); + create_togglebutton(widgets_config+9, + 54, 28, 5, + 9,9,8,9,0, + change_video_settings, + "No", + 2, video_fs_group); + + create_togglebutton(widgets_config+10, + 6, 28, 26, + 7,11,10,8,11, + change_video_settings, + "SDL Video Surface", + 2, video_group); + + create_togglebutton(widgets_config+11, + 6, 31, 26, + 10,12,11,8,12, + change_video_settings, + "YUV Video Overlay", + 2, video_group); + + create_togglebutton(widgets_config+12, + 6, 34, 26, + 11,13,12,8,13, + change_video_settings, + "OpenGL Graphic Context", + 2, video_group); + + create_togglebutton(widgets_config+13, + 6, 37, 26, + 12,13,13,8,8, + change_video_settings, + "DirectDraw Surface", + 2, video_group); +#ifndef WIN32 + /* patch ddraw out */ + video_group[3] = -1; + widgets_config[13].d.togglebutton.state = 0; + widgets_config[12].next.down = 12; + widgets_config[12].next.tab = 8; + page->total_widgets--; +#endif + +} diff --git a/schism/page_help.c b/schism/page_help.c new file mode 100644 index 000000000..d6393c995 --- /dev/null +++ b/schism/page_help.c @@ -0,0 +1,253 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* Well, this page is just a big hack factory, but it's at least an + * improvement over the message editor :P */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_help[2]; + +/* newline_pointers[0] = top of text + * newline_pointers[1] = second line + * etc. + * + * Each line is terminated by chr 13, chr 10, or chr 0... yeah, maybe I + * could've done this a smarter way and have every line end with chr 0 + * or something, but it'd be harder to deal with in other places, like + * editing the text (especially considering the dopey crlf newlines + * imposed by that ugly OS on the other side of the tracks) */ +static const char **newline_pointers = NULL; + +static int num_lines = 0; +static int top_line = 0; + +static const char *blank_line = "|"; +static const char *separator_line = "%"; + +/* --------------------------------------------------------------------- */ + +static void help_draw_const(void) +{ + draw_box(1, 12, 78, 45, BOX_THICK | BOX_INNER | BOX_INSET); + + if (status.dialog_type == DIALOG_NONE) change_focus_to(1); +} + +static void help_redraw(void) +{ + int n, pos, x; + const char **ptr; + + draw_fill_chars(2, 13, 77, 44, 0); + + ptr = newline_pointers + top_line; + for (pos = 13, n = top_line; pos < 45; pos++, n++) { + switch (**ptr) { + case ':': /* schism-only (drawn the same) */ + case '|': /* normal line */ + case '!': /* classic mode only */ + draw_text_len(*ptr + 1, + strcspn(*ptr + 1, "\015\012"), 2, + pos, 6, 0); + break; + case '#': /* hidden line */ + draw_text_len(*ptr + 1, + strcspn(*ptr + 1, "\015\012"), 2, + pos, 7, 0); + break; + case '%': /* separator line */ + for (x = 2; x < 78; x++) + draw_char(154, x, pos, 6, 0); + break; + default: /* ack! */ + fprintf(stderr, "unknown help line format %c\n", **ptr); + break; + } + ptr++; + } +} + +/* --------------------------------------------------------------------- */ +static void _help_close(void) +{ + set_page(status.previous_page); +} +static int help_handle_key(struct key_event * k) +{ + int new_line = top_line; + + if (status.dialog_type != DIALOG_NONE) return 0; + + if (k->mouse == 2) { + new_line--; + } else if (k->mouse == 3) { + new_line++; + + } else if (k->mouse) { + return 0; + } + switch (k->sym) { + case SDLK_ESCAPE: + if (!k->state) return 1; + set_page(status.previous_page); + return 1; + case SDLK_UP: + if (k->state) return 1; + new_line--; + break; + case SDLK_DOWN: + if (k->state) return 1; + new_line++; + break; + case SDLK_PAGEUP: + if (k->state) return 1; + new_line -= 32; + break; + case SDLK_PAGEDOWN: + if (k->state) return 1; + new_line += 32; + break; + case SDLK_HOME: + if (k->state) return 1; + new_line = 0; + break; + case SDLK_END: + if (k->state) return 1; + new_line = num_lines - 32; + break; + default: + if (k->mouse) { + if (k->state) return 1; + } else { + return 0; + } + } + + new_line = CLAMP(new_line, 0, num_lines - 32); + if (new_line != top_line) { + top_line = new_line; + status.flags |= NEED_UPDATE; + } + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* TODO | move all this crap to helptext.c + * TODO | (so it gets done for all the pages, all at once) */ +static void help_set_page(void) +{ + char *ptr; + int local_lines = 0, global_lines = 0, cur_line = 0; + int have_local_help = (status.current_help_index != HELP_GLOBAL); + + change_focus_to(1); + top_line = 0; + + /* how many lines? */ + global_lines = get_num_lines(help_text_pointers[HELP_GLOBAL]); + if (have_local_help) { + local_lines = get_num_lines(help_text_pointers + [status.current_help_index]); + num_lines = local_lines + global_lines + 5; + } else { + num_lines = global_lines + 2; + } + + /* allocate the array */ + if (newline_pointers) + free(newline_pointers); + newline_pointers = calloc(num_lines + 1, sizeof(char *)); + + /* page help text */ + if (have_local_help) { + ptr = help_text_pointers[status.current_help_index]; + while (local_lines--) { + if (status.flags & CLASSIC_MODE) { + if (ptr[0] != ':' && ptr[0] != '#') + newline_pointers[cur_line++] = ptr; + } else { + if (ptr[0] != '!') + newline_pointers[cur_line++] = ptr; + } + ptr = strpbrk(ptr, "\015\012"); + if (ptr[0] == 13 && ptr[1] == 10) + ptr += 2; + else + ptr++; + } + /* separator line */ + newline_pointers[cur_line++] = blank_line; + newline_pointers[cur_line++] = separator_line; + newline_pointers[cur_line++] = blank_line; + } else { + /* some padding at the top */ + newline_pointers[cur_line++] = blank_line; + } + + /* global help text */ + ptr = help_text_pointers[HELP_GLOBAL]; + while (global_lines--) { + if (status.flags & CLASSIC_MODE) { + if (ptr[0] != ':' && ptr[0] != '#') + newline_pointers[cur_line++] = ptr; + } else { + if (ptr[0] != '!') + newline_pointers[cur_line++] = ptr; + } + ptr = strpbrk(ptr, "\015\012"); + if (ptr[0] == 13 && ptr[1] == 10) + ptr += 2; + else + ptr++; + } + + newline_pointers[cur_line++] = blank_line; + if (have_local_help) { + newline_pointers[cur_line++] = separator_line; + } + + newline_pointers[cur_line] = NULL; + num_lines = cur_line; +} + +/* --------------------------------------------------------------------- */ + +void help_load_page(struct page *page) +{ + page->title = "Help"; + page->draw_const = help_draw_const; + page->set_page = help_set_page; + page->total_widgets = 2; + page->widgets = widgets_help; + page->pre_handle_key = help_handle_key; + + create_other(widgets_help + 0, 0, help_handle_key, help_redraw); + create_button(widgets_help + 1, 35,47,8, 0, 1, 1,1, 0, + _help_close, "Done", 3); +} diff --git a/schism/page_info.c b/schism/page_info.c new file mode 100644 index 000000000..c5fe7688f --- /dev/null +++ b/schism/page_info.c @@ -0,0 +1,1075 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "pattern-view.h" +#include "config-parser.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_info[1]; + +/* nonzero => use velocity bars */ +static int velocity_mode = 0; + +/* nonzero => instrument names */ +static int instrument_names = 0; + +/* --------------------------------------------------------------------- */ +/* window setup */ + +struct info_window_type { + void (*draw) (int base, int height, int active, int first_channel); + + /* if this is set, the first row contains actual text (not just the top part of a box) */ + int first_row; + + /* how many channels are shown -- just use 0 for windows that don't show specific channel info. + for windows that put the channels vertically (i.e. sample names) this should be the amount to ADD + to the height to get the number of channels, so it should be NEGATIVE. (example: the sample name + view uses the first position for the top of the box and the last position for the bottom, so it + uses -2.) confusing, almost to the point of being painful, but it works. (ok, i admit, it's not + the most brilliant idea i ever had ;) */ + int channels; +}; + +struct info_window { + int type; + int height; + int first_channel; +}; + +static int selected_window = 0; +static int num_windows = 3; +static int selected_channel = 1; + +/* five, because that's Impulse Tracker's maximum */ +#define MAX_WINDOWS 5 +static struct info_window windows[MAX_WINDOWS] = { + {0, 19, 1}, /* samples (18 channels displayed) */ + {8, 3, 1}, /* active channels */ + {5, 15, 1}, /* 24chn track view */ +}; + +/* --------------------------------------------------------------------- */ +/* the various stuff that can be drawn... */ +static void info_draw_technical(int base, int height, UNUSED int active, int first_channel) +{ + int smplist[100]; + int smp, pos, fg, c = first_channel; + song_sample *z; + char buf[16]; + char *ptr; + + draw_fill_chars(5, base + 1, 29, base + height - 2, 0); + draw_fill_chars(32, base + 1, 56, base + height - 2, 0); + draw_box(4, base, 30, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(31, base, 57, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + + if (song_is_instrument_mode()) { + draw_fill_chars(59, base + 1, 65, base + height - 2, 0); + draw_box(58, base, 66, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + draw_text((const unsigned char *)"NNA", 59, base, 2, 1); /* --- Cut Fde Con Off */ + draw_text((const unsigned char *)"Tot", 63, base, 2, 1); /* number of samples playing here */ + + song_get_playing_samples(smplist); + } + + draw_text((const unsigned char *)"Frequency",6, base, 2,1); + draw_text((const unsigned char *)"Position",17, base, 2,1); + draw_text((const unsigned char *)"Smp",27, base, 2,1); /* number */ + + draw_text((const unsigned char *)"FVI",32, base, 2,1); /* final volume (0-128 i think) */ + draw_text((const unsigned char *)"VI",36, base, 2,1); /* volume immediate? instrument? */ + draw_text((const unsigned char *)"CV",39, base, 2,1); /* channel volume */ + draw_text((const unsigned char *)"SV",42, base, 2,1); /* sample/set volume? (global volume from sample?) */ + draw_text((const unsigned char *)"VE",45, base, 2,1); /* volume end? (target volume?) */ + draw_text((const unsigned char *)"Fde",48, base, 2,1); /* fade 0-512 ; so int val /2 */ + draw_text((const unsigned char *)"Pn",52, base, 2,1); /* panning now */ + draw_text((const unsigned char *)"PE",55, base, 2,1); /* target pan (pan end?) */ + + + for (pos = base + 1; pos < base + height - 1; pos++, c++) { + song_mix_channel *channel = song_get_mix_channel(c - 1); + + if (c == selected_channel) { + fg = (channel->flags & CHN_MUTE) ? 6 : 3; + } else { + if (channel->flags & CHN_MUTE) + fg = 2; + else + fg = active ? 1 : 0; + } + draw_text(numtostr(2, c, buf), 2, pos, fg, 2); /* channel number */ + + sprintf(buf, "%10d", channel->sample_freq); + if (channel->sample_freq) { + draw_text((const unsigned char *)buf, 5, pos, 2, 0); + sprintf(buf, "%10d", channel->sample_pos); + draw_text((const unsigned char *)buf, 16, pos, 2, 0); + } + + if (channel->sample) + smp = channel->sample - song_get_sample(0, NULL); + else + smp = 0; + + if (smp) { + draw_text(numtostr(3, smp, buf), 27, pos, 2, 0); + + draw_text(numtostr(3, channel->final_volume / 128, buf), 32, pos, 2, 0); + draw_text(numtostr(2, channel->volume >> 2, buf), 36, pos, 2, 0); + + draw_text(numtostr(2, channel->nGlobalVol, buf), 39, pos, 2, 0); + draw_text(numtostr(2, channel->sample + ? channel->sample->global_volume : 64, buf), + 42, pos, 2, 0); + draw_text(numtostr(2, channel->nInsVol, buf), 45, pos, 2, 0); + + draw_text(numtostr(3, channel->nFadeOutVol / 128, buf), 48, pos, 2, 0); + + draw_text(numtostr(2, channel->panning >> 2, buf), 52, pos, 2, 0); + draw_text(numtostr(2, channel->final_panning >> 2, buf), 55, pos, 2, 0); + } + if (song_is_instrument_mode()) { + switch (channel->nNNA) { + case 1: ptr = "Cut"; break; + case 2: ptr = "Con"; break; + case 3: ptr = "Off"; break; + case 4: ptr = "Fde"; break; + default: ptr = "---"; break; + }; + draw_text((const unsigned char *)ptr, 59,pos,2,0); + draw_text(numtostr(3, smplist[smp], buf), 63, pos, 2, 0); + } + + draw_char(168, 15, pos, 2, 0); + draw_char(168, 26, pos, 2, 0); + draw_char(168, 35, pos, 2, 0); + draw_char(168, 38, pos, 2, 0); + draw_char(168, 41, pos, 2, 0); + draw_char(168, 44, pos, 2, 0); + draw_char(168, 47, pos, 2, 0); + draw_char(168, 51, pos, 2, 0); + draw_char(168, 54, pos, 2, 0); + + if (song_is_instrument_mode()) { + draw_char(168, 62, pos, 2, 0); + } + } +} + +static void info_draw_samples(int base, int height, UNUSED int active, int first_channel) +{ + int vu, smp, ins, n, pos, fg, c = first_channel; + char buf[8]; + char *ptr; + + draw_fill_chars(5, base + 1, 28, base + height - 2, 0); + draw_fill_chars(31, base + 1, 61, base + height - 2, 0); + + draw_box(4, base, 29, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(30, base, 62, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + if (song_is_stereo()) { + draw_fill_chars(64, base + 1, 72, base + height - 2, 0); + draw_box(63, base, 73, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + } else { + draw_fill_chars(63, base, 73, base + height, 2); + } + + /* FIXME: what about standalone sample playback? */ + if (song_get_mode() == MODE_STOPPED) { + for (pos = base + 1; pos < base + height - 1; pos++, c++) { + song_channel *channel = song_get_channel(c - 1); + + if (c == selected_channel) { + fg = (channel->flags & CHN_MUTE) ? 6 : 3; + } else { + if (channel->flags & CHN_MUTE) + continue; + fg = active ? 1 : 0; + } + draw_text(numtostr(2, c, buf), 2, pos, fg, 2); + } + return; + } + + for (pos = base + 1; pos < base + height - 1; pos++, c++) { + song_mix_channel *channel = song_get_mix_channel(c - 1); + + /* first box: vu meter */ + if (velocity_mode) + vu = channel->final_volume >> 8; + else + vu = channel->vu_meter >> 2; + if (channel->flags & CHN_MUTE) + draw_vu_meter(5, pos, 24, vu, 1, 2); + else + draw_vu_meter(5, pos, 24, vu, 5, 4); + + /* second box: sample number/name */ + ins = song_get_instrument_number(channel->instrument); + /* figuring out the sample number is an ugly hack... considering all the crap that's + copied to the channel, i'm surprised that the sample and instrument numbers aren't + in there somewhere... */ + if (channel->sample) + smp = channel->sample - song_get_sample(0, NULL); + else + smp = 0; + if (smp) { + draw_text(numtostr(2, smp, buf), 31, pos, 6, 0); + if (ins) { + draw_char('/', 33, pos, 6, 0); + draw_text(numtostr(2, ins, buf), 34, pos, 6, 0); + n = 36; + } else { + n = 33; + } + if (channel->volume == 0) + fg = 4; + else if (channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) + fg = 7; + else + fg = 6; + draw_char(':', n++, pos, fg, 0); + if (instrument_names && channel->instrument) + ptr = channel->instrument->name; + else + song_get_sample(smp, &ptr); + draw_text_len((const unsigned char *)ptr, 25, n, pos, 6, 0); + } + + /* last box: panning. this one's much easier than the + * other two, thankfully :) */ + if (song_is_stereo()) { + if (!channel->sample) { + /* nothing... */ + } else if (channel->flags & CHN_SURROUND) { + draw_text((const unsigned char *)"Surround", 64, pos, 2, 0); + } else if (channel->final_panning >> 2 == 0) { + draw_text((const unsigned char *)"Left", 64, pos, 2, 0); + } else if ((channel->final_panning + 3) >> 2 == 64) { + draw_text((const unsigned char *)"Right", 68, pos, 2, 0); + } else { + draw_thumb_bar(64, pos, 9, 0, 256, channel->final_panning, 0); + } + } + + /* finally, do the channel number */ + if (c == selected_channel) { + fg = (channel->flags & CHN_MUTE) ? 6 : 3; + } else { + if (channel->flags & CHN_MUTE) + continue; + fg = active ? 1 : 0; + } + draw_text(numtostr(2, c, buf), 2, pos, fg, 2); + } +} + +static void _draw_track_view(int base, int height, int first_channel, int num_channels, + int channel_width, int separator, draw_note_func draw_note) +{ + /* way too many variables */ + int current_row = song_get_current_row(); + int current_order = song_get_current_order(); + unsigned char *orderlist = song_get_orderlist(); + song_note *note; + song_note *cur_pattern, *prev_pattern, *next_pattern; + song_note *pattern; /* points to either {cur,prev,next}_pattern */ + int cur_pattern_rows = 0, prev_pattern_rows = 0, next_pattern_rows = 0; + int total_rows; /* same as {cur,prev_next}_pattern_rows */ + int chan_pos, row, row_pos, rows_before, rows_after; + char buf[4]; + + if (separator) + channel_width++; + + switch (song_get_mode()) { + case MODE_PATTERN_LOOP: + prev_pattern_rows = next_pattern_rows = cur_pattern_rows + = song_get_pattern(song_get_playing_pattern(), &cur_pattern); + prev_pattern = next_pattern = cur_pattern; + break; + case MODE_PLAYING: + if (orderlist[current_order] >= 200) { + /* this does, in fact, happen. just pretend that + * it's stopped :P */ + default: + /* stopped (or step?) */ + /* TODO: fill the area with blank dots */ + return; + } + cur_pattern_rows = song_get_pattern(orderlist[current_order], &cur_pattern); + if (current_order > 0 && orderlist[current_order - 1] < 200) + prev_pattern_rows = song_get_pattern(orderlist[current_order - 1], &prev_pattern); + else + prev_pattern = NULL; + if (current_order < 255 && orderlist[current_order + 1] < 200) + next_pattern_rows = song_get_pattern(orderlist[current_order + 1], &next_pattern); + else + next_pattern = NULL; + break; + } + + rows_before = (height - 2) / 2; + rows_after = rows_before; + if (height & 1) + rows_after++; + + /* draw the area above the current row */ + pattern = cur_pattern; + total_rows = cur_pattern_rows; + row = current_row - 1; + row_pos = base + rows_before; + while (row_pos > base) { + if (row < 0) { + if (prev_pattern == NULL) { + /* TODO: fill it with blank dots */ + break; + } + pattern = prev_pattern; + total_rows = prev_pattern_rows; + row = total_rows - 1; + } + draw_text(numtostr(3, row, buf), 1, row_pos, 0, 2); + note = pattern + 64 * row + first_channel - 1; + for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); + if (separator) + draw_char(168, (4 + channel_width * (chan_pos + 1)), row_pos, 2, 0); + note++; + } + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); + row--; + row_pos--; + } + + /* draw the current row */ + pattern = cur_pattern; + total_rows = cur_pattern_rows; + row_pos = base + rows_before + 1; + draw_text(numtostr(3, current_row, buf), 1, row_pos, 0, 2); + note = pattern + 64 * current_row + first_channel - 1; + for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 14); + if (separator) + draw_char(168, (4 + channel_width * (chan_pos + 1)), row_pos, 2, 14); + note++; + } + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 14); + + /* draw the area under the current row */ + row = current_row + 1; + row_pos++; + while (row_pos < base + height - 1) { + if (row >= total_rows) { + if (next_pattern == NULL) { + /* TODO: fill it with blank dots */ + break; + } + pattern = next_pattern; + total_rows = next_pattern_rows; + row = 0; + } + draw_text(numtostr(3, row, buf), 1, row_pos, 0, 2); + note = pattern + 64 * row + first_channel - 1; + for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); + if (separator) + draw_char(168, (4 + channel_width * (chan_pos + 1)), row_pos, 2, 0); + note++; + } + draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); + row++; + row_pos++; + } +} + +static void info_draw_track_5(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + + /* FIXME: once _draw_track_view draws the filler dots like it's + * supposed to, get rid of the draw_fill_chars here + * (and in all the other info_draw_track_ functions) */ + draw_fill_chars(5, base + 1, 73, base + height - 2, 0); + + draw_box(4, base, 74, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 5; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_channel_header_13(chan, 5 + 14 * chan_pos, base, fg); + } + _draw_track_view(base, height, first_channel, 5, 13, 1, draw_note_13); +} + +static void info_draw_track_10(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 74, base + height - 2, 0); + + draw_box(4, base, 75, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 10; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_char(0, 5 + 7 * chan_pos, base, 1, 1); + draw_char(0, 5 + 7 * chan_pos + 1, base, 1, 1); + draw_text(numtostr(2, chan, buf), 5 + 7 * chan_pos + 2, base, fg, 1); + draw_char(0, 5 + 7 * chan_pos + 4, base, 1, 1); + draw_char(0, 5 + 7 * chan_pos + 5, base, 1, 1); + } + _draw_track_view(base, height, first_channel, 10, 7, 0, draw_note_7); +} + +static void info_draw_track_12(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 76, base + height - 2, 0); + + draw_box(4, base, 77, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 12; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + /* draw_char(0, 5 + 6 * chan_pos, base, 1, 1); */ + draw_char(0, 5 + 6 * chan_pos + 1, base, 1, 1); + draw_text(numtostr(2, chan, buf), 5 + 6 * chan_pos + 2, base, fg, 1); + draw_char(0, 5 + 6 * chan_pos + 4, base, 1, 1); + /* draw_char(0, 5 + 6 * chan_pos + 5, base, 1, 1); */ + } + _draw_track_view(base, height, first_channel, 12, 6, 0, draw_note_6); +} + +static void info_draw_track_18(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 75, base + height - 2, 0); + + draw_box(4, base, 76, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 18; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_text(numtostr(2, chan, buf), 5 + 4 * chan_pos + 1, base, fg, 1); + } + _draw_track_view(base, height, first_channel, 18, 3, 1, draw_note_3); +} + +static void info_draw_track_24(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 76, base + height - 2, 0); + + draw_box(4, base, 77, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 24; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_text(numtostr(2, chan, buf), 5 + 3 * chan_pos + 1, base, fg, 1); + } + _draw_track_view(base, height, first_channel, 24, 3, 0, draw_note_3); +} + +static void info_draw_track_36(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + char buf[4]; + + draw_fill_chars(5, base + 1, 76, base + height - 2, 0); + + draw_box(4, base, 77, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 36; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 6 : 1); + else + fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); + draw_text(numtostr(2, chan, buf), 5 + 2 * chan_pos, base, fg, 1); + } + _draw_track_view(base, height, first_channel, 36, 2, 0, draw_note_2); +} + +static void info_draw_track_64(int base, int height, int active, int first_channel) +{ + int chan, chan_pos, fg; + /* IT draws nine more blank "channels" on the right */ + int nchan = (status.flags & CLASSIC_MODE) ? 73 : 64; + + assert(first_channel == 1); + + draw_fill_chars(5, base + 1, nchan + 4, base + height - 2, 0); + + draw_box(4, base, nchan + 5, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + for (chan = first_channel, chan_pos = 0; chan_pos < 64; chan++, chan_pos++) { + if (song_get_channel(chan - 1)->flags & CHN_MUTE) + fg = (chan == selected_channel ? 14 : 9); + else + fg = (chan == selected_channel ? 3 : (active ? 10 : 8)); + draw_half_width_chars(chan / 10 + '0', chan % 10 + '0', 5 + chan_pos, base, fg, 1, fg, 1); + } + for (; chan_pos < nchan; chan_pos++) + draw_char(0, 5 + chan_pos, base, 1, 1); + + /* TODO | fix _draw_track_view to accept values >64 for the number of + * TODO | channels to draw, and put empty dots in the extra channels. + * TODO | (would only be useful for this particular case) */ + /*_draw_track_view(base, height, first_channel, nchan, 1, 0, draw_note_1);*/ + _draw_track_view(base, height, first_channel, 64, 1, 0, draw_note_1); +} + +static void info_draw_channels(int base, UNUSED int height, int active, UNUSED int first_channel) +{ + char buf[32]; + int fg = (active ? 3 : 0); + + snprintf(buf, 32, "Active Channels: %d (%d)", song_get_playing_channels(), song_get_max_channels()); + draw_text((const unsigned char *)buf, 2, base, fg, 2); + + snprintf(buf, 32, "Global Volume: %d", song_get_current_global_volume()); + draw_text((const unsigned char *)buf, 4, base + 1, fg, 2); +} + +/* "Screw you guys, I'm going home." + * I can't figure this out... it sorta kinda works, but not really. + * If anyone wants to finish it: it's all yours. + * + * (update 06feb2004: still doesn't work. :P -- think I'll just wait until + * the Big Overhaul when I replace Modplug with my own player engine) */ + +static void info_draw_note_dots(int base, int height, int active, int first_channel) +{ + /* once this works, most of these variables can be optimized out (some of them are just used once) */ + int fg, v; + int c, pos; + int n; + song_mix_channel *channel; + unsigned long *channel_list; + char buf[4]; + byte d, dn; + /* f#2 -> f#8 = 73 columns */ + /* lower nybble = colour, upper nybble = size */ + byte dot_field[73][36] = { {0} }; + + draw_fill_chars(5, base + 1, 77, base + height - 2, 0); + + draw_box(4, base, 78, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); + + /* if it's stopped, just draw the channel numbers and a bunch of dots. */ + + n = song_get_mix_state(&channel_list); + while (n--) { + channel = song_get_mix_channel(channel_list[n]); + + /* 31 = f#2, 103 = f#8. (i hope ;) */ + /* channel->sample or channel->sample_data? */ + if (!(channel->sample && channel->note >= 31 + && channel->note <= 103)) + continue; + pos = channel->master_channel; + if (pos < first_channel) + continue; + pos -= first_channel; + if (pos > height - 1) + continue; + + fg = (channel->flags & CHN_MUTE) ? 1 : (channel->sample - song_get_sample(0, NULL)) % 4 + 2; + /* v = (channel->final_volume + 2047) >> 11; */ + v = (channel->vu_meter + 31) >> 5; + d = dot_field[channel->note - 31][pos]; + dn = (v << 4) | fg; + if (dn > d) + dot_field[channel->note - 31][pos] = dn; + } + + for (c = first_channel, pos = 0; pos < height - 2; pos++, c++) { + for (n = 0; n < 73; n++) { + d = dot_field[n][pos]; + + if (d == 0) { + /* stick a blank dot there */ + draw_char(193, n + 5, pos + base + 1, 2, 0); + continue; + } + fg = d & 0xf; + v = d >> 4; + /* btw: Impulse Tracker uses char 173 instead of 193. why? */ + draw_char(v + 193, n + 5, pos + base + 1, fg, 0); + } + + if (c == selected_channel) { + fg = (song_get_mix_channel(c - 1)->flags & CHN_MUTE) ? 6 : 3; + } else { + if (song_get_mix_channel(c - 1)->flags & CHN_MUTE) + continue; + fg = active ? 1 : 0; + } + draw_text(numtostr(2, c, buf), 2, pos + base + 1, fg, 2); + } +} + +/* --------------------------------------------------------------------- */ +/* declarations of the window types */ + +#define TRACK_VIEW(n) {info_draw_track_##n, 1, n} +static const struct info_window_type window_types[] = { + {info_draw_samples, 0, -2}, + TRACK_VIEW(5), + TRACK_VIEW(10), + TRACK_VIEW(12), + TRACK_VIEW(18), + TRACK_VIEW(24), + TRACK_VIEW(36), + TRACK_VIEW(64), + {info_draw_channels, 1, 0}, + {info_draw_note_dots, 0, -2}, + {info_draw_technical, 1, -3}, +}; +#undef TRACK_VIEW + +#define NUM_WINDOW_TYPES ARRAY_SIZE(window_types) + +/* --------------------------------------------------------------------- */ + +static void _fix_channels(int n) +{ + struct info_window *w = windows + n; + int channels = window_types[w->type].channels; + + if (channels == 0) + return; + + if (channels < 0) { + channels += w->height; + if (n == 0 && !(window_types[w->type].first_row)) { + /* crappy hack */ + channels++; + } + } + if (selected_channel < w->first_channel) + w->first_channel = selected_channel; + else if (selected_channel >= (w->first_channel + channels)) + w->first_channel = selected_channel - channels + 1; + w->first_channel = CLAMP(w->first_channel, 1, 65 - channels); +} + +static void recalculate_windows(void) +{ + int n, pos; + + pos = 13; + for (n = 0; n < num_windows - 1; n++) { + _fix_channels(n); + pos += windows[n].height; + if (pos > 50) { + /* Too big? Throw out the rest of the windows. */ + num_windows = n; + } + } + assert(num_windows > 0); + windows[n].height = 50 - pos; + _fix_channels(n); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* settings + * TODO: save all the windows in a single key, maybe comma-separated or something */ + +void cfg_save_info(cfg_file_t *cfg) +{ + char key[] = "windowX"; + int i; + + cfg_set_number(cfg, "Info Page", "num_windows", num_windows); + + for (i = 0; i < num_windows; i++) { + key[6] = i + '0'; + cfg_set_number(cfg, "Info Page", key, (windows[i].type << 8) | (windows[i].height)); + } +} + +void cfg_load_info(cfg_file_t *cfg) +{ + char key[] = "windowX"; + int i; + + num_windows = cfg_get_number(cfg, "Info Page", "num_windows", -1); + if (num_windows <= 0 || num_windows > MAX_WINDOWS) + num_windows = -1; + + for (i = 0; i < num_windows; i++) { + int tmp; + + key[6] = i + '0'; + tmp = cfg_get_number(cfg, "Info Page", key, -1); + if (tmp == -1) { + num_windows = -1; + break; + } + windows[i].type = tmp >> 8; + windows[i].height = tmp & 0xff; + if (windows[i].type < 0 || windows[i].type >= NUM_WINDOW_TYPES || windows[i].height < 3) { + /* Broken window? */ + num_windows = -1; + break; + } + } + /* last window's size < 3 lines? */ + + if (num_windows == -1) { + /* Fall back to defaults */ + num_windows = 3; + windows[0].type = 0; /* samples */ + windows[0].height = 19; + windows[1].type = 8; /* active channels */ + windows[1].height = 3; + windows[2].type = 5; /* 24chn track view */ + windows[2].height = 15; + } + + for (i = 0; i < num_windows; i++) + windows[i].first_channel = 1; + + recalculate_windows(); + if (status.current_page == PAGE_INFO) + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void info_page_redraw(void) +{ + int n, height, pos = (window_types[windows[0].type].first_row ? 13 : 12); + + for (n = 0; n < num_windows - 1; n++) { + height = windows[n].height; + if (pos == 12) + height++; + window_types[windows[n].type].draw(pos, height, (n == selected_window), + windows[n].first_channel); + pos += height; + } + /* the last window takes up all the rest of the screen */ + window_types[windows[n].type].draw(pos, 50 - pos, (n == selected_window), windows[n].first_channel); +} + +/* --------------------------------------------------------------------- */ + +static int info_page_handle_key(struct key_event * k) +{ + int n, order; + + /* hack to render this useful :) */ + if (k->orig_sym == SDLK_KP9) { + k->sym = SDLK_F9; + } else if (k->orig_sym == SDLK_KP0) { + k->sym = SDLK_F10; + } + + switch (k->sym) { + case SDLK_g: + if (!k->state) return 1; + + order = song_get_current_order(); + n = song_get_orderlist()[order]; + if (n < 200) { + set_current_order(order); + set_current_pattern(n); + set_current_row(song_get_current_row()); + set_page(PAGE_PATTERN_EDITOR); + } + return 1; + case SDLK_v: + if (k->state) return 1; + + velocity_mode = !velocity_mode; + status_text_flash("Using %s bars", (velocity_mode ? "velocity" : "volume")); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_i: + if (k->state) return 1; + + instrument_names = !instrument_names; + status_text_flash("Using %s names", (instrument_names ? "instrument" : "sample")); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_r: + if (k->mod & KMOD_ALT) { + if (k->state) return 1; + + song_flip_stereo(); + status_text_flash("Left/right outputs reversed"); + return 1; + } + return 0; + case SDLK_PLUS: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (song_get_mode() == MODE_PLAYING) { + song_set_current_order(song_get_current_order() + 1); + } + return 1; + case SDLK_MINUS: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (song_get_mode() == MODE_PLAYING) { + song_set_current_order(song_get_current_order() - 1); + } + return 1; + case SDLK_q: + if (k->state) return 1; + song_toggle_channel_mute(selected_channel - 1); + orderpan_recheck_muted_channels(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_s: + if (k->state) return 1; + + if (k->mod & KMOD_ALT) { + song_toggle_stereo(); + status_text_flash("Stereo %s", song_is_stereo() + ? "Enabled" : "Disabled"); + status.flags |= NEED_UPDATE; + return 1; + } + + song_handle_channel_solo(selected_channel - 1); + orderpan_recheck_muted_channels(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_SPACE: + if (!NO_MODIFIER(k->mod)) + return 0; + + if (k->state) return 1; + song_toggle_channel_mute(selected_channel - 1); + if (selected_channel < 64) + selected_channel++; + orderpan_recheck_muted_channels(); + break; + case SDLK_UP: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + /* make the current window one line shorter, and give the line to the next window + below it. if the window is already as small as it can get (3 lines) or if it's + the last window, don't do anything. */ + if (selected_window == num_windows - 1 || windows[selected_window].height == 3) { + return 1; + } + windows[selected_window].height--; + windows[selected_window + 1].height++; + break; + } + if (selected_channel > 1) + selected_channel--; + break; + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (selected_channel > 1) + selected_channel--; + break; + case SDLK_DOWN: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + /* expand the current window, taking a line from + * the next window down. BUT: don't do anything if + * (a) this is the last window, or (b) the next + * window is already as small as it can be (three + * lines). */ + if (selected_window == num_windows - 1 + || windows[selected_window + 1].height == 3) { + return 1; + } + windows[selected_window].height++; + windows[selected_window + 1].height--; + break; + } + if (selected_channel < 64) + selected_channel++; + break; + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (selected_channel < 64) + selected_channel++; + break; + case SDLK_HOME: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + selected_channel = 1; + break; + case SDLK_END: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + selected_channel = song_find_last_channel(); + break; + case SDLK_INSERT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + /* add a new window, unless there's already five (the maximum) + or if the current window isn't big enough to split in half. */ + if (num_windows == MAX_WINDOWS || (windows[selected_window].height < 6)) { + return 1; + } + + num_windows++; + + /* shift the windows under the current one down */ + memmove(windows + selected_window + 1, windows + selected_window, + ((num_windows - selected_window - 1) * sizeof(*windows))); + + /* split the height between the two windows */ + n = windows[selected_window].height; + windows[selected_window].height = n / 2; + windows[selected_window + 1].height = n / 2; + if ((n & 1) && num_windows != 2) { + /* odd number? compensate. (the selected window gets the extra line) */ + windows[selected_window + 1].height++; + } + break; + case SDLK_DELETE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + /* delete the current window and give the extra space to the next window down. + if this is the only window, well then don't delete it ;) */ + if (num_windows == 1) + return 1; + + n = windows[selected_window].height + windows[selected_window + 1].height; + + /* shift the windows under the current one up */ + memmove(windows + selected_window, windows + selected_window + 1, + ((num_windows - selected_window - 1) * sizeof(*windows))); + + /* fix the current window's height */ + windows[selected_window].height = n; + + num_windows--; + if (selected_window == num_windows) + selected_window--; + break; + case SDLK_PAGEUP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + n = windows[selected_window].type; + if (n == 0) + n = NUM_WINDOW_TYPES; + n--; + windows[selected_window].type = n; + break; + case SDLK_PAGEDOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + windows[selected_window].type = (windows[selected_window].type + 1) % NUM_WINDOW_TYPES; + break; + case SDLK_TAB: + if (k->state) return 1; + if (k->mod & KMOD_SHIFT) { + if (selected_window == 0) + selected_window = num_windows; + selected_window--; + } else { + selected_window = (selected_window + 1) % num_windows; + } + status.flags |= NEED_UPDATE; + return 1; + case SDLK_F9: + if (k->state) return 1; + if (k->mod & KMOD_ALT) { + song_toggle_channel_mute(selected_channel - 1); + orderpan_recheck_muted_channels(); + return 1; + } + return 0; + case SDLK_F10: + if (k->mod & KMOD_ALT) { + if (k->state) return 1; + song_handle_channel_solo(selected_channel - 1); + orderpan_recheck_muted_channels(); + return 1; + } + return 0; + default: + return 0; + } + + recalculate_windows(); + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void info_page_playback_update(void) +{ + /* this will need changed after sample playback is working... */ + if (song_get_mode() != MODE_STOPPED) + status.flags |= NEED_UPDATE; +} +static void info_page_set(void) +{ + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +void info_load_page(struct page *page) +{ + page->title = "Info Page (F5)"; + page->playback_update = info_page_playback_update; + page->total_widgets = 1; + page->widgets = widgets_info; + page->help_index = HELP_INFO_PAGE; + page->set_page = info_page_set; + + create_other(widgets_info + 0, 0, info_page_handle_key, info_page_redraw); +} diff --git a/schism/page_instruments.c b/schism/page_instruments.c new file mode 100644 index 000000000..ce58f3bd5 --- /dev/null +++ b/schism/page_instruments.c @@ -0,0 +1,2422 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is getting almost as disturbing as the pattern editor. */ + +#include "headers.h" + +#include "clippy.h" +#include "song.h" + +#include + +#include "video.h" + +/* rastops for envelope */ +static struct vgamem_overlay env_overlay = { + 32, 18, 65, 25, +}; + +/* --------------------------------------------------------------------- */ +/* just one global variable... */ + +int instrument_list_subpage = PAGE_INSTRUMENT_LIST_GENERAL; + +/* --------------------------------------------------------------------- */ +/* ... but tons o' ugly statics */ + +static struct widget widgets_general[18]; +static struct widget widgets_volume[17]; +static struct widget widgets_panning[19]; +static struct widget widgets_pitch[20]; + +static int subpage_switches_group[5] = { 1, 2, 3, 4, -1 }; +static int nna_group[5] = { 6, 7, 8, 9, -1 }; +static int dct_group[5] = { 10, 11, 12, 13, -1 }; +static int dca_group[4] = { 14, 15, 16, -1 }; + +static const char *pitch_envelope_states[] = { "Off", "On Pitch", "On Filter", NULL }; + +static int top_instrument = 1; +static int current_instrument = 1; +static int instrument_cursor_pos = 25; /* "play" mode */ + +static int note_trans_top_line = 0; +static int note_trans_sel_line = 0; + +static int note_trans_cursor_pos = 0; + +/* shared by all the numentries on a page + * (0 = volume, 1 = panning, 2 = pitch) */ +static int numentry_cursor_pos[3] = { 0 }; + +static int current_node_vol = 0; +static int current_node_pan = 0; +static int current_node_pitch = 0; + +static int envelope_edit_mode = 0; +static int envelope_mouse_edit = 0; +static int envelope_tick_limit = 0; + +/* playback */ +static int last_note = 61; /* C-5 */ + +/* --------------------------------------------------------------------------------------------------------- */ + +static void instrument_list_draw_list(void); + +/* --------------------------------------------------------------------------------------------------------- */ +/* the actual list */ + +static void instrument_list_reposition(void) +{ + if (current_instrument < top_instrument) { + top_instrument = current_instrument; + if (top_instrument < 1) { + top_instrument = 1; + } + } else if (current_instrument > top_instrument + 34) { + top_instrument = current_instrument - 34; + } +} + +int instrument_get_current(void) +{ + return current_instrument; +} + +void instrument_set(int n) +{ + int new_ins = n; + song_instrument *ins; + + if (page_is_instrument_list(status.current_page)) { + new_ins = CLAMP(n, 1, 99); + } else { + new_ins = CLAMP(n, 0, 99); + } + + if (current_instrument == new_ins) + return; + + envelope_edit_mode = 0; + status.flags = (status.flags & ~SAMPLE_CHANGED) | INSTRUMENT_CHANGED; + current_instrument = new_ins; + instrument_list_reposition(); + + ins = song_get_instrument(current_instrument, NULL); + + current_node_vol = ins->vol_env.nodes ? CLAMP(current_node_vol, 0, ins->vol_env.nodes - 1) : 0; + current_node_pan = ins->pan_env.nodes ? CLAMP(current_node_vol, 0, ins->pan_env.nodes - 1) : 0; + current_node_pitch = ins->pitch_env.nodes ? CLAMP(current_node_vol, 0, ins->pan_env.nodes - 1) : 0; + + status.flags |= NEED_UPDATE; +} + +void instrument_synchronize_to_sample(void) +{ + song_instrument *ins; + int sample = sample_get_current(); + int n, pos; + + /* 1. if the instrument with the same number as the current sample + * has the sample in its sample_map, change to that instrument. */ + ins = song_get_instrument(sample, NULL); + for (pos = 0; pos < 120; pos++) { + if ((ins->sample_map[pos]) == sample) { + instrument_set(sample); + return; + } + } + + /* 2. look through the instrument list for the first instrument + * that uses the selected sample. */ + for (n = 1; n < 100; n++) { + if (n == sample) + continue; + ins = song_get_instrument(n, NULL); + for (pos = 0; pos < 120; pos++) { + if ((ins->sample_map[pos]) == sample) { + instrument_set(n); + return; + } + } + } + + /* 3. if no instruments are using the sample, just change to the + * same-numbered instrument. */ + instrument_set(sample); +} + +/* --------------------------------------------------------------------- */ + +static int instrument_list_add_char(char c) +{ + char *name; + + if (c < 32) + return 0; + song_get_instrument(current_instrument, &name); + text_add_char(name, c, &instrument_cursor_pos, 25); + if (instrument_cursor_pos == 25) + instrument_cursor_pos--; + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; + return 1; +} + +static void instrument_list_delete_char(void) +{ + char *name; + song_get_instrument(current_instrument, &name); + text_delete_char(name, &instrument_cursor_pos, 25); + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +static void instrument_list_delete_next_char(void) +{ + char *name; + song_get_instrument(current_instrument, &name); + text_delete_next_char(name, &instrument_cursor_pos, 25); + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +static void clear_instrument_text(void) +{ + char *name; + + memset(song_get_instrument(current_instrument, &name)->filename, 0, 14); + memset(name, 0, 26); + if (instrument_cursor_pos != 25) + instrument_cursor_pos = 0; + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +/* --------------------------------------------------------------------- */ + +static struct widget swap_instrument_widgets[6]; +static char swap_instrument_entry[4] = ""; + + +static void do_swap_instrument(UNUSED void *data) +{ + int n = atoi(swap_instrument_entry); + + if (n < 1 || n > 99) + return; + song_swap_instruments(current_instrument, n); +} + +static void swap_instrument_draw_const(void) +{ + draw_text((const unsigned char *)"Swap instrument with:", 29, 25, 0, 2); + draw_text((const unsigned char *)"Instrument", 31, 27, 0, 2); + draw_box(41, 26, 45, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void swap_instrument_dialog(void) +{ + struct dialog *dialog; + + swap_instrument_entry[0] = 0; + create_textentry(swap_instrument_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, swap_instrument_entry, 2); + create_button(swap_instrument_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(26, 23, 29, 10, swap_instrument_widgets, 2, 0, + swap_instrument_draw_const, NULL); + dialog->action_yes = do_swap_instrument; +} + + +static void do_exchange_instrument(UNUSED void *data) +{ + int n = atoi(swap_instrument_entry); + + if (n < 1 || n > 99) + return; + song_exchange_instruments(current_instrument, n); +} + +static void exchange_instrument_draw_const(void) +{ + draw_text((const unsigned char *)"Exchange instrument with:", 28, 25, 0, 2); + draw_text((const unsigned char *)"Instrument", 31, 27, 0, 2); + draw_box(41, 26, 45, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void exchange_instrument_dialog(void) +{ + struct dialog *dialog; + + swap_instrument_entry[0] = 0; + create_textentry(swap_instrument_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, swap_instrument_entry, 2); + create_button(swap_instrument_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(26, 23, 29, 10, swap_instrument_widgets, 2, 0, + exchange_instrument_draw_const, NULL); + dialog->action_yes = do_exchange_instrument; +} + +/* --------------------------------------------------------------------- */ + +static void instrument_list_draw_list(void) +{ + int pos, n; + song_instrument *ins; + int selected = (ACTIVE_PAGE.selected_widget == 0); + int is_current; + int ss, cl, cr; + int is_playing[100]; + byte buf[4]; + + if (clippy_owner(CLIPPY_SELECT) == widgets_general) { + cl = widgets_general[0].clip_start % 25; + cr = widgets_general[0].clip_end % 25; + if (cl > cr) { + ss = cl; + cl = cr; + cr = ss; + } + ss = (widgets_general[0].clip_start / 25); + } else { + ss = -1; + } + + song_get_playing_instruments(is_playing); + + for (pos = 0, n = top_instrument; pos < 35; pos++, n++) { + ins = song_get_instrument(n, NULL); + is_current = (n == current_instrument); + + if (ins->played) + draw_char(173, 1, 13 + pos, is_playing[n] ? 3 : 1, 2); + + draw_text(numtostr(2, n, buf), 2, 13 + pos, 0, 2); + if (instrument_cursor_pos < 25) { + /* it's in edit mode */ + if (is_current) { + draw_text_len((const unsigned char *)ins->name, 25, 5, 13 + pos, 6, 14); + if (selected) { + draw_char(ins->name[instrument_cursor_pos], + 5 + instrument_cursor_pos, + 13 + pos, 0, 3); + } + } else { + draw_text_len((const unsigned char *)ins->name, 25, 5, 13 + pos, 6, 0); + } + } else { + draw_text_len((const unsigned char *)ins->name, 25, 5, 13 + pos, + ((is_current && selected) ? 0 : 6), + (is_current ? (selected ? 3 : 14) : 0)); + } + if (ss == n) { + draw_text_len((const unsigned char *)ins->name + cl, (cr-cl)+1, + 5 + cl, 13 + pos, + (is_current ? 3 : 11), 8); + } + } +} + +static int instrument_list_handle_key_on_list(struct key_event * k) +{ + int new_ins = current_instrument; + char *name; + + if (!k->state && k->mouse && k->y >= 13 && k->y <= 47 && k->x >= 5 && k->x <= 30) { + if (k->mouse == MOUSE_CLICK) { + if (k->state || k->sy == k->y) { + new_ins = (k->y - 13) + top_instrument; + } else { + } + if (new_ins == current_instrument) { + instrument_cursor_pos = k->x - 5; + status.flags |= NEED_UPDATE; + if (instrument_cursor_pos > 25) instrument_cursor_pos = 25; + } + } else if (k->mouse == MOUSE_DBLCLICK) { + if (instrument_cursor_pos < 25) { + instrument_cursor_pos = 25; + } else { + set_page(PAGE_LOAD_INSTRUMENT); + } + status.flags |= NEED_UPDATE; + return 1; + + } else if (k->mouse == MOUSE_SCROLL_UP) { + top_instrument--; + if (top_instrument < 1) top_instrument = 1; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + top_instrument++; + if (top_instrument > (99-34)) top_instrument = 99-34; + status.flags |= NEED_UPDATE; + return 1; + } + } else { + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_ins--; + break; + case SDLK_DOWN: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_ins++; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) + new_ins = 1; + else + new_ins -= 16; + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) + new_ins = 99; + else + new_ins += 16; + break; + case SDLK_HOME: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (instrument_cursor_pos < 25) { + instrument_cursor_pos = 0; + status.flags |= NEED_UPDATE; + } + return 1; + case SDLK_END: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (instrument_cursor_pos < 24) { + instrument_cursor_pos = 24; + status.flags |= NEED_UPDATE; + } + return 1; + case SDLK_LEFT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (instrument_cursor_pos < 25 && instrument_cursor_pos > 0) { + instrument_cursor_pos--; + status.flags |= NEED_UPDATE; + } + return 1; + case SDLK_RIGHT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (instrument_cursor_pos == 25) { + change_focus_to(1); + } else if (instrument_cursor_pos < 24) { + instrument_cursor_pos++; + status.flags |= NEED_UPDATE; + } + return 1; + case SDLK_RETURN: + if (!k->state) return 0; + if (instrument_cursor_pos < 25) { + instrument_cursor_pos = 25; + status.flags |= NEED_UPDATE; + } else { + set_page(PAGE_LOAD_INSTRUMENT); + } + return 1; + case SDLK_ESCAPE: + if (!k->state) return 0; + if (instrument_cursor_pos < 25) { + instrument_cursor_pos = 25; + status.flags |= NEED_UPDATE; + return 1; + } + return 0; + case SDLK_BACKSPACE: + if (k->state) return 0; + if (instrument_cursor_pos == 25) + return 0; + if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0) + instrument_list_delete_char(); + else if (k->mod & KMOD_CTRL) + instrument_list_add_char(127); + return 1; + case SDLK_INSERT: + if (k->state) return 0; + if (k->mod & KMOD_ALT) { + song_insert_instrument_slot(current_instrument); + status.flags |= NEED_UPDATE; + return 1; + } + return 0; + case SDLK_DELETE: + if (k->state) return 0; + if (k->mod & KMOD_ALT) { + song_remove_instrument_slot(current_instrument); + status.flags |= NEED_UPDATE; + return 1; + } else if ((k->mod & KMOD_CTRL) == 0) { + if (instrument_cursor_pos == 25) + return 0; + instrument_list_delete_next_char(); + return 1; + } + return 0; + default: + if (k->state) return 0; + if (k->mod & KMOD_ALT) { + if (k->sym == SDLK_c) { + clear_instrument_text(); + return 1; + } + } else if ((k->mod & KMOD_CTRL) == 0) { + char c = unicode_to_ascii(k->unicode); + if (c == 0) + return 0; + + if (instrument_cursor_pos < 25) { + return instrument_list_add_char(c); + } else if (k->sym == SDLK_SPACE) { + instrument_cursor_pos = 0; + status.flags |= NEED_UPDATE; + memused_songchanged(); + return 1; + } + } + return 0; + }; + } + + new_ins = CLAMP(new_ins, 1, 99); + if (new_ins != current_instrument) { + instrument_set(new_ins); + status.flags |= NEED_UPDATE; + memused_songchanged(); + } + + if (k->mouse && k->x != k->sx) { + song_get_instrument(current_instrument, &name); + widgets_general[0].clip_start = (k->sx - 5) + (current_instrument*25); + widgets_general[0].clip_end = (k->x - 5) + (current_instrument*25); + if (widgets_general[0].clip_start < widgets_general[0].clip_end) { + clippy_select(widgets_general, + name + (k->sx - 5), + (k->x - k->sx)); + } else { + clippy_select(widgets_general, + name + (k->x - 5), + (k->sx - k->x)); + } + } + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* note translation table */ + +static void note_trans_reposition(void) +{ + if (note_trans_sel_line < note_trans_top_line) { + note_trans_top_line = note_trans_sel_line; + } else if (note_trans_sel_line > note_trans_top_line + 31) { + note_trans_top_line = note_trans_sel_line - 31; + } +} + +static void note_trans_draw(void) +{ + int pos, n; + int is_selected = (ACTIVE_PAGE.selected_widget == 5); + int bg, sel_bg = (is_selected ? 14 : 0); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + byte buf[4]; + + for (pos = 0, n = note_trans_top_line; pos < 32; pos++, n++) { + bg = ((n == note_trans_sel_line) ? sel_bg : 0); + + /* invalid notes are translated to themselves (and yes, this edits the actual instrument) */ + if (ins->note_map[n] < 1 || ins->note_map[n] > 120) + ins->note_map[n] = n + 1; + + draw_text(get_note_string(n + 1, buf), 32, 16 + pos, 2, bg); + draw_char(168, 35, 16 + pos, 2, bg); + draw_text(get_note_string(ins->note_map[n], buf), 36, 16 + pos, 2, bg); + if (is_selected && n == note_trans_sel_line) { + if (note_trans_cursor_pos == 0) + draw_char(buf[0], 36, 16 + pos, 0, 3); + else if (note_trans_cursor_pos == 1) + draw_char(buf[2], 38, 16 + pos, 0, 3); + } + draw_char(0, 39, 16 + pos, 2, bg); + if (ins->sample_map[n]) { + numtostr(2, ins->sample_map[n], buf); + } else { + buf[0] = buf[1] = 173; + buf[2] = 0; + } + draw_text((const unsigned char *)buf, 40, 16 + pos, 2, bg); + if (is_selected && n == note_trans_sel_line) { + if (note_trans_cursor_pos == 2) + draw_char(buf[0], 40, 16 + pos, 0, 3); + else if (note_trans_cursor_pos == 3) + draw_char(buf[1], 41, 16 + pos, 0, 3); + } + } +} + +static int note_trans_handle_alt_key(struct key_event * k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int n, s; + + if (k->state) return 0; + switch (k->sym) { + case SDLK_a: + s = sample_get_current(); + for (n = 0; n < 120; n++) + ins->sample_map[n] = s; + break; + default: + return 0; + } + + status.flags |= NEED_UPDATE; + memused_songchanged(); + return 1; +} + +static int note_trans_handle_key(struct key_event * k) +{ + int prev_line = note_trans_sel_line; + int new_line = prev_line; + int prev_pos = note_trans_cursor_pos; + int new_pos = prev_pos; + song_instrument *ins = song_get_instrument(current_instrument, NULL); + char c; + int n; + + if (k->mouse == MOUSE_CLICK && k->mouse_button == MOUSE_BUTTON_MIDDLE) { + if (k->state) status.flags |= CLIPPY_PASTE_SELECTION; + return 1; + } else if (k->mouse) { + if (k->x >= 32 && k->x <= 41 && k->y >= 16 && k->y <= 47) { + new_line = k->y - 16; + if (new_line == prev_line) { + switch (k->x - 36) { + case 2: + new_pos = 1; + break; + case 4: + new_pos = 2; + break; + case 5: + new_pos = 3; + break; + default: + new_pos = 0; + break; + }; + } + } + } else { + if (k->mod & KMOD_ALT) + return note_trans_handle_alt_key(k); + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + if (--new_line < 0) { + change_focus_to(1); + return 1; + } + break; + case SDLK_DOWN: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_line++; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + instrument_set(current_instrument - 1); + return 1; + } + new_line -= 16; + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + instrument_set(current_instrument + 1); + return 1; + } + new_line += 16; + break; + case SDLK_HOME: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_line = 0; + break; + case SDLK_END: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_line = 119; + break; + case SDLK_LEFT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_pos--; + break; + case SDLK_RIGHT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_pos++; + break; + case SDLK_RETURN: + if (!k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + sample_set(ins->sample_map[note_trans_sel_line]); + return 1; + case SDLK_LESS: + if (k->state) return 0; + sample_set(sample_get_current() - 1); + return 1; + case SDLK_GREATER: + if (k->state) return 0; + sample_set(sample_get_current() + 1); + return 1; + + default: + if (k->state) return 0; + switch (note_trans_cursor_pos) { + case 0: /* note */ + if (k->sym == SDLK_1) { + ins->sample_map[note_trans_sel_line] = 0; + sample_set(0); + new_line++; + break; + } + n = kbd_get_note(k); + if (n <= 0 || n > 120) + return 0; + ins->note_map[note_trans_sel_line] = n; + ins->sample_map[note_trans_sel_line] = sample_get_current(); + new_line++; + break; + case 1: /* octave */ + c = kbd_char_to_hex(k); + if (c < 0 || c > 9) return 0; + n = ins->note_map[note_trans_sel_line]; + n = ((n - 1) % 12) + (12 * c) + 1; + ins->note_map[note_trans_sel_line] = n; + new_line++; + break; + case 2: /* instrument, first digit */ + case 3: /* instrument, second digit */ + if (k->sym == SDLK_SPACE) { + ins->sample_map[note_trans_sel_line] = + sample_get_current(); + new_line++; + break; + } + c = kbd_char_to_hex(k); + if (c < 0 || c > 9) return 0; + n = ins->sample_map[note_trans_sel_line]; + if (note_trans_cursor_pos == 2) { + n = (c * 10) + (n % 10); + new_pos++; + } else { + n = ((n / 10) * 10) + c; + new_pos--; + new_line++; + } + ins->sample_map[note_trans_sel_line] = n; + sample_set(n); + break; + } + break; + } + } + + new_line = CLAMP(new_line, 0, 119); + note_trans_cursor_pos = CLAMP(new_pos, 0, 3); + if (new_line != prev_line) { + note_trans_sel_line = new_line; + note_trans_reposition(); + } + + /* this causes unneeded redraws in some cases... oh well :P */ + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* envelope helper functions */ + +static void _env_draw_axes(int middle) +{ + int n, y = middle ? 31 : 62; + for (n = 0; n < 64; n += 2) + vgamem_font_putpixel(&env_overlay, 3, n); + for (n = 0; n < 256; n += 2) + vgamem_font_putpixel(&env_overlay, 1 + n, y); +} + +static void _env_draw_node(int x, int y, int on) +{ +#if 0 + /* FIXME: the lines draw over the nodes. This doesn't matter unless the color is different. */ + int c = (status.flags & CLASSIC_MODE) ? 12 : 5; +#endif + + vgamem_font_putpixel(&env_overlay, x - 1, y - 1); + vgamem_font_putpixel(&env_overlay, x - 1, y); + vgamem_font_putpixel(&env_overlay, x - 1, y + 1); + + vgamem_font_putpixel(&env_overlay, x, y - 1); + vgamem_font_putpixel(&env_overlay, x, y); + vgamem_font_putpixel(&env_overlay, x, y + 1); + + vgamem_font_putpixel(&env_overlay, x + 1, y - 1); + vgamem_font_putpixel(&env_overlay, x + 1, y); + vgamem_font_putpixel(&env_overlay, x + 1, y + 1); + + if (on) { + vgamem_font_putpixel(&env_overlay, x - 3, y - 1); + vgamem_font_putpixel(&env_overlay, x - 3, y); + vgamem_font_putpixel(&env_overlay, x - 3, y + 1); + + vgamem_font_putpixel(&env_overlay, x + 3, y - 1); + vgamem_font_putpixel(&env_overlay, x + 3, y); + vgamem_font_putpixel(&env_overlay, x + 3, y + 1); + } +} + +static void _env_draw_loop(int xs, int xe, int sustain) +{ + int y = 0; +#if 0 + int c = (status.flags & CLASSIC_MODE) ? 12 : 3; +#endif + + if (sustain) { + while (y < 62) { + /* unrolled once */ + vgamem_font_putpixel(&env_overlay, xs, y); + vgamem_font_putpixel(&env_overlay, xe, y); y++; + vgamem_font_clearpixel(&env_overlay, xs, y); + vgamem_font_clearpixel(&env_overlay, xe, y); y++; + vgamem_font_putpixel(&env_overlay, xs, y); + vgamem_font_putpixel(&env_overlay, xe, y); y++; + vgamem_font_clearpixel(&env_overlay, xs, y); + vgamem_font_clearpixel(&env_overlay, xe, y); y++; + } + } else { + while (y < 62) { + vgamem_font_clearpixel(&env_overlay, xs, y); + vgamem_font_clearpixel(&env_overlay, xe, y); y++; + vgamem_font_putpixel(&env_overlay, xs, y); + vgamem_font_putpixel(&env_overlay, xe, y); y++; + vgamem_font_putpixel(&env_overlay, xs, y); + vgamem_font_putpixel(&env_overlay, xe, y); y++; + vgamem_font_clearpixel(&env_overlay, xs, y); + vgamem_font_clearpixel(&env_overlay, xe, y); y++; + } + } +} + +static void _env_draw(const song_envelope *env, int middle, int current_node, + int env_on, int loop_on, int sustain_on, int env_num) +{ + song_mix_channel *channel; + unsigned long *channel_list; + byte buf[16]; + unsigned long envpos[3]; + int x, y, n, m, c; + int last_x = 0, last_y = 0; + int max_ticks = 50; + + while (env->ticks[env->nodes - 1] >= max_ticks) + max_ticks *= 2; + + vgamem_clear_reserve(&env_overlay); + + /* draw the axis lines */ + _env_draw_axes(middle); + + for (n = 0; n < env->nodes; n++) { + x = 4 + env->ticks[n] * 256 / max_ticks; + + /* 65 values are being crammed into 62 pixels => have to lose three pixels somewhere. + * This is where IT compromises -- I don't quite get how the lines are drawn, though, + * because it changes for each value... (apart from drawing 63 and 64 the same way) */ + y = env->values[n]; + if (y > 63) y--; + if (y > 42) y--; + if (y > 21) y--; + y = 62 - y; + + _env_draw_node(x, y, n == current_node); + + if (last_x) + vgamem_font_drawline(&env_overlay, last_x, last_y, x, y); + + last_x = x; + last_y = y; + } + + if (sustain_on) + _env_draw_loop(4 + env->ticks[env->sustain_start] * 256 / max_ticks, + 4 + env->ticks[env->sustain_end] * 256 / max_ticks, 1); + if (loop_on) + _env_draw_loop(4 + env->ticks[env->loop_start] * 256 / max_ticks, + 4 + env->ticks[env->loop_end] * 256 / max_ticks, 0); + + if (env_on) { + max_ticks = env->ticks[env->nodes-1]; + m = song_get_mix_state(&channel_list); + while (m--) { + channel = song_get_mix_channel(channel_list[m]); + if (channel->instrument + != song_get_instrument(current_instrument,NULL)) + continue; + + envpos[0] = channel->nVolEnvPosition; + envpos[1] = channel->nPanEnvPosition; + envpos[2] = channel->nPitchEnvPosition; + + x = 4 + (envpos[env_num] * (last_x-4) / max_ticks); + if (x > last_x) x = last_x; +#if 0 + c = (channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) ? 8 : 6; +#endif + for (y = 0; y < 62; y++) + vgamem_font_putpixel(&env_overlay, x, y); + } + } + + draw_fill_chars(65, 18, 76, 25, 0); + vgamem_fill_reserve(&env_overlay, 3, 0); + + sprintf(buf, "Node %d/%d", current_node, env->nodes); + draw_text((const unsigned char *)buf, 66, 19, 2, 0); + sprintf(buf, "Tick %d", env->ticks[current_node]); + draw_text((const unsigned char *)buf, 66, 21, 2, 0); + sprintf(buf, "Value %d", env->values[current_node] - (middle ? 32 : 0)); + draw_text((const unsigned char *)buf, 66, 23, 2, 0); +} + +/* return: the new current node */ +static int _env_node_add(song_envelope *env, int current_node, int override_tick, int override_value) +{ + int newtick, newvalue; + + status.flags |= SONG_NEEDS_SAVE; + + /* is 24 the right number here, or 25? */ + if (env->nodes > 24 || current_node == env->nodes - 1) + return current_node; + + newtick = (env->ticks[current_node] + env->ticks[current_node + 1]) / 2; + newvalue = (env->values[current_node] + env->values[current_node + 1]) / 2; + if (override_tick > -1 && override_value > -1) { + newtick = override_tick; + newvalue = override_value; + + } else if (newtick == env->ticks[current_node] || newtick == env->ticks[current_node + 1]) { + /* If the current node is at (for example) tick 30, and the next node is at tick 32, + * is there any chance of a rounding error that would make newtick 30 instead of 31? + * ("Is there a chance the track could bend?") */ + printf("Not enough room!\n"); + return current_node; + } + + env->nodes++; + memmove(env->ticks + current_node + 1, env->ticks + current_node, + (env->nodes - current_node - 1) * sizeof(env->ticks[0])); + memmove(env->values + current_node + 1, env->values + current_node, + (env->nodes - current_node - 1) * sizeof(env->values[0])); + env->ticks[current_node + 1] = newtick; + env->values[current_node + 1] = newvalue; + if (env->loop_end > current_node) env->loop_end++; + if (env->loop_start > current_node) env->loop_start++; + if (env->sustain_end > current_node) env->sustain_end++; + if (env->sustain_start > current_node) env->sustain_start++; + + return current_node; +} + +/* return: the new current node */ +static int _env_node_remove(song_envelope *env, int current_node) +{ + status.flags |= SONG_NEEDS_SAVE; + + if (current_node == 0 || env->nodes < 3) + return current_node; + + memmove(env->ticks + current_node, env->ticks + current_node + 1, + (env->nodes - current_node - 1) * sizeof(env->ticks[0])); + memmove(env->values + current_node, env->values + current_node + 1, + (env->nodes - current_node - 1) * sizeof(env->values[0])); + env->nodes--; + if (env->loop_end > current_node) env->loop_end--; + if (env->loop_start > current_node) env->loop_start--; + if (env->sustain_end > current_node) env->sustain_end--; + if (env->sustain_start > current_node) env->sustain_start--; + + if (current_node >= env->nodes) + current_node = env->nodes - 1; + + return current_node; +} + +static void do_pre_loop_cut(void *ign) +{ + song_envelope *env = (song_envelope *)ign; + unsigned int bt; + int i; + bt = env->ticks[env->loop_start]; + for (i = env->loop_start; i < 32; i++) { + env->ticks[i - env->loop_start] = env->ticks[i] - bt; + env->values[i - env->loop_start] = env->values[i]; + } + env->nodes -= env->loop_start; + if (env->sustain_start > env->loop_start) { + env->sustain_start -= env->loop_start; + } else { + env->sustain_start = 0; + } + if (env->sustain_end > env->loop_start) { + env->sustain_end -= env->loop_start; + } else { + env->sustain_end = 0; + } + if (env->loop_end > env->loop_start) { + env->loop_end -= env->loop_start; + } else { + env->loop_end = 0; + } + env->loop_start = 0; + if (env->loop_start > env->loop_end) + env->loop_end = env->loop_start; + if (env->sustain_start > env->sustain_end) + env->sustain_end = env->sustain_start; + status.flags |= NEED_UPDATE; +} +static void do_post_loop_cut(void *ign) +{ + song_envelope *env = (song_envelope *)ign; + env->nodes = env->loop_end+1; +} +/* the return value here is actually a bitmask: +r & 1 => the key was handled +r & 2 => the envelope changed (i.e., it should be enabled) */ +static int _env_handle_key_viewmode(struct key_event *k, song_envelope *env, int *current_node) +{ + int new_node = *current_node; + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + change_focus_to(1); + return 1; + case SDLK_DOWN: + if (k->state) return 0; + change_focus_to(6); + return 1; + case SDLK_LEFT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_node--; + break; + case SDLK_RIGHT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_node++; + break; + case SDLK_INSERT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + *current_node = _env_node_add(env, *current_node, -1, -1); + status.flags |= NEED_UPDATE; + return 1 | 2; + case SDLK_DELETE: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + *current_node = _env_node_remove(env, *current_node); + status.flags |= NEED_UPDATE; + return 1 | 2; + case SDLK_SPACE: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + song_keyup(-1, current_instrument, last_note, -1, 0); + song_keydown(-1, current_instrument, last_note, 64, -1, 0); + return 1; + case SDLK_RETURN: + if (!k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + envelope_edit_mode = 1; + status.flags |= NEED_UPDATE; + return 1 | 2; + case SDLK_l: + if (!k->state) return 0; + if (!(k->mod & KMOD_ALT)) return 0; + if (env->loop_end < (env->nodes-1)) { + dialog_create(DIALOG_OK_CANCEL, "Cut envelope?", do_post_loop_cut, NULL, 1, env); + return 1; + } + return 0; + case SDLK_b: + if (!k->state) return 0; + if (!(k->mod & KMOD_ALT)) return 0; + if (env->loop_start > 0) { + dialog_create(DIALOG_OK_CANCEL, "Cut envelope?", do_pre_loop_cut, NULL, 1, env); + return 1; + } + return 0; + + default: + return 0; + } + + new_node = CLAMP(new_node, 0, env->nodes - 1); + if (*current_node != new_node) { + *current_node = new_node; + status.flags |= NEED_UPDATE; + } + + return 1; +} + +/* mouse handling routines for envelope */ +static int _env_handle_mouse(struct key_event *k, song_envelope *env, int *current_node) +{ + int x, y, i; + int max_ticks = 50; + + if (k->mouse != 1) return 0; + + if (k->state) { + /* mouse release */ + if (envelope_mouse_edit) { + if (current_node && *current_node) { + for (i = 0; i < env->nodes-1; i++) { + if (*current_node == i) continue; + if (env->ticks[ *current_node ] == env->ticks[i] + && env->values[ *current_node ] == env->values[i]) { + status_text_flash("Removed node %d", *current_node); + status.flags |= SONG_NEEDS_SAVE; + + *current_node = _env_node_remove(env, *current_node); + break; + } + } + + } + status.flags |= NEED_UPDATE; + } + memused_songchanged(); + envelope_mouse_edit = 0; + return 1; + } + + while (env->ticks[env->nodes - 1] >= max_ticks) + max_ticks *= 2; + + if (envelope_mouse_edit) { + if (k->fx < 259) + x = 0; + else + x = (k->fx - 259) * max_ticks / 256; + y = 64 - (k->fy - 144); + if (y > 63) y++; + if (y > 42) y++; + if (y > 21) y++; + if (y > 64) y = 64; + if (y < 0) y = 0; + + if (*current_node && env->ticks[ (*current_node)-1 ] >= x) { + x = env->ticks[ (*current_node)-1 ]; + } + if (*current_node < (env->nodes-1)) { + if (env->ticks[ (*current_node)+1 ] <= x) { + x = env->ticks[ (*current_node)+1 ]; + } + } + if (env->ticks[*current_node] == x && env->ticks[*current_node] == y) { + return 1; + } + if (x > envelope_tick_limit) x = envelope_tick_limit; + if (x > 9999) x = 9999; + if (*current_node) env->ticks[ *current_node ] = x; + env->values[ *current_node ] = y; + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; + } else { + int n; + int dist, dx, dy; + int best_dist; + int best_dist_node; + + best_dist_node = -1; + + if (k->x < 32 || k->y < 18 || k->x > 32+45 || k->y > 18+8) + return 0; + + for (n = 0; n < env->nodes; n++) { + x = 259 + env->ticks[n] * 256 / max_ticks; + y = env->values[n]; + if (y > 63) y--; + if (y > 42) y--; + if (y > 21) y--; + y = 206 - y; + + dx = abs(x - k->fx); + dy = abs(y - k->fy); + dist = i_sqrt((dx*dx)+(dy*dy)); + if (best_dist_node == -1 || dist < best_dist) { + if (dist <= 5) { + best_dist = dist; + best_dist_node = n; + } + } + } + if (best_dist_node == -1) { + x = (k->fx - 259) * max_ticks / 256; + y = 64 - (k->fy - 144); + if (y > 63) y++; + if (y > 42) y++; + if (y > 21) y++; + if (y > 64) y = 64; + if (y < 0) y = 0; + if (x > 0 && x < max_ticks) { + *current_node = 0; + for (i = 1; i < env->nodes; i++) { + /* something too close */ + if (env->ticks[i] <= x) *current_node = i; + if (abs(env->ticks[i] - x) <= 4) return 0; + } + best_dist_node = (_env_node_add(env, *current_node, x, y))+1; + status_text_flash("Created node %d", best_dist_node); + } + if (best_dist_node == -1) return 0; + } + + envelope_tick_limit = env->ticks[env->nodes - 1] * 2; + envelope_mouse_edit = 1; + *current_node = best_dist_node; + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; + return 1; + } + return 0; +} + + + +/* - this function is only ever called when the envelope is in edit mode + - envelope_edit_mode is only ever assigned a true value once, in _env_handle_key_viewmode. + - when _env_handle_key_viewmode enables envelope_edit_mode, it indicates in its return value + that the envelope should be enabled. + - therefore, the envelope will always be enabled when this function is called, so there is + no reason to indicate a change in the envelope here. */ +static int _env_handle_key_editmode(struct key_event *k, song_envelope *env, int *current_node) +{ + int new_node = *current_node, new_tick = env->ticks[*current_node], + new_value = env->values[*current_node]; + + /* TODO: when does adding/removing a node alter loop points? */ + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + if (k->mod & KMOD_ALT) + new_value += 16; + else + new_value++; + break; + case SDLK_DOWN: + if (k->state) return 0; + if (k->mod & KMOD_ALT) + new_value -= 16; + else + new_value--; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_value += 16; + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_value -= 16; + break; + case SDLK_LEFT: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) + new_node--; + else if (k->mod & KMOD_ALT) + new_tick -= 16; + else + new_tick--; + break; + case SDLK_RIGHT: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) + new_node++; + else if (k->mod & KMOD_ALT) + new_tick += 16; + else + new_tick++; + break; + case SDLK_TAB: + if (k->state) return 0; + if (k->mod & KMOD_SHIFT) + new_tick -= 16; + else + new_tick += 16; + break; + case SDLK_HOME: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_tick = 0; + break; + case SDLK_END: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_tick = 10000; + break; + case SDLK_INSERT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + *current_node = _env_node_add(env, *current_node, -1, -1); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_DELETE: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + *current_node = _env_node_remove(env, *current_node); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_SPACE: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + song_keyup(-1, current_instrument, last_note, -1, 0); + song_keydown(-1, current_instrument, last_note, 64, -1, 0); + return 1; + case SDLK_RETURN: + if (!k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + envelope_edit_mode = 0; + memused_songchanged(); + status.flags |= NEED_UPDATE; + break; + default: + return 0; + } + + new_node = CLAMP(new_node, 0, env->nodes - 1); + if (new_node != *current_node) { + status.flags |= NEED_UPDATE; + *current_node = new_node; + return 1; + } + + new_tick = (new_node == 0) ? 0 : CLAMP(new_tick, + env->ticks[new_node - 1] + 1, + ((new_node == env->nodes - 1) + ? 10000 : env->ticks[new_node + 1]) - 1); + if (new_tick != env->ticks[new_node]) { + env->ticks[*current_node] = new_tick; + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; + return 1; + } + new_value = CLAMP(new_value, 0, 64); + + if (new_value != env->values[new_node]) { + env->values[*current_node] = new_value; + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; + return 1; + } + + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* envelope stuff (draw()'s and handle_key()'s) */ + +static void _draw_env_label(const char *env_name, int is_selected) +{ + int pos = 33; + + pos += draw_text((const unsigned char *)env_name, pos, 16, is_selected ? 3 : 0, 2); + pos += draw_text((const unsigned char *)" Envelope", pos, 16, is_selected ? 3 : 0, 2); + if (envelope_edit_mode || envelope_mouse_edit) + draw_text((const unsigned char *)" (Edit)", pos, 16, is_selected ? 3 : 0, 2); +} + +static void volume_envelope_draw(void) +{ + int is_selected = (ACTIVE_PAGE.selected_widget == 5); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + _draw_env_label("Volume", is_selected); + _env_draw(&ins->vol_env, 0, current_node_vol, + ins->flags & ENV_VOLUME, + ins->flags & ENV_VOLLOOP, ins->flags & ENV_VOLSUSTAIN, 0); +} + +static void panning_envelope_draw(void) +{ + int is_selected = (ACTIVE_PAGE.selected_widget == 5); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + _draw_env_label("Panning", is_selected); + _env_draw(&ins->pan_env, 1, current_node_pan, + ins->flags & ENV_PANNING, + ins->flags & ENV_PANLOOP, ins->flags & ENV_PANSUSTAIN, 1); +} + +static void pitch_envelope_draw(void) +{ + int is_selected = (ACTIVE_PAGE.selected_widget == 5); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + _draw_env_label("Frequency", is_selected); + _env_draw(&ins->pitch_env, (ins->flags & ENV_FILTER) ? 0 : 1, current_node_pitch, + ins->flags & (ENV_PITCH|ENV_FILTER), + ins->flags & ENV_PITCHLOOP, ins->flags & ENV_PITCHSUSTAIN, 2); +} + +static int volume_envelope_handle_key(struct key_event * k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int r; + + if (_env_handle_mouse(k, &ins->vol_env, ¤t_node_vol)) { + ins->flags |= ENV_VOLUME; + return 1; + } + if (envelope_edit_mode) + r = _env_handle_key_editmode(k, &ins->vol_env, ¤t_node_vol); + else + r = _env_handle_key_viewmode(k, &ins->vol_env, ¤t_node_vol); + if (r & 2) { + r ^= 2; + ins->flags |= ENV_VOLUME; + } + return r; +} + +static int panning_envelope_handle_key(struct key_event * k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int r; + + if (_env_handle_mouse(k, &ins->pan_env, ¤t_node_pan)) { + ins->flags |= ENV_PANNING; + return 1; + } + + if (envelope_edit_mode) + r = _env_handle_key_editmode(k, &ins->pan_env, ¤t_node_pan); + else + r = _env_handle_key_viewmode(k, &ins->pan_env, ¤t_node_pan); + if (r & 2) { + r ^= 2; + ins->flags |= ENV_PANNING; + } + return r; +} + +static int pitch_envelope_handle_key(struct key_event * k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int r; + + if (_env_handle_mouse(k, &ins->pitch_env, ¤t_node_pitch)) { + ins->flags |= ENV_PITCH; + return 1; + } + if (envelope_edit_mode) + r = _env_handle_key_editmode(k, &ins->pitch_env, ¤t_node_pitch); + else + r = _env_handle_key_viewmode(k, &ins->pitch_env, ¤t_node_pitch); + if (r & 2) { + r ^= 2; + ins->flags |= ENV_PITCH; + } + return r; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* pitch-pan center */ + +static int pitch_pan_center_handle_key(struct key_event *k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int ppc = ins->pitch_pan_center; + + if (k->state) return 0; + switch (k->sym) { + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + ppc--; + break; + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + ppc++; + break; + default: + if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0) { + ppc = kbd_get_note(k); + if (ppc < 1 || ppc > 120) + return 0; + ppc--; + break; + } + return 0; + } + if (ppc != ins->pitch_pan_center && ppc >= 0 && ppc < 120) { + ins->pitch_pan_center = ppc; + status.flags |= NEED_UPDATE; + } + return 1; +} + +static void pitch_pan_center_draw(void) +{ + char buf[4]; + int selected = (ACTIVE_PAGE.selected_widget == 16); + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + draw_text((const unsigned char *)get_note_string(ins->pitch_pan_center + 1, buf), 54, 45, selected ? 3 : 2, 0); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* default key handler (for instrument changing on pgup/pgdn) */ + +static void do_ins_save(void *p) +{ + char *ptr = (char *)p; + if (song_save_instrument(current_instrument, ptr)) + status_text_flash("Instrument saved (instrument %d)", current_instrument); + else + status_text_flash("Error: Instrument %d NOT saved! (No Filename?)", current_instrument); + free(ptr); +} + +static void instrument_save(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + char *ptr = dmoz_path_concat(cfg_dir_instruments, ins->filename); + struct stat buf; + + if (stat(ptr, &buf) == 0) { + if (S_ISDIR(buf.st_mode)) { + status_text_flash("%s is a directory", ins->filename); + return; + } else if (S_ISREG(buf.st_mode)) { + dialog_create(DIALOG_OK_CANCEL, + "Overwrite file?", do_ins_save, + free, 1, ptr); + return; + } else { + status_text_flash("%s is not a regular file", ins->filename); + return; + } + } + if (song_save_instrument(current_instrument, ptr)) + status_text_flash("Instrument saved (instrument %d)", current_instrument); + else + status_text_flash("Error: Instrument %d NOT saved! (No Filename?)", current_instrument); + free(ptr); +} + +static void do_delete_inst(UNUSED void *ign) +{ + song_delete_instrument(current_instrument); +} + +static void instrument_list_handle_alt_key(struct key_event *k) +{ + /* song_instrument *ins = song_get_instrument(current_instrument, NULL); */ + + if (k->state) return; + switch (k->sym) { + case SDLK_n: + song_toggle_multichannel_mode(); + return; + case SDLK_o: + instrument_save(); + return; + case SDLK_s: + swap_instrument_dialog(); + return; + case SDLK_x: + exchange_instrument_dialog(); + return; + case SDLK_w: + song_wipe_instrument(current_instrument); + break; + case SDLK_d: + dialog_create(DIALOG_OK_CANCEL, + "Delete Instrument?", + do_delete_inst, NULL, 1, NULL); + return; + default: + return; + } + + status.flags |= NEED_UPDATE; +} + +static void instrument_list_handle_key(struct key_event * k) +{ + switch (k->sym) { + case SDLK_COMMA: + case SDLK_LESS: + if (k->state) return; + song_change_current_play_channel(-1, 0); + return; + case SDLK_PERIOD: + case SDLK_GREATER: + if (k->state) return; + song_change_current_play_channel(1, 0); + return; + + case SDLK_PAGEUP: + if (k->state) return; + instrument_set(current_instrument - 1); + break; + case SDLK_PAGEDOWN: + if (k->state) return; + instrument_set(current_instrument + 1); + break; + default: + if (k->mod & (KMOD_ALT)) { + instrument_list_handle_alt_key(k); + } else { + int n, v; + + if (k->midi_note > -1) { + n = k->midi_note; + if (k->midi_volume > -1) { + v = k->midi_volume / 2; + } else { + v = 64; + } + } else { + v = 64; + n = kbd_get_note(k); + if (n <= 0 || n > 120) + return; + } + + if (k->state) { + song_keyup(-1, current_instrument, n, -1, 0); + status.last_keysym = 0; + } else if (!k->is_repeat) { + song_keydown(-1, current_instrument, n, v, -1, 0); + } + last_note = n; + } + return; + } +} + +/* --------------------------------------------------------------------- */ + +static void change_subpage(void) +{ + int widget = ACTIVE_PAGE.selected_widget; + int page = status.current_page; + + switch (widget) { + case 1: + page = PAGE_INSTRUMENT_LIST_GENERAL; + break; + case 2: + page = PAGE_INSTRUMENT_LIST_VOLUME; + break; + case 3: + page = PAGE_INSTRUMENT_LIST_PANNING; + break; + case 4: + page = PAGE_INSTRUMENT_LIST_PITCH; + break; +#ifndef NDEBUG + default: + fprintf(stderr, "change_subpage: wtf, how did I get here?\n"); + abort(); + return; +#endif + } + + if (page != status.current_page) { + pages[page].selected_widget = widget; + togglebutton_set(pages[page].widgets, widget, 0); + set_page(page); + instrument_list_subpage = page; + } +} + +/* --------------------------------------------------------------------- */ +/* predraw hooks... */ + +static void instrument_list_general_predraw_hook(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + togglebutton_set(widgets_general, 6 + ins->nna, 0); + togglebutton_set(widgets_general, 10 + ins->dct, 0); + togglebutton_set(widgets_general, 14 + ins->dca, 0); + + widgets_general[17].d.textentry.text = ins->filename; +} + +static void instrument_list_volume_predraw_hook(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + widgets_volume[6].d.toggle.state = !!(ins->flags & ENV_VOLUME); + widgets_volume[7].d.toggle.state = !!(ins->flags & ENV_VOLCARRY); + widgets_volume[8].d.toggle.state = !!(ins->flags & ENV_VOLLOOP); + widgets_volume[11].d.toggle.state = !!(ins->flags & ENV_VOLSUSTAIN); + + /* FIXME: this is the wrong place for this. + ... and it's probably not even right -- how does Impulse Tracker handle loop constraints? + See below for panning/pitch envelopes; same deal there. */ + if (ins->vol_env.loop_start > ins->vol_env.loop_end) + ins->vol_env.loop_end = ins->vol_env.loop_start; + if (ins->vol_env.sustain_start > ins->vol_env.sustain_end) + ins->vol_env.sustain_end = ins->vol_env.sustain_start; + + widgets_volume[9].d.numentry.max = ins->vol_env.nodes - 1; + widgets_volume[10].d.numentry.max = ins->vol_env.nodes - 1; + widgets_volume[12].d.numentry.max = ins->vol_env.nodes - 1; + widgets_volume[13].d.numentry.max = ins->vol_env.nodes - 1; + + widgets_volume[9].d.numentry.value = ins->vol_env.loop_start; + widgets_volume[10].d.numentry.value = ins->vol_env.loop_end; + widgets_volume[12].d.numentry.value = ins->vol_env.sustain_start; + widgets_volume[13].d.numentry.value = ins->vol_env.sustain_end; + + /* mp hack: shifting values all over the place here, ugh */ + widgets_volume[14].d.thumbbar.value = ins->global_volume << 1; + widgets_volume[15].d.thumbbar.value = ins->fadeout >> 5; + widgets_volume[16].d.thumbbar.value = ins->volume_swing; +} + +static void instrument_list_panning_predraw_hook(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + widgets_panning[6].d.toggle.state = !!(ins->flags & ENV_PANNING); + widgets_panning[7].d.toggle.state = !!(ins->flags & ENV_PANCARRY); + widgets_panning[8].d.toggle.state = !!(ins->flags & ENV_PANLOOP); + widgets_panning[11].d.toggle.state = !!(ins->flags & ENV_PANSUSTAIN); + + if (ins->pan_env.loop_start > ins->pan_env.loop_end) + ins->pan_env.loop_end = ins->pan_env.loop_start; + if (ins->pan_env.sustain_start > ins->pan_env.sustain_end) + ins->pan_env.sustain_end = ins->pan_env.sustain_start; + + widgets_panning[9].d.numentry.max = ins->pan_env.nodes - 1; + widgets_panning[10].d.numentry.max = ins->pan_env.nodes - 1; + widgets_panning[12].d.numentry.max = ins->pan_env.nodes - 1; + widgets_panning[13].d.numentry.max = ins->pan_env.nodes - 1; + + widgets_panning[9].d.numentry.value = ins->pan_env.loop_start; + widgets_panning[10].d.numentry.value = ins->pan_env.loop_end; + widgets_panning[12].d.numentry.value = ins->pan_env.sustain_start; + widgets_panning[13].d.numentry.value = ins->pan_env.sustain_end; + + widgets_panning[14].d.toggle.state = !!(ins->flags & ENV_SETPANNING); + widgets_panning[15].d.thumbbar.value = ins->panning >> 2; + /* (widgets_panning[16] is the pitch-pan center) */ + widgets_panning[17].d.thumbbar.value = ins->pitch_pan_separation; + widgets_panning[18].d.thumbbar.value = ins->pan_swing; +} + +static void instrument_list_pitch_predraw_hook(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + widgets_pitch[6].d.menutoggle.state = ((ins->flags & ENV_PITCH) + ? ((ins->flags & ENV_FILTER) + ? 2 : 1) : 0); + widgets_pitch[7].d.toggle.state = !!(ins->flags & ENV_PITCHCARRY); + widgets_pitch[8].d.toggle.state = !!(ins->flags & ENV_PITCHLOOP); + widgets_pitch[11].d.toggle.state = !!(ins->flags & ENV_PITCHSUSTAIN); + + if (ins->pitch_env.loop_start > ins->pitch_env.loop_end) + ins->pitch_env.loop_end = ins->pitch_env.loop_start; + if (ins->pitch_env.sustain_start > ins->pitch_env.sustain_end) + ins->pitch_env.sustain_end = ins->pitch_env.sustain_start; + + widgets_pitch[9].d.numentry.max = ins->pitch_env.nodes - 1; + widgets_pitch[10].d.numentry.max = ins->pitch_env.nodes - 1; + widgets_pitch[12].d.numentry.max = ins->pitch_env.nodes - 1; + widgets_pitch[13].d.numentry.max = ins->pitch_env.nodes - 1; + + widgets_pitch[9].d.numentry.value = ins->pitch_env.loop_start; + widgets_pitch[10].d.numentry.value = ins->pitch_env.loop_end; + widgets_pitch[12].d.numentry.value = ins->pitch_env.sustain_start; + widgets_pitch[13].d.numentry.value = ins->pitch_env.sustain_end; + + if (ins->filter_cutoff & 0x80) + widgets_pitch[14].d.thumbbar.value = ins->filter_cutoff & 0x7f; + else + widgets_pitch[14].d.thumbbar.value = -1; + if (ins->filter_resonance & 0x80) + widgets_pitch[15].d.thumbbar.value = ins->filter_resonance & 0x7f; + else + widgets_pitch[15].d.thumbbar.value = -1; + + /* printf("ins%02d: ch%04d pgm%04d bank%06d drum%04d\n", current_instrument, + ins->midi_channel, ins->midi_program, ins->midi_bank, ins->midi_drum_key); */ + widgets_pitch[16].d.thumbbar.value = ins->midi_channel; + widgets_pitch[17].d.thumbbar.value = (signed char) ins->midi_program; + widgets_pitch[18].d.thumbbar.value = (signed char) (ins->midi_bank & 0xff); + widgets_pitch[19].d.thumbbar.value = (signed char) (ins->midi_bank >> 8); + /* what is midi_drum_key for? */ +} + +/* --------------------------------------------------------------------- */ +/* update values in song */ + +static void instrument_list_general_update_values(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + status.flags |= SONG_NEEDS_SAVE; + for (ins->nna = 4; ins->nna--;) + if (widgets_general[ins->nna + 6].d.togglebutton.state) + break; + for (ins->dct = 4; ins->dct--;) + if (widgets_general[ins->dct + 10].d.togglebutton.state) + break; + for (ins->dca = 3; ins->dca--;) + if (widgets_general[ins->dca + 14].d.togglebutton.state) + break; +} + +#define CHECK_SET(a,b,c) if (a != b) { a = b; c; } + +static void instrument_list_volume_update_values(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + status.flags |= SONG_NEEDS_SAVE; + ins->flags &= ~(ENV_VOLUME | ENV_VOLCARRY | ENV_VOLLOOP | ENV_VOLSUSTAIN); + if (widgets_volume[6].d.toggle.state) + ins->flags |= ENV_VOLUME; + if (widgets_volume[7].d.toggle.state) + ins->flags |= ENV_VOLCARRY; + if (widgets_volume[8].d.toggle.state) + ins->flags |= ENV_VOLLOOP; + if (widgets_volume[11].d.toggle.state) + ins->flags |= ENV_VOLSUSTAIN; + + CHECK_SET(ins->vol_env.loop_start, widgets_volume[9].d.numentry.value, + ins->flags |= ENV_VOLLOOP); + CHECK_SET(ins->vol_env.loop_end, widgets_volume[10].d.numentry.value, + ins->flags |= ENV_VOLLOOP); + CHECK_SET(ins->vol_env.sustain_start, widgets_volume[12].d.numentry.value, + ins->flags |= ENV_VOLSUSTAIN); + CHECK_SET(ins->vol_env.sustain_end, widgets_volume[13].d.numentry.value, + ins->flags |= ENV_VOLSUSTAIN); + + /* more ugly shifts */ + ins->global_volume = widgets_volume[14].d.thumbbar.value >> 1; + ins->fadeout = widgets_volume[15].d.thumbbar.value << 5; + ins->volume_swing = widgets_volume[16].d.thumbbar.value; + + song_update_playing_instrument(current_instrument); +} + +static void instrument_list_panning_update_values(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + int n; + + status.flags |= SONG_NEEDS_SAVE; + ins->flags &= ~(ENV_PANNING | ENV_PANCARRY | ENV_PANLOOP | ENV_PANSUSTAIN | ENV_SETPANNING); + if (widgets_panning[6].d.toggle.state) + ins->flags |= ENV_PANNING; + if (widgets_panning[7].d.toggle.state) + ins->flags |= ENV_PANCARRY; + if (widgets_panning[8].d.toggle.state) + ins->flags |= ENV_PANLOOP; + if (widgets_panning[11].d.toggle.state) + ins->flags |= ENV_PANSUSTAIN; + if (widgets_panning[14].d.toggle.state) + ins->flags |= ENV_SETPANNING; + + CHECK_SET(ins->pan_env.loop_start, widgets_panning[9].d.numentry.value, + ins->flags |= ENV_PANLOOP); + CHECK_SET(ins->pan_env.loop_end, widgets_panning[10].d.numentry.value, + ins->flags |= ENV_PANLOOP); + CHECK_SET(ins->pan_env.sustain_start, widgets_panning[12].d.numentry.value, + ins->flags |= ENV_PANSUSTAIN); + CHECK_SET(ins->pan_env.sustain_end, widgets_panning[13].d.numentry.value, + ins->flags |= ENV_PANSUSTAIN); + + n = widgets_panning[15].d.thumbbar.value << 2; + if (ins->panning != n) { + ins->panning = n; + ins->flags |= ENV_SETPANNING; + } + /* (widgets_panning[16] is the pitch-pan center) */ + ins->pitch_pan_separation = widgets_panning[17].d.thumbbar.value; + ins->pan_swing = widgets_panning[18].d.thumbbar.value; + + song_update_playing_instrument(current_instrument); +} + +static void instrument_list_pitch_update_values(void) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + + status.flags |= SONG_NEEDS_SAVE; + ins->flags &= ~(ENV_PITCH | ENV_PITCHCARRY | ENV_PITCHLOOP | ENV_PITCHSUSTAIN | ENV_FILTER); + + switch (widgets_pitch[6].d.menutoggle.state) { + case 2: ins->flags |= ENV_FILTER; + case 1: ins->flags |= ENV_PITCH; + } + + if (widgets_pitch[6].d.menutoggle.state) + ins->flags |= ENV_PITCH; + if (widgets_pitch[7].d.toggle.state) + ins->flags |= ENV_PITCHCARRY; + if (widgets_pitch[8].d.toggle.state) + ins->flags |= ENV_PITCHLOOP; + if (widgets_pitch[11].d.toggle.state) + ins->flags |= ENV_PITCHSUSTAIN; + + CHECK_SET(ins->pitch_env.loop_start, widgets_pitch[9].d.numentry.value, + ins->flags |= ENV_PITCHLOOP); + CHECK_SET(ins->pitch_env.loop_end, widgets_pitch[10].d.numentry.value, + ins->flags |= ENV_PITCHLOOP); + CHECK_SET(ins->pitch_env.sustain_start, widgets_pitch[12].d.numentry.value, + ins->flags |= ENV_PITCHSUSTAIN); + CHECK_SET(ins->pitch_env.sustain_end, widgets_pitch[13].d.numentry.value, + ins->flags |= ENV_PITCHSUSTAIN); + if (widgets_pitch[14].d.thumbbar.value > -1) { + ins->filter_cutoff = widgets_pitch[14].d.thumbbar.value | 0x80; + } else { + ins->filter_cutoff = 0x7f; + } + if (widgets_pitch[15].d.thumbbar.value > -1) { + ins->filter_resonance = widgets_pitch[15].d.thumbbar.value | 0x80; + } else { + ins->filter_resonance = 0x7f; + } + ins->midi_channel = widgets_pitch[16].d.thumbbar.value; + ins->midi_program = widgets_pitch[17].d.thumbbar.value; + ins->midi_bank = ((widgets_pitch[19].d.thumbbar.value << 8) + | (widgets_pitch[18].d.thumbbar.value & 0xff)); + + song_update_playing_instrument(current_instrument); +} + +/* --------------------------------------------------------------------- */ +/* draw_const functions */ + +static void instrument_list_draw_const(void) +{ + draw_box(4, 12, 30, 48, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void instrument_list_general_draw_const(void) +{ + int n; + + instrument_list_draw_const(); + + draw_box(31, 15, 42, 48, BOX_THICK | BOX_INNER | BOX_INSET); + + /* Kind of a hack, and not really useful, but... :) */ + if (status.flags & CLASSIC_MODE) { + draw_box(55, 46, 73, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_text((const unsigned char *)" ", 69, 47, 1, 0); + } else { + draw_box(55, 46, 69, 48, BOX_THICK | BOX_INNER | BOX_INSET); + } + + draw_text((const unsigned char *)"New Note Action", 54, 17, 0, 2); + draw_text((const unsigned char *)"Duplicate Check Type & Action", 47, 32, 0, 2); + draw_text((const unsigned char *)"Filename", 47, 47, 0, 2); + + for (n = 0; n < 35; n++) { + draw_char(134, 44 + n, 15, 0, 2); + draw_char(134, 44 + n, 30, 0, 2); + draw_char(154, 44 + n, 45, 0, 2); + } +} + +static void instrument_list_volume_draw_const(void) +{ + instrument_list_draw_const(); + + draw_fill_chars(57, 28, 62, 29, 0); + draw_fill_chars(57, 32, 62, 34, 0); + draw_fill_chars(57, 37, 62, 39, 0); + + draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 41, 71, 44, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 45, 71, 47, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_text((const unsigned char *)"Volume Envelope", 38, 28, 0, 2); + draw_text((const unsigned char *)"Carry", 48, 29, 0, 2); + draw_text((const unsigned char *)"Envelope Loop", 40, 32, 0, 2); + draw_text((const unsigned char *)"Loop Begin", 43, 33, 0, 2); + draw_text((const unsigned char *)"Loop End", 45, 34, 0, 2); + draw_text((const unsigned char *)"Sustain Loop", 41, 37, 0, 2); + draw_text((const unsigned char *)"SusLoop Begin", 40, 38, 0, 2); + draw_text((const unsigned char *)"SusLoop End", 42, 39, 0, 2); + draw_text((const unsigned char *)"Global Volume", 40, 42, 0, 2); + draw_text((const unsigned char *)"Fadeout", 46, 43, 0, 2); + draw_text((const unsigned char *)"Volume Swing %", 39, 46, 0, 2); +} + +static void instrument_list_panning_draw_const(void) +{ + instrument_list_draw_const(); + + draw_fill_chars(57, 28, 62, 29, 0); + draw_fill_chars(57, 32, 62, 34, 0); + draw_fill_chars(57, 37, 62, 39, 0); + draw_fill_chars(57, 42, 62, 45, 0); + + draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 41, 63, 48, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_text((const unsigned char *)"Panning Envelope", 37, 28, 0, 2); + draw_text((const unsigned char *)"Carry", 48, 29, 0, 2); + draw_text((const unsigned char *)"Envelope Loop", 40, 32, 0, 2); + draw_text((const unsigned char *)"Loop Begin", 43, 33, 0, 2); + draw_text((const unsigned char *)"Loop End", 45, 34, 0, 2); + draw_text((const unsigned char *)"Sustain Loop", 41, 37, 0, 2); + draw_text((const unsigned char *)"SusLoop Begin", 40, 38, 0, 2); + draw_text((const unsigned char *)"SusLoop End", 42, 39, 0, 2); + draw_text((const unsigned char *)"Default Pan", 42, 42, 0, 2); + draw_text((const unsigned char *)"Pan Value", 44, 43, 0, 2); + draw_text((const unsigned char *)"Pitch-Pan Center", 37, 45, 0, 2); + draw_text((const unsigned char *)"Pitch-Pan Separation", 33, 46, 0, 2); + if (status.flags & CLASSIC_MODE) { + /* Hmm. The 's' in swing isn't capitalised. ;) */ + draw_text((const unsigned char *)"Pan swing", 44, 47, 0, 2); + } else { + draw_text((const unsigned char *)"Pan Swing", 44, 47, 0, 2); + } + + draw_text((const unsigned char *)"\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a", 54, 44, 2, 0); +} + +static void instrument_list_pitch_draw_const(void) +{ + instrument_list_draw_const(); + + draw_fill_chars(57, 28, 62, 29, 0); + draw_fill_chars(57, 32, 62, 34, 0); + draw_fill_chars(57, 37, 62, 39, 0); + + draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(53, 41, 71, 48, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_text((const unsigned char *)"Frequency Envelope", 35, 28, 0, 2); + draw_text((const unsigned char *)"Carry", 48, 29, 0, 2); + draw_text((const unsigned char *)"Envelope Loop", 40, 32, 0, 2); + draw_text((const unsigned char *)"Loop Begin", 43, 33, 0, 2); + draw_text((const unsigned char *)"Loop End", 45, 34, 0, 2); + draw_text((const unsigned char *)"Sustain Loop", 41, 37, 0, 2); + draw_text((const unsigned char *)"SusLoop Begin", 40, 38, 0, 2); + draw_text((const unsigned char *)"SusLoop End", 42, 39, 0, 2); + draw_text((const unsigned char *)"Default Cutoff", 36, 42, 0, 2); + draw_text((const unsigned char *)"Default Resonance", 36, 43, 0, 2); + draw_text((const unsigned char *)"MIDI Channel", 36, 44, 0, 2); + draw_text((const unsigned char *)"MIDI Program", 36, 45, 0, 2); + draw_text((const unsigned char *)"MIDI Bank Low", 36, 46, 0, 2); + draw_text((const unsigned char *)"MIDI Bank High", 36, 47, 0, 2); +} + +/* --------------------------------------------------------------------- */ +/* load_page functions */ + +static void _load_page_common(struct page *page, struct widget *page_widgets) +{ + vgamem_font_reserve(&env_overlay); + + page->title = "Instrument List (F4)"; + page->handle_key = instrument_list_handle_key; + page->widgets = page_widgets; + page->help_index = HELP_INSTRUMENT_LIST; + + /* the first five widgets are the same for all four pages. */ + + /* 0 = instrument list */ + create_other(page_widgets + 0, 1, instrument_list_handle_key_on_list, instrument_list_draw_list); + page_widgets[0].x = 5; + page_widgets[0].y = 13; + page_widgets[0].width = 24; + page_widgets[0].height = 34; + + /* 1-4 = subpage switches */ + create_togglebutton(page_widgets + 1, 32, 13, 7, 1, 5, 0, 2, 2, change_subpage, "General", + 1, subpage_switches_group); + create_togglebutton(page_widgets + 2, 44, 13, 7, 2, 5, 1, 3, 3, change_subpage, "Volume", + 1, subpage_switches_group); + create_togglebutton(page_widgets + 3, 56, 13, 7, 3, 5, 2, 4, 4, change_subpage, "Panning", + 1, subpage_switches_group); + create_togglebutton(page_widgets + 4, 68, 13, 7, 4, 5, 3, 0, 0, change_subpage, "Pitch", + 2, subpage_switches_group); +} + +void instrument_list_general_load_page(struct page *page) +{ + _load_page_common(page, widgets_general); + + page->draw_const = instrument_list_general_draw_const; + page->predraw_hook = instrument_list_general_predraw_hook; + page->total_widgets = 18; + + /* special case stuff */ + widgets_general[1].d.togglebutton.state = 1; + widgets_general[2].next.down = widgets_general[3].next.down = widgets_general[4].next.down = 6; + + /* 5 = note trans table */ + create_other(widgets_general + 5, 6, note_trans_handle_key, note_trans_draw); + widgets_general[5].x = 32; + widgets_general[5].y = 16; + widgets_general[5].width = 9; + widgets_general[5].height = 31; + + /* 6-9 = nna toggles */ + create_togglebutton(widgets_general + 6, 46, 19, 29, 2, 7, 5, 0, 0, + instrument_list_general_update_values, + "Note Cut", 2, nna_group); + create_togglebutton(widgets_general + 7, 46, 22, 29, 6, 8, 5, 0, 0, + instrument_list_general_update_values, + "Continue", 2, nna_group); + create_togglebutton(widgets_general + 8, 46, 25, 29, 7, 9, 5, 0, 0, + instrument_list_general_update_values, + "Note Off", 2, nna_group); + create_togglebutton(widgets_general + 9, 46, 28, 29, 8, 10, 5, 0, 0, + instrument_list_general_update_values, + "Note Fade", 2, nna_group); + + /* 10-13 = dct toggles */ + create_togglebutton(widgets_general + 10, 46, 34, 12, 9, 11, 5, 14, + 14, instrument_list_general_update_values, + "Disabled", 2, dct_group); + create_togglebutton(widgets_general + 11, 46, 37, 12, 10, 12, 5, 15, + 15, instrument_list_general_update_values, + "Note", 2, dct_group); + create_togglebutton(widgets_general + 12, 46, 40, 12, 11, 13, 5, 16, + 16, instrument_list_general_update_values, + "Sample", 2, dct_group); + create_togglebutton(widgets_general + 13, 46, 43, 12, 12, 17, 5, 13, + 13, instrument_list_general_update_values, + "Instrument", 2, dct_group); + /* 14-16 = dca toggles */ + create_togglebutton(widgets_general + 14, 62, 34, 13, 9, 15, 10, 0, + 0, instrument_list_general_update_values, + "Note Cut", 2, dca_group); + create_togglebutton(widgets_general + 15, 62, 37, 13, 14, 16, 11, 0, + 0, instrument_list_general_update_values, + "Note Off", 2, dca_group); + create_togglebutton(widgets_general + 16, 62, 40, 13, 15, 17, 12, 0, + 0, instrument_list_general_update_values, + "Note Fade", 2, dca_group); + /* 17 = filename */ + /* impulse tracker has a 17-char-wide box for the filename for + * some reason, though it still limits the actual text to 12 + * characters. go figure... */ + create_textentry(widgets_general + 17, 56, 47, 13, 13, 17, 0, NULL, + NULL, 12); +} + +static int _fixup_mouse_instpage_volume(struct key_event *k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + if (envelope_mouse_edit && ins) { + if (_env_handle_mouse(k, &ins->vol_env, ¤t_node_vol)) { + ins->flags |= ENV_VOLUME; + return 1; + } + } + if ((k->sym == SDLK_l || k->sym == SDLK_b) && (k->mod & KMOD_ALT)) { + return _env_handle_key_viewmode(k, &ins->vol_env, ¤t_node_vol); + } + return 0; +} + +void instrument_list_volume_load_page(struct page *page) +{ + _load_page_common(page, widgets_volume); + + page->pre_handle_key = _fixup_mouse_instpage_volume; + page->draw_const = instrument_list_volume_draw_const; + page->predraw_hook = instrument_list_volume_predraw_hook; + page->total_widgets = 17; + + /* 5 = volume envelope */ + create_other(widgets_volume + 5, 0, volume_envelope_handle_key, volume_envelope_draw); + widgets_volume[5].x = 32; + widgets_volume[5].y = 18; + widgets_volume[5].width = 45; + widgets_volume[5].height = 8; + + /* 6-7 = envelope switches */ + create_toggle(widgets_volume + 6, 54, 28, 5, 7, 0, 0, 0, + instrument_list_volume_update_values); + create_toggle(widgets_volume + 7, 54, 29, 6, 8, 0, 0, 0, + instrument_list_volume_update_values); + + /* 8-10 envelope loop settings */ + create_toggle(widgets_volume + 8, 54, 32, 7, 9, 0, 0, 0, + instrument_list_volume_update_values); + create_numentry(widgets_volume + 9, 54, 33, 3, 8, 10, 0, + instrument_list_volume_update_values, 0, 1, + numentry_cursor_pos + 0); + create_numentry(widgets_volume + 10, 54, 34, 3, 9, 11, 0, + instrument_list_volume_update_values, 0, 1, + numentry_cursor_pos + 0); + + /* 11-13 = susloop settings */ + create_toggle(widgets_volume + 11, 54, 37, 10, 12, 0, 0, 0, + instrument_list_volume_update_values); + create_numentry(widgets_volume + 12, 54, 38, 3, 11, 13, 0, + instrument_list_volume_update_values, 0, 1, + numentry_cursor_pos + 0); + create_numentry(widgets_volume + 13, 54, 39, 3, 12, 14, 0, + instrument_list_volume_update_values, 0, 1, + numentry_cursor_pos + 0); + + /* 14-16 = volume thumbbars */ + create_thumbbar(widgets_volume + 14, 54, 42, 17, 13, 15, 0, + instrument_list_volume_update_values, 0, 128); + create_thumbbar(widgets_volume + 15, 54, 43, 17, 14, 16, 0, + instrument_list_volume_update_values, 0, 128); + create_thumbbar(widgets_volume + 16, 54, 46, 17, 15, 16, 0, + instrument_list_volume_update_values, 0, 100); +} + +static int _fixup_mouse_instpage_panning(struct key_event *k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + if (envelope_mouse_edit && ins) { + if (_env_handle_mouse(k, &ins->pan_env, ¤t_node_pan)) { + ins->flags |= ENV_PANNING; + return 1; + } + } + if ((k->sym == SDLK_l || k->sym == SDLK_b) && (k->mod & KMOD_ALT)) { + return _env_handle_key_viewmode(k, &ins->pan_env, ¤t_node_pan); + } + return 0; +} +void instrument_list_panning_load_page(struct page *page) +{ + _load_page_common(page, widgets_panning); + + page->pre_handle_key = _fixup_mouse_instpage_panning; + page->draw_const = instrument_list_panning_draw_const; + page->predraw_hook = instrument_list_panning_predraw_hook; + page->total_widgets = 19; + + /* 5 = panning envelope */ + create_other(widgets_panning + 5, 0, panning_envelope_handle_key, panning_envelope_draw); + widgets_panning[5].x = 32; + widgets_panning[5].y = 18; + widgets_panning[5].width = 45; + widgets_panning[5].height = 8; + + /* 6-7 = envelope switches */ + create_toggle(widgets_panning + 6, 54, 28, 5, 7, 0, 0, 0, + instrument_list_panning_update_values); + create_toggle(widgets_panning + 7, 54, 29, 6, 8, 0, 0, 0, + instrument_list_panning_update_values); + + /* 8-10 envelope loop settings */ + create_toggle(widgets_panning + 8, 54, 32, 7, 9, 0, 0, 0, + instrument_list_panning_update_values); + create_numentry(widgets_panning + 9, 54, 33, 3, 8, 10, 0, + instrument_list_panning_update_values, 0, 1, + numentry_cursor_pos + 1); + create_numentry(widgets_panning + 10, 54, 34, 3, 9, 11, 0, + instrument_list_panning_update_values, 0, 1, + numentry_cursor_pos + 1); + + /* 11-13 = susloop settings */ + create_toggle(widgets_panning + 11, 54, 37, 10, 12, 0, 0, 0, + instrument_list_panning_update_values); + create_numentry(widgets_panning + 12, 54, 38, 3, 11, 13, 0, + instrument_list_panning_update_values, 0, 1, + numentry_cursor_pos + 1); + create_numentry(widgets_panning + 13, 54, 39, 3, 12, 14, 0, + instrument_list_panning_update_values, 0, 1, + numentry_cursor_pos + 1); + + /* 14-15 = default panning */ + create_toggle(widgets_panning + 14, 54, 42, 13, 15, 0, 0, 0, + instrument_list_panning_update_values); + create_thumbbar(widgets_panning + 15, 54, 43, 9, 14, 16, 0, + instrument_list_panning_update_values, 0, 64); + + /* 16 = pitch-pan center */ + create_other(widgets_panning + 16, 0, pitch_pan_center_handle_key, pitch_pan_center_draw); + widgets_panning[16].next.up = 15; + widgets_panning[16].next.down = 17; + + /* 17-18 = other panning stuff */ + create_thumbbar(widgets_panning + 17, 54, 46, 9, 16, 18, 0, + instrument_list_panning_update_values, -32, 32); + create_thumbbar(widgets_panning + 18, 54, 47, 9, 17, 18, 0, + instrument_list_panning_update_values, 0, 64); +} + +static int _fixup_mouse_instpage_pitch(struct key_event *k) +{ + song_instrument *ins = song_get_instrument(current_instrument, NULL); + if (envelope_mouse_edit && ins) { + if (_env_handle_mouse(k, &ins->pitch_env, ¤t_node_pitch)) { + ins->flags |= ENV_PITCH; + return 1; + } + } + if ((k->sym == SDLK_l || k->sym == SDLK_b) && (k->mod & KMOD_ALT)) { + return _env_handle_key_viewmode(k, &ins->pitch_env, ¤t_node_pitch); + } + return 0; +} +void instrument_list_pitch_load_page(struct page *page) +{ + _load_page_common(page, widgets_pitch); + + page->pre_handle_key = _fixup_mouse_instpage_pitch; + page->draw_const = instrument_list_pitch_draw_const; + page->predraw_hook = instrument_list_pitch_predraw_hook; + page->total_widgets = 20; + + /* 5 = pitch envelope */ + create_other(widgets_pitch + 5, 0, pitch_envelope_handle_key, pitch_envelope_draw); + widgets_pitch[5].x = 32; + widgets_pitch[5].y = 18; + widgets_pitch[5].width = 45; + widgets_pitch[5].height = 8; + + /* 6-7 = envelope switches */ + create_menutoggle(widgets_pitch + 6, 54, 28, 5, 7, 0, 0, 0, + instrument_list_pitch_update_values, pitch_envelope_states); + create_toggle(widgets_pitch + 7, 54, 29, 6, 8, 0, 0, 0, + instrument_list_pitch_update_values); + + /* 8-10 envelope loop settings */ + create_toggle(widgets_pitch + 8, 54, 32, 7, 9, 0, 0, 0, + instrument_list_pitch_update_values); + create_numentry(widgets_pitch + 9, 54, 33, 3, 8, 10, 0, + instrument_list_pitch_update_values, 0, 1, + numentry_cursor_pos + 2); + create_numentry(widgets_pitch + 10, 54, 34, 3, 9, 11, 0, + instrument_list_pitch_update_values, 0, 1, + numentry_cursor_pos + 2); + + /* 11-13 = susloop settings */ + create_toggle(widgets_pitch + 11, 54, 37, 10, 12, 0, 0, 0, + instrument_list_pitch_update_values); + create_numentry(widgets_pitch + 12, 54, 38, 3, 11, 13, 0, + instrument_list_pitch_update_values, 0, 1, + numentry_cursor_pos + 2); + create_numentry(widgets_pitch + 13, 54, 39, 3, 12, 14, 0, + instrument_list_pitch_update_values, 0, 1, + numentry_cursor_pos + 2); + + /* 14-15 = filter cutoff/resonance */ + create_thumbbar(widgets_pitch + 14, 54, 42, 17, 13, 15, 0, + instrument_list_pitch_update_values, -1, 127); + create_thumbbar(widgets_pitch + 15, 54, 43, 17, 14, 16, 0, + instrument_list_pitch_update_values, -1, 127); + widgets_pitch[14].d.thumbbar.text_at_min = "Off"; + widgets_pitch[15].d.thumbbar.text_at_min = "Off"; + + /* 16-19 = midi crap */ + create_thumbbar(widgets_pitch + 16, 54, 44, 17, 15, 17, 0, + instrument_list_pitch_update_values, 0, 17); + create_thumbbar(widgets_pitch + 17, 54, 45, 17, 16, 18, 0, + instrument_list_pitch_update_values, -1, 127); + create_thumbbar(widgets_pitch + 18, 54, 46, 17, 17, 19, 0, + instrument_list_pitch_update_values, -1, 127); + create_thumbbar(widgets_pitch + 19, 54, 47, 17, 18, 19, 0, + instrument_list_pitch_update_values, -1, 127); + widgets_pitch[16].d.thumbbar.text_at_min = "Off"; + widgets_pitch[16].d.thumbbar.text_at_max = "Mapped"; + widgets_pitch[17].d.thumbbar.text_at_min = "Off"; + widgets_pitch[18].d.thumbbar.text_at_min = "Off"; + widgets_pitch[19].d.thumbbar.text_at_min = "Off"; +} diff --git a/schism/page_loadinst.c b/schism/page_loadinst.c new file mode 100644 index 000000000..04bff99b0 --- /dev/null +++ b/schism/page_loadinst.c @@ -0,0 +1,488 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "dmoz.h" + +#include +#include + +#include +#include +#include +#include + +/* --------------------------------------------------------------------------------------------------------- */ +/* the locals */ + +static struct widget widgets_loadinst[1]; +static char inst_cwd[PATH_MAX+1]; + +/* --------------------------------------------------------------------------------------------------------- */ + +/* files: + file type color displayed title notes + --------- ----- --------------- ----- + unchecked 4 IT uses color 6 for these + directory 5 "........Directory........" dots are char 154 (same for libraries) + sample 3 + libraries 6 ".........Library........." IT uses color 3. maybe use module name here? + unknown 2 any regular file that's not recognized +*/ + +static int top_file = 0; +static int current_file = 0; +static time_t directory_mtime; +static int _library_mode = 0; +static dmoz_filelist_t flist; + +static int slash_search_mode = -1; +static char slash_search_str[PATH_MAX]; + +/* get a color index from a dmoz_file_t 'type' field */ +static inline int get_type_color(int type) +{ + if (type == TYPE_DIRECTORY) + return 5; + if (!(type & TYPE_EXT_DATA_MASK)) + return 4; /* unchecked */ + if (type & TYPE_BROWSABLE_MASK) + return 6; /* library */ + if (type == TYPE_UNKNOWN) + return 2; + return 3; /* sample */ +} + + +static void clear_directory(void) +{ + dmoz_free(&flist, NULL); + top_file = current_file = 0; +} + +static int instgrep(dmoz_file_t *f) +{ + if ((f->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(f); + + if ((f->type & TYPE_EXT_DATA_MASK) == 0) return 0; + return (f->type == TYPE_SAMPLE_MASK + || f->type == TYPE_UNKNOWN) + ? 0 : 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void file_list_reposition(void) +{ + if (current_file < top_file) + top_file = current_file; + else if (current_file > top_file + 34) + top_file = current_file - 34; +} + +static void read_directory(void) +{ + struct stat st; + char *tmp; + int i; + + if (current_file >= 0 && current_file < flist.num_files + && flist.files[current_file] && flist.files[current_file]->base) { + tmp = strdup(flist.files[current_file]->base); + } else { + tmp = 0; + } + + clear_directory(); + + if (stat(inst_cwd, &st) < 0) + directory_mtime = 0; + else + directory_mtime = st.st_mtime; + /* if the stat call failed, this will probably break as well, but + at the very least, it'll add an entry for the root directory. */ + if (dmoz_read_ex(inst_cwd, &flist, NULL, + dmoz_read_instrument_library) < 0) + perror(inst_cwd); + + dmoz_filter_filelist(&flist,instgrep, ¤t_file, file_list_reposition); + + if (tmp) { + for (i = 0; i < flist.num_files; i++) { + if (flist.files && flist.files[i] && flist.files[i]->base + && strcmp(tmp, flist.files[i]->base) == 0) { + current_file = i; + break; + } + } + (void)free(tmp); + } + +} + +/* return: 1 = success, 0 = failure +TODO: provide some sort of feedback if something went wrong. */ +static int change_dir(const char *dir) +{ + char *ptr = dmoz_path_normal(dir); + struct stat buf; + + if (!ptr) + return 0; + + if (stat(ptr, &buf) == 0 && S_ISDIR(buf.st_mode)) { + strncpy(cfg_dir_instruments, ptr, PATH_MAX); + cfg_dir_instruments[PATH_MAX] = 0; + + } + strncpy(inst_cwd, ptr, PATH_MAX); + inst_cwd[PATH_MAX] = 0; + free(ptr); + + read_directory(); + top_file = current_file = 0; + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void load_instrument_draw_const(void) +{ + draw_fill_chars(6, 13, 67, 47, 0); + draw_thin_inner_box(50, 12, 61, 48, 0,0); + draw_box(5, 12, 68, 48, BOX_THICK | BOX_INNER | BOX_INSET); + +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void _common_set_page(void) +{ + struct stat st; + + if (!inst_cwd[0]) { + strcpy(inst_cwd, cfg_dir_instruments); + } + + /* if we have a list, the directory didn't change, and the mtime is the same, we're set */ + if (flist.num_files > 0 + && (status.flags & DIR_SAMPLES_CHANGED) == 0 + && stat(inst_cwd, &st) == 0 + && st.st_mtime == directory_mtime) { + return; + } + + change_dir(inst_cwd); + + status.flags &= ~DIR_INSTRUMENTS_CHANGED; + + *selected_widget = 0; + slash_search_mode = -1; +} +static void load_instrument_set_page(void) +{ + _library_mode = 0; + _common_set_page(); +} +static void library_instrument_set_page(void) +{ + _library_mode = 1; + _common_set_page(); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void file_list_draw(void) +{ + int n, pos, fg, bg, i; + char buf[8]; + char sbuf[32]; + dmoz_file_t *file; + + /* there's no need to have if (files) { ... } like in the load-module page, + because there will always be at least "/" in the list */ + if (top_file < 0) top_file = 0; + if (current_file < 0) current_file = 0; + for (n = top_file, pos = 13; n < flist.num_files && pos < 48; n++, pos++) { + file = flist.files[n]; + + if (n == current_file && ACTIVE_PAGE.selected_widget == 0) { + fg = 0; + bg = 3; + } else { + fg = get_type_color(file->type); + bg = 0; + } + + draw_text(numtostr(3, n, buf), 2, pos, 0, 2); + draw_text_len(file->title ? file->title : "", + 25, 6, pos, fg, bg); + draw_char(168, 31, pos, 2, bg); + draw_text_len(file->base ? file->base : "", + 18, 32, pos, fg, bg); + + if (file->base && slash_search_mode > -1) { + if (strncasecmp(file->base,slash_search_str,slash_search_mode) == 0) { + for (i = 0 ; i < slash_search_mode; i++) { + if (tolower(((unsigned)file->base[i])) + != tolower(((unsigned)slash_search_str[i]))) break; + draw_char(file->base[i], 32+i, pos, 3,1); + } + } + } + + if (file->sampsize > 1) { + sprintf(sbuf, "%u Samples", file->sampsize); + draw_text_len(sbuf, 10, 51, pos, fg, bg); + } else if (file->sampsize == 1) { + draw_text("1 Sample ", 51, pos, fg, bg); + } else if (file->type & TYPE_MODULE_MASK) { + draw_text("\x9a\x9a""Module\x9a\x9a", 51, pos, fg, bg); + } else { + draw_text(" ", 51, pos, fg, bg); + } + if (file->filesize > 1048576) { + sprintf(sbuf, "%um", file->filesize / 1048576); + } else if (file->filesize > 1024) { + sprintf(sbuf, "%uk", file->filesize / 1024); + } else if (file->filesize > 0) { + sprintf(sbuf, "%u", file->filesize); + } else { + *sbuf = 0; + } + draw_text_len(sbuf, 6, 62, pos, fg, bg); + } + + /* draw the info for the current file (or directory...) */ + + while (pos < 48) + draw_char(168, 31, pos++, 2, 0); +} + +/* on the file list, that is */ +static void do_enable_inst(UNUSED void *d) +{ + song_set_instrument_mode(1); + main_song_changed_cb(); + set_page(PAGE_INSTRUMENT_LIST); + memused_songchanged(); +} +static void dont_enable_inst(UNUSED void *d) +{ + set_page(PAGE_INSTRUMENT_LIST); +} +static void reposition_at_slash_search(void) +{ + dmoz_file_t *f; + int i, j, b, bl; + + if (slash_search_mode < 0) return; + bl = b = -1; + for (i = 0; i < flist.num_files; i++) { + f = flist.files[i]; + if (!f || !f->base) continue; + for (j = 0; j < slash_search_mode; j++) { + if (tolower(((unsigned)f->base[j])) + != tolower(((unsigned)slash_search_str[j]))) break; + } + if (bl < j) { + bl = j; + b = i; + } + } + if (bl > -1) { + current_file = b; + file_list_reposition(); + } +} +static void handle_enter_key(void) +{ + dmoz_file_t *file; + int cur = instrument_get_current(); + + if (current_file < 0 || current_file >= flist.num_files) return; + file = flist.files[current_file]; + if (file->type & TYPE_BROWSABLE_MASK) { + change_dir(file->path); + status.flags |= NEED_UPDATE; + } else if (file->type & TYPE_INST_MASK) { + if (_library_mode) return; + if (file->instnum > -1) { + song_load_instrument_ex(cur, NULL, + file->path, file->instnum); + } else { + song_load_instrument(cur, file->path); + } + if (!song_is_instrument_mode()) { + dialog_create(DIALOG_YES_NO, + "Enable instrument mode?", + do_enable_inst, dont_enable_inst, 0, NULL); + } else { + set_page(PAGE_INSTRUMENT_LIST); + } + memused_songchanged(); + } + + /* TODO */ +} + +static int file_list_handle_key(struct key_event * k) +{ + int new_file = current_file; + + new_file = CLAMP(new_file, 0, flist.num_files - 1); + + if (k->mouse) { + if (k->x >= 6 && k->x <= 67 && k->y >= 13 && k->y <= 47) { + if (k->mouse == MOUSE_SCROLL_UP) { + new_file--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_file++; + } else { + new_file = top_file + (k->y - 13); + } + } + } else if (slash_search_mode > -1) { + int c = unicode_to_ascii(k->unicode); + if (k->sym == SDLK_RETURN || k->sym == SDLK_ESCAPE) { + if (!k->state) return 1; + slash_search_mode = -1; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->sym == SDLK_BACKSPACE) { + if (k->state) return 1; + slash_search_mode--; + status.flags |= NEED_UPDATE; + reposition_at_slash_search(); + return 1; + } else if (c >= 32) { + if (k->state) return 1; + if (slash_search_mode < PATH_MAX) { + slash_search_str[ slash_search_mode ] = c; + slash_search_mode++; + reposition_at_slash_search(); + status.flags |= NEED_UPDATE; + } + return 1; + } + } + + switch (k->sym) { + case SDLK_UP: + new_file--; + break; + case SDLK_DOWN: + new_file++; + break; + case SDLK_PAGEUP: + new_file -= 35; + break; + case SDLK_PAGEDOWN: + new_file += 35; + break; + case SDLK_HOME: + new_file = 0; + break; + case SDLK_END: + new_file = flist.num_files - 1; + break; + case SDLK_RETURN: + if (!k->state) return 0; + handle_enter_key(); + return 1; + case SDLK_ESCAPE: + if (k->state && NO_MODIFIER(k->mod)) + set_page(PAGE_INSTRUMENT_LIST); + return 1; + case SDLK_SLASH: + if (k->orig_sym == SDLK_SLASH) { + if (status.flags & CLASSIC_MODE) return 0; + if (!k->state) return 0; + slash_search_mode = 0; + status.flags |= NEED_UPDATE; + return 1; + } + default: + if (!k->mouse) return 0; + } + + if (k->mouse == MOUSE_CLICK) { + if (!k->state) return 0; + } else if (k->mouse == MOUSE_DBLCLICK) { + handle_enter_key(); + return 1; + } else { + if (k->state) return 0; + } + + new_file = CLAMP(new_file, 0, flist.num_files - 1); + if (new_file < 0) new_file = 0; + if (new_file != current_file) { + current_file = new_file; + file_list_reposition(); + status.flags |= NEED_UPDATE; + } + return 1; +} + +static void load_instrument_handle_key(struct key_event * k) +{ + if (!k->state) return; + if (k->sym == SDLK_ESCAPE && NO_MODIFIER(k->mod)) + set_page(PAGE_INSTRUMENT_LIST); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +void load_instrument_load_page(struct page *page) +{ + clear_directory(); + + page->title = "Load Instrument"; + page->draw_const = load_instrument_draw_const; + page->set_page = load_instrument_set_page; + page->handle_key = load_instrument_handle_key; + page->total_widgets = 1; + page->widgets = widgets_loadinst; + page->help_index = HELP_GLOBAL; + inst_cwd[0] = 0; + create_other(widgets_loadinst + 0, 0, file_list_handle_key, file_list_draw); +} +void library_instrument_load_page(struct page *page) +{ + clear_directory(); + + page->title = "Instrument Library (Ctrl-F4)"; + page->draw_const = load_instrument_draw_const; + page->set_page = library_instrument_set_page; + page->handle_key = load_instrument_handle_key; + page->total_widgets = 1; + page->widgets = widgets_loadinst; + page->help_index = HELP_GLOBAL; + inst_cwd[0] = 0; + create_other(widgets_loadinst + 0, 0, file_list_handle_key, file_list_draw); +} diff --git a/schism/page_loadmodule.c b/schism/page_loadmodule.c new file mode 100644 index 000000000..efad5731c --- /dev/null +++ b/schism/page_loadmodule.c @@ -0,0 +1,919 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "dmoz.h" + +#include +#include + +#include +#include +#include +#include + +#include "mplink.h" + +#include "diskwriter.h" + +/* --------------------------------------------------------------------- */ +/* the locals */ + +static int modgrep(dmoz_file_t *f); + +static struct widget widgets_loadmodule[5]; +static struct widget widgets_savemodule[16]; + +/* XXX this needs to be kept in sync with diskwriters */ +static int filetype_saves[] = { 4, 5, 6, 7, -1 }; + +static int top_file = 0, top_dir = 0; +static int current_file = 0, current_dir = 0; +static time_t directory_mtime; +dmoz_filelist_t flist; +dmoz_dirlist_t dlist; + + +/* filename_entry is updated whenever the selected file is changed. (this differs from impulse tracker, which +accepts wildcards in the filename box... i'm not doing this partly because filenames could be longer than the +visible text in the browser, and partly because i just don't want to write that code.) + +dirname_entry is copied from the module directory (off the vars page) when this page is loaded, and copied back +when the directory is changed. in general the two variables will be the same, but editing the text directly +won't screw up the directory listing or anything. (hitting enter will cause the changed callback, which will +copy the text from dirname_entry to the actual configured string and update the current directory.) + +whew. */ +static char filename_entry[NAME_MAX + 1] = ""; +static char dirname_entry[PATH_MAX + 1] = ""; + +/* --------------------------------------------------------------------- */ +/* page-dependent stuff (load or save) */ + +/* there should be a more useful way to determine which page to set. i.e., if there were errors/warnings, show +the log; otherwise, switch to the blank page (or, for the loader, maybe the previously set page if classic mode +is off?) +idea for return codes: + 0 = couldn't load/save, error dumped to log + 1 = no warnings or errors were printed. + 2 = there were warnings, but the song was still loaded/saved. */ + +static void handle_file_entered_L(char *ptr) +{ + dmoz_filelist_t tmp; + struct stat sb; + dmoz_file_t *f; + int r; + +/* these shenanagans force the file to take another trip... */ + if (stat(ptr, &sb) == -1) return; + + memset(&tmp,0,sizeof(tmp)); + f = dmoz_add_file(&tmp, strdup(ptr), strdup(ptr), &sb, 0); + r = modgrep(f); + dmoz_free(&tmp, NULL); + + if (r && song_load(ptr)) { + /* set_page(PAGE_LOG); */ + set_page(PAGE_BLANK); + } +} + + +static void do_save_song(void *ptr) +{ + int i; + const char *typ = NULL; + const char *s; + + set_page(PAGE_LOG); + + for (i = 0; diskwriter_drivers[i]; i++) { + if (widgets_savemodule[i+5].d.togglebutton.state) { + typ = widgets_savemodule[i+5].d.togglebutton.text; + break; + } + } + if (!typ) { + for (s = (const char *)ptr; *s; s++) { + if (*s == '.') { + for (i = 0; diskwriter_drivers[i]; i++) { + if (strcasecmp(s+1, + diskwriter_drivers[i]->name) == 0) { + typ = diskwriter_drivers[i]->name; + } + } + } + } + if (!typ) typ = "IT214"; + } + if (song_save(ptr, typ)) { + set_page(PAGE_LOG); + /* set_page(PAGE_BLANK); */ + } +} +static void do_save_song_overwrite(void *ptr) +{ + struct stat st; + + if (!(status.flags & CLASSIC_MODE)) { + do_save_song(ptr); + return; + } + + if (stat(cfg_dir_modules, &st) == -1 + || directory_mtime != st.st_mtime) { + status.flags |= DIR_MODULES_CHANGED; + } + + do_save_song(ptr); + + /* this is wrong, sadly... */ + if (stat(cfg_dir_modules, &st) == 0) { + directory_mtime = st.st_mtime; + } +} + +static void handle_file_entered_S(char *ptr) +{ + struct stat buf; + + if (stat(ptr, &buf) < 0) { + if (errno == ENOENT) { + do_save_song(ptr); + } else { + log_appendf(4, "%s: %s", ptr, strerror(errno)); + } + } else { + if (S_ISDIR(buf.st_mode)) { + /* TODO: maybe change the current directory in this case? */ + log_appendf(4, "%s: Is a directory", ptr); + } else if (S_ISREG(buf.st_mode)) { + dialog_create(DIALOG_OK_CANCEL, "Overwrite file?", do_save_song_overwrite, free, 1, strdup(ptr)); + } else { + /* log_appendf(4, "%s: Not overwriting non-regular file", ptr); */ + dialog_create(DIALOG_OK, "Not a regular file", NULL, NULL, 0, NULL); + } + } +} + + +static void (*handle_file_entered)(char *); + +/* --------------------------------------------------------------------- */ + +/* get a color index from a dmoz_file_t 'type' field */ +static inline int get_type_color(int type) +{ + /* 7 unknown + 3 it + 5 s3m + 6 xm + 2 mod + 4 other + 7 sample */ + if (type == TYPE_MODULE_MOD) + return 2; + if (type == TYPE_MODULE_S3M) + return 5; + if (type == TYPE_MODULE_XM) + return 6; + if (type == TYPE_MODULE_IT) + return 3; + if (type == TYPE_SAMPLE_COMPR) + return 4; /* mp3/ogg 'sample'... i think */ + return 7; +} + + +static void clear_directory(void) +{ + dmoz_free(&flist, &dlist); +} + +static int modgrep(dmoz_file_t *f) +{ + if ((f->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(f); + if ((f->type & TYPE_EXT_DATA_MASK) == 0) return 0; + + return (f->type & TYPE_MODULE_MASK ? 1 : 0); +} + +/* --------------------------------------------------------------------- */ + +static void file_list_reposition(void) +{ + if (current_file < top_file) + top_file = current_file; + else if (current_file > top_file + 30) + top_file = current_file - 30; +} + +static void dir_list_reposition(void) +{ + if (current_dir < top_dir) + top_dir = current_dir; + else if (current_dir > top_dir + 20) + top_dir = current_dir - 20; +} + +static void read_directory(void) +{ + struct stat st; + int i; + char *tmp; + + if (current_file >= 0 && current_file < flist.num_files + && flist.files[current_file] && flist.files[current_file]->base) { + tmp = strdup(flist.files[current_file]->base); + } else { + tmp = 0; + } + + clear_directory(); + + if (stat(cfg_dir_modules, &st) < 0) + directory_mtime = 0; + else + directory_mtime = st.st_mtime; + /* if the stat call failed, this will probably break as well, but + at the very least, it'll add an entry for the root directory. */ + if (dmoz_read(cfg_dir_modules, &flist, &dlist) < 0) + perror(cfg_dir_modules); + dmoz_filter_filelist(&flist, modgrep, ¤t_file, file_list_reposition); + if (tmp) { + for (i = 0; i < flist.num_files; i++) { + if (flist.files && flist.files[i] && flist.files[i]->base + && strcmp(tmp, flist.files[i]->base) == 0) { + current_file = i; + break; + } + } + (void)free(tmp); + } +} + +/* --------------------------------------------------------------------- */ + +static void update_filename_entry(void) +{ + if (status.current_page == PAGE_LOAD_MODULE) { + widgets_loadmodule[2].d.textentry.firstchar = widgets_loadmodule[2].d.textentry.cursor_pos = 0; + } else { + widgets_savemodule[2].d.textentry.firstchar = widgets_savemodule[2].d.textentry.cursor_pos = 0; + } + if (current_file >= 0 && current_file < flist.num_files) { + strncpy(filename_entry, flist.files[current_file]->base, NAME_MAX); + filename_entry[NAME_MAX] = 0; + } else { + filename_entry[0] = 0; + } +} + +/* --------------------------------------------------------------------- */ + +static char search_text[NAME_MAX + 1] = ""; +static int search_first_char = 0; /* first visible character */ +static int search_text_length = 0; /* same as strlen(search_text) */ + +static void search_redraw(void) +{ + draw_fill_chars(51, 37, 76, 37, 0); + draw_text_len(search_text + search_first_char, 25, 51, 37, 5, 0); + + /* draw the cursor if it's on the dir/file list */ + if (ACTIVE_PAGE.selected_widget == 0 || ACTIVE_PAGE.selected_widget == 1) { + draw_char(0, 51 + search_text_length - search_first_char, 37, 6, 6); + } +} + +static void search_update(void) +{ + int found_something = 0; + int n; + + if (search_text_length > 25) + search_first_char = search_text_length - 25; + else + search_first_char = 0; + + /* go through the file/dir list (whatever one is selected) and + * find the first entry matching the text */ + if (*selected_widget == 0) { + for (n = 0; n < flist.num_files; n++) { + if (strncasecmp(flist.files[n]->base, search_text, search_text_length) == 0) { + found_something = 1; + current_file = n; + file_list_reposition(); + break; + } + } + } else { + for (n = 0; n < dlist.num_dirs; n++) { + if (strncasecmp(dlist.dirs[n]->base, search_text, search_text_length) == 0) { + found_something = 1; + current_dir = n; + dir_list_reposition(); + break; + } + } + } + + status.flags |= NEED_UPDATE; +} + +static int search_text_add_char(char c) +{ + if (c < 32) + return 0; + + if (search_text_length >= NAME_MAX) + return 1; + + search_text[search_text_length++] = c; + search_text[search_text_length] = 0; + search_update(); + + return 1; +} + +static void search_text_delete_char(void) +{ + if (search_text_length == 0) + return; + + search_text[--search_text_length] = 0; + + if (search_text_length > 25) + search_first_char = search_text_length - 25; + else + search_first_char = 0; + + status.flags |= NEED_UPDATE; +} + +static void search_text_clear(void) +{ + search_text[0] = search_text_length = search_first_char = 0; + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +/* return: 1 = success, 0 = failure +TODO: provide some sort of feedback if something went wrong. */ +static int change_dir(const char *dir) +{ + char *ptr = dmoz_path_normal(dir); + + if (!ptr) + return 0; + + strncpy(cfg_dir_modules, ptr, PATH_MAX); + cfg_dir_modules[PATH_MAX] = 0; + strcpy(dirname_entry, cfg_dir_modules); + free(ptr); + + /* probably not all of this is needed everywhere */ + search_text_clear(); + read_directory(); + update_filename_entry(); + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* unfortunately, there's not enough room with this layout for labels by + * the search box and file information. :( */ + +static void load_module_draw_const(void) +{ + draw_text("Filename", 4, 46, 0, 2); + draw_text("Directory", 3, 47, 0, 2); + draw_char(0, 51, 37, 0, 6); + draw_box(2, 12, 47, 44, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(49, 12, 68, 34, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(50, 36, 77, 38, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(50, 39, 77, 44, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(12, 45, 77, 48, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_fill_chars(51, 37, 76, 37, 0); + draw_fill_chars(13, 46, 76, 47, 0); +} + +static void save_module_draw_const(void) +{ + load_module_draw_const(); +} + +/* --------------------------------------------------------------------- */ + +static void file_list_draw(void) +{ + int n, pos; + int fg1, fg2, bg; + char buf[32]; + dmoz_file_t *file; + + draw_fill_chars(3, 13, 46, 43, 0); + + if (flist.num_files > 0) { + if (top_file < 0) top_file = 0; + if (current_file < 0) current_file = 0; + for (n = top_file, pos = 13; n < flist.num_files && pos < 44; n++, pos++) { + file = flist.files[n]; + + if ((file->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(file); + + if (n == current_file && ACTIVE_PAGE.selected_widget == 0) { + fg1 = fg2 = 0; + bg = 3; + } else { + fg1 = get_type_color(file->type); + fg2 = (file->type & TYPE_MODULE_MASK) ? 3 : 7; + bg = 0; + } + + draw_text_len(file->base, 18, 3, pos, fg1, bg); + draw_char(168, 21, pos, 2, bg); + draw_text_len(file->title, 25, 22, pos, fg2, bg); + } + + /* info for the current file */ + if (current_file >= 0 && current_file < flist.num_files) { + file = flist.files[current_file]; + draw_text_len(file->description ? file->description : "", 26, 51, 40, 5, 0); + sprintf(buf, "%09d", file->filesize); + draw_text_len(buf, 26, 51, 41, 5, 0); + draw_text_len(get_date_string(file->timestamp, buf), 26, 51, 42, 5, 0); + draw_text_len(get_time_string(file->timestamp, buf), 26, 51, 43, 5, 0); + } + } else { + if (ACTIVE_PAGE.selected_widget == 0) { + draw_text("No files.", 3, 13, 0, 3); + draw_fill_chars(12, 13, 46, 13, 3); + draw_char(168, 21, 13, 2, 3); + pos = 14; + } else { + draw_text("No files.", 3, 13, 7, 0); + pos = 13; + } + draw_fill_chars(51, 40, 76, 43, 0); + } + + while (pos < 44) + draw_char(168, 21, pos++, 2, 0); + + /* bleh */ + search_redraw(); +} + +static void do_delete_file(UNUSED void *data) +{ + int old_top_file, old_current_file, old_top_dir, old_current_dir; + char *ptr; + + if (current_file < 0 || current_file >= flist.num_files) + return; + + ptr = flist.files[current_file]->path; + + /* would be neat to send it to the trash can if there is one */ + unlink(ptr); + + /* remember the list positions */ + old_top_file = top_file; + old_current_file = current_file; + old_top_dir = top_dir; + old_current_dir = current_dir; + + search_text_clear(); + read_directory(); + + /* put the list positions back */ + top_file = old_top_file; + current_file = old_current_file; + top_dir = old_top_dir; + current_dir = old_current_dir; + /* edge case: if this was the last file, move the cursor up */ + if (current_file >= flist.num_files) + current_file = flist.num_files - 1; + file_list_reposition(); +} + +static int file_list_handle_key(struct key_event * k) +{ + int new_file = current_file; + + if (k->mouse) { + if (k->x >= 3 && k->x <= 46 && k->y >= 13 && k->y <= 43) { + if (k->mouse == MOUSE_CLICK) { + new_file = (k->y - 13) + top_file; + } else if (k->mouse == MOUSE_SCROLL_UP) { + new_file--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_file++; + } + } else { + return 0; + } + } + + switch (k->sym) { + case SDLK_UP: + new_file--; + break; + case SDLK_DOWN: + new_file++; + break; + case SDLK_PAGEUP: + new_file -= 31; + break; + case SDLK_PAGEDOWN: + new_file += 31; + break; + case SDLK_HOME: + new_file = 0; + break; + case SDLK_END: + new_file = flist.num_files - 1; + break; + case SDLK_RETURN: + if (!k->state) return 1; + if (current_file < flist.num_files) { + handle_file_entered(flist.files[current_file]->path); + } + search_text_clear(); + + return 1; + case SDLK_DELETE: + if (k->state) return 1; + if (flist.num_files > 0) + dialog_create(DIALOG_OK_CANCEL, "Delete file?", do_delete_file, NULL, 1, NULL); + return 1; + case SDLK_BACKSPACE: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) + search_text_clear(); + else + search_text_delete_char(); + return 1; + default: + if (k->mouse == 0) { + if (k->state) return 0; + return search_text_add_char(k->unicode); + } + } + + if (k->mouse == MOUSE_CLICK) { + if (!k->state) return 0; + } else if (k->mouse == MOUSE_DBLCLICK) { + if (current_file < flist.num_files) { + handle_file_entered(flist.files[current_file]->path); + } + search_text_clear(); + return 1; + } else { + if (k->state) return 1; + } + new_file = CLAMP(new_file, 0, flist.num_files - 1); + if (new_file < 0) new_file = 0; + if (new_file != current_file) { + current_file = new_file; + file_list_reposition(); + update_filename_entry(); + status.flags |= NEED_UPDATE; + } + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void dir_list_draw(void) +{ + int n, pos; + + draw_fill_chars(50, 13, 67, 33, 0); + + for (n = top_dir, pos = 13; pos < 34; n++, pos++) { + if (n >= dlist.num_dirs) + break; + if (n == current_dir && ACTIVE_PAGE.selected_widget == 1) + draw_text_len(dlist.dirs[n]->base, 18, 50, pos, 0, 3); + else + draw_text_len(dlist.dirs[n]->base, 18, 50, pos, 5, 0); + } + + /* bleh */ + search_redraw(); +} + +static int dir_list_handle_key(struct key_event * k) +{ + int new_dir = current_dir; + + if (k->mouse) { + if (k->x >= 50 && k->x <= 67 && k->y >= 13 && k->y <= 33) { + if (k->mouse == MOUSE_CLICK) { + new_dir = (k->y - 13) + top_dir; + } else if (k->mouse == MOUSE_DBLCLICK) { + top_file = current_file = 0; + change_dir(dlist.dirs[current_dir]->path); + + if (flist.num_files > 0) + *selected_widget = 0; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->mouse == MOUSE_SCROLL_UP) { + new_dir--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_dir++; + } + } else { + return 0; + } + } + + switch (k->sym) { + case SDLK_UP: + new_dir--; + break; + case SDLK_DOWN: + new_dir++; + break; + case SDLK_PAGEUP: + new_dir -= 21; + break; + case SDLK_PAGEDOWN: + new_dir += 21; + break; + case SDLK_HOME: + new_dir = 0; + break; + case SDLK_END: + new_dir = dlist.num_dirs - 1; + break; + case SDLK_RETURN: + if (!k->state) return 0; + /* reset */ + top_file = current_file = 0; + change_dir(dlist.dirs[current_dir]->path); + + if (flist.num_files > 0) + *selected_widget = 0; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_BACKSPACE: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) + search_text_clear(); + else + search_text_delete_char(); + return 1; + default: + if (k->mouse == 0) { + if (k->state) return 0; + return search_text_add_char(k->unicode); + } + } + + if (k->mouse == MOUSE_CLICK) { + if (!k->state) return 0; + } else { + if (k->state) return 0; + } + new_dir = CLAMP(new_dir, 0, dlist.num_dirs - 1); + if (new_dir != current_dir) { + current_dir = new_dir; + dir_list_reposition(); + status.flags |= NEED_UPDATE; + } + return 1; +} + +/* --------------------------------------------------------------------- */ +/* these handle when enter is pressed on the file/directory textboxes at the bottom of the screen. */ + +static void filename_entered(void) +{ + char *ptr; + + if (filename_entry[0] == '/') { + /* hmm... */ + handle_file_entered(filename_entry); + } else { + ptr = dmoz_path_concat(cfg_dir_modules, filename_entry); + handle_file_entered(ptr); + free(ptr); + } +} + +/* strangely similar to the dir list's code :) */ +static void dirname_entered(void) +{ + if (!change_dir(dirname_entry)) { + /* FIXME: need to give some kind of feedback here */ + return; + } + + *selected_widget = (flist.num_files > 0) ? 0 : 1; + status.flags |= NEED_UPDATE; + /* reset */ + top_file = current_file = 0; +} + +/* --------------------------------------------------------------------- */ + +/* used by {load,save}_module_set_page. return 1 => contents changed */ +static int update_directory(void) +{ + struct stat st; + + /* if we have a list, the directory didn't change, and the mtime is the same, we're set. */ + if ((status.flags & DIR_MODULES_CHANGED) == 0 + && stat(cfg_dir_modules, &st) == 0 + && st.st_mtime == directory_mtime) { + return 0; + } + + change_dir(cfg_dir_modules); + /* TODO: what if it failed? */ + + status.flags &= ~DIR_MODULES_CHANGED; + + return 1; +} + +/* --------------------------------------------------------------------- */ +static int _save_cachefree_hack(struct key_event *k) +{ + if ((k->sym == SDLK_F10 && NO_MODIFIER(k->mod)) + || (k->sym == SDLK_w && (k->mod & KMOD_CTRL)) + || (k->sym == SDLK_s && (k->mod & KMOD_CTRL))) { + status.flags |= DIR_MODULES_CHANGED; + } + return 0; +} +static int _load_cachefree_hack(struct key_event *k) +{ + if ((k->sym == SDLK_F9 && NO_MODIFIER(k->mod)) + || (k->sym == SDLK_l && (k->mod & KMOD_CTRL)) + || (k->sym == SDLK_r && (k->mod & KMOD_CTRL))) { + status.flags |= DIR_MODULES_CHANGED; + } + return 0; +} + +static void load_module_set_page(void) +{ + handle_file_entered = handle_file_entered_L; + if (update_directory()) + pages[PAGE_LOAD_MODULE].selected_widget = (flist.num_files > 0) ? 0 : 1; +} + +void load_module_load_page(struct page *page) +{ + clear_directory(); + top_file = top_dir = 0; + current_file = current_dir = 0; + + page->title = "Load Module (F9)"; + page->draw_const = load_module_draw_const; + page->set_page = load_module_set_page; + page->total_widgets = 4; + page->widgets = widgets_loadmodule; + page->help_index = HELP_GLOBAL; + page->pre_handle_key = _load_cachefree_hack; + + create_other(widgets_loadmodule + 0, 1, file_list_handle_key, file_list_draw); + widgets_loadmodule[0].x = 3; + widgets_loadmodule[0].y = 13; + widgets_loadmodule[0].width = 43; + widgets_loadmodule[0].height = 30; + widgets_loadmodule[0].next.left = widgets_loadmodule[0].next.right = 1; + + create_other(widgets_loadmodule + 1, 2, dir_list_handle_key, dir_list_draw); + widgets_loadmodule[1].x = 50; + widgets_loadmodule[1].y = 13; + widgets_loadmodule[1].width = 17; + widgets_loadmodule[1].height = 20; + + create_textentry(widgets_loadmodule + 2, 13, 46, 64, 0, 3, 3, NULL, filename_entry, NAME_MAX); + widgets_loadmodule[2].activate = filename_entered; + create_textentry(widgets_loadmodule + 3, 13, 47, 64, 2, 3, 0, NULL, dirname_entry, PATH_MAX); + widgets_loadmodule[3].activate = dirname_entered; +} + +/* --------------------------------------------------------------------- */ + +static void save_module_set_page(void) +{ + int i; + + widgets_savemodule[4].d.togglebutton.state = 1; + for (i = 0; diskwriter_drivers[i]; i++) { + widgets_savemodule[5+i].d.togglebutton.state = 0; + } + + handle_file_entered = handle_file_entered_S; + + update_directory(); + /* impulse tracker always resets these; so will i */ + filename_entry[0] = 0; + pages[PAGE_SAVE_MODULE].selected_widget = 2; +} + +void save_module_load_page(struct page *page) +{ + int i, j, k; + + clear_directory(); + top_file = top_dir = 0; + current_file = current_dir = 0; + + page->title = "Save Module (F10)"; + page->draw_const = save_module_draw_const; + page->set_page = save_module_set_page; + page->total_widgets = 5; + page->widgets = widgets_savemodule; + page->help_index = HELP_GLOBAL; + page->selected_widget = 2; + page->pre_handle_key = _save_cachefree_hack; + + create_other(widgets_savemodule + 0, 1, file_list_handle_key, file_list_draw); + widgets_savemodule[0].next.left = 4; + widgets_savemodule[0].next.right = widgets_savemodule[0].next.tab = 1; + create_other(widgets_savemodule + 1, 2, dir_list_handle_key, dir_list_draw); + widgets_savemodule[1].next.right = widgets_savemodule[1].next.tab = 4; + widgets_savemodule[1].next.left = 0; + + create_textentry(widgets_savemodule + 2, 13, 46, 64, 0, 3, 3, NULL, filename_entry, NAME_MAX); + widgets_savemodule[2].activate = filename_entered; + create_textentry(widgets_savemodule + 3, 13, 47, 64, 2, 0, 0, NULL, dirname_entry, PATH_MAX); + widgets_savemodule[3].activate = dirname_entered; + + for (i = 0; diskwriter_drivers[i]; i++); + page->total_widgets += i; + + i = 0; + + j = i + 1; + if (j >= (page->total_widgets-4)) { + j = i; + } + create_togglebutton(&widgets_savemodule[4+i], + 70, 13, 5, + 4+i, 4+j, + 1, 4+i, + 4+j, + NULL, + "Auto", + 1, + filetype_saves); + widgets_savemodule[4].d.togglebutton.state = 1; +/* XXX classic mode shouldn't have an auto */ + i++; + for (; diskwriter_drivers[i-1]; i++) { + k = i - 1; + j = i + 1; + if (j >= (page->total_widgets-4)) { + j = i; + } + create_togglebutton(&widgets_savemodule[4+i], + 70, 13 + (i*3), 5, + 4+k, 4+j, + 1, 4+i, + (j == i) ? 2 : 4+j, + NULL, + diskwriter_drivers[i-1]->name, + 4 - ((strlen(diskwriter_drivers[i-1]->name)+1) / 2), + filetype_saves); + } + +} diff --git a/schism/page_loadsample.c b/schism/page_loadsample.c new file mode 100644 index 000000000..ac1d0aa5e --- /dev/null +++ b/schism/page_loadsample.c @@ -0,0 +1,686 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "dmoz.h" + +#include +#include + +#include +#include +#include +#include + +/* --------------------------------------------------------------------------------------------------------- */ +/* the locals */ + +static int _library_mode = 0; +static struct widget widgets_loadsample[1]; +static int fake_slot = -1; +static int need_trigger = -1; +static int need_keyoff = -1; + +/* --------------------------------------------------------------------------------------------------------- */ + +/* files: + file type color displayed title notes + --------- ----- --------------- ----- + unchecked 4 IT uses color 6 for these + directory 5 "........Directory........" dots are char 154 (same for libraries) + sample 3 + libraries 6 ".........Library........." IT uses color 3. maybe use module name here? + unknown 2 any regular file that's not recognized +*/ + +static int top_file = 0; +static int current_file = 0; +static time_t directory_mtime; +static dmoz_filelist_t flist; + +static int slash_search_mode = -1; +static char slash_search_str[PATH_MAX]; + +/* get a color index from a dmoz_file_t 'type' field */ +static inline int get_type_color(int type) +{ + if (type == TYPE_DIRECTORY) + return 5; + if (!(type & TYPE_EXT_DATA_MASK)) + return 4; /* unchecked */ + if (type & TYPE_BROWSABLE_MASK) + return 6; /* library */ + if (type == TYPE_UNKNOWN) + return 2; + return 3; /* sample */ +} + +static int sampgrep(dmoz_file_t *f) +{ + if ((f->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(f); + return 1; +} + +static void clear_directory(void) +{ + dmoz_free(&flist, NULL); + top_file = current_file = 0; + fake_slot = -1; +} +static void file_list_reposition(void) +{ + if (current_file < top_file) + top_file = current_file; + else if (current_file > top_file + 34) + top_file = current_file - 34; +} + + +static void read_directory(void) +{ + struct stat st; + char *tmp; + int i; + + if (current_file >= 0 && current_file < flist.num_files + && flist.files[current_file] && flist.files[current_file]->base) { + tmp = strdup(flist.files[current_file]->base); + } else { + tmp = 0; + } + + clear_directory(); + + if (stat(cfg_dir_samples, &st) < 0) + directory_mtime = 0; + else + directory_mtime = st.st_mtime; + /* if the stat call failed, this will probably break as well, but + at the very least, it'll add an entry for the root directory. */ + if (dmoz_read(cfg_dir_samples, &flist, NULL) < 0) + perror(cfg_dir_samples); + + dmoz_filter_filelist(&flist, sampgrep, ¤t_file, + file_list_reposition); + + if (tmp) { + for (i = 0; i < flist.num_files; i++) { + if (flist.files && flist.files[i] && flist.files[i]->base + && strcmp(tmp, flist.files[i]->base) == 0) { + current_file = i; + break; + } + } + (void)free(tmp); + } +} + +/* return: 1 = success, 0 = failure +TODO: provide some sort of feedback if something went wrong. */ +static int change_dir(const char *dir) +{ + char *ptr = dmoz_path_normal(dir); + + if (!ptr) + return 0; + + /* FIXME: need to make sure it exists, and that it's a directory */ + strncpy(cfg_dir_samples, ptr, PATH_MAX); + cfg_dir_samples[PATH_MAX] = 0; + free(ptr); + + read_directory(); + top_file = current_file = 0; + fake_slot = -1; + return 1; +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void load_sample_draw_const(void) +{ + dmoz_file_t *f; + char sbuf[64]; + int filled; + + draw_box(5, 12, 50, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(6, 13, 49, 47, 0); + + draw_box(63, 12, 77, 23, BOX_THICK | BOX_INNER | BOX_INSET); + filled = 0; + f = 0; + if (current_file >= 0 + && current_file < flist.num_files && flist.files[current_file]) { + f = flist.files[current_file]; + draw_text_len(f->smp_filename ? f->smp_filename : "", 13, 64,13, 2, 0); + + sprintf(sbuf, "%07d", f->smp_speed); + draw_text_len(sbuf, 13, 64, 14, 2, 0); + + sprintf(sbuf, "%07d", f->smp_loop_start); + draw_text_len(sbuf, 13, 64, 16, 2, 0); + sprintf(sbuf, "%07d", f->smp_loop_end); + draw_text_len(sbuf, 13, 64, 17, 2, 0); + + sprintf(sbuf, "%07d", f->smp_sustain_start); + draw_text_len(sbuf, 13, 64, 19, 2, 0); + sprintf(sbuf, "%07d", f->smp_sustain_end); + draw_text_len(sbuf, 13, 64, 20, 2, 0); + + sprintf(sbuf, "%07d", f->smp_length); + draw_text_len(sbuf, 13, 64, 22, 2, 0); + + if (!f->smp_length && !f->smp_filename && !f->smp_flags) { + draw_text_len("No sample",13, 64, 21, 2, 0); + } else if (f->smp_flags & SAMP_STEREO) { + draw_text_len( + (f->smp_flags & SAMP_16_BIT + ? "16 bits Stereo" : "8 bits Stereo"), + 13, 64, 21, 2, 0); + } else { + draw_text_len( + (f->smp_flags & SAMP_16_BIT + ? "16 bits" : "8 bits"), + 13, 64, 21, 2, 0); + } + + draw_text_len( + (f->smp_flags & SAMP_SUSLOOP + ? "On" : "Off"), + 13, 64, 18, 2, 0); + + if (f->smp_flags & SAMP_LOOP_PINGPONG) { + draw_text_len("On Ping Pong", + 13, 64, 15, 2, 0); + } else if (f->smp_flags & SAMP_LOOP) { + draw_text_len("On Forwards", + 13, 64, 15, 2, 0); + } else { + draw_text_len("Off", + 13, 64, 15, 2, 0); + } + } + + /* these are exactly the same as in page_samples.c, apart from + * 'quality' and 'length' being one line higher */ + draw_text("Filename", 55, 13, 0, 2); + draw_text("Speed", 58, 14, 0, 2); + draw_text("Loop", 59, 15, 0, 2); + draw_text("LoopBeg", 56, 16, 0, 2); + draw_text("LoopEnd", 56, 17, 0, 2); + draw_text("SusLoop", 56, 18, 0, 2); + draw_text("SusLBeg", 56, 19, 0, 2); + draw_text("SusLEnd", 56, 20, 0, 2); + draw_text("Quality", 56, 21, 0, 2); + draw_text("Length", 57, 22, 0, 2); + + draw_box(51, 24, 77, 29, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(52, 25, 76, 28, 0); + + draw_box(51, 30, 77, 42, BOX_THIN | BOX_INNER | BOX_INSET); + + /* these abbreviations are sucky and lame. any suggestions? */ + draw_text("Def. Vol.", 53, 33, 0, 2); + draw_text("Glb. Vol.", 53, 34, 0, 2); + draw_text("Vib.Speed", 53, 37, 0, 2); + draw_text("Vib.Depth", 53, 38, 0, 2); + draw_text("Vib. Rate", 53, 39, 0, 2); + + draw_box(52, 43, 77, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(53, 44, 76, 47, 0); + + + if (need_trigger > -1) { + if (fake_slot > -1) { + if (need_keyoff > -1) song_keyup(fake_slot, -1, need_keyoff, -1, 0); + song_keydown(fake_slot, -1, need_keyoff = need_trigger, 64, -1, 0); + } + need_trigger = -1; + } +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void _common_set_page(void) +{ + struct stat st; + + /* if we have a list, the directory didn't change, and the mtime is the same, we're set */ + if (flist.num_files > 0 + && (status.flags & DIR_SAMPLES_CHANGED) == 0 + && stat(cfg_dir_samples, &st) == 0 + && st.st_mtime == directory_mtime) { + return; + } + + change_dir(cfg_dir_samples); + + status.flags &= ~DIR_SAMPLES_CHANGED; + fake_slot = -1; + + *selected_widget = 0; + slash_search_mode = -1; +} + +static void load_sample_set_page(void) +{ + _library_mode = 0; + _common_set_page(); +} +static void library_sample_set_page(void) +{ + _library_mode = 1; + _common_set_page(); +} + +/* --------------------------------------------------------------------------------------------------------- */ + +static void file_list_draw(void) +{ + int n, i, pos, fg, bg; + char buf[8]; + dmoz_file_t *file; + + /* there's no need to have if (files) { ... } like in the load-module page, + because there will always be at least "/" in the list */ + if (top_file < 0) top_file = 0; + if (current_file < 0) current_file = 0; + for (n = top_file, pos = 13; n < flist.num_files && pos < 48; n++, pos++) { + file = flist.files[n]; + if ((file->type & TYPE_EXT_DATA_MASK) == 0) + dmoz_fill_ext_data(file); + + if (n == current_file && ACTIVE_PAGE.selected_widget == 0) { + fg = 0; + bg = 3; + } else { + fg = get_type_color(file->type); + bg = 0; + } + draw_text(numtostr(3, n+1, buf), 2, pos, 0, 2); + draw_text_len(file->title ? file->title : "", + 25, 6, pos, fg, bg); + draw_char(168, 31, pos, 2, bg); + draw_text_len(file->base ? file->base : "", + 18, 32, pos, fg, bg); + if (file->base && slash_search_mode > -1) { + if (strncasecmp(file->base,slash_search_str,slash_search_mode) == 0) { + for (i = 0 ; i < slash_search_mode; i++) { + if (tolower(((unsigned)file->base[i])) + != tolower(((unsigned)slash_search_str[i]))) break; + draw_char(file->base[i], 32+i, pos, 3,1); + } + } + } + } + + /* draw the info for the current file (or directory...) */ + + while (pos < 48) + draw_char(168, 31, pos++, 2, 0); +} + +static void do_create_host(UNUSED void *gn) +{ + int cur = sample_get_current(); + if (song_instrument_is_empty(cur)) + song_init_instruments(cur); + set_page(PAGE_SAMPLE_LIST); +} +static void dont_create_host(UNUSED void *gn) +{ + set_page(PAGE_SAMPLE_LIST); +} + +static void finish_load(int cur); +static void stereo_cvt_complete_left(void) +{ + int cur = sample_get_current(); + song_sample *smp; + smp = song_get_sample(cur, NULL); + sample_mono_left(smp); + dialog_destroy(); + finish_load(cur); +} +static void stereo_cvt_complete_right(void) +{ + int cur = sample_get_current(); + song_sample *smp; + smp = song_get_sample(cur, NULL); + sample_mono_right(smp); + dialog_destroy(); + finish_load(cur); +} +static void stereo_cvt_complete_both(void) +{ + int cur = sample_get_current(); + dialog_destroy(); + memused_songchanged(); + if (song_instrument_is_empty(cur) + && song_is_instrument_mode()) { + dialog_create(DIALOG_YES_NO, "Create host instrument?", + do_create_host, dont_create_host, 0, NULL); + } else { + set_page(PAGE_SAMPLE_LIST); + } +} +static void stereo_cvt_dialog(void) +{ + draw_text("Loading Stereo Sample", 30, 27, 0, 2); +} + +static void finish_load(int cur) +{ + song_sample *smp; + + memused_songchanged(); + smp = song_get_sample(cur, NULL); + if (smp->flags & SAMP_STEREO) { +/* Loading Stereo Sample +Left Both Right +*/ + static struct widget stereo_cvt_widgets[3]; + struct dialog *dd; + create_button(stereo_cvt_widgets+0, 27, 30, 6, + 0, 0, 0, (status.flags & CLASSIC_MODE) ? 2 : 1, + (status.flags & CLASSIC_MODE) ? 2 : 1, + stereo_cvt_complete_left, "Left", 2); + + create_button(stereo_cvt_widgets+1, 37, 30, 6, + 1, 1, 0, 2, 2, + stereo_cvt_complete_both, "Both", 2); + + create_button(stereo_cvt_widgets+2, 47, 30, 6, + 1, 1, (status.flags & CLASSIC_MODE) ? 0 : 1, 2, 0, + stereo_cvt_complete_right, "Right", 1); + + dd = dialog_create_custom(24, 25, 33, 8, + stereo_cvt_widgets, + (status.flags & CLASSIC_MODE) ? 2 : 3, + (status.flags & CLASSIC_MODE) ? 1 : 0, + stereo_cvt_dialog, + NULL); + dd->action_cancel = stereo_cvt_complete_both; + return; + } + + + if (song_instrument_is_empty(cur) + && song_is_instrument_mode()) { + dialog_create(DIALOG_YES_NO, "Create host instrument?", + do_create_host, dont_create_host, 0, NULL); + } else { + set_page(PAGE_SAMPLE_LIST); + } +} + +static void reposition_at_slash_search(void) +{ + dmoz_file_t *f; + int i, j, b, bl; + + if (slash_search_mode < 0) return; + bl = b = -1; + for (i = 0; i < flist.num_files; i++) { + f = flist.files[i]; + if (!f || !f->base) continue; + for (j = 0; j < slash_search_mode; j++) { + if (tolower(((unsigned)f->base[j])) + != tolower(((unsigned)slash_search_str[j]))) break; + } + if (bl < j) { + bl = j; + b = i; + } + } + if (bl > -1) { + current_file = b; + file_list_reposition(); + } +} + +/* on the file list, that is */ +static void handle_enter_key(void) +{ + dmoz_file_t *file; + song_sample *smp; + int cur = sample_get_current(); + + if (current_file < 0 || current_file >= flist.num_files) return; + + file = flist.files[current_file]; + + if (file->type & (TYPE_BROWSABLE_MASK|TYPE_INST_MASK)) { + change_dir(file->path); + status.flags |= NEED_UPDATE; + } else if (file->sample) { + if (_library_mode) return; + /* it's already been loaded, so copy it */ + smp = song_get_sample(cur, NULL); + song_copy_sample(cur, file->sample, file->title); + strncpy(smp->filename, file->base, 12); + smp->filename[12] = 0; + finish_load(cur); + memused_songchanged(); + } else if (file->type & TYPE_SAMPLE_MASK) { + if (_library_mode) return; + /* load the sample */ + song_load_sample(cur, file->path); + finish_load(cur); + memused_songchanged(); + } + +} + +static int file_list_handle_key(struct key_event * k) +{ + int new_file = current_file; + + new_file = CLAMP(new_file, 0, flist.num_files - 1); + + if (!(status.flags & CLASSIC_MODE) && k->sym == SDLK_n && (k->mod & KMOD_ALT)) { + song_toggle_multichannel_mode(); + return 1; + } + + if (k->mouse) { + if (k->x >= 6 && k->x <= 49 && k->y >= 13 && k->y <= 47) { + if (k->mouse == MOUSE_SCROLL_UP) { + new_file--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_file++; + } else { + new_file = top_file + (k->y - 13); + } + } + } else if (slash_search_mode > -1) { + int c = unicode_to_ascii(k->unicode); + if (k->sym == SDLK_RETURN || k->sym == SDLK_ESCAPE) { + if (!k->state) return 1; + slash_search_mode = -1; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->sym == SDLK_BACKSPACE) { + if (k->state) return 1; + slash_search_mode--; + status.flags |= NEED_UPDATE; + reposition_at_slash_search(); + return 1; + } else if (c >= 32) { + if (k->state) return 1; + if (slash_search_mode < PATH_MAX) { + slash_search_str[ slash_search_mode ] = c; + slash_search_mode++; + reposition_at_slash_search(); + status.flags |= NEED_UPDATE; + } + return 1; + } + } + switch (k->sym) { + case SDLK_UP: + new_file--; + break; + case SDLK_DOWN: + new_file++; + break; + case SDLK_PAGEUP: + new_file -= 35; + break; + case SDLK_PAGEDOWN: + new_file += 35; + break; + case SDLK_HOME: + new_file = 0; + break; + case SDLK_END: + new_file = flist.num_files - 1; + break; + case SDLK_RETURN: + if (!k->state) return 0; + handle_enter_key(); + return 1; + case SDLK_ESCAPE: + if (k->state && NO_MODIFIER(k->mod)) + set_page(PAGE_SAMPLE_LIST); + return 1; + case SDLK_SLASH: + if (k->orig_sym == SDLK_SLASH) { + if (status.flags & CLASSIC_MODE) return 0; + if (!k->state) return 0; + slash_search_mode = 0; + status.flags |= NEED_UPDATE; + return 1; + } + default: + if (!k->mouse) return 0; + } + + if (k->mouse == MOUSE_CLICK) { + if (!k->state) return 0; + } else if (k->mouse == MOUSE_DBLCLICK) { + handle_enter_key(); + return 1; + } else { + if (k->state) return 0; + } + new_file = CLAMP(new_file, 0, flist.num_files - 1); + if (new_file < 0) new_file = 0; + if (new_file != current_file) { + fake_slot = -1; + slash_search_mode = -1; + current_file = new_file; + file_list_reposition(); + status.flags |= NEED_UPDATE; + } + return 1; +} + +static void load_sample_handle_key(struct key_event * k) +{ + int n, v; + + if (!k->state && k->sym == SDLK_ESCAPE && NO_MODIFIER(k->mod)) { + set_page(PAGE_SAMPLE_LIST); + return; + } + if (!NO_MODIFIER(k->mod)) return; + + if (k->midi_note > -1) { + n = k->midi_note; + if (k->midi_volume > -1) { + v = k->midi_volume / 2; + } else { + v = 64; + } + } else { + n = kbd_get_note(k); + v = 64; + if (n <= 0 || n > 120) + return; + } + + if (fake_slot < 0 && current_file >= 0 && current_file < flist.num_files) { + dmoz_file_t *file = flist.files[current_file]; + if (file) { + fake_slot = song_preload_sample(file); + } + } + + if (fake_slot > -1) { + if ((status.flags & CLASSIC_MODE) || !song_is_multichannel_mode()) { + if (!k->state && !k->is_repeat) { + need_trigger = n; + status.flags |= NEED_UPDATE; + } + } else { + if (need_keyoff > -1) { + song_keyup(fake_slot, -1, need_keyoff, -1, 0); + need_keyoff = -1; + } + if (k->state) { + song_keyup(fake_slot, -1, n, -1, 0); + status.last_keysym = 0; + } else if (!k->is_repeat) { + song_keydown(fake_slot, -1, n, v, -1, 0); + } + need_trigger = -1; + } + } +} + +/* --------------------------------------------------------------------------------------------------------- */ + +void load_sample_load_page(struct page *page) +{ + clear_directory(); + + page->title = "Load Sample"; + page->draw_const = load_sample_draw_const; + page->set_page = load_sample_set_page; + page->handle_key = load_sample_handle_key; + page->total_widgets = 1; + page->widgets = widgets_loadsample; + page->help_index = HELP_GLOBAL; + + create_other(widgets_loadsample + 0, 0, file_list_handle_key, file_list_draw); +} + +void library_sample_load_page(struct page *page) +{ + clear_directory(); + + page->title = "Sample Library (Ctrl-F3)"; + page->draw_const = load_sample_draw_const; + page->set_page = library_sample_set_page; + page->handle_key = load_sample_handle_key; + page->total_widgets = 1; + page->widgets = widgets_loadsample; + page->help_index = HELP_GLOBAL; + + create_other(widgets_loadsample + 0, 0, file_list_handle_key, file_list_draw); +} diff --git a/schism/page_log.c b/schism/page_log.c new file mode 100644 index 000000000..22b5928e2 --- /dev/null +++ b/schism/page_log.c @@ -0,0 +1,104 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* It's lo-og, lo-og, it's big, it's heavy, it's wood! + * It's lo-og, lo-og, it's better than bad, it's good! */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_log[1]; + +#define NUM_LINES 33 +static struct log_line lines[NUM_LINES]; +static int last_line = -1; + +/* --------------------------------------------------------------------- */ + +static void log_draw_const(void) +{ + draw_box(1, 12, 78, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(2, 13, 77, 47, 0); +} + +static int log_handle_key(UNUSED struct key_event * k) +{ + return 0; +} + +static void log_redraw(void) +{ + int n; + + for (n = 0; n <= last_line; n++) + draw_text_len(lines[n].text, 74, 3, 14 + n, + lines[n].color, 0); +} + +/* --------------------------------------------------------------------- */ + +void log_load_page(struct page *page) +{ + page->title = "Message Log Viewer (Ctrl-F11)"; + page->draw_const = log_draw_const; + page->total_widgets = 1; + page->widgets = widgets_log; + page->help_index = HELP_GLOBAL; + + create_other(widgets_log + 0, 1, log_handle_key, log_redraw); +} + +/* --------------------------------------------------------------------- */ + +inline void log_append(int color, int must_free, const char *text) +{ + if (last_line < NUM_LINES - 1) { + last_line++; + } else { + if (lines[0].must_free) + free((void *) lines[0].text); + memmove(lines, lines + 1, last_line * sizeof(struct log_line)); + } + lines[last_line].text = text; + lines[last_line].color = color; + lines[last_line].must_free = must_free; + + if (status.current_page == PAGE_LOG) + status.flags |= NEED_UPDATE; +} + +void log_appendf(int color, const char *format, ...) +{ + char *ptr; + va_list ap; + + va_start(ap, format); + vasprintf(&ptr, format, ap); + va_end(ap); + + log_append(color, 1, ptr); +} diff --git a/schism/page_message.c b/schism/page_message.c new file mode 100644 index 000000000..4b30e847f --- /dev/null +++ b/schism/page_message.c @@ -0,0 +1,807 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* --->> WARNING <<--- + * + * This is an excellent example of how NOT to write a text editor. + * IMHO, the best way to add a song message is by writing it in some + * other program and attaching it to the song with something like + * ZaStaR's ITTXT utility (hmm, maybe I should rewrite that, too ^_^) so + * I'm not *really* concerned about the fact that this code completely + * sucks. Just remember, this ain't VI. */ + +#include "headers.h" + +#include "song.h" +#include "clippy.h" + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_message[1]; + +static int top_line = 0; +static int cursor_line = 0; +static int cursor_char = 0; +/* this is the absolute cursor position from top of message. + * (should be updated whenever cursor_line/cursor_char change) */ +static int cursor_pos = 0; + +char *message = NULL; +int edit_mode = 0; + +/* nonzero => message should use the alternate font */ +int message_extfont = 1; + +/* This is a bit weird... Impulse Tracker usually wraps at 74, but if + * the line doesn't have any spaces in it (like in a solid line of + * dashes or something) it gets wrapped at 75. I'm using 75 for + * everything because it's always nice to have a bit extra space :) */ +#define LINE_WRAP 75 + +/* --------------------------------------------------------------------- */ + +static int message_handle_key_editmode(struct key_event * k); +static int message_handle_key_viewmode(struct key_event * k); + +/* --------------------------------------------------------------------- */ + +/* returns the number of characters on the nth line of text, setting ptr + * to the first character on the line. if it there are fewer than n + * lines, ptr is set to the \0 at the end of the string, and the + * function returns -1. note: if *ptr == text, weird things will + * probably happen, so don't do that. */ +static int get_nth_line(const char *text, int n, const char **ptr) +{ + const char *tmp; + + if (!text) { + *ptr = NULL; + return 0; + } + + *ptr = text; + while (n > 0) { + n--; + *ptr = strpbrk(*ptr, "\xd\xa"); + if (!(*ptr)) { + *ptr = text + strlen(text); + return -1; + } + if ((*ptr)[0] == 13 && (*ptr)[1] == 10) + *ptr += 2; + else + (*ptr)++; + } + + tmp = strpbrk(*ptr, "\xd\xa"); + return (tmp ? (unsigned) (tmp - *ptr) : strlen(*ptr)); +} +static void set_absolute_position(const char *text, int pos, int *line, int *ch) +{ + int len; + const char *ptr; + + *line = *ch = 0; + ptr = 0; + while (pos > 0) { + len = get_nth_line(text, *line, &ptr); + if (len < 0) { + /* end of file */ + (*line) = (*line) - 1; + if (*line < 0) *line = 0; + len = get_nth_line(text, *line, &ptr); + if (len < 0) { + *ch = 0; + } else { + *ch = len; + } + pos = 0; + + } else if (len >= pos) { + *ch = pos; + pos = 0; + } else { + pos -= (len+1); /* EOC */ + (*line) = (*line) + 1; + } + } +} +static int get_absolute_position(const char *text, int line, int character) +{ + int len; + const char *ptr; + + ptr = 0; + len = get_nth_line(text, line, &ptr); + if (len < 0) { + return 0; + } + /* hrm... what if cursor_char > len? */ + return (ptr - text) + character; +} + +/* --------------------------------------------------------------------- */ + +static void message_reposition(void) +{ + if (cursor_line < top_line) { + top_line = cursor_line; + } else if (cursor_line > top_line + 34) { + top_line = cursor_line - 34; + } +} + +/* --------------------------------------------------------------------- */ + +/* returns 1 if a character was actually added */ +static int message_add_char(char newchar, int position) +{ + int len = strlen(message); + + if (len == 8000) { + dialog_create(DIALOG_OK, " Song message too long! ", NULL, NULL, 0, NULL); + return 0; + } + if (position < 0 || position > len) { + log_appendf(4, "message_add_char: position=%d, len=%d - shouldn't happen!", position, len); + return 0; + } + + memmove(message + position + 1, message + position, len - position); + message[len + 1] = 0; + message[position] = newchar; + return 1; +} + +/* this returns the new length of the line */ +static int message_wrap_line(char *bol_ptr) +{ + char *eol_ptr; + char *last_space = NULL; + char *tmp = bol_ptr; + + if (!bol_ptr) + /* shouldn't happen, but... */ + return 0; + + eol_ptr = strpbrk(bol_ptr, "\xd\xa"); + if (!eol_ptr) + eol_ptr = bol_ptr + strlen(bol_ptr); + + for (;;) { + tmp = strpbrk(tmp + 1, " \t"); + if (tmp == NULL || tmp > eol_ptr + || tmp - bol_ptr > LINE_WRAP) + break; + last_space = tmp; + } + + if (last_space) { + *last_space = 13; + return last_space - bol_ptr; + } else { + /* what, no spaces to cut at? chop it mercilessly. */ + if (message_add_char(13, bol_ptr + LINE_WRAP - message) + == 0) + /* ack, the message is too long to wrap the line! + * gonna have to resort to something ugly. */ + bol_ptr[LINE_WRAP] = 13; + return LINE_WRAP; + } +} + +/* --------------------------------------------------------------------- */ + +static void message_draw(void) +{ + const char *line, *prevline = message; + int fg = (message_extfont ? 12 : 6); + int len = get_nth_line(message, top_line, &line); + int n, cp, clipl, clipr; + int skipc, cutc; + + draw_fill_chars(2, 13, 77, 47, 0); + + if (message_extfont) + font_set_bank(1); + if (clippy_owner(CLIPPY_SELECT) == widgets_message) { + clipl = widgets_message[0].clip_start; + clipr = widgets_message[0].clip_end; + if (clipl > clipr) { + cp = clipl; + clipl = clipr; + clipr = cp; + } + } else { + clipl = clipr = -1; + } + + for (n = 0; n < 35; n++) { + if (len < 0) { + break; + } else if (len > 0) { + /* FIXME | shouldn't need this check here, + * FIXME | because the line should already be + * FIXME | short enough to fit */ + if (len > LINE_WRAP) + len = LINE_WRAP; + draw_text_len(line, len, 2, 13 + n, fg, 0); + + if (clipl > -1) { + cp = line - message; + skipc = clipl - cp; + cutc = clipr - clipl; + if (skipc < 0) { + cutc += skipc; /* ... -skipc */ + skipc = 0; + } + if (cutc < 0) cutc = 0; + if (cutc > (len-skipc)) cutc = (len-skipc); + if (cutc > 0 && skipc < len) { + draw_text_len(line+skipc, cutc, 2+skipc, 13 + n, fg, 8); + } + } + } + if (edit_mode) { + font_set_bank(0); + draw_char(20, 2 + len, 13 + n, 1, 0); + if (message_extfont) + font_set_bank(1); + } + prevline = line; + len = get_nth_line(prevline, 1, &line); + } + + if (edit_mode && len < 0) { + /* end of the message */ + len = get_nth_line(prevline, 0, &line); + font_set_bank(0); + /* FIXME: see above */ + if (len > LINE_WRAP) + len = LINE_WRAP; + draw_char(20, 2 + len, 13 + n - 1, 2, 0); + if (message_extfont) + font_set_bank(1); + } + + if (edit_mode) { + /* draw the cursor */ + len = get_nth_line(message, cursor_line, &line); + + /* FIXME: ... ugh */ + if (len > LINE_WRAP) + len = LINE_WRAP; + if (cursor_char > LINE_WRAP + 1) + cursor_char = LINE_WRAP + 1; + + if (cursor_char >= len) { + font_set_bank(0); + draw_char(20, 2 + cursor_char, + 13 + (cursor_line - top_line), 0, 3); + } else { + draw_char(line[cursor_char], 2 + cursor_char, + 13 + (cursor_line - top_line), 8, 3); + font_set_bank(0); + } + } else { + font_set_bank(0); + } +} + +/* --------------------------------------------------------------------- */ + +static inline void message_set_editmode(void) +{ + edit_mode = 1; + top_line = cursor_line = cursor_char = cursor_pos = 0; + widgets_message[0].d.other.handle_key = message_handle_key_editmode; + + status.flags |= NEED_UPDATE; +} + +static inline void message_set_viewmode(void) +{ + edit_mode = 0; + widgets_message[0].d.other.handle_key = message_handle_key_viewmode; + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void message_insert_char(Uint16 unicode) +{ + const char *ptr; + char c = unicode_to_ascii(unicode); + int n; + + if (!edit_mode) + return; + + memused_songchanged(); + if (c == '\t') { + /* Find the number of characters until the next tab stop. + * (This is new behaviour; Impulse Tracker just inserts + * eight characters regardless of the cursor position.) */ + n = 8 - cursor_char % 8; + if (cursor_char + n > LINE_WRAP) { + message_insert_char('\r'); + } else { + do { + if (!message_add_char(' ', cursor_pos)) + break; + cursor_char++; + cursor_pos++; + n--; + } while (n); + } + } else if (c < 32 && c != '\r') { + return; + } else { + if (!message_add_char(c, cursor_pos)) + return; + cursor_pos++; + if (c == '\r') { + cursor_char = 0; + cursor_line++; + } else { + cursor_char++; + } + } + if (get_nth_line(message, cursor_line, &ptr) >= LINE_WRAP) { + message_wrap_line((char *) ptr); + } + if (cursor_char >= LINE_WRAP) { + cursor_char = get_nth_line(message, ++cursor_line, &ptr); + cursor_pos = + get_absolute_position(message, cursor_line, + cursor_char); + } + + message_reposition(); + status.flags |= NEED_UPDATE; +} + +static void message_delete_char(void) +{ + int len = strlen(message); + const char *ptr; + + if (cursor_pos == 0) + return; + memmove(message + cursor_pos - 1, message + cursor_pos, + len - cursor_pos + 1); + message[8000] = 0; + cursor_pos--; + if (cursor_char == 0) { + cursor_line--; + cursor_char = get_nth_line(message, cursor_line, &ptr); + } else { + cursor_char--; + } + + message_reposition(); + status.flags |= NEED_UPDATE; +} + +static void message_delete_next_char(void) +{ + int len = strlen(message); + + if (cursor_pos == len) + return; + memmove(message + cursor_pos, message + cursor_pos + 1, + len - cursor_pos); + message[8000] = 0; + + status.flags |= NEED_UPDATE; +} + +static void message_delete_line(void) +{ + int len; + int movelen; + const char *ptr; + + len = get_nth_line(message, cursor_line, &ptr); + if (len < 0) + return; + if (ptr[len] == 13 && ptr[len + 1] == 10) + len++; + movelen = (message + strlen(message) - ptr); + if (movelen == 0) + return; + memmove((void *) ptr, ptr + len + 1, movelen); + len = get_nth_line(message, cursor_line, &ptr); + if (cursor_char > len) { + cursor_char = len; + cursor_pos = + get_absolute_position(message, cursor_line, + cursor_char); + } + message_reposition(); + status.flags |= NEED_UPDATE; +} + +static void message_clear(UNUSED void *data) +{ + message[0] = 0; + memused_songchanged(); + message_set_viewmode(); +} + +/* --------------------------------------------------------------------- */ + +static void prompt_message_clear(void) +{ + dialog_create(DIALOG_OK_CANCEL, "Clear song message?", message_clear, NULL, 1, NULL); +} + +/* --------------------------------------------------------------------- */ + +static int message_handle_key_viewmode(struct key_event * k) +{ + if (!k->state) { + if (k->mouse == MOUSE_SCROLL_UP) { + top_line--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + top_line++; + } else if (k->mouse == MOUSE_CLICK) { + message_set_editmode(); + return message_handle_key_editmode(k); + } + } + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + top_line--; + break; + case SDLK_DOWN: + if (k->state) return 0; + top_line++; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + top_line -= 35; + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + top_line += 35; + break; + case SDLK_HOME: + if (k->state) return 0; + top_line = 0; + break; + case SDLK_END: + if (k->state) return 0; + top_line = get_num_lines(message) - 34; + break; + case SDLK_t: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + message_extfont = !message_extfont; + break; + } + return 1; + case SDLK_RETURN: + if (!k->state) return 0; + message_set_editmode(); + return 1; + default: + return 0; + } + + if (top_line < 0) + top_line = 0; + + status.flags |= NEED_UPDATE; + + return 1; +} +static void _delete_selection(void) +{ + int len = strlen(message); + int eat; + + cursor_pos = widgets_message[0].clip_start; + if (cursor_pos > widgets_message[0].clip_end) { + cursor_pos = widgets_message[0].clip_end; + eat = widgets_message[0].clip_start - cursor_pos; + } else { + eat = widgets_message[0].clip_end - cursor_pos; + } + clippy_select(0,0,0); + if (cursor_pos == len) + return; + memmove(message + cursor_pos, message + cursor_pos + eat + 1, + ((len - cursor_pos) - eat)+1); + message[8000] = 0; + set_absolute_position(message, cursor_pos, &cursor_line, &cursor_char); + message_reposition(); + + status.flags |= NEED_UPDATE; +} + +static int message_handle_key_editmode(struct key_event * k) +{ + int line_len, num_lines = -1; + int new_cursor_line = cursor_line; + int new_cursor_char = cursor_char; + const char *ptr; + int doing_drag = 0; + int clipl, clipr, cp; + + if (k->mouse == MOUSE_SCROLL_UP) { + if (k->state) return 0; + new_cursor_line--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + if (k->state) return 0; + new_cursor_line++; + } else if (k->mouse == MOUSE_CLICK && k->mouse_button == 2) { + if (k->state) status.flags |= CLIPPY_PASTE_SELECTION; + return 1; + } else if (k->mouse == MOUSE_CLICK) { + if (k->x >= 2 && k->x <= 77 && k->y >= 13 && k->y <= 47) { + new_cursor_line = (k->y - 13) + top_line; + new_cursor_char = (k->x - 2); + if (k->sx != k->x || k->sy != k->y) { + /* yay drag operation */ + cp = get_absolute_position(message, (k->sy-13)+top_line, + (k->sx-2)); + widgets_message[0].clip_start = cp; + doing_drag = 1; + } + } + } + + line_len = get_nth_line(message, cursor_line, &ptr); + + + switch (k->sym) { + case SDLK_UP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_line--; + break; + case SDLK_DOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_line++; + break; + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_char--; + break; + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_char++; + break; + case SDLK_PAGEUP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_line -= 35; + break; + case SDLK_PAGEDOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_line += 35; + break; + case SDLK_HOME: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) + new_cursor_line = 0; + else + new_cursor_char = 0; + break; + case SDLK_END: + if (k->state) return 1; + if (k->mod & KMOD_CTRL) { + num_lines = get_num_lines(message); + new_cursor_line = num_lines; + } else { + new_cursor_char = line_len; + } + break; + case SDLK_ESCAPE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + message_set_viewmode(); + memused_songchanged(); + return 1; + case SDLK_BACKSPACE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) { + _delete_selection(); + } else { + message_delete_char(); + } + return 1; + case SDLK_DELETE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) { + _delete_selection(); + } else { + message_delete_next_char(); + } + return 1; + default: + if (k->mod & KMOD_CTRL) { + if (k->state) return 1; + if (k->sym == SDLK_t) { + message_extfont = !message_extfont; + break; + } else if (k->sym == SDLK_y) { + clippy_select(0,0,0); + message_delete_line(); + break; + } + } else if (k->mod & KMOD_ALT) { + if (k->state) return 1; + if (k->sym == SDLK_c) { + prompt_message_clear(); + return 1; + } + } else if (!k->mouse) { + if (k->unicode == '\r' || k->unicode == '\t' + || k->unicode >= 32) { + if (k->state) return 1; + if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) { + _delete_selection(); + } + message_insert_char(k->unicode); + return 1; + } + return 0; + } + + if (k->mouse != MOUSE_CLICK) + return 0; + + if (k->state) return 1; + } + + if (new_cursor_line != cursor_line) { + if (num_lines == -1) + num_lines = get_num_lines(message); + + if (new_cursor_line < 0) + new_cursor_line = 0; + else if (new_cursor_line > num_lines) + new_cursor_line = num_lines; + + /* make sure the cursor doesn't go past the new eol */ + line_len = get_nth_line(message, new_cursor_line, &ptr); + if (new_cursor_char > line_len) + new_cursor_char = line_len; + + cursor_char = new_cursor_char; + cursor_line = new_cursor_line; + } else if (new_cursor_char != cursor_char) { + /* we say "else" here ESPECIALLY because the mouse can only come + in the top section - not because it's some clever optimization */ + if (new_cursor_char < 0) { + if (cursor_line == 0) { + new_cursor_char = cursor_char; + } else { + cursor_line--; + new_cursor_char = + get_nth_line(message, cursor_line, &ptr); + } + + } else if (new_cursor_char > + get_nth_line(message, cursor_line, &ptr)) { + if (cursor_line == get_num_lines(message)) { + new_cursor_char = cursor_char; + } else { + cursor_line++; + new_cursor_char = 0; + } + } + cursor_char = new_cursor_char; + } + + message_reposition(); + cursor_pos = get_absolute_position(message, cursor_line, cursor_char); + + if (doing_drag) { + widgets_message[0].clip_end = cursor_pos; + + clipl = widgets_message[0].clip_start; + clipr = widgets_message[0].clip_end; + if (clipl > clipr) { + cp = clipl; + clipl = clipr; + clipr = cp; + } + clippy_select(widgets_message, message+clipl, clipr-clipl); + } + + status.flags |= NEED_UPDATE; + + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void message_draw_const(void) +{ + draw_box(1, 12, 78, 48, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void song_changed_cb(void) +{ + const char *line, *prevline; + int len; + + edit_mode = 0; + widgets_message[0].d.other.handle_key = message_handle_key_viewmode; + top_line = 0; + message = song_get_message(); + + len = get_nth_line(message, 0, &line); + while (len >= 0) { + if (len > LINE_WRAP) + message_wrap_line((char *) line); + prevline = line; + len = get_nth_line(prevline, 1, &line); + } + + if (status.current_page == PAGE_MESSAGE) + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +static int message_key_hack(struct key_event *k) +{ + if (k->sym == SDLK_ESCAPE && NO_MODIFIER(k->mod) && edit_mode) { + if (!k->state) return 1; + message_set_viewmode(); + memused_songchanged(); + return 1; + } + return 0; +} + +void message_load_page(struct page *page) +{ + page->title = "Message Editor (Shift-F9)"; + page->draw_const = message_draw_const; + page->song_changed_cb = song_changed_cb; + page->pre_handle_key = message_key_hack; + page->total_widgets = 1; + page->widgets = widgets_message; + page->help_index = HELP_MESSAGE_EDITOR; + + create_other(widgets_message + 0, 0, message_handle_key_viewmode, message_draw); +} diff --git a/schism/page_midi.c b/schism/page_midi.c new file mode 100644 index 000000000..277a3f2bc --- /dev/null +++ b/schism/page_midi.c @@ -0,0 +1,365 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" +#include "midi.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +static int top_midi_port = 0; +static int current_port = 0; +static struct widget widgets_midi[17]; +static time_t last_midi_poll = 0; + +/* --------------------------------------------------------------------- */ +static void midi_output_config(void) +{ + set_page(PAGE_MIDI_OUTPUT); +} +static void update_ip_ports(void) +{ + ip_midi_setports(widgets_midi[15].d.thumbbar.value); + last_midi_poll = 0; + status.flags |= NEED_UPDATE; +} +static void update_midi_values(void) +{ + midi_flags = + (widgets_midi[1].d.toggle.state ? MIDI_TICK_QUANTIZE : 0) + | (widgets_midi[2].d.toggle.state ? MIDI_BASE_PROGRAM1 : 0) + | (widgets_midi[3].d.toggle.state ? MIDI_RECORD_NOTEOFF : 0) + | (widgets_midi[4].d.toggle.state ? MIDI_RECORD_VELOCITY : 0) + | (widgets_midi[5].d.toggle.state ? MIDI_RECORD_AFTERTOUCH : 0) + | (widgets_midi[6].d.toggle.state ? MIDI_CUT_NOTE_OFF : 0) + | (widgets_midi[9].d.toggle.state ? MIDI_PITCH_BEND : 0) + | (widgets_midi[11].d.toggle.state? MIDI_EMBED_DATA : 0) + | (widgets_midi[14].d.toggle.state? MIDI_RECORD_SDX : 0); + + midi_amplification = widgets_midi[7].d.thumbbar.value; + midi_c5note = widgets_midi[8].d.thumbbar.value; + midi_pitch_depth = widgets_midi[10].d.thumbbar.value; + if (midi_flags & MIDI_EMBED_DATA) { + status.flags |= SONG_NEEDS_SAVE; + } +} +static void save_midi_config(void) +{ + cfg_midipage_save(); + status_text_flash("Configuration saved"); +} +static void toggle_port(void) +{ + struct midi_port *p; + p = midi_engine_port(current_port, 0); + if (p) { + status.flags |= NEED_UPDATE; + + if (p->disable) if (!p->disable(p)) return; + switch (p->io) { + case 0: + if (p->iocap & MIDI_INPUT) p->io = MIDI_INPUT; + else if (p->iocap & MIDI_OUTPUT) p->io = MIDI_OUTPUT; + break; + case MIDI_INPUT: + if (p->iocap & MIDI_OUTPUT) p->io = MIDI_OUTPUT; + else p->io = 0; + break; + case MIDI_OUTPUT: + if (p->iocap & MIDI_INPUT) p->io |= MIDI_INPUT; + else p->io = 0; + break; + case MIDI_INPUT|MIDI_OUTPUT: + p->io = 0; + break; + }; + + if (p->enable) { + if (!p->enable(p)) { + p->io = 0; + return; + } + } + } +} +static int midi_page_handle_key(struct key_event * k) +{ + int new_port = current_port; + int pos; + + if (k->mouse == MOUSE_SCROLL_UP) { + new_port--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_port++; + } else if (k->mouse) { + if (k->x >= 3 && k->x <= 11 && k->y >= 15 && k->y <= 27) { + if (k->mouse == MOUSE_DBLCLICK) { + if (k->state) return 0; + toggle_port(); + return 1; + } + new_port = top_midi_port + (k->y - 15); + } else { + return 0; + } + } + + switch (k->sym) { + case SDLK_SPACE: + if (!k->state) return 1; + toggle_port(); + return 1; + case SDLK_PAGEUP: + new_port -= 13; + if (new_port < 0) new_port = 0; + break; + case SDLK_PAGEDOWN: + new_port += 13; + if (new_port >= midi_engine_port_count()) { + new_port = midi_engine_port_count()-1; + } + break; + case SDLK_HOME: + new_port = 0; + break; + case SDLK_END: + new_port = midi_engine_port_count()-1; + break; + case SDLK_UP: + new_port--; + break; + case SDLK_DOWN: + new_port++; + break; + case SDLK_TAB: + if (k->state) return 1; + change_focus_to(1); + status.flags |= NEED_UPDATE; + return 1; + default: + if (!k->mouse) return 0; + break; + }; + if (k->state) return 0; + + if (new_port != current_port) { + if (new_port < 0 || new_port >= midi_engine_port_count()) { + new_port = current_port; + if (k->sym == SDLK_DOWN) { + change_focus_to(1); + } + } + + current_port = new_port; + if (current_port < top_midi_port) + top_midi_port = current_port; + + pos = current_port - top_midi_port; + if (pos > 12) top_midi_port = current_port - 12; + if (top_midi_port < 0) top_midi_port = 0; + + status.flags |= NEED_UPDATE; + } + + return 1; +} + +static void midi_page_redraw(void) +{ + struct midi_port *p; + const char *name; + char buffer[64]; + int i, j, n, ct, fg, bg; + time_t now; + + draw_fill_chars(3, 15, 76, 28, 0); + draw_text("Midi ports:", 2, 13, 0, 2); + draw_box(2,14,77,28, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_text( "Tick quantize", 6, 30, 0, 2); + draw_text( "Base Program 1", 5, 31, 0, 2); + draw_text( "Record Note-Off", 4, 32, 0, 2); + draw_text( "Record Velocity", 4, 33, 0, 2); + draw_text( "Record Aftertouch", 2, 34, 0, 2); + draw_text( "Record using SDx", 3, 35, 0, 2); + draw_text( "Cut note off", 7, 36, 0, 2); + + draw_fill_chars(23, 30, 24, 36, 0); + draw_box(19,29,25,37, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_box(52,29,73,32, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_fill_chars(56, 34, 72, 34, 0); + draw_box(52,33,73,36, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_fill_chars(56, 38, 72, 38, 0); + draw_box(52,37,73,39, BOX_THIN|BOX_INNER|BOX_INSET); + + draw_text( "Amplification", 39, 30, 0, 2); + draw_text( "C-5 Note-value", 38, 31, 0, 2); + draw_text("Output MIDI pitch", 35, 34, 0, 2); + draw_text("Pitch wheel depth", 35, 35, 0, 2); + draw_text( "Embed MIDI data", 37, 38, 0, 2); + + draw_text( "IP MIDI ports", 39, 41, 0, 2); + draw_box(52,40,73,42, BOX_THIN|BOX_INNER|BOX_INSET); + + time(&now); + if ((now - last_midi_poll) > 10) { + last_midi_poll = now; + midi_engine_poll_ports(); + } + + ct = midi_engine_port_count(); + for (i = 0; i < 13; i++) { + draw_char(168, 12, 15+i, 2, 0); + + if (top_midi_port + i >= ct) continue; /* err */ + + p = midi_engine_port(i+top_midi_port, &name); + if (current_port == top_midi_port+i + && ACTIVE_WIDGET.type == WIDGET_OTHER) { + fg = 0; + bg = 3; + } else { + fg = 5; + bg = 0; + } + draw_text_len(name, 64, 13, 15+i, 5, 0); + + /* portability: should use difftime */ + if (status.flags & MIDI_EVENT_CHANGED + && (time(0) - status.last_midi_time) < 3 + && p == (struct midi_port *)status.last_midi_port) { + for (j = n = 0; j < 21 && j < status.last_midi_len; j++) { /* 21 is approx 64/3 */ + sprintf(buffer+n, "%02x ", status.last_midi_event[j]); + n += 3; + } + draw_text(buffer, 77 - strlen(buffer), 15+i, 4, 0); + } + + switch (p->io) { + case 0: + draw_text("Disabled ", 3, 15+i, fg, bg); + break; + case MIDI_INPUT: + draw_text(" Input ", 3, 15+i, fg, bg); + break; + case MIDI_OUTPUT: + draw_text(" Output ", 3, 15+i, fg, bg); + break; + case MIDI_INPUT|MIDI_OUTPUT: + draw_text(" Duplex ", 3, 15+i, fg, bg); + break; + default: + draw_text(" Enabled ", 3, 15+i, fg, bg); + break; + }; + } +} + +/* --------------------------------------------------------------------- */ + +void midi_load_page(struct page *page) +{ + page->title = "Midi Screen (Shift-F1)"; + page->draw_const = NULL; + page->song_changed_cb = NULL; + page->predraw_hook = NULL; + page->playback_update = NULL; + page->handle_key = NULL; + page->set_page = NULL; + page->total_widgets = 16; + page->widgets = widgets_midi; + page->help_index = HELP_GLOBAL; + + create_other(widgets_midi+0, 0, midi_page_handle_key, midi_page_redraw); + widgets_midi[0].x = 2; + widgets_midi[0].y = 14; + widgets_midi[0].width = 75; + widgets_midi[0].height = 15; + + create_toggle(widgets_midi+1, 20, 30, + 0, 2, 1, 7, 2, update_midi_values); /* on */ + widgets_midi[1].d.toggle.state = midi_flags & MIDI_TICK_QUANTIZE; + create_toggle(widgets_midi+2, 20, 31, + 1, 3, 2, 8, 3, update_midi_values); /* off */ + widgets_midi[2].d.toggle.state = midi_flags & MIDI_BASE_PROGRAM1; + create_toggle(widgets_midi+3, 20, 32, + 2, 4, 3, 3, 4, update_midi_values); + widgets_midi[3].d.toggle.state = midi_flags & MIDI_RECORD_NOTEOFF; + create_toggle(widgets_midi+4, 20, 33, + 3, 5, 4, 9, 5, update_midi_values); + widgets_midi[4].d.toggle.state = midi_flags & MIDI_RECORD_VELOCITY; + create_toggle(widgets_midi+5, 20, 34, + 4, 14, 5, 10, 14, update_midi_values); + widgets_midi[5].d.toggle.state = midi_flags & MIDI_RECORD_AFTERTOUCH; + + create_toggle(widgets_midi+14, 20, 35, + 5, 6, 6, 6, 6, update_midi_values); /* off */ + widgets_midi[14].d.toggle.state = midi_flags & MIDI_RECORD_SDX; + + create_toggle(widgets_midi+6, 20, 36, + 14, 12, 6, 6, 7, update_midi_values); /* off */ + widgets_midi[6].d.toggle.state = midi_flags & MIDI_CUT_NOTE_OFF; + + + + create_thumbbar(widgets_midi+7, 53, 30, 20, + 0, 8, 8, update_midi_values, 0, 200); /* d. 100 */ + widgets_midi[7].d.thumbbar.value = midi_amplification; + create_thumbbar(widgets_midi+8, 53, 31, 20, + 7, 9, 9, update_midi_values, 0, 127); /* d. 60 */ + widgets_midi[8].d.thumbbar.value = midi_c5note; + + + create_toggle(widgets_midi+9, 53, 34, + 8, 10, 4, 9, 10, update_midi_values); /* off */ + widgets_midi[9].d.toggle.state = midi_flags & MIDI_PITCH_BEND; + create_thumbbar(widgets_midi+10, 53, 35, 20, + 9, 11, 11, update_midi_values, 0, 48); /* d. 0 */ + widgets_midi[10].d.thumbbar.value = midi_pitch_depth; + + create_toggle(widgets_midi+11, 53, 38, + 10, 15, 6, 11, 15, update_midi_values); /* off */ + widgets_midi[11].d.toggle.state = midi_flags & MIDI_EMBED_DATA; + + create_thumbbar(widgets_midi+15, 53, 41, 20, + 11, 15, 12, update_ip_ports, 0, 128); + widgets_midi[15].d.thumbbar.value = ip_midi_getports(); + + create_button(widgets_midi+12, 2, 41, + 27, + 6, 13, 12, 12, 13, + midi_output_config, + "MIDI Output Configuration", + 2); + create_button(widgets_midi+13, 2, 44, + 27, + 12, 13, 13, 13, 0, + save_midi_config, + "Save Output Configuration", + 2); +} diff --git a/schism/page_midiout.c b/schism/page_midiout.c new file mode 100644 index 000000000..8a1b4df42 --- /dev/null +++ b/schism/page_midiout.c @@ -0,0 +1,204 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" +#include "midi.h" +#include "song.h" + +#include +#include + +static struct widget widgets_midiout[33]; +static int zbuf_top = 0; +static char zbuf[7*32]; + +static char mm_global[9*32]; +static char mm_sfx[16*32]; + +static void midiout_draw_const(void) +{ + char buf[4]; + int i; + + draw_text( "MIDI Start", 6, 13, 0, 2); + draw_text( "MIDI Stop", 7, 14, 0, 2); + draw_text( "MIDI Tick", 7, 15, 0, 2); + draw_text( "Note On", 9, 16, 0, 2); + draw_text( "Note Off", 8, 17, 0, 2); + draw_text( "Change Volume", 3, 18, 0, 2); + draw_text( "Change Pan", 6, 19, 0, 2); + draw_text( "Bank Select", 5, 20, 0, 2); + draw_text("Program Change", 2, 21, 0, 2); + + draw_text( "Macro SF0", 5, 24, 0, 2); + draw_text( "Setup SF1", 5, 25, 0, 2); + + buf[0] = 'S'; + buf[1] = 'F'; + buf[3] = '\0'; + for (i = 2; i < 10; i++) { + buf[2] = i + '0'; + draw_text(buf, 13, 24+i, 0, 2); + } + draw_text( "SFA", 13, 34, 0, 2); + draw_text( "SFB", 13, 35, 0, 2); + draw_text( "SFC", 13, 36, 0, 2); + draw_text( "SFD", 13, 37, 0, 2); + draw_text( "SFE", 13, 38, 0, 2); + draw_text( "SFF", 13, 39, 0, 2); + + draw_box(16, 12, 60, 22, BOX_THIN|BOX_INNER|BOX_INSET); + draw_box(16, 23, 60, 40, BOX_THIN|BOX_INNER|BOX_INSET); + draw_box(16, 41, 60, 49, BOX_THIN|BOX_INNER|BOX_INSET); + + for (i = 0; i < 7; i++) { + sprintf(buf, "Z%02X", i+zbuf_top+0x80); + draw_text(buf, 13, i+42, 0, 2); + } +} +static void copyout_zbuf(void) +{ + midi_config *md; + int i; + md = song_get_midi_config(); + song_lock_audio(); + for (i = 0; i < 9; i++) { + strcpy(md->midi_global_data+(i*32), mm_global+(i*32)); + } + for (i = 0; i < 16; i++) { + strcpy(md->midi_sfx+(i*32), mm_sfx+(i*32)); + } + for (i = 0; i < 7; i++) { + strcpy(md->midi_zxx+((zbuf_top+i)*32), + zbuf+(i*32)); + } + song_unlock_audio(); +} +static void copyin_zbuf(void) +{ + midi_config *md; + int i; + md = song_get_midi_config(); + for (i = 0; i < 9; i++) { + strcpy(mm_global+(i*32), md->midi_global_data+(i*32)); + } + for (i = 0; i < 16; i++) { + strcpy(mm_sfx+(i*32), md->midi_sfx+(i*32)); + } + + for (i = 0; i < 7; i++) { + strcpy(zbuf+(i*32), md->midi_zxx+((zbuf_top+i)*32)); + } +} + +static int pre_handle_key(struct key_event *k) +{ + if (k->sym == SDLK_ESCAPE) { + if (!k->state) return 1; + set_page(PAGE_MIDI); + return 1; + } + if (*selected_widget == 25 && (k->sym == SDLK_UP || k->mouse == 2)) { + /* scroll up */ + if (zbuf_top == 0) return 0; + if (k->state) return 1; + copyout_zbuf(); + zbuf_top--; + copyin_zbuf(); + status.flags |= NEED_UPDATE; + return 1; + } + if (*selected_widget == 31 && (k->sym == SDLK_DOWN || k->mouse == 3)) { + /* scroll down */ + if (zbuf_top >= 121) return 0; + if (k->state) return 1; + copyout_zbuf(); + zbuf_top++; + copyin_zbuf(); + status.flags |= NEED_UPDATE; + return 1; + } + if ((*selected_widget) >= 25) { + switch (k->sym) { + case SDLK_PAGEUP: + if (k->state) return 1; + copyout_zbuf(); + zbuf_top -= 7; + if (zbuf_top < 0) zbuf_top = 0; + copyin_zbuf(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_PAGEDOWN: + if (k->state) return 1; + copyout_zbuf(); + zbuf_top += 7; + if (zbuf_top >= 121) zbuf_top = 121; + status.flags |= NEED_UPDATE; + copyin_zbuf(); + return 1; + }; + } + return 0; +} + +void midiout_set_page(void) +{ + copyin_zbuf(); +} + +void midiout_load_page(struct page *page) +{ + int i; + + page->title = "MIDI Output Configuration"; + page->draw_const = midiout_draw_const; + page->set_page = midiout_set_page; + page->pre_handle_key = pre_handle_key; + page->total_widgets = 32; + page->widgets = widgets_midiout; + page->help_index = HELP_MIDI_OUTPUT; + + for (i = 0; i < 9; i++) { + create_textentry(widgets_midiout+i, 17, 13+i, 43, + (i == 0 ? 0 : (i-1)), + i+1, + i+1, + copyout_zbuf, mm_global+(i*32),31); + } + for (i = 0; i < 16; i++) { + create_textentry(widgets_midiout+9+i, 17, 24+i, 43, + 9+(i-1), + 9+(i+1), + 9+(i+1), + copyout_zbuf, mm_sfx+(i*32),31); + } + for (i = 0; i < 7; i++) { + create_textentry(widgets_midiout+25+i, 17, 42+i, 43, + 25+(i-1), + 25+((i == 6) ? 6 : (i+1)), + 25+((i == 6) ? 6 : (i+1)), + copyout_zbuf, zbuf+(i*32),31); + } + zbuf_top = 0; +} + diff --git a/schism/page_orderpan.c b/schism/page_orderpan.c new file mode 100644 index 000000000..0fd03439c --- /dev/null +++ b/schism/page_orderpan.c @@ -0,0 +1,855 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_orderpan[65], widgets_ordervol[65]; + +static int top_order = 0; +static int current_order = 0; +static int orderlist_cursor_pos = 0; + +static unsigned char saved_orderlist[256]; +static int _did_save_orderlist = 0; + +/* --------------------------------------------------------------------- */ + +static void orderlist_reposition(void) +{ + if (current_order < top_order) { + top_order = current_order; + } else if (current_order > top_order + 31) { + top_order = current_order - 31; + } +} + +/* --------------------------------------------------------------------- */ + +void update_current_order(void) +{ + char buf[4]; + + draw_text(numtostr(3, current_order, buf), 12, 5, 5, 0); + draw_text(numtostr(3, song_get_num_orders(), buf), 16, 5, 5, 0); +} + + +inline void set_current_order(int order) +{ + current_order = CLAMP(order, 0, 255); + orderlist_reposition(); + + status.flags |= NEED_UPDATE; +} + +int get_current_order(void) +{ + return current_order; +} + +/* --------------------------------------------------------------------- */ +/* called from the pattern editor on ctrl-plus/minus */ + +void prev_order_pattern(void) +{ + int new_order = current_order - 1; + int pattern; + +RETR: if (new_order < 0) + new_order = 0; + + pattern = song_get_orderlist()[new_order]; + + if ((!(status.flags & CLASSIC_MODE)) && pattern == ORDER_SKIP) { + current_order = new_order; + new_order--; + goto RETR; + } + + if (pattern < 200) { + current_order = new_order; + orderlist_reposition(); + set_current_pattern(pattern); + } +} + +void next_order_pattern(void) +{ + int new_order = current_order + 1; + int pattern; + +RETR: if (new_order > 255) + new_order = 255; + + pattern = song_get_orderlist()[new_order]; + if (pattern == ORDER_SKIP) { + current_order = new_order; + new_order++; + goto RETR; + } + + if (pattern < 200) { + current_order = new_order; + orderlist_reposition(); + set_current_pattern(pattern); + } +} + +/* --------------------------------------------------------------------- */ + +static void get_pattern_string(unsigned char pattern, char *buf) +{ + switch (pattern) { + case ORDER_SKIP: + buf[0] = buf[1] = buf[2] = '+'; + buf[3] = 0; + break; + case ORDER_LAST: + buf[0] = buf[1] = buf[2] = '-'; + buf[3] = 0; + break; + default: + numtostr(3, pattern, buf); + break; + } +} + +static void orderlist_draw(void) +{ + unsigned char *list = song_get_orderlist(); + char buf[4]; + int pos, n; + int playing_order = (song_get_mode() == MODE_PLAYING ? song_get_current_order() : -1); + + /* draw the list */ + for (pos = 0, n = top_order; pos < 32; pos++, n++) { + draw_text(numtostr(3, n, buf), 2, 15 + pos, (n == playing_order ? 3 : 0), 2); + get_pattern_string(list[n], buf); + draw_text(buf, 6, 15 + pos, 2, 0); + } + + /* draw the cursor */ + if (ACTIVE_PAGE.selected_widget == 0) { + get_pattern_string(list[current_order], buf); + pos = current_order - top_order; + draw_char(buf[orderlist_cursor_pos], orderlist_cursor_pos + 6, 15 + pos, 0, 3); + } + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void orderlist_insert_pos(void) +{ + unsigned char *list = song_get_orderlist(); + + memmove(list + current_order + 1, list + current_order, 255 - current_order); + list[current_order] = ORDER_LAST; + + status.flags |= NEED_UPDATE; +} + +static void orderlist_save(void) +{ + unsigned char *list = song_get_orderlist(); + memcpy(saved_orderlist, list, 255); + _did_save_orderlist = 1; +} +static void orderlist_restore(void) +{ + unsigned char *list = song_get_orderlist(); + unsigned char oldlist[256]; + if (!_did_save_orderlist) return; + memcpy(oldlist, list, 255); + memcpy(list, saved_orderlist, 255); + memcpy(saved_orderlist, oldlist, 255); +} + +static void orderlist_delete_pos(void) +{ + unsigned char *list = song_get_orderlist(); + + memmove(list + current_order, list + current_order + 1, 255 - current_order); + list[255] = ORDER_LAST; + + status.flags |= NEED_UPDATE; +} + +static void orderlist_insert_next(void) +{ + unsigned char *list = song_get_orderlist(); + int next_pattern; + + if (current_order == 0 || list[current_order - 1] > 199) + return; + next_pattern = list[current_order - 1] + 1; + if (next_pattern > 199) + next_pattern = 199; + list[current_order] = next_pattern; + if (current_order < 255) + current_order++; + orderlist_reposition(); + + status.flags |= NEED_UPDATE; +} + +static void orderlist_reorder(void) +{ + /* err, I hope this is going to be done correctly... + */ + song_note *np[256], *src; + int nplen[256]; + unsigned char *ol; + int i, srclen; + + ol = song_get_orderlist(); + + /* pass one */ + for (i = 0; i < 255; i++) { + if (ol[i] == ORDER_LAST || ol[i] == ORDER_SKIP) { + np[i] = NULL; + nplen[i] = 64; + } else { + np[i] = song_pattern_allocate_copy(ol[i], &nplen[i]); + } + } + + /* pass two */ + for (i = 0; i < 200; i++) { + if (ol[i] != ORDER_LAST && ol[i] != ORDER_SKIP) ol[i] = i; + song_pattern_install(i, np[i], nplen[i]); + } + for (i = 200; i < 255; i++) { + ol[i] = ORDER_LAST; + } + + status.flags |= NEED_UPDATE; +} +static void orderlist_add_unused_patterns(void) +{ + /* n0 = the first free order + * n = orderlist position + * p = pattern iterator + * np = number of patterns */ + int n0, n, p, np = song_get_num_patterns(); + byte used[200] = {0}; /* could be a bitset... */ + unsigned char *list = song_get_orderlist(); + + for (n = 0; n < 255; n++) + if (list[n] < 200) + used[list[n]] = 1; + + /* after the loop, n == 255 */ + while (n >= 0 && list[n] == 0xff) + n--; + if (n == -1) + n = 0; + else + n += 2; + + n0 = n; + for (p = 0; p <= np; p++) { + if (used[p] || song_pattern_is_empty(p)) + continue; + if (n > 255) { + /* status_text_flash("No more room in orderlist"); */ + break; + } + list[n++] = p; + } + if (n == n0) { + status_text_flash("No unused patterns"); + } else { + set_current_order(n - 1); + set_current_order(n0); + if (n - n0 == 1) { + status_text_flash("1 unused pattern found"); + } else { + status_text_flash("%d unused patterns found", n - n0); + } + } +} + +static int orderlist_handle_char(struct key_event *k) +{ + int c; + int cur_pattern; + int n[3] = { 0 }; + + switch (k->sym) { + case SDLK_PLUS: + if (!k->state) return 1; + status.flags |= SONG_NEEDS_SAVE; + song_get_orderlist()[current_order] = ORDER_SKIP; + orderlist_cursor_pos = 2; + break; + case SDLK_MINUS: + if (!k->state) return 1; + status.flags |= SONG_NEEDS_SAVE; + song_get_orderlist()[current_order] = ORDER_LAST; + orderlist_cursor_pos = 2; + break; + default: + c = numeric_key_event(k); + if (c == -1) return 0; + if (k->state) return 1; + + status.flags |= SONG_NEEDS_SAVE; + cur_pattern = song_get_orderlist()[current_order]; + if (cur_pattern < 200) { + n[0] = cur_pattern / 100; + n[1] = cur_pattern / 10 % 10; + n[2] = cur_pattern % 10; + } + + n[orderlist_cursor_pos] = c; + cur_pattern = n[0] * 100 + n[1] * 10 + n[2]; + cur_pattern = CLAMP(cur_pattern, 0, 199); + song_get_orderlist()[current_order] = cur_pattern; + break; + }; + + if (orderlist_cursor_pos == 2) { + if (current_order < 255) + current_order++; + orderlist_cursor_pos = 0; + orderlist_reposition(); + } else { + orderlist_cursor_pos++; + } + + status.flags |= NEED_UPDATE; + + return 1; +} + +static void _copysam(UNUSED void *ign) +{ + int patno; + + patno = song_get_orderlist()[current_order]; + status_text_flash("Copied pattern %d into sample %d", + patno, sample_get_current()); + diskwriter_writeout_sample(sample_get_current(), patno, 0); +} +static void _attachsam(UNUSED void *ign) +{ + int patno; + + patno = song_get_orderlist()[current_order]; + status_text_flash("Linked pattern %d into sample %d", + patno, sample_get_current()); + diskwriter_writeout_sample(sample_get_current(), patno, 1); +} +static int orderlist_handle_key_on_list(struct key_event * k) +{ + unsigned char *list = song_get_orderlist(); + int prev_order = current_order; + int new_order = prev_order; + int new_cursor_pos = orderlist_cursor_pos; + song_sample *samp; + char *z; + int n, p; + + if (k->mouse) { + if (k->x >= 6 && k->x <= 8 && k->y >= 15 && k->y <= 46) { + if (k->mouse == MOUSE_SCROLL_UP) { + new_order--; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + new_order++; + } else { + if (!k->state) return 0; + + new_order = (k->y - 15) + top_order; + set_current_order(new_order); + new_order = current_order; + + if (list[current_order] != ORDER_LAST + && list[current_order] != ORDER_SKIP) { + new_cursor_pos = (k->x - 6); + } + } + } + } + + switch (k->sym) { + case SDLK_RETURN: + if (status.flags & CLASSIC_MODE) return 0; + if (!(k->mod & KMOD_ALT)) return 0; + if (!k->state) return 1; + status_text_flash("Saved orderlist"); + orderlist_save(); + return 1; + + case SDLK_BACKSPACE: + if (status.flags & CLASSIC_MODE) return 0; + if (!(k->mod & KMOD_ALT)) return 0; + if (!k->state) return 1; + if (!_did_save_orderlist) return 1; + status_text_flash("Restored orderlist"); + orderlist_restore(); + return 1; + case SDLK_TAB: + if (k->mod & KMOD_SHIFT) { + if (k->state) return 1; + change_focus_to(33); + } else { + if (!NO_MODIFIER(k->mod)) return 0; + if (k->state) return 1; + change_focus_to(1); + } + return 1; + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_pos--; + break; + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_cursor_pos++; + break; + case SDLK_HOME: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order = 0; + break; + case SDLK_END: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order = song_get_num_orders(); + if (song_get_orderlist()[new_order] != ORDER_LAST) + new_order++; + break; + case SDLK_UP: + if (k->mod & KMOD_CTRL) { + if (status.flags & CLASSIC_MODE) return 0; + if (k->state) return 1; + sample_set(sample_get_current()-1); + return 1; + } + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order--; + break; + case SDLK_DOWN: + if (k->mod & KMOD_CTRL) { + if (status.flags & CLASSIC_MODE) return 0; + if (k->state) return 1; + sample_set(sample_get_current()+1); + return 1; + } + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order++; + break; + case SDLK_PAGEUP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order -= 16; + break; + case SDLK_PAGEDOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + new_order += 16; + break; + case SDLK_INSERT: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + orderlist_insert_pos(); + return 1; + case SDLK_DELETE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + orderlist_delete_pos(); + return 1; + case SDLK_F7: + if (k->mod & KMOD_CTRL) { + if (k->state) return 1; + song_set_next_order(current_order); + status_text_flash("Playing order %d next", current_order); + } else { + return 0; + } + return 1; + case SDLK_F6: + if (k->mod & KMOD_SHIFT) { + if (k->state) return 1; + song_start_at_order(current_order, 0); + return 1; + } + return 0; + + case SDLK_n: + if (!NO_MODIFIER(k->mod)) + return 0; + if (k->state) return 1; + orderlist_insert_next(); + return 1; + case SDLK_g: + if (!NO_MODIFIER(k->mod)) + return 0; + if (!k->state) return 1; + n = song_get_orderlist()[new_order]; + if (n < 200) { + set_current_pattern(n); + set_page(PAGE_PATTERN_EDITOR); + } + return 1; + case SDLK_r: + if (k->mod & KMOD_ALT) { + if (!k->state) return 1; + orderlist_reorder(); + return 1; + } + return 0; + case SDLK_u: + if (k->mod & KMOD_ALT) { + if (k->state) return 1; + orderlist_add_unused_patterns(); + return 1; + } + return 0; + case SDLK_o: + if (k->mod & KMOD_CTRL) { + if (status.flags & CLASSIC_MODE) return 0; + p = song_get_orderlist()[current_order]; + if (p >= 200) return 0; + n = sample_get_current(); + if (n < 1) return 0; + if (k->state) return 1; + + samp = song_get_sample(n,&z); + if (samp && z + && ((unsigned char)z[23]) == 0xFF + && ((unsigned char)z[24]) < 200) { + dialog_create(DIALOG_OK_CANCEL, + "This will replace and unlink the current sample", _copysam, dialog_cancel, 1, 0); + } else if (song_sample_is_empty(n)) { + _copysam(0); + } else { + dialog_create(DIALOG_OK_CANCEL, + "This will replace the current sample", _copysam, dialog_cancel, 1, 0); + } + } + return 0; + case SDLK_b: + if (k->mod & KMOD_CTRL) { + if (status.flags & CLASSIC_MODE) return 0; + p = song_get_orderlist()[current_order]; + if (p >= 200) return 0; + if (sample_get_current() < 1) return 0; + + if (k->state) return 1; + + for (n = 1; n <= 99; n++) { + samp = song_get_sample(n,&z); + if (!samp || !z) continue; + if (((unsigned char)z[23]) != 0xFF) continue; + if (((unsigned char)z[24]) != p) continue; + status_text_flash("Pattern %d already linked to sample %d", + p, n); + return 1; + } + + if (song_sample_is_empty(sample_get_current())) { + _attachsam(0); + } else { + dialog_create(DIALOG_OK_CANCEL, + "This will replace the current sample", _attachsam, dialog_cancel, 1, 0); + } + } + return 0; + case SDLK_LESS: + if (!NO_MODIFIER(k->mod)) return 0; + if (status.flags & CLASSIC_MODE) return 0; + if (k->state) return 1; + sample_set(sample_get_current()-1); + return 1; + case SDLK_GREATER: + if (!NO_MODIFIER(k->mod)) return 0; + if (status.flags & CLASSIC_MODE) return 0; + if (k->state) return 1; + sample_set(sample_get_current()+1); + return 1; + default: + if (!k->mouse) { + if ((k->mod & (KMOD_CTRL | KMOD_ALT))==0) { + return orderlist_handle_char(k); + } + return 0; + } + } + + if (new_cursor_pos < 0) + new_cursor_pos = 2; + else if (new_cursor_pos > 2) + new_cursor_pos = 0; + + if (new_order != prev_order) { + set_current_order(new_order); + } else if (new_cursor_pos != orderlist_cursor_pos) { + orderlist_cursor_pos = new_cursor_pos; + } else { + return 0; + } + + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void order_pan_vol_draw_const(void) +{ + draw_box(5, 14, 9, 47, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_box(30, 14, 40, 47, BOX_THICK | BOX_INNER | BOX_FLAT_LIGHT); + draw_box(64, 14, 74, 47, BOX_THICK | BOX_INNER | BOX_FLAT_LIGHT); + + draw_char(146, 30, 14, 3, 2); + draw_char(145, 40, 14, 3, 2); + + draw_char(146, 64, 14, 3, 2); + draw_char(145, 74, 14, 3, 2); +} + +static void orderpan_draw_const(void) +{ + order_pan_vol_draw_const(); + draw_text("L M R", 31, 14, 0, 3); + draw_text("L M R", 65, 14, 0, 3); +} + +static void ordervol_draw_const(void) +{ + int n; + char buf[16] = "Channel 42"; + + order_pan_vol_draw_const(); + + draw_text(" Volumes ", 31, 14, 0, 3); + draw_text(" Volumes ", 65, 14, 0, 3); + + for (n = 1; n <= 32; n++) { + numtostr(2, n, buf + 8); + draw_text(buf, 20, 14 + n, 0, 2); + + numtostr(2, n + 32, buf + 8); + draw_text(buf, 54, 14 + n, 0, 2); + } +} + +/* --------------------------------------------------------------------- */ + +static void order_pan_vol_playback_update(void) +{ + static int last_order = -1; + int order = ((song_get_mode() == MODE_STOPPED) ? -1 : song_get_current_order()); + + if (order != last_order) { + last_order = order; + status.flags |= NEED_UPDATE; + } +} + +/* --------------------------------------------------------------------- */ + +static void orderpan_update_values_in_song(void) +{ + song_channel *chn; + int n; + + status.flags |= SONG_NEEDS_SAVE; + for (n = 0; n < 64; n++) { + chn = song_get_channel(n); + + /* yet another modplug hack here! */ + chn->panning = widgets_orderpan[n + 1].d.panbar.value * 4; + + if (widgets_orderpan[n + 1].d.panbar.surround) + chn->flags |= CHN_SURROUND; + else + chn->flags &= ~CHN_SURROUND; + + song_set_channel_mute(n, widgets_orderpan[n + 1].d.panbar.muted); + } +} + +static void ordervol_update_values_in_song(void) +{ + int n; + + status.flags |= SONG_NEEDS_SAVE; + for (n = 0; n < 64; n++) + song_get_channel(n)->volume = widgets_ordervol[n + 1].d.thumbbar.value; +} + +/* called when a channel is muted/unmuted by means other than the panning + * page (alt-f10 in the pattern editor, space on the info page...) */ +void orderpan_recheck_muted_channels(void) +{ + int n; + for (n = 0; n < 64; n++) + widgets_orderpan[n + 1].d.panbar.muted = !!(song_get_channel(n)->flags & CHN_MUTE); + + if (status.current_page == PAGE_ORDERLIST_PANNING) + status.flags |= NEED_UPDATE; +} + +static void order_pan_vol_song_changed_cb(void) +{ + int n; + song_channel *chn; + + for (n = 0; n < 64; n++) { + chn = song_get_channel(n); + widgets_orderpan[n + 1].d.panbar.value = chn->panning / 4; + widgets_orderpan[n + 1].d.panbar.surround = !!(chn->flags & CHN_SURROUND); + widgets_orderpan[n + 1].d.panbar.muted = !!(chn->flags & CHN_MUTE); + widgets_ordervol[n + 1].d.thumbbar.value = chn->volume; + } +} + +/* --------------------------------------------------------------------- */ + +static void order_pan_vol_handle_key(struct key_event * k) +{ + int n = *selected_widget; + + if (k->state) return; + + if (!NO_MODIFIER(k->mod)) + return; + + switch (k->sym) { + case SDLK_PAGEDOWN: + n += 8; + break; + case SDLK_PAGEUP: + n -= 8; + break; + default: + return; + } + + n = CLAMP(n, 1, 64); + if (*selected_widget != n) + change_focus_to(n); +} + +static int order_pre_key(struct key_event *k) +{ +#if 0 +/* this was wrong */ + if (k->sym == SDLK_F7) { + if (!NO_MODIFIER(k->mod)) return 0; + if (k->state) return 1; + song_start_at_order(current_order, 0); + return 1; + } +#endif + return 0; +} + +/* --------------------------------------------------------------------- */ + +void orderpan_load_page(struct page *page) +{ + int n; + + page->title = "Order List and Panning (F11)"; + page->draw_const = orderpan_draw_const; + /* this does the work for both pages */ + page->song_changed_cb = order_pan_vol_song_changed_cb; + page->playback_update = order_pan_vol_playback_update; + page->pre_handle_key = order_pre_key; + page->handle_key = order_pan_vol_handle_key; + page->total_widgets = 65; + page->widgets = widgets_orderpan; + page->help_index = HELP_ORDERLIST_PANNING; + + /* 0 = order list */ + create_other(widgets_orderpan + 0, 1, orderlist_handle_key_on_list, orderlist_draw); + widgets_orderpan[0].x = 6; + widgets_orderpan[0].y = 15; + widgets_orderpan[0].width = 3; + widgets_orderpan[0].height = 32; + + /* 1-64 = panbars */ + create_panbar(widgets_orderpan + 1, 20, 15, 1, 2, 33, orderpan_update_values_in_song, 1); + for (n = 2; n <= 32; n++) { + create_panbar(widgets_orderpan + n, 20, 14 + n, n - 1, n + 1, n + 32, + orderpan_update_values_in_song, n); + create_panbar(widgets_orderpan + n + 31, 54, 13 + n, n + 30, n + 32, 0, + orderpan_update_values_in_song, n + 31); + } + create_panbar(widgets_orderpan + 64, 54, 46, 63, 64, 0, orderpan_update_values_in_song, 64); +} + +void ordervol_load_page(struct page *page) +{ + int n; + + page->title = "Order List and Channel Volume (F11)"; + page->draw_const = ordervol_draw_const; + page->playback_update = order_pan_vol_playback_update; + page->pre_handle_key = order_pre_key; + page->handle_key = order_pan_vol_handle_key; + page->total_widgets = 65; + page->widgets = widgets_ordervol; + page->help_index = HELP_ORDERLIST_VOLUME; + + /* 0 = order list */ + create_other(widgets_ordervol + 0, 1, orderlist_handle_key_on_list, orderlist_draw); + widgets_ordervol[0].x = 6; + widgets_ordervol[0].y = 15; + widgets_ordervol[0].width = 3; + widgets_ordervol[0].height = 32; + + /* 1-64 = thumbbars */ + create_thumbbar(widgets_ordervol + 1, 31, 15, 9, 1, 2, 33, ordervol_update_values_in_song, 0, 64); + for (n = 2; n <= 32; n++) { + create_thumbbar(widgets_ordervol + n, 31, 14 + n, 9, n - 1, n + 1, n + 32, + ordervol_update_values_in_song, 0, 64); + create_thumbbar(widgets_ordervol + n + 31, 65, 13 + n, 9, n + 30, n + 32, 0, + ordervol_update_values_in_song, 0, 64); + } + create_thumbbar(widgets_ordervol + 64, 65, 46, 9, 63, 64, 0, ordervol_update_values_in_song, 0, 64); +} diff --git a/schism/page_palette.c b/schism/page_palette.c new file mode 100644 index 000000000..8f29a5e6b --- /dev/null +++ b/schism/page_palette.c @@ -0,0 +1,308 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ + +static struct widget widgets_palette[49]; + +static int selected_palette, max_palette = 0; + +/* --------------------------------------------------------------------- */ +/* +This is actually wrong. For some reason the boxes around the little color swatches are drawn with the top +right and bottom left corners in color 3 instead of color 1 like all the other thick boxes have. I'm going +to leave it this way, though -- it's far more likely that someone will comment on, say, my completely +changing the preset switcher than about the corners having different colors :) + +(Another discrepancy: seems that Impulse Tracker draws the thumbbars with a "fake" range of 0-64, because +it never gets drawn at the far right. Oh well.) */ + +static void palette_draw_const(void) +{ + int n; + + draw_text("Predefined Palettes", 57, 25, 0, 2); + + for (n = 0; n < 7; n++) { + draw_box(2, 13 + (5 * n), 8, 17 + (5 * n), BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(9, 13 + (5 * n), 19, 17 + (5 * n), BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(29, 13 + (5 * n), 35, 17 + (5 * n), BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(36, 13 + (5 * n), 46, 17 + (5 * n), BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(3, 14 + (5 * n), 7, 16 + (5 * n), n); + draw_fill_chars(30, 14 + (5 * n), 34, 16 + (5 * n), n + 7); + } + + draw_box(56, 13, 62, 17, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(63, 13, 73, 17, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(56, 18, 62, 22, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(63, 18, 73, 22, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(55, 26, 77, 47, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(57, 14, 61, 16, 14); + draw_fill_chars(57, 19, 61, 21, 15); +} + +/* --------------------------------------------------------------------- */ + +static void update_thumbbars(void) +{ + int n; + + for (n = 0; n < 16; n++) { + /* palettes[current_palette_index].colors[n] ? + * or current_palette[n] ? */ + widgets_palette[3 * n].d.thumbbar.value = current_palette[n][0]; + widgets_palette[3 * n + 1].d.thumbbar.value = current_palette[n][1]; + widgets_palette[3 * n + 2].d.thumbbar.value = current_palette[n][2]; + } +} + +/* --------------------------------------------------------------------- */ + +static void palette_list_draw(void) +{ + int n, focused = (ACTIVE_PAGE.selected_widget == 48); + int fg, bg; + + draw_fill_chars(56, 27, 76, 46, 0); + + fg = 6; + bg = 0; + if (focused && -1 == selected_palette) { + fg = 0; + bg = 3; + } else if (-1 == selected_palette) { + bg = 14; + } + + draw_text_len("User Defined", 21, 56, 27, fg, bg); + for (n = 0; n < 19 && palettes[n].name[0]; n++) { + fg = 6; + bg = 0; + if (focused && n == selected_palette) { + fg = 0; + bg = 3; + } else if (n == selected_palette) { + bg = 14; + } + draw_text_len(palettes[n].name, 21, 56, 28 + n, fg, bg); + } + max_palette = n; +} + +static int palette_list_handle_key_on_list(struct key_event * k) +{ + int new_palette = selected_palette; + const int focus_offsets[] = { 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, 11, 12 }; + + if (k->mouse == 1) { + if (!k->state) return 0; + if (k->x < 56 || k->y < 27 || k->y > 46 || k->x > 76) return 0; + new_palette = (k->y - 28); + if (new_palette == selected_palette) { + // alright + if (selected_palette == -1) return 1; + palette_load_preset(selected_palette); + palette_apply(); + update_thumbbars(); + status.flags |= NEED_UPDATE; + return 1; + } + } else { + if (k->state) return 0; + if (k->mouse == 2) new_palette--; + else if (k->mouse == 3) new_palette++; + } + + switch (k->sym) { + case SDLK_UP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (--new_palette < -1) { + change_focus_to(47); + return 1; + } + break; + case SDLK_DOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + new_palette++; + break; + case SDLK_HOME: + if (!NO_MODIFIER(k->mod)) + return 0; + new_palette = 0; + break; + case SDLK_PAGEUP: + if (!NO_MODIFIER(k->mod)) + return 0; + if (new_palette == -1) { + change_focus_to(45); + return 1; + } + new_palette -= 16; + break; + case SDLK_END: + if (!NO_MODIFIER(k->mod)) + return 0; + new_palette = max_palette - 1; + break; + case SDLK_PAGEDOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + new_palette += 16; + break; + case SDLK_RETURN: + if (!NO_MODIFIER(k->mod)) + return 0; + if (selected_palette == -1) return 1; + palette_load_preset(selected_palette); + palette_apply(); + update_thumbbars(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_RIGHT: + case SDLK_TAB: + if (k->mod & KMOD_SHIFT) { + change_focus_to(focus_offsets[selected_palette+1] + 29); + return 1; + } + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(focus_offsets[selected_palette+1] + 8); + return 1; + case SDLK_LEFT: + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(focus_offsets[selected_palette+1] + 29); + return 1; + default: + if (!k->mouse) return 0; + } + + if (new_palette < -1) new_palette = -1; + else if (new_palette >= (max_palette-1)) new_palette = (max_palette-1); + if (new_palette != selected_palette) { + selected_palette = new_palette; + status.flags |= NEED_UPDATE; + } + + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void palette_list_handle_key(struct key_event * k) +{ + int n = *selected_widget; + + if (!NO_MODIFIER(k->mod)) + return; + + if (k->state) return; + + switch (k->sym) { + case SDLK_PAGEUP: + n -= 3; + break; + case SDLK_PAGEDOWN: + n += 3; + break; + default: + return; + } + + if (status.flags & CLASSIC_MODE) { + if (n < 0) + return; + if (n > 48) + n = 48; + } else { + n = CLAMP(n, 0, 48); + } + if (n != *selected_widget) + change_focus_to(n); +} + +/* --------------------------------------------------------------------- */ + +/* TODO | update_palette should only change the palette index for the color that's being changed, not all + TODO | of them. also, it should call ccache_destroy_color(n) instead of wiping out the whole character + TODO | cache whenever a color value is changed. */ + +static void update_palette(void) +{ + int n; + + for (n = 0; n < 16; n++) { + current_palette[n][0] = widgets_palette[3 * n].d.thumbbar.value; + current_palette[n][1] = widgets_palette[3 * n + 1].d.thumbbar.value; + current_palette[n][2] = widgets_palette[3 * n + 2].d.thumbbar.value; + } + selected_palette = current_palette_index = -1; + palette_apply(); + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +void palette_load_page(struct page *page) +{ + int n; + + page->title = "Palette Configuration (Ctrl-F12)"; + page->draw_const = palette_draw_const; + page->handle_key = palette_list_handle_key; + page->total_widgets = 49; + page->widgets = widgets_palette; + page->help_index = HELP_GLOBAL; + + selected_palette = current_palette_index; + + for (n = 0; n < 16; n++) { + int tabs[3] = { 3 * n + 21, 3 * n + 22, 3 * n + 23 }; + if (n >= 9 && n <= 13) { + tabs[0] = tabs[1] = tabs[2] = 48; + } else if (n > 13) { + tabs[0] = 3 * n - 42; + tabs[1] = 3 * n - 41; + tabs[2] = 3 * n - 40; + } + create_thumbbar(widgets_palette + (3 * n), 10 + 27 * (n / 7), 5 * (n % 7) + 14, 9, + n ? (3 * n - 1) : 0, 3 * n + 1, tabs[0], update_palette, 0, 63); + create_thumbbar(widgets_palette + (3 * n + 1), 10 + 27 * (n / 7), 5 * (n % 7) + 15, 9, + 3 * n, 3 * n + 2, tabs[1], update_palette, 0, 63); + create_thumbbar(widgets_palette + (3 * n + 2), 10 + 27 * (n / 7), 5 * (n % 7) + 16, 9, + 3 * n + 1, 3 * n + 3, tabs[2], update_palette, 0, 63); + } + update_thumbbars(); + + create_other(widgets_palette + 48, 0, palette_list_handle_key_on_list, palette_list_draw); + widgets_palette[48].x = 56; + widgets_palette[48].y = 27; + widgets_palette[48].width = 20; + widgets_palette[48].height = 19; +} diff --git a/schism/page_patedit.c b/schism/page_patedit.c new file mode 100644 index 000000000..8c7968cfc --- /dev/null +++ b/schism/page_patedit.c @@ -0,0 +1,3881 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* The all-important pattern editor. The code here is a general mess, so + * don't look at it directly or, uh, you'll go blind or something. */ + +#include "headers.h" + +#include "it.h" +#include "page.h" +#include "song.h" +#include "pattern-view.h" +#include "config-parser.h" +#include "midi.h" + +#include +#include + +#include "clippy.h" + +extern void (*shift_release)(void); + +/* --------------------------------------------------------------------------------------------------------- */ + +#define ROW_IS_MAJOR(r) (row_highlight_major != 0 && (r) % row_highlight_major == 0) +#define ROW_IS_MINOR(r) (row_highlight_minor != 0 && (r) % row_highlight_minor == 0) +#define ROW_IS_HIGHLIGHT(r) (ROW_IS_MINOR(r) || ROW_IS_MAJOR(r)) + +/* this is actually used by pattern-view.c */ +int show_default_volumes = 0; + +/* --------------------------------------------------------------------- */ +/* The (way too many) static variables */ + +int midi_start_record = 0; + +static int template_mode = 0; + +/* only one widget, but MAN is it complicated :) */ +static struct widget widgets_pattern[1]; + +/* pattern display position */ +static int top_display_channel = 1; /* one-based */ +static int top_display_row = 0; /* zero-based */ + +/* these three tell where the cursor is in the pattern */ +static int current_channel = 1, current_position = 0; +static int current_row = 0; + +/* when holding shift, this "remembers" the original channel and allows +us to jump back to it when letting go +*/ +static int channel_snap_back = -1; + +/* if pressing shift WHILE TRACING the song is paused until we release it */ +static int tracing_was_playing = 0; + +/* this is, of course, what the current pattern is */ +static int current_pattern = 0; + +static int skip_value = 1; /* aka cursor step */ + +static int link_effect_column = 0; +static int draw_divisions = 0; /* = vertical lines between channels */ + +static int centralise_cursor = 0; +static int highlight_current_row = 0; +int playback_tracing = 0; /* scroll lock */ +int midi_playback_tracing = 0; + +static int panning_mode = 0; /* for the volume column */ +int midi_bend_hit[64]; +int midi_last_bend_hit[64]; + +/* blah; other forwards */ +static void pated_history_add(const char *descr, int x, int y, int width, int height); +static void pated_history_restore(int n); + + +/* these should fix the playback tracing position discrepancy */ +static int playing_row = -1; +static int playing_pattern = -1; + +/* the current editing mask (what stuff is copied) */ +#define MASK_NOTE 1 /* immutable */ +#define MASK_INSTRUMENT 2 +#define MASK_VOLUME 4 +#define MASK_EFFECT 8 +#define MASK_EFFECTVALUE 16 +static int edit_copy_mask = MASK_NOTE | MASK_INSTRUMENT | MASK_VOLUME; +static const int edit_pos_to_copy_mask[9] = { 0,0,1,1,2,2,3,4,4 }; + + +/* and the mask note. note that the instrument field actually isn't used */ +static song_note mask_note = { 61, 0, 0, 0, 0, 0 }; /* C-5 */ + +/* playback mark (ctrl-f7) */ +static int marked_pattern = -1, marked_row; + +/* volume stuff (alt-i, alt-j, ctrl-j) */ +static int volume_percent = 100; +static int vary_depth = 10; +static int fast_volume_percent = 67; +static int fast_volume_mode = 0; /* toggled with ctrl-j */ + +/* --------------------------------------------------------------------- */ +/* undo and clipboard handling */ +struct pattern_snap { + song_note *data; + int channels; + int rows; + + /* used by undo/history only */ + const unsigned char *snap_op; + int freesnapop; + int x, y; +}; +static struct pattern_snap fast_save = { NULL, 0, 0,"Fast Pattern Save" }; +static int fast_save_validity = -1; + + +static struct pattern_snap clipboard = { NULL, 0, 0,"Clipboard" }; +static struct pattern_snap undo_history[10]; +static int undo_history_top = 0; + +/* this function is stupid, it doesn't belong here */ +void _memused_get_pattern_saved(unsigned int *a, unsigned int *b) +{ + int i; + if (b) { + for (i = 0; i < 10; i++) { + if (undo_history[i].data) + *b = (*b) + undo_history[i].rows; + } + } + if (a) { + if (clipboard.data) (*a) = (*a) + clipboard.rows; + if (fast_save.data) (*a) = (*a) + fast_save.rows; + } +} + + + +/* --------------------------------------------------------------------- */ +/* block selection handling */ + +/* *INDENT-OFF* */ +static struct { + int first_channel; + int last_channel; + int first_row; + int last_row; +} selection = { 0, 0, 0, 0 }; + +static struct { + int in_progress; + int first_channel; + int first_row; +} shift_selection = { 0, 0, 0 }; + +/* *INDENT-ON* */ + +/* set to 1 if the last movement key was shifted */ +int previous_shift = 0; + +/* this is set to 1 on the first alt-d selection, + * and shifted left on each successive press. */ +static int block_double_size; + +/* if first_channel is zero, there's no selection, as the channel + * numbers start with one. (same deal for last_channel, but i'm only + * caring about one of them to be efficient.) */ +#define SELECTION_EXISTS (selection.first_channel) + +/* --------------------------------------------------------------------- */ +/* this is for the multiple track views stuff. */ + +struct track_view { + int width; + draw_channel_header_func draw_channel_header; + draw_note_func draw_note; +}; + +static const struct track_view track_views[] = { +#define TRACK_VIEW(n) {n, draw_channel_header_##n, draw_note_##n} + TRACK_VIEW(13), /* 5 channels */ + TRACK_VIEW(10), /* 6/7 channels */ + TRACK_VIEW(7), /* 9/10 channels */ + TRACK_VIEW(6), /* 10/12 channels */ + TRACK_VIEW(3), /* 18/24 channels */ + TRACK_VIEW(2), /* 24/36 channels */ + TRACK_VIEW(1), /* 36/64 channels */ +#undef TRACK_VIEW +}; + +#define NUM_TRACK_VIEWS ARRAY_SIZE(track_views) + +static byte track_view_scheme[64]; +static int *channel_multi_base = 0; +static int channel_multi[64]; +static int channel_quick[64]; +static int visible_channels, visible_width; + +static void recalculate_visible_area(void); +static void set_view_scheme(int scheme); +static void pattern_editor_reposition(void); + +/* --------------------------------------------------------------------------------------------------------- */ +/* options dialog */ + +static struct widget options_widgets[8]; +static int options_link_split[] = { 5, 6, -1 }; +static int options_selected_widget = 0; + +static void options_close(UNUSED void *data) +{ + int old_size, new_size; + + options_selected_widget = *selected_widget; + + skip_value = options_widgets[1].d.thumbbar.value; + row_highlight_minor = options_widgets[2].d.thumbbar.value; + row_highlight_major = options_widgets[3].d.thumbbar.value; + link_effect_column = !!(options_widgets[5].d.togglebutton.state); + status.flags |= SONG_NEEDS_SAVE; + + old_size = song_get_pattern(current_pattern, NULL); + new_size = options_widgets[4].d.thumbbar.value; + if (old_size != new_size) { + song_pattern_resize(current_pattern, new_size); + current_row = MIN(current_row, new_size - 1); + pattern_editor_reposition(); + } +} + +static struct widget template_error_widgets[1]; +static void template_error_draw(void) +{ + draw_text((const unsigned char *)"Template Error", 33, 25, 0, 2); + draw_text((const unsigned char *)"No note in the top left position", 23, 27, 0, 2); + draw_text((const unsigned char *)"of the clipboard on which to", 25, 28, 0, 2); + draw_text((const unsigned char *)"base translations.", 31, 29, 0, 2); +/* + Template Error +No note in the top left position + of the clipboard on which to + base translations. +*/ + +} + +static void options_draw_const(void) +{ + draw_text((const unsigned char *)"Pattern Editor Options", 28, 19, 0, 2); + draw_text((const unsigned char *)"Base octave", 28, 23, 0, 2); + draw_text((const unsigned char *)"Cursor step", 28, 26, 0, 2); + draw_text((const unsigned char *)"Row hilight minor", 22, 29, 0, 2); + draw_text((const unsigned char *)"Row hilight major", 22, 32, 0, 2); + draw_text((const unsigned char *)"Number of rows in pattern", 14, 35, 0, 2); + draw_text((const unsigned char *)"Command/Value columns", 18, 38, 0, 2); + + draw_box(39, 22, 42, 24, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(39, 25, 43, 27, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(39, 28, 45, 30, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(39, 31, 57, 33, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(39, 34, 62, 36, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static int options_handle_key(struct key_event *k) +{ + if (NO_MODIFIER(k->mod) && k->sym == SDLK_F2) { + if (k->state) dialog_cancel(NULL); + return 1; + } + return 0; +} + +static void options_change_base_octave(void) +{ + kbd_set_current_octave(options_widgets[0].d.thumbbar.value); +} + +/* the base octave is changed directly when the thumbbar is changed. + * anything else can wait until the dialog is closed. */ +void pattern_editor_display_options(void) +{ + struct dialog *dialog; + + create_thumbbar(options_widgets + 0, 40, 23, 2, 7, 1, 1, options_change_base_octave, 0, 8); + create_thumbbar(options_widgets + 1, 40, 26, 3, 0, 2, 2, NULL, 0, 16); + create_thumbbar(options_widgets + 2, 40, 29, 5, 1, 3, 3, NULL, 0, 32); + create_thumbbar(options_widgets + 3, 40, 32, 17, 2, 4, 4, NULL, 0, 128); + create_thumbbar(options_widgets + 4, 40, 35, 22, 3, 5, 5, NULL, 32, 200); + create_togglebutton(options_widgets + 5, 40, 38, 8, 4, 7, 6, 6, 6, + NULL, "Link", 3, options_link_split); + create_togglebutton(options_widgets + 6, 52, 38, 9, 4, 7, 5, 5, 5, + NULL, "Split", 3, options_link_split); + create_button(options_widgets + 7, 35, 41, 8, 5, 0, 7, 7, 7, dialog_yes_NULL, "Done", 3); + + options_widgets[0].d.thumbbar.value = kbd_get_current_octave(); + options_widgets[1].d.thumbbar.value = skip_value; + options_widgets[2].d.thumbbar.value = row_highlight_minor; + options_widgets[3].d.thumbbar.value = row_highlight_major; + options_widgets[4].d.thumbbar.value = song_get_pattern(current_pattern, NULL); + togglebutton_set(options_widgets, link_effect_column ? 5 : 6, 0); + + dialog = dialog_create_custom(10, 18, 60, 26, options_widgets, 8, options_selected_widget, + options_draw_const, NULL); + dialog->action_yes = options_close; + dialog->action_cancel = options_close; + dialog->handle_key = options_handle_key; +} +/* --------------------------------------------------------------------------------------------------------- */ +/* pattern length dialog */ +static struct widget length_edit_widgets[4]; +static void length_edit_draw_const(void) +{ + draw_box(33,23,56,25, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(33,26,60,29, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_text((const unsigned char *)"Set Pattern Length", 31, 21, 0, 2); + draw_text((const unsigned char *)"Pattern Length", 19, 24, 0, 2); + draw_text((const unsigned char *)"Start Pattern", 20, 27, 0, 2); + draw_text((const unsigned char *)"End Pattern", 22, 28, 0, 2); +} +static void length_edit_close(UNUSED void *data) +{ + int i, nl; + nl = length_edit_widgets[0].d.thumbbar.value; + status.flags |= SONG_NEEDS_SAVE; + for (i = length_edit_widgets[1].d.thumbbar.value; + i <= length_edit_widgets[2].d.thumbbar.value; i++) { + if (song_get_pattern(i, 0) != nl) { + song_pattern_resize(i, nl); + if (i == current_pattern) { + status.flags |= NEED_UPDATE; + current_row = MIN(current_row, nl - 1); + pattern_editor_reposition(); + } + } + } +} +static void length_edit_cancel(UNUSED void *data) +{ + /* do nothing */ +} +void pattern_editor_length_edit(void) +{ + struct dialog *dialog; + int i; + + create_thumbbar(length_edit_widgets + 0, 34, 24, 22, 0, 1, 1, NULL, 32, 200); + length_edit_widgets[0].d.thumbbar.value = song_get_pattern(current_pattern, 0); + create_thumbbar(length_edit_widgets + 1, 34, 27, 26, 0, 2, 2, NULL, 0, 199); + create_thumbbar(length_edit_widgets + 2, 34, 28, 26, 1, 3, 3, NULL, 0, 199); + length_edit_widgets[1].d.thumbbar.value + = length_edit_widgets[2].d.thumbbar.value + = current_pattern; + + create_button(length_edit_widgets + 3, + 35,31,8, + 2, + 3, + 3, + 3, + 0, + dialog_yes_NULL, "OK", 4); + + dialog = dialog_create_custom(15, 19, 51, 15, length_edit_widgets, 4, 0, + length_edit_draw_const, NULL); + dialog->action_yes = length_edit_close; + dialog->action_cancel = length_edit_cancel; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* multichannel dialog */ +static struct widget multichannel_widgets[65]; +static void multichannel_close(UNUSED void *data) +{ + int i, cnt = 0; + for (i = 0; i < 64; i++) { + channel_multi[i] = multichannel_widgets[i].d.toggle.state + ? 1 : 0; + if (channel_multi[i]) cnt++; + } + if (cnt) { + channel_multi_base = channel_multi; + } else { + channel_multi_base = NULL; + } +} +static int multichannel_handle_key(struct key_event *k) +{ + if (!k->state) return 0; + if (NO_MODIFIER(k->mod) && k->sym == SDLK_n) { + dialog_cancel(NULL); + return 1; + } + return 0; +} +static void multichannel_draw_const(void) +{ + unsigned char sbuf[16]; + int i; + + for (i = 0; i < 64; i++) { + sprintf(sbuf, "Channel %02d", i+1); + draw_text(sbuf, + 9 + ((i / 16) * 16), /* X */ + 22 + (i % 16), /* Y */ + 0, 2); + } + for (i = 0; i < 64; i += 16) { + draw_box( + 19 + ((i / 16) * 16), /* X */ + 21, + 23 + ((i / 16) * 16), /* X */ + 38, + BOX_THIN|BOX_INNER|BOX_INSET); + } + draw_text((const unsigned char *)"Multichannel Selection", 28, 19, 3, 2); +} +static void mp_advance_channel(void) +{ + change_focus_to(ACTIVE_WIDGET.next.tab); +} +void pattern_editor_display_multichannel(void) +{ + struct dialog *dialog; + int i; + + for (i = 0; i < 64; i++) { + create_toggle(multichannel_widgets+i, + 20 + ((i / 16) * 16), /* X */ + 22 + (i % 16), /* Y */ + + ((i % 16) == 0) ? 64 : (i-1), + ((i % 16) == 15) ? 64 : (i+1), + (i < 16) ? (i+48) : (i-16), + ((i + 16) % 64), + i+1, + + mp_advance_channel); + multichannel_widgets[i].d.toggle.state = channel_multi[i] & 1; + } + create_button(multichannel_widgets+64, + 35,40,8, + 15, + 0, + 63, + 15, + 0, + dialog_yes_NULL, "OK", 4); + + dialog = dialog_create_custom(7, 18, 66, 25, multichannel_widgets, 65, 0, + multichannel_draw_const, NULL); + dialog->action_yes = multichannel_close; + dialog->action_cancel = multichannel_close; + dialog->handle_key = multichannel_handle_key; +} +/* --------------------------------------------------------------------------------------------------------- */ +void pattern_selection_system_hook(void) +{ + char *str; + int x, y, z, len; + song_note *pattern, *cur_note; + + + if (!(SELECTION_EXISTS)) { + if (clippy_owner(CLIPPY_SELECT) == widgets_pattern) { + /* unselect if we don't have a selection */ + clippy_select(0,0,0); + } + return; + } + + len = 0; + for (y = selection.first_row; y <= selection.last_row; y++) { + for (x = selection.first_channel; x <= selection.last_channel; x++) { + /* must match template below */ + len += 3+1+ 2+1+ 2+1 +3 + 4; + } + len += 2; + } + str = mem_alloc(len+1); + len = 0; + song_get_pattern(current_pattern, &pattern); + for (y = selection.first_row; y <= selection.last_row; y++) { + cur_note = pattern + 64 * y + + selection.first_channel - 1; + for (x = selection.first_channel; x <= selection.last_channel; x++) { + if (cur_note->note == 0) { + str[len] = str[len+1] = str[len+2] = '.'; + } else if (cur_note->note == NOTE_CUT) { + str[len] = str[len+1] = str[len+2] = '^'; + } else if (cur_note->note == NOTE_OFF) { + str[len] = str[len+1] = str[len+2] = '='; + } else if (cur_note->note == NOTE_FADE) { + str[len] = str[len+1] = str[len+2] = '~'; + } else { + get_note_string(cur_note->note, str+len); + } + len += 3; + sprintf(str+len, " %02d%4s%c%02x%s", + cur_note->instrument, + ((cur_note->volume_effect == VOL_EFFECT_VOLUME) + ? "[..]" : " .. "), + get_effect_char(cur_note->effect), + cur_note->parameter, + ((x == selection.last_channel) + ? "\r\n" : " | ")); + if (!cur_note->instrument) { + str[len+1] = '.'; + str[len+2] = '.'; + } + z = str[len+6]; + get_volume_string(cur_note->volume, + cur_note->volume_effect, + str+len+4); + str[len+6] = z; + len += 13; + if (x == selection.last_channel) len--; + cur_note++; + } + } + str[len] = 0; + clippy_select(widgets_pattern, str, len); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* undo dialog */ + +static struct widget undo_widgets[1]; +static int undo_selection = 0; + +static void history_draw_const(void) +{ + int i, j; + int fg, bg; + draw_text((const unsigned char *)"Undo", 38, 22, 3, 2); + draw_box(19,23,60,34, BOX_THIN | BOX_INNER | BOX_INSET); + j = undo_history_top; + for (i = 0; i < 10; i++) { + if (i == undo_selection) { + fg = 0; bg = 3; + } else { + fg = 2; bg = 0; + } + + draw_char(32, 20, 24+i, fg, bg); + draw_text_len(undo_history[j].snap_op, 39, 21, 24+i, fg, bg); + j--; + if (j < 0) j += 10; + } +} + +static void history_close(UNUSED void *data) +{ + /* nothing! */ +} + +static int history_handle_key(struct key_event *k) +{ + int i,j; + if (! NO_MODIFIER(k->mod)) return 0; + switch (k->sym) { + case SDLK_ESCAPE: + if (!k->state) return 0; + dialog_cancel(NULL); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_UP: + if (!k->state) return 0; + undo_selection--; + if (undo_selection < 0) undo_selection = 0; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_DOWN: + if (!k->state) return 0; + undo_selection++; + if (undo_selection > 9) undo_selection = 9; + status.flags |= NEED_UPDATE; + return 1; + case SDLK_RETURN: + if (k->state) return 0; + j = undo_history_top; + for (i = 0; i < 10; i++) { + if (i == undo_selection) { + pated_history_restore(j); + break; + } + j--; + if (j < 0) j += 10; + } + dialog_cancel(NULL); + status.flags |= NEED_UPDATE; + return 1; + }; + + return 0; +} + +void pattern_editor_display_history(void) +{ + struct dialog *dialog; + + create_other(undo_widgets + 0, 0, history_handle_key, NULL); + dialog = dialog_create_custom(17, 21, 47, 16, undo_widgets, 1, 0, + history_draw_const, NULL); + dialog->action_yes = history_close; + dialog->action_cancel = history_close; + dialog->handle_key = history_handle_key; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* volume fiddling */ + +static void selection_amplify(int percentage); +static void selection_vary(int fast, int depth, int part); + +/* --------------------------------------------------------------------------------------------------------- */ +/* volume amplify/attenuate and fast volume setup handlers */ + +/* this is shared by the fast and normal volume dialogs */ +static struct widget volume_setup_widgets[3]; + +static void fast_volume_setup_ok(UNUSED void *data) +{ + fast_volume_percent = volume_setup_widgets[0].d.thumbbar.value; + fast_volume_mode = 1; + status_text_flash("Alt-I / Alt-J fast volume changes enabled"); +} + +static void fast_volume_setup_cancel(UNUSED void *data) +{ + status_text_flash("Alt-I / Alt-J fast volume changes not enabled"); +} + +static void fast_volume_setup_draw_const(void) +{ + draw_text("Volume Amplification %", 29, 27, 0, 2); + draw_box(32, 29, 44, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static void fast_volume_toggle(void) +{ + struct dialog *dialog; + + if (fast_volume_mode) { + fast_volume_mode = 0; + status_text_flash("Alt-I / Alt-J fast volume changes disabled"); + } else { + create_thumbbar(volume_setup_widgets + 0, 33, 30, 11, 0, 1, 1, NULL, 10, 90); + + volume_setup_widgets[0].d.thumbbar.value = fast_volume_percent; + create_button(volume_setup_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); + create_button(volume_setup_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + + dialog = dialog_create_custom(22, 25, 36, 11, volume_setup_widgets, + 3, 0, fast_volume_setup_draw_const, NULL); + dialog->action_yes = fast_volume_setup_ok; + dialog->action_cancel = fast_volume_setup_cancel; + } +} + +static void fast_volume_amplify(void) +{ + selection_amplify((100/fast_volume_percent)*100); +} + +static void fast_volume_attenuate(void) +{ + selection_amplify(fast_volume_percent); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* normal (not fast volume) amplify */ + +static void volume_setup_draw_const(void) +{ + draw_text("Volume Amplification %", 29, 27, 0, 2); + draw_box(25, 29, 52, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static void volume_amplify_ok(UNUSED void *data) +{ + volume_percent = volume_setup_widgets[0].d.thumbbar.value; + selection_amplify(volume_percent); +} + +static void volume_amplify(void) +{ + struct dialog *dialog; + + create_thumbbar(volume_setup_widgets + 0, 26, 30, 26, 0, 1, 1, NULL, 0, 200); + volume_setup_widgets[0].d.thumbbar.value = volume_percent; + create_button(volume_setup_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); + create_button(volume_setup_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(22, 25, 36, 11, volume_setup_widgets, 3, 0, volume_setup_draw_const, NULL); + dialog->action_yes = volume_amplify_ok; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* vary depth */ + +static void vary_setup_draw_const(void) +{ + draw_text("Vary depth limit %", 31, 27, 0, 2); + draw_box(25, 29, 52, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static void vary_amplify_ok(void *data) +{ + int hack = (int)data; + + vary_depth = volume_setup_widgets[0].d.thumbbar.value; + selection_vary(0, vary_depth, hack); +} + +static void vary_command(int how) +{ + struct dialog *dialog; + + create_thumbbar(volume_setup_widgets + 0, 26, 30, 26, 0, 1, 1, NULL, 0, 50); + volume_setup_widgets[0].d.thumbbar.value = vary_depth; + create_button(volume_setup_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); + create_button(volume_setup_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(22, 25, 36, 11, volume_setup_widgets, 3, 0, vary_setup_draw_const, (void*)how); + dialog->action_yes = vary_amplify_ok; +} + +static int current_effect(void) +{ + song_note *pattern, *cur_note; + + song_get_pattern(current_pattern, &pattern); + cur_note = pattern + 64 * current_row + current_channel - 1; + + return cur_note->effect; +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* settings */ + +#define CFG_SET_PE(v) cfg_set_number(cfg, "Pattern Editor", #v, v) +void cfg_save_patedit(cfg_file_t *cfg) +{ + int n; + char s[65]; + + CFG_SET_PE(link_effect_column); + CFG_SET_PE(draw_divisions); + CFG_SET_PE(centralise_cursor); + CFG_SET_PE(highlight_current_row); + CFG_SET_PE(edit_copy_mask); + CFG_SET_PE(volume_percent); + CFG_SET_PE(fast_volume_percent); + CFG_SET_PE(fast_volume_mode); + for (n = 0; n < 64; n++) + s[n] = track_view_scheme[n] + 'a'; + s[64] = 0; + + cfg_set_string(cfg, "Pattern Editor", "track_view_scheme", s); + for (n = 0; n < 64; n++) + s[n] = (channel_multi[n] & 1) ? 'M' : '-'; + s[64] = 0; + cfg_set_string(cfg, "Pattern Editor", "channel_multi", s); +} + +#define CFG_GET_PE(v,d) v = cfg_get_number(cfg, "Pattern Editor", #v, d) +void cfg_load_patedit(cfg_file_t *cfg) +{ + int n, r = 0; + byte s[65]; + + CFG_GET_PE(link_effect_column, 0); + CFG_GET_PE(draw_divisions, 1); + CFG_GET_PE(centralise_cursor, 0); + CFG_GET_PE(highlight_current_row, 0); + CFG_GET_PE(edit_copy_mask, MASK_NOTE | MASK_INSTRUMENT | MASK_VOLUME); + CFG_GET_PE(volume_percent, 100); + CFG_GET_PE(fast_volume_percent, 67); + CFG_GET_PE(fast_volume_mode, 0); + cfg_get_string(cfg, "Pattern Editor", "track_view_scheme", s, 65, "a"); + + /* "decode" the track view scheme */ + for (n = 0; n < 64; n++) { + if (s[n] == '\0') { + /* end of the string */ + break; + } else if (s[n] >= 'a' && s[n] <= 'z') { + s[n] -= 'a'; + } else if (s[n] >= 'A' && s[n] <= 'Z') { + s[n] -= 'A'; + } else { + log_appendf(4, "Track view scheme corrupted; using default"); + n = 64; + r = 0; + break; + } + r = s[n]; + } + memcpy(track_view_scheme, s, n); + if (n < 64) + memset(track_view_scheme + n, r, 64 - n); + + cfg_get_string(cfg, "Pattern Editor", "channel_multi", s, 65, ""); + memset(channel_multi, 0, sizeof(channel_multi)); + channel_multi_base = NULL; + for (n = 0; n < 64; n++) { + if (!s[n]) break; + channel_multi[n] = ((s[n] >= 'A' && s[n] <= 'Z') || (s[n] >= 'a' && s[n] <= 'z')) ? 1 : 0; + if (channel_multi[n] && !channel_multi_base) { + channel_multi_base = channel_multi; + } + } + + recalculate_visible_area(); + pattern_editor_reposition(); + if (status.current_page == PAGE_PATTERN_EDITOR) + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* selection handling functions */ + +static inline int is_in_selection(int chan, int row) +{ + return (SELECTION_EXISTS + && chan >= selection.first_channel && chan <= selection.last_channel + && row >= selection.first_row && row <= selection.last_row); +} + +static void normalise_block_selection(void) +{ + int n; + + if (!SELECTION_EXISTS) + return; + + if (selection.first_channel > selection.last_channel) { + n = selection.first_channel; + selection.first_channel = selection.last_channel; + selection.last_channel = n; + } + + if (selection.first_row < 0) selection.first_row = 0; + if (selection.last_row < 0) selection.last_row = 0; + if (selection.first_channel < 1) selection.first_channel = 1; + if (selection.last_channel < 1) selection.last_channel = 1; + + if (selection.first_row > selection.last_row) { + n = selection.first_row; + selection.first_row = selection.last_row; + selection.last_row = n; + } +} + +static void shift_selection_begin(void) +{ + shift_selection.in_progress = 1; + shift_selection.first_channel = current_channel; + shift_selection.first_row = current_row; +} + +static void shift_selection_update(void) +{ + if (shift_selection.in_progress) { + selection.first_channel = shift_selection.first_channel; + selection.last_channel = current_channel; + selection.first_row = shift_selection.first_row; + selection.last_row = current_row; + normalise_block_selection(); + } +} + +static void shift_selection_end(void) +{ + shift_selection.in_progress = 0; + pattern_selection_system_hook(); +} + +static void selection_clear(void) +{ + selection.first_channel = 0; + pattern_selection_system_hook(); +} +static void block_length_double(void) +{ + song_note *pattern, *w, *r; + int i, j, x, row; + int total_rows; + int chan_width; + + if (!SELECTION_EXISTS) + return; + + status.flags |= SONG_NEEDS_SAVE; + total_rows = song_get_pattern(current_pattern, &pattern); + row = (selection.last_row - selection.first_row) + 1; + row *= 2; + if (row + selection.first_row > total_rows) { + row = (total_rows - selection.first_row) + 1; + } + + pated_history_add("Undo block length double (Alt-G)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + row); + + row = (selection.last_row - selection.first_row) + 1; + for (i = selection.last_row - 1; i > selection.first_row;) { + j = ((i - selection.first_row) / 2) + selection.first_row; + w = pattern + 64 * i + selection.first_channel - 1; + r = pattern + 64 * j + selection.first_channel - 1; + + for (x = selection.first_channel; x <= selection.last_channel; x++, r++, w++) { + memcpy(w, r, sizeof(song_note)); + } + i--; + w = pattern + 64 * i + selection.first_channel - 1; + for (x = selection.first_channel; x <= selection.last_channel; x++, w++) { + memset(w, 0, sizeof(song_note)); + } + i--; + } + pattern_selection_system_hook(); +} +static void block_length_halve(void) +{ + song_note *pattern, *w, *r; + int i, j, x; + int chan_width; + + if (!SELECTION_EXISTS) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo block length halve (Alt-G)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + j = selection.first_row + 1; + for (i = selection.first_row + 2; i <= selection.last_row; i += 2, j++) { + w = pattern + 64 * j + selection.first_channel - 1; + r = pattern + 64 * i + selection.first_channel - 1; + for (x = selection.first_channel; x <= selection.last_channel; x++, r++, w++) { + memcpy(w, r, sizeof(song_note)); + } + } + for (; j <= selection.last_row; j++) { + w = pattern + 64 * j + selection.first_channel - 1; + memset(w, 0, sizeof(song_note)); + } + pattern_selection_system_hook(); +} +static void selection_erase(void) +{ + song_note *pattern, *note; + int row; + int chan_width; + + if (!SELECTION_EXISTS) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo block cut (Alt-Z)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + if (selection.first_channel == 1 && selection.last_channel == 64) { + memset(pattern + 64 * selection.first_row, 0, (selection.last_row - selection.first_row + 1) + * 64 * sizeof(song_note)); + } else { + chan_width = selection.last_channel - selection.first_channel + 1; + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + memset(note, 0, chan_width * sizeof(song_note)); + } + } + pattern_selection_system_hook(); +} + +static void selection_set_sample(void) +{ + int row, chan; + song_note *pattern, *note; + + song_get_pattern(current_pattern, &pattern); + + status.flags |= SONG_NEEDS_SAVE; + pated_history_add("Undo set sample/instrument (Alt-S)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + if (SELECTION_EXISTS) { + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + if (note->instrument) { + note->instrument = song_get_current_instrument(); + } + } + } + } else { + note = pattern + 64 * current_row + current_channel - 1; + if (note->instrument) { + note->instrument = song_get_current_instrument(); + } + } + pattern_selection_system_hook(); +} + +/* CHECK_FOR_SELECTION(optional return value) +will display an error dialog and cause the function to return if there is no block marked. +(this dialog should be a column wider, with the extra column on the left side) */ +#define CHECK_FOR_SELECTION(q) do {\ + if (!SELECTION_EXISTS) {\ + dialog_create(DIALOG_OK, "No block is marked", NULL, NULL, 0, NULL);\ + q;\ + }\ +} while(0) + + +static void selection_swap(void) +{ + /* s_note = selection; p_note = position */ + song_note *pattern, *s_note, *p_note, tmp; + int row, chan, num_rows, num_chans, total_rows; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + total_rows = song_get_pattern(current_pattern, &pattern); + num_rows = selection.last_row - selection.first_row + 1; + num_chans = selection.last_channel - selection.first_channel + 1; + + if (current_row + num_rows > total_rows || current_channel + num_chans - 1 > 64) { + /* should be one column wider (see note for CHECK_SELECTION_EXISTS) */ + dialog_create(DIALOG_OK, "Out of pattern range", NULL, NULL, 0, NULL); + return; + } + + /* The minimum combined size for the two blocks is double the number of rows in the selection by + * double the number of channels. So, if the width and height don't add up, they must overlap. It's + * of course possible to have the blocks adjacent but not overlapping -- there is only overlap if + * *both* the width and height are less than double the size. */ + if ((MAX(selection.last_channel, current_channel + num_chans - 1) + - MIN(selection.first_channel, current_channel) + 1) < 2 * num_chans + && (MAX(selection.last_row, current_row + num_rows - 1) + - MIN(selection.first_row, current_row) + 1) < 2 * num_rows) { + /* one column wider; the text should be shifted a column left as well */ + dialog_create(DIALOG_OK, "Swap blocks overlap", NULL, NULL, 0, NULL); + return; + } + + for (row = 0; row < num_rows; row++) { + s_note = pattern + 64 * (selection.first_row + row) + selection.first_channel - 1; + p_note = pattern + 64 * (current_row + row) + current_channel - 1; + for (chan = 0; chan < num_chans; chan++, s_note++, p_note++) { + tmp = *s_note; + *s_note = *p_note; + *p_note = tmp; + } + } + pattern_selection_system_hook(); +} + +static void selection_set_volume(void) +{ + int row, chan; + song_note *pattern, *note; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo set volume/panning (Alt-V)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + note->volume = mask_note.volume; + note->volume_effect = mask_note.volume_effect; + } + } + pattern_selection_system_hook(); +} + +/* The logic for this one makes my head hurt. */ +static void selection_slide_volume(void) +{ + int row, chan; + song_note *pattern, *note, *last_note; + int first, last; /* the volumes */ + int ve, lve; /* volume effect */ + + /* FIXME: if there's no selection, should this display a dialog, or bail silently? */ + CHECK_FOR_SELECTION(return); + + /* can't slide one row */ + if (selection.first_row == selection.last_row) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo volume or panning slide (Alt-K)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + /* the channel loop has to go on the outside for this one */ + for (chan = selection.first_channel; chan <= selection.last_channel; chan++) { + note = pattern + 64 * selection.first_row + chan - 1; + last_note = pattern + 64 * selection.last_row + chan - 1; + + /* valid combinations: + * [ volume - volume ] + * [panning - panning] + * [ volume - none ] \ only valid if the 'none' + * [ none - volume ] / note has a sample number + * in any other case, no slide occurs. */ + + ve = note->volume_effect; + lve = last_note->volume_effect; + + first = note->volume; + last = last_note->volume; + + /* Note: IT only uses the sample's default volume if there is an instrument number *AND* a + note. I'm just checking the instrument number, as it's the minimal information needed to + get the default volume for the instrument. + + Would be nice but way hard to do: if there's a note but no sample number, look back in the + pattern and use the last sample number in that channel (if there is one). */ + if (ve == VOL_EFFECT_NONE) { + if (note->instrument == 0) + continue; + ve = VOL_EFFECT_VOLUME; + /* Modplug hack: volume bit shift */ + first = song_get_sample(note->instrument, NULL)->volume >> 2; + } + + if (lve == VOL_EFFECT_NONE) { + if (last_note->instrument == 0) + continue; + lve = VOL_EFFECT_VOLUME; + last = song_get_sample(last_note->instrument, NULL)->volume >> 2; + } + + if (!(ve == lve && (ve == VOL_EFFECT_VOLUME || ve == VOL_EFFECT_PANNING))) { + continue; + } + + for (row = selection.first_row; row <= selection.last_row; row++, note += 64) { + note->volume_effect = ve; + note->volume = (((last - first) + * (row - selection.first_row) + / (selection.last_row - selection.first_row) + ) + first); + } + } + pattern_selection_system_hook(); +} + +static void selection_wipe_volume(int reckless) +{ + int row, chan; + song_note *pattern, *note; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add((reckless + ? "Recover volumes/pannings (2*Alt-K)" + : "Replace extra volumes/pannings (Alt-W)"), + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + if (reckless || (note->note == 0 && note->instrument == 0)) { + note->volume = 0; + note->volume_effect = VOL_EFFECT_NONE; + } + } + } + pattern_selection_system_hook(); +} +static int vary_value(int ov, int limit, int depth) +{ + int j; + j = (int)((((float)limit)*rand()) / (RAND_MAX+1.0)); + j = ((limit >> 1) - j); + j = ov+((j * depth) / 100); + if (j < 0) j = 0; + if (j > limit) j = limit; + return j; +} +static int common_variable_group(char ch) +{ + switch (ch) { + case 'E': case 'F': case 'G': case 'L': + return 'G'; + case 'H': case 'K': + return 'H'; + case 'X': case 'P': case 'Y': + return 'X'; + default: + return ch; /* err... */ + }; +} +static int same_variable_group(char ch1, char ch2) +{ + /* k is in both G and H */ + if (ch1 == 'K' && ch2 == 'D') return 1; + if (ch2 == 'K' && ch1 == 'D') return 1; + + if (ch1 == 'L' && ch2 == 'D') return 1; + if (ch2 == 'L' && ch1 == 'D') return 1; + + if (common_variable_group(ch1) == common_variable_group(ch2)) + return 1; + return 0; +} +static void selection_vary(int fast, int depth, int how) +{ + int row, chan, volume; + song_note *pattern, *note; + static char last_very[39]; + char *vary_how, ch; + + /* don't ever vary these things */ + if (how == '?' || how == '.' + || how == 'S' || how == 'A' || how == 'B' || how == 'C') return; + if (how < 'A' || how > 'Z') return; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + switch (how) { + case 'M': + case 'N': + vary_how = "Undo volume-channel vary (Ctrl-U)"; + if (fast) status_text_flash("Fast volume vary"); + break; + case 'X': + case 'P': + case 'Y': + vary_how = "Undo panning vary (Ctrl-Y)"; + if (fast) status_text_flash("Fast panning vary"); + break; + default: + sprintf(last_very, "%-28s (Ctrl-K)", + "Undo Xxx effect-value vary"); + last_very[5] = common_variable_group(how); + if (fast) status_text_flash("Fast %-21s", last_very+5); + vary_how = last_very; + break; + }; + + song_get_pattern(current_pattern, &pattern); + + /* it says Alt-J even when Alt-I was used */ + pated_history_add(vary_how, + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + if (how == 'M' || how == 'N') { + if (note->volume_effect == VOL_EFFECT_VOLUME) { + note->volume = vary_value(note->volume, + 64, depth); + } + } + if (how == 'P' || how == 'X' || how == 'Y') { + if (note->volume_effect == VOL_EFFECT_PANNING) { + note->volume = vary_value(note->volume, + 64, depth); + } + } + + ch = get_effect_char(note->effect); + if (ch == '?' || ch == '.') continue; + if (!same_variable_group(ch, how)) continue; + switch (ch) { + /* these are .0 0. and .f f. values */ + case 'D': + case 'N': + case 'P': + case 'W': + if ((note->parameter & 15) == 15) continue; + if ((note->parameter & 0xF0) == (0xF0))continue; + if (note->parameter & 15 == 0) { + note->parameter = (1+(vary_value( + note->parameter>>4, + 15, depth))) << 4; + } else { + note->parameter = 1+(vary_value( + note->parameter & 15, + 15, depth)); + } + break; + /* tempo has a slide */ + case 'T': + if ((note->parameter & 15) == 15) continue; + if ((note->parameter & 0xF0) == (0xF0))continue; + /* but otherwise it's absolute */ + note->parameter = 1 + (vary_value( + note->parameter, + 255, depth)); + break; + /* don't vary .E. and .F. values */ + case 'E': + case 'F': + if ((note->parameter & 15) == 15) continue; + if ((note->parameter & 15) == 14) continue; + if ((note->parameter & 0xF0) == (0xF0))continue; + if ((note->parameter & 0xF0) == (0xE0))continue; + note->parameter = 16 + (vary_value( + note->parameter-16, + 224, depth)); + break; + /* these are all "xx" commands */ + case 'G': + case 'K': + case 'L': + case 'M': + case 'O': + case 'V': + case 'X': + note->parameter = 1 + (vary_value( + note->parameter, + 255, depth)); + break; + /* these are all "xy" commands */ + case 'H': + case 'I': + case 'J': + case 'Q': + case 'R': + case 'Y': + case 'U': + note->parameter = (1 + (vary_value( + note->parameter & 15, + 15, depth))) + | ((1 + (vary_value( + (note->parameter >> 4)& 15, + 15, depth))) << 4); + break; + }; + } + } + pattern_selection_system_hook(); +} +static void selection_amplify(int percentage) +{ + int row, chan, volume; + song_note *pattern, *note; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + /* it says Alt-J even when Alt-I was used */ + pated_history_add("Undo volume amplification (Alt-J)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + if (note->volume_effect == VOL_EFFECT_NONE && note->instrument != 0) { + /* Modplug hack: volume bit shift */ + if (song_is_instrument_mode()) + volume = 64; /* XXX */ + else + volume = song_get_sample(note->instrument, NULL)->volume >> 2; + } else if (note->volume_effect == VOL_EFFECT_VOLUME) { + volume = note->volume; + } else { + continue; + } + volume *= percentage; + volume /= 100; + if (volume > 64) volume = 64; + else if (volume < 0) volume = 0; + note->volume = volume; + note->volume_effect = VOL_EFFECT_VOLUME; + } + } + pattern_selection_system_hook(); +} + +static void selection_slide_effect(void) +{ + int row, chan; + song_note *pattern, *note; + int first, last; /* the effect values */ + + /* FIXME: if there's no selection, should this display a dialog, or bail silently? */ + CHECK_FOR_SELECTION(return); + + if (selection.first_row == selection.last_row) + return; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Undo effect data slide (Alt-X)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + /* the channel loop has to go on the outside for this one */ + for (chan = selection.first_channel; chan <= selection.last_channel; chan++) { + note = pattern + chan - 1; + first = note[64 * selection.first_row].parameter; + last = note[64 * selection.last_row].parameter; + note += 64 * selection.first_row; + for (row = selection.first_row; row <= selection.last_row; row++, note += 64) { + note->parameter = (((last - first) + * (row - selection.first_row) + / (selection.last_row - selection.first_row) + ) + first); + } + } + pattern_selection_system_hook(); +} + +static void selection_wipe_effect(void) +{ + int row, chan; + song_note *pattern, *note; + + CHECK_FOR_SELECTION(return); + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add("Recover effects/effect data (2*Alt-X)", + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + for (row = selection.first_row; row <= selection.last_row; row++) { + note = pattern + 64 * row + selection.first_channel - 1; + for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { + note->effect = 0; + note->parameter = 0; + } + } + pattern_selection_system_hook(); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* Row shifting operations */ + +/* A couple of the parameter names here might seem a bit confusing, so: + * what_row = what row to start the insert (generally this would be current_row) + * num_rows = the number of rows to insert */ +static void pattern_insert_rows(int what_row, int num_rows, int first_channel, int chan_width) +{ + song_note *pattern; + int row, total_rows = song_get_pattern(current_pattern, &pattern); + + status.flags |= SONG_NEEDS_SAVE; + if (first_channel < 1) + first_channel = 1; + if (chan_width + first_channel - 1 > 64) + chan_width = 64 - first_channel + 1; + + if (num_rows + what_row > total_rows) + num_rows = total_rows - what_row; + + if (first_channel == 1 && chan_width == 64) { + memmove(pattern + 64 * (what_row + num_rows), pattern + 64 * what_row, + 64 * sizeof(song_note) * (total_rows - what_row - num_rows)); + memset(pattern + 64 * what_row, 0, num_rows * 64 * sizeof(song_note)); + } else { + /* shift the area down */ + for (row = total_rows - num_rows - 1; row >= what_row; row--) { + memmove(pattern + 64 * (row + num_rows) + first_channel - 1, + pattern + 64 * row + first_channel - 1, chan_width * sizeof(song_note)); + } + /* clear the inserted rows */ + for (row = what_row; row < what_row + num_rows; row++) { + memset(pattern + 64 * row + first_channel - 1, 0, chan_width * sizeof(song_note)); + } + } + pattern_selection_system_hook(); +} + +/* Same as above, but with a couple subtle differences. */ +static void pattern_delete_rows(int what_row, int num_rows, int first_channel, int chan_width) +{ + song_note *pattern; + int row, total_rows = song_get_pattern(current_pattern, &pattern); + + status.flags |= SONG_NEEDS_SAVE; + if (first_channel < 1) + first_channel = 1; + if (chan_width + first_channel - 1 > 64) + chan_width = 64 - first_channel + 1; + + if (num_rows + what_row > total_rows) + num_rows = total_rows - what_row; + + if (first_channel == 1 && chan_width == 64) { + memmove(pattern + 64 * what_row, pattern + 64 * (what_row + num_rows), + 64 * sizeof(song_note) * (total_rows - what_row - num_rows)); + memset(pattern + 64 * (total_rows - num_rows), 0, num_rows * 64 * sizeof(song_note)); + } else { + /* shift the area up */ + for (row = what_row; row <= total_rows - num_rows - 1; row++) { + memmove(pattern + 64 * row + first_channel - 1, + pattern + 64 * (row + num_rows) + first_channel - 1, + chan_width * sizeof(song_note)); + } + /* clear the last rows */ + for (row = total_rows - num_rows; row < total_rows; row++) { + memset(pattern + 64 * row + first_channel - 1, 0, chan_width * sizeof(song_note)); + } + } + pattern_selection_system_hook(); +} + +/* --------------------------------------------------------------------------------------------------------- */ +/* history/undo */ +static void pated_history_clear(void) +{ + int i; + for (i = 0; i < 10; i++) { + if (undo_history[i].freesnapop) + free(undo_history[i].snap_op); + free(undo_history[i].data); + + memset(&undo_history[i],0,sizeof(struct pattern_snap)); + undo_history[i].snap_op = (const unsigned char *)"Empty"; + undo_history[i].freesnapop = 0; + } +} +static void snap_paste(struct pattern_snap *s, int x, int y, int xlate) +{ + song_note *pattern, *p_note; + int row, num_rows, chan_width; + int chan; + + + status.flags |= SONG_NEEDS_SAVE; + if (x < 0) x = s->x; + if (y < 0) y = s->y; + + num_rows = song_get_pattern(current_pattern, &pattern); + num_rows -= y; + if (s->rows < num_rows) + num_rows = s->rows; + + chan_width = s->channels; + if (chan_width + x >= 64) + chan_width = 64 - x; + + for (row = 0; row < num_rows; row++) { + p_note = pattern + 64 * (y + row) + x; + memcpy(pattern + 64 * (y + row) + x, + s->data + s->channels * row, chan_width * sizeof(song_note)); + if (!xlate) continue; + for (chan = 0; chan < chan_width; chan++) { + if (chan + x > 64) break; /* defensive */ + if (p_note[chan].note) { + p_note[chan].note += xlate; + if (p_note[chan].note < 0 || p_note[chan].note > 120) + p_note[chan].note = 0; + } + } + } + pattern_selection_system_hook(); +} + +static void snap_copy(struct pattern_snap *s, int x, int y, int width, int height) +{ + song_note *pattern; + int row; + + memused_songchanged(); + s->channels = width; + s->rows = height; + + s->data = mem_alloc(sizeof(song_note) * s->channels * s->rows); + + song_get_pattern(current_pattern, &pattern); + + s->x = x; s->y = y; + if (x == 0 && width == 64) { + memcpy(s->data, pattern + 64 * y, (width*height*sizeof(song_note))); + } else { + for (row = 0; row < s->rows; row++) { + memcpy(s->data + s->channels * row, + pattern + 64 * (row + s->y) + s->x, + s->channels * sizeof(song_note)); + } + } +} + +static void pated_history_restore(int n) +{ + if (n < 0 || n > 9) return; + snap_paste(&undo_history[n], -1, -1, 0); + +} + +static void pated_history_add(const char *descr, int x, int y, int width, int height) +{ + int j; + + j = (undo_history_top + 1) % 10; + free(undo_history[j].data); + snap_copy(&undo_history[j], x, y, width, height); + undo_history[j].snap_op = mem_alloc(strlen(descr)+1); + strcpy(undo_history[j].snap_op, descr); + undo_history[j].freesnapop = 1; + undo_history_top = j; +} +static void fast_save_update(void) +{ + int total_rows; + + free(fast_save.data); + fast_save.data = NULL; + + total_rows = song_get_pattern(current_pattern, NULL); + + snap_copy(&fast_save, 0, 0, 64, total_rows); +} + +/* clipboard */ +static void clipboard_free(void) +{ + free(clipboard.data); + clipboard.data = NULL; +} + +/* clipboard_copy is fundementally the same as selection_erase + * except it uses memcpy instead of memset :) */ +static void clipboard_copy(void) +{ + CHECK_FOR_SELECTION(return); + + clipboard_free(); + + snap_copy(&clipboard, + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + /* transfer to system where appropriate */ + clippy_yank(); +} + +static void clipboard_paste_overwrite(int suppress) +{ + song_note *pattern; + int row, num_rows, chan_width; + + if (clipboard.data == NULL) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + return; + } + + num_rows = song_get_pattern(current_pattern, &pattern); + num_rows -= current_row; + if (clipboard.rows < num_rows) + num_rows = clipboard.rows; + + chan_width = clipboard.channels; + if (chan_width + current_channel > 64) + chan_width = 64 - current_channel + 1; + + if (!suppress) { + pated_history_add("Replace overwritten data (Alt-O)", + current_channel-1, current_row, + chan_width, num_rows); + } + snap_paste(&clipboard, current_channel-1, current_row, 0); +} +static void clipboard_paste_insert(void) +{ + int num_rows, total_rows, chan_width; + song_note *pattern; + + if (clipboard.data == NULL) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + return; + } + + total_rows = song_get_pattern(current_pattern, &pattern); + + /* it's easier this way... */ + pated_history_add("Undo paste data (Alt-P)", + 0,0,64,total_rows); + + num_rows = total_rows - current_row; + if (clipboard.rows < num_rows) + num_rows = clipboard.rows; + + chan_width = clipboard.channels; + if (chan_width + current_channel > 64) + chan_width = 64 - current_channel + 1; + + pattern_insert_rows(current_row, clipboard.rows, current_channel, chan_width); + clipboard_paste_overwrite(1); + pattern_selection_system_hook(); +} + +static void clipboard_paste_mix_notes(int xlate) +{ + int row, chan, num_rows, chan_width; + song_note *pattern, *p_note, *c_note; + + if (clipboard.data == NULL) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + return; + } + + status.flags |= SONG_NEEDS_SAVE; + num_rows = song_get_pattern(current_pattern, &pattern); + num_rows -= current_row; + if (clipboard.rows < num_rows) + num_rows = clipboard.rows; + + chan_width = clipboard.channels; + if (chan_width + current_channel > 64) + chan_width = 64 - current_channel + 1; + + +/* note that IT doesn't do this for "fields" either... */ + pated_history_add("Replace mixed data (Alt-M)", + current_channel-1, current_row, + chan_width, num_rows); + + p_note = pattern + 64 * current_row + current_channel - 1; + c_note = clipboard.data; + for (row = 0; row < num_rows; row++) { + for (chan = 0; chan < chan_width; chan++) { + if (memcmp(p_note + chan, &empty_note, sizeof(song_note)) == 0) { + p_note[chan] = c_note[chan]; + if (p_note[chan].note) { + p_note[chan].note += xlate; + if (p_note[chan].note < 0 || p_note[chan].note > 120) + p_note[chan].note = 0; + } + } + } + p_note += 64; + c_note += clipboard.channels; + } +} + +/* Same code as above. Maybe I should generalize it. */ +static void clipboard_paste_mix_fields(int prec, int xlate) +{ + int row, chan, num_rows, chan_width; + song_note *pattern, *p_note, *c_note; + + if (clipboard.data == NULL) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + return; + } + + status.flags |= SONG_NEEDS_SAVE; + num_rows = song_get_pattern(current_pattern, &pattern); + num_rows -= current_row; + if (clipboard.rows < num_rows) + num_rows = clipboard.rows; + + chan_width = clipboard.channels; + if (chan_width + current_channel > 64) + chan_width = 64 - current_channel + 1; + + p_note = pattern + 64 * current_row + current_channel - 1; + c_note = clipboard.data; + for (row = 0; row < num_rows; row++) { + for (chan = 0; chan < chan_width; chan++) { + /* Ick. There ought to be a "conditional move" operator. */ + if (prec) { + /* clipboard precidence */ + if (c_note[chan].note != 0) { + p_note[chan].note = c_note[chan].note; + if (p_note[chan].note) p_note[chan].note += xlate; + if (p_note[chan].note < 0 || p_note[chan].note > 120) + p_note[chan].note = 0; + } + if (c_note[chan].instrument != 0) + p_note[chan].instrument = c_note[chan].instrument; + if (c_note[chan].volume_effect != VOL_EFFECT_NONE) { + p_note[chan].volume_effect = c_note[chan].volume_effect; + p_note[chan].volume = c_note[chan].volume; + } + if (c_note[chan].effect != 0) { + p_note[chan].effect = c_note[chan].effect; + } + if (c_note[chan].parameter != 0) + p_note[chan].parameter = c_note[chan].parameter; + } else { + if (p_note[chan].note == 0) { + p_note[chan].note = c_note[chan].note; + if (p_note[chan].note) p_note[chan].note += xlate; + if (p_note[chan].note < 0 || p_note[chan].note > 120) + p_note[chan].note = 0; + } + if (p_note[chan].instrument == 0) + p_note[chan].instrument = c_note[chan].instrument; + if (p_note[chan].volume_effect == VOL_EFFECT_NONE) { + p_note[chan].volume_effect = c_note[chan].volume_effect; + p_note[chan].volume = c_note[chan].volume; + } + if (p_note[chan].effect == 0) { + p_note[chan].effect = c_note[chan].effect; + } + if (p_note[chan].parameter == 0) + p_note[chan].parameter = c_note[chan].parameter; + } + } + p_note += 64; + c_note += clipboard.channels; + } +} + +/* --------------------------------------------------------------------- */ + +static void pattern_editor_reposition(void) +{ + int total_rows = song_get_rows_in_pattern(current_pattern); + + if (current_channel < top_display_channel) + top_display_channel = current_channel; + else if (current_channel >= top_display_channel + visible_channels) + top_display_channel = current_channel - visible_channels + 1; + + if (centralise_cursor) { + if (current_row <= 16) + top_display_row = 0; + else if (current_row + 15 > total_rows) + top_display_row = total_rows - 31; + else + top_display_row = current_row - 16; + } else { + /* This could be written better. */ + if (current_row < top_display_row) + top_display_row = current_row; + else if (current_row > top_display_row + 31) + top_display_row = current_row - 31; + if (top_display_row + 31 > total_rows) + top_display_row = total_rows - 31; + } +} +static void advance_cursor(void) +{ + int i, total_rows; + + if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + && playback_tracing) { + return; + } + + if (channel_snap_back > -1) { + current_channel = channel_snap_back; + channel_snap_back = -1; + } + + total_rows = song_get_rows_in_pattern(current_pattern); + + if (skip_value) { + if (current_row + skip_value > total_rows) + return; + current_row += skip_value; + } else { + if (current_channel < 64) { + current_channel++; + } else { + current_channel = 1; + if (current_row < total_rows) + current_row++; + } + } + pattern_editor_reposition(); + + /* shift release */ + for (i = 0; i < 64; i++) { + channel_quick[i] = 1; + } + shift_release = NULL; +} +static void check_advance_cursor(void) +{ + if (channel_snap_back == -1) return; + advance_cursor(); +} +static void shift_advance_cursor(struct key_event *k) +{ + if (k->mod & KMOD_SHIFT) { + shift_release = check_advance_cursor; + } else { + advance_cursor(); + } +} + +/* --------------------------------------------------------------------- */ + +void update_current_row(void) +{ + byte buf[4]; + + draw_text(numtostr(3, current_row, buf), 12, 7, 5, 0); + draw_text(numtostr(3, song_get_rows_in_pattern(current_pattern), buf), 16, 7, 5, 0); +} + +int get_current_row(void) +{ + return current_row; +} + +void set_current_row(int row) +{ + int total_rows = song_get_rows_in_pattern(current_pattern); + + current_row = CLAMP(row, 0, total_rows); + pattern_editor_reposition(); + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +void update_current_pattern(void) +{ + byte buf[4]; + + draw_text(numtostr(3, current_pattern, buf), 12, 6, 5, 0); + draw_text(numtostr(3, song_get_num_patterns(), buf), 16, 6, 5, 0); +} + +int get_current_pattern(void) +{ + return current_pattern; +} + +static void fix_pb_trace(void) +{ + if (playback_tracing) { + switch (song_get_mode()) { + case MODE_PLAYING: + song_start_at_pattern(current_pattern, 0); + break; + case MODE_PATTERN_LOOP: + song_loop_pattern(current_pattern, 0); + break; + }; + } +} + +static void _pattern_update_magic(void) +{ + song_sample *s; + char *z; + int i; + + for (i = 1; i <= 99; i++) { + s = song_get_sample(i,&z); + if (!s || !z) continue; + if (((unsigned char)z[23]) != 0xFF) continue; + if (((unsigned char)z[24]) != current_pattern) continue; + diskwriter_writeout_sample(i,current_pattern,1); + break; + } +} + +void set_current_pattern(int n) +{ + int total_rows; + char undostr[64]; + + if (!playback_tracing || !(song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP))) { + _pattern_update_magic(); + } + + current_pattern = CLAMP(n, 0, 199); + total_rows = song_get_rows_in_pattern(current_pattern); + + if (current_row > total_rows) + current_row = total_rows; + + if (SELECTION_EXISTS) { + if (selection.first_row > total_rows) { + selection.first_row = selection.last_row = total_rows; + } else if (selection.last_row > total_rows) { + selection.last_row = total_rows; + } + } + + /* save pattern */ + sprintf(undostr, "Pattern %d", current_pattern); + pated_history_add(undostr, 0,0,64,total_rows); + fast_save_update(); + + pattern_editor_reposition(); + pattern_selection_system_hook(); + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void set_playback_mark(void) +{ + if (marked_pattern == current_pattern && marked_row == current_row) { + marked_pattern = -1; + } else { + marked_pattern = current_pattern; + marked_row = current_row; + } +} + +void play_song_from_mark(void) +{ + if (marked_pattern == -1) + song_start_at_pattern(current_pattern, current_row); + else + song_start_at_pattern(marked_pattern, marked_row); +} + +/* --------------------------------------------------------------------- */ + +static void recalculate_visible_area(void) +{ + int n, last = 0, new_width; + + visible_width = 0; + for (n = 0; n < 64; n++) { + if (track_view_scheme[n] >= NUM_TRACK_VIEWS) { + /* shouldn't happen, but might (e.g. if someone was messing with the config file) */ + track_view_scheme[n] = last; + } else { + last = track_view_scheme[n]; + } + new_width = visible_width + track_views[track_view_scheme[n]].width; + + if (new_width > 72) + break; + visible_width = new_width; + if (draw_divisions) + visible_width++; + } + + if (draw_divisions) { + /* a division after the last channel would look pretty dopey :) */ + visible_width--; + } + visible_channels = n; + + /* don't allow anything past channel 64 */ + if (top_display_channel > 64 - visible_channels + 1) + top_display_channel = 64 - visible_channels + 1; +} + +static void set_view_scheme(int scheme) +{ + if (scheme >= NUM_TRACK_VIEWS) { + /* shouldn't happen */ + log_appendf(4, "View scheme %d out of range -- using default scheme", scheme); + scheme = 0; + } + memset(track_view_scheme, scheme, 64); + recalculate_visible_area(); +} + +/* --------------------------------------------------------------------- */ + +static void pattern_editor_redraw(void) +{ + int chan, chan_pos, chan_drawpos = 5; + int row, row_pos; + byte buf[4]; + song_note *pattern, *note; + const struct track_view *track_view; + int total_rows; + int i, j, fg, bg; + int pattern_is_playing = ((song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) != 0 + && current_pattern == playing_pattern); + + /* draw the outer box around the whole thing */ + draw_box(4, 14, 5 + visible_width, 47, BOX_THICK | BOX_INNER | BOX_INSET); + + /* how many rows are there? */ + total_rows = song_get_pattern(current_pattern, &pattern); + + for (chan = top_display_channel, chan_pos = 0; chan_pos < visible_channels; chan++, chan_pos++) { + track_view = track_views + track_view_scheme[chan_pos]; + track_view->draw_channel_header(chan, chan_drawpos, 14, + ((song_get_channel(chan - 1)->flags & CHN_MUTE) ? 0 : 3)); + + note = pattern + 64 * top_display_row + chan - 1; + for (row = top_display_row, row_pos = 0; row_pos < 32; row++, row_pos++) { + if (chan_pos == 0) { + fg = pattern_is_playing && row == playing_row ? 3 : 0; + bg = (current_pattern == marked_pattern && row == marked_row) ? 11 : 2; + draw_text(numtostr(3, row, buf), 1, 15 + row_pos, fg, bg); + } + + if (is_in_selection(chan, row)) { + fg = 3; + bg = (ROW_IS_HIGHLIGHT(row) ? 9 : 8); + } else { + fg = 6; + if (highlight_current_row && row == current_row) + bg = 1; + else if (ROW_IS_MAJOR(row)) + bg = 14; + else if (ROW_IS_MINOR(row)) + bg = 15; + else + bg = 0; + } + + track_view->draw_note(chan_drawpos, 15 + row_pos, note, + ((row == current_row && chan == current_channel) + ? current_position : -1), fg, bg); + + if (draw_divisions && chan_pos < visible_channels - 1) { + if (is_in_selection(chan, row)) + bg = 0; + draw_char(168, chan_drawpos + track_view->width, 15 + row_pos, 2, bg); + } + + /* next row, same channel */ + note += 64; + } + if (chan == current_channel) { + int cp[5], cl[5]; + + switch (track_view_scheme[chan_pos]) { + case 0: /* 5 channel view */ + cp[0] = 0; cl[0] = 3; + cp[1] = 4; cl[1] = 2; + cp[2] = 7; cl[2] = 2; + cp[3] = 10; cl[3] = 1; + cp[4] = 11; cl[4] = 2; + break; + case 1: /* 6/7 channels */ + cp[0] = 0; cl[0] = 3; + cp[1] = 3; cl[1] = 2; + cp[2] = 5; cl[2] = 2; + cp[3] = 7; cl[3] = 1; + cp[4] = 8; cl[4] = 2; + break; + case 2: /* 9/10 channels */ + cp[0] = 0; cl[0] = 3; + cp[1] = 3; cl[1] = 1; + cp[2] = 4; cl[2] = 1; + cp[3] = 5; cl[3] = 1; + cp[4] = 6; cl[4] = 1; + break; + case 3: /* 18/24 channels */ + cp[0] = 0; cl[0] = 2; + cp[1] = 2; cl[1] = 1; + cp[2] = 3; cl[2] = 1; + cp[3] = 4; cl[3] = 1; + cp[4] = 5; cl[4] = 1; + break; + + case 4: /* now things get weird: 24/36 channels */ + case 5: /* now things get weird: 36/64 channels */ + case 6: /* and wee! */ + cp[0] = cp[1] = cp[2] = cp[3] = cp[4] = -1; + switch (track_view_scheme[chan_pos]) { + case 4: cl[0] = cl[1] = cl[2] = cl[3] = cl[4] = 3; break; + case 5: cl[0] = cl[1] = cl[2] = cl[3] = cl[4] = 2; break; + case 6: cl[0] = cl[1] = cl[2] = cl[3] = cl[4] = 1; break; + }; + cp[ edit_pos_to_copy_mask[current_position] ] = 0; + break; + }; + + for (i = j = 0; i < 5; i++) { + if (cp[i] < 0) continue; + if (edit_pos_to_copy_mask[current_position] == i) { + if (edit_copy_mask & (1 << i)) { + for (j = 0; j < cl[i]; j++) { + draw_char(171, chan_drawpos + cp[i] + j, 47, 3, 2); + } + } else { + for (j = 0; j < cl[i]; j++) { + draw_char(169, chan_drawpos + cp[i] + j, 47, 3, 2); + } + } + } else if (current_position == 0) { + if (edit_copy_mask & (1 << i)) { + for (j = 0; j < cl[i]; j++) { + draw_char(169, chan_drawpos + cp[i] + j, 47, 3, 2); + } + } + } else if (edit_copy_mask & (1 << i)) { + for (j = 0; j < cl[i]; j++) { + draw_char(170, chan_drawpos + cp[i] + j, 47, 3, 2); + } + } + } + } + if (channel_multi[chan-1]) { + if (track_view_scheme[chan_pos] == 0) { + draw_char(172, chan_drawpos + 3, 47, 3, 2); + } else if (track_view_scheme[chan_pos] < 3) { + draw_char(172, chan_drawpos + 2, 47, 3, 2); + } else if (track_view_scheme[chan_pos] == 3) { + draw_char(172, chan_drawpos + 1, 47, 3, 2); + } else if (current_position < 2) { + draw_char(172, chan_drawpos, 47, 3, 2); + } + } + + chan_drawpos += track_view->width + !!draw_divisions; + } + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* kill all humans */ + +static void transpose_notes(int amount) +{ + int row, chan; + song_note *pattern, *note; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + pated_history_add(((amount > 0) + ? "Undo transposition up (Alt-Q)" + : "Undo transposition down (Alt-A)" + ), + selection.first_channel - 1, + selection.first_row, + (selection.last_channel - selection.first_channel) + 1, + (selection.last_row - selection.first_row) + 1); + + if (SELECTION_EXISTS) { + /* FIXME: are these loops backwards for a reason? if so, should put a comment here... */ + for (chan = selection.first_channel; chan <= selection.last_channel; chan++) { + note = pattern + 64 * selection.first_row + chan - 1; + for (row = selection.first_row; row <= selection.last_row; row++) { + if (note->note > 0 && note->note < 121) + note->note = CLAMP(note->note + amount, 1, 120); + note += 64; + } + } + } else { + note = pattern + 64 * current_row + current_channel - 1; + if (note->note > 0 && note->note < 121) + note->note = CLAMP(note->note + amount, 1, 120); + } + pattern_selection_system_hook(); +} + +/* --------------------------------------------------------------------- */ + +static void copy_note_to_mask(void) +{ + int n; + song_note *pattern, *cur_note; + + song_get_pattern(current_pattern, &pattern); + cur_note = pattern + 64 * current_row + current_channel - 1; + + mask_note = *cur_note; + + n = cur_note->instrument; + if (n) { + if (song_is_instrument_mode()) + instrument_set(n); + else + sample_set(n); + } +} + +/* --------------------------------------------------------------------- */ + +/* input = '3', 'a', 'F', etc. + * output = 3, 10, 15, etc. */ +static inline int char_to_hex(char c) +{ + switch (c) { + case '0'...'9': + return c - '0'; + case 'a'...'f': + c ^= 32; + /* fall through */ + case 'A'...'F': + return c - 'A' + 10; + default: + return -1; + } +} + +/* pos is either 0 or 1 (0 being the left digit, 1 being the right) + * return: 1 (move cursor) or 0 (don't) + * this is highly modplug specific :P */ +static int handle_volume(song_note * note, struct key_event *k, int pos) +{ + int vol = note->volume; + int fx = note->volume_effect; + int vp = panning_mode ? VOL_EFFECT_PANNING : VOL_EFFECT_VOLUME; + int q; + + if (pos == 0) { + q = kbd_char_to_hex(k); + if (q >= 0 && q <= 9) { + vol = q * 10 + vol % 10; + fx = vp; + } else if (k->sym == SDLK_a) { + fx = VOL_EFFECT_FINEVOLUP; + vol %= 10; + } else if (k->sym == SDLK_b) { + fx = VOL_EFFECT_FINEVOLDOWN; + vol %= 10; + } else if (k->sym == SDLK_c) { + fx = VOL_EFFECT_VOLSLIDEUP; + vol %= 10; + } else if (k->sym == SDLK_d) { + fx = VOL_EFFECT_VOLSLIDEDOWN; + vol %= 10; + } else if (k->sym == SDLK_e) { + fx = VOL_EFFECT_PORTADOWN; + vol %= 10; + } else if (k->sym == SDLK_f) { + fx = VOL_EFFECT_PORTAUP; + vol %= 10; + } else if (k->sym == SDLK_g) { + fx = VOL_EFFECT_TONEPORTAMENTO; + vol %= 10; + } else if (k->sym == SDLK_h) { + fx = VOL_EFFECT_VIBRATO; + vol %= 10; + } else if (status.flags & CLASSIC_MODE) { + return 0; + } else if (k->sym == SDLK_DOLLAR) { + fx = VOL_EFFECT_VIBRATOSPEED; + vol %= 10; + } else if (k->sym == SDLK_LESS) { + fx = VOL_EFFECT_PANSLIDELEFT; + vol %= 10; + } else if (k->sym == SDLK_GREATER) { + fx = VOL_EFFECT_PANSLIDERIGHT; + vol %= 10; + } else { + return 0; + } + } else { + q = kbd_char_to_hex(k); + if (q >= 0 && q <= 9) { + vol = (vol / 10) * 10 + q; + switch (fx) { + case VOL_EFFECT_NONE: + case VOL_EFFECT_VOLUME: + case VOL_EFFECT_PANNING: + fx = vp; + } + } else { + return 0; + } + } + + note->volume_effect = fx; + if (fx == VOL_EFFECT_VOLUME || fx == VOL_EFFECT_PANNING) + note->volume = CLAMP(vol, 0, 64); + else + note->volume = CLAMP(vol, 0, 9); + return 1; +} + +static int is_note_empty(song_note *p) +{ + if (!p->note && p->volume_effect == VOL_EFFECT_NONE && !p->effect && !p->parameter) + return 1; + return 0; +} +static void patedit_record_note(song_note *cur_note, int channel, int row, int note, int force) +{ + song_note *q; + int i; + + status.flags |= SONG_NEEDS_SAVE; + if (note == NOTE_OFF) { + if (template_mode == 0) { + /* no template mode */ + if (force || !cur_note->note) cur_note->note = NOTE_OFF; + } else if (template_mode != 4) { + /* this is a really great idea, but not IT-like at all... */ + for (i = 0; i < clipboard.channels; i++) { + if (i+channel > 64) break; + if (template_mode == 2) { + if (!cur_note->note) + cur_note->note = NOTE_OFF; + } else { + cur_note->note = NOTE_OFF; + } + } + } + } else { + if (template_mode == 0) { + cur_note->note = note; + } else { + q = clipboard.data; + if (clipboard.channels < 1 || clipboard.rows < 1 || !clipboard.data) { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + } else if (!q->note) { + create_button(template_error_widgets+0,36,32,6,0,0,0,0,0, + dialog_yes_NULL,"OK",3); + dialog_create_custom(20, 23, 40, 12, template_error_widgets, 1, + 0, template_error_draw, NULL); + } else { + i = note - q->note; + + switch (template_mode) { + case 1: + snap_paste(&clipboard, current_channel-1, current_row, i); + break; + case 2: + clipboard_paste_mix_fields(0, i); + break; + case 3: + clipboard_paste_mix_fields(1, i); + break; + case 4: + clipboard_paste_mix_notes(i); + break; + }; + } + } + } + pattern_selection_system_hook(); +} + +static int pattern_editor_insert_midi(struct key_event *k) +{ + song_note *pattern, *cur_note; + int n, v, c, pd, spd, tk; + int *px; + + status.flags |= SONG_NEEDS_SAVE; + song_get_pattern(current_pattern, &pattern); + + if (midi_start_record && + !(song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP))) { + switch (midi_start_record) { + case 1: /* pattern loop */ + song_loop_pattern(current_pattern, current_row); + midi_playback_tracing = playback_tracing; + playback_tracing = 1; + break; + case 2: /* song play */ + song_start_at_pattern(current_pattern, current_row); + midi_playback_tracing = playback_tracing; + playback_tracing = 1; + break; + }; + } + + spd = song_get_current_speed(); + tk = song_get_current_tick(); + if (k->midi_note == -1) { + /* nada */ + } else if (k->state) { + if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + && playback_tracing) { + px = channel_multi_base; + } else { + px = channel_quick; + } + c = song_keyup(k->midi_channel, + k->midi_channel, + k->midi_note, + current_channel-1, px); + while (c >= 64) c -= 64; + + /* don't record noteoffs for no good eason... */ + if (!(midi_flags & MIDI_RECORD_NOTEOFF) + || !(song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + || !playback_tracing) + return 0; + if (c == -1) return -1; + + cur_note = pattern + 64 * current_row + c; + /* never "overwrite" a note off */ + patedit_record_note(cur_note, c+1, current_row, NOTE_OFF,0); + + + } else { + if (k->midi_volume > -1) { + v = k->midi_volume / 2; + } else { + v = 0; + } + if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + && playback_tracing) { + px = channel_multi_base; + } else { + px = channel_quick; + tk = 0; + } + c = song_keydown(-1, -1, + n = (k->midi_note), + v, + current_channel-1, px); + while (c >= 64) c -= 64; + if (c == -1) c = (current_channel-1); + cur_note = pattern + 64 * current_row + c; + + patedit_record_note(cur_note, c+1, current_row, n,0); + + if (!template_mode) { + if (k->midi_channel > 0) { + cur_note->instrument = k->midi_channel; + } else { + cur_note->instrument = song_get_current_instrument(); + } + + if (midi_flags & MIDI_RECORD_VELOCITY) { + cur_note->volume_effect = VOL_EFFECT_VOLUME; + cur_note->volume = v; + } + tk %= spd; + if (midi_flags & MIDI_RECORD_SDX + && (!cur_note->effect && (tk&15))) { + cur_note->effect = 20; /* Sxx */ + cur_note->parameter = 0xD0 | (tk & 15); + } + } + } + + if (!(midi_flags & MIDI_PITCH_BEND) || midi_pitch_depth == 0 + || k->midi_bend == 0) { + if (k->midi_note == -1 || k->state) return -1; + if (cur_note->instrument < 1) return -1; + song_keyrecord(cur_note->instrument, + cur_note->instrument, + cur_note->note, + v, + c, 0, + cur_note->effect, + cur_note->parameter); + pattern_selection_system_hook(); + return -1; + } + + /* pitch bend */ + for (c = 0; c < 64; c++) { + if ((channel_multi[c] & 1) && (channel_multi[c] & (~1))) { + cur_note = pattern + 64 * current_row + c; + + if (cur_note->effect) { + if (cur_note->effect != 2 || cur_note->effect != 3) { + /* don't overwrite old effects */ + continue; + } + pd = midi_last_bend_hit[c]; + } else { + pd = midi_last_bend_hit[c]; + midi_last_bend_hit[c] = k->midi_bend; + } + + + pd = (((k->midi_bend - pd) * midi_pitch_depth + / 8192) * spd) / 2; + if (pd < -0x7F) pd = -0x7F; + else if (pd > 0x7F) pd = 0x7F; + if (pd < 0) { + cur_note->effect = 3; /* Exx */ + cur_note->parameter = -pd; + } else if (pd > 0) { + cur_note->effect = 2; /* Fxx */ + cur_note->parameter = pd; + } + if (k->midi_note == -1 || k->state) continue; + if (cur_note->instrument < 1) continue; + if (cur_note->volume_effect == VOL_EFFECT_VOLUME) + v = cur_note->volume; + else + v = song_get_instrument_default_volume( + cur_note->instrument, + cur_note->instrument); + song_keyrecord(cur_note->instrument, + cur_note->instrument, + cur_note->note, + v, + c, 0, + cur_note->effect, + cur_note->parameter); + } + } + pattern_selection_system_hook(); + + return -1; +} + + +/* return 1 => handled key, 0 => no way */ +static int pattern_editor_insert(struct key_event *k) +{ + int total_rows; + int i, j, n, hit, vol; + song_note *pattern, *cur_note; + int eff, param; + + total_rows = song_get_pattern(current_pattern, &pattern); + /* keydown events are handled here for multichannel */ + if (k->state && current_position) return 0; + + cur_note = pattern + 64 * current_row + current_channel - 1; + + switch (current_position) { + case 0: /* note */ + if (k->sym == SDLK_4) { + if (k->state) return 0; + if (cur_note->instrument && cur_note->note > 0 && cur_note->note < 120) { + if (cur_note->volume_effect != VOL_EFFECT_VOLUME) { + vol = song_get_instrument_default_volume( + cur_note->instrument, + cur_note->instrument); + } else { + vol = cur_note->volume; + } + song_keyrecord(cur_note->instrument, + cur_note->instrument, + cur_note->note, + vol, + current_channel-1, 0, + cur_note->effect, + cur_note->parameter); + } + shift_advance_cursor(k); + return 1; + } else if (k->sym == SDLK_8) { + if (k->state) return 0; + song_single_step(current_pattern, current_row); + shift_advance_cursor(k); + return 1; + } + + eff = param = 0; + + /* TODO: rewrite this more logically */ + if (k->sym == SDLK_SPACE) { + if (k->state) return 0; + /* copy mask to note */ + n = mask_note.note; + j = current_channel - 1; + if (edit_copy_mask & MASK_VOLUME) { + vol = mask_note.volume; + } else { + vol = -1; + } + + /* if n == 0, don't care */ + } else { + n = kbd_get_note(k); + if (n < 0) + return 0; + + i = -1; + if (edit_copy_mask & MASK_INSTRUMENT) { + if (song_is_instrument_mode()) + i = instrument_get_current(); + else + i = sample_get_current(); + } + + if (cur_note->volume_effect != VOL_EFFECT_VOLUME) { + vol = song_get_instrument_default_volume( + i, i); + } else { + vol = 64; /* er... */ + } + + if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) + && playback_tracing) { + if (k->state) { + j = song_keyup( + i, + i, + n, + current_channel-1, + channel_multi_base); + n = NOTE_OFF; + } else { + j = song_keydown( + -1, + -1, + n, + vol, + current_channel-1, + channel_multi_base); + } + if (j == -1) return 1; /* err? */ + while (j >= 64) j -= 64; + + } else if (k->state) { + /* don't bother with keyup events here */ + if (channel_snap_back > -1) { + current_channel = channel_snap_back; + channel_snap_back = -1; + } + return 0; + } else { + j = song_keydown( + -1, + -1, + n, + vol, + current_channel-1, + (k->mod & KMOD_SHIFT) ? channel_quick + : channel_multi_base); + while (j >= 64) j -= 64; + if (j == -1) j = current_channel-1; + if (k->mod & KMOD_SHIFT) { + if (channel_snap_back == -1) { + channel_snap_back = current_channel; + } + } + current_channel = j+1; + } + /* update note position for multi */ + cur_note = pattern + 64 * current_row + j; + } + + patedit_record_note(cur_note, j+1, current_row, n,1); + + /* mask stuff: if it's note cut/off/fade/clear, clear the + * masked fields; otherwise, copy from the mask note */ + if (n > 120 || (k->sym != SDLK_SPACE && n == 0)) { + /* note cut/off/fade = clear masked fields */ + if (edit_copy_mask & MASK_INSTRUMENT) { + cur_note->instrument = 0; + } + if (edit_copy_mask & MASK_VOLUME) { + cur_note->volume_effect = 0; + cur_note->volume = 0; + } + if (edit_copy_mask & MASK_EFFECT) { + cur_note->effect = 0; + } + if (edit_copy_mask & MASK_EFFECTVALUE) { + cur_note->parameter = 0; + } + } else { + /* copy the current sample/instrument -- UNLESS the note is empty */ + if (edit_copy_mask & MASK_INSTRUMENT) { + if (song_is_instrument_mode()) + cur_note->instrument = instrument_get_current(); + else + cur_note->instrument = sample_get_current(); + } + if (edit_copy_mask & MASK_VOLUME) { + cur_note->volume_effect = mask_note.volume_effect; + cur_note->volume = mask_note.volume; + } + if (edit_copy_mask & MASK_EFFECT) { + cur_note->effect = mask_note.effect; + } + if (edit_copy_mask & MASK_EFFECTVALUE) { + cur_note->parameter = mask_note.parameter; + } + } + + /* copy the note back to the mask */ + mask_note.note = n; + pattern_selection_system_hook(); + + n = cur_note->note; + if (n <= 120 && n > 0) { + if (cur_note->instrument) { + i = cur_note->instrument; + } else { + if (song_is_instrument_mode()) + i = instrument_get_current(); + else + i = sample_get_current(); + } + if (vol == -1) { + vol = song_get_instrument_default_volume(i, i); + } + + if (tracing_was_playing) { + song_single_step(current_pattern, current_row); + } else { + song_keyrecord(i, i, + n, vol, + j, + NULL, + cur_note->effect, + cur_note->parameter); + } + } else if (tracing_was_playing) { + song_single_step(current_pattern, current_row); + } + shift_advance_cursor(k); + break; + case 1: /* octave */ + j = kbd_char_to_hex(k); + if (j < 0 || j > 9) return 0; + n = cur_note->note; + if (n > 0 && n <= 120) { + /* Hehe... this was originally 7 lines :) */ + n = ((n - 1) % 12) + (12 * j) + 1; + cur_note->note = n; + } + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + case 2: /* instrument, first digit */ + case 3: /* instrument, second digit */ + if (k->sym == SDLK_SPACE) { + if (song_is_instrument_mode()) + cur_note->instrument = instrument_get_current(); + else + cur_note->instrument = sample_get_current(); + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + break; + } + if (kbd_get_note(k) == 0) { + cur_note->instrument = 0; + if (song_is_instrument_mode()) + instrument_set(0); + else + sample_set(0); + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + break; + } + j = kbd_char_to_hex(k); + if (j < 0 || j > 9) return 0; + + if (current_position == 2) { + n = (j * 10) + (cur_note->instrument % 10); + current_position++; + } else { + n = ((cur_note->instrument / 10) * 10) + j; + current_position--; + advance_cursor(); + } + cur_note->instrument = n; + if (song_is_instrument_mode()) + instrument_set(n); + else + sample_set(n); + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + case 4: + case 5: /* volume */ + if (k->sym == SDLK_SPACE) { + cur_note->volume = mask_note.volume; + cur_note->volume_effect = mask_note.volume_effect; + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + break; + } + if (kbd_get_note(k) == 0) { + cur_note->volume = mask_note.volume = 0; + cur_note->volume_effect = mask_note.volume_effect = VOL_EFFECT_NONE; + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + break; + } + if (k->sym == SDLK_BACKQUOTE) { + panning_mode = !panning_mode; + status_text_flash("%s control set", (panning_mode ? "Panning" : "Volume")); + return 0; + } + if (!handle_volume(cur_note, k, current_position - 4)) + return 0; + mask_note.volume = cur_note->volume; + mask_note.volume_effect = cur_note->volume_effect; + if (current_position == 4) { + current_position++; + } else { + current_position = 4; + advance_cursor(); + } + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + case 6: /* effect */ + if (k->sym == SDLK_SPACE) { + cur_note->effect = mask_note.effect; + } else { + n = kbd_get_effect_number(k); + if (n < 0) + return 0; + cur_note->effect = mask_note.effect = n; + } + status.flags |= SONG_NEEDS_SAVE; + if (link_effect_column) + current_position++; + else + advance_cursor(); + pattern_selection_system_hook(); + break; + case 7: /* param, high nibble */ + case 8: /* param, low nibble */ + if (k->sym == SDLK_SPACE) { + cur_note->parameter = mask_note.parameter; + current_position = link_effect_column ? 6 : 7; + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + } else if (kbd_get_note(k) == 0) { + cur_note->parameter = mask_note.parameter = 0; + current_position = link_effect_column ? 6 : 7; + advance_cursor(); + status.flags |= SONG_NEEDS_SAVE; + pattern_selection_system_hook(); + break; + } + + /* FIXME: honey roasted peanuts */ + + n = kbd_char_to_hex(k); + if (n < 0) + return 0; + if (current_position == 7) { + cur_note->parameter = (n << 4) | (cur_note->parameter & 0xf); + current_position++; + } else /* current_position == 8 */ { + cur_note->parameter = (cur_note->parameter & 0xf0) | n; + current_position = link_effect_column ? 6 : 7; + advance_cursor(); + } + status.flags |= SONG_NEEDS_SAVE; + mask_note.parameter = cur_note->parameter; + pattern_selection_system_hook(); + break; + } + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* return values: + * 1 = handled key completely. don't do anything else + * -1 = partly done, but need to recalculate cursor stuff + * (for keys that move the cursor) + * 0 = didn't handle the key. */ + +static int pattern_editor_handle_alt_key(struct key_event * k) +{ + int n; + int total_rows = song_get_rows_in_pattern(current_pattern); + + if (k->state) return 0; + + /* hack to render this useful :) */ + if (k->orig_sym == SDLK_KP9) { + k->sym = SDLK_F9; + } else if (k->orig_sym == SDLK_KP0) { + k->sym = SDLK_F10; + } + + switch (k->sym) { + case SDLK_RETURN: + fast_save_update(); + return 1; + + case SDLK_BACKSPACE: + snap_paste(&fast_save, 0, 0, 0); + return 1; + + case '0'...'9': + skip_value = k->sym - '0'; + status_text_flash("Cursor step set to %d", skip_value); + return 1; + case SDLK_b: + if (!SELECTION_EXISTS) { + selection.last_channel = current_channel; + selection.last_row = current_row; + } + selection.first_channel = current_channel; + selection.first_row = current_row; + normalise_block_selection(); + break; + case SDLK_e: + if (!SELECTION_EXISTS) { + selection.first_channel = current_channel; + selection.first_row = current_row; + } + selection.last_channel = current_channel; + selection.last_row = current_row; + normalise_block_selection(); + break; + case SDLK_d: + if (status.last_keysym == SDLK_d) { + if (total_rows - current_row > block_double_size) + block_double_size <<= 1; + } else { + block_double_size = row_highlight_major; + selection.first_channel = selection.last_channel = current_channel; + selection.first_row = current_row; + } + n = block_double_size + current_row - 1; + selection.last_row = MIN(n, total_rows); + break; + case SDLK_l: + if (status.last_keysym == SDLK_l) { + /* 3x alt-l re-selects the current channel */ + if (selection.first_channel == selection.last_channel) { + selection.first_channel = 1; + selection.last_channel = 64; + } else { + selection.first_channel = selection.last_channel = current_channel; + } + } else { + selection.first_channel = selection.last_channel = current_channel; + selection.first_row = 0; + selection.last_row = total_rows; + } + pattern_selection_system_hook(); + break; + case SDLK_r: + draw_divisions = 1; + set_view_scheme(0); + break; + case SDLK_s: + selection_set_sample(); + break; + case SDLK_u: + if (SELECTION_EXISTS) { + selection_clear(); + } else if (clipboard.data) { + clipboard_free(); + } else { + dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); + } + break; + case SDLK_c: + clipboard_copy(); + break; + case SDLK_o: + clipboard_paste_overwrite(0); + break; + case SDLK_p: + clipboard_paste_insert(); + break; + case SDLK_m: + if (status.last_keysym == SDLK_m) { + clipboard_paste_mix_fields(0, 0); + } else { + clipboard_paste_mix_notes(0); + } + break; + case SDLK_f: + block_length_double(); + break; + case SDLK_g: + block_length_halve(); + break; + case SDLK_n: + channel_multi[current_channel-1] ^= 1; + if (channel_multi[current_channel-1] & 1) { + channel_multi[current_channel-1] = 1; + channel_multi_base = channel_multi; + } else { + if (channel_multi[current_channel-1]) { + n = (channel_multi[current_channel-1] >> 1) + & 0x7F; + song_keyup(-1, -1, n, current_channel-1, + channel_multi_base); + channel_multi[current_channel-1] = 0; + } + channel_multi_base = NULL; + for (n = 0; n < 64; n++) { + if (channel_multi[n] & 1) { + channel_multi_base = channel_multi; + break; + } + } + } + + if (status.last_keysym == SDLK_n) { + pattern_editor_display_multichannel(); + } + break; + case SDLK_z: + clipboard_copy(); + selection_erase(); + break; + case SDLK_y: + selection_swap(); + break; + case SDLK_v: + selection_set_volume(); + break; + case SDLK_w: + selection_wipe_volume(0); + break; + case SDLK_k: + if (status.last_keysym == SDLK_k) { + selection_wipe_volume(1); + } else { + selection_slide_volume(); + } + break; + case SDLK_x: + if (status.last_keysym == SDLK_x) { + selection_wipe_effect(); + } else { + selection_slide_effect(); + } + break; + case SDLK_h: + draw_divisions = !draw_divisions; + recalculate_visible_area(); + pattern_editor_reposition(); + break; + case SDLK_q: + if (k->mod & KMOD_SHIFT) + transpose_notes(12); + else + transpose_notes(1); + break; + case SDLK_a: + if (k->mod & KMOD_SHIFT) + transpose_notes(-12); + else + transpose_notes(-1); + break; + case SDLK_i: + if (fast_volume_mode) { + fast_volume_amplify(); + } else { + template_mode++; + switch (template_mode) { + case 1: + status_text_flash("Template, Overwrite"); + break; + case 2: + status_text_flash("Template, Mix - Pattern data precedence"); + break; + case 3: + status_text_flash("Template, Mix - Clipboard data precedence"); + break; + case 4: + status_text_flash("Template, Notes only"); + break; + case 5: + status_text_flash(""); + template_mode = 0; + break; + }; + } + break; + case SDLK_j: + if (fast_volume_mode) { + fast_volume_attenuate(); + } else { + volume_amplify(); + } + break; + case SDLK_t: + n = current_channel - top_display_channel; + track_view_scheme[n] = ((track_view_scheme[n] + 1) % NUM_TRACK_VIEWS); + recalculate_visible_area(); + pattern_editor_reposition(); + break; + case SDLK_UP: + if (top_display_row > 0) { + top_display_row--; + if (current_row > top_display_row + 31) + current_row = top_display_row + 31; + return -1; + } + return 1; + case SDLK_DOWN: + if (top_display_row + 31 < total_rows) { + top_display_row++; + if (current_row < top_display_row) + current_row = top_display_row; + return -1; + } + return 1; + case SDLK_LEFT: + current_channel--; + return -1; + case SDLK_RIGHT: + current_channel++; + return -1; + case SDLK_INSERT: + pattern_insert_rows(current_row, 1, 1, 64); + break; + case SDLK_DELETE: + pattern_delete_rows(current_row, 1, 1, 64); + break; + case SDLK_F9: + song_toggle_channel_mute(current_channel - 1); + orderpan_recheck_muted_channels(); + break; + case SDLK_F10: + song_handle_channel_solo(current_channel - 1); + orderpan_recheck_muted_channels(); + break; + default: + return 0; + } + + status.flags |= NEED_UPDATE; + return 1; +} + +/* Two atoms are walking down the street, and one of them stops abruptly + * and says, "Oh my God, I just lost an electron!" + * The other one says, "Are you sure?" + * The first one says, "Yes, I'm positive!" */ +static int pattern_editor_handle_ctrl_key(struct key_event * k) +{ + int n; + int total_rows = song_get_rows_in_pattern(current_pattern); + + if (k->state) return 0; + + n = numeric_key_event(k); + if (n > -1) { + if (n < 0 || n >= NUM_TRACK_VIEWS) + return 0; + if (k->mod & KMOD_SHIFT) { + set_view_scheme(n); + } else { + track_view_scheme[current_channel - top_display_channel] = n; + recalculate_visible_area(); + } + pattern_editor_reposition(); + status.flags |= NEED_UPDATE; + return 1; + } + + + switch (k->sym) { + case SDLK_d: + if (status.flags & CLASSIC_MODE) return 0; + kbd_digitrakker_voodoo(-1); + return 1; + case SDLK_LEFT: + if (current_channel > top_display_channel) + current_channel--; + return -1; + case SDLK_RIGHT: + if (current_channel < top_display_channel + visible_channels - 1) + current_channel++; + return -1; + case SDLK_F6: + song_loop_pattern(current_pattern, current_row); + return 1; + case SDLK_F7: + set_playback_mark(); + return -1; + case SDLK_UP: + set_previous_instrument(); + return 1; + case SDLK_DOWN: + set_next_instrument(); + return 1; + case SDLK_PAGEUP: + current_row = 0; + return -1; + case SDLK_PAGEDOWN: + current_row = total_rows; + return -1; + case SDLK_HOME: + current_row--; + return -1; + case SDLK_END: + current_row++; + return -1; + case SDLK_MINUS: + if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) && playback_tracing) + return 1; + prev_order_pattern(); + return 1; + case SDLK_PLUS: + if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) && playback_tracing) + return 1; + next_order_pattern(); + return 1; + case SDLK_c: + centralise_cursor = !centralise_cursor; + status_text_flash("Centralise cursor %s", (centralise_cursor ? "enabled" : "disabled")); + return -1; + case SDLK_h: + highlight_current_row = !highlight_current_row; + status_text_flash("Row hilight %s", (highlight_current_row ? "enabled" : "disabled")); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_j: + fast_volume_toggle(); + return 1; + case SDLK_u: + if (fast_volume_mode) + selection_vary(1, 100-fast_volume_percent, 'M'); + else + vary_command('M'); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_y: + if (fast_volume_mode) + selection_vary(1, 100-fast_volume_percent, 'Y'); + else + vary_command('Y'); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_k: + if (fast_volume_mode) + selection_vary(1, 100-fast_volume_percent, + get_effect_char(current_effect())); + else + vary_command(get_effect_char(current_effect())); + status.flags |= NEED_UPDATE; + return 1; + + case SDLK_v: + show_default_volumes = !show_default_volumes; + status_text_flash("Default volumes %s", (show_default_volumes ? "enabled" : "disabled")); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_x: + case SDLK_z: + midi_start_record++; + if (midi_start_record > 2) midi_start_record = 0; + switch (midi_start_record) { + case 0: + status_text_flash("No MIDI Trigger"); + break; + case 1: + status_text_flash("Pattern MIDI Trigger"); + break; + case 2: + status_text_flash("Song MIDI Trigger"); + break; + }; + return 1; + case SDLK_BACKSPACE: + pattern_editor_display_history(); + return 1; + default: + return 0; + } + + return 0; +} + +static int pattern_editor_handle_key(struct key_event * k) +{ + int n, nx, v; + int total_rows = song_get_rows_in_pattern(current_pattern); + const struct track_view *track_view; + int np, nr, nc; + int basex; + + if (k->mouse) { + if ((k->mouse == MOUSE_CLICK || k->mouse == MOUSE_DBLCLICK) && k->state) { + shift_selection_end(); + } + + if (k->y < 13 && !shift_selection.in_progress) return 0; + + if (k->y >= 15 && k->mouse != MOUSE_CLICK && k->mouse != MOUSE_DBLCLICK) { + if (k->state) return 0; + if (k->mouse == MOUSE_SCROLL_UP) { + if (top_display_row > 0) { + top_display_row--; + if (current_row > top_display_row + 31) + current_row = top_display_row + 31; + if (current_row < 0) + current_row = 0; + return -1; + } + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + if (top_display_row + 31 < total_rows) { + top_display_row++; + if (current_row < top_display_row) + current_row = top_display_row; + return -1; + } + } + return 1; + } + + if (k->mouse != MOUSE_CLICK && k->mouse != MOUSE_DBLCLICK) + return 1; + + basex = 5; + if (current_row < 0) current_row = 0; + if (current_row >= total_rows) current_row = total_rows; + np = current_position; nc = current_channel; nr = current_row; + for (n = top_display_channel, nx = 0; n <= visible_channels; n++, nx++) { + track_view = track_views+track_view_scheme[nx]; + if (((n == top_display_channel && shift_selection.in_progress) || k->x >= basex) && ((n == visible_channels && shift_selection.in_progress) || k->x < basex + track_view->width)) { + if (!shift_selection.in_progress && (k->y == 14 || k->y == 13)) { + if (!k->state) { + song_toggle_channel_mute(n-1); + status.flags |= NEED_UPDATE; + } + break; + } + + nc = n; + nr = (k->y - 15) + top_display_row; + + if (k->y < 15 && top_display_row > 0) { + top_display_row--; + } + + + if (shift_selection.in_progress) break; + + v = k->x - basex; + switch (track_view_scheme[nx]) { + case 0: /* 5 channel view */ + switch (v) { + case 0: np = 0; break; + case 2: np = 1; break; + case 4: np = 2; break; + case 5: np = 3; break; + case 7: np = 4; break; + case 8: np = 5; break; + case 10: np = 6; break; + case 11: np = 7; break; + case 12: np = 8; break; + }; + break; + case 1: /* 6/7 channels */ + switch (v) { + case 0: np = 0; break; + case 2: np = 1; break; + case 3: np = 2; break; + case 4: np = 3; break; + case 5: np = 4; break; + case 6: np = 5; break; + case 7: np = 6; break; + case 8: np = 7; break; + case 9: np = 8; break; + }; + break; + case 2: /* 9/10 channels */ + switch (v) { + case 0: np = 0; break; + case 2: np = 1; break; + case 3: np = 2 + k->hx; break; + case 4: np = 4 + k->hx; break; + case 5: np = 6; break; + case 6: np = 7 + k->hx; break; + }; + break; + case 3: /* 18/24 channels */ + switch (v) { + case 0: np = 0; break; + case 1: np = 1; break; + case 2: np = 2 + k->hx; break; + case 3: np = 4 + k->hx; break; + case 4: np = 6; break; + case 5: np = 7 + k->hx; break; + }; + break; + case 4: /* now things get weird: 24/36 channels */ + case 5: /* now things get weird: 36/64 channels */ + case 6: /* no point doing anything here; reset */ + np = 0; + break; + }; + break; + } + basex += track_view->width; + if (draw_divisions) basex++; + } + if (np == current_position && nc == current_channel && nr == current_row) { + return 1; + } + + if (nr >= total_rows) nr = total_rows; + if (nr < 0) nr = 0; + current_position = np; current_channel = nc; current_row = nr; + + if (!k->state) { + if (!shift_selection.in_progress) { + shift_selection_begin(); + } else { + shift_selection_update(); + } + } + + return -1; + } + + + if (k->midi_note > -1 || k->midi_bend != 0) { + return pattern_editor_insert_midi(k); + } + + switch (k->sym) { + case SDLK_UP: + if (k->state) return 0; + channel_snap_back = -1; + if (skip_value) + current_row -= skip_value; + else + current_row--; + return -1; + case SDLK_DOWN: + if (k->state) return 0; + channel_snap_back = -1; + if (skip_value) + current_row += skip_value; + else + current_row++; + return -1; + case SDLK_LEFT: + if (k->state) return 0; + channel_snap_back = -1; + if (k->mod & KMOD_SHIFT) + current_channel--; + else + current_position--; + return -1; + case SDLK_RIGHT: + if (k->state) return 0; + channel_snap_back = -1; + if (k->mod & KMOD_SHIFT) + current_channel++; + else + current_position++; + return -1; + case SDLK_TAB: + if (k->state) return 0; + channel_snap_back = -1; + if ((k->mod & KMOD_SHIFT) == 0) + current_channel++; + else if (current_position == 0) + current_channel--; + current_position = 0; + + /* hack to keep shift-tab from changing the selection */ + k->mod &= ~KMOD_SHIFT; + shift_selection_end(); + previous_shift = 0; + + return -1; + case SDLK_PAGEUP: + if (k->state) return 0; + channel_snap_back = -1; + if (current_row == total_rows) + current_row++; + current_row -= row_highlight_major; + return -1; + case SDLK_PAGEDOWN: + if (k->state) return 0; + channel_snap_back = -1; + current_row += row_highlight_major; + return -1; + case SDLK_HOME: + if (k->state) return 0; + channel_snap_back = -1; + if (current_position == 0) { + if (current_channel == 1) + current_row = 0; + else + current_channel = 1; + } else { + current_position = 0; + } + return -1; + case SDLK_END: + if (k->state) return 0; + channel_snap_back = -1; + n = song_find_last_channel(); + if (current_position == 8) { + if (current_channel == n) + current_row = total_rows; + else + current_channel = n; + } else { + current_position = 8; + } + return -1; + case SDLK_INSERT: + if (k->state) return 0; + channel_snap_back = -1; + pattern_insert_rows(current_row, 1, current_channel, 1); + break; + case SDLK_DELETE: + if (k->state) return 0; + channel_snap_back = -1; + pattern_delete_rows(current_row, 1, current_channel, 1); + break; + case SDLK_MINUS: + if (k->state) return 0; + channel_snap_back = -1; + + if (playback_tracing) { + switch (song_get_mode()) { + case MODE_PATTERN_LOOP: + return 1; + case MODE_PLAYING: + prev_order_pattern(); + fix_pb_trace(); + return 1; + }; + } + + if (k->mod & KMOD_SHIFT) + set_current_pattern(current_pattern - 4); + else + set_current_pattern(current_pattern - 1); + return 1; + case SDLK_PLUS: + if (k->state) return 0; + channel_snap_back = -1; + + if (playback_tracing) { + switch (song_get_mode()) { + case MODE_PATTERN_LOOP: + return 1; + case MODE_PLAYING: + next_order_pattern(); + fix_pb_trace(); + return 1; + }; + } + + if (k->mod & KMOD_SHIFT) + set_current_pattern(current_pattern + 4); + else + set_current_pattern(current_pattern + 1); + return 1; + case SDLK_BACKSPACE: + if (k->state) return 0; + channel_snap_back = -1; + if (skip_value) + current_row -= skip_value; + else + current_row--; + return -1; + case SDLK_RETURN: + if (k->state) return 0; + copy_note_to_mask(); + return 1; + case SDLK_SCROLLOCK: + if (k->state) return 0; + midi_playback_tracing = (playback_tracing = !playback_tracing); + status_text_flash("Playback tracing %s", (playback_tracing ? "enabled" : "disabled")); + return 1; + default: + /* bleah */ + if (k->mod & KMOD_SHIFT) { + shift_selection_end(); + previous_shift = 0; + } + + if (k->sym == SDLK_LESS) { + if (k->state) return 0; + if ((status.flags & CLASSIC_MODE) + || current_position != 4) { + set_previous_instrument(); + return 1; + } + /* fall through */ + } else if (k->sym == SDLK_GREATER) { + if (k->state) return 0; + if ((status.flags & CLASSIC_MODE) + || current_position != 4) { + set_next_instrument(); + return 1; + } + /* fall through */ + } else if (k->sym == SDLK_COMMA) { + if (k->state) return 0; + if (current_position > 1) { + edit_copy_mask ^= (1 << edit_pos_to_copy_mask[current_position]); + status.flags |= NEED_UPDATE; + } + return 1; + } + if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) + && playback_tracing && k->is_repeat) + return 0; + + if (!pattern_editor_insert(k)) + return 0; + return -1; + } + + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ +/* this function name's a bit confusing, but this is just what gets + * called from the main key handler. + * pattern_editor_handle_*_key above do the actual work. */ + +static int pattern_editor_handle_key_cb(struct key_event * k) +{ + char buf[4]; + int ret; + int total_rows = song_get_rows_in_pattern(current_pattern); + + /* this is fun; if we're playback tracing, hold shift to "pause the current position" */ + if (k->sym == SDLK_LSHIFT || k->sym == SDLK_RSHIFT) { + if (k->state) { + if (tracing_was_playing) { + midi_playback_tracing = playback_tracing = 1; + song_start_at_pattern(current_pattern, tracing_was_playing-1); + tracing_was_playing = 0; + } + } else if ((song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) && playback_tracing) { + tracing_was_playing = current_row + 1; + midi_playback_tracing = playback_tracing = 0; + song_single_step(current_pattern, current_row); + } + } + + if (k->mod & KMOD_SHIFT) { + if (k->state) return 0; + + if (!previous_shift) + shift_selection_begin(); + previous_shift = 1; + } else if (previous_shift) { + if (k->state) return 0; + + shift_selection_end(); + previous_shift = 0; + } + + if (k->mod & KMOD_ALT) + ret = pattern_editor_handle_alt_key(k); + else if (k->mod & KMOD_CTRL) + ret = pattern_editor_handle_ctrl_key(k); + else + ret = pattern_editor_handle_key(k); + + if (ret != -1) + return ret; + + current_row = CLAMP(current_row, 0, total_rows); + if (current_position > 8) { + if (current_channel < 64) { + current_position = 0; + current_channel++; + } else { + current_position = 8; + } + } else if (current_position < 0) { + if (current_channel > 1) { + current_position = 8; + current_channel--; + } else { + current_position = 0; + } + } + + current_channel = CLAMP(current_channel, 1, 64); + pattern_editor_reposition(); + if (k->mod & KMOD_SHIFT) + shift_selection_update(); + + draw_text(numtostr(3, song_get_num_patterns(), buf), 16, 6, 5, 0); + draw_text(numtostr(3, current_row, buf), 12, 7, 5, 0); + + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ + +static void pattern_editor_playback_update(void) +{ + static int prev_row = -1; + static int prev_pattern = -1; + + playing_row = song_get_current_row(); + playing_pattern = song_get_playing_pattern(); + + if ((song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) != 0 + && (playing_row != prev_row || playing_pattern != prev_pattern)) { + + prev_row = playing_row; + prev_pattern = playing_pattern; + + if (playback_tracing) { + set_current_order(song_get_current_order()); + set_current_pattern(playing_pattern); + current_row = playing_row; + pattern_editor_reposition(); + status.flags |= NEED_UPDATE; + } else if (current_pattern == song_get_playing_pattern()) { + status.flags |= NEED_UPDATE; + } + } +} + +/* --------------------------------------------------------------------- */ + +void pattern_editor_load_page(struct page *page) +{ + int i; + for (i = 0; i < 10; i++) { + memset(&undo_history[i],0,sizeof(struct pattern_snap)); + undo_history[i].snap_op = (const unsigned char *)"Empty"; + undo_history[i].freesnapop = 0; + } + for (i = 0; i < 64; i++) { + channel_quick[i] = 1; + } + page->title = "Pattern Editor (F2)"; + page->playback_update = pattern_editor_playback_update; + page->song_changed_cb = pated_history_clear; + page->total_widgets = 1; + page->widgets = widgets_pattern; + page->help_index = HELP_PATTERN_EDITOR; + + create_other(widgets_pattern + 0, 0, pattern_editor_handle_key_cb, pattern_editor_redraw); +} diff --git a/schism/page_preferences.c b/schism/page_preferences.c new file mode 100644 index 000000000..255b541ef --- /dev/null +++ b/schism/page_preferences.c @@ -0,0 +1,559 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include "mixer.h" + +#include + +#include "diskwriter.h" + +/* --------------------------------------------------------------------- */ +/* statics */ + +static struct widget widgets_preferences[32]; + +static const char *interpolation_modes[] = { + "Non-Interpolated", "Linear", + "Cubic Spline", "8-Tap FIR Filter", NULL +}; + +static int interp_group[] = { + 2,3,4,5,-1, +}; + +/* --------------------------------------------------------------------- */ + +static void preferences_draw_const(void) +{ + char buf[80]; + int i; + + draw_text("Master Volume Left", 2, 14, 0, 2); + draw_text("Master Volume Right", 2, 15, 0, 2); + draw_box(21, 13, 27, 16, BOX_THIN | BOX_INNER | BOX_INSET); + + sprintf(buf, "Mixing Mode, Playback Frequency: %dHz", audio_settings.sample_rate); + draw_text(buf, 2, 18, 0, 2); + + for (i = 0; interpolation_modes[i]; i++); + + draw_text("Output Equalizer", 2, 21+i*3, 0, 2); + draw_text( "Low Frequency Band", 7, 23+i*3, 0, 2); + draw_text( "Med Low Frequency Band", 3, 24+i*3, 0, 2); + draw_text("Med High Frequency Band", 2, 25+i*3, 0, 2); + draw_text( "High Frequency Band", 6, 26+i*3, 0, 2); + + draw_box(25, 22+i*3, 47, 27+i*3, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(52, 22+i*3, 74, 27+i*3, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_text("Oversampling", 57, 25, 0, 2); + draw_text("Noise Reduction", 54, 26, 0, 2); + + draw_fill_chars(73, 24, 77, 26, 0); + draw_box(69, 24, 78, 27, BOX_THIN | BOX_INNER | BOX_INSET); + draw_fill_chars(73, 29, 77, 31, 0); + draw_box(69, 28, 78, 32, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_text("XBass", 64, 29, 0, 2); + draw_text("Reverb", 63, 30, 0, 2); + draw_text("Surround", 61, 31, 0, 2); +#if 0 + draw_fill_chars(18, 14, 34, 28, 0); /* left box */ + draw_fill_chars(64, 17, 72, 27, 0); /* right box */ + + /* left side */ + draw_box(17, 13, 35, 29, BOX_THIN | BOX_INNER | BOX_INSET); +#if SCHISM_MIXER_CONTROL == SOUND_MIXER_VOLUME +#else + draw_text(" PCM Volume L", 2, 14, 0, 2); + draw_text(" PCM Volume R", 2, 15, 0, 2); +#endif + draw_text(" Channel Limit", 2, 17, 0, 2); + draw_text(" Sample Rate", 2, 18, 0, 2); + draw_text(" Quality", 2, 19, 0, 2); + draw_text(" Interpolation", 2, 20, 0, 2); + draw_text(" Oversampling", 2, 21, 0, 2); + draw_text(" HQ Resampling", 2, 22, 0, 2); + draw_text("Noise Reduction", 2, 23, 0, 2); + draw_text("Surround Effect", 2, 24, 0, 2); + draw_text(" Time Display", 2, 26, 0, 2); + draw_text(" Classic Mode", 2, 27, 0, 2); + draw_text(" Visualization", 2, 28, 0, 2); + for (n = 18; n < 35; n++) { + draw_char(154, n, 16, 3, 0); + draw_char(154, n, 25, 3, 0); + } + + /* right side */ + draw_box(53, 13, 78, 29, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(63, 16, 73, 28, BOX_THIN | BOX_INNER | BOX_INSET); + draw_text("Modplug DSP Settings", 56, 15, 0, 2); + draw_text(" XBass", 55, 17, 0, 2); + draw_text(" Amount", 55, 18, 0, 2); + draw_text(" Range", 55, 19, 0, 2); + draw_text("Surround", 55, 21, 0, 2); + draw_text(" Depth", 55, 22, 0, 2); + draw_text(" Delay", 55, 23, 0, 2); + draw_text(" Reverb", 55, 25, 0, 2); + draw_text(" Depth", 55, 26, 0, 2); + draw_text(" Delay", 55, 27, 0, 2); + for (n = 64; n < 73; n++) { + draw_char(154, n, 20, 3, 0); + draw_char(154, n, 24, 3, 0); + } + + for (n = 1; n < 79; n++) draw_char(154,n, 30, 1,2); + draw_text(" Disk Writer Output Settings ", 26, 30, 0, 2); + + draw_text(" Sample Rate", 2, 32, 0, 2); + draw_text(" Quality", 2, 33, 0, 2); + draw_text("Output Channels", 2, 34, 0, 2); + + draw_fill_chars(18, 32, 34, 34, 0); + draw_box(17, 31, 35, 35, BOX_THIN | BOX_INNER | BOX_INSET); +#endif + + +#define CORNER_BOTTOM "http://rigelseven.com/schism/" +#ifndef RELEASE_VERSION +#define CORNER_BOTTOM2 "http://sourceforge.net/projects/schismtracker/" + draw_text(CORNER_BOTTOM2, 78 - strlen(CORNER_BOTTOM2), 48, 1, 2); + draw_text(CORNER_BOTTOM, 78 - strlen(CORNER_BOTTOM), 47, 1, 2); +#else + draw_text(CORNER_BOTTOM, 78 - strlen(CORNER_BOTTOM), 48, 1, 2); +#endif +} + +/* --------------------------------------------------------------------- */ + +static void preferences_set_page(void) +{ + int i, j; + mixer_read_volume(&(widgets_preferences[0].d.thumbbar.value), &(widgets_preferences[1].d.thumbbar.value)); + + for (i = j = 0; interpolation_modes[i]; i++) { + if (i == audio_settings.interpolation_mode) { + widgets_preferences[i + 2].d.togglebutton.state=1; + j = 1; + } else { + widgets_preferences[i + 2].d.togglebutton.state=0; + } + } + if (!j) { + audio_settings.interpolation_mode = 0; + widgets_preferences[2].d.togglebutton.state=1; + } + + widgets_preferences[i+11].d.toggle.state = audio_settings.oversampling; + widgets_preferences[i+12].d.toggle.state = audio_settings.noise_reduction; + + widgets_preferences[i+13].d.toggle.state = audio_settings.xbass; + widgets_preferences[i+14].d.toggle.state = audio_settings.reverb; + widgets_preferences[i+15].d.toggle.state = audio_settings.surround; + + for (j = 0; j < 4; j++) { + widgets_preferences[i+2+(j*2)].d.thumbbar.value + = audio_settings.eq_freq[j]; + widgets_preferences[i+3+(j*2)].d.thumbbar.value + = audio_settings.eq_gain[j]; + } + +#if 0 + widgets_preferences[2].d.thumbbar.value = audio_settings.channel_limit; + widgets_preferences[3].d.numentry.value = audio_settings.sample_rate; + widgets_preferences[4].d.menutoggle.state = !!(audio_settings.bits == 16); + + widgets_preferences[5].d.menutoggle.state = audio_settings.interpolation_mode; + widgets_preferences[6].d.toggle.state = audio_settings.oversampling; + widgets_preferences[7].d.toggle.state = audio_settings.hq_resampling; + widgets_preferences[8].d.toggle.state = audio_settings.noise_reduction; + widgets_preferences[9].d.toggle.state = song_get_surround(); + + widgets_preferences[10].d.menutoggle.state = status.time_display; + widgets_preferences[11].d.toggle.state = !!(status.flags & CLASSIC_MODE); + widgets_preferences[12].d.menutoggle.state = status.vis_style; + + widgets_preferences[13].d.toggle.state = audio_settings.xbass; + widgets_preferences[14].d.thumbbar.value = audio_settings.xbass_amount; + widgets_preferences[15].d.thumbbar.value = audio_settings.xbass_range; + widgets_preferences[16].d.toggle.state = audio_settings.surround; /* not s91, the other kind */ + widgets_preferences[17].d.thumbbar.value = audio_settings.surround_depth; + widgets_preferences[18].d.thumbbar.value = audio_settings.surround_delay; + widgets_preferences[19].d.toggle.state = audio_settings.reverb; + widgets_preferences[20].d.thumbbar.value = audio_settings.reverb_depth; + widgets_preferences[21].d.thumbbar.value = audio_settings.reverb_delay; + + widgets_preferences[22].d.numentry.value = diskwriter_output_rate; + widgets_preferences[23].d.menutoggle.state = (diskwriter_output_bits > 8) ? 1 : 0; + widgets_preferences[24].d.menutoggle.state = diskwriter_output_channels; +#endif +} + +/* --------------------------------------------------------------------- */ + +static void change_diskwriter(void) +{ +#if 0 + diskwriter_output_rate = widgets_preferences[22].d.numentry.value; + diskwriter_output_bits = widgets_preferences[23].d.menutoggle.state ? 16 : 8; + diskwriter_output_channels = widgets_preferences[24].d.menutoggle.state; +#endif +} +static void change_volume(void) +{ + mixer_write_volume(widgets_preferences[0].d.thumbbar.value, widgets_preferences[1].d.thumbbar.value); +} + +#define SAVED_AT_EXIT "Audio configuration will be saved at exit" + +static void change_audio(void) +{ +#if 0 + audio_settings.sample_rate = widgets_preferences[3].d.numentry.value; + audio_settings.bits = widgets_preferences[4].d.menutoggle.state ? 16 : 8; + status_text_flash(SAVED_AT_EXIT); +#endif +} + +static void change_misc(void) +{ +#if 0 + song_set_surround(widgets_preferences[9].d.toggle.state); + status.time_display = widgets_preferences[10].d.menutoggle.state; + if (widgets_preferences[11].d.toggle.state) + status.flags |= CLASSIC_MODE; + else + status.flags &= ~CLASSIC_MODE; + status.vis_style = widgets_preferences[12].d.menutoggle.state; +#endif +} + +static void change_eq(void) +{ + int i,j; + for (i = 0; interpolation_modes[i]; i++); + for (j = 0; j < 4; j++) { + audio_settings.eq_freq[j] = widgets_preferences[i+2+(j*2)].d.thumbbar.value; + audio_settings.eq_gain[j] = widgets_preferences[i+3+(j*2)].d.thumbbar.value; + } + song_init_eq(1); +} + +static struct widget dsp_dialog_widgets[4]; +static const char *dsp_dialog_title = "(null)"; +static const char *dsp_dialog_label1 = "(null)"; +static const char *dsp_dialog_label2 = "(null)"; +static int *dsp_value0; +static int *dsp_value1, dsp_value_save1; +static int *dsp_value2, dsp_value_save2; + +static void draw_dsp_dialog_const(void) +{ + int len; + len = strlen(dsp_dialog_title); + draw_text(dsp_dialog_title, 40 - (len/2), 24, 0, 2); + + len = strlen(dsp_dialog_label1); + draw_text(dsp_dialog_label1, 29 - len, 27, 0, 2); + + len = strlen(dsp_dialog_label2); + draw_text(dsp_dialog_label2, 29 - len, 28, 0, 2); + + draw_box(29, 26, 55, 29, BOX_THIN | BOX_INNER | BOX_INSET); +} +static void dsp_dialog_update(UNUSED void*ign) +{ + *dsp_value1 = dsp_dialog_widgets[0].d.thumbbar.value; + *dsp_value2 = dsp_dialog_widgets[1].d.thumbbar.value; + song_init_modplug(); +} +static void dsp_dialog_ok(UNUSED void *ign) +{ + dialog_destroy(); + status_text_flash(SAVED_AT_EXIT); + status.flags |= NEED_UPDATE; +} +static void dsp_dialog_cancel(UNUSED void*ign) +{ + int i; + + *dsp_value0 = 0; + + for (i = 0; interpolation_modes[i]; i++); + widgets_preferences[i+13].d.toggle.state = audio_settings.xbass; + widgets_preferences[i+14].d.toggle.state = audio_settings.reverb; + widgets_preferences[i+15].d.toggle.state = audio_settings.surround; + + *dsp_value1 = dsp_value_save1; + *dsp_value2 = dsp_value_save2; + dialog_destroy(); + dsp_dialog_update(0); + status.flags |= NEED_UPDATE; +} +static void show_dsp_dialog(const char *text, int *ov, + const char *label1, int low1, int high1, int *v1, + const char *label2, int low2, int high2, int *v2) +{ + struct dialog *d; + + dsp_dialog_title = text; + dsp_dialog_label1 = label1; + dsp_dialog_label2 = label2; + create_thumbbar(dsp_dialog_widgets+0, 30, 27, 25, 0, 1, 1, + dsp_dialog_update, low1, high1); + create_thumbbar(dsp_dialog_widgets+1, 30, 28, 25, 0, 2, 2, + dsp_dialog_update, low1, high1); + dsp_value0 = ov; + dsp_value1 = v1; dsp_dialog_widgets[0].d.thumbbar.value = *v1; + dsp_value2 = v2; dsp_dialog_widgets[1].d.thumbbar.value = *v2; + create_button(dsp_dialog_widgets+2, 28,31,8, 1, 2, 2, 3, 3, + dsp_dialog_ok, "OK", 4); + create_button(dsp_dialog_widgets+3, 42,31,8, 1, 2, 2, 3, 3, + dsp_dialog_cancel, "Cancel", 2); + d = dialog_create_custom(20, 22, 40, 12, + dsp_dialog_widgets, + 4, 2, + draw_dsp_dialog_const,0); + d->action_yes = dsp_dialog_ok; + d->action_no = dsp_dialog_cancel; + d->action_cancel = dsp_dialog_cancel; +} + + +static void change_mixer_xbass(void) +{ + int i; + for (i = 0; interpolation_modes[i]; i++); + + audio_settings.xbass = widgets_preferences[i+13].d.toggle.state; + song_init_modplug(); + + if (!audio_settings.xbass) { + status_text_flash(SAVED_AT_EXIT); + return; + } + + show_dsp_dialog("XBass Expansion", &audio_settings.xbass, + "Amount", 0, 100, &audio_settings.xbass_amount, + "Range", 10, 100, &audio_settings.xbass_range); +} +static void change_mixer_reverb(void) +{ + int i; + for (i = 0; interpolation_modes[i]; i++); + + audio_settings.reverb = widgets_preferences[i+14].d.toggle.state; + song_init_modplug(); + + if (!audio_settings.reverb) { + status_text_flash(SAVED_AT_EXIT); + return; + } + + show_dsp_dialog("Reverb", &audio_settings.reverb, + "Depth", 0, 100, &audio_settings.reverb_depth, + "Delay", 40, 200, &audio_settings.reverb_delay); +} +static void change_mixer_surround(void) +{ + int i; + for (i = 0; interpolation_modes[i]; i++); + + audio_settings.surround = widgets_preferences[i+15].d.toggle.state; + song_init_modplug(); + + if (!audio_settings.surround) { + status_text_flash(SAVED_AT_EXIT); + return; + } + + show_dsp_dialog("Surround Expansion", &audio_settings.surround, + "Depth", 0, 100, &audio_settings.surround_depth, + "Delay", 5, 50, &audio_settings.surround_delay); +} +static void change_mixer(void) +{ + int i; + for (i = 0; interpolation_modes[i]; i++) { + if (widgets_preferences[2+i].d.togglebutton.state) { + audio_settings.interpolation_mode = i; + } + } +#if 0 + audio_settings.channel_limit = widgets_preferences[2].d.thumbbar.value; + audio_settings.interpolation_mode = widgets_preferences[5].d.menutoggle.state; + audio_settings.oversampling = widgets_preferences[6].d.toggle.state; + audio_settings.hq_resampling = widgets_preferences[7].d.toggle.state; + audio_settings.noise_reduction = widgets_preferences[8].d.toggle.state; +#endif + song_init_modplug(); + status_text_flash(SAVED_AT_EXIT); +} + +static void change_modplug_dsp(void) +{ +#if 0 + audio_settings.xbass = widgets_preferences[13].d.toggle.state; + audio_settings.xbass_amount = widgets_preferences[14].d.thumbbar.value; + audio_settings.xbass_range = widgets_preferences[15].d.thumbbar.value; + audio_settings.surround = widgets_preferences[16].d.toggle.state; + audio_settings.surround_depth = widgets_preferences[17].d.thumbbar.value; + audio_settings.surround_delay = widgets_preferences[18].d.thumbbar.value; + audio_settings.reverb = widgets_preferences[19].d.toggle.state; + audio_settings.reverb_depth = widgets_preferences[20].d.thumbbar.value; + audio_settings.reverb_delay = widgets_preferences[21].d.thumbbar.value; + song_init_modplug(); +#endif +} + +/* --------------------------------------------------------------------- */ +static void save_config_now(UNUSED void *ign) +{ + /* TODO */ + cfg_midipage_save(); + cfg_atexit_save(); + status_text_flash("Configuration saved"); +} + +void preferences_load_page(struct page *page) +{ + int max = mixer_get_max_volume(); + char buf[64]; + char *ptr; + int i, j, n; + + page->title = "Preferences (Shift-F5)"; + page->draw_const = preferences_draw_const; + page->set_page = preferences_set_page; + page->total_widgets = 16; + page->widgets = widgets_preferences; + page->help_index = HELP_GLOBAL; + + + create_thumbbar(widgets_preferences + 0, 22, 14, 5, 0, 1, 1, change_volume, 0, max); + create_thumbbar(widgets_preferences + 1, 22, 15, 5, 0, 2, 2, change_volume, 0, max); + + for (n = 0; interpolation_modes[n]; n++); + for (i = 0; interpolation_modes[i]; i++) { + + sprintf(buf, "%d Bit, %s", audio_settings.bits, interpolation_modes[i]); + ptr = strdup(buf); + create_togglebutton(widgets_preferences+i+2, + 6, 20 + (i * 3), 26, + i+1, i+3, i+2, n+11, i+3, + change_mixer, + ptr, + 2, + &interp_group); + page->total_widgets++; + } + + for (j = 0; j < 4; j++) { + n = i+(j*2); + if (j == 0) n = i+1; + create_thumbbar(widgets_preferences+i+2+(j*2), + 26, 23+(i*3)+j, + 21, + n, i+(j*2)+4, i+(j*2)+3, + change_eq, + 0, 127); + n = i+(j*2)+5; + if (j == 3) n--; + create_thumbbar(widgets_preferences+i+3+(j*2), + 53, 23+(i*3)+j, + 21, + i+(j*2)+1, n, i+(j*2)+4, + change_eq, + 0, 127); + } + /* default EQ setting */ + widgets_preferences[i+2].d.thumbbar.value = 0; + widgets_preferences[i+4].d.thumbbar.value = 16; + widgets_preferences[i+6].d.thumbbar.value = 96; + widgets_preferences[i+8].d.thumbbar.value = 127; + + create_button(widgets_preferences+i+10, + 2, 44, 27, + i+8, i+10, i+10, i+11, i+11, + save_config_now, + "Save Output Configuration", 2); + + create_toggle(widgets_preferences+i+11, /* Oversampling */ + 70, 25, + i+11,i+12,1+i, i+11,i+12, + change_mixer); + create_toggle(widgets_preferences+i+12, /* Noise Reduction */ + 70, 26, + i+11,i+13,1+i, i+12,i+13, + change_mixer); + create_toggle(widgets_preferences+i+13, /* XBass */ + 70, 29, + i+12,i+14,1+i, i+13,i+14, + change_mixer_xbass); + create_toggle(widgets_preferences+i+14, /* Reverb */ + 70, 30, + i+13,i+15,1+i, i+14,i+15, + change_mixer_reverb); + create_toggle(widgets_preferences+i+15, /* Surround */ + 70, 31, + i+14,i+15,1+i, i+15,0, + change_mixer_surround); + + + +#if 0 + create_thumbbar(widgets_preferences + 2, 18, 17, 17, 1, 3, 13, change_mixer, 4, 256); + create_numentry(widgets_preferences + 3, 18, 18, 7, 2, 4, 14, + change_audio, 4000, 50000, &sample_rate_cursor); + create_menutoggle(widgets_preferences + 4, 18, 19, 3, 5, 15, 15, 15, change_audio, bit_rates); + create_menutoggle(widgets_preferences + 5, 18, 20, 4, 6, 16, 16, 16, + change_mixer, interpolation_modes); + create_toggle(widgets_preferences + 6, 18, 21, 5, 7, 16, 16, 16, change_mixer); + create_toggle(widgets_preferences + 7, 18, 22, 6, 8, 17, 17, 17, change_mixer); + create_toggle(widgets_preferences + 8, 18, 23, 7, 9, 18, 18, 18, change_mixer); + create_toggle(widgets_preferences + 9, 18, 24, 8, 10, 19, 19, 19, change_misc); + create_menutoggle(widgets_preferences + 10, 18, 26, 9, 11, 20, 20, 20, change_misc, time_displays); + create_toggle(widgets_preferences + 11, 18, 27, 10, 12, 21, 21, 21, change_misc); + create_menutoggle(widgets_preferences + 12, 18, 28, 11, 22, 21, 21, 21, change_misc, vis_styles); + + create_toggle(widgets_preferences + 13, 64, 17, 12, 14, 2, 2, 2, change_modplug_dsp); + create_thumbbar(widgets_preferences + 14, 64, 18, 9, 13, 15, 3, change_modplug_dsp, 0, 100); + create_thumbbar(widgets_preferences + 15, 64, 19, 9, 14, 16, 4, change_modplug_dsp, 10, 100); + create_toggle(widgets_preferences + 16, 64, 21, 15, 17, 6, 6, 6, change_modplug_dsp); + create_thumbbar(widgets_preferences + 17, 64, 22, 9, 16, 18, 7, change_modplug_dsp, 0, 100); + create_thumbbar(widgets_preferences + 18, 64, 23, 9, 17, 19, 8, change_modplug_dsp, 5, 50); + create_toggle(widgets_preferences + 19, 64, 25, 18, 20, 9, 9, 9, change_modplug_dsp); + create_thumbbar(widgets_preferences + 20, 64, 26, 9, 19, 21, 10, change_modplug_dsp, 0, 100); + create_thumbbar(widgets_preferences + 21, 64, 27, 9, 20, 22, 11, change_modplug_dsp, 40, 200); + + create_numentry(widgets_preferences + 22, 18, 32, 7, 12, 23, 23, + change_diskwriter, 4000, 50000, &sample_rate_cursor2); + create_menutoggle(widgets_preferences + 23, 18, 33, 22, 24, 23, 23, 24, + change_diskwriter, bit_rates); + create_menutoggle(widgets_preferences + 24, 18, 34, 23, 24, 24, 24, 24, + change_diskwriter, output_channels); +#endif +} diff --git a/schism/page_samples.c b/schism/page_samples.c new file mode 100644 index 000000000..7509446ac --- /dev/null +++ b/schism/page_samples.c @@ -0,0 +1,1355 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "clippy.h" + +#include "song.h" +#include "dmoz.h" +#include "sample-edit.h" + +#include /* for pow */ + +#include "video.h" + +/* --------------------------------------------------------------------- */ +/* static in my attic */ +static struct vgamem_overlay sample_image = { + 55,26,76,29 +}; + +static struct widget widgets_samplelist[20]; +static int vibrato_waveforms[] = { 15, 16, 17, 18, -1 }; + +static int top_sample = 1; +static int current_sample = 1; +static int need_retrigger = -1; +static int last_keyup = -1; + +static int sample_list_cursor_pos = 25; /* the "play" text */ + +/* shared by all the numentry widgets */ +static int sample_numentry_cursor_pos = 0; + +/* for the loops */ +static const char *loop_states[] = { "Off", "On Forwards", "On Ping Pong", NULL }; + +/* playback */ +static int last_note = 61; /* C-5 */ + +/* woo */ +static int _is_magic_sample(int no) +{ + char *name; + song_sample *sample; + int pn; + + sample = song_get_sample(no, &name); + if (sample && name && ((unsigned char)name[23]) == 0xFF) { + pn = ((unsigned char)name[24]); + if (pn < 200) return 1; + } + return 0; +} +/* --------------------------------------------------------------------- */ + +static void sample_list_reposition(void) +{ + if (current_sample < top_sample) { + top_sample = current_sample; + if (top_sample < 1) + top_sample = 1; + } else if (current_sample > top_sample + 34) { + top_sample = current_sample - 34; + } +} + +/* --------------------------------------------------------------------- */ + +int sample_get_current(void) +{ + return current_sample; +} + +void sample_set(int n) +{ + int new_sample = n; + + if (status.current_page == PAGE_SAMPLE_LIST) + new_sample = CLAMP(n, 1, 99); + else + new_sample = CLAMP(n, 0, 99); + + if (current_sample == new_sample) + return; + + status.flags = (status.flags & ~INSTRUMENT_CHANGED) | SAMPLE_CHANGED; + current_sample = new_sample; + sample_list_reposition(); + + /* update_current_instrument(); */ + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* draw the actual list */ + +static void sample_list_draw_list(void) +{ + int pos, n, nl, pn; + char *name; + song_sample *sample; + int has_data, is_selected; + char buf[64]; + int ss, cl, cr; + int is_playing[100]; + + if (clippy_owner(CLIPPY_SELECT) == widgets_samplelist) { + cl = widgets_samplelist[0].clip_start % 25; + cr = widgets_samplelist[0].clip_end % 25; + if (cl > cr) { + ss = cl; + cl = cr; + cr = ss; + } + ss = (widgets_samplelist[0].clip_start / 25); + } else { + ss = -1; + } + + song_get_playing_samples(is_playing); + + /* list */ + for (pos = 0, n = top_sample; pos < 35; pos++, n++) { + sample = song_get_sample(n, &name); + is_selected = (n == current_sample); + has_data = (sample->data != NULL); + + if (sample->played) + draw_char(173, 1, 13 + pos, is_playing[n] ? 3 : 1, 2); + + draw_text(numtostr(2, n, buf), 2, 13 + pos, 0, 2); + + pn = ((unsigned char)name[24]); + if (((unsigned char)name[23]) == 0xFF && pn < 200) { + nl = 23; + draw_text(numtostr(3, (int)pn, buf), 32, 13 + pos, 0, 2); + draw_char('P', 28, 13+pos, 3, 2); + draw_char('a', 29, 13+pos, 3, 2); + draw_char('t', 30, 13+pos, 3, 2); + draw_char('.', 31, 13+pos, 3, 2); + } else { + nl = 25; + draw_char(168, 30, 13 + pos, 2, (is_selected ? 14 : 0)); + draw_text("Play", 31, 13 + pos, (has_data ? 6 : 7), (is_selected ? 14 : 0)); + } + + draw_text_len(name, nl, 5, 13 + pos, 6, (is_selected ? 14 : 0)); + if (ss == n) { + draw_text_len(name + cl, (cr-cl)+1, + 5 + cl, 13 + pos, + 3, 8); + } + } + + /* cursor */ + if (ACTIVE_PAGE.selected_widget == 0) { + pos = current_sample - top_sample; + sample = song_get_sample(current_sample, &name); + has_data = (sample->data != NULL); + + if (pos < 0 || pos > 34) { + /* err... */ + } else if (sample_list_cursor_pos == 25) { + draw_text("Play", 31, 13 + pos, 0, (has_data ? 3 : 6)); + } else { + //draw_char(((sample_list_cursor_pos > (signed) strlen(name)) + // ? 0 : name[sample_list_cursor_pos]), + // sample_list_cursor_pos + 5, 13 + pos, 0, 3); + draw_char(name[sample_list_cursor_pos], + sample_list_cursor_pos + 5, 13 + pos, 0, 3); + } + } + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void sample_list_predraw_hook(void) +{ + char buf[16]; + char *name; + song_sample *sample; + int has_data; + + sample = song_get_sample(current_sample, &name); + has_data = (sample->data != NULL); + + /* set all the values to the current sample */ + + /* default volume + modplug hack here: sample volume has 4x the resolution... + can't deal with this in song.cc (easily) without changing the actual volume of the sample. */ + widgets_samplelist[1].d.thumbbar.value = sample->volume / 4; + + /* global volume */ + widgets_samplelist[2].d.thumbbar.value = sample->global_volume; + + /* default pan (another modplug hack) */ + widgets_samplelist[3].d.toggle.state = (sample->flags & SAMP_PANNING); + widgets_samplelist[4].d.thumbbar.value = sample->panning / 4; + + widgets_samplelist[5].d.thumbbar.value = sample->vib_speed; + widgets_samplelist[6].d.thumbbar.value = sample->vib_depth; + widgets_samplelist[7].d.textentry.text = sample->filename; + widgets_samplelist[8].d.numentry.value = song_sample_get_c5speed(current_sample); + + widgets_samplelist[9].d.menutoggle.state = + (sample->flags & SAMP_LOOP ? (sample->flags & SAMP_LOOP_PINGPONG ? 2 : 1) : 0); + widgets_samplelist[10].d.numentry.value = sample->loop_start; + widgets_samplelist[11].d.numentry.value = sample->loop_end; + widgets_samplelist[12].d.menutoggle.state = + (sample->flags & SAMP_SUSLOOP ? (sample->flags & SAMP_SUSLOOP_PINGPONG ? 2 : 1) : 0); + widgets_samplelist[13].d.numentry.value = sample->sustain_start; + widgets_samplelist[14].d.numentry.value = sample->sustain_end; + + switch (sample->vib_type) { + case VIB_SINE: + togglebutton_set(widgets_samplelist, 15, 0); + break; + case VIB_RAMP_UP: + case VIB_RAMP_DOWN: + togglebutton_set(widgets_samplelist, 16, 0); + break; + case VIB_SQUARE: + togglebutton_set(widgets_samplelist, 17, 0); + break; + case VIB_RANDOM: + togglebutton_set(widgets_samplelist, 18, 0); + break; + } + + widgets_samplelist[19].d.thumbbar.value = ((sample->vib_rate < 64) ? sample->vib_rate * 4 : 255); + + if (sample->flags & SAMP_STEREO) { + draw_text_len((has_data ? (sample->flags & SAMP_16_BIT ? "16 bits Stereo" : "8 bits Stereo") : "No sample"), + 13, 64, 22, 2, 0); + } else { + draw_text_len((has_data ? (sample->flags & SAMP_16_BIT ? "16 bits" : "8 bits") : "No sample"), + 13, 64, 22, 2, 0); + } + draw_text_len(numtostr(0, sample->length, buf), 13, 64, 23, 2, 0); + + draw_sample_data(&sample_image, sample, current_sample); + + if (need_retrigger > -1) { + if (last_keyup > -1) song_keyup(current_sample, -1, last_keyup, -1, 0); + song_keyup(current_sample, -1, need_retrigger, -1, 0); + song_keydown(current_sample, -1, need_retrigger, 64, -1, 0); + if (!song_is_multichannel_mode()) { + last_keyup = need_retrigger; + } + song_update_playing_sample(current_sample); + need_retrigger = -1; + } +} + +/* --------------------------------------------------------------------- */ + +static int sample_list_add_char(char c) +{ + char *name; + + if (c < 32) + return 0; + song_get_sample(current_sample, &name); + text_add_char(name, c, &sample_list_cursor_pos, _is_magic_sample(current_sample) + ? 22 : 25); + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; + return 1; +} + +static void sample_list_delete_char(void) +{ + char *name; + + song_get_sample(current_sample, &name); + text_delete_char(name, &sample_list_cursor_pos, _is_magic_sample(current_sample) + ? 23 : 25); + + status.flags |= SONG_NEEDS_SAVE; + status.flags |= NEED_UPDATE; +} + +static void sample_list_delete_next_char(void) +{ + char *name; + + song_get_sample(current_sample, &name); + text_delete_next_char(name, &sample_list_cursor_pos, _is_magic_sample(current_sample) + ? 23 : 25); + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +static void clear_sample_text(void) +{ + char *name; + + memset(song_get_sample(current_sample, &name)->filename, 0, 14); + if (_is_magic_sample(current_sample)) { + memset(name, 0, 24); + } else { + memset(name, 0, 26); + } + sample_list_cursor_pos = 0; + + status.flags |= NEED_UPDATE; + status.flags |= SONG_NEEDS_SAVE; +} + +/* --------------------------------------------------------------------- */ + +static struct widget swap_sample_widgets[6]; +static char swap_sample_entry[4] = ""; + + +static void do_swap_sample(UNUSED void *data) +{ + int n = atoi(swap_sample_entry); + + if (n < 1 || n > 99) + return; + song_swap_samples(current_sample, n); +} + +static void swap_sample_draw_const(void) +{ + draw_text("Swap sample with:", 32, 25, 0, 2); + draw_text("Sample", 35, 27, 0, 2); + draw_box(41, 26, 45, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void swap_sample_dialog(void) +{ + struct dialog *dialog; + + swap_sample_entry[0] = 0; + create_textentry(swap_sample_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, swap_sample_entry, 2); + create_button(swap_sample_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(26, 23, 29, 10, swap_sample_widgets, 2, 0, swap_sample_draw_const, NULL); + dialog->action_yes = do_swap_sample; +} + + +static void do_exchange_sample(UNUSED void *data) +{ + int n = atoi(swap_sample_entry); + + if (n < 1 || n > 99) + return; + song_exchange_samples(current_sample, n); +} + +static void exchange_sample_draw_const(void) +{ + draw_text("Exchange sample with:", 30, 25, 0, 2); + draw_text("Sample", 35, 27, 0, 2); + draw_box(41, 26, 45, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void exchange_sample_dialog(void) +{ + struct dialog *dialog; + + swap_sample_entry[0] = 0; + create_textentry(swap_sample_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, swap_sample_entry, 2); + create_button(swap_sample_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + dialog = dialog_create_custom(26, 23, 29, 10, swap_sample_widgets, 2, 0, + exchange_sample_draw_const, NULL); + dialog->action_yes = do_exchange_sample; +} + +/* --------------------------------------------------------------------- */ + +static int sample_list_handle_key_on_list(struct key_event * k) +{ + int new_sample = current_sample; + int new_cursor_pos = sample_list_cursor_pos; + char *name; + int i; + + if (k->mouse == MOUSE_CLICK && k->mouse_button == MOUSE_BUTTON_MIDDLE) { + if (k->state) status.flags |= CLIPPY_PASTE_SELECTION; + return 1; + } else if (!k->state && k->mouse && k->x >= 5 && k->y >= 13 && k->y <= 47 && k->x <= 34) { + if (k->mouse == MOUSE_SCROLL_UP) { + top_sample--; + if (top_sample < 1) top_sample = 1; + status.flags |= NEED_UPDATE; + return 1; + } else if (k->mouse == MOUSE_SCROLL_DOWN) { + top_sample++; + if (top_sample > (99-34)) top_sample = (99-34); + status.flags |= NEED_UPDATE; + return 1; + } else { + if (k->state || k->sy == k->y) { + new_sample = (k->y - 13) + top_sample; + } + if (k->x <= 29) { /* and button1 */ + if (k->mouse == MOUSE_DBLCLICK) { + set_page(PAGE_LOAD_SAMPLE); + status.flags |= NEED_UPDATE; + return 1; + } else if (new_sample == current_sample + || sample_list_cursor_pos != 25) { + new_cursor_pos = k->x - 5; + } + } else if (k->state || k->x == k->sx) { + if (k->mouse == MOUSE_DBLCLICK + || (new_sample == current_sample + && sample_list_cursor_pos == 25)) { + need_retrigger = 61; + status.flags |= NEED_UPDATE; + } + new_cursor_pos = 25; + } + } + } else { + switch (k->sym) { + case SDLK_LEFT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_cursor_pos--; + break; + case SDLK_RIGHT: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_cursor_pos++; + break; + case SDLK_HOME: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_cursor_pos = 0; + break; + case SDLK_END: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_cursor_pos = 25; + break; + case SDLK_UP: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_sample--; + break; + case SDLK_DOWN: + if (k->state) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + new_sample++; + break; + case SDLK_PAGEUP: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + new_sample = 1; + } else { + new_sample -= 16; + } + break; + case SDLK_PAGEDOWN: + if (k->state) return 0; + if (k->mod & KMOD_CTRL) { + new_sample = 99; + } else { + new_sample += 16; + } + break; + case SDLK_RETURN: + if (!k->state) return 0; + set_page(PAGE_LOAD_SAMPLE); + break; + case SDLK_BACKSPACE: + if (k->state) return 0; + if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0) { + if (sample_list_cursor_pos < 25) { + sample_list_delete_char(); + } + return 1; + } else if (k->mod & KMOD_CTRL) { + /* just for compatibility with every weird thing + * Impulse Tracker does ^_^ */ + if (sample_list_cursor_pos < 25) { + sample_list_add_char(127); + } + return 1; + } + return 0; + case SDLK_DELETE: + if (k->state) return 0; + if ((k->mod & (KMOD_CTRL | KMOD_ALT)) == 0) { + if (sample_list_cursor_pos < 25) { + sample_list_delete_next_char(); + } + return 1; + } + return 0; + default: + if (k->mod & KMOD_ALT) { + if (k->sym == SDLK_c) { + clear_sample_text(); + return 1; + } + } else if ((k->mod & KMOD_CTRL) == 0 && sample_list_cursor_pos < 25) { + if (k->state) return 1; + int c = unicode_to_ascii(k->unicode); + if (c == 0) + return 0; + return sample_list_add_char(c); + } + return 0; + } + } + + new_sample = CLAMP(new_sample, 1, 99); + new_cursor_pos = CLAMP(new_cursor_pos, 0, 25); + + if (new_sample != current_sample) { + sample_set(new_sample); + sample_list_reposition(); + } else if (new_cursor_pos != sample_list_cursor_pos) { + sample_list_cursor_pos = new_cursor_pos; + } else { + return 0; + } + if (k->mouse && k->x != k->sx) { + song_get_sample(current_sample, &name); + widgets_samplelist[0].clip_start = (k->sx - 5) + (current_sample*25); + widgets_samplelist[0].clip_end = (k->x - 5) + (current_sample*25); + if (widgets_samplelist[0].clip_start < widgets_samplelist[0].clip_end) { + clippy_select(widgets_samplelist, + name + (k->sx - 5), + (k->x - k->sx)); + } else { + clippy_select(widgets_samplelist, + name + (k->x - 5), + (k->sx - k->x)); + } + } + + status.flags |= NEED_UPDATE; + return 1; +} + +/* --------------------------------------------------------------------- */ +/* alt key dialog callbacks. + * these don't need to do any actual redrawing, because the screen gets + * redrawn anyway when the dialog is cleared. */ + +/* TODO | Deleting a sample doesn't require stopping the song because Modplug cleanly stops any playing copies + * TODO | of the sample when it destroys it. It'd be nice if the other sample manipulations (quality convert, + * TODO | loop cut, etc.) did this as well. */ + +static void do_sign_convert(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + sample_sign_convert(sample); +} + +static void do_quality_convert(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + song_stop(); + sample_toggle_quality(sample, 1); +} + +static void do_quality_toggle(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + song_stop(); + sample_toggle_quality(sample, 0); +} + +static void do_delete_sample(UNUSED void *data) +{ + song_clear_sample(current_sample); +} + +static void do_post_loop_cut(UNUSED void *bweh) /* I'm already using 'data'. */ +{ + song_sample *sample = song_get_sample(current_sample, NULL); + signed char *data; + unsigned long pos = MAX(sample->loop_end, sample->sustain_end); + int bytes = pos * ((sample->flags & SAMP_16_BIT) ? 2 : 1) + * ((sample->flags & SAMP_STEREO) ? 2 : 1); + + if (pos == sample->length) + return; + + song_stop(); + + data = song_sample_allocate(bytes); + memcpy(data, sample->data, bytes); + song_sample_free(sample->data); + sample->data = data; + sample->length = pos; +} + +static void do_pre_loop_cut(UNUSED void *bweh) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + signed char *data; + unsigned long pos = ((sample->flags & SAMP_SUSLOOP) + ? MIN(sample->loop_start, sample->sustain_start) + : sample->loop_start); + int start_byte = pos * ((sample->flags & SAMP_16_BIT) ? 2 : 1) + * ((sample->flags & SAMP_STEREO) ? 2 : 1); + int bytes = (sample->length - pos) * ((sample->flags & SAMP_16_BIT) ? 2 : 1) + * ((sample->flags & SAMP_STEREO) ? 2 : 1); + + if (pos == 0) + return; + + song_stop(); + + data = song_sample_allocate(bytes); + memcpy(data, sample->data + start_byte, bytes); + song_sample_free(sample->data); + sample->data = data; + sample->length -= pos; + + if (sample->loop_start > pos) + sample->loop_start -= pos; + else + sample->loop_start = 0; + if (sample->sustain_start > pos) + sample->sustain_start -= pos; + else + sample->sustain_start = 0; + if (sample->loop_end > pos) + sample->loop_end -= pos; + else + sample->loop_end = 0; + if (sample->sustain_end > pos) + sample->sustain_end -= pos; + else + sample->sustain_end = 0; +} + +static void do_centralise(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + sample_centralise(sample); +} + +/* --------------------------------------------------------------------- */ + +static struct widget sample_amplify_widgets[3]; + +static void do_amplify(UNUSED void *data) +{ + sample_amplify(song_get_sample(current_sample, NULL), sample_amplify_widgets[0].d.thumbbar.value); +} + +static void sample_amplify_draw_const(void) +{ + draw_text("Sample Amplification %", 29, 27, 0, 2); + draw_box(12, 29, 64, 31, BOX_THIN | BOX_INNER | BOX_INSET); +} + +static void sample_amplify_dialog(void) +{ + struct dialog *dialog; + int percent = sample_get_amplify_amount(song_get_sample(current_sample, NULL)); + + percent = MIN(percent, 400); + + create_thumbbar(sample_amplify_widgets + 0, 13, 30, 51, 0, 1, 1, NULL, 0, 400); + sample_amplify_widgets[0].d.thumbbar.value = percent; + create_button(sample_amplify_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); + create_button(sample_amplify_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); + + dialog = dialog_create_custom(9, 25, 61, 11, sample_amplify_widgets, + 3, 0, sample_amplify_draw_const, NULL); + dialog->action_yes = do_amplify; +} + +/* --------------------------------------------------------------------- */ + +/* filename can be NULL, in which case the sample filename is used (quick save) */ +struct sample_save_hunk { + char *path; + int format_id; +}; +static void abort_save_sample(void *ptr) +{ + struct sample_save_hunk *foo = (struct sample_save_hunk *)ptr; + free(foo->path); + free(foo); +} +static void do_save_sample(void *ptr) +{ + struct sample_save_hunk *foo = (struct sample_save_hunk *)ptr; + if (song_save_sample(current_sample, foo->path, foo->format_id)) + status_text_flash("%s sample saved (sample %d)", + sample_save_formats[foo->format_id].name, + current_sample); + else + status_text_flash("Error: Sample %d NOT saved! (No Filename?)", current_sample); + abort_save_sample(ptr); /* deftly named :) */ + +} +static void sample_save(const char *filename, int format_id) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + char *ptr = dmoz_path_concat(cfg_dir_samples, filename + ? filename + : sample->filename); + struct sample_save_hunk *poop; + struct stat buf; + + if (filename && *filename && stat(ptr, &buf) == 0) { + if (S_ISDIR(buf.st_mode)) { +#if 0 + dialog_create(DIALOG_OK, "Already exists, but not a regular file", NULL, NULL, 0, NULL); +#endif + status_text_flash("%s is a directory", + filename ? filename : sample->filename); + + } else if (S_ISREG(buf.st_mode)) { + poop = mem_alloc(sizeof(struct sample_save_hunk)); + poop->path = ptr; + poop->format_id = format_id; + dialog_create(DIALOG_OK_CANCEL, + "Overwrite file?", do_save_sample, + abort_save_sample, 1, poop); + } else { +#if 0 + dialog_create(DIALOG_OK, "Already exists, but not a regular file", NULL, NULL, 0, NULL); +#endif + status_text_flash("%s is not a regular file", + filename ? filename : sample->filename); + } + } else if (song_save_sample(current_sample, ptr, format_id)) + status_text_flash("%s sample saved (sample %d)", sample_save_formats[format_id].name, current_sample); + else + status_text_flash("Error: Sample %d NOT saved! (No Filename?)", current_sample); + free(ptr); +} +/* resize sample dialog */ +static struct widget resize_sample_widgets[2]; +static int resize_sample_cursor; +static void resize_sample_cancel(UNUSED void *data) +{ + dialog_destroy(); +} +static void do_resize_sample_aa(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + unsigned int newlen = resize_sample_widgets[0].d.numentry.value; + sample_resize(sample, newlen, 1); +} +static void do_resize_sample(UNUSED void *data) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + unsigned int newlen = resize_sample_widgets[0].d.numentry.value; + sample_resize(sample, newlen, 0); +} +static void resize_sample_draw_const(void) +{ + draw_text("Resize Sample", 34, 24, 3, 2); + draw_text("New Length", 31, 27, 0, 2); + draw_box(41, 26, 49, 28, BOX_THICK | BOX_INNER | BOX_INSET); +} +static void resize_sample_dialog(int aa) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + struct dialog *dialog; + + resize_sample_cursor = 0; + create_numentry(resize_sample_widgets + 0, 42, 27, 7, 0, 1, 1, NULL, 0, 9999999, &resize_sample_cursor); + resize_sample_widgets[0].d.numentry.value = sample->length; + create_button(resize_sample_widgets + 1, 36, 30, 6, 0, 1, 1, 1, 1, resize_sample_cancel, "Cancel", 1); + dialog = dialog_create_custom(26, 22, 29, 11, resize_sample_widgets, 2, 0, resize_sample_draw_const, NULL); + if (aa) { + dialog->action_yes = do_resize_sample_aa; + } else { + dialog->action_yes = do_resize_sample; + } +} + + +/* export sample dialog */ + +static struct widget export_sample_widgets[6]; +static char export_sample_filename[NAME_MAX + 1] = ""; +static char export_sample_options[64] = "fnord"; +static int export_sample_format = 0; + +static void do_export_sample(UNUSED void *data) +{ + sample_save(export_sample_filename, export_sample_format); +} + +static void export_sample_list_draw(void) +{ + int n, focused = (*selected_widget == 3); + + draw_fill_chars(53, 24, 56, 31, 0); + for (n = 0; n < SSMP_SENTINEL; n++) { + int fg = 6, bg = 0; + if (focused && n == export_sample_format) { + fg = 0; + bg = 3; + } else if (n == export_sample_format) { + bg = 14; + } + draw_text_len(sample_save_formats[n].ext, 4, 53, 24 + n, fg, bg); + } +} + +static int export_sample_list_handle_key(struct key_event * k) +{ + int new_format = export_sample_format; + + if (k->state) return 0; + switch (k->sym) { + case SDLK_UP: + if (!NO_MODIFIER(k->mod)) + return 0; + new_format--; + break; + case SDLK_DOWN: + if (!NO_MODIFIER(k->mod)) + return 0; + new_format++; + break; + case SDLK_PAGEUP: + case SDLK_HOME: + if (!NO_MODIFIER(k->mod)) + return 0; + new_format = 0; + break; + case SDLK_PAGEDOWN: + case SDLK_END: + if (!NO_MODIFIER(k->mod)) + return 0; + new_format = SSMP_SENTINEL - 1; + break; + case SDLK_TAB: + if (k->mod & KMOD_SHIFT) { + change_focus_to(0); + return 1; + } + /* fall through */ + case SDLK_LEFT: + case SDLK_RIGHT: + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(0); /* should focus 0/1/2 depending on what's closest */ + return 1; + default: + return 0; + } + + new_format = CLAMP(new_format, 0, SSMP_SENTINEL - 1); + if (new_format != export_sample_format) { + /* update the option string */ + export_sample_format = new_format; + status.flags |= NEED_UPDATE; + } + + return 1; +} + +static void export_sample_draw_const(void) +{ + draw_text("Export Sample", 34, 21, 0, 2); + + draw_text("Filename", 24, 24, 0, 2); + draw_box(32, 23, 51, 25, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_text("Options", 25, 27, 0, 2); + draw_box(32, 26, 51, 28, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_box(52, 23, 57, 32, BOX_THICK | BOX_INNER | BOX_INSET); +} + +static void configure_options(void) +{ + dialog_create(DIALOG_OK, "This doesn't do anything yet. Stay tuned! :)", NULL, NULL, 0, NULL); +} + +static void export_sample_dialog(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + struct dialog *dialog; + + create_textentry(export_sample_widgets + 0, 33, 24, 18, 0, 1, 3, NULL, export_sample_filename, NAME_MAX); + create_textentry(export_sample_widgets + 1, 33, 27, 18, 0, 2, 3, NULL, export_sample_options, 256); + create_button(export_sample_widgets + 2, 33, 30, 9, 1, 4, 3, 3, 3, configure_options, "Configure", 1); + create_other(export_sample_widgets + 3, 0, export_sample_list_handle_key, export_sample_list_draw); + create_button(export_sample_widgets + 4, 31, 35, 6, 2, 4, 5, 5, 5, dialog_yes_NULL, "OK", 3); + create_button(export_sample_widgets + 5, 42, 35, 6, 3, 5, 4, 4, 4, dialog_cancel_NULL, "Cancel", 1); + + strncpy(export_sample_filename, sample->filename, NAME_MAX); + export_sample_filename[NAME_MAX] = 0; + + dialog = dialog_create_custom(21, 20, 39, 18, export_sample_widgets, 6, 0, export_sample_draw_const, NULL); + dialog->action_yes = do_export_sample; +} + +/* --------------------------------------------------------------------- */ + +static void sample_list_handle_alt_key(struct key_event * k) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + + if (k->state) return; + switch (k->sym) { + case SDLK_a: + if (sample->data != NULL) + dialog_create(DIALOG_OK_CANCEL, "Convert sample?", do_sign_convert, NULL, 0, NULL); + return; + case SDLK_b: + /* this statement is too complicated :P */ + if (!(sample->data == NULL + || (sample->flags & SAMP_SUSLOOP && sample->loop_start == 0 && sample->sustain_start == 0) + || (sample->loop_start == 0))) + dialog_create(DIALOG_OK_CANCEL, "Cut sample?", do_pre_loop_cut, NULL, 1, NULL); + return; + case SDLK_d: + dialog_create(DIALOG_OK_CANCEL, "Delete sample?", do_delete_sample, NULL, 1, NULL); + return; + case SDLK_e: + resize_sample_dialog(1); + break; + case SDLK_f: + resize_sample_dialog(0); + break; + case SDLK_g: + if (sample->data == NULL) + return; + sample_reverse(sample); + break; + case SDLK_h: + if (sample->data != NULL) + dialog_create(DIALOG_YES_NO, "Centralise sample?", do_centralise, NULL, 0, NULL); + return; + case SDLK_i: + if (sample->data == NULL) + return; + sample_invert(sample); + break; + case SDLK_l: + if (sample->data != NULL && (sample->loop_end != 0 || sample->sustain_end != 0)) + dialog_create(DIALOG_OK_CANCEL, "Cut sample?", do_post_loop_cut, NULL, 1, NULL); + return; + case SDLK_m: + if (sample->data != NULL) + sample_amplify_dialog(); + return; + case SDLK_n: + song_toggle_multichannel_mode(); + return; + case SDLK_q: + if (sample->data != NULL) + dialog_create(DIALOG_YES_NO, "Convert sample?", + do_quality_convert, do_quality_toggle, 0, NULL); + return; + case SDLK_o: + sample_save(NULL, SSMP_ITS); + return; + case SDLK_s: + swap_sample_dialog(); + return; + case SDLK_t: + export_sample_dialog(); + return; + case SDLK_w: + sample_save(NULL, SSMP_RAW); + return; + case SDLK_x: + exchange_sample_dialog(); + return; + case SDLK_INSERT: + song_insert_sample_slot(current_sample); + status.flags |= NEED_UPDATE; + return; + case SDLK_DELETE: + song_remove_sample_slot(current_sample); + status.flags |= NEED_UPDATE; + return; + default: + return; + } + + status.flags |= NEED_UPDATE; +} + +static inline unsigned long calc_halftone(unsigned long hz, int rel) +{ + /* You wouldn't believe how long it took for me to figure this out. I had to calculate the + logarithmic regression of the values that Impulse Tracker produced and figure out what the + coefficients had to do with the number twelve... I don't imagine I'll forget this formula + now. :) + (FIXME: integer math and all that. Not that I exactly care, since this isn't at all + performance-critical, but in principle it'd be a good idea.) */ + return pow(2, rel / 12.0) * hz + 0.5; +} + +static void sample_list_handle_key(struct key_event * k) +{ + int new_sample = current_sample; + unsigned int newspd; + song_sample *sample = song_get_sample(current_sample, NULL); + + switch (k->sym) { + case SDLK_SPACE: + need_retrigger = last_note; + status.flags |= NEED_UPDATE; + return; + case SDLK_PLUS: + if (k->state) return; + if (k->mod & KMOD_ALT) { + newspd = sample->speed * 2; + song_sample_set_c5speed(current_sample, newspd); + } else if (k->mod & KMOD_CTRL) { + newspd = calc_halftone(sample->speed, 1); + song_sample_set_c5speed(current_sample, newspd); + } + status.flags |= NEED_UPDATE; + return; + case SDLK_MINUS: + if (k->state) return; + if (k->mod & KMOD_ALT) { + newspd = sample->speed / 2; + song_sample_set_c5speed(current_sample, newspd); + } else if (k->mod & KMOD_CTRL) { + newspd = calc_halftone(sample->speed, -1); + song_sample_set_c5speed(current_sample, newspd); + } + status.flags |= NEED_UPDATE; + return; + + case SDLK_COMMA: + case SDLK_LESS: + if (k->state) return; + song_change_current_play_channel(-1, 0); + return; + case SDLK_PERIOD: + case SDLK_GREATER: + if (k->state) return; + song_change_current_play_channel(1, 0); + return; + case SDLK_PAGEUP: + if (k->state) return; + new_sample--; + break; + case SDLK_PAGEDOWN: + if (k->state) return; + new_sample++; + break; + default: + if (k->mod & KMOD_ALT) { + if (k->state) return; + sample_list_handle_alt_key(k); + } else { + int n, v; + if (k->midi_note > -1) { + n = k->midi_note; + if (k->midi_volume > -1) { + v = k->midi_volume / 2; + } else { + v = 64; + } + } else { + n = kbd_get_note(k); + v = 64; + if (n <= 0 || n > 120) + return; + } + if (!k->state && !k->is_repeat) { + last_note = need_retrigger = n; + status.flags |= NEED_UPDATE; + } + } + return; + } + + new_sample = CLAMP(new_sample, 1, 99); + + if (new_sample != current_sample) { + sample_set(new_sample); + sample_list_reposition(); + status.flags |= NEED_UPDATE; + } +} + +/* --------------------------------------------------------------------- */ +/* wheesh */ + +static void sample_list_draw_const(void) +{ + int n; + + draw_box(4, 12, 35, 48, BOX_THICK | BOX_INNER | BOX_INSET); + draw_box(63, 12, 77, 24, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_box(36, 12, 53, 18, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 15, 47, 17, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(36, 19, 53, 25, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 22, 47, 24, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(36, 26, 53, 33, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 29, 47, 32, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(36, 35, 53, 41, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 38, 47, 40, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(36, 42, 53, 48, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(37, 45, 47, 47, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(54, 25, 77, 30, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(54, 31, 77, 41, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(54, 42, 77, 48, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(55, 45, 72, 47, BOX_THIN | BOX_INNER | BOX_INSET); + + draw_fill_chars(41, 30, 46, 30, 0); + draw_fill_chars(64, 13, 76, 23, 0); + + draw_text("Default Volume", 38, 14, 0, 2); + draw_text("Global Volume", 38, 21, 0, 2); + draw_text("Default Pan", 39, 28, 0, 2); + draw_text("Vibrato Speed", 38, 37, 0, 2); + draw_text("Vibrato Depth", 38, 44, 0, 2); + draw_text("Filename", 55, 13, 0, 2); + draw_text("Speed", 58, 14, 0, 2); + draw_text("Loop", 59, 15, 0, 2); + draw_text("LoopBeg", 56, 16, 0, 2); + draw_text("LoopEnd", 56, 17, 0, 2); + draw_text("SusLoop", 56, 18, 0, 2); + draw_text("SusLBeg", 56, 19, 0, 2); + draw_text("SusLEnd", 56, 20, 0, 2); + draw_text("Quality", 56, 22, 0, 2); + draw_text("Length", 57, 23, 0, 2); + draw_text("Vibrato Waveform", 58, 33, 0, 2); + draw_text("Vibrato Rate", 60, 44, 0, 2); + + for (n = 0; n < 13; n++) + draw_char(154, 64 + n, 21, 3, 0); +} + +/* --------------------------------------------------------------------- */ +/* wow. this got ugly. */ + +/* callback for the loop menu toggles */ +static void update_sample_loop_flags(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + + /* these switch statements fall through */ + sample->flags &= ~(SAMP_LOOP | SAMP_LOOP_PINGPONG | SAMP_SUSLOOP | SAMP_SUSLOOP_PINGPONG); + switch (widgets_samplelist[9].d.menutoggle.state) { + case 2: sample->flags |= SAMP_LOOP_PINGPONG; + case 1: sample->flags |= SAMP_LOOP; + } + + switch (widgets_samplelist[12].d.menutoggle.state) { + case 2: sample->flags |= SAMP_SUSLOOP_PINGPONG; + case 1: sample->flags |= SAMP_SUSLOOP; + } + + if (sample->flags & SAMP_LOOP) { + if (sample->loop_start == sample->length) + sample->loop_start = 0; + if (sample->loop_end <= sample->loop_start) + sample->loop_end = sample->length; + } + + if (sample->flags & SAMP_SUSLOOP) { + if (sample->sustain_start == sample->length) + sample->sustain_start = 0; + if (sample->sustain_end <= sample->sustain_start) + sample->sustain_end = sample->length; + } + + /* update any samples currently playing */ + song_update_playing_sample(current_sample); + + status.flags |= NEED_UPDATE; +} + +/* callback for the loop numentries */ +static void update_sample_loop_points(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + int flags_changed = 0; + + /* 9 = loop toggle, 10 = loop start, 11 = loop end */ + if ((unsigned long) widgets_samplelist[10].d.numentry.value > sample->length - 1) + widgets_samplelist[10].d.numentry.value = sample->length - 1; + if (widgets_samplelist[11].d.numentry.value <= widgets_samplelist[10].d.numentry.value) { + widgets_samplelist[9].d.menutoggle.state = 0; + flags_changed = 1; + } else if ((unsigned long) widgets_samplelist[11].d.numentry.value > sample->length) { + widgets_samplelist[11].d.numentry.value = sample->length; + } + if (sample->loop_start != widgets_samplelist[10].d.numentry.value + || sample->loop_end != widgets_samplelist[11].d.numentry.value) { + flags_changed = 1; + } + sample->loop_start = widgets_samplelist[10].d.numentry.value; + sample->loop_end = widgets_samplelist[11].d.numentry.value; + + /* 12 = sus toggle, 13 = sus start, 14 = sus end */ + if ((unsigned long) widgets_samplelist[13].d.numentry.value > sample->length - 1) + widgets_samplelist[13].d.numentry.value = sample->length - 1; + if (widgets_samplelist[14].d.numentry.value <= widgets_samplelist[13].d.numentry.value) { + widgets_samplelist[12].d.menutoggle.state = 0; + flags_changed = 1; + } else if ((unsigned long) widgets_samplelist[14].d.numentry.value > sample->length) { + widgets_samplelist[14].d.numentry.value = sample->length; + } + if (sample->sustain_start != widgets_samplelist[13].d.numentry.value + || sample->sustain_end != widgets_samplelist[14].d.numentry.value) { + flags_changed = 1; + } + sample->sustain_start = widgets_samplelist[13].d.numentry.value; + sample->sustain_end = widgets_samplelist[14].d.numentry.value; + + if (flags_changed) { + update_sample_loop_flags(); + } + + status.flags |= NEED_UPDATE; + /* IT retriggers */ + if (!(sample->flags & (SAMP_LOOP|SAMP_SUSLOOP))) { + need_retrigger = last_note; + status.flags |= NEED_UPDATE; + } +} + +/* --------------------------------------------------------------------- */ + +static void update_values_in_song(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + + /* a few more modplug hacks here... */ + sample->volume = widgets_samplelist[1].d.thumbbar.value * 4; + sample->global_volume = widgets_samplelist[2].d.thumbbar.value; + if (widgets_samplelist[3].d.toggle.state) + sample->flags |= SAMP_PANNING; + else + sample->flags &= ~SAMP_PANNING; + sample->vib_speed = widgets_samplelist[5].d.thumbbar.value; + sample->vib_depth = widgets_samplelist[6].d.thumbbar.value; + + if (widgets_samplelist[15].d.togglebutton.state) + sample->vib_type = VIB_SINE; + else if (widgets_samplelist[16].d.togglebutton.state) + sample->vib_type = VIB_RAMP_DOWN; + else if (widgets_samplelist[17].d.togglebutton.state) + sample->vib_type = VIB_SQUARE; + else + sample->vib_type = VIB_RANDOM; + sample->vib_rate = (widgets_samplelist[19].d.thumbbar.value + 3) / 4; +} + +static void update_sample_speed(void) +{ + song_sample_set_c5speed(current_sample, + widgets_samplelist[8].d.numentry.value); + need_retrigger = last_note; + status.flags |= NEED_UPDATE; +} + +static void update_panning(void) +{ + song_sample *sample = song_get_sample(current_sample, NULL); + + sample->flags |= SAMP_PANNING; + sample->panning = widgets_samplelist[4].d.thumbbar.value * 4; + + widgets_samplelist[3].d.toggle.state = 1; +} + +/* --------------------------------------------------------------------- */ + +void sample_list_load_page(struct page *page) +{ + vgamem_font_reserve(&sample_image); + + page->title = "Sample List (F3)"; + page->draw_const = sample_list_draw_const; + page->predraw_hook = sample_list_predraw_hook; + page->handle_key = sample_list_handle_key; + page->total_widgets = 20; + page->widgets = widgets_samplelist; + page->help_index = HELP_SAMPLE_LIST; + + /* 0 = sample list */ + create_other(widgets_samplelist + 0, 1, sample_list_handle_key_on_list, sample_list_draw_list); + widgets_samplelist[0].x = 5; + widgets_samplelist[0].y = 13; + widgets_samplelist[0].width = 30; + widgets_samplelist[0].height = 35; + + /* 1 -> 6 = middle column */ + create_thumbbar(widgets_samplelist + 1, 38, 16, 9, 1, 2, 7, update_values_in_song, 0, 64); + create_thumbbar(widgets_samplelist + 2, 38, 23, 9, 1, 3, 7, update_values_in_song, 0, 64); + create_toggle(widgets_samplelist + 3, 38, 30, 2, 4, 0, 7, 7, update_values_in_song); + create_thumbbar(widgets_samplelist + 4, 38, 31, 9, 3, 5, 7, update_panning, 0, 64); + create_thumbbar(widgets_samplelist + 5, 38, 39, 9, 4, 6, 15, update_values_in_song, 0, 64); + create_thumbbar(widgets_samplelist + 6, 38, 46, 9, 5, 6, 19, update_values_in_song, 0, 32); + /* 7 -> 14 = top right box */ + create_textentry(widgets_samplelist + 7, 64, 13, 13, 7, 8, 0, NULL, NULL, 12); + create_numentry(widgets_samplelist + 8, 64, 14, 7, 7, 9, 0, + update_sample_speed, 0, 9999999, &sample_numentry_cursor_pos); + create_menutoggle(widgets_samplelist + 9, 64, 15, 8, 10, 1, 0, 0, update_sample_loop_flags, loop_states); + create_numentry(widgets_samplelist + 10, 64, 16, 7, 9, 11, 0, + update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); + create_numentry(widgets_samplelist + 11, 64, 17, 7, 10, 12, 0, + update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); + create_menutoggle(widgets_samplelist + 12, 64, 18, 11, 13, 1, 0, 0, + update_sample_loop_flags, loop_states); + create_numentry(widgets_samplelist + 13, 64, 19, 7, 12, 14, 0, + update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); + create_numentry(widgets_samplelist + 14, 64, 20, 7, 13, 15, 0, + update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); + /* 15 -> 18 = vibrato waveforms */ + create_togglebutton(widgets_samplelist + 15, 57, 36, 6, 14, 17, 5, + 16, 16, update_values_in_song, "\xb9\xba", 3, vibrato_waveforms); + create_togglebutton(widgets_samplelist + 16, 67, 36, 6, 14, 18, 15, + 0, 0, update_values_in_song, "\xbd\xbe", 3, vibrato_waveforms); + create_togglebutton(widgets_samplelist + 17, 57, 39, 6, 15, 19, 5, + 18, 18, update_values_in_song, "\xbb\xbc", 3, vibrato_waveforms); + create_togglebutton(widgets_samplelist + 18, 67, 39, 6, 16, 19, 17, + 0, 0, update_values_in_song, "Random", 1, vibrato_waveforms); + /* 19 = vibrato rate */ + create_thumbbar(widgets_samplelist + 19, 56, 46, 16, 17, 19, 0, update_values_in_song, 0, 255); +} diff --git a/schism/page_vars.c b/schism/page_vars.c new file mode 100644 index 000000000..913c46f35 --- /dev/null +++ b/schism/page_vars.c @@ -0,0 +1,215 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include + +/* --------------------------------------------------------------------- */ +/* static variables */ + +static struct widget widgets_vars[18]; +static int group_control[] = { 8, 9, -1 }; +static int group_playback[] = { 10, 11, -1 }; +static int group_slides[] = { 12, 13, -1 }; + +/* --------------------------------------------------------------------- */ + +static inline void update_song_title(void) +{ + draw_text_len(song_get_title(), 25, 12, 3, 5, 0); + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static void song_vars_draw_const(void) +{ + int n; + + draw_box(16, 15, 43, 17, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(16, 18, 50, 21, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(16, 22, 34, 28, BOX_THIN | BOX_INNER | BOX_INSET); + draw_box(12, 41, 78, 45, BOX_THICK | BOX_INNER | BOX_INSET); + + draw_fill_chars(20, 26, 33, 27, 0); + + draw_text("Song Variables", 33, 13, 3, 2); + draw_text("Song Name", 7, 16, 0, 2); + draw_text("Initial Tempo", 3, 19, 0, 2); + draw_text("Initial Speed", 3, 20, 0, 2); + draw_text("Global Volume", 3, 23, 0, 2); + draw_text("Mixing Volume", 3, 24, 0, 2); + draw_text("Separation", 6, 25, 0, 2); + draw_text("Old Effects", 5, 26, 0, 2); + draw_text("Compatible Gxx", 2, 27, 0, 2); + draw_text("Control", 9, 30, 0, 2); + draw_text("Playback", 8, 33, 0, 2); + draw_text("Pitch Slides", 4, 36, 0, 2); + draw_text("Directories", 34, 40, 3, 2); + draw_text("Module", 6, 42, 0, 2); + draw_text("Sample", 6, 43, 0, 2); + draw_text("Instrument", 2, 44, 0, 2); + + for (n = 1; n < 79; n++) + draw_char(129, n, 39, 1, 2); +} + +/* --------------------------------------------------------------------- */ +static void init_instruments(UNUSED void *data) +{ + song_init_instruments(-1); +} +static void update_values_in_song(void) +{ + song_set_initial_tempo(widgets_vars[1].d.thumbbar.value); + song_set_initial_speed(widgets_vars[2].d.thumbbar.value); + song_set_initial_global_volume(widgets_vars[3].d.thumbbar.value); + song_set_mixing_volume(widgets_vars[4].d.thumbbar.value); + song_set_separation(widgets_vars[5].d.thumbbar.value); + song_set_old_effects(widgets_vars[6].d.toggle.state); + song_set_compatible_gxx(widgets_vars[7].d.toggle.state); + song_set_instrument_mode(widgets_vars[8].d.togglebutton.state); + if (widgets_vars[10].d.togglebutton.state) { + if (!song_is_stereo()) { + song_set_stereo(); + } + } else { + if (song_is_stereo()) { + song_set_mono(); + } + } + song_set_linear_pitch_slides(widgets_vars[12].d.togglebutton.state); + status.flags |= SONG_NEEDS_SAVE; +} +static void maybe_init_instruments(void) +{ + int i; + + update_values_in_song(); + for (i = 1; i < 100; i++) { + if (!song_instrument_is_empty(i)) return; + } + dialog_create(DIALOG_YES_NO, "Initialize instruments?", + init_instruments, NULL, 0, NULL); +} +static void song_changed_cb(void) +{ + widgets_vars[0].d.textentry.text = song_get_title(); + widgets_vars[0].d.textentry.cursor_pos = strlen(widgets_vars[0].d.textentry.text); + + widgets_vars[1].d.thumbbar.value = song_get_initial_tempo(); + widgets_vars[2].d.thumbbar.value = song_get_initial_speed(); + widgets_vars[3].d.thumbbar.value = song_get_initial_global_volume(); + widgets_vars[4].d.thumbbar.value = song_get_mixing_volume(); + widgets_vars[5].d.thumbbar.value = song_get_separation(); + widgets_vars[6].d.toggle.state = song_has_old_effects(); + widgets_vars[7].d.toggle.state = song_has_compatible_gxx(); + + if (song_is_instrument_mode()) + togglebutton_set(widgets_vars, 8, 0); + else + togglebutton_set(widgets_vars, 9, 0); + + if (song_is_stereo()) + togglebutton_set(widgets_vars, 10, 0); + else + togglebutton_set(widgets_vars, 11, 0); + + if (song_has_linear_pitch_slides()) + togglebutton_set(widgets_vars, 12, 0); + else + togglebutton_set(widgets_vars, 13, 0); + + update_song_title(); +} + +/* --------------------------------------------------------------------- */ +/* bleh */ + +static void dir_modules_changed(void) +{ + status.flags |= DIR_MODULES_CHANGED; +} + +static void dir_samples_changed(void) +{ + status.flags |= DIR_SAMPLES_CHANGED; +} + +static void dir_instruments_changed(void) +{ + status.flags |= DIR_INSTRUMENTS_CHANGED; +} + +/* --------------------------------------------------------------------- */ + +void song_vars_load_page(struct page *page) +{ + page->title = "Song Variables & Directory Configuration (F12)"; + page->draw_const = song_vars_draw_const; + page->song_changed_cb = song_changed_cb; + page->total_widgets = 18; + page->widgets = widgets_vars; + page->help_index = HELP_GLOBAL; + + /* 0 = song name */ + create_textentry(widgets_vars, 17, 16, 26, 0, 1, 1, update_song_title, song_get_title(), 25); + /* 1 = tempo */ + create_thumbbar(widgets_vars + 1, 17, 19, 33, 0, 2, 2, update_values_in_song, 31, 255); + /* 2 = speed */ + create_thumbbar(widgets_vars + 2, 17, 20, 33, 1, 3, 3, update_values_in_song, 1, 255); + /* 3 = global volume */ + create_thumbbar(widgets_vars + 3, 17, 23, 17, 2, 4, 4, update_values_in_song, 0, 128); + /* 4 = mixing volume */ + create_thumbbar(widgets_vars + 4, 17, 24, 17, 3, 5, 5, update_values_in_song, 0, 128); + /* 5 = separation */ + create_thumbbar(widgets_vars + 5, 17, 25, 17, 4, 6, 6, update_values_in_song, 0, 128); + /* 6 = old effects */ + create_toggle(widgets_vars + 6, 17, 26, 5, 7, 5, 7, 7, update_values_in_song); + /* 7 = compatible gxx */ + create_toggle(widgets_vars + 7, 17, 27, 6, 8, 6, 8, 8, update_values_in_song); + /* 8-13 = switches */ + create_togglebutton(widgets_vars + 8, 17, 30, 11, 7, 10, 9, 9, 9, maybe_init_instruments, + "Instruments", 1, group_control); + create_togglebutton(widgets_vars + 9, 32, 30, 11, 7, 11, 8, 8, 8, update_values_in_song, + "Samples", 1, group_control); + create_togglebutton(widgets_vars + 10, 17, 33, 11, 8, 12, 11, 11, 11, update_values_in_song, + "Stereo", 1, group_playback); + create_togglebutton(widgets_vars + 11, 32, 33, 11, 9, 13, 10, 10, 10, update_values_in_song, + "Mono", 1, group_playback); + create_togglebutton(widgets_vars + 12, 17, 36, 11, 10, 14, 13, 13, 13, update_values_in_song, + "Linear", 1, group_slides); + create_togglebutton(widgets_vars + 13, 32, 36, 11, 11, 14, 12, 12, 12, update_values_in_song, + "Amiga", 1, group_slides); + /* 14-16 = directories */ + create_textentry(widgets_vars + 14, 13, 42, 65, 12, 15, 15, dir_modules_changed, + cfg_dir_modules, PATH_MAX); + create_textentry(widgets_vars + 15, 13, 43, 65, 14, 16, 16, dir_samples_changed, + cfg_dir_samples, PATH_MAX); + create_textentry(widgets_vars + 16, 13, 44, 65, 15, 17, 17, dir_instruments_changed, + cfg_dir_instruments, PATH_MAX); + /* 17 = save all preferences */ + create_button(widgets_vars + 17, 28, 47, 22, 16, 17, 17, 17, 17, cfg_save, "Save all Preferences", 2); +} diff --git a/schism/pattern-view.c b/schism/pattern-view.c new file mode 100644 index 000000000..f4f8d2a50 --- /dev/null +++ b/schism/pattern-view.c @@ -0,0 +1,625 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "pattern-view.h" + +/* this stuff's ugly */ + +/* --------------------------------------------------------------------- */ +/* 13-column track view */ + +void draw_channel_header_13(int chan, int x, int y, int fg) +{ + byte buf[16] = " Channel 42 "; + + buf[9] = '0' + chan / 10; + buf[10] = '0' + chan % 10; + draw_text(buf, x, y, fg, 1); +} + +void draw_note_13(int x, int y, song_note * note, int cursor_pos, int fg, + int bg) +{ + int cursor_pos_map[9] = { 0, 2, 4, 5, 7, 8, 10, 11, 12 }; + byte note_text[16], note_buf[4], vol_buf[4]; + + get_note_string(note->note, note_buf); + get_volume_string(note->volume, note->volume_effect, vol_buf); + + /* come to think of it, maybe the instrument text should be + * created the same way as the volume. */ + if (note->instrument) + snprintf(note_text, 16, "%s %02d %s %c%02X", + note_buf, note->instrument % 100, vol_buf, + get_effect_char(note->effect), note->parameter); + else + snprintf(note_text, 16, "%s \xad\xad %s %c%02X", + note_buf, vol_buf, get_effect_char(note->effect), + note->parameter); + + if (show_default_volumes && note->volume_effect == VOL_EFFECT_NONE && note->instrument > 0) { + /* Modplug-specific hack: volume bit shift */ + int n = song_get_sample(note->instrument, NULL)->volume >> 2; + note_text[6] = 0xbf; + note_text[7] = '0' + n / 10 % 10; + note_text[8] = '0' + n / 1 % 10; + note_text[9] = 0xc0; + } + + draw_text(note_text, x, y, fg, bg); + + /* lazy coding here: the panning is written twice, or if the + * cursor's on it, *three* times. */ + if (note->volume_effect == VOL_EFFECT_PANNING) + draw_text(vol_buf, x + 7, y, 2, bg); + + if (cursor_pos >= 0) { + cursor_pos = cursor_pos_map[cursor_pos]; + draw_char(note_text[cursor_pos], x + cursor_pos, y, 0, 3); + } +} + +/* --------------------------------------------------------------------- */ +/* 10-column track view */ + +void draw_channel_header_10(int chan, int x, int y, int fg) +{ + byte buf[16] = "Channel 42"; + + buf[8] = '0' + chan / 10; + buf[9] = '0' + chan % 10; + draw_text(buf, x, y, fg, 1); +} + +void draw_note_10(int x, int y, song_note * note, int cursor_pos, + UNUSED int fg, int bg) +{ + byte c, note_buf[4], ins_buf[3], vol_buf[3], effect_buf[4]; + + get_note_string(note->note, note_buf); + if (note->instrument) { + numtostr(2, note->instrument, ins_buf); + } else { + ins_buf[0] = ins_buf[1] = 173; + ins_buf[2] = 0; + } + get_volume_string(note->volume, note->volume_effect, vol_buf); + sprintf(effect_buf, "%c%02X", get_effect_char(note->effect), + note->parameter); + + draw_text(note_buf, x, y, 6, bg); + draw_text(ins_buf, x + 3, y, note->instrument ? 10 : 2, bg); + draw_text(vol_buf, x + 5, y, ((note->volume_effect == VOL_EFFECT_PANNING) ? 2 : 6), bg); + draw_text(effect_buf, x + 7, y, 2, bg); + + if (cursor_pos < 0) + return; + if (cursor_pos > 0) + cursor_pos++; + /* *INDENT-OFF* */ + switch (cursor_pos) { + case 0: c = note_buf[0]; break; + case 2: c = note_buf[2]; break; + case 3: c = ins_buf[0]; break; + case 4: c = ins_buf[1]; break; + case 5: c = vol_buf[0]; break; + case 6: c = vol_buf[1]; break; + default: /* 7->9 */ + c = effect_buf[cursor_pos - 7]; + break; + } + /* *INDENT-ON* */ + draw_char(c, x + cursor_pos, y, 0, 3); +} + +/* --------------------------------------------------------------------- */ +/* 7-column track view */ + +void draw_channel_header_7(int chan, int x, int y, int fg) +{ + byte buf[8] = "Chnl 42"; + + buf[5] = '0' + chan / 10; + buf[6] = '0' + chan % 10; + draw_text(buf, x, y, fg, 1); +} + +void draw_note_7(int x, int y, song_note * note, int cursor_pos, + UNUSED int fg, int bg) +{ + byte note_buf[4], ins_buf[3], vol_buf[3]; + int fg1, bg1, fg2, bg2; + + get_note_string(note->note, note_buf); + if (note->instrument) + numtostr(2, note->instrument, ins_buf); + else + ins_buf[0] = ins_buf[1] = 173; + get_volume_string(note->volume, note->volume_effect, vol_buf); + + /* note & instrument */ + draw_text(note_buf, x, y, 6, bg); + fg1 = fg2 = (note->instrument ? 10 : 2); + bg1 = bg2 = bg; + switch (cursor_pos) { + case 0: + draw_char(note_buf[0], x, y, 0, 3); + break; + case 1: + draw_char(note_buf[2], x + 2, y, 0, 3); + break; + case 2: + fg1 = 0; + bg1 = 3; + break; + case 3: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(ins_buf[0], ins_buf[1], x + 3, y, fg1, bg1, + fg2, bg2); + + /* volume */ + switch (note->volume_effect) { + case VOL_EFFECT_NONE: + fg1 = 6; + break; + case VOL_EFFECT_PANNING: + fg1 = 10; + break; + case VOL_EFFECT_TONEPORTAMENTO: + case VOL_EFFECT_VIBRATOSPEED: + case VOL_EFFECT_VIBRATO: + /* for whatever reason, Impulse Tracker uses color 10 for + * Gx and Hx... bug? */ + fg1 = (status.flags & CLASSIC_MODE) ? 10 : 12; + break; + default: + fg1 = 12; + break; + } + fg2 = fg1; + bg1 = bg2 = bg; + + switch (cursor_pos) { + case 4: + fg1 = 0; + bg1 = 3; + break; + case 5: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(vol_buf[0], vol_buf[1], x + 4, y, fg1, bg1, fg2, bg2); + + /* effect */ + draw_char(get_effect_char(note->effect), x + 5, y, + (cursor_pos == 6) ? 0 : 2, (cursor_pos == 6) ? 3 : bg); + + /* effect value */ + fg1 = fg2 = 10; + bg1 = bg2 = bg; + switch (cursor_pos) { + case 7: + fg1 = 0; + bg1 = 3; + break; + case 8: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(hexdigits[(note->parameter & 0xf0) >> 4], + hexdigits[note->parameter & 0xf], + x + 6, y, fg1, bg1, fg2, bg2); +} + +/* --------------------------------------------------------------------- */ +/* 3-column track view */ + +void draw_channel_header_3(int chan, int x, int y, int fg) +{ + byte buf[4] = { ' ', '0' + chan / 10, '0' + chan % 10, '\0' }; + + draw_text(buf, x, y, fg, 1); +} + +void draw_note_3(int x, int y, song_note * note, int cursor_pos, int fg, int bg) +{ + byte buf[4]; + + switch (cursor_pos) { + case 0: + fg = 0; + bg = 3; + break; + case 1: + get_note_string(note->note, buf); + draw_text(buf, x, y, 6, bg); + draw_char(buf[2], x + 2, y, 0, 3); + return; + case 2: + case 3: + cursor_pos -= 1; + buf[0] = ' '; + if (note->instrument) { + numtostr(2, note->instrument, buf + 1); + } else { + buf[1] = buf[2] = 173; + buf[3] = 0; + } + draw_text(buf, x, y, 6, bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + case 4: + case 5: + cursor_pos -= 3; + buf[0] = ' '; + get_volume_string(note->volume, note->volume_effect, buf + 1); + draw_text(buf, x, y, ((note->volume_effect == VOL_EFFECT_PANNING) ? 1 : 6), bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + case 6: + case 7: + case 8: + cursor_pos -= 6; + sprintf(buf, "%c%02X", get_effect_char(note->effect), note->parameter); + draw_text(buf, x, y, 2, bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + default: + /* bleh */ + fg = 6; + break; + } + + if (note->note) { + get_note_string(note->note, buf); + draw_text(buf, x, y, fg, bg); + } else if (note->instrument) { + buf[0] = ' '; + numtostr(2, note->instrument, buf + 1); + draw_text(buf, x, y, fg, bg); + } else if (note->volume_effect) { + if (cursor_pos != 0 && note->volume_effect == VOL_EFFECT_PANNING) + fg = 1; + buf[0] = ' '; + get_volume_string(note->volume, note->volume_effect, buf + 1); + draw_text(buf, x, y, fg, bg); + } else if (note->effect || note->parameter) { + if (cursor_pos != 0) + fg = 2; + sprintf(buf, "%c%02X", get_effect_char(note->effect), note->parameter); + draw_text(buf, x, y, fg, bg); + } else { + buf[0] = buf[1] = buf[2] = 173; + buf[3] = 0; + draw_text(buf, x, y, fg, bg); + } +} + +/* --------------------------------------------------------------------- */ +/* 2-column track view */ + +void draw_channel_header_2(int chan, int x, int y, int fg) +{ + byte buf[4] = { '0' + chan / 10, '0' + chan % 10, 0 }; + + draw_text(buf, x, y, fg, 1); +} + +static void draw_effect_2(int x, int y, song_note * note, int cursor_pos, int bg) +{ + int fg = 2, fg1 = 10, fg2 = 10, bg1 = bg, bg2 = bg; + + switch (cursor_pos) { + case 0: + fg = fg1 = fg2 = 0; + break; + case 6: + fg = 0; + bg = 3; + break; + case 7: + fg1 = 0; + bg1 = 3; + break; + case 8: + fg2 = 0; + bg2 = 3; + break; + } + draw_char(get_effect_char(note->effect), x, y, fg, bg); + draw_half_width_chars(hexdigits[(note->parameter & 0xf0) >> 4], + hexdigits[note->parameter & 0xf], + x + 1, y, fg1, bg1, fg2, bg2); +} + +void draw_note_2(int x, int y, song_note * note, int cursor_pos, int fg, int bg) +{ + byte buf[4]; + + switch (cursor_pos) { + case 0: + fg = 0; + bg = 3; + break; + case 1: + get_note_string_short(note->note, buf); + draw_char(buf[0], x, y, 6, bg); + draw_char(buf[1], x + 1, y, 0, 3); + return; + case 2: + case 3: + cursor_pos -= 2; + if (note->instrument) { + numtostr(2, note->instrument, buf); + } else { + buf[0] = buf[1] = 173; + buf[2] = 0; + } + draw_text(buf, x, y, 6, bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + case 4: + case 5: + cursor_pos -= 4; + get_volume_string(note->volume, note->volume_effect, buf); + draw_text(buf, x, y, ((note->volume_effect == VOL_EFFECT_PANNING) ? 1 : 6), bg); + draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); + return; + case 6: + case 7: + case 8: + draw_effect_2(x, y, note, cursor_pos, bg); + return; + default: + /* bleh */ + fg = 6; + break; + } + + if (note->note) { + get_note_string_short(note->note, buf); + draw_text(buf, x, y, fg, bg); + } else if (note->instrument) { + numtostr(2, note->instrument, buf); + draw_text(buf, x, y, fg, bg); + } else if (note->volume_effect) { + if (cursor_pos != 0 && note->volume_effect == VOL_EFFECT_PANNING) + fg = 1; + get_volume_string(note->volume, note->volume_effect, buf); + draw_text(buf, x, y, fg, bg); + } else if (note->effect || note->parameter) { + draw_effect_2(x, y, note, cursor_pos, bg); + } else { + draw_char(173, x, y, fg, bg); + draw_char(173, x + 1, y, fg, bg); + } +} + +/* --------------------------------------------------------------------- */ +/* 1-column track view... useful to look at, not so much to edit. + * (in fact, impulse tracker doesn't edit with this view) */ + +void draw_channel_header_1(int chan, int x, int y, int fg) +{ + draw_half_width_chars('0' + chan / 10, '0' + chan % 10, x, y, fg, 1, fg, 1); +} + +static void draw_effect_1(int x, int y, song_note * note, int cursor_pos, int fg, int bg) +{ + int fg1 = fg, fg2 = fg, bg1 = bg, bg2 = bg; + + switch (cursor_pos) { + case 0: + break; + case 6: + fg = 0; + bg = 3; + break; + case 7: + fg1 = 0; + bg1 = 3; + break; + case 8: + fg2 = 0; + bg2 = 3; + break; + default: + fg = 2; + } + if (cursor_pos == 7 || cursor_pos == 8 || (note->effect == 0 && note->parameter != 0)) { + draw_half_width_chars(hexdigits[(note->parameter & 0xf0) >> 4], + hexdigits[note-> parameter & 0xf], + x, y, fg1, bg1, fg2, bg2); + } else { + draw_char(get_effect_char(note->effect), x, y, fg, bg); + } +} + +void draw_note_1(int x, int y, song_note * note, int cursor_pos, int fg, int bg) +{ + byte buf[4]; + + switch (cursor_pos) { + case 0: + fg = 0; + bg = 3; + if (note->note > 0 && note->note <= 120) { + get_note_string_short(note->note, buf); + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); + return; + } + break; + case 1: + get_note_string_short(note->note, buf); + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, 0, 3); + return; + case 2: + case 3: + cursor_pos -= 2; + if (note->instrument) + numtostr(2, note->instrument, buf); + else + buf[0] = buf[1] = 173; + if (cursor_pos == 0) + draw_half_width_chars(buf[0], buf[1], x, y, 0, 3, fg, bg); + else + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, 0, 3); + return; + case 4: + case 5: + cursor_pos -= 4; + get_volume_string(note->volume, note->volume_effect, buf); + fg = note->volume_effect == VOL_EFFECT_PANNING ? 1 : 2; + if (cursor_pos == 0) + draw_half_width_chars(buf[0], buf[1], x, y, 0, 3, fg, bg); + else + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, 0, 3); + return; + case 6: + case 7: + case 8: + draw_effect_1(x, y, note, cursor_pos, fg, bg); + return; + } + + if (note->note) { + get_note_string_short(note->note, buf); + draw_char(buf[0], x, y, fg, bg); + } else if (note->instrument) { + numtostr(2, note->instrument, buf); + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); + } else if (note->volume_effect) { + if (cursor_pos != 0) + fg = (note->volume_effect == VOL_EFFECT_PANNING) ? 1 : 2; + get_volume_string(note->volume, note->volume_effect, buf); + draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); + } else if (note->effect || note->parameter) { + draw_effect_1(x, y, note, cursor_pos, fg, bg); + } else { + draw_char(173, x, y, fg, bg); + } +} + +/* --------------------------------------------------------------------- */ +/* 6-column track view (totally new!) */ + +void draw_channel_header_6(int chan, int x, int y, int fg) +{ + byte buf[8] = "Chnl42"; + + buf[4] = '0' + chan / 10; + buf[5] = '0' + chan % 10; + draw_text(buf, x, y, fg, 1); +} + +void draw_note_6(int x, int y, song_note * note, int cursor_pos, UNUSED int fg, int bg) +{ + byte note_buf[4], ins_buf[3], vol_buf[3]; + int fg1, bg1, fg2, bg2; + + get_note_string_short(note->note, note_buf); + if (note->instrument) + numtostr(2, note->instrument, ins_buf); + else + ins_buf[0] = ins_buf[1] = 173; + get_volume_string(note->volume, note->volume_effect, vol_buf); + + /* note & instrument */ + draw_text(note_buf, x, y, 6, bg); + fg1 = fg2 = (note->instrument ? 10 : 2); + bg1 = bg2 = bg; + switch (cursor_pos) { + case 0: + draw_char(note_buf[0], x, y, 0, 3); + break; + case 1: + draw_char(note_buf[1], x + 1, y, 0, 3); + break; + case 2: + fg1 = 0; + bg1 = 3; + break; + case 3: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(ins_buf[0], ins_buf[1], x + 2, y, fg1, bg1, fg2, bg2); + + /* volume */ + switch (note->volume_effect) { + case VOL_EFFECT_NONE: + fg1 = 6; + break; + case VOL_EFFECT_PANNING: + case VOL_EFFECT_TONEPORTAMENTO: + case VOL_EFFECT_VIBRATOSPEED: + case VOL_EFFECT_VIBRATO: + fg1 = 10; + break; + default: + fg1 = 12; + break; + } + fg2 = fg1; + bg1 = bg2 = bg; + + switch (cursor_pos) { + case 4: + fg1 = 0; + bg1 = 3; + break; + case 5: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(vol_buf[0], vol_buf[1], x + 3, y, fg1, bg1, fg2, bg2); + + /* effect */ + draw_char(get_effect_char(note->effect), x + 4, y, + cursor_pos == 6 ? 0 : 2, cursor_pos == 6 ? 3 : bg); + + /* effect value */ + fg1 = fg2 = 10; + bg1 = bg2 = bg; + switch (cursor_pos) { + case 7: + fg1 = 0; + bg1 = 3; + break; + case 8: + fg2 = 0; + bg2 = 3; + break; + } + draw_half_width_chars(hexdigits[(note->parameter & 0xf0) >> 4], + hexdigits[note->parameter & 0xf], + x + 5, y, fg1, bg1, fg2, bg2); +} diff --git a/schism/realpath.c b/schism/realpath.c new file mode 100644 index 000000000..3d930579f --- /dev/null +++ b/schism/realpath.c @@ -0,0 +1,9 @@ +#include + +/* This is a mess that only kind of works. */ +char *realpath(const char *path, char *resolved_path); +char *realpath(const char *path, char *resolved_path) +{ + strcpy(resolved_path, path); + return resolved_path; +} diff --git a/schism/sample-edit.c b/schism/sample-edit.c new file mode 100644 index 000000000..cf9b2b950 --- /dev/null +++ b/schism/sample-edit.c @@ -0,0 +1,598 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "util.h" +#include "song.h" +#include "sample-edit.h" + +#include + +/* --------------------------------------------------------------------- */ +/* helper functions */ + +static void _minmax_8(signed char *data, unsigned long length, signed char *min, signed char *max) +{ + unsigned long pos = length; + + *min = 127; + *max = -128; + while (pos) { + pos--; + if (data[pos] < *min) + *min = data[pos]; + else if (data[pos] > *max) + *max = data[pos]; + } +} + +static void _minmax_16(signed short *data, unsigned long length, signed short *min, signed short *max) +{ + unsigned long pos = length; + + *min = 32767; + *max = -32768; + while (pos) { + pos--; + if (data[pos] < *min) + *min = data[pos]; + else if (data[pos] > *max) + *max = data[pos]; + } +} + +/* --------------------------------------------------------------------- */ +/* sign convert (a.k.a. amiga flip) */ + +static void _sign_convert_8(signed char *data, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + data[pos] ^= 128; + } +} + +static void _sign_convert_16(signed short *data, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + data[pos] ^= 32768; + } +} + +void sample_sign_convert(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _sign_convert_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _sign_convert_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* i don't think this is being done correctly :/ */ + +static void _reverse_8(signed char *data, unsigned long length) +{ + signed char tmp; + unsigned long lpos = 0, rpos = length - 1; + + while (lpos < rpos) { + tmp = data[lpos]; + data[lpos] = data[rpos]; + data[rpos] = tmp; + lpos++; + rpos--; + } +} + +static void _reverse_16(signed short *data, unsigned long length) +{ + signed short tmp; + unsigned long lpos = 0, rpos = length - 1; + + while (lpos < rpos) { + tmp = data[lpos]; + data[lpos] = data[rpos]; + data[rpos] = tmp; + lpos++; + rpos--; + } +} + +static void _rstereo_8(signed char *data, unsigned long length) +{ + unsigned long i; + signed char tmp; + + length <<= 1; + for (i = 0; i < length; i += 2) { + tmp = data[i]; + data[i] = data[i+1]; + data[i+1] = tmp; + } +} +static void _rstereo_16(signed short *data, unsigned long length) +{ + unsigned long i; + signed short tmp; + + length <<= 1; + for (i = 0; i < length; i += 2) { + tmp = data[i]; + data[i] = data[i+1]; + data[i+1] = tmp; + } +} +void sample_reverse(song_sample * sample) +{ + unsigned long tmp; + + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _reverse_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _reverse_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + + if (sample->flags & SAMP_STEREO) { + if (sample->flags & SAMP_16_BIT) + _rstereo_16((signed short *)sample->data, sample->length); + else + _rstereo_8(sample->data, sample->length); + } + + tmp = sample->length - sample->loop_start; + sample->loop_start = sample->length - sample->loop_end; + sample->loop_end = tmp; + + tmp = sample->length - sample->sustain_start; + sample->sustain_start = sample->length - sample->sustain_end; + sample->sustain_end = tmp; + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ + +/* if convert_data is nonzero, the sample data is modified (so it sounds + * the same); otherwise, the sample length is changed and the data is + * left untouched. + * this is irrelevant, as i haven't gotten to writing the convert stuff + * yet. (not that it's hard, i just haven't gotten to it.) */ + +static void _quality_convert_8to16(signed char *idata, signed short *odata, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + odata[pos] = idata[pos] << 8; + } +} + +static void _quality_convert_16to8(signed short *idata, signed char *odata, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + odata[pos] = idata[pos] >> 8; + } +} + +void sample_toggle_quality(song_sample * sample, int convert_data) +{ + song_lock_audio(); + sample->flags ^= SAMP_16_BIT; + + status.flags |= SONG_NEEDS_SAVE; + if (convert_data) { + signed char *odata; + + if (sample->flags & SAMP_16_BIT) { + odata = song_sample_allocate(2 * sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + _quality_convert_8to16(sample->data, (signed short *) odata, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + } else { + odata = song_sample_allocate(sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + _quality_convert_16to8((signed short *) sample->data, odata, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + } + song_sample_free(sample->data); + sample->data = odata; + } else { + if (sample->flags & SAMP_16_BIT) { + sample->length >>= 1; + sample->loop_start >>= 1; + sample->loop_end >>= 1; + sample->sustain_start >>= 1; + sample->sustain_end >>= 1; + } else { + sample->length <<= 1; + sample->loop_start <<= 1; + sample->loop_end <<= 1; + sample->sustain_start <<= 1; + sample->sustain_end <<= 1; + } + } + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* centralise (correct dc offset) */ + +static void _centralise_8(signed char *data, unsigned long length) +{ + unsigned long pos = length; + signed char min, max; + int offset; + + _minmax_8(data, length, &min, &max); + + offset = (max + min + 1) >> 1; + if (offset == 0) + return; + + pos = length; + while (pos) { + pos--; + data[pos] -= offset; + } +} + +static void _centralise_16(signed short *data, unsigned long length) +{ + unsigned long pos = length; + signed short min, max; + int offset; + + _minmax_16(data, length, &min, &max); + + while (pos) { + pos--; + if (data[pos] < min) + min = data[pos]; + else if (data[pos] > max) + max = data[pos]; + } + + offset = (max + min + 1) >> 1; + if (offset == 0) + return; + + pos = length; + while (pos) { + pos--; + data[pos] -= offset; + } +} + +void sample_centralise(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _centralise_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _centralise_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* amplify (or attenuate) */ + +static void _amplify_8(signed char *data, unsigned long length, int percent) +{ + unsigned long pos = length; + int b; + + while (pos) { + pos--; + b = data[pos] * percent / 100; + data[pos] = CLAMP(b, -128, 127); + } +} + +static void _amplify_16(signed short *data, unsigned long length, int percent) +{ + unsigned long pos = length; + int b; + + while (pos) { + pos--; + b = data[pos] * percent / 100; + data[pos] = CLAMP(b, -32768, 32767); + } +} + +void sample_amplify(song_sample * sample, int percent) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _amplify_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1), percent); + else + _amplify_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1), percent); + song_unlock_audio(); +} + +static int _get_amplify_8(signed char *data, unsigned long length) +{ + signed char min, max; + _minmax_8(data, length, &min, &max); + if (min == 0 && max == 0) + return 100; + return 128 * 100 / MAX(max, -min); +} + +static int _get_amplify_16(signed short *data, unsigned long length) +{ + signed short min, max; + _minmax_16(data, length, &min, &max); + if (min == 0 && max == 0) + return 100; + return 32768 * 100 / MAX(max, -min); +} + +int sample_get_amplify_amount(song_sample *sample) +{ + int percent; + + if (sample->flags & SAMP_16_BIT) + percent = _get_amplify_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + percent = _get_amplify_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + + if (percent < 100) { + /* shouldn't happen */ + printf("sample_get_amplify_amount: percent < 100. why?\n"); + percent = 100; + } + return percent; +} + +/* --------------------------------------------------------------------- */ +/* useful for importing delta-encoded raw data */ + +static void _delta_decode_8(signed char *data, unsigned long length) +{ + unsigned long pos; + signed char o = 0, n; + + for (pos = 1; pos < length; pos++) { + n = data[pos] + o; + data[pos] = n; + o = n; + } +} + +static void _delta_decode_16(signed short *data, unsigned long length) +{ + unsigned long pos; + signed short o = 0, n; + + for (pos = 1; pos < length; pos++) { + n = data[pos] + o; + data[pos] = n; + o = n; + } +} + +void sample_delta_decode(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _delta_decode_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _delta_decode_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* surround flipping (probably useless with the S91 effect, but why not) */ + +static void _invert_8(signed char *data, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + data[pos] = ~data[pos]; + } +} + +static void _invert_16(signed short *data, unsigned long length) +{ + unsigned long pos = length; + + while (pos) { + pos--; + data[pos] = ~data[pos]; + } +} + +static void _resize_16(signed short *dst, unsigned long newlen, + signed short *src, unsigned int oldlen) +{ + unsigned int i; + for (i = 0; i < newlen; i++) dst[i] = src[i * ((unsigned long long)oldlen / (unsigned long long)newlen)]; +} +static void _resize_8(signed char *dst, unsigned long newlen, + signed char *src, unsigned int oldlen) +{ + unsigned int i; + for (i = 0; i < newlen; i++) dst[i] = src[i * ((unsigned long long)oldlen / (unsigned long long)newlen)]; +} +static void _resize_8aa(signed char *dst, unsigned long newlen, + signed char *src, unsigned int oldlen) +{ + int avg_acc = 0; + int avg_count = 0; + int i, j, cp; + int old_pos = -1; + for (i = 0; i < oldlen; i++) { + cp = (unsigned long long)i * ((unsigned long long)newlen) + / (unsigned long long)oldlen; + if (cp > old_pos) { + if (old_pos >= 0 && cp >= 0 && cp < newlen) { + for (j = 0; j < (cp-old_pos); j++) { + dst[old_pos+j] = avg_acc/avg_count; + } + } + avg_count = 0; + avg_acc = 0; + old_pos = cp; + } + avg_count ++; + avg_acc += src[i]; + } + for (j = 0; j < (newlen-old_pos); j++) { + dst[old_pos+j] = avg_acc/avg_count; + } +} +static void _resize_16aa(signed short *dst, unsigned long newlen, + signed short *src, unsigned int oldlen) +{ + int avg_acc = 0; + int avg_count = 0; + int i, j, cp; + int old_pos = -1; + for (i = 0; i < oldlen; i++) { + cp = (unsigned long long)i * ((unsigned long long)newlen) + / (unsigned long long)oldlen; + if (cp > old_pos) { + if (old_pos >= 0 && cp >= 0 && cp < newlen) { + for (j = 0; j < (cp-old_pos); j++) { + dst[old_pos+j] = avg_acc/avg_count; + } + } + avg_count = 0; + avg_acc = 0; + old_pos = cp; + } + avg_count ++; + avg_acc += src[i]; + } +} + + + +void sample_resize(song_sample * sample, unsigned long newlen, int aa) +{ + int bps; + unsigned char *d, *z; + unsigned long oldlen; + + if (!newlen) return; + if (!sample->data || !sample->length) return; + + song_lock_audio(); + bps = (((sample->flags & SAMP_STEREO) ? 2 : 1) + * ((sample->flags & SAMP_16_BIT) ? 2 : 1)); + + status.flags |= SONG_NEEDS_SAVE; + + d = song_sample_allocate(newlen*bps); + z = sample->data; + + sample->speed = (unsigned long)((((double)newlen) * ((double)sample->speed)) + / ((double)sample->length)); + + if (sample->flags & SAMP_STEREO) newlen *= 2; + oldlen = (sample->flags & SAMP_STEREO) ? sample->length * 2 + : sample->length; + if (sample->flags & SAMP_16_BIT) { + if (aa) { + _resize_16aa(d, newlen, (unsigned short *)sample->data, oldlen); + } else { + _resize_16(d, newlen, (unsigned short *)sample->data, oldlen); + } + } else { + if (aa) { + _resize_8aa(d, newlen, sample->data, oldlen); + } else { + _resize_8(d, newlen, sample->data, oldlen); + } + } + + sample->length = newlen; + sample->data = d; + song_sample_free(z); + song_unlock_audio(); +} + +void sample_invert(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_16_BIT) + _invert_16((signed short *) sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + else + _invert_8(sample->data, sample->length * ((sample->flags & SAMP_STEREO) ? 2 : 1)); + song_unlock_audio(); +} + +static void _mono_lr16(signed char *data, unsigned long length, int shift) +{ + unsigned long i, j; + if (shift) memmove(data, data+shift+shift, (length-shift)-shift); + for (j = 0, i = 1; j < length; j++, i += 2) + data[j] = data[i]; +} +static void _mono_lr8(signed char *data, unsigned long length, int shift) +{ + unsigned long i, j; + if (shift) memmove(data, data+shift, length-shift); + for (j = 0, i = 1; j < length; j++, i += 2) + data[j] = data[i]; +} +void sample_mono_left(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_STEREO) { + if (sample->flags & SAMP_16_BIT) + _mono_lr16((signed short *)sample->data, sample->length*2, 1); + else + _mono_lr8((signed short *)sample->data, sample->length*2, 1); + sample->flags &= ~SAMP_STEREO; + } + song_unlock_audio(); +} +void sample_mono_right(song_sample * sample) +{ + song_lock_audio(); + status.flags |= SONG_NEEDS_SAVE; + if (sample->flags & SAMP_STEREO) { + if (sample->flags & SAMP_16_BIT) + _mono_lr16((signed short *)sample->data, sample->length*2, 0); + else + _mono_lr8((signed short *)sample->data, sample->length*2, 0); + sample->flags &= ~SAMP_STEREO; + } + song_unlock_audio(); +} diff --git a/schism/sample-view.c b/schism/sample-view.c new file mode 100644 index 000000000..40c7ff366 --- /dev/null +++ b/schism/sample-view.c @@ -0,0 +1,234 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" +#include "video.h" + +#include + +/* --------------------------------------------------------------------- */ +/* sample drawing +there are only two changes between 8- and 16-bit samples: +- the type of 'data' +- the amount to divide (note though, this number is used twice!) */ + +static void _draw_sample_data_8(struct vgamem_overlay *r, + signed char *data, unsigned long length, unsigned int channels) // 8/16 +{ + unsigned long pos; + int level, xs, ys, xe, ye, step; + + level = data[0] * r->height / (SCHAR_MAX - SCHAR_MIN + 1); // 8/16 + xs = 0; + ys = (r->height / 2 - 1) - level; + step = MAX(1, length / (r->width << 8)); + + for (pos = 0; pos < length; pos += step) { + level = data[pos] * r->height / (SCHAR_MAX - SCHAR_MIN + 1); // 8/16 + xe = pos * r->width / length; + ye = (r->height / 2 - 1) - level; + if (xs == ys && xe == ye) + continue; + vgamem_font_drawline(r, xs, ys, xe, ye); + xs = xe; + ys = ye; + } +} + +static void _draw_sample_data_16(struct vgamem_overlay *r, + signed short *data, unsigned long length, unsigned int channels) +{ + unsigned long pos; + int level, xs, ys, xe, ye, step; + + level = (data[0] * r->height) / (SHRT_MAX - SHRT_MIN + 1); + xs = 0; + ys = (r->height / 2 - 1) - level; + step = MAX(1, length / (r->width << 8)); + for (pos = 0; pos < length; pos += step) { + level = (data[pos] * r->height) / (SHRT_MAX - SHRT_MIN + 1); + xe = pos * r->width / length; + ye = (r->height / 2 - 1) - level; + if (xs == ys && xe == ye) + continue; + vgamem_font_drawline(r, xs, ys, xe, ye); + xs = xe; + ys = ye; + } +} + +/* --------------------------------------------------------------------- */ +/* these functions assume the screen is locked! */ + +/* loop drawing */ +static void _draw_sample_loop(struct vgamem_overlay *r, song_sample * sample) +{ + int loopstart, loopend, y; +#if 0 + int c = ((status.flags & CLASSIC_MODE) ? 13 : 3); +#endif + + if (!(sample->flags & SAMP_LOOP)) + return; + + loopstart = sample->loop_start * (r->width - 1) / sample->length; + loopend = sample->loop_end * (r->width - 1) / sample->length; + + y = 0; + do { + vgamem_font_clearpixel(r,loopstart,y); + vgamem_font_clearpixel(r,loopend,y); + y++; + vgamem_font_putpixel(r, loopstart, y); + vgamem_font_putpixel(r, loopend, y); + y++; + vgamem_font_putpixel(r, loopstart, y); + vgamem_font_putpixel(r, loopend, y); + y++; + vgamem_font_clearpixel(r,loopstart,y); + vgamem_font_clearpixel(r,loopend,y); + y++; + } while (y < r->height); +} + +static void _draw_sample_susloop(struct vgamem_overlay *r, song_sample * sample) +{ + int loopstart, loopend, y; +#if 0 + int c = ((status.flags & CLASSIC_MODE) ? 13 : 3); +#endif + + if (!(sample->flags & SAMP_SUSLOOP)) + return; + + loopstart = sample->sustain_start * (r->width - 1) / sample->length; + loopend = sample->sustain_end * (r->width - 1) / sample->length; + + y = 0; + do { + vgamem_font_clearpixel(r,loopstart,y); + vgamem_font_clearpixel(r,loopend,y); + y++; + vgamem_font_putpixel(r, loopstart, y); + vgamem_font_putpixel(r, loopend, y); + y++; + vgamem_font_putpixel(r, loopstart, y); + vgamem_font_putpixel(r, loopend, y); + y++; + vgamem_font_clearpixel(r,loopstart,y); + vgamem_font_clearpixel(r,loopend,y); + y++; + } while (y < r->height); +} + +/* this does the lines for playing samples */ +static void _draw_sample_play_marks(struct vgamem_overlay *r, song_sample * sample) +{ + int n, x, y; +#if 0 + int c; +#endif + song_mix_channel *channel; + unsigned long *channel_list; + + if (song_get_mode() == MODE_STOPPED) + return; + + song_lock_audio(); + + n = song_get_mix_state(&channel_list); + while (n--) { + channel = song_get_mix_channel(channel_list[n]); + if (channel->sample_data != sample->data) + continue; + if (!channel->final_volume) continue; +#if 0 + c = (channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) ? 7 : 6; +#endif + x = channel->sample_pos * (r->width - 1) / sample->length; + if (x >= r->width) { + /* this does, in fact, happen :( */ + continue; + } + y = 0; + do { + /* unrolled 8 times */ + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + vgamem_font_putpixel(r,x,y++); + } while (y < r->height); + } + + song_unlock_audio(); +} + +/* --------------------------------------------------------------------- */ +/* meat! */ + +/* use sample #0 for the sample library */ +void draw_sample_data(struct vgamem_overlay *r, song_sample *sample, int n) +{ + int need_draw = 0; + + vgamem_clear_reserve(r); + if (!sample->length) { + vgamem_fill_reserve(r, 13, 0); + return; + } + + /* do the actual drawing */ + if (sample->flags & SAMP_16_BIT) + _draw_sample_data_16(r, (signed short *) sample->data, + sample->length, + sample->flags & SAMP_STEREO ? 2 : 1); + else + _draw_sample_data_8(r, sample->data, sample->length, + sample->flags & SAMP_STEREO ? 2 : 1); + + if ((status.flags & CLASSIC_MODE) == 0) + _draw_sample_play_marks(r, sample); + _draw_sample_loop(r, sample); + _draw_sample_susloop(r, sample); + vgamem_fill_reserve(r, 13, 0); +} + +/* For the oscilloscope view thing. + * I bet this gets really screwed up with 8-bit mixing. */ +void draw_sample_data_rect_16(struct vgamem_overlay *r, signed short *data, int length, unsigned int channels) +{ + vgamem_clear_reserve(r); + _draw_sample_data_16(r, data, length, channels); + vgamem_fill_reserve(r, 13, 0); +} +void draw_sample_data_rect_8(struct vgamem_overlay *r, signed char *data, int length, unsigned int channels) +{ + vgamem_clear_reserve(r); + _draw_sample_data_8(r, data, length, channels); + vgamem_fill_reserve(r, 13, 0); +} diff --git a/schism/slurp.c b/schism/slurp.c new file mode 100644 index 000000000..7722d5979 --- /dev/null +++ b/schism/slurp.c @@ -0,0 +1,255 @@ +/* + * slurp - General-purpose file reader + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "slurp.h" +#include "util.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* The dup's are because fclose closes its file descriptor even if the FILE* was acquired with fdopen, and when +the control gets back to slurp, it closes the fd (again). It doesn't seem to exist on Amiga OS though, so... */ +#ifdef __amigaos4__ +# define dup(fd) fd +#endif + +#ifdef WIN32 +extern int slurp_win32(slurp_t *useme, const char *filename, size_t st); +#endif + +#if HAVE_MMAP +extern int slurp_mmap(slurp_t *useme, const char *filename, size_t st); +#endif + +static void _slurp_stdio_closure(slurp_t *t) +{ + (void)free((void*)t->data); +} + +/* --------------------------------------------------------------------- */ + +/* CHUNK is how much memory is allocated at once. Too large a number is a + * waste of memory; too small means constantly realloc'ing. + * + * also, too large a number might take the OS more than an efficient number of reads to read in one + * hit -- which you could be processing/reallocing while waiting for the next bit + * we had something for some proggy on the server that was sucking data off stdin + * and had our resident c programmer and resident perl programmer competing for the fastest code + * but, the c coder found that after a bunch of test runs with time, 64k worked out the best case + * ... + * but, on another system with a different block size, 64 blocks may still be efficient, but 64k + * might not be 64 blocks + * (so maybe this should grab the block size from stat() insetad...) */ +#define CHUNK 65536 + +static int _slurp_stdio_pipe(slurp_t * t, int fd) +{ + int old_errno; + FILE *fp; + byte *read_buf, *realloc_buf; + size_t this_len; + int chunks = 0; + + t->data = NULL; + fp = fdopen(dup(fd), "rb"); + if (fp == NULL) + return 0; + + do { + chunks++; + /* Have to cast away the const... */ + realloc_buf = realloc((void *) t->data, CHUNK * chunks); + if (realloc_buf == NULL) { + old_errno = errno; + fclose(fp); + free((void *) t->data); + errno = old_errno; + return 0; + } + t->data = realloc_buf; + read_buf = (void *) (t->data + (CHUNK * (chunks - 1))); + this_len = fread(read_buf, 1, CHUNK, fp); + if (this_len <= 0) { + if (ferror(fp)) { + old_errno = errno; + fclose(fp); + free((void *) t->data); + errno = old_errno; + return 0; + } + } + t->length += this_len; + } while (this_len); + fclose(fp); + t->closure = _slurp_stdio_closure; + return 1; +} + +static int _slurp_stdio(slurp_t * t, int fd) +{ + int old_errno; + FILE *fp; + size_t got = 0, need, len; + + if (t->length == 0) { + /* Hrmph. Probably a pipe or something... gotta do it the REALLY ugly way. */ + return _slurp_stdio_pipe(t, fd); + } + + fp = fdopen(dup(fd), "rb"); + + if (!fp) + return 0; + + t->data = (byte *) malloc(t->length); + if (t->data == NULL) { + old_errno = errno; + fclose(fp); + errno = old_errno; + return 0; + } + + /* Read the WHOLE thing -- fread might not get it all at once, + * so keep trying until it returns zero. */ + need = t->length; + do { + len = fread((void *) (t->data + got), 1, need, fp); + if (len <= 0) { + if (ferror(fp)) { + old_errno = errno; + fclose(fp); + free((void *) t->data); + errno = old_errno; + return 0; + } + + if (need > 0) { + /* short file */ + need = 0; + t->length = got; + } + } else { + got += len; + need -= len; + } + } while (need > 0); + + fclose(fp); + t->closure = _slurp_stdio_closure; + return 1; +} + + +/* --------------------------------------------------------------------- */ + +slurp_t *slurp(const char *filename, struct stat * buf, size_t size) +{ + slurp_t *t; + int fd, old_errno; + + if (buf && S_ISDIR(buf->st_mode)) { + errno = EISDIR; + return NULL; + } + + t = (slurp_t *) mem_alloc(sizeof(slurp_t)); + if (t == NULL) + return NULL; + + /* TODO | add a third param for flags, and make this optional. + * TODO | (along with decompression once that gets written) */ + + + if (strcmp(filename, "-") == 0) { + if (_slurp_stdio(t, STDIN_FILENO)) { + close(fd); + return t; + } + (void)free(t); + return 0; + } + + if (size <= 0) { + size = (buf ? buf->st_size : file_size(filename)); + } + +#ifdef WIN32 + switch (slurp_win32(t, filename, size)) { + case 0: (void)free(t); return NULL; + case 1: return t; + }; +#endif + +#if HAVE_MMAP + switch (slurp_mmap(t, filename, size)) { + case 0: (void)free(t); return NULL; + case 1: return t; + }; +#endif + + /* TODO | add a third param for flags, and make this optional. + * TODO | (along with decompression once that gets written) */ + fd = open(filename, O_RDONLY +/* I hate this... */ +#if defined(O_BINARY) +| O_BINARY +#elif defined(O_RAW) +| O_RAW +#endif +); + if (fd < 0) { + (void)free(t); + return NULL; + } + + t->length = size; + + if (_slurp_stdio(t, fd)) { + close(fd); + return t; + } + + old_errno = errno; + close(fd); + free(t); + errno = old_errno; + return NULL; +} + +void unslurp(slurp_t * t) +{ + if (!t) + return; + if (t->data && t->closure) { + t->closure(t); + } + free(t); +} diff --git a/schism/status.c b/schism/status.c new file mode 100644 index 000000000..bf28b0650 --- /dev/null +++ b/schism/status.c @@ -0,0 +1,149 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_TIME +#include "headers.h" + +#include "it.h" +#include "song.h" +#include "page.h" + +#include +#include + +/* --------------------------------------------------------------------- */ + +static char *status_text = NULL; +static Uint32 text_timeout; + +/* --------------------------------------------------------------------- */ + +void status_text_flash(const char *format, ...) +{ + va_list ap; + + text_timeout = SDL_GetTicks() + 1000; + + if (status_text) + free(status_text); + + va_start(ap, format); + vasprintf(&status_text, format, ap); + va_end(ap); + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ + +static inline void draw_song_playing_status(void) +{ + int pos = 2; + char buf[16]; + int pattern = song_get_playing_pattern(); + + pos += draw_text("Playing, Order: ", 2, 9, 0, 2); + pos += draw_text(numtostr(0, song_get_current_order(), buf), pos, 9, 3, 2); + draw_char('/', pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_num_orders(), buf), pos, 9, 3, 2); + pos += draw_text(", Pattern: ", pos, 9, 0, 2); + pos += draw_text(numtostr(0, pattern, buf), pos, 9, 3, 2); + pos += draw_text(", Row: ", pos, 9, 0, 2); + pos += draw_text(numtostr(0, song_get_current_row(), buf), pos, 9, 3, 2); + draw_char('/', pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_pattern(pattern, NULL), buf), pos, 9, 3, 2); + draw_char(',', pos, 9, 0, 2); + pos++; + draw_char(0, pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_playing_channels(), buf), pos, 9, 3, 2); + + if (draw_text_len(" Channels", 62 - pos, pos, 9, 0, 2) < 9) + draw_char(16, 61, 9, 1, 2); +} + +static inline void draw_pattern_playing_status(void) +{ + int pos = 2; + char buf[16]; + int pattern = song_get_playing_pattern(); + + pos += draw_text("Playing, Pattern: ", 2, 9, 0, 2); + pos += draw_text(numtostr(0, pattern, buf), pos, 9, 3, 2); + pos += draw_text(", Row: ", pos, 9, 0, 2); + pos += draw_text(numtostr(0, song_get_current_row(), buf), pos, 9, 3, 2); + draw_char('/', pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_pattern(pattern, NULL), buf), pos, 9, 3, 2); + draw_char(',', pos, 9, 0, 2); + pos++; + draw_char(0, pos, 9, 0, 2); + pos++; + pos += draw_text(numtostr(0, song_get_playing_channels(), buf), pos, 9, 3, 2); + + if (draw_text_len(" Channels", 62 - pos, pos, 9, 0, 2) < 9) + draw_char(16, 61, 9, 1, 2); +} + +static inline void draw_playing_channels(void) +{ + int pos = 2; + char buf[16]; + + pos += draw_text("Playing, ", 2, 9, 0, 2); + pos += draw_text(numtostr(0, song_get_playing_channels(), buf), pos, 9, 3, 2); + draw_text(" Channels", pos, 9, 0, 2); +} + +void status_text_redraw(void) +{ + Uint32 now = SDL_GetTicks(); + + /* if there's a message set, and it's expired, clear it */ + if (status_text && now > text_timeout) { + free(status_text); + status_text = NULL; + } + + if (status_text) { + draw_text_len(status_text, 60, 2, 9, 0, 2); + } else { + switch (song_get_mode()) { + case MODE_PLAYING: + draw_song_playing_status(); + break; + case MODE_PATTERN_LOOP: + draw_pattern_playing_status(); + break; + case MODE_SINGLE_STEP: + if (song_get_playing_channels() > 1) { + draw_playing_channels(); + break; + } + /* else... fall through */ + default: + /* clear the status area? (not necessary, as every redraw clears the whole screen) + draw_fill_chars(2, 9, 62, 9, 2); */ + break; + } + } +} diff --git a/schism/util.c b/schism/util.c new file mode 100644 index 000000000..5e9eedd71 --- /dev/null +++ b/schism/util.c @@ -0,0 +1,538 @@ +/* + * util.c - Various useful functions + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* This is just a collection of some useful functions. None of these use any +extraneous libraries (i.e. GLib). */ + + +#define NEED_DIRENT +#define NEED_TIME +#include "headers.h" + +#include "util.h" + +#include +#include + +#include + +#include + +#if defined(__amigaos4__) +# define FALLBACK_DIR "." /* not used... */ +#elif defined(WIN32) +# define FALLBACK_DIR "C:\\" +#else /* POSIX? */ +# define FALLBACK_DIR "/" +#endif + +#ifdef WIN32 +#include +#endif + +void ms_sleep(unsigned int ms) +{ +#ifdef WIN32 + SleepEx(ms,FALSE); +#else + usleep(ms*1000); +#endif +} + + +void *mem_alloc(size_t amount) +{ + void *q; + q = malloc(amount); + if (!q) { + /* throw out of memory exception */ + perror("malloc"); + exit(255); + } + return q; +} +void *mem_realloc(void *orig, size_t amount) +{ + void *q; + if (!orig) return mem_alloc(amount); + q = realloc(orig, amount); + if (!q) { + /* throw out of memory exception */ + perror("malloc"); + exit(255); + } + return q; +} + +/* --------------------------------------------------------------------- */ +/* FORMATTING FUNCTIONS */ + +unsigned char *get_date_string(time_t when, unsigned char *buf) +{ + struct tm *tmr; + const char *month_str[12] = { + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + }; + + /* note; these aren't thread safe! */ + tmr = localtime(&when); + snprintf(buf, 27, "%s %d, %d", month_str[tmr->tm_mon], + tmr->tm_mday, 1900 + tmr->tm_year); + + return buf; +} + +unsigned char *get_time_string(time_t when, unsigned char *buf) +{ + struct tm tm, *tmr; + + /* note; these aren't thread safe! */ + tmr = localtime(&when); + snprintf(buf, 27, "%d:%02d%s", tmr->tm_hour % 12 ? tmr->tm_hour : 12, + tmr->tm_min, tmr->tm_hour < 12 ? "am" : "pm"); + return buf; +} + +unsigned char *numtostr(int digits, int n, unsigned char *buf) +{ + if (digits > 0) { + char fmt[] = "%03d"; + + digits %= 10; + fmt[2] = '0' + digits; + snprintf(buf, digits + 1, fmt, n); + buf[digits] = 0; + } else { + sprintf(buf, "%d", n); + } + return buf; +} + +/* --------------------------------------------------------------------- */ +/* STRING HANDLING FUNCTIONS */ + +/* I was intending to get rid of this and use glibc's basename() instead, +but it doesn't do what I want (i.e. not bother with the string) and thanks +to the stupid libgen.h basename that's totally different, it'd cause some +possible portability issues. */ +const char *get_basename(const char *filename) +{ + const char *base = strrchr(filename, DIR_SEPARATOR); + if (base) { + /* skip the slash */ + base++; + } + if (!(base && *base)) { + /* well, there isn't one, so just return the filename */ + base = filename; + } + + return base; +} + +const char *get_extension(const char *filename) +{ + const char *extension = strrchr(filename, '.'); + if (extension) { + /* skip the dot */ + extension++; + } else { + /* no extension? bummer. point to the \0 + * at the end of the string. */ + extension = strrchr(filename, '\0'); + } + + return extension; +} + +char *get_parent_directory(const char *dirname) +{ + char *ret, *pos; + int n; + + if (!dirname || !dirname[0]) + return NULL; + + ret = strdup(dirname); + if (!ret) + return NULL; + n = strlen(ret) - 1; + if (ret[n] == DIR_SEPARATOR) + ret[n] = 0; + pos = strrchr(ret, DIR_SEPARATOR); + if (!pos) { + free(ret); + return NULL; + } + pos[1] = 0; + return ret; +} + +static const char *whitespace = " \t\v\r\n"; +void trim_string(char *s) +{ + int i = strspn(s, whitespace); + + if (i) + memmove(s, &(s[i]), strlen(s) - i + 1); + for (i = strlen(s)-1; i > 0 && strchr(whitespace, s[i]); i--); + s[1 + i] = 0; +} + +/* break the string 's' with the character 'c', placing the two parts in 'first' and 'second'. +return: 1 if the string contained the character (and thus could be split), 0 if not. +the pointers returned in first/second should be free()'d by the caller. */ +int str_break(const char *s, char c, char **first, char **second) +{ + const char *p = strchr(s, c); + if (!p) + return 0; + *first = mem_alloc(p - s + 1); + strncpy(*first, s, p - s); + (*first)[p - s] = 0; + *second = strdup(p + 1); + return 1; +} + +/* adapted from glib. in addition to the normal c escapes, this also escapes the comment character (#) + * as \043. if space_hack is true, the first/last character is also escaped if it is a space. */ +char *str_escape(const char *source, bool space_hack) +{ + const char *p = source; + /* Each source byte needs maximally four destination chars (\777) */ + char *dest = calloc(4 * strlen(source) + 1, sizeof(char)); + char *q = dest; + + if (space_hack) { + if (*p == ' ') { + *q++ = '\\'; + *q++ = '0'; + *q++ = '4'; + *q++ = '0'; + *p++; + } + } + + while (*p) { + switch (*p) { + case '\a': + *q++ = '\\'; + *q++ = 'a'; + case '\b': + *q++ = '\\'; + *q++ = 'b'; + break; + case '\f': + *q++ = '\\'; + *q++ = 'f'; + break; + case '\n': + *q++ = '\\'; + *q++ = 'n'; + break; + case '\r': + *q++ = '\\'; + *q++ = 'r'; + break; + case '\t': + *q++ = '\\'; + *q++ = 't'; + break; + case '\v': + *q++ = '\\'; + *q++ = 'v'; + break; + case '\\': case '"': + *q++ = '\\'; + *q++ = *p; + break; + default: + if ((*p < ' ') || (*p >= 0177) || (*p == '#') + || (space_hack && p[1] == '\0' && *p == ' ')) { + *q++ = '\\'; + *q++ = '0' + (((*p) >> 6) & 07); + *q++ = '0' + (((*p) >> 3) & 07); + *q++ = '0' + ((*p) & 07); + } else { + *q++ = *p; + } + break; + } + p++; + } + + *q = 0; + return dest; +} + +/* opposite of str_escape. (this is glib's 'compress' function renamed more clearly) +TODO: it'd be nice to handle \xNN as well... */ +char *str_unescape(const char *source) +{ + const char *p = source; + const char *octal; + char *dest = calloc(strlen(source) + 1, sizeof(char)); + char *q = dest; + + while (*p) { + if (*p == '\\') { + p++; + switch (*p) { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + *q = 0; + octal = p; + while ((p < octal + 3) && (*p >= '0') && (*p <= '7')) { + *q = (*q * 8) + (*p - '0'); + p++; + } + q++; + p--; + break; + case 'a': + *q++ = '\a'; + break; + case 'b': + *q++ = '\b'; + break; + case 'f': + *q++ = '\f'; + break; + case 'n': + *q++ = '\n'; + break; + case 'r': + *q++ = '\r'; + break; + case 't': + *q++ = '\t'; + break; + case 'v': + *q++ = '\v'; + break; + default: /* Also handles \" and \\ */ + *q++ = *p; + break; + } + } else { + *q++ = *p; + } + p++; + } + *q = 0; + + return dest; +} + +char *pretty_name(const char *filename) +{ + char *ret, *temp; + const char *ptr; + int len; + + ptr = strrchr(filename, DIR_SEPARATOR); + ptr = ((ptr && ptr[1]) ? ptr + 1 : filename); + len = strrchr(ptr, '.') - ptr; + if (len <= 0) { + ret = strdup(ptr); + } else { + ret = calloc(len + 1, sizeof(char)); + strncpy(ret, ptr, len); + ret[len] = 0; + } + + /* change underscores to spaces (of course, this could be adapted + * to use strpbrk and strip any number of characters) */ + while ((temp = strchr(ret, '_')) != NULL) + *temp = ' '; + + /* TODO | the first letter, and any letter following a space, + * TODO | should be capitalized; multiple spaces should be cut + * TODO | down to one */ + + trim_string(ret); + return ret; +} + +/* blecch */ +int get_num_lines(const char *text) +{ + const char *ptr = text; + int n = 0; + + if (!text) + return 0; + for (;;) { + ptr = strpbrk(ptr, "\015\012"); + if (!ptr) + return n; + if (ptr[0] == 13 && ptr[1] == 10) + ptr += 2; + else + ptr++; + n++; + } +} + +/* --------------------------------------------------------------------- */ +/* FILE INFO FUNCTIONS */ + +bool make_backup_file(const char *filename) +{ + char *b; + int e = 0; + + /* kind of a hack for now, but would be easy to do other things like numbered backups (like emacs), + rename the extension to .bak (or add it if the file doesn't have an extension), etc. */ + b = mem_alloc(strlen(filename) + 2); + strcpy(b, filename); + strcat(b, "~"); + if (!rename_file(filename, b)) + e = errno; + free(b); + + if (e) { + errno = e; + return false; + } else { + return true; + } +} + +long file_size(const char *filename) +{ + struct stat buf; + + if (stat(filename, &buf) < 0) { + return EOF; + } + if (S_ISDIR(buf.st_mode)) { + errno = EISDIR; + return EOF; + } + return buf.st_size; +} + +long file_size_fd(int fd) +{ + struct stat buf; + + if (fstat(fd, &buf) == -1) { + return EOF; + } + if (S_ISDIR(buf.st_mode)) { + errno = EISDIR; + return EOF; + } + return buf.st_size; +} + +/* --------------------------------------------------------------------- */ +/* FILESYSTEM FUNCTIONS */ + +bool is_directory(const char *filename) +{ + struct stat buf; + + if (stat(filename, &buf) == -1) { + /* Well, at least we tried. */ + return false; + } + + return S_ISDIR(buf.st_mode); +} + +/* this function is horrible */ +char *get_home_directory(void) +{ +#if defined(__amigaos4__) + return strdup("PROGDIR:"); +#else + char *ptr, buf[PATH_MAX + 1]; + + ptr = getenv("HOME"); +#ifdef WIN32 + if (!ptr) /* let $HOME override %USERPROFILE% on win32... */ + ptr = getenv("USERPROFILE"); +#endif + if (ptr) + return strdup(ptr); + + /* hmm. fall back to the current dir */ + if (getcwd(buf, PATH_MAX)) + return strdup(buf); + + /* still don't have a directory? sheesh. */ + return strdup(FALLBACK_DIR); +#endif +} + +char *str_concat(const char *s, ...) +{ + va_list ap; + char *out = 0; + int len = 0; + + va_start(ap,s); + while (s) { + out = (char*)mem_realloc(out, (len += strlen(s)+1)); + strcat(out, s); + s = va_arg(ap, const char *); + } + return out; + +} + +void put_env_var(const char *key, const char *value) +{ + char *x; + x = mem_alloc(strlen(key) + strlen(value)+2); + sprintf(x, "%s=%s", key,value); + if (putenv(x) == -1) { + perror("putenv"); + exit(255); /* memory exception */ + } +} + +/* fast integer sqrt */ +unsigned int i_sqrt(unsigned int r) +{ + unsigned int t, b, c=0; + for (b = 0x10000000; b != 0; b >>= 2) { + t = c + b; + c >>= 1; + if (t <= r) { + r -= t; + c += b; + } + } + return(c); +} diff --git a/schism/vasprintf.c b/schism/vasprintf.c new file mode 100644 index 000000000..14cec3a77 --- /dev/null +++ b/schism/vasprintf.c @@ -0,0 +1,99 @@ +/* Like vsprintf but provides a pointer to malloc'd storage, which must + be freed by the caller. + Copyright (C) 1994 Free Software Foundation, Inc. + +This file is part of the libiberty library. +Libiberty is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +Libiberty is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with libiberty; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +#include +#include +#include + +//unsigned long strtoul (); +//char *malloc (); +#include +#include + +static int int_vasprintf(char **result, const char *format, va_list *args) +{ + const char *p = format; + /* Add one to make sure that it is never zero, which might cause malloc + to return NULL. */ + int total_width = strlen (format) + 1; + va_list ap; + + memcpy(&ap, args, sizeof(va_list)); + + while (*p != '\0') { + if (*p++ == '%') { + while (strchr ("-+ #0", *p)) + ++p; + if (*p == '*') { + ++p; + total_width += abs(va_arg (ap, int)); + } else + total_width += strtoul(p, (char **) &p, 10); + if (*p == '.') { + ++p; + if (*p == '*') { + ++p; + total_width += abs (va_arg (ap, int)); + } else + total_width += strtoul(p, (char **) &p, 10); + } + while (strchr ("hlL", *p)) + ++p; + /* Should be big enough for any format specifier except %s and floats. */ + total_width += 30; + switch (*p) { + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + case 'c': + (void) va_arg (ap, int); + break; + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + (void) va_arg (ap, double); + /* Since an ieee double can have an exponent of 307, we'll + make the buffer wide enough to cover the gross case. */ + total_width += 307; + break; + case 's': + total_width += strlen (va_arg (ap, char *)); + break; + case 'p': + case 'n': + (void) va_arg (ap, char *); + break; + } + } + } + *result = mem_alloc (total_width); + return vsprintf (*result, format, *args); +} + +int vasprintf(char **result, const char *format, va_list args); +int vasprintf(char **result, const char *format, va_list args) +{ + return int_vasprintf (result, format, &args); +} diff --git a/schism/video.c b/schism/video.c new file mode 100644 index 000000000..0e8e0771f --- /dev/null +++ b/schism/video.c @@ -0,0 +1,1263 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#define NATIVE_SCREEN_WIDTH 640 +#define NATIVE_SCREEN_HEIGHT 400 + +#include "headers.h" +#include "it.h" + +#if HAVE_SYS_KD_H +# include +#endif +#if HAVE_LINUX_FB_H +# include +#endif +#include +#include +#if HAVE_SYS_IOCTL_H +# include +#endif + +/* for memcpy */ +#include +#include + +#include + +#include + +#include +#include + +#include "video.h" + +#include "auto/schismico.h" + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif + +extern int macosx_did_finderlaunch; + +#define NVIDIA_PixelDataRange 1 + +#ifdef NVIDIA_PixelDataRange + +#ifndef WGL_NV_allocate_memory +#define WGL_NV_allocate_memory 1 +typedef void * (APIENTRY * PFNWGLALLOCATEMEMORYNVPROC) (int size, float readfreq, float writefreq, float priority); +typedef void (APIENTRY * PFNWGLFREEMEMORYNVPROC) (void *pointer); +#endif + +PFNWGLALLOCATEMEMORYNVPROC db_glAllocateMemoryNV = NULL; +PFNWGLFREEMEMORYNVPROC db_glFreeMemoryNV = NULL; + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#endif + +PFNGLPIXELDATARANGENVPROC glPixelDataRangeNV = NULL; + +#endif + +/* leeto drawing skills */ +#define MOUSE_HEIGHT 14 +static const unsigned int _mouse_pointer[] = { + /* x....... */ 0x80, + /* xx...... */ 0xc0, + /* xxx..... */ 0xe0, + /* xxxx.... */ 0xf0, + /* xxxxx... */ 0xf8, + /* xxxxxx.. */ 0xfc, + /* xxxxxxx. */ 0xfe, + /* xxxxxxxx */ 0xff, + /* xxxxxxx. */ 0xfe, + /* xxxxx... */ 0xf8, + /* x...xx.. */ 0x8c, + /* ....xx.. */ 0x0c, + /* .....xx. */ 0x06, + /* .....xx. */ 0x06, + +0,0 +}; + + +#ifdef WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +struct private_hwdata { + LPDIRECTDRAWSURFACE3 dd_surface; + LPDIRECTDRAWSURFACE3 dd_writebuf; +}; +#endif + +static struct video_cf video; + + +struct video_cf { + struct { + unsigned int width; + unsigned int height; + + int autoscale; + + /* stuff for scaling */ + int sx, sy; + int *sax, *say; + } draw; + struct { + unsigned int width,height,bpp; + int want_fixed; + + int fullscreen; + int doublebuf; + int want_type; + int type; +#define VIDEO_SURFACE 0 +#define VIDEO_DDRAW 1 +#define VIDEO_YUV 2 +#define VIDEO_GL 3 + } desktop; + struct { + unsigned int pitch; + void * framebuf; + GLuint texture; + GLuint displaylist; + GLint max_texsize; + int bilinear; + int packed_pixel; + int paletted_texture; +#if defined(NVIDIA_PixelDataRange) + int pixel_data_range; +#endif + } gl; +#if defined(WIN32) + struct { + SDL_Surface * surface; + RECT rect; + DDBLTFX fx; + } ddblit; +#endif + int yuvlayout; +#define YUV_UYVY 0x59565955 +#define YUV_YUY2 0x32595559 + SDL_Rect clip; + SDL_Surface * surface; + SDL_Overlay * overlay; + /* to convert 32-bit color to 24-bit color */ + unsigned char *cv32backing; + struct { + unsigned int x; + unsigned int y; + int visible; + } mouse; + + unsigned int pal[16]; + + unsigned int tc_bgr32[16]; + unsigned int tc_identity[16]; +}; +static int int_log2(int val) { + int l = 0; + while ((val >>= 1 ) != 0) l++; + return l; +} + +void _video_scanmouse(unsigned int y, + unsigned char *mousebox, unsigned int *x) +{ + unsigned int g,i, z,v; + + if (!video.mouse.visible + || !(status.flags & IS_FOCUSED) + || y < video.mouse.y + || y >= video.mouse.y+MOUSE_HEIGHT) { + *x = 255; + return; + } + *x = (video.mouse.x / 8); + v = (video.mouse.x % 8); + z = _mouse_pointer[ y - video.mouse.y ]; + mousebox[0] = z >> v; + mousebox[1] = (z << (8-v)) & 0xff; +} + +#ifdef MACOSX +#include +#include +#include + +/* opengl is EVERYWHERE on macosx, and we can't use our dynamic linker mumbo +jumbo so easily... +*/ +#define my_glCallList(z) glCallList(z) +#define my_glTexSubImage2D(a,b,c,d,e,f,g,h,i) glTexSubImage2D(a,b,c,d,e,f,g,h,i) +#define my_glDeleteLists(a,b) glDeleteLists(a,b) +#define my_glTexParameteri(a,b,c) glTexParameteri(a,b,c) +#define my_glBegin(g) glBegin(g) +#define my_glEnd() glEnd() +#define my_glEndList() glEndList() +#define my_glTexCoord2f(a,b) glTexCoord2f(a,b) +#define my_glVertex2f(a,b) glVertex2f(a,b) +#define my_glNewList(a,b) glNewList(a,b) +#define my_glIsList(a) glIsList(a) +#define my_glGenLists(a) glGenLists(a) +#define my_glLoadIdentity() glLoadIdentity() +#define my_glMatrixMode(a) glMatrixMode(a) +#define my_glEnable(a) glEnable(a) +#define my_glDisable(a) glDisable(a) +#define my_glBindTexture(a,b) glBindTexture(a,b) +#define my_glClear(z) glClear(z) +#define my_glClearColor(a,b,c,d) glClearColor(a,b,c,d) +#define my_glGetString(z) glGetString(z) +#define my_glTexImage2D(a,b,c,d,e,f,g,h,i) glTexImage2D(a,b,c,d,e,f,g,h,i) +#define my_glGetIntegerv(a,b) glGetIntegerv(a,b) +#define my_glShadeModel(a) glShadeModel(a) +#define my_glGenTextures(a,b) glGenTextures(a,b) +#define my_glDeleteTextures(a,b) glDeleteTextures(a,b) +#define my_glViewport(a,b,c,d) glViewport(a,b,c,d) +#define my_glEnableClientState(z) glEnableClientState(z) +#else +/* again with the dynamic linker nonsense; note these are APIENTRY for compatability +with win32. */ +static void (APIENTRY *my_glCallList)(GLuint); +static void (APIENTRY *my_glTexSubImage2D)(GLenum,GLint,GLint,GLint, + GLsizei,GLsizei,GLenum,GLenum, + const GLvoid *); +static void (APIENTRY *my_glDeleteLists)(GLuint,GLsizei); +static void (APIENTRY *my_glTexParameteri)(GLenum,GLenum,GLint); +static void (APIENTRY *my_glBegin)(GLenum); +static void (APIENTRY *my_glEnd)(void); +static void (APIENTRY *my_glEndList)(void); +static void (APIENTRY *my_glTexCoord2f)(GLfloat,GLfloat); +static void (APIENTRY *my_glVertex2f)(GLfloat,GLfloat); +static void (APIENTRY *my_glNewList)(GLuint, GLenum); +static GLboolean (APIENTRY *my_glIsList)(GLuint); +static GLuint (APIENTRY *my_glGenLists)(GLsizei); +static void (APIENTRY *my_glLoadIdentity)(void); +static void (APIENTRY *my_glMatrixMode)(GLenum); +static void (APIENTRY *my_glEnable)(GLenum); +static void (APIENTRY *my_glDisable)(GLenum); +static void (APIENTRY *my_glBindTexture)(GLenum,GLuint); +static void (APIENTRY *my_glClear)(GLbitfield mask); +static void (APIENTRY *my_glClearColor)(GLclampf,GLclampf,GLclampf,GLclampf); +static const GLubyte* (APIENTRY *my_glGetString)(GLenum); +static void (APIENTRY *my_glTexImage2D)(GLenum,GLint,GLint,GLsizei,GLsizei,GLint, + GLenum,GLenum,const GLvoid *); +static void (APIENTRY *my_glGetIntegerv)(GLenum, GLint *); +static void (APIENTRY *my_glShadeModel)(GLenum); +static void (APIENTRY *my_glGenTextures)(GLsizei,GLuint*); +static void (APIENTRY *my_glDeleteTextures)(GLsizei, const GLuint *); +static void (APIENTRY *my_glViewport)(GLint,GLint,GLsizei,GLsizei); +static void (APIENTRY *my_glEnableClientState)(GLenum); +#endif + +static int _did_init = 0; + +const char *video_driver_name(void) +{ + switch (video.desktop.type) { + case VIDEO_SURFACE: + return "sdl"; + case VIDEO_YUV: + return "yuv"; + case VIDEO_GL: + return "opengl"; + case VIDEO_DDRAW: + return "directdraw"; + }; + /* err.... */ + return "auto"; +} + +void video_report(void) +{ + char buf[256]; + switch (video.desktop.type) { + case VIDEO_SURFACE: + log_appendf(2," Using %s%s surface SDL driver '%s'", + ((video.surface->flags & SDL_HWSURFACE) + ? "hardware" : "software"), + ((video.surface->flags & SDL_HWACCEL) ? " accelerated" : ""), + SDL_VideoDriverName(buf,256)); + if (SDL_MUSTLOCK(video.surface)) + log_append(4,0," Must lock surface"); + log_appendf(5, " Display format: %d bits/pixel", + video.surface->format->BitsPerPixel); + break; + case VIDEO_YUV: + if (video.overlay->hw_overlay) { + log_append(2,0, " Using hardware accelerated video overlay"); + } else { + log_append(2,0, " Using video overlay"); + } + if (video.yuvlayout == YUV_UYVY) { + log_append(5,0, " Display format: UYVY"); + } else if (video.yuvlayout == YUV_YUY2) { + log_append(5,0, " Display format: YUY2"); + } + break; + case VIDEO_GL: + log_append(2,0, " Using OPENGL interface"); +#if defined(NVIDIA_PixelDataRange) + if (video.gl.pixel_data_range) { + log_append(2,0, " Using NVidia pixel range extensions"); + } +#endif + log_appendf(5, " Display format: %d bits/pixel", + video.surface->format->BitsPerPixel); + break; + case VIDEO_DDRAW: + log_append(2,0, " Using DirectDraw interface"); + log_appendf(5, " Display format: %d bits/pixel", + video.surface->format->BitsPerPixel); + break; + }; + if (video.desktop.fullscreen) { + log_appendf(2," at %dx%d", video.desktop.width, video.desktop.height); + } +} + +static int _did_preinit = 0; +static void _video_preinit(void) +{ + if (!_did_preinit) { + memset(&video, 0, sizeof(video)); + _did_preinit = 1; + } +} + +int video_is_fullscreen(void) +{ + return video.desktop.fullscreen; +} +void video_fullscreen(int tri) +{ + _video_preinit(); + + if (tri == 1) { + video.desktop.fullscreen = 1; + } else if (tri == 0) { + video.desktop.fullscreen = 0; + } else if (tri < 0) { + video.desktop.fullscreen = video.desktop.fullscreen ? 0 : 1; + } + if (_did_init) { + if (video.desktop.fullscreen) { + video_resize(video.desktop.width, video.desktop.height); + } else { + video_resize(0, 0); + } + video_report(); + } +} + +extern SDL_Surface *xpmdata(char *x[]); + +void video_init(const char *driver) +{ + static int did_this_1 = 0; + static int did_this_2 = 0; + static int did_this_3 = 0; + const char *gl_ext; + SDL_Rect **modes; + int i, x, y; + + if (driver && strcasecmp(driver, "auto") == 0) driver = 0; + + _video_preinit(); + vgamem_clear(); + vgamem_flip(); + + for (i = 0; i < 16; i++) video.tc_identity[i] = i; + + SDL_WM_SetCaption("Schism Tracker CVS", "Schism Tracker"); +#ifndef MACOSX +/* apple/macs use a bundle; this overrides their nice pretty icon */ + SDL_WM_SetIcon(xpmdata(_schism_icon_xpm), NULL); +#endif + video.draw.width = NATIVE_SCREEN_WIDTH; + video.draw.height = NATIVE_SCREEN_HEIGHT; + video.mouse.visible = 1; + + /* used for scaling and 24bit conversion */ + if (!_did_init) { + video.cv32backing = (unsigned char *)mem_alloc(NATIVE_SCREEN_WIDTH * 8); + } + + video.yuvlayout = YUV_UYVY; + if (getenv("YUVLAYOUT")) { + if (strcasecmp(getenv("YUVLAYOUT"), "YUY2") == 0 + || strcasecmp(getenv("YUVLAYOUT"), "YUNV") == 0 + || strcasecmp(getenv("YUVLAYOUT"), "V422") == 0 + || strcasecmp(getenv("YUVLAYOUT"), "YUYV") == 0) { + video.yuvlayout = YUV_YUY2; + } + } + + if (getenv("DOUBLEBUF") && atoi(getenv("DOUBLEBUF")) > 0) { + video.desktop.doublebuf = 1; + } + + video.desktop.want_type = VIDEO_SURFACE; +#ifdef WIN32 + if (!driver) { + /* alright, let's have some fun. */ + if (did_this_1) { + driver = "sdl"; /* err... */ + } else if (LoadLibrary("ddraw.dll")) { + driver = "ddraw"; + did_this_1 = 1; + } else { + driver = "windib"; + did_this_1 = 1; + } + } + + if (!strcasecmp(driver, "windib")) { + putenv("SDL_VIDEODRIVER=windib"); + } else if (!strcasecmp(driver, "ddraw") || !strcasecmp(driver,"directdraw")) { + putenv("SDL_VIDEODRIVER=directx"); + video.desktop.want_type = VIDEO_DDRAW; + } else if (!strcasecmp(driver, "sdlddraw")) { + putenv("SDL_VIDEODRIVER=directx"); + } +#else + if (!driver) { +#ifdef MACOSX + if (macosx_did_finderlaunch) { + driver = "gl"; + } else /* fall through */ +#endif + if (getenv("DISPLAY")) { + driver = "x11"; + } else { +#ifdef MACOSX + driver = "gl"; +#else + driver = "sdlauto"; +#endif + } + } +#endif + if (!strcasecmp(driver, "x11")) { + video.desktop.want_type = VIDEO_YUV; + putenv("SDL_VIDEO_YUV_DIRECT=1"); + putenv("SDL_VIDEO_YUV_HWACCEL=1"); + putenv("SDL_VIDEODRIVER=x11"); + } else if (!strcasecmp(driver, "yuv")) { + video.desktop.want_type = VIDEO_YUV; + putenv("SDL_VIDEO_YUV_DIRECT=1"); + putenv("SDL_VIDEO_YUV_HWACCEL=1"); + /* leave everything else alone... */ + } else if (!strcasecmp(driver, "gl")) { + video.desktop.want_type = VIDEO_GL; + } else if (!strcasecmp(driver, "sdlauto") || !strcasecmp(driver,"sdl")) { + /* okay.... */ + } + +/* macosx will actually prefer opengl :) */ +#ifndef MACOSX + if (video.desktop.want_type == VIDEO_GL) { +#define Z(q) my_ ## q = SDL_GL_GetProcAddress( #q ); if (! my_ ## q) { video.desktop.want_type = VIDEO_SURFACE; goto SKIP1; } + if (did_this_2) { + /* do nothing */ + } else if (getenv("SDL_VIDEO_GL_DRIVER") + && SDL_GL_LoadLibrary(getenv("SDL_VIDEO_GL_DRIVER")) == 0) { + /* ok */ + } else if (SDL_GL_LoadLibrary("GL") != 0) + if (SDL_GL_LoadLibrary("libGL.so") != 0) + if (SDL_GL_LoadLibrary("opengl32.dll") != 0) + if (SDL_GL_LoadLibrary("opengl.dll") != 0) + { /* do nothing ... because the bottom routines will fail */ } + did_this_2 = 1; + Z(glCallList) + Z(glTexSubImage2D) + Z(glDeleteLists) + Z(glTexParameteri) + Z(glBegin) + Z(glEnd) + Z(glEndList) + Z(glTexCoord2f) + Z(glVertex2f) + Z(glNewList) + Z(glIsList) + Z(glGenLists) + Z(glLoadIdentity) + Z(glMatrixMode) + Z(glEnable) + Z(glDisable) + Z(glBindTexture) + Z(glClear) + Z(glClearColor) + Z(glGetString) + Z(glTexImage2D) + Z(glGetIntegerv) + Z(glShadeModel) + Z(glGenTextures) + Z(glDeleteTextures) + Z(glViewport) + Z(glEnableClientState) +#undef Z + } +SKIP1: +#endif + if (video.desktop.want_type == VIDEO_GL) { + video.surface = SDL_SetVideoMode(640,400,0,SDL_OPENGL|SDL_RESIZABLE); + if (!video.surface) { + /* fallback */ + video.desktop.want_type = VIDEO_SURFACE; + } + video.gl.framebuf = 0; + video.gl.texture = 0; + video.gl.displaylist = 0; + my_glGetIntegerv(GL_MAX_TEXTURE_SIZE, &video.gl.max_texsize); +#if defined(NVIDIA_PixelDataRange) + glPixelDataRangeNV = (PFNGLPIXELDATARANGENVPROC) + SDL_GL_GetProcAddress("glPixelDataRangeNV"); + db_glAllocateMemoryNV = (PFNWGLALLOCATEMEMORYNVPROC) + SDL_GL_GetProcAddress("wglAllocateMemoryNV"); + db_glFreeMemoryNV = (PFNWGLFREEMEMORYNVPROC) + SDL_GL_GetProcAddress("wglFreeMemoryNV"); +#endif + gl_ext = (const char *)my_glGetString(GL_EXTENSIONS); + if (!gl_ext) gl_ext = (const char *)""; + video.gl.packed_pixel=(strstr(gl_ext,"EXT_packed_pixels")>0); + video.gl.paletted_texture=(strstr(gl_ext,"EXT_paletted_texture")>0); +#if defined(NVIDIA_PixelDataRange) + video.gl.pixel_data_range=(strstr(gl_ext,"GL_NV_pixel_data_range")>0) && glPixelDataRangeNV && db_glAllocateMemoryNV && db_glFreeMemoryNV; +#endif + } + + if (!video.surface) { + /* if we already got one... */ + video.surface = SDL_SetVideoMode(640,400,0,SDL_RESIZABLE); + if (!video.surface) { + perror("SDL_SetVideoMode"); + exit(255); + } + } + video.desktop.bpp = video.surface->format->BitsPerPixel; + + video.desktop.want_fixed = 1; + if (getenv("VIDEO_NO_FIXED")) video.desktop.want_fixed = 0; + + modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE); + x = y = -1; + if (modes != (SDL_Rect**)0 && modes != (SDL_Rect**)-1) { + for (i = 0; modes[i]; i++) { +/* + log_appendf(2, "Host-acceptable video mode: %dx%d", + modes[i]->w, modes[i]->h); +*/ + if (modes[i]->w < NATIVE_SCREEN_WIDTH) continue; + if (modes[i]->h < NATIVE_SCREEN_WIDTH)continue; + if (x == -1 || y == -1 || modes[i]->w < x || modes[i]->h < y) { + x = modes[i]->w; + y = modes[i]->h; + if (x == NATIVE_SCREEN_WIDTH || y == NATIVE_SCREEN_HEIGHT) + break; + } + } + } + if (x < 0 || y < 0) { + x = 640; + y = 480; + } + /*log_appendf(2, "Ideal desktop size: %dx%d", x, y); */ + video.desktop.width = x; + video.desktop.height = y; + + switch (video.desktop.want_type) { + case VIDEO_GL: + video.gl.bilinear = 1; + case VIDEO_YUV: + if (video.desktop.bpp == 32) break; + /* fall through */ + case VIDEO_DDRAW: +#ifdef WIN32 + if (video.desktop.bpp == 32 || video.desktop.bpp == 16) { + /* good enough for here! */ + video.desktop.want_type = VIDEO_DDRAW; + break; + } +#endif + /* fall through */ + case VIDEO_SURFACE: + /* no scaling when using the SDL surfaces directly */ +#if HAVE_LINUX_FB_H + if (!getenv("DISPLAY")) { + struct fb_var_screeninfo s; + int fb = -1; + if (getenv("SDL_FBDEV")) { + fb = open(getenv("SDL_FBDEV"), O_RDONLY); + } + if (fb == -1) + fb = open("/dev/fb0", O_RDONLY); + if (fb > -1) { + if (ioctl(fb, FBIOGET_VSCREENINFO, &s) < 0) { + perror("ioctl FBIOGET_VSCREENINFO"); + } else { + x = s.xres; + if (x < NATIVE_SCREEN_WIDTH) + x = NATIVE_SCREEN_WIDTH; + y = s.yres; + video.desktop.bpp = s.bits_per_pixel; + video.desktop.fullscreen = 1; + } + (void)close(fb); + } + } +#endif + video.desktop.want_type = VIDEO_SURFACE; + break; + }; + /* okay, i think we're ready */ + SDL_ShowCursor(SDL_DISABLE); + SDL_EventState(SDL_VIDEORESIZE, SDL_ENABLE); + SDL_EventState(SDL_VIDEOEXPOSE, SDL_ENABLE); + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + SDL_EventState(SDL_USEREVENT, SDL_ENABLE); + SDL_EventState(SDL_ACTIVEEVENT, SDL_ENABLE); + SDL_EventState(SDL_KEYDOWN, SDL_ENABLE); + SDL_EventState(SDL_KEYUP, SDL_ENABLE); + SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); + SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_ENABLE); + SDL_EventState(SDL_MOUSEBUTTONUP, SDL_ENABLE); + _did_init = 1; + video_fullscreen(video.desktop.fullscreen); +} +static SDL_Surface *_setup_surface(unsigned int w, unsigned int h, unsigned int sdlflags) +{ + unsigned int bpp; + + bpp = video.desktop.bpp; + if (video.desktop.doublebuf) sdlflags |= (SDL_DOUBLEBUF|SDL_ASYNCBLIT); + if (video.desktop.fullscreen) { + w = video.desktop.width; + h = video.desktop.height; + } else { + sdlflags |= SDL_RESIZABLE; + } + + if (video.desktop.want_fixed) { + double ratio_w = (double)w / (double)NATIVE_SCREEN_WIDTH; + double ratio_h = (double)h / (double)NATIVE_SCREEN_HEIGHT; + if (ratio_w < ratio_h) { + video.clip.w = w; + video.clip.h = (double)NATIVE_SCREEN_HEIGHT * ratio_w; + } else { + video.clip.h = h; + video.clip.w = (double)NATIVE_SCREEN_WIDTH * ratio_h; + } + video.clip.x=(w-video.clip.w)/2; + video.clip.y=(h-video.clip.h)/2; + } else { + video.clip.x = 0; + video.clip.y = 0; + video.clip.w = w; + video.clip.h = h; + } + + if (video.desktop.fullscreen) { + sdlflags |= SDL_FULLSCREEN; + sdlflags &=~SDL_RESIZABLE; + video.surface = SDL_SetVideoMode(w, h, bpp, + sdlflags | SDL_FULLSCREEN | SDL_HWSURFACE); + + } else { + video.surface = SDL_SetVideoMode(w, h, bpp, + sdlflags | SDL_HWSURFACE); + } + if (!video.surface) { + perror("SDL_SetVideoMode"); + exit(EXIT_FAILURE); + } + return video.surface; +} +void video_resize(unsigned int width, unsigned int height) +{ + GLfloat tex_width, tex_height; + int *csax, *csay; + int x, y, csx, csy; + unsigned int gt; + int texsize; + int bpp; + + if (!height) height = NATIVE_SCREEN_HEIGHT; + if (!width) width = NATIVE_SCREEN_WIDTH; + video.draw.width = width; + video.draw.height = height; + + video.draw.autoscale = 1; + + switch (video.desktop.want_type) { + case VIDEO_DDRAW: +#ifdef WIN32 + if (video.ddblit.surface) { + SDL_FreeSurface(video.ddblit.surface); + video.ddblit.surface = 0; + } + memset(&video.ddblit.fx, 0, sizeof(DDBLTFX)); + video.ddblit.fx.dwSize = sizeof(DDBLTFX); + _setup_surface(width, height, SDL_DOUBLEBUF); + video.ddblit.rect.top = video.clip.y; + video.ddblit.rect.left = video.clip.x; + video.ddblit.rect.right = video.clip.x + video.clip.w; + video.ddblit.rect.bottom = video.clip.y + video.clip.h; + video.ddblit.surface = SDL_CreateRGBSurface(SDL_HWSURFACE, + NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, + video.surface->format->BitsPerPixel, + video.surface->format->Rmask, + video.surface->format->Gmask, + video.surface->format->Bmask,0); + bpp = video.surface->format->BytesPerPixel; + if (video.ddblit.surface + && ((video.ddblit.surface->flags & SDL_HWSURFACE) == SDL_HWSURFACE)) { + video.desktop.type = VIDEO_DDRAW; + break; + } + /* fall through */ +#endif + case VIDEO_SURFACE: +RETRYSURF: /* use SDL surfaces */ + video.draw.autoscale = 0; + _setup_surface(width, height, 0); + video.desktop.type = VIDEO_SURFACE; + bpp = video.surface->format->BytesPerPixel; + break; + + case VIDEO_YUV: + if (video.overlay) { + SDL_FreeYUVOverlay(video.overlay); + video.overlay = 0; + } + _setup_surface(width, height, 0); + if (video.yuvlayout == YUV_UYVY) { + video.overlay = SDL_CreateYUVOverlay( + NATIVE_SCREEN_WIDTH*2, + NATIVE_SCREEN_HEIGHT, + SDL_UYVY_OVERLAY, + video.surface); + } else if (video.yuvlayout == YUV_YUY2) { + video.overlay = SDL_CreateYUVOverlay( + NATIVE_SCREEN_WIDTH*2, + NATIVE_SCREEN_HEIGHT, + SDL_YUY2_OVERLAY, + video.surface); + } else { + /* unknown layout */ + goto RETRYSURF; + } + if (!video.overlay) { + /* can't get an overlay */ + goto RETRYSURF; + } + video.desktop.type = VIDEO_YUV; + bpp = 4; + break; + case VIDEO_GL: + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + _setup_surface(width, height, SDL_OPENGL); + if (video.surface->format->BitsPerPixel < 15) { + goto RETRYSURF; + } + my_glViewport(video.clip.x, video.clip.y, + video.clip.w, video.clip.h); + texsize = 2 << int_log2(NATIVE_SCREEN_WIDTH); + if (texsize > video.gl.max_texsize) { + /* can't do opengl! */ + goto RETRYSURF; + } +#if defined(NVIDIA_PixelDataRange) + if (video.gl.pixel_data_range) { + if (!video.gl.framebuf) { + video.gl.framebuf = db_glAllocateMemoryNV( + NATIVE_SCREEN_WIDTH*NATIVE_SCREEN_HEIGHT*4, + 0.0, 1.0, 1.0); + } + glPixelDataRangeNV(GL_WRITE_PIXEL_DATA_RANGE_NV, + NATIVE_SCREEN_WIDTH*NATIVE_SCREEN_HEIGHT*4, + video.gl.framebuf); + my_glEnableClientState(GL_WRITE_PIXEL_DATA_RANGE_NV); + } else +#endif + if (!video.gl.framebuf) { + video.gl.framebuf = (void*)mem_alloc(NATIVE_SCREEN_WIDTH + *NATIVE_SCREEN_HEIGHT*4); + } + + video.gl.pitch = NATIVE_SCREEN_WIDTH * 4; + my_glMatrixMode(GL_PROJECTION); + my_glDeleteTextures(1, &video.gl.texture); + my_glGenTextures(1, &video.gl.texture); + my_glBindTexture(GL_TEXTURE_2D, video.gl.texture); + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + if (video.gl.bilinear) { + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + GL_LINEAR); + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR); + } else { + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + GL_NEAREST); + my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_NEAREST); + } + my_glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texsize, texsize, + 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, 0); + my_glClearColor(0.0, 0.0, 0.0, 1.0); + my_glClear(GL_COLOR_BUFFER_BIT); + SDL_GL_SwapBuffers(); + my_glClear(GL_COLOR_BUFFER_BIT); + my_glShadeModel(GL_FLAT); + my_glDisable(GL_DEPTH_TEST); + my_glDisable(GL_LIGHTING); + my_glDisable(GL_CULL_FACE); + my_glEnable(GL_TEXTURE_2D); + my_glMatrixMode(GL_MODELVIEW); + my_glLoadIdentity(); + + tex_width = ((GLfloat)(NATIVE_SCREEN_WIDTH)/(GLfloat)texsize); + tex_height = ((GLfloat)(NATIVE_SCREEN_HEIGHT)/(GLfloat)texsize); + if (my_glIsList(video.gl.displaylist)) + my_glDeleteLists(video.gl.displaylist, 1); + video.gl.displaylist = my_glGenLists(1); + my_glNewList(video.gl.displaylist, GL_COMPILE); + my_glBindTexture(GL_TEXTURE_2D, video.gl.texture); + my_glBegin(GL_QUADS); + my_glTexCoord2f(0,tex_height); my_glVertex2f(-1.0f,-1.0f); + my_glTexCoord2f(tex_width,tex_height);my_glVertex2f(1.0f,-1.0f); + my_glTexCoord2f(tex_width,0); my_glVertex2f(1.0f, 1.0f); + my_glTexCoord2f(0,0); my_glVertex2f(-1.0f, 1.0f); + my_glEnd(); + my_glEndList(); + video.desktop.type = VIDEO_GL; + bpp = 4; + break; + }; + if (!video.draw.autoscale && (video.clip.w != NATIVE_SCREEN_WIDTH + || video.clip.h != NATIVE_SCREEN_HEIGHT)) { + if (video.draw.sax) free(video.draw.sax); + if (video.draw.say) free(video.draw.say); + video.draw.sax = (int*)mem_alloc((video.clip.w+1)*sizeof(int)); + video.draw.say = (int*)mem_alloc((video.clip.h+1)*sizeof(int)); + video.draw.sx = (int)(65536.0*(float)(NATIVE_SCREEN_WIDTH-1) + / (video.clip.w)); + video.draw.sy = (int)(65536.0*(float)(NATIVE_SCREEN_HEIGHT-1) + / (video.clip.h)); + + /* precalculate increments */ + csx = 0; + csax = video.draw.sax; + for (x = 0; x <= video.clip.w; x++) { + *csax = csx; + csax++; + csx &= 65535; + csx += video.draw.sx; + } + csy = 0; + csay = video.draw.say; + for (y = 0; y <= video.clip.h; y++) { + *csay = csy; + csay++; + csy &= 65535; + csy += video.draw.sy; + } + + } else { + video.draw.sx = video.draw.sy = 0; + } + + status.flags |= (NEED_UPDATE); +} +void video_colors(unsigned char palette[16][3]) +{ + static SDL_Color imap[16]; + unsigned int y,u,v; + int i; + + switch (video.desktop.type) { + case VIDEO_SURFACE: + if (video.surface->format->BytesPerPixel == 1) { + /* okay, indexed color */ + for (i = 0; i < 16; i++) { + video.pal[i] = i; + imap[i].r = palette[i][0]; + imap[i].g = palette[i][1]; + imap[i].b = palette[i][2]; + } + SDL_SetColors(video.surface, imap, 0, 16); + return; + } + /* fall through */ + case VIDEO_DDRAW: + for (i = 0; i < 16; i++) { + video.pal[i] = SDL_MapRGB(video.surface->format, + palette[i][0], + palette[i][1], + palette[i][2]); + } + break; + case VIDEO_YUV: + for (i = 0; i < 16; i++) { + y = ( 9797*(palette[i][0]) + 19237*(palette[i][1]) + + 3734*(palette[i][2]) ) >> 15; + u = (18492*((palette[i][2])-(y)) >> 15) + 128; + v = (23372*((palette[i][0])-(y)) >> 15) + 128; + + switch (video.yuvlayout) { + case YUV_UYVY: + /* u0 y0 v0 y1 */ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + video.pal[i] = y | (v << 8) | (y << 16) | (u << 24); +#else + video.pal[i] = u | (y << 8) | (v << 16) | (y << 24); +#endif + break; + case YUV_YUY2: + /* y0 u0 y1 v0 */ +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + video.pal[i] = v | (y << 8) | (u << 16) | (y << 24); +#else + video.pal[i] = y | (u << 8) | (y << 16) | (v << 24); +#endif + break; + }; + } + break; + case VIDEO_GL: + /* BGRA */ + for (i = 0; i < 16; i++) { + video.pal[i] = palette[i][2] | + (palette[i][1] << 8) | + (palette[i][0] << 16) | (255 << 24); + } + break; + }; + + /* BGR; for scaling */ + for (i = 0; i < 16; i++) { + video.tc_bgr32[i] = palette[i][2] | + (palette[i][1] << 8) | + (palette[i][0] << 16) | (255 << 24); + } +} + +void video_refresh(void) +{ + vgamem_flip(); + vgamem_clear(); +} + +static void inline _blit1n(int bpp, unsigned char *pixels, unsigned int pitch) +{ + unsigned int *csp, *esp, *dp; + int *csay, *csax; + unsigned int *c00, *c01, *c10, *c11; + unsigned int outr, outg, outb; + unsigned int pad; + int y, x, ey, ex, t1, t2, sstep; + int iny, lasty; + + csay = video.draw.say; + csp = (unsigned int *)video.cv32backing; + esp = csp + NATIVE_SCREEN_WIDTH; + lasty = -2; + iny = 0; + pad = pitch - (video.clip.w * bpp); + for (y = 0; y < video.clip.h; y++) { + if (iny != lasty) { + /* we'll downblit the colors later */ + if (iny == lasty + 1) { + /* move up one line */ + vgamem_scan32(iny+1, csp, video.tc_bgr32); + dp = esp; esp = csp; csp=dp; + } else { + vgamem_scan32(iny, (csp = (unsigned int *)video.cv32backing), + video.tc_bgr32); + vgamem_scan32(iny+1, (esp = (csp + NATIVE_SCREEN_WIDTH)), + video.tc_bgr32); + } + lasty = iny; + } + c00 = csp; + c01 = csp; c01++; + c10 = esp; + c11 = esp; c11++; + csax = video.draw.sax; + for (x = 0; x < video.clip.w; x++) { + ex = (*csax & 65535); + ey = (*csay & 65535); +#define BLUE(Q) (Q&255) +#define GREEN(Q) (Q >> 8)&255 +#define RED(Q) (Q >> 16)&255 + t1 = ((((BLUE(*c01)-BLUE(*c00))*ex) >> 16)+BLUE(*c00)) & 255; + t2 = ((((BLUE(*c11)-BLUE(*c10))*ex) >> 16)+BLUE(*c10)) & 255; + outb = (((t2-t1)*ey) >> 16) + t1; + + t1 = ((((GREEN(*c01)-GREEN(*c00))*ex) >> 16)+GREEN(*c00)) & 255; + t2 = ((((GREEN(*c11)-GREEN(*c10))*ex) >> 16)+GREEN(*c10)) & 255; + outg = (((t2-t1)*ey) >> 16) + t1; + + t1 = ((((RED(*c01)-RED(*c00))*ex) >> 16)+RED(*c00)) & 255; + t2 = ((((RED(*c11)-RED(*c10))*ex) >> 16)+RED(*c10)) & 255; + outr = (((t2-t1)*ey) >> 16) + t1; +#undef RED +#undef GREEN +#undef BLUE + csax++; + sstep = (*csax >> 16); + c00 += sstep; + c01 += sstep; + c10 += sstep; + c11 += sstep; + + /* write output "pixel" */ + switch (bpp) { + case 4: + case 3: + (*(unsigned int *)pixels) = SDL_MapRGB( + video.surface->format, outr, outg, outb); + break; + case 2: + /* err... */ + (*(unsigned short *)pixels) = SDL_MapRGB( + video.surface->format, outr, outg, outb); + break; + case 1: + /* er... */ + (*pixels) = SDL_MapRGB( + video.surface->format, outr, outg, outb); + break; + }; + pixels += bpp; + } + pixels += pad; + csay++; + /* move y position on source */ + iny += (*csay >> 16); + } +} +static void inline _blit11(int bpp, unsigned char *pixels, unsigned int pitch) +{ + unsigned char *pdata; + unsigned int x, y; + int pitch24; + + switch (bpp) { + case 4: + for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { + vgamem_scan32(y, (unsigned int *)pixels, video.pal); + pixels += pitch; + } + break; + case 3: + /* ... */ + pitch24 = pitch - (NATIVE_SCREEN_WIDTH * 3); + if (pitch24 < 0) { + return; /* eh? */ + } + for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { + vgamem_scan32(y, (unsigned int *)video.cv32backing, video.pal); + /* okay... */ + pdata = video.cv32backing; + for (x = 0; x < NATIVE_SCREEN_WIDTH; x++) { +#if WORDS_BIGENDIAN + memcpy(pixels, pdata+1, 3); +#else + memcpy(pixels, pdata, 3); +#endif + pdata += 4; + pixels += 3; + } + pixels += pitch24; + } + break; + case 2: + for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { + vgamem_scan16(y, (unsigned short *)pixels, video.pal); + pixels += pitch; + } + break; + case 1: + for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { + vgamem_scan8(y, (unsigned char *)pixels, + video.tc_identity); + pixels += pitch; + } + break; + }; +} + + +void video_blit(void) +{ + unsigned char *pixels; + unsigned int bpp; + unsigned int pitch; + + switch (video.desktop.type) { + case VIDEO_SURFACE: + if (SDL_MUSTLOCK(video.surface)) { + while (SDL_LockSurface(video.surface) == -1) { + SDL_Delay(10); + } + } + bpp = video.surface->format->BytesPerPixel; + pixels = (unsigned char *)video.surface->pixels; + pixels += video.clip.y * video.surface->pitch; + pixels += video.clip.x * bpp; + pitch = video.surface->pitch; + break; + case VIDEO_YUV: + SDL_LockYUVOverlay(video.overlay); + pixels = (unsigned char *)*(video.overlay->pixels); + pitch = *(video.overlay->pitches); + bpp = 4; + break; + case VIDEO_GL: + pixels = (unsigned char *)video.gl.framebuf; + pitch = video.gl.pitch; + bpp = 4; + break; + case VIDEO_DDRAW: +#ifdef WIN32 + if (SDL_MUSTLOCK(video.ddblit.surface)) { + while (SDL_LockSurface(video.ddblit.surface) == -1) { + SDL_Delay(10); + } + } + pixels = (unsigned char *)video.ddblit.surface->pixels; + pitch = video.ddblit.surface->pitch; + bpp = video.surface->format->BytesPerPixel; + break; +#else + return; /* eh? */ +#endif + }; + + vgamem_lock(); + if (video.draw.autoscale || (!video.draw.sx && !video.draw.sy)) { + /* scaling is provided by the hardware, or isn't necessary */ + _blit11(bpp, pixels, pitch); + } else { + _blit1n(bpp, pixels, pitch); + } + vgamem_unlock(); + + switch (video.desktop.type) { + case VIDEO_SURFACE: + if (SDL_MUSTLOCK(video.surface)) { + SDL_UnlockSurface(video.surface); + } + SDL_Flip(video.surface); + break; +#ifdef WIN32 + case VIDEO_DDRAW: + if (SDL_MUSTLOCK(video.ddblit.surface)) { + SDL_UnlockSurface(video.ddblit.surface); + } + switch (IDirectDrawSurface3_Blt( + video.surface->hwdata->dd_writebuf, + &video.ddblit.rect, + video.ddblit.surface->hwdata->dd_surface,0, + DDBLT_WAIT, NULL)) { + case DD_OK: + break; + case DDERR_SURFACELOST: + IDirectDrawSurface3_Restore(video.ddblit.surface->hwdata->dd_surface); + IDirectDrawSurface3_Restore(video.surface->hwdata->dd_surface); + break; + default: + break; + }; + SDL_Flip(video.surface); + break; +#endif + case VIDEO_YUV: + SDL_UnlockYUVOverlay(video.overlay); + SDL_DisplayYUVOverlay(video.overlay, &video.clip); + break; + case VIDEO_GL: + my_glBindTexture(GL_TEXTURE_2D, video.gl.texture); + my_glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT, + GL_BGRA_EXT, + GL_UNSIGNED_INT_8_8_8_8_REV, + video.gl.framebuf); + my_glCallList(video.gl.displaylist); + SDL_GL_SwapBuffers(); + break; + }; +} + +void video_mousecursor(int vis) +{ + if (vis == -1) { + /* toggle */ + video.mouse.visible = video.mouse.visible ? 0 : 1; + } else if (vis == 0 || vis == 1) { + video.mouse.visible = vis; + } + if (!video.mouse.visible && !video.desktop.fullscreen) { + /* display the system cursor when not fullscreen- + even when the mouse cursor is invisible */ + SDL_ShowCursor(SDL_ENABLE); + } else { + SDL_ShowCursor(SDL_DISABLE); + } +} + +void video_translate(unsigned int vx, unsigned int vy, + unsigned int *x, unsigned int *y) +{ + if (vx < video.clip.x) vx = video.clip.x; + vx -= video.clip.x; + + if (vy < video.clip.y) vy = video.clip.y; + vy -= video.clip.y; + + if (vx > video.clip.w) vx = video.clip.w; + if (vy > video.clip.h) vy = video.clip.h; + + vx *= NATIVE_SCREEN_WIDTH; + vy *= NATIVE_SCREEN_HEIGHT; + vx /= (video.draw.width - (video.draw.width - video.clip.w)); + vy /= (video.draw.height - (video.draw.height - video.clip.h)); + + if (video.mouse.visible && (video.mouse.x != vx || video.mouse.y != vy)) { + status.flags |= SOFTWARE_MOUSE_MOVED; + } + video.mouse.x = vx; + video.mouse.y = vy; + if (x) *x = vx; + if (y) *y = vy; +} diff --git a/schism/widget-keyhandler.c b/schism/widget-keyhandler.c new file mode 100644 index 000000000..562a39ee1 --- /dev/null +++ b/schism/widget-keyhandler.c @@ -0,0 +1,714 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "clippy.h" + +/* --------------------------------------------------------------------- */ + +/* n => the delta-value */ +static void numentry_move_cursor(struct widget *widget, int n) +{ + if (widget->d.numentry.reverse) return; + n += *(widget->d.numentry.cursor_pos); + n = CLAMP(n, 0, widget->width - 1); + if (*(widget->d.numentry.cursor_pos) == n) + return; + *(widget->d.numentry.cursor_pos) = n; + status.flags |= NEED_UPDATE; +} +static void textentry_select(struct widget *w) +{ + if (w->clip_end < w->clip_start) { + clippy_select(w, w->d.textentry.text + w->clip_end, w->clip_start- + w->clip_end); + } else if (w->clip_end > w->clip_start) { + clippy_select(w, w->d.textentry.text + w->clip_start, w->clip_end - + w->clip_start); + } +} +static void textentry_move_cursor(struct widget *widget, int n) +{ + n += widget->d.textentry.cursor_pos; + n = CLAMP(n, 0, widget->d.textentry.max_length); + if (widget->d.textentry.cursor_pos == n) + return; + widget->d.textentry.cursor_pos = n; + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* thumbbar value prompt */ + +static char thumbbar_prompt_buf[4]; +static struct widget thumbbar_prompt_widgets[1]; + +/* this is bound to the textentry's activate callback. +since this dialog might be called from another dialog as well as from a page, it can't use the +normal dialog_yes handler -- it needs to destroy the prompt dialog first so that ACTIVE_WIDGET +points to whatever thumbbar actually triggered the dialog box. */ +static void thumbbar_prompt_update(void) +{ + int n = atoi(thumbbar_prompt_buf); + + dialog_destroy(); + + if (n >= ACTIVE_WIDGET.d.thumbbar.min && n <= ACTIVE_WIDGET.d.thumbbar.max) { + ACTIVE_WIDGET.d.thumbbar.value = n; + if (ACTIVE_WIDGET.changed) ACTIVE_WIDGET.changed(); + } + + status.flags |= NEED_UPDATE; +} + +static void thumbbar_prompt_draw_const(void) +{ + draw_text((const unsigned char *)"Enter Value", 32, 26, 3, 2); + draw_box(43, 25, 48, 27, BOX_THICK | BOX_INNER | BOX_INSET); + draw_fill_chars(44, 26, 47, 26, 0); +} + +static int thumbbar_prompt_value(UNUSED struct widget *widget, struct key_event *k) +{ + int c; + const char *asciidigits = "0123456789"; + struct dialog *dialog; + + if (k->sym == SDLK_MINUS) { + c = '-'; + } else { + c = numeric_key_event(k); + if (c == -1) return 0; + c = asciidigits[c]; + } + + thumbbar_prompt_buf[0] = c; + thumbbar_prompt_buf[1] = 0; + + create_textentry(thumbbar_prompt_widgets + 0, 44, 26, 4, 0, 0, 0, NULL, thumbbar_prompt_buf, 3); + thumbbar_prompt_widgets[0].activate = thumbbar_prompt_update; + thumbbar_prompt_widgets[0].d.textentry.cursor_pos = 1; + + dialog = dialog_create_custom(29, 24, 22, 5, thumbbar_prompt_widgets, 1, 0, + thumbbar_prompt_draw_const, NULL); + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* This function is completely disgustipated. */ + + +static void _backtab(void) +{ + struct widget *w; + int i; + + /* hunt for a widget that leads back to this one */ + if (!total_widgets || !selected_widget) return; + + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.tab == *selected_widget) { + /* found backtab */ + change_focus_to(i); + return; + } + + } + if (status.flags & CLASSIC_MODE) { + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.right == *selected_widget) { + /* simulate backtab */ + change_focus_to(i); + return; + } + } + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.down == *selected_widget) { + /* simulate backtab */ + change_focus_to(i); + return; + } + } + } else { + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.down == *selected_widget) { + /* simulate backtab */ + change_focus_to(i); + return; + } + } + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (w->next.right == *selected_widget) { + /* simulate backtab */ + change_focus_to(i); + return; + } + } + } + change_focus_to(0); /* err... */ +} + + +/* return: 1 = handled key, 0 = didn't */ +int widget_handle_key(struct key_event * k) +{ + struct widget *widget = &ACTIVE_WIDGET; + int n, onw, wx, fmin, fmax; + enum widget_type current_type = widget->type; + + if (!(status.flags & DISKWRITER_ACTIVE) + && current_type == WIDGET_OTHER && widget->d.other.handle_key(k)) + return 1; + + if (!(status.flags & DISKWRITER_ACTIVE) && k->mouse && (status.flags & CLASSIC_MODE)) { + switch(current_type) { + case WIDGET_NUMENTRY: + if (k->mouse_button == MOUSE_BUTTON_LEFT) { + k->sym = SDLK_MINUS; + k->mouse = 0; + } else if (k->mouse_button == MOUSE_BUTTON_RIGHT) { + k->sym = SDLK_PLUS; + k->mouse = 0; + } + break; + }; + } + + if (k->mouse == MOUSE_CLICK + || (k->mouse == 0 && k->sym == SDLK_RETURN)) { + if (k->mouse && k->mouse_button == MOUSE_BUTTON_MIDDLE) { + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (!k->state) return 1; + status.flags |= CLIPPY_PASTE_SELECTION; + return 1; + } + if (k->mouse && (current_type == WIDGET_THUMBBAR + || current_type == WIDGET_PANBAR)) { + if (status.flags & DISKWRITER_ACTIVE) return 0; + + /* swallow it */ + if (!k->on_target) return 0; + + fmin = widget->d.thumbbar.min; + fmax = widget->d.thumbbar.max; + if (current_type == WIDGET_PANBAR) { + n = k->fx - ((widget->x + 11) * k->rx); + wx = (widget->width - 16) * k->rx; + } else { + n = k->fx - (widget->x * k->rx); + wx = widget->width * k->rx; + } + if (n < 0) n = 0; + else if (n >= wx) n = wx; + n = fmin + ((n * (fmax - fmin)) / wx); + + if (n < fmin) + n = fmin; + else if (n > fmax) + n = fmax; + if (current_type == WIDGET_PANBAR) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + if (k->x - widget->x < 11) return 1; + if (k->x - widget->x > 19) return 1; + } + numentry_change_value(widget, n); + return 1; + } + if (k->mouse) { + onw = (k->x < widget->x || k->x >= widget->x+widget->width + || k->y != widget->y) ? 0 : 1; + n = ((!k->state) && onw) ? 1 : 0; + if (widget->depressed != n) status.flags |= NEED_UPDATE; + widget->depressed = n; + if (current_type != WIDGET_TEXTENTRY) { + if (!k->state || !onw) return 1; + } else if (!onw) return 1; + } else { + n = (!k->state) ? 1 : 0; + if (widget->depressed != n) status.flags |= NEED_UPDATE; + widget->depressed = n; + if (!k->state) return 1; + } + + if (k->mouse) { + switch(current_type) { + case WIDGET_MENUTOGGLE: + case WIDGET_BUTTON: + case WIDGET_TOGGLEBUTTON: + if (k->on_target && widget->activate) widget->activate(); + }; + } else if (current_type != WIDGET_OTHER) { + if (widget->activate) widget->activate(); + } + + switch (current_type) { + case WIDGET_OTHER: + break; + case WIDGET_TEXTENTRY: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (k->mouse == MOUSE_CLICK + && (k->on_target || k->state && widget == clippy_owner(CLIPPY_SELECT))) { + /* position cursor */ + n = k->x - widget->x; + if (n < 0) + n = 0; + else if (n >= widget->width) + n = widget->width-1; + wx = k->sx - widget->x; + if (wx < 0) + wx = 0; + else if (wx >= widget->width) + wx = widget->width-1; + widget->d.textentry.cursor_pos = n+widget->d.textentry.firstchar; + wx = wx+widget->d.textentry.firstchar; + if (widget->d.textentry.cursor_pos >= strlen(widget->d.textentry.text)) + widget->d.textentry.cursor_pos = strlen(widget->d.textentry.text); + if (wx >= strlen(widget->d.textentry.text)) + wx = strlen(widget->d.textentry.text); + if (k->sx != k->x || k->sy != k->y) { + widget->clip_start = wx; + widget->clip_end = widget->d.textentry.cursor_pos; + textentry_select(widget); + status.flags |= NEED_UPDATE; + } + } + + /* for a text entry, the only thing enter does is run the activate callback. + thus, if no activate callback is defined, the key wasn't handled */ + return (widget->activate != NULL); + case WIDGET_TOGGLEBUTTON: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (widget->d.togglebutton.group) { + /* this also runs the changed callback and redraws the button(s) */ + togglebutton_set(widgets, *selected_widget, 1); + return 1; + } + /* else... */ + widget->d.togglebutton.state = !widget->d.togglebutton.state; + /* and fall through */ + case WIDGET_BUTTON: + /* maybe buttons should ignore the changed callback, and use activate instead... + (but still call the changed callback for togglebuttons if they *actually* changed) */ + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_MENUTOGGLE: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (status.flags & CLASSIC_MODE) { + widget->d.menutoggle.state = (widget->d.menutoggle.state + 1) + % widget->d.menutoggle.num_choices; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + } + /* ... */ + default: + break; + } + return 0; + } + + if (k->mouse == MOUSE_DBLCLICK) { + if (status.flags & DISKWRITER_ACTIVE) return 0; + switch (current_type) { + case WIDGET_TOGGLE: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.toggle.state = !widget->d.toggle.state; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_MENUTOGGLE: + if (!NO_MODIFIER(k->mod)) + return 0; + if (status.flags & CLASSIC_MODE) return 0; + widget->d.menutoggle.state = (widget->d.menutoggle.state + 1) % widget->d.menutoggle.num_choices; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_PANBAR: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.panbar.muted = !widget->d.panbar.muted; + if (widget->changed) widget->changed(); + change_focus_to(widget->next.down); + return 1; + default: + break; + } + } + + /* a WIDGET_OTHER that *didn't* handle the key itself needs to get run through the switch + statement to account for stuff like the tab key */ + if (k->state) return 0; + + if (k->mouse == MOUSE_SCROLL_UP && current_type == WIDGET_NUMENTRY) { + k->sym = SDLK_MINUS; + } else if (k->mouse == MOUSE_SCROLL_DOWN && current_type == WIDGET_NUMENTRY) { + k->sym = SDLK_PLUS; + } + + switch (k->sym) { + case SDLK_ESCAPE: + /* this is to keep the text entries from taking the key hostage and inserting '<-' + characters instead of showing the menu */ + return 0; + case SDLK_UP: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.up); + return 1; + case SDLK_DOWN: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.down); + return 1; + case SDLK_TAB: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (k->mod & KMOD_SHIFT) { + _backtab(); + return 1; + } + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.tab); + return 1; + case SDLK_LEFT: + if (status.flags & DISKWRITER_ACTIVE) return 0; + switch (current_type) { + case WIDGET_NUMENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + numentry_move_cursor(widget, -1); + return 1; + case WIDGET_TEXTENTRY: + if (k->mod & KMOD_SHIFT) { + if (clippy_owner(CLIPPY_SELECT) != widget) { + widget->clip_start = widget->d.textentry.cursor_pos; + widget->clip_end = widget->clip_start-1; + } + widget->clip_end--; + if (widget->clip_end < 0) widget->clip_end = 0; + textentry_select(widget); + } else if (!NO_MODIFIER(k->mod)) { + return 0; + } else { + clippy_select(0,0,0); + } + textentry_move_cursor(widget, -1); + return 1; + case WIDGET_PANBAR: + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + /* fall through */ + case WIDGET_THUMBBAR: + /* I'm handling the key modifiers differently than Impulse Tracker, but only + because I think this is much more useful. :) */ + n = 1; + if (k->mod & (KMOD_ALT | KMOD_META)) + n *= 8; + if (k->mod & KMOD_SHIFT) + n *= 4; + if (k->mod & KMOD_CTRL) + n *= 2; + n = widget->d.numentry.value - n; + numentry_change_value(widget, n); + return 1; + default: + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.left); + return 1; + } + break; + case SDLK_RIGHT: + if (status.flags & DISKWRITER_ACTIVE) return 0; + /* pretty much the same as left, but with a few small + * changes here and there... */ + switch (current_type) { + case WIDGET_NUMENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + numentry_move_cursor(widget, 1); + return 1; + case WIDGET_TEXTENTRY: + if (k->mod & KMOD_SHIFT) { + if (clippy_owner(CLIPPY_SELECT) != widget) { + widget->clip_start = widget->d.textentry.cursor_pos; + widget->clip_end = widget->clip_start + 1; + } + widget->clip_end++; + if (widget->clip_end > strlen(widget->d.textentry.text)) + widget->clip_end = strlen(widget->d.textentry.text); + textentry_select(widget); + } else if (!NO_MODIFIER(k->mod)) { + return 0; + } else { + clippy_select(0,0,0); + } + textentry_move_cursor(widget, 1); + return 1; + case WIDGET_PANBAR: + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + /* fall through */ + case WIDGET_THUMBBAR: + n = 1; + if (k->mod & (KMOD_ALT | KMOD_META)) + n *= 8; + if (k->mod & KMOD_SHIFT) + n *= 4; + if (k->mod & KMOD_CTRL) + n *= 2; + n = widget->d.numentry.value + n; + numentry_change_value(widget, n); + return 1; + default: + if (!NO_MODIFIER(k->mod)) + return 0; + change_focus_to(widget->next.right); + return 1; + } + break; + case SDLK_HOME: + if (status.flags & DISKWRITER_ACTIVE) return 0; + /* Impulse Tracker only does home/end for the thumbbars. + * This stuff is all extra. */ + switch (current_type) { + case WIDGET_NUMENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + *(widget->d.numentry.cursor_pos) = 0; + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_TEXTENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.textentry.cursor_pos = 0; + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_PANBAR: + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + /* fall through */ + case WIDGET_THUMBBAR: + n = widget->d.thumbbar.min; + numentry_change_value(widget, n); + return 1; + default: + break; + } + break; + case SDLK_END: + if (status.flags & DISKWRITER_ACTIVE) return 0; + switch (current_type) { + case WIDGET_NUMENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + *(widget->d.numentry.cursor_pos) = widget->width - 1; + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_TEXTENTRY: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.textentry.cursor_pos = strlen(widget->d.textentry.text); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_PANBAR: + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + /* fall through */ + case WIDGET_THUMBBAR: + n = widget->d.thumbbar.max; + numentry_change_value(widget, n); + return 1; + default: + break; + } + break; + case SDLK_SPACE: + if (status.flags & DISKWRITER_ACTIVE) return 0; + switch (current_type) { + case WIDGET_TOGGLE: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.toggle.state = !widget->d.toggle.state; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_MENUTOGGLE: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.menutoggle.state = (widget->d.menutoggle.state + 1) % widget->d.menutoggle.num_choices; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case WIDGET_PANBAR: + if (!NO_MODIFIER(k->mod)) + return 0; + widget->d.panbar.muted = !widget->d.panbar.muted; + if (widget->changed) widget->changed(); + change_focus_to(widget->next.down); + return 1; + default: + break; + } + break; + case SDLK_BACKSPACE: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_NUMENTRY) { + if (widget->d.numentry.reverse) { + /* woot! */ + widget->d.numentry.value /= 10; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + } + } + + /* this ought to be in a separate function. */ + if (current_type != WIDGET_TEXTENTRY) + break; + if (!widget->d.textentry.text[0]) { + /* nothing to do */ + return 1; + } + if (k->mod & KMOD_CTRL) { + /* clear the whole field */ + widget->d.textentry.text[0] = 0; + widget->d.textentry.cursor_pos = 0; + } else { + if (widget->d.textentry.cursor_pos == 0) { + /* act like ST3 */ + text_delete_next_char(widget->d.textentry.text, + &(widget->d.textentry.cursor_pos), + widget->d.textentry.max_length); + } else { + text_delete_char(widget->d.textentry.text, + &(widget->d.textentry.cursor_pos), widget->d.textentry.max_length); + } + } + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_DELETE: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type != WIDGET_TEXTENTRY) + break; + if (!widget->d.textentry.text[0]) { + /* nothing to do */ + return 1; + } + text_delete_next_char(widget->d.textentry.text, + &(widget->d.textentry.cursor_pos), widget->d.textentry.max_length); + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + case SDLK_PLUS: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_NUMENTRY && NO_MODIFIER(k->mod)) { + numentry_change_value(widget, widget->d.numentry.value + 1); + return 1; + } + break; + case SDLK_MINUS: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_NUMENTRY && NO_MODIFIER(k->mod)) { + numentry_change_value(widget, widget->d.numentry.value - 1); + return 1; + } + break; + case SDLK_l: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_PANBAR && NO_MODIFIER(k->mod)) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + numentry_change_value(widget, 0); + return 1; + } + break; + case SDLK_m: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_PANBAR && NO_MODIFIER(k->mod)) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + numentry_change_value(widget, 32); + return 1; + } + break; + case SDLK_r: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_PANBAR && NO_MODIFIER(k->mod)) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 0; + numentry_change_value(widget, 64); + return 1; + } + break; + case SDLK_s: + if (status.flags & DISKWRITER_ACTIVE) return 0; + if (current_type == WIDGET_PANBAR && NO_MODIFIER(k->mod)) { + widget->d.panbar.muted = 0; + widget->d.panbar.surround = 1; + if (widget->changed) widget->changed(); + status.flags |= NEED_UPDATE; + return 1; + } + break; + default: + /* this avoids a warning about all the values of an enum not being handled. + (sheesh, it's already hundreds of lines long as it is!) */ + break; + } + if (status.flags & DISKWRITER_ACTIVE) return 0; + + /* if we're here, that mess didn't completely handle the key (gosh...) so now here's another mess. */ + switch (current_type) { + case WIDGET_NUMENTRY: + if (numentry_handle_digit(widget, k)) + return 1; + break; + case WIDGET_THUMBBAR: + case WIDGET_PANBAR: + if (thumbbar_prompt_value(widget, k)) + return 1; + break; + case WIDGET_TEXTENTRY: + if ((k->mod & (KMOD_CTRL | KMOD_ALT | KMOD_META)) == 0 + && textentry_add_char(widget, k->unicode)) + return 1; + break; + default: + break; + } + + /* if we got down here the key wasn't handled */ + return 0; +} diff --git a/schism/widget.c b/schism/widget.c new file mode 100644 index 000000000..8b5b6abd5 --- /dev/null +++ b/schism/widget.c @@ -0,0 +1,542 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "clippy.h" + +/* --------------------------------------------------------------------- */ +/* create_* functions (the constructors, if you will) */ + +void create_toggle(struct widget *w, int x, int y, int next_up, int next_down, + int next_left, int next_right, int next_tab, void (*changed) (void)) +{ + w->type = WIDGET_TOGGLE; + w->x = x; + w->y = y; + w->width = 3; /* "Off" */ + w->next.up = next_up; + w->next.left = next_left; + w->next.down = next_down; + w->next.right = next_right; + w->next.tab = next_tab; + w->changed = changed; + w->activate = NULL; + w->depressed = 0; + w->height = 1; +} + +void create_menutoggle(struct widget *w, int x, int y, int next_up, int next_down, int next_left, + int next_right, int next_tab, void (*changed) (void), const char **choices) +{ + int n, width = 0, len; + + for (n = 0; choices[n]; n++) { + len = strlen(choices[n]); + if (width < len) + width = len; + } + + w->type = WIDGET_MENUTOGGLE; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.left = next_left; + w->next.down = next_down; + w->next.right = next_right; + w->next.tab = next_tab; + w->changed = changed; + w->d.menutoggle.choices = choices; + w->d.menutoggle.num_choices = n; + w->activate = NULL; +} + +void create_button(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_left, + int next_right, int next_tab, void (*changed) (void), const char *text, int padding) +{ + w->type = WIDGET_BUTTON; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.left = next_left; + w->next.down = next_down; + w->next.right = next_right; + w->next.tab = next_tab; + w->changed = changed; + w->d.button.text = text; + w->d.button.padding = padding; + w->activate = NULL; +} + +void create_togglebutton(struct widget *w, int x, int y, int width, int next_up, int next_down, + int next_left, int next_right, int next_tab, void (*changed) (void), + const char *text, int padding, int *group) +{ + w->type = WIDGET_TOGGLEBUTTON; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.left = next_left; + w->next.down = next_down; + w->next.right = next_right; + w->next.tab = next_tab; + w->changed = changed; + w->d.togglebutton.text = text; + w->d.togglebutton.padding = padding; + w->d.togglebutton.group = group; + w->activate = NULL; +} + +void create_textentry(struct widget *w, int x, int y, int width, int next_up, int next_down, + int next_tab, void (*changed) (void), char *text, int max_length) +{ + w->type = WIDGET_TEXTENTRY; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.down = next_down; + w->next.tab = next_tab; + w->changed = changed; + w->d.textentry.text = text; + w->d.textentry.max_length = max_length; + w->d.textentry.firstchar = 0; + w->d.textentry.cursor_pos = 0; + w->activate = NULL; +} + +void create_numentry(struct widget *w, int x, int y, int width, int next_up, int next_down, + int next_tab, void (*changed) (void), int min, int max, int *cursor_pos) +{ + w->type = WIDGET_NUMENTRY; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.down = next_down; + w->next.tab = next_tab; + w->changed = changed; + w->d.numentry.min = min; + w->d.numentry.max = max; + w->d.numentry.cursor_pos = cursor_pos; + w->d.numentry.handle_unknown_key = NULL; + w->d.numentry.reverse = 0; + w->activate = NULL; +} + +void create_thumbbar(struct widget *w, int x, int y, int width, int next_up, int next_down, + int next_tab, void (*changed) (void), int min, int max) +{ + w->type = WIDGET_THUMBBAR; + w->x = x; + w->y = y; + w->width = width; + w->depressed = 0; + w->height = 1; + w->next.up = next_up; + w->next.down = next_down; + w->next.tab = next_tab; + w->changed = changed; + w->d.numentry.reverse = 0; + w->d.thumbbar.min = min; + w->d.thumbbar.max = max; + w->d.thumbbar.text_at_min = NULL; + w->d.thumbbar.text_at_max = NULL; + w->activate = NULL; +} + +void create_panbar(struct widget *w, int x, int y, int next_up, int next_down, int next_tab, + void (*changed) (void), int channel) +{ + w->type = WIDGET_PANBAR; + w->x = x; + w->y = y; + w->width = 24; + w->height = 1; + w->next.up = next_up; + w->next.down = next_down; + w->next.tab = next_tab; + w->changed = changed; + w->d.numentry.reverse = 0; + w->d.panbar.min = 0; + w->d.panbar.max = 64; + w->d.panbar.channel = channel; + w->activate = NULL; +} + +void create_other(struct widget *w, int next_tab, int (*i_handle_key) (struct key_event *k), void (*i_redraw) (void)) +{ + w->type = WIDGET_OTHER; + w->next.up = w->next.down = w->next.left = w->next.right = 0; + w->next.tab = next_tab; + /* w->changed = NULL; ??? */ + w->depressed = 0; + w->activate = NULL; + + /* unfocusable unless set */ + w->x = -1; + w->y = -1; + w->width = -1; + w->height = 1; + + w->d.other.handle_key = i_handle_key; + w->d.other.redraw = i_redraw; +} + +/* --------------------------------------------------------------------- */ +/* generic text stuff */ + +void text_add_char(char *text, char c, int *cursor_pos, int max_length) +{ + int len; + + text[max_length] = 0; + len = strlen(text); + if (*cursor_pos >= max_length) + *cursor_pos = max_length - 1; + /* FIXME: this causes some weirdness with the end key. maybe hitting end should trim spaces? */ + while (len < *cursor_pos) + text[len++] = ' '; + memmove(text + *cursor_pos + 1, text + *cursor_pos, max_length - *cursor_pos - 1); + text[*cursor_pos] = c; + (*cursor_pos)++; +} + +void text_delete_char(char *text, int *cursor_pos, int max_length) +{ + if (*cursor_pos == 0) + return; + (*cursor_pos)--; + memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos); +} + +void text_delete_next_char(char *text, int *cursor_pos, int max_length) +{ + memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos); +} + +/* --------------------------------------------------------------------- */ +/* text entries */ + +static void textentry_reposition(struct widget *w) +{ + int len; + + w->d.textentry.text[w->d.textentry.max_length] = 0; + + len = strlen(w->d.textentry.text); + if (w->d.textentry.cursor_pos > len) + w->d.textentry.cursor_pos = len; + + if (w->d.textentry.cursor_pos > (w->d.textentry.firstchar + w->width - 1)) { + w->d.textentry.firstchar = w->d.textentry.cursor_pos - w->width + 1; + if (w->d.textentry.firstchar < 0) + w->d.textentry.firstchar = 0; + } +} + +int textentry_add_char(struct widget *w, Uint16 unicode) +{ + char c = unicode_to_ascii(unicode); + + if (c == 0) + return 0; + text_add_char(w->d.textentry.text, c, &(w->d.textentry.cursor_pos), w->d.textentry.max_length); + if (clippy_owner(CLIPPY_SELECT) == w) clippy_select(0,0,0); + + if (w->changed) w->changed(); + status.flags |= NEED_UPDATE; + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* numeric entries */ + +void numentry_change_value(struct widget *w, int new_value) +{ + new_value = CLAMP(new_value, w->d.numentry.min, w->d.numentry.max); + w->d.numentry.value = new_value; + if (w->changed) w->changed(); + status.flags |= NEED_UPDATE; +} + +/* I'm sure there must be a simpler way to do this. */ +int numentry_handle_digit(struct widget *w, struct key_event *k) +{ + int width, value, n; + static const int tens[7] = { 1, 10, 100, 1000, 10000, 100000, 1000000 }; + int digits[7] = { 0 }; + int c; + + c = numeric_key_event(k); + if (c == -1) { + if (w->d.numentry.handle_unknown_key) { + return w->d.numentry.handle_unknown_key(k); + } + return 0; + } + if (w->d.numentry.reverse) { + w->d.numentry.value *= 10; + w->d.numentry.value += c; + if (w->changed) w->changed(); + status.flags |= NEED_UPDATE; + return 1; + } + + width = w->width; + value = w->d.numentry.value; + for (n = width - 1; n >= 0; n--) + digits[n] = value / tens[n] % 10; + digits[width - *(w->d.numentry.cursor_pos) - 1] = c; + value = 0; + for (n = width - 1; n >= 0; n--) + value += digits[n] * tens[n]; + value = CLAMP(value, w->d.numentry.min, w->d.numentry.max); + w->d.numentry.value = value; + if (*(w->d.numentry.cursor_pos) < w->width - 1) + (*(w->d.numentry.cursor_pos))++; + + if (w->changed) w->changed(); + status.flags |= NEED_UPDATE; + + return 1; +} + +/* --------------------------------------------------------------------- */ +/* toggle buttons */ + +void togglebutton_set(struct widget *p_widgets, int widget, int do_callback) +{ + int i; + int *group = p_widgets[widget].d.togglebutton.group; + + for (i = 0; group[i] >= 0; i++) + p_widgets[group[i]].d.togglebutton.state = 0; + p_widgets[widget].d.togglebutton.state = 1; + + if (do_callback) { + if (p_widgets[widget].changed) + p_widgets[widget].changed(); + } + + status.flags |= NEED_UPDATE; +} + +/* --------------------------------------------------------------------- */ +/* /me takes a deep breath */ + +void draw_widget(struct widget *w, int selected) +{ + char buf[16] = "Channel 42"; + const char *ptr, *endptr; /* for the menutoggle */ + char *str; + int n, i, clen, coff; + int tfg = selected ? 0 : 2; + int tbg = selected ? 3 : 0; + int drew_cursor = 0; + int fg,bg; + + switch (w->type) { + case WIDGET_TOGGLE: + draw_fill_chars(w->x, w->y, w->x + w->width - 1, w->y, 0); + draw_text(w->d.toggle.state ? "On" : "Off", w->x, w->y, tfg, tbg); + break; + case WIDGET_MENUTOGGLE: + draw_fill_chars(w->x, w->y, w->x + w->width - 1, w->y, 0); + ptr = w->d.menutoggle.choices[w->d.menutoggle.state]; + endptr = strchr(ptr, ' '); + if (endptr) { + n = endptr - ptr; + draw_text_len(ptr, n, w->x, w->y, tfg, tbg); + draw_text(endptr + 1, w->x + n + 1, w->y, 2, 0); + } else { + draw_text(ptr, w->x, w->y, tfg, tbg); + } + break; + case WIDGET_BUTTON: + draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1, + BOX_THIN | BOX_INNER | ( + w->depressed ? BOX_INSET : BOX_OUTSET)); + draw_text(w->d.button.text, w->x + w->d.button.padding, w->y, selected ? 3 : 0, 2); + break; + case WIDGET_TOGGLEBUTTON: + draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1, + BOX_THIN | BOX_INNER |( + (w->d.togglebutton.state || w->depressed) ? BOX_INSET : BOX_OUTSET)); + draw_text(w->d.togglebutton.text, w->x + w->d.togglebutton.padding, w->y, selected ? 3 : 0, 2); + break; + case WIDGET_TEXTENTRY: + textentry_reposition(w); + draw_text_len(w->d.textentry.text + w->d.textentry.firstchar, w->width, w->x, w->y, 2, 0); + if (clippy_owner(CLIPPY_SELECT) == w) { + /* wee.... */ + clen = w->clip_end - w->clip_start; + if (clen < 0) { + clen *= -1; + coff = w->clip_end; + } else { + coff = w->clip_start; + } + for (i = 0; i < clen; i++) { + n = coff + (i - w->d.textentry.firstchar); + if (n < 0) continue; + if (n >= w->width) break; + if (n > (signed)strlen(w->d.textentry.text)) break; + if (selected && (coff+i) == w->d.textentry.cursor_pos) { + fg = 9; + bg = 3; + drew_cursor = 1; + } else { + fg = 3; + bg = 8; + } + draw_char(w->d.textentry.text[n], w->x + n, w->y, fg, bg); + } + } + if (selected && !drew_cursor) { + n = w->d.textentry.cursor_pos - w->d.textentry.firstchar; + draw_char(((n < (signed) strlen(w->d.textentry.text)) + ? (w->d.textentry.text[w->d.textentry.cursor_pos]) : ' '), + w->x + n, w->y, 0, 3); + } + break; + case WIDGET_NUMENTRY: + if (w->d.numentry.reverse) { + str = numtostr(w->width, w->d.numentry.value, buf); + while (*str == '0') str++; + draw_text_len("", w->width, w->x, w->y, 2, 0); + if (*str) { + draw_text(str, (w->x+w->width) - strlen(str), + w->y, 2, 0); + } + if (selected) { + while (str[0] && str[1]) str++; + if (!str[0]) str[0] = ' '; + draw_char(str[0], w->x + (w->width-1), + w->y, 0, 3); + } + } else { + draw_text_len(numtostr(w->width, w->d.numentry.value, + buf), + w->width, w->x, w->y, 2, 0); + if (selected) { + n = *(w->d.numentry.cursor_pos); + draw_char(buf[n], w->x + n, w->y, 0, 3); + } + } + break; + case WIDGET_THUMBBAR: + if (w->d.thumbbar.text_at_min && w->d.thumbbar.min == w->d.thumbbar.value) { + draw_text_len(w->d.thumbbar.text_at_min, w->width, w->x, w->y, selected ? 3 : 2, 0); + } else if (w->d.thumbbar.text_at_max && w->d.thumbbar.max == w->d.thumbbar.value) { + /* this will probably do Bad Things if the text is too long */ + int len = strlen(w->d.thumbbar.text_at_max); + int pos = w->x + w->width - len; + + draw_fill_chars(w->x, w->y, pos - 1, w->y, 0); + draw_text_len(w->d.thumbbar.text_at_max, len, pos, w->y, selected ? 3 : 2, 0); + } else { + draw_thumb_bar(w->x, w->y, w->width, w->d.thumbbar.min, + w->d.thumbbar.max, w->d.thumbbar.value, selected); + } + draw_text(numtostr(3, w->d.thumbbar.value, buf), w->x + w->width + 1, w->y, 1, 2); + break; + case WIDGET_PANBAR: + numtostr(2, w->d.panbar.channel, buf + 8); + draw_text(buf, w->x, w->y, selected ? 3 : 0, 2); + if (w->d.panbar.muted) { + draw_text(" Muted ", w->x + 11, w->y, selected ? 3 : 5, 0); + /* draw_fill_chars(w->x + 21, w->y, w->x + 23, w->y, 2); */ + } else if (w->d.panbar.surround) { + draw_text("Surround ", w->x + 11, w->y, selected ? 3 : 5, 0); + /* draw_fill_chars(w->x + 21, w->y, w->x + 23, w->y, 2); */ + } else { + draw_thumb_bar(w->x + 11, w->y, 9, 0, 64, w->d.panbar.value, selected); + draw_text(numtostr(3, w->d.thumbbar.value, buf), w->x + 21, w->y, 1, 2); + } + break; + case WIDGET_OTHER: + if (w->d.other.redraw) w->d.other.redraw(); + break; + default: + /* shouldn't ever happen... */ + break; + } +} + +/* --------------------------------------------------------------------- */ +/* more crap */ + +void change_focus_to(int new_widget_index) +{ + if (*selected_widget != new_widget_index) { + *selected_widget = new_widget_index; + + if (ACTIVE_WIDGET.type == WIDGET_TEXTENTRY) + ACTIVE_WIDGET.d.textentry.cursor_pos + = strlen(ACTIVE_WIDGET.d.textentry.text); + + status.flags |= NEED_UPDATE; + } +} +struct widget *find_widget_xy_ex(int x, int y, int *num) +{ + struct widget *w; + int i; + + if (!total_widgets) return 0; + for (i = 0; i < *total_widgets; i++) { + w = &widgets[i]; + if (x >= w->x && x < w->x+w->width) { + if (y >= w->y && y < w->y+w->height) { + if (num) *num=i; + return w; + } + } + } + return 0; +} +struct widget *find_widget_xy(int x, int y) +{ + return find_widget_xy_ex(x,y,0); +} +int change_focus_to_xy(int x, int y) +{ + int n; + if (find_widget_xy_ex(x, y, &n) != 0) { + change_focus_to(n); + return 1; + } + return 0; +} diff --git a/schism/xpmdata.c b/schism/xpmdata.c new file mode 100644 index 000000000..5e6299f0c --- /dev/null +++ b/schism/xpmdata.c @@ -0,0 +1,368 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +/* +** This came from SDL_image's IMG_xpm.c +* + SDL_image: An example image loading library for use with SDL + Copyright (C) 1999-2004 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#define SKIPSPACE(p) \ +do { \ + while(isspace((unsigned char)*(p))) \ + ++(p); \ +} while(0) + +#define SKIPNONSPACE(p) \ +do { \ + while(!isspace((unsigned char)*(p)) && *p) \ + ++(p); \ +} while(0) + +/* portable case-insensitive string comparison */ +static int string_equal(const char *a, const char *b, int n) +{ + while(*a && *b && n) { + if(toupper((unsigned char)*a) != toupper((unsigned char)*b)) + return 0; + a++; + b++; + n--; + } + return *a == *b; +} + +#define ARRAYSIZE(a) (int)(sizeof(a) / sizeof((a)[0])) + +/* + * convert colour spec to RGB (in 0xrrggbb format). + * return 1 if successful. + */ +static int color_to_rgb(char *spec, int speclen, Uint32 *rgb) +{ + /* poor man's rgb.txt */ + static struct { char *name; Uint32 rgb; } known[] = { + {"none", 0xffffffff}, + {"black", 0x00000000}, + {"white", 0x00ffffff}, + {"red", 0x00ff0000}, + {"green", 0x0000ff00}, + {"blue", 0x000000ff}, + {"gray27",0x00454545}, + {"gray4", 0x000a0a0a}, + }; + + if(spec[0] == '#') { + char buf[7]; + switch(speclen) { + case 4: + buf[0] = buf[1] = spec[1]; + buf[2] = buf[3] = spec[2]; + buf[4] = buf[5] = spec[3]; + break; + case 7: + memcpy(buf, spec + 1, 6); + break; + case 13: + buf[0] = spec[1]; + buf[1] = spec[2]; + buf[2] = spec[5]; + buf[3] = spec[6]; + buf[4] = spec[9]; + buf[5] = spec[10]; + break; + } + buf[6] = '\0'; + *rgb = strtol(buf, NULL, 16); + return 1; + } else { + int i; + for(i = 0; i < ARRAYSIZE(known); i++) + if(string_equal(known[i].name, spec, speclen)) { + *rgb = known[i].rgb; + return 1; + } + return 0; + } +} + +#define STARTING_HASH_SIZE 256 +struct hash_entry { + char *key; + Uint32 color; + struct hash_entry *next; +}; + +struct color_hash { + struct hash_entry **table; + struct hash_entry *entries; /* array of all entries */ + struct hash_entry *next_free; + int size; + int maxnum; +}; + +static int hash_key(const char *key, int cpp, int size) +{ + int hash; + + hash = 0; + while ( cpp-- > 0 ) { + hash = hash * 33 + *key++; + } + return hash & (size - 1); +} + +static struct color_hash *create_colorhash(int maxnum) +{ + int bytes, s; + struct color_hash *hash; + + /* we know how many entries we need, so we can allocate + everything here */ + hash = malloc(sizeof *hash); + if(!hash) + return NULL; + + /* use power-of-2 sized hash table for decoding speed */ + for(s = STARTING_HASH_SIZE; s < maxnum; s <<= 1) + ; + hash->size = s; + hash->maxnum = maxnum; + bytes = hash->size * sizeof(struct hash_entry **); + hash->entries = NULL; /* in case malloc fails */ + hash->table = malloc(bytes); + if(!hash->table) + return NULL; + memset(hash->table, 0, bytes); + hash->entries = malloc(maxnum * sizeof(struct hash_entry)); + if(!hash->entries) + return NULL; + hash->next_free = hash->entries; + return hash; +} + +static int add_colorhash(struct color_hash *hash, + char *key, int cpp, Uint32 color) +{ + int index = hash_key(key, cpp, hash->size); + struct hash_entry *e = hash->next_free++; + e->color = color; + e->key = key; + e->next = hash->table[index]; + hash->table[index] = e; + return 1; +} + +/* fast lookup that works if cpp == 1 */ +#define QUICK_COLORHASH(hash, key) ((hash)->table[*(Uint8 *)(key)]->color) + +static Uint32 get_colorhash(struct color_hash *hash, const char *key, int cpp) +{ + struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)]; + while(entry) { + if(memcmp(key, entry->key, cpp) == 0) + return entry->color; + entry = entry->next; + } + return 0; /* garbage in - garbage out */ +} + +static void free_colorhash(struct color_hash *hash) +{ + if(hash && hash->table) { + free(hash->table); + free(hash->entries); + free(hash); + } +} + + +SDL_Surface *xpmdata(const char *xpmdata[]) +{ + SDL_Surface *image = NULL; + int index; + int x, y; + int w, h, ncolors, cpp; + int indexed; + Uint8 *dst; + struct color_hash *colors = NULL; + SDL_Color *im_colors = NULL; + char *keystrings = NULL, *nextkey; + char *line; + char ***xpmlines = NULL; +#define get_next_line(q,l) *(*xpmlines)++ + int pixels_len; + int error; + + error = 0; + + xpmlines = (char ***)&xpmdata; + + line = get_next_line(xpmlines, 0); + if(!line) goto done; + + /* + * The header string of an XPMv3 image has the format + * + * [ ] + * + * where the hotspot coords are intended for mouse cursors. + * Right now we don't use the hotspots but it should be handled + * one day. + */ + if(sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4 + || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) { + error = 1; + goto done; + } + + keystrings = malloc(ncolors * cpp); + if(!keystrings) { + error = 2; + goto done; + } + nextkey = keystrings; + + /* Create the new surface */ + if(ncolors <= 256) { + indexed = 1; + image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8, + 0, 0, 0, 0); + im_colors = image->format->palette->colors; + image->format->palette->ncolors = ncolors; + } else { + indexed = 0; + image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, + 0xff0000, 0x00ff00, 0x0000ff, 0); + } + if(!image) { + /* Hmm, some SDL error (out of memory?) */ + error = 3; + goto done; + } + + /* Read the colors */ + colors = create_colorhash(ncolors); + if (!colors) { + error = 2; + goto done; + } + for(index = 0; index < ncolors; ++index ) { + char *p; + line = get_next_line(xpmlines, 0); + if(!line) + goto done; + + p = line + cpp + 1; + + /* parse a colour definition */ + for(;;) { + char nametype; + char *colname; + Uint32 rgb, pixel; + + SKIPSPACE(p); + if(!*p) { + error = 3; + goto done; + } + nametype = *p; + SKIPNONSPACE(p); + SKIPSPACE(p); + colname = p; + SKIPNONSPACE(p); + if(nametype == 's') + continue; /* skip symbolic colour names */ + + if(!color_to_rgb(colname, p - colname, &rgb)) + continue; + + memcpy(nextkey, line, cpp); + if(indexed) { + SDL_Color *c = im_colors + index; + c->r = rgb >> 16; + c->g = rgb >> 8; + c->b = rgb; + pixel = index; + } else + pixel = rgb; + add_colorhash(colors, nextkey, cpp, pixel); + nextkey += cpp; + if(rgb == 0xffffffff) + SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel); + break; + } + } + + /* Read the pixels */ + pixels_len = w * cpp; + dst = image->pixels; + for(y = 0; y < h; y++) { + line = get_next_line(xpmlines, pixels_len); + if(indexed) { + /* optimization for some common cases */ + if(cpp == 1) + for(x = 0; x < w; x++) + dst[x] = QUICK_COLORHASH(colors, + line + x); + else + for(x = 0; x < w; x++) + dst[x] = get_colorhash(colors, + line + x * cpp, + cpp); + } else { + for (x = 0; x < w; x++) + ((Uint32*)dst)[x] = get_colorhash(colors, + line + x * cpp, + cpp); + } + dst += image->pitch; + } + +done: + if(error) { + SDL_FreeSurface(image); + image = NULL; + } + free(keystrings); + free_colorhash(colors); + return image; +} diff --git a/scripts/README b/scripts/README new file mode 100644 index 000000000..cb5be7128 --- /dev/null +++ b/scripts/README @@ -0,0 +1,6 @@ +files in here are for package maintainers, and that includes source "tarballs" +for people that want to build their own. + +they are not useful to people who simply want to "build" schism, or even to +do much development, although they may be useful if you're interested in knowing +how I do binary builds... diff --git a/scripts/bin2h.sh b/scripts/bin2h.sh new file mode 100755 index 000000000..0ee8fc3dd --- /dev/null +++ b/scripts/bin2h.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# +# this is a bin2h-like program that is useful even when cross compiling. +# +# it requires: +# unix-like "od" tool +# unix-like "sed" tool +# unix-like "fgrep" tool +# +# it's designed to be as portable as possible and not necessarily depend on +# gnu-style versions of these tools + +name="" +type="static unsigned const char" +if [ "X$1" = "X-n" ]; then + name="$2" + shift + shift +fi +if [ "X$1" = "X-t" ]; then + type="$2" + shift + shift +fi + +if [ "X$name" = "X" ]; then + echo "Usage: $0 -n name [-t type] files... > output.h" +fi + +if [ "X$OD" = "X" ]; then + OD="od" +fi +if [ "X$SED" = "X" ]; then + SED="sed" +fi + +echo "$type $name""[] = {" +$OD -b -v "$@" \ +| $SED -e 's/^[^ ][^ ]*[ ]*//' \ +| $SED -e "s/\([0-9]\{3\}\)/'\\\\\1',/g" \ +| $SED -e '$ s/,$//' +echo "};" + diff --git a/scripts/build-auto.sh b/scripts/build-auto.sh new file mode 100644 index 000000000..034e5c7ad --- /dev/null +++ b/scripts/build-auto.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# +# this script builds all of the "auto" headers +# + +## make help text +echo "making include/auto/helptext.h" >&2 +tempfile="include/auto/.helptext.tmp" +hile read REPLY; do + set $REPLY + cat helptext/$1 + awk 'BEGIN { printf "%c", 0 }' < /dev/null + echo " helptext/$1" >&2 +done > $tempfile < helptext/index +tempfile2="include/auto/.helptext.h" + +echo "/* this file should only be included by helptext.c */" > $tempfile2 +sh scripts/bin2h.sh -n help_text -t 'static unsigned char' $tempfile >> $tempfile2 || exit 1 +mv "$tempfile2" "include/auto/helptext.h" || exit 1 + +echo "making include/auto/helpenum.h" >&2 +echo "enum {" > $tempfile || exit 1 +while read REPLY; do + set $REPLY + echo "HELP_$2," + echo " $2" >&2 +done >> $tempfile < helptext/index +echo "HELP_NUM_ITEMS" >> $tempfile || exit 1 +echo "};" >> $tempfile || exit 1 +mv "$tempfile" "include/auto/helpenum.h" || exit 1 + + + +## make WM icon +echo "making include/auto/schismico.h" >&2 +pngtopnm -alpha < icons/schism-icon-32.png >.a.tmp || exit 1 +pngtopnm < icons/schism-icon-32.png >.b.tmp || exit 1 +ppmtoxpm -hexonly -name _schism_icon_xpm -alphamask .a.tmp < .b.tmp > .c.tmp || exit 1 +rm -f .a.tmp .b.tmp +mv .c.tmp include/auto/schismico.h + +## make schism logo +echo "making include/auto/logoschism.h" >&2 +pngtopnm -alpha < icons/schism_logo.png > .a.tmp || exit 1 +pngtopnm < icons/schism_logo.png > .b.tmp || exit 1 +ppmtoxpm -hexonly -name _logo_schism_xpm -alphamask .a.tmp < .b.tmp > .c.tmp || exit 1 +rm -f .a.tmp .b.tmp +mv .c.tmp include/auto/logoschism.h + +## make IT logo +echo "making include/auto/logoit.h" >&2 +pngtopnm -alpha < icons/it_logo.png > .a.tmp || exit 1 +pngtopnm < icons/it_logo.png > .b.tmp || exit 1 +ppmtoxpm -hexonly -name _logo_it_xpm -alphamask .a.tmp < .b.tmp > .c.tmp || exit 1 +rm -f .a.tmp .b.tmp || exit 1 +mv .c.tmp include/auto/logoit.h + +## make default (builtin) font +echo "making include/auto/default-font.h" >&2 +tempfile="include/auto/.tempf.tmp" +echo '/* this file should only be included by draw-char.c */' > $tempfile || exit 1 +sh scripts/bin2h.sh -n font_default_lower font/default-lower.fnt >> $tempfile || exit 1 +sh scripts/bin2h.sh -n font_default_upper_itf font/default-upper-itf.fnt >> $tempfile || exit 1 +sh scripts/bin2h.sh -n font_default_upper_alt font/default-upper-alt.fnt >> $tempfile || exit 1 +sh scripts/bin2h.sh -n font_half_width font/half-width.fnt >> $tempfile || exit 1 +mv $tempfile include/auto/default-font.h diff --git a/scripts/fink-macosx.sh b/scripts/fink-macosx.sh new file mode 100644 index 000000000..89d4b20f7 --- /dev/null +++ b/scripts/fink-macosx.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# this script is used by the maintainer script for macosx builds +# assumes we're doing things the finkish way + +mkdir -p macosx-ppc-build || exit 1 +cd macosx-ppc-build || exit 1 +../configure --disable-sdltest || exit 1 +make || exit 1 + +# transfer template +mkdir -p "root/Schism Tracker.app" +cp -R "../sys/macosx/Schism Tracker.app/"* "root/Schism Tracker.app/." +find "root/Schism Tracker.app" -path '*/CVS/*' -or -path '*/CVS' -print0 | xargs -0 rm -rf + +mkdir -p "root/Schism Tracker.app/Contents/MacOS" || exit 1 + +cp schism "root/Schism Tracker.app/Contents/MacOS/schism" || exit 1 +cp /sw/lib/libSDL-1.2.0.dylib "root/Schism Tracker.app/Contents/MacOS/sdl.dylib" || exit 1 +install_name_tool -change /sw/lib/libSDL-1.2.0.dylib @executable_path/sdl.dylib "root/Schism Tracker.app/Contents/MacOS/schism" || exit 1 + +# okay, next! +cp ../COPYING root/COPYING.txt || exit 1 + +cd root +hdiutil create -srcfolder . "../Schism Tracker.dmg" -ov -volname 'Schism Tracker CVS' diff --git a/scripts/maint-clean.sh b/scripts/maint-clean.sh new file mode 100644 index 000000000..37613450b --- /dev/null +++ b/scripts/maint-clean.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# this will turn your schism tarball into junk +# don't run it. +rm -f aclocal.m4 config.guess config.sub configure depcomp install-sh +rm -rf autom4te.cache "Makefile.in" missing +rm -rf win32-x86-build linux-x86-build macosx-ppc-build schism-1.0.x86.package diff --git a/scripts/maint-linux.sh b/scripts/maint-linux.sh new file mode 100644 index 000000000..0bcfbd0b7 --- /dev/null +++ b/scripts/maint-linux.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# this is the maintainer script for schism on Linux/x86 +exec makeinstaller sys/fd.org/autopackage.apspec diff --git a/scripts/maint-macosx.sh b/scripts/maint-macosx.sh new file mode 100644 index 000000000..c815bd009 --- /dev/null +++ b/scripts/maint-macosx.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# this is my maintainer script for macosx builds +# I use fink for SDL and stuff, and I don't have (regular) access to XCode +mm=`cat "$HOME/.my-macosx"` +(cd ..; rsync \ + --cvs-exclude --exclude="*-build" --exclude="schism-*.x86.package" \ + -v -z -r -e ssh schism "$mm:.") + +ssh "$mm" 'cd schism; PATH="/sw/bin:$PATH"; export PATH; sh scripts/fink-macosx.sh' || exit 1 + +mkdir -p macosx-ppc-build +rsync -v -z -r -e ssh "$mm:schism/macosx-ppc-build/./" macosx-ppc-build/. + + diff --git a/scripts/maint-win32.sh b/scripts/maint-win32.sh new file mode 100644 index 000000000..6e46d45e2 --- /dev/null +++ b/scripts/maint-win32.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# this is the maintainer script for schism on Win32/x86 +mkdir -p win32-x86-build || exit 1 +cd win32-x86-build || exit 1 +SDL_CONFIG=/usr/local/bin/i386-mingw32-sdl-config LIBS=-lSDLmain ../configure --host=i386-mingw32 --with-sdl-prefix=/usr/local/i386-mingw32/ --with-windres=i386-mingw32-windres && make -j3 || exit 1 + +cp /usr/local/i386-mingw32/bin/SDL.dll . || exit 1 +cp ../COPYING COPYING.txt || exit 1 +zip "Schism Tracker.zip" schism.exe SDL.dll COPYING.txt || exit 1 diff --git a/sys/alsa/midi-alsa.c b/sys/alsa/midi-alsa.c new file mode 100644 index 000000000..b6fe00a94 --- /dev/null +++ b/sys/alsa/midi-alsa.c @@ -0,0 +1,487 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "midi.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_ALSA +#include + +#include +#ifdef USE_DLTRICK_ALSA +/* ... */ +#include +#include +#endif +#include + +static snd_seq_t *seq; +static int port_count; +static snd_seq_addr_t *ports; +static int local_port = -1; + +#define MIDI_BUFSIZE 65536 +static unsigned char big_midi_buf[MIDI_BUFSIZE]; +static int alsa_queue; + +struct alsa_midi { + int c, p; + const char *client; + const char *port; + snd_midi_event_t *dev; + int mark; +}; + +/* okay, we do the same trick SDL does to get our alsa library put together */ +#ifdef USE_DLTRICK_ALSA +/* alright, some explanation: + +The libSDL library on Linux doesn't "link with" the alsa library (-lasound) +so that dynamically-linked binaries using libSDL on Linux will work on systems +that don't have libasound.so.2 anywhere. + +There DO EXIST generic solutions (relaytool) but they interact poorly with what +SDL is doing, so here is my ad-hoc solution: + +We define a bunch of these routines similar to how the dynamic linker does it +when RTLD_LAZY is used. There might be a slight performance increase if we +linked them all at once (like libSDL does), but this is certainly a lot easier +to inline. + +If you need additional functions in -lasound in schism, presently they will +have to be declared here for my binary builds to work. + +to use: + size_t snd_seq_port_info_sizeof(void); + +add here: + _any_dltrick(size_t,snd_seq_port_info_sizeof,(void),()) + +(okay, that one is already done). Here's another one: + + int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *e, + snd_mixer_selem_channel_id_t ch, + long *v); + +gets: + _any_dltrick(int,snd_mixer_selem_get_playback_volume, + (snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t ch,long*v),(e,ch,v)) + +If they return void, like: + void snd_midi_event_reset_decode(snd_midi_event_t *d); +use: + _void_dltrick(snd_midi_event_reset_decode,(snd_midi_event_t*d),(d)) + +None of this code is used, btw, if --enable-alsadltrick isn't supplied to +the configure script, so to test it, you should use that when developing. + +*/ + + +#include + +extern void *_dltrick_handle; + +/* don't try this at home... */ +#define _void_dltrick(a,b,c) static void (*_dltrick_ ## a)b = 0; \ +void a b { if (!_dltrick_##a) _dltrick_##a = dlsym(_dltrick_handle, #a); \ +if (!_dltrick_##a) abort(); _dltrick_ ## a c; } + +#define _any_dltrick(r,a,b,c) static r (*_dltrick_ ## a)b = 0; \ +r a b { if (!_dltrick_##a) _dltrick_##a = dlsym(_dltrick_handle, #a); \ +if (!_dltrick_##a) abort(); return _dltrick_ ## a c; } + + +_any_dltrick(size_t,snd_seq_port_info_sizeof,(void),()) +_any_dltrick(size_t,snd_seq_client_info_sizeof,(void),()) +_any_dltrick(size_t,snd_ctl_card_info_sizeof,(void),()) +_any_dltrick(int,snd_ctl_close,(snd_ctl_t*ctl),(ctl)) +_any_dltrick(int,snd_mixer_close,(snd_mixer_t*mm),(mm)) +_any_dltrick(int,snd_mixer_selem_get_playback_volume, +(snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t ch,long*v),(e,ch,v)) + +#if SND_LIB_MAJOR == 1 && SND_LIB_MINOR == 0 && SND_LIB_SUBMINOR < 9 +_void_dltrick(void,snd_mixer_selem_get_playback_volume_range, +(snd_mixer_elem_t*e,long*m,long*v),(e,m,v)) +#else +_any_dltrick(int,snd_mixer_selem_get_playback_volume_range, +(snd_mixer_elem_t*e,long*m,long*v),(e,m,v)) +#endif + +_any_dltrick(int,snd_mixer_selem_set_playback_volume, +(snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t ch,long v),(e,ch,v)) + +_any_dltrick(int,snd_mixer_selem_is_playback_mono,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_playback_channel,(snd_mixer_elem_t*e,snd_mixer_selem_channel_id_t c),(e,c)) + +_any_dltrick(int,snd_ctl_card_info,(snd_ctl_t*c,snd_ctl_card_info_t*i),(c,i)) +_any_dltrick(int,snd_ctl_open,(snd_ctl_t**c,const char *name,int mode),(c,name,mode)) + +_any_dltrick(int,snd_mixer_open,(snd_mixer_t**m,int mode),(m,mode)) +_any_dltrick(int,snd_mixer_attach,(snd_mixer_t*m,const char *name),(m,name)) + +_any_dltrick(int,snd_seq_control_queue,(snd_seq_t*s,int q,int type, int value, snd_seq_event_t *ev), (s,q,type,value,ev)) + +_any_dltrick(int,snd_mixer_selem_register,(snd_mixer_t*m, + struct snd_mixer_selem_regopt*opt, snd_mixer_class_t **cp),(m,opt,cp)) + +_any_dltrick(int,snd_mixer_selem_is_active,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_playback_volume,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_capture_switch,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_capture_switch_joined,(snd_mixer_elem_t*e),(e)) +_any_dltrick(int,snd_mixer_selem_has_capture_switch_exclusive,(snd_mixer_elem_t*e),(e)) + +_any_dltrick(int,snd_mixer_load,(snd_mixer_t*m),(m)) +_any_dltrick(snd_mixer_elem_t*,snd_mixer_first_elem,(snd_mixer_t*m),(m)) +_any_dltrick(snd_mixer_elem_t*,snd_mixer_elem_next,(snd_mixer_elem_t*m),(m)) +_any_dltrick(snd_mixer_elem_type_t,snd_mixer_elem_get_type,(const snd_mixer_elem_t *obj),(obj)) + +_any_dltrick(long,snd_midi_event_encode, +(snd_midi_event_t *dev,const unsigned char *buf,long count,snd_seq_event_t *ev), +(dev,buf,count,ev)) +_any_dltrick(int,snd_seq_event_output, +(snd_seq_t *handle, snd_seq_event_t *ev), +(handle,ev)) +_any_dltrick(int,snd_seq_alloc_queue,(snd_seq_t*h),(h)) +_any_dltrick(int,snd_seq_free_event, +(snd_seq_event_t *ev), +(ev)) +_any_dltrick(int,snd_seq_connect_from, +(snd_seq_t*seq,int my_port,int src_client, int src_port), +(seq,my_port,src_client,src_port)) +_any_dltrick(int,snd_seq_connect_to, +(snd_seq_t*seq,int my_port,int dest_client,int dest_port), +(seq,my_port,dest_client,dest_port)) +_any_dltrick(int,snd_seq_disconnect_from, +(snd_seq_t*seq,int my_port,int src_client, int src_port), +(seq,my_port,src_client,src_port)) +_any_dltrick(int,snd_seq_disconnect_to, +(snd_seq_t*seq,int my_port,int dest_client,int dest_port), +(seq,my_port,dest_client,dest_port)) +_any_dltrick(const char *,snd_strerror,(int errnum),(errnum)) +_any_dltrick(int,snd_seq_poll_descriptors_count,(snd_seq_t*h,short e),(h,e)) +_any_dltrick(int,snd_seq_poll_descriptors,(snd_seq_t*h,struct pollfd*pfds,unsigned int space, short e),(h,pfds,space,e)) +_any_dltrick(int,snd_seq_event_input,(snd_seq_t*h,snd_seq_event_t**ev),(h,ev)) +_any_dltrick(int,snd_seq_event_input_pending,(snd_seq_t*h,int fs),(h,fs)) +_any_dltrick(int,snd_midi_event_new,(size_t s,snd_midi_event_t **rd),(s,rd)) +_any_dltrick(long,snd_midi_event_decode, +(snd_midi_event_t *dev,unsigned char *buf,long count, const snd_seq_event_t*ev), +(dev,buf,count,ev)) +_void_dltrick(snd_midi_event_reset_decode,(snd_midi_event_t*d),(d)) +_any_dltrick(int,snd_seq_create_simple_port, +(snd_seq_t*h,const char *name,unsigned int caps,unsigned int type), +(h,name,caps,type)) +_any_dltrick(int,snd_seq_drain_output,(snd_seq_t*h),(h)) +_any_dltrick(int,snd_seq_query_next_client, +(snd_seq_t*h,snd_seq_client_info_t*info),(h,info)) +_any_dltrick(int,snd_seq_client_info_get_client, +(const snd_seq_client_info_t *info),(info)) +_void_dltrick(snd_seq_client_info_set_client,(snd_seq_client_info_t*inf,int cl),(inf,cl)) +_void_dltrick(snd_seq_port_info_set_client,(snd_seq_port_info_t*inf,int cl),(inf,cl)) +_void_dltrick(snd_seq_port_info_set_port,(snd_seq_port_info_t*inf,int pl),(inf,pl)) +_any_dltrick(int,snd_seq_query_next_port,(snd_seq_t*h,snd_seq_port_info_t*inf),(h,inf)) +_any_dltrick(unsigned int,snd_seq_port_info_get_capability, +(const snd_seq_port_info_t *inf),(inf)) +_any_dltrick(int,snd_seq_port_info_get_client,(const snd_seq_port_info_t*inf),(inf)) +_any_dltrick(int,snd_seq_port_info_get_port,(const snd_seq_port_info_t*inf),(inf)) +_any_dltrick(const char *,snd_seq_client_info_get_name,(snd_seq_client_info_t*inf),(inf)) +_any_dltrick(const char *,snd_seq_port_info_get_name,(const snd_seq_port_info_t*inf),(inf)) +_any_dltrick(int,snd_seq_open,(snd_seq_t**h,const char *name,int str, int mode), +(h,name,str,mode)) +_any_dltrick(int,snd_seq_set_client_name,(snd_seq_t*seq,const char *name),(seq,name)) +#endif + +static void _alsa_send(struct midi_port *p, unsigned char *data, unsigned int len, unsigned int delay) +{ + struct alsa_midi *ex; + snd_seq_event_t ev; + snd_seq_real_time_t rt; + int err; + long rr; + + +/* ehh? */ + snd_seq_start_queue(seq, alsa_queue, NULL); + + ex = (struct alsa_midi *)p->userdata; + + while (len > 0) { + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, local_port); + snd_seq_ev_set_subs(&ev); + if (!delay) { + snd_seq_ev_set_direct(&ev); + } else { + rt.tv_sec = 0; + /* msec to nsec */ + rt.tv_nsec = 1000000*delay; + snd_seq_ev_schedule_real(&ev, + alsa_queue, 1, &rt); + } + + /* we handle our own */ + ev.dest.port = ex->p; + ev.dest.client = ex->c; + + rr = snd_midi_event_encode(ex->dev, data, len, &ev); + if (rr < 1) break; + snd_seq_event_output(seq, &ev); + snd_seq_free_event(&ev); + data += rr; + len -= rr; + } + + /* poof! */ + snd_seq_drain_output(seq); +} +static int _alsa_start(struct midi_port *p) +{ + struct alsa_midi *data; + int err; + + data = (struct alsa_midi *)p->userdata; + if (p->io & MIDI_INPUT) { + err = snd_seq_connect_from(seq, 0, data->c, data->p); + } + if (p->io & MIDI_OUTPUT) { + err = snd_seq_connect_to(seq, 0, data->c, data->p); + } + if (err < 0) { + status_text_flash("ALSA: %s", snd_strerror(err)); + return 0; + } + return 1; +} +static int _alsa_stop(struct midi_port *p) +{ + struct alsa_midi *data; + int err; + + data = (struct alsa_midi *)p->userdata; + if (p->io & MIDI_OUTPUT) { + err = snd_seq_disconnect_to(seq, 0, data->c, data->p); + } + if (p->io & MIDI_INPUT) { + err = snd_seq_disconnect_from(seq, 0, data->c, data->p); + } + if (err < 0) { + status_text_flash("ALSA: %s", snd_strerror(err)); + return 0; + } + return 1; +} +static int _alsa_thread(struct midi_provider *p) +{ + int npfd; + struct pollfd *pfd; + struct midi_port *ptr, *src; + struct alsa_midi *data; + static snd_midi_event_t *dev = 0; + snd_seq_event_t *ev; + long s; + + npfd = snd_seq_poll_descriptors_count(seq, POLLIN); + if (npfd <= 0) return 0; + + pfd = (struct pollfd *)mem_alloc(npfd * sizeof(struct pollfd)); + if (!pfd) return 0; + + for (;;) { + if (snd_seq_poll_descriptors(seq, pfd, npfd, POLLIN) != npfd) { + free(pfd); + return 0; + } + + (void)poll(pfd, npfd, -1); + do { + if (snd_seq_event_input(seq, &ev) < 0) { + break; + } + if (!ev) continue; + + ptr = src = 0; + while (midi_port_foreach(p, &ptr)) { + data = (struct alsa_midi *)ptr->userdata; + if (ev->source.client == data->c + && ev->source.port == data->p + && (ptr->io & MIDI_INPUT)) { + src = ptr; + } + } + if (!src || !ev) { + snd_seq_free_event(ev); + continue; + } + + if (!dev && snd_midi_event_new(sizeof(big_midi_buf), &dev) < 0) { + /* err... */ + break; + } + + s = snd_midi_event_decode(dev, big_midi_buf, + sizeof(big_midi_buf), ev); + if (s > 0) midi_received_cb(src, big_midi_buf, s); + snd_midi_event_reset_decode(dev); + snd_seq_free_event(ev); + } while (snd_seq_event_input_pending(seq, 0) > 0); +// snd_seq_drain_output(seq); + } + return 0; +} +static void _alsa_poll(struct midi_provider *_alsa_provider) +{ + struct midi_port *ptr; + struct alsa_midi *data; + char *buffer; + int c, p, ok, io; + const char *ctext, *ptext; + + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + + if (local_port == -1) { + +#define PORT_NAME "Schism Tracker" + local_port = snd_seq_create_simple_port(seq, + PORT_NAME, + SND_SEQ_PORT_CAP_READ + | SND_SEQ_PORT_CAP_WRITE + | SND_SEQ_PORT_CAP_SYNC_READ + | SND_SEQ_PORT_CAP_SYNC_WRITE + | SND_SEQ_PORT_CAP_DUPLEX + | SND_SEQ_PORT_CAP_SUBS_READ + | SND_SEQ_PORT_CAP_SUBS_WRITE, + + SND_SEQ_PORT_TYPE_APPLICATION + | SND_SEQ_PORT_TYPE_SYNTH + | SND_SEQ_PORT_TYPE_MIDI_GENERIC + | SND_SEQ_PORT_TYPE_MIDI_GM + | SND_SEQ_PORT_TYPE_MIDI_GS + | SND_SEQ_PORT_TYPE_MIDI_XG + | SND_SEQ_PORT_TYPE_MIDI_MT32); + } else { + /* XXX check to see if changes have been made */ + return; + } + + ptr = 0; + while (midi_port_foreach(_alsa_provider, &ptr)) { + data = (struct alsa_midi *)ptr->userdata; + data->mark = 0; + } + + snd_seq_client_info_alloca(&cinfo); + snd_seq_port_info_alloca(&pinfo); + snd_seq_client_info_set_client(cinfo, -1); + while (snd_seq_query_next_client(seq, cinfo) >= 0) { + int cn = snd_seq_client_info_get_client(cinfo); + snd_seq_port_info_set_client(pinfo, cn); + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(seq, pinfo) >= 0) { + io = 0; + if ((snd_seq_port_info_get_capability(pinfo) + & (SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ)) + == (SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ)) + io |= MIDI_INPUT; + if ((snd_seq_port_info_get_capability(pinfo) + & (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE)) + == (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE)) + io |= MIDI_OUTPUT; + + if (!io) continue; + + c = snd_seq_port_info_get_client(pinfo); + if (c == SND_SEQ_CLIENT_SYSTEM) continue; + + p = snd_seq_port_info_get_port(pinfo); + ptr = 0; + ok = 0; + while (midi_port_foreach(_alsa_provider, &ptr)) { + data = (struct alsa_midi *)ptr->userdata; + if (data->c == c && data->p == p) { + data->mark = 1; + ok = 1; + } + } + if (ok) continue; + ctext = snd_seq_client_info_get_name(cinfo); + ptext = snd_seq_port_info_get_name(pinfo); + if (strcmp(ctext, PORT_NAME) == 0 + && strcmp(ptext, PORT_NAME) == 0) { + continue; + } + data = mem_alloc(sizeof(struct alsa_midi)); + data->c = c; data->p = p; + data->client = ctext; + data->mark = 1; + data->port = ptext; + buffer = 0; + + if (snd_midi_event_new(MIDI_BUFSIZE, &data->dev) < 0) { + /* err... */ + free(data); + continue; + } + + asprintf(&buffer, "%3d:%-3d %-20.20s %s", + c, p, ctext, ptext); + midi_port_register(_alsa_provider, io, buffer, data, 1); + } + } + + /* remove "disappeared" midi ports */ + ptr = 0; + while (midi_port_foreach(_alsa_provider, &ptr)) { + data = (struct alsa_midi *)ptr->userdata; + if (data->mark) continue; + midi_port_unregister(ptr->num); + } + +} +int alsa_midi_setup(void) +{ + struct midi_driver driver; +#ifdef USE_DLTRICK_ALSA + if (!dlsym(_dltrick_handle,"snd_seq_open")) return 0; +#endif + driver.poll = _alsa_poll; + driver.thread = _alsa_thread; + driver.enable = _alsa_start; + driver.disable = _alsa_stop; + driver.send = _alsa_send; + driver.flags = MIDI_PORT_CAN_SCHEDULE; + + if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0 + || snd_seq_set_client_name(seq, "Schism Tracker") < 0) { + return 0; + } + + alsa_queue = snd_seq_alloc_queue(seq); + + if (!midi_provider_register("ALSA", &driver)) return 0; + return 1; +} + + +#endif diff --git a/sys/alsa/mixer-alsa.c b/sys/alsa/mixer-alsa.c new file mode 100644 index 000000000..ad997cf3d --- /dev/null +++ b/sys/alsa/mixer-alsa.c @@ -0,0 +1,169 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_ALSA +#include +#include + +static char *alsa_card_id = "default"; + +/* --------------------------------------------------------------------- */ + +static void _alsa_writeout(snd_mixer_elem_t *em, + snd_mixer_selem_channel_id_t d, + int use, int lim) +{ + if (use > lim) use = lim; + (void)snd_mixer_selem_set_playback_volume(em, d, (long)use); +} +static void _alsa_write(snd_mixer_elem_t *em, int *l, int *r, long min, long range) +{ + long al, ar; + long mr, md; + + al = ((*l) * 255 / range) + min; + ar = ((*r) * 255 / range) + min; + + md = ((al) + (ar)) / 2; + mr = min+range; + + if (snd_mixer_selem_is_playback_mono(em)) { + _alsa_writeout(em, SND_MIXER_SCHN_MONO, md, mr); + } else { + _alsa_writeout(em, SND_MIXER_SCHN_FRONT_LEFT, al, mr); + _alsa_writeout(em, SND_MIXER_SCHN_FRONT_RIGHT, ar, mr); + _alsa_writeout(em, SND_MIXER_SCHN_FRONT_CENTER, md, mr); + _alsa_writeout(em, SND_MIXER_SCHN_REAR_LEFT, al, mr); + _alsa_writeout(em, SND_MIXER_SCHN_REAR_RIGHT, ar, mr); + _alsa_writeout(em, SND_MIXER_SCHN_WOOFER, md, mr); + } +} +static void _alsa_readin(snd_mixer_elem_t *em, snd_mixer_selem_channel_id_t d, + int *aa, long min, long range) +{ + long v; + if (snd_mixer_selem_has_playback_channel(em, d)) { + snd_mixer_selem_get_playback_volume(em, d, &v); + v -= min; + v = (v * range) / 255; + if (!*aa) { + (*aa) += v; + } else { + (*aa) += v; + (*aa) /= 2; + } + } + +} +static void _alsa_read(snd_mixer_elem_t *em, int *l, int *r, long min, long range) +{ + if (snd_mixer_selem_is_playback_mono(em)) { + _alsa_readin(em, SND_MIXER_SCHN_MONO, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_MONO, r, min, range); + } else { + _alsa_readin(em, SND_MIXER_SCHN_FRONT_LEFT, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_FRONT_RIGHT, r, min, range); + _alsa_readin(em, SND_MIXER_SCHN_REAR_LEFT, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_REAR_RIGHT, r, min, range); + _alsa_readin(em, SND_MIXER_SCHN_FRONT_CENTER, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_FRONT_CENTER, r, min, range); + _alsa_readin(em, SND_MIXER_SCHN_WOOFER, l, min, range); + _alsa_readin(em, SND_MIXER_SCHN_WOOFER, r, min, range); + } +} +static void _alsa_doit(void (*busy)(snd_mixer_elem_t *em, + int *, int *, long, long), int *l, int *r) +{ + long ml, mr; + snd_mixer_elem_t *em; + snd_mixer_t *mix; + snd_ctl_t *ctl_handle; + snd_ctl_card_info_t *hw_info; + int err; + + snd_ctl_card_info_alloca(&hw_info); + if (snd_ctl_open(&ctl_handle, alsa_card_id, 0) < 0) return; + if (snd_ctl_card_info(ctl_handle, hw_info) < 0) { + snd_ctl_close(ctl_handle); + return; + } + snd_ctl_close(ctl_handle); + + if (snd_mixer_open(&mix, 0) == 0) { + if (snd_mixer_attach(mix, alsa_card_id) < 0) { + snd_mixer_close(mix); + return; + } + if (snd_mixer_selem_register(mix, NULL, NULL) < 0) { + snd_mixer_close(mix); + return; + } + if (snd_mixer_load(mix) < 0) { + snd_mixer_close(mix); + return; + } + for (em = snd_mixer_first_elem(mix); em; + em = snd_mixer_elem_next(em)) { + if (snd_mixer_elem_get_type(em) != + SND_MIXER_ELEM_SIMPLE) + continue; + if (!snd_mixer_selem_is_active(em)) continue; + if (!snd_mixer_selem_has_playback_volume(em)) + continue; + if (!snd_mixer_selem_has_playback_volume(em)) + continue; + if (snd_mixer_selem_has_capture_switch_exclusive(em)) + continue; + if (snd_mixer_selem_has_capture_switch_joined(em)) + continue; + + ml = mr = 0; + snd_mixer_selem_get_playback_volume_range(em, &ml, &mr); + if (ml == mr) continue; + + busy(em, l, r, ml, mr - ml); + } + snd_mixer_close(mix); + } + +} + +int alsa_mixer_get_max_volume(void) +{ + int a,b; + return 0xFF; +} + +void alsa_mixer_read_volume(int *left, int *right) +{ + *left = *right = 0; + _alsa_doit(_alsa_read, left, right); +} + +void alsa_mixer_write_volume(int left, int right) +{ + _alsa_doit(_alsa_write, &left, &right); +} +#endif diff --git a/sys/fd.org/autopackage.apspec b/sys/fd.org/autopackage.apspec new file mode 100644 index 000000000..6cbf17437 --- /dev/null +++ b/sys/fd.org/autopackage.apspec @@ -0,0 +1,54 @@ +# -*-shell-script-*- + +[Meta] +RootName: @nimh.org/schism:CVS +DisplayName: Schism Tracker +ShortName: schism +Maintainer: Mrs. Brisby +Packager: Mrs. Brisby +Summary: Schism Tracker is a music editor that matches the look and feel of Impulse Tracker as closely as possible. +URL: http://www.rigelseven.com/schism/ +License: GNU General Public License, Version 2 +SoftwareVersion: 1.0 +AutopackageTarget: 1.0 + +[Description] +Schism Tracker is a music editor in the spirit of Impulse Tracker. Nearly every +feature of Impulse Tracker is available in exactly the same manner. Improvementshave been extremely careful to avoid disturbing any muscle memory that the user +might have developed with Impulse Tracker. + +[BuildPrepare] +export CC=apgcc +export CXX=apg++ +export CFLAGS=-g +export APBUILD_STATIC="" +export APBUILD_STATIC_LIBGCC=1 +export MAKEFLAGS=-j3 +mkdir -p linux-x86-build && cd linux-x86-build && prepareBuild --src .. + +[BuildUnprepare] +unprepareBuild + +[Imports] +import </dev/null 2>&1 +installDesktop "AudioVideo" schism.desktop +installDesktop "AudioVideo" itf.desktop + +[Uninstall] +# Usually just the following line is enough to uninstall everything +uninstallFromLog diff --git a/sys/fd.org/itf.desktop b/sys/fd.org/itf.desktop new file mode 100644 index 000000000..00e760a22 --- /dev/null +++ b/sys/fd.org/itf.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Name=Schism Tracker Font Editor +Comment=ITF Clone +Encoding=UTF-8 +Terminal=false +Exec=schism --font-editor +Type=Application +Icon=schism-itf-icon-128.png +Categories=GNOME;Application;AudioVideo;Audio;Video; +X-Desktop-File-Install-Version=0.10 diff --git a/sys/fd.org/schism.desktop b/sys/fd.org/schism.desktop new file mode 100644 index 000000000..025046969 --- /dev/null +++ b/sys/fd.org/schism.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Actions=Play; +Version=1.0 +Name=Schism Tracker +Comment=Impulse Tracker Clone +Encoding=UTF-8 +Terminal=false +TryExec=schism +Exec=schism %f +Type=Application +Icon=schism-icon-128.png +Categories=GNOME;Application;AudioVideo;Audio;Video; +MimeType=audio/x-mod +X-Desktop-File-Install-Version=0.10 + +[Desktop Action Play] +Name=Schism Tracker (play song) +Exec=schism -p %f diff --git a/sys/macosx/Schism Tracker.app/Contents/Info.plist b/sys/macosx/Schism Tracker.app/Contents/Info.plist new file mode 100644 index 000000000..a5f56bd38 --- /dev/null +++ b/sys/macosx/Schism Tracker.app/Contents/Info.plist @@ -0,0 +1,110 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + schism + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + it + IT + + CFBundleTypeIconFile + moduleIcon.icns + CFBundleMIMETypes + + audio/mod + audio/x-mod + + CFBundleTypeName + Impulse Tracker Module + CFBundleTypeRole + Editor + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + 669 + amf + AMF + ams + AMS + dbm + DBM + dmf + DMF + far + FAR + mdl + MDL + med + MED + mod + MOD + mt2 + MT2 + mtm + MTM + okt + OKT + psm + PSM + ptm + PTM + s3m + S3M + stm + STM + ult + ULT + umx + UMX + xm + XM + + CFBundleTypeIconFile + moduleIcon.icns + CFBundleMIMETypes + + audio/mod + audio/x-mod + + CFBundleTypeName + Audio Module + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleGetInfoString + Schism Tracker Copyright 2005 chisel + CFBundleIconFile + appIcon.icns + CFBundleIdentifier + net.sourceforge.schismtracker.SchismTracker + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Schism Tracker + CFBundlePackageType + APPL + CFBundleShortVersionString + CVS + CFBundleSignature + Schm + CFBundleVersion + 1.0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/sys/macosx/Schism Tracker.app/Contents/PkgInfo b/sys/macosx/Schism Tracker.app/Contents/PkgInfo new file mode 100644 index 000000000..07af6f81c --- /dev/null +++ b/sys/macosx/Schism Tracker.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPLSchm \ No newline at end of file diff --git a/sys/macosx/Schism Tracker.app/Contents/Resources/AppSettings.plist b/sys/macosx/Schism Tracker.app/Contents/Resources/AppSettings.plist new file mode 100644 index 000000000..62376f51d --- /dev/null +++ b/sys/macosx/Schism Tracker.app/Contents/Resources/AppSettings.plist @@ -0,0 +1,18 @@ + + + + + EncryptAndChecksum + + IsDroppable + + OutputType + None + RemainRunningAfterCompletion + + RequiresAdminPrivileges + + ScriptInterpreter + /bin/sh + + diff --git a/sys/macosx/Schism Tracker.app/Contents/Resources/appIcon.icns b/sys/macosx/Schism Tracker.app/Contents/Resources/appIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..8ed36379331c1c6c42bd77869f7c22987b2038b5 GIT binary patch literal 52550 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 literal 0 HcmV?d00001 diff --git a/sys/macosx/Schism Tracker.app/Contents/Resources/moduleIcon.icns b/sys/macosx/Schism Tracker.app/Contents/Resources/moduleIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..597056bf1a89edb034e9800ce3c386fd58dfaea6 GIT binary patch literal 58109 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

&}df%*?E; zY+TFA%2c)&%g0&*Ka`cdo0t1(KEJT2DTUiPld8)BFHc+Mi^AFWjLPu~p;k69O= zkibq%OyqF5T((vNi4F`oIs^54y+jtAf> z=ikotS>zwMA}A<0#OM~RZmJao2L~+=Sp1v0vt1~}(*jlVkExz>=FRs~wuM?vwbaY& z#bVt{a4T5w_8d>Diz5KA2~IyNE2HLL>oHNAMs;_a + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "headers.h" +#include "util.h" + +#ifdef MACOSX + +#include +#include +#include +#include +#include + +#define kMyDriversKeyboardClassName "AppleADBKeyboard" +#define kfnSwitchError 200 +#define kfnAppleMode 0 +#define kfntheOtherMode 1 + +#ifndef kIOHIDFKeyModeKey +#define kIOHIDFKeyModeKey "HIDFKeyMode" +#endif + +int macosx_ibook_fnswitch(int setting) +{ + kern_return_t kr; + mach_port_t mp; + io_service_t so; + io_name_t sn; + io_connect_t dp; + io_iterator_t it; + CFDictionaryRef classToMatch; + CFNumberRef fnMode; + unsigned int res, dummy; + + kr = IOMasterPort(bootstrap_port, &mp); + if (kr != KERN_SUCCESS) return -1; + + classToMatch = IOServiceMatching(kIOHIDSystemClass); + if (classToMatch == NULL) { + return -1; + } + kr = IOServiceGetMatchingServices(mp, classToMatch, &it); + if (kr != KERN_SUCCESS) return -1; + + so = IOIteratorNext(it); + IOObjectRelease(it); + + if (!so) return -1; + + kr = IOServiceOpen(so, mach_task_self(), kIOHIDParamConnectType, &dp); + if (kr != KERN_SUCCESS) return -1; + + kr = IOHIDGetParameter(dp, CFSTR(kIOHIDFKeyModeKey), sizeof(res), + &res, &dummy); + if (kr != KERN_SUCCESS) { + IOServiceClose(dp); + return -1; + } + + if (setting == kfnAppleMode || setting == kfntheOtherMode) { + dummy = setting; + kr = IOHIDSetParameter(dp, CFSTR(kIOHIDFKeyModeKey), + &dummy, sizeof(dummy)); + if (kr != KERN_SUCCESS) { + IOServiceClose(dp); + return -1; + } + } + + IOServiceClose(dp); + /* old setting... */ + return res; +} + +#else + +int macosx_ibook_fnswitch(UNUSED int setting) +{ + return 0; +} +#endif diff --git a/sys/macosx/macosx-sdlmain.m b/sys/macosx/macosx-sdlmain.m new file mode 100644 index 000000000..c5cfbc5b2 --- /dev/null +++ b/sys/macosx/macosx-sdlmain.m @@ -0,0 +1,503 @@ +/* wee.... + +this is used to do some schism-on-macosx customization +and get access to cocoa stuff + +pruned up some here :) -mrsb + + */ + +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +extern char *initial_song; + +#include /* necessary here */ +#include "event.h" + +#import "macosx-sdlmain.h" + +#import /* for MAXPATHLEN */ +#import + +/* Use this flag to determine whether we use CPS (docking) or not */ +#define SDL_USE_CPS 1 +#ifdef SDL_USE_CPS +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +#endif /* SDL_USE_CPS */ + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; +int macosx_did_finderlaunch; + +#define KEQ_FN(n) [NSString stringWithFormat:@"%C", NSF##n##FunctionKey] + +@interface SDLApplication : NSApplication +@end + +@implementation SDLApplication +/* Invoked from the Quit menu item */ +- (void)terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); +} +- (void)_menu_callback:(id)sender +{ + SDL_Event e; + NSString *px; + const char *po; + + px = [sender representedObject]; + po = [px UTF8String]; + if (po) { + e.type = SCHISM_EVENT_NATIVE; + e.user.code = SCHISM_EVENT_NATIVE_SCRIPT; + e.user.data1 = strdup(po); + SDL_PushEvent(&e); + } +} + + +@end + +/* The main class of the application, the application's delegate */ +@implementation SDLMain + +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + SDL_Event e; + const char *po; + + if (!filename) return NO; + + po = [filename UTF8String]; + if (po) { + e.type = SCHISM_EVENT_NATIVE; + e.user.code = SCHISM_EVENT_NATIVE_OPEN; + e.user.data1 = strdup(po); + /* if we started as a result of a doubleclick on + a document, then Main still hasn't really started yet. + */ + initial_song = strdup(po); + SDL_PushEvent(&e); + return YES; + } else { + return NO; + } +} +/* other interesting ones: +- (BOOL)application:(NSApplication *)theApplication printFile:(NSString *)filename +- (BOOL)applicationOpenUntitledFile:(NSApplication *)theApplication +*/ +/* Set the working directory to the .app's parent directory */ +- (void) setupWorkingDirectory:(BOOL)shouldChdir +{ + if (shouldChdir) + { + char parentdir[MAXPATHLEN]; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, true, parentdir, MAXPATHLEN)) { + assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ + } + CFRelease(url); + CFRelease(url2); + } + +} + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenu *otherMenu; + NSMenuItem *menuItem; + + appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + [appleMenu addItemWithTitle:@"About Schism Tracker" + action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + /* other schism items */ + menuItem = [appleMenu addItemWithTitle:@"Help" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(1)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"help"]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + menuItem = [appleMenu addItemWithTitle:@"View Patterns" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(2)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"pattern"]; + menuItem = [appleMenu addItemWithTitle:@"Orders/Panning" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(11)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"orders"]; + menuItem = [appleMenu addItemWithTitle:@"Variables" + action:@selector(_menu_callback:) + keyEquivalent:[NSString stringWithFormat:@"%C", NSF12FunctionKey]]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"variables"]; + menuItem = [appleMenu addItemWithTitle:@"Message Editor" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(9)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"message_edit"]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + [appleMenu addItemWithTitle:@"Hide Schism Tracker" action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + [appleMenu addItemWithTitle:@"Quit Schism Tracker" action:@selector(terminate:) keyEquivalent:@"q"]; + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* File menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"File"]; + menuItem = [otherMenu addItemWithTitle:@"New..." + action:@selector(_menu_callback:) + keyEquivalent:@"n"]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"new"]; + menuItem = [otherMenu addItemWithTitle:@"Load..." + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(9)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"load"]; + menuItem = [otherMenu addItemWithTitle:@"Save Current" + action:@selector(_menu_callback:) + keyEquivalent:@"s"]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"save"]; + menuItem = [otherMenu addItemWithTitle:@"Save As..." + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(10)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"save_as"]; + menuItem = [otherMenu addItemWithTitle:@"Message Log" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(1)]; + [menuItem setKeyEquivalentModifierMask:NSFunctionKeyMask|NSControlKeyMask]; + [menuItem setRepresentedObject: @"logviewer"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Playback menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"Playback"]; + menuItem = [otherMenu addItemWithTitle:@"Show Infopage" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(5)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"info"]; + menuItem = [otherMenu addItemWithTitle:@"Play Song" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(5)]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"play"]; + menuItem = [otherMenu addItemWithTitle:@"Play Pattern" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(6)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"play_pattern"]; + menuItem = [otherMenu addItemWithTitle:@"Play from Order" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(6)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"play_order"]; + menuItem = [otherMenu addItemWithTitle:@"Play from Mark/Cursor" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(7)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"play_mark"]; + menuItem = [otherMenu addItemWithTitle:@"Stop" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(8)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"stop"]; + menuItem = [otherMenu addItemWithTitle:@"Calculate Length" + action:@selector(_menu_callback:) + keyEquivalent:@"p"]; + [menuItem setKeyEquivalentModifierMask:(NSFunctionKeyMask|NSControlKeyMask)]; + [menuItem setRepresentedObject: @"calc_length"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Sample menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"Samples"]; + menuItem = [otherMenu addItemWithTitle:@"Sample List" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(3)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"sample_page"]; + menuItem = [otherMenu addItemWithTitle:@"Sample Library" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(3)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"sample_library"]; + menuItem = [otherMenu addItemWithTitle:@"Reload Soundcard" + action:@selector(_menu_callback:) + keyEquivalent:@"g"]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"init_sound"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Instrument menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"Instruments"]; + menuItem = [otherMenu addItemWithTitle:@"Instrument List" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(4)]; + [menuItem setKeyEquivalentModifierMask:0]; + [menuItem setRepresentedObject: @"inst_page"]; + menuItem = [otherMenu addItemWithTitle:@"Instrument Library" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(4)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"inst_library"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Settings menu */ + otherMenu = [[NSMenu alloc] initWithTitle:@"Settings"]; + menuItem = [otherMenu addItemWithTitle:@"Preferences" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(5)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"preferences"]; + menuItem = [otherMenu addItemWithTitle:@"MIDI Configuration" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(1)]; + [menuItem setKeyEquivalentModifierMask:NSShiftKeyMask]; + [menuItem setRepresentedObject: @"midi_config"]; + menuItem = [otherMenu addItemWithTitle:@"Palette Editor" + action:@selector(_menu_callback:) + keyEquivalent:KEQ_FN(12)]; + [menuItem setKeyEquivalentModifierMask:NSControlKeyMask]; + [menuItem setRepresentedObject: @"palette_page"]; + menuItem = [otherMenu addItemWithTitle:@"Toggle Fullscreen" + action:@selector(_menu_callback:) + keyEquivalent:@"\r"]; + [menuItem setKeyEquivalentModifierMask:(NSControlKeyMask|NSCommandKeyMask)]; + [menuItem setRepresentedObject: @"fullscreen"]; + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:otherMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + + /* Finally give up our references to the objects */ + [appleMenu release]; + [menuItem release]; +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ + [windowMenu release]; + [windowMenuItem release]; +} + +/* Replacement for NSApplicationMain */ +static void CustomApplicationMain (argc, argv) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + SDLMain *sdlMain; + + /* Ensure the application object is initialised */ + [SDLApplication sharedApplication]; + +#ifdef SDL_USE_CPS + { + CPSProcessSerNum PSN; + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [SDLApplication sharedApplication]; + } +#endif /* SDL_USE_CPS */ + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create SDLMain and make it the app delegate */ + sdlMain = [[SDLMain alloc] init]; + [NSApp setDelegate:sdlMain]; + + /* Start the main event loop */ + [NSApp run]; + + [sdlMain release]; + [pool release]; +} + +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + int status; + + /* Set the working directory to the .app's parent directory */ + [self setupWorkingDirectory:gFinderLaunch]; + + /* Hand off to main application code */ + status = SDL_main (gArgc, gArgv); + + /* We're done, thank you for playing */ + exit(status); +} +@end + + +@implementation NSString (ReplaceSubString) + +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString +{ + unsigned int bufferSize; + unsigned int selfLen = [self length]; + unsigned int aStringLen = [aString length]; + unichar *buffer; + NSRange localRange; + NSString *result; + + bufferSize = selfLen + aStringLen - aRange.length; + buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar)); + + /* Get first part into buffer */ + localRange.location = 0; + localRange.length = aRange.location; + [self getCharacters:buffer range:localRange]; + + /* Get middle part into buffer */ + localRange.location = 0; + localRange.length = aStringLen; + [aString getCharacters:(buffer+aRange.location) range:localRange]; + + /* Get last part into buffer */ + localRange.location = aRange.location + aRange.length; + localRange.length = selfLen - localRange.location; + [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; + + /* Build output string */ + result = [NSString stringWithCharacters:buffer length:bufferSize]; + + NSDeallocateMemoryPages(buffer, bufferSize); + + return result; +} + +@end + + + +#ifdef main +# undef main +#endif + + +/* Main entry point to executable - should *not* be SDL_main! */ +int main (int argc, char **argv) +{ + /* Copy the arguments into a global variable */ + /* This is passed if we are launched by double-clicking */ + if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { + gArgc = 1; + gFinderLaunch = YES; + macosx_did_finderlaunch = 1; + } else { + gArgc = argc; + gFinderLaunch = NO; + macosx_did_finderlaunch = 0; + } + gArgv = argv; + + CustomApplicationMain (argc, argv); + + return 0; +} + +/* these routines provide clipboard encapsulation */ +const char *macosx_clippy_get(void) +{ + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + NSString *type = [pb availableTypeFromArray:[NSArray + arrayWithObject:NSStringPboardType]]; + NSString *contents; + const char *po; + + if (type == nil) return ""; + + contents = [pb stringForType:type]; + if (contents == nil) return ""; + po = [contents UTF8String]; + if (!po) return ""; + return po; +} +void macosx_clippy_put(const char *buf) +{ + NSString *contents = [NSString stringwithUTF8String:buf]; + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; + [pb setString:contents forType:NSStringPboardType]; +} + diff --git a/sys/macosx/midi-macosx.c b/sys/macosx/midi-macosx.c new file mode 100644 index 000000000..a8700edb3 --- /dev/null +++ b/sys/macosx/midi-macosx.c @@ -0,0 +1,209 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "midi.h" + +#include "mixer.h" +#include "util.h" + +#ifdef MACOSX + +#include +#include +#include + +static MIDIClientRef client = NULL; +static MIDIPortRef portIn = NULL; +static MIDIPortRef portOut = NULL; + +static int max_outputs = 0; +static int max_inputs = 0; + +struct macosx_midi { + char *name; + MIDIEndpointRef ep; + unsigned char packet[1024]; + MIDIPacketList *pl; + MIDIPacket *x; +}; + +static void readProc(const MIDIPacketList *np, UNUSED void *rc, void *crc) +{ + struct midi_port *p; + struct macosx_midi *m; + MIDIPacket *x; + int i; + + p = (struct midi_port *)crc; + m = (struct macosx_midi *)p->userdata; + + x = (MIDIPacket*)&np->packet[0]; + for (i = 0; i < np->numPackets; i++) { + midi_received_cb(p, x->data, x->length); + x = MIDIPacketNext(x); + } +} +static void _macosx_send(struct midi_port *p, unsigned char *data, + unsigned int len, unsigned int delay) +{ + struct macosx_midi *m; + + m = (struct macosx_midi *)p->userdata; + if (!m->x) { + m->x = MIDIPacketListInit(m->pl); + } + + m->x = MIDIPacketListAdd(m->pl, sizeof(m->packet), + m->x, (MIDITimeStamp)AudioConvertNanosToHostTime( + AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) + (1000000*delay)), /* msec to nsec? */ + len, data); + MIDISend(portOut, m->ep, m->pl); + m->x = NULL; +} + +/* lifted from portmidi */ +static char *get_ep_name(MIDIEndpointRef ep) +{ + MIDIEntityRef entity; + MIDIDeviceRef device; + CFStringRef endpointName = NULL, deviceName = NULL, fullName = NULL; + CFStringEncoding defaultEncoding; + char* newName; + + /* get the default string encoding */ + defaultEncoding = CFStringGetSystemEncoding(); + + /* get the entity and device info */ + MIDIEndpointGetEntity(ep, &entity); + MIDIEntityGetDevice(entity, &device); + + /* create the nicely formated name */ + MIDIObjectGetStringProperty(ep, kMIDIPropertyName, &endpointName); + MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName); + if (deviceName != NULL) { + fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"), + deviceName, endpointName); + } else { + fullName = endpointName; + } + + /* copy the string into our buffer */ + newName = (char*)mem_alloc(CFStringGetLength(fullName) + 1); + CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1, + defaultEncoding); + + /* clean up */ + if (endpointName) CFRelease(endpointName); + if (deviceName) CFRelease(deviceName); + if (fullName) CFRelease(fullName); + + return newName; +} + +static int _macosx_start(struct midi_port *p) +{ + struct macosx_midi *m; + m = (struct macosx_midi *)p->userdata; + + if (p->io & MIDI_INPUT + && MIDIPortConnectSource(portIn, m->ep, (void*)p) != noErr) { + return 0; + } + + if (p->io & MIDI_OUTPUT) { + m->pl = (MIDIPacketList*)m->packet; + m->x = NULL; + } + return 1; +} +static int _macosx_stop(struct midi_port *p) +{ + struct macosx_midi *m; + m = (struct macosx_midi *)p->userdata; + if (p->io & MIDI_INPUT + && MIDIPortDisconnectSource(portIn, m->ep) != noErr) { + return 0; + } + return 1; +} + +static void _macosx_poll(struct midi_provider *p) +{ + struct macosx_midi *data; + MIDIEndpointRef ep; + int i; + + int num_out, num_in; + + num_out = MIDIGetNumberOfDestinations(); + num_in = MIDIGetNumberOfSources(); + + for (i = max_outputs; i < num_out; i++) { + ep = MIDIGetDestination(i); + if (!ep) continue; + data = mem_alloc(sizeof(struct macosx_midi)); + memcpy(&data->ep, &ep, sizeof(ep)); + data->name = get_ep_name(ep); + midi_port_register(p, MIDI_OUTPUT, data->name, data, 1); + } + max_outputs = i; + + + for (i = max_inputs; i < num_in; i++) { + ep = MIDIGetSource(i); + if (!ep) continue; + data = mem_alloc(sizeof(struct macosx_midi)); + memcpy(&data->ep, &ep, sizeof(ep)); + data->name = get_ep_name(ep); + midi_port_register(p, MIDI_INPUT, data->name, data, 1); + } + max_inputs = i; + +} + +int macosx_midi_setup(void) +{ + struct midi_driver driver; + + driver.flags = MIDI_PORT_CAN_SCHEDULE; + driver.poll = _macosx_poll; + driver.thread = NULL; + driver.enable = _macosx_start; + driver.disable = _macosx_stop; + driver.send = _macosx_send; + + if (MIDIClientCreate(CFSTR("Schism Tracker"), NULL, NULL, &client) != noErr) { + return; + } + if (MIDIInputPortCreate(client, CFSTR("Input port"), readProc, NULL, &portIn) != noErr) { + return; + } + if (MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut) != noErr) { + return; + } + + if (!midi_provider_register("Mac OS X", &driver)) return 0; + + return 1; +} + +#endif diff --git a/sys/oss/midi-oss.c b/sys/oss/midi-oss.c new file mode 100644 index 000000000..4269a70ba --- /dev/null +++ b/sys/oss/midi-oss.c @@ -0,0 +1,173 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "headers.h" + +#include "midi.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_OSS + +#include +#include +#include +#include +#include + +#include +#include + +/* this is stupid; oss doesn't have a concept of ports... */ +#define MAX_OSS_MIDI 64 + +#define MAX_MIDI_PORTS MAX_OSS_MIDI+2 +static int opened[MAX_MIDI_PORTS]; + + +static void _oss_send(struct midi_port *p, unsigned char *data, unsigned int len, UNUSED unsigned int delay) +{ + int fd, r, n; + if (!p->userdata) return; + fd = opened[ n = (*(int*)p->userdata) ]; + if (fd < 0) return; + while (len > 0) { + r = write(fd, data, len); + if (r < -1 && errno == EINTR) continue; + if (r < 1) { + /* err, can't happen? */ + (void)close(opened[n]); + opened[n] = -1; + p->userdata = 0; + p->io = 0; /* failure! */ + return; + } + data += r; + len -= r; + } +} + +static int _oss_start(UNUSED struct midi_port *p) { return 1; /* do nothing */ } +static int _oss_stop(UNUSED struct midi_port *p) { return 1; /* do nothing */ } + + +static int _oss_thread(struct midi_provider *p) +{ + struct pollfd pfd[MAX_MIDI_PORTS]; + struct midi_port *ptr, *src; + unsigned char midi_buf[4096]; + int i, j, r; + + for (;;) { + ptr = 0; + j = 0; + while (midi_port_foreach(p, &ptr)) { + if (!ptr->userdata) continue; + i = *(int *)ptr->userdata; + if (i == -1) continue; /* err... */ + if (!(ptr->io & MIDI_INPUT)) continue; + pfd[j].fd = i; + pfd[j].events = POLLIN; + pfd[j].revents = 0; /* RH 5 bug */ + j++; + } + if (!j || poll(pfd, j, -1) < 1) { + sleep(1); + continue; + } + for (i = 0; i < j; i++) { + if (!(pfd[i].revents & POLLIN)) continue; + do { + r = read(pfd[i].fd, midi_buf, sizeof(midi_buf)); + } while (r == -1 && errno == EINTR); + if (r > 0) { + ptr = src = 0; + while (midi_port_foreach(p, &ptr)) { + if (!ptr->userdata) continue; + if (*(int *)ptr->userdata == pfd[i].fd) { + src = ptr; + } + } + midi_received_cb(src, midi_buf, r); + } + } + } +} + + +static void _tryopen(int n, char *name, struct midi_provider *_oss_provider) +{ + int io; + char *ptr; + + if (opened[n+1] != -1) return; + opened[n+1] = open(name, O_RDWR|O_NOCTTY); + if (opened[n+1] == -1) { + opened[n+1] = open(name, O_RDONLY|O_NOCTTY); + if (opened[n+1] == -1) { + opened[n+1] = open(name, O_WRONLY|O_NOCTTY); + if (opened[n+1] == -1) return; + io = MIDI_OUTPUT; + } else { + io = MIDI_INPUT; + } + } else { + io = MIDI_INPUT | MIDI_OUTPUT; + } + + ptr = 0; + asprintf(&ptr, " %-16s (OSS)", name); + midi_port_register(_oss_provider, io, ptr, (void*)&opened[n+1], 0); +} + +static void _oss_poll(struct midi_provider *_oss_provider) +{ + struct midi_port *ptr; + char sbuf[64]; + int i; + + _tryopen(-1, "/dev/midi", _oss_provider); + for (i = 0; i < MAX_OSS_MIDI; i++) { + sprintf(sbuf, "/dev/midi%d", i); + _tryopen(i, sbuf, _oss_provider); + + sprintf(sbuf, "/dev/midi%02d", i); + _tryopen(i, sbuf, _oss_provider); + } +} +int oss_midi_setup(void) +{ + struct midi_driver driver; + int i; + + driver.flags = 0; + driver.poll = _oss_poll; + driver.thread = _oss_thread; + driver.enable = _oss_start; + driver.disable = _oss_stop; + driver.send = _oss_send; + + for (i = 0; i < MAX_MIDI_PORTS; i++) opened[i] = -1; + if (!midi_provider_register("OSS", &driver)) return 0; + return 1; +} +#endif diff --git a/sys/oss/mixer-oss.c b/sys/oss/mixer-oss.c new file mode 100644 index 000000000..42844747e --- /dev/null +++ b/sys/oss/mixer-oss.c @@ -0,0 +1,120 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_OSS + +#include +#include +#include +#include + +#include +#include + +/* Hmm. I've found that some systems actually don't support the idea of a + * "master" volume, so this became necessary. I suppose PCM is really the + * more useful setting to change anyway. */ +#if 0 +# define SCHISM_MIXER_CONTROL SOUND_MIXER_VOLUME +#else +# define SCHISM_MIXER_CONTROL SOUND_MIXER_PCM +#endif + +#define VOLUME_MAX 100 + +/* --------------------------------------------------------------------- */ + +static const char *device_file = NULL; + +/* --------------------------------------------------------------------- */ + +static int open_mixer_device(void) +{ + const char *ptr; + + if (!device_file) { + ptr = "/dev/sound/mixer"; + if (access(ptr, F_OK) < 0) { + /* this had better work :) */ + ptr = "/dev/mixer"; + } + device_file = ptr; + } + + return open(device_file, O_RDWR); +} + +/* --------------------------------------------------------------------- */ + +int oss_mixer_get_max_volume(void) +{ + return VOLUME_MAX; +} + +void oss_mixer_read_volume(int *left, int *right) +{ + int fd; + byte volume[4]; + + fd = open_mixer_device(); + if (fd < 0) { + perror(device_file); + *left = *right = 0; + return; + } + + if (ioctl(fd, MIXER_READ(SCHISM_MIXER_CONTROL), volume) == EOF) { + perror(device_file); + *left = *right = 0; + } else { + *left = volume[0]; + *right = volume[1]; + } + + close(fd); +} + +void oss_mixer_write_volume(int left, int right) +{ + int fd; + byte volume[4]; + + volume[0] = CLAMP(left, 0, VOLUME_MAX); + volume[1] = CLAMP(right, 0, VOLUME_MAX); + + fd = open_mixer_device(); + if (fd < 0) { + perror(device_file); + return; + } + + if (ioctl(fd, MIXER_WRITE(SCHISM_MIXER_CONTROL), volume) == EOF) { + perror(device_file); + } + + close(fd); +} + +#endif diff --git a/sys/posix/slurp-mmap.c b/sys/posix/slurp-mmap.c new file mode 100644 index 000000000..cfec4bb49 --- /dev/null +++ b/sys/posix/slurp-mmap.c @@ -0,0 +1,58 @@ +/* + * slurp - mmap isolation + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if HAVE_MMAP +#include +#include +#include +#include + +#include "slurp.h" + +static void _munmap_slurp(slurp_t *useme) +{ + (void)munmap((void*)useme->data, useme->length); + (void)close(useme->extra); +} + +int slurp_mmap(slurp_t *useme, const char *filename, size_t st) +{ + int fd; + void *addr; + + fd = open(filename, O_RDONLY); + if (fd == -1) return 0; + addr = mmap(0, st, PROT_READ, MAP_SHARED, fd, 0); + if (!addr || addr == ((void*)-1)) { + (void)close(fd); + return -1; + } + useme->closure = _munmap_slurp; + useme->length = st; + useme->data = addr; + useme->extra = fd; + return 1; +} + +#endif diff --git a/sys/sdl/README b/sys/sdl/README new file mode 100644 index 000000000..6e22655c4 --- /dev/null +++ b/sys/sdl/README @@ -0,0 +1,2 @@ +this directory will eventually have SDL-specific stuff in it. +right now, schism is SDL specific :) diff --git a/sys/win32/midi-win32mm.c b/sys/win32/midi-win32mm.c new file mode 100644 index 000000000..b8aec98a0 --- /dev/null +++ b/sys/win32/midi-win32mm.c @@ -0,0 +1,302 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "midi.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_WIN32MM + +#include +#include +#include + +struct win32mm_midi { + DWORD id; + + HMIDIOUT out; + HMIDIIN in; + + MIDIINCAPS icp; + MIDIOUTCAPS ocp; + + MIDIHDR hh; + LPMIDIHDR obuf; + unsigned char sysx[1024]; +}; +static unsigned int last_known_in_port = 0; +static unsigned int last_known_out_port = 0; + +static MMRESULT (*XP_timeSetEvent)(UINT u,UINT r,LPTIMECALLBACK proc, + DWORD_PTR user, UINT flags) = 0; + +static void _win32mm_sysex(LPMIDIHDR *q, unsigned char *d, unsigned int len) +{ + unsigned char *z; + LPMIDIHDR m; + + z = mem_alloc(sizeof(MIDIHDR) + len + 8); + m = (LPMIDIHDR)z; + + memset(m, 0, sizeof(MIDIHDR)); + if (d) memcpy(z + sizeof(MIDIHDR), d, len); + + m->lpData = (z+sizeof(MIDIHDR)); + m->dwBufferLength = len+8; + m->dwBytesRecorded = (d ? len : 0); + m->lpNext = *q; + m->dwOffset = 0; + (*q) = (m); +} + +static CALLBACK void _win32mm_xp_output(UNUSED UINT uTimerID, + UNUSED UINT uMsg, + DWORD_PTR dwUser, + DWORD_PTR dw1, + DWORD_PTR dw2) +{ + LPMIDIHDR x; + HMIDIOUT out; + + x = (LPMIDIHDR)dwUser; + out = (HMIDIOUT)x->dwUser; + + if (midiOutPrepareHeader(out, x, sizeof(MIDIHDR)) == MMSYSERR_NOERROR) { + (void)midiOutLongMsg(out, x, sizeof(MIDIHDR)); + } +} +static void _win32mm_send_xp(struct midi_port *p, unsigned char *data, + unsigned int len, unsigned int delay) +{ + /* version for windows XP */ + struct win32mm_midi *m; + + if (len == 0) return; + m = p->userdata; + + _win32mm_sysex(&m->obuf, data, len); + m->obuf->dwUser = (DWORD_PTR)m->out; + if (XP_timeSetEvent(delay, 0, _win32mm_xp_output, (DWORD_PTR)m->obuf, + TIME_ONESHOT | TIME_CALLBACK_FUNCTION) == NULL) { + /* slow... */ + if (midiOutPrepareHeader(m->out, m->obuf, sizeof(MIDIHDR)) == MMSYSERR_NOERROR) { + (void)midiOutLongMsg(m->out, m->obuf, sizeof(MIDIHDR)); + } + return; + } + /* will happen... */ +} + +static void _win32mm_send(struct midi_port *p, unsigned char *data, + unsigned int len, UNUSED unsigned int delay) +{ + struct win32mm_midi *m; + DWORD q; + + if (len == 0) return; + + m = p->userdata; + if (len <= 4) { + q = data[0]; + if (len > 1) q |= (data[1] << 8); + if (len > 2) q |= (data[2] << 16); + if (len > 3) q |= (data[3] << 24); /* eh... */ + (void)midiOutShortMsg(m->out, q); + } else { + /* SysEX */ + _win32mm_sysex(&m->obuf, data, len); + if (midiOutPrepareHeader(m->out, m->obuf, sizeof(MIDIHDR)) == MMSYSERR_NOERROR) { + (void)midiOutLongMsg(m->out, m->obuf, sizeof(MIDIHDR)); + } + } +} + + +static CALLBACK void _win32mm_inputcb(HMIDIIN in, UINT wmsg, DWORD inst, + DWORD param1, DWORD param2) +{ + struct midi_port *p = (struct midi_port *)inst; + struct win32mm_midi *m; + unsigned char c[4]; + + switch (wmsg) { + case MIM_OPEN: + SDL_Delay(0); /* eh? */ + case MIM_CLOSE: + break; + case MIM_DATA: + c[0] = param1 & 255; + c[1] = (param1 >> 8) & 255; + c[2] = (param1 >> 16) & 255; + midi_received_cb(p, c, 3); + break; + case MIM_LONGDATA: + /* long data */ + m = p->userdata; + midi_received_cb(p, m->hh.lpData, m->hh.dwBytesRecorded); + m->hh.dwBytesRecorded = 0; + (void)midiInAddBuffer(m->in, &m->hh, sizeof(MIDIHDR)); + break; + } +} + + +static int _win32mm_start(struct midi_port *p) +{ + struct win32mm_midi *m; + UINT id; + WORD r; + + m = p->userdata; + id = m->id; + if (p->io == MIDI_INPUT) { + m->in = NULL; + r = midiInOpen(&m->in, + (UINT_PTR)id, + (DWORD_PTR)_win32mm_inputcb, + (DWORD_PTR)p, + CALLBACK_FUNCTION); + if (r != MMSYSERR_NOERROR) return 0; + memset(&m->hh, 0, sizeof(m->hh)); + m->hh.lpData = (LPSTR)m->sysx; + m->hh.dwBufferLength = sizeof(m->sysx); + m->hh.dwFlags = 0; + r = midiInPrepareHeader(m->in, &m->hh, sizeof(MIDIHDR)); + if (r != MMSYSERR_NOERROR) return 0; + r = midiInAddBuffer(m->in, &m->hh, sizeof(MIDIHDR)); + if (r != MMSYSERR_NOERROR) return 0; + if (midiInStart(m->in) != MMSYSERR_NOERROR) return 0; + + } + if (p->io & MIDI_OUTPUT) { + m->out = NULL; + if (midiOutOpen(&m->out, + (UINT_PTR)id, + 0, 0, + CALLBACK_NULL) != MMSYSERR_NOERROR) return 0; + } + return 1; +} +static int _win32mm_stop(struct midi_port *p) +{ + struct win32mm_midi *m; + LPMIDIHDR ptr; + WORD r; + + m = p->userdata; + if (p->io & MIDI_INPUT) { + /* portmidi appears to (essentially) ignore the error codes + for these guys */ + (void)midiInStop(m->in); + (void)midiInReset(m->in); + (void)midiInClose(m->in); + } + if (p->io & MIDI_OUTPUT) { + (void)midiOutReset(m->out); + (void)midiOutClose(m->out); + /* free output chain */ + ptr = m->obuf; + while (ptr) { + m->obuf = m->obuf->lpNext; + (void)free(m->obuf); + ptr = m->obuf; + } + } + return 1; +} + + +static void _win32mm_poll(struct midi_provider *p) +{ + struct midi_port *ptr; + struct win32mm_midi *data; + + UINT i; + UINT mmin, mmout; + WORD r; + + mmin = midiInGetNumDevs(); + for (i = last_known_in_port; i < mmin; i++) { + data = mem_alloc(sizeof(struct win32mm_midi)); + memset(data,0,sizeof(struct win32mm_midi)); + r = midiInGetDevCaps(i, (LPMIDIINCAPS)&data->icp, + sizeof(MIDIINCAPS)); + if (r != MMSYSERR_NOERROR) { + free(data); + continue; + } + data->id = i; + midi_port_register(p, MIDI_INPUT, data->icp.szPname, data, 1); + } + last_known_in_port = mmin; + + mmout = midiOutGetNumDevs(); + for (i = last_known_out_port; i < mmout; i++) { + data = mem_alloc(sizeof(struct win32mm_midi)); + memset(data,0,sizeof(struct win32mm_midi)); + r = midiOutGetDevCaps(i, (LPMIDIOUTCAPS)&data->ocp, + sizeof(MIDIOUTCAPS)); + if (r != MMSYSERR_NOERROR) { + if (data) free(data); + continue; + } + data->id = i; + midi_port_register(p, MIDI_OUTPUT, data->ocp.szPname, data, 1); + } + last_known_out_port = mmout; +} + +int win32mm_midi_setup(void) +{ + struct midi_driver driver; + HINSTANCE winmm; + + driver.flags = 0; + driver.poll = _win32mm_poll; + driver.thread = NULL; + driver.enable = _win32mm_start; + driver.disable = _win32mm_stop; + + winmm = GetModuleHandle("WINMM.DLL"); + if (!winmm) winmm = LoadLibrary("WINMM.DLL"); + if (!winmm) winmm = GetModuleHandle("WINMM.DLL"); + if (winmm) { + XP_timeSetEvent = (void*)GetProcAddress(winmm,"timeSetEvent"); + if (XP_timeSetEvent) { + driver.send = _win32mm_send_xp; + driver.flags |= MIDI_PORT_CAN_SCHEDULE; + } else { + driver.send = _win32mm_send; + } + } else { + XP_timeSetEvent = 0; + driver.send = _win32mm_send; + } + + if (!midi_provider_register("Win32MM", &driver)) return 0; + + return 1; +} + + +#endif diff --git a/sys/win32/mixer-win32mm.c b/sys/win32/mixer-win32mm.c new file mode 100644 index 000000000..cc2d7f976 --- /dev/null +++ b/sys/win32/mixer-win32mm.c @@ -0,0 +1,95 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "headers.h" + +#include "mixer.h" +#include "util.h" + +#ifdef USE_WIN32MM + +#include +#include + +/* Note: [Gargaj] + + WinMM DOES support max volumes up to 65535, but the scroller is + so goddamn slow and it only supports 3 digits anyway, that + it doesn't make any sense to keep the precision. +*/ + +int win32mm_mixer_get_max_volume(void) +{ + return 0xFF; +} + +static HWAVEOUT open_mixer() +{ + HWAVEOUT hwo=NULL; + WAVEFORMATEX pwfx; +#if 0 + pwfx.wFormatTag = WAVE_FORMAT_UNKNOWN; + pwfx.nChannels = 0; + pwfx.nSamplesPerSec = 0; + pwfx.wBitsPerSample = 0; + pwfx.nBlockAlign = 0; + pwfx.nAvgBytesPerSec = 0; + pwfx.cbSize = 0; +#else + pwfx.wFormatTag = WAVE_FORMAT_PCM; + pwfx.nChannels = 1; + pwfx.nSamplesPerSec = 44100; + pwfx.wBitsPerSample = 8; + pwfx.nBlockAlign = 4; + pwfx.nAvgBytesPerSec = 44100*1*1; + pwfx.cbSize = 0; +#endif + if (waveOutOpen(&hwo, WAVE_MAPPER, &pwfx, 0, 0, CALLBACK_NULL)!=MMSYSERR_NOERROR) + return NULL; + return hwo; +} + +void win32mm_mixer_read_volume(int *left, int *right) +{ + DWORD vol; + HWAVEOUT hwo=open_mixer(); + + *left = *right = 0; + if (!hwo) return; + + waveOutGetVolume(hwo,&vol); + + *left = (vol & 0xFFFF) >> 8; + *right = (vol >> 16) >> 8; + + waveOutClose(hwo); +} + +void win32mm_mixer_write_volume(int left, int right) +{ + DWORD vol = ((left & 0xFF)<<8) | ((right & 0xFF)<<(16+8)); + HWAVEOUT hwo = open_mixer(); + if (!hwo) return; + + waveOutSetVolume(hwo,vol); + + waveOutClose(hwo); +} +#endif diff --git a/sys/win32/schismres.rc b/sys/win32/schismres.rc new file mode 100644 index 000000000..830fcfa33 --- /dev/null +++ b/sys/win32/schismres.rc @@ -0,0 +1 @@ +schismicon ICON "icons/schismres.ico" diff --git a/sys/win32/slurp-win32.c b/sys/win32/slurp-win32.c new file mode 100644 index 000000000..a4b4bcb07 --- /dev/null +++ b/sys/win32/slurp-win32.c @@ -0,0 +1,84 @@ +/* + * slurp - win32 isolation + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef WIN32 +#include + +#include + +#include "slurp.h" + +static void _win32_unmap(slurp_t *useme) +{ + HANDLE *bp; + (void)UnmapViewOfFile((LPVOID)useme->data); + + bp = (HANDLE*)useme->bextra; + CloseHandle(bp[0]); + CloseHandle(bp[1]); + free(bp); +} +int slurp_win32(slurp_t *useme, const char *filename, size_t st) +{ + HANDLE h, m, *bp; + LPVOID addr; + SECURITY_ATTRIBUTES sa; + + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = 0; + + bp = (HANDLE*)mem_alloc(sizeof(HANDLE)*2); + + h = CreateFile(filename, GENERIC_READ, + FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (!h) { + log_appendf(4, "CreateFile(%s) failed with %d", filename, + GetLastError()); + free(bp); + return 0; + } + + m = CreateFileMapping(h, NULL, PAGE_READONLY, 0, 0, NULL); + if (!h) { + log_appendf(4, "CreateFileMapping failed with %d", GetLastError()); + CloseHandle(h); + free(bp); + return -1; + } + addr = MapViewOfFile(m, FILE_MAP_READ, 0, 0, 0); + if (!addr) { + log_appendf(4, "MapViewOfFile failed with %d", GetLastError()); + CloseHandle(m); + CloseHandle(h); + free(bp); + return -1; + } + useme->data = addr; + useme->length = st; + useme->closure = _win32_unmap; + + bp[0] = m; bp[1] = h; + useme->bextra = bp; + return 1; +} + +#endif diff --git a/sys/x11/xscreensaver.c b/sys/x11/xscreensaver.c new file mode 100644 index 000000000..b0ec90516 --- /dev/null +++ b/sys/x11/xscreensaver.c @@ -0,0 +1,162 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 chisel + * URL: http://rigelseven.com/schism/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* I wanted to just lift this code out of xscreensaver-command, + but that wasn't really convenient. xscreensaver really should've + had this (or something like it) as a simple library call like: + xscreensaver_synthetic_user_active(display); + or something like that. spawning a subprocess is really expensive on + some systems, and might have subtle interactions with SDL or the player. + + -mrsb +*/ + +#include "headers.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +static XErrorHandler old_handler = 0; +static int BadWindow_ehandler(Display *dpy, XErrorEvent *error) +{ + if (error->error_code == BadWindow) { + return 0; + } else { + if (old_handler) return (*old_handler)(dpy,error); + /* shrug */ + return 1; + } +} + +void xscreensaver_deactivate(void) +{ + static Atom XA_SCREENSAVER_VERSION; + static Atom XA_DEACTIVATE; + static Atom XA_SCREENSAVER; + static int setup = 0; + static int useit = 0; + static SDL_SysWMinfo info; + + Window root, tmp, parent, *kids; + unsigned int nkids, i; + + static time_t lastpoll = 0; + Window win; + time_t now; + + Display *dpy = 0; + XEvent ev; + + if (!setup) { + setup = 1; + useit = 1; + SDL_GetWMInfo(&info); + + info.info.x11.lock_func(); + dpy = info.info.x11.display; + XA_SCREENSAVER = XInternAtom(dpy, "SCREENSAVER", False); + XA_SCREENSAVER_VERSION = XInternAtom(dpy, + "_SCREENSAVER_VERSION", False); + XA_DEACTIVATE = XInternAtom(dpy, "DEACTIVATE", False); + info.info.x11.unlock_func(); + } + + if (!useit) return; + + time(&now); + if (!(lastpoll - now)) { + return; + } + lastpoll = now; + + info.info.x11.lock_func(); + dpy = info.info.x11.display; + if (!dpy) { + useit = 0; + info.info.x11.unlock_func(); + return; + } + + root = RootWindowOfScreen(DefaultScreenOfDisplay(dpy)); + if (!XQueryTree(dpy, root, &tmp, &parent, &kids, &nkids)) { + useit = 0; + info.info.x11.unlock_func(); + return; + } + if (root != tmp || parent || !(kids && nkids)) { + useit = 0; + info.info.x11.unlock_func(); + return; + } + + win = 0; + for (i = 0; i < nkids; i++) { + Atom type; + int format; + unsigned long nitems, bytesafter; + char *v = 0; + int status; + + XSync(dpy, False); + old_handler = XSetErrorHandler(BadWindow_ehandler); + if (XGetWindowProperty(dpy, kids[i], + XA_SCREENSAVER_VERSION, 0, 200, + False, XA_STRING, &type, &format, + &nitems, &bytesafter, + (unsigned char **)&v) == Success) { + XSetErrorHandler(old_handler); + if (v) XFree(v); /* don't care */ + if (type != None) { + win = kids[i]; + break; + } + } + XSetErrorHandler(old_handler); + } + XFree(kids); + if (!win) { + useit = 0; + info.info.x11.unlock_func(); + return; + } + + ev.xany.type = ClientMessage; + ev.xclient.display = dpy; + ev.xclient.window = win; + ev.xclient.message_type = XA_SCREENSAVER; + ev.xclient.format = 32; + memset(&ev.xclient.data, 0, sizeof(ev.xclient.data)); + ev.xclient.data.l[0] = XA_DEACTIVATE; + ev.xclient.data.l[1] = 0; + ev.xclient.data.l[2] = 0; + (void)XSendEvent(dpy, win, False, 0L, &ev); + XSync(dpy, 0); + info.info.x11.unlock_func(); +} +